~launchpad-pqm/launchpad/devel

« back to all changes in this revision

Viewing changes to lib/lp/registry/browser/distroseries.py

Merge db-devel.

Show diffs side-by-side

added added

removed removed

Lines of Context:
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,
109
111
    )
110
112
 
 
113
# DistroSeries statuses that benefit from mass package upgrade support.
 
114
UPGRADABLE_SERIES_STATUSES = [
 
115
    SeriesStatus.FUTURE,
 
116
    SeriesStatus.EXPERIMENTAL,
 
117
    SeriesStatus.DEVELOPMENT,
 
118
    ]
 
119
 
111
120
 
112
121
class DistroSeriesNavigation(GetitemNavigation, BugTargetTraversalMixin,
113
122
    StructuralSubscriptionTargetTraversalMixin):
171
180
    def traverse_queue(self, id):
172
181
        return getUtility(IPackageUploadSet).get(id)
173
182
 
174
 
    @stepthrough('+difference')
175
 
    def traverse_difference(self, name):
176
 
        dsd_source = getUtility(IDistroSeriesDifferenceSource)
177
 
        return dsd_source.getByDistroSeriesAndName(self.context, name)
178
 
 
179
183
 
180
184
class DistroSeriesBreadcrumb(Breadcrumb):
181
185
    """Builds a breadcrumb for an `IDistroSeries`."""
349
353
            self.context.datereleased = UTC_NOW
350
354
 
351
355
 
352
 
class DistroSeriesView(LaunchpadView, MilestoneOverlayMixin):
 
356
class DerivedDistroSeriesMixin:
 
357
 
 
358
    @cachedproperty
 
359
    def has_unique_parent(self):
 
360
        return len(self.context.getParentSeries()) == 1
 
361
 
 
362
    @cachedproperty
 
363
    def unique_parent(self):
 
364
        if self.has_unique_parent:
 
365
            return self.context.getParentSeries()[0]
 
366
        else:
 
367
            None
 
368
 
 
369
    @cachedproperty
 
370
    def number_of_parents(self):
 
371
        return len(self.context.getParentSeries())
 
372
 
 
373
    def getParentName(self, multiple_parent_default=None):
 
374
        if self.has_unique_parent:
 
375
            return ("parent series '%s'" %
 
376
                self.unique_parent.displayname)
 
377
        else:
 
378
            if multiple_parent_default is not None:
 
379
                return multiple_parent_default
 
380
            else:
 
381
                return 'a parent series'
 
382
 
 
383
 
 
384
class DistroSeriesView(LaunchpadView, MilestoneOverlayMixin,
 
385
                       DerivedDistroSeriesMixin):
353
386
 
354
387
    def initialize(self):
355
388
        super(DistroSeriesView, self).initialize()
645
678
 
646
679
 
647
680
# A helper to create package filtering radio button vocabulary.
648
 
NON_BLACKLISTED = 'non-blacklisted'
649
 
BLACKLISTED = 'blacklisted'
 
681
NON_IGNORED = 'non-ignored'
 
682
IGNORED = 'ignored'
650
683
HIGHER_VERSION_THAN_PARENT = 'higher-than-parent'
651
684
RESOLVED = 'resolved'
652
685
 
653
 
DEFAULT_PACKAGE_TYPE = NON_BLACKLISTED
 
686
DEFAULT_PACKAGE_TYPE = NON_IGNORED
654
687
 
655
688
 
656
689
def make_package_type_vocabulary(parent_name, higher_version_option=False):
657
690
    voc = [
658
691
        SimpleTerm(
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"
667
700
                % parent_name)
668
701
        voc.insert(2, higher_term)
669
702
    return SimpleVocabulary(tuple(voc))
696
729
 
697
730
 
698
731
class DistroSeriesDifferenceBaseView(LaunchpadFormView,
699
 
                                     PackageCopyingMixin):
 
732
                                     PackageCopyingMixin,
 
733
                                     DerivedDistroSeriesMixin):
700
734
    """Base class for all pages presenting differences between
701
735
    a derived series and its parent."""
702
736
    schema = IDifferencesFormSchema
731
765
        return NotImplementedError()
732
766
 
733
767
    def setupPackageFilterRadio(self):
 
768
        if self.has_unique_parent:
 
769
            parent_name = "'%s'" % self.unique_parent.displayname
 
770
        else:
 
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,
 
775
                parent_name,
738
776
                self.search_higher_parent_option),
739
777
            default=DEFAULT_PACKAGE_TYPE,
740
778
            required=True))
751
789
            self.form_fields)
752
790
        check_permission('launchpad.Edit', self.context)
753
791
        terms = [
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
840
877
    @cachedproperty
841
878
    def cached_differences(self):
842
879
        """Return a batch navigator of filtered results."""
843
 
        if self.specified_package_type == NON_BLACKLISTED:
844
 
            status=(
 
880
        if self.specified_package_type == NON_IGNORED:
 
881
            status = (
845
882
                DistroSeriesDifferenceStatus.NEEDS_ATTENTION,)
846
883
            child_version_higher = False
847
 
        elif self.specified_package_type == BLACKLISTED:
848
 
            status=(
 
884
        elif self.specified_package_type == IGNORED:
 
885
            status = (
849
886
                DistroSeriesDifferenceStatus.BLACKLISTED_CURRENT)
850
887
            child_version_higher = False
851
888
        elif self.specified_package_type == HIGHER_VERSION_THAN_PARENT:
852
 
            status=(
 
889
            status = (
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
858
895
        else:
859
896
            raise AssertionError('specified_package_type unknown')
860
897
 
861
898
        differences = getUtility(
862
899
            IDistroSeriesDifferenceSource).getForDistroSeries(
863
 
                self.context,
864
 
                difference_type = self.differences_type,
 
900
                self.context, difference_type=self.differences_type,
865
901
                source_package_name_filter=self.specified_name_filter,
866
 
                status=status,
867
 
                child_version_higher=child_version_higher)
 
902
                status=status, child_version_higher=child_version_higher)
868
903
        return BatchNavigator(differences, self.request)
869
904
 
870
905
    @cachedproperty
882
917
            differences = getUtility(
883
918
                IDistroSeriesDifferenceSource).getForDistroSeries(
884
919
                    self.context,
885
 
                    difference_type = self.differences_type,
 
920
                    difference_type=self.differences_type,
886
921
                    status=(
887
922
                        DistroSeriesDifferenceStatus.NEEDS_ATTENTION,
888
923
                        DistroSeriesDifferenceStatus.BLACKLISTED_CURRENT))
901
936
 
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
 
941
        else:
 
942
            parent_name = 'Parent'
904
943
        self.initialize_sync_label(
905
944
            "Sync Selected %s Versions into %s" % (
906
 
                self.context.parent_series.displayname,
 
945
                parent_name,
907
946
                self.context.displayname,
908
947
                ))
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'
920
959
            '</a>).',
921
960
            self.context.displayname,
922
 
            self.context.parent_series.fullseriesname,
923
 
            self.context.parent_series.displayname)
 
961
            self.getParentName())
924
962
 
925
963
    @property
926
964
    def label(self):
927
965
        return (
928
 
            "Source package differences between '%s' and "
929
 
            "parent series '%s'" % (
 
966
            "Source package differences between '%s' and"
 
967
            " %s" % (
930
968
                self.context.displayname,
931
 
                self.context.parent_series.displayname,
 
969
                self.getParentName(multiple_parent_default='parent series'),
932
970
                ))
933
971
 
934
972
    @action(_("Update"), name="update")
941
979
    def sync_sources(self, action, data):
942
980
        self._sync_sources(action, data)
943
981
 
 
982
    def getUpgrades(self):
 
983
        """Find straightforward package upgrades.
 
984
 
 
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.
 
989
 
 
990
        :return: A result set of `DistroSeriesDifference`s.
 
991
        """
 
992
        return getUtility(IDistroSeriesDifferenceSource).getSimpleUpgrades(
 
993
            self.context)
 
994
 
 
995
    @action(_("Upgrade Packages"), name="upgrade", condition='canUpgrade')
 
996
    def upgrade(self, action, data):
 
997
        """Request synchronization of straightforward package upgrades."""
 
998
        self.requestUpgrades()
 
999
 
 
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))
 
1019
 
 
1020
    def canUpgrade(self, action=None):
 
1021
        """Should the form offer a packages upgrade?"""
 
1022
        if getFeatureFlag("soyuz.derived-series-sync.enabled") is None:
 
1023
            return False
 
1024
        elif self.context.status not in UPGRADABLE_SERIES_STATUSES:
 
1025
            # A feature freeze precludes blanket updates.
 
1026
            return False
 
1027
        elif self.getUpgrades().is_empty():
 
1028
            # There are no simple updates to perform.
 
1029
            return False
 
1030
        else:
 
1031
            queue = PackageUploadQueue(self.context, None)
 
1032
            return check_permission("launchpad.Edit", queue)
 
1033
 
944
1034
 
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,
961
1051
                ))
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)
973
1063
 
974
1064
    @property
975
1065
    def label(self):
976
1066
        return (
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,
980
1070
                ))
981
1071
 
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())
1014
1104
 
1015
1105
    @property
1016
1106
    def label(self):
1017
1107
        return (
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(),
1021
1111
                ))
1022
1112
 
1023
1113
    @action(_("Update"), name="update")