730 lines
23 KiB
Python
730 lines
23 KiB
Python
"""Models for the mainapp."""
|
|
|
|
from django.db import models
|
|
from django.utils import timezone
|
|
from django.core.exceptions import ValidationError
|
|
|
|
|
|
# region Anglers & Groups
|
|
|
|
class Angler(models.Model):
|
|
"""A participant of one or more events.
|
|
|
|
Attributes:
|
|
id (int): Automatically incrementing identifier number.
|
|
name (str): Full name or label describing the angler.
|
|
redact (bool): Determines if the `name` attr should be redacted.
|
|
"""
|
|
|
|
id = models.AutoField(primary_key=True)
|
|
name = models.CharField(max_length=128)
|
|
redact = models.BooleanField()
|
|
|
|
class Meta:
|
|
verbose_name = "angler"
|
|
verbose_name_plural = "anglers"
|
|
|
|
def __str__(self):
|
|
return self.name
|
|
|
|
|
|
class AnglerGroup(models.Model):
|
|
"""A group of one or more anglers.
|
|
|
|
Attributes:
|
|
id (int): Automatically incrementing identifier number.
|
|
name (str): A human-readable label to identify this group.
|
|
anglers (list of `Angler`): The members of this group.
|
|
type (int): An enum-like value representing the type of group.
|
|
"""
|
|
|
|
id = models.AutoField(primary_key=True)
|
|
name = models.CharField(max_length=128)
|
|
anglers = models.ForeignKey(to=Angler, on_delete=models.CASCADE)
|
|
|
|
TYPES = (
|
|
(0, "team type 1"), # TODO: Change these placeholders for actual values.
|
|
(1, "team type 2"),
|
|
(2, "team type 3")
|
|
)
|
|
|
|
type = models.PositiveSmallIntegerField(choices=TYPES)
|
|
|
|
class Meta:
|
|
verbose_name = "angler group"
|
|
verbose_name_plural = "angler groups"
|
|
|
|
def __str__(self):
|
|
return f"{self.name} ({self.anglers.count} anglers)"
|
|
|
|
|
|
# region Venues & Waters
|
|
|
|
|
|
class VenueAddress(models.Model):
|
|
id = models.AutoField(primary_key=True)
|
|
street_number = models.IntegerField()
|
|
street_address = models.CharField(max_length=256)
|
|
town = models.CharField(max_length=256)
|
|
provence = models.CharField(max_length=256)
|
|
post_code = models.CharField(max_length=32)
|
|
satnav_post_code = models.CharField(max_length=32)
|
|
country = models.CharField(max_length=128)
|
|
latitude = models.DecimalField(max_digits=9, decimal_places=6, null=True, blank=True)
|
|
longitude = models.DecimalField(max_digits=9, decimal_places=6, null=True, blank=True)
|
|
|
|
class Meta:
|
|
verbose_name = "venue address"
|
|
verbose_name_plural = "venue addresses"
|
|
|
|
def __str__(self):
|
|
return f"{self.street_address}, {self.town} ({self.country})"
|
|
|
|
|
|
class VenueContacts(models.Model):
|
|
id = models.AutoField(primary_key=True)
|
|
phone_number = models.CharField(max_length=64)
|
|
email_address = models.EmailField()
|
|
website_url = models.URLField()
|
|
facebook_url = models.URLField()
|
|
twitter_url = models.URLField()
|
|
instagram_url = models.URLField()
|
|
|
|
class Meta:
|
|
verbose_name = "venue contacts"
|
|
verbose_name_plural = "venue contacts"
|
|
|
|
def __str__(self):
|
|
return self.email_address
|
|
|
|
|
|
class Venue(models.Model):
|
|
id = models.AutoField(primary_key=True)
|
|
name = models.CharField(max_length=128)
|
|
description = models.CharField(max_length=384)
|
|
extra_notes = models.CharField(max_length=1028)
|
|
|
|
created_at = models.DateTimeField(default=timezone.now, editable=False)
|
|
updated_at = models.DateTimeField(default=timezone.now, editable=False)
|
|
|
|
profile_picture = models.ImageField()
|
|
banner_picture = models.ImageField()
|
|
|
|
class Types:
|
|
FISHERY = 0
|
|
CLUB = 1
|
|
PRIVATE = 2
|
|
|
|
type = models.PositiveSmallIntegerField(
|
|
choices=(
|
|
(Types.FISHERY, "Fishery"),
|
|
(Types.CLUB, "Club"),
|
|
(Types.PRIVATE, "Private")
|
|
)
|
|
)
|
|
|
|
address = models.ForeignKey(to=VenueAddress, on_delete=models.CASCADE)
|
|
contacts = models.ForeignKey(to=VenueContacts, on_delete=models.CASCADE)
|
|
|
|
class Meta:
|
|
verbose_name = "venue"
|
|
verbose_name_plural = "venues"
|
|
|
|
def __str__(self):
|
|
return self.name
|
|
|
|
def save(self, *args, **kwargs):
|
|
self.updated_at = timezone.now()
|
|
return super().save(*args, **kwargs)
|
|
|
|
@property
|
|
def waters(self):
|
|
"""Returns all waters belonging to this venue."""
|
|
return Waters.objects.filter(venue=self)
|
|
|
|
|
|
class Waters(models.Model):
|
|
"""A body of water belonging to a particular venue.
|
|
|
|
Attributes:
|
|
id (int): Automatically incrementing identifier number.
|
|
name (str): A human-readable label to identify this item.
|
|
pegs_from (int): The lowest peg number available.
|
|
pegs_to (int): The highest peg number available.
|
|
map (file): An image map showing the body of water.
|
|
venue (`Venue`): The real world venue where this waters can be found.
|
|
type (int): The type of waters represented (canal, river, etc...)
|
|
fish_type (int): The fish categories that can be found in this water.
|
|
"""
|
|
|
|
id = models.AutoField(primary_key=True)
|
|
name = models.CharField(max_length=128)
|
|
pegs_from = models.IntegerField()
|
|
pegs_to = models.IntegerField()
|
|
map = models.ImageField()
|
|
venue = models.ForeignKey(to=Venue, on_delete=models.CASCADE)
|
|
|
|
class Types:
|
|
COMMERCIAL = 0
|
|
NATURAL_STILL = 1
|
|
CANAL = 2
|
|
RIVER = 3
|
|
LOCH = 4
|
|
|
|
type = models.PositiveSmallIntegerField(
|
|
choices=(
|
|
(Types.COMMERCIAL, "Commercial Water"),
|
|
(Types.NATURAL_STILL, "Natural Still Water"),
|
|
(Types.CANAL, "Canal"),
|
|
(Types.RIVER, "River"),
|
|
(Types.LOCH, "Loch")
|
|
)
|
|
)
|
|
|
|
class FishTypes:
|
|
COARSE = 0
|
|
SPECIMEN_CARP = 1
|
|
GAME = 2
|
|
PREDATOR = 3
|
|
|
|
fish_types = models.PositiveSmallIntegerField(
|
|
choices=(
|
|
(FishTypes.COARSE, "Coarse"),
|
|
(FishTypes.SPECIMEN_CARP, "Specimen Carp"),
|
|
(FishTypes.GAME, "Game"),
|
|
(FishTypes.PREDATOR, "Predator")
|
|
)
|
|
)
|
|
|
|
class Meta:
|
|
verbose_name = "waters"
|
|
verbose_name_plural = "waters"
|
|
|
|
def __str__(self):
|
|
return self.name
|
|
|
|
|
|
# region Leagues & Matches
|
|
|
|
|
|
class Match(models.Model):
|
|
"""Represents an a fishing event/competition.
|
|
|
|
Attributes:
|
|
id (int): Automatically incrementing identifier number.
|
|
name (str): A human-readable label to identify this item.
|
|
description (str): A detailed description of the event.
|
|
venue (`Venue`): Where this match is hosted.
|
|
waters (`Waters`): Which waters will be used in this match, can only use waters from the assigned venue.
|
|
meeting_point (str): TODO: what is this for?
|
|
use_metric (bool): Should the metric system be used in measurements, imperial is used if `False`.
|
|
allow_in_tournaments (bool): Should this match be allowed in tournaments? (TODO: is this correct?)
|
|
start_datetime (datetime): When the match will begin.
|
|
end_datetime (datetime): When the match will finish.
|
|
draw_datetime (datetime): When the draw is made (during the match).
|
|
type (int): TODO: match types?
|
|
competitor_type (int): TODO what are these?
|
|
"""
|
|
|
|
id = models.AutoField(primary_key=True)
|
|
name = models.CharField(max_length=128)
|
|
description = models.CharField(max_length=1024)
|
|
|
|
venue = models.ForeignKey(to=Venue, on_delete=models.CASCADE)
|
|
waters = models.ForeignKey(to=Waters, on_delete=models.CASCADE) # can only select waters from the matching venue
|
|
|
|
meeting_point = models.CharField(max_length=1024)
|
|
use_metric = models.BooleanField()
|
|
allow_in_tournaments = models.BooleanField()
|
|
|
|
start_datetime = models.DateTimeField()
|
|
end_datetime = models.DateTimeField()
|
|
draw_datetime = models.DateTimeField()
|
|
|
|
TYPES = (
|
|
(0, "choice 1"),
|
|
(1, "choice 2"),
|
|
(2, "choice 3")
|
|
)
|
|
type = models.PositiveSmallIntegerField(choices=TYPES)
|
|
|
|
COMPETITOR_TYPES = (
|
|
(0, "choice 1"),
|
|
(1, "choice 2"),
|
|
(2, "choice 3")
|
|
)
|
|
competitor_type = models.PositiveSmallIntegerField(choices=COMPETITOR_TYPES)
|
|
|
|
class Meta:
|
|
verbose_name = "match"
|
|
verbose_name_plural = "matches"
|
|
|
|
def __str__(self):
|
|
return self.name
|
|
|
|
|
|
class LeagueRule(models.Model):
|
|
"""Rules for leagues.
|
|
|
|
Rules can be reused against multiple leagues, and aren't strictly owned by
|
|
any particular `League` instance.
|
|
|
|
Attributes:
|
|
id (int): Automatically incrementing identifier number.
|
|
name (str): A human-readable label to identify this item.
|
|
ranking_system (int): TODO
|
|
points_allocation (int): TODO
|
|
points_awarded (int): TODO
|
|
place_secondly_by_weight (bool): TODO
|
|
team_places_secondly_by_section (bool): TODO
|
|
section_placed_positions = (int): TODO
|
|
worst_place_limits (bool): TODO
|
|
attendance_points (int): TODO
|
|
did_not_weigh (int): TODO
|
|
did_not_weigh_value (int): TODO
|
|
left_early (int): TODO
|
|
left_early_value (int): TODO
|
|
did_not_book (int): TODO
|
|
did_not_book_value (int): TODO
|
|
disqualification (int): TODO
|
|
disqualification_value (int): TODO
|
|
best_league_sessions (int): TODO
|
|
worst_league_sessions (int): TODO
|
|
match_placed_positions (int): TODO
|
|
"""
|
|
|
|
id = models.AutoField(primary_key=True)
|
|
name = models.CharField(max_length=128)
|
|
|
|
RANKING_SYSTEMS = (
|
|
(0, "choice 1"),
|
|
(1, "choice 2"),
|
|
(2, "choice 3")
|
|
)
|
|
ranking_system = models.PositiveSmallIntegerField(choices=RANKING_SYSTEMS)
|
|
|
|
POINTS_ALLOCATIONS = (
|
|
(0, "choice 1"),
|
|
(1, "choice 2"),
|
|
(2, "choice 3")
|
|
)
|
|
points_allocation = models.PositiveSmallIntegerField(choices=POINTS_ALLOCATIONS)
|
|
|
|
POINTS_AWARDED = (
|
|
(0, "choice 1"),
|
|
(1, "choice 2"),
|
|
(2, "choice 3")
|
|
)
|
|
points_awarded = models.PositiveSmallIntegerField(choices=POINTS_AWARDED)
|
|
|
|
place_secondly_by_weight = models.BooleanField()
|
|
team_places_secondly_by_section = models.BooleanField()
|
|
section_placed_positions = models.IntegerField()
|
|
worst_place_limits = models.BooleanField()
|
|
attendance_points = models.IntegerField()
|
|
|
|
DID_NOT_WEIGHS = (
|
|
(0, "choice 1"),
|
|
(1, "choice 2"),
|
|
(2, "choice 3")
|
|
)
|
|
did_not_weigh = models.PositiveSmallIntegerField(choices=DID_NOT_WEIGHS)
|
|
did_not_weigh_value = models.IntegerField()
|
|
|
|
LEFT_EARLYS = (
|
|
(0, "choice 1"),
|
|
(1, "choice 2"),
|
|
(2, "choice 3")
|
|
)
|
|
left_early = models.PositiveSmallIntegerField(choices=LEFT_EARLYS)
|
|
left_early_value = models.IntegerField()
|
|
|
|
DID_NOT_BOOKS = (
|
|
(0, "choice 1"),
|
|
(1, "choice 2"),
|
|
(2, "choice 3")
|
|
)
|
|
did_not_book = models.PositiveSmallIntegerField(choices=DID_NOT_BOOKS)
|
|
did_not_book = models.IntegerField()
|
|
|
|
DISQUALIFICATIONS = (
|
|
(0, "choice 1"),
|
|
(1, "choice 2"),
|
|
(2, "choice 3")
|
|
)
|
|
disqualification = models.PositiveSmallIntegerField(choices=DISQUALIFICATIONS)
|
|
disqualification_value = models.IntegerField()
|
|
|
|
best_league_sessions = models.IntegerField()
|
|
worst_league_sessions = models.IntegerField()
|
|
match_placed_positions = models.IntegerField()
|
|
|
|
class Meta:
|
|
verbose_name = "league rule"
|
|
verbose_name_plural = "league rules"
|
|
|
|
def __str__(self):
|
|
return self.name
|
|
|
|
|
|
class League(models.Model):
|
|
"""A League of matches (TODO: should match not be foreignkey linked to league?)
|
|
|
|
Attributes:
|
|
id (int): Automatically incrementing identifier number.
|
|
name (str): A human-readable label to identify this item.
|
|
description (str):
|
|
extra_notes (str):
|
|
profile_picture (file):
|
|
banner_picture (file):
|
|
matches (list of `Match`):
|
|
anglers (list of `Angler`):
|
|
rules (list of `LeagueRule`):
|
|
"""
|
|
|
|
id = models.AutoField(primary_key=True)
|
|
name = models.CharField(max_length=128)
|
|
description = models.CharField(max_length=384)
|
|
extra_notes = models.CharField(max_length=1028)
|
|
|
|
profile_picture = models.ImageField()
|
|
banner_picture = models.ImageField()
|
|
|
|
matches = models.ManyToManyField(to=Match)
|
|
anglers = models.ManyToManyField(to=Angler)
|
|
rules = models.ManyToManyField(to=LeagueRule)
|
|
|
|
class Meta:
|
|
verbose_name = "league"
|
|
verbose_name_plural = "leagues"
|
|
|
|
def __str__(self):
|
|
return self.name
|
|
|
|
@property
|
|
def results(self):
|
|
return LeagueResult.objects.filter(league=self)
|
|
|
|
|
|
class Sponsor(models.Model):
|
|
"""Represent those sponsoring.
|
|
|
|
Attribute:
|
|
id (int): Automatically incrementing identifier number.
|
|
name (str): The sponsor's name.
|
|
url (str): Link to the sponsor's external resource.
|
|
image (file): An image representing the sponsor.
|
|
"""
|
|
|
|
id = models.AutoField(primary_key=True)
|
|
name = models.CharField(max_length=128)
|
|
url = models.URLField()
|
|
image = models.ImageField()
|
|
|
|
class Meta:
|
|
verbose_name = "sponsor"
|
|
verbose_name_plural = "sponsors"
|
|
|
|
def __str__(self):
|
|
return self.name
|
|
|
|
|
|
class LeagueResult(models.Model):
|
|
"""An entry representing an angler's result in a league.
|
|
|
|
Attributes:
|
|
TODO
|
|
"""
|
|
id = models.AutoField(primary_key=True)
|
|
|
|
league = models.ForeignKey(to=League, on_delete=models.CASCADE)
|
|
angler = models.ForeignKey(to=Angler, on_delete=models.CASCADE)
|
|
sponsor = models.ForeignKey(to=Sponsor, on_delete=models.CASCADE, null=True, blank=True)
|
|
|
|
total_weight = models.CharField(max_length=64)
|
|
matches = models.IntegerField()
|
|
date = models.DateField(default=timezone.now)
|
|
|
|
class Meta:
|
|
verbose_name = "league result"
|
|
verbose_name_plural = "league results"
|
|
|
|
def __str__(self):
|
|
return f"{self.league.name} - {self.angler.name}"
|
|
|
|
|
|
# class Venue(models.Model):
|
|
# """Represents a Venue and Waters."""
|
|
|
|
# VENUE_TYPES = (
|
|
# ("FISHERY", "Fishery"),
|
|
# ("CLUB", "Club"),
|
|
# ("PRIVATE", "Private")
|
|
# )
|
|
|
|
# name = models.CharField(max_length=255, null=True, blank=True)
|
|
# description = models.TextField(blank=True, max_length=500, null=True,)
|
|
# extra_notes = models.TextField(blank=True, null=True,)
|
|
# venue_type = models.CharField(choices=VENUE_TYPES, max_length=50, null=True, blank=True)
|
|
|
|
# # Contact information
|
|
# phone_number = models.CharField(max_length=100, null=True, blank=True)
|
|
# email_address = models.EmailField(null=True, blank=True)
|
|
# website_url = models.URLField(null=True, blank=True)
|
|
|
|
# # Location information
|
|
# street_address = models.CharField(max_length=100, null=True, blank=True)
|
|
# city = models.CharField(max_length=255, null=True, blank=True)
|
|
# provence = models.CharField(max_length=100, null=True, blank=True)
|
|
# postal_code = models.CharField(max_length=20, null=True, blank=True)
|
|
# country = models.CharField(max_length=100, null=True, blank=True)
|
|
# latitude = models.DecimalField(max_digits=9, decimal_places=6, null=True, blank=True)
|
|
# longitude = models.DecimalField(max_digits=9, decimal_places=6, null=True, blank=True)
|
|
|
|
# # Socials information
|
|
# twitter_url = models.URLField(blank=True, null=True)
|
|
# instagram_url = models.URLField(blank=True, null=True)
|
|
# facebook_url = models.URLField(blank=True, null=True)
|
|
|
|
# active = models.BooleanField(default=True)
|
|
|
|
# def __str__(self):
|
|
# return self.name
|
|
|
|
# def waters(self):
|
|
# """Returns all waters linked to this venue."""
|
|
|
|
# waters = Waters.objects.filter(venue=self)
|
|
# return waters
|
|
|
|
|
|
# class Waters(models.Model):
|
|
# """Represents the waters of a Venue"""
|
|
|
|
# WATER_TYPES = (
|
|
# ("CW", "Commercial Water"),
|
|
# ("NSW", "Natural Still Water"),
|
|
# ("C", "Canal"),
|
|
# ("R", "River"),
|
|
# ("L", "Loch"),
|
|
# )
|
|
|
|
# FISH_TYPES = (
|
|
# ("C", "Coarse"),
|
|
# ("SC", "Specimen Carp"),
|
|
# ("G", "Game"),
|
|
# ("P", "Predator"),
|
|
# )
|
|
|
|
# venue = models.ForeignKey(Venue, on_delete=models.CASCADE)
|
|
|
|
# name = models.CharField(max_length=100)
|
|
# description = models.TextField(max_length=255)
|
|
|
|
# pegs_min = models.IntegerField()
|
|
# pegs_max = models.IntegerField()
|
|
# water_type = models.CharField(choices=WATER_TYPES, max_length=50)
|
|
# fish_type = models.CharField(choices=FISH_TYPES, max_length=50)
|
|
|
|
# # water_map = models.ImageField()
|
|
|
|
# def __str__(self):
|
|
# return self.name
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# class ReusableAutoField(models.PositiveIntegerField):
|
|
# """A django auto field that can reuse deleted primary keys."""
|
|
|
|
# def get_next_available_id(self, model_cls, using=None):
|
|
# """
|
|
# Returns the next available id for the given model class.
|
|
# """
|
|
# all_ids = set(range(1, model_cls._default_manager.count()+1))
|
|
# used_ids = set(model_cls._default_manager.all().values_list('pk', flat=True))
|
|
# available_ids = all_ids - used_ids
|
|
|
|
# if available_ids:
|
|
# return min(available_ids)
|
|
|
|
# if used_ids:
|
|
# return max(used_ids) + 1
|
|
|
|
# return 1
|
|
|
|
# def get_default(self):
|
|
# """Returns the default value for this field"""
|
|
|
|
# return self.get_next_available_id(self.model)
|
|
|
|
|
|
# class SectionValidator:
|
|
# """Validation class for the `section` field on the `member` model."""
|
|
|
|
# def __init__(self, max_value="ZZZ"):
|
|
# self.max_value = max_value.upper()
|
|
# self.alphabet_size = ord("Z") - ord("A") + 1
|
|
|
|
# def is_valid(self, section: str, team_sections: list[str]=None) -> bool:
|
|
# """Returns boolean if the section passed is valid."""
|
|
# section = section.upper()
|
|
|
|
# if not self._is_alphanumeric(section):
|
|
# return False
|
|
|
|
# if not self._is_in_alphabet(section[0]):
|
|
# return False
|
|
|
|
# if not self._is_in_range(section):
|
|
# return False
|
|
|
|
# if team_sections:
|
|
# if not self._is_unique(section, team_sections):
|
|
# return False
|
|
|
|
# if not self._is_not_adjacent(section, team_sections):
|
|
# return False
|
|
|
|
# return True
|
|
|
|
# def _is_alphanumeric(self, section: str) -> bool:
|
|
# """Returns boolean if all characters in the passed string are alphanumerical."""
|
|
# return all(c.isalnum() for c in section)
|
|
|
|
# def _is_in_alphabet(self, c) -> bool:
|
|
# """Returns boolean if the passed character is alphabetical."""
|
|
# return "A" <= c <= "Z"
|
|
|
|
# def _is_in_range(self, section) -> bool:
|
|
# """Returns boolean if the passed section less or equal to the max value."""
|
|
# section_value = self._section_value(section)
|
|
# max_value = self._section_value(self.max_value)
|
|
|
|
# return section_value <= max_value
|
|
|
|
# def _is_unique(self, section, team_sections) -> bool:
|
|
# """Returns boolean if the passed section is unique amongst `team_sections`."""
|
|
# return section not in team_sections
|
|
|
|
# def _is_not_adjacent(self, section, team_sections) -> bool:
|
|
# """Returns boolean if the passed section is not adjacent to any `team_sections`."""
|
|
# for team_section in team_sections:
|
|
# team_section_value = self._section_value(team_section)
|
|
# section_value = self._section_value(section)
|
|
# if abs(team_section_value - section_value) <= 1:
|
|
# return False
|
|
|
|
# return True
|
|
|
|
# def _section_value(self, section):
|
|
# """Returns the value of the passed section."""
|
|
# n = len(section)
|
|
# value = sum((ord(c) - ord("A") + 1) * self.alphabet_size ** (n - i - 1) for i, c in enumerate(section))
|
|
# return value
|
|
|
|
|
|
# class SectionManager(models.Manager):
|
|
|
|
# @staticmethod
|
|
# def get_max_section():
|
|
# max_section = None
|
|
# max_number = -1
|
|
|
|
# # Iterate through all sections in the database
|
|
# for section in Section.objects.all():
|
|
# section_name = section.name
|
|
# section_number = 0
|
|
|
|
# # Calculate the section number based on the section name
|
|
# for i, char in enumerate(section_name):
|
|
# section_number += (ord(char) - ord('A') + 1) * (26 ** (len(section_name) - i - 1))
|
|
|
|
# # Check if this section has a higher number than the current maximum
|
|
# if section_number > max_number:
|
|
# max_number = section_number
|
|
# max_section = section_name
|
|
|
|
# return max_section
|
|
|
|
# @staticmethod
|
|
# def find_next_section(current_section):
|
|
# if not current_section:
|
|
# return 'A'
|
|
|
|
# # Split current section name into a list of characters
|
|
# chars = list(current_section)
|
|
|
|
# # Increment the last character
|
|
# chars[-1] = chr(ord(chars[-1]) + 1)
|
|
|
|
# # Check if the last character is "Z", and carry over to the next character if necessary
|
|
# for i in range(len(chars) - 1, -1, -1):
|
|
# if chars[i] > 'Z':
|
|
# chars[i] = 'A'
|
|
# if i == 0:
|
|
# # If the first character needs to be incremented, add a new character "A"
|
|
# chars.insert(0, 'A')
|
|
# else:
|
|
# # Increment the previous character
|
|
# chars[i - 1] = chr(ord(chars[i - 1]) + 1)
|
|
# else:
|
|
# break
|
|
|
|
# # Join the characters back into a string and return the result
|
|
# return ''.join(chars)
|
|
|
|
# class Section(models.Model):
|
|
# """Represents a fishing area. Members can be assigned to a section,
|
|
# but no 2 teammates can be in the same or adjacent section."""
|
|
|
|
# character = models.CharField(max_length=3, unique=True, null=False)
|
|
|
|
# objects = SectionManager()
|
|
|
|
# def clean(self):
|
|
# super().clean()
|
|
# self.character = self.character.upper()
|
|
|
|
# def __str__(self) -> str:
|
|
# return self.character
|
|
|
|
|
|
# class Member(models.Model):
|
|
# """Represents a member of a team"""
|
|
|
|
# first_name = models.CharField(max_length=255)
|
|
# last_name = models.CharField(max_length=255)
|
|
# team = models.ForeignKey("Team", on_delete=models.SET_NULL, null=True, blank=True, related_name='members')
|
|
# peg_number = models.PositiveIntegerField(null=True, editable=True, unique=True)
|
|
# section = models.ForeignKey(to=Section, on_delete=models.SET_NULL, null=True, swappable=True, related_name='members')
|
|
|
|
# def __init__(self, *args, **kwargs):
|
|
# super().__init__(*args, **kwargs)
|
|
|
|
# # If the peg_number field is not set, we assign it the smallest
|
|
# # available positive integer, excluding any used values.
|
|
# if not self.peg_number:
|
|
# used_peg_numbers = Member.objects.exclude(id=self.id).exclude(peg_number=None).values_list("peg_number", flat=True)
|
|
# peg_numbers = set(range(1, Member.objects.count() + 1)) - set(used_peg_numbers)
|
|
# if peg_numbers:
|
|
# self.peg_number = min (peg_numbers)
|
|
|
|
# def __str__(self):
|
|
# return f"{self.first_name} {self.last_name} (team {self.team.number}) [section {self.section.character}]"
|
|
|
|
# @property
|
|
# def fullname(self) -> str:
|
|
# return f"{self.first_name} {self.last_name}"
|
|
|
|
|
|
# class Team(models.Model):
|
|
# """Represents a team"""
|
|
|
|
# number = models.PositiveIntegerField(unique=True, null=False, blank=False)
|
|
|
|
# def __str__(self):
|
|
# return f"Team {self.number}"
|