135
135
from lp.registry.interfaces.distributionsourcepackage import (
136
136
IDistributionSourcePackage,
138
from lp.registry.interfaces.distroseries import (
138
from lp.registry.interfaces.distroseries import IDistroSeries
142
139
from lp.registry.interfaces.milestone import (
144
141
IProjectGroupMilestone,
149
146
validate_public_person,
151
from lp.registry.interfaces.product import (
155
from lp.registry.interfaces.productseries import (
148
from lp.registry.interfaces.product import IProduct
149
from lp.registry.interfaces.productseries import IProductSeries
159
150
from lp.registry.interfaces.projectgroup import IProjectGroup
160
151
from lp.registry.interfaces.sourcepackage import ISourcePackage
161
152
from lp.registry.interfaces.sourcepackagename import ISourcePackageNameSet
339
330
return bugtask.bug
342
@block_implicit_flushes
343
def validate_target_attribute(self, attr, value):
344
"""Update the targetnamecache."""
345
# Don't update targetnamecache during _init().
346
if self._SO_creating or self._inhibit_target_check:
348
# Determine the new target attributes.
349
target_params = dict(
350
product=self.product,
351
productseries=self.productseries,
352
sourcepackagename=self.sourcepackagename,
353
distribution=self.distribution,
354
distroseries=self.distroseries)
355
utility_iface_dict = {
356
'productID': IProductSet,
357
'productseriesID': IProductSeriesSet,
358
'sourcepackagenameID': ISourcePackageNameSet,
359
'distributionID': IDistributionSet,
360
'distroseriesID': IDistroSeriesSet,
362
utility_iface = utility_iface_dict[attr]
364
target_params[attr[:-2]] = None
366
target_params[attr[:-2]] = getUtility(utility_iface).get(value)
368
# Update the target name cache with the potential new target. The
369
# attribute changes haven't been made yet, so we need to calculate the
371
self.updateTargetNameCache(bug_target_from_key(**target_params))
376
333
class PassthroughValue:
377
334
"""A wrapper to allow setting values on conjoined bug tasks."""
400
357
# people try to update the conjoined slave via the API.
401
358
conjoined_master = self.conjoined_master
402
359
if conjoined_master is not None:
403
setattr(self.conjoined_master, attr, value)
360
setattr(conjoined_master, attr, value)
406
# The conjoined slave is updated before the master one because,
407
# for distro tasks, conjoined_slave does a comparison on
408
# sourcepackagename, and the sourcepackagenames will not match
409
# if the conjoined master is altered before the conjoined slave!
363
# If there is a conjoined slave, update that.
410
364
conjoined_bugtask = self.conjoined_slave
411
365
if conjoined_bugtask:
412
366
setattr(conjoined_bugtask, attr, PassthroughValue(value))
427
381
return validate_person(self, attr, value)
430
@block_implicit_flushes
431
def validate_sourcepackagename(self, attr, value):
432
is_passthrough = isinstance(value, PassthroughValue)
433
value = validate_conjoined_attribute(self, attr, value)
434
if not is_passthrough:
435
self._syncSourcePackages(value)
436
return validate_target_attribute(self, attr, value)
439
384
def validate_target(bug, target):
440
385
"""Validate a bugtask target against a bug's existing tasks.
446
391
"A fix for this bug has already been requested for %s"
447
392
% target.displayname)
449
if IDistributionSourcePackage.providedBy(target):
394
if (IDistributionSourcePackage.providedBy(target) or
395
ISourcePackage.providedBy(target)):
450
396
# If the distribution has at least one series, check that the
451
397
# source package has been published in the distribution.
452
398
if (target.sourcepackagename is not None and
514
460
bug = ForeignKey(dbName='bug', foreignKey='Bug', notNull=True)
515
461
product = ForeignKey(
516
462
dbName='product', foreignKey='Product',
517
notNull=False, default=None,
518
storm_validator=validate_target_attribute)
463
notNull=False, default=None)
519
464
productseries = ForeignKey(
520
465
dbName='productseries', foreignKey='ProductSeries',
521
notNull=False, default=None,
522
storm_validator=validate_target_attribute)
466
notNull=False, default=None)
523
467
sourcepackagename = ForeignKey(
524
468
dbName='sourcepackagename', foreignKey='SourcePackageName',
525
notNull=False, default=None,
526
storm_validator=validate_sourcepackagename)
469
notNull=False, default=None)
527
470
distribution = ForeignKey(
528
471
dbName='distribution', foreignKey='Distribution',
529
notNull=False, default=None,
530
storm_validator=validate_target_attribute)
472
notNull=False, default=None)
531
473
distroseries = ForeignKey(
532
474
dbName='distroseries', foreignKey='DistroSeries',
533
notNull=False, default=None,
534
storm_validator=validate_target_attribute)
475
notNull=False, default=None)
535
476
milestone = ForeignKey(
536
477
dbName='milestone', foreignKey='Milestone',
537
478
notNull=False, default=None,
712
653
"""See `IBugTask`."""
713
654
return self.bug.isSubscribed(person)
715
def _syncSourcePackages(self, new_spnid):
656
def _syncSourcePackages(self, new_spn):
716
657
"""Synchronize changes to source packages with other distrotasks.
718
659
If one distroseriestask's source package is changed, all the
720
661
package has to be changed, as well as the corresponding
724
# The validator is being called on an incomplete bug task.
664
if self.bug is None or not (self.distribution or self.distroseries):
665
# The validator is being called on a new or non-distro task.
726
if self.distroseries is not None:
727
distribution = self.distroseries.distribution
729
distribution = self.distribution
730
if distribution is not None:
731
for bugtask in self.related_tasks:
732
if bugtask.distroseries:
733
related_distribution = bugtask.distroseries.distribution
735
related_distribution = bugtask.distribution
736
if (related_distribution == distribution and
737
bugtask.sourcepackagenameID == self.sourcepackagenameID):
738
bugtask.sourcepackagenameID = PassthroughValue(new_spnid)
667
distribution = self.distribution or self.distroseries.distribution
668
for bugtask in self.related_tasks:
670
bugtask.sourcepackagename == self.sourcepackagename and
672
bugtask.distribution,
673
getattr(bugtask.distroseries, 'distribution', None)))
675
key = bug_target_to_key(bugtask.target)
676
key['sourcepackagename'] = new_spn
677
bugtask.transitionToTarget(
678
bug_target_from_key(**key),
679
_sync_sourcepackages=False)
740
681
def getContributorInfo(self, user, person):
741
682
"""See `IBugTask`."""
1201
1142
validate_target(self.bug, target)
1203
def transitionToTarget(self, target):
1144
def transitionToTarget(self, target, _sync_sourcepackages=True):
1204
1145
"""See `IBugTask`.
1206
This method allows changing the target of some bug
1207
tasks. The rules it follows are similar to the ones
1208
enforced implicitly by the code in
1209
lib/canonical/launchpad/browser/bugtask.py#BugTaskEditView.
1147
If _sync_sourcepackages is True (the default) and the
1148
sourcepackagename is being changed, any other tasks for the same
1149
name in this distribution will have their names updated to
1150
match. This should only be used by _syncSourcePackages.
1212
1153
if self.target == target:
1223
1164
# current target, or reset it to None
1224
1165
self.milestone = None
1226
# Inhibit validate_target_attribute, as we can't set them all
1227
# atomically, but we know the final result is correct.
1228
self._inhibit_target_check = True
1229
for name, value in bug_target_to_key(target).iteritems():
1167
new_key = bug_target_to_key(target)
1169
# As a special case, if the sourcepackagename has changed then
1170
# we update any other tasks for the same distribution and
1171
# sourcepackagename. This keeps series tasks consistent.
1172
if (_sync_sourcepackages and
1173
new_key['sourcepackagename'] != self.sourcepackagename):
1174
self._syncSourcePackages(new_key['sourcepackagename'])
1176
for name, value in new_key.iteritems():
1230
1177
setattr(self, name, value)
1231
self._inhibit_target_check = False
1232
1178
self.updateTargetNameCache()
1234
1180
# After the target has changed, we need to recalculate the maximum bug