+
+
\ No newline at end of file
diff --git a/src/mainapp/templates/modals/section.html b/src/mainapp/templates/modals/section.html
new file mode 100644
index 0000000..fbd52a2
--- /dev/null
+++ b/src/mainapp/templates/modals/section.html
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+ Section Modal Title
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/mainapp/templates/modals/team.html b/src/mainapp/templates/modals/team.html
new file mode 100644
index 0000000..8135dcc
--- /dev/null
+++ b/src/mainapp/templates/modals/team.html
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+ Team Modal Title
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/mainapp/templates/teams.html b/src/mainapp/templates/teams.html
index ee4b7aa..aa09c4d 100644
--- a/src/mainapp/templates/teams.html
+++ b/src/mainapp/templates/teams.html
@@ -50,11 +50,11 @@
Sort groups by
-
+
-
+
@@ -78,8 +78,7 @@
-
-
+
Loading...
@@ -234,10 +233,11 @@
diff --git a/src/mainapp/urls.py b/src/mainapp/urls.py
index 7da889d..4f35f3b 100644
--- a/src/mainapp/urls.py
+++ b/src/mainapp/urls.py
@@ -8,9 +8,12 @@ urlpatterns = [
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'),
+ path('get-angler-data/', views.get_angler_page_data, name='get-angler-data'),
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')
+ path("get-next-identifier/", views.get_next_identifier, name='get-next-identifier'),
+
+ # Rewrite
+ path('anglers/', views.ManageAnglersView.as_view(), name='anglers'),
]
\ No newline at end of file
diff --git a/src/mainapp/views.py b/src/mainapp/views.py
index cdecbb9..70abaa6 100644
--- a/src/mainapp/views.py
+++ b/src/mainapp/views.py
@@ -6,6 +6,9 @@ from django.shortcuts import render, redirect
from django.http import JsonResponse
from django.db.models import Q, Case, When, Value, IntegerField
from django.db.utils import IntegrityError
+from django.views import View
+from django.views.decorators.http import require_GET, require_POST
+from django.http import HttpRequest
from .models import Team, Member, Section, SectionManager, ReusableAutoField, SectionValidator
@@ -38,7 +41,246 @@ def name_sort_key(section):
else:
return (1, section.name[:-1], section.name[-1])
-def get_teams(request, **kwargs):
+
+class ManageAnglersView(View):
+ """View for the Manage Anglers page."""
+
+ template_name = "anglers.html"
+
+ def get(self, request: HttpRequest, *args, **kwargs) -> HttpRequest:
+ """Handle GET requests to the Manage Anglers page.
+
+ Args:
+ request (HttpRequest): The HttpRequest object, contains GET data.
+
+ Returns:
+ HttpRequest: A render of the Manage Anglers page.
+ """
+
+ return render(request, self.template_name)
+
+ def post(self, request: HttpRequest, *args, **kwargs) -> JsonResponse:
+ """Handle POST requests to the Manage Anglers page.
+
+ Args:
+ request (HttpRequest): The HttpRequest object, contains POST data.
+
+ Returns:
+ JsonResponse: Contains the result of the action.
+ """
+
+ tasks = request.POST.getlist("tasks[]")
+
+ data = {}
+ for task in tasks:
+ data.update(self.handle_task(request, task))
+
+ return JsonResponse(data)
+
+ def handle_task(self, request, task: str) -> dict[str, str]:
+ """Handle a task.
+
+ Args:
+ request (HttpRequest): HttpRequest object, contains POST data.
+ task (str): The task to handle.
+
+ Raises:
+ ValueError: The task is invalid.
+
+ Returns:
+ dict[str, str]: The result of the task.
+ """
+
+ # Format is {key = ACTION-TASK_NAME: value = HANDLER_FUNCTION}
+ task_handlers = {
+ "update-team": self.update_team,
+ "update-section": self.update_section,
+ "update-angler": self.update_angler,
+ "get-teams": self.get_teams,
+ "get-sections": self.get_sections,
+ "get-anglers": self.get_anglers,
+ }
+
+ handler = task_handlers.get(task)
+ if not handler:
+ raise ValueError(f"Invalid task: {task}")
+
+ return handler(request)
+
+ def update_team(self, request) -> dict[str]:
+ """Update a team, returns a dictionary of the new team's data."""
+
+ result = {"form_errors": {}, "team": None}
+ team_id = request.POST.get("id")
+ team_number = request.POST.get("number")
+
+ if not (team_id and team_number):
+ raise ValueError("Team ID or Team Number is missing or empty")
+
+ if team_id == "-1":
+ team = Team(number=team_number)
+ else:
+ team = Team.objects.get(id=team_id)
+ team.number = team_number
+
+ try:
+ team.save()
+ result["team"] = {"id": team.id, "number": team.number}
+ except IntegrityError:
+ result["form_errors"]["#editTeamNumberError"] = "A Team with this number already exists"
+
+ return result
+
+ def update_section(self, request) -> dict[str]:
+ """Update a section, returns a dictionary of the new section's data."""
+
+ result = {"form_errors": {}, "section": None}
+ section_id = request.POST.get("id")
+ section_character = request.POST.get("character")
+
+ if not (section_id and section_character):
+ raise ValueError("Section ID or Section Character is missing or empty")
+
+ if section_id == "-1":
+ section = Section(character=section_character)
+ else:
+ section = Section.objects.get(id=section_id)
+ section.character = section_character
+
+ try:
+ section.save()
+ result["section"] = {"id": section.id, "character": section.character}
+ except IntegrityError:
+ result["form_errors"]["#editSectionNameError"] = "A Section with this character already exists"
+
+ return result
+
+ def update_angler(self, request) -> dict[str]:
+ """Update an Angler, returns a dictionary of the new angler's data."""
+
+ result = {"form_errors": {}, "angler": None}
+ angler_id = request.POST.get("angler_id")
+ forename = request.POST.get("forename")
+ surname = request.POST.get("surname")
+ peg_number = request.POST.get("peg_number")
+ team_id = request.POST.get("team_id")
+ section_id = request.POST.get("section_id")
+
+ if not angler_id:
+ raise ValueError("Invalid angler ID")
+
+ team = Team.objects.get(id=team_id)
+ section = Section.objects.get(id=section_id)
+
+ if angler_id == "-1":
+ angler = Member(
+ first_name=forename,
+ last_name=surname,
+ peg_number=peg_number,
+ team=team,
+ section=section
+ )
+ else:
+ angler = Member.objects.get(id=angler_id)
+ angler.first_name = forename
+ angler.last_name = surname
+ angler.peg_number = peg_number
+ angler.team = team
+ angler.section = section
+
+ angler.save()
+ result["angler"] = {
+ "id": id,
+ "forename": forename,
+ "surname": surname,
+ "peg_number": peg_number,
+ "team_id": team_id,
+ "section_id": section_id,
+ "team_number": angler.team.number,
+ "section_character": angler.section.character
+ }
+
+ return result
+
+ def get_teams(self, request) -> dict[str]:
+ """Returns a dictionary of all teams."""
+
+ search = request.GET.get("search")
+ teams = Team.objects.order_by("number").all()
+
+ # Search works by exluding teams that do not contain members with the search term in their names.
+ if search:
+ search_terms = search.split()
+ members = Member.objects.filter(reduce(lambda x, y: x & y, [
+ Q(first_name__icontains=term) | Q(last_name__icontains=term)
+ for term in search_terms
+ ]))
+ teams = teams.filter(members__in=members).distinct()
+
+ return {"teams": [{"id": team.id, "number": team.number} for team in teams]}
+
+ def get_sections(self, request) -> dict[str]:
+ """Returns a dictionary of all sections."""
+
+ search = request.GET.get("search")
+ sections = Section.objects.order_by("character").all()
+
+ if search:
+ search_terms = search.split()
+ members = Member.objects.filter(reduce(lambda x, y: x & y, [
+ Q(first_name__icontains=term) | Q(last_name__icontains=term)
+ for term in search_terms
+ ]))
+ sections = sections.filter(members__in=members).distinct()
+
+ return {"sections": [{"id": section.id, "character": section.character} for section in sections]}
+
+ def get_anglers(self, request) -> dict[str]:
+ """Returns a dictionary of all anglers."""
+
+ search = request.GET.get("search")
+ anglers = Member.objects.order_by("first_name").all()
+
+ if search:
+ search_terms = search.split()
+ anglers = anglers.filter(reduce(lambda x, y: x & y, [
+ Q(first_name__icontains=term) | Q(last_name__icontains=term)
+ for term in search_terms
+ ])).distinct()
+
+ return {
+ "anglers": [
+ {
+ "id": angler.id,
+ "first_name": angler.first_name,
+ "last_name": angler.last_name,
+ "peg_number": angler.peg_number,
+ "team_id": angler.team.id,
+ "section_id": angler.section.id,
+ "team_number": angler.team.number,
+ "section_character": angler.section.character
+ }
+ for angler in anglers
+ ]
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+def get_angler_page_data(request, **kwargs):
"""Returns a JsonResponse containing a dictionary with a k/v pair for a list of teams.
Args:
@@ -54,44 +296,44 @@ def get_teams(request, **kwargs):
sort_groups = request.POST.get("sortGroups") or "team"
sort_members = request.POST.get("sortMembers") or "peg_number"
- group_object = Team if sort_groups == "team" else Section
- groups = group_object.objects.order_by("name").all()
+ teams = Team.objects.order_by("number").all()
+ sections = Section.objects.order_by("character").all()
- # Filter out teams that don't contain members being searched for
if search:
search_terms = search.split()
members = Member.objects.filter(
reduce(
- lambda x, y: x | y,
+ lambda x, y: x & y, ## changed to AND from OR to fix bug with whitespace searches
[
Q(first_name__icontains=term) | Q(last_name__icontains=term)
for term in search_terms
]
)
)
- groups = groups.filter(members__in=members).distinct()
+ teams = teams.filter(members__in=members).distinct()
+ sections = sections.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:
- group_data = {
- "name": group.name,
- "members": [
- {
- "first": member.first_name,
- "last": member.last_name,
- "id": member.id,
- "peg": member.peg_number,
- "team": member.team.name if member.team else None,
- "section": member.section.name if member.section else None
- }
- for member in group.members.order_by(sort_members).all()
- ]
- }
- response_data["groups"].append(group_data)
+ response_data = {
+ "teams": [
+ {"id": team.id, "number": team.number}
+ for team in teams
+ ],
+ "sections": [
+ {"id": sec.id, "character": sec.character}
+ for sec in sections
+ ],
+ "anglers": [
+ {
+ "id": member.id,
+ "first": member.first_name,
+ "last": member.last_name,
+ "peg": member.peg_number,
+ "team_id": member.team.id if member.team else None,
+ "section_id": member.section.id if member.section else None
+ }
+ for member in Member.objects.order_by(sort_members).all()
+ ]
+ }
response_data["sortGroups"] = sort_groups
response_data["sortMembers"] = sort_members
@@ -101,6 +343,10 @@ def get_teams(request, **kwargs):
return JsonResponse(response_data)
+
+
+
+
def update_member(request):
"""Update a member. Returns a JsonResponse with the updated teams."""
@@ -126,7 +372,7 @@ def update_member(request):
member.save()
- return get_teams(request)
+ return get_angler_page_data(request)
def update_section(request):
"""Update a section, returns JsonResponse with updated teams data."""
@@ -139,26 +385,26 @@ def update_section(request):
validator = SectionValidator()
if not validator.is_valid(section_name):
- json_response = get_teams(request, form_errors={
+ json_response = get_angler_page_data(request, form_errors={
"editSectionName": "This is an invalid section"
})
return json_response
if section_id == "-1":
- section = Section(name=section_name)
+ section = Section(character=section_name)
else:
section = Section.objects.get(id=section_id)
- section.name = section_name
+ section.character = section_name
try:
section.save()
except IntegrityError:
- json_response = get_teams(request, form_errors={
+ json_response = get_angler_page_data(request, form_errors={
"editSectionName": "A Section with this character already exists"
})
return json_response
- return get_teams(request) # returns jsonresponse with new details
+ return get_angler_page_data(request) # returns jsonresponse with new details
def update_team(request):
"""Update a team, returns a JsonResponse with updated teams data."""
@@ -171,18 +417,18 @@ def update_team(request):
try:
if team_id == "-1":
- team = Team.objects.create(name=team_number)
+ team = Team.objects.create(number=team_number)
else:
team = Team.objects.get(id=team_id)
- team.name = team_number
+ team.number = team_number
team.save()
except IntegrityError as error:
- json_response = get_teams(request, form_errors={
+ json_response = get_angler_page_data(request, form_errors={
"editTeamNumber": "A Team with this number already exists"
})
return json_response
- return get_teams(request)
+ return get_angler_page_data(request)
def get_next_peg() -> int:
pass
diff --git a/src/static/css/mainapp/anglers.css b/src/static/css/mainapp/anglers.css
new file mode 100644
index 0000000..e69de29
diff --git a/src/static/js/custom.js b/src/static/js/custom.js
index c0aa691..f77b435 100644
--- a/src/static/js/custom.js
+++ b/src/static/js/custom.js
@@ -7,6 +7,8 @@
$(document).ready(() => {
activateTooltips();
pageLoadMotionEffects();
+
+ $("select").select2({theme: "bootstrap-5", minimumResultsForSearch: -1})
});
@@ -46,4 +48,13 @@ function setMotionEffects(enable) {
else {
$(".fluid-hover-zoom").addClass("fluid-hover-zoom-off").removeClass("fluid-hover-zoom");
}
-}
\ No newline at end of file
+}
+
+jQuery.expr[':'].icontains = function(a, i, m) {
+ return jQuery(a).text().toUpperCase()
+ .indexOf(m[3].toUpperCase()) >= 0;
+};
+
+const findByKey = (array, key, value) => {
+ return array.find(item => item[key] === value);
+}
diff --git a/src/static/js/mainapp/anglers.js b/src/static/js/mainapp/anglers.js
new file mode 100644
index 0000000..10ad73b
--- /dev/null
+++ b/src/static/js/mainapp/anglers.js
@@ -0,0 +1,199 @@
+
+$(document).ready(() => {
+
+ toggleLoading(true);
+
+ // Search Bar Functionality
+
+ searchTimeout = null;
+
+ $("#search").on("input", () => {
+ clearTimeout(searchTimeout);
+ toggleLoading(true);
+
+ searchTimeout = setTimeout(() => {
+ fetchAndLoadGroups();
+ }, 500)
+ });
+
+ $("#searchButton").on("click", () => {
+ fetchAndLoadGroups();
+ });
+
+ $("#sortForm input").on("click", function() {
+ const name = $(this).attr("name");
+ localStorage.setItem(name, $(`input[name='${name}']:checked`, "#sortForm").val());
+
+ fetchAndLoadGroups();
+ });
+
+ // Modals
+
+ $("#addAngler").on("click", () => {
+ addAngler(-1);
+ });
+
+ $("#addTeam").on("click", () => {
+ addTeam(-1);
+ });
+
+ $("#addSection").on("click", () => {
+ addSection(-1);
+ });
+
+ fetchAndLoadGroups();
+
+});
+
+function fetchAndLoadGroups() {
+ toggleLoading(true);
+
+ const showGroups = localStorage.getItem("showGroups");
+ const sortAnglers = localStorage.getItem("sortAnglers");
+ const task = showGroups === "teams" ? "get-teams" : "get-sections";
+
+ $.ajax({
+ type: "POST",
+ url: pageUrl,
+ data: {
+ csrfmiddlewaretoken: csrfMiddlewareToken,
+ tasks: [task, "get-anglers"],
+ sortAnglers: sortAnglers,
+ search: $("#search").val()
+ },
+ error: (error) => {
+ console.error(error);
+ },
+ success: (data) => {
+ loadGroups(data);
+ }
+ });
+
+ toggleLoading(false);
+}
+
+function groupTemplate(group, groupType, oppositeGroupType) {
+ groupValue = groupType === "teams" ? group.number : group.character
+
+ return `
+