from django.db import models from . import SideStoreModel, Tournament, FederalMatchCategory import uuid class Round(SideStoreModel): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=True) tournament = models.ForeignKey(Tournament, on_delete=models.SET_NULL, related_name='rounds', null=True) index = models.IntegerField(default=0) parent = models.ForeignKey('self', blank=True, null=True, on_delete=models.SET_NULL, related_name='children') format = models.IntegerField(default=FederalMatchCategory.NINE_GAMES, choices=FederalMatchCategory.choices, null=True, blank=True) start_date = models.DateTimeField(null=True, blank=True) group_stage_loser_bracket = models.BooleanField(default=False) loser_bracket_mode = models.IntegerField(default=0) def delete_dependencies(self): for round in self.children.all(): round.delete_dependencies() round.delete() for match in self.matches.all(): match.delete_dependencies() match.delete() def __str__(self): if self.parent: return f"LB: {self.name()}" else: return self.name() def save(self, *args, **kwargs): self.store_id = str(self.get_tournament_id()) super().save(*args, **kwargs) def get_tournament_id(self): if self.tournament: return self.tournament.id else: return None def name(self): if self.parent and self.parent.parent is None: return f"Classement {self.parent.name()}" elif self.parent and self.parent.parent is not None: return f"{self.parent.name()}" elif self.group_stage_loser_bracket is True: return "Classement de poule" else: if self.index == 0: return "Finale" elif self.index == 1: return "Demis" elif self.index == 2: return "Quarts" else: squared = 2 ** self.index return f"{squared}ème" def ranking_matches(self, hide_empty_matches): matches = [] for child in self.children.all(): child_matches = child.matches.all() if hide_empty_matches: child_matches = [m for m in child_matches if m.should_appear()] else: child_matches = [m for m in child_matches if m.disabled is False] matches.extend(child_matches) matches.extend(child.ranking_matches(hide_empty_matches)) return matches def all_matches(self, hide_empty_matches): matches = [] matches.extend(self.get_matches_recursive(hide_empty_matches)) return matches def get_matches_recursive(self, hide_empty_matches): matches = list(self.matches.all()) # Retrieve matches associated with the current round if hide_empty_matches: matches = [m for m in matches if m.should_appear()] else: matches = [m for m in matches if m.disabled is False] matches.sort(key=lambda m: m.index) # Recursively fetch matches from child rounds for child_round in self.children.all(): matches.extend(child_round.get_matches_recursive(hide_empty_matches)) return matches def get_depth(self): depth = 0 current_round = self while current_round.parent: depth += 1 current_round = current_round.parent return depth def root_round(self): if self.parent is None: return self else: return self.parent.root_round() def all_matches_are_over(self): for match in self.matches.all(): if match.end_date is None and match.disabled is False: return False return True def prepare_match_group(self, next_round, parent_round, loser_final, double_butterfly_mode, secondHalf): matches = self.matches.filter(disabled=False).order_by('index') if len(matches) == 0: return None if next_round: next_round_matches = next_round.matches.filter(disabled=False).order_by('index') else: next_round_matches = [] if len(matches) < len(next_round_matches): all_matches = self.matches.order_by('index') filtered_matches = [] # Process matches in pairs i = 0 while i < len(all_matches): # Get the current match and its pair (if available) current_match = all_matches[i] pair_match = all_matches[i+1] if i+1 < len(all_matches) else None # Only filter out the pair if both matches are disabled if current_match.disabled and pair_match and pair_match.disabled: # Skip one of the matches in the pair if next_round_matches.filter(index=current_match.index // 2).exists(): filtered_matches.append(current_match) filtered_matches.append(pair_match) pass else: # Keep the current match if current_match.disabled == False: filtered_matches.append(current_match) # If there's a pair match, keep it too if pair_match and pair_match.disabled == False: filtered_matches.append(pair_match) # Move to the next pair i += 2 # Replace the matches list with our filtered list matches = filtered_matches if matches: if len(matches) > 1 and double_butterfly_mode: midpoint = int(len(matches) / 2) first_half_matches = matches[:midpoint] if secondHalf: first_half_matches = matches[midpoint:] else: first_half_matches = list(matches) # Convert QuerySet to a list if self.index == 0 and loser_final: loser_match = loser_final.matches.first() if loser_match: first_half_matches.append(loser_match) if first_half_matches: name = self.name() if parent_round and first_half_matches[0].name is not None: name = first_half_matches[0].name match_group = self.tournament.create_match_group( name=name, matches=first_half_matches, round_id=self.id ) return match_group return None