diff --git a/server/api/settings.py b/server/api/settings.py index 6ef0bdc5..3e282054 100644 --- a/server/api/settings.py +++ b/server/api/settings.py @@ -53,7 +53,8 @@ "corsheaders", "api.healthcheck", "api.leaderboard", - "api.users" + "api.users", + "api.team", ] MIDDLEWARE = [ diff --git a/server/api/team/__init__.py b/server/api/team/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/server/api/team/admin.py b/server/api/team/admin.py new file mode 100644 index 00000000..e69de29b diff --git a/server/api/team/apps.py b/server/api/team/apps.py new file mode 100644 index 00000000..2eebabcc --- /dev/null +++ b/server/api/team/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class TeamConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "api.team" diff --git a/server/api/team/migrations/0001_initial.py b/server/api/team/migrations/0001_initial.py new file mode 100644 index 00000000..bd074524 --- /dev/null +++ b/server/api/team/migrations/0001_initial.py @@ -0,0 +1,89 @@ +# Generated by Django 5.1 on 2025-01-11 03:39 + +import django.db.models.deletion +import uuid +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ("users", "0001_initial"), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name="Team", + fields=[ + ( + "id", + models.UUIDField( + default=uuid.uuid4, + editable=False, + primary_key=True, + serialize=False, + ), + ), + ("name", models.CharField(max_length=100)), + ("description", models.CharField(max_length=100)), + ("time_created", models.DateTimeField(auto_now_add=True)), + ("grades", models.IntegerField()), + ( + "school", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="has", + to="users.school", + ), + ), + ], + ), + migrations.CreateModel( + name="Team_member", + fields=[ + ( + "id", + models.UUIDField( + default=uuid.uuid4, + editable=False, + primary_key=True, + serialize=False, + ), + ), + ("time_added", models.DateTimeField(auto_now_add=True)), + ( + "student", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="isA", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "team", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="has", + to="team.team", + ), + ), + ], + options={ + "constraints": [ + models.UniqueConstraint( + fields=("student", "team"), name="unique_student_team" + ) + ], + }, + ), + ] diff --git a/server/api/team/migrations/__init__.py b/server/api/team/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/server/api/team/models.py b/server/api/team/models.py new file mode 100644 index 00000000..953f338d --- /dev/null +++ b/server/api/team/models.py @@ -0,0 +1,42 @@ +from django.db import models +from django.contrib.auth.models import User +# from api.quiz.models import Quiz +from api.users.models import School +import uuid + +# Create your models here. + + +class Team(models.Model): + id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) + # quiz = models.ForeignKey(Quiz, on_delete=models.SET_NULL, + # null=True, blank=True, related_name="isAssessedBy") + name = models.CharField(max_length=100) + school = models.ForeignKey( + School, on_delete=models.SET_NULL, null=True, blank=True, related_name="has") + description = models.CharField(max_length=100) + time_created = models.DateTimeField(auto_now_add=True) + grades = models.IntegerField() + + def __str__(self): + # Format when printed: Team ID (Name): Grade + return f"Team {self.id} ({self.name}): {self.grades}" + + +class Team_member(models.Model): + id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) + student = models.ForeignKey(User, on_delete=models.SET_NULL, + null=True, blank=True, related_name="isA") + team = models.ForeignKey("Team", on_delete=models.SET_NULL, + null=True, blank=True, related_name="has") + time_added = models.DateTimeField(auto_now_add=True) + + def __str__(self): + # Format when printed: Team ID has member student + return f"Team {self.id} has member {self.student}" + + class Meta: + constraints = [ + models.UniqueConstraint( + fields=["student", "team"], name="unique_student_team") + ] diff --git a/server/api/team/serializers.py b/server/api/team/serializers.py new file mode 100644 index 00000000..6e8f87c0 --- /dev/null +++ b/server/api/team/serializers.py @@ -0,0 +1,18 @@ +from .models import Team, Team_member +from rest_framework import serializers + + +class TeamMemberSerializer(serializers.ModelSerializer): + class Meta: + model = Team_member + fields = '__all__' + read_only_fields = ("id", "time_added") + + +class TeamSerializer(serializers.ModelSerializer): + members = TeamMemberSerializer(source='has', many=True, read_only=True) + + class Meta: + model = Team + fields = '__all__' + read_only_fields = ("id", "time_created") diff --git a/server/api/team/tests.py b/server/api/team/tests.py new file mode 100644 index 00000000..e69de29b diff --git a/server/api/team/urls.py b/server/api/team/urls.py new file mode 100644 index 00000000..14cf2cff --- /dev/null +++ b/server/api/team/urls.py @@ -0,0 +1,11 @@ +from django.urls import path, include +from rest_framework.routers import DefaultRouter +from .views import TeamMemberViewSet, TeamViewSet + +router = DefaultRouter() +router.register(r'teams', TeamViewSet) +router.register(r'team-members', TeamMemberViewSet) + +urlpatterns = [ + path('', include(router.urls)), +] diff --git a/server/api/team/views.py b/server/api/team/views.py new file mode 100644 index 00000000..d56e1576 --- /dev/null +++ b/server/api/team/views.py @@ -0,0 +1,71 @@ +from rest_framework import viewsets, status +from rest_framework.decorators import api_view +from rest_framework.response import Response +from rest_framework.pagination import PageNumberPagination +from django.shortcuts import get_object_or_404 +from .models import Team, Team_member +from .serializers import TeamSerializer, TeamMemberSerializer + + +class TeamPagination(PageNumberPagination): + page_size = 5 + page_size_query_param = 'page_size' + max_page_size = 100 + + +class TeamViewSet(viewsets.ModelViewSet): + queryset = Team.objects.all() + serializer_class = TeamSerializer + pagination_class = TeamPagination + + +class TeamMemberViewSet(viewsets.ModelViewSet): + queryset = Team_member.objects.all() + serializer_class = TeamMemberSerializer + + +@api_view(["GET"]) +def team_list(request): + queryset = Team.objects.all() + paginator = TeamPagination() + paginated_queryset = paginator.paginate_queryset(queryset, request) + serializer = TeamSerializer(paginated_queryset, many=True) + return paginator.get_paginated_response(serializer.data) + + +@api_view(["GET", "PUT", "PATCH", "DELETE"]) +def team_detail(request, pk): + team = get_object_or_404(Team, pk=pk) + + if request.method == "GET": + serializer = TeamSerializer(team) + return Response(serializer.data) + elif request.method in ["PUT", "PATCH"]: + serializer = TeamSerializer( + team, data=request.data, partial=(request.method == "PATCH")) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + elif request.method == "DELETE": + team.delete() + return Response(status=status.HTTP_204_NO_CONTENT) + + +@api_view(["GET", "PUT", "PATCH", "DELETE"]) +def team_member_detail(request, pk): + team_member = get_object_or_404(Team_member, pk=pk) + + if request.method == "GET": + serializer = TeamMemberSerializer(team_member) + return Response(serializer.data) + elif request.method in ["PUT", "PATCH"]: + serializer = TeamMemberSerializer( + team_member, data=request.data, partial=(request.method == "PATCH")) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + elif request.method == "DELETE": + team_member.delete() + return Response(status=status.HTTP_204_NO_CONTENT) diff --git a/server/api/urls.py b/server/api/urls.py index 88556f85..18c322eb 100644 --- a/server/api/urls.py +++ b/server/api/urls.py @@ -20,7 +20,8 @@ urlpatterns = [ path("admin/", admin.site.urls), - path("api/healthcheck/", include(("api.healthcheck.urls"))), + path("api/healthcheck/", include("api.healthcheck.urls")), path("api/leaderboard/", include("api.leaderboard.urls")), path("api/question/", include("api.question.urls")), + path("api/team/", include("api.team.urls")), ] diff --git a/server/api/users/migrations/0001_initial.py b/server/api/users/migrations/0001_initial.py index f7276006..c91777dc 100644 --- a/server/api/users/migrations/0001_initial.py +++ b/server/api/users/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 5.1 on 2024-12-14 06:53 +# Generated by Django 5.1 on 2025-01-11 03:39 import django.db.models.deletion from django.conf import settings @@ -18,7 +18,7 @@ class Migration(migrations.Migration): name="School", fields=[ ("id", models.AutoField(primary_key=True, serialize=False)), - ("name", models.CharField(blank=True, max_length=100)), + ("name", models.CharField(max_length=100)), ("code", models.CharField(max_length=10)), ], ),