diff options
| author | bwarsaw | 2007-05-28 20:21:41 +0000 |
|---|---|---|
| committer | bwarsaw | 2007-05-28 20:21:41 +0000 |
| commit | b18f632faa6de17badabb3c6c7ba61752ac84c37 (patch) | |
| tree | 8b444330b288c5dfc9b25be639d429abfaeb3d3d /Mailman/docs | |
| parent | 5ff792b13599920527b48f92f8bad880668f8f26 (diff) | |
| download | mailman-b18f632faa6de17badabb3c6c7ba61752ac84c37.tar.gz mailman-b18f632faa6de17badabb3c6c7ba61752ac84c37.tar.zst mailman-b18f632faa6de17badabb3c6c7ba61752ac84c37.zip | |
Merge exp-elixir-branch to trunk. There is enough working to make me feel
confident the Elixir branch is ready to become mainline. Also, fewer branches
makes for an easier migration to a dvcs.
Don't expect much of the old test suite to work, or even for much of the old
functionality to work. The changes here are disruptive enough to break higher
level parts of Mailman. But that's okay because I am slowly building up a new
and improved test suite, which will lead to a functional system again.
For now, only the doctests in Mailman/docs (and their related test harnesses)
will pass, but they all do pass. Note that Mailman/docs serve as system
documentation first and unit tests second. You should be able to read the
doctest files to understand the underlying data model.
Other changes included in this merge:
- Added the Mailman.ext extension package.
- zope.interfaces uses to describe major components
- SQLAlchemy/Elixir used as the database model
- Top level doinstall target renamed to justinstall
- 3rd-party packages are now installed in pythonlib/lib/python to be more
compliant with distutils standards. This allows us to use just --home
instead of all the --install-* options.
- No longer need to include the email package or pysqlite, as Python 2.5 is
required (and comes with both packages).
- munepy package is included, for Python enums
- IRosterSets are added as a way to manage a collection of IRosters. Roster
sets are named so that we can maintain the indirection between mailing lists
and rosters, where the two are maintained in different storages.
- IMailingListRosters: remove_*_roster() -> delete_*_roster()
- Remove IMember interface.
- Utils.list_names() -> config.list_manager.names
- fqdn_listname() takes an optional hostname argument.
- Added a bunch of new exceptions used throughout the new interfaces.
- Make LockFile a context manager for use with the 'with' statement.
Diffstat (limited to 'Mailman/docs')
| -rw-r--r-- | Mailman/docs/Makefile.in | 82 | ||||
| -rw-r--r-- | Mailman/docs/__init__.py | 0 | ||||
| -rw-r--r-- | Mailman/docs/addresses.txt | 144 | ||||
| -rw-r--r-- | Mailman/docs/mlist-addresses.txt | 85 | ||||
| -rw-r--r-- | Mailman/docs/mlist-rosters.txt | 118 | ||||
| -rw-r--r-- | Mailman/docs/use-listmanager.txt | 124 | ||||
| -rw-r--r-- | Mailman/docs/use-usermanager.txt | 100 | ||||
| -rw-r--r-- | Mailman/docs/users.txt | 180 |
8 files changed, 833 insertions, 0 deletions
diff --git a/Mailman/docs/Makefile.in b/Mailman/docs/Makefile.in new file mode 100644 index 000000000..0662d8a3e --- /dev/null +++ b/Mailman/docs/Makefile.in @@ -0,0 +1,82 @@ +# Copyright (C) 1998-2007 by the Free Software Foundation, Inc. +# +# This program 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 2 +# of the License, or (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +# USA. + +# NOTE: Makefile.in is converted into Makefile by the configure script +# in the parent directory. Once configure has run, you can recreate +# the Makefile by running just config.status. + +# Variables set by configure + +VPATH= @srcdir@ +srcdir= @srcdir@ +bindir= @bindir@ +prefix= @prefix@ +exec_prefix= @exec_prefix@ +DESTDIR= + +CC= @CC@ +CHMOD= @CHMOD@ +INSTALL= @INSTALL@ + +DEFS= @DEFS@ + +# Customizable but not set by configure + +OPT= @OPT@ +CFLAGS= $(OPT) $(DEFS) +PACKAGEDIR= $(prefix)/Mailman/docs +SHELL= /bin/sh + +OTHERFILES= *.txt +MODULES= *.py + +# Modes for directories and executables created by the install +# process. Default to group-writable directories but +# user-only-writable for executables. +DIRMODE= 775 +EXEMODE= 755 +FILEMODE= 644 +INSTALL_PROGRAM=$(INSTALL) -m $(EXEMODE) + +# Directories make should decend into +SUBDIRS= + +# Rules + +all: + +install: + for f in $(MODULES) $(OTHERFILES); \ + do \ + $(INSTALL) -m $(FILEMODE) $(srcdir)/$$f $(DESTDIR)$(PACKAGEDIR); \ + done + for d in $(SUBDIRS); \ + do \ + (cd $$d; $(MAKE) DESTDIR=$(DESTDIR) install); \ + done + +finish: + +clean: + +distclean: + -rm *.pyc + -rm Makefile + @for d in $(SUBDIRS); \ + do \ + (cd $$d; $(MAKE) distclean); \ + done diff --git a/Mailman/docs/__init__.py b/Mailman/docs/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/Mailman/docs/__init__.py diff --git a/Mailman/docs/addresses.txt b/Mailman/docs/addresses.txt new file mode 100644 index 000000000..a8cf9f655 --- /dev/null +++ b/Mailman/docs/addresses.txt @@ -0,0 +1,144 @@ +Email addresses and rosters +=========================== + +Addresses represent email address, and nothing more. Some addresses are tied +to users that Mailman knows about. For example, a list member is a user that +the system knows about, but a non-member posting from a brand new email +address is a counter-example. + + +Creating a roster +----------------- + +Email address objects are tied to rosters, and rosters are tied to the user +manager. To get things started, access the global user manager and create a +new roster. + + >>> from Mailman.database import flush + >>> from Mailman.configuration import config + >>> mgr = config.user_manager + >>> roster_1 = mgr.create_roster('roster-1') + >>> sorted(roster_1.addresses) + [] + + +Creating addresses +------------------ + +Creating a simple email address object is straight forward. + + >>> addr_1 = roster_1.create('aperson@example.com') + >>> flush() + >>> addr_1.address + 'aperson@example.com' + >>> addr_1.real_name is None + True + +You can also create an email address object with a real name. + + >>> addr_2 = roster_1.create('bperson@example.com', 'Barney Person') + >>> addr_2.address + 'bperson@example.com' + >>> addr_2.real_name + 'Barney Person' + +You can also iterate through all the addresses on a roster. + + >>> sorted(addr.address for addr in roster_1.addresses) + ['aperson@example.com', 'bperson@example.com'] + +You can create another roster and add a bunch of existing addresses to the +second roster. + + >>> roster_2 = mgr.create_roster('roster-2') + >>> flush() + >>> sorted(roster_2.addresses) + [] + >>> for address in roster_1.addresses: + ... roster_2.addresses.append(address) + >>> roster_2.create('cperson@example.com', 'Charlie Person') + <Address: Charlie Person <cperson@example.com> [not verified]> + >>> sorted(addr.address for addr in roster_2.addresses) + ['aperson@example.com', 'bperson@example.com', 'cperson@example.com'] + +The first roster hasn't been affected. + + >>> sorted(addr.address for addr in roster_1.addresses) + ['aperson@example.com', 'bperson@example.com'] + + +Removing addresses +------------------ + +You can remove an address from a roster just by deleting it. + + >>> for addr in roster_1.addresses: + ... if addr.address == 'aperson@example.com': + ... break + >>> addr.address + 'aperson@example.com' + >>> roster_1.addresses.remove(addr) + >>> sorted(addr.address for addr in roster_1.addresses) + ['bperson@example.com'] + +Again, this doesn't affect the other rosters. + + >>> sorted(addr.address for addr in roster_2.addresses) + ['aperson@example.com', 'bperson@example.com', 'cperson@example.com'] + + +Registration and validation +--------------------------- + +Addresses have two dates, the date the address was registered on and the date +the address was validated on. Neither date isset by default. + + >>> addr = roster_1.create('dperson@example.com', 'David Person') + >>> addr.registered_on is None + True + >>> addr.validated_on is None + True + +The registered date takes a Python datetime object. + + >>> from datetime import datetime + >>> addr.registered_on = datetime(2007, 5, 8, 22, 54, 1) + >>> print addr.registered_on + 2007-05-08 22:54:01 + >>> addr.validated_on is None + True + +And of course, you can also set the validation date. + + >>> addr.validated_on = datetime(2007, 5, 13, 22, 54, 1) + >>> print addr.registered_on + 2007-05-08 22:54:01 + >>> print addr.validated_on + 2007-05-13 22:54:01 + + +The null roster +--------------- + +All address objects that have been created are members of the null roster. + + >>> all = mgr.get_roster('') + >>> sorted(addr.address for addr in all.addresses) + ['aperson@example.com', 'bperson@example.com', + 'cperson@example.com', 'dperson@example.com'] + +And conversely, all addresses should have the null roster on their list of +rosters. + + >>> for addr in all.addresses: + ... assert all in addr.rosters, 'Address is missing null roster' + + +Clean up +-------- + + >>> for roster in mgr.rosters: + ... mgr.delete_roster(roster) + >>> flush() + >>> sorted(roster.name for roster in mgr.rosters) + [] diff --git a/Mailman/docs/mlist-addresses.txt b/Mailman/docs/mlist-addresses.txt new file mode 100644 index 000000000..257cf95c7 --- /dev/null +++ b/Mailman/docs/mlist-addresses.txt @@ -0,0 +1,85 @@ +Mailing list addresses +====================== + +Every mailing list has a number of addresses which are publicly available. +These are defined in the IMailingListAddresses interface. + + >>> from Mailman.configuration import config + >>> from Mailman.interfaces import IMailingListAddresses + >>> mlist = config.list_manager.create('_xtest@example.com') + >>> IMailingListAddresses.providedBy(mlist) + True + +The posting address is where people send messages to be posted to the mailing +list. This is exactly the same as the fully qualified list name. + + >>> mlist.fqdn_listname + '_xtest@example.com' + >>> mlist.posting_address + '_xtest@example.com' + +Messages to the mailing list's 'no reply' address always get discarded without +prejudice. + + >>> mlist.noreply_address + 'noreply@example.com' + +The mailing list's owner address reaches the human moderators. + + >>> mlist.owner_address + '_xtest-owner@example.com' + +The request address goes to the list's email command robot. + + >>> mlist.request_address + '_xtest-request@example.com' + +The bounces address accepts and processes all potential bounces. + + >>> mlist.bounces_address + '_xtest-bounces@example.com' + +The join (a.k.a. subscribe) address is where someone can email to get added to +the mailing list. The subscribe alias is a synonym for join, but it's +deprecated. + + >>> mlist.join_address + '_xtest-join@example.com' + >>> mlist.subscribe_address + '_xtest-subscribe@example.com' + +The leave (a.k.a. unsubscribe) address is where someone can email to get added +to the mailing list. The unsubscribe alias is a synonym for leave, but it's +deprecated. + + >>> mlist.leave_address + '_xtest-leave@example.com' + >>> mlist.unsubscribe_address + '_xtest-unsubscribe@example.com' + + +Email confirmations +------------------- + +Email confirmation messages are sent when actions such as subscriptions need +to be confirmed. It requires that a cookie be provided, which will be +included in the local part of the email address. The exact format of this is +dependent on the VERP_CONFIRM_FORMAT configuration variable. + + >>> mlist.confirm_address('cookie') + '_xtest-confirm+cookie@example.com' + >>> mlist.confirm_address('wookie') + '_xtest-confirm+wookie@example.com' + + >>> old_format = config.VERP_CONFIRM_FORMAT + >>> config.VERP_CONFIRM_FORMAT = '$address---$cookie' + >>> mlist.confirm_address('cookie') + '_xtest-confirm---cookie@example.com' + >>> config.VERP_CONFIRM_FORMAT = old_format + + +Clean up +-------- + + >>> for mlist in config.list_manager.mailing_lists: + ... config.list_manager.delete(mlist) diff --git a/Mailman/docs/mlist-rosters.txt b/Mailman/docs/mlist-rosters.txt new file mode 100644 index 000000000..490a07e0c --- /dev/null +++ b/Mailman/docs/mlist-rosters.txt @@ -0,0 +1,118 @@ +Mailing list rosters +==================== + +Mailing lists use rosters to manage and organize users for various purposes. +In order to allow for separate storage of mailing list data and user data, the +connection between mailing list objects and rosters is indirect. Mailing +lists manage roster names, and these roster names are used to find the rosters +that contain the actual users. + + +Privileged rosters +------------------ + +Mailing lists have two types of privileged users, owners and moderators. +Owners get to change the configuration of mailing lists and moderators get to +approve or deny held messages and subscription requests. + +When a mailing list is created, it automatically contains a roster for the +list owners and a roster for the list moderators. + + >>> from Mailman.database import flush + >>> from Mailman.configuration import config + >>> mlist = config.list_manager.create('_xtest@example.com') + >>> flush() + >>> sorted(roster.name for roster in mlist.owner_rosters) + ['_xtest@example.com owners'] + >>> sorted(roster.name for roster in mlist.moderator_rosters) + ['_xtest@example.com moderators'] + +These rosters are initially empty. + + >>> owner_roster = list(mlist.owner_rosters)[0] + >>> sorted(address for address in owner_roster.addresses) + [] + >>> moderator_roster = list(mlist.moderator_rosters)[0] + >>> sorted(address for address in moderator_roster.addresses) + [] + +You can create new rosters and add them to the list of owner or moderator +rosters. + + >>> roster_1 = config.user_manager.create_roster('roster-1') + >>> roster_2 = config.user_manager.create_roster('roster-2') + >>> roster_3 = config.user_manager.create_roster('roster-3') + >>> flush() + +Make roster-1 an owner roster, roster-2 a moderator roster, and roster-3 both +an owner and a moderator roster. + + >>> mlist.add_owner_roster(roster_1) + >>> mlist.add_moderator_roster(roster_2) + >>> mlist.add_owner_roster(roster_3) + >>> mlist.add_moderator_roster(roster_3) + >>> flush() + + >>> sorted(roster.name for roster in mlist.owner_rosters) + ['_xtest@example.com owners', 'roster-1', 'roster-3'] + >>> sorted(roster.name for roster in mlist.moderator_rosters) + ['_xtest@example.com moderators', 'roster-2', 'roster-3'] + + +Privileged users +---------------- + +Rosters are the lower level way of managing owners and moderators, but usually +you just want to know which users have owner and moderator privileges. You +can get the list of such users by using different attributes. + +Because the rosters are all empty to start with, we can create a bunch of +users that will end up being our owners and moderators. + + >>> aperson = config.user_manager.create_user() + >>> bperson = config.user_manager.create_user() + >>> cperson = config.user_manager.create_user() + +These users need addresses, because rosters manage addresses. + + >>> address_1 = roster_1.create('aperson@example.com', 'Anne Person') + >>> aperson.link(address_1) + >>> address_2 = roster_2.create('bperson@example.com', 'Ben Person') + >>> bperson.link(address_2) + >>> address_3 = roster_1.create('cperson@example.com', 'Claire Person') + >>> cperson.link(address_3) + >>> roster_3.addresses.append(address_3) + >>> flush() + +Now that everything is set up, we can iterate through the various collections +of privileged users. Here are the owners of the list. + + >>> from Mailman.interfaces import IUser + >>> addresses = [] + >>> for user in mlist.owners: + ... assert IUser.providedBy(user), 'Non-IUser owner found' + ... for address in user.addresses: + ... addresses.append(address.address) + >>> sorted(addresses) + ['aperson@example.com', 'cperson@example.com'] + +Here are the moderators of the list. + + >>> addresses = [] + >>> for user in mlist.moderators: + ... assert IUser.providedBy(user), 'Non-IUser moderator found' + ... for address in user.addresses: + ... addresses.append(address.address) + >>> sorted(addresses) + ['bperson@example.com', 'cperson@example.com'] + +The administrators of a mailing list are the union of the owners and +moderators. + + >>> addresses = [] + >>> for user in mlist.administrators: + ... assert IUser.providedBy(user), 'Non-IUser administrator found' + ... for address in user.addresses: + ... addresses.append(address.address) + >>> sorted(addresses) + ['aperson@example.com', 'bperson@example.com', 'cperson@example.com'] diff --git a/Mailman/docs/use-listmanager.txt b/Mailman/docs/use-listmanager.txt new file mode 100644 index 000000000..9e237f02f --- /dev/null +++ b/Mailman/docs/use-listmanager.txt @@ -0,0 +1,124 @@ +Using the IListManager interface +================================ + +The IListManager is how you create, delete, and retrieve mailing list +objects. The Mailman system instantiates an IListManager for you based on the +configuration variable MANAGERS_INIT_FUNCTION. The instance is accessible +on the global config object. + + >>> from Mailman.database import flush + >>> from Mailman.configuration import config + >>> from Mailman.interfaces import IListManager + >>> IListManager.providedBy(config.list_manager) + True + >>> mgr = config.list_manager + + +Creating a mailing list +----------------------- + +Creating the list returns the newly created IMailList object. + + >>> from Mailman.interfaces import IMailingList + >>> mlist = mgr.create('_xtest@example.com') + >>> flush() + >>> IMailingList.providedBy(mlist) + True + +This object has an identity. + + >>> from Mailman.interfaces import IMailingListIdentity + >>> IMailingListIdentity.providedBy(mlist) + True + +All lists with identities have a short name, a host name, and a fully +qualified listname. This latter is what uniquely distinguishes the mailing +list to the system. + + >>> mlist.list_name + '_xtest' + >>> mlist.host_name + 'example.com' + >>> mlist.fqdn_listname + '_xtest@example.com' + +If you try to create a mailing list with the same name as an existing list, +you will get an exception. + + >>> mlist_dup = mgr.create('_xtest@example.com') + Traceback (most recent call last): + ... + MMListAlreadyExistsError: _xtest@example.com + + +Deleting a mailing list +----------------------- + +Deleting an existing mailing list also deletes its rosters and roster sets. + + >>> sorted(r.name for r in config.user_manager.rosters) + ['', '_xtest@example.com moderators', '_xtest@example.com owners'] + + >>> mgr.delete(mlist) + >>> flush() + >>> sorted(mgr.names) + [] + >>> sorted(r.name for r in config.user_manager.rosters) + [''] + +Attempting to access attributes of the deleted mailing list raises an +exception: + + >>> mlist.fqdn_listname + Traceback (most recent call last): + ... + AttributeError: fqdn_listname + +After deleting the list, you can create it again. + + >>> mlist = mgr.create('_xtest@example.com') + >>> flush() + >>> mlist.fqdn_listname + '_xtest@example.com' + + +Retrieving a mailing list +------------------------- + +When a mailing list exists, you can ask the list manager for it and you will +always get the same object back. + + >>> mlist_2 = mgr.get('_xtest@example.com') + >>> mlist_2 is mlist + True + +Don't try to get a list that doesn't exist yet though, or you will get an +exception. + + >>> mgr.get('_xtest_2@example.com') + Traceback (most recent call last): + ... + MMUnknownListError: _xtest_2@example.com + + +Iterating over all mailing lists +-------------------------------- + +Once you've created a bunch of mailing lists, you can use the list manager to +iterate over either the list objects, or the list names. + + >>> mlist_3 = mgr.create('_xtest_3@example.com') + >>> mlist_4 = mgr.create('_xtest_4@example.com') + >>> flush() + >>> sorted(mgr.names) + ['_xtest@example.com', '_xtest_3@example.com', '_xtest_4@example.com'] + >>> sorted(m.fqdn_listname for m in mgr.mailing_lists) + ['_xtest@example.com', '_xtest_3@example.com', '_xtest_4@example.com'] + + +Cleaning up +----------- + + >>> for mlist in mgr.mailing_lists: + ... mgr.delete(mlist) + >>> flush() diff --git a/Mailman/docs/use-usermanager.txt b/Mailman/docs/use-usermanager.txt new file mode 100644 index 000000000..f79bff8c6 --- /dev/null +++ b/Mailman/docs/use-usermanager.txt @@ -0,0 +1,100 @@ +The user manager and rosters +============================ + +The IUserManager is how you create, delete, and roster objects. Rosters +manage collections of users. The Mailman system instantiates an IUserManager +for you based on the configuration variable MANAGERS_INIT_FUNCTION. The +instance is accessible on the global config object. + + >>> from Mailman.configuration import config + >>> from Mailman.interfaces import IUserManager + >>> mgr = config.user_manager + >>> IUserManager.providedBy(mgr) + True + + +The default roster +------------------ + +The user manager always contains at least one roster, the 'null' roster or +'all inclusive roster'. + + >>> sorted(roster.name for roster in mgr.rosters) + [''] + + +Adding rosters +-------------- + +You create a roster to hold users. The only thing a roster needs is a name, +basically just an identifying string. + + >>> from Mailman.database import flush + >>> from Mailman.interfaces import IRoster + >>> roster = mgr.create_roster('roster-1') + >>> IRoster.providedBy(roster) + True + >>> roster.name + 'roster-1' + >>> flush() + +If you try to create a roster with the same name as an existing roster, you +will get an exception. + + >>> roster_dup = mgr.create_roster('roster-1') + Traceback (most recent call last): + ... + RosterExistsError: roster-1 + + +Deleting a roster +----------------- + +Delete the roster, and you can then create it again. + + >>> mgr.delete_roster(roster) + >>> flush() + >>> roster = mgr.create_roster('roster-1') + >>> flush() + >>> roster.name + 'roster-1' + + +Retrieving a roster +------------------- + +When a roster exists, you can ask the user manager for it and you will always +get the same object back. + + >>> roster_2 = mgr.get_roster('roster-1') + >>> roster_2.name + 'roster-1' + >>> roster is roster_2 + True + +Trying to get a roster that does not yet exist returns None. + + >>> print mgr.get_roster('no roster') + None + + +Iterating over all the rosters +------------------------------ + +Once you've created a bunch of rosters, you can use the user manager to +iterate over all the rosters. + + >>> roster_2 = mgr.create_roster('roster-2') + >>> roster_3 = mgr.create_roster('roster-3') + >>> roster_4 = mgr.create_roster('roster-4') + >>> flush() + >>> sorted(roster.name for roster in mgr.rosters) + ['', 'roster-1', 'roster-2', 'roster-3', 'roster-4'] + + +Cleaning up +----------- + + >>> for roster in mgr.rosters: + ... mgr.delete_roster(roster) + >>> flush() diff --git a/Mailman/docs/users.txt b/Mailman/docs/users.txt new file mode 100644 index 000000000..caad6b216 --- /dev/null +++ b/Mailman/docs/users.txt @@ -0,0 +1,180 @@ +Users +===== + +Users are entities that combine addresses, preferences, and a password +scheme. Password schemes can be anything from a traditional +challenge/response type password string to an OpenID url. + + +Create, deleting, and managing users +------------------------------------ + +Users are managed by the IUserManager. Users don't have any unique +identifying information, and no such id is needed to create them. + + >>> from Mailman.database import flush + >>> from Mailman.configuration import config + >>> mgr = config.user_manager + >>> user = mgr.create_user() + +Users have a real name, a password scheme, a default profile, and a set of +addresses that they control. All of these data are None or empty for a newly +created user. + + >>> user.real_name is None + True + >>> user.password is None + True + >>> user.addresses + [] + +You can iterate over all the users in a user manager. + + >>> another_user = mgr.create_user() + >>> flush() + >>> all_users = list(mgr.users) + >>> len(list(all_users)) + 2 + >>> user is not another_user + True + >>> user in all_users + True + >>> another_user in all_users + True + +You can also delete users from the user manager. + + >>> mgr.delete_user(user) + >>> mgr.delete_user(another_user) + >>> flush() + >>> len(list(mgr.users)) + 0 + + +Simple user information +----------------------- + +Users may have a real name and a password scheme. + + >>> user = mgr.create_user() + >>> user.password = 'my password' + >>> user.real_name = 'Zoe Person' + >>> flush() + >>> only_person = list(mgr.users)[0] + >>> only_person.password + 'my password' + >>> only_person.real_name + 'Zoe Person' + +The password and real name can be changed at any time. + + >>> user.real_name = 'Zoe X. Person' + >>> user.password = 'another password' + >>> only_person.real_name + 'Zoe X. Person' + >>> only_person.password + 'another password' + + +Users and addresses +------------------- + +One of the pieces of information that a user links to is a set of email +addresses, in the form of IAddress objects. A user can control many +addresses, but addresses may be control by only one user. + +Given a user and an address, you can link the two together. + + >>> roster = mgr.get_roster('') + >>> address = roster.create('aperson@example.com', 'Anne Person') + >>> user.link(address) + >>> flush() + >>> sorted(address.address for address in user.addresses) + ['aperson@example.com'] + +But don't try to link an address to more than one user. + + >>> another_user = mgr.create_user() + >>> another_user.link(address) + Traceback (most recent call last): + ... + AddressAlreadyLinkedError: Anne Person <aperson@example.com> + +You can also ask whether a given user controls a given address. + + >>> user.controls(address) + True + >>> not_my_address = roster.create('bperson@example.com', 'Ben Person') + >>> user.controls(not_my_address) + False + +Given a text email address, the user manager can find the user that controls +that address. + + >>> mgr.get_user('aperson@example.com') is user + True + >>> mgr.get_user('bperson@example.com') is None + True + +Addresses can also be unlinked from a user. + + >>> user.unlink(address) + >>> user.controls(address) + False + >>> mgr.get_user('aperson@example.com') is None + True + +But don't try to unlink the address from a user it's not linked to. + + >>> user.unlink(address) + Traceback (most recent call last): + ... + AddressNotLinkedError: Anne Person <aperson@example.com> + >>> another_user.unlink(address) + Traceback (most recent call last): + ... + AddressNotLinkedError: Anne Person <aperson@example.com> + >>> mgr.delete_user(another_user) + + +Users and profiles +------------------ + +Users always have a default profile. + + >>> from Mailman.interfaces import IProfile + >>> IProfile.providedBy(user.profile) + True + +A profile is a set of preferences such as whether the user wants to receive an +acknowledgment of all of their posts to a mailing list... + + >>> user.profile.acknowledge_posts + False + +...whether the user wants to hide their email addresses on web pages and in +postings to the list... + + >>> user.profile.hide_address + True + +...the language code for the user's preferred language... + + >>> user.profile.preferred_language + 'en' + +...whether the user wants to receive the list's copy of a message if they are +explicitly named in one of the recipient headers... + + >>> user.profile.receive_list_copy + True + +...whether the user wants to receive a copy of their own postings... + + >>> user.profile.receive_own_postings + True + +...and the preferred delivery method. + + >>> print user.profile.delivery_mode + DeliveryMode.regular |
