Finalisation of teams/member model. Added testing script.
This commit is contained in:
parent
8afd3fa905
commit
77b8c2fc2d
23
create_dummy_data.bat
Normal file
23
create_dummy_data.bat
Normal file
@ -0,0 +1,23 @@
|
||||
@echo off
|
||||
echo The purpose of this file is to populate the database with dummy data for testing.
|
||||
set /p confirm=Do you want to continue? [y/n]
|
||||
|
||||
if /i "%confirm%"=="y" (
|
||||
cd /d %~dp0
|
||||
|
||||
if exist src\db.sqlite3 (
|
||||
del src\db.sqlite3
|
||||
)
|
||||
|
||||
call venv\Scripts\activate.bat
|
||||
|
||||
python src/manage.py migrate
|
||||
python src/manage.py create_teams_fixture 10
|
||||
python src/manage.py loaddata teams_fixture
|
||||
python src/manage.py create_members_fixture 3
|
||||
python src/manage.py loaddata members_fixture
|
||||
|
||||
deactivate
|
||||
) else (
|
||||
echo Exiting script...
|
||||
)
|
@ -2,7 +2,7 @@
|
||||
|
||||
from django.contrib import admin
|
||||
|
||||
from .models import Peg, Member, Team, Section
|
||||
from .models import Peg, Member, Team
|
||||
|
||||
|
||||
@admin.register(Peg)
|
||||
@ -33,9 +33,9 @@ class TeamAdmin(admin.ModelAdmin):
|
||||
search_fields = ("team_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 = ("character",)
|
||||
search_fields = ("character",)
|
||||
# list_display = ("character",)
|
||||
# search_fields = ("character",)
|
||||
|
302
src/mainapp/fixtures/members_fixture.json
Normal file
302
src/mainapp/fixtures/members_fixture.json
Normal file
@ -0,0 +1,302 @@
|
||||
[
|
||||
{
|
||||
"model": "mainapp.member",
|
||||
"pk": 1,
|
||||
"fields": {
|
||||
"first_name": "Michael",
|
||||
"last_name": "Jewett",
|
||||
"team": 1,
|
||||
"peg_number": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "mainapp.member",
|
||||
"pk": 2,
|
||||
"fields": {
|
||||
"first_name": "Robert",
|
||||
"last_name": "Kearns",
|
||||
"team": 1,
|
||||
"peg_number": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "mainapp.member",
|
||||
"pk": 3,
|
||||
"fields": {
|
||||
"first_name": "Jack",
|
||||
"last_name": "Colon",
|
||||
"team": 1,
|
||||
"peg_number": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "mainapp.member",
|
||||
"pk": 4,
|
||||
"fields": {
|
||||
"first_name": "Nina",
|
||||
"last_name": "Freeman",
|
||||
"team": 2,
|
||||
"peg_number": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "mainapp.member",
|
||||
"pk": 5,
|
||||
"fields": {
|
||||
"first_name": "Josephine",
|
||||
"last_name": "Mcdonald",
|
||||
"team": 2,
|
||||
"peg_number": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "mainapp.member",
|
||||
"pk": 6,
|
||||
"fields": {
|
||||
"first_name": "Don",
|
||||
"last_name": "Wrape",
|
||||
"team": 2,
|
||||
"peg_number": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "mainapp.member",
|
||||
"pk": 7,
|
||||
"fields": {
|
||||
"first_name": "Audrey",
|
||||
"last_name": "Mcguire",
|
||||
"team": 3,
|
||||
"peg_number": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "mainapp.member",
|
||||
"pk": 8,
|
||||
"fields": {
|
||||
"first_name": "Morris",
|
||||
"last_name": "Delker",
|
||||
"team": 3,
|
||||
"peg_number": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "mainapp.member",
|
||||
"pk": 9,
|
||||
"fields": {
|
||||
"first_name": "Keith",
|
||||
"last_name": "Cline",
|
||||
"team": 3,
|
||||
"peg_number": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "mainapp.member",
|
||||
"pk": 10,
|
||||
"fields": {
|
||||
"first_name": "Vincent",
|
||||
"last_name": "Alconcel",
|
||||
"team": 4,
|
||||
"peg_number": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "mainapp.member",
|
||||
"pk": 11,
|
||||
"fields": {
|
||||
"first_name": "Leroy",
|
||||
"last_name": "Gibson",
|
||||
"team": 4,
|
||||
"peg_number": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "mainapp.member",
|
||||
"pk": 12,
|
||||
"fields": {
|
||||
"first_name": "Ronald",
|
||||
"last_name": "Ross",
|
||||
"team": 4,
|
||||
"peg_number": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "mainapp.member",
|
||||
"pk": 13,
|
||||
"fields": {
|
||||
"first_name": "Dennis",
|
||||
"last_name": "Thompson",
|
||||
"team": 5,
|
||||
"peg_number": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "mainapp.member",
|
||||
"pk": 14,
|
||||
"fields": {
|
||||
"first_name": "Clarence",
|
||||
"last_name": "Nieto",
|
||||
"team": 5,
|
||||
"peg_number": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "mainapp.member",
|
||||
"pk": 15,
|
||||
"fields": {
|
||||
"first_name": "Trena",
|
||||
"last_name": "Robbins",
|
||||
"team": 5,
|
||||
"peg_number": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "mainapp.member",
|
||||
"pk": 16,
|
||||
"fields": {
|
||||
"first_name": "Karen",
|
||||
"last_name": "Vessell",
|
||||
"team": 6,
|
||||
"peg_number": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "mainapp.member",
|
||||
"pk": 17,
|
||||
"fields": {
|
||||
"first_name": "Herb",
|
||||
"last_name": "Mcgowan",
|
||||
"team": 6,
|
||||
"peg_number": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "mainapp.member",
|
||||
"pk": 18,
|
||||
"fields": {
|
||||
"first_name": "Jimmie",
|
||||
"last_name": "Rittenhouse",
|
||||
"team": 6,
|
||||
"peg_number": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "mainapp.member",
|
||||
"pk": 19,
|
||||
"fields": {
|
||||
"first_name": "Lewis",
|
||||
"last_name": "Queener",
|
||||
"team": 7,
|
||||
"peg_number": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "mainapp.member",
|
||||
"pk": 20,
|
||||
"fields": {
|
||||
"first_name": "Nancy",
|
||||
"last_name": "Merritt",
|
||||
"team": 7,
|
||||
"peg_number": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "mainapp.member",
|
||||
"pk": 21,
|
||||
"fields": {
|
||||
"first_name": "Elaine",
|
||||
"last_name": "Foss",
|
||||
"team": 7,
|
||||
"peg_number": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "mainapp.member",
|
||||
"pk": 22,
|
||||
"fields": {
|
||||
"first_name": "Natasha",
|
||||
"last_name": "Perkins",
|
||||
"team": 8,
|
||||
"peg_number": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "mainapp.member",
|
||||
"pk": 23,
|
||||
"fields": {
|
||||
"first_name": "Dean",
|
||||
"last_name": "Carroll",
|
||||
"team": 8,
|
||||
"peg_number": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "mainapp.member",
|
||||
"pk": 24,
|
||||
"fields": {
|
||||
"first_name": "Lauren",
|
||||
"last_name": "Carter",
|
||||
"team": 8,
|
||||
"peg_number": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "mainapp.member",
|
||||
"pk": 25,
|
||||
"fields": {
|
||||
"first_name": "Robert",
|
||||
"last_name": "Orr",
|
||||
"team": 9,
|
||||
"peg_number": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "mainapp.member",
|
||||
"pk": 26,
|
||||
"fields": {
|
||||
"first_name": "Deborah",
|
||||
"last_name": "Johnson",
|
||||
"team": 9,
|
||||
"peg_number": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "mainapp.member",
|
||||
"pk": 27,
|
||||
"fields": {
|
||||
"first_name": "Helen",
|
||||
"last_name": "Henry",
|
||||
"team": 9,
|
||||
"peg_number": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "mainapp.member",
|
||||
"pk": 28,
|
||||
"fields": {
|
||||
"first_name": "Linda",
|
||||
"last_name": "Armstrong",
|
||||
"team": 10,
|
||||
"peg_number": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "mainapp.member",
|
||||
"pk": 29,
|
||||
"fields": {
|
||||
"first_name": "Tricia",
|
||||
"last_name": "Charles",
|
||||
"team": 10,
|
||||
"peg_number": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "mainapp.member",
|
||||
"pk": 30,
|
||||
"fields": {
|
||||
"first_name": "Cortney",
|
||||
"last_name": "Hogan",
|
||||
"team": 10,
|
||||
"peg_number": null
|
||||
}
|
||||
}
|
||||
]
|
72
src/mainapp/fixtures/teams_fixture.json
Normal file
72
src/mainapp/fixtures/teams_fixture.json
Normal file
@ -0,0 +1,72 @@
|
||||
[
|
||||
{
|
||||
"model": "mainapp.team",
|
||||
"pk": 1,
|
||||
"fields": {
|
||||
"section_letter": "A"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "mainapp.team",
|
||||
"pk": 2,
|
||||
"fields": {
|
||||
"section_letter": "B"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "mainapp.team",
|
||||
"pk": 3,
|
||||
"fields": {
|
||||
"section_letter": "C"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "mainapp.team",
|
||||
"pk": 4,
|
||||
"fields": {
|
||||
"section_letter": "D"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "mainapp.team",
|
||||
"pk": 5,
|
||||
"fields": {
|
||||
"section_letter": "E"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "mainapp.team",
|
||||
"pk": 6,
|
||||
"fields": {
|
||||
"section_letter": "F"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "mainapp.team",
|
||||
"pk": 7,
|
||||
"fields": {
|
||||
"section_letter": "G"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "mainapp.team",
|
||||
"pk": 8,
|
||||
"fields": {
|
||||
"section_letter": "H"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "mainapp.team",
|
||||
"pk": 9,
|
||||
"fields": {
|
||||
"section_letter": "J"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "mainapp.team",
|
||||
"pk": 10,
|
||||
"fields": {
|
||||
"section_letter": "K"
|
||||
}
|
||||
}
|
||||
]
|
@ -30,25 +30,6 @@ class Command(BaseCommand):
|
||||
|
||||
self.stdout.write(self.style.SUCCESS(f"Created {num_members} members."))
|
||||
|
||||
|
||||
# create a team fixture file
|
||||
teams_fixture = []
|
||||
for team in teams:
|
||||
team_fixture = {
|
||||
"model": "mainapp.team",
|
||||
"pk": team.pk,
|
||||
"fields": {
|
||||
"team_number": team.team_number,
|
||||
"section": team.section_id
|
||||
}
|
||||
}
|
||||
teams_fixture.append(team_fixture)
|
||||
|
||||
with open("src/mainapp/fixtures/teams_fixture.json", "w") as f:
|
||||
f.write(json.dumps(teams_fixture, indent=2))
|
||||
self.stdout.write(self.style.SUCCESS("Created teams_fixture.json."))
|
||||
|
||||
|
||||
# create a members fixture file
|
||||
members_fixture = []
|
||||
for member in members:
|
||||
@ -58,7 +39,8 @@ class Command(BaseCommand):
|
||||
"fields": {
|
||||
"first_name": member.first_name,
|
||||
"last_name": member.last_name,
|
||||
"team": member.team_id
|
||||
"team": member.team_id,
|
||||
"peg_number": member.peg_number
|
||||
}
|
||||
}
|
||||
members_fixture.append(member_fixture)
|
||||
|
@ -1,6 +1,13 @@
|
||||
import json
|
||||
from string import ascii_uppercase
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
from mainapp.models import Team
|
||||
import random
|
||||
from django.db.utils import IntegrityError
|
||||
from mainapp.models import Team, BLOCKED_SECTION_LETTERS
|
||||
|
||||
for char in BLOCKED_SECTION_LETTERS:
|
||||
ascii_uppercase = ascii_uppercase.replace(char.upper(), "")
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Creates a fixture file for Team objects"
|
||||
@ -10,28 +17,49 @@ class Command(BaseCommand):
|
||||
|
||||
def handle(self, *args, **options):
|
||||
num_teams = options["num_teams"]
|
||||
limit = len(ascii_uppercase)
|
||||
|
||||
if num_teams > limit:
|
||||
self.stdout.write(self.style.ERROR(f"Number of teams is too large [{num_teams}/{limit}]."))
|
||||
return
|
||||
|
||||
# this code is so shit
|
||||
# reminder to please rewrite this please
|
||||
teams = []
|
||||
for i in range(num_teams):
|
||||
team = Team.objects.create()
|
||||
iteration = 0
|
||||
errors = 0
|
||||
while iteration < num_teams:
|
||||
try:
|
||||
team = Team.objects.create(section_letter=ascii_uppercase[iteration])
|
||||
except IntegrityError as err:
|
||||
self.stdout.write(self.style.ERROR(err))
|
||||
errors += 1
|
||||
if errors > limit:
|
||||
break # heavy nesting: not good
|
||||
|
||||
iteration += 1
|
||||
teams.append(team)
|
||||
|
||||
if not teams: # TODO: error message
|
||||
self.stdout.write(self.style.ERROR(f"Couldn't make teams -> ask corban because im too lazy to write an error message right now."))
|
||||
return
|
||||
|
||||
for team in teams:
|
||||
self.stdout.write(self.style.SUCCESS(f"Created team {team.team_number}"))
|
||||
|
||||
filename = f"src/mainapp/fixtures/teams_{num_teams}.json"
|
||||
with open(filename, "w") as f:
|
||||
self.stdout.write(f"Writing fixture to {filename}")
|
||||
f.write("[\n")
|
||||
for i, team in enumerate(teams):
|
||||
f.write(" {\n")
|
||||
f.write(f' "model": "mainapp.team",\n')
|
||||
f.write(f' "pk": {team.team_number},\n')
|
||||
f.write(' "fields": {}\n')
|
||||
f.write(" }")
|
||||
if i < len(teams) - 1:
|
||||
f.write(",")
|
||||
f.write("\n")
|
||||
f.write("]\n")
|
||||
filename = f"src/mainapp/fixtures/teams_fixture.json"
|
||||
|
||||
self.stdout.write(self.style.SUCCESS("Fixture created successfully"))
|
||||
teams_fixture = []
|
||||
for team in teams:
|
||||
team_fixture = {
|
||||
"model": "mainapp.team",
|
||||
"pk": team.pk,
|
||||
"fields": {
|
||||
"section_letter": team.section_letter
|
||||
}
|
||||
}
|
||||
teams_fixture.append(team_fixture)
|
||||
|
||||
with open(filename, "w") as f:
|
||||
f.write(json.dumps(teams_fixture, indent=2))
|
||||
self.stdout.write(self.style.SUCCESS("Created teams_fixture.json."))
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Generated by Django 4.1.5 on 2023-05-06 15:53
|
||||
# Generated by Django 4.1.5 on 2023-05-07 21:57
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
@ -19,17 +19,11 @@ class Migration(migrations.Migration):
|
||||
('peg_number', mainapp.models.ReusableAutoField(default=mainapp.models.ReusableAutoField.get_default, editable=False, primary_key=True, serialize=False)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Section',
|
||||
fields=[
|
||||
('character', models.CharField(max_length=1, primary_key=True, serialize=False, validators=[mainapp.models.validate_section_character])),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Team',
|
||||
fields=[
|
||||
('team_number', mainapp.models.ReusableAutoField(default=mainapp.models.ReusableAutoField.get_default, editable=False, primary_key=True, serialize=False)),
|
||||
('section', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='mainapp.section')),
|
||||
('section_letter', models.CharField(choices=[('A', 'A'), ('B', 'B'), ('C', 'C'), ('D', 'D'), ('E', 'E'), ('F', 'F'), ('G', 'G'), ('H', 'H'), ('I', 'I'), ('J', 'J'), ('K', 'K'), ('L', 'L'), ('M', 'M'), ('N', 'N'), ('O', 'O'), ('P', 'P'), ('Q', 'Q'), ('R', 'R'), ('S', 'S'), ('T', 'T'), ('U', 'U'), ('V', 'V'), ('W', 'W'), ('X', 'X'), ('Y', 'Y'), ('Z', 'Z')], max_length=1, unique=True)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
@ -38,7 +32,8 @@ class Migration(migrations.Migration):
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('first_name', models.CharField(max_length=255)),
|
||||
('last_name', models.CharField(max_length=255)),
|
||||
('team', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='mainapp.team', validators=[mainapp.models.validate_team_size])),
|
||||
('peg_number', models.PositiveIntegerField(null=True, unique=True)),
|
||||
('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])),
|
||||
],
|
||||
),
|
||||
]
|
||||
|
@ -1,13 +1,14 @@
|
||||
from string import ascii_uppercase
|
||||
|
||||
from django.db import models
|
||||
from django.core.exceptions import ValidationError
|
||||
|
||||
# products/models.py
|
||||
from django.db import models
|
||||
|
||||
|
||||
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.
|
||||
@ -50,8 +51,20 @@ 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')
|
||||
peg_number = models.PositiveIntegerField(null=True, editable=True, unique=True)
|
||||
# peg = models.OneToOneField("Peg", on_delete=models.SET_NULL, null=True, blank=True)
|
||||
|
||||
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.team_number}))"
|
||||
|
||||
@ -60,11 +73,16 @@ class Member(models.Model):
|
||||
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"""
|
||||
|
||||
team_number = ReusableAutoField(primary_key=True, default=ReusableAutoField.get_default, editable=False)
|
||||
section = models.OneToOneField("Section", on_delete=models.SET_NULL, null=True, blank=True)
|
||||
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.team_number}"
|
||||
@ -84,17 +102,17 @@ def validate_section_character(value):
|
||||
raise ValidationError(f"The character <{value}> is a prohibited character.")
|
||||
|
||||
|
||||
class Section(models.Model):
|
||||
"""Represents a section of the scoreboard"""
|
||||
# class Section(models.Model):
|
||||
# """Represents a section of the scoreboard"""
|
||||
|
||||
# character field stores a single character but doesnt allow for 'I' or 'i'
|
||||
character = models.CharField(primary_key=True, max_length=1, validators=(validate_section_character, ))
|
||||
# # character field stores a single character but doesnt allow for 'I' or 'i'
|
||||
# character = models.CharField(primary_key=True, max_length=1, validators=(validate_section_character, ))
|
||||
|
||||
def clean(self):
|
||||
self.character = self.character.upper()
|
||||
# def clean(self):
|
||||
# self.character = self.character.upper()
|
||||
|
||||
def __str__(self):
|
||||
return f"Section {self.character}"
|
||||
# def __str__(self):
|
||||
# return f"Section {self.character}"
|
||||
|
||||
|
||||
#class Scoreboard(models.Model):
|
||||
|
@ -12,7 +12,7 @@
|
||||
<div class="container my-4 p-4 pb-0 bg-body-secondary rounded">
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-6 col-xl-8">
|
||||
<h3 class="mb-0">Teams & Members</h3>
|
||||
<h3>Teams & Members</h3>
|
||||
</div>
|
||||
<div class="col-md-6 col-xl-4">
|
||||
<div class="input-group mb-4 mb-md-0">
|
||||
@ -38,7 +38,7 @@
|
||||
|
||||
<!-- Edit Modal -->
|
||||
<div class="modal fade" id="editMemberModal" tabindex="-1">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h3 class="modal-title fs-5" id="editMemberName">Member Name Here</h3>
|
||||
@ -69,179 +69,12 @@
|
||||
</div>
|
||||
{% endblock content %}
|
||||
|
||||
{% load static %}
|
||||
{% block scripts %}
|
||||
<script type="text/javascript">
|
||||
jQuery.expr[':'].icontains = function(a, i, m) {
|
||||
return jQuery(a).text().toUpperCase()
|
||||
.indexOf(m[3].toUpperCase()) >= 0;
|
||||
};
|
||||
|
||||
$(document).ready(() => {
|
||||
teamsLoading(true); // show the loading icon
|
||||
fetchAndLoadTeams(); // load the teams
|
||||
|
||||
var searchTimeout = null;
|
||||
|
||||
$("#search").keyup(() => {
|
||||
clearTimeout(searchTimeout);
|
||||
teamsLoading(true);
|
||||
|
||||
searchTimeout = setTimeout(() => {
|
||||
fetchAndLoadTeams($("#search").val());
|
||||
}, 500)
|
||||
});
|
||||
|
||||
$("#searchButton").on("click", () => {
|
||||
fetchAndLoadTeams($("#search").val());
|
||||
});
|
||||
})
|
||||
|
||||
/**
|
||||
* Returns bool if the string is empty or only contains whitespace
|
||||
*
|
||||
* **/
|
||||
function isEmptyOrSpaces(string) {
|
||||
return string === null || string.match(/^ *$/) !== null;
|
||||
}
|
||||
|
||||
function loadTeams(teams, highlightText="") {
|
||||
$("#teamsContainer").html(""); // Clear the previous listed teams
|
||||
|
||||
if (teams.length < 1) {
|
||||
$("#teamsNotFound").show();
|
||||
}
|
||||
|
||||
// Iterate over and add each team
|
||||
teams.forEach((team, index) => {
|
||||
$("#teamsContainer").append(
|
||||
`<div class='col-12 col-md-6 col-xl-4 mb-4'>
|
||||
<div
|
||||
class='team p-4 bg-body-tertiary rounded h-100 fluid-hover-zoom shadow-sm shadow-on-hover'
|
||||
data-number='${team.team_number}'
|
||||
>
|
||||
<h4>${team.team_number}</h4>
|
||||
<ul class='list-unstyled ul-cols-2 team-members'>
|
||||
</ul>
|
||||
</div>
|
||||
</div>`
|
||||
);
|
||||
|
||||
// Whilimage.pnge we have the team, iterate over and add it's members
|
||||
team.members.forEach((member) => {
|
||||
const fullname = member.first + " " + member.last;
|
||||
|
||||
$("#teamsContainer").find(".team-members").last().append(
|
||||
`<li>
|
||||
<span
|
||||
type='button'
|
||||
class='team-member'
|
||||
data-first='${member.first}'
|
||||
data-last='${member.last}'
|
||||
data-member-id='${member.id}'
|
||||
data-team-number='${member.team}'
|
||||
>
|
||||
${fullname}
|
||||
</span>
|
||||
</li>`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// Highlight all instances where the text matches the search critera
|
||||
if (!isEmptyOrSpaces(highlightText)) {
|
||||
$("#teamsContainer").find(`.team-member:icontains('${highlightText}')`).addClass("border-bottom text-primary fw-bold");
|
||||
}
|
||||
|
||||
$(".team-member").on("click", function() {
|
||||
openEditModal($(this).data("member-id"));
|
||||
});
|
||||
}
|
||||
|
||||
function fetchAndLoadTeams(search="") {
|
||||
$.ajax({
|
||||
url: "{% url 'get-teams' %}",
|
||||
type: "post",
|
||||
data: {
|
||||
"csrfmiddlewaretoken": "{{ csrf_token }}",
|
||||
"search": !isEmptyOrSpaces(search) ? search : null
|
||||
},
|
||||
error: (xhr, textStatus, errorThrown) => { alert(errorThrown); },
|
||||
success: (result) => {
|
||||
teamsLoading(false);
|
||||
loadTeams(result.teams, search);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function teamsLoading(show=true) {
|
||||
$("#teamsNotFound").hide()
|
||||
|
||||
if (show) {
|
||||
$("#teamsContainer").hide();
|
||||
$("#teamsLoadingSpinner").show();
|
||||
return
|
||||
}
|
||||
|
||||
$("#teamsContainer").show();
|
||||
$("#teamsLoadingSpinner").hide();
|
||||
}
|
||||
|
||||
function openEditModal(memberId) {
|
||||
|
||||
// Member data
|
||||
const member = $(`.team-member[data-member-id='${memberId}']`);
|
||||
const first = member.data("first");
|
||||
const last = member.data("last");
|
||||
const teamNumber = member.data("team-number");
|
||||
|
||||
// Load teams as options
|
||||
$("#editMemberTeam").html("");
|
||||
$(".team").map(function() {
|
||||
return $(this).data("number");
|
||||
}).get().forEach((team) => {
|
||||
$("#editMemberTeam").append($(`<option value='${team}'>${team}</option>`));
|
||||
});
|
||||
|
||||
// Load data to form
|
||||
$("#editMemberName").text(first + " " + last);
|
||||
$("#editMemberFirstName").val(first);
|
||||
$("#editMemberLastName").val(last);
|
||||
$("#editMemberTeam").val(teamNumber);
|
||||
|
||||
$("#editMemberModal").modal("show");
|
||||
|
||||
// Update the submit button
|
||||
$("#saveEditModal").off("click").on("click", () => { saveEditModal(memberId); });
|
||||
}
|
||||
|
||||
function saveEditModal(memberId) {
|
||||
|
||||
// Grab the updated data
|
||||
const first = $("#editMemberFirstName").val();
|
||||
const last = $("#editMemberLastName").val();
|
||||
const teamNumber = $("#editMemberTeam").val();
|
||||
const search = $("#search").val();
|
||||
|
||||
teamsLoading(true);
|
||||
|
||||
$.ajax({
|
||||
url: "{% url 'update-member' %}",
|
||||
type: "post",
|
||||
data: {
|
||||
"csrfmiddlewaretoken": "{{ csrf_token }}",
|
||||
"memberId": memberId,
|
||||
"first": first,
|
||||
"last": last,
|
||||
"teamNumber": teamNumber,
|
||||
"search": !isEmptyOrSpaces(search) ? search : null
|
||||
},
|
||||
error: (xhr, textStatus, errorThrown) => { alert(errorThrown); },
|
||||
success: (result) => {
|
||||
teamsLoading(false);
|
||||
loadTeams(result.teams, search);
|
||||
$("#editMemberModal").modal("hide");
|
||||
}
|
||||
});
|
||||
}
|
||||
const getTeamsUrl = "{% url 'get-teams' %}";
|
||||
const updateMemberUrl = "{% url 'update-member' %}";
|
||||
const csrfMiddlewareToken = "{{ csrf_token }}";
|
||||
</script>
|
||||
<script src="{% static 'js/teams.js' %}"></script>
|
||||
{% endblock scripts %}
|
@ -4,7 +4,7 @@ from functools import reduce
|
||||
|
||||
from django.shortcuts import render, redirect
|
||||
from django.http import JsonResponse
|
||||
from django.db.models import Q
|
||||
from django.db.models import Q, Case, When, Value, IntegerField
|
||||
|
||||
from .models import Peg, Team, Member
|
||||
|
||||
@ -58,17 +58,23 @@ def get_teams(request):
|
||||
]
|
||||
)
|
||||
)
|
||||
teams = teams.filter(members__in=members)
|
||||
teams = teams.filter(members__in=members).distinct()
|
||||
|
||||
# Create a dictionary for the data that is JSON safe
|
||||
response_data = {"teams": []}
|
||||
for team in teams:
|
||||
team_data = {
|
||||
"team_number": team.team_number,
|
||||
"section": team.section.name if team.section else None,
|
||||
"section_letter": team.section_letter if team.section_letter else None,
|
||||
"members": [
|
||||
{"first": member.first_name, "last": member.last_name, "id": member.id, "team": team.team_number}
|
||||
for member in team.members.all()
|
||||
{
|
||||
"first": member.first_name,
|
||||
"last": member.last_name,
|
||||
"id": member.id,
|
||||
"team": team.team_number,
|
||||
"peg": member.peg_number
|
||||
}
|
||||
for member in team.members.order_by("peg_number").all()
|
||||
]
|
||||
}
|
||||
response_data["teams"].append(team_data)
|
||||
|
193
src/static/js/teams.js
Normal file
193
src/static/js/teams.js
Normal file
@ -0,0 +1,193 @@
|
||||
jQuery.expr[':'].icontains = function(a, i, m) {
|
||||
return jQuery(a).text().toUpperCase()
|
||||
.indexOf(m[3].toUpperCase()) >= 0;
|
||||
};
|
||||
|
||||
$(document).ready(() => {
|
||||
|
||||
// Enable all tooltips on the page
|
||||
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]')
|
||||
const tooltipList = [...tooltipTriggerList].map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl))
|
||||
|
||||
teamsLoading(true); // show the loading icon
|
||||
fetchAndLoadTeams(); // load the teams
|
||||
|
||||
var searchTimeout = null;
|
||||
|
||||
$("#search").keyup(() => {
|
||||
clearTimeout(searchTimeout);
|
||||
teamsLoading(true);
|
||||
|
||||
searchTimeout = setTimeout(() => {
|
||||
fetchAndLoadTeams($("#search").val());
|
||||
}, 500)
|
||||
});
|
||||
|
||||
$("#searchButton").on("click", () => {
|
||||
fetchAndLoadTeams($("#search").val());
|
||||
});
|
||||
})
|
||||
|
||||
/**
|
||||
* Returns bool if the string is empty or only contains whitespace
|
||||
*
|
||||
* **/
|
||||
function isEmptyOrSpaces(string) {
|
||||
return string === null || string.match(/^ *$/) !== null;
|
||||
}
|
||||
|
||||
function loadTeams(teams, highlightText="") {
|
||||
$("#teamsContainer").html(""); // Clear the previous listed teams
|
||||
|
||||
if (teams.length < 1) {
|
||||
$("#teamsNotFound").show();
|
||||
}
|
||||
|
||||
// Iterate over and add each team
|
||||
teams.forEach((team) => {
|
||||
$("#teamsContainer").append(
|
||||
`<div class='col-12 col-md-6 col-xl-4 mb-4'>
|
||||
<div
|
||||
class='team p-4 bg-body-tertiary rounded h-100 fluid-hover-zoom shadow-sm shadow-on-hover'
|
||||
data-number='${team.team_number}'>
|
||||
<h4>
|
||||
<span class='fs-6'>TEAM</span>
|
||||
${team.team_number}
|
||||
<span class='fs-5 text-secondary'>
|
||||
<span class='fs-6'>SECTION</span>
|
||||
${team.section_letter}
|
||||
</span>
|
||||
</h4>
|
||||
<ul class='list-unstyled team-members mt-3'>
|
||||
</ul>
|
||||
</div>
|
||||
</div>`
|
||||
);
|
||||
|
||||
// Whilimage.pnge we have the team, iterate over and add it's members
|
||||
team.members.forEach((member) => {
|
||||
const fullname = member.first + " " + member.last;
|
||||
|
||||
$("#teamsContainer").find(".team-members").last().append(
|
||||
`<li class='bg-body-secondary mb-3 rounded w-100'>
|
||||
<div
|
||||
type='button'
|
||||
class='team-member px-3 py-2 d-flex'
|
||||
data-first='${member.first}'
|
||||
data-last='${member.last}'
|
||||
data-member-id='${member.id}'
|
||||
data-team-number='${member.team}'>
|
||||
<span class='text-secondary me-2'>
|
||||
${member.peg}
|
||||
</span>
|
||||
<span class='team-member-fullname'>
|
||||
${fullname}
|
||||
</span>
|
||||
<span class='ms-auto text-secondary'>
|
||||
<i class='bi bi-pencil'></i>
|
||||
</span>
|
||||
</div>
|
||||
</li>`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// Highlight all instances where the text matches the search critera
|
||||
if (!isEmptyOrSpaces(highlightText)) {
|
||||
$("#teamsContainer").find('.team-member-fullname').each(function() {
|
||||
var regex = new RegExp(highlightText, 'gi');
|
||||
$(this).html($(this).text().replace(regex, '<span class="text-primary fw-bold">$&</span>'));
|
||||
});
|
||||
}
|
||||
|
||||
$(".team-member").on("click", function() {
|
||||
openEditModal($(this).data("member-id"));
|
||||
});
|
||||
}
|
||||
|
||||
function fetchAndLoadTeams(search="") {
|
||||
$.ajax({
|
||||
url: getTeamsUrl,
|
||||
type: "post",
|
||||
data: {
|
||||
"csrfmiddlewaretoken": csrfMiddlewareToken,
|
||||
"search": !isEmptyOrSpaces(search) ? search : null
|
||||
},
|
||||
error: (xhr, textStatus, errorThrown) => { alert(errorThrown); },
|
||||
success: (result) => {
|
||||
teamsLoading(false);
|
||||
loadTeams(result.teams, search);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function teamsLoading(show=true) {
|
||||
$("#teamsNotFound").hide()
|
||||
|
||||
if (show) {
|
||||
$("#teamsContainer").hide();
|
||||
$("#teamsLoadingSpinner").show();
|
||||
return
|
||||
}
|
||||
|
||||
$("#teamsContainer").show();
|
||||
$("#teamsLoadingSpinner").hide();
|
||||
}
|
||||
|
||||
function openEditModal(memberId) {
|
||||
|
||||
// Member data
|
||||
const member = $(`.team-member[data-member-id='${memberId}']`);
|
||||
const first = member.data("first");
|
||||
const last = member.data("last");
|
||||
const teamNumber = member.data("team-number");
|
||||
|
||||
// Load teams as options
|
||||
$("#editMemberTeam").html("");
|
||||
$(".team").map(function() {
|
||||
return $(this).data("number");
|
||||
}).get().forEach((team) => {
|
||||
$("#editMemberTeam").append($(`<option value='${team}'>${team}</option>`));
|
||||
});
|
||||
|
||||
// Load data to form
|
||||
$("#editMemberName").text(first + " " + last);
|
||||
$("#editMemberFirstName").val(first);
|
||||
$("#editMemberLastName").val(last);
|
||||
$("#editMemberTeam").val(teamNumber);
|
||||
|
||||
$("#editMemberModal").modal("show");
|
||||
|
||||
// Update the submit button
|
||||
$("#saveEditModal").off("click").on("click", () => { saveEditModal(memberId); });
|
||||
}
|
||||
|
||||
function saveEditModal(memberId) {
|
||||
|
||||
// Grab the updated data
|
||||
const first = $("#editMemberFirstName").val();
|
||||
const last = $("#editMemberLastName").val();
|
||||
const teamNumber = $("#editMemberTeam").val();
|
||||
const search = $("#search").val();
|
||||
|
||||
teamsLoading(true);
|
||||
|
||||
$.ajax({
|
||||
url: updateMemberUrl,
|
||||
type: "post",
|
||||
data: {
|
||||
"csrfmiddlewaretoken": csrfMiddlewareToken,
|
||||
"memberId": memberId,
|
||||
"first": first,
|
||||
"last": last,
|
||||
"teamNumber": teamNumber,
|
||||
"search": !isEmptyOrSpaces(search) ? search : null
|
||||
},
|
||||
error: (xhr, textStatus, errorThrown) => { alert(errorThrown); },
|
||||
success: (result) => {
|
||||
teamsLoading(false);
|
||||
loadTeams(result.teams, search);
|
||||
$("#editMemberModal").modal("hide");
|
||||
}
|
||||
});
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user