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

Points: Obecný modul na zacházení s hodnocením řešení

Vše kolem gen_points přesouvám sem.
parent 5e5d5980
Branches
No related tags found
1 merge request!134Prázdné protokoly
from dataclasses import dataclass, field
from decimal import Decimal, InvalidOperation
from enum import auto
from typing import Optional, Tuple, List
import mo.db as db
import mo.rights
import mo.util
from mo.util import assert_not_none
import mo.util_format
@dataclass
class GenPoints:
have_solution: bool = True
is_empty: bool = False
points: Optional[Decimal] = None
error: Optional[str] = None
@staticmethod
def parse(
inp: Optional[str],
for_task: Optional[db.Task] = None,
for_round: Optional[db.Round] = None,
) -> 'GenPoints':
"""Zobecnění mo.util.parse_points(). Naparsuje generalizované body používané při editaci."""
inp = inp.upper() if inp is not None else None
gp = GenPoints()
if inp is None or inp == 'X':
gp.have_solution = False
return gp
if inp == "" or inp == '?':
pass
elif inp == 'P':
gp.is_empty = True
gp.points = Decimal(0)
else:
try:
gp.points = Decimal(inp.replace(',', '.'))
except InvalidOperation:
gp.error = f"Hodnota '{inp}' není číslo ani X nebo P"
return gp
gp.error = mo.util.check_points(gp.points, for_task, for_round)
return gp
class SolActionError(RuntimeError):
pass
@dataclass
class SolAction:
task: db.Task
user: db.User
sol: Optional[db.Solution]
gp: GenPoints
reason: str
rights: mo.rights.RoundRights
to_log: List[str]
allow_add_del: bool = True
did_add_or_del: bool = False
def add_or_del(self) -> Tuple[bool, bool]:
if (self.sol is not None) == self.gp.have_solution:
return False, False
sess = db.get_session()
self.did_add_or_del = True
if self.sol is None:
if not self.allow_add_del:
raise SolActionError('Tento soutěžící úlohu neodevzdal')
if not (self.rights.can_upload_solutions() or self.rights.can_upload_feedback()):
raise SolActionError('Nemáte právo na zakládání nových řešení, můžete jen upravovat body')
self.sol = db.Solution(user=self.user, task=self.task)
sess.add(self.sol)
self.to_log.append(f'Založeno řešení user=#{self.user.user_id} task=#{self.task.task_id}')
mo.util.log(
type=db.LogType.participant,
what=self.user.user_id,
details={'action': 'solution-created', 'task': self.task.task_id, 'reason': self.reason},
)
return True, False
else:
if not self.allow_add_del:
raise SolActionError('Tento soutěžící úlohu odevzdal')
if self.sol.final_submit is not None or self.sol.final_feedback is not None:
raise SolActionError('Nelze smazat řešení, ke kterému existují odevzdané soubory')
if not self.rights.can_upload_solutions():
raise SolActionError('Nemáte právo na mazání řešení')
self.to_log.append(f'Smazáno řešení user=#{self.user.user_id} task=#{self.task.task_id}')
mo.util.log(
type=db.LogType.participant,
what=self.user.user_id,
details={'action': 'solution-removed', 'task': self.task.task_id, 'reason': self.reason},
)
sess.delete(self.sol)
self.sol = None
return False, True
def set_points(self) -> bool:
sol = self.sol
gp = self.gp
if sol is None:
return False
if gp.is_empty == sol.is_empty and gp.points == sol.points:
return False
if not self.rights.can_edit_points():
raise SolActionError('Nemáte právo hodnotit řešení')
sol.points = gp.points
sol.is_empty = gp.is_empty
sess = db.get_session()
sess.add(db.PointsHistory(
task=self.task,
participant_id=self.user.user_id,
user=mo.util.current_log_user,
points_at=mo.now,
points=gp.points,
is_empty=gp.is_empty,
))
return True
def log_changes(self):
sess = db.get_session()
if self.sol and not self.did_add_or_del and sess.is_modified(self.sol):
changes = db.get_object_changes(self.sol)
mo.util.log(
type=db.LogType.participant,
what=self.user.user_id,
details={
'action': 'solution-edit',
'task': self.task.task_id,
'changes': changes,
'reason': self.reason,
},
)
self.to_log.append(f"Řešení user=#{self.user.user_id} task=#{self.task.task_id} modifikováno, změny: {changes}")
def format_sol_editable_points(s: Optional[db.Solution], none_is_qmark: bool = False) -> str:
if s is None:
return 'X'
elif s.is_empty:
return 'P'
elif s.points is None:
return '?' if none_is_qmark else ""
else:
return assert_not_none(mo.util_format.format_decimal(s.points))
......@@ -4,7 +4,6 @@ from dataclasses import dataclass
import datetime
import decimal
import dateutil.tz
from enum import auto
import locale
import logging
import os
......@@ -155,34 +154,6 @@ def parse_points(
return points, check_points(points, for_task, for_round)
class GPAction(db.MOEnum):
no_solution = auto()
no_points = auto()
is_empty = auto()
has_points = auto()
def parse_gen_points(
gen_points: Optional[str], for_task: Optional[db.Task] = None, for_round: Optional[db.Round] = None,
) -> Tuple[GPAction, Optional[decimal.Decimal], Optional[str]]:
"""Zobecnění parse_points(). Naparsuje generalizované body používané při editaci.
Vrátí typ hodnocení (GPAction), body (decimal.Decimal nebo None) a případný error."""
gen_points = gen_points.upper() if gen_points is not None else None
if gen_points is None or gen_points == 'X':
return GPAction.no_solution, None, None
elif gen_points == "" or gen_points == '?':
# Řešení má existovat, ale nemá přidělené body
return GPAction.no_points, None, None
elif gen_points == 'P':
return GPAction.is_empty, decimal.Decimal(0), None
else:
try:
points = decimal.Decimal(gen_points.replace(',', '.'))
except decimal.InvalidOperation:
return "", decimal.Decimal(0), f"Hodnota '{gen_points}' není číslo ani X"
return GPAction.points, points, check_points(points, for_task, for_round)
def check_points(points: decimal.Decimal, for_task: Optional[db.Task] = None, for_round: Optional[db.Round] = None) -> Optional[str]:
"""Zkontroluje body. Pokud je vše ok, tak vrátí None, jinak vrátí text chyby."""
if points < 0:
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment