summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbwarsaw2006-12-29 22:20:25 +0000
committerbwarsaw2006-12-29 22:20:25 +0000
commitf4a456a83b630feb294724ab462c87ca1ce1c3ae (patch)
treec5c88540dae8306d11671f603d8975b01803ea16
parentae185106a624bfa7888aa8722d35194d3c5150e8 (diff)
downloadmailman-f4a456a83b630feb294724ab462c87ca1ce1c3ae.tar.gz
mailman-f4a456a83b630feb294724ab462c87ca1ce1c3ae.tar.zst
mailman-f4a456a83b630feb294724ab462c87ca1ce1c3ae.zip
Merged revisions 8113-8121 via svnmerge from
https://mailman.svn.sourceforge.net/svnroot/mailman/branches/tmp-sqlalchemy-branch ................ r8114 | bwarsaw | 2006-12-06 00:16:54 -0500 (Wed, 06 Dec 2006) | 44 lines Initial take on using SQLAlchemy to store list data in lieu of Python pickles. While all the list data (including OldStyleMemberships attributes) are stored in the database, many attributes are stored as PickleTypes binary data. This isn't idea but it gets things working until a more sophisticated schema can be developed. MailList class is now a new-style class, as is required by SQLAlchemy. This makes several things, er, interesting. Rip out all the low-level pickle reading and writing stuff. Hook SA transaction events into Lock() and Unlock(). Move the hooking of the _memberadaptor into InitTempVars(), which gets called by the SQLAlchemy hooks (MailList.__init__() never is). Add an initialize.py module which centralizes all the initialization bits that command line scripts have to do, including configuration, logging, and atabase initialization. This change also converts bin/withlist to mmshell wrapper. Update to SQLAlchemy 0.3.1. Revamp paths.py.in considerably. There were several problems with the old way. We no longer disable default loading of site-packages so we don't need to add Python's site-packages back to sys.path. Also, because site.addsitedir() causes things like .pth paths to be /appended/ to sys.path, they actually won't override any site-installed packages. E.g. if SQLAlchemy is installed in the system Python, our version will not override. IIUC, setuptools-based packages can be configured to work properly in the face of package versions, however not all packages we currently depend on are setuptools-based. So instead, we steal a bit of stuff from site.py but change things so the prepend .pth stuff to sys.path. Update several modules to use True/False and whitespace normalization. Convert from mm_cfg to config object. Modernize a few coding constructs. Add a couple of exceptions to handle database problems. In the export script, include the widget type in the elements. This helped in my stupid little throw away conversion script, but I think it will be more generally useful. Add an interact.py module which refactors interactive interpreter access. Mostly this is used by withlist -i, but it lets us import Mailman.interact and drop into a prompt just about anywhere (e.g. debugging). ................ r8115 | bwarsaw | 2006-12-07 09:13:56 -0500 (Thu, 07 Dec 2006) | 22 lines Start to flesh out more of the SQLAlchemy mechanisms. Added a MailList.__new__() which hooks instantiation to use a query on dbcontext to get an existing mailing list. A 'no-args' call means we're doing a Create(), though eventually that will change too. For now, disable the CheckVersion() call. Eventually this will be folded into schema migration. list_exists(): Rewrite to use the dbcontext query to determine if the named mailing list exists or not. Requires the fqdn_listname. Eradicate two failed member adaptors: BDBMemberAdaptor and SAMemberships. Change the way the DBContext holds onto tables. It now keeps a dictionary mapping the table's name to the SA Table instance. This makes it easier to look up and use the individual tables. Add 'web_page_url' as an attribute managed by SA, and remove a debugging print. ................ r8116 | bwarsaw | 2006-12-11 07:27:47 -0500 (Mon, 11 Dec 2006) | 29 lines Rework the whole dbcontext and transaction framework. SA already handles nested transactions so we don't have to worry about them. However, we do have the weird situation where some transactions are tied to MailList .Lock()/.Unlock()/.Save() and some are tied to non-mlist actions. So now we use an @txn decorator to put methods in a session transaction, but then we also hook into the above MailList methods as possibly sub-transactions. We use a weakref subclass to manage the MailList interface, with a dictionary mapping MailList fqdn_listnames against transactions. The weakrefs come in by giving us a callback when a MailList gets derefed such that we're guaranteed to rollback any outstanding transaction. Also, we have one global DBContext instance but rather than force the rest of Mailman to deal with context objects, instead we expose API methods on that object into the Mailman.database module, which the rest of the code will use. Such methods must be prepended with 'api_' to get exposed this way. bin/rmlist now works with the SA-backend. I refactored the code here so that other code (namely, the test suite) can more easily and consistently remove a mailing list. This isn't the best place for it ultimately, but it's good enough for now. New convenience functions Utils.split_listname(), .fqdn_listname(). Convert testall to use Mailman.initialize.initialize(). Not all tests work, but I'm down to only 8 failures and 7 errors. Also, do a better job of recovering from failures in setUp(). MailList.__new__() now takes keyword arguments. ................ r8117 | bwarsaw | 2006-12-11 22:58:06 -0500 (Mon, 11 Dec 2006) | 7 lines Unit test repairs; even though the unit tests are still pretty fragile, everything now passes with the SQLAlchemy storage of list data. Added missing 'personalize' column. Converted mailmanctl and qrunner to initialize() interface. Fixed _cookie_path() to not fail if SCRIPT_NAME is not in the environment. ................ r8118 | bwarsaw | 2006-12-27 18:45:41 -0500 (Wed, 27 Dec 2006) | 21 lines Utils.list_names(): Use a database query to get all the list names. dbcontext.py: Added api_get_list_names() to support Utils.list_names(). listdata.py: Added two additional MailList attributes which need to be stored in the database. The first is 'admin_member_chunksize' which isn't modifiable from the web. The second is 'password' which holds the list's password. HTMLFormatObject: item strings can now be unicodes. bin/list_lists.py: Must call initialize() to get the database properly initialized, not just config.load(). This will be a common theme. SecurityManager.py: - Remove md5 and crypt support - Added mailman.debug logger, though it will be only used during debugging. - The 'secret' can be a unicode now. - A few coding style updates; repr() instead of backticks, 'key in dict' instead of 'dict.has_key(key)' ................ r8119 | bwarsaw | 2006-12-27 19:13:09 -0500 (Wed, 27 Dec 2006) | 2 lines genaliases.py: config.load() -> initialize() ................ r8120 | bwarsaw | 2006-12-27 19:17:26 -0500 (Wed, 27 Dec 2006) | 9 lines Blocked revisions 8113 via svnmerge ........ r8113 | bwarsaw | 2006-12-05 23:54:30 -0500 (Tue, 05 Dec 2006) | 3 lines Initialized merge tracking via "svnmerge" with revisions "1-8112" from https://mailman.svn.sourceforge.net/svnroot/mailman/branches/tmp-sqlalchemy-branch ........ ................ r8121 | bwarsaw | 2006-12-28 23:34:52 -0500 (Thu, 28 Dec 2006) | 20 lines Remove SIGTERM handling from all the CGI scripts. This messes with HTTPRunner because when you issue "mailmanctl stop" after the signal handler has been installed, the process will get a SIGTERM, the signal handler will run, and the process will exit with a normal zero code. This will cause mailmanctl to try to restart the HTTPRunner. I don't think we need that stuff at all when running under wsgi with a SQLAlchemy backend. If mailmanctl kills the HTTPRunner in the middle of the process, I believe (but have not tested) that the transaction should get properly rolled back at process exit. We need to make sure about this, and also we need to test the signal handling functionality under traditional CGI environment (if we even still want to support that). Also, make sure that we don't try to initialize the loggers twice in qrunner. This was the cause of all the double entries in logs/qrunner. Fix a coding style nit in mailmanctl.py. De-DOS-ify line endings in loginit.py. ................
-rw-r--r--Mailman/Autoresponder.py22
-rw-r--r--Mailman/BDBMemberAdaptor.py636
-rw-r--r--Mailman/Bouncer.py25
-rw-r--r--Mailman/Cgi/admin.py37
-rw-r--r--Mailman/Cgi/admindb.py20
-rw-r--r--Mailman/Cgi/create.py16
-rw-r--r--Mailman/Cgi/options.py36
-rw-r--r--Mailman/Cgi/subscribe.py20
-rw-r--r--Mailman/Defaults.py.in22
-rw-r--r--Mailman/Digester.py51
-rw-r--r--Mailman/Errors.py16
-rw-r--r--Mailman/GatewayManager.py19
-rw-r--r--Mailman/MTA/Postfix.py2
-rw-r--r--Mailman/MailList.py234
-rw-r--r--Mailman/Makefile.in2
-rw-r--r--Mailman/Pending.py19
-rw-r--r--Mailman/Queue/HTTPRunner.py8
-rw-r--r--Mailman/SAMemberships.py330
-rw-r--r--Mailman/SecurityManager.py65
-rw-r--r--Mailman/TopicMgr.py19
-rw-r--r--Mailman/Utils.py44
-rw-r--r--Mailman/Version.py4
-rw-r--r--Mailman/bin/export.py29
-rw-r--r--Mailman/bin/genaliases.py3
-rw-r--r--Mailman/bin/list_lists.py4
-rw-r--r--Mailman/bin/mailmanctl.py8
-rw-r--r--Mailman/bin/newlist.py8
-rw-r--r--Mailman/bin/qrunner.py5
-rw-r--r--Mailman/bin/rmlist.py100
-rw-r--r--Mailman/bin/testall.py4
-rw-r--r--Mailman/bin/update.py3
-rw-r--r--Mailman/bin/withlist.py249
-rw-r--r--Mailman/configuration.py8
-rw-r--r--Mailman/database/Makefile.in71
-rw-r--r--Mailman/database/__init__.py34
-rw-r--r--Mailman/database/address.py30
-rw-r--r--Mailman/database/dbcontext.py144
-rw-r--r--Mailman/database/listdata.py167
-rw-r--r--Mailman/database/txnsupport.py34
-rw-r--r--Mailman/database/version.py31
-rw-r--r--Mailman/htmlformat.py9
-rw-r--r--Mailman/initialize.py36
-rw-r--r--Mailman/interact.py68
-rw-r--r--Mailman/loginit.py265
-rw-r--r--Mailman/testing/base.py18
-rw-r--r--Mailman/testing/emailbase.py9
-rw-r--r--Mailman/testing/test_handlers.py19
-rw-r--r--Mailman/testing/test_message.py4
-rw-r--r--Mailman/testing/test_security_mgr.py8
-rw-r--r--Makefile.in7
-rw-r--r--bin/Makefile.in4
-rw-r--r--bin/withlist304
-rwxr-xr-xconfigure6
-rw-r--r--configure.in5
-rw-r--r--messages/de/LC_MESSAGES/mailman.po608
-rw-r--r--messages/fr/LC_MESSAGES/mailman.po374
-rw-r--r--messages/nl/LC_MESSAGES/mailman.po54
-rw-r--r--misc/Makefile.in2
-rw-r--r--misc/SQLAlchemy-0.3.0.tar.gzbin547551 -> 0 bytes
-rw-r--r--misc/SQLAlchemy-0.3.1.tar.gzbin0 -> 585351 bytes
-rw-r--r--misc/paths.py.in81
-rw-r--r--scripts/driver37
62 files changed, 2020 insertions, 2477 deletions
diff --git a/Mailman/Autoresponder.py b/Mailman/Autoresponder.py
index aa29e733c..bfa3d3c09 100644
--- a/Mailman/Autoresponder.py
+++ b/Mailman/Autoresponder.py
@@ -1,32 +1,29 @@
-# Copyright (C) 1998,1999,2000,2001,2002 by the Free Software Foundation, Inc.
+# Copyright (C) 1998-2006 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.
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+# USA.
-"""MailList mixin class managing the autoresponder.
-"""
-
-from Mailman import mm_cfg
-from Mailman.i18n import _
+"""MailList mixin class managing the autoresponder."""
class Autoresponder:
def InitVars(self):
# configurable
- self.autorespond_postings = 0
- self.autorespond_admin = 0
+ self.autorespond_postings = False
+ self.autorespond_admin = False
# this value can be
# 0 - no autoresponse on the -request line
# 1 - autorespond, but discard the original message
@@ -40,4 +37,3 @@ class Autoresponder:
self.postings_responses = {}
self.admin_responses = {}
self.request_responses = {}
-
diff --git a/Mailman/BDBMemberAdaptor.py b/Mailman/BDBMemberAdaptor.py
deleted file mode 100644
index 4cd8eaf30..000000000
--- a/Mailman/BDBMemberAdaptor.py
+++ /dev/null
@@ -1,636 +0,0 @@
-# Copyright (C) 2003-2006 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.
-
-"""A MemberAdaptor based on the Berkeley database wrapper for Python.
-
-Requires Python 2.2.2 or newer, and PyBSDDB3 4.1.3 or newer.
-"""
-
-# To use, put the following in a file called extend.py in the mailing list's
-# directory:
-#
-# from Mailman.BDBMemberAdaptor import extend
-#
-# that's it!
-
-import os
-import new
-import time
-import errno
-import struct
-import cPickle as pickle
-
-try:
- # Python 2.3
- from bsddb import db
-except ImportError:
- # earlier Pythons
- from bsddb3 import db
-
-from Mailman import mm_cfg
-from Mailman import Utils
-from Mailman import Errors
-from Mailman import MemberAdaptor
-from Mailman.MailList import MailList
-
-STORAGE_VERSION = 'BA01'
-FMT = '>BHB'
-FMTSIZE = struct.calcsize(FMT)
-
-REGDELIV = 1
-DIGDELIV = 2
-REGFLAG = struct.pack('>B', REGDELIV)
-DIGFLAG = struct.pack('>B', DIGDELIV)
-
-# Positional arguments for _unpack()
-CPADDR = 0
-PASSWD = 1
-LANG = 2
-NAME = 3
-DIGEST = 4
-OPTIONS = 5
-STATUS = 6
-
-
-
-class BDBMemberAdaptor(MemberAdaptor.MemberAdaptor):
- def __init__(self, mlist):
- self._mlist = mlist
- # metainfo -- {key -> value}
- # This table contains storage metadata information. The keys and
- # values are simple strings of variable length. Here are the
- # valid keys:
- #
- # version - the version of the database
- #
- # members -- {address | rec}
- # For all regular delivery members, this maps from the member's
- # key to their data record, which is a string concatenated of the
- # following:
- #
- # -- fixed data (as a packed struct)
- # + 1-byte digest or regular delivery flag
- # + 2-byte option flags
- # + 1-byte delivery status
- # -- variable data (as a pickle of a tuple)
- # + their case preserved address or ''
- # + their plaintext password
- # + their chosen language
- # + their realname or ''
- #
- # status -- {address | status+time}
- # Maps the member's key to their delivery status and change time.
- # These are passed as a tuple and are pickled for storage.
- #
- # topics -- {address | topicstrings}
- # Maps the member's key to their topic strings, concatenated and
- # separated by SEP
- #
- # bounceinfo -- {address | bounceinfo}
- # Maps the member's key to their bounceinfo, as a pickle
- #
- # Make sure the database directory exists
- path = os.path.join(mlist.fullpath(), 'member.db')
- exists = False
- try:
- os.mkdir(path, 02775)
- except OSError, e:
- if e.errno <> errno.EEXIST: raise
- exists = True
- # Create the environment
- self._env = env = db.DBEnv()
- if exists:
- # We must join an existing environment, otherwise we'll get
- # DB_RUNRECOVERY errors when the second process to open the
- # environment begins a transaction. I don't get it.
- env.open(path, db.DB_JOINENV)
- else:
- env.open(path,
- db.DB_CREATE |
- db.DB_RECOVER |
- db.DB_INIT_MPOOL |
- db.DB_INIT_TXN
- )
- self._txn = None
- self._tables = []
- self._metainfo = self._setupDB('metainfo')
- self._members = self._setupDB('members')
- self._status = self._setupDB('status')
- self._topics = self._setupDB('topics')
- self._bounceinfo = self._setupDB('bounceinfo')
- # Check the database version number
- version = self._metainfo.get('version')
- if version is None:
- # Initialize
- try:
- self.txn_begin()
- self._metainfo.put('version', STORAGE_VERSION, txn=self._txn)
- except:
- self.txn_abort()
- raise
- else:
- self.txn_commit()
- else:
- # Currently there's nothing to upgrade
- assert version == STORAGE_VERSION
-
- def _setupDB(self, name):
- d = db.DB(self._env)
- openflags = db.DB_CREATE
- # db 4.1 requires that databases be opened in a transaction. We'll
- # use auto commit, but only if that flag exists (i.e. we're using at
- # least db 4.1).
- try:
- openflags |= db.DB_AUTO_COMMIT
- except AttributeError:
- pass
- d.open(name, db.DB_BTREE, openflags)
- self._tables.append(d)
- return d
-
- def _close(self):
- self.txn_abort()
- for d in self._tables:
- d.close()
- # Checkpoint the database twice, as recommended by Sleepycat
- self._checkpoint()
- self._checkpoint()
- self._env.close()
-
- def _checkpoint(self):
- self._env.txn_checkpoint(0, 0, db.DB_FORCE)
-
- def txn_begin(self):
- assert self._txn is None
- self._txn = self._env.txn_begin()
-
- def txn_commit(self):
- assert self._txn is not None
- self._txn.commit()
- self._checkpoint()
- self._txn = None
-
- def txn_abort(self):
- if self._txn is not None:
- self._txn.abort()
- self._checkpoint()
- self._txn = None
-
- def _unpack(self, member):
- # Assume member is a LCE (i.e. lowercase key)
- rec = self._members.get(member.lower())
- assert rec is not None
- fixed = struct.unpack(FMT, rec[:FMTSIZE])
- vari = pickle.loads(rec[FMTSIZE:])
- return vari + fixed
-
- def _pack(self, member, cpaddr, passwd, lang, name, digest, flags, status):
- # Assume member is a LCE (i.e. lowercase key)
- fixed = struct.pack(FMT, digest, flags, status)
- vari = pickle.dumps((cpaddr, passwd, lang, name))
- self._members.put(member.lower(), fixed+vari, txn=self._txn)
-
- # MemberAdaptor writeable 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 = False
- 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()
- # Should we store the case-preserved address?
- if Utils.LCDomain(member) == member.lower():
- cpaddress = ''
- else:
- cpaddress = member
- # Calculate the realname
- if realname is None:
- realname = ''
- # Calculate the digest flag
- if digest:
- digest = DIGDELIV
- else:
- digest = REGDELIV
- self._pack(member.lower(),
- cpaddress, password, language, realname,
- digest, self._mlist.new_member_options,
- MemberAdaptor.ENABLED)
-
- def removeMember(self, member):
- txn = self._txn
- assert txn is not None
- assert self._mlist.Locked()
- self.__assertIsMember(member)
- key = member.lower()
- # Remove the table entries
- self._members.delete(key, txn=txn)
- if self._status.has_key(key):
- self._status.delete(key, txn=txn)
- if self._topics.has_key(key):
- self._topics.delete(key, txn=txn)
- if self._bounceinfo.has_key(key):
- self._bounceinfo.delete(key, txn=txn)
-
- def changeMemberAddress(self, member, newaddress, nodelete=0):
- assert self._mlist.Locked()
- self.__assertIsMember(member)
- okey = member.lower()
- nkey = newaddress.lower()
- txn = self._txn
- assert txn is not None
- # First, store a new member record, changing the case preserved addr.
- # Then delete the old record.
- cpaddr, passwd, lang, name, digest, flags, sts = self._unpack(okey)
- self._pack(nkey, newaddress, passwd, lang, name, digest, flags, sts)
- if not nodelete:
- self._members.delete(okey, txn)
- # Copy over the status times, topics, and bounce info, if present
- timestr = self._status.get(okey)
- if timestr is not None:
- self._status.put(nkey, timestr, txn=txn)
- if not nodelete:
- self._status.delete(okey, txn)
- topics = self._topics.get(okey)
- if topics is not None:
- self._topics.put(nkey, topics, txn=txn)
- if not nodelete:
- self._topics.delete(okey, txn)
- binfo = self._bounceinfo.get(nkey)
- if binfo is not None:
- self._binfo.put(nkey, binfo, txn=txn)
- if not nodelete:
- self._binfo.delete(okey, txn)
-
- def setMemberPassword(self, member, password):
- assert self._mlist.Locked()
- self.__assertIsMember(member)
- member = member.lower()
- cpaddr, oldpw, lang, name, digest, flags, status = self._unpack(member)
- self._pack(member, cpaddr, password, lang, name, digest, flags, status)
-
- def setMemberLanguage(self, member, language):
- assert self._mlist.Locked()
- self.__assertIsMember(member)
- member = member.lower()
- cpaddr, passwd, olang, name, digest, flags, sts = self._unpack(member)
- self._pack(member, cpaddr, passwd, language, name, digest, flags, sts)
-
- def setMemberOption(self, member, flag, value):
- assert self._mlist.Locked()
- self.__assertIsMember(member)
- member = member.lower()
- cpaddr, passwd, lang, name, digest, options, sts = self._unpack(member)
- # Sanity check for the digest flag
- if flag == mm_cfg.Digests:
- if value:
- # Be sure the list supports digest delivery
- if not self._mlist.digestable:
- raise Errors.CantDigestError
- digest = DIGDELIV
- else:
- # Be sure the list supports regular delivery
- if not self._mlist.nondigestable:
- raise Errors.MustDigestError
- # 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[member] = cpaddr
- digest = REGDELIV
- else:
- if value:
- options |= flag
- else:
- options &= ~flag
- self._pack(member, cpaddr, passwd, lang, name, digest, options, sts)
-
- def setMemberName(self, member, realname):
- assert self._mlist.Locked()
- self.__assertIsMember(member)
- member = member.lower()
- cpaddr, passwd, lang, oldname, digest, flags, sts = self._unpack(
- member)
- self._pack(member, cpaddr, passwd, lang, realname, digest, flags, sts)
-
- def setMemberTopics(self, member, topics):
- assert self._mlist.Locked()
- self.__assertIsMember(member)
- member = member.lower()
- if topics:
- self._topics.put(member, SEP.join(topics), txn=self._txn)
- elif self._topics.has_key(member):
- # No record is the same as no topics
- self._topics.delete(member, self._txn)
-
- 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)
- if status == MemberAdaptor.ENABLED:
- # Enable by resetting their bounce info
- self.setBounceInfo(member, None)
- else:
- # Pickle up the status an the current time and store that in the
- # database. Use binary mode.
- data = pickle.dumps((status, time.time()), 1)
- self._status.put(member.lower(), data, txn=self._txn)
-
- def setBounceInfo(self, member, info):
- assert self._mlist.Locked()
- self.__assertIsMember(member)
- member = member.lower()
- if info is None:
- # This means to reset the bounce and delivery status information
- if self._bounceinfo.has_key(member):
- self._bounceinfo.delete(member, self._txn)
- if self._status.has_key(member):
- self._status.delete(member, self._txn)
- else:
- # Use binary mode
- data = pickle.dumps(info, 1)
- self._status.put(member, data, txn=self._txn)
-
- # The readable interface
-
- # BAW: It would be more efficient to simply return the iterator, but
- # modules like admin.py can't handle that yet. They requires lists.
- def getMembers(self):
- return list(_AllMembersIterator(self._members))
-
- def getRegularMemberKeys(self):
- return list(_DeliveryMemberIterator(self._members, REGFLAG))
-
- def getDigestMemberKeys(self):
- return list(_DeliveryMemberIterator(self._members, DIGFLAG))
-
- def __assertIsMember(self, member):
- if not self.isMember(member):
- raise Errors.NotAMemberError, member
-
- def isMember(self, member):
- return self._members.has_key(member.lower())
-
- def getMemberKey(self, member):
- self.__assertIsMember(member)
- return member.lower()
-
- def getMemberCPAddress(self, member):
- self.__assertIsMember(member)
- cpaddr = self._unpack(member)[CPADDR]
- if cpaddr:
- return cpaddr
- return member
-
- def getMemberCPAddresses(self, members):
- rtn = []
- for member in members:
- member = member.lower()
- if self._members.has_key(member):
- rtn.append(self._unpack(member)[CPADDR])
- else:
- rtn.append(None)
- return rtn
-
- def authenticateMember(self, member, response):
- self.__assertIsMember(member)
- passwd = self._unpack(member)[PASSWD]
- if passwd == response:
- return passwd
- return False
-
- def getMemberPassword(self, member):
- self.__assertIsMember(member)
- return self._unpack(member)[PASSWD]
-
- def getMemberLanguage(self, member):
- if not self.isMember(member):
- return self._mlist.preferred_language
- lang = self._unpack(member)[LANG]
- if lang in self._mlist.GetAvailableLanguages():
- return lang
- return self._mlist.preferred_language
-
- def getMemberOption(self, member, flag):
- self.__assertIsMember(member)
- if flag == mm_cfg.Digests:
- return self._unpack(member)[DIGEST] == DIGDELIV
- options = self._unpack(member)[OPTIONS]
- return bool(options & flag)
-
- def getMemberName(self, member):
- self.__assertIsMember(member)
- name = self._unpack(member)[NAME]
- return name or None
-
- def getMemberTopics(self, member):
- self.__assertIsMember(member)
- topics = self._topics.get(member.lower(), '')
- if not topics:
- return []
- return topics.split(SEP)
-
- def getDeliveryStatus(self, member):
- self.__assertIsMember(member)
- data = self._status.get(member.lower())
- if data is None:
- return MemberAdaptor.ENABLED
- status, when = pickle.loads(data)
- return status
-
- def getDeliveryStatusChangeTime(self, member):
- self.__assertIsMember(member)
- data = self._status.get(member.lower())
- if data is None:
- return 0
- status, when = pickle.loads(data)
- return when
-
- # BAW: see above, re iterators
- def getDeliveryStatusMembers(self, status=(MemberAdaptor.UNKNOWN,
- MemberAdaptor.BYUSER,
- MemberAdaptor.BYADMIN,
- MemberAdaptor.BYBOUNCE)):
- return list(_StatusMemberIterator(self._members, self._status, status))
-
- def getBouncingMembers(self):
- return list(_BouncingMembersIterator(self._bounceinfo))
-
- def getBounceInfo(self, member):
- self.__assertIsMember(member)
- return self._bounceinfo.get(member.lower())
-
-
-
-class _MemberIterator:
- def __init__(self, table):
- self._table = table
- self._c = table.cursor()
-
- def __iter__(self):
- raise NotImplementedError
-
- def next(self):
- raise NotImplementedError
-
- def close(self):
- if self._c:
- self._c.close()
- self._c = None
-
- def __del__(self):
- self.close()
-
-
-class _AllMembersIterator(_MemberIterator):
- def __iter__(self):
- return _AllMembersIterator(self._table)
-
- def next(self):
- rec = self._c.next()
- if rec:
- return rec[0]
- self.close()
- raise StopIteration
-
-
-class _DeliveryMemberIterator(_MemberIterator):
- def __init__(self, table, flag):
- _MemberIterator.__init__(self, table)
- self._flag = flag
-
- def __iter__(self):
- return _DeliveryMemberIterator(self._table, self._flag)
-
- def next(self):
- rec = self._c.next()
- while rec:
- addr, data = rec
- if data[0] == self._flag:
- return addr
- rec = self._c.next()
- self.close()
- raise StopIteration
-
-
-class _StatusMemberIterator(_MemberIterator):
- def __init__(self, table, statustab, status):
- _MemberIterator.__init__(self, table)
- self._statustab = statustab
- self._status = status
-
- def __iter__(self):
- return _StatusMemberIterator(self._table,
- self._statustab,
- self._status)
-
- def next(self):
- rec = self._c.next()
- while rec:
- addr = rec[0]
- data = self._statustab.get(addr)
- if data is None:
- status = MemberAdaptor.ENABLED
- else:
- status, when = pickle.loads(data)
- if status in self._status:
- return addr
- rec = self._c.next()
- self.close()
- raise StopIteration
-
-
-class _BouncingMembersIterator(_MemberIterator):
- def __iter__(self):
- return _BouncingMembersIterator(self._table)
-
- def next(self):
- rec = self._c.next()
- if rec:
- return rec[0]
- self.close()
- raise StopIteration
-
-
-
-# For extend.py
-def fixlock(mlist):
- def Lock(self, timeout=0):
- MailList.Lock(self, timeout)
- try:
- self._memberadaptor.txn_begin()
- except:
- MailList.Unlock(self)
- raise
- mlist.Lock = new.instancemethod(Lock, mlist, MailList)
-
-
-def fixsave(mlist):
- def Save(self):
- self._memberadaptor.txn_commit()
- MailList.Save(self)
- mlist.Save = new.instancemethod(Save, mlist, MailList)
-
-
-def fixunlock(mlist):
- def Unlock(self):
- # It's fine to abort the transaction even if there isn't one in
- # process, say because the Save() already committed it
- self._memberadaptor.txn_abort()
- MailList.Unlock(self)
- mlist.Unlock = new.instancemethod(Unlock, mlist, MailList)
-
-
-def extend(mlist):
- mlist._memberadaptor = BDBMemberAdaptor(mlist)
- fixlock(mlist)
- fixsave(mlist)
- fixunlock(mlist)
- # To make sure we got everything, let's actually delete the
- # OldStyleMemberships dictionaries. Assume if it has one, it has all
- # attributes.
- try:
- del mlist.members
- del mlist.digest_members
- del mlist.passwords
- del mlist.language
- del mlist.user_options
- del mlist.usernames
- del mlist.topics_userinterest
- del mlist.delivery_status
- del mlist.bounce_info
- except AttributeError:
- pass
- # BAW: How can we ensure that the BDBMemberAdaptor is closed?
diff --git a/Mailman/Bouncer.py b/Mailman/Bouncer.py
index aabe9d5ad..d18d70917 100644
--- a/Mailman/Bouncer.py
+++ b/Mailman/Bouncer.py
@@ -24,19 +24,20 @@ import logging
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 Pending
from Mailman import Utils
from Mailman import i18n
-from Mailman import mm_cfg
+from Mailman.configuration import config
EMPTYSTRING = ''
# This constant is supposed to represent the day containing the first midnight
# after the epoch. We'll add (0,)*6 to this tuple to get a value appropriate
# for time.mktime().
-ZEROHOUR_PLUSONEDAY = time.localtime(mm_cfg.days(1))[:3]
+ZEROHOUR_PLUSONEDAY = time.localtime(Defaults.days(1))[:3]
def _(s): return s
@@ -81,19 +82,19 @@ class _BounceInfo:
class Bouncer:
def InitVars(self):
# Configurable...
- self.bounce_processing = mm_cfg.DEFAULT_BOUNCE_PROCESSING
- self.bounce_score_threshold = mm_cfg.DEFAULT_BOUNCE_SCORE_THRESHOLD
- self.bounce_info_stale_after = mm_cfg.DEFAULT_BOUNCE_INFO_STALE_AFTER
+ self.bounce_processing = config.DEFAULT_BOUNCE_PROCESSING
+ self.bounce_score_threshold = config.DEFAULT_BOUNCE_SCORE_THRESHOLD
+ self.bounce_info_stale_after = config.DEFAULT_BOUNCE_INFO_STALE_AFTER
self.bounce_you_are_disabled_warnings = \
- mm_cfg.DEFAULT_BOUNCE_YOU_ARE_DISABLED_WARNINGS
+ config.DEFAULT_BOUNCE_YOU_ARE_DISABLED_WARNINGS
self.bounce_you_are_disabled_warnings_interval = \
- mm_cfg.DEFAULT_BOUNCE_YOU_ARE_DISABLED_WARNINGS_INTERVAL
+ config.DEFAULT_BOUNCE_YOU_ARE_DISABLED_WARNINGS_INTERVAL
self.bounce_unrecognized_goes_to_list_owner = \
- mm_cfg.DEFAULT_BOUNCE_UNRECOGNIZED_GOES_TO_LIST_OWNER
+ config.DEFAULT_BOUNCE_UNRECOGNIZED_GOES_TO_LIST_OWNER
self.bounce_notify_owner_on_disable = \
- mm_cfg.DEFAULT_BOUNCE_NOTIFY_OWNER_ON_DISABLE
+ config.DEFAULT_BOUNCE_NOTIFY_OWNER_ON_DISABLE
self.bounce_notify_owner_on_removal = \
- mm_cfg.DEFAULT_BOUNCE_NOTIFY_OWNER_ON_REMOVAL
+ config.DEFAULT_BOUNCE_NOTIFY_OWNER_ON_REMOVAL
# Not configurable...
#
# This holds legacy member related information. It's keyed by the
@@ -153,7 +154,7 @@ class Bouncer:
# Now that we've adjusted the bounce score for this bounce, let's
# check to see if the disable-by-bounce threshold has been reached.
if info.score >= self.bounce_score_threshold:
- if mm_cfg.VERP_PROBES:
+ if config.VERP_PROBES:
log.info('sending %s list probe to: %s (score %s >= %s)',
self.internal_name(), member, info.score,
self.bounce_score_threshold)
@@ -168,7 +169,7 @@ class Bouncer:
cookie = self.pend_new(Pending.RE_ENABLE, self.internal_name(), member)
info.cookie = cookie
# Disable them
- if mm_cfg.VERP_PROBES:
+ if config.VERP_PROBES:
log.info('%s: %s disabling due to probe bounce received',
self.internal_name(), member)
else:
diff --git a/Mailman/Cgi/admin.py b/Mailman/Cgi/admin.py
index 3fbbd4ca4..d0da502bf 100644
--- a/Mailman/Cgi/admin.py
+++ b/Mailman/Cgi/admin.py
@@ -22,7 +22,6 @@ import re
import cgi
import sha
import sys
-import signal
import urllib
import logging
@@ -127,40 +126,8 @@ def main():
# The html page document
doc = Document()
doc.set_language(mlist.preferred_language)
-
- # From this point on, the MailList object must be locked. However, we
- # must release the lock no matter how we exit. try/finally isn't enough,
- # because of this scenario: user hits the admin page which may take a long
- # time to render; user gets bored and hits the browser's STOP button;
- # browser shuts down socket; server tries to write to broken socket and
- # gets a SIGPIPE. Under Apache 1.3/mod_cgi, Apache catches this SIGPIPE
- # (I presume it is buffering output from the cgi script), then turns
- # around and SIGTERMs the cgi process. Apache waits three seconds and
- # then SIGKILLs the cgi process. We /must/ catch the SIGTERM and do the
- # most reasonable thing we can in as short a time period as possible. If
- # we get the SIGKILL we're screwed (because it's uncatchable and we'll
- # have no opportunity to clean up after ourselves).
- #
- # This signal handler catches the SIGTERM, unlocks the list, and then
- # exits the process. The effect of this is that the changes made to the
- # MailList object will be aborted, which seems like the only sensible
- # semantics.
- #
- # BAW: This may not be portable to other web servers or cgi execution
- # models.
- def sigterm_handler(signum, frame, mlist=mlist):
- # Make sure the list gets unlocked...
- mlist.Unlock()
- # ...and ensure we exit, otherwise race conditions could cause us to
- # enter MailList.Save() while we're in the unlocked state, and that
- # could be bad!
- sys.exit(0)
-
mlist.Lock()
try:
- # Install the emergency shutdown signal handler
- signal.signal(signal.SIGTERM, sigterm_handler)
-
if cgidata.keys():
# There are options to change
change_options(mlist, category, subcat, cgidata, doc)
@@ -190,10 +157,6 @@ def main():
print doc.Format()
mlist.Save()
finally:
- # Now be sure to unlock the list. It's okay if we get a signal here
- # because essentially, the signal handler will do the same thing. And
- # unlocking is unconditional, so it's not an error if we unlock while
- # we're already unlocked.
mlist.Unlock()
diff --git a/Mailman/Cgi/admindb.py b/Mailman/Cgi/admindb.py
index a589d1019..33b4b2627 100644
--- a/Mailman/Cgi/admindb.py
+++ b/Mailman/Cgi/admindb.py
@@ -23,7 +23,6 @@ import sys
import time
import email
import errno
-import signal
import logging
from urllib import quote_plus, unquote_plus
@@ -134,27 +133,8 @@ def main():
if qs and isinstance(qs, list):
details = qs[0]
- # We need a signal handler to catch the SIGTERM that can come from Apache
- # when the user hits the browser's STOP button. See the comment in
- # admin.py for details.
- #
- # BAW: Strictly speaking, the list should not need to be locked just to
- # read the request database. However the request database asserts that
- # the list is locked in order to load it and it's not worth complicating
- # that logic.
- def sigterm_handler(signum, frame, mlist=mlist):
- # Make sure the list gets unlocked...
- mlist.Unlock()
- # ...and ensure we exit, otherwise race conditions could cause us to
- # enter MailList.Save() while we're in the unlocked state, and that
- # could be bad!
- sys.exit(0)
-
mlist.Lock()
try:
- # Install the emergency shutdown signal handler
- signal.signal(signal.SIGTERM, sigterm_handler)
-
realname = mlist.real_name
if not cgidata.keys():
# If this is not a form submission (i.e. there are no keys in the
diff --git a/Mailman/Cgi/create.py b/Mailman/Cgi/create.py
index 378fa8f37..8c11f22e5 100644
--- a/Mailman/Cgi/create.py
+++ b/Mailman/Cgi/create.py
@@ -21,7 +21,6 @@ import os
import cgi
import sha
import sys
-import signal
import logging
from Mailman import Errors
@@ -161,18 +160,7 @@ def process_request(doc, cgidata):
fqdn_listname = '%s@%s' % (listname, email_host)
# We've got all the data we need, so go ahead and try to create the list
mlist = MailList.MailList()
- # See admin.py for why we need to set up the signal handler.
- def sigterm_handler(signum, frame):
- # Make sure the list gets unlocked...
- mlist.Unlock()
- # ...and ensure we exit, otherwise race conditions could cause us to
- # enter MailList.Save() while we're in the unlocked state, and that
- # could be bad!
- sys.exit(0)
try:
- # Install the emergency shutdown signal handler
- signal.signal(signal.SIGTERM, sigterm_handler)
-
pw = sha.new(password).hexdigest()
# Guarantee that all newly created files have the proper permission.
# proper group ownership should be assured by the autoconf script
@@ -205,10 +193,6 @@ def process_request(doc, cgidata):
mlist.default_member_moderation = moderate
mlist.Save()
finally:
- # Now be sure to unlock the list. It's okay if we get a signal here
- # because essentially, the signal handler will do the same thing. And
- # unlocking is unconditional, so it's not an error if we unlock while
- # we're already unlocked.
mlist.Unlock()
# Now do the MTA-specific list creation tasks
if config.MTA:
diff --git a/Mailman/Cgi/options.py b/Mailman/Cgi/options.py
index fceeaaaf6..3b86aeb99 100644
--- a/Mailman/Cgi/options.py
+++ b/Mailman/Cgi/options.py
@@ -20,7 +20,6 @@
import os
import cgi
import sys
-import signal
import urllib
import logging
@@ -369,13 +368,6 @@ address. Upon confirmation, any other mailing list containing the address
_('Addresses may not be blank'))
print doc.Format()
return
-
- # Standard sigterm handler.
- def sigterm_handler(signum, frame, mlist=mlist):
- mlist.Unlock()
- sys.exit(0)
-
- signal.signal(signal.SIGTERM, sigterm_handler)
if set_address:
if cpuser is None:
cpuser = user
@@ -463,15 +455,6 @@ address. Upon confirmation, any other mailing list containing the address
print doc.Format()
return
- # Standard signal handler
- def sigterm_handler(signum, frame, mlist=mlist):
- mlist.Unlock()
- sys.exit(0)
-
- # Okay, zap them. Leave them sitting at the list's listinfo page. We
- # must own the list lock, and we want to make sure the user (BAW: and
- # list admin?) is informed of the removal.
- signal.signal(signal.SIGTERM, sigterm_handler)
mlist.Lock()
needapproval = False
try:
@@ -582,7 +565,6 @@ address. Upon confirmation, any other mailing list containing the address
# Now, lock the list and perform the changes
mlist.Lock()
try:
- signal.signal(signal.SIGTERM, sigterm_handler)
# `values' is a tuple of flags and the web values
for flag, newval in newvals:
# Handle language settings differently
@@ -926,23 +908,10 @@ def lists_of_member(mlist, user):
def change_password(mlist, user, newpw, confirmpw):
- # This operation requires the list lock, so let's set up the signal
- # handling so the list lock will get released when the user hits the
- # browser stop button.
- def sigterm_handler(signum, frame, mlist=mlist):
- # Make sure the list gets unlocked...
- mlist.Unlock()
- # ...and ensure we exit, otherwise race conditions could cause us to
- # enter MailList.Save() while we're in the unlocked state, and that
- # could be bad!
- sys.exit(0)
-
# Must own the list lock!
mlist.Lock()
try:
- # Install the emergency shutdown signal handler
- signal.signal(signal.SIGTERM, sigterm_handler)
- # change the user's password. The password must already have been
+ # Change the user's password. The password must already have been
# compared to the confirmpw and otherwise been vetted for
# acceptability.
mlist.setMemberPassword(user, newpw)
@@ -973,9 +942,6 @@ def global_options(mlist, user, globalopts):
# Must own the list lock!
mlist.Lock()
try:
- # Install the emergency shutdown signal handler
- signal.signal(signal.SIGTERM, sigterm_handler)
-
if globalopts.enable is not None:
mlist.setDeliveryStatus(user, globalopts.enable)
diff --git a/Mailman/Cgi/subscribe.py b/Mailman/Cgi/subscribe.py
index 6aee0e6f8..5c3e5382b 100644
--- a/Mailman/Cgi/subscribe.py
+++ b/Mailman/Cgi/subscribe.py
@@ -20,7 +20,6 @@
import os
import cgi
import sys
-import signal
import logging
from Mailman import Errors
@@ -76,27 +75,8 @@ def main():
i18n.set_language(language)
doc.set_language(language)
- # We need a signal handler to catch the SIGTERM that can come from Apache
- # when the user hits the browser's STOP button. See the comment in
- # admin.py for details.
- #
- # BAW: Strictly speaking, the list should not need to be locked just to
- # read the request database. However the request database asserts that
- # the list is locked in order to load it and it's not worth complicating
- # that logic.
- def sigterm_handler(signum, frame, mlist=mlist):
- # Make sure the list gets unlocked...
- mlist.Unlock()
- # ...and ensure we exit, otherwise race conditions could cause us to
- # enter MailList.Save() while we're in the unlocked state, and that
- # could be bad!
- sys.exit(0)
-
mlist.Lock()
try:
- # Install the emergency shutdown signal handler
- signal.signal(signal.SIGTERM, sigterm_handler)
-
process_form(mlist, doc, cgidata, language)
mlist.Save()
finally:
diff --git a/Mailman/Defaults.py.in b/Mailman/Defaults.py.in
index f539f4e39..4c4a0a822 100644
--- a/Mailman/Defaults.py.in
+++ b/Mailman/Defaults.py.in
@@ -100,20 +100,18 @@ HTML_TO_PLAIN_TEXT_COMMAND = '/usr/bin/lynx -dump %(filename)s'
# Database options
#####
-# Specify the name of the membership adaptor class that implements the
-# MemberAdaptor interface you want to use. The first is traditional
-# pickle-based adapter that was standard for Mailman 2.1 and earlier.
-MEMBER_ADAPTOR_CLASS = 'Mailman.OldStyleMemberships.OldStyleMemberships'
+# Use this to set the SQLAlchemy database engine URL. You generally have one
+# primary database connection for all of Mailman. List data and most rosters
+# will store their data in this database, although external rosters may access
+# other databases in their own way. This string support substitutions using
+# any variable in the Configuration object.
+SQLALCHEMY_ENGINE_URL = 'sqlite:///$DATA_DIR/mailman.db'
-# This is the SQLAlchemy-based adaptor which lets you store all membership
-# data in any of several supported RDBMs.
-#MEMBER_ADAPTOR_CLASS = 'Mailman.SAMemberships.SAMemberships'
+# For debugging purposes
+SQLALCHEMY_ECHO = False
-# If you choose the SAMemberships adaptor, set this variable to specify the
-# connection url to the backend database engine. Specify the placeholder
-# $listdir for the directory that list data is stored in, and $listname for
-# the name of the mailing list.
-SQLALCHEMY_ENGINE_URL = 'sqlite:///$listdir/members.db'
+# XXX REMOVE ME
+MEMBER_ADAPTOR_CLASS = 'Mailman.OldStyleMemberships.OldStyleMemberships'
diff --git a/Mailman/Digester.py b/Mailman/Digester.py
index 2d27b8442..98cb40dd3 100644
--- a/Mailman/Digester.py
+++ b/Mailman/Digester.py
@@ -1,50 +1,50 @@
-# Copyright (C) 1998,1999,2000,2001,2002 by the Free Software Foundation, Inc.
+# Copyright (C) 1998-2006 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.
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+# USA.
"""Mixin class with list-digest handling methods and settings."""
import os
-from stat import ST_SIZE
import errno
-from Mailman import mm_cfg
-from Mailman import Utils
from Mailman import Errors
+from Mailman import Utils
from Mailman.Handlers import ToDigest
+from Mailman.configuration import config
from Mailman.i18n import _
class Digester:
def InitVars(self):
- # Configurable
- self.digestable = mm_cfg.DEFAULT_DIGESTABLE
- self.digest_is_default = mm_cfg.DEFAULT_DIGEST_IS_DEFAULT
- self.mime_is_default_digest = mm_cfg.DEFAULT_MIME_IS_DEFAULT_DIGEST
- self.digest_size_threshhold = mm_cfg.DEFAULT_DIGEST_SIZE_THRESHHOLD
- self.digest_send_periodic = mm_cfg.DEFAULT_DIGEST_SEND_PERIODIC
- self.next_post_number = 1
- self.digest_header = mm_cfg.DEFAULT_DIGEST_HEADER
- self.digest_footer = mm_cfg.DEFAULT_DIGEST_FOOTER
- self.digest_volume_frequency = mm_cfg.DEFAULT_DIGEST_VOLUME_FREQUENCY
- # Non-configurable.
+ # Configurable
+ self.digestable = config.DEFAULT_DIGESTABLE
+ self.digest_is_default = config.DEFAULT_DIGEST_IS_DEFAULT
+ self.mime_is_default_digest = config.DEFAULT_MIME_IS_DEFAULT_DIGEST
+ self.digest_size_threshhold = config.DEFAULT_DIGEST_SIZE_THRESHHOLD
+ self.digest_send_periodic = config.DEFAULT_DIGEST_SEND_PERIODIC
+ self.next_post_number = 1
+ self.digest_header = config.DEFAULT_DIGEST_HEADER
+ self.digest_footer = config.DEFAULT_DIGEST_FOOTER
+ self.digest_volume_frequency = config.DEFAULT_DIGEST_VOLUME_FREQUENCY
+ # Non-configurable.
self.one_last_digest = {}
- self.digest_members = {}
- self.next_digest_number = 1
+ self.digest_members = {}
+ self.next_digest_number = 1
self.digest_last_sent_at = 0
def send_digest_now(self):
@@ -55,7 +55,7 @@ class Digester:
try:
mboxfp = None
# See if there's a digest pending for this mailing list
- if os.stat(digestmbox)[ST_SIZE] > 0:
+ if os.stat(digestmbox).st_size > 0:
mboxfp = open(digestmbox)
ToDigest.send_digests(self, mboxfp)
os.unlink(digestmbox)
@@ -63,10 +63,11 @@ class Digester:
if mboxfp:
mboxfp.close()
except OSError, e:
- if e.errno <> errno.ENOENT: raise
+ if e.errno <> errno.ENOENT:
+ raise
# List has no outstanding digests
- return 0
- return 1
+ return False
+ return True
def bump_digest_volume(self):
self.volume += 1
diff --git a/Mailman/Errors.py b/Mailman/Errors.py
index 26333a688..f2d4fad19 100644
--- a/Mailman/Errors.py
+++ b/Mailman/Errors.py
@@ -175,3 +175,19 @@ class HostileSubscriptionError(MailmanError):
"""A cross-subscription attempt was made."""
# This exception gets raised when an invitee attempts to use the
# invitation to cross-subscribe to some other mailing list.
+
+
+
+# Database exceptions
+class DatabaseError(MailmanError):
+ """A problem with the database occurred."""
+
+
+class SchemaVersionMismatchError(DatabaseError):
+ def __init__(self, got):
+ self._got = got
+
+ def __str__(self):
+ from Mailman.Version import DATABASE_SCHEMA_VERSION
+ return 'Incompatible database schema version (got: %d, expected: %d)' \
+ % (self._got, DATABASE_SCHEMA_VERSION)
diff --git a/Mailman/GatewayManager.py b/Mailman/GatewayManager.py
index 094f0c76d..04c630cbe 100644
--- a/Mailman/GatewayManager.py
+++ b/Mailman/GatewayManager.py
@@ -1,4 +1,4 @@
-# Copyright (C) 1998,1999,2000,2001,2002 by the Free Software Foundation, Inc.
+# Copyright (C) 1998-2006 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
@@ -12,7 +12,8 @@
#
# 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.
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+# USA.
"""Mixin class for configuring Usenet gateway.
@@ -21,18 +22,18 @@ gateway and cron/gate_news for the news->mail gateway.
"""
-from Mailman import mm_cfg
-from Mailman.i18n import _
+from Mailman.configuration import config
+
class GatewayManager:
def InitVars(self):
# Configurable
- self.nntp_host = mm_cfg.DEFAULT_NNTP_HOST
+ self.nntp_host = config.DEFAULT_NNTP_HOST
self.linked_newsgroup = ''
- self.gateway_to_news = 0
- self.gateway_to_mail = 0
- self.news_prefix_subject_too = 1
+ self.gateway_to_news = False
+ self.gateway_to_mail = False
+ self.news_prefix_subject_too = True
# In patch #401270, this was called newsgroup_is_moderated, but the
# semantics weren't quite the same.
- self.news_moderation = 0
+ self.news_moderation = False
diff --git a/Mailman/MTA/Postfix.py b/Mailman/MTA/Postfix.py
index 595581718..2c6f8c60e 100644
--- a/Mailman/MTA/Postfix.py
+++ b/Mailman/MTA/Postfix.py
@@ -284,7 +284,7 @@ def create(mlist, cgi=False, nolock=False, quiet=False):
if not nolock:
lock = makelock()
lock.lock()
- # Do the aliases file, which need to be done in any case
+ # Do the aliases file, which always needs to be done
try:
if config.USE_LMTP:
_do_create(mlist, TRPTFILE, _addtransport)
diff --git a/Mailman/MailList.py b/Mailman/MailList.py
index 555717eff..43ae3046c 100644
--- a/Mailman/MailList.py
+++ b/Mailman/MailList.py
@@ -46,6 +46,7 @@ from Mailman import Errors
from Mailman import LockFile
from Mailman import Utils
from Mailman import Version
+from Mailman import database
from Mailman.UserDesc import UserDesc
from Mailman.configuration import config
@@ -84,9 +85,31 @@ slog = logging.getLogger('mailman.subscribe')
# Use mixins here just to avoid having any one chunk be too large.
-class MailList(HTMLFormatter, Deliverer, ListAdmin,
+class MailList(object, HTMLFormatter, Deliverer, ListAdmin,
Archiver, Digester, SecurityManager, Bouncer, GatewayManager,
Autoresponder, TopicMgr, Pending.Pending):
+ def __new__(cls, *args, **kws):
+ # Search positional and keyword arguments to find the name of the
+ # existing list that is being opened, with the latter taking
+ # precedence. If no name can be found, then make sure there are no
+ # arguments, otherwise it's an error.
+ if 'name' in kws:
+ listname = kws.pop('name')
+ elif not args:
+ if not kws:
+ # We're probably being created from the ORM layer, so just let
+ # the super class do its thing.
+ return super(MailList, cls).__new__(cls, *args, **kws)
+ raise ValueError("'name' argument required'")
+ else:
+ listname = args[0]
+ fqdn_listname = Utils.fqdn_listname(listname)
+ listname, hostname = Utils.split_listname(fqdn_listname)
+ mlist = database.find_list(listname, hostname)
+ if not mlist:
+ raise Errors.MMUnknownListError(fqdn_listname)
+ return mlist
+
#
# A MailList object's basic Python object model support
#
@@ -100,13 +123,6 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin,
baseclass.__init__(self)
# Initialize volatile attributes
self.InitTempVars(name, check_version)
- # Attach a membership adaptor instance.
- parts = config.MEMBER_ADAPTOR_CLASS.split(DOT)
- adaptor_class = parts.pop()
- adaptor_module = DOT.join(parts)
- __import__(adaptor_module)
- mod = sys.modules[adaptor_module]
- self._memberadaptor = getattr(mod, adaptor_class)(self)
# This extension mechanism allows list-specific overrides of any
# method (well, except __init__(), InitTempVars(), and InitVars()
# I think). Note that fullpath() will return None when we're creating
@@ -134,6 +150,8 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin,
self.Load(name, check_version)
def __getattr__(self, name):
+ if name.startswith('_'):
+ return super(MailList, self).__getattr__(name)
# Because we're using delegation, we want to be sure that attribute
# access to a delegated member function gets passed to the
# sub-objects. This of course imposes a specific name resolution
@@ -147,7 +165,7 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin,
except AttributeError:
pass
else:
- raise AttributeError, name
+ raise AttributeError(name)
def __repr__(self):
if self.Locked():
@@ -162,6 +180,7 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin,
# Lock management
#
def Lock(self, timeout=0):
+ database.lock(self)
self.__lock.lock(timeout)
self._memberadaptor.lock()
# Must reload our database for consistency. Watch out for lists that
@@ -173,6 +192,7 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin,
raise
def Unlock(self):
+ database.unlock(self)
self.__lock.unlock(unconditionally=True)
self._memberadaptor.unlock()
@@ -281,6 +301,13 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin,
#
def InitTempVars(self, name, check_version=True):
"""Set transient variables of this and inherited classes."""
+ # Attach a membership adaptor instance.
+ parts = config.MEMBER_ADAPTOR_CLASS.split(DOT)
+ adaptor_class = parts.pop()
+ adaptor_module = DOT.join(parts)
+ __import__(adaptor_module)
+ mod = sys.modules[adaptor_module]
+ self._memberadaptor = getattr(mod, adaptor_class)(self)
# The timestamp is set whenever we load the state from disk. If our
# timestamp is newer than the modtime of the config.pck file, we don't
# need to reload, otherwise... we do.
@@ -519,7 +546,7 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin,
raise Errors.MMListAlreadyExistsError(fqdn_listname)
# Validate the admin's email address
Utils.ValidateEmail(admin_email)
- self._internal_name = listname
+ self._internal_name = self.list_name = listname
self._full_path = os.path.join(config.LIST_DATA_DIR, fqdn_listname)
Utils.makedirs(self._full_path)
# Don't use Lock() since that tries to load the non-existant config.pck
@@ -537,206 +564,35 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin,
self.available_languages = langs
url_host = config.domains[email_host]
self.web_page_url = config.DEFAULT_URL_PATTERN % url_host
+ database.add_list(self)
- #
- # Database and filesystem I/O
- #
- def __save(self, dict):
- # Save the file as a binary pickle, and rotate the old version to a
- # backup file. We must guarantee that config.pck is always valid so
- # we never rotate unless the we've successfully written the temp file.
- # We use pickle now because marshal is not guaranteed to be compatible
- # between Python versions.
- fname = os.path.join(self.fullpath(), 'config.pck')
- fname_tmp = fname + '.tmp.%s.%d' % (socket.gethostname(), os.getpid())
- fname_last = fname + '.last'
- fp = None
- try:
- fp = open(fname_tmp, 'w')
- # Use a binary format... it's more efficient.
- cPickle.dump(dict, fp, 1)
- fp.flush()
- if config.SYNC_AFTER_WRITE:
- os.fsync(fp.fileno())
- fp.close()
- except IOError, e:
- elog.error('Failed config.pck write, retaining old state.\n%s', e)
- if fp is not None:
- os.unlink(fname_tmp)
- raise
- # Now do config.pck.tmp.xxx -> config.pck -> config.pck.last rotation
- # as safely as possible.
- try:
- # might not exist yet
- os.unlink(fname_last)
- except OSError, e:
- if e.errno <> errno.ENOENT: raise
- try:
- # might not exist yet
- os.link(fname, fname_last)
- except OSError, e:
- if e.errno <> errno.ENOENT: raise
- os.rename(fname_tmp, fname)
- # Reset the timestamp
- self.__timestamp = os.path.getmtime(fname)
-
def Save(self):
# Refresh the lock, just to let other processes know we're still
# interested in it. This will raise a NotLockedError if we don't have
# the lock (which is a serious problem!). TBD: do we need to be more
# defensive?
self.__lock.refresh()
+ # Commit the database transaction
+ database.save(self)
+ # The member adaptor may have its own save operation
self._memberadaptor.save()
- # copy all public attributes to serializable dictionary
- dict = {}
- for key, value in self.__dict__.items():
- if key[0] == '_' or isinstance(value, MethodType):
- continue
- dict[key] = value
- # Make config.pck unreadable by `other', as it contains all the
- # list members' passwords (in clear text).
- omask = os.umask(007)
- try:
- self.__save(dict)
- finally:
- os.umask(omask)
- self.SaveRequestsDb()
+ self.SaveRequestsDb()
self.CheckHTMLArchiveDir()
- def __load(self, dbfile):
- # Attempt to load and unserialize the specified database file. This
- # could actually be a config.db (for pre-2.1alpha3) or config.pck,
- # i.e. a marshal or a binary pickle. Actually, it could also be a
- # .last backup file if the primary storage file was corrupt. The
- # decision on whether to unpickle or unmarshal is based on the file
- # extension, but we always save it using pickle (since only it, and
- # not marshal is guaranteed to be compatible across Python versions).
- #
- # On success return a 2-tuple of (dictionary, None). On error, return
- # a 2-tuple of the form (None, errorobj).
- if dbfile.endswith('.db') or dbfile.endswith('.db.last'):
- loadfunc = marshal.load
- elif dbfile.endswith('.pck') or dbfile.endswith('.pck.last'):
- loadfunc = cPickle.load
- else:
- assert 0, 'Bad database file name'
- try:
- # Check the mod time of the file first. If it matches our
- # timestamp, then the state hasn't change since the last time we
- # loaded it. Otherwise open the file for loading, below. If the
- # file doesn't exist, we'll get an EnvironmentError with errno set
- # to ENOENT (EnvironmentError is the base class of IOError and
- # OSError).
- mtime = os.path.getmtime(dbfile)
- if mtime <= self.__timestamp:
- # File is not newer
- return None, None
- fp = open(dbfile)
- except EnvironmentError, e:
- if e.errno <> errno.ENOENT: raise
- # The file doesn't exist yet
- return None, e
- try:
- try:
- d = loadfunc(fp)
- if not isinstance(d, dict):
- return None, 'Load() expected to return a dictionary'
- except (EOFError, ValueError, TypeError, MemoryError,
- cPickle.PicklingError, cPickle.UnpicklingError), e:
- return None, e
- finally:
- fp.close()
- # Update timestamp
- self.__timestamp = mtime
- return d, None
-
def Load(self, fqdn_listname=None, check_version=True):
if fqdn_listname is None:
fqdn_listname = self.fqdn_listname
if not Utils.list_exists(fqdn_listname):
raise Errors.MMUnknownListError(fqdn_listname)
self._memberadaptor.load()
- # We first try to load config.pck, which contains the up-to-date
- # version of the database. If that fails, perhaps because it's
- # corrupted or missing, we'll try to load the backup file
- # config.pck.last.
- #
- # Should both of those fail, we'll look for config.db and
- # config.db.last for backwards compatibility with pre-2.1alpha3
- pfile = os.path.join(self.fullpath(), 'config.pck')
- plast = pfile + '.last'
- dfile = os.path.join(self.fullpath(), 'config.db')
- dlast = dfile + '.last'
- for file in (pfile, plast, dfile, dlast):
- dict, e = self.__load(file)
- if dict is None:
- if e is not None:
- # Had problems with this file; log it and try the next one.
- elog.error("couldn't load config file %s\n%s", file, e)
- else:
- # We already have the most up-to-date state
- return
- else:
- break
- else:
- # Nothing worked, so we have to give up
- elog.error('All %s fallbacks were corrupt, giving up',
- self.internal_name())
- raise Errors.MMCorruptListDatabaseError, e
- # Now, if we didn't end up using the primary database file, we want to
- # copy the fallback into the primary so that the logic in Save() will
- # still work. For giggles, we'll copy it to a safety backup. Note we
- # MUST do this with the underlying list lock acquired.
- if file == plast or file == dlast:
- elog.error('fixing corrupt config file, using: %s', file)
- unlock = True
- try:
- try:
- self.__lock.lock()
- except LockFile.AlreadyLockedError:
- unlock = False
- self.__fix_corrupt_pckfile(file, pfile, plast, dfile, dlast)
- finally:
- if unlock:
- self.__lock.unlock()
- # Copy the loaded dictionary into the attributes of the current
- # mailing list object, then run sanity check on the data.
- self.__dict__.update(dict)
if check_version:
- self.CheckVersion(dict)
+ # XXX for now disable version checks. We'll fold this into schema
+ # updates eventually.
+ #self.CheckVersion(dict)
self.CheckValues()
- def __fix_corrupt_pckfile(self, file, pfile, plast, dfile, dlast):
- if file == plast:
- # Move aside any existing pickle file and delete any existing
- # safety file. This avoids EPERM errors inside the shutil.copy()
- # calls if those files exist with different ownership.
- try:
- os.rename(pfile, pfile + '.corrupt')
- except OSError, e:
- if e.errno <> errno.ENOENT: raise
- try:
- os.remove(pfile + '.safety')
- except OSError, e:
- if e.errno <> errno.ENOENT: raise
- shutil.copy(file, pfile)
- shutil.copy(file, pfile + '.safety')
- elif file == dlast:
- # Move aside any existing marshal file and delete any existing
- # safety file. This avoids EPERM errors inside the shutil.copy()
- # calls if those files exist with different ownership.
- try:
- os.rename(dfile, dfile + '.corrupt')
- except OSError, e:
- if e.errno <> errno.ENOENT: raise
- try:
- os.remove(dfile + '.safety')
- except OSError, e:
- if e.errno <> errno.ENOENT: raise
- shutil.copy(file, dfile)
- shutil.copy(file, dfile + '.safety')
#
diff --git a/Mailman/Makefile.in b/Mailman/Makefile.in
index 5caf04719..2a7bc5e55 100644
--- a/Mailman/Makefile.in
+++ b/Mailman/Makefile.in
@@ -44,7 +44,7 @@ SHELL= /bin/sh
MODULES= $(srcdir)/*.py
SUBDIRS= Cgi Archiver Handlers Bouncers Queue MTA Gui Commands \
- bin testing
+ bin database testing
# Modes for directories and executables created by the install
# process. Default to group-writable directories but
diff --git a/Mailman/Pending.py b/Mailman/Pending.py
index 0c98822bd..eb3b7950e 100644
--- a/Mailman/Pending.py
+++ b/Mailman/Pending.py
@@ -12,7 +12,8 @@
#
# 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.
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+# USA.
"""Track pending actions which require confirmation."""
@@ -23,7 +24,8 @@ import errno
import random
import cPickle
-from Mailman import mm_cfg
+from Mailman.configuration import config
+
# Types of pending records
CHANGE_OF_ADDRESS = 'C'
@@ -43,6 +45,7 @@ _ALLKEYS = (
)
_missing = object()
+_default = object()
@@ -54,7 +57,7 @@ class Pending:
"""Create a new entry in the pending database, returning cookie for it.
"""
assert op in _ALLKEYS, 'op: %s' % op
- lifetime = kws.get('lifetime', mm_cfg.PENDING_REQUEST_LIFE)
+ lifetime = kws.get('lifetime', config.PENDING_REQUEST_LIFE)
# We try the main loop several times. If we get a lock error somewhere
# (for instance because someone broke the lock) we simply try again.
assert self.Locked()
@@ -108,7 +111,7 @@ class Pending:
for cookie in evictions.keys():
if not db.has_key(cookie):
del evictions[cookie]
- db['version'] = mm_cfg.PENDING_FILE_SCHEMA_VERSION
+ db['version'] = config.PENDING_FILE_SCHEMA_VERSION
tmpfile = '%s.tmp.%d.%d' % (self.__pendfile, os.getpid(), now)
omask = os.umask(007)
try:
@@ -145,8 +148,10 @@ class Pending:
self.__save(db)
return content
- def pend_repend(self, cookie, data, lifetime=mm_cfg.PENDING_REQUEST_LIFE):
+ def pend_repend(self, cookie, data, lifetime=_default):
assert self.Locked()
+ if lifetime is _default:
+ lifetime = config.PENDING_REQUEST_LIFE
db = self.__load()
db[cookie] = data
db['evictions'][cookie] = time.time() + lifetime
@@ -173,9 +178,9 @@ def _update(olddb):
# subscription language. Best we can do here is use the server
# default.
db[cookie] = (SUBSCRIPTION,) + data[:-1] + \
- (mm_cfg.DEFAULT_SERVER_LANGUAGE,)
+ (config.DEFAULT_SERVER_LANGUAGE,)
# The old database format kept the timestamp as the time the request
# was made. The new format keeps it as the time the request should be
# evicted.
- evictions[cookie] = data[-1] + mm_cfg.PENDING_REQUEST_LIFE
+ evictions[cookie] = data[-1] + config.PENDING_REQUEST_LIFE
return db
diff --git a/Mailman/Queue/HTTPRunner.py b/Mailman/Queue/HTTPRunner.py
index e542dc9a3..e2c053629 100644
--- a/Mailman/Queue/HTTPRunner.py
+++ b/Mailman/Queue/HTTPRunner.py
@@ -61,6 +61,8 @@ server = make_server(config.HTTP_HOST, config.HTTP_PORT,
qlog.info('HTTPRunner qrunner started.')
-server.serve_forever()
-# We'll never get here, but just in case...
-qlog.info('HTTPRunner qrunner exiting.')
+try:
+ server.serve_forever()
+except:
+ qlog.exception('HTTPRunner qrunner exiting.')
+ raise
diff --git a/Mailman/SAMemberships.py b/Mailman/SAMemberships.py
deleted file mode 100644
index dec76fe58..000000000
--- a/Mailman/SAMemberships.py
+++ /dev/null
@@ -1,330 +0,0 @@
-# Copyright (C) 2006 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.
-
-"""An experimental SQLAlchemy-based membership adaptor."""
-
-# XXX THIS FILE DOES NOT YET WORK!
-
-import os
-import re
-import time
-
-from sqlalchemy import *
-from string import Template
-
-from Mailman import Defaults
-from Mailman import Errors
-from Mailman import MemberAdaptor
-from Mailman import Utils
-from Mailman.configuration import config
-
-NUL = '\0'
-
-
-
-# Python classes representing the data in the SQLAlchemy database. These will
-# be associated with tables via object mappers.
-
-class Member(object):
- def __init__(self, mlist,
- address, realname=None, password=None,
- digests_p=False, language=None):
- self.lckey = address.lower()
- self.address = address
- self.realname = realname
- self.password = password or Utils.MakeRandomPassword()
- self.language = language or mlist.preferred_language
- self.digests_p = digests_p
- self.options = mlist.new_member_options
- self.topics = ''
- self.status = MemberAdaptor.ENABLED
- # XXX This should really be a datetime
- self.disable_time = 0
-
-
-
-_table = None
-_metadata = None
-_mapper = None
-
-def create_table():
- global _table, _metadata, _mapper
-
- if _table:
- return
- _metadata = MetaData('table metadata')
- _table = Table(
- 'members', _metadata,
- Column('member_id', Integer, primary_key=True),
- Column('lckey', Unicode(), index=True, nullable=False),
- Column('address', Unicode(), index=True, nullable=False),
- Column('realname', Unicode()),
- Column('password', Unicode()),
- Column('language', String(2)),
- Column('digest', Boolean),
- Column('options', Integer),
- Column('status', Integer),
- Column('disable_time', Float),
- )
- _mapper = mapper(Member, _table)
-
-
-
-class SAMemberships(MemberAdaptor.MemberAdaptor):
- def __init__(self, mlist):
- self._mlist = mlist
- self._metadata = None
- self._session = None
- self._txn = None
-
- def _connect(self):
- create_table()
- # We cannot connect in the __init__() because our adaptor requires the
- # fqdn_listname to exist. In MailList.Create() that won't be the case.
- #
- # Calculate the engine url, expanding placeholder variables.
- engine_url = Template(config.SQLALCHEMY_ENGINE_URL).substitute(
- {'listname' : self._mlist.fqdn_listname,
- 'listdir' : os.path.join(config.LIST_DATA_DIR,
- self._mlist.fqdn_listname),
- })
- print 'engine_url:', engine_url
- self._engine = create_engine(engine_url)
- self._session = create_session(bind_to=self._engine)
- self._session.bind_table(_table, self._engine)
- self._session.bind_mapper(_mapper, self._engine)
- # XXX There must be a better way to figure out whether the tables need
- # to be created or not.
- try:
- _table.create()
- except exceptions.SQLError:
- pass
-
- #
- # The transaction interface
- #
-
- def load(self):
- if self._session is None:
- self._connect()
- assert self._txn is None
- self._session.clear()
- self._txn = self._session.create_transaction()
-
- def lock(self):
- pass
-
- def save(self):
- # When a MailList is first Create()'d, the load() callback doesn't get
- # called, so there will be no transaction.
- if self._txn:
- self._txn.commit()
- self._txn = None
-
- def unlock(self):
- if self._txn is not None:
- # The MailList has not been saved, but it is being unlocked, so
- # throw away all pending changes.
- self._txn.rollback()
- self._txn = None
-
- #
- # The readable interface
- #
-
- def getMembers(self):
- return [m.lckey for m in self._session.query(Member).select_by()]
-
- def getRegularMemberKeys(self):
- query = self._session.query(Member)
- return [m.lckey for m in query.select(Member.c.digests_p == False)]
-
- def getDigestMemberKeys(self):
- query = self._session.query(Member)
- return [m.lckey for m in query.select(Member.c.digests_p == True)]
-
- def _get_member(self, member):
- members = self._session.query(Member).select_by(lckey=member.lower())
- if not members:
- return None
- assert len(members) == 1
- return members[0]
-
- def _get_member_strict(self, member):
- member_obj = self._get_member(member)
- if not member_obj:
- raise Errors.NotAMemberError(member)
- return member_obj
-
- def isMember(self, member):
- return bool(self._get_member(member))
-
- def getMemberKey(self, member):
- self._get_member_strict(member)
- return member.lower()
-
- def getMemberCPAddress(self, member):
- return self._get_member(member).address
-
- def getMemberCPAddresses(self, members):
- query = self._session.query(Member)
- return [user.address for user in query.select(
- in_(Member.c.lckey, [m.lower() for m in members]))]
-
- def getMemberPassword(self, member):
- return self._get_member_strict(member).password
-
- def authenticateMember(self, member, response):
- return self._get_member_strict(member).password == response
-
- def getMemberLanguage(self, member):
- member = self._get_member(member)
- if member and member.language in self._mlist.GetAvailableLanguages():
- return member.language
- return self._mlist.preferred_language
-
- def getMemberOption(self, member, flag):
- return bool(self._get_member_strict(member).options & flag)
-
- def getMemberName(self, member):
- return self._get_member_strict(member).realname
-
- def getMemberTopics(self, member):
- topics = self._get_member_strict(member).topics
- if not topics:
- return []
- return topics.split(NUL)
-
- def getDeliveryStatus(self, member):
- return self._get_member_strict(member).status
-
- def getDeliveryStatusChangeTime(self, member):
- member = self._get_member_strict(member)
- if member.status == MemberAdaptor.ENABLED:
- return 0
- return member.disable_time
-
- def getDeliveryStatusMembers(self, status=(MemberAdaptor.UNKNOWN,
- MemberAdaptor.BYUSER,
- MemberAdaptor.BYADMIN,
- MemberAdaptor.BYBOUNCE)):
- query = self._session.query(Member)
- return [user.lckey for user in query.select(
- in_(Member.c.status, status))]
-
- def getBouncingMembers(self):
- "XXX"
-
- def getBounceInfo(self):
- "XXX"
-
- #
- # The writable interface
- #
-
- def addNewMember(self, member, **kws):
- assert self._mlist.Locked()
- if self.isMember(member):
- raise Errors.MMAlreadyAMember(member)
- try:
- new_member = Member(self._mlist, member, **kws)
- self._session.save(new_member)
- self._session.flush()
- except TypeError:
- # Transform exception to API specification
- raise ValueError
-
- def removeMember(self, memberkey):
- assert self._mlist.Locked()
- member = self._get_member_strict(memberkey)
- self._session.delete(member)
-
- def changeMemberAddress(self, memberkey, newaddress, nodelete=False):
- assert self._mlist.Locked()
- member = self._get_member_strict(memberkey)
- # First, add the new member from the previous data
- self.addNewMember(newaddress, member.realname, member.password,
- member.digests_p, member.language)
- new_member = self._get_member(newaddress)
- assert new_member
- new_member.options = member.options
- new_member.topics = member.topics
- new_member.status = MemberAdaptor.ENABLED
- new_member.disable_time = 0
- if not nodelete:
- self._session.delete(member)
-
- def setMemberPassword(self, member, password):
- assert self._mlist.Locked()
- self._get_member_strict(member).password = password
-
- def setMemberLanguage(self, member, language):
- assert self._mlist.Locked()
- self._get_member_strict(member).language = language
-
- def setMemberOption(self, member, flag, value):
- assert self._mlist.Locked()
- member = self._get_member_strict(member)
- # XXX the OldStyleMemberships adaptor will raise CantDigestError,
- # MustDigestError, AlreadyReceivingDigests, and
- # AlreadyReceivingRegularDeliveries in certain cases depending on the
- # configuration of the mailing list and the member's delivery status.
- # These semantics are not defined in the API so to keep things simple,
- # I am not reproducing them here. Ideally, adaptors should not be
- # doing semantic integrity checks, but I'm also not going to change
- # the OldStyleMemberships adaptor.
- #
- # We still need to handle digests differently, because they aren't
- # really represented as a unique flag in the options bitfield.
- if flag == Defaults.Digests:
- member.digests_p = bool(value)
- else:
- if value:
- member.options |= flag
- else:
- member.options &= ~flag
-
- def setMemberName(self, member, realname):
- assert self._mlist.Locked()
- self._get_member_strict(member).realname = realname
-
- def setMemberTopics(self, member, topics):
- assert self._mlist.Locked()
- # For simplicity, we represent a user's topics of interest as a
- # null-joined string, which will be split properly by the accessor.
- if not topics:
- topics = None
- else:
- topics = NUL.join(topics)
- self._get_member_strict(member).topics = topics
-
- def setDeliveryStatus(self, member, status):
- assert status in (MemberAdaptor.ENABLED, MemberAdaptor.UNKNOWN,
- MemberAdaptor.BYUSER, MemberAdaptor.BYADMIN,
- MemberAdaptor.BYBOUNCE)
- assert self._mlist.Locked()
- member = self._get_member_strict(member)
- if status == MemberAdaptor.ENABLED:
- # XXX zap bounce info
- disable_time = 0
- else:
- disable_time = time.time()
- member.disable_time = disable_time
- member.status = status
-
- def setBounceInfo(self, member, info):
- "XXX"
diff --git a/Mailman/SecurityManager.py b/Mailman/SecurityManager.py
index 3865071b5..6740a958f 100644
--- a/Mailman/SecurityManager.py
+++ b/Mailman/SecurityManager.py
@@ -28,7 +28,7 @@
#
# Each cookie has the following ingredients: the authorization context's
# secret (i.e. the password, and a timestamp. We generate an SHA1 hex
-# digest of these ingredients, which we call the `mac'. We then marshal
+# digest of these ingredients, which we call the 'mac'. We then marshal
# up a tuple of the timestamp and the mac, hexlify that and return that as
# a cookie keyed off the authcontext. Note that authenticating the user
# also requires the user's email address to be included in the cookie.
@@ -48,7 +48,6 @@
import os
import re
-import md5
import sha
import time
import urllib
@@ -64,19 +63,15 @@ from Mailman import Errors
from Mailman import Utils
from Mailman.configuration import config
-try:
- import crypt
-except ImportError:
- crypt = None
-
log = logging.getLogger('mailman.error')
+dlog = logging.getLogger('mailman.debug')
+
+SLASH = '/'
class SecurityManager:
def InitVars(self):
- # We used to set self.password here, from a crypted_password argument,
- # but that's been removed when we generalized the mixin architecture.
# self.password is really a SecurityManager attribute, but it's set in
# MailList.InitVars().
self.mod_password = None
@@ -144,50 +139,15 @@ class SecurityManager:
if ok:
return Defaults.AuthSiteAdmin
elif ac == Defaults.AuthListAdmin:
- def cryptmatchp(response, secret):
- try:
- salt = secret[:2]
- if crypt and crypt.crypt(response, salt) == secret:
- return True
- return False
- except TypeError:
- # BAW: Hard to say why we can get a TypeError here.
- # SF bug report #585776 says crypt.crypt() can raise
- # this if salt contains null bytes, although I don't
- # know how that can happen (perhaps if a MM2.0 list
- # with USE_CRYPT = 0 has been updated? Doubtful.
- return False
# The password for the list admin and list moderator are not
# kept as plain text, but instead as an sha hexdigest. The
# response being passed in is plain text, so we need to
- # digestify it first. Note however, that for backwards
- # compatibility reasons, we'll also check the admin response
- # against the crypted and md5'd passwords, and if they match,
- # we'll auto-migrate the passwords to sha.
+ # digestify it first.
key, secret = self.AuthContextInfo(ac)
if secret is None:
continue
sharesponse = sha.new(response).hexdigest()
- upgrade = ok = False
if sharesponse == secret:
- ok = True
- elif md5.new(response).digest() == secret:
- ok = upgrade = True
- elif cryptmatchp(response, secret):
- ok = upgrade = True
- if upgrade:
- save_and_unlock = False
- if not self.Locked():
- self.Lock()
- save_and_unlock = True
- try:
- self.password = sharesponse
- if save_and_unlock:
- self.Save()
- finally:
- if save_and_unlock:
- self.Unlock()
- if ok:
return ac
elif ac == Defaults.AuthListModerator:
# The list moderator password must be sha'd
@@ -227,21 +187,22 @@ class SecurityManager:
return False
def _cookie_path(self):
- return '/'.join(os.environ['SCRIPT_NAME'].split('/')[:-1]) + '/'
+ script_name = os.environ.get('SCRIPT_NAME', '')
+ return SLASH.join(script_name.split(SLASH)[:-1]) + SLASH
def MakeCookie(self, authcontext, user=None):
key, secret = self.AuthContextInfo(authcontext, user)
- if key is None or secret is None or not isinstance(secret, str):
+ if key is None or secret is None or not isinstance(secret, basestring):
raise ValueError
# Timestamp
issued = int(time.time())
# Get a digest of the secret, plus other information.
- mac = sha.new(secret + `issued`).hexdigest()
+ mac = sha.new(secret + repr(issued)).hexdigest()
# Create the cookie object.
c = Cookie.SimpleCookie()
c[key] = binascii.hexlify(marshal.dumps((issued, mac)))
c[key]['path'] = self._cookie_path()
- # We use session cookies, so don't set `expires' or `max-age' keys.
+ # We use session cookies, so don't set 'expires' or 'max-age' keys.
# Set the RFC 2109 required header.
c[key]['version'] = 1
return c
@@ -290,7 +251,7 @@ class SecurityManager:
for k in c.keys():
if k.startswith(prefix):
usernames.append(k[len(prefix):])
- # If any check out, we're golden. Note: `@'s are no longer legal
+ # If any check out, we're golden. Note: '@'s are no longer legal
# values in cookie keys.
for user in [Utils.UnobscureEmail(u) for u in usernames]:
ok = self.__checkone(c, authcontext, user)
@@ -307,7 +268,7 @@ class SecurityManager:
key, secret = self.AuthContextInfo(authcontext, user)
except Errors.NotAMemberError:
return False
- if not c.has_key(key) or not isinstance(secret, str):
+ if key not in c or not isinstance(secret, basestring):
return False
# Undo the encoding we performed in MakeCookie() above. BAW: I
# believe this is safe from exploit because marshal can't be forced to
@@ -329,7 +290,7 @@ class SecurityManager:
return False
# Calculate what the mac ought to be based on the cookie's timestamp
# and the shared secret.
- mac = sha.new(secret + `issued`).hexdigest()
+ mac = sha.new(secret + repr(issued)).hexdigest()
if mac <> received_mac:
return False
# Authenticated!
diff --git a/Mailman/TopicMgr.py b/Mailman/TopicMgr.py
index 27f113fb6..9df1b34a8 100644
--- a/Mailman/TopicMgr.py
+++ b/Mailman/TopicMgr.py
@@ -1,27 +1,24 @@
-# Copyright (C) 2001,2002 by the Free Software Foundation, Inc.
+# Copyright (C) 2001-2006 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.
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+# USA.
-"""This class mixes in topic feature configuration for mailing lists.
-"""
+"""This class mixes in topic feature configuration for mailing lists."""
import re
-from Mailman import mm_cfg
-from Mailman.i18n import _
-
class TopicMgr:
@@ -46,7 +43,7 @@ class TopicMgr:
# have a name or pattern are not saved when the submit button is
# pressed).
self.topics = []
- self.topics_enabled = 0
+ self.topics_enabled = False
self.topics_bodylines_limit = 5
# Non-configurable
#
diff --git a/Mailman/Utils.py b/Mailman/Utils.py
index edbf25f31..d89d45f59 100644
--- a/Mailman/Utils.py
+++ b/Mailman/Utils.py
@@ -40,6 +40,7 @@ from email.Errors import HeaderParseError
from string import ascii_letters, digits, whitespace
from Mailman import Errors
+from Mailman import database
from Mailman.SafeDict import SafeDict
from Mailman.configuration import config
@@ -50,12 +51,13 @@ except NameError:
from sets import Set as set
-EMPTYSTRING = ''
-UEMPTYSTRING = u''
+AT = '@'
CR = '\r'
-NL = '\n'
DOT = '.'
+EMPTYSTRING = ''
IDENTCHARS = ascii_letters + digits + '_'
+NL = '\n'
+UEMPTYSTRING = u''
# Search for $(identifier)s strings, except that the trailing s is optional,
# since that's a common mistake
@@ -67,28 +69,26 @@ log = logging.getLogger('mailman.error')
-def list_exists(listname):
- """Return true iff list `listname' exists."""
- # The existance of any of the following file proves the list exists
- # <wink>: config.pck, config.pck.last, config.db, config.db.last
- #
- # The former two are for 2.1alpha3 and beyond, while the latter two are
- # for all earlier versions.
- basepath = os.path.join(config.LIST_DATA_DIR, listname)
- for ext in ('.pck', '.pck.last', '.db', '.db.last'):
- dbfile = os.path.join(basepath, 'config' + ext)
- if os.path.exists(dbfile):
- return True
- return False
+def list_exists(fqdn_listname):
+ """Return true iff list `fqdn_listname' exists."""
+ listname, hostname = split_listname(fqdn_listname)
+ return bool(database.find_list(listname, hostname))
def list_names():
- """Return the names of all lists in default list directory."""
- got = set()
- for fn in os.listdir(config.LIST_DATA_DIR):
- if list_exists(fn):
- got.add(fn)
- return got
+ """Return the fqdn names of all lists in default list directory."""
+ return ['%s@%s' % (listname, hostname)
+ for listname, hostname in database.get_list_names()]
+
+
+def split_listname(listname):
+ if AT in listname:
+ return listname.split(AT, 1)
+ return listname, config.DEFAULT_EMAIL_HOST
+
+
+def fqdn_listname(listname):
+ return AT.join(split_listname(listname))
diff --git a/Mailman/Version.py b/Mailman/Version.py
index 7a61ba545..474873314 100644
--- a/Mailman/Version.py
+++ b/Mailman/Version.py
@@ -36,6 +36,10 @@ REL_SERIAL = 1
HEX_VERSION = ((MAJOR_REV << 24) | (MINOR_REV << 16) | (MICRO_REV << 8) |
(REL_LEVEL << 4) | (REL_SERIAL << 0))
+
+# SQLAlchemy database schema version
+DATABASE_SCHEMA_VERSION = 1
+
# config.pck schema version number
DATA_FILE_VERSION = 98
diff --git a/Mailman/bin/export.py b/Mailman/bin/export.py
index 1419e95df..04f18ef16 100644
--- a/Mailman/bin/export.py
+++ b/Mailman/bin/export.py
@@ -41,6 +41,23 @@ DOLLAR_STRINGS = ('msg_header', 'msg_footer',
'autoresponse_admin_text',
'autoresponse_request_text')
+TYPES = {
+ Defaults.Toggle : 'bool',
+ Defaults.Radio : 'radio',
+ Defaults.String : 'string',
+ Defaults.Text : 'text',
+ Defaults.Email : 'email',
+ Defaults.EmailList : 'email_list',
+ Defaults.Host : 'host',
+ Defaults.Number : 'number',
+ Defaults.FileUpload : 'upload',
+ Defaults.Select : 'select',
+ Defaults.Topics : 'topics',
+ Defaults.Checkbox : 'checkbox',
+ Defaults.EmailListEx : 'email_list_ex',
+ Defaults.HeaderFilter : 'header_filter',
+ }
+
class Indenter:
@@ -57,7 +74,8 @@ class Indenter:
assert self._indent >= 0
def write(self, s):
- self._fp.write(self._indent * self._width * ' ')
+ if s <> '\n':
+ self._fp.write(self._indent * self._width * ' ')
self._fp.write(s)
@@ -150,13 +168,14 @@ class XMLDumper(object):
continue
if not is_converted and varname in DOLLAR_STRINGS:
value = Utils.to_dollar(value)
+ widget_type = TYPES[vtype]
if isinstance(value, list):
- self._push_element('option', name=varname)
+ self._push_element('option', name=varname, type=widget_type)
for v in value:
self._element('value', v)
self._pop_element('option')
else:
- self._element('option', value, name=varname)
+ self._element('option', value, name=varname, type=widget_type)
def _dump_list(self, mlist, with_passwords):
# Write list configuration values
@@ -180,7 +199,7 @@ class XMLDumper(object):
attrs = dict(id=member)
cased = mlist.getMemberCPAddress(member)
if cased <> member:
- dict['original'] = cased
+ attrs['original'] = cased
self._push_element('member', **attrs)
self._element('realname', mlist.getMemberName(member))
if with_passwords:
@@ -266,7 +285,7 @@ With this option, user passwords are included in cleartext. For this reason,
the default is to not include passwords."""))
parser.add_option('-l', '--listname',
default=[], action='append', type='string',
- dest='listnames', help=_("""\
+ metavar='LISTNAME', dest='listnames', help=_("""\
The list to include in the output. If not given, then all mailing lists are
included in the XML output. Multiple -l flags may be given."""))
parser.add_option('-C', '--config',
diff --git a/Mailman/bin/genaliases.py b/Mailman/bin/genaliases.py
index 379b4bd94..2796804df 100644
--- a/Mailman/bin/genaliases.py
+++ b/Mailman/bin/genaliases.py
@@ -26,6 +26,7 @@ from Mailman import Utils
from Mailman import Version
from Mailman.configuration import config
from Mailman.i18n import _
+from Mailman.initialize import initialize
__i18n_templates__ = True
@@ -55,7 +56,7 @@ verbosity."""))
def main():
parser, opts, args = parseargs()
- config.load(opts.config)
+ initialize(opts.config)
# Import the MTA specific module
modulename = 'Mailman.MTA.' + config.MTA
diff --git a/Mailman/bin/list_lists.py b/Mailman/bin/list_lists.py
index c9480217d..87e855f4c 100644
--- a/Mailman/bin/list_lists.py
+++ b/Mailman/bin/list_lists.py
@@ -21,8 +21,8 @@ from Mailman import Defaults
from Mailman import MailList
from Mailman import Utils
from Mailman import Version
-from Mailman.configuration import config
from Mailman.i18n import _
+from Mailman.initialize import initialize
__i18n_templates__ = True
@@ -65,7 +65,7 @@ ignored when -b is given."""))
def main():
parser, opts, args = parseargs()
- config.load(opts.config)
+ initialize(opts.config)
names = list(Utils.list_names())
names.sort()
diff --git a/Mailman/bin/mailmanctl.py b/Mailman/bin/mailmanctl.py
index e3a3ec866..5c85799bc 100644
--- a/Mailman/bin/mailmanctl.py
+++ b/Mailman/bin/mailmanctl.py
@@ -34,6 +34,7 @@ from Mailman import loginit
from Mailman.MailList import MailList
from Mailman.configuration import config
from Mailman.i18n import _
+from Mailman.initialize import initialize
__i18n_templates__ = True
@@ -300,9 +301,8 @@ def main():
global elog, qlog, opts
parser, opts, args = parseargs()
- config.load(opts.config)
+ initialize(opts.config)
- loginit.initialize()
elog = logging.getLogger('mailman.error')
qlog = logging.getLogger('mailman.qrunner')
@@ -456,8 +456,8 @@ def main():
# error!)
restarting = ''
if opts.restart:
- if ((exitstatus == None and killsig <> signal.SIGTERM) or
- (killsig == None and exitstatus <> signal.SIGTERM)):
+ if ((exitstatus is None and killsig <> signal.SIGTERM) or
+ (killsig is None and exitstatus <> signal.SIGTERM)):
# Then
restarting = '[restarting]'
qrname, slice, count, restarts = kids[pid]
diff --git a/Mailman/bin/newlist.py b/Mailman/bin/newlist.py
index 461cd9ee6..edd7ad7bd 100644
--- a/Mailman/bin/newlist.py
+++ b/Mailman/bin/newlist.py
@@ -28,6 +28,7 @@ from Mailman import Utils
from Mailman import Version
from Mailman import i18n
from Mailman.configuration import config
+from Mailman.initialize import initialize
_ = i18n._
@@ -83,7 +84,7 @@ listadmin-addr and admin-password are all specified on the command line."""))
def main():
parser, opts, args = parseargs()
- config.load(opts.config)
+ initialize(opts.config)
# Set up some defaults we couldn't set up in parseargs()
if opts.language is None:
@@ -205,8 +206,3 @@ def main():
msg.send(mlist)
finally:
i18n.set_translation(otrans)
-
-
-
-if __name__ == '__main__':
- main()
diff --git a/Mailman/bin/qrunner.py b/Mailman/bin/qrunner.py
index 1fab69d98..3b71bf448 100644
--- a/Mailman/bin/qrunner.py
+++ b/Mailman/bin/qrunner.py
@@ -24,6 +24,7 @@ from Mailman import Version
from Mailman import loginit
from Mailman.configuration import config
from Mailman.i18n import _
+from Mailman.initialize import initialize
__i18n_templates__ = True
@@ -181,13 +182,11 @@ def main():
global log, opts
parser, opts, args = parseargs()
- config.load(opts.config)
-
# If we're not running as a subprocess of mailmanctl, then we'll log to
# stderr in addition to logging to the log files. We do this by passing a
# value of True to propagate, which allows the 'mailman' root logger to
# see the log messages.
- loginit.initialize(propagate=not opts.subproc)
+ initialize(opts.config, propagate_logs=not opts.subproc)
log = logging.getLogger('mailman.qrunner')
if opts.list:
diff --git a/Mailman/bin/rmlist.py b/Mailman/bin/rmlist.py
index 861e79226..4c7d4055b 100644
--- a/Mailman/bin/rmlist.py
+++ b/Mailman/bin/rmlist.py
@@ -20,27 +20,71 @@ import sys
import shutil
import optparse
-from Mailman import MailList
+from Mailman import Errors
from Mailman import Utils
from Mailman import Version
+from Mailman import database
+from Mailman.MailList import MailList
from Mailman.configuration import config
from Mailman.i18n import _
+from Mailman.initialize import initialize
__i18n_templates__ = True
-def remove_it(listname, filename, msg):
+def remove_it(listname, filename, msg, quiet=False):
if os.path.islink(filename):
- print _('Removing $msg')
+ if not quiet:
+ print _('Removing $msg')
os.unlink(filename)
elif os.path.isdir(filename):
- print _('Removing $msg')
+ if not quiet:
+ print _('Removing $msg')
shutil.rmtree(filename)
elif os.path.isfile(filename):
os.unlink(filename)
else:
- print _('$listname $msg not found as $filename')
+ if not quiet:
+ print _('$listname $msg not found as $filename')
+
+
+
+def delete_list(listname, mlist=None, archives=True, quiet=False):
+ removeables = []
+ if mlist:
+ # Remove the list from the database
+ database.remove_list(mlist)
+ # Do the MTA-specific list deletion tasks
+ if config.MTA:
+ modname = 'Mailman.MTA.' + config.MTA
+ __import__(modname)
+ sys.modules[modname].remove(mlist)
+ # Remove the list directory
+ removeables.append((os.path.join('lists', listname), _('list info')))
+
+ # Remove any stale locks associated with the list
+ for filename in os.listdir(config.LOCK_DIR):
+ fn_listname = filename.split('.')[0]
+ if fn_listname == listname:
+ removeables.append((os.path.join(config.LOCK_DIR, filename),
+ _('stale lock file')))
+
+ if archives:
+ removeables.extend([
+ (os.path.join('archives', 'private', listname),
+ _('private archives')),
+ (os.path.join('archives', 'private', listname + '.mbox'),
+ _('private archives')),
+ (os.path.join('archives', 'public', listname),
+ _('public archives')),
+ (os.path.join('archives', 'public', listname + '.mbox'),
+ _('public archives')),
+ ])
+
+ for dirtmpl, msg in removeables:
+ path = os.path.join(config.VAR_PREFIX, dirtmpl)
+ remove_it(listname, path, msg, quiet)
@@ -75,13 +119,12 @@ remove any residual archives."""))
def main():
parser, opts, args = parseargs()
- config.load(opts.config)
+ initialize(opts.config)
- listname = args[0].lower().strip()
- if '@' not in listname:
- listname = '%s@%s' % (listname, config.DEFAULT_EMAIL_HOST)
-
- if not Utils.list_exists(listname):
+ listname = Utils.fqdn_listname(args[0])
+ try:
+ mlist = MailList(listname, lock=False)
+ except Errors.MMUnknownListError:
if not opts.archives:
print >> sys.stderr, _(
'No such list (or list already deleted): $listname')
@@ -89,43 +132,12 @@ def main():
else:
print _(
'No such list: ${listname}. Removing its residual archives.')
+ mlist = None
if not opts.archives:
print _('Not removing archives. Reinvoke with -a to remove them.')
- removeables = []
- if Utils.list_exists(listname):
- mlist = MailList.MailList(listname, lock=False)
- # Do the MTA-specific list deletion tasks
- if config.MTA:
- modname = 'Mailman.MTA.' + config.MTA
- __import__(modname)
- sys.modules[modname].remove(mlist)
-
- removeables.append((os.path.join('lists', listname), _('list info')))
-
- # Remove any stale locks associated with the list
- for filename in os.listdir(config.LOCK_DIR):
- fn_listname = filename.split('.')[0]
- if fn_listname == listname:
- removeables.append((os.path.join(config.LOCK_DIR, filename),
- _('stale lock file')))
-
- if opts.archives:
- removeables.extend([
- (os.path.join('archives', 'private', listname),
- _('private archives')),
- (os.path.join('archives', 'private', listname + '.mbox'),
- _('private archives')),
- (os.path.join('archives', 'public', listname),
- _('public archives')),
- (os.path.join('archives', 'public', listname + '.mbox'),
- _('public archives')),
- ])
-
- for dirtmpl, msg in removeables:
- path = os.path.join(config.VAR_PREFIX, dirtmpl)
- remove_it(listname, path, msg)
+ delete_list(listname, mlist, opts.archives)
diff --git a/Mailman/bin/testall.py b/Mailman/bin/testall.py
index 61b1cc7c7..6539e6e55 100644
--- a/Mailman/bin/testall.py
+++ b/Mailman/bin/testall.py
@@ -25,8 +25,8 @@ import unittest
from Mailman import Version
from Mailman import loginit
-from Mailman.configuration import config
from Mailman.i18n import _
+from Mailman.initialize import initialize
__i18n_templates__ = True
@@ -138,7 +138,7 @@ def main():
global basedir
parser, opts, args = parseargs()
- config.load(opts.config)
+ initialize(opts.config)
if not args:
args = ['.']
loginit.initialize(propagate=opts.stderr)
diff --git a/Mailman/bin/update.py b/Mailman/bin/update.py
index 72a40ab8c..b6953e007 100644
--- a/Mailman/bin/update.py
+++ b/Mailman/bin/update.py
@@ -37,6 +37,7 @@ from Mailman.OldStyleMemberships import OldStyleMemberships
from Mailman.Queue.Switchboard import Switchboard
from Mailman.configuration import config
from Mailman.i18n import _
+from Mailman.initialize import initialize
__i18n_templates__ = True
@@ -658,7 +659,7 @@ def update_pending():
def main():
parser, opts, args = parseargs()
- config.load(opts.config)
+ initialize(opts.config)
# calculate the versions
lastversion, thisversion = calcversions()
diff --git a/Mailman/bin/withlist.py b/Mailman/bin/withlist.py
new file mode 100644
index 000000000..63513d70b
--- /dev/null
+++ b/Mailman/bin/withlist.py
@@ -0,0 +1,249 @@
+# Copyright (C) 1998-2006 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.
+
+import os
+import sys
+import atexit
+import optparse
+
+from Mailman import Errors
+from Mailman import MailList
+from Mailman import Utils
+from Mailman import Version
+from Mailman import interact
+from Mailman.configuration import config
+from Mailman.i18n import _
+from Mailman.initialize import initialize
+
+__i18n_templates__ = True
+
+LAST_MLIST = None
+VERBOSE = True
+LOCK = False
+
+
+
+def exitfunc(mlist):
+ """Unlock a locked list, but do not implicitly Save() it."""
+ if mlist.Locked():
+ if VERBOSE:
+ listname = mlist.fqdn_listname
+ print >> sys.stderr, _(
+ 'Unlocking (but not saving) list: $listname')
+ mlist.Unlock()
+ if VERBOSE:
+ print >> sys.stderr, _('Finalizing')
+
+
+
+def do_list(listname, args, func):
+ global LAST_MLIST
+
+ if '@' not in listname:
+ listname += '@' + config.DEFAULT_EMAIL_HOST
+
+ if VERBOSE:
+ print >> sys.stderr, _('Loading list $listname'),
+ if LOCK:
+ print >> sys.stderr, _('(locked)')
+ else:
+ print >> sys.stderr, _('(unlocked)')
+
+ try:
+ mlist = MailList.MailList(listname, lock=LOCK)
+ atexit.register(exitfunc, mlist)
+ LAST_MLIST = mlist
+ except Errors.MMUnknownListError:
+ print >> sys.stderr, _('Unknown list: $listname')
+
+ # try to import the module and run the callable
+ if func:
+ return func(mlist, *args)
+ return None
+
+
+
+def parseargs():
+ parser = optparse.OptionParser(version=Version.MAILMAN_VERSION,
+ usage=_("""\
+%prog [options] listname [args ...]
+
+General framework for interacting with a mailing list object.
+
+There are two ways to use this script: interactively or programmatically.
+Using it interactively allows you to play with, examine and modify a MailList
+object from Python's interactive interpreter. When running interactively, a
+MailList object called 'm' will be available in the global namespace. It also
+loads the class MailList into the global namespace.
+
+Programmatically, you can write a function to operate on a MailList object,
+and this script will take care of the housekeeping (see below for examples).
+In that case, the general usage syntax is:
+
+ % bin/withlist [options] listname [args ...]
+
+Here's an example of how to use the -r option. Say you have a file in the
+Mailman installation directory called 'listaddr.py', with the following
+two functions:
+
+ def listaddr(mlist):
+ print mlist.GetListEmail()
+
+ def requestaddr(mlist):
+ print mlist.GetRequestEmail()
+
+Now, from the command line you can print the list's posting address by running
+the following from the command line:
+
+ % bin/withlist -r listaddr mylist
+ Loading list: mylist (unlocked)
+ Importing listaddr ...
+ Running listaddr.listaddr() ...
+ mylist@myhost.com
+
+And you can print the list's request address by running:
+
+ % bin/withlist -r listaddr.requestaddr mylist
+ Loading list: mylist (unlocked)
+ Importing listaddr ...
+ Running listaddr.requestaddr() ...
+ mylist-request@myhost.com
+
+As another example, say you wanted to change the password for a particular
+user on a particular list. You could put the following function in a file
+called 'changepw.py':
+
+ from Mailman.Errors import NotAMemberError
+
+ def changepw(mlist, addr, newpasswd):
+ try:
+ mlist.setMemberPassword(addr, newpasswd)
+ mlist.Save()
+ except NotAMemberError:
+ print 'No address matched:', addr
+
+and run this from the command line:
+
+ % bin/withlist -l -r changepw mylist somebody@somewhere.org foobar"""))
+ parser.add_option('-l', '--lock',
+ default=False, action='store_true', help=_("""\
+Lock the list when opening. Normally the list is opened unlocked (e.g. for
+read-only operations). You can always lock the file after the fact by typing
+'m.Lock()'
+
+Note that if you use this option, you should explicitly call m.Save() before
+exiting, since the interpreter's clean up procedure will not automatically
+save changes to the MailList object (but it will unlock the list)."""))
+ parser.add_option('-i', '--interactive',
+ default=None, action='store_true', help=_("""\
+Leaves you at an interactive prompt after all other processing is complete.
+This is the default unless the -r option is given."""))
+ parser.add_option('-r', '--run',
+ type='string', help=_("""\
+This can be used to run a script with the opened MailList object. This works
+by attempting to import 'module' (which must be in the directory containing
+withlist, or already be accessible on your sys.path), and then calling
+'callable' from the module. callable can be a class or function; it is called
+with the MailList object as the first argument. If additional args are given
+on the command line, they are passed as subsequent positional args to the
+callable.
+
+Note that 'module.' is optional; if it is omitted then a module with the name
+'callable' will be imported.
+
+The global variable 'r' will be set to the results of this call."""))
+ parser.add_option('-a', '--all',
+ default=False, action='store_true', help=_("""\
+This option only works with the -r option. Use this if you want to execute
+the script on all mailing lists. When you use -a you should not include a
+listname argument on the command line. The variable 'r' will be a list of all
+the results."""))
+ parser.add_option('-q', '--quiet',
+ default=False, action='store_true',
+ help=_('Suppress all status messages.'))
+ parser.add_option('-C', '--config',
+ help=_('Alternative configuration file to use'))
+ opts, args = parser.parse_args()
+ return parser, opts, args
+
+
+
+def main():
+ global LAST_MLIST, LOCK, VERBOSE
+
+ parser, opts, args = parseargs()
+ initialize(opts.config, not opts.quiet)
+
+ VERBOSE = not opts.quiet
+ LOCK = opts.lock
+
+ # Append our bin directory to sys.path so that any withlist scripts living
+ # their can be simply imported.
+ sys.path.append(config.BIN_DIR)
+
+ # The default for interact is true unless -r was given
+ if opts.interactive is None:
+ if not opts.run:
+ opts.interactive = True
+ else:
+ opts.interactive = False
+
+ dolist = True
+ if len(args) < 1 and not opts.all:
+ warning = _('No list name supplied.')
+ if opts.interactive:
+ # Let them keep going
+ print >> sys.stderr, warning
+ dolist = False
+ else:
+ parser.error(warning)
+
+ if opts.all and not opts.run:
+ parser.error(_('--all requires --run'))
+
+ # Try to import the module for the callable
+ func = None
+ if opts.run:
+ i = opts.run.find('.')
+ if i < 0:
+ module = opts.run
+ callable = opts.run
+ else:
+ module = opts.run[:i]
+ callable = opts.run[i+1:]
+ if VERBOSE:
+ print >> sys.stderr, _('Importing $module ...')
+ mod = __import__(module)
+ if VERBOSE:
+ print >> sys.stderr, _('Running ${module}.${callable}() ...')
+ func = getattr(mod, callable)
+
+ r = None
+ if opts.all:
+ r = [do_list(listname, args, func) for listname in Utils.list_names()]
+ elif dolist:
+ listname = args.pop(0).lower().strip()
+ r = do_list(listname, args, func)
+
+ # Now go to interactive mode, perhaps
+ if opts.interactive:
+ if dolist:
+ banner = _("The variable 'm' is the $listname MailList instance")
+ else:
+ banner = interact.DEFAULT_BANNER
+ overrides = dict(m=LAST_MLIST, r=r)
+ interact.interact(upframe=False, banner=banner, overrides=overrides)
diff --git a/Mailman/configuration.py b/Mailman/configuration.py
index 0cb656c89..6825d5792 100644
--- a/Mailman/configuration.py
+++ b/Mailman/configuration.py
@@ -148,7 +148,13 @@ class Configuration(object):
E.g. 'HTTPRunner' or 'LMTPRunner'. count is the number of qrunner
slices to create, by default, 1.
"""
- Defaults.QRUNNERS.append((name, count))
+ self.QRUNNERS.append((name, count))
+
+ @property
+ def paths(self):
+ return dict([(k, self.__dict__[k])
+ for k in self.__dict__
+ if k.endswith('_DIR')])
diff --git a/Mailman/database/Makefile.in b/Mailman/database/Makefile.in
new file mode 100644
index 000000000..d59f0b5f3
--- /dev/null
+++ b/Mailman/database/Makefile.in
@@ -0,0 +1,71 @@
+# Copyright (C) 1998-2006 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.
+
+# NOTE: Makefile.in is converted into Makefile by the configure script
+# in the parent directory. Once configure has run, you can recreate
+# the Makefile by running just config.status.
+
+# Variables set by configure
+
+VPATH= @srcdir@
+srcdir= @srcdir@
+bindir= @bindir@
+prefix= @prefix@
+exec_prefix= @exec_prefix@
+DESTDIR=
+
+CC= @CC@
+CHMOD= @CHMOD@
+INSTALL= @INSTALL@
+
+DEFS= @DEFS@
+
+# Customizable but not set by configure
+
+OPT= @OPT@
+CFLAGS= $(OPT) $(DEFS)
+PACKAGEDIR= $(prefix)/Mailman/database
+SHELL= /bin/sh
+
+MODULES= *.py
+
+# Modes for directories and executables created by the install
+# process. Default to group-writable directories but
+# user-only-writable for executables.
+DIRMODE= 775
+EXEMODE= 755
+FILEMODE= 644
+INSTALL_PROGRAM=$(INSTALL) -m $(EXEMODE)
+
+
+# Rules
+
+all:
+
+install:
+ for f in $(MODULES); \
+ do \
+ $(INSTALL) -m $(FILEMODE) $(srcdir)/$$f $(DESTDIR)$(PACKAGEDIR); \
+ done
+
+finish:
+
+clean:
+
+distclean:
+ -rm *.pyc
+ -rm Makefile
diff --git a/Mailman/database/__init__.py b/Mailman/database/__init__.py
new file mode 100644
index 000000000..a2b6c97ce
--- /dev/null
+++ b/Mailman/database/__init__.py
@@ -0,0 +1,34 @@
+# Copyright (C) 2006 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 module exposes the higher level interface methods that the rest of
+# Mailman should use. It essentially hides the dbcontext and the SQLAlchemy
+# session from all other code. The preferred way to use these methods is:
+#
+# from Mailman import database
+# database.add_list(foo)
+
+
+def initialize():
+ from Mailman import database
+ from Mailman.database.dbcontext import dbcontext
+
+ dbcontext.connect()
+ for attr in dir(dbcontext):
+ if attr.startswith('api_'):
+ exposed_name = attr[4:]
+ setattr(database, exposed_name, getattr(dbcontext, attr))
diff --git a/Mailman/database/address.py b/Mailman/database/address.py
new file mode 100644
index 000000000..cea1ba072
--- /dev/null
+++ b/Mailman/database/address.py
@@ -0,0 +1,30 @@
+# Copyright (C) 2006 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.
+
+"""Email addresses."""
+
+from sqlalchemy import *
+
+
+
+def make_table(metadata):
+ table = Table(
+ 'Address', metadata,
+ Column('address_id', Integer, primary_key=True),
+ Column('address', Unicode(4096)),
+ )
+ return table
diff --git a/Mailman/database/dbcontext.py b/Mailman/database/dbcontext.py
new file mode 100644
index 000000000..b812c9b77
--- /dev/null
+++ b/Mailman/database/dbcontext.py
@@ -0,0 +1,144 @@
+# Copyright (C) 2006 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.
+
+import weakref
+
+from sqlalchemy import *
+from string import Template
+
+from Mailman import Version
+from Mailman.configuration import config
+from Mailman.database import address
+from Mailman.database import listdata
+from Mailman.database import version
+from Mailman.database.txnsupport import txn
+
+
+
+class MlistRef(weakref.ref):
+ def __init__(self, mlist, callback):
+ super(MlistRef, self).__init__(mlist, callback)
+ self.fqdn_listname = mlist.fqdn_listname
+
+
+
+class DBContext(object):
+ def __init__(self):
+ self.tables = {}
+ self.metadata = None
+ self.session = None
+ # Special transaction used only for MailList.Lock() .Save() and
+ # .Unlock() interface.
+ self._mlist_txns = {}
+
+ def connect(self):
+ # Calculate the engine url
+ url = Template(config.SQLALCHEMY_ENGINE_URL).safe_substitute(
+ config.paths)
+ self.metadata = BoundMetaData(url)
+ self.metadata.engine.echo = config.SQLALCHEMY_ECHO
+ # Create all the table objects, and then let SA conditionally create
+ # them if they don't yet exist.
+ version_table = None
+ for module in (address, listdata, version):
+ table = module.make_table(self.metadata)
+ self.tables[table.name] = table
+ if module is version:
+ version_table = table
+ self.metadata.create_all()
+ # Validate schema version, updating if necessary (XXX)
+ from Mailman.interact import interact
+ r = version_table.select(version_table.c.component=='schema').execute()
+ row = r.fetchone()
+ if row is None:
+ # Database has not yet been initialized
+ version_table.insert().execute(
+ component='schema',
+ version=Version.DATABASE_SCHEMA_VERSION)
+ elif row.version <> Version.DATABASE_SCHEMA_VERSION:
+ # XXX Update schema
+ raise SchemaVersionMismatchError(row.version)
+ self.session = create_session()
+
+ # Cooperative method for use with @txn decorator
+ def _withtxn(self, meth, *args, **kws):
+ try:
+ txn = self.session.create_transaction()
+ rtn = meth(*args, **kws)
+ except:
+ txn.rollback()
+ raise
+ else:
+ txn.commit()
+ return rtn
+
+ def _unlock_mref(self, mref):
+ txn = self._mlist_txns.pop(mref.fqdn_listname, None)
+ if txn is not None:
+ txn.rollback()
+
+ # Higher level interface
+ def api_lock(self, mlist):
+ # Don't try to re-lock a list
+ if mlist.fqdn_listname in self._mlist_txns:
+ return
+ txn = self.session.create_transaction()
+ mref = MlistRef(mlist, self._unlock_mref)
+ self._mlist_txns[mlist.fqdn_listname] = txn
+
+ def api_unlock(self, mlist):
+ txn = self._mlist_txns.pop(mlist.fqdn_listname, None)
+ if txn is not None:
+ txn.rollback()
+
+ def api_save(self, mlist):
+ # When dealing with MailLists, .Save() will always be followed by
+ # .Unlock(). However lists can also be unlocked without saving. But
+ # if it's been locked it will always be unlocked. So the rollback in
+ # unlock will essentially be no-op'd if we've already saved the list.
+ txn = self._mlist_txns.pop(mlist.fqdn_listname, None)
+ if txn is not None:
+ txn.commit()
+
+ @txn
+ def api_add_list(self, mlist):
+ self.session.save(mlist)
+
+ @txn
+ def api_remove_list(self, mlist):
+ self.session.delete(mlist)
+
+ @txn
+ def api_find_list(self, listname, hostname):
+ from Mailman.MailList import MailList
+ q = self.session.query(MailList)
+ mlists = q.select_by(list_name=listname, host_name=hostname)
+ assert len(mlists) <= 1, 'Duplicate mailing lists!'
+ if mlists:
+ return mlists[0]
+ return None
+
+ @txn
+ def api_get_list_names(self):
+ table = self.tables['Listdata']
+ results = table.select().execute()
+ return [(row[table.c.list_name], row[table.c.host_name])
+ for row in results.fetchall()]
+
+
+
+dbcontext = DBContext()
diff --git a/Mailman/database/listdata.py b/Mailman/database/listdata.py
new file mode 100644
index 000000000..a90027e82
--- /dev/null
+++ b/Mailman/database/listdata.py
@@ -0,0 +1,167 @@
+# Copyright (C) 2006 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.
+
+"""SQLAlchemy based list data storage."""
+
+from sqlalchemy import *
+
+
+
+def make_table(metadata):
+ table = Table(
+ 'Listdata', metadata,
+ # Attributes not directly modifiable via the web u/i
+ Column('list_id', Integer, primary_key=True),
+ Column('list_name', Unicode),
+ Column('web_page_url', Unicode),
+ Column('admin_member_chunksize', Integer),
+ # OldStyleMemberships attributes, temporarily stored as pickles.
+ Column('bounce_info', PickleType),
+ Column('delivery_status', PickleType),
+ Column('digest_members', PickleType),
+ Column('language', PickleType),
+ Column('members', PickleType),
+ Column('passwords', PickleType),
+ Column('topics_userinterest', PickleType),
+ Column('user_options', PickleType),
+ Column('usernames', PickleType),
+ # Attributes which are directly modifiable via the web u/i. The more
+ # complicated attributes are currently stored as pickles, though that
+ # will change as the schema and implementation is developed.
+ Column('accept_these_nonmembers', PickleType),
+ Column('acceptable_aliases', PickleType),
+ Column('admin_immed_notify', Boolean),
+ Column('admin_notify_mchanges', Boolean),
+ Column('administrivia', Boolean),
+ Column('advertised', Boolean),
+ Column('anonymous_list', Boolean),
+ Column('archive', Boolean),
+ Column('archive_private', Boolean),
+ Column('archive_volume_frequency', Integer),
+ Column('autorespond_admin', Boolean),
+ Column('autorespond_postings', Boolean),
+ Column('autorespond_requests', Integer),
+ Column('autoresponse_admin_text', Unicode),
+ Column('autoresponse_graceperiod', Integer),
+ Column('autoresponse_postings_text', Unicode),
+ Column('autoresponse_request_text', Unicode),
+ Column('available_languages', PickleType),
+ Column('ban_list', PickleType),
+ Column('bounce_info_stale_after', Integer),
+ Column('bounce_matching_headers', Unicode),
+ Column('bounce_notify_owner_on_disable', Boolean),
+ Column('bounce_notify_owner_on_removal', Boolean),
+ Column('bounce_processing', Boolean),
+ Column('bounce_score_threshold', Integer),
+ Column('bounce_unrecognized_goes_to_list_owner', Boolean),
+ Column('bounce_you_are_disabled_warnings', Integer),
+ Column('bounce_you_are_disabled_warnings_interval', Integer),
+ Column('collapse_alternatives', Boolean),
+ Column('convert_html_to_plaintext', Boolean),
+ Column('default_member_moderation', Boolean),
+ Column('description', Unicode),
+ Column('digest_footer', Unicode),
+ Column('digest_header', Unicode),
+ Column('digest_is_default', Boolean),
+ Column('digest_send_periodic', Boolean),
+ Column('digest_size_threshhold', Integer),
+ Column('digest_volume_frequency', Integer),
+ Column('digestable', Boolean),
+ Column('discard_these_nonmembers', PickleType),
+ Column('emergency', Boolean),
+ Column('encode_ascii_prefixes', Boolean),
+ Column('filter_action', Integer),
+ Column('filter_content', Boolean),
+ Column('filter_filename_extensions', PickleType),
+ Column('filter_mime_types', PickleType),
+ Column('first_strip_reply_to', Boolean),
+ Column('forward_auto_discards', Boolean),
+ Column('gateway_to_mail', Boolean),
+ Column('gateway_to_news', Boolean),
+ Column('generic_nonmember_action', Integer),
+ Column('goodbye_msg', Unicode),
+ Column('header_filter_rules', PickleType),
+ Column('hold_these_nonmembers', PickleType),
+ Column('host_name', Unicode),
+ Column('include_list_post_header', Boolean),
+ Column('include_rfc2369_headers', Boolean),
+ Column('info', Unicode),
+ Column('linked_newsgroup', Unicode),
+ Column('max_days_to_hold', Integer),
+ Column('max_message_size', Integer),
+ Column('max_num_recipients', Integer),
+ Column('member_moderation_action', Boolean),
+ Column('member_moderation_notice', Unicode),
+ Column('mime_is_default_digest', Boolean),
+ Column('moderator', PickleType),
+ Column('msg_footer', Unicode),
+ Column('msg_header', Unicode),
+ Column('new_member_options', Integer),
+ Column('news_moderation', Boolean),
+ Column('news_prefix_subject_too', Boolean),
+ Column('nntp_host', Unicode),
+ Column('nondigestable', Boolean),
+ Column('nonmember_rejection_notice', Unicode),
+ Column('obscure_addresses', Boolean),
+ Column('owner', PickleType),
+ Column('pass_filename_extensions', PickleType),
+ Column('pass_mime_types', PickleType),
+ Column('password', Unicode),
+ Column('personalize', Integer),
+ Column('preferred_language', Unicode),
+ Column('private_roster', Boolean),
+ Column('real_name', Unicode),
+ Column('reject_these_nonmembers', PickleType),
+ Column('reply_goes_to_list', Boolean),
+ Column('reply_to_address', Unicode),
+ Column('require_explicit_destination', Boolean),
+ Column('respond_to_post_requests', Boolean),
+ Column('scrub_nondigest', Boolean),
+ Column('send_goodbye_msg', Boolean),
+ Column('send_reminders', Boolean),
+ Column('send_welcome_msg', Boolean),
+ Column('subject_prefix', Unicode),
+ Column('subscribe_auto_approval', PickleType),
+ Column('subscribe_policy', Integer),
+ Column('topics', PickleType),
+ Column('topics_bodylines_limit', Integer),
+ Column('topics_enabled', Boolean),
+ Column('umbrella_list', Boolean),
+ Column('umbrella_member_suffix', Unicode),
+ Column('unsubscribe_policy', Integer),
+ Column('welcome_msg', Unicode),
+ )
+ # Avoid circular imports
+ from Mailman.MailList import MailList
+ # We need to ensure MailList.InitTempVars() is called whenever a MailList
+ # instance is created from a row. Use a mapper extension for this.
+ mapper(MailList, table, extension=MailListMapperExtension())
+ return table
+
+
+
+class MailListMapperExtension(MapperExtension):
+ def populate_instance(self, mapper, context, row, mlist, ikey, isnew):
+ if isnew:
+ # Get the list name and host name -- which are required by
+ # InitTempVars() from the row data.
+ list_name = row['listdata_list_name']
+ host_name = row['listdata_host_name']
+ fqdn_name = '%s@%s' % (list_name, host_name)
+ mlist.InitTempVars(fqdn_name)
+ # In all cases, let SA proceed as normal
+ return EXT_PASS
diff --git a/Mailman/database/txnsupport.py b/Mailman/database/txnsupport.py
new file mode 100644
index 000000000..30235b4af
--- /dev/null
+++ b/Mailman/database/txnsupport.py
@@ -0,0 +1,34 @@
+# Copyright (C) 2006 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.
+
+# A transaction wrapping decorator. The basic idea is that methods in the
+# DBContext that need to operate on transaction boundaries can be written to
+# be transaction naive. By wrapping them in this decorator, they
+# automatically become transaction safe.
+
+class txn(object):
+ def __init__(self, func):
+ # func is a function object, not a method (even an unbound method).
+ self._func = func
+
+ def __get__(self, obj, type=None):
+ # Return a wrapper function that creates a bound method from the
+ # function, then calls it wrapped in a transaction boundary. Uses a
+ # non-public method called _withtxn() in the object's class.
+ def wrapper(*args, **kws):
+ return obj._withtxn(self._func.__get__(obj), *args, **kws)
+ return wrapper
diff --git a/Mailman/database/version.py b/Mailman/database/version.py
new file mode 100644
index 000000000..93b97e470
--- /dev/null
+++ b/Mailman/database/version.py
@@ -0,0 +1,31 @@
+# Copyright (C) 2006 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.
+
+"""Schema versions."""
+
+from sqlalchemy import *
+
+
+
+def make_table(metadata):
+ table = Table(
+ 'Version', metadata,
+ Column('version_id', Integer, primary_key=True),
+ Column('component', String(20)),
+ Column('version', Integer),
+ )
+ return table
diff --git a/Mailman/htmlformat.py b/Mailman/htmlformat.py
index ae0007794..1bf9548ed 100644
--- a/Mailman/htmlformat.py
+++ b/Mailman/htmlformat.py
@@ -36,12 +36,11 @@ NL = '\n'
# Format an arbitrary object.
def HTMLFormatObject(item, indent):
"Return a presentation of an object, invoking their Format method if any."
- if type(item) == type(''):
- return item
- elif not hasattr(item, "Format"):
- return `item`
- else:
+ if hasattr(item, 'Format'):
return item.Format(indent)
+ if isinstance(item, basestring):
+ return item
+ return str(item)
def CaseInsensitiveKeyedDict(d):
result = {}
diff --git a/Mailman/initialize.py b/Mailman/initialize.py
new file mode 100644
index 000000000..db26d15df
--- /dev/null
+++ b/Mailman/initialize.py
@@ -0,0 +1,36 @@
+# Copyright (C) 2006 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.
+
+"""Initialize all global state.
+
+Every entrance into the Mailman system, be it by command line, mail program,
+or cgi, must call the initialize function here in order for the system's
+global state to be set up properly. Typically this is called after command
+line argument parsing, since some of the initialization behavior is controlled
+by the command line arguments.
+"""
+
+import Mailman.configuration
+import Mailman.database
+import Mailman.loginit
+
+
+
+def initialize(config=None, propagate_logs=False):
+ Mailman.configuration.config.load(config)
+ Mailman.loginit.initialize(propagate_logs)
+ Mailman.database.initialize()
diff --git a/Mailman/interact.py b/Mailman/interact.py
new file mode 100644
index 000000000..939e7224f
--- /dev/null
+++ b/Mailman/interact.py
@@ -0,0 +1,68 @@
+# Copyright (C) 2006 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.
+
+"""Provide an interactive prompt, mimicking the Python interpreter."""
+
+import os
+import sys
+import code
+
+DEFAULT_BANNER = object()
+
+
+
+def interact(upframe=True, banner=DEFAULT_BANNER, overrides=None):
+ # The interactive prompt's namespace
+ ns = dict()
+ # If uplocals is true, also populate the console's locals with the locals
+ # of the frame that called this function (i.e. one up from here).
+ if upframe:
+ frame = sys._getframe(1)
+ ns.update(frame.f_globals)
+ ns.update(frame.f_locals)
+ if overrides is not None:
+ ns.update(overrides)
+ interp = code.InteractiveConsole(ns)
+ # Try to import the readline module, but don't worry if it's unavailable
+ try:
+ import readline
+ except ImportError:
+ pass
+ # Mimic the real interactive interpreter's loading of any $PYTHONSTARTUP
+ # file. Note that if the startup file is not prepared to be exec'd more
+ # than once, this could cause a problem.
+ startup = os.environ.get('PYTHONSTARTUP')
+ if startup:
+ try:
+ execfile(startup, ns)
+ except:
+ pass
+ # We don't want the funky console object in parentheses in the banner.
+ if banner is DEFAULT_BANNER:
+ banner = '''\
+Python %s on %s
+Type "help", "copyright", "credits" or "license" for more information.''' % (
+ sys.version, sys.platform)
+ elif not banner:
+ banner = None
+ interp.interact(banner)
+ # When an exception occurs in the InteractiveConsole, the various
+ # sys.exc_* attributes get set so that error handling works the same way
+ # there as it does in the built-in interpreter. Be anal about clearing
+ # any exception information before we're done.
+ sys.exc_clear()
+ sys.last_type = sys.last_value = sys.last_traceback = None
diff --git a/Mailman/loginit.py b/Mailman/loginit.py
index 85762e7ec..31a2860ec 100644
--- a/Mailman/loginit.py
+++ b/Mailman/loginit.py
@@ -1,132 +1,133 @@
-# Copyright (C) 2006 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.
-
-"""Logging initialization, using Python's standard logging package.
-
-This module cannot be called 'logging' because that would interfere with the
-import below. Ah, for Python 2.5 and absolute imports.
-"""
-
-import os
-import codecs
-import logging
-
-from Mailman.configuration import config
-
-FMT = '%(asctime)s (%(process)d) %(message)s'
-DATEFMT = '%b %d %H:%M:%S %Y'
-LOGGERS = (
- 'bounce',
- 'config',
- 'debug',
- 'error',
- 'fromusenet',
- 'http',
- 'locks',
- 'mischief',
- 'post',
- 'qrunner',
- 'smtp',
- 'smtp-failure',
- 'subscribe',
- 'vette',
- )
-
-_handlers = []
-
-
-
-class ReopenableFileHandler(logging.Handler):
- def __init__(self, filename):
- self._filename = filename
- self._stream = self._open()
- logging.Handler.__init__(self)
-
- def _open(self):
- return codecs.open(self._filename, 'a', 'utf-8')
-
- def flush(self):
- self._stream.flush()
-
- def emit(self, record):
- try:
- msg = self.format(record)
- fs = '%s\n'
- try:
- self._stream.write(fs % msg)
- except UnicodeError:
- self._stream.write(fs % msg.encode('string-escape'))
- self.flush()
- except:
- self.handleError(record)
-
- def close(self):
- self.flush()
- self._stream.close()
- logging.Handler.close(self)
-
- def reopen(self):
- self._stream.close()
- self._stream = self._open()
-
-
-
-def initialize(propagate=False):
- # XXX Don't call logging.basicConfig() because in Python 2.3, it adds a
- # handler to the root logger that we don't want. When Python 2.4 is the
- # minimum requirement, we can use basicConfig() with keyword arguments.
- #
- # The current set of Mailman logs are:
- #
- # error - All exceptions go to this log
- # bounce - All bounce processing logs go here
- # mischief - Various types of hostile activity
- # post - Information about messages posted to mailing lists
- # vette - Information related to admindb activity
- # smtp - Successful SMTP activity
- # smtp-failure - Unsuccessful SMTP activity
- # subscribe - Information about leaves/joins
- # config - Configuration issues
- # locks - Lock steals
- # qrunner - qrunner start/stops
- # fromusenet - Information related to the Usenet to Mailman gateway
- #
- # There was also a 'debug' logger, but that was mostly unused, so instead
- # we'll use debug level on existing loggers.
- #
- # Start by creating a common formatter and the root logger.
- formatter = logging.Formatter(fmt=FMT, datefmt=DATEFMT)
- log = logging.getLogger('mailman')
- handler = logging.StreamHandler()
- handler.setFormatter(formatter)
- log.addHandler(handler)
- log.setLevel(logging.INFO)
- # Create the subloggers
- for logger in LOGGERS:
- log = logging.getLogger('mailman.' + logger)
- # Propagation to the root logger is how we handle logging to stderr
- # when the qrunners are not run as a subprocess of mailmanctl.
- log.propagate = propagate
- handler = ReopenableFileHandler(os.path.join(config.LOG_DIR, logger))
- _handlers.append(handler)
- handler.setFormatter(formatter)
- log.addHandler(handler)
-
-
-
-def reopen():
- for handler in _handlers:
- handler.reopen()
+# Copyright (C) 2006 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.
+
+"""Logging initialization, using Python's standard logging package.
+
+This module cannot be called 'logging' because that would interfere with the
+import below. Ah, for Python 2.5 and absolute imports.
+"""
+
+import os
+import codecs
+import logging
+
+from Mailman.configuration import config
+
+FMT = '%(asctime)s (%(process)d) %(message)s'
+DATEFMT = '%b %d %H:%M:%S %Y'
+LOGGERS = (
+ 'bounce',
+ 'config',
+ 'debug',
+ 'error',
+ 'fromusenet',
+ 'http',
+ 'locks',
+ 'mischief',
+ 'post',
+ 'qrunner',
+ 'smtp',
+ 'smtp-failure',
+ 'subscribe',
+ 'vette',
+ )
+
+_handlers = []
+
+
+
+class ReopenableFileHandler(logging.Handler):
+ def __init__(self, filename):
+ self._filename = filename
+ self._stream = self._open()
+ logging.Handler.__init__(self)
+
+ def _open(self):
+ return codecs.open(self._filename, 'a', 'utf-8')
+
+ def flush(self):
+ self._stream.flush()
+
+ def emit(self, record):
+ try:
+ msg = self.format(record)
+ fs = '%s\n'
+ try:
+ self._stream.write(fs % msg)
+ except UnicodeError:
+ self._stream.write(fs % msg.encode('string-escape'))
+ self.flush()
+ except:
+ self.handleError(record)
+
+ def close(self):
+ self.flush()
+ self._stream.close()
+ logging.Handler.close(self)
+
+ def reopen(self):
+ self._stream.close()
+ self._stream = self._open()
+
+
+
+def initialize(propagate=False):
+ # XXX Don't call logging.basicConfig() because in Python 2.3, it adds a
+ # handler to the root logger that we don't want. When Python 2.4 is the
+ # minimum requirement, we can use basicConfig() with keyword arguments.
+ #
+ # The current set of Mailman logs are:
+ #
+ # error - All exceptions go to this log
+ # bounce - All bounce processing logs go here
+ # mischief - Various types of hostile activity
+ # post - Information about messages posted to mailing lists
+ # vette - Information related to admindb activity
+ # smtp - Successful SMTP activity
+ # smtp-failure - Unsuccessful SMTP activity
+ # subscribe - Information about leaves/joins
+ # config - Configuration issues
+ # locks - Lock steals
+ # qrunner - qrunner start/stops
+ # fromusenet - Information related to the Usenet to Mailman gateway
+ #
+ # There was also a 'debug' logger, but that was mostly unused, so instead
+ # we'll use debug level on existing loggers.
+ #
+ # Start by creating a common formatter and the root logger.
+ formatter = logging.Formatter(fmt=FMT, datefmt=DATEFMT)
+ log = logging.getLogger('mailman')
+ handler = logging.StreamHandler()
+ handler.setFormatter(formatter)
+ log.addHandler(handler)
+ log.setLevel(logging.INFO)
+ # Create the subloggers
+ for logger in LOGGERS:
+ log = logging.getLogger('mailman.' + logger)
+ # Propagation to the root logger is how we handle logging to stderr
+ # when the qrunners are not run as a subprocess of mailmanctl.
+ log.propagate = propagate
+ handler = ReopenableFileHandler(os.path.join(config.LOG_DIR, logger))
+ _handlers.append(handler)
+ handler.setFormatter(formatter)
+ log.addHandler(handler)
+
+
+
+def reopen():
+ for handler in _handlers:
+ handler.reopen()
diff --git a/Mailman/testing/base.py b/Mailman/testing/base.py
index a65c62bdd..cb0a78819 100644
--- a/Mailman/testing/base.py
+++ b/Mailman/testing/base.py
@@ -28,6 +28,7 @@ from cStringIO import StringIO
from Mailman import MailList
from Mailman import Utils
+from Mailman.bin import rmlist
from Mailman.configuration import config
NL = '\n'
@@ -37,9 +38,6 @@ PERMISSIONS = stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH
class TestBase(unittest.TestCase):
def _configure(self, fp):
-## print >> fp, \
-## "MEMBER_ADAPTOR_CLASS = 'Mailman.SAMemberships.SAMemberships'"
-## config.MEMBER_ADAPTOR_CLASS = 'Mailman.SAMemberships.SAMemberships'
print >> fp, 'add_domain("example.com", "www.example.com")'
# Only add this domain once to the current process
if 'example.com' not in config.domains:
@@ -79,16 +77,6 @@ class TestBase(unittest.TestCase):
def tearDown(self):
self._mlist.Unlock()
- listname = self._mlist.fqdn_listname
- for dirtmpl in ['lists/%s',
- 'archives/private/%s',
- 'archives/private/%s.mbox',
- 'archives/public/%s',
- 'archives/public/%s.mbox',
- ]:
- dir = os.path.join(config.VAR_PREFIX, dirtmpl % listname)
- if os.path.islink(dir):
- os.unlink(dir)
- elif os.path.isdir(dir):
- shutil.rmtree(dir)
+ rmlist.delete_list(self._mlist.fqdn_listname, self._mlist,
+ archives=True, quiet=True)
os.unlink(self._config)
diff --git a/Mailman/testing/emailbase.py b/Mailman/testing/emailbase.py
index 53b915abb..3421efb4b 100644
--- a/Mailman/testing/emailbase.py
+++ b/Mailman/testing/emailbase.py
@@ -59,8 +59,12 @@ class EmailBase(TestBase):
def setUp(self):
TestBase.setUp(self)
- # Second argument is ignored.
- self._server = SinkServer(('localhost', TESTPORT), None)
+ try:
+ # Second argument is ignored.
+ self._server = SinkServer(('localhost', TESTPORT), None)
+ except:
+ TestBase.tearDown(self)
+ raise
try:
os.system('bin/mailmanctl -C %s -q start' % self._config)
# If any errors occur in the above, be sure to manually call
@@ -68,6 +72,7 @@ class EmailBase(TestBase):
# setUp().
except:
self.tearDown()
+ raise
def tearDown(self):
os.system('bin/mailmanctl -C %s -q stop' % self._config)
diff --git a/Mailman/testing/test_handlers.py b/Mailman/testing/test_handlers.py
index e36af3b0f..09ca56c29 100644
--- a/Mailman/testing/test_handlers.py
+++ b/Mailman/testing/test_handlers.py
@@ -143,8 +143,8 @@ Your message entitled
was successfully received by the _xtest mailing list.
-List info page: http://www.example.com/mailman/listinfo/_xtest
-Your preferences: http://www.example.com/mailman/options/_xtest/aperson%40example.org
+List info page: http://www.example.com/mailman/listinfo/_xtest@example.com
+Your preferences: http://www.example.com/mailman/options/_xtest@example.com/aperson%40example.org
""")
# Make sure we dequeued the only message
eq(len(self._sb.files()), 0)
@@ -183,8 +183,8 @@ Your message entitled
was successfully received by the _xtest mailing list.
-List info page: http://www.example.com/mailman/listinfo/_xtest
-Your preferences: http://www.example.com/mailman/options/_xtest/aperson%40example.org
+List info page: http://www.example.com/mailman/listinfo/_xtest@example.com
+Your preferences: http://www.example.com/mailman/options/_xtest@example.com/aperson%40example.org
""")
# Make sure we dequeued the only message
eq(len(self._sb.files()), 0)
@@ -690,13 +690,14 @@ From: aperson@example.org
eq(msg['list-id'], '<_xtest.example.com>')
eq(msg['list-help'], '<mailto:_xtest-request@example.com?subject=help>')
eq(msg['list-unsubscribe'],
- '<http://www.example.com/mailman/listinfo/_xtest>,'
+ '<http://www.example.com/mailman/listinfo/_xtest@example.com>,'
'\n\t<mailto:_xtest-request@example.com?subject=unsubscribe>')
eq(msg['list-subscribe'],
- '<http://www.example.com/mailman/listinfo/_xtest>,'
+ '<http://www.example.com/mailman/listinfo/_xtest@example.com>,'
'\n\t<mailto:_xtest-request@example.com?subject=subscribe>')
eq(msg['list-post'], '<mailto:_xtest@example.com>')
- eq(msg['list-archive'], '<http://www.example.com/pipermail/_xtest>')
+ eq(msg['list-archive'],
+ '<http://www.example.com/pipermail/_xtest@example.com>')
def test_list_headers_with_description(self):
eq = self.assertEqual
@@ -710,10 +711,10 @@ From: aperson@example.org
eq(unicode(msg['list-id']), u'A Test List <_xtest.example.com>')
eq(msg['list-help'], '<mailto:_xtest-request@example.com?subject=help>')
eq(msg['list-unsubscribe'],
- '<http://www.example.com/mailman/listinfo/_xtest>,'
+ '<http://www.example.com/mailman/listinfo/_xtest@example.com>,'
'\n\t<mailto:_xtest-request@example.com?subject=unsubscribe>')
eq(msg['list-subscribe'],
- '<http://www.example.com/mailman/listinfo/_xtest>,'
+ '<http://www.example.com/mailman/listinfo/_xtest@example.com>,'
'\n\t<mailto:_xtest-request@example.com?subject=subscribe>')
eq(msg['list-post'], '<mailto:_xtest@example.com>')
diff --git a/Mailman/testing/test_message.py b/Mailman/testing/test_message.py
index 34e5c0139..cbfabd026 100644
--- a/Mailman/testing/test_message.py
+++ b/Mailman/testing/test_message.py
@@ -56,11 +56,11 @@ class TestSentMessage(EmailBase):
eq(qmsg['list-help'],
'<mailto:_xtest-request@example.com?subject=help>')
eq(qmsg['list-subscribe'], """\
-<http://www.example.com/mailman/listinfo/_xtest>,
+<http://www.example.com/mailman/listinfo/_xtest@example.com>,
\t<mailto:_xtest-request@example.com?subject=subscribe>""")
eq(qmsg['list-id'], '<_xtest.example.com>')
eq(qmsg['list-unsubscribe'], """\
-<http://www.example.com/mailman/listinfo/_xtest>,
+<http://www.example.com/mailman/listinfo/_xtest@example.com>,
\t<mailto:_xtest-request@example.com?subject=unsubscribe>""")
eq(qmsg.get_payload(), 'About your test list')
diff --git a/Mailman/testing/test_security_mgr.py b/Mailman/testing/test_security_mgr.py
index 4b2515140..a8b056464 100644
--- a/Mailman/testing/test_security_mgr.py
+++ b/Mailman/testing/test_security_mgr.py
@@ -57,28 +57,28 @@ class TestSecurityManager(TestBase):
mlist.addNewMember('aperson@dom.ain', password='xxXXxx')
self.assertEqual(
mlist.AuthContextInfo(config.AuthUser, 'aperson@dom.ain'),
- ('_xtest+user+aperson--at--dom.ain', 'xxXXxx'))
+ ('_xtest%40example.com+user+aperson--at--dom.ain', 'xxXXxx'))
def test_auth_context_moderator(self):
mlist = self._mlist
mlist.mod_password = 'yyYYyy'
self.assertEqual(
mlist.AuthContextInfo(config.AuthListModerator),
- ('_xtest+moderator', 'yyYYyy'))
+ ('_xtest%40example.com+moderator', 'yyYYyy'))
def test_auth_context_admin(self):
mlist = self._mlist
mlist.password = 'zzZZzz'
self.assertEqual(
mlist.AuthContextInfo(config.AuthListAdmin),
- ('_xtest+admin', 'zzZZzz'))
+ ('_xtest%40example.com+admin', 'zzZZzz'))
def test_auth_context_site(self):
mlist = self._mlist
mlist.password = 'aaAAaa'
self.assertEqual(
mlist.AuthContextInfo(config.AuthSiteAdmin),
- ('_xtest+admin', 'aaAAaa'))
+ ('_xtest%40example.com+admin', 'aaAAaa'))
def test_auth_context_huh(self):
self.assertEqual(
diff --git a/Makefile.in b/Makefile.in
index 1710e568a..50213cf1a 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -1,4 +1,4 @@
-# Copyright (C) 1998-2003 by the Free Software Foundation, Inc.
+# Copyright (C) 1998-2006 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
@@ -12,7 +12,8 @@
#
# 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.
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+# USA.
# NOTE: Makefile.in is converted into Makefile by the configure script
# in the parent directory. Once configure has run, you can recreate
@@ -49,7 +50,7 @@ VAR_DIRS= \
ARCH_INDEP_DIRS= \
bin etc templates scripts cron pythonlib \
- Mailman Mailman/bin Mailman/Cgi Mailman/Archiver \
+ Mailman Mailman/bin Mailman/database Mailman/Cgi Mailman/Archiver \
Mailman/Handlers Mailman/Queue Mailman/Queue/tests \
Mailman/Bouncers \
Mailman/MTA Mailman/Gui Mailman/Commands messages icons \
diff --git a/bin/Makefile.in b/bin/Makefile.in
index 9384e41bf..0dad76c4b 100644
--- a/bin/Makefile.in
+++ b/bin/Makefile.in
@@ -46,7 +46,7 @@ SHELL= /bin/sh
SCRIPTS= mmshell \
remove_members clone_member \
- sync_members check_db withlist \
+ sync_members check_db \
cleanarch \
list_admins \
fix_url.py convert.py transcheck \
@@ -57,7 +57,7 @@ LN_SCRIPTS= add_members arch change_pw check_perms config_list dumpdb \
export find_member genaliases import inject list_lists \
list_members list_owners mailmanctl mmsitepass newlist \
qrunner rmlist show_config show_qfiles testall unshunt \
- update version
+ update version withlist
BUILDDIR= ../build/bin
diff --git a/bin/withlist b/bin/withlist
deleted file mode 100644
index f4648ed78..000000000
--- a/bin/withlist
+++ /dev/null
@@ -1,304 +0,0 @@
-#! @PYTHON@
-#
-# Copyright (C) 1998-2006 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.
-
-"""General framework for interacting with a mailing list object.
-
-There are two ways to use this script: interactively or programmatically.
-Using it interactively allows you to play with, examine and modify a MailList
-object from Python's interactive interpreter. When running interactively, a
-MailList object called `m' will be available in the global namespace. It also
-loads the class MailList into the global namespace.
-
-Programmatically, you can write a function to operate on a MailList object,
-and this script will take care of the housekeeping (see below for examples).
-In that case, the general usage syntax is:
-
-%% bin/withlist [options] listname [args ...]
-
-Options:
-
- -l / --lock
- Lock the list when opening. Normally the list is opened unlocked
- (e.g. for read-only operations). You can always lock the file after
- the fact by typing `m.Lock()'
-
- Note that if you use this option, you should explicitly call m.Save()
- before exiting, since the interpreter's clean up procedure will not
- automatically save changes to the MailList object (but it will unlock
- the list).
-
- -i / --interactive
- Leaves you at an interactive prompt after all other processing is
- complete. This is the default unless the -r option is given.
-
- --run [module.]callable
- -r [module.]callable
- This can be used to run a script with the opened MailList object.
- This works by attempting to import `module' (which must be in the
- directory containing withlist, or already be accessible on your
- sys.path), and then calling `callable' from the module. callable can
- be a class or function; it is called with the MailList object as the
- first argument. If additional args are given on the command line,
- they are passed as subsequent positional args to the callable.
-
- Note that `module.' is optional; if it is omitted then a module with
- the name `callable' will be imported.
-
- The global variable `r' will be set to the results of this call.
-
- --all / -a
- This option only works with the -r option. Use this if you want to
- execute the script on all mailing lists. When you use -a you should
- not include a listname argument on the command line. The variable `r'
- will be a list of all the results.
-
- --quiet / -q
- Suppress all status messages.
-
- --help / -h
- Print this message and exit
-
-
-Here's an example of how to use the -r option. Say you have a file in the
-Mailman installation directory called `listaddr.py', with the following
-two functions:
-
-def listaddr(mlist):
- print mlist.GetListEmail()
-
-def requestaddr(mlist):
- print mlist.GetRequestEmail()
-
-Now, from the command line you can print the list's posting address by running
-the following from the command line:
-
-%% bin/withlist -r listaddr mylist
-Loading list: mylist (unlocked)
-Importing listaddr ...
-Running listaddr.listaddr() ...
-mylist@myhost.com
-
-And you can print the list's request address by running:
-
-%% bin/withlist -r listaddr.requestaddr mylist
-Loading list: mylist (unlocked)
-Importing listaddr ...
-Running listaddr.requestaddr() ...
-mylist-request@myhost.com
-
-As another example, say you wanted to change the password for a particular
-user on a particular list. You could put the following function in a file
-called `changepw.py':
-
-from Mailman.Errors import NotAMemberError
-
-def changepw(mlist, addr, newpasswd):
- try:
- mlist.setMemberPassword(addr, newpasswd)
- mlist.Save()
- except NotAMemberError:
- print 'No address matched:', addr
-
-and run this from the command line:
- %% bin/withlist -l -r changepw mylist somebody@somewhere.org foobar
-"""
-
-import os
-import sys
-import code
-import getopt
-
-import paths
-from Mailman import Errors
-from Mailman import MailList
-from Mailman import Utils
-from Mailman import loginit
-from Mailman.i18n import _
-from Mailman.configuration import config
-
-
-# `m' will be the MailList object and `r' will be the results of the callable
-m = None
-r = None
-VERBOSE = True
-LOCK = False
-
-
-# Put the bin directory on sys.path -- last
-sys.path.append(os.path.dirname(sys.argv[0]))
-
-
-
-def usage(code, msg=''):
- if code:
- fd = sys.stderr
- else:
- fd = sys.stdout
- print >> fd, _(__doc__)
- if msg:
- print >> fd, msg
- sys.exit(code)
-
-
-def atexit():
- """Unlock a locked list, but do not implicitly Save() it.
-
- This does not get run if the interpreter exits because of a signal, or if
- os._exit() is called. It will get called if an exception occurs though.
- """
- global m
- if not m:
- return
- if m.Locked():
- if VERBOSE:
- listname = m.internal_name()
- print >> sys.stderr, _(
- 'Unlocking (but not saving) list: %(listname)s')
- m.Unlock()
- if VERBOSE:
- print >> sys.stderr, _('Finalizing')
- del m
-
-
-
-def do_list(listname, args, func):
- global m
- # first try to open mailing list
- if VERBOSE:
- print >> sys.stderr, _('Loading list %(listname)s'),
- if LOCK:
- print >> sys.stderr, _('(locked)')
- else:
- print >> sys.stderr, _('(unlocked)')
-
- if '@' not in listname:
- listname += '@' + config.DEFAULT_EMAIL_HOST
-
- try:
- m = MailList.MailList(listname, lock=LOCK)
- except Errors.MMUnknownListError:
- print >> sys.stderr, _('Unknown list: %(listname)s')
- m = None
-
- # try to import the module and run the callable
- if func:
- return func(m, *args)
- return None
-
-
-
-def main():
- global VERBOSE
- global LOCK
- global r
- try:
- opts, args = getopt.getopt(
- sys.argv[1:], 'hlr:qia',
- ['help', 'lock', 'run=', 'quiet', 'interactive', 'all'])
- except getopt.error, msg:
- usage(1, msg)
-
- run = None
- interact = None
- all = False
- dolist = True
- for opt, arg in opts:
- if opt in ('-h', '--help'):
- usage(0)
- elif opt in ('-l', '--lock'):
- LOCK = True
- elif opt in ('-r', '--run'):
- run = arg
- elif opt in ('-q', '--quiet'):
- VERBOSE = False
- elif opt in ('-i', '--interactive'):
- interact = True
- elif opt in ('-a', '--all'):
- all = True
-
- if len(args) < 1 and not all:
- warning = _('No list name supplied.')
- if interact:
- # Let them keep going
- print warning
- dolist = False
- else:
- usage(1, warning)
-
- if all and not run:
- usage(1, _('--all requires --run'))
-
- # XXX Allow for specifying the configuration file via -C/--config
- config.load()
-
- # The default for interact is 1 unless -r was given
- if interact is None:
- if run is None:
- interact = True
- else:
- interact = False
-
- # Initialize logging for those modules that use it. Log to stderr based
- # on the VERBOSE flag.
- loginit.initialize(propagate=VERBOSE)
-
- # try to import the module for the callable
- func = None
- if run:
- i = run.find('.')
- if i < 0:
- module = run
- callable = run
- else:
- module = run[:i]
- callable = run[i+1:]
- if VERBOSE:
- print >> sys.stderr, _('Importing %(module)s...')
- mod = __import__(module)
- if VERBOSE:
- print >> sys.stderr, _('Running %(module)s.%(callable)s()...')
- func = getattr(mod, callable)
-
- if all:
- r = [do_list(listname, args, func) for listname in Utils.list_names()]
- elif dolist:
- listname = args.pop(0).lower().strip()
- r = do_list(listname, args, func)
-
- # Now go to interactive mode, perhaps
- if interact:
- # Attempt to import the readline module, so we emulate the interactive
- # console as closely as possible. Don't worry if it doesn't import.
- # readline works by side-effect.
- try:
- import readline
- except ImportError:
- pass
- namespace = globals().copy()
- namespace.update(locals())
- if dolist:
- ban = _("The variable `m' is the %(listname)s MailList instance")
- else:
- ban = None
- code.InteractiveConsole(namespace).interact(ban)
-
-
-
-sys.exitfunc = atexit
-main()
diff --git a/configure b/configure
index 28752c831..47cbffeed 100755
--- a/configure
+++ b/configure
@@ -1,5 +1,5 @@
#! /bin/sh
-# From configure.in Revision: 8051 .
+# From configure.in Revision: 8086 .
# Guess values for system-dependent variables and create Makefiles.
# Generated by GNU Autoconf 2.59 for GNU Mailman 2.2.0a0.
#
@@ -4298,7 +4298,6 @@ build/bin/remove_members:bin/remove_members \
build/bin/reset_pw.py:bin/reset_pw.py \
build/bin/sync_members:bin/sync_members \
build/bin/transcheck:bin/transcheck \
-build/bin/withlist:bin/withlist \
build/bin/templ2pot.py:bin/templ2pot.py \
build/bin/po2templ.py:bin/po2templ.py \
build/contrib/check_perms_grsecurity.py:contrib/check_perms_grsecurity.py \
@@ -4312,7 +4311,7 @@ build/contrib/rotatelogs.py:contrib/rotatelogs.py \
# scripts. They're removed on a make distclean, so we make them here.
mkdir -p build/bin build/contrib build/cron
- ac_config_files="$ac_config_files misc/paths.py Mailman/Defaults.py Mailman/mm_cfg.py.dist src/Makefile misc/Makefile bin/Makefile Mailman/bin/Makefile Mailman/Makefile Mailman/Cgi/Makefile Mailman/Archiver/Makefile Mailman/Commands/Makefile Mailman/Handlers/Makefile Mailman/Bouncers/Makefile Mailman/Queue/Makefile Mailman/Queue/tests/Makefile Mailman/MTA/Makefile Mailman/Gui/Makefile templates/Makefile cron/Makefile scripts/Makefile messages/Makefile cron/crontab.in misc/mailman Makefile Mailman/testing/Makefile Mailman/testing/bounces/Makefile tests/Makefile tests/msgs/Makefile $SCRIPTS"
+ ac_config_files="$ac_config_files misc/paths.py Mailman/Defaults.py Mailman/mm_cfg.py.dist src/Makefile misc/Makefile bin/Makefile Mailman/bin/Makefile Mailman/Makefile Mailman/database/Makefile Mailman/Cgi/Makefile Mailman/Archiver/Makefile Mailman/Commands/Makefile Mailman/Handlers/Makefile Mailman/Bouncers/Makefile Mailman/Queue/Makefile Mailman/Queue/tests/Makefile Mailman/MTA/Makefile Mailman/Gui/Makefile templates/Makefile cron/Makefile scripts/Makefile messages/Makefile cron/crontab.in misc/mailman Makefile Mailman/testing/Makefile Mailman/testing/bounces/Makefile tests/Makefile tests/msgs/Makefile $SCRIPTS"
ac_config_commands="$ac_config_commands default"
cat >confcache <<\_ACEOF
# This file is a shell script that caches the results of configure
@@ -4876,6 +4875,7 @@ do
"bin/Makefile" ) CONFIG_FILES="$CONFIG_FILES bin/Makefile" ;;
"Mailman/bin/Makefile" ) CONFIG_FILES="$CONFIG_FILES Mailman/bin/Makefile" ;;
"Mailman/Makefile" ) CONFIG_FILES="$CONFIG_FILES Mailman/Makefile" ;;
+ "Mailman/database/Makefile" ) CONFIG_FILES="$CONFIG_FILES Mailman/database/Makefile" ;;
"Mailman/Cgi/Makefile" ) CONFIG_FILES="$CONFIG_FILES Mailman/Cgi/Makefile" ;;
"Mailman/Archiver/Makefile" ) CONFIG_FILES="$CONFIG_FILES Mailman/Archiver/Makefile" ;;
"Mailman/Commands/Makefile" ) CONFIG_FILES="$CONFIG_FILES Mailman/Commands/Makefile" ;;
diff --git a/configure.in b/configure.in
index 6e28128a4..098aef9bf 100644
--- a/configure.in
+++ b/configure.in
@@ -16,7 +16,7 @@
# USA.
dnl Process this file with autoconf to produce a configure script.
-AC_REVISION($Revision: 8086 $)
+AC_REVISION($Revision: 8123 $)
AC_PREREQ(2.0)
AC_INIT([GNU Mailman], [2.2.0a0])
@@ -606,7 +606,6 @@ bin/remove_members \
bin/reset_pw.py \
bin/sync_members \
bin/transcheck \
-bin/withlist \
bin/templ2pot.py \
bin/po2templ.py \
contrib/check_perms_grsecurity.py \
@@ -636,7 +635,7 @@ mkdir -p build/bin build/contrib build/cron
dnl Output everything
AC_OUTPUT([misc/paths.py Mailman/Defaults.py Mailman/mm_cfg.py.dist
src/Makefile misc/Makefile bin/Makefile Mailman/bin/Makefile
- Mailman/Makefile Mailman/Cgi/Makefile
+ Mailman/Makefile Mailman/database/Makefile Mailman/Cgi/Makefile
Mailman/Archiver/Makefile Mailman/Commands/Makefile
Mailman/Handlers/Makefile Mailman/Bouncers/Makefile
Mailman/Queue/Makefile Mailman/Queue/tests/Makefile
diff --git a/messages/de/LC_MESSAGES/mailman.po b/messages/de/LC_MESSAGES/mailman.po
index 93c6a9a4c..9403a4f92 100644
--- a/messages/de/LC_MESSAGES/mailman.po
+++ b/messages/de/LC_MESSAGES/mailman.po
@@ -9,7 +9,7 @@
msgid ""
msgstr ""
"Project-Id-Version: mailman\n"
-"POT-Creation-Date: Thu Mar 23 16:59:29 2006\n"
+"POT-Creation-Date: Sun Jan 8 15:53:47 2006\n"
"PO-Revision-Date: 2006-08-25 22:26+0200\n"
"Last-Translator: Peer Heinlein <p.heinlein@heinlein-support.de>\n"
"Language-Team: <de@li.org>\n"
@@ -19,197 +19,197 @@ msgstr ""
"X-Generator: KBabel 1.11.2\n"
# Mailman/Handlers/Decorate.py:49
-#: Mailman/Archiver/HyperArch.py:123
+#: Mailman/Archiver/HyperArch.py:122
msgid "size not available"
msgstr "Größe nicht verfügbar "
-#: Mailman/Archiver/HyperArch.py:129
+#: Mailman/Archiver/HyperArch.py:128
msgid " %(size)i bytes "
msgstr " %(size)i Bytes "
-#: Mailman/Archiver/HyperArch.py:284 Mailman/Archiver/HyperArch.py:287
-#: Mailman/Archiver/HyperArch.py:415 Mailman/Archiver/HyperArch.py:467
-#: Mailman/Archiver/HyperArch.py:575 Mailman/Archiver/HyperArch.py:1049
-#: Mailman/Archiver/HyperArch.py:1178
+#: Mailman/Archiver/HyperArch.py:283 Mailman/Archiver/HyperArch.py:286
+#: Mailman/Archiver/HyperArch.py:419 Mailman/Archiver/HyperArch.py:471
+#: Mailman/Archiver/HyperArch.py:579 Mailman/Archiver/HyperArch.py:1048
+#: Mailman/Archiver/HyperArch.py:1177
msgid " at "
msgstr " at "
-#: Mailman/Archiver/HyperArch.py:496
+#: Mailman/Archiver/HyperArch.py:500
msgid "Previous message:"
msgstr "Vorherige Nachricht:"
# Mailman/Gui/Archive.py:31
-#: Mailman/Archiver/HyperArch.py:518
+#: Mailman/Archiver/HyperArch.py:522
msgid "Next message:"
msgstr "Nächste Nachricht:"
# Mailman/Archiver/pipermail.py:448
-#: Mailman/Archiver/HyperArch.py:690 Mailman/Archiver/HyperArch.py:726
+#: Mailman/Archiver/HyperArch.py:689 Mailman/Archiver/HyperArch.py:725
msgid "thread"
msgstr "Diskussionsfaden"
# Mailman/Handlers/CalcRecips.py:37 Mailman/Handlers/CookHeaders.py:55
# Mailman/Handlers/Hold.py:240 Mailman/Handlers/Hold.py:274
# Mailman/Handlers/ToDigest.py:213 Mailman/ListAdmin.py:202
-#: Mailman/Archiver/HyperArch.py:691 Mailman/Archiver/HyperArch.py:727
+#: Mailman/Archiver/HyperArch.py:690 Mailman/Archiver/HyperArch.py:726
msgid "subject"
msgstr "(kein Betreff)"
-#: Mailman/Archiver/HyperArch.py:692 Mailman/Archiver/HyperArch.py:728
+#: Mailman/Archiver/HyperArch.py:691 Mailman/Archiver/HyperArch.py:727
msgid "author"
msgstr "Autor"
-#: Mailman/Archiver/HyperArch.py:693 Mailman/Archiver/HyperArch.py:729
+#: Mailman/Archiver/HyperArch.py:692 Mailman/Archiver/HyperArch.py:728
msgid "date"
msgstr "Datum"
-#: Mailman/Archiver/HyperArch.py:765
+#: Mailman/Archiver/HyperArch.py:764
msgid "<P>Currently, there are no archives. </P>"
msgstr "<P>Keine Archive vorhanden. </P>"
-#: Mailman/Archiver/HyperArch.py:803
+#: Mailman/Archiver/HyperArch.py:802
msgid "Gzip'd Text%(sz)s"
msgstr "%(sz)s Text gepackt (gzip)"
-#: Mailman/Archiver/HyperArch.py:808
+#: Mailman/Archiver/HyperArch.py:807
msgid "Text%(sz)s"
msgstr "Text%(sz)s"
# Mailman/Cgi/private.py:62
-#: Mailman/Archiver/HyperArch.py:898
+#: Mailman/Archiver/HyperArch.py:897
msgid "figuring article archives\n"
msgstr "Bearbeite Archive\n"
-#: Mailman/Archiver/HyperArch.py:908
+#: Mailman/Archiver/HyperArch.py:907
msgid "April"
msgstr "April"
-#: Mailman/Archiver/HyperArch.py:908
+#: Mailman/Archiver/HyperArch.py:907
msgid "February"
msgstr "Februar"
-#: Mailman/Archiver/HyperArch.py:908
+#: Mailman/Archiver/HyperArch.py:907
msgid "January"
msgstr "Januar"
-#: Mailman/Archiver/HyperArch.py:908
+#: Mailman/Archiver/HyperArch.py:907
msgid "March"
msgstr "März"
-#: Mailman/Archiver/HyperArch.py:909
+#: Mailman/Archiver/HyperArch.py:908
msgid "August"
msgstr "August"
-#: Mailman/Archiver/HyperArch.py:909
+#: Mailman/Archiver/HyperArch.py:908
msgid "July"
msgstr "Juli"
-#: Mailman/Archiver/HyperArch.py:909
+#: Mailman/Archiver/HyperArch.py:908
msgid "June"
msgstr "Juni"
# Mailman/Cgi/options.py:563
-#: Mailman/Archiver/HyperArch.py:909 Mailman/i18n.py:103
+#: Mailman/Archiver/HyperArch.py:908 Mailman/i18n.py:102
msgid "May"
msgstr "Mai"
# Mailman/HTMLFormatter.py:264
-#: Mailman/Archiver/HyperArch.py:910
+#: Mailman/Archiver/HyperArch.py:909
msgid "December"
msgstr "Dezember"
# Mailman/HTMLFormatter.py:264
-#: Mailman/Archiver/HyperArch.py:910
+#: Mailman/Archiver/HyperArch.py:909
msgid "November"
msgstr "November"
-#: Mailman/Archiver/HyperArch.py:910
+#: Mailman/Archiver/HyperArch.py:909
msgid "October"
msgstr "Oktober"
# Mailman/HTMLFormatter.py:264
-#: Mailman/Archiver/HyperArch.py:910
+#: Mailman/Archiver/HyperArch.py:909
msgid "September"
msgstr "September"
# Mailman/Cgi/admin.py:266 Mailman/Cgi/listinfo.py:132 cron/mailpasswds:147
-#: Mailman/Archiver/HyperArch.py:918
+#: Mailman/Archiver/HyperArch.py:917
msgid "First"
msgstr "Erste(s)"
-#: Mailman/Archiver/HyperArch.py:918
+#: Mailman/Archiver/HyperArch.py:917
msgid "Fourth"
msgstr "Vierte(s)"
-#: Mailman/Archiver/HyperArch.py:918
+#: Mailman/Archiver/HyperArch.py:917
msgid "Second"
msgstr "Zweite(r)"
# Mailman/Cgi/admin.py:816
-#: Mailman/Archiver/HyperArch.py:918
+#: Mailman/Archiver/HyperArch.py:917
msgid "Third"
msgstr "Dritte(s)"
-#: Mailman/Archiver/HyperArch.py:920
+#: Mailman/Archiver/HyperArch.py:919
msgid "%(ord)s quarter %(year)i"
msgstr "%(ord)s Quartal %(year)i"
-#: Mailman/Archiver/HyperArch.py:927
+#: Mailman/Archiver/HyperArch.py:926
msgid "%(month)s %(year)i"
msgstr "%(month)s %(year)i"
-#: Mailman/Archiver/HyperArch.py:932
+#: Mailman/Archiver/HyperArch.py:931
msgid "The Week Of Monday %(day)i %(month)s %(year)i"
msgstr "Woche %(day)i %(month)s %(year)i"
-#: Mailman/Archiver/HyperArch.py:936
+#: Mailman/Archiver/HyperArch.py:935
msgid "%(day)i %(month)s %(year)i"
msgstr "%(day)i %(month)s %(year)i"
-#: Mailman/Archiver/HyperArch.py:1036
+#: Mailman/Archiver/HyperArch.py:1035
msgid "Computing threaded index\n"
msgstr "Berechne verketteten Index\n"
# Mailman/Archiver/pipermail.py:414
-#: Mailman/Archiver/HyperArch.py:1301
+#: Mailman/Archiver/HyperArch.py:1300
msgid "Updating HTML for article %(seq)s"
msgstr "Akutalisiere HTML für Artikel %(seq)s"
-#: Mailman/Archiver/HyperArch.py:1308
+#: Mailman/Archiver/HyperArch.py:1307
msgid "article file %(filename)s is missing!"
msgstr "Artikel-Datei %(filename)s fehlt!"
# Mailman/Archiver/pipermail.py:166 Mailman/Archiver/pipermail.py:167
-#: Mailman/Archiver/pipermail.py:179 Mailman/Archiver/pipermail.py:180
+#: Mailman/Archiver/pipermail.py:172 Mailman/Archiver/pipermail.py:173
msgid "No subject"
msgstr "Kein Betreff"
# Mailman/Archiver/pipermail.py:264
-#: Mailman/Archiver/pipermail.py:287
+#: Mailman/Archiver/pipermail.py:285
msgid "Creating archive directory "
msgstr "Archivverzeichnis wird erstellt"
# Mailman/Archiver/pipermail.py:276
-#: Mailman/Archiver/pipermail.py:299
+#: Mailman/Archiver/pipermail.py:297
msgid "Reloading pickled archive state"
msgstr "Vorheriger Archivzustand wird geladen"
# Mailman/Archiver/pipermail.py:303
-#: Mailman/Archiver/pipermail.py:326
+#: Mailman/Archiver/pipermail.py:324
msgid "Pickling archive state into "
msgstr "Schreibe Archivzustand in Datei "
# Mailman/Archiver/pipermail.py:414
-#: Mailman/Archiver/pipermail.py:437
+#: Mailman/Archiver/pipermail.py:435
msgid "Updating index files for archive [%(archive)s]"
msgstr "Index-Dateien für Archiv [%(archive)s] werden aktualisiert"
# Mailman/Archiver/pipermail.py:448
-#: Mailman/Archiver/pipermail.py:470
+#: Mailman/Archiver/pipermail.py:468
msgid " Thread"
msgstr " Diskussionsfaden"
-#: Mailman/Archiver/pipermail.py:577
+#: Mailman/Archiver/pipermail.py:575
msgid "#%(counter)05d %(msgid)s"
msgstr "#%(counter)05d %(msgid)s"
@@ -250,7 +250,7 @@ msgstr " Die letzte Unzustellbarkeitsmeldung von Ihnen kam am %(date)s"
# Mailman/Handlers/ToDigest.py:213 Mailman/ListAdmin.py:202
#: Mailman/Bouncer.py:279 Mailman/Deliverer.py:145
#: Mailman/Handlers/Acknowledge.py:44 Mailman/Handlers/CookHeaders.py:285
-#: Mailman/Handlers/Hold.py:209 Mailman/Handlers/ToDigest.py:235
+#: Mailman/Handlers/Hold.py:209 Mailman/Handlers/ToDigest.py:236
#: Mailman/ListAdmin.py:223
msgid "(no subject)"
msgstr "(kein Betreff)"
@@ -276,7 +276,7 @@ msgstr "Administrator"
# Mailman/Cgi/rmlist.py:60 Mailman/Cgi/roster.py:55
# Mailman/Cgi/subscribe.py:57
#: Mailman/Cgi/admin.py:75 Mailman/Cgi/admindb.py:90 Mailman/Cgi/confirm.py:62
-#: Mailman/Cgi/edithtml.py:70 Mailman/Cgi/listinfo.py:51
+#: Mailman/Cgi/edithtml.py:69 Mailman/Cgi/listinfo.py:51
#: Mailman/Cgi/options.py:78 Mailman/Cgi/private.py:108
#: Mailman/Cgi/rmlist.py:64 Mailman/Cgi/roster.py:57
#: Mailman/Cgi/subscribe.py:61
@@ -286,7 +286,7 @@ msgstr "Keine Liste mit Namen <em>%(safelistname)s</em> vorhanden."
# Mailman/Cgi/admin.py:81 Mailman/Cgi/admindb.py:76
# Mailman/Cgi/edithtml.py:83 Mailman/Cgi/private.py:121
#: Mailman/Cgi/admin.py:90 Mailman/Cgi/admindb.py:106
-#: Mailman/Cgi/edithtml.py:88 Mailman/Cgi/private.py:133
+#: Mailman/Cgi/edithtml.py:87 Mailman/Cgi/private.py:133
msgid "Authorization failed."
msgstr "Zugang verweigert."
@@ -1411,8 +1411,8 @@ msgid "Size:"
msgstr "Grösse:"
# Mailman/Handlers/Decorate.py:49
-#: Mailman/Cgi/admindb.py:499 Mailman/Handlers/Scrubber.py:207
-#: Mailman/Handlers/Scrubber.py:304 Mailman/Handlers/Scrubber.py:305
+#: Mailman/Cgi/admindb.py:499 Mailman/Handlers/Scrubber.py:209
+#: Mailman/Handlers/Scrubber.py:306 Mailman/Handlers/Scrubber.py:307
msgid "not available"
msgstr "nicht verfügbar"
@@ -1522,9 +1522,9 @@ msgid ""
msgstr ""
"<b>Ungültige Bestätigung:</b> %(safecookie)s.\n"
" <p>Bitte beachten Sie, dass Bestätigungen ungefähr %(days)s Tage nach der "
-"Anfrage ungültig werden. Falls Ihre Bestätigung ungültig geworden ist, senden Sie "
-"bitte eine neue Anfrage. Andernfalls geben Sie bitte Ihre Bestätigung <a "
-"href=\"%(confirmurl)s\">erneut</a> ein."
+"Anfrage ungültig werden. Falls Ihre Bestätigung ungültig geworden ist, "
+"senden Sie bitte eine neue Anfrage. Andernfalls geben Sie bitte Ihre "
+"Bestätigung <a href=\"%(confirmurl)s\">erneut</a> ein."
#: Mailman/Cgi/confirm.py:129
msgid ""
@@ -1569,9 +1569,9 @@ msgid ""
" below. Then hit the <em>Submit</em> button to proceed to the next\n"
" confirmation step."
msgstr ""
-"Bitte geben Sie unten den Bestätigungscode (z.B. <em>cookie</em>) ein, den Sie in der e-"
-"Mail erhalten haben. Dann klicken Sie auf <em>Abschicken</em>, um "
-"den nächsten Bestätigungschritt aufzurufen."
+"Bitte geben Sie unten den Bestätigungscode (z.B. <em>cookie</em>) ein, den "
+"Sie in der e-Mail erhalten haben. Dann klicken Sie auf <em>Abschicken</em>, "
+"um den nächsten Bestätigungschritt aufzurufen."
# Mailman/Cgi/confirm.py:166
#: Mailman/Cgi/confirm.py:212
@@ -1869,8 +1869,9 @@ msgid ""
" %(realname)s list. If you think this restriction is erroneous,\n"
" please contact the list owners at %(owneraddr)s."
msgstr ""
-"%(newaddr)s ist ein Abonnement der Mailingliste permanent von der Mailingliste %(realname)s dauerhaft verweigert. "
-"Wenn Sie glauben, dass ein Fehler vorliegt, wenden Sie sich bitte an\n"
+"%(newaddr)s ist ein Abonnement der Mailingliste permanent von der "
+"Mailingliste %(realname)s dauerhaft verweigert. Wenn Sie glauben, dass ein "
+"Fehler vorliegt, wenden Sie sich bitte an\n"
"den Listen-Administrator %(owneraddr)s."
# Mailman/Cgi/confirm.py:427
@@ -1990,9 +1991,8 @@ msgid ""
" the Subject: header <em>%(subject)s</em> to the mailing list\n"
" %(listname)s."
msgstr ""
-" Sie haben die bereits Nachricht mit dem Betreff "
-"header <em>%(subject)s</em> an die Mailingliste %(listname)s erfolgreich "
-"zurückgezogen."
+" Sie haben die bereits Nachricht mit dem Betreff header <em>%"
+"(subject)s</em> an die Mailingliste %(listname)s erfolgreich zurückgezogen."
# Mailman/Cgi/confirm.py:547
#: Mailman/Cgi/confirm.py:661
@@ -2003,7 +2003,8 @@ msgstr "Zurückgehaltene Nachricht verwerfen"
msgid ""
"The held message you were referred to has\n"
" already been handled by the list administrator."
-msgstr "Die zurückgehaltene Nachricht wurde bereits durch den Moderator bearbeitet!"
+msgstr ""
+"Die zurückgehaltene Nachricht wurde bereits durch den Moderator bearbeitet!"
# Mailman/Cgi/confirm.py:571
#: Mailman/Cgi/confirm.py:700
@@ -2059,8 +2060,8 @@ msgid ""
" href=\"%(optionsurl)s\">visit your member options page</a>.\n"
" "
msgstr ""
-" Sie haben erfolgreich das Abonnement der Mailingliste %(listname)s"
-"reaktiviert. Sie können nun die allgemeine <a href=\"%(optionsurl)s\"> "
+" Sie haben erfolgreich das Abonnement der Mailingliste %(listname)"
+"sreaktiviert. Sie können nun die allgemeine <a href=\"%(optionsurl)s\"> "
"Informationsseite der Mailingliste besuchen</a>."
# Mailman/Deliverer.py:103
@@ -2316,9 +2317,9 @@ msgid ""
" "
msgstr ""
"Sie können hier eine neue Mailingliste erzeugen. Der Name der Mailingliste "
-"wird als e-Mailadresse zum Empfang und Versand von Nachrichten benutzt, daher bitte nur "
-"Kleinbuchstaben verwenden. Nachdem die Liste erst einmal angelegt wurde, "
-"können Sie den Namen nicht mehr ändern.\n"
+"wird als e-Mailadresse zum Empfang und Versand von Nachrichten benutzt, "
+"daher bitte nur Kleinbuchstaben verwenden. Nachdem die Liste erst einmal "
+"angelegt wurde, können Sie den Namen nicht mehr ändern.\n"
" <p>Sie müssen auch einen Eigentümer der Mailingliste angeben. Sobald die "
"Liste ereugt wird, geht eine Information darüber an den Eigentümer, zusammen "
"mit dem Passwort der Liste. Der Eigentümer kann das Passwort ändern und "
@@ -2404,81 +2405,81 @@ msgid "Clear Form"
msgstr "Formular löschen"
# Mailman/Cgi/edithtml.py:42
-#: Mailman/Cgi/edithtml.py:45
+#: Mailman/Cgi/edithtml.py:44
msgid "General list information page"
msgstr "Allgemeine Informationsseite der Liste"
# Mailman/Cgi/edithtml.py:43
-#: Mailman/Cgi/edithtml.py:46
+#: Mailman/Cgi/edithtml.py:45
msgid "Subscribe results page"
msgstr "Ergebnis des Abonnements"
# Mailman/Cgi/edithtml.py:44
-#: Mailman/Cgi/edithtml.py:47
+#: Mailman/Cgi/edithtml.py:46
msgid "User specific options page"
msgstr "Benutzerspezifische Optionen"
-#: Mailman/Cgi/edithtml.py:48
+#: Mailman/Cgi/edithtml.py:47
msgid "Welcome email text file"
msgstr "Text-Datei der Willkommens-Mail"
# Mailman/Cgi/edithtml.py:57
-#: Mailman/Cgi/edithtml.py:60
+#: Mailman/Cgi/edithtml.py:59
msgid "List name is required."
msgstr "Der Name der Liste ist erforderlich"
# Mailman/Cgi/edithtml.py:95
-#: Mailman/Cgi/edithtml.py:100
+#: Mailman/Cgi/edithtml.py:99
msgid "%(realname)s -- Edit html for %(template_info)s"
msgstr "%(realname)s -- html für %(template_info)s bearbeiten"
# Mailman/Cgi/edithtml.py:99
-#: Mailman/Cgi/edithtml.py:106
+#: Mailman/Cgi/edithtml.py:105
msgid "Edit HTML : Error"
msgstr "HTML bearbeiten: Fehler"
# Mailman/Cgi/edithtml.py:100
-#: Mailman/Cgi/edithtml.py:107
+#: Mailman/Cgi/edithtml.py:106
msgid "%(safetemplatename)s: Invalid template"
msgstr "%(safetemplate_name)s: Ungültige Vorlage"
# Mailman/Cgi/edithtml.py:105 Mailman/Cgi/edithtml.py:106
-#: Mailman/Cgi/edithtml.py:112 Mailman/Cgi/edithtml.py:113
+#: Mailman/Cgi/edithtml.py:111 Mailman/Cgi/edithtml.py:112
msgid "%(realname)s -- HTML Page Editing"
msgstr "%(realname)s -- Bearbeitung der HTML-Seiten"
# Mailman/Cgi/edithtml.py:107
-#: Mailman/Cgi/edithtml.py:114
+#: Mailman/Cgi/edithtml.py:113
msgid "Select page to edit:"
msgstr "Wählen Sie die Seite zur Bearbeitung aus:"
# Mailman/Cgi/edithtml.py:133
-#: Mailman/Cgi/edithtml.py:140
+#: Mailman/Cgi/edithtml.py:139
msgid "View or edit the list configuration information."
msgstr "Ansehen oder bearbeiten der Listenkonfiguration."
# Mailman/Cgi/edithtml.py:142
-#: Mailman/Cgi/edithtml.py:148
+#: Mailman/Cgi/edithtml.py:147
msgid "When you are done making changes..."
msgstr "Wenn Sie mit den Änderungen fertig sind..."
# Mailman/Cgi/edithtml.py:143
-#: Mailman/Cgi/edithtml.py:149
+#: Mailman/Cgi/edithtml.py:148
msgid "Submit Changes"
msgstr "Änderungen übermitteln"
# Mailman/Cgi/edithtml.py:150
-#: Mailman/Cgi/edithtml.py:156
+#: Mailman/Cgi/edithtml.py:155
msgid "Can't have empty html page."
msgstr "Sie können keine leere HTML-Seite verwenden"
# Mailman/Cgi/edithtml.py:151
-#: Mailman/Cgi/edithtml.py:157
+#: Mailman/Cgi/edithtml.py:156
msgid "HTML Unchanged."
msgstr "HTML unverändert"
# Mailman/Cgi/edithtml.py:159
-#: Mailman/Cgi/edithtml.py:177
+#: Mailman/Cgi/edithtml.py:176
msgid "HTML successfully updated."
msgstr "HTML erfolgreich aktualisiert"
@@ -2611,7 +2612,8 @@ msgstr "Authorisierung fehlgeschlagen."
msgid ""
"The list administrator may not view the other\n"
" subscriptions for this user."
-msgstr "Der Listenadministrator darf nicht anderen Abonnements eines Nutzers sehen. "
+msgstr ""
+"Der Listenadministrator darf nicht anderen Abonnements eines Nutzers sehen. "
#: Mailman/Cgi/options.py:279 Mailman/Cgi/options.py:322
#: Mailman/Cgi/options.py:442 Mailman/Cgi/options.py:658
@@ -2637,7 +2639,10 @@ msgid ""
"The list administrator may not change the names\n"
" or addresses for this user's other subscriptions. However, the\n"
" subscription for this mailing list has been changed."
-msgstr "Der Listenadministrator darf nicht Name und Adresse dieses Users in anderen Mailinglisten ändern. Wie auch immer - für diese Liste wurden die Einträge geändert."
+msgstr ""
+"Der Listenadministrator darf nicht Name und Adresse dieses Users in anderen "
+"Mailinglisten ändern. Wie auch immer - für diese Liste wurden die Einträge "
+"geändert."
# Mailman/Cgi/options.py:231
#: Mailman/Cgi/options.py:342
@@ -2700,7 +2705,8 @@ msgid ""
" think this restriction is erroneous, please contact\n"
" the list owners at %(owneraddr)s."
msgstr ""
-"Die e-Mail-Adresse %(newaddr)s ist von der Liste verbannt worden. Wenn Sie glauben,\n"
+"Die e-Mail-Adresse %(newaddr)s ist von der Liste verbannt worden. Wenn Sie "
+"glauben,\n"
"dass ein Fehler vorliegt, so kontaktieren Sie bitte den Listenbesitzer:\n"
"%(owneraddr)s."
@@ -2728,8 +2734,9 @@ msgid ""
msgstr ""
"Der Administrator der Liste hat die Auslieferung von Einzelnachrichten "
"deaktiviert, daher wurde diese Option nicht eingeschaltet. Die anderen "
-"Optionen wurden erfolgreich gesetzt. "
-"Der Listenadministrator darf nicht das Passwort dieses Users in anderen Mailinglisten ändern. Wie auch immer - für diese Liste wurden das Passwort geändert."
+"Optionen wurden erfolgreich gesetzt. Der Listenadministrator darf nicht das "
+"Passwort dieses Users in anderen Mailinglisten ändern. Wie auch immer - für "
+"diese Liste wurden das Passwort geändert."
# Mailman/Cgi/options.py:312
#: Mailman/Cgi/options.py:456 Mailman/Commands/cmd_password.py:83
@@ -2785,7 +2792,10 @@ msgid ""
" options for this user's other subscriptions. However the\n"
" options for this mailing list subscription has been\n"
" changed."
-msgstr "Der Listenadministrator darf nicht die Optionen dieses Users in anderen Mailinglisten ändern. Wie auch immer - für diese Liste wurden die Optionen geändert."
+msgstr ""
+"Der Listenadministrator darf nicht die Optionen dieses Users in anderen "
+"Mailinglisten ändern. Wie auch immer - für diese Liste wurden die Optionen "
+"geändert."
# Mailman/Cgi/options.py:458
#: Mailman/Cgi/options.py:665
@@ -3196,9 +3206,9 @@ msgid ""
msgstr ""
"Ihr Abonnement-Antrag ist soeben eingetroffen und wird alsbald bearbeitet. "
"Je nach Konfiguration der Mailingliste kann es erforderlich sein, dass Ihr "
-"Antrag erst noch von Ihnen oder vom Administrator der Liste per e-Mail"
-"bestätigt werden muss. Sollte Ihre Bestätigung erforderlich sein, erhalten "
-"Sie in Kürze eine erklärende e-Mail, mit genauen Anweisungen."
+"Antrag erst noch von Ihnen oder vom Administrator der Liste per e-"
+"Mailbestätigt werden muss. Sollte Ihre Bestätigung erforderlich sein, "
+"erhalten Sie in Kürze eine erklärende e-Mail, mit genauen Anweisungen."
#: Mailman/Cgi/subscribe.py:181
msgid ""
@@ -3427,7 +3437,8 @@ msgstr ""
#: Mailman/Commands/cmd_help.py:47
msgid "You can access your personal options via the following url:"
-msgstr "Sie können Ihre persönlichen Einstellungen unter folgender Webadresse ändern:"
+msgstr ""
+"Sie können Ihre persönlichen Einstellungen unter folgender Webadresse ändern:"
#: Mailman/Commands/cmd_info.py:17
msgid ""
@@ -4105,135 +4116,135 @@ msgid "Digest members:"
msgstr "Mitglieder, die Nachrichtensammlungen erhalten:"
# Mailman/Defaults.py:777
-#: Mailman/Defaults.py:1320
+#: Mailman/Defaults.py:1321
msgid "Catalan"
msgstr "Katalanisch"
-#: Mailman/Defaults.py:1321
+#: Mailman/Defaults.py:1322
msgid "Czech"
msgstr "Tschechisch"
-#: Mailman/Defaults.py:1322
+#: Mailman/Defaults.py:1323
msgid "Danish"
msgstr "Dänisch"
-#: Mailman/Defaults.py:1323
+#: Mailman/Defaults.py:1324
msgid "German"
msgstr "Deutsch"
# Mailman/Defaults.py:772
-#: Mailman/Defaults.py:1324
+#: Mailman/Defaults.py:1325
msgid "English (USA)"
msgstr "Englisch (USA)"
# Mailman/Defaults.py:773
-#: Mailman/Defaults.py:1325
+#: Mailman/Defaults.py:1326
msgid "Spanish (Spain)"
msgstr "Spanisch (Spanien)"
-#: Mailman/Defaults.py:1326
+#: Mailman/Defaults.py:1327
msgid "Estonian"
msgstr "Estonian"
-#: Mailman/Defaults.py:1327
+#: Mailman/Defaults.py:1328
msgid "Euskara"
msgstr "Euskarisch"
-#: Mailman/Defaults.py:1328
+#: Mailman/Defaults.py:1329
msgid "Finnish"
msgstr "Finnisch"
# Mailman/Defaults.py:774
-#: Mailman/Defaults.py:1329
+#: Mailman/Defaults.py:1330
msgid "French"
msgstr "Französisch"
-#: Mailman/Defaults.py:1330
+#: Mailman/Defaults.py:1331
msgid "Croatian"
msgstr "Kroatisch"
# Mailman/Defaults.py:776
-#: Mailman/Defaults.py:1331
+#: Mailman/Defaults.py:1332
msgid "Hungarian"
msgstr "Ungarisch"
-#: Mailman/Defaults.py:1332
+#: Mailman/Defaults.py:1333
msgid "Interlingua"
msgstr "Interlingua"
# Mailman/Defaults.py:777
-#: Mailman/Defaults.py:1333
+#: Mailman/Defaults.py:1334
msgid "Italian"
msgstr "Italienisch"
# Mailman/Defaults.py:778
-#: Mailman/Defaults.py:1334
+#: Mailman/Defaults.py:1335
msgid "Japanese"
msgstr "Japanisch"
# Mailman/Defaults.py:779
-#: Mailman/Defaults.py:1335
+#: Mailman/Defaults.py:1336
msgid "Korean"
msgstr "Koreanisch"
-#: Mailman/Defaults.py:1336
+#: Mailman/Defaults.py:1337
msgid "Lithuanian"
msgstr "Lithuanian"
-#: Mailman/Defaults.py:1337
+#: Mailman/Defaults.py:1338
msgid "Dutch"
msgstr "Holländisch"
# Mailman/Defaults.py:779
-#: Mailman/Defaults.py:1338
+#: Mailman/Defaults.py:1339
msgid "Norwegian"
msgstr "Norwegisch"
-#: Mailman/Defaults.py:1339
+#: Mailman/Defaults.py:1340
msgid "Polish"
msgstr "Polish"
-#: Mailman/Defaults.py:1340
+#: Mailman/Defaults.py:1341
msgid "Portuguese"
msgstr "Portuguese"
-#: Mailman/Defaults.py:1341
+#: Mailman/Defaults.py:1342
msgid "Portuguese (Brazil)"
msgstr "Portuguese (Brazil)"
-#: Mailman/Defaults.py:1342
+#: Mailman/Defaults.py:1343
msgid "Romanian"
msgstr "Rumänisch"
-#: Mailman/Defaults.py:1343
+#: Mailman/Defaults.py:1344
msgid "Russian"
msgstr "Russisch"
-#: Mailman/Defaults.py:1344
+#: Mailman/Defaults.py:1345
msgid "Serbian"
msgstr "Serbisch"
-#: Mailman/Defaults.py:1345
+#: Mailman/Defaults.py:1346
msgid "Slovenian"
msgstr "Slowenisch"
-#: Mailman/Defaults.py:1346
+#: Mailman/Defaults.py:1347
msgid "Swedish"
msgstr "Schwedisch"
-#: Mailman/Defaults.py:1347
+#: Mailman/Defaults.py:1348
msgid "Turkish"
msgstr "Türkisch"
-#: Mailman/Defaults.py:1348
+#: Mailman/Defaults.py:1349
msgid "Ukrainian"
msgstr "Ukrainisch"
-#: Mailman/Defaults.py:1349
+#: Mailman/Defaults.py:1350
msgid "Chinese (China)"
msgstr "Chinesisch (China)"
-#: Mailman/Defaults.py:1350
+#: Mailman/Defaults.py:1351
msgid "Chinese (Taiwan)"
msgstr "Chinesisch (Taiwan)"
@@ -4908,7 +4919,9 @@ msgstr ""
msgid ""
"Should Mailman collapse multipart/alternative to its\n"
" first part content?"
-msgstr "Soll Mailman von MIME-Mails mit multipart/alternative nur den ersten MIME-Teil nehmen?"
+msgstr ""
+"Soll Mailman von MIME-Mails mit multipart/alternative nur den ersten MIME-"
+"Teil nehmen?"
#: Mailman/Gui/ContentFilter.py:121
msgid ""
@@ -4925,11 +4938,12 @@ msgstr ""
msgid ""
"Action to take when a message matches the content filtering\n"
" rules."
-msgstr "Aktion die ausgelöst wird, wenn eine Nachricht auf die Filterregeln zutrifft."
+msgstr ""
+"Aktion die ausgelöst wird, wenn eine Nachricht auf die Filterregeln zutrifft."
#: Mailman/Gui/ContentFilter.py:130
msgid ""
-"One of these actions is taken when the message matches one of\n"
+"One of these actions is take when the message matches one of\n"
" the content filtering rules, meaning, the top-level\n"
" content type matches one of the <a\n"
" href=\"?VARHELP=contentfilter/filter_mime_types\"\n"
@@ -5014,7 +5028,8 @@ msgstr "Wie groß (in KB) soll eine Sammlung vor dem Verschicken werden?"
# Mailman/Gui/Digest.py:52
#: Mailman/Gui/Digest.py:63
-msgid "Should a digest be dispatched daily when the size threshold isn't reached?"
+msgid ""
+"Should a digest be dispatched daily when the size threshold isn't reached?"
msgstr "Sammlung täglich schicken wenn die Größe nicht erreicht wird?"
# Mailman/Gui/Digest.py:56
@@ -5364,15 +5379,16 @@ msgstr "Präfix für Betreffzeile der Listennachrichten"
# Mailman/Gui/General.py:117
#: Mailman/Gui/General.py:143
+#, fuzzy
msgid ""
"This text will be prepended to subject lines of messages\n"
-" posted to the list, to distinguish mailing list messages in\n"
+" posted to the list, to distinguish mailing list messages in in\n"
" mailbox summaries. Brevity is premium here, it's ok to "
"shorten\n"
" long mailing list names to something more concise, as long as "
"it\n"
" still identifies the mailing list.\n"
-" You can also add a sequential number by %%d substitution\n"
+" You can also add a sequencial number by %%d substitution\n"
" directive. eg.; [listname %%d] -> [listname 123]\n"
" (listname %%05d) -> (listname 00123)\n"
" "
@@ -5666,7 +5682,8 @@ msgstr ""
msgid ""
"List-specific text prepended to new-subscriber welcome\n"
" message"
-msgstr "Listenspezifischen Text zum Willkommensgruss für neue Abonnenten hinzufügen"
+msgstr ""
+"Listenspezifischen Text zum Willkommensgruss für neue Abonnenten hinzufügen"
# Mailman/Gui/General.py:127
#: Mailman/Gui/General.py:275
@@ -6049,7 +6066,8 @@ msgid ""
"Encode the\n"
" <a href=\"?VARHELP=general/subject_prefix\">subject\n"
" prefix</a> even when it consists of only ASCII characters?"
-msgstr "Soll der Betreff MIME-kodiert werden, auch wenn er nur ASCII-Zeichen enthält?"
+msgstr ""
+"Soll der Betreff MIME-kodiert werden, auch wenn er nur ASCII-Zeichen enthält?"
#: Mailman/Gui/Language.py:95
msgid ""
@@ -6132,7 +6150,8 @@ msgstr "Verhalten in Sachen Datenverkehr bei sofortiger Mailzustellung."
msgid ""
"Can subscribers choose to receive mail immediately, rather\n"
" than in batched digests?"
-msgstr "Haben Abonnenten die Wahl, ob sie e-Mails sofort statt stapelweise erhalten?"
+msgstr ""
+"Haben Abonnenten die Wahl, ob sie e-Mails sofort statt stapelweise erhalten?"
#: Mailman/Gui/NonDigest.py:52
msgid "Full Personalization"
@@ -6276,19 +6295,22 @@ msgstr "Kopfzeile, die e-Mails an reguläre Listenmitglieder hinzugefügt wird"
msgid ""
"Text prepended to the top of every immediately-delivery\n"
" message. "
-msgstr "Der Text wurde am Beginn jeder sofort zuzustellenden Nachricht eingefügt. "
+msgstr ""
+"Der Text wurde am Beginn jeder sofort zuzustellenden Nachricht eingefügt. "
# Mailman/Gui/NonDigest.py:47
#: Mailman/Gui/NonDigest.py:133
msgid "Footer added to mail sent to regular list members"
-msgstr "Fusszeile, die an e-Mails an reguläre Listenmitglieder hinzugefügt wird"
+msgstr ""
+"Fusszeile, die an e-Mails an reguläre Listenmitglieder hinzugefügt wird"
# Mailman/Gui/NonDigest.py:48
#: Mailman/Gui/NonDigest.py:134
msgid ""
"Text appended to the bottom of every immediately-delivery\n"
" message. "
-msgstr "Der Text wurde ans Ende jeder sofort zuzustellenden Nachricht hinzugefügt. "
+msgstr ""
+"Der Text wurde ans Ende jeder sofort zuzustellenden Nachricht hinzugefügt. "
#: Mailman/Gui/NonDigest.py:140
msgid "Scrub attachments of regular delivery message?"
@@ -6437,7 +6459,11 @@ msgid ""
" list is public or not. See also the\n"
" <a href=\"%(admin)s/archive\">Archival Options</a> section for\n"
" separate archive-related privacy settings."
-msgstr "In diesem Bereich können Sie die Ein-/Austragsregeln konfigurieren und festlegen, ob die Mitglieder einer Liste angezeigt werden und ob die Liste öffentlich ist, oder nicht. Bedenken Sie auch die ähnlichen Optionen unter <a href=\"%(admin)s/archive\">Archiv-Optionen</a>! "
+msgstr ""
+"In diesem Bereich können Sie die Ein-/Austragsregeln konfigurieren und "
+"festlegen, ob die Mitglieder einer Liste angezeigt werden und ob die Liste "
+"öffentlich ist, oder nicht. Bedenken Sie auch die ähnlichen Optionen unter "
+"<a href=\"%(admin)s/archive\">Archiv-Optionen</a>! "
# Mailman/Gui/Privacy.py:85
#: Mailman/Gui/Privacy.py:109
@@ -6901,7 +6927,11 @@ msgid ""
" non-members who post to this list. This notice can include\n"
" the list's owner address by %%(listowner)s and replaces the\n"
" internally crafted default message."
-msgstr "Text der für jede Reject-Nachricht benutzt wird, die an Nicht-Mitglieder gesendet wird, die an die Liste senden wollen. Die Adresse des Listenbesitzers kann durch %%(listowner)s eingebunden werden. Der Text ersetzt die Default-Benachrichtigung. "
+msgstr ""
+"Text der für jede Reject-Nachricht benutzt wird, die an Nicht-Mitglieder "
+"gesendet wird, die an die Liste senden wollen. Die Adresse des "
+"Listenbesitzers kann durch %%(listowner)s eingebunden werden. Der Text "
+"ersetzt die Default-Benachrichtigung. "
#: Mailman/Gui/Privacy.py:318
msgid ""
@@ -7120,32 +7150,32 @@ msgstr ""
"Das Muster `%(safepattern)s' ist keine zulässige Regular Expression\n"
"und wird darum ignoriert. "
-#: Mailman/Gui/Topics.py:36
+#: Mailman/Gui/Topics.py:35
msgid "Topics"
msgstr "Themen"
# Mailman/Gui/Topics.py:31
-#: Mailman/Gui/Topics.py:44
+#: Mailman/Gui/Topics.py:43
msgid "List topic keywords"
msgstr "Stichwörter auflisten"
# Mailman/Gui/Topics.py:33
-#: Mailman/Gui/Topics.py:46
+#: Mailman/Gui/Topics.py:45
msgid "Disabled"
msgstr "Ausgeschaltet"
# Mailman/Gui/Topics.py:33
-#: Mailman/Gui/Topics.py:46
+#: Mailman/Gui/Topics.py:45
msgid "Enabled"
msgstr "Eingeschaltet"
# Mailman/Gui/Topics.py:34
-#: Mailman/Gui/Topics.py:47
+#: Mailman/Gui/Topics.py:46
msgid "Should the topic filter be enabled or disabled?"
msgstr "Themenfilter einschalten oder ausschalten?"
# Mailman/Gui/Topics.py:36
-#: Mailman/Gui/Topics.py:49
+#: Mailman/Gui/Topics.py:48
msgid ""
"The topic filter categorizes each incoming email message\n"
" according to <a\n"
@@ -7189,12 +7219,12 @@ msgstr ""
"\">topics_bodylines_limit</a>."
# Mailman/Gui/Topics.py:57
-#: Mailman/Gui/Topics.py:70
+#: Mailman/Gui/Topics.py:69
msgid "How many body lines should the topic matcher scan?"
msgstr "Wieviele Zeilen des Nachrichtentextes soll die Stichwortsuche prüfen?"
# Mailman/Gui/Topics.py:59
-#: Mailman/Gui/Topics.py:72
+#: Mailman/Gui/Topics.py:71
msgid ""
"The topic matcher will scan this many lines of the message\n"
" body looking for topic keyword matches. Body scanning stops "
@@ -7223,12 +7253,13 @@ msgstr ""
"Suche beendet. "
# Mailman/Gui/Topics.py:70
-#: Mailman/Gui/Topics.py:83
+#: Mailman/Gui/Topics.py:82
msgid "Topic keywords, one per line, to match against each message."
-msgstr "Stichwörter (eines pro Zeile), nach denen in jeder Nachricht gesucht wird."
+msgstr ""
+"Stichwörter (eines pro Zeile), nach denen in jeder Nachricht gesucht wird."
# Mailman/Gui/Topics.py:72
-#: Mailman/Gui/Topics.py:85
+#: Mailman/Gui/Topics.py:84
msgid ""
"Each topic keyword is actually a regular expression, which is\n"
" matched against certain parts of a mail message, specifically "
@@ -7246,7 +7277,7 @@ msgstr ""
"Nachrichtentextes auch einen <code>Keywords:</code>, oder <code>Subject:</"
"code> Header enthalten können, der dann auch durchsucht wird."
-#: Mailman/Gui/Topics.py:123
+#: Mailman/Gui/Topics.py:119
msgid ""
"Topic specifications require both a name and\n"
" a pattern. Incomplete topics will be ignored."
@@ -7255,7 +7286,7 @@ msgstr ""
"eines entsprechenden Suchmusters. Unvollständige Themendefinitionen\n"
"werden ignoriert."
-#: Mailman/Gui/Topics.py:132
+#: Mailman/Gui/Topics.py:128
msgid ""
"The topic pattern '%(safepattern)s' is not a\n"
" legal regular expression. It will be discarded."
@@ -7493,7 +7524,8 @@ msgstr "; es wurde aus unbekannten Gründen deaktiviert"
# Mailman/HTMLFormatter.py:133
#: Mailman/HTMLFormatter.py:149
msgid "Note: your list delivery is currently disabled%(reason)s."
-msgstr "Hinweis: die Zustellung von Nachrichten ist momentan abgeschaltet%(reason)s."
+msgstr ""
+"Hinweis: die Zustellung von Nachrichten ist momentan abgeschaltet%(reason)s."
# Mailman/HTMLFormatter.py:135
#: Mailman/HTMLFormatter.py:152
@@ -7702,7 +7734,8 @@ msgstr "(<i>%(which)s ist nur für die Abonnenten der Liste zugänglich.</i>)"
msgid ""
"(<i>%(which)s is only available to the list\n"
" administrator.</i>)"
-msgstr "(<i>%(which)s ist alleine für den Administrator der Liste einsehbar.</i>)"
+msgstr ""
+"(<i>%(which)s ist alleine für den Administrator der Liste einsehbar.</i>)"
# Mailman/HTMLFormatter.py:257
#: Mailman/HTMLFormatter.py:289
@@ -7747,7 +7780,8 @@ msgstr " <p>Geben Sie Ihre "
# Mailman/HTMLFormatter.py:274
#: Mailman/HTMLFormatter.py:307
msgid " and password to visit the subscribers list: <p><center> "
-msgstr " und das Passwort ein, um die Liste der Abonnenten einzusehen: <p><center> "
+msgstr ""
+" und das Passwort ein, um die Liste der Abonnenten einzusehen: <p><center> "
# Mailman/HTMLFormatter.py:279
#: Mailman/HTMLFormatter.py:312
@@ -7953,7 +7987,8 @@ msgstr "Der MIME-Typ der Nachricht wurde vom Moderator nicht erlaubt."
#: Mailman/Handlers/MimeDel.py:72
msgid "The message's file extension was explicitly disallowed"
-msgstr "Die Datei-Endung des Attachments wurde vom Moderator ausdrücklich verboten."
+msgstr ""
+"Die Datei-Endung des Attachments wurde vom Moderator ausdrücklich verboten."
#: Mailman/Handlers/MimeDel.py:75
msgid "The message's file extension was not explicitly allowed"
@@ -8018,7 +8053,7 @@ msgstr ""
msgid "The Mailman Replybot"
msgstr "Der Mailman ReplyBot"
-#: Mailman/Handlers/Scrubber.py:209
+#: Mailman/Handlers/Scrubber.py:211
msgid ""
"An embedded and charset-unspecified text was scrubbed...\n"
"Name: %(filename)s\n"
@@ -8028,11 +8063,11 @@ msgstr ""
"Name: %(filename)s\n"
"URL: %(url)s\n"
-#: Mailman/Handlers/Scrubber.py:219
+#: Mailman/Handlers/Scrubber.py:221
msgid "HTML attachment scrubbed and removed"
msgstr "Ein Dateianhang mit HTML-Daten wurde abgetrennt und entfernt"
-#: Mailman/Handlers/Scrubber.py:235 Mailman/Handlers/Scrubber.py:260
+#: Mailman/Handlers/Scrubber.py:237 Mailman/Handlers/Scrubber.py:262
msgid ""
"An HTML attachment was scrubbed...\n"
"URL: %(url)s\n"
@@ -8043,19 +8078,19 @@ msgstr ""
# Mailman/Handlers/CalcRecips.py:37 Mailman/Handlers/CookHeaders.py:55
# Mailman/Handlers/Hold.py:240 Mailman/Handlers/Hold.py:274
# Mailman/Handlers/ToDigest.py:213 Mailman/ListAdmin.py:202
-#: Mailman/Handlers/Scrubber.py:272
+#: Mailman/Handlers/Scrubber.py:274
msgid "no subject"
msgstr "kein Betreff"
-#: Mailman/Handlers/Scrubber.py:273
+#: Mailman/Handlers/Scrubber.py:275
msgid "no date"
msgstr "kein Datum"
-#: Mailman/Handlers/Scrubber.py:274
+#: Mailman/Handlers/Scrubber.py:276
msgid "unknown sender"
msgstr "unbekannter Sender"
-#: Mailman/Handlers/Scrubber.py:276
+#: Mailman/Handlers/Scrubber.py:278
msgid ""
"An embedded message was scrubbed...\n"
"From: %(who)s\n"
@@ -8071,7 +8106,7 @@ msgstr ""
"Größe: %(size)s\n"
"URL: %(url)s\n"
-#: Mailman/Handlers/Scrubber.py:307
+#: Mailman/Handlers/Scrubber.py:309
msgid ""
"A non-text attachment was scrubbed...\n"
"Name: %(filename)s\n"
@@ -8087,11 +8122,11 @@ msgstr ""
"Beschreibung: %(desc)s\n"
"URL : %(url)s\n"
-#: Mailman/Handlers/Scrubber.py:342
+#: Mailman/Handlers/Scrubber.py:344
msgid "Skipped content of type %(partctype)s\n"
msgstr "Weggelassener Inhalt vom Typ %(partctype)s\n"
-#: Mailman/Handlers/Scrubber.py:382
+#: Mailman/Handlers/Scrubber.py:384
msgid "-------------- next part --------------\n"
msgstr "-------------- nächster Teil --------------\n"
@@ -8099,51 +8134,51 @@ msgstr "-------------- nächster Teil --------------\n"
msgid "The message headers matched a filter rule"
msgstr "Der Nachrichten-Header traf auf eine Filter-Regel zu"
-#: Mailman/Handlers/SpamDetect.py:132
+#: Mailman/Handlers/SpamDetect.py:134
msgid "Message rejected by filter rule match"
msgstr "Nachricht wurde durch Filter-Regeln geblockt"
# Mailman/Handlers/ToDigest.py:148
-#: Mailman/Handlers/ToDigest.py:158
+#: Mailman/Handlers/ToDigest.py:159
msgid "%(realname)s Digest, Vol %(volume)d, Issue %(issue)d"
msgstr "%(realname)s Nachrichtensammlung, Band %(volume)d, Eintrag %(issue)d"
# Mailman/Handlers/ToDigest.py:186
-#: Mailman/Handlers/ToDigest.py:204
+#: Mailman/Handlers/ToDigest.py:205
msgid "digest header"
msgstr "Kopfzeile der Nachrichtensammlung"
# Mailman/Handlers/ToDigest.py:189
-#: Mailman/Handlers/ToDigest.py:207
+#: Mailman/Handlers/ToDigest.py:208
msgid "Digest Header"
msgstr "Kopfzeile der Nachrichtensammlung"
# Mailman/Handlers/ToDigest.py:202
-#: Mailman/Handlers/ToDigest.py:220
+#: Mailman/Handlers/ToDigest.py:221
msgid "Today's Topics:\n"
msgstr "Meldungen des Tages:\n"
# Mailman/Handlers/ToDigest.py:269
-#: Mailman/Handlers/ToDigest.py:300
+#: Mailman/Handlers/ToDigest.py:301
msgid "Today's Topics (%(msgcount)d messages)"
msgstr "Meldungen des Tages (%(msgcount)d messages)"
-#: Mailman/Handlers/ToDigest.py:326
+#: Mailman/Handlers/ToDigest.py:327
msgid "[Message discarded by content filter]"
msgstr "[Nachricht durch Content-Filter verworfen]"
# Mailman/Handlers/ToDigest.py:295
-#: Mailman/Handlers/ToDigest.py:354
+#: Mailman/Handlers/ToDigest.py:359
msgid "digest footer"
msgstr "Fusszeile der Nachrichtensammlung"
# Mailman/Handlers/ToDigest.py:298
-#: Mailman/Handlers/ToDigest.py:357
+#: Mailman/Handlers/ToDigest.py:362
msgid "Digest Footer"
msgstr "Fusszeile der Nachrichtensammlung"
# Mailman/Handlers/ToDigest.py:312
-#: Mailman/Handlers/ToDigest.py:371
+#: Mailman/Handlers/ToDigest.py:376
msgid "End of "
msgstr "Ende "
@@ -8311,50 +8346,52 @@ msgid "%(dbfile)s permissions must be 066x (got %(octmode)s)"
msgstr "%(dbfile)s Zugriffsrechte sollten 066x sein (sind aber %(octmode)s)"
# Mailman/Deliverer.py:76
-#: Mailman/MailList.py:216
+#: Mailman/MailList.py:215
msgid "Your confirmation is required to join the %(listname)s mailing list"
msgstr "Ihre Bestätigung ist nötig um die Liste %(listname)s zu abonnieren."
# Mailman/Deliverer.py:76
-#: Mailman/MailList.py:227
+#: Mailman/MailList.py:225
msgid "Your confirmation is required to leave the %(listname)s mailing list"
msgstr "Ihre Bestätigung ist nötig um die Liste %(listname)s abzubestellen."
# Mailman/MailList.py:614 Mailman/MailList.py:886
-#: Mailman/MailList.py:880 Mailman/MailList.py:1292
+#: Mailman/MailList.py:878 Mailman/MailList.py:1290
msgid " from %(remote)s"
msgstr " von %(remote)s"
# Mailman/MailList.py:649
-#: Mailman/MailList.py:913
+#: Mailman/MailList.py:911
msgid "subscriptions to %(realname)s require moderator approval"
-msgstr "Das Abonnieren von %(realname)s erfordert die Bestätigung des Moderators"
+msgstr ""
+"Das Abonnieren von %(realname)s erfordert die Bestätigung des Moderators"
# Mailman/MailList.py:711 bin/add_members:258
-#: Mailman/MailList.py:982 bin/add_members:242
+#: Mailman/MailList.py:980 bin/add_members:242
msgid "%(realname)s subscription notification"
msgstr "%(realname)s Abonnierungsbenachrichtigung"
# Mailman/Cgi/confirm.py:268
-#: Mailman/MailList.py:1001
+#: Mailman/MailList.py:999
msgid "unsubscriptions require moderator approval"
msgstr "Abbestellungen erfordern die Bestätigung des Moderators"
# Mailman/MailList.py:739
-#: Mailman/MailList.py:1021
+#: Mailman/MailList.py:1019
msgid "%(realname)s unsubscribe notification"
msgstr "%(realname)s Abbestellungbenachrichtigung"
# Mailman/MailList.py:860
-#: Mailman/MailList.py:1201
+#: Mailman/MailList.py:1199
msgid "subscriptions to %(name)s require administrator approval"
-msgstr "Das Abonnieren von %(name)s erfordert die Bestätigung des Aministrators"
+msgstr ""
+"Das Abonnieren von %(name)s erfordert die Bestätigung des Aministrators"
-#: Mailman/MailList.py:1464
+#: Mailman/MailList.py:1462
msgid "Last autoresponse notification for today"
msgstr "Letzte automatische Benachrichtigung für heute"
-#: Mailman/Queue/BounceRunner.py:310
+#: Mailman/Queue/BounceRunner.py:298
msgid ""
"The attached message was received as a bounce, but either the bounce format\n"
"was not recognized, or no member addresses could be extracted from it. "
@@ -8377,7 +8414,7 @@ msgstr ""
"\n"
# Mailman/MailList.py:711 bin/add_members:258
-#: Mailman/Queue/BounceRunner.py:320
+#: Mailman/Queue/BounceRunner.py:308
msgid "Uncaught bounce notification"
msgstr "Unerkannte Bounce-Benachrichtigung"
@@ -8465,64 +8502,64 @@ msgstr "Gnu's Not Unix (Gnu's sind keine Unickse)"
# Mailman/Gui/Privacy.py:111 Mailman/Gui/Privacy.py:114
# Mailman/Gui/Privacy.py:147 Mailman/Gui/Privacy.py:221
# Mailman/Gui/Usenet.py:43 Mailman/Gui/Usenet.py:47 Mailman/Gui/Usenet.py:51
-#: Mailman/i18n.py:98
+#: Mailman/i18n.py:97
msgid "Mon"
msgstr "Mo"
-#: Mailman/i18n.py:98
+#: Mailman/i18n.py:97
msgid "Thu"
msgstr "Do"
-#: Mailman/i18n.py:98
+#: Mailman/i18n.py:97
msgid "Tue"
msgstr "Di"
-#: Mailman/i18n.py:98
+#: Mailman/i18n.py:97
msgid "Wed"
msgstr "Mi"
-#: Mailman/i18n.py:99
+#: Mailman/i18n.py:98
msgid "Fri"
msgstr "Fr"
-#: Mailman/i18n.py:99
+#: Mailman/i18n.py:98
msgid "Sat"
msgstr "Sa"
-#: Mailman/i18n.py:99
+#: Mailman/i18n.py:98
msgid "Sun"
msgstr "So"
# Mailman/Cgi/admindb.py:181 Mailman/Cgi/admindb.py:280
-#: Mailman/i18n.py:103
+#: Mailman/i18n.py:102
msgid "Apr"
msgstr "Apr"
-#: Mailman/i18n.py:103
+#: Mailman/i18n.py:102
msgid "Feb"
msgstr "Feb"
-#: Mailman/i18n.py:103
+#: Mailman/i18n.py:102
msgid "Jan"
msgstr "Jan"
-#: Mailman/i18n.py:103
+#: Mailman/i18n.py:102
msgid "Jun"
msgstr "Jun"
-#: Mailman/i18n.py:103
+#: Mailman/i18n.py:102
msgid "Mar"
msgstr "Mär"
-#: Mailman/i18n.py:104
+#: Mailman/i18n.py:103
msgid "Aug"
msgstr "Aug"
-#: Mailman/i18n.py:104
+#: Mailman/i18n.py:103
msgid "Dec"
msgstr "Dez"
-#: Mailman/i18n.py:104
+#: Mailman/i18n.py:103
msgid "Jul"
msgstr "Jul"
@@ -8540,25 +8577,27 @@ msgstr "Jul"
# Mailman/Gui/Privacy.py:111 Mailman/Gui/Privacy.py:114
# Mailman/Gui/Privacy.py:147 Mailman/Gui/Privacy.py:221
# Mailman/Gui/Usenet.py:43 Mailman/Gui/Usenet.py:47 Mailman/Gui/Usenet.py:51
-#: Mailman/i18n.py:104
+#: Mailman/i18n.py:103
msgid "Nov"
msgstr "Nov"
-#: Mailman/i18n.py:104
+#: Mailman/i18n.py:103
msgid "Oct"
msgstr "Okt"
-#: Mailman/i18n.py:104
+#: Mailman/i18n.py:103
msgid "Sep"
msgstr "Sep"
-#: Mailman/i18n.py:107
+#: Mailman/i18n.py:106
msgid "Server Local Time"
msgstr "Lokale Serverzeit"
-#: Mailman/i18n.py:146
-msgid "%(wday)s %(mon)s %(day)2i %(hh)02i:%(mm)02i:%(ss)02i %(tzname)s %(year)04i"
-msgstr "%(wday)s %(mon)s %(day)2i %(hh)02i:%(mm)02i:%(ss)02i %(tzname)s %(year)04i"
+#: Mailman/i18n.py:139
+msgid ""
+"%(wday)s %(mon)s %(day)2i %(hh)02i:%(mm)02i:%(ss)02i %(tzname)s %(year)04i"
+msgstr ""
+"%(wday)s %(mon)s %(day)2i %(hh)02i:%(mm)02i:%(ss)02i %(tzname)s %(year)04i"
#: bin/add_members:26
msgid ""
@@ -8691,9 +8730,9 @@ msgstr ""
# Mailman/Cgi/options.py:67 Mailman/Cgi/private.py:96
# Mailman/Cgi/rmlist.py:60 Mailman/Cgi/roster.py:55
# Mailman/Cgi/subscribe.py:57
-#: bin/add_members:210 bin/config_list:109 bin/config_list.orig:105
-#: bin/find_member:97 bin/inject:90 bin/list_admins:89 bin/list_members:232
-#: bin/sync_members:222 cron/bumpdigests:86
+#: bin/add_members:210 bin/config_list:109 bin/find_member:97 bin/inject:90
+#: bin/list_admins:89 bin/list_members:232 bin/sync_members:222
+#: cron/bumpdigests:86
msgid "No such list: %(listname)s"
msgstr "Liste nicht vorhanden: %(listname)s"
@@ -8801,7 +8840,7 @@ msgstr "Der Name der Liste ist erforderlich"
# Mailman/Cgi/options.py:67 Mailman/Cgi/private.py:96
# Mailman/Cgi/rmlist.py:60 Mailman/Cgi/roster.py:55
# Mailman/Cgi/subscribe.py:57
-#: bin/arch:143 bin/change_pw:106 bin/config_list:256 bin/config_list.orig:242
+#: bin/arch:143 bin/change_pw:106 bin/config_list:256
msgid ""
"No such list \"%(listname)s\"\n"
"%(e)s"
@@ -9197,7 +9236,7 @@ msgstr ""
"Zum Beheben starten Sie erneut als %(MAILMAN_USER)s (oder 'root') mit der "
"Option -f."
-#: bin/cleanarch:20
+#: bin/cleanarch:19
msgid ""
"Clean up an .mbox archive file.\n"
"\n"
@@ -9231,15 +9270,15 @@ msgid ""
" Print this message and exit\n"
msgstr ""
-#: bin/cleanarch:83
+#: bin/cleanarch:82
msgid "Unix-From line changed: %(lineno)d"
msgstr "Unix-From Zeile geändert: %(lineno)d"
-#: bin/cleanarch:111
+#: bin/cleanarch:110
msgid "Bad status number: %(arg)s"
msgstr "Ungültiges Argument: %(args)s"
-#: bin/cleanarch:167
+#: bin/cleanarch:166
msgid "%(messages)d messages found"
msgstr "%(messages)d Nachrichten gefunden"
@@ -9400,7 +9439,7 @@ msgstr ""
"Fehler beim Öffnen der Liste \"%(listname)s\", wird übersprungen.\n"
"%(e)s"
-#: bin/config_list:20 bin/config_list.orig:19
+#: bin/config_list:20
msgid ""
"Configure a list from a text file description.\n"
"\n"
@@ -9515,56 +9554,48 @@ msgstr ""
"## Ausgelesen: %(when)s\n"
# Mailman/Gui/Digest.py:27
-#: bin/config_list:143 bin/config_list.orig:131
+#: bin/config_list:143
msgid "options"
msgstr "Optionen"
-#: bin/config_list:202 bin/config_list.orig:188
+#: bin/config_list:202
msgid "legal values are:"
msgstr "zulässige Werte sind: "
-#: bin/config_list:269 bin/config_list.orig:255
+#: bin/config_list:269
msgid "attribute \"%(k)s\" ignored"
msgstr "Attribut \"%(k)s\" wurde ignoriert"
-#: bin/config_list:272 bin/config_list.orig:258
+#: bin/config_list:272
msgid "attribute \"%(k)s\" changed"
msgstr "Attribut \"%(k)s\" wurde geändert"
-#: bin/config_list:278 bin/config_list.orig:264
+#: bin/config_list:278
msgid "Non-standard property restored: %(k)s"
msgstr "Nicht-Standard-Einstellung wurde wiederhergestellt: %(k)s"
-#: bin/config_list:286 bin/config_list.orig:272
+#: bin/config_list:286
msgid "Invalid value for property: %(k)s"
msgstr "Ungültiger Wert für %(k)s"
# Mailman/Cgi/admin.py:1169
-#: bin/config_list:288 bin/config_list.orig:274
+#: bin/config_list:288
msgid "Bad email address for option %(k)s: %(v)s"
msgstr "Ungültige e-Mail-Adresse für %(k)s: %(v)s"
-#: bin/config_list:345 bin/config_list.orig:331
+#: bin/config_list:345
msgid "Only one of -i or -o is allowed"
msgstr "Nur eine der -i und -o Optionen ist erlaubt"
-#: bin/config_list:347 bin/config_list.orig:333
+#: bin/config_list:347
msgid "One of -i or -o is required"
msgstr "Entweder -i oder -o muss angegeben werden"
# Mailman/Cgi/edithtml.py:57
-#: bin/config_list:351 bin/config_list.orig:337
+#: bin/config_list:351
msgid "List name is required"
msgstr "Der Name der Liste ist erforderlich"
-#: bin/config_list.orig:109
-msgid ""
-"## \"%(listname)s\" mailing list configuration settings -*- python -*-\n"
-"## captured on %(when)s\n"
-msgstr ""
-"## Konfiguration der Mailingliste \"%(listname)s\" -*- python -*-\n"
-"## Aufgezeichnet um %(when)s\n"
-
#: bin/convert.py:19
msgid ""
"Convert a list's interpolation strings from %-strings to $-strings.\n"
@@ -10184,7 +10215,7 @@ msgid ""
" all the lists will be displayed.\n"
msgstr ""
-#: bin/mailmanctl:20
+#: bin/mailmanctl:19
msgid ""
"Primary start-up and shutdown script for Mailman's qrunner daemon.\n"
"\n"
@@ -10346,23 +10377,23 @@ msgstr ""
"\n"
" reopen - Schliessen der Logdateien, gefolgt von einem Neuöffnen.\n"
-#: bin/mailmanctl:152
+#: bin/mailmanctl:151
msgid "PID unreadable in: %(pidfile)s"
msgstr "PID in %(pidfile)s nicht lesbar"
-#: bin/mailmanctl:154
+#: bin/mailmanctl:153
msgid "Is qrunner even running?"
msgstr "Läuft qrunner überhaupt?"
-#: bin/mailmanctl:160
+#: bin/mailmanctl:159
msgid "No child with pid: %(pid)s"
msgstr "Kein Kindprozess mit der PID %(pid)s vorhanden"
-#: bin/mailmanctl:162
+#: bin/mailmanctl:161
msgid "Stale pid file removed."
msgstr "Liegengebliebene pid-Datei entfernt."
-#: bin/mailmanctl:220
+#: bin/mailmanctl:219
msgid ""
"The master qrunner lock could not be acquired because it appears as if "
"another\n"
@@ -10371,7 +10402,7 @@ msgstr ""
"Die Sperre des qrunner-Masterprozesses konnte nicht übernommen werden.\n"
"Ein anderer qrunner scheint bereits zu laufen.\n"
-#: bin/mailmanctl:226
+#: bin/mailmanctl:225
msgid ""
"The master qrunner lock could not be acquired. It appears as though there "
"is\n"
@@ -10381,7 +10412,7 @@ msgstr ""
"sieht so aus, als ob dies eine ungültige Sperre ist.\n"
"Rufen Sie das das Programm mailmanctl mit der -s Option nochmals auf.\n"
-#: bin/mailmanctl:232
+#: bin/mailmanctl:231
msgid ""
"The master qrunner lock could not be acquired, because it appears as if "
"some\n"
@@ -10410,42 +10441,42 @@ msgstr ""
# Mailman/Cgi/create.py:101 Mailman/Cgi/create.py:174 bin/newlist:122
# bin/newlist:154
-#: bin/mailmanctl:279 cron/mailpasswds:119
+#: bin/mailmanctl:278 cron/mailpasswds:119
msgid "Site list is missing: %(sitelistname)s"
msgstr "Vermisse die Mailingliste: %(sitelistname)s"
-#: bin/mailmanctl:304
+#: bin/mailmanctl:303
msgid "Run this program as root or as the %(name)s user, or use -u."
msgstr ""
"Führen Sie dieses Programm als Benutzer root, oder %(name)s aus,\n"
"bzw. benutzen Sie die Option -u."
# Mailman/Cgi/admindb.py:338 Mailman/ListAdmin.py:258
-#: bin/mailmanctl:335
+#: bin/mailmanctl:334
msgid "No command given."
msgstr "Keine Anweisung angegeben."
-#: bin/mailmanctl:338
+#: bin/mailmanctl:337
msgid "Bad command: %(command)s"
msgstr "Unverständliche Anweisung: %(command)s"
-#: bin/mailmanctl:343
+#: bin/mailmanctl:342
msgid "Warning! You may encounter permission problems."
msgstr "Warnung! Sie könnten Probleme mit Dateirechte bekommen!"
-#: bin/mailmanctl:352
+#: bin/mailmanctl:351
msgid "Shutting down Mailman's master qrunner"
msgstr "Herunterfahren des qrunner-Masterprozesses"
-#: bin/mailmanctl:359
+#: bin/mailmanctl:358
msgid "Restarting Mailman's master qrunner"
msgstr "Restarte Mailman's qrunner-Masterprozess"
-#: bin/mailmanctl:363
+#: bin/mailmanctl:362
msgid "Re-opening all log files"
msgstr "Wiedereröffnen aller Logdateien"
-#: bin/mailmanctl:399
+#: bin/mailmanctl:398
msgid "Starting Mailman's master qrunner."
msgstr "Starte Mailman's qrunner-Masterprozess."
@@ -10750,9 +10781,19 @@ msgstr "Das Passwort für die Liste darf nicht leer sein"
#: bin/newlist:225
msgid "Hit enter to notify %(listname)s owner..."
-msgstr "Enter drücken, um den Besitzer der Liste %(listname)s zu benachrichtigen..."
+msgstr ""
+"Enter drücken, um den Besitzer der Liste %(listname)s zu benachrichtigen..."
+
+#: bin/po2templ.py:23
+msgid ""
+"po2templ.py\n"
+"\n"
+"Extract templates from language po file.\n"
+"\n"
+"Usage: po2templ.py languages\n"
+msgstr ""
-#: bin/qrunner:20
+#: bin/qrunner:19
#, fuzzy
msgid ""
"Run one or more qrunners, once or repeatedly.\n"
@@ -10815,9 +10856,6 @@ msgid ""
"runner is required unless -l or -h is given, and it must be one of the "
"names\n"
"displayed by the -l switch.\n"
-"\n"
-"Note also that this script should be started up from mailmanctl as a normal\n"
-"operation. It is only useful for debugging if it is run separately.\n"
msgstr ""
"Startet einmalig oder wiederholt einen oder mehrere qrunner.\n"
"\n"
@@ -10874,16 +10912,16 @@ msgstr ""
"Die Angabe des runners ist erforderlich, ausser bei Angabe der Optionen\n"
"-l und -h. Runner muss einer der von der Option -l gelisteten Namen sein.\n"
-#: bin/qrunner:178
+#: bin/qrunner:176
msgid "%(name)s runs the %(runnername)s qrunner"
msgstr "%(name)s startet den %(runnername)s qrunner"
-#: bin/qrunner:179
+#: bin/qrunner:177
msgid "All runs all the above qrunners"
msgstr "All: startet alle o.a. qrunners"
# Mailman/Cgi/admindb.py:338 Mailman/ListAdmin.py:258
-#: bin/qrunner:215
+#: bin/qrunner:213
msgid "No runner name given."
msgstr "Kein runner-Name angegeben."
@@ -11138,7 +11176,7 @@ msgstr "Private Archive"
msgid "public archives"
msgstr "Öffentliche Archive"
-#: bin/show_qfiles:20
+#: bin/show_qfiles:3
msgid ""
"Show the contents of one or more Mailman queue files.\n"
"\n"
@@ -11350,6 +11388,19 @@ msgstr "Hinzugefügt: %(s)s"
msgid "Removed: %(s)s"
msgstr "Entfernt: %(s)s"
+#: bin/templ2pot.py:5
+msgid ""
+"templ2pot.py -- convert mailman template (en) to pot format.\n"
+"\n"
+"Usage: templ2pot.py inputfile ...\n"
+"\n"
+"Options:\n"
+"\n"
+" -h, --help\n"
+"\n"
+"Inputfiles are english templates. Outputs are written to stdout.\n"
+msgstr ""
+
#: bin/transcheck:18
msgid ""
"\n"
@@ -11570,7 +11621,8 @@ msgid ""
" %(o_pri_mbox_file)s\n"
" to\n"
" %(newname)s"
-msgstr "unbekannte Datei im Weg, verschiebe %(o_pri_mbox_file)s nach %(newname)s"
+msgstr ""
+"unbekannte Datei im Weg, verschiebe %(o_pri_mbox_file)s nach %(newname)s"
#: bin/update:302 bin/update:325
msgid ""
@@ -11591,7 +11643,8 @@ msgid ""
" %(o_pub_mbox_file)s\n"
" to\n"
" %(newname)s"
-msgstr " unbekannte Datei im Weg, verschiebe %(o_pub_mbox_file)s nach %(newname)s"
+msgstr ""
+" unbekannte Datei im Weg, verschiebe %(o_pub_mbox_file)s nach %(newname)s"
#: bin/update:350
msgid "- This list looks like it might have <= b4 list templates around"
@@ -13461,7 +13514,7 @@ msgstr ""
#: templates/en/listinfo.html:1
#, fuzzy
msgid ""
-"<!-- $Revision: 8005 $ -->\n"
+"<!-- $Revision: 8123 $ -->\n"
"<HTML>\n"
" <HEAD>\n"
" <TITLE><MM-List-Name> Info Page</TITLE>\n"
@@ -14223,7 +14276,7 @@ msgid ""
"</body>\n"
"</html>"
msgstr ""
-"<!-- $Revision: 8005 $ -->\n"
+"<!-- $Revision: 8123 $ -->\n"
"<html>\n"
"<head>\n"
" <link rel=\"SHORTCUT ICON\" href=\"<mm-favicon>\">\n"
@@ -14386,7 +14439,8 @@ msgstr ""
"<p>Bitte beachten Sie, daß einige der Optionen eine <em>Global ändern</em>\n"
"Checkbox besitzen. Deren ankreuzen bewirkt, daß die Änderungen für\n"
"jede von Ihnen abonnierte Mailingliste auf <mm-host> vorgenommen werden.\n"
-"Klicken Sie auf <em>Meine anderen Abonnements auflisten</em> um zu sehen, welche\n"
+"Klicken Sie auf <em>Meine anderen Abonnements auflisten</em> um zu sehen, "
+"welche\n"
"weiteren Abonnements Sie haben.\n"
"<p>\n"
"<TABLE BORDER=\"0\" CELLSPACING=\"3\" CELLPADDING=\"4\" WIDTH=\"100%\">\n"
@@ -14799,7 +14853,7 @@ msgstr ""
#: templates/en/roster.html:1
#, fuzzy
msgid ""
-"<!-- $Revision: 8005 $ -->\n"
+"<!-- $Revision: 8123 $ -->\n"
"<HTML>\n"
" <HEAD>\n"
" <TITLE><MM-List-Name> Subscribers</TITLE>\n"
@@ -14854,7 +14908,7 @@ msgid ""
"</BODY>\n"
"</HTML>"
msgstr ""
-"<!-- $Revision: 8005 $ -->\n"
+"<!-- $Revision: 8123 $ -->\n"
"<HTML>\n"
" <HEAD>\n"
" <TITLE><MM-List-Name> Abonnenten</TITLE>\n"
@@ -14938,7 +14992,7 @@ msgstr ""
#: templates/en/subscribe.html:1
#, fuzzy
msgid ""
-"<!-- $Revision: 8005 $ -->\n"
+"<!-- $Revision: 8123 $ -->\n"
"<html>\n"
"<head><title><MM-List-Name> Subscription results</title></head>\n"
"<body bgcolor=\"white\">\n"
@@ -14948,7 +15002,7 @@ msgid ""
"</body>\n"
"</html>"
msgstr ""
-"<!-- $Revision: 8005 $ -->\n"
+"<!-- $Revision: 8123 $ -->\n"
"<html>\n"
"<head><title><MM-List-Name> Resultate des Abonnierens</title></head>\n"
"<body bgcolor=\"white\">\n"
@@ -15256,3 +15310,9 @@ msgid ""
"\n"
msgstr "%(member)s hat die Liste %(listname)s erfolgreich abonniert."
+#~ msgid ""
+#~ "## \"%(listname)s\" mailing list configuration settings -*- python -*-\n"
+#~ "## captured on %(when)s\n"
+#~ msgstr ""
+#~ "## Konfiguration der Mailingliste \"%(listname)s\" -*- python -*-\n"
+#~ "## Aufgezeichnet um %(when)s\n"
diff --git a/messages/fr/LC_MESSAGES/mailman.po b/messages/fr/LC_MESSAGES/mailman.po
index 3ca5c0475..d6546b6dc 100644
--- a/messages/fr/LC_MESSAGES/mailman.po
+++ b/messages/fr/LC_MESSAGES/mailman.po
@@ -315,8 +315,10 @@ msgid ""
" name to visit the configuration pages for that list."
msgstr ""
"<p>Ci-dessous le catalogue des listes de diffusion\n"
-" %(mailmanlink)s dont l'existence est publique sur %(hostname)s. Cliquez sur\n"
-" le nom d'une liste pour accéder aux pages de configuration de cette liste."
+" %(mailmanlink)s dont l'existence est publique sur %(hostname)s. "
+"Cliquez sur\n"
+" le nom d'une liste pour accéder aux pages de configuration de "
+"cette liste."
#: Mailman/Cgi/admin.py:267
msgid "right "
@@ -753,7 +755,9 @@ msgstr ""
msgid ""
"<b>ack</b> -- Does the member get acknowledgements of their\n"
" posts?"
-msgstr "<b>acc</b> -- Les abonnés reçoivent-ils un accusé de réception de leurs envois ?"
+msgstr ""
+"<b>acc</b> -- Les abonnés reçoivent-ils un accusé de réception de leurs "
+"envois ?"
#: Mailman/Cgi/admin.py:1077
msgid ""
@@ -1033,7 +1037,8 @@ msgstr "Non abonné"
#: Mailman/Cgi/admin.py:1436
msgid "Ignoring changes to deleted member: %(user)s"
-msgstr "Les modifications apportées à l'abonné supprimé sont ignorées : %(user)s"
+msgstr ""
+"Les modifications apportées à l'abonné supprimé sont ignorées : %(user)s"
#: Mailman/Cgi/admin.py:1476
msgid "Successfully Removed:"
@@ -1395,8 +1400,8 @@ msgstr ""
" votre abonnement aura été confirmé. Vous pourrez le changer en vous\n"
" rendant sur votre page d'options personnelles.\n"
"\n"
-" <p>Ou cliquez sur <em>Annuler ma demande d'inscription</em> si vous "
-"ne voulez\n"
+" <p>Ou cliquez sur <em>Annuler ma demande d'inscription</em> si vous ne "
+"voulez\n"
" plus être abonné à cette liste."
#: Mailman/Cgi/confirm.py:261
@@ -1540,7 +1545,8 @@ msgstr ""
" votre mot de passe et d'autres informations et liens utiles.\n"
"\n"
" <p>Vous pouvez maintenant \n"
-" <a href=\"%(optionsurl)s\">vous rendre sur votre page d'identification\n"
+" <a href=\"%(optionsurl)s\">vous rendre sur votre page "
+"d'identification\n"
" d'abonné</a>."
#: Mailman/Cgi/confirm.py:412
@@ -1590,7 +1596,8 @@ msgid ""
" request."
msgstr ""
"Votre confirmation est nécessaire en vue de compléter votre requête de\n"
-" \trésiliation de votre abonnement à la liste <em>%(listname)s</em>. Vous\n"
+" \trésiliation de votre abonnement à la liste <em>%(listname)s</em>. "
+"Vous\n"
" êtes actuellement abonné avec comme\n"
"\n"
" <ul><li><b>Nom complet&nbsp;:</b> %(fullname)s\n"
@@ -1688,8 +1695,7 @@ msgstr ""
" et vous avez demandé un %(globallys)s de votre adresse courriel vers \n"
"\n"
" <ul><li><b>Nouvelle Adresse Courriel:</b> %(newaddr)s\n"
-"....</ul>"
-"\n"
+"....</ul>\n"
" Cliquez sur le bouton <em>Changer l'adresse</em> ci-dessous pour\n"
" achever le processus de confirmation.\n"
"\n"
@@ -1784,8 +1790,8 @@ msgstr ""
"\n"
" Cliquez sur le bouton <em>Annuler l'envoi</em> pour supprimer l'envoi.\n"
"\n"
-" <p>Ou cliquez sur le bouton <em>Laisser en attente"
-" d'approbation</em> pour attendre la\n"
+" <p>Ou cliquez sur le bouton <em>Laisser en attente d'approbation</em> "
+"pour attendre la\n"
" décision du modérateur quant à l'approbation ou le rejet du message."
#: Mailman/Cgi/confirm.py:716
@@ -1861,7 +1867,8 @@ msgid ""
msgstr ""
"Votre abonnement à la liste %(realname)s est actuellement désactivé\n"
" suite à des rebonds excessifs. Nous vous demandons confirmation pour\n"
-" le réactiver. Nous disposons des informations suivantes sur fichier&nbsp;:\n"
+" le réactiver. Nous disposons des informations suivantes sur "
+"fichier&nbsp;:\n"
"\n"
" <ul><li><b>Adresse d'abonné&nbsp;:</b> %(member)s\n"
" <li><b>Nom d'abonné&nbsp;:</b> %(username)s\n"
@@ -2227,7 +2234,8 @@ msgid ""
" <p>List administrators, you can visit "
msgstr ""
" Pour visiter la page d'information d'une liste privée,\n"
-" ouvrir une URL semblable à celle-ci, mais avec un '/' et le %(adj)s du nom\n"
+" ouvrir une URL semblable à celle-ci, mais avec un '/' et le %(adj)s "
+"du nom\n"
" de la liste ajoutée.\n"
" <p>Administrateurs de liste, vous pouvez visiter "
@@ -2472,7 +2480,8 @@ msgid ""
" other options have been set successfully."
msgstr ""
"L'administrateur de la liste a désactivé le mode de remise groupée\n"
-" pour cette liste, vos options de distribution ne seront donc pas modifiées.\n"
+" pour cette liste, vos options de distribution ne seront donc "
+"pas modifiées.\n"
" Cependant vos autres paramètres ont bien été modifiés."
#: Mailman/Cgi/options.py:669
@@ -3300,7 +3309,8 @@ msgstr ""
" abonnement, mais dit tout simplement à Mailman de ne pas vous\n"
" remettre les messages pour le moment. Ceci est\n"
" particulièrement utile si vous partez en vacance. N'oubliez\n"
-" pas de réactiver la distribution avec « set delivery on » quand vous\n"
+" pas de réactiver la distribution avec « set delivery on » quand "
+"vous\n"
" serez de retour !\n"
"\n"
" set myposts on\n"
@@ -3629,7 +3639,8 @@ msgstr ""
" who passe\n"
" Afficher la liste des abonnés. L'afficheur se limite à\n"
" renvoyer uniquement la liste des administrateurs et des\n"
-" modérateurs; vous devez fournir le mot de passe de l'administrateur ou\n"
+" modérateurs; vous devez fournir le mot de passe de l'administrateur "
+"ou\n"
" celui du modérateur pour récupérer la liste.\n"
#: Mailman/Commands/cmd_who.py:110
@@ -3918,7 +3929,8 @@ msgstr ""
"avec les substitutions clé/valeur suivantes :\n"
"<p><ul>\n"
"\t<li><b>listname</b> - <em>donne le nom de la liste</em>\n"
-"\t<li><b>listurl</b> - <em>donne l'URL de la page information de la liste</em>\n"
+"\t<li><b>listurl</b> - <em>donne l'URL de la page information de la liste</"
+"em>\n"
"\t<li><b>requestemail</b> - <em>donne l'adresse -request de la liste</em>\n"
"\t<li><b>owneremail</b> - <em>donne l'adresse -owner de la liste</em>\n"
"</ul>\n"
@@ -4609,8 +4621,8 @@ msgstr ""
"sections\n"
" du message, ce dernier est vide.\n"
"\n"
-" <p>Lorsque les messages sont supprimés, une entrée de journal est "
-"crée,\n"
+" <p>Lorsque les messages sont supprimés, une entrée de journal "
+"est crée,\n"
" contenant l'ID des messages supprimés. Lorsque les messages\n"
" sont rejetés ou remis au propriétaire de liste, une raison\n"
" du rejet est incluse dans le message de retour à l'expéditeur.\n"
@@ -4636,8 +4648,8 @@ msgstr "Caractéristiques de la distribution par lot des remises groupées."
#: Mailman/Gui/Digest.py:47
msgid "Can list members choose to receive list traffic bunched in digests?"
msgstr ""
-"Les abonnés aux listes peuvent-ils choisir de recevoir le trafic de la liste en "
-"mode groupé ?"
+"Les abonnés aux listes peuvent-ils choisir de recevoir le trafic de la liste "
+"en mode groupé ?"
#: Mailman/Gui/Digest.py:51
msgid "Digest"
@@ -4665,10 +4677,13 @@ msgstr "Lors de la réception groupée, quel est le format par défaut ?"
#: Mailman/Gui/Digest.py:59
msgid "How big in Kb should a digest be before it gets sent out?"
-msgstr "Quelle doit être la taille minimale en ko d'une remise groupée avant d'être envoyée ?"
+msgstr ""
+"Quelle doit être la taille minimale en ko d'une remise groupée avant d'être "
+"envoyée ?"
#: Mailman/Gui/Digest.py:63
-msgid "Should a digest be dispatched daily when the size threshold isn't reached?"
+msgid ""
+"Should a digest be dispatched daily when the size threshold isn't reached?"
msgstr ""
"Les messages groupés doivent être envoyés si la taille seuil n'est pas "
"atteinte ?"
@@ -4695,7 +4710,8 @@ msgstr "Texte attaché (comme message final) au bas de chaque remise groupée. "
#: Mailman/Gui/Digest.py:80
msgid "How often should a new digest volume be started?"
-msgstr "Avec quelle périodicité un nouveau volume groupé doit-il être commencé ?"
+msgstr ""
+"Avec quelle périodicité un nouveau volume groupé doit-il être commencé ?"
#: Mailman/Gui/Digest.py:81
msgid ""
@@ -4721,7 +4737,9 @@ msgstr ""
msgid ""
"Should Mailman send the next digest right now, if it is not\n"
" empty?"
-msgstr "Mailman devrait-il envoyer la prochaine remise groupée maintenant, si elle n'est pas vide ?"
+msgstr ""
+"Mailman devrait-il envoyer la prochaine remise groupée maintenant, si elle "
+"n'est pas vide ?"
#: Mailman/Gui/Digest.py:145
msgid ""
@@ -4813,7 +4831,8 @@ msgstr "Réglages de liste générique"
#: Mailman/Gui/General.py:64
msgid "The public name of this list (make case-changes only)."
-msgstr "Le nom publique de la liste (ne faites des modifications que sur la casse)."
+msgstr ""
+"Le nom publique de la liste (ne faites des modifications que sur la casse)."
#: Mailman/Gui/General.py:65
msgid ""
@@ -4830,12 +4849,12 @@ msgstr ""
"La casse du nom peut être changée pour le rendre plus présentable\n"
" ou en faire un acronyme classique. Toutefois, ce nom sera "
"publié comme étant\n"
-" l'adresse courriel (dans les requêtes de confirmation d'abonnement\n"
+" l'adresse courriel (dans les requêtes de confirmation "
+"d'abonnement\n"
" par exemple), aussi <em>ne doit-il\n"
" pas</em> être altéré (les adresses courriels sont insensible à "
"la casse,\n"
-" mais elles le sont à presque n'importe quoi "
-"d'autre :-)"
+" mais elles le sont à presque n'importe quoi d'autre :-)"
#: Mailman/Gui/General.py:74
msgid ""
@@ -4996,7 +5015,8 @@ msgid ""
" for more info."
msgstr ""
"Une description introductive - quelques paragraphes - à propos de la liste.\n"
-" Elle sera insérée, au format html, en tête de la page listinfo.\n"
+" Elle sera insérée, au format html, en tête de la page "
+"listinfo.\n"
" Les retours chariots marquent la fin des paragraphes - voir "
"détails\n"
" pour plus d'informations."
@@ -5175,8 +5195,8 @@ msgstr ""
"restreintes,\n"
"\t\ten parallèle avec une liste dédiée aux débats. Parmi ces listes on peut "
"citer les listes\n"
-"\t\t« patches » et « checkin », où les modifications des logiciels sont soumises "
-"par des systèmes de contrôleurs\n"
+"\t\t« patches » et « checkin », où les modifications des logiciels sont "
+"soumises par des systèmes de contrôleurs\n"
"\t\tde revisions alors que les discussions concernant ces mêmes "
"modifications se déroulent sur la liste\n"
"\t\tdes développeurs. Pour la gestion de ce type de listes, sélectionner "
@@ -5248,8 +5268,8 @@ msgstr ""
"restreintes,\n"
"\t\ten parallèle avec une liste dédiée aux débats. Parmi ces listes ont peut "
"citer les listes\n"
-"\t\t« patches » et « checkin », où les modifications des logiciels sont soumise "
-"par des systèmes de contrôleurs\n"
+"\t\t« patches » et « checkin », où les modifications des logiciels sont "
+"soumise par des systèmes de contrôleurs\n"
"\t\tde revisions alors que les discussions concernant ces mêmes "
"modifications se déroulent sur la liste\n"
"\t\tdes développeurs. Pour la gestion de ce type de listes, sélectionner "
@@ -5379,8 +5399,8 @@ msgid ""
msgstr ""
"Cette valeur, si elle existe, sera ajoutée au début du message de bienvenue "
"adressé\n"
-" au nouveaux abonnés. Le reste du-dit message décrit les adresses "
-"et URL\n"
+" au nouveaux abonnés. Le reste du-dit message décrit les "
+"adresses et URL\n"
" importantes relatives à la liste de diffusion, par suite,\n"
" vous n'aurez pas besoin d'ajouter de telles informations ici.\n"
" Ceci devrait contenir des informations relatives aux objectifs\n"
@@ -5424,7 +5444,8 @@ msgstr ""
#: Mailman/Gui/General.py:303
msgid "Send goodbye message to members when they are unsubscribed?"
-msgstr "Envoyer un message d'adieu lorsque les membres résilient leur abonnement ?"
+msgstr ""
+"Envoyer un message d'adieu lorsque les membres résilient leur abonnement ?"
#: Mailman/Gui/General.py:306
msgid ""
@@ -5982,7 +6003,8 @@ msgstr ""
" l'utilisateur avec la casse préservée.\n"
" <li><b>user_password</b> - Le mot de passe utilisateur\n"
" <li><b>user_name</b> - Le nom complet de l'utilisateur\n"
-" <li><b>user_optionsurl</b> - L'url de la page d'information de\n"
+" <li><b>user_optionsurl</b> - L'url de la page "
+"d'information de\n"
" l'utilisateur.\n"
" </ul>\n"
" "
@@ -6032,7 +6054,8 @@ msgstr "Texte ajouté au début de tout message à distribution immédiate. "
#: Mailman/Gui/NonDigest.py:133
msgid "Footer added to mail sent to regular list members"
-msgstr "Pied de page ajouté aux messages envoyés aux abonnés normaux de la liste"
+msgstr ""
+"Pied de page ajouté aux messages envoyés aux abonnés normaux de la liste"
#: Mailman/Gui/NonDigest.py:134
msgid ""
@@ -6388,13 +6411,14 @@ msgstr ""
" commencer la ligne avec le caractère ^ pour désigner une <a\n"
" href=\"http://www.python.org/doc/current/lib/module-re.html"
"\">expression\n"
-" rationnelle Python</a>. Si vous utilisez des barres obliques inversées, faites-le "
-"comme\n"
+" rationnelle Python</a>. Si vous utilisez des barres obliques "
+"inversées, faites-le comme\n"
" si vous utilisiez des chaînes Python brutes (i.e. en utilisant "
"une seule\n"
" barre oblique inversée).\n"
"\n"
-" <p>Notez que les correspondances autres que par expressions rationnelles sont toujours faites en premier."
+" <p>Notez que les correspondances autres que par expressions "
+"rationnelles sont toujours faites en premier."
#: Mailman/Gui/Privacy.py:195
msgid "Member filters"
@@ -6783,8 +6807,8 @@ msgid ""
msgstr ""
"Adresses acceptables lorsque l'option « require_explicit_destination » est "
"active\n"
-" Cette option prend une liste d'expressions rationnelles, une par "
-"ligne,\n"
+" Cette option prend une liste d'expressions rationnelles, une "
+"par ligne,\n"
" comparées avec chaque adresse destinataire dans le message. La "
"comparaison\n"
" est effectuée avec la fonction Python re.match(), impliquant un "
@@ -6793,12 +6817,12 @@ msgstr ""
"\n"
" <p>Pour une compatibilité ascendante avec Mailman 1.1, si "
"l'expression\n"
-" rationnelle ne contient pas de « @ », alors le motif est comparé à "
-"la partie\n"
+" rationnelle ne contient pas de « @ », alors le motif est "
+"comparé à la partie\n"
" locale de l'adresse du destinataire. Si cette comparaison "
"échoue ou si le\n"
-" motif contient un « @ », alors le motif est comparé à la totalité "
-"de\n"
+" motif contient un « @ », alors le motif est comparé à la "
+"totalité de\n"
" l'adresse du destinataire.\n"
"\n"
" <p>La comparaison avec la partie locale est obsolète ; pour les "
@@ -6829,7 +6853,8 @@ msgid ""
" "
msgstr ""
"Cette section vous permet de configurer les divers filtres anti-pourriel,\n"
-" ce qui permet de réduire les pourriels que vos abonnés reçoivent.\n"
+" ce qui permet de réduire les pourriels que vos abonnés "
+"reçoivent.\n"
" "
#: Mailman/Gui/Privacy.py:379
@@ -6864,9 +6889,11 @@ msgid ""
msgstr ""
"Chaque règle de filtrage d'en-tête est composée de deux parties, une liste "
"d'\n"
-" expressions rationnelles, une par ligne, et une action à mener.\n"
+" expressions rationnelles, une par ligne, et une action à "
+"mener.\n"
" Mailman compare les en-têtes de messages à chaque expression \n"
-" rationnelle de la règle et en cas de correspondance, le message \n"
+" rationnelle de la règle et en cas de correspondance, le "
+"message \n"
" est rejeté, mis en attente ou supprimé suivant l'action "
"spécifiée. \n"
" Utiliser <em>Différer</em> pour désactiver temporairement la "
@@ -7001,8 +7028,8 @@ msgstr ""
"Le filtre de thème place chaque courriel qui arrive dans une catégorie\n"
" sur la base <a\n"
" href=\"http://www.python.org/doc/current/lib/module-re.html\">\n"
-" des filtres d'expressions rationnelles</a> que vous avez définis "
-"ci-dessous.\n"
+" des filtres d'expressions rationnelles</a> que vous avez "
+"définis ci-dessous.\n"
" Si l'en-tête <code>Objet:</code> ou <code>Mots Clés:</code> "
"correspond\n"
" à un thème, alors le message est logiquement placé dans un\n"
@@ -7230,8 +7257,8 @@ msgstr ""
" mettre cette option à <em>Aucun</em>.\n"
"\n"
" <p>Si le newsgroup est modéré, vous pouvez faire en sorte que\n"
-" la liste de diffusion soit l'adresse de modération du newsgroup. "
-"En\n"
+" la liste de diffusion soit l'adresse de modération du "
+"newsgroup. En\n"
" sélectionnant <em>Modéré</em>, une étape supplémentaire\n"
" entre dans le processus d'approbation. Tous les messages "
"postés\n"
@@ -7380,7 +7407,8 @@ msgstr "; il a été désactivé pour des raisons inconnues"
#: Mailman/HTMLFormatter.py:149
msgid "Note: your list delivery is currently disabled%(reason)s."
-msgstr "Note: les remises sur votre liste sont actuellement désactivées %(reason)s."
+msgstr ""
+"Note: les remises sur votre liste sont actuellement désactivées %(reason)s."
#: Mailman/HTMLFormatter.py:152
msgid "Mail delivery"
@@ -7427,17 +7455,19 @@ msgstr ""
"Veuillez\n"
" re-vérifier que votre adresse d'abonnement est correcte et qu'il "
"n'y a\n"
-" aucun problème de distribution à cette adresse. Votre compte d'échecs de distribution sera\n"
-" re-initialisé si les problèmes sont réglés dans les meilleurs délais."
+" aucun problème de distribution à cette adresse. Votre compte "
+"d'échecs de distribution sera\n"
+" re-initialisé si les problèmes sont réglés dans les meilleurs "
+"délais."
#: Mailman/HTMLFormatter.py:179
msgid ""
"(Note - you are subscribing to a list of mailing lists, so the %(type)s "
"notice will be sent to the admin address for your membership, %(addr)s.)<p>"
msgstr ""
-"(Remarque - Vous avez souscrit un abonnement à la liste de diffusion, un avis de "
-"type %(type)s sera envoyé à l'adresse de l'administrateur pour validation, %"
-"(addr)s.)<p>"
+"(Remarque - Vous avez souscrit un abonnement à la liste de diffusion, un "
+"avis de type %(type)s sera envoyé à l'adresse de l'administrateur pour "
+"validation, %(addr)s.)<p>"
#: Mailman/HTMLFormatter.py:189
msgid ""
@@ -7517,8 +7547,8 @@ msgid ""
" this means that your confirmation request will be sent to the\n"
" `%(sfx)s' account for your address.)"
msgstr ""
-"<p>(Remarquez que ceci est une liste enveloppe (méta-liste), destinée à n'avoir "
-"comme\n"
+"<p>(Remarquez que ceci est une liste enveloppe (méta-liste), destinée à "
+"n'avoir comme\n"
" abonnés que des listes de diffusion. Entre autre conséquences, "
"votre\n"
" requête de confirmation sera envoyée au compte « %(sfx)s » pour "
@@ -7671,7 +7701,8 @@ msgstr "Envoi par un non-abonné sur une liste réservée aux abonnés"
#: Mailman/Handlers/Hold.py:61
msgid "Non-members are not allowed to post messages to this list."
-msgstr "Les non-abonnés ne sont pas autorisés à envoyer des messages sur cette liste."
+msgstr ""
+"Les non-abonnés ne sont pas autorisés à envoyer des messages sur cette liste."
#: Mailman/Handlers/Hold.py:64
msgid "Posting to a restricted list by sender requires approval"
@@ -8128,7 +8159,8 @@ msgstr "notification de résiliation de %(realname)s"
#: Mailman/MailList.py:1199
msgid "subscriptions to %(name)s require administrator approval"
-msgstr "L'abonnement à la liste %(name)s requiert une approbation de l'administrateur"
+msgstr ""
+"L'abonnement à la liste %(name)s requiert une approbation de l'administrateur"
#: Mailman/MailList.py:1462
msgid "Last autoresponse notification for today"
@@ -8190,7 +8222,8 @@ msgid ""
"To obtain instructions, send a message containing just the word \"help\".\n"
msgstr ""
"Aucune commande trouvée dans le message.\n"
-"Pour obtenir des instructions, envoyer un message contenant seulement le mot « help ».\n"
+"Pour obtenir des instructions, envoyer un message contenant seulement le mot "
+"« help ».\n"
#: Mailman/Queue/CommandRunner.py:168
msgid ""
@@ -8303,8 +8336,10 @@ msgid "Server Local Time"
msgstr "Serveur de temps local"
#: Mailman/i18n.py:139
-msgid "%(wday)s %(mon)s %(day)2i %(hh)02i:%(mm)02i:%(ss)02i %(tzname)s %(year)04i"
-msgstr "%(wday)s %(day)2i %(mon)s %(hh)02i:%(mm)02i:%(ss)02i %(tzname)s %(year)04i"
+msgid ""
+"%(wday)s %(mon)s %(day)2i %(hh)02i:%(mm)02i:%(ss)02i %(tzname)s %(year)04i"
+msgstr ""
+"%(wday)s %(day)2i %(mon)s %(hh)02i:%(mm)02i:%(ss)02i %(tzname)s %(year)04i"
#: bin/add_members:26
msgid ""
@@ -8832,19 +8867,23 @@ msgstr " vérification du gid et du mode pour %(path)s"
#: bin/check_perms:120
msgid "%(path)s bad group (has: %(groupname)s, expected %(MAILMAN_GROUP)s)"
-msgstr "Mauvais gid pour %(path)s (est : %(groupname)s, %(MAILMAN_GROUP)s attendu)"
+msgstr ""
+"Mauvais gid pour %(path)s (est : %(groupname)s, %(MAILMAN_GROUP)s attendu)"
#: bin/check_perms:143
msgid "directory permissions must be %(octperms)s: %(path)s"
-msgstr "Les permissions sur les répertoires doivent être de %(octperms)s : %(path)s"
+msgstr ""
+"Les permissions sur les répertoires doivent être de %(octperms)s : %(path)s"
#: bin/check_perms:152
msgid "source perms must be %(octperms)s: %(path)s"
-msgstr "Les permissions sur les sources doivent être de %(octperms)s : %(path)s"
+msgstr ""
+"Les permissions sur les sources doivent être de %(octperms)s : %(path)s"
#: bin/check_perms:163
msgid "article db files must be %(octperms)s: %(path)s"
-msgstr "Les permissions sur les fichiers db doivent être de %(octperms)s: %(path)s"
+msgstr ""
+"Les permissions sur les fichiers db doivent être de %(octperms)s: %(path)s"
#: bin/check_perms:175
msgid "checking mode for %(prefix)s"
@@ -8874,8 +8913,7 @@ msgid ""
" If you're on a shared multiuser system, you should consult the\n"
" installation manual on how to fix this."
msgstr ""
-"Avis : les droits sur le répertoire des archives privées est réglé "
-"sur o+x.\n"
+"Avis : les droits sur le répertoire des archives privées est réglé sur o+x.\n"
"\t Cela pourrait permettre à d'autres utilisateurs de votre système\n"
"\t de lire les archives privées.\n"
"\t Si vous partagez un système multi-utilisateurs, vous devriez consulter\n"
@@ -9124,7 +9162,8 @@ msgstr ""
" Afficher ce message et sortir.\n"
"\n"
"deancienneadr (« de ancienne adresse ») est l'ancienne adresse utilisateur.\n"
-"versnouvelleadr (« vers nouvelle adresse ») est la nouvelle adresse de l'utilisateur.\n"
+"versnouvelleadr (« vers nouvelle adresse ») est la nouvelle adresse de "
+"l'utilisateur.\n"
#: bin/clone_member:94
msgid "processing mailing list:"
@@ -9653,7 +9692,8 @@ msgstr ""
"Options : \n"
"\n"
" -q/--quiet\n"
-" Certaine sorties de MTA peuvent inclure une aide texte plus prolixe. Utiliser\n"
+" Certaine sorties de MTA peuvent inclure une aide texte plus prolixe. "
+"Utiliser\n"
" ceci pour la diminuer.\n"
"\n"
" -h/--help\n"
@@ -9890,8 +9930,8 @@ msgstr ""
" Afficher juste les abonnés normaux (hors remise groupée).\n"
"\n"
" --digest[=genre] / -d [genre]\n"
-" Afficher juste les abonnés en remise groupée. Un argument "
-"facultatif peut\n"
+" Afficher juste les abonnés en remise groupée. Un argument facultatif "
+"peut\n"
" être fourni et sera « mime » ou « plain » pour n'afficher que les\n"
" abonnés recevant ce genre de remise groupée.\n"
"\n"
@@ -10497,8 +10537,8 @@ msgid ""
msgstr ""
"Crée une nouvelle liste sans abonnés.\n"
"\n"
-"Utilisation : %(PROGRAM)s [options] [nom_liste [addr_listeadmin [mot_de_passe-"
-"admin]]]\n"
+"Utilisation : %(PROGRAM)s [options] [nom_liste [addr_listeadmin "
+"[mot_de_passe-admin]]]\n"
"\n"
"Options :\n"
"\n"
@@ -10881,12 +10921,14 @@ msgstr ""
"\n"
" --nouserack\n"
" -n\n"
-" Ne pas envoyer l'accusé de réception utilisateur. Si non spécifié, \n"
+" Ne pas envoyer l'accusé de réception utilisateur. Si non "
+"spécifié, \n"
" la valeur par défaut de la liste est utilisée.\n"
"\n"
" --noadminack\n"
" -N\n"
-" Ne pas envoyer l'accusé de réception administrateur. Si non spécifié, \n"
+" Ne pas envoyer l'accusé de réception administrateur. Si non "
+"spécifié, \n"
" la valeur par défaut de la liste est utilisée.\n"
"\n"
" --help\n"
@@ -11177,10 +11219,12 @@ msgstr ""
"\n"
" --notifyadmin[=<yes|no>]\n"
" -a[=<yes|no>]\n"
-" Détermine si l'administrateur doit être avisé suite à chaque abonnement\n"
+" Détermine si l'administrateur doit être avisé suite à chaque "
+"abonnement\n"
" et à chaque résiliation. Si vous ajouter un grand nombre, vous\n"
" devrez peut être désactiver cette option! Avec -a=yes ou\n"
-" -a, l'administrateur reçoit un avis. Avec l'option -a=no, l'administrateur n'est\n"
+" -a, l'administrateur reçoit un avis. Avec l'option -a=no, "
+"l'administrateur n'est\n"
" pas avisé. En l'absence de l'option -a, le comportement\n"
" par défaut de la liste est utilisé.\n"
"\n"
@@ -11240,7 +11284,8 @@ msgstr "Invalide : %(addr)30s"
#: bin/sync_members:215
msgid "You must fix the preceding invalid addresses first."
-msgstr "Vous devez d'abord régler le problème de l'adresse invalide précédente."
+msgstr ""
+"Vous devez d'abord régler le problème de l'adresse invalide précédente."
#: bin/sync_members:260
msgid "Added : %(s)s"
@@ -11270,7 +11315,8 @@ msgstr ""
"\n"
" -h, --help\n"
"\n"
-"fichiers sont les modèles anglais. Les résultats sont écrits sur la sortie standard stdout.\n"
+"fichiers sont les modèles anglais. Les résultats sont écrits sur la sortie "
+"standard stdout.\n"
#: bin/transcheck:18
msgid ""
@@ -11315,7 +11361,8 @@ msgstr "Analyse une chaîne traduite"
#: bin/transcheck:90
msgid "check for differences between checked in and checked out"
-msgstr "compare les différences entre les versions « checked in » et « checked out »"
+msgstr ""
+"compare les différences entre les versions « checked in » et « checked out »"
#: bin/transcheck:123
msgid "parse a .po file extracting msgids and msgstrs"
@@ -11433,7 +11480,9 @@ msgstr "AVIS : impossible de disposer d'un verrou sur la liste %(listname)s"
#: bin/update:215
msgid "Resetting %(n)s BYBOUNCEs disabled addrs with no bounce info"
-msgstr "Réinitialiser les adresses désactivées %(n)s BYBOUNCEs sans information de rebonds"
+msgstr ""
+"Réinitialiser les adresses désactivées %(n)s BYBOUNCEs sans information de "
+"rebonds"
#: bin/update:221
msgid "Updating the held requests database."
@@ -11586,7 +11635,8 @@ msgstr "Avis ! Suppression du fichier .pck vide : %(pckfile)s"
#: bin/update:550
msgid "Updating Mailman 2.0 pending_subscriptions.db database"
-msgstr "Mise à jour de la base de données pending_subscriptions.db de Mailman 2.0"
+msgstr ""
+"Mise à jour de la base de données pending_subscriptions.db de Mailman 2.0"
#: bin/update:561
msgid "Updating Mailman 2.1.4 pending.pck database"
@@ -11688,7 +11738,8 @@ msgid ""
"This is probably not safe.\n"
"Exiting."
msgstr ""
-"Rétrogradation constatée, de la version %(hexlversion)s vers %(hextversion)s\n"
+"Rétrogradation constatée, de la version %(hexlversion)s vers %(hextversion)"
+"s\n"
"Ceci n'est certainement pas sûr.\n"
"Terminaison."
@@ -12037,7 +12088,8 @@ msgid ""
"given,\n"
"all lists are bumped.\n"
msgstr ""
-"Incrémente le numéro de volume de la remise groupée et réinitialise le numéro à un.\n"
+"Incrémente le numéro de volume de la remise groupée et réinitialise le "
+"numéro à un.\n"
"\n"
"Utilisation : %(PROGRAM)s [options] [nomliste ...]\n"
"\n"
@@ -12367,7 +12419,8 @@ msgstr ""
"\n"
" -l nom de liste\n"
" --listname=nomdeliste\n"
-"\t Envoie la remise groupée à la liste spécifiée, sinon les remises groupées pour toutes\n"
+"\t Envoie la remise groupée à la liste spécifiée, sinon les remises groupées "
+"pour toutes\n"
"\t les listes sont envoyées.\n"
#: templates/en/admindbdetails.html:1
@@ -12530,7 +12583,8 @@ msgstr ""
"administratives pour lesquelles vous avez pris une d&eacute;cision. </b></"
"p>\n"
" \n"
-"<p><a href=\"%(summaryurl)s\">Retourner &agrave; la page r&eacute;sum&eacute;e</a>."
+"<p><a href=\"%(summaryurl)s\">Retourner &agrave; la page r&eacute;sum&eacute;"
+"e</a>."
#: templates/en/admindbpreamble.html:1
msgid ""
@@ -12835,7 +12889,8 @@ msgstr ""
"\t\t%(author_ref)s \t\t\n"
"\t\t%(date_ref)s \t \n"
"\t </li>\n"
-" <li><b><a href=\"%(listinfo)s\">Plus d'informations sur cette liste... \n"
+" <li><b><a href=\"%(listinfo)s\">Plus d'informations sur cette "
+"liste... \n"
" </a></b></li>\n"
" </ul>\n"
" <p><b>D&eacute;but:</b> <i>%(firstdate)s</i><br>\n"
@@ -12924,10 +12979,14 @@ msgstr ""
"\t <tr>\n"
" <td>%(archivelabel)s:</td>\n"
" <td>\n"
-" <A href=\"%(archive)s/thread.html\">[ Enfilade ]</a> \n"
-" <A href=\"%(archive)s/subject.html\">[ Sujet ]</a> \n"
-" <A href=\"%(archive)s/author.html\">[ Auteur ]</a> \n"
-" <A href=\"%(archive)s/date.html\">[ Date ]</a> \n"
+" <A href=\"%(archive)s/thread.html\">[ Enfilade ]</"
+"a> \n"
+" <A href=\"%(archive)s/subject.html\">[ Sujet ]</"
+"a> \n"
+" <A href=\"%(archive)s/author.html\">[ Auteur ]</"
+"a> \n"
+" <A href=\"%(archive)s/date.html\">[ Date ]</"
+"a> \n"
" </td>\n"
" %(textlink)s \n"
" </tr>\n"
@@ -13142,7 +13201,8 @@ msgstr ""
"configuration, envoyez un courriel à l'adresse:\n"
"%(adminaddr)s. \n"
"\n"
-"Ce message a été engendré automatiquement par Mailman %(version)s. Pour plus\n"
+"Ce message a été engendré automatiquement par Mailman %(version)s. Pour "
+"plus\n"
"d'informations sur le logiciel Mailman, consultez le site Internet\n"
"de Mailman à l'adresse http://www.list.org/"
@@ -13192,7 +13252,8 @@ msgstr ""
"\n"
"Vous pouvez utiliser l'interface web pour changer votre statut ou vos\n"
"paramètres d'abonnement, vous pouvez aussi y résilier votre\n"
-"abonnement, choisir de recevoir les messages en mode de remise groupée ou encore\n"
+"abonnement, choisir de recevoir les messages en mode de remise groupée ou "
+"encore\n"
"désactiver toute réception de messages (par exemple si vous partez en\n"
"vacances), etc.\n"
"\n"
@@ -13485,8 +13546,9 @@ msgstr ""
"%(listowner)s."
#: templates/en/listinfo.html:1
+#, fuzzy
msgid ""
-"<!-- $Revision: 8098 $ -->\n"
+"<!-- $Revision: 8123 $ -->\n"
"<HTML>\n"
" <HEAD>\n"
" <TITLE><MM-List-Name> Info Page</TITLE>\n"
@@ -13633,7 +13695,7 @@ msgid ""
"</BODY>\n"
"</HTML>"
msgstr ""
-"<!-- $Revision: 8098 $ -->\n"
+"<!-- $Revision: 8123 $ -->\n"
"<HTML>\n"
" <HEAD>\n"
" <TITLE>Page d'informations de <MM-List-Name></TITLE>\n"
@@ -13656,7 +13718,8 @@ msgstr ""
"\t </tr>\n"
"\t <tr>\n"
"\t <TD COLSPAN=\"1\" WIDTH=\"100%\" BGCOLOR=\"#FFF0D0\">\n"
-"\t <B><FONT COLOR=\"#000000\">&Agrave; propos de <MM-List-Name></FONT></B>\n"
+"\t <B><FONT COLOR=\"#000000\">&Agrave; propos de <MM-List-Name></FONT></"
+"B>\n"
"\t </TD>\n"
" <TD COLSPAN=\"1\" WIDTH=\"100%\" BGCOLOR=\"#FFF0D0\">\n"
" <MM-lang-form-start><MM-displang-box> <MM-list-langs>\n"
@@ -13680,7 +13743,8 @@ msgstr ""
" </TR>\n"
" <tr>\n"
"\t<td colspan=\"2\">\n"
-"\t Pour envoyer un message &agrave; tous les abonn&eacute;s de la liste, envoyez un courriel &agrave; \n"
+"\t Pour envoyer un message &agrave; tous les abonn&eacute;s de la liste, "
+"envoyez un courriel &agrave; \n"
"\t <A HREF=\"mailto:<MM-Posting-Addr>\"><MM-Posting-Addr></A>.\n"
"\n"
"\t <p>Vous pouvez vous abonner &agrave; la liste ou modifier votre \n"
@@ -13689,7 +13753,8 @@ msgstr ""
" </tr>\n"
" <TR>\n"
"\t<TD COLSPAN=\"2\" WIDTH=\"100%\" BGCOLOR=\"#FFF0D0\">\n"
-"\t <B><FONT COLOR=\"#000000\">Abonnement &agrave; <MM-List-Name></FONT></B>\n"
+"\t <B><FONT COLOR=\"#000000\">Abonnement &agrave; <MM-List-Name></FONT></"
+"B>\n"
"\t</TD>\n"
" </TR>\n"
" <tr>\n"
@@ -13702,24 +13767,27 @@ msgstr ""
"\t <TABLE BORDER=\"0\" CELLSPACING=\"2\" CELLPADDING=\"2\"\n"
"\t\tWIDTH=\"70%\" HEIGHT= \"112\">\n"
"\t\t<TR>\n"
-"\t\t <TD BGCOLOR=\"#dddddd\" WIDTH=\"55%\">Votre adresse électronique&nbsp;:</TD>\n"
+"\t\t <TD BGCOLOR=\"#dddddd\" WIDTH=\"55%\">Votre adresse électronique&nbsp;:"
+"</TD>\n"
"\t\t <TD WIDTH=\"33%\"><MM-Subscribe-Box>\n"
"\t</TD>\n"
"\t<TD WIDTH=\"12%\">&nbsp;</TD></TR>\n"
" <tr>\n"
-" <td bgcolor=\"#dddddd\" width=\"55%\">Votre nom (facultatif)&nbsp;:</td>\n"
+" <td bgcolor=\"#dddddd\" width=\"55%\">Votre nom (facultatif)&nbsp;:</"
+"td>\n"
" <td width=\"33%\"><mm-fullname-box></td>\n"
"\t<TD WIDTH=\"12%\">&nbsp;</TD></TR>\n"
" <TR>\n"
"\t<TD COLSPAN=\"3\"><FONT SIZE=-1>Vous pouvez saisir un mot de passe\n"
-"\t confidentiel ci-dessous. Ceci ne procure qu'un faible niveau de s&eacute;curit&eacute;,\n"
+"\t confidentiel ci-dessous. Ceci ne procure qu'un faible niveau de "
+"s&eacute;curit&eacute;,\n"
"\t mais devrait n&eacute;anmoins &eacute;viter que des tiers modifient\n"
"\t votre abonnement. <b>N'utilisez pas un mot de passe important</b> car\n"
"\t il vous sera parfois renvoy&acute; en clair.\n"
"\n"
-" <p>Si vous choisissez de ne pas en fournir, "
-"un mot de\n"
-" passe sera engendr&acute; automatiquement pour vous et vous sera envoy&eacute; une fois que\n"
+" <p>Si vous choisissez de ne pas en fournir, un mot de\n"
+" passe sera engendr&acute; automatiquement pour vous et vous "
+"sera envoy&eacute; une fois que\n"
" vous aurez confirm&acute; votre abonnement. Vous pouvez\n"
" toujours demander un rappel de votre mot de passe\n"
" lorsque vous modifiez vos paramètres personnels.\n"
@@ -13735,7 +13803,8 @@ msgstr ""
"\t<TD><MM-Confirm-Password></TD>\n"
"\t<TD>&nbsp; </TD></TR>\n"
" <tr>\n"
-" <TD BGCOLOR=\"#dddddd\">Dans quelle langue pr&eacute;f&eacute;rez vous lire vos messages&nbsp;?</TD> \n"
+" <TD BGCOLOR=\"#dddddd\">Dans quelle langue pr&eacute;f&eacute;rez "
+"vous lire vos messages&nbsp;?</TD> \n"
" <TD> <MM-list-langs></TD>\n"
" <TD>&nbsp; </TD></TR>\n"
" <mm-digest-question-start>\n"
@@ -13759,8 +13828,8 @@ msgstr ""
" <TR>\n"
" <TD COLSPAN=\"2\" WIDTH=\"100%\" BGCOLOR=\"#FFF0D0\">\n"
"\t<a name=\"subscribers\">\n"
-" <B><FONT COLOR=\"#000000\">Abonn&eacute;s de <MM-List-Name></FONT></B></"
-"a>\n"
+" <B><FONT COLOR=\"#000000\">Abonn&eacute;s de <MM-List-Name></FONT></"
+"B></a>\n"
" </TD>\n"
" </TR>\n"
" <tr>\n"
@@ -14238,7 +14307,7 @@ msgid ""
"</body>\n"
"</html>"
msgstr ""
-"<!-- $Revision: 8098 $ --> \n"
+"<!-- $Revision: 8123 $ --> \n"
" <html>\n"
" <head>\n"
" <link rel=\"SHORTCUT ICON\" href=\"<mm-favicon>\">\n"
@@ -14457,9 +14526,11 @@ msgstr ""
"\n"
"<tr><td bgcolor=\"#cccccc\"> \n"
"<strong> Activer le mode de remise group&eacute;&nbsp;?</strong><p>\n"
-"Si vous activez le mode de remise group&eacute;, vous recevrez les messages \n"
+"Si vous activez le mode de remise group&eacute;, vous recevrez les "
+"messages \n"
"regroup&eacute;s en un seul (une fois par jour en g&eacute;n&eacute;ral,\n"
-"peut-&ecirc;tre plus sur une liste active) au lieu de les recevoir un par un.\n"
+"peut-&ecirc;tre plus sur une liste active) au lieu de les recevoir un par "
+"un.\n"
"Si vous d&eacute;sactivez le mode group&eacute;, vous recevrez tout de "
"m&ecirc;me\n"
"un dernier envoi group&eacute;.\n"
@@ -14469,10 +14540,12 @@ msgstr ""
"</td></tr>\n"
"\n"
"<tr><td bgcolor=\"#cccccc\"> \n"
-"<strong> Recevoir les remises group&eactue;es en MIME ou en Texte brut ?</strong><p> \n"
-"Votre client de messagerie pourrait ou non supporter les remises group&eacute;es\n"
-"MIME. Les remises group&eacute; MIME sont g&eacute;n&eacute;ralement pr&eacute;f&eacute;"
-"r&eacute;s,\n"
+"<strong> Recevoir les remises group&eactue;es en MIME ou en Texte brut ?</"
+"strong><p> \n"
+"Votre client de messagerie pourrait ou non supporter les remises "
+"group&eacute;es\n"
+"MIME. Les remises group&eacute; MIME sont g&eacute;n&eacute;ralement "
+"pr&eacute;f&eacute;r&eacute;s,\n"
"mais si vous avez des probl&egrave;mes pour les lire, choisissez du texte\n"
"brut.\n"
"</td><td bgcolor=\"#cccccc\"> \n"
@@ -14817,14 +14890,20 @@ msgid ""
msgstr ""
"Ceci est un message de test. Vous pouvez l'ignorer.\n"
"\n"
-"La liste de diffusion %(listname)s a reçu des messages de non distribution de vous,\n"
-"indiquant qu'il peut y avoir un problème pour distribuer des messages à %(address)s.\n"
-"Un exemple de ces messages est joint ci-dessous. Veuillez consulter ce message\n"
-"pour vous assurer qu'il n'y a pas de problème avec votre adresse électronique. Vous pouvez le vérifier avec la personne\n"
+"La liste de diffusion %(listname)s a reçu des messages de non distribution "
+"de vous,\n"
+"indiquant qu'il peut y avoir un problème pour distribuer des messages à %"
+"(address)s.\n"
+"Un exemple de ces messages est joint ci-dessous. Veuillez consulter ce "
+"message\n"
+"pour vous assurer qu'il n'y a pas de problème avec votre adresse "
+"électronique. Vous pouvez le vérifier avec la personne\n"
"qui gère votre courrier électronique pour plus d'aide.\n"
"\n"
-"Si vous lisez ceci, vous n'avez rien à faire pour rester abonné à la liste de diffusion.\n"
-"Si ce message n'avait pas été distribué, vous ne seriez pas en train de le lire\n"
+"Si vous lisez ceci, vous n'avez rien à faire pour rester abonné à la liste "
+"de diffusion.\n"
+"Si ce message n'avait pas été distribué, vous ne seriez pas en train de le "
+"lire\n"
"Et votre abonnement aurait été suspendu. Normalement lorsque vous\n"
"êtes suspendu, vous recevez de temps en temps des messages\n"
"vous demandant de réactiver votre abonnement.\n"
@@ -14834,9 +14913,11 @@ msgstr ""
" %(optionsurl)s\n"
"\n"
"Sur votre page d'abonnement, vous pouvez modifier plusieurs paramètres de\n"
-"distribution comme votre adresse électronique et si vous voulez des remises groupées ou non.\n"
+"distribution comme votre adresse électronique et si vous voulez des remises "
+"groupées ou non.\n"
"\n"
-"Pour toutes questions ou problèmes, vous pouvez contacter l'administrateur de la liste\n"
+"Pour toutes questions ou problèmes, vous pouvez contacter l'administrateur "
+"de la liste\n"
"à\n"
"\n"
" %(owneraddr)s"
@@ -14872,8 +14953,9 @@ msgstr ""
" %(adminaddr)s"
#: templates/en/roster.html:1
+#, fuzzy
msgid ""
-"<!-- $Revision: 8098 $ -->\n"
+"<!-- $Revision: 8123 $ -->\n"
"<HTML>\n"
" <HEAD>\n"
" <TITLE><MM-List-Name> Subscribers</TITLE>\n"
@@ -14928,7 +15010,7 @@ msgid ""
"</BODY>\n"
"</HTML>"
msgstr ""
-"<!-- $Revision: 8098 $ -->\n"
+"<!-- $Revision: 8123 $ -->\n"
"<HTML>\n"
" <HEAD>\n"
" <TITLE>Abonn&eacute;s &agrave; <MM-List-Name></TITLE>\n"
@@ -14941,7 +15023,8 @@ msgstr ""
"CELLPADDING=\"5\">\n"
" <TR>\n"
"\t<TD COLSPAN=\"2\" WIDTH=\"100%\" BGCOLOR=\"#99CCFF\" ALIGN=\"CENTER\">\n"
-"\t <B><FONT COLOR=\"#000000\" SIZE=\"+1\">\t\tAbonn&eacute;s &agrave; <MM-List-Name>\n"
+"\t <B><FONT COLOR=\"#000000\" SIZE=\"+1\">\t\tAbonn&eacute;s &agrave; <MM-"
+"List-Name>\n"
"</FONT></B>\n"
"\t</TD>\n"
" </TR>\n"
@@ -14952,7 +15035,8 @@ msgstr ""
" <MM-list-langs><MM-form-end></p> \n"
"\n"
"\t <P>Cliquez sur votre adresse acc&eacute;der &agrave;\n"
-"\t votre page de param&egrave;tres d'abonnement.<br><I>(Les champs entre parenth&egrave;s ont leur distribution \n"
+"\t votre page de param&egrave;tres d'abonnement.<br><I>(Les champs "
+"entre parenth&egrave;s ont leur distribution \n"
"\t suspendue.)</I></P>\n"
"\t</TD>\n"
" </TR>\n"
@@ -14960,13 +15044,15 @@ msgstr ""
"\t<TD BGCOLOR=\"#FFF0D0\" WIDTH=\"50%\">\n"
"\t <center>\n"
"\t <B><FONT COLOR=\"#000000\"><MM-Num-Reg-Users>\n"
-"\t\t Abonn&eacute; de <MM-List-Name> hors remise group&eacute;e&nbsp;:</FONT></B>\n"
+"\t\t Abonn&eacute; de <MM-List-Name> hors remise group&eacute;e&nbsp;:</"
+"FONT></B>\n"
"\t </center>\n"
"\t</TD>\n"
"\t<TD BGCOLOR=\"#FFF0D0\" WIDTH=\"50%\">\n"
"\t <center>\n"
"\t <B><FONT COLOR=\"#000000\"><MM-Num-Digesters>\n"
-"\t\t Abonn&eacute; de <MM-List-Name> en remise group&eacute;e&nbsp;:</FONT></B> \n"
+"\t\t Abonn&eacute; de <MM-List-Name> en remise group&eacute;e&nbsp;:</"
+"FONT></B> \n"
"\t </center>\n"
"\t</TD>\n"
" </TR>\n"
@@ -15009,8 +15095,9 @@ msgstr ""
"pour traiter la requête."
#: templates/en/subscribe.html:1
+#, fuzzy
msgid ""
-"<!-- $Revision: 8098 $ -->\n"
+"<!-- $Revision: 8123 $ -->\n"
"<html>\n"
"<head><title><MM-List-Name> Subscription results</title></head>\n"
"<body bgcolor=\"white\">\n"
@@ -15020,9 +15107,10 @@ msgid ""
"</body>\n"
"</html>"
msgstr ""
-"<!-- $Revision: 8098 $ -->\n"
+"<!-- $Revision: 8123 $ -->\n"
"<html>\n"
-"<head><title>R&eacute;sultats de l'abonnement &agrave;<MM-List-Name></title></head>\n"
+"<head><title>R&eacute;sultats de l'abonnement &agrave;<MM-List-Name></"
+"title></head>\n"
"<body bgcolor=\"white\">\n"
"<h1>R&eacute;sultats de l'abonnement &agrave;<MM-List-Name></h1>\n"
"<MM-Results>\n"
@@ -15208,7 +15296,8 @@ msgstr ""
"Vous, ou quelqu'un qui se fait passer pour vous, a demandé un\n"
"rappel de votre mot de passe d'abonné à la liste %(fqdn_lname)s.\n"
"Vous aurez besoin de ce mot de passe pour modifier vos options\n"
-"d'abonnement (le choix entre les modes de remise groupée et normale par exemple),\n"
+"d'abonnement (le choix entre les modes de remise groupée et normale par "
+"exemple),\n"
"ce mot de passe vous facilite la tâche si vous voulez vous désabonner.\n"
"\n"
"Vous êtes abonné avec l'adresse : %(user)s\n"
@@ -15310,8 +15399,8 @@ msgstr ""
" <BODY BGCOLOR=\"#ffffff\">\n"
" <h1>Archives de %(listname)s</h1>\n"
" <p>\n"
-" Vous pouvez obtenir <a href=\"%(listinfo)s\">plus d'informations sur cette liste</"
-"a>.\n"
+" Vous pouvez obtenir <a href=\"%(listinfo)s\">plus d'informations sur "
+"cette liste</a>.\n"
" </p>\n"
" %(noarchive_msg)s\n"
" %(archive_listing_start)s\n"
@@ -15329,4 +15418,3 @@ msgstr ""
"L'adresse de l'abonné %(name)s a été changée avec succès\n"
"de %(oldaddr)s en %(newaddr)s pour la liste %(listname)s.\n"
"\n"
-
diff --git a/messages/nl/LC_MESSAGES/mailman.po b/messages/nl/LC_MESSAGES/mailman.po
index 169d15d01..290808f90 100644
--- a/messages/nl/LC_MESSAGES/mailman.po
+++ b/messages/nl/LC_MESSAGES/mailman.po
@@ -11856,7 +11856,7 @@ msgstr ""
#: templates/en/listinfo.html:1
#, fuzzy
msgid ""
-"<!-- $Revision: 7946 $ -->\n"
+"<!-- $Revision: 8123 $ -->\n"
"<HTML>\n"
" <HEAD>\n"
" <TITLE><MM-List-Name> Info Page</TITLE>\n"
@@ -12003,7 +12003,7 @@ msgid ""
"</BODY>\n"
"</HTML>"
msgstr ""
-"<!-- $Revision: 7946 $ -->\n"
+"<!-- $Revision: 8123 $ -->\n"
"<HTML>\n"
" <HEAD>\n"
" <TITLE><MM-List-Name> Informatie Pagina</TITLE> \n"
@@ -12586,7 +12586,7 @@ msgid ""
"</body>\n"
"</html>"
msgstr ""
-"<!-- $Revision: 7946 $ -->\n"
+"<!-- $Revision: 8123 $ -->\n"
"<html>\n"
"<head>\n"
" <link rel=\"SHORTCUT ICON\" href=\"<mm-favicon>\">\n"
@@ -13140,7 +13140,7 @@ msgstr ""
#: templates/en/roster.html:1
#, fuzzy
msgid ""
-"<!-- $Revision: 7946 $ -->\n"
+"<!-- $Revision: 8123 $ -->\n"
"<HTML>\n"
" <HEAD>\n"
" <TITLE><MM-List-Name> Subscribers</TITLE>\n"
@@ -13195,7 +13195,7 @@ msgid ""
"</BODY>\n"
"</HTML>"
msgstr ""
-"<!-- $Revision: 7946 $ -->\n"
+"<!-- $Revision: 8123 $ -->\n"
"<HTML>\n"
" <HEAD>\n"
" <TITLE><MM-List-Name> Leden</TITLE>\n"
@@ -13277,7 +13277,7 @@ msgstr ""
#: templates/en/subscribe.html:1
#, fuzzy
msgid ""
-"<!-- $Revision: 7946 $ -->\n"
+"<!-- $Revision: 8123 $ -->\n"
"<html>\n"
"<head><title><MM-List-Name> Subscription results</title></head>\n"
"<body bgcolor=\"white\">\n"
@@ -13287,7 +13287,7 @@ msgid ""
"</body>\n"
"</html>"
msgstr ""
-"<!-- $Revision: 7946 $ -->\n"
+"<!-- $Revision: 8123 $ -->\n"
"<html>\n"
"<head><title><MM-List-Name> Subscription results</title></head>\n"
"<body bgcolor=\"white\">\n"
@@ -13602,28 +13602,28 @@ msgstr ""
"%(member)s is succesvol ingeschreven op de %(listname)s mailinglijst.\n"
"\n"
-msgid ""
-"Approval notices are sent when mail triggers certain of the\n"
-" limits <em>except</em> routine list moderation and spam "
-"filters,\n"
-" for which notices are <em>not</em> sent. This option "
-"overrides\n"
-" ever sending the notice."
-msgstr ""
-"Goedkeuringsmeldingen worden verzonden wanneer berichten\n"
-" aan bepaalde voorwaarden voldoen waarbij het <em>niet</em>\n"
-" gaat om routine lijstmoderatie en spamfilteringen. Daar\n"
-" worden <em>geen</em> meldingen voor verzonden.\n"
-" Deze instelling op nee zetten zorgt ervoor dat er\n"
-" geen melding wordt gestuurd."
+#~ msgid ""
+#~ "Approval notices are sent when mail triggers certain of the\n"
+#~ " limits <em>except</em> routine list moderation and spam "
+#~ "filters,\n"
+#~ " for which notices are <em>not</em> sent. This option "
+#~ "overrides\n"
+#~ " ever sending the notice."
+#~ msgstr ""
+#~ "Goedkeuringsmeldingen worden verzonden wanneer berichten\n"
+#~ " aan bepaalde voorwaarden voldoen waarbij het <em>niet</em>\n"
+#~ " gaat om routine lijstmoderatie en spamfilteringen. Daar\n"
+#~ " worden <em>geen</em> meldingen voor verzonden.\n"
+#~ " Deze instelling op nee zetten zorgt ervoor dat er\n"
+#~ " geen melding wordt gestuurd."
#, fuzzy
-msgid "delivery option set"
-msgstr "optie 'herinneringen' geactiveerd"
+#~ msgid "delivery option set"
+#~ msgstr "optie 'herinneringen' geactiveerd"
#, fuzzy
-msgid "Traditional Chinese"
-msgstr "Aanvullende instellingen"
+#~ msgid "Traditional Chinese"
+#~ msgstr "Aanvullende instellingen"
-msgid "Simplified Chinese"
-msgstr "Vereenvoudigd Chinees"
+#~ msgid "Simplified Chinese"
+#~ msgstr "Vereenvoudigd Chinees"
diff --git a/misc/Makefile.in b/misc/Makefile.in
index 826c84c18..413251641 100644
--- a/misc/Makefile.in
+++ b/misc/Makefile.in
@@ -58,7 +58,7 @@ SETUPCMD= setup.py --quiet install $(SETUPINSTOPTS)
EMAIL= email-4.0.1
SETUPTOOLS= setuptools-0.6c3
PYSQLITE= pysqlite-2.3.2
-SQLALCHEMY= SQLAlchemy-0.3.0
+SQLALCHEMY= SQLAlchemy-0.3.1
SETUPPKGS= $(EMAIL) $(SETUPTOOLS) $(PYSQLITE) $(SQLALCHEMY)
EZINSTOPTS= --install-dir $(DESTDIR)$(PYTHONLIBDIR)
diff --git a/misc/SQLAlchemy-0.3.0.tar.gz b/misc/SQLAlchemy-0.3.0.tar.gz
deleted file mode 100644
index 46f327bf2..000000000
--- a/misc/SQLAlchemy-0.3.0.tar.gz
+++ /dev/null
Binary files differ
diff --git a/misc/SQLAlchemy-0.3.1.tar.gz b/misc/SQLAlchemy-0.3.1.tar.gz
new file mode 100644
index 000000000..a2afa5589
--- /dev/null
+++ b/misc/SQLAlchemy-0.3.1.tar.gz
Binary files differ
diff --git a/misc/paths.py.in b/misc/paths.py.in
index cc1e50fa4..83481cbbf 100644
--- a/misc/paths.py.in
+++ b/misc/paths.py.in
@@ -1,59 +1,84 @@
# -*- python -*-
-# Copyright (C) 1998-2005 by the Free Software Foundation, Inc.
+# Copyright (C) 1998-2006 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
+# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
# USA.
-# This file becomes paths.py which is installed in may directories. By
-# importing this module, sys.path gets `hacked' so that the $prefix/Mailman
-# directory is inserted at the start of that list. That directory really
-# contains the Mailman modules in package form. This file exports two
-# attributes that other modules may use to get the absolute path to the
-# installed Mailman distribution.
+# configure turns this file into paths.py which is installed in the bin
+# directory. By importing this module, sys.path gets `hacked' so that the
+# $prefix/Mailman and $prefix/pythonlib directories are inserted at the start
+# of that list. This file exports two attributes that other modules may use
+# to get the absolute path to the installed Mailman distribution.
+#
+# Note that we can't use site.addsitedir() because that ends up appending
+# directories to sys.path and we really need to add them to the front so that
+# they override anything in the system Python.
import os
import sys
-import site
-# some scripts expect this attribute to be in this module
+# Some scripts expect this attribute to be in this module
prefix = '@prefix@'
exec_prefix = '@exec_prefix@'
-# work around a bogus autoconf 2.12 bug
+# Work around a bogus autoconf 2.12 bug
if exec_prefix == '${prefix}':
exec_prefix = prefix
-# Hack the path to include the parent directory of the $prefix/Mailman package
-# directory.
+pythonlib = os.path.join(prefix, 'pythonlib')
+
+# Hack the path to include the parent directory of $prefix/Mailman
sys.path.insert(0, prefix)
-# Add Python's site-packages for system add-ons. Then add our pythonlib
-# directory on path to pick up any overrides of the standard modules and
-# packages. In both cases, process any .pth files found in either location.
-# Order here matters: our pythonlib directory goes at the front while the
-# system site-packages goes at the end.
-sitedir = os.path.join(sys.prefix, 'lib', 'python'+sys.version[:3],
- 'site-packages')
-pythonlib = os.path.join(prefix, 'pythonlib')
+# Much of this is ripped off from site.py
+paths = set()
+for dirname in sys.path:
+ try:
+ if os.path.isdir(dirname):
+ paths.add(os.path.normcase(os.path.abspath(dirname)))
+ except TypeError:
+ pass
-sys.path.insert(0, pythonlib)
-sys.path.append(sitedir)
+extra_paths = [pythonlib]
+for name in sorted(os.listdir(pythonlib)):
+ if os.path.splitext(name)[1] == '.pth':
+ filename = os.path.join(pythonlib, name)
+ try:
+ fp = open(filename, 'rU')
+ except IOError:
+ continue
+ try:
+ for line in fp:
+ if line.startswith('#'):
+ continue
+ if line.startswith('import'):
+ exec line
+ continue
+ line = line.rstrip()
+ path = os.path.abspath(os.path.join(pythonlib, line))
+ path = os.path.normcase(path)
+ if not path in paths and os.path.exists(path):
+ # Here's what's different than site.py!
+ extra_paths.append(path)
+ paths.add(path)
+ finally:
+ fp.close()
+# Add the new paths to the front of sys.path
+sys.path[0:0] = extra_paths
-site.addsitedir(pythonlib)
-site.addsitedir(sitedir)
# Arabic and Hebrew (RFC-1556) encoding aliases. (temporary solution)
import encodings.aliases
@@ -62,4 +87,4 @@ encodings.aliases.aliases.update({
'iso_8859_6_i': 'iso8859_6',
'iso_8859_8_e': 'iso8859_8',
'iso_8859_8_i': 'iso8859_8',
-})
+ })
diff --git a/scripts/driver b/scripts/driver
index 4cf182dd4..458e891a7 100644
--- a/scripts/driver
+++ b/scripts/driver
@@ -98,6 +98,8 @@ def run_main():
pkg = __import__('Mailman.Cgi', globals(), locals(), [scriptname])
module = getattr(pkg, scriptname)
main = getattr(module, 'main')
+ import signal
+ signal.signal(signal.SIGTERM, sigterm_handler)
try:
try:
sys.stdout = tempstdout
@@ -197,6 +199,7 @@ def print_environment(log=None):
print >> outfp, 'sys.exec_prefix =', sys.exec_prefix
print >> outfp, 'sys.path =', sys.exec_prefix
print >> outfp, 'sys.platform =', sys.platform
+ print >> outfp, 'args =', SPACE.join(sys.argv)
# Write the same information to the HTML sink.
if not STEALTH_MODE:
@@ -275,3 +278,37 @@ useful traceback for you. Please report this to the Mailman administrator at
this site.
"""
print >> sys.__stderr__, '[Mailman: low level unrecoverable exception]'
+
+
+
+# Signal handler to guarantee that, when running under traditional CGI, a
+# locked mailing list will be unlocked when a fatal signal is received.
+#
+# try/finally isn't enough because of this scenario: user hits a CGI page
+# which may take a long time to render; user gets bored and hits the browser's
+# STOP button; browser shuts down socket; server tries to write to broken
+# socket and gets a SIGPIPE. Under Apache 1.3/mod_cgi, Apache catches this
+# SIGPIPE (I presume it is buffering output from the cgi script), then turns
+# around and SIGTERMs the cgi process. Apache waits three seconds and then
+# SIGKILLs the cgi process. We /must/ catch the SIGTERM and do the most
+# reasonable thing we can in as short a time period as possible. If we get
+# the SIGKILL we're screwed because it's uncatchable and we'll have no
+# opportunity to clean up after ourselves.
+#
+# This signal handler catches the SIGTERM, unlocks the list, and then
+# exits the process. The effect of this is that the changes made to the
+# MailList object will be aborted, which seems like the only sensible
+# semantics.
+#
+# Note that we must not install this signal handler when running under the
+# HTTPRunner wsgi server because the sys.exit(0) will cause the supervisor
+# mailmanctl process to restart the HTTPRunner. When running in that
+# environment, just let the normal signal handling do the right thing.
+def sigterm_handler(signum, frame, mlist=mlist):
+ # Make sure the list gets unlocked...
+ if mlist.Locked():
+ mlist.Unlock()
+ # ...and ensure we exit, otherwise race conditions could cause us to
+ # enter MailList.Save() while we're in the unlocked state, and that
+ # could be bad!
+ sys.exit(0)