From 07331078c432e098c7afbf35ed3a93427110d59f Mon Sep 17 00:00:00 2001
From: mpeltriaux <michel.peltriaux@sgdnord.rlp.de>
Date: Mon, 24 Jan 2022 15:20:23 +0100
Subject: [PATCH] #31 API code cleaning

* splits large AbstractModelAPISerializer into different reusable Mixins to increase reusability of code for similar models
---
 api/utils/serializer/v1/compensation.py | 125 +------------
 api/utils/serializer/v1/ecoaccount.py   |   9 +-
 api/utils/serializer/v1/ema.py          |   5 +-
 api/utils/serializer/v1/intervention.py |  16 +-
 api/utils/serializer/v1/serializer.py   | 239 ++++++++++++++++++------
 5 files changed, 212 insertions(+), 182 deletions(-)

diff --git a/api/utils/serializer/v1/compensation.py b/api/utils/serializer/v1/compensation.py
index 6fb8486d..05787795 100644
--- a/api/utils/serializer/v1/compensation.py
+++ b/api/utils/serializer/v1/compensation.py
@@ -7,16 +7,15 @@ Created on: 24.01.22
 """
 from django.db import transaction
 
-from api.utils.serializer.v1.serializer import AbstractModelAPISerializerV1
-from codelist.settings import CODELIST_COMPENSATION_ACTION_ID, CODELIST_BIOTOPES_ID
-from compensation.models import Compensation, CompensationAction, CompensationState, UnitChoices
+from api.utils.serializer.v1.serializer import AbstractModelAPISerializerV1, AbstractCompensationAPISerializerV1Mixin
+from compensation.models import Compensation
 from intervention.models import Intervention
-from konova.models import Geometry, Deadline, DeadlineType
+from konova.models import Geometry
 from konova.tasks import celery_update_parcels
 from user.models import UserActionLogEntry
 
 
-class CompensationAPISerializerV1(AbstractModelAPISerializerV1):
+class CompensationAPISerializerV1(AbstractModelAPISerializerV1, AbstractCompensationAPISerializerV1Mixin):
     model = Compensation
 
     def prepare_lookup(self, id, user):
@@ -88,122 +87,6 @@ class CompensationAPISerializerV1(AbstractModelAPISerializerV1):
         obj.intervention = intervention
         return obj
 
-    def set_deadlines(self, obj, deadline_data):
-        """ Sets the linked deadline data according to the given deadline_data
-
-
-        Args:
-            obj (Compensation): The Compensation object
-            deadline_data (dict): The posted deadline_data
-
-        Returns:
-            obj (Compensation)
-        """
-        found_deadlines = []
-        for entry in deadline_data:
-            deadline_type = entry["type"]
-            date = entry["date"]
-            comment = entry["comment"]
-
-            if deadline_type not in DeadlineType:
-                raise ValueError(f"Invalid deadline type. Choices are {DeadlineType.values}")
-
-            pre_existing_deadlines = 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)
-            else:
-                # Create!
-                new_deadline = Deadline.objects.create(
-                    type=deadline_type,
-                    date=date,
-                    comment=comment,
-                )
-                obj.deadlines.add(new_deadline)
-        return obj
-
-    def set_compensation_states(self, obj, states_data, states_manager):
-        """ Sets the linked compensation state data according to the given states_data
-
-
-        Args:
-            obj (Compensation): The Compensation object
-            states_data (dict): The posted states_data
-            states_manager (Manager): The before_states or after_states manager
-
-        Returns:
-            obj (Compensation)
-        """
-        found_states = []
-        for entry in states_data:
-            biotope_type = entry["biotope"]
-            surface = float(entry["surface"])
-            if surface <= 0:
-                raise ValueError("State surfaces must be > 0")
-            pre_existing_states = 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)
-            else:
-                # Create!
-                new_state = CompensationState.objects.create(
-                    biotope_type=self.konova_code_from_json(biotope_type, CODELIST_BIOTOPES_ID),
-                    surface=surface
-                )
-                states_manager.add(new_state)
-        return obj
-
-    def set_compensation_actions(self, obj, actions_data):
-        """ Sets the linked compensation action data according to the given actions_data
-
-
-        Args:
-            obj (Compensation): The Compensation object
-            actions_data (dict): The posted actions_data
-
-        Returns:
-            obj (Compensation)
-        """
-        found_actions = []
-        for entry in actions_data:
-            action = entry["action"]
-            amount = float(entry["amount"])
-            unit = entry["unit"]
-            comment = entry["comment"]
-
-            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(
-                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)
-            else:
-                # Create!
-                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)
-        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
diff --git a/api/utils/serializer/v1/ecoaccount.py b/api/utils/serializer/v1/ecoaccount.py
index 2491eb11..5ddbffd7 100644
--- a/api/utils/serializer/v1/ecoaccount.py
+++ b/api/utils/serializer/v1/ecoaccount.py
@@ -5,12 +5,17 @@ Contact: michel.peltriaux@sgdnord.rlp.de
 Created on: 24.01.22
 
 """
-from api.utils.serializer.v1.serializer import AbstractModelAPISerializerV1
+from api.utils.serializer.v1.serializer import AbstractModelAPISerializerV1, AbstractCompensationAPISerializerV1Mixin, \
+    LegalAPISerializerV1Mixin, ResponsibilityAPISerializerV1Mixin, DeductableAPISerializerV1Mixin
 from compensation.models import EcoAccount
 from intervention.models import Legal, Responsibility
 
 
-class EcoAccountAPISerializerV1(AbstractModelAPISerializerV1):
+class EcoAccountAPISerializerV1(AbstractModelAPISerializerV1,
+                                AbstractCompensationAPISerializerV1Mixin,
+                                LegalAPISerializerV1Mixin,
+                                ResponsibilityAPISerializerV1Mixin,
+                                DeductableAPISerializerV1Mixin):
     model = EcoAccount
 
     def extend_properties_data(self, entry):
diff --git a/api/utils/serializer/v1/ema.py b/api/utils/serializer/v1/ema.py
index e8f2228e..a2e0bc1f 100644
--- a/api/utils/serializer/v1/ema.py
+++ b/api/utils/serializer/v1/ema.py
@@ -5,12 +5,13 @@ Contact: michel.peltriaux@sgdnord.rlp.de
 Created on: 24.01.22
 
 """
-from api.utils.serializer.v1.serializer import AbstractModelAPISerializerV1
+from api.utils.serializer.v1.serializer import AbstractModelAPISerializerV1, AbstractCompensationAPISerializerV1Mixin, \
+    ResponsibilityAPISerializerV1Mixin
 from ema.models import Ema
 from intervention.models import Responsibility
 
 
-class EmaAPISerializerV1(AbstractModelAPISerializerV1):
+class EmaAPISerializerV1(AbstractModelAPISerializerV1, AbstractCompensationAPISerializerV1Mixin, ResponsibilityAPISerializerV1Mixin):
     model = Ema
 
     def responsible_to_json(self, responsible: Responsibility):
diff --git a/api/utils/serializer/v1/intervention.py b/api/utils/serializer/v1/intervention.py
index 82644083..1b91a6cb 100644
--- a/api/utils/serializer/v1/intervention.py
+++ b/api/utils/serializer/v1/intervention.py
@@ -8,7 +8,8 @@ Created on: 24.01.22
 from django.db import transaction
 from django.db.models import QuerySet
 
-from api.utils.serializer.v1.serializer import AbstractModelAPISerializerV1
+from api.utils.serializer.v1.serializer import AbstractModelAPISerializerV1, \
+    ResponsibilityAPISerializerV1Mixin, LegalAPISerializerV1Mixin
 from codelist.settings import CODELIST_CONSERVATION_OFFICE_ID, CODELIST_REGISTRATION_OFFICE_ID, \
     CODELIST_PROCESS_TYPE_ID, CODELIST_LAW_ID
 from intervention.models import Intervention, Responsibility, Legal
@@ -17,7 +18,7 @@ from konova.tasks import celery_update_parcels
 from user.models import UserActionLogEntry
 
 
-class InterventionAPISerializerV1(AbstractModelAPISerializerV1):
+class InterventionAPISerializerV1(AbstractModelAPISerializerV1, ResponsibilityAPISerializerV1Mixin, LegalAPISerializerV1Mixin):
     model = Intervention
 
     def compensations_to_json(self, qs: QuerySet):
@@ -27,6 +28,17 @@ class InterventionAPISerializerV1(AbstractModelAPISerializerV1):
             )
         )
 
+    def payments_to_json(self, qs: QuerySet):
+        """ Serializes payments into json
+
+        Args:
+            qs (QuerySet): A queryset of Payment entries
+
+        Returns:
+            serialized_json (list)
+        """
+        return list(qs.values("amount", "due_on", "comment"))
+
     def extend_properties_data(self, entry):
         self.properties_data["responsible"] = self.responsible_to_json(entry.responsible)
         self.properties_data["legal"] = self.legal_to_json(entry.legal)
diff --git a/api/utils/serializer/v1/serializer.py b/api/utils/serializer/v1/serializer.py
index 63c71827..f60428b7 100644
--- a/api/utils/serializer/v1/serializer.py
+++ b/api/utils/serializer/v1/serializer.py
@@ -12,7 +12,10 @@ from django.db.models import QuerySet
 
 from api.utils.serializer.serializer import AbstractModelAPISerializer
 from codelist.models import KonovaCode
+from codelist.settings import CODELIST_COMPENSATION_ACTION_ID, CODELIST_BIOTOPES_ID
+from compensation.models import CompensationAction, UnitChoices, CompensationState
 from intervention.models import Responsibility, Legal
+from konova.models import Deadline, DeadlineType
 
 
 class AbstractModelAPISerializerV1(AbstractModelAPISerializer):
@@ -71,49 +74,34 @@ class AbstractModelAPISerializerV1(AbstractModelAPISerializer):
         )
         return code
 
-    def responsible_to_json(self, responsible: Responsibility):
-        """ Serializes Responsibility model into json
+    def created_on_to_json(self, entry):
+        """ Serializes the created_on into json
 
         Args:
-            responsible (Responsibility): The Responsibility entry
+            entry (BaseObject): The entry
 
         Returns:
-            serialized_json (dict)
+            created_on (timestamp)
         """
-        return {
-            "registration_office": self.konova_code_to_json(responsible.registration_office),
-            "registration_file_number": responsible.registration_file_number,
-            "conservation_office": self.konova_code_to_json(responsible.conservation_office),
-            "conservation_file_number": responsible.conservation_file_number,
-            "handler": responsible.handler,
-        }
+        return entry.created.timestamp
 
-    def legal_to_json(self, legal: Legal):
-        """ Serializes Legal model into json
+    def modified_on_to_json(self, entry):
+        """ Serializes the modified_on into json
 
         Args:
-            legal (Legal): The Legal entry
+            entry (BaseObject): The entry
 
         Returns:
-            serialized_json (dict)
+            modified_on (timestamp)
         """
-        return {
-            "registration_date": legal.registration_date,
-            "binding_date": legal.binding_date,
-            "process_type": self.konova_code_to_json(legal.process_type),
-            "laws": [self.konova_code_to_json(law) for law in legal.laws.all()],
-        }
+        modified_on = entry.modified or entry.created
+        modified_on = modified_on.timestamp
+        return modified_on
 
-    def payments_to_json(self, qs: QuerySet):
-        """ Serializes payments into json
 
-        Args:
-            qs (QuerySet): A queryset of Payment entries
-
-        Returns:
-            serialized_json (list)
-        """
-        return list(qs.values("amount", "due_on", "comment"))
+class DeductableAPISerializerV1Mixin:
+    class Meta:
+        abstract = True
 
     def deductions_to_json(self, qs: QuerySet):
         """ Serializes eco account deductions into json
@@ -142,6 +130,171 @@ class AbstractModelAPISerializerV1(AbstractModelAPISerializer):
             for entry in qs
         ]
 
+
+class ResponsibilityAPISerializerV1Mixin:
+    class Meta:
+        abstract = True
+
+    def responsible_to_json(self, responsible: Responsibility):
+        """ Serializes Responsibility model into json
+
+        Args:
+            responsible (Responsibility): The Responsibility entry
+
+        Returns:
+            serialized_json (dict)
+        """
+        return {
+            "registration_office": self.konova_code_to_json(responsible.registration_office),
+            "registration_file_number": responsible.registration_file_number,
+            "conservation_office": self.konova_code_to_json(responsible.conservation_office),
+            "conservation_file_number": responsible.conservation_file_number,
+            "handler": responsible.handler,
+        }
+
+
+class LegalAPISerializerV1Mixin:
+    class Meta:
+        abstract = True
+
+    def legal_to_json(self, legal: Legal):
+        """ Serializes Legal model into json
+
+        Args:
+            legal (Legal): The Legal entry
+
+        Returns:
+            serialized_json (dict)
+        """
+        return {
+            "registration_date": legal.registration_date,
+            "binding_date": legal.binding_date,
+            "process_type": self.konova_code_to_json(legal.process_type),
+            "laws": [self.konova_code_to_json(law) for law in legal.laws.all()],
+        }
+
+
+class AbstractCompensationAPISerializerV1Mixin:
+    class Meta:
+        abstract = True
+
+    def set_deadlines(self, obj, deadline_data):
+        """ Sets the linked deadline data according to the given deadline_data
+
+
+        Args:
+            obj (Compensation): The Compensation object
+            deadline_data (dict): The posted deadline_data
+
+        Returns:
+            obj (Compensation)
+        """
+        found_deadlines = []
+        for entry in deadline_data:
+            deadline_type = entry["type"]
+            date = entry["date"]
+            comment = entry["comment"]
+
+            if deadline_type not in DeadlineType:
+                raise ValueError(f"Invalid deadline type. Choices are {DeadlineType.values}")
+
+            pre_existing_deadlines = 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)
+            else:
+                # Create!
+                new_deadline = Deadline.objects.create(
+                    type=deadline_type,
+                    date=date,
+                    comment=comment,
+                )
+                obj.deadlines.add(new_deadline)
+        return obj
+
+    def set_compensation_states(self, obj, states_data, states_manager):
+        """ Sets the linked compensation state data according to the given states_data
+
+
+        Args:
+            obj (Compensation): The Compensation object
+            states_data (dict): The posted states_data
+            states_manager (Manager): The before_states or after_states manager
+
+        Returns:
+            obj (Compensation)
+        """
+        found_states = []
+        for entry in states_data:
+            biotope_type = entry["biotope"]
+            surface = float(entry["surface"])
+            if surface <= 0:
+                raise ValueError("State surfaces must be > 0")
+            pre_existing_states = 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)
+            else:
+                # Create!
+                new_state = CompensationState.objects.create(
+                    biotope_type=self.konova_code_from_json(biotope_type, CODELIST_BIOTOPES_ID),
+                    surface=surface
+                )
+                states_manager.add(new_state)
+        return obj
+
+    def set_compensation_actions(self, obj, actions_data):
+        """ Sets the linked compensation action data according to the given actions_data
+
+
+        Args:
+            obj (Compensation): The Compensation object
+            actions_data (dict): The posted actions_data
+
+        Returns:
+            obj (Compensation)
+        """
+        found_actions = []
+        for entry in actions_data:
+            action = entry["action"]
+            amount = float(entry["amount"])
+            unit = entry["unit"]
+            comment = entry["comment"]
+
+            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(
+                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)
+            else:
+                # Create!
+                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)
+        return obj
+
     def compensation_state_to_json(self, qs: QuerySet):
         """ Serializes compensation states into json
 
@@ -191,28 +344,4 @@ class AbstractModelAPISerializerV1(AbstractModelAPISerializer):
             "type",
             "date",
             "comment",
-        ))
-
-    def created_on_to_json(self, entry):
-        """ Serializes the created_on into json
-
-        Args:
-            entry (BaseObject): The entry
-
-        Returns:
-            created_on (timestamp)
-        """
-        return entry.created.timestamp
-
-    def modified_on_to_json(self, entry):
-        """ Serializes the modified_on into json
-
-        Args:
-            entry (BaseObject): The entry
-
-        Returns:
-            modified_on (timestamp)
-        """
-        modified_on = entry.modified or entry.created
-        modified_on = modified_on.timestamp
-        return modified_on
+        ))
\ No newline at end of file