This is a short guide to help you get started with Postorius development.
Postorius is a Django application, so if you have developed with Django before, the file structure probably looks familiar. These are the basics:
__init__.py
auth/ # Custom authorization code (List owners and
# moderators)
context_processors.py # Some variables available in all templates
doc/ # Sphinx documentation
fieldset_forms.py # A custom form class to build html forms with
# fieldsets
forms.py # All kinds of classes to build and validate forms
management/ # Commands to use with Django's manage.py script
models.py # Code to connect to mailman.client and provide
# a Django-style model API for lists, mm-users and
# domains
static/ # Static files (CSS, JS, images)
templates/ # HTML Templates
tests/ # Postorius test files
urls.py # URL routes
utils.py # Some handy utilities
views/
views.py # View classes and functions for all views connected
# in urls.py
generic.py # Generic class-based views; Currently holds a
# class for views based on a single mailing list
The source code is hosted on Launchpad, which also means that we are using Bazaar for version control.
Changes are usually not made directly in the project’s trunk branch, but in feature-related personal branches, which get reviewed and then merged into the trunk.
The ideal workflow would be like this:
Launchpad has a nice tour which describes all this in detail.
When the work on Postorius was started, the standard way to write view code in Django was to write a single function for each different view. Since then Django has introduced so-called ‘class-based views’ which make it much easier to reuse view functionality. While using simple functions is not discuraged, it makes sense to check if using a class-based approach could make sense.
Check Postorius’ views/views.py and views/generic.py for examples!
Three of Django’s default User roles are relvant for Postorius:
Apart from these default roles, there are two others relevant in Postorius:
There are a number of decorators to protect views from unauthorized users.
Check out views/views.py or views/api.py for examples!
The last one (basic_auth_login) checks the request header for HTTP Basic Auth credentials and uses those to authenticate against Django’s session-based mechanism. It can be used in cases where a view is accessed from other clients than the web browser.
Please make sure to put it outside the other auth decorators.
Good:
@basic_auth_login
@list_owner_required
def my_view_func(request):
...
Won’t work, because list_owner_required will not recognize the user:
@list_owner_required
@basic_auth_login
def my_view_func(request):
...
Postorius uses mailman.client to connect to Mailman’s REST API. In order to directly use the client, cd to your project folder and execute python manage.py mmclient. This will open a python shell (IPython, if that’s available) and provide you with a client object connected to to your local Mailman API server (it uses the credentials from your settings.py).
A quick example:
$ python manage.py mmclient
>>> client
<Client (user:pwd) http://localhost:8001/3.0/>
>>> print client.system['mailman_version']
GNU Mailman 3.0.0b2+ (Here Again)
>>> mailman_dev = client.get_list('mailman-developers@python.org')
>>> print mailman_dev settings
{u'owner_address': u'mailman-developers@python.org',
u'default_nonmember_action': u'hold', ...}
For detailed information how to use mailman.client, check out its documentation.
Currently only some of the Postorius code is covered by a test. We should change that!
All test modules reside in the postorius/src/postorius/tests directory and this is where you should put your own tests, too. To make the django test runner find your tests, make sure to add them to the folder’s __init__.py:
from postorius.tests import test_utils
from postorius.tests.test_list_members import ListMembersViewTest
from postorius.tests.test_list_settings import ListSettingsViewTest
from postorius.tests.my_own_tests import MyOwnUnitTest
__test__ = {
"Test Utils": test_utils,
"List Members": ListMembersViewTest,
"List Settings": ListSettingsViewTest,
"My Own Test": MyOwnUnitTest,
}
To run the tests go to your project folder and run python manage.py test postorius from there.
Most of Postorius’ code involves some results from calls to the mailman.client library. mailman.client is itself covered by tests, so Postorius’ own tests don’t need to check if mailman.client returns correct results. Instead we can just mock them! This has the big advantage that you can run the test suite without having to worry about the state of the local Mailman database. It also makes the tests run faster, because we spare ourselves the HTTP calls to the local Mailman REST API.
This approach has the obvious downside that the Postorius tests will not recognize any changes to the Mailman API. So at some point there should be some separate integration tests to test the whole chain. But let’s not worry about that for now.
Postorius uses Michael Foord’s mock library for mocking. There are some shortcuts you can use to quickly create mock objects that behave a little bit like objects retreived from mailman.client, like:
These create_mock_* functions are very simple tools that return MagigMock objects with the properties passed to them in a dictionary. They also set some defaults for properties that you didn’t pass to its create_mock_* function. For instance, a mock list created with create_mock_list() will always have members, moderators and owners properties.
postorius.tests.utils.create_mock_domain creates a mock domain object:
>>> properties = dict(contact_address='postmaster@example.org',
... description='Example dot Org',
... mail_host='example.org',
... url_host='www.example.org')
>>> mock_domain = create_mock_domain(properties)
>>> print mock_domain
<MagicMock name='Domain' id='...'>
>>> print mock_domain.contact_address
postmaster@example.org
>>> print mock_domain.description
Example dot Org
>>> print mock_domain.mail_host
example.org
>>> print mock_domain.url_host
www.example.org
postorius.tests.utils.create_mock_list creates a mock list object:
>>> properties = dict(fqdn_listname='testlist@example.org',
... mail_host='example.org',
... list_name='testlist',
... display_name='Test List')
>>> mock_list = create_mock_list(properties)
>>> print mock_list
<MagicMock name='List' id='...'>
>>> print mock_list.fqdn_listname
testlist@example.org
>>> print mock_list.mail_host
example.org
>>> print mock_list.list_name
testlist
>>> print mock_list.display_name
Test List
postorius.tests.utils.create_mock_list creates a mock list object:
>>> properties = dict(fqdn_listname='testlist@example.org',
... address='les@example.org',)
>>> mock_member = create_mock_member(properties)
>>> print mock_member
<MagicMock name='Member' id='...'>
>>> print mock_member.fqdn_listname
testlist@example.org
>>> print mock_member.address
les@example.org