summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/mailman/app/docs/moderator.rst77
-rw-r--r--src/mailman/commands/docs/withlist.rst4
-rw-r--r--src/mailman/database/base.py6
-rw-r--r--src/mailman/interfaces/domain.py2
-rw-r--r--src/mailman/interfaces/listmanager.py6
-rw-r--r--src/mailman/model/docs/mailinglist.rst15
-rw-r--r--src/mailman/model/domain.py4
-rw-r--r--src/mailman/model/listmanager.py3
-rw-r--r--src/mailman/rest/docs/moderation.rst32
-rw-r--r--src/mailman/testing/layers.py7
-rw-r--r--src/mailman/testing/testing.cfg6
11 files changed, 83 insertions, 79 deletions
diff --git a/src/mailman/app/docs/moderator.rst b/src/mailman/app/docs/moderator.rst
index ee9df8eb5..490c9630a 100644
--- a/src/mailman/app/docs/moderator.rst
+++ b/src/mailman/app/docs/moderator.rst
@@ -211,9 +211,9 @@ moderators.
...
... Here's something important about our mailing list.
... """)
- >>> hold_message(mlist, msg, {}, 'Needs approval')
- 2
- >>> handle_message(mlist, 2, Action.discard, forward=['zack@example.com'])
+ >>> req_id = hold_message(mlist, msg, {}, 'Needs approval')
+ >>> handle_message(mlist, req_id, Action.discard,
+ ... forward=['zack@example.com'])
The forwarded message is in the virgin queue, destined for the moderator.
::
@@ -243,10 +243,9 @@ choosing and their preferred language.
>>> from mailman.app.moderator import hold_subscription
>>> from mailman.interfaces.member import DeliveryMode
- >>> hold_subscription(mlist,
- ... 'fred@example.org', 'Fred Person',
+ >>> req_id = hold_subscription(
+ ... mlist, 'fred@example.org', 'Fred Person',
... '{NONE}abcxyz', DeliveryMode.regular, 'en')
- 2
Disposing of membership change requests
@@ -257,26 +256,27 @@ dispositions for this membership change request. The most trivial is to
simply defer a decision for now.
>>> from mailman.app.moderator import handle_subscription
- >>> handle_subscription(mlist, 2, Action.defer)
- >>> requests.get_request(2) is not None
+ >>> handle_subscription(mlist, req_id, Action.defer)
+ >>> requests.get_request(req_id) is not None
True
The held subscription can also be discarded.
- >>> handle_subscription(mlist, 2, Action.discard)
- >>> print(requests.get_request(2))
+ >>> handle_subscription(mlist, req_id, Action.discard)
+ >>> print(requests.get_request(req_id))
None
Gwen tries to subscribe to the mailing list, but...
- >>> hold_subscription(mlist,
- ... 'gwen@example.org', 'Gwen Person',
+ >>> req_id = hold_subscription(
+ ... mlist, 'gwen@example.org', 'Gwen Person',
... '{NONE}zyxcba', DeliveryMode.regular, 'en')
- 2
+
...her request is rejected...
- >>> handle_subscription(mlist, 2, Action.reject, 'This is a closed list')
+ >>> handle_subscription(
+ ... mlist, req_id, Action.reject, 'This is a closed list')
>>> messages = get_queue_messages('virgin')
>>> len(messages)
1
@@ -304,14 +304,13 @@ The subscription can also be accepted. This subscribes the address to the
mailing list.
>>> mlist.send_welcome_message = False
- >>> hold_subscription(mlist,
- ... 'herb@example.org', 'Herb Person',
+ >>> req_id = hold_subscription(
+ ... mlist, 'herb@example.org', 'Herb Person',
... 'abcxyz', DeliveryMode.regular, 'en')
- 2
The moderators accept the subscription request.
- >>> handle_subscription(mlist, 2, Action.accept)
+ >>> handle_subscription(mlist, req_id, Action.accept)
And now Herb is a member of the mailing list.
@@ -328,29 +327,27 @@ the unsubscribing address is required.
Herb now wants to leave the mailing list, but his request must be approved.
>>> from mailman.app.moderator import hold_unsubscription
- >>> hold_unsubscription(mlist, 'herb@example.org')
- 2
+ >>> req_id = hold_unsubscription(mlist, 'herb@example.org')
As with subscription requests, the unsubscription request can be deferred.
>>> from mailman.app.moderator import handle_unsubscription
- >>> handle_unsubscription(mlist, 2, Action.defer)
+ >>> handle_unsubscription(mlist, req_id, Action.defer)
>>> print(mlist.members.get_member('herb@example.org').address)
Herb Person <herb@example.org>
The held unsubscription can also be discarded, and the member will remain
subscribed.
- >>> handle_unsubscription(mlist, 2, Action.discard)
+ >>> handle_unsubscription(mlist, req_id, Action.discard)
>>> print(mlist.members.get_member('herb@example.org').address)
Herb Person <herb@example.org>
The request can be rejected, in which case a message is sent to the member,
and the person remains a member of the mailing list.
- >>> hold_unsubscription(mlist, 'herb@example.org')
- 2
- >>> handle_unsubscription(mlist, 2, Action.reject, 'No can do')
+ >>> req_id = hold_unsubscription(mlist, 'herb@example.org')
+ >>> handle_unsubscription(mlist, req_id, Action.reject, 'No can do')
>>> print(mlist.members.get_member('herb@example.org').address)
Herb Person <herb@example.org>
@@ -381,10 +378,9 @@ Herb gets a rejection notice.
The unsubscription request can also be accepted. This removes the member from
the mailing list.
- >>> hold_unsubscription(mlist, 'herb@example.org')
- 2
+ >>> req_id = hold_unsubscription(mlist, 'herb@example.org')
>>> mlist.send_goodbye_message = False
- >>> handle_unsubscription(mlist, 2, Action.accept)
+ >>> handle_unsubscription(mlist, req_id, Action.accept)
>>> print(mlist.members.get_member('herb@example.org'))
None
@@ -403,9 +399,8 @@ list is configured to send them.
Iris tries to subscribe to the mailing list.
- >>> hold_subscription(mlist, 'iris@example.org', 'Iris Person',
+ >>> req_id = hold_subscription(mlist, 'iris@example.org', 'Iris Person',
... 'password', DeliveryMode.regular, 'en')
- 2
There's now a message in the virgin queue, destined for the list owner.
@@ -429,8 +424,7 @@ There's now a message in the virgin queue, destined for the list owner.
Similarly, the administrator gets notifications on unsubscription requests.
Jeff is a member of the mailing list, and chooses to unsubscribe.
- >>> hold_unsubscription(mlist, 'jeff@example.org')
- 3
+ >>> unsub_req_id = hold_unsubscription(mlist, 'jeff@example.org')
>>> messages = get_queue_messages('virgin')
>>> len(messages)
1
@@ -457,7 +451,7 @@ receive a membership change notice.
>>> mlist.admin_notify_mchanges = True
>>> mlist.admin_immed_notify = False
- >>> handle_subscription(mlist, 2, Action.accept)
+ >>> handle_subscription(mlist, req_id, Action.accept)
>>> messages = get_queue_messages('virgin')
>>> len(messages)
1
@@ -474,9 +468,8 @@ receive a membership change notice.
Similarly when an unsubscription request is accepted, the administrators can
get a notification.
- >>> hold_unsubscription(mlist, 'iris@example.org')
- 4
- >>> handle_unsubscription(mlist, 4, Action.accept)
+ >>> req_id = hold_unsubscription(mlist, 'iris@example.org')
+ >>> handle_unsubscription(mlist, req_id, Action.accept)
>>> messages = get_queue_messages('virgin')
>>> len(messages)
1
@@ -498,10 +491,9 @@ can get a welcome message.
>>> mlist.admin_notify_mchanges = False
>>> mlist.send_welcome_message = True
- >>> hold_subscription(mlist, 'kate@example.org', 'Kate Person',
- ... 'password', DeliveryMode.regular, 'en')
- 4
- >>> handle_subscription(mlist, 4, Action.accept)
+ >>> req_id = hold_subscription(mlist, 'kate@example.org', 'Kate Person',
+ ... 'password', DeliveryMode.regular, 'en')
+ >>> handle_subscription(mlist, req_id, Action.accept)
>>> messages = get_queue_messages('virgin')
>>> len(messages)
1
@@ -523,9 +515,8 @@ Similarly, when the member's unsubscription request is approved, she'll get a
goodbye message.
>>> mlist.send_goodbye_message = True
- >>> hold_unsubscription(mlist, 'kate@example.org')
- 4
- >>> handle_unsubscription(mlist, 4, Action.accept)
+ >>> req_id = hold_unsubscription(mlist, 'kate@example.org')
+ >>> handle_unsubscription(mlist, req_id, Action.accept)
>>> messages = get_queue_messages('virgin')
>>> len(messages)
1
diff --git a/src/mailman/commands/docs/withlist.rst b/src/mailman/commands/docs/withlist.rst
index 827d246cd..e915eb04c 100644
--- a/src/mailman/commands/docs/withlist.rst
+++ b/src/mailman/commands/docs/withlist.rst
@@ -90,13 +90,13 @@ must start with a caret.
>>> args.listname = '^.*example.com'
>>> command.process(args)
The list's display name is Aardvark
- The list's display name is Badger
The list's display name is Badboys
+ The list's display name is Badger
>>> args.listname = '^bad.*'
>>> command.process(args)
- The list's display name is Badger
The list's display name is Badboys
+ The list's display name is Badger
>>> args.listname = '^foo'
>>> command.process(args)
diff --git a/src/mailman/database/base.py b/src/mailman/database/base.py
index e360dcedf..beb9c260d 100644
--- a/src/mailman/database/base.py
+++ b/src/mailman/database/base.py
@@ -114,3 +114,9 @@ class SABaseDatabase:
session = sessionmaker(bind=self.engine)
self.store = session()
self.store.commit()
+
+ # XXX BAW Why doesn't model.py _reset() do this?
+ def destroy(self):
+ """Drop all database tables"""
+ from mailman.database.model import Model
+ Model.metadata.drop_all(self.engine)
diff --git a/src/mailman/interfaces/domain.py b/src/mailman/interfaces/domain.py
index e8610fd76..e7cb0d901 100644
--- a/src/mailman/interfaces/domain.py
+++ b/src/mailman/interfaces/domain.py
@@ -166,6 +166,8 @@ class IDomainManager(Interface):
def __iter__():
"""An iterator over all the domains.
+ Domains are returned sorted by `mail_host`.
+
:return: iterator over `IDomain`.
"""
diff --git a/src/mailman/interfaces/listmanager.py b/src/mailman/interfaces/listmanager.py
index 22d7b3418..0c641fb91 100644
--- a/src/mailman/interfaces/listmanager.py
+++ b/src/mailman/interfaces/listmanager.py
@@ -130,8 +130,10 @@ class IListManager(Interface):
"""
mailing_lists = Attribute(
- """An iterator over all the mailing list objects managed by this list
- manager.""")
+ """An iterator over all the mailing list objects.
+
+ The mailing lists are returned in order sorted by `list_id`.
+ """)
def __iter__():
"""An iterator over all the mailing lists.
diff --git a/src/mailman/model/docs/mailinglist.rst b/src/mailman/model/docs/mailinglist.rst
index 53ba99575..3d01710c5 100644
--- a/src/mailman/model/docs/mailinglist.rst
+++ b/src/mailman/model/docs/mailinglist.rst
@@ -50,7 +50,10 @@ receive a copy of any message sent to the mailing list.
Both addresses appear on the roster of members.
- >>> for member in mlist.members.members:
+ >>> from operator import attrgetter
+ >>> sort_key = attrgetter('address.email')
+
+ >>> for member in sorted(mlist.members.members, key=sort_key):
... print(member)
<Member: aperson@example.com on aardvark@example.com as MemberRole.member>
<Member: bperson@example.com on aardvark@example.com as MemberRole.member>
@@ -72,7 +75,7 @@ A Person is now both a member and an owner of the mailing list. C Person is
an owner and a moderator.
::
- >>> for member in mlist.owners.members:
+ >>> for member in sorted(mlist.owners.members, key=sort_key):
... print(member)
<Member: aperson@example.com on aardvark@example.com as MemberRole.owner>
<Member: cperson@example.com on aardvark@example.com as MemberRole.owner>
@@ -87,13 +90,13 @@ All rosters can also be accessed indirectly.
::
>>> roster = mlist.get_roster(MemberRole.member)
- >>> for member in roster.members:
+ >>> for member in sorted(roster.members, key=sort_key):
... print(member)
<Member: aperson@example.com on aardvark@example.com as MemberRole.member>
<Member: bperson@example.com on aardvark@example.com as MemberRole.member>
>>> roster = mlist.get_roster(MemberRole.owner)
- >>> for member in roster.members:
+ >>> for member in sorted(roster.members, key=sort_key):
... print(member)
<Member: aperson@example.com on aardvark@example.com as MemberRole.owner>
<Member: cperson@example.com on aardvark@example.com as MemberRole.owner>
@@ -122,7 +125,7 @@ just by changing their preferred address.
>>> mlist.subscribe(user)
<Member: Dave Person <dperson@example.com> on aardvark@example.com
as MemberRole.member>
- >>> for member in mlist.members.members:
+ >>> for member in sorted(mlist.members.members, key=sort_key):
... print(member)
<Member: aperson@example.com on aardvark@example.com as MemberRole.member>
<Member: bperson@example.com on aardvark@example.com as MemberRole.member>
@@ -133,7 +136,7 @@ just by changing their preferred address.
>>> new_address.verified_on = now()
>>> user.preferred_address = new_address
- >>> for member in mlist.members.members:
+ >>> for member in sorted(mlist.members.members, key=sort_key):
... print(member)
<Member: aperson@example.com on aardvark@example.com as MemberRole.member>
<Member: bperson@example.com on aardvark@example.com as MemberRole.member>
diff --git a/src/mailman/model/domain.py b/src/mailman/model/domain.py
index 083e1cf51..ef8b1f761 100644
--- a/src/mailman/model/domain.py
+++ b/src/mailman/model/domain.py
@@ -48,7 +48,7 @@ class Domain(Model):
id = Column(Integer, primary_key=True)
- mail_host = Column(Unicode)
+ mail_host = Column(Unicode) # TODO: add index?
base_url = Column(Unicode)
description = Column(Unicode)
contact_address = Column(Unicode)
@@ -170,7 +170,7 @@ class DomainManager:
@dbconnection
def __iter__(self, store):
"""See `IDomainManager`."""
- for domain in store.query(Domain).all():
+ for domain in store.query(Domain).order_by(Domain.mail_host).all():
yield domain
@dbconnection
diff --git a/src/mailman/model/listmanager.py b/src/mailman/model/listmanager.py
index 43a2b8f2a..261490a92 100644
--- a/src/mailman/model/listmanager.py
+++ b/src/mailman/model/listmanager.py
@@ -86,7 +86,8 @@ class ListManager:
@dbconnection
def mailing_lists(self, store):
"""See `IListManager`."""
- for mlist in store.query(MailingList).all():
+ for mlist in store.query(MailingList).order_by(
+ MailingList._list_id).all():
yield mlist
@dbconnection
diff --git a/src/mailman/rest/docs/moderation.rst b/src/mailman/rest/docs/moderation.rst
index 44182eb23..6e2dbb43c 100644
--- a/src/mailman/rest/docs/moderation.rst
+++ b/src/mailman/rest/docs/moderation.rst
@@ -226,10 +226,9 @@ moderator approval.
>>> from mailman.app.moderator import hold_subscription
>>> from mailman.interfaces.member import DeliveryMode
- >>> hold_subscription(
+ >>> sub_req_id = hold_subscription(
... ant, 'anne@example.com', 'Anne Person',
... 'password', DeliveryMode.regular, 'en')
- 1
>>> transaction.commit()
The subscription request is available from the mailing list.
@@ -242,7 +241,7 @@ The subscription request is available from the mailing list.
http_etag: "..."
language: en
password: password
- request_id: 1
+ request_id: ...
type: subscription
when: 2005-08-01T07:49:23
http_etag: "..."
@@ -259,8 +258,7 @@ Bart tries to leave a mailing list, but he may not be allowed to.
>>> from mailman.app.moderator import hold_unsubscription
>>> bart = add_member(ant, 'bart@example.com', 'Bart Person',
... 'password', DeliveryMode.regular, 'en')
- >>> hold_unsubscription(ant, 'bart@example.com')
- 2
+ >>> unsub_req_id = hold_unsubscription(ant, 'bart@example.com')
>>> transaction.commit()
The unsubscription request is also available from the mailing list.
@@ -273,13 +271,13 @@ The unsubscription request is also available from the mailing list.
http_etag: "..."
language: en
password: password
- request_id: 1
+ request_id: ...
type: subscription
when: 2005-08-01T07:49:23
entry 1:
address: bart@example.com
http_etag: "..."
- request_id: 2
+ request_id: ...
type: unsubscription
http_etag: "..."
start: 0
@@ -292,23 +290,25 @@ Viewing individual requests
You can view an individual membership change request by providing the
request id. Anne's subscription request looks like this.
- >>> dump_json('http://localhost:9001/3.0/lists/ant@example.com/requests/1')
+ >>> dump_json('http://localhost:9001/3.0/lists/ant@example.com/'
+ ... 'requests/{}'.format(sub_req_id))
address: anne@example.com
delivery_mode: regular
display_name: Anne Person
http_etag: "..."
language: en
password: password
- request_id: 1
+ request_id: ...
type: subscription
when: 2005-08-01T07:49:23
Bart's unsubscription request looks like this.
- >>> dump_json('http://localhost:9001/3.0/lists/ant@example.com/requests/2')
+ >>> dump_json('http://localhost:9001/3.0/lists/ant@example.com/'
+ ... 'requests/{}'.format(unsub_req_id))
address: bart@example.com
http_etag: "..."
- request_id: 2
+ request_id: ...
type: unsubscription
@@ -328,9 +328,8 @@ data requires an action of one of the following:
Anne's subscription request is accepted.
>>> dump_json('http://localhost:9001/3.0/lists/'
- ... 'ant@example.com/requests/1', {
- ... 'action': 'accept',
- ... })
+ ... 'ant@example.com/requests/{}'.format(sub_req_id),
+ ... {'action': 'accept'})
content-length: 0
date: ...
server: ...
@@ -347,9 +346,8 @@ Anne is now a member of the mailing list.
Bart's unsubscription request is discarded.
>>> dump_json('http://localhost:9001/3.0/lists/'
- ... 'ant@example.com/requests/2', {
- ... 'action': 'discard',
- ... })
+ ... 'ant@example.com/requests/{}'.format(unsub_req_id),
+ ... {'action': 'discard'})
content-length: 0
date: ...
server: ...
diff --git a/src/mailman/testing/layers.py b/src/mailman/testing/layers.py
index 4a9841fc2..6104e64f7 100644
--- a/src/mailman/testing/layers.py
+++ b/src/mailman/testing/layers.py
@@ -190,10 +190,11 @@ class ConfigLayer(MockAndMonkeyLayer):
@classmethod
def tearDown(cls):
assert cls.var_dir is not None, 'Layer not set up'
- # Reset the test database after the tests are done so that there is no
- # data in case the tests are rerun with a database layer like mysql or
- # postgresql which are not deleted in teardown.
reset_the_world()
+ # Destroy the test database after the tests are done so that there is
+ # no data in case the tests are rerun with a database layer like mysql
+ # or postgresql which are not deleted in teardown.
+ config.db.destroy()
config.pop('test config')
shutil.rmtree(cls.var_dir)
cls.var_dir = None
diff --git a/src/mailman/testing/testing.cfg b/src/mailman/testing/testing.cfg
index 43ae6e67e..ff2034069 100644
--- a/src/mailman/testing/testing.cfg
+++ b/src/mailman/testing/testing.cfg
@@ -18,9 +18,9 @@
# A testing configuration.
# For testing against PostgreSQL.
-# [database]
-# class: mailman.database.postgresql.PostgreSQLDatabase
-# url: postgresql://$USER:$USER@localhost/mailman_test
+[database]
+class: mailman.database.postgresql.PostgreSQLDatabase
+url: postgresql://barry:barry@localhost:5433/mailman
[mailman]
site_owner: noreply@example.com