diff --git a/compensation/tables.py b/compensation/tables.py
index 96888cc1..ed89b636 100644
--- a/compensation/tables.py
+++ b/compensation/tables.py
@@ -134,7 +134,7 @@ class CompensationTable(BaseTable, TableRenderMixin):
"""
parcels = value.get_underlying_parcels().values_list(
- "gmrkng",
+ "parcel_group__name",
flat=True
).distinct()
html = render_to_string(
@@ -295,7 +295,7 @@ class EcoAccountTable(BaseTable, TableRenderMixin):
"""
parcels = value.get_underlying_parcels().values_list(
- "gmrkng",
+ "parcel_group__name",
flat=True
).distinct()
html = render_to_string(
diff --git a/ema/tables.py b/ema/tables.py
index f9689517..30968f96 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(
diff --git a/intervention/tables.py b/intervention/tables.py
index f535039d..c8ee504e 100644
--- a/intervention/tables.py
+++ b/intervention/tables.py
@@ -131,7 +131,7 @@ class InterventionTable(BaseTable, TableRenderMixin):
"""
parcels = value.get_underlying_parcels().values_list(
- "gmrkng",
+ "parcel_group__name",
flat=True
).distinct()
html = render_to_string(
diff --git a/konova/admin.py b/konova/admin.py
index 81db8fe2..07be7213 100644
--- a/konova/admin.py
+++ b/konova/admin.py
@@ -7,7 +7,7 @@ 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.utils.message_templates import COMPENSATION_REMOVED_TEMPLATE
from user.models import UserAction
@@ -22,7 +22,7 @@ class GeometryAdmin(admin.ModelAdmin):
class ParcelAdmin(admin.ModelAdmin):
list_display = [
"id",
- "gmrkng",
+ "parcel_group",
"flr",
"flrstck_nnr",
"flrstck_zhlr",
@@ -32,9 +32,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",
]
@@ -105,5 +123,7 @@ class BaseObjectAdmin(BaseResourceAdmin):
#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/filters/mixins.py b/konova/filters/mixins.py
index e6a841ea..beb44dac 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
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/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/models/geometry.py b/konova/models/geometry.py
index bec89c39..fc484a79 100644
--- a/konova/models/geometry.py
+++ b/konova/models/geometry.py
@@ -99,7 +99,7 @@ class Geometry(BaseResource):
Returns:
"""
- from konova.models import Parcel, District, ParcelIntersection
+ from konova.models import Parcel, District, ParcelIntersection, Municipal, ParcelGroup
parcel_fetcher = ParcelWFSFetcher(
geometry_id=self.id,
)
@@ -115,16 +115,28 @@ class Geometry(BaseResource):
# 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 ", "")
+ district = District.objects.get_or_create(
+ key=fetched_parcel["ave:kreisschl"],
+ name=fetched_parcel["ave:kreis"],
+ )[0]
+ municipal = Municipal.objects.get_or_create(
+ key=fetched_parcel["ave:gmdschl"],
+ name=fetched_parcel["ave:gemeinde"],
+ district=district,
+ )[0]
+ parcel_group = ParcelGroup.objects.get_or_create(
+ key=fetched_parcel["ave:gemaschl"],
+ name=fetched_parcel["ave:gemarkung"],
+ municipal=municipal,
+ )[0]
parcel_obj = Parcel.objects.get_or_create(
- gmrkng=fetched_parcel["ave:gemarkung"],
+ district=district,
+ municipal=municipal,
+ parcel_group=parcel_group,
flr=flr_val,
flrstck_nnr=fetched_parcel['ave:flstnrnen'],
flrstck_zhlr=fetched_parcel['ave:flstnrzae'],
)[0]
- district = District.objects.get_or_create(
- gmnd=fetched_parcel["ave:gemeinde"],
- krs=fetched_parcel["ave:kreis"],
- )[0]
parcel_obj.district = district
parcel_obj.updated_on = _now
parcel_obj.save()
@@ -155,9 +167,10 @@ class Geometry(BaseResource):
parcels = self.parcels.filter(
parcelintersection__calculated_on__isnull=False,
).prefetch_related(
- "district"
+ "district",
+ "municipal",
).order_by(
- "gmrkng",
+ "municipal__name",
)
return parcels
diff --git a/konova/models/parcel.py b/konova/models/parcel.py
index 9c887f1a..f74b7af9 100644
--- a/konova/models/parcel.py
+++ b/konova/models/parcel.py
@@ -10,8 +10,64 @@ 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"
+
+ """
+ pass
+
+
+class Municipal(UuidModel, AdministrativeSpatialReference):
+ """ The model Municipal refers to "Gemeinde"
+
+ """
+ district = models.ForeignKey(
+ District,
+ on_delete=models.SET_NULL,
+ null=True,
+ blank=True,
+ )
+
+
+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 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 +80,34 @@ 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,
- )
-
- 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/templates/konova/includes/parcel_table.html b/konova/templates/konova/includes/parcel_table.html
index 68904894..76503572 100644
--- a/konova/templates/konova/includes/parcel_table.html
+++ b/konova/templates/konova/includes/parcel_table.html
@@ -1,15 +1,36 @@
-{% load i18n %}
+{% load i18n l10n %}