Abhilash Raj has proposed merging lp:~raj-abhilash1/mailman/bug_1423756 into 
lp:mailman.

Requested reviews:
  Mailman Coders (mailman-coders)

For more details, see:
https://code.launchpad.net/~raj-abhilash1/mailman/bug_1423756/+merge/254479

Ability to define a server owner and domain owner.

@barry: I couldn't find where are variables filled in the confirm.txt template 
for verification of addresses so two tests related to the same are failing.
-- 
Your team Mailman Coders is requested to review the proposed merge of 
lp:~raj-abhilash1/mailman/bug_1423756 into lp:mailman.
=== modified file 'src/mailman/app/registrar.py'
--- src/mailman/app/registrar.py	2015-01-05 01:22:39 +0000
+++ src/mailman/app/registrar.py	2015-03-28 13:55:44 +0000
@@ -161,8 +161,6 @@
     # For i18n interpolation.
     confirm_url = mlist.domain.confirm_url(event.token)
     email_address = event.pendable['email']
-    domain_name = mlist.domain.mail_host
-    contact_address = mlist.domain.contact_address
     # Send a verification email to the address.
     template = getUtility(ITemplateLoader).get(
         'mailman:///{0}/{1}/confirm.txt'.format(

=== modified file 'src/mailman/commands/docs/create.rst'
--- src/mailman/commands/docs/create.rst	2014-04-28 15:23:35 +0000
+++ src/mailman/commands/docs/create.rst	2015-03-28 13:55:44 +0000
@@ -44,8 +44,7 @@
 
     >>> from mailman.interfaces.domain import IDomainManager
     >>> getUtility(IDomainManager).get('example.xx')
-    <Domain example.xx, base_url: http://example.xx,
-            contact_address: [email protected]>
+    <Domain example.xx, base_url: http://example.xx>
 
 You can also create mailing lists in existing domains without the
 auto-creation flag.

=== modified file 'src/mailman/commands/tests/test_lists.py'
--- src/mailman/commands/tests/test_lists.py	2015-03-14 01:16:51 +0000
+++ src/mailman/commands/tests/test_lists.py	2015-03-28 13:55:44 +0000
@@ -48,7 +48,7 @@
         # LP: #1166911 - non-matching lists were returned.
         getUtility(IDomainManager).add(
             'example.net', 'An example domain.',
-            'http://lists.example.net', '[email protected]')
+            'http://lists.example.net')
         create_list('[email protected]')
         create_list('[email protected]')
         # Only this one should show up.

=== added file 'src/mailman/database/alembic/versions/46e92facee7_add_serverowner_domainowner.py'
--- src/mailman/database/alembic/versions/46e92facee7_add_serverowner_domainowner.py	1970-01-01 00:00:00 +0000
+++ src/mailman/database/alembic/versions/46e92facee7_add_serverowner_domainowner.py	2015-03-28 13:55:44 +0000
@@ -0,0 +1,45 @@
+"""add_serverowner_domainowner
+
+Revision ID: 46e92facee7
+Revises: 33e1f5f6fa8
+Create Date: 2015-03-20 16:01:25.007242
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = '46e92facee7'
+down_revision = '33e1f5f6fa8'
+
+from alembic import op
+import sqlalchemy as sa
+
+
+def upgrade():
+    ### commands auto generated by Alembic - please adjust! ###
+    op.create_table('owner',
+    sa.Column('user_id', sa.Integer(), nullable=False),
+    sa.Column('domain_id', sa.Integer(), nullable=False),
+    sa.ForeignKeyConstraint(['domain_id'], ['domain.id'], ),
+    sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
+    sa.PrimaryKeyConstraint('user_id', 'domain_id')
+    )
+    op.add_column('user', sa.Column('is_server_owner', sa.Boolean(),
+                                    nullable=True))
+    if op.get_bind().dialect.name != 'sqlite':
+        op.drop_column('domain', 'contact_address')
+        # The migration below may be because the first migration was created
+        # before we changed this attribute to a primary_key
+        op.create_foreign_key('_preferred_address', 'user', 'address',
+                              ['_preferred_address_id'], ['id'])
+    ### end Alembic commands ###
+
+
+def downgrade():
+    ### commands auto generated by Alembic - please adjust! ###
+    op.drop_column('user', 'is_server_owner')
+    if op.get_bind().dialect.name != 'sqlite':
+        op.add_column('domain', sa.Column('contact_address', sa.VARCHAR(),
+                                          nullable=True))
+        op.drop_constraint('_preferred_address', 'user', type_='foreignkey')
+    op.drop_table('owner')
+    ### end Alembic commands ###

=== modified file 'src/mailman/interfaces/domain.py'
--- src/mailman/interfaces/domain.py	2015-01-05 01:22:39 +0000
+++ src/mailman/interfaces/domain.py	2015-03-28 13:55:44 +0000
@@ -88,9 +88,8 @@
     description = Attribute(
         'The human readable description of the domain name.')
 
-    contact_address = Attribute("""\
-    The contact address for the human at this domain.
-    E.g. [email protected]""")
+    owners = Attribute("""\
+        The relationship with the user database representing domain owners""")
 
     mailing_lists = Attribute(
         """All mailing lists for this domain.
@@ -112,7 +111,7 @@
 class IDomainManager(Interface):
     """The manager of domains."""
 
-    def add(mail_host, description=None, base_url=None, contact_address=None):
+    def add(mail_host, description=None, base_url=None, owner_id=None):
         """Add a new domain.
 
         :param mail_host: The email host name for the domain.
@@ -123,10 +122,8 @@
             interface of the domain.  If not given, it defaults to
             http://`mail_host`/
         :type base_url: string
-        :param contact_address: The email contact address for the human
-            managing the domain.  If not given, defaults to
-            postmaster@`mail_host`
-        :type contact_address: string
+        :param owner_id: Owner of the domain, defaults to None
+        :type owner_id: integer
         :return: The new domain object
         :rtype: `IDomain`
         :raises `BadDomainSpecificationError`: when the `mail_host` is

=== modified file 'src/mailman/model/docs/domains.rst'
--- src/mailman/model/docs/domains.rst	2014-12-13 18:26:05 +0000
+++ src/mailman/model/docs/domains.rst	2015-03-28 13:55:44 +0000
@@ -9,6 +9,10 @@
     >>> manager = getUtility(IDomainManager)
     >>> manager.remove('example.com')
     <Domain example.com...>
+	>>> from mailman.interfaces.usermanager import IUserManager
+	>>> user_manager = getUtility(IUserManager)
+    >>> user = user_manager.create_user('[email protected]')
+	>>> config.db.commit()
 
 Domains are how Mailman interacts with email host names and web host names.
 ::
@@ -28,17 +32,14 @@
 is the only required piece.  The other parts are inferred from that.
 
     >>> manager.add('example.org')
-    <Domain example.org, base_url: http://example.org,
-            contact_address: [email protected]>
+    <Domain example.org, base_url: http://example.org>
     >>> show_domains()
-    <Domain example.org, base_url: http://example.org,
-            contact_address: [email protected]>
+    <Domain example.org, base_url: http://example.org>
 
 We can remove domains too.
 
     >>> manager.remove('example.org')
-    <Domain example.org, base_url: http://example.org,
-            contact_address: [email protected]>
+    <Domain example.org, base_url: http://example.org>
     >>> show_domains()
     no domains
 
@@ -46,30 +47,34 @@
 web interface for the domain.
 
     >>> manager.add('example.com', base_url='https://mail.example.com')
-    <Domain example.com, base_url: https://mail.example.com,
-            contact_address: [email protected]>
+    <Domain example.com, base_url: https://mail.example.com>
     >>> show_domains()
-    <Domain example.com, base_url: https://mail.example.com,
-            contact_address: [email protected]>
+    <Domain example.com, base_url: https://mail.example.com>
 
-Domains can have explicit descriptions and contact addresses.
+Domains can have explicit descriptions.
 ::
 
     >>> manager.add(
     ...     'example.net',
     ...     base_url='http://lists.example.net',
-    ...     contact_address='[email protected]',
-    ...     description='The example domain')
+    ...     description='The example domain',
+    ...     owner_id=1)
     <Domain example.net, The example domain,
-            base_url: http://lists.example.net,
-            contact_address: [email protected]>
+            base_url: http://lists.example.net>
 
     >>> show_domains()
-    <Domain example.com, base_url: https://mail.example.com,
-            contact_address: [email protected]>
+    <Domain example.com, base_url: https://mail.example.com>
     <Domain example.net, The example domain,
-            base_url: http://lists.example.net,
-            contact_address: [email protected]>
+            base_url: http://lists.example.net>
+
+Domains can have multiple number of owners, ideally one of the owners
+should  have a verified preferred address. However this is not checked
+right now and contact_address from config is used as a fallback.
+::
+
+   >>> net_domain = manager['example.net']
+   >>> net_domain.add_owner(user_manager.get_user('[email protected]'))
+
 
 Domains can list all associated mailing lists with the mailing_lists property.
 ::
@@ -105,8 +110,7 @@
 
     >>> print(manager['example.net'])
     <Domain example.net, The example domain,
-            base_url: http://lists.example.net,
-            contact_address: [email protected]>
+            base_url: http://lists.example.net>
 
 As with dictionaries, you can also get the domain.  If the domain does not
 exist, ``None`` or a default is returned.
@@ -114,8 +118,7 @@
 
     >>> print(manager.get('example.net'))
     <Domain example.net, The example domain,
-            base_url: http://lists.example.net,
-            contact_address: [email protected]>
+            base_url: http://lists.example.net>
 
     >>> print(manager.get('doesnotexist.com'))
     None

=== modified file 'src/mailman/model/domain.py'
--- src/mailman/model/domain.py	2015-01-05 01:40:47 +0000
+++ src/mailman/model/domain.py	2015-03-28 13:55:44 +0000
@@ -29,8 +29,10 @@
     BadDomainSpecificationError, DomainCreatedEvent, DomainCreatingEvent,
     DomainDeletedEvent, DomainDeletingEvent, IDomain, IDomainManager)
 from mailman.model.mailinglist import MailingList
+from mailman.model.user import User, Owner
 from urllib.parse import urljoin, urlparse
 from sqlalchemy import Column, Integer, Unicode
+from sqlalchemy.orm import relationship, backref
 from zope.event import notify
 from zope.interface import implementer
 
@@ -47,12 +49,14 @@
     mail_host = Column(Unicode) # TODO: add index?
     base_url = Column(Unicode)
     description = Column(Unicode)
-    contact_address = Column(Unicode)
+    owners = relationship("User",
+                          secondary="owner",
+                          backref="domains")
 
     def __init__(self, mail_host,
                  description=None,
                  base_url=None,
-                 contact_address=None):
+                 owner=None):
         """Create and register a domain.
 
         :param mail_host: The host name for the email interface.
@@ -63,18 +67,16 @@
             scheme.  If not given, it will be constructed from the
             `mail_host` using the http protocol.
         :type base_url: string
-        :param contact_address: The email address to contact a human for this
-            domain.  If not given, postmaster@`mail_host` will be used.
-        :type contact_address: string
+        :param owner: The `User` who is the owner of this domain
+        :type owner: mailman.models.user.User
         """
         self.mail_host = mail_host
         self.base_url = (base_url
                          if base_url is not None
                          else 'http://' + mail_host)
         self.description = description
-        self.contact_address = (contact_address
-                                if contact_address is not None
-                                else 'postmaster@' + mail_host)
+        if owner is not None:
+            self.owners.append(owner)
 
     @property
     def url_host(self):
@@ -103,13 +105,19 @@
     def __repr__(self):
         """repr(a_domain)"""
         if self.description is None:
-            return ('<Domain {0.mail_host}, base_url: {0.base_url}, '
-                    'contact_address: {0.contact_address}>').format(self)
+            return ('<Domain {0.mail_host}, base_url: {0.base_url}>').format(self)
         else:
             return ('<Domain {0.mail_host}, {0.description}, '
-                    'base_url: {0.base_url}, '
-                    'contact_address: {0.contact_address}>').format(self)
-
+                    'base_url: {0.base_url}>').format(self)
+
+    def add_owner(self, owner):
+        """ Add a domain owner"""
+        self.owners.append(owner)
+
+    @dbconnection
+    def remove_owner(self, store, owner):
+        """ Remove a domain owner"""
+        self.owners.remove(owner)
 
 
 @implementer(IDomainManager)
@@ -121,15 +129,23 @@
             mail_host,
             description=None,
             base_url=None,
-            contact_address=None):
+            owner_id=None):
         """See `IDomainManager`."""
         # Be sure the mail_host is not already registered.  This is probably
         # a constraint that should (also) be maintained in the database.
         if self.get(mail_host) is not None:
             raise BadDomainSpecificationError(
                 'Duplicate email host: %s' % mail_host)
+        # Be sure that the owner exists
+        owner = None
+        if owner_id is not None:
+            owner = store.query(User).get(owner_id)
+            if owner is None:
+                raise BadDomainSpecificationError(
+                    'Owner of this domain does not exist')
+
         notify(DomainCreatingEvent(mail_host))
-        domain = Domain(mail_host, description, base_url, contact_address)
+        domain = Domain(mail_host, description, base_url, owner)
         store.add(domain)
         notify(DomainCreatedEvent(domain))
         return domain

=== modified file 'src/mailman/model/tests/test_domain.py'
--- src/mailman/model/tests/test_domain.py	2015-01-05 01:22:39 +0000
+++ src/mailman/model/tests/test_domain.py	2015-03-28 13:55:44 +0000
@@ -26,9 +26,11 @@
 import unittest
 
 from mailman.app.lifecycle import create_list
+from mailman.config import config
 from mailman.interfaces.domain import (
     DomainCreatedEvent, DomainCreatingEvent, DomainDeletedEvent,
-    DomainDeletingEvent, IDomainManager)
+    DomainDeletingEvent, IDomainManager, BadDomainSpecificationError)
+from mailman.interfaces.usermanager import IUserManager
 from mailman.interfaces.listmanager import IListManager
 from mailman.testing.helpers import event_subscribers
 from mailman.testing.layers import ConfigLayer
@@ -78,6 +80,32 @@
         # Trying to delete a missing domain gives you a KeyError.
         self.assertRaises(KeyError, self._manager.remove, 'doesnotexist.com')
 
+    def test_domain_create_with_owner(self):
+        user = getUtility(IUserManager).create_user('[email protected]')
+        config.db.commit()
+        domain = self._manager.add('example.org', owner_id=user.id)
+        self.assertEqual(len(domain.owners), 1)
+        self.assertEqual(domain.owners[0].id, user.id)
+
+    def test_domain_create_with_non_existent_owner(self):
+        with self.assertRaises(BadDomainSpecificationError):
+            self._manager.add('testdomain.org', owner_id=100)
+
+    def test_add_domain_owner(self):
+        user = getUtility(IUserManager).create_user('[email protected]')
+        config.db.commit()
+        domain = self._manager.add('example.org')
+        domain.add_owner(user)
+        self.assertEqual(len(domain.owners), 1)
+        self.assertEqual(domain.owners[0].id, user.id)
+
+    def test_remove_domain_owner(self):
+        user = getUtility(IUserManager).create_user('[email protected]')
+        config.db.commit()
+        domain = self._manager.add('example.org', owner_id=user.id)
+        domain.remove_owner(user)
+        self.assertEqual(len(domain.owners), 0)
+
 
 
 class TestDomainLifecycleEvents(unittest.TestCase):
@@ -110,3 +138,7 @@
         self.assertEqual(listmanager.get('[email protected]'), None)
         self.assertEqual(listmanager.get('[email protected]'), ewe)
         self.assertEqual(listmanager.get('[email protected]'), fly)
+
+    def test_owners_are_deleted_when_domain_is(self):
+        #TODO: Complete this
+        pass

=== modified file 'src/mailman/model/user.py'
--- src/mailman/model/user.py	2015-03-20 16:38:00 +0000
+++ src/mailman/model/user.py	2015-03-28 13:55:44 +0000
@@ -34,7 +34,7 @@
 from mailman.model.roster import Memberships
 from mailman.utilities.datetime import factory as date_factory
 from mailman.utilities.uid import UniqueIDFactory
-from sqlalchemy import Column, DateTime, ForeignKey, Integer, Unicode
+from sqlalchemy import Column, DateTime, ForeignKey, Integer, Unicode, Boolean
 from sqlalchemy.orm import relationship, backref
 from zope.event import notify
 from zope.interface import implementer
@@ -55,6 +55,7 @@
     _password = Column('password', Unicode)
     _user_id = Column(UUID, index=True)
     _created_on = Column(DateTime)
+    is_server_owner = Column(Boolean, default=False)
 
     addresses = relationship(
         'Address', backref='user',
@@ -176,3 +177,11 @@
     @property
     def memberships(self):
         return Memberships(self)
+
+
+class Owner(Model):
+    """Doomain to owners(user) association class"""
+
+    __tablename__ = 'owner'
+    user_id = Column(Integer, ForeignKey('user.id'), primary_key=True)
+    domain_id = Column(Integer, ForeignKey('domain.id'), primary_key=True)

=== modified file 'src/mailman/rest/docs/addresses.rst'
--- src/mailman/rest/docs/addresses.rst	2015-02-14 01:35:35 +0000
+++ src/mailman/rest/docs/addresses.rst	2015-03-28 13:55:44 +0000
@@ -190,6 +190,7 @@
     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

=== modified file 'src/mailman/rest/docs/domains.rst'
--- src/mailman/rest/docs/domains.rst	2014-12-16 01:01:53 +0000
+++ src/mailman/rest/docs/domains.rst	2015-03-28 13:55:44 +0000
@@ -29,14 +29,12 @@
     >>> domain_manager.add(
     ...     'example.com', 'An example domain', 'http://lists.example.com')
     <Domain example.com, An example domain,
-            base_url: http://lists.example.com,
-            contact_address: [email protected]>
+            base_url: http://lists.example.com>
     >>> transaction.commit()
 
     >>> dump_json('http://localhost:9001/3.0/domains')
     entry 0:
         base_url: http://lists.example.com
-        contact_address: [email protected]
         description: An example domain
         http_etag: "..."
         mail_host: example.com
@@ -51,24 +49,19 @@
 
     >>> domain_manager.add(
     ...     'example.org',
-    ...     base_url='http://mail.example.org',
-    ...     contact_address='[email protected]')
-    <Domain example.org, base_url: http://mail.example.org,
-            contact_address: [email protected]>
+    ...     base_url='http://mail.example.org')
+    <Domain example.org, base_url: http://mail.example.org>
     >>> domain_manager.add(
     ...     'lists.example.net',
     ...     'Porkmasters',
-    ...     'http://example.net',
-    ...     '[email protected]')
+    ...     'http://example.net')
     <Domain lists.example.net, Porkmasters,
-            base_url: http://example.net,
-            contact_address: [email protected]>
+            base_url: http://example.net>
     >>> transaction.commit()
 
     >>> dump_json('http://localhost:9001/3.0/domains')
     entry 0:
         base_url: http://lists.example.com
-        contact_address: [email protected]
         description: An example domain
         http_etag: "..."
         mail_host: example.com
@@ -76,7 +69,6 @@
         url_host: lists.example.com
     entry 1:
         base_url: http://mail.example.org
-        contact_address: [email protected]
         description: None
         http_etag: "..."
         mail_host: example.org
@@ -84,7 +76,6 @@
         url_host: mail.example.org
     entry 2:
         base_url: http://example.net
-        contact_address: [email protected]
         description: Porkmasters
         http_etag: "..."
         mail_host: lists.example.net
@@ -103,7 +94,6 @@
 
     >>> dump_json('http://localhost:9001/3.0/domains/lists.example.net')
     base_url: http://example.net
-    contact_address: [email protected]
     description: Porkmasters
     http_etag: "..."
     mail_host: lists.example.net
@@ -165,7 +155,6 @@
 
     >>> dump_json('http://localhost:9001/3.0/domains/lists.example.com')
     base_url: http://lists.example.com
-    contact_address: [email protected]
     description: None
     http_etag: "..."
     mail_host: lists.example.com
@@ -176,9 +165,7 @@
 ::
 
     >>> domain_manager['lists.example.com']
-    <Domain lists.example.com,
-            base_url: http://lists.example.com,
-            contact_address: [email protected]>
+    <Domain lists.example.com, base_url: http://lists.example.com>
 
     # Unlock the database.
     >>> transaction.abort()
@@ -190,8 +177,7 @@
     >>> dump_json('http://localhost:9001/3.0/domains', {
     ...           'mail_host': 'my.example.com',
     ...           'description': 'My new domain',
-    ...           'base_url': 'http://allmy.example.com',
-    ...           'contact_address': '[email protected]'
+    ...           'base_url': 'http://allmy.example.com'
     ...           })
     content-length: 0
     date: ...
@@ -200,7 +186,6 @@
 
     >>> dump_json('http://localhost:9001/3.0/domains/my.example.com')
     base_url: http://allmy.example.com
-    contact_address: [email protected]
     description: My new domain
     http_etag: "..."
     mail_host: my.example.com
@@ -208,9 +193,7 @@
     url_host: allmy.example.com
 
     >>> domain_manager['my.example.com']
-    <Domain my.example.com, My new domain,
-            base_url: http://allmy.example.com,
-            contact_address: [email protected]>
+    <Domain my.example.com, My new domain, base_url: http://allmy.example.com>
 
     # Unlock the database.
     >>> transaction.abort()

=== modified file 'src/mailman/rest/docs/users.rst'
--- src/mailman/rest/docs/users.rst	2014-12-22 18:40:30 +0000
+++ src/mailman/rest/docs/users.rst	2015-03-28 13:55:44 +0000
@@ -34,6 +34,7 @@
         created_on: 2005-08-01T07:49:23
         display_name: Anne Person
         http_etag: "..."
+        is_server_owner: False
         self_link: http://localhost:9001/3.0/users/1
         user_id: 1
     http_etag: "..."
@@ -50,11 +51,13 @@
         created_on: 2005-08-01T07:49:23
         display_name: Anne Person
         http_etag: "..."
+        is_server_owner: False
         self_link: http://localhost:9001/3.0/users/1
         user_id: 1
     entry 1:
         created_on: 2005-08-01T07:49:23
         http_etag: "..."
+        is_server_owner: False
         self_link: http://localhost:9001/3.0/users/2
         user_id: 2
     http_etag: "..."
@@ -76,6 +79,7 @@
         created_on: 2005-08-01T07:49:23
         display_name: Anne Person
         http_etag: "..."
+        is_server_owner: False
         self_link: http://localhost:9001/3.0/users/1
         user_id: 1
     http_etag: "..."
@@ -86,6 +90,7 @@
     entry 0:
         created_on: 2005-08-01T07:49:23
         http_etag: "..."
+        is_server_owner: False
         self_link: http://localhost:9001/3.0/users/2
         user_id: 2
     http_etag: "..."
@@ -120,6 +125,7 @@
     >>> dump_json('http://localhost:9001/3.0/users/3')
     created_on: 2005-08-01T07:49:23
     http_etag: "..."
+    is_server_owner: False
     password: {plaintext}...
     self_link: http://localhost:9001/3.0/users/3
     user_id: 3
@@ -131,6 +137,7 @@
     >>> dump_json('http://localhost:9001/3.0/users/[email protected]')
     created_on: 2005-08-01T07:49:23
     http_etag: "..."
+    is_server_owner: False
     password: {plaintext}...
     self_link: http://localhost:9001/3.0/users/3
     user_id: 3
@@ -158,6 +165,7 @@
     created_on: 2005-08-01T07:49:23
     display_name: Dave Person
     http_etag: "..."
+    is_server_owner: False
     password: {plaintext}...
     self_link: http://localhost:9001/3.0/users/4
     user_id: 4
@@ -190,6 +198,7 @@
     created_on: 2005-08-01T07:49:23
     display_name: Elly Person
     http_etag: "..."
+    is_server_owner: False
     password: {plaintext}supersekrit
     self_link: http://localhost:9001/3.0/users/5
     user_id: 5
@@ -214,6 +223,7 @@
     created_on: 2005-08-01T07:49:23
     display_name: David Person
     http_etag: "..."
+    is_server_owner: False
     password: {plaintext}...
     self_link: http://localhost:9001/3.0/users/4
     user_id: 4
@@ -238,6 +248,7 @@
     created_on: 2005-08-01T07:49:23
     display_name: David Person
     http_etag: "..."
+    is_server_owner: False
     password: {plaintext}clockwork angels
     self_link: http://localhost:9001/3.0/users/4
     user_id: 4
@@ -260,6 +271,7 @@
     created_on: 2005-08-01T07:49:23
     display_name: David Personhood
     http_etag: "..."
+    is_server_owner: False
     password: {plaintext}the garden
     self_link: http://localhost:9001/3.0/users/4
     user_id: 4
@@ -343,6 +355,7 @@
     created_on: 2005-08-01T07:49:23
     display_name: Fred Person
     http_etag: "..."
+    is_server_owner: False
     self_link: http://localhost:9001/3.0/users/6
     user_id: 6
 
@@ -350,6 +363,7 @@
     created_on: 2005-08-01T07:49:23
     display_name: Fred Person
     http_etag: "..."
+    is_server_owner: False
     self_link: http://localhost:9001/3.0/users/6
     user_id: 6
 
@@ -357,6 +371,7 @@
     created_on: 2005-08-01T07:49:23
     display_name: Fred Person
     http_etag: "..."
+    is_server_owner: False
     self_link: http://localhost:9001/3.0/users/6
     user_id: 6
 
@@ -364,6 +379,7 @@
     created_on: 2005-08-01T07:49:23
     display_name: Fred Person
     http_etag: "..."
+    is_server_owner: False
     self_link: http://localhost:9001/3.0/users/6
     user_id: 6
 
@@ -382,6 +398,7 @@
     created_on: 2005-08-01T07:49:23
     display_name: Elly Person
     http_etag: "..."
+    is_server_owner: False
     password: {plaintext}supersekrit
     self_link: http://localhost:9001/3.0/users/5
     user_id: 5

=== modified file 'src/mailman/rest/domains.py'
--- src/mailman/rest/domains.py	2015-01-05 01:40:47 +0000
+++ src/mailman/rest/domains.py	2015-03-28 13:55:44 +0000
@@ -29,6 +29,7 @@
     BadRequest, CollectionMixin, NotFound, bad_request, child, created, etag,
     no_content, not_found, okay, path_to)
 from mailman.rest.lists import ListsForDomain
+from mailman.rest.users import OwnersForDomain
 from mailman.rest.validator import Validator
 from zope.component import getUtility
 
@@ -41,7 +42,6 @@
         """See `CollectionMixin`."""
         return dict(
             base_url=domain.base_url,
-            contact_address=domain.contact_address,
             description=domain.description,
             mail_host=domain.mail_host,
             self_link=path_to('domains/{0}'.format(domain.mail_host)),
@@ -88,6 +88,17 @@
         else:
             return BadRequest(), []
 
+    @child()
+    def owners(self, request, segments):
+        """/domains/<domain>/owners"""
+        if len(segments) == 0:
+            domain = getUtility(IDomainManager).get(self._domain)
+            if domain is None:
+                return NotFound()
+            return OwnersForDomain(domain)
+        else:
+            return BadRequest(), []
+
 
 class AllDomains(_DomainBase):
     """The domains."""
@@ -99,12 +110,12 @@
             validator = Validator(mail_host=str,
                                   description=str,
                                   base_url=str,
-                                  contact_address=str,
+                                  owner_id=int,
                                   _optional=('description', 'base_url',
-                                             'contact_address'))
+                                             'owner_id'))
             domain = domain_manager.add(**validator(request))
-        except BadDomainSpecificationError:
-            bad_request(response, b'Domain exists')
+        except BadDomainSpecificationError as error:
+            bad_request(response, str(error))
         except ValueError as error:
             bad_request(response, str(error))
         else:

=== modified file 'src/mailman/rest/users.py'
--- src/mailman/rest/users.py	2015-03-20 16:38:00 +0000
+++ src/mailman/rest/users.py	2015-03-28 13:55:44 +0000
@@ -67,8 +67,9 @@
     email=str,
     display_name=str,
     password=str,
-    _optional=('display_name', 'password'),
-    )
+    is_server_owner=bool,
+    _optional=('display_name', 'password', 'is_server_owner'),
+)
 
 
 
@@ -108,7 +109,8 @@
             user_id=user_id,
             created_on=user.created_on,
             self_link=path_to('users/{}'.format(user_id)),
-            )
+            is_server_owner=user.is_server_owner,
+        )
         # Add the password attribute, only if the user has a password.  Same
         # with the real name.  These could be None or the empty string.
         if user.password:
@@ -293,7 +295,8 @@
         del fields['email']
         fields['user_id'] = int
         fields['auto_create'] = as_boolean
-        fields['_optional'] = fields['_optional'] + ('user_id', 'auto_create')
+        fields['_optional'] = fields['_optional'] + ('user_id', 'auto_create',
+                                                     'is_server_owner')
         try:
             validator = Validator(**fields)
             arguments = validator(request)
@@ -328,7 +331,8 @@
         # Process post data and check for an existing user.
         fields = CREATION_FIELDS.copy()
         fields['user_id'] = int
-        fields['_optional'] = fields['_optional'] + ('user_id', 'email')
+        fields['_optional'] = fields['_optional'] + ('user_id', 'email',
+                                                     'is_server_owner')
         try:
             validator = Validator(**fields)
             arguments = validator(request)
@@ -377,3 +381,43 @@
             no_content(response)
         else:
             forbidden(response)
+
+class OwnersForDomain(_UserBase):
+    """Owners for a particular domain."""
+
+    def __init__(self, domain):
+        self._domain = domain
+
+    def on_get(self, request, response):
+        """/domains/<domain>/owners"""
+        resource = self._make_collection(request)
+        okay(response, etag(resource))
+
+    def on_post(self, request, response):
+        """POST to /domains/<domain>/owners """
+        validator = Validator(owner_id=GetterSetter(int))
+        try:
+            values = validator(request)
+        except ValueError as error:
+            bad_request(response, str(error))
+            return
+        owner = getUtility(IUserManager).get_user_by_id(values['owner_id'])
+        self._domain.add_owner(owner)
+        return no_content(response)
+
+    def on_patch(self, request, response):
+        # TODO: complete this
+        pass
+
+    def on_put(self, request, response):
+        # TODO: complete this
+        pass
+
+    def on_delete(self, request, response):
+        # TODO: complete this
+        pass
+
+    @paginate
+    def _get_collection(self, request):
+        """See `CollectionMixin`."""
+        return list(self._domain.owners)

=== modified file 'src/mailman/testing/layers.py'
--- src/mailman/testing/layers.py	2015-01-05 01:22:39 +0000
+++ src/mailman/testing/layers.py	2015-03-28 13:55:44 +0000
@@ -200,7 +200,7 @@
         with transaction():
             getUtility(IDomainManager).add(
                 'example.com', 'An example domain.',
-                'http://lists.example.com', '[email protected]')
+                'http://lists.example.com')
 
     @classmethod
     def testTearDown(cls):

_______________________________________________
Mailman-coders mailing list
[email protected]
https://mail.python.org/mailman/listinfo/mailman-coders

Reply via email to