Skip to content

Commit

Permalink
Перенос проверки в сериализатор занял 7 часов
Browse files Browse the repository at this point in the history
  • Loading branch information
VitalRu committed Sep 23, 2023
1 parent 2b8359c commit 53ec586
Show file tree
Hide file tree
Showing 9 changed files with 144 additions and 67 deletions.
62 changes: 44 additions & 18 deletions backend/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@

from django.core.files.base import ContentFile
from django.db.transaction import atomic
from rest_framework import serializers
from rest_framework import serializers, status
from rest_framework.validators import UniqueTogetherValidator
from rest_framework.exceptions import ValidationError

from recipes.models import Ingredient, IngredientsInRecipe, Recipe, Tag
from users.models import Follow, User
Expand Down Expand Up @@ -47,43 +48,68 @@ class Meta:
'is_subscribed')


class FollowSerializer(serializers.ModelSerializer):
email = serializers.ReadOnlyField(source='author.email')
id = serializers.ReadOnlyField(source='author.id')
username = serializers.ReadOnlyField(source='author.username')
first_name = serializers.ReadOnlyField(source='author.first_name')
last_name = serializers.ReadOnlyField(source='author.last_name')
class FollowRepresentationSerializer(serializers.ModelSerializer):
email = serializers.ReadOnlyField()
id = serializers.ReadOnlyField()
username = serializers.ReadOnlyField()
first_name = serializers.ReadOnlyField()
last_name = serializers.ReadOnlyField()
is_subscribed = serializers.SerializerMethodField()
recipes = serializers.SerializerMethodField()
recipes_count = serializers.SerializerMethodField()

class Meta:
model = Follow
model = User
fields = (
'email', 'id', 'username', 'first_name', 'last_name',
'is_subscribed', 'recipes', 'recipes_count'
)

validators = (
UniqueTogetherValidator(
queryset=Follow.objects.all(),
fields=('user', 'author')
),
)

def get_is_subscribed(self, obj):
request = self.context.get('request')
if request is None or request.user.is_anonymous:
return False
return obj.user.follower.exists()
return obj.follower.exists()

def get_recipes(self, obj):
queryset = obj.author.recipe.all()
queryset = obj.recipe.all()
serializer = RecipeInfoSerializer(queryset, many=True)
return serializer.data

def get_recipes_count(self, obj):
return obj.author.recipe.count()
return obj.recipe.count()


class FollowSerializer(serializers.ModelSerializer):

class Meta:
model = Follow
fields = ('user', 'author')

def to_representation(self, instance):
request = self.context.get('request')
return FollowRepresentationSerializer(
instance.author, context={'request': request}
).data

def create(self, validated_data):
user = self.context['request'].user
author = validated_data['author']

if user.follower.filter(author=author).exists():
raise ValidationError(
'Already subscribed',
code=status.HTTP_400_BAD_REQUEST
)

if user == author:
raise ValidationError(
'Unable to subscribe to yourself',
code=status.HTTP_400_BAD_REQUEST
)

follow = Follow.objects.create(user=user, author=author)
return follow


class IngredientSerializer(serializers.ModelSerializer):
Expand Down
26 changes: 8 additions & 18 deletions backend/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,31 +70,21 @@ def subscriptions(self, request):
@action(
('POST', 'DELETE'), detail=True, permission_classes=(IsAuthenticated,)
)
def subscribe(self, request, id=None):
def subscribe(self, request, id):
author = get_object_or_404(User, id=id)
user = request.user
follower = user.follower.filter(author=author)
if ((request.method == 'POST') and (user.id != author.id)
and not follower.exists()):
if request.method == 'POST':
serializer = FollowSerializer(
Follow.objects.create(user=user, author=author),
context={'request': request},
)
return Response(
serializer.data,
status=status.HTTP_201_CREATED,
data={'user': user.id, 'author': author.id},
context={'request': request}
)
serializer.is_valid()
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
if request.method == 'DELETE':
get_object_or_404(
Follow, user=user, author=author
).delete()
get_object_or_404(Follow, user=user, author=author).delete()
return Response(status=status.HTTP_204_NO_CONTENT)

return Response(
{'errors': 'Invalid operation'},
status=status.HTTP_400_BAD_REQUEST,
)


class IngredientViewSet(viewsets.ModelViewSet):
queryset = Ingredient.objects.all()
Expand Down
10 changes: 8 additions & 2 deletions backend/foodgram/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@

SECRET_KEY = os.getenv('SECRET_KEY', default='secret_key')

DEBUG = os.getenv('DEBUG', default='False')
if os.getenv('DEBUG', default='False').lower() == 'true':
DEBUG = True
else:
DEBUG = False


ALLOWED_HOSTS = ['*']

Expand Down Expand Up @@ -73,20 +77,22 @@
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')


# DATABASES = {
# 'default': {
# 'ENGINE': 'django.db.backends.sqlite3',
# 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
# }
# }

DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': os.getenv('POSTGRES_DB', 'django'),
'USER': os.getenv('POSTGRES_USER', 'django'),
'PASSWORD': os.getenv('POSTGRES_PASSWORD', ''),
'HOST': os.getenv('DB_HOST', ''),
'PORT': os.getenv('DB_PORT', 5432)
'PORT': os.getenv('DB_PORT', 5432),
}
}

Expand Down
18 changes: 6 additions & 12 deletions backend/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,41 +2,35 @@ asgiref==3.7.2
certifi==2023.7.22
cffi==1.15.1
charset-normalizer==3.2.0
cryptography==41.0.3
cryptography==41.0.4
defusedxml==0.7.1
Django==3.2
django-cors-headers==4.2.0
django-filter==23.2
django-rest-framework==0.1.0
django-filter==23.3
django-templated-mail==1.1.1
djangorestframework==3.14.0
djangorestframework-simplejwt==5.3.0
djoser==2.2.0
drf-yasg==1.21.7
flake8==6.1.0
gunicorn==21.2.0
idna==3.4
inflection==0.5.1
isort==5.12.0
mccabe==0.7.0
oauthlib==3.2.2
packaging==23.1
Pillow==10.0.0
Pillow==10.0.1
psycopg2-binary==2.9.7
pycodestyle==2.11.0
pycparser==2.21
pyflakes==3.1.0
PyJWT==2.8.0
python-dotenv==1.0.0
python3-openid==3.2.0
pytz==2023.3
pytz==2023.3.post1
PyYAML==6.0.1
reportlab==4.0.4
requests==2.31.0
requests-oauthlib==1.3.1
social-auth-app-django==5.3.0
social-auth-core==4.4.2
sqlparse==0.4.4
typing_extensions==4.7.1
typing_extensions==4.8.0
uritemplate==4.1.1
urllib3==2.0.4
urllib3==2.0.5
17 changes: 17 additions & 0 deletions backend/users/migrations/0009_remove_user_role.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Generated by Django 3.2 on 2023-09-23 03:19

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
('users', '0008_user_role'),
]

operations = [
migrations.RemoveField(
model_name='user',
name='role',
),
]
17 changes: 17 additions & 0 deletions backend/users/migrations/0010_alter_user_table.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Generated by Django 3.2 on 2023-09-23 05:33

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
('users', '0009_remove_user_role'),
]

operations = [
migrations.AlterModelTable(
name='user',
table=None,
),
]
21 changes: 21 additions & 0 deletions backend/users/migrations/0011_auto_20230923_0641.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Generated by Django 3.2 on 2023-09-23 06:41

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
('users', '0010_alter_user_table'),
]

operations = [
migrations.RemoveConstraint(
model_name='follow',
name='unique_pair',
),
migrations.RemoveConstraint(
model_name='follow',
name='author_not_user',
),
]
22 changes: 22 additions & 0 deletions backend/users/migrations/0012_auto_20230923_0703.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Generated by Django 3.2 on 2023-09-23 07:03

from django.db import migrations, models
import django.db.models.expressions


class Migration(migrations.Migration):

dependencies = [
('users', '0011_auto_20230923_0641'),
]

operations = [
migrations.AddConstraint(
model_name='follow',
constraint=models.UniqueConstraint(fields=('user', 'author'), name='unique_pair'),
),
migrations.AddConstraint(
model_name='follow',
constraint=models.CheckConstraint(check=models.Q(_negated=True, author=django.db.models.expressions.F('user')), name='author_not_user'),
),
]
18 changes: 1 addition & 17 deletions backend/users/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,6 @@
class User(AbstractUser):
"""Кастомная модель пользователя."""

ADMIN = 'admin'
MODERATOR = 'moderator'
USER = 'user'

ROLE_CHOICES = (
(ADMIN, 'ADMIN'), (USER, 'USER')
)

email = models.EmailField('email адрес', unique=True)
username = models.CharField(
'имя пользователя',
Expand All @@ -24,9 +16,6 @@ class User(AbstractUser):
first_name = models.CharField('имя', max_length=150)
last_name = models.CharField('фамилия', max_length=150)
password = models.CharField('пароль', max_length=150)
role = models.CharField(
max_length=15, choices=ROLE_CHOICES, default='user'
)

USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['username', 'password', 'first_name', 'last_name']
Expand All @@ -37,12 +26,7 @@ def get_full_name(self):

return f'{self.first_name} {self.last_name}'

@property
def is_admin(self):
return self.role == 'admin'

class Meta:
db_table = 'auth_user'
ordering = ['id']
verbose_name = 'Пользователь'
verbose_name_plural = 'Пользователи'
Expand Down Expand Up @@ -79,7 +63,7 @@ class Meta:
),
models.CheckConstraint(
check=~models.Q(author=models.F('user')),
name='author_not_user'
name='author_not_user',
),
)

Expand Down

0 comments on commit 53ec586

Please sign in to comment.