From f6fbef96442d8b4e3ffff97a7db00aecba166278 Mon Sep 17 00:00:00 2001 From: Valentin Deniaud Date: Thu, 8 Apr 2021 17:36:36 +0200 Subject: [PATCH] manager: add new recurrence fields (#50560) --- .../migrations/0079_auto_20210412_1747.py | 52 +++++++++++++++++++ chrono/agendas/models.py | 29 ++++++++++- chrono/manager/forms.py | 33 +++++++++--- .../templates/chrono/manager_event_form.html | 4 +- .../templates/chrono/widgets/weekdays.html | 10 ++++ chrono/manager/widgets.py | 6 ++- 6 files changed, 122 insertions(+), 12 deletions(-) create mode 100644 chrono/agendas/migrations/0079_auto_20210412_1747.py create mode 100644 chrono/manager/templates/chrono/widgets/weekdays.html diff --git a/chrono/agendas/migrations/0079_auto_20210412_1747.py b/chrono/agendas/migrations/0079_auto_20210412_1747.py new file mode 100644 index 0000000..0b756b4 --- /dev/null +++ b/chrono/agendas/migrations/0079_auto_20210412_1747.py @@ -0,0 +1,52 @@ +# Generated by Django 2.2.19 on 2021-04-12 15:47 + +import django.contrib.postgres.fields +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('agendas', '0078_absence_reasons'), + ] + + operations = [ + migrations.AddField( + model_name='event', + name='recurrence_days', + field=django.contrib.postgres.fields.ArrayField( + base_field=models.IntegerField( + choices=[(0, 'Mo'), (1, 'Tu'), (2, 'We'), (3, 'Th'), (4, 'Fr'), (5, 'Sa'), (6, 'Su')] + ), + blank=True, + default=list, + null=True, + size=None, + verbose_name='Recurrence days', + ), + ), + migrations.AddField( + model_name='event', + name='recurrence_hour', + field=models.TimeField(blank=True, null=True, verbose_name='Hour'), + ), + migrations.AddField( + model_name='event', + name='recurrence_start_date', + field=models.DateField(blank=True, null=True, verbose_name='Recurrence start date'), + ), + migrations.AddField( + model_name='event', + name='recurrence_weekly_interval', + field=models.IntegerField( + choices=[(1, 'Every week'), (2, 'Every two weeks'), (3, 'Every three weeks')], + default=1, + verbose_name='Repeat', + ), + ), + migrations.AlterField( + model_name='event', + name='start_datetime', + field=models.DateTimeField(blank=True, null=True, verbose_name='Date/time'), + ), + ] diff --git a/chrono/agendas/models.py b/chrono/agendas/models.py index 0834bb2..906069b 100644 --- a/chrono/agendas/models.py +++ b/chrono/agendas/models.py @@ -1083,9 +1083,35 @@ class Event(models.Model): ('weekdays', _('Every weekdays (Monday to Friday)')), ] + WEEKDAY_CHOICES = [ + (0, _('Mo')), + (1, _('Tu')), + (2, _('We')), + (3, _('Th')), + (4, _('Fr')), + (5, _('Sa')), + (6, _('Su')), + ] + + INTERVAL_CHOICES = [ + (1, 'Every week'), + (2, 'Every two weeks'), + (3, 'Every three weeks'), + ] + agenda = models.ForeignKey(Agenda, on_delete=models.CASCADE) - start_datetime = models.DateTimeField(_('Date/time')) + start_datetime = models.DateTimeField(_('Date/time'), null=True, blank=True) repeat = models.CharField(_('Repeat'), max_length=16, blank=True, choices=REPEAT_CHOICES) + recurrence_days = ArrayField( + models.IntegerField(choices=WEEKDAY_CHOICES), + verbose_name=_('Recurrence days'), + default=list, + blank=True, + null=True, + ) + recurrence_hour = models.TimeField(_('Hour'), null=True, blank=True) + recurrence_start_date = models.DateField(_('Recurrence start date'), null=True, blank=True) + recurrence_weekly_interval = models.IntegerField(_('Repeat'), choices=INTERVAL_CHOICES, default=1) recurrence_rule = JSONField(_('Recurrence rule'), null=True) recurrence_end_date = models.DateField(_('Recurrence end date'), null=True, blank=True) primary_event = models.ForeignKey('self', null=True, on_delete=models.CASCADE, related_name='recurrences') @@ -1136,6 +1162,7 @@ class Event(models.Model): def save(self, seen_slugs=None, *args, **kwargs): assert self.agenda.kind != 'virtual', "an event can't reference a virtual agenda" + assert self.start_datetime or self.recurrence_hour and self.recurrence_start_date assert not (self.slug and self.slug.isdigit()), 'slug cannot be a number' self.start_datetime = self.start_datetime.replace(second=0, microsecond=0) self.check_full() diff --git a/chrono/manager/forms.py b/chrono/manager/forms.py index 388f456..81c64d1 100644 --- a/chrono/manager/forms.py +++ b/chrono/manager/forms.py @@ -50,7 +50,7 @@ from chrono.agendas.models import ( ) from . import widgets -from .widgets import SplitDateTimeField +from .widgets import SplitDateTimeField, WeekdaysWidget class AgendaAddForm(forms.ModelForm): @@ -146,7 +146,6 @@ class NewEventForm(forms.ModelForm): fields = [ 'label', 'start_datetime', - 'repeat', 'duration', 'places', ] @@ -155,20 +154,39 @@ class NewEventForm(forms.ModelForm): } +FREQUENCY_CHOICES = ( + ('unique', _('Unique')), + ('recurring', _('Recurring')), +) + + class EventForm(forms.ModelForm): - protected_fields = ('repeat', 'slug', 'start_datetime') + protected_fields = ('slug', 'start_datetime') + + frequency = forms.ChoiceField( + label=_('Event frequency'), widget=forms.RadioSelect, choices=FREQUENCY_CHOICES + ) + recurrence_days = forms.TypedMultipleChoiceField( + choices=Event.WEEKDAY_CHOICES, coerce=int, required=False, widget=WeekdaysWidget + ) class Meta: model = Event widgets = { 'publication_date': forms.DateInput(attrs={'type': 'date'}, format='%Y-%m-%d'), + 'recurrence_hour': forms.DateInput(attrs={'type': 'time'}, format='%H:%M'), + 'recurrence_start_date': forms.DateInput(attrs={'type': 'date'}, format='%Y-%m-%d'), 'recurrence_end_date': forms.DateInput(attrs={'type': 'date'}, format='%Y-%m-%d'), } fields = [ 'label', 'slug', + 'frequency', 'start_datetime', - 'repeat', + 'recurrence_days', + 'recurrence_hour', + 'recurrence_weekly_interval', + 'recurrence_start_date', 'recurrence_end_date', 'duration', 'publication_date', @@ -184,6 +202,7 @@ class EventForm(forms.ModelForm): 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_rule and self.instance.has_recurrences_booked(): for field in self.protected_fields: self.fields[field].disabled = True @@ -191,7 +210,7 @@ class EventForm(forms.ModelForm): 'This field cannot be modified because some recurrences have bookings attached to them.' ) if self.instance.primary_event: - for field in ('slug', 'repeat', 'recurrence_end_date', 'publication_date'): + for field in ('slug', 'recurrence_end_date', 'publication_date'): del self.fields[field] def clean(self): @@ -200,8 +219,6 @@ class EventForm(forms.ModelForm): after=self.cleaned_data['recurrence_end_date'] ): raise ValidationError(_('Bookings exist after this date.')) - if self.cleaned_data.get('recurrence_end_date') and not self.cleaned_data.get('repeat'): - raise ValidationError(_('Recurrence end date makes no sense without repetition.')) def save(self, *args, **kwargs): with transaction.atomic(): @@ -211,7 +228,7 @@ class EventForm(forms.ModelForm): update_fields = { field: value for field, value in self.cleaned_data.items() - if field not in self.protected_fields + if field != 'frequency' and field not in self.protected_fields } self.instance.recurrences.update(**update_fields) diff --git a/chrono/manager/templates/chrono/manager_event_form.html b/chrono/manager/templates/chrono/manager_event_form.html index 1d74945..a0c425d 100644 --- a/chrono/manager/templates/chrono/manager_event_form.html +++ b/chrono/manager/templates/chrono/manager_event_form.html @@ -1,5 +1,5 @@ {% extends "chrono/manager_agenda_view.html" %} -{% load i18n %} +{% load i18n gadjo %} {% block extrascripts %} {{ block.super }} @@ -29,7 +29,7 @@
{% csrf_token %} - {{ form.as_p }} + {{ form|with_template }}
{% trans 'Cancel' %} diff --git a/chrono/manager/templates/chrono/widgets/weekdays.html b/chrono/manager/templates/chrono/widgets/weekdays.html new file mode 100644 index 0000000..8f5effc --- /dev/null +++ b/chrono/manager/templates/chrono/widgets/weekdays.html @@ -0,0 +1,10 @@ +{% spaceless %} + + {% for group, options, index in widget.optgroups %} + {% for option in options %} + {{ option.label }} + {% include "django/forms/widgets/input.html" with widget=option %} + {% endfor %} + {% endfor %} + +{% endspaceless %} diff --git a/chrono/manager/widgets.py b/chrono/manager/widgets.py index 326cdbf..0b18956 100644 --- a/chrono/manager/widgets.py +++ b/chrono/manager/widgets.py @@ -16,7 +16,7 @@ from django.forms.fields import SplitDateTimeField -from django.forms.widgets import TimeInput, SplitDateTimeWidget +from django.forms.widgets import TimeInput, SplitDateTimeWidget, CheckboxSelectMultiple from django.utils.safestring import mark_safe @@ -59,3 +59,7 @@ class TimeWidget(TimeInput): super(TimeWidget, self).__init__(**kwargs) self.attrs['step'] = '300' # 5 minutes self.attrs['pattern'] = '[0-9]{2}:[0-9]{2}' + + +class WeekdaysWidget(CheckboxSelectMultiple): + template_name = 'chrono/widgets/weekdays.html' -- 2.20.1