From 78845177916aa065db361ee6d0b2d33b08cc09e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20=C3=98rstad?= <tobiasio@ntnu.no> Date: Fri, 19 Feb 2021 15:49:00 +0100 Subject: [PATCH] Add view for accessing leaderboard scores --- backend/secfit/workouts/urls.py | 4 +- backend/secfit/workouts/views.py | 46 ++++- frontend/www/scripts/exercise.js | 333 ++++++++++++++++--------------- 3 files changed, 209 insertions(+), 174 deletions(-) diff --git a/backend/secfit/workouts/urls.py b/backend/secfit/workouts/urls.py index 82e8f36..e4c5e6c 100644 --- a/backend/secfit/workouts/urls.py +++ b/backend/secfit/workouts/urls.py @@ -28,10 +28,10 @@ urlpatterns = format_suffix_patterns( name="exercise-instance-list", ), path( - "api/leaderboards/", + "api/leaderboards/<int:pk>/", views.Leaderboards.as_view(), name="leaderboards", - ) + ), path( "api/exercise-instances/<int:pk>/", views.ExerciseInstanceDetail.as_view(), diff --git a/backend/secfit/workouts/views.py b/backend/secfit/workouts/views.py index 50df26d..fd29e92 100644 --- a/backend/secfit/workouts/views.py +++ b/backend/secfit/workouts/views.py @@ -6,10 +6,11 @@ from rest_framework import permissions from rest_framework.parsers import ( JSONParser, ) +import json from rest_framework.decorators import api_view from rest_framework.response import Response from rest_framework.reverse import reverse -from django.db.models import Q +from django.db.models import Q, Sum, F, IntegerField from rest_framework import filters from workouts.parsers import MultipartJsonParser from workouts.permissions import ( @@ -209,11 +210,6 @@ class ExerciseDetail( permission_classes = [permissions.IsAuthenticated] def get(self, request, *args, **kwargs): - print("heiheihei") - print(request.data) - print(request.query_params) - print(request.GET) - print(request.path) return self.retrieve(request, *args, **kwargs) def put(self, request, *args, **kwargs): @@ -232,13 +228,41 @@ class Leaderboards( generics.GenericAPIView, ): + permission_classes = [permissions.IsAuthenticated] + def get(self, request, *args, **kwargs): - path = request.path - exercise_id = path.split("/")[-1] - print(exercise_id) - #ExerciseInstance.objects.filter(Q(exercise__pk=1) & Q(workout__visibility='PU')).values('workout__owner__pk').annotate(amount=Sum(F("sets") * F("number"), output_field=IntegerField())) + path = self.request.path + e_id = path.split("/")[-2] - return {"hei":"Haakon"} + if self.request.user: + + leaderboardNumbers = ExerciseInstance.objects.filter(Q(exercise__pk=e_id) & Q(workout__visibility='PU')).values('workout__owner__pk').annotate(amount=Sum(F("sets") * F("number"), output_field=IntegerField())).order_by('-amount') + + leaderboardResult = [] + + # Iterates through the top 5 entries in the leaderboard and formats it correctly + for i in range(0, min(5, len(leaderboardNumbers))): + leaderboardResult.append({"name": User.objects.get(pk=leaderboardNumbers[i]['workout__owner__pk']).username, "value": leaderboardNumbers[i]['amount']}) + + # Applies the rank to the leaderboard entry; if two or more users have the score they get the same rank + if i > 0 and leaderboardNumbers[i-1]["amount"] == leaderboardNumbers[i]["amount"]: + leaderboardResult[i]["rank"] = leaderboardResult[i-1]["rank"] + else: + leaderboardResult[i]["rank"] = i+1 + + # Finds the user in the leaderboard list. If the user is not in the leaderboard list, + # the user is automatically given a score of 0 and the worst rank + + currentLoggedInUser = self.request.user + + for j in range(0, len(leaderboardNumbers)): + if leaderboardNumbers[j]['workout__owner__pk'] == currentLoggedInUser.pk: + leaderboardResult.append({"name": currentLoggedInUser.username, "value": leaderboardNumbers[j]["amount"], "rank": j+1}) + break + else: + leaderboardResult.append({"name": currentLoggedInUser.username, "value": 0, "rank": len(leaderboardNumbers) + 1}) + + return Response(json.dumps(leaderboardResult)) class ExerciseInstanceList( diff --git a/frontend/www/scripts/exercise.js b/frontend/www/scripts/exercise.js index 854a472..67a4f5c 100644 --- a/frontend/www/scripts/exercise.js +++ b/frontend/www/scripts/exercise.js @@ -4,196 +4,207 @@ let deleteButton; let editButton; let oldFormData; - function handleCancelButtonDuringEdit() { - setReadOnly(true, "#form-exercise"); - okButton.className += " hide"; - deleteButton.className += " hide"; - cancelButton.className += " hide"; - editButton.className = editButton.className.replace(" hide", ""); - - cancelButton.removeEventListener("click", handleCancelButtonDuringEdit); - - let form = document.querySelector("#form-exercise"); - if (oldFormData.has("name")) form.name.value = oldFormData.get("name"); - if (oldFormData.has("description")) form.description.value = oldFormData.get("description"); - if (oldFormData.has("unit")) form.unit.value = oldFormData.get("unit"); - - oldFormData.delete("name"); - oldFormData.delete("description"); - oldFormData.delete("unit"); - + setReadOnly(true, "#form-exercise"); + okButton.className += " hide"; + deleteButton.className += " hide"; + cancelButton.className += " hide"; + editButton.className = editButton.className.replace(" hide", ""); + + cancelButton.removeEventListener("click", handleCancelButtonDuringEdit); + + let form = document.querySelector("#form-exercise"); + if (oldFormData.has("name")) form.name.value = oldFormData.get("name"); + if (oldFormData.has("description")) + form.description.value = oldFormData.get("description"); + if (oldFormData.has("unit")) form.unit.value = oldFormData.get("unit"); + + oldFormData.delete("name"); + oldFormData.delete("description"); + oldFormData.delete("unit"); } function handleCancelButtonDuringCreate() { - window.location.replace("exercises.html"); + window.location.replace("exercises.html"); } async function createExercise() { - let form = document.querySelector("#form-exercise"); - let formData = new FormData(form); - let body = {"name": formData.get("name"), - "description": formData.get("description"), - "unit": formData.get("unit")}; - - let response = await sendRequest("POST", `${HOST}/api/exercises/`, body); - - if (response.ok) { - window.location.replace("exercises.html"); - } else { - let data = await response.json(); - let alert = createAlert("Could not create new exercise!", data); - document.body.prepend(alert); - } + let form = document.querySelector("#form-exercise"); + let formData = new FormData(form); + let body = { + name: formData.get("name"), + description: formData.get("description"), + unit: formData.get("unit"), + }; + + let response = await sendRequest("POST", `${HOST}/api/exercises/`, body); + + if (response.ok) { + window.location.replace("exercises.html"); + } else { + let data = await response.json(); + let alert = createAlert("Could not create new exercise!", data); + document.body.prepend(alert); + } } function handleEditExerciseButtonClick() { - setReadOnly(false, "#form-exercise"); + setReadOnly(false, "#form-exercise"); - editButton.className += " hide"; - okButton.className = okButton.className.replace(" hide", ""); - cancelButton.className = cancelButton.className.replace(" hide", ""); - deleteButton.className = deleteButton.className.replace(" hide", ""); + editButton.className += " hide"; + okButton.className = okButton.className.replace(" hide", ""); + cancelButton.className = cancelButton.className.replace(" hide", ""); + deleteButton.className = deleteButton.className.replace(" hide", ""); - cancelButton.addEventListener("click", handleCancelButtonDuringEdit); + cancelButton.addEventListener("click", handleCancelButtonDuringEdit); - let form = document.querySelector("#form-exercise"); - oldFormData = new FormData(form); + let form = document.querySelector("#form-exercise"); + oldFormData = new FormData(form); } async function deleteExercise(id) { - let response = await sendRequest("DELETE", `${HOST}/api/exercises/${id}/`); - if (!response.ok) { - let data = await response.json(); - let alert = createAlert(`Could not delete exercise ${id}`, data); - document.body.prepend(alert); - } else { - window.location.replace("exercises.html"); - } + let response = await sendRequest("DELETE", `${HOST}/api/exercises/${id}/`); + if (!response.ok) { + let data = await response.json(); + let alert = createAlert(`Could not delete exercise ${id}`, data); + document.body.prepend(alert); + } else { + window.location.replace("exercises.html"); + } } async function retrieveExercise(id) { - let response = await sendRequest("GET", `${HOST}/api/exercises/${id}/`); - console.log(response.ok); - if (!response.ok) { - let data = await response.json(); - let alert = createAlert("Could not retrieve exercise data!", data); - document.body.prepend(alert); - } else { - let exerciseData = await response.json(); - let form = document.querySelector("#form-exercise"); - let formData = new FormData(form); - - for (let key of formData.keys()) { - let selector = `input[name="${key}"], textarea[name="${key}"]`; - let input = form.querySelector(selector); - let newVal = exerciseData[key]; - input.value = newVal; - } + let response = await sendRequest("GET", `${HOST}/api/exercises/${id}/`); + console.log(response.ok); + if (!response.ok) { + let data = await response.json(); + let alert = createAlert("Could not retrieve exercise data!", data); + document.body.prepend(alert); + } else { + let exerciseData = await response.json(); + let form = document.querySelector("#form-exercise"); + let formData = new FormData(form); + + for (let key of formData.keys()) { + let selector = `input[name="${key}"], textarea[name="${key}"]`; + let input = form.querySelector(selector); + let newVal = exerciseData[key]; + input.value = newVal; } + } } async function updateExercise(id) { - let form = document.querySelector("#form-exercise"); - let formData = new FormData(form); - let body = {"name": formData.get("name"), - "description": formData.get("description"), - "unit": formData.get("unit")}; - let response = await sendRequest("PUT", `${HOST}/api/exercises/${id}/`, body); - - if (!response.ok) { - let data = await response.json(); - let alert = createAlert(`Could not update exercise ${id}`, data); - document.body.prepend(alert); - } else { - // duplicate code from handleCancelButtonDuringEdit - // you should refactor this - setReadOnly(true, "#form-exercise"); - okButton.className += " hide"; - deleteButton.className += " hide"; - cancelButton.className += " hide"; - editButton.className = editButton.className.replace(" hide", ""); - - cancelButton.removeEventListener("click", handleCancelButtonDuringEdit); - - oldFormData.delete("name"); - oldFormData.delete("description"); - oldFormData.delete("unit"); - } + let form = document.querySelector("#form-exercise"); + let formData = new FormData(form); + let body = { + name: formData.get("name"), + description: formData.get("description"), + unit: formData.get("unit"), + }; + let response = await sendRequest("PUT", `${HOST}/api/exercises/${id}/`, body); + + if (!response.ok) { + let data = await response.json(); + let alert = createAlert(`Could not update exercise ${id}`, data); + document.body.prepend(alert); + } else { + // duplicate code from handleCancelButtonDuringEdit + // you should refactor this + setReadOnly(true, "#form-exercise"); + okButton.className += " hide"; + deleteButton.className += " hide"; + cancelButton.className += " hide"; + editButton.className = editButton.className.replace(" hide", ""); + + cancelButton.removeEventListener("click", handleCancelButtonDuringEdit); + + oldFormData.delete("name"); + oldFormData.delete("description"); + oldFormData.delete("unit"); + } } async function fetchLeaderBoards() { - //let response = await sendRequest("GET", `${HOST}/api/leaderboards/${id}`); - //Placeholder response and status: - let response = [{"name": "Mark", "value": 301, "rank": 1}, - {"name": "Anton", "value": 245, "rank": 2}, - {"name": "John", "value": 112, "rank": 3}, - {"name": "Joe", "value": 84, "rank": 4}, - {"name": "Larry", "value": 80, "rank": 5}, - {"name": "Glaum", "value": 1, "rank": 85}]; - response.ok = true; - - if (response.ok) { - - let table = document.getElementById("leaderboardstable"); - let row, cell; - - //The users own score will always be placed last in the JSON response - let userIndex = response.length - 1; - - for (let i = 0; i < response.length-1; i++) { - row = table.insertRow(); - cell = row.insertCell(); - cell.textContent = response[i].rank; - cell = row.insertCell(); - cell.textContent = response[i].name; - cell = row.insertCell(); - cell.textContent = response[i].value; - } - //If the user is not in top 5, the users score will also be rendered - if(response[userIndex].rank > 5){ - row = table.insertRow(); - cell = row.insertCell(); - cell.textContent = response[userIndex].rank; - cell = row.insertCell(); - cell.textContent = response[userIndex].name; - cell = row.insertCell(); - cell.textContent = response[userIndex].value; - } + let response = await sendRequest("GET", `${HOST}/api/leaderboards/${id}/`); + console.log(response.ok); + //Placeholder response and status: + let response = [ + { name: "Mark", value: 301, rank: 1 }, + { name: "Anton", value: 245, rank: 2 }, + { name: "John", value: 112, rank: 3 }, + { name: "Joe", value: 84, rank: 4 }, + { name: "Larry", value: 80, rank: 5 }, + { name: "Glaum", value: 1, rank: 85 }, + ]; + response.ok = true; + + if (response.ok) { + let table = document.getElementById("leaderboardstable"); + let row, cell; + + //The users own score will always be placed last in the JSON response + let userIndex = response.length - 1; + + for (let i = 0; i < response.length - 1; i++) { + row = table.insertRow(); + cell = row.insertCell(); + cell.textContent = response[i].rank; + cell = row.insertCell(); + cell.textContent = response[i].name; + cell = row.insertCell(); + cell.textContent = response[i].value; + } + //If the user is not in top 5, the users score will also be rendered + if (response[userIndex].rank > 5) { + row = table.insertRow(); + cell = row.insertCell(); + cell.textContent = response[userIndex].rank; + cell = row.insertCell(); + cell.textContent = response[userIndex].name; + cell = row.insertCell(); + cell.textContent = response[userIndex].value; } + } - return response; + return response; } window.addEventListener("DOMContentLoaded", async () => { - cancelButton = document.querySelector("#btn-cancel-exercise"); - okButton = document.querySelector("#btn-ok-exercise"); - deleteButton = document.querySelector("#btn-delete-exercise"); - editButton = document.querySelector("#btn-edit-exercise"); - oldFormData = null; - - const urlParams = new URLSearchParams(window.location.search); - - // view/edit - if (urlParams.has('id')) { - const exerciseId = urlParams.get('id'); - await retrieveExercise(exerciseId); - - editButton.addEventListener("click", handleEditExerciseButtonClick); - deleteButton.addEventListener("click", (async (id) => await deleteExercise(id)).bind(undefined, exerciseId)); - okButton.addEventListener("click", (async (id) => await updateExercise(id)).bind(undefined, exerciseId)); - } - //create - else { - setReadOnly(false, "#form-exercise"); - - editButton.className += " hide"; - okButton.className = okButton.className.replace(" hide", ""); - cancelButton.className = cancelButton.className.replace(" hide", ""); - - okButton.addEventListener("click", async () => await createExercise()); - cancelButton.addEventListener("click", handleCancelButtonDuringCreate); - } + cancelButton = document.querySelector("#btn-cancel-exercise"); + okButton = document.querySelector("#btn-ok-exercise"); + deleteButton = document.querySelector("#btn-delete-exercise"); + editButton = document.querySelector("#btn-edit-exercise"); + oldFormData = null; + + const urlParams = new URLSearchParams(window.location.search); + + // view/edit + if (urlParams.has("id")) { + const exerciseId = urlParams.get("id"); + await retrieveExercise(exerciseId); + + editButton.addEventListener("click", handleEditExerciseButtonClick); + deleteButton.addEventListener( + "click", + (async (id) => await deleteExercise(id)).bind(undefined, exerciseId) + ); + okButton.addEventListener( + "click", + (async (id) => await updateExercise(id)).bind(undefined, exerciseId) + ); + } + //create + else { + setReadOnly(false, "#form-exercise"); + + editButton.className += " hide"; + okButton.className = okButton.className.replace(" hide", ""); + cancelButton.className = cancelButton.className.replace(" hide", ""); + + okButton.addEventListener("click", async () => await createExercise()); + cancelButton.addEventListener("click", handleCancelButtonDuringCreate); + } - await fetchLeaderBoards(); -}); \ No newline at end of file + await fetchLeaderBoards(); +}); -- GitLab