0005-manager-handle-edition-deletion-of-recurring-event-4.patch
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 |
- |