def _valid_image(self, image):
     """Check that the given image is under the given constraints."""
     # No global import to avoid hard dependency on PIL being installed
     import PIL.Image
     if len(image) > self.max_size:
         raise LaunchpadValidationError(_(dedent("""
             This image exceeds the maximum allowed size in bytes.""")))
     try:
         pil_image = PIL.Image.open(StringIO(image))
     except (IOError, ValueError):
         raise LaunchpadValidationError(_(dedent("""
             The file uploaded was not recognized as an image; please
             check it and retry.""")))
     width, height = pil_image.size
     required_width, required_height = self.dimensions
     if self.exact_dimensions:
         if width != required_width or height != required_height:
             raise LaunchpadValidationError(_(dedent("""
                 This image is not exactly ${width}x${height}
                 pixels in size."""),
                 mapping={'width': required_width,
                          'height': required_height}))
     else:
         if width > required_width or height > required_height:
             raise LaunchpadValidationError(_(dedent("""
                 This image is larger than ${width}x${height}
                 pixels in size."""),
                 mapping={'width': required_width,
                          'height': required_height}))
     return True
 def validate(self, data):
     """Check that user is not attempting to merge a person into itself."""
     dupe_person = data.get('dupe_person')
     target_person = data.get('target_person') or self.user
     if dupe_person is None:
         self.setFieldError(
             'dupe_person', 'The duplicate is not a valid person or team.')
     else:
         if dupe_person == target_person:
             self.addError(_("You can't merge ${name} into itself.",
                   mapping=dict(name=dupe_person.name)))
         dupe_person_ppas = getUtility(IArchiveSet).getPPAOwnedByPerson(
             dupe_person, statuses=[ArchiveStatus.ACTIVE,
                                    ArchiveStatus.DELETING])
         if dupe_person_ppas is not None:
             self.addError(_(
                 "${name} has a PPA that must be deleted before it "
                 "can be merged. It may take ten minutes to remove the "
                 "deleted PPA's files.",
                 mapping=dict(name=dupe_person.name)))
         all_branches = getUtility(IAllBranches)
         if not all_branches.ownedBy(dupe_person).isPrivate().is_empty():
             self.addError(
                 _("${name} owns private branches that must be "
                   "deleted or transferred to another owner first.",
                 mapping=dict(name=dupe_person.name)))
         if dupe_person.isMergePending():
             self.addError(_("${name} is already queued for merging.",
                   mapping=dict(name=dupe_person.name)))
     if target_person is not None and target_person.isMergePending():
         self.addError(_("${name} is already queued for merging.",
               mapping=dict(name=target_person.name)))
    def validate(self, data):
        """Verify that the entered times are valid.

        We check that:
         * they depart after they arrive
         * they don't arrive after the end of the sprint
         * they don't depart before the start of the sprint
        """
        time_starts = data.get('time_starts')
        time_ends = data.get('time_ends')

        if time_starts and time_starts > self.context.time_ends:
            self.setFieldError(
                'time_starts',
                _('Choose an arrival time before the end of the meeting.'))
        if time_ends:
            if time_starts and time_ends < time_starts:
                self.setFieldError(
                    'time_ends',
                    _('The end time must be after the start time.'))
            elif time_ends < self.context.time_starts:
                self.setFieldError(
                    'time_ends', _('Choose a departure time after the '
                                   'start of the meeting.'))
            elif (time_ends.hour == 0 and time_ends.minute == 0 and
                  time_ends.second == 0):
                # We assume the user entered just a date, which gives them
                # midnight in the morning of that day, when they probably want
                # the end of the day.
                data['time_ends'] = min(
                    self.context.time_ends,
                    time_ends + timedelta(days=1, seconds=-1))
 def __init__(self, context, request):
     SimpleInputWidget.__init__(self, context, request)
     fields = form.Fields(
         Choice(__name__='action', source=self._getActionsVocabulary(),
                title=_('Action')),
         Datetime(__name__='announcement_date', title=_('Date'),
                  required=False, default=None))
     fields['action'].custom_widget = CustomWidgetFactory(
         LaunchpadRadioWidget)
     fields['announcement_date'].custom_widget = CustomWidgetFactory(
         DateTimeWidget)
     if IAnnouncement.providedBy(self.context.context):
         # we are editing an existing announcement
         data = {}
         date_announced = self.context.context.date_announced
         data['announcement_date'] = date_announced
         if date_announced is None:
             data['action'] = 'sometime'
         else:
             data['action'] = 'specific'
     else:
         data = {'action': 'immediately'}
     widgets = form.setUpWidgets(
         fields, self.name, context, request, ignore_request=False,
         data=data)
     self.action_widget = widgets['action']
     self.announcement_date_widget = widgets['announcement_date']
 def request_import_action(self, action, data):
     """ Request an upload of translation files. """
     job = getUtility(IRosettaUploadJobSource).create(self.context.branch, NULL_REVISION, True)
     if job is None:
         self.addError(_("Your request could not be filed."))
     else:
         self.request.response.addInfoNotification(_("The import has been requested."))
 def getInputValue(self):
     self._error = None
     action = self.action_widget.getInputValue()
     try:
         announcement_date = self.announcement_date_widget.getInputValue()
     except ConversionError:
         self._error = WidgetInputError(
             self.name, self.label,
             LaunchpadValidationError(
                 _('Please provide a valid date and time.')))
         raise self._error
     if action == 'immediately' and announcement_date is not None:
         self._error = WidgetInputError(
             self.name, self.label,
             LaunchpadValidationError(
                 _('Please do not provide a date if you want to publish '
                   'immediately.')))
         raise self._error
     if action == 'specific' and announcement_date is None:
         self._error = WidgetInputError(
             self.name, self.label,
             LaunchpadValidationError(
                 _('Please provide a publication date.')))
         raise self._error
     if action == 'immediately':
         return datetime.now(pytz.utc)
     elif action == "sometime":
         return None
     elif action == "specific":
         return announcement_date
     else:
         raise AssertionError, 'Unknown action in AnnouncementDateWidget'
    def _validateSignOnlyGPGKey(self, data):
        # Verify the signed content.
        signedcontent = data.get('text_signature')
        if signedcontent is None:
            return

        try:
            signature = getUtility(IGPGHandler).getVerifiedSignature(
                signedcontent.encode('ASCII'))
        except (GPGVerificationError, UnicodeEncodeError) as e:
            self.addError(_(
                'Launchpad could not verify your signature: ${err}',
                mapping=dict(err=str(e))))
            return

        if signature.fingerprint != self.context.fingerprint:
            self.addError(_(
                'The key used to sign the content (${fprint}) is not the '
                'key you were registering',
                mapping=dict(fprint=signature.fingerprint)))
            return

        # We compare the word-splitted content to avoid failures due
        # to whitepace differences.
        if (signature.plain_data.split()
            != self.context.validation_phrase.split()):
            self.addError(_(
                'The signed content does not match the message found '
                'in the email.'))
            return
    def update_action(self, action, data):
        """Update the answer contact registration."""
        want_to_be_answer_contact = data["want_to_be_answer_contact"]
        answer_contact_teams = data.get("answer_contact_teams", [])
        response = self.request.response
        replacements = {"context": self.context.displayname}
        if want_to_be_answer_contact:
            self._updatePreferredLanguages(self.user)
            if self.context.addAnswerContact(self.user, self.user):
                response.addNotification(
                    _("You have been added as an answer contact for " "$context.", mapping=replacements)
                )
        else:
            if self.context.removeAnswerContact(self.user, self.user):
                response.addNotification(
                    _("You have been removed as an answer contact for " "$context.", mapping=replacements)
                )

        for team in self.administrated_teams:
            replacements["teamname"] = team.displayname
            if team in answer_contact_teams:
                self._updatePreferredLanguages(team)
                if self.context.addAnswerContact(team, self.user):
                    response.addNotification(
                        _("$teamname has been added as an answer contact " "for $context.", mapping=replacements)
                    )
            else:
                if self.context.removeAnswerContact(team, self.user):
                    response.addNotification(
                        _("$teamname has been removed as an answer contact " "for $context.", mapping=replacements)
                    )

        self.next_url = canonical_url(self.context, rootsite="answers")
    def createStatusField(self):
        """Create the 'status' field.

        Create the status vocabulary according to the current distroseries
        status:
         * stable   -> CURRENT, SUPPORTED, OBSOLETE
         * unstable -> EXPERIMENTAL, DEVELOPMENT, FROZEN, FUTURE, CURRENT
        """
        stable_status = (
            SeriesStatus.CURRENT,
            SeriesStatus.SUPPORTED,
            SeriesStatus.OBSOLETE,
            )

        if self.context.status not in stable_status:
            terms = [status for status in SeriesStatus.items
                     if status not in stable_status]
            terms.append(SeriesStatus.CURRENT)
        else:
            terms = stable_status

        status_vocabulary = SimpleVocabulary(
            [SimpleTerm(item, item.name, item.title) for item in terms])

        return form.Fields(
            Choice(__name__='status',
                   title=_('Status'),
                   default=self.context.status,
                   vocabulary=status_vocabulary,
                   description=_("Select the distroseries status."),
                   required=True))
    def setUpFields(self):
        super(SourcePackageChangeUpstreamStepTwo, self).setUpFields()

        # The vocabulary for the product series is overridden to just
        # include active series from the product selected in the
        # previous step.
        product_name = self.request.form['field.product']
        self.product = getUtility(IProductSet)[product_name]
        series_list = [
            series for series in self.product.series
            if series.status != SeriesStatus.OBSOLETE]

        # If the product is not being changed, then the current
        # productseries can be the default choice. Otherwise,
        # it will not exist in the vocabulary.
        if (self.context.productseries is not None
            and self.context.productseries.product == self.product):
            series_default = self.context.productseries
            # This only happens for obsolete series, since they aren't
            # added to the vocabulary normally.
            if series_default not in series_list:
                series_list.append(series_default)
        else:
            series_default = None

        # Put the development focus at the top of the list and create
        # the vocabulary.
        dev_focus = self.product.development_focus
        if dev_focus in series_list:
            series_list.remove(dev_focus)
        vocab_terms = [
            SimpleTerm(series, series.name, series.name)
            for series in series_list]
        dev_focus_term = SimpleTerm(
            dev_focus, dev_focus.name, "%s (Recommended)" % dev_focus.name)
        vocab_terms.insert(0, dev_focus_term)

        productseries_choice = Choice(
            __name__='productseries',
            title=_("Series"),
            description=_("The series in this project."),
            vocabulary=SimpleVocabulary(vocab_terms),
            default=series_default,
            required=True)

        # The product selected in the previous step should be displayed,
        # but a widget can't be readonly and pass its value with the
        # form, so the real product field passes the value, and this fake
        # product field displays it.
        display_product_field = TextLine(
            __name__='fake_product',
            title=_("Project"),
            default=self.product.displayname,
            readonly=True)

        self.form_fields = (
            Fields(display_product_field, productseries_choice)
            + self.form_fields)
 def page_title(self):
     """See `SearchQuestionsView`."""
     mapping = dict(
         context=self.context.displayname, search_text=self.search_text, language=self.language.englishname
     )
     if self.search_text:
         return _('${language} questions matching "${search_text}" ' "in ${context}", mapping=mapping)
     else:
         return _("${language} questions in ${context}", mapping=mapping)
 def empty_listing_message(self):
     """See `SearchQuestionsView`."""
     if self.search_text:
         return _(
             'No questions matching "${search_text}" need your ' "attention for ${context}.",
             mapping=dict(context=self.context.displayname, search_text=self.search_text),
         )
     else:
         return _("No questions need your attention for ${context}.", mapping={"context": self.context.displayname})
 def page_title(self):
     """See `SearchQuestionsView`."""
     if self.search_text:
         return _(
             'Questions matching "${search_text}" needing your ' "attention for ${context}",
             mapping=dict(context=self.context.displayname, search_text=self.search_text),
         )
     else:
         return _("Questions needing your attention for ${context}", mapping={"context": self.context.displayname})
Exemple #14
0
 def _createAliasesField(self):
     """Return a PillarAliases field for IProjectGroup.aliases."""
     return form.Fields(
         PillarAliases(
             __name__='aliases', title=_('Aliases'),
             description=_('Other names (separated by space) under which '
                           'this project group is known.'),
             required=False, readonly=False),
         render_context=self.render_context)
 def page_title(self):
     """See `SearchQuestionsView`."""
     if self.search_text:
         return _(
             'Questions you asked matching "${search_text}" for ' "${context}",
             mapping=dict(context=self.context.displayname, search_text=self.search_text),
         )
     else:
         return _("Questions you asked about ${context}", mapping={"context": self.context.displayname})
 def empty_listing_message(self):
     """See `SearchQuestionsView`."""
     if self.search_text:
         return _(
             "You didn't ask any questions matching " '"${search_text}" for ${context}.',
             mapping=dict(context=self.context.displayname, search_text=self.search_text),
         )
     else:
         return _("You didn't ask any questions about ${context}.", mapping={"context": self.context.displayname})
 def page_title(self):
     """Return the page_title that should be used for the listing."""
     replacements = dict(
         displayname=self.context.displayname,
         search_text=self.search_text)
     if self.search_text:
         return _(u'FAQs matching \u201c${search_text}\u201d for '
                  u'$displayname', mapping=replacements)
     else:
         return _('FAQs for $displayname', mapping=replacements)
 def _prependKeepMilestoneActiveField(self):
     keep_milestone_active_checkbox = FormFields(
         Bool(
             __name__='keep_milestone_active',
             title=_("Keep the %s milestone active." % self.context.name),
             description=_(
                 "Only select this if bugs or blueprints still need "
                 "to be targeted to this project release's milestone.")),
         render_context=self.render_context)
     self.form_fields = keep_milestone_active_checkbox + self.form_fields
 def initialize(self):
     LaunchpadFormView.initialize(self)
     # Update the submit label based on the user's permission.
     submit_action = self.__class__.actions.byname["actions.submit"]
     if self.userIsReleaseManager():
         submit_action.label = _("Target")
     elif self.userIsBugSupervisor():
         submit_action.label = _("Nominate")
     else:
         self.request.response.addErrorNotification("You do not have permission to nominate this bug.")
         self.request.response.redirect(canonical_url(self.current_bugtask))
 def empty_listing_message(self):
     """Return the message to render when there are no FAQs to display."""
     replacements = dict(
         displayname=self.context.displayname,
         search_text=self.search_text)
     if self.search_text:
         return _(u'There are no FAQs for $displayname matching '
                  u'\u201c${search_text}\u201d.', mapping=replacements)
     else:
         return _('There are no FAQs for $displayname.',
                  mapping=replacements)
 def empty_listing_message(self):
     """See `SearchQuestionsView`."""
     mapping = dict(
         context=self.context.displayname, search_text=self.search_text, language=self.language.englishname
     )
     if self.search_text:
         return _(
             'No ${language} questions matching "${search_text}" ' "in ${context} for the selected status.",
             mapping=mapping,
         )
     else:
         return _("No ${language} questions in ${context} for the " "selected status.", mapping=mapping)
Exemple #22
0
 def createProductField(self):
     """Create a Choice field to select one of the project's products."""
     return form.Fields(
         Choice(
             __name__='product', vocabulary='ProjectProducts',
             title=_('Project'),
             description=_(
                 '${context} is a group of projects, which specific '
                 'project do you have a question about?',
                 mapping=dict(context=self.context.title)),
             required=True),
         render_context=self.render_context)
 def unlinkBugs(self, action, data):
     response = self.request.response
     target_unmodified = Snapshot(self.context, providing=providedBy(self.context))
     for bug in data["bugs"]:
         replacements = {"bugid": bug.id}
         try:
             self.context.unlinkBug(bug)
             response.addNotification(_("Removed link to bug #$bugid.", mapping=replacements))
         except Unauthorized:
             response.addErrorNotification(_("Cannot remove link to private bug #$bugid.", mapping=replacements))
     notify(ObjectModifiedEvent(self.context, target_unmodified, ["bugs"]))
     self.next_url = canonical_url(self.context)
Exemple #24
0
 def __init__(self, context, request, style):
     SimpleInputWidget.__init__(self, context, request)
     self.style = style
     fields = form.Fields(
         Choice(__name__="action", source=self._getActionsVocabulary(), title=_("Action")),
         Bytes(__name__="image", title=_("Image")),
     )
     fields["action"].custom_widget = CustomWidgetFactory(LaunchpadRadioWidget)
     fields["image"].custom_widget = CustomWidgetFactory(LaunchpadFileWidget, displayWidth=15)
     widgets = form.setUpWidgets(fields, self.name, context, request, ignore_request=False, data={"action": "keep"})
     self.action_widget = widgets["action"]
     self.image_widget = widgets["image"]
Exemple #25
0
    def link(self, view_name):
        """The link to the diff should show the line count.

        Stale diffs will have a stale-diff css class.
        Diffs with conflicts will have a conflict-diff css class.
        Diffs with neither will have clean-diff css class.

        The title of the diff will show the number of lines added or removed
        if available.

        :param view_name: If not None, the link will point to the page with
            that name on this object.
        """
        diff = self._context
        conflict_text = ''
        if diff.has_conflicts:
            conflict_text = _(' (has conflicts)')

        count_text = ''
        added = diff.added_lines_count
        removed = diff.removed_lines_count
        if (added is not None and removed is not None):
            count_text = ' (+%d/-%d)' % (added, removed)

        file_text = ''
        diffstat = diff.diffstat
        if diffstat is not None:
            file_count = len(diffstat)
            file_text = get_plural_text(
                file_count, _(' %d file modified'), _(' %d files modified'))
            file_text = file_text % file_count

        args = {
            'line_count': _('%s lines') % diff.diff_lines_count,
            'file_text': file_text,
            'conflict_text': conflict_text,
            'count_text': count_text,
            'url': self.url(view_name),
            }
        # Under normal circumstances, there will be an associated file,
        # however if the diff is empty, then there is no alias to link to.
        if args['url'] is None:
            return (
                '<span class="empty-diff">'
                '%(line_count)s</span>' % args)
        else:
            return (
                '<a href="%(url)s" class="diff-link">'
                '%(line_count)s%(count_text)s%(file_text)s%(conflict_text)s'
                '</a>' % args)
    def _activateGPGKey(self, key, can_encrypt):
        person_url = canonical_url(self.context.requester)
        lpkey, new, = self.context.activateGPGKey(key, can_encrypt)

        if new:
            self.request.response.addInfoNotification(_(
                "The key ${lpkey} was successfully validated. ",
                mapping=dict(lpkey=lpkey.displayname)))
        else:
            msgid = _(
                'Key ${lpkey} successfully reactivated. '
                '<a href="${url}/+editpgpkeys">See more Information'
                '</a>',
                mapping=dict(lpkey=lpkey.displayname, url=person_url))
            self.request.response.addInfoNotification(structured(msgid))
Exemple #27
0
class ILoginToken(IAuthToken):
    """The object that stores one time tokens.

    Used for validating email addresses and other tasks that require verifying
    if an email address is valid, account merging. All LoginTokens must be
    deleted once they are "consumed".
    """

    fingerprint = Text(
        title=_('OpenPGP key fingerprint used to retrieve key information '
                'when necessary.'),
        required=False,
    )

    validation_phrase = Text(
        title=_("The phrase used to validate sign-only GPG keys"))

    def destroySelf():
        """Remove this LoginToken from the database.

        We need this because once the token is used (either when registering a
        new user or validating an email address), we have to delete it so
        nobody can use that token again.
        """

    def sendGPGValidationRequest(key):
        """Send an email message with a magic URL to confirm the OpenPGP key.
        If fingerprint is set, send the message encrypted.
        """

    def sendMergeRequestEmail():
        """Send an email to self.email (the dupe account's email address)
        with the URL of a page to finish the merge of Launchpad accounts.
        """

    def sendTeamEmailAddressValidationEmail(user):
        """Send an email to self.email containing a URL to the page where it
        can be set as the requester's (the team) contact address.

        The message also includes the team administrator who made this
        request on behalf of the team.
        """

    def sendClaimTeamEmail():
        """Email instructions for claiming a team to self.email."""

    def activateGPGKey(key, can_encrypt):
        """Activate a GPG key.
    def _getGPGKey(self):
        """Look up the OpenPGP key for this login token.

        If the key can not be retrieved from the keyserver, the key
        has been revoked or expired, None is returned and an error is set
        using self.addError.
        """
        gpghandler = getUtility(IGPGHandler)

        requester = self.context.requester
        fingerprint = self.context.fingerprint
        assert fingerprint is not None

        person_url = canonical_url(requester)
        try:
            key = gpghandler.retrieveActiveKey(fingerprint)
        except GPGKeyNotFoundError:
            self.addError(
                structured(_(
                'Launchpad could not import the OpenPGP key %{fingerprint}. '
                'Check that you published it correctly in the '
                'global key ring (using <kbd>gpg --send-keys '
                'KEY</kbd>) and that you entered the fingerprint '
                'correctly (as produced by <kbd>gpg --fingerprint '
                'YOU</kdb>). Try later or <a href="${url}/+editpgpkeys"> '
                'cancel your request</a>.',
                mapping=dict(fingerprint=fingerprint, url=person_url))))
        except GPGKeyRevoked as e:
            # If key is globally revoked, skip the import and consume the
            # token.
            self.addError(
                    structured(_(
                'The key ${key} cannot be validated because it has been '
                'publicly revoked. You will need to generate a new key '
                '(using <kbd>gpg --genkey</kbd>) and repeat the previous '
                'process to <a href="${url}/+editpgpkeys">find and '
                'import</a> the new key.',
                mapping=dict(key=e.key.keyid, url=person_url))))
        except GPGKeyExpired as e:
            self.addError(
                        structured(_(
                'The key ${key} cannot be validated because it has expired. '
                'Change the expiry date (in a terminal, enter '
                '<kbd>gpg --edit-key <var>[email protected]</var></kbd> '
                'then enter <kbd>expire</kbd>), and try again.',
                mapping=dict(key=e.key.keyid))))
        else:
            return key
class AnnouncementRetargetView(AnnouncementFormMixin, LaunchpadFormView):
    """A view to move an annoucement to another project."""

    schema = AnnouncementRetargetForm
    field_names = ['target']
    page_title = 'Move announcement'

    def validate(self, data):
        """Ensure that the person can publish announcement at the new
        target.
        """

        target = data.get('target')

        if target is None:
            self.setFieldError(
                'target', "There is no project with the name '%s'. "
                "Please check that name and try again." %
                self.request.form.get("field.target"))
            return

        if not check_permission('launchpad.Edit', target):
            self.setFieldError(
                'target',
                "You don't have permission to make announcements for "
                "%s. Please check that name and try again." %
                target.displayname)
            return

    @action(_('Retarget'), name='retarget')
    def retarget_action(self, action, data):
        target = data.get('target')
        self.context.retarget(target)
        self.next_url = canonical_url(self.context.target) + '/+announcements'
Exemple #30
0
class BugBranchAddView(LaunchpadFormView):
    """Browser view for linking a bug to a branch."""
    schema = IBugBranch
    # In order to have the branch field rendered using the appropriate
    # widget, we set the LaunchpadFormView attribute for_input to True
    # to get the read only fields rendered as input widgets.
    for_input = True
    page_title = 'Add branch'
    field_names = ['branch']

    @action(_('Continue'), name='continue')
    def continue_action(self, action, data):
        branch = data['branch']
        self.context.bug.linkBranch(branch=branch, registrant=self.user)
        self.request.response.addNotification(
            "Successfully registered branch %s for this bug." % branch.name)

    @property
    def next_url(self):
        return canonical_url(self.context)

    @property
    def label(self):
        return 'Add a branch to bug #%i' % self.context.bug.id

    cancel_url = next_url
Exemple #31
0
class IURIField(ITextLine):
    """A URI.

    A text line that holds a URI.
    """
    trailing_slash = Bool(
        title=_('Whether a trailing slash is required for this field'),
        required=False,
        description=_('If set to True, then the path component of the URI '
                      'will be automatically normalized to end in a slash. '
                      'If set to False, any trailing slash will be '
                      'automatically removed. If set to None, URIs will '
                      'not be normalized.'))

    def normalize(input):
        """Normalize a URI.
Exemple #32
0
class PillarNameField(BlacklistableContentNameField):
    """Base field used for names of distros/project groups/products."""

    errormessage = _("%s is already used by another project")

    def _getByName(self, name):
        return getUtility(IPillarNameSet).getByName(name)
Exemple #33
0
 def unlinkBugs(self, action, data):
     response = self.request.response
     target_unmodified = Snapshot(self.context,
                                  providing=providedBy(self.context))
     for bug in data['bugs']:
         replacements = {'bugid': bug.id}
         try:
             self.context.unlinkBug(bug, user=self.user)
             response.addNotification(
                 _('Removed link to bug #$bugid.', mapping=replacements))
         except Unauthorized:
             response.addErrorNotification(
                 _('Cannot remove link to private bug #$bugid.',
                   mapping=replacements))
     notify(ObjectModifiedEvent(self.context, target_unmodified, ['bugs']))
     self.next_url = canonical_url(self.context)
Exemple #34
0
 def linkBug(self, action, data):
     """Link to the requested bug. Publish an ObjectModifiedEvent and
     display a notification.
     """
     response = self.request.response
     target_unmodified = Snapshot(self.context,
                                  providing=providedBy(self.context))
     bug = data['bug']
     try:
         self.context.linkBug(bug, user=self.user)
     except Unauthorized:
         # XXX flacoste 2006-08-23 bug=57470: This should use proper _().
         self.setFieldError(
             'bug',
             'You are not allowed to link to private bug #%d.' % bug.id)
         return
     bug_props = {'bugid': bug.id, 'title': bug.title}
     response.addNotification(
         _(
             u'Added link to bug #$bugid: '
             u'\N{left double quotation mark}$title'
             u'\N{right double quotation mark}.',
             mapping=bug_props))
     notify(ObjectModifiedEvent(self.context, target_unmodified, ['bugs']))
     self.next_url = canonical_url(self.context)
Exemple #35
0
    def validate(self, data):
        """Make sure the email address this token refers to is not in use."""
        validated = (EmailAddressStatus.VALIDATED,
                     EmailAddressStatus.PREFERRED)
        requester = self.context.requester

        emailset = getUtility(IEmailAddressSet)
        email = emailset.getByEmail(self.context.email)
        if email is not None:
            if requester is None or email.personID != requester.id:
                dupe = email.person
                # Yes, hardcoding an autogenerated field name is an evil
                # hack, but if it fails nothing will happen.
                # -- Guilherme Salgado 2005-07-09
                url = allvhosts.configs['mainsite'].rooturl
                query = urllib.urlencode([('field.dupe_person', dupe.name)])
                url += '/people/+requestmerge?' + query
                self.addError(
                    structured(
                        'This email address is already registered for another '
                        'Launchpad user account. This account can be a '
                        'duplicate of yours, created automatically, and in this '
                        'case you should be able to <a href="%(url)s">merge them'
                        '</a> into a single one.',
                        url=url))
            elif email.status in validated:
                self.addError(
                    _("This email address is already registered and validated "
                      "for your Launchpad account. There's no need to validate "
                      "it again."))
            else:
                # Yay, email is not used by anybody else and is not yet
                # validated.
                pass
class AnnouncementAddView(LaunchpadFormView):
    """A view for creating a new Announcement."""

    schema = AddAnnouncementForm
    label = "Make an announcement"
    page_title = label

    custom_widget_publication_date = AnnouncementDateWidget

    @action(_('Make announcement'), name='announce')
    def announce_action(self, action, data):
        """Registers a new announcement."""
        self.context.announce(user=self.user,
                              title=data.get('title'),
                              summary=data.get('summary'),
                              url=data.get('url'),
                              publication_date=data.get('publication_date'))
        self.next_url = canonical_url(self.context)

    @property
    def action_url(self):
        return "%s/+announce" % canonical_url(self.context)

    @property
    def cancel_url(self):
        """The project's URL."""
        return canonical_url(self.context)
Exemple #37
0
class ITranslationTemplatesBuild(IBuildFarmJob):
    """The build information for translation templates builds."""

    branch = Reference(title=_("The branch that this build operates on."),
                       required=True,
                       readonly=True,
                       schema=IBranch)
Exemple #38
0
class IProductPublic(Interface):

    id = Int(title=_('The Project ID'))

    def userCanView(user):
        """True if the given user has access to this product."""

    def userCanLimitedView(user):
        """True if the given user has limited access to this product."""

    private = exported(
        Bool(
            title=_("Product is confidential"), required=False,
            readonly=True, default=False,
            description=_(
                "This product is visible only to those with access grants.")))
Exemple #39
0
class ISignableArchive(Interface):
    """`SignableArchive` interface.

    `IArchive` adapter for operations that involve signing files.
    """

    archive = Object(title=_('Corresponding IArchive'),
                     required=True,
                     schema=IArchive)

    can_sign = Attribute("True if this archive is set up for signing.")

    def signRepository(suite, pubconf=None, suffix='', log=None):
        """Sign the corresponding repository.

        :param suite: suite name to be signed.
        :param pubconf: an optional publisher configuration instance
            indicating where files should be written (if not passed, uses
            the archive's defaults).
        :param suffix: an optional suffix for repository index files (e.g.
            ".new" to help with publishing files atomically).
        :param log: an optional logger.
        :return: A sequence of output paths that were produced, relative to
            the suite's path, with `suffix` removed.
        :raises CannotSignArchive: if the context archive is not set up for
            signing.
        :raises AssertionError: if there is no Release file in the given
            suite.
        """

    def signFile(suite, path, log=None):
        """Sign the corresponding file.
Exemple #40
0
class FAQEditView(LaunchpadEditFormView):
    """View to change the FAQ details."""

    schema = IFAQ
    label = _('Edit FAQ')
    field_names = ["title", "keywords", "content"]

    @property
    def page_title(self):
        return 'Edit FAQ #%s details' % self.context.id

    @action(_('Save'), name="save")
    def save_action(self, action, data):
        """Update the FAQ details."""
        self.updateContextFromData(data)
        self.next_url = canonical_url(self.context)
Exemple #41
0
class IRevisionAuthor(Interface):
    """Committer of a Bazaar revision."""

    id = Int(title=_('The database revision author ID'))
    name = TextLine(title=_("Revision Author Name"), required=True)
    name_without_email = Attribute(
        "Revision author name without email address.")
    email = Attribute("The email address extracted from the author text.")
    person = PublicPersonChoice(title=_('Author'),
                                required=False,
                                readonly=False,
                                vocabulary='ValidPersonOrTeam')
    personID = Attribute("The primary key of the person")

    def linkToLaunchpadPerson():
        """Attempt to link the revision author to a Launchpad `Person`.
Exemple #42
0
def valid_webref(web_ref):
    """Returns True if web_ref is a valid download URL, or raises a
    LaunchpadValidationError.

    >>> valid_webref('http://example.com')
    True
    >>> valid_webref('https://example.com/foo/bar')
    True
    >>> valid_webref('ftp://example.com/~ming')
    True
    >>> valid_webref('sftp://example.com//absolute/path/maybe')
    True
    >>> valid_webref('other://example.com/moo')
    Traceback (most recent call last):
    ...
    LaunchpadValidationError: ...
    """
    if validate_url(web_ref, ['http', 'https', 'ftp', 'sftp']):
        # Allow ftp so valid_webref can be used for download_url, and so
        # it doesn't lock out weird projects where the site or
        # screenshots are kept on ftp.
        return True
    else:
        raise LaunchpadValidationError(_(dedent("""
            Not a valid URL. Please enter the full URL, including the
            scheme (for instance, http:// for a web URL), and ensure the
            URL uses either http, https or ftp.""")))
Exemple #43
0
class ProductSeriesEditView(LaunchpadEditFormView):
    """A View to edit the attributes of a series."""
    schema = IProductSeries
    field_names = [
        'name', 'summary', 'status', 'branch', 'releasefileglob']
    custom_widget('summary', TextAreaWidget, height=7, width=62)
    custom_widget('releasefileglob', StrippedTextWidget, displayWidth=40)

    @property
    def label(self):
        """The form label."""
        return 'Edit %s %s series' % (
            self.context.product.displayname, self.context.name)

    page_title = label

    def validate(self, data):
        """See `LaunchpadFormView`."""
        branch = data.get('branch')
        if branch is not None:
            message = get_series_branch_error(self.context.product, branch)
            if message:
                self.setFieldError('branch', message)

    @action(_('Change'), name='change')
    def change_action(self, action, data):
        """Update the series."""
        self.updateContextFromData(data)

    @property
    def next_url(self):
        """See `LaunchpadFormView`."""
        return canonical_url(self.context)

    cancel_url = next_url
Exemple #44
0
class BranchLinkToSpecificationView(LaunchpadFormView):
    """The view to create spec-branch links."""

    schema = ISpecificationBranch
    # In order to have the specification field rendered using the appropriate
    # widget, we set the LaunchpadFormView attribute for_input to True
    # to get the read only fields rendered as input widgets.
    for_input = True

    field_names = ['specification']

    @property
    def label(self):
        return "Link to a blueprint"

    @property
    def page_title(self):
        return 'Link branch %s to a blueprint' % self.context.displayname

    @property
    def next_url(self):
        return canonical_url(self.context)

    cancel_url = next_url

    @action(_('Continue'), name='continue')
    def continue_action(self, action, data):
        spec = data['specification']
        spec.linkBranch(branch=self.context, registrant=self.user)
Exemple #45
0
class BuildRescoringView(LaunchpadFormView):
    """View class for build rescoring."""

    schema = IBuildRescoreForm

    @property
    def label(self):
        return 'Rescore %s' % self.context.title

    def initialize(self):
        """See `ILaunchpadFormView`.

        It redirects attempts to rescore builds that cannot be rescored
        to the build context page, so the current page-scrapping libraries
        won't cause any oops.

        It also sets next_url and cancel_url to the build context page, so
        any action will send the user back to the context build page.
        """
        build_url = canonical_url(self.context)
        self.next_url = self.cancel_url = build_url

        if not self.context.can_be_rescored:
            self.request.response.redirect(build_url)

        LaunchpadFormView.initialize(self)

    @action(_("Rescore"), name="rescore")
    def action_rescore(self, action, data):
        """Set the given score value."""
        score = data.get('priority')
        self.context.rescore(score)
        self.request.response.addNotification(
            "Build rescored to %s." % score)
class SpecificationSubscriptionEditView(LaunchpadEditFormView):

    schema = ISpecificationSubscription
    field_names = ['essential']

    @property
    def label(self):
        return "Modify subscription to %s" % self.context.specification.title

    @property
    def cancel_url(self):
        return canonical_url(self.context.specification)

    next_url = cancel_url

    @action(_('Change'), name='change')
    def change_action(self, action, data):
        self.updateContextFromData(data)
        is_current_user_subscription = self.user == self.context.person
        if is_current_user_subscription:
            self.request.response.addInfoNotification(
                "Your subscription has been updated.")
        else:
            self.request.response.addInfoNotification(
                "The subscription for %s has been updated." %
                self.context.person.displayname)
Exemple #47
0
class IEditTranslator(Interface):
    """Set of translator attributes that the translator can edit themselves as
    well as being editable by the translation group owner.

    Translators can edit the data in their `ITranslator` entry.
    Currently this is just the documentation URL.
    """

    style_guide_url = URIField(
        title=_('Translation guidelines'),
        required=False,
        allowed_schemes=['http', 'https', 'ftp'],
        allow_userinfo=False,
        description=_("The URL of the translation team guidelines "
                      "to be followed by this particular translation team. "
                      "Can be any of the http://, https://, or ftp:// links."))
Exemple #48
0
class IBugMute(Interface):
    """A mute on an IBug."""

    person = PersonChoice(title=_('Person'),
                          required=True,
                          vocabulary='ValidPersonOrTeam',
                          readonly=True,
                          description=_("The person subscribed."))
    bug = Reference(IBug,
                    title=_("Bug"),
                    required=True,
                    readonly=True,
                    description=_("The bug to be muted."))
    date_created = Datetime(title=_("The date on which the mute was created."),
                            required=False,
                            readonly=True)
Exemple #49
0
class ISnapBaseSet(ISnapBaseSetEdit):
    """Interface representing the set of bases for snaps."""

    export_as_webservice_collection(ISnapBase)

    def __iter__():
        """Iterate over `ISnapBase`s."""

    def __getitem__(name):
        """Return the `ISnapBase` with this name."""

    @operation_parameters(name=TextLine(title=_("Base name"), required=True))
    @operation_returns_entry(ISnapBase)
    @export_read_operation()
    @operation_for_version("devel")
    def getByName(name):
        """Return the `ISnapBase` with this name.

        :raises NoSuchSnapBase: if no base exists with this name.
        """

    @operation_returns_entry(ISnapBase)
    @export_read_operation()
    @operation_for_version("devel")
    def getDefault():
        """Get the default base.

        This will be used to pick the default distro series for snaps that
        do not specify a base.
        """

    @collection_default_content()
    def getAll():
        """Return all `ISnapBase`s."""
class AnnouncementEditView(AnnouncementFormMixin, LaunchpadFormView):
    """A view which allows you to edit the announcement."""

    schema = AddAnnouncementForm
    field_names = [
        'title',
        'summary',
        'url',
    ]
    page_title = 'Modify announcement'

    @property
    def initial_values(self):
        return {
            'title': self.context.title,
            'summary': self.context.summary,
            'url': self.context.url,
        }

    @action(_('Modify'), name='modify')
    def modify_action(self, action, data):
        self.context.modify(title=data.get('title'),
                            summary=data.get('summary'),
                            url=data.get('url'))
        self.next_url = canonical_url(self.context.target) + '/+announcements'
    def _validate(self, value):
        # import here to avoid circular import
        from lp.services.webapp import canonical_url
        from lazr.uri import URI

        super(DistroMirrorURIField, self)._validate(value)
        uri = URI(self.normalize(value))

        # This field is also used when creating new mirrors and in that case
        # self.context is not an IDistributionMirror so it doesn't make sense
        # to try to get the existing value of the attribute.
        if IDistributionMirror.providedBy(self.context):
            orig_value = self.get(self.context)
            if orig_value is not None and URI(orig_value) == uri:
                return  # url was not changed

        mirror = self.getMirrorByURI(str(uri))
        if mirror is not None:
            message = _(
                'The distribution mirror <a href="${url}">${mirror}</a> '
                'is already registered with this URL.',
                mapping={
                    'url': html_escape(canonical_url(mirror)),
                    'mirror': html_escape(mirror.title)
                })
            raise LaunchpadValidationError(structured(message))
 def createFilterFieldHelper(self, name, source, title):
     """A helper method for creating filter fields."""
     self._initial_values[name] = 'all'
     return form.Fields(Choice(__name__=name, source=source,
                               title=_(title)),
                        custom_widget=self.custom_widgets[name],
                        render_context=self.render_context)
Exemple #53
0
class ITranslationGroupSet(Interface):
    """A container for translation groups."""

    export_as_webservice_collection(ITranslationGroup)

    title = Attribute('Title')

    @operation_parameters(
        name=TextLine(title=_("Name of the translation group"),))
    @operation_returns_entry(ITranslationGroup)
    @export_read_operation()
    @operation_for_version('devel')
    def getByName(name):
        """Get a translation group by name."""

    def __getitem__(name):
        """Get a translation group by name."""

    @collection_default_content()
    def _get():
        """Return a collection of all entries."""

    def __iter__():
        """Iterate through the translation groups in this set."""

    def new(name, title, summary, translation_guide_url, owner):
        """Create a new translation group."""

    def getByPerson(person):
        """Return the translation groups which that person is a member of."""

    def getGroupsCount():
        """Return the amount of translation groups available."""
Exemple #54
0
class MilestoneAddView(MilestoneTagBase, LaunchpadFormView):
    """A view for creating a new Milestone."""

    schema = IMilestone
    field_names = ['name', 'code_name', 'dateexpected', 'summary']
    label = "Register a new milestone"

    custom_widget_dateexpected = DateWidget

    @action(_('Register Milestone'), name='register')
    def register_action(self, action, data):
        """Use the newMilestone method on the context to make a milestone."""
        milestone = self.context.newMilestone(
            name=data.get('name'),
            code_name=data.get('code_name'),
            dateexpected=data.get('dateexpected'),
            summary=data.get('summary'))
        tags = data.get('tags')
        if tags:
            milestone.setTags(tags.lower().split(), self.user)
        self.next_url = canonical_url(self.context)

    @property
    def action_url(self):
        """See `LaunchpadFormView`."""
        return "%s/+addmilestone" % canonical_url(self.context)

    @property
    def cancel_url(self):
        """See `LaunchpadFormView`."""
        return canonical_url(self.context)
Exemple #55
0
class DistributionMirrorReviewView(LaunchpadEditFormView):

    schema = IDistributionMirror
    field_names = ['status', 'whiteboard']

    @property
    def label(self):
        """See `LaunchpadFormView`."""
        return 'Review mirror %s' % self.context.title

    @property
    def page_title(self):
        """The page title."""
        return self.label

    @property
    def cancel_url(self):
        """See `LaunchpadFormView`."""
        return canonical_url(self.context)

    @action(_("Save"), name="save")
    def action_save(self, action, data):
        context = self.context
        if data['status'] != context.status:
            context.reviewer = self.user
            context.date_reviewed = datetime.now(pytz.timezone('UTC'))
        self.updateContextFromData(data)
        self.next_url = canonical_url(context)
Exemple #56
0
class ProductSeriesReviewView(LaunchpadEditFormView):
    """A view to review and change the series `IProduct` and name."""
    schema = IProductSeries
    field_names = ['product', 'name']
    custom_widget('name', TextWidget, displayWidth=20)

    @property
    def label(self):
        """The form label."""
        return 'Administer %s %s series' % (
            self.context.product.displayname, self.context.name)

    page_title = label

    @property
    def cancel_url(self):
        """See `LaunchpadFormView`."""
        return canonical_url(self.context)

    @action(_('Change'), name='change')
    def change_action(self, action, data):
        """Update the series."""
        self.updateContextFromData(data)
        self.request.response.addInfoNotification(
            _('This Series has been changed'))
        self.next_url = canonical_url(self.context)
    def _bug_notification_level_field(self):
        """Return a custom form field for bug_notification_level."""
        # We rebuild the items that we show in the field so that the
        # labels shown are human readable and specific to the +subscribe
        # form. The BugNotificationLevel descriptions are too generic.
        bug_notification_level_terms = [
            SimpleTerm(
                level, level.title,
                self._bug_notification_level_descriptions[level])
            # We reorder the items so that COMMENTS comes first.
            for level in sorted(BugNotificationLevel.items, reverse=True)]
        bug_notification_vocabulary = SimpleVocabulary(
            bug_notification_level_terms)

        if self.current_user_subscription is not None:
            default_value = (
                self.current_user_subscription.bug_notification_level)
        else:
            default_value = BugNotificationLevel.COMMENTS

        bug_notification_level_field = Choice(
            __name__='bug_notification_level', title=_("Tell me when"),
            vocabulary=bug_notification_vocabulary, required=True,
            default=default_value)
        return bug_notification_level_field
Exemple #58
0
class ISnapBuildAdmin(Interface):
    """`ISnapBuild` attributes that require launchpad.Admin."""
    @operation_parameters(score=Int(title=_("Score"), required=True))
    @export_write_operation()
    @operation_for_version("devel")
    def rescore(score):
        """Change the build's score."""
    def setUpFields(self):
        """Setup an extra field with all products using the given bugtracker.

        This extra field is setup only if there is one or more products using
        that bugtracker.
        """
        super(
            BugAlsoAffectsProductWithProductCreationView, self).setUpFields()
        self._loadProductsUsingBugTracker()
        if self.existing_products is None or len(self.existing_products) < 1:
            # No need to setup any extra fields.
            return

        terms = []
        for product in self.existing_products:
            terms.append(SimpleTerm(product, product.name, product.title))
        existing_product = form.FormField(
            Choice(__name__='existing_product',
                   title=_("Existing project"), required=True,
                   vocabulary=SimpleVocabulary(terms)))
        self.form_fields += form.Fields(existing_product)
        if 'field.existing_product' not in self.request.form:
            # This is the first time the form is being submitted, so the
            # request doesn't contain a value for the existing_product
            # widget and thus we'll end up rendering an error message around
            # said widget unless we sneak a value for it in our request.
            self.request.form['field.existing_product'] = terms[0].token