diff --git a/blackfox/api/permissions.py b/blackfox/api/permissions.py index 068d47f..2566c27 100644 --- a/blackfox/api/permissions.py +++ b/blackfox/api/permissions.py @@ -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 diff --git a/blackfox/api/serializers.py b/blackfox/api/serializers.py index 2c31ebe..fd30ee9 100644 --- a/blackfox/api/serializers.py +++ b/blackfox/api/serializers.py @@ -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 diff --git a/blackfox/api/urls.py b/blackfox/api/urls.py index 0af0c09..c72ad55 100644 --- a/blackfox/api/urls.py +++ b/blackfox/api/urls.py @@ -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)), ] diff --git a/blackfox/api/views.py b/blackfox/api/views.py index fa9479c..e15dcac 100644 --- a/blackfox/api/views.py +++ b/blackfox/api/views.py @@ -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 diff --git a/blackfox/blackfox/settings.py b/blackfox/blackfox/settings.py index 846d248..41152e1 100644 --- a/blackfox/blackfox/settings.py +++ b/blackfox/blackfox/settings.py @@ -47,7 +47,7 @@ 'drf_spectacular', 'api', - 'fatsecret_api', + 'fatsecret', 'users', 'training', ] diff --git a/blackfox/db.sqlite3 b/blackfox/db.sqlite3 index 95cd4ea..00885dc 100644 Binary files a/blackfox/db.sqlite3 and b/blackfox/db.sqlite3 differ diff --git a/blackfox/fatsecret_api/__init__.py b/blackfox/fatsecret/__init__.py similarity index 100% rename from blackfox/fatsecret_api/__init__.py rename to blackfox/fatsecret/__init__.py diff --git a/blackfox/fatsecret_api/apps.py b/blackfox/fatsecret/apps.py similarity index 59% rename from blackfox/fatsecret_api/apps.py rename to blackfox/fatsecret/apps.py index 50ac378..885a733 100644 --- a/blackfox/fatsecret_api/apps.py +++ b/blackfox/fatsecret/apps.py @@ -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' diff --git a/blackfox/fatsecret/tools.py b/blackfox/fatsecret/tools.py new file mode 100644 index 0000000..1ba58c3 --- /dev/null +++ b/blackfox/fatsecret/tools.py @@ -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 diff --git a/blackfox/fatsecret_api/urls.py b/blackfox/fatsecret/urls.py similarity index 94% rename from blackfox/fatsecret_api/urls.py rename to blackfox/fatsecret/urls.py index c23f66d..af099b7 100644 --- a/blackfox/fatsecret_api/urls.py +++ b/blackfox/fatsecret/urls.py @@ -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, ) diff --git a/blackfox/fatsecret_api/views.py b/blackfox/fatsecret/views.py similarity index 77% rename from blackfox/fatsecret_api/views.py rename to blackfox/fatsecret/views.py index 0f539fa..9a1da00 100644 --- a/blackfox/fatsecret_api/views.py +++ b/blackfox/fatsecret/views.py @@ -1,42 +1,18 @@ -import datetime as dt -import os - from django.core.cache import cache from django.shortcuts import redirect -from rauth import OAuth1Service from rest_framework import status from rest_framework.permissions import AllowAny from rest_framework.response import Response from rest_framework.views import APIView -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') +from fatsecret.tools import ( + BASE_URL, CALLBACK_URL, fatsecret, unix_date_converter, +) error_date_message = 'Incorrect date format, should be YYYY-MM-DD or YYMMDD' error_request_message = 'Missing FatSecret verification code or request tokens' success_message = 'FatSecret account successfully linked' -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 int: - return epoch + dt.timedelta(date) - return (dt.date.fromisoformat(date) - epoch).days - class RequestTokenView(APIView): diff --git a/blackfox/training/migrations/0001_initial.py b/blackfox/training/migrations/0001_initial.py index 7b37458..a5a1b08 100644 --- a/blackfox/training/migrations/0001_initial.py +++ b/blackfox/training/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 3.2.16 on 2024-07-20 20:47 +# Generated by Django 3.2.16 on 2024-08-11 14:08 import django.core.validators from django.db import migrations, models @@ -33,18 +33,18 @@ class Migration(migrations.Migration): fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('date', models.DateField(db_index=True, verbose_name='дата питания')), - ('calories_actual', models.PositiveIntegerField(blank=True, null=True, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(10000)], verbose_name='калории факт')), - ('calories_target', models.PositiveIntegerField(blank=True, null=True, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(10000)], verbose_name='калории план')), + ('calories_actual', models.PositiveSmallIntegerField(blank=True, null=True, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(10000)], verbose_name='калории факт')), + ('calories_target', models.PositiveSmallIntegerField(blank=True, null=True, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(10000)], verbose_name='калории план')), ('carbohydrate_actual', models.FloatField(blank=True, null=True, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1000)], verbose_name='углеводы факт')), - ('carbohydrate_target', models.FloatField(blank=True, null=True, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1000)], verbose_name='углеводы план')), + ('carbohydrate_target', models.PositiveSmallIntegerField(blank=True, null=True, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1000)], verbose_name='углеводы план')), ('fat_actual', models.FloatField(blank=True, null=True, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1000)], verbose_name='жиры факт')), - ('fat_target', models.FloatField(blank=True, null=True, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1000)], verbose_name='жиры план')), + ('fat_target', models.PositiveSmallIntegerField(blank=True, null=True, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1000)], verbose_name='жиры план')), ('fiber_actual', models.FloatField(blank=True, null=True, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1000)], verbose_name='пищевые волокна факт')), - ('fiber_target', models.FloatField(blank=True, null=True, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1000)], verbose_name='пищевые волокна план')), + ('fiber_target', models.PositiveSmallIntegerField(blank=True, null=True, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1000)], verbose_name='пищевые волокна план')), ('protein_actual', models.FloatField(blank=True, null=True, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1000)], verbose_name='белки факт')), - ('protein_target', models.FloatField(blank=True, null=True, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1000)], verbose_name='белки план')), + ('protein_target', models.PositiveSmallIntegerField(blank=True, null=True, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1000)], verbose_name='белки план')), ('sugar_actual', models.FloatField(blank=True, null=True, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1000)], verbose_name='сахар факт')), - ('sugar_target', models.FloatField(blank=True, null=True, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1000)], verbose_name='сахар план')), + ('sugar_target', models.PositiveSmallIntegerField(blank=True, null=True, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1000)], verbose_name='сахар план')), ], options={ 'ordering': ['-date'], @@ -55,15 +55,13 @@ class Migration(migrations.Migration): fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('start_date', models.DateField(db_index=True, verbose_name='начало проекта')), - ('deadline', models.DateField(db_index=True, verbose_name='окончание проекта')), - ('target_calories', models.PositiveIntegerField(validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(10000)], verbose_name='калории план')), - ('target_carbohydrate', models.FloatField(validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1000)], verbose_name='углеводы план')), - ('target_fat', models.FloatField(validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1000)], verbose_name='жиры план')), - ('target_fiber', models.FloatField(validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1000)], verbose_name='пищевые волокна план')), - ('target_protein', models.FloatField(validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1000)], verbose_name='белки план')), - ('target_sugar', models.FloatField(validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1000)], verbose_name='сахар план')), + ('target_calories', models.PositiveSmallIntegerField(validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(10000)], verbose_name='калории план')), + ('target_carbohydrate', models.PositiveSmallIntegerField(validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1000)], verbose_name='углеводы план')), + ('target_fat', models.PositiveSmallIntegerField(validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1000)], verbose_name='жиры план')), + ('target_fiber', models.PositiveSmallIntegerField(validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1000)], verbose_name='пищевые волокна план')), + ('target_protein', models.PositiveSmallIntegerField(validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1000)], verbose_name='белки план')), + ('target_sugar', models.PositiveSmallIntegerField(validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1000)], verbose_name='сахар план')), ('target_weight', models.FloatField(validators=[django.core.validators.MinValueValidator(30), django.core.validators.MaxValueValidator(250)], verbose_name='целевой вес')), - ('is_closed', models.BooleanField(default=False, verbose_name='проект закрыт')), ], options={ 'ordering': ['-start_date'], diff --git a/blackfox/training/migrations/0002_initial.py b/blackfox/training/migrations/0002_initial.py index 782d097..04b6f7f 100644 --- a/blackfox/training/migrations/0002_initial.py +++ b/blackfox/training/migrations/0002_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 3.2.16 on 2024-07-20 20:47 +# Generated by Django 3.2.16 on 2024-08-11 14:08 from django.conf import settings from django.db import migrations, models @@ -18,12 +18,12 @@ class Migration(migrations.Migration): migrations.AddField( model_name='project', name='coach', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='coach', to=settings.AUTH_USER_MODEL, verbose_name='тренер'), + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='project_coach', to=settings.AUTH_USER_MODEL, verbose_name='тренер'), ), migrations.AddField( model_name='project', name='user', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='project', to=settings.AUTH_USER_MODEL, verbose_name='спортсмен'), + field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='project_user', to=settings.AUTH_USER_MODEL, verbose_name='спортсмен'), ), migrations.AddField( model_name='fooddiary', diff --git a/blackfox/training/models.py b/blackfox/training/models.py index 0f7c0b8..8d43a07 100644 --- a/blackfox/training/models.py +++ b/blackfox/training/models.py @@ -77,13 +77,13 @@ class FoodDiary(models.Model): verbose_name='дата питания', db_index=True, ) - calories_actual = models.PositiveIntegerField( + calories_actual = models.PositiveSmallIntegerField( verbose_name='калории факт', validators=[MinValueValidator(0), MaxValueValidator(10_000)], blank=True, null=True, ) - calories_target = models.PositiveIntegerField( + calories_target = models.PositiveSmallIntegerField( verbose_name='калории план', validators=[MinValueValidator(0), MaxValueValidator(10_000)], blank=True, @@ -95,7 +95,7 @@ class FoodDiary(models.Model): blank=True, null=True, ) - carbohydrate_target = models.FloatField( + carbohydrate_target = models.PositiveSmallIntegerField( verbose_name='углеводы план', validators=[MinValueValidator(0), MaxValueValidator(1_000)], blank=True, @@ -107,7 +107,7 @@ class FoodDiary(models.Model): blank=True, null=True, ) - fat_target = models.FloatField( + fat_target = models.PositiveSmallIntegerField( verbose_name='жиры план', validators=[MinValueValidator(0), MaxValueValidator(1_000)], blank=True, @@ -119,7 +119,7 @@ class FoodDiary(models.Model): blank=True, null=True, ) - fiber_target = models.FloatField( + fiber_target = models.PositiveSmallIntegerField( verbose_name='пищевые волокна план', validators=[MinValueValidator(0), MaxValueValidator(1_000)], blank=True, @@ -131,7 +131,7 @@ class FoodDiary(models.Model): blank=True, null=True, ) - protein_target = models.FloatField( + protein_target = models.PositiveSmallIntegerField( verbose_name='белки план', validators=[MinValueValidator(0), MaxValueValidator(1_000)], blank=True, @@ -143,7 +143,7 @@ class FoodDiary(models.Model): blank=True, null=True, ) - sugar_target = models.FloatField( + sugar_target = models.PositiveSmallIntegerField( verbose_name='сахар план', validators=[MinValueValidator(0), MaxValueValidator(1_000)], blank=True, @@ -164,47 +164,43 @@ def __str__(self): class Project(models.Model): - user = models.ForeignKey( + user = models.OneToOneField( User, on_delete=models.CASCADE, verbose_name='спортсмен', - related_name='project', + related_name='project_user', ) coach = models.ForeignKey( User, on_delete=models.CASCADE, verbose_name='тренер', - related_name='coach', + related_name='project_coach', ) start_date = models.DateField( verbose_name='начало проекта', db_index=True, ) - deadline = models.DateField( - verbose_name='окончание проекта', - db_index=True, - ) - target_calories = models.PositiveIntegerField( + target_calories = models.PositiveSmallIntegerField( verbose_name='калории план', validators=[MinValueValidator(0), MaxValueValidator(10_000)], ) - target_carbohydrate = models.FloatField( + target_carbohydrate = models.PositiveSmallIntegerField( verbose_name='углеводы план', validators=[MinValueValidator(0), MaxValueValidator(1_000)], ) - target_fat = models.FloatField( + target_fat = models.PositiveSmallIntegerField( verbose_name='жиры план', validators=[MinValueValidator(0), MaxValueValidator(1_000)], ) - target_fiber = models.FloatField( + target_fiber = models.PositiveSmallIntegerField( verbose_name='пищевые волокна план', validators=[MinValueValidator(0), MaxValueValidator(1_000)], ) - target_protein = models.FloatField( + target_protein = models.PositiveSmallIntegerField( verbose_name='белки план', validators=[MinValueValidator(0), MaxValueValidator(1_000)], ) - target_sugar = models.FloatField( + target_sugar = models.PositiveSmallIntegerField( verbose_name='сахар план', validators=[MinValueValidator(0), MaxValueValidator(1_000)], ) @@ -212,13 +208,9 @@ class Project(models.Model): verbose_name='целевой вес', validators=[MinValueValidator(30), MaxValueValidator(250)], ) - is_closed = models.BooleanField( - verbose_name='проект закрыт', - default=False, - ) class Meta: ordering = ['-start_date'] def __str__(self): - return f'Цель {self.target_weight} кг. до {self.deadline} г.' + return f'{self.user.username}, целевой вес {self.target_weight} кг.' diff --git a/blackfox/users/migrations/0001_initial.py b/blackfox/users/migrations/0001_initial.py index c822e23..c835990 100644 --- a/blackfox/users/migrations/0001_initial.py +++ b/blackfox/users/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 3.2.16 on 2024-07-20 20:47 +# Generated by Django 3.2.16 on 2024-08-11 14:08 import django.contrib.auth.models import django.contrib.auth.validators