Skip to content
GitLab
Menu
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
Håvard Farestveit
tdt4242-base
Commits
abbc6572
Commit
abbc6572
authored
Apr 08, 2021
by
olavhdi
Browse files
Workouts PYLINT
#28
parent
dcbea426
Pipeline
#126344
passed with stage
in 34 seconds
Changes
5
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
backend/secfit/workouts/mixins.py
View file @
abbc6572
...
...
@@ -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
"""
...
...
backend/secfit/workouts/serializers.py
View file @
abbc6572
...
...
@@ -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:
...
...
backend/secfit/workouts/tests.py
View file @
abbc6572
"""
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_vis
a
ble_to_coach
=
IsCoachAndVisibleToCoach
()
self
.
is_coach_and_vis
i
ble_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_vis
a
ble_to_coach
.
has_object_permission
(
self
.
assertTrue
(
self
.
is_coach_and_vis
i
ble_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_vis
a
ble_to_coach
=
IsCoachOfWorkoutAndVisibleToCoach
()
self
.
is_coach_of_workout_and_vis
i
ble_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_vis
a
ble_to_coach
.
has_object_permission
(
self
.
assertTrue
(
self
.
is_coach_of_workout_and_vis
i
ble_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
...
...
backend/secfit/workouts/urls.py
View file @
abbc6572
"""
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
(
[
...
...
backend/secfit/workouts/views.py
View file @
abbc6572
"""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
(
cookie
O
bject
)
user
=
self
.
get_user
(
cookie
_o
bject
)
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
):