[prev in list] [next in list] [prev in thread] [next in thread] 

List:       mailman-cvs
Subject:    [Mailman-checkins] SF.net SVN: mailman: [8223]
From:       bwarsaw () users ! sourceforge ! net
Date:       2007-05-24 4:21:45
Message-ID: E1Hr4pd-00067t-JU () sc8-pr-svn1 ! sourceforge ! net
[Download RAW message or body]

Revision: 8223
          http://svn.sourceforge.net/mailman/?rev=8223&view=rev
Author:   bwarsaw
Date:     2007-05-23 21:21:45 -0700 (Wed, 23 May 2007)

Log Message:
-----------
Mailng list owners, moderators, and administrators via rosters.

- Update the IMailingListRosters interface.
- Rewrite the MailingList model class so that it uses has_field() instead of
  with_fields().
- Add the IMailingListRosters methods and attributes to MailingList model
  object.
- Added a RosterName database model class to manage the indirect connection
  between mailing lists and rosters.  This can't be a direct connection
  because the list manager storage and user manager storage may be in
  different databases.
- Mailing list creation now adds the default owner and moderator rosters.
- Rewrite MailList.__getattr__() to do also delegate now to the model object.
  Don't use AttributeError in the delegation to make things clearer.  This
  also allows me to remove MailList.list_name and MailList.host_name since
  those will correctly delegate to the model object.

Modified Paths:
--------------
    branches/exp-elixir-branch/Mailman/MailList.py
    branches/exp-elixir-branch/Mailman/database/listmanager.py
    branches/exp-elixir-branch/Mailman/database/model/mailinglist.py
    branches/exp-elixir-branch/Mailman/database/model/roster.py
    branches/exp-elixir-branch/Mailman/interfaces/mlistrosters.py

Added Paths:
-----------
    branches/exp-elixir-branch/Mailman/docs/mlist-rosters.txt
    branches/exp-elixir-branch/Mailman/testing/test_mlist_rosters.py

Modified: branches/exp-elixir-branch/Mailman/MailList.py
===================================================================
--- branches/exp-elixir-branch/Mailman/MailList.py	2007-05-22 03:08:55 UTC (rev 8222)
+++ branches/exp-elixir-branch/Mailman/MailList.py	2007-05-24 04:21:45 UTC (rev 8223)
@@ -93,7 +93,12 @@
                Archiver, Digester, SecurityManager, Bouncer, GatewayManager,
                Autoresponder, TopicMgr, Pending.Pending):
 
-    implements(IMailingList, IMailingListAddresses, IMailingListIdentity)
+    implements(
+        IMailingList,
+        IMailingListAddresses,
+        IMailingListIdentity,
+        IMailingListRosters,
+        )
 
     def __init__(self, data):
         self._data = data
@@ -112,22 +117,24 @@
             init_mlist(self)
 
     def __getattr__(self, name):
+        missing = object()
         if name.startswith('_'):
             return super(MailList, self).__getattr__(name)
-        # Because we're using delegation, we want to be sure that attribute
-        # access to a delegated member function gets passed to the
-        # sub-objects.  This of course imposes a specific name resolution
-        # order.
-        try:
-            return getattr(self._memberadaptor, name)
-        except AttributeError:
-            for guicomponent in self._gui:
-                try:
-                    return getattr(guicomponent, name)
-                except AttributeError:
-                    pass
-            else:
-                raise AttributeError(name)
+        # Delegate to the database model object if it has the attribute.
+        obj = getattr(self._data, name, missing)
+        if obj is not missing:
+            return obj
+        # Delegate to the member adapter next.
+        obj = getattr(self._memberadaptor, name, missing)
+        if obj is not missing:
+            return obj
+        # Finally, delegate to one of the gui components.
+        for guicomponent in self._gui:
+            obj = getattr(guicomponent, name, missing)
+            if obj is not missing:
+                return obj
+        # Nothing left to delegate to, so it's got to be an error.
+        raise AttributeError(name)
 
     def __repr__(self):
         if self.Locked():
@@ -182,14 +189,6 @@
     # IMailingListIdentity
 
     @property
-    def list_name(self):
-        return self._data.list_name
-
-    @property
-    def host_name(self):
-        return self._data.host_name
-
-    @property
     def fqdn_listname(self):
         return Utils.fqdn_listname(self._data.list_name, self._data.host_name)
 
@@ -292,7 +291,6 @@
             user = Utils.ObscureEmail(user)
         return '%s/%s' % (url, urllib.quote(user.lower()))
 
-
 
     #
     # Instance and subcomponent initialization

Modified: branches/exp-elixir-branch/Mailman/database/listmanager.py
===================================================================
--- branches/exp-elixir-branch/Mailman/database/listmanager.py	2007-05-22 03:08:55 \
                UTC (rev 8222)
+++ branches/exp-elixir-branch/Mailman/database/listmanager.py	2007-05-24 04:21:45 \
UTC (rev 8223) @@ -25,6 +25,7 @@
 from Mailman import Errors
 from Mailman.Utils import split_listname, fqdn_listname
 from Mailman.database.model import MailingList
+from Mailman.database.model import Roster
 from Mailman.interfaces import IListManager
 
 
@@ -43,7 +44,11 @@
             raise Errors.MMListAlreadyExistsError(fqdn_listname)
         mlist = MailingList(list_name=listname,
                             host_name=hostname)
-        objectstore.flush()
+        # Mailing lists always have an owner and a moderator roster.
+        owner_roster = Roster(name=fqdn_listname + ' owners')
+        moderator_roster = Roster(name=fqdn_listname + ' moderators')
+        mlist.add_owner_roster(owner_roster)
+        mlist.add_moderator_roster(moderator_roster)
         from Mailman.MailList import MailList
         wrapper = MailList(mlist)
         self._objectmap[mlist] = wrapper
@@ -64,7 +69,6 @@
         # want a MailList object that's had its backing data removed.  OTOH, I
         # don't like reaching into the object to accomplish this.
         mlist._data.delete()
-        objectstore.flush()
         mlist._data = None
 
     @property

Modified: branches/exp-elixir-branch/Mailman/database/model/mailinglist.py
===================================================================
--- branches/exp-elixir-branch/Mailman/database/model/mailinglist.py	2007-05-22 \
                03:08:55 UTC (rev 8222)
+++ branches/exp-elixir-branch/Mailman/database/model/mailinglist.py	2007-05-24 \
04:21:45 UTC (rev 8223) @@ -16,143 +16,235 @@
 # USA.
 
 from elixir import *
+from zope.interface import implements
 
+from Mailman.Utils import fqdn_listname
+from Mailman.configuration import config
+from Mailman.interfaces import *
+from Mailman.database.model.roster import RosterName
 
+
+
 class MailingList(Entity):
-    with_fields(
-        # List identity
-        list_name                                   = Field(Unicode),
-        host_name                                   = Field(Unicode),
-        # Attributes not directly modifiable via the web u/i
-        web_page_url                                = Field(Unicode),
-        admin_member_chunksize                      = Field(Integer),
-        # Attributes which are directly modifiable via the web u/i.  The more
-        # complicated attributes are currently stored as pickles, though that
-        # will change as the schema and implementation is developed.
-        next_request_id                             = Field(Integer),
-        next_digest_number                          = Field(Integer),
-        admin_responses                             = Field(PickleType),
-        postings_responses                          = Field(PickleType),
-        request_responses                           = Field(PickleType),
-        digest_last_sent_at                         = Field(Float),
-        one_last_digest                             = Field(PickleType),
-        volume                                      = Field(Integer),
-        last_post_time                              = Field(Float),
-        # OldStyleMemberships attributes, temporarily stored as pickles.
-        bounce_info                                 = Field(PickleType),
-        delivery_status                             = Field(PickleType),
-        digest_members                              = Field(PickleType),
-        language                                    = Field(PickleType),
-        members                                     = Field(PickleType),
-        passwords                                   = Field(PickleType),
-        topics_userinterest                         = Field(PickleType),
-        user_options                                = Field(PickleType),
-        usernames                                   = Field(PickleType),
-        # Attributes which are directly modifiable via the web u/i.  The more
-        # complicated attributes are currently stored as pickles, though that
-        # will change as the schema and implementation is developed.
-        accept_these_nonmembers                     = Field(PickleType),
-        acceptable_aliases                          = Field(PickleType),
-        admin_immed_notify                          = Field(Boolean),
-        admin_notify_mchanges                       = Field(Boolean),
-        administrivia                               = Field(Boolean),
-        advertised                                  = Field(Boolean),
-        anonymous_list                              = Field(Boolean),
-        archive                                     = Field(Boolean),
-        archive_private                             = Field(Boolean),
-        archive_volume_frequency                    = Field(Integer),
-        autorespond_admin                           = Field(Boolean),
-        autorespond_postings                        = Field(Boolean),
-        autorespond_requests                        = Field(Integer),
-        autoresponse_admin_text                     = Field(Unicode),
-        autoresponse_graceperiod                    = Field(Integer),
-        autoresponse_postings_text                  = Field(Unicode),
-        autoresponse_request_text                   = Field(Unicode),
-        ban_list                                    = Field(PickleType),
-        bounce_info_stale_after                     = Field(Integer),
-        bounce_matching_headers                     = Field(Unicode),
-        bounce_notify_owner_on_disable              = Field(Boolean),
-        bounce_notify_owner_on_removal              = Field(Boolean),
-        bounce_processing                           = Field(Boolean),
-        bounce_score_threshold                      = Field(Integer),
-        bounce_unrecognized_goes_to_list_owner      = Field(Boolean),
-        bounce_you_are_disabled_warnings            = Field(Integer),
-        bounce_you_are_disabled_warnings_interval   = Field(Integer),
-        collapse_alternatives                       = Field(Boolean),
-        convert_html_to_plaintext                   = Field(Boolean),
-        default_member_moderation                   = Field(Boolean),
-        description                                 = Field(Unicode),
-        digest_footer                               = Field(Unicode),
-        digest_header                               = Field(Unicode),
-        digest_is_default                           = Field(Boolean),
-        digest_send_periodic                        = Field(Boolean),
-        digest_size_threshhold                      = Field(Integer),
-        digest_volume_frequency                     = Field(Integer),
-        digestable                                  = Field(Boolean),
-        discard_these_nonmembers                    = Field(PickleType),
-        emergency                                   = Field(Boolean),
-        encode_ascii_prefixes                       = Field(Boolean),
-        filter_action                               = Field(Integer),
-        filter_content                              = Field(Boolean),
-        filter_filename_extensions                  = Field(PickleType),
-        filter_mime_types                           = Field(PickleType),
-        first_strip_reply_to                        = Field(Boolean),
-        forward_auto_discards                       = Field(Boolean),
-        gateway_to_mail                             = Field(Boolean),
-        gateway_to_news                             = Field(Boolean),
-        generic_nonmember_action                    = Field(Integer),
-        goodbye_msg                                 = Field(Unicode),
-        header_filter_rules                         = Field(PickleType),
-        hold_these_nonmembers                       = Field(PickleType),
-        include_list_post_header                    = Field(Boolean),
-        include_rfc2369_headers                     = Field(Boolean),
-        info                                        = Field(Unicode),
-        linked_newsgroup                            = Field(Unicode),
-        max_days_to_hold                            = Field(Integer),
-        max_message_size                            = Field(Integer),
-        max_num_recipients                          = Field(Integer),
-        member_moderation_action                    = Field(Boolean),
-        member_moderation_notice                    = Field(Unicode),
-        mime_is_default_digest                      = Field(Boolean),
-        mod_password                                = Field(Unicode),
-        msg_footer                                  = Field(Unicode),
-        msg_header                                  = Field(Unicode),
-        new_member_options                          = Field(Integer),
-        news_moderation                             = Field(Boolean),
-        news_prefix_subject_too                     = Field(Boolean),
-        nntp_host                                   = Field(Unicode),
-        nondigestable                               = Field(Boolean),
-        nonmember_rejection_notice                  = Field(Unicode),
-        obscure_addresses                           = Field(Boolean),
-        pass_filename_extensions                    = Field(PickleType),
-        pass_mime_types                             = Field(PickleType),
-        password                                    = Field(Unicode),
-        personalize                                 = Field(Integer),
-        post_id                                     = Field(Integer),
-        preferred_language                          = Field(Unicode),
-        private_roster                              = Field(Boolean),
-        real_name                                   = Field(Unicode),
-        reject_these_nonmembers                     = Field(PickleType),
-        reply_goes_to_list                          = Field(Boolean),
-        reply_to_address                            = Field(Unicode),
-        require_explicit_destination                = Field(Boolean),
-        respond_to_post_requests                    = Field(Boolean),
-        scrub_nondigest                             = Field(Boolean),
-        send_goodbye_msg                            = Field(Boolean),
-        send_reminders                              = Field(Boolean),
-        send_welcome_msg                            = Field(Boolean),
-        subject_prefix                              = Field(Unicode),
-        subscribe_auto_approval                     = Field(PickleType),
-        subscribe_policy                            = Field(Integer),
-        topics                                      = Field(PickleType),
-        topics_bodylines_limit                      = Field(Integer),
-        topics_enabled                              = Field(Boolean),
-        umbrella_list                               = Field(Boolean),
-        umbrella_member_suffix                      = Field(Unicode),
-        unsubscribe_policy                          = Field(Integer),
-        welcome_msg                                 = Field(Unicode),
+    implements(
+        IMailingList,
+        IMailingListAddresses,
+        IMailingListIdentity,
+        IMailingListRosters,
         )
-    # Relationships - XXX ondelete='all, delete=orphan' ??
-    has_and_belongs_to_many('owners',               of_kind='Roster')
-    has_and_belongs_to_many('moderators',           of_kind='Roster')
-    has_and_belongs_to_many('available_languages',  of_kind='Language')
+
+    # List identity
+    has_field('list_name',                                  Unicode),
+    has_field('host_name',                                  Unicode),
+    # Attributes not directly modifiable via the web u/i
+    has_field('web_page_url',                               Unicode),
+    has_field('admin_member_chunksize',                     Integer),
+    # Attributes which are directly modifiable via the web u/i.  The more
+    # complicated attributes are currently stored as pickles, though that
+    # will change as the schema and implementation is developed.
+    has_field('next_request_id',                            Integer),
+    has_field('next_digest_number',                         Integer),
+    has_field('admin_responses',                            PickleType),
+    has_field('postings_responses',                         PickleType),
+    has_field('request_responses',                          PickleType),
+    has_field('digest_last_sent_at',                        Float),
+    has_field('one_last_digest',                            PickleType),
+    has_field('volume',                                     Integer),
+    has_field('last_post_time',                             Float),
+    # OldStyleMemberships attributes, temporarily stored as pickles.
+    has_field('bounce_info',                                PickleType),
+    has_field('delivery_status',                            PickleType),
+    has_field('digest_members',                             PickleType),
+    has_field('language',                                   PickleType),
+    has_field('members',                                    PickleType),
+    has_field('passwords',                                  PickleType),
+    has_field('topics_userinterest',                        PickleType),
+    has_field('user_options',                               PickleType),
+    has_field('usernames',                                  PickleType),
+    # Attributes which are directly modifiable via the web u/i.  The more
+    # complicated attributes are currently stored as pickles, though that
+    # will change as the schema and implementation is developed.
+    has_field('accept_these_nonmembers',                    PickleType),
+    has_field('acceptable_aliases',                         PickleType),
+    has_field('admin_immed_notify',                         Boolean),
+    has_field('admin_notify_mchanges',                      Boolean),
+    has_field('administrivia',                              Boolean),
+    has_field('advertised',                                 Boolean),
+    has_field('anonymous_list',                             Boolean),
+    has_field('archive',                                    Boolean),
+    has_field('archive_private',                            Boolean),
+    has_field('archive_volume_frequency',                   Integer),
+    has_field('autorespond_admin',                          Boolean),
+    has_field('autorespond_postings',                       Boolean),
+    has_field('autorespond_requests',                       Integer),
+    has_field('autoresponse_admin_text',                    Unicode),
+    has_field('autoresponse_graceperiod',                   Integer),
+    has_field('autoresponse_postings_text',                 Unicode),
+    has_field('autoresponse_request_text',                  Unicode),
+    has_field('ban_list',                                   PickleType),
+    has_field('bounce_info_stale_after',                    Integer),
+    has_field('bounce_matching_headers',                    Unicode),
+    has_field('bounce_notify_owner_on_disable',             Boolean),
+    has_field('bounce_notify_owner_on_removal',             Boolean),
+    has_field('bounce_processing',                          Boolean),
+    has_field('bounce_score_threshold',                     Integer),
+    has_field('bounce_unrecognized_goes_to_list_owner',     Boolean),
+    has_field('bounce_you_are_disabled_warnings',           Integer),
+    has_field('bounce_you_are_disabled_warnings_interval',  Integer),
+    has_field('collapse_alternatives',                      Boolean),
+    has_field('convert_html_to_plaintext',                  Boolean),
+    has_field('default_member_moderation',                  Boolean),
+    has_field('description',                                Unicode),
+    has_field('digest_footer',                              Unicode),
+    has_field('digest_header',                              Unicode),
+    has_field('digest_is_default',                          Boolean),
+    has_field('digest_send_periodic',                       Boolean),
+    has_field('digest_size_threshhold',                     Integer),
+    has_field('digest_volume_frequency',                    Integer),
+    has_field('digestable',                                 Boolean),
+    has_field('discard_these_nonmembers',                   PickleType),
+    has_field('emergency',                                  Boolean),
+    has_field('encode_ascii_prefixes',                      Boolean),
+    has_field('filter_action',                              Integer),
+    has_field('filter_content',                             Boolean),
+    has_field('filter_filename_extensions',                 PickleType),
+    has_field('filter_mime_types',                          PickleType),
+    has_field('first_strip_reply_to',                       Boolean),
+    has_field('forward_auto_discards',                      Boolean),
+    has_field('gateway_to_mail',                            Boolean),
+    has_field('gateway_to_news',                            Boolean),
+    has_field('generic_nonmember_action',                   Integer),
+    has_field('goodbye_msg',                                Unicode),
+    has_field('header_filter_rules',                        PickleType),
+    has_field('hold_these_nonmembers',                      PickleType),
+    has_field('include_list_post_header',                   Boolean),
+    has_field('include_rfc2369_headers',                    Boolean),
+    has_field('info',                                       Unicode),
+    has_field('linked_newsgroup',                           Unicode),
+    has_field('max_days_to_hold',                           Integer),
+    has_field('max_message_size',                           Integer),
+    has_field('max_num_recipients',                         Integer),
+    has_field('member_moderation_action',                   Boolean),
+    has_field('member_moderation_notice',                   Unicode),
+    has_field('mime_is_default_digest',                     Boolean),
+    has_field('mod_password',                               Unicode),
+    has_field('msg_footer',                                 Unicode),
+    has_field('msg_header',                                 Unicode),
+    has_field('new_member_options',                         Integer),
+    has_field('news_moderation',                            Boolean),
+    has_field('news_prefix_subject_too',                    Boolean),
+    has_field('nntp_host',                                  Unicode),
+    has_field('nondigestable',                              Boolean),
+    has_field('nonmember_rejection_notice',                 Unicode),
+    has_field('obscure_addresses',                          Boolean),
+    has_field('pass_filename_extensions',                   PickleType),
+    has_field('pass_mime_types',                            PickleType),
+    has_field('password',                                   Unicode),
+    has_field('personalize',                                Integer),
+    has_field('post_id',                                    Integer),
+    has_field('preferred_language',                         Unicode),
+    has_field('private_roster',                             Boolean),
+    has_field('real_name',                                  Unicode),
+    has_field('reject_these_nonmembers',                    PickleType),
+    has_field('reply_goes_to_list',                         Boolean),
+    has_field('reply_to_address',                           Unicode),
+    has_field('require_explicit_destination',               Boolean),
+    has_field('respond_to_post_requests',                   Boolean),
+    has_field('scrub_nondigest',                            Boolean),
+    has_field('send_goodbye_msg',                           Boolean),
+    has_field('send_reminders',                             Boolean),
+    has_field('send_welcome_msg',                           Boolean),
+    has_field('subject_prefix',                             Unicode),
+    has_field('subscribe_auto_approval',                    PickleType),
+    has_field('subscribe_policy',                           Integer),
+    has_field('topics',                                     PickleType),
+    has_field('topics_bodylines_limit',                     Integer),
+    has_field('topics_enabled',                             Boolean),
+    has_field('umbrella_list',                              Boolean),
+    has_field('umbrella_member_suffix',                     Unicode),
+    has_field('unsubscribe_policy',                         Integer),
+    has_field('welcome_msg',                                Unicode),
+    # Relationships
+    has_many('owner_nameset',
+             of_kind='Mailman.database.model.roster.RosterName',
+             inverse='owner_mlist')
+    has_many('moderator_nameset',
+             of_kind='Mailman.database.model.roster.RosterName',
+             inverse='moderator_mlist')
+##     has_and_belongs_to_many(
+##         'available_languages',
+##         of_kind='Mailman.database.model.languages.Language')
+
+    @property
+    def owners(self):
+        roster_names = set(roster_name.name
+                           for roster_name in self.owner_nameset)
+        for user in _collect_users(roster_names):
+            yield user
+
+    @property
+    def moderators(self):
+        roster_names = set(roster_name.name
+                           for roster_name in self.moderator_nameset)
+        for user in _collect_users(roster_names):
+            yield user
+
+    @property
+    def administrators(self):
+        owner_names = set(roster_name.name
+                          for roster_name in self.owner_nameset)
+        moderator_names = set(roster_name.name
+                              for roster_name in self.moderator_nameset)
+        roster_names = owner_names.union(moderator_names)
+        for user in _collect_users(roster_names):
+            yield user
+
+    @property
+    def owner_rosters(self):
+        for roster_name in self.owner_nameset:
+            roster = config.user_manager.get_roster(roster_name.name)
+            assert roster is not None, (
+                'Missing owner roster: ' + roster_name.name)
+            yield roster
+
+    @property
+    def moderator_rosters(self):
+        for roster_name in self.moderator_nameset:
+            roster = config.user_manager.get_roster(roster_name.name)
+            assert roster is not None, (
+                'Missing owner roster: ' + roster_name.name)
+            yield roster
+
+    def add_owner_roster(self, roster):
+        roster_name = RosterName(name=roster.name)
+        self.owner_nameset.append(roster_name)
+
+    def remove_owner_roster(self, roster):
+        roster_name = RosterName(name=roster.name)
+        self.owner_nameset.remove(roster_name)
+
+    def add_moderator_roster(self, roster):
+        roster_name = RosterName(name=roster.name)
+        self.moderator_nameset.append(roster_name)
+
+    def remove_moderator_roster(self, roster):
+        roster_name = RosterName(name=roster.name)
+        self.moderator_nameset.remove(roster_name)
+
+
+
+def _collect_users(roster_nameset):
+    users = set()
+    for name in roster_nameset:
+        # Look up the roster, which better exist.
+        roster = config.user_manager.get_roster(name)
+        assert roster is not None, 'Missing administrative roster: ' + name
+        # Rosters collect addresses.  It's not required that an address is
+        # linked to a user, but it must be the case that all addresses on the
+        # owner roster are linked to a user.  Get the user that's linked to
+        # each address and add it to the set.
+        for address in roster.addresses:
+            user = config.user_manager.get_user(address.address)
+            assert user is not None, 'Unlinked address: ' + address.address
+            users.add(user)
+    return users

Modified: branches/exp-elixir-branch/Mailman/database/model/roster.py
===================================================================
--- branches/exp-elixir-branch/Mailman/database/model/roster.py	2007-05-22 03:08:55 \
                UTC (rev 8222)
+++ branches/exp-elixir-branch/Mailman/database/model/roster.py	2007-05-24 04:21:45 \
UTC (rev 8223) @@ -45,3 +45,16 @@
         null_roster.addresses.append(addr)
         addr.rosters.append(null_roster)
         return addr
+
+
+
+# Internal implementation of the indirect connection between mailing lists and
+# rosters.
+class RosterName(Entity):
+    has_field('name', Unicode)
+    belongs_to('owner_mlist',
+               of_kind='Mailman.database.model.mailinglist.MailingList',
+               inverse='owner_nameset')
+    belongs_to('moderator_mlist',
+               of_kind='Mailman.database.model.mailinglist.MailingList',
+               inverse='moderator_nameset')

Added: branches/exp-elixir-branch/Mailman/docs/mlist-rosters.txt
===================================================================
--- branches/exp-elixir-branch/Mailman/docs/mlist-rosters.txt	                        \
                (rev 0)
+++ branches/exp-elixir-branch/Mailman/docs/mlist-rosters.txt	2007-05-24 04:21:45 UTC \
(rev 8223) @@ -0,0 +1,118 @@
+Mailing list rosters
+====================
+
+Mailing lists use rosters to manage and organize users for various purposes.
+In order to allow for separate storage of mailing list data and user data, the
+connection between mailing list objects and rosters is indirect.  Mailing
+lists manage roster names, and these roster names are used to find the rosters
+that contain the actual users.
+
+
+Privileged rosters
+------------------
+
+Mailing lists have two types of privileged users, owners and moderators.
+Owners get to change the configuration of mailing lists and moderators get to
+approve or deny held messages and subscription requests.
+
+When a mailing list is created, it automatically contains a roster for the
+list owners and a roster for the list moderators.
+
+    >>> from Mailman.database import flush
+    >>> from Mailman.configuration import config
+    >>> mlist = config.list_manager.create('_xtest@example.com')
+    >>> flush()
+    >>> sorted(roster.name for roster in mlist.owner_rosters)
+    ['_xtest@example.com owners']
+    >>> sorted(roster.name for roster in mlist.moderator_rosters)
+    ['_xtest@example.com moderators']
+
+These rosters are initially empty.
+
+    >>> owner_roster = list(mlist.owner_rosters)[0]
+    >>> sorted(address for address in owner_roster.addresses)
+    []
+    >>> moderator_roster = list(mlist.moderator_rosters)[0]
+    >>> sorted(address for address in moderator_roster.addresses)
+    []
+
+You can create new rosters and add them to the list of owner or moderator
+rosters.
+
+    >>> roster_1 = config.user_manager.create_roster('roster-1')
+    >>> roster_2 = config.user_manager.create_roster('roster-2')
+    >>> roster_3 = config.user_manager.create_roster('roster-3')
+    >>> flush()
+
+Make roster-1 an owner roster, roster-2 a moderator roster, and roster-3 both
+an owner and a moderator roster.
+
+    >>> mlist.add_owner_roster(roster_1)
+    >>> mlist.add_moderator_roster(roster_2)
+    >>> mlist.add_owner_roster(roster_3)
+    >>> mlist.add_moderator_roster(roster_3)
+    >>> flush()
+
+    >>> sorted(roster.name for roster in mlist.owner_rosters)
+    ['_xtest@example.com owners', 'roster-1', 'roster-3']
+    >>> sorted(roster.name for roster in mlist.moderator_rosters)
+    ['_xtest@example.com moderators', 'roster-2', 'roster-3']
+
+
+Privileged users
+----------------
+
+Rosters are the lower level way of managing owners and moderators, but usually
+you just want to know which users have owner and moderator privileges.  You
+can get the list of such users by using different attributes.
+
+Because the rosters are all empty to start with, we can create a bunch of
+users that will end up being our owners and moderators.
+
+    >>> aperson = config.user_manager.create_user()
+    >>> bperson = config.user_manager.create_user()
+    >>> cperson = config.user_manager.create_user()
+
+These users need addresses, because rosters manage addresses.
+
+    >>> address_1 = roster_1.create('aperson@example.com', 'Anne Person')
+    >>> aperson.link(address_1)
+    >>> address_2 = roster_2.create('bperson@example.com', 'Ben Person')
+    >>> bperson.link(address_2)
+    >>> address_3 = roster_1.create('cperson@example.com', 'Claire Person')
+    >>> cperson.link(address_3)
+    >>> roster_3.addresses.append(address_3)
+    >>> flush()
+
+Now that everything is set up, we can iterate through the various collections
+of privileged users.  Here are the owners of the list.
+
+    >>> from Mailman.interfaces import IUser
+    >>> addresses = []
+    >>> for user in mlist.owners:
+    ...     assert IUser.providedBy(user), 'Non-IUser owner found'
+    ...     for address in user.addresses:
+    ...         addresses.append(address.address)
+    >>> sorted(addresses)
+    ['aperson@example.com', 'cperson@example.com']
+
+Here are the moderators of the list.
+
+    >>> addresses = []
+    >>> for user in mlist.moderators:
+    ...     assert IUser.providedBy(user), 'Non-IUser moderator found'
+    ...     for address in user.addresses:
+    ...         addresses.append(address.address)
+    >>> sorted(addresses)
+    ['bperson@example.com', 'cperson@example.com']
+
+The administrators of a mailing list are the union of the owners and
+moderators.
+
+    >>> addresses = []
+    >>> for user in mlist.administrators:
+    ...     assert IUser.providedBy(user), 'Non-IUser administrator found'
+    ...     for address in user.addresses:
+    ...         addresses.append(address.address)
+    >>> sorted(addresses)
+    ['aperson@example.com', 'bperson@example.com', 'cperson@example.com']

Modified: branches/exp-elixir-branch/Mailman/interfaces/mlistrosters.py
===================================================================
--- branches/exp-elixir-branch/Mailman/interfaces/mlistrosters.py	2007-05-22 03:08:55 \
                UTC (rev 8222)
+++ branches/exp-elixir-branch/Mailman/interfaces/mlistrosters.py	2007-05-24 04:21:45 \
UTC (rev 8223) @@ -29,20 +29,44 @@
     """
 
     owners = Attribute(
-        """An iterator over the IMembers who are the owners of this mailing
-        list.  This iterator will not include the moderators of the mailing
-        list.""")
+        """The IUser owners of this mailing list.
 
+        This does not include the IUsers who are moderators but not owners of
+        the mailing list.""")
+
     moderators = Attribute(
-        """An iterator over the IMembers who are the moderators of this
-        mailing list.  This iterator will not include the owners of the
+        """The IUser moderators of this mailing list.
+
+        This does not include the IUsers who are owners but not moderators of
+        the mailing list.""")
+
+    administrators = Attribute(
+        """The IUser administrators of this mailing list.
+
+        This includes the IUsers who are both owners and moderators of the
         mailing list.""")
 
-    administrator = Attribute(
-        """An iterator over all the IMembers who are administrators of this
-        mailing list.  This includes all the list's owners and all the list's
-        moderators.""")
+    owner_rosters = Attribute(
+        """An iterator over the IRosters containing all the owners of this
+        mailing list.""")
 
+    moderator_rosters = Attribute(
+        """An iterator over the IRosters containing all the moderators of this
+        mailing list.""")
+
+    def add_owner_roster(roster):
+        """Add an IRoster to this mailing list's set of owner rosters."""
+
+    def remove_owner_roster(roster):
+        """Remove an IRoster from this mailing list's set of owner rosters."""
+
+    def add_moderator_roster(roster):
+        """Add an IRoster to this mailing list's set of moderator rosters."""
+
+    def remove_moderator_roster(roster):
+        """Remove an IRoster from this mailing list's set of moderator
+        rosters."""
+
     members = Attribute(
         """An iterator over all the members of the mailing list, regardless of
         whether they are to receive regular messages or digests, or whether
@@ -59,38 +83,14 @@
         deliver disabled or not, or of the type of digest they are to
         receive.""")
 
-    owner_rosters = Attribute(
-        """An iterator over the IRosters containing all the owners of this
-        mailing list.""")
-
-    moderator_rosters = Attribute(
-        """An iterator over the IRosters containing all the moderators of this
-        mailing list.""")
-
     member_rosters = Attribute(
         """An iterator over the IRosters containing all the members of this
         mailing list.""")
 
-    def add_owner_roster(roster):
-        """Add the given IRoster to the list of rosters for the owners of this
-        mailing list."""
-
-    def add_moderator_roster(roster):
-        """Add the given IRoster to the list of rosters for the moderators of
-        this mailing list."""
-
     def add_member_roster(roster):
         """Add the given IRoster to the list of rosters for the members of this
         mailing list."""
 
-    def remove_owner_roster(roster):
-        """Remove the given IRoster to the list of rosters for the owners of
-        this mailing list."""
-
-    def remove_moderator_roster(roster):
-        """Remove the given IRoster to the list of rosters for the moderators
-        of this mailing list."""
-
     def remove_member_roster(roster):
         """Remove the given IRoster to the list of rosters for the members of
         this mailing list."""

Added: branches/exp-elixir-branch/Mailman/testing/test_mlist_rosters.py
===================================================================
--- branches/exp-elixir-branch/Mailman/testing/test_mlist_rosters.py	                 \
                (rev 0)
+++ branches/exp-elixir-branch/Mailman/testing/test_mlist_rosters.py	2007-05-24 \
04:21:45 UTC (rev 8223) @@ -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 the IMailingListRosters interface."""
+
+import doctest
+import unittest
+
+options = doctest.ELLIPSIS | doctest.NORMALIZE_WHITESPACE
+
+
+def test_suite():
+    suite = unittest.TestSuite()
+    suite.addTest(doctest.DocFileSuite('../docs/mlist-rosters.txt',
+                                       optionflags=options))
+    return suite


This was sent by the SourceForge.net collaborative development platform, the world's \
largest Open Source development site. _______________________________________________
Mailman-checkins mailing list
Mailman-checkins@python.org
Unsubscribe: http://mail.python.org/mailman/options/mailman-checkins/mailman-cvs%40progressive-comp.com



[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic