class AccessControlListField(PrincipalListField): widget = JinjaWidget('forms/principal_list_widget.html', single_kwargs=True, acl=True) def __init__(self, *args, **kwargs): # The text of the link that changes the protection mode of the object to protected self.default_text = kwargs.pop('default_text') super(AccessControlListField, self).__init__(*args, **kwargs)
class OverrideMultipleItemsField(HiddenField): """A field similar to `MultipleItemsField` which allows the user to override some values. :param fields: a list of ``(fieldname, title)`` tuples. Should match the fields of the corresponding `MultipleItemsField`. :param field_data: the data from the corresponding `MultipleItemsField`. :param unique_field: the name of the field which is unique among all rows :param edit_fields: a set containing the field names which can be edited If you decide to use this field, please consider adding support for `uuid_field` here! """ widget = JinjaWidget('forms/override_multiple_items_widget.html') def __init__(self, *args, **kwargs): self.fields = kwargs.pop('fields') self.field_data = kwargs.pop('field_data', None) # usually set after creating the form instance self.unique_field = kwargs.pop('unique_field') self.edit_fields = set(kwargs.pop('edit_fields')) super(OverrideMultipleItemsField, self).__init__(*args, **kwargs) def process_formdata(self, valuelist): if is_preprocessed_formdata(valuelist): self.data = valuelist[0] elif valuelist: self.data = json.loads(valuelist[0]) def pre_validate(self, form): valid_keys = {x[self.unique_field] for x in self.field_data} for key, values in self.data.items(): if key not in valid_keys: # e.g. a row removed from field_data that had a value before del self.data[key] continue if values.viewkeys() > self.edit_fields: # e.g. a field that was editable before self.data[key] = {k: v for k, v in values.iteritems() if k in self.edit_fields} # Remove anything empty for key, values in self.data.items(): for field, value in values.items(): if not value: del values[field] if not self.data[key]: del self.data[key] def _value(self): return self.data or {} def get_overridden_value(self, row, name): """Utility for the widget to get the entered value for an editable field""" key = self.get_row_key(row) return self._value().get(key, {}).get(name, '') def get_row_key(self, row): """Utility for the widget to get the unique value for a row""" return row[self.unique_field]
class PrincipalListField(HiddenField): """A field that lets you select a list Indico user/group ("principal") :param groups: If groups should be selectable. :param allow_networks: If ip networks should be selectable. :param allow_emails: If emails should be allowed. :param allow_external: If "search users with no indico account" should be available. Selecting such a user will automatically create a pending user once the form is submitted, even if other fields in the form fail to validate! """ widget = JinjaWidget('forms/principal_list_widget.html', single_kwargs=True) def __init__(self, *args, **kwargs): self.allow_emails = kwargs.pop('allow_emails', False) self.groups = kwargs.pop('groups', False) self.allow_networks = kwargs.pop('allow_networks', False) self.ip_networks = [] if self.allow_networks: self.ip_networks = map(serialize_ip_network_group, IPNetworkGroup.query.filter_by(hidden=False)) # Whether it is allowed to search for external users with no indico account self.allow_external = kwargs.pop('allow_external', False) # Whether the add user dialog is opened immediately when the field is displayed self.open_immediately = kwargs.pop('open_immediately', False) self._event = kwargs.pop('event')(kwargs['_form']) if 'event' in kwargs else None super(PrincipalListField, self).__init__(*args, **kwargs) def _convert_principal(self, principal): return principal_from_fossil(principal, allow_pending=self.allow_external, allow_emails=self.allow_emails, allow_networks=self.allow_networks, existing_data=self.object_data, event=self._event) def process_formdata(self, valuelist): if valuelist: self.data = {self._convert_principal(x) for x in json.loads(valuelist[0])} def pre_validate(self, form): if not self.groups and any(isinstance(p, GroupProxy) for p in self._get_data()): raise ValueError('You cannot select groups') def _value(self): from indico.modules.events.models.persons import PersonLinkBase def key(obj): if isinstance(obj, PersonLinkBase): return obj.display_full_name.lower() name = obj.display_full_name if isinstance(obj, User) else obj.name return obj.principal_type, name.lower() principals = sorted(self._get_data(), key=key) return map(serialize_principal, principals) def _get_data(self): return sorted(self.data) if self.data else []
class PermissionsField(JSONField): from indico.modules.categories.models.categories import Category widget = JinjaWidget('forms/permissions_widget.html', single_kwargs=True, acl=True) type_mapping = { 'event': Event, 'session': Session, 'contribution': Contribution, 'category': Category } def __init__(self, *args, **kwargs): self.object_type = kwargs.pop('object_type') super().__init__(*args, **kwargs) self.ip_networks = list(map(serialize_ip_network_group, IPNetworkGroup.query.filter_by(hidden=False))) @property def event(self): return self.get_form().event @property def category(self): if self.object_type == 'category': return self.get_form().category return self.event.category @property def event_roles(self): return [serialize_event_role(role) for role in sorted(self.event.roles, key=attrgetter('code'))] @property def category_roles(self): from indico.modules.categories.models.roles import CategoryRole from indico.modules.categories.util import serialize_category_role category_roles = CategoryRole.get_category_roles(self.category) return [serialize_category_role(role, legacy=True) for role in category_roles] @property def registration_forms(self): if not self.event.has_feature('registration'): return [] registration_forms = self.event.registration_forms return [serialize_registration_form(regform) for regform in registration_forms] @property def permissions_info(self): return get_permissions_info(PermissionsField.type_mapping[self.object_type])[0] @property def hidden_permissions_info(self): all_permissions = get_available_permissions(PermissionsField.type_mapping[self.object_type]) visible_permissions = get_permissions_info(PermissionsField.type_mapping[self.object_type])[0] return {k: all_permissions[k].friendly_name for k in set(all_permissions) - set(visible_permissions)} def _value(self): return self.data if self.data else []
class OccurrencesField(JSONField): """ A field that lets you select multiple occurrences consisting of a start date/time and a duration. """ widget = JinjaWidget('forms/occurrences_widget.html', single_line=True) CAN_POPULATE = True def __init__(self, *args, **kwargs): self._timezone = kwargs.pop('timezone', None) self.default_time = kwargs.pop('default_time', time(0, 0)) self.default_duration = kwargs.pop('default_duration', timedelta()) kwargs.setdefault('default', []) super(OccurrencesField, self).__init__(*args, **kwargs) def process_formdata(self, valuelist): def _deserialize(occ): try: dt = dateutil.parser.parse('{} {}'.format( occ['date'], occ['time'])) except ValueError: raise ValueError('Invalid date/time: {} {}'.format( escape(occ['date']), escape(occ['time']))) return localize_as_utc( dt, self.timezone), timedelta(minutes=occ['duration']) self.data = [] super(OccurrencesField, self).process_formdata(valuelist) self.data = map(_deserialize, self.data) def _value(self): def _serialize(occ): if isinstance(occ, dict): # raw data from the client return occ dt = occ[0].astimezone(pytz.timezone(self.timezone)) return { 'date': dt.date().isoformat(), 'time': dt.time().isoformat()[:-3], # hh:mm only 'duration': int(occ[1].total_seconds() // 60) } return json.dumps(map(_serialize, self.data)) @property def timezone_field(self): field = getattr(self.get_form(), 'timezone', None) return field if isinstance(field, SelectField) else None @property def timezone(self): if self.timezone_field: return self.timezone_field.data else: return getattr(self.get_form(), 'timezone', session.tzinfo.zone)
class IndicoMarkdownField(TextAreaField): def __init__(self, *args, **kwargs): kwargs.setdefault( 'description', _("You can use Markdown or basic HTML formatting tags.")) super(IndicoMarkdownField, self).__init__(*args, **kwargs) widget = JinjaWidget('forms/markdown_widget.html', single_kwargs=True, rows=5)
class IndicoDateField(DateField): widget = JinjaWidget('forms/date_widget.html', single_line=True, single_kwargs=True) def __init__(self, *args, **kwargs): super(IndicoDateField, self).__init__(*args, parse_kwargs={'dayfirst': True}, display_format='%d/%m/%Y', **kwargs)
class IndicoStaticTextField(Field): """Returns an html element with text taken from this field's value""" widget = JinjaWidget('forms/static_text_widget.html') def __init__(self, *args, **kwargs): self.text_value = kwargs.pop('text') super(IndicoStaticTextField, self).__init__(*args, **kwargs) def _value(self): return self.text_value
class PrincipalListField(HiddenField): """A field that lets you select a list of principals. Principals are users or other objects represending users such as groups or roles that can be added to ACLs. :param allow_external_users: If "search users with no indico account" should be available. Selecting such a user will automatically create a pending user once the form is submitted, even if other fields in the form fail to validate! :param allow_groups: If groups should be selectable. :param allow_event_roles: If event roles should be selectable. :param allow_category_roles: If category roles should be selectable. :param allow_registration_forms: If registration form associated to an event should be selectable. :param allow_emails: If the field should allow bare emails. Those are not selectable in the widget, but may be added to an ACL through other means. """ widget = JinjaWidget('forms/principal_list_widget.html', single_kwargs=True) def __init__(self, *args, **kwargs): self.allow_external_users = kwargs.pop('allow_external_users', False) self.allow_groups = kwargs.pop('allow_groups', False) self.allow_event_roles = kwargs.pop('allow_event_roles', False) self.allow_category_roles = kwargs.pop('allow_category_roles', False) self.allow_registration_forms = kwargs.pop('allow_registration_forms', False) self.allow_emails = kwargs.pop('allow_emails', False) self._event = kwargs.pop('event')(kwargs['_form']) if 'event' in kwargs else None super().__init__(*args, **kwargs) def _convert_principal(self, principal): event_id = self._event.id if self._event else None return principal_from_identifier(principal, event_id=event_id, allow_groups=self.allow_groups, allow_external_users=self.allow_external_users, allow_event_roles=self.allow_event_roles, allow_category_roles=self.allow_category_roles, allow_registration_forms=self.allow_registration_forms, allow_emails=self.allow_emails) def process_formdata(self, valuelist): if valuelist: self._submitted_data = json.loads(valuelist[0]) self.data = {self._convert_principal(x) for x in self._submitted_data} def _value(self): try: return self._submitted_data except AttributeError: return [x.identifier for x in self._get_data()] def _get_data(self): return sorted(self.data, key=attrgetter('identifier')) if self.data else []
class CERNAccessForm(RequestFormBase): regforms = IndicoSelectMultipleCheckboxField( _('Registration forms'), [DataRequired(_('At least one registration form has to be selected'))], widget=JinjaWidget('regform_list_widget.html', 'cern_access')) during_registration = BooleanField( _('Show during user registration'), widget=SwitchWidget(), description=_( "When enabled, users can request site access while registering " "and provide their additional personal data in the registration " "form. In any case, site access is only granted after a manager " "explicitly enables it for the registrants.")) during_registration_preselected = BooleanField( _('Preselect during user registration'), [HiddenUnless('during_registration')], widget=SwitchWidget(), description=_("Preselect the option to request site access during " "registration. Recommended if most registrants will " "need it.")) during_registration_required = BooleanField( _('Require during user registration'), [HiddenUnless('during_registration_preselected')], widget=SwitchWidget(), description=_("Require all users to provide data for site access. " "Registration without entering the data will not be " "possible.")) start_dt_override = IndicoDateTimeField( _('Start date override'), [Optional()], description=_("If set, CERN access will be granted starting at the " "specified date instead of the event's start date")) end_dt_override = IndicoDateTimeField( _('End date override'), [Optional(), LinkedDateTime('start_dt_override', not_equal=True)], description=_( "If set, CERN access will be granted until the specified date " "instead of the event's end date")) def __init__(self, *args, **kwargs): super(CERNAccessForm, self).__init__(*args, **kwargs) regforms = get_regforms(self.event) self._regform_map = {unicode(rf.id): rf for rf in regforms} self.regforms.choices = [(unicode(rf.id), rf.title) for rf in regforms] self.start_dt_override.default_time = self.event.start_dt_local.time() self.end_dt_override.default_time = self.event.end_dt_local.time() def validate_start_dt_override(self, field): if bool(self.start_dt_override.data) != bool( self.end_dt_override.data): raise ValidationError( _('You need to specify both date overrides or neither of them.' )) validate_end_dt_override = validate_start_dt_override
class TrackRoleField(JSONField): """A field that stores a list of e-mail template rules.""" CAN_POPULATE = True widget = JinjaWidget('events/abstracts/forms/track_role_widget.html') @property def users(self): return { user_id: _serialize_user(user) for user_id, user in _get_users_in_roles(self.data) } @property def role_data(self): conveners = set() reviewers = set() # Handle global reviewers/conveners role_data = self.data.pop('*') global_conveners = _get_users(role_data['convener']) global_reviewers = _get_users(role_data['reviewer']) conveners |= global_conveners reviewers |= global_reviewers track_dict = { track.id: track for track in Track.query.with_parent(self.event).filter( Track.id.in_(self.data)) } user_dict = dict(_get_users_in_roles(self.data)) track_roles = {} # Update track-specific reviewers/conveners for track_id, roles in self.data.viewitems(): track = track_dict[int(track_id)] track_roles[track] = defaultdict(set) for role_id, user_ids in roles.viewitems(): users = {user_dict[user_id] for user_id in user_ids} track_roles[track][role_id] = users if role_id == 'convener': conveners |= users elif role_id == 'reviewer': reviewers |= users return { 'track_roles': track_roles, 'global_conveners': global_conveners, 'global_reviewers': global_reviewers, 'all_conveners': conveners, 'all_reviewers': reviewers } def _value(self): return super(TrackRoleField, self)._value() if self.data else '[]'
class SessionBlockPersonLinkListField(PersonLinkListFieldBase): person_link_cls = SessionBlockPersonLink linked_object_attr = 'session_block' widget = JinjaWidget('events/sessions/forms/session_person_link_widget.html') def _serialize_person_link(self, principal, extra_data=None): extra_data = extra_data or {} return dict(extra_data, **serialize_person_link(principal)) def _convert_data(self, data): return list({self._get_person_link(x) for x in data})
class PrincipalListField(HiddenField): """A field that lets you select a list Indico user/group ("principal") :param groups: If groups should be selectable. :param allow_external: If "search users with no indico account" should be available. Selecting such a user will automatically create a pending user once the form is submitted, even if other fields in the form fail to validate! """ widget = JinjaWidget('forms/principal_list_widget.html', single_kwargs=True) def __init__(self, *args, **kwargs): self.allow_emails = kwargs.pop('allow_emails', False) self.groups = kwargs.pop('groups', False) # Whether it is allowed to search for external users with no indico account self.allow_external = kwargs.pop('allow_external', False) super(PrincipalListField, self).__init__(*args, **kwargs) def _convert_principal(self, principal): return principal_from_fossil(principal, allow_pending=self.allow_external, legacy=False, allow_emails=self.allow_emails) def process_formdata(self, valuelist): if valuelist: self.data = { self._convert_principal(x) for x in json.loads(valuelist[0]) } def pre_validate(self, form): if not self.groups and any( isinstance(p, GroupProxy) for p in self._get_data()): raise ValueError(u'You cannot select groups') def _serialize_principal(self, principal): if principal.principal_type == PrincipalType.email: return principal.fossilize() elif principal.principal_type == PrincipalType.user: return serialize_user(principal) else: return serialize_group(principal) def _value(self): principals = sorted(self._get_data(), key=lambda x: x.name.lower()) return map(self._serialize_principal, principals) def _get_data(self): return sorted(self.data) if self.data else []
class PrincipalField(PrincipalListField): """A field that lets you select an Indico user/group ("principal")""" widget = JinjaWidget('forms/principal_widget.html', single_line=True) def _get_data(self): return [] if self.data is None else [self.data] def process_formdata(self, valuelist): if valuelist: data = map(self._convert_principal, json.loads(valuelist[0])) self.data = None if not data else data[0]
class AbstractPersonLinkListField(PersonLinkListFieldBase): """A field to configure a list of abstract persons""" person_link_cls = AbstractPersonLink linked_object_attr = 'abstract' default_sort_alpha = False create_untrusted_persons = True widget = JinjaWidget( 'events/contributions/forms/contribution_person_link_widget.html', allow_empty_email=True) def __init__(self, *args, **kwargs): self.author_types = AuthorType.serialize() self.allow_authors = True self.allow_submitters = False self.show_empty_coauthors = kwargs.pop('show_empty_coauthors', True) self.default_author_type = kwargs.pop('default_author_type', AuthorType.none) self.default_is_submitter = False self.default_is_speaker = False self.require_primary_author = True self.sort_by_last_name = True self.disable_user_search = kwargs.pop('disable_user_search', False) super(AbstractPersonLinkListField, self).__init__(*args, **kwargs) def _convert_data(self, data): return list({self._get_person_link(x) for x in data}) @no_autoflush def _get_person_link(self, data): extra_data = { 'author_type': data.pop('authorType', self.default_author_type), 'is_speaker': data.pop('isSpeaker', self.default_is_speaker) } return super(AbstractPersonLinkListField, self)._get_person_link(data, extra_data) def _serialize_person_link(self, principal, extra_data=None): extra_data = extra_data or {} data = dict(extra_data, **serialize_person_link(principal)) data['isSpeaker'] = principal.is_speaker data['authorType'] = principal.author_type.value return data def pre_validate(self, form): super(AbstractPersonLinkListField, self).pre_validate(form) for person_link in self.data: if not self.allow_authors and person_link.author_type != AuthorType.none: if not self.object_data or person_link not in self.object_data: person_link.author_type = AuthorType.none if person_link.author_type == AuthorType.none and not person_link.is_speaker: raise ValueError( _("{} has no role").format(person_link.full_name))
class ContributionPersonLinkListField(PersonLinkListFieldBase): """A field to configure a list of contribution persons""" person_link_cls = ContributionPersonLink linked_object_attr = 'contrib' widget = JinjaWidget( 'events/contributions/forms/contribution_person_link_widget.html', allow_empty_email=True) def __init__(self, *args, **kwargs): self.author_types = AuthorType.serialize() self.allow_authors = kwargs.pop( 'allow_authors', kwargs['_form'].event.type == 'conference') self.allow_submitters = kwargs.pop('allow_submitters', True) self.show_empty_coauthors = kwargs.pop('show_empty_coauthors', True) self.default_author_type = kwargs.pop('default_author_type', AuthorType.none) self.default_is_submitter = kwargs.pop('default_is_submitter', True) self.default_is_speaker = True super(ContributionPersonLinkListField, self).__init__(*args, **kwargs) def _convert_data(self, data): return { self._get_person_link(x): x.pop('isSubmitter', self.default_is_submitter) for x in data } @no_autoflush def _get_person_link(self, data): extra_data = { 'author_type': data.pop('authorType', self.default_author_type), 'is_speaker': data.pop('isSpeaker', self.default_is_speaker) } return super(ContributionPersonLinkListField, self)._get_person_link(data, extra_data) def _serialize_person_link(self, principal, extra_data=None): is_submitter = self.data[principal] if self.get_form().is_submitted( ) else None return serialize_contribution_person_link(principal, is_submitter=is_submitter) def pre_validate(self, form): super(ContributionPersonLinkListField, self).pre_validate(form) for person_link in self.data: if not self.allow_authors and person_link.author_type != AuthorType.none: if not self.object_data or person_link not in self.object_data: person_link.author_type = AuthorType.none if person_link.author_type == AuthorType.none and not person_link.is_speaker: raise ValueError( _("{} has no role").format(person_link.full_name))
class EventPersonLinkListField(PersonLinkListFieldBase): """A field to manage event's chairpersons.""" person_link_cls = EventPersonLink linked_object_attr = 'event' widget = JinjaWidget('forms/person_link_widget.html') @property def roles(self): return [{ 'name': 'submitter', 'label': _('Submitter'), 'icon': 'paperclip', 'default': self.default_is_submitter }] def __init__(self, *args, **kwargs): self.default_is_submitter = kwargs.pop('default_is_submitter', True) self.empty_message = _('There are no chairpersons') event_type = kwargs.pop('event_type', None) super().__init__(*args, **kwargs) if not event_type and self.object: event_type = self.object.event.type_ if event_type == EventType.lecture: self.empty_message = _('There are no speakers') def _convert_data(self, data): return { self._get_person_link(x): 'submitter' in x.get('roles', []) for x in data } def _serialize_person_link(self, principal): from indico.modules.events.persons.schemas import PersonLinkSchema data = PersonLinkSchema().dump(principal) data['roles'] = [] if (self.get_form().is_submitted() and self.data[principal]) or (principal.event and principal.is_submitter): data['roles'].append('submitter') return data def pre_validate(self, form): super().pre_validate(form) persons = set() for person_link in self.data: if person_link.person in persons: raise ValueError( _("Person with email '{}' is duplicated").format( person_link.person.email)) persons.add(person_link.person)
class IndicoProtectionField(IndicoEnumRadioField): widget = JinjaWidget('forms/protection_widget.html', single_kwargs=True) radio_widget = JinjaWidget('forms/radio_buttons_widget.html', orientation='horizontal', single_kwargs=True) def __init__(self, *args, **kwargs): self.protected_object = kwargs.pop('protected_object')(kwargs['_form']) get_acl_message_url = kwargs.pop('acl_message_url', None) self.acl_message_url = get_acl_message_url( kwargs['_form']) if get_acl_message_url else None self.can_inherit_protection = self.protected_object.protection_parent is not None if not self.can_inherit_protection: kwargs['skip'] = {ProtectionMode.inheriting} super(IndicoProtectionField, self).__init__(*args, enum=ProtectionMode, **kwargs) def render_protection_message(self): from indico.modules.categories.models.categories import Category protected_object = self.get_form().protected_object if hasattr(protected_object, 'get_non_inheriting_objects'): non_inheriting_objects = protected_object.get_non_inheriting_objects( ) else: non_inheriting_objects = [] if isinstance(protected_object.protection_parent, Event): parent_type = _('Event') elif isinstance(protected_object.protection_parent, Category): parent_type = _('Category') else: parent_type = _('Session') rv = render_template('_protection_info.html', field=self, protected_object=protected_object, parent_type=parent_type, non_inheriting_objects=non_inheriting_objects) return Markup(rv)
class IndicoDurationField(Field): widget = JinjaWidget('forms/duration_widget.html', single_line=True, single_kwargs=True) def _value(self): if self.data is None: return 0 else: return int(self.data.total_seconds()) def process_formdata(self, valuelist): if valuelist: self.data = timedelta(seconds=int(valuelist[0]))
class SubContributionPersonLinkListField(ContributionPersonLinkListField): """A field to configure a list of subcontribution persons.""" person_link_cls = SubContributionPersonLink linked_object_attr = 'subcontrib' widget = JinjaWidget('forms/person_link_widget.html', allow_empty_email=True) def _serialize_person_link(self, principal): from indico.modules.events.persons.schemas import PersonLinkSchema data = PersonLinkSchema().dump(principal) data['roles'] = [] if principal.is_speaker: data['roles'].append('speaker') return data
class _MultiChoiceQuerySelectMultipleFieldGrouped(IndicoQuerySelectMultipleField): widget = JinjaWidget('events/abstracts/forms/checkbox_group_grouped_widget.html') def __init__(self, *args, **kwargs): self.get_group = kwargs.pop('get_group', lambda x: x) super(_MultiChoiceQuerySelectMultipleFieldGrouped, self).__init__(*args, **kwargs) def get_grouped_choices(self): return groupby(list(self.iter_choices()), key=lambda x: x[3:]) # group by (group, group label) def iter_choices(self): for pk, obj in self._get_object_list(): yield (pk, self.get_label(obj), obj in self.data, self.get_group(obj), self.get_label(self.get_group(obj)) if self.get_group(obj) else None)
class IndicoStaticTextField(Field): """Return an html element with text taken from this field's value.""" widget = JinjaWidget('forms/static_text_widget.html') def __init__(self, *args, **kwargs): self.text_value = kwargs.pop('text', '') super().__init__(*args, **kwargs) def process_data(self, data): self.text_value = self.data = str(data) def _value(self): return self.text_value
class MultiStringField(HiddenField): """A field with multiple input text fields. :param field: A tuple ``(fieldname, title)`` where the title is used in the placeholder. :param uuid_field: If set, each item will have a UUID assigned and stored in the field specified here. :param unique: Whether the values should be unique. :param sortable: Whether items should be sortable. """ widget = JinjaWidget('forms/multiple_text_input_widget.html', single_line=True) def __init__(self, *args, **kwargs): self.field_name, self.field_caption = kwargs.pop('field') self.sortable = kwargs.pop('sortable', False) self.unique = kwargs.pop('unique', False) self.uuid_field = kwargs.pop('uuid_field', None) super(MultiStringField, self).__init__(*args, **kwargs) def process_formdata(self, valuelist): if is_preprocessed_formdata(valuelist): self.data = valuelist[0] elif valuelist: self.data = json.loads(valuelist[0]) if self.uuid_field: for item in self.data: if self.uuid_field not in item: item[self.uuid_field] = unicode(uuid.uuid4()) def pre_validate(self, form): if not all(isinstance(item, dict) for item in self.data): raise ValueError('Invalid data. Expected list of dicts.') if self.unique: unique_values = {item[self.field_name] for item in self.data} if len(unique_values) != len(self.data): raise ValueError('Items must be unique') if self.uuid_field: unique_uuids = { uuid.UUID(item[self.uuid_field], version=4) for item in self.data } if len(unique_uuids) != len(self.data): raise ValueError('UUIDs must be unique') if not all(item[self.field_name].strip() for item in self.data): raise ValueError('Empty items are not allowed') def _value(self): return self.data or []
class _SingleChoiceQuerySelectMultipleFieldGrouped(_SingleChoiceQuerySelectMultipleField): widget = JinjaWidget('events/abstracts/forms/select_grouped_widget.html') def __init__(self, *args, **kwargs): self.get_group = kwargs.pop('get_group', lambda x: x) super().__init__(*args, **kwargs) def get_grouped_choices(self): return groupby(list(self.iter_choices()), key=lambda x: x[3:]) # group by (group, group label) def iter_choices(self): yield ('__None', self.blank_text, self.data is None, None, None) for pk, obj in self._get_object_list(): yield (pk, self.get_label(obj), obj in self.data, self.get_group(obj), self.get_label(self.get_group(obj)) if self.get_group(obj) else None)
class IndicoDurationField(Field): widget = JinjaWidget('forms/duration_widget.html', single_line=True, single_kwargs=True) def _value(self): if self.data is None: return 0 else: return int(self.data.total_seconds()) def process_formdata(self, valuelist): if valuelist: self.data = timedelta(seconds=int(valuelist[0])) if self.data.total_seconds() % 60: Logger.get('forms').warning('Duration with seconds submitted') raise ValueError('Duration cannot contain seconds')
class PermissionsField(JSONField): widget = JinjaWidget('forms/permissions_widget.html', single_kwargs=True, acl=True) def __init__(self, *args, **kwargs): super(PermissionsField, self).__init__(*args, **kwargs) self.ip_networks = map(serialize_ip_network_group, IPNetworkGroup.query.filter_by(hidden=False)) self.permissions_info = get_permissions_info(Event)[0] @property def event(self): return self.get_form().event @property def roles(self): return [serialize_role(role) for role in sorted(self.get_form().event.roles, key=attrgetter('code'))] def _value(self): return self.data if self.data else '[]'
class PermissionsField(JSONField): from indico.modules.categories.models.categories import Category widget = JinjaWidget('forms/permissions_widget.html', single_kwargs=True, acl=True) type_mapping = { 'event': Event, 'session': Session, 'contribution': Contribution, 'category': Category } def __init__(self, *args, **kwargs): self.object_type = kwargs.pop('object_type') super(PermissionsField, self).__init__(*args, **kwargs) self.ip_networks = map(serialize_ip_network_group, IPNetworkGroup.query.filter_by(hidden=False)) @property def event(self): return self.get_form().event @property def category(self): if self.object_type == 'category': return self.get_form().category return self.event.category @property def event_roles(self): return [serialize_event_role(role) for role in sorted(self.event.roles, key=attrgetter('code'))] @property def category_roles(self): from indico.modules.categories.util import serialize_category_role from indico.modules.categories.models.roles import CategoryRole category_roles = CategoryRole.get_category_roles(self.category) return [serialize_category_role(role, legacy=True) for role in category_roles] @property def permissions_info(self): return get_permissions_info(PermissionsField.type_mapping[self.object_type])[0] def _value(self): return self.data if self.data else []
class EmailRuleListField(JSONField): """A field that stores a list of e-mail template rules.""" CAN_POPULATE = True widget = JinjaWidget('events/abstracts/forms/rule_list_widget.html') accepted_condition_types = (StateCondition, TrackCondition, ContributionTypeCondition) @classproperty @classmethod def condition_class_map(cls): return {r.name: r for r in cls.accepted_condition_types} @property def condition_choices(self): return { c.name: { 'title': c.description, 'labelText': c.label_text, 'options': list(c.get_available_values(event=self.event).viewitems()), 'compatibleWith': c.compatible_with, 'required': c.required } for c in self.accepted_condition_types } def pre_validate(self, form): super(EmailRuleListField, self).pre_validate(form) if not all(self.data): raise ValueError(_('Rules may not be empty')) if any('*' in crit for rule in self.data for crit in rule.itervalues()): # '*' (any) rules should never be included in the JSON, and having # such an entry would result in the rule never passing. raise ValueError('Unexpected "*" criterion') def _value(self): return super(EmailRuleListField, self)._value() if self.data else '[]'
class CategoryField(HiddenField): """WTForms field that lets you select a category. :param require_event_creation_rights: Whether to allow selecting only categories where the user can create events. """ widget = JinjaWidget('forms/category_picker_widget.html') def __init__(self, *args, **kwargs): self.navigator_category_id = 0 self.require_event_creation_rights = kwargs.pop( 'require_event_creation_rights', False) super().__init__(*args, **kwargs) def process_data(self, value): if not value: self.data = None return self.data = value self.navigator_category_id = value.id def process_formdata(self, valuelist): from indico.modules.categories import Category if valuelist: try: category_id = int(json.loads(valuelist[0])['id']) except KeyError: self.data = None else: self.data = Category.get(category_id, is_deleted=False) def _value(self): return { 'id': self.data.id, 'title': self.data.title } if self.data else {} def _get_data(self): return self.data
class EditableFileField(FileField): """A dropzone field that displays its current state and keeps track of deletes.""" widget = JinjaWidget('forms/dropzone_widget.html', editable=True) def __init__(self, *args, **kwargs): self.get_metadata = kwargs.pop('get_metadata', get_file_metadata) self.added_only = kwargs.pop('added_only', False) super().__init__(*args, **kwargs) self.widget_options['editable'] = True def process_formdata(self, valuelist): uploaded = [] deleted = [] for value in valuelist: if isinstance(value, FileStorage): uploaded.append(value) else: deleted = json.loads(value) if not self.allow_multiple_files: uploaded = uploaded[0] if uploaded else None deleted = deleted[0] if deleted else None self.data = uploaded if self.added_only else { 'added': uploaded, 'deleted': deleted } def _value(self): # If form validation fails we still have the dict from `process_formdata` # in `self.data` which cannot be serialized so we fallback to the default # data (if there is any, i.e. if we were editing something) # It would be cleaner to still take e.g. 'deleted' into account and # save/restore the selected files with JavaScript but in most cases our # client-side validation should not fail anyway... data = self.object_data if isinstance(self.data, dict) else self.data if self.allow_multiple_files: return [self.get_metadata(f) for f in data] if data else [] else: return self.get_metadata(data) if data else None