General rewrite of sections + UI changes

General rewrite of sections:
sections are rewritten to be their own model

UI Changes:
updated teams page for sections changes, also rewrote home page.
This commit is contained in:
Corban-Lee 2023-05-11 10:03:48 +01:00
parent e2ad5676c7
commit 8dfe4eb9f2
14 changed files with 4909 additions and 1173 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -2,71 +2,151 @@
{
"model": "mainapp.team",
"pk": 1,
"fields": {
"section_letter": "M"
}
"fields": {}
},
{
"model": "mainapp.team",
"pk": 2,
"fields": {
"section_letter": "K"
}
"fields": {}
},
{
"model": "mainapp.team",
"pk": 3,
"fields": {
"section_letter": "Q"
}
"fields": {}
},
{
"model": "mainapp.team",
"pk": 4,
"fields": {
"section_letter": "V"
}
"fields": {}
},
{
"model": "mainapp.team",
"pk": 5,
"fields": {
"section_letter": "U"
}
"fields": {}
},
{
"model": "mainapp.team",
"pk": 6,
"fields": {
"section_letter": "H"
}
"fields": {}
},
{
"model": "mainapp.team",
"pk": 7,
"fields": {
"section_letter": "A"
}
"fields": {}
},
{
"model": "mainapp.team",
"pk": 8,
"fields": {
"section_letter": "D"
}
"fields": {}
},
{
"model": "mainapp.team",
"pk": 9,
"fields": {
"section_letter": "P"
}
"fields": {}
},
{
"model": "mainapp.team",
"pk": 10,
"fields": {
"section_letter": "G"
}
"fields": {}
},
{
"model": "mainapp.team",
"pk": 11,
"fields": {}
},
{
"model": "mainapp.team",
"pk": 12,
"fields": {}
},
{
"model": "mainapp.team",
"pk": 13,
"fields": {}
},
{
"model": "mainapp.team",
"pk": 14,
"fields": {}
},
{
"model": "mainapp.team",
"pk": 15,
"fields": {}
},
{
"model": "mainapp.team",
"pk": 16,
"fields": {}
},
{
"model": "mainapp.team",
"pk": 17,
"fields": {}
},
{
"model": "mainapp.team",
"pk": 18,
"fields": {}
},
{
"model": "mainapp.team",
"pk": 19,
"fields": {}
},
{
"model": "mainapp.team",
"pk": 20,
"fields": {}
},
{
"model": "mainapp.team",
"pk": 21,
"fields": {}
},
{
"model": "mainapp.team",
"pk": 22,
"fields": {}
},
{
"model": "mainapp.team",
"pk": 23,
"fields": {}
},
{
"model": "mainapp.team",
"pk": 24,
"fields": {}
},
{
"model": "mainapp.team",
"pk": 25,
"fields": {}
},
{
"model": "mainapp.team",
"pk": 26,
"fields": {}
},
{
"model": "mainapp.team",
"pk": 27,
"fields": {}
},
{
"model": "mainapp.team",
"pk": 28,
"fields": {}
},
{
"model": "mainapp.team",
"pk": 29,
"fields": {}
},
{
"model": "mainapp.team",
"pk": 30,
"fields": {}
}
]

View File

@ -1,84 +1,117 @@
import names
import json
import random
from typing import Optional, List, Set
from string import ascii_uppercase
from django.core.management.base import BaseCommand
from django.db.models import Q, Count
from mainapp.models import Member, Team, SectionValidator
from mainapp.models import Member, Team, SectionValidator, Section
# TODO: refactor this file like create_teams_fixtures.py
# SECTION_LETTERS = ascii_uppercase
# def get_next_available_section(member: Member, section_validator: SectionValidator) -> Optional[str]:
# """Returns the next available section for a member."""
# member_sections = member.sections.all().values_list('name', flat=True)
# used_sections = set(member_sections)
# if not member_sections:
# return "A"
# used_sections = sorted(used_sections)
# last_section = member_sections[-1]
# last_section_index = SECTION_LETTERS.index(last_section)
# for i in range(last_section_index + 1, len(SECTION_LETTERS)):
# section = SECTION_LETTERS[i]
# if section_validator.is_valid_section(section) and section not in used_sections:
# return section
# for i in range(last_section_index - 1, -1, -1):
# section = SECTION_LETTERS[i]
# if section_validator.is_valid_section(section) and section not in used_sections:
# return section
# return None
class Command(BaseCommand):
help = "Creates a fixture with randomly generated Member objects"
def add_arguments(self, parser):
parser.add_argument("num_members", type=int)
parser.add_argument("members_per_team", type=int)
def handle(self, *args, **options):
num_members = options["num_members"]
members_per_team = options["members_per_team"]
teams = Team.objects.all()
if not teams:
print("No teams found. Please create some teams first.")
return
raise Exception("no teams")
members = []
new_members = []
for team in teams:
for _ in range(num_members):
first_name = names.get_first_name()
last_name = names.get_last_name()
member = Member.objects.create(first_name=first_name, last_name=last_name, team=team)
members.append(member)
self.stdout.write(self.style.SUCCESS(f"Created {num_members} members."))
i = members_per_team
while i > 0:
# create a members fixture file
members_fixture = []
for member in members:
member_fixture = {
team_member_ids = team.members.values_list('id', flat=True)
print(team_member_ids)
section = random.choice([
sec for sec in
Section.objects.exclude(members__id__in=team_member_ids)
])
member = Member.objects.create(
first_name=names.get_first_name(),
last_name=names.get_last_name(),
section=section,
team=team
)
if not section.is_joinable(member):
member.delete()
i += 1
continue
new_members.append(member)
print(member)
i -= 1
fixtures = []
for member in new_members:
member.delete()
fixture = {
"model": "mainapp.member",
"pk": member.pk,
"fields": {
"first_name": member.first_name,
"last_name": member.last_name,
"team": member.team_id,
"peg_number": member.peg_number,
"section": member.section_id,
"peg_number": member.peg_number
}
}
members_fixture.append(member_fixture)
fixtures.append(fixture)
with open("src/mainapp/fixtures/members_fixture.json", "w") as f:
f.write(json.dumps(members_fixture, indent=2))
f.write(json.dumps(fixtures, indent=2))
self.stdout.write(self.style.SUCCESS("Created members_fixture.json."))
# def handle(self, *args, **options):
# num_members = options["num_members"]
# teams = Team.objects.all()
# if not teams:
# print("No teams found. Please create some teams first.")
# return
# members = []
# for team in teams:
# for _ in range(num_members):
# first_name = names.get_first_name()
# last_name = names.get_last_name()
# member = Member.objects.create(first_name=first_name, last_name=last_name, team=team)
# members.append(member)
# self.stdout.write(self.style.SUCCESS(f"Created {num_members} members."))
# # create a members fixture file
# members_fixture = []
# for member in members:
# member_fixture = {
# "model": "mainapp.member",
# "pk": member.pk,
# "fields": {
# "first_name": member.first_name,
# "last_name": member.last_name,
# "team": member.team_id,
# "peg_number": member.peg_number,
# }
# }
# members_fixture.append(member_fixture)
# with open("src/mainapp/fixtures/members_fixture.json", "w") as f:
# f.write(json.dumps(members_fixture, indent=2))
# self.stdout.write(self.style.SUCCESS("Created members_fixture.json."))

View File

@ -0,0 +1,79 @@
"""Command to create test data fixture for sections."""
import json
from string import ascii_uppercase
from django.core.management.base import BaseCommand
from mainapp.models import Section, SectionValidator
def name_sort_key(name):
if len(name) == 1:
return (0, name)
else:
return (1, name[:-1], name[-1])
class Command(BaseCommand):
help = "Creates a fixture file for Team objects"
def add_arguments(self, parser):
parser.add_argument("amount_of_sections", type=int, help="Number of sections to create")
def handle(self, *args, **options):
amount_of_sections = options["amount_of_sections"]
used_names = set()
last_name = ""
for i in range(amount_of_sections):
add_character = last_name.endswith("Z")
if add_character:
last_name = ""
new_name = last_name if add_character else ""
if not new_name.endswith("Z"):
new_name = new_name[:-1] if len(new_name) > 1 else new_name
try:
new_name += ascii_uppercase[i]
except IndexError:
quotient, remainder = divmod(i, 26)
new_name += ascii_uppercase[quotient - 1] + ascii_uppercase[remainder]
while new_name in used_names:
if new_name[-1] == "Z":
idx = len(new_name) - 2
while idx >= 0 and new_name[idx] == "Z":
idx -= 1
if idx < 0:
new_name = "A" * (len(new_name) + 1)
else:
new_name = new_name[:idx] + chr(ord(new_name[idx]) + 1) + "A" * (len(new_name) - idx - 1)
else:
new_name = new_name[:-1] + chr(ord(new_name[-1]) + 1)
used_names.add(new_name)
last_name = new_name
names = sorted([name for name in used_names], key=name_sort_key)
print([name for name in names])
sections = [Section(name=name) for name in names]
Section.objects.bulk_create(sections)
fixture = [{
"model": "mainapp.section",
"pk": section.pk,
"fields": {
"name": section.name
}
} for section in sections]
for section in sections:
section.delete()
with open("src/mainapp/fixtures/sections_fixture.json", "w") as file:
file.write(json.dumps(fixture, indent=4))
self.stdout.write(self.style.SUCCESS("Created sections_fixture.json."))

View File

@ -1,12 +1,10 @@
"""Command to create test data fixture for teams."""
import random
import json
from string import ascii_uppercase
from django.core.management.base import BaseCommand
from django.db.utils import IntegrityError
from mainapp.models import Team, BLOCKED_SECTION_LETTERS
from mainapp.models import Team
class Command(BaseCommand):
@ -16,43 +14,15 @@ class Command(BaseCommand):
parser.add_argument("amount_of_teams", type=int, help="Number of teams to create")
def handle(self, *args, **options):
existing_teams = Team.objects.all()
# Available sections
available_sections = [
char for char in [*ascii_uppercase]
if char.lower() not in BLOCKED_SECTION_LETTERS
and char not in [team.section_letter for team in existing_teams]
]
if not available_sections:
self.stdout.write(self.style.ERROR(
f"There are no available sections for new teams."
))
return
teams_amount = options["amount_of_teams"]
max_teams = len(available_sections)
if teams_amount > max_teams:
self.stdout.write(self.style.ERROR(
f"Number of teams is too large [{teams_amount}/{max_teams}]."
))
return
# Create the new teams (this will create them in the database)#
new_teams = []
for i in range(teams_amount):
section_letter = random.choice(available_sections)
available_sections.remove(section_letter)
team = Team.objects.create(section_letter=section_letter)
new_teams.append(team)
new_teams = [Team.objects.create() for i in range(teams_amount)]
teams_fixture = [{
"model": "mainapp.team",
"pk": team.pk,
"fields": {"section_letter": team.section_letter}
"fields": {}
} for team in new_teams]
# Remove the teams from the database

View File

@ -1,4 +1,4 @@
# Generated by Django 4.1.5 on 2023-05-09 22:52
# Generated by Django 4.1.5 on 2023-05-10 08:30
from django.db import migrations, models
import django.db.models.deletion
@ -17,13 +17,13 @@ class Migration(migrations.Migration):
name='Section',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(editable=False, max_length=3, unique=True)),
('name', models.CharField(max_length=3, unique=True)),
],
),
migrations.CreateModel(
name='Team',
fields=[
('team_number', mainapp.models.ReusableAutoField(default=mainapp.models.ReusableAutoField.get_default, editable=False, primary_key=True, serialize=False)),
('name', mainapp.models.ReusableAutoField(default=mainapp.models.ReusableAutoField.get_default, editable=False, primary_key=True, serialize=False)),
],
),
migrations.CreateModel(
@ -34,7 +34,7 @@ class Migration(migrations.Migration):
('last_name', models.CharField(max_length=255)),
('peg_number', models.PositiveIntegerField(null=True, unique=True)),
('section', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='mainapp.section')),
('team', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='members', to='mainapp.team', validators=[mainapp.models.validate_team_size])),
('team', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='members', to='mainapp.team')),
],
),
]

View File

@ -1,23 +0,0 @@
# Generated by Django 4.1.5 on 2023-05-09 23:20
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('mainapp', '0001_initial'),
]
operations = [
migrations.RenameField(
model_name='team',
old_name='team_number',
new_name='name',
),
migrations.AlterField(
model_name='section',
name='name',
field=models.CharField(max_length=3, unique=True),
),
]

View File

@ -1,4 +1,4 @@
from string import ascii_uppercase
"""Models for the mainapp."""
from django.db import models
from django.core.exceptions import ValidationError
@ -7,7 +7,7 @@ from django.db import models
class ReusableAutoField(models.PositiveIntegerField):
"""A django auto field that can reuse deleted primary keys"""
"""A django auto field that can reuse deleted primary keys."""
def get_next_available_id(self, model_cls, using=None):
"""
@ -31,15 +31,6 @@ class ReusableAutoField(models.PositiveIntegerField):
return self.get_next_available_id(self.model)
# class Peg(models.Model):
# """Represents a person's peg"""
# peg_number = ReusableAutoField(primary_key=True, default=ReusableAutoField.get_default, editable=False)
# def __str__(self):
# return f"Peg {self.peg_number}"
class SectionValidator:
"""Validation class for the `section` field on the `member` model."""
@ -124,13 +115,54 @@ class Section(models.Model):
return self.name
@property
def members(self):
def get_members(self):
return Member.objects.filter(section=self)
@property
def section_after(self):
"""Returns the next section after this one, or none if not found."""
sections = [sec for sec in Section.objects.all()]
sections.sort(key=lambda sec: sec.name)
try:
return sections[sections.index(self) + 1]
except IndexError:
return None
def validate_team_size(value):
if Member.objects.filter(team=value).count() >= 3:
raise ValidationError('Team already has maximal amount of members (3)')
@property
def section_before(self):
"""Returns the last section before this one, or none if not found."""
sections = [sec for sec in Section.objects.all()]
sections.sort(key=lambda sec: sec.name)
try:
return sections[sections.index(self) - 1]
except IndexError:
return None
def is_joinable(self, member, /) -> bool:
"""Returns boolean if a member is able to join this section."""
if not member.team:
return True
teammates = [teammate for teammate in Member.objects.filter(team=member.team)]
section_after = self.section_after.get_members if self.section_after else []
section_before = self.section_before.get_members if self.section_before else []
for teammate in teammates:
if teammate in section_after:
return False
if teammate in section_before:
return False
return True
return any(
teammate in section_after or teammate in section_before
for teammate in teammates
)
class Member(models.Model):
@ -138,9 +170,9 @@ class Member(models.Model):
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, validators=(validate_team_size,), related_name='members')
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)
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)
@ -153,46 +185,22 @@ class Member(models.Model):
if peg_numbers:
self.peg_number = min (peg_numbers)
# def clean(self):
# super().clean()
# validator = SectionValidator(max_value="ZZZ")
# if not validator.is_valid(self.section):
# raise ValidationError("Invalid section value.")
def __str__(self):
return f"{self.first_name} {self.last_name} (team {self.team.team_number}))"
return f"{self.first_name} {self.last_name} (team {self.team.name}) [section {self.section.name}]"
@property
def fullname(self) -> str:
return f"{self.first_name} {self.last_name}"
BLOCKED_SECTION_LETTERS = ["i"] # lowercase only, sections cannot be any of these letters#
class Team(models.Model):
"""Represents a team"""
name = ReusableAutoField(primary_key=True, default=ReusableAutoField.get_default, editable=False)
# section_letter = models.CharField(max_length=1, unique=True, choices=[
# (char, char) for char in ascii_uppercase if char.lower() not in BLOCKED_SECTION_LETTERS
# ])
def __str__(self):
return f"Team {self.name}"
@property
def members(self) -> list[Member]:
def get_members(self) -> list[Member]:
return Member.objects.filter(team=self)
# def validate_section_character(value):
# """Validates the section character"""
# value = value.upper()
# banned_characters = ["I"]
# if value in banned_characters:
# raise ValidationError(f"The character <{value}> is a prohibited character.")

View File

@ -1,53 +1,90 @@
{% extends "base.html" %}
{% block content %}
<section class="py-5 text-center container">
<div class="row py-lg-5">
<div class="col-lg-6 col-md-8 mx-auto">
<h1 class="fw-light text-white">Results System</h1>
<p class="lead text-white">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi faucibus elementum diam nec semper. Nullam vestibulum enim eu nisi condimentum, vitae suscipit risus imperdiet.</p>
</div>
</div>
</section>
<div class="album py-5 bg-light">
<div class="py-5 bg-body mt-5">
<div class="container">
<div class="row row-cols-1 row-cols-sm-2 row-cols-md-3 g-3">
<div class="text-center mb-4">
<h1 class="fw-light text-white mt-5 pb-5 mb-5 bg-body text-body d-inline-block">
&ThickSpace; Angling Trust Results &ThickSpace;
<div class="border-company border-bottom border-2 w-100 pt-1"></div>
</h1>
</div>
<div class="row row-cols-1 row-cols-sm-2 row-cols-md-3 g-4 justify-contentpaper">
<!-- Teams & Members -->
<div class="col">
<div class="card shadow-sm">
<a href="{% url 'teams' %}">
<svg class="bd-placeholder-img card-img-top" width="100%" height="225" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Placeholder: Thumbnail" preserveAspectRatio="xMidYMid slice" focusable="false"><title>Placeholder</title><rect width="100%" height="100%" fill="#55595c"/><text x="50%" y="50%" fill="#eceeef" dy=".3em">Thumbnail</text></svg>
<div class="card-body">
<p class="card-text">Teams / Members</p>
</div>
</a>
</div>
<a href="{% url 'members' %}" class="text-decoration-none text-body">
<!-- <div class="fluid-hover-zoom shadow-sm md-shadow-on-hover bg-body-tertiary rounded p-5 text-body d-flex flex-wrap justify-content-center align-items-center">
<i class="bi bi-people w-100 text-center" style="font-size: 5rem;"></i>
<h3 class=" fs-3">
Member Management
<div class="border-company border-bottom border-2 w-100 pt-1"></div>
</h3>
</div> -->
<div class="fluid-hover-zoom d-flex flex-wrap justify-content-center shadow-sm md-shadow on-hover bg-body-tertiary rounded text-center p-5 h-100">
<h3 class="h2 w-100">
Member Management
<div class="border-company border-bottom border-2 w-100 pt-1"></div>
</h3>
<p class="mt-3">
Create and manage all members, teams and sections of an event
</p>
<button class="btn btn-lg btn-company mt-3 px-4 ">
Visit Member Management
</button>
</div>
</a>
</div>
<!-- Scoreboard -->
<div class="col">
<div class="card shadow-sm">
<a href="{% url 'scoreboard' %}">
<svg class="bd-placeholder-img card-img-top" width="100%" height="225" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Placeholder: Thumbnail" preserveAspectRatio="xMidYMid slice" focusable="false"><title>Placeholder</title><rect width="100%" height="100%" fill="#55595c"/><text x="50%" y="50%" fill="#eceeef" dy=".3em">Thumbnail</text></svg>
<div class="card-body">
<p class="card-text">Scoreboard</p>
</div>
</a>
</div>
<a href="{% url 'scoreboard' %}" class="text-decoration-none text-body">
<!-- <div class="fluid-hover-zoom shadow-sm md-shadow-on-hover bg-body-tertiary rounded p-5 text-body d-flex flex-wrap justify-content-center align-items-center">
<i class="bi bi-list-stars w-100 text-center" style="font-size: 5rem;"></i>
<h3 class=" fs-3">
&ThickSpace; Scoreboard &ThickSpace;
<div class="border-company border-bottom border-2 w-100 pt-1"></div>
</h3>
</div> -->
<div class="fluid-hover-zoom d-flex flex-wrap justify-content-center shadow-sm md-shadow on-hover bg-body-tertiary rounded text-center p-5 h-100">
<h3 class="h2 w-100">
Live Scoreboard
<div class="border-company border-bottom border-2 w-100 pt-1"></div>
</h3>
<p class="mt-3">
View a live scoreboard of the event, good for a dedicated scoreboard display
</p>
<button class="btn btn-lg btn-company mt-3 px-4 mt-auto">
Visit Live Scoreboard
</button>
</div>
</a>
</div>
<!-- Results -->
<div class="col">
<div class="card shadow-sm">
<a href="{% url 'results' %}">
<svg class="bd-placeholder-img card-img-top" width="100%" height="225" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Placeholder: Thumbnail" preserveAspectRatio="xMidYMid slice" focusable="false"><title>Placeholder</title><rect width="100%" height="100%" fill="#55595c"/><text x="50%" y="50%" fill="#eceeef" dy=".3em">Thumbnail</text></svg>
<div class="card-body">
<p class="card-text">Results</p>
</div>
</a>
</div>
<a href="{% url 'results' %}" class="text-decoration-none text-body">
<!-- <div class="fluid-hover-zoom shadow-sm md-shadow-on-hover bg-body-tertiary rounded p-5 text-body d-flex flex-wrap justify-content-center align-items-center">
<i class="bi bi-journal-text w-100 text-center" style="font-size: 5rem;"></i>
<h3 class=" fs-3">
&ThickSpace; Results &ThickSpace;
<div class="border-company border-bottom border-2 w-100 pt-1"></div>
</h3>
</div> -->
<div class="fluid-hover-zoom d-flex flex-wrap justify-content-center shadow-sm md-shadow on-hover bg-body-tertiary rounded text-center p-5 h-100">
<h3 class="h2 w-100">
Competition Results
<div class="border-company border-bottom border-2 w-100 pt-1"></div>
</h3>
<p class="mt-3">
An organised output of the competition outcome and member performance
</p>
<button class="btn btn-lg btn-company mt-3 px-4 mt-auto">
Visit Competition Results
</button>
</div>
</a>
</div>
</div>

View File

@ -1,9 +1,14 @@
{% extends "base.html" %}
{% block title %}
Teams & Members |
Member Management |
{% endblock title %}
{% block style %}
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/select2-bootstrap-5-theme@1.3.0/dist/select2-bootstrap-5-theme.min.css" />
{% endblock style %}
{% block content %}
<div class="bg-body p-4">
<a href="{% url 'index' %}" class="btn btn-company px-4">Back</a>
@ -11,8 +16,11 @@
<div class="container my-4 p-4 pb-0 bg-body rounded">
<div class="row mb-4">
<div class="col-12 col-xl-4">
<h3 class="mb-3 mb-xl-0">Teams &amp; Members</h3>
<div class="col-12 col-xl-4 d-flex">
<h3 class="mb-3 mb-xl-0">
Member Management
<div class="border-company border-bottom border-2 w-100 pt-1"></div>
</h3>
</div>
<div class="col-sm-3 col-md-6 col-xl-4 mb-3 mb-sm-0">
<div class="input-group justify-content-xl-end">
@ -22,6 +30,9 @@
<button class="btn border-secondary-subtle btn-outline-company" id="addTeam" data-bs-toggle="tooltip" data-bs-title="Add Team">
<i class="bi bi-people"></i>
</button>
<button class="btn border-secondary-subtle btn-outline-company" id="addSection" data-bs-toggle="tooltip" data-bs-title="Add Section">
<i class="bi bi-layers"></i>
</button>
</div>
</div>
<div class="col-sm-9 col-md-6 col-xl-4">
@ -84,12 +95,26 @@
<div class="modal fade" id="editTeamModal" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content border-0">
<div class="modal-header">
<h3 class="modal-title fs-5" id="editTeamTitle">Team Number Here</h3>
<div class="modal-header border-0">
<h3 class="modal-title fs-5" id="editTeamTitle">
Team Number Here
<div class="border-company border-bottom border-2 w-100 pt-1"></div>
</h3>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="close"></button>
</div>
<div class="modal-body">
<form id="editTeamForm">
<div class="col-md-6">
<div class="input-group">
<span class="input-group-text">
<i class="bi bi-tag"></i>
</span>
<div class="form-floating">
<input type="number" class="form-control" name="editTeamName" id="editTeamName">
<label for="editTeamName">Number</label>
</div>
</div>
</div>
<!-- <form id="editTeamForm">
<div class="row">
<div class="col-md-6 mb-3 mb-md-0">
<label for="editTeamNumber" class="form-label">Team Number</label>
@ -100,11 +125,10 @@
<input type="text" name="editTeamSection" class="form-control" id="editTeamSection">
</div>
</div>
</form>
</form> -->
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary" id="saveEditTeamModal">Save Changes</button>
<div class="modal-footer border-0">
<button type="button" class="btn btn-company" id="saveEditTeamModal">Save Changes</button>
</div>
</div>
</div>
@ -114,35 +138,71 @@
<div class="modal fade" id="editMemberModal" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content border-0">
<div class="modal-header">
<h3 class="modal-title fs-5" id="editMemberName">Member Name Here</h3>
<div class="modal-header border-0">
<h3 class="modal-title fs-5 color" id="editMemberName">
Member Name Here
<div class="border-company border-bottom border-2 w-100 pt-1"></div>
</h3>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="close"></button>
</div>
<div class="modal-body">
<form id="editMemberForm">
<form>
<div class="row g-3 mb-3">
<div class="col-md">
<div class="form-floating">
<input class="form-control" type="text" name="editMemberForename" id="editMemberForename" placeholder="Forename" value="First Name">
<label for="editMemberForename">Forename</label>
</div>
</div>
<div class="col-md">
<div class="form-floating">
<input class="form-control" type="text" name="editMemberSurname" id="editMemberSurname" placeholder="Surname" value="Last Name">
<label for="editMemberSurname">Surname</label>
</div>
</div>
</div>
<div class="input-group mb-3">
<span class="input-group-text">
<i class="bi bi-people"></i>
</span>
<select class="form-select" name="editMemberTeam" id="editMemberTeam">
<option value="" class="font-ssp">Team 1</option>
<option value="" class="font-ssp">Team 2</option>
<option value="" class="font-ssp">Team 3</option>
<option value="" class="font-ssp">Team 4</option>
</select>
</div>
<div class="input-group mb-3">
<span class="input-group-text">
<i class="bi bi-layers"></i>
</span>
<select class="form-select" name="editMemberSection" id="editMemberSection">
<option value="">Section A</option>
<option value="">Section B</option>
<option value="">Section C</option>
<option value="">Section D</option>
</select>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label for="editMemberFirstName" class="form-label">Forename</label>
<input type="text" name="editMemberFirstName" id="editMemberFirstName" class="form-control" required minlength="1" maxlength="30">
</div>
<div class="col-md-6 mb-3">
<label for="editMemberLastName" class="form-label">Surname</label>
<input type="text" name="editMemberLastName" id="editMemberLastName" class="form-control" required minlength="1" maxlength="30">
</div>
<div class="col-md-6">
<label for="editMemberTeam" class="form-label">Team</label>
<select name="editMemberTeam" id="editMemberTeam" class="form-select" required></select>
</div>
<div class="col-md-6">
<label for="editMemberPeg" class="form-label">Peg Number</label>
<input type="number" name="editMemberPeg" id="editMemberPeg" class="form-control" min="1" max="9999">
<div class="col-md-6 d-flex">
<div class="input-group">
<span class="input-group-text">
<i class="bi bi-tag"></i>
</span>
<span class="border border-end-0 d-flex align-items-center">
<small class="text-muted mx-2" style="margin-right: 1px;">Peg</small>
</span>
<input type="number" name="editMemberPeg" id="editMemberPeg" class="form-control ps-0 border-start-0 shadow-none" min="1" max="9999" value="1">
</div>
<button type="button" class="btn btn-outline-company border-secondary-subtle ms-3" data-bs-toggle="tooltip" data-bs-placement="right" data-bs-title="Find next available peg">
<i class="bi bi-arrow-clockwise"></i>
</button>
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary" id="saveEditModal">Save Changes</button>
<div class="modal-footer border-0">
<button type="button" class="btn btn-company" id="saveEditModal">Save Changes</button>
</div>
</div>
</div>
@ -151,6 +211,9 @@
{% load static %}
{% block scripts %}
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/select2@4.0.13/dist/js/select2.full.min.js"></script>
<script type="text/javascript">
const getTeamsUrl = "{% url 'get-teams' %}";
const updateMemberUrl = "{% url 'update-member' %}";

View File

@ -5,7 +5,7 @@ urlpatterns = [
path('', views.index, name='index'),
path('results/', views.results, name='results'),
path('scoreboard/', views.scoreboard, name='scoreboard'),
path('teams/', views.teams, name='teams'),
path('members/', views.teams, name='members'),
# path('bulk-peg/', views.bulk_create_pegs, name='bulk-peg'),
path('get-teams/', views.get_teams, name='get-teams'),

View File

@ -31,6 +31,12 @@ def teams(request):
# # return to previous page
# return redirect(request.META.get('HTTP_REFERER'))
def name_sort_key(section):
if len(section.name) == 1:
return (0, section.name)
else:
return (1, section.name[:-1], section.name[-1])
def get_teams(request):
"""Returns a JsonResponse containing a dictionary with a k/v pair for a list of teams.
@ -64,6 +70,9 @@ def get_teams(request):
)
groups = groups.filter(members__in=members).distinct()
if sort_groups == "section":
groups = sorted([group for group in groups], key=name_sort_key)
# Create a dictionary for the data that is JSON safe
response_data = {"groups": []}
for group in groups:
@ -86,8 +95,6 @@ def get_teams(request):
response_data["sortGroups"] = sort_groups
response_data["sortMembers"] = sort_members
print(sort_groups, sort_members)
return JsonResponse(response_data)
def update_member(request):
@ -105,7 +112,7 @@ def update_member(request):
# Get the member and team
member = Member.objects.get(id=member_id)
team = Team.objects.get(team_number=team_number)
team = Team.objects.get(name=team_number)
# Update the member
member.first_name = first

View File

@ -57,6 +57,8 @@ $(document).ready(() => {
}
});
$("select").select2({theme: "bootstrap-5", minimumResultsForSearch: -1})
// load the teams with default filters
fetchAndLoadTeams(...getFilters());
@ -213,58 +215,58 @@ function teamsLoading(show=true) {
function openEditTeamModal(teamNumber) {
// Team data
const team = $(`.team[data-number='${teamNumber}']`);
const section = team.data("section");
// // Team data
// const team = $(`.team[data-number='${teamNumber}']`);
// const section = team.data("section");
// Load data to form
$("#editTeamTitle").text(`Team ${teamNumber}`);
// // Load data to form
// $("#editTeamTitle").text(`Team ${teamNumber}`);
$("#editTeamNumber").val(teamNumber);
$('#editTeamSection').val(section);
// $("#editTeamNumber").val(teamNumber);
// $('#editTeamSection').val(section);
$("#editTeamModal").modal("show");
}
function openEditMemberModal(memberId) {
var modalTitle = "Add Member";
var first = "";
var last = "";
var teamNumber = $(".team").map(function() { return $(this).data("number"); }).get(); // TODO: shorten this
var pegNumber = 0;
var firstPlaceholder = "Forename";
var lastPlaceholder = "Surname";
// var modalTitle = "Add Member";
// var first = "";
// var last = "";
// var teamNumber = $(".team").map(function() { return $(this).data("number"); }).get(); // TODO: shorten this
// var pegNumber = 0;
// var firstPlaceholder = "Forename";
// var lastPlaceholder = "Surname";
// Member data
if (memberId !== -1) {
const member = $(`.team-member[data-member-id='${memberId}']`);
first = member.data("first");
last = member.data("last");
teamNumber = member.data("team-number");
pegNumber = member.data("peg-number");
firstPlaceholder = first;
lastPlaceholder = last;
modalTitle = "Edit: " + first + " " + last
}
// // Member data
// if (memberId !== -1) {
// const member = $(`.team-member[data-member-id='${memberId}']`);
// first = member.data("first");
// last = member.data("last");
// teamNumber = member.data("team-number");
// pegNumber = member.data("peg-number");
// firstPlaceholder = first;
// lastPlaceholder = last;
// modalTitle = "Edit: " + first + " " + last
// }
// Load teams as options
$("#editMemberTeam").html("");
$(".team").map(function() {
return $(this).data("number");
}).get().forEach((team) => {
$("#editMemberTeam").append($(`<option value='${team}'>Team ${team}</option>`));
});
// // Load teams as options
// $("#editMemberTeam").html("");
// $(".team").map(function() {
// return $(this).data("number");
// }).get().forEach((team) => {
// $("#editMemberTeam").append($(`<option value='${team}'>Team ${team}</option>`));
// });
// Load data to form
$("#editMemberName").text(modalTitle);
$("#editMemberFirstName").val(first);
$("#editMemberLastName").val(last);
$("#editMemberTeam").val(teamNumber);
$("#editMemberPeg").val(pegNumber);
// // Load data to form
// $("#editMemberName").text(modalTitle);
// $("#editMemberFirstName").val(first);
// $("#editMemberLastName").val(last);
// $("#editMemberTeam").val(teamNumber);
// $("#editMemberPeg").val(pegNumber);
$("#editMemberFirstName").attr("placeholder", firstPlaceholder);
$("#editMemberLastName").attr("placeholder", lastPlaceholder);
// $("#editMemberFirstName").attr("placeholder", firstPlaceholder);
// $("#editMemberLastName").attr("placeholder", lastPlaceholder);
$("#editMemberModal").modal("show");