Merge lp:~edwin-grubbs/launchpad/bug-535430-needspackaging-timeout-part2 into lp:launchpad/db-devel

Proposed by Edwin Grubbs
Status: Merged
Merged at revision: 9449
Proposed branch: lp:~edwin-grubbs/launchpad/bug-535430-needspackaging-timeout-part2
Merge into: lp:launchpad/db-devel
Diff against target: 636 lines (+296/-94)
12 files modified
database/schema/comments.sql (+5/-1)
database/schema/patch-2207-56-0.sql (+24/-0)
database/schema/security.cfg (+5/-1)
lib/lp/bugs/doc/bug-heat.txt (+14/-0)
lib/lp/bugs/interfaces/bugtarget.py (+7/-2)
lib/lp/bugs/model/bug.py (+2/-2)
lib/lp/bugs/model/bugtarget.py (+8/-12)
lib/lp/bugs/model/bugtask.py (+2/-2)
lib/lp/bugs/tests/test_bugheat.py (+82/-0)
lib/lp/registry/interfaces/distributionsourcepackage.py (+17/-1)
lib/lp/registry/model/distributionsourcepackage.py (+125/-71)
lib/lp/soyuz/model/publishing.py (+5/-2)
To merge this branch: bzr merge lp:~edwin-grubbs/launchpad/bug-535430-needspackaging-timeout-part2
Reviewer Review Type Date Requested Status
Björn Tillenius (community) db Abstain
Brad Crittenden (community) code Approve
Review via email: mp+26830@code.launchpad.net

Description of the change

Summary
-------

This branch is a follow-up to
    lp:~edwin-grubbs/launchpad/bug-535430-needspackaging-timeout-part2

It adds the attributes to the DistributionSourcePackageInDatabase and
DistributionSourcePackage classes to access the columns added to the
DistributionSourcePackage table in the last branch. Merging the
DistributionSourcePackageInDatabase and DistributionSourcePackage
classes is deferred, since it is not certain yet whether errors would
occur if a DSP object required a SPPH row to be created first. I added
code to report a silent oops if there is an unexpected creation of a
DSP.

This branch also popuplates total_bug_heat and bug_count columns in the
DistributionSourcePackage table when a bugtask is added. The
po_message_count column will be populated in a later branch.

Implementation details
----------------------

Allow db users 'lucille', 'uploader', and 'queued' to create a DSP entry
when a SPPH record is created. Allow db user 'processmail' to read the
section table, since that is necessary to check the foreign key on the
DSP table.

    database/schema/security.cfg

Added tests.
    lib/lp/bugs/doc/bug-heat.txt
    lib/lp/bugs/tests/test_bugheat.py

Automatically insert into DistributionSourcePackage table when a
SourcePackagePublishingHistory record is added. (See ensure() method.)
Renamed recalculateMaxBugHeat() to recalculateBugHeatCache() and
overrode it in the model's DistributionSourcePackage class to set
total_bug_heat and bug_count in addition to max_bug_heat, which it was
already setting.
    lib/lp/bugs/interfaces/bugtarget.py
    lib/lp/registry/interfaces/distributionsourcepackage.py
    lib/lp/bugs/model/bug.py
    lib/lp/bugs/model/bugtarget.py
    lib/lp/bugs/model/bugtask.py
    lib/lp/registry/model/distributionsourcepackage.py
    lib/lp/soyuz/model/publishing.py

Tests
-----

./bin/test -vv -t 'bug-heat.txt|test_bugheat'

To post a comment you must log in.
Revision history for this message
Brad Crittenden (bac) wrote :
Download full text (8.3 KiB)

Hi Edwin,

This change looks great. I marked a couple of niggly bits but that's
all.

--bac

> === added file 'database/schema/patch-2207-56-0.sql'
> --- database/schema/patch-2207-56-0.sql 1970-01-01 00:00:00 +0000
> +++ database/schema/patch-2207-56-0.sql 2010-06-04 18:48:29 +0000
> @@ -0,0 +1,24 @@
> +-- Copyright 2009 Canonical Ltd. This software is licensed under the

2010

(The contents of the patch are left for a db review.)

> === modified file 'lib/lp/bugs/tests/test_bugheat.py'
> --- lib/lp/bugs/tests/test_bugheat.py 2010-06-01 09:21:32 +0000
> +++ lib/lp/bugs/tests/test_bugheat.py 2010-06-04 18:48:29 +0000
> @@ -7,8 +7,11 @@

Edwin this test is really readable and easy to follow. Good job.

> import unittest
>
> +from storm.store import Store
> +
> from canonical.testing import LaunchpadZopelessLayer
>
> +from lp.testing import TestCaseWithFactory
> from lp.testing.factory import LaunchpadObjectFactory
>
>
> @@ -58,6 +61,85 @@
> def setUp(self):
> self.target = self.factory.makeDistributionSourcePackage()
>
> +class DistributionSourcePackageNullBugHeatCacheTest(
> + TestCaseWithFactory):
> + """Ensure distro source package cache values start at None."""
> +
> + layer = LaunchpadZopelessLayer
> +
> + def setUp(self):
> + TestCaseWithFactory.setUp(self)
> + self.target = self.factory.makeDistributionSourcePackage()
> +
> + def test_null_max_bug_heat(self):
> + self.assertEqual(None, self.target.max_bug_heat)
> +
> + def test_null_total_bug_heat(self):
> + self.assertEqual(None, self.target.total_bug_heat)
> +
> + def test_null_bug_count(self):
> + self.assertEqual(None, self.target.bug_count)
> +
> +
> +class DistributionSourcePackageZeroRecalculateBugHeatCacheTest(
> + TestCaseWithFactory):
> + """Ensure distro source package cache values become zero properly."""
> +
> + layer = LaunchpadZopelessLayer
> +
> + def setUp(self):
> + TestCaseWithFactory.setUp(self)
> + self.target = self.factory.makeDistributionSourcePackage()
> + self.target.recalculateBugHeatCache()
> +
> + def test_zero_max_bug_heat(self):
> + self.assertEqual(0, self.target.max_bug_heat)
> +
> + def test_zero_total_bug_heat(self):
> + self.assertEqual(0, self.target.total_bug_heat)
> +
> + def test_zero_bug_count(self):
> + self.assertEqual(0, self.target.bug_count)
> +
> +
> +class DistributionSourcePackageMultipleBugsRecalculateBugHeatCacheTest(
> + TestCaseWithFactory):
> + """Ensure distro source package cache values are set properly."""
> +
> + layer = LaunchpadZopelessLayer
> +
> + def setUp(self):
> + TestCaseWithFactory.setUp(self)
> + self.target = self.factory.makeDistributionSourcePackage()
> + self.bugtask1 = self.factory.makeBugTask(target=self.target)
> + self.bugtask2 = self.factory.makeBugTask(target=self.target)
> + # Bug heat gets calculated by complicated rules in a db
> + # stored procedure. We will override them here to avoid
> + # testing inconsitencies if those values are calculated
> + # differently in the future.
> + #...

Read more...

review: Approve (code)
Revision history for this message
Edwin Grubbs (edwin-grubbs) wrote :

I assume the changes to database/schema/security.cfg the needs to be reviewed by either Bjorn or Stub. I had to add permissions for db users that had been inserting into the SourcePackagePublishingHistory, since that will cause the DistributionSourcePackage table to be inserted into or updated. The permissions have been changed for the db users "lucille", "uploader", "queued", and "processmail".

I forgot to create this merge proposal with the previous branch as the dependency, so you will see the same schema changes that have already been reviewed.

Revision history for this message
Björn Tillenius (bjornt) wrote :

Changes to security.cfg don't need a DB review, just make sure they are tested.

review: Abstain (db)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'database/schema/comments.sql'
2--- database/schema/comments.sql 2010-06-03 22:21:24 +0000
3+++ database/schema/comments.sql 2010-06-10 01:43:25 +0000
4@@ -472,7 +472,11 @@
5
6 COMMENT ON TABLE DistributionSourcePackage IS 'Representing a sourcepackage in a distribution across all distribution series.';
7 COMMENT ON COLUMN DistributionSourcePackage.bug_reporting_guidelines IS 'Guidelines to the end user for reporting bugs on a particular a source package in a distribution.';
8-COMMENT ON COLUMN DistributionSourcePackage.max_bug_heat IS 'The highest heat value across bugs for this source package.';
9+COMMENT ON COLUMN DistributionSourcePackage.max_bug_heat IS 'The highest heat value across bugs for this source package. NULL means it has not yet been calculated.';
10+COMMENT ON COLUMN DistributionSourcePackage.total_bug_heat IS 'Sum of bug heat matching the package distribution and sourcepackagename. NULL means it has not yet been calculated.';
11+COMMENT ON COLUMN DistributionSourcePackage.bug_count IS 'Number of bugs matching the package distribution and sourcepackagename. NULL means it has not yet been calculated.';
12+COMMENT ON COLUMN DistributionSourcePackage.po_message_count IS 'Number of translations matching the package distribution and sourcepackagename. NULL means it has not yet been calculated.';
13+COMMENT ON COLUMN DistributionSourcePackage.section IS 'Cached section matching the latest SourcePackagePublishingHistory record by distribution and sourcepackagename whose archive purpose is PRIMARY and whose distroseries releasestatus is CURRENT.';
14 COMMENT ON COLUMN DistributionSourcePackage.bug_reported_acknowledgement IS 'A message of acknowledgement to display to a bug reporter after they\'ve reported a new bug.';
15
16 -- DistributionSourcePackageCache
17
18=== added file 'database/schema/patch-2207-56-0.sql'
19--- database/schema/patch-2207-56-0.sql 1970-01-01 00:00:00 +0000
20+++ database/schema/patch-2207-56-0.sql 2010-06-10 01:43:25 +0000
21@@ -0,0 +1,24 @@
22+-- Copyright 2010 Canonical Ltd. This software is licensed under the
23+-- GNU Affero General Public License version 3 (see the file LICENSE).
24+
25+SET client_min_messages=ERROR;
26+
27+INSERT INTO LaunchpadDatabaseRevision VALUES (2207, 56, 0);
28+
29+/*
30+Existing Schema:
31+
32+CREATE TABLE distributionsourcepackage (
33+ id integer NOT NULL,
34+ distribution integer NOT NULL,
35+ sourcepackagename integer NOT NULL,
36+ bug_reporting_guidelines text,
37+ max_bug_heat integer
38+);
39+*/
40+
41+ALTER TABLE DistributionSourcePackage ADD COLUMN total_bug_heat INTEGER;
42+ALTER TABLE DistributionSourcePackage ADD COLUMN bug_count INTEGER;
43+ALTER TABLE DistributionSourcePackage ADD COLUMN po_message_count INTEGER;
44+ALTER TABLE DistributionSourcePackage
45+ ADD COLUMN section INTEGER NOT NULL REFERENCES section(id);
46
47=== modified file 'database/schema/security.cfg'
48--- database/schema/security.cfg 2010-06-07 10:24:03 +0000
49+++ database/schema/security.cfg 2010-06-10 01:43:25 +0000
50@@ -746,6 +746,7 @@
51 public.binarypackagepublishinghistory = SELECT, INSERT, UPDATE, DELETE
52 public.sourcepackagepublishinghistory = SELECT, INSERT, UPDATE, DELETE
53 public.sourcepackagepublishinghistory = SELECT
54+public.distributionsourcepackage = SELECT, INSERT, UPDATE
55
56 # Closing bugs for publication copies.
57 public.bug = SELECT, UPDATE
58@@ -1051,6 +1052,7 @@
59 public.distroseries = SELECT, UPDATE
60 public.distroarchseries = SELECT
61 public.sourcepackagepublishinghistory = SELECT
62+public.distributionsourcepackage = SELECT, INSERT, UPDATE
63 public.sourcepackagefilepublishing = SELECT
64 public.binarypackagefilepublishing = SELECT
65 public.binarypackagepublishinghistory = SELECT
66@@ -1175,8 +1177,9 @@
67 public.sourcepackagefilepublishing = SELECT
68 public.binarypackagefilepublishing = SELECT
69 public.sourcepackagepublishinghistory = SELECT, INSERT, UPDATE
70+public.distributionsourcepackage = SELECT, INSERT, UPDATE
71 public.binarypackagepublishinghistory = SELECT, INSERT, UPDATE
72-public.sourcepackagerecipebuildjob = SELECT, INSERT, UPDATE
73+public.sourcepackagerecipebuildjob = SELECT, INSERT, UPDATE
74 public.component = SELECT
75 public.section = SELECT
76 public.componentselection = SELECT
77@@ -1464,6 +1467,7 @@
78 public.sourcepackagerelease = SELECT
79 public.sourcepackagepublishinghistory = SELECT
80 public.structuralsubscription = SELECT
81+public.section = SELECT
82
83 # Karma
84 public.karma = SELECT, INSERT
85
86=== modified file 'lib/lp/bugs/doc/bug-heat.txt'
87--- lib/lp/bugs/doc/bug-heat.txt 2010-05-27 16:40:54 +0000
88+++ lib/lp/bugs/doc/bug-heat.txt 2010-06-10 01:43:25 +0000
89@@ -308,6 +308,20 @@
90 >>> print dsp.max_bug_heat
91 123
92
93+A DistributionSourcePackage also has a cached value for bug_count and
94+total_bug_heat.
95+
96+ >>> print dsp.bug_count
97+ 1
98+ >>> print dsp.total_bug_heat
99+ 123
100+ >>> dsp_task2 = factory.makeBugTask(target=dsp)
101+ >>> dsp_task2.bug.setHeat(7)
102+ >>> print dsp.bug_count
103+ 2
104+ >>> print dsp.total_bug_heat
105+ 130
106+
107 Transitioning from one target to another, calculates the value for the new
108 target.
109
110
111=== modified file 'lib/lp/bugs/interfaces/bugtarget.py'
112--- lib/lp/bugs/interfaces/bugtarget.py 2010-06-09 08:26:26 +0000
113+++ lib/lp/bugs/interfaces/bugtarget.py 2010-06-10 01:43:25 +0000
114@@ -272,8 +272,13 @@
115 def setMaxBugHeat(heat):
116 """Set the max_bug_heat for this context."""
117
118- def recalculateMaxBugHeat():
119- """Recalculate and set the max_bug_heat for this context."""
120+ def recalculateBugHeatCache():
121+ """Recalculate and set the various bug heat values for this context.
122+
123+ Several different objects cache max_bug_heat.
124+ When DistributionSourcePackage is the target, the total_bug_heat
125+ and bug_count are also cached.
126+ """
127
128
129 class BugDistroSeriesTargetDetails:
130
131=== modified file 'lib/lp/bugs/model/bug.py'
132--- lib/lp/bugs/model/bug.py 2010-06-04 09:12:26 +0000
133+++ lib/lp/bugs/model/bug.py 2010-06-10 01:43:25 +0000
134@@ -887,7 +887,7 @@
135
136 # When a new task is added the bug's heat becomes relevant to the
137 # target's max_bug_heat.
138- target.recalculateMaxBugHeat()
139+ target.recalculateBugHeatCache()
140
141 return new_task
142
143@@ -1569,7 +1569,7 @@
144 self.heat = heat
145 self.heat_last_updated = timestamp
146 for task in self.bugtasks:
147- task.target.recalculateMaxBugHeat()
148+ task.target.recalculateBugHeatCache()
149
150 def updateHeat(self):
151 """See `IBug`."""
152
153=== modified file 'lib/lp/bugs/model/bugtarget.py'
154--- lib/lp/bugs/model/bugtarget.py 2010-05-21 04:38:42 +0000
155+++ lib/lp/bugs/model/bugtarget.py 2010-06-10 01:43:25 +0000
156@@ -235,12 +235,15 @@
157 else:
158 raise NotImplementedError
159
160- def recalculateMaxBugHeat(self):
161- """See `IHasBugHeat`."""
162+ def recalculateBugHeatCache(self):
163+ """See `IHasBugHeat`.
164+
165+ DistributionSourcePackage overrides this method.
166+ """
167 if IProductSeries.providedBy(self):
168- return self.product.recalculateMaxBugHeat()
169+ return self.product.recalculateBugHeatCache()
170 if IDistroSeries.providedBy(self):
171- return self.distribution.recalculateMaxBugHeat()
172+ return self.distribution.recalculateBugHeatCache()
173 if ISourcePackage.providedBy(self):
174 # Should only happen for nominations, so we can safely skip
175 # recalculating max_heat.
176@@ -276,13 +279,6 @@
177 WHERE Bugtask.bug = Bug.id AND
178 Bugtask.product = Product.id AND
179 Product.project = %s""" % sqlvalues(self)]
180- elif IDistributionSourcePackage.providedBy(self):
181- sql = ["""SELECT MAX(heat)
182- FROM Bug, Bugtask
183- WHERE Bugtask.bug = Bug.id AND
184- Bugtask.distribution = %s AND
185- Bugtask.sourcepackagename = %s""" % sqlvalues(
186- self.distribution, self.sourcepackagename)]
187 else:
188 raise NotImplementedError
189
190@@ -299,7 +295,7 @@
191 # If the product is part of a project group we calculate the maximum
192 # heat for the project group too.
193 if IProduct.providedBy(self) and self.project is not None:
194- self.project.recalculateMaxBugHeat()
195+ self.project.recalculateBugHeatCache()
196
197
198
199
200=== modified file 'lib/lp/bugs/model/bugtask.py'
201--- lib/lp/bugs/model/bugtask.py 2010-06-07 18:11:08 +0000
202+++ lib/lp/bugs/model/bugtask.py 2010-06-10 01:43:25 +0000
203@@ -1045,8 +1045,8 @@
204 # After the target has changed, we need to recalculate the maximum bug
205 # heat for the new and old targets.
206 if self.target != target_before_change:
207- target_before_change.recalculateMaxBugHeat()
208- self.target.recalculateMaxBugHeat()
209+ target_before_change.recalculateBugHeatCache()
210+ self.target.recalculateBugHeatCache()
211
212 def updateTargetNameCache(self, newtarget=None):
213 """See `IBugTask`."""
214
215=== modified file 'lib/lp/bugs/tests/test_bugheat.py'
216--- lib/lp/bugs/tests/test_bugheat.py 2010-06-01 09:21:32 +0000
217+++ lib/lp/bugs/tests/test_bugheat.py 2010-06-10 01:43:25 +0000
218@@ -7,8 +7,11 @@
219
220 import unittest
221
222+from storm.store import Store
223+
224 from canonical.testing import LaunchpadZopelessLayer
225
226+from lp.testing import TestCaseWithFactory
227 from lp.testing.factory import LaunchpadObjectFactory
228
229
230@@ -58,6 +61,85 @@
231 def setUp(self):
232 self.target = self.factory.makeDistributionSourcePackage()
233
234+class DistributionSourcePackageNullBugHeatCacheTest(
235+ TestCaseWithFactory):
236+ """Ensure distro source package cache values start at None."""
237+
238+ layer = LaunchpadZopelessLayer
239+
240+ def setUp(self):
241+ TestCaseWithFactory.setUp(self)
242+ self.target = self.factory.makeDistributionSourcePackage()
243+
244+ def test_null_max_bug_heat(self):
245+ self.assertEqual(None, self.target.max_bug_heat)
246+
247+ def test_null_total_bug_heat(self):
248+ self.assertEqual(None, self.target.total_bug_heat)
249+
250+ def test_null_bug_count(self):
251+ self.assertEqual(None, self.target.bug_count)
252+
253+
254+class DistributionSourcePackageZeroRecalculateBugHeatCacheTest(
255+ TestCaseWithFactory):
256+ """Ensure distro source package cache values become zero properly."""
257+
258+ layer = LaunchpadZopelessLayer
259+
260+ def setUp(self):
261+ TestCaseWithFactory.setUp(self)
262+ self.target = self.factory.makeDistributionSourcePackage()
263+ self.target.recalculateBugHeatCache()
264+
265+ def test_zero_max_bug_heat(self):
266+ self.assertEqual(0, self.target.max_bug_heat)
267+
268+ def test_zero_total_bug_heat(self):
269+ self.assertEqual(0, self.target.total_bug_heat)
270+
271+ def test_zero_bug_count(self):
272+ self.assertEqual(0, self.target.bug_count)
273+
274+
275+class DistributionSourcePackageMultipleBugsRecalculateBugHeatCacheTest(
276+ TestCaseWithFactory):
277+ """Ensure distro source package cache values are set properly."""
278+
279+ layer = LaunchpadZopelessLayer
280+
281+ def setUp(self):
282+ TestCaseWithFactory.setUp(self)
283+ self.target = self.factory.makeDistributionSourcePackage()
284+ self.bugtask1 = self.factory.makeBugTask(target=self.target)
285+ self.bugtask2 = self.factory.makeBugTask(target=self.target)
286+ # Bug heat gets calculated by complicated rules in a db
287+ # stored procedure. We will override them here to avoid
288+ # testing inconsitencies if those values are calculated
289+ # differently in the future.
290+ # target.recalculateBugHeatCache() should be called
291+ # automatically by bug.setHeat().
292+ bug1 = self.bugtask1.bug
293+ bug2 = self.bugtask2.bug
294+ bug1.setHeat(7)
295+ bug2.setHeat(19)
296+ Store.of(bug1).flush()
297+ self.max_heat = max(bug1.heat, bug2.heat)
298+ self.total_heat = sum([bug1.heat, bug2.heat])
299+
300+ def test_max_bug_heat(self):
301+ self.assertEqual(self.max_heat, self.target.max_bug_heat)
302+
303+ def test_total_bug_heat(self):
304+ self.assertEqual(self.total_heat, self.target.total_bug_heat)
305+ self.failUnless(
306+ self.target.total_bug_heat > self.target.max_bug_heat,
307+ "Total bug heat should be more than the max bug heat, "
308+ "since we know that multiple bugs have nonzero heat.")
309+
310+ def test_bug_count(self):
311+ self.assertEqual(2, self.target.bug_count)
312+
313
314 class SourcePackageMaxHeatByTargetTest(
315 MaxHeatByTargetBase, unittest.TestCase):
316
317=== modified file 'lib/lp/registry/interfaces/distributionsourcepackage.py'
318--- lib/lp/registry/interfaces/distributionsourcepackage.py 2010-02-12 13:39:56 +0000
319+++ lib/lp/registry/interfaces/distributionsourcepackage.py 2010-06-10 01:43:25 +0000
320@@ -78,9 +78,25 @@
321 "no such package -- this occurs when there is no current series for "
322 "the distribution.")
323
324+ total_bug_heat = Attribute(
325+ "Sum of the bug heat for all the bugs matching the distribution "
326+ "and sourcepackagename of the IDistributionSourcePackage.")
327+
328+ max_bug_heat = Attribute(
329+ "Maximum bug heat for a single bug matching the distribution "
330+ "and sourcepackagename of the IDistributionSourcePackage.")
331+
332+ bug_count = Attribute(
333+ "Number of bugs matching the distribution and sourcepackagename "
334+ "of the IDistributionSourcePackage.")
335+
336+ po_message_count = Attribute(
337+ "Number of translations matching the distribution and "
338+ "sourcepackagename of the IDistributionSourcePackage.")
339+
340 def getReleasesAndPublishingHistory():
341 """Return a list of all releases of this source package in this
342- distribution and their correspodning publishing history.
343+ distribution and their corresponding publishing history.
344
345 Items in the list are tuples comprised of a
346 DistributionSourcePackage and a list of
347
348=== modified file 'lib/lp/registry/model/distributionsourcepackage.py'
349--- lib/lp/registry/model/distributionsourcepackage.py 2010-06-08 13:07:44 +0000
350+++ lib/lp/registry/model/distributionsourcepackage.py 2010-06-10 01:43:25 +0000
351@@ -15,13 +15,17 @@
352 import operator
353
354 from sqlobject.sqlbuilder import SQLConstant
355-from storm.expr import And, Desc, In, Join, Lower
356+from storm.expr import And, Count, Desc, In, Join, Lower, Max, Sum
357 from storm.store import EmptyResultSet
358 from storm.locals import Int, Reference, Store, Storm, Unicode
359+from zope.component import getUtility
360+from zope.error.interfaces import IErrorReportingUtility
361 from zope.interface import implements
362
363+
364 from canonical.database.sqlbase import sqlvalues
365 from canonical.launchpad.database.emailaddress import EmailAddress
366+from lp.registry.interfaces.series import SeriesStatus
367 from lp.registry.model.distroseries import DistroSeries
368 from lp.registry.model.packaging import Packaging
369 from lp.registry.model.structuralsubscription import (
370@@ -30,7 +34,7 @@
371 from canonical.lazr.utils import smartquote
372 from lp.answers.interfaces.questiontarget import IQuestionTarget
373 from lp.bugs.interfaces.bugtarget import IHasBugHeat
374-from lp.bugs.model.bug import BugSet, get_bug_tags_open_count
375+from lp.bugs.model.bug import Bug, BugSet, get_bug_tags_open_count
376 from lp.bugs.model.bugtarget import BugTargetBase, HasBugHeatMixin
377 from lp.bugs.model.bugtask import BugTask
378 from lp.code.model.hasbranches import HasBranchesMixin, HasMergeProposalsMixin
379@@ -43,6 +47,7 @@
380 SourcePackage, SourcePackageQuestionTargetMixin)
381 from lp.soyuz.interfaces.archive import ArchivePurpose
382 from lp.soyuz.interfaces.publishing import PackagePublishingStatus
383+from lp.soyuz.interfaces.section import ISectionSet
384 from lp.soyuz.model.archive import Archive
385 from lp.soyuz.model.distributionsourcepackagerelease import (
386 DistributionSourcePackageRelease)
387@@ -54,6 +59,40 @@
388 CustomLanguageCode, HasCustomLanguageCodesMixin)
389
390
391+class DistributionSourcePackageProperty:
392+ def __init__(self, attrname):
393+ self.attrname = attrname
394+
395+ def __get__(self, obj, class_):
396+ return getattr(obj._self_in_database, self.attrname, None)
397+
398+ def __set__(self, obj, value):
399+ if obj._self_in_database is None:
400+ # Log an oops without raising an error.
401+ exception = AssertionError(
402+ "DistributionSourcePackage record should have been created "
403+ "earlier in the database for distro=%s, sourcepackagename=%s"
404+ % (obj.distribution.name, obj.sourcepackagename.name))
405+ getUtility(IErrorReportingUtility).raising(
406+ (exception.__class__, exception, None))
407+ spph = Store.of(obj.distribution).find(
408+ SourcePackagePublishingHistory,
409+ SourcePackagePublishingHistory.distroseriesID ==
410+ DistroSeries.id,
411+ DistroSeries.distributionID == obj.distribution.id,
412+ SourcePackagePublishingHistory.sourcepackagereleaseID ==
413+ SourcePackageRelease.id,
414+ SourcePackageRelease.sourcepackagenameID ==
415+ obj.sourcepackagename.id
416+ ).order_by(Desc(SourcePackagePublishingHistory.id)).first()
417+ if spph is None:
418+ section = getUtility(ISectionSet)['misc']
419+ else:
420+ section = spph.section
421+ obj._new(obj.distribution, obj.sourcepackagename, section)
422+ setattr(obj._self_in_database, self.attrname, value)
423+
424+
425 class DistributionSourcePackage(BugTargetBase,
426 SourcePackageQuestionTargetMixin,
427 StructuralSubscriptionTargetMixin,
428@@ -72,6 +111,16 @@
429 IDistributionSourcePackage, IHasBugHeat, IHasCustomLanguageCodes,
430 IQuestionTarget)
431
432+ bug_reporting_guidelines = DistributionSourcePackageProperty(
433+ 'bug_reporting_guidelines')
434+ bug_reported_acknowledgement = DistributionSourcePackageProperty(
435+ 'bug_reported_acknowledgement')
436+ max_bug_heat = DistributionSourcePackageProperty('max_bug_heat')
437+ total_bug_heat = DistributionSourcePackageProperty('total_bug_heat')
438+ bug_count = DistributionSourcePackageProperty('bug_count')
439+ po_message_count = DistributionSourcePackageProperty('po_message_count')
440+ section = DistributionSourcePackageProperty('section')
441+
442 def __init__(self, distribution, sourcepackagename):
443 self.distribution = distribution
444 self.sourcepackagename = sourcepackagename
445@@ -118,75 +167,34 @@
446 # measure while DistributionSourcePackage is not yet hooked
447 # into the database but we need access to some of the fields
448 # in the database.
449- return Store.of(self.distribution).find(
450- DistributionSourcePackageInDatabase,
451- DistributionSourcePackageInDatabase.sourcepackagename == (
452- self.sourcepackagename),
453- DistributionSourcePackageInDatabase.distribution == (
454- self.distribution)
455- ).one()
456-
457- def _get_bug_reporting_guidelines(self):
458- """See `IBugTarget`."""
459- dsp_in_db = self._self_in_database
460- if dsp_in_db is not None:
461- return dsp_in_db.bug_reporting_guidelines
462- return None
463-
464- def _set_bug_reporting_guidelines(self, value):
465- """See `IBugTarget`."""
466- dsp_in_db = self._self_in_database
467- if dsp_in_db is None:
468- dsp_in_db = DistributionSourcePackageInDatabase()
469- dsp_in_db.sourcepackagename = self.sourcepackagename
470- dsp_in_db.distribution = self.distribution
471- Store.of(self.distribution).add(dsp_in_db)
472- dsp_in_db.bug_reporting_guidelines = value
473-
474- bug_reporting_guidelines = property(
475- _get_bug_reporting_guidelines,
476- _set_bug_reporting_guidelines)
477-
478- def _get_bug_reported_acknowledgement(self):
479- """See `IBugTarget`."""
480- dsp_in_db = self._self_in_database
481- if dsp_in_db is not None:
482- return dsp_in_db.bug_reported_acknowledgement
483- return None
484-
485- def _set_bug_reported_acknowledgement(self, value):
486- """See `IBugTarget`."""
487- dsp_in_db = self._self_in_database
488- if dsp_in_db is None:
489- dsp_in_db = DistributionSourcePackageInDatabase()
490- dsp_in_db.sourcepackagename = self.sourcepackagename
491- dsp_in_db.distribution = self.distribution
492- Store.of(self.distribution).add(dsp_in_db)
493- dsp_in_db.bug_reported_acknowledgement = value
494-
495- bug_reported_acknowledgement = property(
496- _get_bug_reported_acknowledgement,
497- _set_bug_reported_acknowledgement)
498-
499- def _get_max_bug_heat(self):
500- """See `IHasBugs`."""
501- dsp_in_db = self._self_in_database
502- if dsp_in_db is None:
503- return None
504- else:
505- return dsp_in_db.max_bug_heat
506-
507- def _set_max_bug_heat(self, value):
508- """See `IHasBugs`."""
509- dsp_in_db = self._self_in_database
510- if dsp_in_db is None:
511- dsp_in_db = DistributionSourcePackageInDatabase()
512- dsp_in_db.sourcepackagename = self.sourcepackagename
513- dsp_in_db.distribution = self.distribution
514- Store.of(self.distribution).add(dsp_in_db)
515- dsp_in_db.max_bug_heat = value
516-
517- max_bug_heat = property(_get_max_bug_heat, _set_max_bug_heat)
518+ return self._get(self.distribution, self.sourcepackagename)
519+
520+ def recalculateBugHeatCache(self):
521+ """See `IHasBugHeat`."""
522+ self.max_bug_heat = IStore(Bug).find(
523+ Max(Bug.heat),
524+ BugTask.bug == Bug.id,
525+ BugTask.distributionID == self.distribution.id,
526+ BugTask.sourcepackagenameID == self.sourcepackagename.id).one()
527+ if self.max_bug_heat is None:
528+ # SELECT MAX(Bug.heat) returns NULL if zero rows match.
529+ self.max_bug_heat = 0
530+
531+ self.total_bug_heat = IStore(Bug).find(
532+ Sum(Bug.heat),
533+ BugTask.bug == Bug.id,
534+ BugTask.distributionID == self.distribution.id,
535+ BugTask.sourcepackagenameID == self.sourcepackagename.id).one()
536+ if self.total_bug_heat is None:
537+ self.total_bug_heat = 0
538+
539+ self.bug_count = IStore(Bug).find(
540+ Count(Bug.heat),
541+ BugTask.bug == Bug.id,
542+ BugTask.distributionID == self.distribution.id,
543+ BugTask.sourcepackagenameID == self.sourcepackagename.id).one()
544+ if self.bug_count is None:
545+ self.bug_count = 0
546
547 @property
548 def latest_overall_publication(self):
549@@ -504,6 +512,47 @@
550 In(Lower(EmailAddress.email), email_addresses))
551 return result_set
552
553+ @classmethod
554+ def _get(cls, distribution, sourcepackagename):
555+ return Store.of(distribution).find(
556+ DistributionSourcePackageInDatabase,
557+ DistributionSourcePackageInDatabase.sourcepackagename ==
558+ sourcepackagename,
559+ DistributionSourcePackageInDatabase.distribution ==
560+ distribution
561+ ).one()
562+
563+ @classmethod
564+ def _new(cls, distribution, sourcepackagename, section):
565+ dsp = DistributionSourcePackageInDatabase()
566+ dsp.distribution = distribution
567+ dsp.sourcepackagename = sourcepackagename
568+ dsp.section = section
569+ Store.of(distribution).add(dsp)
570+ return dsp
571+
572+ @classmethod
573+ def ensure(cls, spph):
574+ """Create DistributionSourcePackage record, if necessary.
575+
576+ Only create a record for primary archives (i.e. not for PPAs).
577+ If it already exists, update the section attribute.
578+ """
579+ # Import here to avoid import loop.
580+ sourcepackagename = spph.sourcepackagerelease.sourcepackagename
581+ distribution = spph.distroseries.distribution
582+ section = spph.section
583+
584+ if spph.archive.purpose == ArchivePurpose.PRIMARY:
585+ dsp = cls._get(distribution, sourcepackagename)
586+ if dsp is None:
587+ dsp = cls._new(distribution, sourcepackagename, section)
588+ elif spph.distroseries.status == SeriesStatus.CURRENT:
589+ # If the distroseries.status is not CURRENT, it should
590+ # not override the section from a previously created
591+ # SourcePackagePublishingHistory.
592+ dsp.section = section
593+
594
595 class DistributionSourcePackageInDatabase(Storm):
596 """Temporary class to allow access to the database."""
597@@ -529,4 +578,9 @@
598 bug_reported_acknowledgement = Unicode()
599
600 max_bug_heat = Int()
601+ total_bug_heat = Int()
602+ bug_count = Int()
603+ po_message_count = Int()
604
605+ section_id = Int(name='section')
606+ section = Reference(section_id, 'Section.id')
607
608=== modified file 'lib/lp/soyuz/model/publishing.py'
609--- lib/lp/soyuz/model/publishing.py 2010-05-21 12:25:51 +0000
610+++ lib/lp/soyuz/model/publishing.py 2010-06-10 01:43:25 +0000
611@@ -49,12 +49,12 @@
612 from lp.soyuz.model.binarypackagename import BinaryPackageName
613 from lp.soyuz.model.binarypackagerelease import (BinaryPackageRelease,
614 BinaryPackageReleaseDownloadCount)
615+from lp.soyuz.interfaces.archive import ArchivePurpose
616 from lp.soyuz.model.files import (
617 BinaryPackageFile, SourcePackageReleaseFile)
618 from canonical.launchpad.database.librarian import (
619 LibraryFileAlias, LibraryFileContent)
620 from lp.soyuz.model.packagediff import PackageDiff
621-from lp.soyuz.interfaces.archive import ArchivePurpose
622 from lp.soyuz.interfaces.archivearch import IArchiveArchSet
623 from lp.soyuz.interfaces.binarypackagebuild import (
624 BuildSetStatus, IBinaryPackageBuildSet)
625@@ -1162,7 +1162,10 @@
626 section=section,
627 status=PackagePublishingStatus.PENDING,
628 datecreated=UTC_NOW)
629-
630+ # Import here to prevent import loop.
631+ from lp.registry.model.distributionsourcepackage import (
632+ DistributionSourcePackage)
633+ DistributionSourcePackage.ensure(pub)
634 return pub
635
636 def getBuildsForSourceIds(

Subscribers

People subscribed via source and target branches

to status/vote changes: