summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/mailman/database/mailman.sql421
-rw-r--r--src/mailman/interfaces/mailinglist.py14
-rw-r--r--src/mailman/model/docs/mailinglist.txt51
-rw-r--r--src/mailman/model/mailinglist.py34
-rw-r--r--src/mailman/model/member.py29
5 files changed, 324 insertions, 225 deletions
diff --git a/src/mailman/database/mailman.sql b/src/mailman/database/mailman.sql
index 64dc21cde..5af8656c2 100644
--- a/src/mailman/database/mailman.sql
+++ b/src/mailman/database/mailman.sql
@@ -1,13 +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,
@@ -23,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
@@ -83,137 +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,
@@ -225,54 +231,59 @@ CREATE TABLE onelastdigest (
CONSTRAINT onelastdigest_address_id_fk
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,
- _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)
-);
+ 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);
@@ -286,4 +297,4 @@ CREATE TABLE ban (
email TEXT,
mailing_list TEXT,
PRIMARY KEY (id)
-);
+ );
diff --git a/src/mailman/interfaces/mailinglist.py b/src/mailman/interfaces/mailinglist.py
index 32f21a792..23d21cd34 100644
--- a/src/mailman/interfaces/mailinglist.py
+++ b/src/mailman/interfaces/mailinglist.py
@@ -232,18 +232,20 @@ class IMailingList(Interface):
:rtype: Roster
"""
- def subscribe(address, role):
- """Subscribe the given address to the mailing list.
+ def subscribe(subscriber, role):
+ """Subscribe the given address or user to the mailing list.
- :param address: The address to subscribe.
- :type address: `IAddress`
+ :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 is already subscribed
- to the mailing list with the given role.
+ :raises AlreadySubscribedError: If the address or user is already
+ subscribed to the mailing list with the given role.
"""
# Posting history.
diff --git a/src/mailman/model/docs/mailinglist.txt b/src/mailman/model/docs/mailinglist.txt
index cc95c077c..ec9e37ee0 100644
--- a/src/mailman/model/docs/mailinglist.txt
+++ b/src/mailman/model/docs/mailinglist.txt
@@ -101,3 +101,54 @@ All rosters can also be accessed indirectly.
... print member
<Member: cperson@example.com on aardvark@example.com
as MemberRole.moderator>
+
+
+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/mailinglist.py b/src/mailman/model/mailinglist.py
index 895ff1f0e..2b205775e 100644
--- a/src/mailman/model/mailinglist.py
+++ b/src/mailman/model/mailinglist.py
@@ -38,12 +38,14 @@ 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 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
@@ -443,20 +445,32 @@ class MailingList(Model):
raise TypeError(
'Undefined MemberRole: {0}'.format(role))
- def subscribe(self, address, role=MemberRole.member):
+ def subscribe(self, subscriber, role=MemberRole.member):
"""See `IMailingList`."""
store = Store.of(self)
- member = store.find(
- Member,
- Member.role == role,
- Member.mailing_list == self.fqdn_listname,
- Member.address == address).one()
- if member:
- raise AlreadySubscribedError(
- self.fqdn_listname, address.email, role)
+ 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,
- address=address)
+ subscriber=subscriber)
member.preferences = Preferences()
store.add(member)
return member
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)