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 AgreementForm(IndicoForm): agreed = IndicoRadioField(_("Do you agree with the stated above?"), [InputRequired()], coerce=lambda x: bool(int(x)), choices=[(1, _("I agree")), (0, _("I disagree"))]) reason = TextAreaField(_("Reason"))
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'), [ UsedIf(lambda form, _: form.filter_type.data == 'sessions'), DataRequired() ], description=_('Include materials from selected sessions'), coerce=int) contributions = IndicoSelectMultipleCheckboxField( _('Contributions'), [ UsedIf(lambda form, _: form.filter_type.data == 'contributions'), DataRequired() ], description=_('Include materials from selected contributions'), coerce=int) dates = IndicoSelectMultipleCheckboxField( _('Events scheduled on'), [ UsedIf(lambda form, _: form.filter_type.data == 'dates'), DataRequired() ], description=_( 'Include materials from sessions/contributions scheduled ' 'on the selected dates'))
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=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 VCRoomLinkFormBase(IndicoForm): conditional_fields = {'contribution', 'block'} linking = IndicoRadioField(_("Link to"), [DataRequired()], choices=[('event', _("Event")), ('contribution', _("Contribution")), ('block', _("Session"))], widget=LinkingWidget()) contribution = SelectField(_("Contribution"), [UsedIf(lambda form, field: form.linking.data == 'contribution'), DataRequired()], coerce=lambda x: int(x) if x else None) block = SelectField(_("Session block"), [UsedIf(lambda form, field: form.linking.data == 'block'), DataRequired()], coerce=lambda x: int(x) if x else None) show = BooleanField(_('Show room'), widget=SwitchWidget(), description=_('Display this room on the event page')) def __init__(self, *args, **kwargs): self.event = kwargs.pop('event') super().__init__(*args, **kwargs) contrib_choices = [(contrib.id, contrib.title) for contrib in sorted(self.event.contributions, key=attrgetter('title'))] blocks = SessionBlock.find(SessionBlock.session.has((Session.event == self.event) & ~Session.is_deleted)) block_choices = [(block.id, block.full_title) for block in sorted(blocks, key=attrgetter('full_title'))] self.contribution.choices = [('', _("Please select a contribution"))] + contrib_choices self.block.choices = [('', _("Please select a session block"))] + block_choices
class AttachmentPackageForm(IndicoForm): added_since = DateField( _('Added Since'), [Optional()], parse_kwargs={'dayfirst': True}, 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 VCRoomLinkFormBase(IndicoForm): conditional_fields = {'contribution', 'block'} linking = IndicoRadioField(_("Link to"), [DataRequired()], choices=[('event', _("Event")), ('contribution', _("Contribution")), ('block', _("Session"))], widget=LinkingWidget()) contribution = SelectField(_("Contribution"), [ UsedIf(lambda form, field: form.linking.data == 'contribution'), DataRequired() ]) block = SelectField(_("Session block"), [ UsedIf(lambda form, field: form.linking.data == 'block'), DataRequired() ]) show = BooleanField(_('Show room'), widget=SwitchWidget(), description=_('Display this room on the event page')) def __init__(self, *args, **kwargs): self.event = kwargs.pop('event') super(VCRoomLinkFormBase, self).__init__(*args, **kwargs) contrib_choices = [(contrib.id, contrib.title) for contrib in sorted( self.event.getContributionList(), key=attrgetter('title'))] block_choices = [(full_block_id(block), block.getFullTitle()) for block in sorted(self.event.getSessionSlotList(), key=methodcaller('getFullTitle'))] self.contribution.choices = [('', _("Please select a contribution")) ] + contrib_choices self.block.choices = [('', _("Please select a session block")) ] + block_choices
class AgreementAnswerSubmissionForm(IndicoForm): answer = IndicoRadioField(_("Answer"), [InputRequired()], coerce=lambda x: bool(int(x)), choices=[(1, _("Agreement")), (0, _("Disagreement"))]) document = FileField(_("Document"), [UsedIf(lambda form, field: form.answer.data), DataRequired()]) upload_confirm = BooleanField(_("I confirm that I'm uploading a document that clearly shows this person's answer"), [UsedIf(lambda form, field: form.answer.data), DataRequired()]) understand = BooleanField(_("I understand that I'm answering the agreement on behalf of this person"), [DataRequired()], description=_("This answer is legally binding and can't be changed " "afterwards."))
class VCRoomAttachForm(VCRoomAttachFormBase): 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'))])
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().__init__(event, **kwargs)
class VCRoomLinkFormBase(IndicoForm): conditional_fields = {'contribution', 'block'} linking = IndicoRadioField(_("Link to"), [DataRequired()], choices=[('event', _("Event")), ('contribution', _("Contribution")), ('block', _("Session"))], widget=LinkingWidget()) contribution = SelectField(_("Contribution"), [ UsedIf(lambda form, field: form.linking.data == 'contribution'), DataRequired() ], coerce=lambda x: int(x) if x else None) block = SelectField(_("Session block"), [ UsedIf(lambda form, field: form.linking.data == 'block'), DataRequired() ], coerce=lambda x: int(x) if x else None) show = BooleanField(_('Show room'), widget=SwitchWidget(), description=_('Display this room on the event page')) def __init__(self, *args, **kwargs): self.event = kwargs.pop('event') super().__init__(*args, **kwargs) contrib_choices = [ (contrib.id, '{} (#{}, {})'.format( contrib.title, contrib.friendly_id, format_datetime(contrib.start_dt, timezone=self.event.tzinfo))) for contrib in sorted(self.event.contributions, key=lambda c: (c.title, c.start_dt or as_utc( datetime(1970, 1, 1)))) if contrib.start_dt is not None ] blocks = (SessionBlock.query.filter( SessionBlock.session.has((Session.event == self.event) & ~Session.is_deleted)).all()) block_choices = [(block.id, '{} ({})'.format( block.full_title, format_datetime(block.start_dt, timezone=self.event.tzinfo))) for block in sorted( blocks, key=attrgetter('full_title', 'start_dt'))] self.contribution.choices = [('', _("Please select a contribution")) ] + contrib_choices self.block.choices = [('', _("Please select a session block")) ] + block_choices
class CloneRepeatabilityForm(IndicoForm): repeatability = IndicoRadioField(_('How to repeat'), choices=CLONE_REPEAT_CHOICES, coerce=lambda x: x or 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 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): default_widget_attrs = {'absolute_time': {'placeholder': 'HH:MM'}} recipient_fields = {'recipients', 'send_to_participants'} schedule_fields = { 'schedule_type', 'absolute_date', 'absolute_time', '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_date = DateField( _('Date'), [HiddenUnless('schedule_type', 'absolute'), DataRequired()], parse_kwargs={'dayfirst': True}) absolute_time = TimeField( _('Time'), [HiddenUnless('schedule_type', 'absolute'), InputRequired()]) # 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." )) def __init__(self, *args, **kwargs): self.event = kwargs.pop('event') super(ReminderForm, self).__init__(*args, **kwargs) self.absolute_time.description = _( 'Your active timezone is {tz}.').format(tz=self.timezone) self._set_email_choices() if self.event.getType() == 'simple_event': del self.include_summary def _set_email_choices(self): # User emails = {session.user.email: session.user.full_name} # Creator emails[self.event.as_event.creator. email] = self.event.as_event.creator.full_name # Support support = self.event.getSupportInfo() emails[support.getEmail()] = support.getCaption() or support.getEmail() # Chairs emails.update((pl.email, pl.full_name) for pl in self.event.as_event.person_links if pl.email) # Current email to avoid destructive modifications emails.setdefault(self.reply_to_address.object_data, self.reply_to_address.object_data) # Sanitize and format emails emails = { to_unicode(email.strip().lower()): '{} <{}>'.format(to_unicode(name), to_unicode(email)) for email, name in emails.iteritems() if email and email.strip() } self.reply_to_address.choices = sorted( emails.items(), key=lambda x: (x[0] != session.user.email, x[1].lower())) 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')) def validate_absolute_date(self, field): if self.schedule_type.data == 'absolute' and field.data < date.today(): raise ValidationError(_('The specified date is in the past')) @property def timezone(self): return DisplayTZ(conf=self.event).getDisplayTZ() @generated_data def scheduled_dt(self): if self.schedule_type.data == 'absolute': if self.absolute_date.data is None or self.absolute_time.data is None: return None dt = datetime.combine(self.absolute_date.data, self.absolute_time.data) return get_timezone(self.timezone).localize(dt).astimezone( pytz.utc) elif self.schedule_type.data == 'relative': if self.relative_delta.data is None: return None return self.event.getStartDate() - 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