========= Addresses ========= The REST API can be used to manage addresses. There are no addresses yet. >>> dump_json('http://localhost:9001/3.0/addresses') http_etag: "..." start: 0 total_size: 0 When an address is created via the internal API, it is available in the REST API. :: >>> from zope.component import getUtility >>> from mailman.interfaces.usermanager import IUserManager >>> user_manager = getUtility(IUserManager) >>> anne = user_manager.create_address('anne@example.com') >>> transaction.commit() >>> dump_json('http://localhost:9001/3.0/addresses') entry 0: email: anne@example.com http_etag: "..." original_email: anne@example.com registered_on: 2005-08-01T07:49:23 self_link: http://localhost:9001/3.0/addresses/anne@example.com http_etag: "..." start: 0 total_size: 1 Anne's address can also be accessed directly. >>> dump_json('http://localhost:9001/3.0/addresses/anne@example.com') email: anne@example.com http_etag: "..." original_email: anne@example.com registered_on: 2005-08-01T07:49:23 self_link: http://localhost:9001/3.0/addresses/anne@example.com Bart registers with a mixed-case address. The canonical URL always includes the lower-case version. >>> bart = user_manager.create_address('Bart.Person@example.com') >>> transaction.commit() >>> dump_json( ... 'http://localhost:9001/3.0/addresses/bart.person@example.com') email: bart.person@example.com http_etag: "..." original_email: Bart.Person@example.com registered_on: 2005-08-01T07:49:23 self_link: http://localhost:9001/3.0/addresses/bart.person@example.com But his address record can be accessed with the case-preserved version too. >>> dump_json( ... 'http://localhost:9001/3.0/addresses/Bart.Person@example.com') email: bart.person@example.com http_etag: "..." original_email: Bart.Person@example.com registered_on: 2005-08-01T07:49:23 self_link: http://localhost:9001/3.0/addresses/bart.person@example.com When an address has a real name associated with it, this is also available in 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 registered_on: 2005-08-01T07:49:23 self_link: http://localhost:9001/3.0/addresses/cris@example.com Verifying ========= When the address gets verified, this attribute is available in the REST representation. >>> from mailman.utilities.datetime import now >>> anne.verified_on = now() >>> transaction.commit() >>> dump_json('http://localhost:9001/3.0/addresses/anne@example.com') email: anne@example.com http_etag: "..." original_email: anne@example.com registered_on: 2005-08-01T07:49:23 self_link: http://localhost:9001/3.0/addresses/anne@example.com verified_on: 2005-08-01T07:49:23 Addresses can also be verified through the REST API, by POSTing to the 'verify' sub-resource. The POST data is ignored. >>> dump_json('http://localhost:9001/3.0/addresses/' ... 'cris@example.com/verify', {}) content-length: 0 date: ... server: ... status: 204 Now Cris's address is verified. >>> 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 registered_on: 2005-08-01T07:49:23 self_link: http://localhost:9001/3.0/addresses/cris@example.com verified_on: 2005-08-01T07:49:23 If you should ever need to 'unverify' an address, POST to the 'unverify' sub-resource. Again, the POST data is ignored. >>> dump_json('http://localhost:9001/3.0/addresses/' ... 'cris@example.com/unverify', {}) content-length: 0 date: ... server: ... status: 204 Now Cris's address is unverified. >>> 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 registered_on: 2005-08-01T07:49:23 self_link: http://localhost:9001/3.0/addresses/cris@example.com The user ======== To link an address to a user, a POST request can be sent to the ``/user`` sub-resource of the address. If the user does not exist, it will be created. >>> dump_json('http://localhost:9001/3.0/addresses/cris@example.com/user', ... {'display_name': 'Cris X. Person'}) content-length: 0 date: ... location: http://localhost:9001/3.0/users/1 server: ... status: 201 The user is now created and the address is linked to it: >>> cris.user >>> cris_user = user_manager.get_user('cris@example.com') >>> cris_user >>> cris.user == cris_user True >>> [a.email for a in cris_user.addresses] ['cris@example.com'] A link to the user resource is now available as a sub-resource. >>> 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 registered_on: 2005-08-01T07:49:23 self_link: http://localhost:9001/3.0/addresses/cris@example.com user: http://localhost:9001/3.0/users/1 To prevent automatic user creation from taking place, add the `auto_create` parameter to the POST request and set it to a false-equivalent value like 0: >>> dump_json('http://localhost:9001/3.0/addresses/anne@example.com/user', ... {'display_name': 'Anne User', 'auto_create': 0}) Traceback (most recent call last): ... urllib.error.HTTPError: HTTP Error 403: ... A request to the `/user` sub-resource will return the linked user's representation: >>> dump_json('http://localhost:9001/3.0/addresses/cris@example.com/user') created_on: 2005-08-01T07:49:23 display_name: Cris X. Person http_etag: "..." is_server_owner: False password: ... self_link: http://localhost:9001/3.0/users/1 user_id: 1 The address and the user can be unlinked by sending a DELETE request on the `/user` resource. The user itself is not deleted, only the link. >>> dump_json('http://localhost:9001/3.0/addresses/cris@example.com/user', ... method='DELETE') content-length: 0 date: ... server: ... status: 204 >>> transaction.abort() >>> cris.user == None True >>> from uuid import UUID >>> user_manager.get_user_by_id(UUID(int=1)) >>> dump_json('http://localhost:9001/3.0/addresses/cris@example.com/user') Traceback (most recent call last): ... urllib.error.HTTPError: HTTP Error 404: ... You can link an existing user to an address by passing the user's ID in the POST request. >>> dump_json('http://localhost:9001/3.0/addresses/cris@example.com/user', ... {'user_id': 1}) content-length: 0 date: ... server: ... status: 200 >>> dump_json('http://localhost:9001/3.0/addresses/cris@example.com/user') created_on: ... display_name: Cris X. Person http_etag: ... password: ... self_link: http://localhost:9001/3.0/users/1 user_id: 1 To link an address to a different user, you can either send a DELETE request followed by a POST request, or you can send a PUT request. >>> dump_json('http://localhost:9001/3.0/addresses/cris@example.com/user', ... {'display_name': 'Cris Q Person'}, method="PUT") content-length: 0 date: ... location: http://localhost:9001/3.0/users/2 server: ... status: 201 >>> dump_json('http://localhost:9001/3.0/addresses/cris@example.com/user') created_on: ... display_name: Cris Q Person http_etag: ... password: ... self_link: http://localhost:9001/3.0/users/2 user_id: 2 User addresses ============== Users control addresses. The canonical URLs for these user-controlled addresses live in the ``/addresses`` namespace. :: >>> dave = user_manager.create_user('dave@example.com', 'Dave Person') >>> 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 registered_on: 2005-08-01T07:49:23 self_link: http://localhost:9001/3.0/addresses/dave@example.com user: http://localhost:9001/3.0/users/3 http_etag: "..." start: 0 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 registered_on: 2005-08-01T07:49:23 self_link: http://localhost:9001/3.0/addresses/dave@example.com user: http://localhost:9001/3.0/users/3 A user can be associated with multiple email addresses. You can add new addresses to an existing user. >>> dump_json( ... 'http://localhost:9001/3.0/users/dave@example.com/addresses', { ... 'email': 'dave.person@example.org' ... }) content-length: 0 date: ... location: http://localhost:9001/3.0/addresses/dave.person@example.org server: ... status: 201 When you add the new address, you can give it an optional display name. >>> dump_json( ... 'http://localhost:9001/3.0/users/dave@example.com/addresses', { ... 'email': 'dp@example.org', ... 'display_name': 'Davie P', ... }) content-length: 0 date: ... location: http://localhost:9001/3.0/addresses/dp@example.org server: ... status: 201 The user controls these new addresses. >>> dump_json('http://localhost:9001/3.0/users/dave@example.com/addresses') entry 0: email: dave.person@example.org http_etag: "..." original_email: dave.person@example.org registered_on: 2005-08-01T07:49:23 self_link: http://localhost:9001/3.0/addresses/dave.person@example.org user: http://localhost:9001/3.0/users/3 entry 1: display_name: Dave Person email: dave@example.com http_etag: "..." original_email: dave@example.com registered_on: 2005-08-01T07:49:23 self_link: http://localhost:9001/3.0/addresses/dave@example.com user: http://localhost:9001/3.0/users/3 entry 2: display_name: Davie P email: dp@example.org http_etag: "..." original_email: dp@example.org registered_on: 2005-08-01T07:49:23 self_link: http://localhost:9001/3.0/addresses/dp@example.org user: http://localhost:9001/3.0/users/3 http_etag: "..." start: 0 total_size: 3 Memberships =========== Addresses can be subscribed to mailing lists. When they are, all the membership records for that address are easily accessible via the REST API. Elle registers several email addresses. >>> elle = user_manager.create_user('elle@example.com', 'Elle Person') >>> subscriber = list(elle.addresses)[0] >>> elle.register('eperson@example.com') >>> elle.register('elle.person@example.com') Elle subscribes to two mailing lists with one of her addresses. :: >>> ant = create_list('ant@example.com') >>> bee = create_list('bee@example.com') >>> ant.subscribe(subscriber) on ant@example.com as MemberRole.member> >>> bee.subscribe(subscriber) on bee@example.com as MemberRole.member> >>> transaction.commit() Elle can get her memberships for each of her email addresses. :: >>> dump_json('http://localhost:9001/3.0/addresses/' ... 'elle@example.com/memberships') entry 0: address: http://localhost:9001/3.0/addresses/elle@example.com delivery_mode: regular email: elle@example.com http_etag: "..." list_id: ant.example.com member_id: 1 role: member self_link: http://localhost:9001/3.0/members/1 user: http://localhost:9001/3.0/users/4 entry 1: address: http://localhost:9001/3.0/addresses/elle@example.com delivery_mode: regular email: elle@example.com http_etag: "..." list_id: bee.example.com member_id: 2 role: member self_link: http://localhost:9001/3.0/members/2 user: http://localhost:9001/3.0/users/4 http_etag: "..." start: 0 total_size: 2 >>> dump_json('http://localhost:9001/3.0/addresses/' ... 'eperson@example.com/memberships') http_etag: "..." start: 0 total_size: 0 When Elle subscribes to the `bee` list again with a different address, this does not show up in the list of memberships for his other address. :: >>> subscriber = user_manager.get_address('eperson@example.com') >>> bee.subscribe(subscriber) >>> transaction.commit() >>> dump_json('http://localhost:9001/3.0/addresses/' ... 'elle@example.com/memberships') entry 0: address: http://localhost:9001/3.0/addresses/elle@example.com delivery_mode: regular email: elle@example.com http_etag: "..." list_id: ant.example.com member_id: 1 role: member self_link: http://localhost:9001/3.0/members/1 user: http://localhost:9001/3.0/users/4 entry 1: address: http://localhost:9001/3.0/addresses/elle@example.com delivery_mode: regular email: elle@example.com http_etag: "..." list_id: bee.example.com member_id: 2 role: member self_link: http://localhost:9001/3.0/members/2 user: http://localhost:9001/3.0/users/4 http_etag: "..." start: 0 total_size: 2 >>> dump_json('http://localhost:9001/3.0/addresses/' ... 'eperson@example.com/memberships') entry 0: address: http://localhost:9001/3.0/addresses/eperson@example.com delivery_mode: regular email: eperson@example.com http_etag: "..." list_id: bee.example.com member_id: 3 role: member self_link: http://localhost:9001/3.0/members/3 user: http://localhost:9001/3.0/users/4 http_etag: "..." start: 0 total_size: 1 Deleting ======== Addresses can be deleted via the REST API. :: >>> fred = user_manager.create_address('fred@example.com', 'Fred Person') >>> transaction.commit() >>> dump_json('http://localhost:9001/3.0/addresses/fred@example.com') display_name: Fred Person email: fred@example.com http_etag: "..." original_email: fred@example.com registered_on: 2005-08-01T07:49:23 self_link: http://localhost:9001/3.0/addresses/fred@example.com >>> dump_json('http://localhost:9001/3.0/addresses/fred@example.com', ... method='DELETE') content-length: 0 date: ... server: ... status: 204 >>> transaction.abort() >>> print(user_manager.get_address('fred@example.com')) None If an address is linked to a user, deleting the address does not delete the user, it just unlinks it. :: >>> gwen = user_manager.create_user('gwen@example.com', 'Gwen Person') >>> transaction.commit() >>> dump_json('http://localhost:9001/3.0/users/5/addresses') entry 0: display_name: Gwen Person email: gwen@example.com http_etag: "..." original_email: gwen@example.com registered_on: 2005-08-01T07:49:23 self_link: http://localhost:9001/3.0/addresses/gwen@example.com user: http://localhost:9001/3.0/users/5 http_etag: "795b0680c57ec2df3dceb68ccce2619fecdc7225" start: 0 total_size: 1 >>> dump_json('http://localhost:9001/3.0/addresses/gwen@example.com', ... method='DELETE') content-length: 0 date: ... server: ... status: 204 >>> dump_json('http://localhost:9001/3.0/users/5/addresses') http_etag: "..." start: 0 total_size: 0