103
103
from lp.services.worlddata.interfaces.language import ILanguageSet
104
104
from lp.soyuz.browser.archive import PackageCopyingMixin
105
105
from lp.soyuz.browser.packagesearch import PackageSearchViewBase
106
from lp.soyuz.interfaces.packagecopyjob import IPlainPackageCopyJobSource
106
107
from lp.soyuz.interfaces.queue import IPackageUploadSet
108
from lp.soyuz.model.queue import PackageUploadQueue
107
109
from lp.translations.browser.distroseries import (
108
110
check_distroseries_translations_viewable,
113
# DistroSeries statuses that benefit from mass package upgrade support.
114
UPGRADABLE_SERIES_STATUSES = [
116
SeriesStatus.EXPERIMENTAL,
117
SeriesStatus.DEVELOPMENT,
112
121
class DistroSeriesNavigation(GetitemNavigation, BugTargetTraversalMixin,
113
122
StructuralSubscriptionTargetTraversalMixin):
171
180
def traverse_queue(self, id):
172
181
return getUtility(IPackageUploadSet).get(id)
174
@stepthrough('+difference')
175
def traverse_difference(self, name):
176
dsd_source = getUtility(IDistroSeriesDifferenceSource)
177
return dsd_source.getByDistroSeriesAndName(self.context, name)
180
184
class DistroSeriesBreadcrumb(Breadcrumb):
181
185
"""Builds a breadcrumb for an `IDistroSeries`."""
349
353
self.context.datereleased = UTC_NOW
352
class DistroSeriesView(LaunchpadView, MilestoneOverlayMixin):
356
class DerivedDistroSeriesMixin:
359
def has_unique_parent(self):
360
return len(self.context.getParentSeries()) == 1
363
def unique_parent(self):
364
if self.has_unique_parent:
365
return self.context.getParentSeries()[0]
370
def number_of_parents(self):
371
return len(self.context.getParentSeries())
373
def getParentName(self, multiple_parent_default=None):
374
if self.has_unique_parent:
375
return ("parent series '%s'" %
376
self.unique_parent.displayname)
378
if multiple_parent_default is not None:
379
return multiple_parent_default
381
return 'a parent series'
384
class DistroSeriesView(LaunchpadView, MilestoneOverlayMixin,
385
DerivedDistroSeriesMixin):
354
387
def initialize(self):
355
388
super(DistroSeriesView, self).initialize()
647
680
# A helper to create package filtering radio button vocabulary.
648
NON_BLACKLISTED = 'non-blacklisted'
649
BLACKLISTED = 'blacklisted'
681
NON_IGNORED = 'non-ignored'
650
683
HIGHER_VERSION_THAN_PARENT = 'higher-than-parent'
651
684
RESOLVED = 'resolved'
653
DEFAULT_PACKAGE_TYPE = NON_BLACKLISTED
686
DEFAULT_PACKAGE_TYPE = NON_IGNORED
656
689
def make_package_type_vocabulary(parent_name, higher_version_option=False):
659
NON_BLACKLISTED, NON_BLACKLISTED, 'Non blacklisted packages'),
660
SimpleTerm(BLACKLISTED, BLACKLISTED, 'Blacklisted packages'),
661
SimpleTerm(RESOLVED, RESOLVED, "Resolved packages")]
692
NON_IGNORED, NON_IGNORED, 'Non ignored packages'),
693
SimpleTerm(IGNORED, IGNORED, 'Ignored packages'),
694
SimpleTerm(RESOLVED, RESOLVED, "Resolved package differences")]
662
695
if higher_version_option:
663
696
higher_term = SimpleTerm(
664
697
HIGHER_VERSION_THAN_PARENT,
665
698
HIGHER_VERSION_THAN_PARENT,
666
"Blacklisted packages with a higher version than in '%s'"
699
"Ignored packages with a higher version than in %s"
668
701
voc.insert(2, higher_term)
669
702
return SimpleVocabulary(tuple(voc))
731
765
return NotImplementedError()
733
767
def setupPackageFilterRadio(self):
768
if self.has_unique_parent:
769
parent_name = "'%s'" % self.unique_parent.displayname
771
parent_name = 'parent'
734
772
return form.Fields(Choice(
735
773
__name__='package_type',
736
774
vocabulary=make_package_type_vocabulary(
737
self.context.parent_series.displayname,
738
776
self.search_higher_parent_option),
739
777
default=DEFAULT_PACKAGE_TYPE,
751
789
self.form_fields)
752
790
check_permission('launchpad.Edit', self.context)
754
SimpleTerm(diff, diff.source_package_name.name,
755
diff.source_package_name.name)
756
for diff in self.cached_differences.batch]
792
SimpleTerm(diff, diff.id)
793
for diff in self.cached_differences.batch]
757
794
diffs_vocabulary = SimpleVocabulary(terms)
758
795
choice = self.form_fields['selected_differences'].field.value_type
759
796
choice.vocabulary = diffs_vocabulary
841
878
def cached_differences(self):
842
879
"""Return a batch navigator of filtered results."""
843
if self.specified_package_type == NON_BLACKLISTED:
880
if self.specified_package_type == NON_IGNORED:
845
882
DistroSeriesDifferenceStatus.NEEDS_ATTENTION,)
846
883
child_version_higher = False
847
elif self.specified_package_type == BLACKLISTED:
884
elif self.specified_package_type == IGNORED:
849
886
DistroSeriesDifferenceStatus.BLACKLISTED_CURRENT)
850
887
child_version_higher = False
851
888
elif self.specified_package_type == HIGHER_VERSION_THAN_PARENT:
853
890
DistroSeriesDifferenceStatus.BLACKLISTED_CURRENT)
854
891
child_version_higher = True
855
892
elif self.specified_package_type == RESOLVED:
856
status=DistroSeriesDifferenceStatus.RESOLVED
893
status = DistroSeriesDifferenceStatus.RESOLVED
857
894
child_version_higher = False
859
896
raise AssertionError('specified_package_type unknown')
861
898
differences = getUtility(
862
899
IDistroSeriesDifferenceSource).getForDistroSeries(
864
difference_type = self.differences_type,
900
self.context, difference_type=self.differences_type,
865
901
source_package_name_filter=self.specified_name_filter,
867
child_version_higher=child_version_higher)
902
status=status, child_version_higher=child_version_higher)
868
903
return BatchNavigator(differences, self.request)
882
917
differences = getUtility(
883
918
IDistroSeriesDifferenceSource).getForDistroSeries(
885
difference_type = self.differences_type,
920
difference_type=self.differences_type,
887
922
DistroSeriesDifferenceStatus.NEEDS_ATTENTION,
888
923
DistroSeriesDifferenceStatus.BLACKLISTED_CURRENT))
902
937
def initialize(self):
903
938
# Update the label for sync action.
939
if self.has_unique_parent:
940
parent_name = "'%s'" % self.unique_parent.displayname
942
parent_name = 'Parent'
904
943
self.initialize_sync_label(
905
944
"Sync Selected %s Versions into %s" % (
906
self.context.parent_series.displayname,
907
946
self.context.displayname,
909
948
super(DistroSeriesLocalDifferencesView, self).initialize()
912
951
def explanation(self):
913
952
return structured(
914
953
"Source packages shown here are present in both %s "
915
"and the parent series, %s, but are different somehow. "
954
"and %s, but are different somehow. "
916
955
"Changes could be in either or both series so check the "
917
"versions (and the diff if necessary) before syncing the %s "
956
"versions (and the diff if necessary) before syncing the parent "
918
957
'version (<a href="/+help/soyuz/derived-series-syncing.html" '
919
'target="help">Read more about syncing from the parent series'
958
'target="help">Read more about syncing from a parent series'
921
960
self.context.displayname,
922
self.context.parent_series.fullseriesname,
923
self.context.parent_series.displayname)
961
self.getParentName())
928
"Source package differences between '%s' and "
929
"parent series '%s'" % (
966
"Source package differences between '%s' and"
930
968
self.context.displayname,
931
self.context.parent_series.displayname,
969
self.getParentName(multiple_parent_default='parent series'),
934
972
@action(_("Update"), name="update")
941
979
def sync_sources(self, action, data):
942
980
self._sync_sources(action, data)
982
def getUpgrades(self):
983
"""Find straightforward package upgrades.
985
These are updates for packages that this distroseries shares
986
with a parent series, for which there have been updates in the
987
parent, and which do not have any changes in this series that
988
might complicate a sync.
990
:return: A result set of `DistroSeriesDifference`s.
992
return getUtility(IDistroSeriesDifferenceSource).getSimpleUpgrades(
995
@action(_("Upgrade Packages"), name="upgrade", condition='canUpgrade')
996
def upgrade(self, action, data):
997
"""Request synchronization of straightforward package upgrades."""
998
self.requestUpgrades()
1000
def requestUpgrades(self):
1001
"""Request sync of packages that can be easily upgraded."""
1002
target_distroseries = self.context
1003
target_archive = target_distroseries.main_archive
1004
differences_by_archive = (
1005
getUtility(IDistroSeriesDifferenceSource)
1006
.collateDifferencesByParentArchive(self.getUpgrades()))
1007
for source_archive, differences in differences_by_archive.iteritems():
1008
source_package_info = [
1009
(difference.source_package_name.name,
1010
difference.parent_source_version)
1011
for difference in differences]
1012
getUtility(IPlainPackageCopyJobSource).create(
1013
source_package_info, source_archive, target_archive,
1014
target_distroseries, PackagePublishingPocket.UPDATES)
1015
self.request.response.addInfoNotification(
1016
(u"Upgrades of {context.displayname} packages have been "
1017
u"requested. Please give Launchpad some time to complete "
1018
u"these.").format(context=self.context))
1020
def canUpgrade(self, action=None):
1021
"""Should the form offer a packages upgrade?"""
1022
if getFeatureFlag("soyuz.derived-series-sync.enabled") is None:
1024
elif self.context.status not in UPGRADABLE_SERIES_STATUSES:
1025
# A feature freeze precludes blanket updates.
1027
elif self.getUpgrades().is_empty():
1028
# There are no simple updates to perform.
1031
queue = PackageUploadQueue(self.context, None)
1032
return check_permission("launchpad.Edit", queue)
945
1035
class DistroSeriesMissingPackagesView(DistroSeriesDifferenceBaseView,
946
1036
LaunchpadFormView):
956
1046
def initialize(self):
957
1047
# Update the label for sync action.
958
1048
self.initialize_sync_label(
959
"Include Selected packages into into %s" % (
1049
"Include Selected packages into %s" % (
960
1050
self.context.displayname,
962
1052
super(DistroSeriesMissingPackagesView, self).initialize()
965
1055
def explanation(self):
966
1056
return structured(
967
1057
"Packages that are listed here are those that have been added to "
968
"the specific packages %s that were used to create %s. They are "
969
"listed here so you can consider including them in %s.",
970
self.context.parent_series.displayname,
1058
"the specific packages in %s that were used to create %s. "
1059
"They are listed here so you can consider including them in %s.",
1060
self.getParentName(),
971
1061
self.context.displayname,
972
1062
self.context.displayname)
975
1065
def label(self):
977
"Packages in parent series '%s' but not in '%s'" % (
978
self.context.parent_series.displayname,
1067
"Packages in %s but not in '%s'" % (
1068
self.getParentName(),
979
1069
self.context.displayname,
1008
1098
def explanation(self):
1009
1099
return structured(
1010
1100
"Packages that are listed here are those that have been added to "
1011
"%s but are not yet part of the parent series %s.",
1101
"%s but are not yet part of %s.",
1012
1102
self.context.displayname,
1013
self.context.parent_series.displayname)
1103
self.getParentName())
1016
1106
def label(self):
1018
"Packages in '%s' but not in parent series '%s'" % (
1108
"Packages in '%s' but not in %s" % (
1019
1109
self.context.displayname,
1020
self.context.parent_series.displayname,
1110
self.getParentName(),
1023
1113
@action(_("Update"), name="update")