예제 #1
0
    def main_action(self, data):
        """Create the new bug task, confirming if necessary."""
        bug_url = data.get('bug_url', '')
        target = self.getTarget(data)

        if (not bug_url and
            not self.request.get('ignore_missing_remote_bug') and
            target.bug_tracking_usage != ServiceUsage.LAUNCHPAD):
            # We have no URL for the remote bug and the target does not use
            # Launchpad for bug tracking, so we warn the user this is not
            # optimal and ask for his confirmation.

            # Add a hidden field to fool LaunchpadFormView into thinking we
            # submitted the action it expected when in fact we're submiting
            # something else to indicate the user has confirmed.
            confirm_button = structured(
                '<input type="hidden" name="%s" value="1" />'
                '<input style="font-size: smaller" type="submit"'
                ' value="Add Anyway" name="ignore_missing_remote_bug" />',
                self.continue_action.__name__)
            self.notifications.append(structured(
                dedent("""
                    %s doesn't use Launchpad as its bug tracker. Without a bug
                    URL to watch, the %s status will not update automatically.
                    %s"""),
                target.displayname, target.displayname,
                confirm_button).escapedtext)
            return None
        # Create the task.
        return super(DistroBugTaskCreationStep, self).main_action(data)
예제 #2
0
    def toTerm(self, watch):
        if watch.url.startswith('mailto:'):
            user = getUtility(ILaunchBag).user
            if user is None:
                title = html_escape(
                    FormattersAPI(watch.bugtracker.title).obfuscate_email())
            else:
                url = watch.url
                if url in watch.bugtracker.title:
                    title = html_escape(watch.bugtracker.title).replace(
                        html_escape(url),
                        structured(
                            '<a href="%s">%s</a>', url, url).escapedtext)
                else:
                    title = structured(
                        '%s &lt;<a href="%s">%s</a>&gt;',
                        watch.bugtracker.title, url, url[7:]).escapedtext
        else:
            title = structured(
                '%s <a href="%s">#%s</a>',
                watch.bugtracker.title, watch.url,
                watch.remotebug).escapedtext

        # title is already HTML-escaped.
        return SimpleTerm(watch, watch.id, title)
예제 #3
0
    def heading(self):
        """Return the heading text for the page.

        If the view provides `IEditableContextTitle` then the top heading is
        rendered from the view's `title_edit_widget` and is generally
        editable.

        Otherwise, if the context provides `IRootContext` then we return an
        H1, else an H2.
        """
        # Check the view; is the title editable?
        if IEditableContextTitle.providedBy(self._view):
            return self._view.title_edit_widget()
        # The title is static, but only the context's index view gets an H1.
        if IMajorHeadingView.providedBy(self._view):
            heading = structured('h1')
        else:
            heading = structured('h2')
        # If there is actually no root context, then it's a top-level
        # context-less page so Launchpad.net is shown as the branding.
        if self.root_context is None:
            title = 'Launchpad.net'
        else:
            title = self.root_context.title
        # For non-editable titles, generate the static heading.
        return structured("<%(heading)s>%(title)s</%(heading)s>",
                          heading=heading,
                          title=title).escapedtext
예제 #4
0
    def heading(self):
        """Return the heading text for the page.

        If the view provides `IEditableContextTitle` then the top heading is
        rendered from the view's `title_edit_widget` and is generally
        editable.

        Otherwise, if the context provides `IRootContext` then we return an
        H1, else an H2.
        """
        # Check the view; is the title editable?
        if IEditableContextTitle.providedBy(self._view):
            return self._view.title_edit_widget()
        # The title is static, but only the context's index view gets an H1.
        if IMajorHeadingView.providedBy(self._view):
            heading = structured("h1")
        else:
            heading = structured("h2")
        # If there is actually no root context, then it's a top-level
        # context-less page so Launchpad.net is shown as the branding.
        if self.root_context is None:
            title = "Launchpad.net"
        else:
            title = self.root_context.title
        # For non-editable titles, generate the static heading.
        return structured("<%(heading)s>%(title)s</%(heading)s>", heading=heading, title=title).escapedtext
예제 #5
0
    def guide_links(self):
        """Links to translation group/team guidelines, if available.

        If no guidelines are available, returns the empty string.
        """
        group_guide = self.translation_group_guide
        team_guide = self.translation_team_guide
        if group_guide is None and team_guide is None:
            return ""

        links = []
        if group_guide is not None:
            links.append(
                structured(
                    '<a class="style-guide-url" href="%s">%s instructions</a>',
                    group_guide, self.translation_group.title).escapedtext)

        if team_guide is not None:
            if group_guide is None:
                # Use team's full name.
                name = self.translator.displayname
            else:
                # Full team name may get tedious after we just named the
                # group.  Just use the language name.
                name = self.context.language.englishname
            links.append(
                structured(
                    '<a class="style-guide-url" href="%s"> %s guidelines</a>',
                    team_guide, name).escapedtext)

        text = ' and '.join(links).rstrip()

        return "Before translating, be sure to go through %s." % text
예제 #6
0
def channels_validator(channels):
    """Return True if the channels in a list are valid, or raise a
    LaunchpadValidationError.
    """
    tracks = set()
    branches = set()
    for name in channels:
        try:
            track, risk, branch = split_channel_name(name)
        except ValueError:
            message = _(
                "Invalid channel name '${name}'. Channel names must be of the "
                "form 'track/risk/branch', 'track/risk', 'risk/branch', or "
                "'risk'.",
                mapping={'name': html_escape(name)})
            raise LaunchpadValidationError(structured(message))
        tracks.add(track)
        branches.add(branch)

    # XXX cjwatson 2018-05-08: These are slightly arbitrary restrictions,
    # but they make the UI much simpler.

    if len(tracks) != 1:
        message = _("Channels must belong to the same track.")
        raise LaunchpadValidationError(structured(message))

    if len(branches) != 1:
        message = _("Channels must belong to the same branch.")
        raise LaunchpadValidationError(structured(message))

    return True
예제 #7
0
    def _getUnsubscribeNotification(self, user, unsubed_dupes):
        """Construct and return the unsubscribe-from-bug feedback message.

        :user: The IPerson or ITeam that was unsubscribed from the bug.
        :unsubed_dupes: The list of IBugs that are dupes from which the
                        user was unsubscribed.
        """
        current_bug = self.context.bug
        current_user = self.user
        unsubed_dupes_msg_fragment = self._getUnsubscribedDupesMsgFragment(
            unsubed_dupes)

        if user == current_user:
            # Consider that the current user may have been "locked out"
            # of a bug if they unsubscribed themselves from a private
            # bug!
            if check_permission("launchpad.View", current_bug):
                # The user still has permission to see this bug, so no
                # special-casing needed.
                return structured(
                    "You have been unsubscribed from bug %s%s.",
                    current_bug.id, unsubed_dupes_msg_fragment).escapedtext
            else:
                return structured(
                    "You have been unsubscribed from bug %s%s. You no "
                    "longer have access to this private bug.",
                    current_bug.id, unsubed_dupes_msg_fragment).escapedtext
        else:
            return structured(
                "%s has been unsubscribed from bug %s%s.",
                user.displayname, current_bug.id,
                unsubed_dupes_msg_fragment).escapedtext
예제 #8
0
    def _getUnsubscribedDupesMsgFragment(self, unsubed_dupes):
        """Return the duplicates fragment of the unsubscription notification.

        This piece lists the duplicates from which the user was
        unsubscribed.
        """
        if not unsubed_dupes:
            return ""

        dupe_links = []
        for unsubed_dupe in unsubed_dupes:
            dupe_links.append(structured(
                '<a href="%s" title="%s">#%s</a>',
                canonical_url(unsubed_dupe), unsubed_dupe.title,
                unsubed_dupe.id))
        # We can't current join structured()s, so do it manually.
        dupe_links_string = structured(
            ", ".join(['%s'] * len(dupe_links)), *dupe_links)

        num_dupes = len(unsubed_dupes)
        if num_dupes > 1:
            plural_suffix = "s"
        else:
            plural_suffix = ""

        return structured(
            " and %(num_dupes)d duplicate%(plural_suffix)s "
            "(%(dupe_links_string)s)",
            num_dupes=num_dupes, plural_suffix=plural_suffix,
            dupe_links_string=dupe_links_string)
예제 #9
0
    def _getUnsubscribeNotification(self, user, unsubed_dupes):
        """Construct and return the unsubscribe-from-bug feedback message.

        :user: The IPerson or ITeam that was unsubscribed from the bug.
        :unsubed_dupes: The list of IBugs that are dupes from which the
                        user was unsubscribed.
        """
        current_bug = self.context.bug
        current_user = self.user
        unsubed_dupes_msg_fragment = self._getUnsubscribedDupesMsgFragment(
            unsubed_dupes)

        if user == current_user:
            # Consider that the current user may have been "locked out"
            # of a bug if they unsubscribed themselves from a private
            # bug!
            if check_permission("launchpad.View", current_bug):
                # The user still has permission to see this bug, so no
                # special-casing needed.
                return structured(
                    "You have been unsubscribed from bug %s%s.",
                    current_bug.id, unsubed_dupes_msg_fragment).escapedtext
            else:
                return structured(
                    "You have been unsubscribed from bug %s%s. You no "
                    "longer have access to this private bug.",
                    current_bug.id, unsubed_dupes_msg_fragment).escapedtext
        else:
            return structured(
                "%s has been unsubscribed from bug %s%s.",
                user.displayname, current_bug.id,
                unsubed_dupes_msg_fragment).escapedtext
예제 #10
0
    def _getUnsubscribedDupesMsgFragment(self, unsubed_dupes):
        """Return the duplicates fragment of the unsubscription notification.

        This piece lists the duplicates from which the user was
        unsubscribed.
        """
        if not unsubed_dupes:
            return ""

        dupe_links = []
        for unsubed_dupe in unsubed_dupes:
            dupe_links.append(structured(
                '<a href="%s" title="%s">#%s</a>',
                canonical_url(unsubed_dupe), unsubed_dupe.title,
                unsubed_dupe.id))
        # We can't current join structured()s, so do it manually.
        dupe_links_string = structured(
            ", ".join(['%s'] * len(dupe_links)), *dupe_links)

        num_dupes = len(unsubed_dupes)
        if num_dupes > 1:
            plural_suffix = "s"
        else:
            plural_suffix = ""

        return structured(
            " and %(num_dupes)s duplicate%(plural_suffix)s "
            "(%(dupe_links_string)s)",
            num_dupes=num_dupes, plural_suffix=plural_suffix,
            dupe_links_string=dupe_links_string)
예제 #11
0
 def test_kwargs(self):
     # Keyword args work too.
     inner = structured('<b>%s</b>', '<i>some text</i>')
     outer = structured(
         '<li>%(capt)s: %(body)s</li>', capt='First & last', body=inner)
     self.assertEqual(
         '<li>First &amp; last: <b>&lt;i&gt;some text&lt;/i&gt;</b></li>',
         outer.escapedtext)
예제 #12
0
 def icons_and_name(self):
     """Icon list and name, linked to changes file if appropriate."""
     iconlist_id = "queue%d-iconlist" % self.id
     icons = self.composeIconList()
     icon_string = structured('\n'.join(['%s'] * len(icons)), *icons)
     link = self.composeNameAndChangesLink()
     return structured("""<div id="%s"> %s %s (%s)</div>""", iconlist_id,
                       icon_string, link, self.displayarchs).escapedtext
예제 #13
0
 def test_structured_args_passed_through(self):
     # If an IStructuredString is used as an argument, its
     # .escapedtext is included verbatim. Other arguments are still
     # escaped.
     inner = structured('<b>%s</b>', '<i>some text</i>')
     outer = structured('<li>%s: %s</li>', 'First & last', inner)
     self.assertEqual(
         '<li>First &amp; last: <b>&lt;i&gt;some text&lt;/i&gt;</b></li>',
         outer.escapedtext)
예제 #14
0
 def test_structured_args_passed_through(self):
     # If an IStructuredString is used as an argument, its
     # .escapedtext is included verbatim. Other arguments are still
     # escaped.
     inner = structured('<b>%s</b>', '<i>some text</i>')
     outer = structured('<li>%s: %s</li>', 'First & last', inner)
     self.assertEqual(
         '<li>First &amp; last: <b>&lt;i&gt;some text&lt;/i&gt;</b></li>',
         outer.escapedtext)
예제 #15
0
 def icons_and_name(self):
     """Icon list and name, linked to changes file if appropriate."""
     iconlist_id = "queue%d-iconlist" % self.id
     icons = self.composeIconList()
     icon_string = structured('\n'.join(['%s'] * len(icons)), *icons)
     link = self.composeNameAndChangesLink()
     return structured(
         """<div id="%s"> %s %s (%s)</div>""",
         iconlist_id, icon_string, link, self.displayarchs).escapedtext
예제 #16
0
 def test_kwargs(self):
     # Keyword args work too.
     inner = structured('<b>%s</b>', '<i>some text</i>')
     outer = structured('<li>%(capt)s: %(body)s</li>',
                        capt='First & last',
                        body=inner)
     self.assertEqual(
         '<li>First &amp; last: <b>&lt;i&gt;some text&lt;/i&gt;</b></li>',
         outer.escapedtext)
예제 #17
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)
            basic_file_text = get_plural_text(file_count,
                                              _('%d file modified'),
                                              _('%d files modified'))
            basic_file_text = basic_file_text % file_count
            diffstat_text = '<br/>'.join(
                structured('%s (+%s/-%s)', path, added, removed).escapedtext
                for path, (added, removed) in sorted(diffstat.items()))
            file_text = (
                '<div class="collapsible"><span>%s</span><div>%s</div></div>' %
                (basic_file_text, diffstat_text))

        args = {
            'line_count': _('%s lines') % diff.diff_lines_count,
            '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 structured(
                '<span class="empty-diff">'
                '%(line_count)s</span>', **args).escapedtext
        else:
            return structured(
                '<a href="%(url)s" class="diff-link">'
                '%(line_count)s%(count_text)s%(conflict_text)s'
                '</a>', **args).escapedtext + file_text
예제 #18
0
    def format_ssdiff(self):
        """Format the string as a side-by-side diff."""
        # Trim off trailing carriage returns.
        text = self._stringtoformat.rstrip('\n')
        if not text:
            return text
        result = ['<table class="diff ssdiff">']

        queue_orig = []
        queue_mod = []
        for css_class, row, orig_row, mod_row, line in parse_diff(text):
            if orig_row is not None and mod_row is None:
                # Text line only in original version.  Queue until we find a
                # common or non-text line.
                queue_orig.append((row, orig_row, line[1:]))
            elif mod_row is not None and orig_row is None:
                # Text line only in modified version.  Queue until we find a
                # common or non-text line.
                queue_mod.append((row, mod_row, line[1:]))
            else:
                # Common or non-text line; emit any queued differences, then
                # this line.
                if queue_orig or queue_mod:
                    self._ssdiff_emit_queued_lines(result, queue_orig,
                                                   queue_mod)
                    queue_orig = []
                    queue_mod = []
                if orig_row is None and mod_row is None:
                    # Non-text line.
                    cells = [
                        structured('<td class="%s" colspan="4">%s</td>',
                                   css_class, line).escapedtext,
                    ]
                else:
                    # Line common to both versions.
                    if line.startswith(' '):
                        line = line[1:]
                    cells = [
                        '<td class="ss-line-no unselectable">%s</td>' %
                        orig_row,
                        structured('<td class="text">%s</td>',
                                   line).escapedtext,
                        '<td class="ss-line-no unselectable">%s</td>' %
                        mod_row,
                        structured('<td class="text">%s</td>',
                                   line).escapedtext,
                    ]
                self._ssdiff_emit_line(result, row, cells)
        if queue_orig or queue_mod:
            self._ssdiff_emit_queued_lines(result, queue_orig, queue_mod)

        result.append('</table>')
        return ''.join(result)
예제 #19
0
    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
예제 #20
0
    def initialize(self):
        response = self.request.response

        # Add some notifications
        for count in range(1, 3):
            response.addDebugNotification(
                structured('Debug notification <b>%d</b>' % count))
            response.addInfoNotification(
                structured('Info notification <b>%d</b>' % count))
            response.addWarningNotification(
                structured('Warning notification <b>%d</b>' %count))
            response.addErrorNotification(
                structured('Error notification <b>%d</b>' % count))
예제 #21
0
 def logo(self):
     """Return the logo image for the top header breadcrumb's context."""
     logo_context = (
         self.heading_breadcrumbs[0].context if self.heading_breadcrumbs
         else None)
     adapter = queryAdapter(logo_context, IPathAdapter, 'image')
     if logo_context is not None:
         return structured(
             '<a href="%s">%s</a>',
             canonical_url(logo_context, rootsite='mainsite'),
             structured(adapter.logo())).escapedtext
     else:
         return adapter.logo()
예제 #22
0
    def validateStep(self, data):
        """Check that

        1. there's no bug_url if the target uses malone;
        2. there is a package with the given name;
        3. it's possible to create a new task for the given package/distro.
        """
        target = self.getTarget(data)
        bug_url = data.get('bug_url')
        if bug_url and target.bug_tracking_usage == ServiceUsage.LAUNCHPAD:
            self.addError(
                "Bug watches can not be added for %s, as it uses Launchpad"
                " as its official bug tracker. Alternatives are to add a"
                " watch for another project, or a comment containing a"
                " URL to the related bug report." % target.displayname)

        distribution = data.get('distribution')
        sourcepackagename = data.get('sourcepackagename')
        entered_package = self.request.form.get(
            self.widgets['sourcepackagename'].name)
        if sourcepackagename is None and entered_package:
            # The entered package doesn't exist.
            if distribution.has_published_binaries:
                binary_tracking = ''
            else:
                binary_tracking = structured(
                    ' Launchpad does not track binary package names '
                    'in %s.', distribution.displayname)
            error = structured(
                'There is no package in %s named "%s".%s',
                distribution.displayname, entered_package,
                binary_tracking)
            self.setFieldError('sourcepackagename', error)
        elif not IDistributionSourcePackage.providedBy(sourcepackagename):
            try:
                target = distribution
                if sourcepackagename:
                    target = target.getSourcePackage(sourcepackagename)
                # The validity of the source package has already been checked
                # by the bug target widget.
                validate_new_target(
                    self.context.bug, target, check_source_package=False)
                if sourcepackagename:
                    data['sourcepackagename'] = target
            except IllegalTarget as e:
                if sourcepackagename:
                    self.setFieldError('sourcepackagename', e[0])
                else:
                    self.setFieldError('distribution', e[0])

        super(DistroBugTaskCreationStep, self).validateStep(data)
예제 #23
0
    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
예제 #24
0
 def composeIcon(self, alt, icon, title=None):
     """Compose an icon for the package's icon list."""
     # These should really be sprites!
     if title is None:
         title = alt
     return structured('<img alt="[%s]" src="/@@/%s" title="%s" />', alt,
                       icon, title)
예제 #25
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
예제 #26
0
 def recipes_link(self):
     """A link to recipes for this reference."""
     count = self.context.recipes.count()
     if count == 0:
         # Nothing to link to.
         return 'No recipes using this branch.'
     elif count == 1:
         # Link to the single recipe.
         return structured(
             '<a href="%s">1 recipe</a> using this branch.',
             canonical_url(self.context.recipes.one())).escapedtext
     else:
         # Link to a recipe listing.
         return structured(
             '<a href="+recipes">%s recipes</a> using this branch.',
             count).escapedtext
예제 #27
0
    def validateStep(self, data):
        if data.get('product'):
            try:
                validate_new_target(self.context.bug, data.get('product'))
            except IllegalTarget as e:
                self.setFieldError('product', e[0])
            return

        entered_product = self.request.form.get(self.widgets['product'].name)
        if not entered_product:
            return

        # The user has entered a product name but we couldn't find it.
        # Tell the user to search for it using the popup widget as it'll allow
        # the user to register a new product if the one he is looking for is
        # not yet registered.
        widget_link_id = self.widgets['product'].show_widget_id
        self.setFieldError(
            'product',
            structured("""
                There is no project in Launchpad named "%s". Please
                <a href="/projects"
                onclick="LPJS.use('event').Event.simulate(
                         document.getElementById('%s'), 'click');
                         return false;"
                >search for it</a> as it may be
                registered with a different name.""",
                entered_product, widget_link_id))
예제 #28
0
    def validate(self, data):
        self.language_code = data.get('language_code')
        self.language = data.get('language')
        if self.language_code is not None:
            self.language_code = self.language_code.strip()

        if not self.language_code:
            self.setFieldError('language_code', "No code was entered.")
            return

        if not check_code(self.language_code):
            self.setFieldError('language_code', "Invalid language code.")
            return

        existing_code = self.context.getCustomLanguageCode(self.language_code)
        if existing_code is not None:
            if existing_code.language != self.language:
                self.setFieldError(
                    'language_code',
                    structured(
                        "There already is a custom language code '%s'.",
                            self.language_code))
                return
        else:
            self.create = True
예제 #29
0
    def makeBranchFromURL(self, url):
        """Make a mirrored branch for `url`.

        The product and owner of the branch are derived from information in
        the launchbag. The name of the branch is derived from the last segment
        of the URL and is guaranteed to be unique for the product.

        :param url: The URL to mirror.
        :return: An `IBranch`.
        """
        # XXX: JonathanLange 2008-12-08 spec=package-branches: This method
        # needs to be rewritten to get the sourcepackage and distroseries out
        # of the launch bag.
        url = unicode(URI(url).ensureNoSlash())
        if getUtility(IBranchLookup).getByUrl(url) is not None:
            raise AlreadyRegisteredError('Already a branch for %r' % url)
        # Make sure the URL is valid.
        IBranch['url'].validate(url)
        product = self.getProduct()
        if product is None:
            raise NoProductError("Could not find product in LaunchBag.")
        owner = self.getPerson()
        name = self.getBranchNameFromURL(url)
        namespace = get_branch_namespace(person=owner, product=product)
        branch = namespace.createBranchWithPrefix(BranchType.MIRRORED,
                                                  name,
                                                  owner,
                                                  url=url)
        branch.requestMirror()
        self.request.response.addNotification(
            structured('Registered %s' %
                       BranchFormatterAPI(branch).link(None)))
        return branch
예제 #30
0
 def explanation(self):
     return structured(
         "Packages that are listed here are those that have been added to "
         "the specific packages in %s that were used to create %s. "
         "They are listed here so you can consider including them in %s.",
         self.getParentName(), self.context.displayname,
         self.context.displayname)
예제 #31
0
    def makeBranchFromURL(self, url):
        """Make a mirrored branch for `url`.

        The product and owner of the branch are derived from information in
        the launchbag. The name of the branch is derived from the last segment
        of the URL and is guaranteed to be unique for the product.

        :param url: The URL to mirror.
        :return: An `IBranch`.
        """
        # XXX: JonathanLange 2008-12-08 spec=package-branches: This method
        # needs to be rewritten to get the sourcepackage and distroseries out
        # of the launch bag.
        url = unicode(URI(url).ensureNoSlash())
        if getUtility(IBranchLookup).getByUrl(url) is not None:
            raise AlreadyRegisteredError('Already a branch for %r' % url)
        # Make sure the URL is valid.
        IBranch['url'].validate(url)
        product = self.getProduct()
        if product is None:
            raise NoProductError("Could not find product in LaunchBag.")
        owner = self.getPerson()
        name = self.getBranchNameFromURL(url)
        namespace = get_branch_namespace(person=owner, product=product)
        branch = namespace.createBranchWithPrefix(
            BranchType.MIRRORED, name, owner, url=url)
        branch.requestMirror()
        self.request.response.addNotification(
            structured('Registered %s' %
                       BranchFormatterAPI(branch).link(None)))
        return branch
예제 #32
0
    def _subscription_field(self):
        subscription_terms = []
        self_subscribed = False
        is_really_muted = self.user_is_muted
        if is_really_muted:
            subscription_terms.insert(0, self._unmute_user_term)
        for person in self._subscribers_for_current_user:
            if person.id == self.user.id:
                if is_really_muted:
                    # We've already added the unmute option.
                    continue
                else:
                    if self.user_is_subscribed_directly:
                        subscription_terms.append(
                            self._update_subscription_term)
                    subscription_terms.insert(
                        0, self._unsubscribe_current_user_term)
                    self_subscribed = True
            else:
                subscription_terms.append(
                    SimpleTerm(
                        person, person.name,
                        structured(
                            'unsubscribe <a href="%s">%s</a> from this bug',
                            canonical_url(person),
                            person.displayname).escapedtext))
        if not self_subscribed:
            if not is_really_muted:
                subscription_terms.insert(
                    0,
                    SimpleTerm(self.user, self.user.name,
                               'subscribe me to this bug'))
            elif not self.user_is_subscribed_directly:
                subscription_terms.insert(
                    0,
                    SimpleTerm(
                        'update-subscription', 'update-subscription',
                        'unmute bug mail from this bug and subscribe me to '
                        'this bug'))

        # Add punctuation to the list of terms.
        if len(subscription_terms) > 1:
            for term in subscription_terms[:-1]:
                term.title += ','
            subscription_terms[-2].title += ' or'
            subscription_terms[-1].title += '.'

        subscription_vocabulary = SimpleVocabulary(subscription_terms)
        if self.user_is_subscribed_directly or self.user_is_muted:
            default_subscription_value = self._update_subscription_term.value
        else:
            default_subscription_value = (
                subscription_vocabulary.getTermByToken(self.user.name).value)

        subscription_field = Choice(__name__='subscription',
                                    title=_("Subscription options"),
                                    vocabulary=subscription_vocabulary,
                                    required=True,
                                    default=default_subscription_value)
        return subscription_field
예제 #33
0
 def composeIcon(self, alt, icon, title=None):
     """Compose an icon for the package's icon list."""
     # These should really be sprites!
     if title is None:
         title = alt
     return structured(
         '<img alt="[%s]" src="/@@/%s" title="%s" />', alt, icon, title)
예제 #34
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
예제 #35
0
 def delete_action(self, action, data):
     libraryfile_url = ProxiedLibraryFileAlias(
         self.context.libraryfile, self.context).http_url
     self.request.response.addInfoNotification(structured(
         'Attachment "<a href="%(url)s">%(name)s</a>" has been deleted.',
         url=libraryfile_url, name=self.context.title))
     self.context.removeFromBug(user=self.user)
    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))
예제 #37
0
 def delete_action(self, action, data):
     libraryfile_url = ProxiedLibraryFileAlias(
         self.context.libraryfile, self.context).http_url
     self.request.response.addInfoNotification(structured(
         'Attachment "<a href="%(url)s">%(name)s</a>" has been deleted.',
         url=libraryfile_url, name=self.context.title))
     self.context.removeFromBug(user=self.user)
예제 #38
0
 def explanation(self):
     return structured(
         "Packages that are listed here are those that have been added to "
         "the specific packages in %s that were used to create %s. "
         "They are listed here so you can consider including them in %s.",
         self.getParentName(),
         self.context.displayname,
         self.context.displayname)
예제 #39
0
 def test_escapes_args(self):
     # Normal string arguments are escaped before they're inserted
     # into the formatted string.
     struct = structured('<b>%s</b> %s', 'I am <i>escaped</i>!',
                         '"& I\'m too."')
     self.assertEqual(
         '<b>I am &lt;i&gt;escaped&lt;/i&gt;!</b> '
         '&quot;&amp; I&#x27;m too.&quot;', struct.escapedtext)
예제 #40
0
    def _subscription_field(self):
        subscription_terms = []
        self_subscribed = False
        is_really_muted = self.user_is_muted
        if is_really_muted:
            subscription_terms.insert(0, self._unmute_user_term)
        for person in self._subscribers_for_current_user:
            if person.id == self.user.id:
                if is_really_muted:
                    # We've already added the unmute option.
                    continue
                else:
                    if self.user_is_subscribed_directly:
                        subscription_terms.append(
                            self._update_subscription_term)
                    subscription_terms.insert(
                        0, self._unsubscribe_current_user_term)
                    self_subscribed = True
            else:
                subscription_terms.append(
                    SimpleTerm(
                        person, person.name,
                        structured(
                            'unsubscribe <a href="%s">%s</a> from this bug',
                            canonical_url(person),
                            person.displayname).escapedtext))
        if not self_subscribed:
            if not is_really_muted:
                subscription_terms.insert(0,
                    SimpleTerm(
                        self.user, self.user.name,
                        'subscribe me to this bug'))
            elif not self.user_is_subscribed_directly:
                subscription_terms.insert(0,
                    SimpleTerm(
                        'update-subscription', 'update-subscription',
                        'unmute bug mail from this bug and subscribe me to '
                        'this bug'))

        # Add punctuation to the list of terms.
        if len(subscription_terms) > 1:
            for term in subscription_terms[:-1]:
                term.title += ','
            subscription_terms[-2].title += ' or'
            subscription_terms[-1].title += '.'

        subscription_vocabulary = SimpleVocabulary(subscription_terms)
        if self.user_is_subscribed_directly or self.user_is_muted:
            default_subscription_value = self._update_subscription_term.value
        else:
            default_subscription_value = (
                subscription_vocabulary.getTermByToken(self.user.name).value)

        subscription_field = Choice(
            __name__='subscription', title=_("Subscription options"),
            vocabulary=subscription_vocabulary, required=True,
            default=default_subscription_value)
        return subscription_field
예제 #41
0
 def composeNameAndChangesLink(self):
     """Compose HTML: upload name and link to changes file."""
     if self.changesfile is None:
         return self.displayname
     else:
         return structured(
             '<a href="%s" title="Changes file for %s">%s</a>',
             self.changesfile.http_url, self.displayname,
             self.displayname)
예제 #42
0
 def recipe_build_details(self):
     """Return a linkified string containing details about a
     SourcePackageRecipeBuild.
     """
     sprb = self.context.sourcepackagerelease.source_package_recipe_build
     if sprb is not None:
         if sprb.recipe is None:
             recipe = "deleted recipe"
         else:
             recipe = structured('recipe <a href="%s">%s</a>', canonical_url(sprb.recipe), sprb.recipe.name)
         return structured(
             '<a href="%s">Built</a> by %s for <a href="%s">%s</a>',
             canonical_url(sprb),
             recipe,
             canonical_url(sprb.requester),
             sprb.requester.displayname,
         ).escapedtext
     return None
예제 #43
0
 def recipe_build_details(self):
     """Return a linkified string containing details about a
     SourcePackageRecipeBuild.
     """
     sprb = self.context.sourcepackagerelease.source_package_recipe_build
     if sprb is not None:
         if sprb.recipe is None:
             recipe = 'deleted recipe'
         else:
             recipe = structured(
                 'recipe <a href="%s">%s</a>',
                 canonical_url(sprb.recipe), sprb.recipe.name)
         return structured(
             '<a href="%s">Built</a> by %s for <a href="%s">%s</a>',
                 canonical_url(sprb), recipe,
                 canonical_url(sprb.requester),
                 sprb.requester.displayname).escapedtext
     return None
예제 #44
0
 def test_escapes_args(self):
     # Normal string arguments are escaped before they're inserted
     # into the formatted string.
     struct = structured(
         '<b>%s</b> %s', 'I am <i>escaped</i>!', '"& I\'m too."')
     self.assertEqual(
         '<b>I am &lt;i&gt;escaped&lt;/i&gt;!</b> '
         '&quot;&amp; I&#x27;m too.&quot;',
         struct.escapedtext)
예제 #45
0
 def reset_action(self, action, data):
     bug_watch = self.context
     bug_watch.reset()
     self.request.response.addInfoNotification(
         structured(
         'The <a href="%(url)s">%(bugtracker)s #%(remote_bug)s</a>'
         ' bug watch has been reset.',
         url=bug_watch.url, bugtracker=bug_watch.bugtracker.name,
         remote_bug=bug_watch.remotebug))
예제 #46
0
 def canDeleteAction(self, action):
     """Is the delete action available."""
     if self.context.is_development_focus:
         self.addError(self.development_focus_message)
     if self.has_linked_packages:
         self.addError(structured(self.linked_packages_message))
     if self.has_translations:
         self.addError(self.translations_message)
     return self.can_delete
예제 #47
0
 def icon_link(self, id, icon, url, text, hidden):
     """The HTML link to a configuration page."""
     if hidden:
         css_class = 'sprite %s action-icon hidden' % icon
     else:
         css_class = 'sprite %s action-icon' % icon
     return structured(
         '<a id="%s" class="%s" href="%s">%s</a>',
         id, css_class, url, text)
예제 #48
0
def get_series_branch_error(product, branch):
    """Check if the given branch is suitable for the given product.

    Returns an HTML error message on error, and None otherwise.
    """
    if branch.product != product:
        return structured(
            '<a href="%s">%s</a> is not a branch of <a href="%s">%s</a>.',
            canonical_url(branch), branch.unique_name, canonical_url(product),
            product.displayname)
예제 #49
0
 def change_action(self, action, data):
     """Redirect to the target page with a success message."""
     self.updateContextFromData(data)
     if self.context.bug_supervisor is None:
         message = (
             "Successfully cleared the bug supervisor. "
             "You can set the bug supervisor again at any time.")
     else:
         message = structured('Bug supervisor privilege granted.')
     self.request.response.addNotification(message)
예제 #50
0
 def delete_action(self, action, data):
     bugwatch = self.context
     # Build the notification first, whilst we still have the data.
     notification_message = structured(
         'The <a href="%(url)s">%(bugtracker)s #%(remote_bug)s</a>'
         ' bug watch has been deleted.',
         url=bugwatch.url, bugtracker=bugwatch.bugtracker.name,
         remote_bug=bugwatch.remotebug)
     bugwatch.bug.removeWatch(bugwatch, self.user)
     self.request.response.addInfoNotification(notification_message)
예제 #51
0
 def reschedule_action(self, action, data):
     """Schedule the current bug watch for immediate checking."""
     bugwatch = self.context
     bugwatch.setNextCheck(UTC_NOW)
     self.request.response.addInfoNotification(
         structured(
             'The <a href="%(url)s">%(bugtracker)s #%(remote_bug)s</a> '
             'bug watch has been scheduled for immediate checking.',
             url=bugwatch.url, bugtracker=bugwatch.bugtracker.name,
             remote_bug=bugwatch.remotebug))