Revision: 8221
          http://svn.sourceforge.net/mailman/?rev=8221&view=rev
Author:   bwarsaw
Date:     2007-05-19 16:27:17 -0700 (Sat, 19 May 2007)

Log Message:
-----------
Many updates to flesh out users, profiles, and addresses:

- Added Elixir-based implementations for IUser and IProfile.  Link IAddresses
  and IUsers.  IUsers can have many IAddresses, but IAddresses may only be
  linked to one IUser.

- Change the names of some of the methods in the IProfile interface.  Remove
  the posting_permission attribute, since posting permissions are not really
  preferences.

- Add to IUserManager the following methods: create_user(), delete_user(),
  get_user(), and the users attribute.  IUserManagers now manage both users
  and addresses (which I may want to change since that's a little icky).

- Delete from IUser the user_id attribute.  Change the default_profile
  attribute to 'profile'.  Remove the add_address() and remove_address()
  methods.  Add a link() and unlink() method.  Now IAddresses are created by
  an IRoster and linked or unlinked to IUsers.

- Added a generate IManager interface, though this is currently unused and may
  go away.

- Remove the IDeliveryMode interfaces.  Use the DeliveryMode enum instead.

- Remove the IMember interface.  This is essentially what IUser has become.

- Remove the IDeliveryStatus interface.  Use the DeliveryStatus enum instead.

- Added doctests for users, and the interaction between users, profiles, and
  addresses.

- Remove the calls to objectstore.flush() from model objects.  The database
  must now be flushed explicitly.  Added flush() calls to doctests as
  necessary.  I still need to work out the transaction model, and make it work
  with MailLists, but this can come later.

- Added several exceptions for the new interfaces.

- Added an EnumType converter for SQLAlchemy so that Enums can be properly
  stored and retrieved from the database.

- Updated the munepy package to version 1.1.  We need Enums to implement
  equality comparisons for use with SA's ORM.  Still, don't implement ordered
  comparisons (== uses 'is' and != uses 'not is').

- Rip some non-working crap out of the inmemory.py file.  This really needs to
  be rethought.

Modified Paths:
--------------
    branches/exp-elixir-branch/Mailman/Errors.py
    branches/exp-elixir-branch/Mailman/database/__init__.py
    branches/exp-elixir-branch/Mailman/database/model/__init__.py
    branches/exp-elixir-branch/Mailman/database/model/address.py
    branches/exp-elixir-branch/Mailman/database/usermanager.py
    branches/exp-elixir-branch/Mailman/docs/addresses.txt
    branches/exp-elixir-branch/Mailman/interfaces/profile.py
    branches/exp-elixir-branch/Mailman/interfaces/user.py
    branches/exp-elixir-branch/Mailman/interfaces/usermanager.py
    branches/exp-elixir-branch/Mailman/testing/inmemory.py
    branches/exp-elixir-branch/misc/Makefile.in

Added Paths:
-----------
    branches/exp-elixir-branch/Mailman/constants.py
    branches/exp-elixir-branch/Mailman/database/model/profile.py
    branches/exp-elixir-branch/Mailman/database/model/user.py
    branches/exp-elixir-branch/Mailman/database/types.py
    branches/exp-elixir-branch/Mailman/docs/users.txt
    branches/exp-elixir-branch/Mailman/interfaces/manager.py
    branches/exp-elixir-branch/Mailman/testing/test_user.py
    branches/exp-elixir-branch/misc/munepy-1.1-py2.5.egg

Removed Paths:
-------------
    branches/exp-elixir-branch/Mailman/interfaces/deliverymode.py
    branches/exp-elixir-branch/Mailman/interfaces/deliverystatus.py
    branches/exp-elixir-branch/Mailman/interfaces/member.py
    branches/exp-elixir-branch/misc/munepy-1.0-py2.5.egg

Modified: branches/exp-elixir-branch/Mailman/Errors.py
===================================================================
--- branches/exp-elixir-branch/Mailman/Errors.py        2007-05-18 00:14:58 UTC 
(rev 8220)
+++ branches/exp-elixir-branch/Mailman/Errors.py        2007-05-19 23:27:17 UTC 
(rev 8221)
@@ -231,6 +231,18 @@
     """The named roster does not exist."""
 
 
-class ExistingAddressError(RosterError):
+
+class AddressError(MailmanError):
+    """A general address-related error occurred."""
+
+
+class ExistingAddressError(AddressError):
     """The given email address already exists."""
 
+
+class AddressAlreadyLinkedError(AddressError):
+    """The address is already linked to a user."""
+
+
+class AddressNotLinkedError(AddressError):
+    """The address is not linked to the user."""

Added: branches/exp-elixir-branch/Mailman/constants.py
===================================================================
--- branches/exp-elixir-branch/Mailman/constants.py                             
(rev 0)
+++ branches/exp-elixir-branch/Mailman/constants.py     2007-05-19 23:27:17 UTC 
(rev 8221)
@@ -0,0 +1,44 @@
+# 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
+# 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.
+
+"""Various constants and enumerations."""
+
+from munepy import Enum
+
+
+
+class DeliveryMode(Enum):
+    # Regular (i.e. non-digest) delivery
+    regular = 1
+    # Plain text digest delivery
+    plaintext_digests = 2
+    # MIME digest delivery
+    mime_digests = 3
+    # Summary digests
+    summary_digests = 4
+
+
+
+class DeliveryStatus(Enum):
+    # Delivery is enabled
+    enabled = 1
+    # Delivery was disabled by the user
+    by_user = 2
+    # Delivery was disabled due to bouncing addresses
+    by_bounces = 3
+    # Delivery was disabled by an administrator or moderator
+    by_moderator = 4

Modified: branches/exp-elixir-branch/Mailman/database/__init__.py
===================================================================
--- branches/exp-elixir-branch/Mailman/database/__init__.py     2007-05-18 
00:14:58 UTC (rev 8220)
+++ branches/exp-elixir-branch/Mailman/database/__init__.py     2007-05-19 
23:27:17 UTC (rev 8221)
@@ -19,10 +19,17 @@
 
 import os
 
+from elixir import objectstore
+
 from Mailman.database.listmanager import ListManager
 from Mailman.database.usermanager import UserManager
 
+__all__ = [
+    'initialize',
+    'flush',
+    ]
 
+
 
 def initialize():
     from Mailman.LockFile import LockFile
@@ -35,3 +42,8 @@
         model.initialize()
     config.list_manager = ListManager()
     config.user_manager = UserManager()
+    flush()
+
+
+def flush():
+    objectstore.flush()

Modified: branches/exp-elixir-branch/Mailman/database/model/__init__.py
===================================================================
--- branches/exp-elixir-branch/Mailman/database/model/__init__.py       
2007-05-18 00:14:58 UTC (rev 8220)
+++ branches/exp-elixir-branch/Mailman/database/model/__init__.py       
2007-05-19 23:27:17 UTC (rev 8221)
@@ -20,6 +20,7 @@
     'Language',
     'MailingList',
     'Roster',
+    'User',
     'Version',
     ]
 
@@ -35,12 +36,15 @@
 
 elixir.delay_setup = True
 
+from Mailman import constants
 from Mailman.Errors import SchemaVersionMismatchError
 from Mailman.configuration import config
 from Mailman.database.model.address import Address
 from Mailman.database.model.language import Language
 from Mailman.database.model.mailinglist import MailingList
+from Mailman.database.model.profile import Profile
 from Mailman.database.model.roster import Roster
+from Mailman.database.model.user import User
 from Mailman.database.model.version import Version
 
 

Modified: branches/exp-elixir-branch/Mailman/database/model/address.py
===================================================================
--- branches/exp-elixir-branch/Mailman/database/model/address.py        
2007-05-18 00:14:58 UTC (rev 8220)
+++ branches/exp-elixir-branch/Mailman/database/model/address.py        
2007-05-19 23:27:17 UTC (rev 8221)
@@ -33,6 +33,7 @@
     # Relationships
     has_and_belongs_to_many('rosters',
                             of_kind='Mailman.database.model.roster.Roster')
+    belongs_to('user', of_kind='Mailman.database.model.user.User')
 
     def __str__(self):
         return formataddr((self.real_name, self.address))

Copied: branches/exp-elixir-branch/Mailman/database/model/profile.py (from rev 
8220, branches/exp-elixir-branch/Mailman/database/model/address.py)
===================================================================
--- branches/exp-elixir-branch/Mailman/database/model/profile.py                
                (rev 0)
+++ branches/exp-elixir-branch/Mailman/database/model/profile.py        
2007-05-19 23:27:17 UTC (rev 8221)
@@ -0,0 +1,46 @@
+# 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
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+# USA.
+
+from elixir import *
+from email.utils import formataddr
+from zope.interface import implements
+
+from Mailman.constants import DeliveryMode
+from Mailman.database.types import EnumType
+from Mailman.interfaces import IProfile
+
+
+class Profile(Entity):
+    implements(IProfile)
+
+    has_field('acknowledge_posts',      Boolean)
+    has_field('hide_address',           Boolean)
+    has_field('preferred_language',     Unicode)
+    has_field('receive_list_copy',      Boolean)
+    has_field('receive_own_postings',   Boolean)
+    has_field('delivery_mode',          EnumType)
+    # Relationships
+    belongs_to('user', of_kind='Mailman.database.model.user.User')
+
+    def __init__(self):
+        super(Profile, self).__init__()
+        self.acknowledge_posts      = False
+        self.hide_address           = True
+        self.preferred_language     = 'en'
+        self.receive_list_copy      = True
+        self.receive_own_postings   = True
+        self.delivery_mode          = DeliveryMode.regular

Copied: branches/exp-elixir-branch/Mailman/database/model/user.py (from rev 
8220, branches/exp-elixir-branch/Mailman/database/model/address.py)
===================================================================
--- branches/exp-elixir-branch/Mailman/database/model/user.py                   
        (rev 0)
+++ branches/exp-elixir-branch/Mailman/database/model/user.py   2007-05-19 
23:27:17 UTC (rev 8221)
@@ -0,0 +1,50 @@
+# 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
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+# USA.
+
+from elixir import *
+from email.utils import formataddr
+from zope.interface import implements
+
+from Mailman import Errors
+from Mailman.database.model import Address
+from Mailman.interfaces import IUser
+
+
+class User(Entity):
+    implements(IUser)
+
+    has_field('real_name',  Unicode)
+    has_field('password',   Unicode)
+    # Relationships
+    has_one('profile', of_kind='Mailman.database.model.profile.Profile')
+    has_many('addresses', of_kind='Mailman.database.model.address.Address')
+
+    def link(self, address):
+        if address.user is not None:
+            raise Errors.AddressAlreadyLinkedError(address)
+        address.user = self
+        self.addresses.append(address)
+
+    def unlink(self, address):
+        if address.user is None:
+            raise Errors.AddressNotLinkedError(address)
+        address.user = None
+        self.addresses.remove(address)
+
+    def controls(self, address):
+        found = Address.get_by(address=address.address)
+        return bool(found and found.user is self)

Added: branches/exp-elixir-branch/Mailman/database/types.py
===================================================================
--- branches/exp-elixir-branch/Mailman/database/types.py                        
        (rev 0)
+++ branches/exp-elixir-branch/Mailman/database/types.py        2007-05-19 
23:27:17 UTC (rev 8221)
@@ -0,0 +1,40 @@
+# 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.
+
+import sys
+
+from sqlalchemy import types
+
+
+
+# SQLAlchemy custom type for storing enums in the database.
+class EnumType(types.TypeDecorator):
+    # Enums can be stored as strings of the form:
+    # full.path.to.Enum:intval
+    impl = types.String
+
+    def convert_bind_param(self, value, engine):
+        return '%s:%s.%d' % (value.enumclass.__module__,
+                             value.enumclass.__name__,
+                             int(value))
+
+    def convert_result_value(self, value, engine):
+        path, intvalue = value.rsplit(':', 1)
+        modulename, classname = intvalue.rsplit('.', 1)
+        __import__(modulename)
+        cls = getattr(sys.modules[modulename], classname)
+        return cls[int(intvalue)]

Modified: branches/exp-elixir-branch/Mailman/database/usermanager.py
===================================================================
--- branches/exp-elixir-branch/Mailman/database/usermanager.py  2007-05-18 
00:14:58 UTC (rev 8220)
+++ branches/exp-elixir-branch/Mailman/database/usermanager.py  2007-05-19 
23:27:17 UTC (rev 8221)
@@ -27,7 +27,10 @@
 from Mailman import Errors
 from Mailman.LockFile import LockFile
 from Mailman.configuration import config
+from Mailman.database.model import Address
+from Mailman.database.model import Profile
 from Mailman.database.model import Roster
+from Mailman.database.model import User
 from Mailman.interfaces import IUserManager
 
 
@@ -49,9 +52,7 @@
         roster = Roster.get_by(name=name)
         if roster:
             raise Errors.RosterExistsError(name)
-        roster = Roster(name=name)
-        objectstore.flush()
-        return roster
+        return Roster(name=name)
 
     def get_roster(self, name):
         roster = Roster.get_by(name=name)
@@ -61,9 +62,27 @@
 
     def delete_roster(self, roster):
         roster.delete()
-        objectstore.flush()
 
     @property
     def rosters(self):
         for roster in Roster.select():
             yield roster
+
+    def create_user(self):
+        user = User()
+        # Users always have a profile
+        user.profile = Profile()
+        user.profile.user = user
+        return user
+
+    def delete_user(self, user):
+        user.delete()
+
+    @property
+    def users(self):
+        for user in User.select():
+            yield user
+
+    def get_user(self, address):
+        found = Address.get_by(address=address)
+        return found and found.user

Modified: branches/exp-elixir-branch/Mailman/docs/addresses.txt
===================================================================
--- branches/exp-elixir-branch/Mailman/docs/addresses.txt       2007-05-18 
00:14:58 UTC (rev 8220)
+++ branches/exp-elixir-branch/Mailman/docs/addresses.txt       2007-05-19 
23:27:17 UTC (rev 8221)
@@ -15,6 +15,7 @@
 new roster.
 
     >>> from Mailman.configuration import config
+    >>> from Mailman.database import flush
     >>> mgr = config.user_manager
     >>> roster_1 = mgr.create_roster('roster-1')
     >>> sorted(roster_1.addresses)
@@ -27,6 +28,7 @@
 Creating a simple email address object is straight forward.
 
     >>> addr_1 = roster_1.create('[EMAIL PROTECTED]')
+    >>> flush()
     >>> addr_1.address
     '[EMAIL PROTECTED]'
     >>> addr_1.real_name is None
@@ -136,5 +138,6 @@
 
     >>> for roster in mgr.rosters:
     ...     mgr.delete_roster(roster)
+    >>> flush()
     >>> list(mgr.rosters)
     []

Added: branches/exp-elixir-branch/Mailman/docs/users.txt
===================================================================
--- branches/exp-elixir-branch/Mailman/docs/users.txt                           
(rev 0)
+++ branches/exp-elixir-branch/Mailman/docs/users.txt   2007-05-19 23:27:17 UTC 
(rev 8221)
@@ -0,0 +1,180 @@
+Users
+=====
+
+Users are entities that combine addresses, preferences, and a password
+scheme.  Password schemes can be anything from a traditional
+challenge/response type password string to an OpenID url.
+
+
+Create, deleting, and managing users
+------------------------------------
+
+Users are managed by the IUserManager.  Users don't have any unique
+identifying information, and no such id is needed to create them.
+
+    >>> from Mailman.configuration import config
+    >>> from Mailman.database import flush
+    >>> mgr = config.user_manager
+    >>> user = mgr.create_user()
+
+Users have a real name, a password scheme, a default profile, and a set of
+addresses that they control.  All of these data are None or empty for a newly
+created user.
+
+    >>> user.real_name is None
+    True
+    >>> user.password is None
+    True
+    >>> user.addresses
+    []
+
+You can iterate over all the users in a user manager.
+
+    >>> another_user = mgr.create_user()
+    >>> flush()
+    >>> all_users = list(mgr.users)
+    >>> len(list(all_users))
+    2
+    >>> user is not another_user
+    True
+    >>> user in all_users
+    True
+    >>> another_user in all_users
+    True
+
+You can also delete users from the user manager.
+
+    >>> mgr.delete_user(user)
+    >>> mgr.delete_user(another_user)
+    >>> flush()
+    >>> len(list(mgr.users))
+    0
+
+
+Simple user information
+-----------------------
+
+Users may have a real name and a password scheme.
+
+    >>> user = mgr.create_user()
+    >>> user.password = 'my password'
+    >>> user.real_name = 'Zoe Person'
+    >>> flush()
+    >>> only_person = list(mgr.users)[0]
+    >>> only_person.password
+    'my password'
+    >>> only_person.real_name
+    'Zoe Person'
+
+The password and real name can be changed at any time.
+
+    >>> user.real_name = 'Zoe X. Person'
+    >>> user.password = 'another password'
+    >>> only_person.real_name
+    'Zoe X. Person'
+    >>> only_person.password
+    'another password'
+
+
+Users and addresses
+-------------------
+
+One of the pieces of information that a user links to is a set of email
+addresses, in the form of IAddress objects.  A user can control many
+addresses, but addresses may be control by only one user.
+
+Given a user and an address, you can link the two together.
+
+    >>> roster = mgr.get_roster('')
+    >>> address = roster.create('[EMAIL PROTECTED]', 'Anne Person')
+    >>> user.link(address)
+    >>> flush()
+    >>> sorted(address.address for address in user.addresses)
+    ['[EMAIL PROTECTED]']
+
+But don't try to link an address to more than one user.
+
+    >>> another_user = mgr.create_user()
+    >>> another_user.link(address)
+    Traceback (most recent call last):
+    ...
+    AddressAlreadyLinkedError: Anne Person <[EMAIL PROTECTED]>
+
+You can also ask whether a given user controls a given address.
+
+    >>> user.controls(address)
+    True
+    >>> not_my_address = roster.create('[EMAIL PROTECTED]', 'Ben Person')
+    >>> user.controls(not_my_address)
+    False
+
+Given a text email address, the user manager can find the user that controls
+that address.
+
+    >>> mgr.get_user('[EMAIL PROTECTED]') is user
+    True
+    >>> mgr.get_user('[EMAIL PROTECTED]') is None
+    True
+
+Addresses can also be unlinked from a user.
+
+    >>> user.unlink(address)
+    >>> user.controls(address)
+    False
+    >>> mgr.get_user('[EMAIL PROTECTED]') is None
+    True
+
+But don't try to unlink the address from a user it's not linked to.
+
+    >>> user.unlink(address)
+    Traceback (most recent call last):
+    ...
+    AddressNotLinkedError: Anne Person <[EMAIL PROTECTED]>
+    >>> another_user.unlink(address)
+    Traceback (most recent call last):
+    ...
+    AddressNotLinkedError: Anne Person <[EMAIL PROTECTED]>
+    >>> mgr.delete_user(another_user)
+
+
+Users and profiles
+------------------
+
+Users always have a default profile.
+
+    >>> from Mailman.interfaces import IProfile
+    >>> IProfile.providedBy(user.profile)
+    True
+
+A profile is a set of preferences such as whether the user wants to receive an
+acknowledgment of all of their posts to a mailing list...
+
+    >>> user.profile.acknowledge_posts
+    False
+
+...whether the user wants to hide their email addresses on web pages and in
+postings to the list...
+
+    >>> user.profile.hide_address
+    True
+
+...the language code for the user's preferred language...
+
+    >>> user.profile.preferred_language
+    'en'
+
+...whether the user wants to receive the list's copy of a message if they are
+explicitly named in one of the recipient headers...
+
+    >>> user.profile.receive_list_copy
+    True
+
+...whether the user wants to receive a copy of their own postings...
+
+    >>> user.profile.receive_own_postings
+    True
+
+...and the preferred delivery method.
+
+    >>> print user.profile.delivery_mode
+    DeliveryMode.regular

Deleted: branches/exp-elixir-branch/Mailman/interfaces/deliverymode.py
===================================================================
--- branches/exp-elixir-branch/Mailman/interfaces/deliverymode.py       
2007-05-18 00:14:58 UTC (rev 8220)
+++ branches/exp-elixir-branch/Mailman/interfaces/deliverymode.py       
2007-05-19 23:27:17 UTC (rev 8221)
@@ -1,41 +0,0 @@
-# Copyright (C) 2007 by the Free Software Foundation, Inc.
-#
-# This program is free software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License
-# as published by the Free Software Foundation; either version 2
-# of the License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
-# USA.
-
-"""Marker interfaces for various delivery modes."""
-
-from zope.interface import Interface, Attribute
-
-
-
-class IDeliveryMode(Interface):
-    """A generic delivery mode."""
-
-
-class IRegularDelivery(IDeliveryMode):
-    """Regular delivery mode."""
-
-
-class IDigestDelivery(IDeliveryMode):
-    """Base interface for digest delivery."""
-
-
-class IPlainTextDigestDelivery(IDigestDelivery):
-    """Plain text digest delivery mode."""
-
-
-class IMIMEDigestDeliver(IDigestDelivery):
-    """MIME digest delivery mode."""

Deleted: branches/exp-elixir-branch/Mailman/interfaces/deliverystatus.py
===================================================================
--- branches/exp-elixir-branch/Mailman/interfaces/deliverystatus.py     
2007-05-18 00:14:58 UTC (rev 8220)
+++ branches/exp-elixir-branch/Mailman/interfaces/deliverystatus.py     
2007-05-19 23:27:17 UTC (rev 8221)
@@ -1,56 +0,0 @@
-# Copyright (C) 2007 by the Free Software Foundation, Inc.
-#
-# This program is free software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License
-# as published by the Free Software Foundation; either version 2
-# of the License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
-# USA.
-
-"""Interface for various delivery status."""
-
-from zope.interface import Interface, Attribute
-
-
-
-class IDeliveryStatus(Interface):
-    """Interface for delivery statuses."""
-
-    enabled = Attribute(
-        """Boolean specifying whether delivery is enabled or not.""")
-
-
-class IDeliveryDisabledByUser(IDeliveryStatus):
-    """Interface for deliveries disabled by the user."""
-
-
-class IDeliveryDisabledByAdministrator(IDeliveryStatus):
-    """Interface for deliveries disabled by the list owner or moderator."""
-
-    reason = Attribute(
-        """The text reason given by the list administrator.""")
-
-
-class IDeliveryDisabledByBounces(IDeliveryStatus):
-    """Interface for deliveries disabled due to excessive bounces."""
-
-    bounce_info = Attribute(
-        """The IBounceInfo statistics.""")
-
-
-class IDeliveryTemporarilySuspended(IDeliveryStatus):
-    """Interface for temporarily suspended delivery, e.g. while on vacation."""
-
-    start_date = Attribute(
-        """Date at which delivery suspension begins.""")
-
-    end_date = Attribute(
-        """Date at which delivery suspension ends.""")

Added: branches/exp-elixir-branch/Mailman/interfaces/manager.py
===================================================================
--- branches/exp-elixir-branch/Mailman/interfaces/manager.py                    
        (rev 0)
+++ branches/exp-elixir-branch/Mailman/interfaces/manager.py    2007-05-19 
23:27:17 UTC (rev 8221)
@@ -0,0 +1,57 @@
+# 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.
+
+"""Generic database object manager interface."""
+
+from zope.interface import Interface, Attribute
+
+
+
+class IManaged(Interface):
+    """An object managed by an IManager."""
+
+    name = Attribute("""The name of the managed object.""")
+
+
+
+class IManager(Interface):
+    """Create and manage profiles."""
+
+    def create(name):
+        """Create and return a new IManaged object.
+
+        name is the unique name for this object.  Raises
+        ExistingManagedObjectError if an IManaged object with the given name
+        already exists.
+        """
+
+    def get(name):
+        """Return the named IManaged object.
+
+        Raises NoSuchManagedObjectError if the named IManaged object does not
+        exist.
+        """
+
+    def delete(name):
+        """Delete the named IManaged object.
+
+        Raises NoSuchManagedObjectError if the named IManaged object does not
+        exist.
+        """
+
+    iterator = Attribute(
+        """Return an iterator over the all the IManaged objects.""")

Deleted: branches/exp-elixir-branch/Mailman/interfaces/member.py
===================================================================
--- branches/exp-elixir-branch/Mailman/interfaces/member.py     2007-05-18 
00:14:58 UTC (rev 8220)
+++ branches/exp-elixir-branch/Mailman/interfaces/member.py     2007-05-19 
23:27:17 UTC (rev 8221)
@@ -1,38 +0,0 @@
-# Copyright (C) 2007 by the Free Software Foundation, Inc.
-#
-# This program is free software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License
-# as published by the Free Software Foundation; either version 2
-# of the License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
-# USA.
-
-"""Interface for an email address which is a member of a roster."""
-
-from zope.interface import Interface, Attribute
-
-
-
-class IMember(Interface):
-    """An email address member of a roster.
-
-    Members are considered equal if they have the same address and are a
-    member of the same roster.
-    """
-
-    address = Attribute(
-        """Read-only IAddress of this member.""")
-
-    roster = Attribute(
-        """Read-only IRoster this email address is a member of.""")
-
-    profile = Attribute(
-        """The IProfile for this member's subscription.""")

Modified: branches/exp-elixir-branch/Mailman/interfaces/profile.py
===================================================================
--- branches/exp-elixir-branch/Mailman/interfaces/profile.py    2007-05-18 
00:14:58 UTC (rev 8220)
+++ branches/exp-elixir-branch/Mailman/interfaces/profile.py    2007-05-19 
23:27:17 UTC (rev 8221)
@@ -24,34 +24,30 @@
 class IProfile(Interface):
     """Delivery related information."""
 
-    acknowledge = Attribute(
+    acknowledge_posts = Attribute(
         """Boolean specifying whether to send an acknowledgment receipt for
         every posting to the mailing list.
         """)
 
-    hide = Attribute(
+    hide_address = Attribute(
         """Boolean specifying whether to hide this email address from fellow
         list members.
         """)
 
-    language = Attribute(
+    preferred_language = Attribute(
         """Preferred language for interacting with a mailing list.""")
 
-    list_copy = Attribute(
+    receive_list_copy = Attribute(
         """Boolean specifying whether to receive a list copy if the user is
         explicitly named in one of the recipient headers.
         """)
 
-    own_postings = Attribute(
+    receive_own_postings = Attribute(
         """Boolean specifying whether to receive a list copy of the user's own
         postings to the mailing list.
         """)
 
     delivery_mode = Attribute(
-        """The IDeliveryMode.""")
+        """The preferred delivery mode.
 
-    delivery_status = Attribute(
-        """The IDeliveryStatus.""")
-
-    posting_permission = Attribute(
-        """The IPostingPermission.""")
+        This is an enum constant of the type DeliveryMode.""")

Modified: branches/exp-elixir-branch/Mailman/interfaces/user.py
===================================================================
--- branches/exp-elixir-branch/Mailman/interfaces/user.py       2007-05-18 
00:14:58 UTC (rev 8220)
+++ branches/exp-elixir-branch/Mailman/interfaces/user.py       2007-05-19 
23:27:17 UTC (rev 8221)
@@ -22,38 +22,38 @@
 
 
 class IUser(Interface):
-    """A basic user.
+    """A basic user."""
 
-    Users compare equal if they have the same user_id and are managed by the
-    same IUserManager instance.
-    """
-
-    user_id = Attribute(
-        """Read-only IUserManager-wide unique user id.""")
-
     real_name = Attribute(
         """This user's Real Name.""")
 
     password = Attribute(
         """This user's password information.""")
 
-    default_profile = Attribute(
+    profile = Attribute(
         """The default IProfile for this user.""")
 
     addresses = Attribute(
         """An iterator over all the IAddresses controlled by this user.""")
 
-    def add_address(address):
-        """Add the email address to the set of addresses controlled by this
-        user.  address must be a text email address.
+    def link(address):
+        """Link this user to the given IAddress.
+
+        Raises AddressAlreadyLinkedError if this IAddress is already linked to
+        another user.
         """
 
-    def remove_address(address):
-        """Remove the email address from the set of addresses controlled by
-        this user.  address must be a text email address.
+    def unlink(address):
+        """Unlink this IAddress from the user.
+
+        Raises AddressNotLinkedError if this address is not linked to this
+        user, either because it's not linked to any user or it's linked to
+        some other user.
         """
 
     def controls(address):
-        """Return True if this user controls the given email address,
-        otherwise False.  address must be a text email address
+        """Determine whether this user controls the given email address.
+
+        'address' is a text email address.  This method returns true if the
+        user controls the given email address, otherwise false.
         """

Modified: branches/exp-elixir-branch/Mailman/interfaces/usermanager.py
===================================================================
--- branches/exp-elixir-branch/Mailman/interfaces/usermanager.py        
2007-05-18 00:14:58 UTC (rev 8220)
+++ branches/exp-elixir-branch/Mailman/interfaces/usermanager.py        
2007-05-19 23:27:17 UTC (rev 8221)
@@ -53,3 +53,18 @@
 
     rosters = Attribute(
         """An iterator over all IRosters managed by this user manager.""")
+
+    def create_user():
+        """Create and return an IUser."""
+
+    def delete_user(user):
+        """Delete the given IUser."""
+
+    def get_user(address):
+        """Get the user that controls the given email address, or None.
+
+        'address' is a text email address.
+        """
+
+    users = Attribute(
+        """An iterator over all the IUsers managed by this user manager.""")

Modified: branches/exp-elixir-branch/Mailman/testing/inmemory.py
===================================================================
--- branches/exp-elixir-branch/Mailman/testing/inmemory.py      2007-05-18 
00:14:58 UTC (rev 8220)
+++ branches/exp-elixir-branch/Mailman/testing/inmemory.py      2007-05-19 
23:27:17 UTC (rev 8221)
@@ -134,65 +134,6 @@
 
 
 
-class RegularDelivery(object):
-    implements(IRegularDelivery)
-
-
-class PlainTextDigestDelivery(object):
-    implements(IPlainTextDigestDelivery)
-
-
-class MIMEDigestDelivery(object):
-    implements(IMIMEDigestDeliver)
-
-
-
-class DeliveryEnabled(object):
-    implements(IDeliveryStatus)
-
-    @property
-    def enabled(self):
-        return True
-
-
-class DeliveryDisabled(object):
-    implements(IDeliveryStatus)
-
-    @property
-    def enabled(self):
-        return False
-
-
-class DeliveryDisabledByUser(DeliveryDisabled):
-    implements(IDeliveryDisabledByUser)
-
-
-class DeliveryDisabledbyAdministrator(DeliveryDisabled):
-    implements(IDeliveryDisabledByAdministrator)
-
-    reason = u'Unknown'
-
-
-class DeliveryDisabledByBounces(DeliveryDisabled):
-    implements(IDeliveryDisabledByBounces)
-
-    bounce_info = 'XXX'
-
-
-class DeliveryTemporarilySuspended(object):
-    implements(IDeliveryTemporarilySuspended)
-
-    def __init__(self, start_date, end_date):
-        self.start_date = start_date
-        self.end_date   = end_date
-
-    @property
-    def enabled(self):
-        now = datetime.datetime.now()
-        return not (self.start_date <= now < self.end_date)
-
-
-
 class OkayToPost(object):
     implements(IPostingPermission)
 
@@ -201,21 +142,6 @@
 
 
 
-class Profile(object):
-    implements(IProfile)
-
-    # System defaults
-    acknowledge         = False
-    hide                = True
-    language            = 'en'
-    list_copy           = True
-    own_postings        = True
-    delivery_mode       = RegularDelivery()
-    delivery_status     = DeliveryEnabled()
-    posting_permission  = OkayToPost()
-
-
-
 class Roster(object):
     implements(IRoster)
 

Copied: branches/exp-elixir-branch/Mailman/testing/test_user.py (from rev 8220, 
branches/exp-elixir-branch/Mailman/testing/test_address.py)
===================================================================
--- branches/exp-elixir-branch/Mailman/testing/test_user.py                     
        (rev 0)
+++ branches/exp-elixir-branch/Mailman/testing/test_user.py     2007-05-19 
23:27:17 UTC (rev 8221)
@@ -0,0 +1,30 @@
+# 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.
+
+"""Doctest harness for testing users."""
+
+import doctest
+import unittest
+
+options = doctest.ELLIPSIS | doctest.NORMALIZE_WHITESPACE
+
+
+def test_suite():
+    suite = unittest.TestSuite()
+    suite.addTest(doctest.DocFileSuite('../docs/users.txt',
+                                       optionflags=options))
+    return suite

Modified: branches/exp-elixir-branch/misc/Makefile.in
===================================================================
--- branches/exp-elixir-branch/misc/Makefile.in 2007-05-18 00:14:58 UTC (rev 
8220)
+++ branches/exp-elixir-branch/misc/Makefile.in 2007-05-19 23:27:17 UTC (rev 
8221)
@@ -64,7 +64,7 @@
 EZCMD=         $(PYTHONLIBDIR)/bin/easy_install $(EZINSTOPTS)
 
 WSGIREF=       wsgiref-0.1.2-py2.4.egg
-MUNEPY=                munepy-1.0-py2.5.egg
+MUNEPY=                munepy-1.1-py2.5.egg
 EZPKGS=                $(WSGIREF) $(MUNEPY)
 
 # Modes for directories and executables created by the install

Deleted: branches/exp-elixir-branch/misc/munepy-1.0-py2.5.egg
===================================================================
(Binary files differ)

Added: branches/exp-elixir-branch/misc/munepy-1.1-py2.5.egg
===================================================================
(Binary files differ)


Property changes on: branches/exp-elixir-branch/misc/munepy-1.1-py2.5.egg
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream


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