Skip to content
Snippets Groups Projects
Commit 42168b20 authored by Martin Mareš's avatar Martin Mareš
Browse files

Další části webového rozhraní k soutěžím, zejména import účastníků

parent 25c412bc
No related branches found
No related tags found
No related merge requests found
......@@ -2,6 +2,9 @@
- dodělat reset hesla, posílat maily
- org: kontrola práv
- poznámka u uživatele
- budeme znát roky narození?
- uklidit v logování
- na import by bylo hezké mít testy
### Dořešit v importu škol ###
......
......@@ -26,7 +26,7 @@ def get_menu():
items = [
MenuItem(url_for('org_index'), "Org"),
MenuItem(url_for('org_place_root'), "Místa"),
MenuItem(url_for('org_contest_root'), "Kola"),
MenuItem(url_for('org_contest_root'), "Soutěž"),
MenuItem(url_for('org_users'), "Uživatelé"),
]
......
from flask import render_template, g, redirect, url_for, flash, request
from flask_wtf import FlaskForm
import flask_wtf.file
import locale
import os
import secrets
from sqlalchemy.orm import joinedload
from typing import List
import werkzeug.exceptions
import wtforms
import mo
import mo.csv
import mo.db as db
import mo.imports
import mo.rights
import mo.util
from mo.web import app
......@@ -271,12 +277,17 @@ def org_contest_root():
return render_template('org_contest_root.html', rounds=rounds, level_names=mo.db.place_level_names)
def get_round(id: int) -> db.Round:
round = db.get_session().query(db.Round).get(id)
if not round:
raise werkzeug.exceptions.NotFound()
return round
@app.route('/org/contest/r/<int:id>/')
def org_contest_round(id: int):
sess = db.get_session()
round = sess.query(db.Round).get(id)
if not round:
raise werkzeug.exceptions.NotFound()
round = get_round(id)
contests = (sess.query(db.Contest)
.filter_by(round=round)
......@@ -288,14 +299,82 @@ def org_contest_round(id: int):
return render_template('org_contest_round.html', round=round, contests=contests, level_names=mo.db.place_level_names)
@app.route('/org/contest/c/<int:id>')
def org_contest(id):
sess = db.get_session()
contest = (sess.query(db.Contest)
def get_contest(id: int) -> db.Contest:
contest = (db.get_session().query(db.Contest)
.options(joinedload(db.Contest.place),
joinedload(db.Contest.round))
.get(id))
if not contest:
raise werkzeug.exceptions.NotFound()
return contest
@app.route('/org/contest/c/<int:id>')
def org_contest(id):
contest = get_contest(id)
rr = mo.rights.Rights(g.user)
rr.get_for_contest(contest)
return render_template(
'org_contest.html',
contest=contest,
rights=sorted(rr.current_rights, key=lambda r: r. name),
can_manage=rr.have_right(mo.rights.Right.manage_contest),
)
class ContestImportForm(FlaskForm):
file = flask_wtf.file.FileField("Soubor", validators=[flask_wtf.file.FileRequired()])
submit = wtforms.SubmitField('Importovat')
@app.route('/org/contest/c/<int:id>/import', methods=('GET', 'POST'))
def org_contest_import(id):
contest = get_contest(id)
rr = mo.rights.Rights(g.user)
rr.get_for_contest(contest)
if not rr.have_right(mo.rights.Right.manage_contest):
raise werkzeug.exceptions.Forbidden()
form = ContestImportForm()
errs = []
if form.validate_on_submit():
tmp_name = secrets.token_hex(16) + '.csv'
tmp_path = os.path.join(app.instance_path, 'imports', tmp_name)
form.file.data.save(tmp_path)
app.logger.info('Import: Zpracovávám soubor %s pro contest_id=%s, uid=%s', tmp_name, contest.contest_id, g.user.user_id)
errs = mo.imports.import_contest(contest, tmp_path)
if not errs:
mo.util.log(
type=db.LogType.contest,
what=contest.contest_id,
details={'action': 'import'}
)
db.get_session().commit()
flash('Účastníci importováni', 'success')
return redirect(url_for('org_contest', id=contest.contest_id))
else:
flash('Došlo k chybě při importu (detaily níže)', 'danger')
return render_template(
'org_contest_import.html',
contest=contest,
form=form,
errs=errs,
)
@app.route('/org/contest/import/help.html')
def org_contest_import_help():
return render_template('org_contest_import_help.html')
return render_template('org_contest.html', contest=contest)
@app.route('/org/contest/import/sablona.csv')
def org_contest_import_template():
out = mo.imports.contest_template()
resp = app.make_response(out)
resp.content_type = 'text/csv; charset=utf=8'
return resp
......@@ -10,4 +10,19 @@
<tr><td>Oblast<td><a href='{{ url_for('org_place', id=contest.place.place_id) }}'>{{ contest.place.name }}</a>
</table>
{% if can_manage %}
<p><a href='{{ url_for('org_contest_import', id=contest.contest_id) }}'>Importovat účastníky</a>
{% endif %}
<h3>Vaše práva k této soutěži</h3>
{% if rights %}
<ul>
{% for r in rights %}
<li>{{ r.name }}
{% endfor %}
</ul>
{% else %}
<p>Žádná.
{% endif %}
{% endblock %}
{% extends "base.html" %}
{% import "bootstrap/wtf.html" as wtf %}
{% block body %}
<h2>Soutěž {{ contest.round.round_code() }}: {{ contest.place.name }}</h2>
<a href='{{ url_for('org_contest', id=contest.contest_id) }}'>Zpět na soutěž</a>
{% if errs %}
<h3>Chyby při importu</h3>
<pre>
{% for e in errs %}
{{ e }}
{% endfor %}
</pre>
{% endif %}
<h3>Import účastníků</h3>
<p>Účastníky soutěže můžete importovat ve <a href='{{ url_for('org_contest_import_help') }}'>formátu CSV</a>
podle <a href='{{ url_for('org_contest_import_template') }}'>šablony</a>.
{{ wtf.quick_form(form, form_type='horizontal') }}
{% endblock %}
{% extends "base.html" %}
{% block body %}
<h2>Import účastníků</h2>
<p>Připravte soubor ve formátu CSV (comma-separated values) v kódování znaků UTF-8.
Na každém řádku je jeden účastník, sloupce jsou oddělené čárkami. Pokud
potřebujete v nějakém sloupci použít čárku, uzavřete hodnotu sloupce do
uvozovek (obyčejných, ne českých). Běžné tabulkové kalkulátory (třeba Excel
nebo LibreOffice Calc) umí v tomto formátu ukládat.
<p>První řádek tabulky obsahuje názvy sloupců. Ty najdete v šabloně, kterou si
můžete stáhnout ze stránky importu. Názvy neměňte.
<p>Definovány jsou tyto sloupce:
<table class=data>
<tr><th>Název<th>Obsah
<tr><td>email<td>e-mailová adresa
<tr><td>krestni<td>Křestní jméno
<tr><td>prijmeni<td>Příjmení
<tr><td>kod_skoly<td>Kód školy (viz katalog škol na tomto webu)
<tr><td>rocnik<td>Navštěvovaný ročník (třída). Pro základní školy je to číslo od 1 do 9, pro <i>k</i>-tý ročník <i>r</i>-leté střední školy má formát <i>k</i>/<i>r</i>.
<tr><td>rok_naroz<td>Rok narození
</table>
{% endblock %}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment