Update race result standings handling
All checks were successful
Build Formula10 Docker Image / build-docker (push) Successful in 14s
All checks were successful
Build Formula10 Docker Image / build-docker (push) Successful in 14s
This commit is contained in:
@ -154,40 +154,51 @@ def find_or_create_race_result(race_name: str) -> RaceResult:
|
||||
|
||||
def update_race_result(race_name: str, pxx_driver_names_list: List[str], dnf_driver_names_list: List[str], excluded_driver_names_list: List[str]) -> Response:
|
||||
# Use strings as keys, as these dicts will be serialized to json
|
||||
pxx_driver_names: Dict[str, str] = {str(position + 1): driver for position, driver in enumerate(pxx_driver_names_list)}
|
||||
dnf_driver_names: Dict[str, str] = {str(position + 1): driver for position, driver in enumerate(pxx_driver_names_list) if driver in dnf_driver_names_list}
|
||||
# The pxx_driver_names_list contains all 20 drivers, so use that one to determine positions for dnf_driver_names and excluded_driver_names
|
||||
pxx_driver_names: Dict[str, str] = {
|
||||
str(position + 1): driver for position, driver in enumerate(pxx_driver_names_list)
|
||||
if driver not in dnf_driver_names_list and driver not in excluded_driver_names_list
|
||||
}
|
||||
dnf_driver_names: Dict[str, str] = {
|
||||
str(position + 1): driver for position, driver in enumerate(pxx_driver_names_list)
|
||||
if driver in dnf_driver_names_list and driver not in excluded_driver_names_list
|
||||
}
|
||||
excluded_driver_names: Dict[str, str] = {
|
||||
str(position + 1): driver for position, driver in enumerate(pxx_driver_names_list)
|
||||
if driver in excluded_driver_names_list
|
||||
}
|
||||
|
||||
# This one is only used for validation
|
||||
excluded_driver_names: Dict[str, str] = {str(position + 1): driver for position, driver in enumerate(pxx_driver_names_list) if driver in excluded_driver_names_list}
|
||||
best_excluded_driver_position: int = sorted(list(map(int, list(excluded_driver_names.keys()))))[0] if len(excluded_driver_names) > 0 else 21
|
||||
worst_dnf_driver_position: int = sorted(list(map(int, list(dnf_driver_names.keys()))), reverse=True)[0] if len(dnf_driver_names) > 0 else best_excluded_driver_position - 1
|
||||
|
||||
if not positions_are_contiguous(list(dnf_driver_names.keys())):
|
||||
# All dictionaries should be disjunct and result in a complete list from P1-P20 if merged
|
||||
union: Dict[str, str] = pxx_driver_names | dnf_driver_names | excluded_driver_names
|
||||
if len(union) != 20 or not positions_are_contiguous(list(union.keys())) or "1" not in union or "20" not in union:
|
||||
return redirect(f"/result/{quote(race_name)}")
|
||||
|
||||
if not positions_are_contiguous(list(excluded_driver_names.keys())):
|
||||
return redirect(f"/result/{quote(race_name)}")
|
||||
# dnf_drivers have positions above excluded_drivers
|
||||
if len(excluded_driver_names) > 0:
|
||||
best_excluded_driver_position: int = min(map(int, excluded_driver_names.keys()))
|
||||
for position in dnf_driver_names.keys():
|
||||
if int(position) >= best_excluded_driver_position:
|
||||
return redirect(f"/result/{quote(race_name)}")
|
||||
|
||||
# DNF + exclude is exclusive
|
||||
for driver_name in dnf_driver_names_list:
|
||||
if driver_name in excluded_driver_names_list:
|
||||
return redirect(f"/result/{quote(race_name)}")
|
||||
# pxx_drivers have positions above dnf_drivers
|
||||
if len(dnf_driver_names) > 0:
|
||||
best_dnf_driver_position: int = min(map(int, dnf_driver_names.keys()))
|
||||
for position in pxx_driver_names.keys():
|
||||
if int(position) >= best_dnf_driver_position:
|
||||
return redirect(f"/result/{quote(race_name)}")
|
||||
|
||||
# Excluded drivers occupy the last positions, if any are excluded
|
||||
if len(excluded_driver_names_list) > 0 and not "20" in excluded_driver_names:
|
||||
return redirect(f"/result/{quote(race_name)}")
|
||||
|
||||
# DNF drivers occupy the positions between finishing and excluded drivers
|
||||
if len(dnf_driver_names_list) > 0 and worst_dnf_driver_position + 1 != best_excluded_driver_position:
|
||||
print(dnf_driver_names, worst_dnf_driver_position)
|
||||
print(excluded_driver_names, best_excluded_driver_position)
|
||||
return redirect(f"/result/{quote(race_name)}")
|
||||
# pxx_drivers have positions above excluded_drivers
|
||||
if len(excluded_driver_names) > 0:
|
||||
best_excluded_driver_position: int = min(map(int, excluded_driver_names.keys()))
|
||||
for position in pxx_driver_names.keys():
|
||||
if int(position) >= best_excluded_driver_position:
|
||||
return redirect(f"/result/{quote(race_name)}")
|
||||
|
||||
|
||||
race_result: RaceResult = find_or_create_race_result(race_name)
|
||||
race_result.pxx_driver_names = pxx_driver_names
|
||||
race_result.dnf_driver_names = dnf_driver_names if len(dnf_driver_names_list) > 0 else {"20": "NONE"}
|
||||
race_result.excluded_driver_names = excluded_driver_names_list
|
||||
race_result.dnf_driver_names = dnf_driver_names
|
||||
race_result.excluded_driver_names = excluded_driver_names
|
||||
|
||||
db.session.commit()
|
||||
|
||||
|
@ -19,9 +19,7 @@ db.init_app(app)
|
||||
# TODO
|
||||
# General
|
||||
|
||||
# - A lot of validation (esp. in the model), each input should be checked (e.g. DNF, excluded order)...
|
||||
# - NONE driver handling: If PXX = NONE is selected, excluded drivers have to be taken into account
|
||||
# - Show place when entering race result
|
||||
# - Show place when entering race result (would require updating the drag'n'drop code...)
|
||||
|
||||
# - Show cards of previous race results, like with season guesses?
|
||||
# - Make the season card grid left-aligned? So e.g. 2 cards are not spread over the whole screen with large gaps?
|
||||
|
57
model.py
57
model.py
@ -161,18 +161,18 @@ class RaceResult(db.Model):
|
||||
self.dnf_driver_names_json = json.dumps(new_dnf_driver_names)
|
||||
|
||||
@property
|
||||
def excluded_driver_names(self) -> List[str]:
|
||||
def excluded_driver_names(self) -> Dict[str, str]:
|
||||
return json.loads(self.excluded_driver_names_json)
|
||||
|
||||
@excluded_driver_names.setter
|
||||
def excluded_driver_names(self, new_excluded_driver_names: List[str]):
|
||||
def excluded_driver_names(self, new_excluded_driver_names: Dict[str, str]):
|
||||
self.excluded_driver_names_json = json.dumps(new_excluded_driver_names)
|
||||
|
||||
# Relationships
|
||||
race: Mapped["Race"] = relationship("Race", foreign_keys=[race_name])
|
||||
_pxx_drivers: Dict[str, Driver] | None = None
|
||||
_dnf_drivers: Dict[str, Driver] | None = None
|
||||
_excluded_drivers: List[Driver] | None = None
|
||||
_excluded_drivers: Dict[str, Driver] | None = None
|
||||
|
||||
@property
|
||||
def pxx_drivers(self) -> Dict[str, Driver]:
|
||||
@ -187,16 +187,6 @@ class RaceResult(db.Model):
|
||||
|
||||
return self._pxx_drivers
|
||||
|
||||
@property
|
||||
def pxx_drivers_values(self) -> List[Driver]:
|
||||
drivers: List[Driver] = list()
|
||||
|
||||
# I don't know what order dict.values() etc. will return...
|
||||
for position in range(1, 21):
|
||||
drivers.append(self.pxx_drivers[str(position)])
|
||||
|
||||
return drivers
|
||||
|
||||
@property
|
||||
def dnf_drivers(self) -> Dict[str, Driver]:
|
||||
if self._dnf_drivers is None:
|
||||
@ -211,28 +201,55 @@ class RaceResult(db.Model):
|
||||
return self._dnf_drivers
|
||||
|
||||
@property
|
||||
def excluded_drivers(self) -> List[Driver]:
|
||||
def excluded_drivers(self) -> Dict[str, Driver]:
|
||||
if self._excluded_drivers is None:
|
||||
self._excluded_drivers = list()
|
||||
for driver_name in self.excluded_driver_names:
|
||||
self._excluded_drivers = dict()
|
||||
for position, driver_name in self.excluded_driver_names.items():
|
||||
driver: Driver | None = db.session.query(Driver).filter_by(name=driver_name).first()
|
||||
if driver is None:
|
||||
raise Exception(f"Error: Couldn't find driver with id {driver_name}")
|
||||
|
||||
self._excluded_drivers.append(driver)
|
||||
self._excluded_drivers[position] = driver
|
||||
|
||||
return self._excluded_drivers
|
||||
|
||||
def pxx(self, offset: int = 0) -> Driver:
|
||||
def pxx(self, offset: int = 0) -> Driver | None:
|
||||
pxx_num: str = str(self.race.pxx + offset)
|
||||
|
||||
if pxx_num not in self.pxx_drivers:
|
||||
raise Exception(f"Error: Position {self.race.pxx} not contained in race result")
|
||||
none_driver: Driver | None = db.session.query(Driver).filter_by(name="NONE").first()
|
||||
if none_driver is None:
|
||||
raise Exception("NONE-driver not found in database")
|
||||
|
||||
return none_driver
|
||||
|
||||
return self.pxx_drivers[pxx_num]
|
||||
|
||||
@property
|
||||
def dnf(self) -> Driver:
|
||||
return sorted(self.dnf_drivers.items(), reverse=True)[0][1] # SortedList[FirstElement][KeyPairValue]
|
||||
none_driver: Driver | None = db.session.query(Driver).filter_by(name="NONE").first()
|
||||
if none_driver is None:
|
||||
raise Exception("NONE-driver not found in database")
|
||||
|
||||
return sorted(self.dnf_drivers.items(), reverse=True)[0][1] if len(self.dnf_drivers) > 0 else none_driver
|
||||
|
||||
def single_position(self, position: str) -> Driver:
|
||||
if position in self.pxx_drivers:
|
||||
return self.pxx_drivers[position]
|
||||
|
||||
if position in self.dnf_drivers:
|
||||
return self.dnf_drivers[position]
|
||||
|
||||
if position in self.excluded_drivers:
|
||||
return self.excluded_drivers[position]
|
||||
|
||||
raise Exception(f"Driver for position {position} not found in pxx/dnf/excluded")
|
||||
|
||||
@property
|
||||
def all_positions(self) -> List[Driver]:
|
||||
return [
|
||||
self.single_position(str(position)) for position in range(1, 21)
|
||||
]
|
||||
|
||||
|
||||
class RaceGuess(db.Model):
|
||||
|
@ -98,23 +98,23 @@
|
||||
|
||||
{#@formatter:off#}
|
||||
{% macro pxx_guess_colorization(driver_abbr='', result=none) -%}
|
||||
{% if driver_abbr == result.pxx(-3).abbr %}fw-bold
|
||||
{% elif driver_abbr == result.pxx(-2).abbr %}text-danger fw-bold
|
||||
{% elif driver_abbr == result.pxx(-1).abbr %}text-warning fw-bold
|
||||
{% elif driver_abbr == result.pxx(0).abbr %}text-success fw-bold
|
||||
{% elif driver_abbr == result.pxx(1).abbr %}text-warning fw-bold
|
||||
{% elif driver_abbr == result.pxx(2).abbr %}text-danger fw-bold
|
||||
{% elif driver_abbr == result.pxx(3).abbr %}fw-bold{% endif %}
|
||||
{% if (driver_abbr == result.pxx(-3).abbr) and (driver_abbr != "NON") %}fw-bold
|
||||
{% elif (driver_abbr == result.pxx(-2).abbr) and (driver_abbr != "NON") %}text-danger fw-bold
|
||||
{% elif (driver_abbr == result.pxx(-1).abbr) and (driver_abbr != "NON") %}text-warning fw-bold
|
||||
{% elif (driver_abbr == result.pxx(0).abbr) %}text-success fw-bold
|
||||
{% elif (driver_abbr == result.pxx(1).abbr) and (driver_abbr != "NON") %}text-warning fw-bold
|
||||
{% elif (driver_abbr == result.pxx(2).abbr) and (driver_abbr != "NON") %}text-danger fw-bold
|
||||
{% elif (driver_abbr == result.pxx(3).abbr) and (driver_abbr != "NON") %}fw-bold{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro pxx_points_tooltip_text(driver_abbr='', result=none) -%}
|
||||
{% if driver_abbr == result.pxx(-3).abbr %}1 Point
|
||||
{% elif driver_abbr == result.pxx(-2).abbr %}3 Points
|
||||
{% elif driver_abbr == result.pxx(-1).abbr %}6 Points
|
||||
{% elif driver_abbr == result.pxx(0).abbr %}10 Points
|
||||
{% elif driver_abbr == result.pxx(1).abbr %}6 Points
|
||||
{% elif driver_abbr == result.pxx(2).abbr %}3 Points
|
||||
{% elif driver_abbr == result.pxx(3).abbr %}1 Point
|
||||
{% if (driver_abbr == result.pxx(-3).abbr) and (driver_abbr != "NON") %}1 Point
|
||||
{% elif (driver_abbr == result.pxx(-2).abbr) and (driver_abbr != "NON") %}3 Points
|
||||
{% elif (driver_abbr == result.pxx(-1).abbr) and (driver_abbr != "NON") %}6 Points
|
||||
{% elif (driver_abbr == result.pxx(0).abbr) %}10 Points
|
||||
{% elif (driver_abbr == result.pxx(1).abbr) and (driver_abbr != "NON") %}6 Points
|
||||
{% elif (driver_abbr == result.pxx(2).abbr) and (driver_abbr != "NON") %}3 Points
|
||||
{% elif (driver_abbr == result.pxx(3).abbr) and (driver_abbr != "NON") %}1 Point
|
||||
{% else %}0 Points{% endif %}
|
||||
{%- endmacro %}
|
||||
|
||||
|
@ -65,17 +65,14 @@
|
||||
{% endif %}
|
||||
</h5>
|
||||
|
||||
<form action="/result-enter/
|
||||
|
||||
|
||||
|
||||
|
||||
{%- if active_result is not none %}{{ active_result.race.name }}{% else %}{{ current_race.name }}{% endif %}"
|
||||
{# @formatter:off #}
|
||||
<form action="/result-enter/{%- if active_result is not none %}{{ active_result.race.name }}{% else %}{{ current_race.name }}{% endif %}"
|
||||
method="post">
|
||||
{# @formatter:on #}
|
||||
<ul id="columns" class="list-group list-group-flush">
|
||||
|
||||
{% if active_result is not none %}
|
||||
{% set drivers = active_result.pxx_drivers_values %}
|
||||
{% set drivers = active_result.all_positions %}
|
||||
{% else %}
|
||||
{% set drivers = model.all_drivers_except_none() %}
|
||||
{% endif %}
|
||||
@ -98,9 +95,10 @@
|
||||
<div class="form-check form-check-reverse d-inline-block mx-2">
|
||||
<input type="checkbox" class="form-check-input" value="{{ driver.name }}"
|
||||
id="exclude-{{ driver.name }}" name="exclude-drivers"
|
||||
{% if (active_result is not none) and (driver in active_result.excluded_drivers) %}checked{% endif %}>
|
||||
{% if (active_result is not none) and (driver in active_result.excluded_drivers.values()) %}checked{% endif %}>
|
||||
<label for="exclude-{{ driver.name }}"
|
||||
class="form-check-label text-muted" data-bs-toggle="tooltip" title="Exclude driver from points calculation">Exclude</label>
|
||||
class="form-check-label text-muted" data-bs-toggle="tooltip"
|
||||
title="Exclude driver from points calculation">Exclude</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
Reference in New Issue
Block a user