Project 'mj/mo-submit' was moved to 'mo-p/osmo'. Please update any links and bookmarks that may still have the old path.
        Select Git revision
      
  upgrade-20210819.sql
  org_score.py  11.29 KiB 
from decimal import Decimal
from flask import render_template, request
from flask.helpers import flash, url_for
from typing import List, Optional, Union
from flask_wtf.form import FlaskForm
import werkzeug.exceptions
from werkzeug.utils import redirect
import wtforms
import mo
import mo.db as db
from mo.rights import Right
from mo.score import Score
from mo.web import app
from mo.web.org_contest import get_context
from mo.web.table import Cell, CellInput, CellLink, Column, Row, Table, cell_pion_link
from mo.util_format import format_decimal, inflect_number
class OrderCell(Cell):
    place: int
    span: int
    continuation: bool
    def __init__(self, place: int, span: int = 1, continuation: bool = False):
        self.place = place
        self.span = span
        self.continuation = continuation
    def __str__(self) -> str:
        if self.span == 1:
            return f"{self.place}."
        else:
            return f"{self.place}.–{self.place + self.span - 1}."
    def to_html(self) -> str:
        if self.continuation:
            return ""  # covered by rowspan cell above this one
        elif self.span == 1:
            return f"<td>{self.__str__()}"
        else:
            return f"<td rowspan='{self.span}'>{self.__str__()}"
class SolTotalPointsCell(Cell):
    points: Decimal
    def __init__(self, points: Decimal):
        self.points = points
    def __str__(self) -> str:
        return format_decimal(self.points)
    def to_html(self) -> str:
        return '<td style="text-align: right"><b>' + str(self) + '</b>'
class SolPointsCell(Cell):
    contest_id: int
    user: db.User
    sol: Optional[db.Solution]
    link_to_paper: bool
    def __init__(self, contest_id: int, user: db.User, sol: Optional[db.Solution], link_to_paper: bool):
        self.contest_id = contest_id
        self.user = user
        self.sol = sol
        self.link_to_paper = link_to_paper
    def __str__(self) -> str:
        if not self.sol:
            return '–'
        elif self.sol.points is None:
            return '?'
        return format_decimal(self.sol.points)
    def to_html(self) -> str:
        td = "<td style='text-align: right'>"
        if not self.sol:
            return td+'–'
        elif self.sol.points is None:
            points = '<span class="unknown">?</span>'
        else:
            points = format_decimal(self.sol.points)
        if not self.link_to_paper:
            return f'<td>{points}'
        else:
            url = url_for('org_submit_list', ct_id=self.contest_id, user_id=self.user.user_id, task_id=self.sol.task_id)
            return td+f'<a href="{url}" title="Zobrazit detail řešení">{points}</a>'
class ScoreEditForm(FlaskForm):
    submit = wtforms.SubmitField("Uložit zjednoznačnění")
@app.route('/org/contest/r/<int:round_id>/score')
@app.route('/org/contest/r/<int:round_id>/score/edit', methods=('GET', 'POST'), endpoint="org_score_edit")
@app.route('/org/contest/r/<int:round_id>/h/<int:hier_id>/score')
@app.route('/org/contest/r/<int:round_id>/h/<int:hier_id>/score/edit', methods=('GET', 'POST'), endpoint="org_score_edit")
@app.route('/org/contest/c/<int:ct_id>/score')
@app.route('/org/contest/c/<int:ct_id>/score/edit', methods=('GET', 'POST'), endpoint="org_score_edit")
@app.route('/public/kolo/<int:round_id>/vysledky', endpoint="public_score")
@app.route('/public/kolo/<int:round_id>/h/<int:hier_id>/vysledky', endpoint="public_score")
@app.route('/public/soutez/<int:ct_id>/vysledky', endpoint="public_score")
def org_score(round_id: Optional[int] = None, hier_id: Optional[int] = None, ct_id: Optional[int] = None):
    public = request.endpoint == "public_score"
    ctx = get_context(round_id=round_id, hier_id=hier_id, ct_id=ct_id, public=public)
    contest = ctx.contest
    round = ctx.round
    format = request.args.get('format', "")
    sess = db.get_session()
    state = round.state
    if contest:
        state = contest.state
    if state != db.RoundState.closed and not ctx.rights.have_right(Right.view_contestants):
        raise werkzeug.exceptions.Forbidden()
    is_edit = request.endpoint == 'org_score_edit'
    if is_edit and not ctx.rights.have_right(Right.manage_contest):
        raise werkzeug.exceptions.Forbidden()
    score = Score(round.master, contest, ctx.hier_place)
    tasks = score.get_tasks()
    tasks.sort(key=mo.util.sortby_task_code)
    results = score.get_sorted_results()
    messages = score.get_messages()
    edit_form: Optional[ScoreEditForm] = None
    if is_edit:
        edit_form = ScoreEditForm()
        if edit_form.validate_on_submit():
            count = 0
            for result in results:
                try:
                    score_suborder = int(request.form.get(f"suborder_{result.user.user_id}"))
                except ValueError:
                    score_suborder = None
                if score_suborder != result.pion.score_suborder:
                    app.logger.info(f"Změněno zjednoznačnění u soutěžícího #{result.user.user_id} v soutěži #{result.pion.contest_id}: {result.pion.score_suborder}->{score_suborder}")
                    mo.util.log(
                        type=db.LogType.participant,
                        what=result.user.user_id,
                        details={
                            'action': 'pion-changed-suborder',
                            'contest_id': result.pion.contest_id,
                            'old_score_suborder': result.pion.score_suborder,
                            'new_score_suborder': score_suborder
                        },
                    )
                    result.pion.score_suborder = score_suborder
                    count += 1
            if count > 0:
                sess.commit()
                flash('Změněno zjednoznačnění u ' + inflect_number(count, 'soutěžícího', 'soutěžících', 'soutěžících'), 'success')
            else:
                flash('Žádné změny k uložení', 'info')
            return redirect(ctx.url_for('org_score'))
    # Pro tvorbu odkazů na správné contesty ve výsledkovkách dělených kol
    all_subcontests: List[db.Contest] = sess.query(db.Contest).filter(
        db.Contest.round_id.in_(
            sess.query(db.Round.round_id).filter_by(master_round_id=round.master_round_id)
        )
    ).all()
    subcontest_id_map = {}
    for subcontest in all_subcontests:
        subcontest_id_map[(subcontest.round_id, subcontest.master_contest_id)] = subcontest.contest_id
    # Construct columns
    is_export = (format != "")
    columns = []
    columns.append(Column(key='order', name='poradi', title='Pořadí'))
    if is_export:
        columns.append(Column(key='status', name='stav'))
    columns.append(Column(key='participant', name='ucastnik', title='Účastník'))
    if is_export and not public:
        columns.append(Column(key='email', name='email'))
    if not ct_id and round.level > 0:
        columns.append(Column(key='contest', name='oblast', title=round.get_level().name.title()))
    if is_export:
        columns.append(Column(key='pion_place', name='soutezni_misto'))
    columns.append(Column(key='school', name='skola', title='Škola'))
    columns.append(Column(key='grade', name='rocnik', title='Ročník'))
    if is_export and not public:
        columns.append(Column(key='birth_year', name='rok_narozeni'))
    for task in tasks:
        title = task.code
        if contest and contest.state != db.RoundState.closed:
            local_ct_id = subcontest_id_map[(task.round_id, contest.master_contest_id)]
            title = task.code
            if ctx.rights.can_view_submits():
                title = '<a href="{}">{}</a>'.format(
                    url_for('org_contest_task', ct_id=local_ct_id, task_id=task.task_id),
                    task.code
            )
            if ctx.rights.can_edit_points():
                title += ' <a href="{}" title="Editovat body" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-pencil"></span></a>'.format(
                    url_for('org_contest_task_edit', ct_id=local_ct_id, task_id=task.task_id),
                )
        columns.append(Column(key=f'task_{task.task_id}', name=task.code, title=title))
    columns.append(Column(key='total_points', name='celkove_body', title='Celkové body'))
    if is_edit:
        columns.append(Column(key='suborder', name='zjednoznacneni_poradi', title='Pořadí na sdíleném místě'))
    # columns.append(Column(key='order_key', name='order_key', title='Třídící klíč'))
    # Construct rows
    table_rows = []
    for result in results:
        user, pant, pion = result.user, result.pant, result.pion
        school = pant.school_place
        local_pion_ct_id = subcontest_id_map[(round.round_id, pion.contest_id)]
        order_cell = OrderCell(result.order.place, result.order.span, result.order.continuation)
        if result.winner:
            status = 'vítěz'
        elif result.successful:
            status = 'úspěšný'
        else:
            status = ""
        row = Row(keys={
            'order':        order_cell,
            'status':       status,
            'user':         user,
            'email':        user.email,
            'participant':  user.full_name() if state == db.RoundState.closed else cell_pion_link(user, local_pion_ct_id, user.full_name()),
            'contest':      CellLink(pion.contest.place.name or "–", url_for('public_score' if public else 'org_score', ct_id=pion.contest_id)),
            'pion_place':   pion.place.name,
            'school':       school.name or "–",
            'grade':        pant.grade,
            'total_points': SolTotalPointsCell(result.get_total_points()),
            'birth_year':   pant.birth_year,
            'order_key':    result._order_key,
            'suborder':     CellInput(f"suborder_{user.user_id}", pion.score_suborder, "number", attrs={"size": 6}),
        })
        sols = result.get_sols_map()
        for task in tasks:
            local_sol_ct_id = subcontest_id_map[(task.round_id, pion.contest_id)]
            row.keys[f'task_{task.task_id}'] = SolPointsCell(
                contest_id=local_sol_ct_id, user=user, sol=sols.get(task.task_id),
                link_to_paper=ctx.rights.can_view_submits() and state != db.RoundState.closed,
            )
        if result.winner:
            row.html_attr = {"class": "warning", "title": "Vítěz"}
        elif result.successful:
            row.html_attr = {"class": "success", "title": "Úspěšný řešitel"}
        table_rows.append(row)
    filename = f"vysledky_{round.year}-{round.category}-{round.level}"
    if contest:
        filename += f"_oblast_{contest.place.code or contest.place.place_id}"
    table = Table(
        columns=columns,
        rows=table_rows,
        filename=filename,
        show_downlink=not is_edit,
    )
    group_rounds = round.get_group_rounds(True)
    group_rounds.sort(key=lambda r: r.round_code())
    if format == "":
        return render_template(
            'org_score.html',
            ctx=ctx,
            contest=contest, round=round, tasks=tasks,
            table=table, messages=messages,
            need_exp = any(task.experiment for task in tasks),
            group_rounds=group_rounds,
            can_view_submits=ctx.rights.can_view_submits(),
            public=public,
            edit_form=edit_form,
            RoundScoreMode=db.RoundScoreMode,
        )
    else:
        return table.send_as(format)