Merge pull request '49_Parcel_calculation' (#56) from 49_Parcel_calculation into master

Reviewed-on: SGD-Nord/konova#56
pull/58/head
Michel Peltriaux 3 years ago
commit e618d454e2

@ -64,8 +64,8 @@ class TimespanReport:
responsible__conservation_office__id=id, responsible__conservation_office__id=id,
legal__registration_date__gt=LKOMPVZVO_PUBLISH_DATE, legal__registration_date__gt=LKOMPVZVO_PUBLISH_DATE,
deleted=None, deleted=None,
created__timestamp__gte=date_from, created__timestamp__date__gte=date_from,
created__timestamp__lte=date_to, created__timestamp__date__lte=date_to,
) )
self.queryset_checked = self.queryset.filter( self.queryset_checked = self.queryset.filter(
checked__isnull=False checked__isnull=False
@ -231,8 +231,8 @@ class TimespanReport:
intervention__responsible__conservation_office__id=id, intervention__responsible__conservation_office__id=id,
intervention__legal__registration_date__gt=LKOMPVZVO_PUBLISH_DATE, intervention__legal__registration_date__gt=LKOMPVZVO_PUBLISH_DATE,
deleted=None, deleted=None,
intervention__created__timestamp__gte=date_from, intervention__created__date__timestamp__gte=date_from,
intervention__created__timestamp__lte=date_to, intervention__created__date__timestamp__lte=date_to,
) )
self.queryset_checked = self.queryset.filter( self.queryset_checked = self.queryset.filter(
intervention__checked__isnull=False intervention__checked__isnull=False
@ -400,8 +400,8 @@ class TimespanReport:
self.queryset = EcoAccount.objects.filter( self.queryset = EcoAccount.objects.filter(
responsible__conservation_office__id=id, responsible__conservation_office__id=id,
deleted=None, deleted=None,
created__timestamp__gte=date_from, created__timestamp__date__gte=date_from,
created__timestamp__lte=date_to, created__timestamp__date__lte=date_to,
) )
self.queryset_recorded = self.queryset.filter( self.queryset_recorded = self.queryset.filter(
recorded__isnull=False recorded__isnull=False
@ -479,8 +479,8 @@ class TimespanReport:
legal__registration_date__lte=LKOMPVZVO_PUBLISH_DATE, legal__registration_date__lte=LKOMPVZVO_PUBLISH_DATE,
responsible__conservation_office__id=id, responsible__conservation_office__id=id,
deleted=None, deleted=None,
created__timestamp__gte=date_from, created__timestamp__date__gte=date_from,
created__timestamp__lte=date_to, created__timestamp__date__lte=date_to,
) )
self.queryset_intervention_recorded = self.queryset_intervention.filter( self.queryset_intervention_recorded = self.queryset_intervention.filter(
recorded__isnull=False recorded__isnull=False

@ -170,7 +170,7 @@ class AbstractCompensation(BaseObject, GeoReferencedMixin):
""" """
if not self.is_shared_with(request.user): if not self.is_shared_with(request.user):
messages.info(request, DATA_UNSHARED_EXPLANATION) messages.info(request, DATA_UNSHARED_EXPLANATION)
request = self._set_geometry_conflict_message(request) request = self.set_geometry_conflict_message(request)
return request return request

@ -110,7 +110,10 @@
{% include 'map/geom_form.html' %} {% include 'map/geom_form.html' %}
</div> </div>
<div class="row"> <div class="row">
{% include 'konova/comment_card.html' %} {% include 'konova/includes/parcels.html' %}
</div>
<div class="row">
{% include 'konova/includes/comment_card.html' %}
</div> </div>
</div> </div>
</div> </div>

@ -93,7 +93,10 @@
{% include 'map/geom_form.html' %} {% include 'map/geom_form.html' %}
</div> </div>
<div class="row"> <div class="row">
{% include 'konova/comment_card.html' %} {% include 'konova/includes/parcels.html' %}
</div>
<div class="row">
{% include 'konova/includes/comment_card.html' %}
</div> </div>
</div> </div>
</div> </div>

@ -37,6 +37,9 @@
<div class="row"> <div class="row">
{% include 'map/geom_form.html' %} {% include 'map/geom_form.html' %}
</div> </div>
<div class="row">
{% include 'konova/includes/parcels.html' %}
</div>
<div class="row"> <div class="row">
<div class="col-sm-6 col-md-6 col-lg-6"> <div class="col-sm-6 col-md-6 col-lg-6">
<h4>{% trans 'Open in browser' %}</h4> <h4>{% trans 'Open in browser' %}</h4>

@ -54,6 +54,9 @@
<div class="row"> <div class="row">
{% include 'map/geom_form.html' %} {% include 'map/geom_form.html' %}
</div> </div>
<div class="row">
{% include 'konova/includes/parcels.html' %}
</div>
<div class="row"> <div class="row">
<div class="col-sm-6 col-md-6 col-lg-6"> <div class="col-sm-6 col-md-6 col-lg-6">
<h4>{% trans 'Open in browser' %}</h4> <h4>{% trans 'Open in browser' %}</h4>

@ -170,6 +170,7 @@ def detail_view(request: HttpRequest, id: str):
template = "compensation/detail/compensation/view.html" template = "compensation/detail/compensation/view.html"
comp = get_object_or_404(Compensation, id=id) comp = get_object_or_404(Compensation, id=id)
geom_form = SimpleGeomForm(instance=comp) geom_form = SimpleGeomForm(instance=comp)
parcels = comp.get_underlying_parcels()
_user = request.user _user = request.user
is_data_shared = comp.intervention.is_shared_with(_user) is_data_shared = comp.intervention.is_shared_with(_user)
@ -189,6 +190,7 @@ def detail_view(request: HttpRequest, id: str):
context = { context = {
"obj": comp, "obj": comp,
"geom_form": geom_form, "geom_form": geom_form,
"parcels": parcels,
"has_access": is_data_shared, "has_access": is_data_shared,
"actions": actions, "actions": actions,
"before_states": before_states, "before_states": before_states,
@ -451,6 +453,7 @@ def report_view(request: HttpRequest, id: str):
geom_form = SimpleGeomForm( geom_form = SimpleGeomForm(
instance=comp instance=comp
) )
parcels = comp.get_underlying_parcels()
qrcode_img = generate_qr_code( qrcode_img = generate_qr_code(
request.build_absolute_uri(reverse("compensation:report", args=(id,))), request.build_absolute_uri(reverse("compensation:report", args=(id,))),
10 10
@ -472,6 +475,7 @@ def report_view(request: HttpRequest, id: str):
"before_states": before_states, "before_states": before_states,
"after_states": after_states, "after_states": after_states,
"geom_form": geom_form, "geom_form": geom_form,
"parcels": parcels,
"actions": actions, "actions": actions,
} }
context = BaseContext(request, context).context context = BaseContext(request, context).context

@ -181,6 +181,7 @@ def detail_view(request: HttpRequest, id: str):
id=id id=id
) )
geom_form = SimpleGeomForm(instance=acc) geom_form = SimpleGeomForm(instance=acc)
parcels = acc.get_underlying_parcels()
_user = request.user _user = request.user
is_data_shared = acc.is_shared_with(_user) is_data_shared = acc.is_shared_with(_user)
@ -207,6 +208,7 @@ def detail_view(request: HttpRequest, id: str):
context = { context = {
"obj": acc, "obj": acc,
"geom_form": geom_form, "geom_form": geom_form,
"parcels": parcels,
"has_access": is_data_shared, "has_access": is_data_shared,
"before_states": before_states, "before_states": before_states,
"after_states": after_states, "after_states": after_states,
@ -553,6 +555,7 @@ def report_view(request:HttpRequest, id: str):
geom_form = SimpleGeomForm( geom_form = SimpleGeomForm(
instance=acc instance=acc
) )
parcels = acc.get_underlying_parcels()
qrcode_img = generate_qr_code( qrcode_img = generate_qr_code(
request.build_absolute_uri(reverse("ema:report", args=(id,))), request.build_absolute_uri(reverse("ema:report", args=(id,))),
10 10
@ -580,6 +583,7 @@ def report_view(request:HttpRequest, id: str):
"before_states": before_states, "before_states": before_states,
"after_states": after_states, "after_states": after_states,
"geom_form": geom_form, "geom_form": geom_form,
"parcels": parcels,
"actions": actions, "actions": actions,
"deductions": deductions, "deductions": deductions,
} }

@ -106,7 +106,7 @@ class Ema(AbstractCompensation, ShareableObjectMixin, RecordableObjectMixin):
""" """
if not self.is_shared_with(request.user): if not self.is_shared_with(request.user):
messages.info(request, DATA_UNSHARED_EXPLANATION) messages.info(request, DATA_UNSHARED_EXPLANATION)
self._set_geometry_conflict_message(request) self.set_geometry_conflict_message(request)
return request return request

@ -2,7 +2,13 @@
{% load i18n l10n static fontawesome_5 humanize %} {% load i18n l10n static fontawesome_5 humanize %}
{% block head %} {% block head %}
{% comment %}
dal documentation (django-autocomplete-light) states using form.media for adding needed scripts.
This does not work properly with modal forms, as the scripts are not loaded properly inside the modal.
Therefore the script linkages from form.media have been extracted and put inside dal/scripts.html to ensure
these scripts are loaded when needed.
{% endcomment %}
{% include 'dal/scripts.html' %}
{% endblock %} {% endblock %}
{% block body %} {% block body %}
@ -77,7 +83,15 @@
</div> </div>
</div> </div>
<div class="col-sm-12 col-md-12 col-lg-6"> <div class="col-sm-12 col-md-12 col-lg-6">
<div class="row">
{% include 'map/geom_form.html' %} {% include 'map/geom_form.html' %}
</div>
<div class="row">
{% include 'konova/includes/parcels.html' %}
</div>
<div class="row">
{% include 'konova/includes/comment_card.html' %}
</div>
</div> </div>
</div> </div>
<hr> <hr>

@ -41,6 +41,9 @@
<div class="row"> <div class="row">
{% include 'map/geom_form.html' %} {% include 'map/geom_form.html' %}
</div> </div>
<div class="row">
{% include 'konova/includes/parcels.html' %}
</div>
<div class="row"> <div class="row">
<div class="col-sm-6 col-md-6 col-lg-6"> <div class="col-sm-6 col-md-6 col-lg-6">
<h4>{% trans 'Open in browser' %}</h4> <h4>{% trans 'Open in browser' %}</h4>

@ -125,6 +125,7 @@ def detail_view(request: HttpRequest, id: str):
ema = get_object_or_404(Ema, id=id, deleted=None) ema = get_object_or_404(Ema, id=id, deleted=None)
geom_form = SimpleGeomForm(instance=ema) geom_form = SimpleGeomForm(instance=ema)
parcels = ema.get_underlying_parcels()
_user = request.user _user = request.user
is_data_shared = ema.is_shared_with(_user) is_data_shared = ema.is_shared_with(_user)
@ -143,6 +144,7 @@ def detail_view(request: HttpRequest, id: str):
context = { context = {
"obj": ema, "obj": ema,
"geom_form": geom_form, "geom_form": geom_form,
"parcels": parcels,
"has_access": is_data_shared, "has_access": is_data_shared,
"before_states": before_states, "before_states": before_states,
"after_states": after_states, "after_states": after_states,
@ -464,6 +466,7 @@ def report_view(request:HttpRequest, id: str):
geom_form = SimpleGeomForm( geom_form = SimpleGeomForm(
instance=ema, instance=ema,
) )
parcels = ema.get_underlying_parcels()
qrcode_img = generate_qr_code( qrcode_img = generate_qr_code(
request.build_absolute_uri(reverse("ema:report", args=(id,))), request.build_absolute_uri(reverse("ema:report", args=(id,))),
10 10
@ -485,6 +488,7 @@ def report_view(request:HttpRequest, id: str):
"before_states": before_states, "before_states": before_states,
"after_states": after_states, "after_states": after_states,
"geom_form": geom_form, "geom_form": geom_form,
"parcels": parcels,
"actions": actions, "actions": actions,
} }
context = BaseContext(request, context).context context = BaseContext(request, context).context

@ -278,7 +278,7 @@ class Intervention(BaseObject, ShareableObjectMixin, RecordableObjectMixin, Chec
""" """
if not self.is_shared_with(request.user): if not self.is_shared_with(request.user):
messages.info(request, DATA_UNSHARED_EXPLANATION) messages.info(request, DATA_UNSHARED_EXPLANATION)
request = self._set_geometry_conflict_message(request) request = self.set_geometry_conflict_message(request)
return request return request

@ -127,7 +127,10 @@
{% include 'map/geom_form.html' %} {% include 'map/geom_form.html' %}
</div> </div>
<div class="row"> <div class="row">
{% include 'konova/comment_card.html' %} {% include 'konova/includes/parcels.html' %}
</div>
<div class="row">
{% include 'konova/includes/comment_card.html' %}
</div> </div>
</div> </div>
</div> </div>

@ -100,6 +100,9 @@
<div class="row"> <div class="row">
{% include 'map/geom_form.html' %} {% include 'map/geom_form.html' %}
</div> </div>
<div class="row">
{% include 'konova/includes/parcels.html' %}
</div>
<div class="row"> <div class="row">
<div class="col-sm-6 col-md-6 col-lg-6"> <div class="col-sm-6 col-md-6 col-lg-6">
<h4>{% trans 'Open in browser' %}</h4> <h4>{% trans 'Open in browser' %}</h4>

@ -236,6 +236,8 @@ def detail_view(request: HttpRequest, id: str):
instance=intervention, instance=intervention,
) )
parcels = intervention.get_underlying_parcels()
# Inform user about revocation # Inform user about revocation
if intervention.legal.revocations.exists(): if intervention.legal.revocations.exists():
messages.error( messages.error(
@ -249,6 +251,7 @@ def detail_view(request: HttpRequest, id: str):
"compensations": compensations, "compensations": compensations,
"has_access": is_data_shared, "has_access": is_data_shared,
"geom_form": geom_form, "geom_form": geom_form,
"parcels": parcels,
"is_default_member": in_group(_user, DEFAULT_GROUP), "is_default_member": in_group(_user, DEFAULT_GROUP),
"is_zb_member": in_group(_user, ZB_GROUP), "is_zb_member": in_group(_user, ZB_GROUP),
"is_ets_member": in_group(_user, ETS_GROUP), "is_ets_member": in_group(_user, ETS_GROUP),
@ -544,6 +547,7 @@ def report_view(request:HttpRequest, id: str):
geom_form = SimpleGeomForm( geom_form = SimpleGeomForm(
instance=intervention instance=intervention
) )
parcels = intervention.get_underlying_parcels()
distinct_deductions = intervention.deductions.all().distinct( distinct_deductions = intervention.deductions.all().distinct(
"account" "account"
@ -562,6 +566,7 @@ def report_view(request:HttpRequest, id: str):
"qrcode": qrcode_img, "qrcode": qrcode_img,
"qrcode_lanis": qrcode_img_lanis, "qrcode_lanis": qrcode_img_lanis,
"geom_form": geom_form, "geom_form": geom_form,
"parcels": parcels,
} }
context = BaseContext(request, context).context context = BaseContext(request, context).context
return render(request, template, context) return render(request, template, context)

@ -7,7 +7,7 @@ Created on: 22.07.21
""" """
from django.contrib import admin from django.contrib import admin
from konova.models import Geometry, Deadline, GeometryConflict from konova.models import Geometry, Deadline, GeometryConflict, Parcel, District
class GeometryAdmin(admin.ModelAdmin): class GeometryAdmin(admin.ModelAdmin):
@ -17,6 +17,25 @@ class GeometryAdmin(admin.ModelAdmin):
] ]
class ParcelAdmin(admin.ModelAdmin):
list_display = [
"id",
"gmrkng",
"flr",
"flrstck_nnr",
"flrstck_zhlr",
"updated_on",
]
class DistrictAdmin(admin.ModelAdmin):
list_display = [
"id",
"gmnd",
"krs",
]
class GeometryConflictAdmin(admin.ModelAdmin): class GeometryConflictAdmin(admin.ModelAdmin):
list_display = [ list_display = [
"conflicting_geometry", "conflicting_geometry",
@ -52,5 +71,7 @@ class BaseObjectAdmin(admin.ModelAdmin):
admin.site.register(Geometry, GeometryAdmin) admin.site.register(Geometry, GeometryAdmin)
admin.site.register(Parcel, ParcelAdmin)
admin.site.register(District, DistrictAdmin)
admin.site.register(GeometryConflict, GeometryConflictAdmin) admin.site.register(GeometryConflict, GeometryConflictAdmin)
admin.site.register(Deadline, DeadlineAdmin) admin.site.register(Deadline, DeadlineAdmin)

@ -15,18 +15,17 @@ class BaseContext:
""" """
Holds all base data which is needed for every context rendering Holds all base data which is needed for every context rendering
""" """
context = { context = None
"base_title": BASE_TITLE,
"base_frontend_title": BASE_FRONTEND_TITLE,
"language": LANGUAGE_CODE,
"user": None,
"current_role": None,
"help_link": HELP_LINK,
}
def __init__(self, request: HttpRequest, additional_context: dict = {}): def __init__(self, request: HttpRequest, additional_context: dict = {}):
self.context["language"] = request.LANGUAGE_CODE self.context = {
self.context["user"] = request.user "base_title": BASE_TITLE,
"base_frontend_title": BASE_FRONTEND_TITLE,
"language": request.LANGUAGE_CODE,
"user": request.user,
"current_role": None,
"help_link": HELP_LINK
}
# Add additional context, derived from given parameters # Add additional context, derived from given parameters
self.context.update(additional_context) self.context.update(additional_context)

@ -287,6 +287,7 @@ class SimpleGeomForm(BaseForm):
geometry = self.instance.geometry geometry = self.instance.geometry
geometry.geom = self.cleaned_data.get("geom", MultiPolygon(srid=DEFAULT_SRID)) geometry.geom = self.cleaned_data.get("geom", MultiPolygon(srid=DEFAULT_SRID))
geometry.modified = action geometry.modified = action
geometry.update_parcels()
geometry.save() geometry.save()
except LookupError: except LookupError:
# No geometry or linked instance holding a geometry exist --> create a new one! # No geometry or linked instance holding a geometry exist --> create a new one!

@ -9,7 +9,7 @@ from compensation.models import CompensationState, Compensation, EcoAccount, Com
from ema.models import Ema 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 from konova.models import Deadline, Geometry, Parcel, District
from user.models import UserActionLogEntry from user.models import UserActionLogEntry
@ -23,6 +23,7 @@ class Command(BaseKonovaCommand):
self.sanitize_actions() self.sanitize_actions()
self.sanitize_deadlines() self.sanitize_deadlines()
self.sanitize_geometries() self.sanitize_geometries()
self.sanitize_parcels_and_districts()
except KeyboardInterrupt: except KeyboardInterrupt:
self._break_line() self._break_line()
exit(-1) exit(-1)
@ -266,3 +267,34 @@ class Command(BaseKonovaCommand):
self._write_success("No unused states found.") self._write_success("No unused states found.")
self._break_line() self._break_line()
def sanitize_parcels_and_districts(self):
""" Removes unattached parcels and districts
Returns:
"""
self._write_warning("=== Sanitize parcels and districts ===")
unrelated_parcels = Parcel.objects.filter(
geometries=None,
)
num_unrelated_parcels = unrelated_parcels.count()
if num_unrelated_parcels > 0:
self._write_error(f"Found {num_unrelated_parcels} unrelated parcel entries. Delete now...")
unrelated_parcels.delete()
self._write_success("Unrelated parcels deleted.")
else:
self._write_success("No unrelated parcels found.")
unrelated_districts = District.objects.filter(
parcels=None,
)
num_unrelated_districts = unrelated_districts.count()
if num_unrelated_districts > 0:
self._write_error(f"Found {num_unrelated_districts} unrelated district entries. Delete now...")
unrelated_districts.delete()
self._write_success("Unrelated districts deleted.")
else:
self._write_success("No unrelated districts found.")
self._break_line()

@ -0,0 +1,41 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: michel.peltriaux@sgdnord.rlp.de
Created on: 04.01.22
"""
from konova.management.commands.setup import BaseKonovaCommand
from konova.models import Geometry, Parcel, District
class Command(BaseKonovaCommand):
help = "Checks the database' sanity and removes unused entries"
def handle(self, *args, **options):
try:
self.update_all_parcels()
except KeyboardInterrupt:
self._break_line()
exit(-1)
def update_all_parcels(self):
num_parcels_before = Parcel.objects.count()
num_districts_before = District.objects.count()
self._write_warning("=== Update parcels and districts ===")
geometries = Geometry.objects.all().exclude(
geom=None
)
self._write_warning(f"Process parcels for {geometries.count()} geometry entries now ...")
for geometry in geometries:
geometry.update_parcels()
num_parcels_after = Parcel.objects.count()
num_districts_after = District.objects.count()
if num_parcels_after != num_parcels_before:
self._write_error(f"Parcels have changed: {num_parcels_before} to {num_parcels_after} entries. You should run the sanitize command.")
if num_districts_after != num_districts_before:
self._write_error(f"Districts have changed: {num_districts_before} to {num_districts_after} entries. You should run the sanitize command.")
self._write_success("Updating parcels done!")
self._break_line()

@ -9,3 +9,4 @@ from .object import *
from .deadline import * from .deadline import *
from .document import * from .document import *
from .geometry import * from .geometry import *
from .parcel import *

@ -7,9 +7,10 @@ Created on: 15.11.21
""" """
from django.contrib.gis.db.models import MultiPolygonField from django.contrib.gis.db.models import MultiPolygonField
from django.db import models from django.db import models
from django.db.models import Q from django.utils import timezone
from konova.models import BaseResource, UuidModel from konova.models import BaseResource, UuidModel
from konova.utils.wfs.spatial import ParcelWFSFetcher
class Geometry(BaseResource): class Geometry(BaseResource):
@ -32,7 +33,7 @@ class Geometry(BaseResource):
""" """
# If no geometry is given or important data is missing, we can not perform any checks # 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): if self.geom is None:
return None return None
self.recheck_existing_conflicts() self.recheck_existing_conflicts()
@ -44,7 +45,10 @@ class Geometry(BaseResource):
).distinct() ).distinct()
for match in overlapping_geoms: for match in overlapping_geoms:
GeometryConflict.objects.get_or_create(conflicting_geometry=self, affected_geometry=match) # Make sure this conflict is not already known but in a swapped constellation
conflict_exists_swapped = GeometryConflict.objects.filter(conflicting_geometry=match, affected_geometry=self).exists()
if not conflict_exists_swapped:
GeometryConflict.objects.get_or_create(conflicting_geometry=self, affected_geometry=match)
def recheck_existing_conflicts(self): def recheck_existing_conflicts(self):
""" Rechecks GeometryConflict entries """ Rechecks GeometryConflict entries
@ -69,7 +73,6 @@ class Geometry(BaseResource):
resolved_conflicts = all_conflicted_by_conflicts.exclude(id__in=still_conflicting_conflicts) resolved_conflicts = all_conflicted_by_conflicts.exclude(id__in=still_conflicting_conflicts)
resolved_conflicts.delete() resolved_conflicts.delete()
def get_data_objects(self): def get_data_objects(self):
""" Getter for all objects which are related to this geometry """ Getter for all objects which are related to this geometry
@ -90,6 +93,54 @@ class Geometry(BaseResource):
objs += set_objs objs += set_objs
return objs return objs
def update_parcels(self):
""" Updates underlying parcel information
Returns:
"""
from konova.models import Parcel, District
parcel_fetcher = ParcelWFSFetcher(
geometry_id=self.id,
)
typename = "ave:Flurstueck"
fetched_parcels = parcel_fetcher.get_features(
typename
)
underlying_parcels = []
for result in fetched_parcels:
fetched_parcel = result[typename]
parcel_obj = Parcel.objects.get_or_create(
gmrkng=fetched_parcel["ave:gemarkung"],
flr=fetched_parcel["ave:flur"],
flrstck_nnr=fetched_parcel['ave:flstnrnen'],
flrstck_zhlr=fetched_parcel['ave:flstnrzae'],
)[0]
district = District.objects.get_or_create(
gmnd=fetched_parcel["ave:gemeinde"],
krs=fetched_parcel["ave:kreis"],
)[0]
parcel_obj.district = district
parcel_obj.updated_on = timezone.now()
parcel_obj.save()
underlying_parcels.append(parcel_obj)
self.parcels.set(underlying_parcels)
def get_underlying_parcels(self):
""" Getter for related parcels and their districts
Returns:
parcels (QuerySet): The related parcels as queryset
"""
parcels = self.parcels.all().prefetch_related(
"district"
).order_by(
"gmrkng",
)
return parcels
class GeometryConflict(UuidModel): class GeometryConflict(UuidModel):
""" """

@ -420,13 +420,29 @@ class GeoReferencedMixin(models.Model):
class Meta: class Meta:
abstract = True abstract = True
def _set_geometry_conflict_message(self, request: HttpRequest): def get_underlying_parcels(self):
""" Getter for related parcels
Returns:
parcels (Iterable): An empty list or a Queryset
"""
if self.geometry is not None:
return self.geometry.get_underlying_parcels()
else:
return []
def set_geometry_conflict_message(self, request: HttpRequest):
if self.geometry is None:
return request
instance_objs = [] instance_objs = []
add_message = False add_message = False
conflicts = self.geometry.conflicts_geometries.all() conflicts = self.geometry.conflicts_geometries.all()
for conflict in conflicts: for conflict in conflicts:
instance_objs += conflict.affected_geometry.get_data_objects() instance_objs += conflict.affected_geometry.get_data_objects()
add_message = True add_message = True
conflicts = self.geometry.conflicted_by_geometries.all() conflicts = self.geometry.conflicted_by_geometries.all()
for conflict in conflicts: for conflict in conflicts:
instance_objs += conflict.conflicting_geometry.get_data_objects() instance_objs += conflict.conflicting_geometry.get_data_objects()

@ -0,0 +1,79 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: michel.peltriaux@sgdnord.rlp.de
Created on: 16.12.21
"""
from django.db import models
from konova.models import UuidModel
class Parcel(UuidModel):
""" The Parcel model holds administrative data on the covered properties.
Due to the unique but relevant naming of the administrative data, we have to use these namings as field
names in german. Any try to translate them to English result in strange or insufficient translations.
All fields have to be CharFields as well, since there are e.g. Flurstücksnummer holding e.g. '123____' which
can not be realized using numerical fields.
To avoid conflicts due to german Umlaute, the field names are shortened and vocals are dropped.
"""
geometries = models.ManyToManyField("konova.Geometry", related_name="parcels", blank=True)
district = models.ForeignKey("konova.District", on_delete=models.SET_NULL, null=True, blank=True, related_name="parcels")
gmrkng = models.CharField(
max_length=1000,
help_text="Gemarkung",
null=True,
blank=True,
)
flrstck_nnr = models.CharField(
max_length=1000,
help_text="Flurstücksnenner",
null=True,
blank=True,
)
flrstck_zhlr = models.CharField(
max_length=1000,
help_text="Flurstückszähler",
null=True,
blank=True,
)
flr = models.CharField(
max_length=1000,
help_text="Flur",
null=True,
blank=True,
)
updated_on = models.DateTimeField(auto_now_add=True)
def __str__(self):
return f"{self.gmrkng} | {self.flr} | {self.flrstck_zhlr} | {self.flrstck_nnr}"
class District(UuidModel):
""" The model District holds more coarse information, such as Kreis, Verbandsgemeinde and Gemeinde.
There might be the case that a geometry lies on a hundred Parcel entries but only on one District entry.
Therefore a geometry can have a lot of relations to Parcel entries but only a few or only a single one to one
District.
"""
gmnd = models.CharField(
max_length=1000,
help_text="Gemeinde",
null=True,
blank=True,
)
krs = models.CharField(
max_length=1000,
help_text="Kreis",
null=True,
blank=True,
)
def __str__(self):
return f"{self.gmnd} | {self.krs}"

@ -80,3 +80,8 @@ LANIS_ZOOM_LUT = {
1000: 30, 1000: 30,
500: 31, 500: 31,
} }
# Parcel WFS settings
PARCEL_WFS_BASE_URL = "https://www.geoportal.rlp.de/registry/wfs/519"
PARCEL_WFS_USER = "ksp"
PARCEL_WFS_PW = "CHANGE_ME"

@ -0,0 +1,29 @@
{% load i18n %}
<div>
<h3>{% trans 'Spatial reference' %}</h3>
</div>
<div class="table-container w-100 scroll-300">
<table class="table table-hover">
<thead>
<tr>
<th scope="col">{% trans 'Kreis' %}</th>
<th scope="col">{% trans 'Gemarkung' %}</th>
<th scope="col">{% trans 'Parcel' %}</th>
<th scope="col">{% trans 'Parcel counter' %}</th>
<th scope="col">{% trans 'Parcel number' %}</th>
</tr>
</thead>
<tbody>
{% for parcel in parcels %}
<tr>
<td>{{parcel.district.krs|default_if_none:"-"}}</td>
<td>{{parcel.gmrkng|default_if_none:"-"}}</td>
<td>{{parcel.flr|default_if_none:"-"}}</td>
<td>{{parcel.flrstck_zhlr|default_if_none:"-"}}</td>
<td>{{parcel.flrstck_nnr|default_if_none:"-"}}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>

@ -9,7 +9,7 @@ from django.contrib.gis.db.models.functions import Translate
from konova.models import Geometry, GeometryConflict from konova.models import Geometry, GeometryConflict
from konova.tests.test_views import BaseTestCase from konova.tests.test_views import BaseTestCase
from user.models import UserActionLogEntry from konova.utils.wfs.spatial import ParcelWFSFetcher
class GeometryTestCase(BaseTestCase): class GeometryTestCase(BaseTestCase):
@ -17,16 +17,29 @@ class GeometryTestCase(BaseTestCase):
def setUpTestData(cls): def setUpTestData(cls):
super().setUpTestData() super().setUpTestData()
geom = cls.create_dummy_geometry() geom = cls.create_dummy_geometry()
action = UserActionLogEntry.get_created_action(cls.superuser)
cls.geom_1 = Geometry.objects.create( cls.geom_1 = Geometry.objects.create(
geom=geom, geom=geom,
created=action,
) )
cls.geom_2 = Geometry.objects.create( cls.geom_2 = Geometry.objects.create(
geom=geom, geom=geom,
created=action,
) )
def test_geometry_parcel_caluclation(self):
""" Tests whether newly created geometries already have parcels calculated during save
Returns:
"""
has_parcels = self.geom_1.parcels.all().exists()
self.assertFalse(has_parcels, msg=f"{self.geom_1.id} has parcels but should not!")
self.geom_1.update_parcels()
self.geom_1.refresh_from_db()
parcels = self.geom_1.parcels.all()
has_parcels = parcels.exists()
parcel_districts = parcels.values_list("district", flat=True)
self.assertTrue(has_parcels, msg=f"{self.geom_1.id} has no parcels but should!")
self.assertEqual(parcels.count(), len(parcel_districts), msg=f"Not every parcel has exactly one district!")
def test_geometry_conflict(self): def test_geometry_conflict(self):
""" Tests whether a geometry conflict will be present in case of identical/overlaying geometries and """ Tests whether a geometry conflict will be present in case of identical/overlaying geometries and
if the conflict will be resolved if one geometry is edited. if the conflict will be resolved if one geometry is edited.
@ -34,8 +47,9 @@ class GeometryTestCase(BaseTestCase):
Returns: Returns:
""" """
self.geom_1.check_for_conflicts() conflict = GeometryConflict.objects.all()
conflict = GeometryConflict.objects.all().first() self.assertEqual(1, conflict.count())
conflict = conflict.first()
self.assertEqual(conflict.conflicting_geometry, self.geom_2) self.assertEqual(conflict.conflicting_geometry, self.geom_2)
self.assertEqual(conflict.affected_geometry, self.geom_1) self.assertEqual(conflict.affected_geometry, self.geom_1)
@ -47,3 +61,19 @@ class GeometryTestCase(BaseTestCase):
self.geom_1.check_for_conflicts() self.geom_1.check_for_conflicts()
num_conflict = GeometryConflict.objects.all().count() num_conflict = GeometryConflict.objects.all().count()
self.assertEqual(0, num_conflict) self.assertEqual(0, num_conflict)
def test_wfs_fetch(self):
""" Tests the fetching functionality of ParcelWFSFetcher
+++ Test relies on the availability of the RLP Flurstück WFS +++
Returns:
"""
fetcher = ParcelWFSFetcher(
geometry_id=self.geom_1.id,
)
features = fetcher.get_features(
"ave:Flurstueck",
)
self.assertNotEqual(0, len(features), msg="Spatial wfs get feature did not work!")

@ -214,7 +214,7 @@ class BaseTestCase(TestCase):
Returns: Returns:
""" """
polygon = Polygon.from_bbox((7.157593, 49.882247, 7.816772, 50.266521)) polygon = Polygon.from_bbox((7.592449, 50.359385, 7.593382, 50.359874))
polygon.srid = 4326 polygon.srid = 4326
polygon = polygon.transform(3857, clone=True) polygon = polygon.transform(3857, clone=True)
return MultiPolygon(polygon, srid=3857) # 3857 is the default srid used for MultiPolygonField in the form return MultiPolygon(polygon, srid=3857) # 3857 is the default srid used for MultiPolygonField in the form

@ -0,0 +1,175 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: michel.peltriaux@sgdnord.rlp.de
Created on: 17.12.21
"""
from abc import abstractmethod
import requests
import xmltodict
from django.contrib.gis.db.models.functions import AsGML, Transform
from requests.auth import HTTPDigestAuth
from konova.settings import DEFAULT_SRID_RLP, PARCEL_WFS_USER, PARCEL_WFS_PW
class AbstractWFSFetcher:
""" Base class for fetching WFS data
"""
# base_url represents not the capabilities url but the parameter-free base url
base_url = None
version = None
auth_user = None
auth_pw = None
auth_digest_obj = None
class Meta:
abstract = True
def __init__(self, base_url: str, version: str = "1.1.0", auth_user: str = None, auth_pw: str = None, *args, **kwargs):
self.base_url = base_url
self.version = version
self.auth_pw = auth_pw
self.auth_user = auth_user
self._create_auth_obj()
def _create_auth_obj(self):
if self.auth_pw is not None and self.auth_user is not None:
self.auth_digest_obj = HTTPDigestAuth(
self.auth_user,
self.auth_pw
)
@abstractmethod
def get_features(self, feature_identifier: str, filter_str: str):
raise NotImplementedError
class ParcelWFSFetcher(AbstractWFSFetcher):
""" Fetches features from a special parcel WFS
"""
geometry_id = None
geometry_property_name = None
count = 100
def __init__(self, geometry_id: str, geometry_property_name: str = "msGeometry", *args, **kwargs):
super().__init__(
version="2.0.0",
base_url="https://www.geoportal.rlp.de/registry/wfs/519",
auth_user=PARCEL_WFS_USER,
auth_pw=PARCEL_WFS_PW,
*args,
**kwargs
)
self.geometry_id = geometry_id
self.geometry_property_name = geometry_property_name
def _create_spatial_filter(self,
geometry_operation: str,
filter_srid: str = None):
""" Creates a xml spatial filter according to the WFS filter specification
Args:
geometry_operation (str): One of the WFS supported spatial filter operations (according to capabilities)
filter_srid (str): Used to transform the geometry into the spatial reference system identified by this srid
Returns:
spatial_filter (str): The spatial filter element
"""
from konova.models import Geometry
if filter_srid is None:
filter_srid = DEFAULT_SRID_RLP
geom_gml = Geometry.objects.filter(
id=self.geometry_id
).annotate(
transformed=Transform(srid=filter_srid, expression="geom")
).annotate(
gml=AsGML('transformed')
).first().gml
spatial_filter = f"<Filter><{geometry_operation}><PropertyName>{self.geometry_property_name}</PropertyName>{geom_gml}</{geometry_operation}></Filter>"
return spatial_filter
def _create_post_data(self,
geometry_operation: str,
filter_srid: str = None,
typenames: str = None,
start_index: int = 0,
):
""" Creates a POST body content for fetching features
Args:
geometry_operation (str): One of the WFS supported spatial filter operations (according to capabilities)
filter_srid (str): Used to transform the geometry into the spatial reference system identified by this srid
Returns:
_filter (str): A proper xml WFS filter
"""
start_index = str(start_index)
spatial_filter = self._create_spatial_filter(
geometry_operation,
filter_srid
)
_filter = f'<wfs:GetFeature service="WFS" version="{self.version}" xmlns:wfs="http://www.opengis.net/wfs/2.0" xmlns:fes="http://www.opengis.net/fes/2.0" xmlns:myns="http://www.someserver.com/myns" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.opengis.net/wfs/2.0 http://schemas.opengis.net/wfs/2.0.0/wfs.xsd" count="{self.count}" startindex="{start_index}"><wfs:Query typeNames="{typenames}">{spatial_filter}</wfs:Query></wfs:GetFeature>'
return _filter
def get_features(self,
typenames: str,
spatial_operator: str = "Intersects",
filter_srid: str = None,
start_index: int = 0,
):
""" Fetches features from the WFS using POST
POST is required since GET has a character limit around 4000. Having a larger filter would result in errors,
which do not occur in case of POST.
Args:
typenames (str): References to parameter 'typenames' in a WFS GetFeature request
spatial_operator (str): Defines the spatial operation for filtering
filter_srid (str): Defines the spatial reference system, the geometry shall be transformed into for filtering
start_index (str): References to parameter 'startindex' in a
Returns:
features (list): A list of returned features
"""
features = []
while start_index is not None:
post_body = self._create_post_data(
spatial_operator,
filter_srid,
typenames,
start_index
)
response = requests.post(
url=self.base_url,
data=post_body,
auth=self.auth_digest_obj
)
content = response.content.decode("utf-8")
content = xmltodict.parse(content)
collection = content.get(
"wfs:FeatureCollection",
{},
)
members = collection.get(
"wfs:member",
[],
)
if len(members) > 1:
features += members
else:
features += [members]
if collection.get("@next", None) is not None:
start_index += self.count
else:
start_index = None
return features

Binary file not shown.

@ -11,15 +11,15 @@
#: intervention/forms/forms.py:52 intervention/forms/forms.py:154 #: intervention/forms/forms.py:52 intervention/forms/forms.py:154
#: intervention/forms/forms.py:166 intervention/forms/modalForms.py:125 #: intervention/forms/forms.py:166 intervention/forms/modalForms.py:125
#: intervention/forms/modalForms.py:138 intervention/forms/modalForms.py:151 #: intervention/forms/modalForms.py:138 intervention/forms/modalForms.py:151
#: konova/forms.py:139 konova/forms.py:240 konova/forms.py:308 #: konova/forms.py:139 konova/forms.py:240 konova/forms.py:309
#: konova/forms.py:335 konova/forms.py:345 konova/forms.py:358 #: konova/forms.py:336 konova/forms.py:346 konova/forms.py:359
#: konova/forms.py:370 konova/forms.py:388 user/forms.py:38 #: konova/forms.py:371 konova/forms.py:389 user/forms.py:38
#, fuzzy #, fuzzy
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-12-16 09:17+0100\n" "POT-Creation-Date: 2022-01-05 14:04+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -329,7 +329,7 @@ msgstr "Automatisch generiert"
#: intervention/templates/intervention/detail/includes/documents.html:28 #: intervention/templates/intervention/detail/includes/documents.html:28
#: intervention/templates/intervention/detail/view.html:31 #: intervention/templates/intervention/detail/view.html:31
#: intervention/templates/intervention/report/report.html:12 #: intervention/templates/intervention/report/report.html:12
#: konova/forms.py:334 #: konova/forms.py:335
msgid "Title" msgid "Title"
msgstr "Bezeichnung" msgstr "Bezeichnung"
@ -356,7 +356,7 @@ msgstr "Kompensation XY; Flur ABC"
#: intervention/templates/intervention/detail/includes/documents.html:31 #: intervention/templates/intervention/detail/includes/documents.html:31
#: intervention/templates/intervention/detail/includes/payments.html:34 #: intervention/templates/intervention/detail/includes/payments.html:34
#: intervention/templates/intervention/detail/includes/revocation.html:38 #: intervention/templates/intervention/detail/includes/revocation.html:38
#: konova/forms.py:369 konova/templates/konova/comment_card.html:16 #: konova/forms.py:370 konova/templates/konova/includes/comment_card.html:16
msgid "Comment" msgid "Comment"
msgstr "Kommentar" msgstr "Kommentar"
@ -472,7 +472,7 @@ msgstr "Zahlung wird an diesem Datum erwartet"
#: compensation/forms/modalForms.py:62 compensation/forms/modalForms.py:239 #: compensation/forms/modalForms.py:62 compensation/forms/modalForms.py:239
#: compensation/forms/modalForms.py:317 intervention/forms/modalForms.py:152 #: compensation/forms/modalForms.py:317 intervention/forms/modalForms.py:152
#: konova/forms.py:371 #: konova/forms.py:372
msgid "Additional comment, maximum {} letters" msgid "Additional comment, maximum {} letters"
msgstr "Zusätzlicher Kommentar, maximal {} Zeichen" msgstr "Zusätzlicher Kommentar, maximal {} Zeichen"
@ -793,7 +793,7 @@ msgstr "Dokumente"
#: compensation/templates/compensation/detail/eco_account/includes/documents.html:14 #: compensation/templates/compensation/detail/eco_account/includes/documents.html:14
#: ema/templates/ema/detail/includes/documents.html:14 #: ema/templates/ema/detail/includes/documents.html:14
#: intervention/templates/intervention/detail/includes/documents.html:14 #: intervention/templates/intervention/detail/includes/documents.html:14
#: konova/forms.py:387 #: konova/forms.py:388
msgid "Add new document" msgid "Add new document"
msgstr "Neues Dokument hinzufügen" msgstr "Neues Dokument hinzufügen"
@ -1056,41 +1056,41 @@ msgstr "Kompensation {} hinzugefügt"
msgid "Compensation {} edited" msgid "Compensation {} edited"
msgstr "Kompensation {} bearbeitet" msgstr "Kompensation {} bearbeitet"
#: compensation/views/compensation.py:228 compensation/views/eco_account.py:307 #: compensation/views/compensation.py:230 compensation/views/eco_account.py:309
#: ema/views.py:181 intervention/views.py:474 #: ema/views.py:181 intervention/views.py:477
msgid "Log" msgid "Log"
msgstr "Log" msgstr "Log"
#: compensation/views/compensation.py:251 #: compensation/views/compensation.py:253
msgid "Compensation removed" msgid "Compensation removed"
msgstr "Kompensation entfernt" msgstr "Kompensation entfernt"
#: compensation/views/compensation.py:272 compensation/views/eco_account.py:459 #: compensation/views/compensation.py:274 compensation/views/eco_account.py:461
#: ema/views.py:348 intervention/views.py:129 #: ema/views.py:348 intervention/views.py:129
msgid "Document added" msgid "Document added"
msgstr "Dokument hinzugefügt" msgstr "Dokument hinzugefügt"
#: compensation/views/compensation.py:341 compensation/views/eco_account.py:353 #: compensation/views/compensation.py:343 compensation/views/eco_account.py:355
#: ema/views.py:286 #: ema/views.py:286
msgid "State added" msgid "State added"
msgstr "Zustand hinzugefügt" msgstr "Zustand hinzugefügt"
#: compensation/views/compensation.py:362 compensation/views/eco_account.py:374 #: compensation/views/compensation.py:364 compensation/views/eco_account.py:376
#: ema/views.py:307 #: ema/views.py:307
msgid "Action added" msgid "Action added"
msgstr "Maßnahme hinzugefügt" msgstr "Maßnahme hinzugefügt"
#: compensation/views/compensation.py:383 compensation/views/eco_account.py:439 #: compensation/views/compensation.py:385 compensation/views/eco_account.py:441
#: ema/views.py:328 #: ema/views.py:328
msgid "Deadline added" msgid "Deadline added"
msgstr "Frist/Termin hinzugefügt" msgstr "Frist/Termin hinzugefügt"
#: compensation/views/compensation.py:405 compensation/views/eco_account.py:396 #: compensation/views/compensation.py:407 compensation/views/eco_account.py:398
#: ema/views.py:418 #: ema/views.py:418
msgid "State removed" msgid "State removed"
msgstr "Zustand gelöscht" msgstr "Zustand gelöscht"
#: compensation/views/compensation.py:427 compensation/views/eco_account.py:418 #: compensation/views/compensation.py:429 compensation/views/eco_account.py:420
#: ema/views.py:440 #: ema/views.py:440
msgid "Action removed" msgid "Action removed"
msgstr "Maßnahme entfernt" msgstr "Maßnahme entfernt"
@ -1103,45 +1103,45 @@ msgstr "Ökokonto {} hinzugefügt"
msgid "Eco-Account {} edited" msgid "Eco-Account {} edited"
msgstr "Ökokonto {} bearbeitet" msgstr "Ökokonto {} bearbeitet"
#: compensation/views/eco_account.py:255 #: compensation/views/eco_account.py:257
msgid "Eco-account removed" msgid "Eco-account removed"
msgstr "Ökokonto entfernt" msgstr "Ökokonto entfernt"
#: compensation/views/eco_account.py:283 #: compensation/views/eco_account.py:285
msgid "Deduction removed" msgid "Deduction removed"
msgstr "Abbuchung entfernt" msgstr "Abbuchung entfernt"
#: compensation/views/eco_account.py:328 ema/views.py:261 #: compensation/views/eco_account.py:330 ema/views.py:261
#: intervention/views.py:516 #: intervention/views.py:519
msgid "{} unrecorded" msgid "{} unrecorded"
msgstr "{} entzeichnet" msgstr "{} entzeichnet"
#: compensation/views/eco_account.py:328 ema/views.py:261 #: compensation/views/eco_account.py:330 ema/views.py:261
#: intervention/views.py:516 #: intervention/views.py:519
msgid "{} recorded" msgid "{} recorded"
msgstr "{} verzeichnet" msgstr "{} verzeichnet"
#: compensation/views/eco_account.py:529 intervention/views.py:497 #: compensation/views/eco_account.py:531 intervention/views.py:500
msgid "Deduction added" msgid "Deduction added"
msgstr "Abbuchung hinzugefügt" msgstr "Abbuchung hinzugefügt"
#: compensation/views/eco_account.py:612 ema/views.py:516 #: compensation/views/eco_account.py:614 ema/views.py:516
#: intervention/views.py:372 #: intervention/views.py:375
msgid "{} has already been shared with you" msgid "{} has already been shared with you"
msgstr "{} wurde bereits für Sie freigegeben" msgstr "{} wurde bereits für Sie freigegeben"
#: compensation/views/eco_account.py:617 ema/views.py:521 #: compensation/views/eco_account.py:619 ema/views.py:521
#: intervention/views.py:377 #: intervention/views.py:380
msgid "{} has been shared with you" msgid "{} has been shared with you"
msgstr "{} ist nun für Sie freigegeben" msgstr "{} ist nun für Sie freigegeben"
#: compensation/views/eco_account.py:624 ema/views.py:528 #: compensation/views/eco_account.py:626 ema/views.py:528
#: intervention/views.py:384 #: intervention/views.py:387
msgid "Share link invalid" msgid "Share link invalid"
msgstr "Freigabelink ungültig" msgstr "Freigabelink ungültig"
#: compensation/views/eco_account.py:647 ema/views.py:551 #: compensation/views/eco_account.py:649 ema/views.py:551
#: intervention/views.py:407 #: intervention/views.py:410
msgid "Share settings updated" msgid "Share settings updated"
msgstr "Freigabe Einstellungen aktualisiert" msgstr "Freigabe Einstellungen aktualisiert"
@ -1333,7 +1333,7 @@ msgstr "Kompensationen und Zahlungen geprüft"
msgid "Run check" msgid "Run check"
msgstr "Prüfung vornehmen" msgstr "Prüfung vornehmen"
#: intervention/forms/modalForms.py:196 konova/forms.py:453 #: intervention/forms/modalForms.py:196 konova/forms.py:454
msgid "" msgid ""
"I, {} {}, confirm that all necessary control steps have been performed by " "I, {} {}, confirm that all necessary control steps have been performed by "
"myself." "myself."
@ -1472,31 +1472,31 @@ msgstr ""
msgid "Intervention {} added" msgid "Intervention {} added"
msgstr "Eingriff {} hinzugefügt" msgstr "Eingriff {} hinzugefügt"
#: intervention/views.py:243 #: intervention/views.py:245
msgid "This intervention has {} revocations" msgid "This intervention has {} revocations"
msgstr "Dem Eingriff liegen {} Widersprüche vor" msgstr "Dem Eingriff liegen {} Widersprüche vor"
#: intervention/views.py:290 #: intervention/views.py:293
msgid "Intervention {} edited" msgid "Intervention {} edited"
msgstr "Eingriff {} bearbeitet" msgstr "Eingriff {} bearbeitet"
#: intervention/views.py:325 #: intervention/views.py:328
msgid "{} removed" msgid "{} removed"
msgstr "{} entfernt" msgstr "{} entfernt"
#: intervention/views.py:346 #: intervention/views.py:349
msgid "Revocation removed" msgid "Revocation removed"
msgstr "Widerspruch entfernt" msgstr "Widerspruch entfernt"
#: intervention/views.py:428 #: intervention/views.py:431
msgid "Check performed" msgid "Check performed"
msgstr "Prüfung durchgeführt" msgstr "Prüfung durchgeführt"
#: intervention/views.py:450 #: intervention/views.py:453
msgid "Revocation added" msgid "Revocation added"
msgstr "Widerspruch hinzugefügt" msgstr "Widerspruch hinzugefügt"
#: intervention/views.py:521 #: intervention/views.py:524
msgid "There are errors on this intervention:" msgid "There are errors on this intervention:"
msgstr "Es liegen Fehler in diesem Eingriff vor:" msgstr "Es liegen Fehler in diesem Eingriff vor:"
@ -1525,11 +1525,11 @@ msgstr "Speichern"
msgid "Not editable" msgid "Not editable"
msgstr "Nicht editierbar" msgstr "Nicht editierbar"
#: konova/forms.py:138 konova/forms.py:307 #: konova/forms.py:138 konova/forms.py:308
msgid "Confirm" msgid "Confirm"
msgstr "Bestätige" msgstr "Bestätige"
#: konova/forms.py:150 konova/forms.py:316 #: konova/forms.py:150 konova/forms.py:317
msgid "Remove" msgid "Remove"
msgstr "Löschen" msgstr "Löschen"
@ -1542,56 +1542,56 @@ msgstr "Sie sind dabei {} {} zu löschen"
msgid "Geometry" msgid "Geometry"
msgstr "Geometrie" msgstr "Geometrie"
#: konova/forms.py:317 #: konova/forms.py:318
msgid "Are you sure?" msgid "Are you sure?"
msgstr "Sind Sie sicher?" msgstr "Sind Sie sicher?"
#: konova/forms.py:344 #: konova/forms.py:345
msgid "Created on" msgid "Created on"
msgstr "Erstellt" msgstr "Erstellt"
#: konova/forms.py:346 #: konova/forms.py:347
msgid "When has this file been created? Important for photos." msgid "When has this file been created? Important for photos."
msgstr "Wann wurde diese Datei erstellt oder das Foto aufgenommen?" msgstr "Wann wurde diese Datei erstellt oder das Foto aufgenommen?"
#: konova/forms.py:357 #: konova/forms.py:358
#: venv/lib/python3.7/site-packages/django/db/models/fields/files.py:231 #: venv/lib/python3.7/site-packages/django/db/models/fields/files.py:231
msgid "File" msgid "File"
msgstr "Datei" msgstr "Datei"
#: konova/forms.py:359 #: konova/forms.py:360
msgid "Allowed formats: pdf, jpg, png. Max size 15 MB." msgid "Allowed formats: pdf, jpg, png. Max size 15 MB."
msgstr "Formate: pdf, jpg, png. Maximal 15 MB." msgstr "Formate: pdf, jpg, png. Maximal 15 MB."
#: konova/forms.py:405 #: konova/forms.py:406
msgid "Unsupported file type" msgid "Unsupported file type"
msgstr "Dateiformat nicht unterstützt" msgstr "Dateiformat nicht unterstützt"
#: konova/forms.py:412 #: konova/forms.py:413
msgid "File too large" msgid "File too large"
msgstr "Datei zu groß" msgstr "Datei zu groß"
#: konova/forms.py:421 #: konova/forms.py:422
msgid "Added document" msgid "Added document"
msgstr "Dokument hinzugefügt" msgstr "Dokument hinzugefügt"
#: konova/forms.py:444 #: konova/forms.py:445
msgid "Confirm record" msgid "Confirm record"
msgstr "Verzeichnen bestätigen" msgstr "Verzeichnen bestätigen"
#: konova/forms.py:452 #: konova/forms.py:453
msgid "Record data" msgid "Record data"
msgstr "Daten verzeichnen" msgstr "Daten verzeichnen"
#: konova/forms.py:459 #: konova/forms.py:460
msgid "Confirm unrecord" msgid "Confirm unrecord"
msgstr "Entzeichnen bestätigen" msgstr "Entzeichnen bestätigen"
#: konova/forms.py:460 #: konova/forms.py:461
msgid "Unrecord data" msgid "Unrecord data"
msgstr "Daten entzeichnen" msgstr "Daten entzeichnen"
#: konova/forms.py:461 #: konova/forms.py:462
msgid "I, {} {}, confirm that this data must be unrecorded." msgid "I, {} {}, confirm that this data must be unrecorded."
msgstr "" msgstr ""
"Ich, {} {}, bestätige, dass diese Daten wieder entzeichnet werden müssen." "Ich, {} {}, bestätige, dass diese Daten wieder entzeichnet werden müssen."
@ -1663,6 +1663,30 @@ msgstr "Anzeigen"
msgid "Deduct" msgid "Deduct"
msgstr "Abbuchen" msgstr "Abbuchen"
#: konova/templates/konova/includes/parcels.html:3
msgid "Spatial reference"
msgstr "Raumreferenz"
#: konova/templates/konova/includes/parcels.html:9
msgid "Kreis"
msgstr "Kreis"
#: konova/templates/konova/includes/parcels.html:10
msgid "Gemarkung"
msgstr "Gemarkung"
#: konova/templates/konova/includes/parcels.html:11
msgid "Parcel"
msgstr "Flur"
#: konova/templates/konova/includes/parcels.html:12
msgid "Parcel counter"
msgstr "Flurstückzähler"
#: konova/templates/konova/includes/parcels.html:13
msgid "Parcel number"
msgstr "Flurstücknenner"
#: konova/templates/konova/widgets/generate-content-input.html:6 #: konova/templates/konova/widgets/generate-content-input.html:6
msgid "Generate new" msgid "Generate new"
msgstr "Neu generieren" msgstr "Neu generieren"
@ -1726,8 +1750,8 @@ msgid ""
"Action canceled. Eco account is recorded or deductions exist. Only " "Action canceled. Eco account is recorded or deductions exist. Only "
"conservation office member can perform this action." "conservation office member can perform this action."
msgstr "" msgstr ""
"Aktion abgebrochen. Ökokonto ist bereits verzeichnet oder Abbuchungen liegen vor. Nur " "Aktion abgebrochen. Ökokonto ist bereits verzeichnet oder Abbuchungen liegen "
"Eintragungsstellennutzer können diese Aktion jetzt durchführen." "vor. Nur Eintragungsstellennutzer können diese Aktion jetzt durchführen."
#: konova/utils/message_templates.py:25 #: konova/utils/message_templates.py:25
msgid "Edited general data" msgid "Edited general data"

@ -14,10 +14,14 @@ django-tables2==2.3.4
et-xmlfile==1.1.0 et-xmlfile==1.1.0
idna==2.10 idna==2.10
importlib-metadata==2.1.1 importlib-metadata==2.1.1
itsdangerous<1.0.0 itsdangerous==0.24
openpyxl==3.0.9 openpyxl==3.0.9
OWSLib==0.25.0
psycopg2-binary==2.9.1 psycopg2-binary==2.9.1
pyproj==3.2.1
python-dateutil==2.8.2
pytz==2020.4 pytz==2020.4
PyYAML==6.0
qrcode==7.3.1 qrcode==7.3.1
requests==2.25.0 requests==2.25.0
six==1.15.0 six==1.15.0
@ -25,4 +29,5 @@ soupsieve==2.2.1
sqlparse==0.4.1 sqlparse==0.4.1
urllib3==1.26.2 urllib3==1.26.2
webservices==0.7 webservices==0.7
xmltodict==0.12.0
zipp==3.4.1 zipp==3.4.1

Loading…
Cancel
Save