Commit b61c82e5 authored by Barry Warsaw's avatar Barry Warsaw

trunk merge

parents f771f80d 66440232
[buildout]
parts =
interpreter
test
unzip = true
develop = .
[interpreter]
recipe = zc.recipe.egg
interpreter = py
eggs =
mailman
[test]
recipe = zc.recipe.testrunner
eggs =
mailman
defaults = '--tests-pattern ^tests --exit-with-status'.split()
# Hack in extra arguments to zope.testrunner.
initialization = from mailman.testing import initialize;
initialize('${buildout:directory}')
This diff is collapsed.
......@@ -93,22 +93,22 @@ case second `m'. Any other spelling is incorrect.""",
'console_scripts' : list(scripts),
},
install_requires = [
'enum34',
'flufl.bounce',
'flufl.enum',
'flufl.i18n',
'flufl.lock',
'httplib2',
'lazr.config',
'lazr.smtptest',
'mock',
'nose2',
'passlib',
'restish',
'storm',
'zc.buildout',
'zope.component',
'zope.configuration',
'zope.event',
'zope.interface',
'zope.testing<4',
],
test_suite = 'nose2.collector.collector',
)
......@@ -44,7 +44,7 @@ except ImportError:
#
# Do *not* do this if we're building the documentation.
if 'build_sphinx' not in sys.argv:
if sys.argv[0].split(os.sep)[-1] == 'test':
if any('nose2' in arg for arg in sys.argv):
from mailman.testing.i18n import initialize
else:
from mailman.core.i18n import initialize
......
......@@ -52,8 +52,9 @@ script that will produce no output to force the hooks to run.
>>> import subprocess
>>> from mailman.testing.layers import ConfigLayer
>>> def call():
... exe = os.path.join(os.path.dirname(sys.executable), 'mailman')
... proc = subprocess.Popen(
... 'bin/mailman lists --domain ignore -q'.split(),
... [exe, 'lists', '--domain', 'ignore', '-q'],
... cwd=ConfigLayer.root_directory,
... env=dict(MAILMAN_CONFIG_FILE=config_path,
... PYTHONPATH=config_directory),
......
......@@ -246,7 +246,7 @@ def handle_subscription(mlist, id, action, comment=None):
lang=getUtility(ILanguageManager)[data['language']])
elif action is Action.accept:
key, data = requestdb.get_request(id)
delivery_mode = DeliveryMode(data['delivery_mode'])
delivery_mode = DeliveryMode[data['delivery_mode']]
address = data['address']
display_name = data['display_name']
language = getUtility(ILanguageManager)[data['language']]
......
......@@ -54,7 +54,7 @@ def _membership_sort_key(member):
The members are sorted first by unique list id, then by subscribed email
address, then by role.
"""
return (member.list_id, member.address.email, int(member.role))
return (member.list_id, member.address.email, member.role.value)
......
......@@ -34,7 +34,7 @@ import socket
import logging
from datetime import timedelta
from flufl.enum import Enum
from enum import Enum
from flufl.lock import Lock, NotLockedError, TimeOutError
from lazr.config import as_boolean
......
......@@ -65,10 +65,20 @@ class Conf:
key-values pair from any section matching the given key will be
displayed.
"""))
command_parser.add_argument(
'-t', '--sort',
default=False, action='store_true',
help=_('Sort the output by sections and keys.'))
def _get_value(self, section, key):
return getattr(getattr(config, section), key)
def _sections(self, sort_p):
sections = config.schema._section_schemas
if sort_p:
sections = sorted(sections)
return sections
def _print_full_syntax(self, section, key, value, output):
print('[{}] {}: {}'.format(section, key, value), file=output)
......@@ -78,8 +88,10 @@ class Conf:
def _show_section_error(self, section):
self.parser.error('No such section: {}'.format(section))
def _print_values_for_section(self, section, output):
def _print_values_for_section(self, section, output, sort_p):
current_section = getattr(config, section)
if sort_p:
current_section = sorted(current_section)
for key in current_section:
self._print_full_syntax(section, key,
self._get_value(section, key), output)
......@@ -94,6 +106,7 @@ class Conf:
# Process the command, ignoring the closing of the output file.
section = args.section
key = args.key
sort_p = args.sort
# Case 1: Both section and key are given, so we can directly look up
# the value.
if section is not None and key is not None:
......@@ -106,12 +119,12 @@ class Conf:
# Case 2: Section is given, key is not given.
elif section is not None and key is None:
if self._section_exists(section):
self._print_values_for_section(section, output)
self._print_values_for_section(section, output, sort_p)
else:
self._show_section_error(section)
# Case 3: Section is not given, key is given.
elif section is None and key is not None:
for current_section in config.schema._section_schemas:
for current_section in self._sections(sort_p):
# We have to ensure that the current section actually exists
# and that it contains the given key.
if (self._section_exists(current_section) and
......@@ -124,12 +137,13 @@ class Conf:
# Case 4: Neither section nor key are given, just display all the
# sections and their corresponding key/value pairs.
elif section is None and key is None:
for current_section in config.schema._section_schemas:
for current_section in self._sections(sort_p):
# However, we have to make sure that the current sections and
# key which are being looked up actually exist before trying
# to print them.
if self._section_exists(current_section):
self._print_values_for_section(current_section, output)
self._print_values_for_section(
current_section, output, sort_p)
def process(self, args):
"""See `ICLISubCommand`."""
......
......@@ -130,7 +130,7 @@ class Members:
DeliveryMode.mime_digests,
DeliveryMode.summary_digests]
elif args.digest is not None:
digest_types = [DeliveryMode(args.digest + '_digests')]
digest_types = [DeliveryMode[args.digest + '_digests']]
else:
# Don't filter on digest type.
pass
......@@ -140,7 +140,7 @@ class Members:
elif args.nomail == 'byadmin':
status_types = [DeliveryStatus.by_moderator]
elif args.nomail.startswith('by'):
status_types = [DeliveryStatus('by_' + args.nomail[2:])]
status_types = [DeliveryStatus['by_' + args.nomail[2:]]]
elif args.nomail == 'enabled':
status_types = [DeliveryStatus.enabled]
elif args.nomail == 'unknown':
......
......@@ -64,4 +64,4 @@ class Status:
message = _('GNU Mailman is in an unexpected state '
'($hostname != $fqdn_name)')
print(message)
return int(status)
return status.value
......@@ -14,6 +14,7 @@ a specific key-value pair, or several key-value pairs.
... key = None
... section = None
... output = None
... sort = False
>>> from mailman.commands.cli_conf import Conf
>>> command = Conf()
......@@ -64,5 +65,16 @@ If you specify both a section and a key, you will get the corresponding value.
>>> command.process(FakeArgs)
[email protected]
You can also sort the output. The output is first sorted by section, then by
key.
>>> FakeArgs.key = None
>>> FakeArgs.section = 'shell'
>>> FakeArgs.sort = True
>>> command.process(FakeArgs)
[shell] banner: Welcome to the GNU Mailman shell
[shell] prompt: >>>
[shell] use_ipython: no
.. _`Postfix command postconf(1)`: http://www.postfix.org/postconf.1.html
......@@ -31,6 +31,7 @@ import mock
import tempfile
import unittest
from StringIO import StringIO
from mailman.commands.cli_conf import Conf
from mailman.testing.layers import ConfigLayer
......@@ -40,6 +41,7 @@ class FakeArgs:
section = None
key = None
output = None
sort = False
class FakeParser:
......@@ -99,3 +101,13 @@ class TestConf(unittest.TestCase):
finally:
os.remove(filename)
self.assertEqual(contents, 'no\n')
def test_sort_by_section(self):
self.args.output = '-'
self.args.sort = True
output = StringIO()
with mock.patch('sys.stdout', output):
self.command.process(self.args)
last_line = ''
for line in output.getvalue().splitlines():
self.assertTrue(line > last_line)
......@@ -67,11 +67,11 @@ def upgrade(database, store, version, module_path):
def archive_policy(archive, archive_private):
"""Convert archive and archive_private to archive_policy."""
if archive == 0:
return int(ArchivePolicy.never)
return ArchivePolicy.never.value
elif archive_private == 1:
return int(ArchivePolicy.private)
return ArchivePolicy.private.value
else:
return int(ArchivePolicy.public)
return ArchivePolicy.public.value
......
......@@ -32,7 +32,7 @@ from storm.variables import Variable
class _EnumVariable(Variable):
"""Storm variable for supporting flufl.enum.Enum types.
"""Storm variable for supporting enum types.
To use this, make the database column a INTEGER.
"""
......@@ -46,18 +46,18 @@ class _EnumVariable(Variable):
return None
if not from_db:
return value
return self._enum[value]
return self._enum(value)
def parse_get(self, value, to_db):
if value is None:
return None
if not to_db:
return value
return int(value)
return value.value
class Enum(SimpleProperty):
"""Custom Enum type for Storm supporting flufl.enum.Enums."""
"""Custom type for Storm supporting enums."""
variable_class = _EnumVariable
......
......@@ -37,10 +37,12 @@ processing queues.
node [shape=box, color=lightblue, style=filled];
msg [shape=ellipse, color=black, fillcolor=white];
lmtpd [label="LMTP\nSERVER"];
rts [label="Return\nto Sender"];
msg -> MTA [label="SMTP"];
MTA -> lmtpd [label="LMTP"];
lmtpd -> MTA [label="reject"];
lmtpd -> IN -> PIPELINE [label=".pck"];
IN -> rts;
lmtpd -> BOUNCES [label=".pck"];
lmtpd -> COMMAND [label=".pck"];
}
......@@ -49,7 +51,9 @@ 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.
added to the `pipeline` (i.e. posting) queue. (Note that rejecting at this
stage is *not* equivalent to rejecting during LMTP processing. This issue is
currently unresolved.)
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
......@@ -60,7 +64,8 @@ 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 (not depicted above) is a special queue for messages created
by Mailman.
.. graphviz::
......@@ -97,12 +102,13 @@ The default set of rules looks something like this:
subgraph rules {
rankdir=TB;
node [shape=record];
approved [label="<in> approved | { <no> | <yes> }"];
emergency [label="<in> emergency | { <no> | <yes> }"];
loop [label="<in> loop | { <no> | <yes> }"];
modmember [label="<in> member\nmoderated | { <no> | <yes> }"];
administrivia [group="0", label="<in> administrivia | <always> "];
maxsize [label="<in> max\ size | {<in> no | <yes>}"];
approved [label="<in> approved | { <no> no | <yes> }"];
emergency [label="<in> emergency | { <no> no | <yes> }"];
loop [label="<in> loop | { <no> no | <yes> }"];
modmember [label="<in> member\nmoderated | { <no> no | <yes> }"];
administrivia [group="0",
label="<in> administrivia | { <no> no | <yes> }"];
maxsize [label="<in> max\ size | {<no> no | <yes>}"];
any [label="<in> any | {<no> | <yes>}"];
truth [label="<in> truth | <always>"];
......@@ -114,6 +120,7 @@ The default set of rules looks something like this:
DISCARD [shape=invhouse, color=black, style=solid];
MODERATION [color=wheat];
HOLD [color=wheat];
action [color=wheat];
}
{ PIPELINE [shape=box, style=filled, color=cyan]; }
......@@ -130,7 +137,8 @@ The default set of rules looks something like this:
modmember:no -> administrivia:in;
modmember:yes -> MODERATION;
administrivia:always -> maxsize:in;
administrivia:no -> maxsize:in;
administrivia:yes -> action;
maxsize:no -> any:in;
maxsize:yes -> MODERATION;
......@@ -145,7 +153,7 @@ The default set of rules looks something like this:
Configuration
=============
Uses `lazr.config`_, essentially an "ini"-style configuration format.
Mailman 3 uses `lazr.config`_, essentially an "ini"-style configuration format.
Each Runner's configuration object knows whether it should be started
when the Mailman daemon starts, and what queue the Runner manages.
......
......@@ -82,7 +82,7 @@ lists and archives, etc., are available at:
Requirements
============
Mailman 3.0 requires `Python 2.7`_ or newer.
Mailman 3.0 requires `Python 2.7`_.
.. _`GNU Mailman`: http://www.list.org
......
......@@ -2,34 +2,75 @@
Hooking up your mail server
===========================
Mailman needs to be hooked up to your mail server (a.k.a. *mail transport
agent* or *MTA*) both to accept incoming mail and to deliver outgoing mail.
Mailman itself never delivers messages to the end user; it lets its immediate
upstream mail server do that.
Mailman needs to communicate with your *MTA* (*mail transport agent*
or *mail server*, the software which handles sending mail across the
Internet), both to accept incoming mail and to deliver outgoing mail.
Mailman itself never delivers messages to the end user. It sends them
to its immediate upstream MTA, which delivers them. In the same way,
Mailman never receives mail directly. Mail from outside always comes
via the MTA.
Mailman accepts incoming messages from the MTA using the `Local Mail
Transfer Protocol`_ (LMTP_) interface. Mailman can use other incoming
transports, but LMTP is much more efficient than spawning a process
just to do the delivery. Most open source MTAs support LMTP for local
delivery. If yours doesn't, and you need to use a different
interface, please ask on the `mailing list or on IRC`_.
Mailman passes all outgoing messages to the MTA using the `Simple Mail
Transfer Protocol`_ (SMTP_).
Cooperation between Mailman and the MTA requires some configuration of
both. MTA configuration differs for each of the available MTAs, and
there is a section for each one. Instructions for Postfix are given
below. We would really appreciate contributions of configurations for
Exim and Sendmail, and welcome information about other popular open
source mail servers.
Configuring Mailman to communicate with the MTA is straightforward,
and basically the same for all MTAs. In your ``mailman.cfg`` file,
add (or edit) a section like the following::
The preferred way to allow Mailman to accept incoming messages from your mail
server is to use the `Local Mail Transfer Protocol`_ (LMTP_) interface. Most
open source mail server support LMTP for local delivery, and this is much more
efficient than spawning a process just to do the delivery.
Your mail server should also accept `Simple Mail Transfer Protocol`_ (SMTP_)
connections from Mailman, for all outgoing messages.
The specific instructions for hooking your mail server up to Mailman differs
depending on which mail server you're using. The following are instructions
for the popular open source mail servers.
[mta]
incoming: mailman.mta.postfix.LMTP
outgoing: mailman.mta.deliver.deliver
lmtp_host: 127.0.0.1
lmtp_port: 8024
smtp_host: localhost
smtp_port: 25
Note that Mailman provides lots of configuration variables that you can use to
tweak performance for your operating environment. See the
This configuration is for a system where Mailman and the MTA are on
the same host.
The ``incoming`` and ``outgoing`` parameters identify the Python
objects used to communicate with the MTA. The ``deliver`` module used
in ``outgoing`` is pretty standard across all MTAs. The ``postfix``
module in ``incoming`` is specific to Postfix. See the section for
your MTA below for details on these parameters.
``lmtp_host`` and ``lmtp_port`` are parameters which are used by
Mailman, but also will be passed to the MTA to identify the Mailman
host. The "same host" case is special; some MTAs (including Postfix)
do not recognize "localhost", and need the numerical IP address. If
they are on different hosts, ``lmtp_host`` should be set to the domain
name or IP address of the Mailman host. ``lmtp_port`` is fairly
arbitrary (there is no standard port for LMTP). Use any port
convenient for your site. "8024" is as good as any, unless another
service is using it.
``smtp_host`` and ``smtp_port`` are parameters used to identify the
MTA to Mailman. If the MTA and Mailman are on separate hosts,
``smtp_host`` should be set to the domain name or IP address of the
MTA host. ``smtp_port`` will almost always be 25, which is the
standard port for SMTP. (Some special site configurations set it to a
different port. If you need this, you probably already know that,
know why, and what to do, too!)
Mailman also provides many other configuration variables that you can
use to tweak performance for your operating environment. See the
``src/mailman/config/schema.cfg`` file for details.
Exim
====
Contributions are welcome!
Postfix
=======
......@@ -131,12 +172,19 @@ the Postfix documentation at:
.. _`mydestination`: http://www.postfix.org/postconf.5.html#mydestination
Exim
====
Contributions are welcome!
Sendmail
========
Contributions are welcome!
.. _`mailing list or on IRC`: START.html#contact-us
.. _`Local Mail Transfer Protocol`:
http://en.wikipedia.org/wiki/Local_Mail_Transfer_Protocol
.. _LMTP: http://www.faqs.org/rfcs/rfc2033.html
......
......@@ -12,12 +12,27 @@ Here is a history of user visible changes to Mailman.
===============================
(2013-XX-XX)
Development
-----------
* Mailman 3 no longer uses ``zc.buildout`` and tests are now run by the
``nose2`` test runner. See ``src/mailman/docs/START.rst`` for details on
how to build Mailman and run the test suite.
* Use the ``enum34`` package instead of ``flufl.enum``.
REST
----
* Add ``reply_to_address`` and ``first_strip_reply_to`` as writable
attributes of a mailing list's configuration. (LP: #1157881)
* Support pagination of some large collections (lists, users, members).
Given by Florian Fuchs. (LP: #1156529)
* Expose ``hide_address`` to the ``.../preferences`` REST API. Contributed
by Sneha Priscilla.
Commands
--------
* `mailman conf` now has a `-t/--sort` flag which sorts the output by section
and then key. Contributed by Karl-Aksel Puulmann and David Soto
(LP: 1162492)
Configuration
-------------
......
......@@ -25,58 +25,64 @@ search, and retrieval of archived messages to a separate application (a simple
implementation is provided). The web interface (known as `Postorius`_) and
archiver (known as `Hyperkitty`_) are in separate development.
Contributions are welcome. Please submit bug reports on the Mailman bug
tracker at https://bugs.launchpad.net/mailman though you will currently need
to have a login on Launchpad to do so. You can also send email to the
[email protected] mailing list.
Contact Us
==========
Contributions of code, problem reports, and feature requests are welcome.
Please submit bug reports on the Mailman bug tracker at
https://bugs.launchpad.net/mailman (you need to have a login on Launchpad to
do so). You can also send email to the [email protected] mailing
list, or ask on IRC channel ``#mailman`` on Freenode.
Requirements
============
Python 2.7 is required. It can either be the default 'python' on your
``$PATH`` or it can be accessible via the ``python2.7`` binary.
If your operating system does not include Python, see http://www.python.org
downloading and installing it from source. Python 3 is not yet supported.
In this documentation, a bare ``python`` refers to the Python executable used
to invoke ``bootstrap.py``.
``$PATH`` or it can be accessible via the ``python2.7`` binary. If
your operating system does not include Python, see http://www.python.org
for information about downloading installers (where available) and
installing it from source (when necessary or preferred). Python 3 is
not yet supported.
Mailman 3 is now based on the `zc.buildout`_ infrastructure, which greatly
simplifies testing Mailman. Buildout is not required for installation.
During the beta program, you may need some additional dependencies, such as a
C compiler and the Python development headers and libraries. You will need an
internet connection.
You may need some additional dependencies, which are either available from
your OS vendor, or can be downloaded automatically from the `Python
Cheeseshop`_.
Building Mailman 3
==================
We provide several recipes for building Mailman. All should generally work,
but some may provide a better experience for developing Mailman versus
deploying Mailman.
To build Mailman for development purposes, you will create a virtual
environment. You need to have the `virtualenv`_ program installed.
Building for development
------------------------
The best way to build Mailman for development is to use the `zc.buildout`_
tools. This will download all Mailman dependencies from the `Python
Cheeseshop`_. The dependencies will get installed locally, but isolated from
your system Python. Here are the commands to build Mailman for development::
First, create a virtual environment. By default ``virtualenv`` uses the
``python`` executable it finds first on your ``$PATH``. Make sure this is
Python 2.7. The directory you install the virtualenv into is up to you, but
for purposes of this document, we'll install it into ``/tmp/py27``::
% virtualenv --system-site-packages /tmp/py27
% python bootstrap.py
% bin/buildout
Now, activate the virtual environment and set it up for development::
% source /tmp/py27/bin/activate
% python setup.py develop
Sit back and have some Kombucha while you wait for everything to download and
install.
Now you can run the test suite via::
% bin/test -vv
% nose2 -v
You should see no failures. You can also run a subset of the full test suite
by filter tests on the module or test name using the ``-P`` option::
You should see no failures.
% nose2 -v -P user
Build the online docs by running::
......@@ -91,21 +97,6 @@ doctests by looking in all the 'doc' directories under the 'mailman' package.
Doctests are documentation first, so they should give you a pretty good idea
how various components of Mailman 3 work.
Building for deployment using virtualenv
----------------------------------------
`virtualenv`_ is a way to create isolated Python environments. You can use
virtualenv as a way to deploy Mailman without installing it into your system
Python. There are lots of ways to use virtualenv, but as described here, it
will be default use any dependencies which are already installed in your
system, downloading from the Cheeseshop only those which are missing. Here
are the steps to install Mailman using virtualenv::
$ virtualenv --system-site-packages /path/to/your/installation
$ source /path/to/your/installation/bin/activate
$ python setup.py install
Once everything is downloaded and installed, you can initialize Mailman and
get a display of the basic configuration settings by running::
......@@ -149,8 +140,9 @@ Try ``bin/mailman --help`` for more details. You can use the commands
``bin/mailman start`` to start the runner subprocess daemons, and of course
``bin/mailman stop`` to stop them.
Postorius is being developed as a separate, Django-based project. For now,
all configuration happens via the command line and REST API.
Postorius, a web UI for administration and subscriber settings, is being
developed as a separate, Django-based project. For now, the most flexible
means of configuration is via the command line and REST API.
Mailman Web UI
......@@ -167,16 +159,16 @@ Postorius was prototyped at the `Pycon 2012 sprint`_, so it is "very alpha" as
of Mailman 3 beta 1, and comes in several components. In particular, it
requires a `Django`_ installation, and Bazaar checkouts of the `REST client
module`_ and `Postorius`_ itself. Building it is fairly straightforward,
however, given Florian Fuchs' `Five Minute Guide` from his `blog post`_ on the
based on Florian Fuchs' `Five Minute Guide` from his `blog post`_ on the
Mailman wiki. (Check the `blog post`_ for the most recent version!)
The Archiver
------------
In Mailman 3, the archivers are decoupled from the core engine. It is useful
to provide a simple, standard interface for third-party archiving tools and
services. For this reason, Mailman 3 defines a formal interface to insert
In Mailman 3, the archivers are decoupled from the core engine. Instead,
Mailman 3 provides a simple, standard interface for third-party archiving tools
and services. For this reason, Mailman 3 defines a formal interface to insert
messages into any of a number of configured archivers, using whatever protocol
is appropriate for that archiver. Summary, search, and retrieval of archived
posts are handled by a separate application.
......@@ -190,7 +182,6 @@ email application that speaks LMTP or SMTP will be able to use Hyperkitty.
A `five minute guide to Hyperkitty`_ is based on Toshio Kuratomi's README.
.. _`zc.buildout`: http://pypi.python.org/pypi/zc.buildout
.. _`Postorius`: https://launchpad.net/postorius
.. _`Hyperkitty`: https://launchpad.net/hyperkitty
.. _`Django`: http://djangoproject.org/
......
......@@ -24,7 +24,7 @@ __all__ = [
]
from flufl.enum import Enum
from enum import Enum
......
......@@ -27,7 +27,7 @@ __all__ = [
]
from flufl.enum import Enum
from enum import Enum
from zope.interface import Interface, Attribute
......
......@@ -30,7 +30,7 @@ __all__ = [
from datetime import timedelta
from flufl.enum import Enum
from enum import Enum
from zope.interface import Interface, Attribute
ALWAYS_REPLY = timedelta()
......
......@@ -28,7 +28,7 @@ __all__ = [
]
from flufl.enum import Enum
from enum import Enum
from zope.interface import Attribute, Interface
......
......@@ -35,7 +35,7 @@ __all__ = [
]