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

Proposed by Colin Watson
Status: Merged
Approved by: Colin Watson
Approved revision: b5ffde7d61e04caea653e37d76b17541cab1994b
Merge reported by: Otto Co-Pilot
Merged at revision: not available
Proposed branch: ~cjwatson/launchpad:stormify-specification
Merge into: launchpad:master
Diff against target: 1114 lines (+288/-233)
24 files modified
lib/lp/blueprints/doc/specification.rst (+3/-3)
lib/lp/blueprints/interfaces/specificationsubscription.py (+0/-1)
lib/lp/blueprints/model/specification.py (+157/-139)
lib/lp/blueprints/model/specificationdependency.py (+17/-10)
lib/lp/blueprints/model/specificationmessage.py (+25/-11)
lib/lp/blueprints/model/specificationsearch.py (+4/-4)
lib/lp/blueprints/tests/test_specification.py (+1/-1)
lib/lp/blueprints/tests/test_specification_access_policy_triggers.py (+8/-9)
lib/lp/blueprints/vocabularies/specification.py (+3/-3)
lib/lp/blueprints/vocabularies/specificationdependency.py (+13/-13)
lib/lp/registry/browser/__init__.py (+3/-1)
lib/lp/registry/doc/person-account.rst (+4/-1)
lib/lp/registry/doc/productseries.rst (+16/-14)
lib/lp/registry/model/distribution.py (+6/-2)
lib/lp/registry/model/distroseries.py (+1/-1)
lib/lp/registry/model/milestone.py (+5/-3)
lib/lp/registry/model/person.py (+6/-6)
lib/lp/registry/model/product.py (+6/-2)
lib/lp/registry/model/productseries.py (+2/-2)
lib/lp/registry/model/projectgroup.py (+2/-2)
lib/lp/registry/services/sharingservice.py (+2/-2)
lib/lp/registry/tests/test_person.py (+1/-1)
lib/lp/registry/tests/test_sharingjob.py (+1/-0)
lib/lp/scripts/harness.py (+2/-2)
Reviewer Review Type Date Requested Status
Andrey Fedoseev (community) Approve
Review via email: mp+428840@code.launchpad.net

Commit message

Convert remaining lp.blueprints models to Storm

Description of the change

This ports `Specification`, `SpecificationDependency`, and `SpecificationMessage` away from the deprecated SQLObject style.

To post a comment you must log in.
Revision history for this message
Andrey Fedoseev (andrey-fedoseev) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/lib/lp/blueprints/doc/specification.rst b/lib/lp/blueprints/doc/specification.rst
2index 47b6121..6c490cb 100644
3--- a/lib/lp/blueprints/doc/specification.rst
4+++ b/lib/lp/blueprints/doc/specification.rst
5@@ -320,13 +320,13 @@ If there are dependencies between the specs, the method returns a
6 mapping between them.
7
8 >>> spec_a.createDependency(spec_b)
9- <SpecificationDependency at ...>
10+ <...SpecificationDependency object at ...>
11
12 >>> spec_a.createDependency(spec_c)
13- <SpecificationDependency at ...>
14+ <...SpecificationDependency object at ...>
15
16 >>> spec_c.createDependency(spec_d)
17- <SpecificationDependency at ...>
18+ <...SpecificationDependency object at ...>
19
20 >>> deps_dict = specset.getDependencyDict(
21 ... [spec_a, spec_b, spec_c, spec_d])
22diff --git a/lib/lp/blueprints/interfaces/specificationsubscription.py b/lib/lp/blueprints/interfaces/specificationsubscription.py
23index c727957..eef862e 100644
24--- a/lib/lp/blueprints/interfaces/specificationsubscription.py
25+++ b/lib/lp/blueprints/interfaces/specificationsubscription.py
26@@ -38,7 +38,6 @@ class ISpecificationSubscription(Interface):
27 )
28 personID = Attribute("db person value")
29 specification = Int(title=_("Specification"), required=True, readonly=True)
30- specificationID = Attribute("db specification value")
31 essential = Bool(
32 title=_("Participation essential"),
33 required=True,
34diff --git a/lib/lp/blueprints/model/specification.py b/lib/lp/blueprints/model/specification.py
35index 707d397..cffe3d2 100644
36--- a/lib/lp/blueprints/model/specification.py
37+++ b/lib/lp/blueprints/model/specification.py
38@@ -12,9 +12,23 @@ __all__ = [
39
40 import operator
41
42+import pytz
43 from lazr.lifecycle.event import ObjectCreatedEvent
44 from lazr.lifecycle.objectdelta import ObjectDelta
45-from storm.locals import SQL, Count, Desc, Join, Or, ReferenceSet, Store
46+from storm.locals import (
47+ SQL,
48+ Bool,
49+ Count,
50+ DateTime,
51+ Desc,
52+ Int,
53+ Join,
54+ Or,
55+ Reference,
56+ ReferenceSet,
57+ Store,
58+ Unicode,
59+)
60 from zope.component import getUtility
61 from zope.event import notify
62 from zope.interface import implementer
63@@ -71,22 +85,13 @@ from lp.registry.interfaces.productseries import IProductSeries
64 from lp.registry.model.milestone import Milestone
65 from lp.services.database import bulk
66 from lp.services.database.constants import DEFAULT, UTC_NOW
67-from lp.services.database.datetimecol import UtcDateTimeCol
68 from lp.services.database.enumcol import DBEnum
69 from lp.services.database.interfaces import IStore
70 from lp.services.database.sqlbase import (
71- SQLBase,
72 convert_storm_clause_to_string,
73 sqlvalues,
74 )
75-from lp.services.database.sqlobject import (
76- BoolCol,
77- ForeignKey,
78- IntCol,
79- SQLMultipleJoin,
80- SQLRelatedJoin,
81- StringCol,
82-)
83+from lp.services.database.stormbase import StormBase
84 from lp.services.mail.helpers import get_contact_email_addresses
85 from lp.services.propertycache import cachedproperty, get_property_cache
86 from lp.services.webapp.interfaces import ILaunchBag
87@@ -160,15 +165,17 @@ SPECIFICATION_POLICY_DEFAULT_TYPES = {
88
89
90 @implementer(ISpecification, IBugLinkTarget, IInformationType)
91-class Specification(SQLBase, BugLinkTargetMixin, InformationTypeMixin):
92+class Specification(StormBase, BugLinkTargetMixin, InformationTypeMixin):
93 """See ISpecification."""
94
95- _defaultOrder = ["-priority", "definition_status", "name", "id"]
96+ __storm_table__ = "Specification"
97+ __storm_order__ = ("-priority", "definition_status", "name", "id")
98
99 # db field names
100- name = StringCol(unique=True, notNull=True)
101- title = StringCol(notNull=True)
102- summary = StringCol(notNull=True)
103+ id = Int(primary=True)
104+ name = Unicode(allow_none=False)
105+ title = Unicode(allow_none=False)
106+ summary = Unicode(allow_none=False)
107 definition_status = DBEnum(
108 enum=SpecificationDefinitionStatus,
109 allow_none=False,
110@@ -179,110 +186,94 @@ class Specification(SQLBase, BugLinkTargetMixin, InformationTypeMixin):
111 allow_none=False,
112 default=SpecificationPriority.UNDEFINED,
113 )
114- _assignee = ForeignKey(
115- dbName="assignee",
116- notNull=False,
117- foreignKey="Person",
118- storm_validator=validate_public_person,
119- default=None,
120- )
121- _drafter = ForeignKey(
122- dbName="drafter",
123- notNull=False,
124- foreignKey="Person",
125- storm_validator=validate_public_person,
126- default=None,
127- )
128- _approver = ForeignKey(
129- dbName="approver",
130- notNull=False,
131- foreignKey="Person",
132- storm_validator=validate_public_person,
133+ _assignee_id = Int(
134+ name="assignee",
135+ allow_none=True,
136+ validator=validate_public_person,
137 default=None,
138 )
139- owner = ForeignKey(
140- dbName="owner",
141- foreignKey="Person",
142- storm_validator=validate_public_person,
143- notNull=True,
144- )
145- datecreated = UtcDateTimeCol(notNull=True, default=DEFAULT)
146- product = ForeignKey(
147- dbName="product", foreignKey="Product", notNull=False, default=None
148- )
149- productseries = ForeignKey(
150- dbName="productseries",
151- foreignKey="ProductSeries",
152- notNull=False,
153+ _assignee = Reference(_assignee_id, "Person.id")
154+ _drafter_id = Int(
155+ name="drafter",
156+ allow_none=True,
157+ validator=validate_public_person,
158 default=None,
159 )
160- distribution = ForeignKey(
161- dbName="distribution",
162- foreignKey="Distribution",
163- notNull=False,
164+ _drafter = Reference(_drafter_id, "Person.id")
165+ _approver_id = Int(
166+ name="approver",
167+ allow_none=True,
168+ validator=validate_public_person,
169 default=None,
170 )
171- distroseries = ForeignKey(
172- dbName="distroseries",
173- foreignKey="DistroSeries",
174- notNull=False,
175- default=None,
176+ _approver = Reference(_approver_id, "Person.id")
177+ owner_id = Int(
178+ name="owner", validator=validate_public_person, allow_none=False
179 )
180+ owner = Reference(owner_id, "Person.id")
181+ datecreated = DateTime(allow_none=False, default=DEFAULT, tzinfo=pytz.UTC)
182+ product_id = Int(name="product", allow_none=True, default=None)
183+ product = Reference(product_id, "Product.id")
184+ productseries_id = Int(name="productseries", allow_none=True, default=None)
185+ productseries = Reference(productseries_id, "ProductSeries.id")
186+ distribution_id = Int(name="distribution", allow_none=True, default=None)
187+ distribution = Reference(distribution_id, "Distribution.id")
188+ distroseries_id = Int(name="distroseries", allow_none=True, default=None)
189+ distroseries = Reference(distroseries_id, "DistroSeries.id")
190 goalstatus = DBEnum(
191 enum=SpecificationGoalStatus,
192 allow_none=False,
193 default=SpecificationGoalStatus.PROPOSED,
194 )
195- goal_proposer = ForeignKey(
196- dbName="goal_proposer",
197- notNull=False,
198- foreignKey="Person",
199- storm_validator=validate_public_person,
200+ goal_proposer_id = Int(
201+ name="goal_proposer",
202+ allow_none=True,
203+ validator=validate_public_person,
204 default=None,
205 )
206- date_goal_proposed = UtcDateTimeCol(notNull=False, default=None)
207- goal_decider = ForeignKey(
208- dbName="goal_decider",
209- notNull=False,
210- foreignKey="Person",
211- storm_validator=validate_public_person,
212+ goal_proposer = Reference(goal_proposer_id, "Person.id")
213+ date_goal_proposed = DateTime(
214+ allow_none=True, default=None, tzinfo=pytz.UTC
215+ )
216+ goal_decider_id = Int(
217+ name="goal_decider",
218+ allow_none=True,
219+ validator=validate_public_person,
220 default=None,
221 )
222- date_goal_decided = UtcDateTimeCol(notNull=False, default=None)
223- milestone = ForeignKey(
224- dbName="milestone", foreignKey="Milestone", notNull=False, default=None
225+ goal_decider = Reference(goal_decider_id, "Person.id")
226+ date_goal_decided = DateTime(
227+ allow_none=True, default=None, tzinfo=pytz.UTC
228 )
229- specurl = StringCol(notNull=False, default=None)
230- whiteboard = StringCol(notNull=False, default=None)
231- direction_approved = BoolCol(notNull=True, default=False)
232- man_days = IntCol(notNull=False, default=None)
233+ milestone_id = Int(name="milestone", allow_none=True, default=None)
234+ milestone = Reference(milestone_id, "Milestone.id")
235+ specurl = Unicode(allow_none=True, default=None)
236+ whiteboard = Unicode(allow_none=True, default=None)
237+ direction_approved = Bool(allow_none=False, default=False)
238+ man_days = Int(allow_none=True, default=None)
239 implementation_status = DBEnum(
240 enum=SpecificationImplementationStatus,
241 allow_none=False,
242 default=SpecificationImplementationStatus.UNKNOWN,
243 )
244- superseded_by = ForeignKey(
245- dbName="superseded_by",
246- foreignKey="Specification",
247- notNull=False,
248- default=None,
249- )
250- completer = ForeignKey(
251- dbName="completer",
252- notNull=False,
253- foreignKey="Person",
254- storm_validator=validate_public_person,
255+ superseded_by_id = Int(name="superseded_by", allow_none=True, default=None)
256+ superseded_by = Reference(superseded_by_id, "Specification.id")
257+ completer_id = Int(
258+ name="completer",
259+ allow_none=True,
260+ validator=validate_public_person,
261 default=None,
262 )
263- date_completed = UtcDateTimeCol(notNull=False, default=None)
264- starter = ForeignKey(
265- dbName="starter",
266- notNull=False,
267- foreignKey="Person",
268- storm_validator=validate_public_person,
269+ completer = Reference(completer_id, "Person.id")
270+ date_completed = DateTime(allow_none=True, default=None, tzinfo=pytz.UTC)
271+ starter_id = Int(
272+ name="starter",
273+ allow_none=True,
274+ validator=validate_public_person,
275 default=None,
276 )
277- date_started = UtcDateTimeCol(notNull=False, default=None)
278+ starter = Reference(starter_id, "Person.id")
279+ date_started = DateTime(allow_none=True, default=None, tzinfo=pytz.UTC)
280
281 # useful joins
282 _subscriptions = ReferenceSet(
283@@ -298,32 +289,59 @@ class Specification(SQLBase, BugLinkTargetMixin, InformationTypeMixin):
284 order_by=("Person.display_name", "Person.name"),
285 )
286 sprint_links = ReferenceSet(
287- "<primary key>",
288+ "id",
289 "SprintSpecification.specification_id",
290 order_by="SprintSpecification.id",
291 )
292 sprints = ReferenceSet(
293- "<primary key>",
294+ "id",
295 "SprintSpecification.specification_id",
296 "SprintSpecification.sprint_id",
297 "Sprint.id",
298 order_by="Sprint.name",
299 )
300- spec_dependency_links = SQLMultipleJoin(
301- "SpecificationDependency", joinColumn="specification", orderBy="id"
302+ spec_dependency_links = ReferenceSet(
303+ "id",
304+ "SpecificationDependency.specification_id",
305+ order_by="SpecificationDependency.id",
306 )
307
308- dependencies = SQLRelatedJoin(
309- "Specification",
310- joinColumn="specification",
311- otherColumn="dependency",
312- orderBy="title",
313- intermediateTable="SpecificationDependency",
314+ dependencies = ReferenceSet(
315+ "id",
316+ "SpecificationDependency.specification_id",
317+ "SpecificationDependency.dependency_id",
318+ "Specification.id",
319+ order_by="Specification.title",
320 )
321 information_type = DBEnum(
322 enum=InformationType, allow_none=False, default=InformationType.PUBLIC
323 )
324
325+ def __init__(
326+ self,
327+ name,
328+ title,
329+ summary,
330+ owner,
331+ definition_status=DEFAULT,
332+ assignee=None,
333+ drafter=None,
334+ approver=None,
335+ specurl=None,
336+ whiteboard=None,
337+ ):
338+ super().__init__()
339+ self.name = name
340+ self.title = title
341+ self.summary = summary
342+ self.owner = owner
343+ self.definition_status = definition_status
344+ self._assignee = assignee
345+ self._drafter = drafter
346+ self._approver = approver
347+ self.specurl = specurl
348+ self.whiteboard = whiteboard
349+
350 @cachedproperty
351 def linked_branches(self):
352 return list(
353@@ -355,44 +373,44 @@ class Specification(SQLBase, BugLinkTargetMixin, InformationTypeMixin):
354
355 def getDependencies(self, user=None):
356 return self._fetch_children_or_parents(
357- SpecificationDependency.specificationID,
358- SpecificationDependency.dependencyID,
359+ SpecificationDependency.specification_id,
360+ SpecificationDependency.dependency_id,
361 user,
362 )
363
364 def getBlockedSpecs(self, user=None):
365 return self._fetch_children_or_parents(
366- SpecificationDependency.dependencyID,
367- SpecificationDependency.specificationID,
368+ SpecificationDependency.dependency_id,
369+ SpecificationDependency.specification_id,
370 user,
371 )
372
373- def set_assignee(self, person):
374- self.subscribeIfAccessGrantNeeded(person)
375- self._assignee = person
376-
377- def get_assignee(self):
378+ @property
379+ def assignee(self):
380 return self._assignee
381
382- assignee = property(get_assignee, set_assignee)
383-
384- def set_drafter(self, person):
385+ @assignee.setter
386+ def assignee(self, person):
387 self.subscribeIfAccessGrantNeeded(person)
388- self._drafter = person
389+ self._assignee = person
390
391- def get_drafter(self):
392+ @property
393+ def drafter(self):
394 return self._drafter
395
396- drafter = property(get_drafter, set_drafter)
397-
398- def set_approver(self, person):
399+ @drafter.setter
400+ def drafter(self, person):
401 self.subscribeIfAccessGrantNeeded(person)
402- self._approver = person
403+ self._drafter = person
404
405- def get_approver(self):
406+ @property
407+ def approver(self):
408 return self._approver
409
410- approver = property(get_approver, set_approver)
411+ @approver.setter
412+ def approver(self, person):
413+ self.subscribeIfAccessGrantNeeded(person)
414+ self._approver = person
415
416 def subscribeIfAccessGrantNeeded(self, person):
417 """Subscribe person if this specification is not public and if
418@@ -777,22 +795,22 @@ class Specification(SQLBase, BugLinkTargetMixin, InformationTypeMixin):
419 """See ISpecification."""
420 newstatus = None
421 if self.is_started:
422- if self.starterID is None:
423+ if self.starter_id is None:
424 newstatus = SpecificationLifecycleStatus.STARTED
425 self.date_started = UTC_NOW
426 self.starter = user
427 else:
428- if self.starterID is not None:
429+ if self.starter_id is not None:
430 newstatus = SpecificationLifecycleStatus.NOTSTARTED
431 self.date_started = None
432 self.starter = None
433 if self.is_complete:
434- if self.completerID is None:
435+ if self.completer_id is None:
436 newstatus = SpecificationLifecycleStatus.COMPLETE
437 self.date_completed = UTC_NOW
438 self.completer = user
439 else:
440- if self.completerID is not None:
441+ if self.completer_id is not None:
442 self.date_completed = None
443 self.completer = None
444 if self.is_started:
445@@ -1025,8 +1043,8 @@ class Specification(SQLBase, BugLinkTargetMixin, InformationTypeMixin):
446 # see if a relevant dependency link exists, and if so, delete it
447 for deplink in self.spec_dependency_links:
448 if deplink.dependency.id == specification.id:
449- SpecificationDependency.delete(deplink.id)
450- return deplink
451+ Store.of(deplink).remove(deplink)
452+ return
453
454 def all_deps(self, user=None):
455 return list(
456@@ -1253,7 +1271,7 @@ class SpecificationSet(HasSpecificationsMixin):
457 (Specification.implementation_status, Count()),
458 Or(
459 Specification.productseries == product_series,
460- Specification.milestoneID.is_in(
461+ Specification.milestone_id.is_in(
462 list(
463 product_series.all_milestones.values(Milestone.id)
464 )
465@@ -1291,15 +1309,15 @@ class SpecificationSet(HasSpecificationsMixin):
466
467 def getByURL(self, url):
468 """See ISpecificationSet."""
469- return Specification.selectOneBy(specurl=url)
470+ return IStore(Specification).find(Specification, specurl=url).one()
471
472 def getByName(self, pillar, name):
473 """See ISpecificationSet."""
474 clauses = [Specification.name == name]
475 if IDistribution.providedBy(pillar):
476- clauses.append(Specification.distributionID == pillar.id)
477+ clauses.append(Specification.distribution == pillar)
478 elif IProduct.providedBy(pillar):
479- clauses.append(Specification.productID == pillar.id)
480+ clauses.append(Specification.product == pillar)
481 return IStore(Specification).find(Specification, *clauses).one()
482
483 @property
484@@ -1349,9 +1367,9 @@ class SpecificationSet(HasSpecificationsMixin):
485 summary=summary,
486 definition_status=definition_status,
487 owner=owner,
488- _approver=approver,
489- _assignee=assignee,
490- _drafter=drafter,
491+ approver=approver,
492+ assignee=assignee,
493+ drafter=drafter,
494 whiteboard=whiteboard,
495 )
496 spec.setTarget(target)
497@@ -1386,14 +1404,14 @@ class SpecificationSet(HasSpecificationsMixin):
498 for spec_id, dep_id in results:
499 if spec_id not in dependencies:
500 dependencies[spec_id] = []
501- dependency = Specification.get(dep_id)
502+ dependency = IStore(Specification).get(Specification, dep_id)
503 dependencies[spec_id].append(dependency)
504
505 return dependencies
506
507 def get(self, spec_id):
508 """See lp.blueprints.interfaces.specification.ISpecificationSet."""
509- return Specification.get(spec_id)
510+ return IStore(Specification).get(Specification, spec_id)
511
512 def empty_list(self):
513 """See `ISpecificationSet`."""
514diff --git a/lib/lp/blueprints/model/specificationdependency.py b/lib/lp/blueprints/model/specificationdependency.py
515index 365b4be..0a90436 100644
516--- a/lib/lp/blueprints/model/specificationdependency.py
517+++ b/lib/lp/blueprints/model/specificationdependency.py
518@@ -3,23 +3,30 @@
519
520 __all__ = ["SpecificationDependency"]
521
522+from storm.locals import Int, Reference
523 from zope.interface import implementer
524
525 from lp.blueprints.interfaces.specificationdependency import (
526 ISpecificationDependency,
527 )
528-from lp.services.database.sqlbase import SQLBase
529-from lp.services.database.sqlobject import ForeignKey
530+from lp.services.database.stormbase import StormBase
531
532
533 @implementer(ISpecificationDependency)
534-class SpecificationDependency(SQLBase):
535+class SpecificationDependency(StormBase):
536 """A link between a spec and a bug."""
537
538- _table = "SpecificationDependency"
539- specification = ForeignKey(
540- dbName="specification", foreignKey="Specification", notNull=True
541- )
542- dependency = ForeignKey(
543- dbName="dependency", foreignKey="Specification", notNull=True
544- )
545+ __storm_table__ = "SpecificationDependency"
546+
547+ id = Int(primary=True)
548+
549+ specification_id = Int(name="specification", allow_none=False)
550+ specification = Reference(specification_id, "Specification.id")
551+
552+ dependency_id = Int(name="dependency", allow_none=False)
553+ dependency = Reference(dependency_id, "Specification.id")
554+
555+ def __init__(self, specification, dependency):
556+ super().__init__()
557+ self.specification = specification
558+ self.dependency = dependency
559diff --git a/lib/lp/blueprints/model/specificationmessage.py b/lib/lp/blueprints/model/specificationmessage.py
560index 6edb0e7..3603a9e 100644
561--- a/lib/lp/blueprints/model/specificationmessage.py
562+++ b/lib/lp/blueprints/model/specificationmessage.py
563@@ -5,28 +5,38 @@ __all__ = ["SpecificationMessage", "SpecificationMessageSet"]
564
565 from email.utils import make_msgid
566
567+from storm.locals import Bool, Int, Reference
568 from zope.interface import implementer
569
570 from lp.blueprints.interfaces.specificationmessage import (
571 ISpecificationMessage,
572 ISpecificationMessageSet,
573 )
574-from lp.services.database.sqlbase import SQLBase
575-from lp.services.database.sqlobject import BoolCol, ForeignKey
576+from lp.services.database.interfaces import IStore
577+from lp.services.database.stormbase import StormBase
578 from lp.services.messages.model.message import Message, MessageChunk
579
580
581 @implementer(ISpecificationMessage)
582-class SpecificationMessage(SQLBase):
583+class SpecificationMessage(StormBase):
584 """A table linking specifications and messages."""
585
586- _table = "SpecificationMessage"
587+ __storm_table__ = "SpecificationMessage"
588
589- specification = ForeignKey(
590- dbName="specification", foreignKey="Specification", notNull=True
591- )
592- message = ForeignKey(dbName="message", foreignKey="Message", notNull=True)
593- visible = BoolCol(notNull=True, default=True)
594+ id = Int(primary=True)
595+
596+ specification_id = Int(name="specification", allow_none=False)
597+ specification = Reference(specification_id, "Specification.id")
598+
599+ message_id = Int(name="message", allow_none=False)
600+ message = Reference(message_id, "Message.id")
601+
602+ visible = Bool(allow_none=False, default=True)
603+
604+ def __init__(self, specification, message):
605+ super().__init__()
606+ self.specification = specification
607+ self.message = message
608
609
610 @implementer(ISpecificationMessageSet)
611@@ -39,8 +49,12 @@ class SpecificationMessageSet:
612 owner=owner, rfc822msgid=make_msgid("blueprint"), subject=subject
613 )
614 MessageChunk(message=msg, content=content, sequence=1)
615- return SpecificationMessage(specification=spec, message=msg)
616+ specmessage = SpecificationMessage(specification=spec, message=msg)
617+ IStore(SpecificationMessage).flush()
618+ return specmessage
619
620 def get(self, specmessageid):
621 """See ISpecificationMessageSet."""
622- return SpecificationMessage.get(specmessageid)
623+ return IStore(SpecificationMessage).get(
624+ SpecificationMessage, specmessageid
625+ )
626diff --git a/lib/lp/blueprints/model/specificationsearch.py b/lib/lp/blueprints/model/specificationsearch.py
627index 2f00a54..f505628 100644
628--- a/lib/lp/blueprints/model/specificationsearch.py
629+++ b/lib/lp/blueprints/model/specificationsearch.py
630@@ -153,9 +153,9 @@ def search_specifications(
631 for spec in rows:
632 if need_people:
633 person_ids |= {
634- spec._assigneeID,
635- spec._approverID,
636- spec._drafterID,
637+ spec._assignee_id,
638+ spec._approver_id,
639+ spec._drafter_id,
640 }
641 if need_branches:
642 get_property_cache(spec).linked_branches = []
643@@ -215,7 +215,7 @@ def get_specification_active_product_filter(context):
644 return [], []
645 from lp.registry.model.product import Product
646
647- tables = [LeftJoin(Product, Specification.productID == Product.id)]
648+ tables = [LeftJoin(Product, Specification.product_id == Product.id)]
649 active_products = Or(Specification.product == None, Product.active == True)
650 return tables, [active_products]
651
652diff --git a/lib/lp/blueprints/tests/test_specification.py b/lib/lp/blueprints/tests/test_specification.py
653index fa7404e..62fbea3 100644
654--- a/lib/lp/blueprints/tests/test_specification.py
655+++ b/lib/lp/blueprints/tests/test_specification.py
656@@ -586,7 +586,7 @@ class SpecificationTests(TestCaseWithFactory):
657 def _fetch_specs_visible_for_user(self, user):
658 return Store.of(self.product).find(
659 Specification,
660- Specification.productID == self.product.id,
661+ Specification.product == self.product,
662 *get_specification_privacy_filter(user),
663 )
664
665diff --git a/lib/lp/blueprints/tests/test_specification_access_policy_triggers.py b/lib/lp/blueprints/tests/test_specification_access_policy_triggers.py
666index e192cb7..2ba7153 100644
667--- a/lib/lp/blueprints/tests/test_specification_access_policy_triggers.py
668+++ b/lib/lp/blueprints/tests/test_specification_access_policy_triggers.py
669@@ -17,15 +17,14 @@ class TestSpecificationAccessPolicyTriggers(TestCaseWithFactory):
670
671 def fetchPolicies(self, specification):
672 # We may be dealing with private specs, so just ignore security.
673- return (
674- IStore(Specification)
675- .execute(
676- "SELECT access_policy, access_grants FROM specification WHERE "
677- "id = ?",
678- (removeSecurityProxy(specification).id,),
679- )
680- .get_one()
681- )
682+ store = IStore(Specification)
683+ # Ensure that the specification's ID is available.
684+ store.flush()
685+ return store.execute(
686+ "SELECT access_policy, access_grants FROM specification WHERE "
687+ "id = ?",
688+ (removeSecurityProxy(specification).id,),
689+ ).get_one()
690
691 def assertAccess(self, specification, expected_policy, expected_grants):
692 policy, grants = self.fetchPolicies(specification)
693diff --git a/lib/lp/blueprints/vocabularies/specification.py b/lib/lp/blueprints/vocabularies/specification.py
694index 55526cc..68a6a51 100644
695--- a/lib/lp/blueprints/vocabularies/specification.py
696+++ b/lib/lp/blueprints/vocabularies/specification.py
697@@ -14,16 +14,16 @@ from zope.schema.vocabulary import SimpleTerm
698
699 from lp.blueprints.model.specification import Specification
700 from lp.services.webapp.interfaces import ILaunchBag
701-from lp.services.webapp.vocabulary import SQLObjectVocabularyBase
702+from lp.services.webapp.vocabulary import StormVocabularyBase
703
704
705-class SpecificationVocabulary(SQLObjectVocabularyBase):
706+class SpecificationVocabulary(StormVocabularyBase):
707 """List specifications for the current product or distribution in
708 ILaunchBag, EXCEPT for the current spec in LaunchBag if one exists.
709 """
710
711 _table = Specification
712- _orderBy = "title"
713+ _order_by = "title"
714
715 def __iter__(self):
716 launchbag = getUtility(ILaunchBag)
717diff --git a/lib/lp/blueprints/vocabularies/specificationdependency.py b/lib/lp/blueprints/vocabularies/specificationdependency.py
718index de5b89c..d458c05 100644
719--- a/lib/lp/blueprints/vocabularies/specificationdependency.py
720+++ b/lib/lp/blueprints/vocabularies/specificationdependency.py
721@@ -8,7 +8,7 @@ __all__ = [
722 "SpecificationDependenciesVocabulary",
723 ]
724
725-from storm.locals import SQL, And, Store
726+from storm.locals import SQL, Store
727 from zope.component import getUtility
728 from zope.interface import implementer
729 from zope.schema.vocabulary import SimpleTerm
730@@ -28,12 +28,12 @@ from lp.services.webapp.interfaces import ILaunchBag
731 from lp.services.webapp.vocabulary import (
732 CountableIterator,
733 IHugeVocabulary,
734- SQLObjectVocabularyBase,
735+ StormVocabularyBase,
736 )
737
738
739 @implementer(IHugeVocabulary)
740-class SpecificationDepCandidatesVocabulary(SQLObjectVocabularyBase):
741+class SpecificationDepCandidatesVocabulary(StormVocabularyBase):
742 """Specifications that could be dependencies of this spec.
743
744 This includes only those specs that are not blocked by this spec (directly
745@@ -54,7 +54,7 @@ class SpecificationDepCandidatesVocabulary(SQLObjectVocabularyBase):
746 """
747
748 _table = Specification
749- _orderBy = "name"
750+ _order_by = "name"
751 displayname = "Select a blueprint"
752 step_title = "Search"
753
754@@ -74,7 +74,7 @@ class SpecificationDepCandidatesVocabulary(SQLObjectVocabularyBase):
755 user = getattr(getUtility(ILaunchBag), "user", None)
756 return spec not in set(self.context.all_blocked(user=user))
757
758- def _order_by(self):
759+ def _order_search_by(self):
760 """Look at the context to provide grouping.
761
762 If the blueprint is for a project, then matching results for that
763@@ -187,7 +187,7 @@ class SpecificationDepCandidatesVocabulary(SQLObjectVocabularyBase):
764 fti_search(Specification, query),
765 self._exclude_blocked_query(),
766 )
767- .order_by(self._order_by())
768+ .order_by(self._order_search_by())
769 )
770
771 def __iter__(self):
772@@ -198,17 +198,17 @@ class SpecificationDepCandidatesVocabulary(SQLObjectVocabularyBase):
773 return self._is_valid_candidate(obj)
774
775
776-class SpecificationDependenciesVocabulary(SQLObjectVocabularyBase):
777+class SpecificationDependenciesVocabulary(StormVocabularyBase):
778 """List specifications on which the current specification depends."""
779
780 _table = Specification
781- _orderBy = "title"
782+ _order_by = "title"
783
784 @property
785- def _filter(self):
786+ def _clauses(self):
787 user = getattr(getUtility(ILaunchBag), "user", None)
788- return And(
789- SpecificationDependency.specificationID == self.context.id,
790- SpecificationDependency.dependencyID == Specification.id,
791+ return [
792+ SpecificationDependency.specification == self.context,
793+ SpecificationDependency.dependency_id == Specification.id,
794 *get_specification_privacy_filter(user),
795- )
796+ ]
797diff --git a/lib/lp/registry/browser/__init__.py b/lib/lp/registry/browser/__init__.py
798index 403345b..5cad743 100644
799--- a/lib/lp/registry/browser/__init__.py
800+++ b/lib/lp/registry/browser/__init__.py
801@@ -244,7 +244,9 @@ class RegistryDeleteViewMixin:
802 Store.of(bugtask).remove(nb.conjoined_primary)
803 else:
804 nb.milestone = None
805- removeSecurityProxy(milestone.all_specifications).set(milestoneID=None)
806+ removeSecurityProxy(milestone.all_specifications).set(
807+ milestone_id=None
808+ )
809 getUtility(ISpecificationWorkItemSet).unlinkMilestone(milestone)
810 self._deleteRelease(milestone.product_release)
811 milestone.destroySelf()
812diff --git a/lib/lp/registry/doc/person-account.rst b/lib/lp/registry/doc/person-account.rst
813index 0ed70c6..f5e2424 100644
814--- a/lib/lp/registry/doc/person-account.rst
815+++ b/lib/lp/registry/doc/person-account.rst
816@@ -76,7 +76,10 @@ will cause this spec to be reassigned.
817
818 >>> from lp.blueprints.model.specification import Specification
819 >>> from lp.registry.model.person import Person
820- >>> spec = Specification.selectFirst("assignee IS NULL", orderBy='id')
821+ >>> from lp.services.database.interfaces import IStore
822+ >>> spec = IStore(Specification).find(
823+ ... Specification, _assignee=None
824+ ... ).order_by("id").first()
825 >>> spec.assignee = foobar
826
827 >>> for membership in foobar.team_memberships:
828diff --git a/lib/lp/registry/doc/productseries.rst b/lib/lp/registry/doc/productseries.rst
829index 18d0c9c..44d6516 100644
830--- a/lib/lp/registry/doc/productseries.rst
831+++ b/lib/lp/registry/doc/productseries.rst
832@@ -201,25 +201,27 @@ is informational.
833 We will create two specs for onezero and use them to demonstrate the
834 filtering.
835
836- >>> from lp.services.database.constants import UTC_NOW
837+ >>> from lp.blueprints.enums import SpecificationDefinitionStatus
838+ >>> from lp.blueprints.interfaces.specification import ISpecificationSet
839 >>> carlos = getUtility(IPersonSet).getByName('carlos')
840- >>> from lp.blueprints.model.specification import Specification
841- >>> a = Specification(name='a', title='A', summary='AA', owner=carlos,
842- ... product=firefox, productseries=onezero,
843- ... specurl='http://wbc.com/two', goal_proposer=carlos,
844- ... date_goal_proposed=UTC_NOW)
845- >>> b = Specification(name='b', title='b', summary='bb', owner=carlos,
846- ... product=firefox, productseries=onezero,
847- ... specurl='http://fds.com/adsf', goal_proposer=carlos,
848- ... date_goal_proposed=UTC_NOW)
849+ >>> _ = login_person(carlos)
850+ >>> a = getUtility(ISpecificationSet).new(
851+ ... name="a", title="A", specurl="http://wbc.com/two", summary="AA",
852+ ... definition_status=SpecificationDefinitionStatus.NEW, owner=carlos,
853+ ... target=firefox,
854+ ... )
855+ >>> a.proposeGoal(onezero, carlos)
856+ >>> b = getUtility(ISpecificationSet).new(
857+ ... name="b", title="b", specurl="http://fds.com/adsf", summary="bb",
858+ ... definition_status=SpecificationDefinitionStatus.NEW, owner=carlos,
859+ ... target=firefox,
860+ ... )
861+ >>> b.proposeGoal(onezero, carlos)
862
863 Now, we will make one of them accepted, the other declined, and both of
864 them informational.
865
866- >>> from lp.blueprints.enums import (
867- ... SpecificationDefinitionStatus,
868- ... SpecificationImplementationStatus,
869- ... )
870+ >>> from lp.blueprints.enums import SpecificationImplementationStatus
871 >>> a.definition_status = b.definition_status = (
872 ... SpecificationDefinitionStatus.APPROVED)
873 >>> a.implementation_status = (
874diff --git a/lib/lp/registry/model/distribution.py b/lib/lp/registry/model/distribution.py
875index 3dd493e..d8ed65c 100644
876--- a/lib/lp/registry/model/distribution.py
877+++ b/lib/lp/registry/model/distribution.py
878@@ -1404,7 +1404,7 @@ class Distribution(
879 - informationalness: we will show ANY if nothing is said
880
881 """
882- base_clauses = [Specification.distributionID == self.id]
883+ base_clauses = [Specification.distribution == self]
884 return search_specifications(
885 self,
886 base_clauses,
887@@ -1419,7 +1419,11 @@ class Distribution(
888
889 def getSpecification(self, name):
890 """See `ISpecificationTarget`."""
891- return Specification.selectOneBy(distribution=self, name=name)
892+ return (
893+ IStore(Specification)
894+ .find(Specification, distribution=self, name=name)
895+ .one()
896+ )
897
898 def getAllowedSpecificationInformationTypes(self):
899 """See `ISpecificationTarget`."""
900diff --git a/lib/lp/registry/model/distroseries.py b/lib/lp/registry/model/distroseries.py
901index c3f912d..41f4e9a 100644
902--- a/lib/lp/registry/model/distroseries.py
903+++ b/lib/lp/registry/model/distroseries.py
904@@ -974,7 +974,7 @@ class DistroSeries(
905 - informationalness: if nothing is said, ANY
906
907 """
908- base_clauses = [Specification.distroseriesID == self.id]
909+ base_clauses = [Specification.distroseries == self]
910 return search_specifications(
911 self,
912 base_clauses,
913diff --git a/lib/lp/registry/model/milestone.py b/lib/lp/registry/model/milestone.py
914index 0171a61..f7b1abb 100644
915--- a/lib/lp/registry/model/milestone.py
916+++ b/lib/lp/registry/model/milestone.py
917@@ -147,7 +147,7 @@ class MilestoneData:
918 from lp.blueprints.model.specification import Specification
919
920 return Store.of(self).find(
921- Specification, Specification.milestoneID == self.id
922+ Specification, Specification.milestone == self
923 )
924
925 def getSpecifications(self, user):
926@@ -166,7 +166,9 @@ class MilestoneData:
927 product_origin, clauses = get_specification_active_product_filter(self)
928 origin.extend(product_origin)
929 clauses.extend(get_specification_privacy_filter(user))
930- origin.append(LeftJoin(Person, Specification._assigneeID == Person.id))
931+ origin.append(
932+ LeftJoin(Person, Specification._assignee_id == Person.id)
933+ )
934 milestones = self._milestone_ids_expr(user)
935
936 results = (
937@@ -180,7 +182,7 @@ class MilestoneData:
938 Specification.id,
939 tables=[Specification],
940 where=(
941- Specification.milestoneID.is_in(milestones)
942+ Specification.milestone_id.is_in(milestones)
943 ),
944 ),
945 Select(
946diff --git a/lib/lp/registry/model/person.py b/lib/lp/registry/model/person.py
947index db8a777..9239372 100644
948--- a/lib/lp/registry/model/person.py
949+++ b/lib/lp/registry/model/person.py
950@@ -1630,7 +1630,7 @@ class Person(
951 SpecificationWorkItem.specification_id.is_in(
952 Select(
953 Specification.id,
954- where=Specification._assigneeID.is_in(
955+ where=Specification._assignee_id.is_in(
956 self.participant_ids
957 ),
958 )
959@@ -1670,7 +1670,7 @@ class Person(
960 Milestone,
961 Coalesce(
962 SpecificationWorkItem.milestone_id,
963- Specification.milestoneID,
964+ Specification.milestone_id,
965 )
966 == Milestone.id,
967 ),
968@@ -1697,17 +1697,17 @@ class Person(
969 specs = bulk.load_related(
970 Specification, workitems, ["specification_id"]
971 )
972- bulk.load_related(Product, specs, ["productID"])
973- bulk.load_related(Distribution, specs, ["distributionID"])
974+ bulk.load_related(Product, specs, ["product_id"])
975+ bulk.load_related(Distribution, specs, ["distribution_id"])
976 assignee_ids = set(
977 [workitem.assignee_id for workitem in workitems]
978- + [spec._assigneeID for spec in specs]
979+ + [spec._assignee_id for spec in specs]
980 )
981 assignee_ids.discard(None)
982 bulk.load(Person, assignee_ids, store)
983 milestone_ids = set(
984 [workitem.milestone_id for workitem in workitems]
985- + [spec.milestoneID for spec in specs]
986+ + [spec.milestone_id for spec in specs]
987 )
988 milestone_ids.discard(None)
989 bulk.load(Milestone, milestone_ids, store)
990diff --git a/lib/lp/registry/model/product.py b/lib/lp/registry/model/product.py
991index 357e5c4..3cae421 100644
992--- a/lib/lp/registry/model/product.py
993+++ b/lib/lp/registry/model/product.py
994@@ -1418,7 +1418,7 @@ class Product(
995 need_workitems=False,
996 ):
997 """See `IHasSpecifications`."""
998- base_clauses = [Specification.productID == self.id]
999+ base_clauses = [Specification.product == self]
1000 return search_specifications(
1001 self,
1002 base_clauses,
1003@@ -1433,7 +1433,11 @@ class Product(
1004
1005 def getSpecification(self, name):
1006 """See `ISpecificationTarget`."""
1007- return Specification.selectOneBy(product=self, name=name)
1008+ return (
1009+ IStore(Specification)
1010+ .find(Specification, product=self, name=name)
1011+ .one()
1012+ )
1013
1014 def getSeries(self, name):
1015 """See `IProduct`."""
1016diff --git a/lib/lp/registry/model/productseries.py b/lib/lp/registry/model/productseries.py
1017index 16cbb10..0edd632 100644
1018--- a/lib/lp/registry/model/productseries.py
1019+++ b/lib/lp/registry/model/productseries.py
1020@@ -348,7 +348,7 @@ class ProductSeries(
1021 - informational, which defaults to showing BOTH if nothing is said
1022
1023 """
1024- base_clauses = [Specification.productseriesID == self.id]
1025+ base_clauses = [Specification.productseries == self]
1026 return search_specifications(
1027 self,
1028 base_clauses,
1029@@ -365,7 +365,7 @@ class ProductSeries(
1030 @property
1031 def all_specifications(self):
1032 return Store.of(self).find(
1033- Specification, Specification.productseriesID == self.id
1034+ Specification, Specification.productseries == self
1035 )
1036
1037 def _customizeSearchParams(self, search_params):
1038diff --git a/lib/lp/registry/model/projectgroup.py b/lib/lp/registry/model/projectgroup.py
1039index b50b11c..0dda2e0 100644
1040--- a/lib/lp/registry/model/projectgroup.py
1041+++ b/lib/lp/registry/model/projectgroup.py
1042@@ -287,7 +287,7 @@ class ProjectGroup(
1043 ):
1044 """See `IHasSpecifications`."""
1045 base_clauses = [
1046- Specification.productID == Product.id,
1047+ Specification.product_id == Product.id,
1048 Product.projectgroupID == self.id,
1049 ]
1050 tables = [Specification]
1051@@ -296,7 +296,7 @@ class ProjectGroup(
1052 tables.append(
1053 Join(
1054 ProductSeries,
1055- Specification.productseriesID == ProductSeries.id,
1056+ Specification.productseries_id == ProductSeries.id,
1057 )
1058 )
1059 return search_specifications(
1060diff --git a/lib/lp/registry/services/sharingservice.py b/lib/lp/registry/services/sharingservice.py
1061index 5548b4a..b628808 100644
1062--- a/lib/lp/registry/services/sharingservice.py
1063+++ b/lib/lp/registry/services/sharingservice.py
1064@@ -435,9 +435,9 @@ class SharingService:
1065 AccessPolicy,
1066 And(
1067 Or(
1068- Specification.distributionID
1069+ Specification.distribution_id
1070 == AccessPolicy.distribution_id,
1071- Specification.productID == AccessPolicy.product_id,
1072+ Specification.product_id == AccessPolicy.product_id,
1073 ),
1074 AccessPolicy.type == Specification.information_type,
1075 ),
1076diff --git a/lib/lp/registry/tests/test_person.py b/lib/lp/registry/tests/test_person.py
1077index 7fa6c28..2fa9c5c 100644
1078--- a/lib/lp/registry/tests/test_person.py
1079+++ b/lib/lp/registry/tests/test_person.py
1080@@ -1027,7 +1027,7 @@ class TestPersonStates(TestCaseWithFactory):
1081 )
1082
1083 def test_Specification_person_validator(self):
1084- specification = Specification.select(limit=1)[0]
1085+ specification = IStore(Specification).find(Specification).first()
1086 for attr_name in [
1087 "assignee",
1088 "drafter",
1089diff --git a/lib/lp/registry/tests/test_sharingjob.py b/lib/lp/registry/tests/test_sharingjob.py
1090index 6c7c65a..e017e72 100644
1091--- a/lib/lp/registry/tests/test_sharingjob.py
1092+++ b/lib/lp/registry/tests/test_sharingjob.py
1093@@ -159,6 +159,7 @@ class SharingJobDerivedTestCase(TestCaseWithFactory):
1094 def test_repr_specifications(self):
1095 requestor = self.factory.makePerson()
1096 specification = self.factory.makeSpecification()
1097+ IStore(specification).flush()
1098 job = getUtility(IRemoveArtifactSubscriptionsJobSource).create(
1099 requestor, artifacts=[specification]
1100 )
1101diff --git a/lib/lp/scripts/harness.py b/lib/lp/scripts/harness.py
1102index c33077c..4bf8084 100644
1103--- a/lib/lp/scripts/harness.py
1104+++ b/lib/lp/scripts/harness.py
1105@@ -81,8 +81,8 @@ def _get_locals():
1106 proj = ProjectGroup.get(1)
1107 b2 = Bug.get(2)
1108 b1 = Bug.get(1)
1109- s = Specification.get(1)
1110- q = Question.get(1)
1111+ s = store.get(Specification, 1)
1112+ q = store.get(Question, 1)
1113 # Silence unused name warnings
1114 d, p, ds, prod, proj, b2, b1, s, q
1115

Subscribers

People subscribed via source and target branches

to status/vote changes: