Adding Teams & Sections is functional-ish
This commit is contained in:
parent
8dfe4eb9f2
commit
628764a338
@ -1,5 +1,7 @@
|
|||||||
"""Models for the mainapp."""
|
"""Models for the mainapp."""
|
||||||
|
|
||||||
|
from string import ascii_uppercase
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
|
|
||||||
@ -96,12 +98,65 @@ class SectionValidator:
|
|||||||
return value
|
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):
|
class Section(models.Model):
|
||||||
"""Represents a fishing area. Members can be assigned to a section,
|
"""Represents a fishing area. Members can be assigned to a section,
|
||||||
but no 2 teammates can be in the same or adjacent section."""
|
but no 2 teammates can be in the same or adjacent section."""
|
||||||
|
|
||||||
name = models.CharField(max_length=3, unique=True, null=False)
|
name = models.CharField(max_length=3, unique=True, null=False)
|
||||||
|
|
||||||
|
objects = SectionManager()
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
super().clean()
|
super().clean()
|
||||||
|
|
||||||
@ -118,52 +173,6 @@ class Section(models.Model):
|
|||||||
def get_members(self):
|
def get_members(self):
|
||||||
return Member.objects.filter(section=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
|
|
||||||
|
|
||||||
@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):
|
class Member(models.Model):
|
||||||
"""Represents a member of a team"""
|
"""Represents a member of a team"""
|
||||||
@ -192,11 +201,18 @@ class Member(models.Model):
|
|||||||
def fullname(self) -> str:
|
def fullname(self) -> str:
|
||||||
return f"{self.first_name} {self.last_name}"
|
return f"{self.first_name} {self.last_name}"
|
||||||
|
|
||||||
|
|
||||||
|
class TeamManager(models.Manager):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class Team(models.Model):
|
class Team(models.Model):
|
||||||
"""Represents a team"""
|
"""Represents a team"""
|
||||||
|
|
||||||
name = ReusableAutoField(primary_key=True, default=ReusableAutoField.get_default, editable=False)
|
name = ReusableAutoField(primary_key=True, default=ReusableAutoField.get_default, editable=False)
|
||||||
|
|
||||||
|
objects = TeamManager()
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"Team {self.name}"
|
return f"Team {self.name}"
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@
|
|||||||
</div> -->
|
</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">
|
<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">
|
<h3 class="h2 w-100">
|
||||||
Member Management
|
Angler Management
|
||||||
<div class="border-company border-bottom border-2 w-100 pt-1"></div>
|
<div class="border-company border-bottom border-2 w-100 pt-1"></div>
|
||||||
</h3>
|
</h3>
|
||||||
<p class="mt-3">
|
<p class="mt-3">
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
{% block title %}
|
{% block title %}
|
||||||
Member Management |
|
Manage Anglers |
|
||||||
{% endblock title %}
|
{% endblock title %}
|
||||||
|
|
||||||
{% block style %}
|
{% block style %}
|
||||||
@ -10,21 +11,22 @@
|
|||||||
{% endblock style %}
|
{% endblock style %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="bg-body p-4">
|
<div class="bg-body p-4 d-flex">
|
||||||
<a href="{% url 'index' %}" class="btn btn-company px-4">Back</a>
|
<a href="{% url 'index' %}" class="btn btn-company px-4">Back</a>
|
||||||
|
<img class="ms-auto" width="40" src="{% static 'img/logo.webp' %}">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="container my-4 p-4 pb-0 bg-body rounded">
|
<div class="container my-4 p-4 pb-0 bg-body rounded">
|
||||||
<div class="row mb-4">
|
<div class="row mb-4">
|
||||||
<div class="col-12 col-xl-4 d-flex">
|
<div class="col-12 col-xl-4 d-flex">
|
||||||
<h3 class="mb-3 mb-xl-0">
|
<h3 class="mb-3 mb-xl-0">
|
||||||
Member Management
|
Manage Anglers
|
||||||
<div class="border-company border-bottom border-2 w-100 pt-1"></div>
|
<div class="border-company border-bottom border-2 w-100 pt-1"></div>
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-3 col-md-6 col-xl-4 mb-3 mb-sm-0">
|
<div class="col-sm-3 col-md-6 col-xl-4 mb-3 mb-sm-0">
|
||||||
<div class="input-group justify-content-xl-end">
|
<div class="input-group justify-content-xl-end">
|
||||||
<button class="btn border-secondary-subtle btn-outline-company" id="addMember" data-bs-toggle="tooltip" data-bs-title="Add Member">
|
<button class="btn border-secondary-subtle btn-outline-company" id="addMember" data-bs-toggle="tooltip" data-bs-title="Add Angler">
|
||||||
<i class="bi bi-person"></i>
|
<i class="bi bi-person"></i>
|
||||||
</button>
|
</button>
|
||||||
<button class="btn border-secondary-subtle btn-outline-company" id="addTeam" data-bs-toggle="tooltip" data-bs-title="Add Team">
|
<button class="btn border-secondary-subtle btn-outline-company" id="addTeam" data-bs-toggle="tooltip" data-bs-title="Add Team">
|
||||||
@ -40,7 +42,7 @@
|
|||||||
<button type="button" data-bs-toggle="dropdown" class="btn btn-outline-company border-secondary-subtle">
|
<button type="button" data-bs-toggle="dropdown" class="btn btn-outline-company border-secondary-subtle">
|
||||||
<i class="bi bi-sort-up"></i>
|
<i class="bi bi-sort-up"></i>
|
||||||
</button>
|
</button>
|
||||||
<input type="search" class="form-control border-secondary-subtle shadow-none" placeholder="Search Members" id="search">
|
<input type="search" class="form-control border-secondary-subtle shadow-none" placeholder="Search Anglers" id="search">
|
||||||
<button type="button" class="btn btn-outline-company border-secondary-subtle rounded-end" id="searchButton"><i class="bi bi-search"></i></button>
|
<button type="button" class="btn btn-outline-company border-secondary-subtle rounded-end" id="searchButton"><i class="bi bi-search"></i></button>
|
||||||
<form id="sortForm">
|
<form id="sortForm">
|
||||||
<ul class="dropdown-menu py-3 text-body-secondary bg-body-tertiary border border-light-subtle shadow-sm justify-self-center mt-2" onclick="event.stopPropagation()">
|
<ul class="dropdown-menu py-3 text-body-secondary bg-body-tertiary border border-light-subtle shadow-sm justify-self-center mt-2" onclick="event.stopPropagation()">
|
||||||
@ -59,7 +61,7 @@
|
|||||||
</li>
|
</li>
|
||||||
<li class="dropdown-divider my-3 mx-4 bg-light-subtle"></li>
|
<li class="dropdown-divider my-3 mx-4 bg-light-subtle"></li>
|
||||||
<li class="px-4">
|
<li class="px-4">
|
||||||
<h3 class="h6 mb-3">Sort members by</h3>
|
<h3 class="h6 mb-3">Sort anglers by</h3>
|
||||||
<div>
|
<div>
|
||||||
<div class="d-inline-block form-check">
|
<div class="d-inline-block form-check">
|
||||||
<input type="radio" class="form-check-input" value="first_name" name="sortMembers" id="sortMembersName">
|
<input type="radio" class="form-check-input" value="first_name" name="sortMembers" id="sortMembersName">
|
||||||
@ -91,6 +93,38 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Edit Section Modal -->
|
||||||
|
<div class="modal fade" id="editSectionModal" tabindex="-1" role="dialog">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content border-0">
|
||||||
|
<div class="modal-header border-0">
|
||||||
|
<h3 class="modal-title fs-5" id="editTeamTitle">
|
||||||
|
Section Character 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">
|
||||||
|
<div class="col-md-6 d-flex">
|
||||||
|
<div class="input-group">
|
||||||
|
<span class="input-group-text" data-bs-toggle="tooltip" data-bs-placement="left" data-bs-title="Section Character">
|
||||||
|
<i class="bi bi-layers"></i>
|
||||||
|
</span>
|
||||||
|
<input type="text" name="editSectionName" id="editSectionName" class="form-control" minlength="1" maxlength="3" value="A">
|
||||||
|
</div>
|
||||||
|
<button type="button" id="editSectionNameButton" class="btn btn-outline-company border-secondary-subtle ms-3" data-bs-toggle="tooltip" data-bs-placement="right" data-bs-title="Find next available character">
|
||||||
|
<i class="bi bi-arrow-clockwise"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 mt-2 text-danger" id="editSectionNameError" style="display: hidden"></div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer border-0">
|
||||||
|
<button type="button" class="btn btn-company px-4" id="saveEditSectionModal">Save</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Edit Team Modal -->
|
<!-- Edit Team Modal -->
|
||||||
<div class="modal fade" id="editTeamModal" tabindex="-1" role="dialog">
|
<div class="modal fade" id="editTeamModal" tabindex="-1" role="dialog">
|
||||||
<div class="modal-dialog">
|
<div class="modal-dialog">
|
||||||
@ -103,32 +137,21 @@
|
|||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="close"></button>
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="close"></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6 d-flex">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<span class="input-group-text">
|
<span class="input-group-text" data-bs-toggle="tooltip" data-bs-placement="left" data-bs-title="Team number">
|
||||||
<i class="bi bi-tag"></i>
|
<i class="bi bi-tag"></i>
|
||||||
</span>
|
</span>
|
||||||
<div class="form-floating">
|
<input type="number" name="editTeamNumber" id="editTeamNumber" class="form-control" min="1" max="9999" value="1">
|
||||||
<input type="number" class="form-control" name="editTeamName" id="editTeamName">
|
|
||||||
<label for="editTeamName">Number</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<button type="button" id="editTeamNumberButton" class="btn btn-outline-company border-secondary-subtle ms-3" data-bs-toggle="tooltip" data-bs-placement="right" data-bs-title="Find next available number">
|
||||||
|
<i class="bi bi-arrow-clockwise"></i>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<!-- <form id="editTeamForm">
|
<div class="col-md-6 mt-2 text-danger" id="editTeamNumberError" style="display: hidden"></div>
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-6 mb-3 mb-md-0">
|
|
||||||
<label for="editTeamNumber" class="form-label">Team Number</label>
|
|
||||||
<input type="number" name="editTeamNumber" class="form-control" id="editTeamNumber">
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6">
|
|
||||||
<label for="editTeamSection" class="form-label">Section Letter</label>
|
|
||||||
<input type="text" name="editTeamSection" class="form-control" id="editTeamSection">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form> -->
|
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer border-0">
|
<div class="modal-footer border-0">
|
||||||
<button type="button" class="btn btn-company" id="saveEditTeamModal">Save Changes</button>
|
<button type="button" class="btn btn-company px-4" id="saveEditTeamModal">Save</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -140,7 +163,7 @@
|
|||||||
<div class="modal-content border-0">
|
<div class="modal-content border-0">
|
||||||
<div class="modal-header border-0">
|
<div class="modal-header border-0">
|
||||||
<h3 class="modal-title fs-5 color" id="editMemberName">
|
<h3 class="modal-title fs-5 color" id="editMemberName">
|
||||||
Member Name Here
|
Angler Name Here
|
||||||
<div class="border-company border-bottom border-2 w-100 pt-1"></div>
|
<div class="border-company border-bottom border-2 w-100 pt-1"></div>
|
||||||
</h3>
|
</h3>
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="close"></button>
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="close"></button>
|
||||||
@ -162,7 +185,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="input-group mb-3">
|
<div class="input-group mb-3">
|
||||||
<span class="input-group-text">
|
<span class="input-group-text" data-bs-toggle="tooltip" data-bs-placement="left" data-bs-title="Team">
|
||||||
<i class="bi bi-people"></i>
|
<i class="bi bi-people"></i>
|
||||||
</span>
|
</span>
|
||||||
<select class="form-select" name="editMemberTeam" id="editMemberTeam">
|
<select class="form-select" name="editMemberTeam" id="editMemberTeam">
|
||||||
@ -173,7 +196,7 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="input-group mb-3">
|
<div class="input-group mb-3">
|
||||||
<span class="input-group-text">
|
<span class="input-group-text" data-bs-toggle="tooltip" data-bs-placement="left" data-bs-title="Section">
|
||||||
<i class="bi bi-layers"></i>
|
<i class="bi bi-layers"></i>
|
||||||
</span>
|
</span>
|
||||||
<select class="form-select" name="editMemberSection" id="editMemberSection">
|
<select class="form-select" name="editMemberSection" id="editMemberSection">
|
||||||
@ -186,13 +209,10 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-6 d-flex">
|
<div class="col-md-6 d-flex">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<span class="input-group-text">
|
<span class="input-group-text" data-bs-toggle="tooltip" data-bs-placement="left" data-bs-title="Peg Number">
|
||||||
<i class="bi bi-tag"></i>
|
<i class="bi bi-tag"></i>
|
||||||
</span>
|
</span>
|
||||||
<span class="border border-end-0 d-flex align-items-center">
|
<input type="number" name="editMemberPeg" id="editMemberPeg" class="form-control" min="1" max="9999" value="1">
|
||||||
<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>
|
</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">
|
<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>
|
<i class="bi bi-arrow-clockwise"></i>
|
||||||
@ -202,14 +222,13 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer border-0">
|
<div class="modal-footer border-0">
|
||||||
<button type="button" class="btn btn-company" id="saveEditModal">Save Changes</button>
|
<button type="button" class="btn btn-company px-4" id="saveEditModal">Save</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|
||||||
{% load static %}
|
|
||||||
{% block scripts %}
|
{% 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.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 src="https://cdn.jsdelivr.net/npm/select2@4.0.13/dist/js/select2.full.min.js"></script>
|
||||||
@ -217,6 +236,9 @@
|
|||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
const getTeamsUrl = "{% url 'get-teams' %}";
|
const getTeamsUrl = "{% url 'get-teams' %}";
|
||||||
const updateMemberUrl = "{% url 'update-member' %}";
|
const updateMemberUrl = "{% url 'update-member' %}";
|
||||||
|
const updateSectionUrl = "{% url 'update-section' %}"
|
||||||
|
const updateTeamUrl = "{% url 'update-team' %}"
|
||||||
|
const getNextIdentifierUrl = "{% url 'get-next-identifier' %}"
|
||||||
const csrfMiddlewareToken = "{{ csrf_token }}";
|
const csrfMiddlewareToken = "{{ csrf_token }}";
|
||||||
</script>
|
</script>
|
||||||
<script src="{% static 'js/teams.js' %}"></script>
|
<script src="{% static 'js/teams.js' %}"></script>
|
||||||
|
@ -9,5 +9,8 @@ urlpatterns = [
|
|||||||
|
|
||||||
# path('bulk-peg/', views.bulk_create_pegs, name='bulk-peg'),
|
# path('bulk-peg/', views.bulk_create_pegs, name='bulk-peg'),
|
||||||
path('get-teams/', views.get_teams, name='get-teams'),
|
path('get-teams/', views.get_teams, name='get-teams'),
|
||||||
path('update-member/', views.update_member, name='update-member')
|
path('update-member/', views.update_member, name='update-member'),
|
||||||
|
path('update-section/', views.update_section, name='update-section'),
|
||||||
|
path('update-team/', views.update_team, name='update-team'),
|
||||||
|
path("get-next-identifier/", views.get_next_identifier, name='get-next-identifier')
|
||||||
]
|
]
|
@ -5,8 +5,9 @@ from functools import reduce
|
|||||||
from django.shortcuts import render, redirect
|
from django.shortcuts import render, redirect
|
||||||
from django.http import JsonResponse
|
from django.http import JsonResponse
|
||||||
from django.db.models import Q, Case, When, Value, IntegerField
|
from django.db.models import Q, Case, When, Value, IntegerField
|
||||||
|
from django.db.utils import IntegrityError
|
||||||
|
|
||||||
from .models import Team, Member, Section
|
from .models import Team, Member, Section, SectionManager, ReusableAutoField, SectionValidator
|
||||||
|
|
||||||
|
|
||||||
def index(request):
|
def index(request):
|
||||||
@ -37,7 +38,7 @@ def name_sort_key(section):
|
|||||||
else:
|
else:
|
||||||
return (1, section.name[:-1], section.name[-1])
|
return (1, section.name[:-1], section.name[-1])
|
||||||
|
|
||||||
def get_teams(request):
|
def get_teams(request, **kwargs):
|
||||||
"""Returns a JsonResponse containing a dictionary with a k/v pair for a list of teams.
|
"""Returns a JsonResponse containing a dictionary with a k/v pair for a list of teams.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -95,6 +96,9 @@ def get_teams(request):
|
|||||||
response_data["sortGroups"] = sort_groups
|
response_data["sortGroups"] = sort_groups
|
||||||
response_data["sortMembers"] = sort_members
|
response_data["sortMembers"] = sort_members
|
||||||
|
|
||||||
|
for key, value in kwargs.items():
|
||||||
|
response_data[key] = value
|
||||||
|
|
||||||
return JsonResponse(response_data)
|
return JsonResponse(response_data)
|
||||||
|
|
||||||
def update_member(request):
|
def update_member(request):
|
||||||
@ -123,3 +127,99 @@ def update_member(request):
|
|||||||
member.save()
|
member.save()
|
||||||
|
|
||||||
return get_teams(request)
|
return get_teams(request)
|
||||||
|
|
||||||
|
def update_section(request):
|
||||||
|
"""Update a section, returns JsonResponse with updated teams data."""
|
||||||
|
|
||||||
|
if not request.POST:
|
||||||
|
return
|
||||||
|
|
||||||
|
section_id = request.POST.get("sectionId")
|
||||||
|
section_name = request.POST.get("sectionName")
|
||||||
|
|
||||||
|
validator = SectionValidator()
|
||||||
|
if not validator.is_valid(section_name):
|
||||||
|
json_response = get_teams(request, form_errors={
|
||||||
|
"editSectionName": "This is an invalid section"
|
||||||
|
})
|
||||||
|
return json_response
|
||||||
|
|
||||||
|
if section_id == "-1":
|
||||||
|
section = Section(name=section_name)
|
||||||
|
else:
|
||||||
|
section = Section.objects.get(id=section_id)
|
||||||
|
section.name = section_name
|
||||||
|
|
||||||
|
try:
|
||||||
|
section.save()
|
||||||
|
except IntegrityError:
|
||||||
|
json_response = get_teams(request, form_errors={
|
||||||
|
"editSectionName": "A Section with this character already exists"
|
||||||
|
})
|
||||||
|
return json_response
|
||||||
|
|
||||||
|
return get_teams(request) # returns jsonresponse with new details
|
||||||
|
|
||||||
|
def update_team(request):
|
||||||
|
"""Update a team, returns a JsonResponse with updated teams data."""
|
||||||
|
|
||||||
|
if not request.POST:
|
||||||
|
return
|
||||||
|
|
||||||
|
team_id = request.POST.get("teamId")
|
||||||
|
team_number = request.POST.get("teamNumber")
|
||||||
|
|
||||||
|
try:
|
||||||
|
if team_id == "-1":
|
||||||
|
team = Team.objects.create(name=team_number)
|
||||||
|
else:
|
||||||
|
team = Team.objects.get(id=team_id)
|
||||||
|
team.name = team_number
|
||||||
|
team.save()
|
||||||
|
except IntegrityError as error:
|
||||||
|
json_response = get_teams(request, form_errors={
|
||||||
|
"editTeamNumber": "A Team with this number already exists"
|
||||||
|
})
|
||||||
|
return json_response
|
||||||
|
|
||||||
|
return get_teams(request)
|
||||||
|
|
||||||
|
def get_next_peg() -> int:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_next_section() -> str:
|
||||||
|
|
||||||
|
section_name = SectionManager.get_max_section()
|
||||||
|
return SectionManager.find_next_section(section_name)
|
||||||
|
|
||||||
|
|
||||||
|
def get_next_team() -> int:
|
||||||
|
|
||||||
|
field = ReusableAutoField
|
||||||
|
field.model = Team
|
||||||
|
|
||||||
|
return field().get_default()
|
||||||
|
|
||||||
|
|
||||||
|
def get_next_identifier(request):
|
||||||
|
"""Get the next available identifer (peg, section character, etc.) for an object."""
|
||||||
|
|
||||||
|
if not request.POST:
|
||||||
|
return
|
||||||
|
|
||||||
|
item = request.POST.get("item")
|
||||||
|
|
||||||
|
match item:
|
||||||
|
case "member_peg":
|
||||||
|
result = get_next_peg()
|
||||||
|
|
||||||
|
case "section_name":
|
||||||
|
result = get_next_section()
|
||||||
|
|
||||||
|
case "team_number":
|
||||||
|
result = get_next_team()
|
||||||
|
|
||||||
|
case _:
|
||||||
|
raise ValueError(f"Bad identifier item: {item}")
|
||||||
|
|
||||||
|
return JsonResponse({"identifier": result})
|
||||||
|
BIN
src/static/img/logo.webp
Normal file
BIN
src/static/img/logo.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 28 KiB |
@ -57,6 +57,39 @@ $(document).ready(() => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$("#editSectionNameButton").on("click", () => {
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: getNextIdentifierUrl,
|
||||||
|
type: "post",
|
||||||
|
data: {
|
||||||
|
"csrfmiddlewaretoken": csrfMiddlewareToken,
|
||||||
|
"item": "section_name"
|
||||||
|
},
|
||||||
|
error: (xhr, textStatus, errorThrown) => { alert(xhr + " " + textStatus + " " + errorThrown); },
|
||||||
|
success: (result) => {
|
||||||
|
$("#editSectionName").val(result.identifier)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#editTeamNumberButton").on("click", () => {
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: getNextIdentifierUrl,
|
||||||
|
type: "post",
|
||||||
|
data: {
|
||||||
|
"csrfmiddlewaretoken": csrfMiddlewareToken,
|
||||||
|
"item": "team_number"
|
||||||
|
},
|
||||||
|
error: (xhr, textStatus, errorThrown) => { alert(xhr + " " + textStatus + " " + errorThrown); },
|
||||||
|
success: (result) => {
|
||||||
|
$("#editTeamNumber").val(result.identifier)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
$("select").select2({theme: "bootstrap-5", minimumResultsForSearch: -1})
|
$("select").select2({theme: "bootstrap-5", minimumResultsForSearch: -1})
|
||||||
|
|
||||||
// load the teams with default filters
|
// load the teams with default filters
|
||||||
@ -180,6 +213,10 @@ function loadTeams(groups, highlightText="", groupType) {
|
|||||||
$("#addMember").on("click", () => {
|
$("#addMember").on("click", () => {
|
||||||
openEditMemberModal(-1);
|
openEditMemberModal(-1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$("#addSection").on("click", () => {
|
||||||
|
openEditSectionModal(-1);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function fetchAndLoadTeams(search="", sortGroups="", sortMembers="") {
|
function fetchAndLoadTeams(search="", sortGroups="", sortMembers="") {
|
||||||
@ -213,19 +250,88 @@ function teamsLoading(show=true) {
|
|||||||
$("#teamsLoadingSpinner").hide();
|
$("#teamsLoadingSpinner").hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
function openEditTeamModal(teamNumber) {
|
function openEditSectionModal(sectionId) {
|
||||||
|
|
||||||
// // Team data
|
$("#editSectionNameError").hide();
|
||||||
// const team = $(`.team[data-number='${teamNumber}']`);
|
$("#editSectionModal").modal("show");
|
||||||
// const section = team.data("section");
|
|
||||||
|
|
||||||
// // Load data to form
|
$("#saveEditSectionModal").off("click").on("click", () => {
|
||||||
// $("#editTeamTitle").text(`Team ${teamNumber}`);
|
|
||||||
|
|
||||||
// $("#editTeamNumber").val(teamNumber);
|
if(!$("#editSectionName").val().match(/^[A-Za-z]+$/)) {
|
||||||
// $('#editTeamSection').val(section);
|
$("#editSectionName").focus();
|
||||||
|
$("#editSectionNameError").text("Invalid section character").show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
teamsLoading(true);
|
||||||
|
const [search, sortGroups, sortMembers] = getFilters();
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: updateSectionUrl,
|
||||||
|
type: "post",
|
||||||
|
data: {
|
||||||
|
"csrfmiddlewaretoken": csrfMiddlewareToken,
|
||||||
|
"sectionId": sectionId,
|
||||||
|
"sectionName": $("#editSectionName").val(),
|
||||||
|
"search": search,
|
||||||
|
"sortGroups": sortGroups,
|
||||||
|
"sortMembers": sortMembers
|
||||||
|
},
|
||||||
|
error: (xhr, textStatus, errorThrown) => { alert(xhr + " " + textStatus + " " + errorThrown); },
|
||||||
|
success: (result) => {
|
||||||
|
|
||||||
|
teamsLoading(false);
|
||||||
|
loadTeams(result.groups, search, result.sortGroups);
|
||||||
|
|
||||||
|
if (!result.form_errors) {
|
||||||
|
$("#editSectionModal").modal("hide");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$("#editSectionName").focus();
|
||||||
|
$("#editSectionNameError").text(result.form_errors.editSectionName).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function openEditTeamModal(teamId) {
|
||||||
|
|
||||||
$("#editTeamModal").modal("show");
|
$("#editTeamModal").modal("show");
|
||||||
|
|
||||||
|
$("#saveEditTeamModal").off("click").on("click", () => {
|
||||||
|
teamsLoading(true);
|
||||||
|
|
||||||
|
const [search, sortGroups, sortMembers] = getFilters()
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: updateTeamUrl,
|
||||||
|
type: "post",
|
||||||
|
data: {
|
||||||
|
"csrfmiddlewaretoken": csrfMiddlewareToken,
|
||||||
|
"teamId": teamId,
|
||||||
|
"teamNumber": $("#editTeamNumber").val(),
|
||||||
|
"search": search,
|
||||||
|
"sortGroups": sortGroups,
|
||||||
|
"sortMembers": sortMembers
|
||||||
|
},
|
||||||
|
error: (xhr, textStatus, errorThrown) => { alert(xhr + " " + textStatus + " " + errorThrown); },
|
||||||
|
success: (result) => {
|
||||||
|
|
||||||
|
teamsLoading(false);
|
||||||
|
loadTeams(result.groups, search, result.sortGroups);
|
||||||
|
|
||||||
|
if (!result.form_errors) {
|
||||||
|
$("#editTeamModal").modal("hide");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$("#editTeamNumber").focus();
|
||||||
|
$("#editTeamNumberError").text(result.form_errors.editTeamNumber).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function openEditMemberModal(memberId) {
|
function openEditMemberModal(memberId) {
|
||||||
@ -271,7 +377,9 @@ function openEditMemberModal(memberId) {
|
|||||||
$("#editMemberModal").modal("show");
|
$("#editMemberModal").modal("show");
|
||||||
|
|
||||||
// Update the submit button
|
// Update the submit button
|
||||||
$("#saveEditModal").off("click").on("click", () => { saveEditMemberModal(memberId); });
|
$("#saveEditModal").off("click").on("click", () => {
|
||||||
|
alert("you pressed save member");
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveEditMemberModal(memberId) {
|
function saveEditMemberModal(memberId) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user