membership.rst 12.7 KB
Newer Older
1
================
2 3 4
List memberships
================

5 6 7 8
Users represent people in Mailman, members represent subscriptions.  Users
control email addresses, and rosters are collections of members.  A member
ties a subscribed email address to a role, such as `member`, `administrator`,
or `moderator`.  Even non-members are represented by a roster.
9 10 11

Roster sets are collections of rosters and a mailing list has a single roster
set that contains all its members, regardless of that member's role.
12 13 14 15 16 17

Mailing lists and roster sets have an indirect relationship, through the
roster set's name.  Roster also have names, but are related to roster sets
by a more direct containment relationship.  This is because it is possible to
store mailing list data in a different database than user data.

18 19
When we create a mailing list, it starts out with no members, owners,
moderators, administrators, or nonmembers.
20

21 22
    >>> from mailman.app.lifecycle import create_list
    >>> from mailman.testing.documentation import dump_list    
23
    >>> mlist = create_list('ant@example.com')
24 25 26 27 28 29 30 31 32 33
    >>> dump_list(mlist.members.members)
    *Empty*
    >>> dump_list(mlist.owners.members)
    *Empty*
    >>> dump_list(mlist.moderators.members)
    *Empty*
    >>> dump_list(mlist.administrators.members)
    *Empty*
    >>> dump_list(mlist.nonmembers.members)
    *Empty*
34

35 36

Administrators
37
==============
38 39

A mailing list's administrators are defined as union of the list's owners and
40 41 42
moderators.  We can add new owners or moderators to this list by assigning
roles to users.  First we have to create the user, because there are no users
in the user database yet.
43

44 45 46 47
    >>> from mailman.interfaces.usermanager import IUserManager
    >>> from zope.component import getUtility
    >>> user_manager = getUtility(IUserManager)
    >>> user_1 = user_manager.create_user('aperson@example.com', 'Anne Person')
48
    >>> print(user_1)
49
    <User "Anne Person" (...) at ...>
50 51 52 53

We can add Anne as an owner of the mailing list, by creating a member role for
her.

54
    >>> from mailman.interfaces.member import MemberRole
55
    >>> address_1 = list(user_1.addresses)[0]
56
    >>> mlist.subscribe(address_1, MemberRole.owner)
57
    <Member: Anne Person <aperson@example.com> on
58
             ant@example.com as MemberRole.owner>
59
    >>> dump_list(member.address for member in mlist.owners.members)
60
    Anne Person <aperson@example.com>
61 62 63 64

Adding Anne as a list owner also makes her an administrator, but does not make
her a moderator.  Nor does it make her a member of the list.

65 66 67 68 69 70
    >>> dump_list(member.address for member in mlist.administrators.members)
    Anne Person <aperson@example.com>
    >>> dump_list(member.address for member in mlist.moderators.members)
    *Empty*
    >>> dump_list(member.address for member in mlist.members.members)
    *Empty*
71

72
Bart becomes a moderator of the list.
73

74
    >>> user_2 = user_manager.create_user('bperson@example.com', 'Bart Person')
75
    >>> print(user_2)
76
    <User "Bart Person" (...) at ...>
77
    >>> address_2 = list(user_2.addresses)[0]
78
    >>> mlist.subscribe(address_2, MemberRole.moderator)
79
    <Member: Bart Person <bperson@example.com>
80
             on ant@example.com as MemberRole.moderator>
81
    >>> dump_list(member.address for member in mlist.moderators.members)
82
    Bart Person <bperson@example.com>
83

84
Now, both Anne and Bart are list administrators.
85 86 87 88 89
::

    >>> from operator import attrgetter
    >>> def dump_members(roster):
    ...     all_addresses = list(member.address for member in roster)
90
    ...     sorted_addresses = sorted(all_addresses, key=attrgetter('email'))
91
    ...     dump_list(sorted_addresses)
92

93
    >>> dump_members(mlist.administrators.members)
94 95
    Anne Person <aperson@example.com>
    Bart Person <bperson@example.com>
96 97 98


Members
99
=======
100

101 102
Similarly, list members are born of users being subscribed with the proper
role.
103

104
    >>> user_3 = user_manager.create_user(
105
    ...     'cperson@example.com', 'Cris Person')
106
    >>> address_3 = list(user_3.addresses)[0]
107
    >>> member = mlist.subscribe(address_3, MemberRole.member)
108
    >>> member
109
    <Member: Cris Person <cperson@example.com>
110
             on ant@example.com as MemberRole.member>
111

112 113 114 115 116
Cris's user record can also be retrieved from her member record.

    >>> member.user
    <User "Cris Person" (3) at ...>

117
Cris will be a regular delivery member but not a digest member.
118

119 120 121 122 123 124
    >>> dump_members(mlist.members.members)
    Cris Person <cperson@example.com>
    >>> dump_members(mlist.regular_members.members)
    Cris Person <cperson@example.com>
    >>> dump_members(mlist.digest_members.addresses)
    *Empty*
125 126 127

It's easy to make the list administrators members of the mailing list too.

128
    >>> members = []
129
    >>> for address in mlist.administrators.addresses:
130
    ...     member = mlist.subscribe(address, MemberRole.member)
131
    ...     members.append(member)
132
    >>> dump_list(members, key=attrgetter('address.email'))
133
    <Member: Anne Person <aperson@example.com> on
134
             ant@example.com as MemberRole.member>
135
    <Member: Bart Person <bperson@example.com> on
136
             ant@example.com as MemberRole.member>
137 138 139 140 141 142 143 144 145 146
    >>> dump_members(mlist.members.members)
    Anne Person <aperson@example.com>
    Bart Person <bperson@example.com>
    Cris Person <cperson@example.com>
    >>> dump_members(mlist.regular_members.members)
    Anne Person <aperson@example.com>
    Bart Person <bperson@example.com>
    Cris Person <cperson@example.com>
    >>> dump_members(mlist.digest_members.members)
    *Empty*
147 148 149 150 151 152 153 154


Nonmembers
==========

Nonmembers are used to represent people who have posted to the mailing list
but are not subscribed to the mailing list.  These may be legitimate users who
have found the mailing list and wish to interact without a direct
155
subscription, or they may be spammers who should never be allowed to contact
156 157 158 159
the mailing list.  Because all the same moderation rules can be applied to
nonmembers, we represent them as the same type of object but with a different
role.

160
    >>> user_6 = user_manager.create_user('fperson@example.com', 'Fred Person')
161
    >>> address_6 = list(user_6.addresses)[0]
162
    >>> member_6 = mlist.subscribe(address_6, MemberRole.nonmember)
163
    >>> member_6
164
    <Member: Fred Person <fperson@example.com> on ant@example.com
165
             as MemberRole.nonmember>
166 167
    >>> dump_members(mlist.nonmembers.members)
    Fred Person <fperson@example.com>
168

169
Nonmembers do not get delivery of any messages.
170

171 172 173 174 175 176 177 178 179 180
    >>> dump_members(mlist.members.members)
    Anne Person <aperson@example.com>
    Bart Person <bperson@example.com>
    Cris Person <cperson@example.com>
    >>> dump_members(mlist.regular_members.members)
    Anne Person <aperson@example.com>
    Bart Person <bperson@example.com>
    Cris Person <cperson@example.com>
    >>> dump_members(mlist.digest_members.members)
    *Empty*
181 182 183


Finding members
184
===============
185

186 187
You can find the ``IMember`` object that is a member of a roster for a given
text email address by using the ``IRoster.get_member()`` method.
188

Barry Warsaw's avatar
Barry Warsaw committed
189
    >>> mlist.owners.get_member('aperson@example.com')
190
    <Member: Anne Person <aperson@example.com> on
191
             ant@example.com as MemberRole.owner>
Barry Warsaw's avatar
Barry Warsaw committed
192
    >>> mlist.administrators.get_member('aperson@example.com')
193
    <Member: Anne Person <aperson@example.com> on
194
             ant@example.com as MemberRole.owner>
Barry Warsaw's avatar
Barry Warsaw committed
195
    >>> mlist.members.get_member('aperson@example.com')
196
    <Member: Anne Person <aperson@example.com> on
197
             ant@example.com as MemberRole.member>
198
    >>> mlist.nonmembers.get_member('fperson@example.com')
199
    <Member: Fred Person <fperson@example.com> on
200
             ant@example.com as MemberRole.nonmember>
201 202 203 204

However, if the address is not subscribed with the appropriate role, then None
is returned.

205
    >>> print(mlist.administrators.get_member('zperson@example.com'))
206
    None
207
    >>> print(mlist.moderators.get_member('aperson@example.com'))
208
    None
209
    >>> print(mlist.members.get_member('zperson@example.com'))
210
    None
211
    >>> print(mlist.nonmembers.get_member('aperson@example.com'))
212
    None
213 214 215


All subscribers
216
===============
217 218 219 220 221

There is also a roster containing all the subscribers of a mailing list,
regardless of their role.

    >>> def sortkey(member):
222
    ...     return (member.address.email, member.role.value)
223
    >>> for member in sorted(mlist.subscribers.members, key=sortkey):
224
    ...     print(member.address.email, member.role)
225 226 227 228 229 230
    aperson@example.com MemberRole.member
    aperson@example.com MemberRole.owner
    bperson@example.com MemberRole.member
    bperson@example.com MemberRole.moderator
    cperson@example.com MemberRole.member
    fperson@example.com MemberRole.nonmember
231 232


233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264
Subscriber type
===============

Members can be subscribed to a mailing list either via an explicit address, or
indirectly through a user's preferred address.  Sometimes you want to know
which one it is.

Herb subscribes to the mailing list via an explicit address.

    >>> herb = user_manager.create_address(
    ...     'hperson@example.com', 'Herb Person')
    >>> herb_member = mlist.subscribe(herb)

Iris subscribes to the mailing list via her preferred address.

    >>> iris = user_manager.make_user(
    ...     'iperson@example.com', 'Iris Person')
    >>> preferred = list(iris.addresses)[0]
    >>> from mailman.utilities.datetime import now
    >>> preferred.verified_on = now()
    >>> iris.preferred_address = preferred
    >>> iris_member = mlist.subscribe(iris)

When we need to know which way a member is subscribed, we can look at the this
attribute.

    >>> herb_member.subscriber
    <Address: Herb Person <hperson@example.com> [not verified] at ...>
    >>> iris_member.subscriber
    <User "Iris Person" (5) at ...>


265 266
Moderation actions
==================
267

268 269 270 271 272
All members of any role have a *moderation action* which specifies how
postings from that member are handled.  By default, owners and moderators are
automatically accepted for posting to the mailing list.

    >>> for member in sorted(mlist.administrators.members,
273
    ...                      key=attrgetter('address.email')):
274
    ...     print(member.address.email, member.role, member.moderation_action)
275
    aperson@example.com MemberRole.owner     Action.accept
276 277
    bperson@example.com MemberRole.moderator Action.accept

278 279 280
By default, members and nonmembers have their action set to None, meaning that
the mailing list's ``default_member_action`` or ``default_nonmember_action``
will be used.
281 282

    >>> for member in sorted(mlist.members.members,
283
    ...                      key=attrgetter('address.email')):
284
    ...     print(member.address.email, member.role, member.moderation_action)
285 286 287 288 289
    aperson@example.com MemberRole.member None
    bperson@example.com MemberRole.member None
    cperson@example.com MemberRole.member None
    hperson@example.com MemberRole.member None
    iperson@example.com MemberRole.member None
290

291
    >>> for member in mlist.nonmembers.members:
292
    ...     print(member.address.email, member.role, member.moderation_action)
293 294 295 296 297
    fperson@example.com MemberRole.nonmember None

The mailing list's default action for members is *deferred*, which specifies
that the posting should go through the normal moderation checks. Its default
action for nonmembers is to hold for moderator approval.
298 299 300 301 302 303 304 305


Changing subscriptions
======================

When a user is subscribed to a mailing list via a specific address they
control (as opposed to being subscribed with their preferred address), they
can change their delivery address by setting the appropriate parameter.  Note
306
though that the address they're changing to must be verified.
307 308 309 310 311

    >>> bee = create_list('bee@example.com')
    >>> gwen = user_manager.create_user('gwen@example.com')
    >>> gwen_address = list(gwen.addresses)[0]
    >>> gwen_member = bee.subscribe(gwen_address)
312
    >>> for m in bee.members.members:
313
    ...     print(m.member_id.int, m.mailing_list.list_id, m.address.email)
314
    9 bee.example.com gwen@example.com
315 316 317 318 319 320 321 322 323 324 325 326 327

Gwen gets a email address.

    >>> new_address = gwen.register('gperson@example.com')

Gwen verifies her email address, and updates her membership.

    >>> from mailman.utilities.datetime import now
    >>> new_address.verified_on = now()
    >>> gwen_member.address = new_address

Now her membership reflects the new address.

328
    >>> for m in bee.members.members:
329
    ...     print(m.member_id.int, m.mailing_list.list_id, m.address.email)
330
    9 bee.example.com gperson@example.com
331 332 333 334 335 336 337 338 339 340


Events
======

An event is triggered when a new member is subscribed to a mailing list.
::

    >>> from mailman.testing.helpers import event_subscribers
    >>> def handle_event(event):
341
    ...     print(event)
342 343 344 345 346 347 348 349 350 351 352 353

    >>> cat = create_list('cat@example.com')
    >>> herb = user_manager.create_address('herb@example.com')
    >>> with event_subscribers(handle_event):
    ...     member = cat.subscribe(herb)
    herb@example.com joined cat.example.com

An event is triggered when a member is unsubscribed from a mailing list.

    >>> with event_subscribers(handle_event):
    ...     member.unsubscribe()
    herb@example.com left cat.example.com