Merge ~pappacena/launchpad:stormify-bug-task into launchpad:master

Proposed by Thiago F. Pappacena
Status: Merged
Approved by: Thiago F. Pappacena
Approved revision: 2f08d3244eaaca8c0543a2ea9e8905a84cacc209
Merge reported by: Otto Co-Pilot
Merged at revision: not available
Proposed branch: ~pappacena/launchpad:stormify-bug-task
Merge into: launchpad:master
Diff against target: 1317 lines (+273/-229)
28 files modified
lib/lp/bugs/browser/buglisting.py (+3/-3)
lib/lp/bugs/browser/cvereport.py (+2/-2)
lib/lp/bugs/browser/person.py (+2/-2)
lib/lp/bugs/browser/tests/bugs-views.txt (+4/-1)
lib/lp/bugs/browser/tests/test_bugtask.py (+5/-5)
lib/lp/bugs/configure.zcml (+8/-8)
lib/lp/bugs/doc/bugtask-expiration.txt (+6/-3)
lib/lp/bugs/doc/bugtask-retrieval.txt (+10/-6)
lib/lp/bugs/doc/bugtask-search.txt (+27/-12)
lib/lp/bugs/feed/bug.py (+3/-3)
lib/lp/bugs/interfaces/bugtask.py (+12/-8)
lib/lp/bugs/interfaces/bugtaskfilter.py (+3/-3)
lib/lp/bugs/model/bug.py (+11/-11)
lib/lp/bugs/model/bugtask.py (+124/-109)
lib/lp/bugs/model/bugtasksearch.py (+22/-22)
lib/lp/bugs/model/cve.py (+1/-1)
lib/lp/bugs/model/personsubscriptioninfo.py (+3/-3)
lib/lp/bugs/scripts/bugsummaryrebuild.py (+4/-4)
lib/lp/bugs/scripts/bugtasktargetnamecaches.py (+3/-3)
lib/lp/code/model/branchcollection.py (+1/-1)
lib/lp/registry/browser/milestone.py (+2/-2)
lib/lp/registry/model/distribution.py (+1/-1)
lib/lp/registry/model/distroseries.py (+2/-2)
lib/lp/registry/model/person.py (+3/-3)
lib/lp/registry/model/product.py (+1/-1)
lib/lp/registry/model/productseries.py (+3/-3)
lib/lp/registry/model/sourcepackage.py (+6/-6)
lib/lp/registry/scripts/closeaccount.py (+1/-1)
Reviewer Review Type Date Requested Status
Colin Watson (community) Approve
Ioana Lasc (community) Approve
Review via email: mp+393473@code.launchpad.net

Commit message

Stormifying BugTask class, to prepare it to receive foreign key to OCI project

Description of the change

This MP is quite big in terms of lines changed, but it's mostly a refactoring, and the final behavior shouldn't have changed.

To post a comment you must log in.
Revision history for this message
Thiago F. Pappacena (pappacena) wrote :

The changes are all related to BugTask refactoring. Mostly:

- Using StormBase instead of SQLBase class;

- Renaming the foreign key ID attributes to match newer pattern ("foreignkey_id", instead of "foreignKeyID"). This change itself is responsible for most of the lines changed, since there were queries across the code base using BugTask attributes (like BugTask.bugID, BugTask.assigneeID, etc);

- Refactoring of the `validate_conjoined_attribute` method to keep track of the changed attributes separately, on `BugTask.passthrough_attrs` dict; currently, we are setting a `PassthroughValue` object wrapper in some attributes, but Storm doesn't allow it (an `Int` attribute only accepts ints, for example).

- Replacing `BugTask.get(id)` with `getUtility(BugTaskSet).get(id)`;

- Changing `BugTask.select`/`BugTask.selectBy` with `store.find(BugTask, ...)`

Revision history for this message
Ioana Lasc (ilasc) wrote :

Nicely done!

A few minor comments around make lint failures:

./lib/lp/bugs/doc/bugtask-expiration.txt
     159: source exceeds 78 characters.
./lib/lp/bugs/doc/bugtask-retrieval.txt
       1: narrative uses a moin header.
      17: narrative uses a moin header.
      26: source has trailing whitespace.
      42: narrative uses a moin header.
       8: 'NotFoundError' imported but unused
./lib/lp/bugs/doc/bugtask-search.txt
       1: narrative uses a moin header.
      27: narrative uses a moin header.
      58: narrative uses a moin header.
     116: narrative uses a moin header.
     150: narrative uses a moin header.
     155: narrative uses a moin header.
     203: narrative uses a moin header.
     222: narrative uses a moin header.
     245: narrative uses a moin header.

You can use this as an example for the moin header issues: https://code.launchpad.net/~ilasc/launchpad/+git/launchpad/+merge/390284

review: Needs Fixing
Revision history for this message
Thiago F. Pappacena (pappacena) wrote :

Good catch, ilasc! Thanks for the review!

It should be good now.

Revision history for this message
Ioana Lasc (ilasc) :
review: Approve
Revision history for this message
Colin Watson (cjwatson) wrote :

This mostly looks fine, but I'm interested in your thoughts on my comments regarding PassthroughValue; maybe there's some other reason for the refactoring that I missed.

review: Approve
3f09d61... by Thiago F. Pappacena

Changing _defaultOrder attribute to __storm_order__ on BugTask

Revision history for this message
Thiago F. Pappacena (pappacena) wrote :

ctwatson, I've added a reply to your comment on PassthoughValue, and submitted a patch for the other 2 comments.

Revision history for this message
Colin Watson (cjwatson) :
Revision history for this message
Thiago F. Pappacena (pappacena) :
Revision history for this message
Colin Watson (cjwatson) :
Revision history for this message
Thiago F. Pappacena (pappacena) :
2f08d32... by Thiago F. Pappacena

Reverting the PassthroughValue wrapper removal

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/lib/lp/bugs/browser/buglisting.py b/lib/lp/bugs/browser/buglisting.py
2index c0e67a6..6acbf8a 100644
3--- a/lib/lp/bugs/browser/buglisting.py
4+++ b/lib/lp/bugs/browser/buglisting.py
5@@ -1,4 +1,4 @@
6-# Copyright 2009-2019 Canonical Ltd. This software is licensed under the
7+# Copyright 2009-2020 Canonical Ltd. This software is licensed under the
8 # GNU Affero General Public License version 3 (see the file LICENSE).
9
10 """IBugTask-related browser views."""
11@@ -657,8 +657,8 @@ class BugTaskListingItem:
12 else:
13 milestone_name = None
14 assignee = None
15- if self.assigneeID is not None:
16- assignee = self.people[self.assigneeID].displayname
17+ if self.assignee_id is not None:
18+ assignee = self.people[self.assignee_id].displayname
19 reporter = self.people[self.bug.ownerID]
20
21 # the case that there is no target context (e.g. viewing bug that
22diff --git a/lib/lp/bugs/browser/cvereport.py b/lib/lp/bugs/browser/cvereport.py
23index 9626295..e799288 100644
24--- a/lib/lp/bugs/browser/cvereport.py
25+++ b/lib/lp/bugs/browser/cvereport.py
26@@ -1,4 +1,4 @@
27-# Copyright 2009-2011 Canonical Ltd. This software is licensed under the
28+# Copyright 2009-2020 Canonical Ltd. This software is licensed under the
29 # GNU Affero General Public License version 3 (see the file LICENSE).
30
31 """Views to generate CVE reports (as in distro & distroseries/+cve pages)."""
32@@ -122,7 +122,7 @@ class CVEReportView(LaunchpadView):
33
34 # The page contains links to the bug task assignees:
35 # Pre-load the related Person and ValidPersonCache records
36- assignee_ids = [task.assigneeID for task in bugtasks]
37+ assignee_ids = [task.assignee_id for task in bugtasks]
38 list(getUtility(IPersonSet).getPrecachedPersonsFromIDs(
39 assignee_ids, need_validity=True))
40
41diff --git a/lib/lp/bugs/browser/person.py b/lib/lp/bugs/browser/person.py
42index c25219d..b3f4fae 100644
43--- a/lib/lp/bugs/browser/person.py
44+++ b/lib/lp/bugs/browser/person.py
45@@ -1,4 +1,4 @@
46-# Copyright 2012 Canonical Ltd. This software is licensed under the
47+# Copyright 2012-2020 Canonical Ltd. This software is licensed under the
48 # GNU Affero General Public License version 3 (see the file LICENSE).
49
50 """IPerson browser views related to bugs."""
51@@ -132,7 +132,7 @@ class RelevantMilestonesMixin:
52 """Return data used to render the milestone checkboxes."""
53 tasks = self.searchUnbatched()
54 milestones = sorted(
55- load_related(Milestone, tasks, ['milestoneID']),
56+ load_related(Milestone, tasks, ['milestone_id']),
57 key=milestone_sort_key, reverse=True)
58 return [
59 dict(title=milestone.title, value=milestone.id, checked=False)
60diff --git a/lib/lp/bugs/browser/tests/bugs-views.txt b/lib/lp/bugs/browser/tests/bugs-views.txt
61index 54baf33..59fce27 100644
62--- a/lib/lp/bugs/browser/tests/bugs-views.txt
63+++ b/lib/lp/bugs/browser/tests/bugs-views.txt
64@@ -22,8 +22,11 @@ Launchpad currently.
65
66 >>> from lp.bugs.interfaces.bugtask import BugTaskStatus
67 >>> from lp.bugs.model.bugtask import BugTask
68+ >>> from lp.services.database.interfaces import IStore
69+ >>> store = IStore(BugTask)
70 >>> [bugtask.bug.id
71- ... for bugtask in BugTask.selectBy(_status=BugTaskStatus.FIXRELEASED)]
72+ ... for bugtask in store.find(
73+ ... BugTask, BugTask._status == BugTaskStatus.FIXRELEASED)]
74 [8]
75 >>> for bug in bugs_view.most_recently_fixed_bugs:
76 ... print("%s: %s" % (bug.id, bug.title))
77diff --git a/lib/lp/bugs/browser/tests/test_bugtask.py b/lib/lp/bugs/browser/tests/test_bugtask.py
78index dd33206..cb98e74 100644
79--- a/lib/lp/bugs/browser/tests/test_bugtask.py
80+++ b/lib/lp/bugs/browser/tests/test_bugtask.py
81@@ -129,7 +129,7 @@ class TestBugTaskView(TestCaseWithFactory):
82 0, 10, login_method=lambda: login(ADMIN_EMAIL))
83 # This may seem large: it is; there is easily another 25% fat in
84 # there.
85- self.assertThat(recorder1, HasQueryCount(LessThan(85)))
86+ self.assertThat(recorder1, HasQueryCount(LessThan(86)))
87 self.assertThat(recorder2, HasQueryCount.byEquality(recorder1))
88
89 def test_rendered_query_counts_constant_with_attachments(self):
90@@ -140,7 +140,7 @@ class TestBugTaskView(TestCaseWithFactory):
91 lambda: self.getUserBrowser(url, person),
92 lambda: self.factory.makeBugAttachment(bug=task.bug),
93 1, 9, login_method=lambda: login(ADMIN_EMAIL))
94- self.assertThat(recorder1, HasQueryCount(LessThan(86)))
95+ self.assertThat(recorder1, HasQueryCount(LessThan(87)))
96 self.assertThat(recorder2, HasQueryCount.byEquality(recorder1))
97
98 def makeLinkedBranchMergeProposal(self, sourcepackage, bug, owner):
99@@ -175,7 +175,7 @@ class TestBugTaskView(TestCaseWithFactory):
100 recorder1, recorder2 = record_two_runs(
101 lambda: self.getUserBrowser(url, owner),
102 make_merge_proposals, 0, 1)
103- self.assertThat(recorder1, HasQueryCount(LessThan(93)))
104+ self.assertThat(recorder1, HasQueryCount(LessThan(94)))
105 # Ideally this should be much fewer, but this tries to keep a win of
106 # removing more than half of these.
107 self.assertThat(
108@@ -221,7 +221,7 @@ class TestBugTaskView(TestCaseWithFactory):
109 lambda: self.getUserBrowser(url, person),
110 lambda: add_activity("description", self.factory.makePerson()),
111 1, 20, login_method=lambda: login(ADMIN_EMAIL))
112- self.assertThat(recorder1, HasQueryCount(LessThan(86)))
113+ self.assertThat(recorder1, HasQueryCount(LessThan(87)))
114 self.assertThat(recorder2, HasQueryCount.byEquality(recorder1))
115
116 def test_rendered_query_counts_constant_with_milestones(self):
117@@ -231,7 +231,7 @@ class TestBugTaskView(TestCaseWithFactory):
118
119 with celebrity_logged_in('admin'):
120 browses_under_limit = BrowsesWithQueryLimit(
121- 86, self.factory.makePerson())
122+ 87, self.factory.makePerson())
123
124 self.assertThat(bug, browses_under_limit)
125
126diff --git a/lib/lp/bugs/configure.zcml b/lib/lp/bugs/configure.zcml
127index 7edae44..3e4544b 100644
128--- a/lib/lp/bugs/configure.zcml
129+++ b/lib/lp/bugs/configure.zcml
130@@ -167,9 +167,9 @@
131 <allow
132 attributes="
133 id
134- assigneeID
135+ assignee_id
136 bug
137- bugID
138+ bug_id
139 target
140 date_assigned
141 datecreated
142@@ -182,12 +182,12 @@
143 date_fix_released
144 date_left_closed
145 date_closed
146- distributionID
147- distroseriesID
148- milestoneID
149- productID
150- productseriesID
151- sourcepackagenameID
152+ distribution_id
153+ distroseries_id
154+ milestone_id
155+ product_id
156+ productseries_id
157+ sourcepackagename_id
158 task_age
159 bug_subscribers
160 is_complete
161diff --git a/lib/lp/bugs/doc/bugtask-expiration.txt b/lib/lp/bugs/doc/bugtask-expiration.txt
162index 4d4e82b..11b755e 100644
163--- a/lib/lp/bugs/doc/bugtask-expiration.txt
164+++ b/lib/lp/bugs/doc/bugtask-expiration.txt
165@@ -156,7 +156,8 @@ Let's also make some bugs that almost qualify for expiration.
166 >>> ubuntu_alsa = ubuntu.getSourcePackage('alsa-utils')
167 >>> another_assigned_bugtask = create_old_bug(
168 ... 'assigned', 61, ubuntu, assignee=sample_person)
169- >>> another_assigned_bugtask.transitionToTarget(ubuntu_alsa, sample_person)
170+ >>> another_assigned_bugtask.transitionToTarget(
171+ ... ubuntu_alsa, sample_person)
172 >>> ubuntu_evolution = ubuntu.getSourcePackage('evolution')
173 >>> invalid_bugtask = bugtaskset.createTask(
174 ... another_assigned_bugtask.bug, sample_person, ubuntu_evolution,
175@@ -472,7 +473,9 @@ Running the script
176 There are no Expired Bugtasks in sampledata, from the tests above.
177
178 >>> from lp.bugs.model.bugtask import BugTask
179- >>> BugTask.selectBy(status=BugTaskStatus.EXPIRED).count()
180+ >>> from lp.services.database.interfaces import IStore
181+ >>> store = IStore(BugTask)
182+ >>> store.find(BugTask, BugTask.status == BugTaskStatus.EXPIRED).count()
183 0
184
185 We want to check the hoary bugtask messages later.
186@@ -510,7 +513,7 @@ config.malone.expiration_dbuser.
187 >>> process.returncode
188 0
189
190- >>> bugtasks = [BugTask.get(bugtask.id) for bugtask in bugtasks]
191+ >>> bugtasks = [bugtaskset.get(bugtask.id) for bugtask in bugtasks]
192
193
194 After the script has run
195diff --git a/lib/lp/bugs/doc/bugtask-retrieval.txt b/lib/lp/bugs/doc/bugtask-retrieval.txt
196index e78c433..5fac87c 100644
197--- a/lib/lp/bugs/doc/bugtask-retrieval.txt
198+++ b/lib/lp/bugs/doc/bugtask-retrieval.txt
199@@ -1,19 +1,21 @@
200-= Retrieving bug tasks =
201+Retrieving bug tasks
202+*********************
203
204 The IBugTaskSet interface provide a couple of methods for retrieving
205 IBugTask instances. For convenience, Launchpad provides a default
206 implementation of IBugTaskSet, allowing retrieval of any bug task in
207 Launchpad. We'll use this implementation for demonstration purposes:
208
209- >>> from lp.app.errors import NotFoundError
210 >>> from lp.bugs.interfaces.bugtask import (
211 ... IBugTask,
212 ... IBugTaskSet,
213 ... )
214+ >>> from lp.services.database.interfaces import IStore
215 >>> task_set = getUtility(IBugTaskSet)
216
217
218-== Retrieving a single bug task ==
219+Retrieving a single bug task
220+*****************************
221
222 The IBugTaskSet get method retrieves a single bug task matching a given
223 ID:
224@@ -22,7 +24,7 @@ ID:
225 >>> retrieved_task
226 <BugTask ...>
227
228- >>> from lp.testing import verifyObject
229+ >>> from lp.testing import verifyObject
230 >>> verifyObject(IBugTask, retrieved_task)
231 True
232
233@@ -38,15 +40,17 @@ raises a NotFoundError:
234 NotFoundError: ...
235
236
237-== Retrieving multiple bug tasks ==
238+Retrieving multiple bug tasks
239+******************************
240
241 The IBugTaskSet getMultiple method can retrieve multiple bug tasks in a
242 single operation. To demonstrate, we'll begin by generating a list of
243 sample bug tasks:
244
245 >>> from lp.bugs.model.bugtask import BugTask
246+ >>> store = IStore(BugTask)
247 >>> sample_task_count = 10
248- >>> sample_tasks = BugTask.select(limit=sample_task_count)
249+ >>> sample_tasks = store.find(BugTask)[:sample_task_count]
250 >>> sample_task_ids = [task.id for task in sample_tasks]
251
252 When given a sequence of bug task IDs, the method returns a dictionary
253diff --git a/lib/lp/bugs/doc/bugtask-search.txt b/lib/lp/bugs/doc/bugtask-search.txt
254index 750b3c5..1e45abe 100644
255--- a/lib/lp/bugs/doc/bugtask-search.txt
256+++ b/lib/lp/bugs/doc/bugtask-search.txt
257@@ -1,4 +1,5 @@
258-= Searching BugTasks =
259+Searching BugTasks
260+*******************
261
262 BugTasks are usually searched through an IBugTarget's searchTasks()
263 method, but they all delegate the search to IBugTaskSet.search(). That
264@@ -6,19 +7,26 @@ method accepts a single parameter; an BugTaskSearchParams instance.
265
266 >>> from lp.bugs.interfaces.bugtask import IBugTaskSet
267 >>> from lp.bugs.interfaces.bugtasksearch import BugTaskSearchParams
268+ >>> from lp.services.database.interfaces import IStore
269 >>> bugtask_set = getUtility(IBugTaskSet)
270 >>> all_public = BugTaskSearchParams(user=None)
271 >>> found_bugtasks = bugtask_set.search(all_public)
272
273+ >>> from lp.app.enums import InformationType
274+ >>> from lp.bugs.model.bug import Bug
275 >>> from lp.bugs.model.bugtask import BugTask
276- >>> all_public_bugtasks = BugTask.select(
277- ... "BugTask.bug = Bug.id AND Bug.information_type IN (1, 2)",
278- ... clauseTables=['Bug'])
279+ >>> store = IStore(BugTask)
280+ >>> info_types = [InformationType.PUBLIC, InformationType.PUBLICSECURITY]
281+ >>> all_public_bugtasks = store.find(
282+ ... BugTask,
283+ ... BugTask.bug_id == Bug.id,
284+ ... Bug.information_type.is_in(info_types))
285 >>> found_bugtasks.count() == all_public_bugtasks.count()
286 True
287
288
289-== Searching using bug full-text index ==
290+Searching using bug full-text index
291+************************************
292
293 The searchtext parameter does an extensive and expensive search (it
294 looks through the bug's full text index, bug comments, bugtask
295@@ -49,7 +57,8 @@ But if we put that word in the bug #4 description, it will be found.
296 ... print("#%s" % bugtask.bug.id)
297 #4
298
299-=== BugTaskSearchParams' parameters searchtext and fast_searchtext ===
300+BugTaskSearchParams' parameters searchtext and fast_searchtext
301+***************************************************************
302
303 Normally, the parameter searchtext should be used. The alternative
304 parameter fast_searchtext requires a syntactically correct tsquery
305@@ -107,7 +116,8 @@ Passing invalid tsquery expressions as fast_searchtext raises an exception.
306 >>> transaction.abort()
307
308
309-== Bugs with partner packages ==
310+Bugs with partner packages
311+***************************
312
313 Bugs may also be targeted to partner packages. First turn "cdrkit" into
314 a partner package:
315@@ -141,12 +151,14 @@ We can file a bug against it and see that show up in a search:
316 1
317
318
319-== Ordering search results ==
320+Ordering search results
321+************************
322
323 The result returned by bugtask searches can come sorted by a specified order
324
325
326-=== Ordering by number of duplicates ===
327+Ordering by number of duplicates
328+*********************************
329
330 It is possible to sort the results by the number of duplicates each bag has.
331
332@@ -194,7 +206,8 @@ a duplicate.
333 cdrkit (Ubuntu) Bug to be fixed in trunk
334
335
336-=== Ordering by number of comments ===
337+Ordering by number of comments
338+*******************************
339
340 It is also possible to sort the results by the number of comments on a bug.
341
342@@ -213,7 +226,8 @@ Here is the list of bugs for Ubuntu, sorted by their number of comments.
343 Bug to be fixed in trunk [1 comments]
344
345
346-=== Ordering by bug heat ===
347+Ordering by bug heat
348+*********************
349
350 Another way of sorting searches is by bug heat.
351
352@@ -236,7 +250,8 @@ Another way of sorting searches is by bug heat.
353 Firefox does not support SVG [heat: 1]
354
355
356-=== Ordering by patch age ===
357+Ordering by patch age
358+**********************
359
360 We can also sort search results by the creation time of the youngest
361 patch attached to a bug.
362diff --git a/lib/lp/bugs/feed/bug.py b/lib/lp/bugs/feed/bug.py
363index 218f199..3f443b0 100644
364--- a/lib/lp/bugs/feed/bug.py
365+++ b/lib/lp/bugs/feed/bug.py
366@@ -1,4 +1,4 @@
367-# Copyright 2009-2017 Canonical Ltd. This software is licensed under the
368+# Copyright 2009-2020 Canonical Ltd. This software is licensed under the
369 # GNU Affero General Public License version 3 (see the file LICENSE).
370
371 """Bug feed (syndication) views."""
372@@ -147,9 +147,9 @@ class BugsFeedBase(FeedBase):
373 """
374 bug_ids = []
375 for task in tasks:
376- if task.bugID in bug_ids:
377+ if task.bug_id in bug_ids:
378 continue
379- bug_ids.append(task.bugID)
380+ bug_ids.append(task.bug_id)
381 if len(bug_ids) >= self.quantity:
382 break
383 # XXX: BradCrittenden 2008-03-26 bug=TBD:
384diff --git a/lib/lp/bugs/interfaces/bugtask.py b/lib/lp/bugs/interfaces/bugtask.py
385index f7c1600..7051df8 100644
386--- a/lib/lp/bugs/interfaces/bugtask.py
387+++ b/lib/lp/bugs/interfaces/bugtask.py
388@@ -1,4 +1,4 @@
389-# Copyright 2009-2016 Canonical Ltd. This software is licensed under the
390+# Copyright 2009-2020 Canonical Ltd. This software is licensed under the
391 # GNU Affero General Public License version 3 (see the file LICENSE).
392
393 """Bug task interfaces."""
394@@ -374,6 +374,9 @@ class IllegalTarget(Exception):
395
396 class IBugTaskDelete(Interface):
397 """An interface for operations allowed with the Delete permission."""
398+ def destroySelf():
399+ """Removes this BugTask from the database."""
400+
401 @export_destructor_operation()
402 @call_with(who=REQUEST_USER)
403 @operation_for_version('devel')
404@@ -395,30 +398,31 @@ class IBugTask(IHasBug, IBugTaskDelete):
405 id = Int(title=_("Bug Task #"))
406 bug = exported(
407 BugField(title=_("Bug"), readonly=True))
408+ bug_id = Int(title=_("The bug ID for this bug task"))
409 product = Choice(
410 title=_('Project'), required=False, vocabulary='Product')
411- productID = Attribute('The product ID')
412+ product_id = Attribute('The product ID')
413 productseries = Choice(
414 title=_('Series'), required=False, vocabulary='ProductSeries')
415- productseriesID = Attribute('The product series ID')
416+ productseries_id = Attribute('The product series ID')
417 sourcepackagename = Choice(
418 title=_("Package"), required=False,
419 vocabulary='SourcePackageName')
420- sourcepackagenameID = Attribute('The sourcepackagename ID')
421+ sourcepackagename_id = Attribute('The sourcepackagename ID')
422 distribution = Choice(
423 title=_("Distribution"), required=False, vocabulary='Distribution')
424- distributionID = Attribute('The distribution ID')
425+ distribution_id = Attribute('The distribution ID')
426 distroseries = Choice(
427 title=_("Series"), required=False,
428 vocabulary='DistroSeries')
429- distroseriesID = Attribute('The distroseries ID')
430+ distroseries_id = Attribute('The distroseries ID')
431 milestone = exported(ReferenceChoice(
432 title=_('Milestone'),
433 required=False,
434 readonly=True,
435 vocabulary='BugTaskMilestone',
436 schema=Interface)) # IMilestone
437- milestoneID = Attribute('The id of the milestone.')
438+ milestone_id = Attribute('The id of the milestone.')
439
440 # The status and importance's vocabularies do not
441 # contain an UNKNOWN item in bugtasks that aren't linked to a remote
442@@ -440,7 +444,7 @@ class IBugTask(IHasBug, IBugTaskDelete):
443 title=_('Assigned to'), required=False,
444 vocabulary='ValidAssignee',
445 readonly=True))
446- assigneeID = Int(title=_('The assignee ID (for eager loading)'))
447+ assignee_id = Int(title=_('The assignee ID (for eager loading)'))
448 bugtargetdisplayname = exported(
449 Text(title=_("The short, descriptive name of the target"),
450 readonly=True),
451diff --git a/lib/lp/bugs/interfaces/bugtaskfilter.py b/lib/lp/bugs/interfaces/bugtaskfilter.py
452index 898c955..4ecccfb 100644
453--- a/lib/lp/bugs/interfaces/bugtaskfilter.py
454+++ b/lib/lp/bugs/interfaces/bugtaskfilter.py
455@@ -1,4 +1,4 @@
456-# Copyright 2011 Canonical Ltd. This software is licensed under the
457+# Copyright 2011-2020 Canonical Ltd. This software is licensed under the
458 # GNU Affero General Public License version 3 (see the file LICENSE).
459
460 """Fiter bugtasks based on context."""
461@@ -67,7 +67,7 @@ def filter_bugtasks_by_context(context, bugtasks):
462
463 bug_mapping = defaultdict(list)
464 for task in bugtasks:
465- bug_mapping[task.bugID].append(weight_calculator(task))
466+ bug_mapping[task.bug_id].append(weight_calculator(task))
467
468 filtered = [sorted(tasks)[0].task for tasks in six.itervalues(bug_mapping)]
469- return sorted(filtered, key=attrgetter('bugID'))
470+ return sorted(filtered, key=attrgetter('bug_id'))
471diff --git a/lib/lp/bugs/model/bug.py b/lib/lp/bugs/model/bug.py
472index c1e35a1..12f9e72 100644
473--- a/lib/lp/bugs/model/bug.py
474+++ b/lib/lp/bugs/model/bug.py
475@@ -659,14 +659,14 @@ class Bug(SQLBase, InformationTypeMixin):
476 from lp.registry.model.productseries import ProductSeries
477 from lp.registry.model.sourcepackagename import SourcePackageName
478 store = Store.of(self)
479- tasks = list(store.find(BugTask, BugTask.bugID == self.id))
480+ tasks = list(store.find(BugTask, BugTask.bug_id == self.id))
481 # The bugtasks attribute is iterated in the API and web
482 # services, so it needs to preload all related data otherwise
483 # late evaluation is triggered in both places. Separately,
484 # bugtask_sort_key requires the related products, series,
485 # distros, distroseries and source package names to be loaded.
486- ids = set(map(operator.attrgetter('assigneeID'), tasks))
487- ids.update(map(operator.attrgetter('ownerID'), tasks))
488+ ids = set(map(operator.attrgetter('assignee_id'), tasks))
489+ ids.update(map(operator.attrgetter('owner_id'), tasks))
490 ids.discard(None)
491 if ids:
492 list(getUtility(IPersonSet).getPrecachedPersonsFromIDs(
493@@ -678,11 +678,11 @@ class Bug(SQLBase, InformationTypeMixin):
494 if not ids:
495 return
496 list(store.find(klass, klass.id.is_in(ids)))
497- load_something('productID', Product)
498- load_something('productseriesID', ProductSeries)
499- load_something('distributionID', Distribution)
500- load_something('distroseriesID', DistroSeries)
501- load_something('sourcepackagenameID', SourcePackageName)
502+ load_something('product_id', Product)
503+ load_something('productseries_id', ProductSeries)
504+ load_something('distribution_id', Distribution)
505+ load_something('distroseries_id', DistroSeries)
506+ load_something('sourcepackagename_id', SourcePackageName)
507 list(store.find(BugWatch, BugWatch.bugID == self.id))
508 return sorted(tasks, key=bugtask_sort_key)
509
510@@ -692,7 +692,7 @@ class Bug(SQLBase, InformationTypeMixin):
511 from lp.registry.model.product import Product
512 return Store.of(self).using(
513 BugTask,
514- LeftJoin(Product, Product.id == BugTask.productID)
515+ LeftJoin(Product, Product.id == BugTask.product_id)
516 ).find(
517 BugTask, bug=self
518 ).order_by(
519@@ -2619,10 +2619,10 @@ class BugSubscriptionInfo:
520 """
521 if self.bugtask is None:
522 assignees = load_people(
523- Person.id.is_in(Select(BugTask.assigneeID,
524+ Person.id.is_in(Select(BugTask.assignee_id,
525 BugTask.bug == self.bug)))
526 else:
527- assignees = load_people(Person.id == self.bugtask.assigneeID)
528+ assignees = load_people(Person.id == self.bugtask.assignee_id)
529 if self.bug.private:
530 return IStore(Person).find(Person,
531 Person.id.is_in([a.id for a in assignees]),
532diff --git a/lib/lp/bugs/model/bugtask.py b/lib/lp/bugs/model/bugtask.py
533index fb2bfee..32440f1 100644
534--- a/lib/lp/bugs/model/bugtask.py
535+++ b/lib/lp/bugs/model/bugtask.py
536@@ -33,11 +33,6 @@ import re
537 from lazr.lifecycle.event import ObjectDeletedEvent
538 import pytz
539 import six
540-from sqlobject import (
541- ForeignKey,
542- SQLObjectNotFound,
543- StringCol,
544- )
545 from storm.expr import (
546 And,
547 Cast,
548@@ -51,6 +46,12 @@ from storm.expr import (
549 Sum,
550 )
551 from storm.info import ClassAlias
552+from storm.properties import (
553+ DateTime,
554+ Int,
555+ Unicode,
556+ )
557+from storm.references import Reference
558 from storm.store import (
559 EmptyResultSet,
560 Store,
561@@ -124,18 +125,17 @@ from lp.services.database.bulk import (
562 load_related,
563 )
564 from lp.services.database.constants import UTC_NOW
565-from lp.services.database.datetimecol import UtcDateTimeCol
566 from lp.services.database.decoratedresultset import DecoratedResultSet
567-from lp.services.database.enumcol import EnumCol
568+from lp.services.database.enumcol import DBEnum
569 from lp.services.database.interfaces import IStore
570 from lp.services.database.nl_search import nl_phrase_search
571 from lp.services.database.sqlbase import (
572 block_implicit_flushes,
573 cursor,
574 quote,
575- SQLBase,
576 sqlvalues,
577 )
578+from lp.services.database.stormbase import StormBase
579 from lp.services.helpers import shortlist
580 from lp.services.propertycache import get_property_cache
581 from lp.services.searchbuilder import any
582@@ -263,23 +263,18 @@ class PassthroughValue:
583
584 @block_implicit_flushes
585 def validate_conjoined_attribute(self, attr, value):
586+ # If this is a conjoined slave then call setattr on the master.
587+ # Effectively this means that making a change to the slave will
588+ # actually make the change to the master (which will then be passed
589+ # down to the slave, of course). This helps to prevent OOPSes when
590+ # people try to update the conjoined slave via the API.
591+
592 # If the value has been wrapped in a _PassthroughValue instance,
593 # then we are being updated by our conjoined master: pass the
594 # value through without any checking.
595 if isinstance(value, PassthroughValue):
596 return value.value
597
598- # Check to see if the object is being instantiated. This test is specific
599- # to SQLBase. Checking for specific attributes (like self.bug) is
600- # insufficient and fragile.
601- if self._SO_creating:
602- return value
603-
604- # If this is a conjoined slave then call setattr on the master.
605- # Effectively this means that making a change to the slave will
606- # actually make the change to the master (which will then be passed
607- # down to the slave, of course). This helps to prevent OOPSes when
608- # people try to update the conjoined slave via the API.
609 conjoined_master = self.conjoined_master
610 if conjoined_master is not None:
611 setattr(conjoined_master, attr, value)
612@@ -409,13 +404,13 @@ def validate_new_target(bug, target, check_source_package=True):
613
614
615 @implementer(IBugTask)
616-class BugTask(SQLBase):
617+class BugTask(StormBase):
618 """See `IBugTask`."""
619- _table = "BugTask"
620- _defaultOrder = ['distribution', 'product', 'productseries',
621- 'distroseries', 'milestone', 'sourcepackagename']
622+ __storm_table__ = "BugTask"
623+ __storm_order__ = ['distribution', 'product', 'productseries',
624+ 'distroseries', 'milestone', 'sourcepackagename']
625 _CONJOINED_ATTRIBUTES = (
626- "_status", "importance", "assigneeID", "milestoneID",
627+ "_status", "importance", "assignee_id", "milestone_id",
628 "date_assigned", "date_confirmed", "date_inprogress",
629 "date_closed", "date_incomplete", "date_left_new",
630 "date_triaged", "date_fix_committed", "date_fix_released",
631@@ -424,66 +419,84 @@ class BugTask(SQLBase):
632
633 _inhibit_target_check = False
634
635- bug = ForeignKey(dbName='bug', foreignKey='Bug', notNull=True)
636- product = ForeignKey(
637- dbName='product', foreignKey='Product',
638- notNull=False, default=None)
639- productseries = ForeignKey(
640- dbName='productseries', foreignKey='ProductSeries',
641- notNull=False, default=None)
642- sourcepackagename = ForeignKey(
643- dbName='sourcepackagename', foreignKey='SourcePackageName',
644- notNull=False, default=None)
645- distribution = ForeignKey(
646- dbName='distribution', foreignKey='Distribution',
647- notNull=False, default=None)
648- distroseries = ForeignKey(
649- dbName='distroseries', foreignKey='DistroSeries',
650- notNull=False, default=None)
651- milestone = ForeignKey(
652- dbName='milestone', foreignKey='Milestone',
653- notNull=False, default=None,
654- storm_validator=validate_conjoined_attribute)
655- _status = EnumCol(
656- dbName='status', notNull=True,
657- schema=(BugTaskStatus, BugTaskStatusSearch),
658+ id = Int(primary=True)
659+
660+ bug_id = Int(name='bug', allow_none=False)
661+ bug = Reference(bug_id, 'Bug.id')
662+
663+ product_id = Int(name="product", allow_none=True)
664+ product = Reference(product_id, 'Product.id')
665+
666+ productseries_id = Int(name="productseries", allow_none=True)
667+ productseries = Reference(productseries_id, 'ProductSeries.id')
668+
669+ sourcepackagename_id = Int(name="sourcepackagename", allow_none=True)
670+ sourcepackagename = Reference(sourcepackagename_id, "SourcePackageName.id")
671+
672+ distribution_id = Int(name="distribution", allow_none=True)
673+ distribution = Reference(distribution_id, "Distribution.id")
674+
675+ distroseries_id = Int(name="distroseries", allow_none=True)
676+ distroseries = Reference(distroseries_id, "DistroSeries.id")
677+
678+ milestone_id = Int(
679+ name="milestone", allow_none=True,
680+ validator=validate_conjoined_attribute)
681+ milestone = Reference(milestone_id, "Milestone.id")
682+
683+ _status = DBEnum(
684+ name='status', allow_none=False,
685+ enum=(BugTaskStatus, BugTaskStatusSearch),
686 default=BugTaskStatus.NEW,
687- storm_validator=validate_status)
688- importance = EnumCol(
689- dbName='importance', notNull=True,
690- schema=BugTaskImportance,
691+ validator=validate_status)
692+ importance = DBEnum(
693+ name='importance', allow_none=False,
694+ enum=BugTaskImportance,
695 default=BugTaskImportance.UNDECIDED,
696- storm_validator=validate_conjoined_attribute)
697- assignee = ForeignKey(
698- dbName='assignee', foreignKey='Person',
699- storm_validator=validate_assignee,
700- notNull=False, default=None)
701- bugwatch = ForeignKey(dbName='bugwatch', foreignKey='BugWatch',
702- notNull=False, default=None)
703- date_assigned = UtcDateTimeCol(notNull=False, default=None,
704- storm_validator=validate_conjoined_attribute)
705- datecreated = UtcDateTimeCol(notNull=False, default=UTC_NOW)
706- date_confirmed = UtcDateTimeCol(notNull=False, default=None,
707- storm_validator=validate_conjoined_attribute)
708- date_inprogress = UtcDateTimeCol(notNull=False, default=None,
709- storm_validator=validate_conjoined_attribute)
710- date_closed = UtcDateTimeCol(notNull=False, default=None,
711- storm_validator=validate_conjoined_attribute)
712- date_incomplete = UtcDateTimeCol(notNull=False, default=None,
713- storm_validator=validate_conjoined_attribute)
714- date_left_new = UtcDateTimeCol(notNull=False, default=None,
715- storm_validator=validate_conjoined_attribute)
716- date_triaged = UtcDateTimeCol(notNull=False, default=None,
717- storm_validator=validate_conjoined_attribute)
718- date_fix_committed = UtcDateTimeCol(notNull=False, default=None,
719- storm_validator=validate_conjoined_attribute)
720- date_fix_released = UtcDateTimeCol(notNull=False, default=None,
721- storm_validator=validate_conjoined_attribute)
722- date_left_closed = UtcDateTimeCol(notNull=False, default=None,
723- storm_validator=validate_conjoined_attribute)
724- owner = ForeignKey(
725- dbName='owner', foreignKey='Person',
726- storm_validator=validate_public_person, notNull=True)
727+ validator=validate_conjoined_attribute)
728+
729+ assignee_id = Int(
730+ name="assignee", allow_none=True, validator=validate_assignee)
731+ assignee = Reference(assignee_id, "Person.id")
732+
733+ bugwatch_id = Int(name="bugwatch", allow_none=True)
734+ bugwatch = Reference(bugwatch_id, "BugWatch.id")
735+
736+ date_assigned = DateTime(
737+ tzinfo=pytz.UTC, allow_none=True, default=None,
738+ validator=validate_conjoined_attribute)
739+ datecreated = DateTime(tzinfo=pytz.UTC, allow_none=True, default=UTC_NOW)
740+ date_confirmed = DateTime(
741+ tzinfo=pytz.UTC, allow_none=True, default=None,
742+ validator=validate_conjoined_attribute)
743+ date_inprogress = DateTime(
744+ tzinfo=pytz.UTC, allow_none=True, default=None,
745+ validator=validate_conjoined_attribute)
746+ date_closed = DateTime(
747+ tzinfo=pytz.UTC, allow_none=True, default=None,
748+ validator=validate_conjoined_attribute)
749+ date_incomplete = DateTime(
750+ tzinfo=pytz.UTC, allow_none=True, default=None,
751+ validator=validate_conjoined_attribute)
752+ date_left_new = DateTime(
753+ tzinfo=pytz.UTC, allow_none=True, default=None,
754+ validator=validate_conjoined_attribute)
755+ date_triaged = DateTime(
756+ tzinfo=pytz.UTC, allow_none=True, default=None,
757+ validator=validate_conjoined_attribute)
758+ date_fix_committed = DateTime(
759+ tzinfo=pytz.UTC, allow_none=True, default=None,
760+ validator=validate_conjoined_attribute)
761+ date_fix_released = DateTime(
762+ tzinfo=pytz.UTC, allow_none=True, default=None,
763+ validator=validate_conjoined_attribute)
764+ date_left_closed = DateTime(
765+ tzinfo=pytz.UTC, allow_none=True, default=None,
766+ validator=validate_conjoined_attribute)
767+
768+ owner_id = Int(
769+ name="owner", allow_none=False, validator=validate_public_person)
770+ owner = Reference(owner_id, "Person.id")
771 # The targetnamecache is a value that is only supposed to be set
772 # when a bugtask is created/modified or by the
773 # update-bugtask-targetnamecaches cronscript. For this reason it's
774@@ -492,8 +505,8 @@ class BugTask(SQLBase):
775 #
776 # This field is actually incorrectly named, since it currently
777 # stores the bugtargetdisplayname.
778- targetnamecache = StringCol(
779- dbName='targetnamecache', notNull=False, default=None)
780+ targetnamecache = Unicode(
781+ name='targetnamecache', allow_none=True, default=None)
782
783 @property
784 def status(self):
785@@ -596,6 +609,9 @@ class BugTask(SQLBase):
786 "Cannot delete only bugtask affecting: %s."
787 % self.target.bugtargetdisplayname)
788
789+ def destroySelf(self):
790+ Store.of(self).remove(self)
791+
792 def delete(self, who=None):
793 """See `IBugTask`."""
794 if who is None:
795@@ -644,7 +660,7 @@ class BugTask(SQLBase):
796 matching_bugtasks, user, limit)
797
798 # Make sure to exclude the bug of the current bugtask.
799- return [bug for bug in matching_bugs if bug.id != self.bugID]
800+ return [bug for bug in matching_bugs if bug.id != self.bug_id]
801
802 def subscribe(self, person, subscribed_by):
803 """See `IBugTask`."""
804@@ -716,7 +732,7 @@ class BugTask(SQLBase):
805 'A product should always have a development series.')
806 devel_focusID = self.product.development_focusID
807 for bugtask in bugtasks:
808- if bugtask.productseriesID == devel_focusID:
809+ if bugtask.productseries_id == devel_focusID:
810 conjoined_master = bugtask
811 break
812
813@@ -1089,8 +1105,8 @@ class BugTask(SQLBase):
814 # series have tasks on this bug.
815 if not Store.of(self).find(
816 BugTask,
817- BugTask.bugID == self.bugID,
818- BugTask.distroseriesID == DistroSeries.id,
819+ BugTask.bug_id == self.bug_id,
820+ BugTask.distroseries_id == DistroSeries.id,
821 DistroSeries.distributionID.is_in(
822 distro.id for distro in distros if distro),
823 ).is_empty():
824@@ -1324,7 +1340,7 @@ class BugTask(SQLBase):
825 return self.userHasBugSupervisorPrivilegesContext(self.target, user)
826
827 def __repr__(self):
828- return "<BugTask for bug %s on %r>" % (self.bugID, self.target)
829+ return "<BugTask for bug %s on %r>" % (self.bug_id, self.target)
830
831
832 @implementer(IBugTaskSet)
833@@ -1346,9 +1362,8 @@ class BugTaskSet:
834 # XXX: JSK: 2007-12-19: This method should probably return
835 # None when task_id is not present. See:
836 # https://bugs.launchpad.net/launchpad/+bug/123592
837- try:
838- bugtask = BugTask.get(task_id)
839- except SQLObjectNotFound:
840+ bugtask = IStore(BugTask).find(BugTask, BugTask.id == task_id).one()
841+ if bugtask is None:
842 raise NotFoundError("BugTask with ID %s does not exist." %
843 str(task_id))
844 return bugtask
845@@ -1358,7 +1373,7 @@ class BugTaskSet:
846 # Import locally to avoid circular imports.
847 from lp.bugs.model.bug import Bug, BugTag
848 bugtask_ids = set(bugtask.id for bugtask in bugtasks)
849- bug_ids = set(bugtask.bugID for bugtask in bugtasks)
850+ bug_ids = set(bugtask.bug_id for bugtask in bugtasks)
851 tags = IStore(BugTag).find(
852 (BugTag.tag, BugTask.id),
853 BugTask.bug == Bug.id,
854@@ -1375,7 +1390,7 @@ class BugTaskSet:
855 # Avoid circular imports.
856 from lp.registry.interfaces.person import IPersonSet
857 people_ids = set(
858- [bugtask.assigneeID for bugtask in bugtasks] +
859+ [bugtask.assignee_id for bugtask in bugtasks] +
860 [bugtask.bug.ownerID for bugtask in bugtasks])
861 people = getUtility(IPersonSet).getPrecachedPersonsFromIDs(people_ids)
862 return dict(
863@@ -1387,7 +1402,7 @@ class BugTaskSet:
864 from lp.bugs.model.bug import Bug
865 from lp.bugs.model.bugbranch import BugBranch
866
867- bug_ids = set(bugtask.bugID for bugtask in bugtasks)
868+ bug_ids = set(bugtask.bug_id for bugtask in bugtasks)
869 bug_ids_with_specifications = set(
870 int(id) for _, id in getUtility(IXRefSet).findFromMany(
871 [(u'bug', six.text_type(bug_id)) for bug_id in bug_ids],
872@@ -1397,7 +1412,7 @@ class BugTaskSet:
873 # Badging looks up milestones too : eager load into the storm cache.
874 milestoneset = getUtility(IMilestoneSet)
875 # And trigger a load:
876- milestone_ids = set(map(attrgetter('milestoneID'), bugtasks))
877+ milestone_ids = set(map(attrgetter('milestone_id'), bugtasks))
878 milestone_ids.discard(None)
879 if milestone_ids:
880 list(milestoneset.getByIds(milestone_ids))
881@@ -1415,7 +1430,7 @@ class BugTaskSet:
882
883 badge_properties = {}
884 for bugtask in bugtasks:
885- bug = bugs[bugtask.bugID]
886+ bug = bugs[bugtask.bug_id]
887 badge_properties[bugtask] = {
888 'has_specification':
889 bug.id in bug_ids_with_specifications,
890@@ -1433,7 +1448,7 @@ class BugTaskSet:
891 task_ids = [int(task_id) for task_id in task_ids]
892 # Query the database, returning the results in a dictionary:
893 if len(task_ids) > 0:
894- tasks = BugTask.select('id in %s' % sqlvalues(task_ids))
895+ tasks = IStore(BugTask).find(BugTask, BugTask.id.is_in(task_ids))
896 return dict([(task.id, task) for task in tasks])
897 else:
898 return {}
899@@ -1486,12 +1501,12 @@ class BugTaskSet:
900 eager_load = None
901 else:
902 def eager_load(rows):
903- load_related(Bug, rows, ['bugID'])
904- load_related(Product, rows, ['productID'])
905- load_related(ProductSeries, rows, ['productseriesID'])
906- load_related(Distribution, rows, ['distributionID'])
907- load_related(DistroSeries, rows, ['distroseriesID'])
908- load_related(SourcePackageName, rows, ['sourcepackagenameID'])
909+ load_related(Bug, rows, ['bug_id'])
910+ load_related(Product, rows, ['product_id'])
911+ load_related(ProductSeries, rows, ['productseries_id'])
912+ load_related(Distribution, rows, ['distribution_id'])
913+ load_related(DistroSeries, rows, ['distroseries_id'])
914+ load_related(SourcePackageName, rows, ['sourcepackagename_id'])
915 return search_bugs(eager_load, (params,) + args)
916
917 def searchBugIds(self, params):
918@@ -1757,7 +1772,7 @@ class BugTaskSet:
919 ids = ids[:limit]
920
921 return DecoratedResultSet(
922- ids, lambda id: BugTask.get(id),
923+ ids, lambda id: getUtility(IBugTaskSet).get(id),
924 pre_iter_hook=lambda rows: load(BugTask, rows))
925
926 def _getTargetJoinAndClause(self, target):
927@@ -1946,12 +1961,12 @@ class BugTaskSet:
928 # for the milestone vocabulary
929 for task in bugtasks:
930 task = removeSecurityProxy(task)
931- distro_ids.add(task.distributionID)
932- distro_series_ids.add(task.distroseriesID)
933- product_ids.add(task.productID)
934+ distro_ids.add(task.distribution_id)
935+ distro_series_ids.add(task.distroseries_id)
936+ product_ids.add(task.product_id)
937 if task.productseries:
938 product_ids.add(task.productseries.productID)
939- product_series_ids.add(task.productseriesID)
940+ product_series_ids.add(task.productseries_id)
941
942 distro_ids.discard(None)
943 distro_series_ids.discard(None)
944diff --git a/lib/lp/bugs/model/bugtasksearch.py b/lib/lp/bugs/model/bugtasksearch.py
945index 15a489d..9ceb517 100644
946--- a/lib/lp/bugs/model/bugtasksearch.py
947+++ b/lib/lp/bugs/model/bugtasksearch.py
948@@ -957,9 +957,9 @@ def _build_exclude_conjoined_clause(milestone):
949 current_series = milestone.distribution.currentseries
950 join = LeftJoin(
951 ConjoinedMaster,
952- And(ConjoinedMaster.bugID == BugTaskFlat.bug_id,
953+ And(ConjoinedMaster.bug_id == BugTaskFlat.bug_id,
954 BugTaskFlat.distribution_id == milestone.distribution.id,
955- ConjoinedMaster.distroseriesID == current_series.id,
956+ ConjoinedMaster.distroseries_id == current_series.id,
957 Not(ConjoinedMaster._status.is_in(
958 BugTask._NON_CONJOINED_STATUSES))))
959 join_tables = [(ConjoinedMaster, join)]
960@@ -974,8 +974,8 @@ def _build_exclude_conjoined_clause(milestone):
961 LeftJoin(Product, BugTaskFlat.product_id == Product.id),
962 LeftJoin(
963 ConjoinedMaster,
964- And(ConjoinedMaster.bugID == BugTaskFlat.bug_id,
965- ConjoinedMaster.productseriesID
966+ And(ConjoinedMaster.bug_id == BugTaskFlat.bug_id,
967+ ConjoinedMaster.productseries_id
968 == Product.development_focusID,
969 Not(ConjoinedMaster._status.is_in(
970 BugTask._NON_CONJOINED_STATUSES)))),
971@@ -987,9 +987,9 @@ def _build_exclude_conjoined_clause(milestone):
972 milestone.product.development_focusID)
973 join = LeftJoin(
974 ConjoinedMaster,
975- And(ConjoinedMaster.bugID == BugTaskFlat.bug_id,
976+ And(ConjoinedMaster.bug_id == BugTaskFlat.bug_id,
977 BugTaskFlat.product_id == milestone.product.id,
978- ConjoinedMaster.productseriesID == dev_focus_id,
979+ ConjoinedMaster.productseries_id == dev_focus_id,
980 Not(ConjoinedMaster._status.is_in(
981 BugTask._NON_CONJOINED_STATUSES))))
982 join_tables = [(ConjoinedMaster, join)]
983@@ -1054,9 +1054,9 @@ def _build_pending_bugwatch_elsewhere_clause(params):
984 # Restrict the target to params.upstream_target.
985 target = params.upstream_target
986 if IProduct.providedBy(target):
987- target_col = RelatedBugTask.productID
988+ target_col = RelatedBugTask.product_id
989 elif IDistribution.providedBy(target):
990- target_col = RelatedBugTask.distributionID
991+ target_col = RelatedBugTask.distribution_id
992 else:
993 raise AssertionError(
994 'params.upstream_target must be a Distribution or '
995@@ -1070,10 +1070,10 @@ def _build_pending_bugwatch_elsewhere_clause(params):
996 extra_joins = [
997 LeftJoin(
998 OtherDistribution,
999- OtherDistribution.id == RelatedBugTask.distributionID),
1000+ OtherDistribution.id == RelatedBugTask.distribution_id),
1001 LeftJoin(
1002 OtherProduct,
1003- OtherProduct.id == RelatedBugTask.productID),
1004+ OtherProduct.id == RelatedBugTask.product_id),
1005 ]
1006 target_clause = Or(
1007 OtherDistribution.official_malone == False,
1008@@ -1091,9 +1091,9 @@ def _build_pending_bugwatch_elsewhere_clause(params):
1009 1,
1010 tables=[RelatedBugTask] + extra_joins,
1011 where=And(
1012- RelatedBugTask.bugID == BugTaskFlat.bug_id,
1013+ RelatedBugTask.bug_id == BugTaskFlat.bug_id,
1014 task_match_clause,
1015- RelatedBugTask.bugwatchID == None,
1016+ RelatedBugTask.bugwatch_id == None,
1017 RelatedBugTask._status != BugTaskStatus.INVALID,
1018 target_clause)))
1019
1020@@ -1102,18 +1102,18 @@ def _build_no_upstream_bugtask_clause(params):
1021 """Return a clause for BugTaskSearchParams.has_no_upstream_bugtask."""
1022 OtherBugTask = ClassAlias(BugTask)
1023 if params.upstream_target is None:
1024- target = OtherBugTask.productID != None
1025+ target = OtherBugTask.product_id != None
1026 elif IProduct.providedBy(params.upstream_target):
1027- target = OtherBugTask.productID == params.upstream_target.id
1028+ target = OtherBugTask.product_id == params.upstream_target.id
1029 elif IDistribution.providedBy(params.upstream_target):
1030- target = OtherBugTask.distributionID == params.upstream_target.id
1031+ target = OtherBugTask.distribution_id == params.upstream_target.id
1032 else:
1033 raise AssertionError(
1034 'params.upstream_target must be a Distribution or '
1035 'a Product')
1036 return Not(Exists(Select(
1037 1, tables=[OtherBugTask],
1038- where=And(OtherBugTask.bugID == BugTaskFlat.bug_id, target))))
1039+ where=And(OtherBugTask.bug_id == BugTaskFlat.bug_id, target))))
1040
1041
1042 def _build_open_or_resolved_upstream_clause(params,
1043@@ -1128,12 +1128,12 @@ def _build_open_or_resolved_upstream_clause(params,
1044 RelatedBugTask._status, any(*statuses_for_upstream_tasks))
1045 if params.upstream_target is None:
1046 watch_target_clause = True
1047- no_watch_target_clause = RelatedBugTask.productID != None
1048+ no_watch_target_clause = RelatedBugTask.product_id != None
1049 else:
1050 if IProduct.providedBy(params.upstream_target):
1051- target_col = RelatedBugTask.productID
1052+ target_col = RelatedBugTask.product_id
1053 elif IDistribution.providedBy(params.upstream_target):
1054- target_col = RelatedBugTask.distributionID
1055+ target_col = RelatedBugTask.distribution_id
1056 else:
1057 raise AssertionError(
1058 'params.upstream_target must be a Distribution or '
1059@@ -1144,14 +1144,14 @@ def _build_open_or_resolved_upstream_clause(params,
1060 1,
1061 tables=[RelatedBugTask],
1062 where=And(
1063- RelatedBugTask.bugID == BugTaskFlat.bug_id,
1064+ RelatedBugTask.bug_id == BugTaskFlat.bug_id,
1065 RelatedBugTask.id != BugTaskFlat.bugtask_id,
1066 Or(
1067 And(watch_target_clause,
1068- RelatedBugTask.bugwatchID != None,
1069+ RelatedBugTask.bugwatch_id != None,
1070 watch_status_clause),
1071 And(no_watch_target_clause,
1072- RelatedBugTask.bugwatchID == None,
1073+ RelatedBugTask.bugwatch_id == None,
1074 no_watch_status_clause)))))
1075
1076
1077diff --git a/lib/lp/bugs/model/cve.py b/lib/lp/bugs/model/cve.py
1078index 05b5fa8..553bff3 100644
1079--- a/lib/lp/bugs/model/cve.py
1080+++ b/lib/lp/bugs/model/cve.py
1081@@ -203,7 +203,7 @@ class CveSet:
1082
1083 def getBugCvesForBugTasks(self, bugtasks, cve_mapper=None):
1084 """See ICveSet."""
1085- bugs = bulk.load_related(Bug, bugtasks, ('bugID', ))
1086+ bugs = bulk.load_related(Bug, bugtasks, ('bug_id', ))
1087 if len(bugs) == 0:
1088 return []
1089 store = Store.of(bugtasks[0])
1090diff --git a/lib/lp/bugs/model/personsubscriptioninfo.py b/lib/lp/bugs/model/personsubscriptioninfo.py
1091index 3767e6b..1d82c22 100644
1092--- a/lib/lp/bugs/model/personsubscriptioninfo.py
1093+++ b/lib/lp/bugs/model/personsubscriptioninfo.py
1094@@ -1,4 +1,4 @@
1095-# Copyright 2011-2012 Canonical Ltd. This software is licensed under the
1096+# Copyright 2011-2020 Canonical Ltd. This software is licensed under the
1097 # GNU Affero General Public License version 3 (see the file LICENSE).
1098
1099 __metaclass__ = type
1100@@ -184,8 +184,8 @@ class PersonSubscriptions(object):
1101 list(getUtility(IPersonSet).getPrecachedPersonsFromIDs(
1102 [bug.ownerID for bug in bugs]))
1103 all_tasks = [task for task in bug.bugtasks for bug in bugs]
1104- load_related(Product, all_tasks, ['productID'])
1105- load_related(Distribution, all_tasks, ['distributionID'])
1106+ load_related(Product, all_tasks, ['product_id'])
1107+ load_related(Distribution, all_tasks, ['distribution_id'])
1108 for bug in bugs:
1109 # indicate the reporter and bug_supervisor
1110 duplicates.annotateReporter(bug, bug.owner)
1111diff --git a/lib/lp/bugs/scripts/bugsummaryrebuild.py b/lib/lp/bugs/scripts/bugsummaryrebuild.py
1112index 3537a98..ab89e7b 100644
1113--- a/lib/lp/bugs/scripts/bugsummaryrebuild.py
1114+++ b/lib/lp/bugs/scripts/bugsummaryrebuild.py
1115@@ -1,4 +1,4 @@
1116-# Copyright 2012-2018 Canonical Ltd. This software is licensed under the
1117+# Copyright 2012-2020 Canonical Ltd. This software is licensed under the
1118 # GNU Affero General Public License version 3 (see the file LICENSE).
1119
1120 __metaclass__ = type
1121@@ -69,9 +69,9 @@ def get_bugsummary_targets():
1122 def get_bugtask_targets():
1123 """Get the current set of targets represented in BugTask."""
1124 new_targets = set(IStore(BugTask).find(
1125- (BugTask.productID, BugTask.productseriesID,
1126- BugTask.distributionID, BugTask.distroseriesID,
1127- BugTask.sourcepackagenameID)).config(distinct=True))
1128+ (BugTask.product_id, BugTask.productseries_id,
1129+ BugTask.distribution_id, BugTask.distroseries_id,
1130+ BugTask.sourcepackagename_id)).config(distinct=True))
1131 # BugSummary counts package tasks in the packageless totals, so
1132 # ensure that there's also a packageless total for each distro(series).
1133 new_targets.update(set(
1134diff --git a/lib/lp/bugs/scripts/bugtasktargetnamecaches.py b/lib/lp/bugs/scripts/bugtasktargetnamecaches.py
1135index 429ff49..5e4cb1e 100644
1136--- a/lib/lp/bugs/scripts/bugtasktargetnamecaches.py
1137+++ b/lib/lp/bugs/scripts/bugtasktargetnamecaches.py
1138@@ -1,4 +1,4 @@
1139-# Copyright 2009 Canonical Ltd. This software is licensed under the
1140+# Copyright 2009-2020 Canonical Ltd. This software is licensed under the
1141 # GNU Affero General Public License version 3 (see the file LICENSE).
1142
1143 """A utility module for the update-bugtasktargetnamecaches.py cronscript."""
1144@@ -32,8 +32,8 @@ from lp.services.looptuner import (
1145 # These two tuples must be in the same order. They specify the ID
1146 # columns to get from BugTask, and the classes that they correspond to.
1147 target_columns = (
1148- BugTask.productID, BugTask.productseriesID, BugTask.distributionID,
1149- BugTask.distroseriesID, BugTask.sourcepackagenameID,
1150+ BugTask.product_id, BugTask.productseries_id, BugTask.distribution_id,
1151+ BugTask.distroseries_id, BugTask.sourcepackagename_id,
1152 BugTask.targetnamecache)
1153 target_classes = (
1154 Product, ProductSeries, Distribution, DistroSeries, SourcePackageName)
1155diff --git a/lib/lp/code/model/branchcollection.py b/lib/lp/code/model/branchcollection.py
1156index f07416a..0ab5fd8 100644
1157--- a/lib/lp/code/model/branchcollection.py
1158+++ b/lib/lp/code/model/branchcollection.py
1159@@ -550,7 +550,7 @@ class GenericBranchCollection:
1160 store = IStore(BugBranch)
1161 rs = store.using(
1162 BugBranch,
1163- Join(BugTask, BugTask.bugID == BugBranch.bug_id),
1164+ Join(BugTask, BugTask.bug_id == BugBranch.bug_id),
1165 ).find(
1166 (BugTask, BugBranch),
1167 BugBranch.bug_id.is_in(bug_ids),
1168diff --git a/lib/lp/registry/browser/milestone.py b/lib/lp/registry/browser/milestone.py
1169index 936967a..263bd8a 100644
1170--- a/lib/lp/registry/browser/milestone.py
1171+++ b/lib/lp/registry/browser/milestone.py
1172@@ -1,4 +1,4 @@
1173-# Copyright 2009-2018 Canonical Ltd. This software is licensed under the
1174+# Copyright 2009-2020 Canonical Ltd. This software is licensed under the
1175 # GNU Affero General Public License version 3 (see the file LICENSE).
1176
1177 """Milestone views."""
1178@@ -234,7 +234,7 @@ class MilestoneViewMixin(object):
1179 # We want the assignees loaded as we show them in the milestone home
1180 # page.
1181 list(getUtility(IPersonSet).getPrecachedPersonsFromIDs(
1182- [bug.assigneeID for bug in non_conjoined_slaves],
1183+ [bug.assignee_id for bug in non_conjoined_slaves],
1184 need_validity=True))
1185 return non_conjoined_slaves
1186
1187diff --git a/lib/lp/registry/model/distribution.py b/lib/lp/registry/model/distribution.py
1188index b9baba7..0f07325 100644
1189--- a/lib/lp/registry/model/distribution.py
1190+++ b/lib/lp/registry/model/distribution.py
1191@@ -1473,7 +1473,7 @@ class Distribution(SQLBase, BugTargetBase, MakesAnnouncements,
1192 distributionID = self.id
1193
1194 def weight_function(bugtask):
1195- if bugtask.distributionID == distributionID:
1196+ if bugtask.distribution_id == distributionID:
1197 return OrderedBugTask(1, bugtask.id, bugtask)
1198 return OrderedBugTask(2, bugtask.id, bugtask)
1199
1200diff --git a/lib/lp/registry/model/distroseries.py b/lib/lp/registry/model/distroseries.py
1201index 9f0fc8c..2811a42 100644
1202--- a/lib/lp/registry/model/distroseries.py
1203+++ b/lib/lp/registry/model/distroseries.py
1204@@ -1454,9 +1454,9 @@ class DistroSeries(SQLBase, BugTargetBase, HasSpecificationsMixin,
1205 distributionID = self.distributionID
1206
1207 def weight_function(bugtask):
1208- if bugtask.distroseriesID == seriesID:
1209+ if bugtask.distroseries_id == seriesID:
1210 return OrderedBugTask(1, bugtask.id, bugtask)
1211- elif bugtask.distributionID == distributionID:
1212+ elif bugtask.distribution_id == distributionID:
1213 return OrderedBugTask(2, bugtask.id, bugtask)
1214 else:
1215 return OrderedBugTask(3, bugtask.id, bugtask)
1216diff --git a/lib/lp/registry/model/person.py b/lib/lp/registry/model/person.py
1217index 6f96764..197fd84 100644
1218--- a/lib/lp/registry/model/person.py
1219+++ b/lib/lp/registry/model/person.py
1220@@ -1477,8 +1477,8 @@ class Person(
1221 tasks = list(getUtility(IBugTaskSet).search(search_params))
1222 # Eager load the things we need that are not already eager loaded by
1223 # BugTaskSet.search().
1224- bulk.load_related(Person, tasks, ['assigneeID'])
1225- bulk.load_related(Milestone, tasks, ['milestoneID'])
1226+ bulk.load_related(Person, tasks, ['assignee_id'])
1227+ bulk.load_related(Milestone, tasks, ['milestone_id'])
1228
1229 for task in tasks:
1230 # We skip masters (instead of slaves) from conjoined relationships
1231@@ -1821,7 +1821,7 @@ class Person(
1232 Bug.id,
1233 tables=(
1234 Bug,
1235- Join(BugTask, BugTask.bugID == Bug.id)),
1236+ Join(BugTask, BugTask.bug_id == Bug.id)),
1237 where=And(Bug.information_type.is_in(
1238 PRIVATE_INFORMATION_TYPES),
1239 BugTask.assignee == self.id)),
1240diff --git a/lib/lp/registry/model/product.py b/lib/lp/registry/model/product.py
1241index ae43ede..07d652e 100644
1242--- a/lib/lp/registry/model/product.py
1243+++ b/lib/lp/registry/model/product.py
1244@@ -1470,7 +1470,7 @@ class Product(SQLBase, BugTargetBase, MakesAnnouncements,
1245 productID = self.id
1246
1247 def weight_function(bugtask):
1248- if bugtask.productID == productID:
1249+ if bugtask.product_id == productID:
1250 return OrderedBugTask(1, bugtask.id, bugtask)
1251 return OrderedBugTask(2, bugtask.id, bugtask)
1252
1253diff --git a/lib/lp/registry/model/productseries.py b/lib/lp/registry/model/productseries.py
1254index 416577e..3459214 100644
1255--- a/lib/lp/registry/model/productseries.py
1256+++ b/lib/lp/registry/model/productseries.py
1257@@ -1,4 +1,4 @@
1258-# Copyright 2009-2013 Canonical Ltd. This software is licensed under the
1259+# Copyright 2009-2020 Canonical Ltd. This software is licensed under the
1260 # GNU Affero General Public License version 3 (see the file LICENSE).
1261
1262 """Models for `IProductSeries`."""
1263@@ -575,9 +575,9 @@ class ProductSeries(SQLBase, BugTargetBase, HasMilestonesMixin,
1264 productID = self.productID
1265
1266 def weight_function(bugtask):
1267- if bugtask.productseriesID == seriesID:
1268+ if bugtask.productseries_id == seriesID:
1269 return OrderedBugTask(1, bugtask.id, bugtask)
1270- elif bugtask.productID == productID:
1271+ elif bugtask.product_id == productID:
1272 return OrderedBugTask(2, bugtask.id, bugtask)
1273 else:
1274 return OrderedBugTask(3, bugtask.id, bugtask)
1275diff --git a/lib/lp/registry/model/sourcepackage.py b/lib/lp/registry/model/sourcepackage.py
1276index 9e40417..61b66e4 100644
1277--- a/lib/lp/registry/model/sourcepackage.py
1278+++ b/lib/lp/registry/model/sourcepackage.py
1279@@ -1,4 +1,4 @@
1280-# Copyright 2009-2015 Canonical Ltd. This software is licensed under the
1281+# Copyright 2009-2020 Canonical Ltd. This software is licensed under the
1282 # GNU Affero General Public License version 3 (see the file LICENSE).
1283
1284 """Database classes that implement SourcePackage items."""
1285@@ -774,14 +774,14 @@ class SourcePackage(BugTargetBase, HasCodeImportsMixin,
1286 distributionID = self.distroseries.distributionID
1287
1288 def weight_function(bugtask):
1289- if bugtask.sourcepackagenameID == sourcepackagenameID:
1290- if bugtask.distroseriesID == seriesID:
1291+ if bugtask.sourcepackagename_id == sourcepackagenameID:
1292+ if bugtask.distroseries_id == seriesID:
1293 return OrderedBugTask(1, bugtask.id, bugtask)
1294- elif bugtask.distributionID == distributionID:
1295+ elif bugtask.distribution_id == distributionID:
1296 return OrderedBugTask(2, bugtask.id, bugtask)
1297- elif bugtask.distroseriesID == seriesID:
1298+ elif bugtask.distroseries_id == seriesID:
1299 return OrderedBugTask(3, bugtask.id, bugtask)
1300- elif bugtask.distributionID == distributionID:
1301+ elif bugtask.distribution_id == distributionID:
1302 return OrderedBugTask(4, bugtask.id, bugtask)
1303 # Catch the default case, and where there is a task for the same
1304 # sourcepackage on a different distro.
1305diff --git a/lib/lp/registry/scripts/closeaccount.py b/lib/lp/registry/scripts/closeaccount.py
1306index fba057f..9949b95 100644
1307--- a/lib/lp/registry/scripts/closeaccount.py
1308+++ b/lib/lp/registry/scripts/closeaccount.py
1309@@ -235,7 +235,7 @@ def close_account(username, log):
1310
1311 # Reassign their bugs
1312 table_notification('BugTask')
1313- store.find(BugTask, BugTask.assigneeID == person.id).set(assigneeID=None)
1314+ store.find(BugTask, BugTask.assignee_id == person.id).set(assignee_id=None)
1315
1316 # Reassign questions assigned to the user, and close all their questions
1317 # in non-final states since nobody else can.