From 36b96575b46b22a573e7356f24d5fc021d59b33f Mon Sep 17 00:00:00 2001 From: Feazom Date: Fri, 28 Jun 2024 11:32:32 +0500 Subject: [PATCH] fix func call --- config/locale/en/LC_MESSAGES/django.po | 16 +++++++++ config/locale/ru/LC_MESSAGES/django.po | 16 +++++++++ open_schools_platform/common/views.py | 34 ++++++++++++++++++- .../tests/views/test_get_circle_api.py | 8 +++-- .../organization_management/circles/views.py | 5 ++- .../migrations/0009_auto_20240628_1136.py | 23 +++++++++++++ .../employees/models.py | 3 ++ .../employees/roles.py | 18 ++++++++++ .../employees/services.py | 7 +++- .../employees/tests/utils.py | 6 ++-- .../organizations/views.py | 4 ++- 11 files changed, 132 insertions(+), 8 deletions(-) create mode 100644 open_schools_platform/organization_management/employees/migrations/0009_auto_20240628_1136.py create mode 100644 open_schools_platform/organization_management/employees/roles.py diff --git a/config/locale/en/LC_MESSAGES/django.po b/config/locale/en/LC_MESSAGES/django.po index 5637a594..faea0afb 100644 --- a/config/locale/en/LC_MESSAGES/django.po +++ b/config/locale/en/LC_MESSAGES/django.po @@ -194,3 +194,19 @@ msgstr " By %(filter_title)s " #: .\templates\templates\input_filter.html:18 msgid "Remove" msgstr "Remove" + + +msgid "creator" +msgstr "creator" + + +msgid "director" +msgstr "director" + + +msgid "employee" +msgstr "employee" + + +msgid "view only" +msgstr "view only" \ No newline at end of file diff --git a/config/locale/ru/LC_MESSAGES/django.po b/config/locale/ru/LC_MESSAGES/django.po index 6fe65953..5778ca7c 100644 --- a/config/locale/ru/LC_MESSAGES/django.po +++ b/config/locale/ru/LC_MESSAGES/django.po @@ -208,3 +208,19 @@ msgstr " От %(filter_title)s " #: .\templates\templates\input_filter.html:18 msgid "Remove" msgstr "Удалить" + + +msgid "creator" +msgstr "создатель" + + +msgid "director" +msgstr "директор" + + +msgid "employee" +msgstr "сотрудник" + + +msgid "view only" +msgstr "только просмотр" \ No newline at end of file diff --git a/open_schools_platform/common/views.py b/open_schools_platform/common/views.py index 43274988..351e1e38 100644 --- a/open_schools_platform/common/views.py +++ b/open_schools_platform/common/views.py @@ -1,10 +1,14 @@ +from operator import attrgetter from typing import Type, Dict, Union, List, Any +from django.contrib.contenttypes.models import ContentType from django.views import View +from rest_framework.exceptions import PermissionDenied from rest_framework.fields import ChoiceField, Field from rest_framework.serializers import Serializer as RestFrameworkSerializer from open_schools_platform.common.types import DjangoViewType +from open_schools_platform.organization_management.employees.roles import role_hierarchy def MultipleViewManager(handlers: Dict[str, Type[DjangoViewType]]) -> Type[DjangoViewType]: @@ -30,7 +34,7 @@ def dispatch(self, request, *args, **kwargs): return BaseManageView -def convert_dict_to_serializer(dictionary: Dict[str, Union[RestFrameworkSerializer, List[str]]])\ +def convert_dict_to_serializer(dictionary: Dict[str, Union[RestFrameworkSerializer, List[str]]]) \ -> Type[RestFrameworkSerializer]: class Serializer(RestFrameworkSerializer): # type: ignore pass @@ -45,3 +49,31 @@ class Serializer(RestFrameworkSerializer): # type: ignore Serializer._declared_fields = fields return Serializer + + +def ensure_role_permission(target_model, relation, target_profile, role): + def decorator(func): + def wrapper(*args, **kwargs): + if len(args) > 1: + request = args[1] + related_entities = {} + related_entities.update(request.data) + related_entities.update(request.query_params) + related_entities.update(request.parser_context.get('kwargs', {})) + + pk = related_entities.get(f'{target_model}_id') or related_entities.get(target_model) + if pk: + target_object = ContentType.objects.get(model=target_model).get_object_for_this_type(pk=pk) + retriever = attrgetter(relation) + profile = getattr(request.user, target_profile) + if profile: + relation_object = retriever(target_object).filter(**{target_profile: profile.id}).first() + + if hasattr(relation_object, 'role'): + if relation_object.role in role_hierarchy.get(role, ()): + return func(*args, **kwargs) + raise PermissionDenied + + return wrapper + + return decorator diff --git a/open_schools_platform/organization_management/circles/tests/views/test_get_circle_api.py b/open_schools_platform/organization_management/circles/tests/views/test_get_circle_api.py index f46237c5..3d05122f 100644 --- a/open_schools_platform/organization_management/circles/tests/views/test_get_circle_api.py +++ b/open_schools_platform/organization_management/circles/tests/views/test_get_circle_api.py @@ -2,6 +2,8 @@ from django.urls import reverse from rest_framework.test import APIClient from open_schools_platform.organization_management.circles.tests.utils import create_test_circle +from open_schools_platform.organization_management.employees.tests.utils import create_test_employee +from open_schools_platform.organization_management.organizations.tests.utils import create_test_organization from open_schools_platform.user_management.users.tests.utils import create_logged_in_user @@ -11,7 +13,9 @@ def setUp(self): self.circle_url = lambda pk: reverse("api:organization-management:circles:circle", args=[pk]) def test_successfully_get_circle(self): - create_logged_in_user(instance=self) - circle = create_test_circle() + user = create_logged_in_user(instance=self) + organization = create_test_organization() + circle = create_test_circle(organization) + create_test_employee(user, organization) response = self.client.get(self.circle_url(pk=str(circle.id))) self.assertEqual(200, response.status_code) diff --git a/open_schools_platform/organization_management/circles/views.py b/open_schools_platform/organization_management/circles/views.py index a6c0ae35..d7aed607 100644 --- a/open_schools_platform/organization_management/circles/views.py +++ b/open_schools_platform/organization_management/circles/views.py @@ -20,11 +20,12 @@ from .filters import CircleFilter from .paginators import ApiCircleListPagination from .selectors import get_circle, get_circles +from ..employees.roles import EmployeeRole from ..teachers.selectors import get_teacher_profile from ..teachers.serializers import CreateCircleInviteTeacherSerializer from ..teachers.services import create_teacher from ...common.utils import get_dict_excluding_fields -from ...common.views import convert_dict_to_serializer +from ...common.views import convert_dict_to_serializer, ensure_role_permission from ...parent_management.families.selectors import get_families from ...parent_management.parents.services import get_parent_profile_or_create_new_user, \ get_parent_family_or_create_new @@ -53,6 +54,7 @@ class CreateCircleApi(ApiAuthMixin, CreateAPIView): responses={201: convert_dict_to_serializer({"circle": GetCircleSerializer()}), 404: "No such organization"}, tags=[SwaggerTags.ORGANIZATION_MANAGEMENT_CIRCLES], ) + @ensure_role_permission('organization', 'employees', 'employee_profile', EmployeeRole.employee) def post(self, request): create_circle_serializer = CreateCircleSerializer(data=request.data) create_circle_serializer.is_valid(raise_exception=True) @@ -101,6 +103,7 @@ class GetCircleApi(ApiAuthMixin, APIView): tags=[SwaggerTags.ORGANIZATION_MANAGEMENT_CIRCLES], responses={200: convert_dict_to_serializer({"circle": GetCircleSerializer()})} ) + @ensure_role_permission('circle', 'organization.employees', 'employee_profile', EmployeeRole.view_only) def get(self, request, circle_id): circle = get_circle( filters={"id": str(circle_id)}, diff --git a/open_schools_platform/organization_management/employees/migrations/0009_auto_20240628_1136.py b/open_schools_platform/organization_management/employees/migrations/0009_auto_20240628_1136.py new file mode 100644 index 00000000..b9f87857 --- /dev/null +++ b/open_schools_platform/organization_management/employees/migrations/0009_auto_20240628_1136.py @@ -0,0 +1,23 @@ +# Generated by Django 3.2.12 on 2024-06-28 06:36 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('employees', '0008_auto_20230823_0756'), + ] + + operations = [ + migrations.AddField( + model_name='employee', + name='role', + field=models.CharField(blank=True, choices=[('creator', 'creator'), ('director', 'director'), ('employee', 'employee'), ('view_only', 'view only')], default='employee', max_length=50, null=True), + ), + migrations.AddField( + model_name='historicalemployee', + name='role', + field=models.CharField(blank=True, choices=[('creator', 'creator'), ('director', 'director'), ('employee', 'employee'), ('view_only', 'view only')], default='employee', max_length=50, null=True), + ), + ] diff --git a/open_schools_platform/organization_management/employees/models.py b/open_schools_platform/organization_management/employees/models.py index 839f2731..034ae409 100644 --- a/open_schools_platform/organization_management/employees/models.py +++ b/open_schools_platform/organization_management/employees/models.py @@ -7,6 +7,7 @@ from simple_history.models import HistoricalRecords from open_schools_platform.common.models import BaseModel, BaseManager +from open_schools_platform.organization_management.employees.roles import EmployeeRole from open_schools_platform.organization_management.organizations.models import Organization from open_schools_platform.user_management.users.models import User @@ -58,6 +59,8 @@ class Employee(BaseModel): null=True, default=None, blank=True, on_delete=models.CASCADE) name = models.CharField(max_length=255) position = models.CharField(max_length=255, blank=True, default="") + role = models.CharField(choices=EmployeeRole.choices, default=EmployeeRole.employee, + max_length=50, null=True, blank=True) history = HistoricalRecords() objects = EmployeeManager() # type: ignore[assignment] diff --git a/open_schools_platform/organization_management/employees/roles.py b/open_schools_platform/organization_management/employees/roles.py new file mode 100644 index 00000000..9a0ae772 --- /dev/null +++ b/open_schools_platform/organization_management/employees/roles.py @@ -0,0 +1,18 @@ +from django.utils.translation import gettext_lazy as _ +from django.db import models + + +class EmployeeRole(models.TextChoices): + creator = 'creator', _('creator') + director = 'director', _('director') + employee = 'employee', _('employee') + view_only = 'view_only', _('view only') + + +# defines which role can be replaced for a given key +role_hierarchy = { + 'creator': ('creator',), + 'director': ('director', 'creator'), + 'employee': ('employee', 'director', 'creator'), + 'view_only': ('view_only', 'employee', 'director', 'creator') +} diff --git a/open_schools_platform/organization_management/employees/services.py b/open_schools_platform/organization_management/employees/services.py index 86f83faa..8631fbfe 100644 --- a/open_schools_platform/organization_management/employees/services.py +++ b/open_schools_platform/organization_management/employees/services.py @@ -12,7 +12,11 @@ from django.utils.translation import gettext_lazy as _ -def create_employee(name: str, position: str = "", user: User = None, organization: Organization = None) -> Employee: +def create_employee(name: str, + position: str = "", + user: User = None, + organization: Organization = None, + role=None) -> Employee: employee_profile = None if user: employee_profile = user.employee_profile @@ -22,6 +26,7 @@ def create_employee(name: str, position: str = "", user: User = None, organizati employee_profile=employee_profile, organization=organization, position=position, + role=role ) return employee diff --git a/open_schools_platform/organization_management/employees/tests/utils.py b/open_schools_platform/organization_management/employees/tests/utils.py index 6d744e51..9dd71914 100644 --- a/open_schools_platform/organization_management/employees/tests/utils.py +++ b/open_schools_platform/organization_management/employees/tests/utils.py @@ -1,6 +1,7 @@ from typing import Any, Dict, List from open_schools_platform.common.filters import SoftCondition +from open_schools_platform.organization_management.employees.roles import EmployeeRole from open_schools_platform.organization_management.employees.selectors import get_employees from open_schools_platform.organization_management.employees.services import create_employee @@ -79,12 +80,13 @@ def create_test_organizations(): return organizations -def create_test_employee(user: User, organization: Organization = None): +def create_test_employee(user: User, organization: Organization = None, role=None): employee_data = { "name": "Andrey", "position": "Chief director", "user": user, - "organization": organization + "organization": organization, + "role": role or EmployeeRole.employee } # type: Dict[Any, Any] return create_employee(**employee_data) diff --git a/open_schools_platform/organization_management/organizations/views.py b/open_schools_platform/organization_management/organizations/views.py index aa1f873f..92aeda93 100644 --- a/open_schools_platform/organization_management/organizations/views.py +++ b/open_schools_platform/organization_management/organizations/views.py @@ -17,6 +17,7 @@ from open_schools_platform.organization_management.circles.models import Circle from open_schools_platform.organization_management.circles.paginators import ApiCircleListPagination from open_schools_platform.organization_management.circles.selectors import get_circle, get_circles +from open_schools_platform.organization_management.employees.roles import EmployeeRole from open_schools_platform.organization_management.employees.serializers import GetEmployeeSerializer, \ UpdateOrganizationInviteEmployeeSerializer, CreateOrganizationInviteEmployeeSerializer from open_schools_platform.organization_management.employees.services import create_employee, \ @@ -71,7 +72,8 @@ def post(self, request, *args, **kwargs): employee = create_employee(name=request.user.name, user=request.user, organization=org, - position="Creator") + position="Creator", + role=EmployeeRole.creator) return Response({"creator_employee": GetEmployeeSerializer(employee).data}, status=201)