Projet

Général

Profil

0001-manager-add-more-granular-control-over-event-recurre.patch

Valentin Deniaud, 29 avril 2021 16:15

Télécharger (48,7 ko)

Voir les différences:

Subject: [PATCH 1/2] manager: add more granular control over event recurrence
 (#50560)

 .../commands/update_event_recurrences.py      |   2 +-
 .../migrations/0083_auto_20210421_1556.py     |  80 ++++++++++++
 chrono/agendas/models.py                      | 118 +++++++++++-------
 chrono/api/views.py                           |   2 +-
 chrono/manager/forms.py                       |  66 ++++++++--
 chrono/manager/static/css/style.scss          |   4 +
 .../chrono/manager_agenda_event_fragment.html |   2 +-
 .../templates/chrono/manager_event_form.html  |  19 ++-
 .../templates/chrono/widgets/weekdays.html    |  10 ++
 chrono/manager/views.py                       |   8 +-
 chrono/manager/widgets.py                     |  13 +-
 tests/manager/test_all.py                     |  32 +++--
 tests/manager/test_event.py                   |  41 +++---
 tests/test_agendas.py                         |  97 ++++++++++----
 tests/test_api.py                             |  42 +++++--
 tests/test_ensure_jsonbfields.py              |   5 -
 tests/test_import_export.py                   |   8 +-
 17 files changed, 415 insertions(+), 134 deletions(-)
 create mode 100644 chrono/agendas/migrations/0083_auto_20210421_1556.py
 create mode 100644 chrono/manager/templates/chrono/widgets/weekdays.html
chrono/agendas/management/commands/update_event_recurrences.py
24 24
    help = 'Update event recurrences to reflect exceptions'
25 25

  
26 26
    def handle(self, **options):
27
        agendas = Agenda.objects.filter(kind='events', event__recurrence_rule__isnull=False).distinct()
27
        agendas = Agenda.objects.filter(kind='events', event__recurrence_days__isnull=False).distinct()
28 28
        for agenda in agendas:
29 29
            agenda.update_event_recurrences()
chrono/agendas/migrations/0083_auto_20210421_1556.py
1
# Generated by Django 2.2.19 on 2021-04-21 13:56
2

  
3
import django.contrib.postgres.fields
4
from dateutil.rrule import DAILY, WEEKLY
5
from django.db import migrations, models
6

  
7

  
8
def migrate_recurrence_fields(apps, schema_editor):
9
    Event = apps.get_model('agendas', 'Event')
10

  
11
    for event in Event.objects.filter(recurrence_days__isnull=False):
12
        if event.recurrence_rule['freq'] == DAILY:
13
            event.recurrence_days = list(range(7))
14
        elif event.recurrence_rule['freq'] == WEEKLY:
15
            event.recurrence_days = event.recurrence_rule['byweekday']
16
        event.recurrence_week_interval = event.recurrence_rule.get('interval', 1)
17
        event.save()
18

  
19

  
20
def reverse_migrate_recurrence_fields(apps, schema_editor):
21
    Event = apps.get_model('agendas', 'Event')
22

  
23
    for event in Event.objects.filter(recurrence_days__isnull=False):
24
        rrule = {}
25
        if event.recurrence_days == list(range(7)):
26
            event.repeat = 'daily'
27
            rrule['freq'] = DAILY
28
        else:
29
            rrule['freq'] = WEEKLY
30
            rrule['byweekday'] = event.recurrence_days
31
            if event.recurrence_days == list(range(5)):
32
                event.repeat = 'weekdays'
33
            elif event.recurrence_week_interval == 2:
34
                event.repeat = '2-weeks'
35
                rrule['interval'] = 2
36
            else:
37
                event.repeat = 'weekly'
38
        event.recurrence_rule = rrule
39
        event.save()
40

  
41

  
42
class Migration(migrations.Migration):
43

  
44
    dependencies = [
45
        ('agendas', '0082_text_to_jsonb'),
46
    ]
47

  
48
    operations = [
49
        migrations.AddField(
50
            model_name='event',
51
            name='recurrence_days',
52
            field=django.contrib.postgres.fields.ArrayField(
53
                base_field=models.IntegerField(
54
                    choices=[(0, 'Mo'), (1, 'Tu'), (2, 'We'), (3, 'Th'), (4, 'Fr'), (5, 'Sa'), (6, 'Su')]
55
                ),
56
                blank=True,
57
                null=True,
58
                size=None,
59
                verbose_name='Recurrence days',
60
            ),
61
        ),
62
        migrations.AddField(
63
            model_name='event',
64
            name='recurrence_week_interval',
65
            field=models.IntegerField(
66
                choices=[(1, 'Every week'), (2, 'Every two weeks'), (3, 'Every three weeks')],
67
                default=1,
68
                verbose_name='Repeat',
69
            ),
70
        ),
71
        migrations.RunPython(migrate_recurrence_fields, reverse_migrate_recurrence_fields),
72
        migrations.RemoveField(
73
            model_name='event',
74
            name='recurrence_rule',
75
        ),
76
        migrations.RemoveField(
77
            model_name='event',
78
            name='repeat',
79
        ),
80
    ]
chrono/agendas/models.py
592 592
            entries = self.prefetched_events
593 593
        else:
594 594
            # recurring events are never opened
595
            entries = self.event_set.filter(recurrence_rule__isnull=True)
595
            entries = self.event_set.filter(recurrence_days__isnull=True)
596 596
            # exclude canceled events except for event recurrences
597 597
            entries = entries.filter(Q(cancelled=False) | Q(primary_event__isnull=False))
598 598
            # we never want to allow booking for past events.
......
661 661
            recurring_events = self.prefetched_recurring_events
662 662
            exceptions = self.prefetched_exceptions
663 663
        else:
664
            recurring_events = self.event_set.filter(recurrence_rule__isnull=False)
664
            recurring_events = self.event_set.filter(recurrence_days__isnull=False)
665 665
            exceptions = self.get_recurrence_exceptions(min_start, max_start)
666 666

  
667 667
        for event in recurring_events:
......
678 678

  
679 679
    @transaction.atomic
680 680
    def update_event_recurrences(self):
681
        recurring_events = self.event_set.filter(recurrence_rule__isnull=False)
681
        recurring_events = self.event_set.filter(recurrence_days__isnull=False)
682 682
        recurrences = self.event_set.filter(primary_event__isnull=False)
683 683

  
684 684
        # remove recurrences
......
1140 1140

  
1141 1141

  
1142 1142
class Event(models.Model):
1143
    REPEAT_CHOICES = [
1144
        ('daily', _('Daily')),
1145
        ('weekly', _('Weekly')),
1146
        ('2-weeks', _('Once every two weeks')),
1147
        ('weekdays', _('Every weekdays (Monday to Friday)')),
1143
    WEEKDAY_CHOICES = [
1144
        (0, _('Mo')),
1145
        (1, _('Tu')),
1146
        (2, _('We')),
1147
        (3, _('Th')),
1148
        (4, _('Fr')),
1149
        (5, _('Sa')),
1150
        (6, _('Su')),
1151
    ]
1152

  
1153
    INTERVAL_CHOICES = [
1154
        (1, 'Every week'),
1155
        (2, 'Every two weeks'),
1156
        (3, 'Every three weeks'),
1148 1157
    ]
1149 1158

  
1150 1159
    agenda = models.ForeignKey(Agenda, on_delete=models.CASCADE)
1151 1160
    start_datetime = models.DateTimeField(_('Date/time'))
1152
    repeat = models.CharField(_('Repeat'), max_length=16, blank=True, choices=REPEAT_CHOICES)
1153
    recurrence_rule = JSONField(_('Recurrence rule'), null=True, blank=True)
1161
    recurrence_days = ArrayField(
1162
        models.IntegerField(choices=WEEKDAY_CHOICES),
1163
        verbose_name=_('Recurrence days'),
1164
        blank=True,
1165
        null=True,
1166
    )
1167
    recurrence_week_interval = models.IntegerField(_('Repeat'), choices=INTERVAL_CHOICES, default=1)
1154 1168
    recurrence_end_date = models.DateField(_('Recurrence end date'), null=True, blank=True)
1155 1169
    primary_event = models.ForeignKey('self', null=True, on_delete=models.CASCADE, related_name='recurrences')
1156 1170
    duration = models.PositiveIntegerField(_('Duration (in minutes)'), default=None, null=True, blank=True)
......
1205 1219
        self.check_full()
1206 1220
        if not self.slug:
1207 1221
            self.slug = generate_slug(self, seen_slugs=seen_slugs, agenda=self.agenda)
1208
        self.recurrence_rule = self.get_recurrence_rule()
1209 1222
        return super(Event, self).save(*args, **kwargs)
1210 1223

  
1211 1224
    @property
......
1229 1242
            return False
1230 1243
        if self.agenda.maximal_booking_delay and self.start_datetime > self.agenda.max_booking_datetime:
1231 1244
            return False
1232
        if self.recurrence_rule is not None:
1245
        if self.recurrence_days is not None:
1233 1246
            # bookable recurrences probably exist
1234 1247
            return True
1235 1248
        if self.agenda.minimal_booking_delay and self.start_datetime < self.agenda.min_booking_datetime:
......
1352 1365
        else:
1353 1366
            event = cls(**data)
1354 1367
            event.save()
1355
        if event.recurrence_rule and event.recurrence_end_date:
1368
        if event.recurrence_days and event.recurrence_end_date:
1356 1369
            event.refresh_from_db()
1357 1370
            event.recurrences.filter(start_datetime__gt=event.recurrence_end_date).delete()
1358 1371
            update_fields = {
......
1381 1394
        return {
1382 1395
            'start_datetime': make_naive(self.start_datetime).strftime('%Y-%m-%d %H:%M:%S'),
1383 1396
            'publication_date': self.publication_date.strftime('%Y-%m-%d') if self.publication_date else None,
1384
            'repeat': self.repeat,
1385
            'recurrence_rule': self.recurrence_rule,
1397
            'recurrence_days': self.recurrence_days,
1398
            'recurrence_week_interval': self.recurrence_week_interval,
1386 1399
            'recurrence_end_date': recurrence_end_date,
1387 1400
            'places': self.places,
1388 1401
            'waiting_list_places': self.waiting_list_places,
......
1475 1488

  
1476 1489
        if self.publication_date and self.publication_date > min_datetime.date():
1477 1490
            min_datetime = make_aware(datetime.datetime.combine(self.publication_date, datetime.time(0, 0)))
1478
        if self.recurrence_end_date:
1479
            self.recurrence_rule['until'] = datetime.datetime.combine(
1480
                self.recurrence_end_date, datetime.time(0, 0)
1481
            )
1482 1491

  
1483 1492
        # remove pytz info because dateutil doesn't support DST changes
1484 1493
        min_datetime = make_naive(min_datetime)
......
1500 1509
        return recurrences
1501 1510

  
1502 1511
    def get_recurrence_display(self):
1503
        repeat = str(self.get_repeat_display())
1504 1512
        time = date_format(localtime(self.start_datetime), 'TIME_FORMAT')
1505
        if self.repeat in ('weekly', '2-weeks'):
1506
            day = date_format(localtime(self.start_datetime), 'l')
1507
            return _('%(every_x_days)s on %(day)s at %(time)s') % {
1508
                'every_x_days': repeat,
1509
                'day': day,
1510
                'time': time,
1513

  
1514
        days_count = len(self.recurrence_days)
1515
        if days_count == 7:
1516
            repeat = _('Daily')
1517
        elif days_count > 1 and (self.recurrence_days[-1] - self.recurrence_days[0]) == days_count - 1:
1518
            # days are contiguous
1519
            repeat = _('From %(weekday)s to %(last_weekday)s') % {
1520
                'weekday': str(WEEKDAYS[self.recurrence_days[0]]),
1521
                'last_weekday': str(WEEKDAYS[self.recurrence_days[-1]]),
1511 1522
            }
1512 1523
        else:
1513
            return _('%(every_x_days)s at %(time)s') % {'every_x_days': repeat, 'time': time}
1514

  
1515
    def get_recurrence_rule(self):
1516
        rrule = {}
1517
        if self.repeat == 'daily':
1518
            rrule['freq'] = DAILY
1519
        elif self.repeat == 'weekly':
1520
            rrule['freq'] = WEEKLY
1521
            rrule['byweekday'] = [localtime(self.start_datetime).weekday()]
1522
        elif self.repeat == '2-weeks':
1523
            rrule['freq'] = WEEKLY
1524
            rrule['byweekday'] = [localtime(self.start_datetime).weekday()]
1525
            rrule['interval'] = 2
1526
        elif self.repeat == 'weekdays':
1527
            rrule['freq'] = WEEKLY
1528
            rrule['byweekday'] = [i for i in range(5)]
1529
        else:
1530
            return None
1531
        return rrule
1524
            repeat = _('On %(weekdays)s') % {
1525
                'weekdays': ', '.join([str(WEEKDAYS[i]) for i in self.recurrence_days])
1526
            }
1527

  
1528
        recurrence_display = _('%(On_day_x)s at %(time)s') % {'On_day_x': repeat, 'time': time}
1529

  
1530
        if self.recurrence_week_interval > 1:
1531
            if self.recurrence_week_interval == 2:
1532
                every_x_weeks = _('every two weeks')
1533
            elif self.recurrence_week_interval == 3:
1534
                every_x_weeks = _('every three weeks')
1535
            recurrence_display = _('%(Every_x_days)s, once %(every_x_weeks)s') % {
1536
                'Every_x_days': recurrence_display,
1537
                'every_x_weeks': every_x_weeks,
1538
            }
1539

  
1540
        if self.recurrence_end_date:
1541
            end_date = date_format(self.recurrence_end_date, 'DATE_FORMAT')
1542
            recurrence_display = _('%(Every_x_days)s, until %(date)s') % {
1543
                'Every_x_days': recurrence_display,
1544
                'date': end_date,
1545
            }
1546
        return recurrence_display
1547

  
1548
    @property
1549
    def recurrence_rule(self):
1550
        recurrence_rule = {
1551
            'freq': WEEKLY,
1552
            'byweekday': self.recurrence_days,
1553
            'interval': self.recurrence_week_interval,
1554
        }
1555
        if self.recurrence_end_date:
1556
            recurrence_rule['until'] = datetime.datetime.combine(
1557
                self.recurrence_end_date, datetime.time(0, 0)
1558
            )
1559
        return recurrence_rule
1532 1560

  
1533 1561
    def has_recurrences_booked(self, after=None):
1534 1562
        return Booking.objects.filter(
chrono/api/views.py
564 564
                cancelled=False,
565 565
                start_datetime__gte=localtime(now()),
566 566
            ).order_by()
567
            recurring_event_queryset = Event.objects.filter(recurrence_rule__isnull=False)
567
            recurring_event_queryset = Event.objects.filter(recurrence_days__isnull=False)
568 568
            exceptions_desk = Desk.objects.filter(slug='_exceptions_holder').prefetch_related(
569 569
                'unavailability_calendars'
570 570
            )
chrono/manager/forms.py
50 50
)
51 51

  
52 52
from . import widgets
53
from .widgets import SplitDateTimeField
53
from .widgets import SplitDateTimeField, WeekdaysWidget
54 54

  
55 55

  
56 56
class AgendaAddForm(forms.ModelForm):
......
141 141

  
142 142

  
143 143
class NewEventForm(forms.ModelForm):
144
    frequency = forms.ChoiceField(
145
        label=_('Event frequency'),
146
        widget=forms.RadioSelect,
147
        choices=(
148
            ('unique', _('Unique')),
149
            ('recurring', _('Recurring')),
150
        ),
151
        initial='unique',
152
    )
153
    recurrence_days = forms.TypedMultipleChoiceField(
154
        choices=Event.WEEKDAY_CHOICES, coerce=int, required=False, widget=WeekdaysWidget
155
    )
156

  
144 157
    class Meta:
145 158
        model = Event
146 159
        fields = [
147 160
            'label',
148 161
            'start_datetime',
149
            'repeat',
162
            'frequency',
163
            'recurrence_days',
164
            'recurrence_week_interval',
165
            'recurrence_end_date',
150 166
            'duration',
151 167
            'places',
152 168
        ]
153 169
        field_classes = {
154 170
            'start_datetime': SplitDateTimeField,
155 171
        }
172
        widgets = {
173
            'recurrence_end_date': forms.DateInput(attrs={'type': 'date'}, format='%Y-%m-%d'),
174
        }
175

  
176
    def clean_recurrence_days(self):
177
        recurrence_days = self.cleaned_data['recurrence_days']
178
        if recurrence_days == []:
179
            return None
180
        return recurrence_days
156 181

  
157 182

  
158
class EventForm(forms.ModelForm):
159
    protected_fields = ('repeat', 'slug', 'start_datetime')
183
class EventForm(NewEventForm):
184
    protected_fields = (
185
        'slug',
186
        'start_datetime',
187
        'frequency',
188
        'recurrence_days',
189
        'recurrence_week_interval',
190
    )
160 191

  
161 192
    class Meta:
162 193
        model = Event
......
168 199
            'label',
169 200
            'slug',
170 201
            'start_datetime',
171
            'repeat',
202
            'frequency',
203
            'recurrence_days',
204
            'recurrence_week_interval',
172 205
            'recurrence_end_date',
173 206
            'duration',
174 207
            'publication_date',
......
184 217

  
185 218
    def __init__(self, *args, **kwargs):
186 219
        super().__init__(*args, **kwargs)
187
        if self.instance.recurrence_rule and self.instance.has_recurrences_booked():
220
        self.fields['frequency'].initial = 'recurring' if self.instance.recurrence_days else 'unique'
221
        if self.instance.recurrence_days and self.instance.has_recurrences_booked():
188 222
            for field in self.protected_fields:
189 223
                self.fields[field].disabled = True
190 224
                self.fields[field].help_text = _(
191 225
                    'This field cannot be modified because some recurrences have bookings attached to them.'
192 226
                )
193 227
        if self.instance.primary_event:
194
            for field in ('slug', 'repeat', 'recurrence_end_date', 'publication_date'):
228
            for field in (
229
                'slug',
230
                'recurrence_end_date',
231
                'publication_date',
232
                'frequency',
233
                'recurrence_days',
234
                'recurrence_week_interval',
235
            ):
195 236
                del self.fields[field]
196 237

  
197 238
    def clean(self):
......
200 241
            after=self.cleaned_data['recurrence_end_date']
201 242
        ):
202 243
            raise ValidationError(_('Bookings exist after this date.'))
203
        if self.cleaned_data.get('recurrence_end_date') and not self.cleaned_data.get('repeat'):
204
            raise ValidationError(_('Recurrence end date makes no sense without repetition.'))
244

  
245
        if self.cleaned_data.get('frequency') == 'unique':
246
            self.cleaned_data['recurrence_days'] = None
247
            self.cleaned_data['recurrence_end_date'] = None
205 248

  
206 249
    def save(self, *args, **kwargs):
207 250
        with transaction.atomic():
208 251
            if any(field for field in self.changed_data if field in self.protected_fields):
209 252
                self.instance.recurrences.all().delete()
210
            elif self.instance.recurrence_rule:
253
            elif self.instance.recurrence_days:
254
                protected_fields = list(self.protected_fields) + ['recurrence_end_date', 'frequency']
211 255
                update_fields = {
212 256
                    field: value
213 257
                    for field, value in self.cleaned_data.items()
214
                    if field != 'recurrence_end_date' and field not in self.protected_fields
258
                    if field not in protected_fields
215 259
                }
216 260
                self.instance.recurrences.update(**update_fields)
217 261

  
chrono/manager/static/css/style.scss
411 411
		background-color: $color;
412 412
	}
413 413
}
414

  
415
form div.widget[id^=id_recurrence] {
416
      padding-left: 1em;
417
}
chrono/manager/templates/chrono/manager_agenda_event_fragment.html
20 20
    {% else %}
21 21
      {% if event.label %}{{ event.label }} / {% endif %}
22 22
    {% endif %}
23
    {% if not event.repeat %}
23
    {% if not event.recurrence_days %}
24 24
      {% if view_mode == 'day_view' %}{{ event.start_datetime|time }}{% else %}{{ event.start_datetime }}{% endif %}
25 25
    {% else %}
26 26
      {{ event.get_recurrence_display }}
chrono/manager/templates/chrono/manager_event_form.html
1 1
{% extends "chrono/manager_agenda_view.html" %}
2
{% load i18n %}
2
{% load i18n gadjo %}
3 3

  
4 4
{% block extrascripts %}
5 5
{{ block.super }}
......
29 29
<form method="post" enctype="multipart/form-data">
30 30
  {% csrf_token %}
31 31
  <input type="hidden" name="next" value="{% firstof request.POST.next request.GET.next %}">
32
  {{ form.as_p }}
32
  {{ form|with_template }}
33 33
  <div class="buttons">
34 34
    <button class="submit-button">{% trans "Save" %}</button>
35 35
    <a class="cancel" href="{{ view.get_success_url }}">{% trans 'Cancel' %}</a>
36 36
  </div>
37

  
38
  <script>
39
    $(function () {
40
      recurrence_fields = $('.widget[id^=id_recurrence]');
41
      $('input[type=radio][name=frequency]').change(function() {
42
        if(!this.checked)
43
          return;
44
        if(this.value == 'unique') {
45
          recurrence_fields.hide();
46
        } else {
47
          recurrence_fields.show();
48
        }
49
      }).change();
50
    });
51
  </script>
37 52
</form>
38 53
{% endblock %}
chrono/manager/templates/chrono/widgets/weekdays.html
1
{% spaceless %}
2
<span id="{{ widget.attrs.id }}" class="inputs-buttons-group{% if widget.attrs.class %} {{ widget.attrs.class }}{% endif %}">
3
	{% for group, options, index in widget.optgroups %}
4
	{% for option in options %}
5
	{% include "django/forms/widgets/input.html" with widget=option %}
6
	<label{% if option.attrs.id %} for="{{ option.attrs.id }}"{% endif %}>{{ option.label }}</label>
7
	{% endfor %}
8
	{% endfor %}
9
</span>
10
{% endspaceless %}
chrono/manager/views.py
1049 1049

  
1050 1050
    def get_queryset(self):
1051 1051
        if self.agenda.kind == 'events':
1052
            queryset = self.agenda.event_set.filter(recurrence_rule__isnull=True)
1052
            queryset = self.agenda.event_set.filter(recurrence_days__isnull=True)
1053 1053
        else:
1054 1054
            self.agenda.prefetch_desks_and_exceptions()
1055 1055
            if self.agenda.kind == 'meetings':
......
1578 1578
        if self.agenda.kind == 'events':
1579 1579
            context['has_absence_reasons'] = AbsenceReasonGroup.objects.exists()
1580 1580
            context['has_recurring_events'] = self.agenda.event_set.filter(
1581
                recurrence_rule__isnull=False
1581
                recurrence_days__isnull=False
1582 1582
            ).exists()
1583 1583
            desk = Desk.objects.get(agenda=self.agenda, slug='_exceptions_holder')
1584 1584
            context['exceptions'] = TimePeriodException.objects.filter(
......
1860 1860
    pk_url_kwarg = 'event_pk'
1861 1861

  
1862 1862
    def dispatch(self, request, *args, **kwargs):
1863
        if self.get_object().recurrence_rule:
1863
        if self.get_object().recurrence_days:
1864 1864
            raise Http404('this view makes no sense for recurring events')
1865 1865
        return super().dispatch(request, *args, **kwargs)
1866 1866

  
......
1895 1895
        if (
1896 1896
            self.request.GET.get('next') == 'settings'
1897 1897
            or self.request.POST.get('next') == 'settings'
1898
            or self.object.recurrence_rule
1898
            or self.object.recurrence_days
1899 1899
        ):
1900 1900
            return reverse('chrono-manager-agenda-settings', kwargs={'pk': self.agenda.id})
1901 1901
        return reverse('chrono-manager-event-view', kwargs={'pk': self.agenda.id, 'event_pk': self.object.id})
chrono/manager/widgets.py
16 16

  
17 17

  
18 18
from django.forms.fields import SplitDateTimeField
19
from django.forms.widgets import SplitDateTimeWidget, TimeInput
19
from django.forms.widgets import SplitDateTimeWidget, TimeInput, CheckboxSelectMultiple
20 20
from django.utils.safestring import mark_safe
21 21

  
22 22

  
......
59 59
        super(TimeWidget, self).__init__(**kwargs)
60 60
        self.attrs['step'] = '300'  # 5 minutes
61 61
        self.attrs['pattern'] = '[0-9]{2}:[0-9]{2}'
62

  
63

  
64
class WeekdaysWidget(CheckboxSelectMultiple):
65
    template_name = 'chrono/widgets/weekdays.html'
66

  
67
    def id_for_label(self, id_, index=None):
68
        """Workaround CheckboxSelectMultiple id_for_label, which would return empty string when
69
        index is None, leading to more complicated JS from our side."""
70
        if index is None:
71
            index = ''
72
        return super(CheckboxSelectMultiple, self).id_for_label(id_, index)
tests/manager/test_all.py
2607 2607
    Event.objects.create(
2608 2608
        label='xyz', start_datetime=localtime().replace(day=11, month=11, year=2020), places=10, agenda=agenda
2609 2609
    )
2610
    recurring_start_datetime = localtime().replace(day=4, month=11, year=2020)
2610 2611
    event = Event.objects.create(
2611 2612
        label='abc',
2612
        start_datetime=localtime().replace(day=4, month=11, year=2020),
2613
        start_datetime=recurring_start_datetime,
2613 2614
        places=10,
2614 2615
        agenda=agenda,
2615
        repeat='weekly',
2616
        recurrence_days=[recurring_start_datetime.weekday()],
2616 2617
    )
2617 2618

  
2618 2619
    with CaptureQueriesContext(connection) as ctx:
......
2635 2636
    # create another event with recurrence, the same day/time
2636 2637
    start_datetime = localtime().replace(day=4, month=11, year=2020)
2637 2638
    event = Event.objects.create(
2638
        label='def', start_datetime=start_datetime, places=10, agenda=agenda, repeat='weekly'
2639
        label='def',
2640
        start_datetime=start_datetime,
2641
        places=10,
2642
        agenda=agenda,
2643
        recurrence_days=[start_datetime.weekday()],
2639 2644
    )
2640 2645
    resp = app.get('/manage/agendas/%s/2020/11/11/' % agenda.pk)
2641 2646
    # the event occurence in DB does not hide recurrence of the second recurrent event
......
2675 2680
    # add recurring event on every Wednesday
2676 2681
    start_datetime = localtime().replace(day=4, month=11, year=2020)
2677 2682
    event = Event.objects.create(
2678
        label='abc', start_datetime=start_datetime, places=10, agenda=agenda, repeat='weekly'
2683
        label='abc',
2684
        start_datetime=start_datetime,
2685
        places=10,
2686
        agenda=agenda,
2687
        recurrence_days=[start_datetime.weekday()],
2679 2688
    )
2680 2689

  
2681 2690
    with CaptureQueriesContext(connection) as ctx:
......
2715 2724
    # create another event with recurrence, the same day/time
2716 2725
    start_datetime = localtime().replace(day=4, month=11, year=2020)
2717 2726
    event = Event.objects.create(
2718
        label='def', start_datetime=start_datetime, places=10, agenda=agenda, repeat='weekly'
2727
        label='def',
2728
        start_datetime=start_datetime,
2729
        places=10,
2730
        agenda=agenda,
2731
        recurrence_days=[start_datetime.weekday()],
2719 2732
    )
2720 2733
    resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.id, 2020, 12))
2721 2734
    # the event occurence in DB does not hide recurrence of the second recurrent event
......
2798 2811
        places=42,
2799 2812
    )
2800 2813
    # weekly recurring event, first recurrence is in the past but second is in range
2814
    start_datetime = now() - datetime.timedelta(days=3)
2801 2815
    event = Event.objects.create(
2802 2816
        label='event G',
2803
        start_datetime=now() - datetime.timedelta(days=3),
2817
        start_datetime=start_datetime,
2804 2818
        places=10,
2805 2819
        agenda=agenda,
2806
        repeat='weekly',
2820
        recurrence_days=[start_datetime.weekday()],
2807 2821
    )
2808 2822
    resp = app.get('/manage/agendas/%s/events/open/' % agenda.pk)
2809 2823
    assert 'event A' not in resp.text
......
4449 4463
    resp = app.get('/manage/agendas/%s/settings' % agenda.id)
4450 4464
    assert not 'Recurrence exceptions' in resp.text
4451 4465

  
4452
    event.repeat = 'daily'
4466
    event.recurrence_days = list(range(7))
4453 4467
    event.save()
4454 4468

  
4455 4469
    resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.id, 2021, 7))
......
4493 4507
    event = Event.objects.create(
4494 4508
        start_datetime=now(),
4495 4509
        places=10,
4496
        repeat='daily',
4510
        recurrence_days=list(range(7)),
4497 4511
        recurrence_end_date=now() + datetime.timedelta(days=30),
4498 4512
        agenda=agenda,
4499 4513
    )
tests/manager/test_event.py
83 83
        assert (
84 84
            resp.text.count('Enter a valid date')
85 85
            or resp.text.count('Enter a valid time') == 1
86
            or resp.text.count('This field is required.') == 1
86
            or resp.text.count('This field is required.') >= 1
87 87
        )
88 88

  
89 89

  
......
223 223

  
224 224
    app = login(app)
225 225
    resp = app.get('/manage/agendas/%s/events/%s/edit' % (agenda.id, event.id))
226
    resp.form['repeat'] = 'weekly'
226
    resp.form['frequency'] = 'recurring'
227
    resp.form['recurrence_days'] = [localtime().weekday()]
227 228
    resp = resp.form.submit()
228 229

  
229 230
    # detail page doesn't exist
230 231
    resp = app.get('/manage/agendas/%s/events/%s/' % (agenda.id, event.id), status=404)
231 232

  
232 233
    resp = app.get('/manage/agendas/%s/settings' % agenda.id, status=200)
233
    assert 'Weekly on Tuesday at 1:10 p.m.' in resp.text
234
    assert 'On Tuesday at 1:10 p.m.' in resp.text
234 235
    # event is bookable regardless of minimal_booking_delay, since it has bookable recurrences
235 236
    assert len(resp.pyquery.find('.bookable')) == 1
236 237

  
......
250 251

  
251 252
    # but some fields should not be updated
252 253
    assert event_recurrence.slug != event.slug
253
    assert not event_recurrence.repeat
254
    assert not event_recurrence.recurrence_days
255
    assert not event_recurrence.recurrence_week_interval
254 256

  
255 257
    # changing recurrence attribute removes event recurrences
256 258
    resp = app.get('/manage/agendas/%s/events/%s/edit' % (agenda.id, event.id))
257
    resp.form['repeat'] = ''
259
    resp.form['frequency'] = 'unique'
258 260
    resp = resp.form.submit().follow()
259 261
    assert not Event.objects.filter(primary_event=event).exists()
260 262

  
......
272 274
    event_recurrence = event.get_or_create_event_recurrence(event.start_datetime + datetime.timedelta(days=7))
273 275
    Booking.objects.create(event=event_recurrence)
274 276
    resp = app.get('/manage/agendas/%s/events/%s/edit' % (agenda.id, event.id))
275
    assert 'disabled' in resp.form['repeat'].attrs
277
    assert 'disabled' in resp.form['frequency'].attrs
278
    assert all('disabled' in resp.form.get('recurrence_days', index=i).attrs for i in range(7))
279
    assert 'disabled' in resp.form['recurrence_week_interval'].attrs
276 280
    assert 'disabled' in resp.form['slug'].attrs
277 281
    assert 'disabled' in resp.form['start_datetime_0'].attrs
278 282
    assert 'disabled' in resp.form['start_datetime_1'].attrs
......
287 291
    assert 'Delete' not in resp.text
288 292

  
289 293
    resp = resp.click('Options')
290
    assert {'slug', 'repeat', 'recurrence_end_date', 'publication_date'}.isdisjoint(resp.form.fields)
294
    assert {
295
        'slug',
296
        'frequency',
297
        'recurrence_days',
298
        'recurence_weekly_interval',
299
        'recurrence_end_date',
300
        'publication_date',
301
    }.isdisjoint(resp.form.fields)
291 302

  
292 303

  
293 304
def test_edit_recurring_event_with_end_date(settings, app, admin_user, freezer):
294 305
    freezer.move_to('2021-01-12 12:10')
295 306
    agenda = Agenda.objects.create(label='Foo bar', kind='events')
296
    event = Event.objects.create(start_datetime=now(), places=10, repeat='daily', agenda=agenda)
307
    event = Event.objects.create(
308
        start_datetime=now(), places=10, recurrence_days=list(range(7)), agenda=agenda
309
    )
297 310

  
298 311
    app = login(app)
299 312
    resp = app.get('/manage/agendas/%s/events/%s/edit' % (agenda.id, event.id))
......
301 314
    resp = resp.form.submit()
302 315

  
303 316
    # recurrences are created automatically
304
    event = Event.objects.get(recurrence_rule__isnull=False)
317
    event = Event.objects.get(recurrence_days__isnull=False)
305 318
    assert Event.objects.filter(primary_event=event).count() == 5
306 319
    assert Event.objects.filter(primary_event=event, start_datetime=now()).exists()
307 320

  
......
345 358
    assert Event.objects.filter(primary_event=event).count() == 4
346 359
    assert 'Bookings exist after this date' in resp.text
347 360

  
348
    Booking.objects.all().delete()
349
    resp = app.get('/manage/agendas/%s/events/%s/edit' % (agenda.id, event.id))
350
    resp.form['repeat'] = ''
351
    resp = resp.form.submit()
352
    assert 'Recurrence end date makes no sense without repetition.' in resp.text
353

  
354 361

  
355 362
def test_booked_places(app, admin_user):
356 363
    agenda = Agenda(label=u'Foo bar')
......
444 451
def test_delete_recurring_event(app, admin_user, freezer):
445 452
    agenda = Agenda.objects.create(label='Foo bar', kind='events')
446 453
    start_datetime = now() + datetime.timedelta(days=10)
447
    event = Event.objects.create(start_datetime=start_datetime, places=10, agenda=agenda, repeat='weekly')
454
    event = Event.objects.create(
455
        start_datetime=start_datetime, places=10, agenda=agenda, recurrence_days=[start_datetime.weekday()]
456
    )
448 457

  
449 458
    app = login(app)
450 459
    resp = app.get('/manage/agendas/%s/settings' % agenda.id, status=200)
tests/test_agendas.py
1857 1857
    event = Event.objects.create(
1858 1858
        agenda=agenda,
1859 1859
        start_datetime=now(),
1860
        repeat='weekly',
1860
        recurrence_days=[now().weekday()],
1861 1861
        label='Event',
1862 1862
        places=10,
1863 1863
        waiting_list_places=10,
......
1876 1876

  
1877 1877
    event_json = event.export_json()
1878 1878
    first_event_json = first_event.export_json()
1879
    different_fields = ['slug', 'repeat', 'recurrence_rule']
1879
    different_fields = ['slug', 'recurrence_days', 'recurrence_week_interval']
1880 1880
    assert all(first_event_json[k] == event_json[k] for k in event_json if k not in different_fields)
1881 1881

  
1882 1882
    second_event = recurrences[1]
......
1900 1900
    freezer.move_to('2020-10-24 12:00')
1901 1901
    settings.TIME_ZONE = 'Europe/Brussels'
1902 1902
    agenda = Agenda.objects.create(label='Agenda', kind='events')
1903
    event = Event.objects.create(agenda=agenda, start_datetime=now(), repeat='weekly', places=5)
1903
    event = Event.objects.create(
1904
        agenda=agenda, start_datetime=now(), recurrence_days=[now().weekday()], places=5
1905
    )
1904 1906
    event.refresh_from_db()
1905 1907
    dt = localtime()
1906 1908
    recurrences = event.get_recurrences(dt, dt + datetime.timedelta(days=8))
......
1918 1920
    assert event_after_dst.slug == new_event_after_dst.slug
1919 1921

  
1920 1922

  
1921
def test_recurring_events_weekday_midnight(freezer):
1922
    freezer.move_to('2021-01-06 23:30')
1923
    weekday = localtime().weekday()
1924
    agenda = Agenda.objects.create(label='Agenda', kind='events')
1925
    event = Event.objects.create(agenda=agenda, start_datetime=now(), repeat='weekly', places=5)
1926

  
1927
    assert event.recurrence_rule['byweekday'][0] == weekday
1928

  
1929

  
1930
def test_recurring_events_repeat(freezer):
1923
def test_recurring_events_repetition(freezer):
1931 1924
    freezer.move_to('2021-01-06 12:00')  # Wednesday
1932 1925
    agenda = Agenda.objects.create(label='Agenda', kind='events')
1933 1926
    event = Event.objects.create(
1934 1927
        agenda=agenda,
1935 1928
        start_datetime=now(),
1936
        repeat='daily',
1929
        recurrence_days=list(range(7)),  # everyday
1937 1930
        places=5,
1938 1931
    )
1939 1932
    event.refresh_from_db()
......
1949 1942
    for i in range(len(recurrences) - 1):
1950 1943
        assert recurrences[i].start_datetime + datetime.timedelta(days=1) == recurrences[i + 1].start_datetime
1951 1944

  
1952
    event.repeat = 'weekdays'
1945
    event.recurrence_days = list(range(5))  # from Monday to Friday
1953 1946
    event.save()
1954 1947
    recurrences = event.get_recurrences(
1955 1948
        localtime() + datetime.timedelta(days=1), localtime() + datetime.timedelta(days=7)
......
1959 1952
    assert recurrences[1].start_datetime == start_datetime + datetime.timedelta(days=5)
1960 1953
    assert recurrences[-1].start_datetime == start_datetime + datetime.timedelta(days=7)
1961 1954

  
1962
    event.repeat = '2-weeks'
1955
    event.recurrence_days = [localtime(event.start_datetime).weekday()]  # from Monday to Friday
1956
    event.recurrence_week_interval = 2
1963 1957
    event.save()
1964 1958
    recurrences = event.get_recurrences(
1965 1959
        localtime() + datetime.timedelta(days=3), localtime() + datetime.timedelta(days=45)
......
1972 1966
            recurrences[i].start_datetime + datetime.timedelta(days=14) == recurrences[i + 1].start_datetime
1973 1967
        )
1974 1968

  
1969
    event.recurrence_days = [3]  # Tuesday but start_datetime is a Wednesday
1970
    event.recurrence_week_interval = 1
1971
    event.save()
1972
    recurrences = event.get_recurrences(localtime(), localtime() + datetime.timedelta(days=10))
1973
    assert len(recurrences) == 2
1974
    # no recurrence exist on Wednesday
1975
    assert all(localtime(r.start_datetime).weekday() == 3 for r in recurrences)
1976

  
1975 1977

  
1976 1978
@pytest.mark.freeze_time('2021-01-06')
1977 1979
def test_recurring_events_with_end_date():
......
1979 1981
    event = Event.objects.create(
1980 1982
        agenda=agenda,
1981 1983
        start_datetime=now(),
1982
        repeat='daily',
1984
        recurrence_days=list(range(7)),
1983 1985
        places=5,
1984 1986
        recurrence_end_date=(now() + datetime.timedelta(days=5)).date(),
1985 1987
    )
......
1997 1999
def test_recurring_events_sort(freezer):
1998 2000
    freezer.move_to('2021-01-06 12:00')  # Wednesday
1999 2001
    agenda = Agenda.objects.create(label='Agenda', kind='events')
2000
    Event.objects.create(agenda=agenda, slug='a', start_datetime=now(), repeat='daily', places=5)
2001
    Event.objects.create(agenda=agenda, slug='b', start_datetime=now(), repeat='daily', duration=10, places=5)
2002
    Event.objects.create(agenda=agenda, slug='c', start_datetime=now(), repeat='daily', duration=5, places=5)
2003 2002
    Event.objects.create(
2004
        agenda=agenda, slug='d', start_datetime=now() + datetime.timedelta(hours=1), repeat='daily', places=5
2003
        agenda=agenda, slug='a', start_datetime=now(), recurrence_days=list(range(7)), places=5
2004
    )
2005
    Event.objects.create(
2006
        agenda=agenda, slug='b', start_datetime=now(), recurrence_days=list(range(7)), duration=10, places=5
2007
    )
2008
    Event.objects.create(
2009
        agenda=agenda, slug='c', start_datetime=now(), recurrence_days=list(range(7)), duration=5, places=5
2010
    )
2011
    Event.objects.create(
2012
        agenda=agenda,
2013
        slug='d',
2014
        start_datetime=now() + datetime.timedelta(hours=1),
2015
        recurrence_days=list(range(7)),
2016
        places=5,
2005 2017
    )
2006 2018

  
2007 2019
    events = agenda.get_open_events()[:8]
......
2021 2033
    event = Event.objects.create(
2022 2034
        agenda=agenda,
2023 2035
        start_datetime=now(),
2024
        repeat='daily',
2036
        recurrence_days=list(range(7)),
2025 2037
        places=5,
2026 2038
    )
2027 2039
    event.refresh_from_db()
......
2094 2106
    daily_event = Event.objects.create(
2095 2107
        agenda=agenda,
2096 2108
        start_datetime=now(),
2097
        repeat='daily',
2109
        recurrence_days=list(range(7)),
2098 2110
        places=5,
2099 2111
        recurrence_end_date=datetime.date(year=2021, month=5, day=8),
2100 2112
    )
2101 2113
    weekly_event = Event.objects.create(
2102 2114
        agenda=agenda,
2103 2115
        start_datetime=now(),
2104
        repeat='weekly',
2116
        recurrence_days=[now().weekday()],
2105 2117
        places=5,
2106 2118
        recurrence_end_date=datetime.date(year=2021, month=6, day=1),
2107 2119
    )
......
2110 2122
    daily_event_no_end_date = Event.objects.create(
2111 2123
        agenda=agenda,
2112 2124
        start_datetime=now() + datetime.timedelta(hours=2),
2113
        repeat='daily',
2125
        recurrence_days=list(range(7)),
2114 2126
        places=5,
2115 2127
    )
2116 2128
    daily_event_no_end_date.refresh_from_db()
......
2147 2159
    assert Booking.objects.count() == 1
2148 2160
    assert Event.objects.filter(primary_event=daily_event_no_end_date).count() == 1
2149 2161
    assert agenda.recurrence_exceptions_report.events.get() == event
2162

  
2163

  
2164
def test_recurring_events_display(freezer):
2165
    freezer.move_to('2021-01-06 12:30')
2166
    agenda = Agenda.objects.create(label='Agenda', kind='events')
2167
    event = Event.objects.create(
2168
        agenda=agenda, start_datetime=now(), recurrence_days=list(range(7)), places=5
2169
    )
2170

  
2171
    assert event.get_recurrence_display() == 'Daily at 1:30 p.m.'
2172

  
2173
    event.recurrence_days = [1, 2, 3, 4]
2174
    event.save()
2175
    assert event.get_recurrence_display() == 'From Tuesday to Friday at 1:30 p.m.'
2176

  
2177
    event.recurrence_days = [4, 5, 6]
2178
    event.save()
2179
    assert event.get_recurrence_display() == 'From Friday to Sunday at 1:30 p.m.'
2180

  
2181
    event.recurrence_days = [1, 4, 6]
2182
    event.save()
2183
    assert event.get_recurrence_display() == 'On Tuesday, Friday, Sunday at 1:30 p.m.'
2184

  
2185
    event.recurrence_days = [0]
2186
    event.recurrence_week_interval = 2
2187
    event.save()
2188
    assert event.get_recurrence_display() == 'On Monday at 1:30 p.m., once every two weeks'
2189

  
2190
    event.recurrence_week_interval = 3
2191
    event.recurrence_end_date = now() + datetime.timedelta(days=7)
2192
    event.save()
2193
    assert (
2194
        event.get_recurrence_display()
2195
        == 'On Monday at 1:30 p.m., once every three weeks, until Jan. 13, 2021'
2196
    )
tests/test_api.py
300 300
        start_datetime=now(),
301 301
        places=10,
302 302
        agenda=event_agenda,
303
        repeat='daily',
303
        recurrence_days=list(range(7)),
304 304
    )
305 305
    assert len(event_agenda.get_open_events()) == 2
306 306
    resp = app.get('/api/agenda/', params={'with_open_events': '1'})
......
308 308

  
309 309
    for i in range(10):
310 310
        event_agenda = Agenda.objects.create(label='Foo bar', category=category_a)
311
        event = Event.objects.create(start_datetime=now(), places=10, agenda=event_agenda, repeat='daily')
311
        event = Event.objects.create(
312
            start_datetime=now(), places=10, agenda=event_agenda, recurrence_days=[now().weekday()]
313
        )
312 314
        TimePeriodException.objects.create(
313 315
            desk=event_agenda.desk_set.get(),
314 316
            start_datetime=now(),
......
602 604
    event.delete()
603 605

  
604 606
    # recurrent event
607
    start_datetime = localtime().replace(hour=12, minute=0)
605 608
    event = Event.objects.create(
606 609
        slug='recurrent',
607
        start_datetime=localtime().replace(hour=12, minute=0),
608
        repeat='weekly',
610
        start_datetime=start_datetime,
611
        recurrence_days=[start_datetime.weekday()],
609 612
        places=2,
610 613
        agenda=agenda,
611 614
    )
......
1312 1315
    event.delete()
1313 1316

  
1314 1317
    # recurrent event
1318
    start_datetime = localtime().replace(hour=12, minute=0)
1315 1319
    event = Event.objects.create(
1316 1320
        slug='recurrent',
1317
        start_datetime=localtime().replace(hour=12, minute=0),
1318
        repeat='weekly',
1321
        start_datetime=start_datetime,
1322
        recurrence_days=[start_datetime.weekday()],
1319 1323
        places=2,
1320 1324
        agenda=agenda,
1321 1325
    )
......
5995 5999
    # recurring event
5996 6000
    Event.objects.all().delete()
5997 6001
    Event.objects.create(
5998
        slug='abc', label='Test', start_datetime=localtime(), repeat='weekly', places=5, agenda=agenda
6002
        slug='abc',
6003
        label='Test',
6004
        start_datetime=localtime(),
6005
        recurrence_days=[localtime().weekday()],
6006
        places=5,
6007
        agenda=agenda,
5999 6008
    )
6000 6009
    resp = app.get(api_url)
6001 6010
    assert resp.json['meta']['first_bookable_slot']['text'] == 'Test (May 27, 2017, 1:12 a.m.)'
......
6188 6197
        label='Foo bar', kind='events', minimal_booking_delay=1, maximal_booking_delay=30
6189 6198
    )
6190 6199
    base_event = Event.objects.create(
6191
        slug='abc', label='Test', start_datetime=localtime(), repeat='weekly', places=5, agenda=agenda
6200
        slug='abc',
6201
        label='Test',
6202
        start_datetime=localtime(),
6203
        recurrence_days=[localtime().weekday()],
6204
        places=5,
6205
        agenda=agenda,
6192 6206
    )
6193 6207

  
6194 6208
    resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug)
......
6256 6270
        label='Foo bar', kind='events', minimal_booking_delay=0, maximal_booking_delay=30
6257 6271
    )
6258 6272
    event = Event.objects.create(
6259
        slug='abc', start_datetime=localtime(), repeat='weekly', places=5, agenda=agenda
6273
        slug='abc',
6274
        start_datetime=localtime(),
6275
        recurrence_days=[localtime().weekday()],
6276
        places=5,
6277
        agenda=agenda,
6260 6278
    )
6261 6279
    event.refresh_from_db()
6262 6280

  
......
6339 6357
        label='Foo bar', kind='events', minimal_booking_delay=1, maximal_booking_delay=30
6340 6358
    )
6341 6359
    event = Event.objects.create(
6342
        slug='abc', start_datetime=localtime(), repeat='weekly', places=5, agenda=agenda
6360
        slug='abc',
6361
        start_datetime=localtime(),
6362
        recurrence_days=[localtime().weekday()],
6363
        places=5,
6364
        agenda=agenda,
6343 6365
    )
6344 6366

  
6345 6367
    resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug)
tests/test_ensure_jsonbfields.py
12 12
    json_fields = (
13 13
        'extra_data',
14 14
        'booking_errors',
15
        'recurrence_rule',
16 15
    )
17 16

  
18 17
    with connection.cursor() as cursor:
......
34 33
            '''ALTER TABLE agendas_eventcancellationreport
35 34
              ALTER COLUMN booking_errors TYPE text USING booking_errors::text'''
36 35
        )
37
        cursor.execute(
38
            '''ALTER TABLE agendas_event
39
              ALTER COLUMN recurrence_rule TYPE text USING recurrence_rule::text'''
40
        )
41 36

  
42 37
    call_command('ensure_jsonb')
43 38

  
tests/test_import_export.py
202 202
    event = Event.objects.create(
203 203
        agenda=agenda,
204 204
        start_datetime=now(),
205
        repeat='daily',
205
        recurrence_days=list(range(7)),
206
        recurrence_week_interval=2,
206 207
        places=10,
207 208
        slug='test',
208 209
    )
......
223 224
    assert Event.objects.count() == 1
224 225
    event = Agenda.objects.get(label='Foo Bar').event_set.first()
225 226
    assert event.primary_event is None
226
    assert event.repeat == 'daily'
227
    assert event.recurrence_rule == {'freq': DAILY}
227
    assert event.recurrence_days == list(range(7))
228
    assert event.recurrence_week_interval == 2
228 229

  
229 230
    # importing event with end recurrence date creates recurrences
230 231
    event.recurrence_end_date = now() + datetime.timedelta(days=7)
232
    event.recurrence_week_interval = 1
231 233
    event.save()
232 234
    output = get_output_of_command('export_site')
233 235
    import_site(data={}, clean=True)
234
-