diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/mailman/database/mailman.sql | 2 | ||||
| -rw-r--r-- | src/mailman/interfaces/domain.py | 3 | ||||
| -rw-r--r-- | src/mailman/interfaces/mailinglist.py | 53 | ||||
| -rw-r--r-- | src/mailman/model/domain.py | 6 | ||||
| -rw-r--r-- | src/mailman/model/mailinglist.py | 7 | ||||
| -rw-r--r-- | src/mailman/rest/docs/configuration.txt | 42 | ||||
| -rw-r--r-- | src/mailman/rest/helpers.py | 12 | ||||
| -rw-r--r-- | src/mailman/rest/lists.py | 71 | ||||
| -rw-r--r-- | src/mailman/styles/default.py | 2 |
9 files changed, 172 insertions, 26 deletions
diff --git a/src/mailman/database/mailman.sql b/src/mailman/database/mailman.sql index 84a906fb1..702f357be 100644 --- a/src/mailman/database/mailman.sql +++ b/src/mailman/database/mailman.sql @@ -97,7 +97,7 @@ CREATE TABLE mailinglist ( next_digest_number INTEGER, digest_last_sent_at TIMESTAMP, volume INTEGER, - last_post_time TIMESTAMP, + last_post_at TIMESTAMP, accept_these_nonmembers BLOB, acceptable_aliases_id INTEGER, admin_immed_notify BOOLEAN, diff --git a/src/mailman/interfaces/domain.py b/src/mailman/interfaces/domain.py index b7fc1c91f..25727ef22 100644 --- a/src/mailman/interfaces/domain.py +++ b/src/mailman/interfaces/domain.py @@ -55,6 +55,9 @@ class IDomain(Interface): The base url for the Mailman server at this domain, which includes the scheme and host name.""") + scheme = Attribute( + """The protocol scheme used to contact this list's server.""") + description = Attribute( 'The human readable description of the domain name.') diff --git a/src/mailman/interfaces/mailinglist.py b/src/mailman/interfaces/mailinglist.py index 27ea7a2b8..bede58b6c 100644 --- a/src/mailman/interfaces/mailinglist.py +++ b/src/mailman/interfaces/mailinglist.py @@ -62,6 +62,9 @@ class IMailingList(Interface): # List identity + created_at = Attribute( + """The date and time that the mailing list was created.""") + list_name = Attribute("""\ The read-only short name of the mailing list. Note that where a Mailman installation supports multiple domains, this short name may @@ -70,6 +73,7 @@ class IMailingList(Interface): 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 @@ -142,42 +146,27 @@ class IMailingList(Interface): """) join_address = Attribute( - """The address to which subscription requests should be sent. See - subscribe_address for a backward compatible alias. - """) + """The address to which subscription requests should be sent.""") leave_address = Attribute( - """The address to which unsubscription requests should be sent. See - unsubscribe_address for a backward compatible alias. - """) + """The address to which unsubscription requests should be sent.""") subscribe_address = Attribute( """Deprecated address to which subscription requests may be sent. This address is provided for backward compatibility only. See - join_address for the preferred alias. + `join_address` for the preferred alias. """) - leave_address = Attribute( + unsubscribe_address = Attribute( """Deprecated address to which unsubscription requests may be sent. This address is provided for backward compatibility only. See - leave_address for the preferred alias. + `leave_address` for the preferred alias. """) def confirm_address(cookie=''): """The address used for various forms of email confirmation.""" - creation_date = Attribute( - """The date and time that the mailing list was created.""") - - last_post_date = Attribute( - """The date and time a message was last posted to the mailing list.""") - - post_id = Attribute( - """A monotonically increasing integer sequentially assigned to each - list posting.""") - - digest_last_sent_at = Attribute( - """The date and time a digest of this mailing list was last sent.""") + # Rosters. owners = Attribute( """The IUser owners of this mailing list. @@ -227,6 +216,20 @@ class IMailingList(Interface): :rtype: Roster """ + # Posting history. + + last_post_at = Attribute( + """The date and time a message was last posted to the mailing list.""") + + post_id = Attribute( + """A monotonically increasing integer sequentially assigned to each + list posting.""") + + # Digests. + + digest_last_sent_at = Attribute( + """The date and time a digest of this mailing list was last sent.""") + volume = Attribute( """A monotonically increasing integer sequentially assigned to each new digest volume. The volume number may be bumped either @@ -268,10 +271,12 @@ class IMailingList(Interface): When a digest is being sent, each decorator may modify the final digest text.""") - protocol = Attribute( + # Web access. + + scheme = Attribute( """The protocol scheme used to contact this list's server. - The web server on thi protocol provides the web interface for this + The web server on this protocol provides the web interface for this mailing list. The protocol scheme should be 'http' or 'https'.""") web_host = Attribute( @@ -289,6 +294,8 @@ class IMailingList(Interface): 'location' attribute. """ + # Processing. + pipeline = Attribute( """The name of this mailing list's processing pipeline. diff --git a/src/mailman/model/domain.py b/src/mailman/model/domain.py index e19890cc3..44e2939cb 100644 --- a/src/mailman/model/domain.py +++ b/src/mailman/model/domain.py @@ -77,10 +77,16 @@ class Domain(Model): @property def url_host(self): + """See `IDomain`.""" # pylint: disable-msg=E1101 # no netloc member; yes it does return urlparse(self.base_url).netloc + @property + def scheme(self): + """See `IDomain`.""" + return urlparse(self.base_url).scheme + def confirm_url(self, token=''): """See `IDomain`.""" return urljoin(self.base_url, 'confirm/' + token) diff --git a/src/mailman/model/mailinglist.py b/src/mailman/model/mailinglist.py index 65611f563..f3c76dc25 100644 --- a/src/mailman/model/mailinglist.py +++ b/src/mailman/model/mailinglist.py @@ -77,7 +77,7 @@ class MailingList(Model): next_digest_number = Int() digest_last_sent_at = DateTime() volume = Int() - last_post_time = DateTime() + last_post_at = DateTime() # Implicit destination. acceptable_aliases_id = Int() acceptable_alias = Reference(acceptable_aliases_id, 'AcceptableAlias.id') @@ -218,6 +218,11 @@ class MailingList(Model): return getUtility(IDomainManager)[self.host_name] @property + def scheme(self): + """See `IMailingList`.""" + return self.domain.scheme + + @property def web_host(self): """See `IMailingList`.""" return self.domain.url_host diff --git a/src/mailman/rest/docs/configuration.txt b/src/mailman/rest/docs/configuration.txt new file mode 100644 index 000000000..d2c79fd09 --- /dev/null +++ b/src/mailman/rest/docs/configuration.txt @@ -0,0 +1,42 @@ +========================== +Mailing list configuration +========================== + +Mailing lists can be configured via the REST API. + + >>> create_list('test-one@example.com') + <mailing list "test-one@example.com" at ...> + >>> transaction.commit() + +All readable attributes for a list are available on a sub-resource. + + >>> dump_json('http://localhost:8001/3.0/lists/' + ... 'test-one@example.com/config') + bounces_address: test-one-bounces@example.com + collapse_alternatives: True + convert_html_to_plaintext: False + created_at: 20...T... + digest_last_sent_at: None + digest_size_threshold: 30.0 + filter_content: False + fqdn_listname: test-one@example.com + host_name: example.com + http_etag: "..." + include_list_post_header: True + include_rfc2369_headers: True + join_address: test-one-join@example.com + last_post_at: None + leave_address: test-one-leave@example.com + list_id: test-one.example.com + list_name: test-one + next_digest_number: 1 + no_reply_address: noreply@example.com + owner_address: test-one-owner@example.com + pipeline: built-in + post_id: 1 + posting_address: test-one@example.com + real_name: Test-one + request_address: test-one-request@example.com + scheme: http + volume: 1 + web_host: lists.example.com diff --git a/src/mailman/rest/helpers.py b/src/mailman/rest/helpers.py index be5d2b565..b17cdbca7 100644 --- a/src/mailman/rest/helpers.py +++ b/src/mailman/rest/helpers.py @@ -32,6 +32,7 @@ __all__ = [ import json import hashlib +from datetime import datetime from lazr.config import as_boolean from restish.http import Response @@ -61,6 +62,15 @@ def path_to(resource): +class ExtendedEncoder(json.JSONEncoder): + """An extended JSON encoder which knows about other data types.""" + + def default(self, obj): + if isinstance(obj, datetime): + return obj.isoformat() + return json.JSONEncoder.default(self, obj) + + def etag(resource): """Calculate the etag and return a JSON representation. @@ -78,7 +88,7 @@ def etag(resource): assert 'http_etag' not in resource, 'Resource already etagged' etag = hashlib.sha1(repr(resource)).hexdigest() resource['http_etag'] = '"{0}"'.format(etag) - return json.dumps(resource) + return json.dumps(resource, cls=ExtendedEncoder) diff --git a/src/mailman/rest/lists.py b/src/mailman/rest/lists.py index ddda613c9..a9d894714 100644 --- a/src/mailman/rest/lists.py +++ b/src/mailman/rest/lists.py @@ -23,6 +23,7 @@ __metaclass__ = type __all__ = [ 'AList', 'AllLists', + 'ListConfiguration', ] @@ -80,6 +81,18 @@ def roster_matcher(request, segments): return None +@restish_matcher +def config_matcher(request, segments): + """A matcher for a mailing list's configuration resource. + + e.g. /config + """ + if len(segments) == 1 and segments[0] == 'config': + return (), {}, () + # It's something else. + return None + + class _ListBase(resource.Resource, CollectionMixin): """Shared base class for mailing list representations.""" @@ -133,7 +146,13 @@ class AList(_ListBase): """Return the collection of all a mailing list's members.""" return MembersOfList(self._mlist, role) + @resource.child(config_matcher) + def config(self, request, segments): + """Return a mailing list configuration object.""" + return ListConfiguration(self._mlist) + + class AllLists(_ListBase): """The mailing lists.""" @@ -160,3 +179,55 @@ class AllLists(_ListBase): """/lists""" resource = self._make_collection(request) return http.ok([], etag(resource)) + + + +class ListConfiguration(resource.Resource): + """A mailing list configuration resource.""" + + def __init__(self, mailing_list): + self._mlist = mailing_list + + @resource.GET() + def configuration(self, request): + """Return a mailing list's readable configuration.""" + # The set of readable IMailingList attributes. + readable=( + # Identity. + 'created_at', + 'list_name', + 'host_name', + 'fqdn_listname', + 'real_name', + 'list_id', + 'include_list_post_header', + 'include_rfc2369_headers', + # Contact addresses. + 'posting_address', + 'no_reply_address', + 'owner_address', + 'request_address', + 'bounces_address', + 'join_address', + 'leave_address', + # Posting history. + 'last_post_at', + 'post_id', + # Digests. + 'digest_last_sent_at', + 'volume', + 'next_digest_number', + 'digest_size_threshold', + # Web access. + 'scheme', + 'web_host', + # Processing. + 'pipeline', + 'filter_content', + 'convert_html_to_plaintext', + 'collapse_alternatives', + ) + resource = {} + for attribute in readable: + resource[attribute] = getattr(self._mlist, attribute) + return http.ok([], etag(resource)) diff --git a/src/mailman/styles/default.py b/src/mailman/styles/default.py index 4caa01424..6a1b9a4df 100644 --- a/src/mailman/styles/default.py +++ b/src/mailman/styles/default.py @@ -97,6 +97,8 @@ from: .*@uplinkpro.com mlist.administrivia = True mlist.preferred_language = 'en' mlist.collapse_alternatives = True + mlist.convert_html_to_plaintext = False + mlist.filter_content = False # Digest related variables mlist.digestable = True mlist.digest_is_default = False |
