manager: allow importing partial bookings events (#78061)
gitea/chrono/pipeline/head This commit looks good Details

This commit is contained in:
Valentin Deniaud 2023-05-31 17:02:57 +02:00
parent 18470cb6be
commit aa511e416f
6 changed files with 61 additions and 19 deletions

View File

@ -1133,8 +1133,8 @@ class ImportEventsForm(forms.Form):
)
events = None
def __init__(self, agenda_pk, **kwargs):
self.agenda_pk = agenda_pk
def __init__(self, agenda, **kwargs):
self.agenda = agenda
super().__init__(**kwargs)
def clean_events_csv_file(self):
@ -1143,6 +1143,8 @@ class ImportEventsForm(forms.Form):
super().__init__(message)
self.message = format_html(message, event_no=mark_safe(ordinal(event_no + 1)))
exclude_from_validation = ['desk', 'meeting_type', 'primary_event']
content = self.cleaned_data['events_csv_file'].read()
if b'\0' in content:
raise ValidationError(_('Invalid file format.'))
@ -1164,10 +1166,10 @@ class ImportEventsForm(forms.Form):
events = []
warnings = {}
events_by_slug = {x.slug: x for x in Event.objects.filter(agenda=self.agenda_pk)}
events_by_slug = {x.slug: x for x in Event.objects.filter(agenda=self.agenda.pk)}
event_ids_with_bookings = set(
Booking.objects.filter(
event__agenda=self.agenda_pk, cancellation_datetime__isnull=True
event__agenda=self.agenda.pk, cancellation_datetime__isnull=True
).values_list('event_id', flat=True)
)
seen_slugs = set(events_by_slug.keys())
@ -1196,9 +1198,9 @@ class ImportEventsForm(forms.Form):
event.label = label
if event is None:
# new event
event = Event(agenda_id=self.agenda_pk, label=label)
event = Event(agenda_id=self.agenda.pk, label=label)
# generate a slug if not provided
event.slug = slug or generate_slug(event, seen_slugs=seen_slugs, agenda=self.agenda_pk)
event.slug = slug or generate_slug(event, seen_slugs=seen_slugs, agenda=self.agenda.pk)
# maintain caches
seen_slugs.add(event.slug)
events_by_slug[event.slug] = event
@ -1270,14 +1272,20 @@ class ImportEventsForm(forms.Form):
else:
raise ValidationError(_('Invalid file format. (date/time format, {event_no} event)'), i)
if len(csvline) >= 11 and csvline[10]: # duration is optional
try:
event.duration = int(csvline[10])
except ValueError:
raise ValidationError(_('Invalid file format. (duration, {event_no} event)'), i)
if self.agenda.partial_bookings:
if len(csvline) < 11 or not csvline[10]:
raise ValidationError(_('Invalid file format. (missing end_time, {event_no} event)'), i)
event.end_time = csvline[10]
else:
exclude_from_validation.append('end_time')
if len(csvline) >= 11 and csvline[10]: # duration is optional
try:
event.duration = int(csvline[10])
except ValueError:
raise ValidationError(_('Invalid file format. (duration, {event_no} event)'), i)
try:
event.full_clean(exclude=['desk', 'meeting_type', 'primary_event', 'end_time'])
event.full_clean(exclude=exclude_from_validation)
except ValidationError as e:
errors = [_('Invalid file format:\n')]
for label, field_errors in e.message_dict.items():

View File

@ -21,7 +21,7 @@
{% csrf_token %}
{{ form.as_p }}
<p>
<a href="{% url 'chrono-manager-sample-events-csv' %}">{% trans 'Download sample file' %}</a>
<a href="{% url 'chrono-manager-sample-events-csv' pk=agenda.id %}">{% trans 'Download sample file' %}</a>
</p>
<div class="buttons">
<button>{% trans "Import" %}</button>

View File

@ -1,2 +1,2 @@
{% load i18n %}{% trans 'date' %},{% trans 'time' %},{% trans 'number of places' %},{% trans 'number of places in waiting list' %},{% trans 'label' %},{% trans 'identifier' %},{% trans 'description' %},{% trans 'pricing' %},{% trans 'URL' %},{% trans 'publication date/time' %},{% trans 'duration' %}
{{ some_future_date|date:"Y-m-d" }},{{ some_future_date|date:"H:i" }},15,0,{% trans "example event" as label %}{{ label }},{{ label|slugify }},,,https://www.example.net,{{ some_future_date|date:"Y-m-d H:i" }},30
{% load i18n %}{% trans 'date' %},{% trans 'time' %},{% trans 'number of places' %},{% trans 'number of places in waiting list' %},{% trans 'label' %},{% trans 'identifier' %},{% trans 'description' %},{% trans 'pricing' %},{% trans 'URL' %},{% trans 'publication date/time' %},{% if agenda.partial_bookings %}{% trans 'end time' %}{% else %}{% trans 'duration' %}{% endif %}
{{ some_future_date|date:"Y-m-d" }},{{ some_future_date|date:"H:i" }},15,0,{% trans "example event" as label %}{{ label }},{{ label|slugify }},,,https://www.example.net,{{ some_future_date|date:"Y-m-d H:i" }},{% if agenda.partial_bookings %}{{ some_future_date|date:"H:i" }}{% else %}30{% endif %}

View File

@ -434,8 +434,8 @@ urlpatterns = [
views.subscription_extra_user_block,
name='chrono-manager-subscription-extra-user-block',
),
re_path(
r'^agendas/events.csv$',
path(
'agendas/<int:pk>/events.csv',
views.agenda_import_events_sample_csv,
name='chrono-manager-sample-events-csv',
),

View File

@ -2106,7 +2106,8 @@ class AgendaEventDuplicateView(ManagedAgendaMixin, UpdateView):
event_duplicate = AgendaEventDuplicateView.as_view()
class AgendaImportEventsSampleView(TemplateView):
class AgendaImportEventsSampleView(ManagedAgendaMixin, DetailView):
model = Agenda
template_name = 'chrono/manager_sample_events.txt'
content_type = 'text/csv'
@ -2131,7 +2132,7 @@ class AgendaImportEventsView(ManagedAgendaMixin, FormView):
def get_form_kwargs(self):
kwargs = super(FormView, self).get_form_kwargs()
kwargs['agenda_pk'] = self.kwargs['pk']
kwargs['agenda'] = self.agenda
return kwargs
def form_valid(self, form):

View File

@ -788,6 +788,7 @@ def test_import_events(app, admin_user):
sample_csv_resp = resp.click('Download sample file')
assert sample_csv_resp.content_type == 'text/csv'
assert sample_csv_resp.text.startswith('date,time')
assert 'end time' not in sample_csv_resp.text
resp.form['events_csv_file'] = Upload('t.csv', sample_csv_resp.content, 'text/csv')
resp = resp.form.submit(status=302)
@ -1166,6 +1167,38 @@ def test_import_events_wrong_kind(app, admin_user):
app.get('/manage/agendas/%s/import-events' % agenda.id, status=404)
def test_import_events_partial_bookings(app, admin_user):
agenda = Agenda.objects.create(label='Foo bar', kind='events', partial_bookings=True)
Desk.objects.create(agenda=agenda, slug='_exceptions_holder')
app = login(app)
resp = app.get('/manage/agendas/%s/import-events' % agenda.pk)
sample_csv_resp = resp.click('Download sample file')
assert 'end time' in sample_csv_resp.text
assert 'duration' not in sample_csv_resp.text
resp.form['events_csv_file'] = Upload('t.csv', sample_csv_resp.content, 'text/csv')
resp = resp.form.submit(status=302)
event = Event.objects.get()
assert event.end_time == datetime.time(14, 00)
# no end time
resp = app.get('/manage/agendas/%s/import-events' % agenda.pk)
resp.form['events_csv_file'] = Upload(
't.csv', b'2016-09-16,18:00,10,5,label,slug,description,pricing,url,2016-09-16', 'text/csv'
)
resp = resp.form.submit(status=200)
assert 'Invalid file format. (missing end_time' in resp.text
# invalid end time
resp = app.get('/manage/agendas/%s/import-events' % agenda.pk)
resp.form['events_csv_file'] = Upload(
't.csv', b'2016-09-16,18:00,10,5,label,slug,description,pricing,url,2016-09-16,xxx', 'text/csv'
)
resp = resp.form.submit(status=200)
assert '“xxx” value has an invalid format' in resp.text
@pytest.mark.freeze_time('2022-05-24')
def test_event_detail(app, admin_user):
agenda = Agenda.objects.create(label='Events', kind='events')