diff --git a/hr/api_views.py b/hr/api_views.py index 39c7242..476bca8 100644 --- a/hr/api_views.py +++ b/hr/api_views.py @@ -11,7 +11,7 @@ Position, ) from hr.pagination import SmallSetPagination -from hr.permissions import IsNotRussianEmail +from hr.permissions import IsNotRussianEmail, HasPosition from hr.pydantic_models import WorkingDays from hr.serializers import ( EmployeeSerializer, @@ -27,7 +27,7 @@ class EmployeeViewSet(viewsets.ModelViewSet): queryset = Employee.objects.all().order_by() serializer_class = EmployeeSerializer pagination_class = SmallSetPagination - permission_classes = [IsNotRussianEmail] + permission_classes = [IsNotRussianEmail, HasPosition] def get_queryset(self): queryset = super().get_queryset() @@ -64,7 +64,7 @@ def post(self, request, *args, **kwargs): working=serializer.validated_data['working_days'], sick=serializer.validated_data['sick_days'], holiday=serializer.validated_data['holiday_days'], - vacation=serializer.validated_data['holiday_days'], + vacation=serializer.validated_data['vacation_days'], ) salary = calculator.calculate_salary(month_days=month_days) diff --git a/hr/permissions.py b/hr/permissions.py index f8f414d..9d7704a 100644 --- a/hr/permissions.py +++ b/hr/permissions.py @@ -1,4 +1,5 @@ from rest_framework import permissions +from rest_framework.exceptions import PermissionDenied class IsNotRussianEmail(permissions.BasePermission): @@ -10,3 +11,9 @@ def has_permission(self, request, view): if request.user and request.user.email: return not request.user.email.endswith('.ru') return False + + +class HasPosition(permissions.BasePermission): + + def has_object_permission(self, request, view, obj): + return request.user.is_authenticated and request.user.position is not None diff --git a/hr/serializers.py b/hr/serializers.py index b864df2..bd764a8 100644 --- a/hr/serializers.py +++ b/hr/serializers.py @@ -4,7 +4,10 @@ Employee, Position, ) -from hr.validators import validate_positive +from hr.validators import ( + validate_positive, + validate_holiday_days +) class EmployeeSerializer(serializers.ModelSerializer): @@ -22,7 +25,7 @@ class Meta: class SalarySerializer(serializers.Serializer): employee = serializers.PrimaryKeyRelatedField(queryset=Employee.objects.all()) working_days = serializers.IntegerField(validators=[validate_positive]) - holiday_days = serializers.IntegerField() + holiday_days = serializers.IntegerField(validators=[validate_holiday_days]) sick_days = serializers.IntegerField(default=0, max_value=4) vacation_days = serializers.IntegerField(default=0) @@ -44,3 +47,11 @@ def validate_sick_days(self, value): if value > 3: raise serializers.ValidationError('The number of sick days cannot be more than 3.') return value + + def validate_vacation_days(self, value): + """ + Checks that the number of sick days does not exceed 3. + """ + if value > 5: + raise serializers.ValidationError('The number of vacation days cannot be more than 5.') + return value diff --git a/hr/validators.py b/hr/validators.py index 37fe8de..8a4acbc 100644 --- a/hr/validators.py +++ b/hr/validators.py @@ -4,3 +4,7 @@ def validate_positive(value): if value < 0: raise serializers.ValidationError('This field must be positive.') + +def validate_holiday_days(value): + if value > 10: + raise serializers.ValidationError('Holiday days number cannot be more than 10.') diff --git a/poetry.lock b/poetry.lock index 2fe8e33..08b3b80 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand. [[package]] name = "annotated-types" @@ -6,6 +6,7 @@ version = "0.6.0" description = "Reusable constraint types to use with typing.Annotated" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "annotated_types-0.6.0-py3-none-any.whl", hash = "sha256:0641064de18ba7a25dee8f96403ebc39113d0cb953a01429249d5c7564666a43"}, {file = "annotated_types-0.6.0.tar.gz", hash = "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d"}, @@ -17,6 +18,7 @@ version = "3.7.2" description = "ASGI specs, helper code, and adapters" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "asgiref-3.7.2-py3-none-any.whl", hash = "sha256:89b2ef2247e3b562a16eef663bc0e2e703ec6468e2fa8a5cd61cd449786d4f6e"}, {file = "asgiref-3.7.2.tar.gz", hash = "sha256:9e0ce3aa93a819ba5b45120216b23878cf6e8525eb3848653452b4192b92afed"}, @@ -31,6 +33,7 @@ version = "4.2.6" description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "Django-4.2.6-py3-none-any.whl", hash = "sha256:a64d2487cdb00ad7461434320ccc38e60af9c404773a2f95ab0093b4453a3215"}, {file = "Django-4.2.6.tar.gz", hash = "sha256:08f41f468b63335aea0d904c5729e0250300f6a1907bf293a65499496cdbc68f"}, @@ -51,6 +54,7 @@ version = "0.18.11" description = "Translates Django models using a registration approach." optional = false python-versions = "*" +groups = ["main"] files = [ {file = "django-modeltranslation-0.18.11.tar.gz", hash = "sha256:a6e2c459e3b31852287d030bc6e29fa28576db97455dccd399fe08ac8e9223b9"}, {file = "django_modeltranslation-0.18.11-py3-none-any.whl", hash = "sha256:81b68e4dc806a3b779ac88babe1cbd99d5318d374a43b3737a65fb0f4c1cffe8"}, @@ -66,6 +70,7 @@ version = "3.15.1" description = "Web APIs for Django, made easy." optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "djangorestframework-3.15.1-py3-none-any.whl", hash = "sha256:3ccc0475bce968608cf30d07fb17d8e52d1d7fc8bfe779c905463200750cbca6"}, {file = "djangorestframework-3.15.1.tar.gz", hash = "sha256:f88fad74183dfc7144b2756d0d2ac716ea5b4c7c9840995ac3bfd8ec034333c1"}, @@ -80,6 +85,7 @@ version = "5.3.1" description = "A minimal JSON Web Token authentication plugin for Django REST Framework" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "djangorestframework_simplejwt-5.3.1-py3-none-any.whl", hash = "sha256:381bc966aa46913905629d472cd72ad45faa265509764e20ffd440164c88d220"}, {file = "djangorestframework_simplejwt-5.3.1.tar.gz", hash = "sha256:6c4bd37537440bc439564ebf7d6085e74c5411485197073f508ebdfa34bc9fae"}, @@ -104,6 +110,7 @@ version = "1.21.7" description = "Automated generation of real Swagger/OpenAPI 2.0 schemas from Django Rest Framework code." optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "drf-yasg-1.21.7.tar.gz", hash = "sha256:4c3b93068b3dfca6969ab111155e4dd6f7b2d680b98778de8fd460b7837bdb0d"}, {file = "drf_yasg-1.21.7-py3-none-any.whl", hash = "sha256:f85642072c35e684356475781b7ecf5d218fff2c6185c040664dd49f0a4be181"}, @@ -128,6 +135,7 @@ version = "24.9.0" description = "Faker is a Python package that generates fake data for you." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "Faker-24.9.0-py3-none-any.whl", hash = "sha256:97c7874665e8eb7b517f97bf3b59f03bf3f07513fe2c159e98b6b9ea6b9f2b3d"}, {file = "Faker-24.9.0.tar.gz", hash = "sha256:73b1e7967b0ceeac42fc99a8c973bb49e4499cc4044d20d17ab661d5cb7eda1d"}, @@ -142,6 +150,7 @@ version = "0.5.1" description = "A port of Ruby on Rails inflector to Python" optional = false python-versions = ">=3.5" +groups = ["main"] files = [ {file = "inflection-0.5.1-py2.py3-none-any.whl", hash = "sha256:f38b2b640938a4f35ade69ac3d053042959b62a0f1076a5bbaa1b9526605a8a2"}, {file = "inflection-0.5.1.tar.gz", hash = "sha256:1a29730d366e996aaacffb2f1f1cb9593dc38e2ddd30c91250c6dde09ea9b417"}, @@ -153,6 +162,7 @@ version = "24.0" description = "Core utilities for Python packages" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"}, {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, @@ -164,6 +174,7 @@ version = "10.3.0" description = "Python Imaging Library (Fork)" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "pillow-10.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:90b9e29824800e90c84e4022dd5cc16eb2d9605ee13f05d47641eb183cd73d45"}, {file = "pillow-10.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a2c405445c79c3f5a124573a051062300936b0281fee57637e706453e452746c"}, @@ -241,7 +252,7 @@ docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-inline fpx = ["olefile"] mic = ["olefile"] tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] -typing = ["typing-extensions"] +typing = ["typing-extensions ; python_version < \"3.10\""] xmp = ["defusedxml"] [[package]] @@ -250,6 +261,7 @@ version = "2.7.1" description = "Data validation using Python type hints" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "pydantic-2.7.1-py3-none-any.whl", hash = "sha256:e029badca45266732a9a79898a15ae2e8b14840b1eabbb25844be28f0b33f3d5"}, {file = "pydantic-2.7.1.tar.gz", hash = "sha256:e9dbb5eada8abe4d9ae5f46b9939aead650cd2b68f249bb3a8139dbe125803cc"}, @@ -269,6 +281,7 @@ version = "2.18.2" description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "pydantic_core-2.18.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:9e08e867b306f525802df7cd16c44ff5ebbe747ff0ca6cf3fde7f36c05a59a81"}, {file = "pydantic_core-2.18.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f0a21cbaa69900cbe1a2e7cad2aa74ac3cf21b10c3efb0fa0b80305274c0e8a2"}, @@ -360,6 +373,7 @@ version = "2.8.0" description = "JSON Web Token implementation in Python" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "PyJWT-2.8.0-py3-none-any.whl", hash = "sha256:59127c392cc44c2da5bb3192169a91f429924e17aff6534d70fdc02ab3e04320"}, {file = "PyJWT-2.8.0.tar.gz", hash = "sha256:57e28d156e3d5c10088e0c68abb90bfac3df82b40a71bd0daa20c65ccd5c23de"}, @@ -377,6 +391,7 @@ version = "2.9.0.post0" description = "Extensions to the standard Python datetime module" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["main"] files = [ {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, @@ -391,6 +406,7 @@ version = "2024.1" description = "World timezone definitions, modern and historical" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "pytz-2024.1-py2.py3-none-any.whl", hash = "sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319"}, {file = "pytz-2024.1.tar.gz", hash = "sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812"}, @@ -402,6 +418,7 @@ version = "6.0.1" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, @@ -462,6 +479,7 @@ version = "1.16.0" description = "Python 2 and 3 compatibility utilities" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +groups = ["main"] files = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, @@ -473,6 +491,7 @@ version = "0.4.4" description = "A non-validating SQL parser." optional = false python-versions = ">=3.5" +groups = ["main"] files = [ {file = "sqlparse-0.4.4-py3-none-any.whl", hash = "sha256:5430a4fe2ac7d0f93e66f1efc6e1338a41884b7ddf2a350cedd20ccc4d9d28f3"}, {file = "sqlparse-0.4.4.tar.gz", hash = "sha256:d446183e84b8349fa3061f0fe7f06ca94ba65b426946ffebe6e3e8295332420c"}, @@ -489,6 +508,7 @@ version = "4.11.0" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "typing_extensions-4.11.0-py3-none-any.whl", hash = "sha256:c1f94d72897edaf4ce775bb7558d5b79d8126906a14ea5ed1635921406c0387a"}, {file = "typing_extensions-4.11.0.tar.gz", hash = "sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0"}, @@ -500,6 +520,8 @@ version = "2023.3" description = "Provider of IANA time zone data" optional = false python-versions = ">=2" +groups = ["main"] +markers = "sys_platform == \"win32\"" files = [ {file = "tzdata-2023.3-py2.py3-none-any.whl", hash = "sha256:7e65763eef3120314099b6939b5546db7adce1e7d6f2e179e3df563c70511eda"}, {file = "tzdata-2023.3.tar.gz", hash = "sha256:11ef1e08e54acb0d4f95bdb1be05da659673de4acbd21bf9c69e94cc5e907a3a"}, @@ -511,12 +533,13 @@ version = "4.1.1" description = "Implementation of RFC 6570 URI Templates" optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "uritemplate-4.1.1-py2.py3-none-any.whl", hash = "sha256:830c08b8d99bdd312ea4ead05994a38e8936266f84b9a7878232db50b044e02e"}, {file = "uritemplate-4.1.1.tar.gz", hash = "sha256:4346edfc5c3b79f694bccd6d6099a322bbeb628dbf2cd86eea55a456ce5124f0"}, ] [metadata] -lock-version = "2.0" +lock-version = "2.1" python-versions = "^3.11" content-hash = "36448e6d4ef6dbce51d5fc6e79dc3f527b8ddb216c4419dddebad07ea6cd5405"