class ContributionDurationForm(IndicoForm): duration = TimeDeltaField( _('Duration'), [DataRequired(), MaxDuration(timedelta(days=1))], default=timedelta(minutes=20), units=('minutes', 'hours')) def __init__(self, *args, **kwargs): self.contrib = kwargs.pop('contrib') super(ContributionDurationForm, self).__init__(*args, **kwargs) def validate_duration(self, field): if field.errors: return if self.contrib.is_scheduled: event = self.contrib.event_new day = self.contrib.start_dt.astimezone(event.tzinfo).date() if day == event.end_dt_local.date(): latest_dt = event.end_dt error_msg = _( "With this duration, the contribution would exceed the event end time." ) else: latest_dt = get_day_end(day, tzinfo=event.tzinfo) error_msg = _( "With this duration, the contribution would exceed the current day." ) if self.contrib.start_dt + field.data > latest_dt: raise ValidationError(error_msg)
class PluginSettingsForm(IndicoForm): adams_url = URLField(_('ADaMS URL'), [DataRequired()], description=_('The URL of the ADaMS REST API')) username = StringField(_('Username'), [DataRequired()], description=_('The login used to authenticate with ADaMS service')) password = IndicoPasswordField(_('Password'), [DataRequired()], description=_('The password used to authenticate with ADaMS service')) secret_key = IndicoPasswordField(_('Secret key'), [DataRequired()], description=_('Secret key to sign ADaMS requests')) authorized_users = PrincipalListField(_('Authorized users'), allow_groups=True, description=_('List of users/groups who can send requests')) excluded_categories = MultipleItemsField('Excluded categories', fields=[{'id': 'id', 'caption': 'Category ID'}]) access_ticket_template = QuerySelectField(_("Access ticket template"), allow_blank=True, blank_text=_("No access ticket selected"), get_label='title', description=_("Ticket template allowing access to CERN")) earliest_start_dt = IndicoDateTimeField(_("Earliest start date"), [Optional()], default_time=time(0, 0), description=_("The earliest date an event can start to qualify for CERN " "access badges")) delete_personal_data_after = TimeDeltaField(_('Delete personal data'), [DataRequired()], units=('days',), description=_('Personal data will be deleted once the event has ' 'finished and the duration specified here has been ' 'exceeded. Once the data has been deleted, access badges ' 'will not be accessible anymore.')) api_username = StringField(_('Username'), [DataRequired()], description=_('The username to access the API')) api_password = IndicoPasswordField(_('Password'), [DataRequired()], toggle=True, description=_('The password to access the API')) def __init__(self, *args, **kwargs): super(PluginSettingsForm, self).__init__(*args, **kwargs) self.access_ticket_template.query = (DesignerTemplate.query .filter(DesignerTemplate.category_id == 0, DesignerTemplate.type == TemplateType.badge) .order_by(db.func.lower(DesignerTemplate.title)))
class SubContributionForm(IndicoForm): title = StringField(_('Title'), [DataRequired()]) description = TextAreaField(_('Description')) duration = TimeDeltaField( _('Duration'), [DataRequired(), MaxDuration(timedelta(hours=24))], default=timedelta(minutes=20), units=('minutes', 'hours')) speakers = SubContributionPersonLinkListField( _('Speakers'), allow_submitters=False, description=_('The speakers of the subcontribution')) references = ReferencesField( _("External IDs"), reference_class=SubContributionReference, description=_("Manage external resources for this sub-contribution")) @generated_data def render_mode(self): return RenderMode.markdown def __init__(self, *args, **kwargs): self.event = kwargs.pop('event') self.subcontrib = kwargs.pop('subcontrib', None) super(SubContributionForm, self).__init__(*args, **kwargs)
class SessionForm(IndicoForm): title = StringField(_('Title'), [DataRequired()]) code = StringField( _('Session code'), description=_('The code that will identify the session in the Book of ' 'Abstracts.')) description = TextAreaField(_('Description')) default_contribution_duration = TimeDeltaField( _('Default contribution duration'), units=('minutes', 'hours'), description=_('Duration that a contribution created within this ' 'session will have by default.'), default=timedelta(minutes=20)) type = QuerySelectField(_("Type"), get_label='name', allow_blank=True, blank_text=_("No type selected")) location_data = IndicoLocationField( _("Default location"), description=_("Default location for blocks inside the session.")) colors = IndicoPalettePickerField(_('Colours'), color_list=get_colors()) def __init__(self, *args, **kwargs): event = kwargs.pop('event') super(SessionForm, self).__init__(*args, **kwargs) if event.type != 'conference': del self.code del self.type else: self.type.query = SessionType.query.with_parent(event) if not self.type.query.has_rows(): del self.type
class SessionForm(IndicoForm): title = StringField(_('Title'), [DataRequired()]) code = StringField( _('Session code'), description=_('The code that will identify the session in the Book of ' 'Abstracts.')) description = TextAreaField(_('Description')) default_contribution_duration = TimeDeltaField( _('Default contribution duration'), units=('minutes', 'hours'), description=_('Duration that a contribution created within this ' 'session will have by default.'), default=timedelta(minutes=20)) location_data = IndicoLocationField( _("Default location"), description=_("Default location for blocks inside the session.")) colors = IndicoPalettePickerField(_('Colours'), color_list=get_colors()) is_poster = BooleanField( _('Poster session'), widget=SwitchWidget(), description= _('Whether the session is a poster session or contains normal presentations.' )) def __init__(self, *args, **kwargs): event = kwargs.pop('event') super(SessionForm, self).__init__(*args, **kwargs) if event.type != 'conference': del self.is_poster del self.code
class ContributionForm(IndicoForm): title = StringField(_("Title"), [DataRequired()]) description = TextAreaField(_("Description")) start_dt = IndicoDateTimeField(_("Start date"), [DataRequired(), DateTimeRange(earliest=lambda form, field: form._get_earliest_start_dt(), latest=lambda form, field: form._get_latest_start_dt())], allow_clear=False, description=_("Start date of the contribution")) duration = TimeDeltaField(_("Duration"), [DataRequired(), MaxDuration(timedelta(hours=24))], default=timedelta(minutes=20), units=('minutes', 'hours')) type = QuerySelectField(_("Type"), get_label='name', allow_blank=True, blank_text=_("No type selected")) person_link_data = ContributionPersonLinkListField(_("People")) location_data = IndicoLocationField(_("Location")) keywords = IndicoTagListField(_('Keywords')) references = ReferencesField(_("External IDs"), reference_class=ContributionReference, description=_("Manage external resources for this contribution")) board_number = StringField(_("Board Number")) code = StringField(_('Programme code')) @generated_data def render_mode(self): return RenderMode.markdown def __init__(self, *args, **kwargs): self.event = kwargs.pop('event') self.contrib = kwargs.pop('contrib', None) self.session_block = kwargs.get('session_block') self.timezone = self.event.timezone to_schedule = kwargs.pop('to_schedule', False) super(ContributionForm, self).__init__(*args, **kwargs) self.type.query = self.event.contribution_types if self.event.type != 'conference': self.person_link_data.label.text = _("Speakers") if not self.type.query.count(): del self.type if not to_schedule and (self.contrib is None or not self.contrib.is_scheduled): del self.start_dt def _get_earliest_start_dt(self): return self.session_block.start_dt if self.session_block else self.event.start_dt def _get_latest_start_dt(self): return self.session_block.end_dt if self.session_block else self.event.end_dt def validate_duration(self, field): start_dt = self.start_dt.data if self.start_dt else None if start_dt: end_dt = start_dt + field.data if self.session_block and end_dt > self.session_block.end_dt: raise ValidationError(_("With the current duration the contribution exceeds the block end date")) if end_dt > self.event.end_dt: raise ValidationError(_('With the current duration the contribution exceeds the event end date')) @property def custom_field_names(self): return tuple([field_name for field_name in self._fields if field_name.startswith('custom_')])
class EntryFormMixin: _entry_type = None _default_duration = None _display_fields = None time = IndicoTimeField(_("Start time"), [InputRequired()]) duration = TimeDeltaField(_("Duration"), [DataRequired(), MaxDuration(timedelta(hours=24))], units=('minutes', 'hours')) def __init__(self, *args, **kwargs): self.event = kwargs['event'] self.session_block = kwargs.get('session_block') self.day = kwargs.pop('day') if self._default_duration is not None: kwargs.setdefault('time', self._get_default_time()) defaults = kwargs.get('obj') or FormDefaults() if 'duration' not in defaults: if self._entry_type == TimetableEntryType.CONTRIBUTION and self.session_block: defaults.duration = self.session_block.session.default_contribution_duration else: defaults.duration = self._default_duration kwargs['obj'] = defaults super().__init__(*args, **kwargs) @property def data(self): data = super().data del data['time'] return data @generated_data def start_dt(self): if self.time.data is not None: dt = datetime.combine(self.day, self.time.data) return self.event.tzinfo.localize(dt).astimezone(utc) def validate_duration(self, field): if not self.start_dt.data: return end_dt = self.start_dt.data + field.data if end_dt.astimezone(self.event.tzinfo).date() > self.event.end_dt_local.date(): raise ValidationError(_("{} exceeds current day. Adjust start time or duration.") .format(self._entry_type.title.capitalize())) def _get_default_time(self): if self.session_block: # inside a block we suggest right after the latest contribution # or fall back to the block start time if it's empty entry = self.session_block.timetable_entry start_dt = max(x.end_dt for x in entry.children) if entry.children else entry.start_dt else: # outside a block we find the first slot where a contribution would fit start_dt = find_next_start_dt(self._default_duration, obj=self.session_block or self.event, day=None if self.session_block else self.day) return start_dt.astimezone(self.event.tzinfo).time() if start_dt else None
class EntryFormMixin(object): _entry_type = None _default_duration = None _display_fields = None time = TimeField(_("Start time"), [InputRequired()]) duration = TimeDeltaField( _("Duration"), [DataRequired(), MaxDuration(timedelta(hours=24))], units=('minutes', 'hours')) def __init__(self, *args, **kwargs): self.event = kwargs['event'] self.session_block = kwargs.get('session_block') self.day = kwargs.pop('day') if self._default_duration is not None: kwargs.setdefault('time', self._get_default_time()) defaults = kwargs.get('obj') or FormDefaults() if 'duration' not in defaults: defaults.duration = self._default_duration kwargs['obj'] = defaults super(EntryFormMixin, self).__init__(*args, **kwargs) @property def data(self): data = super(EntryFormMixin, self).data del data['time'] return data @generated_data def start_dt(self): if self.time.data is not None: dt = datetime.combine(self.day, self.time.data) return self.event.tzinfo.localize(dt).astimezone(utc) def validate_duration(self, field): if not self.start_dt.data: return end_dt = self.start_dt.data + field.data if end_dt.astimezone( self.event.tzinfo).date() > self.event.end_dt_local.date(): raise ValidationError( _("{} exceeds current day. Adjust start time or duration."). format(self._entry_type.title.capitalize())) def _get_default_time(self): start_dt = find_next_start_dt( self._default_duration, obj=self.session_block or self.event, day=None if self.session_block else self.day) return start_dt.astimezone( self.event.tzinfo).time() if start_dt else None
class SettingsForm(IndicoForm): debug = BooleanField( _('Debug mode'), widget=SwitchWidget(), description=_( "If enabled, requests are not sent to the API but logged instead")) service_url = URLField( _('Service URL'), [URL(require_tld=False)], description=_("The URL of the CERN calendar service")) username = StringField( _('Username'), [DataRequired()], description=_( "The username used to authenticate with the CERN calendar service") ) password = IndicoPasswordField( _('Password'), [DataRequired()], toggle=True, description=_( "The password used to authenticate with the CERN calendar service") ) status = SelectField( _('Status'), [DataRequired()], choices=_status_choices, description=_("The default status of the event in the calendar")) reminder = BooleanField(_('Reminder'), description=_("Enable calendar reminder")) reminder_minutes = IntegerField( _('Reminder time'), [NumberRange(min=0)], description=_("Remind users X minutes before the event")) id_prefix = StringField( _('Prefix'), description=_( "Prefix for calendar item IDs. If you change this, existing calendar entries " "cannot be deleted/updated anymore!")) timeout = FloatField(_('Request timeout'), [NumberRange(min=0.25)], description=_("Request timeout in seconds")) max_event_duration = TimeDeltaField( _('Maximum Duration'), [DataRequired()], units=('days', ), description=_('Events lasting longer will not be sent to Exchange'))
class ContributionDefaultDurationForm(IndicoForm): duration = TimeDeltaField(_('Duration'), [DataRequired(), MaxDuration(timedelta(days=1))], units=('minutes', 'hours'))
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 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