From b56805227a1136b8573c0cf6c098ba54d33882ac Mon Sep 17 00:00:00 2001
From: borzechof99 <borzechof99@mi.fu-berlin.de>
Date: Fri, 25 Jun 2021 19:04:53 +0200
Subject: [PATCH] Implement Sport PUT,PATCH,DELETE, IncompleteSport GET,
 Criteria GET

---
 unisportomat/quiz/models.py       |  12 ++++
 unisportomat/quiz/serializers.py  |  81 ++++++++++++++++++----
 unisportomat/quiz/views.py        | 109 ++++++++++++++++++++++++++++--
 unisportomat/unisportomat/urls.py |   2 +
 4 files changed, 182 insertions(+), 22 deletions(-)

diff --git a/unisportomat/quiz/models.py b/unisportomat/quiz/models.py
index 23168aa..fd3c446 100644
--- a/unisportomat/quiz/models.py
+++ b/unisportomat/quiz/models.py
@@ -125,6 +125,18 @@ class Criterion(models.Model):
     def __str__(self):
         return self.name
 
+    def get_active_sum(self):
+
+        num_active = 0
+        sum = 0
+
+        for rating_obj in CriterionRating.objects.filter(criterion=self):
+            if rating_obj.rating != -1:
+                num_active += 1
+                sum += rating_obj.rating
+
+        return num_active, sum
+
 
 class CallToMove(models.Model):
     """Defines text and image that are used to show a call to move between questions"""
diff --git a/unisportomat/quiz/serializers.py b/unisportomat/quiz/serializers.py
index 601fdbb..68d94c4 100644
--- a/unisportomat/quiz/serializers.py
+++ b/unisportomat/quiz/serializers.py
@@ -81,22 +81,26 @@ class SingleSportSerializer(serializers.BaseSerializer):
     def to_internal_value(self, request):
         """
         The Data in the Request is taken and written to another Dictionary.
-        During this process, the Data is Validated on:
-        - whether Name and URL for Sport exist
-        - whether the Rating Value and Criterion ID are valid
+        During this process, the Data is Validated on whether the Rating Value and Criterion ID are valid.
+        If the Request is PATCHing or PUTting an existing Sport, not every field must be existant.
+        So, the existance is explicitly checked.
         """
 
-        data = {"valid_request": True}
+        sport_dictionary = {}
 
-        for field_value in ["name", "url"]:
-            try:
-                data[field_value] = request.data[field_value]
-            except:
-                data["valid_request"] = False
-                return data
+        # If The Sport is Being Patched, Name or URL may not be changed.
+        # That means that those Fields might not be sent in the Request,
+        # leading to needing to check whether they exist.
+        if "name" in request.data.keys():
+            sport_dictionary["name"] = request.data["name"]
+
+        if "url" in request.data.keys():
+            sport_dictionary["url"] = request.data["url"]
 
-        data["criteria"] = []
+        # A Number of Criteria may be sent with the Sport
+        sport_dictionary["criteria"] = []
 
+        # For every Sent Criterion, the ID of the Criterion and the Rating Value is being tested for Validity
         for criterion in request.data["criteria"]:
 
             value = criterion["value"]
@@ -104,9 +108,56 @@ class SingleSportSerializer(serializers.BaseSerializer):
                 validate_rating(value)
                 crit = Criterion.objects.get(pk=criterion["id"])
             except:
-                data["valid_request"] = False
-                return data
+                return None
+
+            sport_dictionary["criteria"].append((crit, value))
+
+        return sport_dictionary
+
+
+class IncompleteSportSerializer(serializers.BaseSerializer):
+    def to_representation(self, incomplete_sports):
+
+        incomplete_sport_list = []
+
+        for sport in incomplete_sports:
+            incomplete_sport = {
+                "id": sport.pk,
+                "name": sport.name,
+                "criteria": [],
+            }
+
+            for crit in sport.criteria_ratings.iterator():
+
+                if sport.get_rating(crit) != -1:
+                    continue
+
+                incomplete_sport["criteria"].append(
+                    {
+                        "id": crit.pk,
+                        "name": crit.name,
+                    }
+                )
+
+            incomplete_sport_list.append(incomplete_sport)
+
+        return incomplete_sport_list
+
+
+class CriteriaSerializer(serializers.BaseSerializer):
+    def to_representation(self, data):
+
+        criteria_list = []
+
+        for crit, active_sports, sum_of_weights in data:
+
+            criterion_dict = {}
+
+            criterion_dict["id"] = crit.pk
+            criterion_dict["name"] = crit.name
+            criterion_dict["number_of_sports_active"] = active_sports
+            criterion_dict["sum_of_weights"] = sum_of_weights
 
-            data["criteria"].append((crit, value))
+            criteria_list.append(criterion_dict)
 
-        return data
+        return criteria_list
diff --git a/unisportomat/quiz/views.py b/unisportomat/quiz/views.py
index dc02417..f3d56cd 100644
--- a/unisportomat/quiz/views.py
+++ b/unisportomat/quiz/views.py
@@ -16,6 +16,8 @@ from .serializers import (
     CriterionListSerializer,
     QuestionListSerializer,
     SingleSportSerializer,
+    CriteriaSerializer,
+    IncompleteSportSerializer,
 )
 from .models import Sport, Criterion, Question
 
@@ -86,7 +88,6 @@ class SmallSportListView(viewsets.ViewSet):
 
         sports = Sport.objects.all()
         sports = paginator.paginate_queryset(sports, request)
-        criteria = Criterion.objects.all()
         is_filled_tuples = []
 
         for sport in sports:
@@ -103,7 +104,7 @@ class SmallSportListView(viewsets.ViewSet):
         data_dict = SingleSportSerializer().to_internal_value(request)
 
         # A Try at Error Catching
-        if not data_dict["valid_request"]:
+        if data_dict == None:
             return HttpResponse(status=400)
 
         new_sport = Sport.objects.create_sport()
@@ -128,7 +129,9 @@ class SmallSportListView(viewsets.ViewSet):
 
         sport = get_object_or_404(Sport, pk=pk)
 
-        return Response(SingleSportSerializer(sport).data)
+        response = SingleSportSerializer(sport)
+
+        return Response(response.data)
 
     # PUT for api/admin/sport/<id>/
     def update(self, request, pk=None):
@@ -136,15 +139,107 @@ class SmallSportListView(viewsets.ViewSet):
         TODO
         """
 
-        data_dict = SingleSportSerializer().to_internal_value(request)
+        # Get Data from Serializer
+        request_data = SingleSportSerializer().to_internal_value(request)
 
+        if request_data == None:
+            # Something is Broke, so Refuse Changing Data
+            return HttpResponse(status=400)
+
+        # Get Sport from Data
         sport = Sport.objects.get(pk=pk)
 
-        print(data_dict)
+        # Apply New Data to Sport
+        sport.name = request_data["name"]
+        sport.url = request_data["url"]
+
+        # Overwrite Criterion Ratings
+        for criterion, value in request_data["criteria"]:
+            sport.rate(criterion, value)
 
-        return Response({"test": "Updating a Single Entry"})
+        # Re-Serialize changed Sport and Send it back
+        response = SingleSportSerializer(sport)
+
+        return Response(response.data)
+
+    # PATCH for api/admin/sport/<id>/
+    def partial_update(self, request, pk=None):
+
+        # Get Data from Serializer
+        request_data = SingleSportSerializer().to_internal_value(request)
+
+        if request_data == None:
+            # Something is Broke, so Refuse Changing Data
+            return HttpResponse(status=400)
+
+        # Get Sport from Data
+        sport = Sport.objects.get(pk=pk)
+
+        # Apply New Data to Sport, if it exists
+        if "name" in request_data.keys():
+            sport.name = request_data["name"]
+
+        if "url" in request_data.keys():
+            sport.url = request_data["url"]
+
+        # Overwrite Criterion Ratings
+        for criterion, value in request_data["criteria"]:
+            sport.rate(criterion, value)
+
+        # Re-Serialize changed Sport and Send it back
+        response = SingleSportSerializer(sport)
+
+        return Response(response.data)
 
     # DELETE for api/admin/sport/<id>/
     def destroy(self, request, pk=None):
 
-        return Response({"test": "Removing a Single Entry"})
+        sport = get_object_or_404(Sport, pk=pk)
+
+        sport.delete()
+
+        return HttpResponse(status=404)
+
+
+class IncompleteSportView(APIView):
+
+    authentication_classes = []
+
+    # GET for api/admin/sport/incomplete
+    def get(self, request):
+
+        incomplete_sport_list = []
+
+        for sport in Sport.objects.iterator():
+
+            if not sport.is_filled():
+                incomplete_sport_list.append(sport)
+
+        response = IncompleteSportSerializer(incomplete_sport_list)
+
+        return Response(response.data)
+
+
+class CriteriaView(APIView):
+
+    """
+    View for the List of Criteria and their Metadata
+    TODO: More Documentation
+    """
+
+    authentication_classes = []
+
+    # GET for api/admin/criteria
+    def get(self, request):
+
+        data = []
+
+        for crit in Criterion.objects.iterator():
+
+            active_sports, sum_of_weights = crit.get_active_sum()
+
+            data.append((crit, active_sports, sum_of_weights))
+
+        response = CriteriaSerializer(data)
+
+        return Response(response.data)
diff --git a/unisportomat/unisportomat/urls.py b/unisportomat/unisportomat/urls.py
index 06f8bb1..6814e5c 100644
--- a/unisportomat/unisportomat/urls.py
+++ b/unisportomat/unisportomat/urls.py
@@ -27,4 +27,6 @@ router.register(r"small-sport-list", views.SmallSportListView, "small-sport-list
 urlpatterns = [
     path("admin/", admin.site.urls),
     path("api/admin/", include(router.urls)),
+    path("api/admin/sport/incomplete/", views.IncompleteSportView.as_view()),
+    path("api/admin/criteria/", views.CriteriaView.as_view()),
 ]
-- 
GitLab