Merge lp:~julian-edwards/launchpad/set-previous-series-bug-805913 into lp:launchpad

Proposed by Julian Edwards on 2011-07-05
Status: Merged
Approved by: Julian Edwards on 2011-07-06
Approved revision: no longer in the source branch.
Merged at revision: 13384
Proposed branch: lp:~julian-edwards/launchpad/set-previous-series-bug-805913
Merge into: lp:launchpad
Diff against target: 299 lines (+115/-33)
6 files modified
lib/lp/registry/browser/distroseries.py (+10/-2)
lib/lp/registry/browser/tests/test_distroseries.py (+36/-11)
lib/lp/registry/interfaces/distroseries.py (+14/-3)
lib/lp/registry/model/distroseries.py (+17/-12)
lib/lp/registry/model/sourcepackage.py (+5/-5)
lib/lp/registry/tests/test_distroseries.py (+33/-0)
To merge this branch: bzr merge lp:~julian-edwards/launchpad/set-previous-series-bug-805913
Reviewer Review Type Date Requested Status
Jeroen T. Vermeulen (community) 2011-07-05 Approve on 2011-07-06
Review via email: mp+66942@code.launchpad.net

Commit Message

[r=jtv][bug=805913] Make DistroSeries.previous_series get auto-populated when a new series is created.

Description of the Change

= Summary =
Make DistroSeries.previous_series get auto-populated when a new series is created.

== Implementation details ==
1. rename prior_series to priorReleasedSeries and convert to a method (only one place was using it and it didn't need to be a cachedproperty)
2. refactor the code in priorReleasedSeries so it's also in IDistroSeriesSet.
3. Stormify the query.
4. Add tests for it! (there were none before)
5. Make the browser code for adding a new series call the utility method to work out what the previous_series should be.
6. Tests for that.

ADDENDUM!

It turns out that going back to the prior released is not the right thing since there can be many unreleased series, so the code now searches back to the most recent series and uses that (if there is one).

== Tests ==
bin/test -cvvt TestDistroSeriesAddView -t test_priorReleasedSeries

== Demo and Q/A ==
+initseries currently blows up when initialising because of the missing data,
this will fix it and easily checked.

= Launchpad lint =

Checking for conflicts and issues in changed files.

Linting changed files:
  lib/lp/registry/tests/test_distroseries.py
  lib/lp/registry/interfaces/distroseries.py
  lib/lp/registry/browser/tests/test_distroseries.py
  lib/lp/registry/model/sourcepackage.py
  lib/lp/registry/model/distroseries.py
  lib/lp/registry/browser/distroseries.py

To post a comment you must log in.
Jeroen T. Vermeulen (jtv) wrote :

Some definite improvements here, even without considering the fixed bug. I do have some problems with the code though: you seem to test for the presence of a release date as an implicit side effect of SQL comparison semantics for nulls (whereas, on a side note, doing the same comparison in python would include unreleased series!). Please make this explicit: add a check or a comment, and test that the right thing happens with unreleased series.

Also, what about createAndAdd in the view code? It uses self.context.series[0] for previous_series. Should that be self.context.priorReleasedSeries()[0]?

(I can't say that priorReleasedSeries works for me as a name, by the way: I'd pronounce it with emphasis on the one word that wasn't capitalized. That's rarely an issue with the start-with-a-verb style we used to require.)

Jeroen

review: Approve
Jeroen T. Vermeulen (jtv) :
review: Needs Fixing
Julian Edwards (julian-edwards) wrote :

Thanks for the review, let me know how it looks now.

On Wednesday 06 July 2011 14:14:24 you wrote:
> Review: Approve
> Some definite improvements here, even without considering the fixed bug. I
> do have some problems with the code though: you seem to test for the
> presence of a release date as an implicit side effect of SQL comparison
> semantics for nulls (whereas, on a side note, doing the same comparison in
> python would include unreleased series!). Please make this explicit: add
> a check or a comment, and test that the right thing happens with
> unreleased series.

Right, thanks for spotting that. I've changed it to check that datereleased is
not NULL.

> Also, what about createAndAdd in the view code? It uses
> self.context.series[0] for previous_series. Should that be
> self.context.priorReleasedSeries()[0]?

No, it's correct, you should have paid attention to the addendum :)

> (I can't say that priorReleasedSeries works for me as a name, by the way:
> I'd pronounce it with emphasis on the one word that wasn't capitalized.
> That's rarely an issue with the start-with-a-verb style we used to
> require.)

There's no accounting for taste!

Jeroen T. Vermeulen (jtv) wrote :

This is a little bit clearer, thanks.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/registry/browser/distroseries.py'
2--- lib/lp/registry/browser/distroseries.py 2011-07-05 15:51:35 +0000
3+++ lib/lp/registry/browser/distroseries.py 2011-07-07 08:59:47 +0000
4@@ -93,7 +93,9 @@
5 DistroSeriesDifferenceStatus,
6 DistroSeriesDifferenceType,
7 )
8-from lp.registry.interfaces.distroseries import IDistroSeries
9+from lp.registry.interfaces.distroseries import (
10+ IDistroSeries,
11+ )
12 from lp.registry.interfaces.distroseriesdifference import (
13 IDistroSeriesDifferenceSource,
14 )
15@@ -620,6 +622,12 @@
16 @action(_('Add Series'), name='create')
17 def createAndAdd(self, action, data):
18 """Create and add a new Distribution Series"""
19+ # 'series' is a cached property so this won't issue 2 queries.
20+ if self.context.series:
21+ previous_series = self.context.series[0]
22+ else:
23+ previous_series = None
24+ # previous_series will be None if there isn't one.
25 distroseries = self.context.newSeries(
26 name=data['name'],
27 displayname=data['displayname'],
28@@ -627,7 +635,7 @@
29 summary=data['summary'],
30 description=u"",
31 version=data['version'],
32- previous_series=None,
33+ previous_series=previous_series,
34 registrant=self.user)
35 notify(ObjectCreatedEvent(distroseries))
36 self.next_url = canonical_url(distroseries)
37
38=== modified file 'lib/lp/registry/browser/tests/test_distroseries.py'
39--- lib/lp/registry/browser/tests/test_distroseries.py 2011-07-05 15:51:35 +0000
40+++ lib/lp/registry/browser/tests/test_distroseries.py 2011-07-07 08:59:47 +0000
41@@ -5,6 +5,7 @@
42
43 __metaclass__ = type
44
45+from datetime import timedelta
46 import difflib
47 import re
48 from textwrap import TextWrapper
49@@ -62,6 +63,7 @@
50 getFeatureFlag,
51 )
52 from lp.services.features.testing import FeatureFixture
53+from lp.services.utils import utc_now
54 from lp.soyuz.enums import (
55 ArchivePermissionType,
56 PackagePublishingStatus,
57@@ -469,13 +471,12 @@
58
59 layer = DatabaseFunctionalLayer
60
61- def test_submit(self):
62- # When creating a new DistroSeries via DistroSeriesAddView, the title
63- # is set to the same as the displayname (title is, in any case,
64- # deprecated), the description is left empty, and previous_series is
65- # None (DistroSeriesInitializeView takes care of setting that).
66- user = self.factory.makePerson()
67- distribution = self.factory.makeDistribution(owner=user)
68+ def setUp(self):
69+ super(TestDistroSeriesAddView, self).setUp()
70+ self.user = self.factory.makePerson()
71+ self.distribution = self.factory.makeDistribution(owner=self.user)
72+
73+ def createNewDistroseries(self):
74 form = {
75 "field.name": u"polished",
76 "field.version": u"12.04",
77@@ -483,17 +484,41 @@
78 "field.summary": u"Even The Register likes it.",
79 "field.actions.create": u"Add Series",
80 }
81- with person_logged_in(user):
82- create_initialized_view(distribution, "+addseries", form=form)
83- distroseries = distribution.getSeries(u"polished")
84+ with person_logged_in(self.user):
85+ create_initialized_view(self.distribution, "+addseries",
86+ form=form)
87+ distroseries = self.distribution.getSeries(u"polished")
88+ return distroseries
89+
90+ def assertCreated(self, distroseries):
91 self.assertEqual(u"polished", distroseries.name)
92 self.assertEqual(u"12.04", distroseries.version)
93 self.assertEqual(u"Polished Polecat", distroseries.displayname)
94 self.assertEqual(u"Polished Polecat", distroseries.title)
95 self.assertEqual(u"Even The Register likes it.", distroseries.summary)
96 self.assertEqual(u"", distroseries.description)
97+ self.assertEqual(self.user, distroseries.owner)
98+
99+ def test_plain_submit(self):
100+ # When creating a new DistroSeries via DistroSeriesAddView, the title
101+ # is set to the same as the displayname (title is, in any case,
102+ # deprecated), the description is left empty, and previous_series is
103+ # None (DistroSeriesInitializeView takes care of setting that).
104+ distroseries = self.createNewDistroseries()
105+ self.assertCreated(distroseries)
106 self.assertIs(None, distroseries.previous_series)
107- self.assertEqual(user, distroseries.owner)
108+
109+ def test_submit_sets_previous_series(self):
110+ # Creating a new series when one already exists should set the
111+ # previous_series.
112+ old_series = self.factory.makeDistroSeries(self.distribution,
113+ version='11.10')
114+ older_series = self.factory.makeDistroSeries(self.distribution,
115+ version='11.04')
116+ old_time = utc_now() - timedelta(days=5)
117+ removeSecurityProxy(old_series).datereleased = old_time
118+ distroseries = self.createNewDistroseries()
119+ self.assertEqual(old_series, distroseries.previous_series)
120
121
122 class TestDistroSeriesInitializeView(TestCaseWithFactory):
123
124=== modified file 'lib/lp/registry/interfaces/distroseries.py'
125--- lib/lp/registry/interfaces/distroseries.py 2011-07-05 10:47:28 +0000
126+++ lib/lp/registry/interfaces/distroseries.py 2011-07-07 08:59:47 +0000
127@@ -360,9 +360,8 @@
128 automatically upgrade within backports, but not into it.
129 """))
130
131- # other properties
132- prior_series = Attribute(
133- "Prior series *by date* from the same distribution.")
134+ def priorReleasedSeries():
135+ """Prior series *by date* from the same distribution."""
136
137 main_archive = exported(
138 Reference(
139@@ -1070,6 +1069,18 @@
140 released == None will do no filtering on status.
141 """
142
143+ def priorReleasedSeries(self, distribution, prior_to_date):
144+ """Find distroseries for the supplied distro released before a
145+ certain date.
146+
147+ :param distribution: An `IDistribution` in which to search for its
148+ series.
149+ :param prior_to_date: A `datetime`
150+
151+ :return: `IResultSet` of `IDistroSeries` that were released before
152+ prior_to_date, ordered in increasing order of age.
153+ """
154+
155
156 @error_status(httplib.BAD_REQUEST)
157 class DerivationError(Exception):
158
159=== modified file 'lib/lp/registry/model/distroseries.py'
160--- lib/lp/registry/model/distroseries.py 2011-07-04 16:01:04 +0000
161+++ lib/lp/registry/model/distroseries.py 2011-07-07 08:59:47 +0000
162@@ -688,22 +688,14 @@
163 orderBy=["Language.englishname"])
164 return result
165
166- @cachedproperty
167- def prior_series(self):
168+ def priorReleasedSeries(self):
169 """See `IDistroSeries`."""
170- # This property is cached because it is used intensely inside
171- # sourcepackage.py; avoiding regeneration reduces a lot of
172- # count(*) queries.
173 datereleased = self.datereleased
174 # if this one is unreleased, use the last released one
175 if not datereleased:
176- datereleased = 'NOW'
177- results = DistroSeries.select('''
178- distribution = %s AND
179- datereleased < %s
180- ''' % sqlvalues(self.distribution.id, datereleased),
181- orderBy=['-datereleased'])
182- return list(results)
183+ datereleased = UTC_NOW
184+ return getUtility(IDistroSeriesSet).priorReleasedSeries(
185+ self.distribution, datereleased)
186
187 @property
188 def bug_reporting_guidelines(self):
189@@ -2030,4 +2022,17 @@
190 if orderBy is not None:
191 return DistroSeries.select(where_clause, orderBy=orderBy)
192 else:
193+
194 return DistroSeries.select(where_clause)
195+
196+ def priorReleasedSeries(self, distribution, prior_to_date):
197+ """See `IDistroSeriesSet`."""
198+ store = Store.of(distribution)
199+ results = store.find(
200+ DistroSeries,
201+ DistroSeries.distributionID == distribution.id,
202+ DistroSeries.datereleased < prior_to_date,
203+ DistroSeries.datereleased != None
204+ ).order_by(Desc(DistroSeries.datereleased))
205+
206+ return results
207
208=== modified file 'lib/lp/registry/model/sourcepackage.py'
209--- lib/lp/registry/model/sourcepackage.py 2011-06-08 15:00:44 +0000
210+++ lib/lp/registry/model/sourcepackage.py 2011-07-07 08:59:47 +0000
211@@ -44,7 +44,6 @@
212 BugTargetBase,
213 HasBugHeatMixin,
214 )
215-from lp.bugs.model.bugtask import BugTask
216 from lp.buildmaster.enums import BuildStatus
217 from lp.code.model.seriessourcepackagebranch import (
218 SeriesSourcePackageBranchSet,
219@@ -428,9 +427,9 @@
220 # if we are an ubuntu sourcepackage, try the previous series of
221 # ubuntu
222 if self.distribution == ubuntu:
223- ubuntuseries = self.distroseries.prior_series
224- if ubuntuseries:
225- previous_ubuntu_series = ubuntuseries[0]
226+ ubuntuseries = self.distroseries.priorReleasedSeries()
227+ previous_ubuntu_series = ubuntuseries.first()
228+ if previous_ubuntu_series is not None:
229 sp = SourcePackage(sourcepackagename=self.sourcepackagename,
230 distroseries=previous_ubuntu_series)
231 return sp.packaging
232@@ -500,7 +499,8 @@
233 """See `IBugTarget`."""
234 return self.distroseries.getUsedBugTags()
235
236- def getUsedBugTagsWithOpenCounts(self, user, tag_limit=0, include_tags=None):
237+ def getUsedBugTagsWithOpenCounts(self, user, tag_limit=0,
238+ include_tags=None):
239 """See IBugTarget."""
240 # Circular fail.
241 from lp.bugs.model.bugsummary import BugSummary
242
243=== modified file 'lib/lp/registry/tests/test_distroseries.py'
244--- lib/lp/registry/tests/test_distroseries.py 2011-07-04 15:40:04 +0000
245+++ lib/lp/registry/tests/test_distroseries.py 2011-07-07 08:59:47 +0000
246@@ -5,6 +5,9 @@
247
248 __metaclass__ = type
249
250+from datetime import (
251+ timedelta,
252+)
253 import transaction
254 from zope.component import getUtility
255 from zope.security.proxy import removeSecurityProxy
256@@ -20,6 +23,7 @@
257 from lp.registry.errors import NoSuchDistroSeries
258 from lp.registry.interfaces.distroseries import IDistroSeriesSet
259 from lp.registry.interfaces.pocket import PackagePublishingPocket
260+from lp.services.utils import utc_now
261 from lp.soyuz.enums import (
262 ArchivePurpose,
263 PackagePublishingStatus,
264@@ -256,6 +260,35 @@
265 distroseries=distroseries, archive=distroseries.main_archive)
266 self.assertTrue(distroseries.isInitialized())
267
268+ def test_priorReleasedSeries(self):
269+ # Make sure that previousReleasedSeries returns all series with a
270+ # release date less than the contextual series,
271+ # ordered by descending date.
272+ distro = self.factory.makeDistribution()
273+ unreleased = self.factory.makeDistroSeries(distribution=distro)
274+ ds1 = self.factory.makeDistroSeries(distribution=distro)
275+ ds2 = self.factory.makeDistroSeries(distribution=distro)
276+ ds3 = self.factory.makeDistroSeries(distribution=distro)
277+ ds4 = self.factory.makeDistroSeries(distribution=distro)
278+
279+ now = utc_now()
280+ older = now - timedelta(days=5)
281+ oldest = now - timedelta(days=10)
282+ newer = now + timedelta(days=15)
283+ removeSecurityProxy(ds1).datereleased = oldest
284+ removeSecurityProxy(ds2).datereleased = older
285+ removeSecurityProxy(ds3).datereleased = now
286+ removeSecurityProxy(ds4).datereleased = newer
287+
288+ # The data set up here is 5 distroseries. where one is unreleased,
289+ # ds1 and ds2 are released and in the past and ds4 is released but
290+ # in the future compared to ds3.
291+
292+ prior = ds3.priorReleasedSeries()
293+ self.assertEqual(
294+ [ds2, ds1],
295+ list(prior))
296+
297 def test_getDifferenceComments_gets_DistroSeriesDifferenceComments(self):
298 distroseries = self.factory.makeDistroSeries()
299 dsd = self.factory.makeDistroSeriesDifference(