From fc0cd2f086ff842ade19df88c40954f43e3a220c Mon Sep 17 00:00:00 2001 From: mpeltriaux Date: Wed, 26 Jan 2022 09:16:37 +0100 Subject: [PATCH] #31 API Share * adds support for GET and PUT of sharing users for all data types (compensation is shared via intervention) --- api/urls/v1/urls.py | 8 +- api/utils/serializer/v1/compensation.py | 3 +- api/views/views.py | 125 ++++++++++++++++++++++++ compensation/models/compensation.py | 23 +++++ 4 files changed, 157 insertions(+), 2 deletions(-) diff --git a/api/urls/v1/urls.py b/api/urls/v1/urls.py index 42c74c6c..313a43b9 100644 --- a/api/urls/v1/urls.py +++ b/api/urls/v1/urls.py @@ -8,17 +8,23 @@ Created on: 21.01.22 from django.urls import path from api.views.v1.views import EmaAPIViewV1, EcoAccountAPIViewV1, CompensationAPIViewV1, InterventionAPIViewV1 -from api.views.views import InterventionCheckAPIView +from api.views.views import InterventionCheckAPIView, InterventionAPIShareView, EcoAccountAPIShareView, EmaAPIShareView app_name = "v1" urlpatterns = [ path("intervention//check", InterventionCheckAPIView.as_view(), name="intervention-check"), + path("intervention//share", InterventionAPIShareView.as_view(), name="intervention-share"), path("intervention/", InterventionAPIViewV1.as_view(), name="intervention"), path("intervention/", InterventionAPIViewV1.as_view(), name="intervention"), + path("compensation/", CompensationAPIViewV1.as_view(), name="compensation"), path("compensation/", CompensationAPIViewV1.as_view(), name="compensation"), + + path("ecoaccount//share", EcoAccountAPIShareView.as_view(), name="ecoaccount-share"), path("ecoaccount/", EcoAccountAPIViewV1.as_view(), name="ecoaccount"), path("ecoaccount/", EcoAccountAPIViewV1.as_view(), name="ecoaccount"), + + path("ema//share", EmaAPIShareView.as_view(), name="ema-share"), path("ema/", EmaAPIViewV1.as_view(), name="ema"), path("ema/", EmaAPIViewV1.as_view(), name="ema"), ] diff --git a/api/utils/serializer/v1/compensation.py b/api/utils/serializer/v1/compensation.py index eaf1694a..cc3428b2 100644 --- a/api/utils/serializer/v1/compensation.py +++ b/api/utils/serializer/v1/compensation.py @@ -12,6 +12,7 @@ from compensation.models import Compensation from intervention.models import Intervention from konova.models import Geometry from konova.tasks import celery_update_parcels +from konova.utils.message_templates import DATA_UNSHARED from user.models import UserActionLogEntry @@ -88,7 +89,7 @@ class CompensationAPISerializerV1(AbstractModelAPISerializerV1, AbstractCompensa is_shared = intervention.is_shared_with(user) if not is_shared: - raise PermissionError("Intervention not shared with user") + raise PermissionError(DATA_UNSHARED) obj.intervention = intervention return obj diff --git a/api/views/views.py b/api/views/views.py index 3d475787..32b0e500 100644 --- a/api/views/views.py +++ b/api/views/views.py @@ -5,14 +5,20 @@ Contact: michel.peltriaux@sgdnord.rlp.de Created on: 21.01.22 """ +import json +from django.db.models import QuerySet from django.http import JsonResponse, HttpRequest from django.views import View from django.views.decorators.csrf import csrf_exempt from api.models import APIUserToken from api.settings import KSP_TOKEN_HEADER_IDENTIFIER +from compensation.models import EcoAccount +from ema.models import Ema from intervention.models import Intervention +from konova.utils.message_templates import DATA_UNSHARED +from user.models import User class AbstractAPIView(View): @@ -129,3 +135,122 @@ class InterventionCheckAPIView(AbstractAPIView): ] } return all_valid, check_details + + +class AbstractModelShareAPIView(AbstractAPIView): + model = None + + class Meta: + abstract = True + + def get(self, request: HttpRequest, id): + """ Performs the GET request handling + + Args: + request (HttpRequest): The incoming request + id (str): The object's id + + Returns: + + """ + try: + users = self._get_shared_users_of_object(id) + except Exception as e: + return self.return_error_response(e) + + data = { + "users": [ + user.username for user in users + ] + } + + return JsonResponse(data) + + def put(self, request: HttpRequest, id): + """ Performs the PUT request handling + + Args: + request (HttpRequest): The incoming request + id (str): The object's id + + Returns: + + """ + + try: + success = self._process_put_body(request.body, id) + except Exception as e: + return self.return_error_response(e) + data = { + "success": success, + } + return JsonResponse(data) + + def _check_user_has_shared_access(self, obj): + """ Raises a PermissionError if user has no shared access + + Args: + obj (BaseObject): The object + + Returns: + + """ + is_shared = obj.is_shared_with(self.user) + if not is_shared: + raise PermissionError(DATA_UNSHARED) + + def _get_shared_users_of_object(self, id) -> QuerySet: + """ Check permissions and get the users + + Args: + id (str): The object's id + + Returns: + users (QuerySet) + """ + obj = self.model.objects.get( + id=id + ) + self._check_user_has_shared_access(obj) + users = obj.shared_users + return users + + def _process_put_body(self, body: bytes, id: str): + """ Reads the body data, performs validity checks and sets the new users + + Args: + body (bytes): The request.body + id (str): The object's id + + Returns: + success (bool) + """ + obj = self.model.objects.get(id=id) + self._check_user_has_shared_access(obj) + + new_users = json.loads(body.decode("utf-8")) + new_users = new_users.get("users", []) + if len(new_users) == 0: + raise ValueError("Shared user list must not be empty!") + + # Eliminate duplicates + new_users = list(dict.fromkeys(new_users)) + + # Make sure each of these names exist as a user + new_users_objs = [] + for user in new_users: + new_users_objs.append(User.objects.get(username=user)) + obj.share_with_list(new_users_objs) + return True + + +class InterventionAPIShareView(AbstractModelShareAPIView): + model = Intervention + + +class EcoAccountAPIShareView(AbstractModelShareAPIView): + model = EcoAccount + + +class EmaAPIShareView(AbstractModelShareAPIView): + model = Ema \ No newline at end of file diff --git a/compensation/models/compensation.py b/compensation/models/compensation.py index 59a3fd99..89d6dc2f 100644 --- a/compensation/models/compensation.py +++ b/compensation/models/compensation.py @@ -245,6 +245,29 @@ class Compensation(AbstractCompensation, CEFMixin, CoherenceMixin): # Compensations inherit their shared state from the interventions return self.intervention.is_shared_with(user) + def share_with(self, user: User): + """ Adds user to list of shared access users + + Args: + user (User): The user to be added to the object + + Returns: + + """ + if not self.intervention.is_shared_with(user): + self.intervention.users.add(user) + + def share_with_list(self, user_list: list): + """ Sets the list of shared access users + + Args: + user_list (list): The users to be added to the object + + Returns: + + """ + self.intervention.users.set(user_list) + @property def shared_users(self) -> QuerySet: """ Shortcut for fetching the users which have shared access on this object