manager: report all errors at once in CSV import (#70523)
gitea/chrono/pipeline/head This commit looks good
Details
gitea/chrono/pipeline/head This commit looks good
Details
This commit is contained in:
parent
b9c02c20bd
commit
8127fbff66
|
@ -1206,13 +1206,6 @@ class ImportEventsForm(forms.Form):
|
|||
super().__init__(**kwargs)
|
||||
|
||||
def clean_events_csv_file(self):
|
||||
class ValidationErrorWithOrdinal(ValidationError):
|
||||
line_offset = 1
|
||||
|
||||
def __init__(self, message, event_no):
|
||||
super().__init__(message)
|
||||
self.message = format_html(message, event_no=mark_safe(ordinal(event_no + self.line_offset)))
|
||||
|
||||
self.exclude_from_validation = ['desk', 'meeting_type', 'primary_event']
|
||||
|
||||
content = self.cleaned_data['events_csv_file'].read()
|
||||
|
@ -1234,6 +1227,7 @@ class ImportEventsForm(forms.Form):
|
|||
except csv.Error:
|
||||
dialect = None
|
||||
|
||||
errors = []
|
||||
self.events = []
|
||||
self.warnings = {}
|
||||
self.events_by_slug = {x.slug: x for x in Event.objects.filter(agenda=self.agenda.pk)}
|
||||
|
@ -1243,20 +1237,36 @@ class ImportEventsForm(forms.Form):
|
|||
).values_list('event_id', flat=True)
|
||||
)
|
||||
self.seen_slugs = set(self.events_by_slug.keys())
|
||||
line_offset = 1
|
||||
for i, csvline in enumerate(csv.reader(StringIO(content), dialect=dialect)):
|
||||
if not csvline:
|
||||
continue
|
||||
|
||||
if i == 0 and csvline[0].strip('#') in ('date', 'Date', _('date'), _('Date')):
|
||||
ValidationErrorWithOrdinal.line_offset = 0
|
||||
line_offset = 0
|
||||
continue
|
||||
|
||||
event = self.parse_csvline(csvline, i, ValidationErrorWithOrdinal)
|
||||
self.events.append(event)
|
||||
try:
|
||||
event = self.parse_csvline(csvline)
|
||||
except ValidationError as e:
|
||||
for error in getattr(e, 'error_list', [e]):
|
||||
errors.append(
|
||||
format_html(
|
||||
'{message} ({event_no} event)',
|
||||
message=error.message,
|
||||
event_no=mark_safe(ordinal(i + line_offset)),
|
||||
)
|
||||
)
|
||||
else:
|
||||
self.events.append(event)
|
||||
|
||||
def parse_csvline(self, csvline, i, ValidationErrorWithOrdinal):
|
||||
if errors:
|
||||
errors = [_('Invalid file format:')] + errors
|
||||
raise ValidationError(errors)
|
||||
|
||||
def parse_csvline(self, csvline):
|
||||
if len(csvline) < 3:
|
||||
raise ValidationErrorWithOrdinal(_('Invalid file format. ({event_no} event)'), i)
|
||||
raise ValidationError(_('Not enough columns.'))
|
||||
|
||||
# label needed to generate a slug
|
||||
label = None
|
||||
|
@ -1308,20 +1318,16 @@ class ImportEventsForm(forms.Form):
|
|||
event.start_datetime = event_datetime
|
||||
break
|
||||
else:
|
||||
raise ValidationErrorWithOrdinal(
|
||||
_('Invalid file format. (date/time format, {event_no} event)'), i
|
||||
)
|
||||
raise ValidationError(_('Wrong start date/time format.'))
|
||||
try:
|
||||
event.places = int(csvline[2])
|
||||
except ValueError:
|
||||
raise ValidationError(_('Invalid file format. (number of places, {event_no} event)'), i)
|
||||
raise ValidationError(_('Number of places must be an integer.'))
|
||||
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
|
||||
)
|
||||
raise ValidationError(_('Number of places in waiting list must be an integer.'))
|
||||
|
||||
column_index = 7
|
||||
for more_attr in ('description', 'pricing', 'url'):
|
||||
|
@ -1347,11 +1353,11 @@ class ImportEventsForm(forms.Form):
|
|||
except ValueError:
|
||||
continue
|
||||
else:
|
||||
raise ValidationError(_('Invalid file format. (date/time format, {event_no} event)'), i)
|
||||
raise ValidationError(_('Wrong publication date/time format.'))
|
||||
|
||||
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)
|
||||
raise ValidationError(_('Missing end_time.'))
|
||||
event.end_time = csvline[10]
|
||||
else:
|
||||
self.exclude_from_validation.append('end_time')
|
||||
|
@ -1359,19 +1365,16 @@ class ImportEventsForm(forms.Form):
|
|||
try:
|
||||
event.duration = int(csvline[10])
|
||||
except ValueError:
|
||||
raise ValidationError(_('Invalid file format. (duration, {event_no} event)'), i)
|
||||
raise ValidationError(_('Duration must be an integer.'))
|
||||
|
||||
try:
|
||||
event.full_clean(exclude=self.exclude_from_validation)
|
||||
except ValidationError as e:
|
||||
errors = [_('Invalid file format:\n')]
|
||||
errors = []
|
||||
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,
|
||||
}
|
||||
msg += ', '.join(field_errors)
|
||||
errors.append(msg)
|
||||
raise ValidationError(errors)
|
||||
|
||||
|
|
|
@ -798,7 +798,8 @@ def test_import_events(app, admin_user):
|
|||
resp = app.get('/manage/agendas/%s/import-events' % agenda.id, status=200)
|
||||
resp.form['events_csv_file'] = Upload('t.csv', b'xx', 'text/csv')
|
||||
resp = resp.form.submit(status=200)
|
||||
assert 'Invalid file format.' in resp.text
|
||||
assert 'Invalid file format:' in resp.text
|
||||
assert 'Wrong start date/time format. (1st event)' in resp.text
|
||||
|
||||
resp.form['events_csv_file'] = Upload('t.csv', b'xxxx\0\0xxxx', 'text/csv')
|
||||
resp = resp.form.submit(status=200)
|
||||
|
@ -806,15 +807,16 @@ def test_import_events(app, admin_user):
|
|||
|
||||
resp.form['events_csv_file'] = Upload('t.csv', b'2016-14-16,18:00', 'text/csv')
|
||||
resp = resp.form.submit(status=200)
|
||||
assert 'Invalid file format.' in resp.text
|
||||
assert 'Invalid file format:' in resp.text
|
||||
assert 'Not enough columns. (1st event)' in resp.text
|
||||
|
||||
resp.form['events_csv_file'] = Upload('t.csv', b'date,time,etc.\n2016-14-16,18:00,10', 'text/csv')
|
||||
resp = resp.form.submit(status=200)
|
||||
assert 'Invalid file format. (date/time format, 1st event)' in resp.text
|
||||
assert 'Wrong start date/time format. (1st event)' in resp.text
|
||||
|
||||
resp.form['events_csv_file'] = Upload('t.csv', b'2016-14-16,18:00,10', 'text/csv')
|
||||
resp = resp.form.submit(status=200)
|
||||
assert 'Invalid file format. (date/time format, 1st event)' in resp.text
|
||||
assert 'Wrong start date/time format. (1st event)' in resp.text
|
||||
|
||||
with override_settings(LANGUAGE_CODE='fr-fr'):
|
||||
resp.form['events_csv_file'] = Upload('t.csv', b'2016-14-16,18:00,10', 'text/csv')
|
||||
|
@ -824,11 +826,11 @@ def test_import_events(app, admin_user):
|
|||
|
||||
resp.form['events_csv_file'] = Upload('t.csv', b'2016-09-16,18:00,blah', 'text/csv')
|
||||
resp = resp.form.submit(status=200)
|
||||
assert 'Invalid file format. (number of places,' in resp.text
|
||||
assert 'Number of places must be an integer. (1st event)' in resp.text
|
||||
|
||||
resp.form['events_csv_file'] = Upload('t.csv', b'2016-09-16,18:00,10,blah', 'text/csv')
|
||||
resp = resp.form.submit(status=200)
|
||||
assert 'Invalid file format. (number of places in waiting list,' in resp.text
|
||||
assert 'Number of places in waiting list must be an integer. (1st event)' in resp.text
|
||||
|
||||
resp.form['events_csv_file'] = Upload('t.csv', b'2016-09-16,18:00,10,5,' + b'x' * 151, 'text/csv')
|
||||
resp = resp.form.submit(status=200)
|
||||
|
@ -988,7 +990,7 @@ def test_import_events(app, admin_user):
|
|||
't.csv', b'2016-09-16,18:00,10,5,label,slug,description,pricing,url,foobar', 'text/csv'
|
||||
)
|
||||
resp = resp.form.submit(status=200)
|
||||
assert 'Invalid file format. (date/time format' in resp.text
|
||||
assert 'Wrong publication date/time format. (1st event)' in resp.text
|
||||
|
||||
# duration bad format
|
||||
resp = app.get('/manage/agendas/%s/import-events' % agenda.id, status=200)
|
||||
|
@ -996,7 +998,7 @@ def test_import_events(app, admin_user):
|
|||
't.csv', b'2016-09-16,18:00,10,5,label,slug,description,pricing,url,2016-09-16,foobar', 'text/csv'
|
||||
)
|
||||
resp = resp.form.submit(status=200)
|
||||
assert 'Invalid file format. (duration' in resp.text
|
||||
assert 'Duration must be an integer. (1st event)' in resp.text
|
||||
|
||||
# import events with empty slugs
|
||||
Event.objects.all().delete()
|
||||
|
@ -1027,8 +1029,7 @@ def test_import_events(app, admin_user):
|
|||
resp = app.get('/manage/agendas/%s/import-events' % agenda.id, status=200)
|
||||
resp.form['events_csv_file'] = Upload('t.csv', b'2016-09-16,18:00,10,5,label,1234', 'text/csv')
|
||||
resp = resp.form.submit(status=200)
|
||||
assert 'value cannot be a number' in resp.text
|
||||
assert 'Identifier:' in resp.text # verbose_name is shown, not field name ('slug:')
|
||||
assert 'Identifier: This value cannot be a number. (1st event)' in resp.text
|
||||
|
||||
|
||||
def test_import_event_nested_quotes(app, admin_user):
|
||||
|
@ -1192,7 +1193,7 @@ def test_import_events_partial_bookings(app, admin_user):
|
|||
'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
|
||||
assert 'Missing end_time.' in resp.text
|
||||
|
||||
# invalid end time
|
||||
resp = app.get('/manage/agendas/%s/import-events' % agenda.pk)
|
||||
|
@ -1203,6 +1204,27 @@ def test_import_events_partial_bookings(app, admin_user):
|
|||
assert '“xxx” value has an invalid format' in resp.text
|
||||
|
||||
|
||||
def test_import_events_multiple_errors(app, admin_user):
|
||||
agenda = Agenda.objects.create(label='Foo bar', kind='events')
|
||||
Desk.objects.create(agenda=agenda, slug='_exceptions_holder')
|
||||
|
||||
app = login(app)
|
||||
resp = app.get('/manage/agendas/%s/import-events' % agenda.pk)
|
||||
resp.form['events_csv_file'] = Upload(
|
||||
't.csv',
|
||||
b'2016-09-17,18:00,10,5,label,slug\n' # valid event
|
||||
b'2016-09-17,19:00,xxx,5,label2,slug2\n' # invalid places
|
||||
b'2016-09-17,20:00,10,5,,1234\n', # invalid slug
|
||||
'text/csv',
|
||||
)
|
||||
resp = resp.form.submit(status=200)
|
||||
assert [x.text for x in resp.pyquery('.errorlist li')] == [
|
||||
'Invalid file format:',
|
||||
'Number of places must be an integer. (2nd event)',
|
||||
'Identifier: This value cannot be a number. (3rd event)',
|
||||
]
|
||||
|
||||
|
||||
@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