13677.4.1
by Danilo Segan
Refactor getNonActiveSubscribers into WITH query. |
1 |
# Copyright 2009-2011 Canonical Ltd. This software is licensed under the
|
8687.15.22
by Karl Fogel
Add the copyright header block to the remaining .py files. |
2 |
# GNU Affero General Public License version 3 (see the file LICENSE).
|
7533.2.1
by Julian Edwards
ArchiveSubscriber content class, first attempt, no tests yet. |
3 |
|
4 |
"""Database class for table ArchiveSubscriber."""
|
|
5 |
||
6 |
__metaclass__ = type |
|
7 |
||
8 |
__all__ = [ |
|
7862.1.5
by Michael Nelson
Reverting some unitentional changes (different quotes, extra commas) from diff. |
9 |
'ArchiveSubscriber', |
7533.2.1
by Julian Edwards
ArchiveSubscriber content class, first attempt, no tests yet. |
10 |
]
|
11 |
||
12 |
import pytz |
|
11403.1.4
by Henning Eggers
Reformatted imports using format-imports script r32. |
13 |
from storm.expr import ( |
14 |
And, |
|
15 |
Desc, |
|
13677.4.1
by Danilo Segan
Refactor getNonActiveSubscribers into WITH query. |
16 |
Join, |
11403.1.4
by Henning Eggers
Reformatted imports using format-imports script r32. |
17 |
LeftJoin, |
18 |
)
|
|
19 |
from storm.locals import ( |
|
20 |
DateTime, |
|
21 |
Int, |
|
22 |
Reference, |
|
23 |
Store, |
|
24 |
Storm, |
|
25 |
Unicode, |
|
26 |
)
|
|
8546.1.1
by Julian Edwards
IArchiveSubscriber.getNonActiveSubscribers() now behaves properly when the subscriber is not a team. |
27 |
from storm.store import EmptyResultSet |
8546.1.3
by Julian Edwards
noodles' review comments |
28 |
from zope.component import getUtility |
7533.2.1
by Julian Edwards
ArchiveSubscriber content class, first attempt, no tests yet. |
29 |
from zope.interface import implements |
30 |
||
7533.2.6
by Julian Edwards
Add doctest |
31 |
from canonical.database.constants import UTC_NOW |
32 |
from canonical.database.enumcol import DBEnum |
|
7675.759.17
by Brad Crittenden
Reverted removal of validate_person. |
33 |
from lp.registry.interfaces.person import validate_person |
7675.110.3
by Curtis Hovey
Ran the migration script to move registry code to lp.registry. |
34 |
from lp.registry.model.teammembership import TeamParticipation |
14550.1.1
by Steve Kowalik
Run format-imports over lib/lp and lib/canonical/launchpad |
35 |
from lp.services.identity.interfaces.emailaddress import EmailAddressStatus |
36 |
from lp.services.identity.model.emailaddress import EmailAddress |
|
37 |
from lp.soyuz.enums import ArchiveSubscriberStatus |
|
11403.1.4
by Henning Eggers
Reformatted imports using format-imports script r32. |
38 |
from lp.soyuz.interfaces.archiveauthtoken import IArchiveAuthTokenSet |
14550.1.1
by Steve Kowalik
Run format-imports over lib/lp and lib/canonical/launchpad |
39 |
from lp.soyuz.interfaces.archivesubscriber import IArchiveSubscriber |
11403.1.4
by Henning Eggers
Reformatted imports using format-imports script r32. |
40 |
from lp.soyuz.model.archiveauthtoken import ArchiveAuthToken |
7533.2.1
by Julian Edwards
ArchiveSubscriber content class, first attempt, no tests yet. |
41 |
|
42 |
||
43 |
class ArchiveSubscriber(Storm): |
|
44 |
"""See `IArchiveSubscriber`."""
|
|
45 |
implements(IArchiveSubscriber) |
|
46 |
__storm_table__ = 'ArchiveSubscriber' |
|
47 |
||
48 |
id = Int(primary=True) |
|
49 |
||
7844.4.7
by Michael Nelson
Added the ability to have ArchiveAuthTokens also returned when calling IArchiveSubscriber.getBySubscriber(). |
50 |
archive_id = Int(name='archive', allow_none=False) |
51 |
archive = Reference(archive_id, 'Archive.id') |
|
7533.2.1
by Julian Edwards
ArchiveSubscriber content class, first attempt, no tests yet. |
52 |
|
7844.4.7
by Michael Nelson
Added the ability to have ArchiveAuthTokens also returned when calling IArchiveSubscriber.getBySubscriber(). |
53 |
registrant_id = Int(name='registrant', allow_none=False) |
54 |
registrant = Reference(registrant_id, 'Person.id') |
|
7533.2.1
by Julian Edwards
ArchiveSubscriber content class, first attempt, no tests yet. |
55 |
|
56 |
date_created = DateTime( |
|
7533.2.9
by Julian Edwards
Review suggestions from Brad |
57 |
name='date_created', allow_none=False, tzinfo=pytz.UTC) |
7533.2.1
by Julian Edwards
ArchiveSubscriber content class, first attempt, no tests yet. |
58 |
|
8369.2.8
by Brad Crittenden
Allow private teams to own and subscribe to private PPAs |
59 |
subscriber_id = Int( |
7675.759.17
by Brad Crittenden
Reverted removal of validate_person. |
60 |
name='subscriber', allow_none=False, |
61 |
validator=validate_person) |
|
7844.4.7
by Michael Nelson
Added the ability to have ArchiveAuthTokens also returned when calling IArchiveSubscriber.getBySubscriber(). |
62 |
subscriber = Reference(subscriber_id, 'Person.id') |
7533.2.1
by Julian Edwards
ArchiveSubscriber content class, first attempt, no tests yet. |
63 |
|
64 |
date_expires = DateTime( |
|
7533.2.9
by Julian Edwards
Review suggestions from Brad |
65 |
name='date_expires', allow_none=True, tzinfo=pytz.UTC) |
7533.2.1
by Julian Edwards
ArchiveSubscriber content class, first attempt, no tests yet. |
66 |
|
7533.2.6
by Julian Edwards
Add doctest |
67 |
status = DBEnum( |
68 |
name='status', allow_none=False, |
|
69 |
enum=ArchiveSubscriberStatus) |
|
7533.2.1
by Julian Edwards
ArchiveSubscriber content class, first attempt, no tests yet. |
70 |
|
71 |
description = Unicode(name='description', allow_none=True) |
|
72 |
||
73 |
date_cancelled = DateTime( |
|
7533.2.9
by Julian Edwards
Review suggestions from Brad |
74 |
name='date_cancelled', allow_none=True, tzinfo=pytz.UTC) |
7533.2.1
by Julian Edwards
ArchiveSubscriber content class, first attempt, no tests yet. |
75 |
|
7844.4.7
by Michael Nelson
Added the ability to have ArchiveAuthTokens also returned when calling IArchiveSubscriber.getBySubscriber(). |
76 |
cancelled_by_id = Int(name='cancelled_by', allow_none=True) |
77 |
cancelled_by = Reference(cancelled_by_id, 'Person.id') |
|
7533.2.3
by Julian Edwards
Add IArchive.newSubscription, ArchiveSubscriberSet, zcml and security wrappers. |
78 |
|
7941.3.2
by Michael Nelson
Added IArchiveSubscriber.title. |
79 |
@property
|
8072.4.1
by Michael Nelson
Split previous branch, including only the browser code and 1 necessary new template here. |
80 |
def displayname(self): |
7941.3.2
by Michael Nelson
Added IArchiveSubscriber.title. |
81 |
"""See `IArchiveSubscriber`."""
|
8831.2.1
by Michael Nelson
Initial wording changes for mpt. |
82 |
return "%s's access to %s" % ( |
7976.2.4
by Celso Providelo
fixing new code. |
83 |
self.subscriber.displayname, self.archive.displayname) |
7941.3.2
by Michael Nelson
Added IArchiveSubscriber.title. |
84 |
|
7533.2.5
by Julian Edwards
Add cancellation method |
85 |
def cancel(self, cancelled_by): |
86 |
"""See `IArchiveSubscriber`."""
|
|
87 |
self.date_cancelled = UTC_NOW |
|
88 |
self.cancelled_by = cancelled_by |
|
7533.2.6
by Julian Edwards
Add doctest |
89 |
self.status = ArchiveSubscriberStatus.CANCELLED |
7533.2.5
by Julian Edwards
Add cancellation method |
90 |
|
8137.17.24
by Barry Warsaw
thread merge |
91 |
def getNonActiveSubscribers(self): |
92 |
"""See `IArchiveSubscriber`."""
|
|
93 |
# Imported here because of circular imports.
|
|
94 |
from lp.registry.model.person import Person |
|
95 |
||
96 |
store = Store.of(self) |
|
97 |
if self.subscriber.is_team: |
|
98 |
||
13677.4.1
by Danilo Segan
Refactor getNonActiveSubscribers into WITH query. |
99 |
# We get all the people who already have active tokens for
|
100 |
# this archive (for example, through separate subscriptions).
|
|
13677.4.2
by Danilo Segan
Refactor it some more to use LEFT JOIN instead. |
101 |
auth_token = LeftJoin( |
102 |
ArchiveAuthToken, |
|
103 |
And(ArchiveAuthToken.person_id == Person.id, |
|
104 |
ArchiveAuthToken.archive_id == self.archive_id, |
|
105 |
ArchiveAuthToken.date_deactivated == None)) |
|
106 |
||
107 |
team_participation = Join( |
|
108 |
TeamParticipation, |
|
109 |
TeamParticipation.personID == Person.id) |
|
110 |
||
13677.4.3
by Danilo Segan
Make getNonActiveSubscribers return preferred email addresses as well to cut down on the individual queries for each of them. |
111 |
# Only return people with preferred email address set.
|
112 |
preferred_email = Join( |
|
113 |
EmailAddress, EmailAddress.personID == Person.id) |
|
114 |
||
8137.17.24
by Barry Warsaw
thread merge |
115 |
# We want to get all participants who are themselves
|
116 |
# individuals, not teams:
|
|
13677.4.2
by Danilo Segan
Refactor it some more to use LEFT JOIN instead. |
117 |
non_active_subscribers = store.using( |
13677.4.3
by Danilo Segan
Make getNonActiveSubscribers return preferred email addresses as well to cut down on the individual queries for each of them. |
118 |
Person, team_participation, preferred_email, auth_token).find( |
119 |
(Person, EmailAddress), |
|
120 |
EmailAddress.status == EmailAddressStatus.PREFERRED, |
|
8137.17.24
by Barry Warsaw
thread merge |
121 |
TeamParticipation.teamID == self.subscriber_id, |
13677.4.1
by Danilo Segan
Refactor getNonActiveSubscribers into WITH query. |
122 |
Person.teamowner == None, |
13677.4.3
by Danilo Segan
Make getNonActiveSubscribers return preferred email addresses as well to cut down on the individual queries for each of them. |
123 |
# There is no existing archive auth token.
|
13677.4.2
by Danilo Segan
Refactor it some more to use LEFT JOIN instead. |
124 |
ArchiveAuthToken.person_id == None) |
8137.17.24
by Barry Warsaw
thread merge |
125 |
non_active_subscribers.order_by(Person.name) |
126 |
return non_active_subscribers |
|
127 |
else: |
|
8546.1.2
by Julian Edwards
Replace a count() with a .any() which is a bit more storm-like. |
128 |
# Subscriber is not a team.
|
8546.1.3
by Julian Edwards
noodles' review comments |
129 |
token_set = getUtility(IArchiveAuthTokenSet) |
130 |
if token_set.getActiveTokenForArchiveAndPerson( |
|
8546.1.4
by Julian Edwards
compare explicitly to None as per guidelines |
131 |
self.archive, self.subscriber) is not None: |
8546.1.1
by Julian Edwards
IArchiveSubscriber.getNonActiveSubscribers() now behaves properly when the subscriber is not a team. |
132 |
# There are active tokens, so return an empty result
|
133 |
# set.
|
|
134 |
return EmptyResultSet() |
|
135 |
||
136 |
# Otherwise return a result set containing only the
|
|
13677.4.3
by Danilo Segan
Make getNonActiveSubscribers return preferred email addresses as well to cut down on the individual queries for each of them. |
137 |
# subscriber and their preferred email address.
|
138 |
return store.find( |
|
139 |
(Person, EmailAddress), |
|
140 |
Person.id == self.subscriber_id, |
|
141 |
EmailAddress.personID == Person.id, |
|
142 |
EmailAddress.status == EmailAddressStatus.PREFERRED) |
|
8137.17.24
by Barry Warsaw
thread merge |
143 |
|
7533.2.3
by Julian Edwards
Add IArchive.newSubscription, ArchiveSubscriberSet, zcml and security wrappers. |
144 |
|
145 |
class ArchiveSubscriberSet: |
|
146 |
"""See `IArchiveSubscriberSet`."""
|
|
147 |
||
12965.4.2
by Abel Deuring
ArchiveSubscriberSet.getBySubscriber() and ArchiveSubscriberSet.getBySubscriberWithActiveToken() refactored |
148 |
def _getBySubscriber(self, subscriber, archive, current_only, |
149 |
with_active_tokens): |
|
150 |
"""Return all the subscriptions for a person.
|
|
7844.4.9
by Michael Nelson
Factored out getBySubscriberWithTokens() and created private methods to keep the queries DRY. |
151 |
|
12965.4.2
by Abel Deuring
ArchiveSubscriberSet.getBySubscriber() and ArchiveSubscriberSet.getBySubscriberWithActiveToken() refactored |
152 |
:param subscriber: An `IPerson` for whom to return all
|
153 |
`ArchiveSubscriber` records.
|
|
154 |
:param archive: An optional `IArchive` which restricts
|
|
155 |
the results to that particular archive.
|
|
156 |
:param current_only: Whether the result should only include current
|
|
157 |
subscriptions (which is the default).
|
|
158 |
:param with_active_tokens: Indicates whether the tokens for the given
|
|
159 |
subscribers subscriptions should be included in the resultset.
|
|
160 |
By default the tokens are not included in the resultset.
|
|
161 |
^ """
|
|
7844.4.9
by Michael Nelson
Factored out getBySubscriberWithTokens() and created private methods to keep the queries DRY. |
162 |
# Grab the extra Storm expressions, for this query,
|
163 |
# depending on the params:
|
|
164 |
extra_exprs = self._getExprsForSubscriptionQueries( |
|
165 |
archive, current_only) |
|
12965.4.2
by Abel Deuring
ArchiveSubscriberSet.getBySubscriber() and ArchiveSubscriberSet.getBySubscriberWithActiveToken() refactored |
166 |
origin = [ |
167 |
ArchiveSubscriber, |
|
168 |
Join( |
|
169 |
TeamParticipation, |
|
170 |
TeamParticipation.teamID == ArchiveSubscriber.subscriber_id)] |
|
171 |
||
172 |
if with_active_tokens: |
|
173 |
result_row = (ArchiveSubscriber, ArchiveAuthToken) |
|
174 |
# We need a left join with ArchiveSubscriber as
|
|
175 |
# the origin:
|
|
176 |
origin.append( |
|
177 |
LeftJoin( |
|
178 |
ArchiveAuthToken, |
|
179 |
And( |
|
180 |
ArchiveAuthToken.archive_id == |
|
181 |
ArchiveSubscriber.archive_id, |
|
182 |
ArchiveAuthToken.person_id == subscriber.id, |
|
183 |
ArchiveAuthToken.date_deactivated == None))) |
|
184 |
else: |
|
185 |
result_row = ArchiveSubscriber |
|
7844.4.9
by Michael Nelson
Factored out getBySubscriberWithTokens() and created private methods to keep the queries DRY. |
186 |
|
187 |
# Set the main expression to find all the subscriptions for
|
|
188 |
# which the subscriber is a direct subscriber OR is a member
|
|
189 |
# of a subscribed team.
|
|
190 |
# Note: the subscription to the owner itself will also be
|
|
191 |
# part of the join as there is a TeamParticipation entry
|
|
192 |
# showing that each person is a member of the "team" that
|
|
193 |
# consists of themselves.
|
|
194 |
store = Store.of(subscriber) |
|
12965.4.2
by Abel Deuring
ArchiveSubscriberSet.getBySubscriber() and ArchiveSubscriberSet.getBySubscriberWithActiveToken() refactored |
195 |
return store.using(*origin).find( |
196 |
result_row, |
|
12965.4.1
by Abel Deuring
remove a pointless inefficient subquery in ArchiveSubscriberSet.getBySubscriber() and ArchiveSubscriberSet.getBySubscriberWithActiveToken() |
197 |
TeamParticipation.personID == subscriber.id, |
7844.4.9
by Michael Nelson
Factored out getBySubscriberWithTokens() and created private methods to keep the queries DRY. |
198 |
*extra_exprs).order_by(Desc(ArchiveSubscriber.date_created)) |
199 |
||
12965.4.2
by Abel Deuring
ArchiveSubscriberSet.getBySubscriber() and ArchiveSubscriberSet.getBySubscriberWithActiveToken() refactored |
200 |
def getBySubscriber(self, subscriber, archive=None, current_only=True): |
201 |
"""See `IArchiveSubscriberSet`."""
|
|
202 |
return self._getBySubscriber(subscriber, archive, current_only, False) |
|
203 |
||
7862.1.6
by Michael Nelson
Changes after al-maisan's review. Mainly clarifying the names of methods. |
204 |
def getBySubscriberWithActiveToken(self, subscriber, archive=None): |
7844.4.9
by Michael Nelson
Factored out getBySubscriberWithTokens() and created private methods to keep the queries DRY. |
205 |
"""See `IArchiveSubscriberSet`."""
|
12965.4.2
by Abel Deuring
ArchiveSubscriberSet.getBySubscriber() and ArchiveSubscriberSet.getBySubscriberWithActiveToken() refactored |
206 |
return self._getBySubscriber(subscriber, archive, True, True) |
7844.4.9
by Michael Nelson
Factored out getBySubscriberWithTokens() and created private methods to keep the queries DRY. |
207 |
|
208 |
def getByArchive(self, archive, current_only=True): |
|
209 |
"""See `IArchiveSubscriberSet`."""
|
|
210 |
# Grab the extra Storm expressions, for this query,
|
|
211 |
# depending on the params:
|
|
212 |
extra_exprs = self._getExprsForSubscriptionQueries( |
|
213 |
archive, current_only) |
|
214 |
||
215 |
store = Store.of(archive) |
|
216 |
return store.find( |
|
217 |
ArchiveSubscriber, |
|
218 |
*extra_exprs).order_by(Desc(ArchiveSubscriber.date_created)) |
|
219 |
||
220 |
def _getExprsForSubscriptionQueries(self, archive=None, |
|
221 |
current_only=True): |
|
222 |
"""Return the Storm expressions required for the parameters.
|
|
223 |
||
224 |
Just to keep the code DRY.
|
|
225 |
"""
|
|
7660.9.4
by Michael Nelson
Changes after Celso's review. |
226 |
extra_exprs = [] |
7844.4.7
by Michael Nelson
Added the ability to have ArchiveAuthTokens also returned when calling IArchiveSubscriber.getBySubscriber(). |
227 |
|
228 |
# Restrict the results to the specified archive if requested:
|
|
7660.9.4
by Michael Nelson
Changes after Celso's review. |
229 |
if archive: |
230 |
extra_exprs.append(ArchiveSubscriber.archive == archive) |
|
231 |
||
7844.4.7
by Michael Nelson
Added the ability to have ArchiveAuthTokens also returned when calling IArchiveSubscriber.getBySubscriber(). |
232 |
# Restrict the results to only those subscriptions that are current
|
233 |
# if requested:
|
|
7844.4.5
by Michael Nelson
Renamed the ArchiveSubscriberStatus.ACTIVE to CURRENT. |
234 |
if current_only: |
7844.4.3
by Michael Nelson
Added default active_only param to IArchiveSubscriberSet.getBySubscriber(). |
235 |
extra_exprs.append( |
7844.4.5
by Michael Nelson
Renamed the ArchiveSubscriberStatus.ACTIVE to CURRENT. |
236 |
ArchiveSubscriber.status == ArchiveSubscriberStatus.CURRENT) |
7844.4.3
by Michael Nelson
Added default active_only param to IArchiveSubscriberSet.getBySubscriber(). |
237 |
|
7844.4.9
by Michael Nelson
Factored out getBySubscriberWithTokens() and created private methods to keep the queries DRY. |
238 |
return extra_exprs |