Merge pull request '49_Parcel_calculation' (#56) from 49_Parcel_calculation into master
Reviewed-on: SGD-Nord/konova#56
This commit is contained in:
		
						commit
						e618d454e2
					
				@ -64,8 +64,8 @@ class TimespanReport:
 | 
			
		||||
                responsible__conservation_office__id=id,
 | 
			
		||||
                legal__registration_date__gt=LKOMPVZVO_PUBLISH_DATE,
 | 
			
		||||
                deleted=None,
 | 
			
		||||
                created__timestamp__gte=date_from,
 | 
			
		||||
                created__timestamp__lte=date_to,
 | 
			
		||||
                created__timestamp__date__gte=date_from,
 | 
			
		||||
                created__timestamp__date__lte=date_to,
 | 
			
		||||
            )
 | 
			
		||||
            self.queryset_checked = self.queryset.filter(
 | 
			
		||||
                checked__isnull=False
 | 
			
		||||
@ -231,8 +231,8 @@ class TimespanReport:
 | 
			
		||||
                intervention__responsible__conservation_office__id=id,
 | 
			
		||||
                intervention__legal__registration_date__gt=LKOMPVZVO_PUBLISH_DATE,
 | 
			
		||||
                deleted=None,
 | 
			
		||||
                intervention__created__timestamp__gte=date_from,
 | 
			
		||||
                intervention__created__timestamp__lte=date_to,
 | 
			
		||||
                intervention__created__date__timestamp__gte=date_from,
 | 
			
		||||
                intervention__created__date__timestamp__lte=date_to,
 | 
			
		||||
            )
 | 
			
		||||
            self.queryset_checked = self.queryset.filter(
 | 
			
		||||
                intervention__checked__isnull=False
 | 
			
		||||
@ -400,8 +400,8 @@ class TimespanReport:
 | 
			
		||||
            self.queryset = EcoAccount.objects.filter(
 | 
			
		||||
                responsible__conservation_office__id=id,
 | 
			
		||||
                deleted=None,
 | 
			
		||||
                created__timestamp__gte=date_from,
 | 
			
		||||
                created__timestamp__lte=date_to,
 | 
			
		||||
                created__timestamp__date__gte=date_from,
 | 
			
		||||
                created__timestamp__date__lte=date_to,
 | 
			
		||||
            )
 | 
			
		||||
            self.queryset_recorded = self.queryset.filter(
 | 
			
		||||
                recorded__isnull=False
 | 
			
		||||
@ -479,8 +479,8 @@ class TimespanReport:
 | 
			
		||||
                legal__registration_date__lte=LKOMPVZVO_PUBLISH_DATE,
 | 
			
		||||
                responsible__conservation_office__id=id,
 | 
			
		||||
                deleted=None,
 | 
			
		||||
                created__timestamp__gte=date_from,
 | 
			
		||||
                created__timestamp__lte=date_to,
 | 
			
		||||
                created__timestamp__date__gte=date_from,
 | 
			
		||||
                created__timestamp__date__lte=date_to,
 | 
			
		||||
            )
 | 
			
		||||
            self.queryset_intervention_recorded = self.queryset_intervention.filter(
 | 
			
		||||
                recorded__isnull=False
 | 
			
		||||
 | 
			
		||||
@ -170,7 +170,7 @@ class AbstractCompensation(BaseObject, GeoReferencedMixin):
 | 
			
		||||
        """
 | 
			
		||||
        if not self.is_shared_with(request.user):
 | 
			
		||||
            messages.info(request, DATA_UNSHARED_EXPLANATION)
 | 
			
		||||
        request = self._set_geometry_conflict_message(request)
 | 
			
		||||
        request = self.set_geometry_conflict_message(request)
 | 
			
		||||
        return request
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -110,7 +110,10 @@
 | 
			
		||||
                {% include 'map/geom_form.html' %}
 | 
			
		||||
            </div>
 | 
			
		||||
            <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>
 | 
			
		||||
 | 
			
		||||
@ -93,7 +93,10 @@
 | 
			
		||||
                {% include 'map/geom_form.html' %}
 | 
			
		||||
            </div>
 | 
			
		||||
            <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>
 | 
			
		||||
 | 
			
		||||
@ -37,6 +37,9 @@
 | 
			
		||||
        <div class="row">
 | 
			
		||||
            {% include 'map/geom_form.html' %}
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="row">
 | 
			
		||||
            {% include 'konova/includes/parcels.html' %}
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="row">
 | 
			
		||||
            <div class="col-sm-6 col-md-6 col-lg-6">
 | 
			
		||||
                <h4>{% trans 'Open in browser' %}</h4>
 | 
			
		||||
 | 
			
		||||
@ -54,6 +54,9 @@
 | 
			
		||||
        <div class="row">
 | 
			
		||||
            {% include 'map/geom_form.html' %}
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="row">
 | 
			
		||||
            {% include 'konova/includes/parcels.html' %}
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="row">
 | 
			
		||||
            <div class="col-sm-6 col-md-6 col-lg-6">
 | 
			
		||||
                <h4>{% trans 'Open in browser' %}</h4>
 | 
			
		||||
 | 
			
		||||
@ -170,6 +170,7 @@ def detail_view(request: HttpRequest, id: str):
 | 
			
		||||
    template = "compensation/detail/compensation/view.html"
 | 
			
		||||
    comp = get_object_or_404(Compensation, id=id)
 | 
			
		||||
    geom_form = SimpleGeomForm(instance=comp)
 | 
			
		||||
    parcels = comp.get_underlying_parcels()
 | 
			
		||||
    _user = request.user
 | 
			
		||||
    is_data_shared = comp.intervention.is_shared_with(_user)
 | 
			
		||||
 | 
			
		||||
@ -189,6 +190,7 @@ def detail_view(request: HttpRequest, id: str):
 | 
			
		||||
    context = {
 | 
			
		||||
        "obj": comp,
 | 
			
		||||
        "geom_form": geom_form,
 | 
			
		||||
        "parcels": parcels,
 | 
			
		||||
        "has_access": is_data_shared,
 | 
			
		||||
        "actions": actions,
 | 
			
		||||
        "before_states": before_states,
 | 
			
		||||
@ -451,6 +453,7 @@ def report_view(request: HttpRequest, id: str):
 | 
			
		||||
    geom_form = SimpleGeomForm(
 | 
			
		||||
        instance=comp
 | 
			
		||||
    )
 | 
			
		||||
    parcels = comp.get_underlying_parcels()
 | 
			
		||||
    qrcode_img = generate_qr_code(
 | 
			
		||||
        request.build_absolute_uri(reverse("compensation:report", args=(id,))),
 | 
			
		||||
        10
 | 
			
		||||
@ -472,6 +475,7 @@ def report_view(request: HttpRequest, id: str):
 | 
			
		||||
        "before_states": before_states,
 | 
			
		||||
        "after_states": after_states,
 | 
			
		||||
        "geom_form": geom_form,
 | 
			
		||||
        "parcels": parcels,
 | 
			
		||||
        "actions": actions,
 | 
			
		||||
    }
 | 
			
		||||
    context = BaseContext(request, context).context
 | 
			
		||||
 | 
			
		||||
@ -181,6 +181,7 @@ def detail_view(request: HttpRequest, id: str):
 | 
			
		||||
        id=id
 | 
			
		||||
    )
 | 
			
		||||
    geom_form = SimpleGeomForm(instance=acc)
 | 
			
		||||
    parcels = acc.get_underlying_parcels()
 | 
			
		||||
    _user = request.user
 | 
			
		||||
    is_data_shared = acc.is_shared_with(_user)
 | 
			
		||||
 | 
			
		||||
@ -207,6 +208,7 @@ def detail_view(request: HttpRequest, id: str):
 | 
			
		||||
    context = {
 | 
			
		||||
        "obj": acc,
 | 
			
		||||
        "geom_form": geom_form,
 | 
			
		||||
        "parcels": parcels,
 | 
			
		||||
        "has_access": is_data_shared,
 | 
			
		||||
        "before_states": before_states,
 | 
			
		||||
        "after_states": after_states,
 | 
			
		||||
@ -553,6 +555,7 @@ def report_view(request:HttpRequest, id: str):
 | 
			
		||||
    geom_form = SimpleGeomForm(
 | 
			
		||||
        instance=acc
 | 
			
		||||
    )
 | 
			
		||||
    parcels = acc.get_underlying_parcels()
 | 
			
		||||
    qrcode_img = generate_qr_code(
 | 
			
		||||
        request.build_absolute_uri(reverse("ema:report", args=(id,))),
 | 
			
		||||
        10
 | 
			
		||||
@ -580,6 +583,7 @@ def report_view(request:HttpRequest, id: str):
 | 
			
		||||
        "before_states": before_states,
 | 
			
		||||
        "after_states": after_states,
 | 
			
		||||
        "geom_form": geom_form,
 | 
			
		||||
        "parcels": parcels,
 | 
			
		||||
        "actions": actions,
 | 
			
		||||
        "deductions": deductions,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -106,7 +106,7 @@ class Ema(AbstractCompensation, ShareableObjectMixin, RecordableObjectMixin):
 | 
			
		||||
        """
 | 
			
		||||
        if not self.is_shared_with(request.user):
 | 
			
		||||
            messages.info(request, DATA_UNSHARED_EXPLANATION)
 | 
			
		||||
        self._set_geometry_conflict_message(request)
 | 
			
		||||
        self.set_geometry_conflict_message(request)
 | 
			
		||||
        return request
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -2,7 +2,13 @@
 | 
			
		||||
{% load i18n l10n static fontawesome_5 humanize %}
 | 
			
		||||
 | 
			
		||||
{% 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 %}
 | 
			
		||||
 | 
			
		||||
{% block body %}
 | 
			
		||||
@ -77,7 +83,15 @@
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="col-sm-12 col-md-12 col-lg-6">
 | 
			
		||||
            <div class="row">
 | 
			
		||||
            {% 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>
 | 
			
		||||
    <hr>
 | 
			
		||||
 | 
			
		||||
@ -41,6 +41,9 @@
 | 
			
		||||
        <div class="row">
 | 
			
		||||
            {% include 'map/geom_form.html' %}
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="row">
 | 
			
		||||
            {% include 'konova/includes/parcels.html' %}
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="row">
 | 
			
		||||
            <div class="col-sm-6 col-md-6 col-lg-6">
 | 
			
		||||
                <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)
 | 
			
		||||
 | 
			
		||||
    geom_form = SimpleGeomForm(instance=ema)
 | 
			
		||||
    parcels = ema.get_underlying_parcels()
 | 
			
		||||
    _user = request.user
 | 
			
		||||
    is_data_shared = ema.is_shared_with(_user)
 | 
			
		||||
 | 
			
		||||
@ -143,6 +144,7 @@ def detail_view(request: HttpRequest, id: str):
 | 
			
		||||
    context = {
 | 
			
		||||
        "obj": ema,
 | 
			
		||||
        "geom_form": geom_form,
 | 
			
		||||
        "parcels": parcels,
 | 
			
		||||
        "has_access": is_data_shared,
 | 
			
		||||
        "before_states": before_states,
 | 
			
		||||
        "after_states": after_states,
 | 
			
		||||
@ -464,6 +466,7 @@ def report_view(request:HttpRequest, id: str):
 | 
			
		||||
    geom_form = SimpleGeomForm(
 | 
			
		||||
        instance=ema,
 | 
			
		||||
    )
 | 
			
		||||
    parcels = ema.get_underlying_parcels()
 | 
			
		||||
    qrcode_img = generate_qr_code(
 | 
			
		||||
        request.build_absolute_uri(reverse("ema:report", args=(id,))),
 | 
			
		||||
        10
 | 
			
		||||
@ -485,6 +488,7 @@ def report_view(request:HttpRequest, id: str):
 | 
			
		||||
        "before_states": before_states,
 | 
			
		||||
        "after_states": after_states,
 | 
			
		||||
        "geom_form": geom_form,
 | 
			
		||||
        "parcels": parcels,
 | 
			
		||||
        "actions": actions,
 | 
			
		||||
    }
 | 
			
		||||
    context = BaseContext(request, context).context
 | 
			
		||||
 | 
			
		||||
@ -278,7 +278,7 @@ class Intervention(BaseObject, ShareableObjectMixin, RecordableObjectMixin, Chec
 | 
			
		||||
        """
 | 
			
		||||
        if not self.is_shared_with(request.user):
 | 
			
		||||
            messages.info(request, DATA_UNSHARED_EXPLANATION)
 | 
			
		||||
        request = self._set_geometry_conflict_message(request)
 | 
			
		||||
        request = self.set_geometry_conflict_message(request)
 | 
			
		||||
        return request
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -127,7 +127,10 @@
 | 
			
		||||
                {% include 'map/geom_form.html' %}
 | 
			
		||||
            </div>
 | 
			
		||||
            <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>
 | 
			
		||||
 | 
			
		||||
@ -100,6 +100,9 @@
 | 
			
		||||
        <div class="row">
 | 
			
		||||
            {% include 'map/geom_form.html' %}
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="row">
 | 
			
		||||
            {% include 'konova/includes/parcels.html' %}
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="row">
 | 
			
		||||
            <div class="col-sm-6 col-md-6 col-lg-6">
 | 
			
		||||
                <h4>{% trans 'Open in browser' %}</h4>
 | 
			
		||||
 | 
			
		||||
@ -236,6 +236,8 @@ def detail_view(request: HttpRequest, id: str):
 | 
			
		||||
        instance=intervention,
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    parcels = intervention.get_underlying_parcels()
 | 
			
		||||
 | 
			
		||||
    # Inform user about revocation
 | 
			
		||||
    if intervention.legal.revocations.exists():
 | 
			
		||||
        messages.error(
 | 
			
		||||
@ -249,6 +251,7 @@ def detail_view(request: HttpRequest, id: str):
 | 
			
		||||
        "compensations": compensations,
 | 
			
		||||
        "has_access": is_data_shared,
 | 
			
		||||
        "geom_form": geom_form,
 | 
			
		||||
        "parcels": parcels,
 | 
			
		||||
        "is_default_member": in_group(_user, DEFAULT_GROUP),
 | 
			
		||||
        "is_zb_member": in_group(_user, ZB_GROUP),
 | 
			
		||||
        "is_ets_member": in_group(_user, ETS_GROUP),
 | 
			
		||||
@ -544,6 +547,7 @@ def report_view(request:HttpRequest, id: str):
 | 
			
		||||
    geom_form = SimpleGeomForm(
 | 
			
		||||
        instance=intervention
 | 
			
		||||
    )
 | 
			
		||||
    parcels = intervention.get_underlying_parcels()
 | 
			
		||||
 | 
			
		||||
    distinct_deductions = intervention.deductions.all().distinct(
 | 
			
		||||
        "account"
 | 
			
		||||
@ -562,6 +566,7 @@ def report_view(request:HttpRequest, id: str):
 | 
			
		||||
        "qrcode": qrcode_img,
 | 
			
		||||
        "qrcode_lanis": qrcode_img_lanis,
 | 
			
		||||
        "geom_form": geom_form,
 | 
			
		||||
        "parcels": parcels,
 | 
			
		||||
    }
 | 
			
		||||
    context = BaseContext(request, context).context
 | 
			
		||||
    return render(request, template, context)
 | 
			
		||||
 | 
			
		||||
@ -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, GeometryConflict, Parcel, District
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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):
 | 
			
		||||
    list_display = [
 | 
			
		||||
        "conflicting_geometry",
 | 
			
		||||
@ -52,5 +71,7 @@ class BaseObjectAdmin(admin.ModelAdmin):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
admin.site.register(Geometry, GeometryAdmin)
 | 
			
		||||
admin.site.register(Parcel, ParcelAdmin)
 | 
			
		||||
admin.site.register(District, DistrictAdmin)
 | 
			
		||||
admin.site.register(GeometryConflict, GeometryConflictAdmin)
 | 
			
		||||
admin.site.register(Deadline, DeadlineAdmin)
 | 
			
		||||
 | 
			
		||||
@ -15,18 +15,17 @@ class BaseContext:
 | 
			
		||||
    """
 | 
			
		||||
    Holds all base data which is needed for every context rendering
 | 
			
		||||
    """
 | 
			
		||||
    context = {
 | 
			
		||||
        "base_title": BASE_TITLE,
 | 
			
		||||
        "base_frontend_title": BASE_FRONTEND_TITLE,
 | 
			
		||||
        "language": LANGUAGE_CODE,
 | 
			
		||||
        "user": None,
 | 
			
		||||
        "current_role": None,
 | 
			
		||||
        "help_link": HELP_LINK,
 | 
			
		||||
    }
 | 
			
		||||
    context = None
 | 
			
		||||
 | 
			
		||||
    def __init__(self, request: HttpRequest, additional_context: dict = {}):
 | 
			
		||||
        self.context["language"] = request.LANGUAGE_CODE
 | 
			
		||||
        self.context["user"] = request.user
 | 
			
		||||
        self.context = {
 | 
			
		||||
            "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
 | 
			
		||||
        self.context.update(additional_context)
 | 
			
		||||
 | 
			
		||||
@ -287,6 +287,7 @@ class SimpleGeomForm(BaseForm):
 | 
			
		||||
            geometry = self.instance.geometry
 | 
			
		||||
            geometry.geom = self.cleaned_data.get("geom", MultiPolygon(srid=DEFAULT_SRID))
 | 
			
		||||
            geometry.modified = action
 | 
			
		||||
            geometry.update_parcels()
 | 
			
		||||
            geometry.save()
 | 
			
		||||
        except LookupError:
 | 
			
		||||
            # 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 intervention.models import Intervention
 | 
			
		||||
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
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -23,6 +23,7 @@ class Command(BaseKonovaCommand):
 | 
			
		||||
            self.sanitize_actions()
 | 
			
		||||
            self.sanitize_deadlines()
 | 
			
		||||
            self.sanitize_geometries()
 | 
			
		||||
            self.sanitize_parcels_and_districts()
 | 
			
		||||
        except KeyboardInterrupt:
 | 
			
		||||
            self._break_line()
 | 
			
		||||
            exit(-1)
 | 
			
		||||
@ -266,3 +267,34 @@ class Command(BaseKonovaCommand):
 | 
			
		||||
            self._write_success("No unused states found.")
 | 
			
		||||
        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()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										41
									
								
								konova/management/commands/update_all_parcels.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								konova/management/commands/update_all_parcels.py
									
									
									
									
									
										Normal file
									
								
							@ -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 .document 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.db import models
 | 
			
		||||
from django.db.models import Q
 | 
			
		||||
from django.utils import timezone
 | 
			
		||||
 | 
			
		||||
from konova.models import BaseResource, UuidModel
 | 
			
		||||
from konova.utils.wfs.spatial import ParcelWFSFetcher
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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 self.geom is None or (self.created is None and self.modified is None):
 | 
			
		||||
        if self.geom is None:
 | 
			
		||||
            return None
 | 
			
		||||
 | 
			
		||||
        self.recheck_existing_conflicts()
 | 
			
		||||
@ -44,7 +45,10 @@ class Geometry(BaseResource):
 | 
			
		||||
        ).distinct()
 | 
			
		||||
 | 
			
		||||
        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):
 | 
			
		||||
        """ Rechecks GeometryConflict entries
 | 
			
		||||
@ -69,7 +73,6 @@ class Geometry(BaseResource):
 | 
			
		||||
        resolved_conflicts = all_conflicted_by_conflicts.exclude(id__in=still_conflicting_conflicts)
 | 
			
		||||
        resolved_conflicts.delete()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def get_data_objects(self):
 | 
			
		||||
        """ Getter for all objects which are related to this geometry
 | 
			
		||||
 | 
			
		||||
@ -90,6 +93,54 @@ class Geometry(BaseResource):
 | 
			
		||||
            objs += set_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):
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
@ -420,13 +420,29 @@ class GeoReferencedMixin(models.Model):
 | 
			
		||||
    class Meta:
 | 
			
		||||
        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 = []
 | 
			
		||||
        add_message = False
 | 
			
		||||
        conflicts = self.geometry.conflicts_geometries.all()
 | 
			
		||||
 | 
			
		||||
        for conflict in conflicts:
 | 
			
		||||
            instance_objs += conflict.affected_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()
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										79
									
								
								konova/models/parcel.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								konova/models/parcel.py
									
									
									
									
									
										Normal file
									
								
							@ -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,
 | 
			
		||||
    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"
 | 
			
		||||
							
								
								
									
										29
									
								
								konova/templates/konova/includes/parcels.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								konova/templates/konova/includes/parcels.html
									
									
									
									
									
										Normal file
									
								
							@ -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.tests.test_views import BaseTestCase
 | 
			
		||||
from user.models import UserActionLogEntry
 | 
			
		||||
from konova.utils.wfs.spatial import ParcelWFSFetcher
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class GeometryTestCase(BaseTestCase):
 | 
			
		||||
@ -17,16 +17,29 @@ class GeometryTestCase(BaseTestCase):
 | 
			
		||||
    def setUpTestData(cls):
 | 
			
		||||
        super().setUpTestData()
 | 
			
		||||
        geom = cls.create_dummy_geometry()
 | 
			
		||||
        action = UserActionLogEntry.get_created_action(cls.superuser)
 | 
			
		||||
        cls.geom_1 = Geometry.objects.create(
 | 
			
		||||
            geom=geom,
 | 
			
		||||
            created=action,
 | 
			
		||||
        )
 | 
			
		||||
        cls.geom_2 = Geometry.objects.create(
 | 
			
		||||
            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):
 | 
			
		||||
        """ 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.
 | 
			
		||||
@ -34,8 +47,9 @@ class GeometryTestCase(BaseTestCase):
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        self.geom_1.check_for_conflicts()
 | 
			
		||||
        conflict = GeometryConflict.objects.all().first()
 | 
			
		||||
        conflict = GeometryConflict.objects.all()
 | 
			
		||||
        self.assertEqual(1, conflict.count())
 | 
			
		||||
        conflict = conflict.first()
 | 
			
		||||
        self.assertEqual(conflict.conflicting_geometry, self.geom_2)
 | 
			
		||||
        self.assertEqual(conflict.affected_geometry, self.geom_1)
 | 
			
		||||
 | 
			
		||||
@ -47,3 +61,19 @@ class GeometryTestCase(BaseTestCase):
 | 
			
		||||
        self.geom_1.check_for_conflicts()
 | 
			
		||||
        num_conflict = GeometryConflict.objects.all().count()
 | 
			
		||||
        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:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        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 = polygon.transform(3857, clone=True)
 | 
			
		||||
        return MultiPolygon(polygon, srid=3857)  # 3857 is the default srid used for MultiPolygonField in the form
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										175
									
								
								konova/utils/wfs/spatial.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										175
									
								
								konova/utils/wfs/spatial.py
									
									
									
									
									
										Normal file
									
								
							@ -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:166 intervention/forms/modalForms.py:125
 | 
			
		||||
#: 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:335 konova/forms.py:345 konova/forms.py:358
 | 
			
		||||
#: konova/forms.py:370 konova/forms.py:388 user/forms.py:38
 | 
			
		||||
#: konova/forms.py:139 konova/forms.py:240 konova/forms.py:309
 | 
			
		||||
#: konova/forms.py:336 konova/forms.py:346 konova/forms.py:359
 | 
			
		||||
#: konova/forms.py:371 konova/forms.py:389 user/forms.py:38
 | 
			
		||||
#, fuzzy
 | 
			
		||||
msgid ""
 | 
			
		||||
msgstr ""
 | 
			
		||||
"Project-Id-Version: PACKAGE VERSION\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"
 | 
			
		||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\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/view.html:31
 | 
			
		||||
#: intervention/templates/intervention/report/report.html:12
 | 
			
		||||
#: konova/forms.py:334
 | 
			
		||||
#: konova/forms.py:335
 | 
			
		||||
msgid "Title"
 | 
			
		||||
msgstr "Bezeichnung"
 | 
			
		||||
 | 
			
		||||
@ -356,7 +356,7 @@ msgstr "Kompensation XY; Flur ABC"
 | 
			
		||||
#: intervention/templates/intervention/detail/includes/documents.html:31
 | 
			
		||||
#: intervention/templates/intervention/detail/includes/payments.html:34
 | 
			
		||||
#: 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"
 | 
			
		||||
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:317 intervention/forms/modalForms.py:152
 | 
			
		||||
#: konova/forms.py:371
 | 
			
		||||
#: konova/forms.py:372
 | 
			
		||||
msgid "Additional comment, maximum {} letters"
 | 
			
		||||
msgstr "Zusätzlicher Kommentar, maximal {} Zeichen"
 | 
			
		||||
 | 
			
		||||
@ -793,7 +793,7 @@ msgstr "Dokumente"
 | 
			
		||||
#: compensation/templates/compensation/detail/eco_account/includes/documents.html:14
 | 
			
		||||
#: ema/templates/ema/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"
 | 
			
		||||
msgstr "Neues Dokument hinzufügen"
 | 
			
		||||
 | 
			
		||||
@ -1056,41 +1056,41 @@ msgstr "Kompensation {} hinzugefügt"
 | 
			
		||||
msgid "Compensation {} edited"
 | 
			
		||||
msgstr "Kompensation {} bearbeitet"
 | 
			
		||||
 | 
			
		||||
#: compensation/views/compensation.py:228 compensation/views/eco_account.py:307
 | 
			
		||||
#: ema/views.py:181 intervention/views.py:474
 | 
			
		||||
#: compensation/views/compensation.py:230 compensation/views/eco_account.py:309
 | 
			
		||||
#: ema/views.py:181 intervention/views.py:477
 | 
			
		||||
msgid "Log"
 | 
			
		||||
msgstr "Log"
 | 
			
		||||
 | 
			
		||||
#: compensation/views/compensation.py:251
 | 
			
		||||
#: compensation/views/compensation.py:253
 | 
			
		||||
msgid "Compensation removed"
 | 
			
		||||
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
 | 
			
		||||
msgid "Document added"
 | 
			
		||||
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
 | 
			
		||||
msgid "State added"
 | 
			
		||||
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
 | 
			
		||||
msgid "Action added"
 | 
			
		||||
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
 | 
			
		||||
msgid "Deadline added"
 | 
			
		||||
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
 | 
			
		||||
msgid "State removed"
 | 
			
		||||
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
 | 
			
		||||
msgid "Action removed"
 | 
			
		||||
msgstr "Maßnahme entfernt"
 | 
			
		||||
@ -1103,45 +1103,45 @@ msgstr "Ökokonto {} hinzugefügt"
 | 
			
		||||
msgid "Eco-Account {} edited"
 | 
			
		||||
msgstr "Ökokonto {} bearbeitet"
 | 
			
		||||
 | 
			
		||||
#: compensation/views/eco_account.py:255
 | 
			
		||||
#: compensation/views/eco_account.py:257
 | 
			
		||||
msgid "Eco-account removed"
 | 
			
		||||
msgstr "Ökokonto entfernt"
 | 
			
		||||
 | 
			
		||||
#: compensation/views/eco_account.py:283
 | 
			
		||||
#: compensation/views/eco_account.py:285
 | 
			
		||||
msgid "Deduction removed"
 | 
			
		||||
msgstr "Abbuchung entfernt"
 | 
			
		||||
 | 
			
		||||
#: compensation/views/eco_account.py:328 ema/views.py:261
 | 
			
		||||
#: intervention/views.py:516
 | 
			
		||||
#: compensation/views/eco_account.py:330 ema/views.py:261
 | 
			
		||||
#: intervention/views.py:519
 | 
			
		||||
msgid "{} unrecorded"
 | 
			
		||||
msgstr "{} entzeichnet"
 | 
			
		||||
 | 
			
		||||
#: compensation/views/eco_account.py:328 ema/views.py:261
 | 
			
		||||
#: intervention/views.py:516
 | 
			
		||||
#: compensation/views/eco_account.py:330 ema/views.py:261
 | 
			
		||||
#: intervention/views.py:519
 | 
			
		||||
msgid "{} recorded"
 | 
			
		||||
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"
 | 
			
		||||
msgstr "Abbuchung hinzugefügt"
 | 
			
		||||
 | 
			
		||||
#: compensation/views/eco_account.py:612 ema/views.py:516
 | 
			
		||||
#: intervention/views.py:372
 | 
			
		||||
#: compensation/views/eco_account.py:614 ema/views.py:516
 | 
			
		||||
#: intervention/views.py:375
 | 
			
		||||
msgid "{} has already been shared with you"
 | 
			
		||||
msgstr "{} wurde bereits für Sie freigegeben"
 | 
			
		||||
 | 
			
		||||
#: compensation/views/eco_account.py:617 ema/views.py:521
 | 
			
		||||
#: intervention/views.py:377
 | 
			
		||||
#: compensation/views/eco_account.py:619 ema/views.py:521
 | 
			
		||||
#: intervention/views.py:380
 | 
			
		||||
msgid "{} has been shared with you"
 | 
			
		||||
msgstr "{} ist nun für Sie freigegeben"
 | 
			
		||||
 | 
			
		||||
#: compensation/views/eco_account.py:624 ema/views.py:528
 | 
			
		||||
#: intervention/views.py:384
 | 
			
		||||
#: compensation/views/eco_account.py:626 ema/views.py:528
 | 
			
		||||
#: intervention/views.py:387
 | 
			
		||||
msgid "Share link invalid"
 | 
			
		||||
msgstr "Freigabelink ungültig"
 | 
			
		||||
 | 
			
		||||
#: compensation/views/eco_account.py:647 ema/views.py:551
 | 
			
		||||
#: intervention/views.py:407
 | 
			
		||||
#: compensation/views/eco_account.py:649 ema/views.py:551
 | 
			
		||||
#: intervention/views.py:410
 | 
			
		||||
msgid "Share settings updated"
 | 
			
		||||
msgstr "Freigabe Einstellungen aktualisiert"
 | 
			
		||||
 | 
			
		||||
@ -1333,7 +1333,7 @@ msgstr "Kompensationen und Zahlungen geprüft"
 | 
			
		||||
msgid "Run check"
 | 
			
		||||
msgstr "Prüfung vornehmen"
 | 
			
		||||
 | 
			
		||||
#: intervention/forms/modalForms.py:196 konova/forms.py:453
 | 
			
		||||
#: intervention/forms/modalForms.py:196 konova/forms.py:454
 | 
			
		||||
msgid ""
 | 
			
		||||
"I, {} {}, confirm that all necessary control steps have been performed by "
 | 
			
		||||
"myself."
 | 
			
		||||
@ -1472,31 +1472,31 @@ msgstr ""
 | 
			
		||||
msgid "Intervention {} added"
 | 
			
		||||
msgstr "Eingriff {} hinzugefügt"
 | 
			
		||||
 | 
			
		||||
#: intervention/views.py:243
 | 
			
		||||
#: intervention/views.py:245
 | 
			
		||||
msgid "This intervention has {} revocations"
 | 
			
		||||
msgstr "Dem Eingriff liegen {} Widersprüche vor"
 | 
			
		||||
 | 
			
		||||
#: intervention/views.py:290
 | 
			
		||||
#: intervention/views.py:293
 | 
			
		||||
msgid "Intervention {} edited"
 | 
			
		||||
msgstr "Eingriff {} bearbeitet"
 | 
			
		||||
 | 
			
		||||
#: intervention/views.py:325
 | 
			
		||||
#: intervention/views.py:328
 | 
			
		||||
msgid "{} removed"
 | 
			
		||||
msgstr "{} entfernt"
 | 
			
		||||
 | 
			
		||||
#: intervention/views.py:346
 | 
			
		||||
#: intervention/views.py:349
 | 
			
		||||
msgid "Revocation removed"
 | 
			
		||||
msgstr "Widerspruch entfernt"
 | 
			
		||||
 | 
			
		||||
#: intervention/views.py:428
 | 
			
		||||
#: intervention/views.py:431
 | 
			
		||||
msgid "Check performed"
 | 
			
		||||
msgstr "Prüfung durchgeführt"
 | 
			
		||||
 | 
			
		||||
#: intervention/views.py:450
 | 
			
		||||
#: intervention/views.py:453
 | 
			
		||||
msgid "Revocation added"
 | 
			
		||||
msgstr "Widerspruch hinzugefügt"
 | 
			
		||||
 | 
			
		||||
#: intervention/views.py:521
 | 
			
		||||
#: intervention/views.py:524
 | 
			
		||||
msgid "There are errors on this intervention:"
 | 
			
		||||
msgstr "Es liegen Fehler in diesem Eingriff vor:"
 | 
			
		||||
 | 
			
		||||
@ -1525,11 +1525,11 @@ msgstr "Speichern"
 | 
			
		||||
msgid "Not editable"
 | 
			
		||||
msgstr "Nicht editierbar"
 | 
			
		||||
 | 
			
		||||
#: konova/forms.py:138 konova/forms.py:307
 | 
			
		||||
#: konova/forms.py:138 konova/forms.py:308
 | 
			
		||||
msgid "Confirm"
 | 
			
		||||
msgstr "Bestätige"
 | 
			
		||||
 | 
			
		||||
#: konova/forms.py:150 konova/forms.py:316
 | 
			
		||||
#: konova/forms.py:150 konova/forms.py:317
 | 
			
		||||
msgid "Remove"
 | 
			
		||||
msgstr "Löschen"
 | 
			
		||||
 | 
			
		||||
@ -1542,56 +1542,56 @@ msgstr "Sie sind dabei {} {} zu löschen"
 | 
			
		||||
msgid "Geometry"
 | 
			
		||||
msgstr "Geometrie"
 | 
			
		||||
 | 
			
		||||
#: konova/forms.py:317
 | 
			
		||||
#: konova/forms.py:318
 | 
			
		||||
msgid "Are you sure?"
 | 
			
		||||
msgstr "Sind Sie sicher?"
 | 
			
		||||
 | 
			
		||||
#: konova/forms.py:344
 | 
			
		||||
#: konova/forms.py:345
 | 
			
		||||
msgid "Created on"
 | 
			
		||||
msgstr "Erstellt"
 | 
			
		||||
 | 
			
		||||
#: konova/forms.py:346
 | 
			
		||||
#: konova/forms.py:347
 | 
			
		||||
msgid "When has this file been created? Important for photos."
 | 
			
		||||
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
 | 
			
		||||
msgid "File"
 | 
			
		||||
msgstr "Datei"
 | 
			
		||||
 | 
			
		||||
#: konova/forms.py:359
 | 
			
		||||
#: konova/forms.py:360
 | 
			
		||||
msgid "Allowed formats: pdf, jpg, png. Max size 15 MB."
 | 
			
		||||
msgstr "Formate: pdf, jpg, png. Maximal 15 MB."
 | 
			
		||||
 | 
			
		||||
#: konova/forms.py:405
 | 
			
		||||
#: konova/forms.py:406
 | 
			
		||||
msgid "Unsupported file type"
 | 
			
		||||
msgstr "Dateiformat nicht unterstützt"
 | 
			
		||||
 | 
			
		||||
#: konova/forms.py:412
 | 
			
		||||
#: konova/forms.py:413
 | 
			
		||||
msgid "File too large"
 | 
			
		||||
msgstr "Datei zu groß"
 | 
			
		||||
 | 
			
		||||
#: konova/forms.py:421
 | 
			
		||||
#: konova/forms.py:422
 | 
			
		||||
msgid "Added document"
 | 
			
		||||
msgstr "Dokument hinzugefügt"
 | 
			
		||||
 | 
			
		||||
#: konova/forms.py:444
 | 
			
		||||
#: konova/forms.py:445
 | 
			
		||||
msgid "Confirm record"
 | 
			
		||||
msgstr "Verzeichnen bestätigen"
 | 
			
		||||
 | 
			
		||||
#: konova/forms.py:452
 | 
			
		||||
#: konova/forms.py:453
 | 
			
		||||
msgid "Record data"
 | 
			
		||||
msgstr "Daten verzeichnen"
 | 
			
		||||
 | 
			
		||||
#: konova/forms.py:459
 | 
			
		||||
#: konova/forms.py:460
 | 
			
		||||
msgid "Confirm unrecord"
 | 
			
		||||
msgstr "Entzeichnen bestätigen"
 | 
			
		||||
 | 
			
		||||
#: konova/forms.py:460
 | 
			
		||||
#: konova/forms.py:461
 | 
			
		||||
msgid "Unrecord data"
 | 
			
		||||
msgstr "Daten entzeichnen"
 | 
			
		||||
 | 
			
		||||
#: konova/forms.py:461
 | 
			
		||||
#: konova/forms.py:462
 | 
			
		||||
msgid "I, {} {}, confirm that this data must be unrecorded."
 | 
			
		||||
msgstr ""
 | 
			
		||||
"Ich, {} {}, bestätige, dass diese Daten wieder entzeichnet werden müssen."
 | 
			
		||||
@ -1663,6 +1663,30 @@ msgstr "Anzeigen"
 | 
			
		||||
msgid "Deduct"
 | 
			
		||||
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
 | 
			
		||||
msgid "Generate new"
 | 
			
		||||
msgstr "Neu generieren"
 | 
			
		||||
@ -1726,8 +1750,8 @@ msgid ""
 | 
			
		||||
"Action canceled. Eco account is recorded or deductions exist. Only "
 | 
			
		||||
"conservation office member can perform this action."
 | 
			
		||||
msgstr ""
 | 
			
		||||
"Aktion abgebrochen. Ökokonto ist bereits verzeichnet oder Abbuchungen liegen vor. Nur "
 | 
			
		||||
"Eintragungsstellennutzer können diese Aktion jetzt durchführen."
 | 
			
		||||
"Aktion abgebrochen. Ökokonto ist bereits verzeichnet oder Abbuchungen liegen "
 | 
			
		||||
"vor. Nur Eintragungsstellennutzer können diese Aktion jetzt durchführen."
 | 
			
		||||
 | 
			
		||||
#: konova/utils/message_templates.py:25
 | 
			
		||||
msgid "Edited general data"
 | 
			
		||||
 | 
			
		||||
@ -14,10 +14,14 @@ django-tables2==2.3.4
 | 
			
		||||
et-xmlfile==1.1.0
 | 
			
		||||
idna==2.10
 | 
			
		||||
importlib-metadata==2.1.1
 | 
			
		||||
itsdangerous<1.0.0
 | 
			
		||||
itsdangerous==0.24
 | 
			
		||||
openpyxl==3.0.9
 | 
			
		||||
OWSLib==0.25.0
 | 
			
		||||
psycopg2-binary==2.9.1
 | 
			
		||||
pyproj==3.2.1
 | 
			
		||||
python-dateutil==2.8.2
 | 
			
		||||
pytz==2020.4
 | 
			
		||||
PyYAML==6.0
 | 
			
		||||
qrcode==7.3.1
 | 
			
		||||
requests==2.25.0
 | 
			
		||||
six==1.15.0
 | 
			
		||||
@ -25,4 +29,5 @@ soupsieve==2.2.1
 | 
			
		||||
sqlparse==0.4.1
 | 
			
		||||
urllib3==1.26.2
 | 
			
		||||
webservices==0.7
 | 
			
		||||
xmltodict==0.12.0
 | 
			
		||||
zipp==3.4.1
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user