From 77b8c2fc2d3f67ec154e9706174760c987215d0d Mon Sep 17 00:00:00 2001 From: Corban-Lee <77944149+XordK@users.noreply.github.com> Date: Sun, 7 May 2023 23:20:34 +0100 Subject: [PATCH] Finalisation of teams/member model. Added testing script. --- create_dummy_data.bat | 23 ++ src/mainapp/admin.py | 12 +- src/mainapp/fixtures/members_fixture.json | 302 ++++++++++++++++++ src/mainapp/fixtures/teams_fixture.json | 72 +++++ .../commands/create_members_fixture.py | 22 +- .../commands/create_teams_fixture.py | 66 ++-- src/mainapp/migrations/0001_initial.py | 13 +- src/mainapp/models.py | 40 ++- src/mainapp/templates/teams.html | 181 +---------- src/mainapp/views.py | 16 +- src/static/js/teams.js | 193 +++++++++++ 11 files changed, 696 insertions(+), 244 deletions(-) create mode 100644 create_dummy_data.bat create mode 100644 src/mainapp/fixtures/members_fixture.json create mode 100644 src/mainapp/fixtures/teams_fixture.json create mode 100644 src/static/js/teams.js diff --git a/create_dummy_data.bat b/create_dummy_data.bat new file mode 100644 index 0000000..707f121 --- /dev/null +++ b/create_dummy_data.bat @@ -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... +) diff --git a/src/mainapp/admin.py b/src/mainapp/admin.py index 7a9093b..045e89c 100644 --- a/src/mainapp/admin.py +++ b/src/mainapp/admin.py @@ -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",) diff --git a/src/mainapp/fixtures/members_fixture.json b/src/mainapp/fixtures/members_fixture.json new file mode 100644 index 0000000..776e9a9 --- /dev/null +++ b/src/mainapp/fixtures/members_fixture.json @@ -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 + } + } +] \ No newline at end of file diff --git a/src/mainapp/fixtures/teams_fixture.json b/src/mainapp/fixtures/teams_fixture.json new file mode 100644 index 0000000..f2de973 --- /dev/null +++ b/src/mainapp/fixtures/teams_fixture.json @@ -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" + } + } +] \ No newline at end of file diff --git a/src/mainapp/management/commands/create_members_fixture.py b/src/mainapp/management/commands/create_members_fixture.py index 351cdf1..39c9812 100644 --- a/src/mainapp/management/commands/create_members_fixture.py +++ b/src/mainapp/management/commands/create_members_fixture.py @@ -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) diff --git a/src/mainapp/management/commands/create_teams_fixture.py b/src/mainapp/management/commands/create_teams_fixture.py index 1211396..64da24b 100644 --- a/src/mainapp/management/commands/create_teams_fixture.py +++ b/src/mainapp/management/commands/create_teams_fixture.py @@ -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.")) diff --git a/src/mainapp/migrations/0001_initial.py b/src/mainapp/migrations/0001_initial.py index e2e00e4..6c3342c 100644 --- a/src/mainapp/migrations/0001_initial.py +++ b/src/mainapp/migrations/0001_initial.py @@ -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])), ], ), ] diff --git a/src/mainapp/models.py b/src/mainapp/models.py index 7ae25cb..89872aa 100644 --- a/src/mainapp/models.py +++ b/src/mainapp/models.py @@ -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): diff --git a/src/mainapp/templates/teams.html b/src/mainapp/templates/teams.html index eba6989..8571e34 100644 --- a/src/mainapp/templates/teams.html +++ b/src/mainapp/templates/teams.html @@ -12,7 +12,7 @@
-

Teams & Members

+

Teams & Members

@@ -38,7 +38,7 @@