summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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():