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)
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 <<a href="%s">%s</a>>', 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)
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
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
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
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
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
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)
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)
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 & last: <b><i>some text</i></b></li>', outer.escapedtext)
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
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 & last: <b><i>some text</i></b></li>', outer.escapedtext)
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
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 & last: <b><i>some text</i></b></li>', outer.escapedtext)
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
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)
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
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))
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()
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)
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
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)
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
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
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))
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
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
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)
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
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
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)
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
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))
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 <i>escaped</i>!</b> ' '"& I'm too."', struct.escapedtext)
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
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)
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
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
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 <i>escaped</i>!</b> ' '"& I'm too."', struct.escapedtext)
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))
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
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)
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)
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)
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)
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))