[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