0001-manager-display-link-to-booking-form-on-events-45417.patch
chrono/agendas/migrations/0062_auto_20200915_1401.py | ||
---|---|---|
39 | 39 |
'email_extra_info', |
40 | 40 |
models.TextField( |
41 | 41 |
blank=True, |
42 |
help_text='Basic information such as event name, time and date are already included', |
|
42 |
help_text='Basic information such as event name, time and date are already included.',
|
|
43 | 43 |
verbose_name='Additional text to incude in emails', |
44 | 44 |
), |
45 | 45 |
), |
... | ... | |
48 | 48 |
'sms_extra_info', |
49 | 49 |
models.TextField( |
50 | 50 |
blank=True, |
51 |
help_text='Basic information such as event name, time and date are already included', |
|
51 |
help_text='Basic information such as event name, time and date are already included.',
|
|
52 | 52 |
verbose_name='Additional text to incude in SMS', |
53 | 53 |
), |
54 | 54 |
), |
chrono/agendas/migrations/0063_booking_form_url.py | ||
---|---|---|
1 |
# -*- coding: utf-8 -*- |
|
2 |
from __future__ import unicode_literals |
|
3 | ||
4 |
import chrono.agendas.models |
|
5 |
from django.db import migrations, models |
|
6 | ||
7 | ||
8 |
class Migration(migrations.Migration): |
|
9 | ||
10 |
dependencies = [ |
|
11 |
('agendas', '0062_auto_20200915_1401'), |
|
12 |
] |
|
13 | ||
14 |
operations = [ |
|
15 |
migrations.AddField( |
|
16 |
model_name='agenda', |
|
17 |
name='booking_form_url', |
|
18 |
field=models.CharField( |
|
19 |
blank=True, |
|
20 |
max_length=200, |
|
21 |
validators=[chrono.agendas.models.django_template_validator], |
|
22 |
verbose_name='Booking form URL', |
|
23 |
), |
|
24 |
), |
|
25 |
] |
chrono/agendas/models.py | ||
---|---|---|
35 | 35 |
from django.core.validators import MaxValueValidator |
36 | 36 |
from django.db import models, transaction |
37 | 37 |
from django.db.models import Count, Q, Case, When |
38 |
from django.template import engines, Context, Template, TemplateSyntaxError, VariableDoesNotExist |
|
38 | 39 |
from django.urls import reverse |
39 | 40 |
from django.utils import functional |
40 | 41 |
from django.utils.dates import WEEKDAYS |
... | ... | |
115 | 116 |
raise ValidationError(_('This value cannot be a number.')) |
116 | 117 | |
117 | 118 | |
119 |
def django_template_validator(value): |
|
120 |
try: |
|
121 |
engines['django'].from_string(value) |
|
122 |
except TemplateSyntaxError as e: |
|
123 |
raise ValidationError(_('syntax error: %s') % e) |
|
124 | ||
125 | ||
118 | 126 |
class ICSError(Exception): |
119 | 127 |
pass |
120 | 128 | |
... | ... | |
171 | 179 |
'Category', verbose_name=_('Category'), blank=True, null=True, on_delete=models.SET_NULL |
172 | 180 |
) |
173 | 181 |
default_view = models.CharField(_('Default view'), max_length=20, choices=AGENDA_VIEWS, default='month') |
182 |
booking_form_url = models.CharField( |
|
183 |
_('Booking form URL'), max_length=200, blank=True, validators=[django_template_validator] |
|
184 |
) |
|
174 | 185 | |
175 | 186 |
class Meta: |
176 | 187 |
ordering = ['label'] |
... | ... | |
289 | 300 |
if hasattr(self, 'reminder_settings'): |
290 | 301 |
agenda['reminder_settings'] = self.reminder_settings.export_json() |
291 | 302 |
if self.kind == 'events': |
303 |
agenda['default_view'] = self.default_view |
|
304 |
agenda['booking_form_url'] = self.booking_form_url |
|
292 | 305 |
agenda['events'] = [x.export_json() for x in self.event_set.all()] |
293 | 306 |
if hasattr(self, 'notifications_settings'): |
294 | 307 |
agenda['notifications_settings'] = self.notifications_settings.export_json() |
... | ... | |
487 | 500 |
) |
488 | 501 |
return entries |
489 | 502 | |
503 |
def get_booking_form_url(self): |
|
504 |
if not self.booking_form_url: |
|
505 |
return |
|
506 |
template_vars = Context(settings.TEMPLATE_VARS) |
|
507 |
try: |
|
508 |
return Template(self.booking_form_url).render(template_vars) |
|
509 |
except (VariableDoesNotExist, TemplateSyntaxError): |
|
510 |
return |
|
511 | ||
490 | 512 | |
491 | 513 |
class VirtualMember(models.Model): |
492 | 514 |
'''Trough model to link virtual agendas to their real agendas. |
chrono/manager/forms.py | ||
---|---|---|
75 | 75 |
'minimal_booking_delay', |
76 | 76 |
'maximal_booking_delay', |
77 | 77 |
'default_view', |
78 |
'booking_form_url', |
|
78 | 79 |
] |
79 | 80 | |
80 | 81 |
def __init__(self, *args, **kwargs): |
... | ... | |
84 | 85 |
self.fields['maximal_booking_delay'].required = True |
85 | 86 |
if kwargs['instance'].kind != 'events': |
86 | 87 |
del self.fields['default_view'] |
88 |
del self.fields['booking_form_url'] |
|
87 | 89 | |
88 | 90 | |
89 | 91 |
class ResourceAddForm(forms.ModelForm): |
chrono/manager/templates/chrono/manager_agenda_event_fragment.html | ||
---|---|---|
44 | 44 |
</a> |
45 | 45 |
{% if settings_view %} |
46 | 46 |
<a rel="popup" class="delete" href="{% url 'chrono-manager-event-delete' pk=agenda.pk event_pk=event.pk %}?next=settings">{% trans "remove" %}</a> |
47 |
{% elif not event.cancellation_status %}
|
|
48 |
<a rel="popup" class="link-action-text cancel" href="{% url 'chrono-manager-event-cancel' pk=agenda.pk event_pk=event.pk %}?next={{ request.path }}">{% trans "Cancel" %}</a>
|
|
47 |
{% elif agenda.booking_form_url %}
|
|
48 |
<a class="link-action-text" href="{{ agenda.get_booking_form_url }}?agenda={{ agenda.slug }}&event={{ event.slug }}">{% trans "Booking form" %}</a>
|
|
49 | 49 |
{% endif %} |
50 | 50 |
<span class="occupation-bar"></span> |
51 | 51 |
</li> |
chrono/manager/templates/chrono/manager_event_detail.html | ||
---|---|---|
33 | 33 |
<a rel="popup" class="link-action-text cancel" href="{% url 'chrono-manager-event-cancel' pk=agenda.pk event_pk=event.pk %}?next={{ request.path }}">{% trans "Cancel" %}</a> |
34 | 34 |
<a href="{% url 'chrono-manager-event-edit' pk=agenda.id event_pk=object.id %}">{% trans "Options" %}</a> |
35 | 35 |
{% endif %} |
36 |
{% if object.agenda.booking_form_url %} |
|
37 |
<a href="{{ object.agenda.get_booking_form_url }}?agenda={{ object.agenda.slug }}&event={{ event.slug }}">{% trans "Booking form" %}</a> |
|
38 |
{% endif %} |
|
36 | 39 |
</span> |
37 | 40 |
{% endblock %} |
38 | 41 |
tests/test_import_export.py | ||
---|---|---|
134 | 134 |
shutil.rmtree(tempdir) |
135 | 135 | |
136 | 136 | |
137 |
def test_import_export_events_agenda_options(app): |
|
138 |
agenda = Agenda.objects.create( |
|
139 |
label='Foo Bar', |
|
140 |
kind='events', |
|
141 |
default_view='open_events', |
|
142 |
booking_form_url='{{ eservices_url }}backoffice/submission/inscription-aux-activites/', |
|
143 |
) |
|
144 | ||
145 |
output = get_output_of_command('export_site') |
|
146 |
assert len(json.loads(output)['agendas']) == 1 |
|
147 |
import_site(data={}, clean=True) |
|
148 | ||
149 |
with tempfile.NamedTemporaryFile() as f: |
|
150 |
f.write(force_bytes(output)) |
|
151 |
f.flush() |
|
152 |
call_command('import_site', f.name) |
|
153 | ||
154 |
assert Agenda.objects.count() == 1 |
|
155 |
agenda = Agenda.objects.first() |
|
156 |
assert agenda.default_view == 'open_events' |
|
157 |
assert agenda.booking_form_url == '{{ eservices_url }}backoffice/submission/inscription-aux-activites/' |
|
158 | ||
159 | ||
137 | 160 |
def test_import_export_event_details(app): |
138 | 161 |
agenda = Agenda.objects.create(label='Foo Bar', kind='events') |
139 | 162 |
Event.objects.create( |
tests/test_manager.py | ||
---|---|---|
17 | 17 |
from django.utils.encoding import force_text |
18 | 18 |
from django.utils.timezone import make_aware, now, localtime |
19 | 19 | |
20 |
import datetime |
|
21 | 20 |
import freezegun |
22 | 21 |
import pytest |
23 | 22 |
import requests |
... | ... | |
877 | 876 |
assert resp.form['label'].value == 'Foo bar' |
878 | 877 |
resp.form['label'] = 'Foo baz' |
879 | 878 |
assert 'default_view' in resp.context['form'].fields |
879 |
assert 'booking_form_url' in resp.context['form'].fields |
|
880 | 880 |
resp = resp.form.submit() |
881 | 881 |
assert resp.location.endswith('/manage/agendas/%s/settings' % agenda_events.pk) |
882 | 882 |
resp = resp.follow() |
... | ... | |
886 | 886 | |
887 | 887 |
resp = app.get('/manage/agendas/%s/edit' % agenda_meetings.pk) |
888 | 888 |
assert 'default_view' not in resp.context['form'].fields |
889 |
assert 'booking_form_url' not in resp.context['form'].fields |
|
889 | 890 |
resp = app.get('/manage/agendas/%s/edit' % agenda_virtual.pk) |
890 | 891 |
assert 'default_view' not in resp.context['form'].fields |
892 |
assert 'booking_form_url' not in resp.context['form'].fields |
|
891 | 893 | |
892 | 894 | |
893 | 895 |
def test_options_agenda_cant_unset_delays(app, admin_user): |
... | ... | |
3805 | 3807 |
event = Event.objects.create( |
3806 | 3808 |
label='xyz', start_datetime=now() + datetime.timedelta(days=1), places=10, agenda=agenda |
3807 | 3809 |
) |
3808 |
day = event.start_datetime |
|
3809 | 3810 | |
3810 | 3811 |
login(app) |
3811 |
resp = app.get('/manage/agendas/%s/%d/%d/' % (agenda.id, day.year, day.month))
|
|
3812 |
assert '0/10 bookings' in resp.text
|
|
3812 |
resp = app.get('/manage/agendas/%d/events/%d/' % (agenda.pk, event.pk))
|
|
3813 |
assert 'Bookings (0/10)' in resp.text
|
|
3813 | 3814 | |
3814 | 3815 |
resp = resp.click('Cancel', href='/cancel') |
3815 |
assert not 'related bookings' in resp.text
|
|
3816 |
assert 'related bookings' not in resp.text
|
|
3816 | 3817 | |
3817 |
booking = Booking.objects.create(event=event)
|
|
3818 |
booking2 = Booking.objects.create(event=event)
|
|
3818 |
Booking.objects.create(event=event) |
|
3819 |
Booking.objects.create(event=event) |
|
3819 | 3820 | |
3820 |
resp = app.get('/manage/agendas/%s/%d/%d/' % (agenda.id, day.year, day.month))
|
|
3821 |
assert '2/10 bookings' in resp.text
|
|
3821 |
resp = app.get('/manage/agendas/%d/events/%d/' % (agenda.pk, event.pk))
|
|
3822 |
assert 'Bookings (2/10)' in resp.text
|
|
3822 | 3823 | |
3823 |
resp = resp.click('Cancel', href='/cancel')
|
|
3824 |
resp = resp.click('Cancel', href='manage/agendas/%d/events/%d/cancel' % (agenda.pk, event.pk))
|
|
3824 | 3825 |
assert '2 related bookings will also be cancelled.' in resp.text |
3825 | 3826 | |
3826 | 3827 |
resp = resp.form.submit().follow() |
3827 | 3828 |
assert 'Cancelled' in resp.text |
3828 |
assert '0/10 bookings' in resp.text
|
|
3829 |
assert 'Bookings (0/10)' in resp.text
|
|
3829 | 3830 |
assert Booking.objects.filter(event=event, cancellation_datetime__isnull=False).count() == 2 |
3830 | 3831 | |
3831 | 3832 | |
... | ... | |
3847 | 3848 |
resp = resp.click('Cancellation error reports') |
3848 | 3849 |
assert 'No error report' in resp.text |
3849 | 3850 | |
3850 |
resp = app.get('/manage/agendas/%s/%d/%d/' % (agenda.id, day.year, day.month)) |
|
3851 |
resp = resp.click('Cancel', href='/cancel') |
|
3851 |
resp = app.get('/manage/agendas/%d/events/%d/cancel' % (agenda.pk, event.pk)) |
|
3852 | 3852 |
resp = resp.form.submit().follow() |
3853 | 3853 |
assert 'Cancellation in progress' in resp.text |
3854 | 3854 | |
... | ... | |
3891 | 3891 |
event = Event.objects.create( |
3892 | 3892 |
label='xyz', start_datetime=now() + datetime.timedelta(days=1), places=10, agenda=agenda |
3893 | 3893 |
) |
3894 |
booking = Booking.objects.create(event=event) |
|
3895 |
booking2 = Booking.objects.create(event=event, backoffice_url='http://example.org/backoffice/xx/') |
|
3896 |
day = event.start_datetime |
|
3894 |
Booking.objects.create(event=event) |
|
3895 |
Booking.objects.create(event=event, backoffice_url='http://example.org/backoffice/xx/') |
|
3897 | 3896 | |
3898 | 3897 |
login(app) |
3899 |
resp = app.get('/manage/agendas/%s/%d/%d/' % (agenda.id, day.year, day.month)) |
|
3900 |
resp = resp.click('Cancel', href='/cancel') |
|
3898 |
resp = app.get('/manage/agendas/%d/events/%d/cancel' % (agenda.pk, event.pk)) |
|
3901 | 3899 |
assert 'event has bookings with no callback url configured' in resp.text |
3902 | 3900 |
assert 'Proceed with cancellation' not in resp.text |
3903 | 3901 | |
3904 | 3902 | |
3903 |
def test_event_booking_form_url(settings, app, admin_user): |
|
3904 |
settings.TEMPLATE_VARS = {'eservices_url': 'http://demarches/'} |
|
3905 |
agenda = Agenda.objects.create(label='Events', kind='events') |
|
3906 |
event = Event.objects.create( |
|
3907 |
label='xyz', start_datetime=now() + datetime.timedelta(days=1), places=10, agenda=agenda |
|
3908 |
) |
|
3909 |
day = event.start_datetime |
|
3910 | ||
3911 |
login(app) |
|
3912 | ||
3913 |
assert agenda.get_booking_form_url() is None |
|
3914 |
resp = app.get('/manage/agendas/%d/%d/%d/' % (agenda.pk, day.year, day.month)) |
|
3915 |
assert 'Booking form' not in resp.text |
|
3916 |
resp = app.get('/manage/agendas/%d/events/open/' % agenda.pk) |
|
3917 |
assert 'Booking form' not in resp.text |
|
3918 |
resp = app.get('/manage/agendas/%d/events/%d/' % (agenda.pk, event.pk)) |
|
3919 |
assert 'Booking form' not in resp.text |
|
3920 | ||
3921 |
agenda.booking_form_url = '{{ eservices_url }}backoffice/submission/inscription-aux-activites/' |
|
3922 |
agenda.save() |
|
3923 |
assert ( |
|
3924 |
agenda.get_booking_form_url() == 'http://demarches/backoffice/submission/inscription-aux-activites/' |
|
3925 |
) |
|
3926 |
resp = app.get('/manage/agendas/%d/%d/%d/' % (agenda.pk, day.year, day.month)) |
|
3927 |
assert ( |
|
3928 |
'<a class="link-action-text" href="http://demarches/backoffice/submission/inscription-aux-activites/?agenda=%s&event=%s">Booking form</a>' |
|
3929 |
% (agenda.slug, event.slug) |
|
3930 |
in resp.text |
|
3931 |
) |
|
3932 |
resp = app.get('/manage/agendas/%d/events/open/' % agenda.pk) |
|
3933 |
assert ( |
|
3934 |
'<a class="link-action-text" href="http://demarches/backoffice/submission/inscription-aux-activites/?agenda=%s&event=%s">Booking form</a>' |
|
3935 |
% (agenda.slug, event.slug) |
|
3936 |
in resp.text |
|
3937 |
) |
|
3938 |
resp = app.get('/manage/agendas/%d/events/%d/' % (agenda.pk, event.pk)) |
|
3939 |
assert ( |
|
3940 |
'<a href="http://demarches/backoffice/submission/inscription-aux-activites/?agenda=%s&event=%s">Booking form</a>' |
|
3941 |
% (agenda.slug, event.slug) |
|
3942 |
in resp.text |
|
3943 |
) |
|
3944 | ||
3945 | ||
3905 | 3946 |
def test_agenda_notifications(app, admin_user, managers_group): |
3906 | 3947 |
agenda = Agenda.objects.create(label='Events', kind='events') |
3907 | 3948 | |
3908 |
- |