Projet

Général

Profil

0005-manager-handle-edition-deletion-of-recurring-event-4.patch

Valentin Deniaud, 28 janvier 2021 12:38

Télécharger (10,6 ko)

Voir les différences:

Subject: [PATCH 5/5] manager: handle edition/deletion of recurring event
 (#41663)

 chrono/agendas/models.py                      |  12 ++-
 chrono/manager/forms.py                       |  25 +++++
 .../chrono/manager_event_detail.html          |   2 +
 chrono/manager/views.py                       |   1 +
 tests/test_manager.py                         | 100 ++++++++++++++++++
 5 files changed, 138 insertions(+), 2 deletions(-)
chrono/agendas/models.py
995 995
        today = localtime(now()).date()
996 996
        event_day = localtime(self.start_datetime).date()
997 997
        days_to_event = event_day - today
998
        if days_to_event < datetime.timedelta(days=self.agenda.minimal_booking_delay):
999
            return False
1000 998
        if self.agenda.maximal_booking_delay:
1001 999
            if days_to_event >= datetime.timedelta(days=self.agenda.maximal_booking_delay):
1002 1000
                return False
1001
        if self.recurrence_rule is not None:
1002
            # bookable recurrences probably exist
1003
            return True
1004
        if days_to_event < datetime.timedelta(days=self.agenda.minimal_booking_delay):
1005
            return False
1003 1006
        if self.start_datetime < now():
1004 1007
            # past the event date, we may want in the future to allow for some
1005 1008
            # extra late booking but it's forbidden for now.
......
1211 1214
            return None
1212 1215
        return rrule
1213 1216

  
1217
    def has_recurrences_booked(self):
1218
        return Booking.objects.filter(
1219
            event__primary_event=self, event__start_datetime__gt=now(), cancellation_datetime__isnull=True
1220
        ).exists()
1221

  
1214 1222

  
1215 1223
class BookingColor(models.Model):
1216 1224
    COLOR_COUNT = 8
chrono/manager/forms.py
23 23
from django.conf import settings
24 24
from django.contrib.auth.models import Group
25 25
from django.core.exceptions import FieldDoesNotExist
26
from django.db import transaction
26 27
from django.forms import ValidationError
27 28
from django.utils.encoding import force_text
28 29
from django.utils.six import StringIO
......
176 177

  
177 178

  
178 179
class EventForm(forms.ModelForm):
180
    protected_fields = ('repeat', 'slug', 'start_datetime')
181

  
179 182
    class Meta:
180 183
        model = Event
181 184
        widgets = {
......
198 201
            'start_datetime': SplitDateTimeField,
199 202
        }
200 203

  
204
    def __init__(self, *args, **kwargs):
205
        super().__init__(*args, **kwargs)
206
        if self.instance.recurrence_rule and self.instance.has_recurrences_booked():
207
            for field in self.protected_fields:
208
                self.fields[field].disabled = True
209
                self.fields[field].help_text = _(
210
                    'This field cannot be modified because some recurrences have bookings attached to them.'
211
                )
212

  
213
    def save(self, *args, **kwargs):
214
        with transaction.atomic():
215
            if any(field for field in self.changed_data if field in self.protected_fields):
216
                self.instance.recurrences.all().delete()
217
            elif self.instance.recurrence_rule:
218
                update_fields = {
219
                    field: value
220
                    for field, value in self.cleaned_data.items()
221
                    if field not in self.protected_fields
222
                }
223
                self.instance.recurrences.update(**update_fields)
224
            return super().save(*args, **kwargs)
225

  
201 226

  
202 227
class AgendaResourceForm(forms.Form):
203 228
    resource = forms.ModelChoiceField(label=_('Resource'), queryset=Resource.objects.none())
chrono/manager/templates/chrono/manager_event_detail.html
33 33
{% if not event.cancellation_status %}
34 34
<a rel="popup" href="{% url 'chrono-manager-event-cancel' pk=agenda.pk event_pk=event.pk %}?next={{ request.path }}">{% trans "Cancel" %}</a>
35 35
{% endif %}
36
{% if not object.primary_event %}
36 37
<a href="{% url 'chrono-manager-event-edit' pk=agenda.id event_pk=object.id %}">{% trans "Options" %}</a>
37 38
{% endif %}
39
{% endif %}
38 40
{% if object.agenda.booking_form_url %}
39 41
<a href="{{ object.agenda.get_booking_form_url }}?agenda={{ object.agenda.slug }}&event={{ event.slug }}">{% trans "Booking form" %}</a>
40 42
{% endif %}
chrono/manager/views.py
1690 1690
        context['cannot_delete'] = bool(
1691 1691
            self.object.booking_set.filter(cancellation_datetime__isnull=True).exists()
1692 1692
            and self.object.start_datetime > now()
1693
            or self.object.has_recurrences_booked()
1693 1694
        )
1694 1695
        return context
1695 1696

  
tests/test_manager.py
1356 1356
    assert event.publication_date is None
1357 1357

  
1358 1358

  
1359
def test_edit_recurring_event(settings, app, admin_user, freezer):
1360
    freezer.move_to('2021-01-12 12:10')
1361
    agenda = Agenda.objects.create(
1362
        label='Foo bar', kind='events', minimal_booking_delay=15, maximal_booking_delay=30
1363
    )
1364
    event = Event.objects.create(start_datetime=now(), places=10, agenda=agenda)
1365

  
1366
    app = login(app)
1367
    resp = app.get('/manage/agendas/%s/events/%s/edit' % (agenda.id, event.id))
1368
    resp.form['repeat'] = 'weekly'
1369
    resp = resp.form.submit()
1370

  
1371
    resp = app.get('/manage/agendas/%s/settings' % agenda.id, status=200)
1372
    assert 'Weekly on Tuesday at 1:10 p.m.' in resp.text
1373
    # event is bookable regardless of minimal_booking_delay, since it has bookable recurrences
1374
    assert len(resp.pyquery.find('.bookable')) == 1
1375

  
1376
    # maximal_booking_delay is accounted for, because no recurrences are bookable
1377
    freezer.move_to('2020-11-12')
1378
    resp = app.get('/manage/agendas/%s/settings' % agenda.id, status=200)
1379
    assert len(resp.pyquery.find('.not-bookable')) == 1
1380

  
1381
    # editing recurring event updates event recurrences
1382
    event.refresh_from_db()
1383
    event_recurrence = event.get_or_create_event_recurrence(event.start_datetime)
1384
    resp = app.get('/manage/agendas/%s/events/%s/edit' % (agenda.id, event.id))
1385
    resp.form['places'] = 20
1386
    resp = resp.form.submit().follow()
1387
    event_recurrence.refresh_from_db()
1388
    assert event_recurrence.places == 20
1389

  
1390
    # changing recurrence attribute removes event recurrences
1391
    resp = app.get('/manage/agendas/%s/events/%s/edit' % (agenda.id, event.id))
1392
    resp.form['repeat'] = ''
1393
    resp = resp.form.submit().follow()
1394
    assert not Event.objects.filter(primary_event=event).exists()
1395

  
1396
    # same goes with changing slug
1397
    event.recurrence = 'weekly'
1398
    event.save()
1399
    event_recurrence = event.get_or_create_event_recurrence(event.start_datetime)
1400
    assert Event.objects.filter(primary_event=event).exists()
1401
    resp = app.get('/manage/agendas/%s/events/%s/edit' % (agenda.id, event.id))
1402
    resp.form['slug'] = 'hop'
1403
    resp = resp.form.submit().follow()
1404
    assert not Event.objects.filter(primary_event=event).exists()
1405

  
1406
    # changing recurring attribute or slug is forbidden if there are bookings for future recurrences
1407
    event_recurrence = event.get_or_create_event_recurrence(event.start_datetime + datetime.timedelta(days=7))
1408
    Booking.objects.create(event=event_recurrence)
1409
    resp = app.get('/manage/agendas/%s/events/%s/edit' % (agenda.id, event.id))
1410
    assert 'disabled' in resp.form['repeat'].attrs
1411
    assert 'disabled' in resp.form['slug'].attrs
1412
    assert 'disabled' in resp.form['start_datetime_0'].attrs
1413
    assert 'disabled' in resp.form['start_datetime_1'].attrs
1414

  
1415
    # changing it anyway doesn't work
1416
    resp.form['slug'] = 'changed'
1417
    resp = resp.form.submit()
1418
    assert not Event.objects.filter(slug='changed').exists()
1419

  
1420
    # individually editing event recurrence is not supported
1421
    resp = app.get('/manage/agendas/%s/events/%s/' % (agenda.id, event_recurrence.id))
1422
    assert 'Options' not in resp.text
1423

  
1424

  
1359 1425
def test_booked_places(app, admin_user):
1360 1426
    agenda = Agenda(label=u'Foo bar')
1361 1427
    agenda.save()
......
1445 1511
    resp = resp.form.submit(status=403)
1446 1512

  
1447 1513

  
1514
def test_delete_recurring_event(app, admin_user, freezer):
1515
    agenda = Agenda.objects.create(label='Foo bar', kind='events')
1516
    start_datetime = now() + datetime.timedelta(days=10)
1517
    event = Event.objects.create(start_datetime=start_datetime, places=10, agenda=agenda, repeat='weekly')
1518

  
1519
    app = login(app)
1520
    resp = app.get('/manage/agendas/%s/settings' % agenda.id, status=200)
1521
    resp = resp.click(href='/manage/agendas/%s/events/%s/edit' % (agenda.id, event.id))
1522
    resp = resp.click('Delete')
1523
    assert 'Are you sure you want to delete this event?' in resp.text
1524

  
1525
    event_recurrence = event.get_or_create_event_recurrence(event.start_datetime)
1526
    booking = Booking.objects.create(event=event_recurrence)
1527
    resp = app.get('/manage/agendas/%s/settings' % agenda.id, status=200)
1528
    resp = resp.click(href='/manage/agendas/%s/events/%s/edit' % (agenda.id, event.id))
1529
    resp = resp.click('Delete')
1530
    assert 'This cannot be removed' in resp.text
1531

  
1532
    booking.cancellation_datetime = now()
1533
    booking.save()
1534
    resp = app.get('/manage/agendas/%s/settings' % agenda.id, status=200)
1535
    resp = resp.click(href='/manage/agendas/%s/events/%s/edit' % (agenda.id, event.id))
1536
    resp = resp.click('Delete')
1537
    assert 'Are you sure you want to delete this event?' in resp.text
1538

  
1539
    booking.cancellation_datetime = None
1540
    booking.save()
1541
    freezer.move_to(now() + datetime.timedelta(days=11))
1542
    resp = app.get('/manage/agendas/%s/settings' % agenda.id, status=200)
1543
    resp = resp.click(href='/manage/agendas/%s/events/%s/edit' % (agenda.id, event.id))
1544
    resp = resp.click('Delete')
1545
    assert 'Are you sure you want to delete this event?' in resp.text
1546

  
1547

  
1448 1548
def test_delete_event_as_manager(app, manager_user):
1449 1549
    agenda = Agenda(label=u'Foo bar')
1450 1550
    agenda.edit_role = manager_user.groups.all()[0]
1451
-