~launchpad-pqm/launchpad/devel

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
# Copyright 2009-2011 Canonical Ltd.  This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).

"""Database class for table ArchiveSubscriber."""

__metaclass__ = type

__all__ = [
    'ArchiveSubscriber',
    ]

import pytz
from storm.expr import (
    And,
    Desc,
    Join,
    LeftJoin,
    )
from storm.locals import (
    DateTime,
    Int,
    Reference,
    Store,
    Storm,
    Unicode,
    )
from storm.store import EmptyResultSet
from zope.component import getUtility
from zope.interface import implements

from canonical.database.constants import UTC_NOW
from canonical.database.enumcol import DBEnum
from lp.registry.interfaces.person import validate_person
from lp.registry.model.teammembership import TeamParticipation
from lp.services.identity.interfaces.emailaddress import EmailAddressStatus
from lp.services.identity.model.emailaddress import EmailAddress
from lp.soyuz.enums import ArchiveSubscriberStatus
from lp.soyuz.interfaces.archiveauthtoken import IArchiveAuthTokenSet
from lp.soyuz.interfaces.archivesubscriber import IArchiveSubscriber
from lp.soyuz.model.archiveauthtoken import ArchiveAuthToken


class ArchiveSubscriber(Storm):
    """See `IArchiveSubscriber`."""
    implements(IArchiveSubscriber)
    __storm_table__ = 'ArchiveSubscriber'

    id = Int(primary=True)

    archive_id = Int(name='archive', allow_none=False)
    archive = Reference(archive_id, 'Archive.id')

    registrant_id = Int(name='registrant', allow_none=False)
    registrant = Reference(registrant_id, 'Person.id')

    date_created = DateTime(
        name='date_created', allow_none=False, tzinfo=pytz.UTC)

    subscriber_id = Int(
        name='subscriber', allow_none=False,
        validator=validate_person)
    subscriber = Reference(subscriber_id, 'Person.id')

    date_expires = DateTime(
        name='date_expires', allow_none=True, tzinfo=pytz.UTC)

    status = DBEnum(
        name='status', allow_none=False,
        enum=ArchiveSubscriberStatus)

    description = Unicode(name='description', allow_none=True)

    date_cancelled = DateTime(
        name='date_cancelled', allow_none=True, tzinfo=pytz.UTC)

    cancelled_by_id = Int(name='cancelled_by', allow_none=True)
    cancelled_by = Reference(cancelled_by_id, 'Person.id')

    @property
    def displayname(self):
        """See `IArchiveSubscriber`."""
        return "%s's access to %s" % (
            self.subscriber.displayname, self.archive.displayname)

    def cancel(self, cancelled_by):
        """See `IArchiveSubscriber`."""
        self.date_cancelled = UTC_NOW
        self.cancelled_by = cancelled_by
        self.status = ArchiveSubscriberStatus.CANCELLED

    def getNonActiveSubscribers(self):
        """See `IArchiveSubscriber`."""
        # Imported here because of circular imports.
        from lp.registry.model.person import Person

        store = Store.of(self)
        if self.subscriber.is_team:

            # We get all the people who already have active tokens for
            # this archive (for example, through separate subscriptions).
            auth_token = LeftJoin(
                ArchiveAuthToken,
                And(ArchiveAuthToken.person_id == Person.id,
                    ArchiveAuthToken.archive_id == self.archive_id,
                    ArchiveAuthToken.date_deactivated == None))

            team_participation = Join(
                TeamParticipation,
                TeamParticipation.personID == Person.id)

            # Only return people with preferred email address set.
            preferred_email = Join(
                EmailAddress, EmailAddress.personID == Person.id)

            # We want to get all participants who are themselves
            # individuals, not teams:
            non_active_subscribers = store.using(
                Person, team_participation, preferred_email, auth_token).find(
                (Person, EmailAddress),
                EmailAddress.status == EmailAddressStatus.PREFERRED,
                TeamParticipation.teamID == self.subscriber_id,
                Person.teamowner == None,
                # There is no existing archive auth token.
                ArchiveAuthToken.person_id == None)
            non_active_subscribers.order_by(Person.name)
            return non_active_subscribers
        else:
            # Subscriber is not a team.
            token_set = getUtility(IArchiveAuthTokenSet)
            if token_set.getActiveTokenForArchiveAndPerson(
                self.archive, self.subscriber) is not None:
                # There are active tokens, so return an empty result
                # set.
                return EmptyResultSet()

            # Otherwise return a result set containing only the
            # subscriber and their preferred email address.
            return store.find(
                (Person, EmailAddress),
                Person.id == self.subscriber_id,
                EmailAddress.personID == Person.id,
                EmailAddress.status == EmailAddressStatus.PREFERRED)


class ArchiveSubscriberSet:
    """See `IArchiveSubscriberSet`."""

    def _getBySubscriber(self, subscriber, archive, current_only,
                         with_active_tokens):
        """Return all the subscriptions for a person.

        :param subscriber: An `IPerson` for whom to return all
            `ArchiveSubscriber` records.
        :param archive: An optional `IArchive` which restricts
            the results to that particular archive.
        :param current_only: Whether the result should only include current
            subscriptions (which is the default).
        :param with_active_tokens: Indicates whether the tokens for the given
            subscribers subscriptions should be included in the resultset.
            By default the tokens are not included in the resultset.
^       """
        # Grab the extra Storm expressions, for this query,
        # depending on the params:
        extra_exprs = self._getExprsForSubscriptionQueries(
            archive, current_only)
        origin = [
            ArchiveSubscriber,
            Join(
                TeamParticipation,
                TeamParticipation.teamID == ArchiveSubscriber.subscriber_id)]

        if with_active_tokens:
            result_row = (ArchiveSubscriber, ArchiveAuthToken)
            # We need a left join with ArchiveSubscriber as
            # the origin:
            origin.append(
                LeftJoin(
                    ArchiveAuthToken,
                    And(
                        ArchiveAuthToken.archive_id ==
                            ArchiveSubscriber.archive_id,
                        ArchiveAuthToken.person_id == subscriber.id,
                        ArchiveAuthToken.date_deactivated == None)))
        else:
            result_row = ArchiveSubscriber

        # Set the main expression to find all the subscriptions for
        # which the subscriber is a direct subscriber OR is a member
        # of a subscribed team.
        # Note: the subscription to the owner itself will also be
        # part of the join as there is a TeamParticipation entry
        # showing that each person is a member of the "team" that
        # consists of themselves.
        store = Store.of(subscriber)
        return store.using(*origin).find(
            result_row,
            TeamParticipation.personID == subscriber.id,
            *extra_exprs).order_by(Desc(ArchiveSubscriber.date_created))

    def getBySubscriber(self, subscriber, archive=None, current_only=True):
        """See `IArchiveSubscriberSet`."""
        return self._getBySubscriber(subscriber, archive, current_only, False)

    def getBySubscriberWithActiveToken(self, subscriber, archive=None):
        """See `IArchiveSubscriberSet`."""
        return self._getBySubscriber(subscriber, archive, True, True)

    def getByArchive(self, archive, current_only=True):
        """See `IArchiveSubscriberSet`."""
        # Grab the extra Storm expressions, for this query,
        # depending on the params:
        extra_exprs = self._getExprsForSubscriptionQueries(
            archive, current_only)

        store = Store.of(archive)
        return store.find(
            ArchiveSubscriber,
            *extra_exprs).order_by(Desc(ArchiveSubscriber.date_created))

    def _getExprsForSubscriptionQueries(self, archive=None,
                                        current_only=True):
        """Return the Storm expressions required for the parameters.

        Just to keep the code DRY.
        """
        extra_exprs = []

        # Restrict the results to the specified archive if requested:
        if archive:
            extra_exprs.append(ArchiveSubscriber.archive == archive)

        # Restrict the results to only those subscriptions that are current
        # if requested:
        if current_only:
            extra_exprs.append(
                ArchiveSubscriber.status == ArchiveSubscriberStatus.CURRENT)

        return extra_exprs