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): ...@@ -11,7 +11,7 @@ class IsCommentVisibleToUser(permissions.BasePermission):
- The comment is on a workout owned by the user - 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. # Write permissions are only allowed to the owner.
return ( return (
obj.workout.visibility == "PU" obj.workout.visibility == "PU"
......
from django.shortcuts import render
from rest_framework import generics, mixins from rest_framework import generics, mixins
from comments.models import Comment, Like from comments.models import Comment, Like
from rest_framework import permissions from rest_framework import permissions
...@@ -12,7 +11,6 @@ from rest_framework.filters import OrderingFilter ...@@ -12,7 +11,6 @@ from rest_framework.filters import OrderingFilter
class CommentList( class CommentList(
mixins.ListModelMixin, mixins.CreateModelMixin, generics.GenericAPIView mixins.ListModelMixin, mixins.CreateModelMixin, generics.GenericAPIView
): ):
# queryset = Comment.objects.all()
serializer_class = CommentSerializer serializer_class = CommentSerializer
permission_classes = [permissions.IsAuthenticated] permission_classes = [permissions.IsAuthenticated]
filter_backends = [OrderingFilter] filter_backends = [OrderingFilter]
...@@ -40,29 +38,24 @@ class CommentList( ...@@ -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 coach visibility workout and the user is the workout owner's coach
- The comment is on a workout owned by the user - 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. qs = Comment.objects.all()
# Or maybe not. for index in range(len(qs)):
if not IsCommentVisibleToUser().has_object_permission(self.request, qs[index]):
qs = Comment.objects.filter( qs.remove(qs[index])
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()
return qs return qs
# Details of comment
class CommentDetail( class CommentDetail(
mixins.RetrieveModelMixin, mixins.RetrieveModelMixin,
mixins.UpdateModelMixin, mixins.UpdateModelMixin,
mixins.DestroyModelMixin, mixins.DestroyModelMixin,
generics.GenericAPIView, generics.GenericAPIView,
): ):
'''Details of comment
'''
queryset = Comment.objects.all() queryset = Comment.objects.all()
serializer_class = CommentSerializer serializer_class = CommentSerializer
permission_classes = [ permission_classes = [
...@@ -81,6 +74,10 @@ class CommentDetail( ...@@ -81,6 +74,10 @@ class CommentDetail(
# List of likes # List of likes
class LikeList(mixins.ListModelMixin, mixins.CreateModelMixin, generics.GenericAPIView): class LikeList(mixins.ListModelMixin, mixins.CreateModelMixin, generics.GenericAPIView):
'''Details of likes
'''
serializer_class = LikeSerializer serializer_class = LikeSerializer
permission_classes = [permissions.IsAuthenticated] permission_classes = [permissions.IsAuthenticated]
...@@ -97,13 +94,15 @@ class LikeList(mixins.ListModelMixin, mixins.CreateModelMixin, generics.GenericA ...@@ -97,13 +94,15 @@ class LikeList(mixins.ListModelMixin, mixins.CreateModelMixin, generics.GenericA
return Like.objects.filter(owner=self.request.user) return Like.objects.filter(owner=self.request.user)
# Details of like
class LikeDetail( class LikeDetail(
mixins.RetrieveModelMixin, mixins.RetrieveModelMixin,
mixins.UpdateModelMixin, mixins.UpdateModelMixin,
mixins.DestroyModelMixin, mixins.DestroyModelMixin,
generics.GenericAPIView, generics.GenericAPIView,
): ):
'''Details of like
'''
queryset = Like.objects.all() queryset = Like.objects.all()
serializer_class = LikeSerializer serializer_class = LikeSerializer
permission_classes = [permissions.IsAuthenticated] permission_classes = [permissions.IsAuthenticated]
......
...@@ -11,7 +11,6 @@ class CustomUserAdmin(UserAdmin): ...@@ -11,7 +11,6 @@ class CustomUserAdmin(UserAdmin):
add_form = CustomUserCreationForm add_form = CustomUserCreationForm
form = CustomUserChangeForm form = CustomUserChangeForm
model = get_user_model() model = get_user_model()
# list_display = UserAdmin.list_display + ('coach',)
fieldsets = UserAdmin.fieldsets + ((None, {"fields": ("coach",)}),) fieldsets = UserAdmin.fieldsets + ((None, {"fields": ("coach",)}),)
add_fieldsets = UserAdmin.add_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 users import views
from rest_framework.urlpatterns import format_suffix_patterns
urlpatterns = [ urlpatterns = [
path("api/users/", views.UserList.as_view(), name="user-list"), path("api/users/", views.UserList.as_view(), name="user-list"),
......
import django
from rest_framework import mixins, generics from rest_framework import mixins, generics
from workouts.mixins import CreateListModelMixin from workouts.mixins import CreateListModelMixin
from rest_framework import permissions from rest_framework import permissions
...@@ -10,15 +9,11 @@ from users.serializers import ( ...@@ -10,15 +9,11 @@ from users.serializers import (
UserGetSerializer, UserGetSerializer,
) )
from rest_framework.permissions import ( from rest_framework.permissions import (
AllowAny,
IsAdminUser,
IsAuthenticated,
IsAuthenticatedOrReadOnly, IsAuthenticatedOrReadOnly,
) )
from users.models import Offer, AthleteFile from users.models import Offer, AthleteFile
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.db.models import Q from django.db.models import Q
from django.shortcuts import get_object_or_404
from rest_framework.parsers import MultiPartParser, FormParser from rest_framework.parsers import MultiPartParser, FormParser
from users.permissions import IsCurrentUser, IsAthlete, IsCoach from users.permissions import IsCurrentUser, IsAthlete, IsCoach
from workouts.permissions import IsOwner, IsReadOnly from workouts.permissions import IsOwner, IsReadOnly
...@@ -98,7 +93,6 @@ class OfferList( ...@@ -98,7 +93,6 @@ class OfferList(
serializer.save(owner=self.request.user) serializer.save(owner=self.request.user)
def get_queryset(self): def get_queryset(self):
qs = Offer.objects.none()
result = Offer.objects.none() result = Offer.objects.none()
if self.request.user: if self.request.user:
...@@ -106,22 +100,20 @@ class OfferList( ...@@ -106,22 +100,20 @@ class OfferList(
Q(owner=self.request.user) | Q(recipient=self.request.user) Q(owner=self.request.user) | Q(recipient=self.request.user)
).distinct() ).distinct()
qp = self.request.query_params qp = self.request.query_params
u = self.request.user user = self.request.user
# filtering by status (if provided) status = qp.get("status", None)
s = qp.get("status", None) if status is not None and self.request is not None:
if s is not None and self.request is not None: qs = qs.filter(status=status)
qs = qs.filter(status=s)
if qp.get("status", None) is None: if qp.get("status", None) is None:
qs = Offer.objects.filter(Q(owner=u)).distinct() qs = Offer.objects.filter(Q(owner=user)).distinct()
# filtering by category (sent or received) category = qp.get("category", None)
c = qp.get("category", None) if category is not None and qp is not None:
if c is not None and qp is not None: if category == "sent":
if c == "sent": qs = qs.filter(owner=user)
qs = qs.filter(owner=u) elif category == "received":
elif c == "received": qs = qs.filter(recipient=user)
qs = qs.filter(recipient=u)
return qs return qs
else: else:
return result return result
......
...@@ -2,29 +2,9 @@ ...@@ -2,29 +2,9 @@
log workouts (Workout), which contain instances (ExerciseInstance) of various log workouts (Workout), which contain instances (ExerciseInstance) of various
type of exercises (Exercise). The user can also upload files (WorkoutFile) . type of exercises (Exercise). The user can also upload files (WorkoutFile) .
""" """
import os
from django.db import models 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 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. # Create your models here.
class Workout(models.Model): class Workout(models.Model):
"""Django model for a workout that users can log. """Django model for a workout that users can log.
......
...@@ -109,33 +109,20 @@ class WorkoutSerializer(serializers.HyperlinkedModelSerializer): ...@@ -109,33 +109,20 @@ class WorkoutSerializer(serializers.HyperlinkedModelSerializer):
return workout return workout
def update(self, instance, validated_data): def update_exercises(self, validated_data, instance):
"""Custom logic for updating a Workout with its ExerciseInstances and Workouts. """Custom logic and helper function for updating a Workout with its ExerciseInstances.
This updates existing exercise instances without adding or deleting object.
This is needed because each object in both exercise_instances and files must be iterated zip() will yield n 2-tuples, where n is min(len(exercise_instance), len(exercise_instance_data))
over and handled individually.
Args: Args:
instance (Workout): Current Workout object
validated_data: Contains data for validated fields 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_data = validated_data.pop("exercise_instances")
exercise_instances = instance.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( for exercise_instance, exercise_instance_data in zip(
exercise_instances.all(), exercise_instances_data exercise_instances.all(), exercise_instances_data
): ):
...@@ -162,7 +149,15 @@ class WorkoutSerializer(serializers.HyperlinkedModelSerializer): ...@@ -162,7 +149,15 @@ class WorkoutSerializer(serializers.HyperlinkedModelSerializer):
for i in range(len(exercise_instances_data), len(exercise_instances.all())): for i in range(len(exercise_instances_data), len(exercise_instances.all())):
exercise_instances.all()[i].delete() 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: if "files" in validated_data:
files_data = validated_data.pop("files") files_data = validated_data.pop("files")
...@@ -184,6 +179,31 @@ class WorkoutSerializer(serializers.HyperlinkedModelSerializer): ...@@ -184,6 +179,31 @@ class WorkoutSerializer(serializers.HyperlinkedModelSerializer):
for i in range(len(files_data), len(files.all())): for i in range(len(files_data), len(files.all())):
files.all()[i].delete() 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 return instance
def get_owner_username(self, obj): def get_owner_username(self, obj):
......
...@@ -54,13 +54,15 @@ def api_root(request, format=None): ...@@ -54,13 +54,15 @@ def api_root(request, format=None):
) )
# Allow users to save a persistent session in their browser
class RememberMe( class RememberMe(
mixins.ListModelMixin, mixins.ListModelMixin,
mixins.CreateModelMixin, mixins.CreateModelMixin,
mixins.DestroyModelMixin, mixins.DestroyModelMixin,
generics.GenericAPIView, generics.GenericAPIView,
): ):
"""Allow users to save a persistent session in their browser
"""
serializer_class = RememberMeSerializer serializer_class = RememberMeSerializer
...@@ -71,10 +73,10 @@ class RememberMe( ...@@ -71,10 +73,10 @@ class RememberMe(
return Response({"remember_me": self.rememberme()}) return Response({"remember_me": self.rememberme()})
def post(self, request): def post(self, request):
cookieObject = namedtuple("Cookies", request.COOKIES.keys())( cookie_object = namedtuple("Cookies", request.COOKIES.keys())(
*request.COOKIES.values() *request.COOKIES.values()
) )
user = self.get_user(cookieObject) user = self.get_user(cookie_object)
refresh = RefreshToken.for_user(user) refresh = RefreshToken.for_user(user)
return Response( return Response(
{ {
...@@ -83,8 +85,8 @@ class RememberMe( ...@@ -83,8 +85,8 @@ class RememberMe(
} }
) )
def get_user(self, cookieObject): def get_user(self, cookie_object):
decode = base64.b64decode(cookieObject.remember_me) decode = base64.b64decode(cookie_object.remember_me)
user, sign = pickle.loads(decode) user, sign = pickle.loads(decode)
# Validate signature # Validate signature
......
...@@ -4,36 +4,32 @@ let deleteButton; ...@@ -4,36 +4,32 @@ let deleteButton;
let editButton; let editButton;
let oldFormData; let oldFormData;
class MuscleGroup { class MuscleGroup {
constructor(type) { isValidType = false;
this.isValidType = false; validTypes = ["Legs", "Chest", "Back", "Arms", "Abdomen", "Shoulders"];
this.validTypes = ["Legs", "Chest", "Back", "Arms", "Abdomen", "Shoulders"]
constructor(type) {
this.type = this.validTypes.includes(type) ? type : undefined; this.type = this.validTypes.includes(type) ? type : undefined;
}; }
setMuscleGroupType = (newType) => { setMuscleGroupType = (newType) => {
this.isValidType = false; if (!this.validTypes.includes(newType)) {
this.isValidType = false;
if(this.validTypes.includes(newType)){
this.isValidType = true;
this.type = newType;
}
else{
alert("Invalid muscle group!"); alert("Invalid muscle group!");
return;
} }
this.isValidType = true;
this.type = newType;
}; };
getMuscleGroupType = () => { getMuscleGroupType = () => {
console.log(this.type, "SWIOEFIWEUFH")
return this.type; return this.type;
} };
} }
function handleCancelButtonDuringEdit() { function handleCancelButtonDuringEdit(e, afterUpdate = false) {
setReadOnly(true, "#form-exercise"); setReadOnly(true, "#form-exercise");
document.querySelector("select").setAttribute("disabled", "") document.querySelector("select").setAttribute("disabled", "");
okButton.className += " hide"; okButton.className += " hide";
deleteButton.className += " hide"; deleteButton.className += " hide";
cancelButton.className += " hide"; cancelButton.className += " hide";
...@@ -41,21 +37,22 @@ function handleCancelButtonDuringEdit() { ...@@ -41,21 +37,22 @@ function handleCancelButtonDuringEdit() {
cancelButton.removeEventListener("click", handleCancelButtonDuringEdit); cancelButton.removeEventListener("click", handleCancelButtonDuringEdit);
let form = document.querySelector("#form-exercise"); if (!afterUpdate) {
if (oldFormData.has("name")) form.name.value = oldFormData.get("name"); let form = document.querySelector("#form-exercise");
if (oldFormData.has("description")) form.description.value = oldFormData.get("description"); if (oldFormData.has("name")) form.name.value = oldFormData.get("name");
if (oldFormData.has("duration")) form.duration.value = oldFormData.get("duration"); if (oldFormData.has("description")) form.description.value = oldFormData.get("description");
if (oldFormData.has("calories")) form.calories.value = oldFormData.get("calories"); if (oldFormData.has("duration")) form.duration.value = oldFormData.get("duration");
if (oldFormData.has("muscleGroup")) form.muscleGroup.value = oldFormData.get("muscleGroup"); if (oldFormData.has("calories")) form.calories.value = oldFormData.get("calories");
if (oldFormData.has("unit")) form.unit.value = oldFormData.get("unit"); 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("name");
oldFormData.delete("description"); oldFormData.delete("description");
oldFormData.delete("duration"); oldFormData.delete("duration");
oldFormData.delete("calories"); oldFormData.delete("calories");
oldFormData.delete("muscleGroup"); oldFormData.delete("muscleGroup");
oldFormData.delete("unit"); oldFormData.delete("unit");
} }
function handleCancelButtonDuringCreate() { function handleCancelButtonDuringCreate() {
...@@ -63,15 +60,17 @@ function handleCancelButtonDuringCreate() { ...@@ -63,15 +60,17 @@ function handleCancelButtonDuringCreate() {
} }
async function createExercise() { async function createExercise() {
document.querySelector("select").removeAttribute("disabled") document.querySelector("select").removeAttribute("disabled");
let form = document.querySelector("#form-exercise"); let form = document.querySelector("#form-exercise");
let formData = new FormData(form); let formData = new FormData(form);
let body = {"name": formData.get("name"), let body = {
"description": formData.get("description"), name: formData.get("name"),
"duration": formData.get("duration"), description: formData.get("description"),
"calories": formData.get("calories"), duration: formData.get("duration"),
"muscleGroup": formData.get("muscleGroup"), calories: formData.get("calories"),
"unit": formData.get("unit")}; muscleGroup: formData.get("muscleGroup"),
unit: formData.get("unit"),
};
let response = await sendRequest("POST", `${HOST}/api/exercises/`, body); let response = await sendRequest("POST", `${HOST}/api/exercises/`, body);
...@@ -87,7 +86,7 @@ async function createExercise() { ...@@ -87,7 +86,7 @@ async function createExercise() {
function handleEditExerciseButtonClick() { function handleEditExerciseButtonClick() {
setReadOnly(false, "#form-exercise"); setReadOnly(false, "#form-exercise");
document.querySelector("select").removeAttribute("disabled") document.querySelector("select").removeAttribute("disabled");
editButton.className += " hide"; editButton.className += " hide";
okButton.className = okButton.className.replace(" hide", ""); okButton.className = okButton.className.replace(" hide", "");
...@@ -114,26 +113,26 @@ async function deleteExercise(id) { ...@@ -114,26 +113,26 @@ async function deleteExercise(id) {
async function retrieveExercise(id) { async function retrieveExercise(id) {
let response = await sendRequest("GET", `${HOST}/api/exercises/${id}/`); let response = await sendRequest("GET", `${HOST}/api/exercises/${id}/`);
console.log(response.ok) console.log(response.ok);