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 setUpSubWidgets(self): if self._widgets_set_up: return if bool(getFeatureFlag('disclosure.dsp_picker.enabled')): # Replace the default field with a field that uses the better # vocabulary. package_vocab = 'DistributionSourcePackage' else: package_vocab = 'BinaryAndSourcePackageName' 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=package_vocab), ] self.distribution_widget = CustomWidgetFactory(LaunchpadDropdownWidget) for field in fields: setUpWidget(self, field.__name__, field, IInputWidget, prefix=self.name) self._widgets_set_up = True
def setUpSubWidgets(self): if self._widgets_set_up: return if bool(getFeatureFlag("disclosure.dsp_picker.enabled")): # Replace the default field with a field that uses the better # vocabulary. package_vocab = "DistributionSourcePackage" else: package_vocab = "BinaryAndSourcePackageName" fields = [ Choice(__name__="project", title=u"Project", required=True, vocabulary="Product"), Choice(__name__="distribution", title=u"Distribution", required=True, vocabulary="Distribution", default=getUtility(ILaunchpadCelebrities).ubuntu), Choice(__name__="package", title=u"Package", required=False, vocabulary=package_vocab), ] if not self._read_only: self.distribution_widget = CustomWidgetFactory( LaunchpadDropdownWidget) for field in fields: setUpWidget(self, field.__name__, field, self._sub_widget_interface, prefix=self.name) self._widgets_set_up = True
def setUpSubWidgets(self): if self._widgets_set_up: return fields = [ Choice(__name__="person", title=u"Person", required=False, vocabulary="ValidPersonOrTeam"), ] if self._read_only: self.person_widget = CustomWidgetFactory( GitGranteePersonDisplayWidget) else: self.person_widget = CustomWidgetFactory( PersonPickerWidget, # XXX cjwatson 2018-10-18: This is a little unfortunate, but # otherwise there's no spacing at all between the # (deliberately unlabelled) radio button and the text box. style="margin-left: 4px;") for field in fields: setUpWidget(self, field.__name__, field, self._sub_widget_interface, prefix=self.name) self._widgets_set_up = True
def initialize(self): """See `LaunchpadView.initialize`.""" review_status_field = copy_field( ICodeImport['review_status'], required=False, default=None) self.review_status_widget = CustomWidgetFactory(DropdownWidgetWithAny) setUpWidget(self, 'review_status', review_status_field, IInputWidget) rcs_type_field = copy_field( ICodeImport['rcs_type'], required=False, default=None) self.rcs_type_widget = CustomWidgetFactory(DropdownWidgetWithAny) setUpWidget(self, 'rcs_type', rcs_type_field, IInputWidget) # status should be None if either (a) there were no query arguments # supplied, i.e. the user browsed directly to this page (this is when # hasValidInput returns False) or (b) the user chose 'Any' in the # status widget (this is when hasValidInput returns True but # getInputValue returns None). review_status = None if self.review_status_widget.hasValidInput(): review_status = self.review_status_widget.getInputValue() # Similar for 'type' rcs_type = None if self.rcs_type_widget.hasValidInput(): rcs_type = self.rcs_type_widget.getInputValue() imports = self.context.search( review_status=review_status, rcs_type=rcs_type) self.batchnav = BatchNavigator(imports, self.request)
def 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 BrandingChangeView(LaunchpadEditFormView): """This is a base class that MUST be subclassed for each object, because each object will have a different description for its branding that is part of its own interface. For each subclass, specify the schema ("IPerson") and the field_names (some subset of icon, logo, mugshot). """ @property def label(self): return ('Change the images used to represent %s in Launchpad' % self.context.displayname) page_title = "Change branding" custom_widget_icon = CustomWidgetFactory(ImageChangeWidget, ImageChangeWidget.EDIT_STYLE) custom_widget_logo = CustomWidgetFactory(ImageChangeWidget, ImageChangeWidget.EDIT_STYLE) custom_widget_mugshot = CustomWidgetFactory(ImageChangeWidget, ImageChangeWidget.EDIT_STYLE) @action("Change Branding", name='change') def change_action(self, action, data): self.updateContextFromData(data) @property def next_url(self): return canonical_url(self.context) cancel_url = next_url
def __init__(self, context, request): SimpleInputWidget.__init__(self, context, request) fields = form.Fields( Choice(__name__='action', source=self._getActionsVocabulary(), title=_('Action')), Datetime(__name__='announcement_date', title=_('Date'), required=False, default=None)) fields['action'].custom_widget = CustomWidgetFactory( LaunchpadRadioWidget) fields['announcement_date'].custom_widget = CustomWidgetFactory( DateTimeWidget) if IAnnouncement.providedBy(self.context.context): # we are editing an existing announcement data = {} date_announced = self.context.context.date_announced data['announcement_date'] = date_announced if date_announced is None: data['action'] = 'sometime' else: data['action'] = 'specific' else: data = {'action': 'immediately'} widgets = form.setUpWidgets(fields, self.name, context, request, ignore_request=False, data=data) self.action_widget = widgets['action'] self.announcement_date_widget = widgets['announcement_date']
class ProductReleaseEditView(LaunchpadEditFormView): """Edit view for ProductRelease objects""" schema = IProductRelease field_names = [ "datereleased", "release_notes", "changelog", ] custom_widget_datereleased = DateTimeWidget custom_widget_release_notes = CustomWidgetFactory( TextAreaWidget, height=7, width=62) custom_widget_changelog = CustomWidgetFactory( TextAreaWidget, height=7, width=62) @property def label(self): """The form label.""" return smartquote('Edit %s release details' % self.context.title) page_title = label @action('Change', name='change') def change_action(self, action, data): self.updateContextFromData(data) self.next_url = canonical_url(self.context) @property def cancel_url(self): return canonical_url(self.context)
class SprintEditView(LaunchpadEditFormView): """Form for editing sprints""" schema = ISprint label = "Edit sprint details" field_names = [ 'name', 'title', 'summary', 'home_page', 'driver', 'time_zone', 'time_starts', 'time_ends', 'is_physical', 'address', ] custom_widget_summary = CustomWidgetFactory(TextAreaWidget, height=5) custom_widget_time_starts = CustomWidgetFactory(DateTimeWidget, display_zone=False) custom_widget_time_ends = CustomWidgetFactory(DateTimeWidget, display_zone=False) custom_widget_address = CustomWidgetFactory(TextAreaWidget, height=3) def setUpWidgets(self): LaunchpadEditFormView.setUpWidgets(self) timeformat = '%Y-%m-%d %H:%M' self.widgets['time_starts'].timeformat = timeformat self.widgets['time_ends'].timeformat = timeformat time_zone_widget = self.widgets['time_zone'] # What time zone are the start and end values relative to? if time_zone_widget.hasValidInput(): tz = pytz.timezone(time_zone_widget.getInputValue()) else: tz = pytz.timezone(self.context.time_zone) self.widgets['time_starts'].required_time_zone = tz self.widgets['time_ends'].required_time_zone = tz def validate(self, data): time_starts = data.get('time_starts') time_ends = data.get('time_ends') if time_starts and time_ends and time_ends < time_starts: self.setFieldError('time_ends', "This event can't start after it ends") @action(_('Change'), name='change') def change_action(self, action, data): self.updateContextFromData(data) @property def next_url(self): return canonical_url(self.context) @property def cancel_url(self): return canonical_url(self.context)
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()
class BugSubscriptionFilterEditViewBase(LaunchpadEditFormView, AdvancedSubscriptionMixin): """Base class for edit or create views of `IBugSubscriptionFilter`.""" schema = IBugSubscriptionFilter field_names = ( "description", "statuses", "importances", "information_types", "tags", "find_all_tags", ) custom_widget_description = CustomWidgetFactory( TextWidget, displayWidth=50) custom_widget_statuses = LabeledMultiCheckBoxWidget custom_widget_importances = LabeledMultiCheckBoxWidget custom_widget_information_types = LabeledMultiCheckBoxWidget custom_widget_tags = CustomWidgetFactory( BugTagsFrozenSetWidget, displayWidth=35) # Define in concrete subclass to be the target of the # structural subscription that we are modifying. target = None # This is used by the AdvancedSubscriptionMixin. current_user_subscription = None @cachedproperty def _bug_notification_level_descriptions(self): return bug_notification_level_description_mapping( 'a bug in %s' % self.target.displayname) def setUpFields(self): """Set up fields for form. Overrides the usual implementation to also set up bug notification.""" super(BugSubscriptionFilterEditViewBase, self).setUpFields() self._setUpBugNotificationLevelField() @property def next_url(self): """Return to the user's structural subscriptions page.""" return canonical_url( self.user, view_name="+structural-subscriptions") cancel_url = next_url
class ProductReleaseAddViewBase(LaunchpadFormView): """Base class for creating a release from an existing or new milestone. Subclasses need to define the field_names a form action. """ schema = IProductRelease custom_widget_datereleased = DateTimeWidget custom_widget_release_notes = CustomWidgetFactory( TextAreaWidget, height=7, width=62) custom_widget_changelog = CustomWidgetFactory( TextAreaWidget, height=7, width=62) def _prependKeepMilestoneActiveField(self): keep_milestone_active_checkbox = FormFields( Bool( __name__='keep_milestone_active', title=_("Keep the %s milestone active." % self.context.name), description=_( "Only select this if bugs or blueprints still need " "to be targeted to this project release's milestone.")), render_context=self.render_context) self.form_fields = keep_milestone_active_checkbox + self.form_fields def _createRelease(self, milestone, data): """Create product release for this milestone.""" newrelease = milestone.createProductRelease( self.user, changelog=data['changelog'], release_notes=data['release_notes'], datereleased=data['datereleased']) # Set Milestone.active to false, since bugs & blueprints # should not be targeted to a milestone in the past. if data.get('keep_milestone_active') is False: milestone.active = False self.next_url = canonical_url(newrelease.milestone) notify(ObjectCreatedEvent(newrelease)) @property def label(self): """The form label.""" return smartquote('Create a new release for %s' % self.context.product.displayname) page_title = label @property def cancel_url(self): return canonical_url(self.context)
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 __init__(self, field_name, widget, *args, **kwargs): self.field_name = field_name if widget is None: self.widget = None else: self.widget = CustomWidgetFactory(widget, *args, **kwargs) addClassAdvisor(self.advise)
def _setUpBugNotificationLevelField(self): """Set up the bug_notification_level field.""" self.form_fields = self.form_fields.omit('bug_notification_level') self.form_fields += formlib.form.Fields( self._bug_notification_level_field) self.form_fields['bug_notification_level'].custom_widget = ( CustomWidgetFactory(RadioWidget))
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()
class ProductSeriesReviewView(LaunchpadEditFormView): """A view to review and change the series `IProduct` and name.""" schema = IProductSeries field_names = ['product', 'name'] custom_widget_name = CustomWidgetFactory(TextWidget, displayWidth=20) @property def label(self): """The form label.""" return 'Administer %s %s series' % (self.context.product.displayname, self.context.name) page_title = label @property def cancel_url(self): """See `LaunchpadFormView`.""" return canonical_url(self.context) @action(_('Change'), name='change') def change_action(self, action, data): """Update the series.""" self.updateContextFromData(data) self.request.response.addInfoNotification( _('This Series has been changed')) self.next_url = canonical_url(self.context)
def __init__(self, field, vocabulary, request): super(LicenseWidget, self).__init__(field, vocabulary, request) # We want to put the license_info widget inside the licences widget's # HTML, for better alignment and JavaScript dynamism. This is # accomplished by ghosting the form's license_info widget (see # lp/registry/browser/product.py and the GhostWidget implementation # below) and creating a custom widget here. It's a pretty simple text # widget so create that now. The fun part is that it's all within the # same form, so posts work correctly. self.license_info = Text(__name__='license_info') self.license_info_widget = CustomWidgetFactory(DescriptionWidget) # The initial value of the license_info widget will be taken from the # field's context when available. This will be the IProduct when # we're editing an existing project, but when we're creating a new # one, it'll be an IProductSet, which does not have license_info. initial_value = getattr(field.context, 'license_info', None) setUpWidget(self, 'license_info', self.license_info, IInputWidget, prefix='field', value=initial_value, context=field.context) self.source_package_release = None # These will get filled in by _categorize(). They are the number of # selected licences in the category. The actual count doesn't matter, # since if it's greater than 0 it will start opened. Note that we # always want the recommended licences to be opened, so we initialize # its value to 1. self.recommended_count = 1 self.more_count = 0 self.deprecated_count = 0 self.special_count = 0
class NameBlacklistAddView(NameBlacklistValidationMixin, LaunchpadFormView): """View for adding a blacklist expression.""" schema = INameBlacklist field_names = ['regexp', 'admin', 'comment'] label = "Add a new blacklist expression" page_title = label custom_widget_regexp = CustomWidgetFactory(TextWidget, displayWidth=60) @property def cancel_url(self): """See `LaunchpadFormView`.""" return canonical_url(self.context) next_url = cancel_url @action("Add to blacklist", name='add') def add_action(self, action, data): name_blacklist_set = getUtility(INameBlacklistSet) name_blacklist_set.create( regexp=data['regexp'], comment=data['comment'], admin=data['admin'], ) self.request.response.addInfoNotification( 'Regular expression "%s" has been added to the name blacklist.' % data['regexp'])
def setUpWidgets(self, context=None): """Set up the widgets using the view's form fields and the context. If no context is given, the view's context is used.""" for field in self.form_fields: # Honour the custom_widget value if it was already set. This is # important for some existing forms. if field.custom_widget is None: widget = getattr(self, 'custom_widget_%s' % field.__name__, None) if widget is not None: if IWidgetFactory.providedBy(widget): field.custom_widget = widget else: # Allow views to save some typing in common cases. field.custom_widget = CustomWidgetFactory(widget) if context is None: context = self.context self.widgets = form.setUpWidgets(self.form_fields, self.prefix, context, self.request, data=self.initial_values, adapters=self.adapters, ignore_request=False) for field_name, help_link in self.help_links.iteritems(): self.widgets[field_name].help_link = help_link
def widget(self, _context, field, **kw): attrs = kw class_ = attrs.pop("class_", None) # Try to do better than accepting the string value by looking through # the interfaces and trying to find the field, so that we can use # 'fromUnicode()' if isinstance(class_, type): ifaces = implementedBy(class_) for name, value in kw.items(): for iface in ifaces: if name in iface: attrs[name] = iface[name].fromUnicode(value) break if class_ is None: # The _default_widget_factory is required to allow the # <widget> directive to be given without a "class" # attribute. This can be used to override some of the # presentational attributes of the widget implementation. class_ = self._default_widget_factory # don't wrap a factory into a factory if IWidgetFactory.providedBy(class_): factory = class_ else: factory = CustomWidgetFactory(class_, **attrs) self._widgets[field+'_widget'] = factory
class V(object): name_widget = CustomWidgetFactory(Text) first_widget = CustomWidgetFactory(Text) last_widget = CustomWidgetFactory(Text) email_widget = CustomWidgetFactory(Text) address_widget = CustomWidgetFactory(Text) getfoo_widget = CustomWidgetFactory(Text) extra1_widget = CustomWidgetFactory(Text) extra2_widget = CustomWidgetFactory(Text)
class LanguageAdminView(LaunchpadEditFormView): """Handle an admin form submission.""" rootsite = 'translations' schema = ILanguage custom_widget_countries = CustomWidgetFactory(LabeledMultiCheckBoxWidget, orientation='vertical') field_names = [ 'code', 'englishname', 'nativename', 'pluralforms', 'pluralexpression', 'visible', 'direction', 'countries' ] page_title = "Change details" @property def label(self): """The form label""" return "Edit %s in Launchpad" % describe_language(self.context) @property def cancel_url(self): """See LaunchpadFormView.""" return canonical_url(self.context, rootsite=self.rootsite) @property def next_url(self): return canonical_url(self.context, rootsite=self.rootsite) @action("Admin Language", name="admin") def admin_action(self, action, data): self.updateContextFromData(data) def _validateCode(self, new_code): """Validate a change in language code.""" language_set = getUtility(ILanguageSet) if language_set.getLanguageByCode(new_code) is not None: self.setFieldError('code', 'There is already a language with that code.') def _validatePluralData(self, pluralforms, pluralexpression): """Validate plural expression and number of plural forms.""" try: make_friendly_plural_forms(pluralexpression, pluralforms) except BadPluralExpression as e: self.setFieldError('pluralexpression', str(e)) def validate(self, data): new_code = data.get('code') if new_code != self.context.code: self._validateCode(new_code) pluralexpression = data.get('pluralexpression') pluralforms = data.get('pluralforms') if pluralexpression is not None: self._validatePluralData(pluralforms, pluralexpression)
def setUpFields(self): """See `LaunchpadFormView`.""" super(BugSubscriptionSubscribeSelfView, self).setUpFields() if self.user is None: return self.form_fields += formlib.form.Fields(self._subscription_field) self._setUpBugNotificationLevelField() self.form_fields['subscription'].custom_widget = CustomWidgetFactory( RadioWidget)
class BuilderSetAddView(LaunchpadFormView): """View class for adding new Builders.""" schema = IBuilder label = "Register a new build machine" field_names = [ 'name', 'title', 'processors', 'url', 'active', 'virtualized', 'vm_host', 'vm_reset_protocol', 'owner' ] custom_widget_owner = HiddenUserWidget custom_widget_url = CustomWidgetFactory(TextWidget, displayWidth=30) custom_widget_vm_host = CustomWidgetFactory(TextWidget, displayWidth=30) custom_widget_processors = LabeledMultiCheckBoxWidget @action(_('Register builder'), name='register') def register_action(self, action, data): """Register a new builder.""" builder = getUtility(IBuilderSet).new( processors=data.get('processors'), url=data.get('url'), name=data.get('name'), title=data.get('title'), owner=data.get('owner'), active=data.get('active'), virtualized=data.get('virtualized'), vm_host=data.get('vm_host'), vm_reset_protocol=data.get('vm_reset_protocol'), ) notify(ObjectCreatedEvent(builder)) self.next_url = canonical_url(builder) @property def page_title(self): """Return a relevant page title for this view.""" return self.label @property def cancel_url(self): """Canceling the add action should go back to the build farm.""" return canonical_url(self.context)
def __init__(self, context, request, style): SimpleInputWidget.__init__(self, context, request) self.style = style fields = form.Fields( Choice(__name__='action', source=self._getActionsVocabulary(), title=_('Action')), Bytes(__name__='image', title=_('Image'))) fields['action'].custom_widget = CustomWidgetFactory( LaunchpadRadioWidget) fields['image'].custom_widget = CustomWidgetFactory( LaunchpadFileWidget, displayWidth=15) widgets = form.setUpWidgets(fields, self.name, context, request, ignore_request=False, data={'action': 'keep'}) self.action_widget = widgets['action'] self.image_widget = widgets['image']
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)
class ProductSeriesEditView(LaunchpadEditFormView): """A View to edit the attributes of a series.""" schema = IProductSeries field_names = ['name', 'summary', 'status', 'branch', 'releasefileglob'] custom_widget_summary = CustomWidgetFactory(TextAreaWidget, height=7, width=62) custom_widget_releasefileglob = CustomWidgetFactory(StrippedTextWidget, displayWidth=40) @property def label(self): """The form label.""" return 'Edit %s %s series' % (self.context.product.displayname, self.context.name) page_title = label def validate(self, data): """See `LaunchpadFormView`.""" branch = data.get('branch') if branch is not None: message = get_series_branch_error(self.context.product, branch) if message: self.setFieldError('branch', message) @action(_('Change'), name='change') def change_action(self, action, data): """Update the series.""" self.updateContextFromData(data) @property def next_url(self): """See `LaunchpadFormView`.""" return canonical_url(self.context) cancel_url = next_url
def test_customWidgetFactory(self): """Verify that the widget can be constructed via the CustomWidgetFactory (Issue #293) """ value_type = TextLine(__name__=u'bar') self.field = List(__name__=u'foo', value_type=value_type) request = TestRequest() # set up the custom widget factory and verify that it works sw = CustomWidgetFactory(ListSequenceWidget) widget = sw(self.field, request) assert widget.subwidget is None assert widget.context.value_type is value_type # set up a variant that specifies the subwidget to use and verify it class PollOption(object): pass ow = CustomWidgetFactory(ObjectWidget, PollOption) sw = CustomWidgetFactory(ListSequenceWidget, subwidget=ow) widget = sw(self.field, request) assert widget.subwidget is ow assert widget.context.value_type is value_type
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
def test_subwidget(self): """This test verifies that the specified subwidget is not ignored. (Issue #293) """ self.field = List(__name__=u'foo', value_type=TextLine(__name__=u'bar')) request = TestRequest() class PollOption(object): pass ow = CustomWidgetFactory(ObjectWidget, PollOption) widget = SequenceWidget(self.field, self.field.value_type, request, subwidget=ow) assert widget.subwidget is ow
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
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 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()