Commit 15439eb3 authored by Marius Engen's avatar Marius Engen
Browse files

Merge branch 'review/backend-workouts' into 'master'

Review/backend workouts

See merge request !7
parents 756a581e 193ec55a
Pipeline #172978 passed with stages
in 2 minutes and 4 seconds
from django.shortcuts import render
from rest_framework import generics, mixins
"""Comments views."""
from django.db.models import Q
from rest_framework import generics, mixins, permissions
from rest_framework.filters import OrderingFilter
from comments.models import Comment, Like
from rest_framework import permissions
from comments.permissions import IsCommentVisibleToUser
from workouts.permissions import IsOwner, IsReadOnly
from comments.serializers import CommentSerializer, LikeSerializer
from django.db.models import Q
from rest_framework.filters import OrderingFilter
from workouts.permissions import IsOwner, IsReadOnly
# Create your views here.
class CommentList(
mixins.ListModelMixin, mixins.CreateModelMixin, generics.GenericAPIView
):
# queryset = Comment.objects.all()
"""List all comments, or create a new comment."""
serializer_class = CommentSerializer
permission_classes = [permissions.IsAuthenticated]
filter_backends = [OrderingFilter]
ordering_fields = ["timestamp"]
def get(self, request, *args, **kwargs):
"""Get a list of all comments."""
return self.list(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
"""Create a new comment."""
return self.create(request, *args, **kwargs)
def perform_create(self, serializer):
"""Create a new comment."""
serializer.save(owner=self.request.user)
def get_queryset(self):
"""Get the queryset for the list of comments."""
workout_pk = self.kwargs.get("pk")
qs = Comment.objects.none()
queryset = Comment.objects.none()
if workout_pk:
qs = Comment.objects.filter(workout=workout_pk)
queryset = Comment.objects.filter(workout=workout_pk)
elif self.request.user:
"""A comment should be visible to the requesting user if any of the following hold:
- The comment is on a public visibility workout
- The comment was written by the user
- 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(
queryset = Comment.objects.filter(
Q(workout__visibility="PU")
| Q(owner=self.request.user)
| (
......@@ -54,15 +52,16 @@ class CommentList(
| Q(workout__owner=self.request.user)
).distinct()
return qs
return queryset
# Details of comment
class CommentDetail(
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
generics.GenericAPIView,
):
"""Retrieve, update or delete a comment instance."""
queryset = Comment.objects.all()
serializer_class = CommentSerializer
permission_classes = [
......@@ -70,50 +69,60 @@ class CommentDetail(
]
def get(self, request, *args, **kwargs):
"""Get a comment instance."""
return self.retrieve(request, *args, **kwargs)
def put(self, request, *args, **kwargs):
"""Update a comment instance."""
return self.update(request, *args, **kwargs)
def delete(self, request, *args, **kwargs):
"""Delete a comment instance."""
return self.destroy(request, *args, **kwargs)
# List of likes
class LikeList(mixins.ListModelMixin, mixins.CreateModelMixin, generics.GenericAPIView):
"""List all likes, or create a new like."""
serializer_class = LikeSerializer
permission_classes = [permissions.IsAuthenticated]
def get(self, request, *args, **kwargs):
"""Get a list of all likes."""
return self.list(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
"""Create a new like."""
return self.create(request, *args, **kwargs)
def perform_create(self, serializer):
"""Create a new like."""
serializer.save(owner=self.request.user)
def get_queryset(self):
"""Get the queryset for the list of likes."""
return Like.objects.filter(owner=self.request.user)
# Details of like
class LikeDetail(
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
generics.GenericAPIView,
):
"""Retrieve, update or delete a like instance."""
queryset = Like.objects.all()
serializer_class = LikeSerializer
permission_classes = [permissions.IsAuthenticated]
_Detail = []
def get(self, request, *args, **kwargs):
"""Get a like instance."""
return self.retrieve(request, *args, **kwargs)
def put(self, request, *args, **kwargs):
"""Update a like instance."""
return self.update(request, *args, **kwargs)
def delete(self, request, *args, **kwargs):
"""Delete a like instance."""
return self.destroy(request, *args, **kwargs)
[flake8]
ignore = E203, E266, E501, W503
max-line-length = 88
max-complexity = 18
select = B,C,E,F,W,T4
[pylint]
max-line-length = 88
[pylint.messages_control]
disable = C0330, C0326, C0301
# [isort]
# src_paths = ["backend"]
# multi_line_output=3
# include_trailing_comma=True
# force_grid_wrap=0
# use_parentheses=True
# line_length=88
# [tool:pytest]
# testpaths=tests
# DJANGO_SETTINGS_MODULE = config.settings.dev
# python_files = tests.py test_*.py *_tests.py
\ No newline at end of file
"""Admin model for User"""
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from .models import Offer, AthleteFile
from django.contrib.auth import get_user_model
from .forms import CustomUserChangeForm, CustomUserCreationForm
from django.contrib.auth.admin import UserAdmin
# Register your models here.
from .forms import CustomUserChangeForm, CustomUserCreationForm
from .models import AthleteFile, Offer
class CustomUserAdmin(UserAdmin):
"""Custom user admin model."""
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
"""URL for User model."""
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
"""Views for the users app."""
from django.contrib.auth import get_user_model
from django.db.models import Q
from rest_framework import generics, mixins, permissions
from rest_framework.parsers import FormParser, MultiPartParser
from rest_framework.permissions import (
IsAuthenticatedOrReadOnly,
)
from users.models import AthleteFile, Offer
from users.permissions import IsAthlete, IsCoach, IsCurrentUser
from users.serializers import (
UserSerializer,
OfferSerializer,
AthleteFileSerializer,
UserPutSerializer,
OfferSerializer,
UserGetSerializer,
UserPutSerializer,
UserSerializer,
)
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.mixins import CreateListModelMixin
from workouts.permissions import IsOwner, IsReadOnly
# Create your views here.
class UserList(mixins.ListModelMixin, mixins.CreateModelMixin, generics.GenericAPIView):
"""List all users, or create a new user."""
serializer_class = UserSerializer
users = []
admins = []
def get(self, request, *args, **kwargs):
"""Get a list of all users.
If the user is an admin, return all users.
If the user is a coach, return all users that are athletes.
If the user is an athlete, return all users that are athletes.
"""
self.serializer_class = UserGetSerializer
return self.list(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
"""Create a new user."""
return self.create(request, *args, **kwargs)
def get_queryset(self):
qs = get_user_model().objects.all()
"""Get the queryset for the list of users."""
queryset = get_user_model().objects.all()
if self.request.user:
# Return the currently logged in user
status = self.request.query_params.get("user", None)
if status and status == "current":
qs = get_user_model().objects.filter(pk=self.request.user.pk)
queryset = get_user_model().objects.filter(pk=self.request.user.pk)
return qs
return queryset
class UserDetail(
......@@ -54,12 +63,16 @@ class UserDetail(
mixins.DestroyModelMixin,
generics.GenericAPIView,
):
"""Retrieve, update or delete a user instance."""
lookup_field_options = ["pk", "username"]
serializer_class = UserSerializer
queryset = get_user_model().objects.all()
permission_classes = [permissions.IsAuthenticated & (IsCurrentUser | IsReadOnly)]
def get_object(self):
"""Get the object for the detail view."""
for field in self.lookup_field_options:
if field in self.kwargs:
self.lookup_field = field
......@@ -68,63 +81,76 @@ class UserDetail(
return super().get_object()
def get(self, request, *args, **kwargs):
"""Get a user instance."""
self.serializer_class = UserGetSerializer
return self.retrieve(request, *args, **kwargs)
def delete(self, request, *args, **kwargs):
"""Delete a user instance."""
return self.destroy(request, *args, **kwargs)
def put(self, request, *args, **kwargs):
"""Update a user instance."""
self.serializer_class = UserPutSerializer
return self.update(request, *args, **kwargs)
def patch(self, request, *args, **kwargs):
"""Update a user instance."""
return self.partial_update(request, *args, **kwargs)
class OfferList(
mixins.ListModelMixin, mixins.CreateModelMixin, generics.GenericAPIView
):
"""List all offers, or create a new offer."""
permission_classes = [IsAuthenticatedOrReadOnly]
serializer_class = OfferSerializer
def get(self, request, *args, **kwargs):
"""Get a list of all offers."""
return self.list(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
"""Create a new offer."""
return self.create(request, *args, **kwargs)
def perform_create(self, serializer):
"""Set the owner of the offer."""
serializer.save(owner=self.request.user)
def get_queryset(self):
qs = Offer.objects.none()
"""Get the queryset for the list of offers."""
result = Offer.objects.none()
if self.request.user:
qs = Offer.objects.filter(
queryset = Offer.objects.filter(
Q(owner=self.request.user) | Q(recipient=self.request.user)
).distinct()
qp = self.request.query_params
u = self.request.user
query_parameters = self.request.query_params
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)
if qp.get("status", None) is None:
qs = Offer.objects.filter(Q(owner=u)).distinct()
status = query_parameters.get("status", None)
if status is not None and self.request is not None:
queryset = queryset.filter(status=status)
if query_parameters.get("status", None) is None:
queryset = Offer.objects.filter(Q(owner=user)).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)
return qs
else:
return result
category = query_parameters.get("category", None)
if category is not None and query_parameters is not None:
if category == "sent":
queryset = queryset.filter(owner=user)
elif category == "received":
queryset = queryset.filter(recipient=user)
return queryset
return result
class OfferDetail(
......@@ -133,20 +159,26 @@ class OfferDetail(
mixins.DestroyModelMixin,
generics.GenericAPIView,
):
"""Retrieve, update or delete a offer instance."""
permission_classes = [IsAuthenticatedOrReadOnly]
queryset = Offer.objects.all()
serializer_class = OfferSerializer
def get(self, request, *args, **kwargs):
"""Get a offer instance."""
return self.retrieve(request, *args, **kwargs)
def put(self, request, *args, **kwargs):
"""Update a offer instance."""
return self.update(request, *args, **kwargs)
def patch(self, request, *args, **kwargs):
"""Update a offer instance."""
return self.partial_update(request, *args, **kwargs)
def delete(self, request, *args, **kwargs):
"""Delete a offer instance."""
return self.destroy(request, *args, **kwargs)
......@@ -156,29 +188,35 @@ class AthleteFileList(
CreateListModelMixin,
generics.GenericAPIView,
):
"""List all athlete files, or create a new athlete file."""
queryset = AthleteFile.objects.all()
serializer_class = AthleteFileSerializer
permission_classes = [permissions.IsAuthenticated & (IsAthlete | IsCoach)]
parser_classes = [MultiPartParser, FormParser]
def get(self, request, *args, **kwargs):
"""Get a list of all athlete files."""
return self.list(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
"""Create a new athlete file."""
return self.create(request, *args, **kwargs)
def perform_create(self, serializer):
"""Set the owner of the athlete file."""
serializer.save(owner=self.request.user)
def get_queryset(self):
qs = AthleteFile.objects.none()
"""Get the queryset for the list of athlete files."""
queryset = AthleteFile.objects.none()
if self.request.user:
qs = AthleteFile.objects.filter(
queryset = AthleteFile.objects.filter(
Q(athlete=self.request.user) | Q(owner=self.request.user)
).distinct()
return qs
return queryset
class AthleteFileDetail(
......@@ -187,12 +225,16 @@ class AthleteFileDetail(
mixins.DestroyModelMixin,
generics.GenericAPIView,
):
"""Retrieve, update or delete a athlete file instance."""
queryset = AthleteFile.objects.all()
serializer_class = AthleteFileSerializer
permission_classes = [permissions.IsAuthenticated & (IsAthlete | IsOwner)]
def get(self, request, *args, **kwargs):
"""Get a athlete file instance."""
return self.retrieve(request, *args, **kwargs)
def delete(self, request, *args, **kwargs):
"""Delete a athlete file instance."""
return self.destroy(request, *args, **kwargs)
......@@ -6,15 +6,23 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('workouts', '0002_auto_20200910_0222'),
("workouts", "0002_auto_20200910_0222"),
]
operations = [
migrations.CreateModel(
name='RememberMe',
name="RememberMe",
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('remember_me', models.CharField(max_length=500)),
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("remember_me", models.CharField(max_length=500)),
],
),
]
......@@ -6,23 +6,23 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('workouts', '0003_rememberme'),
("workouts", "0003_rememberme"),
]
operations = [
migrations.AddField(
model_name='exercise',
name='calories',
model_name="exercise",
name="calories",
field=models.IntegerField(default=0),
),
migrations.AddField(
model_name='exercise',
name='duration',
model_name="exercise",
name="duration",
field=models.IntegerField(default=0),
),
migrations.AddField(
model_name='exercise',
name='muscleGroup',
field=models.TextField(default='Legs'),
model_name="exercise",
name="muscleGroup",
field=models.TextField(default="Legs"),
),
]
......@@ -2,27 +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))
from django.core.files.storage import FileSystemStorage
from django.db import models
# Create your models here.
......@@ -64,7 +46,13 @@ class Workout(models.Model):
# Retrieve average rating for workout on demand
@property
def average_rating(self):
return Workout.objects.filter(comments__workout=self.id).aggregate(average_rating=models.Avg('comments__rating'))['average_rating']
"""
Returns:
float: Average rating of workout
"""
return Workout.objects.filter(comments__workout=self.id).aggregate(
average_rating=models.Avg("comments__rating")
)["average_rating"]
class Meta:
ordering = ["-date"]
......@@ -76,7 +64,8 @@ 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., Pushups, Crunches, or
Lunges.
Attributes:
name: