diff options
| -rw-r--r-- | Mailman/Handlers/Acknowledge.py | 22 | ||||
| -rw-r--r-- | Mailman/Utils.py | 2 | ||||
| -rw-r--r-- | Mailman/database/model/mailinglist.py | 12 | ||||
| -rw-r--r-- | Mailman/database/model/member.py | 35 | ||||
| -rw-r--r-- | Mailman/database/model/roster.py | 96 | ||||
| -rw-r--r-- | Mailman/docs/acknowledge.txt | 176 | ||||
| -rw-r--r-- | Mailman/docs/membership.txt | 67 | ||||
| -rw-r--r-- | Mailman/interfaces/member.py | 57 | ||||
| -rw-r--r-- | Mailman/interfaces/mlistweb.py | 25 | ||||
| -rw-r--r-- | Mailman/interfaces/roster.py | 7 | ||||
| -rw-r--r-- | Mailman/interfaces/rosterset.py | 47 | ||||
| -rw-r--r-- | Mailman/testing/test_acknowledge.py (renamed from Mailman/database/model/rosterset.py) | 33 | ||||
| -rw-r--r-- | Mailman/testing/test_handlers.py | 129 |
13 files changed, 425 insertions, 283 deletions
diff --git a/Mailman/Handlers/Acknowledge.py b/Mailman/Handlers/Acknowledge.py index d02920cde..078c3ac92 100644 --- a/Mailman/Handlers/Acknowledge.py +++ b/Mailman/Handlers/Acknowledge.py @@ -36,30 +36,30 @@ __i18n_templates__ = True def process(mlist, msg, msgdata): # Extract the sender's address and find them in the user database sender = msgdata.get('original_sender', msg.get_sender()) - try: - ack = mlist.getMemberOption(sender, config.AcknowledgePosts) - if not ack: - return - except Errors.NotAMemberError: + member = mlist.members.get_member(sender) + if member is None: + return + ack = member.acknowledge_posts + if not ack: return # Okay, they want acknowledgement of their post. Give them their original # subject. BAW: do we want to use the decoded header? origsubj = msgdata.get('origsubj', msg.get('subject', _('(no subject)'))) # Get the user's preferred language - lang = msgdata.get('lang', mlist.getMemberLanguage(sender)) + lang = msgdata.get('lang', member.preferred_language) # Now get the acknowledgement template realname = mlist.real_name text = Utils.maketext( 'postack.txt', {'subject' : Utils.oneline(origsubj, Utils.GetCharSet(lang)), 'listname' : realname, - 'listinfo_url': mlist.GetScriptURL('listinfo', absolute=1), - 'optionsurl' : mlist.GetOptionsURL(sender, absolute=1), - }, lang=lang, mlist=mlist, raw=1) + 'listinfo_url': mlist.script_url('listinfo'), + 'optionsurl' : member.options_url, + }, lang=lang, mlist=mlist, raw=True) # Craft the outgoing message, with all headers and attributes # necessary for general delivery. Then enqueue it to the outgoing # queue. - subject = _('$realname post acknowledgement') - usermsg = Message.UserNotification(sender, mlist.GetBouncesEmail(), + subject = _('$realname post acknowledgment') + usermsg = Message.UserNotification(sender, mlist.bounces_address, subject, text, lang) usermsg.send(mlist) diff --git a/Mailman/Utils.py b/Mailman/Utils.py index 27a61567e..064de570e 100644 --- a/Mailman/Utils.py +++ b/Mailman/Utils.py @@ -475,7 +475,7 @@ def findtext(templatefile, dict=None, raw=False, lang=None, mlist=None): # Calculate the locations to scan searchdirs = [] if mlist is not None: - searchdirs.append(mlist.fullpath()) + searchdirs.append(mlist.full_path) searchdirs.append(os.path.join(config.TEMPLATE_DIR, mlist.host_name)) searchdirs.append(os.path.join(config.TEMPLATE_DIR, 'site')) searchdirs.append(config.TEMPLATE_DIR) diff --git a/Mailman/database/model/mailinglist.py b/Mailman/database/model/mailinglist.py index edd2eab0d..4c7492310 100644 --- a/Mailman/database/model/mailinglist.py +++ b/Mailman/database/model/mailinglist.py @@ -30,6 +30,7 @@ class MailingList(Entity): IMailingListAddresses, IMailingListIdentity, IMailingListRosters, + IMailingListWeb, ) # List identity @@ -179,3 +180,14 @@ class MailingList(Entity): def fqdn_listname(self): """See IMailingListIdentity.""" return fqdn_listname(self.list_name, self.host_name) + + @property + def web_host(self): + """See IMailingListWeb.""" + return config.domains[self.host_name] + + def script_url(self, target, context=None): + """See IMailingListWeb.""" + # XXX Handle the case for when context is not None; those would be + # relative URLs. + return self.web_page_url + target + '/' + self.fqdn_listname diff --git a/Mailman/database/model/member.py b/Mailman/database/model/member.py index d9562aede..efb72ee11 100644 --- a/Mailman/database/model/member.py +++ b/Mailman/database/model/member.py @@ -19,6 +19,7 @@ from elixir import * from zope.interface import implements from Mailman.Utils import split_listname +from Mailman.constants import SystemDefaultPreferences from Mailman.database.types import EnumType from Mailman.interfaces import IMember, IPreferences @@ -42,3 +43,37 @@ class Member(Entity): def __repr__(self): return '<Member: %s on %s as %s>' % ( self.address, self.mailing_list, self.role) + + def _lookup(self, preference): + pref = getattr(self.preferences, preference) + if pref is not None: + return pref + pref = getattr(self.address.preferences, preference) + if pref is not None: + return pref + if self.address.user: + pref = getattr(self.address.user.preferences, preference) + if pref is not None: + return pref + return getattr(SystemDefaultPreferences, preference) + + @property + def delivery_mode(self): + return self._lookup('delivery_mode') + + @property + def acknowledge_posts(self): + return self._lookup('acknowledge_posts') + + @property + def preferred_language(self): + return self._lookup('preferred_language') + + def unsubscribe(self): + self.preferences.delete() + self.delete() + + @property + def options_url(self): + # XXX Um, this is definitely wrong + return 'http://example.com/' + self.address.address diff --git a/Mailman/database/model/roster.py b/Mailman/database/model/roster.py index ee50cddf0..0730d2b4b 100644 --- a/Mailman/database/model/roster.py +++ b/Mailman/database/model/roster.py @@ -22,11 +22,12 @@ the ones that fit a particular role. These are used as the member, owner, moderator, and administrator roster filters. """ +from sqlalchemy import * from zope.interface import implements from Mailman.constants import DeliveryMode, MemberRole from Mailman.constants import SystemDefaultPreferences -from Mailman.database.model import Member +from Mailman.database.model import Address, Member from Mailman.interfaces import IRoster @@ -42,12 +43,16 @@ class AbstractRoster(object): """ implements(IRoster) + role = None + def __init__(self, mlist): self._mlist = mlist @property def members(self): - raise NotImplementedError + for member in Member.select_by(mailing_list=self._mlist.fqdn_listname, + role=self.role): + yield member @property def users(self): @@ -67,21 +72,27 @@ class AbstractRoster(object): for member in self.members: yield member.address + def get_member(self, address): + results = Member.select( + and_(Member.c.mailing_list == self._mlist.fqdn_listname, + Member.c.role == self.role, + Address.c.address == address, + Member.c.address_id == Address.c.id)) + if len(results) == 0: + return None + elif len(results) == 1: + return results[0] + else: + assert len(results) <= 1, ( + 'Too many matching member results: %s' % results) + class MemberRoster(AbstractRoster): """Return all the members of a list.""" name = 'member' - - @property - def members(self): - # Query for all the Members which have a role of MemberRole.member and - # are subscribed to this mailing list. XXX we have to use a private - # data attribute of MailList for now. - for member in Member.select_by(mailing_list=self._mlist.fqdn_listname, - role=MemberRole.member): - yield member + role = MemberRole.member @@ -89,15 +100,7 @@ class OwnerRoster(AbstractRoster): """Return all the owners of a list.""" name = 'owner' - - @property - def members(self): - # Query for all the Members which have a role of MemberRole.member and - # are subscribed to this mailing list. XXX we have to use a private - # data attribute of MailList for now. - for member in Member.select_by(mailing_list=self._mlist.fqdn_listname, - role=MemberRole.owner): - yield member + role = MemberRole.owner @@ -105,15 +108,7 @@ class ModeratorRoster(AbstractRoster): """Return all the owners of a list.""" name = 'moderator' - - @property - def members(self): - # Query for all the Members which have a role of MemberRole.member and - # are subscribed to this mailing list. XXX we have to use a private - # data attribute of MailList for now. - for member in Member.select_by(mailing_list=self._mlist.fqdn_listname, - role=MemberRole.moderator): - yield member + role = MemberRole.moderator @@ -125,30 +120,31 @@ class AdministratorRoster(AbstractRoster): @property def members(self): # Administrators are defined as the union of the owners and the - # moderators. Until I figure out a more efficient way of doing this, - # this will have to do. - owners = Member.select_by(mailing_list=self._mlist.fqdn_listname, - role=MemberRole.owner) - moderators = Member.select_by(mailing_list=self._mlist.fqdn_listname, - role=MemberRole.moderator) - members = set(owners) - members.update(set(moderators)) + # moderators. + members = Member.select( + and_(Member.c.mailing_list == self._mlist.fqdn_listname, + or_(Member.c.role == MemberRole.owner, + Member.c.role == MemberRole.moderator))) for member in members: yield member - - -def _delivery_mode(member): - if member.preferences.delivery_mode is not None: - return member.preferences.delivery_mode - if member.address.preferences.delivery_mode is not None: - return member.address.preferences.delivery_mode - if (member.address.user and - member.address.user.preferences.delivery_mode is not None): - return member.address.user.preferences.delivery_mode - return SystemDefaultPreferences.delivery_mode + def get_member(self, address): + results = Member.select( + and_(Member.c.mailing_list == self._mlist.fqdn_listname, + or_(Member.c.role == MemberRole.moderator, + Member.c.role == MemberRole.owner), + Address.c.address == address, + Member.c.address_id == Address.c.id)) + if len(results) == 0: + return None + elif len(results) == 1: + return results[0] + else: + assert len(results) <= 1, ( + 'Too many matching member results: %s' % results) + class RegularMemberRoster(AbstractRoster): """Return all the regular delivery members of a list.""" @@ -161,7 +157,7 @@ class RegularMemberRoster(AbstractRoster): # that have a regular delivery mode. for member in Member.select_by(mailing_list=self._mlist.fqdn_listname, role=MemberRole.member): - if _delivery_mode(member) == DeliveryMode.regular: + if member.delivery_mode == DeliveryMode.regular: yield member @@ -186,5 +182,5 @@ class DigestMemberRoster(AbstractRoster): # that have one of the digest delivery modes. for member in Member.select_by(mailing_list=self._mlist.fqdn_listname, role=MemberRole.member): - if _delivery_mode(member) in _digest_modes: + if member.delivery_mode in _digest_modes: yield member diff --git a/Mailman/docs/acknowledge.txt b/Mailman/docs/acknowledge.txt new file mode 100644 index 000000000..6c7e3c28f --- /dev/null +++ b/Mailman/docs/acknowledge.txt @@ -0,0 +1,176 @@ +Message acknowledgment +====================== + +When a user posts a message to a mailing list, and that user has chosen to +receive acknowledgments of their postings, Mailman will sent them such an +acknowledgment. + + >>> from email import message_from_string + >>> from Mailman.Message import Message + >>> from Mailman.Handlers.Acknowledge import process + >>> from Mailman.configuration import config + >>> from Mailman.database import flush + >>> mlist = config.list_manager.create('_xtest@example.com') + >>> mlist.real_name = 'XTest' + >>> # XXX This will almost certainly change once we've worked out the web + >>> # space layout for mailing lists now. + >>> mlist._data.web_page_url = 'http://lists.example.com/' + >>> flush() + + >>> # Ensure that the virgin queue is empty, since we'll be checking this + >>> # for new auto-response messages. + >>> from Mailman.Queue.sbcache import get_switchboard + >>> virginq = get_switchboard(config.VIRGINQUEUE_DIR) + >>> virginq.files() + [] + +Subscribe a user to the mailing list. + + >>> from Mailman.constants import MemberRole + >>> user_1 = config.user_manager.create_user('aperson@example.com') + >>> address_1 = list(user_1.addresses)[0] + >>> address_1.subscribe(mlist, MemberRole.member) + <Member: aperson@example.com on _xtest@example.com as MemberRole.member> + >>> flush() + + +Non-member posts +---------------- + +Non-members can't get acknowledgments of their posts to the mailing list. + + >>> msg = message_from_string("""\ + ... From: bperson@example.com + ... + ... """, Message) + >>> process(mlist, msg, {}) + >>> virginq.files() + [] + +We can also specify the original sender in the message's metadata. If that +person is also not a member, no acknowledgment will be sent either. + + >>> msg = message_from_string("""\ + ... From: bperson@example.com + ... + ... """, Message) + >>> process(mlist, msg, dict(original_sender='cperson@example.com')) + >>> virginq.files() + [] + + +No acknowledgment requested +--------------------------- + +Unless the user has requested acknowledgments, they will not get one. + + >>> msg = message_from_string("""\ + ... From: aperson@example.com + ... + ... """, Message) + >>> process(mlist, msg, {}) + >>> virginq.files() + [] + +Similarly if the original sender is specified in the message metadata, and +that sender is a member but not one who has requested acknowledgments, none +will be sent. + + >>> user_2 = config.user_manager.create_user('dperson@example.com') + >>> address_2 = list(user_2.addresses)[0] + >>> address_2.subscribe(mlist, MemberRole.member) + <Member: dperson@example.com on _xtest@example.com as MemberRole.member> + >>> flush() + + >>> process(mlist, msg, dict(original_sender='dperson@example.com')) + >>> virginq.files() + [] + + +Requested acknowledgments +------------------------- + +If the member requests acknowledgments, Mailman will send them one when they +post to the mailing list. + + >>> user_1.preferences.acknowledge_posts = True + >>> flush() + +The receipt will include the original message's subject in the response body, + + >>> msg = message_from_string("""\ + ... From: aperson@example.com + ... Subject: Something witty and insightful + ... + ... """, Message) + >>> process(mlist, msg, {}) + + >>> len(virginq.files()) + 1 + >>> qmsg, qdata = virginq.dequeue(virginq.files()[0]) + >>> virginq.files() + [] + >>> # Print only some of the meta data. The rest is uninteresting. + >>> qdata['listname'] + '_xtest@example.com' + >>> qdata['recips'] + ['aperson@example.com'] + >>> print qmsg.as_string() + MIME-Version: 1.0 + Content-Type: text/plain; charset="us-ascii" + Content-Transfer-Encoding: 7bit + Subject: XTest post acknowledgment + From: _xtest-bounces@example.com + To: aperson@example.com + Message-ID: ... + Date: ... + Precedence: bulk + <BLANKLINE> + Your message entitled + <BLANKLINE> + Something witty and insightful + <BLANKLINE> + was successfully received by the XTest mailing list. + <BLANKLINE> + List info page: http://lists.example.com/listinfo/_xtest@example.com + Your preferences: http://example.com/aperson@example.com + <BLANKLINE> + +If there is no subject, then the receipt will use a generic message. + + >>> msg = message_from_string("""\ + ... From: aperson@example.com + ... + ... """, Message) + >>> process(mlist, msg, {}) + + >>> len(virginq.files()) + 1 + >>> qmsg, qdata = virginq.dequeue(virginq.files()[0]) + >>> virginq.files() + [] + >>> # Print only some of the meta data. The rest is uninteresting. + >>> qdata['listname'] + '_xtest@example.com' + >>> qdata['recips'] + ['aperson@example.com'] + >>> print qmsg.as_string() + MIME-Version: 1.0 + Content-Type: text/plain; charset="us-ascii" + Content-Transfer-Encoding: 7bit + Subject: XTest post acknowledgment + From: _xtest-bounces@example.com + To: aperson@example.com + Message-ID: ... + Date: ... + Precedence: bulk + <BLANKLINE> + Your message entitled + <BLANKLINE> + (no subject) + <BLANKLINE> + was successfully received by the XTest mailing list. + <BLANKLINE> + List info page: http://lists.example.com/listinfo/_xtest@example.com + Your preferences: http://example.com/aperson@example.com + <BLANKLINE> diff --git a/Mailman/docs/membership.txt b/Mailman/docs/membership.txt index 36b1508a8..9b6465d4a 100644 --- a/Mailman/docs/membership.txt +++ b/Mailman/docs/membership.txt @@ -168,12 +168,15 @@ Claire will be a regular delivery member but not a digest member. It's easy to make the list administrators members of the mailing list too. + >>> members = [] >>> for address in mlist.administrators.addresses: - ... address.subscribe(mlist, MemberRole.member) - <Member: Ben Person <bperson@example.com> on - _xtest@example.com as MemberRole.member> - <Member: Anne Person <aperson@example.com> on - _xtest@example.com as MemberRole.member> + ... member = address.subscribe(mlist, MemberRole.member) + ... members.append(member) + >>> sorted(members, key=lambda m: m.address.address) + [<Member: Anne Person <aperson@example.com> on + _xtest@example.com as MemberRole.member>, + <Member: Ben Person <bperson@example.com> on + _xtest@example.com as MemberRole.member>] >>> flush() >>> sorted(address.address for address in mlist.members.addresses) ['aperson@example.com', 'bperson@example.com', 'cperson@example.com'] @@ -181,3 +184,57 @@ It's easy to make the list administrators members of the mailing list too. ['aperson@example.com', 'bperson@example.com', 'cperson@example.com'] >>> sorted(address.address for address in mlist.digest_members.addresses) [] + + +Finding members +--------------- + +You can find the IMember object that is a member of a roster for a given text +email address by using an IRoster's .get_member() method. + + >>> mlist.owners.get_member('aperson@example.com') + <Member: Anne Person <aperson@example.com> on + _xtest@example.com as MemberRole.owner> + >>> mlist.administrators.get_member('aperson@example.com') + <Member: Anne Person <aperson@example.com> on + _xtest@example.com as MemberRole.owner> + >>> mlist.members.get_member('aperson@example.com') + <Member: Anne Person <aperson@example.com> on + _xtest@example.com as MemberRole.member> + +However, if the address is not subscribed with the appropriate role, then None +is returned. + + >>> print mlist.administrators.get_member('zperson@example.com') + None + >>> print mlist.moderators.get_member('aperson@example.com') + None + >>> print mlist.members.get_member('zperson@example.com') + None + + +Clean up +-------- + + >>> for member in mlist.members.members: + ... member.unsubscribe() + >>> for admin in mlist.administrators.members: + ... admin.unsubscribe() + >>> flush() + >>> list(mlist.members.members) + [] + >>> list(mlist.administrators.members) + [] + >>> for user in config.user_manager.users: + ... config.user_manager.delete_user(user) + >>> for address in config.user_manager.addresses: + ... config.user_manager.delete_address(address) + >>> for mlist in config.list_manager.mailing_lists: + ... config.list_manager.delete(mlist) + >>> flush() + >>> list(config.user_manager.users) + [] + >>> list(config.user_manager.addresses) + [] + >>> list(config.list_manager.mailing_lists) + [] diff --git a/Mailman/interfaces/member.py b/Mailman/interfaces/member.py index 5d5e3f717..4bb03f41d 100644 --- a/Mailman/interfaces/member.py +++ b/Mailman/interfaces/member.py @@ -32,17 +32,54 @@ class IMember(Interface): """The email address that's subscribed to the list.""") preferences = Attribute( - """The set of preferences for this subscription. + """This member's preferences.""") - This will return an IPreferences object using the following lookup - rules: + role = Attribute( + """The role of this membership.""") + + def unsubscribe(): + """Unsubscribe (and delete) this member from the mailing list.""" + + acknowledge_posts = Attribute( + """This is the actual acknowledgment setting for this member. + + Unlike going through the preferences, this attribute return the + preference value based on the following lookup order: - 1. member - 2. address - 3. user - 4. mailing list - 5. system default + 1. The member + 2. The address + 3. The user + 4. System default """) - role = Attribute( - """The role of this membership.""") + delivery_mode = Attribute( + """This is the actual delivery mode for this member. + + Unlike going through the preferences, this attribute return the + preference value based on the following lookup order: + + 1. The member + 2. The address + 3. The user + 4. System default + """) + + preferred_language = Attribute( + """This is the actual preferred language for this member. + + Unlike going through the preferences, this attribute return the + preference value based on the following lookup order: + + 1. The member + 2. The address + 3. The user + 4. System default + """) + + options_url = Attribute( + """Return the url for the given member's option page. + + XXX This needs a serious re-think in the face of the unified user + database, since a member's options aren't tied to any specific mailing + list. So in what part of the web-space does the user's options live? + """) diff --git a/Mailman/interfaces/mlistweb.py b/Mailman/interfaces/mlistweb.py index 16eb94281..728fd1990 100644 --- a/Mailman/interfaces/mlistweb.py +++ b/Mailman/interfaces/mlistweb.py @@ -21,19 +21,26 @@ from zope.interface import Interface, Attribute -class IMailingListURLs(Interface): +class IMailingListWeb(Interface): """The web addresses associated with a mailing list.""" protocol = Attribute( - """The web protocol to use to contact the server providing the web - interface for this mailing list, e.g. 'http' or 'https'.""") + """The protocol scheme used to contact this list's server. + + The web server on thi protocol provides the web interface for this + mailing list. The protocol scheme should be 'http' or 'https'.""") web_host = Attribute( - """The read-only domain name of the host to contact for interacting - with the web interface of the mailing list.""") + """This list's web server's domain. + + The read-only domain name of the host to contact for interacting with + the web interface of the mailing list.""") def script_url(target, context=None): - """Return the url to the given script target. If 'context' is not - given, or is None, then an absolute url is returned. If context is - given, it must be an IMailingListRequest object, and the returned url - will be relative to that object's 'location' attribute.""" + """Return the url to the given script target. + + If 'context' is not given, or is None, then an absolute url is + returned. If context is given, it must be an IMailingListRequest + object, and the returned url will be relative to that object's + 'location' attribute. + """ diff --git a/Mailman/interfaces/roster.py b/Mailman/interfaces/roster.py index 281e02a05..3b323d0b4 100644 --- a/Mailman/interfaces/roster.py +++ b/Mailman/interfaces/roster.py @@ -44,3 +44,10 @@ class IRoster(Interface): This returns all the addresses for all the users for all the members managed by this roster. """) + + def get_member(address): + """Return the IMember for the given address. + + 'address' is a text email address. If no matching member is found, + None is returned. + """ diff --git a/Mailman/interfaces/rosterset.py b/Mailman/interfaces/rosterset.py deleted file mode 100644 index 12f28bffa..000000000 --- a/Mailman/interfaces/rosterset.py +++ /dev/null @@ -1,47 +0,0 @@ -# Copyright (C) 2007 by the Free Software Foundation, Inc. -# -# This program 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 2 -# of the License, or (at your option) any later version. -# -# This program 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 this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, -# USA. - -"""Interface for a collection of rosters.""" - -from zope.interface import Interface, Attribute - - - -class IRosterSet(Interface): - """A collection of IRosters.""" - - serial = Attribute( - """The unique integer serial number for this roster set. - - This is necessary to enforce the separation between the list storage - and the user/roster storage. You should always reference a roster set - indirectly through its serial number.""") - - rosters = Attribute( - """An iterator over all the IRosters in this collection.""") - - def add(roster): - """Add the IRoster to this collection. - - Does nothing if the roster is already a member of this collection. - """ - - def delete(roster): - """Delete the IRoster from this collection. - - Does nothing if the roster is not a member of this collection. - """ diff --git a/Mailman/database/model/rosterset.py b/Mailman/testing/test_acknowledge.py index f84b52c15..a40d0c9e4 100644 --- a/Mailman/database/model/rosterset.py +++ b/Mailman/testing/test_acknowledge.py @@ -1,4 +1,4 @@ -# Copyright (C) 2006-2007 by the Free Software Foundation, Inc. +# Copyright (C) 2007 by the Free Software Foundation, Inc. # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -15,27 +15,18 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, # USA. -from elixir import * -from zope.interface import implements +"""Doctest harness for testing message acknowledgment.""" -from Mailman.interfaces import IRosterSet +import doctest +import unittest -ROSTER_KIND = 'Mailman.database.model.roster.Roster' +options = (doctest.ELLIPSIS + | doctest.NORMALIZE_WHITESPACE + | doctest.REPORT_NDIFF) - -# Internal implementation of roster sets for use with mailing lists. These -# are owned by the user storage. -class RosterSet(Entity): - implements(IRosterSet) - - has_field('name', Unicode) - has_and_belongs_to_many('rosters', of_kind=ROSTER_KIND) - - def add(self, roster): - if roster not in self.rosters: - self.rosters.append(roster) - - def delete(self, roster): - if roster in self.rosters: - self.rosters.remove(roster) +def test_suite(): + suite = unittest.TestSuite() + suite.addTest(doctest.DocFileSuite('../docs/acknowledge.txt', + optionflags=options)) + return suite diff --git a/Mailman/testing/test_handlers.py b/Mailman/testing/test_handlers.py index f4ad2ba4f..a0a196dae 100644 --- a/Mailman/testing/test_handlers.py +++ b/Mailman/testing/test_handlers.py @@ -62,135 +62,6 @@ def password(cleartext): -class TestAcknowledge(TestBase): - def setUp(self): - TestBase.setUp(self) - # We're going to want to inspect this queue directory - self._sb = Switchboard(config.VIRGINQUEUE_DIR) - # Add a member - self._mlist.addNewMember('aperson@example.org') - self._mlist.personalize = False - - def tearDown(self): - for f in os.listdir(config.VIRGINQUEUE_DIR): - os.unlink(os.path.join(config.VIRGINQUEUE_DIR, f)) - TestBase.tearDown(self) - - def test_no_ack_msgdata(self): - eq = self.assertEqual - # Make sure there are no files in the virgin queue already - eq(len(self._sb.files()), 0) - msg = email.message_from_string("""\ -From: aperson@example.org - -""", Message.Message) - Acknowledge.process(self._mlist, msg, - {'original_sender': 'aperson@example.org'}) - eq(len(self._sb.files()), 0) - - def test_no_ack_not_a_member(self): - eq = self.assertEqual - # Make sure there are no files in the virgin queue already - eq(len(self._sb.files()), 0) - msg = email.message_from_string("""\ -From: bperson@example.com - -""", Message.Message) - Acknowledge.process(self._mlist, msg, - {'original_sender': 'bperson@example.com'}) - eq(len(self._sb.files()), 0) - - def test_no_ack_sender(self): - eq = self.assertEqual - eq(len(self._sb.files()), 0) - msg = email.message_from_string("""\ -From: aperson@example.org - -""", Message.Message) - Acknowledge.process(self._mlist, msg, {}) - eq(len(self._sb.files()), 0) - - def test_ack_no_subject(self): - eq = self.assertEqual - self._mlist.setMemberOption( - 'aperson@example.org', config.AcknowledgePosts, 1) - eq(len(self._sb.files()), 0) - msg = email.message_from_string("""\ -From: aperson@example.org - -""", Message.Message) - Acknowledge.process(self._mlist, msg, {}) - files = self._sb.files() - eq(len(files), 1) - qmsg, qdata = self._sb.dequeue(files[0]) - # Check the .db file - eq(qdata.get('listname'), '_xtest@example.com') - eq(qdata.get('recips'), ['aperson@example.org']) - eq(qdata.get('version'), 3) - # Check the .pck - eq(str(qmsg['subject']), '_xtest post acknowledgement') - eq(qmsg['to'], 'aperson@example.org') - eq(qmsg['from'], '_xtest-bounces@example.com') - eq(qmsg.get_content_type(), 'text/plain') - eq(qmsg.get_param('charset'), 'us-ascii') - msgid = qmsg['message-id'] - self.failUnless(msgid.startswith('<mailman.')) - self.failUnless(msgid.endswith('._xtest@example.com>')) - eq(qmsg.get_payload(), """\ -Your message entitled - - (no subject) - -was successfully received by the _xtest mailing list. - -List info page: http://www.example.com/mailman/listinfo/_xtest@example.com -Your preferences: http://www.example.com/mailman/options/_xtest@example.com/aperson%40example.org -""") - # Make sure we dequeued the only message - eq(len(self._sb.files()), 0) - - def test_ack_with_subject(self): - eq = self.assertEqual - self._mlist.setMemberOption( - 'aperson@example.org', config.AcknowledgePosts, 1) - eq(len(self._sb.files()), 0) - msg = email.message_from_string("""\ -From: aperson@example.org -Subject: Wish you were here - -""", Message.Message) - Acknowledge.process(self._mlist, msg, {}) - files = self._sb.files() - eq(len(files), 1) - qmsg, qdata = self._sb.dequeue(files[0]) - # Check the .db file - eq(qdata.get('listname'), '_xtest@example.com') - eq(qdata.get('recips'), ['aperson@example.org']) - eq(qdata.get('version'), 3) - # Check the .pck - eq(str(qmsg['subject']), '_xtest post acknowledgement') - eq(qmsg['to'], 'aperson@example.org') - eq(qmsg['from'], '_xtest-bounces@example.com') - eq(qmsg.get_content_type(), 'text/plain') - eq(qmsg.get_param('charset'), 'us-ascii') - msgid = qmsg['message-id'] - self.failUnless(msgid.startswith('<mailman.')) - self.failUnless(msgid.endswith('._xtest@example.com>')) - eq(qmsg.get_payload(), """\ -Your message entitled - - Wish you were here - -was successfully received by the _xtest mailing list. - -List info page: http://www.example.com/mailman/listinfo/_xtest@example.com -Your preferences: http://www.example.com/mailman/options/_xtest@example.com/aperson%40example.org -""") - # Make sure we dequeued the only message - eq(len(self._sb.files()), 0) - - - class TestAfterDelivery(TestBase): # Both msg and msgdata are ignored def test_process(self): |
