summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorBarry Warsaw2011-01-01 11:28:29 -0500
committerBarry Warsaw2011-01-01 11:28:29 -0500
commit3f1f5a2826feb9c5fb202ae266ba7f0ff76ebe21 (patch)
tree1bab06750e306942180b18383c06ad2804f98677 /src
parentd0f8e9e03d3c55641165b73a4d8971ec514a9cdc (diff)
downloadmailman-3f1f5a2826feb9c5fb202ae266ba7f0ff76ebe21.tar.gz
mailman-3f1f5a2826feb9c5fb202ae266ba7f0ff76ebe21.tar.zst
mailman-3f1f5a2826feb9c5fb202ae266ba7f0ff76ebe21.zip
Diffstat (limited to 'src')
-rw-r--r--src/mailman/app/docs/lifecycle.txt34
-rw-r--r--src/mailman/app/registrar.py34
-rw-r--r--src/mailman/commands/cli_members.py10
-rw-r--r--src/mailman/commands/docs/create.txt16
-rw-r--r--src/mailman/commands/docs/members.txt46
-rw-r--r--src/mailman/commands/docs/membership.txt2
-rw-r--r--src/mailman/commands/eml_membership.py20
-rw-r--r--src/mailman/database/mailman.sql2
-rw-r--r--src/mailman/interfaces/address.py20
-rw-r--r--src/mailman/interfaces/registrar.py21
-rw-r--r--src/mailman/interfaces/user.py34
-rw-r--r--src/mailman/interfaces/usermanager.py87
-rw-r--r--src/mailman/model/address.py20
-rw-r--r--src/mailman/model/docs/addresses.txt133
-rw-r--r--src/mailman/model/docs/membership.txt18
-rw-r--r--src/mailman/model/docs/registration.txt23
-rw-r--r--src/mailman/model/docs/requests.txt6
-rw-r--r--src/mailman/model/docs/usermanager.txt65
-rw-r--r--src/mailman/model/docs/users.txt50
-rw-r--r--src/mailman/model/member.py2
-rw-r--r--src/mailman/model/roster.py4
-rw-r--r--src/mailman/model/user.py22
-rw-r--r--src/mailman/model/usermanager.py37
-rw-r--r--src/mailman/pipeline/calculate_recipients.py6
-rw-r--r--src/mailman/pipeline/decorate.py4
-rw-r--r--src/mailman/pipeline/docs/calc-recips.txt16
-rw-r--r--src/mailman/pipeline/docs/file-recips.txt27
-rw-r--r--src/mailman/pipeline/file_recipients.py2
-rw-r--r--src/mailman/queue/digest.py6
-rw-r--r--src/mailman/queue/docs/incoming.txt2
-rw-r--r--src/mailman/rest/adapters.py2
-rw-r--r--src/mailman/rest/members.py8
-rw-r--r--src/mailman/tests/test_documentation.py2
33 files changed, 429 insertions, 352 deletions
diff --git a/src/mailman/app/docs/lifecycle.txt b/src/mailman/app/docs/lifecycle.txt
index 43ba1f565..b421d1800 100644
--- a/src/mailman/app/docs/lifecycle.txt
+++ b/src/mailman/app/docs/lifecycle.txt
@@ -77,20 +77,27 @@ You can also specify a list of owner email addresses. If these addresses are
not yet known, they will be registered, and new users will be linked to them.
However the addresses are not verified.
- >>> owners = ['aperson@example.com', 'bperson@example.com',
- ... 'cperson@example.com', 'dperson@example.com']
+ >>> owners = [
+ ... 'aperson@example.com',
+ ... 'bperson@example.com',
+ ... 'cperson@example.com',
+ ... 'dperson@example.com',
+ ... ]
>>> mlist_2 = create_list('test_2@example.com', owners)
>>> print mlist_2.fqdn_listname
test_2@example.com
>>> print mlist_2.msg_footer
test footer
- >>> sorted(addr.address for addr in mlist_2.owners.addresses)
- [u'aperson@example.com', u'bperson@example.com',
- u'cperson@example.com', u'dperson@example.com']
+ >>> dump_list(address.email for address in mlist_2.owners.addresses)
+ aperson@example.com
+ bperson@example.com
+ cperson@example.com
+ dperson@example.com
None of the owner addresses are verified.
- >>> any(addr.verified_on is not None for addr in mlist_2.owners.addresses)
+ >>> any(address.verified_on is not None
+ ... for address in mlist_2.owners.addresses)
False
However, all addresses are linked to users.
@@ -117,8 +124,11 @@ the system, they won't be created again.
>>> user_d.real_name = 'Dirk Person'
>>> mlist_3 = create_list('test_3@example.com', owners)
- >>> sorted(user.real_name for user in mlist_3.owners.users)
- [u'Anne Person', u'Bart Person', u'Caty Person', u'Dirk Person']
+ >>> dump_list(user.real_name for user in mlist_3.owners.users)
+ Anne Person
+ Bart Person
+ Caty Person
+ Dirk Person
Deleting a list
@@ -139,6 +149,8 @@ artifacts.
We should now be able to completely recreate the mailing list.
>>> mlist_2a = create_list('test_2@example.com', owners)
- >>> sorted(addr.address for addr in mlist_2a.owners.addresses)
- [u'aperson@example.com', u'bperson@example.com',
- u'cperson@example.com', u'dperson@example.com']
+ >>> dump_list(address.email for address in mlist_2a.owners.addresses)
+ aperson@example.com
+ bperson@example.com
+ cperson@example.com
+ dperson@example.com
diff --git a/src/mailman/app/registrar.py b/src/mailman/app/registrar.py
index c68c98691..d939da4c8 100644
--- a/src/mailman/app/registrar.py
+++ b/src/mailman/app/registrar.py
@@ -53,15 +53,15 @@ class Registrar:
implements(IRegistrar)
- def register(self, mlist, address, real_name=None):
+ def register(self, mlist, email, real_name=None):
"""See `IUserRegistrar`."""
# First, do validation on the email address. If the address is
# invalid, it will raise an exception, otherwise it just returns.
- validate(address)
+ validate(email)
# Create a pendable for the registration.
pendable = PendableRegistration(
type=PendableRegistration.PEND_KEY,
- address=address,
+ email=email,
real_name=real_name)
pendable['list_name'] = mlist.fqdn_listname
token = getUtility(IPendings).add(pendable)
@@ -74,12 +74,12 @@ class Registrar:
confirm_address = mlist.confirm_address(token)
# For i18n interpolation.
confirm_url = mlist.domain.confirm_url(token)
- email_address = address
+ email_address = email
domain_name = mlist.domain.email_host
contact_address = mlist.domain.contact_address
# Send a verification email to the address.
text = _(resource_string('mailman.templates.en', 'verify.txt'))
- msg = UserNotification(address, confirm_address, subject, text)
+ msg = UserNotification(email, confirm_address, subject, text)
msg.send(mlist)
return token
@@ -90,7 +90,7 @@ class Registrar:
if pendable is None:
return False
missing = object()
- address = pendable.get('address', missing)
+ email = pendable.get('email', missing)
real_name = pendable.get('real_name', missing)
list_name = pendable.get('list_name', missing)
if pendable.get('type') != PendableRegistration.PEND_KEY:
@@ -104,41 +104,41 @@ class Registrar:
# and an IUser linked to this IAddress. See if any of these objects
# currently exist in our database.
user_manager = getUtility(IUserManager)
- addr = (user_manager.get_address(address)
- if address is not missing else None)
- user = (user_manager.get_user(address)
- if address is not missing else None)
+ address = (user_manager.get_address(email)
+ if email is not missing else None)
+ user = (user_manager.get_user(email)
+ if email is not missing else None)
# If there is neither an address nor a user matching the confirmed
# record, then create the user, which will in turn create the address
# and link the two together
- if addr is None:
+ if address is None:
assert user is None, 'How did we get a user but not an address?'
- user = user_manager.create_user(address, real_name)
+ user = user_manager.create_user(email, real_name)
# Because the database changes haven't been flushed, we can't use
# IUserManager.get_address() to find the IAddress just created
# under the hood. Instead, iterate through the IUser's addresses,
# of which really there should be only one.
- for addr in user.addresses:
- if addr.address == address:
+ for address in user.addresses:
+ if address.email == email:
break
else:
raise AssertionError('Could not find expected IAddress')
elif user is None:
user = user_manager.create_user()
user.real_name = real_name
- user.link(addr)
+ user.link(address)
else:
# The IAddress and linked IUser already exist, so all we need to
# do is verify the address.
pass
- addr.verified_on = datetime.datetime.now()
+ address.verified_on = datetime.datetime.now()
# If this registration is tied to a mailing list, subscribe the person
# to the list right now.
list_name = pendable.get('list_name')
if list_name is not None:
mlist = getUtility(IListManager).get(list_name)
if mlist:
- addr.subscribe(mlist, MemberRole.member)
+ address.subscribe(mlist, MemberRole.member)
return True
def discard(self, token):
diff --git a/src/mailman/commands/cli_members.py b/src/mailman/commands/cli_members.py
index e18b4b686..e3159a589 100644
--- a/src/mailman/commands/cli_members.py
+++ b/src/mailman/commands/cli_members.py
@@ -156,21 +156,21 @@ class Members:
if len(addresses) == 0:
print >> fp, mlist.fqdn_listname, 'has no members'
return
- for address in sorted(addresses, key=attrgetter('address')):
+ for address in sorted(addresses, key=attrgetter('email')):
if args.regular:
- member = mlist.members.get_member(address.address)
+ member = mlist.members.get_member(address.email)
if member.delivery_mode != DeliveryMode.regular:
continue
if args.digest is not None:
- member = mlist.members.get_member(address.address)
+ member = mlist.members.get_member(address.email)
if member.delivery_mode not in digest_types:
continue
if args.nomail is not None:
- member = mlist.members.get_member(address.address)
+ member = mlist.members.get_member(address.email)
if member.delivery_status not in status_types:
continue
print >> fp, formataddr(
- (address.real_name, address.original_address))
+ (address.real_name, address.original_email))
finally:
if fp is not sys.stdout:
fp.close()
diff --git a/src/mailman/commands/docs/create.txt b/src/mailman/commands/docs/create.txt
index 70fb44d8f..31663a851 100644
--- a/src/mailman/commands/docs/create.txt
+++ b/src/mailman/commands/docs/create.txt
@@ -77,8 +77,8 @@ Setting the owner
By default, no list owners are specified.
- >>> print list(mlist.owners.addresses)
- []
+ >>> dump_list(mlist.owners.addresses)
+ *Empty*
But you can specify an owner address on the command line when you create the
mailing list.
@@ -91,8 +91,8 @@ mailing list.
Created mailing list: test4@example.com
>>> mlist = list_manager.get('test4@example.com')
- >>> print list(mlist.owners.addresses)
- [<Address: foo@example.org [not verified] at ...>]
+ >>> dump_list(repr(address) for address in mlist.owners.addresses)
+ <Address: foo@example.org [not verified] at ...>
You can even specify more than one address for the owners.
::
@@ -104,10 +104,10 @@ You can even specify more than one address for the owners.
>>> mlist = list_manager.get('test5@example.com')
>>> from operator import attrgetter
- >>> print sorted(mlist.owners.addresses, key=attrgetter('address'))
- [<Address: bar@example.net [not verified] at ...>,
- <Address: baz@example.net [not verified] at ...>,
- <Address: foo@example.net [not verified] at ...>]
+ >>> dump_list(repr(address) for address in mlist.owners.addresses)
+ <Address: bar@example.net [not verified] at ...>
+ <Address: baz@example.net [not verified] at ...>
+ <Address: foo@example.net [not verified] at ...>
Setting the language
diff --git a/src/mailman/commands/docs/members.txt b/src/mailman/commands/docs/members.txt
index 602e1bbe5..18a916781 100644
--- a/src/mailman/commands/docs/members.txt
+++ b/src/mailman/commands/docs/members.txt
@@ -205,8 +205,6 @@ need a file containing email addresses and full names that can be parsed by
::
>>> mlist2 = create_list('test2@example.com')
- >>> addresses = [
- ... ]
>>> import os
>>> path = os.path.join(config.VAR_DIR, 'addresses.txt')
@@ -221,8 +219,11 @@ need a file containing email addresses and full names that can be parsed by
>>> args.listname = [mlist2.fqdn_listname]
>>> command.process(args)
- >>> sorted(address.address for address in mlist2.members.addresses)
- [u'aperson@example.com', u'bperson@example.com', u'cperson@example.com']
+ >>> from operator import attrgetter
+ >>> dump_list(mlist2.members.addresses, key=attrgetter('email'))
+ aperson@example.com
+ Bart Person <bperson@example.com>
+ Cate Person <cperson@example.com>
You can also specify ``-`` as the filename, in which case the addresses are
taken from standard input.
@@ -244,9 +245,13 @@ taken from standard input.
>>> command.process(args)
>>> sys.stdin = sys.__stdin__
- >>> sorted(address.address for address in mlist2.members.addresses)
- [u'aperson@example.com', u'bperson@example.com', u'cperson@example.com',
- u'dperson@example.com', u'eperson@example.com', u'fperson@example.com']
+ >>> dump_list(mlist2.members.addresses, key=attrgetter('email'))
+ aperson@example.com
+ Bart Person <bperson@example.com>
+ Cate Person <cperson@example.com>
+ dperson@example.com
+ Elly Person <eperson@example.com>
+ Fred Person <fperson@example.com>
Blank lines and lines that begin with '#' are ignored.
::
@@ -262,10 +267,15 @@ Blank lines and lines that begin with '#' are ignored.
>>> args.input_filename = path
>>> command.process(args)
- >>> sorted(address.address for address in mlist2.members.addresses)
- [u'aperson@example.com', u'bperson@example.com', u'cperson@example.com',
- u'dperson@example.com', u'eperson@example.com', u'fperson@example.com',
- u'gperson@example.com', u'iperson@example.com']
+ >>> dump_list(mlist2.members.addresses, key=attrgetter('email'))
+ aperson@example.com
+ Bart Person <bperson@example.com>
+ Cate Person <cperson@example.com>
+ dperson@example.com
+ Elly Person <eperson@example.com>
+ Fred Person <fperson@example.com>
+ gperson@example.com
+ iperson@example.com
Addresses which are already subscribed are ignored, although a warning is
printed.
@@ -282,10 +292,16 @@ printed.
Already subscribed (skipping): gperson@example.com
Already subscribed (skipping): aperson@example.com
- >>> sorted(address.address for address in mlist2.members.addresses)
- [u'aperson@example.com', u'bperson@example.com', u'cperson@example.com',
- u'dperson@example.com', u'eperson@example.com', u'fperson@example.com',
- u'gperson@example.com', u'iperson@example.com', u'jperson@example.com']
+ >>> dump_list(mlist2.members.addresses, key=attrgetter('email'))
+ aperson@example.com
+ Bart Person <bperson@example.com>
+ Cate Person <cperson@example.com>
+ dperson@example.com
+ Elly Person <eperson@example.com>
+ Fred Person <fperson@example.com>
+ gperson@example.com
+ iperson@example.com
+ jperson@example.com
Displaying members
diff --git a/src/mailman/commands/docs/membership.txt b/src/mailman/commands/docs/membership.txt
index 3d611a160..0da7ffadf 100644
--- a/src/mailman/commands/docs/membership.txt
+++ b/src/mailman/commands/docs/membership.txt
@@ -281,7 +281,7 @@ to unsubscribe Anne from the alpha mailing list.
>>> print unicode(results)
The results of your email command are provided below.
<BLANKLINE>
- Invalid or unverified address: anne.person@example.org
+ Invalid or unverified email address: anne.person@example.org
<BLANKLINE>
>>> print mlist.members.get_member('anne@example.com')
diff --git a/src/mailman/commands/eml_membership.py b/src/mailman/commands/eml_membership.py
index 26d96ce3c..7da742b39 100644
--- a/src/mailman/commands/eml_membership.py
+++ b/src/mailman/commands/eml_membership.py
@@ -144,36 +144,36 @@ class Leave:
def process(self, mlist, msg, msgdata, arguments, results):
"""See `IEmailCommand`."""
- address = msg.sender
- if not address:
+ email = msg.sender
+ if not email:
print >> results, _(
- '$self.name: No valid address found to unsubscribe')
+ '$self.name: No valid email address found to unsubscribe')
return ContinueProcessing.no
user_manager = getUtility(IUserManager)
- user = user_manager.get_user(address)
+ user = user_manager.get_user(email)
if user is None:
- print >> results, _('No registered user for address: $address')
+ print >> results, _('No registered user for email address: $email')
return ContinueProcessing.no
# The address that the -leave command was sent from, must be verified.
# Otherwise you could link a bogus address to anyone's account, and
# then send a leave command from that address.
- if user_manager.get_address(address).verified_on is None:
- print >> results, _('Invalid or unverified address: $address')
+ if user_manager.get_address(email).verified_on is None:
+ print >> results, _('Invalid or unverified email address: $email')
return ContinueProcessing.no
for user_address in user.addresses:
# Only recognize verified addresses.
if user_address.verified_on is None:
continue
- member = mlist.members.get_member(user_address.address)
+ member = mlist.members.get_member(user_address.email)
if member is not None:
break
else:
# None of the user's addresses are subscribed to this mailing list.
print >> results, _(
- '$self.name: $address is not a member of $mlist.fqdn_listname')
+ '$self.name: $email is not a member of $mlist.fqdn_listname')
return ContinueProcessing.no
member.unsubscribe()
- person = formataddr((user.real_name, address))
+ person = formataddr((user.real_name, email))
print >> results, _('$person left $mlist.fqdn_listname')
return ContinueProcessing.yes
diff --git a/src/mailman/database/mailman.sql b/src/mailman/database/mailman.sql
index cfe44c8e1..bb7f9dc57 100644
--- a/src/mailman/database/mailman.sql
+++ b/src/mailman/database/mailman.sql
@@ -23,7 +23,7 @@ CREATE INDEX ix_acceptablealias_alias
CREATE TABLE address (
id INTEGER NOT NULL,
- address TEXT,
+ email TEXT,
_original TEXT,
real_name TEXT,
verified_on TIMESTAMP,
diff --git a/src/mailman/interfaces/address.py b/src/mailman/interfaces/address.py
index 89a4c5b3b..72d021ae0 100644
--- a/src/mailman/interfaces/address.py
+++ b/src/mailman/interfaces/address.py
@@ -60,20 +60,20 @@ class InvalidEmailAddressError(AddressError):
class IAddress(Interface):
"""Email address related information."""
- address = Attribute(
+ email = Attribute(
"""Read-only text email address.""")
- original_address = Attribute(
- """Read-only original case-preserved address.
+ original_email = Attribute(
+ """Read-only original case-preserved email address.
- For almost all intents and purposes, addresses in Mailman are case
- insensitive, however because RFC 2821 allows for case sensitive local
- parts, Mailman preserves the case of the original address when
- emailing the user.
+ For almost all intents and purposes, email addresses in Mailman are
+ case insensitive, however because RFC 2821 allows for case sensitive
+ local parts, Mailman preserves the case of the original email address
+ when delivering a message to the user.
- `original_address` will be the same as address if the original address
- was all lower case. Otherwise `original_address` will be the case
- preserved address; `address` will always be lower case.
+ `original_email` will be the same as `email` if the original email
+ address was all lower case. Otherwise `original_email` will be the
+ case preserved email address; `email` will always be lower case.
""")
real_name = Attribute(
diff --git a/src/mailman/interfaces/registrar.py b/src/mailman/interfaces/registrar.py
index c85a0103c..fefd13698 100644
--- a/src/mailman/interfaces/registrar.py
+++ b/src/mailman/interfaces/registrar.py
@@ -35,28 +35,29 @@ from zope.interface import Interface
class IRegistrar(Interface):
- """Interface for registering and verifying addresses and users.
+ """Interface for registering and verifying email addresses and users.
- This is a higher level interface to user registration, address
+ This is a higher level interface to user registration, email address
confirmation, etc. than the IUserManager. The latter does no validation,
syntax checking, or confirmation, while this interface does.
"""
- def register(mlist, address, real_name=None):
+ def register(mlist, email, real_name=None):
"""Register the email address, requesting verification.
- No IAddress or IUser is created during this step, but after successful
- confirmation, it is guaranteed that an IAddress with a linked IUser
- will exist. When a verified IAddress matching address already exists,
- this method will do nothing, except link a new IUser to the IAddress
- if one is not yet associated with the address.
+ No `IAddress` or `IUser` is created during this step, but after
+ successful confirmation, it is guaranteed that an `IAddress` with a
+ linked `IUser` will exist. When a verified `IAddress` matching
+ `email` already exists, this method will do nothing, except link a new
+ `IUser` to the `IAddress` if one is not yet associated with the
+ email address.
In all cases, the email address is sanity checked for validity first.
:param mlist: The mailing list that is the focus of this registration.
:type mlist: `IMailingList`
- :param address: The email address to register.
- :type address: str
+ :param email: The email address to register.
+ :type email: str
:param real_name: The optional real name of the user.
:type real_name: str
:return: The confirmation token string.
diff --git a/src/mailman/interfaces/user.py b/src/mailman/interfaces/user.py
index 824f6e99c..21ccc460b 100644
--- a/src/mailman/interfaces/user.py
+++ b/src/mailman/interfaces/user.py
@@ -33,29 +33,31 @@ class IUser(Interface):
"""A basic user."""
real_name = Attribute(
- """This user's Real Name.""")
+ """This user's real name.""")
password = Attribute(
"""This user's password information.""")
addresses = Attribute(
- """An iterator over all the IAddresses controlled by this user.""")
+ """An iterator over all the `IAddresses` controlled by this user.""")
memberships = Attribute(
"""A roster of this user's memberships.""")
- def register(address, real_name=None):
+ def register(email, 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. 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.
+ :param email: The text email address to register.
+ :type email: str
+ :param real_name: The user's real name. If not given the empty string
+ is used.
+ :type real_name: str
+ :return: The address object linked to the user. If the associated
+ address object already existed (unlinked to a user) then the
+ `real_name` parameter is ignored.
+ :rtype: `IAddress`
+ :raises AddressAlreadyLinkedError: if this `IAddress` is already
+ linked to another user.
"""
def link(address):
@@ -73,11 +75,13 @@ class IUser(Interface):
some other user.
"""
- def controls(address):
+ def controls(email):
"""Determine whether this user controls the given email address.
- 'address' is a text email address. This method returns true if the
- user controls the given email address, otherwise false.
+ :param email: The text email address to register.
+ :type email: str
+ :return: True if the user controls the given email address.
+ :rtype: bool
"""
preferences = Attribute(
diff --git a/src/mailman/interfaces/usermanager.py b/src/mailman/interfaces/usermanager.py
index 16c45ebcc..5893d74f3 100644
--- a/src/mailman/interfaces/usermanager.py
+++ b/src/mailman/interfaces/usermanager.py
@@ -15,7 +15,7 @@
# You should have received a copy of the GNU General Public License along with
# GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
-"""Interface describing a user manager service."""
+"""Interface describing the user management service."""
from __future__ import absolute_import, unicode_literals
@@ -30,67 +30,74 @@ from zope.interface import Interface, Attribute
class IUserManager(Interface):
- """The interface of a global user manager service.
+ """The global user management service."""
- Different user managers have different concepts of what a user is, and the
- users managed by different IUserManagers are completely independent. This
- is how you can separate the user contexts for different domains, on a
- multiple domain system.
+ def create_user(email=None, real_name=None):
+ """Create and return an `IUser`.
- There is one special roster, the null roster ('') which contains all
- IUsers in all IRosters.
- """
-
- def create_user(address=None, real_name=None):
- """Create and return an IUser.
-
- When address is given, an IAddress is also created and linked to the
- new IUser object. If the address already exists, an
- `ExistingAddressError` is raised. If the address exists but is
- already linked to another user, an AddressAlreadyLinkedError is
- raised.
-
- When real_name is given, the IUser's real_name is set to this string.
- If an IAddress is also created and linked, its real_name is set to the
- same string.
+ :param email: The text email address for the user being created.
+ :type email: str
+ :param real_name: The real name of the user.
+ :type real_name: str
+ :return: The newly created user, with the given email address and real
+ name, if given.
+ :rtype: `IUser`
+ :raises ExistingAddressError: when the email address is already
+ registered.
"""
def delete_user(user):
- """Delete the given IUser."""
+ """Delete the given user.
- def get_user(address):
+ :param user: The user to delete.
+ :type user: `IUser`.
+ """
+
+ def get_user(email):
"""Get the user that controls the given email address, or None.
- 'address' is a text email address.
+ :param email: The email address to look up.
+ :type email: str
+ :return: The user found or None.
+ :rtype: `IUser`.
"""
users = Attribute(
- """An iterator over all the IUsers managed by this user manager.""")
+ """An iterator over all the `IUsers` managed by this user manager.""")
- def create_address(address, real_name=None):
- """Create and return an unlinked IAddress object.
+ def create_address(email, real_name=None):
+ """Create and return an address unlinked to any user.
- address is the text email address. If real_name is not given, it
- defaults to the empty string. If the IAddress already exists an
- ExistingAddressError is raised.
+ :param email: The text email address for the address being created.
+ :type email: str
+ :param real_name: The real name associated with the address.
+ :type real_name: str
+ :return: The newly created address object, with the given email
+ address and real name, if given.
+ :rtype: `IAddress`
+ :raises ExistingAddressError: when the email address is already
+ registered.
"""
def delete_address(address):
- """Delete the given IAddress object.
+ """Delete the given `IAddress` object.
+
+ If the `IAddress` is linked to a user, it is first unlinked before it
+ is deleted.
- If this IAddress linked to a user, it is first unlinked before it is
- deleted.
+ :param address: The address to delete.
+ :type address: `IAddress`.
"""
- def get_address(address):
- """Find and return the `IAddress` matching a text address.
+ def get_address(email):
+ """Find and return the `IAddress` matching an email address.
- :param address: the text email address
- :type address: string
+ :param email: The text email address.
+ :type email: str
:return: The matching `IAddress` object, or None if no registered
- `IAddress` matches the text address
+ `IAddress` matches the text address.
:rtype: `IAddress` or None
"""
addresses = Attribute(
- """An iterator over all the IAddresses managed by this manager.""")
+ """An iterator over all the `IAddresses` managed by this manager.""")
diff --git a/src/mailman/model/address.py b/src/mailman/model/address.py
index 6209858a8..d2b87e5b6 100644
--- a/src/mailman/model/address.py
+++ b/src/mailman/model/address.py
@@ -41,7 +41,7 @@ class Address(Model):
implements(IAddress)
id = Int(primary=True)
- address = Unicode()
+ email = Unicode()
_original = Unicode()
real_name = Unicode()
verified_on = DateTime()
@@ -52,15 +52,15 @@ class Address(Model):
preferences_id = Int()
preferences = Reference(preferences_id, 'Preferences.id')
- def __init__(self, address, real_name):
+ def __init__(self, email, real_name):
super(Address, self).__init__()
- lower_case = address.lower()
- self.address = lower_case
+ lower_case = email.lower()
+ self.email = lower_case
self.real_name = real_name
- self._original = (None if lower_case == address else address)
+ self._original = (None if lower_case == email else email)
def __str__(self):
- addr = (self.address if self._original is None else self._original)
+ addr = (self.email if self._original is None else self._original)
return formataddr((self.real_name, addr))
def __repr__(self):
@@ -71,7 +71,7 @@ class Address(Model):
address_str, verified, id(self))
else:
return '<Address: {0} [{1}] key: {2} at {3:#x}>'.format(
- address_str, verified, self.address, id(self))
+ address_str, verified, self.email, id(self))
def subscribe(self, mailing_list, role):
# This member has no preferences by default.
@@ -83,7 +83,7 @@ class Address(Model):
Member.address == self).one()
if member:
raise AlreadySubscribedError(
- mailing_list.fqdn_listname, self.address, role)
+ mailing_list.fqdn_listname, self.email, role)
member = Member(role=role,
mailing_list=mailing_list.fqdn_listname,
address=self)
@@ -92,5 +92,5 @@ class Address(Model):
return member
@property
- def original_address(self):
- return (self.address if self._original is None else self._original)
+ 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 aeddb5e31..0ddacb321 100644
--- a/src/mailman/model/docs/addresses.txt
+++ b/src/mailman/model/docs/addresses.txt
@@ -18,42 +18,45 @@ Creating addresses
Addresses are created directly through the user manager, which starts out with
no addresses.
- >>> sorted(address.address for address in user_manager.addresses)
- []
+ >>> dump_list(address.email for address in user_manager.addresses)
+ *Empty*
Creating an unlinked email address is straightforward.
>>> address_1 = user_manager.create_address('aperson@example.com')
- >>> sorted(address.address for address in user_manager.addresses)
- [u'aperson@example.com']
+ >>> dump_list(address.email for address in user_manager.addresses)
+ aperson@example.com
However, such addresses have no real name.
- >>> address_1.real_name
- u''
+ >>> print address_1.real_name
+ <BLANKLINE>
You can also create an email address object with a real name.
>>> address_2 = user_manager.create_address(
... 'bperson@example.com', 'Ben Person')
- >>> sorted(address.address for address in user_manager.addresses)
- [u'aperson@example.com', u'bperson@example.com']
- >>> sorted(address.real_name for address in user_manager.addresses)
- [u'', u'Ben Person']
+ >>> dump_list(address.email for address in user_manager.addresses)
+ aperson@example.com
+ bperson@example.com
+ >>> dump_list(address.real_name for address in user_manager.addresses)
+ <BLANKLINE>
+ Ben Person
The ``str()`` of the address is the RFC 2822 preferred originator format,
while the ``repr()`` carries more information.
- >>> str(address_2)
- 'Ben Person <bperson@example.com>'
- >>> repr(address_2)
- '<Address: Ben Person <bperson@example.com> [not verified] at 0x...>'
+ >>> print str(address_2)
+ Ben Person <bperson@example.com>
+ >>> print repr(address_2)
+ <Address: Ben Person <bperson@example.com> [not verified] at 0x...>
You can assign real names to existing addresses.
>>> address_1.real_name = 'Anne Person'
- >>> sorted(address.real_name for address in user_manager.addresses)
- [u'Anne Person', u'Ben Person']
+ >>> dump_list(address.real_name for address in user_manager.addresses)
+ Anne Person
+ Ben Person
These addresses are not linked to users, and can be seen by searching the user
manager for an associated user.
@@ -68,12 +71,16 @@ interface.
>>> user_1 = user_manager.create_user(
... 'cperson@example.com', u'Claire Person')
- >>> sorted(address.address for address in user_1.addresses)
- [u'cperson@example.com']
- >>> sorted(address.address for address in user_manager.addresses)
- [u'aperson@example.com', u'bperson@example.com', u'cperson@example.com']
- >>> sorted(address.real_name for address in user_manager.addresses)
- [u'Anne Person', u'Ben Person', u'Claire Person']
+ >>> dump_list(address.email for address in user_1.addresses)
+ cperson@example.com
+ >>> dump_list(address.email for address in user_manager.addresses)
+ aperson@example.com
+ bperson@example.com
+ cperson@example.com
+ >>> dump_list(address.real_name for address in user_manager.addresses)
+ Anne Person
+ Ben Person
+ Claire Person
And now you can find the associated user.
@@ -91,26 +98,28 @@ Deleting addresses
You can remove an unlinked address from the user manager.
>>> user_manager.delete_address(address_1)
- >>> sorted(address.address for address in user_manager.addresses)
- [u'bperson@example.com', u'cperson@example.com']
- >>> sorted(address.real_name for address in user_manager.addresses)
- [u'Ben Person', u'Claire Person']
+ >>> dump_list(address.email for address in user_manager.addresses)
+ bperson@example.com
+ cperson@example.com
+ >>> dump_list(address.real_name for address in user_manager.addresses)
+ Ben Person
+ Claire Person
Deleting a linked address does not delete the user, but it does unlink the
address from the user.
- >>> sorted(address.address for address in user_1.addresses)
- [u'cperson@example.com']
+ >>> dump_list(address.email for address in user_1.addresses)
+ cperson@example.com
>>> user_1.controls('cperson@example.com')
True
>>> address_3 = list(user_1.addresses)[0]
>>> user_manager.delete_address(address_3)
- >>> sorted(address.address for address in user_1.addresses)
- []
+ >>> dump_list(address.email for address in user_1.addresses)
+ *Empty*
>>> user_1.controls('cperson@example.com')
False
- >>> sorted(address.address for address in user_manager.addresses)
- [u'bperson@example.com']
+ >>> dump_list(address.email for address in user_manager.addresses)
+ bperson@example.com
Registration and validation
@@ -153,34 +162,36 @@ subscribed, a role is specified.
>>> address_5 = user_manager.create_address(
... 'eperson@example.com', 'Elly Person')
- >>> mlist = create_list('_xtext@example.com')
+ >>> 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
- _xtext@example.com as MemberRole.owner>
+ test@example.com as MemberRole.owner>
>>> address_5.subscribe(mlist, MemberRole.member)
<Member: Elly Person <eperson@example.com> on
- _xtext@example.com as MemberRole.member>
+ test@example.com as MemberRole.member>
Now Elly is both an owner and a member of the mailing list.
- >>> sorted(mlist.owners.members)
- [<Member: Elly Person <eperson@example.com> on
- _xtext@example.com as MemberRole.owner>]
- >>> sorted(mlist.moderators.members)
- []
- >>> sorted(mlist.administrators.members)
- [<Member: Elly Person <eperson@example.com> on
- _xtext@example.com as MemberRole.owner>]
- >>> sorted(mlist.members.members)
- [<Member: Elly Person <eperson@example.com> on
- _xtext@example.com as MemberRole.member>]
- >>> sorted(mlist.regular_members.members)
- [<Member: Elly Person <eperson@example.com> on
- _xtext@example.com as MemberRole.member>]
- >>> sorted(mlist.digest_members.members)
- []
+ >>> 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
@@ -198,18 +209,18 @@ The str() of such an address prints the RFC 2822 preferred originator format
with the original case-preserved address. The repr() contains all the gory
details.
- >>> str(address_6)
- 'Frank Person <FPERSON@example.com>'
- >>> repr(address_6)
- '<Address: Frank Person <FPERSON@example.com> [not verified]
- key: fperson@example.com at 0x...>'
+ >>> print str(address_6)
+ Frank Person <FPERSON@example.com>
+ >>> print repr(address_6)
+ <Address: Frank Person <FPERSON@example.com> [not verified]
+ key: fperson@example.com at 0x...>
Both the case-insensitive version of the address and the original
-case-preserved version are available on attributes of the IAddress object.
+case-preserved version are available on attributes of the `IAddress` object.
- >>> print address_6.address
+ >>> print address_6.email
fperson@example.com
- >>> print address_6.original_address
+ >>> print address_6.original_email
FPERSON@example.com
Because addresses are case-insensitive for all other purposes, you cannot
@@ -231,7 +242,7 @@ create an address that differs only in case.
You can get the address using either the lower cased version or case-preserved
version. In fact, searching for an address is case insensitive.
- >>> print user_manager.get_address('fperson@example.com').address
+ >>> print user_manager.get_address('fperson@example.com').email
fperson@example.com
- >>> print user_manager.get_address('FPERSON@example.com').address
+ >>> print user_manager.get_address('FPERSON@example.com').email
fperson@example.com
diff --git a/src/mailman/model/docs/membership.txt b/src/mailman/model/docs/membership.txt
index ebf1b6ebf..00e79d733 100644
--- a/src/mailman/model/docs/membership.txt
+++ b/src/mailman/model/docs/membership.txt
@@ -85,7 +85,7 @@ Now, both Anne and Bart are list administrators.
>>> from operator import attrgetter
>>> def dump_members(roster):
... all_addresses = list(member.address for member in roster)
- ... sorted_addresses = sorted(all_addresses, key=attrgetter('address'))
+ ... sorted_addresses = sorted(all_addresses, key=attrgetter('email'))
... dump_list(sorted_addresses)
>>> dump_members(mlist.administrators.members)
@@ -121,7 +121,7 @@ It's easy to make the list administrators members of the mailing list too.
>>> for address in mlist.administrators.addresses:
... member = address.subscribe(mlist, MemberRole.member)
... members.append(member)
- >>> dump_list(members, key=attrgetter('address.address'))
+ >>> dump_list(members, key=attrgetter('address.email'))
<Member: Anne Person <aperson@example.com> on
test@example.com as MemberRole.member>
<Member: Bart Person <bperson@example.com> on
@@ -211,9 +211,9 @@ There is also a roster containing all the subscribers of a mailing list,
regardless of their role.
>>> def sortkey(member):
- ... return (member.address.address, int(member.role))
+ ... return (member.address.email, int(member.role))
>>> for member in sorted(mlist.subscribers.members, key=sortkey):
- ... print member.address.address, member.role
+ ... print member.address.email, member.role
aperson@example.com MemberRole.member
aperson@example.com MemberRole.owner
bperson@example.com MemberRole.member
@@ -242,8 +242,8 @@ postings from that member are handled. By default, owners and moderators are
automatically accepted for posting to the mailing list.
>>> for member in sorted(mlist.administrators.members,
- ... key=attrgetter('address.address')):
- ... print member.address.address, member.role, member.moderation_action
+ ... key=attrgetter('address.email')):
+ ... print member.address.email, member.role, member.moderation_action
aperson@example.com MemberRole.owner Action.accept
bperson@example.com MemberRole.moderator Action.accept
@@ -251,8 +251,8 @@ By default, members have a *deferred* action which specifies that the posting
should go through the normal moderation checks.
>>> for member in sorted(mlist.members.members,
- ... key=attrgetter('address.address')):
- ... print member.address.address, member.role, member.moderation_action
+ ... key=attrgetter('address.email')):
+ ... print member.address.email, member.role, member.moderation_action
aperson@example.com MemberRole.member Action.defer
bperson@example.com MemberRole.member Action.defer
cperson@example.com MemberRole.member Action.defer
@@ -260,5 +260,5 @@ should go through the normal moderation checks.
Postings by nonmembers are held for moderator approval by default.
>>> for member in mlist.nonmembers.members:
- ... print member.address.address, member.role, member.moderation_action
+ ... print member.address.email, member.role, member.moderation_action
fperson@example.com MemberRole.nonmember Action.hold
diff --git a/src/mailman/model/docs/registration.txt b/src/mailman/model/docs/registration.txt
index 050a4d0f3..e92c63f52 100644
--- a/src/mailman/model/docs/registration.txt
+++ b/src/mailman/model/docs/registration.txt
@@ -100,7 +100,7 @@ But this address is waiting for confirmation.
>>> pendingdb = getUtility(IPendings)
>>> dump_msgdata(pendingdb.confirm(token, expunge=False))
- address : aperson@example.com
+ email : aperson@example.com
list_name: alpha@example.com
real_name: Anne Person
type : registration
@@ -178,8 +178,8 @@ token and uses that to confirm the pending registration.
>>> registrar.confirm(token)
True
-Now, there is an ``IAddress`` in the database matching the address, as well as
-an ``IUser`` linked to this address. The ``IAddress`` is verified.
+Now, there is an `IAddress` in the database matching the address, as well as
+an `IUser` linked to this address. The `IAddress` is verified.
>>> found_address = user_manager.get_address('aperson@example.com')
>>> found_address
@@ -187,7 +187,7 @@ an ``IUser`` linked to this address. The ``IAddress`` is verified.
>>> found_user = user_manager.get_user('aperson@example.com')
>>> found_user
<User "Anne Person" at ...>
- >>> found_user.controls(found_address.address)
+ >>> found_user.controls(found_address.email)
True
>>> from datetime import datetime
>>> isinstance(found_address.verified_on, datetime)
@@ -247,8 +247,7 @@ Discarding
==========
A confirmation token can also be discarded, say if the user changes his or her
-mind about registering. When discarded, no ``IAddress`` or ``IUser`` is
-created.
+mind about registering. When discarded, no `IAddress` or `IUser` is created.
::
>>> token = registrar.register(mlist, 'eperson@example.com', 'Elly Person')
@@ -282,8 +281,8 @@ can be used.
>>> address.verified_on = datetime.now()
>>> from operator import attrgetter
- >>> sorted((addr for addr in dperson.addresses), key=attrgetter('address'))
- [<Address: Dave Person <dperson@example.com> [verified] at ...>]
+ >>> dump_list(repr(address) for address in dperson.addresses)
+ <Address: Dave Person <dperson@example.com> [verified] at ...>
>>> dperson.register('david.person@example.com', 'David Person')
<Address: David Person <david.person@example.com> [not verified] at ...>
>>> token = registrar.register(mlist, 'david.person@example.com')
@@ -299,9 +298,9 @@ can be used.
True
>>> user
<User "Dave Person" at ...>
- >>> sorted((addr for addr in user.addresses), key=attrgetter('address'))
- [<Address: David Person <david.person@example.com> [verified] at ...>,
- <Address: Dave Person <dperson@example.com> [verified] at ...>]
+ >>> dump_list(repr(address) for address in user.addresses)
+ <Address: Dave Person <dperson@example.com> [verified] at ...>
+ <Address: David Person <david.person@example.com> [verified] at ...>
Corner cases
@@ -313,7 +312,7 @@ confirm method will just return False.
>>> registrar.confirm(bytes('no token'))
False
-Likewise, if you try to confirm, through the ``IUserRegistrar`` interface, a
+Likewise, if you try to confirm, through the `IUserRegistrar` interface, a
token that doesn't match a registration event, you will get ``None``.
However, the pending event matched with that token will still be removed.
diff --git a/src/mailman/model/docs/requests.txt b/src/mailman/model/docs/requests.txt
index 6cca8f602..94c81e1dc 100644
--- a/src/mailman/model/docs/requests.txt
+++ b/src/mailman/model/docs/requests.txt
@@ -56,8 +56,8 @@ The list's requests database starts out empty.
>>> requests.count
0
- >>> list(requests.held_requests)
- []
+ >>> dump_list(requests.held_requests)
+ *Empty*
At the lowest level, the requests database is very simple. Holding a request
requires a request type (as an enum value), a key, and an optional dictionary
@@ -697,7 +697,7 @@ Frank Person is now a member of the mailing list.
>>> from zope.component import getUtility
>>> user_manager = getUtility(IUserManager)
- >>> user = user_manager.get_user(member.address.address)
+ >>> user = user_manager.get_user(member.address.email)
>>> print user.real_name
Frank Person
>>> print user.password
diff --git a/src/mailman/model/docs/usermanager.txt b/src/mailman/model/docs/usermanager.txt
index a6bd4fed2..7b333248c 100644
--- a/src/mailman/model/docs/usermanager.txt
+++ b/src/mailman/model/docs/usermanager.txt
@@ -24,10 +24,10 @@ have a password.
>>> verifyObject(IUser, user)
True
- >>> sorted(address.address for address in user.addresses)
- []
- >>> user.real_name
- u''
+ >>> dump_list(address.email for address in user.addresses)
+ *Empty*
+ >>> print user.real_name
+ <BLANKLINE>
>>> print user.password
None
@@ -39,52 +39,59 @@ The user has preferences, but none of them will be specified.
A user can be assigned a real name.
>>> user.real_name = 'Anne Person'
- >>> sorted(user.real_name for user in user_manager.users)
- [u'Anne Person']
+ >>> dump_list(user.real_name for user in user_manager.users)
+ Anne Person
A user can be assigned a password.
>>> user.password = 'secret'
- >>> sorted(user.password for user in user_manager.users)
- [u'secret']
+ >>> dump_list(user.password for user in user_manager.users)
+ secret
You can also create a user with an address to start out with.
>>> user_2 = user_manager.create_user('bperson@example.com')
>>> verifyObject(IUser, user_2)
True
- >>> sorted(address.address for address in user_2.addresses)
- [u'bperson@example.com']
- >>> sorted(user.real_name for user in user_manager.users)
- [u'', u'Anne Person']
+ >>> dump_list(address.email for address in user_2.addresses)
+ bperson@example.com
+ >>> dump_list(user.real_name for user in user_manager.users)
+ <BLANKLINE>
+ Anne Person
As above, you can assign a real name to such users.
>>> user_2.real_name = 'Ben Person'
- >>> sorted(user.real_name for user in user_manager.users)
- [u'Anne Person', u'Ben Person']
+ >>> dump_list(user.real_name for user in user_manager.users)
+ Anne Person
+ Ben Person
You can also create a user with just a real name.
>>> user_3 = user_manager.create_user(real_name='Claire Person')
>>> verifyObject(IUser, user_3)
True
- >>> sorted(address.address for address in user.addresses)
- []
- >>> sorted(user.real_name for user in user_manager.users)
- [u'Anne Person', u'Ben Person', u'Claire Person']
+ >>> dump_list(address.email for address in user.addresses)
+ *Empty*
+ >>> dump_list(user.real_name for user in user_manager.users)
+ Anne Person
+ Ben Person
+ Claire Person
Finally, you can create a user with both an address and a real name.
>>> user_4 = user_manager.create_user('dperson@example.com', 'Dan Person')
>>> verifyObject(IUser, user_3)
True
- >>> sorted(address.address for address in user_4.addresses)
- [u'dperson@example.com']
- >>> sorted(address.real_name for address in user_4.addresses)
- [u'Dan Person']
- >>> sorted(user.real_name for user in user_manager.users)
- [u'Anne Person', u'Ben Person', u'Claire Person', u'Dan Person']
+ >>> dump_list(address.email for address in user_4.addresses)
+ dperson@example.com
+ >>> dump_list(address.real_name for address in user_4.addresses)
+ Dan Person
+ >>> dump_list(user.real_name for user in user_manager.users)
+ Anne Person
+ Ben Person
+ Claire Person
+ Dan Person
Deleting users
@@ -94,8 +101,10 @@ You delete users by going through the user manager. The deleted user is no
longer available through the user manager iterator.
>>> user_manager.delete_user(user)
- >>> sorted(user.real_name for user in user_manager.users)
- [u'Ben Person', u'Claire Person', u'Dan Person']
+ >>> dump_list(user.real_name for user in user_manager.users)
+ Ben Person
+ Claire Person
+ Dan Person
Finding users
@@ -107,7 +116,7 @@ that the ``.get_user()`` method takes a string email address, not an
``IAddress`` object.
>>> address = list(user_4.addresses)[0]
- >>> found_user = user_manager.get_user(address.address)
+ >>> found_user = user_manager.get_user(address.email)
>>> found_user
<User "Dan Person" at ...>
>>> found_user is user_4
@@ -119,5 +128,5 @@ with it, you will get ``None`` back.
>>> print user_manager.get_user('zperson@example.com')
None
>>> user_4.unlink(address)
- >>> print user_manager.get_user(address.address)
+ >>> print user_manager.get_user(address.email)
None
diff --git a/src/mailman/model/docs/users.txt b/src/mailman/model/docs/users.txt
index b36c250f9..bbfef8391 100644
--- a/src/mailman/model/docs/users.txt
+++ b/src/mailman/model/docs/users.txt
@@ -21,19 +21,19 @@ Users may have a real name and a password.
>>> user_1 = user_manager.create_user()
>>> user_1.password = 'my password'
>>> user_1.real_name = 'Zoe Person'
- >>> sorted(user.real_name for user in user_manager.users)
- [u'Zoe Person']
- >>> sorted(user.password for user in user_manager.users)
- [u'my password']
+ >>> dump_list(user.real_name for user in user_manager.users)
+ Zoe Person
+ >>> dump_list(user.password for user in user_manager.users)
+ my password
The password and real name can be changed at any time.
>>> user_1.real_name = 'Zoe X. Person'
>>> user_1.password = 'another password'
- >>> sorted(user.real_name for user in user_manager.users)
- [u'Zoe X. Person']
- >>> sorted(user.password for user in user_manager.users)
- [u'another password']
+ >>> dump_list(user.real_name for user in user_manager.users)
+ Zoe X. Person
+ >>> dump_list(user.password for user in user_manager.users)
+ another password
Users addresses
@@ -50,19 +50,25 @@ address on a user object.
<Address: Zoe Person <zperson@example.com> [not verified] at 0x...>
>>> user_1.register('zperson@example.org')
<Address: zperson@example.org [not verified] at 0x...>
- >>> sorted(address.address for address in user_1.addresses)
- [u'zperson@example.com', u'zperson@example.org']
- >>> sorted(address.real_name for address in user_1.addresses)
- [u'', u'Zoe Person']
+ >>> dump_list(address.email for address in user_1.addresses)
+ zperson@example.com
+ zperson@example.org
+ >>> dump_list(address.real_name for address in user_1.addresses)
+ <BLANKLINE>
+ Zoe Person
You can also create the address separately and then link it to the user.
>>> address_1 = user_manager.create_address('zperson@example.net')
>>> user_1.link(address_1)
- >>> sorted(address.address for address in user_1.addresses)
- [u'zperson@example.com', u'zperson@example.net', u'zperson@example.org']
- >>> sorted(address.real_name for address in user_1.addresses)
- [u'', u'', u'Zoe Person']
+ >>> dump_list(address.email for address in user_1.addresses)
+ zperson@example.com
+ zperson@example.net
+ zperson@example.org
+ >>> dump_list(address.real_name for address in user_1.addresses)
+ <BLANKLINE>
+ <BLANKLINE>
+ Zoe Person
But don't try to link an address to more than one user.
@@ -74,7 +80,7 @@ But don't try to link an address to more than one user.
You can also ask whether a given user controls a given address.
- >>> user_1.controls(address_1.address)
+ >>> user_1.controls(address_1.email)
True
>>> user_1.controls('bperson@example.com')
False
@@ -163,8 +169,10 @@ membership role.
::
>>> user_1.link(address_1)
- >>> sorted(address.address for address in user_1.addresses)
- [u'zperson@example.com', u'zperson@example.net', u'zperson@example.org']
+ >>> dump_list(address.email for address in user_1.addresses)
+ zperson@example.com
+ zperson@example.net
+ zperson@example.org
>>> com = user_manager.get_address('zperson@example.com')
>>> org = user_manager.get_address('zperson@example.org')
>>> net = user_manager.get_address('zperson@example.net')
@@ -194,10 +202,10 @@ membership role.
>>> len(members)
4
>>> def sortkey(member):
- ... return (member.address.address, member.mailing_list,
+ ... return (member.address.email, member.mailing_list,
... int(member.role))
>>> for member in sorted(members, key=sortkey):
- ... print member.address.address, member.mailing_list, member.role
+ ... print member.address.email, member.mailing_list, member.role
zperson@example.com xtest_1@example.com MemberRole.member
zperson@example.net xtest_3@example.com MemberRole.moderator
zperson@example.org xtest_2@example.com MemberRole.member
diff --git a/src/mailman/model/member.py b/src/mailman/model/member.py
index 5dde5c629..8723a3ca4 100644
--- a/src/mailman/model/member.py
+++ b/src/mailman/model/member.py
@@ -110,7 +110,7 @@ class Member(Model):
@property
def options_url(self):
# XXX Um, this is definitely wrong
- return 'http://example.com/' + self.address.address
+ return 'http://example.com/' + self.address.email
def unsubscribe(self):
config.db.store.remove(self.preferences)
diff --git a/src/mailman/model/roster.py b/src/mailman/model/roster.py
index 89ef98531..2b098ff54 100644
--- a/src/mailman/model/roster.py
+++ b/src/mailman/model/roster.py
@@ -99,7 +99,7 @@ class AbstractRoster:
Member,
Member.mailing_list == self._mlist.fqdn_listname,
Member.role == self.role,
- Address.address == address,
+ Address.email == address,
Member.address_id == Address.id)
if results.count() == 0:
return None
@@ -169,7 +169,7 @@ class AdministratorRoster(AbstractRoster):
Member.mailing_list == self._mlist.fqdn_listname,
Or(Member.role == MemberRole.moderator,
Member.role == MemberRole.owner),
- Address.address == address,
+ Address.email == address,
Member.address_id == Address.id)
if results.count() == 0:
return None
diff --git a/src/mailman/model/user.py b/src/mailman/model/user.py
index 327a3b184..9aa751ea1 100644
--- a/src/mailman/model/user.py
+++ b/src/mailman/model/user.py
@@ -66,28 +66,28 @@ class User(Model):
raise AddressNotLinkedError(address)
address.user = None
- def controls(self, address):
+ def controls(self, email):
"""See `IUser`."""
- found = config.db.store.find(Address, address=address)
+ found = config.db.store.find(Address, email=email)
if found.count() == 0:
return False
assert found.count() == 1, 'Unexpected count'
return found[0].user is self
- def register(self, address, real_name=None):
+ def register(self, email, real_name=None):
"""See `IUser`."""
# First, see if the address already exists
- addrobj = config.db.store.find(Address, address=address).one()
- if addrobj is None:
+ address = config.db.store.find(Address, email=email).one()
+ if address is None:
if real_name is None:
real_name = ''
- addrobj = Address(address=address, real_name=real_name)
- addrobj.preferences = Preferences()
+ address = Address(email=email, real_name=real_name)
+ address.preferences = Preferences()
# Link the address to the user if it is not already linked.
- if addrobj.user is not None:
- raise AddressAlreadyLinkedError(addrobj)
- addrobj.user = self
- return addrobj
+ if address.user is not None:
+ raise AddressAlreadyLinkedError(address)
+ address.user = self
+ return address
@property
def memberships(self):
diff --git a/src/mailman/model/usermanager.py b/src/mailman/model/usermanager.py
index da12ba33c..11e9ad24e 100644
--- a/src/mailman/model/usermanager.py
+++ b/src/mailman/model/usermanager.py
@@ -39,13 +39,12 @@ from mailman.model.user import User
class UserManager:
implements(IUserManager)
- def create_user(self, address=None, real_name=None):
+ def create_user(self, email=None, real_name=None):
user = User()
user.real_name = ('' if real_name is None else real_name)
- if address:
- addrobj = Address(address, user.real_name)
- addrobj.preferences = Preferences()
- user.link(addrobj)
+ if email:
+ address = self.create_address(email, real_name)
+ user.link(address)
user.preferences = Preferences()
config.db.store.add(user)
return user
@@ -53,13 +52,8 @@ class UserManager:
def delete_user(self, user):
config.db.store.remove(user)
- @property
- def users(self):
- for user in config.db.store.find(User):
- yield user
-
- def get_user(self, address):
- addresses = config.db.store.find(Address, address=address.lower())
+ def get_user(self, email):
+ addresses = config.db.store.find(Address, email=email.lower())
if addresses.count() == 0:
return None
elif addresses.count() == 1:
@@ -67,17 +61,22 @@ class UserManager:
else:
raise AssertionError('Unexpected query count')
- def create_address(self, address, real_name=None):
- addresses = config.db.store.find(Address, address=address.lower())
+ @property
+ def users(self):
+ for user in config.db.store.find(User):
+ yield user
+
+ def create_address(self, email, real_name=None):
+ addresses = config.db.store.find(Address, email=email.lower())
if addresses.count() == 1:
found = addresses[0]
- raise ExistingAddressError(found.original_address)
+ raise ExistingAddressError(found.original_email)
assert addresses.count() == 0, 'Unexpected results'
if real_name is None:
real_name = ''
- # It's okay not to lower case the 'address' argument because the
+ # It's okay not to lower case the 'email' argument because the
# constructor will do the right thing.
- address = Address(address, real_name)
+ address = Address(email, real_name)
address.preferences = Preferences()
config.db.store.add(address)
return address
@@ -89,8 +88,8 @@ class UserManager:
address.user.unlink(address)
config.db.store.remove(address)
- def get_address(self, address):
- addresses = config.db.store.find(Address, address=address.lower())
+ def get_address(self, email):
+ addresses = config.db.store.find(Address, email=email.lower())
if addresses.count() == 0:
return None
elif addresses.count() == 1:
diff --git a/src/mailman/pipeline/calculate_recipients.py b/src/mailman/pipeline/calculate_recipients.py
index 8ff799f85..3892b3085 100644
--- a/src/mailman/pipeline/calculate_recipients.py
+++ b/src/mailman/pipeline/calculate_recipients.py
@@ -91,12 +91,12 @@ delivery. The original message as received by Mailman is attached.
""")
raise errors.RejectMessage(Utils.wrap(text))
# Calculate the regular recipients of the message
- recipients = set(member.address.address
+ recipients = set(member.address.email
for member in mlist.regular_members.members
if member.delivery_status == DeliveryStatus.enabled)
# Remove the sender if they don't want to receive their own posts
- if not include_sender and member.address.address in recipients:
- recipients.remove(member.address.address)
+ if not include_sender and member.address.email in recipients:
+ recipients.remove(member.address.email)
# Handle topic classifications
do_topic_filters(mlist, msg, msgdata, recipients)
# Bookkeeping
diff --git a/src/mailman/pipeline/decorate.py b/src/mailman/pipeline/decorate.py
index 66ec83e90..4648b17f3 100644
--- a/src/mailman/pipeline/decorate.py
+++ b/src/mailman/pipeline/decorate.py
@@ -57,12 +57,12 @@ def process(mlist, msg, msgdata):
member = mlist.members.get_member(recipient)
d['user_address'] = recipient
if user is not None and member is not None:
- d['user_delivered_to'] = member.address.original_address
+ d['user_delivered_to'] = member.address.original_email
# BAW: Hmm, should we allow this?
d['user_password'] = user.password
d['user_language'] = member.preferred_language.description
d['user_name'] = (user.real_name if user.real_name
- else member.address.original_address)
+ else member.address.original_email)
d['user_optionsurl'] = member.options_url
# These strings are descriptive for the log file and shouldn't be i18n'd
d.update(msgdata.get('decoration-data', {}))
diff --git a/src/mailman/pipeline/docs/calc-recips.txt b/src/mailman/pipeline/docs/calc-recips.txt
index 27c2ba806..efa1bc9c7 100644
--- a/src/mailman/pipeline/docs/calc-recips.txt
+++ b/src/mailman/pipeline/docs/calc-recips.txt
@@ -59,8 +59,9 @@ but not all of the recipients.
>>> handler = config.handlers['calculate-recipients']
>>> handler.process(mlist, msg, msgdata)
- >>> sorted(msgdata['recipients'])
- [u'qperson@example.com', u'zperson@example.com']
+ >>> dump_list(msgdata['recipients'])
+ qperson@example.com
+ zperson@example.com
Regular delivery recipients
@@ -71,8 +72,10 @@ soon as they are posted. In other words, these folks are not digest members.
>>> msgdata = {}
>>> handler.process(mlist, msg, msgdata)
- >>> sorted(msgdata['recipients'])
- [u'aperson@example.com', u'bperson@example.com', u'cperson@example.com']
+ >>> dump_list(msgdata['recipients'])
+ aperson@example.com
+ bperson@example.com
+ cperson@example.com
Members can elect not to receive a list copy of their own postings.
@@ -84,8 +87,9 @@ Members can elect not to receive a list copy of their own postings.
... """)
>>> msgdata = {}
>>> handler.process(mlist, msg, msgdata)
- >>> sorted(msgdata['recipients'])
- [u'aperson@example.com', u'bperson@example.com']
+ >>> dump_list(msgdata['recipients'])
+ aperson@example.com
+ bperson@example.com
Members can also elect not to receive a list copy of any message on which they
are explicitly named as a recipient. However, see the `avoid duplicates`_
diff --git a/src/mailman/pipeline/docs/file-recips.txt b/src/mailman/pipeline/docs/file-recips.txt
index c7eeb9ce8..c994f820e 100644
--- a/src/mailman/pipeline/docs/file-recips.txt
+++ b/src/mailman/pipeline/docs/file-recips.txt
@@ -30,8 +30,8 @@ returns.
<BLANKLINE>
A message.
<BLANKLINE>
- >>> msgdata
- {u'recipients': 7}
+ >>> dump_msgdata(msgdata)
+ recipients: 7
Missing file
@@ -50,8 +50,8 @@ empty.
No such file or directory: u'.../_xtest@example.com/members.txt'
>>> msgdata = {}
>>> handler.process(mlist, msg, msgdata)
- >>> sorted(msgdata['recipients'])
- []
+ >>> dump_list(msgdata['recipients'])
+ *Empty*
Existing file
@@ -74,9 +74,13 @@ addresses are returned as the set of recipients.
>>> msgdata = {}
>>> handler.process(mlist, msg, msgdata)
- >>> sorted(msgdata['recipients'])
- ['bperson@example.com', 'cperson@example.com', 'dperson@example.com',
- 'eperson@example.com', 'fperson@example.com', 'gperson@example.com']
+ >>> dump_list(msgdata['recipients'])
+ bperson@example.com
+ cperson@example.com
+ dperson@example.com
+ eperson@example.com
+ fperson@example.com
+ gperson@example.com
However, if the sender of the original message is a member of the list and
their address is in the include file, the sender's address is *not* included
@@ -99,6 +103,9 @@ in the recipients list.
... """)
>>> msgdata = {}
>>> handler.process(mlist, msg, msgdata)
- >>> sorted(msgdata['recipients'])
- ['bperson@example.com', 'dperson@example.com',
- 'eperson@example.com', 'fperson@example.com', 'gperson@example.com']
+ >>> dump_list(msgdata['recipients'])
+ bperson@example.com
+ dperson@example.com
+ eperson@example.com
+ fperson@example.com
+ gperson@example.com
diff --git a/src/mailman/pipeline/file_recipients.py b/src/mailman/pipeline/file_recipients.py
index bd163a283..f4cf7a4c6 100644
--- a/src/mailman/pipeline/file_recipients.py
+++ b/src/mailman/pipeline/file_recipients.py
@@ -61,5 +61,5 @@ class FileRecipients:
# recipients.
member = mlist.members.get_member(msg.sender)
if member is not None:
- addrs.discard(member.address.address)
+ addrs.discard(member.address.email)
msgdata['recipients'] = addrs
diff --git a/src/mailman/queue/digest.py b/src/mailman/queue/digest.py
index 46228580d..3b2aff11c 100644
--- a/src/mailman/queue/digest.py
+++ b/src/mailman/queue/digest.py
@@ -337,7 +337,7 @@ class DigestRunner(Runner):
continue
# Send the digest to the case-preserved address of the digest
# members.
- email_address = member.address.original_address
+ email_address = member.address.original_email
if member.delivery_mode == DeliveryMode.plaintext_digests:
rfc1153_recipients.add(email_address)
elif member.delivery_mode == DeliveryMode.mime_digests:
@@ -349,9 +349,9 @@ class DigestRunner(Runner):
# Add also the folks who are receiving one last digest.
for address, delivery_mode in mlist.last_digest_recipients:
if delivery_mode == DeliveryMode.plaintext_digests:
- rfc1153_recipients.add(address.original_address)
+ rfc1153_recipients.add(address.original_email)
elif delivery_mode == DeliveryMode.mime_digests:
- mime_recipients.add(address.original_address)
+ mime_recipients.add(address.original_email)
else:
raise AssertionError(
'OLD recipient "{0}" unexpected delivery mode: {1}'.format(
diff --git a/src/mailman/queue/docs/incoming.txt b/src/mailman/queue/docs/incoming.txt
index b0f644098..75130c4cc 100644
--- a/src/mailman/queue/docs/incoming.txt
+++ b/src/mailman/queue/docs/incoming.txt
@@ -71,7 +71,7 @@ not linked to a user and are unverified.
... email = '{0}@example.com'.format(localpart)
... address = user_manager.get_address(email)
... print '{0}; verified? {1}; user? {2}'.format(
- ... address.address,
+ ... address.email,
... ('No' if address.verified_on is None else 'Yes'),
... user_manager.get_user(email))
xperson@example.com; verified? No; user? None
diff --git a/src/mailman/rest/adapters.py b/src/mailman/rest/adapters.py
index 817032ccf..4a3ec9ebd 100644
--- a/src/mailman/rest/adapters.py
+++ b/src/mailman/rest/adapters.py
@@ -51,7 +51,7 @@ class SubscriptionService:
# XXX 2010-02-24 barry Clean this up.
# lazr.restful requires the return value to be a concrete list.
members = []
- address_of_member = attrgetter('address.address')
+ address_of_member = attrgetter('address.email')
list_manager = getUtility(IListManager)
for fqdn_listname in sorted(list_manager.names):
mailing_list = list_manager.get(fqdn_listname)
diff --git a/src/mailman/rest/members.py b/src/mailman/rest/members.py
index 1b3023865..243836fbe 100644
--- a/src/mailman/rest/members.py
+++ b/src/mailman/rest/members.py
@@ -51,9 +51,9 @@ class _MemberBase(resource.Resource, CollectionMixin):
enum, dot, role = str(member.role).partition('.')
return dict(
fqdn_listname=member.mailing_list,
- address=member.address.address,
+ address=member.address.email,
self_link=path_to('lists/{0}/{1}/{2}'.format(
- member.mailing_list, role, member.address.address)),
+ member.mailing_list, role, member.address.email)),
)
def _get_collection(self, request):
@@ -114,7 +114,7 @@ class AllMembers(_MemberBase):
# wsgiref wants headers to be bytes, not unicodes. Also, we have to
# quote any unsafe characters in the address. Specifically, we need
# to quote forward slashes, but not @-signs.
- quoted_address = quote(member.address.address, safe=b'@')
+ quoted_address = quote(member.address.email, safe=b'@')
location = path_to('lists/{0}/member/{1}'.format(
member.mailing_list, quoted_address))
# Include no extra headers or body.
@@ -140,7 +140,7 @@ class MembersOfList(_MemberBase):
# Overrides _MemberBase._get_collection() because we only want to
# return the members from the requested roster.
roster = self._mlist.get_roster(self._role)
- address_of_member = attrgetter('address.address')
+ address_of_member = attrgetter('address.email')
return list(sorted(roster.members, key=address_of_member))
@resource.GET()
diff --git a/src/mailman/tests/test_documentation.py b/src/mailman/tests/test_documentation.py
index f89979ba2..a8157a18f 100644
--- a/src/mailman/tests/test_documentation.py
+++ b/src/mailman/tests/test_documentation.py
@@ -110,7 +110,7 @@ def dump_msgdata(msgdata, *additional_skips):
print '{0:{2}}: {1}'.format(key, msgdata[key], longest)
-def dump_list(list_of_things, key=None):
+def dump_list(list_of_things, key=str):
"""Print items in a string to get rid of stupid u'' prefixes."""
# List of things may be a generator.
list_of_things = list(list_of_things)