summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBarry Warsaw2007-09-27 22:15:00 -0400
committerBarry Warsaw2007-09-27 22:15:00 -0400
commit4a2ca2c159accdca7e1101a94efb5a4499407b72 (patch)
tree1d2d507bbe53cb071aa15095f67edd76434bf836
parent65c64773d910b3b2a3e2a9b9db4669e57170ece2 (diff)
downloadmailman-4a2ca2c159accdca7e1101a94efb5a4499407b72.tar.gz
mailman-4a2ca2c159accdca7e1101a94efb5a4499407b72.tar.zst
mailman-4a2ca2c159accdca7e1101a94efb5a4499407b72.zip
-rw-r--r--Mailman/Bouncer.py29
-rw-r--r--Mailman/Defaults.py3
-rw-r--r--Mailman/Handlers/ToDigest.py1
-rw-r--r--Mailman/MailList.py20
-rw-r--r--Mailman/MemberAdaptor.py388
-rw-r--r--Mailman/OldStyleMemberships.py379
-rw-r--r--Mailman/Version.py9
-rw-r--r--Mailman/bin/__init__.py1
-rw-r--r--Mailman/bin/list_lists.py1
-rw-r--r--Mailman/bin/list_members.py1
-rw-r--r--Mailman/bin/newlist.py1
-rw-r--r--Mailman/bin/set_members.py186
-rw-r--r--Mailman/constants.py2
-rw-r--r--Mailman/database/model/mailinglist.py10
-rw-r--r--Mailman/interfaces/mailinglist.py200
-rw-r--r--Mailman/interfaces/mlistdigests.py66
-rw-r--r--Mailman/interfaces/mlistemail.py78
-rw-r--r--Mailman/interfaces/mlistid.py46
-rw-r--r--Mailman/interfaces/mlistrosters.py68
-rw-r--r--Mailman/interfaces/mliststats.py38
-rw-r--r--Mailman/interfaces/mlistweb.py46
-rw-r--r--Mailman/versions.py517
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())