Commit cdbdf698 authored by Nikolai Roede Dokken's avatar Nikolai Roede Dokken
Browse files

Merge branch 'refactor' into 'master'

Refactor

See merge request !4
parents c459efad 66344fd8
Pipeline #170939 passed with stages
in 2 minutes and 6 seconds
......@@ -11,7 +11,7 @@ class IsCommentVisibleToUser(permissions.BasePermission):
- The comment is on a workout owned by the user
"""
def has_object_permission(self, request, view, obj):
def has_object_permission(self, request, obj):
# Write permissions are only allowed to the owner.
return (
obj.workout.visibility == "PU"
......
from django.shortcuts import render
from rest_framework import generics, mixins
from comments.models import Comment, Like
from rest_framework import permissions
......@@ -12,7 +11,6 @@ from rest_framework.filters import OrderingFilter
class CommentList(
mixins.ListModelMixin, mixins.CreateModelMixin, generics.GenericAPIView
):
# queryset = Comment.objects.all()
serializer_class = CommentSerializer
permission_classes = [permissions.IsAuthenticated]
filter_backends = [OrderingFilter]
......@@ -40,29 +38,24 @@ class CommentList(
- The comment is on a coach visibility workout and the user is the workout owner's coach
- The comment is on a workout owned by the user
"""
# The code below is kind of duplicate of the one in ./permissions.py
# We should replace it with a better solution.
# Or maybe not.
qs = Comment.objects.filter(
Q(workout__visibility="PU")
| Q(owner=self.request.user)
| (
Q(workout__visibility="CO")
& Q(workout__owner__coach=self.request.user)
)
| Q(workout__owner=self.request.user)
).distinct()
qs = Comment.objects.all()
for index in range(len(qs)):
if not IsCommentVisibleToUser().has_object_permission(self.request, qs[index]):
qs.remove(qs[index])
return qs
# Details of comment
class CommentDetail(
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
generics.GenericAPIView,
):
'''Details of comment
'''
queryset = Comment.objects.all()
serializer_class = CommentSerializer
permission_classes = [
......@@ -81,6 +74,10 @@ class CommentDetail(
# List of likes
class LikeList(mixins.ListModelMixin, mixins.CreateModelMixin, generics.GenericAPIView):
'''Details of likes
'''
serializer_class = LikeSerializer
permission_classes = [permissions.IsAuthenticated]
......@@ -97,13 +94,15 @@ class LikeList(mixins.ListModelMixin, mixins.CreateModelMixin, generics.GenericA
return Like.objects.filter(owner=self.request.user)
# Details of like
class LikeDetail(
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
generics.GenericAPIView,
):
'''Details of like
'''
queryset = Like.objects.all()
serializer_class = LikeSerializer
permission_classes = [permissions.IsAuthenticated]
......
......@@ -11,7 +11,6 @@ class CustomUserAdmin(UserAdmin):
add_form = CustomUserCreationForm
form = CustomUserChangeForm
model = get_user_model()
# list_display = UserAdmin.list_display + ('coach',)
fieldsets = UserAdmin.fieldsets + ((None, {"fields": ("coach",)}),)
add_fieldsets = UserAdmin.add_fieldsets + ((None, {"fields": ("coach",)}),)
......
from django.urls import path, include
from django.urls import path
from users import views
from rest_framework.urlpatterns import format_suffix_patterns
urlpatterns = [
path("api/users/", views.UserList.as_view(), name="user-list"),
......
import django
from rest_framework import mixins, generics
from workouts.mixins import CreateListModelMixin
from rest_framework import permissions
......@@ -10,15 +9,11 @@ from users.serializers import (
UserGetSerializer,
)
from rest_framework.permissions import (
AllowAny,
IsAdminUser,
IsAuthenticated,
IsAuthenticatedOrReadOnly,
)
from users.models import Offer, AthleteFile
from django.contrib.auth import get_user_model
from django.db.models import Q
from django.shortcuts import get_object_or_404
from rest_framework.parsers import MultiPartParser, FormParser
from users.permissions import IsCurrentUser, IsAthlete, IsCoach
from workouts.permissions import IsOwner, IsReadOnly
......@@ -98,7 +93,6 @@ class OfferList(
serializer.save(owner=self.request.user)
def get_queryset(self):
qs = Offer.objects.none()
result = Offer.objects.none()
if self.request.user:
......@@ -106,22 +100,20 @@ class OfferList(
Q(owner=self.request.user) | Q(recipient=self.request.user)
).distinct()
qp = self.request.query_params
u = self.request.user
user = self.request.user
# filtering by status (if provided)
s = qp.get("status", None)
if s is not None and self.request is not None:
qs = qs.filter(status=s)
status = qp.get("status", None)
if status is not None and self.request is not None:
qs = qs.filter(status=status)
if qp.get("status", None) is None:
qs = Offer.objects.filter(Q(owner=u)).distinct()
# filtering by category (sent or received)
c = qp.get("category", None)
if c is not None and qp is not None:
if c == "sent":
qs = qs.filter(owner=u)
elif c == "received":
qs = qs.filter(recipient=u)
qs = Offer.objects.filter(Q(owner=user)).distinct()
category = qp.get("category", None)
if category is not None and qp is not None:
if category == "sent":
qs = qs.filter(owner=user)
elif category == "received":
qs = qs.filter(recipient=user)
return qs
else:
return result
......
......@@ -2,29 +2,9 @@
log workouts (Workout), which contain instances (ExerciseInstance) of various
type of exercises (Exercise). The user can also upload files (WorkoutFile) .
"""
import os
from django.db import models
from django.core.files.storage import FileSystemStorage
from django.conf import settings
from django.contrib.auth import get_user_model
class OverwriteStorage(FileSystemStorage):
"""Filesystem storage for overwriting files. Currently unused."""
def get_available_name(self, name, max_length=None):
"""https://djangosnippets.org/snippets/976/
Returns a filename that's free on the target storage system, and
available for new content to be written to.
Args:
name (str): Name of the file
max_length (int, optional): Maximum length of a file name. Defaults to None.
"""
if self.exists(name):
os.remove(os.path.join(settings.MEDIA_ROOT, name))
# Create your models here.
class Workout(models.Model):
"""Django model for a workout that users can log.
......
......@@ -109,33 +109,20 @@ class WorkoutSerializer(serializers.HyperlinkedModelSerializer):
return workout
def update(self, instance, validated_data):
"""Custom logic for updating a Workout with its ExerciseInstances and Workouts.
This is needed because each object in both exercise_instances and files must be iterated
over and handled individually.
def update_exercises(self, validated_data, instance):
"""Custom logic and helper function for updating a Workout with its ExerciseInstances.
This updates existing exercise instances without adding or deleting object.
zip() will yield n 2-tuples, where n is min(len(exercise_instance), len(exercise_instance_data))
Args:
instance (Workout): Current Workout object
validated_data: Contains data for validated fields
instance (Workout): Current Workout object
Returns:
Workout: Updated Workout instance
"""
exercise_instances_data = validated_data.pop("exercise_instances")
exercise_instances = instance.exercise_instances
instance.name = validated_data.get("name", instance.name)
instance.notes = validated_data.get("notes", instance.notes)
instance.visibility = validated_data.get("visibility", instance.visibility)
instance.date = validated_data.get("date", instance.date)
instance.save()
# Handle ExerciseInstances
# This updates existing exercise instances without adding or deleting object.
# zip() will yield n 2-tuples, where n is
# min(len(exercise_instance), len(exercise_instance_data))
for exercise_instance, exercise_instance_data in zip(
exercise_instances.all(), exercise_instances_data
):
......@@ -162,7 +149,15 @@ class WorkoutSerializer(serializers.HyperlinkedModelSerializer):
for i in range(len(exercise_instances_data), len(exercise_instances.all())):
exercise_instances.all()[i].delete()
# Handle WorkoutFiles
def update_files(self, validated_data, instance):
"""Custom logic and helper function for updating a Workout with its WorkoutFiles.
Args:
validated_data: Contains data for validated fields
instance (Workout): Current Workout object
"""
if "files" in validated_data:
files_data = validated_data.pop("files")
......@@ -184,6 +179,31 @@ class WorkoutSerializer(serializers.HyperlinkedModelSerializer):
for i in range(len(files_data), len(files.all())):
files.all()[i].delete()
def update(self, instance, validated_data):
"""Custom logic for updating a Workout with its ExerciseInstances and Workouts.
This is needed because each object in both exercise_instances and files must be iterated
over and handled individually.
Args:
instance (Workout): Current Workout object
validated_data: Contains data for validated fields
Returns:
Workout: Updated Workout instance
"""
instance.name = validated_data.get("name", instance.name)
instance.notes = validated_data.get("notes", instance.notes)
instance.visibility = validated_data.get("visibility", instance.visibility)
instance.date = validated_data.get("date", instance.date)
instance.save()
self.update_exercises(validated_data, instance)
self.update_files(validated_data, instance)
return instance
def get_owner_username(self, obj):
......
......@@ -54,13 +54,15 @@ def api_root(request, format=None):
)
# Allow users to save a persistent session in their browser
class RememberMe(
mixins.ListModelMixin,
mixins.CreateModelMixin,
mixins.DestroyModelMixin,
generics.GenericAPIView,
):
"""Allow users to save a persistent session in their browser
"""
serializer_class = RememberMeSerializer
......@@ -71,10 +73,10 @@ class RememberMe(
return Response({"remember_me": self.rememberme()})
def post(self, request):
cookieObject = namedtuple("Cookies", request.COOKIES.keys())(
cookie_object = namedtuple("Cookies", request.COOKIES.keys())(
*request.COOKIES.values()
)
user = self.get_user(cookieObject)
user = self.get_user(cookie_object)
refresh = RefreshToken.for_user(user)
return Response(
{
......@@ -83,8 +85,8 @@ class RememberMe(
}
)
def get_user(self, cookieObject):
decode = base64.b64decode(cookieObject.remember_me)
def get_user(self, cookie_object):
decode = base64.b64decode(cookie_object.remember_me)
user, sign = pickle.loads(decode)
# Validate signature
......
......@@ -4,36 +4,32 @@ let deleteButton;
let editButton;
let oldFormData;
class MuscleGroup {
constructor(type) {
this.isValidType = false;
this.validTypes = ["Legs", "Chest", "Back", "Arms", "Abdomen", "Shoulders"]
class MuscleGroup {
isValidType = false;
validTypes = ["Legs", "Chest", "Back", "Arms", "Abdomen", "Shoulders"];
constructor(type) {
this.type = this.validTypes.includes(type) ? type : undefined;
};
}
setMuscleGroupType = (newType) => {
this.isValidType = false;
if(this.validTypes.includes(newType)){
this.isValidType = true;
this.type = newType;
}
else{
if (!this.validTypes.includes(newType)) {
this.isValidType = false;
alert("Invalid muscle group!");
return;
}
this.isValidType = true;
this.type = newType;
};
getMuscleGroupType = () => {
console.log(this.type, "SWIOEFIWEUFH")
return this.type;
}
};
}
function handleCancelButtonDuringEdit() {
function handleCancelButtonDuringEdit(e, afterUpdate = false) {
setReadOnly(true, "#form-exercise");
document.querySelector("select").setAttribute("disabled", "")
document.querySelector("select").setAttribute("disabled", "");
okButton.className += " hide";
deleteButton.className += " hide";
cancelButton.className += " hide";
......@@ -41,21 +37,22 @@ function handleCancelButtonDuringEdit() {
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("duration")) form.duration.value = oldFormData.get("duration");
if (oldFormData.has("calories")) form.calories.value = oldFormData.get("calories");
if (oldFormData.has("muscleGroup")) form.muscleGroup.value = oldFormData.get("muscleGroup");
if (oldFormData.has("unit")) form.unit.value = oldFormData.get("unit");
if (!afterUpdate) {
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("duration")) form.duration.value = oldFormData.get("duration");
if (oldFormData.has("calories")) form.calories.value = oldFormData.get("calories");
if (oldFormData.has("muscleGroup")) form.muscleGroup.value = oldFormData.get("muscleGroup");
if (oldFormData.has("unit")) form.unit.value = oldFormData.get("unit");
}
oldFormData.delete("name");
oldFormData.delete("description");
oldFormData.delete("duration");
oldFormData.delete("calories");
oldFormData.delete("muscleGroup");
oldFormData.delete("unit");
}
function handleCancelButtonDuringCreate() {
......@@ -63,15 +60,17 @@ function handleCancelButtonDuringCreate() {
}
async function createExercise() {
document.querySelector("select").removeAttribute("disabled")
document.querySelector("select").removeAttribute("disabled");
let form = document.querySelector("#form-exercise");
let formData = new FormData(form);
let body = {"name": formData.get("name"),
"description": formData.get("description"),
"duration": formData.get("duration"),
"calories": formData.get("calories"),
"muscleGroup": formData.get("muscleGroup"),
"unit": formData.get("unit")};
let body = {
name: formData.get("name"),
description: formData.get("description"),
duration: formData.get("duration"),
calories: formData.get("calories"),
muscleGroup: formData.get("muscleGroup"),
unit: formData.get("unit"),
};
let response = await sendRequest("POST", `${HOST}/api/exercises/`, body);
......@@ -87,7 +86,7 @@ async function createExercise() {
function handleEditExerciseButtonClick() {
setReadOnly(false, "#form-exercise");
document.querySelector("select").removeAttribute("disabled")
document.querySelector("select").removeAttribute("disabled");
editButton.className += " hide";
okButton.className = okButton.className.replace(" hide", "");
......@@ -114,26 +113,26 @@ async function deleteExercise(id) {
async function retrieveExercise(id) {
let response = await sendRequest("GET", `${HOST}/api/exercises/${id}/`);
console.log(response.ok)
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 {
document.querySelector("select").removeAttribute("disabled")
document.querySelector("select").removeAttribute("disabled");
let exerciseData = await response.json();
let form = document.querySelector("#form-exercise");
let formData = new FormData(form);
for (let key of formData.keys()) {
let selector
key !== "muscleGroup" ? selector = `input[name="${key}"], textarea[name="${key}"]` : selector = `select[name=${key}]`
let selector =
key !== "muscleGroup" ? `input[name="${key}"], textarea[name="${key}"]` : `select[name=${key}]`;
let input = form.querySelector(selector);
let newVal = exerciseData[key];
input.value = newVal;
}
document.querySelector("select").setAttribute("disabled", "")
document.querySelector("select").setAttribute("disabled", "");
}
}
......@@ -141,17 +140,19 @@ async function updateExercise(id) {
let form = document.querySelector("#form-exercise");
let formData = new FormData(form);
let muscleGroupSelector = document.querySelector("select")
muscleGroupSelector.removeAttribute("disabled")
let muscleGroupSelector = document.querySelector("select");
muscleGroupSelector.removeAttribute("disabled");
let selectedMuscleGroup = new MuscleGroup(formData.get("muscleGroup"));
let body = {"name": formData.get("name"),
"description": formData.get("description"),
"duration": formData.get("duration"),
"calories": formData.get("calories"),
"muscleGroup": selectedMuscleGroup.getMuscleGroupType(),
"unit": formData.get("unit")};
let body = {
name: formData.get("name"),
description: formData.get("description"),
duration: formData.get("duration"),
calories: formData.get("calories"),
muscleGroup: selectedMuscleGroup.getMuscleGroupType(),
unit: formData.get("unit"),
};
let response = await sendRequest("PUT", `${HOST}/api/exercises/${id}/`, body);
if (!response.ok) {
......@@ -159,23 +160,7 @@ async function updateExercise(id) {
let alert = createAlert(`Could not update exercise ${id}`, data);
document.body.prepend(alert);
} else {
muscleGroupSelector.setAttribute("disabled", "")
// 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("duration");
oldFormData.delete("calories");
oldFormData.delete("muscleGroup");
oldFormData.delete("unit");
handleCancelButtonDuringEdit(true);
}
}
......@@ -189,23 +174,22 @@ window.addEventListener("DOMContentLoaded", async () => {
const urlParams = new URLSearchParams(window.location.search);
// view/edit
if (urlParams.has('id')) {
const exerciseId = urlParams.get('id');
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));
}
deleteButton.addEventListener("click", (async (id) => deleteExercise(id)).bind(undefined, exerciseId));
okButton.addEventListener("click", (async (id) => updateExercise(id)).bind(undefined, exerciseId));
return;
}
//create
else {
setReadOnly(false, "#form-exercise");
setReadOnly(false, "#form-exercise");
editButton.className += " hide";
okButton.className = okButton.className.replace(" hide", "");
cancelButton.className = cancelButton.className.replace(" hide", "");
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);
}
});
\ No newline at end of file
okButton.addEventListener("click", async () => createExercise());
cancelButton.addEventListener("click", handleCancelButtonDuringCreate);
});
let goBackButton;
let submitNewFileButton;
async function retrieveWorkoutImages(id) {
async function retrieveWorkoutImages(id) {
let workoutData = null;
let response = await sendRequest("GET", `${HOST}/api/workouts/${id}/`);
if (!response.ok) {
let data = await response.json();
let alert = createAlert("Could not retrieve workout data!", data);
document.body.prepend(alert);
} else {
workoutData = await response.json();
return workoutData;
}
workoutData = await response.json();
document.getElementById("workout-title").innerHTML = "Workout name: " + workoutData["name"];
document.getElementById("workout-owner").innerHTML = "Owner: " + workoutData["owner_username"];
document.getElementById("workout-title").innerHTML = "Workout name: " + workoutData["name"];
document.getElementById("workout-owner").innerHTML = "Owner: " + workoutData["owner_username"];
let hasNoImages = workoutData.files.length == 0;
let noImageText = document.querySelector("#no-images-text");
let hasNoImages = workoutData.files.length == 0;