diff options
| author | bwarsaw | 2007-01-18 06:29:42 +0000 |
|---|---|---|
| committer | bwarsaw | 2007-01-18 06:29:42 +0000 |
| commit | 372d4c2fdf072f6bfedca5fc84a2d5bb427418e6 (patch) | |
| tree | 594db647158d8156f51ea6d05aba093f29ae061e | |
| parent | 1e63bc4a3b6d9197e66f57e11f4b6733a3b324dd (diff) | |
| download | mailman-372d4c2fdf072f6bfedca5fc84a2d5bb427418e6.tar.gz mailman-372d4c2fdf072f6bfedca5fc84a2d5bb427418e6.tar.zst mailman-372d4c2fdf072f6bfedca5fc84a2d5bb427418e6.zip | |
| -rw-r--r-- | Mailman/Cgi/admin.py | 10 | ||||
| -rw-r--r-- | Mailman/Cgi/confirm.py | 4 | ||||
| -rw-r--r-- | Mailman/Cgi/listinfo.py | 4 | ||||
| -rw-r--r-- | Mailman/Cgi/options.py | 4 | ||||
| -rw-r--r-- | Mailman/Defaults.py.in | 4 | ||||
| -rw-r--r-- | Mailman/Gui/Language.py | 26 | ||||
| -rw-r--r-- | Mailman/HTMLFormatter.py | 42 | ||||
| -rw-r--r-- | Mailman/MailList.py | 37 | ||||
| -rw-r--r-- | Mailman/OldStyleMemberships.py | 2 | ||||
| -rw-r--r-- | Mailman/bin/import.py | 5 | ||||
| -rw-r--r-- | Mailman/bin/mailmanctl.py | 5 | ||||
| -rw-r--r-- | Mailman/bin/testall.py | 6 | ||||
| -rw-r--r-- | Mailman/database/address.py | 6 | ||||
| -rw-r--r-- | Mailman/database/dbcontext.py | 34 | ||||
| -rw-r--r-- | Mailman/database/languages.py | 53 | ||||
| -rw-r--r-- | Mailman/database/listdata.py | 16 | ||||
| -rw-r--r-- | Mailman/database/version.py | 6 | ||||
| -rw-r--r-- | Mailman/testing/base.py | 42 | ||||
| -rw-r--r-- | Mailman/testing/emailbase.py | 9 | ||||
| -rw-r--r-- | Mailman/testing/test_handlers.py | 7 | ||||
| -rw-r--r-- | Mailman/testing/test_membership.py | 26 | ||||
| -rw-r--r-- | Mailman/testing/test_security_mgr.py | 42 |
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) |
