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

Proposed by Colin Watson
Status: Merged
Approved by: Colin Watson
Approved revision: acecce897e099996ced68f7110f291eb8f5cb4dd
Merge reported by: Otto Co-Pilot
Merged at revision: not available
Proposed branch: ~cjwatson/launchpad:stormify-product
Merge into: launchpad:master
Diff against target: 1252 lines (+308/-245)
23 files modified
lib/lp/bugs/doc/bugnotificationrecipients.rst (+2/-2)
lib/lp/codehosting/tests/test_acceptance.py (+8/-4)
lib/lp/registry/doc/commercialsubscription.rst (+2/-1)
lib/lp/registry/model/distribution.py (+10/-10)
lib/lp/registry/model/person.py (+25/-32)
lib/lp/registry/model/pillar.py (+3/-2)
lib/lp/registry/model/product.py (+171/-106)
lib/lp/registry/model/projectgroup.py (+5/-2)
lib/lp/registry/services/sharingservice.py (+4/-4)
lib/lp/registry/stories/product/xx-product-index.rst (+8/-5)
lib/lp/registry/stories/productrelease/xx-productrelease-basics.rst (+2/-1)
lib/lp/registry/stories/project/xx-project-index.rst (+4/-3)
lib/lp/registry/tests/test_commercialprojects_vocabularies.py (+1/-1)
lib/lp/registry/tests/test_product.py (+1/-1)
lib/lp/registry/vocabularies.py (+13/-21)
lib/lp/scripts/harness.py (+1/-1)
lib/lp/services/database/bulk.py (+1/-1)
lib/lp/services/statistics/model/statistics.py (+32/-35)
lib/lp/services/webapp/vocabulary.py (+5/-5)
lib/lp/translations/model/translationsoverview.py (+2/-1)
lib/lp/translations/stories/productseries/xx-productseries-translations.rst (+3/-3)
lib/lp/translations/stories/standalone/xx-product-export.rst (+4/-3)
lib/lp/translations/tests/test_autoapproval.py (+1/-1)
Reviewer Review Type Date Requested Status
Simone Pelosi Approve
Review via email: mp+450113@code.launchpad.net

Commit message

Convert Product to Storm

Description of the change

There are a number of places that try to handle either a `Distribution` or a `Product` using similar code, so to avoid too much hassle I also converted `Distribution.owner` and `Distribution.driver` to the new style. For similar reasons I ended up changing `SQLObjectVocabularyBase.__contains__` to accept `Storm` objects as well as `SQLBase` objects and to use Storm-style query syntax; in some ways this is a little odd, but `SQLObjectVocabularyBase` only has a few users left and will go away soon.

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/doc/bugnotificationrecipients.rst b/lib/lp/bugs/doc/bugnotificationrecipients.rst
2index c3688c9..ecbe624 100644
3--- a/lib/lp/bugs/doc/bugnotificationrecipients.rst
4+++ b/lib/lp/bugs/doc/bugnotificationrecipients.rst
5@@ -71,8 +71,8 @@ Let's up some data for our test:
6 ... )
7 >>> debian = Distribution.selectOneBy(name="debian")
8 >>> pmount = debian.getSourcePackage("pmount")
9- >>> alsa_utils = Product.selectOneBy(name="alsa-utils")
10- >>> gnomebaker = Product.selectOneBy(name="gnomebaker")
11+ >>> alsa_utils = IStore(Product).find(Product, name="alsa-utils").one()
12+ >>> gnomebaker = IStore(Product).find(Product, name="gnomebaker").one()
13 >>> personset = getUtility(IPersonSet)
14
15 Here's where getBugNotificationRecipients() starts off. First, a
16diff --git a/lib/lp/codehosting/tests/test_acceptance.py b/lib/lp/codehosting/tests/test_acceptance.py
17index ecf597a..ece2d62 100644
18--- a/lib/lp/codehosting/tests/test_acceptance.py
19+++ b/lib/lp/codehosting/tests/test_acceptance.py
20@@ -35,6 +35,7 @@ from lp.codehosting.vfs import branch_id_to_path
21 from lp.registry.model.person import Person
22 from lp.registry.model.product import Product
23 from lp.services.config import config
24+from lp.services.database.interfaces import IStore
25 from lp.services.testing.profiled import profiled
26 from lp.testing import TestCaseWithFactory
27 from lp.testing.layers import ZopelessAppServerLayer
28@@ -210,7 +211,7 @@ class SSHTestCase(TestCaseWithTransport, LoomTestMixin, TestCaseWithFactory):
29 if productName is None:
30 product = None
31 else:
32- product = Product.selectOneBy(name=productName)
33+ product = IStore(Product).find(Product, name=productName).one()
34 namespace = get_branch_namespace(owner, product)
35 return namespace.getByName(branchName)
36
37@@ -338,7 +339,7 @@ class AcceptanceTests(WithScenarios, SSHTestCase):
38 if product_name == "+junk":
39 product = None
40 else:
41- product = Product.selectOneBy(name=product_name)
42+ product = IStore(Product).find(Product, name=product_name).one()
43 if branch_type == BranchType.MIRRORED:
44 url = "http://example.com"
45 else:
46@@ -399,7 +400,10 @@ class AcceptanceTests(WithScenarios, SSHTestCase):
47 ZopelessAppServerLayer.txn.begin()
48 branch = self.getDatabaseBranch("testuser", None, "test-branch")
49 branch.owner.name = "renamed-user"
50- branch.setTarget(user=branch.owner, project=Product.byName("firefox"))
51+ branch.setTarget(
52+ user=branch.owner,
53+ project=IStore(Product).find(Product, name="firefox").one(),
54+ )
55 branch.name = "renamed-branch"
56 ZopelessAppServerLayer.txn.commit()
57
58@@ -542,7 +546,7 @@ class AcceptanceTests(WithScenarios, SSHTestCase):
59 # We can also push branches to URLs like /+branch/firefox
60 # Hack 'firefox' so we have permission to do this.
61 ZopelessAppServerLayer.txn.begin()
62- firefox = Product.selectOneBy(name="firefox")
63+ firefox = IStore(Product).find(Product, name="firefox").one()
64 testuser = Person.selectOneBy(name="testuser")
65 firefox.development_focus.owner = testuser
66 ZopelessAppServerLayer.txn.commit()
67diff --git a/lib/lp/registry/doc/commercialsubscription.rst b/lib/lp/registry/doc/commercialsubscription.rst
68index 6912fe4..fe06b94 100644
69--- a/lib/lp/registry/doc/commercialsubscription.rst
70+++ b/lib/lp/registry/doc/commercialsubscription.rst
71@@ -346,8 +346,9 @@ their commercial subscription was modified.
72 All the products are returned when no parameters are passed in.
73
74 >>> from lp.registry.model.product import Product
75+ >>> from lp.services.database.interfaces import IStore
76 >>> review_listing = product_set.forReview(commercial_member)
77- >>> review_listing.count() == Product.select().count()
78+ >>> review_listing.count() == IStore(Product).find(Product).count()
79 True
80
81 The full text search will not match strings with dots in their name
82diff --git a/lib/lp/registry/model/distribution.py b/lib/lp/registry/model/distribution.py
83index 7045053..81040cb 100644
84--- a/lib/lp/registry/model/distribution.py
85+++ b/lib/lp/registry/model/distribution.py
86@@ -261,12 +261,12 @@ class Distribution(
87 dbName="mugshot", foreignKey="LibraryFileAlias", default=None
88 )
89 domainname = StringCol(notNull=True)
90- owner = ForeignKey(
91- dbName="owner",
92- foreignKey="Person",
93- storm_validator=validate_person_or_closed_team,
94- notNull=True,
95+ owner_id = Int(
96+ name="owner",
97+ validator=validate_person_or_closed_team,
98+ allow_none=False,
99 )
100+ owner = Reference(owner_id, "Person.id")
101 registrant = ForeignKey(
102 dbName="registrant",
103 foreignKey="Person",
104@@ -282,13 +282,13 @@ class Distribution(
105 )
106 bug_reporting_guidelines = StringCol(default=None)
107 bug_reported_acknowledgement = StringCol(default=None)
108- driver = ForeignKey(
109- dbName="driver",
110- foreignKey="Person",
111- storm_validator=validate_public_person,
112- notNull=False,
113+ driver_id = Int(
114+ name="driver",
115+ validator=validate_public_person,
116+ allow_none=True,
117 default=None,
118 )
119+ driver = Reference(driver_id, "Person.id")
120 members = ForeignKey(
121 dbName="members",
122 foreignKey="Person",
123diff --git a/lib/lp/registry/model/person.py b/lib/lp/registry/model/person.py
124index 1aa22bc..6514417 100644
125--- a/lib/lp/registry/model/person.py
126+++ b/lib/lp/registry/model/person.py
127@@ -203,7 +203,6 @@ from lp.services.database.sqlbase import (
128 SQLBase,
129 convert_storm_clause_to_string,
130 cursor,
131- quote,
132 sqlvalues,
133 )
134 from lp.services.database.sqlobject import (
135@@ -1181,12 +1180,12 @@ class Person(
136 ownership_participation = ClassAlias(TeamParticipation)
137 clauses.extend(
138 [
139- Product._ownerID == ownership_participation.team_id,
140+ Product._owner_id == ownership_participation.team_id,
141 ownership_participation.person_id == self.id,
142 ]
143 )
144 else:
145- clauses.append(Product._ownerID == self.id)
146+ clauses.append(Product._owner_id == self.id)
147
148 # We only want to use the extra query if match_name is not None and it
149 # is not the empty string ('' or u'').
150@@ -1269,11 +1268,11 @@ class Person(
151 TeamParticipation, Person.id == TeamParticipation.person_id
152 ),
153 LeftJoin(
154- Product, TeamParticipation.team_id == Product._ownerID
155+ Product, TeamParticipation.team_id == Product._owner_id
156 ),
157 LeftJoin(
158 Distribution,
159- TeamParticipation.team_id == Distribution.ownerID,
160+ TeamParticipation.team_id == Distribution.owner_id,
161 ),
162 Join(
163 CommercialSubscription,
164@@ -1300,31 +1299,25 @@ class Person(
165 The given pillar must be either an IProduct or an IDistribution.
166 """
167 if IProduct.providedBy(pillar):
168- where_clause = "product = %s" % sqlvalues(pillar)
169+ pillar_clause = KarmaCache.product == pillar
170 elif IDistribution.providedBy(pillar):
171- where_clause = "distribution = %s" % sqlvalues(pillar)
172+ pillar_clause = KarmaCache.distribution == pillar
173 else:
174 raise AssertionError(
175 "Pillar must be a product or distro, got %s" % pillar
176 )
177- replacements = sqlvalues(person=self)
178- replacements["where_clause"] = where_clause
179- query = (
180- """
181- SELECT DISTINCT KarmaCategory.id
182- FROM KarmaCategory
183- JOIN KarmaCache ON KarmaCache.category = KarmaCategory.id
184- WHERE %(where_clause)s
185- AND category IS NOT NULL
186- AND person = %(person)s
187- """
188- % replacements
189- )
190- cur = cursor()
191- cur.execute(query)
192- ids = [id for [id] in cur.fetchall()]
193- return IStore(KarmaCategory).find(
194- KarmaCategory, KarmaCategory.id.is_in(ids)
195+ return (
196+ IStore(KarmaCategory)
197+ .using(
198+ KarmaCategory,
199+ Join(KarmaCache, KarmaCache.category == KarmaCategory.id),
200+ )
201+ .find(
202+ KarmaCategory,
203+ pillar_clause,
204+ IsNot(KarmaCache.category_id, None),
205+ KarmaCache.person == self,
206+ )
207 )
208
209 @property
210@@ -4561,14 +4554,14 @@ class PersonSet:
211
212 def getPeopleWithBranches(self, product=None):
213 """See `IPersonSet`."""
214- branch_clause = "SELECT owner FROM Branch"
215+ # Circular import.
216+ from lp.code.model.branch import Branch
217+
218+ branch_params = {}
219 if product is not None:
220- branch_clause += " WHERE product = %s" % quote(product)
221- return Person.select(
222- """
223- Person.id in (%s)
224- """
225- % branch_clause
226+ branch_params["where"] = Branch.product == product
227+ return IStore(Person).find(
228+ Person, Person.id.is_in(Select(Branch.owner_id, **branch_params))
229 )
230
231 def updatePersonalStandings(self):
232diff --git a/lib/lp/registry/model/pillar.py b/lib/lp/registry/model/pillar.py
233index 580d50d..de817fb 100644
234--- a/lib/lp/registry/model/pillar.py
235+++ b/lib/lp/registry/model/pillar.py
236@@ -307,7 +307,7 @@ class PillarNameSet:
237 pillar_names = set(rows).union(
238 load_related(PillarName, rows, ["alias_for"])
239 )
240- pillars = load_related(Product, pillar_names, ["productID"])
241+ pillars = load_related(Product, pillar_names, ["product_id"])
242 pillars.extend(
243 load_related(ProjectGroup, pillar_names, ["projectgroup_id"])
244 )
245@@ -333,7 +333,8 @@ class PillarName(SQLBase):
246 name = StringCol(
247 dbName="name", notNull=True, unique=True, alternateID=True
248 )
249- product = ForeignKey(foreignKey="Product", dbName="product")
250+ product_id = Int(name="product", allow_none=True)
251+ product = Reference(product_id, "Product.id")
252 projectgroup_id = Int(name="project", allow_none=True)
253 projectgroup = Reference(projectgroup_id, "ProjectGroup.id")
254 distribution = ForeignKey(foreignKey="Distribution", dbName="distribution")
255diff --git a/lib/lp/registry/model/product.py b/lib/lp/registry/model/product.py
256index f828474..f3bb669 100644
257--- a/lib/lp/registry/model/product.py
258+++ b/lib/lp/registry/model/product.py
259@@ -31,7 +31,16 @@ from storm.expr import (
260 Or,
261 Select,
262 )
263-from storm.locals import Int, List, Reference, ReferenceSet, Store, Unicode
264+from storm.locals import (
265+ Bool,
266+ DateTime,
267+ Int,
268+ List,
269+ Reference,
270+ ReferenceSet,
271+ Store,
272+ Unicode,
273+)
274 from zope.component import getUtility
275 from zope.event import notify
276 from zope.interface import implementer
277@@ -143,17 +152,10 @@ from lp.registry.model.sourcepackagename import SourcePackageName
278 from lp.registry.model.teammembership import TeamParticipation
279 from lp.services.database import bulk
280 from lp.services.database.constants import UTC_NOW
281-from lp.services.database.datetimecol import UtcDateTimeCol
282 from lp.services.database.decoratedresultset import DecoratedResultSet
283 from lp.services.database.enumcol import DBEnum
284 from lp.services.database.interfaces import IStore
285-from lp.services.database.sqlbase import SQLBase, sqlvalues
286-from lp.services.database.sqlobject import (
287- BoolCol,
288- ForeignKey,
289- SQLObjectNotFound,
290- StringCol,
291-)
292+from lp.services.database.stormbase import StormBase
293 from lp.services.database.stormexpr import (
294 ArrayAgg,
295 ArrayIntersects,
296@@ -245,7 +247,7 @@ specification_policy_default = {
297
298 @implementer(IBugSummaryDimension, IHasCustomLanguageCodes, IProduct)
299 class Product(
300- SQLBase,
301+ StormBase,
302 BugTargetBase,
303 HasDriversMixin,
304 OfficialBugTagTargetMixin,
305@@ -269,65 +271,60 @@ class Product(
306 ):
307 """A Product."""
308
309- _table = "Product"
310+ __storm_table__ = "Product"
311
312+ id = Int(primary=True)
313 projectgroup_id = Int(name="project", allow_none=True, default=None)
314 projectgroup = Reference(projectgroup_id, "ProjectGroup.id")
315- _owner = ForeignKey(
316- dbName="owner",
317- foreignKey="Person",
318- storm_validator=validate_person_or_closed_team,
319- notNull=True,
320+ _owner_id = Int(
321+ name="owner",
322+ validator=validate_person_or_closed_team,
323+ allow_none=False,
324 )
325- registrant = ForeignKey(
326- dbName="registrant",
327- foreignKey="Person",
328- storm_validator=validate_public_person,
329- notNull=True,
330+ _owner = Reference(_owner_id, "Person.id")
331+ registrant_id = Int(
332+ name="registrant", validator=validate_public_person, allow_none=False
333 )
334- bug_supervisor = ForeignKey(
335- dbName="bug_supervisor",
336- foreignKey="Person",
337- storm_validator=validate_person,
338- notNull=False,
339- default=None,
340- )
341- driver = ForeignKey(
342- dbName="driver",
343- foreignKey="Person",
344- storm_validator=validate_person,
345- notNull=False,
346+ registrant = Reference(registrant_id, "Person.id")
347+ bug_supervisor_id = Int(
348+ name="bug_supervisor",
349+ validator=validate_person,
350+ allow_none=True,
351 default=None,
352 )
353- name = StringCol(
354- dbName="name", notNull=True, alternateID=True, unique=True
355+ bug_supervisor = Reference(bug_supervisor_id, "Person.id")
356+ driver_id = Int(
357+ name="driver", validator=validate_person, allow_none=True, default=None
358 )
359- display_name = StringCol(dbName="displayname", notNull=True)
360- _title = StringCol(dbName="title", notNull=True)
361- summary = StringCol(dbName="summary", notNull=True)
362- description = StringCol(notNull=False, default=None)
363- datecreated = UtcDateTimeCol(
364- dbName="datecreated", notNull=True, default=UTC_NOW
365+ driver = Reference(driver_id, "Person.id")
366+ name = Unicode(name="name", allow_none=False)
367+ display_name = Unicode(name="displayname", allow_none=False)
368+ _title = Unicode(name="title", allow_none=False)
369+ summary = Unicode(name="summary", allow_none=False)
370+ description = Unicode(allow_none=True, default=None)
371+ datecreated = DateTime(
372+ name="datecreated",
373+ allow_none=False,
374+ default=UTC_NOW,
375+ tzinfo=timezone.utc,
376 )
377- homepageurl = StringCol(dbName="homepageurl", notNull=False, default=None)
378- homepage_content = StringCol(default=None)
379- icon_id = Int(name="icon", default=None)
380+ homepageurl = Unicode(name="homepageurl", allow_none=True, default=None)
381+ homepage_content = Unicode(default=None)
382+ icon_id = Int(name="icon", allow_none=True, default=None)
383 icon = Reference(icon_id, "LibraryFileAlias.id")
384- logo = ForeignKey(
385- dbName="logo", foreignKey="LibraryFileAlias", default=None
386+ logo_id = Int(name="logo", allow_none=True, default=None)
387+ logo = Reference(logo_id, "LibraryFileAlias.id")
388+ mugshot_id = Int(name="mugshot", allow_none=True, default=None)
389+ mugshot = Reference(mugshot_id, "LibraryFileAlias.id")
390+ screenshotsurl = Unicode(
391+ name="screenshotsurl", allow_none=True, default=None
392 )
393- mugshot = ForeignKey(
394- dbName="mugshot", foreignKey="LibraryFileAlias", default=None
395+ wikiurl = Unicode(name="wikiurl", allow_none=True, default=None)
396+ programminglang = Unicode(
397+ name="programminglang", allow_none=True, default=None
398 )
399- screenshotsurl = StringCol(
400- dbName="screenshotsurl", notNull=False, default=None
401- )
402- wikiurl = StringCol(dbName="wikiurl", notNull=False, default=None)
403- programminglang = StringCol(
404- dbName="programminglang", notNull=False, default=None
405- )
406- downloadurl = StringCol(dbName="downloadurl", notNull=False, default=None)
407- lastdoap = StringCol(dbName="lastdoap", notNull=False, default=None)
408+ downloadurl = Unicode(name="downloadurl", allow_none=True, default=None)
409+ lastdoap = Unicode(name="lastdoap", allow_none=True, default=None)
410 translationgroup_id = Int(
411 name="translationgroup", allow_none=True, default=None
412 )
413@@ -344,14 +341,14 @@ class Product(
414 translation_focus = Reference(translation_focus_id, "ProductSeries.id")
415 bugtracker_id = Int(name="bugtracker", allow_none=True, default=None)
416 bugtracker = Reference(bugtracker_id, "BugTracker.id")
417- official_answers = BoolCol(
418- dbName="official_answers", notNull=True, default=False
419+ official_answers = Bool(
420+ name="official_answers", allow_none=False, default=False
421 )
422- official_blueprints = BoolCol(
423- dbName="official_blueprints", notNull=True, default=False
424+ official_blueprints = Bool(
425+ name="official_blueprints", allow_none=False, default=False
426 )
427- official_malone = BoolCol(
428- dbName="official_malone", notNull=True, default=False
429+ official_malone = Bool(
430+ name="official_malone", allow_none=False, default=False
431 )
432 remote_product = Unicode(
433 name="remote_product", allow_none=True, default=None
434@@ -363,6 +360,70 @@ class Product(
435 # to an artifact in the policy is sufficient for access.
436 access_policies = List(type=Int())
437
438+ _creating = False
439+
440+ def __init__(
441+ self,
442+ owner,
443+ registrant,
444+ name,
445+ display_name,
446+ title,
447+ summary,
448+ projectgroup=None,
449+ bug_supervisor=None,
450+ driver=None,
451+ description=None,
452+ homepageurl=None,
453+ icon=None,
454+ logo=None,
455+ mugshot=None,
456+ screenshotsurl=None,
457+ wikiurl=None,
458+ programminglang=None,
459+ downloadurl=None,
460+ vcs=None,
461+ information_type=InformationType.PUBLIC,
462+ project_reviewed=False,
463+ sourceforgeproject=None,
464+ license_info=None,
465+ ):
466+ super().__init__()
467+ try:
468+ self._creating = True
469+ self.owner = owner
470+ self.registrant = registrant
471+ self.name = name
472+ self.display_name = display_name
473+ self._title = title
474+ self.summary = summary
475+ self.projectgroup = projectgroup
476+ self.bug_supervisor = bug_supervisor
477+ self.driver = driver
478+ self.description = description
479+ self.homepageurl = homepageurl
480+ self.icon = icon
481+ self.logo = logo
482+ self.mugshot = mugshot
483+ self.screenshotsurl = screenshotsurl
484+ self.wikiurl = wikiurl
485+ self.programminglang = programminglang
486+ self.downloadurl = downloadurl
487+ self.vcs = vcs
488+ self.information_type = information_type
489+ self.project_reviewed = project_reviewed
490+ self.sourceforgeproject = sourceforgeproject
491+ self.license_info = license_info
492+ except Exception:
493+ # If validating references such as `owner` fails, then the new
494+ # object may have been added to the store first. Remove it
495+ # again in that case.
496+ store = Store.of(self)
497+ if store is not None:
498+ store.remove(self)
499+ raise
500+ del self._creating
501+
502 @property
503 def displayname(self):
504 return self.display_name
505@@ -417,7 +478,7 @@ class Product(
506 if value in PROPRIETARY_INFORMATION_TYPES:
507 if self.answers_usage == ServiceUsage.LAUNCHPAD:
508 yield CannotChangeInformationType("Answers is enabled.")
509- if self._SO_creating or value not in PROPRIETARY_INFORMATION_TYPES:
510+ if self._creating or value not in PROPRIETARY_INFORMATION_TYPES:
511 return
512 # Additional checks when transitioning an existing product to a
513 # proprietary type
514@@ -524,7 +585,7 @@ class Product(
515 # maintainer as required for the Product.
516 # However, only on edits. If this is a new Product it's handled
517 # already.
518- if not self._SO_creating:
519+ if not self._creating:
520 if (
521 old_info_type == InformationType.PUBLIC
522 and value != InformationType.PUBLIC
523@@ -644,11 +705,11 @@ class Product(
524 """See `HasMilestonesMixin`."""
525 return Milestone.product == self
526
527- enable_bug_expiration = BoolCol(
528- dbName="enable_bug_expiration", notNull=True, default=False
529+ enable_bug_expiration = Bool(
530+ name="enable_bug_expiration", allow_none=False, default=False
531 )
532- project_reviewed = BoolCol(dbName="reviewed", notNull=True, default=False)
533- reviewer_whiteboard = StringCol(notNull=False, default=None)
534+ project_reviewed = Bool(name="reviewed", allow_none=False, default=False)
535+ reviewer_whiteboard = Unicode(allow_none=True, default=None)
536 private_bugs = False
537 bug_sharing_policy = DBEnum(
538 enum=BugSharingPolicy, allow_none=True, default=None
539@@ -661,9 +722,9 @@ class Product(
540 allow_none=True,
541 default=SpecificationSharingPolicy.PUBLIC,
542 )
543- autoupdate = BoolCol(dbName="autoupdate", notNull=True, default=False)
544+ autoupdate = Bool(name="autoupdate", allow_none=False, default=False)
545 freshmeatproject = None
546- sourceforgeproject = StringCol(notNull=False, default=None)
547+ sourceforgeproject = Unicode(allow_none=True, default=None)
548 # While the interface defines this field as required, we need to
549 # allow it to be NULL so we can create new product records before
550 # the corresponding series records.
551@@ -671,9 +732,9 @@ class Product(
552 name="development_focus", allow_none=True, default=None
553 )
554 development_focus = Reference(development_focus_id, "ProductSeries.id")
555- bug_reporting_guidelines = StringCol(default=None)
556- bug_reported_acknowledgement = StringCol(default=None)
557- enable_bugfiling_duplicate_search = BoolCol(notNull=True, default=True)
558+ bug_reporting_guidelines = Unicode(default=None)
559+ bug_reported_acknowledgement = Unicode(default=None)
560+ enable_bugfiling_duplicate_search = Bool(allow_none=False, default=True)
561
562 def _validate_active(self, attr, value):
563 # Validate deactivation.
564@@ -685,29 +746,27 @@ class Product(
565 )
566 return value
567
568- active = BoolCol(
569- dbName="active",
570- notNull=True,
571+ active = Bool(
572+ name="active",
573+ allow_none=False,
574 default=True,
575- storm_validator=_validate_active,
576+ validator=_validate_active,
577 )
578
579 def _validate_license_info(self, attr, value):
580- if not self._SO_creating and value != self.license_info:
581+ if not self._creating and value != self.license_info:
582 # Clear the project_reviewed and license_approved flags
583 # if the licence changes.
584 self._resetLicenseReview()
585 return value
586
587- license_info = StringCol(
588- dbName="license_info",
589- default=None,
590- storm_validator=_validate_license_info,
591+ license_info = Unicode(
592+ name="license_info", default=None, validator=_validate_license_info
593 )
594
595 def _validate_license_approved(self, attr, value):
596 """Ensure licence approved is only applied to the correct licences."""
597- if not self._SO_creating:
598+ if not self._creating:
599 licenses = list(self.licenses)
600 if value:
601 if (
602@@ -723,11 +782,11 @@ class Product(
603 self.project_reviewed = True
604 return value
605
606- license_approved = BoolCol(
607- dbName="license_approved",
608- notNull=True,
609+ license_approved = Bool(
610+ name="license_approved",
611+ allow_none=False,
612 default=False,
613- storm_validator=_validate_license_approved,
614+ validator=_validate_license_approved,
615 )
616
617 def getAllowedBugInformationTypes(self):
618@@ -1720,7 +1779,7 @@ def get_precached_products(
619 for attr_name in role_names:
620 person_ids.update(
621 map(
622- lambda x: getattr(x, attr_name + "ID"),
623+ lambda x: getattr(x, attr_name + "_id"),
624 products_by_id.values(),
625 )
626 )
627@@ -1880,7 +1939,7 @@ class ProductSet:
628 return result
629
630 def do_eager_load(rows):
631- owner_ids = set(map(operator.attrgetter("_ownerID"), rows))
632+ owner_ids = set(map(operator.attrgetter("_owner_id"), rows))
633 # +detailed-listing renders the person with team branding.
634 list(
635 getUtility(IPersonSet).getPrecachedPersonsFromIDs(
636@@ -1892,12 +1951,12 @@ class ProductSet:
637
638 def get(self, productid):
639 """See `IProductSet`."""
640- try:
641- return Product.get(productid)
642- except SQLObjectNotFound:
643+ product = IStore(Product).get(Product, productid)
644+ if product is None:
645 raise NotFoundError(
646 "Product with ID %s does not exist" % str(productid)
647 )
648+ return product
649
650 def getByName(self, name, ignore_inactive=False):
651 """See `IProductSet`."""
652@@ -1908,18 +1967,25 @@ class ProductSet:
653
654 def getProductsWithBranches(self, num_products=None):
655 """See `IProductSet`."""
656- results = Product.select(
657- """
658- Product.id in (
659- select distinct(product) from Branch
660- where lifecycle_status in %s)
661- and Product.active
662- """
663- % sqlvalues(DEFAULT_BRANCH_STATUS_IN_LISTING),
664- orderBy="name",
665+ results = (
666+ IStore(Product)
667+ .find(
668+ Product,
669+ Product.id.is_in(
670+ Select(
671+ Branch.product_id,
672+ where=Branch.lifecycle_status.is_in(
673+ DEFAULT_BRANCH_STATUS_IN_LISTING
674+ ),
675+ distinct=True,
676+ )
677+ ),
678+ Product.active,
679+ )
680+ .order_by(Product.name)
681 )
682 if num_products is not None:
683- results = results.limit(num_products)
684+ results = results[:num_products]
685 return results
686
687 def createProduct(
688@@ -1975,7 +2041,7 @@ class ProductSet:
689 registrant=registrant,
690 name=name,
691 display_name=display_name,
692- _title=title,
693+ title=title,
694 projectgroup=projectgroup,
695 summary=summary,
696 description=description,
697@@ -1983,7 +2049,6 @@ class ProductSet:
698 screenshotsurl=screenshotsurl,
699 wikiurl=wikiurl,
700 downloadurl=downloadurl,
701- freshmeatproject=None,
702 sourceforgeproject=sourceforgeproject,
703 programminglang=programminglang,
704 project_reviewed=project_reviewed,
705@@ -2211,7 +2276,7 @@ class ProductSet:
706 Product.id == ProductSeries.product_id,
707 POTemplate.productseries_id == ProductSeries.id,
708 Product.translations_usage == ServiceUsage.LAUNCHPAD,
709- Person.id == Product._ownerID,
710+ Person.id == Product._owner_id,
711 )
712 .config(distinct=True)
713 .order_by(Product.display_name)
714diff --git a/lib/lp/registry/model/projectgroup.py b/lib/lp/registry/model/projectgroup.py
715index 81457e3..668be1d 100644
716--- a/lib/lp/registry/model/projectgroup.py
717+++ b/lib/lp/registry/model/projectgroup.py
718@@ -175,6 +175,7 @@ class ProjectGroup(
719 logo=None,
720 mugshot=None,
721 ):
722+ super().__init__()
723 try:
724 self.owner = owner
725 self.registrant = registrant
726@@ -221,7 +222,9 @@ class ProjectGroup(
727 return list(self.getProducts(getUtility(ILaunchBag).user))
728
729 def getProduct(self, name):
730- return Product.selectOneBy(projectgroup=self, name=name)
731+ return (
732+ IStore(Product).find(Product, projectgroup=self, name=name).one()
733+ )
734
735 def getConfigurableProducts(self):
736 return [
737@@ -336,7 +339,7 @@ class ProjectGroup(
738 """See `OfficialBugTagTargetMixin`."""
739 And(
740 ProjectGroup.id == Product.projectgroup_id,
741- Product.id == OfficialBugTag.productID,
742+ Product.id == OfficialBugTag.product_id,
743 )
744
745 @property
746diff --git a/lib/lp/registry/services/sharingservice.py b/lib/lp/registry/services/sharingservice.py
747index 5fc8c27..65f355b 100644
748--- a/lib/lp/registry/services/sharingservice.py
749+++ b/lib/lp/registry/services/sharingservice.py
750@@ -162,13 +162,13 @@ class SharingService:
751 teams_sql = SQL("SELECT team from teams")
752 store = store.with_(with_statement)
753 if IProduct.implementedBy(pillar_class):
754- ownerID = pillar_class._ownerID
755+ owner_id = pillar_class._owner_id
756 else:
757- ownerID = pillar_class.ownerID
758+ owner_id = pillar_class.owner_id
759 filter = Or(
760 extra_filter or False,
761- ownerID.is_in(teams_sql),
762- pillar_class.driverID.is_in(teams_sql),
763+ owner_id.is_in(teams_sql),
764+ pillar_class.driver_id.is_in(teams_sql),
765 )
766 tables = [
767 AccessPolicyGrantFlat,
768diff --git a/lib/lp/registry/stories/product/xx-product-index.rst b/lib/lp/registry/stories/product/xx-product-index.rst
769index 06afe68..ec30192 100644
770--- a/lib/lp/registry/stories/product/xx-product-index.rst
771+++ b/lib/lp/registry/stories/product/xx-product-index.rst
772@@ -27,13 +27,14 @@ Evolution has no external links.
773 Now update Tomcat to actually have this data:
774
775 >>> import transaction
776- >>> from lp.services.database.sqlbase import flush_database_updates
777 >>> from lp.registry.enums import VCSType
778 >>> from lp.registry.model.product import Product
779+ >>> from lp.services.database.interfaces import IStore
780+ >>> from lp.services.database.sqlbase import flush_database_updates
781 >>> from lp.testing import ANONYMOUS, login, logout
782 >>> login(ANONYMOUS)
783
784- >>> tomcat = Product.selectOneBy(name="tomcat")
785+ >>> tomcat = IStore(Product).find(Product, name="tomcat").one()
786 >>> tomcat.vcs = VCSType.GIT
787 >>> tomcat.homepageurl = "http://home.page/"
788 >>> tomcat.sourceforgeproject = "sf-tomcat"
789@@ -67,7 +68,7 @@ When the sourceforge URL is identical to the homepage, we omit the homepage:
790
791 >>> login(ANONYMOUS)
792
793- >>> tomcat = Product.selectOneBy(name="tomcat")
794+ >>> tomcat = IStore(Product).find(Product, name="tomcat").one()
795 >>> tomcat.homepageurl = "http://sourceforge.net/projects/sf-tomcat"
796
797 >>> logout()
798@@ -130,7 +131,7 @@ If the project doesn't qualify for free hosting, or if it doesn't have
799 much time left on its commercial subscription, a portlet is displayed to
800 direct the owner to purchase a subscription.
801
802- >>> firefox = Product.selectOneBy(name="firefox")
803+ >>> firefox = IStore(Product).find(Product, name="firefox").one()
804 >>> ignored = login_person(firefox.owner)
805 >>> firefox.licenses = [License.OTHER_PROPRIETARY]
806 >>> firefox.license_info = "Internal project."
807@@ -285,7 +286,9 @@ Aliases
808 When a project has one or more aliases, they're shown on the project's
809 home page.
810
811- >>> Product.byName("firefox").setAliases(["iceweasel", "snowchicken"])
812+ >>> IStore(Product).find(Product, name="firefox").one().setAliases(
813+ ... ["iceweasel", "snowchicken"]
814+ ... )
815 >>> anon_browser.open("http://launchpad.test/firefox")
816 >>> print(extract_text(find_tag_by_id(anon_browser.contents, "aliases")))
817 Also known as: iceweasel, snowchicken
818diff --git a/lib/lp/registry/stories/productrelease/xx-productrelease-basics.rst b/lib/lp/registry/stories/productrelease/xx-productrelease-basics.rst
819index da4df2c..f25171d 100644
820--- a/lib/lp/registry/stories/productrelease/xx-productrelease-basics.rst
821+++ b/lib/lp/registry/stories/productrelease/xx-productrelease-basics.rst
822@@ -133,7 +133,8 @@ release too.
823
824 >>> from lp.registry.model.person import Person
825 >>> from lp.registry.model.product import Product
826- >>> tomcat = Product.selectOneBy(name="tomcat")
827+ >>> from lp.services.database.interfaces import IStore
828+ >>> tomcat = IStore(Product).find(Product, name="tomcat").one()
829 >>> print(tomcat.owner.name)
830 ubuntu-team
831
832diff --git a/lib/lp/registry/stories/project/xx-project-index.rst b/lib/lp/registry/stories/project/xx-project-index.rst
833index 6f170cb..dc3d406 100644
834--- a/lib/lp/registry/stories/project/xx-project-index.rst
835+++ b/lib/lp/registry/stories/project/xx-project-index.rst
836@@ -158,14 +158,15 @@ Inactive products are not included in that list, though.
837 # Use the DB classes directly to avoid having to setup a zope interaction
838 # (i.e. login()) and bypass the security proxy.
839 >>> from lp.registry.model.product import Product
840- >>> firefox = Product.byName("firefox")
841+ >>> from lp.services.database.interfaces import IStore
842+ >>> firefox = IStore(Product).find(Product, name="firefox").one()
843
844 # Unlink the source packages so the project can be deactivated.
845 >>> from lp.testing import unlink_source_packages
846 >>> login("admin@canonical.com")
847 >>> unlink_source_packages(firefox)
848 >>> firefox.active = False
849- >>> firefox.syncUpdate()
850+ >>> IStore(firefox).flush()
851
852 >>> logout()
853 >>> browser.open("http://launchpad.test/mozilla")
854@@ -176,7 +177,7 @@ Inactive products are not included in that list, though.
855 <a...Mozilla Thunderbird</a>
856
857 >>> firefox.active = True
858- >>> firefox.syncUpdate()
859+ >>> IStore(firefox).flush()
860
861
862 Project Group bug subscriptions
863diff --git a/lib/lp/registry/tests/test_commercialprojects_vocabularies.py b/lib/lp/registry/tests/test_commercialprojects_vocabularies.py
864index d9dbc16..3af8c17 100644
865--- a/lib/lp/registry/tests/test_commercialprojects_vocabularies.py
866+++ b/lib/lp/registry/tests/test_commercialprojects_vocabularies.py
867@@ -54,7 +54,7 @@ class TestCommProjVocabulary(TestCaseWithFactory):
868 def test_attributes(self):
869 self.assertEqual("Select a commercial project", self.vocab.displayname)
870 self.assertEqual("Search", self.vocab.step_title)
871- self.assertEqual("displayname", self.vocab._orderBy)
872+ self.assertEqual("displayname", self.vocab._order_by)
873
874 def test_searchForTerms_empty(self):
875 # An empty search will return all active maintained projects.
876diff --git a/lib/lp/registry/tests/test_product.py b/lib/lp/registry/tests/test_product.py
877index 895ee15..607d872 100644
878--- a/lib/lp/registry/tests/test_product.py
879+++ b/lib/lp/registry/tests/test_product.py
880@@ -1805,7 +1805,7 @@ class ProductAttributeCacheTestCase(TestCaseWithFactory):
881
882 def setUp(self):
883 super().setUp()
884- self.product = Product.selectOneBy(name="tomcat")
885+ self.product = IStore(Product).find(Product, name="tomcat").one()
886
887 def testLicensesCache(self):
888 """License cache should be cleared automatically."""
889diff --git a/lib/lp/registry/vocabularies.py b/lib/lp/registry/vocabularies.py
890index 8642d22..c03b5c4 100644
891--- a/lib/lp/registry/vocabularies.py
892+++ b/lib/lp/registry/vocabularies.py
893@@ -87,7 +87,6 @@ from zope.interface import implementer
894 from zope.schema.interfaces import IVocabularyTokenized
895 from zope.schema.vocabulary import SimpleTerm, SimpleVocabulary
896 from zope.security.interfaces import Unauthorized
897-from zope.security.proxy import isinstance as zisinstance
898 from zope.security.proxy import removeSecurityProxy
899
900 from lp.answers.interfaces.question import IQuestion
901@@ -147,7 +146,7 @@ from lp.registry.model.teammembership import TeamParticipation
902 from lp.services.database import bulk
903 from lp.services.database.decoratedresultset import DecoratedResultSet
904 from lp.services.database.interfaces import IStore
905-from lp.services.database.sqlbase import SQLBase, sqlvalues
906+from lp.services.database.sqlbase import sqlvalues
907 from lp.services.database.sqlobject import AND, CONTAINSSTRING, OR
908 from lp.services.database.stormexpr import (
909 RegexpMatch,
910@@ -247,27 +246,16 @@ class KarmaCategoryVocabulary(NamedStormVocabulary):
911
912
913 @implementer(IHugeVocabulary)
914-class ProductVocabulary(SQLObjectVocabularyBase):
915+class ProductVocabulary(StormVocabularyBase):
916 """All `IProduct` objects vocabulary."""
917
918 step_title = "Search"
919
920 _table = Product
921- _orderBy = "displayname"
922+ _order_by = "displayname"
923+ _clauses = [Product.active]
924 displayname = "Select a project"
925
926- def __contains__(self, obj):
927- # Sometimes this method is called with an SQLBase instance, but
928- # z3 form machinery sends through integer ids. This might be due
929- # to a bug somewhere.
930- where = "active='t' AND id=%d"
931- if zisinstance(obj, SQLBase):
932- product = self._table.selectOne(where % obj.id)
933- return product is not None and product == obj
934- else:
935- product = self._table.selectOne(where % int(obj))
936- return product is not None
937-
938 def toTerm(self, obj):
939 """See `IVocabulary`."""
940 return SimpleTerm(obj, obj.name, obj.title)
941@@ -276,7 +264,11 @@ class ProductVocabulary(SQLObjectVocabularyBase):
942 """See `IVocabularyTokenized`."""
943 # Product names are always lowercase.
944 token = token.lower()
945- product = self._table.selectOneBy(name=token, active=True)
946+ product = (
947+ IStore(self._table)
948+ .find(self._table, self._table.active, name=token)
949+ .one()
950+ )
951 if product is None:
952 raise LookupError(token)
953 return self.toTerm(product)
954@@ -584,7 +576,7 @@ class ValidPersonOrTeamVocabulary(
955 @cachedproperty
956 def store(self):
957 """The storm store."""
958- return IStore(Product)
959+ return IStore(Person)
960
961 @cachedproperty
962 def _karma_context_constraint(self):
963@@ -1521,7 +1513,7 @@ class MilestoneWithDateExpectedVocabulary(MilestoneVocabulary):
964
965
966 @implementer(IHugeVocabulary)
967-class CommercialProjectsVocabulary(NamedSQLObjectVocabulary):
968+class CommercialProjectsVocabulary(NamedStormVocabulary):
969 """List all commercial projects.
970
971 A commercial project is an active project that can have a commercial
972@@ -1531,7 +1523,7 @@ class CommercialProjectsVocabulary(NamedSQLObjectVocabulary):
973 """
974
975 _table = Product
976- _orderBy = "displayname"
977+ _order_by = "displayname"
978 step_title = "Search"
979
980 @property
981@@ -1982,7 +1974,7 @@ class PillarVocabularyBase(NamedStormHugeVocabulary):
982 store = IStore(PillarName)
983 origin = [
984 PillarName,
985- LeftJoin(Product, Product.id == PillarName.productID),
986+ LeftJoin(Product, Product.id == PillarName.product_id),
987 ]
988 base_clauses = [
989 ProductSet.getProductPrivacyFilter(getUtility(ILaunchBag).user)
990diff --git a/lib/lp/scripts/harness.py b/lib/lp/scripts/harness.py
991index 26ca7fe..d6dc0d0 100644
992--- a/lib/lp/scripts/harness.py
993+++ b/lib/lp/scripts/harness.py
994@@ -75,7 +75,7 @@ def _get_locals():
995 d = Distribution.get(1)
996 p = Person.get(1)
997 ds = DistroSeries.get(1)
998- prod = Product.get(1)
999+ prod = store.get(Product, 1)
1000 proj = store.get(ProjectGroup, 1)
1001 b2 = store.get(Bug, 2)
1002 b1 = store.get(Bug, 1)
1003diff --git a/lib/lp/services/database/bulk.py b/lib/lp/services/database/bulk.py
1004index bec5499..a90dc0f 100644
1005--- a/lib/lp/services/database/bulk.py
1006+++ b/lib/lp/services/database/bulk.py
1007@@ -185,7 +185,7 @@ def load_related(object_type, owning_objects, foreign_keys):
1008 :param object_type: The object type to load - e.g. Person.
1009 :param owning_objects: The objects holding the references. E.g. Bug.
1010 :param foreign_keys: A list of attributes that should be inspected for
1011- keys. e.g. ['ownerID']
1012+ keys. e.g. ['owner_id']
1013 """
1014 keys = set()
1015 for owning_object in owning_objects:
1016diff --git a/lib/lp/services/statistics/model/statistics.py b/lib/lp/services/statistics/model/statistics.py
1017index 11b5376..205b05c 100644
1018--- a/lib/lp/services/statistics/model/statistics.py
1019+++ b/lib/lp/services/statistics/model/statistics.py
1020@@ -18,12 +18,15 @@ from lp.answers.enums import QuestionStatus
1021 from lp.answers.model.question import Question
1022 from lp.app.enums import ServiceUsage
1023 from lp.blueprints.interfaces.specification import ISpecificationSet
1024+from lp.blueprints.model.specification import Specification
1025 from lp.bugs.model.bug import Bug
1026 from lp.bugs.model.bugtask import BugTask
1027 from lp.code.interfaces.branchcollection import IAllBranches
1028 from lp.code.interfaces.gitcollection import IAllGitRepositories
1029+from lp.code.model.branch import Branch
1030 from lp.registry.interfaces.person import IPersonSet
1031 from lp.registry.model.product import Product
1032+from lp.registry.model.productseries import ProductSeries
1033 from lp.services.database.constants import UTC_NOW
1034 from lp.services.database.interfaces import IStore
1035 from lp.services.database.sqlbase import cursor
1036@@ -115,7 +118,7 @@ class LaunchpadStatisticSet:
1037
1038 self.update(
1039 "products_using_malone",
1040- Product.selectBy(official_malone=True).count(),
1041+ store.find(Product, official_malone=True).count(),
1042 )
1043 ztm.commit()
1044
1045@@ -139,65 +142,59 @@ class LaunchpadStatisticSet:
1046 ztm.commit()
1047
1048 def _updateRegistryStatistics(self, ztm):
1049+ store = IStore(Product)
1050 self.update(
1051 "active_products",
1052- Product.select("active IS TRUE", distinct=True).count(),
1053+ store.find(Product, Product.active).config(distinct=True).count(),
1054 )
1055 self.update(
1056 "products_with_translations",
1057- Product.select(
1058- """
1059- POTemplate.productseries = ProductSeries.id AND
1060- Product.id = ProductSeries.product AND
1061- Product.active = TRUE
1062- """,
1063- clauseTables=["ProductSeries", "POTemplate"],
1064- distinct=True,
1065- ).count(),
1066+ store.find(
1067+ Product,
1068+ POTemplate.productseries == ProductSeries.id,
1069+ ProductSeries.product == Product.id,
1070+ Product.active,
1071+ )
1072+ .config(distinct=True)
1073+ .count(),
1074 )
1075 self.update(
1076 "products_with_blueprints",
1077- Product.select(
1078- "Specification.product=Product.id AND Product.active IS TRUE",
1079- distinct=True,
1080- clauseTables=["Specification"],
1081- ).count(),
1082+ store.find(
1083+ Product, Specification.product == Product.id, Product.active
1084+ )
1085+ .config(distinct=True)
1086+ .count(),
1087 )
1088 self.update(
1089 "products_with_branches",
1090- Product.select(
1091- "Branch.product=Product.id AND Product.active IS TRUE",
1092- distinct=True,
1093- clauseTables=["Branch"],
1094- ).count(),
1095+ store.find(Product, Branch.product == Product.id, Product.active)
1096+ .config(distinct=True)
1097+ .count(),
1098 )
1099 self.update(
1100 "products_with_bugs",
1101- Product.select(
1102- "BugTask.product=Product.id AND Product.active IS TRUE",
1103- distinct=True,
1104- clauseTables=["BugTask"],
1105- ).count(),
1106+ store.find(Product, BugTask.product == Product.id, Product.active)
1107+ .config(distinct=True)
1108+ .count(),
1109 )
1110 self.update(
1111 "products_with_questions",
1112- Product.select(
1113- "Question.product=Product.id AND Product.active IS TRUE",
1114- distinct=True,
1115- clauseTables=["Question"],
1116- ).count(),
1117+ store.find(Product, Question.product == Product.id, Product.active)
1118+ .config(distinct=True)
1119+ .count(),
1120 )
1121 self.update(
1122 "reviewed_products",
1123- Product.selectBy(project_reviewed=True, active=True).count(),
1124+ store.find(Product, project_reviewed=True, active=True).count(),
1125 )
1126
1127 def _updateRosettaStatistics(self, ztm):
1128 self.update(
1129 "products_using_rosetta",
1130- Product.selectBy(
1131- translations_usage=ServiceUsage.LAUNCHPAD
1132- ).count(),
1133+ IStore(Product)
1134+ .find(Product, translations_usage=ServiceUsage.LAUNCHPAD)
1135+ .count(),
1136 )
1137 self.update(
1138 "potemplate_count", IStore(POTemplate).find(POTemplate).count()
1139diff --git a/lib/lp/services/webapp/vocabulary.py b/lib/lp/services/webapp/vocabulary.py
1140index 057ecbf..294dd8c 100644
1141--- a/lib/lp/services/webapp/vocabulary.py
1142+++ b/lib/lp/services/webapp/vocabulary.py
1143@@ -329,19 +329,19 @@ class SQLObjectVocabularyBase(FilteredVocabularyBase):
1144 # Sometimes this method is called with an SQLBase instance, but
1145 # z3 form machinery sends through integer ids. This might be due
1146 # to a bug somewhere.
1147- if zisinstance(obj, SQLBase):
1148- clause = self._table.q.id == obj.id
1149+ if zisinstance(obj, (SQLBase, Storm)): # noqa: B1
1150+ clause = self._table.id == obj.id
1151 if self._filter:
1152 # XXX kiko 2007-01-16: this code is untested.
1153 clause = AND(clause, self._filter)
1154- found_obj = self._table.selectOne(clause)
1155+ found_obj = IStore(self._table).find(self._table, clause).one()
1156 return found_obj is not None and found_obj == obj
1157 else:
1158- clause = self._table.q.id == int(obj)
1159+ clause = self._table.id == int(obj)
1160 if self._filter:
1161 # XXX kiko 2007-01-16: this code is untested.
1162 clause = AND(clause, self._filter)
1163- found_obj = self._table.selectOne(clause)
1164+ found_obj = IStore(self._table).find(self._table, clause).one()
1165 return found_obj is not None
1166
1167 def getTerm(self, value):
1168diff --git a/lib/lp/translations/model/translationsoverview.py b/lib/lp/translations/model/translationsoverview.py
1169index 76c1a3f..793b1e5 100644
1170--- a/lib/lp/translations/model/translationsoverview.py
1171+++ b/lib/lp/translations/model/translationsoverview.py
1172@@ -8,6 +8,7 @@ from zope.interface import implementer
1173 from lp.app.enums import InformationType, ServiceUsage
1174 from lp.registry.model.distribution import Distribution
1175 from lp.registry.model.product import Product
1176+from lp.services.database.interfaces import IStore
1177 from lp.services.database.sqlbase import cursor, sqlvalues
1178 from lp.services.utils import round_half_up
1179 from lp.translations.interfaces.translationsoverview import (
1180@@ -94,7 +95,7 @@ class TranslationsOverview:
1181 if maximum is None or relative_karma > maximum:
1182 maximum = relative_karma
1183 if product_id is not None:
1184- pillar = Product.get(product_id)
1185+ pillar = IStore(Product).get(Product, product_id)
1186 elif distro_id is not None:
1187 pillar = Distribution.get(distro_id)
1188 else:
1189diff --git a/lib/lp/translations/stories/productseries/xx-productseries-translations.rst b/lib/lp/translations/stories/productseries/xx-productseries-translations.rst
1190index 55d0792..d39fa47 100644
1191--- a/lib/lp/translations/stories/productseries/xx-productseries-translations.rst
1192+++ b/lib/lp/translations/stories/productseries/xx-productseries-translations.rst
1193@@ -180,7 +180,8 @@ Launchpad Translations.
1194
1195 # Use the raw DB object to bypass the security proxy.
1196 >>> from lp.registry.model.product import Product
1197- >>> product = Product.byName("bazaar")
1198+ >>> from lp.services.database.interfaces import IStore
1199+ >>> product = IStore(Product).find(Product, name="bazaar").one()
1200 >>> product.translations_usage = ServiceUsage.NOT_APPLICABLE
1201
1202 When the owner now visits the upload page for trunk, there's a notice.
1203@@ -416,9 +417,8 @@ project admin does not see the link for configuring other branches.
1204 A new series is added.
1205
1206 >>> from lp.registry.interfaces.series import SeriesStatus
1207- >>> from lp.registry.model.product import Product
1208 >>> login("foo.bar@canonical.com")
1209- >>> evolution = Product.byName("evolution")
1210+ >>> evolution = IStore(Product).find(Product, name="evolution").one()
1211 >>> series = factory.makeProductSeries(product=evolution, name="evo-new")
1212 >>> series.status = SeriesStatus.EXPERIMENTAL
1213 >>> logout()
1214diff --git a/lib/lp/translations/stories/standalone/xx-product-export.rst b/lib/lp/translations/stories/standalone/xx-product-export.rst
1215index 73edc9c..d0f7bce 100644
1216--- a/lib/lp/translations/stories/standalone/xx-product-export.rst
1217+++ b/lib/lp/translations/stories/standalone/xx-product-export.rst
1218@@ -47,9 +47,10 @@ Use the DB classes directly to avoid having to setup a zope interaction
1219
1220 >>> from lp.app.enums import ServiceUsage
1221 >>> from lp.registry.model.product import Product
1222- >>> product = Product.byName("evolution")
1223+ >>> from lp.services.database.interfaces import IStore
1224+ >>> product = IStore(Product).find(Product, name="evolution").one()
1225 >>> product.translations_usage = ServiceUsage.NOT_APPLICABLE
1226- >>> product.sync()
1227+ >>> IStore(product).flush()
1228 >>> browser.open("http://translations.launchpad.test/evolution")
1229 >>> browser.getLink("download")
1230 Traceback (most recent call last):
1231@@ -59,7 +60,7 @@ Use the DB classes directly to avoid having to setup a zope interaction
1232 Restore previous state for subsequent tests, and verify.
1233
1234 >>> product.translations_usage = ServiceUsage.LAUNCHPAD
1235- >>> product.sync()
1236+ >>> IStore(product).flush()
1237 >>> browser.open("http://translations.launchpad.test/evolution")
1238 >>> browser.getLink("download") is not None
1239 True
1240diff --git a/lib/lp/translations/tests/test_autoapproval.py b/lib/lp/translations/tests/test_autoapproval.py
1241index de59805..eeadbb0 100644
1242--- a/lib/lp/translations/tests/test_autoapproval.py
1243+++ b/lib/lp/translations/tests/test_autoapproval.py
1244@@ -1203,7 +1203,7 @@ class TestCleanup(TestCaseWithFactory, GardenerDbUserMixin):
1245 self.assertTrue(self._exists(entry_id))
1246
1247 entry.productseries.product.active = False
1248- entry.productseries.product.syncUpdate()
1249+ self.store.flush()
1250
1251 self.becomeTheGardener()
1252 self.queue._cleanUpInactiveProductEntries(self.store)

Subscribers

People subscribed via source and target branches

to status/vote changes: