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
82a077c0
Commit
82a077c0
authored
Apr 19, 2021
by
Elias Larsen
Browse files
Merge branch 'master' into elias/fix/frontend
parents
2ae117b7
de11c207
Changes
11
Hide whitespace changes
Inline
Side-by-side
backend/secfit/secfit/settings.py
View file @
82a077c0
...
...
@@ -12,21 +12,24 @@ https://docs.djangoproject.com/en/3.1/ref/settings/
from
pathlib
import
Path
import
os
from
typing
import
Dict
import
dj_database_url
import
dotenv
# Get the GROUPID variable to accept connections from the application server and NGINX
groupid
=
os
.
environ
.
get
(
"GROUPID"
,
"0"
)
group
_
id
=
os
.
environ
.
get
(
"GROUPID"
,
"0"
)
# Email configuration
# The host must be running within NTNU's VPN (vpn.ntnu.no) to allow this config
# Usage: https://docs.djangoproject.com/en/3.1/topics/email/#obtaining-an-instance-of-an-email-backend
# Usage:
# https://docs.djangoproject.com/en/3.1/topics/email/#obtaining-an-instance-of-an-email-backend
EMAIL_BACKEND
=
"django.core.mail.backends.smtp.EmailBackend"
EMAIL_HOST
=
"mx.ntnu.no"
EMAIL_USE_TLS
=
False
EMAIL_PORT
=
25
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR
=
Path
(
__file__
).
resolve
(
strict
=
True
).
parent
.
parent
dotenv_file
=
os
.
path
.
join
(
BASE_DIR
,
".env"
)
...
...
@@ -42,8 +45,8 @@ ALLOWED_HOSTS = [
"127.0.0.1"
,
"localhost"
,
"0.0.0.0"
,
"10."
+
groupid
+
".0.6"
,
"10."
+
groupid
+
".0.4"
,
"10."
+
group
_
id
+
".0.6"
,
"10."
+
group
_
id
+
".0.4"
,
"molde.idi.ntnu.no"
,
"10.0.2.2"
,
"tdt4242-t11.herokuapp.com"
,
...
...
@@ -96,12 +99,11 @@ TEMPLATES = [
WSGI_APPLICATION
=
"secfit.wsgi.application"
# Database
# https://docs.djangoproject.com/en/3.1/ref/settings/#databases
if
'DATABASE_URL'
in
os
.
environ
:
DATABASES
=
{
DATABASES
:
Dict
[
str
,
Dict
]
=
{
"default"
:
{}
}
db_from_env
=
dj_database_url
.
config
(
conn_max_age
=
500
)
...
...
@@ -114,8 +116,6 @@ else:
}
}
# CORS Policy
CORS_ORIGIN_ALLOW_ALL
=
(
True
...
...
@@ -134,7 +134,6 @@ USE_L10N = True
USE_TZ
=
True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.1/howto/static-files/
...
...
@@ -146,7 +145,6 @@ STATIC_URL = "/static/"
MEDIA_ROOT
=
os
.
path
.
join
(
BASE_DIR
,
"media"
)
MEDIA_URL
=
"/media/"
REST_FRAMEWORK
=
{
"DEFAULT_PAGINATION_CLASS"
:
"rest_framework.pagination.PageNumberPagination"
,
"PAGE_SIZE"
:
10
,
...
...
backend/secfit/workouts/mixins.py
View file @
82a077c0
...
...
@@ -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/models.py
View file @
82a077c0
...
...
@@ -71,7 +71,7 @@ class Workout(models.Model):
class
Exercise
(
models
.
Model
):
"""Django model for an exercise type that users can create.
Each exercise instance must have an exercise type, e.g., Pushups, Crunches, or Lunges.
Each exercise instance must have an exercise type, e.g., Push
-
ups, Crunches, or Lunges.
Attributes:
name: Name of the exercise type
...
...
backend/secfit/workouts/parsers.py
View file @
82a077c0
...
...
@@ -3,6 +3,7 @@
import
json
from
rest_framework
import
parsers
# Thanks to https://stackoverflow.com/a/50514630
class
MultipartJsonParser
(
parsers
.
MultiPartParser
):
"""Parser for serializing multipart data containing both files and JSON.
...
...
@@ -24,10 +25,7 @@ class MultipartJsonParser(parsers.MultiPartParser):
data
[
key
]
=
value
continue
if
"{"
in
value
or
"["
in
value
:
try
:
data
[
key
]
=
json
.
loads
(
value
)
except
ValueError
:
data
[
key
]
=
value
data
[
key
]
=
get_key
(
value
)
else
:
data
[
key
]
=
value
...
...
@@ -36,3 +34,14 @@ class MultipartJsonParser(parsers.MultiPartParser):
new_files
[
"files"
].
append
({
"file"
:
file
})
return
parsers
.
DataAndFiles
(
data
,
new_files
)
def
get_key
(
value
):
"""
Tries to fetch a key from the dataset
"""
try
:
key
=
json
.
loads
(
value
)
except
ValueError
:
key
=
value
return
key
backend/secfit/workouts/serializers.py
View file @
82a077c0
...
...
@@ -94,50 +94,84 @@ class WorkoutSerializer(serializers.HyperlinkedModelSerializer):
Workout: A newly created Workout
"""
exercise_instances_data
=
validated_data
.
pop
(
"exercise_instances"
)
files
_data
=
[]
validated_
files
=
[]
if
"files"
in
validated_data
:
files
_data
=
validated_data
.
pop
(
"files"
)
validated_
files
=
validated_data
.
pop
(
"files"
)
workout
=
Workout
.
objects
.
create
(
**
validated_data
)
for
exercise_instance_data
in
exercise_instances_data
:
ExerciseInstance
.
objects
.
create
(
workout
=
workout
,
**
exercise_instance_data
)
for
file_data
in
files
_data
:
for
file_data
in
validated_
files
:
WorkoutFile
.
objects
.
create
(
workout
=
workout
,
owner
=
workout
.
owner
,
file
=
file_data
.
get
(
"file"
)
)
return
workout
def
update
(
self
,
instance
,
validated_data
):
"""Custom logic for updating a Workout with its ExerciseInstances and Workouts.
@
staticmethod
def
delete_files
(
instance
,
data
):
""""Removes the selected data from the instanced object
This is needed because each object in both exercise_instances and files must be iterated
over and handled individually.
Args:
instance: Data object
data: Data to remove
"""
for
i
in
range
(
len
(
data
),
len
(
instance
.
all
())):
instance
.
all
()[
i
].
delete
()
@
staticmethod
def
create_workout_files
(
instance
,
files
,
validated_files
):
"""Creates the workout files
Args:
instance (Workout): Current Workout object
validated_data: Contains data for validated fields
instance: Workout instance
files: Collection of files
validated_files: Data for validated fields
Returns:
Workout: Updated
Workout instance
instance:
Workout instance
"""
exercise_instances_data
=
validated_data
.
pop
(
"exercise_instances"
)
exercise_instances
=
instance
.
exercise_instances
for
i
in
range
(
len
(
files
.
all
()),
len
(
validated_files
)):
WorkoutFile
.
objects
.
create
(
workout
=
instance
,
owner
=
instance
.
owner
,
file
=
validated_files
[
i
].
get
(
"file"
),
)
return
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
()
@
staticmethod
def
create_exercise_instance
(
instance
,
exercise_instances
,
exercise_instances_data
):
"""Creates the workout files
# Handle ExerciseInstances
Args:
instance: Workout instance
exercise_instances: Collection of workouts
exercise_instances_data: Data for workout fields
Returns:
instance: Workout instance
"""
for
i
in
range
(
len
(
exercise_instances
.
all
()),
len
(
exercise_instances_data
)):
exercise_instance_data
=
exercise_instances_data
[
i
]
ExerciseInstance
.
objects
.
create
(
workout
=
instance
,
**
exercise_instance_data
)
return
instance
@
staticmethod
def
update_exercise_instances
(
exercise_instances
,
exercise_instances_data
):
"""Updates existing exercise instances without adding or deleting object
Args:
exercise_instances: Collection of workouts
exercise_instances_data: Data for workout fields
"""
# 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
exercise_instances
.
all
(),
exercise_instances_data
):
exercise_instance
.
exercise
=
exercise_instance_data
.
get
(
"exercise"
,
exercise_instance
.
exercise
...
...
@@ -150,43 +184,59 @@ class WorkoutSerializer(serializers.HyperlinkedModelSerializer):
)
exercise_instance
.
save
()
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
"""
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
self
.
update_exercise_instances
(
exercise_instances
,
exercise_instances_data
)
# If new exercise instances have been added to the workout, then create them
if
len
(
exercise_instances_data
)
>
len
(
exercise_instances
.
all
()):
for
i
in
range
(
len
(
exercise_instances
.
all
()),
len
(
exercise_instances_data
)):
exercise_instance_data
=
exercise_instances_data
[
i
]
ExerciseInstance
.
objects
.
create
(
workout
=
instance
,
**
exercise_instance_data
)
instance
=
self
.
create_exercise_instance
(
instance
,
exercise_instances
,
exercise_instances_data
)
# Else if exercise instances have been removed from the workout, then delete them
elif
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
()
self
.
delete_files
(
exercise_instances_data
,
exercise_instances
)
# Handle WorkoutFiles
if
"files"
in
validated_data
:
files
_data
=
validated_data
.
pop
(
"files"
)
validated_
files
=
validated_data
.
pop
(
"files"
)
files
=
instance
.
files
for
file
,
file_data
in
zip
(
files
.
all
(),
files
_data
):
for
file
,
file_data
in
zip
(
files
.
all
(),
validated_
files
):
file
.
file
=
file_data
.
get
(
"file"
,
file
.
file
)
# If new files have been added, creating new WorkoutFiles
if
len
(
files_data
)
>
len
(
files
.
all
()):
for
i
in
range
(
len
(
files
.
all
()),
len
(
files_data
)):
WorkoutFile
.
objects
.
create
(
workout
=
instance
,
owner
=
instance
.
owner
,
file
=
files_data
[
i
].
get
(
"file"
),
)
if
len
(
validated_files
)
>
len
(
files
.
all
()):
instance
=
self
.
create_workout_files
(
instance
,
files
,
validated_files
)
# Else if files have been removed, delete WorkoutFiles
elif
len
(
files_data
)
<
len
(
files
.
all
()):
for
i
in
range
(
len
(
files_data
),
len
(
files
.
all
())):
files
.
all
()[
i
].
delete
()
elif
len
(
validated_files
)
<
len
(
files
.
all
()):
self
.
delete_files
(
files
,
validated_files
)
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/__init__.py
0 → 100644
View file @
82a077c0
from
workouts.tests.workouts_serializer
import
WorkoutSerializerTestCase
from
workouts.tests.test
import
IsOwnerTestCase
,
IsOwnerOfWorkoutTestCase
,
IsCoachAndVisibleToCoachTestCase
,
IsCoachOfWorkoutAndVisibleToCoachTestCase
,
IsPublicTestCase
,
IsWorkoutPublicTestCase
,
IsReadOnlyTestCase
\ No newline at end of file
backend/secfit/workouts/tests.py
→
backend/secfit/workouts/tests
/test
.py
View file @
82a077c0
"""
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
:
"""
Imitates a (mutable) request
"""
def
__init__
(
self
):
self
.
method
=
""
self
.
data
=
""
self
.
user
=
None
class
MockWorkout
():
class
MockWorkout
:
"""
Imitates a workout
"""
def
__init__
(
self
):
try
:
user
=
User
.
objects
.
get
(
pk
=
'1'
)
except
:
except
User
.
DoesNotExist
:
user
=
User
.
objects
.
create
()
workout_data
=
{
...
...
@@ -34,7 +43,11 @@ class MockWorkout():
self
.
workout
=
Workout
.
objects
.
create
(
**
workout_data
)
self
.
workout
.
owner
.
coach
=
User
()
class
IsOwnerTestCase
(
TestCase
):
"""
Ownership permissions
"""
def
setUp
(
self
):
self
.
is_owner
=
IsOwner
()
self
.
request
=
MockRequest
()
...
...
@@ -42,128 +55,193 @@ 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
))
"""
Object permission
"""
self
.
assertIs
(
self
.
is_owner
.
has_object_permission
(
self
.
request
,
None
,
self
.
workout
.
workout
),
False
)
self
.
request
.
user
=
self
.
workout
.
workout
.
owner
self
.
assertTrue
(
self
.
is_owner
.
has_object_permission
(
self
.
request
,
None
,
self
.
workout
.
workout
))
self
.
assertIs
(
self
.
is_owner
.
has_object_permission
(
self
.
request
,
None
,
self
.
workout
.
workout
),
True
)
class
IsOwnerOfWorkoutTestCase
(
TestCase
):
"""
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
.
assert
True
(
self
.
is_owner_of_workout
.
has_permission
(
request
,
None
))
self
.
assert
Is
(
self
.
is_owner_of_workout
.
has_permission
(
request
,
None
)
,
True
)
def
test_has_permission_workout
(
self
):
"""
POST workout permission
"""
request
=
MockRequest
()
request
.
method
=
"POST"
request
.
data
=
{
"workout"
:
""
}
self
.
assert
False
(
self
.
is_owner_of_workout
.
has_permission
(
request
,
None
))
self
.
assert
Is
(
self
.
is_owner_of_workout
.
has_permission
(
request
,
None
)
,
False
)
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
.
assertIs
(
self
.
is_owner_of_workout
.
has_permission
(
request
,
None
),
True
)
def
test_has_object_permission
(
self
):
self
.
assertFalse
(
self
.
is_owner_of_workout
.
has_object_permission
(
self
.
request
,
None
,
self
.
workout
))
"""
Ownership permissions
"""
self
.
assertIs
(
self
.
is_owner_of_workout
.
has_object_permission
(
self
.
request
,
None
,
self
.
workout
),
False
)
self
.
request
.
user
=
self
.
workout
.
workout
.
owner
self
.
assertTrue
(
self
.
is_owner_of_workout
.
has_object_permission
(
self
.
request
,
None
,
self
.
workout
))
self
.
assertIs
(
self
.
is_owner_of_workout
.
has_object_permission
(
self
.
request
,
None
,
self
.
workout
),
True
)
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
(
"""
Coach has object permissions
"""
self
.
assertIs
(
self
.
is_coach_and_visible_to_coach
.
has_object_permission
(
self
.
request
,
None
,
self
.
workout
.
workout
))
)
,
False
)
self
.
workout
.
workout
.
owner
.
coach
=
self
.
request
.
user
self
.
assert
True
(
self
.
is_coach_and_vis
a
ble_to_coach
.
has_object_permission
(
self
.
assert
Is
(
self
.
is_coach_and_vis
i
ble_to_coach
.
has_object_permission
(
self
.
request
,
None
,
self
.
workout
.
workout
))
),
True
)
class
IsCoachOfWorkoutAndVisibleToCoachTestCase
(
TestCase
):
"""
Coach 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
(
"""
Coach permissions
"""
self
.
assertIs
(
self
.
is_coach_of_workout_and_visible_to_coach
.
has_object_permission
(
self
.
request
,
None
,
self
.
workout
))
)
,
False
)
self
.
workout
.
workout
.
owner
.
coach
=
self
.
request
.
user
self
.
assert
True
(
self
.
is_coach_of_workout_and_vis
a
ble_to_coach
.
has_object_permission
(
self
.
assert
Is
(
self
.
is_coach_of_workout_and_vis
i
ble_to_coach
.
has_object_permission
(
self
.
request
,
None
,
self
.
workout
))
)
,
True
)
class
IsPublicTestCase
(
TestCase
):
"""
Test case public permissions
"""
def
setUp
(
self
):
self
.
workout
=
MockWorkout
()
self
.
is_public
=
IsPublic
()
def
test_has_object_permission
(
self
):
self
.
assertTrue
(
self
.
is_public
.
has_object_permission
(
"""
Workout permissions
"""
self
.
assertIs
(
self
.
is_public
.
has_object_permission
(
None
,
None
,
self
.
workout
.
workout
)
)
self
.
workout
.
workout
)
,
True
)
self
.
workout
.
workout
.
visibility
=
"CO"
self
.
assert
False
(
self
.
is_public
.
has_object_permission
(
self
.
assert
Is
(
self
.
is_public
.
has_object_permission
(
None
,
None
,
self
.
workout
.
workout
)
)
self
.
workout
.
workout
),
False
)
class
IsWorkoutPublicTestCase
(
TestCase
):
"""
Workout is publicly available
"""
def
setUp
(
self
):
self
.
workout
=
MockWorkout
()
self
.
is_workout_public
=
IsWorkoutPublic
()
def
test_has_object_permission
(
self
):
self
.
assertTrue
(
self
.
is_workout_public
.
has_object_permission
(
"""
Workout permissions
"""
self
.
assertIs
(
self
.
is_workout_public
.
has_object_permission
(
None
,
None
,
self
.
workout
)
)
self
.
workout
)
,
True
)
self
.
workout
.
workout
.
visibility
=
"N"
self
.
assert
False
(
self
.
is_workout_public
.
has_object_permission
(
self
.
assert
Is
(
self
.
is_workout_public
.
has_object_permission
(
None
,
None
,
self
.
workout
)
)
self
.
workout
),
False
)
class
IsReadOnlyTestCase
(
TestCase
):
"""
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
):
self
.
assertTrue
(
self
.
is_read_only
.
has_object_permission
(
self
.
request
,
None
,
None
))
"""
Object permissions
"""
self
.
assertIs
(
self
.
is_read_only
.
has_object_permission
(
self
.
request
,
None
,
None
),
True
)
self
.
request
.
method
=
None
self
.
assert
False
(
self
.
is_read_only
.
has_object_permission
(
self
.
request
,
None
,
None
))
self
.
assert
Is
(
self
.
is_read_only
.
has_object_permission
(
self
.
request
,
None
,
None
)
,
False
)
backend/secfit/workouts/tests/test_data.py
0 → 100644
View file @
82a077c0
test_data
=
{
"name"
:
"testCreate"
,
"date"
:
"2021-03-20 19:00+0100"
,
"owner_id"
:
"1"
,