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
Removed a bunch of files that are obsolete. The interfaces are all folded
into the IMailingList interface. OTOH, MemberAdaptor.py is completely useless now (though not entirely eradicated), as is OldStyleMemberships.py. versions.py isn't necessary any longer either because we'll have to do database migrations (and conversions from MM2.1) completely differently. New command line script 'set_members' which is used to take a CSV file and syncing that to a list's membership. Added back the DeliveryStatus.unknown item because we'll need it when we migrate MM 2.1 databases.
-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())