manager: split methods of CSV import (#70523)
This commit is contained in:
parent
60f31525ee
commit
b9c02c20bd
|
@ -1213,7 +1213,7 @@ class ImportEventsForm(forms.Form):
|
|||
super().__init__(message)
|
||||
self.message = format_html(message, event_no=mark_safe(ordinal(event_no + self.line_offset)))
|
||||
|
||||
exclude_from_validation = ['desk', 'meeting_type', 'primary_event']
|
||||
self.exclude_from_validation = ['desk', 'meeting_type', 'primary_event']
|
||||
|
||||
content = self.cleaned_data['events_csv_file'].read()
|
||||
if b'\0' in content:
|
||||
|
@ -1234,49 +1234,105 @@ class ImportEventsForm(forms.Form):
|
|||
except csv.Error:
|
||||
dialect = None
|
||||
|
||||
events = []
|
||||
warnings = {}
|
||||
events_by_slug = {x.slug: x for x in Event.objects.filter(agenda=self.agenda.pk)}
|
||||
event_ids_with_bookings = set(
|
||||
self.events = []
|
||||
self.warnings = {}
|
||||
self.events_by_slug = {x.slug: x for x in Event.objects.filter(agenda=self.agenda.pk)}
|
||||
self.event_ids_with_bookings = set(
|
||||
Booking.objects.filter(
|
||||
event__agenda=self.agenda.pk, cancellation_datetime__isnull=True
|
||||
).values_list('event_id', flat=True)
|
||||
)
|
||||
seen_slugs = set(events_by_slug.keys())
|
||||
self.seen_slugs = set(self.events_by_slug.keys())
|
||||
for i, csvline in enumerate(csv.reader(StringIO(content), dialect=dialect)):
|
||||
if not csvline:
|
||||
continue
|
||||
if len(csvline) < 3:
|
||||
raise ValidationErrorWithOrdinal(_('Invalid file format. ({event_no} event)'), i)
|
||||
|
||||
if i == 0 and csvline[0].strip('#') in ('date', 'Date', _('date'), _('Date')):
|
||||
ValidationErrorWithOrdinal.line_offset = 0
|
||||
continue
|
||||
|
||||
# label needed to generate a slug
|
||||
label = None
|
||||
if len(csvline) >= 5:
|
||||
label = force_str(csvline[4])
|
||||
event = self.parse_csvline(csvline, i, ValidationErrorWithOrdinal)
|
||||
self.events.append(event)
|
||||
|
||||
# get or create event
|
||||
event = None
|
||||
slug = None
|
||||
if len(csvline) >= 6:
|
||||
slug = force_str(csvline[5]) if csvline[5] else None
|
||||
# get existing event if relevant
|
||||
if slug and slug in seen_slugs:
|
||||
event = events_by_slug[slug]
|
||||
# update label
|
||||
event.label = label
|
||||
if event is None:
|
||||
# new event
|
||||
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)
|
||||
# maintain caches
|
||||
seen_slugs.add(event.slug)
|
||||
events_by_slug[event.slug] = event
|
||||
def parse_csvline(self, csvline, i, ValidationErrorWithOrdinal):
|
||||
if len(csvline) < 3:
|
||||
raise ValidationErrorWithOrdinal(_('Invalid file format. ({event_no} event)'), i)
|
||||
|
||||
# label needed to generate a slug
|
||||
label = None
|
||||
if len(csvline) >= 5:
|
||||
label = force_str(csvline[4])
|
||||
|
||||
# get or create event
|
||||
event = None
|
||||
slug = None
|
||||
if len(csvline) >= 6:
|
||||
slug = force_str(csvline[5]) if csvline[5] else None
|
||||
# get existing event if relevant
|
||||
if slug and slug in self.seen_slugs:
|
||||
event = self.events_by_slug[slug]
|
||||
# update label
|
||||
event.label = label
|
||||
if event is None:
|
||||
# new event
|
||||
event = Event(agenda_id=self.agenda.pk, label=label)
|
||||
# generate a slug if not provided
|
||||
event.slug = slug or generate_slug(event, seen_slugs=self.seen_slugs, agenda=self.agenda.pk)
|
||||
# maintain caches
|
||||
self.seen_slugs.add(event.slug)
|
||||
self.events_by_slug[event.slug] = event
|
||||
|
||||
for datetime_fmt in (
|
||||
'%Y-%m-%d %H:%M',
|
||||
'%d/%m/%Y %H:%M',
|
||||
'%d/%m/%Y %Hh%M',
|
||||
'%Y-%m-%d %H:%M:%S',
|
||||
'%d/%m/%Y %H:%M:%S',
|
||||
):
|
||||
try:
|
||||
event_datetime = make_aware(
|
||||
datetime.datetime.strptime('%s %s' % tuple(csvline[:2]), datetime_fmt)
|
||||
)
|
||||
except ValueError:
|
||||
continue
|
||||
if (
|
||||
event.pk is not None
|
||||
and event.start_datetime != event_datetime
|
||||
and event.start_datetime > now()
|
||||
and event.pk in self.event_ids_with_bookings
|
||||
and event.pk not in self.warnings
|
||||
):
|
||||
# event start datetime has changed, event is not past and has not cancelled bookings
|
||||
# => warn the user
|
||||
self.warnings[event.pk] = event
|
||||
event.start_datetime = event_datetime
|
||||
break
|
||||
else:
|
||||
raise ValidationErrorWithOrdinal(
|
||||
_('Invalid file format. (date/time format, {event_no} event)'), i
|
||||
)
|
||||
try:
|
||||
event.places = int(csvline[2])
|
||||
except ValueError:
|
||||
raise ValidationError(_('Invalid file format. (number of places, {event_no} event)'), i)
|
||||
if len(csvline) >= 4:
|
||||
try:
|
||||
event.waiting_list_places = int(csvline[3])
|
||||
except ValueError:
|
||||
raise ValidationError(
|
||||
_('Invalid file format. (number of places in waiting list, {event_no} event)'), i
|
||||
)
|
||||
|
||||
column_index = 7
|
||||
for more_attr in ('description', 'pricing', 'url'):
|
||||
if len(csvline) >= column_index:
|
||||
setattr(event, more_attr, csvline[column_index - 1])
|
||||
column_index += 1
|
||||
|
||||
if len(csvline) >= 10 and csvline[9]: # publication date is optional
|
||||
for datetime_fmt in (
|
||||
'%Y-%m-%d',
|
||||
'%d/%m/%Y',
|
||||
'%Y-%m-%d %H:%M',
|
||||
'%d/%m/%Y %H:%M',
|
||||
'%d/%m/%Y %Hh%M',
|
||||
|
@ -1284,93 +1340,42 @@ class ImportEventsForm(forms.Form):
|
|||
'%d/%m/%Y %H:%M:%S',
|
||||
):
|
||||
try:
|
||||
event_datetime = make_aware(
|
||||
datetime.datetime.strptime('%s %s' % tuple(csvline[:2]), datetime_fmt)
|
||||
event.publication_datetime = make_aware(
|
||||
datetime.datetime.strptime(csvline[9], datetime_fmt)
|
||||
)
|
||||
break
|
||||
except ValueError:
|
||||
continue
|
||||
if (
|
||||
event.pk is not None
|
||||
and event.start_datetime != event_datetime
|
||||
and event.start_datetime > now()
|
||||
and event.pk in event_ids_with_bookings
|
||||
and event.pk not in warnings
|
||||
):
|
||||
# event start datetime has changed, event is not past and has not cancelled bookings
|
||||
# => warn the user
|
||||
warnings[event.pk] = event
|
||||
event.start_datetime = event_datetime
|
||||
break
|
||||
else:
|
||||
raise ValidationErrorWithOrdinal(
|
||||
_('Invalid file format. (date/time format, {event_no} event)'), i
|
||||
)
|
||||
try:
|
||||
event.places = int(csvline[2])
|
||||
except ValueError:
|
||||
raise ValidationError(_('Invalid file format. (number of places, {event_no} event)'), i)
|
||||
if len(csvline) >= 4:
|
||||
raise ValidationError(_('Invalid file format. (date/time format, {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:
|
||||
self.exclude_from_validation.append('end_time')
|
||||
if len(csvline) >= 11 and csvline[10]: # duration is optional
|
||||
try:
|
||||
event.waiting_list_places = int(csvline[3])
|
||||
event.duration = int(csvline[10])
|
||||
except ValueError:
|
||||
raise ValidationError(
|
||||
_('Invalid file format. (number of places in waiting list, {event_no} event)'), i
|
||||
)
|
||||
raise ValidationError(_('Invalid file format. (duration, {event_no} event)'), i)
|
||||
|
||||
column_index = 7
|
||||
for more_attr in ('description', 'pricing', 'url'):
|
||||
if len(csvline) >= column_index:
|
||||
setattr(event, more_attr, csvline[column_index - 1])
|
||||
column_index += 1
|
||||
try:
|
||||
event.full_clean(exclude=self.exclude_from_validation)
|
||||
except ValidationError as e:
|
||||
errors = [_('Invalid file format:\n')]
|
||||
for label, field_errors in e.message_dict.items():
|
||||
label_name = self.get_verbose_name(label)
|
||||
msg = _('%s: ') % label_name if label_name else ''
|
||||
msg += _('%(errors)s (line %(line)d)') % {
|
||||
'errors': ', '.join(field_errors),
|
||||
'line': i + 1,
|
||||
}
|
||||
errors.append(msg)
|
||||
raise ValidationError(errors)
|
||||
|
||||
if len(csvline) >= 10 and csvline[9]: # publication date is optional
|
||||
for datetime_fmt in (
|
||||
'%Y-%m-%d',
|
||||
'%d/%m/%Y',
|
||||
'%Y-%m-%d %H:%M',
|
||||
'%d/%m/%Y %H:%M',
|
||||
'%d/%m/%Y %Hh%M',
|
||||
'%Y-%m-%d %H:%M:%S',
|
||||
'%d/%m/%Y %H:%M:%S',
|
||||
):
|
||||
try:
|
||||
event.publication_datetime = make_aware(
|
||||
datetime.datetime.strptime(csvline[9], datetime_fmt)
|
||||
)
|
||||
break
|
||||
except ValueError:
|
||||
continue
|
||||
else:
|
||||
raise ValidationError(_('Invalid file format. (date/time format, {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=exclude_from_validation)
|
||||
except ValidationError as e:
|
||||
errors = [_('Invalid file format:\n')]
|
||||
for label, field_errors in e.message_dict.items():
|
||||
label_name = self.get_verbose_name(label)
|
||||
msg = _('%s: ') % label_name if label_name else ''
|
||||
msg += _('%(errors)s (line %(line)d)') % {
|
||||
'errors': ', '.join(field_errors),
|
||||
'line': i + 1,
|
||||
}
|
||||
errors.append(msg)
|
||||
raise ValidationError(errors)
|
||||
events.append(event)
|
||||
self.events = events
|
||||
self.warnings = warnings
|
||||
return event
|
||||
|
||||
@staticmethod
|
||||
def get_verbose_name(field_name):
|
||||
|
|
Loading…
Reference in New Issue