diff --git a/api/urls/v1/urls.py b/api/urls/v1/urls.py index 37cd52f..42c74c6 100644 --- a/api/urls/v1/urls.py +++ b/api/urls/v1/urls.py @@ -8,9 +8,11 @@ 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 app_name = "v1" urlpatterns = [ + path("intervention//check", InterventionCheckAPIView.as_view(), name="intervention-check"), path("intervention/", InterventionAPIViewV1.as_view(), name="intervention"), path("intervention/", InterventionAPIViewV1.as_view(), name="intervention"), path("compensation/", CompensationAPIViewV1.as_view(), name="compensation"), diff --git a/api/utils/serializer/v1/intervention.py b/api/utils/serializer/v1/intervention.py index cca9506..a36b547 100644 --- a/api/utils/serializer/v1/intervention.py +++ b/api/utils/serializer/v1/intervention.py @@ -182,7 +182,6 @@ class InterventionAPISerializerV1(AbstractModelAPISerializerV1, # Fill in data to objects properties = json_model["properties"] obj.title = properties["title"] - obj.modified = update_action self.set_responsibility(obj, properties.get("responsible", None)) self.set_legal(obj, properties.get("legal", None)) self.set_payments(obj, properties.get("payments", None)) @@ -194,7 +193,7 @@ class InterventionAPISerializerV1(AbstractModelAPISerializerV1, obj.legal.save() obj.save() - obj.log.add(update_action) + obj.mark_as_edited(user) celery_update_parcels.delay(obj.geometry.id) diff --git a/api/views/v1/views.py b/api/views/v1/views.py index fa55458..c7ebaff 100644 --- a/api/views/v1/views.py +++ b/api/views/v1/views.py @@ -13,13 +13,23 @@ from api.utils.serializer.v1.compensation import CompensationAPISerializerV1 from api.utils.serializer.v1.ecoaccount import EcoAccountAPISerializerV1 from api.utils.serializer.v1.ema import EmaAPISerializerV1 from api.utils.serializer.v1.intervention import InterventionAPISerializerV1 -from api.views.views import AbstractModelAPIView +from api.views.views import AbstractAPIView -class AbstractModelAPIViewV1(AbstractModelAPIView): +class AbstractAPIViewV1(AbstractAPIView): """ Holds general serialization functions for API v1 """ + serializer = None + + def __init__(self, *args, **kwargs): + self.lookup = { + "id": None, # must be set in subclasses + "deleted__isnull": True, + "users__in": [], # must be set in subclasses + } + super().__init__(*args, **kwargs) + self.serializer = self.serializer() def get(self, request: HttpRequest, id=None): """ Handles the GET request @@ -80,17 +90,17 @@ class AbstractModelAPIViewV1(AbstractModelAPIView): return JsonResponse({"id": updated_id}) -class InterventionAPIViewV1(AbstractModelAPIViewV1): +class InterventionAPIViewV1(AbstractAPIViewV1): serializer = InterventionAPISerializerV1 -class CompensationAPIViewV1(AbstractModelAPIViewV1): +class CompensationAPIViewV1(AbstractAPIViewV1): serializer = CompensationAPISerializerV1 -class EcoAccountAPIViewV1(AbstractModelAPIViewV1): +class EcoAccountAPIViewV1(AbstractAPIViewV1): serializer = EcoAccountAPISerializerV1 -class EmaAPIViewV1(AbstractModelAPIViewV1): +class EmaAPIViewV1(AbstractAPIViewV1): serializer = EmaAPISerializerV1 diff --git a/api/views/views.py b/api/views/views.py index 9608aec..3d47578 100644 --- a/api/views/views.py +++ b/api/views/views.py @@ -6,15 +6,16 @@ Created on: 21.01.22 """ -from django.http import JsonResponse +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 intervention.models import Intervention -class AbstractModelAPIView(View): +class AbstractAPIView(View): """ Base class for API views The API must follow the GeoJSON Specification RFC 7946 @@ -22,21 +23,11 @@ class AbstractModelAPIView(View): https://datatracker.ietf.org/doc/html/rfc7946 """ - serializer = None user = None class Meta: abstract = True - def __init__(self, *args, **kwargs): - self.lookup = { - "id": None, # must be set in subclasses - "deleted__isnull": True, - "users__in": [], # must be set in subclasses - } - super().__init__(*args, **kwargs) - self.serializer = self.serializer() - @csrf_exempt def dispatch(self, request, *args, **kwargs): try: @@ -65,3 +56,76 @@ class AbstractModelAPIView(View): }, status=status_code ) + + +class InterventionCheckAPIView(AbstractAPIView): + + def get(self, request: HttpRequest, id): + """ Takes the GET request + + Args: + request (HttpRequest): The incoming request + id (str): The intervention's id + + Returns: + response (JsonResponse) + """ + if not self.user.is_zb_user(): + return self.return_error_response("Permission not granted", 403) + try: + obj = Intervention.objects.get( + id=id, + users__in=[self.user] + ) + except Exception as e: + return self.return_error_response(e) + + all_valid, check_details = self.run_quality_checks(obj) + + if all_valid: + log_entry = obj.set_checked(self.user) + obj.log.add(log_entry) + + data = { + "success": all_valid, + "details": check_details + } + return JsonResponse(data) + + def run_quality_checks(self, obj: Intervention) -> (bool, dict): + """ Performs a check for intervention and related compensations + + Args: + obj (Intervention): The intervention + + Returns: + all_valid (boold): Whether an error occured or not + check_details (dict): A dict containg details on which elements have errors + """ + # Run quality check for Intervention + all_valid = True + intervention_checker = obj.quality_check() + all_valid = intervention_checker.valid and all_valid + + # Run quality checks for linked compensations + comps = obj.compensations.all() + comp_checkers = [] + for comp in comps: + comp_checker = comp.quality_check() + comp_checkers.append(comp_checker) + all_valid = comp_checker.valid and all_valid + + check_details = { + "intervention": { + "id": obj.id, + "errors": intervention_checker.messages + }, + "compensations": [ + { + "id": comp_checker.obj.id, + "errors": comp_checker.messages + } + for comp_checker in comp_checkers + ] + } + return all_valid, check_details diff --git a/intervention/models/intervention.py b/intervention/models/intervention.py index 766346f..5e2aca0 100644 --- a/intervention/models/intervention.py +++ b/intervention/models/intervention.py @@ -154,6 +154,7 @@ class Intervention(BaseObject, ShareableObjectMixin, RecordableObjectMixin, Chec def set_unrecorded(self, user: User): log_entry = super().set_unrecorded(user) self.add_log_entry_to_compensations(log_entry) + return log_entry def set_recorded(self, user: User) -> UserActionLogEntry: log_entry = super().set_recorded(user) @@ -259,11 +260,6 @@ class Intervention(BaseObject, ShareableObjectMixin, RecordableObjectMixin, Chec Returns: """ - user_action_edit = UserActionLogEntry.get_edited_action(performing_user, comment=edit_comment) - self.log.add(user_action_edit) - self.modified = user_action_edit - self.save() - super().mark_as_edited(performing_user, request) if self.checked: self.set_unchecked() diff --git a/konova/decorators.py b/konova/decorators.py index 4edf554..583afbe 100644 --- a/konova/decorators.py +++ b/konova/decorators.py @@ -75,9 +75,7 @@ def default_group_required(function): @wraps(function) def wrap(request, *args, **kwargs): user = request.user - has_group = user.groups.filter( - name=DEFAULT_GROUP - ).exists() + has_group = user.is_default_user() if has_group: return function(request, *args, **kwargs) else: @@ -95,9 +93,7 @@ def registration_office_group_required(function): @wraps(function) def wrap(request, *args, **kwargs): user = request.user - has_group = user.groups.filter( - name=ZB_GROUP - ).exists() + has_group = user.is_zb_user() if has_group: return function(request, *args, **kwargs) else: @@ -115,9 +111,7 @@ def conservation_office_group_required(function): @wraps(function) def wrap(request, *args, **kwargs): user = request.user - has_group = user.groups.filter( - name=ETS_GROUP - ).exists() + has_group = user.is_ets_user() if has_group: return function(request, *args, **kwargs) else: diff --git a/konova/models/object.py b/konova/models/object.py index 5a7d6f1..4b3959c 100644 --- a/konova/models/object.py +++ b/konova/models/object.py @@ -277,7 +277,8 @@ class RecordableObjectMixin(models.Model): self.save() if self.recorded: - self.set_unrecorded(performing_user) + action = self.set_unrecorded(performing_user) + self.log.add(action) if request: messages.info( request, diff --git a/user/models/user.py b/user/models/user.py index c461751..540b42a 100644 --- a/user/models/user.py +++ b/user/models/user.py @@ -9,6 +9,7 @@ from django.contrib.auth.models import AbstractUser from django.db import models +from konova.settings import ZB_GROUP, DEFAULT_GROUP, ETS_GROUP from konova.utils.mailer import Mailer from user.enums import UserNotificationEnum @@ -28,6 +29,36 @@ class User(AbstractUser): id=notification_enum.value ).exists() + def is_zb_user(self): + """ Shortcut for checking whether a user is of a special group or not + + Returns: + bool + """ + return self.groups.filter( + name=ZB_GROUP + ).exists() + + def is_default_user(self): + """ Shortcut for checking whether a user is of a special group or not + + Returns: + bool + """ + return self.groups.filter( + name=DEFAULT_GROUP + ).exists() + + def is_ets_user(self): + """ Shortcut for checking whether a user is of a special group or not + + Returns: + bool + """ + return self.groups.filter( + name=ETS_GROUP + ).exists() + def send_mail_shared_access_removed(self, obj_identifier): """ Sends a mail to the user in case of removed shared access