summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBarry Warsaw2007-06-15 00:50:40 -0400
committerBarry Warsaw2007-06-15 00:50:40 -0400
commit067f871fdcaf51a0de8a1468006d3bad2e3a9a24 (patch)
tree8404972f48ad55b4a2353c2f33369fb51351e500
parent125f16ea72934e4dd18529a597b155c0aaca9ff6 (diff)
downloadmailman-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.py2
-rw-r--r--Mailman/database/model/mailinglist.py3
-rw-r--r--Mailman/database/model/member.py20
-rw-r--r--Mailman/database/model/profile.py11
-rw-r--r--Mailman/database/model/roster.py16
-rw-r--r--Mailman/database/model/user.py15
-rw-r--r--Mailman/database/types.py4
-rw-r--r--Mailman/database/usermanager.py2
-rw-r--r--Mailman/docs/usermanager.txt9
-rw-r--r--Mailman/docs/users.txt145
-rw-r--r--Mailman/interfaces/address.py3
-rw-r--r--Mailman/interfaces/profile.py38
-rw-r--r--Mailman/interfaces/user.py19
-rw-r--r--Mailman/testing/test_user.py4
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():