-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #41 from Evgeniy-Golodnykh/develop
Update Bodystats, FoodDiary and Project views, serializers and models
- Loading branch information
Showing
15 changed files
with
295 additions
and
113 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,42 +1,104 @@ | ||
import datetime as dt | ||
|
||
from django.contrib.auth import get_user_model | ||
from rest_framework import serializers | ||
|
||
from training.models import BodyStatsDiary, FoodDiary, Project | ||
from users.serializers import CustomUserSerializer | ||
|
||
User = get_user_model() | ||
|
||
current_date = dt.date.today() | ||
error_date_message = 'The date cannot be greater than the current one' | ||
diary_entry_exists_message = ( | ||
'A diary entry for the current user and date already exists' | ||
) | ||
project_exists_message = 'A project with this User already exists' | ||
user_not_coach_message = 'A user cannot be a coach at the same time' | ||
|
||
|
||
class BodyStatsDiarySerializer(serializers.ModelSerializer): | ||
"""A serializer to read BodyStatsDiary instances.""" | ||
|
||
user = CustomUserSerializer(read_only=True) | ||
|
||
class Meta: | ||
model = BodyStatsDiary | ||
exclude = ['id'] | ||
fields = '__all__' | ||
|
||
|
||
class FoodDiarySerializer(serializers.ModelSerializer): | ||
class CreateUpdateBodyStatsDiarySerializer(serializers.ModelSerializer): | ||
"""A serializer to create/update BodyStatsDiary instances.""" | ||
|
||
class Meta: | ||
model = FoodDiary | ||
exclude = ['id'] | ||
model = BodyStatsDiary | ||
exclude = ('user',) | ||
|
||
def validate_date(self, input_date): | ||
if input_date > current_date: | ||
raise serializers.ValidationError(error_date_message) | ||
return input_date | ||
|
||
def create(self, validated_data): | ||
user = self.context['request'].user | ||
date = validated_data.get('date') | ||
if BodyStatsDiary.objects.filter(user=user, date=date).exists(): | ||
raise serializers.ValidationError(diary_entry_exists_message) | ||
validated_data['user'] = user | ||
return BodyStatsDiary.objects.create(**validated_data) | ||
|
||
def to_representation(self, instance): | ||
request = self.context.get('request') | ||
context = {'request': request} | ||
return BodyStatsDiarySerializer(instance, context=context).data | ||
|
||
|
||
class UserSerializer(serializers.ModelSerializer): | ||
class FoodDiarySerializer(serializers.ModelSerializer): | ||
"""A serializer to read FoodDiary instances.""" | ||
|
||
user = CustomUserSerializer(read_only=True) | ||
|
||
class Meta: | ||
model = User | ||
fields = ['id'] | ||
model = FoodDiary | ||
fields = '__all__' | ||
|
||
|
||
class ProjectSerializer(serializers.ModelSerializer): | ||
user = UserSerializer(read_only=True) | ||
coach = UserSerializer(read_only=True) | ||
start_date = serializers.DateField() | ||
deadline = serializers.DateField() | ||
target_calories = serializers.IntegerField(min_value=0, max_value=10_000) | ||
target_carbohydrate = serializers.FloatField(min_value=0, max_value=1_000) | ||
target_fat = serializers.FloatField(min_value=0, max_value=1_000) | ||
target_fiber = serializers.FloatField(min_value=0, max_value=1_000) | ||
target_protein = serializers.FloatField(min_value=0, max_value=1_000) | ||
target_sugar = serializers.FloatField(min_value=0, max_value=1_000) | ||
target_weight = serializers.FloatField(min_value=30, max_value=250) | ||
"""A serializer to read Project instances.""" | ||
|
||
user = CustomUserSerializer(read_only=True) | ||
coach = CustomUserSerializer(read_only=True) | ||
|
||
class Meta: | ||
model = Project | ||
exclude = ['id', 'is_closed'] | ||
fields = '__all__' | ||
|
||
|
||
class CreateUpdateProjectSerializer(ProjectSerializer): | ||
"""A serializer to create/update Project instances.""" | ||
|
||
user = serializers.SlugRelatedField( | ||
queryset=User.objects, | ||
slug_field='username', | ||
) | ||
coach = serializers.SlugRelatedField( | ||
queryset=User.objects, | ||
slug_field='username', | ||
) | ||
|
||
def validate_user(self, user): | ||
if Project.objects.filter(user=user).exists(): | ||
raise serializers.ValidationError(project_exists_message) | ||
return user | ||
|
||
def validate(self, data): | ||
user = data.get('user') | ||
coach = data.get('coach') | ||
if user == coach: | ||
raise serializers.ValidationError(user_not_coach_message) | ||
return data | ||
|
||
def to_representation(self, instance): | ||
request = self.context.get('request') | ||
context = {'request': request} | ||
return ProjectSerializer(instance, context=context).data |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,41 +1,84 @@ | ||
from rest_framework import filters, viewsets | ||
from django.contrib.auth import get_user_model | ||
from django.shortcuts import get_object_or_404 | ||
from rest_framework import filters, status, viewsets | ||
from rest_framework.permissions import IsAuthenticatedOrReadOnly | ||
from rest_framework.response import Response | ||
from rest_framework.views import APIView | ||
|
||
from api.permissions import IsAdmin, IsCoach | ||
from api.serializers import ( | ||
BodyStatsDiarySerializer, FoodDiarySerializer, ProjectSerializer, | ||
BodyStatsDiarySerializer, CreateUpdateBodyStatsDiarySerializer, | ||
CreateUpdateProjectSerializer, FoodDiarySerializer, ProjectSerializer, | ||
) | ||
from fatsecret.tools import get_fooddiary_instance | ||
from training.models import BodyStatsDiary, FoodDiary, Project | ||
|
||
User = get_user_model() | ||
fatsecret_account_not_exists_message = 'Please link your Fatsecret account' | ||
project_not_exists_message = 'Please create a project for current user' | ||
|
||
|
||
class BodyStatsDiaryViewSet(viewsets.ModelViewSet): | ||
permission_classes = [IsAuthenticatedOrReadOnly] | ||
serializer_class = BodyStatsDiarySerializer | ||
queryset = BodyStatsDiary.objects.all() | ||
filter_backends = [filters.SearchFilter] | ||
search_fields = ['user', 'date'] | ||
|
||
def perform_create(self, serializer): | ||
serializer.save(user=self.request.user) | ||
def get_serializer_class(self): | ||
if self.action in ('create', 'partial_update'): | ||
return CreateUpdateBodyStatsDiarySerializer | ||
return BodyStatsDiarySerializer | ||
|
||
def get_queryset(self): | ||
if self.request.user.role == 'user': | ||
return BodyStatsDiary.objects.filter(user=self.request.user) | ||
return BodyStatsDiary.objects.all() | ||
|
||
|
||
class FoodDiaryViewSet(viewsets.ModelViewSet): | ||
class FoodDiaryViewSet(viewsets.ReadOnlyModelViewSet): | ||
permission_classes = [IsAuthenticatedOrReadOnly] | ||
serializer_class = FoodDiarySerializer | ||
queryset = FoodDiary.objects.all() | ||
filter_backends = [filters.SearchFilter] | ||
search_fields = ['user', 'date'] | ||
|
||
def perform_create(self, serializer): | ||
serializer.save(user=self.request.user) | ||
def get_queryset(self): | ||
if self.request.user.role == 'user': | ||
return FoodDiary.objects.filter(user=self.request.user) | ||
return FoodDiary.objects.all() | ||
|
||
|
||
class FoodDiaryCreateView(APIView): | ||
|
||
def post(self, request): | ||
username = request.query_params.get('user') | ||
if username: | ||
user = get_object_or_404(User, username=username) | ||
else: | ||
user = request.user | ||
if not user.fatsecret_token or not user.fatsecret_secret: | ||
return Response( | ||
{'message': fatsecret_account_not_exists_message}, | ||
status=status.HTTP_400_BAD_REQUEST | ||
) | ||
if not Project.objects.filter(user=user).exists(): | ||
return Response( | ||
{'message': project_not_exists_message}, | ||
status=status.HTTP_400_BAD_REQUEST | ||
) | ||
FoodDiary.objects.bulk_create( | ||
objs=get_fooddiary_instance(user) | ||
) | ||
queryset = FoodDiary.objects.filter(user=user) | ||
serializer = FoodDiarySerializer(queryset, many=True) | ||
return Response(serializer.data) | ||
|
||
|
||
class ProjectViewSet(viewsets.ModelViewSet): | ||
permission_classes = [IsAdmin, IsCoach] | ||
serializer_class = ProjectSerializer | ||
permission_classes = [IsAdmin | IsCoach] | ||
queryset = Project.objects.all() | ||
filter_backends = [filters.SearchFilter] | ||
search_fields = ['user', 'coach', 'start_date'] | ||
|
||
def perform_create(self, serializer): | ||
serializer.save(trainer=self.request.user) | ||
def get_serializer_class(self): | ||
if self.action in ('create', 'partial_update'): | ||
return CreateUpdateProjectSerializer | ||
return ProjectSerializer |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -47,7 +47,7 @@ | |
'drf_spectacular', | ||
|
||
'api', | ||
'fatsecret_api', | ||
'fatsecret', | ||
'users', | ||
'training', | ||
] | ||
|
Binary file not shown.
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
from django.apps import AppConfig | ||
|
||
|
||
class FatsecretApiConfig(AppConfig): | ||
class FatsecretConfig(AppConfig): | ||
default_auto_field = 'django.db.models.BigAutoField' | ||
name = 'fatsecret_api' | ||
name = 'fatsecret' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
import datetime as dt | ||
import os | ||
|
||
from rauth import OAuth1Service | ||
|
||
from training.models import FoodDiary, Project | ||
|
||
CONSUMER_KEY = os.getenv('FATSECRET_CONSUMER_KEY') | ||
CONSUMER_SECRET = os.getenv('FATSECRET_CONSUMER_SECRET') | ||
REQUEST_TOKEN_URL = 'https://www.fatsecret.com/oauth/request_token' | ||
AUTHORIZE_URL = 'https://www.fatsecret.com/oauth/authorize' | ||
ACCESS_TOKEN_URL = 'https://www.fatsecret.com/oauth/access_token' | ||
BASE_URL = 'https://platform.fatsecret.com/rest/server.api' | ||
CALLBACK_URL = os.getenv('FATSECRET_CALLBACK_URL') | ||
|
||
fatsecret = OAuth1Service( | ||
consumer_key=CONSUMER_KEY, | ||
consumer_secret=CONSUMER_SECRET, | ||
request_token_url=REQUEST_TOKEN_URL, | ||
access_token_url=ACCESS_TOKEN_URL, | ||
authorize_url=AUTHORIZE_URL, | ||
base_url=BASE_URL, | ||
) | ||
|
||
|
||
def unix_date_converter(date): | ||
epoch = dt.date.fromtimestamp(0) | ||
if type(date) is str: | ||
return (dt.date.fromisoformat(date) - epoch).days | ||
return (date - epoch).days | ||
|
||
|
||
def food_caclulator(foods, project, date): | ||
instance = { | ||
'user': project.user, | ||
'date': date, | ||
'calories_target': project.target_calories, | ||
'carbohydrate_target': project.target_carbohydrate, | ||
'fat_target': project.target_fat, | ||
'fiber_target': project.target_fiber, | ||
'protein_target': project.target_protein, | ||
'sugar_target': project.target_sugar, | ||
} | ||
for food in foods: | ||
instance['calories_actual'] = ( | ||
instance.get('calories_actual', 0) | ||
+ int(food.get('calories', 0)) | ||
) | ||
instance['carbohydrate_actual'] = round( | ||
(instance.get('carbohydrate_actual', 0) | ||
+ float(food.get('carbohydrate', 0))), | ||
2 | ||
) | ||
instance['fat_actual'] = round( | ||
(instance.get('fat_actual', 0) | ||
+ float(food.get('fat', 0))), | ||
2 | ||
) | ||
instance['fiber_actual'] = round( | ||
(instance.get('fiber_actual', 0) | ||
+ float(food.get('fiber', 0))), | ||
2 | ||
) | ||
instance['protein_actual'] = round( | ||
(instance.get('protein_actual', 0) | ||
+ float(food.get('protein', 0))), | ||
2 | ||
) | ||
instance['sugar_actual'] = round( | ||
(instance.get('sugar_actual', 0) | ||
+ float(food.get('sugar', 0))), | ||
2 | ||
) | ||
return instance | ||
|
||
|
||
def get_fooddiary_instance(user): | ||
fooddiary = FoodDiary.objects.filter(user=user).first() | ||
project = Project.objects.filter(user=user).first() | ||
if fooddiary: | ||
last_date = fooddiary.date | ||
FoodDiary.objects.filter(id=fooddiary.id).delete() | ||
else: | ||
last_date = project.start_date | ||
current_date = dt.date.today() | ||
|
||
session = fatsecret.get_session( | ||
token=(user.fatsecret_token, user.fatsecret_secret) | ||
) | ||
params = {'method': 'food_entries.get.v2', 'format': 'json'} | ||
|
||
objects = [] | ||
while last_date <= current_date: | ||
params['date'] = unix_date_converter(last_date) | ||
fatsecret_data = session.get(BASE_URL, params=params).json() | ||
food_entries = fatsecret_data.get('food_entries') | ||
if food_entries: | ||
objects.append( | ||
FoodDiary(**food_caclulator( | ||
food_entries.get('food_entry'), project, last_date | ||
)) | ||
) | ||
last_date += dt.timedelta(1) | ||
session.close() | ||
|
||
return objects |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.