From 1c7ed517fa442fb005165f1a88086643aa97d93c Mon Sep 17 00:00:00 2001 From: earlinn Date: Sat, 3 Feb 2024 12:50:39 +0600 Subject: [PATCH] Check the user's age (from 6 to 110 years old is acceptable) at the API level, update this check at the model level --- backend/api/users_serializers.py | 18 +++++++++++++++++- backend/users/models.py | 27 +++++++++++++++++++++------ poetry.lock | 27 ++++++++++++++++++++++++++- pyproject.toml | 1 + 4 files changed, 65 insertions(+), 8 deletions(-) diff --git a/backend/api/users_serializers.py b/backend/api/users_serializers.py index 83b3a03..d84723c 100644 --- a/backend/api/users_serializers.py +++ b/backend/api/users_serializers.py @@ -1,12 +1,20 @@ +from dateutil.relativedelta import relativedelta from django.contrib.auth import get_user_model from django.db import transaction +from django.utils import timezone from djoser.serializers import UserCreateSerializer as DjoserUserCreateSerializer from djoser.serializers import UserDeleteSerializer as DjoserUserDeleteSerializer from djoser.serializers import UserSerializer as DjoserUserSerializer from rest_framework import serializers from rest_framework.validators import UniqueValidator -from users.models import Address +from users.models import ( + BIRTH_DATE_TOO_OLD_ERROR_MESSAGE, + BIRTH_DATE_TOO_YOUNG_ERROR_MESSAGE, + MAX_USER_AGE, + MIN_USER_AGE, + Address, +) from users.utils import city_choices User = get_user_model() @@ -69,6 +77,14 @@ class Meta: "photo", ) + def validate_birth_date(self, value): + now = timezone.now() + if value + relativedelta(years=MIN_USER_AGE) > now.date(): + raise serializers.ValidationError(BIRTH_DATE_TOO_YOUNG_ERROR_MESSAGE) + if value + relativedelta(years=MAX_USER_AGE) < now.date(): + raise serializers.ValidationError(BIRTH_DATE_TOO_OLD_ERROR_MESSAGE) + return value + def get_address_quantity(self, obj) -> int: return obj.addresses.count() diff --git a/backend/users/models.py b/backend/users/models.py index ce6b23d..d9cec3d 100644 --- a/backend/users/models.py +++ b/backend/users/models.py @@ -1,3 +1,4 @@ +from dateutil.relativedelta import relativedelta from django.contrib.auth.models import AbstractUser from django.contrib.auth.validators import UnicodeUsernameValidator from django.core.exceptions import ValidationError @@ -13,6 +14,16 @@ "форматах '+7XXXXXXXXXX', '7XXXXXXXXXX' или '8XXXXXXXXXX'." ) PHONE_NUMBER_REGEX = r"^(\+7|7|8)\d{10}$" +MIN_USER_AGE = 6 +MAX_USER_AGE = 110 +BIRTH_DATE_TOO_YOUNG_ERROR_MESSAGE = ( + "Указана неверная дата рождения, " + f"пользователю должно быть не менее {MIN_USER_AGE} лет." +) +BIRTH_DATE_TOO_OLD_ERROR_MESSAGE = ( + "Указана неверная дата рождения, " + f"пользователю должно быть не более {MAX_USER_AGE} лет." +) @cleanup.select @@ -59,12 +70,16 @@ def clean_fields(self, exclude=None): """Checks the user's birth date.""" super().clean_fields(exclude=exclude) now = timezone.now() - if self.birth_date: - if ( - self.birth_date.year > now.year - or (now.year - self.birth_date.year) > 120 - ): - raise ValidationError("Указана неверная дата рождения.") + if ( + self.birth_date + and self.birth_date + relativedelta(years=MIN_USER_AGE) > now.date() + ): + raise ValidationError(BIRTH_DATE_TOO_YOUNG_ERROR_MESSAGE) + if ( + self.birth_date + and self.birth_date + relativedelta(years=MAX_USER_AGE) < now.date() + ): + raise ValidationError(BIRTH_DATE_TOO_OLD_ERROR_MESSAGE) class Address(models.Model): diff --git a/poetry.lock b/poetry.lock index 7d48642..c24c18c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1137,6 +1137,20 @@ pytest = ">=7.0.0" docs = ["sphinx", "sphinx-rtd-theme"] testing = ["Django", "django-configurations (>=2.0)"] +[[package]] +name = "python-dateutil" +version = "2.8.2" +description = "Extensions to the standard Python datetime module" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +files = [ + {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, + {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, +] + +[package.dependencies] +six = ">=1.5" + [[package]] name = "python-dotenv" version = "1.0.0" @@ -1401,6 +1415,17 @@ files = [ {file = "rpds_py-0.12.0.tar.gz", hash = "sha256:7036316cc26b93e401cedd781a579be606dad174829e6ad9e9c5a0da6e036f80"}, ] +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] + [[package]] name = "social-auth-app-django" version = "5.4.0" @@ -1536,4 +1561,4 @@ zstd = ["zstandard (>=0.18.0)"] [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "def4f9b11c18cf5bd2ae56ac30d2dc25ad1833bc78ef40fa4fd3a4eb2a190b48" +content-hash = "343d60ded67368126a186ea8603f2e85435faf73c2656cd32cd58bdccb514700" diff --git a/pyproject.toml b/pyproject.toml index 4ea716e..fc6e4d9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,6 +24,7 @@ pytest = "^7.4.3" pytest-django = "^4.7.0" django-phonenumber-field = {extras = ["phonenumberslite"], version = "^7.2.0"} stripe = "^7.8.2" +python-dateutil = "^2.8.2" [tool.poetry.group.dev.dependencies]