diff --git a/sportorg/gui/dialogs/entry_mass_edit.py b/sportorg/gui/dialogs/entry_mass_edit.py index a988e865..6738a0b6 100644 --- a/sportorg/gui/dialogs/entry_mass_edit.py +++ b/sportorg/gui/dialogs/entry_mass_edit.py @@ -201,7 +201,7 @@ def accept(self, *args, **kwargs): cur_person.qual = change_qual if self.bib_checkbox.isChecked(): - cur_person.change_bib(change_bib) + cur_person.set_bib(change_bib) if self.world_code_checkbox.isChecked(): cur_person.world_code = change_world_code @@ -210,7 +210,7 @@ def accept(self, *args, **kwargs): cur_person.national_code = change_national_code if self.card_checkbox.isChecked(): - cur_person.change_card(change_card_number) + cur_person.set_card_number(change_card_number) if self.start_time_checkbox.isChecked(): cur_person.start_time = change_start_time diff --git a/sportorg/gui/dialogs/merge_results.py b/sportorg/gui/dialogs/merge_results.py index 5a6505b3..b697fc87 100644 --- a/sportorg/gui/dialogs/merge_results.py +++ b/sportorg/gui/dialogs/merge_results.py @@ -93,9 +93,9 @@ def apply_changes_impl(self): result_list.sort(key=lambda c: c.finish_time) final_result = ResultSportident() - final_result.change_bib(cur_bib) + final_result.bib = cur_bib final_result.person = result_list[0].person - final_result.change_card(result_list[0].card_number) + final_result.card_number = result_list[0].card_number cur_cp = first_cp for i in result_list: diff --git a/sportorg/gui/dialogs/person_edit.py b/sportorg/gui/dialogs/person_edit.py index c65e0a65..bfcd6bb7 100644 --- a/sportorg/gui/dialogs/person_edit.py +++ b/sportorg/gui/dialogs/person_edit.py @@ -14,7 +14,14 @@ from sportorg.gui.global_access import GlobalAccess from sportorg.language import translate from sportorg.models.constant import get_names, get_race_groups, get_race_teams -from sportorg.models.memory import Limit, Organization, Qualification, find, race +from sportorg.models.memory import ( + Limit, + Organization, + Person, + Qualification, + find, + race, +) from sportorg.models.result.result_calculation import ResultCalculation from sportorg.modules.configs.configs import Config from sportorg.modules.live.live import live_client @@ -25,11 +32,13 @@ class PersonEditDialog(BaseDialog): GROUP_NAME = '' ORGANIZATION_NAME = '' - def __init__(self, person, is_new=False): + def __init__(self, person: Person, is_new=False): super().__init__(GlobalAccess().get_main_window()) self.current_object = person self.is_new = is_new self.is_item_valid = {} + self.bib = person.bib + self.card_number = person.card_number time_format = 'hh:mm:ss' if race().get_setting('time_accuracy', 0): @@ -91,7 +100,7 @@ def __init__(self, person, is_new=False): ), NumberField( title=translate('Bib'), - object=person, + object=self, key='bib', id='bib', minimum=0, @@ -123,7 +132,7 @@ def __init__(self, person, is_new=False): ), NumberField( title=translate('Punch card #'), - object=person, + object=self, key='card_number', id='card_number', minimum=0, @@ -264,6 +273,10 @@ def on_card_number_changed(self): def apply(self): person = self.current_object + if self.bib != person.bib: + person.set_bib(self.bib) + if self.card_number != person.card_number: + person.set_card_number(self.card_number) if self.is_new: race().add_person(person) diff --git a/sportorg/gui/dialogs/result_edit.py b/sportorg/gui/dialogs/result_edit.py index c172ce59..f05a512a 100644 --- a/sportorg/gui/dialogs/result_edit.py +++ b/sportorg/gui/dialogs/result_edit.py @@ -24,7 +24,7 @@ from sportorg.gui.utils.custom_controls import AdvComboBox, AdvSpinBox, AdvTimeEdit from sportorg.language import translate from sportorg.models.constant import StatusComments -from sportorg.models.memory import Limit, ResultStatus, Split, race +from sportorg.models.memory import Limit, Result, ResultStatus, Split, race from sportorg.models.result.result_calculation import ResultCalculation from sportorg.models.result.result_checker import ResultChecker, ResultCheckerException from sportorg.models.result.split_calculation import GroupSplits @@ -36,7 +36,7 @@ class ResultEditDialog(QDialog): def __init__(self, result, is_new=False): super().__init__(GlobalAccess().get_main_window()) - self.current_object = result + self.current_object: Result = result self.is_new = is_new self.time_format = 'hh:mm:ss' @@ -260,14 +260,14 @@ def apply_changes_impl(self): if new_bib == 0: if result.person and result.is_punch(): if result.person.card_number == result.card_number: - result.person.change_card(0) + result.person.set_card_number(0) result.person = None elif cur_bib != new_bib: new_person = race().find_person_by_bib(new_bib) if new_person: if result.person: if result.is_punch(): - result.person.change_card(0) + result.person.set_card_number(0) result.person = new_person if result.is_punch(): race().person_card_number(result.person, result.card_number) diff --git a/sportorg/gui/dialogs/settings.py b/sportorg/gui/dialogs/settings.py index c74fc4c1..f3d69aea 100644 --- a/sportorg/gui/dialogs/settings.py +++ b/sportorg/gui/dialogs/settings.py @@ -24,6 +24,7 @@ get_current_race_index, move_down_race, move_up_race, + race, races, set_current_race_index, ) diff --git a/sportorg/gui/dialogs/text_io.py b/sportorg/gui/dialogs/text_io.py index cef2fa13..ec63b681 100644 --- a/sportorg/gui/dialogs/text_io.py +++ b/sportorg/gui/dialogs/text_io.py @@ -384,7 +384,7 @@ def set_property(person, key, value, **options): elif key == translate('Bib'): if value.isdigit(): new_bib = int(value) - person.change_bib(new_bib) + person.set_bib(new_bib) elif key == translate('Comment'): person.comment = value elif key == translate('IOF id'): diff --git a/sportorg/gui/menu/actions.py b/sportorg/gui/menu/actions.py index ef0c8a70..a8703a94 100644 --- a/sportorg/gui/menu/actions.py +++ b/sportorg/gui/menu/actions.py @@ -619,6 +619,7 @@ class RecheckingAction(Action, metaclass=ActionFactory): def execute(self): ResultChecker.check_all() ResultCalculation(race()).process_results() + race().rebuild_indexes() self.app.refresh() diff --git a/sportorg/gui/tabs/memory_model.py b/sportorg/gui/tabs/memory_model.py index 729fa39b..c650657f 100644 --- a/sportorg/gui/tabs/memory_model.py +++ b/sportorg/gui/tabs/memory_model.py @@ -279,8 +279,8 @@ def duplicate(self, position): person = self.race.persons[position] new_person = copy(person) new_person.id = uuid.uuid4() - new_person.bib = 0 - new_person.card_number = 0 + new_person.set_bib(0) + new_person.set_card_number(0) self.race.persons.insert(position, new_person) def get_values_from_object(self, obj): diff --git a/sportorg/models/memory.py b/sportorg/models/memory.py index 0a5524ff..c534699a 100644 --- a/sportorg/models/memory.py +++ b/sportorg/models/memory.py @@ -1282,8 +1282,8 @@ def __init__(self): self.name = '' self.surname = '' - self.card_number = 0 - self.bib = 0 + self._card_number = 0 + self._bib = 0 self.birth_date: Optional[date] = None self.organization: Optional[Organization] = None @@ -1372,8 +1372,8 @@ def to_dict(self): def update_data(self, data): self.name = str(data['name']) self.surname = str(data['surname']) - self.change_card(data['card_number']) - self.change_bib(int(data['bib'])) + self.set_card_number(int(data['card_number'])) + self.set_bib(int(data['bib'])) self.contact = [] if data['world_code']: self.world_code = str(data['world_code']) @@ -1393,25 +1393,70 @@ def update_data(self, data): elif 'year' in data and data['year']: # back compatibility with v 1.0.0 self.set_year(int(data['year'])) - def change_bib(self, new_bib: int): - if self.bib == new_bib: + @property + def bib(self): + return self._bib + + @bib.setter + def bib(self, new_bib: int): + raise NotImplementedError('Please, use set_bib()') + + def set_bib(self, new_bib: int) -> None: + if self._bib == new_bib: return + self._index_bib(new_bib) + self._bib = new_bib + def _index_bib(self, new_bib: int) -> None: r = race() - if self.bib in r.person_index_bib: + if self._bib != new_bib and self._bib in r.person_index_bib: r.person_index_bib.pop(self.bib) - self.bib = new_bib - r.index_person(self) + if new_bib > 0 and new_bib in r.person_index_bib: + other_person = r.person_index_bib[new_bib] + if not other_person is self: + logging.info( + 'Duplicate bib %s (do nothing): %s | %s', + new_bib, + self, + other_person, + ) + r.person_index_bib[new_bib] = self + + def index_bib(self) -> None: + self._index_bib(self.bib) + + @property + def card_number(self): + return self._card_number + + @card_number.setter + def card_number(self, new_card: int): + raise NotImplementedError('Please, use set_card_number()') - def change_card(self, new_card: int): - if self.card_number == new_card: + def set_card_number(self, new_card: int): + if self._card_number == new_card: return + self._index_card(new_card) + self._card_number = new_card + + def _index_card(self, new_card): r = race() - if self.card_number in r.person_index_card: - r.person_index_card.pop(self.card_number) - self.card_number = new_card - r.index_person(self) + if self._card_number != new_card and self._card_number in r.person_index_card: + r.person_index_card.pop(self._card_number) + if new_card > 0 and new_card in r.person_index_card: + other_person = r.person_index_card[new_card] + if not other_person is self: + logging.info( + 'Duplicate card %s (do nothing): %s | %s', + new_card, + self, + other_person, + ) + r.person_index_card[new_card] = self + + def index_card(self): + self._index_card(self._card_number) class RaceData(Model): @@ -1714,27 +1759,49 @@ def get_setting(self, setting, nvl_value=None): def get_days(self, date_=None): return self.data.get_days(date_) - def person_card_number(self, person, number=0): - person.change_card(number) + def person_card_number(self, person: Person, number=0): + person.set_card_number(number) for p in self.persons: if p.card_number == number and p != person: - p.card_number = 0 + p.set_card_number(0) p.is_rented_card = False return p + def rebuild_indexes(self): + self.person_index_bib = {} + self.person_index_card = {} + for person in self.persons: + person.index_bib() + person.index_card() + def delete_persons(self, indexes): indexes = sorted(indexes, reverse=True) persons = [] for i in indexes: person = self.persons[i] persons.append(person) - for result in self.results: - if result.person is person: - result.person = None - result.bib = person.bib + self.remove_person_from_indexes(person) del self.persons[i] return persons + def remove_person_from_indexes(self, person: Person): + for result in self.results: + if result.person is person: + result.person = None + result.bib = person.bib + if ( + person.bib + and person.bib in self.person_index_bib + and self.person_index_bib[person.bib] is person + ): + del self.person_index_bib[person.bib] + if ( + person.card_number + and person.card_number in self.person_index_card + and self.person_index_card[person.card_number] is person + ): + del self.person_index_card[person.card_number] + def delete_results(self, indexes): indexes = sorted(indexes, reverse=True) results = [] @@ -1789,12 +1856,12 @@ def find_person_result(self, person: Person) -> Optional[Result]: return i return None - def find_person_by_bib(self, bib: int): + def find_person_by_bib(self, bib: int) -> Person: if bib in self.person_index_bib: return self.person_index_bib[bib] return None - def find_person_by_card(self, card: int): + def find_person_by_card(self, card: int) -> Person: if card in self.person_index_card: return self.person_index_card[card] return None @@ -2295,7 +2362,7 @@ def is_out_of_competition(self): def set_bib(self): if self.person: - self.person.change_bib(self.get_bib()) + self.person.set_bib(self.get_bib()) def set_person(self, person): self.person = person diff --git a/sportorg/models/start/relay.py b/sportorg/models/start/relay.py index 14e55e9f..12f86ed9 100644 --- a/sportorg/models/start/relay.py +++ b/sportorg/models/start/relay.py @@ -48,7 +48,7 @@ def get_leg_count(): def set_next_relay_number_to_person(person): - person.change_bib(get_next_relay_number_setting()) + person.set_bib(get_next_relay_number_setting()) set_next_relay_number(get_next_relay_number(person.bib)) diff --git a/sportorg/models/start/start_preparation.py b/sportorg/models/start/start_preparation.py index ed7378fa..46c5f0b0 100644 --- a/sportorg/models/start/start_preparation.py +++ b/sportorg/models/start/start_preparation.py @@ -573,10 +573,10 @@ def set_numbers_by_minute(self, persons, first_number=1): if current_person.start_time: start_time = current_person.start_time delta = (start_time - first_start).to_minute() - current_person.change_bib(int(min_num + delta)) + current_person.set_bib(int(min_num + delta)) max_assigned_num = max(max_assigned_num, current_person.bib) else: - current_person.change_bib(0) + current_person.set_bib(0) if max_assigned_num > first_number: return max_assigned_num + 1 @@ -586,7 +586,7 @@ def set_numbers_by_order(self, persons, first_number=1, interval=1): cur_number = first_number if persons and len(persons) > 0: for current_person in persons: - current_person.change_bib(cur_number) + current_person.set_bib(cur_number) cur_number += interval return cur_number @@ -821,14 +821,14 @@ def copy_bib_to_card_number(): obj = race() for person in obj.persons: if person.bib: - person.change_card(person.bib) + person.set_card_number(person.bib) def copy_card_number_to_bib(): obj = race() for person in obj.persons: if person.card_number: - person.change_bib(person.card_number) + person.set_bib(person.card_number) def clone_relay_legs(min_bib, max_bib, increment): @@ -841,6 +841,6 @@ def clone_relay_legs(min_bib, max_bib, increment): if person.bib and min_bib <= person.bib <= max_bib: new_person = copy(person) new_person.id = uuid.uuid4() - new_person.change_bib(person.bib + increment) - new_person.card_number = 0 + new_person.set_bib(person.bib + increment) + new_person.set_card_number(0) obj.persons.append(new_person) diff --git a/sportorg/modules/backup/json.py b/sportorg/modules/backup/json.py index 194371ef..d74bc952 100644 --- a/sportorg/modules/backup/json.py +++ b/sportorg/modules/backup/json.py @@ -40,10 +40,6 @@ def load(file): set_current_race_index(current_race) obj = race() - # while parsing of data index is created in old object, available as race() - obj.person_index_card = tmp_obj.person_index_card - obj.person_index_bib = tmp_obj.person_index_bib - ResultChecker.check_all() ResultCalculation(obj).process_results() RaceSplits(obj).generate() @@ -66,6 +62,8 @@ def get_races_from_file(file): obj = Race() obj.id = uuid.UUID(str(race_dict['id'])) obj.update_data(race_dict) + # while parsing of data index is created in old object, available as race() + move_indexes_to_new_race(obj) event.append(obj) current_race = 0 if 'current_race' in data: @@ -73,6 +71,13 @@ def get_races_from_file(file): return event, current_race +def move_indexes_to_new_race(obj): + obj.person_index_bib = race().person_index_bib + race().person_index_bib = {} + obj.person_index_card = race().person_index_card + race().person_index_card = {} + + def race_migrate(data): for person in data['persons']: if 'sportident_card' in person: diff --git a/sportorg/modules/iof/iof_xml.py b/sportorg/modules/iof/iof_xml.py index 68002088..d8008842 100644 --- a/sportorg/modules/iof/iof_xml.py +++ b/sportorg/modules/iof/iof_xml.py @@ -156,14 +156,14 @@ def create_person(person_entry): if 'race_numbers' in person_entry and len(person_entry['race_numbers']): person.comment = 'C:' + ''.join(person_entry['race_numbers']) if 'control_card' in person_entry and person_entry['control_card']: - person.change_card(int(person_entry['control_card'])) + person.set_card_number(int(person_entry['control_card'])) if 'bib' in person_entry['person'] and person_entry['person']['bib']: - person.change_bib(int(person_entry['person']['bib'])) + person.set_bib(int(person_entry['person']['bib'])) elif ( 'bib' in person_entry['person']['extensions'] and person_entry['person']['extensions']['bib'] ): - person.change_bib(int(person_entry['person']['extensions']['bib'])) + person.set_bib(int(person_entry['person']['extensions']['bib'])) if ( 'qual' in person_entry['person']['extensions'] and person_entry['person']['extensions']['qual'] @@ -199,11 +199,10 @@ def import_from_entry_list(entries) -> None: person.card_number, ) ) - person.card_number = 0 + person.set_card_number(0) if len(persons_dupl_names): logging.info('{}'.format(translate('Duplicate names'))) for person in sorted(persons_dupl_names, key=lambda x: x.full_name): - person.card_number = 0 logging.info( '{} {} {} {}'.format( person.full_name, @@ -260,9 +259,9 @@ def import_from_result_list(results) -> None: if card > 0: new_result.card_number = card if person.card_number == 0: - person.change_card(new_result.card_number) + person.set_card_number(new_result.card_number) - person.change_bib(int(bib)) + person.set_bib(int(bib)) person.start_time = start new_result.person = person diff --git a/sportorg/modules/recovery/recovery_orgeo_finish_csv.py b/sportorg/modules/recovery/recovery_orgeo_finish_csv.py index 04ad1c73..c444abf2 100644 --- a/sportorg/modules/recovery/recovery_orgeo_finish_csv.py +++ b/sportorg/modules/recovery/recovery_orgeo_finish_csv.py @@ -62,7 +62,7 @@ def recovery(file_name: str, race: Race) -> None: person.name = name[spl_pos + 1 :] else: person.name = name - person.change_bib(int(bib)) + person.set_bib(int(bib)) team_name = tokens[POS_TEAM] team = race.find_team(team_name) diff --git a/sportorg/modules/sportident/result_generation.py b/sportorg/modules/sportident/result_generation.py index 5990f5bf..b2012a91 100644 --- a/sportorg/modules/sportident/result_generation.py +++ b/sportorg/modules/sportident/result_generation.py @@ -218,14 +218,14 @@ def _add_result(self): def _create_person(self): new_person = Person() - new_person.change_bib(self._get_max_bib() + 1) + new_person.set_bib(self._get_max_bib() + 1) existing_person = race().find_person_by_card(self._result.card_number) if existing_person: new_person_copy = deepcopy(existing_person) new_person_copy.id = new_person.id - new_person_copy.change_bib(new_person.bib) + new_person_copy.set_bib(new_person.bib) new_person = new_person_copy - new_person.card_number = 0 + new_person.set_card_number(0) else: new_person.surname = translate('Competitor') + ' #' + str(new_person.bib) diff --git a/sportorg/modules/winorient/wdb.py b/sportorg/modules/winorient/wdb.py index 1f4605b2..46ddde73 100644 --- a/sportorg/modules/winorient/wdb.py +++ b/sportorg/modules/winorient/wdb.py @@ -120,7 +120,7 @@ def create_objects(self): index_of_first_space = str(man.name.strip()).find(' ') if index_of_first_space > 0: new_person.name = man.name.strip()[index_of_first_space + 1 :].strip() - new_person.change_bib(man.number) + new_person.set_bib(man.number) if man.qualification: if man.qualification == 10: man.qualification = Qualification.MSMK.value # Convert ZMS to MSMK diff --git a/sportorg/modules/winorient/winorient.py b/sportorg/modules/winorient/winorient.py index 700b15a3..fedfc572 100644 --- a/sportorg/modules/winorient/winorient.py +++ b/sportorg/modules/winorient/winorient.py @@ -39,9 +39,9 @@ def import_csv(source): person = memory.Person() person.name = person_dict['name'] person.surname = person_dict['surname'] - person.change_bib(person_dict['bib']) + person.set_bib(int(person_dict['bib'])) person.set_year(person_dict['year']) - person.change_card(int(person_dict['sportident_card'])) + person.set_card_number(int(person_dict['sportident_card'])) person.group = memory.find(obj.groups, name=person_dict['group_name']) person.organization = person_org person.qual = Qualification(qual_id) @@ -73,7 +73,7 @@ def import_csv(source): person.card_number, ) ) - person.card_number = 0 + person.set_card_number(0) if len(persons_dupl_names): logging.info('{}'.format(translate('Duplicate names'))) for person in sorted(persons_dupl_names, key=lambda x: x.full_name):