Revision: 8144
          http://svn.sourceforge.net/mailman/?rev=8144&view=rev
Author:   bwarsaw
Date:     2007-01-17 22:29:42 -0800 (Wed, 17 Jan 2007)

Log Message:
-----------
Rework MailList.available_languages so that we don't need to use a PickleType
column in the database for this list of strings.  We use SQLAlchemy's
many-to-many relationship, however because of this, you cannot simply append
new unicodes to .available_languages.  You need to wrap the language code in a
Language instance and append that instance to the list.

In order to handle this, I added a property MailList.language_codes which
returns a list of the code strings (not Language instances).  Also new are
MailList.set_languages() for setting (i.e. overriding) the set of available
languages for the list; and add_language() which takes a single language code,
wraps it, and appends it.  The code does not and should not use
.available_languages directory any more.

MailList.GetAvailableLanguages() is removed.  The 'available_languages' column
is removed from the Listdata table.

Add a getValue() to Mailman.Gui.Language in order to unwrap the language codes
stored in the database's association table.  Modify _setValue() to do the
wrapping.

In dbcontext.py, don't import * from the sqlalchemy package.  It contains a
'logging' name which is not the standard Python logging package.  I also added
essentially a bag of attributes class called Tables which will hold references
to all the SA tables that are created.  Update the make_table() API to take an
instance of Tables.

Added a close() method to DBContext.  This is needed for the updated unit test
suite.

Changed bin/import.py so that when available_languages is being set, it calls
MailList.set_languages() instead of trying to set that attribute directly.

Updated some language idioms while I was at it.

More eradication of mm_cfg in favor of the config object and the Defaults
module.

In testall.py, call initialize() instead of loginit.initialize().

Promote MAX_RESTARTS into a Defaults.py.in variable.  This is because the unit
tests will knock that value down to something not so annoying should one of
the qrunner-required tests traceback.

Several other important changes to the unit test suite (which now completely
succeeds again!):

- Set the uid and gid of the temporary mailman.cfg and tmp*.db files to the
  Mailman user and group as specified in the config object.

- Make sure that all of the tests point to a SQLite database file that was
  created with the tempfile module.  This way we don't pollute our main
  database with data that is getting created during the unit tests.

- In the TestBase.setUp() method, be sure to close the existing dbcontext,
  clear out the mappers, and then reconnect the dbcontext with the new
  SQLALCHEMY_ENGINE_URL pointing to the tempfile.  However, we don't need to
  reload the MailList instance any more.

- Make all tests work, except for the tests that require crypt.  That upgrade
  path will not be available in this version of Mailman.

Modified Paths:
--------------
    trunk/mailman/Mailman/Cgi/admin.py
    trunk/mailman/Mailman/Cgi/confirm.py
    trunk/mailman/Mailman/Cgi/listinfo.py
    trunk/mailman/Mailman/Cgi/options.py
    trunk/mailman/Mailman/Defaults.py.in
    trunk/mailman/Mailman/Gui/Language.py
    trunk/mailman/Mailman/HTMLFormatter.py
    trunk/mailman/Mailman/MailList.py
    trunk/mailman/Mailman/OldStyleMemberships.py
    trunk/mailman/Mailman/bin/import.py
    trunk/mailman/Mailman/bin/mailmanctl.py
    trunk/mailman/Mailman/bin/testall.py
    trunk/mailman/Mailman/database/address.py
    trunk/mailman/Mailman/database/dbcontext.py
    trunk/mailman/Mailman/database/listdata.py
    trunk/mailman/Mailman/database/version.py
    trunk/mailman/Mailman/testing/base.py
    trunk/mailman/Mailman/testing/emailbase.py
    trunk/mailman/Mailman/testing/test_handlers.py
    trunk/mailman/Mailman/testing/test_membership.py
    trunk/mailman/Mailman/testing/test_security_mgr.py

Added Paths:
-----------
    trunk/mailman/Mailman/database/languages.py

Modified: trunk/mailman/Mailman/Cgi/admin.py
===================================================================
--- trunk/mailman/Mailman/Cgi/admin.py  2007-01-14 04:34:29 UTC (rev 8143)
+++ trunk/mailman/Mailman/Cgi/admin.py  2007-01-18 06:29:42 UTC (rev 8144)
@@ -626,11 +626,11 @@
         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 @@
         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)

Modified: trunk/mailman/Mailman/Cgi/confirm.py
===================================================================
--- trunk/mailman/Mailman/Cgi/confirm.py        2007-01-14 04:34:29 UTC (rev 
8143)
+++ trunk/mailman/Mailman/Cgi/confirm.py        2007-01-18 06:29:42 UTC (rev 
8144)
@@ -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 @@
         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)

Modified: trunk/mailman/Mailman/Cgi/listinfo.py
===================================================================
--- trunk/mailman/Mailman/Cgi/listinfo.py       2007-01-14 04:34:29 UTC (rev 
8143)
+++ trunk/mailman/Mailman/Cgi/listinfo.py       2007-01-18 06:29:42 UTC (rev 
8144)
@@ -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 @@
                                                     _('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',

Modified: trunk/mailman/Mailman/Cgi/options.py
===================================================================
--- trunk/mailman/Mailman/Cgi/options.py        2007-01-14 04:34:29 UTC (rev 
8143)
+++ trunk/mailman/Mailman/Cgi/options.py        2007-01-18 06:29:42 UTC (rev 
8144)
@@ -540,7 +540,7 @@
             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 @@
     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')))

Modified: trunk/mailman/Mailman/Defaults.py.in
===================================================================
--- trunk/mailman/Mailman/Defaults.py.in        2007-01-14 04:34:29 UTC (rev 
8143)
+++ trunk/mailman/Mailman/Defaults.py.in        2007-01-18 06:29:42 UTC (rev 
8144)
@@ -756,7 +756,11 @@
 # 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
 
+
 
 #####
 # General defaults

Modified: trunk/mailman/Mailman/Gui/Language.py
===================================================================
--- trunk/mailman/Mailman/Gui/Language.py       2007-01-14 04:34:29 UTC (rev 
8143)
+++ trunk/mailman/Mailman/Gui/Language.py       2007-01-18 06:29:42 UTC (rev 
8144)
@@ -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 @@
     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 @@
             # 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 @@
 
             ]
 
-    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

Modified: trunk/mailman/Mailman/HTMLFormatter.py
===================================================================
--- trunk/mailman/Mailman/HTMLFormatter.py      2007-01-14 04:34:29 UTC (rev 
8143)
+++ trunk/mailman/Mailman/HTMLFormatter.py      2007-01-18 06:29:42 UTC (rev 
8144)
@@ -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 @@
     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 @@
         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 @@
             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 @@
         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 @@
             '<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 @@
         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)

Modified: trunk/mailman/Mailman/MailList.py
===================================================================
--- trunk/mailman/Mailman/MailList.py   2007-01-14 04:34:29 UTC (rev 8143)
+++ trunk/mailman/Mailman/MailList.py   2007-01-18 06:29:42 UTC (rev 8144)
@@ -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 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 @@
             user = Utils.ObscureEmail(user)
         return '%s/%s' % (url, urllib.quote(user.lower()))
 
+
 
     #
     # Instance and subcomponent initialization
@@ -415,7 +417,6 @@
         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 @@
         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 @@
     #
     # 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

Modified: trunk/mailman/Mailman/OldStyleMemberships.py
===================================================================
--- trunk/mailman/Mailman/OldStyleMemberships.py        2007-01-14 04:34:29 UTC 
(rev 8143)
+++ trunk/mailman/Mailman/OldStyleMemberships.py        2007-01-18 06:29:42 UTC 
(rev 8144)
@@ -127,7 +127,7 @@
     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
 

Modified: trunk/mailman/Mailman/bin/import.py
===================================================================
--- trunk/mailman/Mailman/bin/import.py 2007-01-14 04:34:29 UTC (rev 8143)
+++ trunk/mailman/Mailman/bin/import.py 2007-01-18 06:29:42 UTC (rev 8144)
@@ -223,7 +223,10 @@
                               '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:

Modified: trunk/mailman/Mailman/bin/mailmanctl.py
===================================================================
--- trunk/mailman/Mailman/bin/mailmanctl.py     2007-01-14 04:34:29 UTC (rev 
8143)
+++ trunk/mailman/Mailman/bin/mailmanctl.py     2007-01-18 06:29:42 UTC (rev 
8144)
@@ -45,7 +45,6 @@
 # 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 @@
                 # 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.

Modified: trunk/mailman/Mailman/bin/testall.py
===================================================================
--- trunk/mailman/Mailman/bin/testall.py        2007-01-14 04:34:29 UTC (rev 
8143)
+++ trunk/mailman/Mailman/bin/testall.py        2007-01-18 06:29:42 UTC (rev 
8144)
@@ -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 unittest
 
 from Mailman import Version
-from Mailman import loginit
 from Mailman.i18n import _
 from Mailman.initialize import initialize
 
@@ -138,10 +137,9 @@
     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__)

Modified: trunk/mailman/Mailman/database/address.py
===================================================================
--- trunk/mailman/Mailman/database/address.py   2007-01-14 04:34:29 UTC (rev 
8143)
+++ trunk/mailman/Mailman/database/address.py   2007-01-18 06:29:42 UTC (rev 
8144)
@@ -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 @@
 
 
 
-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)

Modified: trunk/mailman/Mailman/database/dbcontext.py
===================================================================
--- trunk/mailman/Mailman/database/dbcontext.py 2007-01-14 04:34:29 UTC (rev 
8143)
+++ trunk/mailman/Mailman/database/dbcontext.py 2007-01-18 06:29:42 UTC (rev 
8144)
@@ -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 @@
         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 @@
         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 @@
             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 @@
 
     @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()]

Added: trunk/mailman/Mailman/database/languages.py
===================================================================
--- trunk/mailman/Mailman/database/languages.py                         (rev 0)
+++ trunk/mailman/Mailman/database/languages.py 2007-01-18 06:29:42 UTC (rev 
8144)
@@ -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')

Modified: trunk/mailman/Mailman/database/listdata.py
===================================================================
--- trunk/mailman/Mailman/database/listdata.py  2007-01-14 04:34:29 UTC (rev 
8143)
+++ trunk/mailman/Mailman/database/listdata.py  2007-01-18 06:29:42 UTC (rev 
8144)
@@ -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 @@
 
 
 
-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 @@
         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 @@
         )
     # 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)
 
 
 

Modified: trunk/mailman/Mailman/database/version.py
===================================================================
--- trunk/mailman/Mailman/database/version.py   2007-01-14 04:34:29 UTC (rev 
8143)
+++ trunk/mailman/Mailman/database/version.py   2007-01-18 06:29:42 UTC (rev 
8144)
@@ -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 @@
 
 
 
-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)

Modified: trunk/mailman/Mailman/testing/base.py
===================================================================
--- trunk/mailman/Mailman/testing/base.py       2007-01-14 04:34:29 UTC (rev 
8143)
+++ trunk/mailman/Mailman/testing/base.py       2007-01-18 06:29:42 UTC (rev 
8144)
@@ -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 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 @@
             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('[EMAIL PROTECTED]', '[EMAIL PROTECTED]', '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 @@
         rmlist.delete_list(self._mlist.fqdn_listname, self._mlist,
                            archives=True, quiet=True)
         os.unlink(self._config)
+        os.unlink(self._dbfile)

Modified: trunk/mailman/Mailman/testing/emailbase.py
===================================================================
--- trunk/mailman/Mailman/testing/emailbase.py  2007-01-14 04:34:29 UTC (rev 
8143)
+++ trunk/mailman/Mailman/testing/emailbase.py  2007-01-18 06:29:42 UTC (rev 
8144)
@@ -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 @@
         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 @@
                 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 @@
                 MSGTEXT = None
             except asyncore.ExitNow:
                 pass
+            asyncore.close_all()
             return MSGTEXT
         finally:
             self._mlist.Lock()

Modified: trunk/mailman/Mailman/testing/test_handlers.py
===================================================================
--- trunk/mailman/Mailman/testing/test_handlers.py      2007-01-14 04:34:29 UTC 
(rev 8143)
+++ trunk/mailman/Mailman/testing/test_handlers.py      2007-01-18 06:29:42 UTC 
(rev 8144)
@@ -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 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 @@
 
 
 
-def password(plaintext):
-    return sha.new(plaintext).hexdigest()
+def password(cleartext):
+    return passwords.make_secret(cleartext, 'ssha')
 
 
 

Modified: trunk/mailman/Mailman/testing/test_membership.py
===================================================================
--- trunk/mailman/Mailman/testing/test_membership.py    2007-01-14 04:34:29 UTC 
(rev 8143)
+++ trunk/mailman/Mailman/testing/test_membership.py    2007-01-18 06:29:42 UTC 
(rev 8144)
@@ -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 @@
 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 @@
 
 
 
+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 TestMembers(TestBase):
     def setUp(self):
         TestBase.setUp(self)
+        self._member_password = password('xxXXxx')
         self._mlist.addNewMember('[EMAIL PROTECTED]',
                                  digest=0,
-                                 password='xxXXxx',
+                                 password=self._member_password,
                                  language='xx',
                                  realname='A. Nice Person')
 
@@ -95,7 +102,7 @@
         eq(mlist.getMemberCPAddress('[EMAIL PROTECTED]'), '[EMAIL PROTECTED]')
         eq(mlist.getMemberCPAddresses(('[EMAIL PROTECTED]', '[EMAIL 
PROTECTED]')),
            ['[EMAIL PROTECTED]', None])
-        eq(mlist.getMemberPassword('[EMAIL PROTECTED]'), 'xxXXxx')
+        eq(mlist.getMemberPassword('[EMAIL PROTECTED]'), self._member_password)
         eq(mlist.getMemberLanguage('[EMAIL PROTECTED]'), 'en')
         eq(mlist.getMemberOption('[EMAIL PROTECTED]', config.Digests), 0)
         eq(mlist.getMemberOption('[EMAIL PROTECTED]', 
config.AcknowledgePosts), 0)
@@ -106,7 +113,7 @@
         mlist = self._mlist
         self.failIf(mlist.authenticateMember('[EMAIL PROTECTED]', 'xxx'))
         self.assertEqual(mlist.authenticateMember('[EMAIL PROTECTED]', 
'xxXXxx'),
-                         'xxXXxx')
+                         self._member_password)
 
     def test_remove_member(self):
         eq = self.assertEqual
@@ -161,7 +168,7 @@
         eq(mlist.getMemberCPAddress('[EMAIL PROTECTED]'), '[EMAIL PROTECTED]')
         eq(mlist.getMemberCPAddresses(('[EMAIL PROTECTED]', '[EMAIL 
PROTECTED]')),
            ['[EMAIL PROTECTED]', None])
-        eq(mlist.getMemberPassword('[EMAIL PROTECTED]'), 'xxXXxx')
+        eq(mlist.getMemberPassword('[EMAIL PROTECTED]'), self._member_password)
         eq(mlist.getMemberLanguage('[EMAIL PROTECTED]'), 'en')
         eq(mlist.getMemberOption('[EMAIL PROTECTED]', config.Digests), 0)
         eq(mlist.getMemberOption('[EMAIL PROTECTED]', 
config.AcknowledgePosts), 0)
@@ -188,9 +195,10 @@
     def test_set_password(self):
         eq = self.assertEqual
         mlist = self._mlist
-        mlist.setMemberPassword('[EMAIL PROTECTED]', 'yyYYyy')
-        eq(mlist.getMemberPassword('[EMAIL PROTECTED]'), 'yyYYyy')
-        eq(mlist.authenticateMember('[EMAIL PROTECTED]', 'yyYYyy'), 'yyYYyy')
+        new_password = password('yyYYyy')
+        mlist.setMemberPassword('[EMAIL PROTECTED]', new_password)
+        eq(mlist.getMemberPassword('[EMAIL PROTECTED]'), new_password)
+        eq(mlist.authenticateMember('[EMAIL PROTECTED]', 'yyYYyy'), 
new_password)
         self.failIf(mlist.authenticateMember('[EMAIL PROTECTED]', 'xxXXxx'))
 
     def test_set_language(self):
@@ -200,7 +208,7 @@
         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('[EMAIL PROTECTED]', 'xx')
             self.assertEqual(self._mlist.getMemberLanguage('[EMAIL 
PROTECTED]'),
                              'xx')

Modified: trunk/mailman/Mailman/testing/test_security_mgr.py
===================================================================
--- trunk/mailman/Mailman/testing/test_security_mgr.py  2007-01-14 04:34:29 UTC 
(rev 8143)
+++ trunk/mailman/Mailman/testing/test_security_mgr.py  2007-01-18 06:29:42 UTC 
(rev 8144)
@@ -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 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 @@
         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 @@
 
     def test_user(self):
         mlist = self._mlist
-        mlist.addNewMember('[EMAIL PROTECTED]', password='nosrepa')
+        mlist.addNewMember('[EMAIL PROTECTED]', password=password('nosrepa'))
         self.assertEqual(mlist.Authenticate(
             [config.AuthUser], 'nosrepa', '[EMAIL PROTECTED]'), 
config.AuthUser)
 


This was sent by the SourceForge.net collaborative development platform, the 
world's largest Open Source development site.
_______________________________________________
Mailman-checkins mailing list
[email protected]
Unsubscribe: 
http://mail.python.org/mailman/options/mailman-checkins/archive%40jab.org

Reply via email to