Integrated models for teams & members page
This commit is contained in:
parent
8394de0dcb
commit
8afd3fa905
@ -19,9 +19,9 @@ class PegAdmin(admin.ModelAdmin):
|
||||
class MemberAdmin(admin.ModelAdmin):
|
||||
"""Admin model for the Member model."""
|
||||
|
||||
list_display = ("first_name", "last_name", "user", "team", "peg")
|
||||
search_fields = ("first_name", "last_name", "user", "team", "peg")
|
||||
list_filter = ("team", "peg")
|
||||
list_display = ("first_name", "last_name", "team")
|
||||
search_fields = ("first_name", "last_name", "team")
|
||||
list_filter = ("team",)
|
||||
|
||||
|
||||
@admin.register(Team)
|
||||
|
0
src/mainapp/management/__init__.py
Normal file
0
src/mainapp/management/__init__.py
Normal file
0
src/mainapp/management/commands/__init__.py
Normal file
0
src/mainapp/management/commands/__init__.py
Normal file
68
src/mainapp/management/commands/create_members_fixture.py
Normal file
68
src/mainapp/management/commands/create_members_fixture.py
Normal file
@ -0,0 +1,68 @@
|
||||
import random
|
||||
import names
|
||||
import json
|
||||
from django.core.management.base import BaseCommand
|
||||
from mainapp.models import Member, Team
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Creates a fixture with randomly generated Member objects"
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument("num_members", type=int)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
num_members = options["num_members"]
|
||||
teams = Team.objects.all()
|
||||
if not teams:
|
||||
print("No teams found. Please create some teams first.")
|
||||
return
|
||||
|
||||
members = []
|
||||
for team in teams:
|
||||
for _ in range(num_members):
|
||||
first_name = names.get_first_name()
|
||||
last_name = names.get_last_name()
|
||||
member = Member(first_name=first_name, last_name=last_name, team=team)
|
||||
members.append(member)
|
||||
|
||||
Member.objects.bulk_create(members)
|
||||
|
||||
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:
|
||||
member_fixture = {
|
||||
"model": "mainapp.member",
|
||||
"pk": member.pk,
|
||||
"fields": {
|
||||
"first_name": member.first_name,
|
||||
"last_name": member.last_name,
|
||||
"team": member.team_id
|
||||
}
|
||||
}
|
||||
members_fixture.append(member_fixture)
|
||||
|
||||
with open("src/mainapp/fixtures/members_fixture.json", "w") as f:
|
||||
f.write(json.dumps(members_fixture, indent=2))
|
||||
self.stdout.write(self.style.SUCCESS("Created members_fixture.json."))
|
37
src/mainapp/management/commands/create_teams_fixture.py
Normal file
37
src/mainapp/management/commands/create_teams_fixture.py
Normal file
@ -0,0 +1,37 @@
|
||||
from django.core.management.base import BaseCommand
|
||||
from mainapp.models import Team
|
||||
import random
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Creates a fixture file for Team objects"
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument("num_teams", type=int, help="Number of teams to create")
|
||||
|
||||
def handle(self, *args, **options):
|
||||
num_teams = options["num_teams"]
|
||||
|
||||
teams = []
|
||||
for i in range(num_teams):
|
||||
team = Team.objects.create()
|
||||
teams.append(team)
|
||||
|
||||
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")
|
||||
|
||||
self.stdout.write(self.style.SUCCESS("Fixture created successfully"))
|
@ -1,6 +1,5 @@
|
||||
# Generated by Django 4.1.5 on 2023-01-26 14:24
|
||||
# Generated by Django 4.1.5 on 2023-05-06 15:53
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import mainapp.models
|
||||
@ -11,7 +10,6 @@ class Migration(migrations.Migration):
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
@ -40,8 +38,7 @@ 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')),
|
||||
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||
('team', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='mainapp.team', validators=[mainapp.models.validate_team_size])),
|
||||
],
|
||||
),
|
||||
]
|
||||
|
@ -1,25 +0,0 @@
|
||||
# Generated by Django 4.1.5 on 2023-01-26 14:31
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import mainapp.models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('mainapp', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='member',
|
||||
name='peg',
|
||||
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='mainapp.peg'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='member',
|
||||
name='team',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='mainapp.team', validators=[mainapp.models.validate_team_size]),
|
||||
),
|
||||
]
|
@ -49,13 +49,16 @@ class Member(models.Model):
|
||||
|
||||
first_name = models.CharField(max_length=255)
|
||||
last_name = models.CharField(max_length=255)
|
||||
user = models.OneToOneField("auth.User", on_delete=models.CASCADE)
|
||||
team = models.ForeignKey("Team", on_delete=models.SET_NULL, null=True, blank=True, validators=(validate_team_size,))
|
||||
peg = models.OneToOneField("Peg", on_delete=models.SET_NULL, null=True, blank=True)
|
||||
team = models.ForeignKey("Team", on_delete=models.SET_NULL, null=True, blank=True, validators=(validate_team_size,), related_name='members')
|
||||
# peg = models.OneToOneField("Peg", on_delete=models.SET_NULL, null=True, blank=True)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.first_name} {self.last_name} (team {self.team.team_number}))"
|
||||
|
||||
@property
|
||||
def fullname(self) -> str:
|
||||
return f"{self.first_name} {self.last_name}"
|
||||
|
||||
|
||||
class Team(models.Model):
|
||||
"""Represents a team"""
|
||||
@ -66,6 +69,10 @@ class Team(models.Model):
|
||||
def __str__(self):
|
||||
return f"Team {self.team_number}"
|
||||
|
||||
@property
|
||||
def members(self) -> list[Member]:
|
||||
Member.objects.filter(team=self)
|
||||
|
||||
|
||||
def validate_section_character(value):
|
||||
"""Validates the section character"""
|
||||
|
@ -62,7 +62,7 @@
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||
<button type="button" class="btn btn-primary">Save Changes</button>
|
||||
<button type="button" class="btn btn-primary" id="saveEditModal">Save Changes</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -101,7 +101,6 @@
|
||||
*
|
||||
* **/
|
||||
function isEmptyOrSpaces(string) {
|
||||
console.log(string === null || string.match(/^ *$/) !== null);
|
||||
return string === null || string.match(/^ *$/) !== null;
|
||||
}
|
||||
|
||||
@ -109,43 +108,53 @@
|
||||
$("#teamsContainer").html(""); // Clear the previous listed teams
|
||||
|
||||
if (teams.length < 1) {
|
||||
$("#teamsNotFound").show()
|
||||
$("#teamsNotFound").show();
|
||||
}
|
||||
|
||||
// Iterate over and add each team
|
||||
teams.forEach((team, index) => {
|
||||
|
||||
const teamColumn = $(
|
||||
`<div class='col-12 col-md-6 col-xl-4 mb-4 team-column'>
|
||||
<div class='p-4 bg-body-tertiary rounded h-100 fluid-hover-zoom shadow-sm shadow-on-hover'>
|
||||
<h4>${team.identifier}</h4>
|
||||
$("#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>`
|
||||
);
|
||||
|
||||
teamColumn.css("opacity", 1);
|
||||
$("#teamsContainer").append(teamColumn);
|
||||
|
||||
// While we have the team, iterate over and add it's members
|
||||
// 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'
|
||||
onclick='javascript:openEditModal("${member.name}");'
|
||||
>
|
||||
${member.name}
|
||||
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="") {
|
||||
@ -158,7 +167,7 @@
|
||||
},
|
||||
error: (xhr, textStatus, errorThrown) => { alert(errorThrown); },
|
||||
success: (result) => {
|
||||
teamsLoading(false)
|
||||
teamsLoading(false);
|
||||
loadTeams(result.teams, search);
|
||||
}
|
||||
});
|
||||
@ -177,17 +186,62 @@
|
||||
$("#teamsLoadingSpinner").hide();
|
||||
}
|
||||
|
||||
function openEditModal(name) {
|
||||
function openEditModal(memberId) {
|
||||
|
||||
var [first, last] = name.split(" ");
|
||||
var teamIdentifier = $(`.team-member:contains('${name}')`).parent().parent().parent().find("h4").text();
|
||||
// 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");
|
||||
|
||||
$("#editMemberName").text(name);
|
||||
// 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(teamIdentifier)
|
||||
$("#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");
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
{% endblock scripts %}
|
@ -9,4 +9,5 @@ urlpatterns = [
|
||||
|
||||
path('bulk-peg/', views.bulk_create_pegs, name='bulk-peg'),
|
||||
path('get-teams/', views.get_teams, name='get-teams'),
|
||||
path('update-member/', views.update_member, name='update-member')
|
||||
]
|
@ -1,11 +1,13 @@
|
||||
"""Views for the main app."""
|
||||
|
||||
from string import ascii_lowercase
|
||||
from functools import reduce
|
||||
|
||||
from django.shortcuts import render, redirect
|
||||
from django.http import JsonResponse
|
||||
from django.db.models import Q
|
||||
|
||||
from .models import Peg, Team, Member
|
||||
|
||||
from .models import Peg
|
||||
|
||||
def index(request):
|
||||
return render(request, 'index.html')
|
||||
@ -30,39 +32,69 @@ def bulk_create_pegs(request):
|
||||
return redirect(request.META.get('HTTP_REFERER'))
|
||||
|
||||
def get_teams(request):
|
||||
"""Returns list of teams."""
|
||||
"""Returns a JsonResponse containing a dictionary with a k/v pair for a list of teams.
|
||||
|
||||
Args:
|
||||
request: the web request object.
|
||||
Returns:
|
||||
JsonResponse: dictionary of teams like so {'teams': [{}, {}, {}]}.
|
||||
"""
|
||||
|
||||
if not request.POST:
|
||||
return
|
||||
|
||||
search = request.POST.get("search")
|
||||
teams = Team.objects.order_by("team_number").all()
|
||||
|
||||
from .teams_demo_test import created_teams
|
||||
|
||||
# Filter out teams that don't contain members being searched for
|
||||
if search:
|
||||
unfiltered_teams = created_teams.copy()
|
||||
created_teams = [
|
||||
team for team in unfiltered_teams
|
||||
if any(
|
||||
search.lower() in name for name in
|
||||
[member["name"].lower() for member in team["members"]]
|
||||
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)
|
||||
|
||||
created_teams.sort(reverse=False, key=lambda team: team["identifier"])
|
||||
return JsonResponse({"teams": created_teams})
|
||||
# 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,
|
||||
"members": [
|
||||
{"first": member.first_name, "last": member.last_name, "id": member.id, "team": team.team_number}
|
||||
for member in team.members.all()
|
||||
]
|
||||
}
|
||||
response_data["teams"].append(team_data)
|
||||
|
||||
# if not search:
|
||||
# created_teams.sort(reverse=False, key=lambda team: team["identifier"])
|
||||
# return JsonResponse({"teams": created_teams})
|
||||
return JsonResponse(response_data)
|
||||
|
||||
# # Create a new list only containing teams that meet the search criteria
|
||||
# filtered_teams = []
|
||||
# for team in created_teams:
|
||||
# member_names = [member["name"].lower() for member in team["members"]]
|
||||
# if any(search.lower() in name for name in member_names):
|
||||
# filtered_teams.append(team)
|
||||
def update_member(request):
|
||||
"""Update a member. Returns a JsonResponse with the updated teams."""
|
||||
|
||||
# filtered_teams.sort(reverse=False, key=lambda team: team["identifier"])
|
||||
if not request.POST:
|
||||
return
|
||||
|
||||
# return JsonResponse({"teams": filtered_teams})
|
||||
# Get the updated values
|
||||
member_id = request.POST.get("memberId")
|
||||
first = request.POST.get("first")
|
||||
last = request.POST.get("last")
|
||||
team_number = request.POST.get("teamNumber")
|
||||
|
||||
# Get the member and team
|
||||
member = Member.objects.get(id=member_id)
|
||||
team = Team.objects.get(team_number=team_number)
|
||||
|
||||
# Update the member
|
||||
member.first_name = first
|
||||
member.last_name = last
|
||||
member.team = team
|
||||
member.save()
|
||||
|
||||
return get_teams(request)
|
||||
|
Loading…
x
Reference in New Issue
Block a user