diff --git a/mo/csv.py b/mo/csv.py index 31c73ad2d8a73f455d81b4c2636e344556aab95a..39051bcd517139d796e915a8c9991a0d448f841d 100644 --- a/mo/csv.py +++ b/mo/csv.py @@ -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) diff --git a/mo/imports.py b/mo/imports.py index 1922896b8383f3f8ec89d2932f00de1860d0377b..6d59a2ca9a4b0c8e36cc30d9a4e8b7135a388453 100644 --- a/mo/imports.py +++ b/mo/imports.py @@ -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') - 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_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') + + 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: diff --git a/mo/web/org_contest.py b/mo/web/org_contest.py index 71fce79775fc997234e8dbbe431fe4589fc31a8f..468f29ef34100b722976056f144e6d2fded70c93 100644 --- a/mo/web/org_contest.py +++ b/mo/web/org_contest.py @@ -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 ) diff --git a/mo/web/templates/org_generic_batch_points.html b/mo/web/templates/org_generic_batch_points.html index dc4e0dc13a6522a7f06848933f87b8dcbc8ad1b9..e3c728b385f6e650c5edbdc4eac166d2092866c0 100644 --- a/mo/web/templates/org_generic_batch_points.html +++ b/mo/web/templates/org_generic_batch_points.html @@ -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> diff --git a/mo/web/templates/org_generic_import.html b/mo/web/templates/org_generic_import.html index 0e1618a5fcccaa6a038bfa7d5e1454d803491435..ebe4616a3b4ec546ed6704446e8efc829e73adec 100644 --- a/mo/web/templates/org_generic_import.html +++ b/mo/web/templates/org_generic_import.html @@ -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.