Merge ~cjwatson/launchpad:stormify-product into launchpad:master
- Git
- lp:~cjwatson/launchpad
- stormify-product
- Merge into 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) |
Related bugs: |
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.
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | diff --git a/lib/lp/bugs/doc/bugnotificationrecipients.rst b/lib/lp/bugs/doc/bugnotificationrecipients.rst |
2 | index 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 |
16 | diff --git a/lib/lp/codehosting/tests/test_acceptance.py b/lib/lp/codehosting/tests/test_acceptance.py |
17 | index 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() |
67 | diff --git a/lib/lp/registry/doc/commercialsubscription.rst b/lib/lp/registry/doc/commercialsubscription.rst |
68 | index 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 |
82 | diff --git a/lib/lp/registry/model/distribution.py b/lib/lp/registry/model/distribution.py |
83 | index 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", |
123 | diff --git a/lib/lp/registry/model/person.py b/lib/lp/registry/model/person.py |
124 | index 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): |
232 | diff --git a/lib/lp/registry/model/pillar.py b/lib/lp/registry/model/pillar.py |
233 | index 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") |
255 | diff --git a/lib/lp/registry/model/product.py b/lib/lp/registry/model/product.py |
256 | index 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) |
714 | diff --git a/lib/lp/registry/model/projectgroup.py b/lib/lp/registry/model/projectgroup.py |
715 | index 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 |
746 | diff --git a/lib/lp/registry/services/sharingservice.py b/lib/lp/registry/services/sharingservice.py |
747 | index 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, |
768 | diff --git a/lib/lp/registry/stories/product/xx-product-index.rst b/lib/lp/registry/stories/product/xx-product-index.rst |
769 | index 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 |
818 | diff --git a/lib/lp/registry/stories/productrelease/xx-productrelease-basics.rst b/lib/lp/registry/stories/productrelease/xx-productrelease-basics.rst |
819 | index 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 | |
832 | diff --git a/lib/lp/registry/stories/project/xx-project-index.rst b/lib/lp/registry/stories/project/xx-project-index.rst |
833 | index 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 |
863 | diff --git a/lib/lp/registry/tests/test_commercialprojects_vocabularies.py b/lib/lp/registry/tests/test_commercialprojects_vocabularies.py |
864 | index 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. |
876 | diff --git a/lib/lp/registry/tests/test_product.py b/lib/lp/registry/tests/test_product.py |
877 | index 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.""" |
889 | diff --git a/lib/lp/registry/vocabularies.py b/lib/lp/registry/vocabularies.py |
890 | index 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) |
990 | diff --git a/lib/lp/scripts/harness.py b/lib/lp/scripts/harness.py |
991 | index 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) |
1003 | diff --git a/lib/lp/services/database/bulk.py b/lib/lp/services/database/bulk.py |
1004 | index 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: |
1016 | diff --git a/lib/lp/services/statistics/model/statistics.py b/lib/lp/services/statistics/model/statistics.py |
1017 | index 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() |
1139 | diff --git a/lib/lp/services/webapp/vocabulary.py b/lib/lp/services/webapp/vocabulary.py |
1140 | index 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): |
1168 | diff --git a/lib/lp/translations/model/translationsoverview.py b/lib/lp/translations/model/translationsoverview.py |
1169 | index 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: |
1189 | diff --git a/lib/lp/translations/stories/productseries/xx-productseries-translations.rst b/lib/lp/translations/stories/productseries/xx-productseries-translations.rst |
1190 | index 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() |
1214 | diff --git a/lib/lp/translations/stories/standalone/xx-product-export.rst b/lib/lp/translations/stories/standalone/xx-product-export.rst |
1215 | index 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 |
1240 | diff --git a/lib/lp/translations/tests/test_autoapproval.py b/lib/lp/translations/tests/test_autoapproval.py |
1241 | index 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) |
LGTM!