Merge lp:~wgrant/launchpad/bug-830849 into lp:launchpad

Proposed by William Grant
Status: Merged
Approved by: William Grant
Approved revision: no longer in the source branch.
Merged at revision: 13754
Proposed branch: lp:~wgrant/launchpad/bug-830849
Merge into: lp:launchpad
Prerequisite: lp:~wgrant/launchpad/bug-830803
Diff against target: 304 lines (+73/-101)
3 files modified
lib/lp/bugs/model/bugtask.py (+43/-97)
lib/lp/bugs/model/tests/test_bugsummary.py (+7/-4)
lib/lp/bugs/model/tests/test_bugtask.py (+23/-0)
To merge this branch: bzr merge lp:~wgrant/launchpad/bug-830849
Reviewer Review Type Date Requested Status
Henning Eggers (community) Approve
Review via email: mp+72379@code.launchpad.net

Commit message

Merge validate_target_attribute and validate_sourcepackagename into BugTask.transitionToTarget.

Description of the change

Now that all writes to BugTask's target key attributes go through transitionToTarget, we can merge the ugly Storm column validators into the method.

validate_target_attribute has pretty much just been deleted, as its checks and updateTargetNameCache were already handled by transitionToTarget.

validate_sourcepackagename has been replaced by calling _syncSourcePackages in transitionToTarget.

To post a comment you must log in.
Revision history for this message
Henning Eggers (henninge) wrote :

Thank you for branch. The code looks same to me but I have two issues with style:

Multi-line if conditions don't read well as it is hard to distinguish the condition from the body of the statement. Please either use a variable for the condition or apply an extra step of indentation for the second line of the condition.

I, too, favor short code and short names for variables that are short-lived, but single-letter variable names are still an abomination. ;-) No reason not to use "target" instead of t. And while you are at it, I don't think "distroseries" is *that* long that it needs to be shortened.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/bugs/model/bugtask.py'
2--- lib/lp/bugs/model/bugtask.py 2011-08-17 12:41:43 +0000
3+++ lib/lp/bugs/model/bugtask.py 2011-08-22 13:26:47 +0000
4@@ -135,10 +135,7 @@
5 from lp.registry.interfaces.distributionsourcepackage import (
6 IDistributionSourcePackage,
7 )
8-from lp.registry.interfaces.distroseries import (
9- IDistroSeries,
10- IDistroSeriesSet,
11- )
12+from lp.registry.interfaces.distroseries import IDistroSeries
13 from lp.registry.interfaces.milestone import (
14 IMilestoneSet,
15 IProjectGroupMilestone,
16@@ -148,14 +145,8 @@
17 validate_person,
18 validate_public_person,
19 )
20-from lp.registry.interfaces.product import (
21- IProduct,
22- IProductSet,
23- )
24-from lp.registry.interfaces.productseries import (
25- IProductSeries,
26- IProductSeriesSet,
27- )
28+from lp.registry.interfaces.product import IProduct
29+from lp.registry.interfaces.productseries import IProductSeries
30 from lp.registry.interfaces.projectgroup import IProjectGroup
31 from lp.registry.interfaces.sourcepackage import ISourcePackage
32 from lp.registry.interfaces.sourcepackagename import ISourcePackageNameSet
33@@ -339,40 +330,6 @@
34 return bugtask.bug
35
36
37-@block_implicit_flushes
38-def validate_target_attribute(self, attr, value):
39- """Update the targetnamecache."""
40- # Don't update targetnamecache during _init().
41- if self._SO_creating or self._inhibit_target_check:
42- return value
43- # Determine the new target attributes.
44- target_params = dict(
45- product=self.product,
46- productseries=self.productseries,
47- sourcepackagename=self.sourcepackagename,
48- distribution=self.distribution,
49- distroseries=self.distroseries)
50- utility_iface_dict = {
51- 'productID': IProductSet,
52- 'productseriesID': IProductSeriesSet,
53- 'sourcepackagenameID': ISourcePackageNameSet,
54- 'distributionID': IDistributionSet,
55- 'distroseriesID': IDistroSeriesSet,
56- }
57- utility_iface = utility_iface_dict[attr]
58- if value is None:
59- target_params[attr[:-2]] = None
60- else:
61- target_params[attr[:-2]] = getUtility(utility_iface).get(value)
62-
63- # Update the target name cache with the potential new target. The
64- # attribute changes haven't been made yet, so we need to calculate the
65- # target manually.
66- self.updateTargetNameCache(bug_target_from_key(**target_params))
67-
68- return value
69-
70-
71 class PassthroughValue:
72 """A wrapper to allow setting values on conjoined bug tasks."""
73
74@@ -400,13 +357,10 @@
75 # people try to update the conjoined slave via the API.
76 conjoined_master = self.conjoined_master
77 if conjoined_master is not None:
78- setattr(self.conjoined_master, attr, value)
79+ setattr(conjoined_master, attr, value)
80 return value
81
82- # The conjoined slave is updated before the master one because,
83- # for distro tasks, conjoined_slave does a comparison on
84- # sourcepackagename, and the sourcepackagenames will not match
85- # if the conjoined master is altered before the conjoined slave!
86+ # If there is a conjoined slave, update that.
87 conjoined_bugtask = self.conjoined_slave
88 if conjoined_bugtask:
89 setattr(conjoined_bugtask, attr, PassthroughValue(value))
90@@ -427,15 +381,6 @@
91 return validate_person(self, attr, value)
92
93
94-@block_implicit_flushes
95-def validate_sourcepackagename(self, attr, value):
96- is_passthrough = isinstance(value, PassthroughValue)
97- value = validate_conjoined_attribute(self, attr, value)
98- if not is_passthrough:
99- self._syncSourcePackages(value)
100- return validate_target_attribute(self, attr, value)
101-
102-
103 def validate_target(bug, target):
104 """Validate a bugtask target against a bug's existing tasks.
105
106@@ -446,7 +391,8 @@
107 "A fix for this bug has already been requested for %s"
108 % target.displayname)
109
110- if IDistributionSourcePackage.providedBy(target):
111+ if (IDistributionSourcePackage.providedBy(target) or
112+ ISourcePackage.providedBy(target)):
113 # If the distribution has at least one series, check that the
114 # source package has been published in the distribution.
115 if (target.sourcepackagename is not None and
116@@ -514,24 +460,19 @@
117 bug = ForeignKey(dbName='bug', foreignKey='Bug', notNull=True)
118 product = ForeignKey(
119 dbName='product', foreignKey='Product',
120- notNull=False, default=None,
121- storm_validator=validate_target_attribute)
122+ notNull=False, default=None)
123 productseries = ForeignKey(
124 dbName='productseries', foreignKey='ProductSeries',
125- notNull=False, default=None,
126- storm_validator=validate_target_attribute)
127+ notNull=False, default=None)
128 sourcepackagename = ForeignKey(
129 dbName='sourcepackagename', foreignKey='SourcePackageName',
130- notNull=False, default=None,
131- storm_validator=validate_sourcepackagename)
132+ notNull=False, default=None)
133 distribution = ForeignKey(
134 dbName='distribution', foreignKey='Distribution',
135- notNull=False, default=None,
136- storm_validator=validate_target_attribute)
137+ notNull=False, default=None)
138 distroseries = ForeignKey(
139 dbName='distroseries', foreignKey='DistroSeries',
140- notNull=False, default=None,
141- storm_validator=validate_target_attribute)
142+ notNull=False, default=None)
143 milestone = ForeignKey(
144 dbName='milestone', foreignKey='Milestone',
145 notNull=False, default=None,
146@@ -712,7 +653,7 @@
147 """See `IBugTask`."""
148 return self.bug.isSubscribed(person)
149
150- def _syncSourcePackages(self, new_spnid):
151+ def _syncSourcePackages(self, new_spn):
152 """Synchronize changes to source packages with other distrotasks.
153
154 If one distroseriestask's source package is changed, all the
155@@ -720,22 +661,22 @@
156 package has to be changed, as well as the corresponding
157 distrotask.
158 """
159- if self.bug is None:
160- # The validator is being called on an incomplete bug task.
161+ if self.bug is None or not (self.distribution or self.distroseries):
162+ # The validator is being called on a new or non-distro task.
163 return
164- if self.distroseries is not None:
165- distribution = self.distroseries.distribution
166- else:
167- distribution = self.distribution
168- if distribution is not None:
169- for bugtask in self.related_tasks:
170- if bugtask.distroseries:
171- related_distribution = bugtask.distroseries.distribution
172- else:
173- related_distribution = bugtask.distribution
174- if (related_distribution == distribution and
175- bugtask.sourcepackagenameID == self.sourcepackagenameID):
176- bugtask.sourcepackagenameID = PassthroughValue(new_spnid)
177+ distribution = self.distribution or self.distroseries.distribution
178+ for bugtask in self.related_tasks:
179+ relevant = (
180+ bugtask.sourcepackagename == self.sourcepackagename and
181+ distribution in (
182+ bugtask.distribution,
183+ getattr(bugtask.distroseries, 'distribution', None)))
184+ if relevant:
185+ key = bug_target_to_key(bugtask.target)
186+ key['sourcepackagename'] = new_spn
187+ bugtask.transitionToTarget(
188+ bug_target_from_key(**key),
189+ _sync_sourcepackages=False)
190
191 def getContributorInfo(self, user, person):
192 """See `IBugTask`."""
193@@ -1200,13 +1141,13 @@
194
195 validate_target(self.bug, target)
196
197- def transitionToTarget(self, target):
198+ def transitionToTarget(self, target, _sync_sourcepackages=True):
199 """See `IBugTask`.
200
201- This method allows changing the target of some bug
202- tasks. The rules it follows are similar to the ones
203- enforced implicitly by the code in
204- lib/canonical/launchpad/browser/bugtask.py#BugTaskEditView.
205+ If _sync_sourcepackages is True (the default) and the
206+ sourcepackagename is being changed, any other tasks for the same
207+ name in this distribution will have their names updated to
208+ match. This should only be used by _syncSourcePackages.
209 """
210
211 if self.target == target:
212@@ -1223,12 +1164,17 @@
213 # current target, or reset it to None
214 self.milestone = None
215
216- # Inhibit validate_target_attribute, as we can't set them all
217- # atomically, but we know the final result is correct.
218- self._inhibit_target_check = True
219- for name, value in bug_target_to_key(target).iteritems():
220+ new_key = bug_target_to_key(target)
221+
222+ # As a special case, if the sourcepackagename has changed then
223+ # we update any other tasks for the same distribution and
224+ # sourcepackagename. This keeps series tasks consistent.
225+ if (_sync_sourcepackages and
226+ new_key['sourcepackagename'] != self.sourcepackagename):
227+ self._syncSourcePackages(new_key['sourcepackagename'])
228+
229+ for name, value in new_key.iteritems():
230 setattr(self, name, value)
231- self._inhibit_target_check = False
232 self.updateTargetNameCache()
233
234 # After the target has changed, we need to recalculate the maximum bug
235
236=== modified file 'lib/lp/bugs/model/tests/test_bugsummary.py'
237--- lib/lp/bugs/model/tests/test_bugsummary.py 2011-08-22 06:11:15 +0000
238+++ lib/lp/bugs/model/tests/test_bugsummary.py 2011-08-22 13:26:47 +0000
239@@ -752,8 +752,10 @@
240 def test_changeDistroSeriesSourcePackage(self):
241 distribution = self.factory.makeDistribution()
242 series = self.factory.makeDistroSeries(distribution=distribution)
243- package_a = self.factory.makeSourcePackage(distroseries=series)
244- package_b = self.factory.makeSourcePackage(distroseries=series)
245+ package_a = self.factory.makeSourcePackage(
246+ distroseries=series, publish=True)
247+ package_b = self.factory.makeSourcePackage(
248+ distroseries=series, publish=True)
249 sourcepackagename_a = package_a.sourcepackagename
250 sourcepackagename_b = package_b.sourcepackagename
251 bug_task = self.factory.makeBugTask(target=package_a)
252@@ -789,7 +791,8 @@
253 BugSummary.sourcepackagename == sourcepackagename_b),
254 0)
255
256- removeSecurityProxy(bug_task).sourcepackagename = sourcepackagename_b
257+ bug_task.transitionToTarget(
258+ series.getSourcePackage(sourcepackagename_b))
259
260 self.assertEqual(
261 self.getPublicCount(
262@@ -850,7 +853,7 @@
263 BugSummary.sourcepackagename == sourcepackagename),
264 1)
265
266- removeSecurityProxy(bug_task).sourcepackagename = None
267+ bug_task.transitionToTarget(series)
268
269 self.assertEqual(
270 self.getPublicCount(
271
272=== modified file 'lib/lp/bugs/model/tests/test_bugtask.py'
273--- lib/lp/bugs/model/tests/test_bugtask.py 2011-08-11 05:07:38 +0000
274+++ lib/lp/bugs/model/tests/test_bugtask.py 2011-08-22 13:26:47 +0000
275@@ -1910,6 +1910,29 @@
276 new_product.bugtargetdisplayname,
277 removeSecurityProxy(task).targetnamecache)
278
279+ def test_matching_sourcepackage_tasks_updated_when_name_changed(self):
280+ # If the sourcepackagename is changed, it's changed on all tasks
281+ # with the same distribution and sourcepackagename.
282+
283+ # Create a distribution and distroseries with tasks.
284+ ds = self.factory.makeDistroSeries()
285+ bug = self.factory.makeBug(distribution=ds.distribution)
286+ ds_task = self.factory.makeBugTask(bug=bug, target=ds)
287+
288+ # Also create a task for another distro. It will not be touched.
289+ other_distro = self.factory.makeDistribution()
290+ self.factory.makeBugTask(bug=bug, target=other_distro)
291+
292+ self.assertContentEqual(
293+ (task.target for task in bug.bugtasks),
294+ [ds, ds.distribution, other_distro])
295+ sp = self.factory.makeSourcePackage(distroseries=ds, publish=True)
296+ with person_logged_in(ds_task.owner):
297+ ds_task.transitionToTarget(sp)
298+ self.assertContentEqual(
299+ (t.target for t in bug.bugtasks),
300+ [sp, sp.distribution_sourcepackage, other_distro])
301+
302
303 class TestBugTargetKeys(TestCaseWithFactory):
304 """Tests for bug_target_to_key and bug_target_from_key."""