summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/mailman/Archiver/HyperArch.py6
-rw-r--r--src/mailman/app/bounces.py6
-rw-r--r--src/mailman/app/docs/bounces.rst12
-rw-r--r--src/mailman/app/docs/lifecycle.rst10
-rw-r--r--src/mailman/app/docs/message.rst61
-rw-r--r--src/mailman/app/membership.py19
-rw-r--r--src/mailman/app/moderator.py22
-rw-r--r--src/mailman/app/notifications.py31
-rw-r--r--src/mailman/app/registrar.py10
-rw-r--r--src/mailman/app/subscriptions.py8
-rw-r--r--src/mailman/app/tests/test_bounces.py19
-rw-r--r--src/mailman/app/tests/test_notifications.py2
-rw-r--r--src/mailman/archiving/docs/common.rst9
-rw-r--r--src/mailman/archiving/prototype.py59
-rw-r--r--src/mailman/archiving/tests/__init__.py0
-rw-r--r--src/mailman/archiving/tests/test_prototype.py174
-rw-r--r--src/mailman/commands/cli_lists.py2
-rw-r--r--src/mailman/commands/cli_members.py18
-rw-r--r--src/mailman/commands/cli_withlist.py4
-rw-r--r--src/mailman/commands/docs/import.rst4
-rw-r--r--src/mailman/commands/docs/info.rst1
-rw-r--r--src/mailman/commands/docs/membership.rst2
-rw-r--r--src/mailman/commands/docs/withlist.rst18
-rw-r--r--src/mailman/commands/eml_membership.py8
-rw-r--r--src/mailman/config/config.py1
-rw-r--r--src/mailman/config/schema.cfg3
-rw-r--r--src/mailman/database/schema/postgres.sql6
-rw-r--r--src/mailman/database/schema/sqlite.sql6
-rw-r--r--src/mailman/docs/8-miles-high.rst147
-rw-r--r--src/mailman/docs/NEWS.rst14
-rw-r--r--src/mailman/email/message.py16
-rw-r--r--src/mailman/email/tests/__init__.py0
-rw-r--r--src/mailman/email/tests/test_message.py60
-rw-r--r--src/mailman/interfaces/address.py4
-rw-r--r--src/mailman/interfaces/mailinglist.py8
-rw-r--r--src/mailman/interfaces/registrar.py6
-rw-r--r--src/mailman/interfaces/roster.py3
-rw-r--r--src/mailman/interfaces/subscriptions.py6
-rw-r--r--src/mailman/interfaces/user.py14
-rw-r--r--src/mailman/interfaces/usermanager.py14
-rw-r--r--src/mailman/model/address.py8
-rw-r--r--src/mailman/model/docs/addresses.rst12
-rw-r--r--src/mailman/model/docs/pending.rst12
-rw-r--r--src/mailman/model/docs/registration.rst2
-rw-r--r--src/mailman/model/docs/requests.rst10
-rw-r--r--src/mailman/model/docs/usermanager.rst22
-rw-r--r--src/mailman/model/docs/users.rst12
-rw-r--r--src/mailman/model/mailinglist.py4
-rw-r--r--src/mailman/model/roster.py69
-rw-r--r--src/mailman/model/tests/test_roster.py156
-rw-r--r--src/mailman/model/user.py16
-rw-r--r--src/mailman/model/usermanager.py14
-rw-r--r--src/mailman/mta/personalized.py2
-rw-r--r--src/mailman/pipeline/acknowledge.py7
-rw-r--r--src/mailman/pipeline/calculate_recipients.py4
-rw-r--r--src/mailman/pipeline/decorate.py6
-rw-r--r--src/mailman/pipeline/docs/acknowledge.rst42
-rw-r--r--src/mailman/pipeline/docs/decorate.rst6
-rw-r--r--src/mailman/pipeline/docs/replybot.rst2
-rw-r--r--src/mailman/pipeline/replybot.py14
-rw-r--r--src/mailman/rest/addresses.py4
-rw-r--r--src/mailman/rest/configuration.py2
-rw-r--r--src/mailman/rest/docs/addresses.rst6
-rw-r--r--src/mailman/rest/docs/configuration.rst20
-rw-r--r--src/mailman/rest/docs/domains.rst3
-rw-r--r--src/mailman/rest/docs/lists.rst12
-rw-r--r--src/mailman/rest/docs/membership.rst4
-rw-r--r--src/mailman/rest/docs/users.rst24
-rw-r--r--src/mailman/rest/helpers.py4
-rw-r--r--src/mailman/rest/lists.py6
-rw-r--r--src/mailman/rest/members.py4
-rw-r--r--src/mailman/rest/tests/test_lists.py53
-rw-r--r--src/mailman/rest/tests/test_membership.py4
-rw-r--r--src/mailman/rest/users.py8
-rw-r--r--src/mailman/rules/administrivia.py2
-rw-r--r--src/mailman/rules/moderation.py4
-rw-r--r--src/mailman/rules/suspicious.py4
-rw-r--r--src/mailman/runners/digest.py7
-rw-r--r--src/mailman/runners/docs/digester.rst10
-rw-r--r--src/mailman/styles/default.py4
-rw-r--r--src/mailman/templates/en/footer-generic.txt2
-rw-r--r--src/mailman/templates/en/masthead.txt4
-rw-r--r--src/mailman/templates/en/postack.txt2
-rw-r--r--src/mailman/testing/mailman-fr.mobin1945 -> 1992 bytes
-rw-r--r--src/mailman/testing/mailman-fr.po12
-rw-r--r--src/mailman/testing/mailman-xx.mobin509 -> 515 bytes
-rw-r--r--src/mailman/testing/mailman-xx.po4
-rw-r--r--src/mailman/utilities/importer.py1
-rw-r--r--src/mailman/utilities/tests/test_import.py8
89 files changed, 1029 insertions, 422 deletions
diff --git a/src/mailman/Archiver/HyperArch.py b/src/mailman/Archiver/HyperArch.py
index 017c14342..1419b56bc 100644
--- a/src/mailman/Archiver/HyperArch.py
+++ b/src/mailman/Archiver/HyperArch.py
@@ -415,7 +415,7 @@ class Article(pipermail.Article):
d["datestr_html"] = self.quote(ctime(int(self.date)))
d["body"] = self._get_body()
d['listurl'] = self._mlist.script_url('listinfo')
- d['listname'] = self._mlist.real_name
+ d['listname'] = self._mlist.display_name
d['encoding'] = ''
charset = self._lang.charset
d["encoding"] = html_charset % charset
@@ -639,7 +639,7 @@ class HyperArchive(pipermail.T):
return html_quote(ctime(s), self.lang.code)
# Avoid i18n side-effects
with _.using(mlist.preferred_language.code):
- d = {"listname": html_quote(mlist.real_name, self.lang.code),
+ d = {"listname": html_quote(mlist.display_name, self.lang.code),
"archtype": self.type,
"archive": self.volNameToDesc(self.archive),
"listinfo": mlist.script_url('listinfo'),
@@ -672,7 +672,7 @@ class HyperArchive(pipermail.T):
mlist = self.maillist
listname = mlist.fqdn_listname
mbox = os.path.join(mlist.archive_dir()+'.mbox', listname+'.mbox')
- d = {"listname": mlist.real_name,
+ d = {"listname": mlist.display_name,
"listinfo": mlist.script_url('listinfo'),
"fullarch": '../%s.mbox/%s.mbox' % (listname, listname),
"size": sizeof(mbox, mlist.preferred_language),
diff --git a/src/mailman/app/bounces.py b/src/mailman/app/bounces.py
index a9bed97ac..d88621e9b 100644
--- a/src/mailman/app/bounces.py
+++ b/src/mailman/app/bounces.py
@@ -215,7 +215,7 @@ def send_probe(member, msg):
)
# Calculate the Subject header, in the member's preferred language.
with _.using(member.preferred_language.code):
- subject = _('$mlist.real_name mailing list probe message')
+ subject = _('$mlist.display_name mailing list probe message')
# Craft the probe message. This will be a multipart where the first part
# is the probe text and the second part is the message that caused this
# probe to be sent.
@@ -225,7 +225,9 @@ def send_probe(member, msg):
notice = MIMEText(text, _charset=mlist.preferred_language.charset)
probe.attach(notice)
probe.attach(MIMEMessage(msg))
- probe.send(mlist, envsender=probe_sender, verp=False, probe_token=token)
+ # Probes should not have the Precedence: bulk header.
+ probe.send(mlist, envsender=probe_sender, verp=False, probe_token=token,
+ add_precedence=False)
return token
diff --git a/src/mailman/app/docs/bounces.rst b/src/mailman/app/docs/bounces.rst
index f825064e3..5510f2207 100644
--- a/src/mailman/app/docs/bounces.rst
+++ b/src/mailman/app/docs/bounces.rst
@@ -12,12 +12,12 @@ Mailman can bounce messages back to the original sender. This is essentially
equivalent to rejecting the message with notification. Mailing lists can
bounce a message with an optional error message.
- >>> mlist = create_list('_xtest@example.com')
+ >>> mlist = create_list('text@example.com')
Any message can be bounced.
>>> msg = message_from_string("""\
- ... To: _xtest@example.com
+ ... To: text@example.com
... From: aperson@example.com
... Subject: Something important
...
@@ -36,7 +36,7 @@ to the original message author.
1
>>> print items[0].msg.as_string()
Subject: Something important
- From: _xtest-owner@example.com
+ From: text-owner@example.com
To: aperson@example.com
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="..."
@@ -54,7 +54,7 @@ to the original message author.
Content-Type: message/rfc822
MIME-Version: 1.0
<BLANKLINE>
- To: _xtest@example.com
+ To: text@example.com
From: aperson@example.com
Subject: Something important
<BLANKLINE>
@@ -74,7 +74,7 @@ passed in as an instance of a ``RejectMessage`` exception.
1
>>> print items[0].msg.as_string()
Subject: Something important
- From: _xtest-owner@example.com
+ From: text-owner@example.com
To: aperson@example.com
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="..."
@@ -92,7 +92,7 @@ passed in as an instance of a ``RejectMessage`` exception.
Content-Type: message/rfc822
MIME-Version: 1.0
<BLANKLINE>
- To: _xtest@example.com
+ To: text@example.com
From: aperson@example.com
Subject: Something important
<BLANKLINE>
diff --git a/src/mailman/app/docs/lifecycle.rst b/src/mailman/app/docs/lifecycle.rst
index d8356db74..c9d3ed10d 100644
--- a/src/mailman/app/docs/lifecycle.rst
+++ b/src/mailman/app/docs/lifecycle.rst
@@ -119,13 +119,13 @@ the system, they won't be created again.
>>> user_b = user_manager.get_user('bperson@example.com')
>>> user_c = user_manager.get_user('cperson@example.com')
>>> user_d = user_manager.get_user('dperson@example.com')
- >>> user_a.real_name = 'Anne Person'
- >>> user_b.real_name = 'Bart Person'
- >>> user_c.real_name = 'Caty Person'
- >>> user_d.real_name = 'Dirk Person'
+ >>> user_a.display_name = 'Anne Person'
+ >>> user_b.display_name = 'Bart Person'
+ >>> user_c.display_name = 'Caty Person'
+ >>> user_d.display_name = 'Dirk Person'
>>> mlist_3 = create_list('test_3@example.com', owners)
- >>> dump_list(user.real_name for user in mlist_3.owners.users)
+ >>> dump_list(user.display_name for user in mlist_3.owners.users)
Anne Person
Bart Person
Caty Person
diff --git a/src/mailman/app/docs/message.rst b/src/mailman/app/docs/message.rst
index 3e3293196..3c3fd8ea8 100644
--- a/src/mailman/app/docs/message.rst
+++ b/src/mailman/app/docs/message.rst
@@ -2,7 +2,7 @@
Messages
========
-Mailman has its own Message classes, derived from the standard
+Mailman has its own `Message` classes, derived from the standard
``email.message.Message`` class, but providing additional useful methods.
@@ -13,7 +13,7 @@ When Mailman needs to send a message to a user, it creates a
``UserNotification`` instance, and then calls the ``.send()`` method on this
object. This method requires a mailing list instance.
- >>> mlist = create_list('_xtest@example.com')
+ >>> mlist = create_list('test@example.com')
The ``UserNotification`` constructor takes the recipient address, the sender
address, an optional subject, optional body text, and optional language.
@@ -21,28 +21,69 @@ address, an optional subject, optional body text, and optional language.
>>> from mailman.email.message import UserNotification
>>> msg = UserNotification(
... 'aperson@example.com',
- ... '_xtest@example.com',
+ ... 'test@example.com',
... 'Something you need to know',
... 'I needed to tell you this.')
>>> msg.send(mlist)
The message will end up in the `virgin` queue.
- >>> switchboard = config.switchboards['virgin']
- >>> len(switchboard.files)
+ >>> from mailman.testing.helpers import get_queue_messages
+ >>> messages = get_queue_messages('virgin')
+ >>> len(messages)
1
- >>> filebase = switchboard.files[0]
- >>> qmsg, qmsgdata = switchboard.dequeue(filebase)
- >>> switchboard.finish(filebase)
- >>> print qmsg.as_string()
+ >>> print messages[0].msg.as_string()
MIME-Version: 1.0
Content-Type: text/plain; charset="us-ascii"
Content-Transfer-Encoding: 7bit
Subject: Something you need to know
- From: _xtest@example.com
+ From: test@example.com
To: aperson@example.com
Message-ID: ...
Date: ...
Precedence: bulk
<BLANKLINE>
I needed to tell you this.
+
+The message above got a `Precedence: bulk` header added by default. If the
+message we're sending already has a `Precedence:` header, it shouldn't be
+changed.
+
+ >>> del msg['precedence']
+ >>> msg['Precedence'] = 'list'
+ >>> msg.send(mlist)
+
+Again, the message will end up in the `virgin` queue but with the original
+`Precedence:` header.
+
+ >>> messages = get_queue_messages('virgin')
+ >>> len(messages)
+ 1
+ >>> print messages[0].msg['precedence']
+ list
+
+Sometimes we want to send the message without a `Precedence:` header such as
+when we send a probe message.
+
+ >>> del msg['precedence']
+ >>> msg.send(mlist, add_precedence=False)
+
+Again, the message will end up in the `virgin` queue but without the
+`Precedence:` header.
+
+ >>> messages = get_queue_messages('virgin')
+ >>> len(messages)
+ 1
+ >>> print messages[0].msg['precedence']
+ None
+
+However, if the message already has a `Precedence:` header, setting the
+`precedence=False` argument will have no effect.
+
+ >>> msg['Precedence'] = 'junk'
+ >>> msg.send(mlist, add_precedence=False)
+ >>> messages = get_queue_messages('virgin')
+ >>> len(messages)
+ 1
+ >>> print messages[0].msg['precedence']
+ junk
diff --git a/src/mailman/app/membership.py b/src/mailman/app/membership.py
index 1d075afcd..e31a1695c 100644
--- a/src/mailman/app/membership.py
+++ b/src/mailman/app/membership.py
@@ -43,7 +43,7 @@ from mailman.utilities.i18n import make
-def add_member(mlist, email, realname, password, delivery_mode, language,
+def add_member(mlist, email, display_name, password, delivery_mode, language,
role=MemberRole.member):
"""Add a member right now.
@@ -54,8 +54,8 @@ def add_member(mlist, email, realname, password, delivery_mode, language,
:type mlist: `IMailingList`
:param email: The email address to subscribe.
:type email: str
- :param realname: The subscriber's full name.
- :type realname: str
+ :param display_name: The subscriber's full name.
+ :type display_name: str
:param password: The subscriber's plain text password.
:type password: str
:param delivery_mode: The delivery mode the subscriber has chosen.
@@ -86,14 +86,15 @@ def add_member(mlist, email, realname, password, delivery_mode, language,
if address is None:
# Nope, we don't even know about this address, so create both the
# user and address now.
- user = user_manager.create_user(email, realname)
+ user = user_manager.create_user(email, display_name)
# Do it this way so we don't have to flush the previous change.
address = list(user.addresses)[0]
else:
# The address object exists, but it's not linked to a user.
# Create the user and link it now.
user = user_manager.create_user()
- user.real_name = (realname if realname else address.real_name)
+ user.display_name = (
+ display_name if display_name else address.display_name)
user.link(address)
# Encrypt the password using the currently selected scheme. The
# scheme is recorded in the hashed password string.
@@ -148,12 +149,12 @@ def delete_member(mlist, email, admin_notif=None, userack=None):
# ...and to the administrator.
if admin_notif:
user = getUtility(IUserManager).get_user(email)
- realname = user.real_name
- subject = _('$mlist.real_name unsubscription notification')
+ display_name = user.display_name
+ subject = _('$mlist.display_name unsubscription notification')
text = make('adminunsubscribeack.txt',
mailing_list=mlist,
- listname=mlist.real_name,
- member=formataddr((realname, email)),
+ listname=mlist.display_name,
+ member=formataddr((display_name, email)),
)
msg = OwnerNotification(mlist, subject, text,
roster=mlist.administrators)
diff --git a/src/mailman/app/moderator.py b/src/mailman/app/moderator.py
index 01dc4a232..2e2711809 100644
--- a/src/mailman/app/moderator.py
+++ b/src/mailman/app/moderator.py
@@ -162,7 +162,7 @@ def handle_message(mlist, id, action,
# Get a copy of the original message from the message store.
msg = message_store.get_message_by_id(message_id)
# It's possible the forwarding address list is a comma separated list
- # of realname/address pairs.
+ # of display_name/address pairs.
addresses = [addr[1] for addr in getaddresses(forward)]
language = mlist.preferred_language
if len(addresses) == 1:
@@ -197,10 +197,10 @@ def handle_message(mlist, id, action,
-def hold_subscription(mlist, address, realname, password, mode, language):
+def hold_subscription(mlist, address, display_name, password, mode, language):
data = dict(when=now().isoformat(),
address=address,
- realname=realname,
+ display_name=display_name,
password=password,
delivery_mode=str(mode),
language=language)
@@ -213,7 +213,7 @@ def hold_subscription(mlist, address, realname, password, mode, language):
# Possibly notify the administrator in default list language
if mlist.admin_immed_notify:
subject = _(
- 'New subscription request to list $mlist.real_name from $address')
+ 'New subscription request to $mlist.display_name from $address')
text = make('subauth.txt',
mailing_list=mlist,
username=address,
@@ -249,11 +249,11 @@ def handle_subscription(mlist, id, action, comment=None):
enum_value = data['delivery_mode'].split('.')[-1]
delivery_mode = DeliveryMode(enum_value)
address = data['address']
- realname = data['realname']
+ display_name = data['display_name']
language = getUtility(ILanguageManager)[data['language']]
password = data['password']
try:
- add_member(mlist, address, realname, password,
+ add_member(mlist, address, display_name, password,
delivery_mode, language)
except AlreadySubscribedError:
# The address got subscribed in some other way after the original
@@ -264,9 +264,9 @@ def handle_subscription(mlist, id, action, comment=None):
send_welcome_message(mlist, address, language, delivery_mode)
if mlist.admin_notify_mchanges:
send_admin_subscription_notice(
- mlist, address, realname, language)
+ mlist, address, display_name, language)
slog.info('%s: new %s, %s %s', mlist.fqdn_listname,
- delivery_mode, formataddr((realname, address)),
+ delivery_mode, formataddr((display_name, address)),
'via admin approval')
else:
raise AssertionError('Unexpected action: {0}'.format(action))
@@ -285,7 +285,7 @@ def hold_unsubscription(mlist, address):
# Possibly notify the administrator of the hold
if mlist.admin_immed_notify:
subject = _(
- 'New unsubscription request from $mlist.real_name by $address')
+ 'New unsubscription request from $mlist.display_name by $address')
text = make('unsubauth.txt',
mailing_list=mlist,
address=address,
@@ -335,7 +335,7 @@ def _refuse(mlist, request, recip, comment, origmsg=None, lang=None):
# As this message is going to the requester, try to set the language to
# his/her language choice, if they are a member. Otherwise use the list's
# preferred language.
- realname = mlist.real_name
+ display_name = mlist.display_name
if lang is None:
member = mlist.members.get_member(recip)
lang = (mlist.preferred_language
@@ -357,7 +357,7 @@ def _refuse(mlist, request, recip, comment, origmsg=None, lang=None):
'---------- ' + _('Original Message') + ' ----------',
str(origmsg)
])
- subject = _('Request to mailing list "$realname" rejected')
+ subject = _('Request to mailing list "$display_name" rejected')
msg = UserNotification(recip, mlist.bounces_address, subject, text, lang)
msg.send(mlist)
diff --git a/src/mailman/app/notifications.py b/src/mailman/app/notifications.py
index 5604d5f05..bc5b326ac 100644
--- a/src/mailman/app/notifications.py
+++ b/src/mailman/app/notifications.py
@@ -17,7 +17,7 @@
"""Sending notifications."""
-from __future__ import unicode_literals
+from __future__ import absolute_import, print_function, unicode_literals
__metaclass__ = type
__all__ = [
@@ -80,12 +80,12 @@ def send_welcome_message(mlist, address, language, delivery_mode, text=''):
# Find the IMember object which is subscribed to the mailing list, because
# from there, we can get the member's options url.
member = mlist.members.get_member(address)
- user_name = member.user.real_name
+ user_name = member.user.display_name
options_url = member.options_url
# Get the text from the template.
text = expand(welcome, dict(
fqdn_listname=mlist.fqdn_listname,
- list_name=mlist.real_name,
+ list_name=mlist.display_name,
listinfo_uri=mlist.script_url('listinfo'),
list_requests=mlist.request_address,
user_name=user_name,
@@ -99,7 +99,7 @@ def send_welcome_message(mlist, address, language, delivery_mode, text=''):
msg = UserNotification(
formataddr((user_name, address)),
mlist.request_address,
- _('Welcome to the "$mlist.real_name" mailing list${digmode}'),
+ _('Welcome to the "$mlist.display_name" mailing list${digmode}'),
text, language)
msg['X-No-Archive'] = 'yes'
msg.send(mlist, verp=as_boolean(config.mta.verp_personalized_deliveries))
@@ -125,31 +125,32 @@ def send_goodbye_message(mlist, address, language):
goodbye = ''
msg = UserNotification(
address, mlist.bounces_address,
- _('You have been unsubscribed from the $mlist.real_name mailing list'),
+ _('You have been unsubscribed from the $mlist.display_name '
+ 'mailing list'),
goodbye, language)
msg.send(mlist, verp=as_boolean(config.mta.verp_personalized_deliveries))
-def send_admin_subscription_notice(mlist, address, full_name, language):
+def send_admin_subscription_notice(mlist, address, display_name, language):
"""Send the list administrators a subscription notice.
- :param mlist: the mailing list
+ :param mlist: The mailing list.
:type mlist: IMailingList
- :param address: the address being subscribed
+ :param address: The address being subscribed.
:type address: string
- :param full_name: the name of the subscriber
- :type full_name: string
- :param language: the language of the address's realname
+ :param display_name: The name of the subscriber.
+ :type display_name: string
+ :param language: The language of the address's display name.
:type language: string
"""
with _.using(mlist.preferred_language.code):
- subject = _('$mlist.real_name subscription notification')
- full_name = full_name.encode(language.charset, 'replace')
+ subject = _('$mlist.display_name subscription notification')
+ display_name = display_name.encode(language.charset, 'replace')
text = make('adminsubscribeack.txt',
mailing_list=mlist,
- listname=mlist.real_name,
- member=formataddr((full_name, address)),
+ listname=mlist.display_name,
+ member=formataddr((display_name, address)),
)
msg = OwnerNotification(mlist, subject, text, roster=mlist.administrators)
msg.send(mlist)
diff --git a/src/mailman/app/registrar.py b/src/mailman/app/registrar.py
index e439b3c54..b95fda1f8 100644
--- a/src/mailman/app/registrar.py
+++ b/src/mailman/app/registrar.py
@@ -59,7 +59,7 @@ class Registrar:
implements(IRegistrar)
- def register(self, mlist, email, real_name=None, delivery_mode=None):
+ def register(self, mlist, email, display_name=None, delivery_mode=None):
"""See `IUserRegistrar`."""
if delivery_mode is None:
delivery_mode = DeliveryMode.regular
@@ -70,7 +70,7 @@ class Registrar:
pendable = PendableRegistration(
type=PendableRegistration.PEND_KEY,
email=email,
- real_name=real_name,
+ display_name=display_name,
delivery_mode=delivery_mode.name)
pendable['list_name'] = mlist.fqdn_listname
token = getUtility(IPendings).add(pendable)
@@ -104,7 +104,7 @@ class Registrar:
return False
missing = object()
email = pendable.get('email', missing)
- real_name = pendable.get('real_name', missing)
+ display_name = pendable.get('display_name', missing)
list_name = pendable.get('list_name', missing)
pended_delivery_mode = pendable.get('delivery_mode', 'regular')
try:
@@ -133,7 +133,7 @@ class Registrar:
# and link the two together
if address is None:
assert user is None, 'How did we get a user but not an address?'
- user = user_manager.create_user(email, real_name)
+ user = user_manager.create_user(email, display_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,
@@ -145,7 +145,7 @@ class Registrar:
raise AssertionError('Could not find expected IAddress')
elif user is None:
user = user_manager.create_user()
- user.real_name = real_name
+ user.display_name = display_name
user.link(address)
else:
# The IAddress and linked IUser already exist, so all we need to
diff --git a/src/mailman/app/subscriptions.py b/src/mailman/app/subscriptions.py
index 931d7f4c7..60f8cdebe 100644
--- a/src/mailman/app/subscriptions.py
+++ b/src/mailman/app/subscriptions.py
@@ -145,7 +145,7 @@ class SubscriptionService:
yield member
def join(self, fqdn_listname, subscriber,
- real_name=None,
+ display_name=None,
delivery_mode=DeliveryMode.regular,
role=MemberRole.member):
"""See `ISubscriptionService`."""
@@ -158,8 +158,8 @@ class SubscriptionService:
# it's a valid email address, and let InvalidEmailAddressError
# propagate up.
getUtility(IEmailValidator).validate(subscriber)
- if real_name is None:
- real_name, at, domain = subscriber.partition('@')
+ if display_name is None:
+ display_name, at, domain = subscriber.partition('@')
# Because we want to keep the REST API simple, there is no
# password or language given to us. We'll use the system's
# default language for the user's default language. We'll set the
@@ -167,7 +167,7 @@ class SubscriptionService:
# it can't be retrieved. Note that none of these are used unless
# the address is completely new to us.
password = generate(int(config.passwords.password_length))
- return add_member(mlist, subscriber, real_name, password,
+ return add_member(mlist, subscriber, display_name, password,
delivery_mode,
system_preferences.preferred_language, role)
else:
diff --git a/src/mailman/app/tests/test_bounces.py b/src/mailman/app/tests/test_bounces.py
index be2c5cb78..d0d94df5e 100644
--- a/src/mailman/app/tests/test_bounces.py
+++ b/src/mailman/app/tests/test_bounces.py
@@ -21,6 +21,11 @@ from __future__ import absolute_import, print_function, unicode_literals
__metaclass__ = type
__all__ = [
+ 'TestMaybeForward',
+ 'TestProbe',
+ 'TestSendProbe',
+ 'TestSendProbeNonEnglish',
+ 'TestVERP',
]
@@ -212,7 +217,7 @@ Message-ID: <first>
self.assertEqual(set(pendable.keys()),
set(['member_id', 'message_id']))
# member_ids are pended as unicodes.
- self.assertEqual(uuid.UUID(hex=pendable['member_id']),
+ self.assertEqual(uuid.UUID(hex=pendable['member_id']),
self._member.member_id)
self.assertEqual(pendable['message_id'], '<first>')
@@ -264,10 +269,16 @@ Message-ID: <first>
# Check the headers of the outer message.
token = send_probe(self._member, self._msg)
message = get_queue_messages('virgin')[0].msg
- self.assertEqual(message['From'],
+ self.assertEqual(message['from'],
'test-bounces+{0}@example.com'.format(token))
- self.assertEqual(message['To'], 'anne@example.com')
- self.assertEqual(message['Subject'], 'Test mailing list probe message')
+ self.assertEqual(message['to'], 'anne@example.com')
+ self.assertEqual(message['subject'], 'Test mailing list probe message')
+
+ def test_no_precedence_header(self):
+ # Probe messages should not have a Precedence header (LP: #808821).
+ send_probe(self._member, self._msg)
+ message = get_queue_messages('virgin')[0].msg
+ self.assertEqual(message['precedence'], None)
diff --git a/src/mailman/app/tests/test_notifications.py b/src/mailman/app/tests/test_notifications.py
index 42f482582..8cce1be6f 100644
--- a/src/mailman/app/tests/test_notifications.py
+++ b/src/mailman/app/tests/test_notifications.py
@@ -50,7 +50,7 @@ class TestNotifications(unittest.TestCase):
def setUp(self):
self._mlist = create_list('test@example.com')
self._mlist.welcome_message_uri = 'mailman:///welcome.txt'
- self._mlist.real_name = 'Test List'
+ self._mlist.display_name = 'Test List'
self.var_dir = tempfile.mkdtemp()
config.push('template config', """\
[paths.testing]
diff --git a/src/mailman/archiving/docs/common.rst b/src/mailman/archiving/docs/common.rst
index 45ec8f194..5f7cfe42b 100644
--- a/src/mailman/archiving/docs/common.rst
+++ b/src/mailman/archiving/docs/common.rst
@@ -62,12 +62,13 @@ The archiver is also able to archive the message.
>>> os.path.exists(pckpath)
True
-Note however that the prototype archiver can't archive messages.
+The `prototype` archiver archives messages to a maildir.
>>> archivers['prototype'].archive_message(mlist, msg)
- Traceback (most recent call last):
- ...
- NotImplementedError
+ >>> archive_path = os.path.join(
+ ... config.ARCHIVE_DIR, 'prototype', mlist.fqdn_listname, 'new')
+ >>> len(os.listdir(archive_path))
+ 1
The Mail-Archive.com
diff --git a/src/mailman/archiving/prototype.py b/src/mailman/archiving/prototype.py
index 55d78074e..453c6c770 100644
--- a/src/mailman/archiving/prototype.py
+++ b/src/mailman/archiving/prototype.py
@@ -25,11 +25,22 @@ __all__ = [
]
+import os
+import errno
+import logging
+
+from datetime import timedelta
+from mailbox import Maildir
from urlparse import urljoin
+
+from flufl.lock import Lock, TimeOutError
from zope.interface import implements
+from mailman.config import config
from mailman.interfaces.archiver import IArchiver
+log = logging.getLogger('mailman.error')
+
class Prototype:
@@ -61,5 +72,49 @@ class Prototype:
@staticmethod
def archive_message(mlist, message):
- """See `IArchiver`."""
- raise NotImplementedError
+ """See `IArchiver`.
+
+ This archiver saves messages into a maildir.
+ """
+ archive_dir = os.path.join(config.ARCHIVE_DIR, 'prototype')
+ try:
+ os.makedirs(archive_dir, 0775)
+ except OSError as error:
+ # If this already exists, then we're fine
+ if error.errno != errno.EEXIST:
+ raise
+
+ # Maildir will throw an error if the directories are partially created
+ # (for instance the toplevel exists but cur, new, or tmp do not)
+ # therefore we don't create the toplevel as we did above.
+ list_dir = os.path.join(archive_dir, mlist.fqdn_listname)
+ mailbox = Maildir(list_dir, create=True, factory=None)
+ lock_file = os.path.join(
+ config.LOCK_DIR, '{0}-maildir.lock'.format(mlist.fqdn_listname))
+
+ # Lock the maildir as Maildir.add() is not threadsafe. Don't use the
+ # context manager because it's not an error if we can't acquire the
+ # archiver lock. We'll just log the problem and continue.
+ #
+ # XXX 2012-03-14 BAW: When we extend the chain/pipeline architecture
+ # to other runners, e.g. the archive runner, it would be better to let
+ # any TimeOutError propagate up. That would cause the message to be
+ # re-queued and tried again later, rather than being discarded as
+ # happens now below.
+ lock = Lock(lock_file)
+ try:
+ lock.lock(timeout=timedelta(seconds=1))
+ # Add the message to the maildir. The return value could be used
+ # to construct the file path if necessary. E.g.
+ #
+ # os.path.join(archive_dir, mlist.fqdn_listname, 'new',
+ # message_key)
+ mailbox.add(message)
+ except TimeOutError:
+ # Log the error and go on.
+ log.error('Unable to acquire prototype archiver lock for {0}, '
+ 'discarding: {1}'.format(
+ mlist.fqdn_listname,
+ message.get('message-id', 'n/a')))
+ finally:
+ lock.unlock(unconditionally=True)
diff --git a/src/mailman/archiving/tests/__init__.py b/src/mailman/archiving/tests/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/mailman/archiving/tests/__init__.py
diff --git a/src/mailman/archiving/tests/test_prototype.py b/src/mailman/archiving/tests/test_prototype.py
new file mode 100644
index 000000000..29f6ba1cb
--- /dev/null
+++ b/src/mailman/archiving/tests/test_prototype.py
@@ -0,0 +1,174 @@
+# Copyright (C) 2012 by the Free Software Foundation, Inc.
+#
+# This file is part of GNU Mailman.
+#
+# GNU Mailman is free software: you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or (at your option)
+# any later version.
+#
+# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+# more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
+
+"""Test the prototype archiver."""
+
+from __future__ import absolute_import, print_function, unicode_literals
+
+__metaclass__ = type
+__all__ = [
+ 'TestPrototypeArchiver',
+ ]
+
+
+import os
+import shutil
+import tempfile
+import unittest
+import threading
+
+from email import message_from_file
+from flufl.lock import Lock
+
+from mailman.app.lifecycle import create_list
+from mailman.archiving.prototype import Prototype
+from mailman.config import config
+from mailman.testing.helpers import LogFileMark
+from mailman.testing.helpers import (
+ specialized_message_from_string as mfs)
+from mailman.testing.layers import ConfigLayer
+from mailman.utilities.email import add_message_hash
+
+
+class TestPrototypeArchiver(unittest.TestCase):
+ """Test the prototype archiver."""
+
+ layer = ConfigLayer
+
+ def setUp(self):
+ # Create a fake mailing list and message object
+ self._msg = mfs("""\
+To: test@example.com
+From: anne@example.com
+Subject: Testing the test list
+Message-ID: <ant>
+X-Message-ID-Hash: MS6QLWERIJLGCRF44J7USBFDELMNT2BW
+
+Tests are better than no tests
+but the water deserves to be swum.
+""")
+ self._mlist = create_list('test@example.com')
+ config.db.commit()
+ # Set up a temporary directory for the prototype archiver so that it's
+ # easier to clean up.
+ self._tempdir = tempfile.mkdtemp()
+ config.push('prototype', """
+ [paths.testing]
+ archive_dir: {0}
+ """.format(self._tempdir))
+ # Capture the structure of a maildir.
+ self._expected_dir_structure = set(
+ (os.path.join(config.ARCHIVE_DIR, path) for path in (
+ 'prototype',
+ os.path.join('prototype', self._mlist.fqdn_listname),
+ os.path.join('prototype', self._mlist.fqdn_listname, 'cur'),
+ os.path.join('prototype', self._mlist.fqdn_listname, 'new'),
+ os.path.join('prototype', self._mlist.fqdn_listname, 'tmp'),
+ )))
+ self._expected_dir_structure.add(config.ARCHIVE_DIR)
+
+ def tearDown(self):
+ shutil.rmtree(self._tempdir)
+ config.pop('prototype')
+
+ def _find(self, path):
+ all_filenames = set()
+ for dirpath, dirnames, filenames in os.walk(path):
+ if not isinstance(dirpath, unicode):
+ dirpath = unicode(dirpath)
+ all_filenames.add(dirpath)
+ for filename in filenames:
+ new_filename = filename
+ if not isinstance(filename, unicode):
+ new_filename = unicode(filename)
+ all_filenames.add(os.path.join(dirpath, new_filename))
+ return all_filenames
+
+ def test_archive_maildir_created(self):
+ # Archiving a message to the prototype archiver should create the
+ # expected directory structure.
+ Prototype.archive_message(self._mlist, self._msg)
+ all_filenames = self._find(config.ARCHIVE_DIR)
+ # Check that the directory structure has been created and we have one
+ # more file (the archived message) than expected directories.
+ archived_messages = all_filenames - self._expected_dir_structure
+ self.assertEqual(len(archived_messages), 1)
+ self.assertTrue(
+ archived_messages.pop().startswith(
+ os.path.join(config.ARCHIVE_DIR, 'prototype',
+ self._mlist.fqdn_listname, 'new')))
+
+ def test_archive_maildir_existence_does_not_raise(self):
+ # Archiving a second message does not cause an EEXIST to be raised
+ # when a second message is archived.
+ new_dir = None
+ Prototype.archive_message(self._mlist, self._msg)
+ for directory in ('cur', 'new', 'tmp'):
+ path = os.path.join(config.ARCHIVE_DIR, 'prototype',
+ self._mlist.fqdn_listname, directory)
+ if directory == 'new':
+ new_dir = path
+ self.assertTrue(os.path.isdir(path))
+ # There should be one message in the 'new' directory.
+ self.assertEqual(len(os.listdir(new_dir)), 1)
+ # Archive a second message. If an exception occurs, let it fail the
+ # test. Afterward, two messages should be in the 'new' directory.
+ del self._msg['message-id']
+ del self._msg['x-message-id-hash']
+ self._msg['Message-ID'] = '<bee>'
+ add_message_hash(self._msg)
+ Prototype.archive_message(self._mlist, self._msg)
+ self.assertEqual(len(os.listdir(new_dir)), 2)
+
+ def test_archive_lock_used(self):
+ # Test that locking the maildir when adding works as a failure here
+ # could mean we lose mail.
+ lock_file = os.path.join(
+ config.LOCK_DIR, '{0}-maildir.lock'.format(
+ self._mlist.fqdn_listname))
+ with Lock(lock_file):
+ # Acquire the archiver lock, then make sure the archiver logs the
+ # fact that it could not acquire the lock.
+ archive_thread = threading.Thread(
+ target=Prototype.archive_message,
+ args=(self._mlist, self._msg))
+ mark = LogFileMark('mailman.error')
+ archive_thread.run()
+ # Test that the archiver output the correct error.
+ line = mark.readline()
+ # XXX 2012-03-15 BAW: we really should remove timestamp prefixes
+ # from the loggers when under test.
+ self.assertTrue(line.endswith(
+ 'Unable to acquire prototype archiver lock for {0}, '
+ 'discarding: {1}\n'.format(
+ self._mlist.fqdn_listname,
+ self._msg.get('message-id'))))
+ # Check that the message didn't get archived.
+ created_files = self._find(config.ARCHIVE_DIR)
+ self.assertEqual(self._expected_dir_structure, created_files)
+
+ def test_prototype_archiver_good_path(self):
+ # Verify the good path; the message gets archived.
+ Prototype.archive_message(self._mlist, self._msg)
+ new_path = os.path.join(
+ config.ARCHIVE_DIR, 'prototype', self._mlist.fqdn_listname, 'new')
+ archived_messages = list(os.listdir(new_path))
+ self.assertEqual(len(archived_messages), 1)
+ # Check that the email has been added.
+ with open(os.path.join(new_path, archived_messages[0])) as fp:
+ archived_message = message_from_file(fp)
+ self.assertEqual(self._msg.as_string(), archived_message.as_string())
diff --git a/src/mailman/commands/cli_lists.py b/src/mailman/commands/cli_lists.py
index 42e67e3a8..5629f33c1 100644
--- a/src/mailman/commands/cli_lists.py
+++ b/src/mailman/commands/cli_lists.py
@@ -109,7 +109,7 @@ class Lists:
for mlist in mailing_lists:
if args.names:
identifier = '{0} [{1}]'.format(
- mlist.fqdn_listname, mlist.real_name)
+ mlist.fqdn_listname, mlist.display_name)
else:
identifier = mlist.fqdn_listname
longest = max(len(identifier), longest)
diff --git a/src/mailman/commands/cli_members.py b/src/mailman/commands/cli_members.py
index f37fb6ecb..2bf6be848 100644
--- a/src/mailman/commands/cli_members.py
+++ b/src/mailman/commands/cli_members.py
@@ -17,7 +17,7 @@
"""The 'members' subcommand."""
-from __future__ import absolute_import, unicode_literals
+from __future__ import absolute_import, print_function, unicode_literals
__metaclass__ = type
__all__ = [
@@ -155,7 +155,7 @@ class Members:
try:
addresses = list(mlist.members.addresses)
if len(addresses) == 0:
- print >> fp, mlist.fqdn_listname, 'has no members'
+ print(mlist.fqdn_listname, 'has no members', file=fp)
return
for address in sorted(addresses, key=attrgetter('email')):
if args.regular:
@@ -170,8 +170,9 @@ class Members:
member = mlist.members.get_member(address.email)
if member.delivery_status not in status_types:
continue
- print >> fp, formataddr(
- (address.real_name, address.original_email))
+ print(
+ formataddr((address.display_name, address.original_email)),
+ file=fp)
finally:
if fp is not sys.stdout:
fp.close()
@@ -194,19 +195,20 @@ class Members:
if line.startswith('#') or len(line.strip()) == 0:
continue
# Parse the line and ensure that the values are unicodes.
- real_name, email = parseaddr(line)
- real_name = real_name.decode(fp.encoding)
+ display_name, email = parseaddr(line)
+ display_name = display_name.decode(fp.encoding)
email = email.decode(fp.encoding)
# Give the user a default, user-friendly password.
password = generate(int(config.passwords.password_length))
try:
- add_member(mlist, email, real_name, password,
+ add_member(mlist, email, display_name, password,
DeliveryMode.regular,
mlist.preferred_language.code)
except AlreadySubscribedError:
# It's okay if the address is already subscribed, just
# print a warning and continue.
- print 'Already subscribed (skipping):', email, real_name
+ print('Already subscribed (skipping):',
+ email, display_name)
finally:
if fp is not sys.stdin:
fp.close()
diff --git a/src/mailman/commands/cli_withlist.py b/src/mailman/commands/cli_withlist.py
index 3b1b36b1a..55d9ff5ec 100644
--- a/src/mailman/commands/cli_withlist.py
+++ b/src/mailman/commands/cli_withlist.py
@@ -230,8 +230,8 @@ As another example, say you wanted to change the display name for a particular
mailing list. You could put the following function in a file called
'change.pw':
- def change(mlist, real_name):
- mlist.real_name = real_name
+ def change(mlist, display_name):
+ mlist.display_name = display_name
# Required to save changes to the database.
commit()
diff --git a/src/mailman/commands/docs/import.rst b/src/mailman/commands/docs/import.rst
index 1092063a4..2ab6f99bd 100644
--- a/src/mailman/commands/docs/import.rst
+++ b/src/mailman/commands/docs/import.rst
@@ -48,9 +48,9 @@ import, the mailing list's 'real name' has changed.
>>> FakeArgs.pickle_file = [
... resource_filename('mailman.testing', 'config.pck')]
- >>> print mlist.real_name
+ >>> print mlist.display_name
Import
>>> command.process(FakeArgs)
- >>> print mlist.real_name
+ >>> print mlist.display_name
Test
diff --git a/src/mailman/commands/docs/info.rst b/src/mailman/commands/docs/info.rst
index 34883711e..ad034a1a6 100644
--- a/src/mailman/commands/docs/info.rst
+++ b/src/mailman/commands/docs/info.rst
@@ -59,6 +59,7 @@ The File System Hierarchy layout is the same every by definition.
Python ...
...
File system paths:
+ ARCHIVE_DIR = /var/lib/mailman/archives
BIN_DIR = /sbin
DATA_DIR = /var/lib/mailman/data
ETC_DIR = /etc
diff --git a/src/mailman/commands/docs/membership.rst b/src/mailman/commands/docs/membership.rst
index f9d3aacd0..638705e91 100644
--- a/src/mailman/commands/docs/membership.rst
+++ b/src/mailman/commands/docs/membership.rst
@@ -157,7 +157,7 @@ list.
<BLANKLINE>
>>> user = user_manager.get_user('anne@example.com')
- >>> print user.real_name
+ >>> print user.display_name
Anne Person
>>> list(user.addresses)
[<Address: Anne Person <anne@example.com> [verified] at ...>]
diff --git a/src/mailman/commands/docs/withlist.rst b/src/mailman/commands/docs/withlist.rst
index f00208490..99a366c9a 100644
--- a/src/mailman/commands/docs/withlist.rst
+++ b/src/mailman/commands/docs/withlist.rst
@@ -54,8 +54,8 @@ single argument, the mailing list.
... def showme(mailing_list):
... print "The list's name is", mailing_list.fqdn_listname
...
- ... def realname(mailing_list):
- ... print "The list's real name is", mailing_list.real_name
+ ... def displayname(mailing_list):
+ ... print "The list's display name is", mailing_list.display_name
... """
If the name of the function is the same as the module, then you only need to
@@ -71,9 +71,9 @@ name the function once.
The function's name can also be different than the modules name. In that
case, just give the full module path name to the function you want to call.
- >>> args.run = 'showme.realname'
+ >>> args.run = 'showme.displayname'
>>> command.process(args)
- The list's real name is Aardvark
+ The list's display name is Aardvark
Multiple lists
@@ -89,14 +89,14 @@ must start with a caret.
>>> args.listname = '^.*example.com'
>>> command.process(args)
- The list's real name is Aardvark
- The list's real name is Badger
- The list's real name is Badboys
+ The list's display name is Aardvark
+ The list's display name is Badger
+ The list's display name is Badboys
>>> args.listname = '^bad.*'
>>> command.process(args)
- The list's real name is Badger
- The list's real name is Badboys
+ The list's display name is Badger
+ The list's display name is Badboys
>>> args.listname = '^foo'
>>> command.process(args)
diff --git a/src/mailman/commands/eml_membership.py b/src/mailman/commands/eml_membership.py
index 386316eb9..d6f7a47d9 100644
--- a/src/mailman/commands/eml_membership.py
+++ b/src/mailman/commands/eml_membership.py
@@ -65,7 +65,7 @@ used.
delivery_mode = self._parse_arguments(arguments, results)
if delivery_mode is ContinueProcessing.no:
return ContinueProcessing.no
- real_name, address = parseaddr(msg['from'])
+ display_name, address = parseaddr(msg['from'])
# Address could be None or the empty string.
if not address:
address = msg.sender
@@ -81,7 +81,7 @@ used.
return ContinueProcessing.yes
joins.add(address)
results.joins = joins
- person = formataddr((real_name, address))
+ person = formataddr((display_name, address))
# Is this person already a member of the list? Search for all
# matching memberships.
members = getUtility(ISubscriptionService).find_members(
@@ -90,7 +90,7 @@ used.
print(_('$person is already a member'), file=results)
else:
getUtility(IRegistrar).register(mlist, address,
- real_name, delivery_mode)
+ display_name, delivery_mode)
print(_('Confirmation email sent to $person'), file=results)
return ContinueProcessing.yes
@@ -177,7 +177,7 @@ You may be asked to confirm your request.""")
file=results)
return ContinueProcessing.no
member.unsubscribe()
- person = formataddr((user.real_name, email))
+ person = formataddr((user.display_name, email))
print(_('$person left $mlist.fqdn_listname'), file=results)
return ContinueProcessing.yes
diff --git a/src/mailman/config/config.py b/src/mailman/config/config.py
index 034b76b4f..e3b4f88a7 100644
--- a/src/mailman/config/config.py
+++ b/src/mailman/config/config.py
@@ -173,6 +173,7 @@ class Configuration:
lock_dir = category.lock_dir,
log_dir = category.log_dir,
messages_dir = category.messages_dir,
+ archive_dir = category.archive_dir,
pipermail_private_dir = category.pipermail_private_dir,
pipermail_public_dir = category.pipermail_public_dir,
queue_dir = category.queue_dir,
diff --git a/src/mailman/config/schema.cfg b/src/mailman/config/schema.cfg
index e662633e6..7e15ab82f 100644
--- a/src/mailman/config/schema.cfg
+++ b/src/mailman/config/schema.cfg
@@ -113,6 +113,9 @@ etc_dir: $var_dir/etc
ext_dir: $var_dir/ext
# Directory where the default IMessageStore puts its messages.
messages_dir: $var_dir/messages
+# Directory for archive backends to store their messages in. Archivers should
+# create a subdirectory in here to store their files.
+archive_dir: $var_dir/archives
# Directory for public Pipermail archiver artifacts.
pipermail_public_dir: $var_dir/archives/public
# Directory for private Pipermail archiver artifacts.
diff --git a/src/mailman/database/schema/postgres.sql b/src/mailman/database/schema/postgres.sql
index 713d6d1a3..9becdb5dc 100644
--- a/src/mailman/database/schema/postgres.sql
+++ b/src/mailman/database/schema/postgres.sql
@@ -89,7 +89,7 @@ CREATE TABLE mailinglist (
posting_pipeline TEXT,
preferred_language TEXT,
private_roster BOOLEAN,
- real_name TEXT,
+ display_name TEXT,
reject_these_nonmembers BYTEA,
reply_goes_to_list INTEGER,
reply_to_address TEXT,
@@ -154,7 +154,7 @@ CREATE TABLE address (
id SERIAL NOT NULL,
email TEXT,
_original TEXT,
- real_name TEXT,
+ display_name TEXT,
verified_on TIMESTAMP,
registered_on TIMESTAMP,
user_id INTEGER,
@@ -168,7 +168,7 @@ CREATE TABLE address (
CREATE TABLE "user" (
id SERIAL NOT NULL,
- real_name TEXT,
+ display_name TEXT,
password BYTEA,
_user_id UUID,
_created_on TIMESTAMP,
diff --git a/src/mailman/database/schema/sqlite.sql b/src/mailman/database/schema/sqlite.sql
index f835a8d84..650c38a54 100644
--- a/src/mailman/database/schema/sqlite.sql
+++ b/src/mailman/database/schema/sqlite.sql
@@ -27,7 +27,7 @@ CREATE TABLE address (
id INTEGER NOT NULL,
email TEXT,
_original TEXT,
- real_name TEXT,
+ display_name TEXT,
verified_on TIMESTAMP,
registered_on TIMESTAMP,
user_id INTEGER,
@@ -185,7 +185,7 @@ CREATE TABLE mailinglist (
posting_pipeline TEXT,
preferred_language TEXT,
private_roster BOOLEAN,
- real_name TEXT,
+ display_name TEXT,
reject_these_nonmembers BLOB,
reply_goes_to_list INTEGER,
reply_to_address TEXT,
@@ -278,7 +278,7 @@ CREATE TABLE preferences (
CREATE TABLE user (
id INTEGER NOT NULL,
- real_name TEXT,
+ display_name TEXT,
password BINARY,
_user_id TEXT,
_created_on TIMESTAMP,
diff --git a/src/mailman/docs/8-miles-high.rst b/src/mailman/docs/8-miles-high.rst
index 0869bd563..812d78d72 100644
--- a/src/mailman/docs/8-miles-high.rst
+++ b/src/mailman/docs/8-miles-high.rst
@@ -8,26 +8,27 @@ Notes from the PyCon 2012 Mailman Sprint
diagrams from his "Mailman" presentation at PyCon 2012.
Transcribed by Stephen Turnbull.
-These are notes from the Mailman sprint at PyCon 2012. They are not
+*These are notes from the Mailman sprint at PyCon 2012. They are not
terribly well organized, nor fully fleshed out. Please edit and push
-branches to Launchpad at lp:mailman or post patches to <WHERE?> <URL?>.
+branches to Launchpad at lp:mailman or post patches to
+<https://bugs.launchpad.net/mailman>.*
-The intent of this document is to provide a view of Mailman 3's
-workflow and structures from "eight miles high".
+The intent of this document is to provide a view of Mailman 3's workflow and
+structures from "eight miles high".
Basic Messaging Handling Workflow
----------------------------------
+=================================
-Mailman accepts a message via the LMTP protocol (RFC 2033). It
-implements a simple LMTP server internally based on the LMTP server
-provided in the Python stdlib. The LMTP server's responsibility is to
-parse the message into a tuple (*mlist*, *msg*, *msg_data*). If the
-parse fails (including messages which Mailman considers to be invalid
-due to lack of Message-Id as strongly recommended by RFC 2822 and RFC
-5322), the message will be rejected, otherwise the tuple is pickled,
-and the resulting *message pickle* added to one of the IN, COMMAND, or
-BOUNCE processing queues.
+Mailman accepts a message via the LMTP protocol (RFC 2033). It implements a
+simple LMTP server internally based on the LMTP server provided in the Python
+standard library. The LMTP server's responsibility is to parse the message
+into a tuple (*mlist*, *msg*, *msgdata*). If the parse fails (including
+messages which Mailman considers to be invalid due to lack of `Message-Id` as
+strongly recommended by RFC 2822 and RFC 5322), the message will be rejected,
+otherwise the parsed message and metadata dictionary are pickled, and the
+resulting *message pickle* added to one of the `in`, `command`, or `bounce`
+processing queues.
.. graphviz::
@@ -39,27 +40,27 @@ BOUNCE processing queues.
msg -> MTA [label="SMTP"];
MTA -> lmtpd [label="LMTP"];
lmtpd -> MTA [label="reject"];
- lmtpd -> IN -> POSTING [label=".pck"];
+ lmtpd -> IN -> PIPELINE [label=".pck"];
lmtpd -> BOUNCES [label=".pck"];
lmtpd -> COMMAND [label=".pck"];
}
-The IN queue is processed by *filter chains* (explained below) to
-determine whether the post (or administrative request) will be
-processed. If not allowed, the message pickle is discarded, rejected
-(returned to sender), or held (added to the MODERATION queue -- not
-shown). Otherwise the message is added to the POSTING queue.
+The `in` queue is processed by *filter chains* (explained below) to determine
+whether the post (or administrative request) will be processed. If not
+allowed, the message pickle is discarded, rejected (returned to sender), or
+held (saved for moderator approval -- not shown). Otherwise the message is
+added to the `pipeline` (i.e. posting) queue.
-Each of the COMMAND, BOUNCE, and POSTING queues is processed by a
-*pipeline of handlers* as in Mailman 2's pipeline. (Some functions
-such as spam detection that were handled in the Mailman 2 pipeline are
-now in the filter chains.)
+Each of the `command`, `bounce`, and `pipeline` queues is processed by a
+*pipeline of handlers* as in Mailman 2's pipeline. (Some functions such as
+spam detection that were handled in the Mailman 2 pipeline are now in the
+filter chains.)
-Handlers may copy messages to other queues (*e.g.*, ARCHIVE), and
-eventually posts for distribution end up in the OUT queue for
-injection into the MTA.
+Handlers may copy messages to other queues (*e.g.*, `archive`), and eventually
+posted messages for distribution to the list membership end up in the `out`
+queue for injection into the MTA.
-The VIRGIN queue is a special queue for messages created by Mailman.
+The `virgin` queue is a special queue for messages created by Mailman.
.. graphviz::
@@ -69,7 +70,7 @@ The VIRGIN queue is a special queue for messages created by Mailman.
"calculate\nrecipients" -> "to digest" -> "to archive" -> \
"to outgoing" }
node [shape=box, color=lightblue, style=filled, group=1]
- { rank=same; POSTING -> "MIME\ndelete" }
+ { rank=same; PIPELINE -> "MIME\ndelete" }
{ rank=same; "to digest" -> DIGEST }
{ rank=same; "to archive" -> ARCHIVE }
{ rank=same; "to outgoing" -> OUT }
@@ -77,16 +78,15 @@ The VIRGIN queue is a special queue for messages created by Mailman.
Message Filtering
------------------
+=================
-Once a message has been classified as a post or administrivia, rules
-are applied to determine whether the message should be distributed or
-acted on. Rules include things like "if the message's sender is a
-non-member, hold it for moderation", or "if the message contains an
-Approved field with a valid password, distribute it". A rule may also
-make no decision, in which case the message pickle is passed on to the
-next rule in the filter chain. The default set of rules looks
-something like this:
+Once a message has been classified as a post or administrivia, rules are
+applied to determine whether the message should be distributed or acted on.
+Rules include things like "if the message's sender is a non-member, hold it
+for moderation", or "if the message contains an `Approved` header with a valid
+password, allow it to be posted". A rule may also make no decision, in which
+case message processing is passed on to the next rule in the filter chain.
+The default set of rules looks something like this:
.. graphviz::
@@ -107,12 +107,12 @@ something like this:
any [group=0, label="<f0> any | {<f1> | <f2>}"]
truth [label="<f0> truth | <f1>"]
approved:f1 -> emergency:f0 [weight=100]
- emergency:f1 -> loop:f0
- loop:f1 -> modmember:f0
- modmember:f1 -> administrivia:f0
- administrivia:f1 -> maxsize:f0
- maxsize:f1 -> any:f0
- any:f1 -> truth:f0
+ emergency:f1 -> loop:f0
+ loop:f1 -> modmember:f0
+ modmember:f1 -> administrivia:f0
+ administrivia:f1 -> maxsize:f0
+ maxsize:f1 -> any:f0
+ any:f1 -> truth:f0
}
subgraph queues {
rankdir=TB
@@ -122,22 +122,22 @@ something like this:
MODERATION [color=wheat];
HOLD [color=wheat];
}
- { POSTING [shape=box, style=filled, color=cyan]; }
+ { PIPELINE [shape=box, style=filled, color=cyan]; }
IN -> approved:f0
- approved:f2 -> POSTING [minlen=2]
+ approved:f2 -> PIPELINE [minlen=2]
loop:f2 -> DISCARD
modmember:f2 -> MODERATION
emergency:f2:e -> HOLD
maxsize:f2 -> MODERATION
any:f2 -> MODERATION
- truth:f1 -> POSTING [minlen=2]
+ truth:f1 -> PIPELINE [minlen=2]
}
Configuration
--------------
+=============
Uses lazr.config.
@@ -146,45 +146,44 @@ when the Mailman daemon starts, and what queue the Runner manages.
Shell Commands
---------------
+==============
-bin/mailman: This is an ubercommand, with subcommands for all the
-various things admins might want to do, similar to mailmanctl, but
-with more functionality.
+`bin/mailman`: This is an ubercommand, with subcommands for all the various
+things admins might want to do, similar to Mailman 2's mailmanctl, but with
+more functionality.
-bin/master: The runner manager: starts, watches, stops the runner
+`bin/master`: The runner manager: starts, watches, stops the runner
daemons.
-bin/runner: Individual runner daemons. Each instance is configured
-with a configure object specified on the command line, and other
-command line options.
+`bin/runner`: Individual runner daemons. Each instance is configured with
+arguments specified on the command line.
User Model
-----------
+==========
A *user* represents a person. A user has an *id* and a *display
-name*, and a list of addresses.
+name*, and optionally a list of linked addresses.
-Each *address* is a separate object, linked to a user with a user id.
+Each *address* is a separate object, linked to no more than one user.
-A list *member* is a link from a user to a mailing list. Each list
-member has a user id, a mailing list name, an address (which may be
-None, which will be replaced by the user's preferred address, a list
-of preferences, and a *role* such as "owner" or "moderator". Roles
-are used to determine what kinds of mail the user receives via that
-membership. *Owners* will receive mail to *list*-owner, but not posts
-and moderation traffic, for example. A user with multiple roles on a
-single list will therefore have multiple memberships in that list, one
-for each role.
+A list *member* associates an address with a mailing list. Each list member
+has a id, a mailing list name, an address (which may be `None`, representing
+the user's *preferred address*), a list of preferences, and a *role* such as
+"owner" or "moderator". Roles are used to determine what kinds of mail the
+user receives via that membership. *Owners* will receive mail to
+*list*-owner, but not posts and moderation traffic, for example. A user with
+multiple roles on a single list will therefore have multiple memberships in
+that list, one for each role.
-Roles are implemented by "magical, invisible" *rosters*.
+Roles are implemented by "magical, invisible" *rosters* which are objects
+representing queries on the membership database.
List Styles
------------
+===========
-Each list *style* is a named object. Its attributes are functions
-used to apply the relevant style settings to the mailing list *at
-creation time*. Since these are functions, they can be composed in
-various ways, to create substyles, *etc*.
+Each list *style* is a named object. Its attributes are functions used to
+apply the relevant style settings to the mailing list *at creation time*.
+Since these are functions, they can be composed in various ways, to create
+substyles, *etc*.
diff --git a/src/mailman/docs/NEWS.rst b/src/mailman/docs/NEWS.rst
index 6869e2889..31e3ce7b9 100644
--- a/src/mailman/docs/NEWS.rst
+++ b/src/mailman/docs/NEWS.rst
@@ -46,6 +46,11 @@ Architecture
attribute on the message object, instead of trusting a possibly incorrect
value if it's already set. The individual `IArchiver` implementations no
longer set the `X-Message-ID-Hash` header.
+ * The Prototype archiver now stores its files in maildir format inside of
+ `$var_dir/archives/prototype`, given by Toshio Kuratomi.
+ * Improved "8 mile high" document distilled by Stephen Turnbull from the
+ Pycon 2012 Mailman 3 sprint. Also improvements to the Sphinx build given
+ by Andrea Crotti.
Database
--------
@@ -60,6 +65,7 @@ Database
- digest_footer -> digest_footer_uri
- start_chain -> posting_chain
- pipeline -> posting_pipeline
+ - real_name -> display_name (mailinglist, user, address)
REST
----
@@ -67,6 +73,8 @@ REST
resources now accept a `held` path component. GETing this returns all held
messages for the mailing list. POSTing to a specific request id under this
url can dispose of the message using `Action` enums.
+ * Mailing list resources now have a `member_count` attribute which gives the
+ number of subscribed members. Given by Toshio Kuratomi.
Interfaces
----------
@@ -84,6 +92,10 @@ Interfaces
* New `ITemplateLoader` utility.
* `ILanguageManager.add()` returns the `ILanguage` object just created.
* `IMailinglist.decorators` removed; it was unused
+ * `IMailingList.real_name` -> `IMailingList.display_name`
+ * `IUser.real_name` -> `IUser.display_name`
+ * `IAddress.real_name` -> `IAddress.display_name`
+ * Add property `IRoster.member_count`.
Commands
--------
@@ -105,6 +117,8 @@ Commands
Bug fixes
---------
+ * Subscription disabled probe warning notification messages are now sent
+ without a `Precedence:` header. Given by Mark Sapiro. (LP: #808821)
* Fixed KeyError in retry runner, contributed by Stephen A. Goss.
(LP: #872391)
* Fixed bogus use of `bounce_processing` attribute (should have been
diff --git a/src/mailman/email/message.py b/src/mailman/email/message.py
index d7bf81055..dcea82425 100644
--- a/src/mailman/email/message.py
+++ b/src/mailman/email/message.py
@@ -142,7 +142,7 @@ class Message(email.message.Message):
else '')
else:
field_values = self.get_all(header, [])
- senders.extend(address.lower() for (real_name, address)
+ senders.extend(address.lower() for (display_name, address)
in email.utils.getaddresses(field_values))
# Filter out None and the empty string.
return [sender for sender in senders if sender]
@@ -178,10 +178,20 @@ class UserNotification(Message):
self['To'] = recipients
self.recipients = set([recipients])
- def send(self, mlist, **_kws):
+ def send(self, mlist, add_precedence=True, **_kws):
"""Sends the message by enqueuing it to the 'virgin' queue.
This is used for all internally crafted messages.
+
+ :param mlist: The mailing list to send the message to.
+ :type mlist: `IMailingList`
+ :param add_precedence: Flag indicating whether a `Precedence: bulk`
+ header should be added to the message or not.
+ :type add_precedence: bool
+
+ This function also accepts arbitrary keyword arguments. The key/value
+ pairs for **kws is added to the metadata dictionary associated with
+ the enqueued message.
"""
# Since we're crafting the message from whole cloth, let's make sure
# this message has a Message-ID.
@@ -193,7 +203,7 @@ class UserNotification(Message):
# UserNotifications are typically for admin messages, and for messages
# other than list explosions. Send these out as Precedence: bulk, but
# don't override an existing Precedence: header.
- if 'precedence' not in self:
+ if 'precedence' not in self and add_precedence:
self['Precedence'] = 'bulk'
self._enqueue(mlist, **_kws)
diff --git a/src/mailman/email/tests/__init__.py b/src/mailman/email/tests/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/mailman/email/tests/__init__.py
diff --git a/src/mailman/email/tests/test_message.py b/src/mailman/email/tests/test_message.py
new file mode 100644
index 000000000..ee4f6135d
--- /dev/null
+++ b/src/mailman/email/tests/test_message.py
@@ -0,0 +1,60 @@
+# Copyright (C) 2012 by the Free Software Foundation, Inc.
+#
+# This file is part of GNU Mailman.
+#
+# GNU Mailman is free software: you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or (at your option)
+# any later version.
+#
+# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+# more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
+
+"""Test the message API."""
+
+from __future__ import absolute_import, print_function, unicode_literals
+
+__metaclass__ = type
+__all__ = [
+ 'TestMessage',
+ ]
+
+
+import unittest
+
+from mailman.app.lifecycle import create_list
+from mailman.email.message import UserNotification
+from mailman.testing.helpers import get_queue_messages
+from mailman.testing.layers import ConfigLayer
+
+
+
+class TestMessage(unittest.TestCase):
+ """Test the message API."""
+
+ layer = ConfigLayer
+
+ def setUp(self):
+ self._mlist = create_list('test@example.com')
+ self._msg = UserNotification(
+ 'aperson@example.com',
+ 'test@example.com',
+ 'Something you need to know',
+ 'I needed to tell you this.')
+
+ def test_one_precedence_header(self):
+ # Ensure that when the original message already has a Precedence:
+ # header, UserNotification.send(..., add_precedence=True, ...) does
+ # not add a second header.
+ self.assertEqual(self._msg['precedence'], None)
+ self._msg['Precedence'] = 'omg wtf bbq'
+ self._msg.send(self._mlist)
+ messages = get_queue_messages('virgin')
+ self.assertEqual(len(messages), 1)
+ self.assertEqual(messages[0].msg.get_all('precedence'),
+ ['omg wtf bbq'])
diff --git a/src/mailman/interfaces/address.py b/src/mailman/interfaces/address.py
index c26fb92c9..6c371e1c0 100644
--- a/src/mailman/interfaces/address.py
+++ b/src/mailman/interfaces/address.py
@@ -84,8 +84,8 @@ class IAddress(Interface):
case preserved email address; `email` will always be lower case.
""")
- real_name = Attribute(
- """Optional real name associated with the email address.""")
+ display_name = Attribute(
+ """Optional display name associated with the email address.""")
registered_on = Attribute(
"""The date and time at which this email address was registered.
diff --git a/src/mailman/interfaces/mailinglist.py b/src/mailman/interfaces/mailinglist.py
index a3e6e443a..3c7ea9ee8 100644
--- a/src/mailman/interfaces/mailinglist.py
+++ b/src/mailman/interfaces/mailinglist.py
@@ -91,11 +91,9 @@ class IMailingList(Interface):
domain = Attribute(
"""The `IDomain` that this mailing list is defined in.""")
- real_name = Attribute("""\
- The short human-readable descriptive name for the mailing list. By
- default, this is the capitalized `list_name`, but it can be changed to
- anything. This is used in locations such as the message footers and
- Subject prefix.
+ display_name = Attribute("""\
+ The short human-readable descriptive name for the mailing list. This
+ is used in locations such as the message footers and Subject prefix.
""")
description = Attribute("""\
diff --git a/src/mailman/interfaces/registrar.py b/src/mailman/interfaces/registrar.py
index 0f4fe2edf..f00e167ff 100644
--- a/src/mailman/interfaces/registrar.py
+++ b/src/mailman/interfaces/registrar.py
@@ -42,7 +42,7 @@ class IRegistrar(Interface):
syntax checking, or confirmation, while this interface does.
"""
- def register(mlist, email, real_name=None, delivery_mode=None):
+ def register(mlist, email, display_name=None, delivery_mode=None):
"""Register the email address, requesting verification.
No `IAddress` or `IUser` is created during this step, but after
@@ -58,8 +58,8 @@ class IRegistrar(Interface):
:type mlist: `IMailingList`
:param email: The email address to register.
:type email: str
- :param real_name: The optional real name of the user.
- :type real_name: str
+ :param display_name: The optional display name of the user.
+ :type display_name: str
:param delivery_mode: The optional delivery mode for this
registration. If not given, regular delivery is used.
:type delivery_mode: `DeliveryMode`
diff --git a/src/mailman/interfaces/roster.py b/src/mailman/interfaces/roster.py
index 4ec3c611c..ebe057d21 100644
--- a/src/mailman/interfaces/roster.py
+++ b/src/mailman/interfaces/roster.py
@@ -40,6 +40,9 @@ class IRoster(Interface):
members = Attribute(
"""An iterator over all the IMembers managed by this roster.""")
+ member_count = Attribute(
+ """The number of members managed by this roster.""")
+
users = Attribute(
"""An iterator over all the IUsers reachable by this roster.
diff --git a/src/mailman/interfaces/subscriptions.py b/src/mailman/interfaces/subscriptions.py
index 584ca4132..85f333cf8 100644
--- a/src/mailman/interfaces/subscriptions.py
+++ b/src/mailman/interfaces/subscriptions.py
@@ -92,7 +92,7 @@ class ISubscriptionService(Interface):
def __iter__():
"""See `get_members()`."""
- def join(fqdn_listname, subscriber, real_name=None,
+ def join(fqdn_listname, subscriber, display_name=None,
delivery_mode=DeliveryMode.regular,
role=MemberRole.member):
"""Subscribe to a mailing list.
@@ -109,10 +109,10 @@ class ISubscriptionService(Interface):
:param subscriber: The email address or user id of the user getting
subscribed.
:type subscriber: string or int
- :param real_name: The name of the user. This is only used if a new
+ :param display_name: The name of the user. This is only used if a new
user is created, and it defaults to the local part of the email
address if not given.
- :type real_name: string
+ :type display_name: string
:param delivery_mode: The delivery mode for this subscription. This
can be one of the enum values of `DeliveryMode`. If not given,
regular delivery is assumed.
diff --git a/src/mailman/interfaces/user.py b/src/mailman/interfaces/user.py
index ad1ac9282..aba9bcbba 100644
--- a/src/mailman/interfaces/user.py
+++ b/src/mailman/interfaces/user.py
@@ -47,8 +47,8 @@ class UnverifiedAddressError(MailmanError):
class IUser(Interface):
"""A basic user."""
- real_name = Attribute(
- """This user's real name.""")
+ display_name = Attribute(
+ """This user's display name.""")
password = Attribute(
"""This user's password information.""")
@@ -68,17 +68,17 @@ class IUser(Interface):
memberships = Attribute(
"""A roster of this user's memberships.""")
- def register(email, real_name=None):
+ def register(email, display_name=None):
"""Register the given email address and link it to this user.
: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
+ :param display_name: The user's display name. If not given the empty
+ string is used.
+ :type display_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.
+ `display_name` parameter is ignored.
:rtype: `IAddress`
:raises AddressAlreadyLinkedError: if this `IAddress` is already
linked to another user.
diff --git a/src/mailman/interfaces/usermanager.py b/src/mailman/interfaces/usermanager.py
index 364fdc6e7..773bd1046 100644
--- a/src/mailman/interfaces/usermanager.py
+++ b/src/mailman/interfaces/usermanager.py
@@ -32,13 +32,13 @@ from zope.interface import Interface, Attribute
class IUserManager(Interface):
"""The global user management service."""
- def create_user(email=None, real_name=None):
+ def create_user(email=None, display_name=None):
"""Create and return an `IUser`.
: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
+ :param display_name: The display name of the user.
+ :type display_name: str
:return: The newly created user, with the given email address and real
name, if given.
:rtype: `IUser`
@@ -74,15 +74,15 @@ class IUserManager(Interface):
users = Attribute(
"""An iterator over all the `IUsers` managed by this user manager.""")
- def create_address(email, real_name=None):
+ def create_address(email, display_name=None):
"""Create and return an address unlinked to any user.
: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
+ :param display_name: The display name associated with the address.
+ :type display_name: str
:return: The newly created address object, with the given email
- address and real name, if given.
+ address and display name, if given.
:rtype: `IAddress`
:raises ExistingAddressError: when the email address is already
registered.
diff --git a/src/mailman/model/address.py b/src/mailman/model/address.py
index 1a891a7c7..a0a13a16b 100644
--- a/src/mailman/model/address.py
+++ b/src/mailman/model/address.py
@@ -41,7 +41,7 @@ class Address(Model):
id = Int(primary=True)
email = Unicode()
_original = Unicode()
- real_name = Unicode()
+ display_name = Unicode()
verified_on = DateTime()
registered_on = DateTime()
@@ -50,17 +50,17 @@ class Address(Model):
preferences_id = Int()
preferences = Reference(preferences_id, 'Preferences.id')
- def __init__(self, email, real_name):
+ def __init__(self, email, display_name):
super(Address, self).__init__()
lower_case = email.lower()
self.email = lower_case
- self.real_name = real_name
+ self.display_name = display_name
self._original = (None if lower_case == email else email)
self.registered_on = now()
def __str__(self):
addr = (self.email if self._original is None else self._original)
- return formataddr((self.real_name, addr))
+ return formataddr((self.display_name, addr))
def __repr__(self):
verified = ('verified' if self.verified_on else 'not verified')
diff --git a/src/mailman/model/docs/addresses.rst b/src/mailman/model/docs/addresses.rst
index 01e68c954..dfeac2b2a 100644
--- a/src/mailman/model/docs/addresses.rst
+++ b/src/mailman/model/docs/addresses.rst
@@ -29,7 +29,7 @@ Creating an unlinked email address is straightforward.
However, such addresses have no real name.
- >>> print address_1.real_name
+ >>> print address_1.display_name
<BLANKLINE>
You can also create an email address object with a real name.
@@ -39,7 +39,7 @@ You can also create an email address object with a real name.
>>> 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)
+ >>> dump_list(address.display_name for address in user_manager.addresses)
<BLANKLINE>
Ben Person
@@ -53,8 +53,8 @@ while the ``repr()`` carries more information.
You can assign real names to existing addresses.
- >>> address_1.real_name = 'Anne Person'
- >>> dump_list(address.real_name for address in user_manager.addresses)
+ >>> address_1.display_name = 'Anne Person'
+ >>> dump_list(address.display_name for address in user_manager.addresses)
Anne Person
Ben Person
@@ -77,7 +77,7 @@ interface.
aperson@example.com
bperson@example.com
cperson@example.com
- >>> dump_list(address.real_name for address in user_manager.addresses)
+ >>> dump_list(address.display_name for address in user_manager.addresses)
Anne Person
Ben Person
Claire Person
@@ -101,7 +101,7 @@ You can remove an unlinked address from the user manager.
>>> 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)
+ >>> dump_list(address.display_name for address in user_manager.addresses)
Ben Person
Claire Person
diff --git a/src/mailman/model/docs/pending.rst b/src/mailman/model/docs/pending.rst
index 707e8a7fc..1bf1ee0e9 100644
--- a/src/mailman/model/docs/pending.rst
+++ b/src/mailman/model/docs/pending.rst
@@ -23,7 +23,7 @@ token that can be used in urls and such.
>>> subscription = SimplePendable(
... type='subscription',
... address='aperson@example.com',
- ... realname='Anne Person',
+ ... display_name='Anne Person',
... language='en',
... password='xyz')
>>> token = pendingdb.add(subscription)
@@ -40,11 +40,11 @@ is returned.
None
>>> pendable = pendingdb.confirm(token)
>>> dump_msgdata(pendable)
- address : aperson@example.com
- language: en
- password: xyz
- realname: Anne Person
- type : subscription
+ address : aperson@example.com
+ display_name: Anne Person
+ language : en
+ password : xyz
+ type : subscription
After confirmation, the token is no longer in the database.
diff --git a/src/mailman/model/docs/registration.rst b/src/mailman/model/docs/registration.rst
index 5ed2503b3..112864f21 100644
--- a/src/mailman/model/docs/registration.rst
+++ b/src/mailman/model/docs/registration.rst
@@ -102,9 +102,9 @@ But this address is waiting for confirmation.
>>> dump_msgdata(pendingdb.confirm(token, expunge=False))
delivery_mode: regular
+ display_name : Anne Person
email : aperson@example.com
list_name : alpha@example.com
- real_name : Anne Person
type : registration
diff --git a/src/mailman/model/docs/requests.rst b/src/mailman/model/docs/requests.rst
index 31597cf3a..1e461e36c 100644
--- a/src/mailman/model/docs/requests.rst
+++ b/src/mailman/model/docs/requests.rst
@@ -205,7 +205,7 @@ For this section, we need a mailing list and at least one message.
>>> mlist = create_list('alist@example.com')
>>> mlist.preferred_language = 'en'
- >>> mlist.real_name = 'A Test List'
+ >>> mlist.display_name = 'A Test List'
>>> msg = message_from_string("""\
... From: aperson@example.org
... To: alist@example.com
@@ -453,8 +453,7 @@ queue when the message is held.
MIME-Version: 1.0
Content-Type: text/plain; charset="us-ascii"
Content-Transfer-Encoding: 7bit
- Subject: New subscription request to list A Test List from
- cperson@example.org
+ Subject: New subscription request to A Test List from cperson@example.org
From: alist-owner@example.com
To: alist-owner@example.com
Message-ID: ...
@@ -563,8 +562,7 @@ subscription and the fact that they may need to approve it.
MIME-Version: 1.0
Content-Type: text/plain; charset="us-ascii"
Content-Transfer-Encoding: 7bit
- Subject: New subscription request to list A Test List from
- fperson@example.org
+ Subject: New subscription request to A Test List from fperson@example.org
From: alist-owner@example.com
To: alist-owner@example.com
Message-ID: ...
@@ -696,7 +694,7 @@ Frank Person is now a member of the mailing list.
<Language [en] English (USA)>
>>> print member.delivery_mode
DeliveryMode.regular
- >>> print member.user.real_name
+ >>> print member.user.display_name
Frank Person
>>> print member.user.password
{CLEARTEXT}abcxyz
diff --git a/src/mailman/model/docs/usermanager.rst b/src/mailman/model/docs/usermanager.rst
index 8ad8d71a4..727f82835 100644
--- a/src/mailman/model/docs/usermanager.rst
+++ b/src/mailman/model/docs/usermanager.rst
@@ -26,7 +26,7 @@ have a password.
>>> dump_list(address.email for address in user.addresses)
*Empty*
- >>> print user.real_name
+ >>> print user.display_name
<BLANKLINE>
>>> print user.password
None
@@ -38,8 +38,8 @@ The user has preferences, but none of them will be specified.
A user can be assigned a real name.
- >>> user.real_name = 'Anne Person'
- >>> dump_list(user.real_name for user in user_manager.users)
+ >>> user.display_name = 'Anne Person'
+ >>> dump_list(user.display_name for user in user_manager.users)
Anne Person
A user can be assigned a password.
@@ -55,25 +55,25 @@ You can also create a user with an address to start out with.
True
>>> dump_list(address.email for address in user_2.addresses)
bperson@example.com
- >>> dump_list(user.real_name for user in user_manager.users)
+ >>> dump_list(user.display_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'
- >>> dump_list(user.real_name for user in user_manager.users)
+ >>> user_2.display_name = 'Ben Person'
+ >>> dump_list(user.display_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')
+ >>> user_3 = user_manager.create_user(display_name='Claire Person')
>>> verifyObject(IUser, user_3)
True
>>> dump_list(address.email for address in user.addresses)
*Empty*
- >>> dump_list(user.real_name for user in user_manager.users)
+ >>> dump_list(user.display_name for user in user_manager.users)
Anne Person
Ben Person
Claire Person
@@ -85,9 +85,9 @@ Finally, you can create a user with both an address and a real name.
True
>>> dump_list(address.email for address in user_4.addresses)
dperson@example.com
- >>> dump_list(address.real_name for address in user_4.addresses)
+ >>> dump_list(address.display_name for address in user_4.addresses)
Dan Person
- >>> dump_list(user.real_name for user in user_manager.users)
+ >>> dump_list(user.display_name for user in user_manager.users)
Anne Person
Ben Person
Claire Person
@@ -101,7 +101,7 @@ 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)
- >>> dump_list(user.real_name for user in user_manager.users)
+ >>> dump_list(user.display_name for user in user_manager.users)
Ben Person
Claire Person
Dan Person
diff --git a/src/mailman/model/docs/users.rst b/src/mailman/model/docs/users.rst
index 7325d66b9..ae2acfd48 100644
--- a/src/mailman/model/docs/users.rst
+++ b/src/mailman/model/docs/users.rst
@@ -21,17 +21,17 @@ Users may have a real name and a password.
>>> user_1 = user_manager.create_user()
>>> user_1.password = b'my password'
- >>> user_1.real_name = 'Zoe Person'
- >>> dump_list(user.real_name for user in user_manager.users)
+ >>> user_1.display_name = 'Zoe Person'
+ >>> dump_list(user.display_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.display_name = 'Zoe X. Person'
>>> user_1.password = b'another password'
- >>> dump_list(user.real_name for user in user_manager.users)
+ >>> dump_list(user.display_name for user in user_manager.users)
Zoe X. Person
>>> dump_list(user.password for user in user_manager.users)
another password
@@ -78,7 +78,7 @@ address on a user object.
>>> 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)
+ >>> dump_list(address.display_name for address in user_1.addresses)
<BLANKLINE>
Zoe Person
@@ -90,7 +90,7 @@ You can also create the address separately and then link it to the user.
zperson@example.com
zperson@example.net
zperson@example.org
- >>> dump_list(address.real_name for address in user_1.addresses)
+ >>> dump_list(address.display_name for address in user_1.addresses)
<BLANKLINE>
<BLANKLINE>
Zoe Person
diff --git a/src/mailman/model/mailinglist.py b/src/mailman/model/mailinglist.py
index 4a6b000ec..d7256d1c9 100644
--- a/src/mailman/model/mailinglist.py
+++ b/src/mailman/model/mailinglist.py
@@ -173,7 +173,7 @@ class MailingList(Model):
posting_pipeline = Unicode()
_preferred_language = Unicode(name='preferred_language')
private_roster = Bool()
- real_name = Unicode()
+ display_name = Unicode()
reject_these_nonmembers = Pickle()
reply_goes_to_list = Enum(ReplyToMunging)
reply_to_address = Unicode()
@@ -206,7 +206,7 @@ class MailingList(Model):
# rosters explicitly.
self.__storm_loaded__()
self.personalize = Personalization.none
- self.real_name = string.capwords(
+ self.display_name = string.capwords(
SPACE.join(listname.split(UNDERSCORE)))
makedirs(self.data_path)
diff --git a/src/mailman/model/roster.py b/src/mailman/model/roster.py
index 35ddcf438..48d434ab1 100644
--- a/src/mailman/model/roster.py
+++ b/src/mailman/model/roster.py
@@ -64,16 +64,24 @@ class AbstractRoster:
def __init__(self, mlist):
self._mlist = mlist
+ def _query(self):
+ return config.db.store.find(
+ Member,
+ mailing_list=self._mlist.fqdn_listname,
+ role=self.role)
+
@property
def members(self):
"""See `IRoster`."""
- for member in config.db.store.find(
- Member,
- mailing_list=self._mlist.fqdn_listname,
- role=self.role):
+ for member in self._query():
yield member
@property
+ def member_count(self):
+ """See `IRoster`."""
+ return self._query().count()
+
+ @property
def users(self):
"""See `IRoster`."""
# Members are linked to addresses, which in turn are linked to users.
@@ -149,18 +157,12 @@ class AdministratorRoster(AbstractRoster):
name = 'administrator'
- @property
- def members(self):
- """See `IRoster`."""
- # Administrators are defined as the union of the owners and the
- # moderators.
- members = config.db.store.find(
- Member,
- Member.mailing_list == self._mlist.fqdn_listname,
- Or(Member.role == MemberRole.owner,
- Member.role == MemberRole.moderator))
- for member in members:
- yield member
+ def _query(self):
+ return config.db.store.find(
+ Member,
+ Member.mailing_list == self._mlist.fqdn_listname,
+ Or(Member.role == MemberRole.owner,
+ Member.role == MemberRole.moderator))
def get_member(self, address):
"""See `IRoster`."""
@@ -184,6 +186,14 @@ class AdministratorRoster(AbstractRoster):
class DeliveryMemberRoster(AbstractRoster):
"""Return all the members having a particular kind of delivery."""
+ @property
+ def member_count(self):
+ """See `IRoster`."""
+ # XXX 2012-03-15 BAW: It would be nice to make this more efficient.
+ # The problem is that you'd have to change the loop in _get_members()
+ # checking the delivery mode to a query parameter.
+ return len(tuple(self.members))
+
def _get_members(self, *delivery_modes):
"""The set of members for a mailing list, filter by delivery mode.
@@ -234,13 +244,10 @@ class Subscribers(AbstractRoster):
name = 'subscribers'
- @property
- def members(self):
- """See `IRoster`."""
- for member in config.db.store.find(
- Member,
- mailing_list=self._mlist.fqdn_listname):
- yield member
+ def _query(self):
+ return config.db.store.find(
+ Member,
+ mailing_list=self._mlist.fqdn_listname)
@@ -254,15 +261,23 @@ class Memberships:
def __init__(self, user):
self._user = user
- @property
- def members(self):
- """See `IRoster`."""
+ def _query(self):
results = config.db.store.find(
Member,
Or(Member.user_id == self._user.id,
And(Address.user_id == self._user.id,
Member.address_id == Address.id)))
- for member in results.config(distinct=True):
+ return results.config(distinct=True)
+
+ @property
+ def member_count(self):
+ """See `IRoster`."""
+ return self._query().count()
+
+ @property
+ def members(self):
+ """See `IRoster`."""
+ for member in self._query():
yield member
@property
diff --git a/src/mailman/model/tests/test_roster.py b/src/mailman/model/tests/test_roster.py
new file mode 100644
index 000000000..8d5a7b81b
--- /dev/null
+++ b/src/mailman/model/tests/test_roster.py
@@ -0,0 +1,156 @@
+# Copyright (C) 2012 by the Free Software Foundation, Inc.
+#
+# This file is part of GNU Mailman.
+#
+# GNU Mailman is free software: you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or (at your option)
+# any later version.
+#
+# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+# more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
+
+"""Test rosters."""
+
+from __future__ import absolute_import, print_function, unicode_literals
+
+__metaclass__ = type
+__all__ = [
+ 'TestMailingListRoster',
+ 'TestMembershipsRoster',
+ ]
+
+
+import unittest
+
+from zope.component import getUtility
+
+from mailman.app.lifecycle import create_list
+from mailman.interfaces.member import DeliveryMode, MemberRole
+from mailman.interfaces.usermanager import IUserManager
+from mailman.testing.layers import ConfigLayer
+from mailman.utilities.datetime import now
+
+
+
+class TestMailingListRoster(unittest.TestCase):
+ """Test various aspects of a mailing list's roster."""
+
+ layer = ConfigLayer
+
+ def setUp(self):
+ self._mlist = create_list('test@example.com')
+ user_manager = getUtility(IUserManager)
+ self._anne = user_manager.create_address('anne@example.com')
+ self._bart = user_manager.create_address('bart@example.com')
+ self._cris = user_manager.create_address('cris@example.com')
+
+ def test_no_members(self):
+ # Nobody with any role is subscribed to the mailing list.
+ self.assertEqual(self._mlist.owners.member_count, 0)
+ self.assertEqual(self._mlist.moderators.member_count, 0)
+ self.assertEqual(self._mlist.administrators.member_count, 0)
+ self.assertEqual(self._mlist.members.member_count, 0)
+ self.assertEqual(self._mlist.regular_members.member_count, 0)
+ self.assertEqual(self._mlist.digest_members.member_count, 0)
+ self.assertEqual(self._mlist.subscribers.member_count, 0)
+
+ def test_one_regular_member(self):
+ # One person getting regular delivery is subscribed to the mailing
+ # list as a member.
+ self._mlist.subscribe(self._anne, role=MemberRole.member)
+ self.assertEqual(self._mlist.owners.member_count, 0)
+ self.assertEqual(self._mlist.moderators.member_count, 0)
+ self.assertEqual(self._mlist.administrators.member_count, 0)
+ self.assertEqual(self._mlist.members.member_count, 1)
+ self.assertEqual(self._mlist.regular_members.member_count, 1)
+ self.assertEqual(self._mlist.digest_members.member_count, 0)
+ self.assertEqual(self._mlist.subscribers.member_count, 1)
+
+ def test_two_regular_members(self):
+ # Two people getting regular delivery are subscribed to the mailing
+ # list as members.
+ self._mlist.subscribe(self._anne, role=MemberRole.member)
+ self._mlist.subscribe(self._bart, role=MemberRole.member)
+ self.assertEqual(self._mlist.owners.member_count, 0)
+ self.assertEqual(self._mlist.moderators.member_count, 0)
+ self.assertEqual(self._mlist.administrators.member_count, 0)
+ self.assertEqual(self._mlist.members.member_count, 2)
+ self.assertEqual(self._mlist.regular_members.member_count, 2)
+ self.assertEqual(self._mlist.digest_members.member_count, 0)
+ self.assertEqual(self._mlist.subscribers.member_count, 2)
+
+ def test_one_regular_members_one_digest_member(self):
+ # Two people are subscribed to the mailing list as members. One gets
+ # regular delivery and one gets digest delivery.
+ self._mlist.subscribe(self._anne, role=MemberRole.member)
+ member = self._mlist.subscribe(self._bart, role=MemberRole.member)
+ member.preferences.delivery_mode = DeliveryMode.mime_digests
+ self.assertEqual(self._mlist.owners.member_count, 0)
+ self.assertEqual(self._mlist.moderators.member_count, 0)
+ self.assertEqual(self._mlist.administrators.member_count, 0)
+ self.assertEqual(self._mlist.members.member_count, 2)
+ self.assertEqual(self._mlist.regular_members.member_count, 1)
+ self.assertEqual(self._mlist.digest_members.member_count, 1)
+ self.assertEqual(self._mlist.subscribers.member_count, 2)
+
+ def test_a_person_is_both_a_member_and_an_owner(self):
+ # Anne is the owner of a mailing list and she gets subscribed as a
+ # member of the mailing list, receiving regular deliveries.
+ self._mlist.subscribe(self._anne, role=MemberRole.member)
+ self._mlist.subscribe(self._anne, role=MemberRole.owner)
+ self.assertEqual(self._mlist.owners.member_count, 1)
+ self.assertEqual(self._mlist.moderators.member_count, 0)
+ self.assertEqual(self._mlist.administrators.member_count, 1)
+ self.assertEqual(self._mlist.members.member_count, 1)
+ self.assertEqual(self._mlist.regular_members.member_count, 1)
+ self.assertEqual(self._mlist.digest_members.member_count, 0)
+ self.assertEqual(self._mlist.subscribers.member_count, 2)
+
+ def test_a_bunch_of_members_and_administrators(self):
+ # Anne is the owner of a mailing list, and Bart is a moderator. Anne
+ # gets subscribed as a member of the mailing list, receiving regular
+ # deliveries. Cris subscribes to the mailing list as a digest member.
+ self._mlist.subscribe(self._anne, role=MemberRole.owner)
+ self._mlist.subscribe(self._bart, role=MemberRole.moderator)
+ self._mlist.subscribe(self._anne, role=MemberRole.member)
+ member = self._mlist.subscribe(self._cris, role=MemberRole.member)
+ member.preferences.delivery_mode = DeliveryMode.mime_digests
+ self.assertEqual(self._mlist.owners.member_count, 1)
+ self.assertEqual(self._mlist.moderators.member_count, 1)
+ self.assertEqual(self._mlist.administrators.member_count, 2)
+ self.assertEqual(self._mlist.members.member_count, 2)
+ self.assertEqual(self._mlist.regular_members.member_count, 1)
+ self.assertEqual(self._mlist.digest_members.member_count, 1)
+ self.assertEqual(self._mlist.subscribers.member_count, 4)
+
+
+
+class TestMembershipsRoster(unittest.TestCase):
+ """Test the memberships roster."""
+
+ layer = ConfigLayer
+
+ def setUp(self):
+ self._ant = create_list('ant@example.com')
+ self._bee = create_list('bee@example.com')
+ user_manager = getUtility(IUserManager)
+ self._anne = user_manager.create_user('anne@example.com')
+ preferred = list(self._anne.addresses)[0]
+ preferred.verified_on = now()
+ self._anne.preferred_address = preferred
+
+ def test_no_memberships(self):
+ # An unsubscribed user has no memberships.
+ self.assertEqual(self._anne.memberships.member_count, 0)
+
+ def test_subscriptions(self):
+ # Anne subscribes to a couple of mailing lists.
+ self._ant.subscribe(self._anne)
+ self._bee.subscribe(self._anne)
+ self.assertEqual(self._anne.memberships.member_count, 2)
diff --git a/src/mailman/model/user.py b/src/mailman/model/user.py
index d1f59f957..11c719eea 100644
--- a/src/mailman/model/user.py
+++ b/src/mailman/model/user.py
@@ -51,7 +51,7 @@ class User(Model):
implements(IUser)
id = Int(primary=True)
- real_name = Unicode()
+ display_name = Unicode()
password = RawStr()
_user_id = UUID()
_created_on = DateTime()
@@ -62,20 +62,20 @@ class User(Model):
preferences_id = Int()
preferences = Reference(preferences_id, 'Preferences.id')
- def __init__(self, real_name=None, preferences=None):
+ def __init__(self, display_name=None, preferences=None):
super(User, self).__init__()
self._created_on = date_factory.now()
user_id = uid_factory.new_uid()
assert config.db.store.find(User, _user_id=user_id).count() == 0, (
'Duplicate user id {0}'.format(user_id))
self._user_id = user_id
- self.real_name = ('' if real_name is None else real_name)
+ self.display_name = ('' if display_name is None else display_name)
self.preferences = preferences
config.db.store.add(self)
def __repr__(self):
short_user_id = self.user_id.int
- return '<User "{0.real_name}" ({2}) at {1:#x}>'.format(
+ return '<User "{0.display_name}" ({2}) at {1:#x}>'.format(
self, id(self), short_user_id)
@property
@@ -132,14 +132,14 @@ class User(Model):
assert found.count() == 1, 'Unexpected count'
return found[0].user is self
- def register(self, email, real_name=None):
+ def register(self, email, display_name=None):
"""See `IUser`."""
# First, see if the address already exists
address = config.db.store.find(Address, email=email).one()
if address is None:
- if real_name is None:
- real_name = ''
- address = Address(email=email, real_name=real_name)
+ if display_name is None:
+ display_name = ''
+ address = Address(email=email, display_name=display_name)
address.preferences = Preferences()
# Link the address to the user if it is not already linked.
if address.user is not None:
diff --git a/src/mailman/model/usermanager.py b/src/mailman/model/usermanager.py
index 0c5322987..c8a5c65a2 100644
--- a/src/mailman/model/usermanager.py
+++ b/src/mailman/model/usermanager.py
@@ -40,11 +40,11 @@ from mailman.model.user import User
class UserManager:
implements(IUserManager)
- def create_user(self, email=None, real_name=None):
+ def create_user(self, email=None, display_name=None):
"""See `IUserManager`."""
- user = User(real_name, Preferences())
+ user = User(display_name, Preferences())
if email:
- address = self.create_address(email, real_name)
+ address = self.create_address(email, display_name)
user.link(address)
return user
@@ -72,18 +72,18 @@ class UserManager:
for user in config.db.store.find(User):
yield user
- def create_address(self, email, real_name=None):
+ def create_address(self, email, display_name=None):
"""See `IUserManager`."""
addresses = config.db.store.find(Address, email=email.lower())
if addresses.count() == 1:
found = addresses[0]
raise ExistingAddressError(found.original_email)
assert addresses.count() == 0, 'Unexpected results'
- if real_name is None:
- real_name = ''
+ if display_name is None:
+ display_name = ''
# It's okay not to lower case the 'email' argument because the
# constructor will do the right thing.
- address = Address(email, real_name)
+ address = Address(email, display_name)
address.preferences = Preferences()
config.db.store.add(address)
return address
diff --git a/src/mailman/mta/personalized.py b/src/mailman/mta/personalized.py
index a0b0d4b76..cebc73cae 100644
--- a/src/mailman/mta/personalized.py
+++ b/src/mailman/mta/personalized.py
@@ -63,7 +63,7 @@ class PersonalizedMixin:
# Convert the unicode name to an email-safe representation.
# Create a Header instance for the name so that it's properly
# encoded for email transport.
- name = Header(user.real_name).encode()
+ name = Header(user.display_name).encode()
msg.replace_header('To', formataddr((name, recipient)))
diff --git a/src/mailman/pipeline/acknowledge.py b/src/mailman/pipeline/acknowledge.py
index 8a0088ce4..0e0916337 100644
--- a/src/mailman/pipeline/acknowledge.py
+++ b/src/mailman/pipeline/acknowledge.py
@@ -69,20 +69,21 @@ class Acknowledge:
else member.preferred_language)
charset = language_manager[language.code].charset
# Now get the acknowledgement template.
- realname = mlist.real_name
+ display_name = mlist.display_name
text = make('postack.txt',
mailing_list=mlist,
language=language.code,
wrap=False,
subject=oneline(original_subject, charset),
- listname=realname,
+ list_name=mlist.list_name,
+ display_name=display_name,
listinfo_url=mlist.script_url('listinfo'),
optionsurl=member.options_url,
)
# Craft the outgoing message, with all headers and attributes
# necessary for general delivery. Then enqueue it to the outgoing
# queue.
- subject = _('$realname post acknowledgment')
+ subject = _('$display_name post acknowledgment')
usermsg = UserNotification(sender, mlist.bounces_address,
subject, text, language)
usermsg.send(mlist)
diff --git a/src/mailman/pipeline/calculate_recipients.py b/src/mailman/pipeline/calculate_recipients.py
index 6118a4561..15be07ecd 100644
--- a/src/mailman/pipeline/calculate_recipients.py
+++ b/src/mailman/pipeline/calculate_recipients.py
@@ -82,9 +82,9 @@ class CalculateRecipients:
# Bad Urgent: password, so reject it instead of passing it on.
# I think it's better that the sender know they screwed up
# than to deliver it normally.
- realname = mlist.real_name
+ listname = mlist.display_name
text = _("""\
-Your urgent message to the $realname mailing list was not authorized for
+Your urgent message to the $listname mailing list was not authorized for
delivery. The original message as received by Mailman is attached.
""")
raise errors.RejectMessage(wrap(text))
diff --git a/src/mailman/pipeline/decorate.py b/src/mailman/pipeline/decorate.py
index 1291a2668..d6d156048 100644
--- a/src/mailman/pipeline/decorate.py
+++ b/src/mailman/pipeline/decorate.py
@@ -57,7 +57,8 @@ def process(mlist, msg, msgdata):
d['user_address'] = recipient
d['user_delivered_to'] = member.address.original_email
d['user_language'] = member.preferred_language.description
- d['user_name'] = (member.user.real_name if member.user.real_name
+ d['user_name'] = (member.user.display_name
+ if member.user.display_name
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
@@ -215,8 +216,9 @@ def decorate(mlist, uri, extradict=None):
# any key/value pairs in the extradict.
substitutions = dict(
fqdn_listname = mlist.fqdn_listname,
- list_name = mlist.real_name,
+ list_name = mlist.list_name,
host_name = mlist.mail_host,
+ display_name = mlist.display_name,
listinfo_uri = mlist.script_url('listinfo'),
list_requests = mlist.request_address,
description = mlist.description,
diff --git a/src/mailman/pipeline/docs/acknowledge.rst b/src/mailman/pipeline/docs/acknowledge.rst
index 8c8552190..479aa4ea6 100644
--- a/src/mailman/pipeline/docs/acknowledge.rst
+++ b/src/mailman/pipeline/docs/acknowledge.rst
@@ -8,15 +8,15 @@ acknowledgment.
::
>>> mlist = create_list('test@example.com')
- >>> mlist.real_name = 'XTest'
+ >>> mlist.display_name = 'Test'
>>> mlist.preferred_language = 'en'
>>> # XXX This will almost certainly change once we've worked out the web
>>> # space layout for mailing lists now.
>>> # Ensure that the virgin queue is empty, since we'll be checking this
>>> # for new auto-response messages.
- >>> virginq = config.switchboards['virgin']
- >>> virginq.files
+ >>> from mailman.testing.helpers import get_queue_messages
+ >>> get_queue_messages('virgin')
[]
Subscribe a user to the mailing list.
@@ -46,7 +46,7 @@ Non-members can't get acknowledgments of their posts to the mailing list.
>>> handler = config.handlers['acknowledge']
>>> handler.process(mlist, msg, {})
- >>> virginq.files
+ >>> get_queue_messages('virgin')
[]
We can also specify the original sender in the message's metadata. If that
@@ -58,7 +58,7 @@ person is also not a member, no acknowledgment will be sent either.
... """)
>>> handler.process(mlist, msg,
... dict(original_sender='cperson@example.com'))
- >>> virginq.files
+ >>> get_queue_messages('virgin')
[]
@@ -72,7 +72,7 @@ Unless the user has requested acknowledgments, they will not get one.
...
... """)
>>> handler.process(mlist, msg, {})
- >>> virginq.files
+ >>> get_queue_messages('virgin')
[]
Similarly if the original sender is specified in the message metadata, and
@@ -87,7 +87,7 @@ will be sent.
>>> handler.process(mlist, msg,
... dict(original_sender='dperson@example.com'))
- >>> virginq.files
+ >>> get_queue_messages('virgin')
[]
@@ -107,23 +107,21 @@ The receipt will include the original message's subject in the response body,
...
... """)
>>> handler.process(mlist, msg, {})
- >>> len(virginq.files)
+ >>> messages = get_queue_messages('virgin')
+ >>> len(messages)
1
- >>> qmsg, qdata = virginq.dequeue(virginq.files[0])
- >>> virginq.files
- []
- >>> dump_msgdata(qdata)
+ >>> dump_msgdata(messages[0].msgdata)
_parsemsg : False
listname : test@example.com
nodecorate : True
recipients : set([u'aperson@example.com'])
reduced_list_headers: True
...
- >>> print qmsg.as_string()
+ >>> print messages[0].msg.as_string()
...
MIME-Version: 1.0
...
- Subject: XTest post acknowledgment
+ Subject: Test post acknowledgment
From: test-bounces@example.com
To: aperson@example.com
...
@@ -133,7 +131,7 @@ The receipt will include the original message's subject in the response body,
<BLANKLINE>
Something witty and insightful
<BLANKLINE>
- was successfully received by the XTest mailing list.
+ was successfully received by the Test mailing list.
<BLANKLINE>
List info page: http://lists.example.com/listinfo/test@example.com
Your preferences: http://example.com/aperson@example.com
@@ -146,22 +144,20 @@ If there is no subject, then the receipt will use a generic message.
...
... """)
>>> handler.process(mlist, msg, {})
- >>> len(virginq.files)
+ >>> messages = get_queue_messages('virgin')
+ >>> len(messages)
1
- >>> qmsg, qdata = virginq.dequeue(virginq.files[0])
- >>> virginq.files
- []
- >>> dump_msgdata(qdata)
+ >>> dump_msgdata(messages[0].msgdata)
_parsemsg : False
listname : test@example.com
nodecorate : True
recipients : set([u'aperson@example.com'])
reduced_list_headers: True
...
- >>> print qmsg.as_string()
+ >>> print messages[0].msg.as_string()
MIME-Version: 1.0
...
- Subject: XTest post acknowledgment
+ Subject: Test post acknowledgment
From: test-bounces@example.com
To: aperson@example.com
...
@@ -171,7 +167,7 @@ If there is no subject, then the receipt will use a generic message.
<BLANKLINE>
(no subject)
<BLANKLINE>
- was successfully received by the XTest mailing list.
+ was successfully received by the Test mailing list.
<BLANKLINE>
List info page: http://lists.example.com/listinfo/test@example.com
Your preferences: http://example.com/aperson@example.com
diff --git a/src/mailman/pipeline/docs/decorate.rst b/src/mailman/pipeline/docs/decorate.rst
index e24e1e252..6fa8212ac 100644
--- a/src/mailman/pipeline/docs/decorate.rst
+++ b/src/mailman/pipeline/docs/decorate.rst
@@ -89,12 +89,12 @@ short descriptive name for the mailing list).
::
>>> with open(myheader_path, 'w') as fp:
- ... print >> fp, '$list_name header'
+ ... print >> fp, '$display_name header'
>>> with open(myfooter_path, 'w') as fp:
- ... print >> fp, '$list_name footer'
+ ... print >> fp, '$display_name footer'
>>> msg = message_from_string(msg_text)
- >>> mlist.real_name = 'XTest'
+ >>> mlist.display_name = 'XTest'
>>> process(mlist, msg, {})
>>> print msg.as_string()
From: aperson@example.org
diff --git a/src/mailman/pipeline/docs/replybot.rst b/src/mailman/pipeline/docs/replybot.rst
index 208f6aae9..7cdd7c928 100644
--- a/src/mailman/pipeline/docs/replybot.rst
+++ b/src/mailman/pipeline/docs/replybot.rst
@@ -8,7 +8,7 @@ responses are subject to various conditions, such as headers in the original
message or the amount of time since the last auto-response.
>>> mlist = create_list('_xtest@example.com')
- >>> mlist.real_name = 'XTest'
+ >>> mlist.display_name = 'XTest'
Basic automatic responding
diff --git a/src/mailman/pipeline/replybot.py b/src/mailman/pipeline/replybot.py
index fc58792e8..83aa40214 100644
--- a/src/mailman/pipeline/replybot.py
+++ b/src/mailman/pipeline/replybot.py
@@ -101,14 +101,16 @@ class Replybot:
return
# Okay, we know we're going to respond to this sender, craft the
# message, send it, and update the database.
- realname = mlist.real_name
+ display_name = mlist.display_name
subject = _(
- 'Auto-response for your message to the "$realname" mailing list')
+ 'Auto-response for your message to the "$display_name" '
+ 'mailing list')
# Do string interpolation into the autoresponse text
- d = dict(listname = realname,
- listurl = mlist.script_url('listinfo'),
- requestemail = mlist.request_address,
- owneremail = mlist.owner_address,
+ d = dict(list_name = mlist.list_name,
+ display_name = display_name,
+ listurl = mlist.script_url('listinfo'),
+ requestemail = mlist.request_address,
+ owneremail = mlist.owner_address,
)
# Interpolation and Wrap the response text.
text = wrap(expand(response_text, d))
diff --git a/src/mailman/rest/addresses.py b/src/mailman/rest/addresses.py
index 8872f6ca8..e791dcfbd 100644
--- a/src/mailman/rest/addresses.py
+++ b/src/mailman/rest/addresses.py
@@ -53,8 +53,8 @@ class _AddressBase(resource.Resource, CollectionMixin):
self_link=path_to('addresses/{0}'.format(address.email)),
)
# Add optional attributes. These can be None or the empty string.
- if address.real_name:
- representation['real_name'] = address.real_name
+ if address.display_name:
+ representation['display_name'] = address.display_name
if address.verified_on:
representation['verified_on'] = address.verified_on
return representation
diff --git a/src/mailman/rest/configuration.py b/src/mailman/rest/configuration.py
index 5caf53d3c..d6b27cc6c 100644
--- a/src/mailman/rest/configuration.py
+++ b/src/mailman/rest/configuration.py
@@ -195,7 +195,7 @@ ATTRIBUTES = dict(
post_id=GetterSetter(None),
posting_address=GetterSetter(None),
posting_pipeline=GetterSetter(pipeline_validator),
- real_name=GetterSetter(unicode),
+ display_name=GetterSetter(unicode),
reply_goes_to_list=GetterSetter(enum_validator(ReplyToMunging)),
request_address=GetterSetter(None),
scheme=GetterSetter(None),
diff --git a/src/mailman/rest/docs/addresses.rst b/src/mailman/rest/docs/addresses.rst
index 71ab1f0f3..a8f875d12 100644
--- a/src/mailman/rest/docs/addresses.rst
+++ b/src/mailman/rest/docs/addresses.rst
@@ -77,10 +77,10 @@ the REST API.
>>> cris = user_manager.create_address('cris@example.com', 'Cris Person')
>>> transaction.commit()
>>> dump_json('http://localhost:9001/3.0/addresses/cris@example.com')
+ display_name: Cris Person
email: cris@example.com
http_etag: "..."
original_email: cris@example.com
- real_name: Cris Person
registered_on: 2005-08-01T07:49:23
self_link: http://localhost:9001/3.0/addresses/cris@example.com
@@ -115,10 +115,10 @@ addresses live in the /addresses namespace.
>>> transaction.commit()
>>> dump_json('http://localhost:9001/3.0/users/dave@example.com/addresses')
entry 0:
+ display_name: Dave Person
email: dave@example.com
http_etag: "..."
original_email: dave@example.com
- real_name: Dave Person
registered_on: 2005-08-01T07:49:23
self_link: http://localhost:9001/3.0/addresses/dave@example.com
http_etag: "..."
@@ -126,10 +126,10 @@ addresses live in the /addresses namespace.
total_size: 1
>>> dump_json('http://localhost:9001/3.0/addresses/dave@example.com')
+ display_name: Dave Person
email: dave@example.com
http_etag: "..."
original_email: dave@example.com
- real_name: Dave Person
registered_on: 2005-08-01T07:49:23
self_link: http://localhost:9001/3.0/addresses/dave@example.com
diff --git a/src/mailman/rest/docs/configuration.rst b/src/mailman/rest/docs/configuration.rst
index 1f83ff262..676b3426c 100644
--- a/src/mailman/rest/docs/configuration.rst
+++ b/src/mailman/rest/docs/configuration.rst
@@ -37,6 +37,7 @@ All readable attributes for a list are available on a sub-resource.
description:
digest_last_sent_at: None
digest_size_threshold: 30.0
+ display_name: Test-one
filter_content: False
fqdn_listname: test-one@example.com
generic_nonmember_action: 1
@@ -54,7 +55,6 @@ All readable attributes for a list are available on a sub-resource.
post_id: 1
posting_address: test-one@example.com
posting_pipeline: default-posting-pipeline
- real_name: Test-one
reply_goes_to_list: no_munging
request_address: test-one-request@example.com
scheme: http
@@ -88,7 +88,7 @@ all the writable attributes in one request.
... autoresponse_owner_text='the owner',
... autoresponse_postings_text='the mailing list',
... autoresponse_request_text='the robot',
- ... real_name='Fnords',
+ ... display_name='Fnords',
... description='This is my mailing list',
... include_rfc2369_headers=False,
... include_list_post_header=False,
@@ -136,13 +136,13 @@ These values are changed permanently.
description: This is my mailing list
...
digest_size_threshold: 10.5
+ display_name: Fnords
filter_content: True
...
include_list_post_header: False
include_rfc2369_headers: False
...
posting_pipeline: virgin
- real_name: Fnords
reply_goes_to_list: point_to_list
...
send_welcome_message: False
@@ -168,7 +168,7 @@ must be included. It is an error to leave one or more out...
... autoresponse_owner_text='the owner',
... autoresponse_postings_text='the mailing list',
... autoresponse_request_text='the robot',
- ... real_name='Fnords',
+ ... display_name='Fnords',
... description='This is my mailing list',
... include_rfc2369_headers=False,
... include_list_post_header=False,
@@ -208,7 +208,7 @@ must be included. It is an error to leave one or more out...
... autoresponse_owner_text='the owner',
... autoresponse_postings_text='the mailing list',
... autoresponse_request_text='the robot',
- ... real_name='Fnords',
+ ... display_name='Fnords',
... description='This is my mailing list',
... include_rfc2369_headers=False,
... include_list_post_header=False,
@@ -241,7 +241,7 @@ It is also an error to spell an attribute value incorrectly...
... autoresponse_owner_text='the owner',
... autoresponse_postings_text='the mailing list',
... autoresponse_request_text='the robot',
- ... real_name='Fnords',
+ ... display_name='Fnords',
... description='This is my mailing list',
... include_rfc2369_headers=False,
... include_list_post_header=False,
@@ -273,7 +273,7 @@ It is also an error to spell an attribute value incorrectly...
... autoresponse_owner_text='the owner',
... autoresponse_postings_text='the mailing list',
... autoresponse_request_text='the robot',
- ... real_name='Fnords',
+ ... display_name='Fnords',
... description='This is my mailing list',
... include_rfc2369_headers=False,
... include_list_post_header=False,
@@ -305,7 +305,7 @@ It is also an error to spell an attribute value incorrectly...
... autoresponse_owner_text='the owner',
... autoresponse_postings_text='the mailing list',
... autoresponse_request_text='the robot',
- ... real_name='Fnords',
+ ... display_name='Fnords',
... description='This is my mailing list',
... include_rfc2369_headers=False,
... include_list_post_header=False,
@@ -328,7 +328,7 @@ Using ``PATCH``, you can change just one attribute.
>>> dump_json('http://localhost:9001/3.0/lists/'
... 'test-one@example.com/config',
- ... dict(real_name='My List'),
+ ... dict(display_name='My List'),
... 'PATCH')
content-length: 0
date: ...
@@ -337,7 +337,7 @@ Using ``PATCH``, you can change just one attribute.
These values are changed permanently.
- >>> print mlist.real_name
+ >>> print mlist.display_name
My List
diff --git a/src/mailman/rest/docs/domains.rst b/src/mailman/rest/docs/domains.rst
index a8a4fd027..c890af7fa 100644
--- a/src/mailman/rest/docs/domains.rst
+++ b/src/mailman/rest/docs/domains.rst
@@ -136,10 +136,13 @@ example.com domain does not contain any mailing lists.
>>> dump_json('http://localhost:9001/3.0/domains/example.com/lists')
entry 0:
+ display_name: Test-domains
fqdn_listname: test-domains@example.com
http_etag: "..."
...
+ member_count: 0
self_link: http://localhost:9001/3.0/lists/test-domains@example.com
+ volume: 1
http_etag: "..."
start: 0
total_size: 1
diff --git a/src/mailman/rest/docs/lists.rst b/src/mailman/rest/docs/lists.rst
index c412f6edc..610244968 100644
--- a/src/mailman/rest/docs/lists.rst
+++ b/src/mailman/rest/docs/lists.rst
@@ -20,12 +20,14 @@ Create a mailing list in a domain and it's accessible via the API.
>>> dump_json('http://localhost:9001/3.0/lists')
entry 0:
+ display_name: Test-one
fqdn_listname: test-one@example.com
http_etag: "..."
list_name: test-one
mail_host: example.com
- real_name: Test-one
+ member_count: 0
self_link: http://localhost:9001/3.0/lists/test-one@example.com
+ volume: 1
http_etag: "..."
start: 0
total_size: 1
@@ -35,12 +37,14 @@ You can also query for lists from a particular domain.
>>> dump_json('http://localhost:9001/3.0/domains/example.com/lists')
entry 0:
+ display_name: Test-one
fqdn_listname: test-one@example.com
http_etag: "..."
list_name: test-one
mail_host: example.com
- real_name: Test-one
+ member_count: 0
self_link: http://localhost:9001/3.0/lists/test-one@example.com
+ volume: 1
http_etag: "..."
start: 0
total_size: 1
@@ -82,12 +86,14 @@ The mailing list exists in the database.
It is also available via the location given in the response.
>>> dump_json('http://localhost:9001/3.0/lists/test-two@example.com')
+ display_name: Test-two
fqdn_listname: test-two@example.com
http_etag: "..."
list_name: test-two
mail_host: example.com
- real_name: Test-two
+ member_count: 0
self_link: http://localhost:9001/3.0/lists/test-two@example.com
+ volume: 1
However, you are not allowed to create a mailing list in a domain that does
not exist.
diff --git a/src/mailman/rest/docs/membership.rst b/src/mailman/rest/docs/membership.rst
index e339291aa..860d33c21 100644
--- a/src/mailman/rest/docs/membership.rst
+++ b/src/mailman/rest/docs/membership.rst
@@ -480,7 +480,7 @@ get gets a regular delivery.
>>> dump_json('http://localhost:9001/3.0/members', {
... 'fqdn_listname': 'ant@example.com',
... 'subscriber': 'eperson@example.com',
- ... 'real_name': 'Elly Person',
+ ... 'display_name': 'Elly Person',
... })
content-length: 0
date: ...
@@ -608,7 +608,7 @@ Fred joins the `ant` mailing list but wants MIME digest delivery.
>>> dump_json('http://localhost:9001/3.0/members', {
... 'fqdn_listname': 'ant@example.com',
... 'subscriber': 'fperson@example.com',
- ... 'real_name': 'Fred Person',
+ ... 'display_name': 'Fred Person',
... 'delivery_mode': 'mime_digests',
... })
content-length: 0
diff --git a/src/mailman/rest/docs/users.rst b/src/mailman/rest/docs/users.rst
index 145b069d9..a20306e17 100644
--- a/src/mailman/rest/docs/users.rst
+++ b/src/mailman/rest/docs/users.rst
@@ -26,8 +26,8 @@ When there are users in the database, they can be retrieved as a collection.
>>> dump_json('http://localhost:9001/3.0/users')
entry 0:
created_on: 2005-08-01T07:49:23
+ display_name: Anne Person
http_etag: "..."
- real_name: Anne Person
self_link: http://localhost:9001/3.0/users/1
user_id: 1
http_etag: "..."
@@ -48,8 +48,8 @@ returned in the REST API.
>>> dump_json('http://localhost:9001/3.0/users')
entry 0:
created_on: 2005-08-01T07:49:23
+ display_name: Anne Person
http_etag: "..."
- real_name: Anne Person
self_link: http://localhost:9001/3.0/users/1
user_id: 1
entry 1:
@@ -72,7 +72,7 @@ email address for the user, a password, and optionally the user's full name.
>>> transaction.abort()
>>> dump_json('http://localhost:9001/3.0/users', {
... 'email': 'bart@example.com',
- ... 'real_name': 'Bart Person',
+ ... 'display_name': 'Bart Person',
... 'password': 'bbb',
... })
content-length: 0
@@ -92,9 +92,9 @@ It is also available via the location given in the response.
>>> dump_json('http://localhost:9001/3.0/users/3')
created_on: 2005-08-01T07:49:23
+ display_name: Bart Person
http_etag: "..."
password: {CLEARTEXT}bbb
- real_name: Bart Person
self_link: http://localhost:9001/3.0/users/3
user_id: 3
@@ -103,9 +103,9 @@ them with user ids. Thus, a user can be retrieved via its email address.
>>> dump_json('http://localhost:9001/3.0/users/bart@example.com')
created_on: 2005-08-01T07:49:23
+ display_name: Bart Person
http_etag: "..."
password: {CLEARTEXT}bbb
- real_name: Bart Person
self_link: http://localhost:9001/3.0/users/3
user_id: 3
@@ -117,7 +117,7 @@ therefore cannot be retrieved. It can be reset though.
>>> transaction.abort()
>>> dump_json('http://localhost:9001/3.0/users', {
... 'email': 'cris@example.com',
- ... 'real_name': 'Cris Person',
+ ... 'display_name': 'Cris Person',
... })
content-length: 0
date: ...
@@ -127,9 +127,9 @@ therefore cannot be retrieved. It can be reset though.
>>> dump_json('http://localhost:9001/3.0/users/4')
created_on: 2005-08-01T07:49:23
+ display_name: Cris Person
http_etag: "..."
password: {CLEARTEXT}...
- real_name: Cris Person
self_link: http://localhost:9001/3.0/users/4
user_id: 4
@@ -204,10 +204,10 @@ sorted in lexical order by original (i.e. case-preserved) email address.
registered_on: 2005-08-01T07:49:23
self_link: http://localhost:9001/3.0/addresses/bart.person@example.com
entry 2:
+ display_name: Bart Person
email: bart@example.com
http_etag: "..."
original_email: bart@example.com
- real_name: Bart Person
registered_on: 2005-08-01T07:49:23
self_link: http://localhost:9001/3.0/addresses/bart@example.com
entry 3:
@@ -225,32 +225,32 @@ In fact, any of these addresses can be used to look up Bart's user record.
>>> dump_json('http://localhost:9001/3.0/users/bart@example.com')
created_on: 2005-08-01T07:49:23
+ display_name: Bart Person
http_etag: "..."
password: {CLEARTEXT}bbb
- real_name: Bart Person
self_link: http://localhost:9001/3.0/users/3
user_id: 3
>>> dump_json('http://localhost:9001/3.0/users/bart.person@example.com')
created_on: 2005-08-01T07:49:23
+ display_name: Bart Person
http_etag: "..."
password: {CLEARTEXT}bbb
- real_name: Bart Person
self_link: http://localhost:9001/3.0/users/3
user_id: 3
>>> dump_json('http://localhost:9001/3.0/users/bperson@example.com')
created_on: 2005-08-01T07:49:23
+ display_name: Bart Person
http_etag: "..."
password: {CLEARTEXT}bbb
- real_name: Bart Person
self_link: http://localhost:9001/3.0/users/3
user_id: 3
>>> dump_json('http://localhost:9001/3.0/users/Bart.Q.Person@example.com')
created_on: 2005-08-01T07:49:23
+ display_name: Bart Person
http_etag: "..."
password: {CLEARTEXT}bbb
- real_name: Bart Person
self_link: http://localhost:9001/3.0/users/3
user_id: 3
diff --git a/src/mailman/rest/helpers.py b/src/mailman/rest/helpers.py
index 2ce62b0fe..2824a894e 100644
--- a/src/mailman/rest/helpers.py
+++ b/src/mailman/rest/helpers.py
@@ -77,10 +77,10 @@ class ExtendedEncoder(json.JSONEncoder):
seconds = obj.seconds + obj.microseconds / 1000000.0
return '{0}d{1}s'.format(obj.days, seconds)
return '{0}d'.format(obj.days)
- elif hasattr(obj, 'enumclass') and issubclass(obj.enumclass, Enum):
+ elif hasattr(obj, 'enum') and issubclass(obj.enum, Enum):
# It's up to the decoding validator to associate this name with
# the right Enum class.
- return obj.enumname
+ return obj.name
return json.JSONEncoder.default(self, obj)
diff --git a/src/mailman/rest/lists.py b/src/mailman/rest/lists.py
index 0103022e7..c95c9a88a 100644
--- a/src/mailman/rest/lists.py
+++ b/src/mailman/rest/lists.py
@@ -105,10 +105,12 @@ class _ListBase(resource.Resource, CollectionMixin):
def _resource_as_dict(self, mlist):
"""See `CollectionMixin`."""
return dict(
+ display_name=mlist.display_name,
fqdn_listname=mlist.fqdn_listname,
- mail_host=mlist.mail_host,
list_name=mlist.list_name,
- real_name=mlist.real_name,
+ mail_host=mlist.mail_host,
+ member_count=mlist.members.member_count,
+ volume=mlist.volume,
self_link=path_to('lists/{0}'.format(mlist.fqdn_listname)),
)
diff --git a/src/mailman/rest/members.py b/src/mailman/rest/members.py
index d55aa7dc2..761e3147c 100644
--- a/src/mailman/rest/members.py
+++ b/src/mailman/rest/members.py
@@ -199,10 +199,10 @@ class AllMembers(_MemberBase):
validator = Validator(
fqdn_listname=unicode,
subscriber=subscriber_validator,
- real_name=unicode,
+ display_name=unicode,
delivery_mode=enum_validator(DeliveryMode),
role=enum_validator(MemberRole),
- _optional=('delivery_mode', 'real_name', 'role'))
+ _optional=('delivery_mode', 'display_name', 'role'))
member = service.join(**validator(request))
except AlreadySubscribedError:
return http.conflict([], b'Member already subscribed')
diff --git a/src/mailman/rest/tests/test_lists.py b/src/mailman/rest/tests/test_lists.py
index 78b793c6c..b030a2e8d 100644
--- a/src/mailman/rest/tests/test_lists.py
+++ b/src/mailman/rest/tests/test_lists.py
@@ -17,23 +17,31 @@
"""REST list tests."""
-from __future__ import absolute_import, unicode_literals
+from __future__ import absolute_import, print_function, unicode_literals
__metaclass__ = type
__all__ = [
+ 'TestLists',
+ 'TestListsMissing',
]
import unittest
from urllib2 import HTTPError
+from zope.component import getUtility
+from mailman.app.lifecycle import create_list
+from mailman.config import config
+from mailman.interfaces.usermanager import IUserManager
from mailman.testing.helpers import call_api
from mailman.testing.layers import RESTLayer
-class TestLists(unittest.TestCase):
+class TestListsMissing(unittest.TestCase):
+ """Test expected failures."""
+
layer = RESTLayer
def test_missing_list_roster_member_404(self):
@@ -79,3 +87,44 @@ class TestLists(unittest.TestCase):
self.assertEqual(exc.code, 404)
else:
raise AssertionError('Expected HTTPError')
+
+
+
+class TestLists(unittest.TestCase):
+ """Test various aspects of mailing list resources."""
+
+ layer = RESTLayer
+
+ def setUp(self):
+ self._mlist = create_list('test@example.com')
+ config.db.commit()
+ self._usermanager = getUtility(IUserManager)
+
+ def test_member_count_with_no_members(self):
+ # The list initially has 0 members.
+ resource, response = call_api(
+ 'http://localhost:9001/3.0/lists/test@example.com')
+ self.assertEqual(response.status, 200)
+ self.assertEqual(resource['member_count'], 0)
+
+ def test_member_count_with_one_member(self):
+ # Add a member to a list and check that the resource reflects this.
+ anne = self._usermanager.create_address('anne@example.com')
+ self._mlist.subscribe(anne)
+ config.db.commit()
+ resource, response = call_api(
+ 'http://localhost:9001/3.0/lists/test@example.com')
+ self.assertEqual(response.status, 200)
+ self.assertEqual(resource['member_count'], 1)
+
+ def test_member_count_with_two_members(self):
+ # Add two members to a list and check that the resource reflects this.
+ anne = self._usermanager.create_address('anne@example.com')
+ self._mlist.subscribe(anne)
+ bart = self._usermanager.create_address('bar@example.com')
+ self._mlist.subscribe(bart)
+ config.db.commit()
+ resource, response = call_api(
+ 'http://localhost:9001/3.0/lists/test@example.com')
+ self.assertEqual(response.status, 200)
+ self.assertEqual(resource['member_count'], 2)
diff --git a/src/mailman/rest/tests/test_membership.py b/src/mailman/rest/tests/test_membership.py
index 568a56306..202e5f057 100644
--- a/src/mailman/rest/tests/test_membership.py
+++ b/src/mailman/rest/tests/test_membership.py
@@ -124,7 +124,7 @@ class TestMembership(unittest.TestCase):
call_api('http://localhost:9001/3.0/members', {
'fqdn_listname': 'test@example.com',
'subscriber': 'anne@example.com',
- 'real_name': 'Anne Person',
+ 'display_name': 'Anne Person',
'delivery_mode': 'invalid-mode',
})
except HTTPError as exc:
@@ -138,7 +138,7 @@ class TestMembership(unittest.TestCase):
content, response = call_api('http://localhost:9001/3.0/members', {
'fqdn_listname': 'test@example.com',
'subscriber': 'hugh/person@example.com',
- 'real_name': 'Hugh Person',
+ 'display_name': 'Hugh Person',
})
self.assertEqual(content, None)
self.assertEqual(response.status, 201)
diff --git a/src/mailman/rest/users.py b/src/mailman/rest/users.py
index 0a1503930..4e1362120 100644
--- a/src/mailman/rest/users.py
+++ b/src/mailman/rest/users.py
@@ -60,8 +60,8 @@ class _UserBase(resource.Resource, CollectionMixin):
# with the real name. These could be None or the empty string.
if user.password:
resource['password'] = user.password
- if user.real_name:
- resource['real_name'] = user.real_name
+ if user.display_name:
+ resource['display_name'] = user.display_name
return resource
def _get_collection(self, request):
@@ -84,9 +84,9 @@ class AllUsers(_UserBase):
"""Create a new user."""
try:
validator = Validator(email=unicode,
- real_name=unicode,
+ display_name=unicode,
password=unicode,
- _optional=('real_name', 'password'))
+ _optional=('display_name', 'password'))
arguments = validator(request)
except ValueError as error:
return http.bad_request([], str(error))
diff --git a/src/mailman/rules/administrivia.py b/src/mailman/rules/administrivia.py
index 790c16c19..41c6edf30 100644
--- a/src/mailman/rules/administrivia.py
+++ b/src/mailman/rules/administrivia.py
@@ -81,7 +81,7 @@ class Administrivia:
lineno = 0
for line in lines:
line = line.strip()
- if line == '':
+ if len(line) == 0:
continue
lineno += 1
if lineno > config.mailman.email_commands_max_lines:
diff --git a/src/mailman/rules/moderation.py b/src/mailman/rules/moderation.py
index bcec47cba..cb27d89d8 100644
--- a/src/mailman/rules/moderation.py
+++ b/src/mailman/rules/moderation.py
@@ -57,7 +57,7 @@ class MemberModeration:
elif action is not None:
# We must stringify the moderation action so that it can be
# stored in the pending request table.
- msgdata['moderation_action'] = action.enumname
+ msgdata['moderation_action'] = action.name
msgdata['moderation_sender'] = sender
return True
# The sender is not a member so this rule does not match.
@@ -98,7 +98,7 @@ class NonmemberModeration:
elif action is not None:
# We must stringify the moderation action so that it can be
# stored in the pending request table.
- msgdata['moderation_action'] = action.enumname
+ msgdata['moderation_action'] = action.name
msgdata['moderation_sender'] = sender
return True
# The sender must be a member, so this rule does not match.
diff --git a/src/mailman/rules/suspicious.py b/src/mailman/rules/suspicious.py
index a06a2d0aa..ad1ab42cd 100644
--- a/src/mailman/rules/suspicious.py
+++ b/src/mailman/rules/suspicious.py
@@ -68,7 +68,7 @@ def _parse_matching_header_opt(mlist):
# This didn't look like a header line. BAW: should do a
# better job of informing the list admin.
log.error('bad bounce_matching_header line: %s\n%s',
- mlist.real_name, line)
+ mlist.display_name, line)
else:
header = line[:i]
value = line[i+1:].lstrip()
@@ -79,7 +79,7 @@ def _parse_matching_header_opt(mlist):
# job of informing the list admin.
log.error("""\
bad regexp in bounce_matching_header line: %s
-\n%s (cause: %s)""", mlist.real_name, value, error)
+\n%s (cause: %s)""", mlist.display_name, value, error)
else:
all.append((header, cre, line))
return all
diff --git a/src/mailman/runners/digest.py b/src/mailman/runners/digest.py
index 2730fc427..b4ae9a442 100644
--- a/src/mailman/runners/digest.py
+++ b/src/mailman/runners/digest.py
@@ -63,9 +63,8 @@ class Digester:
self._mlist = mlist
self._charset = mlist.preferred_language.charset
# This will be used in the Subject, so use $-strings.
- realname = mlist.real_name
- issue = digest_number
- self._digest_id = _('$realname Digest, Vol $volume, Issue $issue')
+ self._digest_id = _(
+ '$mlist.display_name Digest, Vol $volume, Issue $digest_number')
self._subject = Header(self._digest_id,
self._charset,
header_name='Subject')
@@ -83,7 +82,7 @@ class Digester:
# ahead and add it now.
self._masthead = make('masthead.txt',
mailing_list=mlist,
- real_name=mlist.real_name,
+ display_name=mlist.display_name,
got_list_email=mlist.posting_address,
got_listinfo_url=mlist.script_url('listinfo'),
got_request_email=mlist.request_address,
diff --git a/src/mailman/runners/docs/digester.rst b/src/mailman/runners/docs/digester.rst
index 5a20db556..4b9481f3e 100644
--- a/src/mailman/runners/docs/digester.rst
+++ b/src/mailman/runners/docs/digester.rst
@@ -346,7 +346,7 @@ You can see that the digests contain a mix of French and Japanese.
Content-Type: multipart/mixed; boundary="===============...=="
MIME-Version: 1.0
From: test-request@example.com
- Subject: Groupe Test, Vol. 1, Parution 2
+ Subject: Groupe Test, Vol 1, Parution 2
To: test@example.com
Reply-To: test@example.com
Date: ...
@@ -356,7 +356,7 @@ You can see that the digests contain a mix of French and Japanese.
Content-Type: text/plain; charset="iso-8859-1"
MIME-Version: 1.0
Content-Transfer-Encoding: quoted-printable
- Content-Description: Groupe Test, Vol. 1, Parution 2
+ Content-Description: Groupe Test, Vol 1, Parution 2
<BLANKLINE>
Envoyez vos messages pour la liste Test =E0
test@example.com
@@ -413,7 +413,7 @@ French and Japanese characters.
>>> print rfc1153.msg.as_string()
From: test-request@example.com
- Subject: Groupe Test, Vol. 1, Parution 2
+ Subject: Groupe Test, Vol 1, Parution 2
To: test@example.com
Reply-To: test@example.com
Date: ...
@@ -471,8 +471,8 @@ The content can be decoded to see the actual digest text.
"'http://lists.example.com/listinfo/test@example.com'",
"''",
"''",
- "'Fin de Groupe Test, Vol. 1, Parution 2'",
- "'**************************************'"]
+ "'Fin de Groupe Test, Vol 1, Parution 2'",
+ "'*************************************'"]
>>> config.pop('french')
diff --git a/src/mailman/styles/default.py b/src/mailman/styles/default.py
index 95672c62c..471b43272 100644
--- a/src/mailman/styles/default.py
+++ b/src/mailman/styles/default.py
@@ -55,7 +55,7 @@ class DefaultStyle:
# For cut-n-paste convenience.
mlist = mailing_list
# List identity.
- mlist.real_name = mlist.list_name.capitalize()
+ mlist.display_name = mlist.list_name.capitalize()
mlist.list_id = '{0.list_name}.{0.mail_host}'.format(mlist)
mlist.include_rfc2369_headers = True
mlist.include_list_post_header = True
@@ -132,7 +132,7 @@ from: .*@uplinkpro.com
# Max autoresponses per day. A mapping between addresses and a
# 2-tuple of the date of the last autoresponse and the number of
# autoresponses sent on that date.
- mlist.subject_prefix = _('[$mlist.real_name] ')
+ mlist.subject_prefix = _('[$mlist.display_name] ')
mlist.header_uri = None
mlist.footer_uri = 'mailman:///$listname/$language/footer-generic.txt'
# Set this to Never if the list's preferred language uses us-ascii,
diff --git a/src/mailman/templates/en/footer-generic.txt b/src/mailman/templates/en/footer-generic.txt
index ef76e4986..d31e885f0 100644
--- a/src/mailman/templates/en/footer-generic.txt
+++ b/src/mailman/templates/en/footer-generic.txt
@@ -1,4 +1,4 @@
_______________________________________________
-$list_name mailing list
+$display_name mailing list
$fqdn_listname
${listinfo_uri}
diff --git a/src/mailman/templates/en/masthead.txt b/src/mailman/templates/en/masthead.txt
index 01cd6afec..5d4cc9696 100644
--- a/src/mailman/templates/en/masthead.txt
+++ b/src/mailman/templates/en/masthead.txt
@@ -1,4 +1,4 @@
-Send $real_name mailing list submissions to
+Send $display_name mailing list submissions to
$got_list_email
To subscribe or unsubscribe via the World Wide Web, visit
@@ -10,4 +10,4 @@ You can reach the person managing the list at
$got_owner_email
When replying, please edit your Subject line so it is more specific than
-"Re: Contents of $real_name digest..."
+"Re: Contents of $display_name digest..."
diff --git a/src/mailman/templates/en/postack.txt b/src/mailman/templates/en/postack.txt
index a2d6e972b..7f5836344 100644
--- a/src/mailman/templates/en/postack.txt
+++ b/src/mailman/templates/en/postack.txt
@@ -2,7 +2,7 @@ Your message entitled
$subject
-was successfully received by the $listname mailing list.
+was successfully received by the $display_name mailing list.
List info page: $listinfo_url
Your preferences: $optionsurl
diff --git a/src/mailman/testing/mailman-fr.mo b/src/mailman/testing/mailman-fr.mo
index df721cb0f..5758baf60 100644
--- a/src/mailman/testing/mailman-fr.mo
+++ b/src/mailman/testing/mailman-fr.mo
Binary files differ
diff --git a/src/mailman/testing/mailman-fr.po b/src/mailman/testing/mailman-fr.po
index 5f8c910e2..ca4ce8c32 100644
--- a/src/mailman/testing/mailman-fr.po
+++ b/src/mailman/testing/mailman-fr.po
@@ -17,7 +17,7 @@ msgstr ""
#: templates/en/masthead.txt:1
msgid ""
-"Send $real_name mailing list submissions to\n"
+"Send $display_name mailing list submissions to\n"
"\t$got_list_email\n"
"\n"
"To subscribe or unsubscribe via the World Wide Web, visit\n"
@@ -29,9 +29,9 @@ msgid ""
"\t$got_owner_email\n"
"\n"
"When replying, please edit your Subject line so it is more specific than\n"
-"\"Re: Contents of $real_name digest...\""
+"\"Re: Contents of $display_name digest...\""
msgstr ""
-"Envoyez vos messages pour la liste $real_name à\n"
+"Envoyez vos messages pour la liste $display_name à\n"
"\t$got_list_email\n"
"\n"
"Pour vous (dés)abonner par le web, consultez\n"
@@ -45,11 +45,11 @@ msgstr ""
"\t$got_owner_email\n"
"\n"
"Si vous répondez, n'oubliez pas de changer l'objet du message afin\n"
-"qu'il soit plus spécifique que « Re: Contenu du groupe de $real_name... »"
+"qu'il soit plus spécifique que « Re: Contenu du groupe de $display_name... »"
#: Mailman/Handlers/ToDigest.py:159
-msgid "$realname Digest, Vol $volume, Issue $issue"
-msgstr "Groupe $realname, Vol. $volume, Parution $issue"
+msgid "$mlist.display_name Digest, Vol $volume, Issue $digest_number"
+msgstr "Groupe $mlist.display_name, Vol $volume, Parution $digest_number"
#: Mailman/Handlers/ToDigest.py:205
msgid "digest header"
diff --git a/src/mailman/testing/mailman-xx.mo b/src/mailman/testing/mailman-xx.mo
index ecbad0364..bfc2e0845 100644
--- a/src/mailman/testing/mailman-xx.mo
+++ b/src/mailman/testing/mailman-xx.mo
Binary files differ
diff --git a/src/mailman/testing/mailman-xx.po b/src/mailman/testing/mailman-xx.po
index e2ad28c33..c6543d3d5 100644
--- a/src/mailman/testing/mailman-xx.po
+++ b/src/mailman/testing/mailman-xx.po
@@ -13,5 +13,5 @@ msgstr ""
"Generated-By: pygettext.py 1.3\n"
#: src/mailman/app/bounces.py:227
-msgid "$mlist.real_name mailing list probe message"
-msgstr "ailing-may ist-lay $mlist.real_name obe-pray essage-may"
+msgid "$mlist.display_name mailing list probe message"
+msgstr "ailing-may ist-lay $mlist.display_name obe-pray essage-may"
diff --git a/src/mailman/utilities/importer.py b/src/mailman/utilities/importer.py
index d31a3892f..f77d86e9a 100644
--- a/src/mailman/utilities/importer.py
+++ b/src/mailman/utilities/importer.py
@@ -56,6 +56,7 @@ TYPES = dict(
# Attribute names in Mailman 2 which are renamed in Mailman 3.
NAME_MAPPINGS = dict(
host_name='mail_host',
+ real_name='display_name',
)
diff --git a/src/mailman/utilities/tests/test_import.py b/src/mailman/utilities/tests/test_import.py
index 2ce25ddc6..58a51e61b 100644
--- a/src/mailman/utilities/tests/test_import.py
+++ b/src/mailman/utilities/tests/test_import.py
@@ -49,11 +49,11 @@ class TestBasicImport(unittest.TestCase):
def _import(self):
import_config_pck(self._mlist, self._pckdict)
- def test_real_name(self):
- # The mlist.real_name gets set.
- self.assertEqual(self._mlist.real_name, 'Blank')
+ def test_display_name(self):
+ # The mlist.display_name gets set from the old list's real_name.
+ self.assertEqual(self._mlist.display_name, 'Blank')
self._import()
- self.assertEqual(self._mlist.real_name, 'Test')
+ self.assertEqual(self._mlist.display_name, 'Test')
def test_mail_host(self):
# The mlist.mail_host gets set.