class CodeImportSetView(LaunchpadView): """The default view for `ICodeImportSet`. We present the CodeImportSet as a list of all imports. """ page_title = 'Code Imports' def initialize(self): """See `LaunchpadView.initialize`.""" review_status_field = copy_field( ICodeImport['review_status'], required=False, default=None) self.review_status_widget = CustomWidgetFactory(DropdownWidgetWithAny) setUpWidget(self, 'review_status', review_status_field, IInputWidget) rcs_type_field = copy_field( ICodeImport['rcs_type'], required=False, default=None) self.rcs_type_widget = CustomWidgetFactory(DropdownWidgetWithAny) setUpWidget(self, 'rcs_type', rcs_type_field, IInputWidget) # status should be None if either (a) there were no query arguments # supplied, i.e. the user browsed directly to this page (this is when # hasValidInput returns False) or (b) the user chose 'Any' in the # status widget (this is when hasValidInput returns True but # getInputValue returns None). review_status = None if self.review_status_widget.hasValidInput(): review_status = self.review_status_widget.getInputValue() # Similar for 'type' rcs_type = None if self.rcs_type_widget.hasValidInput(): rcs_type = self.rcs_type_widget.getInputValue() imports = self.context.search( review_status=review_status, rcs_type=rcs_type) self.batchnav = BatchNavigator(imports, self.request)
class LaunchpadTargetWidget(BrowserWidget, InputWidget): """Widget for selecting a product, distribution or package target.""" implements(IAlwaysSubmittedWidget, IMultiLineWidgetLayout, IInputWidget) template = ViewPageTemplateFile('templates/launchpad-target.pt') default_option = "package" _widgets_set_up = False def getDistributionVocabulary(self): return 'Distribution' def getProductVocabulary(self): return 'Product' def setUpSubWidgets(self): if self._widgets_set_up: return fields = [ Choice( __name__='product', title=u'Project', required=True, vocabulary=self.getProductVocabulary()), Choice( __name__='distribution', title=u"Distribution", required=True, vocabulary=self.getDistributionVocabulary(), default=getUtility(ILaunchpadCelebrities).ubuntu), Choice( __name__='package', title=u"Package", required=False, vocabulary='BinaryAndSourcePackageName'), ] self.distribution_widget = CustomWidgetFactory( LaunchpadDropdownWidget) for field in fields: setUpWidget( self, field.__name__, field, IInputWidget, prefix=self.name) self._widgets_set_up = True def setUpOptions(self): """Set up options to be rendered.""" self.options = {} for option in ['package', 'product']: attributes = dict( type='radio', name=self.name, value=option, id='%s.option.%s' % (self.name, option)) if self.request.form_ng.getOne( self.name, self.default_option) == option: attributes['checked'] = 'checked' self.options[option] = renderElement('input', **attributes) self.package_widget.onKeyPress = ( "selectWidget('%s.option.package', event)" % self.name) self.product_widget.onKeyPress = ( "selectWidget('%s.option.product', event)" % self.name) def hasInput(self): return self.name in self.request.form def hasValidInput(self): """See zope.formlib.interfaces.IInputWidget.""" try: self.getInputValue() return True except (InputErrors, UnexpectedFormData): return False def getInputValue(self): """See zope.formlib.interfaces.IInputWidget.""" self.setUpSubWidgets() form_value = self.request.form_ng.getOne(self.name) if form_value == 'product': try: return self.product_widget.getInputValue() except MissingInputError: self._error = WidgetInputError( self.name, self.label, LaunchpadValidationError('Please enter a project name')) raise self._error except ConversionError: entered_name = self.request.form_ng.getOne( "%s.product" % self.name) self._error = WidgetInputError( self.name, self.label, LaunchpadValidationError( "There is no project named '%s' registered in" " Launchpad" % entered_name)) raise self._error elif form_value == 'package': try: distribution = self.distribution_widget.getInputValue() except ConversionError: entered_name = self.request.form_ng.getOne( "%s.distribution" % self.name) self._error = WidgetInputError( self.name, self.label, LaunchpadValidationError( "There is no distribution named '%s' registered in" " Launchpad" % entered_name)) raise self._error if self.package_widget.hasInput(): try: package_name = self.package_widget.getInputValue() if package_name is None: return distribution if IDistributionSourcePackage.providedBy(package_name): dsp = package_name else: source_name = ( distribution.guessPublishedSourcePackageName( package_name.name)) dsp = distribution.getSourcePackage(source_name) except (ConversionError, NotFoundError): entered_name = self.request.form_ng.getOne( '%s.package' % self.name) self._error = WidgetInputError( self.name, self.label, LaunchpadValidationError( "There is no package named '%s' published in %s." % (entered_name, distribution.displayname))) raise self._error return dsp else: return distribution else: raise UnexpectedFormData("No valid option was selected.") def setRenderedValue(self, value): """See IWidget.""" self.setUpSubWidgets() if IProduct.providedBy(value): self.default_option = 'product' self.product_widget.setRenderedValue(value) elif IDistribution.providedBy(value): self.default_option = 'package' self.distribution_widget.setRenderedValue(value) elif IDistributionSourcePackage.providedBy(value): self.default_option = 'package' self.distribution_widget.setRenderedValue(value.distribution) self.package_widget.setRenderedValue(value.sourcepackagename) else: raise AssertionError('Not a valid value: %r' % value) def __call__(self): """See zope.formlib.interfaces.IBrowserWidget.""" self.setUpSubWidgets() self.setUpOptions() return self.template()
class BugTaskBugWatchWidget(RadioWidget): """A widget for linking a bug watch to a bug task.""" def __init__(self, field, vocabulary, request): RadioWidget.__init__(self, field, vocabulary, request) self.url_widget = CustomWidgetFactory(URIWidget) setUpWidget(self, 'url', BugWatchEditForm['url'], IInputWidget, context=field.context) self.setUpJavascript() def setUpJavascript(self): """Set up JS to select the "new bugwatch" option automatically.""" select_js = "selectWidget('%s.%s', event)" % (self.name, self._new_bugwatch_value) self.url_widget.extra = 'onKeyPress="%s"' % select_js def setPrefix(self, prefix): RadioWidget.setPrefix(self, prefix) self.url_widget.setPrefix(prefix) self.setUpJavascript() _messageNoValue = "None, the status of the bug is updated manually." _new_bugwatch_value = 'NEW' def _toFieldValue(self, form_value): """Convert the textual token to a field value. If the form value is _new_bugwatch_value, create a new bug watch, otherwise look up an existing one. """ if form_value == self._new_bugwatch_value: try: url = self.url_widget.getInputValue() bugtracker, remote_bug = getUtility( IBugWatchSet).extractBugTrackerAndBug(url) bugtask = self.context.context return bugtask.bug.addWatch(bugtracker, remote_bug, getUtility(ILaunchBag).user) except WidgetInputError as error: # Prefix the error with the widget name, since the error # will be display at the top of the page, and not right # next to the widget. raise WidgetInputError(self.context.__name__, self.label, 'Remote Bug: %s' % error.doc()) except (NoBugTrackerFound, UnrecognizedBugTrackerURL) as error: raise WidgetInputError(self.context.__name__, self.label, 'Invalid bug tracker URL.') else: return RadioWidget._toFieldValue(self, form_value) def _getFormValue(self): """Return the form value. We have to override this method in this class since the original one uses getInputValue(), which it shouldn't do. """ if not self._renderedValueSet(): return self.request.form_ng.getOne(self.name, self._missing) else: return self._toFormValue(self._data) def _div(self, cssClass, contents, **kw): """Don't render a div tag.""" return contents def _joinButtonToMessage(self, option_tag, label, input_id): """Join the input tag with the label.""" row_template = get_widget_template('bugtask-bugwatch-widget.txt') return row_template % { 'input_tag': option_tag, 'input_id': input_id, 'input_label': label } #XXX: Bjorn Tillenius 2006-04-26: # This method is mostly copied from RadioWidget.renderItems() and # modified to actually work. RadioWidget.renderItems() should be # fixed upstream so that we can override it and only do the last # part locally, the part after "# Add an option for creating...". # http://www.zope.org/Collectors/Zope3-dev/592 def renderItems(self, value): """Render the items with with the correct radio button selected.""" # XXX: Bjorn Tillenius 2006-04-26 # This works around the fact that we incorrectly gets the form # value instead of a valid field value. if value == self._missing: value = self.context.missing_value elif (isinstance(value, basestring) and value != self._new_bugwatch_value): value = self._toFieldValue(value) # check if we want to select first item, the previously selected item # or the "nothing selected" item. nothing_selected = None if (value == self.context.missing_value and getattr(self, 'firstItem', False) and len(self.vocabulary) > 0 and self.context.required): # Grab the first item from the iterator: values = [iter(self.vocabulary).next().value] elif value != self.context.missing_value: values = [value] else: # the "nothing selected" option will be checked nothing_selected = 'checked' values = [] items = self.renderItemsWithValues(values) if not self.context.required: kwargs = { 'index': None, 'text': self.translate(self._messageNoValue), 'value': '', 'name': self.name, 'cssClass': self.cssClass } if nothing_selected: option = self.renderSelectedItem(**kwargs) else: option = self.renderItem(**kwargs) items.insert(0, option) # Add an option for creating a new bug watch. option_text = ('<div>URL: %s</div>' % self.url_widget()) if value == self._new_bugwatch_value: option = self.renderSelectedItem(self._new_bugwatch_value, option_text, self._new_bugwatch_value, self.name, self.cssClass) else: option = self.renderItem(self._new_bugwatch_value, option_text, self._new_bugwatch_value, self.name, self.cssClass) items.append(option) return items def renderItem(self, index, text, value, name, cssClass): """Render an item. We override this method to use the _joinButtonToMessage method instead of the _joinButtonToMessageTemplate which doesn't have access to the id. """ id = '%s.%s' % (name, index) elem = renderElement(u'input', value=value, name=name, id=id, cssClass=cssClass, type='radio') return self._joinButtonToMessage(elem, text, input_id=id) def renderSelectedItem(self, index, text, value, name, cssClass): """Render a selected item. We override this method to use the _joinButtonToMessage method instead of the _joinButtonToMessageTemplate which doesn't have access to the id. """ id = '%s.%s' % (name, index) elem = renderElement(u'input', value=value, name=name, id=id, cssClass=cssClass, checked="checked", type='radio') return self._joinButtonToMessage(elem, text, input_id=id) def renderValue(self, value): """Render the widget with the selected value. The original renderValue separates the items with either ' ' or '<br />' which isn't suitable for us. """ rendered_items = self.renderItems(value) return renderElement('table', cssClass=self.cssClass, contents='\n'.join(rendered_items))
class StoreChannelsWidget(BrowserWidget, InputWidget): template = ViewPageTemplateFile("templates/storechannels.pt") _separator = channel_components_delimiter _default_track = 'latest' _widgets_set_up = False def __init__(self, field, value_type, request): # We don't use value_type. super(StoreChannelsWidget, self).__init__(field, request) # disable help_text for the global widget self.hint = None def setUpSubWidgets(self): if self._widgets_set_up: return fields = [ TextLine(__name__="track", title=u"Track", required=False, description=_( "Track defines a series for your software. " "If not specified, the default track ('latest') is " "assumed.")), List( __name__="risks", title=u"Risk", required=False, value_type=Choice(vocabulary="SnapStoreChannel"), description=_("Risks denote the stability of your software.")), TextLine( __name__="branch", title=u"Branch", required=False, description=_( "Branches provide users with an easy way to test bug " "fixes. They are temporary and created on demand. If " "not specified, no branch is used.")), ] self.risks_widget = CustomWidgetFactory(LabeledMultiCheckBoxWidget) for field in fields: setUpWidget(self, field.__name__, field, IInputWidget, prefix=self.name) self.risks_widget.orientation = 'horizontal' self._widgets_set_up = True @property def has_risks_vocabulary(self): risks_widget = getattr(self, 'risks_widget', None) return risks_widget and bool(risks_widget.vocabulary) def buildChannelName(self, track, risk, branch): """Return channel name composed from given track, risk, and branch.""" channel = risk if track and track != self._default_track: channel = self._separator.join((track, channel)) if branch: channel = self._separator.join((channel, branch)) return channel def splitChannelName(self, channel): """Return extracted track, risk, and branch from given channel name.""" try: track, risk, branch = split_channel_name(channel) except ValueError: raise AssertionError("Not a valid value: %r" % channel) return track, risk, branch def setRenderedValue(self, value): """See `IWidget`.""" self.setUpSubWidgets() if value: # NOTE: atm target channels must belong to the same track and # branch tracks = set() branches = set() risks = [] for channel in value: track, risk, branch = self.splitChannelName(channel) tracks.add(track) risks.append(risk) branches.add(branch) if len(tracks) != 1 or len(branches) != 1: raise AssertionError("Not a valid value: %r" % value) track = tracks.pop() self.track_widget.setRenderedValue(track) self.risks_widget.setRenderedValue(risks) branch = branches.pop() self.branch_widget.setRenderedValue(branch) else: self.track_widget.setRenderedValue(None) self.risks_widget.setRenderedValue(None) self.branch_widget.setRenderedValue(None) def hasInput(self): """See `IInputWidget`.""" return ("%s.risks" % self.name) in self.request.form def hasValidInput(self): """See `IInputWidget`.""" try: self.getInputValue() return True except (InputErrors, UnexpectedFormData): return False def getInputValue(self): """See `IInputWidget`.""" self.setUpSubWidgets() track = self.track_widget.getInputValue() risks = self.risks_widget.getInputValue() branch = self.branch_widget.getInputValue() if track and self._separator in track: error_msg = "Track name cannot include '%s'." % self._separator raise WidgetInputError(self.name, self.label, LaunchpadValidationError(error_msg)) if branch and self._separator in branch: error_msg = "Branch name cannot include '%s'." % self._separator raise WidgetInputError(self.name, self.label, LaunchpadValidationError(error_msg)) channels = [ self.buildChannelName(track, risk, branch) for risk in risks ] return channels def error(self): """See `IBrowserWidget`.""" try: if self.hasInput(): self.getInputValue() except InputErrors as error: self._error = error return super(StoreChannelsWidget, self).error() def __call__(self): """See `IBrowserWidget`.""" self.setUpSubWidgets() return self.template()
class ProductBugTrackerWidget(LaunchpadRadioWidget): """Widget for selecting a product bug tracker.""" _joinButtonToMessageTemplate = u'%s %s' template = ViewPageTemplateFile('templates/product-bug-tracker.pt') def __init__(self, field, vocabulary, request): LaunchpadRadioWidget.__init__(self, field, vocabulary, request) # Bug tracker widget. self.bugtracker = Choice(vocabulary="WebBugTracker", __name__='bugtracker') self.bugtracker_widget = CustomWidgetFactory(BugTrackerPickerWidget) setUpWidget(self, 'bugtracker', self.bugtracker, IInputWidget, prefix=self.name, value=field.context.bugtracker, context=field.context) self.bugtracker_widget.onKeyPress = ("selectWidget('%s.2', event);" % self.name) # Upstream email address field and widget. ## This is to make email address bug trackers appear ## separately from the main bug tracker list. self.upstream_email_address = StrippedTextLine( required=False, constraint=email_validator, __name__='upstream_email_address') self.upstream_email_address_widget = ( CustomWidgetFactory(StrippedTextWidget)) setUpWidget(self, 'upstream_email_address', self.upstream_email_address, IInputWidget, prefix=self.name, value='', context=self.upstream_email_address.context) ## Select the corresponding radio option automatically if ## the user starts typing. if self.upstream_email_address_widget.extra is None: self.upstream_email_address_widget.extra = '' self.upstream_email_address_widget.extra += ( ''' onkeypress="selectWidget('%s.3', event);"\n''' % self.name) def _renderItem(self, index, text, value, name, cssClass, checked=False): # This form has a custom need to render their labels separately, # because of a Firefox problem: see comment in renderItems. kw = {} if checked: kw['checked'] = 'checked' id = '%s.%s' % (name, index) elem = renderElement(u'input', value=value, name=name, id=id, cssClass=cssClass, type='radio', **kw) return '%s %s' % (elem, text) def _toFieldValue(self, form_value): if form_value == "malone": return self.context.malone_marker elif form_value == "external": return self.bugtracker_widget.getInputValue() elif form_value == "external-email": email_address = self.upstream_email_address_widget.getInputValue() if email_address is None or len(email_address) == 0: self.upstream_email_address_widget._error = ( LaunchpadValidationError('Please enter an email address.')) raise self.upstream_email_address_widget._error bugtracker = getUtility(IBugTrackerSet).ensureBugTracker( 'mailto:%s' % email_address, getUtility(ILaunchBag).user, BugTrackerType.EMAILADDRESS) return bugtracker elif form_value == "project": return None def getInputValue(self): return self._toFieldValue(self._getFormInput()) def setRenderedValue(self, value): self._data = value if value is not self.context.malone_marker: self.bugtracker_widget.setRenderedValue(value) def _renderLabel(self, text, index): """Render a label for the option with the specified index.""" option_id = '%s.%s' % (self.name, index) return u'<label for="%s" style="font-weight: normal">%s</label>' % ( option_id, text) def error(self): """Concatenate errors from this widget and sub-widgets.""" errors = [ super(ProductBugTrackerWidget, self).error(), self.upstream_email_address_widget.error() ] return '; '.join(err for err in errors if len(err) > 0) def renderItems(self, value): """Custom-render the radio-buttons and dependent widgets. Some of the radio options have dependent widgets: the bug tracker drop-down box, and the email address text field. To render these in the correct place we must override the default rendering of `LaunchpadRadioWidget`. We must also make sure that these dependent widgets are populated with the correct information, specifically the bug tracker selected, or the email address where bugs must be reported. """ field = self.context product = field.context if value == self._missing: value = field.missing_value # Bugs tracked in Launchpad Bugs. malone_item_arguments = dict(index=0, text=self._renderLabel("In Launchpad", 0), value="malone", name=self.name, cssClass=self.cssClass) # Project or somewhere else. project = product.project if project is None or project.bugtracker is None: project_bugtracker_caption = "Somewhere else" else: project_bugtracker_caption = structured( 'In the %s bug tracker (<a href="%s">%s</a>)</label>', project.displayname, canonical_url(project.bugtracker), project.bugtracker.title).escapedtext project_bugtracker_arguments = dict(index=1, text=self._renderLabel( project_bugtracker_caption, 1), value="project", name=self.name, cssClass=self.cssClass) # External bug tracker. ## The bugtracker widget can't be within the <label> tag, ## since Firefox doesn't cope with it well. external_bugtracker_text = "%s %s" % (self._renderLabel( "In a registered bug tracker:", 2), self.bugtracker_widget()) external_bugtracker_arguments = dict(index=2, text=external_bugtracker_text, value="external", name=self.name, cssClass=self.cssClass) # Upstream email address (special-case bug tracker). if (IBugTracker.providedBy(value) and value.bugtrackertype == BugTrackerType.EMAILADDRESS): self.upstream_email_address_widget.setRenderedValue( value.baseurl.lstrip('mailto:')) external_bugtracker_email_text = "%s %s" % (self._renderLabel( "By emailing an upstream bug contact:\n", 3), self.upstream_email_address_widget()) external_bugtracker_email_arguments = dict( index=3, text=external_bugtracker_email_text, value="external-email", name=self.name, cssClass=self.cssClass) # All the choices arguments in order. all_arguments = { 'launchpad': malone_item_arguments, 'external_bugtracker': external_bugtracker_arguments, 'external_email': external_bugtracker_email_arguments, 'unknown': project_bugtracker_arguments, } # Figure out the selected choice. if value == field.malone_marker: selected = malone_item_arguments elif value != self.context.missing_value: # value will be 'external-email' if there was an error on # upstream_email_address_widget. if (value == 'external-email' or (IBugTracker.providedBy(value) and value.bugtrackertype == BugTrackerType.EMAILADDRESS)): selected = external_bugtracker_email_arguments else: selected = external_bugtracker_arguments else: selected = project_bugtracker_arguments # Render. for name, arguments in all_arguments.items(): if arguments is selected: render = self.renderSelectedItem else: render = self.renderItem yield (name, render(**arguments)) def renderValue(self, value): # Render the items with subordinate fields and support markup. self.bug_trackers = dict(self.renderItems(value)) self.product = self.context.context # The view must also use GhostWidget for the 'remote_product' field. self.remote_product = copy_field(IProduct['remote_product']) self.remote_product_widget = CustomWidgetFactory(TextWidget) setUpWidget(self, 'remote_product', self.remote_product, IInputWidget, prefix='field', value=self.product.remote_product, context=self.product) # The view must also use GhostWidget for the 'enable_bug_expiration' # field. self.enable_bug_expiration = copy_field( IProduct['enable_bug_expiration']) self.enable_bug_expiration_widget = CustomWidgetFactory(CheckBoxWidget) setUpWidget(self, 'enable_bug_expiration', self.enable_bug_expiration, IInputWidget, prefix='field', value=self.product.enable_bug_expiration, context=self.product) return self.template()
class BugTaskBugWatchWidget(RadioWidget): """A widget for linking a bug watch to a bug task.""" def __init__(self, field, vocabulary, request): RadioWidget.__init__(self, field, vocabulary, request) self.url_widget = CustomWidgetFactory(URIWidget) setUpWidget( self, 'url', BugWatchEditForm['url'], IInputWidget, context=field.context) self.setUpJavascript() def setUpJavascript(self): """Set up JS to select the "new bugwatch" option automatically.""" select_js = "selectWidget('%s.%s', event)" % ( self.name, self._new_bugwatch_value) self.url_widget.extra = 'onKeyPress="%s"' % select_js def setPrefix(self, prefix): RadioWidget.setPrefix(self, prefix) self.url_widget.setPrefix(prefix) self.setUpJavascript() _messageNoValue = "None, the status of the bug is updated manually." _new_bugwatch_value = 'NEW' def _toFieldValue(self, form_value): """Convert the textual token to a field value. If the form value is _new_bugwatch_value, create a new bug watch, otherwise look up an existing one. """ if form_value == self._new_bugwatch_value: try: url = self.url_widget.getInputValue() bugtracker, remote_bug = getUtility( IBugWatchSet).extractBugTrackerAndBug(url) bugtask = self.context.context return bugtask.bug.addWatch( bugtracker, remote_bug, getUtility(ILaunchBag).user) except WidgetInputError as error: # Prefix the error with the widget name, since the error # will be display at the top of the page, and not right # next to the widget. raise WidgetInputError( self.context.__name__, self.label, 'Remote Bug: %s' % error.doc()) except (NoBugTrackerFound, UnrecognizedBugTrackerURL) as error: raise WidgetInputError( self.context.__name__, self.label, 'Invalid bug tracker URL.') else: return RadioWidget._toFieldValue(self, form_value) def _getFormValue(self): """Return the form value. We have to override this method in this class since the original one uses getInputValue(), which it shouldn't do. """ if not self._renderedValueSet(): return self.request.form_ng.getOne(self.name, self._missing) else: return self._toFormValue(self._data) def _div(self, cssClass, contents, **kw): """Don't render a div tag.""" return contents def _joinButtonToMessage(self, option_tag, label, input_id): """Join the input tag with the label.""" row_template = get_widget_template('bugtask-bugwatch-widget.txt') return row_template % { 'input_tag': option_tag, 'input_id': input_id, 'input_label': label} #XXX: Bjorn Tillenius 2006-04-26: # This method is mostly copied from RadioWidget.renderItems() and # modified to actually work. RadioWidget.renderItems() should be # fixed upstream so that we can override it and only do the last # part locally, the part after "# Add an option for creating...". # http://www.zope.org/Collectors/Zope3-dev/592 def renderItems(self, value): """Render the items with with the correct radio button selected.""" # XXX: Bjorn Tillenius 2006-04-26 # This works around the fact that we incorrectly gets the form # value instead of a valid field value. if value == self._missing: value = self.context.missing_value elif (isinstance(value, basestring) and value != self._new_bugwatch_value): value = self._toFieldValue(value) # check if we want to select first item, the previously selected item # or the "nothing selected" item. nothing_selected = None if (value == self.context.missing_value and getattr(self, 'firstItem', False) and len(self.vocabulary) > 0 and self.context.required): # Grab the first item from the iterator: values = [iter(self.vocabulary).next().value] elif value != self.context.missing_value: values = [value] else: # the "nothing selected" option will be checked nothing_selected = 'checked' values = [] items = self.renderItemsWithValues(values) if not self.context.required: kwargs = { 'index': None, 'text': self.translate(self._messageNoValue), 'value': '', 'name': self.name, 'cssClass': self.cssClass} if nothing_selected: option = self.renderSelectedItem(**kwargs) else: option = self.renderItem(**kwargs) items.insert(0, option) # Add an option for creating a new bug watch. option_text = ( '<div>URL: %s</div>' % self.url_widget()) if value == self._new_bugwatch_value: option = self.renderSelectedItem( self._new_bugwatch_value, option_text, self._new_bugwatch_value, self.name, self.cssClass) else: option = self.renderItem( self._new_bugwatch_value, option_text, self._new_bugwatch_value, self.name, self.cssClass) items.append(option) return items def renderItem(self, index, text, value, name, cssClass): """Render an item. We override this method to use the _joinButtonToMessage method instead of the _joinButtonToMessageTemplate which doesn't have access to the id. """ id = '%s.%s' % (name, index) elem = renderElement(u'input', value=value, name=name, id=id, cssClass=cssClass, type='radio') return self._joinButtonToMessage(elem, text, input_id=id) def renderSelectedItem(self, index, text, value, name, cssClass): """Render a selected item. We override this method to use the _joinButtonToMessage method instead of the _joinButtonToMessageTemplate which doesn't have access to the id. """ id = '%s.%s' % (name, index) elem = renderElement(u'input', value=value, name=name, id=id, cssClass=cssClass, checked="checked", type='radio') return self._joinButtonToMessage(elem, text, input_id=id) def renderValue(self, value): """Render the widget with the selected value. The original renderValue separates the items with either ' ' or '<br />' which isn't suitable for us. """ rendered_items = self.renderItems(value) return renderElement( 'table', cssClass=self.cssClass, contents='\n'.join(rendered_items))
class ProductBugTrackerWidget(LaunchpadRadioWidget): """Widget for selecting a product bug tracker.""" _joinButtonToMessageTemplate = u'%s %s' template = ViewPageTemplateFile('templates/product-bug-tracker.pt') def __init__(self, field, vocabulary, request): LaunchpadRadioWidget.__init__(self, field, vocabulary, request) # Bug tracker widget. self.bugtracker = Choice( vocabulary="WebBugTracker", __name__='bugtracker') self.bugtracker_widget = CustomWidgetFactory(BugTrackerPickerWidget) setUpWidget( self, 'bugtracker', self.bugtracker, IInputWidget, prefix=self.name, value=field.context.bugtracker, context=field.context) self.bugtracker_widget.onKeyPress = ( "selectWidget('%s.2', event);" % self.name) # Upstream email address field and widget. ## This is to make email address bug trackers appear ## separately from the main bug tracker list. self.upstream_email_address = StrippedTextLine( required=False, constraint=email_validator, __name__='upstream_email_address') self.upstream_email_address_widget = ( CustomWidgetFactory(StrippedTextWidget)) setUpWidget( self, 'upstream_email_address', self.upstream_email_address, IInputWidget, prefix=self.name, value='', context=self.upstream_email_address.context) ## Select the corresponding radio option automatically if ## the user starts typing. if self.upstream_email_address_widget.extra is None: self.upstream_email_address_widget.extra = '' self.upstream_email_address_widget.extra += ( ''' onkeypress="selectWidget('%s.3', event);"\n''' % self.name) def _renderItem(self, index, text, value, name, cssClass, checked=False): # This form has a custom need to render their labels separately, # because of a Firefox problem: see comment in renderItems. kw = {} if checked: kw['checked'] = 'checked' id = '%s.%s' % (name, index) elem = renderElement(u'input', value=value, name=name, id=id, cssClass=cssClass, type='radio', **kw) return '%s %s' % (elem, text) def _toFieldValue(self, form_value): if form_value == "malone": return self.context.malone_marker elif form_value == "external": return self.bugtracker_widget.getInputValue() elif form_value == "external-email": email_address = self.upstream_email_address_widget.getInputValue() if email_address is None or len(email_address) == 0: self.upstream_email_address_widget._error = ( LaunchpadValidationError( 'Please enter an email address.')) raise self.upstream_email_address_widget._error bugtracker = getUtility(IBugTrackerSet).ensureBugTracker( 'mailto:%s' % email_address, getUtility(ILaunchBag).user, BugTrackerType.EMAILADDRESS) return bugtracker elif form_value == "project": return None def getInputValue(self): return self._toFieldValue(self._getFormInput()) def setRenderedValue(self, value): self._data = value if value is not self.context.malone_marker: self.bugtracker_widget.setRenderedValue(value) def _renderLabel(self, text, index): """Render a label for the option with the specified index.""" option_id = '%s.%s' % (self.name, index) return u'<label for="%s" style="font-weight: normal">%s</label>' % ( option_id, text) def error(self): """Concatenate errors from this widget and sub-widgets.""" errors = [super(ProductBugTrackerWidget, self).error(), self.upstream_email_address_widget.error()] return '; '.join(err for err in errors if len(err) > 0) def renderItems(self, value): """Custom-render the radio-buttons and dependent widgets. Some of the radio options have dependent widgets: the bug tracker drop-down box, and the email address text field. To render these in the correct place we must override the default rendering of `LaunchpadRadioWidget`. We must also make sure that these dependent widgets are populated with the correct information, specifically the bug tracker selected, or the email address where bugs must be reported. """ field = self.context product = field.context if value == self._missing: value = field.missing_value # Bugs tracked in Launchpad Bugs. malone_item_arguments = dict( index=0, text=self._renderLabel("In Launchpad", 0), value="malone", name=self.name, cssClass=self.cssClass) # Project or somewhere else. project = product.project if project is None or project.bugtracker is None: project_bugtracker_caption = "Somewhere else" else: project_bugtracker_caption = structured( 'In the %s bug tracker (<a href="%s">%s</a>)</label>', project.displayname, canonical_url(project.bugtracker), project.bugtracker.title).escapedtext project_bugtracker_arguments = dict( index=1, text=self._renderLabel(project_bugtracker_caption, 1), value="project", name=self.name, cssClass=self.cssClass) # External bug tracker. ## The bugtracker widget can't be within the <label> tag, ## since Firefox doesn't cope with it well. external_bugtracker_text = "%s %s" % ( self._renderLabel("In a registered bug tracker:", 2), self.bugtracker_widget()) external_bugtracker_arguments = dict( index=2, text=external_bugtracker_text, value="external", name=self.name, cssClass=self.cssClass) # Upstream email address (special-case bug tracker). if (IBugTracker.providedBy(value) and value.bugtrackertype == BugTrackerType.EMAILADDRESS): self.upstream_email_address_widget.setRenderedValue( value.baseurl.lstrip('mailto:')) external_bugtracker_email_text = "%s %s" % ( self._renderLabel("By emailing an upstream bug contact:\n", 3), self.upstream_email_address_widget()) external_bugtracker_email_arguments = dict( index=3, text=external_bugtracker_email_text, value="external-email", name=self.name, cssClass=self.cssClass) # All the choices arguments in order. all_arguments = { 'launchpad': malone_item_arguments, 'external_bugtracker': external_bugtracker_arguments, 'external_email': external_bugtracker_email_arguments, 'unknown': project_bugtracker_arguments, } # Figure out the selected choice. if value == field.malone_marker: selected = malone_item_arguments elif value != self.context.missing_value: # value will be 'external-email' if there was an error on # upstream_email_address_widget. if (value == 'external-email' or ( IBugTracker.providedBy(value) and value.bugtrackertype == BugTrackerType.EMAILADDRESS)): selected = external_bugtracker_email_arguments else: selected = external_bugtracker_arguments else: selected = project_bugtracker_arguments # Render. for name, arguments in all_arguments.items(): if arguments is selected: render = self.renderSelectedItem else: render = self.renderItem yield (name, render(**arguments)) def renderValue(self, value): # Render the items with subordinate fields and support markup. self.bug_trackers = dict(self.renderItems(value)) self.product = self.context.context # The view must also use GhostWidget for the 'remote_product' field. self.remote_product = copy_field(IProduct['remote_product']) self.remote_product_widget = CustomWidgetFactory(TextWidget) setUpWidget( self, 'remote_product', self.remote_product, IInputWidget, prefix='field', value=self.product.remote_product, context=self.product) # The view must also use GhostWidget for the 'enable_bug_expiration' # field. self.enable_bug_expiration = copy_field( IProduct['enable_bug_expiration']) self.enable_bug_expiration_widget = CustomWidgetFactory( CheckBoxWidget) setUpWidget( self, 'enable_bug_expiration', self.enable_bug_expiration, IInputWidget, prefix='field', value=self.product.enable_bug_expiration, context=self.product) return self.template()
class LaunchpadTargetWidget(BrowserWidget, InputWidget): """Widget for selecting a product, distribution or package target.""" implements(IAlwaysSubmittedWidget, IMultiLineWidgetLayout, IInputWidget) template = ViewPageTemplateFile('templates/launchpad-target.pt') default_option = "package" _widgets_set_up = False def getDistributionVocabulary(self): return 'Distribution' def getProductVocabulary(self): return 'Product' def setUpSubWidgets(self): if self._widgets_set_up: return fields = [ Choice(__name__='product', title=u'Project', required=True, vocabulary=self.getProductVocabulary()), Choice(__name__='distribution', title=u"Distribution", required=True, vocabulary=self.getDistributionVocabulary(), default=getUtility(ILaunchpadCelebrities).ubuntu), Choice(__name__='package', title=u"Package", required=False, vocabulary='BinaryAndSourcePackageName'), ] self.distribution_widget = CustomWidgetFactory(LaunchpadDropdownWidget) for field in fields: setUpWidget(self, field.__name__, field, IInputWidget, prefix=self.name) self._widgets_set_up = True def setUpOptions(self): """Set up options to be rendered.""" self.options = {} for option in ['package', 'product']: attributes = dict(type='radio', name=self.name, value=option, id='%s.option.%s' % (self.name, option)) if self.request.form_ng.getOne(self.name, self.default_option) == option: attributes['checked'] = 'checked' self.options[option] = renderElement('input', **attributes) self.package_widget.onKeyPress = ( "selectWidget('%s.option.package', event)" % self.name) self.product_widget.onKeyPress = ( "selectWidget('%s.option.product', event)" % self.name) def hasInput(self): return self.name in self.request.form def hasValidInput(self): """See zope.formlib.interfaces.IInputWidget.""" try: self.getInputValue() return True except (InputErrors, UnexpectedFormData): return False def getInputValue(self): """See zope.formlib.interfaces.IInputWidget.""" self.setUpSubWidgets() form_value = self.request.form_ng.getOne(self.name) if form_value == 'product': try: return self.product_widget.getInputValue() except MissingInputError: self._error = WidgetInputError( self.name, self.label, LaunchpadValidationError('Please enter a project name')) raise self._error except ConversionError: entered_name = self.request.form_ng.getOne("%s.product" % self.name) self._error = WidgetInputError( self.name, self.label, LaunchpadValidationError( "There is no project named '%s' registered in" " Launchpad" % entered_name)) raise self._error elif form_value == 'package': try: distribution = self.distribution_widget.getInputValue() except ConversionError: entered_name = self.request.form_ng.getOne("%s.distribution" % self.name) self._error = WidgetInputError( self.name, self.label, LaunchpadValidationError( "There is no distribution named '%s' registered in" " Launchpad" % entered_name)) raise self._error if self.package_widget.hasInput(): try: package_name = self.package_widget.getInputValue() if package_name is None: return distribution if IDistributionSourcePackage.providedBy(package_name): dsp = package_name else: source_name = ( distribution.guessPublishedSourcePackageName( package_name.name)) dsp = distribution.getSourcePackage(source_name) except (ConversionError, NotFoundError): entered_name = self.request.form_ng.getOne('%s.package' % self.name) self._error = WidgetInputError( self.name, self.label, LaunchpadValidationError( "There is no package named '%s' published in %s." % (entered_name, distribution.displayname))) raise self._error return dsp else: return distribution else: raise UnexpectedFormData("No valid option was selected.") def setRenderedValue(self, value): """See IWidget.""" self.setUpSubWidgets() if IProduct.providedBy(value): self.default_option = 'product' self.product_widget.setRenderedValue(value) elif IDistribution.providedBy(value): self.default_option = 'package' self.distribution_widget.setRenderedValue(value) elif IDistributionSourcePackage.providedBy(value): self.default_option = 'package' self.distribution_widget.setRenderedValue(value.distribution) self.package_widget.setRenderedValue(value.sourcepackagename) else: raise AssertionError('Not a valid value: %r' % value) def __call__(self): """See zope.formlib.interfaces.IBrowserWidget.""" self.setUpSubWidgets() self.setUpOptions() return self.template()