From 39302e4562e6718efe31d1cfa4ab1c4dfafc8dfa Mon Sep 17 00:00:00 2001 From: Piotr Dybowski Date: Thu, 23 May 2019 16:10:14 +0200 Subject: [PATCH 1/6] Refactor `BaseSerializerTestCase` based tests - introduce setup - reduce code duplication - remove unnecessary / unused code - improve asserts (add descriptive message) --- .../tests/test_unit_report_serializer.py | 20 +++++----- .../tests/test_unit_customuser_serializer.py | 40 +++++++++++-------- utils/base_tests.py | 27 +++++++------ 3 files changed, 46 insertions(+), 41 deletions(-) diff --git a/employees/tests/test_unit_report_serializer.py b/employees/tests/test_unit_report_serializer.py index 1da1fe607..8cce59be5 100644 --- a/employees/tests/test_unit_report_serializer.py +++ b/employees/tests/test_unit_report_serializer.py @@ -22,16 +22,9 @@ class DataSetUpToTests(BaseSerializerTestCase): serializer_class = ReportSerializer - required_input = { - "date": datetime.datetime.now().date(), - "description": "Some description", - "author": None, - "project": None, - "work_hours": Decimal("8.00"), - } def setUp(self): - + super().setUp() self.sample_string_for_type_validation_tests = "This is a string" author = CustomUser( email="testuser@codepoets.it", password="newuserpasswd", first_name="John", last_name="Doe", country="PL" @@ -43,9 +36,14 @@ def setUp(self): project.full_clean() project.save() - self.required_input["author"] = author - self.required_input["project"] = project - self.required_input["task_activities"] = TaskActivityType.objects.get(name="Other") + self.required_input = { + "date": datetime.datetime.now().date(), + "description": "Some description", + "author": author, + "project": project, + "work_hours": Decimal("8.00"), + "task_activities": TaskActivityType.objects.get(name="Other") + } class ReportSerializerTests(DataSetUpToTests): diff --git a/users/tests/test_unit_customuser_serializer.py b/users/tests/test_unit_customuser_serializer.py index 1f6c10821..77d4cbbf2 100644 --- a/users/tests/test_unit_customuser_serializer.py +++ b/users/tests/test_unit_customuser_serializer.py @@ -9,16 +9,19 @@ class TestUserSerializerField(BaseSerializerTestCase): serializer_class = UserSerializer - required_input = { - "email": "example@codepoets.it", - "first_name": "Jan", - "last_name": "Kowalski", - "date_of_birth": datetime.datetime.now().date(), - "phone_number": "123456789", - "country": "PL", - "user_type": "EMPLOYEE", - "password": "passwduser", - } + + def setUp(self): + super().setUp() + self.required_input = { + "email": "example@codepoets.it", + "first_name": "Jan", + "last_name": "Kowalski", + "date_of_birth": datetime.datetime.strptime("2001-04-19", "%Y-%m-%d").date(), + "phone_number": "123456789", + "country": "PL", + "user_type": "EMPLOYEE", + "password": "passwduser", + } def test_user_serializer_email_field_should_accept_correct_input(self): self.field_should_accept_input("email", "testuser@codepoets.it") @@ -70,13 +73,16 @@ def test_user_serializer_password_field_should_accept_correct_input(self): class TestCustomRegisterSerializerField(BaseSerializerTestCase): serializer_class = CustomRegisterSerializer - required_input = { - "email": "example@codepoets.it", - "first_name": "Jan", - "last_name": "Kowalski", - "password": "passwduser", - "password_confirmation": "passwduser", - } + + def setUp(self): + super().setUp() + self.required_input = { + "email": "example@codepoets.it", + "first_name": "Jan", + "last_name": "Kowalski", + "password": "passwduser", + "password_confirmation": "passwduser", + } def test_register_serializer_email_field_should_accept_correct_input(self): self.field_should_accept_input("email", "testuser@codepoets.it") diff --git a/utils/base_tests.py b/utils/base_tests.py index bb31d3d09..15ebaab0b 100644 --- a/utils/base_tests.py +++ b/utils/base_tests.py @@ -119,39 +119,40 @@ def key_should_not_accept_incorrect_input(self, field, value, error_message=None class BaseSerializerTestCase(TestCase): - serializer_class = None - required_input = {} # dict containing fields and values for successful validation - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - assert issubclass(self.serializer_class, BaseSerializer) - assert isinstance(self.required_input, dict) + def setUp(self): + super().setUp() + self.required_input = {} def initiate_serializer(self, field, value): # Create serializer with one field altered from required_input - values = self.required_input.copy() - values[field] = value - return self.serializer_class(data=values) # pylint: disable=not-callable + self.required_input[field] = value + return self.serializer_class(data=self.required_input) # pylint: disable=not-callable def default_serializer(self): # Create serializer from required_input return self.serializer_class(data=self.required_input) # pylint: disable=not-callable - def _field_input_acceptance_test(self, field, value, is_valid, error_message=None): + def _field_input_acceptance_test(self, field, value, should_be_valid, error_message=None): # Private method containing base code for running serializer validation tests serializer = self.initiate_serializer(field, value) - self.assertEqual(serializer.is_valid(), is_valid) + is_valid = serializer.is_valid() + self.assertEqual( + is_valid, + should_be_valid, + msg=f"Serializer is {is_valid}, but should be {should_be_valid} for {field} = {value}" + ) if error_message is not None: self.assertEqual(str(serializer.errors[field][0]), error_message) def field_should_accept_input(self, field, value): # Test that putting specified value in specified field should result in successful serializer validation - self._field_input_acceptance_test(field=field, value=value, is_valid=True) + self._field_input_acceptance_test(field=field, value=value, should_be_valid=True) def field_should_not_accept_input(self, field, value, error_message=None): # Test that putting value in specified field should not result in successful serializer validation - self._field_input_acceptance_test(field=field, value=value, is_valid=False, error_message=error_message) + self._field_input_acceptance_test(field=field, value=value, should_be_valid=False, error_message=error_message) def field_should_accept_null(self, field): # Test that leaving specified field empty should result in successful serializer validation From 8501730e1c81198e9891de36eb92af335795bbb0 Mon Sep 17 00:00:00 2001 From: Piotr Dybowski Date: Mon, 27 May 2019 10:09:59 +0200 Subject: [PATCH 2/6] Refactor strings - use `_` as shortcut for `ugettext_lazy - add `VALIDATION_ERROR_AGE_NOT_ACCEPTED` --- users/common/strings.py | 81 ++++++++++++++++++++--------------------- 1 file changed, 39 insertions(+), 42 deletions(-) diff --git a/users/common/strings.py b/users/common/strings.py index 3e2c30180..192486074 100644 --- a/users/common/strings.py +++ b/users/common/strings.py @@ -1,49 +1,47 @@ # pylint: disable=line-too-long from enum import Enum -from django.utils.translation import ugettext_lazy +from django.utils.translation import ugettext_lazy as _ from users.common import constants from utils.mixins import NotCallableMixin class ConfirmationMessages: - SUCCESSFUL_UPDATE_USER_MESSAGE = ugettext_lazy("Account has been successfully updated!") - SUCCESSFUL_USER_PASSWORD_CHANGE_MESSAGE = ugettext_lazy("Your password has been successfully updated!") - FAILED_USER_PASSWORD_CHANGE_MESSAGE = ugettext_lazy("Please correct the error below.") + SUCCESSFUL_UPDATE_USER_MESSAGE = _("Account has been successfully updated!") + SUCCESSFUL_USER_PASSWORD_CHANGE_MESSAGE = _("Your password has been successfully updated!") + FAILED_USER_PASSWORD_CHANGE_MESSAGE = _("Please correct the error below.") class PermissionsMessage: - NONE_ADMIN_USER = ugettext_lazy("You are not allowed to enter - for administration only.") - NONE_ADMIN_OR_OWNER_USER = ugettext_lazy("It's none of your business.") + NONE_ADMIN_USER = _("You are not allowed to enter - for administration only.") + NONE_ADMIN_OR_OWNER_USER = _("It's none of your business.") class CustomUserAdminText: - PERSONAL_INFO = ugettext_lazy("Personal info") - STATUS = ugettext_lazy("Status") - PERMISSIONS = ugettext_lazy("Permissions") - IMPORTANT_DATES = ugettext_lazy("Important dates") + PERSONAL_INFO = _("Personal info") + STATUS = _("Status") + PERMISSIONS = _("Permissions") + IMPORTANT_DATES = _("Important dates") class CustomUserModelText: - VERBOSE_NAME_USER = ugettext_lazy("user") - VERBOSE_NAME_PLURAL_USERS = ugettext_lazy("users") - - EMAIL_ADDRESS = ugettext_lazy("email address") - FIRST_NAME = ugettext_lazy("first name") - LAST_NAME = ugettext_lazy("last name") - IS_STAFF = ugettext_lazy("staff status") - STAFF_HELP_TEXT = ugettext_lazy("Designates whether the user can log into this admin site.") - IS_ACTIVE = ugettext_lazy("active") - ACTIVE_HELP_TEXT = ugettext_lazy( + VERBOSE_NAME_USER = _("user") + VERBOSE_NAME_PLURAL_USERS = _("users") + + EMAIL_ADDRESS = _("email address") + FIRST_NAME = _("first name") + LAST_NAME = _("last name") + IS_STAFF = _("staff status") + STAFF_HELP_TEXT = _("Designates whether the user can log into this admin site.") + IS_ACTIVE = _("active") + ACTIVE_HELP_TEXT = _( "Designates whether this user should be treated as active. Unselect this instead of deleting accounts." ) - DATE_JOINED = ugettext_lazy("date joined") - DATE_OF_BIRTH = ugettext_lazy("date of birth") - UPDATED_AT = ugettext_lazy("updated at") - PHONE_REGEX_MESSAGE = ugettext_lazy( - "Phone number must be entered in the format: '999999999'. Up to 15 digits allowed." - ) + DATE_JOINED = _("date joined") + DATE_OF_BIRTH = _("date of birth") + UPDATED_AT = _("updated at") + PHONE_REGEX_MESSAGE = _("Phone number must be entered in the format: '999999999'. Up to 15 digits allowed.") class ValidationErrorText: @@ -56,28 +54,27 @@ class ValidationErrorText: "Please enter an e-mail address with a valid domain (" + ", ".join(constants.VALID_EMAIL_DOMAIN_LIST) + ")" ) VALIDATION_ERROR_EMAIL_MESSAGE_DOMAIN_SHORT = "Please enter an e-mail address with a valid domain" - VALIDATION_ERROR_SIGNUP_EMAIL_MESSAGE = ugettext_lazy("A user is already registered with this e-mail address.") - VALIDATION_ERROR_SIGNUP_PASSWORD_MESSAGE = ugettext_lazy("The two password fields didn't match.") + VALIDATION_ERROR_SIGNUP_EMAIL_MESSAGE = _("A user is already registered with this e-mail address.") + VALIDATION_ERROR_SIGNUP_PASSWORD_MESSAGE = _("The two password fields didn't match.") + VALIDATION_ERROR_AGE_NOT_ACCEPTED = _("User can't be below 18 or above 99 years old.") class CustomUserCountryText: - POLAND = ugettext_lazy("Poland") - UNITED_STATES = ugettext_lazy("United States") - UNITED_KINGDOM = ugettext_lazy("United Kingdom") - GERMANY = ugettext_lazy("Germany") - FRANCE = ugettext_lazy("France") + POLAND = _("Poland") + UNITED_STATES = _("United States") + UNITED_KINGDOM = _("United Kingdom") + GERMANY = _("Germany") + FRANCE = _("France") class CustomUserUserTypeText: - EMPLOYEE = ugettext_lazy("Employee") - MANAGER = ugettext_lazy("Manager") - ADMIN = ugettext_lazy("Admin") + EMPLOYEE = _("Employee") + MANAGER = _("Manager") + ADMIN = _("Admin") class SuccessInfoAfterRegistrationText(NotCallableMixin, Enum): - CONGRATULATIONS = ugettext_lazy("Congratulations!") - ACCOUNT_CREATED = ugettext_lazy("Your account has been successfully created! Now you can sign in!") - REDIRECTION_INFO = ugettext_lazy( - "You will be redirected in few seconds to the login site or press a button to make it faster." - ) - OKAY_BUTTON = ugettext_lazy("Okay!") + CONGRATULATIONS = _("Congratulations!") + ACCOUNT_CREATED = _("Your account has been successfully created! Now you can sign in!") + REDIRECTION_INFO = _("You will be redirected in few seconds to the login site or press a button to make it faster.") + OKAY_BUTTON = _("Okay!") From f149ce51d40c446b61e657fe16eafcb549941ec1 Mon Sep 17 00:00:00 2001 From: Piotr Dybowski Date: Mon, 27 May 2019 10:10:05 +0200 Subject: [PATCH 3/6] Add validation of user age (for user UserSerializer) and unit tests for it --- employees/tests/test_unit_report_serializer.py | 2 +- users/serializers.py | 9 +++++++++ users/tests/test_unit_customuser_serializer.py | 14 +++++++++++++- utils/base_tests.py | 3 +-- 4 files changed, 24 insertions(+), 4 deletions(-) diff --git a/employees/tests/test_unit_report_serializer.py b/employees/tests/test_unit_report_serializer.py index 8cce59be5..3335519d0 100644 --- a/employees/tests/test_unit_report_serializer.py +++ b/employees/tests/test_unit_report_serializer.py @@ -42,7 +42,7 @@ def setUp(self): "author": author, "project": project, "work_hours": Decimal("8.00"), - "task_activities": TaskActivityType.objects.get(name="Other") + "task_activities": TaskActivityType.objects.get(name="Other"), } diff --git a/users/serializers.py b/users/serializers.py index 554c46cc3..ec9daf5df 100644 --- a/users/serializers.py +++ b/users/serializers.py @@ -1,3 +1,4 @@ +import datetime import logging from typing import Any from typing import Dict @@ -7,6 +8,7 @@ from allauth.account.adapter import get_adapter as allauth_get_adapter from allauth.account.utils import setup_user_email from allauth.utils import email_address_exists +from dateutil.relativedelta import relativedelta from django.http import HttpRequest from django_countries.serializers import CountryFieldMixin from rest_framework import serializers @@ -43,6 +45,13 @@ def validate_email(self, email: str) -> str: raise serializers.ValidationError(ValidationErrorText.VALIDATION_ERROR_SIGNUP_EMAIL_MESSAGE) return email + def validate_date_of_birth(self, date_of_birth: datetime.date) -> datetime.date: # pylint: disable=no-self-use + if date_of_birth is not None: + age = relativedelta(datetime.datetime.now(), date_of_birth).years + if not 18 <= age <= 99: + raise serializers.ValidationError(CustomValidationErrorText.VALIDATION_ERROR_AGE_NOT_ACCEPTED) + return date_of_birth + class UserListSerializer(UserSerializer): url = serializers.HyperlinkedIdentityField(view_name="users-detail") diff --git a/users/tests/test_unit_customuser_serializer.py b/users/tests/test_unit_customuser_serializer.py index 77d4cbbf2..9e98e607e 100644 --- a/users/tests/test_unit_customuser_serializer.py +++ b/users/tests/test_unit_customuser_serializer.py @@ -1,5 +1,7 @@ import datetime +from freezegun import freeze_time + from users.common import constants from users.common.model_helpers import create_user_using_full_clean_and_save from users.serializers import CustomRegisterSerializer @@ -7,6 +9,7 @@ from utils.base_tests import BaseSerializerTestCase +@freeze_time("2019-05-23 11:00") class TestUserSerializerField(BaseSerializerTestCase): serializer_class = UserSerializer @@ -44,7 +47,8 @@ def test_user_serializer_last_name_field_should_not_accept_string_longer_than_se self.field_should_not_accept_input("last_name", "a" * (constants.LAST_NAME_MAX_LENGTH + 1)) def test_user_serializer_date_of_birth_field_should_accept_correct_input(self): - self.field_should_accept_input("date_of_birth", datetime.datetime.now().date()) + date_of_birth = datetime.datetime.strptime("2001-04-19", "%Y-%m-%d").date() + self.field_should_accept_input("date_of_birth", date_of_birth) def test_user_serializer_date_of_birth_field_may_be_empty(self): self.field_should_accept_null("date_of_birth") @@ -70,6 +74,14 @@ def test_user_serializer_user_type_field_should_accept_correct_input(self): def test_user_serializer_password_field_should_accept_correct_input(self): self.field_should_accept_input("password", "password") + def test_user_serializer_users_age_cannot_be_below_18(self): + date_of_birth = datetime.datetime.strptime("2011-04-19", "%Y-%m-%d").date() + self.field_should_not_accept_input("date_of_birth", date_of_birth) + + def test_user_serializer_users_age_cannot_be_above_100(self): + date_of_birth = datetime.datetime.strptime("1919-04-19", "%Y-%m-%d").date() + self.field_should_not_accept_input("date_of_birth", date_of_birth) + class TestCustomRegisterSerializerField(BaseSerializerTestCase): serializer_class = CustomRegisterSerializer diff --git a/utils/base_tests.py b/utils/base_tests.py index 15ebaab0b..f479a7428 100644 --- a/utils/base_tests.py +++ b/utils/base_tests.py @@ -3,7 +3,6 @@ from django.test import TestCase from django.utils import timezone from freezegun import freeze_time -from rest_framework.serializers import BaseSerializer class BaseModelTestCase(TestCase): @@ -141,7 +140,7 @@ def _field_input_acceptance_test(self, field, value, should_be_valid, error_mess self.assertEqual( is_valid, should_be_valid, - msg=f"Serializer is {is_valid}, but should be {should_be_valid} for {field} = {value}" + msg=f"Serializer is {is_valid}, but should be {should_be_valid} for {field} = {value}", ) if error_message is not None: self.assertEqual(str(serializer.errors[field][0]), error_message) From e385fc750de3874cbb475e746bc7ceec63cbb21c Mon Sep 17 00:00:00 2001 From: Piotr Dybowski Date: Mon, 27 May 2019 13:11:04 +0200 Subject: [PATCH 4/6] Add custom `UserAgeValidator` and unit tests for it --- users/tests/test_unit_validators.py | 46 +++++++++++++++++++++++++++++ users/validators.py | 25 ++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 users/tests/test_unit_validators.py create mode 100644 users/validators.py diff --git a/users/tests/test_unit_validators.py b/users/tests/test_unit_validators.py new file mode 100644 index 000000000..fbfccda22 --- /dev/null +++ b/users/tests/test_unit_validators.py @@ -0,0 +1,46 @@ +import datetime +from django.core.exceptions import ValidationError +from django.test import TestCase +from freezegun import freeze_time + +from users.validators import UserAgeValidator + + +@freeze_time("2019-05-27") +class TestUserAgeValidator(TestCase): + def setUp(self): + self.user_age_validator = UserAgeValidator( + UserAgeValidator.MINIMAL_ACCETABLE_AGLE, + UserAgeValidator.MAXIMAL_ACCEPTABLE_AGE, + ) + + def test_that_users_age_can_be_none(self): + self._assert_date_of_birth_is_valid(None) + + def test_that_users_age_can_be_equal_to_lower_limit(self): + # user is exactly 18 years old + date_of_birth = datetime.datetime.strptime("2001-05-27", "%Y-%m-%d").date() + self._assert_date_of_birth_is_valid(date_of_birth) + + def test_that_users_age_can_be_equal_to_upper_limit(self): + # user is exactly 100 years old - 1 day (still 99) + date_of_birth = datetime.datetime.strptime("1919-05-28", "%Y-%m-%d").date() + self._assert_date_of_birth_is_valid(date_of_birth) + + def test_that_when_users_age_is_below_lower_limit_validation_error_is_raised(self): + # user is exactly 18 years old - 1 day (still 17) + date_of_birth = datetime.datetime.strptime("2001-05-28", "%Y-%m-%d").date() + with self.assertRaises(ValidationError): + self.user_age_validator(date_of_birth) + + def test_that_when_users_age_is_above_upper_limit_validation_error_is_raised(self): + # user is exactly 100 years old + date_of_birth = datetime.datetime.strptime("1919-05-27", "%Y-%m-%d").date() + with self.assertRaises(ValidationError): + self.user_age_validator(date_of_birth) + + def _assert_date_of_birth_is_valid(self, date_of_birth): + try: + self.user_age_validator(date_of_birth) + except Exception as e: + self.fail(f"Unexpected exception {str(e)} has occurred!") diff --git a/users/validators.py b/users/validators.py new file mode 100644 index 000000000..02a4f6c04 --- /dev/null +++ b/users/validators.py @@ -0,0 +1,25 @@ +import datetime +from django.utils.deconstruct import deconstructible +from typing import Optional + +from dateutil.relativedelta import relativedelta +from django.core.exceptions import ValidationError + +from users.common.strings import ValidationErrorText + + +@deconstructible +class UserAgeValidator: + MINIMAL_ACCETABLE_AGLE = 18 + MAXIMAL_ACCEPTABLE_AGE = 99 + + def __init__(self, minimal_age: int, maximal_age: int) -> None: + self.minimal_age = minimal_age + self.maximal_age = maximal_age + + def __call__(self, users_birth_date: Optional[datetime.date]) -> Optional[datetime.date]: + if users_birth_date is not None: + age = relativedelta(datetime.datetime.now(), users_birth_date).years + if not self.MINIMAL_ACCETABLE_AGLE <= age <= self.MAXIMAL_ACCEPTABLE_AGE: + raise ValidationError(ValidationErrorText.VALIDATION_ERROR_AGE_NOT_ACCEPTED) + return users_birth_date From 25cc3bfa3f3ce2c790eb0e5491f2652bfb0b7119 Mon Sep 17 00:00:00 2001 From: Piotr Dybowski Date: Mon, 27 May 2019 14:00:49 +0200 Subject: [PATCH 5/6] [M] Move user's age validation to model - delete user's age validation from serializers - add `UserAgeValidator` to `CustomUser` model for `date_of_birth` field - adapt `UserAgeValidator` model's tests --- users/migrations/0002_auto_20190527_1153.py | 19 +++++++++++++ users/models.py | 8 +++++- users/serializers.py | 9 ------ users/tests/test_unit_customuser_model.py | 28 +++++++++++-------- .../tests/test_unit_customuser_serializer.py | 8 ------ users/tests/test_unit_validators.py | 6 ++-- users/validators.py | 2 +- 7 files changed, 47 insertions(+), 33 deletions(-) create mode 100644 users/migrations/0002_auto_20190527_1153.py diff --git a/users/migrations/0002_auto_20190527_1153.py b/users/migrations/0002_auto_20190527_1153.py new file mode 100644 index 000000000..6e8842932 --- /dev/null +++ b/users/migrations/0002_auto_20190527_1153.py @@ -0,0 +1,19 @@ +# Generated by Django 2.1.8 on 2019-05-27 11:53 + +from django.db import migrations, models +import users.validators + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='customuser', + name='date_of_birth', + field=models.DateField(blank=True, null=True, validators=[users.validators.UserAgeValidator(18, 99)], verbose_name='date of birth'), + ), + ] diff --git a/users/models.py b/users/models.py index 05147e391..dfaf548ce 100644 --- a/users/models.py +++ b/users/models.py @@ -20,6 +20,7 @@ from users.common.strings import ValidationErrorText from users.common.utils import custom_validate_email_function from users.common.validators import PhoneRegexValidator +from users.validators import UserAgeValidator logger = logging.getLogger(__name__) @@ -85,7 +86,12 @@ class UserType(ChoiceEnum): CustomUserModelText.IS_ACTIVE, default=True, help_text=CustomUserModelText.ACTIVE_HELP_TEXT ) date_joined = models.DateTimeField(CustomUserModelText.DATE_JOINED, auto_now_add=True) - date_of_birth = models.DateField(CustomUserModelText.DATE_OF_BIRTH, blank=True, null=True) + date_of_birth = models.DateField( + CustomUserModelText.DATE_OF_BIRTH, + blank=True, + null=True, + validators=[UserAgeValidator(UserAgeValidator.MINIMAL_ACCETABLE_AGLE, UserAgeValidator.MAXIMAL_ACCEPTABLE_AGE)], + ) updated_at = models.DateTimeField(CustomUserModelText.UPDATED_AT, auto_now=True) phone_number = models.CharField( validators=[PhoneRegexValidator], max_length=constants.PHONE_NUMBER_MAX_LENGTH, blank=True, null=True diff --git a/users/serializers.py b/users/serializers.py index ec9daf5df..554c46cc3 100644 --- a/users/serializers.py +++ b/users/serializers.py @@ -1,4 +1,3 @@ -import datetime import logging from typing import Any from typing import Dict @@ -8,7 +7,6 @@ from allauth.account.adapter import get_adapter as allauth_get_adapter from allauth.account.utils import setup_user_email from allauth.utils import email_address_exists -from dateutil.relativedelta import relativedelta from django.http import HttpRequest from django_countries.serializers import CountryFieldMixin from rest_framework import serializers @@ -45,13 +43,6 @@ def validate_email(self, email: str) -> str: raise serializers.ValidationError(ValidationErrorText.VALIDATION_ERROR_SIGNUP_EMAIL_MESSAGE) return email - def validate_date_of_birth(self, date_of_birth: datetime.date) -> datetime.date: # pylint: disable=no-self-use - if date_of_birth is not None: - age = relativedelta(datetime.datetime.now(), date_of_birth).years - if not 18 <= age <= 99: - raise serializers.ValidationError(CustomValidationErrorText.VALIDATION_ERROR_AGE_NOT_ACCEPTED) - return date_of_birth - class UserListSerializer(UserSerializer): url = serializers.HyperlinkedIdentityField(view_name="users-detail") diff --git a/users/tests/test_unit_customuser_model.py b/users/tests/test_unit_customuser_model.py index 1ada1c376..ae37e846c 100644 --- a/users/tests/test_unit_customuser_model.py +++ b/users/tests/test_unit_customuser_model.py @@ -4,6 +4,7 @@ from django.core.exceptions import ValidationError from django.core.validators import MaxLengthValidator from django.test import TestCase +from freezegun import freeze_time from users.common import constants from users.common.model_helpers import create_user_using_full_clean_and_save @@ -117,18 +118,22 @@ def test_that_email_user_should_send_email_to_self_user(self): self.assertEqual(mocked_method(), "Email has been sent successfully") +@freeze_time("2019-05-27") class TestCustomUserModelField(BaseModelTestCase): model_class = CustomUser - required_input = { - "email": "example@codepoets.it", - "first_name": "Jan", - "last_name": "Kowalski", - "date_of_birth": datetime.datetime.now().date(), - "phone_number": "123456789", - "country": "PL", - "user_type": "EMPLOYEE", - "password": "passwduser", - } + + def setUp(self): + super().setUp() + self.required_input = { + "email": "example@codepoets.it", + "first_name": "Jan", + "last_name": "Kowalski", + "date_of_birth": datetime.datetime.strptime("2000-05-13", "%Y-%m-%d").date(), + "phone_number": "123456789", + "country": "PL", + "user_type": "EMPLOYEE", + "password": "passwduser", + } def test_customuser_model_email_field_should_accept_correct_input(self): self.field_should_accept_input("email", "user@codepoets.it") @@ -166,7 +171,8 @@ def test_customuser_model_date_joined_field_should_be_filled_on_save(self): self.field_should_have_non_null_default("date_joined") def test_customuser_model_date_of_birth_field_should_accept_correct_input(self): - self.field_should_accept_input("date_of_birth", datetime.datetime.now().date()) + date_of_birth = datetime.datetime.strptime("2001-05-26", "%Y-%m-%d").date() + self.field_should_accept_input("date_of_birth", date_of_birth) def test_customuser_model_date_of_birth_field_may_be_empty(self): self.field_should_accept_null("date_of_birth") diff --git a/users/tests/test_unit_customuser_serializer.py b/users/tests/test_unit_customuser_serializer.py index 9e98e607e..fa2461f47 100644 --- a/users/tests/test_unit_customuser_serializer.py +++ b/users/tests/test_unit_customuser_serializer.py @@ -74,14 +74,6 @@ def test_user_serializer_user_type_field_should_accept_correct_input(self): def test_user_serializer_password_field_should_accept_correct_input(self): self.field_should_accept_input("password", "password") - def test_user_serializer_users_age_cannot_be_below_18(self): - date_of_birth = datetime.datetime.strptime("2011-04-19", "%Y-%m-%d").date() - self.field_should_not_accept_input("date_of_birth", date_of_birth) - - def test_user_serializer_users_age_cannot_be_above_100(self): - date_of_birth = datetime.datetime.strptime("1919-04-19", "%Y-%m-%d").date() - self.field_should_not_accept_input("date_of_birth", date_of_birth) - class TestCustomRegisterSerializerField(BaseSerializerTestCase): serializer_class = CustomRegisterSerializer diff --git a/users/tests/test_unit_validators.py b/users/tests/test_unit_validators.py index fbfccda22..84faa51de 100644 --- a/users/tests/test_unit_validators.py +++ b/users/tests/test_unit_validators.py @@ -1,4 +1,5 @@ import datetime + from django.core.exceptions import ValidationError from django.test import TestCase from freezegun import freeze_time @@ -10,8 +11,7 @@ class TestUserAgeValidator(TestCase): def setUp(self): self.user_age_validator = UserAgeValidator( - UserAgeValidator.MINIMAL_ACCETABLE_AGLE, - UserAgeValidator.MAXIMAL_ACCEPTABLE_AGE, + UserAgeValidator.MINIMAL_ACCETABLE_AGLE, UserAgeValidator.MAXIMAL_ACCEPTABLE_AGE ) def test_that_users_age_can_be_none(self): @@ -42,5 +42,5 @@ def test_that_when_users_age_is_above_upper_limit_validation_error_is_raised(sel def _assert_date_of_birth_is_valid(self, date_of_birth): try: self.user_age_validator(date_of_birth) - except Exception as e: + except Exception as e: # pylint:disable=broad-except self.fail(f"Unexpected exception {str(e)} has occurred!") diff --git a/users/validators.py b/users/validators.py index 02a4f6c04..7ae922b9b 100644 --- a/users/validators.py +++ b/users/validators.py @@ -1,9 +1,9 @@ import datetime -from django.utils.deconstruct import deconstructible from typing import Optional from dateutil.relativedelta import relativedelta from django.core.exceptions import ValidationError +from django.utils.deconstruct import deconstructible from users.common.strings import ValidationErrorText From e62ad3cedae926c6e22bfed397fc960a25359c3f Mon Sep 17 00:00:00 2001 From: Karol Beker Date: Mon, 27 May 2019 14:49:41 +0200 Subject: [PATCH 6/6] Add note to realease-notes --- RELEASE-NOTES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 664cd7ee8..8a89920e0 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -23,6 +23,7 @@ - Bugfix disable possibility to change report's author by Admin user ([#201](https://github.com/Code-Poets/sheetstorm/pull/201)) - Bugfix for access to editing managers list and update tests ([#197](https://github.com/Code-Poets/sheetstorm/pull/197)) - Bugfix show all project list ([#237](https://github.com/Code-Poets/sheetstorm/pull/237)) +- Bugfix user birth day has no limit ([#231](https://github.com/Code-Poets/sheetstorm/pull/231)) - Bugfix wrong projects displayed after report creation fail ([#228](https://github.com/Code-Poets/sheetstorm/pull/228))