""" Author: Michel Peltriaux Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany Contact: michel.peltriaux@sgdnord.rlp.de Created on: 15.11.21 """ import shutil from django.contrib import messages from django.contrib.auth.models import User from django.db import models, transaction from django.db.models import QuerySet from django.http import HttpRequest from compensation.models import EcoAccountDeduction from intervention.managers import InterventionManager from intervention.models.legal import Legal from intervention.models.responsibility import Responsibility from intervention.models.revocation import RevocationDocument, Revocation from intervention.utils.quality import InterventionQualityChecker from konova.models import generate_document_file_upload_path, AbstractDocument, BaseObject, \ ShareableObjectMixin, \ RecordableObjectMixin, CheckableObjectMixin, GeoReferencedMixin from konova.settings import LANIS_LINK_TEMPLATE, LANIS_ZOOM_LUT, DEFAULT_SRID_RLP from konova.utils.message_templates import DATA_UNSHARED_EXPLANATION from user.models import UserActionLogEntry class Intervention(BaseObject, ShareableObjectMixin, RecordableObjectMixin, CheckableObjectMixin, GeoReferencedMixin): """ Interventions are e.g. construction sites where nature used to be. """ responsible = models.OneToOneField( Responsibility, on_delete=models.SET_NULL, null=True, blank=True, help_text="Holds data on responsible organizations ('Zulassungsbehörde', 'Eintragungsstelle')" ) legal = models.OneToOneField( Legal, on_delete=models.SET_NULL, null=True, blank=True, help_text="Holds data on legal dates or law" ) objects = InterventionManager() def __str__(self): return "{} ({})".format(self.identifier, self.title) def save(self, *args, **kwargs): """ Custom save functionality Performs some pre-save checks: 1. Checking for existing identifiers Args: *args (): **kwargs (): Returns: """ if self.identifier is None or len(self.identifier) == 0: # No identifier given by the user self.identifier = self.generate_new_identifier() # Before saving, make sure the given identifier is not used in the meanwhile while Intervention.objects.filter(identifier=self.identifier).exclude(id=self.id).exists(): self.identifier = self.generate_new_identifier() super().save(*args, **kwargs) def delete(self, using=None, keep_parents=False): to_delete = [ self.legal, self.responsible, self.geometry, self.log.all() ] for entry in to_delete: try: entry.delete() except AttributeError: pass super().delete(using, keep_parents) def quality_check(self) -> InterventionQualityChecker: """ Quality check Returns: ret_msgs (list): Holds error messages """ checker = InterventionQualityChecker(obj=self) checker.run_check() return checker def get_LANIS_link(self) -> str: """ Generates a link for LANIS depending on the geometry Returns: """ try: geom = self.geometry.geom.transform(DEFAULT_SRID_RLP, clone=True) x = geom.centroid.x y = geom.centroid.y area = int(geom.envelope.area) z_l = 16 for k_area, v_zoom in LANIS_ZOOM_LUT.items(): if k_area < area: z_l = v_zoom break zoom_lvl = z_l except AttributeError: # If no geometry has been added, yet. x = 1 y = 1 zoom_lvl = 6 return LANIS_LINK_TEMPLATE.format( zoom_lvl, x, y, ) def get_documents(self) -> (QuerySet, QuerySet): """ Getter for all documents of an intervention Returns: revoc_docs (QuerySet): The queryset of a revocation document regular_docs (QuerySet): The queryset of regular other documents """ revoc_docs = RevocationDocument.objects.filter( instance__in=self.legal.revocations.all() ) regular_docs = InterventionDocument.objects.filter( instance=self ) return revoc_docs, regular_docs def set_unchecked(self): super().set_unchecked() def set_checked(self, user: User) -> UserActionLogEntry: log_entry = super().set_checked(user) self.add_log_entry_to_compensations(log_entry) return log_entry def set_unrecorded(self, user: User): log_entry = super().set_unrecorded(user) self.add_log_entry_to_compensations(log_entry) def set_recorded(self, user: User) -> UserActionLogEntry: log_entry = super().set_recorded(user) self.add_log_entry_to_compensations(log_entry) return log_entry def add_log_entry_to_compensations(self, log_entry: UserActionLogEntry): """ Adds the log entry to related compensations Args: log_entry (UserActionLogEntry): The log entry Returns: """ comps = self.compensations.all() for comp in comps: comp.log.add(log_entry) def add_payment(self, form): """ Adds a new payment to the intervention Args: form (NewPaymentForm): The form holding the data Returns: """ from compensation.models import Payment form_data = form.cleaned_data user = form.user with transaction.atomic(): created_action = UserActionLogEntry.get_created_action(user) pay = Payment.objects.create( created=created_action, amount=form_data.get("amount", -1), due_on=form_data.get("due", None), comment=form_data.get("comment", None), intervention=self, ) return pay def add_revocation(self, form): """ Adds a new revocation to the intervention Args: form (NewRevocationModalForm): The form holding the data Returns: """ form_data = form.cleaned_data user = form.user with transaction.atomic(): created_action = UserActionLogEntry.get_created_action(user) revocation = Revocation.objects.create( date=form_data["date"], legal=self.legal, comment=form_data["comment"], created=created_action, ) if form_data["file"]: RevocationDocument.objects.create( title="revocation_of_{}".format(self.identifier), date_of_creation=form_data["date"], comment=form_data["comment"], file=form_data["file"], instance=revocation ) return revocation def add_deduction(self, form): """ Adds a new deduction to the intervention Args: form (NewDeductionModalForm): The form holding the data Returns: """ form_data = form.cleaned_data user = form.user with transaction.atomic(): user_action_create = UserActionLogEntry.get_created_action(user) deduction = EcoAccountDeduction.objects.create( intervention=self, account=form_data["account"], surface=form_data["surface"], created=user_action_create, ) return deduction def mark_as_edited(self, performing_user: User, request: HttpRequest = None, edit_comment: str = None): """ In case the object or a related object changed, internal processes need to be started, such as unrecord and uncheck Args: performing_user (User): The user which performed the editing action 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() def set_status_messages(self, request: HttpRequest): """ Setter for different information that need to be rendered Adds messages to the given HttpRequest Args: request (HttpRequest): The incoming request Returns: request (HttpRequest): The modified request """ if not self.is_shared_with(request.user): messages.info(request, DATA_UNSHARED_EXPLANATION) request = self.set_geometry_conflict_message(request) return request class InterventionDocument(AbstractDocument): """ Specializes document upload for an intervention with certain path """ instance = models.ForeignKey( Intervention, on_delete=models.CASCADE, related_name="documents", ) file = models.FileField( upload_to=generate_document_file_upload_path, max_length=1000, ) def delete(self, *args, **kwargs): """ Custom delete functionality for InterventionDocuments. Removes the folder from the file system if there are no further documents for this entry. Args: *args (): **kwargs (): Returns: """ revoc_docs, other_intervention_docs = self.instance.get_documents() folder_path = None if revoc_docs.count() == 0 and other_intervention_docs.count() == 1: # The only file left for this intervention is the one which is currently processed and will be deleted # Make sure that the intervention folder itself is deleted as well, not only the file # Therefore take the folder path from the file path folder_path = self.file.path.split("/")[:-1] folder_path = "/".join(folder_path) # Remove the file itself super().delete(*args, **kwargs) # If a folder path has been set, we need to delete the whole folder! if folder_path is not None: try: shutil.rmtree(folder_path) except FileNotFoundError: # Folder seems to be missing already... pass