diff --git a/src/mainapp/admin.py b/src/mainapp/admin.py index cce404d..fc6b9fd 100644 --- a/src/mainapp/admin.py +++ b/src/mainapp/admin.py @@ -2,7 +2,14 @@ from django.contrib import admin -from .models import Member, Team, Section +from .models import Venue +# from .models import Member, Team, Section + + +@admin.register(Venue) +class VenueAdmin(admin.ModelAdmin): + """Admin model for the Venue model.""" + # @admin.register(Peg) @@ -15,27 +22,27 @@ from .models import Member, Team, Section # search_fields = ("peg_number",) -@admin.register(Member) -class MemberAdmin(admin.ModelAdmin): - """Admin model for the Member model.""" +# @admin.register(Member) +# class MemberAdmin(admin.ModelAdmin): +# """Admin model for the Member model.""" - list_display = ("first_name", "last_name", "team") - search_fields = ("first_name", "last_name", "team") - list_filter = ("team",) +# list_display = ("first_name", "last_name", "team") +# search_fields = ("first_name", "last_name", "team") +# list_filter = ("team",) -@admin.register(Team) -class TeamAdmin(admin.ModelAdmin): - """Admin model for the Team model.""" +# @admin.register(Team) +# class TeamAdmin(admin.ModelAdmin): +# """Admin model for the Team model.""" - # readonly_fields = ("id", "number",) - list_display = ("id", "number",) - search_fields = ("number",) +# # readonly_fields = ("id", "number",) +# list_display = ("id", "number",) +# search_fields = ("number",) -@admin.register(Section) -class SectionAdmin(admin.ModelAdmin): - """Admin model for the Section model.""" +# @admin.register(Section) +# class SectionAdmin(admin.ModelAdmin): +# """Admin model for the Section model.""" - list_display = ("id", "character",) - search_fields = ("character",) +# list_display = ("id", "character",) +# search_fields = ("character",) diff --git a/src/mainapp/migrations/0002_venue_delete_member_delete_section_delete_team.py b/src/mainapp/migrations/0002_venue_delete_member_delete_section_delete_team.py new file mode 100644 index 0000000..f9fd235 --- /dev/null +++ b/src/mainapp/migrations/0002_venue_delete_member_delete_section_delete_team.py @@ -0,0 +1,45 @@ +# Generated by Django 4.1.5 on 2023-10-22 21:33 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('mainapp', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='Venue', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=255)), + ('description', models.TextField(blank=True)), + ('extra_notes', models.TextField(blank=True)), + ('venue_type', models.CharField(choices=[('FISHERY', 'Fishery'), ('CLUB', 'Club'), ('PRIVATE', 'Private')], max_length=50)), + ('phone_number', models.CharField(max_length=100)), + ('email_address', models.EmailField(max_length=254)), + ('website_url', models.URLField()), + ('street_address', models.CharField(max_length=100)), + ('city', models.CharField(max_length=255)), + ('provence', models.CharField(max_length=100)), + ('postal_code', models.CharField(max_length=20)), + ('country', models.CharField(max_length=100)), + ('latitude', models.DecimalField(blank=True, decimal_places=6, max_digits=9, null=True)), + ('longitude', models.DecimalField(blank=True, decimal_places=6, max_digits=9, null=True)), + ('twitter_url', models.URLField(blank=True)), + ('instagram_url', models.URLField(blank=True)), + ('facebook_url', models.URLField(blank=True)), + ], + ), + migrations.DeleteModel( + name='Member', + ), + migrations.DeleteModel( + name='Section', + ), + migrations.DeleteModel( + name='Team', + ), + ] diff --git a/src/mainapp/models.py b/src/mainapp/models.py index 4a4ecae..a604a83 100644 --- a/src/mainapp/models.py +++ b/src/mainapp/models.py @@ -4,194 +4,231 @@ from django.db import models from django.core.exceptions import ValidationError -class ReusableAutoField(models.PositiveIntegerField): - """A django auto field that can reuse deleted primary keys.""" +class Venue(models.Model): + """Represents a Venue and Waters.""" - 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 + VENUE_TYPES = ( + ("FISHERY", "Fishery"), + ("CLUB", "Club"), + ("PRIVATE", "Private") + ) - if available_ids: - return min(available_ids) + name = models.CharField(max_length=255) + description = models.TextField(blank=True) + extra_notes = models.TextField(blank=True) + venue_type = models.CharField(choices=VENUE_TYPES, max_length=50) - if used_ids: - return max(used_ids) + 1 + # Contact information + phone_number = models.CharField(max_length=100) + email_address = models.EmailField() + website_url = models.URLField() - return 1 + # Location information + street_address = models.CharField(max_length=100) + city = models.CharField(max_length=255) + provence = models.CharField(max_length=100) + postal_code = models.CharField(max_length=20) + country = models.CharField(max_length=100) + 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) - 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) + # Socials information + twitter_url = models.URLField(blank=True) + instagram_url = models.URLField(blank=True) + facebook_url = models.URLField(blank=True) 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}" + return self.name -class Team(models.Model): - """Represents a team""" +# class ReusableAutoField(models.PositiveIntegerField): +# """A django auto field that can reuse deleted primary keys.""" - number = models.PositiveIntegerField(unique=True, null=False, blank=False) +# 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 - def __str__(self): - return f"Team {self.number}" +# 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}" diff --git a/src/mainapp/templates/venues.html b/src/mainapp/templates/venues.html new file mode 100644 index 0000000..68d5ab0 --- /dev/null +++ b/src/mainapp/templates/venues.html @@ -0,0 +1,181 @@ +{% extends "base.html" %} +{% load static %} + +{% block title %} + Venues and Waters | +{% endblock title %} + +{% block style %} +{% endblock style %} + +{% block header_buttons %} + +{% endblock header_buttons %} + +{% block content %} + +
{{ venue.type }}
+{{ venue.street_address }}
+{{ venue.city }}, {{ venue.provence }}
+{{ venue.postal_code }}
+{{ venue.phone_number }}
+{{ venue.email_address }}
+