Compare commits

..

No commits in common. "286ed609da0d2f55157ad009471619aa72cb32aa" and "0ec1744158dc126da036752e6e436a8d62afec12" have entirely different histories.

15 changed files with 28 additions and 217 deletions

View File

@ -2,7 +2,6 @@ from django.contrib import admin
from compensation.models import Compensation, CompensationAction, CompensationState, Payment, \
EcoAccountDeduction, EcoAccount
from konova.admin import BaseObjectAdmin
class CompensationStateAdmin(admin.ModelAdmin):
@ -23,7 +22,7 @@ class CompensationActionAdmin(admin.ModelAdmin):
]
class CompensationAdmin(BaseObjectAdmin):
class CompensationAdmin(admin.ModelAdmin):
list_display = [
"id",
"identifier",

View File

@ -7,7 +7,6 @@ Created on: 16.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, Sum
@ -17,14 +16,12 @@ from django.utils.translation import gettext_lazy as _
from compensation.managers import CompensationManager
from compensation.models import CompensationState, CompensationAction
from compensation.utils.quality import CompensationQualityChecker
from konova.models import BaseObject, AbstractDocument, Deadline, generate_document_file_upload_path, \
GeoReferencedMixin
from konova.models import BaseObject, AbstractDocument, Geometry, Deadline, generate_document_file_upload_path
from konova.settings import DEFAULT_SRID_RLP, LANIS_LINK_TEMPLATE
from konova.utils.message_templates import DATA_UNSHARED_EXPLANATION
from user.models import UserActionLogEntry
class AbstractCompensation(BaseObject, GeoReferencedMixin):
class AbstractCompensation(BaseObject):
"""
Abstract compensation model which holds basic attributes, shared by subclasses like the regular Compensation,
EMA or EcoAccount.
@ -44,6 +41,8 @@ class AbstractCompensation(BaseObject, GeoReferencedMixin):
deadlines = models.ManyToManyField("konova.Deadline", blank=True, related_name="+")
geometry = models.ForeignKey(Geometry, null=True, blank=True, on_delete=models.SET_NULL)
class Meta:
abstract = True
@ -157,22 +156,6 @@ class AbstractCompensation(BaseObject, GeoReferencedMixin):
checker.run_check()
return checker
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 CEFMixin(models.Model):
""" Provides CEF flag as Mixin

View File

@ -184,7 +184,8 @@ def detail_view(request: HttpRequest, id: str):
sum_after_states = after_states.aggregate(Sum("surface"))["surface__sum"] or 0
diff_states = abs(sum_before_states - sum_after_states)
request = comp.set_status_messages(request)
if not is_data_shared:
messages.info(request, DATA_UNSHARED_EXPLANATION)
context = {
"obj": comp,

View File

@ -202,7 +202,8 @@ def detail_view(request: HttpRequest, id: str):
)
actions = acc.actions.all()
request = acc.set_status_messages(request)
if not is_data_shared:
messages.info(request, DATA_UNSHARED_EXPLANATION)
context = {
"obj": acc,

View File

@ -7,17 +7,15 @@ Created on: 15.11.21
"""
import shutil
from django.contrib import messages
from django.contrib.auth.models import User
from django.db import models
from django.db.models import QuerySet
from django.http import HttpRequest
from compensation.models import AbstractCompensation
from ema.managers import EmaManager
from ema.utils.quality import EmaQualityChecker
from konova.models import AbstractDocument, generate_document_file_upload_path, RecordableObjectMixin, ShareableObjectMixin
from konova.settings import DEFAULT_SRID_RLP, LANIS_LINK_TEMPLATE
from konova.utils.message_templates import DATA_UNSHARED_EXPLANATION
class Ema(AbstractCompensation, ShareableObjectMixin, RecordableObjectMixin):
@ -93,22 +91,6 @@ class Ema(AbstractCompensation, ShareableObjectMixin, RecordableObjectMixin):
)
return docs
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)
self._set_geometry_conflict_message(request)
return request
class EmaDocument(AbstractDocument):
"""

View File

@ -138,7 +138,8 @@ def detail_view(request: HttpRequest, id: str):
sum_after_states = after_states.aggregate(Sum("surface"))["surface__sum"] or 0
diff_states = abs(sum_before_states - sum_after_states)
ema.set_status_messages(request)
if not is_data_shared:
messages.info(request, DATA_UNSHARED_EXPLANATION)
context = {
"obj": ema,

View File

@ -1,10 +1,10 @@
from django.contrib import admin
from intervention.models import Intervention, Responsibility, Legal, Revocation, InterventionDocument
from konova.admin import AbstractDocumentAdmin, BaseObjectAdmin
from konova.admin import AbstractDocumentAdmin
class InterventionAdmin(BaseObjectAdmin):
class InterventionAdmin(admin.ModelAdmin):
list_display = [
"id",
"identifier",
@ -13,11 +13,9 @@ class InterventionAdmin(BaseObjectAdmin):
"deleted",
]
class InterventionDocumentAdmin(AbstractDocumentAdmin):
pass
class ResponsibilityAdmin(admin.ModelAdmin):
list_display = [
"id",

View File

@ -7,7 +7,6 @@ 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
@ -19,15 +18,13 @@ 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.models import generate_document_file_upload_path, AbstractDocument, Geometry, BaseObject, ShareableObjectMixin, \
RecordableObjectMixin, CheckableObjectMixin
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):
class Intervention(BaseObject, ShareableObjectMixin, RecordableObjectMixin, CheckableObjectMixin):
"""
Interventions are e.g. construction sites where nature used to be.
"""
@ -45,6 +42,7 @@ class Intervention(BaseObject, ShareableObjectMixin, RecordableObjectMixin, Chec
blank=True,
help_text="Holds data on legal dates or law"
)
geometry = models.ForeignKey(Geometry, null=True, blank=True, on_delete=models.SET_NULL)
objects = InterventionManager()
@ -265,22 +263,6 @@ class Intervention(BaseObject, ShareableObjectMixin, RecordableObjectMixin, Chec
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):
"""

View File

@ -255,7 +255,8 @@ def detail_view(request: HttpRequest, id: str):
"LANIS_LINK": intervention.get_LANIS_link()
}
request = intervention.set_status_messages(request)
if not is_data_shared:
messages.info(request, DATA_UNSHARED_EXPLANATION)
context = BaseContext(request, context).context
return render(request, template, context)

View File

@ -7,7 +7,7 @@ Created on: 22.07.21
"""
from django.contrib import admin
from konova.models import Geometry, Deadline, GeometryConflict
from konova.models import Geometry, Deadline
class GeometryAdmin(admin.ModelAdmin):
@ -17,14 +17,6 @@ class GeometryAdmin(admin.ModelAdmin):
]
class GeometryConflictAdmin(admin.ModelAdmin):
list_display = [
"conflicting_geometry",
"existing_geometry",
"detected_on",
]
class AbstractDocumentAdmin(admin.ModelAdmin):
list_display = [
"id",
@ -43,14 +35,5 @@ class DeadlineAdmin(admin.ModelAdmin):
]
class BaseObjectAdmin(admin.ModelAdmin):
readonly_fields = [
"modified",
"deleted",
"created",
]
admin.site.register(Geometry, GeometryAdmin)
admin.site.register(GeometryConflict, GeometryConflictAdmin)
admin.site.register(Deadline, DeadlineAdmin)

View File

@ -282,13 +282,11 @@ class SimpleGeomForm(BaseForm):
"""
try:
if self.instance is None or self.instance.geometry is None:
raise LookupError
geometry = self.instance.geometry
geometry.geom = self.cleaned_data.get("geom", MultiPolygon(srid=DEFAULT_SRID))
geometry.modified = action
geometry.save()
except LookupError:
except AttributeError:
# No geometry or linked instance holding a geometry exist --> create a new one!
geometry = Geometry.objects.create(
geom=self.cleaned_data.get("geom", MultiPolygon(srid=DEFAULT_SRID)),

View File

@ -208,7 +208,7 @@ class Command(BaseKonovaCommand):
if num_entries > 0:
self._write_error(f"Found {num_entries} geometries not attached to anything. Delete now...")
unattached_geometries.delete()
self._write_success("Geometries deleted.")
self._write_success("Deadlines deleted.")
else:
self._write_success("No unattached geometries found.")
self._break_line()

View File

@ -6,97 +6,13 @@ Created on: 15.11.21
"""
from django.contrib.gis.db.models import MultiPolygonField
from django.db import models
from django.db.models import Q
from konova.models import BaseResource, UuidModel
from konova.models import BaseResource
class Geometry(BaseResource):
"""
Geometry model
Outsourced geometry model so multiple versions of the same object can refer to the same geometry if it is not changed
"""
from konova.settings import DEFAULT_SRID
geom = MultiPolygonField(null=True, blank=True, srid=DEFAULT_SRID)
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
self.check_for_conflicts()
def check_for_conflicts(self):
""" Checks for geometry overlaps
Creates a new GeometryConflict entry for each overlap to another geometry, which has already been there before
Returns:
"""
# If no geometry is given or important data is missing, we can not perform any checks
if self.geom is None or (self.created is None and self.modified is None):
return None
check_timestamp_obj = self.modified or self.created
ts = check_timestamp_obj.timestamp
overlapping_geoms = Geometry.objects.filter(
Q(modified__timestamp__lte=ts) |
Q(created__timestamp__lte=ts),
geom__intersects=self.geom,
).exclude(
id=self.id
).distinct()
# Drop known conflicts for this object to replace with new ones
self.conflicts_geometries.all().delete()
for match in overlapping_geoms:
GeometryConflict.objects.get_or_create(conflicting_geometry=self, existing_geometry=match)
# Rerun the conflict check for all conflicts where this object is not the cause but the one that already existed.
# It may be possible that this object has been edited, so the conflicts would be resolved in the newer entries.
existing_conflicts = self.conflicted_by_geometries.all()
for conflict in existing_conflicts:
conflicting_geom = conflict.conflicting_geometry
conflicting_geom.check_for_conflicts()
def get_data_objects(self):
""" Getter for all objects which are related to this geometry
Returns:
objs (list): The list of objects
"""
objs = []
sets = [
self.intervention_set,
self.compensation_set,
self.ema_set,
self.ecoaccount_set,
]
for _set in sets:
set_objs = _set.filter(
deleted=None
)
objs += set_objs
return objs
class GeometryConflict(UuidModel):
"""
Geometry conflicts model
If a new/edited geometry overlays an existing geometry, there will be a new GeometryConflict on the db
"""
conflicting_geometry = models.ForeignKey(
Geometry,
on_delete=models.CASCADE,
help_text="The geometry which came second",
related_name="conflicts_geometries"
)
existing_geometry = models.ForeignKey(
Geometry,
on_delete=models.CASCADE,
help_text="The geometry which came first",
related_name="conflicted_by_geometries"
)
detected_on = models.DateTimeField(auto_now_add=True, null=True)
def __str__(self):
return f"{self.conflicting_geometry.id} conflicts with {self.existing_geometry.id}"
geom = MultiPolygonField(null=True, blank=True, srid=DEFAULT_SRID)

View File

@ -7,7 +7,6 @@ Created on: 15.11.21
"""
import uuid
from abc import abstractmethod
from django.contrib import messages
from django.contrib.auth.models import User
@ -21,7 +20,7 @@ from ema.settings import EMA_ACCOUNT_IDENTIFIER_LENGTH, EMA_ACCOUNT_IDENTIFIER_T
from intervention.settings import INTERVENTION_IDENTIFIER_LENGTH, INTERVENTION_IDENTIFIER_TEMPLATE
from konova.utils import generators
from konova.utils.generators import generate_random_string
from konova.utils.message_templates import CHECKED_RECORDED_RESET, GEOMETRY_CONFLICT_WITH_TEMPLATE
from konova.utils.message_templates import CHECKED_RECORDED_RESET
from user.models import UserActionLogEntry, UserAction
@ -95,10 +94,6 @@ class BaseObject(BaseResource):
class Meta:
abstract = True
@abstractmethod
def set_status_messages(self, request: HttpRequest):
raise NotImplementedError
def mark_as_deleted(self, user: User):
""" Mark an entry as deleted
@ -412,29 +407,3 @@ class ShareableObjectMixin(models.Model):
id__in=accessing_users
)
self.share_with_list(users)
class GeoReferencedMixin(models.Model):
geometry = models.ForeignKey("konova.Geometry", null=True, blank=True, on_delete=models.SET_NULL)
class Meta:
abstract = True
def _set_geometry_conflict_message(self, request: HttpRequest):
instance_objs = []
add_message = False
conflicts = self.geometry.conflicts_geometries.all()
for conflict in conflicts:
instance_objs += conflict.existing_geometry.get_data_objects()
add_message = True
conflicts = self.geometry.conflicted_by_geometries.all()
for conflict in conflicts:
instance_objs += conflict.conflicting_geometry.get_data_objects()
add_message = True
if add_message:
instance_identifiers = [x.identifier for x in instance_objs]
instance_identifiers = ", ".join(instance_identifiers)
message_str = GEOMETRY_CONFLICT_WITH_TEMPLATE.format(instance_identifiers)
messages.info(request, message_str)
return request

View File

@ -25,7 +25,4 @@ CANCEL_ACC_RECORDED_OR_DEDUCTED = _("Action canceled. Eco account is recorded or
EDITED_GENERAL_DATA = _("Edited general data")
ADDED_COMPENSATION_STATE = _("Added compensation state")
ADDED_DEADLINE = _("Added deadline")
ADDED_COMPENSATION_ACTION = _("Added compensation action")
# Geometry conflicts
GEOMETRY_CONFLICT_WITH_TEMPLATE = _("Geometry conflict detected with {}")
ADDED_COMPENSATION_ACTION = _("Added compensation action")