summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/mailman/database/mailman.sql2
-rw-r--r--src/mailman/interfaces/domain.py3
-rw-r--r--src/mailman/interfaces/mailinglist.py53
-rw-r--r--src/mailman/model/domain.py6
-rw-r--r--src/mailman/model/mailinglist.py7
-rw-r--r--src/mailman/rest/docs/configuration.txt42
-rw-r--r--src/mailman/rest/helpers.py12
-rw-r--r--src/mailman/rest/lists.py71
-rw-r--r--src/mailman/styles/default.py2
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