diff --git a/api/tests/v1/create/compensation_create_post_body.json b/api/tests/v1/create/compensation_create_post_body.json
index b0d21e83..865621a1 100644
--- a/api/tests/v1/create/compensation_create_post_body.json
+++ b/api/tests/v1/create/compensation_create_post_body.json
@@ -6,6 +6,7 @@
"title": "Test_compensation",
"is_cef": false,
"is_coherence_keeping": false,
+ "is_pik": false,
"intervention": "MUST_BE_SET_IN_TEST",
"before_states": [
],
diff --git a/api/tests/v1/create/ecoaccount_create_post_body.json b/api/tests/v1/create/ecoaccount_create_post_body.json
index 8300277d..7550d8cd 100644
--- a/api/tests/v1/create/ecoaccount_create_post_body.json
+++ b/api/tests/v1/create/ecoaccount_create_post_body.json
@@ -5,6 +5,7 @@
"properties": {
"title": "Test_ecoaccount",
"deductable_surface": 10000.0,
+ "is_pik": false,
"responsible": {
"conservation_office": null,
"conservation_file_number": null,
diff --git a/api/tests/v1/create/ema_create_post_body.json b/api/tests/v1/create/ema_create_post_body.json
index 4949b7ab..8b826661 100644
--- a/api/tests/v1/create/ema_create_post_body.json
+++ b/api/tests/v1/create/ema_create_post_body.json
@@ -4,6 +4,7 @@
],
"properties": {
"title": "Test_ema",
+ "is_pik": false,
"responsible": {
"conservation_office": null,
"conservation_file_number": null,
diff --git a/api/tests/v1/get/test_api_get.py b/api/tests/v1/get/test_api_get.py
index 953b0f69..0db58cbc 100644
--- a/api/tests/v1/get/test_api_get.py
+++ b/api/tests/v1/get/test_api_get.py
@@ -122,6 +122,7 @@ class APIV1GetTestCase(BaseAPIV1TestCase):
props = geojson["properties"]
props["is_cef"]
props["is_coherence_keeping"]
+ props["is_pik"]
props["intervention"]
props["intervention"]["id"]
props["intervention"]["identifier"]
diff --git a/api/tests/v1/update/compensation_update_put_body.json b/api/tests/v1/update/compensation_update_put_body.json
index 57ad5ed4..4ae91ac7 100644
--- a/api/tests/v1/update/compensation_update_put_body.json
+++ b/api/tests/v1/update/compensation_update_put_body.json
@@ -46,6 +46,7 @@
"title": "TEST_compensation_CHANGED",
"is_cef": true,
"is_coherence_keeping": true,
+ "is_pik": true,
"intervention": "CHANGE_BEFORE_RUN!!!",
"before_states": [],
"after_states": [],
diff --git a/api/tests/v1/update/ecoaccount_update_put_body.json b/api/tests/v1/update/ecoaccount_update_put_body.json
index ff636ff0..2b8235cb 100644
--- a/api/tests/v1/update/ecoaccount_update_put_body.json
+++ b/api/tests/v1/update/ecoaccount_update_put_body.json
@@ -45,6 +45,7 @@
"properties": {
"title": "TEST_account_CHANGED",
"deductable_surface": "100000.0",
+ "is_pik": true,
"responsible": {
"conservation_office": null,
"conservation_file_number": "123-TEST",
diff --git a/api/tests/v1/update/ema_update_put_body.json b/api/tests/v1/update/ema_update_put_body.json
index cc835850..6c7adf68 100644
--- a/api/tests/v1/update/ema_update_put_body.json
+++ b/api/tests/v1/update/ema_update_put_body.json
@@ -52,6 +52,7 @@
"detail": "TEST_HANDLER_CHANGED"
}
},
+ "is_pik": true,
"before_states": [],
"after_states": [],
"actions": [],
diff --git a/api/tests/v1/update/test_api_update.py b/api/tests/v1/update/test_api_update.py
index bfc670bc..ff867e69 100644
--- a/api/tests/v1/update/test_api_update.py
+++ b/api/tests/v1/update/test_api_update.py
@@ -97,6 +97,7 @@ class APIV1UpdateTestCase(BaseAPIV1TestCase):
self.assertNotEqual(modified_on, self.compensation.modified)
self.assertEqual(put_props["is_cef"], self.compensation.is_cef)
self.assertEqual(put_props["is_coherence_keeping"], self.compensation.is_coherence_keeping)
+ self.assertEqual(put_props["is_pik"], self.compensation.is_pik)
self.assertEqual(len(put_props["actions"]), self.compensation.actions.count())
self.assertEqual(len(put_props["before_states"]), self.compensation.before_states.count())
self.assertEqual(len(put_props["after_states"]), self.compensation.after_states.count())
diff --git a/api/utils/serializer/v1/compensation.py b/api/utils/serializer/v1/compensation.py
index 24e499ef..89dbe0e6 100644
--- a/api/utils/serializer/v1/compensation.py
+++ b/api/utils/serializer/v1/compensation.py
@@ -34,6 +34,7 @@ class CompensationAPISerializerV1(AbstractModelAPISerializerV1, AbstractCompensa
def _extend_properties_data(self, entry):
self.properties_data["is_cef"] = entry.is_cef
self.properties_data["is_coherence_keeping"] = entry.is_coherence_keeping
+ self.properties_data["is_pik"] = entry.is_pik
self.properties_data["intervention"] = self.intervention_to_json(entry.intervention)
self.properties_data["before_states"] = self._compensation_state_to_json(entry.before_states.all())
self.properties_data["after_states"] = self._compensation_state_to_json(entry.after_states.all())
@@ -113,6 +114,7 @@ class CompensationAPISerializerV1(AbstractModelAPISerializerV1, AbstractCompensa
obj.title = properties["title"]
obj.is_cef = properties["is_cef"]
obj.is_coherence_keeping = properties["is_coherence_keeping"]
+ obj.is_pik = properties.get("is_pik", False)
obj = self.set_intervention(obj, properties["intervention"], user)
obj.geometry.save()
@@ -149,6 +151,7 @@ class CompensationAPISerializerV1(AbstractModelAPISerializerV1, AbstractCompensa
obj.title = properties["title"]
obj.is_cef = properties["is_cef"]
obj.is_coherence_keeping = properties["is_coherence_keeping"]
+ obj.is_pik = properties.get("is_pik", False)
obj.modified = update_action
obj.geometry.geom = self._create_geometry_from_json(json_model)
obj.geometry.modified = update_action
diff --git a/api/utils/serializer/v1/ecoaccount.py b/api/utils/serializer/v1/ecoaccount.py
index 7fe4373d..106789f4 100644
--- a/api/utils/serializer/v1/ecoaccount.py
+++ b/api/utils/serializer/v1/ecoaccount.py
@@ -25,6 +25,7 @@ class EcoAccountAPISerializerV1(AbstractModelAPISerializerV1,
model = EcoAccount
def _extend_properties_data(self, entry):
+ self.properties_data["is_pik"] = entry.is_pik
self.properties_data["deductable_surface"] = entry.deductable_surface
self.properties_data["deductable_surface_available"] = entry.deductable_surface - entry.get_deductions_surface()
self.properties_data["responsible"] = self._responsible_to_json(entry.responsible)
@@ -122,6 +123,7 @@ class EcoAccountAPISerializerV1(AbstractModelAPISerializerV1,
properties = json_model["properties"]
obj.identifier = obj.generate_new_identifier()
obj.title = properties["title"]
+ obj.is_pik = properties.get("is_pik", False)
try:
obj.deductable_surface = float(properties["deductable_surface"])
@@ -169,6 +171,7 @@ class EcoAccountAPISerializerV1(AbstractModelAPISerializerV1,
# Fill in data to objects
properties = json_model["properties"]
obj.title = properties["title"]
+ obj.is_pik = properties.get("is_pik", False)
obj.deductable_surface = float(properties["deductable_surface"])
obj.modified = update_action
obj.geometry.geom = self._create_geometry_from_json(json_model)
diff --git a/api/utils/serializer/v1/ema.py b/api/utils/serializer/v1/ema.py
index dfda7249..787a64b2 100644
--- a/api/utils/serializer/v1/ema.py
+++ b/api/utils/serializer/v1/ema.py
@@ -21,6 +21,7 @@ class EmaAPISerializerV1(AbstractModelAPISerializerV1, AbstractCompensationAPISe
model = Ema
def _extend_properties_data(self, entry):
+ self.properties_data["is_pik"] = entry.is_pik
self.properties_data["responsible"] = self._responsible_to_json(entry.responsible)
self.properties_data["before_states"] = self._compensation_state_to_json(entry.before_states.all())
self.properties_data["after_states"] = self._compensation_state_to_json(entry.after_states.all())
@@ -104,6 +105,7 @@ class EmaAPISerializerV1(AbstractModelAPISerializerV1, AbstractCompensationAPISe
properties = json_model["properties"]
obj.identifier = obj.generate_new_identifier()
obj.title = properties["title"]
+ obj.is_pik = properties.get("is_pik", False)
obj = self._set_responsibility(obj, properties["responsible"])
obj.geometry.save()
@@ -141,6 +143,7 @@ class EmaAPISerializerV1(AbstractModelAPISerializerV1, AbstractCompensationAPISe
# Fill in data to objects
properties = json_model["properties"]
obj.title = properties["title"]
+ obj.is_pik = properties.get("is_pik", False)
obj.modified = update_action
obj.geometry.geom = self._create_geometry_from_json(json_model)
obj.geometry.modified = update_action
diff --git a/api/utils/serializer/v1/intervention.py b/api/utils/serializer/v1/intervention.py
index 1ce2d684..af13c4a2 100644
--- a/api/utils/serializer/v1/intervention.py
+++ b/api/utils/serializer/v1/intervention.py
@@ -132,6 +132,7 @@ class InterventionAPISerializerV1(AbstractModelAPISerializerV1,
id__in=payments
)
obj.payments.set(payments)
+ obj.send_data_to_egon()
return obj
def create_model_from_json(self, json_model, user):
@@ -197,7 +198,7 @@ class InterventionAPISerializerV1(AbstractModelAPISerializerV1,
obj.legal.save()
obj.save()
- obj.mark_as_edited(user)
+ obj.mark_as_edited(user, edit_comment="API update")
celery_update_parcels.delay(obj.geometry.id)
diff --git a/api/utils/serializer/v1/serializer.py b/api/utils/serializer/v1/serializer.py
index 23d1f692..9d3b9dfb 100644
--- a/api/utils/serializer/v1/serializer.py
+++ b/api/utils/serializer/v1/serializer.py
@@ -75,7 +75,10 @@ class AbstractModelAPISerializerV1(AbstractModelAPISerializer):
Returns:
"""
- if json_str is None or len(json_str) == 0:
+ if json_str is None:
+ return None
+ json_str = str(json_str)
+ if len(json_str) == 0:
return None
code = KonovaCode.objects.get(
atom_id=json_str,
diff --git a/codelist/admin.py b/codelist/admin.py
index 55ce827d..ccdcb057 100644
--- a/codelist/admin.py
+++ b/codelist/admin.py
@@ -33,6 +33,7 @@ class KonovaCodeAdmin(admin.ModelAdmin):
"is_selectable",
"is_leaf",
"parent",
+ "found_in_codelists",
]
search_fields = [
@@ -42,6 +43,12 @@ class KonovaCodeAdmin(admin.ModelAdmin):
"short_name",
]
+ def found_in_codelists(self, obj):
+ codelists = KonovaCodeList.objects.filter(
+ codes__in=[obj]
+ ).values_list("id", flat=True)
+ codelists = "\n".join(str(x) for x in codelists)
+ return codelists
#admin.site.register(KonovaCodeList, KonovaCodeListAdmin)
admin.site.register(KonovaCode, KonovaCodeAdmin)
diff --git a/codelist/models.py b/codelist/models.py
index ac9f2941..92feb1b2 100644
--- a/codelist/models.py
+++ b/codelist/models.py
@@ -65,24 +65,23 @@ class KonovaCode(models.Model):
ret_val += ", " + self.parent.long_name
return ret_val
- def add_children(self):
+ def add_children(self, order_by: str = "long_name"):
""" Adds all children (resurcively until leaf) as .children to the KonovaCode
Returns:
code (KonovaCode): The manipulated KonovaCode instance
"""
if self.is_leaf:
- return None
+ return self
children = KonovaCode.objects.filter(
- code_lists__in=self.code_lists.all(),
parent=self
).order_by(
- "long_name"
+ order_by
)
self.children = children
for child in children:
- child.add_children()
+ child.add_children(order_by)
return self
diff --git a/compensation/admin.py b/compensation/admin.py
index 5f792f76..2821a146 100644
--- a/compensation/admin.py
+++ b/compensation/admin.py
@@ -21,16 +21,30 @@ class AbstractCompensationAdmin(BaseObjectAdmin):
"identifier",
"title",
"comment",
- "after_states",
- "before_states",
+ "list_after_states",
+ "list_before_states",
+ "geometry",
]
def get_readonly_fields(self, request, obj=None):
return super().get_readonly_fields(request, obj) + [
- "after_states",
- "before_states",
+ "list_after_states",
+ "list_before_states",
+ "geometry",
]
+ def list_after_states(self, obj):
+ states = obj.after_states.all()
+ states = [str(state) for state in states]
+ states = "\n".join(states)
+ return states
+
+ def list_before_states(self, obj):
+ states = obj.before_states.all()
+ states = [str(state) for state in states]
+ states = "\n".join(states)
+ return states
+
class CompensationAdmin(AbstractCompensationAdmin):
autocomplete_fields = [
@@ -41,6 +55,7 @@ class CompensationAdmin(AbstractCompensationAdmin):
return super().get_fields(request, obj) + [
"is_cef",
"is_coherence_keeping",
+ "is_pik",
"intervention",
]
diff --git a/compensation/filters.py b/compensation/filters.py
index b6377092..c9bf9b70 100644
--- a/compensation/filters.py
+++ b/compensation/filters.py
@@ -60,7 +60,7 @@ class CheckboxCompensationTableFilter(CheckboxTableFilter):
if not value:
return queryset.filter(
Q(intervention__users__in=[self.user]) | # requesting user has access
- Q(intervention__teams__users__in=[self.user])
+ Q(intervention__teams__in=self.user.shared_teams)
).distinct()
else:
return queryset
diff --git a/compensation/forms/forms.py b/compensation/forms/forms.py
index 2cc25a8f..2733932b 100644
--- a/compensation/forms/forms.py
+++ b/compensation/forms/forms.py
@@ -160,7 +160,23 @@ class CoherenceCompensationFormMixin(forms.Form):
)
-class NewCompensationForm(AbstractCompensationForm, CEFCompensationFormMixin, CoherenceCompensationFormMixin):
+class PikCompensationFormMixin(forms.Form):
+ """ A form mixin, providing PIK compensation field
+
+ """
+ is_pik = forms.BooleanField(
+ label_suffix="",
+ label=_("Is PIK"),
+ help_text=_("Optionally: Whether this compensation is a compensation integrated in production?"),
+ required=False,
+ widget=forms.CheckboxInput()
+ )
+
+
+class NewCompensationForm(AbstractCompensationForm,
+ CEFCompensationFormMixin,
+ CoherenceCompensationFormMixin,
+ PikCompensationFormMixin):
""" Form for creating new compensations.
Can be initialized with an intervention id for preselecting the related intervention.
@@ -191,6 +207,7 @@ class NewCompensationForm(AbstractCompensationForm, CEFCompensationFormMixin, Co
"identifier",
"title",
"intervention",
+ "is_pik",
"is_cef",
"is_coherence_keeping",
"comment",
@@ -234,6 +251,7 @@ class NewCompensationForm(AbstractCompensationForm, CEFCompensationFormMixin, Co
intervention = self.cleaned_data.get("intervention", None)
is_cef = self.cleaned_data.get("is_cef", None)
is_coherence_keeping = self.cleaned_data.get("is_coherence_keeping", None)
+ is_pik = self.cleaned_data.get("is_pik", None)
comment = self.cleaned_data.get("comment", None)
# Create log entry
@@ -249,6 +267,7 @@ class NewCompensationForm(AbstractCompensationForm, CEFCompensationFormMixin, Co
created=action,
is_cef=is_cef,
is_coherence_keeping=is_coherence_keeping,
+ is_pik=is_pik,
geometry=geometry,
comment=comment,
)
@@ -281,6 +300,7 @@ class EditCompensationForm(NewCompensationForm):
"intervention": self.instance.intervention,
"is_cef": self.instance.is_cef,
"is_coherence_keeping": self.instance.is_coherence_keeping,
+ "is_pik": self.instance.is_pik,
"comment": self.instance.comment,
}
disabled_fields = []
@@ -297,6 +317,7 @@ class EditCompensationForm(NewCompensationForm):
intervention = self.cleaned_data.get("intervention", None)
is_cef = self.cleaned_data.get("is_cef", None)
is_coherence_keeping = self.cleaned_data.get("is_coherence_keeping", None)
+ is_pik = self.cleaned_data.get("is_pik", None)
comment = self.cleaned_data.get("comment", None)
# Create log entry
@@ -313,6 +334,7 @@ class EditCompensationForm(NewCompensationForm):
self.instance.is_cef = is_cef
self.instance.is_coherence_keeping = is_coherence_keeping
self.instance.comment = comment
+ self.instance.is_pik = is_pik
self.instance.modified = action
self.instance.save()
@@ -322,7 +344,7 @@ class EditCompensationForm(NewCompensationForm):
return self.instance
-class NewEcoAccountForm(AbstractCompensationForm, CompensationResponsibleFormMixin):
+class NewEcoAccountForm(AbstractCompensationForm, CompensationResponsibleFormMixin, PikCompensationFormMixin):
""" Form for creating eco accounts
Inherits from basic AbstractCompensationForm and further form fields from CompensationResponsibleFormMixin
@@ -363,6 +385,7 @@ class NewEcoAccountForm(AbstractCompensationForm, CompensationResponsibleFormMix
"registration_date",
"surface",
"conservation_file_number",
+ "is_pik",
"handler_type",
"handler_detail",
"comment",
@@ -392,6 +415,7 @@ class NewEcoAccountForm(AbstractCompensationForm, CompensationResponsibleFormMix
surface = self.cleaned_data.get("surface", None)
conservation_office = self.cleaned_data.get("conservation_office", None)
conservation_file_number = self.cleaned_data.get("conservation_file_number", None)
+ is_pik = self.cleaned_data.get("is_pik", None)
comment = self.cleaned_data.get("comment", None)
# Create log entry
@@ -423,6 +447,7 @@ class NewEcoAccountForm(AbstractCompensationForm, CompensationResponsibleFormMix
created=action,
geometry=geometry,
comment=comment,
+ is_pik=is_pik,
legal=legal
)
acc.share_with_user(user)
@@ -458,6 +483,7 @@ class EditEcoAccountForm(NewEcoAccountForm):
"registration_date": reg_date,
"conservation_office": self.instance.responsible.conservation_office,
"conservation_file_number": self.instance.responsible.conservation_file_number,
+ "is_pik": self.instance.is_pik,
"comment": self.instance.comment,
}
disabled_fields = []
@@ -478,6 +504,7 @@ class EditEcoAccountForm(NewEcoAccountForm):
conservation_office = self.cleaned_data.get("conservation_office", None)
conservation_file_number = self.cleaned_data.get("conservation_file_number", None)
comment = self.cleaned_data.get("comment", None)
+ is_pik = self.cleaned_data.get("is_pik", None)
# Create log entry
action = UserActionLogEntry.get_edited_action(user)
@@ -503,6 +530,7 @@ class EditEcoAccountForm(NewEcoAccountForm):
self.instance.deductable_surface = surface
self.instance.geometry = geometry
self.instance.comment = comment
+ self.instance.is_pik = is_pik
self.instance.modified = action
self.instance.save()
diff --git a/compensation/forms/modalForms.py b/compensation/forms/modalForms.py
index ebfb1ebd..581a7b3a 100644
--- a/compensation/forms/modalForms.py
+++ b/compensation/forms/modalForms.py
@@ -17,7 +17,8 @@ from codelist.models import KonovaCode
from codelist.settings import CODELIST_BIOTOPES_ID, CODELIST_COMPENSATION_ACTION_ID, CODELIST_BIOTOPES_EXTRA_CODES_ID, \
CODELIST_COMPENSATION_ACTION_DETAIL_ID
from compensation.models import CompensationDocument, EcoAccountDocument
-from intervention.inputs import CompensationActionTreeCheckboxSelectMultiple
+from intervention.inputs import CompensationActionTreeCheckboxSelectMultiple, \
+ CompensationStateTreeRadioSelect
from konova.contexts import BaseContext
from konova.forms import BaseModalForm, NewDocumentModalForm, RemoveModalForm
from konova.models import DeadlineType
@@ -128,6 +129,7 @@ class EditPaymentModalForm(NewPaymentForm):
payment.comment = self.cleaned_data.get("comment", None)
payment.save()
self.instance.mark_as_edited(self.user, self.request, edit_comment=PAYMENT_EDITED)
+ self.instance.send_data_to_egon()
return payment
@@ -155,22 +157,12 @@ class NewStateModalForm(BaseModalForm):
What has been on this area before changes/compensations have been applied and what will be the result ('after')?
"""
- biotope_type = forms.ModelChoiceField(
+ biotope_type = forms.ChoiceField(
label=_("Biotope Type"),
label_suffix="",
required=True,
help_text=_("Select the biotope type"),
- queryset=KonovaCode.objects.filter(
- is_archived=False,
- is_leaf=True,
- code_lists__in=[CODELIST_BIOTOPES_ID],
- ),
- widget=autocomplete.ModelSelect2(
- url="codes-biotope-autocomplete",
- attrs={
- "data-placeholder": _("Biotope Type"),
- }
- ),
+ widget=CompensationStateTreeRadioSelect(),
)
biotope_extra = forms.ModelMultipleChoiceField(
label=_("Biotope additional type"),
@@ -208,6 +200,16 @@ class NewStateModalForm(BaseModalForm):
super().__init__(*args, **kwargs)
self.form_title = _("New state")
self.form_caption = _("Insert data for the new state")
+ choices = KonovaCode.objects.filter(
+ code_lists__in=[CODELIST_BIOTOPES_ID],
+ is_archived=False,
+ is_leaf=True,
+ ).values_list("id", flat=True)
+ choices = [
+ (choice, choice)
+ for choice in choices
+ ]
+ self.fields["biotope_type"].choices = choices
def save(self, is_before_state: bool = False):
state = self.instance.add_state(self, is_before_state)
@@ -270,8 +272,9 @@ class EditCompensationStateModalForm(NewStateModalForm):
self.state = kwargs.pop("state", None)
super().__init__(*args, **kwargs)
self.form_title = _("Edit state")
+ biotope_type_id = self.state.biotope_type.id if self.state.biotope_type else None
form_data = {
- "biotope_type": self.state.biotope_type,
+ "biotope_type": biotope_type_id,
"biotope_extra": self.state.biotope_type_details.all(),
"surface": self.state.surface,
}
@@ -279,7 +282,8 @@ class EditCompensationStateModalForm(NewStateModalForm):
def save(self, is_before_state: bool = False):
state = self.state
- state.biotope_type = self.cleaned_data.get("biotope_type", None)
+ biotope_type_id = self.cleaned_data.get("biotope_type", None)
+ state.biotope_type = KonovaCode.objects.get(id=biotope_type_id)
state.biotope_type_details.set(self.cleaned_data.get("biotope_extra", []))
state.surface = self.cleaned_data.get("surface", None)
state.save()
diff --git a/compensation/migrations/0007_auto_20220531_1245.py b/compensation/migrations/0007_auto_20220531_1245.py
new file mode 100644
index 00000000..688ffb18
--- /dev/null
+++ b/compensation/migrations/0007_auto_20220531_1245.py
@@ -0,0 +1,23 @@
+# Generated by Django 3.1.3 on 2022-05-31 10:45
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('compensation', '0006_ecoaccount_teams'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='compensation',
+ name='is_pik',
+ field=models.BooleanField(blank=True, default=False, help_text="Flag if compensation is a 'Produktonsintegrierte Kompensation'", null=True),
+ ),
+ migrations.AddField(
+ model_name='ecoaccount',
+ name='is_pik',
+ field=models.BooleanField(blank=True, default=False, help_text="Flag if compensation is a 'Produktonsintegrierte Kompensation'", null=True),
+ ),
+ ]
diff --git a/compensation/models/compensation.py b/compensation/models/compensation.py
index ea69cefc..2e42ff7a 100644
--- a/compensation/models/compensation.py
+++ b/compensation/models/compensation.py
@@ -8,6 +8,8 @@ Created on: 16.11.21
import shutil
from django.contrib import messages
+
+from codelist.models import KonovaCode
from user.models import User, Team
from django.db import models, transaction
from django.db.models import QuerySet, Sum
@@ -142,8 +144,10 @@ class AbstractCompensation(BaseObject, GeoReferencedMixin):
"""
form_data = form.cleaned_data
with transaction.atomic():
+ biotope_type_id = form_data["biotope_type"]
+ code = KonovaCode.objects.get(id=biotope_type_id)
state = CompensationState.objects.create(
- biotope_type=form_data["biotope_type"],
+ biotope_type=code,
surface=form_data["surface"],
)
state_additional_types = form_data["biotope_extra"]
@@ -253,7 +257,22 @@ class CoherenceMixin(models.Model):
abstract = True
-class Compensation(AbstractCompensation, CEFMixin, CoherenceMixin):
+class PikMixin(models.Model):
+ """ Provides PIK flag as Mixin
+
+ """
+ is_pik = models.BooleanField(
+ blank=True,
+ null=True,
+ default=False,
+ help_text="Flag if compensation is a 'Produktonsintegrierte Kompensation'"
+ )
+
+ class Meta:
+ abstract = True
+
+
+class Compensation(AbstractCompensation, CEFMixin, CoherenceMixin, PikMixin):
"""
Regular compensation, linked to an intervention
"""
@@ -418,6 +437,18 @@ class Compensation(AbstractCompensation, CEFMixin, CoherenceMixin):
super().set_status_messages(request)
return request
+ @property
+ def is_recorded(self):
+ """ Getter for record status as property
+
+ Since compensations inherit their record status from their intervention, the intervention's status is being
+ returned
+
+ Returns:
+
+ """
+ return self.intervention.is_recorded
+
class CompensationDocument(AbstractDocument):
"""
diff --git a/compensation/models/eco_account.py b/compensation/models/eco_account.py
index 6d95b399..3d48b691 100644
--- a/compensation/models/eco_account.py
+++ b/compensation/models/eco_account.py
@@ -17,14 +17,13 @@ from django.db.models import Sum, QuerySet
from django.utils.translation import gettext_lazy as _
from compensation.managers import EcoAccountManager, EcoAccountDeductionManager
-from compensation.models.compensation import AbstractCompensation
+from compensation.models.compensation import AbstractCompensation, PikMixin
from compensation.utils.quality import EcoAccountQualityChecker
from konova.models import ShareableObjectMixin, RecordableObjectMixin, AbstractDocument, BaseResource, \
generate_document_file_upload_path
-from konova.settings import DEFAULT_SRID_RLP, LANIS_LINK_TEMPLATE
-class EcoAccount(AbstractCompensation, ShareableObjectMixin, RecordableObjectMixin):
+class EcoAccount(AbstractCompensation, ShareableObjectMixin, RecordableObjectMixin, PikMixin):
"""
An eco account is a kind of 'prepaid' compensation. It can be compared to an account that already has been filled
with some kind of currency. From this account one is able to deduct currency for current projects.
diff --git a/compensation/tables.py b/compensation/tables.py
index 96888cc1..1373f2cd 100644
--- a/compensation/tables.py
+++ b/compensation/tables.py
@@ -5,17 +5,15 @@ Contact: michel.peltriaux@sgdnord.rlp.de
Created on: 01.12.20
"""
-from user.models import User
+from konova.utils.message_templates import DATA_IS_UNCHECKED, DATA_CHECKED_ON_TEMPLATE, DATA_CHECKED_PREVIOUSLY_TEMPLATE
from django.http import HttpRequest
from django.template.loader import render_to_string
from django.urls import reverse
from django.utils.html import format_html
-from django.utils.timezone import localtime
from django.utils.translation import gettext_lazy as _
from compensation.filters import CompensationTableFilter, EcoAccountTableFilter
from compensation.models import Compensation, EcoAccount
-from konova.sub_settings.django_settings import DEFAULT_DATE_TIME_FORMAT
from konova.utils.tables import BaseTable, TableRenderMixin
import django_tables2 as tables
@@ -111,16 +109,21 @@ class CompensationTable(BaseTable, TableRenderMixin):
"""
html = ""
checked = value is not None
- tooltip = _("Not checked yet")
+ tooltip = DATA_IS_UNCHECKED
+ previously_checked = record.intervention.get_last_checked_action()
if checked:
- value = value.timestamp
- value = localtime(value)
- checked_on = value.strftime(DEFAULT_DATE_TIME_FORMAT)
- tooltip = _("Checked on {} by {}").format(checked_on, record.intervention.checked.user)
+ checked_on = value.get_timestamp_str_formatted()
+ tooltip = DATA_CHECKED_ON_TEMPLATE.format(checked_on, record.intervention.checked.user)
html += self.render_checked_star(
tooltip=tooltip,
icn_filled=checked,
)
+ if previously_checked and not checked:
+ checked_on = previously_checked.get_timestamp_str_formatted()
+ tooltip = DATA_CHECKED_PREVIOUSLY_TEMPLATE.format(checked_on, previously_checked.user)
+ html += self.render_previously_checked_star(
+ tooltip=tooltip,
+ )
return format_html(html)
def render_d(self, value, record: Compensation):
@@ -134,7 +137,7 @@ class CompensationTable(BaseTable, TableRenderMixin):
"""
parcels = value.get_underlying_parcels().values_list(
- "gmrkng",
+ "parcel_group__name",
flat=True
).distinct()
html = render_to_string(
@@ -159,9 +162,7 @@ class CompensationTable(BaseTable, TableRenderMixin):
recorded = value is not None
tooltip = _("Not recorded yet")
if recorded:
- value = value.timestamp
- value = localtime(value)
- on = value.strftime(DEFAULT_DATE_TIME_FORMAT)
+ on = value.get_timestamp_str_formatted()
tooltip = _("Recorded on {} by {}").format(on, record.intervention.recorded.user)
html += self.render_bookmark(
tooltip=tooltip,
@@ -179,11 +180,7 @@ class CompensationTable(BaseTable, TableRenderMixin):
Returns:
"""
- if value is None:
- value = User.objects.none()
- has_access = value.filter(
- id=self.user.id
- ).exists()
+ has_access = record.is_shared_with(self.user)
html = self.render_icn(
tooltip=_("Full access granted") if has_access else _("Access not granted"),
@@ -295,7 +292,7 @@ class EcoAccountTable(BaseTable, TableRenderMixin):
"""
parcels = value.get_underlying_parcels().values_list(
- "gmrkng",
+ "parcel_group__name",
flat=True
).distinct()
html = render_to_string(
@@ -320,9 +317,7 @@ class EcoAccountTable(BaseTable, TableRenderMixin):
checked = value is not None
tooltip = _("Not recorded yet. Can not be used for deductions, yet.")
if checked:
- value = value.timestamp
- value = localtime(value)
- on = value.strftime(DEFAULT_DATE_TIME_FORMAT)
+ on = value.get_timestamp_str_formatted()
tooltip = _("Recorded on {} by {}").format(on, record.recorded.user)
html += self.render_bookmark(
tooltip=tooltip,
@@ -343,7 +338,7 @@ class EcoAccountTable(BaseTable, TableRenderMixin):
html = ""
# Do not use value in here, since value does use unprefetched 'users' manager, where record has already
# prefetched users data
- has_access = self.user in record.users.all()
+ has_access = record.is_shared_with(self.user)
html += self.render_icn(
tooltip=_("Full access granted") if has_access else _("Access not granted"),
icn_class="fas fa-edit rlp-r-inv" if has_access else "far fa-edit",
diff --git a/compensation/templates/compensation/detail/compensation/view.html b/compensation/templates/compensation/detail/compensation/view.html
index e3871510..5a125ccd 100644
--- a/compensation/templates/compensation/detail/compensation/view.html
+++ b/compensation/templates/compensation/detail/compensation/view.html
@@ -39,6 +39,16 @@
+
{% trans 'Is CEF compensation' %} |
@@ -66,6 +76,11 @@
{% fa5_icon 'star' 'far' %}
+ {% if last_checked %}
+
+ {% fa5_icon 'star' 'fas' %}
+
+ {% endif %}
{% else %}
{% fa5_icon 'star' %}
@@ -90,15 +105,21 @@
{% trans 'Last modified' %} |
- {{obj.modified.timestamp|default_if_none:""|naturalday}}
-
- {{obj.modified.user.username}}
+ {% if obj.modified %}
+ {{obj.modified.timestamp|default_if_none:""}}
+
+ {{obj.modified.user.username}}
+ {% else %}
+ {{obj.created.timestamp|default_if_none:""}}
+
+ {{obj.created.user.username}}
+ {% endif %}
|
{% trans 'Shared with' %} |
- {% for team in obj.intervention.teams.all %}
+ {% for team in obj.intervention.shared_teams %}
{% include 'user/includes/team_data_modal_button.html' %}
{% endfor %}
@@ -113,10 +134,12 @@
- {% include 'map/geom_form.html' %}
+
+ {% include 'map/geom_form.html' %}
+
- {% include 'konova/includes/parcels.html' %}
+ {% include 'konova/includes/parcels/parcels.html' %}
{% include 'konova/includes/comment_card.html' %}
diff --git a/compensation/templates/compensation/detail/eco_account/view.html b/compensation/templates/compensation/detail/eco_account/view.html
index ee4a0f73..432315a8 100644
--- a/compensation/templates/compensation/detail/eco_account/view.html
+++ b/compensation/templates/compensation/detail/eco_account/view.html
@@ -70,18 +70,34 @@
{% trans 'Action handler' %} |
{{obj.responsible.handler|default_if_none:""}} |
|
+
+ {% trans 'Is PIK' %} |
+
+ {% if obj.is_pik %}
+ {% trans 'Yes' %}
+ {% else %}
+ {% trans 'No' %}
+ {% endif %}
+ |
+
{% trans 'Last modified' %} |
- {{obj.modified.timestamp|default_if_none:""|naturalday}}
-
- {{obj.modified.user.username}}
+ {% if obj.modified %}
+ {{obj.modified.timestamp|default_if_none:""}}
+
+ {{obj.modified.user.username}}
+ {% else %}
+ {{obj.created.timestamp|default_if_none:""}}
+
+ {{obj.created.user.username}}
+ {% endif %}
|
{% trans 'Shared with' %} |
- {% for team in obj.teams.all %}
+ {% for team in obj.shared_teams %}
{% include 'user/includes/team_data_modal_button.html' %}
{% endfor %}
@@ -95,10 +111,12 @@
- {% include 'map/geom_form.html' %}
+
+ {% include 'map/geom_form.html' %}
+
- {% include 'konova/includes/parcels.html' %}
+ {% include 'konova/includes/parcels/parcels.html' %}
{% include 'konova/includes/comment_card.html' %}
diff --git a/compensation/templates/compensation/report/compensation/report.html b/compensation/templates/compensation/report/compensation/report.html
index 25130f72..2be278aa 100644
--- a/compensation/templates/compensation/report/compensation/report.html
+++ b/compensation/templates/compensation/report/compensation/report.html
@@ -20,6 +20,36 @@
|
+
+ {% trans 'Is PIK' %} |
+
+ {% if obj.is_pik %}
+ {% trans 'Yes' %}
+ {% else %}
+ {% trans 'No' %}
+ {% endif %}
+ |
+
+
+ {% trans 'Is CEF' %} |
+
+ {% if obj.is_cef %}
+ {% trans 'Yes' %}
+ {% else %}
+ {% trans 'No' %}
+ {% endif %}
+ |
+
+
+ {% trans 'Is coherence keeping' %} |
+
+ {% if obj.is_coherence_keeping %}
+ {% trans 'Yes' %}
+ {% else %}
+ {% trans 'No' %}
+ {% endif %}
+ |
+
{% trans 'Last modified' %} |
@@ -35,20 +65,15 @@
- {% include 'map/geom_form.html' %}
+
+ {% include 'map/geom_form.html' %}
+
- {% include 'konova/includes/parcels.html' %}
+ {% include 'konova/includes/parcels/parcels.html' %}
-
- {% trans 'Open in browser' %}
- {{ qrcode|safe }}
-
-
- {% trans 'View in LANIS' %}
- {{ qrcode_lanis|safe }}
-
+ {% include 'konova/includes/report/qrcodes.html' %}
diff --git a/compensation/templates/compensation/report/eco_account/report.html b/compensation/templates/compensation/report/eco_account/report.html
index 44496ea8..e2147f69 100644
--- a/compensation/templates/compensation/report/eco_account/report.html
+++ b/compensation/templates/compensation/report/eco_account/report.html
@@ -20,6 +20,16 @@
| {% trans 'Conservation office file number' %} |
{{obj.responsible.conservation_file_number|default_if_none:""}} |
+
+ {% trans 'Is PIK' %} |
+
+ {% if obj.is_pik %}
+ {% trans 'Yes' %}
+ {% else %}
+ {% trans 'No' %}
+ {% endif %}
+ |
+
{% trans 'Deductions for' %} |
@@ -48,20 +58,15 @@
- {% include 'map/geom_form.html' %}
+
+ {% include 'map/geom_form.html' %}
+
- {% include 'konova/includes/parcels.html' %}
+ {% include 'konova/includes/parcels/parcels.html' %}
-
- {% trans 'Open in browser' %}
- {{ qrcode|safe }}
-
-
- {% trans 'View in LANIS' %}
- {{ qrcode_lanis|safe }}
-
+ {% include 'konova/includes/report/qrcodes.html' %}
diff --git a/compensation/tests/compensation/test_workflow.py b/compensation/tests/compensation/test_workflow.py
index 5b7decff..bba12334 100644
--- a/compensation/tests/compensation/test_workflow.py
+++ b/compensation/tests/compensation/test_workflow.py
@@ -50,18 +50,20 @@ class CompensationWorkflowTestCase(BaseWorkflowTestCase):
test_id = self.create_dummy_string()
test_title = self.create_dummy_string()
test_geom = self.create_dummy_geometry()
+ geom_json = self.create_geojson(test_geom)
post_data = {
"identifier": test_id,
"title": test_title,
- "geom": test_geom.geojson,
+ "geom": geom_json,
"intervention": self.intervention.id,
}
pre_creation_intervention_log_count = self.intervention.log.count()
# Preserve the current number of intervention's compensations
num_compensations = self.intervention.compensations.count()
- self.client_user.post(new_url, post_data)
+ response = self.client_user.post(new_url, post_data)
+ self.assertEqual(302, response.status_code)
self.intervention.refresh_from_db()
self.assertEqual(num_compensations + 1, self.intervention.compensations.count())
new_compensation = self.intervention.compensations.get(identifier=test_id)
@@ -87,10 +89,11 @@ class CompensationWorkflowTestCase(BaseWorkflowTestCase):
test_id = self.create_dummy_string()
test_title = self.create_dummy_string()
test_geom = self.create_dummy_geometry()
+ geom_json = self.create_geojson(test_geom)
post_data = {
"identifier": test_id,
"title": test_title,
- "geom": test_geom.geojson,
+ "geom": geom_json,
}
pre_creation_intervention_log_count = self.intervention.log.count()
@@ -125,6 +128,7 @@ class CompensationWorkflowTestCase(BaseWorkflowTestCase):
new_identifier = self.create_dummy_string()
new_comment = self.create_dummy_string()
new_geometry = MultiPolygon(srid=4326) # Create an empty geometry
+ geojson = self.create_geojson(new_geometry)
check_on_elements = {
self.compensation.title: new_title,
@@ -139,7 +143,7 @@ class CompensationWorkflowTestCase(BaseWorkflowTestCase):
"title": new_title,
"intervention": self.intervention.id, # just keep the intervention as it is
"comment": new_comment,
- "geom": new_geometry.geojson,
+ "geom": geojson,
}
self.client_user.post(url, post_data)
self.compensation.refresh_from_db()
@@ -261,3 +265,26 @@ class CompensationWorkflowTestCase(BaseWorkflowTestCase):
self.assertIn(recorded, self.compensation.log.all())
self.assertEqual(pre_record_log_count + 1, self.compensation.log.count())
+ def test_non_editable_after_recording(self):
+ """ Tests that the compensation can not be edited after being recorded
+
+ User must be redirected to another page
+
+ Returns:
+
+ """
+ self.assertIsNotNone(self.compensation)
+ self.assertFalse(self.compensation.is_recorded)
+ edit_url = reverse("compensation:edit", args=(self.compensation.id,))
+ response = self.client_user.get(edit_url)
+ has_redirect = response.status_code == 302
+ self.assertFalse(has_redirect)
+
+ self.compensation.intervention.set_recorded(self.user)
+ self.assertTrue(self.compensation.is_recorded)
+
+ edit_url = reverse("compensation:edit", args=(self.compensation.id,))
+ response = self.client_user.get(edit_url)
+ has_redirect = response.status_code == 302
+ self.assertTrue(has_redirect)
+ self.compensation.intervention.set_unrecorded(self.user)
diff --git a/compensation/tests/ecoaccount/test_workflow.py b/compensation/tests/ecoaccount/test_workflow.py
index 03b4996a..d1a3cf09 100644
--- a/compensation/tests/ecoaccount/test_workflow.py
+++ b/compensation/tests/ecoaccount/test_workflow.py
@@ -40,12 +40,13 @@ class EcoAccountWorkflowTestCase(BaseWorkflowTestCase):
test_id = self.create_dummy_string()
test_title = self.create_dummy_string()
test_geom = self.create_dummy_geometry()
+ geom_json = self.create_geojson(test_geom)
test_deductable_surface = 1000
test_conservation_office = self.get_conservation_office_code()
post_data = {
"identifier": test_id,
"title": test_title,
- "geom": test_geom.geojson,
+ "geom": geom_json,
"deductable_surface": test_deductable_surface,
"conservation_office": test_conservation_office.id
}
@@ -302,3 +303,27 @@ class EcoAccountWorkflowTestCase(BaseWorkflowTestCase):
self.assertEqual(pre_edit_account_log_count + 1, account.log.count())
self.assertEqual(intervention.log.first().action, UserAction.EDITED)
self.assertEqual(account.log.first().action, UserAction.EDITED)
+
+ def test_non_editable_after_recording(self):
+ """ Tests that the eco_account can not be edited after being recorded
+
+ User must be redirected to another page
+
+ Returns:
+
+ """
+ self.assertIsNotNone(self.eco_account)
+ self.assertFalse(self.eco_account.is_recorded)
+ edit_url = reverse("compensation:acc:edit", args=(self.eco_account.id,))
+ response = self.client_user.get(edit_url)
+ has_redirect = response.status_code == 302
+ self.assertFalse(has_redirect)
+
+ self.eco_account.set_recorded(self.user)
+ self.assertTrue(self.eco_account.is_recorded)
+
+ edit_url = reverse("compensation:acc:edit", args=(self.eco_account.id,))
+ response = self.client_user.get(edit_url)
+ has_redirect = response.status_code == 302
+ self.assertTrue(has_redirect)
+ self.eco_account.set_unrecorded(self.user)
diff --git a/compensation/views/compensation.py b/compensation/views/compensation.py
index 6dcf442b..31087ed7 100644
--- a/compensation/views/compensation.py
+++ b/compensation/views/compensation.py
@@ -1,4 +1,5 @@
from django.contrib.auth.decorators import login_required
+from django.core.exceptions import ObjectDoesNotExist
from django.db.models import Sum
from django.http import HttpRequest, JsonResponse
from django.shortcuts import render
@@ -22,7 +23,7 @@ from konova.utils.message_templates import FORM_INVALID, IDENTIFIER_REPLACED, DA
CHECKED_RECORDED_RESET, COMPENSATION_ADDED_TEMPLATE, COMPENSATION_REMOVED_TEMPLATE, DOCUMENT_ADDED, \
COMPENSATION_STATE_REMOVED, COMPENSATION_STATE_ADDED, COMPENSATION_ACTION_REMOVED, COMPENSATION_ACTION_ADDED, \
DEADLINE_ADDED, DEADLINE_REMOVED, DOCUMENT_EDITED, COMPENSATION_STATE_EDITED, COMPENSATION_ACTION_EDITED, \
- DEADLINE_EDITED
+ DEADLINE_EDITED, RECORDED_BLOCKS_EDIT, PARAMS_INVALID, DATA_CHECKED_PREVIOUSLY_TEMPLATE
from konova.utils.user_checks import in_group
@@ -69,6 +70,19 @@ def new_view(request: HttpRequest, intervention_id: str = None):
"""
template = "compensation/form/view.html"
+ if intervention_id is not None:
+ try:
+ intervention = Intervention.objects.get(id=intervention_id)
+ except ObjectDoesNotExist:
+ messages.error(request, PARAMS_INVALID)
+ return redirect("home")
+ if intervention.is_recorded:
+ messages.info(
+ request,
+ RECORDED_BLOCKS_EDIT
+ )
+ return redirect("intervention:detail", id=intervention_id)
+
data_form = NewCompensationForm(request.POST or None, intervention_id=intervention_id)
geom_form = SimpleGeomForm(request.POST or None, read_only=False)
if request.method == "POST":
@@ -134,6 +148,13 @@ def edit_view(request: HttpRequest, id: str):
template = "compensation/form/view.html"
# Get object from db
comp = get_object_or_404(Compensation, id=id)
+ if comp.is_recorded:
+ messages.info(
+ request,
+ RECORDED_BLOCKS_EDIT
+ )
+ return redirect("compensation:detail", id=id)
+
# Create forms, initialize with values from db/from POST request
data_form = EditCompensationForm(request.POST or None, instance=comp)
geom_form = SimpleGeomForm(request.POST or None, read_only=False, instance=comp)
@@ -196,8 +217,15 @@ def detail_view(request: HttpRequest, id: str):
request = comp.set_status_messages(request)
+ last_checked = comp.intervention.get_last_checked_action()
+ last_checked_tooltip = ""
+ if last_checked:
+ last_checked_tooltip = DATA_CHECKED_PREVIOUSLY_TEMPLATE.format(last_checked.get_timestamp_str_formatted(), last_checked.user)
+
context = {
"obj": comp,
+ "last_checked": last_checked,
+ "last_checked_tooltip": last_checked_tooltip,
"geom_form": geom_form,
"parcels": parcels,
"has_access": is_data_shared,
@@ -596,14 +624,12 @@ def report_view(request: HttpRequest, id: str):
instance=comp
)
parcels = comp.get_underlying_parcels()
- qrcode_img = generate_qr_code(
- request.build_absolute_uri(reverse("compensation:report", args=(id,))),
- 10
- )
- qrcode_img_lanis = generate_qr_code(
- comp.get_LANIS_link(),
- 7
- )
+
+ qrcode_url = request.build_absolute_uri(reverse("compensation:report", args=(id,)))
+ qrcode_img = generate_qr_code(qrcode_url, 10)
+ qrcode_lanis_url = comp.get_LANIS_link()
+ qrcode_img_lanis = generate_qr_code(qrcode_lanis_url, 7)
+
# Order states by surface
before_states = comp.before_states.all().order_by("-surface").prefetch_related("biotope_type")
after_states = comp.after_states.all().order_by("-surface").prefetch_related("biotope_type")
@@ -611,8 +637,14 @@ def report_view(request: HttpRequest, id: str):
context = {
"obj": comp,
- "qrcode": qrcode_img,
- "qrcode_lanis": qrcode_img_lanis,
+ "qrcode": {
+ "img": qrcode_img,
+ "url": qrcode_url,
+ },
+ "qrcode_lanis": {
+ "img": qrcode_img_lanis,
+ "url": qrcode_lanis_url,
+ },
"has_access": False, # disables action buttons during rendering
"before_states": before_states,
"after_states": after_states,
diff --git a/compensation/views/eco_account.py b/compensation/views/eco_account.py
index 85b13714..ecaccbe6 100644
--- a/compensation/views/eco_account.py
+++ b/compensation/views/eco_account.py
@@ -35,7 +35,8 @@ from konova.utils.generators import generate_qr_code
from konova.utils.message_templates import IDENTIFIER_REPLACED, FORM_INVALID, DATA_UNSHARED, DATA_UNSHARED_EXPLANATION, \
CANCEL_ACC_RECORDED_OR_DEDUCTED, DEDUCTION_REMOVED, DEDUCTION_ADDED, DOCUMENT_ADDED, COMPENSATION_STATE_REMOVED, \
COMPENSATION_STATE_ADDED, COMPENSATION_ACTION_REMOVED, COMPENSATION_ACTION_ADDED, DEADLINE_ADDED, DEADLINE_REMOVED, \
- DEDUCTION_EDITED, DOCUMENT_EDITED, COMPENSATION_STATE_EDITED, COMPENSATION_ACTION_EDITED, DEADLINE_EDITED
+ DEDUCTION_EDITED, DOCUMENT_EDITED, COMPENSATION_STATE_EDITED, COMPENSATION_ACTION_EDITED, DEADLINE_EDITED, \
+ RECORDED_BLOCKS_EDIT
from konova.utils.user_checks import in_group
@@ -145,6 +146,13 @@ def edit_view(request: HttpRequest, id: str):
template = "compensation/form/view.html"
# Get object from db
acc = get_object_or_404(EcoAccount, id=id)
+ if acc.is_recorded:
+ messages.info(
+ request,
+ RECORDED_BLOCKS_EDIT
+ )
+ return redirect("compensation:acc:detail", id=id)
+
# Create forms, initialize with values from db/from POST request
data_form = EditEcoAccountForm(request.POST or None, instance=acc)
geom_form = SimpleGeomForm(request.POST or None, read_only=False, instance=acc)
@@ -731,18 +739,16 @@ def report_view(request:HttpRequest, id: str):
instance=acc
)
parcels = acc.get_underlying_parcels()
- qrcode_img = generate_qr_code(
- request.build_absolute_uri(reverse("ema:report", args=(id,))),
- 10
- )
- qrcode_img_lanis = generate_qr_code(
- acc.get_LANIS_link(),
- 7
- )
+
+ qrcode_url = request.build_absolute_uri(reverse("ema:report", args=(id,)))
+ qrcode_img = generate_qr_code(qrcode_url, 10)
+ qrcode_lanis_url = acc.get_LANIS_link()
+ qrcode_img_lanis = generate_qr_code(qrcode_lanis_url, 7)
+
# Order states by surface
before_states = acc.before_states.all().order_by("-surface").select_related("biotope_type__parent")
after_states = acc.after_states.all().order_by("-surface").select_related("biotope_type__parent")
- actions = acc.actions.all().select_related("action_type__parent")
+ actions = acc.actions.all().prefetch_related("action_type__parent")
# Reduce amount of db fetched data to the bare minimum we need in the template (deduction's intervention id and identifier)
deductions = acc.deductions.all()\
@@ -752,8 +758,14 @@ def report_view(request:HttpRequest, id: str):
context = {
"obj": acc,
- "qrcode": qrcode_img,
- "qrcode_lanis": qrcode_img_lanis,
+ "qrcode": {
+ "img": qrcode_img,
+ "url": qrcode_url,
+ },
+ "qrcode_lanis": {
+ "img": qrcode_img_lanis,
+ "url": qrcode_lanis_url,
+ },
"has_access": False, # disables action buttons during rendering
"before_states": before_states,
"after_states": after_states,
diff --git a/ema/forms.py b/ema/forms.py
index 91b8de19..a7e82c4f 100644
--- a/ema/forms.py
+++ b/ema/forms.py
@@ -12,14 +12,15 @@ from django.db import transaction
from django.urls import reverse, reverse_lazy
from django.utils.translation import gettext_lazy as _
-from compensation.forms.forms import AbstractCompensationForm, CompensationResponsibleFormMixin
+from compensation.forms.forms import AbstractCompensationForm, CompensationResponsibleFormMixin, \
+ PikCompensationFormMixin
from ema.models import Ema, EmaDocument
from intervention.models import Responsibility, Handler
from konova.forms import SimpleGeomForm, NewDocumentModalForm
from user.models import UserActionLogEntry
-class NewEmaForm(AbstractCompensationForm, CompensationResponsibleFormMixin):
+class NewEmaForm(AbstractCompensationForm, CompensationResponsibleFormMixin, PikCompensationFormMixin):
""" Form for creating new EMA objects.
Inherits basic form fields from AbstractCompensationForm and additional from CompensationResponsibleFormMixin.
@@ -31,6 +32,7 @@ class NewEmaForm(AbstractCompensationForm, CompensationResponsibleFormMixin):
"title",
"conservation_office",
"conservation_file_number",
+ "is_pik",
"handler_type",
"handler_detail",
"comment",
@@ -58,6 +60,7 @@ class NewEmaForm(AbstractCompensationForm, CompensationResponsibleFormMixin):
handler_detail = self.cleaned_data.get("handler_detail", None)
conservation_office = self.cleaned_data.get("conservation_office", None)
conservation_file_number = self.cleaned_data.get("conservation_file_number", None)
+ is_pik = self.cleaned_data.get("is_pik", None)
comment = self.cleaned_data.get("comment", None)
# Create log entry
@@ -83,6 +86,7 @@ class NewEmaForm(AbstractCompensationForm, CompensationResponsibleFormMixin):
created=action,
geometry=geometry,
comment=comment,
+ is_pik=is_pik,
)
# Add the creating user to the list of shared users
@@ -116,6 +120,7 @@ class EditEmaForm(NewEmaForm):
"conservation_office": self.instance.responsible.conservation_office,
"conservation_file_number": self.instance.responsible.conservation_file_number,
"comment": self.instance.comment,
+ "is_pik": self.instance.is_pik,
}
disabled_fields = []
self.load_initial_data(
@@ -133,6 +138,7 @@ class EditEmaForm(NewEmaForm):
conservation_office = self.cleaned_data.get("conservation_office", None)
conservation_file_number = self.cleaned_data.get("conservation_file_number", None)
comment = self.cleaned_data.get("comment", None)
+ is_pik = self.cleaned_data.get("is_pik", None)
# Create log entry
action = UserActionLogEntry.get_edited_action(user)
@@ -152,6 +158,7 @@ class EditEmaForm(NewEmaForm):
self.instance.title = title
self.instance.geometry = geometry
self.instance.comment = comment
+ self.instance.is_pik = is_pik
self.instance.modified = action
self.instance.save()
diff --git a/ema/migrations/0004_ema_is_pik.py b/ema/migrations/0004_ema_is_pik.py
new file mode 100644
index 00000000..534155a7
--- /dev/null
+++ b/ema/migrations/0004_ema_is_pik.py
@@ -0,0 +1,18 @@
+# Generated by Django 3.1.3 on 2022-05-31 10:45
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('ema', '0003_ema_teams'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='ema',
+ name='is_pik',
+ field=models.BooleanField(blank=True, default=False, help_text="Flag if compensation is a 'Produktonsintegrierte Kompensation'", null=True),
+ ),
+ ]
diff --git a/ema/models/ema.py b/ema/models/ema.py
index 983bdbd7..abec7c43 100644
--- a/ema/models/ema.py
+++ b/ema/models/ema.py
@@ -13,15 +13,14 @@ from django.db.models import QuerySet
from django.http import HttpRequest
from django.urls import reverse
-from compensation.models import AbstractCompensation
+from compensation.models import AbstractCompensation, PikMixin
from ema.managers import EmaManager
from ema.utils.quality import EmaQualityChecker
from konova.models import AbstractDocument, generate_document_file_upload_path, RecordableObjectMixin, ShareableObjectMixin
-from konova.settings import DEFAULT_SRID_RLP, LANIS_LINK_TEMPLATE
from konova.utils.message_templates import DATA_UNSHARED_EXPLANATION, DOCUMENT_REMOVED_TEMPLATE
-class Ema(AbstractCompensation, ShareableObjectMixin, RecordableObjectMixin):
+class Ema(AbstractCompensation, ShareableObjectMixin, RecordableObjectMixin, PikMixin):
"""
EMA = Ersatzzahlungsmaßnahme
(compensation actions from payments)
diff --git a/ema/tables.py b/ema/tables.py
index f9689517..d26a31dc 100644
--- a/ema/tables.py
+++ b/ema/tables.py
@@ -104,7 +104,7 @@ class EmaTable(BaseTable, TableRenderMixin):
"""
parcels = value.get_underlying_parcels().values_list(
- "gmrkng",
+ "parcel_group__name",
flat=True
).distinct()
html = render_to_string(
@@ -115,7 +115,6 @@ class EmaTable(BaseTable, TableRenderMixin):
)
return html
-
def render_r(self, value, record: Ema):
""" Renders the registered column for a EMA
@@ -130,9 +129,7 @@ class EmaTable(BaseTable, TableRenderMixin):
recorded = value is not None
tooltip = _("Not recorded yet")
if recorded:
- value = value.timestamp
- value = localtime(value)
- on = value.strftime(DEFAULT_DATE_TIME_FORMAT)
+ on = value.get_timestamp_str_formatted()
tooltip = _("Recorded on {} by {}").format(on, record.recorded.user)
html += self.render_bookmark(
tooltip=tooltip,
@@ -151,9 +148,7 @@ class EmaTable(BaseTable, TableRenderMixin):
"""
html = ""
- has_access = value.filter(
- id=self.user.id
- ).exists()
+ has_access = record.is_shared_with(self.user)
html += self.render_icn(
tooltip=_("Full access granted") if has_access else _("Access not granted"),
diff --git a/ema/templates/ema/detail/view.html b/ema/templates/ema/detail/view.html
index 7b567038..16f31378 100644
--- a/ema/templates/ema/detail/view.html
+++ b/ema/templates/ema/detail/view.html
@@ -56,29 +56,38 @@
| {% trans 'Action handler' %} |
{{obj.responsible.handler|default_if_none:""}} |
+
+ {% trans 'Is PIK' %} |
+
+ {% if obj.is_pik %}
+ {% trans 'Yes' %}
+ {% else %}
+ {% trans 'No' %}
+ {% endif %}
+ |
+
{% trans 'Last modified' %} |
{% if obj.modified %}
- {{obj.modified.timestamp|default_if_none:""|naturalday}}
+ {{obj.modified.timestamp|default_if_none:""}}
{{obj.modified.user.username}}
{% else %}
- {{obj.created.timestamp|default_if_none:""|naturalday}}
+ {{obj.created.timestamp|default_if_none:""}}
{{obj.created.user.username}}
-
{% endif %}
|
{% trans 'Shared with' %} |
- {% for team in obj.teams.all %}
+ {% for team in obj.shared_teams %}
{% include 'user/includes/team_data_modal_button.html' %}
{% endfor %}
- {% for user in obj.users.all %}
+ {% for user in obj.user.all %}
{% include 'user/includes/contact_modal_button.html' %}
{% endfor %}
|
@@ -88,10 +97,12 @@
- {% include 'map/geom_form.html' %}
+
+ {% include 'map/geom_form.html' %}
+
- {% include 'konova/includes/parcels.html' %}
+ {% include 'konova/includes/parcels/parcels.html' %}
{% include 'konova/includes/comment_card.html' %}
diff --git a/ema/templates/ema/report/report.html b/ema/templates/ema/report/report.html
index 86252c0a..fd166596 100644
--- a/ema/templates/ema/report/report.html
+++ b/ema/templates/ema/report/report.html
@@ -20,6 +20,16 @@
{% trans 'Conservation office file number' %} |
{{obj.responsible.conservation_file_number|default_if_none:""}} |
+
+ {% trans 'Is PIK' %} |
+
+ {% if obj.is_pik %}
+ {% trans 'Yes' %}
+ {% else %}
+ {% trans 'No' %}
+ {% endif %}
+ |
+
{% trans 'Last modified' %} |
@@ -35,20 +45,15 @@
- {% include 'map/geom_form.html' %}
+
+ {% include 'map/geom_form.html' %}
+
- {% include 'konova/includes/parcels.html' %}
+ {% include 'konova/includes/parcels/parcels.html' %}
-
- {% trans 'Open in browser' %}
- {{ qrcode|safe }}
-
-
- {% trans 'View in LANIS' %}
- {{ qrcode_lanis|safe }}
-
+ {% include 'konova/includes/report/qrcodes.html' %}
diff --git a/ema/tests/test_workflow.py b/ema/tests/test_workflow.py
index 3306a21f..4391fdc6 100644
--- a/ema/tests/test_workflow.py
+++ b/ema/tests/test_workflow.py
@@ -41,11 +41,12 @@ class EmaWorkflowTestCase(BaseWorkflowTestCase):
test_id = self.create_dummy_string()
test_title = self.create_dummy_string()
test_geom = self.create_dummy_geometry()
+ geom_json = self.create_geojson(test_geom)
test_conservation_office = self.get_conservation_office_code()
post_data = {
"identifier": test_id,
"title": test_title,
- "geom": test_geom.geojson,
+ "geom": geom_json,
"conservation_office": test_conservation_office.id
}
self.client_user.post(new_url, post_data)
@@ -117,6 +118,32 @@ class EmaWorkflowTestCase(BaseWorkflowTestCase):
self.assertEqual(pre_edit_log_count + 1, self.ema.log.count())
self.assertEqual(self.ema.log.first().action, UserAction.EDITED)
+ def test_non_editable_after_recording(self):
+ """ Tests that the EMA can not be edited after being recorded
+
+ User must be redirected to another page
+
+ Returns:
+
+ """
+ self.superuser.groups.add(self.groups.get(name=ETS_GROUP))
+ self.assertIsNotNone(self.ema)
+ self.ema.share_with_user(self.superuser)
+ self.assertFalse(self.ema.is_recorded)
+ edit_url = reverse("ema:edit", args=(self.ema.id,))
+ response = self.client_user.get(edit_url)
+ has_redirect = response.status_code == 302
+ self.assertFalse(has_redirect)
+
+ self.ema.set_recorded(self.superuser)
+ self.assertTrue(self.ema.is_recorded)
+
+ edit_url = reverse("ema:edit", args=(self.ema.id,))
+ response = self.client_user.get(edit_url)
+ has_redirect = response.status_code == 302
+ self.assertTrue(has_redirect)
+ self.ema.set_unrecorded(self.superuser)
+
def test_recordability(self):
"""
This tests if the recordability of the Ema is triggered by the quality of it's data (e.g. not all fields filled)
diff --git a/ema/views.py b/ema/views.py
index c145511f..ce0d68f5 100644
--- a/ema/views.py
+++ b/ema/views.py
@@ -26,7 +26,7 @@ from konova.utils.generators import generate_qr_code
from konova.utils.message_templates import IDENTIFIER_REPLACED, FORM_INVALID, DATA_UNSHARED, DATA_UNSHARED_EXPLANATION, \
DOCUMENT_ADDED, COMPENSATION_STATE_REMOVED, COMPENSATION_STATE_ADDED, COMPENSATION_ACTION_REMOVED, \
COMPENSATION_ACTION_ADDED, DEADLINE_ADDED, DEADLINE_REMOVED, DOCUMENT_EDITED, COMPENSATION_STATE_EDITED, \
- COMPENSATION_ACTION_EDITED, DEADLINE_EDITED
+ COMPENSATION_ACTION_EDITED, DEADLINE_EDITED, RECORDED_BLOCKS_EDIT
from konova.utils.user_checks import in_group
@@ -213,6 +213,13 @@ def edit_view(request: HttpRequest, id: str):
template = "compensation/form/view.html"
# Get object from db
ema = get_object_or_404(Ema, id=id)
+ if ema.is_recorded:
+ messages.info(
+ request,
+ RECORDED_BLOCKS_EDIT
+ )
+ return redirect("ema:detail", id=id)
+
# Create forms, initialize with values from db/from POST request
data_form = EditEmaForm(request.POST or None, instance=ema)
geom_form = SimpleGeomForm(request.POST or None, read_only=False, instance=ema)
@@ -563,14 +570,12 @@ def report_view(request:HttpRequest, id: str):
instance=ema,
)
parcels = ema.get_underlying_parcels()
- qrcode_img = generate_qr_code(
- request.build_absolute_uri(reverse("ema:report", args=(id,))),
- 10
- )
- qrcode_img_lanis = generate_qr_code(
- ema.get_LANIS_link(),
- 7
- )
+
+ qrcode_url = request.build_absolute_uri(reverse("ema:report", args=(id,)))
+ qrcode_img = generate_qr_code(qrcode_url, 10)
+ qrcode_lanis_url = ema.get_LANIS_link()
+ qrcode_img_lanis = generate_qr_code(qrcode_lanis_url, 7)
+
# Order states by surface
before_states = ema.before_states.all().order_by("-surface").prefetch_related("biotope_type")
after_states = ema.after_states.all().order_by("-surface").prefetch_related("biotope_type")
@@ -578,8 +583,14 @@ def report_view(request:HttpRequest, id: str):
context = {
"obj": ema,
- "qrcode": qrcode_img,
- "qrcode_lanis": qrcode_img_lanis,
+ "qrcode": {
+ "img": qrcode_img,
+ "url": qrcode_url
+ },
+ "qrcode_lanis": {
+ "img": qrcode_img_lanis,
+ "url": qrcode_lanis_url
+ },
"has_access": False, # disables action buttons during rendering
"before_states": before_states,
"after_states": after_states,
diff --git a/intervention/admin.py b/intervention/admin.py
index 3d874df6..932ddb93 100644
--- a/intervention/admin.py
+++ b/intervention/admin.py
@@ -25,12 +25,14 @@ class InterventionAdmin(BaseObjectAdmin):
"checked",
"recorded",
"users",
+ "geometry",
]
def get_readonly_fields(self, request, obj=None):
return super().get_readonly_fields(request, obj) + [
"checked",
"recorded",
+ "geometry",
]
diff --git a/intervention/forms/forms.py b/intervention/forms/forms.py
index b83c4fba..b85ba101 100644
--- a/intervention/forms/forms.py
+++ b/intervention/forms/forms.py
@@ -216,6 +216,10 @@ class NewInterventionForm(BaseForm):
identifier = tmp_intervention.generate_new_identifier()
self.initialize_form_field("identifier", identifier)
+ def is_valid(self):
+ super_valid_result = super().is_valid()
+ return super_valid_result
+
def save(self, user: User, geom_form: SimpleGeomForm):
with transaction.atomic():
# Fetch data from cleaned POST values
diff --git a/intervention/forms/modalForms.py b/intervention/forms/modalForms.py
index 6a02962e..07911be9 100644
--- a/intervention/forms/modalForms.py
+++ b/intervention/forms/modalForms.py
@@ -427,13 +427,22 @@ class NewDeductionModalForm(BaseModalForm):
"""
super_result = super().is_valid()
acc = self.cleaned_data["account"]
+ intervention = self.cleaned_data["intervention"]
+ objects_valid = True
if not acc.recorded:
self.add_error(
"account",
_("Eco-account {} is not recorded yet. You can only deduct from recorded accounts.").format(acc.identifier)
)
- return False
+ objects_valid = False
+
+ if intervention.is_recorded:
+ self.add_error(
+ "intervention",
+ _("Intervention {} is currently recorded. To change any data on it, the entry must be unrecorded.").format(intervention.identifier)
+ )
+ objects_valid = False
rest_surface = self._get_available_surface(acc)
form_surface = float(self.cleaned_data["surface"])
@@ -447,7 +456,7 @@ class NewDeductionModalForm(BaseModalForm):
format_german_float(rest_surface),
),
)
- return is_valid_surface and super_result
+ return is_valid_surface and objects_valid and super_result
def __create_deduction(self):
""" Creates the deduction
diff --git a/intervention/inputs.py b/intervention/inputs.py
index 34fe0434..a08c0b74 100644
--- a/intervention/inputs.py
+++ b/intervention/inputs.py
@@ -1,6 +1,6 @@
from django import forms
from codelist.models import KonovaCode
-from codelist.settings import CODELIST_COMPENSATION_ACTION_ID
+from codelist.settings import CODELIST_COMPENSATION_ACTION_ID, CODELIST_BIOTOPES_ID
class DummyFilterInput(forms.HiddenInput):
@@ -38,7 +38,17 @@ class TreeCheckboxSelectMultiple(forms.CheckboxSelectMultiple):
""" Provides multiple selection of parent-child data
"""
- template_name = "konova/widgets/checkbox-tree-select.html"
+ template_name = "konova/widgets/tree/checkbox/checkbox-tree-select.html"
+
+ class meta:
+ abstract = True
+
+
+class TreeRadioSelect(forms.RadioSelect):
+ """ Provides single selection of parent-child data
+
+ """
+ template_name = "konova/widgets/tree/radio/radio-tree-select.html"
class meta:
abstract = True
@@ -68,6 +78,30 @@ class KonovaCodeTreeCheckboxSelectMultiple(TreeCheckboxSelectMultiple):
return context
+class KonovaCodeTreeRadioSelect(TreeRadioSelect):
+ """ Provides single selection of KonovaCode
+
+ """
+ filter = None
+
+ def __init__(self, *args, **kwargs):
+ self.code_list = kwargs.pop("code_list", None)
+ self.filter = kwargs.pop("filter", {})
+ super().__init__(*args, **kwargs)
+
+ def get_context(self, name, value, attrs):
+ context = super().get_context(name, value, attrs)
+ codes = KonovaCode.objects.filter(
+ **self.filter,
+ )
+ codes = [
+ parent_code.add_children()
+ for parent_code in codes
+ ]
+ context["codes"] = codes
+ return context
+
+
class CompensationActionTreeCheckboxSelectMultiple(KonovaCodeTreeCheckboxSelectMultiple):
""" Provides multiple selection of CompensationActions
@@ -79,4 +113,31 @@ class CompensationActionTreeCheckboxSelectMultiple(KonovaCodeTreeCheckboxSelectM
self.filter = {
"code_lists__in": [CODELIST_COMPENSATION_ACTION_ID],
"parent": None,
- }
\ No newline at end of file
+ }
+
+
+class CompensationStateTreeRadioSelect(KonovaCodeTreeRadioSelect):
+ """ Provides single selection of CompensationState
+
+ """
+ filter = None
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.filter = {
+ "code_lists__in": [CODELIST_BIOTOPES_ID],
+ "parent": None,
+ "is_archived": False,
+ }
+
+ def get_context(self, name, value, attrs):
+ context = super().get_context(name, value, attrs)
+ codes = KonovaCode.objects.filter(
+ **self.filter,
+ )
+ codes = [
+ parent_code.add_children("short_name")
+ for parent_code in codes
+ ]
+ context["codes"] = codes
+ return context
\ No newline at end of file
diff --git a/intervention/models/intervention.py b/intervention/models/intervention.py
index 167c27af..dd15beb1 100644
--- a/intervention/models/intervention.py
+++ b/intervention/models/intervention.py
@@ -13,6 +13,7 @@ from django.db.models.fields.files import FieldFile
from django.urls import reverse
from django.utils import timezone
+from intervention.tasks import celery_export_to_egon
from user.models import User
from django.db import models, transaction
from django.db.models import QuerySet
@@ -131,6 +132,16 @@ class Intervention(BaseObject, ShareableObjectMixin, RecordableObjectMixin, Chec
self.add_log_entry_to_compensations(log_entry)
return log_entry
+ def send_data_to_egon(self):
+ """ Performs the export to rabbitmq of this intervention's data
+
+ FOLLOWING BACKWARDS COMPATIBILITY LOGIC
+
+ Returns:
+
+ """
+ celery_export_to_egon.delay(self.id)
+
def set_recorded(self, user: User) -> UserActionLogEntry:
log_entry = super().set_recorded(user)
self.add_log_entry_to_compensations(log_entry)
@@ -171,6 +182,8 @@ class Intervention(BaseObject, ShareableObjectMixin, RecordableObjectMixin, Chec
intervention=self,
)
self.mark_as_edited(user, form.request, edit_comment=PAYMENT_ADDED)
+
+ self.send_data_to_egon()
return pay
def add_revocation(self, form):
@@ -335,6 +348,7 @@ class Intervention(BaseObject, ShareableObjectMixin, RecordableObjectMixin, Chec
with transaction.atomic():
payment.delete()
self.mark_as_edited(user, request=form.request, edit_comment=PAYMENT_REMOVED)
+ self.send_data_to_egon()
class InterventionDocument(AbstractDocument):
diff --git a/intervention/settings.py b/intervention/settings.py
index 2b6ebee8..a5c974d4 100644
--- a/intervention/settings.py
+++ b/intervention/settings.py
@@ -6,4 +6,11 @@ Created on: 30.11.20
"""
INTERVENTION_IDENTIFIER_LENGTH = 6
-INTERVENTION_IDENTIFIER_TEMPLATE = "EIV-{}"
\ No newline at end of file
+INTERVENTION_IDENTIFIER_TEMPLATE = "EIV-{}"
+
+# EGON connection settings via rabbitmq
+# NEEDED FOR BACKWARDS COMPATIBILITY
+EGON_RABBITMQ_HOST = "CHANGE_ME"
+EGON_RABBITMQ_PORT = "CHANGE_ME"
+EGON_RABBITMQ_USER = "CHANGE_ME"
+EGON_RABBITMQ_PW = "CHANGE_ME"
diff --git a/intervention/tables.py b/intervention/tables.py
index f535039d..cff9391c 100644
--- a/intervention/tables.py
+++ b/intervention/tables.py
@@ -9,12 +9,11 @@ from django.http import HttpRequest
from django.template.loader import render_to_string
from django.urls import reverse
from django.utils.html import format_html
-from django.utils.timezone import localtime
from django.utils.translation import gettext_lazy as _
from intervention.filters import InterventionTableFilter
from intervention.models import Intervention
-from konova.sub_settings.django_settings import DEFAULT_DATE_TIME_FORMAT, DEFAULT_DATE_FORMAT
+from konova.utils.message_templates import DATA_CHECKED_ON_TEMPLATE, DATA_IS_UNCHECKED, DATA_CHECKED_PREVIOUSLY_TEMPLATE
from konova.utils.tables import BaseTable, TableRenderMixin
import django_tables2 as tables
@@ -108,16 +107,21 @@ class InterventionTable(BaseTable, TableRenderMixin):
"""
html = ""
checked = value is not None
- tooltip = _("Not checked yet")
+ previously_checked = record.get_last_checked_action()
+ tooltip = DATA_IS_UNCHECKED
if checked:
- value = value.timestamp
- value = localtime(value)
- checked_on = value.strftime(DEFAULT_DATE_TIME_FORMAT)
- tooltip = _("Checked on {} by {}").format(checked_on, record.checked.user)
+ checked_on = value.get_timestamp_str_formatted()
+ tooltip = DATA_CHECKED_ON_TEMPLATE.format(checked_on, record.checked.user)
html += self.render_checked_star(
tooltip=tooltip,
icn_filled=checked,
)
+ if previously_checked and not checked:
+ checked_on = previously_checked.get_timestamp_str_formatted()
+ tooltip = DATA_CHECKED_PREVIOUSLY_TEMPLATE.format(checked_on, previously_checked.user)
+ html += self.render_previously_checked_star(
+ tooltip=tooltip,
+ )
return format_html(html)
def render_d(self, value, record: Intervention):
@@ -131,7 +135,7 @@ class InterventionTable(BaseTable, TableRenderMixin):
"""
parcels = value.get_underlying_parcels().values_list(
- "gmrkng",
+ "parcel_group__name",
flat=True
).distinct()
html = render_to_string(
@@ -156,9 +160,7 @@ class InterventionTable(BaseTable, TableRenderMixin):
checked = value is not None
tooltip = _("Not recorded yet")
if checked:
- value = value.timestamp
- value = localtime(value)
- on = value.strftime(DEFAULT_DATE_TIME_FORMAT)
+ on = value.get_timestamp_str_formatted()
tooltip = _("Recorded on {} by {}").format(on, record.recorded.user)
html += self.render_bookmark(
tooltip=tooltip,
@@ -177,9 +179,7 @@ class InterventionTable(BaseTable, TableRenderMixin):
"""
html = ""
- has_access = value.filter(
- id=self.user.id
- ).exists()
+ has_access = record.is_shared_with(self.user)
html += self.render_icn(
tooltip=_("Full access granted") if has_access else _("Access not granted"),
diff --git a/intervention/tasks.py b/intervention/tasks.py
new file mode 100644
index 00000000..3488e189
--- /dev/null
+++ b/intervention/tasks.py
@@ -0,0 +1,18 @@
+"""
+Author: Michel Peltriaux
+Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
+Contact: michel.peltriaux@sgdnord.rlp.de
+Created on: 21.03.22
+
+"""
+from celery import shared_task
+
+from intervention.utils.egon_export import EgonExporter
+
+
+@shared_task
+def celery_export_to_egon(intervention_id: str):
+ from intervention.models import Intervention
+ intervention = Intervention.objects.get(id=intervention_id)
+ egon_exporter = EgonExporter(intervention)
+ egon_exporter.export_to_rabbitmq()
diff --git a/intervention/templates/intervention/detail/view.html b/intervention/templates/intervention/detail/view.html
index f5680cc3..1a596bb0 100644
--- a/intervention/templates/intervention/detail/view.html
+++ b/intervention/templates/intervention/detail/view.html
@@ -1,5 +1,5 @@
{% extends 'base.html' %}
-{% load i18n l10n static fontawesome_5 humanize %}
+{% load i18n l10n static fontawesome_5 %}
{% block head %}
{% comment %}
@@ -70,6 +70,11 @@
{% fa5_icon 'star' 'far' %}
+ {% if last_checked %}
+
+ {% fa5_icon 'star' 'fas' %}
+
+ {% endif %}
{% else %}
{% fa5_icon 'star' %}
@@ -106,15 +111,21 @@
{% trans 'Last modified' %} |
- {{obj.created.timestamp|default_if_none:""|naturalday}}
-
- {{obj.created.user.username}}
+ {% if obj.modified %}
+ {{obj.modified.timestamp|default_if_none:""}}
+
+ {{obj.modified.user.username}}
+ {% else %}
+ {{obj.created.timestamp|default_if_none:""}}
+
+ {{obj.created.user.username}}
+ {% endif %}
|
{% trans 'Shared with' %} |
- {% for team in obj.teams.all %}
+ {% for team in obj.shared_teams %}
{% include 'user/includes/team_data_modal_button.html' %}
{% endfor %}
@@ -128,10 +139,12 @@
- {% include 'map/geom_form.html' %}
+
+ {% include 'map/geom_form.html' %}
+
- {% include 'konova/includes/parcels.html' %}
+ {% include 'konova/includes/parcels/parcels.html' %}
{% include 'konova/includes/comment_card.html' %}
diff --git a/intervention/templates/intervention/report/report.html b/intervention/templates/intervention/report/report.html
index ccc2f226..1c1016d0 100644
--- a/intervention/templates/intervention/report/report.html
+++ b/intervention/templates/intervention/report/report.html
@@ -94,20 +94,15 @@
- {% include 'map/geom_form.html' %}
+
+ {% include 'map/geom_form.html' %}
+
- {% include 'konova/includes/parcels.html' %}
+ {% include 'konova/includes/parcels/parcels.html' %}
-
- {% trans 'Open in browser' %}
- {{ qrcode|safe }}
-
-
- {% trans 'View in LANIS' %}
- {{ qrcode_lanis|safe }}
-
+ {% include 'konova/includes/report/qrcodes.html' %}
diff --git a/intervention/tests/test_workflow.py b/intervention/tests/test_workflow.py
index c5290503..9124c3e8 100644
--- a/intervention/tests/test_workflow.py
+++ b/intervention/tests/test_workflow.py
@@ -46,6 +46,7 @@ class InterventionWorkflowTestCase(BaseWorkflowTestCase):
test_id = self.create_dummy_string()
test_title = self.create_dummy_string()
test_geom = self.create_dummy_geometry()
+ geom_json = self.create_geojson(test_geom)
new_url = reverse("intervention:new", args=())
@@ -59,7 +60,7 @@ class InterventionWorkflowTestCase(BaseWorkflowTestCase):
post_data = {
"identifier": test_id,
"title": test_title,
- "geom": test_geom.geojson,
+ "geom": geom_json,
}
response = self.client_user.post(
new_url,
@@ -89,6 +90,30 @@ class InterventionWorkflowTestCase(BaseWorkflowTestCase):
self.assertIn(self.superuser, obj.users.all())
self.assertEqual(1, obj.users.count())
+ def test_non_editable_after_recording(self):
+ """ Tests that the intervention can not be edited after being recorded
+
+ User must be redirected to another page
+
+ Returns:
+
+ """
+ self.assertIsNotNone(self.intervention)
+ self.assertFalse(self.intervention.is_recorded)
+ edit_url = reverse("intervention:edit", args=(self.intervention.id,))
+ response = self.client_user.get(edit_url)
+ has_redirect = response.status_code == 302
+ self.assertFalse(has_redirect)
+
+ self.intervention.set_recorded(self.user)
+ self.assertTrue(self.intervention.is_recorded)
+
+ edit_url = reverse("intervention:edit", args=(self.intervention.id,))
+ response = self.client_user.get(edit_url)
+ has_redirect = response.status_code == 302
+ self.assertTrue(has_redirect)
+ self.intervention.set_unrecorded(self.user)
+
def test_checkability(self):
""" Tests that the intervention can only be checked if all required data has been added
diff --git a/intervention/utils/egon_export.py b/intervention/utils/egon_export.py
new file mode 100644
index 00000000..14ea6b61
--- /dev/null
+++ b/intervention/utils/egon_export.py
@@ -0,0 +1,250 @@
+"""
+Author: Michel Peltriaux
+Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
+Contact: michel.peltriaux@sgdnord.rlp.de
+Created on: 07.03.22
+
+"""
+import base64
+import json
+
+import pika
+import xmltodict
+from django.db.models import Sum
+
+from intervention.settings import EGON_RABBITMQ_HOST, EGON_RABBITMQ_USER, EGON_RABBITMQ_PW, EGON_RABBITMQ_PORT
+from konova.sub_settings.django_settings import DEFAULT_DATE_FORMAT
+
+from konova.sub_settings.lanis_settings import DEFAULT_SRID_RLP
+
+
+class EgonExporter:
+ """
+ EGON is the payment management system of SNU RLP. Due to compatibility reasons we need to provide the old style
+ of data transmission between KSP and EGON:
+ 1. Create GML from intervention object
+ 2. Send created GML to the appropriate RabbitMQ channel
+ """
+ intervention = None
+ gml_builder = None
+
+ def __init__(self, intervention):
+ self.intervention = intervention
+ self.gml_builder = EgonGmlBuilder(intervention)
+
+ def export_to_rabbitmq(self):
+ """ Sends the exporter gml to message broker rabbitmq to be fetched by EGON application from there
+
+ Returns:
+
+ """
+ msg = {
+ "nachricht": self.gml_builder.gml,
+ }
+ msg = json.dumps(msg)
+ print(msg)
+ credentials = pika.PlainCredentials(EGON_RABBITMQ_USER, EGON_RABBITMQ_PW)
+ params = pika.ConnectionParameters(
+ EGON_RABBITMQ_HOST,
+ EGON_RABBITMQ_PORT,
+ "/",
+ credentials
+ )
+ conn = pika.BlockingConnection(params)
+ channel = conn.channel()
+ channel.basic_publish(
+ exchange="",
+ routing_key="KSP_EGON",
+ body=msg.encode("utf-8"),
+ )
+ conn.close()
+
+
+class EgonGmlBuilder:
+ """
+ Creates the GML for EGON export
+ """
+ intervention = None
+ gml = None
+
+ def __init__(self, intervention):
+ self.intervention = intervention
+ self.gml = self.build_gml()
+
+ def _gen_flurstuecksKennzeichen(self, parcel):
+ """ Generates oneo:flurstuecksKennzeichen to provide backwards compatibility
+
+ Args:
+ parcel (Parcel): The requested parcel
+
+ Returns:
+ str
+ """
+ gmrkng_code = "{0:06d}".format(int(parcel.parcel_group.key) or 0)
+ flr_code = "{0:03d}".format(int(parcel.flr or 0))
+ flrstckzhlr_code = "{0:05d}".format(int(parcel.flrstck_zhlr or 0))
+ flrstcknnr_code = "{0:06d}".format(int(parcel.flrstck_nnr or 0))
+ return gmrkng_code + flr_code + flrstckzhlr_code + flrstcknnr_code
+
+ def _sum_all_payments(self):
+ all_payments = self.intervention.payments.aggregate(
+ summed=Sum("amount")
+ )["summed"]
+ return all_payments
+
+ def _gen_kompensationsArt(self) -> (str, int):
+ comp_type = "Ersatzzahlung"
+ comp_type_code = 774898901
+ if self.intervention.compensations.exists():
+ comp_type += " und Kompensation"
+ comp_type_code = 771655351
+ return comp_type, comp_type_code
+
+ def _gen_geometry_list(self):
+ geom = self.intervention.geometry.geom
+ geom.transform(DEFAULT_SRID_RLP)
+ geoms_list = [
+ {
+ "gml:Polygon": {
+ "gml:exterior": {
+ "gml:LinearRing": {
+ "gml:posList": " ".join([f"{str(coord[0])} {str(coord[1])}" for coord in coords[0]])
+ }
+ }
+ }
+ } for coords in geom.coords
+ ]
+ return geoms_list
+
+ def _gen_raumreferenz(self):
+ parcels = self.intervention.get_underlying_parcels()
+ spatial_reference_list = [
+ {
+ "oneo:datumAbgleich": None,
+ "oneo:ortsangabe": {
+ "oneo:Ortsangaben": {
+ "oneo:kreisSchluessel": {
+ "xlink:href": f"http://register.naturschutz.rlp.de/repository/services/referenzliste/588/{parcel.district.key}",
+ },
+ "oneo:gemeindeSchluessel": {
+ "xlink:href": f"http://register.naturschutz.rlp.de/repository/services/referenzliste/910/{parcel.municipal.key}",
+ },
+ "oneo:verbandsgemeindeSchluessel": {
+ "xlink:href": f"http://register.naturschutz.rlp.de/repository/services/referenzliste/589/{None}",
+ },
+ "oneo:flurstuecksKennzeichen": self._gen_flurstuecksKennzeichen(parcel),
+ }
+ },
+ } for parcel in parcels
+ ]
+ return spatial_reference_list
+
+ def _gen_foto(self):
+ revoc_docs, regular_docs = self.intervention.get_documents()
+ docs_list = [
+ {
+ "oneo:Foto": {
+ "oneo:aufnahmezeitpunkt": doc.date_of_creation.strftime(DEFAULT_DATE_FORMAT),
+ "oneo:bemerkung": doc.comment,
+ "oneo:fotoverweis": base64.b64encode(doc.file.read()).decode("utf-8"),
+ "oneo:dateiname": doc.title,
+ "oneo:hauptfoto": False,
+ }
+ } for doc in regular_docs
+ ]
+ return docs_list
+
+ def build_gml(self):
+ comp_type, comp_type_code = self._gen_kompensationsArt()
+ payment = self.intervention.payments.first()
+ payment_date = None
+ if payment is not None:
+ payment_date = payment.due_on
+ payment_date = payment_date.strftime(DEFAULT_DATE_FORMAT)
+
+ cons_office = self.intervention.responsible.conservation_office
+ reg_office = self.intervention.responsible.registration_office
+ law = self.intervention.legal.laws.first()
+ process_type = self.intervention.legal.process_type
+ handler = self.intervention.responsible.handler
+ reg_date = self.intervention.legal.registration_date
+ bind_date = self.intervention.legal.binding_date
+
+ xml_dict = {
+ "wfs:FeatureCollection": {
+ "@xmlns:wfs": "http://www.opengis.net/wfs",
+ "@xmlns:xlink": "http://www.w3.org/1999/xlink",
+ "@xmlns:oneo": "http://www.osiris-projekt.rlp.de/oneo",
+ "@xmlns:gmlexr": "http://www.opengis.net/gml/3.3/exr",
+ "@xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance",
+ "@xmlns:gml": "http://www.opengis.net/gml/3.2",
+ "oneo:Eingriffsverfahren": {
+ "@gml:id": self.intervention.identifier,
+ "oneo:azEintragungsstelle": self.intervention.responsible.conservation_file_number,
+ "oneo:azZulassungsstelle": self.intervention.responsible.registration_file_number,
+ "oneo:bemerkungZulassungsstelle": None,
+ "oneo:eintragungsstelle": {
+ "@xlink:href": f"http://register.naturschutz.rlp.de/repository/services/referenzliste/907/{cons_office.atom_id if cons_office else None}",
+ "#text": cons_office.long_name if cons_office else None
+ },
+ "oneo:zulassungsstelle": {
+ "@xlink:href": f"http://register.naturschutz.rlp.de/repository/services/referenzliste/1053/{reg_office.atom_id if reg_office else None}",
+ "#text": reg_office.long_name if reg_office else None
+ },
+ "oneo:ersatzzahlung": self._sum_all_payments(),
+ "oneo:kompensationsart": {
+ "@xlink:href": f"http://register.naturschutz.rlp.de/repository/services/referenzliste/88140/{comp_type_code}",
+ "#text": comp_type
+ },
+ "oneo:verfahrensrecht": {
+ "@xlink:href": f"http://register.naturschutz.rlp.de/repository/services/referenzliste/1048/{law.atom_id if law else None}",
+ "#text": law.short_name if law else None
+ },
+ "oneo:verfahrenstyp": {
+ "@xlink:href": f"http://register.naturschutz.rlp.de/repository/services/referenzliste/44382/{process_type.atom_id if process_type else None}",
+ "#text": process_type.long_name if process_type else None,
+ },
+ "oneo:eingreifer": {
+ "oneo:Eingreifer": {
+ "oneo:art": {
+ "@xlink:href": f"http://register.naturschutz.rlp.de/repository/services/referenzliste/1053/{handler.type.atom_id if handler.type else None}",
+ "#text": handler.type.long_name if handler.type else None,
+ },
+ "oneo:bemerkung": handler.detail if handler else None,
+ }
+ },
+ "oneo:erfasser": {
+ "oneo:Erfasser": {
+ "oneo:name": None,
+ "oneo:bemerkung": None,
+ }
+ },
+ "oneo:zulassung": {
+ "oneo:Zulassungstermin": {
+ "oneo:bauBeginn": payment_date,
+ "oneo:erlass": reg_date.strftime(DEFAULT_DATE_FORMAT) if reg_date else None,
+ "oneo:rechtsKraft": bind_date.strftime(DEFAULT_DATE_FORMAT) if bind_date else None,
+ }
+ },
+ "oneo:geometrie": {
+ "gml:multiSurfaceProperty": {
+ "gml:MultiPolygon": {
+ "@srsName": f"http://www.opengis.net/gml/srs/epsg.xml#{DEFAULT_SRID_RLP}",
+ "gml:polygonMember": self._gen_geometry_list(),
+ }
+ },
+ },
+ "oneo:kennung": self.intervention.identifier,
+ "oneo:bezeichnung": self.intervention.title,
+ "oneo:bemerkung": self.intervention.comment,
+ "oneo:verantwortlicheStelle": None,
+ "oneo:veroffentlichtAm": None,
+ "oneo:raumreferenz": {
+ "oneo:Raumreferenz": self._gen_raumreferenz(),
+ },
+ "oneo:foto": self._gen_foto(),
+ }
+ },
+ }
+ gml = xmltodict.unparse(xml_dict)
+ return gml
diff --git a/intervention/views.py b/intervention/views.py
index 3004a79f..36577202 100644
--- a/intervention/views.py
+++ b/intervention/views.py
@@ -18,7 +18,8 @@ from konova.utils.documents import remove_document, get_document
from konova.utils.generators import generate_qr_code
from konova.utils.message_templates import INTERVENTION_INVALID, FORM_INVALID, IDENTIFIER_REPLACED, \
CHECKED_RECORDED_RESET, DEDUCTION_REMOVED, DEDUCTION_ADDED, REVOCATION_ADDED, REVOCATION_REMOVED, \
- COMPENSATION_REMOVED_TEMPLATE, DOCUMENT_ADDED, DEDUCTION_EDITED, REVOCATION_EDITED, DOCUMENT_EDITED
+ COMPENSATION_REMOVED_TEMPLATE, DOCUMENT_ADDED, DEDUCTION_EDITED, REVOCATION_EDITED, DOCUMENT_EDITED, \
+ RECORDED_BLOCKS_EDIT, DATA_CHECKED_PREVIOUSLY_TEMPLATE
from konova.utils.user_checks import in_group
@@ -264,15 +265,18 @@ def detail_view(request: HttpRequest, id: str):
geom_form = SimpleGeomForm(
instance=intervention,
)
-
- parcels = intervention.get_underlying_parcels()
+ last_checked = intervention.get_last_checked_action()
+ last_checked_tooltip = ""
+ if last_checked:
+ last_checked_tooltip = DATA_CHECKED_PREVIOUSLY_TEMPLATE.format(last_checked.get_timestamp_str_formatted(), last_checked.user)
context = {
"obj": intervention,
+ "last_checked": last_checked,
+ "last_checked_tooltip": last_checked_tooltip,
"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),
@@ -302,6 +306,13 @@ def edit_view(request: HttpRequest, id: str):
template = "intervention/form/view.html"
# Get object from db
intervention = get_object_or_404(Intervention, id=id)
+ if intervention.is_recorded:
+ messages.info(
+ request,
+ RECORDED_BLOCKS_EDIT
+ )
+ return redirect("intervention:detail", id=id)
+
# Create forms, initialize with values from db/from POST request
data_form = EditInterventionForm(request.POST or None, instance=intervention)
geom_form = SimpleGeomForm(request.POST or None, read_only=False, instance=intervention)
@@ -693,19 +704,22 @@ def report_view(request:HttpRequest, id: str):
distinct_deductions = intervention.deductions.all().distinct(
"account"
)
- qrcode_img = generate_qr_code(
- request.build_absolute_uri(reverse("intervention:report", args=(id,))),
- 10
- )
- qrcode_img_lanis = generate_qr_code(
- intervention.get_LANIS_link(),
- 7
- )
+ qrcode_url = request.build_absolute_uri(reverse("intervention:report", args=(id,)))
+ qrcode_img = generate_qr_code(qrcode_url, 10)
+ qrcode_lanis_url = intervention.get_LANIS_link()
+ qrcode_img_lanis = generate_qr_code(qrcode_lanis_url, 7)
+
context = {
"obj": intervention,
"deductions": distinct_deductions,
- "qrcode": qrcode_img,
- "qrcode_lanis": qrcode_img_lanis,
+ "qrcode": {
+ "img": qrcode_img,
+ "url": qrcode_url,
+ },
+ "qrcode_lanis": {
+ "img": qrcode_img_lanis,
+ "url": qrcode_lanis_url,
+ },
"geom_form": geom_form,
"parcels": parcels,
TAB_TITLE_IDENTIFIER: tab_title,
diff --git a/konova/admin.py b/konova/admin.py
index 81db8fe2..b30f4b14 100644
--- a/konova/admin.py
+++ b/konova/admin.py
@@ -7,7 +7,8 @@ Created on: 22.07.21
"""
from django.contrib import admin
-from konova.models import Geometry, Deadline, GeometryConflict, Parcel, District
+from konova.models import Geometry, Deadline, GeometryConflict, Parcel, District, Municipal, ParcelGroup
+from konova.sub_settings.lanis_settings import DEFAULT_SRID_RLP
from konova.utils.message_templates import COMPENSATION_REMOVED_TEMPLATE
from user.models import UserAction
@@ -16,13 +17,28 @@ class GeometryAdmin(admin.ModelAdmin):
list_display = [
"id",
"created",
+ "st_area",
]
+ readonly_fields = [
+ "st_area",
+ "created",
+ "modified",
+ ]
+
+ def st_area(self, obj):
+ val = None
+ geom = obj.geom
+ if geom is not None:
+ geom.transform(ct=DEFAULT_SRID_RLP)
+ val = geom.area
+ return val
+ st_area.short_description = f"Area (srid={DEFAULT_SRID_RLP})"
class ParcelAdmin(admin.ModelAdmin):
list_display = [
"id",
- "gmrkng",
+ "parcel_group",
"flr",
"flrstck_nnr",
"flrstck_zhlr",
@@ -32,9 +48,27 @@ class ParcelAdmin(admin.ModelAdmin):
class DistrictAdmin(admin.ModelAdmin):
list_display = [
+ "name",
+ "key",
+ "id",
+ ]
+
+
+class MunicipalAdmin(admin.ModelAdmin):
+ list_display = [
+ "name",
+ "key",
+ "district",
+ "id",
+ ]
+
+
+class ParcelGroupAdmin(admin.ModelAdmin):
+ list_display = [
+ "name",
+ "key",
+ "municipal",
"id",
- "gmnd",
- "krs",
]
@@ -64,6 +98,18 @@ class DeadlineAdmin(admin.ModelAdmin):
]
+class DeletableObjectMixinAdmin(admin.ModelAdmin):
+ class Meta:
+ abstract = True
+
+ def restore_deleted_data(self, request, queryset):
+ queryset = queryset.filter(
+ deleted__isnull=False
+ )
+ for entry in queryset:
+ entry.deleted.delete()
+
+
class BaseResourceAdmin(admin.ModelAdmin):
fields = [
"created",
@@ -75,7 +121,7 @@ class BaseResourceAdmin(admin.ModelAdmin):
]
-class BaseObjectAdmin(BaseResourceAdmin):
+class BaseObjectAdmin(BaseResourceAdmin, DeletableObjectMixinAdmin):
search_fields = [
"identifier",
"title",
@@ -92,18 +138,13 @@ class BaseObjectAdmin(BaseResourceAdmin):
"deleted",
]
- def restore_deleted_data(self, request, queryset):
- queryset = queryset.filter(
- deleted__isnull=False
- )
- for entry in queryset:
- entry.deleted.delete()
-
# Outcommented for a cleaner admin backend on production
#admin.site.register(Geometry, GeometryAdmin)
#admin.site.register(Parcel, ParcelAdmin)
#admin.site.register(District, DistrictAdmin)
+#admin.site.register(Municipal, MunicipalAdmin)
+#admin.site.register(ParcelGroup, ParcelGroupAdmin)
#admin.site.register(GeometryConflict, GeometryConflictAdmin)
#admin.site.register(Deadline, DeadlineAdmin)
diff --git a/konova/autocompletes.py b/konova/autocompletes.py
index a5848f5e..a59d66d3 100644
--- a/konova/autocompletes.py
+++ b/konova/autocompletes.py
@@ -53,14 +53,16 @@ class InterventionAutocomplete(Select2QuerySetView):
"""
def get_queryset(self):
- if self.request.user.is_anonymous:
+ user = self.request.user
+ if user.is_anonymous:
return Intervention.objects.none()
qs = Intervention.objects.filter(
- deleted=None,
- users__in=[self.request.user],
+ Q(deleted=None) &
+ Q(users__in=[user]) |
+ Q(teams__in=user.teams.all())
).order_by(
"identifier"
- )
+ ).distinct()
if self.q:
qs = qs.filter(
Q(identifier__icontains=self.q) |
@@ -95,7 +97,9 @@ class ShareTeamAutocomplete(Select2QuerySetView):
def get_queryset(self):
if self.request.user.is_anonymous:
return Team.objects.none()
- qs = Team.objects.all()
+ qs = Team.objects.filter(
+ deleted__isnull=True
+ )
if self.q:
# Due to privacy concerns only a full username match will return the proper user entry
qs = qs.filter(
@@ -107,6 +111,29 @@ class ShareTeamAutocomplete(Select2QuerySetView):
return qs
+class TeamAdminAutocomplete(Select2QuerySetView):
+ """ Autocomplete for share with teams
+
+ """
+ def get_queryset(self):
+ if self.request.user.is_anonymous:
+ return User.objects.none()
+ qs = User.objects.filter(
+ id__in=self.forwarded.get("members", [])
+ ).exclude(
+ id__in=self.forwarded.get("admins", [])
+ )
+ if self.q:
+ # Due to privacy concerns only a full username match will return the proper user entry
+ qs = qs.filter(
+ name__icontains=self.q
+ )
+ qs = qs.order_by(
+ "username"
+ )
+ return qs
+
+
class KonovaCodeAutocomplete(Select2GroupQuerySetView):
"""
Provides simple autocomplete functionality for codes
diff --git a/konova/filters/mixins.py b/konova/filters/mixins.py
index e6a841ea..cfd9cc4c 100644
--- a/konova/filters/mixins.py
+++ b/konova/filters/mixins.py
@@ -145,26 +145,20 @@ class GeoReferencedTableFilterMixin(django_filters.FilterSet):
class Meta:
abstract = True
- def _filter_parcel_reference(self, queryset, name, value, filter_value) -> QuerySet:
- """ Filters the parcel entries by a given filter_value.
-
- filter_value may already include further filter annotations like 'xy__icontains'
+ def _filter_parcel_reference(self, queryset, filter_q) -> QuerySet:
+ """ Filters the parcel entries by a given filter_q
Args:
- queryset ():
- name ():
- value ():
- filter_value ():
+ queryset (QuerySet): The queryset
+ filter_q (Q): The Q-style filter expression
Returns:
"""
- _filter = {
- filter_value: value
- }
matching_parcels = Parcel.objects.filter(
- **_filter
+ filter_q
)
+
related_geoms = matching_parcels.values(
"geometries"
).distinct()
@@ -185,8 +179,9 @@ class GeoReferencedTableFilterMixin(django_filters.FilterSet):
"""
matching_districts = District.objects.filter(
- krs__icontains=value
- )
+ Q(name__icontains=value) |
+ Q(key__icontains=value)
+ ).distinct()
matching_parcels = Parcel.objects.filter(
district__in=matching_districts
)
@@ -209,7 +204,10 @@ class GeoReferencedTableFilterMixin(django_filters.FilterSet):
Returns:
"""
- queryset = self._filter_parcel_reference(queryset, name, value, "gmrkng__icontains")
+ queryset = self._filter_parcel_reference(
+ queryset,
+ Q(parcel_group__name__icontains=value) | Q(parcel_group__key__icontains=value),
+ )
return queryset
def filter_parcel(self, queryset, name, value) -> QuerySet:
@@ -224,7 +222,10 @@ class GeoReferencedTableFilterMixin(django_filters.FilterSet):
"""
value = value.replace("-", "")
- queryset = self._filter_parcel_reference(queryset, name, value, "flr")
+ queryset = self._filter_parcel_reference(
+ queryset,
+ Q(flr=value),
+ )
return queryset
def filter_parcel_counter(self, queryset, name, value) -> QuerySet:
@@ -239,7 +240,10 @@ class GeoReferencedTableFilterMixin(django_filters.FilterSet):
"""
value = value.replace("-", "")
- queryset = self._filter_parcel_reference(queryset, name, value, "flrstck_zhlr")
+ queryset = self._filter_parcel_reference(
+ queryset,
+ Q(flrstck_zhlr=value)
+ )
return queryset
def filter_parcel_number(self, queryset, name, value) -> QuerySet:
@@ -254,7 +258,10 @@ class GeoReferencedTableFilterMixin(django_filters.FilterSet):
"""
value = value.replace("-", "")
- queryset = self._filter_parcel_reference(queryset, name, value, "flrstck_nnr")
+ queryset = self._filter_parcel_reference(
+ queryset,
+ Q(flrstck_nnr=value),
+ )
return queryset
@@ -298,7 +305,7 @@ class ShareableTableFilterMixin(django_filters.FilterSet):
if not value:
return queryset.filter(
Q(users__in=[self.user]) | # requesting user has access
- Q(teams__users__in=[self.user])
+ Q(teams__in=self.user.shared_teams)
).distinct()
else:
return queryset
diff --git a/konova/forms.py b/konova/forms.py
index fd359cb8..0a341e0c 100644
--- a/konova/forms.py
+++ b/konova/forms.py
@@ -5,18 +5,20 @@ Contact: michel.peltriaux@sgdnord.rlp.de
Created on: 16.11.20
"""
-
+import json
from abc import abstractmethod
from bootstrap_modal_forms.forms import BSModalForm
from bootstrap_modal_forms.utils import is_ajax
from django import forms
from django.contrib import messages
+from django.contrib.gis import gdal
from django.db.models.fields.files import FieldFile
+from konova.sub_settings.lanis_settings import DEFAULT_SRID_RLP
from user.models import User
-from django.contrib.gis.forms import OSMWidget, MultiPolygonField
-from django.contrib.gis.geos import MultiPolygon
+from django.contrib.gis.forms import MultiPolygonField
+from django.contrib.gis.geos import MultiPolygon, Polygon
from django.db import transaction
from django.http import HttpRequest, HttpResponseRedirect
from django.shortcuts import render
@@ -57,6 +59,8 @@ class BaseForm(forms.Form):
self.has_required_fields = True
break
+ self.check_for_recorded_instance()
+
@abstractmethod
def save(self):
# To be implemented in subclasses!
@@ -136,6 +140,38 @@ class BaseForm(forms.Form):
set_class = set_class.replace(cls, "")
self.fields[field].widget.attrs["class"] = set_class
+ def check_for_recorded_instance(self):
+ """ Checks if the instance is recorded and runs some special logic if yes
+
+ If the instance is recorded, the form shall not display any possibility to
+ edit any data. Instead, the users should get some information about why they can not edit anything.
+
+ There are situations where the form should be rendered regularly,
+ e.g deduction forms for (recorded) eco accounts.
+
+ Returns:
+
+ """
+ from intervention.forms.modalForms import NewDeductionModalForm, EditEcoAccountDeductionModalForm, \
+ RemoveEcoAccountDeductionModalForm
+ is_none = self.instance is None
+ is_other_data_type = not isinstance(self.instance, BaseObject)
+ is_deduction_form = isinstance(
+ self,
+ (
+ NewDeductionModalForm,
+ EditEcoAccountDeductionModalForm,
+ RemoveEcoAccountDeductionModalForm,
+ )
+ )
+
+ if is_none or is_other_data_type or is_deduction_form:
+ # Do nothing
+ return
+
+ if self.instance.is_recorded:
+ self.template = "form/recorded_no_edit.html"
+
class RemoveForm(BaseForm):
check = forms.BooleanField(
@@ -238,41 +274,85 @@ class SimpleGeomForm(BaseForm):
""" A geometry form for rendering geometry read-only using a widget
"""
+ read_only = True
geom = MultiPolygonField(
- srid=DEFAULT_SRID,
+ srid=DEFAULT_SRID_RLP,
label=_("Geometry"),
help_text=_(""),
label_suffix="",
required=False,
disabled=False,
- widget=OSMWidget(
- attrs={
- "map_width": 600,
- "map_height": 400,
- # default_zoom defines the nearest possible zoom level from which the JS automatically
- # zooms out if geometry requires a larger view port. So define a larger range for smaller geometries
- "default_zoom": 25,
- }
- )
)
def __init__(self, *args, **kwargs):
- read_only = kwargs.pop("read_only", True)
+ self.read_only = kwargs.pop("read_only", True)
super().__init__(*args, **kwargs)
# Initialize geometry
try:
geom = self.instance.geometry.geom
self.empty = geom.empty
+
+ if self.empty:
+ raise AttributeError
+
+ geojson = self.instance.geometry.as_feature_collection(srid=DEFAULT_SRID_RLP)
+ geom = json.dumps(geojson)
except AttributeError:
# If no geometry exists for this form, we simply set the value to None and zoom to the maximum level
- geom = None
+ geom = ""
self.empty = True
- self.fields["geom"].widget.attrs["default_zoom"] = 1
self.initialize_form_field("geom", geom)
- if read_only:
- self.fields["geom"].disabled = True
+
+ def is_valid(self):
+ super().is_valid()
+ is_valid = True
+
+ # Get geojson from form
+ geom = self.data["geom"]
+ if geom is None or len(geom) == 0:
+ # empty geometry is a valid geometry
+ return is_valid
+ geom = json.loads(geom)
+
+ # Write submitted data back into form field to make sure invalid geometry
+ # will be rendered again on failed submit
+ self.initialize_form_field("geom", self.data["geom"])
+
+ # Read geojson into gdal geometry
+ # HINT: This can be simplified if the geojson format holds data in epsg:4326 (GDAL provides direct creation for
+ # this case)
+ features = []
+ features_json = geom.get("features", [])
+ for feature in features_json:
+ g = gdal.OGRGeometry(json.dumps(feature.get("geometry", feature)), srs=DEFAULT_SRID_RLP)
+ if g.geom_type not in ["Polygon", "MultiPolygon"]:
+ self.add_error("geom", _("Only surfaces allowed. Points or lines must be buffered."))
+ is_valid = False
+ return is_valid
+
+ polygon = Polygon.from_ewkt(g.ewkt)
+ is_valid = polygon.valid
+ if not is_valid:
+ self.add_error("geom", polygon.valid_reason)
+ return is_valid
+
+ features.append(polygon)
+ form_geom = MultiPolygon(srid=DEFAULT_SRID_RLP)
+ for feature in features:
+ form_geom = form_geom.union(feature)
+
+ # Make sure to convert into a MultiPolygon. Relevant if a single Polygon is provided.
+ if form_geom.geom_type != "MultiPolygon":
+ form_geom = MultiPolygon(form_geom, srid=DEFAULT_SRID_RLP)
+
+ # Write unioned Multipolygon into cleaned data
+ if self.cleaned_data is None:
+ self.cleaned_data = {}
+ self.cleaned_data["geom"] = form_geom.ewkt
+
+ return is_valid
def save(self, action: UserActionLogEntry):
""" Saves the form's geometry
@@ -410,7 +490,6 @@ class NewDocumentModalForm(BaseModalForm):
super().__init__(*args, **kwargs)
self.form_title = _("Add new document")
self.form_caption = _("")
- self.template = "modal/modal_form.html"
self.form_attrs = {
"enctype": "multipart/form-data", # important for file upload
}
@@ -597,4 +676,12 @@ class RecordModalForm(BaseModalForm):
self.instance.set_unrecorded(self.user)
else:
self.instance.set_recorded(self.user)
- return self.instance
\ No newline at end of file
+ return self.instance
+
+ def check_for_recorded_instance(self):
+ """ Overwrite the check method for doing nothing on the RecordModalForm
+
+ Returns:
+
+ """
+ pass
diff --git a/konova/management/commands/sanitize_db.py b/konova/management/commands/sanitize_db.py
index b5be8349..b296b2a7 100644
--- a/konova/management/commands/sanitize_db.py
+++ b/konova/management/commands/sanitize_db.py
@@ -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, Parcel, District
+from konova.models import Deadline, Geometry, Parcel, District, Municipal, ParcelGroup
from user.models import UserActionLogEntry, UserAction
@@ -271,13 +271,26 @@ class Command(BaseKonovaCommand):
self._write_success("No unused states found.")
self._break_line()
+ def __sanitize_parcel_sub_type(self, cls):
+ unrelated_entries = cls.objects.filter(
+ parcels=None,
+ )
+ num_unrelated_entries = unrelated_entries.count()
+ cls_name = cls.__name__
+ if num_unrelated_entries > 0:
+ self._write_error(f"Found {num_unrelated_entries} unrelated {cls_name} entries. Delete now...")
+ unrelated_entries.delete()
+ self._write_success(f"Unrelated {cls_name} deleted.")
+ else:
+ self._write_success(f"No unrelated {cls_name} found.")
+
def sanitize_parcels_and_districts(self):
""" Removes unattached parcels and districts
Returns:
"""
- self._write_warning("=== Sanitize parcels and districts ===")
+ self._write_warning("=== Sanitize administrative spatial references ===")
unrelated_parcels = Parcel.objects.filter(
geometries=None,
)
@@ -289,16 +302,12 @@ class Command(BaseKonovaCommand):
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()
+ sub_types = [
+ District,
+ Municipal,
+ ParcelGroup
+ ]
+ for sub_type in sub_types:
+ self.__sanitize_parcel_sub_type(sub_type)
+ self._break_line()
\ No newline at end of file
diff --git a/konova/management/commands/update_all_parcels.py b/konova/management/commands/update_all_parcels.py
index b15f4c63..66fde415 100644
--- a/konova/management/commands/update_all_parcels.py
+++ b/konova/management/commands/update_all_parcels.py
@@ -5,10 +5,13 @@ Contact: michel.peltriaux@sgdnord.rlp.de
Created on: 04.01.22
"""
+import datetime
from pyexpat import ExpatError
from requests.exceptions import ProxyError
+from django.contrib.gis.db.models.functions import Area
+
from konova.management.commands.setup import BaseKonovaCommand
from konova.models import Geometry, Parcel, District
@@ -32,8 +35,11 @@ class Command(BaseKonovaCommand):
num_parcels_before = Parcel.objects.count()
num_districts_before = District.objects.count()
self._write_warning("=== Update parcels and districts ===")
+ # Order geometries by size to process smaller once at first
geometries = Geometry.objects.all().exclude(
geom=None
+ ).annotate(area=Area("geom")).order_by(
+ 'area'
)
if ids is not None:
diff --git a/konova/migrations/0006_auto_20220411_0835.py b/konova/migrations/0006_auto_20220411_0835.py
new file mode 100644
index 00000000..bf74643c
--- /dev/null
+++ b/konova/migrations/0006_auto_20220411_0835.py
@@ -0,0 +1,71 @@
+# Generated by Django 3.1.3 on 2022-04-11 06:35
+
+from django.db import migrations, models
+import django.db.models.deletion
+import uuid
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('konova', '0005_auto_20220216_0856'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Municipal',
+ fields=[
+ ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
+ ('key', models.IntegerField(blank=True, help_text='Represents Gemeindeschlüssel', null=True)),
+ ('name', models.CharField(blank=True, help_text='Gemeinde', max_length=1000, null=True)),
+ ],
+ options={
+ 'abstract': False,
+ },
+ ),
+ migrations.RenameField(
+ model_name='district',
+ old_name='krs',
+ new_name='name',
+ ),
+ migrations.RemoveField(
+ model_name='district',
+ name='gmnd',
+ ),
+ migrations.RemoveField(
+ model_name='parcel',
+ name='gmrkng',
+ ),
+ migrations.AddField(
+ model_name='district',
+ name='key',
+ field=models.IntegerField(blank=True, help_text='Represents Kreisschlüssel', null=True),
+ ),
+ migrations.CreateModel(
+ name='ParcelGroup',
+ fields=[
+ ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
+ ('key', models.IntegerField(blank=True, help_text='Represents Gemarkungsschlüssel', null=True)),
+ ('name', models.CharField(blank=True, help_text='Gemarkung', max_length=1000, null=True)),
+ ('municipal', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='konova.municipal')),
+ ],
+ options={
+ 'abstract': False,
+ },
+ ),
+ migrations.AddField(
+ model_name='municipal',
+ name='district',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='konova.district'),
+ ),
+ migrations.AddField(
+ model_name='parcel',
+ name='municipal',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='parcels', to='konova.municipal'),
+ ),
+ migrations.AddField(
+ model_name='parcel',
+ name='parcel_group',
+ field=models.ForeignKey(blank=True, help_text='Gemarkung', null=True, on_delete=django.db.models.deletion.SET_NULL, to='konova.parcelgroup'),
+ ),
+ ]
diff --git a/konova/migrations/0007_auto_20220411_0848.py b/konova/migrations/0007_auto_20220411_0848.py
new file mode 100644
index 00000000..fcc2b45b
--- /dev/null
+++ b/konova/migrations/0007_auto_20220411_0848.py
@@ -0,0 +1,28 @@
+# Generated by Django 3.1.3 on 2022-04-11 06:48
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('konova', '0006_auto_20220411_0835'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='district',
+ name='key',
+ field=models.CharField(blank=True, help_text='Represents Kreisschlüssel', max_length=255, null=True),
+ ),
+ migrations.AlterField(
+ model_name='municipal',
+ name='key',
+ field=models.CharField(blank=True, help_text='Represents Gemeindeschlüssel', max_length=255, null=True),
+ ),
+ migrations.AlterField(
+ model_name='parcelgroup',
+ name='key',
+ field=models.CharField(blank=True, help_text='Represents Gemarkungsschlüssel', max_length=255, null=True),
+ ),
+ ]
diff --git a/konova/migrations/0008_auto_20220411_0914.py b/konova/migrations/0008_auto_20220411_0914.py
new file mode 100644
index 00000000..c56b6215
--- /dev/null
+++ b/konova/migrations/0008_auto_20220411_0914.py
@@ -0,0 +1,48 @@
+# Generated by Django 3.1.3 on 2022-04-11 07:14
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('konova', '0007_auto_20220411_0848'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='municipal',
+ name='key',
+ field=models.CharField(blank=True, help_text='Represents Kreisschlüssel', max_length=255, null=True),
+ ),
+ migrations.AlterField(
+ model_name='municipal',
+ name='name',
+ field=models.CharField(blank=True, help_text='Kreis', max_length=1000, null=True),
+ ),
+ migrations.AlterField(
+ model_name='parcel',
+ name='flr',
+ field=models.IntegerField(blank=True, help_text='Flur', null=True),
+ ),
+ migrations.AlterField(
+ model_name='parcel',
+ name='flrstck_nnr',
+ field=models.IntegerField(blank=True, help_text='Flurstücksnenner', null=True),
+ ),
+ migrations.AlterField(
+ model_name='parcel',
+ name='flrstck_zhlr',
+ field=models.IntegerField(blank=True, help_text='Flurstückszähler', null=True),
+ ),
+ migrations.AlterField(
+ model_name='parcelgroup',
+ name='key',
+ field=models.CharField(blank=True, help_text='Represents Kreisschlüssel', max_length=255, null=True),
+ ),
+ migrations.AlterField(
+ model_name='parcelgroup',
+ name='name',
+ field=models.CharField(blank=True, help_text='Kreis', max_length=1000, null=True),
+ ),
+ ]
diff --git a/konova/migrations/0009_auto_20220411_1004.py b/konova/migrations/0009_auto_20220411_1004.py
new file mode 100644
index 00000000..d0679bf3
--- /dev/null
+++ b/konova/migrations/0009_auto_20220411_1004.py
@@ -0,0 +1,19 @@
+# Generated by Django 3.1.3 on 2022-04-11 08:04
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('konova', '0008_auto_20220411_0914'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='parcel',
+ name='parcel_group',
+ field=models.ForeignKey(blank=True, help_text='Gemarkung', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='parcels', to='konova.parcelgroup'),
+ ),
+ ]
diff --git a/konova/migrations/0010_auto_20220420_1034.py b/konova/migrations/0010_auto_20220420_1034.py
new file mode 100644
index 00000000..c916ff54
--- /dev/null
+++ b/konova/migrations/0010_auto_20220420_1034.py
@@ -0,0 +1,17 @@
+# Generated by Django 3.1.3 on 2022-04-20 08:34
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('konova', '0009_auto_20220411_1004'),
+ ]
+
+ operations = [
+ migrations.AddConstraint(
+ model_name='parcel',
+ constraint=models.UniqueConstraint(fields=('district', 'municipal', 'parcel_group', 'flr', 'flrstck_nnr', 'flrstck_zhlr'), name='Unique parcel constraint'),
+ ),
+ ]
diff --git a/konova/migrations/0011_auto_20220420_1101.py b/konova/migrations/0011_auto_20220420_1101.py
new file mode 100644
index 00000000..b402818e
--- /dev/null
+++ b/konova/migrations/0011_auto_20220420_1101.py
@@ -0,0 +1,25 @@
+# Generated by Django 3.1.3 on 2022-04-20 09:01
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('konova', '0010_auto_20220420_1034'),
+ ]
+
+ operations = [
+ migrations.AddConstraint(
+ model_name='district',
+ constraint=models.UniqueConstraint(fields=('key', 'name'), name='Unique district constraint'),
+ ),
+ migrations.AddConstraint(
+ model_name='municipal',
+ constraint=models.UniqueConstraint(fields=('key', 'name', 'district'), name='Unique municipal constraint'),
+ ),
+ migrations.AddConstraint(
+ model_name='parcelgroup',
+ constraint=models.UniqueConstraint(fields=('key', 'name', 'municipal'), name='Unique parcel group constraint'),
+ ),
+ ]
diff --git a/konova/models/geometry.py b/konova/models/geometry.py
index bec89c39..b996feb7 100644
--- a/konova/models/geometry.py
+++ b/konova/models/geometry.py
@@ -5,11 +5,15 @@ Contact: michel.peltriaux@sgdnord.rlp.de
Created on: 15.11.21
"""
+import json
+
from django.contrib.gis.db.models import MultiPolygonField
-from django.db import models
+from django.contrib.gis.geos import Polygon
+from django.db import models, transaction
from django.utils import timezone
from konova.models import BaseResource, UuidModel
+from konova.sub_settings.lanis_settings import DEFAULT_SRID_RLP
from konova.utils.wfs.spatial import ParcelWFSFetcher
@@ -20,6 +24,9 @@ class Geometry(BaseResource):
from konova.settings import DEFAULT_SRID
geom = MultiPolygonField(null=True, blank=True, srid=DEFAULT_SRID)
+ def __str__(self):
+ return str(self.id)
+
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
self.check_for_conflicts()
@@ -93,13 +100,14 @@ class Geometry(BaseResource):
objs += set_objs
return objs
+ @transaction.atomic
def update_parcels(self):
""" Updates underlying parcel information
Returns:
"""
- from konova.models import Parcel, District, ParcelIntersection
+ from konova.models import Parcel, District, ParcelIntersection, Municipal, ParcelGroup
parcel_fetcher = ParcelWFSFetcher(
geometry_id=self.id,
)
@@ -110,20 +118,38 @@ class Geometry(BaseResource):
_now = timezone.now()
underlying_parcels = []
for result in fetched_parcels:
- fetched_parcel = result[typename]
+ parcel_properties = result["properties"]
# There could be parcels which include the word 'Flur',
# which needs to be deleted and just keep the numerical values
## THIS CAN BE REMOVED IN THE FUTURE, WHEN 'Flur' WON'T OCCUR ANYMORE!
- flr_val = fetched_parcel["ave:flur"].replace("Flur ", "")
- parcel_obj = Parcel.objects.get_or_create(
- gmrkng=fetched_parcel["ave:gemarkung"],
- flr=flr_val,
- flrstck_nnr=fetched_parcel['ave:flstnrnen'],
- flrstck_zhlr=fetched_parcel['ave:flstnrzae'],
- )[0]
+ flr_val = parcel_properties["flur"].replace("Flur ", "")
district = District.objects.get_or_create(
- gmnd=fetched_parcel["ave:gemeinde"],
- krs=fetched_parcel["ave:kreis"],
+ key=parcel_properties["kreisschl"],
+ name=parcel_properties["kreis"],
+ )[0]
+ municipal = Municipal.objects.get_or_create(
+ key=parcel_properties["gmdschl"],
+ name=parcel_properties["gemeinde"],
+ district=district,
+ )[0]
+ parcel_group = ParcelGroup.objects.get_or_create(
+ key=parcel_properties["gemaschl"],
+ name=parcel_properties["gemarkung"],
+ municipal=municipal,
+ )[0]
+ flrstck_nnr = parcel_properties['flstnrnen']
+ if not flrstck_nnr:
+ flrstck_nnr = None
+ flrstck_zhlr = parcel_properties['flstnrzae']
+ if not flrstck_zhlr:
+ flrstck_zhlr = None
+ parcel_obj = Parcel.objects.get_or_create(
+ district=district,
+ municipal=municipal,
+ parcel_group=parcel_group,
+ flr=flr_val,
+ flrstck_nnr=flrstck_nnr,
+ flrstck_zhlr=flrstck_zhlr,
)[0]
parcel_obj.district = district
parcel_obj.updated_on = _now
@@ -131,6 +157,7 @@ class Geometry(BaseResource):
underlying_parcels.append(parcel_obj)
# Update the linked parcels
+ self.parcels.clear()
self.parcels.set(underlying_parcels)
# Set the calculated_on intermediate field, so this related data will be found on lookups
@@ -151,17 +178,55 @@ class Geometry(BaseResource):
Returns:
parcels (QuerySet): The related parcels as queryset
"""
-
parcels = self.parcels.filter(
parcelintersection__calculated_on__isnull=False,
).prefetch_related(
- "district"
+ "district",
+ "municipal",
).order_by(
- "gmrkng",
+ "municipal__name",
)
return parcels
+ def count_underlying_parcels(self):
+ """ Getter for number of underlying parcels
+
+ Returns:
+
+ """
+ num_parcels = self.parcels.filter(
+ parcelintersection__calculated_on__isnull=False,
+ ).count()
+ return num_parcels
+
+ def as_feature_collection(self, srid=DEFAULT_SRID_RLP):
+ """ Returns a FeatureCollection structure holding all polygons of the MultiPolygon as single features
+
+ This method is used to convert a single MultiPolygon into multiple Polygons, which can be used as separated
+ features in the NETGIS map client.
+
+ Args:
+ srid (int): The spatial reference system identifier to be transformed to
+
+ Returns:
+ geojson (dict): The FeatureCollection json (as dict)
+ """
+ geom = self.geom
+ geom.transform(ct=srid)
+
+ polygons = []
+ for coords in geom.coords:
+ p = Polygon(coords[0], srid=geom.srid)
+ polygons.append(p)
+ geojson = {
+ "type": "FeatureCollection",
+ "features": [
+ json.loads(x.geojson) for x in polygons
+ ]
+ }
+ return geojson
+
class GeometryConflict(UuidModel):
"""
diff --git a/konova/models/object.py b/konova/models/object.py
index f224b68c..b468932a 100644
--- a/konova/models/object.py
+++ b/konova/models/object.py
@@ -87,25 +87,15 @@ class BaseResource(UuidModel):
super().delete()
-class BaseObject(BaseResource):
- """
- A basic object model, which specifies BaseResource.
+class DeletableObjectMixin(models.Model):
+ """ Wraps deleted field and related functionality
- Mainly used for intervention, compensation, ecoaccount
"""
- identifier = models.CharField(max_length=1000, null=True, blank=True)
- title = models.CharField(max_length=1000, null=True, blank=True)
deleted = models.ForeignKey("user.UserActionLogEntry", on_delete=models.SET_NULL, null=True, blank=True, related_name='+')
- comment = models.TextField(null=True, blank=True)
- log = models.ManyToManyField("user.UserActionLogEntry", blank=True, help_text="Keeps all user actions of an object", editable=False)
class Meta:
abstract = True
- @abstractmethod
- def set_status_messages(self, request: HttpRequest):
- raise NotImplementedError
-
def mark_as_deleted(self, user, send_mail: bool = True):
""" Mark an entry as deleted
@@ -140,6 +130,25 @@ class BaseObject(BaseResource):
self.save()
+
+class BaseObject(BaseResource, DeletableObjectMixin):
+ """
+ A basic object model, which specifies BaseResource.
+
+ Mainly used for intervention, compensation, ecoaccount
+ """
+ identifier = models.CharField(max_length=1000, null=True, blank=True)
+ title = models.CharField(max_length=1000, null=True, blank=True)
+ comment = models.TextField(null=True, blank=True)
+ log = models.ManyToManyField("user.UserActionLogEntry", blank=True, help_text="Keeps all user actions of an object", editable=False)
+
+ class Meta:
+ abstract = True
+
+ @abstractmethod
+ def set_status_messages(self, request: HttpRequest):
+ raise NotImplementedError
+
def mark_as_edited(self, performing_user, request: HttpRequest = None, edit_comment: str = None):
""" In case the object or a related object changed the log history needs to be updated
@@ -289,6 +298,8 @@ class RecordableObjectMixin(models.Model):
from user.models import UserActionLogEntry
if self.recorded:
return None
+
+ self.unshare_with_default_users()
action = UserActionLogEntry.get_recorded_action(user)
self.recorded = action
self.save()
@@ -335,6 +346,15 @@ class RecordableObjectMixin(models.Model):
"""
raise NotImplementedError("Implement this in the subclass!")
+ @property
+ def is_recorded(self):
+ """ Getter for record status as property
+
+ Returns:
+
+ """
+ return self.recorded is not None
+
class CheckableObjectMixin(models.Model):
# Checks - Refers to "Genehmigen" but optional
@@ -397,6 +417,20 @@ class CheckableObjectMixin(models.Model):
self.log.add(action)
return action
+ def get_last_checked_action(self):
+ """ Getter for the most recent checked action on the log
+
+ Returns:
+ previously_checked (UserActionLogEntry): The most recent checked action
+ """
+ from user.models import UserAction
+ previously_checked = self.log.filter(
+ action=UserAction.CHECKED
+ ).order_by(
+ "-timestamp"
+ ).first()
+ return previously_checked
+
class ShareableObjectMixin(models.Model):
# Users having access on this object
@@ -459,8 +493,8 @@ class ShareableObjectMixin(models.Model):
Returns:
"""
- directly_shared = self.users.filter(id=user.id).exists()
- team_shared = self.teams.filter(
+ directly_shared = self.shared_users.filter(id=user.id).exists()
+ team_shared = self.shared_teams.filter(
users__in=[user]
).exists()
is_shared = directly_shared or team_shared
@@ -597,7 +631,9 @@ class ShareableObjectMixin(models.Model):
Returns:
teams (QuerySet)
"""
- return self.teams.all()
+ return self.teams.filter(
+ deleted__isnull=True
+ )
@abstractmethod
def get_share_url(self):
@@ -608,6 +644,26 @@ class ShareableObjectMixin(models.Model):
"""
raise NotImplementedError("Must be implemented in subclasses!")
+ def unshare_with_default_users(self):
+ """ Removes all shared users from direct shared access which are only default group users
+
+ Returns:
+
+ """
+ from konova.utils.user_checks import is_default_group_only
+ users = self.shared_users
+ cleaned_users = []
+ default_users = []
+ for user in users:
+ if not is_default_group_only(user):
+ cleaned_users.append(user)
+ else:
+ default_users.append(user)
+ self.share_with_user_list(cleaned_users)
+
+ for user in default_users:
+ celery_send_mail_shared_access_removed.delay(self.identifier, self.title, user.id)
+
class GeoReferencedMixin(models.Model):
geometry = models.ForeignKey("konova.Geometry", null=True, blank=True, on_delete=models.SET_NULL)
@@ -621,10 +677,21 @@ class GeoReferencedMixin(models.Model):
Returns:
parcels (Iterable): An empty list or a Queryset
"""
+ result = []
if self.geometry is not None:
- return self.geometry.get_underlying_parcels()
- else:
- return []
+ result = self.geometry.get_underlying_parcels()
+ return result
+
+ def count_underlying_parcels(self):
+ """ Getter for number of underlying parcels
+
+ Returns:
+
+ """
+ result = 0
+ if self.geometry is not None:
+ result = self.geometry.count_underlying_parcels()
+ return result
def set_geometry_conflict_message(self, request: HttpRequest):
if self.geometry is None:
diff --git a/konova/models/parcel.py b/konova/models/parcel.py
index 9c887f1a..cc91e006 100644
--- a/konova/models/parcel.py
+++ b/konova/models/parcel.py
@@ -10,8 +10,98 @@ from django.db import models
from konova.models import UuidModel
+class AdministrativeSpatialReference(models.Model):
+ key = models.CharField(
+ max_length=255,
+ help_text="Represents Kreisschlüssel",
+ null=True,
+ blank=True
+ )
+ name = models.CharField(
+ max_length=1000,
+ help_text="Kreis",
+ null=True,
+ blank=True,
+ )
+
+ class Meta:
+ abstract = True
+
+ def __str__(self):
+ return f"{self.name} ({self.key})"
+
+ @property
+ def table_str(self):
+ return f"{self.name} ({self.key})"
+
+
+class District(UuidModel, AdministrativeSpatialReference):
+ """ The model District refers to "Kreis"
+
+ """
+
+ class Meta:
+ constraints = [
+ models.UniqueConstraint(
+ fields=[
+ "key",
+ "name",
+ ],
+ name="Unique district constraint"
+ )
+ ]
+
+
+class Municipal(UuidModel, AdministrativeSpatialReference):
+ """ The model Municipal refers to "Gemeinde"
+
+ """
+ district = models.ForeignKey(
+ District,
+ on_delete=models.SET_NULL,
+ null=True,
+ blank=True,
+ )
+
+ class Meta:
+ constraints = [
+ models.UniqueConstraint(
+ fields=[
+ "key",
+ "name",
+ "district",
+ ],
+ name="Unique municipal constraint"
+ )
+ ]
+
+
+class ParcelGroup(UuidModel, AdministrativeSpatialReference):
+ """ The model ParcelGroup refers to "Gemarkung", which is defined as a loose group of parcels
+
+ """
+ municipal = models.ForeignKey(
+ Municipal,
+ on_delete=models.SET_NULL,
+ null=True,
+ blank=True,
+ )
+
+ class Meta:
+ constraints = [
+ models.UniqueConstraint(
+ fields=[
+ "key",
+ "name",
+ "municipal",
+ ],
+ name="Unique parcel group constraint"
+ )
+ ]
+
+
class Parcel(UuidModel):
- """ The Parcel model holds administrative data on the covered properties.
+ """ The Parcel model holds administrative data on 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.
@@ -24,59 +114,49 @@ class Parcel(UuidModel):
"""
geometries = models.ManyToManyField("konova.Geometry", blank=True, related_name="parcels", through='ParcelIntersection')
district = models.ForeignKey("konova.District", on_delete=models.SET_NULL, null=True, blank=True, related_name="parcels")
- gmrkng = models.CharField(
- max_length=1000,
+ municipal = models.ForeignKey("konova.Municipal", on_delete=models.SET_NULL, null=True, blank=True, related_name="parcels")
+ parcel_group = models.ForeignKey(
+ "konova.ParcelGroup",
+ on_delete=models.SET_NULL,
help_text="Gemarkung",
null=True,
blank=True,
+ related_name="parcels"
)
- flrstck_nnr = models.CharField(
- max_length=1000,
+ flr = models.IntegerField(
+ help_text="Flur",
+ null=True,
+ blank=True,
+ )
+ flrstck_nnr = models.IntegerField(
help_text="Flurstücksnenner",
null=True,
blank=True,
)
- flrstck_zhlr = models.CharField(
- max_length=1000,
+ flrstck_zhlr = models.IntegerField(
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,
- )
+ class Meta:
+ constraints = [
+ models.UniqueConstraint(
+ fields=[
+ "district",
+ "municipal",
+ "parcel_group",
+ "flr",
+ "flrstck_nnr",
+ "flrstck_zhlr",
+ ],
+ name="Unique parcel constraint"
+ )
+ ]
def __str__(self):
- return f"{self.gmnd} | {self.krs}"
+ return f"{self.parcel_group} | {self.flr} | {self.flrstck_zhlr} | {self.flrstck_nnr}"
class ParcelIntersection(UuidModel):
diff --git a/konova/static/css/konova.css b/konova/static/css/konova.css
index 7e7f3fd1..ed797046 100644
--- a/konova/static/css/konova.css
+++ b/konova/static/css/konova.css
@@ -262,4 +262,13 @@ Similar to bootstraps 'shadow-lg'
padding-left: 2em;
}
- */
\ No newline at end of file
+ */
+.collapse-icn > i{
+ transition: all 0.3s ease;
+}
+.collapsed .collapse-icn > i{
+ transform: rotate(-90deg);
+}
+.tree-label.badge{
+ font-size: 90%;
+}
\ No newline at end of file
diff --git a/konova/sub_settings/django_settings.py b/konova/sub_settings/django_settings.py
index b0d6ff8a..746df289 100644
--- a/konova/sub_settings/django_settings.py
+++ b/konova/sub_settings/django_settings.py
@@ -11,6 +11,7 @@ https://docs.djangoproject.com/en/3.1/ref/settings/
"""
import os
from django.utils.translation import gettext_lazy as _
+from django.conf.locale.de import formats as de_formats
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = os.path.dirname(
@@ -45,8 +46,8 @@ ALLOWED_HOSTS = [
LOGIN_URL = "/login/"
# Session settings
-#SESSION_COOKIE_AGE = 30 * 60 # 30 minutes
-#SESSION_SAVE_EVERY_REQUEST = True
+SESSION_COOKIE_AGE = 60 * 60 # 60 minutes
+SESSION_SAVE_EVERY_REQUEST = True
# Application definition
@@ -162,9 +163,15 @@ LANGUAGES = [
USE_THOUSAND_SEPARATOR = True
+# Regular python relevant date/datetime formatting
DEFAULT_DATE_TIME_FORMAT = '%d.%m.%Y %H:%M:%S'
DEFAULT_DATE_FORMAT = '%d.%m.%Y'
+# Template relevant date/datetime formatting
+# See the Note on here: https://docs.djangoproject.com/en/3.2/ref/templates/builtins/#date
+de_formats.DATETIME_FORMAT = "d.m.Y, H:i"
+de_formats.DATE_FORMAT = "d.m.Y"
+
TIME_ZONE = 'Europe/Berlin'
USE_I18N = True
@@ -184,6 +191,8 @@ STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, "static")
STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'konova/static'),
+ os.path.join(BASE_DIR, 'templates/map/client'), # NETGIS map client files
+ os.path.join(BASE_DIR, 'templates/map/client/libs'), # NETGIS map client files
]
# DJANGO DEBUG TOOLBAR
diff --git a/konova/sub_settings/table_settings.py b/konova/sub_settings/table_settings.py
index f32a0835..1699ad1b 100644
--- a/konova/sub_settings/table_settings.py
+++ b/konova/sub_settings/table_settings.py
@@ -19,6 +19,6 @@ PAGE_SIZE_OPTIONS_TUPLES = [
(50, 50),
(100, 100),
]
-PAGE_SIZE_DEFAULT = 5
+PAGE_SIZE_DEFAULT = 10
PAGE_SIZE_MAX = 100
PAGE_DEFAULT = 1
diff --git a/konova/templates/konova/includes/parcel_table.html b/konova/templates/konova/includes/parcel_table.html
deleted file mode 100644
index 68904894..00000000
--- a/konova/templates/konova/includes/parcel_table.html
+++ /dev/null
@@ -1,32 +0,0 @@
-{% load i18n %}
-
\ No newline at end of file
diff --git a/konova/templates/konova/includes/parcels/parcel_table_content.html b/konova/templates/konova/includes/parcels/parcel_table_content.html
new file mode 100644
index 00000000..549a8092
--- /dev/null
+++ b/konova/templates/konova/includes/parcels/parcel_table_content.html
@@ -0,0 +1,22 @@
+{% load l10n i18n %}
+{% for parcel in parcels %}
+ {% if forloop.last and next_page %}
+
+ {{parcel.parcel_group.name|default_if_none:"-"}} |
+ {{parcel.parcel_group.key|default_if_none:"-"}} |
+ {{parcel.flr|default_if_none:"-"|unlocalize}} |
+ {{parcel.flrstck_zhlr|default_if_none:"-"|unlocalize}} |
+ {{parcel.flrstck_nnr|default_if_none:"-"|unlocalize}} |
+
+ {% else %}
+
+ {{parcel.parcel_group.name|default_if_none:"-"}} |
+ {{parcel.parcel_group.key|default_if_none:"-"}} |
+ {{parcel.flr|default_if_none:"-"|unlocalize}} |
+ {{parcel.flrstck_zhlr|default_if_none:"-"|unlocalize}} |
+ {{parcel.flrstck_nnr|default_if_none:"-"|unlocalize}} |
+
+ {% endif %}
+{% endfor %}
\ No newline at end of file
diff --git a/konova/templates/konova/includes/parcels/parcel_table_frame.html b/konova/templates/konova/includes/parcels/parcel_table_frame.html
new file mode 100644
index 00000000..21b75b76
--- /dev/null
+++ b/konova/templates/konova/includes/parcels/parcel_table_frame.html
@@ -0,0 +1,49 @@
+{% load i18n l10n %}
+
\ No newline at end of file
diff --git a/konova/templates/konova/includes/parcels.html b/konova/templates/konova/includes/parcels/parcels.html
similarity index 78%
rename from konova/templates/konova/includes/parcels.html
rename to konova/templates/konova/includes/parcels/parcels.html
index 952fde11..a8a882ed 100644
--- a/konova/templates/konova/includes/parcels.html
+++ b/konova/templates/konova/includes/parcels/parcels.html
@@ -8,7 +8,7 @@
-
+
diff --git a/konova/templates/konova/includes/report/qrcodes.html b/konova/templates/konova/includes/report/qrcodes.html
new file mode 100644
index 00000000..5b52d0c1
--- /dev/null
+++ b/konova/templates/konova/includes/report/qrcodes.html
@@ -0,0 +1,19 @@
+{% load i18n %}
+
+
+
+
\ No newline at end of file
diff --git a/konova/templates/konova/widgets/checkbox-tree-select-content.html b/konova/templates/konova/widgets/tree/checkbox/checkbox-tree-select-content.html
similarity index 53%
rename from konova/templates/konova/widgets/checkbox-tree-select-content.html
rename to konova/templates/konova/widgets/tree/checkbox/checkbox-tree-select-content.html
index 9bf57253..84f97ece 100644
--- a/konova/templates/konova/widgets/checkbox-tree-select-content.html
+++ b/konova/templates/konova/widgets/tree/checkbox/checkbox-tree-select-content.html
@@ -2,18 +2,23 @@
{% for code in codes %}
- {% include 'konova/widgets/checkbox-tree-select-content.html' %}
+ {% include 'konova/widgets/tree/checkbox/checkbox-tree-select-content.html' %}
\ No newline at end of file
diff --git a/konova/tests/test_autocompletes.py b/konova/tests/test_autocompletes.py
index 95a3508d..1533d572 100644
--- a/konova/tests/test_autocompletes.py
+++ b/konova/tests/test_autocompletes.py
@@ -72,6 +72,7 @@ class AutocompleteTestCase(BaseTestCase):
"codes-conservation-office-autocomplete",
"share-user-autocomplete",
"share-team-autocomplete",
+ "team-admin-autocomplete",
]
for test in tests:
self.client.login(username=self.superuser.username, password=self.superuser_pw)
diff --git a/konova/tests/test_views.py b/konova/tests/test_views.py
index 28b3f8a2..73029f96 100644
--- a/konova/tests/test_views.py
+++ b/konova/tests/test_views.py
@@ -6,9 +6,11 @@ Created on: 26.10.21
"""
import datetime
+import json
from codelist.settings import CODELIST_CONSERVATION_OFFICE_ID
from ema.models import Ema
+from konova.sub_settings.lanis_settings import DEFAULT_SRID_RLP
from user.models import User, Team
from django.contrib.auth.models import Group
from django.contrib.gis.geos import MultiPolygon, Polygon
@@ -272,7 +274,6 @@ class BaseTestCase(TestCase):
team = Team.objects.get_or_create(
name="Testteam",
description="Testdescription",
- admin=self.superuser,
)[0]
team.users.add(self.superuser)
@@ -287,8 +288,28 @@ class BaseTestCase(TestCase):
"""
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
+ polygon = polygon.transform(DEFAULT_SRID_RLP, clone=True)
+ return MultiPolygon(polygon, srid=DEFAULT_SRID_RLP)
+
+ def create_geojson(self, geometry):
+ """ Creates a default structure including geojson from a geometry
+
+ Args:
+ geometry ():
+
+ Returns:
+
+ """
+ geom_json = {
+ "features": [
+ {
+ "type": "Feature",
+ "geometry": json.loads(geometry.geojson),
+ }
+ ]
+ }
+ geom_json = json.dumps(geom_json)
+ return geom_json
def create_dummy_handler(self) -> Handler:
""" Creates a Handler
@@ -410,11 +431,12 @@ class BaseTestCase(TestCase):
return
if geom1.srid != geom2.srid:
+ tolerance = 0.001
# Due to prior possible transformation of any of these geometries, we need to make sure there exists a
# transformation from one coordinate system into the other, which is valid
geom1_t = geom1.transform(geom2.srid, clone=True)
geom2_t = geom2.transform(geom1.srid, clone=True)
- self.assertTrue(geom1_t.equals(geom2) or geom2_t.equals(geom1))
+ self.assertTrue(geom1_t.equals_exact(geom2, tolerance) or geom2_t.equals_exact(geom1, tolerance))
else:
self.assertTrue(geom1.equals(geom2))
diff --git a/konova/urls.py b/konova/urls.py
index f6ca3a32..45d1f82f 100644
--- a/konova/urls.py
+++ b/konova/urls.py
@@ -21,10 +21,10 @@ from konova.autocompletes import EcoAccountAutocomplete, \
InterventionAutocomplete, CompensationActionCodeAutocomplete, BiotopeCodeAutocomplete, LawCodeAutocomplete, \
RegistrationOfficeCodeAutocomplete, ConservationOfficeCodeAutocomplete, ProcessTypeCodeAutocomplete, \
ShareUserAutocomplete, BiotopeExtraCodeAutocomplete, CompensationActionDetailCodeAutocomplete, \
- ShareTeamAutocomplete, HandlerCodeAutocomplete, CompensationHandlerCodeAutocomplete
+ ShareTeamAutocomplete, HandlerCodeAutocomplete, TeamAdminAutocomplete, CompensationHandlerCodeAutocomplete
from konova.settings import SSO_SERVER, SSO_PUBLIC_KEY, SSO_PRIVATE_KEY, DEBUG
from konova.sso.sso import KonovaSSOClient
-from konova.views import logout_view, home_view, get_geom_parcels
+from konova.views import logout_view, home_view, get_geom_parcels, get_geom_parcels_content, map_client_proxy_view
sso_client = KonovaSSOClient(SSO_SERVER, SSO_PUBLIC_KEY, SSO_PRIVATE_KEY)
urlpatterns = [
@@ -40,7 +40,9 @@ urlpatterns = [
path('cl/', include("codelist.urls")),
path('analysis/', include("analysis.urls")),
path('api/', include("api.urls")),
- path('geom/ /parcels', get_geom_parcels, name="geometry-parcels"),
+ path('geom//parcels/', get_geom_parcels, name="geometry-parcels"),
+ path('geom//parcels/', get_geom_parcels_content, name="geometry-parcels-content"),
+ path('client/proxy', map_client_proxy_view, name="map-client-proxy"),
# Autocomplete paths for all apps
path("atcmplt/eco-accounts", EcoAccountAutocomplete.as_view(), name="accounts-autocomplete"),
@@ -57,6 +59,7 @@ urlpatterns = [
path("atcmplt/codes/comp/handler", CompensationHandlerCodeAutocomplete.as_view(), name="codes-compensation-handler-autocomplete"),
path("atcmplt/share/u", ShareUserAutocomplete.as_view(), name="share-user-autocomplete"),
path("atcmplt/share/t", ShareTeamAutocomplete.as_view(), name="share-team-autocomplete"),
+ path("atcmplt/team/admin", TeamAdminAutocomplete.as_view(), name="team-admin-autocomplete"),
]
if DEBUG:
diff --git a/konova/utils/message_templates.py b/konova/utils/message_templates.py
index 64b7eab1..f6e3ca18 100644
--- a/konova/utils/message_templates.py
+++ b/konova/utils/message_templates.py
@@ -17,6 +17,7 @@ IDENTIFIER_REPLACED = _("The identifier '{}' had to be changed to '{}' since ano
ENTRY_REMOVE_MISSING_PERMISSION = _("Only conservation or registration office users are allowed to remove entries.")
MISSING_GROUP_PERMISSION = _("You need to be part of another user group.")
CHECKED_RECORDED_RESET = _("Status of Checked and Recorded reseted")
+RECORDED_BLOCKS_EDIT = _("Entry is recorded. To edit data, the entry first needs to be unrecorded.")
# SHARE
DATA_UNSHARED = _("This data is not shared with you")
@@ -80,3 +81,8 @@ GEOMETRY_CONFLICT_WITH_TEMPLATE = _("Geometry conflict detected with {}")
# INTERVENTION
INTERVENTION_HAS_REVOCATIONS_TEMPLATE = _("This intervention has {} revocations")
+
+# CHECKED
+DATA_CHECKED_ON_TEMPLATE = _("Checked on {} by {}")
+DATA_CHECKED_PREVIOUSLY_TEMPLATE = _("Data has changed since last check on {} by {}")
+DATA_IS_UNCHECKED = _("Current data not checked yet")
diff --git a/konova/utils/tables.py b/konova/utils/tables.py
index d3a0406b..d7468725 100644
--- a/konova/utils/tables.py
+++ b/konova/utils/tables.py
@@ -112,6 +112,17 @@ class BaseTable(tables.tables.Table):
icon
)
+ def render_previously_checked_star(self, tooltip: str = None):
+ """
+ Returns a star icon for a check action in the past
+ """
+ icon = "fas fa-star rlp-gd-inv"
+ return format_html(
+ "",
+ tooltip,
+ icon
+ )
+
def render_bookmark(self, tooltip: str = None, icn_filled: bool = False):
"""
Returns a bookmark icon
diff --git a/konova/utils/wfs/spatial.py b/konova/utils/wfs/spatial.py
index 8a7bf360..e6cae847 100644
--- a/konova/utils/wfs/spatial.py
+++ b/konova/utils/wfs/spatial.py
@@ -5,12 +5,13 @@ Contact: michel.peltriaux@sgdnord.rlp.de
Created on: 17.12.21
"""
+import json
from abc import abstractmethod
+from json import JSONDecodeError
from time import sleep
import requests
-import xmltodict
-from django.contrib.gis.db.models.functions import AsGML, Transform
+from django.contrib.gis.db.models.functions import AsGML, Transform, MakeValid
from requests.auth import HTTPDigestAuth
from konova.settings import DEFAULT_SRID_RLP, PARCEL_WFS_USER, PARCEL_WFS_PW, PROXIES
@@ -90,7 +91,7 @@ class ParcelWFSFetcher(AbstractWFSFetcher):
).annotate(
transformed=Transform(srid=filter_srid, expression="geom")
).annotate(
- gml=AsGML('transformed')
+ gml=AsGML(MakeValid('transformed'))
).first().gml
spatial_filter = f"<{geometry_operation}>{self.geometry_property_name}{geom_gml}{geometry_operation}>"
return spatial_filter
@@ -115,7 +116,7 @@ class ParcelWFSFetcher(AbstractWFSFetcher):
geometry_operation,
filter_srid
)
- _filter = f'{spatial_filter}'
+ _filter = f'{spatial_filter}'
return _filter
def get_features(self,
@@ -139,7 +140,7 @@ class ParcelWFSFetcher(AbstractWFSFetcher):
Returns:
features (list): A list of returned features
"""
- features = []
+ found_features = []
while start_index is not None:
post_body = self._create_post_data(
spatial_operator,
@@ -155,19 +156,11 @@ class ParcelWFSFetcher(AbstractWFSFetcher):
)
content = response.content.decode("utf-8")
- content = xmltodict.parse(content)
- collection = content.get(
- "wfs:FeatureCollection",
- {},
- )
-
- # Check if collection is an exception and does not contain the requested data
- if len(collection) == 0:
- exception = content.get(
- "ows:ExceptionReport",
- {}
- )
- if len(exception) > 0 and rerun_on_exception:
+ try:
+ # Check if collection is an exception and does not contain the requested data
+ content = json.loads(content)
+ except JSONDecodeError as e:
+ if rerun_on_exception:
# Wait a second before another try
sleep(1)
self.get_features(
@@ -177,22 +170,21 @@ class ParcelWFSFetcher(AbstractWFSFetcher):
start_index,
rerun_on_exception=False
)
-
- members = collection.get(
- "wfs:member",
- None,
- )
- if members is not None:
- if len(members) > 1:
- # extend feature list with found list of new feature members
- features += members
else:
- # convert single found feature member into list and extent feature list
- features += [members]
+ e.msg += content
+ raise e
+ fetched_features = content.get(
+ "features",
+ {},
+ )
- if collection.get("@next", None) is not None:
- start_index += self.count
- else:
+ found_features += fetched_features
+
+ if len(fetched_features) < self.count:
+ # The response was not 'full', so we got everything to fetch
start_index = None
+ else:
+ # If a 'full' response returned, there might be more to fetch. Increase the start_index!
+ start_index += self.count
- return features
+ return found_features
diff --git a/konova/views.py b/konova/views.py
index db967e85..4fadf6a0 100644
--- a/konova/views.py
+++ b/konova/views.py
@@ -5,9 +5,12 @@ Contact: michel.peltriaux@sgdnord.rlp.de
Created on: 16.11.20
"""
+import json
+
+import requests
from django.contrib.auth import logout
from django.contrib.auth.decorators import login_required
-from django.http import HttpRequest, HttpResponse
+from django.http import HttpRequest, HttpResponse, JsonResponse
from django.shortcuts import redirect, render, get_object_or_404
from django.template.loader import render_to_string
from django.utils import timezone
@@ -17,7 +20,7 @@ from compensation.models import Compensation, EcoAccount
from intervention.models import Intervention
from konova.contexts import BaseContext
from konova.decorators import any_group_check
-from konova.models import Deadline, Geometry
+from konova.models import Deadline, Geometry, Municipal
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
from news.models import ServerMessage
from konova.settings import SSO_SERVER_BASE
@@ -110,12 +113,12 @@ def get_geom_parcels(request: HttpRequest, id: str):
id (str): The geometry's id
Returns:
-
+ A rendered piece of HTML
"""
# HTTP code 286 states that the HTMX should stop polling for updates
# https://htmx.org/docs/#polling
status_code = 286
- template = "konova/includes/parcel_table.html"
+ template = "konova/includes/parcels/parcel_table_frame.html"
geom = get_object_or_404(Geometry, id=id)
parcels = geom.get_underlying_parcels()
geos_geom = geom.geom
@@ -130,8 +133,23 @@ def get_geom_parcels(request: HttpRequest, id: str):
status_code = 200
if parcels_available or no_geometry_given:
+ parcels = parcels.order_by("-municipal", "flr", "flrstck_zhlr", "flrstck_nnr")
+ municipals = parcels.order_by("municipal").distinct("municipal").values("municipal__id")
+ municipals = Municipal.objects.filter(id__in=municipals)
+
+ rpp = 100
+ num_all_parcels = parcels.count()
+ parcels = parcels[:rpp]
+ next_page = 1
+ if len(parcels) < rpp:
+ next_page = None
+
context = {
+ "num_parcels": num_all_parcels,
"parcels": parcels,
+ "municipals": municipals,
+ "geom_id": str(id),
+ "next_page": next_page,
}
html = render_to_string(template, context, request)
return HttpResponse(html, status=status_code)
@@ -139,6 +157,49 @@ def get_geom_parcels(request: HttpRequest, id: str):
return HttpResponse(None, status=404)
+@login_required
+def get_geom_parcels_content(request: HttpRequest, id: str, page: int):
+ """ Getter for infinite scroll of HTMX
+
+ Returns parcels of a specific page/slice of the found parcel set.
+ Implementation of infinite scroll htmx example: https://htmx.org/examples/infinite-scroll/
+
+ Args:
+ request (HttpRequest): The incoming request
+ id (str): The geometry's id
+ page (int): The requested page number
+
+ Returns:
+ A rendered piece of HTML
+ """
+ if page < 0:
+ raise AssertionError("Parcel page can not be negative")
+
+ # HTTP code 286 states that the HTMX should stop polling for updates
+ # https://htmx.org/docs/#polling
+ status_code = 286
+ template = "konova/includes/parcels/parcel_table_content.html"
+ geom = get_object_or_404(Geometry, id=id)
+ parcels = geom.get_underlying_parcels()
+
+ parcels = parcels.order_by("-municipal", "flr", "flrstck_zhlr", "flrstck_nnr")
+ rpp = 100
+ from_p = rpp * (page-1)
+ to_p = rpp * (page)
+ next_page = page + 1
+ parcels = parcels[from_p:to_p]
+ if len(parcels) < rpp:
+ next_page = None
+
+ context = {
+ "parcels": parcels,
+ "geom_id": str(id),
+ "next_page": next_page,
+ }
+ html = render_to_string(template, context, request)
+ return HttpResponse(html, status=status_code)
+
+
def get_404_view(request: HttpRequest, exception=None):
""" Returns a 404 handling view
@@ -164,3 +225,26 @@ def get_500_view(request: HttpRequest):
"""
context = BaseContext.context
return render(request, "500.html", context, status=500)
+
+
+@login_required
+def map_client_proxy_view(request: HttpRequest):
+ """ Provides proxy functionality for NETGIS map client.
+
+ Used for fetching content of a provided url
+
+ Args:
+ request (HttpRequest): The incoming request
+
+ Returns:
+
+ """
+ url = request.META.get("QUERY_STRING")
+ response = requests.get(url)
+ body = json.loads(response.content)
+ if response.status_code != 200:
+ return JsonResponse({
+ "status_code": response.status_code,
+ "content": body,
+ })
+ return JsonResponse(body)
diff --git a/locale/de/LC_MESSAGES/django.mo b/locale/de/LC_MESSAGES/django.mo
index b3bf61fd..885f0b9d 100644
Binary files a/locale/de/LC_MESSAGES/django.mo and b/locale/de/LC_MESSAGES/django.mo differ
diff --git a/locale/de/LC_MESSAGES/django.po b/locale/de/LC_MESSAGES/django.po
index e8786ab9..93238630 100644
--- a/locale/de/LC_MESSAGES/django.po
+++ b/locale/de/LC_MESSAGES/django.po
@@ -3,9 +3,9 @@
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR , YEAR.
#
-#: compensation/filters.py:123 compensation/forms/modalForms.py:36
-#: compensation/forms/modalForms.py:47 compensation/forms/modalForms.py:63
-#: compensation/forms/modalForms.py:358 compensation/forms/modalForms.py:466
+#: compensation/filters.py:123 compensation/forms/modalForms.py:37
+#: compensation/forms/modalForms.py:48 compensation/forms/modalForms.py:64
+#: compensation/forms/modalForms.py:362 compensation/forms/modalForms.py:470
#: intervention/forms/forms.py:54 intervention/forms/forms.py:174
#: intervention/forms/forms.py:186 intervention/forms/modalForms.py:150
#: intervention/forms/modalForms.py:163 intervention/forms/modalForms.py:176
@@ -15,18 +15,18 @@
#: konova/filters/mixins.py:107 konova/filters/mixins.py:108
#: konova/filters/mixins.py:120 konova/filters/mixins.py:121
#: konova/filters/mixins.py:134 konova/filters/mixins.py:135
-#: konova/filters/mixins.py:270 konova/filters/mixins.py:316
-#: konova/filters/mixins.py:354 konova/filters/mixins.py:355
-#: konova/filters/mixins.py:386 konova/filters/mixins.py:387
-#: konova/forms.py:143 konova/forms.py:244 konova/forms.py:315
-#: konova/forms.py:359 konova/forms.py:369 konova/forms.py:382
-#: konova/forms.py:394 konova/forms.py:412 user/forms.py:42
+#: konova/filters/mixins.py:277 konova/filters/mixins.py:323
+#: konova/filters/mixins.py:361 konova/filters/mixins.py:362
+#: konova/filters/mixins.py:393 konova/filters/mixins.py:394
+#: konova/forms.py:179 konova/forms.py:281 konova/forms.py:395
+#: konova/forms.py:439 konova/forms.py:449 konova/forms.py:462
+#: konova/forms.py:474 konova/forms.py:492 user/forms.py:42
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2022-03-03 12:08+0100\n"
+"POT-Creation-Date: 2022-05-31 13:05+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME \n"
"Language-Team: LANGUAGE \n"
@@ -52,7 +52,7 @@ msgstr "Bis"
#: intervention/forms/forms.py:102
#: intervention/templates/intervention/detail/view.html:56
#: intervention/templates/intervention/report/report.html:37
-#: intervention/utils/quality.py:49 konova/filters/mixins.py:396
+#: intervention/utils/quality.py:49 konova/filters/mixins.py:403
msgid "Conservation office"
msgstr "Eintragungsstelle"
@@ -61,11 +61,11 @@ msgid "Select the responsible office"
msgstr "Verantwortliche Stelle"
#: analysis/forms.py:58 compensation/forms/forms.py:88
-#: compensation/forms/forms.py:118 compensation/forms/forms.py:183
+#: compensation/forms/forms.py:118 compensation/forms/forms.py:199
#: intervention/forms/forms.py:64 intervention/forms/forms.py:81
#: intervention/forms/forms.py:97 intervention/forms/forms.py:113
#: intervention/forms/forms.py:154 intervention/forms/modalForms.py:49
-#: intervention/forms/modalForms.py:63 user/forms.py:196
+#: intervention/forms/modalForms.py:63 user/forms.py:196 user/forms.py:260
msgid "Click for selection"
msgstr "Auswählen..."
@@ -77,7 +77,7 @@ msgstr "Bericht generieren"
msgid "Select a timespan and the desired conservation office"
msgstr "Wählen Sie die Zeitspanne und die gewünschte Eintragungsstelle"
-#: analysis/forms.py:69 konova/forms.py:191
+#: analysis/forms.py:69 konova/forms.py:227
msgid "Continue"
msgstr "Weiter"
@@ -97,7 +97,7 @@ msgstr ""
#: analysis/templates/analysis/reports/includes/eco_account/amount.html:3
#: analysis/templates/analysis/reports/includes/intervention/amount.html:3
#: analysis/templates/analysis/reports/includes/old_data/amount.html:3
-#: compensation/forms/modalForms.py:450
+#: compensation/forms/modalForms.py:454
#: compensation/templates/compensation/detail/eco_account/includes/deductions.html:34
#: intervention/templates/intervention/detail/includes/deductions.html:31
msgid "Amount"
@@ -138,11 +138,11 @@ msgstr "Zuständigkeitsbereich"
#: analysis/templates/analysis/reports/includes/intervention/amount.html:17
#: analysis/templates/analysis/reports/includes/intervention/compensated_by.html:8
#: analysis/templates/analysis/reports/includes/intervention/laws.html:17
-#: compensation/tables.py:40
-#: compensation/templates/compensation/detail/compensation/view.html:64
-#: intervention/tables.py:39
+#: compensation/tables.py:38
+#: compensation/templates/compensation/detail/compensation/view.html:74
+#: intervention/tables.py:38
#: intervention/templates/intervention/detail/view.html:68
-#: user/models/user_action.py:20
+#: user/models/user_action.py:21
msgid "Checked"
msgstr "Geprüft"
@@ -154,14 +154,14 @@ msgstr "Geprüft"
#: analysis/templates/analysis/reports/includes/intervention/compensated_by.html:9
#: analysis/templates/analysis/reports/includes/intervention/laws.html:20
#: analysis/templates/analysis/reports/includes/old_data/amount.html:18
-#: compensation/tables.py:46 compensation/tables.py:222
-#: compensation/templates/compensation/detail/compensation/view.html:78
+#: compensation/tables.py:44 compensation/tables.py:219
+#: compensation/templates/compensation/detail/compensation/view.html:93
#: compensation/templates/compensation/detail/eco_account/includes/deductions.html:31
#: compensation/templates/compensation/detail/eco_account/view.html:45
#: ema/tables.py:44 ema/templates/ema/detail/view.html:35
-#: intervention/tables.py:45
-#: intervention/templates/intervention/detail/view.html:82
-#: user/models/user_action.py:21
+#: intervention/tables.py:44
+#: intervention/templates/intervention/detail/view.html:87
+#: user/models/user_action.py:22
msgid "Recorded"
msgstr "Verzeichnet"
@@ -198,7 +198,7 @@ msgid "Other registration office"
msgstr "Andere Zulassungsbehörden"
#: analysis/templates/analysis/reports/includes/compensation/card_compensation.html:11
-#: compensation/tables.py:67
+#: compensation/tables.py:65
#: intervention/templates/intervention/detail/includes/compensations.html:8
#: intervention/templates/intervention/report/report.html:45
msgid "Compensations"
@@ -215,7 +215,7 @@ msgstr "Abbuchungen"
#: analysis/templates/analysis/reports/includes/eco_account/deductions.html:9
#: analysis/templates/analysis/reports/includes/eco_account/deductions.html:11
-#: compensation/forms/modalForms.py:195
+#: compensation/forms/modalForms.py:187
#: compensation/templates/compensation/detail/compensation/includes/states-after.html:36
#: compensation/templates/compensation/detail/compensation/includes/states-before.html:36
#: compensation/templates/compensation/detail/eco_account/includes/states-after.html:36
@@ -227,7 +227,7 @@ msgid "Surface"
msgstr "Fläche"
#: analysis/templates/analysis/reports/includes/intervention/card_intervention.html:10
-#: intervention/tables.py:66
+#: intervention/tables.py:65
msgid "Interventions"
msgstr "Eingriffe"
@@ -248,7 +248,7 @@ msgid "Compensation"
msgstr "Kompensation"
#: analysis/templates/analysis/reports/includes/intervention/compensated_by.html:21
-#: compensation/forms/modalForms.py:76
+#: compensation/forms/modalForms.py:77
msgid "Payment"
msgstr "Zahlung"
@@ -285,8 +285,8 @@ msgid "Type"
msgstr "Typ"
#: analysis/templates/analysis/reports/includes/old_data/amount.html:24
-#: compensation/tables.py:89 intervention/forms/modalForms.py:375
-#: intervention/forms/modalForms.py:382 intervention/tables.py:88
+#: compensation/tables.py:87 intervention/forms/modalForms.py:375
+#: intervention/forms/modalForms.py:382 intervention/tables.py:87
#: intervention/templates/intervention/detail/view.html:19
#: konova/templates/konova/includes/quickstart/interventions.html:4
#: templates/navbars/navbar.html:22
@@ -294,7 +294,7 @@ msgid "Intervention"
msgstr "Eingriff"
#: analysis/templates/analysis/reports/includes/old_data/amount.html:34
-#: compensation/tables.py:266
+#: compensation/tables.py:263
#: compensation/templates/compensation/detail/eco_account/view.html:20
#: intervention/forms/modalForms.py:348 intervention/forms/modalForms.py:355
#: konova/templates/konova/includes/quickstart/ecoaccounts.html:4
@@ -314,9 +314,9 @@ msgstr "Vor"
msgid "Show only unrecorded"
msgstr "Nur unverzeichnete anzeigen"
-#: compensation/forms/forms.py:32 compensation/tables.py:25
-#: compensation/tables.py:197 ema/tables.py:29 intervention/forms/forms.py:28
-#: intervention/tables.py:24
+#: compensation/forms/forms.py:32 compensation/tables.py:23
+#: compensation/tables.py:194 ema/tables.py:29 intervention/forms/forms.py:28
+#: intervention/tables.py:23
#: intervention/templates/intervention/detail/includes/compensations.html:30
msgid "Identifier"
msgstr "Kennung"
@@ -326,8 +326,8 @@ msgstr "Kennung"
msgid "Generated automatically"
msgstr "Automatisch generiert"
-#: compensation/forms/forms.py:44 compensation/tables.py:30
-#: compensation/tables.py:202
+#: compensation/forms/forms.py:44 compensation/tables.py:28
+#: compensation/tables.py:199
#: compensation/templates/compensation/detail/compensation/includes/documents.html:28
#: compensation/templates/compensation/detail/compensation/view.html:32
#: compensation/templates/compensation/detail/eco_account/includes/documents.html:28
@@ -337,12 +337,12 @@ msgstr "Automatisch generiert"
#: ema/tables.py:34 ema/templates/ema/detail/includes/documents.html:28
#: ema/templates/ema/detail/view.html:31
#: ema/templates/ema/report/report.html:12 intervention/forms/forms.py:40
-#: intervention/tables.py:29
+#: intervention/tables.py:28
#: intervention/templates/intervention/detail/includes/compensations.html:33
#: 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:358
+#: konova/forms.py:438
msgid "Title"
msgstr "Bezeichnung"
@@ -350,12 +350,12 @@ msgstr "Bezeichnung"
msgid "An explanatory name"
msgstr "Aussagekräftiger Titel"
-#: compensation/forms/forms.py:50 ema/forms.py:50 ema/forms.py:108
+#: compensation/forms/forms.py:50 ema/forms.py:52 ema/forms.py:112
msgid "Compensation XY; Location ABC"
msgstr "Kompensation XY; Flur ABC"
-#: compensation/forms/forms.py:57 compensation/forms/modalForms.py:62
-#: compensation/forms/modalForms.py:357 compensation/forms/modalForms.py:465
+#: compensation/forms/forms.py:57 compensation/forms/modalForms.py:63
+#: compensation/forms/modalForms.py:361 compensation/forms/modalForms.py:469
#: compensation/templates/compensation/detail/compensation/includes/actions.html:35
#: compensation/templates/compensation/detail/compensation/includes/deadlines.html:34
#: compensation/templates/compensation/detail/compensation/includes/documents.html:34
@@ -369,11 +369,11 @@ msgstr "Kompensation XY; Flur ABC"
#: intervention/templates/intervention/detail/includes/documents.html:34
#: intervention/templates/intervention/detail/includes/payments.html:34
#: intervention/templates/intervention/detail/includes/revocation.html:38
-#: konova/forms.py:393 konova/templates/konova/includes/comment_card.html:16
+#: konova/forms.py:473 konova/templates/konova/includes/comment_card.html:16
msgid "Comment"
msgstr "Kommentar"
-#: compensation/forms/forms.py:59 compensation/forms/modalForms.py:467
+#: compensation/forms/forms.py:59 compensation/forms/modalForms.py:471
#: intervention/forms/forms.py:200
msgid "Additional comment"
msgstr "Zusätzlicher Kommentar"
@@ -431,118 +431,131 @@ msgid ""
"Optionally: Whether this compensation is a coherence keeping compensation?"
msgstr "Optional: Handelt es sich um eine Kohärenzsicherungsmaßnahme?"
-#: compensation/forms/forms.py:174
+#: compensation/forms/forms.py:169
+#: compensation/templates/compensation/detail/compensation/view.html:44
+#: compensation/templates/compensation/detail/eco_account/view.html:75
+#: ema/templates/ema/detail/view.html:61
+msgid "Is PIK"
+msgstr "Ist PIK Maßnahme"
+
+#: compensation/forms/forms.py:170
+msgid ""
+"Optionally: Whether this compensation is a compensation integrated in "
+"production?"
+msgstr "Optional: Handelt es sich um eine produktionsintegrierte Kompensation?"
+
+#: compensation/forms/forms.py:190
#: compensation/templates/compensation/detail/compensation/view.html:36
#: compensation/templates/compensation/report/compensation/report.html:16
msgid "compensates intervention"
msgstr "kompensiert Eingriff"
-#: compensation/forms/forms.py:176
+#: compensation/forms/forms.py:192
msgid "Select the intervention for which this compensation compensates"
msgstr "Wählen Sie den Eingriff, für den diese Kompensation bestimmt ist"
-#: compensation/forms/forms.py:202 compensation/views/compensation.py:96
+#: compensation/forms/forms.py:219 compensation/views/compensation.py:110
msgid "New compensation"
msgstr "Neue Kompensation"
-#: compensation/forms/forms.py:273
+#: compensation/forms/forms.py:292
msgid "Edit compensation"
msgstr "Bearbeite Kompensation"
-#: compensation/forms/forms.py:334 compensation/utils/quality.py:84
+#: compensation/forms/forms.py:356 compensation/utils/quality.py:84
msgid "Available Surface"
msgstr "Verfügbare Fläche"
-#: compensation/forms/forms.py:337
+#: compensation/forms/forms.py:359
msgid "The amount that can be used for deductions"
msgstr "Die für Abbuchungen zur Verfügung stehende Menge"
-#: compensation/forms/forms.py:346
+#: compensation/forms/forms.py:368
#: compensation/templates/compensation/detail/eco_account/view.html:67
#: compensation/utils/quality.py:72
msgid "Agreement date"
msgstr "Vereinbarungsdatum"
-#: compensation/forms/forms.py:348
+#: compensation/forms/forms.py:370
msgid "When did the parties agree on this?"
msgstr "Wann wurde dieses Ökokonto offiziell vereinbart?"
-#: compensation/forms/forms.py:373 compensation/views/eco_account.py:107
+#: compensation/forms/forms.py:396 compensation/views/eco_account.py:108
msgid "New Eco-Account"
msgstr "Neues Ökokonto"
-#: compensation/forms/forms.py:382
+#: compensation/forms/forms.py:405
msgid "Eco-Account XY; Location ABC"
msgstr "Ökokonto XY; Flur ABC"
-#: compensation/forms/forms.py:442
+#: compensation/forms/forms.py:467
msgid "Edit Eco-Account"
msgstr "Ökokonto bearbeiten"
-#: compensation/forms/modalForms.py:37
+#: compensation/forms/modalForms.py:38
msgid "in Euro"
msgstr "in Euro"
-#: compensation/forms/modalForms.py:46
+#: compensation/forms/modalForms.py:47
#: intervention/templates/intervention/detail/includes/payments.html:31
msgid "Due on"
msgstr "Fällig am"
-#: compensation/forms/modalForms.py:49
+#: compensation/forms/modalForms.py:50
msgid "Due on which date"
msgstr "Zahlung wird an diesem Datum erwartet"
-#: compensation/forms/modalForms.py:64 compensation/forms/modalForms.py:359
-#: intervention/forms/modalForms.py:177 konova/forms.py:395
+#: compensation/forms/modalForms.py:65 compensation/forms/modalForms.py:363
+#: intervention/forms/modalForms.py:177 konova/forms.py:475
msgid "Additional comment, maximum {} letters"
msgstr "Zusätzlicher Kommentar, maximal {} Zeichen"
-#: compensation/forms/modalForms.py:77
+#: compensation/forms/modalForms.py:78
msgid "Add a payment for intervention '{}'"
msgstr "Neue Ersatzzahlung zu Eingriff '{}' hinzufügen"
-#: compensation/forms/modalForms.py:97
+#: compensation/forms/modalForms.py:98
msgid "If there is no date you can enter, please explain why."
msgstr "Falls Sie kein Datum angeben können, erklären Sie bitte weshalb."
-#: compensation/forms/modalForms.py:116
+#: compensation/forms/modalForms.py:117
#: intervention/templates/intervention/detail/includes/payments.html:59
msgid "Edit payment"
msgstr "Zahlung bearbeiten"
-#: compensation/forms/modalForms.py:159 compensation/forms/modalForms.py:171
+#: compensation/forms/modalForms.py:161
msgid "Biotope Type"
msgstr "Biotoptyp"
-#: compensation/forms/modalForms.py:162
+#: compensation/forms/modalForms.py:164
msgid "Select the biotope type"
msgstr "Biotoptyp wählen"
-#: compensation/forms/modalForms.py:176 compensation/forms/modalForms.py:188
+#: compensation/forms/modalForms.py:168 compensation/forms/modalForms.py:180
msgid "Biotope additional type"
msgstr "Zusatzbezeichnung"
-#: compensation/forms/modalForms.py:179
+#: compensation/forms/modalForms.py:171
msgid "Select an additional biotope type"
msgstr "Zusatzbezeichnung wählen"
-#: compensation/forms/modalForms.py:198 intervention/forms/modalForms.py:366
+#: compensation/forms/modalForms.py:190 intervention/forms/modalForms.py:366
msgid "in m²"
msgstr ""
-#: compensation/forms/modalForms.py:209
+#: compensation/forms/modalForms.py:201
msgid "New state"
msgstr "Neuer Zustand"
-#: compensation/forms/modalForms.py:210
+#: compensation/forms/modalForms.py:202
msgid "Insert data for the new state"
msgstr "Geben Sie die Daten des neuen Zustandes ein"
-#: compensation/forms/modalForms.py:217 konova/forms.py:193
+#: compensation/forms/modalForms.py:219 konova/forms.py:229
msgid "Object removed"
msgstr "Objekt entfernt"
-#: compensation/forms/modalForms.py:272
+#: compensation/forms/modalForms.py:274
#: compensation/templates/compensation/detail/compensation/includes/states-after.html:62
#: compensation/templates/compensation/detail/compensation/includes/states-before.html:62
#: compensation/templates/compensation/detail/eco_account/includes/states-after.html:62
@@ -552,15 +565,15 @@ msgstr "Objekt entfernt"
msgid "Edit state"
msgstr "Zustand bearbeiten"
-#: compensation/forms/modalForms.py:329
+#: compensation/forms/modalForms.py:333
msgid "Deadline Type"
msgstr "Fristart"
-#: compensation/forms/modalForms.py:332
+#: compensation/forms/modalForms.py:336
msgid "Select the deadline type"
msgstr "Fristart wählen"
-#: compensation/forms/modalForms.py:341
+#: compensation/forms/modalForms.py:345
#: compensation/templates/compensation/detail/compensation/includes/deadlines.html:31
#: compensation/templates/compensation/detail/eco_account/includes/deadlines.html:31
#: ema/templates/ema/detail/includes/deadlines.html:31
@@ -568,30 +581,30 @@ msgstr "Fristart wählen"
msgid "Date"
msgstr "Datum"
-#: compensation/forms/modalForms.py:344
+#: compensation/forms/modalForms.py:348
msgid "Select date"
msgstr "Datum wählen"
-#: compensation/forms/modalForms.py:371
+#: compensation/forms/modalForms.py:375
msgid "New deadline"
msgstr "Neue Frist"
-#: compensation/forms/modalForms.py:372
+#: compensation/forms/modalForms.py:376
msgid "Insert data for the new deadline"
msgstr "Geben Sie die Daten der neuen Frist ein"
-#: compensation/forms/modalForms.py:385
+#: compensation/forms/modalForms.py:389
#: compensation/templates/compensation/detail/compensation/includes/deadlines.html:59
#: compensation/templates/compensation/detail/eco_account/includes/deadlines.html:57
#: ema/templates/ema/detail/includes/deadlines.html:57
msgid "Edit deadline"
msgstr "Frist/Termin bearbeiten"
-#: compensation/forms/modalForms.py:413
+#: compensation/forms/modalForms.py:417
msgid "Action Type"
msgstr "Maßnahmentyp"
-#: compensation/forms/modalForms.py:416
+#: compensation/forms/modalForms.py:420
msgid ""
"An action can consist of multiple different action types. All the selected "
"action types are expected to be performed according to the amount and unit "
@@ -601,35 +614,35 @@ msgstr ""
"hier gewählten Einträge sollen mit der weiter unten angegebenen Einheit und "
"Menge umgesetzt werden. "
-#: compensation/forms/modalForms.py:421 compensation/forms/modalForms.py:433
+#: compensation/forms/modalForms.py:425 compensation/forms/modalForms.py:437
msgid "Action Type detail"
msgstr "Zusatzmerkmal"
-#: compensation/forms/modalForms.py:424
+#: compensation/forms/modalForms.py:428
msgid "Select the action type detail"
msgstr "Zusatzmerkmal wählen"
-#: compensation/forms/modalForms.py:438
+#: compensation/forms/modalForms.py:442
msgid "Unit"
msgstr "Einheit"
-#: compensation/forms/modalForms.py:441
+#: compensation/forms/modalForms.py:445
msgid "Select the unit"
msgstr "Einheit wählen"
-#: compensation/forms/modalForms.py:453
+#: compensation/forms/modalForms.py:457
msgid "Insert the amount"
msgstr "Menge eingeben"
-#: compensation/forms/modalForms.py:478
+#: compensation/forms/modalForms.py:482
msgid "New action"
msgstr "Neue Maßnahme"
-#: compensation/forms/modalForms.py:479
+#: compensation/forms/modalForms.py:483
msgid "Insert data for the new action"
msgstr "Geben Sie die Daten der neuen Maßnahme ein"
-#: compensation/forms/modalForms.py:503
+#: compensation/forms/modalForms.py:507
#: compensation/templates/compensation/detail/compensation/includes/actions.html:68
#: compensation/templates/compensation/detail/eco_account/includes/actions.html:67
#: ema/templates/ema/detail/includes/actions.html:65
@@ -675,70 +688,62 @@ msgstr ""
"Es wurde bereits mehr Fläche abgebucht, als Sie nun als abbuchbar einstellen "
"wollen. Kontaktieren Sie die für die Abbuchungen verantwortlichen Nutzer!"
-#: compensation/tables.py:35 compensation/tables.py:207 ema/tables.py:39
-#: intervention/tables.py:34 konova/filters/mixins.py:98
+#: compensation/tables.py:33 compensation/tables.py:204 ema/tables.py:39
+#: intervention/tables.py:33 konova/filters/mixins.py:98
msgid "Parcel gmrkng"
msgstr "Gemarkung"
-#: compensation/tables.py:52 compensation/tables.py:228 ema/tables.py:50
-#: intervention/tables.py:51
+#: compensation/tables.py:50 compensation/tables.py:225 ema/tables.py:50
+#: intervention/tables.py:50
msgid "Editable"
msgstr "Freigegeben"
-#: compensation/tables.py:58 compensation/tables.py:234 ema/tables.py:56
-#: intervention/tables.py:57
+#: compensation/tables.py:56 compensation/tables.py:231 ema/tables.py:56
+#: intervention/tables.py:56
msgid "Last edit"
msgstr "Zuletzt bearbeitet"
-#: compensation/tables.py:89 compensation/tables.py:266 ema/tables.py:89
-#: intervention/tables.py:88
+#: compensation/tables.py:87 compensation/tables.py:263 ema/tables.py:89
+#: intervention/tables.py:87
msgid "Open {}"
msgstr "Öffne {}"
-#: compensation/tables.py:114 intervention/tables.py:111
-msgid "Not checked yet"
-msgstr "Noch nicht geprüft"
-
-#: compensation/tables.py:119 intervention/tables.py:116
-msgid "Checked on {} by {}"
-msgstr "Am {} von {} geprüft worden"
-
-#: compensation/tables.py:160
-#: compensation/templates/compensation/detail/compensation/view.html:81
+#: compensation/tables.py:163
+#: compensation/templates/compensation/detail/compensation/view.html:96
#: compensation/templates/compensation/detail/eco_account/includes/deductions.html:58
#: compensation/templates/compensation/detail/eco_account/view.html:48
-#: ema/tables.py:131 ema/templates/ema/detail/view.html:38
-#: intervention/tables.py:157
-#: intervention/templates/intervention/detail/view.html:85
+#: ema/tables.py:130 ema/templates/ema/detail/view.html:38
+#: intervention/tables.py:161
+#: intervention/templates/intervention/detail/view.html:90
msgid "Not recorded yet"
msgstr "Noch nicht verzeichnet"
-#: compensation/tables.py:165 compensation/tables.py:326 ema/tables.py:136
-#: intervention/tables.py:162
+#: compensation/tables.py:166 compensation/tables.py:321 ema/tables.py:133
+#: intervention/tables.py:164
msgid "Recorded on {} by {}"
msgstr "Am {} von {} verzeichnet worden"
-#: compensation/tables.py:189 compensation/tables.py:348 ema/tables.py:159
+#: compensation/tables.py:186 compensation/tables.py:343 ema/tables.py:154
#: intervention/tables.py:185
msgid "Full access granted"
msgstr "Für Sie freigegeben - Datensatz kann bearbeitet werden"
-#: compensation/tables.py:189 compensation/tables.py:348 ema/tables.py:159
+#: compensation/tables.py:186 compensation/tables.py:343 ema/tables.py:154
#: intervention/tables.py:185
msgid "Access not granted"
msgstr "Nicht freigegeben - Datensatz nur lesbar"
-#: compensation/tables.py:212
+#: compensation/tables.py:209
#: compensation/templates/compensation/detail/eco_account/view.html:36
#: konova/templates/konova/widgets/progressbar.html:3
msgid "Available"
msgstr "Verfügbar"
-#: compensation/tables.py:243
+#: compensation/tables.py:240
msgid "Eco Accounts"
msgstr "Ökokonten"
-#: compensation/tables.py:321
+#: compensation/tables.py:318
msgid "Not recorded yet. Can not be used for deductions, yet."
msgstr ""
"Noch nicht verzeichnet. Kann noch nicht für Abbuchungen genutzt werden."
@@ -790,7 +795,7 @@ msgstr "Menge"
#: intervention/templates/intervention/detail/includes/documents.html:39
#: intervention/templates/intervention/detail/includes/payments.html:39
#: intervention/templates/intervention/detail/includes/revocation.html:43
-#: templates/log.html:10 user/templates/user/team/index.html:32
+#: templates/log.html:10 user/templates/user/team/index.html:33
msgid "Action"
msgstr "Aktionen"
@@ -871,7 +876,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:411
+#: konova/forms.py:491
msgid "Add new document"
msgstr "Neues Dokument hinzufügen"
@@ -879,7 +884,7 @@ msgstr "Neues Dokument hinzufügen"
#: compensation/templates/compensation/detail/eco_account/includes/documents.html:31
#: ema/templates/ema/detail/includes/documents.html:31
#: intervention/templates/intervention/detail/includes/documents.html:31
-#: konova/forms.py:368
+#: konova/forms.py:448
msgid "Created on"
msgstr "Erstellt"
@@ -887,7 +892,7 @@ msgstr "Erstellt"
#: compensation/templates/compensation/detail/eco_account/includes/documents.html:61
#: ema/templates/ema/detail/includes/documents.html:61
#: intervention/templates/intervention/detail/includes/documents.html:65
-#: konova/forms.py:474
+#: konova/forms.py:553
msgid "Edit document"
msgstr "Dokument bearbeiten"
@@ -963,63 +968,69 @@ msgstr "Neuen Ausgangszustand hinzufügen"
msgid "Missing surfaces according to states after: "
msgstr "Fehlende Flächenmengen laut Zielzustand: "
-#: compensation/templates/compensation/detail/compensation/view.html:44
-msgid "Is CEF compensation"
-msgstr "Ist CEF Maßnahme"
-
#: compensation/templates/compensation/detail/compensation/view.html:47
#: compensation/templates/compensation/detail/compensation/view.html:57
+#: compensation/templates/compensation/detail/compensation/view.html:67
+#: compensation/templates/compensation/detail/eco_account/view.html:78
+#: ema/templates/ema/detail/view.html:64
#: venv/lib/python3.7/site-packages/django/forms/widgets.py:710
msgid "Yes"
msgstr "Ja"
#: compensation/templates/compensation/detail/compensation/view.html:49
#: compensation/templates/compensation/detail/compensation/view.html:59
+#: compensation/templates/compensation/detail/compensation/view.html:69
+#: compensation/templates/compensation/detail/eco_account/view.html:80
+#: ema/templates/ema/detail/view.html:66
#: venv/lib/python3.7/site-packages/django/forms/widgets.py:711
msgid "No"
msgstr "Nein"
#: compensation/templates/compensation/detail/compensation/view.html:54
+msgid "Is CEF compensation"
+msgstr "Ist CEF Maßnahme"
+
+#: compensation/templates/compensation/detail/compensation/view.html:64
msgid "Is Coherence keeping compensation"
msgstr "Ist Kohärenzsicherungsmaßnahme"
-#: compensation/templates/compensation/detail/compensation/view.html:71
-#: intervention/templates/intervention/detail/view.html:75
+#: compensation/templates/compensation/detail/compensation/view.html:86
+#: intervention/templates/intervention/detail/view.html:80
msgid "Checked on "
msgstr "Geprüft am "
-#: compensation/templates/compensation/detail/compensation/view.html:71
-#: compensation/templates/compensation/detail/compensation/view.html:85
+#: compensation/templates/compensation/detail/compensation/view.html:86
+#: compensation/templates/compensation/detail/compensation/view.html:100
#: compensation/templates/compensation/detail/eco_account/includes/deductions.html:56
#: compensation/templates/compensation/detail/eco_account/view.html:52
#: ema/templates/ema/detail/view.html:42
-#: intervention/templates/intervention/detail/view.html:75
-#: intervention/templates/intervention/detail/view.html:89
+#: intervention/templates/intervention/detail/view.html:80
+#: intervention/templates/intervention/detail/view.html:94
msgid "by"
msgstr "von"
-#: compensation/templates/compensation/detail/compensation/view.html:85
+#: compensation/templates/compensation/detail/compensation/view.html:100
#: compensation/templates/compensation/detail/eco_account/view.html:52
#: ema/templates/ema/detail/view.html:42
-#: intervention/templates/intervention/detail/view.html:89
+#: intervention/templates/intervention/detail/view.html:94
msgid "Recorded on "
msgstr "Verzeichnet am"
-#: compensation/templates/compensation/detail/compensation/view.html:92
-#: compensation/templates/compensation/detail/eco_account/view.html:75
+#: compensation/templates/compensation/detail/compensation/view.html:107
+#: compensation/templates/compensation/detail/eco_account/view.html:85
#: compensation/templates/compensation/report/compensation/report.html:24
#: compensation/templates/compensation/report/eco_account/report.html:37
-#: ema/templates/ema/detail/view.html:61
+#: ema/templates/ema/detail/view.html:71
#: ema/templates/ema/report/report.html:24
-#: intervention/templates/intervention/detail/view.html:108
+#: intervention/templates/intervention/detail/view.html:113
#: intervention/templates/intervention/report/report.html:87
msgid "Last modified"
msgstr "Zuletzt bearbeitet"
-#: compensation/templates/compensation/detail/compensation/view.html:100
-#: compensation/templates/compensation/detail/eco_account/view.html:83
-#: ema/templates/ema/detail/view.html:76
-#: intervention/templates/intervention/detail/view.html:116
+#: compensation/templates/compensation/detail/compensation/view.html:121
+#: compensation/templates/compensation/detail/eco_account/view.html:99
+#: ema/templates/ema/detail/view.html:85
+#: intervention/templates/intervention/detail/view.html:127
msgid "Shared with"
msgstr "Freigegeben für"
@@ -1058,7 +1069,7 @@ msgstr "Eingriffskennung"
#: compensation/templates/compensation/detail/eco_account/includes/deductions.html:37
#: intervention/templates/intervention/detail/includes/deductions.html:34
-#: user/models/user_action.py:23
+#: user/models/user_action.py:24
msgid "Created"
msgstr "Erstellt"
@@ -1067,7 +1078,7 @@ msgid "Recorded on"
msgstr "Verzeichnet am"
#: compensation/templates/compensation/detail/eco_account/includes/deductions.html:65
-#: intervention/forms/modalForms.py:481
+#: intervention/forms/modalForms.py:490
#: intervention/templates/intervention/detail/includes/deductions.html:60
msgid "Edit Deduction"
msgstr "Abbuchung bearbeiten"
@@ -1095,8 +1106,8 @@ msgstr "Keine Flächenmenge für Abbuchungen eingegeben. Bitte bearbeiten."
#: intervention/templates/intervention/detail/view.html:55
#: intervention/templates/intervention/detail/view.html:59
#: intervention/templates/intervention/detail/view.html:63
-#: intervention/templates/intervention/detail/view.html:95
-#: intervention/templates/intervention/detail/view.html:99
+#: intervention/templates/intervention/detail/view.html:100
+#: intervention/templates/intervention/detail/view.html:104
msgid "Missing"
msgstr "fehlt"
@@ -1112,20 +1123,6 @@ msgstr "Maßnahmenträger"
msgid "Report"
msgstr "Bericht"
-#: compensation/templates/compensation/report/compensation/report.html:45
-#: compensation/templates/compensation/report/eco_account/report.html:58
-#: ema/templates/ema/report/report.html:45
-#: intervention/templates/intervention/report/report.html:104
-msgid "Open in browser"
-msgstr "Im Browser öffnen"
-
-#: compensation/templates/compensation/report/compensation/report.html:49
-#: compensation/templates/compensation/report/eco_account/report.html:62
-#: ema/templates/ema/report/report.html:49
-#: intervention/templates/intervention/report/report.html:108
-msgid "View in LANIS"
-msgstr "In LANIS öffnen"
-
#: compensation/templates/compensation/report/eco_account/report.html:24
msgid "Deductions for"
msgstr "Abbuchungen für"
@@ -1155,80 +1152,80 @@ msgstr ""
msgid "Responsible data"
msgstr "Daten zu den verantwortlichen Stellen"
-#: compensation/views/compensation.py:52
+#: compensation/views/compensation.py:53
msgid "Compensations - Overview"
msgstr "Kompensationen - Übersicht"
-#: compensation/views/compensation.py:151 konova/utils/message_templates.py:35
+#: compensation/views/compensation.py:172 konova/utils/message_templates.py:36
msgid "Compensation {} edited"
msgstr "Kompensation {} bearbeitet"
-#: compensation/views/compensation.py:161 compensation/views/eco_account.py:165
-#: ema/views.py:233 intervention/views.py:327
+#: compensation/views/compensation.py:182 compensation/views/eco_account.py:173
+#: ema/views.py:240 intervention/views.py:338
msgid "Edit {}"
msgstr "Bearbeite {}"
-#: compensation/views/compensation.py:240 compensation/views/eco_account.py:351
-#: ema/views.py:194 intervention/views.py:531
+#: compensation/views/compensation.py:268 compensation/views/eco_account.py:359
+#: ema/views.py:194 intervention/views.py:542
msgid "Log"
msgstr "Log"
-#: compensation/views/compensation.py:584 compensation/views/eco_account.py:719
-#: ema/views.py:551 intervention/views.py:677
+#: compensation/views/compensation.py:612 compensation/views/eco_account.py:727
+#: ema/views.py:558 intervention/views.py:688
msgid "Report {}"
msgstr "Bericht {}"
-#: compensation/views/eco_account.py:64
+#: compensation/views/eco_account.py:65
msgid "Eco-account - Overview"
msgstr "Ökokonten - Übersicht"
-#: compensation/views/eco_account.py:97
+#: compensation/views/eco_account.py:98
msgid "Eco-Account {} added"
msgstr "Ökokonto {} hinzugefügt"
-#: compensation/views/eco_account.py:155
+#: compensation/views/eco_account.py:163
msgid "Eco-Account {} edited"
msgstr "Ökokonto {} bearbeitet"
-#: compensation/views/eco_account.py:268
+#: compensation/views/eco_account.py:276
msgid "Eco-account removed"
msgstr "Ökokonto entfernt"
-#: compensation/views/eco_account.py:372 ema/views.py:275
-#: intervention/views.py:630
+#: compensation/views/eco_account.py:380 ema/views.py:282
+#: intervention/views.py:641
msgid "{} unrecorded"
msgstr "{} entzeichnet"
-#: compensation/views/eco_account.py:372 ema/views.py:275
-#: intervention/views.py:630
+#: compensation/views/eco_account.py:380 ema/views.py:282
+#: intervention/views.py:641
msgid "{} recorded"
msgstr "{} verzeichnet"
-#: compensation/views/eco_account.py:792 ema/views.py:617
-#: intervention/views.py:428
+#: compensation/views/eco_account.py:804 ema/views.py:628
+#: intervention/views.py:439
msgid "{} has already been shared with you"
msgstr "{} wurde bereits für Sie freigegeben"
-#: compensation/views/eco_account.py:797 ema/views.py:622
-#: intervention/views.py:433
+#: compensation/views/eco_account.py:809 ema/views.py:633
+#: intervention/views.py:444
msgid "{} has been shared with you"
msgstr "{} ist nun für Sie freigegeben"
-#: compensation/views/eco_account.py:804 ema/views.py:629
-#: intervention/views.py:440
+#: compensation/views/eco_account.py:816 ema/views.py:640
+#: intervention/views.py:451
msgid "Share link invalid"
msgstr "Freigabelink ungültig"
-#: compensation/views/eco_account.py:827 ema/views.py:652
-#: intervention/views.py:463
+#: compensation/views/eco_account.py:839 ema/views.py:663
+#: intervention/views.py:474
msgid "Share settings updated"
msgstr "Freigabe Einstellungen aktualisiert"
-#: ema/forms.py:41 ema/views.py:98
+#: ema/forms.py:43 ema/views.py:98
msgid "New EMA"
msgstr "Neue EMA hinzufügen"
-#: ema/forms.py:102
+#: ema/forms.py:106
msgid "Edit EMA"
msgstr "Bearbeite EMA"
@@ -1260,11 +1257,11 @@ msgstr "EMAs - Übersicht"
msgid "EMA {} added"
msgstr "EMA {} hinzugefügt"
-#: ema/views.py:223
+#: ema/views.py:230
msgid "EMA {} edited"
msgstr "EMA {} bearbeitet"
-#: ema/views.py:256
+#: ema/views.py:263
msgid "EMA removed"
msgstr "EMA entfernt"
@@ -1286,7 +1283,7 @@ msgstr "Mehrfachauswahl möglich"
#: intervention/forms/forms.py:86
#: intervention/templates/intervention/detail/view.html:48
#: intervention/templates/intervention/report/report.html:29
-#: intervention/utils/quality.py:46 konova/filters/mixins.py:364
+#: intervention/utils/quality.py:46 konova/filters/mixins.py:371
msgid "Registration office"
msgstr "Zulassungsbehörde"
@@ -1314,23 +1311,23 @@ msgid "Intervention handler detail"
msgstr "Detailangabe zum Eingriffsverursacher"
#: intervention/forms/forms.py:173
-#: intervention/templates/intervention/detail/view.html:96
+#: intervention/templates/intervention/detail/view.html:101
#: intervention/templates/intervention/report/report.html:79
#: intervention/utils/quality.py:73
msgid "Registration date"
msgstr "Datum Zulassung bzw. Satzungsbeschluss"
#: intervention/forms/forms.py:185
-#: intervention/templates/intervention/detail/view.html:100
+#: intervention/templates/intervention/detail/view.html:105
#: intervention/templates/intervention/report/report.html:83
msgid "Binding on"
-msgstr "Datum Bestandskraft"
+msgstr "Datum Bestandskraft bzw. Rechtskraft"
-#: intervention/forms/forms.py:211 intervention/views.py:94
+#: intervention/forms/forms.py:211 intervention/views.py:95
msgid "New intervention"
msgstr "Neuer Eingriff"
-#: intervention/forms/forms.py:294
+#: intervention/forms/forms.py:298
msgid "Edit intervention"
msgstr "Eingriff bearbeiten"
@@ -1406,7 +1403,7 @@ msgstr "Kompensationen und Zahlungen geprüft"
msgid "Run check"
msgstr "Prüfung vornehmen"
-#: intervention/forms/modalForms.py:264 konova/forms.py:515
+#: intervention/forms/modalForms.py:264 konova/forms.py:594
msgid ""
"I, {} {}, confirm that all necessary control steps have been performed by "
"myself."
@@ -1430,7 +1427,7 @@ msgstr "Neue Abbuchung"
msgid "Enter the information for a new deduction from a chosen eco-account"
msgstr "Geben Sie die Informationen für eine neue Abbuchung ein."
-#: intervention/forms/modalForms.py:434
+#: intervention/forms/modalForms.py:436
msgid ""
"Eco-account {} is not recorded yet. You can only deduct from recorded "
"accounts."
@@ -1438,7 +1435,15 @@ msgstr ""
"Ökokonto {} ist noch nicht verzeichnet. Abbuchungen können nur von "
"verzeichneten Ökokonten erfolgen."
-#: intervention/forms/modalForms.py:444
+#: intervention/forms/modalForms.py:443
+msgid ""
+"Intervention {} is currently recorded. To change any data on it, the entry "
+"must be unrecorded."
+msgstr ""
+"Eingriff {} ist verzeichnet. Der Eintrag muss erst entzeichnet werden um "
+"fortfahren zu können."
+
+#: intervention/forms/modalForms.py:453
msgid ""
"The account {} has not enough surface for a deduction of {} m². There are "
"only {} m² left"
@@ -1485,7 +1490,7 @@ msgid "Remove payment"
msgstr "Zahlung entfernen"
#: intervention/templates/intervention/detail/includes/revocation.html:8
-#: intervention/templates/intervention/detail/view.html:104
+#: intervention/templates/intervention/detail/view.html:109
msgid "Revocations"
msgstr "Widersprüche"
@@ -1507,7 +1512,7 @@ msgstr "Widerspruch entfernen"
msgid "Intervention handler"
msgstr "Eingriffsverursacher"
-#: intervention/templates/intervention/detail/view.html:103
+#: intervention/templates/intervention/detail/view.html:108
msgid "Exists"
msgstr "vorhanden"
@@ -1526,7 +1531,7 @@ msgstr "Widersprüche liegen vor"
#: intervention/utils/quality.py:76
msgid "Binding date"
-msgstr "Datum Bestandskraft"
+msgstr "Datum Bestandskraft bzw. Rechtskraft"
#: intervention/utils/quality.py:79
msgid "Laws"
@@ -1538,27 +1543,27 @@ msgstr ""
"Kein Ausgleich jeglicher Art gefunden (Kompensation, Ersatzzahlung, "
"Abbuchung)"
-#: intervention/views.py:51
+#: intervention/views.py:52
msgid "Interventions - Overview"
msgstr "Eingriffe - Übersicht"
-#: intervention/views.py:84
+#: intervention/views.py:85
msgid "Intervention {} added"
msgstr "Eingriff {} hinzugefügt"
-#: intervention/views.py:315
+#: intervention/views.py:326
msgid "Intervention {} edited"
msgstr "Eingriff {} bearbeitet"
-#: intervention/views.py:351
+#: intervention/views.py:362
msgid "{} removed"
msgstr "{} entfernt"
-#: intervention/views.py:484
+#: intervention/views.py:495
msgid "Check performed"
msgstr "Prüfung durchgeführt"
-#: intervention/views.py:635
+#: intervention/views.py:646
msgid "There are errors on this intervention:"
msgstr "Es liegen Fehler in diesem Eingriff vor:"
@@ -1588,6 +1593,7 @@ msgid "Search for file number"
msgstr "Nach Aktenzeichen suchen"
#: konova/filters/mixins.py:85
+#: konova/templates/konova/includes/parcels/parcel_table_frame.html:18
msgid "District"
msgstr "Kreis"
@@ -1600,7 +1606,7 @@ msgid "Search for parcel gmrkng"
msgstr "Nach Gemarkung suchen"
#: konova/filters/mixins.py:111
-#: konova/templates/konova/includes/parcel_table.html:13
+#: konova/templates/konova/includes/parcels/parcel_table_frame.html:39
msgid "Parcel"
msgstr "Flur"
@@ -1609,7 +1615,7 @@ msgid "Search for parcel"
msgstr "Nach Flur suchen"
#: konova/filters/mixins.py:124
-#: konova/templates/konova/includes/parcel_table.html:14
+#: konova/templates/konova/includes/parcels/parcel_table_frame.html:40
msgid "Parcel counter"
msgstr "Flurstückzähler"
@@ -1618,7 +1624,7 @@ msgid "Search for parcel counter"
msgstr "Nach Flurstückzähler suchen"
#: konova/filters/mixins.py:138
-#: konova/templates/konova/includes/parcel_table.html:15
+#: konova/templates/konova/includes/parcels/parcel_table_frame.html:41
msgid "Parcel number"
msgstr "Flurstücknenner"
@@ -1626,85 +1632,90 @@ msgstr "Flurstücknenner"
msgid "Search for parcel number"
msgstr "Nach Flurstücknenner suchen"
-#: konova/filters/mixins.py:269
+#: konova/filters/mixins.py:276
msgid "Show unshared"
msgstr "Nicht freigegebene anzeigen"
-#: konova/filters/mixins.py:315
+#: konova/filters/mixins.py:322
msgid "Show recorded"
msgstr "Verzeichnete anzeigen"
-#: konova/filters/mixins.py:365
+#: konova/filters/mixins.py:372
msgid "Search for registration office"
msgstr "Nach Zulassungsbehörde suchen"
-#: konova/filters/mixins.py:397
+#: konova/filters/mixins.py:404
msgid "Search for conservation office"
msgstr "Nch Eintragungsstelle suchen"
-#: konova/forms.py:39 templates/form/collapsable/form.html:62
+#: konova/forms.py:41 templates/form/collapsable/form.html:62
msgid "Save"
msgstr "Speichern"
-#: konova/forms.py:71
+#: konova/forms.py:75
msgid "Not editable"
msgstr "Nicht editierbar"
-#: konova/forms.py:142 konova/forms.py:314
+#: konova/forms.py:178 konova/forms.py:394
msgid "Confirm"
msgstr "Bestätige"
-#: konova/forms.py:154 konova/forms.py:323
+#: konova/forms.py:190 konova/forms.py:403
msgid "Remove"
msgstr "Löschen"
-#: konova/forms.py:156
+#: konova/forms.py:192
msgid "You are about to remove {} {}"
msgstr "Sie sind dabei {} {} zu löschen"
-#: konova/forms.py:243 konova/utils/quality.py:44 konova/utils/quality.py:46
+#: konova/forms.py:280 konova/utils/quality.py:44 konova/utils/quality.py:46
#: templates/form/collapsable/form.html:45
msgid "Geometry"
msgstr "Geometrie"
-#: konova/forms.py:324
+#: konova/forms.py:331
+msgid "Only surfaces allowed. Points or lines must be buffered."
+msgstr ""
+"Nur Flächen erlaubt. Punkte oder Linien müssen zu Flächen gepuffert werden."
+
+#: konova/forms.py:404
msgid "Are you sure?"
msgstr "Sind Sie sicher?"
-#: konova/forms.py:370
+#: konova/forms.py:450
msgid "When has this file been created? Important for photos."
msgstr "Wann wurde diese Datei erstellt oder das Foto aufgenommen?"
-#: konova/forms.py:381
+#: konova/forms.py:461
#: venv/lib/python3.7/site-packages/django/db/models/fields/files.py:231
msgid "File"
msgstr "Datei"
-#: konova/forms.py:383
+#: konova/forms.py:463
msgid "Allowed formats: pdf, jpg, png. Max size 15 MB."
msgstr "Formate: pdf, jpg, png. Maximal 15 MB."
-#: konova/forms.py:449
+#: konova/forms.py:528
msgid "Added document"
msgstr "Dokument hinzugefügt"
-#: konova/forms.py:506
+#: konova/forms.py:585
msgid "Confirm record"
msgstr "Verzeichnen bestätigen"
-#: konova/forms.py:514
+#: konova/forms.py:593
msgid "Record data"
msgstr "Daten verzeichnen"
-#: konova/forms.py:521
+#: konova/forms.py:600
msgid "Confirm unrecord"
msgstr "Entzeichnen bestätigen"
-#: konova/forms.py:522
+#: konova/forms.py:601
msgid "Unrecord data"
msgstr "Daten entzeichnen"
-#: konova/forms.py:523
+#: konova/forms.py:602
msgid "I, {} {}, confirm that this data must be unrecorded."
msgstr ""
"Ich, {} {}, bestätige, dass diese Daten wieder entzeichnet werden müssen."
@@ -1745,36 +1756,48 @@ msgstr "Kontrolle am"
msgid "Other"
msgstr "Sonstige"
-#: konova/sub_settings/django_settings.py:159
+#: konova/sub_settings/django_settings.py:160
msgid "German"
msgstr ""
-#: konova/sub_settings/django_settings.py:160
+#: konova/sub_settings/django_settings.py:161
msgid "English"
msgstr ""
-#: konova/templates/konova/includes/parcel_table.html:5
+#: konova/templates/konova/includes/parcels/parcel_table_frame.html:5
msgid "Parcels can not be calculated, since no geometry is given."
msgstr ""
"Flurstücke können nicht berechnet werden, da keine Geometrie eingegeben "
"wurde."
-#: konova/templates/konova/includes/parcel_table.html:11
-msgid "Kreis"
-msgstr "Kreis"
+#: konova/templates/konova/includes/parcels/parcel_table_frame.html:11
+msgid "Parcels found"
+msgstr "Flurstücke"
-#: konova/templates/konova/includes/parcel_table.html:12
-msgid "Gemarkung"
+#: konova/templates/konova/includes/parcels/parcel_table_frame.html:16
+msgid "Municipal"
+msgstr "Gemeinde"
+
+#: konova/templates/konova/includes/parcels/parcel_table_frame.html:17
+msgid "Municipal key"
+msgstr "Gemeindeschlüssel"
+
+#: konova/templates/konova/includes/parcels/parcel_table_frame.html:19
+msgid "District key"
+msgstr "Kreisschlüssel"
+
+#: konova/templates/konova/includes/parcels/parcel_table_frame.html:37
+msgid "Parcel group"
msgstr "Gemarkung"
-#: konova/templates/konova/includes/parcels.html:7
+#: konova/templates/konova/includes/parcels/parcel_table_frame.html:38
+msgid "Parcel group key"
+msgstr "Gemarkungsschlüssel"
+
+#: konova/templates/konova/includes/parcels/parcels.html:7
msgid "Spatial reference"
msgstr "Raumreferenz"
-#: konova/templates/konova/includes/parcels.html:11
-msgid "Loading..."
-msgstr "Lade..."
-
#: konova/templates/konova/includes/quickstart/compensations.html:20
#: konova/templates/konova/includes/quickstart/ecoaccounts.html:20
#: konova/templates/konova/includes/quickstart/interventions.html:20
@@ -1793,10 +1816,13 @@ msgstr "Neu"
msgid "Show"
msgstr "Anzeigen"
-#: konova/templates/konova/widgets/checkbox-tree-select.html:4
-#: templates/generic_index.html:56
-msgid "Search"
-msgstr "Suchen"
+#: konova/templates/konova/includes/report/qrcodes.html:7
+msgid "Open in browser"
+msgstr "Im Browser öffnen"
+
+#: konova/templates/konova/includes/report/qrcodes.html:15
+msgid "View in LANIS"
+msgstr "In LANIS öffnen"
#: konova/templates/konova/widgets/generate-content-input.html:6
msgid "Generate new"
@@ -1810,6 +1836,12 @@ msgstr "In Zwischenablage kopieren"
msgid "Copied to clipboard"
msgstr "In Zwischenablage kopiert"
+#: konova/templates/konova/widgets/tree/checkbox/checkbox-tree-select.html:4
+#: konova/templates/konova/widgets/tree/radio/radio-tree-select.html:4
+#: templates/generic_index.html:56
+msgid "Search"
+msgstr "Suchen"
+
#: konova/utils/mailer.py:68 konova/utils/mailer.py:137
msgid "{} - Shared access removed"
msgstr "{} - Zugriff entzogen"
@@ -1886,11 +1918,18 @@ msgstr "Hierfür müssen Sie einer anderen Nutzergruppe angehören!"
msgid "Status of Checked and Recorded reseted"
msgstr "'Geprüft'/'Verzeichnet' wurde zurückgesetzt"
-#: konova/utils/message_templates.py:22
+#: konova/utils/message_templates.py:20
+msgid ""
+"Entry is recorded. To edit data, the entry first needs to be unrecorded."
+msgstr ""
+"Eintrag ist verzeichnet. Um Daten zu bearbeiten, muss der Eintrag erst "
+"entzeichnet werden."
+
+#: konova/utils/message_templates.py:23
msgid "This data is not shared with you"
msgstr "Diese Daten sind für Sie nicht freigegeben"
-#: konova/utils/message_templates.py:23
+#: konova/utils/message_templates.py:24
msgid ""
"Remember: This data has not been shared with you, yet. This means you can "
"only read but can not edit or perform any actions like running a check or "
@@ -1900,15 +1939,15 @@ msgstr ""
"bedeutet, dass Sie nur lesenden Zugriff hierauf haben und weder bearbeiten, "
"noch Prüfungen durchführen oder verzeichnen können."
-#: konova/utils/message_templates.py:26
+#: konova/utils/message_templates.py:27
msgid "Unsupported file type"
msgstr "Dateiformat nicht unterstützt"
-#: konova/utils/message_templates.py:27
+#: konova/utils/message_templates.py:28
msgid "File too large"
msgstr "Datei zu groß"
-#: konova/utils/message_templates.py:30
+#: konova/utils/message_templates.py:31
msgid ""
"Action canceled. Eco account is recorded or deductions exist. Only "
"conservation office member can perform this action."
@@ -1916,122 +1955,135 @@ msgstr ""
"Aktion abgebrochen. Ökokonto ist bereits verzeichnet oder Abbuchungen liegen "
"vor. Nur Eintragungsstellennutzer können diese Aktion jetzt durchführen."
-#: konova/utils/message_templates.py:33
+#: konova/utils/message_templates.py:34
msgid "Compensation {} added"
msgstr "Kompensation {} hinzugefügt"
-#: konova/utils/message_templates.py:34
+#: konova/utils/message_templates.py:35
msgid "Compensation {} removed"
msgstr "Kompensation {} entfernt"
-#: konova/utils/message_templates.py:36
+#: konova/utils/message_templates.py:37
msgid "Added compensation action"
msgstr "Maßnahme hinzugefügt"
-#: konova/utils/message_templates.py:37
+#: konova/utils/message_templates.py:38
msgid "Added compensation state"
msgstr "Zustand hinzugefügt"
-#: konova/utils/message_templates.py:40
+#: konova/utils/message_templates.py:41
msgid "State removed"
msgstr "Zustand gelöscht"
-#: konova/utils/message_templates.py:41
+#: konova/utils/message_templates.py:42
msgid "State edited"
msgstr "Zustand bearbeitet"
-#: konova/utils/message_templates.py:42
+#: konova/utils/message_templates.py:43
msgid "State added"
msgstr "Zustand hinzugefügt"
-#: konova/utils/message_templates.py:45
+#: konova/utils/message_templates.py:46
msgid "Action added"
msgstr "Maßnahme hinzugefügt"
-#: konova/utils/message_templates.py:46
+#: konova/utils/message_templates.py:47
msgid "Action edited"
msgstr "Maßnahme bearbeitet"
-#: konova/utils/message_templates.py:47
+#: konova/utils/message_templates.py:48
msgid "Action removed"
msgstr "Maßnahme entfernt"
-#: konova/utils/message_templates.py:50
+#: konova/utils/message_templates.py:51
msgid "Deduction added"
msgstr "Abbuchung hinzugefügt"
-#: konova/utils/message_templates.py:51
+#: konova/utils/message_templates.py:52
msgid "Deduction edited"
msgstr "Abbuchung bearbeitet"
-#: konova/utils/message_templates.py:52
+#: konova/utils/message_templates.py:53
msgid "Deduction removed"
msgstr "Abbuchung entfernt"
-#: konova/utils/message_templates.py:55
+#: konova/utils/message_templates.py:56
msgid "Deadline added"
msgstr "Frist/Termin hinzugefügt"
-#: konova/utils/message_templates.py:56
+#: konova/utils/message_templates.py:57
msgid "Deadline edited"
msgstr "Frist/Termin bearbeitet"
-#: konova/utils/message_templates.py:57
+#: konova/utils/message_templates.py:58
msgid "Deadline removed"
msgstr "Frist/Termin gelöscht"
-#: konova/utils/message_templates.py:60
+#: konova/utils/message_templates.py:61
msgid "Payment added"
msgstr "Zahlung hinzugefügt"
-#: konova/utils/message_templates.py:61
+#: konova/utils/message_templates.py:62
msgid "Payment edited"
msgstr "Zahlung bearbeitet"
-#: konova/utils/message_templates.py:62
+#: konova/utils/message_templates.py:63
msgid "Payment removed"
msgstr "Zahlung gelöscht"
-#: konova/utils/message_templates.py:65
+#: konova/utils/message_templates.py:66
msgid "Revocation added"
msgstr "Widerspruch hinzugefügt"
-#: konova/utils/message_templates.py:66
+#: konova/utils/message_templates.py:67
msgid "Revocation edited"
msgstr "Widerspruch bearbeitet"
-#: konova/utils/message_templates.py:67
+#: konova/utils/message_templates.py:68
msgid "Revocation removed"
msgstr "Widerspruch entfernt"
-#: konova/utils/message_templates.py:70
+#: konova/utils/message_templates.py:71
msgid "Document '{}' deleted"
msgstr "Dokument '{}' gelöscht"
-#: konova/utils/message_templates.py:71
+#: konova/utils/message_templates.py:72
msgid "Document added"
msgstr "Dokument hinzugefügt"
-#: konova/utils/message_templates.py:72
+#: konova/utils/message_templates.py:73
msgid "Document edited"
msgstr "Dokument bearbeitet"
-#: konova/utils/message_templates.py:75
+#: konova/utils/message_templates.py:76
msgid "Edited general data"
msgstr "Allgemeine Daten bearbeitet"
-#: konova/utils/message_templates.py:76
+#: konova/utils/message_templates.py:77
msgid "Added deadline"
msgstr "Frist/Termin hinzugefügt"
-#: konova/utils/message_templates.py:79
+#: konova/utils/message_templates.py:80
msgid "Geometry conflict detected with {}"
msgstr "Geometriekonflikt mit folgenden Einträgen erkannt: {}"
-#: konova/utils/message_templates.py:82
+#: konova/utils/message_templates.py:83
msgid "This intervention has {} revocations"
msgstr "Dem Eingriff liegen {} Widersprüche vor"
+#: konova/utils/message_templates.py:86
+msgid "Checked on {} by {}"
+msgstr "Am {} von {} geprüft worden"
+
+#: konova/utils/message_templates.py:87
+msgid "Data has changed since last check on {} by {}"
+msgstr ""
+"Daten wurden nach der letzten Prüfung geändert. Letzte Prüfung am {} durch {}"
+
+#: konova/utils/message_templates.py:88
+msgid "Current data not checked yet"
+msgstr "Momentane Daten noch nicht geprüft"
+
#: konova/utils/messenger.py:70
msgid "{} checked"
msgstr "{} geprüft"
@@ -2048,7 +2100,7 @@ msgstr "{} wurde erfolgreich vom Nutzer {} geprüft! {}"
msgid "missing"
msgstr "fehlt"
-#: konova/views.py:96 templates/navbars/navbar.html:16
+#: konova/views.py:99 templates/navbars/navbar.html:16
msgid "Home"
msgstr "Home"
@@ -2355,6 +2407,25 @@ msgstr "Allgemeine Daten"
msgid "Cancel"
msgstr "Abbrechen"
+#: templates/form/recorded_no_edit.html:9
+msgid "This data is recorded"
+msgstr "Daten sind verzeichnet"
+
+#: templates/form/recorded_no_edit.html:14
+msgid ""
+"\n"
+" Whilst recorded the data is published publicly. If you wish to edit "
+"any information on this data, the data needs\n"
+" to be unrecorded first. Do not forget to record it afterwards, "
+"again.\n"
+" "
+msgstr ""
+"\n"
+"Verzeichnete Daten sind öffentlich einsehbar. Wenn Sie Informationen "
+"überarbeiten möchten muss dieser Datensatz zunächst entzeichnet werden. "
+"Vergessen Sie nicht ihn anschließend wieder zu verzeichnen.\n"
+" "
+
#: templates/form/table/generic_table_form_body.html:24
msgid "Fields with * are required."
msgstr "* sind Pflichtfelder."
@@ -2435,13 +2506,17 @@ msgstr "Daten nicht veröffentlicht"
msgid ""
"\n"
" The data you want to see is not recorded and might still be work "
-"in progress. Please come back another time.\n"
+"in progress or the legal binding date has\n"
+" not been reached yet. We can not publish this report as long as "
+"revocations could occur.\n"
+" Please come back later.\n"
" "
msgstr ""
"\n"
-" Diese Daten sind noch nicht veröffentlicht und können daher "
-"aktuell nicht eingesehen werden. Schauen Sie zu einem späteren Zeitpunkt "
-"wieder vorbei. \n"
+" Diese Daten sind noch nicht veröffentlicht und/oder haben das "
+"Bestands-/Rechtskraftdatum noch nicht erreicht. Sie können daher aktuell "
+"nicht eingesehen werden. Schauen Sie zu einem späteren Zeitpunkt wieder "
+"vorbei. \n"
" "
#: templates/table/gmrkng_col.html:6
@@ -2492,11 +2567,11 @@ msgstr "Neuen Token generieren"
msgid "A new token needs to be validated by an administrator!"
msgstr "Neue Tokens müssen durch Administratoren freigeschaltet werden!"
-#: user/forms.py:168 user/forms.py:172 user/forms.py:323 user/forms.py:328
+#: user/forms.py:168 user/forms.py:172 user/forms.py:354 user/forms.py:359
msgid "Team name"
msgstr "Team Name"
-#: user/forms.py:179 user/forms.py:336 user/templates/user/team/index.html:30
+#: user/forms.py:179 user/forms.py:367 user/templates/user/team/index.html:30
msgid "Description"
msgstr "Beschreibung"
@@ -2524,39 +2599,63 @@ msgstr ""
"Sie werden standardmäßig der Administrator dieses Teams. Sie müssen sich "
"selbst nicht zur Liste der Mitglieder hinzufügen."
-#: user/forms.py:218 user/forms.py:279
+#: user/forms.py:218 user/forms.py:296
msgid "Name already taken. Try another."
msgstr "Name bereits vergeben. Probieren Sie einen anderen."
-#: user/forms.py:249
-msgid "Admin"
-msgstr "Administrator"
+#: user/forms.py:248
+msgid "Admins"
+msgstr "Administratoren"
#: user/forms.py:250
msgid "Administrators manage team details and members"
msgstr "Administratoren verwalten die Teamdaten und Mitglieder"
-#: user/forms.py:263
-msgid "Selected admin ({}) needs to be a member of this team."
-msgstr "Gewählter Administrator ({}) muss ein Mitglied des Teams sein."
+#: user/forms.py:273
+msgid "Selected admins need to be members of this team."
+msgstr "Gewählte Administratoren müssen Teammitglieder sein."
-#: user/forms.py:291 user/templates/user/team/index.html:51
+#: user/forms.py:280
+msgid "There must be at least one admin on this team."
+msgstr "Es muss mindestens einen Administrator für das Team geben."
+
+#: user/forms.py:308 user/templates/user/team/index.html:60
msgid "Edit team"
msgstr "Team bearbeiten"
-#: user/forms.py:347
+#: user/forms.py:335
+msgid ""
+"ATTENTION!\n"
+"\n"
+"Removing the team means all members will lose their access to data, based on "
+"this team! \n"
+"\n"
+"Are you sure to remove this team?"
+msgstr ""
+"ACHTUNG!\n"
+"\n"
+"Wenn dieses Team gelöscht wird, verlieren alle Teammitglieder den Zugriff "
+"auf die Daten, die nur über dieses Team freigegeben sind!\n"
+"\n"
+"Sind Sie sicher, dass Sie dieses Team löschen möchten?"
+
+#: user/forms.py:345 user/templates/user/team/index.html:56
+msgid "Leave team"
+msgstr "Team verlassen"
+
+#: user/forms.py:378
msgid "Team"
msgstr "Team"
-#: user/models/user_action.py:22
+#: user/models/user_action.py:23
msgid "Unrecorded"
msgstr "Entzeichnet"
-#: user/models/user_action.py:24
+#: user/models/user_action.py:25
msgid "Edited"
msgstr "Bearbeitet"
-#: user/models/user_action.py:25
+#: user/models/user_action.py:26
msgid "Deleted"
msgstr "Gelöscht"
@@ -2573,8 +2672,8 @@ msgid "Name"
msgstr ""
#: user/templates/user/index.html:21
-msgid "Groups"
-msgstr "Gruppen"
+msgid "Permissions"
+msgstr "Berechtigungen"
#: user/templates/user/index.html:34
msgid ""
@@ -2606,22 +2705,22 @@ msgid "Notification settings"
msgstr "Benachrichtigungen"
#: user/templates/user/index.html:58
-msgid "See or edit your API token"
-msgstr "API token einsehen oder neu generieren"
-
-#: user/templates/user/index.html:61
-msgid "API"
-msgstr ""
-
-#: user/templates/user/index.html:66
msgid "Manage teams"
msgstr ""
-#: user/templates/user/index.html:69 user/templates/user/team/index.html:18
+#: user/templates/user/index.html:61 user/templates/user/team/index.html:18
#: user/views.py:167
msgid "Teams"
msgstr ""
+#: user/templates/user/index.html:66
+msgid "See or edit your API token"
+msgstr "API token einsehen oder neu generieren"
+
+#: user/templates/user/index.html:69
+msgid "API"
+msgstr ""
+
#: user/templates/user/team/index.html:20
msgid "Add new team"
msgstr "Neues Team hinzufügen"
@@ -2630,7 +2729,11 @@ msgstr "Neues Team hinzufügen"
msgid "Members"
msgstr "Mitglieder"
-#: user/templates/user/team/index.html:54
+#: user/templates/user/team/index.html:32
+msgid "Administrator"
+msgstr ""
+
+#: user/templates/user/team/index.html:63
msgid "Remove team"
msgstr "Team entfernen"
@@ -2682,14 +2785,22 @@ msgstr "API Nutzer Token"
msgid "New team added"
msgstr "Neues Team hinzugefügt"
-#: user/views.py:191
+#: user/views.py:192
msgid "Team edited"
msgstr "Team bearbeitet"
-#: user/views.py:204
+#: user/views.py:206
msgid "Team removed"
msgstr "Team gelöscht"
+#: user/views.py:220
+msgid "You are not a member of this team"
+msgstr "Sie sind kein Mitglied dieses Teams"
+
+#: user/views.py:227
+msgid "Left Team"
+msgstr "Team verlassen"
+
#: venv/lib/python3.7/site-packages/bootstrap4/components.py:17
#: venv/lib/python3.7/site-packages/bootstrap4/templates/bootstrap4/form_errors.html:3
#: venv/lib/python3.7/site-packages/bootstrap4/templates/bootstrap4/messages.html:4
@@ -4189,5 +4300,20 @@ msgstr ""
msgid "Unable to connect to qpid with SASL mechanism %s"
msgstr ""
+#~ msgid "Groups"
+#~ msgstr "Gruppen"
+
+#~ msgid "Show more..."
+#~ msgstr "Mehr anzeigen..."
+
+#~ msgid "Kreis"
+#~ msgstr "Kreis"
+
+#~ msgid "Gemarkung"
+#~ msgstr "Gemarkung"
+
+#~ msgid "Loading..."
+#~ msgstr "Lade..."
+
#~ msgid "Who handles the eco-account"
#~ msgstr "Wer für die Herrichtung des Ökokontos verantwortlich ist"
diff --git a/requirements.txt b/requirements.txt
index e2f43108..79a479ed 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -28,6 +28,7 @@ kombu==5.2.3
openpyxl==3.0.9
OWSLib==0.25.0
packaging==21.3
+pika==1.2.0
prompt-toolkit==3.0.24
psycopg2-binary==2.9.1
pyparsing==3.0.6
diff --git a/templates/form/recorded_no_edit.html b/templates/form/recorded_no_edit.html
new file mode 100644
index 00000000..45fd08df
--- /dev/null
+++ b/templates/form/recorded_no_edit.html
@@ -0,0 +1,19 @@
+{% load i18n fontawesome_5 %}
+
+
+
+
+ {% fa5_icon 'bookmark' %}
+
+
+ {% trans 'This data is recorded' %}
+
+
+
+
+ {% blocktrans %}
+ Whilst recorded the data is published publicly. If you wish to edit any information on this data, the data needs
+ to be unrecorded first. Do not forget to record it afterwards, again.
+ {% endblocktrans %}
+
+
\ No newline at end of file
diff --git a/templates/form/table/generic_table_form.html b/templates/form/table/generic_table_form.html
index a89ee4bf..5c00b89a 100644
--- a/templates/form/table/generic_table_form.html
+++ b/templates/form/table/generic_table_form.html
@@ -11,7 +11,7 @@
{% if form.form_caption is not None %}
- {{ form.form_caption }}
+ {{ form.form_caption|linebreaks }}
{% endif %}
| | |