manager: allow importing partial bookings events (#78061)
gitea/chrono/pipeline/head This commit looks good
Details
gitea/chrono/pipeline/head This commit looks good
Details
parent
18470cb6be
commit
aa511e416f
|
@ -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():
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 %}
|
||||
|
|
|
@ -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',
|
||||
),
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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')
|
||||
|
|
Loading…
Reference in New Issue