chrono/chrono/manager/forms.py

918 lines
32 KiB
Python

# chrono - agendas system
# Copyright (C) 2016 Entr'ouvert
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import collections
import csv
import datetime
import django_filters
from django import forms
from django.conf import settings
from django.contrib.auth.models import Group
from django.core.exceptions import FieldDoesNotExist
from django.forms import ValidationError
from django.utils.encoding import force_text
from django.utils.six import StringIO
from django.utils.timezone import make_aware, now
from django.utils.translation import ugettext_lazy as _
from chrono.agendas.models import (
WEEKDAYS_LIST,
AbsenceReason,
AbsenceReasonGroup,
Agenda,
AgendaNotificationsSettings,
AgendaReminderSettings,
Booking,
Desk,
Event,
MeetingType,
Resource,
TimePeriod,
TimePeriodException,
TimePeriodExceptionSource,
UnavailabilityCalendar,
VirtualMember,
generate_slug,
)
from . import widgets
from .widgets import SplitDateTimeField, WeekdaysWidget
class AbsenceReasonForm(forms.ModelForm):
class Meta:
model = AbsenceReason
fields = ['label', 'slug']
def clean_slug(self):
slug = self.cleaned_data['slug']
if self.instance.group.absence_reasons.filter(slug=slug).exclude(pk=self.instance.pk).exists():
raise ValidationError(_('Another absence reason exists with the same identifier.'))
return slug
class AgendaAddForm(forms.ModelForm):
edit_role = forms.ModelChoiceField(
label=_('Edit Role'), required=False, queryset=Group.objects.all().order_by('name')
)
view_role = forms.ModelChoiceField(
label=_('View Role'), required=False, queryset=Group.objects.all().order_by('name')
)
class Meta:
model = Agenda
fields = ['label', 'kind', 'category', 'edit_role', 'view_role']
def save(self, *args, **kwargs):
create = self.instance.pk is None
super().save()
if create and self.instance.kind == 'meetings':
default_desk = self.instance.desk_set.create(label=_('Desk 1'))
default_desk.import_timeperiod_exceptions_from_settings(enable=True)
self.instance.desk_simple_management = True
self.instance.save()
return self.instance
class AgendaEditForm(forms.ModelForm):
class Meta:
model = Agenda
fields = [
'label',
'slug',
'category',
'anonymize_delay',
'default_view',
'booking_form_url',
'event_display_template',
]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if kwargs['instance'].kind != 'events':
del self.fields['booking_form_url']
del self.fields['event_display_template']
self.fields['default_view'].choices = [
(k, v) for k, v in self.fields['default_view'].choices if k != 'open_events'
]
class AgendaBookingDelaysForm(forms.ModelForm):
class Meta:
model = Agenda
fields = [
'minimal_booking_delay',
'minimal_booking_delay_in_working_days',
'maximal_booking_delay',
]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if kwargs['instance'].kind != 'virtual':
self.fields['minimal_booking_delay'].required = True
self.fields['maximal_booking_delay'].required = True
if kwargs['instance'].kind != 'events' or settings.WORKING_DAY_CALENDAR is None:
del self.fields['minimal_booking_delay_in_working_days']
class AgendaRolesForm(AgendaAddForm):
class Meta:
model = Agenda
fields = [
'edit_role',
'view_role',
]
class UnavailabilityCalendarAddForm(forms.ModelForm):
class Meta:
model = UnavailabilityCalendar
fields = ['label', 'edit_role', 'view_role']
edit_role = forms.ModelChoiceField(
label=_('Edit Role'), required=False, queryset=Group.objects.all().order_by('name')
)
view_role = forms.ModelChoiceField(
label=_('View Role'), required=False, queryset=Group.objects.all().order_by('name')
)
class UnavailabilityCalendarEditForm(UnavailabilityCalendarAddForm):
class Meta:
model = UnavailabilityCalendar
fields = ['label', 'slug', 'edit_role', 'view_role']
class NewEventForm(forms.ModelForm):
frequency = forms.ChoiceField(
label=_('Event frequency'),
widget=forms.RadioSelect,
choices=(
('unique', _('Unique')),
('recurring', _('Recurring')),
),
initial='unique',
)
recurrence_days = forms.TypedMultipleChoiceField(
choices=Event.WEEKDAY_CHOICES,
coerce=int,
required=False,
widget=WeekdaysWidget,
label=_('Recurrence days'),
)
class Meta:
model = Event
fields = [
'label',
'start_datetime',
'frequency',
'recurrence_days',
'recurrence_week_interval',
'recurrence_end_date',
'duration',
'places',
]
field_classes = {
'start_datetime': SplitDateTimeField,
}
widgets = {
'recurrence_end_date': forms.DateInput(attrs={'type': 'date'}, format='%Y-%m-%d'),
}
def clean_recurrence_days(self):
recurrence_days = self.cleaned_data['recurrence_days']
if recurrence_days == []:
return None
return recurrence_days
class EventForm(NewEventForm):
protected_fields = (
'slug',
'start_datetime',
'frequency',
'recurrence_days',
'recurrence_week_interval',
)
class Meta:
model = Event
widgets = {
'recurrence_end_date': forms.DateInput(attrs={'type': 'date'}, format='%Y-%m-%d'),
}
fields = [
'label',
'slug',
'start_datetime',
'frequency',
'recurrence_days',
'recurrence_week_interval',
'recurrence_end_date',
'duration',
'publication_datetime',
'places',
'waiting_list_places',
'description',
'pricing',
'url',
]
field_classes = {
'start_datetime': SplitDateTimeField,
'publication_datetime': SplitDateTimeField,
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['frequency'].initial = 'recurring' if self.instance.recurrence_days else 'unique'
if self.instance.recurrence_days and self.instance.has_recurrences_booked():
for field in self.protected_fields:
self.fields[field].disabled = True
self.fields[field].help_text = _(
'This field cannot be modified because some recurrences have bookings attached to them.'
)
if self.instance.primary_event:
for field in (
'slug',
'recurrence_end_date',
'publication_datetime',
'frequency',
'recurrence_days',
'recurrence_week_interval',
):
del self.fields[field]
def clean_slug(self):
slug = self.cleaned_data['slug']
if self.instance.agenda.event_set.filter(slug=slug).exclude(pk=self.instance.pk).exists():
raise ValidationError(_('Another event exists with the same identifier.'))
return slug
def clean(self):
super().clean()
if 'recurrence_end_date' in self.changed_data and self.instance.has_recurrences_booked(
after=self.cleaned_data['recurrence_end_date']
):
raise ValidationError(_('Bookings exist after this date.'))
if self.cleaned_data.get('frequency') == 'unique':
self.cleaned_data['recurrence_days'] = None
self.cleaned_data['recurrence_end_date'] = None
def save(self, *args, **kwargs):
with self.instance.update_recurrences(
self.changed_data,
self.cleaned_data,
self.protected_fields,
list(self.protected_fields) + ['recurrence_end_date', 'frequency'],
):
super().save(*args, **kwargs)
return self.instance
class BookingCheckFilterSet(django_filters.FilterSet):
class Meta:
model = Booking
fields = []
def __init__(self, *args, **kwargs):
self.agenda = kwargs.pop('agenda')
super().__init__(*args, **kwargs)
# build filters from DB
agenda_filters = self.agenda.get_booking_check_filters()
filters = collections.defaultdict(set)
for extra_data in self.queryset.filter(extra_data__has_any_keys=agenda_filters).values_list(
'extra_data', flat=True
):
for k, v in extra_data.items():
if k in agenda_filters:
filters[k].add(v)
filters = sorted(filters.items())
filters = {k: sorted(list(v)) for k, v in filters}
# add filters to filterset
for key, values in filters.items():
self.filters[key] = django_filters.ChoiceFilter(
label=_('Filter by %s') % key,
field_name='extra_data__%s' % key,
lookup_expr='iexact',
choices=[(v, v) for v in values],
empty_label=_('all'),
widget=forms.RadioSelect,
)
class BookingAbsenceReasonForm(forms.Form):
reason = forms.ChoiceField(required=False)
def __init__(self, *args, **kwargs):
agenda = kwargs.pop('agenda')
super().__init__(*args, **kwargs)
if agenda.absence_reasons_group:
self.fields['reason'].choices = [('', '---------')] + [
(r.label, r.label) for r in agenda.absence_reasons_group.absence_reasons.all()
]
class AgendaResourceForm(forms.Form):
resource = forms.ModelChoiceField(label=_('Resource'), queryset=Resource.objects.none())
def __init__(self, *args, **kwargs):
agenda = kwargs.pop('agenda')
super().__init__(*args, **kwargs)
self.fields['resource'].queryset = Resource.objects.exclude(agenda=agenda)
class NewMeetingTypeForm(forms.ModelForm):
class Meta:
model = MeetingType
exclude = ['agenda', 'slug', 'deleted']
def clean(self):
super().clean()
agenda = self.instance.agenda
for virtual_agenda in agenda.virtual_agendas.all():
for real_agenda in virtual_agenda.real_agendas.all():
if real_agenda != agenda:
raise ValidationError(
_("Can't add a meetingtype to an agenda that is included in a virtual agenda.")
)
class MeetingTypeForm(forms.ModelForm):
class Meta:
model = MeetingType
exclude = ['agenda', 'deleted']
def clean_slug(self):
slug = self.cleaned_data['slug']
if self.instance.agenda.meetingtype_set.filter(slug=slug).exclude(pk=self.instance.pk).exists():
raise ValidationError(_('Another meeting type exists with the same identifier.'))
return slug
def clean(self):
super().clean()
for virtual_agenda in self.instance.agenda.virtual_agendas.all():
if virtual_agenda.real_agendas.count() == 1:
continue
for mt in virtual_agenda.iter_meetingtypes():
if (
mt.label == self.instance.label
and mt.slug == self.instance.slug
and mt.duration == self.instance.duration
):
raise ValidationError(
_('This meetingtype is used by a virtual agenda: %s' % virtual_agenda)
)
class TimePeriodAddForm(forms.Form):
weekdays = forms.MultipleChoiceField(
label=_('Days'), widget=forms.CheckboxSelectMultiple(), choices=WEEKDAYS_LIST
)
start_time = forms.TimeField(label=_('Start Time'), widget=widgets.TimeWidget())
end_time = forms.TimeField(label=_('End Time'), widget=widgets.TimeWidget())
def clean_end_time(self):
if self.cleaned_data['end_time'] <= self.cleaned_data['start_time']:
raise ValidationError(_('End time must come after start time.'))
return self.cleaned_data['end_time']
class TimePeriodForm(forms.ModelForm):
class Meta:
model = TimePeriod
widgets = {
'start_time': widgets.TimeWidget(),
'end_time': widgets.TimeWidget(),
}
exclude = ['agenda', 'desk']
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.old_weekday = self.instance.weekday
self.old_start_time = self.instance.start_time
self.old_end_time = self.instance.end_time
def clean_end_time(self):
if self.cleaned_data['end_time'] <= self.cleaned_data['start_time']:
raise ValidationError(_('End time must come after start time.'))
return self.cleaned_data['end_time']
def save(self):
super().save()
if not self.instance.desk:
return self.instance
if not self.instance.desk.agenda.desk_simple_management:
return self.instance
for desk in self.instance.desk.agenda.desk_set.exclude(pk=self.instance.desk.pk):
timeperiod = desk.timeperiod_set.filter(
weekday=self.old_weekday, start_time=self.old_start_time, end_time=self.old_end_time
).first()
if timeperiod is not None:
timeperiod.weekday = self.instance.weekday
timeperiod.start_time = self.instance.start_time
timeperiod.end_time = self.instance.end_time
timeperiod.save()
return self.instance
class NewDeskForm(forms.ModelForm):
copy_from = forms.ModelChoiceField(
label=_('Copy settings of desk'),
required=False,
queryset=Desk.objects.none(),
)
class Meta:
model = Desk
exclude = ['agenda', 'slug']
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.instance.agenda.desk_simple_management:
del self.fields['copy_from']
else:
self.fields['copy_from'].queryset = Desk.objects.filter(agenda=self.instance.agenda)
def save(self):
if self.instance.agenda.desk_simple_management:
desk = self.instance.agenda.desk_set.first()
if desk is not None:
return desk.duplicate(label=self.cleaned_data['label'])
elif self.cleaned_data['copy_from']:
return self.cleaned_data['copy_from'].duplicate(label=self.cleaned_data['label'])
super().save()
self.instance.import_timeperiod_exceptions_from_settings(enable=True)
return self.instance
class DeskForm(forms.ModelForm):
class Meta:
model = Desk
exclude = ['agenda']
def clean_slug(self):
slug = self.cleaned_data['slug']
if self.instance.agenda.desk_set.filter(slug=slug).exclude(pk=self.instance.pk).exists():
raise ValidationError(_('Another desk exists with the same identifier.'))
return slug
class TimePeriodExceptionForm(forms.ModelForm):
class Meta:
model = TimePeriodException
fields = ['start_datetime', 'end_datetime', 'label']
field_classes = {
'start_datetime': SplitDateTimeField,
'end_datetime': SplitDateTimeField,
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.old_label = self.instance.label
self.old_start_datetime = self.instance.start_datetime
self.old_end_datetime = self.instance.end_datetime
def clean(self):
cleaned_data = super().clean()
if 'start_datetime' in cleaned_data and 'end_datetime' in cleaned_data:
if cleaned_data['end_datetime'] <= cleaned_data['start_datetime']:
self.add_error('end_datetime', _('End datetime must be greater than start datetime.'))
return cleaned_data
def save(self):
super().save()
self.exceptions = [self.instance]
desk_simple_management = False
if self.instance.desk_id:
desk_simple_management = self.instance.desk.agenda.desk_simple_management
if desk_simple_management:
for desk in self.instance.desk.agenda.desk_set.exclude(pk=self.instance.desk_id):
exception = desk.timeperiodexception_set.filter(
source__isnull=True,
label=self.old_label,
start_datetime=self.old_start_datetime,
end_datetime=self.old_end_datetime,
).first()
if exception is not None:
exception.label = self.instance.label
exception.start_datetime = self.instance.start_datetime
exception.end_datetime = self.instance.end_datetime
exception.save()
self.exceptions.append(exception)
return self.instance
class NewTimePeriodExceptionForm(TimePeriodExceptionForm):
all_desks = forms.BooleanField(label=_('Apply exception on all desks of the agenda'), required=False)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.instance.unavailability_calendar:
del self.fields['all_desks']
elif self.instance.desk_id and self.instance.desk.agenda.desk_set.count() == 1:
del self.fields['all_desks']
elif self.instance.desk_id and self.instance.desk.agenda.desk_simple_management:
del self.fields['all_desks']
def save(self):
super().save()
self.exceptions = [self.instance]
all_desks = self.cleaned_data.get('all_desks')
desk_simple_management = False
if self.instance.desk_id:
desk_simple_management = self.instance.desk.agenda.desk_simple_management
if all_desks or desk_simple_management:
for desk in self.instance.desk.agenda.desk_set.exclude(pk=self.instance.desk_id):
self.exceptions.append(
TimePeriodException.objects.create(
desk=desk,
label=self.instance.label,
start_datetime=self.instance.start_datetime,
end_datetime=self.instance.end_datetime,
)
)
return self.instance
class VirtualMemberForm(forms.ModelForm):
class Meta:
model = VirtualMember
fields = ['real_agenda']
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['real_agenda'].queryset = Agenda.objects.filter(kind='meetings').exclude(
virtual_agendas=self.instance.virtual_agenda
)
class ImportEventsForm(forms.Form):
events_csv_file = forms.FileField(
label=_('Events File'),
required=True,
help_text=_(
'CSV file with date, time, number of places, '
'number of places in waiting list, label, and '
'optionally, identifier, description, pricing, '
'URL, and publication date as columns.'
),
)
events = None
def __init__(self, agenda_pk, **kwargs):
self.agenda_pk = agenda_pk
super().__init__(**kwargs)
def clean_events_csv_file(self):
content = self.cleaned_data['events_csv_file'].read()
if b'\0' in content:
raise ValidationError(_('Invalid file format.'))
for charset in ('utf-8-sig', 'iso-8859-15'):
try:
content = content.decode(charset)
break
except UnicodeDecodeError:
continue
# all byte-sequences are ok for iso-8859-15 so we will always reach
# this line with content being a unicode string.
try:
dialect = csv.Sniffer().sniff(content)
except csv.Error:
dialect = None
events = []
warnings = {}
events_by_slug = {x.slug: x for x in Event.objects.filter(agenda=self.agenda_pk)}
event_ids_with_bookings = set(
Booking.objects.filter(
event__agenda=self.agenda_pk, cancellation_datetime__isnull=True
).values_list('event_id', flat=True)
)
seen_slugs = set(events_by_slug.keys())
for i, csvline in enumerate(csv.reader(StringIO(content), dialect=dialect)):
if not csvline:
continue
if len(csvline) < 3:
raise ValidationError(_('Invalid file format. (line %d)') % (i + 1))
if i == 0 and csvline[0].strip('#') in ('date', 'Date', _('date'), _('Date')):
continue
# label needed to generate a slug
label = None
if len(csvline) >= 5:
label = force_text(csvline[4])
# get or create event
event = None
slug = None
if len(csvline) >= 6:
slug = force_text(csvline[5]) if csvline[5] else None
# get existing event if relevant
if slug and slug in seen_slugs:
event = events_by_slug[slug]
# update label
event.label = label
if event is None:
# new event
event = Event(agenda_id=self.agenda_pk, label=label)
# generate a slug if not provided
event.slug = slug or generate_slug(event, seen_slugs=seen_slugs, agenda=self.agenda_pk)
# maintain caches
seen_slugs.add(event.slug)
events_by_slug[event.slug] = event
for datetime_fmt in (
'%Y-%m-%d %H:%M',
'%d/%m/%Y %H:%M',
'%d/%m/%Y %Hh%M',
'%Y-%m-%d %H:%M:%S',
'%d/%m/%Y %H:%M:%S',
):
try:
event_datetime = make_aware(
datetime.datetime.strptime('%s %s' % tuple(csvline[:2]), datetime_fmt)
)
except ValueError:
continue
if (
event.pk is not None
and event.start_datetime != event_datetime
and event.start_datetime > now()
and event.pk in event_ids_with_bookings
and event.pk not in warnings
):
# event start datetime has changed, event is not past and has not cancelled bookings
# => warn the user
warnings[event.pk] = event
event.start_datetime = event_datetime
break
else:
raise ValidationError(_('Invalid file format. (date/time format, line %d)') % (i + 1))
try:
event.places = int(csvline[2])
except ValueError:
raise ValidationError(_('Invalid file format. (number of places, line %d)') % (i + 1))
if len(csvline) >= 4:
try:
event.waiting_list_places = int(csvline[3])
except ValueError:
raise ValidationError(
_('Invalid file format. (number of places in waiting list, line %d)') % (i + 1)
)
column_index = 7
for more_attr in ('description', 'pricing', 'url'):
if len(csvline) >= column_index:
setattr(event, more_attr, csvline[column_index - 1])
column_index += 1
if len(csvline) >= 10 and csvline[9]: # publication date is optional
for datetime_fmt in (
'%Y-%m-%d',
'%d/%m/%Y',
'%Y-%m-%d %H:%M',
'%d/%m/%Y %H:%M',
'%d/%m/%Y %Hh%M',
'%Y-%m-%d %H:%M:%S',
'%d/%m/%Y %H:%M:%S',
):
try:
event.publication_datetime = make_aware(
datetime.datetime.strptime(csvline[9], datetime_fmt)
)
break
except ValueError:
continue
else:
raise ValidationError(_('Invalid file format. (date/time format, line %d)') % (i + 1))
if len(csvline) >= 11 and csvline[10]: # duration is optional
try:
event.duration = int(csvline[10])
except ValueError:
raise ValidationError(_('Invalid file format. (duration, line %d)') % (i + 1))
try:
event.full_clean(exclude=['desk', 'meeting_type', 'primary_event'])
except ValidationError as e:
errors = [_('Invalid file format:\n')]
for label, field_errors in e.message_dict.items():
label_name = self.get_verbose_name(label)
msg = _('%s: ') % label_name if label_name else ''
msg += _('%(errors)s (line %(line)d)') % {
'errors': ', '.join(field_errors),
'line': i + 1,
}
errors.append(msg)
raise ValidationError(errors)
events.append(event)
self.events = events
self.warnings = warnings
@staticmethod
def get_verbose_name(field_name):
try:
return Event._meta.get_field(field_name).verbose_name
except FieldDoesNotExist:
return ''
class ExceptionsImportForm(forms.ModelForm):
ics_file = forms.FileField(
label=_('ICS File'),
required=False,
help_text=_('ICS file containing events which will be considered as exceptions.'),
)
ics_url = forms.URLField(
label=_('URL'),
required=False,
help_text=_('URL to remote calendar which will be synchronised hourly.'),
)
all_desks = forms.BooleanField(label=_('Apply exceptions on all desks of the agenda'), required=False)
class Meta:
model = Desk
fields = []
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.instance.agenda.desk_set.count() == 1:
del self.fields['all_desks']
elif self.instance.agenda.desk_simple_management:
del self.fields['all_desks']
def clean(self, *args, **kwargs):
cleaned_data = super().clean(*args, **kwargs)
if not cleaned_data.get('ics_file') and not cleaned_data.get('ics_url'):
raise forms.ValidationError(_('Please provide an ICS File or an URL.'))
class TimePeriodExceptionSourceReplaceForm(forms.ModelForm):
ics_newfile = forms.FileField(
label=_('ICS File'),
required=False,
help_text=_('ICS file containing events which will be considered as exceptions.'),
)
class Meta:
model = TimePeriodExceptionSource
fields = []
def save(self, *args, **kwargs):
def store_source(source):
if bool(source.ics_file):
source.ics_file.delete()
source.ics_file = self.cleaned_data['ics_newfile']
source.ics_filename = self.cleaned_data['ics_newfile'].name
source.save()
self.cleaned_data['ics_newfile'].seek(0)
old_filename = self.instance.ics_filename
store_source(self.instance)
if self.instance.desk.agenda.desk_simple_management:
for desk in self.instance.desk.agenda.desk_set.exclude(pk=self.instance.desk_id):
source = desk.timeperiodexceptionsource_set.filter(ics_filename=old_filename).first()
if source is not None:
store_source(source)
return self.instance
class AgendasImportForm(forms.Form):
agendas_json = forms.FileField(label=_('Export File'))
class AgendaDuplicateForm(forms.Form):
label = forms.CharField(label=_('New label'), max_length=150, required=False)
class BookingCancelForm(forms.ModelForm):
disable_trigger = forms.BooleanField(
label=_('Do not send cancel trigger to form'), initial=False, required=False, widget=forms.HiddenInput
)
def show_trigger_checkbox(self):
self.fields['disable_trigger'].widget = forms.CheckboxInput()
class Meta:
model = Booking
fields = []
class EventCancelForm(forms.ModelForm):
class Meta:
model = Event
fields = []
class AgendaBookingCheckSettingsForm(forms.ModelForm):
class Meta:
model = Agenda
fields = [
'absence_reasons_group',
'booking_check_filters',
'booking_user_block_template',
'mark_event_checked_auto',
'disable_check_update',
]
widgets = {'booking_user_block_template': forms.Textarea(attrs={'rows': 3})}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if not AbsenceReasonGroup.objects.exists():
del self.fields['absence_reasons_group']
class AgendaNotificationsForm(forms.ModelForm):
class Meta:
model = AgendaNotificationsSettings
exclude = ['agenda']
@staticmethod
def update_choices(choices, settings):
new_choices = []
for choice in choices:
if choice[0] in (settings.EDIT_ROLE, settings.VIEW_ROLE):
role = settings.get_role_from_choice(choice[0]) or _('undefined')
choice = (choice[0], '%s (%s)' % (choice[1], role))
new_choices.append(choice)
return new_choices
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for email_field in AgendaNotificationsSettings.get_email_field_names():
self.fields[email_field].widget.attrs['size'] = 80
self.fields[email_field].label = ''
self.fields[email_field].help_text = _('Enter a comma separated list of email addresses.')
settings = kwargs['instance']
for role_field in AgendaNotificationsSettings.get_role_field_names():
field = self.fields[role_field]
field.choices = self.update_choices(field.choices, settings)
class AgendaReminderForm(forms.ModelForm):
class Meta:
model = AgendaReminderSettings
exclude = ['agenda']
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if not settings.SMS_URL:
del self.fields['send_sms']
del self.fields['sms_extra_info']
def clean(self):
cleaned_data = super().clean()
if cleaned_data['days'] and not (cleaned_data['send_email'] or cleaned_data.get('send_sms')):
raise ValidationError(_('Select at least one notification medium.'))
return cleaned_data
class AgendasExportForm(forms.Form):
agendas = forms.BooleanField(label=_('Agendas'), required=False, initial=True)
unavailability_calendars = forms.BooleanField(
label=_('Unavailability calendars'), required=False, initial=True
)
absence_reason_groups = forms.BooleanField(label=_('Absence reason groups'), required=False, initial=True)