summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/mailman/database/listmanager.py6
-rw-r--r--src/mailman/docs/listmanager.txt13
-rw-r--r--src/mailman/interfaces/domain.py8
-rw-r--r--src/mailman/interfaces/listmanager.py14
-rw-r--r--src/mailman/interfaces/mailinglist.py39
-rw-r--r--src/mailman/rest/adapters.py3
-rw-r--r--src/mailman/rest/configure.zcml3
-rw-r--r--src/mailman/rest/docs/lists.txt12
-rw-r--r--src/mailman/rest/publication.py2
-rw-r--r--src/mailman/rest/testing/__init__.py0
-rw-r--r--src/mailman/rest/testing/server.py63
-rw-r--r--src/mailman/rest/urls.py6
-rw-r--r--src/mailman/rest/webservice.py3
-rw-r--r--src/mailman/testing/layers.py24
14 files changed, 106 insertions, 90 deletions
diff --git a/src/mailman/database/listmanager.py b/src/mailman/database/listmanager.py
index 790a2509a..887ec5e1f 100644
--- a/src/mailman/database/listmanager.py
+++ b/src/mailman/database/listmanager.py
@@ -40,6 +40,7 @@ class ListManager(object):
implements(IListManager)
+ # pylint: disable-msg=R0201
def create(self, fqdn_listname):
"""See `IListManager`."""
listname, hostname = fqdn_listname.split('@', 1)
@@ -80,3 +81,8 @@ class ListManager(object):
"""See `IListManager`."""
for mlist in config.db.store.find(MailingList):
yield '{0}@{1}'.format(mlist.list_name, mlist.host_name)
+
+ def get_mailing_lists(self):
+ """See `IListManager`."""
+ # lazr.restful will not allow this to be a generator.
+ return list(self.mailing_lists)
diff --git a/src/mailman/docs/listmanager.txt b/src/mailman/docs/listmanager.txt
index 830f6d962..c082dc001 100644
--- a/src/mailman/docs/listmanager.txt
+++ b/src/mailman/docs/listmanager.txt
@@ -1,5 +1,6 @@
-Using the IListManager interface
-================================
+========================
+The mailing list manager
+========================
The IListManager is how you create, delete, and retrieve mailing list
objects. The Mailman system instantiates an IListManager for you based on the
@@ -13,7 +14,7 @@ on the global config object.
Creating a mailing list
------------------------
+=======================
Creating the list returns the newly created IMailList object.
@@ -43,7 +44,7 @@ you will get an exception.
Deleting a mailing list
------------------------
+=======================
Use the list manager to delete a mailing list.
@@ -59,7 +60,7 @@ After deleting the list, you can create it again.
Retrieving a mailing list
--------------------------
+=========================
When a mailing list exists, you can ask the list manager for it and you will
always get the same object back.
@@ -75,7 +76,7 @@ If you try to get a list that doesn't existing yet, you get None.
Iterating over all mailing lists
---------------------------------
+================================
Once you've created a bunch of mailing lists, you can use the list manager to
iterate over either the list objects, or the list names.
diff --git a/src/mailman/interfaces/domain.py b/src/mailman/interfaces/domain.py
index 0692d21d5..1546f9487 100644
--- a/src/mailman/interfaces/domain.py
+++ b/src/mailman/interfaces/domain.py
@@ -97,5 +97,9 @@ class IDomainSet(Interface):
export_as_webservice_collection(IDomain)
@collection_default_content()
- def __iter__():
- """Iterate over all domains."""
+ def get_domains():
+ """The list of all domains.
+
+ :return: The list of all known domains.
+ :rtype: list of `IDomain`
+ """
diff --git a/src/mailman/interfaces/listmanager.py b/src/mailman/interfaces/listmanager.py
index e7cdd9da7..c8f49f447 100644
--- a/src/mailman/interfaces/listmanager.py
+++ b/src/mailman/interfaces/listmanager.py
@@ -26,8 +26,12 @@ __all__ = [
]
+from lazr.restful.declarations import (
+ collection_default_content, export_as_webservice_collection)
from zope.interface import Interface, Attribute
+
from mailman.interfaces.errors import MailmanError
+from mailman.interfaces.mailinglist import IMailingList
@@ -49,6 +53,8 @@ class IListManager(Interface):
`mylist@example.com`.
"""
+ export_as_webservice_collection(IMailingList)
+
def create(fqdn_listname):
"""Create a mailing list with the given name.
@@ -82,3 +88,11 @@ class IListManager(Interface):
names = Attribute(
"""An iterator over the fully qualified list names of all mailing
lists managed by this list manager.""")
+
+ @collection_default_content()
+ def get_mailing_lists():
+ """The list of all mailing lists.
+
+ :return: The list of all known mailing lists.
+ :rtype: list of `IMailingList`
+ """
diff --git a/src/mailman/interfaces/mailinglist.py b/src/mailman/interfaces/mailinglist.py
index e71a47d61..0717ab2e6 100644
--- a/src/mailman/interfaces/mailinglist.py
+++ b/src/mailman/interfaces/mailinglist.py
@@ -30,8 +30,13 @@ __all__ = [
]
+from lazr.restful.declarations import (
+ export_as_webservice_entry, exported)
from munepy import Enum
from zope.interface import Interface, Attribute
+from zope.schema import TextLine
+
+from mailman.i18n import _
@@ -66,31 +71,39 @@ class DigestFrequency(Enum):
class IMailingList(Interface):
"""A mailing list."""
+ export_as_webservice_entry()
+
# List identity
- list_name = Attribute(
- """The read-only short name of the mailing list. Note that where a
+ list_name = exported(TextLine(
+ title=_("The mailing list's short name"),
+ description=_("""\
+ The read-only short name of the mailing list. Note that where a
Mailman installation supports multiple domains, this short name may
not be unique. Use the fqdn_listname attribute for a guaranteed
unique id for the mailing list. This short name is always the local
part of the posting email address. For example, if messages are
posted to mylist@example.com, then the list_name is 'mylist'.
- """)
+ """)))
- host_name = Attribute(
- """The read-only domain name 'hosting' this mailing list. This is
- always the domain name part of the posting email address, and it may
- bear no relationship to the web url used to access this mailing list.
- For example, if messages are posted to mylist@example.com, then the
+ host_name = exported(TextLine(
+ title=_("The mailing list's host name"),
+ description=_("""\
+ The read-only domain name 'hosting' this mailing list. This is always
+ the domain name part of the posting email address, and it may bear no
+ relationship to the web url used to access this mailing list. For
+ example, if messages are posted to mylist@example.com, then the
host_name is 'example.com'.
- """)
+ """)))
- fqdn_listname = Attribute(
- """The read-only fully qualified name of the mailing list. This is
- the guaranteed unique id for the mailing list, and it is always the
+ fqdn_listname = exported(TextLine(
+ title=_("The mailing list's filly qualified name"),
+ description=_("""\
+ The read-only fully qualified name of the mailing list. This is the
+ guaranteed unique id for the mailing list, and it is always the
address to which messages are posted, e.g. mylist@example.com. It is
always comprised of the list_name + '@' + host_name.
- """)
+ """)))
real_name = Attribute(
"""The short human-readable descriptive name for the mailing list. By
diff --git a/src/mailman/rest/adapters.py b/src/mailman/rest/adapters.py
index b74028eaa..d5ae01498 100644
--- a/src/mailman/rest/adapters.py
+++ b/src/mailman/rest/adapters.py
@@ -43,8 +43,9 @@ class DomainSet:
def __init__(self, config):
self._config = config
- def __iter__(self):
+ def get_domains(self):
"""See `IDomainSet`."""
+ # lazr.restful will not allow this to be a generator.
domains = self._config.domains
return [domains[domain] for domain in sorted(domains)]
diff --git a/src/mailman/rest/configure.zcml b/src/mailman/rest/configure.zcml
index 5d0a7c08f..164bbd445 100644
--- a/src/mailman/rest/configure.zcml
+++ b/src/mailman/rest/configure.zcml
@@ -8,8 +8,9 @@
<include package="lazr.restful" file="meta.zcml"/>
<include package="lazr.restful" file="configure.zcml"/>
- <webservice:register module="mailman.interfaces.system" />
<webservice:register module="mailman.interfaces.domain" />
+ <webservice:register module="mailman.interfaces.listmanager" />
+ <webservice:register module="mailman.interfaces.system" />
<adapter
for="mailman.config.config.IConfiguration"
diff --git a/src/mailman/rest/docs/lists.txt b/src/mailman/rest/docs/lists.txt
new file mode 100644
index 000000000..5cdf05639
--- /dev/null
+++ b/src/mailman/rest/docs/lists.txt
@@ -0,0 +1,12 @@
+=============
+Mailing lists
+=============
+
+The REST API can be queried for the set of known mailing lists. There is a
+top level collection that can return all the mailing lists. There aren't any
+yet though.
+
+ >>> dump_json('http://localhost:8001/3.0/lists')
+ resource_type_link: https://localhost:8001/3.0/#mailing_lists
+ start: None
+ total_size: 0
diff --git a/src/mailman/rest/publication.py b/src/mailman/rest/publication.py
index a50976102..1b0e1ee00 100644
--- a/src/mailman/rest/publication.py
+++ b/src/mailman/rest/publication.py
@@ -41,6 +41,7 @@ from mailman.interfaces.rest import IResolvePathNames
class Publication:
"""Very simple implementation of `IPublication`."""
+
implements(IPublication)
def __init__(self, application):
@@ -94,6 +95,7 @@ class Publication:
def endRequest(self, request, ob):
"""Ends the interaction."""
+ config.db.commit()
endInteraction()
diff --git a/src/mailman/rest/testing/__init__.py b/src/mailman/rest/testing/__init__.py
deleted file mode 100644
index e69de29bb..000000000
--- a/src/mailman/rest/testing/__init__.py
+++ /dev/null
diff --git a/src/mailman/rest/testing/server.py b/src/mailman/rest/testing/server.py
deleted file mode 100644
index c4fe2f1ec..000000000
--- a/src/mailman/rest/testing/server.py
+++ /dev/null
@@ -1,63 +0,0 @@
-# Copyright (C) 2009 by the Free Software Foundation, Inc.
-#
-# This file is part of GNU Mailman.
-#
-# GNU Mailman is free software: you can redistribute it and/or modify it under
-# the terms of the GNU General Public License as published by the Free
-# Software Foundation, either version 3 of the License, or (at your option)
-# any later version.
-#
-# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT
-# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
-# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
-# more details.
-#
-# You should have received a copy of the GNU General Public License along with
-# GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
-
-"""A testable REST server."""
-
-from __future__ import absolute_import, unicode_literals
-
-__metaclass__ = type
-__all__ = [
- 'TestableServer',
- ]
-
-
-import logging
-import threading
-
-from urllib2 import urlopen
-
-from mailman.rest.webservice import make_server
-
-
-log = logging.getLogger('mailman.http')
-
-
-
-class TestableServer:
- """A REST server which polls for the stop action."""
-
- def __init__(self):
- self.server = make_server()
- self.event = threading.Event()
- self.thread = threading.Thread(target=self.loop)
- self.thread.daemon = True
-
- def start(self):
- """Start the server."""
- self.thread.start()
-
- def stop(self):
- """Stop the server by firing the event."""
- self.event.set()
- # Fire off one more request so the handle_request() will exit.
- fp = urlopen('http://localhost:8001/3.0/system')
- fp.close()
- self.thread.join()
-
- def loop(self):
- while not self.event.is_set():
- self.server.handle_request()
diff --git a/src/mailman/rest/urls.py b/src/mailman/rest/urls.py
index fe12a6ed8..38f676c7f 100644
--- a/src/mailman/rest/urls.py
+++ b/src/mailman/rest/urls.py
@@ -26,6 +26,8 @@ __all__ = [
]
+import logging
+
from zope.component import adapts
from zope.interface import implements, Interface
from zope.traversing.browser.interfaces import IAbsoluteURL
@@ -35,6 +37,8 @@ from mailman.core.system import system
from mailman.rest.configuration import AdminWebServiceConfiguration
from mailman.rest.webservice import AdminWebServiceApplication
+log = logging.getLogger('mailman.http')
+
class BasicURLMapper:
@@ -72,11 +76,13 @@ class FallbackURLMapper(BasicURLMapper):
:rtype: string
:raises KeyError: if no path component can be found.
"""
+ log.debug('generic url mapper lookup: %s', ob)
# Special cases.
if isinstance(ob, AdminWebServiceApplication):
return ''
urls = {
system: 'system',
+ #config.db.list_manager: 'lists',
}
return urls[ob]
diff --git a/src/mailman/rest/webservice.py b/src/mailman/rest/webservice.py
index a76ad3863..0469e442b 100644
--- a/src/mailman/rest/webservice.py
+++ b/src/mailman/rest/webservice.py
@@ -49,6 +49,7 @@ log = logging.getLogger('mailman.http')
+# pylint: disable-msg: W0232
class AdminWebServiceRequest(WebServiceRequestTraversal, BrowserRequest):
"""A request for the admin REST interface."""
@@ -81,9 +82,11 @@ class AdminWebServiceApplication:
def get(self, name):
"""Maps root names to resources."""
+ log.debug('Getting top level name: %s', name)
top_level = dict(
system=system,
domains=IDomainSet(config),
+ lists=config.db.list_manager,
)
return top_level.get(name)
diff --git a/src/mailman/testing/layers.py b/src/mailman/testing/layers.py
index 9e4364a7a..1765efc4e 100644
--- a/src/mailman/testing/layers.py
+++ b/src/mailman/testing/layers.py
@@ -32,20 +32,23 @@ import os
import sys
import shutil
import logging
+import datetime
import tempfile
from pkg_resources import resource_string
from textwrap import dedent
+from urllib2 import urlopen, URLError
from mailman.config import config
from mailman.core import initialize
from mailman.core.logging import get_handler
from mailman.i18n import _
-from mailman.testing.helpers import SMTPServer
+from mailman.testing.helpers import SMTPServer, TestableMaster
from mailman.utilities.datetime import factory
from mailman.utilities.string import expand
+TEST_TIMEOUT = datetime.timedelta(seconds=5)
NL = '\n'
@@ -230,12 +233,25 @@ class RESTLayer(SMTPLayer):
server = None
+ @staticmethod
+ def _wait_for_rest_server():
+ until = datetime.datetime.now() + TEST_TIMEOUT
+ while datetime.datetime.now() < until:
+ try:
+ fp = urlopen('http://localhost:8001/3.0/system')
+ except URLError:
+ pass
+ else:
+ fp.close()
+ break
+ else:
+ raise RuntimeError('REST server did not start up')
+
@classmethod
def setUp(cls):
assert cls.server is None, 'Layer already set up'
- from mailman.rest.testing.server import TestableServer
- cls.server = TestableServer()
- cls.server.start()
+ cls.server = TestableMaster(cls._wait_for_rest_server)
+ cls.server.start('rest')
@classmethod
def tearDown(cls):