Skip to content

Commit

Permalink
Merge pull request #41 from Evgeniy-Golodnykh/develop
Browse files Browse the repository at this point in the history
Update Bodystats, FoodDiary and Project views, serializers and models
  • Loading branch information
Evgeniy-Golodnykh authored Aug 13, 2024
2 parents debe439 + 4451d87 commit 80d32cd
Show file tree
Hide file tree
Showing 15 changed files with 295 additions and 113 deletions.
2 changes: 1 addition & 1 deletion blackfox/api/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,4 @@ class IsCoach(permissions.BasePermission):
message = 'Only coach can perform this.'

def has_permission(self, request, view):
return request.user.is_authenticated and request.user.coach
return request.user.is_authenticated and request.user.is_coach
100 changes: 81 additions & 19 deletions blackfox/api/serializers.py
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
10 changes: 7 additions & 3 deletions blackfox/api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,22 @@
TokenObtainPairView, TokenRefreshView,
)

from api.views import BodyStatsDiaryViewSet, FoodDiaryViewSet, ProjectViewSet
from api.views import (
BodyStatsDiaryViewSet, FoodDiaryCreateView, FoodDiaryViewSet,
ProjectViewSet,
)

router = DefaultRouter()
router.register('bodystats', BodyStatsDiaryViewSet, basename='bodystats')
router.register('food', FoodDiaryViewSet, basename='food')
router.register('fooddiary', FoodDiaryViewSet, basename='fooddiary')
router.register('project', ProjectViewSet, basename='project')

urlpatterns = [
path('signup/', UserViewSet.as_view({'post': 'create'}), name='signup'),
path('login/', TokenObtainPairView.as_view(), name='login'),
path('login/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
path('fatsecret/', include('fatsecret_api.urls')),
path('fatsecret/', include('fatsecret.urls')),
path('fooddiary/', FoodDiaryCreateView.as_view(), name='create_fooddiary'),
path('', include('djoser.urls')),
path('', include(router.urls)),
]
71 changes: 57 additions & 14 deletions blackfox/api/views.py
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
2 changes: 1 addition & 1 deletion blackfox/blackfox/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
'drf_spectacular',

'api',
'fatsecret_api',
'fatsecret',
'users',
'training',
]
Expand Down
Binary file modified blackfox/db.sqlite3
Binary file not shown.
File renamed without changes.
4 changes: 2 additions & 2 deletions blackfox/fatsecret_api/apps.py → blackfox/fatsecret/apps.py
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'
106 changes: 106 additions & 0 deletions blackfox/fatsecret/tools.py
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
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
"""URLs for connection to FatSecret API"""

from django.urls import path
from fatsecret_api.views import (

from fatsecret.views import (
AccessTokenView, FoodDiaryDailyView, FoodDiaryMonthlyView,
RequestTokenView, WeightDiaryView,
)
Expand Down
Loading

0 comments on commit 80d32cd

Please sign in to comment.