Merge ~cjwatson/launchpad:stormify-person into launchpad:master

Proposed by Colin Watson
Status: Merged
Approved by: Colin Watson
Approved revision: 589e261816f7bd622560f2293397fd1f2f2fddd3
Merge reported by: Otto Co-Pilot
Merged at revision: not available
Proposed branch: ~cjwatson/launchpad:stormify-person
Merge into: launchpad:master
Diff against target: 976 lines (+188/-171)
14 files modified
lib/lp/bugs/model/bugtasksearch.py (+32/-36)
lib/lp/registry/browser/person.py (+1/-1)
lib/lp/registry/doc/person-merge.rst (+4/-0)
lib/lp/registry/doc/vocabularies.rst (+0/-1)
lib/lp/registry/interfaces/person.py (+5/-5)
lib/lp/registry/model/distroseries.py (+1/-1)
lib/lp/registry/model/person.py (+124/-103)
lib/lp/registry/personmerge.py (+1/-2)
lib/lp/registry/scripts/closeaccount.py (+1/-1)
lib/lp/registry/vocabularies.py (+14/-16)
lib/lp/translations/model/poexportrequest.py (+1/-1)
lib/lp/translations/model/pofile.py (+1/-1)
lib/lp/translations/model/translationgroup.py (+1/-1)
lib/lp/translations/scripts/remove_translations.py (+2/-2)
Reviewer Review Type Date Requested Status
Simone Pelosi Approve
Review via email: mp+451654@code.launchpad.net

Commit message

Convert Person to Storm

To post a comment you must log in.
Revision history for this message
Simone Pelosi (pelpsi) wrote :

LGTM!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/lib/lp/bugs/model/bugtasksearch.py b/lib/lp/bugs/model/bugtasksearch.py
2index 8de8c55..08fe8dc 100644
3--- a/lib/lp/bugs/model/bugtasksearch.py
4+++ b/lib/lp/bugs/model/bugtasksearch.py
5@@ -29,8 +29,9 @@ from storm.expr import (
6 Row,
7 Select,
8 Union,
9+ With,
10 )
11-from storm.info import ClassAlias
12+from storm.info import ClassAlias, get_cls_info
13 from storm.references import Reference
14 from zope.component import getUtility
15 from zope.security.proxy import isinstance as zope_isinstance
16@@ -77,10 +78,6 @@ from lp.registry.model.teammembership import TeamParticipation
17 from lp.services.database.bulk import load
18 from lp.services.database.decoratedresultset import DecoratedResultSet
19 from lp.services.database.interfaces import IStore
20-from lp.services.database.sqlbase import (
21- convert_storm_clause_to_string,
22- sqlvalues,
23-)
24 from lp.services.database.stormexpr import (
25 ArrayAgg,
26 ArrayIntersects,
27@@ -224,10 +221,10 @@ def search_bugs(pre_iter_hook, alternatives, just_bug_ids=False):
28 clauseTables,
29 bugtask_decorator,
30 join_tables,
31- with_clause,
32+ with_clauses,
33 ] = _build_query(alternatives[0])
34- if with_clause:
35- store = store.with_(with_clause)
36+ if with_clauses:
37+ store = store.with_(with_clauses)
38 decorators.append(bugtask_decorator)
39 origin = _build_origin(
40 join_tables + orderby_joins, clauseTables, start
41@@ -242,12 +239,12 @@ def search_bugs(pre_iter_hook, alternatives, just_bug_ids=False):
42 clauseTables,
43 decorator,
44 join_tables,
45- with_clause,
46+ with_clauses,
47 ] = _build_query(params)
48 origin = _build_origin(join_tables, clauseTables, start)
49 localstore = store
50- if with_clause:
51- localstore = store.with_(with_clause)
52+ if with_clauses:
53+ localstore = store.with_(with_clauses)
54 next_result = localstore.using(*origin).find(BugTaskFlat, query)
55 results.append(next_result)
56 # NB: assumes the decorators are all compatible.
57@@ -492,9 +489,16 @@ def _build_query(params):
58
59 if params.structural_subscriber is not None:
60 with_clauses.append(
61- """ss as (SELECT * from StructuralSubscription
62- WHERE StructuralSubscription.subscriber = %s)"""
63- % sqlvalues(params.structural_subscriber)
64+ With(
65+ "ss",
66+ Select(
67+ get_cls_info(StructuralSubscription).columns,
68+ where=(
69+ StructuralSubscription.subscriber
70+ == params.structural_subscriber
71+ ),
72+ ),
73+ )
74 )
75
76 class StructuralSubscriptionCTE(StructuralSubscription):
77@@ -761,27 +765,23 @@ def _build_query(params):
78 )
79 store = IStore(Bug)
80 with_clauses.append(
81- convert_storm_clause_to_string(
82- WithMaterialized(
83- "commented_bug_ids",
84- store,
85- Union(commented_messages, commented_activities),
86- )
87+ WithMaterialized(
88+ "commented_bug_ids",
89+ store,
90+ Union(commented_messages, commented_activities),
91 )
92 )
93 with_clauses.append(
94- convert_storm_clause_to_string(
95- WithMaterialized(
96- "commented_bugtask_ids",
97- store,
98- Select(
99- BugTaskFlat.bugtask_id,
100- tables=[BugTaskFlat],
101- where=BugTaskFlat.bug_id.is_in(
102- Select(Column("bug", "commented_bug_ids"))
103- ),
104+ WithMaterialized(
105+ "commented_bugtask_ids",
106+ store,
107+ Select(
108+ BugTaskFlat.bugtask_id,
109+ tables=[BugTaskFlat],
110+ where=BugTaskFlat.bug_id.is_in(
111+ Select(Column("bug", "commented_bug_ids"))
112 ),
113- )
114+ ),
115 )
116 )
117 extra_clauses.append(
118@@ -921,11 +921,7 @@ def _build_query(params):
119 obj = decor(obj)
120 return obj
121
122- if with_clauses:
123- with_clause = SQL(", ".join(with_clauses))
124- else:
125- with_clause = None
126- return (query, clauseTables, decorator, join_tables, with_clause)
127+ return (query, clauseTables, decorator, join_tables, with_clauses)
128
129
130 def _process_order_by(params):
131diff --git a/lib/lp/registry/browser/person.py b/lib/lp/registry/browser/person.py
132index d14004b..4b4e920 100644
133--- a/lib/lp/registry/browser/person.py
134+++ b/lib/lp/registry/browser/person.py
135@@ -2099,7 +2099,7 @@ class PersonParticipationView(LaunchpadView):
136 # The member is a direct member; use the membership data.
137 datejoined = membership.datejoined
138 dateexpires = membership.dateexpires
139- if membership.person_id == team.teamownerID:
140+ if membership.person_id == team.teamowner_id:
141 role = "Owner"
142 elif membership.status == TeamMembershipStatus.ADMIN:
143 role = "Admin"
144diff --git a/lib/lp/registry/doc/person-merge.rst b/lib/lp/registry/doc/person-merge.rst
145index f50b89c..65d98e4 100644
146--- a/lib/lp/registry/doc/person-merge.rst
147+++ b/lib/lp/registry/doc/person-merge.rst
148@@ -278,6 +278,7 @@ create, and then delete, the needed two people.
149
150 >>> from lp.registry.model.person import PersonSet, Person
151 >>> from lp.registry.interfaces.person import PersonCreationRationale
152+ >>> from lp.services.database.interfaces import IStore
153 >>> personset = PersonSet()
154
155 >>> skip = []
156@@ -312,11 +313,14 @@ create, and then delete, the needed two people.
157 ... display_name="Merge Winner",
158 ... creation_rationale=lp,
159 ... )
160+ ... IStore(Person).add(winner)
161 ... loser = Person(
162 ... name=name + ".loser",
163 ... display_name="Merge Loser",
164 ... creation_rationale=lp,
165 ... )
166+ ... IStore(Person).add(loser)
167+ ... IStore(Person).flush()
168 ... yield winner, loser
169 ...
170 >>> endless_supply_of_players = new_players()
171diff --git a/lib/lp/registry/doc/vocabularies.rst b/lib/lp/registry/doc/vocabularies.rst
172index ad6bf4b..ab7f5c5 100644
173--- a/lib/lp/registry/doc/vocabularies.rst
174+++ b/lib/lp/registry/doc/vocabularies.rst
175@@ -611,7 +611,6 @@ Any person that's already merged is not part of this vocabulary:
176
177 >>> naked_cprov = removeSecurityProxy(cprov)
178 >>> naked_cprov.merged = 1
179- >>> naked_cprov.syncUpdate()
180 >>> cprov in vocab
181 False
182
183diff --git a/lib/lp/registry/interfaces/person.py b/lib/lp/registry/interfaces/person.py
184index fe77d12..4e66cfd 100644
185--- a/lib/lp/registry/interfaces/person.py
186+++ b/lib/lp/registry/interfaces/person.py
187@@ -220,7 +220,7 @@ def validate_membership_policy(obj, attr, value):
188 return None
189
190 # If we are just creating a new team, it can have any membership policy.
191- if getattr(obj, "_SO_creating", True):
192+ if getattr(obj, "_creating", True):
193 return value
194
195 team = obj
196@@ -792,7 +792,7 @@ class IPersonLimitedView(IHasIcon, IHasLogo):
197 "in listings of bugs or on a person's membership table."
198 ),
199 )
200- iconID = Int(title=_("Icon ID"), required=True, readonly=True)
201+ icon_id = Int(title=_("Icon ID"), required=True, readonly=True)
202 logo = exported(
203 LogoImageUpload(
204 title=_("Logo"),
205@@ -806,7 +806,7 @@ class IPersonLimitedView(IHasIcon, IHasLogo):
206 ),
207 )
208 )
209- logoID = Int(title=_("Logo ID"), required=True, readonly=True)
210+ logo_id = Int(title=_("Logo ID"), required=True, readonly=True)
211 # title is required for the Launchpad Page Layout main template
212 title = Attribute("Person Page Title")
213 is_probationary = exported(
214@@ -890,7 +890,7 @@ class IPersonViewRestricted(
215 ),
216 )
217 )
218- mugshotID = Int(title=_("Mugshot ID"), required=True, readonly=True)
219+ mugshot_id = Int(title=_("Mugshot ID"), required=True, readonly=True)
220
221 languages = exported(
222 CollectionField(
223@@ -1111,7 +1111,7 @@ class IPersonViewRestricted(
224 ),
225 exported_as="team_owner",
226 )
227- teamownerID = Int(
228+ teamowner_id = Int(
229 title=_("The Team Owner's ID or None"), required=False, readonly=True
230 )
231 preferredemail = exported(
232diff --git a/lib/lp/registry/model/distroseries.py b/lib/lp/registry/model/distroseries.py
233index 22d2895..4119824 100644
234--- a/lib/lp/registry/model/distroseries.py
235+++ b/lib/lp/registry/model/distroseries.py
236@@ -1580,7 +1580,7 @@ class DistroSeries(
237 POTemplate.distroseries == self,
238 POTemplate.iscurrent == True,
239 )
240- contributors = contributors.order_by(*Person._storm_sortingColumns)
241+ contributors = contributors.order_by(*Person._sortingColumns)
242 contributors = contributors.config(distinct=True)
243 return contributors
244
245diff --git a/lib/lp/registry/model/person.py b/lib/lp/registry/model/person.py
246index 81f4187..dcf118e 100644
247--- a/lib/lp/registry/model/person.py
248+++ b/lib/lp/registry/model/person.py
249@@ -62,7 +62,7 @@ from storm.expr import (
250 With,
251 )
252 from storm.info import ClassAlias
253-from storm.locals import Int, Reference, ReferenceSet, Unicode
254+from storm.locals import Bool, DateTime, Int, Reference, ReferenceSet, Unicode
255 from storm.store import EmptyResultSet, Store
256 from twisted.conch.ssh.common import getNS
257 from twisted.conch.ssh.keys import Key
258@@ -193,24 +193,16 @@ from lp.registry.model.teammembership import (
259 )
260 from lp.services.config import config
261 from lp.services.database import bulk, postgresql
262-from lp.services.database.constants import UTC_NOW
263-from lp.services.database.datetimecol import UtcDateTimeCol
264+from lp.services.database.constants import DEFAULT, UTC_NOW
265 from lp.services.database.decoratedresultset import DecoratedResultSet
266 from lp.services.database.enumcol import DBEnum
267 from lp.services.database.interfaces import IStore
268 from lp.services.database.policy import PrimaryDatabasePolicy
269 from lp.services.database.sqlbase import (
270- SQLBase,
271 convert_storm_clause_to_string,
272 cursor,
273 sqlvalues,
274 )
275-from lp.services.database.sqlobject import (
276- BoolCol,
277- ForeignKey,
278- IntCol,
279- StringCol,
280-)
281 from lp.services.database.stormbase import StormBase
282 from lp.services.database.stormexpr import WithMaterialized, fti_search
283 from lp.services.helpers import backslashreplace, shortlist
284@@ -282,7 +274,7 @@ class TeamInvitationEvent:
285 self.team = team
286
287
288-class ValidPersonCache(SQLBase):
289+class ValidPersonCache(StormBase):
290 """Flags if a Person is active and usable in Launchpad.
291
292 This is readonly, as this is a view in the database.
293@@ -294,6 +286,10 @@ class ValidPersonCache(SQLBase):
294 corroborating information.
295 """
296
297+ __storm_table__ = "ValidPersonCache"
298+
299+ id = Int(primary=True)
300+
301
302 def validate_person_visibility(person, attr, value):
303 """Validate changes in visibility.
304@@ -355,14 +351,14 @@ class PersonSettings(StormBase):
305
306 __storm_table__ = "PersonSettings"
307
308- personID = Int("person", default=None, primary=True)
309- person = Reference(personID, "Person.id")
310+ person_id = Int("person", default=None, primary=True)
311+ person = Reference(person_id, "Person.id")
312
313- selfgenerated_bugnotifications = BoolCol(notNull=True, default=False)
314+ selfgenerated_bugnotifications = Bool(allow_none=False, default=False)
315
316- expanded_notification_footers = BoolCol(notNull=False, default=False)
317+ expanded_notification_footers = Bool(allow_none=True, default=False)
318
319- require_strong_email_authentication = BoolCol(notNull=False, default=False)
320+ require_strong_email_authentication = Bool(allow_none=True, default=False)
321
322
323 def readonly_settings(message, interface):
324@@ -420,7 +416,7 @@ _readonly_person_settings = readonly_settings(
325 @implementer(IPerson)
326 @delegate_to(IPersonSettings, context="_person_settings")
327 class Person(
328- SQLBase,
329+ StormBase,
330 HasBugsBase,
331 HasSpecificationsMixin,
332 HasTranslationImportsMixin,
333@@ -431,14 +427,54 @@ class Person(
334 ):
335 """A Person."""
336
337- def __init__(self, *args, **kwargs):
338- super().__init__(*args, **kwargs)
339- # Initialize our PersonSettings object/record.
340+ __storm_table__ = "Person"
341+
342+ id = Int(primary=True)
343+
344+ _creating = False
345+
346+ def __init__(
347+ self,
348+ name,
349+ display_name,
350+ account=None,
351+ teamowner=None,
352+ description=None,
353+ membership_policy=DEFAULT,
354+ defaultrenewalperiod=None,
355+ defaultmembershipperiod=None,
356+ creation_rationale=None,
357+ creation_comment=None,
358+ registrant=None,
359+ hide_email_addresses=False,
360+ ):
361+ super().__init__()
362+ self._creating = True
363+ self.name = name
364+ self.display_name = display_name
365+ self.account = account
366+ self.teamowner = teamowner
367+ self.description = description
368+ self.membership_policy = membership_policy
369+ self.defaultrenewalperiod = defaultrenewalperiod
370+ self.defaultmembershipperiod = defaultmembershipperiod
371+ self.creation_rationale = creation_rationale
372+ self.creation_comment = creation_comment
373+ self.registrant = registrant
374+ self.hide_email_addresses = hide_email_addresses
375 if not self.is_team:
376- # This is a Person, not a team. Teams may want a TeamSettings
377- # in the future.
378+ # Initialize our PersonSettings object/record. This is a
379+ # Person, not a team. Teams may want a TeamSettings in the
380+ # future.
381 settings = PersonSettings()
382 settings.person = self
383+ self.__storm_loaded__()
384+ del self._creating
385+
386+ def __storm_loaded__(self):
387+ """Mark the person as a team when created or fetched from database."""
388+ if self.is_team:
389+ alsoProvides(self, ITeam)
390
391 @cachedproperty
392 def _person_settings(self):
393@@ -462,13 +498,11 @@ class Person(
394 return self.id
395
396 sortingColumns = SQL("person_sort_key(Person.displayname, Person.name)")
397- # Redefine the default ordering into Storm syntax.
398- _storm_sortingColumns = ("Person.displayname", "Person.name")
399 # When doing any sort of set operations (union, intersect, except_) with
400- # SQLObject we can't use sortingColumns because the table name Person is
401- # not available in that context, so we use this one.
402+ # Storm we can't use sortingColumns because the table name Person is not
403+ # available in that context, so we use this one.
404 _sortingColumnsForSetOperations = SQL("person_sort_key(displayname, name)")
405- _defaultOrder = sortingColumns
406+ __storm_order__ = sortingColumns
407 _visibility_warning_cache_key = None
408 _visibility_warning_cache = None
409
410@@ -490,7 +524,7 @@ class Person(
411 else:
412 mailing_list = getUtility(IMailingListSet).get(self.name)
413 can_rename = (
414- self._SO_creating
415+ self._creating
416 or not self.is_team
417 or mailing_list is None
418 or mailing_list.status == MailingListStatus.PURGED
419@@ -499,35 +533,27 @@ class Person(
420 # Everything's okay, so let SQLObject do the normal thing.
421 return value
422
423- name = StringCol(
424- dbName="name",
425- alternateID=True,
426- notNull=True,
427- storm_validator=_validate_name,
428- )
429+ name = Unicode(name="name", allow_none=False, validator=_validate_name)
430
431 def __repr__(self):
432 displayname = backslashreplace(self.displayname)
433 return "<Person %s (%s)>" % (self.name, displayname)
434
435- display_name = StringCol(dbName="displayname", notNull=True)
436+ display_name = Unicode(name="displayname", allow_none=False)
437
438 @property
439 def displayname(self):
440 return self.display_name
441
442- teamdescription = StringCol(dbName="teamdescription", default=None)
443- homepage_content = StringCol(default=None)
444- _description = StringCol(dbName="description", default=None)
445- icon = ForeignKey(
446- dbName="icon", foreignKey="LibraryFileAlias", default=None
447- )
448- logo = ForeignKey(
449- dbName="logo", foreignKey="LibraryFileAlias", default=None
450- )
451- mugshot = ForeignKey(
452- dbName="mugshot", foreignKey="LibraryFileAlias", default=None
453- )
454+ teamdescription = Unicode(name="teamdescription", default=None)
455+ homepage_content = Unicode(default=None)
456+ _description = Unicode(name="description", default=None)
457+ icon_id = Int(name="icon", allow_none=True, default=None)
458+ icon = Reference(icon_id, "LibraryFileAlias.id")
459+ logo_id = Int(name="logo", allow_none=True, default=None)
460+ logo = Reference(logo_id, "LibraryFileAlias.id")
461+ mugshot_id = Int(name="mugshot", allow_none=True, default=None)
462+ mugshot = Reference(mugshot_id, "LibraryFileAlias.id")
463
464 @property
465 def account_status(self):
466@@ -546,12 +572,13 @@ class Person(
467 raise NoAccountError()
468 self.account.setStatus(status, user, comment)
469
470- teamowner = ForeignKey(
471- dbName="teamowner",
472- foreignKey="Person",
473+ teamowner_id = Int(
474+ name="teamowner",
475+ validator=validate_public_person,
476+ allow_none=True,
477 default=None,
478- storm_validator=validate_public_person,
479 )
480+ teamowner = Reference(teamowner_id, "Person.id")
481
482 sshkeys = ReferenceSet("id", "SSHKey.person_id")
483
484@@ -565,30 +592,32 @@ class Person(
485 default=TeamMembershipPolicy.RESTRICTED,
486 validator=validate_membership_policy,
487 )
488- defaultrenewalperiod = IntCol(dbName="defaultrenewalperiod", default=None)
489- defaultmembershipperiod = IntCol(
490- dbName="defaultmembershipperiod", default=None
491- )
492+ defaultrenewalperiod = Int(name="defaultrenewalperiod", default=None)
493+ defaultmembershipperiod = Int(name="defaultmembershipperiod", default=None)
494 mailing_list_auto_subscribe_policy = DBEnum(
495 enum=MailingListAutoSubscribePolicy,
496 default=MailingListAutoSubscribePolicy.ON_REGISTRATION,
497 )
498
499- merged = ForeignKey(dbName="merged", foreignKey="Person", default=None)
500+ merged_id = Int(name="merged", allow_none=True, default=None)
501+ merged = Reference(merged_id, "Person.id")
502
503- datecreated = UtcDateTimeCol(notNull=True, default=UTC_NOW)
504+ datecreated = DateTime(
505+ allow_none=False, default=UTC_NOW, tzinfo=timezone.utc
506+ )
507 creation_rationale = DBEnum(enum=PersonCreationRationale, default=None)
508- creation_comment = StringCol(default=None)
509- registrant = ForeignKey(
510- dbName="registrant",
511- foreignKey="Person",
512+ creation_comment = Unicode(default=None)
513+ registrant_id = Int(
514+ name="registrant",
515+ validator=validate_public_person,
516+ allow_none=True,
517 default=None,
518- storm_validator=validate_public_person,
519 )
520- hide_email_addresses = BoolCol(notNull=True, default=False)
521- verbose_bugnotifications = BoolCol(notNull=True, default=True)
522+ registrant = Reference(registrant_id, "Person.id")
523+ hide_email_addresses = Bool(allow_none=False, default=False)
524+ verbose_bugnotifications = Bool(allow_none=False, default=True)
525
526- signedcocs = ReferenceSet("<primary key>", "SignedCodeOfConduct.owner_id")
527+ signedcocs = ReferenceSet("id", "SignedCodeOfConduct.owner_id")
528 _ircnicknames = ReferenceSet("id", "IrcID.person_id")
529 jabberids = ReferenceSet("id", "JabberID.person_id")
530
531@@ -604,7 +633,7 @@ class Person(
532 allow_none=False,
533 )
534
535- personal_standing_reason = StringCol(default=None)
536+ personal_standing_reason = Unicode(default=None)
537
538 @property
539 def description(self):
540@@ -703,12 +732,6 @@ class Person(
541 person_language.delete()
542 self.deleteLanguagesCache()
543
544- def _init(self, *args, **kw):
545- """Mark the person as a team when created or fetched from database."""
546- SQLBase._init(self, *args, **kw)
547- if self.teamownerID is not None:
548- alsoProvides(self, ITeam)
549-
550 def convertToTeam(self, team_owner):
551 """See `IPerson`."""
552 if self.is_team:
553@@ -1009,7 +1032,7 @@ class Person(
554 @property
555 def is_team(self):
556 """See `IPerson`."""
557- return self.teamownerID is not None
558+ return self.teamowner_id is not None
559
560 @property
561 def mailing_list(self):
562@@ -1117,7 +1140,7 @@ class Person(
563 OR product.bug_supervisor = %(person)s
564 )
565 """ % sqlvalues(
566- person=self
567+ person=self.id
568 )
569
570 return "%s AND (%s)" % (
571@@ -1157,7 +1180,7 @@ class Person(
572 ) _pillar
573 ON PillarName.name = _pillar.name
574 """
575- % sqlvalues(person=self)
576+ % sqlvalues(person=self.id)
577 )
578
579 results = IStore(self).using(SQL(origin)).find(find_spec)
580@@ -1260,7 +1283,6 @@ class Person(
581 CommercialSubscription,
582 )
583 from lp.registry.model.distribution import Distribution
584- from lp.registry.model.person import Person
585 from lp.registry.model.product import Product
586 from lp.registry.model.teammembership import TeamParticipation
587
588@@ -1599,7 +1621,6 @@ class Person(
589 def getAssignedSpecificationWorkItemsDueBefore(self, date, user):
590 """See `IPerson`."""
591 from lp.registry.model.distribution import Distribution
592- from lp.registry.model.person import Person
593 from lp.registry.model.product import Product
594
595 store = Store.of(self)
596@@ -1811,7 +1832,7 @@ class Person(
597 And(
598 TeamParticipation.team_id == self.id,
599 TeamParticipation.person_id != self.id,
600- Person.teamownerID != None,
601+ IsNot(Person.teamowner_id, None),
602 ),
603 need_api=True,
604 )
605@@ -2061,7 +2082,7 @@ class Person(
606 Select(
607 Person.id,
608 tables=[Person],
609- where=Person.teamownerID.is_in(team_select),
610+ where=Person.teamowner_id.is_in(team_select),
611 ),
612 Select(
613 TeamMembership.team_id,
614@@ -2942,7 +2963,7 @@ class Person(
615 Person,
616 Person.id == TeamParticipation.team_id,
617 TeamParticipation.person == self,
618- IsNot(Person.teamownerID, None),
619+ IsNot(Person.teamowner_id, None),
620 )
621 .order_by(Person.sortingColumns)
622 )
623@@ -2950,11 +2971,10 @@ class Person(
624 @property
625 def teams_indirectly_participated_in(self):
626 """See `IPerson`."""
627- Team = ClassAlias(Person, "Team")
628 store = Store.of(self)
629 origin = [
630- Team,
631- Join(TeamParticipation, Team.id == TeamParticipation.team_id),
632+ Person,
633+ Join(TeamParticipation, Person.id == TeamParticipation.team_id),
634 LeftJoin(
635 TeamMembership,
636 And(
637@@ -2969,9 +2989,8 @@ class Person(
638 ),
639 ),
640 ]
641- find_objects = Team
642 return store.using(*origin).find(
643- find_objects,
644+ Person,
645 And(
646 TeamParticipation.person == self.id,
647 TeamParticipation.person != TeamParticipation.team_id,
648@@ -2988,8 +3007,8 @@ class Person(
649 Person,
650 Person.id == TeamParticipation.team_id,
651 TeamParticipation.person == self,
652- IsNot(Person.teamownerID, None),
653- IsNot(Person.iconID, None),
654+ IsNot(Person.teamowner_id, None),
655+ IsNot(Person.icon_id, None),
656 TeamParticipation.team != self,
657 )
658 .order_by(Person.sortingColumns)
659@@ -4155,6 +4174,9 @@ class PersonSet:
660 defaultrenewalperiod=defaultrenewalperiod,
661 membership_policy=membership_policy,
662 )
663+ store = IStore(Person)
664+ store.add(team)
665+ store.flush()
666 notify(ObjectCreatedEvent(team))
667 # Here we add the owner as a team admin manually because we know what
668 # we're doing (so we don't need to do any sanity checks) and we don't
669@@ -4267,19 +4289,18 @@ class PersonSet:
670 if not displayname:
671 displayname = name.capitalize()
672
673- if account is None:
674- account_id = None
675- else:
676- account_id = account.id
677 person = Person(
678 name=name,
679 display_name=displayname,
680- account_id=account_id,
681+ account=account,
682 creation_rationale=rationale,
683 creation_comment=comment,
684 hide_email_addresses=hide_email_addresses,
685 registrant=registrant,
686 )
687+ store = IStore(Person)
688+ store.add(person)
689+ store.flush()
690 return person
691
692 def ensurePerson(
693@@ -4309,7 +4330,7 @@ class PersonSet:
694 """See `IPersonSet`."""
695 clauses = [Person.name == name]
696 if ignore_merged:
697- clauses.append(Is(Person.mergedID, None))
698+ clauses.append(Is(Person.merged_id, None))
699 return IStore(Person).find(Person, *clauses).one()
700
701 def getByAccount(self, account):
702@@ -4323,8 +4344,8 @@ class PersonSet:
703 IStore(Person)
704 .find(
705 Person,
706- Is(Person.teamownerID, None),
707- Is(Person.mergedID, None),
708+ Is(Person.teamowner_id, None),
709+ Is(Person.merged_id, None),
710 )
711 .count()
712 )
713@@ -4334,8 +4355,8 @@ class PersonSet:
714 IStore(Person)
715 .find(
716 Person,
717- IsNot(Person.teamownerID, None),
718- Is(Person.mergedID, None),
719+ IsNot(Person.teamowner_id, None),
720+ Is(Person.merged_id, None),
721 )
722 .count()
723 )
724@@ -4601,15 +4622,15 @@ class PersonSet:
725 """See `IPersonSet`."""
726 aliases = []
727 aliases.extend(
728- person.iconID for person in people if person.iconID is not None
729+ person.icon_id for person in people if person.icon_id is not None
730 )
731 aliases.extend(
732- person.logoID for person in people if person.logoID is not None
733+ person.logo_id for person in people if person.logo_id is not None
734 )
735 aliases.extend(
736- person.mugshotID
737+ person.mugshot_id
738 for person in people
739- if person.mugshotID is not None
740+ if person.mugshot_id is not None
741 )
742 if not aliases:
743 return
744@@ -4805,7 +4826,7 @@ class PersonSet:
745
746 def preload_for_people(rows):
747 if need_teamowner or need_api:
748- bulk.load(Person, [row[0].teamownerID for row in rows])
749+ bulk.load(Person, [row[0].teamowner_id for row in rows])
750
751 def prepopulate_person(row):
752 result = row[0]
753@@ -5559,7 +5580,7 @@ def _get_recipients_for_team(team):
754 EmailAddress.person != None,
755 Account.status == AccountStatus.ACTIVE,
756 ),
757- Person.teamownerID != None,
758+ IsNot(Person.teamowner_id, None),
759 ),
760 ).config(distinct=True)
761 next_ids = []
762diff --git a/lib/lp/registry/personmerge.py b/lib/lp/registry/personmerge.py
763index cec406c..2787bc2 100644
764--- a/lib/lp/registry/personmerge.py
765+++ b/lib/lp/registry/personmerge.py
766@@ -1230,8 +1230,7 @@ def merge_people(from_person, to_person, reviewer, delete=False):
767 cur.execute("SELECT id FROM Person WHERE name = %s" % sqlvalues(name))
768 i += 1
769 cur.execute(
770- "UPDATE Person SET name = %s WHERE id = %s"
771- % sqlvalues(name, from_person)
772+ "UPDATE Person SET name = %s WHERE id = %s" % sqlvalues(name, from_id)
773 )
774
775 # Since we've updated the database behind Storm's back,
776diff --git a/lib/lp/registry/scripts/closeaccount.py b/lib/lp/registry/scripts/closeaccount.py
777index c7d4c55..7df78d0 100644
778--- a/lib/lp/registry/scripts/closeaccount.py
779+++ b/lib/lp/registry/scripts/closeaccount.py
780@@ -236,7 +236,7 @@ def close_account(username, log):
781 # Keep the corresponding PersonSettings row, but reset everything to the
782 # defaults.
783 table_notification("PersonSettings")
784- store.find(PersonSettings, PersonSettings.personID == person.id).set(
785+ store.find(PersonSettings, PersonSettings.person == person).set(
786 selfgenerated_bugnotifications=DEFAULT,
787 # XXX cjwatson 2018-11-29: These two columns have NULL defaults, but
788 # perhaps shouldn't?
789diff --git a/lib/lp/registry/vocabularies.py b/lib/lp/registry/vocabularies.py
790index 56453ca..0104a33 100644
791--- a/lib/lp/registry/vocabularies.py
792+++ b/lib/lp/registry/vocabularies.py
793@@ -71,6 +71,8 @@ from storm.expr import (
794 And,
795 Column,
796 Desc,
797+ Is,
798+ IsNot,
799 Join,
800 LeftJoin,
801 Not,
802@@ -176,7 +178,6 @@ from lp.services.webapp.vocabulary import (
803 IHugeVocabulary,
804 NamedStormHugeVocabulary,
805 NamedStormVocabulary,
806- SQLObjectVocabularyBase,
807 StormVocabularyBase,
808 VocabularyFilter,
809 )
810@@ -208,7 +209,6 @@ class BasePersonVocabulary:
811 If the token contains an '@', treat it like an email. Otherwise,
812 treat it like a name.
813 """
814- token = six.ensure_text(token)
815 if "@" in token:
816 # This looks like an email token, so let's do an object
817 # lookup based on that.
818@@ -369,11 +369,11 @@ def project_products_vocabulary_factory(context):
819 )
820
821
822-class UserTeamsParticipationVocabulary(SQLObjectVocabularyBase):
823+class UserTeamsParticipationVocabulary(StormVocabularyBase):
824 """Describes the public teams in which the current user participates."""
825
826 _table = Person
827- _orderBy = "display_name"
828+ _order_by = "display_name"
829
830 INCLUDE_PRIVATE_TEAM = False
831
832@@ -401,7 +401,7 @@ class UserTeamsParticipationVocabulary(SQLObjectVocabularyBase):
833 teams = list(
834 IStore(Person)
835 .find(Person, *clauses)
836- .order_by(Person._storm_sortingColumns)
837+ .order_by(Person._sortingColumns)
838 )
839 # Users can view all the teams they belong to.
840 precache_permission_for_objects(
841@@ -428,7 +428,7 @@ class UserTeamsParticipationVocabulary(SQLObjectVocabularyBase):
842
843 @implementer(IHugeVocabulary)
844 class NonMergedPeopleAndTeamsVocabulary(
845- BasePersonVocabulary, SQLObjectVocabularyBase
846+ BasePersonVocabulary, StormVocabularyBase
847 ):
848 """The set of all non-merged people and teams.
849
850@@ -437,7 +437,7 @@ class NonMergedPeopleAndTeamsVocabulary(
851 a preferred email address, that is, unvalidated person profiles.
852 """
853
854- _orderBy = ["display_name"]
855+ _order_by = ["display_name"]
856 displayname = "Select a Person or Team"
857 step_title = "Search"
858
859@@ -449,7 +449,7 @@ class NonMergedPeopleAndTeamsVocabulary(
860 return getUtility(IPersonSet).find(text)
861
862 def search(self, text, vocab_filter=None):
863- """See `SQLObjectVocabularyBase`.
864+ """See `StormVocabularyBase`.
865
866 Return people/teams whose fti or email address match :text.
867 """
868@@ -461,7 +461,7 @@ class NonMergedPeopleAndTeamsVocabulary(
869
870 @implementer(IHugeVocabulary)
871 class PersonAccountToMergeVocabulary(
872- BasePersonVocabulary, SQLObjectVocabularyBase
873+ BasePersonVocabulary, StormVocabularyBase
874 ):
875 """The set of all non-merged people with at least one email address.
876
877@@ -469,7 +469,7 @@ class PersonAccountToMergeVocabulary(
878 accounts to merge. You *don't* want to use it.
879 """
880
881- _orderBy = ["display_name"]
882+ _order_by = ["display_name"]
883 displayname = "Select a Person to Merge"
884 step_title = "Search"
885 must_have_email = True
886@@ -486,7 +486,7 @@ class PersonAccountToMergeVocabulary(
887 )
888
889 def search(self, text, vocab_filter=None):
890- """See `SQLObjectVocabularyBase`.
891+ """See `StormVocabularyBase`.
892
893 Return people whose fti or email address match :text.
894 """
895@@ -516,7 +516,7 @@ class VocabularyFilterPerson(VocabularyFilter):
896
897 @property
898 def filter_terms(self):
899- return [Person.teamownerID == None]
900+ return [Is(Person.teamowner_id, None)]
901
902
903 class VocabularyFilterTeam(VocabularyFilter):
904@@ -529,13 +529,11 @@ class VocabularyFilterTeam(VocabularyFilter):
905
906 @property
907 def filter_terms(self):
908- return [Person.teamownerID != None]
909+ return [IsNot(Person.teamowner_id, None)]
910
911
912 @implementer(IHugeVocabulary)
913-class ValidPersonOrTeamVocabulary(
914- BasePersonVocabulary, SQLObjectVocabularyBase
915-):
916+class ValidPersonOrTeamVocabulary(BasePersonVocabulary, StormVocabularyBase):
917 """The set of valid, viewable Persons/Teams in Launchpad.
918
919 A Person is considered valid if they have a preferred email address, and
920diff --git a/lib/lp/translations/model/poexportrequest.py b/lib/lp/translations/model/poexportrequest.py
921index 3313457..9dd9ec5 100644
922--- a/lib/lp/translations/model/poexportrequest.py
923+++ b/lib/lp/translations/model/poexportrequest.py
924@@ -92,7 +92,7 @@ class POExportRequestSet:
925 )
926
927 query_params = {
928- "person": quote(person),
929+ "person": quote(person.id),
930 "format": quote(format),
931 "templates": potemplate_ids,
932 "pofiles": pofile_ids,
933diff --git a/lib/lp/translations/model/pofile.py b/lib/lp/translations/model/pofile.py
934index 14626b1..623fb69 100644
935--- a/lib/lp/translations/model/pofile.py
936+++ b/lib/lp/translations/model/pofile.py
937@@ -456,7 +456,7 @@ class POFile(StormBase, POFileMixIn):
938 )
939 .config(distinct=True)
940 )
941- contributors = contributors.order_by(*Person._storm_sortingColumns)
942+ contributors = contributors.order_by(*Person._sortingColumns)
943 contributors = contributors.config(distinct=True)
944 return contributors
945
946diff --git a/lib/lp/translations/model/translationgroup.py b/lib/lp/translations/model/translationgroup.py
947index 943b38e..c8dc2dd 100644
948--- a/lib/lp/translations/model/translationgroup.py
949+++ b/lib/lp/translations/model/translationgroup.py
950@@ -175,7 +175,7 @@ class TranslationGroup(StormBase):
951 Translator,
952 Language,
953 Person,
954- LeftJoin(LibraryFileAlias, LibraryFileAlias.id == Person.iconID),
955+ LeftJoin(LibraryFileAlias, LibraryFileAlias.id == Person.icon_id),
956 LeftJoin(
957 LibraryFileContent,
958 LibraryFileContent.id == LibraryFileAlias.contentID,
959diff --git a/lib/lp/translations/scripts/remove_translations.py b/lib/lp/translations/scripts/remove_translations.py
960index c042b8a..e854640 100644
961--- a/lib/lp/translations/scripts/remove_translations.py
962+++ b/lib/lp/translations/scripts/remove_translations.py
963@@ -444,11 +444,11 @@ def remove_translations(
964 conditions = set()
965 if submitter is not None:
966 conditions.add(
967- "TranslationMessage.submitter = %s" % sqlvalues(submitter)
968+ "TranslationMessage.submitter = %s" % sqlvalues(submitter.id)
969 )
970 if reviewer is not None:
971 conditions.add(
972- "TranslationMessage.reviewer = %s" % sqlvalues(reviewer)
973+ "TranslationMessage.reviewer = %s" % sqlvalues(reviewer.id)
974 )
975 if date_created is not None:
976 conditions.add(

Subscribers

People subscribed via source and target branches

to status/vote changes: