From d58ca3f324829a6d1c716bcf3599db3f2da84efb Mon Sep 17 00:00:00 2001 From: mpeltriaux Date: Mon, 24 Jan 2022 15:56:02 +0100 Subject: [PATCH] #31 API PUT Compensation * adds support for PUT compensation (Update) * improves updating of related objects * adds missing payment PUT support for intervention API --- api/utils/serializer/v1/compensation.py | 41 ++++++++++++--- api/utils/serializer/v1/intervention.py | 66 +++++++++++++++++++++++-- api/utils/serializer/v1/serializer.py | 64 +++++++++++++++--------- 3 files changed, 138 insertions(+), 33 deletions(-) diff --git a/api/utils/serializer/v1/compensation.py b/api/utils/serializer/v1/compensation.py index 05787795..e2ec2609 100644 --- a/api/utils/serializer/v1/compensation.py +++ b/api/utils/serializer/v1/compensation.py @@ -78,16 +78,21 @@ class CompensationAPISerializerV1(AbstractModelAPISerializerV1, AbstractCompensa Returns: obj (Compensation) """ + if obj.intervention.id == intervention_id: + # Nothing to do here + return obj + intervention = Intervention.objects.get( id=intervention_id, ) is_shared = intervention.is_shared_with(user) + if not is_shared: raise PermissionError("Intervention not shared with user") + obj.intervention = intervention return obj - def create_model_from_json(self, json_model, user): """ Creates a new entry for the model based on the contents of json_model @@ -123,6 +128,23 @@ class CompensationAPISerializerV1(AbstractModelAPISerializerV1, AbstractCompensa return obj.id + def get_obj_from_db(self, id, user): + """ Returns the object from database + + Fails if id not found or user does not have shared access + + Args: + id (str): The object's id + user (User): The API user + + Returns: + + """ + return self.model.objects.get( + id=id, + intervention__users__in=[user] + ) + def update_model_from_json(self, id, json_model, user): """ Updates an entry for the model based on the contents of json_model @@ -135,21 +157,28 @@ class CompensationAPISerializerV1(AbstractModelAPISerializerV1, AbstractCompensa created_id (str): The id of the newly created Compensation entry """ with transaction.atomic(): + update_action = UserActionLogEntry.get_edited_action(user, "API update") obj = self.get_obj_from_db(id, user) # Fill in data to objects properties = json_model["properties"] obj.title = properties["title"] - self.set_responsibility(obj, properties["responsible"]) - self.set_legal(obj, properties["legal"]) + obj.is_cef = properties["is_cef"] + obj.is_coherence_keeping = properties["is_coherence_keeping"] + obj.modified = update_action obj.geometry.geom = self.create_geometry_from_json(json_model) + obj.geometry.modified = update_action + obj = self.set_intervention(obj, properties["intervention"], user) - obj.responsible.save() obj.geometry.save() - obj.legal.save() obj.save() - obj.users.add(user) + obj = self.set_compensation_actions(obj, properties["actions"]) + obj = self.set_compensation_states(obj, properties["before_states"], obj.before_states) + obj = self.set_compensation_states(obj, properties["after_states"], obj.after_states) + obj = self.set_deadlines(obj, properties["deadlines"]) + + obj.log.add(update_action) celery_update_parcels.delay(obj.geometry.id) diff --git a/api/utils/serializer/v1/intervention.py b/api/utils/serializer/v1/intervention.py index 1b91a6cb..34fb75c5 100644 --- a/api/utils/serializer/v1/intervention.py +++ b/api/utils/serializer/v1/intervention.py @@ -9,16 +9,20 @@ from django.db import transaction from django.db.models import QuerySet from api.utils.serializer.v1.serializer import AbstractModelAPISerializerV1, \ - ResponsibilityAPISerializerV1Mixin, LegalAPISerializerV1Mixin + ResponsibilityAPISerializerV1Mixin, LegalAPISerializerV1Mixin, DeductableAPISerializerV1Mixin from codelist.settings import CODELIST_CONSERVATION_OFFICE_ID, CODELIST_REGISTRATION_OFFICE_ID, \ CODELIST_PROCESS_TYPE_ID, CODELIST_LAW_ID +from compensation.models import Payment from intervention.models import Intervention, Responsibility, Legal from konova.models import Geometry from konova.tasks import celery_update_parcels from user.models import UserActionLogEntry -class InterventionAPISerializerV1(AbstractModelAPISerializerV1, ResponsibilityAPISerializerV1Mixin, LegalAPISerializerV1Mixin): +class InterventionAPISerializerV1(AbstractModelAPISerializerV1, + ResponsibilityAPISerializerV1Mixin, + LegalAPISerializerV1Mixin, + DeductableAPISerializerV1Mixin): model = Intervention def compensations_to_json(self, qs: QuerySet): @@ -119,6 +123,58 @@ class InterventionAPISerializerV1(AbstractModelAPISerializerV1, ResponsibilityAP obj.responsible.handler = responsibility_data["handler"] return obj + def set_payments(self, obj, payment_data): + """ Sets the linked Payment data according to the given payment_data + + + Args: + obj (Compensation): The Compensation object + payment_data (dict): The posted payment_data + + Returns: + obj (intervention) + """ + payments = [] + for entry in payment_data: + due_on = entry["due_on"] + amount = float(entry["amount"]) + comment = entry["comment"] + + # Check on validity + if amount <= 0: + raise ValueError("Payment amount must be > 0") + + no_due_on = due_on is None or len(due_on) == 0 + no_comment = comment is None or len(comment) == 0 + + if no_due_on and no_comment: + raise ValueError("If no due_on can be provided, you need to explain why using the comment") + + # If this exact data is already existing, we do not create it new. Instead put it's id in the list of + # entries, we will use to set the new actions + pre_existing_payment = obj.payments.filter( + amount=amount, + due_on=due_on, + comment=comment, + ).exclude( + id__in=payments + ).first() + if pre_existing_payment is not None: + payments.append(pre_existing_payment.id) + else: + # Create and add id to list + new_payment = Payment.objects.create( + amount=amount, + due_on=due_on, + comment=comment, + ) + payments.append(new_payment.id) + payments = Payment.objects.filter( + id__in=payments + ) + obj.payments.set(payments) + return obj + def create_model_from_json(self, json_model, user): """ Creates a new entry for the model based on the contents of json_model @@ -163,21 +219,25 @@ class InterventionAPISerializerV1(AbstractModelAPISerializerV1, ResponsibilityAP created_id (str): The id of the newly created Intervention entry """ with transaction.atomic(): + update_action = UserActionLogEntry.get_edited_action(user, "API update") obj = self.get_obj_from_db(id, user) # Fill in data to objects properties = json_model["properties"] obj.title = properties["title"] + obj.modified = update_action self.set_responsibility(obj, properties["responsible"]) self.set_legal(obj, properties["legal"]) + self.set_payments(obj, properties["payments"]) obj.geometry.geom = self.create_geometry_from_json(json_model) + obj.geometry.modified = update_action obj.responsible.save() obj.geometry.save() obj.legal.save() obj.save() - obj.users.add(user) + obj.log.add(update_action) celery_update_parcels.delay(obj.geometry.id) diff --git a/api/utils/serializer/v1/serializer.py b/api/utils/serializer/v1/serializer.py index f60428b7..af3fa96f 100644 --- a/api/utils/serializer/v1/serializer.py +++ b/api/utils/serializer/v1/serializer.py @@ -189,32 +189,36 @@ class AbstractCompensationAPISerializerV1Mixin: Returns: obj (Compensation) """ - found_deadlines = [] + deadlines = [] for entry in deadline_data: deadline_type = entry["type"] date = entry["date"] comment = entry["comment"] + # Check on validity if deadline_type not in DeadlineType: raise ValueError(f"Invalid deadline type. Choices are {DeadlineType.values}") - pre_existing_deadlines = obj.deadlines.filter( + # If this exact data is already existing, we do not create it new. Instead put it's id in the list of + # entries, we will use to set the new actions + pre_existing_deadline = obj.deadlines.filter( type=deadline_type, date=date, comment=comment, ).exclude( - id__in=found_deadlines - ) - if pre_existing_deadlines.count() > 0: - found_deadlines += pre_existing_deadlines.values_list("id", flat=True) + id__in=deadlines + ).first() + if pre_existing_deadline is not None: + deadlines.append(pre_existing_deadline.id) else: - # Create! + # Create and add id to list new_deadline = Deadline.objects.create( type=deadline_type, date=date, comment=comment, ) - obj.deadlines.add(new_deadline) + deadlines.append(new_deadline.id) + obj.deadlines.set(deadlines) return obj def set_compensation_states(self, obj, states_data, states_manager): @@ -229,27 +233,34 @@ class AbstractCompensationAPISerializerV1Mixin: Returns: obj (Compensation) """ - found_states = [] + states = [] for entry in states_data: biotope_type = entry["biotope"] surface = float(entry["surface"]) + + # Check on validity if surface <= 0: raise ValueError("State surfaces must be > 0") - pre_existing_states = states_manager.filter( + + # If this exact data is already existing, we do not create it new. Instead put it's id in the list of + # entries, we will use to set the new actions + pre_existing_state = states_manager.filter( biotope_type__atom_id=biotope_type, surface=surface, ).exclude( - id__in=found_states - ) - if pre_existing_states.count() > 0: - found_states += pre_existing_states.values_list("id", flat=True) + id__in=states + ).first() + if pre_existing_state is not None: + states.append(pre_existing_state.id) else: - # Create! + # Create and add id to list new_state = CompensationState.objects.create( biotope_type=self.konova_code_from_json(biotope_type, CODELIST_BIOTOPES_ID), surface=surface ) - states_manager.add(new_state) + states.append(new_state.id) + + states_manager.set(states) return obj def set_compensation_actions(self, obj, actions_data): @@ -263,36 +274,41 @@ class AbstractCompensationAPISerializerV1Mixin: Returns: obj (Compensation) """ - found_actions = [] + actions = [] for entry in actions_data: action = entry["action"] amount = float(entry["amount"]) unit = entry["unit"] comment = entry["comment"] + # Check on validity if amount <= 0: raise ValueError("Action amount must be > 0") if unit not in UnitChoices: raise ValueError(f"Invalid unit. Choices are {UnitChoices.values}") - pre_existing_actions = obj.actions.filter( + + # If this exact data is already existing, we do not create it new. Instead put it's id in the list of + # entries, we will use to set the new actions + pre_existing_action = obj.actions.filter( action_type__atom_id=action, amount=amount, unit=unit, comment=comment, ).exclude( - id__in=found_actions - ) - if pre_existing_actions.count() > 0: - found_actions += pre_existing_actions.values_list("id", flat=True) + id__in=actions + ).first() + if pre_existing_action is not None: + actions.append(pre_existing_action.id) else: - # Create! + # Create and add id to list new_action = CompensationAction.objects.create( action_type=self.konova_code_from_json(action, CODELIST_COMPENSATION_ACTION_ID), amount=amount, unit=unit, comment=comment, ) - obj.actions.add(new_action) + actions.append(new_action.id) + obj.actions.set(actions) return obj def compensation_state_to_json(self, qs: QuerySet):