class fossirProtectionField(fossirEnumRadioField):
    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(fossirProtectionField, self).__init__(*args, enum=ProtectionMode, **kwargs)

    def render_protection_message(self):
        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, db.m.Event):
            parent_type = _('Event')
        elif isinstance(protected_object.protection_parent, db.m.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)
Example #2
0
def make_review_form(event, review_type):
    """Extends the paper WTForm to add the extra fields.

    Each extra field will use a field named ``custom_ID``.

    :param event: The `Event` for which to create the paper review form.
    :param review_type: The `PaperReviewType` for which to create the paper review form.
    :return: A `PaperReviewForm` subclass.
    """
    form_class = type(b'_PaperReviewForm', (PaperReviewForm, ), {})
    for idx, question in enumerate(
            event.cfp.get_questions_for_review_type(review_type), start=1):
        name = 'question_{}'.format(question.id)
        range_ = event.cfp.rating_range
        field = RadioField(question.text,
                           validators=[DataRequired()],
                           choices=[(unicode(n), unicode(n))
                                    for n in range(range_[0], range_[1] + 1)],
                           widget=JinjaWidget(
                               'events/reviews/rating_widget.html',
                               question=question,
                               rating_range=event.cfp.rating_range,
                               inline_js=True,
                               question_idx=idx))
        setattr(form_class, name, field)
    return form_class
Example #3
0
class fossirPalettePickerField(JSONField):
    """Field allowing user to pick a color from a set of predefined values"""

    widget = JinjaWidget('forms/palette_picker_widget.html')
    CAN_POPULATE = True

    def __init__(self, *args, **kwargs):
        self.color_list = kwargs.pop('color_list')
        super(fossirPalettePickerField, self).__init__(*args, **kwargs)

    def pre_validate(self, form):
        if self.data not in self.color_list:
            raise ValueError(_('Invalid colors selected'))

    def process_formdata(self, valuelist):
        super(fossirPalettePickerField, self).process_formdata(valuelist)
        self.data = ColorTuple(self.data['text'], self.data['background'])

    def process_data(self, value):
        super(fossirPalettePickerField, self).process_data(value)
        if self.object_data and self.object_data not in self.color_list:
            self.color_list = self.color_list + [self.object_data]

    def _value(self):
        return self.data._asdict()
class EventPersonLinkListField(PersonLinkListFieldBase):
    """A field to manage event's chairpersons"""

    person_link_cls = EventPersonLink
    linked_object_attr = 'event'
    widget = JinjaWidget('events/forms/event_person_link_widget.html')

    def __init__(self, *args, **kwargs):
        self.allow_submitters = True
        self.default_is_submitter = kwargs.pop('default_is_submitter', True)
        super(EventPersonLinkListField, 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
        }

    def _serialize_person_link(self, principal, extra_data=None):
        extra_data = extra_data or {}
        data = dict(extra_data, **serialize_person_link(principal))
        data['isSubmitter'] = self.data[principal] if self.get_form(
        ).is_submitted() else principal.is_submitter
        return data

    def pre_validate(self, form):
        super(PersonLinkListFieldBase, self).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)
Example #5
0
class fossirMarkdownField(TextAreaField):
    """A Markdown-enhanced textarea.

    When using the editor you need to include the markdown JS/CSS
    bundles and also the MathJax JS bundle (even when using only
    the editor without Mathjax).

    :param editor: Whether to use the WMD widget with its live preview
    :param mathjax: Whether to use MathJax in the WMD live preview
    """

    widget = JinjaWidget('forms/markdown_widget.html',
                         single_kwargs=True,
                         rows=5)

    def __init__(self, *args, **kwargs):
        self.use_editor = kwargs.pop('editor', False)
        self.use_mathjax = kwargs.pop('mathjax', False)
        orig_id = kwargs['_prefix'] + kwargs['_name']
        if self.use_editor:
            # WMD relies on this awful ID :/
            kwargs['id'] = 'wmd-input-f_' + orig_id
        else:
            kwargs.setdefault(
                'description',
                _(u"You can use Markdown or basic HTML formatting tags."))
        super(fossirMarkdownField, self).__init__(*args, **kwargs)
        self.orig_id = orig_id
class fossirEmailRecipientsField(Field):
    widget = JinjaWidget('forms/email_recipients_widget.html',
                         single_kwargs=True)

    def process_data(self, data):
        self.data = sorted(data, key=unicode.lower)
        self.text_value = ', '.join(data)
        self.count = len(data)
Example #7
0
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 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 fossirDateField(DateField):
    widget = JinjaWidget('forms/date_widget.html',
                         single_line=True,
                         single_kwargs=True)

    def __init__(self, *args, **kwargs):
        super(fossirDateField, self).__init__(*args,
                                              parse_kwargs={'dayfirst': True},
                                              display_format='%d/%m/%Y',
                                              **kwargs)
Example #10
0
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 '[]'
Example #11
0
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})
Example #12
0
class PrincipalField(PrincipalListField):
    """A field that lets you select an fossir 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]
Example #13
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
        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))
Example #14
0
class fossirStaticTextField(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(fossirStaticTextField, self).__init__(*args, **kwargs)

    def process_data(self, data):
        self.text_value = self.data = unicode(data)

    def _value(self):
        return self.text_value
Example #15
0
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 '[]'
Example #16
0
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(EditableFileField, self).__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
Example #17
0
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 fossirWeekDayRepetitionField(Field):
    """ Field that lets you select an ordinal day of the week."""

    widget = JinjaWidget('forms/week_day_repetition_widget.html',
                         single_line=True)

    WEEK_DAY_NUMBER_CHOICES = ((1, _('first')), (2, _('second')),
                               (3, _('third')), (4, _('fourth')), (-1,
                                                                   _('last')))

    def __init__(self, *args, **kwargs):
        locale = get_current_locale()
        self.day_number_options = self.WEEK_DAY_NUMBER_CHOICES
        self.week_day_options = [(n, locale.weekday(n, short=False))
                                 for n in xrange(7)]
        self.week_day_options.append((-1, _('Any day')))
        self.day_number_missing = False
        self.week_day_missing = False
        super(fossirWeekDayRepetitionField, self).__init__(*args, **kwargs)

    def process_formdata(self, valuelist):
        self.data = ()
        if any(valuelist):
            if not valuelist[0]:
                self.day_number_missing = True
            if not valuelist[1]:
                self.week_day_missing = True
        if valuelist:
            self.data = tuple(map(int, valuelist))

    @property
    def day_number_data(self):
        return self.data[0] if len(self.data) > 0 else None

    @property
    def week_day_data(self):
        return self.data[1] if len(self.data) > 1 else None
Example #19
0
class PaperEmailSettingsField(JSONField):
    CAN_POPULATE = True
    widget = JinjaWidget('events/papers/forms/paper_email_settings_widget.html')

    @property
    def event(self):
        return self.get_form().event

    def process_formdata(self, valuelist):
        if valuelist:
            self.data = json.loads(valuelist[0])
            data = {}
            for key, value in self.data.iteritems():
                data[key] = RoleConverter.to_python(value) if isinstance(value, list) else value
            self.data = data

    def _value(self):
        return {
            'notify_on_added_to_event': [x.name for x in settings.get(self.event, 'notify_on_added_to_event')],
            'notify_on_assigned_contrib': [x.name for x in settings.get(self.event, 'notify_on_assigned_contrib')],
            'notify_on_paper_submission': [x.name for x in settings.get(self.event, 'notify_on_paper_submission')],
            'notify_judge_on_review': settings.get(self.event, 'notify_judge_on_review'),
            'notify_author_on_judgment': settings.get(self.event, 'notify_author_on_judgment')
        }
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 set(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]
Example #21
0
class PrincipalListField(HiddenField):
    """A field that lets you select a list fossir 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 fossir 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 fossir 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)
        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)

    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 _serialize_principal(self, principal):
        if principal.principal_type == PrincipalType.email:
            return principal.fossilize()
        elif principal.principal_type == PrincipalType.network:
            return serialize_ip_network_group(principal)
        elif principal.principal_type == PrincipalType.user:
            return serialize_user(principal)
        elif principal.is_group:
            return serialize_group(principal)
        else:
            raise ValueError('Invalid principal: {} ({})'.format(
                principal, principal.principal_type))

    def _value(self):
        from fossir.modules.events.models.persons import PersonLinkBase

        def key(obj):
            if isinstance(obj, PersonLinkBase):
                return obj.name.lower()
            return obj.principal_type, obj.name.lower()

        principals = sorted(self._get_data(), key=key)
        return map(self._serialize_principal, principals)

    def _get_data(self):
        return sorted(self.data) if self.data else []
class TimeDeltaField(Field):
    """A field that lets the user select a simple timedelta.

    It does not support mixing multiple units, but it is smart enough
    to switch to a different unit to represent a timedelta that could
    not be represented otherwise.

    :param units: The available units. Must be a tuple containing any
                  any of 'seconds', 'minutes', 'hours' and 'days'.
                  If not specified, ``('hours', 'days')`` is assumed.
    """

    widget = JinjaWidget('forms/timedelta_widget.html',
                         single_line=True,
                         single_kwargs=True)
    # XXX: do not translate, "Minutes" is ambiguous without context
    unit_names = {
        'seconds': 'Seconds',
        'minutes': 'Minutes',
        'hours': 'Hours',
        'days': 'Days'
    }
    magnitudes = OrderedDict([('days', 86400), ('hours', 3600),
                              ('minutes', 60), ('seconds', 1)])

    def __init__(self, *args, **kwargs):
        self.units = kwargs.pop('units', ('hours', 'days'))
        super(TimeDeltaField, self).__init__(*args, **kwargs)

    @property
    def best_unit(self):
        """Return the largest unit that covers the current timedelta"""
        if self.data is None:
            return None
        seconds = int(self.data.total_seconds())
        for unit, magnitude in self.magnitudes.iteritems():
            if not seconds % magnitude:
                return unit
        return 'seconds'

    @property
    def choices(self):
        best_unit = self.best_unit
        choices = [(unit, self.unit_names[unit]) for unit in self.units]
        # Add whatever unit is necessary to represent the currenet value if we have one
        if best_unit and best_unit not in self.units:
            choices.append(
                (best_unit, '({})'.format(self.unit_names[best_unit])))
        return choices

    def process_formdata(self, valuelist):
        if valuelist and len(valuelist) == 2:
            value = int(valuelist[0])
            unit = valuelist[1]
            if unit not in self.magnitudes:
                raise ValueError('Invalid unit')
            self.data = timedelta(seconds=self.magnitudes[unit] * value)

    def pre_validate(self, form):
        if self.best_unit in self.units:
            return
        if self.object_data is None:
            raise ValueError(_('Please choose a valid unit.'))
        if self.object_data != self.data:
            raise ValueError(
                _('Please choose a different unit or keep the previous value.')
            )

    def _value(self):
        if self.data is None:
            return '', ''
        else:
            return int(self.data.total_seconds()) // self.magnitudes[
                self.best_unit], self.best_unit
class RelativeDeltaField(Field):
    """A field that lets the user select a simple timedelta.

    It does not support mixing multiple units, but it is smart enough
    to switch to a different unit to represent a timedelta that could
    not be represented otherwise.

    :param units: The available units. Must be a tuple containing any
                  any of 'seconds', 'minutes', 'hours' and 'days'.
                  If not specified, ``('hours', 'days')`` is assumed.
    """

    widget = JinjaWidget('forms/timedelta_widget.html',
                         single_line=True,
                         single_kwargs=True)
    # XXX: do not translate, "Minutes" is ambiguous without context
    unit_names = {
        'seconds': 'Seconds',
        'minutes': 'Minutes',
        'hours': 'Hours',
        'days': 'Days',
        'weeks': 'Weeks',
        'months': 'Months',
        'years': 'Years'
    }
    magnitudes = OrderedDict([('years', relativedelta(years=1)),
                              ('months', relativedelta(months=1)),
                              ('weeks', relativedelta(weeks=1)),
                              ('days', relativedelta(days=1)),
                              ('hours', relativedelta(hours=1)),
                              ('minutes', relativedelta(minutes=1)),
                              ('seconds', relativedelta(seconds=1))])

    def __init__(self, *args, **kwargs):
        self.units = kwargs.pop('units', ('hours', 'days'))
        super(RelativeDeltaField, self).__init__(*args, **kwargs)

    @property
    def split_data(self):
        if self.data is None:
            return None, None
        for unit in self.magnitudes:
            number = getattr(self.data, unit)
            if number:
                return number, unit
        raise ValueError('Unsupported relativedelta() unit')

    @property
    def choices(self):
        choices = [(unit, self.unit_names[unit]) for unit in self.units]
        number, unit = self.split_data
        if number is not None and unit not in self.units:
            # Add whatever unit is necessary to represent the currenet value if we have one
            choices.append((unit, '({})'.format(self.unit_names[unit])))
        return choices

    def process_formdata(self, valuelist):
        if valuelist and len(valuelist) == 2:
            value = int(valuelist[0])
            unit = valuelist[1]
            if unit not in self.magnitudes:
                raise ValueError('Invalid unit')
            self.data = (self.magnitudes[unit] * value).normalized()

    def pre_validate(self, form):
        if self.object_data is None:
            raise ValueError(_('Please choose a valid unit.'))

    def _value(self):
        if self.data is None:
            return '', ''
        return self.split_data
Example #24
0
class CategoryField(HiddenField):
    """WTForms field that lets you select a category.

    :param allow_events: Whether to allow selecting a category that
                         contains events.
    :param allow_subcats: Whether to allow selecting a category that
                          contains subcategories.
    :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.allow_events = kwargs.pop('allow_events', True)
        self.allow_subcats = kwargs.pop('allow_subcats', True)
        self.require_event_creation_rights = kwargs.pop(
            'require_event_creation_rights', False)
        super(CategoryField, self).__init__(*args, **kwargs)

    def pre_validate(self, form):
        if self.data:
            self._validate(self.data)

    def process_data(self, value):
        if not value:
            self.data = None
            return
        try:
            self._validate(value)
        except ValueError:
            self.data = None
            self.navigator_category_id = value.id
        else:
            self.data = value
            self.navigator_category_id = value.id

    def process_formdata(self, valuelist):
        from fossir.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 _validate(self, category):
        if not self.allow_events and category.has_only_events:
            raise ValueError(
                _("Categories containing only events are not allowed."))
        if not self.allow_subcats and category.children:
            raise ValueError(
                _("Categories containing subcategories are not allowed."))

    def _value(self):
        return {
            'id': self.data.id,
            'title': self.data.title
        } if self.data else {}

    def _get_data(self):
        return self.data
Example #25
0
class fossirRadioField(RadioField):
    widget = JinjaWidget('forms/radio_buttons_widget.html', single_kwargs=True)

    def __init__(self, *args, **kwargs):
        self.option_orientation = kwargs.pop('orientation', 'vertical')
        super(fossirRadioField, self).__init__(*args, **kwargs)
Example #26
0
class fossirTagListField(HiddenFieldList):
    widget = JinjaWidget('forms/tag_list_widget.html', single_kwargs=True)
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 flat: If True, the field returns a list of string values instead
                 of dicts.  Cannot be combined with `uuid_field`.
    :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.flat = kwargs.pop('flat', False)
        self.uuid_field = kwargs.pop('uuid_field', None)
        if self.flat and self.uuid_field:
            raise ValueError('`uuid_field` and `flat` are mutually exclusive')
        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):
        try:
            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')
        finally:
            if self.flat:
                self.data = [x[self.field_name] for x in self.data]

    def _value(self):
        if not self.data:
            return []
        elif self.flat:
            return [{self.field_name: x} for x in self.data]
        else:
            return self.data
class fossirQuerySelectMultipleCheckboxField(fossirQuerySelectMultipleField):
    option_widget = CheckboxInput()
    widget = JinjaWidget('forms/checkbox_group_widget.html',
                         single_kwargs=True)
Example #29
0
class fossirEnumRadioField(fossirEnumSelectField):
    widget = JinjaWidget('forms/radio_buttons_widget.html',
                         orientation='horizontal',
                         single_kwargs=True)
    option_widget = RadioInput()
class MultipleItemsField(HiddenField):
    """A field with multiple items consisting of multiple string values.

    :param fields: A list of dicts with the following arguments:
                   'id': the unique ID of the field
                   'caption': the title of the column and the placeholder
                   'type': 'text|number|select', the type of the field
                   'coerce': callable to convert the value to a python type.
                             the type must be comvertible back to a string,
                             so usually you just want something like `int`
                             or `float` here.
                   In case the type is 'select', the property 'choices' of the
                   `MultipleItemsField` or the 'choices' kwarg needs to be a dict
                   where the key is the 'id' of the select field and the value is
                   another dict mapping the option's id to it caption.
    :param uuid_field: If set, each item will have a UUID assigned and
                       stored in the field specified here.  The name
                       specified here may not be in `fields`.
    :param uuid_field_opaque: If set, the `uuid_field` is considered opaque,
                              i.e. it is never touched by this field.  This
                              is useful when you subclass the field and use
                              e.g. actual database IDs instead of UUIDs.
    :param unique_field: The name of a field in `fields` that needs
                         to be unique.
    :param sortable: Whether items should be sortable.
    """

    widget = JinjaWidget('forms/multiple_items_widget.html')

    def __init__(self, *args, **kwargs):
        self.fields = getattr(self, 'fields', None) or kwargs.pop('fields')
        self.uuid_field = kwargs.pop('uuid_field', None)
        self.uuid_field_opaque = kwargs.pop('uuid_field_opaque', False)
        self.unique_field = kwargs.pop('unique_field', None)
        self.sortable = kwargs.pop('sortable', False)
        self.choices = getattr(self, 'choices', kwargs.pop('choices', {}))
        self.serialized_data = {}
        if self.uuid_field:
            assert self.uuid_field != self.unique_field
            assert self.uuid_field not in self.fields
        self.field_names = {
            item['id']: item['caption']
            for item in self.fields
        }
        super(MultipleItemsField, self).__init__(*args, **kwargs)

    def process_formdata(self, valuelist):
        if is_preprocessed_formdata(valuelist):
            self.data = valuelist[0]
        if valuelist:
            self.data = json.loads(valuelist[0])
            # Preserve dict data, because the self.data can be modified by a subclass
            self.serialized_data = json.loads(valuelist[0])
            if self.uuid_field and not self.uuid_field_opaque:
                for item in self.data:
                    if self.uuid_field not in item:
                        item[self.uuid_field] = unicode(uuid.uuid4())

    def pre_validate(self, form):
        unique_used = set()
        uuid_used = set()
        coercions = {
            f['id']: f['coerce']
            for f in self.fields if f.get('coerce') is not None
        }
        for i, item in enumerate(self.serialized_data):
            if not isinstance(item, dict):
                raise ValueError('Invalid item type: {}'.format(
                    type(item).__name__))
            item_keys = set(item)
            if self.uuid_field:
                item_keys.discard(self.uuid_field)
            if item_keys != {x['id'] for x in self.fields}:
                raise ValueError('Invalid item (bad keys): {}'.format(
                    escape(', '.join(item.viewkeys()))))
            if self.unique_field:
                if item[self.unique_field] in unique_used:
                    raise ValueError('{} must be unique'.format(
                        self.field_names[self.unique_field]))
                unique_used.add(item[self.unique_field])
            if self.uuid_field and not self.uuid_field_opaque:
                if item[self.uuid_field] in uuid_used:
                    raise ValueError('UUID must be unique')
                # raises ValueError if uuid is invalid
                uuid.UUID(item[self.uuid_field], version=4)
                uuid_used.add(item[self.uuid_field])
            for key, fn in coercions.viewitems():
                try:
                    self.data[i][key] = fn(self.data[i][key])
                except ValueError:
                    raise ValueError(
                        u"Invalid value for field '{}': {}".format(
                            self.field_names[key], escape(item[key])))

    def _value(self):
        return self.data or []

    @property
    def _field_spec(self):
        # Field data for the widget; skip non-json-serializable data
        return [{k: v
                 for k, v in field.iteritems() if k != 'coerce'}
                for field in self.fields]