예제 #1
0
    def setupBuildList(self):
        """Setup a batched build records list.

        Return None, so use tal:condition="not: view/setupBuildList" to
        invoke it in template.
        """
        # recover selected build state
        state_tag = self.request.get('build_state', '')
        self.text = self.request.get('build_text', None)

        if self.text == '':
            self.text = None

        # build self.state & self.available_states structures
        self._setupMappedStates(state_tag)

        # By default, we use the binary_only class attribute, but we
        # ensure it is true if we are passed an arch tag or a name.
        binary_only = self.binary_only
        if self.text is not None or self.arch_tag is not None:
            binary_only = True

        # request context build records according to the selected state
        builds = self.context.getBuildRecords(
            build_state=self.state, name=self.text, arch_tag=self.arch_tag,
            user=self.user, binary_only=binary_only)
        self.batchnav = BatchNavigator(
            builds, self.request, range_factory=self.range_factory(builds))
        # We perform this extra step because we don't what to issue one
        # extra query to retrieve the BuildQueue for each Build (batch item)
        # A more elegant approach should be extending Batching class and
        # integrating the fix into it. However the current solution is
        # simpler and shorter, producing the same result. cprov 20060810
        self.complete_builds = setupCompleteBuilds(
            self.batchnav.currentBatch())
예제 #2
0
    def batchnav(self):
        """Iterate over person's translation_history."""
        translations_person = ITranslationsPerson(self.context)
        batchnav = BatchNavigator(
            translations_person.translation_history, self.request)

        pofiletranslatorset = getUtility(IPOFileTranslatorSet)
        batch = batchnav.currentBatch()
        self._pofiletranslator_cache = (
            pofiletranslatorset.prefetchPOFileTranslatorRelations(batch))

        return batchnav
예제 #3
0
    def batchnav(self):
        """Iterate over person's translation_history."""
        translations_person = ITranslationsPerson(self.context)
        batchnav = BatchNavigator(translations_person.translation_history,
                                  self.request)

        pofiletranslatorset = getUtility(IPOFileTranslatorSet)
        batch = batchnav.currentBatch()
        self._pofiletranslator_cache = (
            pofiletranslatorset.prefetchPOFileTranslatorRelations(batch))

        return batchnav
예제 #4
0
    def setupBuildList(self):
        """Setup a batched build records list.

        Return None, so use tal:condition="not: view/setupBuildList" to
        invoke it in template.
        """
        # recover selected build state
        state_tag = self.request.get('build_state', '')
        self.text = self.request.get('build_text', None)

        if self.text == '':
            self.text = None

        # build self.state & self.available_states structures
        self._setupMappedStates(state_tag)

        # By default, we use the binary_only class attribute, but we
        # ensure it is true if we are passed an arch tag or a name.
        binary_only = self.binary_only
        if self.text is not None or self.arch_tag is not None:
            binary_only = True

        # request context build records according to the selected state
        builds = self.context.getBuildRecords(
            build_state=self.state, name=self.text, arch_tag=self.arch_tag,
            user=self.user, binary_only=binary_only)
        self.batchnav = BatchNavigator(
            builds, self.request, range_factory=self.range_factory(builds))
        # We perform this extra step because we don't what to issue one
        # extra query to retrieve the BuildQueue for each Build (batch item)
        # A more elegant approach should be extending Batching class and
        # integrating the fix into it. However the current solution is
        # simpler and shorter, producing the same result. cprov 20060810
        self.complete_builds = setupCompleteBuilds(
            self.batchnav.currentBatch())
예제 #5
0
    def search_results(self):
        """Process search form request."""
        if self.name_filter is None:
            return None

        # Preserve self.show_inactive state because it's used in the
        # template and build a boolean field to be passed for
        # searchPPAs.
        show_inactive = (self.show_inactive == 'on')

        ppas = self.context.searchPPAs(text=self.name_filter,
                                       show_inactive=show_inactive,
                                       user=self.user)

        self.batchnav = BatchNavigator(ppas, self.request)
        return self.batchnav.currentBatch()
예제 #6
0
    def initialize(self):
        """See `LaunchpadView.initialize`."""
        review_status_field = copy_field(
            ICodeImport['review_status'], required=False, default=None)
        self.review_status_widget = CustomWidgetFactory(DropdownWidgetWithAny)
        setUpWidget(self, 'review_status',  review_status_field, IInputWidget)

        rcs_type_field = copy_field(
            ICodeImport['rcs_type'], required=False, default=None)
        self.rcs_type_widget = CustomWidgetFactory(DropdownWidgetWithAny)
        setUpWidget(self, 'rcs_type',  rcs_type_field, IInputWidget)

        # status should be None if either (a) there were no query arguments
        # supplied, i.e. the user browsed directly to this page (this is when
        # hasValidInput returns False) or (b) the user chose 'Any' in the
        # status widget (this is when hasValidInput returns True but
        # getInputValue returns None).
        review_status = None
        if self.review_status_widget.hasValidInput():
            review_status = self.review_status_widget.getInputValue()
        # Similar for 'type'
        rcs_type = None
        if self.rcs_type_widget.hasValidInput():
            rcs_type = self.rcs_type_widget.getInputValue()

        imports = self.context.search(
            review_status=review_status, rcs_type=rcs_type)

        self.batchnav = BatchNavigator(imports, self.request)
 def batchnav(self):
     # No point using StormRangeFactory right now, as the sorted
     # lookup can't be fully indexed (it spans multiple archives).
     return BatchNavigator(
         DecoratedResultSet(self.context.publishing_history,
                            pre_iter_hook=self._preload_people),
         self.request)
예제 #8
0
 def _getBatchNavigator(self, grantees):
     """Return the batch navigator to be used to batch the grantees."""
     return BatchNavigator(grantees,
                           self.request,
                           hide_counts=True,
                           size=config.launchpad.default_batch_size,
                           range_factory=StormRangeFactory(grantees))
예제 #9
0
    def cached_differences(self):
        """Return a batch navigator of filtered results."""
        package_type_dsd_status = {
            NON_IGNORED:
            DistroSeriesDifferenceStatus.NEEDS_ATTENTION,
            HIGHER_VERSION_THAN_PARENT:
            (DistroSeriesDifferenceStatus.BLACKLISTED_CURRENT),
            RESOLVED:
            DistroSeriesDifferenceStatus.RESOLVED,
            ALL:
            None,
        }

        # If the package_type option is not supported, add an error to
        # the field and return an empty list.
        if self.specified_package_type not in package_type_dsd_status:
            self.setFieldError('package_type', 'Invalid option')
            differences = []
        else:
            status = package_type_dsd_status[self.specified_package_type]
            child_version_higher = (
                self.specified_package_type == HIGHER_VERSION_THAN_PARENT)
            differences = get_dsd_source().getForDistroSeries(
                self.context,
                difference_type=self.differences_type,
                name_filter=self.specified_name_filter,
                status=status,
                child_version_higher=child_version_higher,
                packagesets=self.specified_packagesets_filter,
                changed_by=self.specified_changed_by_filter)

        return BatchNavigator(differences, self.request)
예제 #10
0
    def initialize(self, series, translationgroup):
        self.series = series
        self.translationgroup = translationgroup
        self.form = self.request.form

        if IDistroSeriesLanguage.providedBy(self.context):
            self.batchnav = BatchNavigator(
                self.series.getCurrentTranslationTemplates(),
                self.request)
            self.pofiles = self.context.getPOFilesFor(
                self.batchnav.currentBatch())
            load_related(
                SourcePackageName, self.batchnav.currentBatch(),
                ['sourcepackagenameID'])
        else:
            self.batchnav = BatchNavigator(self.context.pofiles, self.request)
            self.pofiles = self.batchnav.currentBatch()
예제 #11
0
    def initialize(self):
        """Ensure that we are dealing with a private archive."""
        # If this archive is not private, then we should not be
        # managing the subscribers.
        if not self.context.private:
            self.request.response.addNotification(
                "Only private archives can have subscribers.")
            self.request.response.redirect(canonical_url(self.context))
            return

        super(ArchiveSubscribersView, self).initialize()
        subscription_set = getUtility(IArchiveSubscriberSet)
        self.subscriptions = subscription_set.getByArchive(self.context)
        self.batchnav = BatchNavigator(self.subscriptions,
                                       self.request,
                                       range_factory=StormRangeFactory(
                                           self.subscriptions))
예제 #12
0
    def members(self):
        """Return a batch navigator for all members.

        This batch does not test for whether the person has specifications or
        not.
        """
        members = self.context.allmembers
        batch_nav = BatchNavigator(members, self.request, size=20)
        return batch_nav
예제 #13
0
 def initialize(self):
     """See `LaunchpadView`."""
     self.person = None
     person = self.request.form.get('person')
     if person is None:
         self.request.response.addErrorNotification(
             "No person to filter by specified.")
         translations = None
     else:
         self.person = getUtility(IPersonSet).getByName(person)
         if self.person is None:
             self.request.response.addErrorNotification(
                 "Requested person not found.")
             translations = None
         else:
             translations = self.context.getTranslationsFilteredBy(
                 person=self.person)
     self.batchnav = BatchNavigator(translations, self.request,
                                    size=self.DEFAULT_BATCH_SIZE)
예제 #14
0
    def setupQueueList(self):
        """Setup a batched queue list.

        Returns None, so use tal:condition="not: view/setupQueueList" to
        invoke it in template.
        """

        # recover selected queue state and name filter
        self.name_filter = self.request.get('queue_text', '')

        try:
            state_value = int(self.request.get('queue_state', ''))
        except ValueError:
            state_value = PackageUploadStatus.NEW.value

        try:
            self.state = PackageUploadStatus.items[state_value]
        except KeyError:
            raise UnexpectedFormData(
                'No suitable status found for value "%s"' % state_value)

        self.queue = self.context.getPackageUploadQueue(self.state)

        valid_states = [
            PackageUploadStatus.NEW,
            PackageUploadStatus.ACCEPTED,
            PackageUploadStatus.REJECTED,
            PackageUploadStatus.DONE,
            PackageUploadStatus.UNAPPROVED,
        ]

        self.filtered_options = []

        for state in valid_states:
            selected = (state == self.state)
            self.filtered_options.append(
                dict(name=state.title, value=state.value, selected=selected))

        queue_items = self.context.getPackageUploads(status=self.state,
                                                     name=self.name_filter)
        self.batchnav = BatchNavigator(queue_items,
                                       self.request,
                                       size=QUEUE_SIZE)
예제 #15
0
class DistributionPPASearchView(LaunchpadView):
    """Search PPAs belonging to the Distribution in question."""

    page_title = "Personal Package Archives"

    def initialize(self):
        self.name_filter = self.request.get('name_filter')
        if isinstance(self.name_filter, list):
            # This happens if someone hand-hacks the URL so that it has
            # more than one name_filter field.  We could do something
            # like form.getOne() so that the request would be rejected,
            # but we can acutally do better and join the terms supplied
            # instead.
            self.name_filter = " ".join(self.name_filter)
        self.show_inactive = self.request.get('show_inactive')

    @property
    def label(self):
        return 'Personal Package Archives for %s' % self.context.title

    @property
    def search_results(self):
        """Process search form request."""
        if self.name_filter is None:
            return None

        # Preserve self.show_inactive state because it's used in the
        # template and build a boolean field to be passed for
        # searchPPAs.
        show_inactive = (self.show_inactive == 'on')

        ppas = self.context.searchPPAs(text=self.name_filter,
                                       show_inactive=show_inactive,
                                       user=self.user)

        self.batchnav = BatchNavigator(ppas, self.request)
        return self.batchnav.currentBatch()

    @property
    def distribution_has_ppas(self):
        return not self.context.getAllPPAs().is_empty()

    @property
    def latest_ppa_source_publications(self):
        """Return the last 5 sources publication in the context PPAs."""
        archive_set = getUtility(IArchiveSet)
        return archive_set.getLatestPPASourcePublicationsForDistribution(
            distribution=self.context)

    @property
    def most_active_ppas(self):
        """Return the last 5 most active PPAs."""
        archive_set = getUtility(IArchiveSet)
        return archive_set.getMostActivePPAsForDistribution(
            distribution=self.context)
예제 #16
0
class POFileFilteredView(LaunchpadView):
    """A filtered view for a `POFile`."""

    DEFAULT_BATCH_SIZE = 50

    @property
    def _person_name(self):
        """Person's display name.  Graceful about unknown persons."""
        if self.person is None:
            return "unknown person"
        else:
            return self.person.displayname

    @property
    def page_title(self):
        """See `LaunchpadView`."""
        return smartquote('Translations by %s in "%s"') % (
            self._person_name, self.context.title)

    def label(self):
        """See `LaunchpadView`."""
        return "Translations by %s" % self._person_name

    def initialize(self):
        """See `LaunchpadView`."""
        self.person = None
        person = self.request.form.get('person')
        if person is None:
            self.request.response.addErrorNotification(
                "No person to filter by specified.")
            translations = None
        else:
            self.person = getUtility(IPersonSet).getByName(person)
            if self.person is None:
                self.request.response.addErrorNotification(
                    "Requested person not found.")
                translations = None
            else:
                translations = self.context.getTranslationsFilteredBy(
                    person=self.person)
        self.batchnav = BatchNavigator(translations, self.request,
                                       size=self.DEFAULT_BATCH_SIZE)

    @property
    def translations(self):
        """Group a list of `TranslationMessages` under `POTMsgSets`.

        Batching is done over TranslationMessages, and in order to
        display them grouped by English string, we transform the
        current batch.
        """
        return FilteredPOTMsgSets(self.batchnav.currentBatch(),
                                  self.context).potmsgsets
예제 #17
0
class DistributionPPASearchView(LaunchpadView):
    """Search PPAs belonging to the Distribution in question."""

    page_title = "Personal Package Archives"

    def initialize(self):
        self.name_filter = self.request.get("name_filter")
        if isinstance(self.name_filter, list):
            # This happens if someone hand-hacks the URL so that it has
            # more than one name_filter field.  We could do something
            # like form.getOne() so that the request would be rejected,
            # but we can acutally do better and join the terms supplied
            # instead.
            self.name_filter = " ".join(self.name_filter)
        self.show_inactive = self.request.get("show_inactive")

    @property
    def label(self):
        return "Personal Package Archives for %s" % self.context.title

    @property
    def search_results(self):
        """Process search form request."""
        if self.name_filter is None:
            return None

        # Preserve self.show_inactive state because it's used in the
        # template and build a boolean field to be passed for
        # searchPPAs.
        show_inactive = self.show_inactive == "on"

        ppas = self.context.searchPPAs(text=self.name_filter, show_inactive=show_inactive, user=self.user)

        self.batchnav = BatchNavigator(ppas, self.request)
        return self.batchnav.currentBatch()

    @property
    def distribution_has_ppas(self):
        return not self.context.getAllPPAs().is_empty()

    @property
    def latest_ppa_source_publications(self):
        """Return the last 5 sources publication in the context PPAs."""
        archive_set = getUtility(IArchiveSet)
        return archive_set.getLatestPPASourcePublicationsForDistribution(distribution=self.context)

    @property
    def most_active_ppas(self):
        """Return the last 5 most active PPAs."""
        archive_set = getUtility(IArchiveSet)
        return archive_set.getMostActivePPAsForDistribution(distribution=self.context)
예제 #18
0
    def search_results(self):
        """Process search form request."""
        if self.name_filter is None:
            return None

        # Preserve self.show_inactive state because it's used in the
        # template and build a boolean field to be passed for
        # searchPPAs.
        show_inactive = self.show_inactive == "on"

        ppas = self.context.searchPPAs(text=self.name_filter, show_inactive=show_inactive, user=self.user)

        self.batchnav = BatchNavigator(ppas, self.request)
        return self.batchnav.currentBatch()
    def initialize(self):
        """Ensure that we are dealing with a private archive."""
        # If this archive is not private, then we should not be
        # managing the subscribers.
        if not self.context.private:
            self.request.response.addNotification("Only private archives can have subscribers.")
            self.request.response.redirect(canonical_url(self.context))
            return

        super(ArchiveSubscribersView, self).initialize()
        subscription_set = getUtility(IArchiveSubscriberSet)
        self.subscriptions = subscription_set.getByArchive(self.context)
        self.batchnav = BatchNavigator(
            self.subscriptions, self.request, range_factory=StormRangeFactory(self.subscriptions)
        )
예제 #20
0
    def searchResults(self):
        """Return the questions corresponding to the search."""
        if self.search_params is None:
            # Search button wasn't clicked, use the default filter.
            # Copy it so that it doesn't get mutated accidently.
            self.search_params = dict(self.getDefaultFilter())

        # The search parameters used is defined by the union of the fields
        # present in ISearchQuestionsForm (search_text, status, sort) and the
        # ones defined in getDefaultFilter() which varies based on the
        # concrete view class.
        question_collection = IQuestionCollection(self.context)
        return BatchNavigator(
            question_collection.searchQuestions(**self.search_params),
            self.request)
예제 #21
0
 def assertEmptyResultSetsWorking(self, range_factory):
     # getSlice() and getSliceByIndex() return empty ShadowedLists.
     sliced = range_factory.getSlice(1)
     self.assertEqual([], sliced.values)
     self.assertEqual([], sliced.shadow_values)
     sliced = range_factory.getSliceByIndex(0, 1)
     self.assertEqual([], sliced.values)
     self.assertEqual([], sliced.shadow_values)
     # The endpoint memos are empty strings.
     request = LaunchpadTestRequest()
     batchnav = BatchNavigator(
         range_factory.resultset, request, size=3,
         range_factory=range_factory)
     first, last = range_factory.getEndpointMemos(batchnav.batch)
     self.assertEqual('', first)
     self.assertEqual('', last)
예제 #22
0
 def test_getEndpointMemos__decorated_result_set(self):
     # getEndpointMemos() works for DecoratedResultSet
     # instances too.
     resultset = self.makeDecoratedStormResultSet()
     resultset.order_by(LibraryFileAlias.id)
     range_factory = StormRangeFactory(resultset)
     request = LaunchpadTestRequest()
     batchnav = BatchNavigator(
         resultset, request, size=3, range_factory=range_factory)
     first, last = range_factory.getEndpointMemos(batchnav.batch)
     expected_first = simplejson.dumps(
         [resultset.get_plain_result_set()[0][1].id],
         cls=DateTimeJSONEncoder)
     expected_last = simplejson.dumps(
         [resultset.get_plain_result_set()[2][1].id],
         cls=DateTimeJSONEncoder)
     self.assertEqual(expected_first, first)
     self.assertEqual(expected_last, last)
예제 #23
0
 def test_getEndpointMemos(self):
     # getEndpointMemos() returns JSON representations of the
     # sort fields of the first and last element of a batch.
     resultset = self.makeStormResultSet()
     resultset.order_by(Person.name)
     range_factory = StormRangeFactory(resultset)
     memo_value = range_factory.getOrderValuesFor(resultset[0])
     request = LaunchpadTestRequest(
         QUERY_STRING='memo=%s' % simplejson.dumps(memo_value))
     batchnav = BatchNavigator(
         resultset, request, size=3, range_factory=range_factory)
     first, last = range_factory.getEndpointMemos(batchnav.batch)
     expected_first = simplejson.dumps(
         [resultset[1].name], cls=DateTimeJSONEncoder)
     expected_last = simplejson.dumps(
         [resultset[3].name], cls=DateTimeJSONEncoder)
     self.assertEqual(expected_first, first)
     self.assertEqual(expected_last, last)
예제 #24
0
    def setupQueueList(self):
        """Setup a batched queue list.

        Returns None, so use tal:condition="not: view/setupQueueList" to
        invoke it in template.
        """

        # recover selected queue state and name filter
        self.name_filter = self.request.get('queue_text', '')

        try:
            state_value = int(self.request.get('queue_state', ''))
        except ValueError:
            state_value = PackageUploadStatus.NEW.value

        try:
            self.state = PackageUploadStatus.items[state_value]
        except KeyError:
            raise UnexpectedFormData(
                'No suitable status found for value "%s"' % state_value)

        self.queue = self.context.getPackageUploadQueue(self.state)

        valid_states = [
            PackageUploadStatus.NEW,
            PackageUploadStatus.ACCEPTED,
            PackageUploadStatus.REJECTED,
            PackageUploadStatus.DONE,
            PackageUploadStatus.UNAPPROVED,
            ]

        self.filtered_options = []

        for state in valid_states:
            selected = (state == self.state)
            self.filtered_options.append(
                dict(name=state.title, value=state.value, selected=selected))

        queue_items = self.context.getPackageUploads(
            status=self.state, name=self.name_filter)
        self.batchnav = BatchNavigator(
            queue_items, self.request, size=QUEUE_SIZE)
    def initialize(self):
        if IPerson.providedBy(self.context):
            self.is_person = True
        elif IDistribution.providedBy(self.context):
            self.is_target = True
            self.is_pillar = True
            self.show_series = True
        elif IProduct.providedBy(self.context):
            self.is_target = True
            self.is_pillar = True
            self.has_wiki = True
            self.show_series = True
        elif IProjectGroup.providedBy(self.context):
            self.is_project = True
            self.is_pillar = True
            self.has_wiki = True
            self.show_target = True
            self.show_series = True
        elif IProjectGroupSeries.providedBy(self.context):
            self.show_milestone = True
            self.show_target = True
            self.show_series = True
        elif (IProductSeries.providedBy(self.context)
              or IDistroSeries.providedBy(self.context)):
            self.is_series = True
            self.show_milestone = True
        elif ISprint.providedBy(self.context):
            self.is_sprint = True
            self.show_target = True
        else:
            raise AssertionError('Unknown blueprint listing site.')

        if IHasDrivers.providedBy(self.context):
            self.has_drivers = True

        self.batchnav = BatchNavigator(
            self.specs, self.request, size=config.launchpad.default_batch_size)
예제 #26
0
    def subscribedBugTasks(self):
        """
        Return a BatchNavigator for distinct bug tasks to which the person is
        subscribed.
        """
        bug_tasks = self.context.searchTasks(
            None,
            user=self.user,
            order_by='-date_last_updated',
            status=(BugTaskStatus.NEW, BugTaskStatus.INCOMPLETE,
                    BugTaskStatus.CONFIRMED, BugTaskStatus.TRIAGED,
                    BugTaskStatus.INPROGRESS, BugTaskStatus.FIXCOMMITTED,
                    BugTaskStatus.INVALID),
            bug_subscriber=self.context)

        sub_bug_tasks = []
        sub_bugs = set()

        # XXX: GavinPanella 2010-10-08 bug=656904: This materializes the
        # entire result set. It would probably be more efficient implemented
        # with a pre_iter_hook on a DecoratedResultSet.
        for task in bug_tasks:
            # We order the bugtasks by date_last_updated but we always display
            # the default task for the bug. This is to avoid ordering issues
            # in tests and also prevents user confusion (because nothing is
            # more confusing than your subscription targets changing seemingly
            # at random).
            if task.bug not in sub_bugs:
                # XXX: GavinPanella 2010-10-08 bug=656904: default_bugtask
                # causes a query to be executed. It would be more efficient to
                # get the default bugtask in bulk, in a pre_iter_hook on a
                # DecoratedResultSet perhaps.
                sub_bug_tasks.append(task.bug.default_bugtask)
                sub_bugs.add(task.bug)

        return BatchNavigator(sub_bug_tasks, self.request)
예제 #27
0
 def cached_packagings(self):
     """The batched upstream packaging links."""
     packagings = self.context.getPrioritizedPackagings()
     navigator = BatchNavigator(packagings, self.request, size=20)
     navigator.setHeadings('packaging', 'packagings')
     return navigator
class ArchiveSubscribersView(LaunchpadFormView):
    """A view for listing and creating archive subscribers."""

    schema = IArchiveSubscriberUI
    field_names = ["subscriber", "date_expires", "description"]
    custom_widget("description", TextWidget, displayWidth=40)
    custom_widget("date_expires", CustomWidgetFactory(DateWidget))
    custom_widget("subscriber", PersonPickerWidget, header="Select the subscriber")

    @property
    def label(self):
        """Return a label for the view's main heading."""
        return "Manage access to " + self.context.title

    def initialize(self):
        """Ensure that we are dealing with a private archive."""
        # If this archive is not private, then we should not be
        # managing the subscribers.
        if not self.context.private:
            self.request.response.addNotification("Only private archives can have subscribers.")
            self.request.response.redirect(canonical_url(self.context))
            return

        super(ArchiveSubscribersView, self).initialize()
        subscription_set = getUtility(IArchiveSubscriberSet)
        self.subscriptions = subscription_set.getByArchive(self.context)
        self.batchnav = BatchNavigator(
            self.subscriptions, self.request, range_factory=StormRangeFactory(self.subscriptions)
        )

    @cachedproperty
    def current_subscriptions_batch(self):
        """Return the subscriptions of the current batch.

        Bulk loads the related Person records.
        """
        batch = list(self.batchnav.currentBatch())
        ids = map(attrgetter("subscriber_id"), batch)
        list(getUtility(IPersonSet).getPrecachedPersonsFromIDs(ids, need_validity=True))
        return batch

    @cachedproperty
    def has_subscriptions(self):
        """Return whether this archive has any subscribers."""
        return self.subscriptions.any() is not None

    def validate_new_subscription(self, action, data):
        """Ensure the subscriber isn't already subscribed.

        Also ensures that the expiry date is in the future.
        """
        form.getWidgetsData(self.widgets, "field", data)
        subscriber = data.get("subscriber")
        date_expires = data.get("date_expires")

        if subscriber is not None:
            subscriber_set = getUtility(IArchiveSubscriberSet)
            current_subscription = subscriber_set.getBySubscriber(subscriber, archive=self.context)

            # XXX noodles 20090212 bug=246200: use bool() when it gets fixed
            # in storm.
            if current_subscription.any() is not None:
                self.setFieldError("subscriber", "%s is already subscribed." % subscriber.displayname)

        if date_expires:
            if date_expires < datetime.date.today():
                self.setFieldError("date_expires", "The expiry date must be in the future.")

    @action(u"Add", name="add", validator="validate_new_subscription")
    def create_subscription(self, action, data):
        """Create a subscription for the supplied user."""
        # As we present a date selection to the user for expiry, we
        # need to convert the value into a datetime with UTC:
        date_expires = data["date_expires"]
        if date_expires:
            date_expires = datetime.datetime(
                date_expires.year, date_expires.month, date_expires.day, tzinfo=pytz.timezone("UTC")
            )
        self.context.newSubscription(
            data["subscriber"], self.user, description=data["description"], date_expires=date_expires
        )

        subscriber_individuals = data["subscriber"].displayname
        if data["subscriber"].is_team:
            subscriber_individuals = "Members of " + subscriber_individuals

        notification = (
            "You have granted access for %(subscriber)s to install "
            "software from %(archive)s. "
            "%(subscriber_individuals)s will be notified of the access "
            " via email."
        ) % {
            "subscriber": data["subscriber"].displayname,
            "archive": self.context.displayname,
            "subscriber_individuals": subscriber_individuals,
        }

        self.request.response.addNotification(notification)

        # Just ensure a redirect happens (back to ourselves).
        self.next_url = str(self.request.URL)
예제 #29
0
 def getAllBatched(self):
     return BatchNavigator(self.context.getAll(), self.request)
예제 #30
0
 def initialize(self):
     self.batchnav = BatchNavigator(self.context.watches, self.request)
예제 #31
0
class ArchiveSubscribersView(LaunchpadFormView):
    """A view for listing and creating archive subscribers."""

    schema = IArchiveSubscriberUI
    field_names = ['subscriber', 'date_expires', 'description']
    custom_widget_description = CustomWidgetFactory(TextWidget,
                                                    displayWidth=40)
    custom_widget_date_expires = DateWidget
    custom_widget_subscriber = CustomWidgetFactory(
        PersonPickerWidget, header="Select the subscriber")

    @property
    def label(self):
        """Return a label for the view's main heading."""
        return "Manage access to " + self.context.title

    def initialize(self):
        """Ensure that we are dealing with a private archive."""
        # If this archive is not private, then we should not be
        # managing the subscribers.
        if not self.context.private:
            self.request.response.addNotification(
                "Only private archives can have subscribers.")
            self.request.response.redirect(canonical_url(self.context))
            return

        super(ArchiveSubscribersView, self).initialize()
        subscription_set = getUtility(IArchiveSubscriberSet)
        self.subscriptions = subscription_set.getByArchive(self.context)
        self.batchnav = BatchNavigator(self.subscriptions,
                                       self.request,
                                       range_factory=StormRangeFactory(
                                           self.subscriptions))

    @cachedproperty
    def current_subscriptions_batch(self):
        """Return the subscriptions of the current batch.

        Bulk loads the related Person records.
        """
        batch = list(self.batchnav.currentBatch())
        ids = map(attrgetter('subscriber_id'), batch)
        list(
            getUtility(IPersonSet).getPrecachedPersonsFromIDs(
                ids, need_validity=True))
        return batch

    @cachedproperty
    def has_subscriptions(self):
        """Return whether this archive has any subscribers."""
        return self.subscriptions.any() is not None

    def validate_new_subscription(self, action, data):
        """Ensure the subscriber isn't already subscribed.

        Also ensures that the expiry date is in the future.
        """
        form.getWidgetsData(self.widgets, 'field', data)
        subscriber = data.get('subscriber')
        date_expires = data.get('date_expires')

        if subscriber is not None:
            subscriber_set = getUtility(IArchiveSubscriberSet)
            current_subscription = subscriber_set.getBySubscriber(
                subscriber, archive=self.context)

            # XXX noodles 20090212 bug=246200: use bool() when it gets fixed
            # in storm.
            if current_subscription.any() is not None:
                self.setFieldError(
                    'subscriber',
                    "%s is already subscribed." % subscriber.displayname)

        if date_expires:
            if date_expires < datetime.date.today():
                self.setFieldError('date_expires',
                                   "The expiry date must be in the future.")

    @action(u"Add", name="add", validator="validate_new_subscription")
    def create_subscription(self, action, data):
        """Create a subscription for the supplied user."""
        # As we present a date selection to the user for expiry, we
        # need to convert the value into a datetime with UTC:
        date_expires = data['date_expires']
        if date_expires:
            date_expires = datetime.datetime(date_expires.year,
                                             date_expires.month,
                                             date_expires.day,
                                             tzinfo=pytz.timezone('UTC'))
        self.context.newSubscription(data['subscriber'],
                                     self.user,
                                     description=data['description'],
                                     date_expires=date_expires)

        subscriber_individuals = data['subscriber'].displayname
        if data['subscriber'].is_team:
            subscriber_individuals = "Members of " + subscriber_individuals

        notification = (
            "You have granted access for %(subscriber)s to install "
            "software from %(archive)s. "
            "%(subscriber_individuals)s will be notified of the access "
            " via email.") % {
                'subscriber': data['subscriber'].displayname,
                'archive': self.context.displayname,
                'subscriber_individuals': subscriber_individuals,
            }

        self.request.response.addNotification(notification)

        # Just ensure a redirect happens (back to ourselves).
        self.next_url = str(self.request.URL)
예제 #32
0
class QueueItemsView(LaunchpadView):
    """Base class used to present objects that contain queue items.

    It retrieves the UI queue_state selector action and sets up a proper
    batched list with the requested results. See further UI details in
    template/distroseries-queue.pt and callsite details in DistroSeries
    view classes.
    """

    def setupQueueList(self):
        """Setup a batched queue list.

        Returns None, so use tal:condition="not: view/setupQueueList" to
        invoke it in template.
        """

        # recover selected queue state and name filter
        self.name_filter = self.request.get('queue_text', '')

        try:
            state_value = int(self.request.get('queue_state', ''))
        except ValueError:
            state_value = PackageUploadStatus.NEW.value

        try:
            self.state = PackageUploadStatus.items[state_value]
        except KeyError:
            raise UnexpectedFormData(
                'No suitable status found for value "%s"' % state_value)

        self.queue = self.context.getPackageUploadQueue(self.state)

        valid_states = [
            PackageUploadStatus.NEW,
            PackageUploadStatus.ACCEPTED,
            PackageUploadStatus.REJECTED,
            PackageUploadStatus.DONE,
            PackageUploadStatus.UNAPPROVED,
            ]

        self.filtered_options = []

        for state in valid_states:
            selected = (state == self.state)
            self.filtered_options.append(
                dict(name=state.title, value=state.value, selected=selected))

        queue_items = self.context.getPackageUploads(
            status=self.state, name=self.name_filter)
        self.batchnav = BatchNavigator(
            queue_items, self.request, size=QUEUE_SIZE)

    def builds_dict(self, upload_ids, binary_files):
        """Return a dictionary of PackageUploadBuild keyed on build ID.

        :param upload_ids: A list of PackageUpload IDs.
        :param binary_files: A list of BinaryPackageReleaseFiles.
        """
        build_ids = [binary_file.binarypackagerelease.build.id
                     for binary_file in binary_files]
        upload_set = getUtility(IPackageUploadSet)
        package_upload_builds = upload_set.getBuildByBuildIDs(build_ids)
        package_upload_builds_dict = {}
        for package_upload_build in package_upload_builds:
            package_upload_builds_dict[
                package_upload_build.build.id] = package_upload_build
        return package_upload_builds_dict

    def binary_files_dict(self, package_upload_builds_dict, binary_files):
        """Build a dictionary of lists of binary files keyed by upload ID.

        To do this efficiently we need to get all the PackageUploadBuild
        records at once, otherwise the IBuild.package_upload property
        causes one query per iteration of the loop.
        """
        build_upload_files = {}
        binary_package_names = set()
        for binary_file in binary_files:
            binary_package_names.add(
                binary_file.binarypackagerelease.binarypackagename.id)
            build_id = binary_file.binarypackagerelease.build.id
            upload_id = package_upload_builds_dict[build_id].packageupload.id
            if upload_id not in build_upload_files:
                build_upload_files[upload_id] = []
            build_upload_files[upload_id].append(binary_file)
        return build_upload_files, binary_package_names

    def source_files_dict(self, package_upload_source_dict, source_files):
        """Return a dictionary of source files keyed on PackageUpload ID."""
        source_upload_files = {}
        for source_file in source_files:
            upload_id = package_upload_source_dict[
                source_file.sourcepackagerelease.id].packageupload.id
            if upload_id not in source_upload_files:
                source_upload_files[upload_id] = []
            source_upload_files[upload_id].append(source_file)
        return source_upload_files

    def calculateOldBinaries(self, binary_package_names):
        """Calculate uploaded binary files in this batch that are old."""
        name_set = getUtility(IBinaryPackageNameSet)
        # removeSecurityProxy is needed because sqlvalues() inside
        # getNotNewByIDs can't handle a security-wrapped list of
        # integers.
        archive_ids = removeSecurityProxy(
            self.context.distribution.all_distro_archive_ids)
        old_binary_packages = name_set.getNotNewByNames(
            binary_package_names, self.context, archive_ids)
        # Listify to avoid repeated queries.
        return list(old_binary_packages)

    def getPackagesetsFor(self, source_package_releases):
        """Find associated `Packagesets`.

        :param source_package_releases: A sequence of `SourcePackageRelease`s.
        """
        sprs = [spr for spr in source_package_releases if spr is not None]
        return getUtility(IPackagesetSet).getForPackages(
            self.context, set(spr.sourcepackagenameID for spr in sprs))

    def loadPackageCopyJobs(self, uploads):
        """Batch-load `PackageCopyJob`s and related information."""
        package_copy_jobs = load_related(
            PackageCopyJob, uploads, ['package_copy_job_id'])
        archives = load_related(
            Archive, package_copy_jobs, ['source_archive_id'])
        load_related(Distribution, archives, ['distributionID'])
        person_ids = map(attrgetter('ownerID'), archives)
        jobs = load_related(Job, package_copy_jobs, ['job_id'])
        person_ids.extend(map(attrgetter('requester_id'), jobs))
        list(getUtility(IPersonSet).getPrecachedPersonsFromIDs(
            person_ids, need_validity=True, need_icon=True))

    def decoratedQueueBatch(self):
        """Return the current batch, converted to decorated objects.

        Each batch item, a PackageUpload, is converted to a
        CompletePackageUpload.  This avoids many additional SQL queries
        in the +queue template.
        """
        uploads = list(self.batchnav.currentBatch())

        if len(uploads) == 0:
            return None

        upload_ids = [upload.id for upload in uploads]
        puses = load_referencing(
            PackageUploadSource, uploads, ['packageuploadID'])
        pubs = load_referencing(
            PackageUploadBuild, uploads, ['packageuploadID'])

        source_sprs = load_related(
            SourcePackageRelease, puses, ['sourcepackagereleaseID'])
        bpbs = load_related(BinaryPackageBuild, pubs, ['buildID'])
        bprs = load_referencing(BinaryPackageRelease, bpbs, ['buildID'])
        source_files = load_referencing(
            SourcePackageReleaseFile, source_sprs, ['sourcepackagereleaseID'])
        binary_files = load_referencing(
            BinaryPackageFile, bprs, ['binarypackagereleaseID'])
        file_lfas = load_related(
            LibraryFileAlias, source_files + binary_files, ['libraryfileID'])
        load_related(LibraryFileContent, file_lfas, ['contentID'])

        # Get a dictionary of lists of binary files keyed by upload ID.
        package_upload_builds_dict = self.builds_dict(upload_ids, binary_files)

        build_upload_files, binary_package_names = self.binary_files_dict(
            package_upload_builds_dict, binary_files)

        # Get a dictionary of lists of source files keyed by upload ID.
        package_upload_source_dict = {}
        for pus in puses:
            package_upload_source_dict[pus.sourcepackagereleaseID] = pus
        source_upload_files = self.source_files_dict(
            package_upload_source_dict, source_files)

        # Get a list of binary package names that already exist in
        # the distribution.  The avoids multiple queries to is_new
        # on IBinaryPackageRelease.
        self.old_binary_packages = self.calculateOldBinaries(
            binary_package_names)

        package_sets = self.getPackagesetsFor(source_sprs)

        self.loadPackageCopyJobs(uploads)

        return [
            CompletePackageUpload(
                item, build_upload_files, source_upload_files, package_sets)
            for item in uploads]

    def is_new(self, binarypackagerelease):
        """Return True if the binarypackagerelease has no ancestry."""
        return (
            binarypackagerelease.binarypackagename
            not in self.old_binary_packages)

    def availableActions(self):
        """Return the available actions according to the selected queue state.

        Returns a list of labelled actions or an empty list.
        """
        # States that support actions.
        mutable_states = [
            PackageUploadStatus.NEW,
            PackageUploadStatus.REJECTED,
            PackageUploadStatus.UNAPPROVED
            ]

        # Return actions only for supported states and require
        # edit permission.
        if (self.state in mutable_states and
            check_permission('launchpad.Edit', self.queue)):
            return ['Accept', 'Reject']

        # No actions for unsupported states.
        return []

    def performQueueAction(self):
        """Execute the designed action over the selected queue items.

        Returns a message describing the action executed or None if nothing
        was done.
        """
        # Immediately bail out if the page is not the result of a submission.
        if self.request.method != "POST":
            return

        # Also bail out if an unauthorised user is faking submissions.
        if not check_permission('launchpad.Edit', self.queue):
            self.error = 'You do not have permission to act on queue items.'
            return

        # Retrieve the form data.
        accept = self.request.form.get('Accept', '')
        reject = self.request.form.get('Reject', '')
        rejection_comment = self.request.form.get('rejection_comment', '')
        component_override = self.request.form.get('component_override', '')
        section_override = self.request.form.get('section_override', '')
        priority_override = self.request.form.get('priority_override', '')
        queue_ids = self.request.form.get('QUEUE_ID', '')

        # If no boxes were checked, bail out.
        if (not accept and not reject) or not queue_ids:
            return

        # If we're asked to reject with no comment, bail.
        if reject and not rejection_comment:
            self.error = 'Rejection comment required.'
            return

        # Determine if there is a source override requested.
        new_component = None
        new_section = None
        try:
            if component_override:
                new_component = getUtility(IComponentSet)[component_override]
        except NotFoundError:
            self.error = "Invalid component: %s" % component_override
            return

        # Get a list of components for which the user has rights to
        # override to or from.
        permission_set = getUtility(IArchivePermissionSet)
        component_permissions = permission_set.componentsForQueueAdmin(
            self.context.main_archive, self.user)
        allowed_components = set(
            permission.component for permission in component_permissions)
        pocket_permissions = permission_set.pocketsForQueueAdmin(
            self.context.main_archive, self.user)

        try:
            if section_override:
                new_section = getUtility(ISectionSet)[section_override]
        except NotFoundError:
            self.error = "Invalid section: %s" % section_override
            return

        # Determine if there is a binary override requested.
        new_priority = None
        if priority_override not in name_priority_map:
            self.error = "Invalid priority: %s" % priority_override
            return

        new_priority = name_priority_map[priority_override]

        # Process the requested action.
        if not isinstance(queue_ids, list):
            queue_ids = [queue_ids]

        queue_set = getUtility(IPackageUploadSet)

        if accept:
            action = "accept"
        elif reject:
            action = "reject"

        success = []
        failure = []
        for queue_id in queue_ids:
            queue_item = queue_set.get(int(queue_id))
            # First check that the user has rights to accept/reject this
            # item by virtue of which component it has.
            if not check_permission('launchpad.Edit', queue_item):
                existing_component_names = ", ".join(
                    component.name for component in queue_item.components)
                failure.append(
                    "FAILED: %s (You have no rights to %s component(s) "
                    "'%s')" % (queue_item.displayname,
                               action,
                               existing_component_names))
                continue

            # Sources and binaries are mutually exclusive when it comes to
            # overriding, so only one of these will be set.
            try:
                for permission in pocket_permissions:
                    if (permission.pocket == queue_item.pocket and
                        permission.distroseries in (
                            None, queue_item.distroseries)):
                        item_allowed_components = (
                            queue_item.distroseries.upload_components)
                else:
                    item_allowed_components = allowed_components
                source_overridden = queue_item.overrideSource(
                    new_component, new_section, item_allowed_components)
                binary_changes = [{
                    "component": new_component,
                    "section": new_section,
                    "priority": new_priority,
                    }]
                binary_overridden = queue_item.overrideBinaries(
                    binary_changes, item_allowed_components)
            except (QueueAdminUnauthorizedError,
                    QueueInconsistentStateError) as info:
                failure.append("FAILED: %s (%s)" %
                               (queue_item.displayname, info))
                continue

            feedback_interpolations = {
                "name": queue_item.displayname,
                "component": "(unchanged)",
                "section": "(unchanged)",
                "priority": "(unchanged)",
                }
            if new_component:
                feedback_interpolations['component'] = new_component.name
            if new_section:
                feedback_interpolations['section'] = new_section.name
            if new_priority:
                feedback_interpolations[
                    'priority'] = new_priority.title.lower()

            try:
                if action == 'accept':
                    queue_item.acceptFromQueue(user=self.user)
                elif action == 'reject':
                    queue_item.rejectFromQueue(
                        user=self.user, comment=rejection_comment)
            except (QueueAdminUnauthorizedError,
                    QueueInconsistentStateError) as info:
                failure.append('FAILED: %s (%s)' %
                               (queue_item.displayname, info))
            else:
                if source_overridden:
                    desc = "%(name)s(%(component)s/%(section)s)"
                elif binary_overridden:
                    desc = "%(name)s(%(component)s/%(section)s/%(priority)s)"
                else:
                    desc = "%(name)s"
                success.append(
                    "OK: " + desc % feedback_interpolations)

        for message in success:
            self.request.response.addInfoNotification(message)
        for message in failure:
            self.request.response.addErrorNotification(message)
        # Greasy hack!  Is there a better way of setting GET data in the
        # response?
        # (This is needed to make the user see the same queue page
        # after the redirection)
        url = str(self.request.URL) + "?queue_state=%s" % self.state.value
        self.request.response.redirect(url)

    def sortedSections(self):
        """Possible sections for the context distroseries.

        Return an iterable of possible sections for the context distroseries
        sorted by their name.
        """
        return sorted(
            self.context.sections, key=attrgetter('name'))

    def priorities(self):
        """An iterable of priorities from PackagePublishingPriority."""
        return (priority for priority in PackagePublishingPriority)
예제 #33
0
class BaseSeriesLanguageView(LaunchpadView):
    """View base class to render translation status for an
    `IDistroSeries` and `IProductSeries`

    This class should not be directly instantiated.
    """

    pofiles = None
    label = "Translatable templates"
    series = None
    parent = None
    translationgroup = None

    def initialize(self, series, translationgroup):
        self.series = series
        self.translationgroup = translationgroup
        self.form = self.request.form

        if IDistroSeriesLanguage.providedBy(self.context):
            self.batchnav = BatchNavigator(
                self.series.getCurrentTranslationTemplates(),
                self.request)
            self.pofiles = self.context.getPOFilesFor(
                self.batchnav.currentBatch())
            load_related(
                SourcePackageName, self.batchnav.currentBatch(),
                ['sourcepackagenameID'])
        else:
            self.batchnav = BatchNavigator(self.context.pofiles, self.request)
            self.pofiles = self.batchnav.currentBatch()

    @property
    def translation_group(self):
        """Return the translation group for these translations.

        Return None if there's no translation group for them.
        """
        return self.translationgroup

    @cachedproperty
    def translation_team(self):
        """Return the translation team for these translations.

        Return None if there's no translation team for them.
        """
        if self.translation_group is not None:
            team = self.translation_group.query_translator(
                self.context.language)
        else:
            team = None
        return team

    @property
    def access_level_description(self):
        """Must not be called when there's no translation group."""
        if self.user is None:
            return ("You are not logged in. Please log in to work "
                    "on translations.")

        translations_person = ITranslationsPerson(self.user)
        translations_contact_link = None

        if self.translation_team:
            translations_contact_link = PersonFormatterAPI(
                self.translation_team.translator).link(None)
        elif self.translation_group:
            translations_contact_link = PersonFormatterAPI(
                self.translation_group.owner).link(None)
        else:
            assert self.translation_group is not None, (
                "Must not be called when there's no translation group.")

        if not translations_person.translations_relicensing_agreement:
            translation_license_url = PersonFormatterAPI(
                self.user).url(
                    view_name='+licensing',
                    rootsite='translations')
            return ("To make translations in Launchpad you need to "
                    "agree with the "
                    "<a href='%s'>Translations licensing</a>.") % (
                        translation_license_url)

        if len(self.pofiles) > 0:
            sample_pofile = self.pofiles[0]
            if sample_pofile.canEditTranslations(self.user):
                return "You can add and review translations."

            if sample_pofile.canAddSuggestions(self.user):
                return ("Your suggestions will be held for review by "
                        "%s. If you need help, or your translations are "
                        "not being reviewed, please get in touch with "
                        "%s.") % (
                            translations_contact_link,
                            translations_contact_link)

            permission = sample_pofile.translationpermission
            if permission == TranslationPermission.CLOSED:
                return ("These templates can be translated only by "
                        "their managers.")

        if self.translation_team is None:
            return ("Since there is nobody to manage translation "
                    "approvals into this language, you cannot add "
                    "new suggestions. If you are interested in making "
                    "translations, please contact %s.") % (
                        translations_contact_link)

        raise AssertionError(
            "BUG! Couldn't identify the user's access level for these "
            "translations.")
예제 #34
0
 def deliveries(self):
     return BatchNavigator(
         self.context.deliveries, self.request, hide_counts=True,
         range_factory=StormRangeFactory(self.context.deliveries))
예제 #35
0
 def batchnav(self):
     return BatchNavigator(
         getUtility(IWebhookSet).findByTarget(self.context),
         self.request)
예제 #36
0
class BuildRecordsView(LaunchpadView):
    """Base class used to present objects that contains build records.

    It retrieves the UI build_state selector action and setup a proper
    batched list with the requested results. See further UI details in
    template/builds-list.pt and callsite details in Builder, Distribution,
    DistroSeries, DistroArchSeries and SourcePackage view classes.
    """

    page_title = 'Builds'

    # Currenly most build records views are interested in binaries
    # only, but subclasses can set this if desired.
    binary_only = True

    range_factory = ListRangeFactory

    @property
    def label(self):
        return 'Builds for %s' % self.context.displayname

    def setupBuildList(self):
        """Setup a batched build records list.

        Return None, so use tal:condition="not: view/setupBuildList" to
        invoke it in template.
        """
        # recover selected build state
        state_tag = self.request.get('build_state', '')
        self.text = self.request.get('build_text', None)

        if self.text == '':
            self.text = None

        # build self.state & self.available_states structures
        self._setupMappedStates(state_tag)

        # By default, we use the binary_only class attribute, but we
        # ensure it is true if we are passed an arch tag or a name.
        binary_only = self.binary_only
        if self.text is not None or self.arch_tag is not None:
            binary_only = True

        # request context build records according to the selected state
        builds = self.context.getBuildRecords(
            build_state=self.state, name=self.text, arch_tag=self.arch_tag,
            user=self.user, binary_only=binary_only)
        self.batchnav = BatchNavigator(
            builds, self.request, range_factory=self.range_factory(builds))
        # We perform this extra step because we don't what to issue one
        # extra query to retrieve the BuildQueue for each Build (batch item)
        # A more elegant approach should be extending Batching class and
        # integrating the fix into it. However the current solution is
        # simpler and shorter, producing the same result. cprov 20060810
        self.complete_builds = setupCompleteBuilds(
            self.batchnav.currentBatch())

    @property
    def arch_tag(self):
        """Return the architecture tag from the request."""
        arch_tag = self.request.get('arch_tag', None)
        if arch_tag == '' or arch_tag == 'all':
            return None
        else:
            return arch_tag

    @cachedproperty
    def architecture_options(self):
        """Return the architecture options for the context."""
        # Guard against contexts that cannot tell us the available
        # distroarchseries.
        if safe_hasattr(self.context, 'architectures') is False:
            return []

        # Grab all the architecture tags for the context.
        arch_tags = [
            arch.architecturetag for arch in self.context.architectures]

        # We cannot assume that the arch_tags will be distinct, so
        # create a distinct and sorted list:
        arch_tags = sorted(set(arch_tags))

        # Create the initial 'all architectures' option.
        if self.arch_tag is None:
            selected = 'selected'
        else:
            selected = None
        options = [
            dict(name='All architectures', value='all', selected=selected)]

        # Create the options for the select box, ensuring to mark
        # the currently selected one.
        for arch_tag in arch_tags:
            if arch_tag == self.arch_tag:
                selected = 'selected'
            else:
                selected = None

            options.append(
                dict(name=arch_tag, value=arch_tag, selected=selected))

        return options

    def _setupMappedStates(self, tag):
        """Build self.state and self.availableStates structures.

        self.state is the corresponding dbschema for requested state_tag

        self.available_states is a dictionary containing the options with
        suitables attributes (name, value, selected) to easily fill an HTML
        <select> section.

        Raise UnexpectedFormData if no corresponding state for passed 'tag'
        was found.
        """
        # Default states map.
        state_map = {
            'built': BuildStatus.FULLYBUILT,
            'failed': BuildStatus.FAILEDTOBUILD,
            'depwait': BuildStatus.MANUALDEPWAIT,
            'chrootwait': BuildStatus.CHROOTWAIT,
            'superseded': BuildStatus.SUPERSEDED,
            'uploadfail': BuildStatus.FAILEDTOUPLOAD,
            'all': None,
            }
        # Include pristine (not yet assigned to a builder) builds,
        # if requested.
        if self.show_builder_info:
            extra_state_map = {
                'building': BuildStatus.BUILDING,
                'pending': BuildStatus.NEEDSBUILD,
                }
            state_map.update(**extra_state_map)

        # Lookup for the correspondent state or fallback to the default
        # one if tag is empty string.
        if tag:
            try:
                self.state = state_map[tag]
            except (KeyError, TypeError):
                raise UnexpectedFormData(
                    'No suitable state found for value "%s"' % tag)
        else:
            self.state = self.default_build_state

        # Build a dictionary with organized information for rendering
        # the HTML <select> section.
        self.available_states = []
        for tag, state in state_map.items():
            if state:
                name = state.title.strip()
            else:
                name = 'All states'

            if state == self.state:
                selected = 'selected'
            else:
                selected = None

            self.available_states.append(
                dict(name=name, value=tag, selected=selected))

    @property
    def default_build_state(self):
        """The build state to be present as default.

        It allows the callsites to control which default status they
        want to present when the page is first loaded.
        """
        return BuildStatus.BUILDING

    @property
    def show_builder_info(self):
        """Control the presentation of builder information.

        It allows the callsite to control if they want a builder column
        in its result table or not. It's only omitted in builder-index page.
        """
        return True

    @property
    def show_arch_selector(self):
        """Control whether the architecture selector is presented.

        This allows the callsite to control if they want the architecture
        selector presented in the UI.
        """
        return False

    @property
    def search_name(self):
        """Control the presentation of search box."""
        return True

    @property
    def form_submitted(self):
        return "build_state" in self.request.form

    @property
    def no_results(self):
        return self.form_submitted and not self.complete_builds
 def specs_batched(self):
     navigator = BatchNavigator(self.specs, self.request, size=500)
     navigator.setHeadings('specification', 'specifications')
     return navigator
예제 #38
0
    def __call__(self):
        name = self.request.form.get('name')
        if name is None:
            raise MissingInputError('name', '')

        search_text = self.request.form.get('search_text')
        if search_text is None:
            raise MissingInputError('search_text', '')
        search_filter = self.request.form.get('search_filter')

        try:
            factory = getUtility(IVocabularyFactory, name)
        except ComponentLookupError:
            raise UnexpectedFormData(
                'Unknown vocabulary %r' % name)

        vocabulary = factory(self.context)

        if IHugeVocabulary.providedBy(vocabulary):
            matches = vocabulary.searchForTerms(search_text, search_filter)
            total_size = matches.count()
        else:
            matches = list(vocabulary)
            total_size = len(matches)

        batch_navigator = BatchNavigator(matches, self.request)

        # We need to collate what IPickerEntrySource adapters are required for
        # the items in the current batch. We expect that the batch will be
        # homogenous and so only one adapter instance is required, but we
        # allow for the case where the batch may contain disparate entries
        # requiring different adapter implementations.

        # A mapping from adapter class name -> adapter instance
        adapter_cache = {}
        # A mapping from adapter class name -> list of vocab terms
        picker_entry_terms = {}
        for term in batch_navigator.currentBatch():
            picker_entry_source = IPickerEntrySource(term.value)
            adapter_class = picker_entry_source.__class__.__name__
            picker_terms = picker_entry_terms.get(adapter_class)
            if picker_terms is None:
                picker_terms = []
                picker_entry_terms[adapter_class] = picker_terms
                adapter_cache[adapter_class] = picker_entry_source
            picker_terms.append(term.value)

        # A mapping from vocab terms -> picker entries
        picker_term_entries = {}

        # For the list of terms associated with a picker adapter, we get the
        # corresponding picker entries by calling the adapter.
        for adapter_class, term_values in picker_entry_terms.items():
            picker_entries = adapter_cache[adapter_class].getPickerEntries(
                term_values,
                self.context)
            for term_value, picker_entry in izip(term_values, picker_entries):
                picker_term_entries[term_value] = picker_entry

        result = []
        for term in batch_navigator.currentBatch():
            entry = dict(value=term.token, title=term.title)
            # The canonical_url without just the path (no hostname) can
            # be passed directly into the REST PATCH call.
            api_request = IWebServiceClientRequest(self.request)
            try:
                entry['api_uri'] = canonical_url(
                    term.value, request=api_request,
                    path_only_if_possible=True)
            except NoCanonicalUrl:
                # The exception is caught, because the api_url is only
                # needed for inplace editing via a REST call. The
                # form picker doesn't need the api_url.
                entry['api_uri'] = 'Could not find canonical url.'
            picker_entry = picker_term_entries[term.value]
            if picker_entry.description is not None:
                if len(picker_entry.description) > MAX_DESCRIPTION_LENGTH:
                    entry['description'] = (
                        picker_entry.description[:MAX_DESCRIPTION_LENGTH - 3]
                        + '...')
                else:
                    entry['description'] = picker_entry.description
            if picker_entry.image is not None:
                entry['image'] = picker_entry.image
            if picker_entry.css is not None:
                entry['css'] = picker_entry.css
            if picker_entry.alt_title is not None:
                entry['alt_title'] = picker_entry.alt_title
            if picker_entry.title_link is not None:
                entry['title_link'] = picker_entry.title_link
            if picker_entry.details is not None:
                entry['details'] = picker_entry.details
            if picker_entry.alt_title_link is not None:
                entry['alt_title_link'] = picker_entry.alt_title_link
            if picker_entry.link_css is not None:
                entry['link_css'] = picker_entry.link_css
            if picker_entry.badges:
                entry['badges'] = picker_entry.badges
            if picker_entry.metadata is not None:
                entry['metadata'] = picker_entry.metadata
            if picker_entry.target_type is not None:
                entry['target_type'] = picker_entry.target_type
            result.append(entry)

        self.request.response.setHeader('Content-type', 'application/json')
        return simplejson.dumps(dict(total_size=total_size, entries=result))
 def specs_batched(self):
     navigator = BatchNavigator(self.specs, self.request, size=500)
     navigator.setHeadings('specification', 'specifications')
     return navigator
예제 #40
0
class BuildRecordsView(LaunchpadView):
    """Base class used to present objects that contains build records.

    It retrieves the UI build_state selector action and setup a proper
    batched list with the requested results. See further UI details in
    template/builds-list.pt and callsite details in Builder, Distribution,
    DistroSeries, DistroArchSeries and SourcePackage view classes.
    """

    page_title = 'Builds'

    # Currenly most build records views are interested in binaries
    # only, but subclasses can set this if desired.
    binary_only = True

    range_factory = ListRangeFactory

    @property
    def label(self):
        return 'Builds for %s' % self.context.displayname

    def setupBuildList(self):
        """Setup a batched build records list.

        Return None, so use tal:condition="not: view/setupBuildList" to
        invoke it in template.
        """
        # recover selected build state
        state_tag = self.request.get('build_state', '')
        self.text = self.request.get('build_text', None)

        if self.text == '':
            self.text = None

        # build self.state & self.available_states structures
        self._setupMappedStates(state_tag)

        # By default, we use the binary_only class attribute, but we
        # ensure it is true if we are passed an arch tag or a name.
        binary_only = self.binary_only
        if self.text is not None or self.arch_tag is not None:
            binary_only = True

        # request context build records according to the selected state
        builds = self.context.getBuildRecords(
            build_state=self.state, name=self.text, arch_tag=self.arch_tag,
            user=self.user, binary_only=binary_only)
        self.batchnav = BatchNavigator(
            builds, self.request, range_factory=self.range_factory(builds))
        # We perform this extra step because we don't what to issue one
        # extra query to retrieve the BuildQueue for each Build (batch item)
        # A more elegant approach should be extending Batching class and
        # integrating the fix into it. However the current solution is
        # simpler and shorter, producing the same result. cprov 20060810
        self.complete_builds = setupCompleteBuilds(
            self.batchnav.currentBatch())

    @property
    def arch_tag(self):
        """Return the architecture tag from the request."""
        arch_tag = self.request.get('arch_tag', None)
        if arch_tag == '' or arch_tag == 'all':
            return None
        else:
            return arch_tag

    @cachedproperty
    def architecture_options(self):
        """Return the architecture options for the context."""
        # Guard against contexts that cannot tell us the available
        # distroarchseries.
        if safe_hasattr(self.context, 'architectures') is False:
            return []

        # Grab all the architecture tags for the context.
        arch_tags = [
            arch.architecturetag for arch in self.context.architectures]

        # We cannot assume that the arch_tags will be distinct, so
        # create a distinct and sorted list:
        arch_tags = sorted(set(arch_tags))

        # Create the initial 'all architectures' option.
        if self.arch_tag is None:
            selected = 'selected'
        else:
            selected = None
        options = [
            dict(name='All architectures', value='all', selected=selected)]

        # Create the options for the select box, ensuring to mark
        # the currently selected one.
        for arch_tag in arch_tags:
            if arch_tag == self.arch_tag:
                selected = 'selected'
            else:
                selected = None

            options.append(
                dict(name=arch_tag, value=arch_tag, selected=selected))

        return options

    def _setupMappedStates(self, tag):
        """Build self.state and self.availableStates structures.

        self.state is the corresponding dbschema for requested state_tag

        self.available_states is a dictionary containing the options with
        suitables attributes (name, value, selected) to easily fill an HTML
        <select> section.

        Raise UnexpectedFormData if no corresponding state for passed 'tag'
        was found.
        """
        # Default states map.
        state_map = {
            'built': BuildStatus.FULLYBUILT,
            'failed': BuildStatus.FAILEDTOBUILD,
            'depwait': BuildStatus.MANUALDEPWAIT,
            'chrootwait': BuildStatus.CHROOTWAIT,
            'superseded': BuildStatus.SUPERSEDED,
            'uploadfail': BuildStatus.FAILEDTOUPLOAD,
            'all': None,
            }
        # Include pristine (not yet assigned to a builder) builds,
        # if requested.
        if self.show_builder_info:
            extra_state_map = {
                'building': BuildStatus.BUILDING,
                'pending': BuildStatus.NEEDSBUILD,
                }
            state_map.update(**extra_state_map)

        # Lookup for the correspondent state or fallback to the default
        # one if tag is empty string.
        if tag:
            try:
                self.state = state_map[tag]
            except (KeyError, TypeError):
                raise UnexpectedFormData(
                    'No suitable state found for value "%s"' % tag)
        else:
            self.state = self.default_build_state

        # Build a dictionary with organized information for rendering
        # the HTML <select> section.
        self.available_states = []
        for tag, state in state_map.items():
            if state:
                name = state.title.strip()
            else:
                name = 'All states'

            if state == self.state:
                selected = 'selected'
            else:
                selected = None

            self.available_states.append(
                dict(name=name, value=tag, selected=selected))

    @property
    def default_build_state(self):
        """The build state to be present as default.

        It allows the callsites to control which default status they
        want to present when the page is first loaded.
        """
        return BuildStatus.BUILDING

    @property
    def show_builder_info(self):
        """Control the presentation of builder information.

        It allows the callsite to control if they want a builder column
        in its result table or not. It's only omitted in builder-index page.
        """
        return True

    @property
    def show_arch_selector(self):
        """Control whether the architecture selector is presented.

        This allows the callsite to control if they want the architecture
        selector presented in the UI.
        """
        return False

    @property
    def search_name(self):
        """Control the presentation of search box."""
        return True

    @property
    def form_submitted(self):
        return "build_state" in self.request.form

    @property
    def no_results(self):
        return self.form_submitted and not self.complete_builds
예제 #41
0
 def announcement_nav(self):
     return BatchNavigator(self.announcements,
                           self.request,
                           size=self.batch_size)
예제 #42
0
 def cached_unlinked_packages(self):
     """The batched `ISourcePackage`s that needs packaging links."""
     packages = self.context.getPrioritizedUnlinkedSourcePackages()
     navigator = BatchNavigator(packages, self.request, size=20)
     navigator.setHeadings('package', 'packages')
     return navigator
예제 #43
0
 def milestone_batch_navigator(self):
     return BatchNavigator(self.context.all_milestones, self.request)
예제 #44
0
 def changes(self):
     navigator = BatchNavigator(ChangeLog.get(), self.request, size=10)
     navigator.setHeadings('change', 'changes')
     return navigator