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())
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
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
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): """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)
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))
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)
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()
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))
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
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)
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)
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)
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
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)
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) )
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)
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)
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)
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)
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)
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)
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)
def getAllBatched(self): return BatchNavigator(self.context.getAll(), self.request)
def initialize(self): self.batchnav = BatchNavigator(self.context.watches, self.request)
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)
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)
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.")
def deliveries(self): return BatchNavigator( self.context.deliveries, self.request, hide_counts=True, range_factory=StormRangeFactory(self.context.deliveries))
def batchnav(self): return BatchNavigator( getUtility(IWebhookSet).findByTarget(self.context), self.request)
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
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 announcement_nav(self): return BatchNavigator(self.announcements, self.request, size=self.batch_size)
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
def milestone_batch_navigator(self): return BatchNavigator(self.context.all_milestones, self.request)
def changes(self): navigator = BatchNavigator(ChangeLog.get(), self.request, size=10) navigator.setHeadings('change', 'changes') return navigator