Skip to content
Snippets Groups Projects
Select Git revision
  • b95de10676aa2a131fb7dc74cecf984cd4b1f824
  • devel default
  • master
  • fo
  • jirka/typing
  • fo-base
  • mj/submit-images
  • jk/issue-96
  • jk/issue-196
  • honza/add-contestant
  • honza/mr7
  • honza/mrf
  • honza/mrd
  • honza/mra
  • honza/mr6
  • honza/submit-images
  • honza/kolo-vs-soutez
  • jh-stress-test-wip
  • shorten-schools
19 results

org_score.py

Blame
  • 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)