diff options
| author | Barry Warsaw | 2007-06-15 00:50:40 -0400 |
|---|---|---|
| committer | Barry Warsaw | 2007-06-15 00:50:40 -0400 |
| commit | 067f871fdcaf51a0de8a1468006d3bad2e3a9a24 (patch) | |
| tree | 8404972f48ad55b4a2353c2f33369fb51351e500 | |
| parent | 125f16ea72934e4dd18529a597b155c0aaca9ff6 (diff) | |
| download | mailman-067f871fdcaf51a0de8a1468006d3bad2e3a9a24.tar.gz mailman-067f871fdcaf51a0de8a1468006d3bad2e3a9a24.tar.zst mailman-067f871fdcaf51a0de8a1468006d3bad2e3a9a24.zip | |
Update the IUser interface and tests, specifically as it relates to
preferences. IAddresses, IUsers, and IMembers all get preferences by default,
althoughthe attributes of these preferences are None by default.
IMailingLists don't get preferences by default though; because these live in
the user database, we can't cross-polinate them in the mailing lists. We'll
figure something out later for these.
IUser.register(): Add this method which registers and links an address to the
user.
Allow EnumType database columns to accept and return Nones. This is useful
for when the columns are not defined NOT NULL.
Update doctests.
Removed teh hide_address preference. I can't think of a reason not to want to
hide addresses for everyone.
| -rw-r--r-- | Mailman/database/model/address.py | 2 | ||||
| -rw-r--r-- | Mailman/database/model/mailinglist.py | 3 | ||||
| -rw-r--r-- | Mailman/database/model/member.py | 20 | ||||
| -rw-r--r-- | Mailman/database/model/profile.py | 11 | ||||
| -rw-r--r-- | Mailman/database/model/roster.py | 16 | ||||
| -rw-r--r-- | Mailman/database/model/user.py | 15 | ||||
| -rw-r--r-- | Mailman/database/types.py | 4 | ||||
| -rw-r--r-- | Mailman/database/usermanager.py | 2 | ||||
| -rw-r--r-- | Mailman/docs/usermanager.txt | 9 | ||||
| -rw-r--r-- | Mailman/docs/users.txt | 145 | ||||
| -rw-r--r-- | Mailman/interfaces/address.py | 3 | ||||
| -rw-r--r-- | Mailman/interfaces/profile.py | 38 | ||||
| -rw-r--r-- | Mailman/interfaces/user.py | 19 | ||||
| -rw-r--r-- | Mailman/testing/test_user.py | 4 |
14 files changed, 171 insertions, 120 deletions
diff --git a/Mailman/database/model/address.py b/Mailman/database/model/address.py index 450c40235..7500197a8 100644 --- a/Mailman/database/model/address.py +++ b/Mailman/database/model/address.py @@ -50,8 +50,10 @@ class Address(Entity): def subscribe(self, mlist, role): from Mailman.database.model import Member + from Mailman.database.model import Preferences # This member has no preferences by default. member = Member(role=role, mailing_list=mlist.fqdn_listname, address=self) + member.preferences = Preferences() return member diff --git a/Mailman/database/model/mailinglist.py b/Mailman/database/model/mailinglist.py index 1e7823911..edd2eab0d 100644 --- a/Mailman/database/model/mailinglist.py +++ b/Mailman/database/model/mailinglist.py @@ -22,8 +22,6 @@ from Mailman.Utils import fqdn_listname, split_listname from Mailman.configuration import config from Mailman.interfaces import * -PREFERENCE_KIND = 'Mailman.database.model.profile.Preferences' - class MailingList(Entity): @@ -156,7 +154,6 @@ class MailingList(Entity): has_field('unsubscribe_policy', Integer), has_field('welcome_msg', Unicode), # Relationships - belongs_to('preferences', of_kind=PREFERENCE_KIND) ## has_and_belongs_to_many( ## 'available_languages', ## of_kind='Mailman.database.model.languages.Language') diff --git a/Mailman/database/model/member.py b/Mailman/database/model/member.py index bcf859cf4..89a98d327 100644 --- a/Mailman/database/model/member.py +++ b/Mailman/database/model/member.py @@ -19,7 +19,6 @@ from elixir import * from zope.interface import implements from Mailman.Utils import split_listname -from Mailman.constants import SystemDefaultPreferences from Mailman.database.types import EnumType from Mailman.interfaces import IMember, IPreferences @@ -36,27 +35,10 @@ class Member(Entity): has_field('mailing_list', Unicode) # Relationships belongs_to('address', of_kind=ADDRESS_KIND) - belongs_to('_preferences', of_kind=PREFERENCE_KIND) + belongs_to('preferences', of_kind=PREFERENCE_KIND) # Options using_options(shortnames=True) def __repr__(self): return '<Member: %s on %s as %s>' % ( self.address, self.mailing_list, self.role) - - @property - def preferences(self): - from Mailman.database.model import MailingList - if self._preferences: - return self._preferences - if self.address.preferences: - return self.address.preferences - # It's possible this address isn't linked to a user. - if self.address.user and self.address.user.preferences: - return self.address.user.preferences - list_name, host_name = split_listname(self.mailing_list) - mlist = MailingList.get_by(list_name=list_name, - host_name=host_name) - if mlist.preferences: - return mlist.preferences - return SystemDefaultPreferences diff --git a/Mailman/database/model/profile.py b/Mailman/database/model/profile.py index 935ae08fb..33511f54b 100644 --- a/Mailman/database/model/profile.py +++ b/Mailman/database/model/profile.py @@ -19,7 +19,6 @@ from elixir import * from email.utils import formataddr from zope.interface import implements -from Mailman.constants import SystemDefaultPreferences as Prefs from Mailman.database.types import EnumType from Mailman.interfaces import IPreferences @@ -41,11 +40,5 @@ class Preferences(Entity): # Options using_options(shortnames=True) - def __init__(self): - super(Preferences, self).__init__() - self.acknowledge_posts = Prefs.acknowledge_posts - self.hide_address = Prefs.hide_address - self.preferred_language = Prefs.preferred_language - self.receive_list_copy = Prefs.receive_list_copy - self.receive_own_postings = Prefs.receive_own_postings - self.delivery_mode = Prefs.delivery_mode + def __repr__(self): + return '<Preferences object at %#x>' % id(self) diff --git a/Mailman/database/model/roster.py b/Mailman/database/model/roster.py index 03aa9efc3..ee50cddf0 100644 --- a/Mailman/database/model/roster.py +++ b/Mailman/database/model/roster.py @@ -25,6 +25,7 @@ moderator, and administrator roster filters. from zope.interface import implements from Mailman.constants import DeliveryMode, MemberRole +from Mailman.constants import SystemDefaultPreferences from Mailman.database.model import Member from Mailman.interfaces import IRoster @@ -137,6 +138,17 @@ class AdministratorRoster(AbstractRoster): +def _delivery_mode(member): + if member.preferences.delivery_mode is not None: + return member.preferences.delivery_mode + if member.address.preferences.delivery_mode is not None: + return member.address.preferences.delivery_mode + if (member.address.user and + member.address.user.preferences.delivery_mode is not None): + return member.address.user.preferences.delivery_mode + return SystemDefaultPreferences.delivery_mode + + class RegularMemberRoster(AbstractRoster): """Return all the regular delivery members of a list.""" @@ -149,7 +161,7 @@ class RegularMemberRoster(AbstractRoster): # that have a regular delivery mode. for member in Member.select_by(mailing_list=self._mlist.fqdn_listname, role=MemberRole.member): - if member.preferences.delivery_mode == DeliveryMode.regular: + if _delivery_mode(member) == DeliveryMode.regular: yield member @@ -174,5 +186,5 @@ class DigestMemberRoster(AbstractRoster): # that have one of the digest delivery modes. for member in Member.select_by(mailing_list=self._mlist.fqdn_listname, role=MemberRole.member): - if member.preferences.delivery_mode in _digest_modes: + if _delivery_mode(member) in _digest_modes: yield member diff --git a/Mailman/database/model/user.py b/Mailman/database/model/user.py index 45fcbfdd3..9419f181d 100644 --- a/Mailman/database/model/user.py +++ b/Mailman/database/model/user.py @@ -21,6 +21,7 @@ from zope.interface import implements from Mailman import Errors from Mailman.database.model import Address +from Mailman.database.model import Preferences from Mailman.interfaces import IUser ADDRESS_KIND = 'Mailman.database.model.address.Address' @@ -57,3 +58,17 @@ class User(Entity): def controls(self, address): found = Address.get_by(address=address) return bool(found and found.user is self) + + def register(self, address, real_name=None): + # First, see if the address already exists + addrobj = Address.get_by(address=address) + if addrobj is None: + if real_name is None: + real_name = '' + addrobj = Address(address=address, real_name=real_name) + # Link the address to the user if it is not already linked. + if addrobj.user is not None: + raise Errors.AddressAlreadyLinkedError(addrobj) + addrobj.user = self + self.addresses.append(addrobj) + return addrobj diff --git a/Mailman/database/types.py b/Mailman/database/types.py index 00ad29559..79ea8767d 100644 --- a/Mailman/database/types.py +++ b/Mailman/database/types.py @@ -28,11 +28,15 @@ class EnumType(types.TypeDecorator): impl = types.String def convert_bind_param(self, value, engine): + if value is None: + return None return '%s:%s.%d' % (value.enumclass.__module__, value.enumclass.__name__, int(value)) def convert_result_value(self, value, engine): + if value is None: + return None path, intvalue = value.rsplit(':', 1) modulename, classname = intvalue.rsplit('.', 1) __import__(modulename) diff --git a/Mailman/database/usermanager.py b/Mailman/database/usermanager.py index 2f12188aa..569ca2152 100644 --- a/Mailman/database/usermanager.py +++ b/Mailman/database/usermanager.py @@ -40,6 +40,7 @@ class UserManager(object): user.real_name = (real_name if real_name is not None else '') if address: user.link(Address(address=address, real_name=user.real_name)) + user.preferences = Preferences() return user def delete_user(self, user): @@ -61,6 +62,7 @@ class UserManager(object): if real_name is None: real_name = '' address = Address(address=address, real_name=real_name) + address.preferences = Preferences() return address def delete_address(self, address): diff --git a/Mailman/docs/usermanager.txt b/Mailman/docs/usermanager.txt index 389c68956..1f863b606 100644 --- a/Mailman/docs/usermanager.txt +++ b/Mailman/docs/usermanager.txt @@ -20,7 +20,7 @@ Creating users There are several ways you can create a user object. The simplest is to create a 'blank' user by not providing an address or real name at creation time. This user will have an empty string as their real name, but will not -have a password or preferences. +have a password. >>> from Mailman.interfaces import IUser >>> user = mgr.create_user() @@ -31,11 +31,14 @@ have a password or preferences. [] >>> user.real_name '' - >>> print user.preferences - None >>> print user.password None +The user has preferences, but none of them will be specified. + + >>> print user.preferences + <Preferences ...> + A user can be assigned a real name. >>> user.real_name = 'Anne Person' diff --git a/Mailman/docs/users.txt b/Mailman/docs/users.txt index a65527eff..2b60ed0bc 100644 --- a/Mailman/docs/users.txt +++ b/Mailman/docs/users.txt @@ -17,9 +17,9 @@ User data Users may have a real name and a password. - >>> user = mgr.create_user() - >>> user.password = 'my password' - >>> user.real_name = 'Zoe Person' + >>> user_1 = mgr.create_user() + >>> user_1.password = 'my password' + >>> user_1.real_name = 'Zoe Person' >>> flush() >>> sorted(user.real_name for user in mgr.users) ['Zoe Person'] @@ -28,8 +28,8 @@ Users may have a real name and a password. The password and real name can be changed at any time. - >>> user.real_name = 'Zoe X. Person' - >>> user.password = 'another password' + >>> user_1.real_name = 'Zoe X. Person' + >>> user_1.password = 'another password' >>> flush() >>> sorted(user.real_name for user in mgr.users) ['Zoe X. Person'] @@ -41,101 +41,124 @@ Users addresses --------------- One of the pieces of information that a user links to is a set of email -addresses, in the form of IAddress objects. A user can control many -addresses, but addresses may be control by only one user. +addresses they control, in the form of IAddress objects. A user can control +many addresses, but addresses may be controlled by only one user. -Given a user and an address, you can link the two together. +The easiest way to link a user to an address is to just register the new +address on a user object. - >>> roster = mgr.get_roster('') - >>> address = roster.create('aperson@example.com', 'Anne Person') - >>> user.link(address) + >>> user_1.register('zperson@example.com', 'Zoe Person') + <Address: Zoe Person <zperson@example.com> [not verified]> + >>> user_1.register('zperson@example.org') + <Address: zperson@example.org [not verified]> >>> flush() - >>> sorted(address.address for address in user.addresses) - ['aperson@example.com'] + >>> sorted(address.address for address in user_1.addresses) + ['zperson@example.com', 'zperson@example.org'] + >>> sorted(address.real_name for address in user_1.addresses) + ['', 'Zoe Person'] + +You can also create the address separately and then link it to the user. + + >>> address_1 = mgr.create_address('zperson@example.net') + >>> user_1.link(address_1) + >>> flush() + >>> sorted(address.address for address in user_1.addresses) + ['zperson@example.com', 'zperson@example.net', 'zperson@example.org'] + >>> sorted(address.real_name for address in user_1.addresses) + ['', '', 'Zoe Person'] But don't try to link an address to more than one user. >>> another_user = mgr.create_user() - >>> another_user.link(address) + >>> another_user.link(address_1) Traceback (most recent call last): ... - AddressAlreadyLinkedError: Anne Person <aperson@example.com> + AddressAlreadyLinkedError: zperson@example.net You can also ask whether a given user controls a given address. - >>> user.controls(address) + >>> user_1.controls(address_1.address) True - >>> not_my_address = roster.create('bperson@example.com', 'Ben Person') - >>> user.controls(not_my_address) + >>> user_1.controls('bperson@example.com') False Given a text email address, the user manager can find the user that controls that address. - >>> mgr.get_user('aperson@example.com') is user + >>> mgr.get_user('zperson@example.com') is user_1 True - >>> mgr.get_user('bperson@example.com') is None + >>> mgr.get_user('zperson@example.net') is user_1 True + >>> mgr.get_user('zperson@example.org') is user_1 + True + >>> print mgr.get_user('bperson@example.com') + None Addresses can also be unlinked from a user. - >>> user.unlink(address) - >>> user.controls(address) + >>> user_1.unlink(address_1) + >>> user_1.controls('zperson@example.net') False - >>> mgr.get_user('aperson@example.com') is None - True + >>> print mgr.get_user('aperson@example.net') + None But don't try to unlink the address from a user it's not linked to. - >>> user.unlink(address) + >>> user_1.unlink(address_1) Traceback (most recent call last): ... - AddressNotLinkedError: Anne Person <aperson@example.com> - >>> another_user.unlink(address) + AddressNotLinkedError: zperson@example.net + >>> another_user.unlink(address_1) Traceback (most recent call last): ... - AddressNotLinkedError: Anne Person <aperson@example.com> - >>> mgr.delete_user(another_user) + AddressNotLinkedError: zperson@example.net -Users and profiles ------------------- +Users and preferences +--------------------- -Users always have a default profile. +This is a helper function for the following section. - >>> from Mailman.interfaces import IProfile - >>> IProfile.providedBy(user.profile) - True - -A profile is a set of preferences such as whether the user wants to receive an -acknowledgment of all of their posts to a mailing list... + >>> def show_prefs(prefs): + ... print 'acknowledge_posts :', prefs.acknowledge_posts + ... print 'preferred_language :', prefs.preferred_language + ... print 'receive_list_copy :', prefs.receive_list_copy + ... print 'receive_own_postings :', prefs.receive_own_postings + ... print 'delivery_mode :', prefs.delivery_mode - >>> user.profile.acknowledge_posts - False +Users have preferences, but these preferences have no default settings. -...whether the user wants to hide their email addresses on web pages and in -postings to the list... + >>> from Mailman.interfaces import IPreferences + >>> show_prefs(user_1.preferences) + acknowledge_posts : None + preferred_language : None + receive_list_copy : None + receive_own_postings : None + delivery_mode : None - >>> user.profile.hide_address - True +Some of these preferences are booleans and they can be set to True or False. -...the language code for the user's preferred language... - - >>> user.profile.preferred_language - 'en' - -...whether the user wants to receive the list's copy of a message if they are -explicitly named in one of the recipient headers... - - >>> user.profile.receive_list_copy - True - -...whether the user wants to receive a copy of their own postings... + >>> from Mailman.constants import DeliveryMode + >>> prefs = user_1.preferences + >>> prefs.acknowledge_posts = True + >>> prefs.preferred_language = 'it' + >>> prefs.receive_list_copy = False + >>> prefs.receive_own_postings = False + >>> prefs.delivery_mode = DeliveryMode.regular + >>> flush() + >>> show_prefs(user_1.preferences) + acknowledge_posts : True + preferred_language : it + receive_list_copy : False + receive_own_postings : False + delivery_mode : DeliveryMode.regular - >>> user.profile.receive_own_postings - True -...and the preferred delivery method. +Clean up +-------- - >>> print user.profile.delivery_mode - DeliveryMode.regular + >>> for user in mgr.users: + ... mgr.delete_user(user) + >>> flush() + >>> sorted(mgr.users) + [] diff --git a/Mailman/interfaces/address.py b/Mailman/interfaces/address.py index 1363d56ab..c367774c3 100644 --- a/Mailman/interfaces/address.py +++ b/Mailman/interfaces/address.py @@ -48,3 +48,6 @@ class IAddress(Interface): role is a Mailman.constants.MemberRole enum. """ + + preferences = Attribute( + """This address's preferences.""") diff --git a/Mailman/interfaces/profile.py b/Mailman/interfaces/profile.py index a0b5131cb..17cfebae6 100644 --- a/Mailman/interfaces/profile.py +++ b/Mailman/interfaces/profile.py @@ -25,29 +25,37 @@ class IPreferences(Interface): """Delivery related information.""" acknowledge_posts = Attribute( - """Boolean specifying whether to send an acknowledgment receipt for - every posting to the mailing list. - """) + """Send an acknowledgment for every posting? - hide_address = Attribute( - """Boolean specifying whether to hide this email address from fellow - list members. - """) + This preference can be True, False, or None. True means the user is + sent a receipt for each message they send to the mailing list. False + means that no receipt is sent. None means no preference is + specified.""") preferred_language = Attribute( - """Preferred language for interacting with a mailing list.""") + """The preferred language for interacting with a mailing list. + + This is either the language code for the preferred language, or None + meaning no preferred language is specified.""") receive_list_copy = Attribute( - """Boolean specifying whether to receive a list copy if the user is - explicitly named in one of the recipient headers. - """) + """Should an explicit recipient receive a list copy? + + When a list member is explicitly named in a message's recipients + (e.g. the To or CC headers), and this preference is True, the + recipient will still receive a list copy of the message. When False, + this list copy will be suppressed. None means no preference is + specified.""") receive_own_postings = Attribute( - """Boolean specifying whether to receive a list copy of the user's own - postings to the mailing list. - """) + """Should the poster get a list copy of their own messages? + + When this preference is True, a list copy will be sent to the poster + of all messages. When False, this list copy will be suppressed. None + means no preference is specified.""") delivery_mode = Attribute( """The preferred delivery mode. - This is an enum constant of the type DeliveryMode.""") + This is an enum constant of the type DeliveryMode. It may also be + None which means that no preference is specified.""") diff --git a/Mailman/interfaces/user.py b/Mailman/interfaces/user.py index 9e89c2416..f7647597d 100644 --- a/Mailman/interfaces/user.py +++ b/Mailman/interfaces/user.py @@ -30,19 +30,21 @@ class IUser(Interface): password = Attribute( """This user's password information.""") - preferences = Attribute( - """The default preferences for this user.""") - addresses = Attribute( """An iterator over all the IAddresses controlled by this user.""") - def register(address): + def register(address, real_name=None): """Register the given email address and link it to this user. In this case, 'address' is a text email address, not an IAddress - object. Raises AddressAlreadyLinkedError if this IAddress is already - linked to another user. If the corresponding IAddress already exists - but is not linked, then it is simply linked to the user. + object. If real_name is not given, the empty string is used. + + Raises AddressAlreadyLinkedError if this IAddress is already linked to + another user. If the corresponding IAddress already exists but is not + linked, then it is simply linked to the user, in which case + real_name is ignored. + + Return the new IAddress object. """ def link(address): @@ -66,3 +68,6 @@ class IUser(Interface): 'address' is a text email address. This method returns true if the user controls the given email address, otherwise false. """ + + preferences = Attribute( + """This user's preferences.""") diff --git a/Mailman/testing/test_user.py b/Mailman/testing/test_user.py index 1c075a164..59b686795 100644 --- a/Mailman/testing/test_user.py +++ b/Mailman/testing/test_user.py @@ -20,7 +20,9 @@ import doctest import unittest -options = doctest.ELLIPSIS | doctest.NORMALIZE_WHITESPACE +options = (doctest.ELLIPSIS + | doctest.NORMALIZE_WHITESPACE + | doctest.REPORT_NDIFF) def test_suite(): |
