summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbwarsaw2007-01-18 06:29:42 +0000
committerbwarsaw2007-01-18 06:29:42 +0000
commit372d4c2fdf072f6bfedca5fc84a2d5bb427418e6 (patch)
tree594db647158d8156f51ea6d05aba093f29ae061e
parent1e63bc4a3b6d9197e66f57e11f4b6733a3b324dd (diff)
downloadmailman-372d4c2fdf072f6bfedca5fc84a2d5bb427418e6.tar.gz
mailman-372d4c2fdf072f6bfedca5fc84a2d5bb427418e6.tar.zst
mailman-372d4c2fdf072f6bfedca5fc84a2d5bb427418e6.zip
-rw-r--r--Mailman/Cgi/admin.py10
-rw-r--r--Mailman/Cgi/confirm.py4
-rw-r--r--Mailman/Cgi/listinfo.py4
-rw-r--r--Mailman/Cgi/options.py4
-rw-r--r--Mailman/Defaults.py.in4
-rw-r--r--Mailman/Gui/Language.py26
-rw-r--r--Mailman/HTMLFormatter.py42
-rw-r--r--Mailman/MailList.py37
-rw-r--r--Mailman/OldStyleMemberships.py2
-rw-r--r--Mailman/bin/import.py5
-rw-r--r--Mailman/bin/mailmanctl.py5
-rw-r--r--Mailman/bin/testall.py6
-rw-r--r--Mailman/database/address.py6
-rw-r--r--Mailman/database/dbcontext.py34
-rw-r--r--Mailman/database/languages.py53
-rw-r--r--Mailman/database/listdata.py16
-rw-r--r--Mailman/database/version.py6
-rw-r--r--Mailman/testing/base.py42
-rw-r--r--Mailman/testing/emailbase.py9
-rw-r--r--Mailman/testing/test_handlers.py7
-rw-r--r--Mailman/testing/test_membership.py26
-rw-r--r--Mailman/testing/test_security_mgr.py42
22 files changed, 245 insertions, 145 deletions
diff --git a/Mailman/Cgi/admin.py b/Mailman/Cgi/admin.py
index 6977b4592..5cbf1affc 100644
--- a/Mailman/Cgi/admin.py
+++ b/Mailman/Cgi/admin.py
@@ -626,11 +626,11 @@ def get_item_gui_value(mlist, category, kind, varname, params, extra):
return container
elif kind == mm_cfg.Select:
if params:
- values, legend, selected = params
+ values, legend, selected = params
else:
- values = mlist.GetAvailableLanguages()
- legend = map(_, map(Utils.GetLanguageDescr, values))
- selected = values.index(mlist.preferred_language)
+ codes = mlist.language_codes
+ legend = [Utils.GetLanguageDescr(code) for code in codes]
+ selected = codes.index(mlist.preferred_language)
return SelectOptions(varname, values, legend, selected)
elif kind == mm_cfg.Topics:
# A complex and specialized widget type that allows for setting of a
@@ -990,7 +990,7 @@ def membership_options(mlist, subcat, cgidata, doc, form):
cells.append(Center(CheckBox('%s_plain' % addr, value, checked)))
# User's preferred language
langpref = mlist.getMemberLanguage(addr)
- langs = mlist.GetAvailableLanguages()
+ langs = mlist.language_codes
langdescs = [_(Utils.GetLanguageDescr(lang)) for lang in langs]
try:
selected = langs.index(langpref)
diff --git a/Mailman/Cgi/confirm.py b/Mailman/Cgi/confirm.py
index 626a80c02..d2bc3b0aa 100644
--- a/Mailman/Cgi/confirm.py
+++ b/Mailman/Cgi/confirm.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2001-2006 by the Free Software Foundation, Inc.
+# Copyright (C) 2001-2007 by the Free Software Foundation, Inc.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -285,7 +285,7 @@ def subscription_prompt(mlist, doc, cookie, userdesc):
table.AddRow([Label(_('Receive digests?')),
RadioButtonArray('digests', (_('No'), _('Yes')),
checked=digest, values=(0, 1))])
- langs = mlist.GetAvailableLanguages()
+ langs = mlist.language_codes
values = [_(Utils.GetLanguageDescr(l)) for l in langs]
try:
selected = langs.index(lang)
diff --git a/Mailman/Cgi/listinfo.py b/Mailman/Cgi/listinfo.py
index 37c7f8ea9..b43d10f91 100644
--- a/Mailman/Cgi/listinfo.py
+++ b/Mailman/Cgi/listinfo.py
@@ -1,4 +1,4 @@
-# Copyright (C) 1998-2006 by the Free Software Foundation, Inc.
+# Copyright (C) 1998-2007 by the Free Software Foundation, Inc.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -191,7 +191,7 @@ def list_listinfo(mlist, lang):
_('Edit Options')).Format()
# If only one language is enabled for this mailing list, omit the choice
# buttons.
- if len(mlist.GetAvailableLanguages()) == 1:
+ if len(mlist.language_codes) == 1:
displang = ''
else:
displang = mlist.FormatButton('displang-button',
diff --git a/Mailman/Cgi/options.py b/Mailman/Cgi/options.py
index 1ce8ee234..f357bcf15 100644
--- a/Mailman/Cgi/options.py
+++ b/Mailman/Cgi/options.py
@@ -540,7 +540,7 @@ address. Upon confirmation, any other mailing list containing the address
newvals.append((flag, newval))
# The user language is handled a little differently
- if userlang not in mlist.GetAvailableLanguages():
+ if userlang not in mlist.language_codes:
newvals.append((SETLANGUAGE, mlist.preferred_language))
else:
newvals.append((SETLANGUAGE, userlang))
@@ -828,7 +828,7 @@ def loginpage(mlist, doc, user, lang):
table.AddRow([Center(Header(2, title))])
table.AddCellInfo(table.GetCurrentRowIndex(), 0,
bgcolor=mm_cfg.WEB_HEADER_COLOR)
- if len(mlist.GetAvailableLanguages()) > 1:
+ if len(mlist.language_codes) > 1:
langform = Form(actionurl)
langform.AddItem(SubmitButton('displang-button',
_('View this page in')))
diff --git a/Mailman/Defaults.py.in b/Mailman/Defaults.py.in
index 1b85bf861..03c13bd86 100644
--- a/Mailman/Defaults.py.in
+++ b/Mailman/Defaults.py.in
@@ -756,6 +756,10 @@ QRUNNER_SAVE_BAD_MESSAGES = Yes
# affects both message pickles and MailList config.pck files.
SYNC_AFTER_WRITE = No
+# The maximum number of times that the mailmanctl watcher will try to restart
+# a qrunner that exits uncleanly.
+MAX_RESTARTS = 10
+
#####
diff --git a/Mailman/Gui/Language.py b/Mailman/Gui/Language.py
index 65fe4c4a6..8f73a8b3f 100644
--- a/Mailman/Gui/Language.py
+++ b/Mailman/Gui/Language.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2001-2006 by the Free Software Foundation, Inc.
+# Copyright (C) 2001-2007 by the Free Software Foundation, Inc.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -19,10 +19,11 @@
import codecs
+from Mailman import Utils
from Mailman import i18n
from Mailman import mm_cfg
-from Mailman import Utils
from Mailman.Gui.GUIBase import GUIBase
+from Mailman.database.languages import Language
_ = i18n._
@@ -35,9 +36,8 @@ class Language(GUIBase):
def GetConfigInfo(self, mlist, category, subcat=None):
if category <> 'language':
return None
-
# Set things up for the language choices
- langs = mlist.GetAvailableLanguages()
+ langs = mlist.language_codes
langnames = [_(Utils.GetLanguageDescr(L)) for L in langs]
try:
langi = langs.index(mlist.preferred_language)
@@ -45,7 +45,6 @@ class Language(GUIBase):
# Someone must have deleted the list's preferred language. Could
# be other trouble lurking!
langi = 0
-
# Only allow the admin to choose a language if the system has a
# charset for it. I think this is the best way to test for that.
def checkcodec(charset):
@@ -112,10 +111,21 @@ class Language(GUIBase):
]
- def _setValue(self, mlist, property, val, doc):
+ def _setValue(self, mlist, prop, val, doc):
# If we're changing the list's preferred language, change the I18N
# context as well
- if property == 'preferred_language':
+ if prop == 'preferred_language':
i18n.set_language(val)
doc.set_language(val)
- GUIBase._setValue(self, mlist, property, val, doc)
+ # Language codes must be wrapped
+ if prop == 'available_languages':
+ mlist.set_languages(*val)
+ else:
+ GUIBase._setValue(self, mlist, prop, val, doc)
+
+ def getValue(self, mlist, kind, varname, params):
+ if varname == 'available_languages':
+ # Unwrap Language instances, to return just the code
+ return [language.code for language in mlist.available_languages]
+ # Returning None tells the infrastructure to use getattr
+ return None
diff --git a/Mailman/HTMLFormatter.py b/Mailman/HTMLFormatter.py
index f61ccbb82..2283f84ed 100644
--- a/Mailman/HTMLFormatter.py
+++ b/Mailman/HTMLFormatter.py
@@ -1,4 +1,4 @@
-# Copyright (C) 1998-2006 by the Free Software Foundation, Inc.
+# Copyright (C) 1998-2007 by the Free Software Foundation, Inc.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -20,9 +20,10 @@
import re
import time
+from Mailman import Defaults
from Mailman import MemberAdaptor
from Mailman import Utils
-from Mailman import mm_cfg
+from Mailman.configuration import config
from Mailman.htmlformat import *
from Mailman.i18n import _
@@ -62,7 +63,7 @@ class HTMLFormatter:
def FormatUsers(self, digest, lang=None):
if lang is None:
lang = self.preferred_language
- conceal_sub = mm_cfg.ConcealSubscription
+ conceal_sub = Defaults.ConcealSubscription
people = []
if digest:
digestmembers = self.getDigestMemberKeys()
@@ -102,7 +103,7 @@ class HTMLFormatter:
return concealed + UnorderedList(*tuple(items)).Format()
def FormatOptionButton(self, option, value, user):
- if option == mm_cfg.DisableDelivery:
+ if option == Defaults.DisableDelivery:
optval = self.getDeliveryStatus(user) <> MemberAdaptor.ENABLED
else:
optval = self.getMemberOption(user, option)
@@ -110,16 +111,17 @@ class HTMLFormatter:
checked = ' CHECKED'
else:
checked = ''
- name = {mm_cfg.DontReceiveOwnPosts : 'dontreceive',
- mm_cfg.DisableDelivery : 'disablemail',
- mm_cfg.DisableMime : 'mime',
- mm_cfg.AcknowledgePosts : 'ackposts',
- mm_cfg.Digests : 'digest',
- mm_cfg.ConcealSubscription : 'conceal',
- mm_cfg.SuppressPasswordReminder : 'remind',
- mm_cfg.ReceiveNonmatchingTopics : 'rcvtopic',
- mm_cfg.DontReceiveDuplicates : 'nodupes',
- }[option]
+ name = {
+ Defaults.DontReceiveOwnPosts : 'dontreceive',
+ Defaults.DisableDelivery : 'disablemail',
+ Defaults.DisableMime : 'mime',
+ Defaults.AcknowledgePosts : 'ackposts',
+ Defaults.Digests : 'digest',
+ Defaults.ConcealSubscription : 'conceal',
+ Defaults.SuppressPasswordReminder : 'remind',
+ Defaults.ReceiveNonmatchingTopics : 'rcvtopic',
+ Defaults.DontReceiveDuplicates : 'nodupes',
+ }[option]
return '<input type=radio name="%s" value="%d"%s>' % (
name, value, checked)
@@ -374,7 +376,7 @@ class HTMLFormatter:
member_len = len(self.getRegularMemberKeys())
# If only one language is enabled for this mailing list, omit the
# language choice buttons.
- if len(self.GetAvailableLanguages()) == 1:
+ if len(self.language_codes) == 1:
listlangs = _(Utils.GetLanguageDescr(self.preferred_language))
else:
listlangs = self.GetLangSelectBox(lang).Format()
@@ -401,8 +403,8 @@ class HTMLFormatter:
'<mm-host>' : self.host_name,
'<mm-list-langs>' : listlangs,
}
- if mm_cfg.IMAGE_LOGOS:
- d['<mm-favicon>'] = mm_cfg.IMAGE_LOGOS + mm_cfg.SHORTCUT_ICON
+ if config.IMAGE_LOGOS:
+ d['<mm-favicon>'] = config.IMAGE_LOGOS + config.SHORTCUT_ICON
return d
def GetAllReplacements(self, lang=None):
@@ -421,14 +423,14 @@ class HTMLFormatter:
if lang is None:
lang = self.preferred_language
# Figure out the available languages
- values = self.GetAvailableLanguages()
- legend = map(_, map(Utils.GetLanguageDescr, values))
+ values = self.language_codes
+ legend = [Utils.GetLanguageDescr(code) for code in values]
try:
selected = values.index(lang)
except ValueError:
try:
selected = values.index(self.preferred_language)
except ValueError:
- selected = mm_cfg.DEFAULT_SERVER_LANGUAGE
+ selected = config.DEFAULT_SERVER_LANGUAGE
# Return the widget
return SelectOptions(varname, values, legend, selected)
diff --git a/Mailman/MailList.py b/Mailman/MailList.py
index 9f790c242..640baa314 100644
--- a/Mailman/MailList.py
+++ b/Mailman/MailList.py
@@ -1,4 +1,4 @@
-# Copyright (C) 1998-2006 by the Free Software Foundation, Inc.
+# Copyright (C) 1998-2007 by the Free Software Foundation, Inc.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -49,6 +49,7 @@ from Mailman import Version
from Mailman import database
from Mailman.UserDesc import UserDesc
from Mailman.configuration import config
+from Mailman.database.languages import Language
# Base classes
from Mailman import Pending
@@ -295,6 +296,7 @@ class MailList(object, HTMLFormatter, Deliverer, ListAdmin,
user = Utils.ObscureEmail(user)
return '%s/%s' % (url, urllib.quote(user.lower()))
+
#
# Instance and subcomponent initialization
@@ -415,7 +417,6 @@ class MailList(object, HTMLFormatter, Deliverer, ListAdmin,
self.admin_member_chunksize = config.DEFAULT_ADMIN_MEMBER_CHUNKSIZE
self.administrivia = config.DEFAULT_ADMINISTRIVIA
self.preferred_language = config.DEFAULT_SERVER_LANGUAGE
- self.available_languages = []
self.include_rfc2369_headers = 1
self.include_list_post_header = 1
self.filter_mime_types = config.DEFAULT_FILTER_MIME_TYPES
@@ -559,9 +560,9 @@ class MailList(object, HTMLFormatter, Deliverer, ListAdmin,
self.InitVars(listname, admin_email, crypted_password)
self.CheckValues()
if langs is None:
- self.available_languages = [self.preferred_language]
- else:
- self.available_languages = langs
+ langs = [self.preferred_language]
+ for language_code in langs:
+ self.set_languages(*langs)
url_host = config.domains[email_host]
self.web_page_url = config.DEFAULT_URL_PATTERN % url_host
database.add_list(self)
@@ -1440,14 +1441,26 @@ bad regexp in bounce_matching_header line: %s
#
# Multilingual (i18n) support
#
- def GetAvailableLanguages(self):
- langs = self.available_languages
+ def set_languages(self, *language_codes):
+ # Don't use the language_codes property because that will add the
+ # default server language. The effect would be that the default
+ # server language would never get added to the list's list of
+ # languages.
+ self.available_languages = [Language(code) for code in language_codes
+ if code in config.LC_DESCRIPTIONS]
+
+ def add_language(self, language_code):
+ self.available_languages.append(Language(language_code))
+
+ @property
+ def language_codes(self):
+ # Callers of this method expect a list of language codes
+ codes = [lang.code for lang in self.available_languages
+ if lang.code in config.LC_DESCRIPTIONS]
# If we don't add this, and the site admin has never added any
# language support to the list, then the general admin page may have a
# blank field where the list owner is supposed to chose the list's
# preferred language.
- if config.DEFAULT_SERVER_LANGUAGE not in langs:
- langs.append(config.DEFAULT_SERVER_LANGUAGE)
- # When testing, it's possible we've disabled a language, so just
- # filter things out so we don't get tracebacks.
- return [lang for lang in langs if config.LC_DESCRIPTIONS.has_key(lang)]
+ if config.DEFAULT_SERVER_LANGUAGE not in codes:
+ codes.append(config.DEFAULT_SERVER_LANGUAGE)
+ return codes
diff --git a/Mailman/OldStyleMemberships.py b/Mailman/OldStyleMemberships.py
index 70f076481..3a69a2dfc 100644
--- a/Mailman/OldStyleMemberships.py
+++ b/Mailman/OldStyleMemberships.py
@@ -127,7 +127,7 @@ class OldStyleMemberships(MemberAdaptor.MemberAdaptor):
def getMemberLanguage(self, member):
lang = self.__mlist.language.get(
member.lower(), self.__mlist.preferred_language)
- if lang in self.__mlist.GetAvailableLanguages():
+ if lang in self.__mlist.language_codes:
return lang
return self.__mlist.preferred_language
diff --git a/Mailman/bin/import.py b/Mailman/bin/import.py
index e9171f73a..023ae1aba 100644
--- a/Mailman/bin/import.py
+++ b/Mailman/bin/import.py
@@ -223,7 +223,10 @@ def create(all_listdata):
'filter_filename_extensions',
'pass_filename_extensions'):
value = value.splitlines()
- setattr(mlist, option, value)
+ if option == 'available_languages':
+ mlist.set_languages(*value)
+ else:
+ setattr(mlist, option, value)
for member in list_roster:
mid = member['id']
if VERBOSE:
diff --git a/Mailman/bin/mailmanctl.py b/Mailman/bin/mailmanctl.py
index 2c79797d8..6d3636501 100644
--- a/Mailman/bin/mailmanctl.py
+++ b/Mailman/bin/mailmanctl.py
@@ -45,7 +45,6 @@ DOT = '.'
# needn't be (much) longer than SNOOZE. We pad it 6 hours just to be safe.
LOCK_LIFETIME = Defaults.days(1) + Defaults.hours(6)
SNOOZE = Defaults.days(1)
-MAX_RESTARTS = 10
@@ -465,10 +464,10 @@ Master qrunner detected subprocess exit
# See if we've reached the maximum number of allowable restarts
if exitstatus <> signal.SIGINT:
restarts += 1
- if restarts > MAX_RESTARTS:
+ if restarts > config.MAX_RESTARTS:
qlog.info("""\
Qrunner %s reached maximum restart limit of %d, not restarting.""",
- qrname, MAX_RESTARTS)
+ qrname, config.MAX_RESTARTS)
restarting = ''
# Now perhaps restart the process unless it exited with a
# SIGTERM or we aren't restarting.
diff --git a/Mailman/bin/testall.py b/Mailman/bin/testall.py
index 6539e6e55..61fa98dd8 100644
--- a/Mailman/bin/testall.py
+++ b/Mailman/bin/testall.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2001-2006 by the Free Software Foundation, Inc.
+# Copyright (C) 2001-2007 by the Free Software Foundation, Inc.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -24,7 +24,6 @@ import optparse
import unittest
from Mailman import Version
-from Mailman import loginit
from Mailman.i18n import _
from Mailman.initialize import initialize
@@ -138,10 +137,9 @@ def main():
global basedir
parser, opts, args = parseargs()
- initialize(opts.config)
+ initialize(opts.config, propagate_logs=opts.stderr)
if not args:
args = ['.']
- loginit.initialize(propagate=opts.stderr)
import Mailman
basedir = os.path.dirname(Mailman.__file__)
diff --git a/Mailman/database/address.py b/Mailman/database/address.py
index cea1ba072..672a366b1 100644
--- a/Mailman/database/address.py
+++ b/Mailman/database/address.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2006 by the Free Software Foundation, Inc.
+# Copyright (C) 2006-2007 by the Free Software Foundation, Inc.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -21,10 +21,10 @@ from sqlalchemy import *
-def make_table(metadata):
+def make_table(metadata, tables):
table = Table(
'Address', metadata,
Column('address_id', Integer, primary_key=True),
Column('address', Unicode(4096)),
)
- return table
+ tables.bind(table)
diff --git a/Mailman/database/dbcontext.py b/Mailman/database/dbcontext.py
index b2b1fb826..66c021447 100644
--- a/Mailman/database/dbcontext.py
+++ b/Mailman/database/dbcontext.py
@@ -16,15 +16,17 @@
# USA.
import os
+import logging
import weakref
-from sqlalchemy import *
+from sqlalchemy import BoundMetaData, create_session
from string import Template
from urlparse import urlparse
from Mailman import Version
from Mailman.configuration import config
from Mailman.database import address
+from Mailman.database import languages
from Mailman.database import listdata
from Mailman.database import version
from Mailman.database.txnsupport import txn
@@ -37,10 +39,17 @@ class MlistRef(weakref.ref):
self.fqdn_listname = mlist.fqdn_listname
+class Tables(object):
+ def bind(self, table, attrname=None):
+ if attrname is None:
+ attrname = table.name.lower()
+ setattr(self, attrname, table)
+
+
class DBContext(object):
def __init__(self):
- self.tables = {}
+ self.tables = Tables()
self.metadata = None
self.session = None
# Special transaction used only for MailList.Lock() .Save() and
@@ -69,21 +78,17 @@ class DBContext(object):
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
+ # them if they don't yet exist. NOTE: this order matters!
+ for module in (languages, address, listdata, version):
+ module.make_table(self.metadata, self.tables)
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()
+ r = self.tables.version.select(
+ self.tables.version.c.component=='schema').execute()
row = r.fetchone()
if row is None:
# Database has not yet been initialized
- version_table.insert().execute(
+ self.tables.version.insert().execute(
component='schema',
version=Version.DATABASE_SCHEMA_VERSION)
elif row.version <> Version.DATABASE_SCHEMA_VERSION:
@@ -91,6 +96,9 @@ class DBContext(object):
raise SchemaVersionMismatchError(row.version)
self.session = create_session()
+ def close(self):
+ self.session.close()
+
def _touch(self, url):
parts = urlparse(url)
# XXX Python 2.5; use parts.scheme and parts.path
@@ -176,7 +184,7 @@ class DBContext(object):
@txn
def api_get_list_names(self):
- table = self.tables['Listdata']
+ table = self.tables.listdata
results = table.select().execute()
return [(row[table.c.list_name], row[table.c.host_name])
for row in results.fetchall()]
diff --git a/Mailman/database/languages.py b/Mailman/database/languages.py
new file mode 100644
index 000000000..6032a67df
--- /dev/null
+++ b/Mailman/database/languages.py
@@ -0,0 +1,53 @@
+# Copyright (C) 2007 by the Free Software Foundation, Inc.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+# USA.
+
+"""Available languages table."""
+
+from sqlalchemy import *
+
+
+
+class Language(object):
+ def __init__(self, code):
+ self.code = code
+
+ def __repr__(self):
+ return u'<Language "%s">' % self.code
+
+ def __unicode__(self):
+ return self.code
+
+ __str__ = __unicode__
+
+
+
+def make_table(metadata, tables):
+ language_table = Table(
+ 'Language', metadata,
+ # Two letter language code
+ Column('language_id', Integer, primary_key=True),
+ Column('code', Unicode),
+ )
+ # Associate List
+ available_languages_table = Table(
+ 'AvailableLanguage', metadata,
+ Column('list_id', Integer, ForeignKey('Listdata.list_id')),
+ Column('language_id', Integer, ForeignKey('Language.language_id')),
+ )
+ mapper(Language, language_table)
+ tables.bind(language_table)
+ tables.bind(available_languages_table, 'available_languages')
diff --git a/Mailman/database/listdata.py b/Mailman/database/listdata.py
index 3c51f694b..be361d586 100644
--- a/Mailman/database/listdata.py
+++ b/Mailman/database/listdata.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2006 by the Free Software Foundation, Inc.
+# Copyright (C) 2006-2007 by the Free Software Foundation, Inc.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -21,7 +21,7 @@ from sqlalchemy import *
-def make_table(metadata):
+def make_table(metadata, tables):
table = Table(
'Listdata', metadata,
# Attributes not directly modifiable via the web u/i
@@ -59,7 +59,6 @@ def make_table(metadata):
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),
@@ -148,10 +147,17 @@ def make_table(metadata):
)
# Avoid circular imports
from Mailman.MailList import MailList
+ from Mailman.database.languages import Language
# 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
+ props = dict(available_languages=
+ relation(Language,
+ secondary=tables.available_languages,
+ lazy=False))
+ mapper(MailList, table,
+ extension=MailListMapperExtension(),
+ properties=props)
+ tables.bind(table)
diff --git a/Mailman/database/version.py b/Mailman/database/version.py
index 93b97e470..57c50b0ef 100644
--- a/Mailman/database/version.py
+++ b/Mailman/database/version.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2006 by the Free Software Foundation, Inc.
+# Copyright (C) 2006-2007 by the Free Software Foundation, Inc.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -21,11 +21,11 @@ from sqlalchemy import *
-def make_table(metadata):
+def make_table(metadata, tables):
table = Table(
'Version', metadata,
Column('version_id', Integer, primary_key=True),
Column('component', String(20)),
Column('version', Integer),
)
- return table
+ tables.bind(table)
diff --git a/Mailman/testing/base.py b/Mailman/testing/base.py
index cb0a78819..a61740956 100644
--- a/Mailman/testing/base.py
+++ b/Mailman/testing/base.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2001-2006 by the Free Software Foundation, Inc.
+# Copyright (C) 2001-2007 by the Free Software Foundation, Inc.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -18,6 +18,8 @@
"""Test base class which handles creating and deleting a test list."""
import os
+import grp
+import pwd
import stat
import shutil
import difflib
@@ -25,19 +27,25 @@ import tempfile
import unittest
from cStringIO import StringIO
+from sqlalchemy.orm import clear_mappers
from Mailman import MailList
from Mailman import Utils
from Mailman.bin import rmlist
from Mailman.configuration import config
+from Mailman.database.dbcontext import dbcontext
NL = '\n'
-PERMISSIONS = stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH
class TestBase(unittest.TestCase):
def _configure(self, fp):
+ # Make sure that we don't pollute the real database with our test
+ # mailing list.
+ test_engine_url = 'sqlite:///' + self._dbfile
+ print >> fp, 'SQLALCHEMY_ENGINE_URL = "%s"' % test_engine_url
+ config.SQLALCHEMY_ENGINE_URL = test_engine_url
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:
@@ -54,24 +62,37 @@ class TestBase(unittest.TestCase):
raise self.failureException(fp.getvalue())
def setUp(self):
+ mailman_uid = pwd.getpwnam(config.MAILMAN_USER).pw_uid
+ mailman_gid = grp.getgrnam(config.MAILMAN_GROUP).gr_gid
# Write a temporary configuration file, but allow for subclasses to
- # add additional data.
- fd, self._config = tempfile.mkstemp(suffix='.cfg')
+ # add additional data. Make sure the config and db files, which
+ # mkstemp creates, has the proper ownership and permissions.
+ fd, self._config = tempfile.mkstemp(dir=config.DATA_DIR, suffix='.cfg')
os.close(fd)
+ os.chmod(self._config, 0440)
+ os.chown(self._config, mailman_uid, mailman_gid)
+ fd, self._dbfile = tempfile.mkstemp(dir=config.DATA_DIR, suffix='.db')
+ os.close(fd)
+ os.chmod(self._dbfile, 0660)
+ os.chown(self._dbfile, mailman_uid, mailman_gid)
fp = open(self._config, 'w')
try:
self._configure(fp)
finally:
fp.close()
- os.chmod(self._config, PERMISSIONS)
+ # Be sure to close the connection to the current database, and then
+ # reconnect to the new temporary SQLite database. Otherwise we end up
+ # with turds in the main database and our qrunner subprocesses won't
+ # find the mailing list. Because our global config object's
+ # SQLALCHEMY_ENGINE_URL variable has already been updated, the
+ # connect() call will open the database file the qrunners will be
+ # rendezvousing on.
+ dbcontext.close()
+ clear_mappers()
+ dbcontext.connect()
mlist = MailList.MailList()
mlist.Create('_xtest@example.com', 'owner@example.com', 'xxxxx')
mlist.Save()
- # We need to reload the mailing list to ensure that the member
- # adaptors are all sync'd up. This isn't strictly necessary with the
- # OldStyleMemberships adaptor, but it may be required for other
- # adaptors
- mlist.Load()
# This leaves the list in a locked state
self._mlist = mlist
@@ -80,3 +101,4 @@ class TestBase(unittest.TestCase):
rmlist.delete_list(self._mlist.fqdn_listname, self._mlist,
archives=True, quiet=True)
os.unlink(self._config)
+ os.unlink(self._dbfile)
diff --git a/Mailman/testing/emailbase.py b/Mailman/testing/emailbase.py
index 3421efb4b..e033195de 100644
--- a/Mailman/testing/emailbase.py
+++ b/Mailman/testing/emailbase.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2001-2006 by the Free Software Foundation, Inc.
+# Copyright (C) 2001-2007 by the Free Software Foundation, Inc.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -56,6 +56,10 @@ class EmailBase(TestBase):
TestBase._configure(self, fp)
print >> fp, 'SMTPPORT =', TESTPORT
config.SMTPPORT = TESTPORT
+ # Don't go nuts on mailmanctl restarts. If a qrunner fails once, it
+ # will keep failing.
+ print >> fp, 'MAX_RESTARTS = 1'
+ config.MAX_RESTARTS = 1
def setUp(self):
TestBase.setUp(self)
@@ -86,7 +90,7 @@ class EmailBase(TestBase):
time.sleep(3)
except socket.error, e:
# IWBNI e had an errno attribute
- if e[0] == errno.ECONNREFUSED:
+ if e[0] in (errno.ECONNREFUSED, errno.ETIMEDOUT):
break
else:
raise
@@ -105,6 +109,7 @@ class EmailBase(TestBase):
MSGTEXT = None
except asyncore.ExitNow:
pass
+ asyncore.close_all()
return MSGTEXT
finally:
self._mlist.Lock()
diff --git a/Mailman/testing/test_handlers.py b/Mailman/testing/test_handlers.py
index 09ca56c29..2e964389e 100644
--- a/Mailman/testing/test_handlers.py
+++ b/Mailman/testing/test_handlers.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2001-2006 by the Free Software Foundation, Inc.
+# Copyright (C) 2001-2007 by the Free Software Foundation, Inc.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -30,6 +30,7 @@ from email.Generator import Generator
from Mailman import Errors
from Mailman import Message
from Mailman import Version
+from Mailman import passwords
from Mailman.MailList import MailList
from Mailman.Queue.Switchboard import Switchboard
from Mailman.configuration import config
@@ -57,8 +58,8 @@ from Mailman.Handlers import ToUsenet
-def password(plaintext):
- return sha.new(plaintext).hexdigest()
+def password(cleartext):
+ return passwords.make_secret(cleartext, 'ssha')
diff --git a/Mailman/testing/test_membership.py b/Mailman/testing/test_membership.py
index b98e1b187..ed8ebb1f4 100644
--- a/Mailman/testing/test_membership.py
+++ b/Mailman/testing/test_membership.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2001-2006 by the Free Software Foundation, Inc.
+# Copyright (C) 2001-2007 by the Free Software Foundation, Inc.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -24,6 +24,7 @@ import unittest
from Mailman import MailList
from Mailman import MemberAdaptor
from Mailman import Utils
+from Mailman import passwords
from Mailman.Errors import NotAMemberError
from Mailman.UserDesc import UserDesc
from Mailman.configuration import config
@@ -31,6 +32,11 @@ from Mailman.testing.base import TestBase
+def password(cleartext):
+ return passwords.make_secret(cleartext, 'ssha')
+
+
+
class TestNoMembers(TestBase):
def test_no_member(self):
eq = self.assertEqual
@@ -78,9 +84,10 @@ class TestNoMembers(TestBase):
class TestMembers(TestBase):
def setUp(self):
TestBase.setUp(self)
+ self._member_password = password('xxXXxx')
self._mlist.addNewMember('person@dom.ain',
digest=0,
- password='xxXXxx',
+ password=self._member_password,
language='xx',
realname='A. Nice Person')
@@ -95,7 +102,7 @@ class TestMembers(TestBase):
eq(mlist.getMemberCPAddress('person@dom.ain'), 'person@dom.ain')
eq(mlist.getMemberCPAddresses(('person@dom.ain', 'noperson@dom.ain')),
['person@dom.ain', None])
- eq(mlist.getMemberPassword('person@dom.ain'), 'xxXXxx')
+ eq(mlist.getMemberPassword('person@dom.ain'), self._member_password)
eq(mlist.getMemberLanguage('person@dom.ain'), 'en')
eq(mlist.getMemberOption('person@dom.ain', config.Digests), 0)
eq(mlist.getMemberOption('person@dom.ain', config.AcknowledgePosts), 0)
@@ -106,7 +113,7 @@ class TestMembers(TestBase):
mlist = self._mlist
self.failIf(mlist.authenticateMember('person@dom.ain', 'xxx'))
self.assertEqual(mlist.authenticateMember('person@dom.ain', 'xxXXxx'),
- 'xxXXxx')
+ self._member_password)
def test_remove_member(self):
eq = self.assertEqual
@@ -161,7 +168,7 @@ class TestMembers(TestBase):
eq(mlist.getMemberCPAddress('nice@dom.ain'), 'nice@dom.ain')
eq(mlist.getMemberCPAddresses(('nice@dom.ain', 'nonice@dom.ain')),
['nice@dom.ain', None])
- eq(mlist.getMemberPassword('nice@dom.ain'), 'xxXXxx')
+ eq(mlist.getMemberPassword('nice@dom.ain'), self._member_password)
eq(mlist.getMemberLanguage('nice@dom.ain'), 'en')
eq(mlist.getMemberOption('nice@dom.ain', config.Digests), 0)
eq(mlist.getMemberOption('nice@dom.ain', config.AcknowledgePosts), 0)
@@ -188,9 +195,10 @@ class TestMembers(TestBase):
def test_set_password(self):
eq = self.assertEqual
mlist = self._mlist
- mlist.setMemberPassword('person@dom.ain', 'yyYYyy')
- eq(mlist.getMemberPassword('person@dom.ain'), 'yyYYyy')
- eq(mlist.authenticateMember('person@dom.ain', 'yyYYyy'), 'yyYYyy')
+ new_password = password('yyYYyy')
+ mlist.setMemberPassword('person@dom.ain', new_password)
+ eq(mlist.getMemberPassword('person@dom.ain'), new_password)
+ eq(mlist.authenticateMember('person@dom.ain', 'yyYYyy'), new_password)
self.failIf(mlist.authenticateMember('person@dom.ain', 'xxXXxx'))
def test_set_language(self):
@@ -200,7 +208,7 @@ class TestMembers(TestBase):
odesc = config.LC_DESCRIPTIONS.copy()
try:
config.add_language('xx', 'Xxian', 'utf-8')
- self._mlist.available_languages.append('xx')
+ self._mlist.add_language('xx')
self._mlist.setMemberLanguage('person@dom.ain', 'xx')
self.assertEqual(self._mlist.getMemberLanguage('person@dom.ain'),
'xx')
diff --git a/Mailman/testing/test_security_mgr.py b/Mailman/testing/test_security_mgr.py
index a8b056464..543330aed 100644
--- a/Mailman/testing/test_security_mgr.py
+++ b/Mailman/testing/test_security_mgr.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2001-2006 by the Free Software Foundation, Inc.
+# Copyright (C) 2001-2007 by the Free Software Foundation, Inc.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -24,23 +24,19 @@ import errno
import Cookie
import unittest
-try:
- import crypt
-except ImportError:
- crypt = None
-
# Don't use cStringIO because we're going to inherit
from StringIO import StringIO
from Mailman import Errors
from Mailman import Utils
+from Mailman import passwords
from Mailman.configuration import config
from Mailman.testing.base import TestBase
-def password(plaintext):
- return sha.new(plaintext).hexdigest()
+def password(cleartext):
+ return passwords.make_secret(cleartext, 'ssha')
@@ -130,34 +126,6 @@ class TestAuthenticate(TestBase):
self.assertEqual(self._mlist.Authenticate(
[config.AuthListAdmin], 'xxxxxx'), config.UnAuthorized)
- def test_list_admin_upgrade(self):
- eq = self.assertEqual
- mlist = self._mlist
- mlist.password = md5.new('ssSSss').digest()
- eq(mlist.Authenticate(
- [config.AuthListAdmin], 'ssSSss'), config.AuthListAdmin)
- eq(mlist.password, password('ssSSss'))
- # Test crypt upgrades if crypt is supported
- if crypt:
- mlist.password = crypt.crypt('rrRRrr', 'zc')
- eq(self._mlist.Authenticate(
- [config.AuthListAdmin], 'rrRRrr'), config.AuthListAdmin)
- eq(mlist.password, password('rrRRrr'))
-
- def test_list_admin_oldstyle_unauth(self):
- eq = self.assertEqual
- mlist = self._mlist
- mlist.password = md5.new('ssSSss').digest()
- eq(mlist.Authenticate(
- [config.AuthListAdmin], 'xxxxxx'), config.UnAuthorized)
- eq(mlist.password, md5.new('ssSSss').digest())
- # Test crypt upgrades if crypt is supported
- if crypt:
- mlist.password = crypted = crypt.crypt('rrRRrr', 'zc')
- eq(self._mlist.Authenticate(
- [config.AuthListAdmin], 'xxxxxx'), config.UnAuthorized)
- eq(mlist.password, crypted)
-
def test_list_moderator(self):
self._mlist.mod_password = password('mmMMmm')
self.assertEqual(self._mlist.Authenticate(
@@ -165,7 +133,7 @@ class TestAuthenticate(TestBase):
def test_user(self):
mlist = self._mlist
- mlist.addNewMember('aperson@dom.ain', password='nosrepa')
+ mlist.addNewMember('aperson@dom.ain', password=password('nosrepa'))
self.assertEqual(mlist.Authenticate(
[config.AuthUser], 'nosrepa', 'aperson@dom.ain'), config.AuthUser)