Skip to content
Snippets Groups Projects
Commit 19cc463e authored by Jan Prachař's avatar Jan Prachař
Browse files

O něco blbuvzdornější import účastníků

* Varování, pokud je pravděpodobně překlep v názvu sloupce a při importu
  pak dojde k chybě.
* Rozlišení, jestli je fomát ročníku uplně špatně, nebo jen neodpovídá
  typu školy.
parent c52c69b9
No related branches found
No related tags found
1 merge request!39O něco blbuvzdornější import účastníků
......@@ -7,6 +7,7 @@
# - quoting pomocí uvozovek
import csv
import difflib
from enum import auto
from dataclasses import dataclass, fields
from typing import Type, List, IO, Sequence
......@@ -108,6 +109,7 @@ def write(file: IO, fmt: FileFormat, row_class: Type[Row], rows: Sequence[Row]):
def read(file: IO, fmt: FileFormat, row_class: Type[Row]):
reader = csv.reader(file, dialect=fmt.get_dialect(), strict=True)
warnings = []
header: List[str] = []
rows: List[Row] = []
columns = set(field.name for field in fields(row_class))
......@@ -120,6 +122,13 @@ def read(file: IO, fmt: FileFormat, row_class: Type[Row]):
header = r
if not any(h in columns for h in header):
raise MissingHeaderError()
for h in header:
if not h in columns:
best_matches = difflib.get_close_matches(h, columns, n=1, cutoff=0.8)
if best_matches:
warnings.append(
"Neznámý sloupec '{}', měli jste na mysli '{}'?".format(
h, best_matches[0]))
else:
row = row_class()
not_empty = False
......@@ -133,4 +142,4 @@ def read(file: IO, fmt: FileFormat, row_class: Type[Row]):
if not_empty:
rows.append(row)
return rows
return (rows, warnings)
......@@ -36,6 +36,7 @@ import_type_names = {
class Import:
# Výsledek importu
errors: List[str]
warnings: List[str]
cnt_rows: int = 0
cnt_new_users: int = 0
cnt_new_participants: int = 0
......@@ -69,6 +70,7 @@ class Import:
def __init__(self):
self.errors = []
self.warnings = []
self.rr = None
self.place_cache = {}
self.school_place_cache = {}
......@@ -164,11 +166,16 @@ class Import:
# lidé připisují všechny možné i nemožné znaky, které vypadají jako apostrof :)
rocnik = re.sub('[\'"\u00b4\u2019]', "", rocnik)
if (school.is_ss and re.fullmatch(r'\d/\d', rocnik)
or school.is_zs and re.fullmatch(r'\d', rocnik)):
return rocnik
if (not re.fullmatch(r'\d(/\d)?', rocnik)):
return self.error(f'Ročník má neplatný formát, musí to být buď číslice, nebo číslice/číslice')
if (not school.is_zs and re.fullmatch(r'\d', rocnik)):
return self.error(f'Ročník pro střední školu ({school.place.name}) zapisujte ve formátu číslice/číslice')
return self.error('Ročník neodpovídá typu školy: pro základní je to číslice, pro střední číslice/číslice')
if (not school.is_ss and re.fullmatch(r'\d/\d', rocnik)):
return self.error(f'Ročník pro základní školu ({school.place.name}) zapisujte jako číslici 1–9')
return rocnik
def parse_born(self, rok: str) -> Optional[int]:
if not re.fullmatch(r'\d{4}', rok):
......@@ -377,9 +384,11 @@ class Import:
try:
with open(path, encoding=charset) as file:
try:
rows: List[mo.csv.Row] = mo.csv.read(file=file, fmt=self.fmt, row_class=self.row_class)
rows: List[mo.csv.Row]
rows, warnings = mo.csv.read(file=file, fmt=self.fmt, row_class=self.row_class)
self.warnings += warnings
except MissingHeaderError:
return self.error('Souboru chybí hlavička s názvy sloupců')
return self.error('Souboru chybí první řádek s názvy sloupců')
except UnicodeDecodeError:
return self.error(f'Soubor není v kódování {self.fmt.get_charset()}')
except Exception as e:
......
......@@ -364,6 +364,7 @@ def generic_import(round: db.Round, contest: Optional[db.Contest]):
form = ImportForm()
errs = []
warnings = []
if form.validate_on_submit():
fmt = form.fmt.data
imp = create_import(user=g.user, type=form.typ.data, fmt=fmt, round=round, contest=contest)
......@@ -383,6 +384,7 @@ def generic_import(round: db.Round, contest: Optional[db.Contest]):
return redirect(url_for('org_round', id=round.round_id))
else:
errs = imp.errors
warnings = imp.warnings
else:
flash('Vyberte si prosím soubor', 'danger')
elif form.get_template.data:
......@@ -398,6 +400,7 @@ def generic_import(round: db.Round, contest: Optional[db.Contest]):
round=round,
form=form,
errs=errs,
warnings=warnings
)
......@@ -1283,6 +1286,7 @@ def generic_batch_points(round: db.Round, contest: Optional[db.Contest], task: d
form = BatchPointsForm()
errs = []
warnings = []
if form.validate_on_submit():
fmt = form.fmt.data
imp = create_import(user=g.user, type=ImportType.points, fmt=fmt, round=round, contest=contest, task=task, allow_add_del=form.add_del_sols.data)
......@@ -1302,6 +1306,7 @@ def generic_batch_points(round: db.Round, contest: Optional[db.Contest], task: d
return redirect(url_for('org_round', id=round.round_id))
else:
errs = imp.errors
warnings = imp.warnings
else:
flash('Vyberte si prosím soubor', 'danger')
elif form.get_template.data:
......@@ -1316,6 +1321,7 @@ def generic_batch_points(round: db.Round, contest: Optional[db.Contest], task: d
round=round, contest=contest, task=task,
form=form,
errs=errs,
warnings=warnings
)
......
......@@ -8,6 +8,16 @@
{% block body %}
{% if warnings %}
<h3>Varování při importu</h3>
<div class="alert alert-warning" role="alert" style="white-space: pre-line">{{ "" -}}
{% for e in warnings %}
{{ e }}
{% endfor %}
</div>
{% endif %}
{% if errs %}
<h3>Chyby při importu</h3>
......
......@@ -9,14 +9,24 @@ Import dat do {% if contest %}soutěžní oblasti {{ contest.place.name }}{% els
{% endblock %}
{% block body %}
{% if warnings %}
<h3>Varování při importu</h3>
<div class="alert alert-warning" role="alert" style="white-space: pre-line">{{ "" -}}
{% for e in warnings %}
{{ e }}
{% endfor %}
</div>
{% endif %}
{% if errs %}
<h3>Chyby při importu</h3>
<pre><div class="alert alert-danger" role="alert">{{ "" -}}
<div class="alert alert-danger" role="alert" style="white-space: pre-line">{{ "" -}}
{% for e in errs %}
{{ e }}
{% endfor %}
</div></pre>
</div>
{% endif %}
<p>Zde je možné importovat účastníky soutěže, dozor na soutěžních místech a opravovatele.
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment