class AbstractReviewingSettingsForm(IndicoForm): """Settings form for abstract reviewing.""" scale_lower = IntegerField(_('Scale (from)'), [InputRequired()]) scale_upper = IntegerField(_('Scale (to)'), [InputRequired()]) allow_convener_judgment = BooleanField(_('Allow track conveners to judge'), widget=SwitchWidget(), description=_('Enabling this allows track conveners to make a judgment ' 'such as accepting or rejecting an abstract.')) allow_convener_track_change = BooleanField(_('Allow conveners to change tracks'), [HiddenUnless('allow_convener_judgment', value=False, preserve_data=True)], widget=SwitchWidget(), description=_('Enabling this allows track conveners to update the track ' 'an abstract is part of.')) allow_comments = BooleanField(_('Allow comments'), widget=SwitchWidget(), description=_('Enabling this allows judges, conveners and reviewers to leave ' 'comments on abstracts.')) allow_contributors_in_comments = BooleanField(_('Allow contributors in comments'), [HiddenUnless('allow_comments', preserve_data=True)], widget=SwitchWidget(), description=_('Enabling this allows submitters, authors, and ' 'speakers to also participate in the comments.')) reviewing_instructions = IndicoMarkdownField(_('Reviewing Instructions'), editor=True, description=_('These instructions will be displayed right before the ' 'reviewing form.')) judgment_instructions = IndicoMarkdownField(_('Judgment Instructions'), editor=True, description=_('These instructions will be displayed right before the ' 'decision box.')) def __init__(self, *args, **kwargs): self.event = kwargs.pop('event') self.has_ratings = kwargs.pop('has_ratings', False) super().__init__(*args, **kwargs) if self.has_ratings: self.scale_upper.warning = _('Changing the ratings scale will proportionally affect existing ratings.') def validate_scale_upper(self, field): lower = self.scale_lower.data upper = self.scale_upper.data if lower is None or upper is None: return if lower >= upper: raise ValidationError(_("The scale's 'to' value must be greater than the 'from' value.")) if upper - lower > 20: raise ValidationError(_("The difference between 'to' and' from' may not be greater than 20."))
class AttachmentFolderForm(IndicoForm): title = HiddenField(_("Name"), [DataRequired()], widget=TypeaheadWidget(), description=_("The name of the folder.")) description = TextAreaField(_("Description"), description=_("Description of the folder and its content")) protected = BooleanField(_("Protected"), widget=SwitchWidget()) acl = AccessControlListField(_("Access control list"), [UsedIf(lambda form, field: form.protected.data)], allow_groups=True, allow_external_users=True, default_text=_('Restrict access to this folder'), description=_("The list of users and groups allowed to access the folder")) is_always_visible = BooleanField(_("Always Visible"), [HiddenUnless('is_hidden', value=False)], widget=SwitchWidget(), description=_("By default, folders are always visible, even if a user cannot " "access them. You can disable this behavior here, hiding the folder " "for anyone who does not have permission to access it.")) is_hidden = BooleanField(_("Always hidden"), [HiddenUnless('is_always_visible', value=False)], widget=SwitchWidget(), description=_("Always hide the folder and its contents from public display areas of " "the event. You can use this for folders to store non-image files used " "e.g. in download links. The access permissions still apply.")) def __init__(self, *args, **kwargs): self.linked_object = kwargs.pop('linked_object') super(AttachmentFolderForm, self).__init__(*args, **kwargs) self.title.choices = self._get_title_suggestions() def _get_title_suggestions(self): query = db.session.query(AttachmentFolder.title).filter_by(is_deleted=False, is_default=False, object=self.linked_object) existing = set(x[0] for x in query) suggestions = set(get_default_folder_names()) - existing if self.title.data: suggestions.add(self.title.data) return sorted(suggestions) def validate_is_always_visible(self, field): if self.is_always_visible.data and self.is_hidden.data: raise ValidationError('These two options cannot be used at the same time') validate_is_hidden = validate_is_always_visible @generated_data def protection_mode(self): return ProtectionMode.protected if self.protected.data else ProtectionMode.inheriting
class AttachmentPackageForm(IndicoForm): added_since = IndicoDateField(_('Added Since'), [Optional()], description=_('Include only attachments uploaded after this date')) filter_type = IndicoRadioField(_('Include'), [DataRequired()]) sessions = IndicoSelectMultipleCheckboxField(_('Sessions'), [HiddenUnless('filter_type', 'sessions'), DataRequired()], description=_('Include materials from selected sessions'), coerce=int) contributions = IndicoSelectMultipleCheckboxField(_('Contributions'), [HiddenUnless('filter_type', 'contributions'), DataRequired()], description=_('Include materials from selected contributions'), coerce=int) dates = IndicoSelectMultipleCheckboxField(_('Events scheduled on'), [HiddenUnless('filter_type', 'dates'), DataRequired()], description=_('Include materials from sessions/contributions scheduled ' 'on the selected dates'))
class DeadlineForm(IndicoForm): deadline = IndicoDateTimeField(_("Deadline"), [Optional()], default_time=time(23, 59)) enforce = BooleanField(_("Enforce deadline"), [HiddenUnless('deadline')], widget=SwitchWidget()) def __init__(self, *args, **kwargs): self.event = kwargs.pop('event') super(DeadlineForm, self).__init__(*args, **kwargs)
class TicketsForm(IndicoForm): tickets_enabled = BooleanField(_('Enable Tickets'), widget=SwitchWidget(), description=_('Create tickets for registrations using this registration form.')) ticket_on_email = BooleanField(_('Attach to registration e-mail'), [HiddenUnless('tickets_enabled', preserve_data=True)], widget=SwitchWidget(), description=_('Attach PDF ticket to the email sent to a user after completing ' 'their registration.')) ticket_on_event_page = BooleanField(_('Download from event homepage'), [HiddenUnless('tickets_enabled', preserve_data=True)], widget=SwitchWidget(), description=_('Allow users to download their ticket from the ' 'conference homepage.')) ticket_on_summary_page = BooleanField(_('Download from summary page'), [HiddenUnless('tickets_enabled', preserve_data=True)], widget=SwitchWidget(), description=_('Allow users to download their ticket from the registration ' 'summary page.'))
class AbstractSubmissionSettingsForm(IndicoForm): """Settings form for abstract submission.""" announcement = IndicoMarkdownField(_('Announcement'), editor=True) allow_multiple_tracks = BooleanField(_('Multiple tracks'), widget=SwitchWidget(), description=_('Allow the selection of multiple tracks')) tracks_required = BooleanField(_('Require tracks'), widget=SwitchWidget(), description=_('Make the track selection mandatory')) contrib_type_required = BooleanField(_('Require contrib. type'), widget=SwitchWidget(), description=_('Make the selection of a contribution type mandatory')) allow_attachments = BooleanField(_('Allow attachments'), widget=SwitchWidget(), description=_('Allow files to be attached to the abstract')) copy_attachments = BooleanField(_('Copy attachments'), [HiddenUnless('allow_attachments')], widget=SwitchWidget(), description=_('Copy attachments to the contribution when accepting an abstract')) allow_speakers = BooleanField(_('Allow speakers'), widget=SwitchWidget(), description=_('Allow the selection of the abstract speakers')) speakers_required = BooleanField(_('Require a speaker'), [HiddenUnless('allow_speakers')], widget=SwitchWidget(), description=_('Make the selection of at least one author as speaker mandatory')) allow_editing = IndicoEnumSelectField(_('Allow editing'), enum=AllowEditingType, sorted=True, description=_('Specify who will be able to edit the abstract')) contribution_submitters = IndicoEnumSelectField(_('Contribution submitters'), enum=SubmissionRightsType, sorted=True, description=_('Specify who will get contribution submission rights ' 'once an abstract has been accepted')) authorized_submitters = PrincipalListField(_('Authorized submitters'), event=lambda form: form.event, allow_external_users=True, allow_groups=True, allow_event_roles=True, allow_category_roles=True, description=_('These users may always submit abstracts, ' 'even outside the regular submission period.')) submission_instructions = IndicoMarkdownField(_('Instructions'), editor=True, description=_('These instructions will be displayed right before the ' 'submission form')) @generated_data def announcement_render_mode(self): return RenderMode.markdown def __init__(self, *args, **kwargs): self.event = kwargs.pop('event') super().__init__(*args, **kwargs) def validate_contrib_type_required(self, field): if field.data and not self.event.contribution_types.count(): raise ValidationError(_('The event has no contribution types defined.'))
class InvitedAbstractMixin(object): users_with_no_account = IndicoRadioField(_('Type of user'), [DataRequired()], default='existing', choices=(('existing', _('Existing user')), ('new', _('New user')))) submitter = PrincipalField( _('Submitter'), [HiddenUnless('users_with_no_account', 'existing'), DataRequired()], allow_external_users=True, description=_('The person invited to submit the abstract')) first_name = StringField( _('First name'), [HiddenUnless('users_with_no_account', 'new'), DataRequired()]) last_name = StringField( _('Family name'), [HiddenUnless('users_with_no_account', 'new'), DataRequired()]) email = EmailField( _('Email address'), [HiddenUnless('users_with_no_account', 'new'), DataRequired()], filters=[lambda x: x.lower() if x else x]) def __init__(self, *args, **kwargs): self.event = kwargs['event'] super(InvitedAbstractMixin, self).__init__(*args, **kwargs) def validate_email(self, field): if get_user_by_email(field.data): raise ValidationError( _('There is already a user with this email address')) def validate(self): from indico.modules.events.abstracts.util import can_create_invited_abstracts if not can_create_invited_abstracts(self.event): raise ValidationError( _('You have to create an "Invited" abstract notification template in order to ' 'be able to create invited abstracts.')) else: return super(InvitedAbstractMixin, self).validate()
class MultiSelectConfigForm: options = MultiStringField( _('Options'), [DataRequired()], field=('option', _('option')), unique=True, uuid_field='id', sortable=True, description=_('Specify the answers the user can select')) min_choices = IntegerField( _("Minimum choices"), [HiddenUnless('is_required'), Optional(), NumberRange(min=0)], description=_("The minimum amount of options the user has to choose.")) max_choices = IntegerField( _("Maximum choices"), [HiddenUnless('is_required'), Optional(), NumberRange(min=1)], description=_("The maximum amount of options the user may choose.")) def _validate_min_max_choices(self): if (self.min_choices.data is not None and self.max_choices.data is not None and self.min_choices.data > self.max_choices.data): raise ValidationError( _('Maximum choices must be greater than minimum choices.')) def validate_min_choices(self, field): if field.data is None: return if field.data >= len(self.options.data): raise ValidationError( _("Minimum choices must be fewer than the total number of options." )) def validate_max_choices(self, field): if field.data is None: return self._validate_min_max_choices() if field.data > len(self.options.data): raise ValidationError( _("Maximum choices must be fewer or equal than the total number of options." ))
class SurveyForm(IndicoForm): _notification_fields = ('notifications_enabled', 'notify_participants', 'start_notification_emails', 'new_submission_emails') title = StringField(_("Title"), [DataRequired()], description=_("The title of the survey")) introduction = TextAreaField(_("Introduction"), description=_("An introduction to be displayed before the survey")) anonymous = BooleanField(_("Anonymous submissions"), widget=SwitchWidget(), description=_("User information will not be attached to submissions")) require_user = BooleanField(_("Only logged-in users"), [HiddenUnless('anonymous')], widget=SwitchWidget(), description=_("Still require users to be logged in for submitting the survey")) limit_submissions = BooleanField(_("Limit submissions"), widget=SwitchWidget(), description=_("Whether there is a submission cap")) submission_limit = IntegerField(_("Capacity"), [HiddenUnless('limit_submissions'), DataRequired(), NumberRange(min=1)], description=_("Maximum number of submissions accepted")) notifications_enabled = BooleanField(_('Enabled'), widget=SwitchWidget(), description=_('Send email notifications for specific events related to the ' 'survey.')) notify_participants = BooleanField(_('Participants'), [HiddenUnless('notifications_enabled', preserve_data=True)], widget=SwitchWidget(), description=_('Notify participants of the event when this survey starts.')) start_notification_emails = EmailListField(_('Start notification recipients'), [HiddenUnless('notifications_enabled', preserve_data=True)], description=_('Email addresses to notify about the start of the survey')) new_submission_emails = EmailListField(_('New submission notification recipients'), [HiddenUnless('notifications_enabled', preserve_data=True)], description=_('Email addresses to notify when a new submission is made')) def __init__(self, *args, **kwargs): self.event = kwargs.pop('event', None) super(IndicoForm, self).__init__(*args, **kwargs) def validate_title(self, field): query = Survey.find(Survey.event_id == self.event.id, db.func.lower(Survey.title) == field.data.lower(), Survey.title != field.object_data, ~Survey.is_deleted) if query.count(): raise ValidationError(_("There is already an survey named \"{}\" on this event".format(escape(field.data)))) def post_validate(self): if not self.anonymous.data: self.require_user.data = True
class NewBookingSimpleForm(NewBookingConfirmForm): submit_check = SubmitField(_('Check conflicts')) booking_reason = TextAreaField(_('Reason'), [UsedIf(lambda form, field: not form.submit_check.data), DataRequired()]) room_usage = RadioField(validators=[UsedIf(lambda form, field: not form.submit_check.data), DataRequired()], choices=[('current_user', _("I'll be using the room myself")), ('other_user', _("I'm booking the room for someone else"))]) booked_for_user = PrincipalField(_('User'), [UsedIf(lambda form, field: not form.submit_check.data), HiddenUnless('room_usage', 'other_user'), DataRequired()], allow_external=True)
class UserPreferencesForm(IndicoForm): lang = SelectField(_('Language')) timezone = SelectField(_('Timezone')) force_timezone = BooleanField( _('Use my timezone'), widget=SwitchWidget(), description=_( "Always use my current timezone instead of an event's timezone.")) show_future_events = BooleanField( _('Show future events'), widget=SwitchWidget(), description=_('Show future events by default.')) show_past_events = BooleanField( _('Show past events'), widget=SwitchWidget(), description=_('Show past events by default.')) name_format = IndicoEnumSelectField( _('Name format'), enum=NameFormat, description=_('Default format in which names are displayed')) use_previewer_pdf = BooleanField( _('Use previewer for PDF files'), widget=SwitchWidget(), description= _('The previewer is used by default for image and text files, but not for PDF files.' )) add_ical_alerts = BooleanField( _('Add alerts to iCal'), widget=SwitchWidget(), description=_('Add an event reminder to exported iCal files/URLs.')) add_ical_alerts_mins = IntegerField( _('iCal notification time'), [HiddenUnless('add_ical_alerts'), NumberRange(min=0)], description=_('Number of minutes to notify before an event.')) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) locales = [(code, f'{name} ({territory})' if territory else name) for code, (name, territory, __) in get_all_locales().items()] self.lang.choices = sorted(locales, key=itemgetter(1)) self.timezone.choices = list(zip(common_timezones, common_timezones)) if self.timezone.object_data and self.timezone.object_data not in common_timezones_set: self.timezone.choices.append( (self.timezone.object_data, self.timezone.object_data))
def __init__(self, *args, **kwargs): if config.LOCAL_IDENTITIES: for field in ('username', 'password', 'confirm_password'): inject_validators(self, field, [HiddenUnless('create_identity')], early=True) super(AdminAccountRegistrationForm, self).__init__(*args, **kwargs) del self.comment if not config.LOCAL_IDENTITIES: del self.username del self.password del self.confirm_password del self.create_identity
class CloneRepeatUntilFormBase(CloneRepeatOnceForm): stop_criterion = IndicoRadioField( _('Clone'), [DataRequired()], default='num_times', choices=(('day', _('Until a given day (inclusive)')), ('num_times', _('A number of times')))) until_dt = IndicoDateField( _('Day'), [HiddenUnless('stop_criterion', 'day'), DataRequired()]) num_times = IntegerField(_('Number of times'), [ HiddenUnless('stop_criterion', 'num_times'), DataRequired(), NumberRange(1, 100, message=_("You can clone a maximum of 100 times")) ], default=2) def __init__(self, event, **kwargs): kwargs.setdefault('until_dt', (self._calc_start_dt(event) + timedelta(days=14)).date()) super(CloneRepeatUntilFormBase, self).__init__(event, **kwargs)
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 EventSettingsForm(PaymentEventSettingsFormBase): use_event_api_keys = BooleanField( _('Use event API keys'), [Optional()], default=False, description=_('Override the organization Stripe API keys.'), widget=SwitchWidget(), ) pub_key = StringField( _('Publishable key'), [ HiddenUnless('use_event_api_keys'), UsedIf(lambda form, _: form.use_event_api_keys.data), DataRequired(), ], description=_('Publishable API key for the stripe.com account')) sec_key = StringField( _('Secret key'), [ HiddenUnless('use_event_api_keys'), UsedIf(lambda form, _: form.use_event_api_keys.data), DataRequired(), ], description=_('Secret API key for the stripe.com account')) org_name = StringField(_('Organizer name'), [Optional()], default='Organization', description=_('Name of the event organizer')) description = StringField( _('Description'), [Optional()], default='Payment for conference', description=_( 'A description of the product or service being purchased')) require_postal_code = BooleanField( _('Require postal code input'), [Optional()], default=False, description=_( 'Require registrants to input their postal code when filling the' ' payment form. Enabling this will decrease the chance of the' ' payment being marked as fraudulent.'), widget=SwitchWidget(), )
class BulkAbstractJudgmentForm(AbstractJudgmentFormBase): _order = ('judgment', 'accepted_track', 'override_contrib_type', 'accepted_contrib_type', 'session', 'duplicate_of', 'merged_into', 'merge_persons', 'judgment_comment', 'send_notifications') judgment = HiddenEnumField(enum=AbstractAction, skip={AbstractAction.change_tracks}) abstract_id = HiddenFieldList() submitted = HiddenField() override_contrib_type = BooleanField(_("Override contribution type"), [HiddenUnless('judgment', AbstractAction.accept)], widget=SwitchWidget(), description=_("Override the contribution type for all selected abstracts")) def __init__(self, *args, **kwargs): self.event = kwargs.pop('event') super(BulkAbstractJudgmentForm, self).__init__(*args, **kwargs) if self.accepted_track: self.accepted_track.description = _("The abstracts will be accepted in this track") if self.accepted_contrib_type: self.accepted_contrib_type.description = _("The abstracts will be converted into a contribution of this " "type") else: del self.override_contrib_type if self.session: self.session.description = _("The generated contributions will be allocated in this session") self.duplicate_of.description = _("The selected abstracts will be marked as duplicate of the specified " "abstract") self.merged_into.description = _("The selected abstracts will be merged into the specified abstract") self.merge_persons.description = _("Authors and speakers of the selected abstracts will be added to the " "specified abstract") self.duplicate_of.excluded_abstract_ids = set(kwargs['abstract_id']) self.merged_into.excluded_abstract_ids = set(kwargs['abstract_id']) if kwargs['judgment']: self._remove_unused_fields(kwargs['judgment']) def _remove_unused_fields(self, judgment): for field in list(self): validator = next((v for v in field.validators if isinstance(v, HiddenUnless) and v.field == 'judgment'), None) if validator is None: continue if not any(v.name == judgment for v in validator.value): delattr(self, field.name) def is_submitted(self): return super(BulkAbstractJudgmentForm, self).is_submitted() and 'submitted' in request.form @classmethod def _add_contrib_type_hidden_unless(cls): # In the bulk form we need to hide/disable the contrib type selector unless we want to # override the type specified in the abstract. However, multiple HiddenUnless validators # are not supported in the client-side JS so we only add it to this form - it removes # inactive fields on the server side so it still works (the JavaScript picks up the last # HiddenUnless validator) inject_validators(BulkAbstractJudgmentForm, 'accepted_contrib_type', [HiddenUnless('override_contrib_type')])
class TimetablePDFExportForm(IndicoForm): _pdf_options_fields = {'pagesize', 'fontsize', 'firstPageNumber'} advanced = BooleanField(_("Advanced timetable"), widget=SwitchWidget(), description=_("Advanced customization options")) document_settings = IndicoSelectMultipleCheckboxBooleanField(_('Document settings'), [HiddenUnless('advanced')], choices=_DOCUMENT_SETTINGS_CHOICES) contribution_info = IndicoSelectMultipleCheckboxBooleanField(_('Contributions related info'), [HiddenUnless('advanced')], choices=_CONTRIBUTION_CHOICES) session_info = IndicoSelectMultipleCheckboxBooleanField(_('Sessions related info'), [HiddenUnless('advanced')], choices=_SESSION_CHOICES) visible_entries = IndicoSelectMultipleCheckboxBooleanField(_('Breaks and contributions'), [HiddenUnless('advanced')], choices=_VISIBLE_ENTRIES_CHOICES) other = IndicoSelectMultipleCheckboxBooleanField(_('Miscellaneous'), choices=_OTHER_CHOICES) pagesize = SelectField(_('Page size'), choices=[('A0', 'A0'), ('A1', 'A1'), ('A2', 'A2'), ('A3', 'A3'), ('A4', 'A4'), ('A5', 'A5'), ('Letter', 'Letter')], default='A4') fontsize = SelectField(_('Font size'), choices=[('xxx-small', _('xxx-small')), ('xx-small', _('xx-small')), ('x-small', _('x-small')), ('smaller', _('smaller')), ('small', _('small')), ('normal', _('normal')), ('large', _('large')), ('larger', _('larger'))], default='normal') firstPageNumber = IntegerField(_('Number for the first page'), [NumberRange(min=1)], default=1, widget=NumberInput(step=1)) submitted = HiddenField() def is_submitted(self): return 'submitted' in request.args @property def data_for_format(self): if not self.advanced.data: fields = ('visible_entries',) else: fields = set(get_form_field_names(TimetablePDFExportForm)) - self._pdf_options_fields - {'csrf_token', 'advanced'} data = {} for fieldname in fields: data.update(getattr(self, fieldname).data) return data
class AbstractSubmissionSettingsForm(IndicoForm): """Settings form for abstract submission""" announcement = IndicoMarkdownField(_('Announcement'), editor=True) allow_multiple_tracks = BooleanField( _('Multiple tracks'), widget=SwitchWidget(), description=_("Allow the selection of multiple tracks")) tracks_required = BooleanField( _('Require tracks'), widget=SwitchWidget(), description=_("Make the track selection mandatory")) contrib_type_required = BooleanField( _('Require contrib. type'), widget=SwitchWidget(), description=_("Make the selection of a contribution type mandatory")) allow_attachments = BooleanField( _('Allow attachments'), widget=SwitchWidget(), description=_("Allow files to be attached to the abstract")) allow_speakers = BooleanField( _('Allow speakers'), widget=SwitchWidget(), description=_("Allow the selection of the abstract speakers")) speakers_required = BooleanField( _('Require a speaker'), [HiddenUnless('allow_speakers')], widget=SwitchWidget(), description=_( "Make the selection of at least one author as speaker mandatory")) authorized_submitters = PrincipalListField( _("Authorized submitters"), description=_("These users may always submit abstracts, even outside " "the regular submission period.")) submission_instructions = IndicoMarkdownField( _('Instructions'), editor=True, description=_("These instructions will be displayed right before the " "submission form.")) @generated_data def announcement_render_mode(self): return RenderMode.markdown def __init__(self, *args, **kwargs): self.event = kwargs.pop('event') super(AbstractSubmissionSettingsForm, self).__init__(*args, **kwargs) def validate_contrib_type_required(self, field): if field.data and not self.event.contribution_types.count(): raise ValidationError( _('The event has no contribution types defined.'))
class SingleChoiceConfigForm(object): display_type = IndicoRadioField(_('Display type'), [DataRequired()], description=_('Widget that will be used to render the available options'), choices=[('radio', _('Radio buttons')), ('select', _('Drop-down list'))], default='radio') radio_display_type = IndicoRadioField(_('Alignment'), [DataRequired(), HiddenUnless('display_type', 'radio')], description=_('The arrangement of the options'), choices=[('vertical', _('Vertical')), ('horizontal', _('Horizontal'))]) options = MultiStringField(_('Options'), [DataRequired()], field=('option', _('option')), unique=True, uuid_field='id', sortable=True, description=_('Specify the options the user can choose from'))
class AbstractReviewingSettingsForm(IndicoForm): """Settings form for abstract reviewing""" RATING_FIELDS = ('scale_lower', 'scale_upper') scale_lower = IntegerField(_("Scale (from)"), [UsedIf(lambda form, field: not form.has_ratings), InputRequired()]) scale_upper = IntegerField(_("Scale (to)"), [UsedIf(lambda form, field: not form.has_ratings), InputRequired()]) allow_convener_judgment = BooleanField(_("Allow track conveners to judge"), widget=SwitchWidget(), description=_("Enabling this allows track conveners to make a judgment " "such as accepting or rejecting an abstract.")) allow_comments = BooleanField(_("Allow comments"), widget=SwitchWidget(), description=_("Enabling this allows judges, conveners and reviewers to leave " "comments on abstracts.")) allow_contributors_in_comments = BooleanField(_("Allow contributors in comments"), [HiddenUnless('allow_comments', preserve_data=True)], widget=SwitchWidget(), description=_("Enabling this allows submitters, authors, and " "speakers to also participate in the comments.")) reviewing_instructions = IndicoMarkdownField(_('Reviewing Instructions'), editor=True, description=_("These instructions will be displayed right before the " "reviewing form.")) judgment_instructions = IndicoMarkdownField(_('Judgment Instructions'), editor=True, description=_("These instructions will be displayed right before the " "decision box.")) def __init__(self, *args, **kwargs): self.event = kwargs.pop('event') self.has_ratings = kwargs.pop('has_ratings', False) super(AbstractReviewingSettingsForm, self).__init__(*args, **kwargs) if self.has_ratings: self.scale_upper.warning = _("Some reviewers have already submitted ratings so the scale cannot be changed " "anymore.") def validate_scale_upper(self, field): lower = self.scale_lower.data upper = self.scale_upper.data if lower is None or upper is None: return if lower >= upper: raise ValidationError(_("The scale's 'to' value must be greater than the 'from' value.")) if upper - lower > 20: raise ValidationError(_("The difference between 'to' and' from' may not be greater than 20.")) @property def data(self): data = super(AbstractReviewingSettingsForm, self).data if self.has_ratings: for key in self.RATING_FIELDS: del data[key] return data
class AbstractJudgmentFormBase(IndicoForm): """Form base class for abstract judgment operations""" _order = ('judgment', 'accepted_track', 'accepted_contrib_type', 'session', 'duplicate_of', 'merged_into', 'merge_persons', 'judgment_comment', 'send_notifications') accepted_track = QuerySelectField(_("Track"), [HiddenUnless('judgment', AbstractAction.accept)], get_label=lambda obj: obj.title_with_group, allow_blank=True, blank_text=_("Choose a track..."), description=_("The abstract will be accepted in this track")) accepted_contrib_type = QuerySelectField(_("Contribution type"), [HiddenUnless('judgment', AbstractAction.accept)], get_label=lambda x: x.name.title(), allow_blank=True, blank_text=_("You may choose a contribution type..."), description=_("The abstract will be converted " "into a contribution of this type")) session = QuerySelectField(_("Session"), [HiddenUnless('judgment', AbstractAction.accept)], get_label='title', allow_blank=True, blank_text=_("You may choose a session..."), description=_("The generated contribution will be allocated in this session")) duplicate_of = AbstractField(_("Duplicate of"), [HiddenUnless('judgment', AbstractAction.mark_as_duplicate), DataRequired()], description=_("The current abstract will be marked as duplicate of the selected one"), ajax_endpoint='abstracts.other_abstracts') merged_into = AbstractField(_("Merge into"), [HiddenUnless('judgment', AbstractAction.merge), DataRequired()], description=_("The current abstract will be merged into the selected one"), ajax_endpoint='abstracts.other_abstracts') merge_persons = BooleanField(_("Merge persons"), [HiddenUnless('judgment', AbstractAction.merge)], description=_("Authors and speakers of the current abstract will be added to the " "selected one")) judgment_comment = TextAreaField(_("Comment"), render_kw={'placeholder': _("Leave a comment for the submitter..."), 'class': 'grow'}) # TODO: show only if notifications apply? send_notifications = BooleanField(_("Send notifications to submitter"), default=True) def __init__(self, *args, **kwargs): super(AbstractJudgmentFormBase, self).__init__(*args, **kwargs) self.session.query = Session.query.with_parent(self.event).order_by(Session.title) if not self.session.query.count(): del self.session self.accepted_track.query = Track.query.with_parent(self.event).order_by(Track.position) if not self.accepted_track.query.count(): del self.accepted_track self.accepted_contrib_type.query = (ContributionType.query .with_parent(self.event) .order_by(ContributionType.name)) if not self.accepted_contrib_type.query.count(): del self.accepted_contrib_type @property def split_data(self): abstract_data = self.data judgment_data = { 'judgment': abstract_data.pop('judgment'), 'send_notifications': abstract_data.pop('send_notifications'), 'contrib_session': abstract_data.pop('session', None), 'merge_persons': abstract_data.pop('merge_persons', None) } return judgment_data, abstract_data
class MenuBuiltinEntryForm(IndicoForm): custom_title = BooleanField(_("Custom title"), widget=SwitchWidget()) title = StringField(_("Title"), [HiddenUnless('custom_title'), DataRequired()]) is_enabled = BooleanField(_("Show"), widget=SwitchWidget()) def __init__(self, *args, **kwargs): entry = kwargs.pop('entry') super(MenuBuiltinEntryForm, self).__init__(*args, **kwargs) self.custom_title.description = _("If you customize the title, that title is used regardless of the user's " "language preference. The default title <strong>{title}</strong> is " "displayed in the user's language.").format(title=entry.default_data.title) def post_validate(self): if not self.custom_title.data: self.title.data = None
class NewBookingConfirmForm(NewBookingPeriodForm): room_usage = RadioField([DataRequired()], choices=[('current_user', _("I'll be using the room myself")), ('other_user', _("I'm booking the room for someone else"))]) booked_for_user = PrincipalField(_('User'), [HiddenUnless('room_usage', 'other_user'), DataRequired()], allow_external=True) booking_reason = TextAreaField(_('Reason'), [DataRequired()]) uses_vc = BooleanField(_('I will use videoconference equipment')) needs_vc_assistance = BooleanField(_('Request assistance for the startup of the videoconference session. ' 'This support is usually performed remotely.')) needs_assistance = BooleanField(_('Request personal assistance for meeting startup')) submit_book = SubmitField(_('Create booking')) submit_prebook = SubmitField(_('Create pre-booking')) def validate_needs_vc_assistance(self, field): if field.data and not self.uses_vc.data: raise ValidationError(_('Videoconference equipment is not used.'))
class ConferenceLayoutForm(LoggedLayoutForm): is_searchable = BooleanField(_("Enable search"), widget=SwitchWidget(), description=_("Enable search within the event")) show_nav_bar = BooleanField(_("Show navigation bar"), widget=SwitchWidget(), description=_("Show the navigation bar at the top")) show_banner = BooleanField(_("\"Now happening\""), widget=SwitchWidget(on_label=_("ON"), off_label=_("OFF")), description=_("Show a banner with the current entries from the timetable")) show_social_badges = BooleanField(_("Show social badges"), widget=SwitchWidget()) name_format = IndicoEnumSelectField(_('Name format'), enum=NameFormat, none=_('Inherit from user preferences'), description=_('Format in which names are displayed')) # Style header_text_color = StringField(_("Text colour"), widget=ColorPickerWidget()) header_background_color = StringField(_("Background colour"), widget=ColorPickerWidget()) # Announcement announcement = StringField(_("Announcement"), [UsedIf(lambda form, field: form.show_announcement.data)], description=_("Short message shown below the title")) show_announcement = BooleanField(_("Show announcement"), widget=SwitchWidget(), description=_("Show the announcement message")) # Timetable timetable_by_room = BooleanField(_("Group by room"), widget=SwitchWidget(), description=_("Group the entries of the timetable by room by default")) timetable_detailed = BooleanField(_("Show detailed view"), widget=SwitchWidget(), description=_("Show the detailed view of the timetable by default.")) timetable_theme = SelectField(_('Theme'), [Optional()], coerce=lambda x: x or None) # Themes use_custom_css = BooleanField(_("Use custom CSS"), widget=SwitchWidget(), description=_("Use a custom CSS file as a theme for the conference page. Deactivate " "this option to reveal the available Indico themes.")) theme = SelectField(_("Theme"), [Optional(), HiddenUnless('use_custom_css', False)], coerce=lambda x: (x or None), description=_("Currently selected theme of the conference page. Click on the Preview button to " "preview and select a different one.")) def __init__(self, *args, **kwargs): self.event = kwargs.pop('event') super(ConferenceLayoutForm, self).__init__(*args, **kwargs) self.timetable_theme.choices = [('', _('Default'))] + _get_timetable_theme_choices(self.event) self.theme.choices = _get_conference_theme_choices() def validate_use_custom_css(self, field): if field.data and not self.event.has_stylesheet: raise ValidationError(_('Cannot enable custom stylesheet unless there is one.'))
def __init__(self, *args, **kwargs): defaults = kwargs['obj'] if defaults.host_user is None and defaults.host is not None: host = principal_from_identifier(defaults.host) defaults.host_choice = 'myself' if host == session.user else 'someone_else' defaults.host_user = None if host == session.user else host allow_webinars = current_plugin.settings.get('allow_webinars') if allow_webinars: for field_name in { 'mute_audio', 'mute_participant_video', 'waiting_room' }: inject_validators(self, field_name, [HiddenUnless('meeting_type', 'regular')]) super().__init__(*args, **kwargs) if not allow_webinars: del self.meeting_type
class RegistrationFormForm(IndicoForm): _price_fields = ('currency', 'base_price') _registrant_notification_fields = ('notification_sender_address', 'message_pending', 'message_unpaid', 'message_complete') _manager_notification_fields = ('manager_notifications_enabled', 'manager_notification_recipients') _special_fields = _price_fields + _registrant_notification_fields + _manager_notification_fields title = StringField(_("Title"), [DataRequired()], description=_("The title of the registration form")) introduction = TextAreaField( _("Introduction"), description= _("Introduction to be displayed when filling out the registration form" )) contact_info = StringField( _("Contact info"), description= _("How registrants can get in touch with somebody for extra information" )) moderation_enabled = BooleanField( _("Moderated"), widget=SwitchWidget(), description=_("If enabled, registrations require manager approval")) require_login = BooleanField( _("Only logged-in users"), widget=SwitchWidget(), description=_("Users must be logged in to register")) require_user = BooleanField( _("Registrant must have account"), widget=SwitchWidget(), description=_( "Registrations emails must be associated with an Indico account")) limit_registrations = BooleanField( _("Limit registrations"), widget=SwitchWidget(), description=_("Whether there is a limit of registrations")) registration_limit = IntegerField( _("Capacity"), [ HiddenUnless('limit_registrations'), DataRequired(), NumberRange(min=1) ], description=_("Maximum number of registrations")) modification_mode = IndicoEnumSelectField( _("Modification allowed"), enum=ModificationMode, description=_("Will users be able to modify their data? When?")) publish_registrations_enabled = BooleanField( _('Publish registrations'), widget=SwitchWidget(), description=_("Registrations from this form will be displayed in the " "event page")) publish_registration_count = BooleanField( _("Publish number of registrations"), widget=SwitchWidget(), description=_("Number of registered participants will be displayed in " "the event page")) publish_checkin_enabled = BooleanField( _('Publish check-in status'), widget=SwitchWidget(), description=_( "Check-in status will be shown publicly on the event page")) base_price = DecimalField( _('Registration fee'), [ NumberRange(min=0, max=999999.99), Optional(), _check_if_payment_required ], filters=[lambda x: x if x is not None else 0], widget=NumberInput(step='0.01'), description=_("A fixed fee all users have to pay when registering.")) currency = SelectField(_('Currency'), [DataRequired()], description=_('The currency for new registrations')) notification_sender_address = StringField(_('Notification sender address'), [IndicoEmail()], filters=[lambda x: (x or None)]) message_pending = TextAreaField( _("Message for pending registrations"), description=_("Text included in emails sent to pending registrations")) message_unpaid = TextAreaField( _("Message for unpaid registrations"), description=_("Text included in emails sent to unpaid registrations")) message_complete = TextAreaField( _("Message for complete registrations"), description=_( "Text included in emails sent to complete registrations")) manager_notifications_enabled = BooleanField( _('Enabled'), widget=SwitchWidget(), description=_("Enable notifications to managers about registrations")) manager_notification_recipients = EmailListField( _('List of recipients'), [ HiddenUnless('manager_notifications_enabled', preserve_data=True), DataRequired() ], description=_("Email addresses that will receive notifications")) def __init__(self, *args, **kwargs): self.event = kwargs.pop('event') super().__init__(*args, **kwargs) self._set_currencies() self.notification_sender_address.description = _( 'Email address set as the sender of all ' 'notifications sent to users. If empty, ' 'then {} is used.'.format(config.NO_REPLY_EMAIL)) def _set_currencies(self): currencies = [(c['code'], '{0[code]} ({0[name]})'.format(c)) for c in payment_settings.get('currencies')] self.currency.choices = sorted(currencies, key=lambda x: x[1].lower())
class AbstractReviewForm(IndicoForm): """Form for reviewing an abstract""" _order = ('proposed_action', 'proposed_contribution_type', 'proposed_related_abstract', 'proposed_tracks', 'comment') comment = TextAreaField(_("Comment"), render_kw={ 'placeholder': _("You may leave a comment (only visible to " "conveners and judges)...") }) proposed_action = IndicoEnumSelectField(_("Proposed Action"), [DataRequired()], enum=AbstractAction) proposed_related_abstract = AbstractField( _("Target Abstract"), [ HiddenUnless( 'proposed_action', {AbstractAction.mark_as_duplicate, AbstractAction.merge}), DataRequired() ], description= _("The current abstract should be marked as duplicate of the selected one" ), ajax_endpoint='abstracts.other_abstracts') proposed_contribution_type = QuerySelectField( _("Contribution type"), [HiddenUnless('proposed_action', AbstractAction.accept)], get_label=lambda x: x.name.title(), allow_blank=True, blank_text=_("You may propose a contribution type...")) proposed_tracks = IndicoQuerySelectMultipleCheckboxField( _("Propose for tracks"), [ HiddenUnless('proposed_action', AbstractAction.change_tracks), DataRequired() ], collection_class=set, get_label='title') def __init__(self, edit=False, *args, **kwargs): abstract = kwargs.pop('abstract') super(AbstractReviewForm, self).__init__(*args, **kwargs) self.event = abstract.event if not edit: self.proposed_action.none = _("Propose an action...") self.proposed_related_abstract.excluded_abstract_ids = {abstract.id} self.proposed_contribution_type.query = ( ContributionType.query.with_parent(self.event).order_by( ContributionType.name)) if not self.proposed_contribution_type.query.count(): del self.proposed_contribution_type reviewed_for_track_ids = {t.id for t in abstract.reviewed_for_tracks} existing_prop_track_cond = (Track.id.in_( t.id for t in self.proposed_tracks.object_data) if self.proposed_tracks.object_data else False) self.proposed_tracks.query = (Track.query.with_parent( self.event).filter( db.or_(Track.id.notin_(reviewed_for_track_ids), existing_prop_track_cond)).order_by(Track.position)) if not self.proposed_tracks.query.count(): del self.proposed_tracks self.proposed_action.skip.add(AbstractAction.change_tracks) @property def split_data(self): data = self.data return { 'questions_data': {k: v for k, v in data.iteritems() if k.startswith('question_')}, 'review_data': { k: v for k, v in data.iteritems() if not k.startswith('question_') } }
class VCRoomForm(VCRoomFormBase): """Contains all information concerning a Zoom booking.""" advanced_fields = { 'mute_audio', 'mute_host_video', 'mute_participant_video' } | VCRoomFormBase.advanced_fields skip_fields = advanced_fields | VCRoomFormBase.conditional_fields meeting_type = IndicoRadioField( _('Meeting Type'), description=_('The type of Zoom meeting to be created'), orientation='horizontal', choices=[('regular', _('Regular Meeting')), ('webinar', _('Webinar'))]) host_choice = IndicoRadioField(_('Meeting Host'), [DataRequired()], choices=[('myself', _('Myself')), ('someone_else', _('Someone else')) ]) host_user = PrincipalField( _('User'), [HiddenUnless('host_choice', 'someone_else'), DataRequired()]) password = StringField( _('Passcode'), [DataRequired(), IndicoRegexp(r'^\d{8,10}$')], description=_('Meeting passcode (8-10 digits)')) password_visibility = IndicoRadioField( _('Passcode visibility'), description=_("Who should be able to know this meeting's passcode"), orientation='horizontal', choices=[('everyone', _('Everyone')), ('logged_in', _('Logged-in users')), ('registered', _('Registered participants')), ('no_one', _('No one'))]) mute_audio = BooleanField( _('Mute audio'), widget=SwitchWidget(), description=_('Participants will join the VC room muted by default ')) mute_host_video = BooleanField( _('Mute video (host)'), widget=SwitchWidget(), description=_('The host will join the VC room with video disabled')) mute_participant_video = BooleanField( _('Mute video (participants)'), widget=SwitchWidget(), description=_( 'Participants will join the VC room with video disabled')) waiting_room = BooleanField( _('Waiting room'), widget=SwitchWidget(), description=_( 'Participants may be kept in a waiting room by the host')) description = TextAreaField( _('Description'), description=_('Optional description for this room')) def __init__(self, *args, **kwargs): defaults = kwargs['obj'] if defaults.host_user is None and defaults.host is not None: host = principal_from_identifier(defaults.host) defaults.host_choice = 'myself' if host == session.user else 'someone_else' defaults.host_user = None if host == session.user else host allow_webinars = current_plugin.settings.get('allow_webinars') if allow_webinars: for field_name in { 'mute_audio', 'mute_participant_video', 'waiting_room' }: inject_validators(self, field_name, [HiddenUnless('meeting_type', 'regular')]) super().__init__(*args, **kwargs) if not allow_webinars: del self.meeting_type def validate_host_choice(self, field): if field.data == 'myself': self._check_zoom_user(session.user) def validate_host_user(self, field): if self.host_choice.data == 'someone_else': self._check_zoom_user(field.data) def _check_zoom_user(self, user): if find_enterprise_email(user) is None: raise ValidationError(_('This user has no Zoom account')) def validate_name(self, field): # Duplicate names are fine on Zoom pass @generated_data def host(self): if self.host_choice is None: return None elif self.host_choice.data == 'myself': return session.user.identifier else: return self.host_user.data.identifier if self.host_user.data else None
class ReminderForm(IndicoForm): recipient_fields = {'recipients', 'send_to_participants'} schedule_fields = {'schedule_type', 'absolute_dt', 'relative_delta'} schedule_recipient_fields = recipient_fields | schedule_fields # Schedule schedule_type = IndicoRadioField( _('Type'), [DataRequired()], choices=[('relative', _("Relative to the event start time")), ('absolute', _("Fixed date/time")), ('now', _('Send immediately'))]) relative_delta = TimeDeltaField( _('Offset'), [HiddenUnless('schedule_type', 'relative'), DataRequired()]) absolute_dt = IndicoDateTimeField(_('Date'), [ HiddenUnless('schedule_type', 'absolute'), DataRequired(), DateTimeRange() ]) # Recipients recipients = EmailListField(_('Email addresses'), description=_('One email address per line.')) send_to_participants = BooleanField( _('Participants'), description=_('Send the reminder to all participants/registrants ' 'of the event.')) # Misc reply_to_address = SelectField( _('Sender'), [DataRequired()], description=_('The email address that will show up as the sender.')) message = TextAreaField( _('Note'), description=_('A custom message to include in the email.')) include_summary = BooleanField( _('Include agenda'), description=_( "Includes a simple text version of the event's agenda in the email." )) include_description = BooleanField( _('Include description'), description=_("Includes the event's description in the email.")) def __init__(self, *args, **kwargs): self.event = kwargs.pop('event') self.timezone = self.event.timezone super().__init__(*args, **kwargs) self.reply_to_address.choices = (list( self.event.get_allowed_sender_emails( extra=self.reply_to_address.object_data).items())) if self.event.type_ == EventType.lecture: del self.include_summary def validate_recipients(self, field): if not field.data and not self.send_to_participants.data: raise ValidationError( _('If participants are not included you need to specify recipients.' )) def validate_send_to_participants(self, field): if not field.data and not self.recipients.data: raise ValidationError( _('If no recipients are specified you need to include participants.' )) def validate_schedule_type(self, field): # Be graceful and allow a reminder that's in the past but on the same day. # It will be sent immediately but that way we are a little bit more user-friendly if field.data == 'now': return scheduled_dt = self.scheduled_dt.data if scheduled_dt is not None and scheduled_dt.date() < now_utc().date(): raise ValidationError(_('The specified date is in the past')) @generated_data def scheduled_dt(self): if self.schedule_type.data == 'absolute': if self.absolute_dt.data is None: return None return self.absolute_dt.data elif self.schedule_type.data == 'relative': if self.relative_delta.data is None: return None return self.event.start_dt - self.relative_delta.data elif self.schedule_type.data == 'now': return now_utc() @generated_data def event_start_delta(self): return self.relative_delta.data if self.schedule_type.data == 'relative' else None
class PluginSettingsForm(VCPluginSettingsFormBase): _fieldsets = [ (_('API Credentials'), ['api_key', 'api_secret', 'webhook_token']), (_('Zoom Account'), [ 'user_lookup_mode', 'email_domains', 'authenticators', 'enterprise_domain', 'allow_webinars' ]), (_('Room Settings'), [ 'mute_audio', 'mute_host_video', 'mute_participant_video', 'join_before_host', 'waiting_room' ]), (_('Notifications'), ['creation_email_footer', 'send_host_url', 'notification_emails']), (_('Access'), ['managers', 'acl']) ] api_key = StringField(_('API Key'), [DataRequired()]) api_secret = IndicoPasswordField(_('API Secret'), [DataRequired()], toggle=True) webhook_token = IndicoPasswordField( _('Webhook Token'), toggle=True, description=_("Specify Zoom's webhook token if you want live updates")) user_lookup_mode = IndicoEnumSelectField( _('User lookup mode'), [DataRequired()], enum=UserLookupMode, description=_('Specify how Indico should look up the zoom user that ' 'corresponds to an Indico user.')) email_domains = TextListField( _('E-mail domains'), [ HiddenUnless('user_lookup_mode', UserLookupMode.email_domains), DataRequired() ], description= _('List of e-mail domains which can use the Zoom API. Indico attempts ' 'to find Zoom accounts using all email addresses of a user which use ' 'those domains.')) authenticators = TextListField( _('Indico identity providers'), [ HiddenUnless('user_lookup_mode', UserLookupMode.authenticators), DataRequired() ], description= _('Identity providers from which to get usernames. ' 'Indico queries those providers using the email addresses of the user ' 'and attempts to find Zoom accounts having an email address with the ' 'format username@enterprise-domain.')) enterprise_domain = StringField( _('Enterprise domain'), [ HiddenUnless('user_lookup_mode', UserLookupMode.authenticators), DataRequired() ], description=_( 'The domain name used together with the usernames from the Indico ' 'identity provider')) allow_webinars = BooleanField( _('Allow Webinars (Experimental)'), widget=SwitchWidget(), description=_( 'Allow webinars to be created through Indico. Use at your own risk.' )) mute_audio = BooleanField( _('Mute audio'), widget=SwitchWidget(), description=_('Participants will join the VC room muted by default ')) mute_host_video = BooleanField( _('Mute video (host)'), widget=SwitchWidget(), description=_('The host will join the VC room with video disabled')) mute_participant_video = BooleanField( _('Mute video (participants)'), widget=SwitchWidget(), description=_( 'Participants will join the VC room with video disabled')) join_before_host = BooleanField( _('Join Before Host'), widget=SwitchWidget(), description=_( 'Allow participants to join the meeting before the host starts the ' 'meeting. Only used for scheduled or recurring meetings.')) waiting_room = BooleanField( _('Waiting room'), widget=SwitchWidget(), description=_( 'Participants may be kept in a waiting room by the host')) creation_email_footer = TextAreaField( _('Creation email footer'), widget=CKEditorWidget(), description=_( 'Footer to append to emails sent upon creation of a VC room')) send_host_url = BooleanField( _('Send host URL'), widget=SwitchWidget(), description=_( 'Whether to send an e-mail with the Host URL to the meeting host upon ' 'creation of a meeting')) def validate_authenticators(self, field): invalid = set(field.data) - set(multipass.identity_providers) if invalid: raise ValidationError( _('Invalid identity providers: {}').format( escape(', '.join(invalid))))