← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] ~pelpsi/launchpad:social-account-interface-and-implementation into launchpad:master

 

Simone Pelosi has proposed merging ~pelpsi/launchpad:social-account-interface-and-implementation into launchpad:master.

Commit message:
SocialAccount interface and implementation
    
SocialAccount interface to reflect DB table SocialAccount:
each row of this new table is storing SocialAccount infomation 
associated to one person. One person could have more than one
SocialAccount. This change is required since we are implementing
Matrix.


Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~pelpsi/launchpad/+git/launchpad/+merge/457527
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~pelpsi/launchpad:social-account-interface-and-implementation into launchpad:master.
diff --git a/lib/lp/registry/configure.zcml b/lib/lp/registry/configure.zcml
index 3cf36a8..e362c03 100644
--- a/lib/lp/registry/configure.zcml
+++ b/lib/lp/registry/configure.zcml
@@ -650,6 +650,17 @@
             interface="lp.registry.interfaces.jabber.IJabberIDSet"/>
     </lp:securedutility>
     <class
+        class="lp.registry.model.person.SocialAccountSet">
+        <allow
+            interface="lp.registry.interfaces.socialaccount.ISocialAccountSet"/>
+    </class>
+    <lp:securedutility
+        class="lp.registry.model.person.SocialAccountSet"
+        provides="lp.registry.interfaces.socialaccount.ISocialAccountSet">
+        <allow
+            interface="lp.registry.interfaces.socialaccount.ISocialAccountSet"/>
+    </lp:securedutility>
+    <class
         class="lp.registry.model.pillar.PillarName">
         <allow
             interface="lp.registry.interfaces.pillar.IPillarName"/>
diff --git a/lib/lp/registry/doc/socialaccount.rst b/lib/lp/registry/doc/socialaccount.rst
new file mode 100644
index 0000000..84542be
--- /dev/null
+++ b/lib/lp/registry/doc/socialaccount.rst
@@ -0,0 +1,31 @@
+Social Accounts
+==========
+
+Social Accounts are associated with a person and must be created through the
+ISocialAccountSet utility.
+
+    >>> from lp.registry.interfaces.person import IPersonSet
+    >>> from lp.registry.interfaces.role import IHasOwner
+    >>> from lp.registry.interfaces.socialaccount import (
+    ...     ISocialAccount,
+    ...     ISocialAccountSet,
+    ...     PlatformType,
+    ... )
+
+The new() method of ISocialAccountSet takes the person who will be associated
+with the Social Account, a platform type and an identity dictionary.
+
+    >>> salgado = getUtility(IPersonSet).getByName("salgado")
+    >>> identity = {}
+    >>> identity["matrix_id"] = "abc"
+    >>> social_account = getUtility(ISocialAccountSet).new(
+    ...     salgado, PlatformType.MATRIX, identity
+    ... )
+
+The returned SocialAccount object provides both ISocialAccount and IHasOwner.
+
+    >>> from lp.testing import verifyObject
+    >>> verifyObject(ISocialAccount, social_account)
+    True
+    >>> verifyObject(IHasOwner, social_account)
+    True
diff --git a/lib/lp/registry/interfaces/person.py b/lib/lp/registry/interfaces/person.py
index 3aeae4c..bdb5ca6 100644
--- a/lib/lp/registry/interfaces/person.py
+++ b/lib/lp/registry/interfaces/person.py
@@ -121,6 +121,7 @@ from lp.registry.interfaces.location import (
 from lp.registry.interfaces.mailinglistsubscription import (
     MailingListAutoSubscribePolicy,
 )
+from lp.registry.interfaces.socialaccount import ISocialAccount
 from lp.registry.interfaces.ssh import ISSHKey
 from lp.registry.interfaces.teammembership import (
     ITeamMembership,
@@ -1030,6 +1031,15 @@ class IPersonViewRestricted(
         ),
         exported_as="jabber_ids",
     )
+    socialaccounts = exported(
+        CollectionField(
+            title=_("List of Social Accounts of this Person."),
+            readonly=True,
+            required=False,
+            value_type=Reference(schema=ISocialAccount),
+        ),
+        exported_as="social_accounts",
+    )
     team_memberships = exported(
         CollectionField(
             title=_(
@@ -3241,3 +3251,4 @@ patch_reference_property(IIrcID, "person", IPerson)
 patch_reference_property(IJabberID, "person", IPerson)
 patch_reference_property(IWikiName, "person", IPerson)
 patch_reference_property(IEmailAddress, "person", IPerson)
+patch_reference_property(ISocialAccount, "person", IPerson)
diff --git a/lib/lp/registry/interfaces/socialaccount.py b/lib/lp/registry/interfaces/socialaccount.py
new file mode 100644
index 0000000..f23a457
--- /dev/null
+++ b/lib/lp/registry/interfaces/socialaccount.py
@@ -0,0 +1,90 @@
+# Copyright 2009 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""Jabber interfaces."""
+
+__all__ = ["ISocialAccount", "ISocialAccountSet", "PlatformType"]
+
+from lazr.enum import DBEnumeratedType, DBItem
+from lazr.restful.declarations import exported, exported_as_webservice_entry
+from lazr.restful.fields import Reference
+from zope.interface import Interface
+from zope.schema import Choice, Dict, Int, TextLine
+
+from lp import _
+from lp.registry.interfaces.role import IHasOwner
+
+
+class PlatformType(DBEnumeratedType):
+    """Platform Type
+
+    When a social account is added it is referring
+    to a given platform type.
+    """
+
+    MATRIX = DBItem(
+        1,
+        """
+        Matrix platform
+
+        To define matrix
+        """,
+    )
+
+    JABBER = DBItem(
+        2,
+        """
+        Jabber platform
+
+        To define jabber
+        """,
+    )
+
+
+@exported_as_webservice_entry(as_of="beta")
+class ISocialAccount(IHasOwner):
+    """Jabber specific user ID"""
+
+    id = Int(title=_("Database ID"), required=True, readonly=True)
+    # schema=Interface will be overridden in person.py because of circular
+    # dependencies.
+    person = exported(
+        Reference(
+            title=_("Owner"), required=True, schema=Interface, readonly=True
+        )
+    )
+
+    platform = exported(
+        Choice(
+            title=_("Platform type"),
+            required=True,
+            vocabulary=PlatformType,
+            default=PlatformType.MATRIX,
+        )
+    )
+
+    identity = exported(
+        Dict(
+            title=_("SocialAccount identity"),
+            key_type=TextLine(),
+            required=True,
+            readonly=False,
+            description=_(
+                "A dictionary mapping attributes for the social media account "
+                "(JSON format specific per social media platform). "
+            ),
+        )
+    )
+
+    def destroySelf():
+        """Delete this SocialAccount from the database."""
+
+
+class ISocialAccountSet(Interface):
+    """The set of SocialAccounts."""
+
+    def new(self, person, platform, identity):
+        """Create a new SocialAccount pointing to the given Person."""
+
+    def getByPerson(person):
+        """Return all SocialAccounts for the given person."""
diff --git a/lib/lp/registry/model/person.py b/lib/lp/registry/model/person.py
index 779616d..aa5f4be 100644
--- a/lib/lp/registry/model/person.py
+++ b/lib/lp/registry/model/person.py
@@ -19,6 +19,8 @@ __all__ = [
     "PersonLanguage",
     "PersonSet",
     "PersonSettings",
+    "SocialAccount",
+    "SocialAccountSet",
     "SSHKey",
     "SSHKeySet",
     "TeamInvitationEvent",
@@ -62,7 +64,15 @@ from storm.expr import (
     With,
 )
 from storm.info import ClassAlias
-from storm.locals import Bool, DateTime, Int, Reference, ReferenceSet, Unicode
+from storm.locals import (
+    JSON,
+    Bool,
+    DateTime,
+    Int,
+    Reference,
+    ReferenceSet,
+    Unicode,
+)
 from storm.store import EmptyResultSet, Store
 from twisted.conch.ssh.common import getNS
 from twisted.conch.ssh.keys import Key
@@ -161,6 +171,11 @@ from lp.registry.interfaces.persontransferjob import IPersonMergeJobSource
 from lp.registry.interfaces.product import IProduct, IProductSet
 from lp.registry.interfaces.projectgroup import IProjectGroup
 from lp.registry.interfaces.role import IPersonRoles
+from lp.registry.interfaces.socialaccount import (
+    ISocialAccount,
+    ISocialAccountSet,
+    PlatformType,
+)
 from lp.registry.interfaces.ssh import (
     SSH_TEXT_TO_KEY_TYPE,
     ISSHKey,
@@ -646,6 +661,7 @@ class Person(
     signedcocs = ReferenceSet("id", "SignedCodeOfConduct.owner_id")
     _ircnicknames = ReferenceSet("id", "IrcID.person_id")
     jabberids = ReferenceSet("id", "JabberID.person_id")
+    socialaccounts = ReferenceSet("id", "SocialAccount.person_id")
 
     visibility = DBEnum(
         enum=PersonVisibility,
@@ -5321,6 +5337,45 @@ class WikiNameSet:
         return wiki_name
 
 
+@implementer(ISocialAccount)
+class SocialAccount(StormBase, HasOwnerMixin):
+    __storm_table__ = "SocialAccount"
+
+    id = Int(primary=True)
+    person_id = Int(name="person", allow_none=False)
+    person = Reference(person_id, "Person.id")
+    platform = DBEnum(
+        name="platform",
+        allow_none=False,
+        enum=PlatformType,
+    )
+    identity = JSON(name="identity", allow_none=False)
+
+    def __init__(self, person, platform, identity):
+        super().__init__()
+        self.person = person
+        self.platform = platform
+        self.identity = identity
+
+    def destroySelf(self):
+        IStore(self).remove(self)
+
+
+@implementer(ISocialAccountSet)
+class SocialAccountSet:
+    def getByPerson(self, person):
+        """See `ISocialAccountSet`."""
+        return IStore(SocialAccount).find(SocialAccount, person=person)
+
+    def new(self, person, platform, identity):
+        """See `ISocialAccountSet`."""
+        social_account = SocialAccount(
+            person=person, platform=platform, identity=identity
+        )
+        IStore(social_account).flush()
+        return social_account
+
+
 @implementer(IJabberID)
 class JabberID(StormBase, HasOwnerMixin):
     __storm_table__ = "JabberID"
diff --git a/lib/lp/registry/tests/test_person.py b/lib/lp/registry/tests/test_person.py
index 5381103..30fd456 100644
--- a/lib/lp/registry/tests/test_person.py
+++ b/lib/lp/registry/tests/test_person.py
@@ -38,6 +38,10 @@ from lp.registry.interfaces.karma import IKarmaCacheManager
 from lp.registry.interfaces.person import ImmutableVisibilityError, IPersonSet
 from lp.registry.interfaces.pocket import PackagePublishingPocket
 from lp.registry.interfaces.product import IProductSet
+from lp.registry.interfaces.socialaccount import (
+    ISocialAccountSet,
+    PlatformType,
+)
 from lp.registry.interfaces.teammembership import ITeamMembershipSet
 from lp.registry.model.karma import KarmaCategory, KarmaTotalCache
 from lp.registry.model.person import Person, get_recipients
@@ -438,6 +442,18 @@ class TestPerson(TestCaseWithFactory):
         self.assertEqual(None, person.homepage_content)
         self.assertEqual(None, person.teamdescription)
 
+    def test_social_account(self):
+        user = self.factory.makePerson()
+        attributes = {}
+        attributes["matrix_id"] = "abc"
+        getUtility(ISocialAccountSet).new(
+            user, PlatformType.MATRIX, attributes
+        )
+        social_account = user.socialaccounts.one()
+        social_account = removeSecurityProxy(social_account)
+        self.assertEqual(social_account.platform, PlatformType.MATRIX)
+        self.assertEqual(social_account.identity["matrix_id"], "abc")
+
     def test_getAffiliatedPillars_kinds(self):
         # Distributions, project groups, and projects are returned in this
         # same order.

Follow ups