diff options
| author | Barry Warsaw | 2007-09-27 22:15:00 -0400 |
|---|---|---|
| committer | Barry Warsaw | 2007-09-27 22:15:00 -0400 |
| commit | 4a2ca2c159accdca7e1101a94efb5a4499407b72 (patch) | |
| tree | 1d2d507bbe53cb071aa15095f67edd76434bf836 | |
| parent | 65c64773d910b3b2a3e2a9b9db4669e57170ece2 (diff) | |
| download | mailman-4a2ca2c159accdca7e1101a94efb5a4499407b72.tar.gz mailman-4a2ca2c159accdca7e1101a94efb5a4499407b72.tar.zst mailman-4a2ca2c159accdca7e1101a94efb5a4499407b72.zip | |
| -rw-r--r-- | Mailman/Bouncer.py | 29 | ||||
| -rw-r--r-- | Mailman/Defaults.py | 3 | ||||
| -rw-r--r-- | Mailman/Handlers/ToDigest.py | 1 | ||||
| -rw-r--r-- | Mailman/MailList.py | 20 | ||||
| -rw-r--r-- | Mailman/MemberAdaptor.py | 388 | ||||
| -rw-r--r-- | Mailman/OldStyleMemberships.py | 379 | ||||
| -rw-r--r-- | Mailman/Version.py | 9 | ||||
| -rw-r--r-- | Mailman/bin/__init__.py | 1 | ||||
| -rw-r--r-- | Mailman/bin/list_lists.py | 1 | ||||
| -rw-r--r-- | Mailman/bin/list_members.py | 1 | ||||
| -rw-r--r-- | Mailman/bin/newlist.py | 1 | ||||
| -rw-r--r-- | Mailman/bin/set_members.py | 186 | ||||
| -rw-r--r-- | Mailman/constants.py | 2 | ||||
| -rw-r--r-- | Mailman/database/model/mailinglist.py | 10 | ||||
| -rw-r--r-- | Mailman/interfaces/mailinglist.py | 200 | ||||
| -rw-r--r-- | Mailman/interfaces/mlistdigests.py | 66 | ||||
| -rw-r--r-- | Mailman/interfaces/mlistemail.py | 78 | ||||
| -rw-r--r-- | Mailman/interfaces/mlistid.py | 46 | ||||
| -rw-r--r-- | Mailman/interfaces/mlistrosters.py | 68 | ||||
| -rw-r--r-- | Mailman/interfaces/mliststats.py | 38 | ||||
| -rw-r--r-- | Mailman/interfaces/mlistweb.py | 46 | ||||
| -rw-r--r-- | Mailman/versions.py | 517 |
22 files changed, 402 insertions, 1688 deletions
diff --git a/Mailman/Bouncer.py b/Mailman/Bouncer.py index f4a8ce90f..e823a3859 100644 --- a/Mailman/Bouncer.py +++ b/Mailman/Bouncer.py @@ -25,11 +25,11 @@ from email.MIMEMessage import MIMEMessage from email.MIMEText import MIMEText from Mailman import Defaults -from Mailman import MemberAdaptor from Mailman import Message from Mailman import Utils from Mailman import i18n from Mailman.configuration import config +from Mailman.constants import DeliveryStatus EMPTYSTRING = '' @@ -40,11 +40,12 @@ ZEROHOUR_PLUSONEDAY = time.localtime(60 * 60 * 24)[:3] def _(s): return s -REASONS = {MemberAdaptor.BYBOUNCE: _('due to excessive bounces'), - MemberAdaptor.BYUSER: _('by yourself'), - MemberAdaptor.BYADMIN: _('by the list administrator'), - MemberAdaptor.UNKNOWN: _('for unknown reasons'), - } +REASONS = { + DeliveryStatus.by_bounces : _('due to excessive bounces'), + DeliveryStatus.by_user : _('by yourself'), + DeliveryStatus.by_moderator : _('by the list administrator'), + DeliveryStatus.unknown : _('for unknown reasons'), + } _ = i18n._ @@ -94,7 +95,7 @@ class Bouncer: log.info('%s: %s bounce score: %s', self.internal_name(), member, info.score) # Continue to the check phase below - elif self.getDeliveryStatus(member) <> MemberAdaptor.ENABLED: + elif self.getDeliveryStatus(member) <> DeliveryStatus.enabled: # The user is already disabled, so we can just ignore subsequent # bounces. These are likely due to residual messages that were # sent before disabling the member, but took a while to bounce. @@ -150,7 +151,7 @@ class Bouncer: log.info('%s: %s disabling due to bounce score %s >= %s', self.internal_name(), member, info.score, self.bounce_score_threshold) - self.setDeliveryStatus(member, MemberAdaptor.BYBOUNCE) + self.setDeliveryStatus(member, DeliveryStatus.by_bounces) self.sendNextNotification(member) if self.bounce_notify_owner_on_disable: self.__sendAdminBounceNotice(member, msg) @@ -201,15 +202,15 @@ class Bouncer: # Expunge the pending cookie for the user. We throw away the # returned data. self.pend_confirm(info.cookie) - if reason == MemberAdaptor.BYBOUNCE: + if reason == DeliveryStatus.by_bounces: log.info('%s: %s deleted after exhausting notices', self.internal_name(), member) slog.info('%s: %s auto-unsubscribed [reason: %s]', self.internal_name(), member, - {MemberAdaptor.BYBOUNCE: 'BYBOUNCE', - MemberAdaptor.BYUSER: 'BYUSER', - MemberAdaptor.BYADMIN: 'BYADMIN', - MemberAdaptor.UNKNOWN: 'UNKNOWN'}.get( + {DeliveryStatus.by_bounces: 'BYBOUNCE', + DeliveryStatus.by_user: 'BYUSER', + DeliveryStatus.by_moderator: 'BYADMIN', + DeliveryStatus.unknown: 'UNKNOWN'}.get( reason, 'invalid value')) return # Send the next notification @@ -224,7 +225,7 @@ class Bouncer: else: txtreason = _(txtreason) # Give a little bit more detail on bounce disables - if reason == MemberAdaptor.BYBOUNCE: + if reason == DeliveryStatus.by_bounces: date = time.strftime('%d-%b-%Y', time.localtime(Utils.midnight(info.date))) extra = _(' The last bounce received from you was dated %(date)s') diff --git a/Mailman/Defaults.py b/Mailman/Defaults.py index b53065da6..592d6d0ff 100644 --- a/Mailman/Defaults.py +++ b/Mailman/Defaults.py @@ -123,9 +123,6 @@ SQLALCHEMY_ENGINE_URL = 'sqlite:///$DATA_DIR/mailman.db' # For debugging purposes SQLALCHEMY_ECHO = False -# XXX REMOVE ME -MEMBER_ADAPTOR_CLASS = 'Mailman.OldStyleMemberships.OldStyleMemberships' - ##### diff --git a/Mailman/Handlers/ToDigest.py b/Mailman/Handlers/ToDigest.py index 7e2dcc6d2..131bf4588 100644 --- a/Mailman/Handlers/ToDigest.py +++ b/Mailman/Handlers/ToDigest.py @@ -51,7 +51,6 @@ from Mailman.Handlers.Decorate import decorate from Mailman.Handlers.Scrubber import process as scrubber from Mailman.Mailbox import Mailbox from Mailman.Mailbox import Mailbox -from Mailman.MemberAdaptor import ENABLED from Mailman.Queue.sbcache import get_switchboard from Mailman.configuration import config from Mailman.constants import DeliveryMode, DeliveryStatus diff --git a/Mailman/MailList.py b/Mailman/MailList.py index 900daaa11..da37eb6e1 100644 --- a/Mailman/MailList.py +++ b/Mailman/MailList.py @@ -279,26 +279,6 @@ class MailList(object, Archiver, Digester, SecurityManager, Bouncer): # # Sanity checks # - def CheckVersion(self, stored_state): - """Auto-update schema if necessary.""" - if self.data_version >= Version.DATA_FILE_VERSION: - return - # Then reload the database (but don't recurse). Force a reload even - # if we have the most up-to-date state. - self.Load(self.fqdn_listname, check_version=False) - # We must hold the list lock in order to update the schema - waslocked = self.Locked() - if not waslocked: - self.Lock() - try: - from versions import Update - Update(self, stored_state) - self.data_version = Version.DATA_FILE_VERSION - self.Save() - finally: - if not waslocked: - self.Unlock() - def CheckValues(self): """Normalize selected values to known formats.""" if '' in urlparse(self.web_page_url)[:2]: diff --git a/Mailman/MemberAdaptor.py b/Mailman/MemberAdaptor.py deleted file mode 100644 index 6b5b4a5e2..000000000 --- a/Mailman/MemberAdaptor.py +++ /dev/null @@ -1,388 +0,0 @@ -# Copyright (C) 2001-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. - -"""This is an interface to list-specific membership information. - -This class should not be instantiated directly, but instead, it should be -subclassed for specific adaptation to membership databases. The default -MM2.0.x style adaptor is in OldStyleMemberships.py. Through the extend.py -mechanism, you can instantiate different membership information adaptors to -get info out of LDAP, Zope, other, or any combination of the above. - -Members have three pieces of identifying information: a unique identifying -opaque key (KEY), a lower-cased email address (LCE), and a case-preserved -email (CPE) address. Adaptors must ensure that both member keys and lces can -uniquely identify a member, and that they can (usually) convert freely between -keys and lces. Most methods must accept either a key or an lce, unless -specifically documented otherwise. - -The CPE is always used to calculate the recipient address for a message. Some -remote MTAs make a distinction based on localpart case, so we always send -messages to the case-preserved address. Note that DNS is case insensitive so -it doesn't matter what the case is for the domain part of an email address, -although by default, we case-preserve that too. - -The adaptors must support the readable interface for getting information about -memberships, and may optionally support the writeable interface. If they do -not, then members cannot change their list attributes via Mailman's web or -email interfaces. Updating membership information in that case is the -backend's responsibility. Adaptors are allowed to support parts of the -writeable interface. - -For any writeable method not supported, a NotImplementedError exception should -be raised. -""" - -# Delivery statuses -ENABLED = 0 # enabled -UNKNOWN = 1 # legacy disabled -BYUSER = 2 # disabled by user choice -BYADMIN = 3 # disabled by admin choice -BYBOUNCE = 4 # disabled by bounces - - - -class MemberAdaptor: - def __init__(self, mlist): - """Create the adaptor, attached to the given mailing list.""" - raise NotImplementedError - - # - # Transaction interface - # - def load(self): - """Called when the mailing list data is loaded. - - The adaptor should refresh/clear all in-memory objects. The reason is - that other processes may have changed the database since the last time - the adaptor has been accessed. - """ - - def lock(self): - """Called when the mailing list is locked. - - This should correspond to a database 'begin'. - """ - raise NotImplementedError - - def save(self): - """Called when a locked mailing list is saved. - - This should correspond to a database 'commit' except that the adaptor - should record that the database has been saved, and this flag should - be checked in any subsequent unlock() calls. - """ - raise NotImplementedError - - def unlock(self): - """Called when a locked mailing list is unlocked. - - Generally, this can be no-op'd if save() was previously called, but if - not, then a database 'rollback' should be issued. There is no - explicit transaction rollback operation in the MailList API, but - processes will unlock without saving to mean the same thing. - """ - raise NotImplementedError - - # - # The readable interface - # - def getMembers(self): - """Get the LCE for all the members of the mailing list.""" - raise NotImplementedError - - def getRegularMemberKeys(self): - """Get the LCE for all regular delivery members (i.e. non-digest).""" - raise NotImplementedError - - def getDigestMemberKeys(self): - """Get the LCE for all digest delivery members.""" - raise NotImplementedError - - def isMember(self, member): - """Return 1 if member KEY/LCE is a valid member, otherwise 0.""" - - def getMemberKey(self, member): - """Return the KEY for the member KEY/LCE. - - If member does not refer to a valid member, raise NotAMemberError. - """ - raise NotImplementedError - - def getMemberCPAddress(self, member): - """Return the CPE for the member KEY/LCE. - - If member does not refer to a valid member, raise NotAMemberError. - """ - raise NotImplementedError - - def getMemberCPAddresses(self, members): - """Return a sequence of CPEs for the given sequence of members. - - The returned sequence will be the same length as members. If any of - the KEY/LCEs in members does not refer to a valid member, that entry - in the returned sequence will be None (i.e. NotAMemberError is never - raised). - """ - raise NotImplementedError - - def authenticateMember(self, member, response): - """Authenticate the member KEY/LCE with the given response. - - If the response authenticates the member, return a secret that is - known only to the authenticated member. This need not be the member's - password, but it will be used to craft a session cookie, so it should - be persistent for the life of the session. - - If the authentication failed return False. If member did not refer to - a valid member, raise NotAMemberError. - - Normally, the response will be the password typed into a web form or - given in an email command, but it needn't be. It is up to the adaptor - to compare the typed response to the user's authentication token. - """ - raise NotImplementedError - - def getMemberPassword(self, member): - """Return the member's password. - - If the member KEY/LCE is not a member of the list, raise - NotAMemberError. - """ - raise NotImplementedError - - def getMemberLanguage(self, member): - """Return the preferred language for the member KEY/LCE. - - The language returned must be a key in mm_cfg.LC_DESCRIPTIONS and the - mailing list must support that language. - - If member does not refer to a valid member, the list's default - language is returned instead of raising a NotAMemberError error. - """ - raise NotImplementedError - - def getMemberOption(self, member, flag): - """Return the boolean state of the member option for member KEY/LCE. - - Option flags are defined in Defaults.py. - - If member does not refer to a valid member, raise NotAMemberError. - """ - raise NotImplementedError - - def getMemberName(self, member): - """Return the full name of the member KEY/LCE. - - None is returned if the member has no registered full name. The - returned value may be a Unicode string if there are non-ASCII - characters in the name. NotAMemberError is raised if member does not - refer to a valid member. - """ - raise NotImplementedError - - def getMemberTopics(self, member): - """Return the list of topics this member is interested in. - - The return value is a list of strings which name the topics. - """ - raise NotImplementedError - - def getDeliveryStatus(self, member): - """Return the delivery status of this member. - - Value is one of the module constants: - - ENABLED - The deliveries to the user are not disabled - UNKNOWN - Deliveries are disabled for unknown reasons. The - primary reason for this to happen is that we've copied - their delivery status from a legacy version which didn't - keep track of disable reasons - BYUSER - The user explicitly disable deliveries - BYADMIN - The list administrator explicitly disabled deliveries - BYBOUNCE - The system disabled deliveries due to bouncing - - If member is not a member of the list, raise NotAMemberError. - """ - raise NotImplementedError - - def getDeliveryStatusChangeTime(self, member): - """Return the time of the last disabled delivery status change. - - Time is returned in float seconds since the epoch. XXX this should be - a Python datetime. - - If the current delivery status is ENABLED, the status change time will - be zero. If member is not a member of the list, raise - NotAMemberError. - """ - raise NotImplementedError - - def getDeliveryStatusMembers(self, - status=(UNKNOWN, BYUSER, BYADMIN, BYBOUNCE)): - """Return the list of members with a matching delivery status. - - Optional `status' if given, must be a sequence containing one or more - of ENABLED, UNKNOWN, BYUSER, BYADMIN, or BYBOUNCE. The members whose - delivery status is in this sequence are returned. - """ - raise NotImplementedError - - def getBouncingMembers(self): - """Return the list of members who have outstanding bounce information. - - This list of members doesn't necessarily overlap with - getDeliveryStatusMembers() since getBouncingMembers() will return - member who have bounced but not yet reached the disable threshold. - """ - raise NotImplementedError - - def getBounceInfo(self, member): - """Return the member's bounce information. - - A value of None means there is no bounce information registered for - the member. - - Bounce info is opaque to the MemberAdaptor. It is set by - setBounceInfo() and returned by this method without modification. - - If member is not a member of the list, raise NotAMemberError. - """ - raise NotImplementedError - - - # - # The writeable interface - # - def addNewMember(self, member, **kws): - """Subscribes a new member to the mailing list. - - member is the case-preserved address to subscribe. The LCE is - calculated from this argument. Return the new member KEY. - - This method also takes a keyword dictionary which can be used to set - additional attributes on the member. The actual set of supported - keywords is adaptor specific, but should at least include: - - - digest == subscribing to digests instead of regular delivery - - password == user's password - - language == user's preferred language - - realname == user's full name (should be Unicode if there are - non-ASCII characters in the name) - - Any values not passed to **kws is set to the adaptor-specific - defaults. - - Raise AlreadyAMemberError it the member is already subscribed to the - list. Raises ValueError if **kws contains an invalid option. - """ - raise NotImplementedError - - def removeMember(self, memberkey): - """Unsubscribes the member from the mailing list. - - Raise NotAMemberError if member is not subscribed to the list. - """ - raise NotImplementedError - - def changeMemberAddress(self, memberkey, newaddress, nodelete=False): - """Change the address for the member KEY. - - memberkey will be a KEY, not an LCE. newaddress should be the - new case-preserved address for the member; the LCE will be calculated - from newaddress. - - If memberkey does not refer to a valid member, raise NotAMemberError. - No verification on the new address is done here (such assertions - should be performed by the caller). - - If nodelete flag is true, then the old membership is not removed. - """ - raise NotImplementedError - - def setMemberPassword(self, member, password): - """Set the password for member LCE/KEY. - - If member does not refer to a valid member, raise NotAMemberError. - """ - raise NotImplementedError - - def setMemberLanguage(self, member, language): - """Set the language for the member LCE/KEY. - - If member does not refer to a valid member, raise NotAMemberError. - """ - raise NotImplementedError - - def setMemberOption(self, member, flag, value): - """Set the option for the given member to value. - - member is an LCE/KEY, flag is one of the option flags defined in - Default.py, and value is a boolean. - - If member does not refer to a valid member, raise NotAMemberError. - """ - raise NotImplementedError - - def setMemberName(self, member, realname): - """Set the member's full name. - - member is an LCE/KEY and realname is an arbitrary string. It should - be a Unicode string if there are non-ASCII characters in the name. - NotAMemberError is raised if member does not refer to a valid member. - """ - raise NotImplementedError - - def setMemberTopics(self, member, topics): - """Add list of topics to member's interest. - - member is an LCE/KEY and realname is an arbitrary string. - NotAMemberError is raised if member does not refer to a valid member. - topics must be a sequence of strings. - """ - raise NotImplementedError - - def setDeliveryStatus(self, member, status): - """Set the delivery status of the member's address. - - Status must be one of the module constants: - - ENABLED - The deliveries to the user are not disabled - UNKNOWN - Deliveries are disabled for unknown reasons. The - primary reason for this to happen is that we've copied - their delivery status from a legacy version which didn't - keep track of disable reasons - BYUSER - The user explicitly disable deliveries - BYADMIN - The list administrator explicitly disabled deliveries - BYBOUNCE - The system disabled deliveries due to bouncing - - This method also records the time (in seconds since epoch) at which - the last status change was made. If the delivery status is changed to - ENABLED, then the change time information will be deleted. This value - is retrievable via getDeliveryStatusChangeTime(). - """ - raise NotImplementedError - - def setBounceInfo(self, member, info): - """Set the member's bounce information. - - When info is None, any bounce info for the member is cleared. - - Bounce info is opaque to the MemberAdaptor. It is set by this method - and returned by getBounceInfo() without modification. - """ - raise NotImplementedError diff --git a/Mailman/OldStyleMemberships.py b/Mailman/OldStyleMemberships.py deleted file mode 100644 index 594096d9e..000000000 --- a/Mailman/OldStyleMemberships.py +++ /dev/null @@ -1,379 +0,0 @@ -# Copyright (C) 2001-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. - -"""Old style Mailman membership adaptor. - -This adaptor gets and sets member information on the MailList object given to -the constructor. It also equates member keys and lower-cased email addresses, -i.e. KEY is LCE. - -This is the adaptor used by default in Mailman 2.1. -""" - -import time - -from Mailman import Errors -from Mailman import MemberAdaptor -from Mailman import Utils -from Mailman import passwords -from Mailman.configuration import config - -ISREGULAR = 1 -ISDIGEST = 2 - -# XXX check for bare access to mlist.members, mlist.digest_members, -# mlist.user_options, mlist.passwords, mlist.topics_userinterest - -# XXX Fix Errors.MMAlreadyAMember and Errors.NotAMember -# Actually, fix /all/ errors - - - -class OldStyleMemberships(MemberAdaptor.MemberAdaptor): - def __init__(self, mlist): - self.__mlist = mlist - - # - # Transaction interface - # - - # These are all no-op'd because the data is all attached to and managed by - # the MailList object. - def load(self): pass - def lock(self): pass - def save(self): pass - def unlock(self): pass - - # - # Read interface - # - def getMembers(self): - return self.__mlist.members.keys() + self.__mlist.digest_members.keys() - - def getRegularMemberKeys(self): - return self.__mlist.members.keys() - - def getDigestMemberKeys(self): - return self.__mlist.digest_members.keys() - - def __get_cp_member(self, member): - lcmember = member.lower() - missing = [] - val = self.__mlist.members.get(lcmember, missing) - if val is not missing: - if isinstance(val, str): - return val, ISREGULAR - else: - return lcmember, ISREGULAR - val = self.__mlist.digest_members.get(lcmember, missing) - if val is not missing: - if isinstance(val, str): - return val, ISDIGEST - else: - return lcmember, ISDIGEST - return None, None - - def isMember(self, member): - cpaddr, where = self.__get_cp_member(member) - if cpaddr is not None: - return True - return False - - def getMemberKey(self, member): - cpaddr, where = self.__get_cp_member(member) - if cpaddr is None: - raise Errors.NotAMemberError, member - return member.lower() - - def getMemberCPAddress(self, member): - cpaddr, where = self.__get_cp_member(member) - if cpaddr is None: - raise Errors.NotAMemberError, member - return cpaddr - - def getMemberCPAddresses(self, members): - return [self.__get_cp_member(member)[0] for member in members] - - def getMemberPassword(self, member): - secret = self.__mlist.passwords.get(member.lower()) - if secret is None: - raise Errors.NotAMemberError, member - return secret - - def authenticateMember(self, member, response): - secret = self.getMemberPassword(member) - if passwords.check_response(secret, response): - return secret - return False - - def __assertIsMember(self, member): - if not self.isMember(member): - raise Errors.NotAMemberError, member - - def getMemberLanguage(self, member): - lang = self.__mlist.language.get( - member.lower(), self.__mlist.preferred_language) - if lang in self.__mlist.language_codes: - return lang - return self.__mlist.preferred_language - - def getMemberOption(self, member, flag): - self.__assertIsMember(member) - if flag == mm_cfg.Digests: - cpaddr, where = self.__get_cp_member(member) - return where == ISDIGEST - option = self.__mlist.user_options.get(member.lower(), 0) - return not not (option & flag) - - def getMemberName(self, member): - self.__assertIsMember(member) - return self.__mlist.usernames.get(member.lower()) - - def getMemberTopics(self, member): - self.__assertIsMember(member) - return self.__mlist.topics_userinterest.get(member.lower(), []) - - def getDeliveryStatus(self, member): - self.__assertIsMember(member) - return self.__mlist.delivery_status.get( - member.lower(), - # Values are tuples, so the default should also be a tuple. The - # second item will be ignored. - (MemberAdaptor.ENABLED, 0))[0] - - def getDeliveryStatusChangeTime(self, member): - self.__assertIsMember(member) - return self.__mlist.delivery_status.get( - member.lower(), - # Values are tuples, so the default should also be a tuple. The - # second item will be ignored. - (MemberAdaptor.ENABLED, 0))[1] - - def getDeliveryStatusMembers(self, status=(MemberAdaptor.UNKNOWN, - MemberAdaptor.BYUSER, - MemberAdaptor.BYADMIN, - MemberAdaptor.BYBOUNCE)): - return [member for member in self.getMembers() - if self.getDeliveryStatus(member) in status] - - def getBouncingMembers(self): - return [member.lower() for member in self.__mlist.bounce_info.keys()] - - def getBounceInfo(self, member): - self.__assertIsMember(member) - return self.__mlist.bounce_info.get(member.lower()) - - # - # Write interface - # - def addNewMember(self, member, **kws): - assert self.__mlist.Locked() - # Make sure this address isn't already a member - if self.isMember(member): - raise Errors.MMAlreadyAMember, member - # Parse the keywords - digest = 0 - password = Utils.MakeRandomPassword() - language = self.__mlist.preferred_language - realname = None - if kws.has_key('digest'): - digest = kws['digest'] - del kws['digest'] - if kws.has_key('password'): - password = kws['password'] - del kws['password'] - if kws.has_key('language'): - language = kws['language'] - del kws['language'] - if kws.has_key('realname'): - realname = kws['realname'] - del kws['realname'] - # Assert that no other keywords are present - if kws: - raise ValueError, kws.keys() - # If the localpart has uppercase letters in it, then the value in the - # members (or digest_members) dict is the case preserved address. - # Otherwise the value is 0. Note that the case of the domain part is - # of course ignored. - if Utils.LCDomain(member) == member.lower(): - value = 0 - else: - value = member - member = member.lower() - if digest: - self.__mlist.digest_members[member] = value - else: - self.__mlist.members[member] = value - self.setMemberPassword(member, password) - - self.setMemberLanguage(member, language) - if realname: - self.setMemberName(member, realname) - # Set the member's default set of options - if self.__mlist.new_member_options: - self.__mlist.user_options[member] = self.__mlist.new_member_options - - def removeMember(self, member): - assert self.__mlist.Locked() - self.__assertIsMember(member) - # Delete the appropriate entries from the various MailList attributes. - # Remember that not all of them will have an entry (only those with - # values different than the default). - memberkey = member.lower() - for attr in ('passwords', 'user_options', 'members', 'digest_members', - 'language', 'topics_userinterest', 'usernames', - 'bounce_info', 'delivery_status', - ): - dict = getattr(self.__mlist, attr) - if dict.has_key(memberkey): - del dict[memberkey] - - def changeMemberAddress(self, member, newaddress, nodelete=0): - assert self.__mlist.Locked() - # Make sure the old address is a member. Assertions that the new - # address is not already a member is done by addNewMember() below. - self.__assertIsMember(member) - # Get the old values - memberkey = member.lower() - fullname = self.getMemberName(memberkey) - flags = self.__mlist.user_options.get(memberkey, 0) - digestsp = self.getMemberOption(memberkey, mm_cfg.Digests) - password = self.__mlist.passwords.get(memberkey, - Utils.MakeRandomPassword()) - lang = self.getMemberLanguage(memberkey) - delivery = self.__mlist.delivery_status.get(member.lower(), - (MemberAdaptor.ENABLED,0)) - # First, possibly delete the old member - if not nodelete: - self.removeMember(memberkey) - # Now, add the new member - self.addNewMember(newaddress, realname=fullname, digest=digestsp, - password=password, language=lang) - # Set the entire options bitfield - if flags: - self.__mlist.user_options[newaddress.lower()] = flags - # If this is a straightforward address change, i.e. nodelete = 0, - # preserve the delivery status and time if BYUSER or BYADMIN - if delivery[0] in (MemberAdaptor.BYUSER, MemberAdaptor.BYADMIN)\ - and not nodelete: - self.__mlist.delivery_status[newaddress.lower()] = delivery - - def setMemberPassword(self, memberkey, password): - assert self.__mlist.Locked() - self.__assertIsMember(memberkey) - self.__mlist.passwords[memberkey.lower()] = password - - def setMemberLanguage(self, memberkey, language): - assert self.__mlist.Locked() - self.__assertIsMember(memberkey) - self.__mlist.language[memberkey.lower()] = language - - def setMemberOption(self, member, flag, value): - assert self.__mlist.Locked() - self.__assertIsMember(member) - memberkey = member.lower() - # There's one extra gotcha we have to deal with. If the user is - # toggling the Digests flag, then we need to move their entry from - # mlist.members to mlist.digest_members or vice versa. Blarg. Do - # this before the flag setting below in case it fails. - # - # XXX Adaptors should not be doing these semantic integrity checks, - # but for backward compatibility I'm not changing this. New adaptors - # should not mimic this behavior. - if flag == mm_cfg.Digests: - if value: - # Be sure the list supports digest delivery - if not self.__mlist.digestable: - raise Errors.CantDigestError - # The user is turning on digest mode - if self.__mlist.digest_members.has_key(memberkey): - raise Errors.AlreadyReceivingDigests, member - cpuser = self.__mlist.members.get(memberkey) - if cpuser is None: - raise Errors.NotAMemberError, member - del self.__mlist.members[memberkey] - self.__mlist.digest_members[memberkey] = cpuser - else: - # Be sure the list supports regular delivery - if not self.__mlist.nondigestable: - raise Errors.MustDigestError - # The user is turning off digest mode - if self.__mlist.members.has_key(memberkey): - raise Errors.AlreadyReceivingRegularDeliveries, member - cpuser = self.__mlist.digest_members.get(memberkey) - if cpuser is None: - raise Errors.NotAMemberError, member - del self.__mlist.digest_members[memberkey] - self.__mlist.members[memberkey] = cpuser - # When toggling off digest delivery, we want to be sure to set - # things up so that the user receives one last digest, - # otherwise they may lose some email - self.__mlist.one_last_digest[memberkey] = cpuser - # We don't need to touch user_options because the digest state - # isn't kept as a bitfield flag. - return - # This is a bit kludgey because the semantics are that if the user has - # no options set (i.e. the value would be 0), then they have no entry - # in the user_options dict. We use setdefault() here, and then del - # the entry below just to make things (questionably) cleaner. - self.__mlist.user_options.setdefault(memberkey, 0) - if value: - self.__mlist.user_options[memberkey] |= flag - else: - self.__mlist.user_options[memberkey] &= ~flag - if not self.__mlist.user_options[memberkey]: - del self.__mlist.user_options[memberkey] - - def setMemberName(self, member, realname): - assert self.__mlist.Locked() - self.__assertIsMember(member) - self.__mlist.usernames[member.lower()] = realname - - def setMemberTopics(self, member, topics): - assert self.__mlist.Locked() - self.__assertIsMember(member) - memberkey = member.lower() - if topics: - self.__mlist.topics_userinterest[memberkey] = topics - # if topics is empty, then delete the entry in this dictionary - elif self.__mlist.topics_userinterest.has_key(memberkey): - del self.__mlist.topics_userinterest[memberkey] - - def setDeliveryStatus(self, member, status): - assert status in (MemberAdaptor.ENABLED, MemberAdaptor.UNKNOWN, - MemberAdaptor.BYUSER, MemberAdaptor.BYADMIN, - MemberAdaptor.BYBOUNCE) - assert self.__mlist.Locked() - self.__assertIsMember(member) - member = member.lower() - if status == MemberAdaptor.ENABLED: - # Enable by resetting their bounce info. - self.setBounceInfo(member, None) - else: - self.__mlist.delivery_status[member] = (status, time.time()) - - def setBounceInfo(self, member, info): - assert self.__mlist.Locked() - self.__assertIsMember(member) - member = member.lower() - if info is None: - if self.__mlist.bounce_info.has_key(member): - del self.__mlist.bounce_info[member] - if self.__mlist.delivery_status.has_key(member): - del self.__mlist.delivery_status[member] - else: - self.__mlist.bounce_info[member] = info diff --git a/Mailman/Version.py b/Mailman/Version.py index a922d88ad..e81214611 100644 --- a/Mailman/Version.py +++ b/Mailman/Version.py @@ -40,17 +40,8 @@ HEX_VERSION = ((MAJOR_REV << 24) | (MINOR_REV << 16) | (MICRO_REV << 8) | # SQLAlchemy database schema version DATABASE_SCHEMA_VERSION = 1 -# config.pck schema version number -DATA_FILE_VERSION = 98 - # qfile/*.db schema version number QFILE_SCHEMA_VERSION = 3 -# version number for the lists/<listname>/pending.db file schema -PENDING_FILE_SCHEMA_VERSION = 2 - -# version number for the lists/<listname>/request.db file schema -REQUESTS_FILE_SCHEMA_VERSION = 1 - # Printable version string used by command line scripts MAILMAN_VERSION = 'GNU Mailman ' + VERSION diff --git a/Mailman/bin/__init__.py b/Mailman/bin/__init__.py index 2bb75a4c8..af8e5a50b 100644 --- a/Mailman/bin/__init__.py +++ b/Mailman/bin/__init__.py @@ -50,6 +50,7 @@ __all__ = [ 'request', 'rmlist', 'senddigests', + 'set_members', 'show_config', 'show_qfiles', 'testall', diff --git a/Mailman/bin/list_lists.py b/Mailman/bin/list_lists.py index 145043759..54c2f5534 100644 --- a/Mailman/bin/list_lists.py +++ b/Mailman/bin/list_lists.py @@ -18,7 +18,6 @@ import optparse from Mailman import Defaults -from Mailman import MailList from Mailman import Version from Mailman.configuration import config from Mailman.i18n import _ diff --git a/Mailman/bin/list_members.py b/Mailman/bin/list_members.py index 2e1213979..d9721761b 100644 --- a/Mailman/bin/list_members.py +++ b/Mailman/bin/list_members.py @@ -21,7 +21,6 @@ import optparse from email.Utils import formataddr from Mailman import Errors -from Mailman import MailList from Mailman import Utils from Mailman import Version from Mailman.configuration import config diff --git a/Mailman/bin/newlist.py b/Mailman/bin/newlist.py index be594fac2..5de48e858 100644 --- a/Mailman/bin/newlist.py +++ b/Mailman/bin/newlist.py @@ -24,7 +24,6 @@ import datetime import optparse from Mailman import Errors -from Mailman import MailList from Mailman import Message from Mailman import Utils from Mailman import Version diff --git a/Mailman/bin/set_members.py b/Mailman/bin/set_members.py new file mode 100644 index 000000000..83df7381b --- /dev/null +++ b/Mailman/bin/set_members.py @@ -0,0 +1,186 @@ +# 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. + +from __future__ import with_statement + +import csv +import optparse + +from Mailman import Message +from Mailman import Utils +from Mailman import Version +from Mailman import i18n +from Mailman import passwords +from Mailman.app.membership import add_member +from Mailman.configuration import config +from Mailman.constants import DeliveryMode +from Mailman.initialize import initialize + + +_ = i18n._ +__i18n_templates__ = True + +DELIVERY_MODES = { + 'regular': DeliveryMode.regular, + 'plain': DeliveryMode.plaintext_digests, + 'mime': DeliveryMode.mime_digests, + } + + + +def parseargs(): + parser = optparse.OptionParser(version=Version.MAILMAN_VERSION, + usage=_("""\ +%prog [options] csv-file + +Set the membership of a mailing list to that described in a CSV file. Each +row of the CSV file has the following format. Only the address column is +required. + + - email address + - full name (default: the empty string) + - delivery mode (default: regular delivery) [1] + +[1] The delivery mode is a case insensitive string of the following values: + + regular - regular, i.e. immediate delivery + mime - MIME digest delivery + plain - plain text (RFC 1153) digest delivery + +Any address not included in the CSV file is removed from the list membership. +""")) + parser.add_option('-l', '--listname', + type='string', help=_("""\ +Mailng list to set the membership for.""")) + parser.add_option('-w', '--welcome-msg', + type='string', metavar='<y|n>', help=_("""\ +Set whether or not to send the list members a welcome message, overriding +whatever the list's 'send_welcome_msg' setting is.""")) + parser.add_option('-a', '--admin-notify', + type='string', metavar='<y|n>', help=_("""\ +Set whether or not to send the list administrators a notification on the +success/failure of these subscriptions, overriding whatever the list's +'admin_notify_mchanges' setting is.""")) + parser.add_option('-v', '--verbose', action='store_true', + help=_('Increase verbosity')) + parser.add_option('-C', '--config', + help=_('Alternative configuration file to use')) + opts, args = parser.parse_args() + if opts.welcome_msg is not None: + ch = opts.welcome_msg[0].lower() + if ch == 'y': + opts.welcome_msg = True + elif ch == 'n': + opts.welcome_msg = False + else: + parser.error(_('Illegal value for -w: $opts.welcome_msg')) + if opts.admin_notify is not None: + ch = opts.admin_notify[0].lower() + if ch == 'y': + opts.admin_notify = True + elif ch == 'n': + opts.admin_notify = False + else: + parser.error(_('Illegal value for -a: $opts.admin_notify')) + return parser, opts, args + + + +def parse_file(filename): + members = {} + with open(filename) as fp: + for row in csv.reader(fp): + if len(row) == 0: + continue + elif len(row) == 1: + address = row[0] + real_name = None + delivery_mode = DeliveryMode.regular + elif len(row) == 2: + address, real_name = row + delivery_mode = DeliveryMode.regular + else: + # Ignore extra columns + address, real_name = row[0:2] + delivery_mode = DELIVERY_MODES.get(row[2].lower()) + if delivery_mode is None: + delivery_mode = DeliveryMode.regular + members[address] = real_name, delivery_mode + return members + + + +def main(): + parser, opts, args = parseargs() + initialize(opts.config) + + mlist = config.db.list_manager.get(opts.listname) + if mlist is None: + parser.error(_('No such list: $opts.listname')) + + # Set up defaults. + if opts.welcome_msg is None: + send_welcome_msg = mlist.send_welcome_msg + else: + send_welcome_msg = opts.welcome_msg + if opts.admin_notify is None: + admin_notify = mlist.admin_notify_mchanges + else: + admin_notify = opts.admin_notify + + # Parse the csv files. + member_data = {} + for filename in args: + member_data.update(parse_file(filename)) + + future_members = set(member_data) + current_members = set(obj.address for obj in mlist.members.addresses) + add_members = future_members - current_members + delete_members = current_members - future_members + change_members = current_members & future_members + + with i18n.using_language(mlist.preferred_language): + # Start by removing all the delete members. + for address in delete_members: + print _('deleting address: $address') + member = mlist.members.get_member(address) + member.unsubscribe() + # For all members that are in both lists, update their full name and + # delivery mode. + for address in change_members: + print _('updating address: $address') + real_name, delivery_mode = member_data[address] + member = mlist.members.get_member(address) + member.preferences.delivery_mode = delivery_mode + user = config.db.user_manager.get_user(address) + user.real_name = real_name + for address in add_members: + print _('adding address: $address') + real_name, delivery_mode = member_data[address] + password = passwords.make_secret( + Utils.MakeRandomPassword(), + passwords.lookup_scheme(config.PASSWORD_SCHEME)) + add_member(mlist, address, real_name, password, delivery_mode, + mlist.preferred_language, send_welcome_msg, + admin_notify) + + config.db.flush() + + + +if __name__ == '__main__': + main() diff --git a/Mailman/constants.py b/Mailman/constants.py index 55a258af1..cb2a656dc 100644 --- a/Mailman/constants.py +++ b/Mailman/constants.py @@ -54,6 +54,8 @@ class DeliveryStatus(Enum): by_bounces = 3 # Delivery was disabled by an administrator or moderator by_moderator = 4 + # Disabled for unknown reasons. + unknown = 5 diff --git a/Mailman/database/model/mailinglist.py b/Mailman/database/model/mailinglist.py index 7fa9aca38..b37bcbce1 100644 --- a/Mailman/database/model/mailinglist.py +++ b/Mailman/database/model/mailinglist.py @@ -23,7 +23,7 @@ from zope.interface import implements from Mailman.Utils import fqdn_listname, makedirs, split_listname from Mailman.configuration import config -from Mailman.interfaces import * +from Mailman.interfaces import IMailingList from Mailman.database.types import EnumType, TimeDeltaType SPACE = ' ' @@ -32,13 +32,7 @@ UNDERSCORE = '_' class MailingList(Entity): - implements( - IMailingList, - IMailingListAddresses, - IMailingListIdentity, - IMailingListRosters, - IMailingListWeb, - ) + implements(IMailingList) # List identity has_field('list_name', Unicode), diff --git a/Mailman/interfaces/mailinglist.py b/Mailman/interfaces/mailinglist.py index c162801ab..0bb176b96 100644 --- a/Mailman/interfaces/mailinglist.py +++ b/Mailman/interfaces/mailinglist.py @@ -15,14 +15,206 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, # USA. -"""Marker interface for a mailing list.""" +"""Interface for a mailing list.""" from zope.interface import Interface, Attribute class IMailingList(Interface): - """Marker interface for a mailing list. + """A mailing list.""" - This is usually composed with several other interfaces. - """ + 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 + 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 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 + address to which messages are posted, e.g. mylist@example.com. It is + always comprised of the list_name + '@' + host_name. + """) + + posting_address = Attribute( + """The address to which messages are posted for copying to the full + list membership, where 'full' of course means those members for which + delivery is currently enabled. + """) + + noreply_address = Attribute( + """The address to which all messages will be immediately discarded, + without prejudice or record. This address is specific to the ddomain, + even though it's available on the IMailingListAddresses interface. + Generally, humans should never respond directly to this address. + """) + + owner_address = Attribute( + """The address which reaches the owners and moderators of the mailing + list. There is no address which reaches just the owners or just the + moderators of a mailing list. + """) + + request_address = Attribute( + """The address which reaches the email robot for this mailing list. + This robot can process various email commands such as changing + delivery options, getting information or help about the mailing list, + or processing subscrptions and unsubscriptions (although for the + latter two, it's better to use the join_address and leave_address. + """) + + bounces_address = Attribute( + """The address which reaches the automated bounce processor for this + mailing list. Generally, humans should never respond directly to this + address. + """) + + join_address = Attribute( + """The address to which subscription requests should be sent. See + subscribe_address for a backward compatible alias. + """) + + leave_address = Attribute( + """The address to which unsubscription requests should be sent. See + unsubscribe_address for a backward compatible alias. + """) + + 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. + """) + + leave_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. + """) + + 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_number = Attribute( + """A monotonically increasing integer sequentially assigned to each + list posting.""") + + last_digest_date = Attribute( + """The date and time a digest of this mailing list was last sent.""") + + owners = Attribute( + """The IUser owners of this mailing list. + + This does not include the IUsers who are moderators but not owners of + the mailing list.""") + + moderators = Attribute( + """The IUser moderators of this mailing list. + + This does not include the IUsers who are owners but not moderators of + the mailing list.""") + + administrators = Attribute( + """The IUser administrators of this mailing list. + + This includes the IUsers who are both owners and moderators of the + mailing list.""") + + members = Attribute( + """An iterator over all the members of the mailing list, regardless of + whether they are to receive regular messages or digests, or whether + they have their delivery disabled or not.""") + + regular_members = Attribute( + """An iterator over all the IMembers who are to receive regular + postings (i.e. non-digests) from the mailing list, regardless of + whether they have their delivery disabled or not.""") + + digest_members = Attribute( + """An iterator over all the IMembers who are to receive digests of + postings to this mailing list, regardless of whether they have their + deliver disabled or not, or of the type of digest they are to + receive.""") + + subscribers = Attribute( + """An iterator over all IMembers subscribed to this list, with any + role. + """) + + volume_number = Attribute( + """A monotonically increasing integer sequentially assigned to each + new digest volume. The volume number may be bumped either + automatically (i.e. on a defined schedule) or manually. When the + volume number is bumped, the digest number is always reset to 1.""") + + digest_number = Attribute( + """A sequence number for a specific digest in a given volume. When + the digest volume number is bumped, the digest number is reset to + 1.""") + + def bump(): + """Bump the digest's volume number to the next integer in the + sequence, and reset the digest number to 1. + """ + message_count = Attribute( + """The number of messages in the digest currently being collected.""") + + digest_size = Attribute( + """The approximate size in kilobytes of the digest currently being + collected.""") + + messages = Attribute( + """An iterator over all the messages in the digest currently being + created. Returns individual IPostedMessage objects. + """) + + limits = Attribute( + """An iterator over the IDigestLimiters associated with this digest. + Each limiter can make a determination of whether the digest has + reached the threshold for being automatically sent.""") + + def send(): + """Send this digest now.""" + + decorators = Attribute( + """An iterator over all the IDecorators associated with this digest. + When a digest is being sent, each decorator may modify the final + digest text.""") + + protocol = Attribute( + """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( + """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. + """ diff --git a/Mailman/interfaces/mlistdigests.py b/Mailman/interfaces/mlistdigests.py deleted file mode 100644 index bd9467b14..000000000 --- a/Mailman/interfaces/mlistdigests.py +++ /dev/null @@ -1,66 +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 digest related information.""" - -from zope.interface import Interface, Attribute - - - -class IMailingListDigests(Interface): - """Digest related information for the mailing list.""" - - volume_number = Attribute( - """A monotonically increasing integer sequentially assigned to each - new digest volume. The volume number may be bumped either - automatically (i.e. on a defined schedule) or manually. When the - volume number is bumped, the digest number is always reset to 1.""") - - digest_number = Attribute( - """A sequence number for a specific digest in a given volume. When - the digest volume number is bumped, the digest number is reset to - 1.""") - - def bump(): - """Bump the digest's volume number to the next integer in the - sequence, and reset the digest number to 1. - """ - - message_count = Attribute( - """The number of messages in the digest currently being collected.""") - - digest_size = Attribute( - """The approximate size in kilobytes of the digest currently being - collected.""") - - messages = Attribute( - """An iterator over all the messages in the digest currently being - created. Returns individual IPostedMessage objects. - """) - - limits = Attribute( - """An iterator over the IDigestLimiters associated with this digest. - Each limiter can make a determination of whether the digest has - reached the threshold for being automatically sent.""") - - def send(): - """Send this digest now.""" - - decorators = Attribute( - """An iterator over all the IDecorators associated with this digest. - When a digest is being sent, each decorator may modify the final - digest text.""") diff --git a/Mailman/interfaces/mlistemail.py b/Mailman/interfaces/mlistemail.py deleted file mode 100644 index 958ea324d..000000000 --- a/Mailman/interfaces/mlistemail.py +++ /dev/null @@ -1,78 +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 the email addresses associated with a mailing list.""" - -from zope.interface import Interface, Attribute - - - -class IMailingListAddresses(Interface): - """The email addresses associated with a mailing list. - - All attributes are read-only. - """ - - posting_address = Attribute( - """The address to which messages are posted for copying to the full - list membership, where 'full' of course means those members for which - delivery is currently enabled.""") - - noreply_address = Attribute( - """The address to which all messages will be immediately discarded, - without prejudice or record. This address is specific to the ddomain, - even though it's available on the IMailingListAddresses interface. - Generally, humans should never respond directly to this address.""") - - owner_address = Attribute( - """The address which reaches the owners and moderators of the mailing - list. There is no address which reaches just the owners or just the - moderators of a mailing list.""") - - request_address = Attribute( - """The address which reaches the email robot for this mailing list. - This robot can process various email commands such as changing - delivery options, getting information or help about the mailing list, - or processing subscrptions and unsubscriptions (although for the - latter two, it's better to use the join_address and leave_address.""") - - bounces_address = Attribute( - """The address which reaches the automated bounce processor for this - mailing list. Generally, humans should never respond directly to this - address.""") - - join_address = Attribute( - """The address to which subscription requests should be sent. See - subscribe_address for a backward compatible alias.""") - - leave_address = Attribute( - """The address to which unsubscription requests should be sent. See - unsubscribe_address for a backward compatible alias.""") - - 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.""") - - leave_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.""") - - def confirm_address(cookie=''): - """The address used for various forms of email confirmation.""" - diff --git a/Mailman/interfaces/mlistid.py b/Mailman/interfaces/mlistid.py deleted file mode 100644 index ecd4b39cb..000000000 --- a/Mailman/interfaces/mlistid.py +++ /dev/null @@ -1,46 +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 mailing list identity.""" - -from zope.interface import Interface, Attribute - - - -class IMailingListIdentity(Interface): - """The basic identifying information of a mailing list.""" - - 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 - 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 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 - address to which messages are posted, e.g. mylist@example.com. It is - always comprised of the list_name + '@' + host_name.""") diff --git a/Mailman/interfaces/mlistrosters.py b/Mailman/interfaces/mlistrosters.py deleted file mode 100644 index 86cd4ec91..000000000 --- a/Mailman/interfaces/mlistrosters.py +++ /dev/null @@ -1,68 +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 mailing list rosters and roster sets.""" - -from zope.interface import Interface, Attribute - - - -class IMailingListRosters(Interface): - """Mailing list rosters, roster sets, and members. - - This are all the email addresses that might possibly get messages from or - relating to this mailing list. - """ - - owners = Attribute( - """The IUser owners of this mailing list. - - This does not include the IUsers who are moderators but not owners of - the mailing list.""") - - moderators = Attribute( - """The IUser moderators of this mailing list. - - This does not include the IUsers who are owners but not moderators of - the mailing list.""") - - administrators = Attribute( - """The IUser administrators of this mailing list. - - This includes the IUsers who are both owners and moderators of the - mailing list.""") - - members = Attribute( - """An iterator over all the members of the mailing list, regardless of - whether they are to receive regular messages or digests, or whether - they have their delivery disabled or not.""") - - regular_members = Attribute( - """An iterator over all the IMembers who are to receive regular - postings (i.e. non-digests) from the mailing list, regardless of - whether they have their delivery disabled or not.""") - - digest_members = Attribute( - """An iterator over all the IMembers who are to receive digests of - postings to this mailing list, regardless of whether they have their - deliver disabled or not, or of the type of digest they are to - receive.""") - - subscribers = Attribute( - """An iterator over all IMembers subscribed to this list, with any - role. - """) diff --git a/Mailman/interfaces/mliststats.py b/Mailman/interfaces/mliststats.py deleted file mode 100644 index 9ed25b1ce..000000000 --- a/Mailman/interfaces/mliststats.py +++ /dev/null @@ -1,38 +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 various mailing list statistics.""" - -from zope.interface import Interface, Attribute - - - -class IMailingListStatistics(Interface): - """Various statistics of a mailing list.""" - - 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_number = Attribute( - """A monotonically increasing integer sequentially assigned to each - list posting.""") - - last_digest_date = Attribute( - """The date and time a digest of this mailing list was last sent.""") diff --git a/Mailman/interfaces/mlistweb.py b/Mailman/interfaces/mlistweb.py deleted file mode 100644 index 728fd1990..000000000 --- a/Mailman/interfaces/mlistweb.py +++ /dev/null @@ -1,46 +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 the web addresses associated with a mailing list.""" - -from zope.interface import Interface, Attribute - - - -class IMailingListWeb(Interface): - """The web addresses associated with a mailing list.""" - - protocol = Attribute( - """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( - """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. - """ diff --git a/Mailman/versions.py b/Mailman/versions.py deleted file mode 100644 index 8052db346..000000000 --- a/Mailman/versions.py +++ /dev/null @@ -1,517 +0,0 @@ -# Copyright (C) 1998-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. - - -"""Routines which rectify an old mailing list with current structure. - -The MailList.CheckVersion() method looks for an old .data_version setting in -the loaded structure, and if found calls the Update() routine from this -module, supplying the list and the state last loaded from storage. The state -is necessary to distinguish from default assignments done in the .InitVars() -methods, before .CheckVersion() is called. - -For new versions you should add sections to the UpdateOldVars() and the -UpdateOldUsers() sections, to preserve the sense of settings across structural -changes. Note that the routines have only one pass - when .CheckVersions() -finds a version change it runs this routine and then updates the data_version -number of the list, and then does a .Save(), so the transformations won't be -run again until another version change is detected. -""" - -import email -import logging - -from Mailman import Message -from Mailman import Utils -from Mailman.MemberAdaptor import UNKNOWN -from Mailman.configuration import config - -log = logging.getLogger('mailman.error') - - - -def Update(l, stored_state): - "Dispose of old vars and user options, mapping to new ones when suitable." - ZapOldVars(l) - UpdateOldUsers(l) - NewVars(l) - UpdateOldVars(l, stored_state) - CanonicalizeUserOptions(l) - NewRequestsDatabase(l) - - - -def ZapOldVars(mlist): - for name in ('num_spawns', 'filter_prog', 'clobber_date', - 'public_archive_file_dir', 'private_archive_file_dir', - 'archive_directory', - # Pre-2.1a4 bounce data - 'minimum_removal_date', - 'minimum_post_count_before_bounce_action', - 'automatic_bounce_action', - 'max_posts_between_bounces', - ): - if hasattr(mlist, name): - delattr(mlist, name) - - - -uniqueval = [] -def UpdateOldVars(l, stored_state): - """Transform old variable values into new ones, deleting old ones. - stored_state is last snapshot from file, as opposed to from InitVars().""" - - def PreferStored(oldname, newname, newdefault=uniqueval, - l=l, state=stored_state): - """Use specified old value if new value is not in stored state. - - If the old attr does not exist, and no newdefault is specified, the - new attr is *not* created - so either specify a default or be positive - that the old attr exists - or don't depend on the new attr. - - """ - if hasattr(l, oldname): - if not state.has_key(newname): - setattr(l, newname, getattr(l, oldname)) - delattr(l, oldname) - if not hasattr(l, newname) and newdefault is not uniqueval: - setattr(l, newname, newdefault) - - # Migrate to 2.1b3, baw 17-Aug-2001 - if hasattr(l, 'dont_respond_to_post_requests'): - oldval = getattr(l, 'dont_respond_to_post_requests') - if not hasattr(l, 'respond_to_post_requests'): - l.respond_to_post_requests = not oldval - del l.dont_respond_to_post_requests - - # Migrate to 2.1b3, baw 13-Oct-2001 - # Basic defaults for new variables - if not hasattr(l, 'default_member_moderation'): - l.default_member_moderation = config.DEFAULT_DEFAULT_MEMBER_MODERATION - if not hasattr(l, 'accept_these_nonmembers'): - l.accept_these_nonmembers = [] - if not hasattr(l, 'hold_these_nonmembers'): - l.hold_these_nonmembers = [] - if not hasattr(l, 'reject_these_nonmembers'): - l.reject_these_nonmembers = [] - if not hasattr(l, 'discard_these_nonmembers'): - l.discard_these_nonmembers = [] - if not hasattr(l, 'forward_auto_discards'): - l.forward_auto_discards = config.DEFAULT_FORWARD_AUTO_DISCARDS - if not hasattr(l, 'generic_nonmember_action'): - l.generic_nonmember_action = config.DEFAULT_GENERIC_NONMEMBER_ACTION - # Now convert what we can... Note that the interaction between the - # MM2.0.x attributes `moderated', `member_posting_only', and `posters' is - # so confusing, it makes my brain really ache. Which is why they go away - # in MM2.1. I think the best we can do semantically is the following: - # - # - If moderated == yes, then any sender who's address is not on the - # posters attribute would get held for approval. If the sender was on - # the posters list, then we'd defer judgement to a later step - # - If member_posting_only == yes, then members could post without holds, - # and if there were any addresses added to posters, they could also post - # without holds. - # - If member_posting_only == no, then what happens depends on the value - # of the posters attribute: - # o If posters was empty, then anybody can post without their - # message being held for approval - # o If posters was non-empty, then /only/ those addresses could post - # without approval, i.e. members not on posters would have their - # messages held for approval. - # - # How to translate this mess to MM2.1 values? I'm sure I got this wrong - # before, but here's how we're going to do it, as of MM2.1b3. - # - # - We'll control member moderation through their Moderate flag, and - # non-member moderation through the generic_nonmember_action, - # hold_these_nonmembers, and accept_these_nonmembers. - # - If moderated == yes then we need to troll through the addresses on - # posters, and any non-members would get added to - # accept_these_nonmembers. /Then/ we need to troll through the - # membership and any member on posters would get their Moderate flag - # unset, while members not on posters would get their Moderate flag set. - # Then generic_nonmember_action gets set to 1 (hold) so nonmembers get - # moderated, and default_member_moderation will be set to 1 (hold) so - # new members will also get held for moderation. We'll stop here. - # - We only get to here if moderated == no. - # - If member_posting_only == yes, then we'll turn off the Moderate flag - # for members. We troll through the posters attribute and add all those - # addresses to accept_these_nonmembers. We'll also set - # generic_nonmember_action to 1 and default_member_moderation to 0. - # We'll stop here. - # - We only get to here if member_posting_only == no - # - If posters is empty, then anybody could post without being held for - # approval, so we'll set generic_nonmember_action to 0 (accept), and - # we'll turn off the Moderate flag for all members. We'll also turn off - # default_member_moderation so new members can post without approval. - # We'll stop here. - # - We only get here if posters is non-empty. - # - This means that /only/ the addresses on posters got to post without - # being held for approval. So first, we troll through posters and add - # all non-members to accept_these_nonmembers. Then we troll through the - # membership and if their address is on posters, we'll clear their - # Moderate flag, otherwise we'll set it. We'll turn on - # default_member_moderation so new members get moderated. We'll set - # generic_nonmember_action to 1 (hold) so all other non-members will get - # moderated. And I think we're finally done. - # - # SIGH. - if hasattr(l, 'moderated'): - # We'll assume we're converting all these attributes at once - if l.moderated: - for addr in l.posters: - if not l.isMember(addr): - l.accept_these_nonmembers.append(addr) - for member in l.getMembers(): - l.setMemberOption(member, config.Moderate, - # reset for explicitly named members - member not in l.posters) - l.generic_nonmember_action = 1 - l.default_member_moderation = 1 - elif l.member_posting_only: - for addr in l.posters: - if not l.isMember(addr): - l.accept_these_nonmembers.append(addr) - for member in l.getMembers(): - l.setMemberOption(member, config.Moderate, 0) - l.generic_nonmember_action = 1 - l.default_member_moderation = 0 - elif not l.posters: - for member in l.getMembers(): - l.setMemberOption(member, config.Moderate, 0) - l.generic_nonmember_action = 0 - l.default_member_moderation = 0 - else: - for addr in l.posters: - if not l.isMember(addr): - l.accept_these_nonmembers.append(addr) - for member in l.getMembers(): - l.setMemberOption(member, config.Moderate, - # reset for explicitly named members - member not in l.posters) - l.generic_nonmember_action = 1 - l.default_member_moderation = 1 - # Now get rid of the old attributes - del l.moderated - del l.posters - del l.member_posting_only - if hasattr(l, 'forbidden_posters'): - # For each of the posters on this list, if they are members, toggle on - # their moderation flag. If they are not members, then add them to - # hold_these_nonmembers. - forbiddens = l.forbidden_posters - for addr in forbiddens: - if l.isMember(addr): - l.setMemberOption(addr, config.Moderate, 1) - else: - l.hold_these_nonmembers.append(addr) - del l.forbidden_posters - - # Migrate to 1.0b6, klm 10/22/1998: - PreferStored('reminders_to_admins', 'umbrella_list', - config.DEFAULT_UMBRELLA_LIST) - - # Migrate up to 1.0b5: - PreferStored('auto_subscribe', 'open_subscribe') - PreferStored('closed', 'private_roster') - PreferStored('mimimum_post_count_before_removal', - 'mimimum_post_count_before_bounce_action') - PreferStored('bad_posters', 'forbidden_posters') - PreferStored('automatically_remove', 'automatic_bounce_action') - if hasattr(l, "open_subscribe"): - if l.open_subscribe: - if config.ALLOW_OPEN_SUBSCRIBE: - l.subscribe_policy = 0 - else: - l.subscribe_policy = 1 - else: - l.subscribe_policy = 2 # admin approval - delattr(l, "open_subscribe") - if not hasattr(l, "administrivia"): - setattr(l, "administrivia", config.DEFAULT_ADMINISTRIVIA) - if not hasattr(l, "admin_member_chunksize"): - setattr(l, "admin_member_chunksize", - config.DEFAULT_ADMIN_MEMBER_CHUNKSIZE) - # - # this attribute was added then deleted, so there are a number of - # cases to take care of - # - if hasattr(l, "posters_includes_members"): - if l.posters_includes_members: - if l.posters: - l.member_posting_only = 1 - else: - if l.posters: - l.member_posting_only = 0 - delattr(l, "posters_includes_members") - elif l.data_version <= 10 and l.posters: - # make sure everyone gets the behavior the list used to have, but only - # for really old versions of Mailman (1.0b5 or before). Any newer - # version of Mailman should not get this attribute whacked. - l.member_posting_only = 0 - # - # transfer the list data type for holding members and digest members - # to the dict data type starting file format version 11 - # - if isinstance(l.members, list): - members = {} - for m in l.members: - members[m] = 1 - l.members = members - if isinstance(l.digest_members, list): - dmembers = {} - for dm in l.digest_members: - dmembers[dm] = 1 - l.digest_members = dmembers - # - # set admin_notify_mchanges - # - if not hasattr(l, "admin_notify_mchanges"): - setattr(l, "admin_notify_mchanges", - config.DEFAULT_ADMIN_NOTIFY_MCHANGES) - # - # Convert the members and digest_members addresses so that the keys of - # both these are always lowercased, but if there is a case difference, the - # value contains the case preserved value - # - for k in l.members.keys(): - if k.lower() <> k: - l.members[k.lower()] = Utils.LCDomain(k) - del l.members[k] - elif isinstance(l.members[k], str) and k == l.members[k].lower(): - # already converted - pass - else: - l.members[k] = 0 - for k in l.digest_members.keys(): - if k.lower() <> k: - l.digest_members[k.lower()] = Utils.LCDomain(k) - del l.digest_members[k] - elif isinstance(l.digest_members[k], str) and \ - k == l.digest_members[k].lower(): - # already converted - pass - else: - l.digest_members[k] = 0 - # - # Convert pre 2.2 topics regexps which were compiled in verbose mode - # to a non-verbose equivalent. - # - if stored_state['data_version'] <= 97 and stored_state.has_key('topics'): - l.topics = [] - for name, pattern, description, emptyflag in stored_state['topics']: - pattern = Utils.strip_verbose_pattern(pattern) - l.topics.append((name, pattern, description, emptyflag)) - - - -def NewVars(l): - """Add defaults for these new variables if they don't exist.""" - def add_only_if_missing(attr, initval, l=l): - if not hasattr(l, attr): - setattr(l, attr, initval) - # 1.2 beta 1, baw 18-Feb-2000 - # Autoresponder mixin class attributes - add_only_if_missing('autorespond_postings', 0) - add_only_if_missing('autorespond_admin', 0) - add_only_if_missing('autorespond_requests', 0) - add_only_if_missing('autoresponse_postings_text', '') - add_only_if_missing('autoresponse_admin_text', '') - add_only_if_missing('autoresponse_request_text', '') - add_only_if_missing('autoresponse_graceperiod', 90) - add_only_if_missing('postings_responses', {}) - add_only_if_missing('admin_responses', {}) - add_only_if_missing('reply_goes_to_list', '') - add_only_if_missing('preferred_language', config.DEFAULT_SERVER_LANGUAGE) - add_only_if_missing('available_languages', []) - add_only_if_missing('digest_volume_frequency', - config.DEFAULT_DIGEST_VOLUME_FREQUENCY) - add_only_if_missing('digest_last_sent_at', 0) - add_only_if_missing('mod_password', None) - add_only_if_missing('moderator', []) - add_only_if_missing('topics', []) - add_only_if_missing('topics_enabled', 0) - add_only_if_missing('topics_bodylines_limit', 5) - add_only_if_missing('one_last_digest', {}) - add_only_if_missing('usernames', {}) - add_only_if_missing('personalize', 0) - add_only_if_missing('first_strip_reply_to', - config.DEFAULT_FIRST_STRIP_REPLY_TO) - add_only_if_missing('subscribe_auto_approval', - config.DEFAULT_SUBSCRIBE_AUTO_APPROVAL) - add_only_if_missing('unsubscribe_policy', - config.DEFAULT_UNSUBSCRIBE_POLICY) - add_only_if_missing('send_goodbye_msg', config.DEFAULT_SEND_GOODBYE_MSG) - add_only_if_missing('include_rfc2369_headers', 1) - add_only_if_missing('include_list_post_header', 1) - add_only_if_missing('bounce_score_threshold', - config.DEFAULT_BOUNCE_SCORE_THRESHOLD) - add_only_if_missing('bounce_info_stale_after', - config.DEFAULT_BOUNCE_INFO_STALE_AFTER) - add_only_if_missing('bounce_you_are_disabled_warnings', - config.DEFAULT_BOUNCE_YOU_ARE_DISABLED_WARNINGS) - add_only_if_missing( - 'bounce_you_are_disabled_warnings_interval', - config.DEFAULT_BOUNCE_YOU_ARE_DISABLED_WARNINGS_INTERVAL) - add_only_if_missing( - 'bounce_unrecognized_goes_to_list_owner', - config.DEFAULT_BOUNCE_UNRECOGNIZED_GOES_TO_LIST_OWNER) - add_only_if_missing( - 'bounce_notify_owner_on_disable', - config.DEFAULT_BOUNCE_NOTIFY_OWNER_ON_DISABLE) - add_only_if_missing( - 'bounce_notify_owner_on_removal', - config.DEFAULT_BOUNCE_NOTIFY_OWNER_ON_REMOVAL) - add_only_if_missing('ban_list', []) - add_only_if_missing('filter_mime_types', config.DEFAULT_FILTER_MIME_TYPES) - add_only_if_missing('pass_mime_types', config.DEFAULT_PASS_MIME_TYPES) - add_only_if_missing('filter_content', config.DEFAULT_FILTER_CONTENT) - add_only_if_missing('convert_html_to_plaintext', - config.DEFAULT_CONVERT_HTML_TO_PLAINTEXT) - add_only_if_missing('filter_action', config.DEFAULT_FILTER_ACTION) - add_only_if_missing('delivery_status', {}) - # This really ought to default to config.HOLD, but that doesn't work with - # the current GUI description model. So, 0==Hold, 1==Reject, 2==Discard - add_only_if_missing('member_moderation_action', 0) - add_only_if_missing('member_moderation_notice', '') - add_only_if_missing('new_member_options', - config.DEFAULT_NEW_MEMBER_OPTIONS) - # Emergency moderation flag - add_only_if_missing('emergency', 0) - add_only_if_missing('hold_and_cmd_autoresponses', {}) - add_only_if_missing('news_prefix_subject_too', 1) - # Should prefixes be encoded? - if Utils.GetCharSet(l.preferred_language) == 'us-ascii': - encode = 0 - else: - encode = 2 - add_only_if_missing('encode_ascii_prefixes', encode) - add_only_if_missing('news_moderation', 0) - add_only_if_missing('header_filter_rules', []) - # Scrubber in regular delivery - add_only_if_missing('scrub_nondigest', 0) - # ContentFilter by file extensions - add_only_if_missing('filter_filename_extensions', - config.DEFAULT_FILTER_FILENAME_EXTENSIONS) - add_only_if_missing('pass_filename_extensions', []) - # automatic discard - add_only_if_missing('max_days_to_hold', 0) - add_only_if_missing('nonmember_rejection_notice', '') - # multipart/alternative collapse - add_only_if_missing('collapse_alternatives', - config.DEFAULT_COLLAPSE_ALTERNATIVES) - - - -def UpdateOldUsers(mlist): - """Transform sense of changed user options.""" - # pre-1.0b11 to 1.0b11. Force all keys in l.passwords to be lowercase - passwords = {} - for k, v in mlist.passwords.items(): - passwords[k.lower()] = v - mlist.passwords = passwords - # Go through all the keys in bounce_info. If the key is not a member, or - # if the data is not a _BounceInfo instance, chuck the bounce info. We're - # doing things differently now. - from Mailman.Bouncer import _BounceInfo - for m in mlist.bounce_info.keys(): - if not mlist.isMember(m) or not isinstance(mlist.getBounceInfo(m), - _BounceInfo): - del mlist.bounce_info[m] - - - -def CanonicalizeUserOptions(l): - """Fix up the user options.""" - # I want to put a flag in the list database which tells this routine to - # never try to canonicalize the user options again. - if getattr(l, 'useropts_version', 0) > 0: - return - # pre 1.0rc2 to 1.0rc3. For all keys in l.user_options to be lowercase, - # but merge options for both cases - options = {} - for k, v in l.user_options.items(): - if k is None: - continue - lcuser = k.lower() - flags = 0 - if options.has_key(lcuser): - flags = options[lcuser] - flags |= v - options[lcuser] = flags - l.user_options = options - # 2.1alpha3 -> 2.1alpha4. The DisableDelivery flag is now moved into - # get/setDeilveryStatus(). This must be done after the addresses are - # canonicalized. - for k, v in l.user_options.items(): - if not l.isMember(k): - # There's a key in user_options that isn't associated with a real - # member address. This is likely caused by an earlier bug. - del l.user_options[k] - continue - if l.getMemberOption(k, config.DisableDelivery): - # Convert this flag into a legacy disable - l.setDeliveryStatus(k, UNKNOWN) - l.setMemberOption(k, config.DisableDelivery, 0) - l.useropts_version = 1 - - - -def NewRequestsDatabase(l): - """With version 1.2, we use a new pending request database schema.""" - r = getattr(l, 'requests', {}) - if not r: - # no old-style requests - return - for k, v in r.items(): - if k == 'post': - # This is a list of tuples with the following format - # - # a sequential request id integer - # a timestamp float - # a message tuple: (author-email-str, message-text-str) - # a reason string - # the subject string - # - # We'll re-submit this as a new HoldMessage request, but we'll - # blow away the original timestamp and request id. This means the - # request will live a little longer than it possibly should have, - # but that's no big deal. - for p in v: - author, text = p[2] - reason = p[3] - msg = email.message_from_string(text, Message.Message) - l.HoldMessage(msg, reason) - del r[k] - elif k == 'add_member': - # This is a list of tuples with the following format - # - # a sequential request id integer - # a timestamp float - # a digest flag (0 == nodigest, 1 == digest) - # author-email-str - # password - # - # See the note above; the same holds true. - for ign, ign, digest, addr, password in v: - l.HoldSubscription(addr, '', password, digest, - config.DEFAULT_SERVER_LANGUAGE) - del r[k] - else: - log.error("""\ -VERY BAD NEWS. Unknown pending request type `%s' found for list: %s""", - k, l.internal_name()) |
