Commit abbc6572 authored by olavhdi's avatar olavhdi
Browse files

Workouts PYLINT #28

parent dcbea426
Pipeline #126344 passed with stage
in 34 seconds
......@@ -3,7 +3,7 @@ Mixins for the workouts application
"""
class CreateListModelMixin(object):
class CreateListModelMixin:
"""Mixin that allows to create multiple objects from lists.
Taken from https://stackoverflow.com/a/48885641
"""
......
......@@ -137,7 +137,7 @@ class WorkoutSerializer(serializers.HyperlinkedModelSerializer):
# 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
exercise_instances.all(), exercise_instances_data
):
exercise_instance.exercise = exercise_instance_data.get(
"exercise", exercise_instance.exercise
......@@ -186,7 +186,8 @@ class WorkoutSerializer(serializers.HyperlinkedModelSerializer):
return instance
def get_owner_username(self, obj):
@staticmethod
def get_owner_username(obj):
"""Returns the owning user's username
Args:
......
"""
Tests for the workouts application.
"""
from rest_framework import permissions
from workouts.permissions import *
from users.models import User
from workouts.models import Workout
from django.test import TestCase
import datetime
from rest_framework import permissions
import pytz
from django.test import TestCase
from workouts.permissions import \
IsOwner, IsOwnerOfWorkout, IsCoachAndVisibleToCoach, \
IsCoachOfWorkoutAndVisibleToCoach, IsPublic, IsWorkoutPublic, \
IsReadOnly
from workouts.models import Workout
from users.models import User
# Create your tests here.
class MockRequest():
class MockRequest:
"""
Mocks a request
"""
def __init__(self):
self.method = ""
self.data = ""
self.user = None
class MockWorkout():
class MockWorkout:
"""
Mocks a workout for testing
"""
def __init__(self):
try:
user = User.objects.get(pk='1')
except:
except User.DoesNotExist:
user = User.objects.create()
workout_data = {
......@@ -34,7 +44,11 @@ class MockWorkout():
self.workout = Workout.objects.create(**workout_data)
self.workout.owner.coach = User()
class IsOwnerTestCase(TestCase):
"""
Checks ownership permissions
"""
def setUp(self):
self.is_owner = IsOwner()
self.request = MockRequest()
......@@ -42,76 +56,121 @@ class IsOwnerTestCase(TestCase):
self.request.user = User()
def test_has_object_permission(self):
self.assertFalse(self.is_owner.has_object_permission(self.request, None, self.workout.workout))
"""
Validates object permission
"""
self.assertFalse(self.is_owner.has_object_permission(
self.request, None, self.workout.workout
))
self.request.user = self.workout.workout.owner
self.assertTrue(self.is_owner.has_object_permission(self.request, None, self.workout.workout))
self.assertTrue(self.is_owner.has_object_permission(
self.request, None, self.workout.workout
))
class IsOwnerOfWorkoutTestCase(TestCase):
"""
Validates owner of workout permissions
"""
def setUp(self):
self.is_owner_of_workout = IsOwnerOfWorkout()
self.request = MockRequest()
self.workout = MockWorkout()
def test_has_permission_method(self):
"""
Get permission
"""
request = MockRequest()
request.method = "GET"
self.assertTrue(self.is_owner_of_workout.has_permission(request, None))
def test_has_permission_workout(self):
"""
POST workout permission
"""
request = MockRequest()
request.method = "POST"
request.data = {"workout": ""}
self.assertFalse(self.is_owner_of_workout.has_permission(request, None))
def test_has_permission_user(self):
"""
POST workout permission via REST API
"""
request = MockRequest()
request.method = "POST"
request.user = self.workout.workout.owner
request.data = {"workout": "http://localhost:8000/api/workouts/1/"}
self.assertTrue(self.is_owner_of_workout.has_permission(request, None))
request.data = {
"workout": "http://localhost:8000/api/workouts/1/"
}
self.assertTrue(self.is_owner_of_workout.has_permission(
request, None
))
def test_has_object_permission(self):
self.assertFalse(self.is_owner_of_workout.has_object_permission(self.request, None, self.workout))
"""
Ownership permissions
"""
self.assertFalse(self.is_owner_of_workout.has_object_permission(
self.request, None, self.workout
))
self.request.user = self.workout.workout.owner
self.assertTrue(self.is_owner_of_workout.has_object_permission(self.request, None, self.workout))
self.assertTrue(self.is_owner_of_workout.has_object_permission(
self.request, None, self.workout
))
class IsCoachAndVisibleToCoachTestCase(TestCase):
"""
Coach permissions
"""
def setUp(self):
self.is_coach_and_visable_to_coach = IsCoachAndVisibleToCoach()
self.is_coach_and_visible_to_coach = IsCoachAndVisibleToCoach()
self.request = MockRequest()
self.request.user = User()
self.workout = MockWorkout()
def test_has_object_permission(self):
self.assertFalse(self.is_coach_and_visable_to_coach.has_object_permission(
"""
Validates object permissions
"""
self.assertFalse(self.is_coach_and_visible_to_coach.has_object_permission(
self.request,
None,
self.workout.workout
))
self.workout.workout.owner.coach = self.request.user
self.assertTrue(self.is_coach_and_visable_to_coach.has_object_permission(
self.assertTrue(self.is_coach_and_visible_to_coach.has_object_permission(
self.request,
None,
self.workout.workout
))
class IsCoachOfWorkoutAndVisibleToCoachTestCase(TestCase):
"""
Validates coach relation and permissions for a specific workout
"""
def setUp(self):
self.is_coach_of_workout_and_visable_to_coach = IsCoachOfWorkoutAndVisibleToCoach()
self.is_coach_of_workout_and_visible_to_coach = IsCoachOfWorkoutAndVisibleToCoach()
self.request = MockRequest()
self.request.user = User()
self.workout = MockWorkout()
def test_has_object_permission(self):
self.assertFalse(self.is_coach_of_workout_and_visable_to_coach.has_object_permission(
"""
Validates coach permissions
"""
self.assertFalse(self.is_coach_of_workout_and_visible_to_coach.has_object_permission(
self.request,
None,
self.workout
))
self.workout.workout.owner.coach = self.request.user
self.assertTrue(self.is_coach_of_workout_and_visable_to_coach.has_object_permission(
self.assertTrue(self.is_coach_of_workout_and_visible_to_coach.has_object_permission(
self.request,
None,
self.workout
......@@ -119,50 +178,70 @@ class IsCoachOfWorkoutAndVisibleToCoachTestCase(TestCase):
class IsPublicTestCase(TestCase):
"""
Validates test case public permissions
"""
def setUp(self):
self.workout = MockWorkout()
self.is_public = IsPublic()
def test_has_object_permission(self):
"""
Validates workout permissions
"""
self.assertTrue(self.is_public.has_object_permission(
None,
None,
self.workout.workout)
)
self.workout.workout
))
self.workout.workout.visibility = "CO"
self.assertFalse(self.is_public.has_object_permission(
None,
None,
self.workout.workout)
)
self.workout.workout
))
class IsWorkoutPublicTestCase(TestCase):
"""
Checks that a workout is publicly available
"""
def setUp(self):
self.workout = MockWorkout()
self.is_workout_public = IsWorkoutPublic()
def test_has_object_permission(self):
"""
Validates workout permissions
"""
self.assertTrue(self.is_workout_public.has_object_permission(
None,
None,
self.workout)
)
self.workout
))
self.workout.workout.visibility = "N"
self.assertFalse(self.is_workout_public.has_object_permission(
None,
None,
self.workout)
)
self.workout
))
class IsReadOnlyTestCase(TestCase):
"""
Checks that objects have the correct read only permission
"""
def setUp(self):
self.is_read_only = IsReadOnly()
self.request = MockRequest()
self.request.method = permissions.SAFE_METHODS.__getitem__(1)
def test_has_object_permission(self):
"""
Validates that the test has the correct object permissions
"""
self.assertTrue(self.is_read_only.has_object_permission(self.request, None, None))
self.request.method = None
......
"""
Connecting URL paths to django.apps.workouts
"""
from django.urls import path, include
from workouts import views
from rest_framework.urlpatterns import format_suffix_patterns
from rest_framework_simplejwt.views import (
TokenObtainPairView,
TokenRefreshView,
)
from workouts import views
# This is a bit messy and will need to change
urlpatterns = format_suffix_patterns(
[
......
"""Contains views for the workouts application. These are mostly class-based views.
"""
Contains views for the workouts application. These are mostly class-based views.
"""
import base64
import pickle
from collections import namedtuple
from django.db.models import Q
from django.core.exceptions import PermissionDenied
from django.core.signing import Signer
from rest_framework import generics, mixins
from rest_framework import permissions
from rest_framework import filters
from rest_framework.parsers import (
JSONParser,
)
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 rest_framework import filters
from rest_framework.response import Response
from rest_framework_simplejwt.tokens import RefreshToken
from workouts.parsers import MultipartJsonParser
from workouts.permissions import (
IsOwner,
......@@ -26,55 +32,59 @@ from workouts.models import Workout, Exercise, ExerciseInstance, WorkoutFile
from workouts.serializers import WorkoutSerializer, ExerciseSerializer
from workouts.serializers import RememberMeSerializer
from workouts.serializers import ExerciseInstanceSerializer, WorkoutFileSerializer
from django.core.exceptions import PermissionDenied
from rest_framework_simplejwt.tokens import RefreshToken
from rest_framework.response import Response
import json
from collections import namedtuple
import base64, pickle
from django.core.signing import Signer
@api_view(["GET"])
def api_root(request, format=None):
def api_root(request, serializer_format=None):
"""
Define api root
"""
return Response(
{
"users": reverse("user-list", request=request, format=format),
"workouts": reverse("workout-list", request=request, format=format),
"exercises": reverse("exercise-list", request=request, format=format),
"users": reverse("user-list", request=request, format=serializer_format),
"workouts": reverse("workout-list", request=request, format=serializer_format),
"exercises": reverse("exercise-list", request=request, format=serializer_format),
"exercise-instances": reverse(
"exercise-instance-list", request=request, format=format
"exercise-instance-list", request=request, format=serializer_format
),
"workout-files": reverse(
"workout-file-list", request=request, format=format
"workout-file-list", request=request, format=serializer_format
),
"comments": reverse("comment-list", request=request, format=format),
"likes": reverse("like-list", request=request, format=format),
"comments": reverse("comment-list", request=request, format=serializer_format),
"likes": reverse("like-list", request=request, format=serializer_format),
}
)
# Allow users to save a persistent session in their browser
class RememberMe(
mixins.ListModelMixin,
mixins.CreateModelMixin,
mixins.DestroyModelMixin,
generics.GenericAPIView,
mixins.ListModelMixin,
mixins.CreateModelMixin,
mixins.DestroyModelMixin,
generics.GenericAPIView,
):
"""
Creates cookie for session storage
"""
serializer_class = RememberMeSerializer
def get(self, request):
if request.user.is_authenticated == False:
"""
Retrieves cookie if user is authenticated
"""
if not request.user.is_authenticated:
raise PermissionDenied
else:
return Response({"remember_me": self.rememberme()})
return Response({"remember_me": self.rememberme()})
def post(self, request):
cookieObject = namedtuple("Cookies", request.COOKIES.keys())(
"""
Use refresh token to get new cookies
"""
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,26 +93,37 @@ class RememberMe(
}
)
def get_user(self, cookieObject):
decode = base64.b64decode(cookieObject.remember_me)
def get_user(self, cookie_object):
"""
Fetches user
"""
decode = base64.b64decode(cookie_object.remember_me)
user, sign = pickle.loads(decode)
# Validate signature
if sign == self.sign_user(user):
return user
raise PermissionDenied
def rememberme(self):
creds = [self.request.user, self.sign_user(str(self.request.user))]
return base64.b64encode(pickle.dumps(creds))
def sign_user(self, username):
"""
Returns the rememberme token
"""
credentials = [self.request.user, self.sign_user(str(self.request.user))]
return base64.b64encode(pickle.dumps(credentials))
@staticmethod
def sign_user(username):
"""
Creates a signing hash based on the username
"""
signer = Signer()
signed_user = signer.sign(username)
return signed_user
class WorkoutList(
mixins.ListModelMixin, mixins.CreateModelMixin, generics.GenericAPIView
mixins.ListModelMixin, mixins.CreateModelMixin, generics.GenericAPIView
):
"""Class defining the web response for the creation of a Workout, or displaying a list
of Workouts
......@@ -122,15 +143,27 @@ class WorkoutList(
ordering_fields = ["name", "date", "owner__username"]
def get(self, request, *args, **kwargs):
"""
Get list of visible workouts
"""
return self.list(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
"""
Create multiple workouts
"""
return self.create(request, *args, **kwargs)
def perform_create(self, serializer):
"""
Saves ownership
"""
serializer.save(owner=self.request.user)
def get_queryset(self):
"""
Return all workouts you should be able to view
"""
qs = Workout.objects.none()
if self.request.user:
# A workout should be visible to the requesting user if any of the following hold:
......@@ -146,10 +179,10 @@ class WorkoutList(
class WorkoutDetail(
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
generics.GenericAPIView,
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
generics.GenericAPIView,
):
"""Class defining the web response for the details of an individual Workout.
......@@ -165,17 +198,26 @@ class WorkoutDetail(
parser_classes = [MultipartJsonParser, JSONParser]
def get(self, request, *args, **kwargs):
"""
Retrieve a workout
"""
return self.retrieve(request, *args, **kwargs)
def put(self, request, *args, **kwargs):
"""
Update a workout
"""
return self.update(request, *args, **kwargs)
def delete(self, request, *args, **kwargs):
"""
Delete a workout
"""
return self.destroy(request, *args, **kwargs)
class ExerciseList(
mixins.ListModelMixin, mixins.CreateModelMixin, generics.GenericAPIView
mixins.ListModelMixin, mixins.CreateModelMixin, generics.GenericAPIView
):
"""Class defining the web response for the creation of an Exercise, or
a list of Exercises.
......@@ -188,17 +230,23 @@ class ExerciseList(
permission_classes = [permissions.IsAuthenticated]
def get(self, request, *args, **kwargs):
"""
Retrieve visible exercises
"""
return self.list(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
"""
Create exercise(s)
"""
return self.create(request, *args, **kwargs)
class ExerciseDetail(
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
generics.GenericAPIView,
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
generics.GenericAPIView,
):
"""Class defining the web response for the details of an individual Exercise.
......@@ -210,36 +258,60 @@ class ExerciseDetail(
permission_classes = [permissions.IsAuthenticated]
def get(self, request, *args, **kwargs):
"""
Retrieve exercise
"""
return self.retrieve(request, *args, **kwargs)
def put(self, request, *args, **kwargs):
"""
Update exercise
"""
return self.update(request, *args, **kwargs)
def patch(self, request, *args, **kwargs):
"""
Update parts of exercise
"""
return self.partial_update(request, *args, **kwargs)
def delete(self, request, *args, **kwargs):
"""
Delete exercise
"""
return self.destroy(request, *args, **kwargs)
class ExerciseInstanceList(
mixins.ListModelMixin,
mixins.CreateModelMixin,
CreateListModelMixin,
generics.GenericAPIView,
mixins.ListModelMixin,
mixins.CreateModelMixin,
CreateListModelMixin,
generics.GenericAPIView,
):
"""Class defining the web response for the creation"""
"""Class defining the web response for a list of excercise instances.
HTTP methods: GET, POST
"""
serializer_class = ExerciseInstanceSerializer
permission_classes = [permissions.IsAuthenticated & IsOwnerOfWorkout]
def get(self, request, *args, **kwargs):