diff options
| author | Barry Warsaw | 2011-04-17 11:37:06 -0400 |
|---|---|---|
| committer | Barry Warsaw | 2011-04-17 11:37:06 -0400 |
| commit | c2130f46808b3cf3cc27311d6d6f6b608e76b267 (patch) | |
| tree | f020d0d8034f0e21bc3f00787e5d3b7f447954e5 | |
| parent | c894152772595a1acbd6dc4c1ac0e033888ea923 (diff) | |
| parent | e2ed3ae7dfa138a8288b4c49cd2b671c35f6c703 (diff) | |
| download | mailman-c2130f46808b3cf3cc27311d6d6f6b608e76b267.tar.gz mailman-c2130f46808b3cf3cc27311d6d6f6b608e76b267.tar.zst mailman-c2130f46808b3cf3cc27311d6d6f6b608e76b267.zip | |
24 files changed, 583 insertions, 359 deletions
diff --git a/src/mailman/app/lifecycle.py b/src/mailman/app/lifecycle.py index b30266f3b..689fab484 100644 --- a/src/mailman/app/lifecycle.py +++ b/src/mailman/app/lifecycle.py @@ -80,11 +80,11 @@ def create_list(fqdn_listname, owners=None): # owners of the mailing list. user_manager = getUtility(IUserManager) for owner_address in owners: - addr = user_manager.get_address(owner_address) - if addr is None: + address = user_manager.get_address(owner_address) + if address is None: user = user_manager.create_user(owner_address) - addr = list(user.addresses)[0] - addr.subscribe(mlist, MemberRole.owner) + address = list(user.addresses)[0] + mlist.subscribe(address, MemberRole.owner) return mlist diff --git a/src/mailman/app/membership.py b/src/mailman/app/membership.py index aaf7f05df..be2382a7f 100644 --- a/src/mailman/app/membership.py +++ b/src/mailman/app/membership.py @@ -99,7 +99,7 @@ def add_member(mlist, email, realname, password, delivery_mode, language): # scheme is recorded in the hashed password string. user.password = encrypt_password(password) user.preferences.preferred_language = language - member = address.subscribe(mlist, MemberRole.member) + member = mlist.subscribe(address, MemberRole.member) member.preferences.delivery_mode = delivery_mode else: # The user exists and is linked to the address. @@ -110,8 +110,7 @@ def add_member(mlist, email, realname, password, delivery_mode, language): raise AssertionError( 'User should have had linked address: {0}'.format(address)) # Create the member and set the appropriate preferences. - # pylint: disable-msg=W0631 - member = address.subscribe(mlist, MemberRole.member) + member = mlist.subscribe(address, MemberRole.member) member.preferences.preferred_language = language member.preferences.delivery_mode = delivery_mode return member diff --git a/src/mailman/app/registrar.py b/src/mailman/app/registrar.py index ec899237a..f6f2e8679 100644 --- a/src/mailman/app/registrar.py +++ b/src/mailman/app/registrar.py @@ -138,7 +138,7 @@ class Registrar: if list_name is not None: mlist = getUtility(IListManager).get(list_name) if mlist: - address.subscribe(mlist, MemberRole.member) + mlist.subscribe(address, MemberRole.member) return True def discard(self, token): diff --git a/src/mailman/database/mailman.sql b/src/mailman/database/mailman.sql index 7c09fb79f..5af8656c2 100644 --- a/src/mailman/database/mailman.sql +++ b/src/mailman/database/mailman.sql @@ -1,12 +1,13 @@ CREATE TABLE _request ( - id INTEGER NOT NULL, - "key" TEXT, - request_type TEXT, - data_hash TEXT, - mailing_list_id INTEGER, - PRIMARY KEY (id), - CONSTRAINT _request_mailing_list_id_fk FOREIGN KEY(mailing_list_id) REFERENCES mailinglist (id) -); + id INTEGER NOT NULL, + "key" TEXT, + request_type TEXT, + data_hash TEXT, + mailing_list_id INTEGER, + PRIMARY KEY (id), + CONSTRAINT _request_mailing_list_id_fk + FOREIGN KEY (mailing_list_id) REFERENCES mailinglist (id) + ); CREATE TABLE acceptablealias ( id INTEGER NOT NULL, @@ -14,7 +15,7 @@ CREATE TABLE acceptablealias ( mailing_list_id INTEGER NOT NULL, PRIMARY KEY (id), CONSTRAINT acceptablealias_mailing_list_id_fk - FOREIGN KEY(mailing_list_id) REFERENCES mailinglist (id) + FOREIGN KEY (mailing_list_id) REFERENCES mailinglist (id) ); CREATE INDEX ix_acceptablealias_mailing_list_id ON acceptablealias (mailing_list_id); @@ -22,33 +23,33 @@ CREATE INDEX ix_acceptablealias_alias ON acceptablealias ("alias"); CREATE TABLE address ( - id INTEGER NOT NULL, - email TEXT, - _original TEXT, - real_name TEXT, - verified_on TIMESTAMP, - registered_on TIMESTAMP, - user_id INTEGER, - preferences_id INTEGER, - PRIMARY KEY (id), - CONSTRAINT address_user_id_fk FOREIGN KEY(user_id) REFERENCES user (id), - CONSTRAINT address_preferences_id_fk FOREIGN KEY(preferences_id) REFERENCES preferences (id) -); + id INTEGER NOT NULL, + email TEXT, + _original TEXT, + real_name TEXT, + verified_on TIMESTAMP, + registered_on TIMESTAMP, + user_id INTEGER, + preferences_id INTEGER, + PRIMARY KEY (id), + CONSTRAINT address_user_id_fk + FOREIGN KEY (user_id) REFERENCES user (id), + CONSTRAINT address_preferences_id_fk + FOREIGN KEY (preferences_id) REFERENCES preferences (id) + ); CREATE TABLE autoresponserecord ( - id INTEGER NOT NULL, - address_id INTEGER, - mailing_list_id INTEGER, - response_type INTEGER, - date_sent TIMESTAMP, - PRIMARY KEY (id), - CONSTRAINT autoresponserecord_address_id_fk - FOREIGN KEY (address_id) - REFERENCES address (id), - CONSTRAINT autoresponserecord_mailing_list_id - FOREIGN KEY (mailing_list_id) - REFERENCES mailinglist (id) - ); + id INTEGER NOT NULL, + address_id INTEGER, + mailing_list_id INTEGER, + response_type INTEGER, + date_sent TIMESTAMP, + PRIMARY KEY (id), + CONSTRAINT autoresponserecord_address_id_fk + FOREIGN KEY (address_id) REFERENCES address (id), + CONSTRAINT autoresponserecord_mailing_list_id + FOREIGN KEY (mailing_list_id) REFERENCES mailinglist (id) + ); CREATE INDEX ix_autoresponserecord_address_id ON autoresponserecord (address_id); CREATE INDEX ix_autoresponserecord_mailing_list_id @@ -61,8 +62,7 @@ CREATE TABLE contentfilter ( filter_type INTEGER, PRIMARY KEY (id), CONSTRAINT contentfilter_mailing_list_id - FOREIGN KEY (mailing_list_id) - REFERENCES mailinglist (id) + FOREIGN KEY (mailing_list_id) REFERENCES mailinglist (id) ); CREATE INDEX ix_contentfilter_mailing_list_id ON contentfilter (mailing_list_id); @@ -83,135 +83,143 @@ CREATE TABLE language ( ); CREATE TABLE mailinglist ( - id INTEGER NOT NULL, - -- List identity - list_name TEXT, - host_name TEXT, - list_id TEXT, - include_list_post_header BOOLEAN, - include_rfc2369_headers BOOLEAN, - -- Attributes not directly modifiable via the web u/i - created_at TIMESTAMP, - admin_member_chunksize INTEGER, - next_request_id INTEGER, - next_digest_number INTEGER, - digest_last_sent_at TIMESTAMP, - volume INTEGER, - last_post_at TIMESTAMP, - accept_these_nonmembers BLOB, - acceptable_aliases_id INTEGER, - admin_immed_notify BOOLEAN, - admin_notify_mchanges BOOLEAN, - administrivia BOOLEAN, - advertised BOOLEAN, - anonymous_list BOOLEAN, - archive BOOLEAN, - archive_private BOOLEAN, - archive_volume_frequency INTEGER, - -- Automatic responses. - autorespond_owner INTEGER, - autoresponse_owner_text TEXT, - autorespond_postings INTEGER, - autoresponse_postings_text TEXT, - autorespond_requests INTEGER, - autoresponse_request_text TEXT, - autoresponse_grace_period TEXT, - -- Bounces. - bounce_info_stale_after TEXT, - bounce_matching_headers TEXT, - bounce_notify_owner_on_disable BOOLEAN, - bounce_notify_owner_on_removal BOOLEAN, - bounce_processing BOOLEAN, - bounce_score_threshold INTEGER, - bounce_unrecognized_goes_to_list_owner BOOLEAN, - bounce_you_are_disabled_warnings INTEGER, - bounce_you_are_disabled_warnings_interval TEXT, - -- Content filtering. - filter_content BOOLEAN, - collapse_alternatives BOOLEAN, - convert_html_to_plaintext BOOLEAN, - default_member_action INTEGER, - default_nonmember_action INTEGER, - description TEXT, - digest_footer TEXT, - digest_header TEXT, - digest_is_default BOOLEAN, - digest_send_periodic BOOLEAN, - digest_size_threshold INTEGER, - digest_volume_frequency TEXT, - digestable BOOLEAN, - discard_these_nonmembers BLOB, - emergency BOOLEAN, - encode_ascii_prefixes BOOLEAN, - first_strip_reply_to BOOLEAN, - forward_auto_discards BOOLEAN, - gateway_to_mail BOOLEAN, - gateway_to_news BOOLEAN, - generic_nonmember_action INTEGER, - goodbye_msg TEXT, - header_matches BLOB, - hold_these_nonmembers BLOB, - info TEXT, - linked_newsgroup TEXT, - max_days_to_hold INTEGER, - max_message_size INTEGER, - max_num_recipients INTEGER, - member_moderation_notice TEXT, - mime_is_default_digest BOOLEAN, - moderator_password TEXT, - msg_footer TEXT, - msg_header TEXT, - new_member_options INTEGER, - news_moderation TEXT, - news_prefix_subject_too BOOLEAN, - nntp_host TEXT, - nondigestable BOOLEAN, - nonmember_rejection_notice TEXT, - obscure_addresses BOOLEAN, - personalize TEXT, - pipeline TEXT, - post_id INTEGER, - preferred_language TEXT, - private_roster BOOLEAN, - real_name TEXT, - reject_these_nonmembers BLOB, - reply_goes_to_list TEXT, - reply_to_address TEXT, - require_explicit_destination BOOLEAN, - respond_to_post_requests BOOLEAN, - scrub_nondigest BOOLEAN, - send_goodbye_msg BOOLEAN, - send_reminders BOOLEAN, - send_welcome_msg BOOLEAN, - start_chain TEXT, - subject_prefix TEXT, - subscribe_auto_approval BLOB, - subscribe_policy INTEGER, - topics BLOB, - topics_bodylines_limit INTEGER, - topics_enabled BOOLEAN, - unsubscribe_policy INTEGER, - welcome_msg TEXT, - PRIMARY KEY (id) -); + id INTEGER NOT NULL, + -- List identity + list_name TEXT, + host_name TEXT, + list_id TEXT, + include_list_post_header BOOLEAN, + include_rfc2369_headers BOOLEAN, + -- Attributes not directly modifiable via the web u/i + created_at TIMESTAMP, + admin_member_chunksize INTEGER, + next_request_id INTEGER, + next_digest_number INTEGER, + digest_last_sent_at TIMESTAMP, + volume INTEGER, + last_post_at TIMESTAMP, + accept_these_nonmembers BLOB, + acceptable_aliases_id INTEGER, + admin_immed_notify BOOLEAN, + admin_notify_mchanges BOOLEAN, + administrivia BOOLEAN, + advertised BOOLEAN, + anonymous_list BOOLEAN, + archive BOOLEAN, + archive_private BOOLEAN, + archive_volume_frequency INTEGER, + -- Automatic responses. + autorespond_owner INTEGER, + autoresponse_owner_text TEXT, + autorespond_postings INTEGER, + autoresponse_postings_text TEXT, + autorespond_requests INTEGER, + autoresponse_request_text TEXT, + autoresponse_grace_period TEXT, + -- Bounces. + bounce_info_stale_after TEXT, + bounce_matching_headers TEXT, + bounce_notify_owner_on_disable BOOLEAN, + bounce_notify_owner_on_removal BOOLEAN, + bounce_processing BOOLEAN, + bounce_score_threshold INTEGER, + bounce_unrecognized_goes_to_list_owner BOOLEAN, + bounce_you_are_disabled_warnings INTEGER, + bounce_you_are_disabled_warnings_interval TEXT, + -- Content filtering. + filter_content BOOLEAN, + collapse_alternatives BOOLEAN, + convert_html_to_plaintext BOOLEAN, + default_member_action INTEGER, + default_nonmember_action INTEGER, + description TEXT, + digest_footer TEXT, + digest_header TEXT, + digest_is_default BOOLEAN, + digest_send_periodic BOOLEAN, + digest_size_threshold INTEGER, + digest_volume_frequency TEXT, + digestable BOOLEAN, + discard_these_nonmembers BLOB, + emergency BOOLEAN, + encode_ascii_prefixes BOOLEAN, + first_strip_reply_to BOOLEAN, + forward_auto_discards BOOLEAN, + gateway_to_mail BOOLEAN, + gateway_to_news BOOLEAN, + generic_nonmember_action INTEGER, + goodbye_msg TEXT, + header_matches BLOB, + hold_these_nonmembers BLOB, + info TEXT, + linked_newsgroup TEXT, + max_days_to_hold INTEGER, + max_message_size INTEGER, + max_num_recipients INTEGER, + member_moderation_notice TEXT, + mime_is_default_digest BOOLEAN, + moderator_password TEXT, + msg_footer TEXT, + msg_header TEXT, + new_member_options INTEGER, + news_moderation TEXT, + news_prefix_subject_too BOOLEAN, + nntp_host TEXT, + nondigestable BOOLEAN, + nonmember_rejection_notice TEXT, + obscure_addresses BOOLEAN, + personalize TEXT, + pipeline TEXT, + post_id INTEGER, + preferred_language TEXT, + private_roster BOOLEAN, + real_name TEXT, + reject_these_nonmembers BLOB, + reply_goes_to_list TEXT, + reply_to_address TEXT, + require_explicit_destination BOOLEAN, + respond_to_post_requests BOOLEAN, + scrub_nondigest BOOLEAN, + send_goodbye_msg BOOLEAN, + send_reminders BOOLEAN, + send_welcome_msg BOOLEAN, + start_chain TEXT, + subject_prefix TEXT, + subscribe_auto_approval BLOB, + subscribe_policy INTEGER, + topics BLOB, + topics_bodylines_limit INTEGER, + topics_enabled BOOLEAN, + unsubscribe_policy INTEGER, + welcome_msg TEXT, + PRIMARY KEY (id) + ); + CREATE TABLE member ( - id INTEGER NOT NULL, - role TEXT, - mailing_list TEXT, - moderation_action INTEGER, - address_id INTEGER, - preferences_id INTEGER, - PRIMARY KEY (id), - CONSTRAINT member_address_id_fk FOREIGN KEY(address_id) REFERENCES address (id), - CONSTRAINT member_preferences_id_fk FOREIGN KEY(preferences_id) REFERENCES preferences (id) -); + id INTEGER NOT NULL, + role TEXT, + mailing_list TEXT, + moderation_action INTEGER, + address_id INTEGER, + preferences_id INTEGER, + user_id INTEGER, + PRIMARY KEY (id), + CONSTRAINT member_address_id_fk + FOREIGN KEY (address_id) REFERENCES address (id), + CONSTRAINT member_preferences_id_fk + FOREIGN KEY (preferences_id) REFERENCES preferences (id) + CONSTRAINT member_user_id_fk + FOREIGN KEY (user_id) REFERENCES user (id) + ); + CREATE TABLE message ( - id INTEGER NOT NULL, - message_id_hash TEXT, - path TEXT, - message_id TEXT, - PRIMARY KEY (id) -); + id INTEGER NOT NULL, + message_id_hash TEXT, + path TEXT, + message_id TEXT, + PRIMARY KEY (id) + ); + CREATE TABLE onelastdigest ( id INTEGER NOT NULL, mailing_list_id INTEGER, @@ -219,55 +227,63 @@ CREATE TABLE onelastdigest ( delivery_mode TEXT, PRIMARY KEY (id), CONSTRAINT onelastdigest_mailing_list_id_fk - FOREIGN KEY(mailing_list_id) REFERENCES mailinglist(id), + FOREIGN KEY (mailing_list_id) REFERENCES mailinglist(id), CONSTRAINT onelastdigest_address_id_fk - FOREIGN KEY(address_id) REFERENCES address(id) + FOREIGN KEY (address_id) REFERENCES address(id) ); + CREATE TABLE pended ( - id INTEGER NOT NULL, - token TEXT, - expiration_date TIMESTAMP, - PRIMARY KEY (id) -); + id INTEGER NOT NULL, + token TEXT, + expiration_date TIMESTAMP, + PRIMARY KEY (id) + ); + CREATE TABLE pendedkeyvalue ( - id INTEGER NOT NULL, - "key" TEXT, - value TEXT, - pended_id INTEGER, - PRIMARY KEY (id), - CONSTRAINT pendedkeyvalue_pended_id_fk FOREIGN KEY(pended_id) REFERENCES pended (id) -); + id INTEGER NOT NULL, + "key" TEXT, + value TEXT, + pended_id INTEGER, + PRIMARY KEY (id), + CONSTRAINT pendedkeyvalue_pended_id_fk + FOREIGN KEY (pended_id) REFERENCES pended (id) + ); + CREATE TABLE preferences ( - id INTEGER NOT NULL, - acknowledge_posts BOOLEAN, - hide_address BOOLEAN, - preferred_language TEXT, - receive_list_copy BOOLEAN, - receive_own_postings BOOLEAN, - delivery_mode TEXT, - delivery_status TEXT, - PRIMARY KEY (id) -); + id INTEGER NOT NULL, + acknowledge_posts BOOLEAN, + hide_address BOOLEAN, + preferred_language TEXT, + receive_list_copy BOOLEAN, + receive_own_postings BOOLEAN, + delivery_mode TEXT, + delivery_status TEXT, + PRIMARY KEY (id) + ); + CREATE TABLE user ( - id INTEGER NOT NULL, - real_name TEXT, - password BINARY, - _user_id TEXT, - _created_on TIMESTAMP, - preferences_id INTEGER, - PRIMARY KEY (id), - CONSTRAINT user_preferences_id_fk - FOREIGN KEY(preferences_id) - REFERENCES preferences (id) -); + id INTEGER NOT NULL, + real_name TEXT, + password BINARY, + _user_id TEXT, + _created_on TIMESTAMP, + _preferred_address_id INTEGER, + preferences_id INTEGER, + PRIMARY KEY (id), + CONSTRAINT user_preferences_id_fk + FOREIGN KEY (preferences_id) REFERENCES preferences (id), + CONSTRAINT _preferred_address_id_fk + FOREIGN KEY (_preferred_address_id) REFERENCES address (id) + ); CREATE INDEX ix_user_user_id ON user (_user_id); CREATE TABLE version ( - id INTEGER NOT NULL, - component TEXT, - version INTEGER, - PRIMARY KEY (id) -); + id INTEGER NOT NULL, + component TEXT, + version INTEGER, + PRIMARY KEY (id) + ); + CREATE INDEX ix__request_mailing_list_id ON _request (mailing_list_id); CREATE INDEX ix_address_preferences_id ON address (preferences_id); CREATE INDEX ix_address_user_id ON address (user_id); @@ -281,4 +297,4 @@ CREATE TABLE ban ( email TEXT, mailing_list TEXT, PRIMARY KEY (id) -); + ); diff --git a/src/mailman/interfaces/address.py b/src/mailman/interfaces/address.py index c051c9b0c..2f6d05bba 100644 --- a/src/mailman/interfaces/address.py +++ b/src/mailman/interfaces/address.py @@ -41,6 +41,7 @@ class AddressError(MailmanError): """A general address-related error occurred.""" def __init__(self, address): + super(AddressError, self).__init__() self.address = address def __str__(self): @@ -99,16 +100,6 @@ class IAddress(Interface): None if the email address has not yet been validated. The specific method of validation is not defined here.""") - def subscribe(mailing_list, role): - """Subscribe the address to the given mailing list with the given role. - - :param mailing_list: The IMailingList being subscribed to. - :param role: A MemberRole enum value. - :return: The IMember representing this subscription. - :raises AlreadySubscribedError: If the address is already subscribed - to the mailing list with the given role. - """ - preferences = Attribute( """This address's preferences.""") diff --git a/src/mailman/interfaces/mailinglist.py b/src/mailman/interfaces/mailinglist.py index d8c0ebb26..23d21cd34 100644 --- a/src/mailman/interfaces/mailinglist.py +++ b/src/mailman/interfaces/mailinglist.py @@ -182,7 +182,7 @@ class IMailingList(Interface): def confirm_address(cookie=''): """The address used for various forms of email confirmation.""" - # Rosters. + # Rosters and subscriptions. owners = Attribute( """The IUser owners of this mailing list. @@ -232,6 +232,22 @@ class IMailingList(Interface): :rtype: Roster """ + def subscribe(subscriber, role): + """Subscribe the given address or user to the mailing list. + + :param subscriber: The address or user to subscribe to the mailing + list. The user's preferred address receives deliveries, if she + has one, otherwise no address for the user appears in the rosters. + :type subscriber: `IUser` or `IAddress` + :param role: The role being subscribed to (e.g. a member, owner, or + moderator of a mailing list. + :type role: `MemberRole` + :return: The member object representing the subscription. + :rtype: `IMember` + :raises AlreadySubscribedError: If the address or user is already + subscribed to the mailing list with the given role. + """ + # Posting history. last_post_at = Attribute( diff --git a/src/mailman/interfaces/user.py b/src/mailman/interfaces/user.py index 2c2652413..ceffec57f 100644 --- a/src/mailman/interfaces/user.py +++ b/src/mailman/interfaces/user.py @@ -22,11 +22,26 @@ from __future__ import absolute_import, unicode_literals __metaclass__ = type __all__ = [ 'IUser', + 'UnverifiedAddressError', ] from zope.interface import Interface, Attribute +from mailman.interfaces.errors import MailmanError + + + +class UnverifiedAddressError(MailmanError): + """Unverified address cannot be used as a user's preferred address.""" + + def __init__(self, address): + super(UnverifiedAddressError, self).__init__() + self.address = address + + def __str__(self): + return self.address + class IUser(Interface): @@ -47,6 +62,9 @@ class IUser(Interface): addresses = Attribute( """An iterator over all the `IAddresses` controlled by this user.""") + preferred_address = Attribute( + """The user's preferred `IAddress`. This must be validated.""") + memberships = Attribute( """A roster of this user's memberships.""") diff --git a/src/mailman/model/address.py b/src/mailman/model/address.py index 6fa310c48..92f7f8986 100644 --- a/src/mailman/model/address.py +++ b/src/mailman/model/address.py @@ -26,14 +26,11 @@ __all__ = [ from email.utils import formataddr -from storm.locals import DateTime, Int, Reference, Store, Unicode +from storm.locals import DateTime, Int, Reference, Unicode from zope.interface import implements from mailman.database.model import Model -from mailman.interfaces.member import AlreadySubscribedError from mailman.interfaces.address import IAddress -from mailman.model.member import Member -from mailman.model.preferences import Preferences @@ -73,24 +70,6 @@ class Address(Model): return '<Address: {0} [{1}] key: {2} at {3:#x}>'.format( address_str, verified, self.email, id(self)) - def subscribe(self, mailing_list, role): - # This member has no preferences by default. - store = Store.of(self) - member = store.find( - Member, - Member.role == role, - Member.mailing_list == mailing_list.fqdn_listname, - Member.address == self).one() - if member: - raise AlreadySubscribedError( - mailing_list.fqdn_listname, self.email, role) - member = Member(role=role, - mailing_list=mailing_list.fqdn_listname, - address=self) - member.preferences = Preferences() - store.add(member) - return member - @property def original_email(self): return (self.email if self._original is None else self._original) diff --git a/src/mailman/model/docs/addresses.txt b/src/mailman/model/docs/addresses.txt index fdcc993b5..ffbb897ab 100644 --- a/src/mailman/model/docs/addresses.txt +++ b/src/mailman/model/docs/addresses.txt @@ -153,47 +153,6 @@ And of course, you can also set the validation date. 2007-05-13 22:54:01 -Subscriptions -============= - -Addresses get subscribed to mailing lists, not users. When the address is -subscribed, a role is specified. -:: - - >>> address_5 = user_manager.create_address( - ... 'eperson@example.com', 'Elly Person') - >>> mlist = create_list('test@example.com') - - >>> from mailman.interfaces.member import MemberRole - >>> address_5.subscribe(mlist, MemberRole.owner) - <Member: Elly Person <eperson@example.com> on - test@example.com as MemberRole.owner> - >>> address_5.subscribe(mlist, MemberRole.member) - <Member: Elly Person <eperson@example.com> on - test@example.com as MemberRole.member> - -Now Elly is both an owner and a member of the mailing list. - - >>> def memberkey(member): - ... return member.mailing_list, member.address.email, int(member.role) - >>> dump_list(mlist.owners.members, key=memberkey) - <Member: Elly Person <eperson@example.com> on - test@example.com as MemberRole.owner> - >>> dump_list(mlist.moderators.members, key=memberkey) - *Empty* - >>> dump_list(mlist.administrators.members, key=memberkey) - <Member: Elly Person <eperson@example.com> on - test@example.com as MemberRole.owner> - >>> dump_list(mlist.members.members, key=memberkey) - <Member: Elly Person <eperson@example.com> on - test@example.com as MemberRole.member> - >>> dump_list(mlist.regular_members.members, key=memberkey) - <Member: Elly Person <eperson@example.com> on - test@example.com as MemberRole.member> - >>> dump_list(mlist.digest_members.members, key=memberkey) - *Empty* - - Case-preserved addresses ======================== diff --git a/src/mailman/model/docs/mailinglist.txt b/src/mailman/model/docs/mailinglist.txt index 33d681762..ec9e37ee0 100644 --- a/src/mailman/model/docs/mailinglist.txt +++ b/src/mailman/model/docs/mailinglist.txt @@ -9,16 +9,16 @@ the system by its posting address, i.e. the email address you would send a message to in order to post a message to the mailing list. This must be fully qualified. - >>> alpha = create_list('alpha@example.com') - >>> print alpha.fqdn_listname - alpha@example.com + >>> mlist = create_list('aardvark@example.com') + >>> print mlist.fqdn_listname + aardvark@example.com The mailing list also has convenient attributes for accessing the list's short name (i.e. local part) and host name. - >>> print alpha.list_name - alpha - >>> print alpha.host_name + >>> print mlist.list_name + aardvark + >>> print mlist.host_name example.com @@ -29,46 +29,126 @@ Mailing list membership is represented by `rosters`. Each mailing list has several rosters of members, representing the subscribers to the mailing list, the owners, the moderators, and so on. The rosters are defined by a membership role. + +Addresses can be explicitly subscribed to a mailing list. By default, a +subscription puts the address in the `member` role, meaning that address will +receive a copy of any message sent to the mailing list. :: + >>> from mailman.interfaces.usermanager import IUserManager + >>> from zope.component import getUtility + >>> user_manager = getUtility(IUserManager) + + >>> aperson = user_manager.create_address('aperson@example.com') + >>> bperson = user_manager.create_address('bperson@example.com') + >>> mlist.subscribe(aperson) + <Member: aperson@example.com on aardvark@example.com as MemberRole.member> + >>> mlist.subscribe(bperson) + <Member: bperson@example.com on aardvark@example.com as MemberRole.member> + +Both addresses appear on the roster of members. + + >>> for member in mlist.members.members: + ... print member + <Member: aperson@example.com on aardvark@example.com as MemberRole.member> + <Member: bperson@example.com on aardvark@example.com as MemberRole.member> + +By explicitly specifying the role of the subscription, an address can be added +to the owner and moderator rosters. + >>> from mailman.interfaces.member import MemberRole - >>> from mailman.testing.helpers import subscribe + >>> mlist.subscribe(aperson, MemberRole.owner) + <Member: aperson@example.com on aardvark@example.com as MemberRole.owner> + >>> cperson = user_manager.create_address('cperson@example.com') + >>> mlist.subscribe(cperson, MemberRole.owner) + <Member: cperson@example.com on aardvark@example.com as MemberRole.owner> + >>> mlist.subscribe(cperson, MemberRole.moderator) + <Member: cperson@example.com on aardvark@example.com + as MemberRole.moderator> - >>> subscribe(alpha, 'Anne') - >>> subscribe(alpha, 'Bart') - >>> subscribe(alpha, 'Cris') - >>> subscribe(alpha, 'Anne', MemberRole.owner) - >>> subscribe(alpha, 'Dave', MemberRole.owner) - >>> subscribe(alpha, 'Elle', MemberRole.moderator) +A Person is now both a member and an owner of the mailing list. C Person is +an owner and a moderator. +:: + + >>> for member in mlist.owners.members: + ... print member + <Member: aperson@example.com on aardvark@example.com as MemberRole.owner> + <Member: cperson@example.com on aardvark@example.com as MemberRole.owner> -We can retrieve a roster directly... + >>> for member in mlist.moderators.members: + ... print member + <Member: cperson@example.com on aardvark@example.com + as MemberRole.moderator> - >>> for member in alpha.members.members: - ... print member.address - Anne Person <aperson@example.com> - Bart Person <bperson@example.com> - Cris Person <cperson@example.com> -...or programmatically. +All rosters can also be accessed indirectly. +:: - >>> roster = alpha.get_roster(MemberRole.member) + >>> roster = mlist.get_roster(MemberRole.member) >>> for member in roster.members: - ... print member.address - Anne Person <aperson@example.com> - Bart Person <bperson@example.com> - Cris Person <cperson@example.com> + ... print member + <Member: aperson@example.com on aardvark@example.com as MemberRole.member> + <Member: bperson@example.com on aardvark@example.com as MemberRole.member> -This includes the roster of owners... + >>> roster = mlist.get_roster(MemberRole.owner) + >>> for member in roster.members: + ... print member + <Member: aperson@example.com on aardvark@example.com as MemberRole.owner> + <Member: cperson@example.com on aardvark@example.com as MemberRole.owner> - >>> roster = alpha.get_roster(MemberRole.owner) + >>> roster = mlist.get_roster(MemberRole.moderator) >>> for member in roster.members: - ... print member.address - Anne Person <aperson@example.com> - Dave Person <dperson@example.com> + ... print member + <Member: cperson@example.com on aardvark@example.com + as MemberRole.moderator> -...and moderators. - >>> roster = alpha.get_roster(MemberRole.moderator) - >>> for member in roster.members: - ... print member.address - Elle Person <eperson@example.com> +Subscribing users +================= + +An alternative way of subscribing to a mailing list is as a user with a +preferred address. This way the user can change their subscription address +just by changing their preferred address. +:: + + >>> from mailman.utilities.datetime import now + >>> user = user_manager.create_user('dperson@example.com', 'Dave Person') + >>> address = list(user.addresses)[0] + >>> address.verified_on = now() + >>> user.preferred_address = address + + >>> mlist.subscribe(user) + <Member: Dave Person <dperson@example.com> on aardvark@example.com + as MemberRole.member> + >>> for member in mlist.members.members: + ... print member + <Member: aperson@example.com on aardvark@example.com as MemberRole.member> + <Member: bperson@example.com on aardvark@example.com as MemberRole.member> + <Member: Dave Person <dperson@example.com> on aardvark@example.com + as MemberRole.member> + + >>> new_address = user.register('dave.person@example.com') + >>> new_address.verified_on = now() + >>> user.preferred_address = new_address + + >>> for member in mlist.members.members: + ... print member + <Member: aperson@example.com on aardvark@example.com as MemberRole.member> + <Member: bperson@example.com on aardvark@example.com as MemberRole.member> + <Member: dave.person@example.com on aardvark@example.com + as MemberRole.member> + +A user is not allowed to subscribe more than once to the mailing list. + + >>> mlist.subscribe(user) + Traceback (most recent call last): + ... + AlreadySubscribedError: <User "Dave Person" (1) at ...> + is already a MemberRole.member of mailing list aardvark@example.com + +However, they are allowed to subscribe again with a specific address, even if +this address is their preferred address. + + >>> mlist.subscribe(user.preferred_address) + <Member: dave.person@example.com + on aardvark@example.com as MemberRole.member> diff --git a/src/mailman/model/docs/membership.txt b/src/mailman/model/docs/membership.txt index 6f5b82622..8a3f0da16 100644 --- a/src/mailman/model/docs/membership.txt +++ b/src/mailman/model/docs/membership.txt @@ -51,7 +51,7 @@ her. >>> from mailman.interfaces.member import MemberRole >>> address_1 = list(user_1.addresses)[0] - >>> address_1.subscribe(mlist, MemberRole.owner) + >>> mlist.subscribe(address_1, MemberRole.owner) <Member: Anne Person <aperson@example.com> on test@example.com as MemberRole.owner> >>> dump_list(member.address for member in mlist.owners.members) @@ -73,7 +73,7 @@ Bart becomes a moderator of the list. >>> print user_2 <User "Bart Person" (...) at ...> >>> address_2 = list(user_2.addresses)[0] - >>> address_2.subscribe(mlist, MemberRole.moderator) + >>> mlist.subscribe(address_2, MemberRole.moderator) <Member: Bart Person <bperson@example.com> on test@example.com as MemberRole.moderator> >>> dump_list(member.address for member in mlist.moderators.members) @@ -102,7 +102,7 @@ role. >>> user_3 = user_manager.create_user( ... 'cperson@example.com', 'Cris Person') >>> address_3 = list(user_3.addresses)[0] - >>> member = address_3.subscribe(mlist, MemberRole.member) + >>> member = mlist.subscribe(address_3, MemberRole.member) >>> member <Member: Cris Person <cperson@example.com> on test@example.com as MemberRole.member> @@ -125,7 +125,7 @@ It's easy to make the list administrators members of the mailing list too. >>> members = [] >>> for address in mlist.administrators.addresses: - ... member = address.subscribe(mlist, MemberRole.member) + ... member = mlist.subscribe(address, MemberRole.member) ... members.append(member) >>> dump_list(members, key=attrgetter('address.email')) <Member: Anne Person <aperson@example.com> on @@ -157,7 +157,7 @@ role. >>> user_6 = user_manager.create_user('fperson@example.com', 'Fred Person') >>> address_6 = list(user_6.addresses)[0] - >>> member_6 = address_6.subscribe(mlist, MemberRole.nonmember) + >>> member_6 = mlist.subscribe(address_6, MemberRole.nonmember) >>> member_6 <Member: Fred Person <fperson@example.com> on test@example.com as MemberRole.nonmember> @@ -233,7 +233,7 @@ Double subscriptions It is an error to subscribe someone to a list with the same role twice. - >>> address_1.subscribe(mlist, MemberRole.owner) + >>> mlist.subscribe(address_1, MemberRole.owner) Traceback (most recent call last): ... AlreadySubscribedError: aperson@example.com is already a MemberRole.owner diff --git a/src/mailman/model/docs/requests.txt b/src/mailman/model/docs/requests.txt index 2ff173422..bebb61259 100644 --- a/src/mailman/model/docs/requests.txt +++ b/src/mailman/model/docs/requests.txt @@ -716,12 +716,12 @@ notification. >>> from mailman.interfaces.member import MemberRole >>> user_1 = user_manager.create_user('gperson@example.com') >>> address_1 = list(user_1.addresses)[0] - >>> address_1.subscribe(mlist, MemberRole.member) + >>> mlist.subscribe(address_1, MemberRole.member) <Member: gperson@example.com on alist@example.com as MemberRole.member> >>> user_2 = user_manager.create_user('hperson@example.com') >>> address_2 = list(user_2.addresses)[0] - >>> address_2.subscribe(mlist, MemberRole.member) + >>> mlist.subscribe(address_2, MemberRole.member) <Member: hperson@example.com on alist@example.com as MemberRole.member> >>> id_5 = moderator.hold_unsubscription(mlist, 'gperson@example.com') diff --git a/src/mailman/model/docs/users.txt b/src/mailman/model/docs/users.txt index c8244c506..1813ef636 100644 --- a/src/mailman/model/docs/users.txt +++ b/src/mailman/model/docs/users.txt @@ -4,7 +4,8 @@ Users Users are entities that represent people. A user has a real name and a optional encoded password. A user may also have an optional preferences and a -set of addresses they control. +set of addresses they control. They can even have a *preferred address*, +i.e. one that they use by default. See `usermanager.txt`_ for examples of how to create, delete, and find users. @@ -142,6 +143,90 @@ But don't try to unlink the address from a user it's not linked to. AddressNotLinkedError: zperson@example.net +Preferred address +================= + +Users can register a preferred address. When subscribing to a mailing list, +unless some other address is explicitly specified, the user will be subscribed +with their preferred address. This allows them to change their preferred +address once, and have all their subscriptions automatically track this +change. + +By default, a user has no preferred address. + + >>> user_2 = user_manager.create_user() + >>> print user_2.preferred_address + None + +Even when a user registers an address, this address will not be set as the +preferred address. + + >>> anne = user_2.register('anne@example.com', 'Anne Person') + >>> print user_2.preferred_address + None + +The preferred address must be explicitly registered, however only verified +address may be registered as preferred. + + >>> anne + <Address: Anne Person <anne@example.com> [not verified] at ...> + >>> user_2.preferred_address = anne + Traceback (most recent call last): + ... + UnverifiedAddressError: Anne Person <anne@example.com> + +Once the address has been verified though, it can be set as the preferred +address, but only if the address is either controlled by the user or +uncontrolled. In the latter case, setting it as the preferred address makes +it controlled by the user. +:: + + >>> from mailman.utilities.datetime import now + >>> anne.verified_on = now() + >>> anne + <Address: Anne Person <anne@example.com> [verified] at ...> + >>> user_2.controls(anne.email) + True + >>> user_2.preferred_address = anne + >>> user_2.preferred_address + <Address: Anne Person <anne@example.com> [verified] at ...> + + >>> aperson = user_manager.create_address('aperson@example.com') + >>> user_2.controls(aperson.email) + False + >>> aperson.verified_on = now() + >>> user_2.preferred_address = aperson + >>> user_2.controls(aperson.email) + True + + >>> zperson = list(user_1.addresses)[0] + >>> zperson.verified_on = now() + >>> user_2.controls(zperson.email) + False + >>> user_1.controls(zperson.email) + True + >>> user_2.preferred_address = zperson + Traceback (most recent call last): + ... + AddressAlreadyLinkedError: Zoe Person <zperson@example.com> + +A user can disavow their preferred address. + + >>> user_2.preferred_address + <Address: aperson@example.com [verified] at ...> + >>> del user_2.preferred_address + >>> print user_2.preferred_address + None + +The preferred address always shows up in the set of addresses controlled by +this user. + + >>> for address in user_2.addresses: + ... print address.email + anne@example.com + aperson@example.com + + Users and preferences ===================== @@ -207,14 +292,14 @@ membership role. >>> mlist_3 = create_list('xtest_3@example.com') >>> from mailman.interfaces.member import MemberRole - >>> com.subscribe(mlist_1, MemberRole.member) + >>> mlist_1.subscribe(com, MemberRole.member) <Member: Zoe Person <zperson@example.com> on xtest_1@example.com as MemberRole.member> - >>> org.subscribe(mlist_2, MemberRole.member) + >>> mlist_2.subscribe(org, MemberRole.member) <Member: zperson@example.org on xtest_2@example.com as MemberRole.member> - >>> org.subscribe(mlist_2, MemberRole.owner) + >>> mlist_2.subscribe(org, MemberRole.owner) <Member: zperson@example.org on xtest_2@example.com as MemberRole.owner> - >>> net.subscribe(mlist_3, MemberRole.moderator) + >>> mlist_3.subscribe(net, MemberRole.moderator) <Member: zperson@example.net on xtest_3@example.com as MemberRole.moderator> diff --git a/src/mailman/model/mailinglist.py b/src/mailman/model/mailinglist.py index 488b6da3d..2b205775e 100644 --- a/src/mailman/model/mailinglist.py +++ b/src/mailman/model/mailinglist.py @@ -38,15 +38,19 @@ from zope.interface import implements from mailman.config import config from mailman.database.model import Model from mailman.database.types import Enum +from mailman.interfaces.address import IAddress from mailman.interfaces.domain import IDomainManager from mailman.interfaces.languages import ILanguageManager from mailman.interfaces.mailinglist import ( IAcceptableAlias, IAcceptableAliasSet, IMailingList, Personalization) -from mailman.interfaces.member import MemberRole +from mailman.interfaces.member import AlreadySubscribedError, MemberRole from mailman.interfaces.mime import FilterType +from mailman.interfaces.user import IUser from mailman.model import roster from mailman.model.digests import OneLastDigest +from mailman.model.member import Member from mailman.model.mime import ContentFilter +from mailman.model.preferences import Preferences from mailman.utilities.filesystem import makedirs from mailman.utilities.string import expand @@ -441,6 +445,36 @@ class MailingList(Model): raise TypeError( 'Undefined MemberRole: {0}'.format(role)) + def subscribe(self, subscriber, role=MemberRole.member): + """See `IMailingList`.""" + store = Store.of(self) + if IAddress.providedBy(subscriber): + member = store.find( + Member, + Member.role == role, + Member.mailing_list == self.fqdn_listname, + Member._address == subscriber).one() + if member: + raise AlreadySubscribedError( + self.fqdn_listname, subscriber.email, role) + elif IUser.providedBy(subscriber): + member = store.find( + Member, + Member.role == role, + Member.mailing_list == self.fqdn_listname, + Member._user == subscriber).one() + if member: + raise AlreadySubscribedError( + self.fqdn_listname, subscriber, role) + else: + raise ValueError('subscriber must be an address or user') + member = Member(role=role, + mailing_list=self.fqdn_listname, + subscriber=subscriber) + member.preferences = Preferences() + store.add(member) + return member + class AcceptableAlias(Model): diff --git a/src/mailman/model/member.py b/src/mailman/model/member.py index d32c586d9..50ae921c3 100644 --- a/src/mailman/model/member.py +++ b/src/mailman/model/member.py @@ -33,8 +33,10 @@ from mailman.core.constants import system_preferences from mailman.database.model import Model from mailman.database.types import Enum from mailman.interfaces.action import Action +from mailman.interfaces.address import IAddress from mailman.interfaces.listmanager import IListManager from mailman.interfaces.member import IMember, MemberRole +from mailman.interfaces.user import IUser from mailman.interfaces.usermanager import IUserManager @@ -48,14 +50,25 @@ class Member(Model): moderation_action = Enum() address_id = Int() - address = Reference(address_id, 'Address.id') + _address = Reference(address_id, 'Address.id') preferences_id = Int() preferences = Reference(preferences_id, 'Preferences.id') + user_id = Int() + _user = Reference(user_id, 'User.id') - def __init__(self, role, mailing_list, address): + def __init__(self, role, mailing_list, subscriber): self.role = role self.mailing_list = mailing_list - self.address = address + if IAddress.providedBy(subscriber): + self._address = subscriber + # Look this up dynamically. + self._user = None + elif IUser.providedBy(subscriber): + self._user = subscriber + # Look this up dynamically. + self._address = None + else: + raise ValueError('subscriber must be a user or address') if role in (MemberRole.owner, MemberRole.moderator): self.moderation_action = Action.accept elif role is MemberRole.member: @@ -72,8 +85,16 @@ class Member(Model): self.address, self.mailing_list, self.role) @property + def address(self): + return (self._user.preferred_address + if self._address is None + else self._address) + + @property def user(self): - return getUtility(IUserManager).get_user(self.address.email) + return (self._user + if self._address is None + else getUtility(IUserManager).get_user(self._address.email)) def _lookup(self, preference): pref = getattr(self.preferences, preference) diff --git a/src/mailman/model/user.py b/src/mailman/model/user.py index f0048c5f4..7c4c15d36 100644 --- a/src/mailman/model/user.py +++ b/src/mailman/model/user.py @@ -32,7 +32,7 @@ from mailman.config import config from mailman.database.model import Model from mailman.interfaces.address import ( AddressAlreadyLinkedError, AddressNotLinkedError) -from mailman.interfaces.user import IUser +from mailman.interfaces.user import IUser, UnverifiedAddressError from mailman.model.address import Address from mailman.model.preferences import Preferences from mailman.model.roster import Memberships @@ -53,6 +53,8 @@ class User(Model): _created_on = DateTime() addresses = ReferenceSet(id, 'Address.user_id') + _preferred_address_id = Int() + _preferred_address = Reference(_preferred_address_id, 'Address.id') preferences_id = Int() preferences = Reference(preferences_id, 'Preferences.id') @@ -93,6 +95,30 @@ class User(Model): raise AddressNotLinkedError(address) address.user = None + @property + def preferred_address(self): + """See `IUser`.""" + return self._preferred_address + + @preferred_address.setter + def preferred_address(self, address): + """See `IUser`.""" + if address.verified_on is None: + raise UnverifiedAddressError(address) + if self.controls(address.email): + # This user already controls the email address. + pass + elif address.user is None: + self.link(address) + elif address.user != self: + raise AddressAlreadyLinkedError(address) + self._preferred_address = address + + @preferred_address.deleter + def preferred_address(self): + """See `IUser`.""" + self._preferred_address = None + def controls(self, email): """See `IUser`.""" found = config.db.store.find(Address, email=email) diff --git a/src/mailman/mta/docs/decorating.txt b/src/mailman/mta/docs/decorating.txt index 0bc9649c8..05196eb78 100644 --- a/src/mailman/mta/docs/decorating.txt +++ b/src/mailman/mta/docs/decorating.txt @@ -69,17 +69,17 @@ list. >>> anne = user_manager.create_user('aperson@example.com', 'Anne Person') >>> anne.password = b'AAA' - >>> list(anne.addresses)[0].subscribe(mlist, MemberRole.member) + >>> mlist.subscribe(list(anne.addresses)[0], MemberRole.member) <Member: Anne Person <aperson@example.com> ... >>> bart = user_manager.create_user('bperson@example.com', 'Bart Person') >>> bart.password = b'BBB' - >>> list(bart.addresses)[0].subscribe(mlist, MemberRole.member) + >>> mlist.subscribe(list(bart.addresses)[0], MemberRole.member) <Member: Bart Person <bperson@example.com> ... >>> cris = user_manager.create_user('cperson@example.com', 'Cris Person') >>> cris.password = b'CCC' - >>> list(cris.addresses)[0].subscribe(mlist, MemberRole.member) + >>> mlist.subscribe(list(cris.addresses)[0], MemberRole.member) <Member: Cris Person <cperson@example.com> ... The decorations happen when the message is delivered. diff --git a/src/mailman/pipeline/docs/acknowledge.txt b/src/mailman/pipeline/docs/acknowledge.txt index 5e4240626..b2ce11b7f 100644 --- a/src/mailman/pipeline/docs/acknowledge.txt +++ b/src/mailman/pipeline/docs/acknowledge.txt @@ -29,7 +29,7 @@ Subscribe a user to the mailing list. >>> from mailman.interfaces.member import MemberRole >>> user_1 = user_manager.create_user('aperson@example.com') >>> address_1 = list(user_1.addresses)[0] - >>> address_1.subscribe(mlist, MemberRole.member) + >>> mlist.subscribe(address_1, MemberRole.member) <Member: aperson@example.com on _xtest@example.com as MemberRole.member> @@ -82,7 +82,7 @@ will be sent. >>> user_2 = user_manager.create_user('dperson@example.com') >>> address_2 = list(user_2.addresses)[0] - >>> address_2.subscribe(mlist, MemberRole.member) + >>> mlist.subscribe(address_2, MemberRole.member) <Member: dperson@example.com on _xtest@example.com as MemberRole.member> >>> handler.process(mlist, msg, diff --git a/src/mailman/pipeline/docs/avoid-duplicates.txt b/src/mailman/pipeline/docs/avoid-duplicates.txt index bd753f9e9..1e46793c2 100644 --- a/src/mailman/pipeline/docs/avoid-duplicates.txt +++ b/src/mailman/pipeline/docs/avoid-duplicates.txt @@ -19,8 +19,8 @@ Create some members we're going to use. >>> address_b = user_manager.create_address('bperson@example.com') >>> from mailman.interfaces.member import MemberRole - >>> member_a = address_a.subscribe(mlist, MemberRole.member) - >>> member_b = address_b.subscribe(mlist, MemberRole.member) + >>> member_a = mlist.subscribe(address_a, MemberRole.member) + >>> member_b = mlist.subscribe(address_b, MemberRole.member) >>> # This is the message metadata dictionary as it would be produced by >>> # the CalcRecips handler. >>> recips = dict( diff --git a/src/mailman/pipeline/docs/calc-recips.txt b/src/mailman/pipeline/docs/calc-recips.txt index efa1bc9c7..6dca85816 100644 --- a/src/mailman/pipeline/docs/calc-recips.txt +++ b/src/mailman/pipeline/docs/calc-recips.txt @@ -26,12 +26,12 @@ start out with. First, create a bunch of addresses... ...then subscribe these addresses to the mailing list as members... >>> from mailman.interfaces.member import MemberRole - >>> member_a = address_a.subscribe(mlist, MemberRole.member) - >>> member_b = address_b.subscribe(mlist, MemberRole.member) - >>> member_c = address_c.subscribe(mlist, MemberRole.member) - >>> member_d = address_d.subscribe(mlist, MemberRole.member) - >>> member_e = address_e.subscribe(mlist, MemberRole.member) - >>> member_f = address_f.subscribe(mlist, MemberRole.member) + >>> member_a = mlist.subscribe(address_a, MemberRole.member) + >>> member_b = mlist.subscribe(address_b, MemberRole.member) + >>> member_c = mlist.subscribe(address_c, MemberRole.member) + >>> member_d = mlist.subscribe(address_d, MemberRole.member) + >>> member_e = mlist.subscribe(address_e, MemberRole.member) + >>> member_f = mlist.subscribe(address_f, MemberRole.member) ...then make some of the members digest members. diff --git a/src/mailman/pipeline/docs/file-recips.txt b/src/mailman/pipeline/docs/file-recips.txt index c994f820e..7d157ccc5 100644 --- a/src/mailman/pipeline/docs/file-recips.txt +++ b/src/mailman/pipeline/docs/file-recips.txt @@ -93,7 +93,7 @@ in the recipients list. ... 'cperson@example.com') >>> from mailman.interfaces.member import MemberRole - >>> address_1.subscribe(mlist, MemberRole.member) + >>> mlist.subscribe(address_1, MemberRole.member) <Member: cperson@example.com on _xtest@example.com as MemberRole.member> >>> msg = message_from_string("""\ diff --git a/src/mailman/queue/docs/digester.txt b/src/mailman/queue/docs/digester.txt index 56aaf17c5..285b2072a 100644 --- a/src/mailman/queue/docs/digester.txt +++ b/src/mailman/queue/docs/digester.txt @@ -496,7 +496,7 @@ digests, or MIME digests. >>> from mailman.interfaces.member import DeliveryMode, MemberRole >>> def subscribe(email, mode): ... address = user_manager.create_address(email) - ... member = address.subscribe(mlist, MemberRole.member) + ... member = mlist.subscribe(address, MemberRole.member) ... member.preferences.delivery_mode = mode ... return member diff --git a/src/mailman/rules/moderation.py b/src/mailman/rules/moderation.py index 733edd70c..5377b14a2 100644 --- a/src/mailman/rules/moderation.py +++ b/src/mailman/rules/moderation.py @@ -86,7 +86,7 @@ class NonmemberModeration: address = user_manager.get_address(sender) assert address is not None, ( 'Posting address is not registered: {0}'.format(sender)) - address.subscribe(mlist, MemberRole.nonmember) + mlist.subscribe(address, MemberRole.nonmember) # Do nonmember moderation check. for sender in msg.senders: nonmember = mlist.nonmembers.get_member(sender) diff --git a/src/mailman/testing/helpers.py b/src/mailman/testing/helpers.py index 2ba778813..bd52d5d6e 100644 --- a/src/mailman/testing/helpers.py +++ b/src/mailman/testing/helpers.py @@ -272,12 +272,12 @@ def subscribe(mlist, first_name, role=MemberRole.member): if address is None: person = user_manager.create_user(email, full_name) preferred_address = list(person.addresses)[0] - preferred_address.subscribe(mlist, role) + mlist.subscribe(preferred_address, role) else: - address.subscribe(mlist, role) + mlist.subscribe(address, role) else: preferred_address = list(person.addresses)[0] - preferred_address.subscribe(mlist, role) + mlist.subscribe(preferred_address, role) config.db.commit() |
