Skip to content
Snippets Groups Projects

Tabulky řešení

Merged Jiří Setnička requested to merge jirka/solutions-table into devel
All threads resolved!

Files

+ 163
81
@@ -5,6 +5,7 @@ import flask_wtf.file
@@ -5,6 +5,7 @@ import flask_wtf.file
import locale
import locale
import os
import os
import secrets
import secrets
 
from sqlalchemy import func
from sqlalchemy.orm import joinedload
from sqlalchemy.orm import joinedload
from sqlalchemy.orm.query import Query
from sqlalchemy.orm.query import Query
from typing import List, Tuple, Optional, Sequence, Dict
from typing import List, Tuple, Optional, Sequence, Dict
@@ -197,15 +198,69 @@ def get_contest_rr(id: int, right_needed: Optional[Right] = None) -> Tuple[db.Co
@@ -197,15 +198,69 @@ def get_contest_rr(id: int, right_needed: Optional[Right] = None) -> Tuple[db.Co
return contest, rr
return contest, rr
 
def get_contest_site_rr(id: int, site_id: Optional[int], right_needed: Optional[Right] = None) -> Tuple[db.Contest, db.Place, Rights]:
 
if site_id is None:
 
contest, rr = get_contest_rr(id, right_needed)
 
return contest, None, rr
 
 
contest = get_contest(id)
 
site = db.get_session().query(db.Place).get(site_id)
 
if not site:
 
raise werkzeug.exceptions.NotFound()
 
 
rr = Rights(g.user)
 
rr.get_for_contest_site(contest, site)
 
 
if not (right_needed is None or rr.have_right(right_needed)):
 
raise werkzeug.exceptions.Forbidden()
 
 
return contest, site, rr
 
 
@app.route('/org/contest/c/<int:id>')
@app.route('/org/contest/c/<int:id>')
def org_contest(id: int):
@app.route('/org/contest/c/<int:id>/site/<int:site_id>/')
contest, rr = get_contest_rr(id, None)
def org_contest(id: int, site_id: Optional[int] = None):
 
sess = db.get_session()
 
contest, site, rr = get_contest_site_rr(id, site_id, None)
 
 
sol_counts_q = (
 
sess.query(db.Solution.task_id, func.count(db.Solution.task_id))
 
.filter(db.Solution.task_id.in_(
 
sess.query(db.Task.task_id).filter_by(round=contest.round)
 
))
 
)
 
if site:
 
sol_counts_q = sol_counts_q.filter(db.Solution.user_id.in_(
 
sess.query(db.Participation.user_id).filter_by(place=site)
 
))
 
sol_counts = {}
 
for task_id, count in sol_counts_q.group_by(db.Solution.task_id).all():
 
sol_counts[task_id] = count
 
 
tasks = sess.query(db.Task).filter_by(round=contest.round).all()
 
tasks.sort(key=lambda t: t.code)
 
for task in tasks:
 
task.sol_count = sol_counts[task.task_id] if task.task_id in sol_counts else 0
 
 
 
count = None
 
places_counts = None
 
if site_id:
 
count = sess.query(db.Participation).filter_by(place_id=site_id).count()
 
else:
 
places_counts = (
 
sess.query(db.Place, func.count('*'))
 
.select_from(db.Participation).join(db.Place)
 
.group_by(db.Place)
 
.filter(db.Participation.contest_id == id).all()
 
)
return render_template(
return render_template(
'org_contest.html',
'org_contest.html',
contest=contest,
contest=contest, site=site,
rights=sorted(rr.current_rights, key=lambda r: r. name),
rights=sorted(rr.current_rights, key=lambda r: r. name),
can_manage=rr.have_right(Right.manage_contest),
can_manage=rr.have_right(Right.manage_contest),
 
tasks=tasks, places_counts=places_counts, count=count,
)
)
@@ -250,8 +305,9 @@ def org_contest_import_template():
@@ -250,8 +305,9 @@ def org_contest_import_template():
@app.route('/org/contest/c/<int:id>/ucastnici', methods=('GET', 'POST'))
@app.route('/org/contest/c/<int:id>/ucastnici', methods=('GET', 'POST'))
def org_contest_list(id: int):
@app.route('/org/contest/c/<int:id>/site/<int:site_id>/ucastnici', methods=('GET', 'POST'))
contest, rr = get_contest_rr(id)
def org_contest_list(id: int, site_id: Optional[int] = None):
 
contest, site, rr = get_contest_site_rr(id, site_id)
can_edit = rr.have_right(Right.manage_contest)
can_edit = rr.have_right(Right.manage_contest)
format = request.args.get('format', "")
format = request.args.get('format', "")
@@ -261,7 +317,7 @@ def org_contest_list(id: int):
@@ -261,7 +317,7 @@ def org_contest_list(id: int):
round=contest.round, contest=contest,
round=contest.round, contest=contest,
school=db.get_place_by_code(filter.school.data),
school=db.get_place_by_code(filter.school.data),
# contest_place=db.get_place_by_code(filter.contest_place.data),
# contest_place=db.get_place_by_code(filter.contest_place.data),
participation_place=db.get_place_by_code(filter.participation_place.data),
participation_place=site if site else db.get_place_by_code(filter.participation_place.data),
participation_state=None if filter.participation_state.data == '*' else filter.participation_state.data
participation_state=None if filter.participation_state.data == '*' else filter.participation_state.data
)
)
@@ -279,7 +335,7 @@ def org_contest_list(id: int):
@@ -279,7 +335,7 @@ def org_contest_list(id: int):
table = make_contestant_table(query, add_checkbox=can_edit)
table = make_contestant_table(query, add_checkbox=can_edit)
return render_template(
return render_template(
'org_contest_list.html',
'org_contest_list.html',
contest=contest,
contest=contest, site=site,
table=table,
table=table,
filter=filter, count=count, action_form=action_form,
filter=filter, count=count, action_form=action_form,
)
)
@@ -371,126 +427,111 @@ def make_contestant_table(query: Query, add_checkbox: bool = False, add_contest_
@@ -371,126 +427,111 @@ def make_contestant_table(query: Query, add_checkbox: bool = False, add_contest_
@app.route('/org/contest/c/<int:id>/reseni')
@app.route('/org/contest/c/<int:id>/reseni')
def org_contest_solutions(id: int):
@app.route('/org/contest/c/<int:id>/site/<int:site_id>/reseni')
# FIXME: Práva?
def org_contest_solutions(id: int, site_id: Optional[int] = None):
# FIXME: Hlavička stránky podle Jirkova předělání
contest, site, rr = get_contest_site_rr(id, site_id, Right.manage_contest)
contest, rr = get_contest_rr(id, Right.manage_contest)
format = request.args.get('format', "")
sess = db.get_session()
sess = db.get_session()
 
pions_subq = sess.query(db.Participation.user_id).filter_by(contest=contest)
 
if site:
 
pions_subq = pions_subq.filter_by(place=site)
 
pions_subq = pions_subq.subquery()
pions = (sess.query(db.Participation)
pions = (sess.query(db.Participation)
.filter_by(contest=contest)
.filter(db.Participation.user_id.in_(pions_subq))
.options(joinedload(db.Participation.user))
.options(joinedload(db.Participation.user))
.all())
.all())
 
tasks_subq = sess.query(db.Task.task_id).filter_by(round=contest.round).subquery()
tasks = (sess.query(db.Task)
tasks = (sess.query(db.Task)
.filter_by(round=contest.round)
.filter_by(round=contest.round)
.order_by(db.Task.code)
.order_by(db.Task.code)
.all())
.all())
pions_subq = (sess.query(db.Participation.user_id)
sols = sess.query(db.Solution).filter(
.filter_by(contest=contest)
db.Solution.user_id.in_(pions_subq),
.subquery())
db.Solution.task_id.in_(tasks_subq)
).options(
sols = (sess.query(db.Solution)
joinedload(db.Solution.last_submit_obj),
.filter(db.Solution.user_id.in_(pions_subq))
joinedload(db.Solution.last_feedback_obj)
.options(joinedload(db.Solution.last_submit_obj),
).all()
joinedload(db.Solution.last_feedback_obj))
.all())
print('XXX pions:', pions) # FIXME
print('XXX tasks:', tasks)
print('XXX sols:', sols)
cols = [ Column(key='name', name='jmeno', title='Jméno') ]
task_sols: Dict[int, Dict[int, db.Solution]] = {}
task_sols: Dict[int, Dict[int, db.Solution]] = {}
for t in tasks:
for t in tasks:
cols.append(Column(key=f't-{t.task_id}', name=t.code))
task_sols[t.task_id] = {}
task_sols[t.task_id] = {}
for s in sols:
for s in sols:
task_sols[s.task_id][s.user_id] = s
task_sols[s.task_id][s.user_id] = s
rows = []
def paper_link(paper: db.Paper) -> str:
for pion in pions:
return url_for('org_submit_paper',
user = pion.user
contest_id=contest.contest_id,
r = {
paper_id=paper.paper_id,
'name': user.full_name(),
site_id=site_id,
}
filename=mo.web.util.task_paper_filename(paper))
for t in tasks:
s = task_sols[t.task_id].get(user.user_id, None)
if s is not None:
cell = '*'
else:
cell = ""
r[f't-{t.task_id}'] = cell
rows.append(r)
print('XXX cols:', cols) # FIXME
print('XXX rows:', rows)
table = Table(
return render_template(
columns=cols,
'org_contest_solutions.html',
rows=rows,
contest=contest, site=site,
filename='reseni',
pions=pions, tasks=tasks, tasks_sols=task_sols,
 
paper_link=paper_link,
)
)
if format == "":
return render_template(
'org_contest_solutions.html',
contest=contest,
table=table,
)
else:
return table.send_as(format)
@dataclass
@dataclass
class SolutionContext:
class SolutionContext:
contest: db.Contest
contest: db.Contest
round: db.Round
round: db.Round
pion: db.Participation
pion: db.Participation
user: db.User
user: Optional[db.User]
task: db.Task
task: db.Task
 
site: Optional[db.Place]
allow_view: bool
allow_view: bool
allow_upload_solutions: bool
allow_upload_solutions: bool
allow_upload_feedback: bool
allow_upload_feedback: bool
def get_solution_context(contest_id: int, user_id: int, task_id: int, site_id: Optional[int]) -> SolutionContext:
def get_solution_context(contest_id: int, user_id: Optional[int], task_id: int, site_id: Optional[int]) -> SolutionContext:
sess = db.get_session()
sess = db.get_session()
# Nejprve zjistíme, zda existuje soutěž
# Nejprve zjistíme, zda existuje soutěž
contest = get_contest(contest_id)
contest = get_contest(contest_id)
round = contest.round
round = contest.round
# Zkontrolujeme, zda se účastník opravdu účastní soutěže
pion = (sess.query(db.Participation)
.filter_by(user_id=user_id, contest_id=contest_id)
.options(joinedload(db.Participation.place),
joinedload(db.Participation.user))
.one_or_none())
if not pion:
raise werkzeug.exceptions.NotFound()
# A zda soutěží na zadaném soutěžním místě, je-li určeno
if site_id is not None and site_id != pion.site_id:
raise werkzeug.exceptions.NotFound()
# Najdeme úlohu a ověříme, že je součástí soutěže
# Najdeme úlohu a ověříme, že je součástí soutěže
task = sess.query(db.Task).get(task_id)
task = sess.query(db.Task).get(task_id)
if not task or task.round != round:
if not task or task.round != round:
raise werkzeug.exceptions.NotFound()
raise werkzeug.exceptions.NotFound()
# Pokud je uvedeno soutěžní místo, hledáme práva k němu, jinak k soutěži
site = None
if site_id is not None:
user = None
site = pion.place
if user_id is not None:
 
# Zkontrolujeme, zda se účastník opravdu účastní soutěže
 
pion = (sess.query(db.Participation)
 
.filter_by(user_id=user_id, contest_id=contest_id)
 
.options(joinedload(db.Participation.place),
 
joinedload(db.Participation.user))
 
.one_or_none())
 
if not pion:
 
raise werkzeug.exceptions.NotFound()
 
user = pion.user
 
 
# A zda soutěží na zadaném soutěžním místě, je-li určeno
 
if site_id is not None and site_id != pion.place_id:
 
raise werkzeug.exceptions.NotFound()
 
 
# Pokud je uvedeno soutěžní místo, hledáme práva k němu, jinak k soutěži
 
if site_id is not None:
 
site = pion.place
else:
else:
site = contest.place
pion = None
 
 
if site_id is not None:
 
site = sess.query(db.Place).get(site_id)
 
if not site:
 
raise werkzeug.exceptions.NotFound()
 
rr = Rights(g.user)
rr = Rights(g.user)
rr.get_for_contest_site(contest, site)
rr.get_for_contest_site(contest, site or contest.place)
# Kdo má právo na jaké operace
# Kdo má právo na jaké operace
allow_upload_solutions = (rr.have_right(Right.manage_contest)
allow_upload_solutions = (rr.have_right(Right.manage_contest)
@@ -507,8 +548,9 @@ def get_solution_context(contest_id: int, user_id: int, task_id: int, site_id: O
@@ -507,8 +548,9 @@ def get_solution_context(contest_id: int, user_id: int, task_id: int, site_id: O
contest=contest,
contest=contest,
round=round,
round=round,
pion=pion,
pion=pion,
user=pion.user,
user=user,
task=task,
task=task,
 
site=site,
allow_view=allow_view,
allow_view=allow_view,
allow_upload_solutions=allow_upload_solutions,
allow_upload_solutions=allow_upload_solutions,
allow_upload_feedback=allow_upload_feedback,
allow_upload_feedback=allow_upload_feedback,
@@ -672,3 +714,43 @@ def org_proctor_import_template():
@@ -672,3 +714,43 @@ def org_proctor_import_template():
@app.route('/doc/import-dozor')
@app.route('/doc/import-dozor')
def org_proctor_import_help():
def org_proctor_import_help():
return render_template('doc_import_proctor.html')
return render_template('doc_import_proctor.html')
 
 
 
def get_solutions_query(
 
task: db.Task,
 
for_contest: Optional[db.Contest] = None,
 
for_site: Optional[db.Place] = None) -> Query:
 
sess = db.get_session()
 
pions_filter = sess.query(db.Participation.user_id)
 
if for_contest:
 
pions_filter = pions_filter.filter_by(contest=for_contest)
 
if for_site:
 
pions_filter = pions_filter.filter_by(place=for_site)
 
 
solutions = sess.query(db.Solution).filter_by(task=task)
 
if for_contest or for_site:
 
solutions = solutions.filter(db.Solution.user_id.in_(pions_filter))
 
 
return solutions
 
 
 
@app.route('/org/contest/c/<int:contest_id>/task/<int:task_id>/')
 
@app.route('/org/contest/c/<int:contest_id>/site/<int:site_id>/task/<int:task_id>/')
 
def org_contest_task_submits(contest_id: int, task_id: int, site_id: Optional[int] = None):
 
sc = get_solution_context(contest_id, None, task_id, site_id)
 
 
q = get_solutions_query(sc.task, for_contest=sc.contest, for_site=sc.site)
 
solutions: List[db.Solution] = q.all()
 
 
def paper_link(paper: db.Paper) -> str:
 
return url_for('org_submit_paper',
 
contest_id=sc.contest.contest_id,
 
paper_id=paper.paper_id,
 
site_id=site_id,
 
filename=mo.web.util.task_paper_filename(paper))
 
 
return render_template(
 
"org_contest_task.html",
 
sc=sc, solutions=solutions,
 
paper_link=paper_link,
 
)
Loading