Compare commits

...

8 Commits

Author SHA1 Message Date
617d969a10 #31 API PUT/POST EcoAccount
* adds support for PUT and POST of EcoAccount API
2022-01-24 16:56:06 +01:00
89fb867ab2 #31 API PUT/POST Ema
* adds support for PUT and POST of Ema
* moves set_responsibility() and set_legal() from Intervention API Serializer into proper Mixins where they belong to
2022-01-24 16:43:37 +01:00
f461a8e38d #31 API Improvement
* adds support for returning all shared data
* adds documentation
2022-01-24 16:23:38 +01:00
d58ca3f324 #31 API PUT Compensation
* adds support for PUT compensation (Update)
* improves updating of related objects
* adds missing payment PUT support for intervention API
2022-01-24 15:56:02 +01:00
07331078c4 #31 API code cleaning
* splits large AbstractModelAPISerializer into different reusable Mixins to increase reusability of code for similar models
2022-01-24 15:20:23 +01:00
02e72e015f #31 API POST Compensation
* adds documentations
* adds check for valid deadline type
2022-01-24 15:04:20 +01:00
79acf63dbf #31 API POST Compensation
* adds initialize_objects to an abstractmethod of the super class to be implemented in subclasses
* differentiates error messages if intervention does not exist or is just not shared with the user
2022-01-24 14:51:50 +01:00
2fa2876090 #31 API POST Compensation
* adds support for POST of new compensations
* adds shared_users property to BaseObject and Compensation to simplify fetching of shared users (Compensation inherits from intervention)
* extends compensation admin index
* modifies compensation manager which led to invisibility of deleted entries in the admin backend
* fixes bug in sanitize_db.py where CREATED useractions would be removed if they are not found on any log but still are used on the .created attribute of the objects
2022-01-24 14:41:56 +01:00
12 changed files with 788 additions and 121 deletions

View File

@ -52,7 +52,6 @@ class AbstractModelAPISerializer:
""" """
raise NotImplementedError("Must be implemented in subclasses") raise NotImplementedError("Must be implemented in subclasses")
@abstractmethod
def prepare_lookup(self, _id, user): def prepare_lookup(self, _id, user):
""" Updates lookup dict for db fetching """ Updates lookup dict for db fetching
@ -63,7 +62,12 @@ class AbstractModelAPISerializer:
Returns: Returns:
""" """
self.lookup["id"] = _id if _id is None:
# Return all objects
del self.lookup["id"]
else:
# Return certain objects
self.lookup["id"] = _id
self.lookup["users__in"] = [user] self.lookup["users__in"] = [user]
def fetch_and_serialize(self): def fetch_and_serialize(self):
@ -74,8 +78,10 @@ class AbstractModelAPISerializer:
Returns: Returns:
serialized_data (dict) serialized_data (dict)
""" """
entry = self.model.objects.get(**self.lookup) entries = self.model.objects.filter(**self.lookup)
serialized_data = self.model_to_geo_json(entry) serialized_data = {}
for entry in entries:
serialized_data[str(entry.id)] = self.model_to_geo_json(entry)
return serialized_data return serialized_data
@abstractmethod @abstractmethod
@ -134,4 +140,19 @@ class AbstractModelAPISerializer:
return self.model.objects.get( return self.model.objects.get(
id=id, id=id,
users__in=[user] users__in=[user]
) )
@abstractmethod
def initialize_objects(self, json_model, user):
""" Initializes all needed objects from the json_model data
Does not persist data to the DB!
Args:
json_model (dict): The json data
user (User): The API user
Returns:
obj (Intervention)
"""
raise NotImplementedError("Must be implemented in subclasses")

View File

@ -5,15 +5,21 @@ Contact: michel.peltriaux@sgdnord.rlp.de
Created on: 24.01.22 Created on: 24.01.22
""" """
from api.utils.serializer.v1.serializer import AbstractModelAPISerializerV1 from django.db import transaction
from api.utils.serializer.v1.serializer import AbstractModelAPISerializerV1, AbstractCompensationAPISerializerV1Mixin
from compensation.models import Compensation from compensation.models import Compensation
from intervention.models import Intervention
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 model = Compensation
def prepare_lookup(self, id, user): def prepare_lookup(self, id, user):
self.lookup["id"] = id super().prepare_lookup(id, user)
del self.lookup["users__in"] del self.lookup["users__in"]
self.lookup["intervention__users__in"] = [user] self.lookup["intervention__users__in"] = [user]
@ -31,4 +37,149 @@ class CompensationAPISerializerV1(AbstractModelAPISerializerV1):
self.properties_data["before_states"] = self.compensation_state_to_json(entry.before_states.all()) self.properties_data["before_states"] = self.compensation_state_to_json(entry.before_states.all())
self.properties_data["after_states"] = self.compensation_state_to_json(entry.after_states.all()) self.properties_data["after_states"] = self.compensation_state_to_json(entry.after_states.all())
self.properties_data["actions"] = self.compensation_actions_to_json(entry.actions.all()) self.properties_data["actions"] = self.compensation_actions_to_json(entry.actions.all())
self.properties_data["deadlines"] = self.deadlines_to_json(entry.deadlines.all()) self.properties_data["deadlines"] = self.deadlines_to_json(entry.deadlines.all())
def initialize_objects(self, json_model, user):
""" Initializes all needed objects from the json_model data
Does not persist data to the DB!
Args:
json_model (dict): The json data
user (User): The API user
Returns:
obj (Compensation)
"""
create_action = UserActionLogEntry.get_created_action(user, comment="API Import")
# Create geometry
json_geom = self.create_geometry_from_json(json_model)
geometry = Geometry()
geometry.geom = json_geom
geometry.created = create_action
# Create linked objects
obj = Compensation()
created = create_action
obj.created = created
obj.geometry = geometry
return obj
def set_intervention(self, obj, intervention_id, user):
""" Sets the linked compensation according to the given id
Fails if no such intervention found or user has no shared access
Args:
obj (Compensation): The Compensation object
intervention_id (str): The intervention's id
user (User): The API user
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
Args:
json_model (dict): The json containing data
user (User): The API user
Returns:
created_id (str): The id of the newly created Compensation entry
"""
with transaction.atomic():
obj = self.initialize_objects(json_model, user)
# Fill in data to objects
properties = json_model["properties"]
obj.identifier = obj.generate_new_identifier()
obj.title = properties["title"]
obj.is_cef = properties["is_cef"]
obj.is_coherence_keeping = properties["is_coherence_keeping"]
obj = self.set_intervention(obj, properties["intervention"], user)
obj.geometry.save()
obj.save()
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(obj.created)
celery_update_parcels.delay(obj.geometry.id)
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
Args:
id (str): The object's id
json_model (dict): The json containing data
user (User): The API user
Returns:
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"]
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.geometry.save()
obj.save()
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)
return obj.id

View File

@ -5,12 +5,22 @@ Contact: michel.peltriaux@sgdnord.rlp.de
Created on: 24.01.22 Created on: 24.01.22
""" """
from api.utils.serializer.v1.serializer import AbstractModelAPISerializerV1 from django.db import transaction
from api.utils.serializer.v1.serializer import AbstractModelAPISerializerV1, AbstractCompensationAPISerializerV1Mixin, \
LegalAPISerializerV1Mixin, ResponsibilityAPISerializerV1Mixin, DeductableAPISerializerV1Mixin
from compensation.models import EcoAccount from compensation.models import EcoAccount
from intervention.models import Legal, Responsibility from intervention.models import Legal, Responsibility
from konova.models import Geometry
from konova.tasks import celery_update_parcels
from user.models import UserActionLogEntry
class EcoAccountAPISerializerV1(AbstractModelAPISerializerV1): class EcoAccountAPISerializerV1(AbstractModelAPISerializerV1,
AbstractCompensationAPISerializerV1Mixin,
LegalAPISerializerV1Mixin,
ResponsibilityAPISerializerV1Mixin,
DeductableAPISerializerV1Mixin):
model = EcoAccount model = EcoAccount
def extend_properties_data(self, entry): def extend_properties_data(self, entry):
@ -34,4 +44,115 @@ class EcoAccountAPISerializerV1(AbstractModelAPISerializerV1):
"conservation_office": self.konova_code_to_json(responsible.conservation_office), "conservation_office": self.konova_code_to_json(responsible.conservation_office),
"conservation_file_number": responsible.conservation_file_number, "conservation_file_number": responsible.conservation_file_number,
"handler": responsible.handler, "handler": responsible.handler,
} }
def set_legal(self, obj, legal_data):
obj.legal.registration_date = legal_data.get("agreement_date", None)
return obj
def initialize_objects(self, json_model, user):
""" Initializes all needed objects from the json_model data
Does not persist data to the DB!
Args:
json_model (dict): The json data
user (User): The API user
Returns:
obj (Compensation)
"""
create_action = UserActionLogEntry.get_created_action(user, comment="API Import")
# Create geometry
json_geom = self.create_geometry_from_json(json_model)
geometry = Geometry()
geometry.geom = json_geom
geometry.created = create_action
# Create linked objects
obj = EcoAccount()
obj.responsible = Responsibility()
obj.legal = Legal()
created = create_action
obj.created = created
obj.geometry = geometry
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
Args:
json_model (dict): The json containing data
user (User): The API user
Returns:
created_id (str): The id of the newly created EcoAccount entry
"""
with transaction.atomic():
obj = self.initialize_objects(json_model, user)
# Fill in data to objects
properties = json_model["properties"]
obj.identifier = obj.generate_new_identifier()
obj.title = properties["title"]
obj.deductable_surface = float(properties["deductable_surface"])
obj = self.set_responsibility(obj, properties["responsible"])
obj = self.set_legal(obj, properties["legal"])
obj.geometry.save()
obj.responsible.save()
obj.legal.save()
obj.save()
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(obj.created)
obj.users.add(user)
celery_update_parcels.delay(obj.geometry.id)
return obj.id
def update_model_from_json(self, id, json_model, user):
""" Updates an entry for the model based on the contents of json_model
Args:
id (str): The object's id
json_model (dict): The json containing data
user (User): The API user
Returns:
created_id (str): The id of the newly created EcoAccount 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.deductable_surface = float(properties["deductable_surface"])
obj.modified = update_action
obj.geometry.geom = self.create_geometry_from_json(json_model)
obj.geometry.modified = update_action
obj = self.set_responsibility(obj, properties["responsible"])
obj = self.set_legal(obj, properties["legal"])
obj.geometry.save()
obj.responsible.save()
obj.legal.save()
obj.save()
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)
return obj.id

View File

@ -5,12 +5,18 @@ Contact: michel.peltriaux@sgdnord.rlp.de
Created on: 24.01.22 Created on: 24.01.22
""" """
from api.utils.serializer.v1.serializer import AbstractModelAPISerializerV1 from django.db import transaction
from api.utils.serializer.v1.serializer import AbstractModelAPISerializerV1, AbstractCompensationAPISerializerV1Mixin, \
ResponsibilityAPISerializerV1Mixin
from ema.models import Ema from ema.models import Ema
from intervention.models import Responsibility from intervention.models import Responsibility
from konova.models import Geometry
from konova.tasks import celery_update_parcels
from user.models import UserActionLogEntry
class EmaAPISerializerV1(AbstractModelAPISerializerV1): class EmaAPISerializerV1(AbstractModelAPISerializerV1, AbstractCompensationAPISerializerV1Mixin, ResponsibilityAPISerializerV1Mixin):
model = Ema model = Ema
def responsible_to_json(self, responsible: Responsibility): def responsible_to_json(self, responsible: Responsibility):
@ -26,3 +32,103 @@ class EmaAPISerializerV1(AbstractModelAPISerializerV1):
self.properties_data["after_states"] = self.compensation_state_to_json(entry.after_states.all()) self.properties_data["after_states"] = self.compensation_state_to_json(entry.after_states.all())
self.properties_data["actions"] = self.compensation_actions_to_json(entry.actions.all()) self.properties_data["actions"] = self.compensation_actions_to_json(entry.actions.all())
self.properties_data["deadlines"] = self.deadlines_to_json(entry.deadlines.all()) self.properties_data["deadlines"] = self.deadlines_to_json(entry.deadlines.all())
def initialize_objects(self, json_model, user):
""" Initializes all needed objects from the json_model data
Does not persist data to the DB!
Args:
json_model (dict): The json data
user (User): The API user
Returns:
obj (Compensation)
"""
create_action = UserActionLogEntry.get_created_action(user, comment="API Import")
# Create geometry
json_geom = self.create_geometry_from_json(json_model)
geometry = Geometry()
geometry.geom = json_geom
geometry.created = create_action
# Create linked objects
obj = Ema()
obj.responsible = Responsibility()
created = create_action
obj.created = created
obj.geometry = geometry
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
Args:
json_model (dict): The json containing data
user (User): The API user
Returns:
created_id (str): The id of the newly created Ema entry
"""
with transaction.atomic():
obj = self.initialize_objects(json_model, user)
# Fill in data to objects
properties = json_model["properties"]
obj.identifier = obj.generate_new_identifier()
obj.title = properties["title"]
obj = self.set_responsibility(obj, properties["responsible"])
obj.geometry.save()
obj.responsible.save()
obj.save()
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(obj.created)
obj.users.add(user)
celery_update_parcels.delay(obj.geometry.id)
return obj.id
def update_model_from_json(self, id, json_model, user):
""" Updates an entry for the model based on the contents of json_model
Args:
id (str): The object's id
json_model (dict): The json containing data
user (User): The API user
Returns:
created_id (str): The id of the newly created Ema 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
obj.geometry.geom = self.create_geometry_from_json(json_model)
obj.geometry.modified = update_action
obj = self.set_responsibility(obj, properties["responsible"])
obj.geometry.save()
obj.responsible.save()
obj.save()
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)
return obj.id

View File

@ -8,16 +8,19 @@ Created on: 24.01.22
from django.db import transaction from django.db import transaction
from django.db.models import QuerySet from django.db.models import QuerySet
from api.utils.serializer.v1.serializer import AbstractModelAPISerializerV1 from api.utils.serializer.v1.serializer import AbstractModelAPISerializerV1, \
from codelist.settings import CODELIST_CONSERVATION_OFFICE_ID, CODELIST_REGISTRATION_OFFICE_ID, \ ResponsibilityAPISerializerV1Mixin, LegalAPISerializerV1Mixin, DeductableAPISerializerV1Mixin
CODELIST_PROCESS_TYPE_ID, CODELIST_LAW_ID from compensation.models import Payment
from intervention.models import Intervention, Responsibility, Legal from intervention.models import Intervention, Responsibility, Legal
from konova.models import Geometry from konova.models import Geometry
from konova.tasks import celery_update_parcels from konova.tasks import celery_update_parcels
from user.models import UserActionLogEntry from user.models import UserActionLogEntry
class InterventionAPISerializerV1(AbstractModelAPISerializerV1): class InterventionAPISerializerV1(AbstractModelAPISerializerV1,
ResponsibilityAPISerializerV1Mixin,
LegalAPISerializerV1Mixin,
DeductableAPISerializerV1Mixin):
model = Intervention model = Intervention
def compensations_to_json(self, qs: QuerySet): def compensations_to_json(self, qs: QuerySet):
@ -27,6 +30,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): def extend_properties_data(self, entry):
self.properties_data["responsible"] = self.responsible_to_json(entry.responsible) self.properties_data["responsible"] = self.responsible_to_json(entry.responsible)
self.properties_data["legal"] = self.legal_to_json(entry.legal) self.properties_data["legal"] = self.legal_to_json(entry.legal)
@ -46,63 +60,76 @@ class InterventionAPISerializerV1(AbstractModelAPISerializerV1):
Returns: Returns:
obj (Intervention) obj (Intervention)
""" """
create_action = UserActionLogEntry.get_created_action(user, comment="API Import")
# Create geometry # Create geometry
json_geom = self.create_geometry_from_json(json_model) json_geom = self.create_geometry_from_json(json_model)
geometry = Geometry() geometry = Geometry()
geometry.geom = json_geom geometry.geom = json_geom
geometry.created = create_action
# Create linked objects # Create linked objects
obj = Intervention() obj = Intervention()
resp = Responsibility() resp = Responsibility()
legal = Legal() legal = Legal()
created = UserActionLogEntry.get_created_action(user, comment="API Import") created = create_action
obj.legal = legal obj.legal = legal
obj.created = created obj.created = created
obj.geometry = geometry obj.geometry = geometry
obj.responsible = resp obj.responsible = resp
return obj return obj
def set_legal(self, obj, legal_data): def set_payments(self, obj, payment_data):
""" Sets the legal data contents to the provided legal_data dict """ Sets the linked Payment data according to the given payment_data
Args: Args:
obj (Intervention): The intervention object obj (Compensation): The Compensation object
legal_data (dict): The new data payment_data (dict): The posted payment_data
Returns: Returns:
obj obj (intervention)
""" """
obj.legal.registration_date = legal_data["registration_date"] if payment_data is None:
obj.legal.binding_date = legal_data["binding_date"] return obj
obj.legal.process_type = self.konova_code_from_json( payments = []
legal_data["process_type"], for entry in payment_data:
CODELIST_PROCESS_TYPE_ID, due_on = entry["due_on"]
) amount = float(entry["amount"])
laws = [self.konova_code_from_json(law, CODELIST_LAW_ID) for law in legal_data["laws"]] comment = entry["comment"]
obj.legal.laws.set(laws)
return obj
def set_responsibility(self, obj, responsibility_data: dict):
""" Sets the responsible data contents to the provided responsibility_data dict
Args: # Check on validity
obj (Intervention): The intervention object if amount <= 0:
responsibility_data (dict): The new data raise ValueError("Payment amount must be > 0")
Returns: no_due_on = due_on is None or len(due_on) == 0
obj no_comment = comment is None or len(comment) == 0
"""
obj.responsible.registration_office = self.konova_code_from_json( if no_due_on and no_comment:
responsibility_data["registration_office"], raise ValueError("If no due_on can be provided, you need to explain why using the comment")
CODELIST_REGISTRATION_OFFICE_ID
# 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.responsible.registration_file_number = responsibility_data["registration_file_number"] obj.payments.set(payments)
obj.responsible.conservation_office = self.konova_code_from_json(
responsibility_data["conservation_office"],
CODELIST_CONSERVATION_OFFICE_ID,
)
obj.responsible.conservation_file_number = responsibility_data["conservation_file_number"]
obj.responsible.handler = responsibility_data["handler"]
return obj return obj
def create_model_from_json(self, json_model, user): def create_model_from_json(self, json_model, user):
@ -131,6 +158,7 @@ class InterventionAPISerializerV1(AbstractModelAPISerializerV1):
obj.save() obj.save()
obj.users.add(user) obj.users.add(user)
obj.log.add(obj.created)
celery_update_parcels.delay(obj.geometry.id) celery_update_parcels.delay(obj.geometry.id)
@ -148,21 +176,25 @@ class InterventionAPISerializerV1(AbstractModelAPISerializerV1):
created_id (str): The id of the newly created Intervention entry created_id (str): The id of the newly created Intervention entry
""" """
with transaction.atomic(): with transaction.atomic():
update_action = UserActionLogEntry.get_edited_action(user, "API update")
obj = self.get_obj_from_db(id, user) obj = self.get_obj_from_db(id, user)
# Fill in data to objects # Fill in data to objects
properties = json_model["properties"] properties = json_model["properties"]
obj.title = properties["title"] obj.title = properties["title"]
self.set_responsibility(obj, properties["responsible"]) obj.modified = update_action
self.set_legal(obj, properties["legal"]) self.set_responsibility(obj, properties.get("responsible", None))
self.set_legal(obj, properties.get("legal", None))
self.set_payments(obj, properties.get("payments", None))
obj.geometry.geom = self.create_geometry_from_json(json_model) obj.geometry.geom = self.create_geometry_from_json(json_model)
obj.geometry.modified = update_action
obj.responsible.save() obj.responsible.save()
obj.geometry.save() obj.geometry.save()
obj.legal.save() obj.legal.save()
obj.save() obj.save()
obj.users.add(user) obj.log.add(update_action)
celery_update_parcels.delay(obj.geometry.id) celery_update_parcels.delay(obj.geometry.id)

View File

@ -12,7 +12,11 @@ from django.db.models import QuerySet
from api.utils.serializer.serializer import AbstractModelAPISerializer from api.utils.serializer.serializer import AbstractModelAPISerializer
from codelist.models import KonovaCode from codelist.models import KonovaCode
from codelist.settings import CODELIST_COMPENSATION_ACTION_ID, CODELIST_BIOTOPES_ID, CODELIST_PROCESS_TYPE_ID, \
CODELIST_LAW_ID, CODELIST_REGISTRATION_OFFICE_ID, CODELIST_CONSERVATION_OFFICE_ID
from compensation.models import CompensationAction, UnitChoices, CompensationState
from intervention.models import Responsibility, Legal from intervention.models import Responsibility, Legal
from konova.models import Deadline, DeadlineType
class AbstractModelAPISerializerV1(AbstractModelAPISerializer): class AbstractModelAPISerializerV1(AbstractModelAPISerializer):
@ -47,6 +51,8 @@ class AbstractModelAPISerializerV1(AbstractModelAPISerializer):
Returns: Returns:
serialized_json (dict) serialized_json (dict)
""" """
if konova_code is None:
return None
return { return {
"atom_id": konova_code.atom_id, "atom_id": konova_code.atom_id,
"long_name": konova_code.long_name, "long_name": konova_code.long_name,
@ -71,49 +77,34 @@ class AbstractModelAPISerializerV1(AbstractModelAPISerializer):
) )
return code return code
def responsible_to_json(self, responsible: Responsibility): def created_on_to_json(self, entry):
""" Serializes Responsibility model into json """ Serializes the created_on into json
Args: Args:
responsible (Responsibility): The Responsibility entry entry (BaseObject): The entry
Returns: Returns:
serialized_json (dict) created_on (timestamp)
""" """
return { return entry.created.timestamp if entry.created is not None else None
"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,
}
def legal_to_json(self, legal: Legal): def modified_on_to_json(self, entry):
""" Serializes Legal model into json """ Serializes the modified_on into json
Args: Args:
legal (Legal): The Legal entry entry (BaseObject): The entry
Returns: Returns:
serialized_json (dict) modified_on (timestamp)
""" """
return { modified_on = entry.modified or entry.created
"registration_date": legal.registration_date, modified_on = modified_on.timestamp if modified_on is not None else None
"binding_date": legal.binding_date, return modified_on
"process_type": self.konova_code_to_json(legal.process_type),
"laws": [self.konova_code_to_json(law) for law in legal.laws.all()],
}
def payments_to_json(self, qs: QuerySet):
""" Serializes payments into json
Args: class DeductableAPISerializerV1Mixin:
qs (QuerySet): A queryset of Payment entries class Meta:
abstract = True
Returns:
serialized_json (list)
"""
return list(qs.values("amount", "due_on", "comment"))
def deductions_to_json(self, qs: QuerySet): def deductions_to_json(self, qs: QuerySet):
""" Serializes eco account deductions into json """ Serializes eco account deductions into json
@ -142,6 +133,234 @@ class AbstractModelAPISerializerV1(AbstractModelAPISerializer):
for entry in qs 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,
}
def set_responsibility(self, obj, responsibility_data: dict):
""" Sets the responsible data contents to the provided responsibility_data dict
Args:
obj (Intervention): The intervention object
responsibility_data (dict): The new data
Returns:
obj
"""
if responsibility_data is None:
return obj
obj.responsible.registration_office = self.konova_code_from_json(
responsibility_data.get("registration_office", None),
CODELIST_REGISTRATION_OFFICE_ID
)
obj.responsible.registration_file_number = responsibility_data.get("registration_file_number", None)
obj.responsible.conservation_office = self.konova_code_from_json(
responsibility_data.get("conservation_office", None),
CODELIST_CONSERVATION_OFFICE_ID,
)
obj.responsible.conservation_file_number = responsibility_data.get("conservation_file_number", None)
obj.responsible.handler = responsibility_data.get("handler", None)
return obj
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()],
}
def set_legal(self, obj, legal_data):
""" Sets the legal data contents to the provided legal_data dict
Args:
obj (Intervention): The intervention object
legal_data (dict): The new data
Returns:
obj
"""
if legal_data is None:
return obj
obj.legal.registration_date = legal_data.get("registration_date", None)
obj.legal.binding_date = legal_data.get("binding_date", None)
obj.legal.process_type = self.konova_code_from_json(
legal_data.get("process_type", None),
CODELIST_PROCESS_TYPE_ID,
)
laws = [self.konova_code_from_json(law, CODELIST_LAW_ID) for law in legal_data.get("laws", [])]
obj.legal.laws.set(laws)
return obj
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)
"""
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}")
# 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=deadlines
).first()
if pre_existing_deadline is not None:
deadlines.append(pre_existing_deadline.id)
else:
# Create and add id to list
new_deadline = Deadline.objects.create(
type=deadline_type,
date=date,
comment=comment,
)
deadlines.append(new_deadline.id)
obj.deadlines.set(deadlines)
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)
"""
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")
# 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=states
).first()
if pre_existing_state is not None:
states.append(pre_existing_state.id)
else:
# 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.append(new_state.id)
states_manager.set(states)
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)
"""
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}")
# 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=actions
).first()
if pre_existing_action is not None:
actions.append(pre_existing_action.id)
else:
# 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,
)
actions.append(new_action.id)
obj.actions.set(actions)
return obj
def compensation_state_to_json(self, qs: QuerySet): def compensation_state_to_json(self, qs: QuerySet):
""" Serializes compensation states into json """ Serializes compensation states into json
@ -191,28 +410,4 @@ class AbstractModelAPISerializerV1(AbstractModelAPISerializer):
"type", "type",
"date", "date",
"comment", "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

View File

@ -28,21 +28,29 @@ class AbstractModelAPIViewV1(AbstractModelAPIView):
Args: Args:
request (HttpRequest): The incoming request request (HttpRequest): The incoming request
id (str): The entries id id (str): The entries id (optional)
Returns: Returns:
response (JsonResponse)
""" """
try: try:
if id is None:
raise AttributeError("No id provided")
self.serializer.prepare_lookup(id, self.user) self.serializer.prepare_lookup(id, self.user)
data = self.serializer.fetch_and_serialize() data = self.serializer.fetch_and_serialize()
except Exception as e: except Exception as e:
return self.return_error_response(e, 500) return self.return_error_response(e, 500)
return JsonResponse(data) return JsonResponse(data)
def post(self, request: HttpRequest, id=None): def post(self, request: HttpRequest):
""" Handles the POST request
Performs creation of new data
Args:
request (HttpRequest): The incoming request
Returns:
response (JsonResponse)
"""
try: try:
body = request.body.decode("utf-8") body = request.body.decode("utf-8")
body = json.loads(body) body = json.loads(body)
@ -52,6 +60,17 @@ class AbstractModelAPIViewV1(AbstractModelAPIView):
return JsonResponse({"id": created_id}) return JsonResponse({"id": created_id})
def put(self, request: HttpRequest, id=None): def put(self, request: HttpRequest, id=None):
""" Handles the PUT request
Performs updating
Args:
request (HttpRequest): The incoming request
id (str): The entries id
Returns:
"""
try: try:
body = request.body.decode("utf-8") body = request.body.decode("utf-8")
body = json.loads(body) body = json.loads(body)

View File

@ -29,6 +29,7 @@ class CompensationAdmin(BaseObjectAdmin):
"identifier", "identifier",
"title", "title",
"created", "created",
"deleted",
] ]

View File

@ -35,9 +35,7 @@ class CompensationManager(models.Manager):
""" """
def get_queryset(self): def get_queryset(self):
return super().get_queryset().filter( return super().get_queryset().select_related(
deleted__isnull=True,
).select_related(
"modified", "modified",
"intervention", "intervention",
"intervention__recorded", "intervention__recorded",

View File

@ -245,6 +245,15 @@ class Compensation(AbstractCompensation, CEFMixin, CoherenceMixin):
# Compensations inherit their shared state from the interventions # Compensations inherit their shared state from the interventions
return self.intervention.is_shared_with(user) return self.intervention.is_shared_with(user)
@property
def shared_users(self) -> QuerySet:
""" Shortcut for fetching the users which have shared access on this object
Returns:
users (QuerySet)
"""
return self.intervention.users.all()
def get_LANIS_link(self) -> str: def get_LANIS_link(self) -> str:
""" Generates a link for LANIS depending on the geometry """ Generates a link for LANIS depending on the geometry

View File

@ -10,7 +10,7 @@ from ema.models import Ema
from intervention.models import Intervention from intervention.models import Intervention
from konova.management.commands.setup import BaseKonovaCommand from konova.management.commands.setup import BaseKonovaCommand
from konova.models import Deadline, Geometry, Parcel, District from konova.models import Deadline, Geometry, Parcel, District
from user.models import UserActionLogEntry from user.models import UserActionLogEntry, UserAction
class Command(BaseKonovaCommand): class Command(BaseKonovaCommand):
@ -55,7 +55,11 @@ class Command(BaseKonovaCommand):
""" """
self._write_warning("=== Sanitize log entries ===") self._write_warning("=== Sanitize log entries ===")
all_log_entries = UserActionLogEntry.objects.all() # Exclude created log entries from being cleaned, since they can be part of objects which do not have logs
# Being in a log (or not) is essential for this cleanup
all_log_entries = UserActionLogEntry.objects.all().exclude(
action=UserAction.CREATED
)
intervention_log_entries_ids = self.get_all_log_entries_ids(Intervention) intervention_log_entries_ids = self.get_all_log_entries_ids(Intervention)
attached_log_entries_id = intervention_log_entries_ids.union( attached_log_entries_id = intervention_log_entries_ids.union(

View File

@ -10,6 +10,7 @@ import uuid
from abc import abstractmethod from abc import abstractmethod
from django.contrib import messages from django.contrib import messages
from django.db.models import QuerySet
from konova.tasks import celery_send_mail_shared_access_removed, celery_send_mail_shared_access_given, \ from konova.tasks import celery_send_mail_shared_access_removed, celery_send_mail_shared_access_given, \
celery_send_mail_shared_data_recorded, celery_send_mail_shared_data_unrecorded, \ celery_send_mail_shared_data_recorded, celery_send_mail_shared_data_unrecorded, \
@ -124,7 +125,7 @@ class BaseObject(BaseResource):
self.log.add(action) self.log.add(action)
# Send mail # Send mail
shared_users = self.users.all().values_list("id", flat=True) shared_users = self.shared_users.values_list("id", flat=True)
for user_id in shared_users: for user_id in shared_users:
celery_send_mail_shared_data_deleted.delay(self.identifier, user_id) celery_send_mail_shared_data_deleted.delay(self.identifier, user_id)
@ -464,6 +465,15 @@ class ShareableObjectMixin(models.Model):
# Set new shared users # Set new shared users
self.share_with_list(users) self.share_with_list(users)
@property
def shared_users(self) -> QuerySet:
""" Shortcut for fetching the users which have shared access on this object
Returns:
users (QuerySet)
"""
return self.users.all()
class GeoReferencedMixin(models.Model): class GeoReferencedMixin(models.Model):
geometry = models.ForeignKey("konova.Geometry", null=True, blank=True, on_delete=models.SET_NULL) geometry = models.ForeignKey("konova.Geometry", null=True, blank=True, on_delete=models.SET_NULL)