Compare commits
62 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 029b9cf794 | |||
| 059972b4cd | |||
| 90fffb9576 | |||
| 155d9d1d38 | |||
| b8d9343682 | |||
| 4ab713a908 | |||
| f2c5e7ae01 | |||
| e048d44c95 | |||
| ad02a62eaf | |||
| 49d02b31f5 | |||
| 0b2cf2a0a4 | |||
| 618cf3a756 | |||
| e06e0e8306 | |||
| 870d822c3a | |||
| cf874225c1 | |||
| 25cd104cf6 | |||
| ec0a5fefbd | |||
| c50632e7a0 | |||
| 3cd23b1761 | |||
| e664fbbb7d | |||
| feed774679 | |||
| 746ee7a283 | |||
| b0f9ee4ac0 | |||
| 814f35c426 | |||
| 7c4940729c | |||
| 7d0c405f58 | |||
| cbb137a902 | |||
| 14fee4474f | |||
| deb97fbbf3 | |||
| eb2d01eeea | |||
| 933332c1ef | |||
| 11e5d82086 | |||
| 7650e0bf73 | |||
| b1fe9ed9cb | |||
| ddb1e82fbc | |||
| 1a8034fa20 | |||
| a203d73471 | |||
| e6c0d8b1cf | |||
| 20c9950b7f | |||
| 729a8f991c | |||
| f4a1dd17b1 | |||
| 8e73387032 | |||
| 97b7156b9c | |||
| 18242d2cba | |||
| 2c20069dea | |||
| 87b01e8fdd | |||
| bce271ceaa | |||
| ac443bd9eb | |||
| eb248be6f4 | |||
| ba7ae0b0b2 | |||
| 27c7802760 | |||
| 6c8227ed17 | |||
| 757598970b | |||
| f38de97cf4 | |||
| 750afdff08 | |||
| b4cc919b02 | |||
| 49f57a4bf6 | |||
| c5f8e0c705 | |||
| 0bff9e0018 | |||
| 0e9f0ba53e | |||
| 9ee0bddde9 | |||
| 66a2387791 |
@@ -17,6 +17,7 @@ from compensation.models import Compensation, Payment, EcoAccountDeduction, EcoA
|
||||
from intervention.models import Intervention
|
||||
from konova.models import Geometry
|
||||
from konova.sub_settings.django_settings import BASE_DIR, DEFAULT_DATE_FORMAT
|
||||
from konova.sub_settings.lanis_settings import DEFAULT_SRID_RLP
|
||||
|
||||
|
||||
class TimespanReport:
|
||||
@@ -103,9 +104,9 @@ class TimespanReport:
|
||||
"iterable": self.evaluated_laws,
|
||||
"attrs": [
|
||||
"short_name",
|
||||
"num",
|
||||
"num_checked",
|
||||
"num_recorded",
|
||||
"num",
|
||||
]
|
||||
},
|
||||
"i_laws_checked": self.law_sum_checked,
|
||||
@@ -333,7 +334,7 @@ class TimespanReport:
|
||||
return Geometry.objects.filter(
|
||||
id__in=ids
|
||||
).annotate(
|
||||
geom_cast=Cast("geom", MultiPolygonField())
|
||||
geom_cast=Cast("geom", MultiPolygonField(srid=DEFAULT_SRID_RLP))
|
||||
).annotate(
|
||||
num=NumGeometries("geom_cast")
|
||||
).aggregate(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"eco_account": "CHANGE_BEFORE_RUN!!!",
|
||||
"surface": 500.0,
|
||||
"surface": 500.50,
|
||||
"intervention": "CHANGE_BEFORE_RUN!!!"
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"eco_account": "CHANGE_BEFORE_RUN!!!",
|
||||
"surface": 523400.0,
|
||||
"surface": 523400.50,
|
||||
"intervention": "CHANGE_BEFORE_RUN!!!"
|
||||
}
|
||||
@@ -136,8 +136,6 @@ class AbstractModelAPISerializer:
|
||||
geometry = geos.fromstr(geojson)
|
||||
if geometry.srid != DEFAULT_SRID_RLP:
|
||||
geometry.transform(DEFAULT_SRID_RLP)
|
||||
if geometry.empty:
|
||||
geometry = None
|
||||
return geometry
|
||||
|
||||
def _get_obj_from_db(self, id, user):
|
||||
|
||||
@@ -11,7 +11,7 @@ from api.utils.serializer.v1.serializer import AbstractModelAPISerializerV1, Abs
|
||||
from compensation.models import Compensation
|
||||
from intervention.models import Intervention
|
||||
from konova.models import Geometry
|
||||
from konova.tasks import celery_update_parcels
|
||||
from konova.tasks import celery_update_parcels, celery_check_for_geometry_conflicts
|
||||
from konova.utils.message_templates import DATA_UNSHARED
|
||||
from user.models import UserActionLogEntry
|
||||
|
||||
@@ -64,6 +64,7 @@ class CompensationAPISerializerV1(AbstractModelAPISerializerV1, AbstractCompensa
|
||||
obj = Compensation()
|
||||
created = create_action
|
||||
obj.created = created
|
||||
obj.modified = created
|
||||
obj.geometry = geometry
|
||||
return obj
|
||||
|
||||
@@ -125,11 +126,12 @@ class CompensationAPISerializerV1(AbstractModelAPISerializerV1, AbstractCompensa
|
||||
obj = self._set_compensation_states(obj, properties["after_states"], obj.after_states)
|
||||
obj = self._set_deadlines(obj, properties["deadlines"])
|
||||
|
||||
obj.log.add(obj.created)
|
||||
obj.log.add(obj.created)
|
||||
|
||||
celery_update_parcels.delay(obj.geometry.id)
|
||||
celery_update_parcels.delay(obj.geometry.id)
|
||||
celery_check_for_geometry_conflicts.delay(obj.geometry.id)
|
||||
|
||||
return obj.id
|
||||
return obj.id
|
||||
|
||||
def update_model_from_json(self, id, json_model, user):
|
||||
""" Updates an entry for the model based on the contents of json_model
|
||||
@@ -165,8 +167,8 @@ class CompensationAPISerializerV1(AbstractModelAPISerializerV1, AbstractCompensa
|
||||
obj = self._set_compensation_states(obj, properties["after_states"], obj.after_states)
|
||||
obj = self._set_deadlines(obj, properties["deadlines"])
|
||||
|
||||
obj.log.add(update_action)
|
||||
obj.log.add(update_action)
|
||||
|
||||
celery_update_parcels.delay(obj.geometry.id)
|
||||
celery_update_parcels.delay(obj.geometry.id)
|
||||
|
||||
return obj.id
|
||||
return obj.id
|
||||
|
||||
@@ -13,7 +13,7 @@ from codelist.settings import CODELIST_CONSERVATION_OFFICE_ID, CODELIST_HANDLER_
|
||||
from compensation.models import EcoAccount
|
||||
from intervention.models import Legal, Responsibility, Handler
|
||||
from konova.models import Geometry
|
||||
from konova.tasks import celery_update_parcels
|
||||
from konova.tasks import celery_update_parcels, celery_check_for_geometry_conflicts
|
||||
from user.models import UserActionLogEntry
|
||||
|
||||
|
||||
@@ -103,6 +103,7 @@ class EcoAccountAPISerializerV1(AbstractModelAPISerializerV1,
|
||||
obj.legal = Legal()
|
||||
created = create_action
|
||||
obj.created = created
|
||||
obj.modified = created
|
||||
obj.geometry = geometry
|
||||
return obj
|
||||
|
||||
@@ -146,12 +147,13 @@ class EcoAccountAPISerializerV1(AbstractModelAPISerializerV1,
|
||||
obj = self._set_compensation_states(obj, properties["after_states"], obj.after_states)
|
||||
obj = self._set_deadlines(obj, properties["deadlines"])
|
||||
|
||||
obj.log.add(obj.created)
|
||||
obj.users.add(user)
|
||||
obj.log.add(obj.created)
|
||||
obj.users.add(user)
|
||||
|
||||
celery_update_parcels.delay(obj.geometry.id)
|
||||
celery_update_parcels.delay(obj.geometry.id)
|
||||
celery_check_for_geometry_conflicts.delay(obj.geometry.id)
|
||||
|
||||
return obj.id
|
||||
return obj.id
|
||||
|
||||
def update_model_from_json(self, id, json_model, user):
|
||||
""" Updates an entry for the model based on the contents of json_model
|
||||
@@ -190,8 +192,8 @@ class EcoAccountAPISerializerV1(AbstractModelAPISerializerV1,
|
||||
obj = self._set_compensation_states(obj, properties["after_states"], obj.after_states)
|
||||
obj = self._set_deadlines(obj, properties["deadlines"])
|
||||
|
||||
obj.log.add(update_action)
|
||||
obj.log.add(update_action)
|
||||
|
||||
celery_update_parcels.delay(obj.geometry.id)
|
||||
celery_update_parcels.delay(obj.geometry.id)
|
||||
|
||||
return obj.id
|
||||
return obj.id
|
||||
|
||||
@@ -13,7 +13,7 @@ from codelist.settings import CODELIST_CONSERVATION_OFFICE_ID, CODELIST_HANDLER_
|
||||
from ema.models import Ema
|
||||
from intervention.models import Responsibility, Handler
|
||||
from konova.models import Geometry
|
||||
from konova.tasks import celery_update_parcels
|
||||
from konova.tasks import celery_update_parcels, celery_check_for_geometry_conflicts
|
||||
from user.models import UserActionLogEntry
|
||||
|
||||
|
||||
@@ -85,6 +85,7 @@ class EmaAPISerializerV1(AbstractModelAPISerializerV1, AbstractCompensationAPISe
|
||||
)
|
||||
created = create_action
|
||||
obj.created = created
|
||||
obj.modified = created
|
||||
obj.geometry = geometry
|
||||
return obj
|
||||
|
||||
@@ -118,12 +119,13 @@ class EmaAPISerializerV1(AbstractModelAPISerializerV1, AbstractCompensationAPISe
|
||||
obj = self._set_compensation_states(obj, properties["after_states"], obj.after_states)
|
||||
obj = self._set_deadlines(obj, properties["deadlines"])
|
||||
|
||||
obj.log.add(obj.created)
|
||||
obj.users.add(user)
|
||||
obj.log.add(obj.created)
|
||||
obj.users.add(user)
|
||||
|
||||
celery_update_parcels.delay(obj.geometry.id)
|
||||
celery_update_parcels.delay(obj.geometry.id)
|
||||
celery_check_for_geometry_conflicts.delay(obj.geometry.id)
|
||||
|
||||
return obj.id
|
||||
return obj.id
|
||||
|
||||
def update_model_from_json(self, id, json_model, user):
|
||||
""" Updates an entry for the model based on the contents of json_model
|
||||
@@ -159,8 +161,8 @@ class EmaAPISerializerV1(AbstractModelAPISerializerV1, AbstractCompensationAPISe
|
||||
obj = self._set_compensation_states(obj, properties["after_states"], obj.after_states)
|
||||
obj = self._set_deadlines(obj, properties["deadlines"])
|
||||
|
||||
obj.log.add(update_action)
|
||||
obj.log.add(update_action)
|
||||
|
||||
celery_update_parcels.delay(obj.geometry.id)
|
||||
celery_update_parcels.delay(obj.geometry.id)
|
||||
|
||||
return obj.id
|
||||
return obj.id
|
||||
|
||||
@@ -13,7 +13,7 @@ from api.utils.serializer.v1.serializer import AbstractModelAPISerializerV1, \
|
||||
from compensation.models import Payment
|
||||
from intervention.models import Intervention, Responsibility, Legal, Handler
|
||||
from konova.models import Geometry
|
||||
from konova.tasks import celery_update_parcels
|
||||
from konova.tasks import celery_update_parcels, celery_check_for_geometry_conflicts
|
||||
from user.models import UserActionLogEntry
|
||||
|
||||
|
||||
@@ -76,6 +76,7 @@ class InterventionAPISerializerV1(AbstractModelAPISerializerV1,
|
||||
created = create_action
|
||||
obj.legal = legal
|
||||
obj.created = created
|
||||
obj.modified = created
|
||||
obj.geometry = geometry
|
||||
obj.responsible = resp
|
||||
return obj
|
||||
@@ -161,12 +162,13 @@ class InterventionAPISerializerV1(AbstractModelAPISerializerV1,
|
||||
obj.legal.save()
|
||||
obj.save()
|
||||
|
||||
obj.users.add(user)
|
||||
obj.log.add(obj.created)
|
||||
obj.users.add(user)
|
||||
obj.log.add(obj.created)
|
||||
|
||||
celery_update_parcels.delay(obj.geometry.id)
|
||||
celery_update_parcels.delay(obj.geometry.id)
|
||||
celery_check_for_geometry_conflicts.delay(obj.geometry.id)
|
||||
|
||||
return obj.id
|
||||
return obj.id
|
||||
|
||||
def update_model_from_json(self, id, json_model, user):
|
||||
""" Updates an entry for the model based on the contents of json_model
|
||||
@@ -198,8 +200,8 @@ class InterventionAPISerializerV1(AbstractModelAPISerializerV1,
|
||||
obj.legal.save()
|
||||
obj.save()
|
||||
|
||||
obj.mark_as_edited(user, edit_comment="API update")
|
||||
obj.mark_as_edited(user, edit_comment="API update")
|
||||
|
||||
celery_update_parcels.delay(obj.geometry.id)
|
||||
celery_update_parcels.delay(obj.geometry.id)
|
||||
|
||||
return obj.id
|
||||
return obj.id
|
||||
|
||||
@@ -9,6 +9,7 @@ Created on: 24.01.22
|
||||
import json
|
||||
|
||||
from django.contrib.gis.geos import MultiPolygon
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.db.models import QuerySet
|
||||
|
||||
from api.utils.serializer.serializer import AbstractModelAPISerializer
|
||||
@@ -80,10 +81,14 @@ class AbstractModelAPISerializerV1(AbstractModelAPISerializer):
|
||||
json_str = str(json_str)
|
||||
if len(json_str) == 0:
|
||||
return None
|
||||
code = KonovaCode.objects.get(
|
||||
atom_id=json_str,
|
||||
code_lists__in=[code_list_identifier]
|
||||
)
|
||||
try:
|
||||
code = KonovaCode.objects.get(
|
||||
atom_id=json_str,
|
||||
code_lists__in=[code_list_identifier]
|
||||
)
|
||||
except ObjectDoesNotExist as e:
|
||||
msg = f"{e.args[0]} ({json_str} not found in official list {code_list_identifier})"
|
||||
raise ObjectDoesNotExist(msg)
|
||||
return code
|
||||
|
||||
def _created_on_to_json(self, entry):
|
||||
@@ -387,7 +392,8 @@ class AbstractCompensationAPISerializerV1Mixin:
|
||||
self._konova_code_from_json(e, CODELIST_COMPENSATION_ACTION_DETAIL_ID) for e in entry["action_details"]
|
||||
]
|
||||
amount = float(entry["amount"])
|
||||
unit = entry["unit"]
|
||||
# Mapping of old "qm" into "m²"
|
||||
unit = UnitChoices.m2.value if entry["unit"] == "qm" else entry["unit"]
|
||||
comment = entry["comment"]
|
||||
|
||||
# Check on validity
|
||||
|
||||
@@ -81,13 +81,15 @@ class EcoAccountAdmin(AbstractCompensationAdmin):
|
||||
]
|
||||
|
||||
filter_horizontal = [
|
||||
"users"
|
||||
"users",
|
||||
"teams",
|
||||
]
|
||||
|
||||
def get_fields(self, request, obj=None):
|
||||
return super().get_fields(request, obj) + [
|
||||
"deductable_surface",
|
||||
"users"
|
||||
"users",
|
||||
"teams",
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -129,12 +129,11 @@ class NewCompensationForm(AbstractCompensationForm,
|
||||
self.initialize_form_field("identifier", identifier)
|
||||
self.fields["identifier"].widget.attrs["url"] = reverse_lazy("compensation:new-id")
|
||||
|
||||
def __create_comp(self, user, geom_form) -> Compensation:
|
||||
def __create_comp(self, user):
|
||||
""" Creates the compensation from form data
|
||||
|
||||
Args:
|
||||
user (User): The performing user
|
||||
geom_form (SimpleGeomForm): The geometry form
|
||||
|
||||
Returns:
|
||||
comp (Compensation): The compensation object
|
||||
@@ -150,8 +149,6 @@ class NewCompensationForm(AbstractCompensationForm,
|
||||
|
||||
# Create log entry
|
||||
action = UserActionLogEntry.get_created_action(user)
|
||||
# Process the geometry form
|
||||
geometry = geom_form.save(action)
|
||||
|
||||
# Finally create main object
|
||||
comp = Compensation.objects.create(
|
||||
@@ -159,21 +156,27 @@ class NewCompensationForm(AbstractCompensationForm,
|
||||
title=title,
|
||||
intervention=intervention,
|
||||
created=action,
|
||||
modified=action,
|
||||
is_cef=is_cef,
|
||||
is_coherence_keeping=is_coherence_keeping,
|
||||
is_pik=is_pik,
|
||||
geometry=geometry,
|
||||
comment=comment,
|
||||
)
|
||||
|
||||
# Add the log entry to the main objects log list
|
||||
comp.log.add(action)
|
||||
return comp
|
||||
return comp, action
|
||||
|
||||
def save(self, user: User, geom_form: SimpleGeomForm):
|
||||
with transaction.atomic():
|
||||
comp = self.__create_comp(user, geom_form)
|
||||
comp, action = self.__create_comp(user)
|
||||
comp.intervention.mark_as_edited(user, edit_comment=COMPENSATION_ADDED_TEMPLATE.format(comp.identifier))
|
||||
|
||||
# Process the geometry form
|
||||
geometry = geom_form.save(action)
|
||||
comp.geometry = geometry
|
||||
comp.save()
|
||||
|
||||
return comp
|
||||
|
||||
|
||||
@@ -205,6 +208,9 @@ class EditCompensationForm(NewCompensationForm):
|
||||
|
||||
def save(self, user: User, geom_form: SimpleGeomForm):
|
||||
with transaction.atomic():
|
||||
# Create log entry
|
||||
action = UserActionLogEntry.get_edited_action(user)
|
||||
|
||||
# Fetch data from cleaned POST values
|
||||
identifier = self.cleaned_data.get("identifier", None)
|
||||
title = self.cleaned_data.get("title", None)
|
||||
@@ -214,17 +220,9 @@ class EditCompensationForm(NewCompensationForm):
|
||||
is_pik = self.cleaned_data.get("is_pik", None)
|
||||
comment = self.cleaned_data.get("comment", None)
|
||||
|
||||
# Create log entry
|
||||
action = UserActionLogEntry.get_edited_action(user)
|
||||
|
||||
# Process the geometry form
|
||||
geometry = geom_form.save(action)
|
||||
|
||||
# Finally create main object
|
||||
self.instance.identifier = identifier
|
||||
self.instance.title = title
|
||||
self.instance.intervention = intervention
|
||||
self.instance.geometry = geometry
|
||||
self.instance.is_cef = is_cef
|
||||
self.instance.is_coherence_keeping = is_coherence_keeping
|
||||
self.instance.comment = comment
|
||||
@@ -233,6 +231,11 @@ class EditCompensationForm(NewCompensationForm):
|
||||
self.instance.save()
|
||||
|
||||
self.instance.log.add(action)
|
||||
|
||||
intervention.mark_as_edited(user, self.request, EDITED_GENERAL_DATA)
|
||||
return self.instance
|
||||
|
||||
# Process the geometry form (NOT ATOMIC TRANSACTION DUE TO CELERY!)
|
||||
geometry = geom_form.save(action)
|
||||
self.instance.geometry = geometry
|
||||
self.instance.save()
|
||||
|
||||
return self.instance
|
||||
|
||||
@@ -14,6 +14,7 @@ from compensation.forms.mixins import CompensationResponsibleFormMixin, PikCompe
|
||||
from compensation.models import EcoAccount
|
||||
from intervention.models import Handler, Responsibility, Legal
|
||||
from konova.forms import SimpleGeomForm
|
||||
from konova.forms.modals import RemoveModalForm
|
||||
from user.models import User, UserActionLogEntry
|
||||
|
||||
|
||||
@@ -93,8 +94,6 @@ class NewEcoAccountForm(AbstractCompensationForm, CompensationResponsibleFormMix
|
||||
|
||||
# Create log entry
|
||||
action = UserActionLogEntry.get_created_action(user)
|
||||
# Process the geometry form
|
||||
geometry = geom_form.save(action)
|
||||
|
||||
handler = Handler.objects.create(
|
||||
type=handler_type,
|
||||
@@ -118,7 +117,7 @@ class NewEcoAccountForm(AbstractCompensationForm, CompensationResponsibleFormMix
|
||||
responsible=responsible,
|
||||
deductable_surface=surface,
|
||||
created=action,
|
||||
geometry=geometry,
|
||||
modified=action,
|
||||
comment=comment,
|
||||
is_pik=is_pik,
|
||||
legal=legal
|
||||
@@ -127,6 +126,12 @@ class NewEcoAccountForm(AbstractCompensationForm, CompensationResponsibleFormMix
|
||||
|
||||
# Add the log entry to the main objects log list
|
||||
acc.log.add(action)
|
||||
|
||||
# Process the geometry form
|
||||
geometry = geom_form.save(action)
|
||||
acc.geometry = geometry
|
||||
acc.save()
|
||||
acc.update_deductable_rest()
|
||||
return acc
|
||||
|
||||
|
||||
@@ -182,9 +187,6 @@ class EditEcoAccountForm(NewEcoAccountForm):
|
||||
# Create log entry
|
||||
action = UserActionLogEntry.get_edited_action(user)
|
||||
|
||||
# Process the geometry form
|
||||
geometry = geom_form.save(action)
|
||||
|
||||
# Update responsible data
|
||||
self.instance.responsible.handler.type = handler_type
|
||||
self.instance.responsible.handler.detail = handler_detail
|
||||
@@ -201,7 +203,6 @@ class EditEcoAccountForm(NewEcoAccountForm):
|
||||
self.instance.identifier = identifier
|
||||
self.instance.title = title
|
||||
self.instance.deductable_surface = surface
|
||||
self.instance.geometry = geometry
|
||||
self.instance.comment = comment
|
||||
self.instance.is_pik = is_pik
|
||||
self.instance.modified = action
|
||||
@@ -209,4 +210,23 @@ class EditEcoAccountForm(NewEcoAccountForm):
|
||||
|
||||
# Add the log entry to the main objects log list
|
||||
self.instance.log.add(action)
|
||||
|
||||
# Process the geometry form (NOT ATOMIC TRANSACTION DUE TO CELERY!)
|
||||
geometry = geom_form.save(action)
|
||||
self.instance.geometry = geometry
|
||||
self.instance.save()
|
||||
self.instance.update_deductable_rest()
|
||||
return self.instance
|
||||
|
||||
|
||||
class RemoveEcoAccountModalForm(RemoveModalForm):
|
||||
|
||||
def is_valid(self):
|
||||
super_valid = super().is_valid()
|
||||
has_deductions = self.instance.deductions.exists()
|
||||
if has_deductions:
|
||||
self.add_error(
|
||||
"confirm",
|
||||
_("The account can not be removed, since there are still deductions.")
|
||||
)
|
||||
return super_valid and not has_deductions
|
||||
|
||||
36
compensation/migrations/0011_ecoaccount_deductable_rest.py
Normal file
36
compensation/migrations/0011_ecoaccount_deductable_rest.py
Normal file
@@ -0,0 +1,36 @@
|
||||
# Generated by Django 3.1.3 on 2022-10-11 11:39
|
||||
|
||||
from django.db import migrations, models
|
||||
from django.db.models import Sum
|
||||
|
||||
|
||||
def fill_deductable_rest(apps, schema_editor):
|
||||
EcoAccount = apps.get_model("compensation", "EcoAccount")
|
||||
accs = EcoAccount.objects.all()
|
||||
for acc in accs:
|
||||
|
||||
deductions = acc.deductions.filter(
|
||||
intervention__deleted=None,
|
||||
)
|
||||
deductions_surfaces = deductions.aggregate(Sum("surface"))["surface__sum"] or 0
|
||||
available_surfaces = acc.deductable_surface or deductions_surfaces
|
||||
rest = available_surfaces - deductions_surfaces
|
||||
|
||||
acc.deductable_rest = rest
|
||||
acc.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('compensation', '0010_auto_20220815_1030'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='ecoaccount',
|
||||
name='deductable_rest',
|
||||
field=models.FloatField(blank=True, default=0, help_text='Amount of deductable rest', null=True),
|
||||
),
|
||||
migrations.RunPython(fill_deductable_rest)
|
||||
]
|
||||
26
compensation/migrations/0012_auto_20221116_1322.py
Normal file
26
compensation/migrations/0012_auto_20221116_1322.py
Normal file
@@ -0,0 +1,26 @@
|
||||
# Generated by Django 3.1.3 on 2022-11-16 12:22
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('user', '0006_auto_20220815_0759'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('compensation', '0011_ecoaccount_deductable_rest'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='ecoaccount',
|
||||
name='teams',
|
||||
field=models.ManyToManyField(blank=True, help_text='Teams having access (data shared with)', to='user.Team'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='ecoaccount',
|
||||
name='users',
|
||||
field=models.ManyToManyField(blank=True, help_text='Users having access (data shared with)', to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
]
|
||||
35
compensation/migrations/0013_auto_20221117_0819.py
Normal file
35
compensation/migrations/0013_auto_20221117_0819.py
Normal file
@@ -0,0 +1,35 @@
|
||||
# Generated by Django 3.1.3 on 2022-11-17 07:19
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
from compensation.models import UnitChoices
|
||||
|
||||
|
||||
def harmonize_action_units(apps, schema_editor):
|
||||
"""
|
||||
CompensationAction units (based on UnitChoices) can be mixed up at this point where
|
||||
* qm represents m² and
|
||||
* m2 represents m²
|
||||
|
||||
We drop qm in support of m2
|
||||
|
||||
"""
|
||||
CompensationAction = apps.get_model("compensation", "CompensationAction")
|
||||
actions = CompensationAction.objects.filter(
|
||||
unit="qm"
|
||||
)
|
||||
|
||||
for action in actions:
|
||||
action.unit = UnitChoices.m2
|
||||
action.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('compensation', '0012_auto_20221116_1322'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(harmonize_action_units),
|
||||
]
|
||||
18
compensation/migrations/0014_auto_20221118_1620.py
Normal file
18
compensation/migrations/0014_auto_20221118_1620.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 3.1.3 on 2022-11-18 15:20
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('compensation', '0013_auto_20221117_0819'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='compensationaction',
|
||||
name='unit',
|
||||
field=models.CharField(blank=True, choices=[('cm', 'cm'), ('m', 'm'), ('m2', 'm²'), ('m3', 'm³'), ('km', 'km'), ('ha', 'ha'), ('pcs', 'Pieces')], max_length=100, null=True),
|
||||
),
|
||||
]
|
||||
@@ -19,8 +19,9 @@ class UnitChoices(models.TextChoices):
|
||||
"""
|
||||
cm = "cm", _("cm")
|
||||
m = "m", _("m")
|
||||
m2 = "m2", _("m²")
|
||||
m3 = "m3", _("m³")
|
||||
km = "km", _("km")
|
||||
qm = "qm", _("m²")
|
||||
ha = "ha", _("ha")
|
||||
st = "pcs", _("Pieces") # pieces
|
||||
|
||||
|
||||
@@ -35,6 +35,12 @@ class EcoAccount(AbstractCompensation, ShareableObjectMixin, RecordableObjectMix
|
||||
help_text="Amount of deductable surface - can be lower than the total surface due to deduction limitations",
|
||||
default=0,
|
||||
)
|
||||
deductable_rest = models.FloatField(
|
||||
blank=True,
|
||||
null=True,
|
||||
help_text="Amount of deductable rest",
|
||||
default=0,
|
||||
)
|
||||
|
||||
legal = models.OneToOneField(
|
||||
"intervention.Legal",
|
||||
@@ -100,28 +106,29 @@ class EcoAccount(AbstractCompensation, ShareableObjectMixin, RecordableObjectMix
|
||||
"""
|
||||
return self.after_states.all().aggregate(Sum("surface"))["surface__sum"] or 0
|
||||
|
||||
def get_available_rest(self) -> (float, float):
|
||||
def __calculate_deductable_rest(self):
|
||||
""" Calculates available rest surface of the eco account
|
||||
|
||||
Args:
|
||||
|
||||
Returns:
|
||||
ret_val_total (float): Total amount
|
||||
ret_val_relative (float): Amount as percentage (0-100)
|
||||
"""
|
||||
deductions = self.deductions.filter(
|
||||
intervention__deleted=None,
|
||||
)
|
||||
deductions_surfaces = deductions.aggregate(Sum("surface"))["surface__sum"] or 0
|
||||
available_surfaces = self.deductable_surface or deductions_surfaces ## no division by zero
|
||||
ret_val_total = available_surfaces - deductions_surfaces
|
||||
|
||||
if available_surfaces > 0:
|
||||
ret_val_relative = int((ret_val_total / available_surfaces) * 100)
|
||||
available_surface = self.deductable_surface
|
||||
if available_surface is None:
|
||||
# Fallback!
|
||||
available_surface = deductions_surfaces
|
||||
else:
|
||||
ret_val_relative = 0
|
||||
available_surface = float(available_surface)
|
||||
|
||||
return ret_val_total, ret_val_relative
|
||||
ret_val = available_surface - deductions_surfaces
|
||||
|
||||
return ret_val
|
||||
|
||||
def quality_check(self) -> EcoAccountQualityChecker:
|
||||
""" Quality check
|
||||
@@ -181,6 +188,29 @@ class EcoAccount(AbstractCompensation, ShareableObjectMixin, RecordableObjectMix
|
||||
for team_id in shared_teams:
|
||||
celery_send_mail_deduction_changed_team.delay(self.identifier, self.title, team_id, data_change)
|
||||
|
||||
def update_deductable_rest(self):
|
||||
"""
|
||||
Updates deductable_rest, which holds the amount of rest surface for this account.
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
self.deductable_rest = self.__calculate_deductable_rest()
|
||||
self.save()
|
||||
|
||||
def get_deductable_rest_relative(self):
|
||||
"""
|
||||
Returns deductable_rest relative to deductable_surface mapped to [0,100]
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
try:
|
||||
ret_val = int((self.deductable_rest / (self.deductable_surface or 0)) * 100)
|
||||
except ZeroDivisionError:
|
||||
ret_val = 0
|
||||
return ret_val
|
||||
|
||||
|
||||
class EcoAccountDocument(AbstractDocument):
|
||||
"""
|
||||
@@ -272,3 +302,8 @@ class EcoAccountDeduction(BaseResource):
|
||||
self.intervention.mark_as_edited(user, edit_comment=DEDUCTION_REMOVED)
|
||||
self.account.mark_as_edited(user, edit_comment=DEDUCTION_REMOVED)
|
||||
super().delete(*args, **kwargs)
|
||||
self.account.update_deductable_rest()
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
super().save(*args, **kwargs)
|
||||
self.account.update_deductable_rest()
|
||||
|
||||
@@ -14,11 +14,11 @@ from django.utils.translation import gettext_lazy as _
|
||||
from compensation.filters.compensation import CompensationTableFilter
|
||||
from compensation.models import Compensation
|
||||
from konova.utils.message_templates import DATA_IS_UNCHECKED, DATA_CHECKED_ON_TEMPLATE, DATA_CHECKED_PREVIOUSLY_TEMPLATE
|
||||
from konova.utils.tables import BaseTable, TableRenderMixin
|
||||
from konova.utils.tables import BaseTable, TableRenderMixin, TableOrderMixin
|
||||
import django_tables2 as tables
|
||||
|
||||
|
||||
class CompensationTable(BaseTable, TableRenderMixin):
|
||||
class CompensationTable(BaseTable, TableRenderMixin, TableOrderMixin):
|
||||
id = tables.Column(
|
||||
verbose_name=_("Identifier"),
|
||||
orderable=True,
|
||||
@@ -31,7 +31,7 @@ class CompensationTable(BaseTable, TableRenderMixin):
|
||||
)
|
||||
d = tables.Column(
|
||||
verbose_name=_("Parcel gmrkng"),
|
||||
orderable=True,
|
||||
orderable=False,
|
||||
accessor="geometry",
|
||||
)
|
||||
c = tables.Column(
|
||||
@@ -126,28 +126,6 @@ class CompensationTable(BaseTable, TableRenderMixin):
|
||||
)
|
||||
return format_html(html)
|
||||
|
||||
def render_d(self, value, record: Compensation):
|
||||
""" Renders the parcel district column for a compensation
|
||||
|
||||
Args:
|
||||
value (str): The geometry
|
||||
record (Compensation): The compensation record
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
parcels = value.get_underlying_parcels().values_list(
|
||||
"parcel_group__name",
|
||||
flat=True
|
||||
).distinct()
|
||||
html = render_to_string(
|
||||
"table/gmrkng_col.html",
|
||||
{
|
||||
"entries": parcels
|
||||
}
|
||||
)
|
||||
return html
|
||||
|
||||
def render_r(self, value, record: Compensation):
|
||||
""" Renders the registered column for a compensation
|
||||
|
||||
@@ -170,20 +148,3 @@ class CompensationTable(BaseTable, TableRenderMixin):
|
||||
)
|
||||
return format_html(html)
|
||||
|
||||
def render_e(self, value, record: Compensation):
|
||||
""" Renders the editable column for a compensation
|
||||
|
||||
Args:
|
||||
value (str): The identifier value
|
||||
record (Compensation): The compensation record
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
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",
|
||||
)
|
||||
return format_html(html)
|
||||
|
||||
@@ -13,12 +13,12 @@ from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from compensation.filters.eco_account import EcoAccountTableFilter
|
||||
from compensation.models import EcoAccount
|
||||
from konova.utils.tables import TableRenderMixin, BaseTable
|
||||
from konova.utils.tables import TableRenderMixin, BaseTable, TableOrderMixin
|
||||
|
||||
import django_tables2 as tables
|
||||
|
||||
|
||||
class EcoAccountTable(BaseTable, TableRenderMixin):
|
||||
class EcoAccountTable(BaseTable, TableRenderMixin, TableOrderMixin):
|
||||
id = tables.Column(
|
||||
verbose_name=_("Identifier"),
|
||||
orderable=True,
|
||||
@@ -31,13 +31,13 @@ class EcoAccountTable(BaseTable, TableRenderMixin):
|
||||
)
|
||||
d = tables.Column(
|
||||
verbose_name=_("Parcel gmrkng"),
|
||||
orderable=True,
|
||||
orderable=False,
|
||||
accessor="geometry",
|
||||
)
|
||||
av = tables.Column(
|
||||
verbose_name=_("Available"),
|
||||
orderable=True,
|
||||
empty_values=[],
|
||||
accessor="deductable_rest",
|
||||
attrs={
|
||||
"th": {
|
||||
"class": "w-20",
|
||||
@@ -100,38 +100,19 @@ class EcoAccountTable(BaseTable, TableRenderMixin):
|
||||
""" Renders the available column for an eco account
|
||||
|
||||
Args:
|
||||
value (str): The identifier value
|
||||
value (float): The deductable_rest
|
||||
record (EcoAccount): The eco account record
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
value_total, value_relative = record.get_available_rest()
|
||||
try:
|
||||
value_relative = record.get_deductable_rest_relative()
|
||||
except ZeroDivisionError:
|
||||
value_relative = 0
|
||||
html = render_to_string("konova/widgets/progressbar.html", {"value": value_relative})
|
||||
return format_html(html)
|
||||
|
||||
def render_d(self, value, record):
|
||||
""" Renders the parcel district column for a compensation
|
||||
|
||||
Args:
|
||||
value (str): The geometry
|
||||
record (Compensation): The compensation record
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
parcels = value.get_underlying_parcels().values_list(
|
||||
"parcel_group__name",
|
||||
flat=True
|
||||
).distinct()
|
||||
html = render_to_string(
|
||||
"table/gmrkng_col.html",
|
||||
{
|
||||
"entries": parcels
|
||||
}
|
||||
)
|
||||
return html
|
||||
|
||||
def render_r(self, value, record: EcoAccount):
|
||||
""" Renders the recorded column for an eco account
|
||||
|
||||
@@ -153,23 +134,3 @@ class EcoAccountTable(BaseTable, TableRenderMixin):
|
||||
icn_filled=checked,
|
||||
)
|
||||
return format_html(html)
|
||||
|
||||
def render_e(self, value, record: EcoAccount):
|
||||
""" Renders the editable column for an eco account
|
||||
|
||||
Args:
|
||||
value (str): The identifier value
|
||||
record (EcoAccount): The eco account record
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
html = ""
|
||||
# Do not use value in here, since value does use unprefetched 'users' manager, where record has already
|
||||
# prefetched users data
|
||||
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",
|
||||
)
|
||||
return format_html(html)
|
||||
|
||||
@@ -74,6 +74,10 @@
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<div class="alert alert-danger mb-0">
|
||||
{% trans 'Missing' %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -68,6 +68,10 @@
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<div class="alert alert-danger mb-0">
|
||||
{% trans 'Missing' %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -68,6 +68,10 @@
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<div class="alert alert-danger mb-0">
|
||||
{% trans 'Missing' %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -124,7 +124,7 @@
|
||||
{% endfor %}
|
||||
<hr>
|
||||
{% if has_access %}
|
||||
{% for user in obj.users.all %}
|
||||
{% for user in obj.intervention.shared_users %}
|
||||
{% include 'user/includes/contact_modal_button.html' %}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
|
||||
@@ -73,6 +73,10 @@
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<div class="alert alert-danger mb-0">
|
||||
{% trans 'Missing' %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -68,6 +68,10 @@
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<div class="alert alert-danger mb-0">
|
||||
{% trans 'Missing' %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -68,6 +68,10 @@
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<div class="alert alert-danger mb-0">
|
||||
{% trans 'Missing' %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -71,6 +71,7 @@ class CompensationWorkflowTestCase(BaseWorkflowTestCase):
|
||||
self.assertEqual(new_compensation.title, test_title)
|
||||
self.assert_equal_geometries(new_compensation.geometry.geom, test_geom)
|
||||
self.assertEqual(new_compensation.log.count(), 1)
|
||||
self.assertEqual(new_compensation.created, new_compensation.modified)
|
||||
|
||||
# Expect logs to be set
|
||||
self.assertEqual(pre_creation_intervention_log_count + 1, self.intervention.log.count())
|
||||
|
||||
@@ -47,7 +47,7 @@ class EcoAccountWorkflowTestCase(BaseWorkflowTestCase):
|
||||
"identifier": test_id,
|
||||
"title": test_title,
|
||||
"geom": geom_json,
|
||||
"deductable_surface": test_deductable_surface,
|
||||
"surface": test_deductable_surface,
|
||||
"conservation_office": test_conservation_office.id
|
||||
}
|
||||
self.client_user.post(new_url, post_data)
|
||||
@@ -61,8 +61,11 @@ class EcoAccountWorkflowTestCase(BaseWorkflowTestCase):
|
||||
|
||||
self.assertEqual(acc.identifier, test_id)
|
||||
self.assertEqual(acc.title, test_title)
|
||||
self.assertEqual(acc.deductable_surface, test_deductable_surface)
|
||||
self.assertEqual(acc.deductable_rest, test_deductable_surface)
|
||||
self.assert_equal_geometries(acc.geometry.geom, test_geom)
|
||||
self.assertEqual(acc.log.count(), 1)
|
||||
self.assertEqual(acc.created, acc.modified)
|
||||
|
||||
# Expect logs to be set
|
||||
self.assertEqual(acc.log.count(), 1)
|
||||
@@ -84,7 +87,7 @@ class EcoAccountWorkflowTestCase(BaseWorkflowTestCase):
|
||||
new_comment = self.create_dummy_string()
|
||||
new_geometry = MultiPolygon(srid=4326) # Create an empty geometry
|
||||
test_conservation_office = self.get_conservation_office_code()
|
||||
test_deductable_surface = 10005
|
||||
test_deductable_surface = self.eco_account.deductable_surface + 100
|
||||
|
||||
check_on_elements = {
|
||||
self.eco_account.title: new_title,
|
||||
@@ -110,6 +113,7 @@ class EcoAccountWorkflowTestCase(BaseWorkflowTestCase):
|
||||
self.eco_account.title: new_title,
|
||||
self.eco_account.identifier: new_identifier,
|
||||
self.eco_account.deductable_surface: test_deductable_surface,
|
||||
self.eco_account.deductable_rest: test_deductable_surface,
|
||||
self.eco_account.comment: new_comment,
|
||||
}
|
||||
|
||||
@@ -185,7 +189,7 @@ class EcoAccountWorkflowTestCase(BaseWorkflowTestCase):
|
||||
|
||||
# Prepare data for deduction creation
|
||||
deduct_url = reverse("compensation:acc:new-deduction", args=(self.eco_account.id,))
|
||||
test_surface = 10.00
|
||||
test_surface = 10.50
|
||||
post_data = {
|
||||
"surface": test_surface,
|
||||
"account": self.eco_account.id,
|
||||
@@ -194,17 +198,17 @@ class EcoAccountWorkflowTestCase(BaseWorkflowTestCase):
|
||||
# Perform request --> expect to fail
|
||||
self.client_user.post(deduct_url, post_data)
|
||||
|
||||
# Expect that no deduction has been created
|
||||
# Expect that no deduction has been created since the eco account is not recorded, yet
|
||||
self.assertEqual(0, self.eco_account.deductions.count())
|
||||
self.assertEqual(0, self.intervention.deductions.count())
|
||||
self.assertEqual(pre_deduction_acc_log_count, 0)
|
||||
self.assertEqual(pre_deduction_int_log_count, 0)
|
||||
|
||||
# Now mock the eco account as it would be recorded (with invalid data)
|
||||
# Make sure the deductible surface is high enough for the request
|
||||
# Make sure the deductible surface is valid for the request
|
||||
self.eco_account.set_recorded(self.superuser)
|
||||
self.eco_account.refresh_from_db()
|
||||
self.eco_account.deductable_surface = test_surface + 1.00
|
||||
self.eco_account.deductable_surface = test_surface + 1.0
|
||||
self.eco_account.save()
|
||||
self.assertIsNotNone(self.eco_account.recorded)
|
||||
self.assertGreater(self.eco_account.deductable_surface, test_surface)
|
||||
@@ -216,10 +220,12 @@ class EcoAccountWorkflowTestCase(BaseWorkflowTestCase):
|
||||
self.client_user.post(deduct_url, post_data)
|
||||
|
||||
# Expect that the deduction has been created
|
||||
self.eco_account.refresh_from_db()
|
||||
self.assertEqual(1, self.eco_account.deductions.count())
|
||||
self.assertEqual(1, self.intervention.deductions.count())
|
||||
deduction = self.eco_account.deductions.first()
|
||||
self.assertEqual(deduction.surface, test_surface)
|
||||
self.assertEqual(self.eco_account.deductable_rest, self.eco_account.deductable_surface - deduction.surface)
|
||||
self.assertEqual(deduction.account, self.eco_account)
|
||||
self.assertEqual(deduction.intervention, self.intervention)
|
||||
|
||||
@@ -230,7 +236,7 @@ class EcoAccountWorkflowTestCase(BaseWorkflowTestCase):
|
||||
self.assertTrue(self.intervention.log.first().action == UserAction.EDITED)
|
||||
|
||||
def test_edit_deduction(self):
|
||||
test_surface = self.eco_account.get_available_rest()[0]
|
||||
test_surface = self.eco_account.deductable_rest
|
||||
self.eco_account.set_recorded(self.superuser)
|
||||
self.intervention.share_with_user(self.superuser)
|
||||
self.eco_account.refresh_from_db()
|
||||
@@ -239,7 +245,7 @@ class EcoAccountWorkflowTestCase(BaseWorkflowTestCase):
|
||||
deduction = EcoAccountDeduction.objects.create(
|
||||
intervention=self.intervention,
|
||||
account=self.eco_account,
|
||||
surface=0
|
||||
surface=1.10
|
||||
)
|
||||
self.assertEqual(1, self.intervention.deductions.count())
|
||||
self.assertEqual(1, self.eco_account.deductions.count())
|
||||
@@ -262,6 +268,7 @@ class EcoAccountWorkflowTestCase(BaseWorkflowTestCase):
|
||||
self.eco_account.refresh_from_db()
|
||||
deduction.refresh_from_db()
|
||||
|
||||
self.assertEqual(self.eco_account.deductable_rest, self.eco_account.deductable_surface - deduction.surface)
|
||||
self.assertEqual(num_deductions_intervention, self.intervention.deductions.count())
|
||||
self.assertEqual(num_deductions_account, self.eco_account.deductions.count())
|
||||
self.assertEqual(deduction.surface, test_surface)
|
||||
@@ -275,6 +282,7 @@ class EcoAccountWorkflowTestCase(BaseWorkflowTestCase):
|
||||
def test_remove_deduction(self):
|
||||
intervention = self.deduction.intervention
|
||||
account = self.deduction.account
|
||||
deducted_surface = self.deduction.surface
|
||||
|
||||
# Prepare url and form data to be posted
|
||||
new_url = reverse("compensation:acc:remove-deduction", args=(account.id, self.deduction.id))
|
||||
@@ -287,6 +295,7 @@ class EcoAccountWorkflowTestCase(BaseWorkflowTestCase):
|
||||
|
||||
pre_edit_intervention_log_count = intervention.log.count()
|
||||
pre_edit_account_log_count = account.log.count()
|
||||
pre_edit_account_rest = account.deductable_rest
|
||||
num_deductions_intervention = intervention.deductions.count()
|
||||
num_deductions_account = account.deductions.count()
|
||||
|
||||
@@ -297,6 +306,7 @@ class EcoAccountWorkflowTestCase(BaseWorkflowTestCase):
|
||||
|
||||
self.assertEqual(num_deductions_intervention - 1, intervention.deductions.count())
|
||||
self.assertEqual(num_deductions_account - 1, account.deductions.count())
|
||||
self.assertEqual(account.deductable_rest, pre_edit_account_rest + deducted_surface)
|
||||
|
||||
# Expect logs to be set
|
||||
self.assertEqual(pre_edit_intervention_log_count + 1, intervention.log.count())
|
||||
|
||||
@@ -46,6 +46,8 @@ def index_view(request: HttpRequest):
|
||||
compensations = Compensation.objects.filter(
|
||||
deleted=None, # only show those which are not deleted individually
|
||||
intervention__deleted=None, # and don't show the ones whose intervention has been deleted
|
||||
).order_by(
|
||||
"-modified__timestamp"
|
||||
)
|
||||
table = CompensationTable(
|
||||
request=request,
|
||||
|
||||
@@ -13,13 +13,12 @@ from django.shortcuts import get_object_or_404, redirect, render
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from compensation.forms.eco_account import EditEcoAccountForm, NewEcoAccountForm
|
||||
from compensation.forms.eco_account import EditEcoAccountForm, NewEcoAccountForm, RemoveEcoAccountModalForm
|
||||
from compensation.models import EcoAccount
|
||||
from compensation.tables.eco_account import EcoAccountTable
|
||||
from konova.contexts import BaseContext
|
||||
from konova.decorators import shared_access_required, default_group_required, any_group_check, login_required_modal
|
||||
from konova.forms import SimpleGeomForm
|
||||
from konova.forms.modals import RemoveModalForm
|
||||
from konova.settings import ETS_GROUP, DEFAULT_GROUP, ZB_GROUP
|
||||
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
|
||||
from konova.utils.message_templates import CANCEL_ACC_RECORDED_OR_DEDUCTED, RECORDED_BLOCKS_EDIT, FORM_INVALID, \
|
||||
@@ -42,6 +41,8 @@ def index_view(request: HttpRequest):
|
||||
template = "generic_index.html"
|
||||
eco_accounts = EcoAccount.objects.filter(
|
||||
deleted=None,
|
||||
).order_by(
|
||||
"-modified__timestamp"
|
||||
)
|
||||
table = EcoAccountTable(
|
||||
request=request,
|
||||
@@ -200,7 +201,8 @@ def detail_view(request: HttpRequest, id: str):
|
||||
sum_after_states = after_states.aggregate(Sum("surface"))["surface__sum"] or 0
|
||||
diff_states = abs(sum_before_states - sum_after_states)
|
||||
# Calculate rest of available surface for deductions
|
||||
available_total, available_relative = acc.get_available_rest()
|
||||
available_total = acc.deductable_rest
|
||||
available_relative = acc.get_deductable_rest_relative()
|
||||
|
||||
# Prefetch related data to decrease the amount of db connections
|
||||
deductions = acc.deductions.filter(
|
||||
@@ -259,7 +261,7 @@ def remove_view(request: HttpRequest, id: str):
|
||||
messages.info(request, CANCEL_ACC_RECORDED_OR_DEDUCTED)
|
||||
return redirect("compensation:acc:detail", id=id)
|
||||
|
||||
form = RemoveModalForm(request.POST or None, instance=acc, request=request)
|
||||
form = RemoveEcoAccountModalForm(request.POST or None, instance=acc, request=request)
|
||||
return form.process_request(
|
||||
request=request,
|
||||
msg_success=_("Eco-account removed"),
|
||||
|
||||
@@ -6,12 +6,14 @@ from ema.models import Ema
|
||||
|
||||
class EmaAdmin(AbstractCompensationAdmin):
|
||||
filter_horizontal = [
|
||||
"users"
|
||||
"users",
|
||||
"teams",
|
||||
]
|
||||
|
||||
def get_fields(self, request, obj=None):
|
||||
return super().get_fields(request, obj) + [
|
||||
"users"
|
||||
"users",
|
||||
"teams",
|
||||
]
|
||||
|
||||
admin.site.register(Ema, EmaAdmin)
|
||||
|
||||
17
ema/forms.py
17
ema/forms.py
@@ -64,8 +64,6 @@ class NewEmaForm(AbstractCompensationForm, CompensationResponsibleFormMixin, Pik
|
||||
|
||||
# Create log entry
|
||||
action = UserActionLogEntry.get_created_action(user)
|
||||
# Process the geometry form
|
||||
geometry = geom_form.save(action)
|
||||
|
||||
handler = Handler.objects.create(
|
||||
type=handler_type,
|
||||
@@ -83,7 +81,7 @@ class NewEmaForm(AbstractCompensationForm, CompensationResponsibleFormMixin, Pik
|
||||
title=title,
|
||||
responsible=responsible,
|
||||
created=action,
|
||||
geometry=geometry,
|
||||
modified=action,
|
||||
comment=comment,
|
||||
is_pik=is_pik,
|
||||
)
|
||||
@@ -93,6 +91,11 @@ class NewEmaForm(AbstractCompensationForm, CompensationResponsibleFormMixin, Pik
|
||||
|
||||
# Add the log entry to the main objects log list
|
||||
acc.log.add(action)
|
||||
|
||||
# Process the geometry form (NOT ATOMIC TRANSACTION DUE TO CELERY!)
|
||||
geometry = geom_form.save(action)
|
||||
acc.geometry = geometry
|
||||
acc.save()
|
||||
return acc
|
||||
|
||||
|
||||
@@ -141,8 +144,6 @@ class EditEmaForm(NewEmaForm):
|
||||
|
||||
# Create log entry
|
||||
action = UserActionLogEntry.get_edited_action(user)
|
||||
# Process the geometry form
|
||||
geometry = geom_form.save(action)
|
||||
|
||||
# Update responsible data
|
||||
self.instance.responsible.handler.type = handler_type
|
||||
@@ -155,7 +156,6 @@ class EditEmaForm(NewEmaForm):
|
||||
# Update main oject data
|
||||
self.instance.identifier = identifier
|
||||
self.instance.title = title
|
||||
self.instance.geometry = geometry
|
||||
self.instance.comment = comment
|
||||
self.instance.is_pik = is_pik
|
||||
self.instance.modified = action
|
||||
@@ -163,6 +163,11 @@ class EditEmaForm(NewEmaForm):
|
||||
|
||||
# Add the log entry to the main objects log list
|
||||
self.instance.log.add(action)
|
||||
|
||||
# Process the geometry form (NOT ATOMIC TRANSACTION DUE TO CELERY!)
|
||||
geometry = geom_form.save(action)
|
||||
self.instance.geometry = geometry
|
||||
self.instance.save()
|
||||
return self.instance
|
||||
|
||||
|
||||
|
||||
26
ema/migrations/0008_auto_20221116_1322.py
Normal file
26
ema/migrations/0008_auto_20221116_1322.py
Normal file
@@ -0,0 +1,26 @@
|
||||
# Generated by Django 3.1.3 on 2022-11-16 12:22
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('user', '0006_auto_20220815_0759'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('ema', '0007_auto_20220815_1030'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='ema',
|
||||
name='teams',
|
||||
field=models.ManyToManyField(blank=True, help_text='Teams having access (data shared with)', to='user.Team'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='ema',
|
||||
name='users',
|
||||
field=models.ManyToManyField(blank=True, help_text='Users having access (data shared with)', to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
]
|
||||
@@ -6,21 +6,18 @@ Created on: 19.08.21
|
||||
|
||||
"""
|
||||
from django.http import HttpRequest
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils.html import format_html
|
||||
from django.utils.timezone import localtime
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.urls import reverse
|
||||
|
||||
import django_tables2 as tables
|
||||
|
||||
from konova.sub_settings.django_settings import DEFAULT_DATE_TIME_FORMAT
|
||||
from konova.utils.tables import BaseTable, TableRenderMixin
|
||||
from konova.utils.tables import BaseTable, TableRenderMixin, TableOrderMixin
|
||||
from ema.filters import EmaTableFilter
|
||||
from ema.models import Ema
|
||||
|
||||
|
||||
class EmaTable(BaseTable, TableRenderMixin):
|
||||
class EmaTable(BaseTable, TableRenderMixin, TableOrderMixin):
|
||||
"""
|
||||
Since EMA and compensation are basically the same, we can reuse CompensationTableFilter and extend the EMA filter
|
||||
in the future by inheriting.
|
||||
@@ -37,7 +34,7 @@ class EmaTable(BaseTable, TableRenderMixin):
|
||||
)
|
||||
d = tables.Column(
|
||||
verbose_name=_("Parcel gmrkng"),
|
||||
orderable=True,
|
||||
orderable=False,
|
||||
accessor="geometry",
|
||||
)
|
||||
r = tables.Column(
|
||||
@@ -93,28 +90,6 @@ class EmaTable(BaseTable, TableRenderMixin):
|
||||
)
|
||||
return format_html(html)
|
||||
|
||||
def render_d(self, value, record: Ema):
|
||||
""" Renders the parcel district column for a ema
|
||||
|
||||
Args:
|
||||
value (str): The geometry
|
||||
record (Ema): The ema record
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
parcels = value.get_underlying_parcels().values_list(
|
||||
"parcel_group__name",
|
||||
flat=True
|
||||
).distinct()
|
||||
html = render_to_string(
|
||||
"table/gmrkng_col.html",
|
||||
{
|
||||
"entries": parcels
|
||||
}
|
||||
)
|
||||
return html
|
||||
|
||||
def render_r(self, value, record: Ema):
|
||||
""" Renders the registered column for a EMA
|
||||
|
||||
@@ -136,22 +111,3 @@ class EmaTable(BaseTable, TableRenderMixin):
|
||||
icn_filled=recorded,
|
||||
)
|
||||
return format_html(html)
|
||||
|
||||
def render_e(self, value, record: Ema):
|
||||
""" Renders the editable column for a EMA
|
||||
|
||||
Args:
|
||||
value (str): The identifier value
|
||||
record (Ema): The EMA record
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
html = ""
|
||||
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",
|
||||
)
|
||||
return format_html(html)
|
||||
|
||||
@@ -71,6 +71,10 @@
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<div class="alert alert-danger mb-0">
|
||||
{% trans 'Missing' %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -66,6 +66,10 @@
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<div class="alert alert-danger mb-0">
|
||||
{% trans 'Missing' %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -66,6 +66,10 @@
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<div class="alert alert-danger mb-0">
|
||||
{% trans 'Missing' %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -62,6 +62,7 @@ class EmaWorkflowTestCase(BaseWorkflowTestCase):
|
||||
self.assertEqual(ema.title, test_title)
|
||||
self.assert_equal_geometries(ema.geometry.geom, test_geom)
|
||||
self.assertEqual(ema.log.count(), 1)
|
||||
self.assertEqual(ema.created, ema.modified)
|
||||
|
||||
# Expect logs to be set
|
||||
self.assertEqual(ema.log.count(), 1)
|
||||
|
||||
@@ -40,8 +40,9 @@ def index_view(request: HttpRequest):
|
||||
emas = Ema.objects.filter(
|
||||
deleted=None,
|
||||
).order_by(
|
||||
"-modified"
|
||||
"-modified__timestamp"
|
||||
)
|
||||
|
||||
table = EmaTable(
|
||||
request,
|
||||
queryset=emas
|
||||
|
||||
@@ -14,7 +14,8 @@ class InterventionAdmin(BaseObjectAdmin):
|
||||
]
|
||||
|
||||
filter_horizontal = [
|
||||
"users"
|
||||
"users",
|
||||
"teams",
|
||||
]
|
||||
|
||||
def get_fields(self, request, obj=None):
|
||||
@@ -25,6 +26,7 @@ class InterventionAdmin(BaseObjectAdmin):
|
||||
"checked",
|
||||
"recorded",
|
||||
"users",
|
||||
"teams",
|
||||
"geometry",
|
||||
]
|
||||
|
||||
|
||||
@@ -263,9 +263,6 @@ class NewInterventionForm(BaseForm):
|
||||
handler=handler,
|
||||
)
|
||||
|
||||
# Process the geometry form
|
||||
geometry = geom_form.save(action)
|
||||
|
||||
# Finally create main object, holding the other objects
|
||||
intervention = Intervention.objects.create(
|
||||
identifier=identifier,
|
||||
@@ -273,7 +270,7 @@ class NewInterventionForm(BaseForm):
|
||||
responsible=responsibility_data,
|
||||
legal=legal_data,
|
||||
created=action,
|
||||
geometry=geometry,
|
||||
modified=action,
|
||||
comment=comment,
|
||||
)
|
||||
|
||||
@@ -282,6 +279,12 @@ class NewInterventionForm(BaseForm):
|
||||
|
||||
# Add the performing user as the first user having access to the data
|
||||
intervention.share_with_user(user)
|
||||
|
||||
# Process the geometry form (NOT ATOMIC TRANSACTION DUE TO CELERY!)
|
||||
geometry = geom_form.save(action)
|
||||
intervention.geometry = geometry
|
||||
intervention.save()
|
||||
|
||||
return intervention
|
||||
|
||||
|
||||
@@ -370,9 +373,6 @@ class EditInterventionForm(NewInterventionForm):
|
||||
|
||||
user_action = self.instance.mark_as_edited(user, edit_comment=EDITED_GENERAL_DATA)
|
||||
|
||||
geometry = geom_form.save(user_action)
|
||||
self.instance.geometry = geometry
|
||||
|
||||
self.instance.log.add(user_action)
|
||||
|
||||
self.instance.identifier = identifier
|
||||
@@ -381,5 +381,10 @@ class EditInterventionForm(NewInterventionForm):
|
||||
self.instance.modified = user_action
|
||||
self.instance.save()
|
||||
|
||||
# Process the geometry form (NOT ATOMIC TRANSACTION DUE TO CELERY!)
|
||||
geometry = geom_form.save(user_action)
|
||||
self.instance.geometry = geometry
|
||||
self.instance.save()
|
||||
|
||||
return self.instance
|
||||
|
||||
|
||||
@@ -163,6 +163,10 @@ class NewEcoAccountDeductionModalForm(BaseModalForm):
|
||||
self.cleaned_data["account"].mark_as_edited(self.user, edit_comment=DEDUCTION_ADDED)
|
||||
return deduction
|
||||
|
||||
def check_for_recorded_instance(self):
|
||||
# Ignore super() implementation
|
||||
return
|
||||
|
||||
|
||||
class EditEcoAccountDeductionModalForm(NewEcoAccountDeductionModalForm):
|
||||
deduction = None
|
||||
@@ -231,6 +235,16 @@ class EditEcoAccountDeductionModalForm(NewEcoAccountDeductionModalForm):
|
||||
old_account.send_notification_mail_on_deduction_change(data_changes)
|
||||
return deduction
|
||||
|
||||
def check_for_recorded_instance(self):
|
||||
"""
|
||||
Extension to super class base method
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
if self.deduction.intervention.is_recorded:
|
||||
self.block_form()
|
||||
|
||||
|
||||
class RemoveEcoAccountDeductionModalForm(RemoveModalForm):
|
||||
""" Removing modal form for EcoAccountDeduction
|
||||
@@ -249,4 +263,8 @@ class RemoveEcoAccountDeductionModalForm(RemoveModalForm):
|
||||
with transaction.atomic():
|
||||
self.deduction.intervention.mark_as_edited(self.user, edit_comment=DEDUCTION_REMOVED)
|
||||
self.deduction.account.mark_as_edited(self.user, edit_comment=DEDUCTION_REMOVED)
|
||||
self.deduction.delete()
|
||||
self.deduction.delete()
|
||||
|
||||
def check_for_recorded_instance(self):
|
||||
if self.deduction.intervention.is_recorded:
|
||||
self.block_form()
|
||||
|
||||
@@ -10,4 +10,23 @@ from konova.forms.modals import NewDocumentModalForm
|
||||
|
||||
|
||||
class NewInterventionDocumentModalForm(NewDocumentModalForm):
|
||||
document_model = InterventionDocument
|
||||
document_model = InterventionDocument
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
""" Extension of regular NewDocumentModalForm
|
||||
|
||||
Checks whether payments exist on the intervention and sends the data to EGON
|
||||
|
||||
Args:
|
||||
*args ():
|
||||
**kwargs ():
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
doc = super().save(*args, **kwargs)
|
||||
|
||||
if self.instance.payments.exists():
|
||||
self.instance.send_data_to_egon()
|
||||
|
||||
return doc
|
||||
|
||||
26
intervention/migrations/0008_auto_20221116_1322.py
Normal file
26
intervention/migrations/0008_auto_20221116_1322.py
Normal file
@@ -0,0 +1,26 @@
|
||||
# Generated by Django 3.1.3 on 2022-11-16 12:22
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('user', '0006_auto_20220815_0759'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('intervention', '0007_auto_20220815_1030'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='intervention',
|
||||
name='teams',
|
||||
field=models.ManyToManyField(blank=True, help_text='Teams having access (data shared with)', to='user.Team'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='intervention',
|
||||
name='users',
|
||||
field=models.ManyToManyField(blank=True, help_text='Users having access (data shared with)', to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
]
|
||||
@@ -14,11 +14,11 @@ from django.utils.translation import gettext_lazy as _
|
||||
from intervention.filters import InterventionTableFilter
|
||||
from intervention.models import Intervention
|
||||
from konova.utils.message_templates import DATA_CHECKED_ON_TEMPLATE, DATA_IS_UNCHECKED, DATA_CHECKED_PREVIOUSLY_TEMPLATE
|
||||
from konova.utils.tables import BaseTable, TableRenderMixin
|
||||
from konova.utils.tables import BaseTable, TableRenderMixin, TableOrderMixin
|
||||
import django_tables2 as tables
|
||||
|
||||
|
||||
class InterventionTable(BaseTable, TableRenderMixin):
|
||||
class InterventionTable(BaseTable, TableRenderMixin, TableOrderMixin):
|
||||
id = tables.Column(
|
||||
verbose_name=_("Identifier"),
|
||||
orderable=True,
|
||||
@@ -31,7 +31,7 @@ class InterventionTable(BaseTable, TableRenderMixin):
|
||||
)
|
||||
d = tables.Column(
|
||||
verbose_name=_("Parcel gmrkng"),
|
||||
orderable=True,
|
||||
orderable=False,
|
||||
accessor="geometry",
|
||||
)
|
||||
c = tables.Column(
|
||||
@@ -124,28 +124,6 @@ class InterventionTable(BaseTable, TableRenderMixin):
|
||||
)
|
||||
return format_html(html)
|
||||
|
||||
def render_d(self, value, record: Intervention):
|
||||
""" Renders the parcel district column for an intervention
|
||||
|
||||
Args:
|
||||
value (str): The intervention geometry
|
||||
record (Intervention): The intervention record
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
parcels = value.get_underlying_parcels().values_list(
|
||||
"parcel_group__name",
|
||||
flat=True
|
||||
).distinct()
|
||||
html = render_to_string(
|
||||
"table/gmrkng_col.html",
|
||||
{
|
||||
"entries": parcels
|
||||
}
|
||||
)
|
||||
return html
|
||||
|
||||
def render_r(self, value, record: Intervention):
|
||||
""" Renders the recorded column for an intervention
|
||||
|
||||
@@ -168,22 +146,3 @@ class InterventionTable(BaseTable, TableRenderMixin):
|
||||
)
|
||||
return format_html(html)
|
||||
|
||||
def render_e(self, value, record: Intervention):
|
||||
""" Renders the editable column for an intervention
|
||||
|
||||
Args:
|
||||
value (str): The identifier value
|
||||
record (Intervention): The intervention record
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
html = ""
|
||||
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",
|
||||
)
|
||||
return format_html(html)
|
||||
|
||||
|
||||
@@ -20,6 +20,11 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% if has_payment_without_document %}
|
||||
<div class="alert alert-danger mb-0">
|
||||
{% trans 'You entered a payment. Please upload the legal document which defines the payment`s amount.' %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="card-body scroll-300 p-2">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
|
||||
@@ -11,7 +11,7 @@ from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.urls import reverse
|
||||
|
||||
from compensation.models import Payment, EcoAccountDeduction
|
||||
from intervention.models import Intervention
|
||||
from intervention.models import Intervention, InterventionDocument
|
||||
from konova.settings import ETS_GROUP, ZB_GROUP
|
||||
from konova.tests.test_views import BaseWorkflowTestCase
|
||||
from user.models import UserActionLogEntry, UserAction
|
||||
@@ -78,6 +78,7 @@ class InterventionWorkflowTestCase(BaseWorkflowTestCase):
|
||||
self.assertEqual(1, obj.log.count())
|
||||
self.assertEqual(obj.log.first().action, UserAction.CREATED)
|
||||
self.assertEqual(obj.log.first().user, self.superuser)
|
||||
self.assertEqual(obj.created, obj.modified)
|
||||
except ObjectDoesNotExist:
|
||||
# Fail if there is no such object
|
||||
self.fail()
|
||||
@@ -153,13 +154,16 @@ class InterventionWorkflowTestCase(BaseWorkflowTestCase):
|
||||
payment = Payment.objects.create(amount=10.00, due_on=None, comment="No due date because test")
|
||||
self.intervention.payments.add(payment)
|
||||
|
||||
# Since there is a payment, we need to add a dummy document (mocking a legal document for payment info)
|
||||
document = self.create_dummy_document(InterventionDocument, self.intervention)
|
||||
|
||||
# Run request again
|
||||
self.client_user.post(check_url, post_data)
|
||||
|
||||
# Update intervention from db
|
||||
self.intervention.refresh_from_db()
|
||||
|
||||
# We expect the intervention to be checked now and contains the proper data
|
||||
# We expect the intervention to be checked now and contain the proper data
|
||||
# Attention: We check the timestamp only on the date, not the time, since the microseconds delay would result
|
||||
# in an unwanted assertion error
|
||||
checked = self.intervention.checked
|
||||
@@ -209,6 +213,9 @@ class InterventionWorkflowTestCase(BaseWorkflowTestCase):
|
||||
payment = Payment.objects.create(amount=10.00, due_on=None, comment="No due date because test")
|
||||
self.intervention.payments.add(payment)
|
||||
|
||||
# Since there is a payment, we need to add a dummy document (mocking a legal document for payment info)
|
||||
document = self.create_dummy_document(InterventionDocument, self.intervention)
|
||||
|
||||
# Run request again
|
||||
self.client_user.post(record_url, post_data)
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import json
|
||||
import pika
|
||||
import xmltodict
|
||||
from django.db.models import Sum
|
||||
from django.utils import formats
|
||||
|
||||
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
|
||||
@@ -92,6 +93,9 @@ class EgonGmlBuilder:
|
||||
)["summed"]
|
||||
return all_payments
|
||||
|
||||
def _float_to_localized_string(self, value: float):
|
||||
return formats.number_format(value, use_l10n=True, decimal_pos=2)
|
||||
|
||||
def _gen_kompensationsArt(self) -> (str, int):
|
||||
comp_type = "Ersatzzahlung"
|
||||
comp_type_code = 774898901
|
||||
@@ -191,7 +195,7 @@ class EgonGmlBuilder:
|
||||
"@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:ersatzzahlung": self._float_to_localized_string(self._sum_all_payments()),
|
||||
"oneo:kompensationsart": {
|
||||
"@xlink:href": f"http://register.naturschutz.rlp.de/repository/services/referenzliste/88140/{comp_type_code}",
|
||||
"#text": comp_type
|
||||
|
||||
@@ -21,8 +21,21 @@ class InterventionQualityChecker(AbstractQualityChecker):
|
||||
self._check_legal_data()
|
||||
self._check_compensations()
|
||||
self._check_geometry()
|
||||
self._check_payment_documents()
|
||||
self.valid = len(self.messages) == 0
|
||||
|
||||
def _check_payment_documents(self):
|
||||
""" Checks existence of documents in case of payments
|
||||
|
||||
There should be at least one legal document which defines the payment's total amount.
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
has_payment_without_document = self.obj.payments.exists() and not self.obj.get_documents()[1].exists()
|
||||
if has_payment_without_document:
|
||||
self._add_missing_attr_name(_("Documents"))
|
||||
|
||||
def _check_responsible_data(self):
|
||||
""" Checks data quality of related Responsibility
|
||||
|
||||
|
||||
@@ -45,6 +45,8 @@ def index_view(request: HttpRequest):
|
||||
deleted=None, # not deleted
|
||||
).select_related(
|
||||
"legal"
|
||||
).order_by(
|
||||
"-modified__timestamp"
|
||||
)
|
||||
table = InterventionTable(
|
||||
request=request,
|
||||
@@ -157,6 +159,8 @@ def detail_view(request: HttpRequest, id: str):
|
||||
if last_checked:
|
||||
last_checked_tooltip = DATA_CHECKED_PREVIOUSLY_TEMPLATE.format(last_checked.get_timestamp_str_formatted(), last_checked.user)
|
||||
|
||||
has_payment_without_document = intervention.payments.exists() and not intervention.get_documents()[1].exists()
|
||||
|
||||
context = {
|
||||
"obj": intervention,
|
||||
"last_checked": last_checked,
|
||||
@@ -168,6 +172,7 @@ def detail_view(request: HttpRequest, id: str):
|
||||
"is_zb_member": in_group(_user, ZB_GROUP),
|
||||
"is_ets_member": in_group(_user, ETS_GROUP),
|
||||
"LANIS_LINK": intervention.get_LANIS_link(),
|
||||
"has_payment_without_document": has_payment_without_document,
|
||||
TAB_TITLE_IDENTIFIER: f"{intervention.identifier} - {intervention.title}",
|
||||
}
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ class GeoReferencedTableFilterMixin(django_filters.FilterSet):
|
||||
),
|
||||
)
|
||||
# Parcel
|
||||
p = django_filters.CharFilter(
|
||||
p = django_filters.NumberFilter(
|
||||
method="filter_parcel",
|
||||
label=_(""),
|
||||
label_suffix=_(""),
|
||||
@@ -59,7 +59,7 @@ class GeoReferencedTableFilterMixin(django_filters.FilterSet):
|
||||
),
|
||||
)
|
||||
# Parcel counter
|
||||
pc = django_filters.CharFilter(
|
||||
pc = django_filters.NumberFilter(
|
||||
method="filter_parcel_counter",
|
||||
label=_(""),
|
||||
label_suffix=_(""),
|
||||
@@ -73,7 +73,7 @@ class GeoReferencedTableFilterMixin(django_filters.FilterSet):
|
||||
)
|
||||
|
||||
# Parcel counter
|
||||
pn = django_filters.CharFilter(
|
||||
pn = django_filters.NumberFilter(
|
||||
method="filter_parcel_number",
|
||||
label=_(""),
|
||||
label_suffix=_(""),
|
||||
@@ -165,7 +165,6 @@ class GeoReferencedTableFilterMixin(django_filters.FilterSet):
|
||||
Returns:
|
||||
|
||||
"""
|
||||
value = value.replace("-", "")
|
||||
queryset = self._filter_parcel_reference(
|
||||
queryset,
|
||||
Q(flr=value),
|
||||
@@ -183,7 +182,6 @@ class GeoReferencedTableFilterMixin(django_filters.FilterSet):
|
||||
Returns:
|
||||
|
||||
"""
|
||||
value = value.replace("-", "")
|
||||
queryset = self._filter_parcel_reference(
|
||||
queryset,
|
||||
Q(flrstck_zhlr=value)
|
||||
@@ -201,7 +199,6 @@ class GeoReferencedTableFilterMixin(django_filters.FilterSet):
|
||||
Returns:
|
||||
|
||||
"""
|
||||
value = value.replace("-", "")
|
||||
queryset = self._filter_parcel_reference(
|
||||
queryset,
|
||||
Q(flrstck_nnr=value),
|
||||
|
||||
@@ -134,24 +134,21 @@ class BaseForm(forms.Form):
|
||||
Returns:
|
||||
|
||||
"""
|
||||
from intervention.forms.modals.deduction import NewEcoAccountDeductionModalForm, EditEcoAccountDeductionModalForm, \
|
||||
RemoveEcoAccountDeductionModalForm
|
||||
from konova.forms.modals.resubmission_form import ResubmissionModalForm
|
||||
is_none = self.instance is None
|
||||
is_other_data_type = not isinstance(self.instance, BaseObject)
|
||||
is_deduction_form_from_account = isinstance(
|
||||
self,
|
||||
(
|
||||
NewEcoAccountDeductionModalForm,
|
||||
ResubmissionModalForm,
|
||||
EditEcoAccountDeductionModalForm,
|
||||
RemoveEcoAccountDeductionModalForm,
|
||||
)
|
||||
) and isinstance(self.instance, EcoAccount)
|
||||
|
||||
if is_none or is_other_data_type or is_deduction_form_from_account:
|
||||
if is_none or is_other_data_type:
|
||||
# Do nothing
|
||||
return
|
||||
|
||||
if self.instance.is_recorded:
|
||||
self.template = "form/recorded_no_edit.html"
|
||||
self.block_form()
|
||||
|
||||
def block_form(self):
|
||||
"""
|
||||
Overwrites template, providing no actions
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
self.template = "form/recorded_no_edit.html"
|
||||
@@ -10,11 +10,12 @@ import json
|
||||
from django.contrib.gis import gdal
|
||||
from django.contrib.gis.forms import MultiPolygonField
|
||||
from django.contrib.gis.geos import MultiPolygon, Polygon
|
||||
from django.contrib.gis.geos.prototypes.io import WKTWriter
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from konova.forms.base_form import BaseForm
|
||||
from konova.models import Geometry
|
||||
from konova.tasks import celery_update_parcels
|
||||
from konova.tasks import celery_update_parcels, celery_check_for_geometry_conflicts
|
||||
from konova.sub_settings.lanis_settings import DEFAULT_SRID_RLP
|
||||
from user.models import UserActionLogEntry
|
||||
|
||||
@@ -62,6 +63,7 @@ class SimpleGeomForm(BaseForm):
|
||||
geom = self.data["geom"]
|
||||
if geom is None or len(geom) == 0:
|
||||
# empty geometry is a valid geometry
|
||||
self.cleaned_data["geom"] = MultiPolygon(srid=DEFAULT_SRID_RLP).ewkt
|
||||
return is_valid
|
||||
geom = json.loads(geom)
|
||||
|
||||
@@ -74,9 +76,26 @@ class SimpleGeomForm(BaseForm):
|
||||
# this case)
|
||||
features = []
|
||||
features_json = geom.get("features", [])
|
||||
accepted_ogr_types = [
|
||||
"Polygon",
|
||||
"Polygon25D",
|
||||
"MultiPolygon",
|
||||
"MultiPolygon25D",
|
||||
]
|
||||
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"]:
|
||||
feature_geom = feature.get("geometry", feature)
|
||||
if feature_geom is None:
|
||||
# Fallback for rare cases where a feature does not contain any geometry
|
||||
continue
|
||||
|
||||
feature_geom = json.dumps(feature_geom)
|
||||
g = gdal.OGRGeometry(feature_geom, srs=DEFAULT_SRID_RLP)
|
||||
|
||||
flatten_geometry = g.coord_dim > 2
|
||||
if flatten_geometry:
|
||||
g = self.__flatten_geom_to_2D(g)
|
||||
|
||||
if g.geom_type not in accepted_ogr_types:
|
||||
self.add_error("geom", _("Only surfaces allowed. Points or lines must be buffered."))
|
||||
is_valid = False
|
||||
return is_valid
|
||||
@@ -88,6 +107,8 @@ class SimpleGeomForm(BaseForm):
|
||||
return is_valid
|
||||
|
||||
features.append(polygon)
|
||||
|
||||
# Unionize all geometry features into one new MultiPolygon
|
||||
form_geom = MultiPolygon(srid=DEFAULT_SRID_RLP)
|
||||
for feature in features:
|
||||
form_geom = form_geom.union(feature)
|
||||
@@ -128,6 +149,17 @@ class SimpleGeomForm(BaseForm):
|
||||
geom=self.cleaned_data.get("geom", MultiPolygon(srid=DEFAULT_SRID_RLP)),
|
||||
created=action,
|
||||
)
|
||||
# Start the parcel update procedure in a background process
|
||||
# Start parcel update and geometry conflict checking procedure in a background process
|
||||
celery_update_parcels.delay(geometry.id)
|
||||
return geometry
|
||||
celery_check_for_geometry_conflicts.delay(geometry.id)
|
||||
return geometry
|
||||
|
||||
def __flatten_geom_to_2D(self, geom):
|
||||
"""
|
||||
Enforces a given OGRGeometry from higher dimensions into 2D
|
||||
|
||||
"""
|
||||
wkt_w = WKTWriter(dim=2)
|
||||
g_wkt = wkt_w.write(geom.geos).decode("utf-8")
|
||||
geom = gdal.OGRGeometry(g_wkt)
|
||||
return geom
|
||||
|
||||
@@ -83,3 +83,6 @@ class ResubmissionModalForm(BaseModalForm):
|
||||
self.instance.resubmissions.add(self.resubmission)
|
||||
return self.resubmission
|
||||
|
||||
def check_for_recorded_instance(self):
|
||||
# Ignore logic in super() implementation
|
||||
return
|
||||
|
||||
137
konova/management/commands/generate_report.py
Normal file
137
konova/management/commands/generate_report.py
Normal file
@@ -0,0 +1,137 @@
|
||||
"""
|
||||
Author: Michel Peltriaux
|
||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||
Created on: 26.10.22
|
||||
|
||||
"""
|
||||
import zipfile
|
||||
from io import BytesIO
|
||||
|
||||
from django.core.mail import EmailMessage
|
||||
from django.utils import timezone
|
||||
from django.utils.datetime_safe import datetime
|
||||
|
||||
from analysis.utils.excel.excel import TempExcelFile
|
||||
from analysis.utils.report import TimespanReport
|
||||
from codelist.models import KonovaCode
|
||||
from codelist.settings import CODELIST_CONSERVATION_OFFICE_ID
|
||||
from konova.management.commands.setup import BaseKonovaCommand
|
||||
from konova.sub_settings.django_settings import DEFAULT_FROM_EMAIL, ADMINS
|
||||
|
||||
|
||||
class Command(BaseKonovaCommand):
|
||||
help = "Generates reports for conservation offices"
|
||||
|
||||
__from_key = "from"
|
||||
__to_key = "to"
|
||||
__for_key = "for"
|
||||
|
||||
from_date = None
|
||||
to_date = None
|
||||
offices = None
|
||||
|
||||
def add_arguments(self, parser):
|
||||
try:
|
||||
parser.add_argument(f"--{self.__from_key}", type=str, nargs="+")
|
||||
parser.add_argument(f"--{self.__to_key}", type=str, nargs="+")
|
||||
parser.add_argument(f"--{self.__for_key}", type=str, nargs="*")
|
||||
except ValueError as e:
|
||||
self._write_error(f"Argument error: {e}")
|
||||
exit(-1)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
self.__check_arguments(options)
|
||||
in_memory_zipped_reports = self.generate_reports_in_memory_zipped()
|
||||
self.send_mail_to_admins(in_memory_zipped_reports)
|
||||
|
||||
def generate_reports_in_memory_zipped(self):
|
||||
"""
|
||||
Generates in-memory reports and zips into in-memory zip file.
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
memory_zip = BytesIO()
|
||||
with zipfile.ZipFile(memory_zip, "w", compression=zipfile.ZIP_DEFLATED) as zf:
|
||||
for office in self.offices:
|
||||
self._write_warning(f" Process report for {office.long_name}...")
|
||||
report = TimespanReport(office.id, self.from_date, self.to_date)
|
||||
excel_file = TempExcelFile(report.excel_template_path, report.excel_map)
|
||||
zf.writestr(zinfo_or_arcname=f"{office.long_name}_{self.from_date.date()}_{self.to_date.date()}.xlsx", data=excel_file.stream)
|
||||
self._write_success(f"Reports generated for {self.offices.count()} offices and zipped.")
|
||||
return memory_zip.getvalue()
|
||||
|
||||
def __check_arguments(self, options):
|
||||
"""
|
||||
Checks given parameters for validity
|
||||
|
||||
Args:
|
||||
options ():
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
_from_value = options.get(self.__from_key, [None])[0]
|
||||
_to_value = options.get(self.__to_key, [None])[0]
|
||||
_for_value = options.get(self.__for_key, [])
|
||||
|
||||
# Check ISO dates
|
||||
try:
|
||||
_from_date = timezone.make_aware(datetime.fromisoformat(_from_value))
|
||||
_to_date = timezone.make_aware(datetime.fromisoformat(_to_value))
|
||||
except Exception as e:
|
||||
self._write_warning(f"One of the dates is not in ISO format (YYYY-MM-DD). {e}")
|
||||
exit(-1)
|
||||
|
||||
# Check conservation office IDs
|
||||
_filter = {
|
||||
"is_archived": False,
|
||||
"is_leaf": True,
|
||||
"code_lists__in": [CODELIST_CONSERVATION_OFFICE_ID],
|
||||
}
|
||||
offices = KonovaCode.objects.filter(**_filter)
|
||||
if _for_value is not None and len(_for_value) != 0:
|
||||
# Specifc offices requested
|
||||
offices = offices.filter(short_name__in=_for_value)
|
||||
all_requested_offices_exist = offices.count() == len(_for_value)
|
||||
if not all_requested_offices_exist:
|
||||
offices_short_name = set(offices.values_list("short_name", flat=True))
|
||||
missing_ids = []
|
||||
for val in _for_value:
|
||||
if val not in offices_short_name:
|
||||
missing_ids.append(val)
|
||||
self._write_warning(
|
||||
f"Unknown offices: {missing_ids}"
|
||||
)
|
||||
exit(-1)
|
||||
|
||||
self.offices = offices
|
||||
self.from_date = _from_date
|
||||
self.to_date = _to_date
|
||||
|
||||
def send_mail_to_admins(self, attachment):
|
||||
office_names = [office.long_name for office in self.offices]
|
||||
admin_mails = [admin[1] for admin in ADMINS]
|
||||
|
||||
date_from_date = self.from_date.date()
|
||||
date_to_date = self.to_date.date()
|
||||
|
||||
mail = EmailMessage(
|
||||
subject="Konova reports generated",
|
||||
body=f"Zipped reports attached from {date_from_date} to {date_to_date} for {office_names}.",
|
||||
from_email=DEFAULT_FROM_EMAIL,
|
||||
to=admin_mails,
|
||||
attachments=[
|
||||
(
|
||||
f"reports_{date_from_date}_{date_to_date}.zip",
|
||||
attachment,
|
||||
"application/zip"
|
||||
)
|
||||
]
|
||||
)
|
||||
success = mail.send()
|
||||
if success == 1:
|
||||
self._write_success(f"Mails with zip as attachment sent to {admin_mails}.")
|
||||
else:
|
||||
self._write_error(f"Something went wrong during mail sending. Returned sending code was '{success}'")
|
||||
@@ -8,13 +8,11 @@ Created on: 15.11.21
|
||||
import json
|
||||
|
||||
from django.contrib.gis.db.models import MultiPolygonField
|
||||
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.tasks import celery_check_for_geometry_conflicts
|
||||
from konova.utils.wfs.spatial import ParcelWFSFetcher
|
||||
|
||||
|
||||
@@ -29,7 +27,18 @@ class Geometry(BaseResource):
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
super().save(*args, **kwargs)
|
||||
celery_check_for_geometry_conflicts.delay(self.id)
|
||||
|
||||
@property
|
||||
def geom_small_buffered(self):
|
||||
"""
|
||||
Returns a smaller buffered version of the geometry.
|
||||
Can be used to shrink the geometry used for intersection purposes to avoid intersection detection on
|
||||
neighbouring geometries.
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
return self.geom.buffer(-0.001)
|
||||
|
||||
def check_for_conflicts(self):
|
||||
""" Checks for new geometry overlaps
|
||||
@@ -44,9 +53,8 @@ class Geometry(BaseResource):
|
||||
return None
|
||||
|
||||
self.recheck_existing_conflicts()
|
||||
|
||||
overlapping_geoms = Geometry.objects.filter(
|
||||
geom__intersects=self.geom,
|
||||
geom__intersects=self.geom_small_buffered,
|
||||
).exclude(
|
||||
id=self.id
|
||||
).distinct()
|
||||
@@ -68,14 +76,14 @@ class Geometry(BaseResource):
|
||||
"""
|
||||
all_conflicts_as_conflicting = self.conflicts_geometries.all()
|
||||
still_conflicting_conflicts = all_conflicts_as_conflicting.filter(
|
||||
affected_geometry__geom__intersects=self.geom
|
||||
affected_geometry__geom__intersects=self.geom_small_buffered
|
||||
)
|
||||
resolved_conflicts = all_conflicts_as_conflicting.exclude(id__in=still_conflicting_conflicts)
|
||||
resolved_conflicts.delete()
|
||||
|
||||
all_conflicted_by_conflicts = self.conflicted_by_geometries.all()
|
||||
still_conflicting_conflicts = all_conflicted_by_conflicts.filter(
|
||||
conflicting_geometry__geom__intersects=self.geom
|
||||
conflicting_geometry__geom__intersects=self.geom_small_buffered
|
||||
)
|
||||
resolved_conflicts = all_conflicted_by_conflicts.exclude(id__in=still_conflicting_conflicts)
|
||||
resolved_conflicts.delete()
|
||||
@@ -108,6 +116,11 @@ class Geometry(BaseResource):
|
||||
|
||||
"""
|
||||
from konova.models import Parcel, District, ParcelIntersection, Municipal, ParcelGroup
|
||||
|
||||
if self.geom.empty:
|
||||
# Nothing to do
|
||||
return
|
||||
|
||||
parcel_fetcher = ParcelWFSFetcher(
|
||||
geometry_id=self.id,
|
||||
)
|
||||
@@ -213,16 +226,22 @@ class Geometry(BaseResource):
|
||||
geojson (dict): The FeatureCollection json (as dict)
|
||||
"""
|
||||
geom = self.geom
|
||||
geom.transform(ct=srid)
|
||||
if geom.srid != srid:
|
||||
geom.transform(ct=srid)
|
||||
|
||||
polygons = []
|
||||
for coords in geom.coords:
|
||||
p = Polygon(coords[0], srid=geom.srid)
|
||||
polygons.append(p)
|
||||
geojson = {
|
||||
"type": "FeatureCollection",
|
||||
"crs": {
|
||||
"type": "name",
|
||||
"properties": {
|
||||
"name": f"urn:ogc:def:crs:EPSG::{geom.srid}"
|
||||
}
|
||||
},
|
||||
"features": [
|
||||
json.loads(x.geojson) for x in polygons
|
||||
{
|
||||
"type": "Feature",
|
||||
"geometry": json.loads(geom.json),
|
||||
}
|
||||
]
|
||||
}
|
||||
return geojson
|
||||
|
||||
@@ -434,8 +434,16 @@ class CheckableObjectMixin(models.Model):
|
||||
|
||||
class ShareableObjectMixin(models.Model):
|
||||
# Users having access on this object
|
||||
users = models.ManyToManyField("user.User", help_text="Users having access (data shared with)")
|
||||
teams = models.ManyToManyField("user.Team", help_text="Teams having access (data shared with)")
|
||||
users = models.ManyToManyField(
|
||||
"user.User",
|
||||
help_text="Users having access (data shared with)",
|
||||
blank=True
|
||||
)
|
||||
teams = models.ManyToManyField(
|
||||
"user.Team",
|
||||
help_text="Teams having access (data shared with)",
|
||||
blank=True
|
||||
)
|
||||
access_token = models.CharField(
|
||||
max_length=255,
|
||||
null=True,
|
||||
@@ -698,18 +706,16 @@ class GeoReferencedMixin(models.Model):
|
||||
return request
|
||||
|
||||
instance_objs = []
|
||||
add_message = False
|
||||
conflicts = self.geometry.conflicts_geometries.all()
|
||||
|
||||
for conflict in conflicts:
|
||||
instance_objs += conflict.affected_geometry.get_data_objects()
|
||||
add_message = True
|
||||
|
||||
conflicts = self.geometry.conflicted_by_geometries.all()
|
||||
for conflict in conflicts:
|
||||
instance_objs += conflict.conflicting_geometry.get_data_objects()
|
||||
add_message = True
|
||||
|
||||
add_message = len(instance_objs) > 0
|
||||
if add_message:
|
||||
instance_identifiers = [x.identifier for x in instance_objs]
|
||||
instance_identifiers = ", ".join(instance_identifiers)
|
||||
|
||||
@@ -10,5 +10,5 @@ BASE_TITLE_SHORT = "KSP"
|
||||
BASE_TITLE = "KSP - Kompensationsverzeichnis Service Portal"
|
||||
BASE_FRONTEND_TITLE = "Kompensationsverzeichnis Service Portal"
|
||||
TAB_TITLE_IDENTIFIER = "tab_title"
|
||||
HELP_LINK = "https://dienste.naturschutz.rlp.de/doku/doku.php?id=ksp:start"
|
||||
HELP_LINK = "https://dienste.naturschutz.rlp.de/doku/doku.php?id=ksp2:start"
|
||||
IMPRESSUM_LINK = "https://naturschutz.rlp.de/index.php?q=impressum"
|
||||
|
||||
@@ -15,7 +15,7 @@ DEFAULT_SRID_RLP = 25832
|
||||
|
||||
# Needed to redirect to LANIS
|
||||
## Values to be inserted are [zoom_level, x_coord, y_coord]
|
||||
LANIS_LINK_TEMPLATE = "https://geodaten.naturschutz.rlp.de/kartendienste_naturschutz/index.php?lang=de&zl={}&x={}&y={}&bl=tk_rlp_tms_grau&bo=1&lo=0.8,0.8,0.8,0.6,0.8,0.8,0.8,0.8,0.8&layers=eiv_f,eiv_l,eiv_p,kom_f,kom_l,kom_p,oek_f,ema_f,mae&service=kartendienste_naturschutz"
|
||||
LANIS_LINK_TEMPLATE = "https://geodaten.naturschutz.rlp.de/kartendienste_naturschutz/index.php?lang=de&zl={}&x={}&y={}&bl=tk_rlp_tms_grau&bo=1&lo=0.8,0.8,0.8,0.6,0.8,0.8,0.8,0.8,0.8&layers=eiv_recorded,eiv_unrecorded,kom_recorded,kom_unrecorded,oek_recorded,oek_unrecorded,ema_recorded,ema_unrecorded,mae&service=kartendienste_naturschutz"
|
||||
## This look up table (LUT) defines different zoom levels on the size of the calculate area of a geometry.
|
||||
LANIS_ZOOM_LUT = {
|
||||
1000000000: 6,
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
{% load i18n l10n %}
|
||||
{% load i18n l10n fontawesome_5 %}
|
||||
<div class="table-container w-100 scroll-300">
|
||||
{% if parcels|length == 0 %}
|
||||
<article class="alert alert-info">
|
||||
{% fa5_icon 'search-location' %}
|
||||
{% trans 'Parcels can not be calculated, since no geometry is given.' %}
|
||||
</article>
|
||||
{% else %}
|
||||
|
||||
@@ -2,17 +2,32 @@
|
||||
<div class="col-sm-12">
|
||||
<div class="card">
|
||||
<div class="card-header rlp-r">
|
||||
<h5>
|
||||
{% fa5_icon 'search-location' %}
|
||||
{% trans 'Spatial reference' %}
|
||||
</h5>
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<h5>
|
||||
{% fa5_icon 'search-location' %}
|
||||
{% trans 'Spatial reference' %}
|
||||
</h5>
|
||||
</div>
|
||||
<div class="col-6 text-right">
|
||||
<h5>
|
||||
{{ obj.geometry.geom.area|floatformat:2 }} m²
|
||||
</h5>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% if geom_form.instance.geometry %}
|
||||
<div hx-trigger="load, every 5s" hx-get="{% url 'geometry-parcels' geom_form.instance.geometry.id %}">
|
||||
<div class="row justify-content-center">
|
||||
<span class="spinner-border rlp-r-inv" role="status"></span>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="alert alert-danger">
|
||||
{% translate 'No geometry entry found on database. Please contact an admin!' %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -115,6 +115,19 @@ class BaseTestCase(TestCase):
|
||||
"""
|
||||
return f"{prefix}{generate_random_string(3, True)}"
|
||||
|
||||
def create_dummy_document(self, DocumentModel, instance):
|
||||
""" Creates a document db entry which can be used for tests
|
||||
|
||||
"""
|
||||
doc = DocumentModel.objects.create(
|
||||
title="TEST_doc",
|
||||
comment="",
|
||||
file=None,
|
||||
date_of_creation="1970-01-01",
|
||||
instance=instance,
|
||||
)
|
||||
return doc
|
||||
|
||||
def create_dummy_intervention(self):
|
||||
""" Creates an intervention which can be used for tests
|
||||
|
||||
@@ -187,6 +200,7 @@ class BaseTestCase(TestCase):
|
||||
eco_account = EcoAccount.objects.create(
|
||||
identifier="TEST",
|
||||
title="Test_title",
|
||||
deductable_surface=500,
|
||||
legal=lega_data,
|
||||
responsible=responsible_data,
|
||||
created=action,
|
||||
|
||||
@@ -92,11 +92,14 @@ class Mailer:
|
||||
msg
|
||||
)
|
||||
|
||||
def send_mail_shared_access_given_team(self, obj_identifier, obj_title, team):
|
||||
def send_mail_shared_access_given_team(self, obj_identifier, obj_title, team, users_to_notify):
|
||||
""" Send a mail if a team just got access to the object
|
||||
|
||||
Args:
|
||||
obj_identifier (str): The object identifier
|
||||
obj_title (str): Title of the main object
|
||||
team (Team): Team to be notified
|
||||
users_to_notify (QueryDict): Contains the team users which should be notified
|
||||
|
||||
Returns:
|
||||
|
||||
@@ -108,18 +111,21 @@ class Mailer:
|
||||
"EMAIL_REPLY_TO": EMAIL_REPLY_TO,
|
||||
}
|
||||
msg = render_to_string("email/sharing/shared_access_given_team.html", context)
|
||||
user_mail_address = team.users.values_list("email", flat=True)
|
||||
user_mail_address = users_to_notify.values_list("email", flat=True)
|
||||
self.send(
|
||||
user_mail_address,
|
||||
_("{} - Shared access given").format(obj_identifier),
|
||||
msg
|
||||
)
|
||||
|
||||
def send_mail_shared_access_removed_team(self, obj_identifier, obj_title, team):
|
||||
def send_mail_shared_access_removed_team(self, obj_identifier, obj_title, team, users_to_notify):
|
||||
""" Send a mail if a team just lost access to the object
|
||||
|
||||
Args:
|
||||
obj_identifier (str): The object identifier
|
||||
obj_title (str): Title of the main object
|
||||
team (Team): Team to be notified
|
||||
users_to_notify (QueryDict): Contains the team users which should be notified
|
||||
|
||||
Returns:
|
||||
|
||||
@@ -131,18 +137,21 @@ class Mailer:
|
||||
"EMAIL_REPLY_TO": EMAIL_REPLY_TO,
|
||||
}
|
||||
msg = render_to_string("email/sharing/shared_access_removed_team.html", context)
|
||||
user_mail_address = team.users.values_list("email", flat=True)
|
||||
user_mail_address = users_to_notify.values_list("email", flat=True)
|
||||
self.send(
|
||||
user_mail_address,
|
||||
_("{} - Shared access removed").format(obj_identifier),
|
||||
msg
|
||||
)
|
||||
|
||||
def send_mail_shared_data_unrecorded_team(self, obj_identifier, obj_title, team):
|
||||
def send_mail_shared_data_unrecorded_team(self, obj_identifier, obj_title, team, users_to_notify):
|
||||
""" Send a mail if data has just been unrecorded
|
||||
|
||||
Args:
|
||||
obj_identifier (str): The object identifier
|
||||
obj_title (str): Title of the main object
|
||||
team (Team): Team to be notified
|
||||
users_to_notify (QueryDict): Contains the team users which should be notified
|
||||
|
||||
Returns:
|
||||
|
||||
@@ -154,18 +163,21 @@ class Mailer:
|
||||
"EMAIL_REPLY_TO": EMAIL_REPLY_TO,
|
||||
}
|
||||
msg = render_to_string("email/recording/shared_data_unrecorded_team.html", context)
|
||||
user_mail_address = team.users.values_list("email", flat=True)
|
||||
user_mail_address = users_to_notify.values_list("email", flat=True)
|
||||
self.send(
|
||||
user_mail_address,
|
||||
_("{} - Shared data unrecorded").format(obj_identifier),
|
||||
msg
|
||||
)
|
||||
|
||||
def send_mail_shared_data_recorded_team(self, obj_identifier, obj_title, team):
|
||||
def send_mail_shared_data_recorded_team(self, obj_identifier, obj_title, team, users_to_notify):
|
||||
""" Send a mail if data has just been recorded
|
||||
|
||||
Args:
|
||||
obj_identifier (str): The object identifier
|
||||
obj_title (str): Title of the main object
|
||||
team (Team): Team to be notified
|
||||
users_to_notify (QueryDict): Contains the team users which should be notified
|
||||
|
||||
Returns:
|
||||
|
||||
@@ -177,18 +189,21 @@ class Mailer:
|
||||
"EMAIL_REPLY_TO": EMAIL_REPLY_TO,
|
||||
}
|
||||
msg = render_to_string("email/recording/shared_data_recorded_team.html", context)
|
||||
user_mail_address = team.users.values_list("email", flat=True)
|
||||
user_mail_address = users_to_notify.values_list("email", flat=True)
|
||||
self.send(
|
||||
user_mail_address,
|
||||
_("{} - Shared data recorded").format(obj_identifier),
|
||||
msg
|
||||
)
|
||||
|
||||
def send_mail_shared_data_checked_team(self, obj_identifier, obj_title, team):
|
||||
def send_mail_shared_data_checked_team(self, obj_identifier, obj_title, team, users_to_notify):
|
||||
""" Send a mail if data has just been checked
|
||||
|
||||
Args:
|
||||
obj_identifier (str): The object identifier
|
||||
obj_title (str): Title of the main object
|
||||
team (Team): Team to be notified
|
||||
users_to_notify (QueryDict): Contains the team users which should be notified
|
||||
|
||||
Returns:
|
||||
|
||||
@@ -200,14 +215,14 @@ class Mailer:
|
||||
"EMAIL_REPLY_TO": EMAIL_REPLY_TO,
|
||||
}
|
||||
msg = render_to_string("email/checking/shared_data_checked_team.html", context)
|
||||
user_mail_address = team.users.values_list("email", flat=True)
|
||||
user_mail_address = users_to_notify.values_list("email", flat=True)
|
||||
self.send(
|
||||
user_mail_address,
|
||||
_("{} - Shared data checked").format(obj_identifier),
|
||||
msg
|
||||
)
|
||||
|
||||
def send_mail_deduction_changed_team(self, obj_identifier, obj_title, team, data_changes):
|
||||
def send_mail_deduction_changed_team(self, obj_identifier, obj_title, team, data_changes, users_to_notify):
|
||||
""" Send a mail if deduction has been changed
|
||||
|
||||
Args:
|
||||
@@ -215,7 +230,7 @@ class Mailer:
|
||||
obj_title (str): Title of the main object
|
||||
team (Team): Team to be notified
|
||||
data_changes (dict): Contains the old|new changes of the deduction changes
|
||||
|
||||
users_to_notify (QueryDict): Contains the team users which should be notified
|
||||
Returns:
|
||||
|
||||
"""
|
||||
@@ -227,14 +242,14 @@ class Mailer:
|
||||
"data_changes": data_changes,
|
||||
}
|
||||
msg = render_to_string("email/other/deduction_changed_team.html", context)
|
||||
user_mail_address = team.users.values_list("email", flat=True)
|
||||
user_mail_address = users_to_notify.values_list("email", flat=True)
|
||||
self.send(
|
||||
user_mail_address,
|
||||
_("{} - Deduction changed").format(obj_identifier),
|
||||
msg
|
||||
)
|
||||
|
||||
def send_mail_shared_data_deleted_team(self, obj_identifier, obj_title, team):
|
||||
def send_mail_shared_data_deleted_team(self, obj_identifier, obj_title, team, users_to_notify):
|
||||
""" Send a mail if data has just been deleted
|
||||
|
||||
Args:
|
||||
@@ -250,7 +265,7 @@ class Mailer:
|
||||
"EMAIL_REPLY_TO": EMAIL_REPLY_TO,
|
||||
}
|
||||
msg = render_to_string("email/deleting/shared_data_deleted_team.html", context)
|
||||
user_mail_address = team.users.values_list("email", flat=True)
|
||||
user_mail_address = users_to_notify.values_list("email", flat=True)
|
||||
self.send(
|
||||
user_mail_address,
|
||||
_("{} - Shared data deleted").format(obj_identifier),
|
||||
|
||||
@@ -7,11 +7,14 @@ Created on: 25.11.20
|
||||
"""
|
||||
|
||||
from django.core.paginator import PageNotAnInteger, EmptyPage
|
||||
from django.db.models import F
|
||||
from django.http import HttpRequest
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils.html import format_html
|
||||
import django_tables2 as tables
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from konova.models import BaseObject
|
||||
from konova.models import BaseObject, GeoReferencedMixin, ShareableObjectMixin
|
||||
from konova.settings import PAGE_SIZE_DEFAULT, PAGE_PARAM, RESULTS_PER_PAGE_PARAM, PAGE_SIZE_OPTIONS
|
||||
|
||||
|
||||
@@ -173,4 +176,56 @@ class TableRenderMixin:
|
||||
max_length = 75
|
||||
if len(value) > max_length:
|
||||
value = f"{value[:max_length]}..."
|
||||
return value
|
||||
return value
|
||||
|
||||
def render_d(self, value, record: GeoReferencedMixin):
|
||||
""" Renders the parcel district column
|
||||
|
||||
Args:
|
||||
value (str): The intervention geometry
|
||||
record (GeoReferencedMixin): The record
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
parcels = value.get_underlying_parcels().values_list(
|
||||
"parcel_group__name",
|
||||
flat=True
|
||||
).distinct()
|
||||
html = render_to_string(
|
||||
"table/gmrkng_col.html",
|
||||
{
|
||||
"entries": parcels,
|
||||
"geometry": record.geometry
|
||||
}
|
||||
)
|
||||
return html
|
||||
|
||||
def render_e(self, value, record: ShareableObjectMixin):
|
||||
""" Renders the editable column
|
||||
|
||||
Args:
|
||||
value (str): The identifier value
|
||||
record (ShareableObjectMixin): The record
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
html = ""
|
||||
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",
|
||||
)
|
||||
return format_html(html)
|
||||
|
||||
|
||||
class TableOrderMixin:
|
||||
"""
|
||||
Holds different order_by methods for general purposes
|
||||
|
||||
"""
|
||||
def order_lm(self, queryset, is_asc):
|
||||
queryset = queryset.order_by(F('modified__timestamp').desc(nulls_last=True))
|
||||
return (queryset, is_asc)
|
||||
|
||||
@@ -11,10 +11,11 @@ from json import JSONDecodeError
|
||||
from time import sleep
|
||||
|
||||
import requests
|
||||
from django.contrib.gis.db.models.functions import AsGML, Transform, MakeValid
|
||||
from django.contrib.gis.db.models.functions import AsGML, MakeValid
|
||||
from django.db.models import Func, F
|
||||
from requests.auth import HTTPDigestAuth
|
||||
|
||||
from konova.settings import DEFAULT_SRID_RLP, PARCEL_WFS_USER, PARCEL_WFS_PW, PROXIES
|
||||
from konova.settings import PARCEL_WFS_USER, PARCEL_WFS_PW, PROXIES
|
||||
|
||||
|
||||
class AbstractWFSFetcher:
|
||||
@@ -72,33 +73,34 @@ class ParcelWFSFetcher(AbstractWFSFetcher):
|
||||
self.geometry_property_name = geometry_property_name
|
||||
|
||||
def _create_spatial_filter(self,
|
||||
geometry_operation: str,
|
||||
filter_srid: str = None):
|
||||
geometry_operation: str):
|
||||
""" Creates a xml spatial filter according to the WFS filter specification
|
||||
|
||||
The geometry needs to be shrinked by a very small factor (-0.01) before a GML can be created for intersection
|
||||
checking. Otherwise perfect parcel outline placement on top of a neighbouring parcel would result in an
|
||||
intersection hit, despite the fact they do not truly intersect just because their vertices match.
|
||||
|
||||
Args:
|
||||
geometry_operation (str): One of the WFS supported spatial filter operations (according to capabilities)
|
||||
filter_srid (str): Used to transform the geometry into the spatial reference system identified by this srid
|
||||
|
||||
Returns:
|
||||
spatial_filter (str): The spatial filter element
|
||||
"""
|
||||
from konova.models import Geometry
|
||||
if filter_srid is None:
|
||||
filter_srid = DEFAULT_SRID_RLP
|
||||
geom_gml = Geometry.objects.filter(
|
||||
|
||||
geom = Geometry.objects.filter(
|
||||
id=self.geometry_id
|
||||
).annotate(
|
||||
transformed=Transform(srid=filter_srid, expression="geom")
|
||||
smaller=Func(F('geom'), -0.001, function="ST_Buffer") # same as geometry.geom_small_buffered but for QuerySet
|
||||
).annotate(
|
||||
gml=AsGML(MakeValid('transformed'))
|
||||
).first().gml
|
||||
gml=AsGML(MakeValid('smaller'))
|
||||
).first()
|
||||
geom_gml = geom.gml
|
||||
spatial_filter = f"<Filter><{geometry_operation}><PropertyName>{self.geometry_property_name}</PropertyName>{geom_gml}</{geometry_operation}></Filter>"
|
||||
return spatial_filter
|
||||
|
||||
def _create_post_data(self,
|
||||
geometry_operation: str,
|
||||
filter_srid: str = None,
|
||||
typenames: str = None,
|
||||
start_index: int = 0,
|
||||
):
|
||||
@@ -106,15 +108,13 @@ class ParcelWFSFetcher(AbstractWFSFetcher):
|
||||
|
||||
Args:
|
||||
geometry_operation (str): One of the WFS supported spatial filter operations (according to capabilities)
|
||||
filter_srid (str): Used to transform the geometry into the spatial reference system identified by this srid
|
||||
|
||||
Returns:
|
||||
_filter (str): A proper xml WFS filter
|
||||
"""
|
||||
start_index = str(start_index)
|
||||
spatial_filter = self._create_spatial_filter(
|
||||
geometry_operation,
|
||||
filter_srid
|
||||
geometry_operation
|
||||
)
|
||||
_filter = f'<wfs:GetFeature service="WFS" version="{self.version}" xmlns:wfs="http://www.opengis.net/wfs/2.0" xmlns:fes="http://www.opengis.net/fes/2.0" xmlns:myns="http://www.someserver.com/myns" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.opengis.net/wfs/2.0 http://schemas.opengis.net/wfs/2.0.0/wfs.xsd" count="{self.count}" startindex="{start_index}" outputFormat="application/json; subtype=geojson"><wfs:Query typeNames="{typenames}">{spatial_filter}</wfs:Query></wfs:GetFeature>'
|
||||
return _filter
|
||||
@@ -144,7 +144,6 @@ class ParcelWFSFetcher(AbstractWFSFetcher):
|
||||
while start_index is not None:
|
||||
post_body = self._create_post_data(
|
||||
spatial_operator,
|
||||
filter_srid,
|
||||
typenames,
|
||||
start_index
|
||||
)
|
||||
|
||||
@@ -32,16 +32,16 @@ def get_geom_parcels(request: HttpRequest, id: str):
|
||||
parcels = geom.get_underlying_parcels()
|
||||
geos_geom = geom.geom
|
||||
|
||||
parcels_are_currently_calculated = geos_geom is not None and geos_geom.area > 0 and len(parcels) == 0
|
||||
geometry_exists = not geos_geom.empty
|
||||
parcels_are_currently_calculated = geometry_exists and geos_geom.area > 0 and len(parcels) == 0
|
||||
parcels_available = len(parcels) > 0
|
||||
no_geometry_given = geos_geom is None
|
||||
|
||||
if parcels_are_currently_calculated:
|
||||
# Parcels are being calculated right now. Change the status code, so polling stays active for fetching
|
||||
# resutls after the calculation
|
||||
status_code = 200
|
||||
|
||||
if parcels_available or no_geometry_given:
|
||||
if parcels_available or not geometry_exists:
|
||||
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)
|
||||
|
||||
@@ -6,6 +6,7 @@ Created on: 19.08.22
|
||||
|
||||
"""
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.db.models import Q
|
||||
from django.http import HttpRequest
|
||||
from django.shortcuts import render
|
||||
from django.utils import timezone
|
||||
@@ -50,22 +51,22 @@ def home_view(request: HttpRequest):
|
||||
)
|
||||
# Then fetch only user related ones
|
||||
user_interventions = interventions.filter(
|
||||
users__in=[user]
|
||||
)
|
||||
Q(users__in=[user]) | Q(teams__in=user.shared_teams)
|
||||
).distinct()
|
||||
|
||||
# Repeat for other objects
|
||||
comps = Compensation.objects.filter(
|
||||
deleted=None,
|
||||
)
|
||||
user_comps = comps.filter(
|
||||
intervention__users__in=[user]
|
||||
)
|
||||
Q(intervention__users__in=[user]) | Q(intervention__teams__in=user.shared_teams)
|
||||
).distinct()
|
||||
eco_accs = EcoAccount.objects.filter(
|
||||
deleted=None,
|
||||
)
|
||||
user_ecco_accs = eco_accs.filter(
|
||||
users__in=[user]
|
||||
)
|
||||
Q(users__in=[user]) | Q(teams__in=user.shared_teams)
|
||||
).distinct()
|
||||
|
||||
additional_context = {
|
||||
"msgs": msgs,
|
||||
|
||||
Binary file not shown.
@@ -43,7 +43,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2022-08-25 10:57+0200\n"
|
||||
"POT-Creation-Date: 2022-11-16 13:36+0100\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@@ -204,7 +204,7 @@ msgstr "Geprüft"
|
||||
#: 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
|
||||
#: ema/tables.py:41 ema/templates/ema/detail/view.html:35
|
||||
#: intervention/tables.py:44
|
||||
#: intervention/templates/intervention/detail/view.html:87
|
||||
#: user/models/user_action.py:22
|
||||
@@ -357,7 +357,7 @@ msgid "Show only unrecorded"
|
||||
msgstr "Nur unverzeichnete anzeigen"
|
||||
|
||||
#: compensation/forms/compensation.py:30 compensation/tables/compensation.py:23
|
||||
#: compensation/tables/eco_account.py:23 ema/tables.py:29
|
||||
#: compensation/tables/eco_account.py:23 ema/tables.py:26
|
||||
#: intervention/forms/intervention.py:29 intervention/tables.py:23
|
||||
#: intervention/templates/intervention/detail/includes/compensations.html:30
|
||||
msgid "Identifier"
|
||||
@@ -376,12 +376,12 @@ msgstr "Automatisch generiert"
|
||||
#: compensation/templates/compensation/detail/eco_account/view.html:32
|
||||
#: compensation/templates/compensation/report/compensation/report.html:12
|
||||
#: compensation/templates/compensation/report/eco_account/report.html:12
|
||||
#: ema/tables.py:34 ema/templates/ema/detail/includes/documents.html:28
|
||||
#: ema/tables.py:31 ema/templates/ema/detail/includes/documents.html:28
|
||||
#: ema/templates/ema/detail/view.html:31
|
||||
#: ema/templates/ema/report/report.html:12
|
||||
#: intervention/forms/intervention.py:41 intervention/tables.py:28
|
||||
#: intervention/templates/intervention/detail/includes/compensations.html:33
|
||||
#: intervention/templates/intervention/detail/includes/documents.html:28
|
||||
#: intervention/templates/intervention/detail/includes/documents.html:33
|
||||
#: intervention/templates/intervention/detail/view.html:31
|
||||
#: intervention/templates/intervention/report/report.html:12
|
||||
#: konova/forms/modals/document_form.py:24
|
||||
@@ -411,7 +411,7 @@ msgstr "Kompensation XY; Flur ABC"
|
||||
#: ema/templates/ema/detail/includes/documents.html:34
|
||||
#: intervention/forms/intervention.py:199
|
||||
#: intervention/forms/modals/revocation.py:45
|
||||
#: intervention/templates/intervention/detail/includes/documents.html:34
|
||||
#: intervention/templates/intervention/detail/includes/documents.html:39
|
||||
#: intervention/templates/intervention/detail/includes/payments.html:34
|
||||
#: intervention/templates/intervention/detail/includes/revocation.html:38
|
||||
#: konova/forms/modals/document_form.py:59
|
||||
@@ -446,37 +446,43 @@ msgstr "Neue Kompensation"
|
||||
msgid "Edit compensation"
|
||||
msgstr "Bearbeite Kompensation"
|
||||
|
||||
#: compensation/forms/eco_account.py:29 compensation/utils/quality.py:95
|
||||
#: compensation/forms/eco_account.py:30 compensation/utils/quality.py:95
|
||||
msgid "Available Surface"
|
||||
msgstr "Verfügbare Fläche"
|
||||
|
||||
#: compensation/forms/eco_account.py:32
|
||||
#: compensation/forms/eco_account.py:33
|
||||
msgid "The amount that can be used for deductions"
|
||||
msgstr "Die für Abbuchungen zur Verfügung stehende Menge"
|
||||
|
||||
#: compensation/forms/eco_account.py:41
|
||||
#: compensation/forms/eco_account.py:42
|
||||
#: compensation/templates/compensation/detail/eco_account/view.html:67
|
||||
#: compensation/utils/quality.py:83
|
||||
msgid "Agreement date"
|
||||
msgstr "Vereinbarungsdatum"
|
||||
|
||||
#: compensation/forms/eco_account.py:43
|
||||
#: compensation/forms/eco_account.py:44
|
||||
msgid "When did the parties agree on this?"
|
||||
msgstr "Wann wurde dieses Ökokonto offiziell vereinbart?"
|
||||
|
||||
#: compensation/forms/eco_account.py:69
|
||||
#: compensation/forms/eco_account.py:70
|
||||
#: compensation/views/eco_account/eco_account.py:94
|
||||
msgid "New Eco-Account"
|
||||
msgstr "Neues Ökokonto"
|
||||
|
||||
#: compensation/forms/eco_account.py:78
|
||||
#: compensation/forms/eco_account.py:79
|
||||
msgid "Eco-Account XY; Location ABC"
|
||||
msgstr "Ökokonto XY; Flur ABC"
|
||||
|
||||
#: compensation/forms/eco_account.py:140
|
||||
#: compensation/forms/eco_account.py:143
|
||||
msgid "Edit Eco-Account"
|
||||
msgstr "Ökokonto bearbeiten"
|
||||
|
||||
#: compensation/forms/eco_account.py:228
|
||||
msgid "The account can not be removed, since there are still deductions."
|
||||
msgstr ""
|
||||
"Das Ökokonto kann nicht entfernt werden, da hierzu noch Abbuchungen "
|
||||
"vorliegen."
|
||||
|
||||
#: compensation/forms/mixins.py:37
|
||||
#: compensation/templates/compensation/detail/eco_account/view.html:63
|
||||
#: compensation/templates/compensation/report/eco_account/report.html:20
|
||||
@@ -734,14 +740,14 @@ msgstr ""
|
||||
msgid "Pieces"
|
||||
msgstr "Stück"
|
||||
|
||||
#: compensation/models/eco_account.py:56
|
||||
#: compensation/models/eco_account.py:62
|
||||
msgid ""
|
||||
"Deductable surface can not be larger than existing surfaces in after states"
|
||||
msgstr ""
|
||||
"Die abbuchbare Fläche darf die Gesamtfläche der Zielzustände nicht "
|
||||
"überschreiten"
|
||||
|
||||
#: compensation/models/eco_account.py:63
|
||||
#: compensation/models/eco_account.py:69
|
||||
msgid ""
|
||||
"Deductable surface can not be smaller than the sum of already existing "
|
||||
"deductions. Please contact the responsible users for the deductions!"
|
||||
@@ -750,54 +756,42 @@ msgstr ""
|
||||
"wollen. Kontaktieren Sie die für die Abbuchungen verantwortlichen Nutzer!"
|
||||
|
||||
#: compensation/tables/compensation.py:33 compensation/tables/eco_account.py:33
|
||||
#: ema/tables.py:39 intervention/tables.py:33
|
||||
#: ema/tables.py:36 intervention/tables.py:33
|
||||
#: konova/filters/mixins/geo_reference.py:42
|
||||
msgid "Parcel gmrkng"
|
||||
msgstr "Gemarkung"
|
||||
|
||||
#: compensation/tables/compensation.py:50 compensation/tables/eco_account.py:54
|
||||
#: ema/tables.py:50 intervention/tables.py:50
|
||||
#: ema/tables.py:47 intervention/tables.py:50
|
||||
msgid "Editable"
|
||||
msgstr "Freigegeben"
|
||||
|
||||
#: compensation/tables/compensation.py:56 compensation/tables/eco_account.py:60
|
||||
#: ema/tables.py:56 intervention/tables.py:56
|
||||
#: ema/tables.py:53 intervention/tables.py:56
|
||||
msgid "Last edit"
|
||||
msgstr "Zuletzt bearbeitet"
|
||||
|
||||
#: compensation/tables/compensation.py:87 compensation/tables/eco_account.py:92
|
||||
#: ema/tables.py:89 intervention/tables.py:87
|
||||
#: ema/tables.py:86 intervention/tables.py:87
|
||||
msgid "Open {}"
|
||||
msgstr "Öffne {}"
|
||||
|
||||
#: compensation/tables/compensation.py:163
|
||||
#: compensation/tables/compensation.py:141
|
||||
#: 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:130 ema/templates/ema/detail/view.html:38
|
||||
#: intervention/tables.py:161
|
||||
#: ema/tables.py:105 ema/templates/ema/detail/view.html:38
|
||||
#: intervention/tables.py:139
|
||||
#: intervention/templates/intervention/detail/view.html:90
|
||||
msgid "Not recorded yet"
|
||||
msgstr "Noch nicht verzeichnet"
|
||||
|
||||
#: compensation/tables/compensation.py:166
|
||||
#: compensation/tables/eco_account.py:150 ema/tables.py:133
|
||||
#: intervention/tables.py:164
|
||||
#: compensation/tables/compensation.py:144
|
||||
#: compensation/tables/eco_account.py:131 ema/tables.py:108
|
||||
#: intervention/tables.py:142
|
||||
msgid "Recorded on {} by {}"
|
||||
msgstr "Am {} von {} verzeichnet worden"
|
||||
|
||||
#: compensation/tables/compensation.py:186
|
||||
#: compensation/tables/eco_account.py:172 ema/tables.py:154
|
||||
#: intervention/tables.py:185
|
||||
msgid "Full access granted"
|
||||
msgstr "Für Sie freigegeben - Datensatz kann bearbeitet werden"
|
||||
|
||||
#: compensation/tables/compensation.py:186
|
||||
#: compensation/tables/eco_account.py:172 ema/tables.py:154
|
||||
#: intervention/tables.py:185
|
||||
msgid "Access not granted"
|
||||
msgstr "Nicht freigegeben - Datensatz nur lesbar"
|
||||
|
||||
#: compensation/tables/eco_account.py:38
|
||||
#: compensation/templates/compensation/detail/eco_account/view.html:36
|
||||
#: konova/templates/konova/widgets/progressbar.html:3
|
||||
@@ -808,7 +802,7 @@ msgstr "Verfügbar"
|
||||
msgid "Eco Accounts"
|
||||
msgstr "Ökokonten"
|
||||
|
||||
#: compensation/tables/eco_account.py:147
|
||||
#: compensation/tables/eco_account.py:128
|
||||
msgid "Not recorded yet. Can not be used for deductions, yet."
|
||||
msgstr ""
|
||||
"Noch nicht verzeichnet. Kann noch nicht für Abbuchungen genutzt werden."
|
||||
@@ -857,7 +851,7 @@ msgstr "Menge"
|
||||
#: ema/templates/ema/detail/includes/states-before.html:40
|
||||
#: intervention/templates/intervention/detail/includes/compensations.html:38
|
||||
#: intervention/templates/intervention/detail/includes/deductions.html:39
|
||||
#: intervention/templates/intervention/detail/includes/documents.html:39
|
||||
#: intervention/templates/intervention/detail/includes/documents.html:44
|
||||
#: 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:33
|
||||
@@ -876,6 +870,34 @@ msgstr "Keine Zusatzmerkmale"
|
||||
msgid "Remove action"
|
||||
msgstr "Maßnahme entfernen"
|
||||
|
||||
#: compensation/templates/compensation/detail/compensation/includes/actions.html:79
|
||||
#: compensation/templates/compensation/detail/compensation/includes/states-after.html:73
|
||||
#: compensation/templates/compensation/detail/compensation/includes/states-before.html:73
|
||||
#: compensation/templates/compensation/detail/eco_account/includes/actions.html:78
|
||||
#: compensation/templates/compensation/detail/eco_account/includes/states-after.html:73
|
||||
#: compensation/templates/compensation/detail/eco_account/includes/states-before.html:73
|
||||
#: compensation/templates/compensation/detail/eco_account/view.html:58
|
||||
#: compensation/templates/compensation/detail/eco_account/view.html:62
|
||||
#: compensation/templates/compensation/detail/eco_account/view.html:66
|
||||
#: compensation/templates/compensation/detail/eco_account/view.html:70
|
||||
#: ema/templates/ema/detail/includes/actions.html:76
|
||||
#: ema/templates/ema/detail/includes/states-after.html:71
|
||||
#: ema/templates/ema/detail/includes/states-before.html:71
|
||||
#: ema/templates/ema/detail/view.html:48 ema/templates/ema/detail/view.html:52
|
||||
#: ema/templates/ema/detail/view.html:56
|
||||
#: intervention/templates/intervention/detail/view.html:30
|
||||
#: intervention/templates/intervention/detail/view.html:34
|
||||
#: intervention/templates/intervention/detail/view.html:38
|
||||
#: intervention/templates/intervention/detail/view.html:47
|
||||
#: intervention/templates/intervention/detail/view.html:51
|
||||
#: 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:100
|
||||
#: intervention/templates/intervention/detail/view.html:104
|
||||
msgid "Missing"
|
||||
msgstr "Fehlend"
|
||||
|
||||
#: compensation/templates/compensation/detail/compensation/includes/controls.html:5
|
||||
#: compensation/templates/compensation/detail/eco_account/includes/controls.html:5
|
||||
#: ema/templates/ema/detail/includes/controls.html:5
|
||||
@@ -963,7 +985,7 @@ msgstr "Neues Dokument hinzufügen"
|
||||
#: compensation/templates/compensation/detail/compensation/includes/documents.html:31
|
||||
#: 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
|
||||
#: intervention/templates/intervention/detail/includes/documents.html:36
|
||||
#: konova/forms/modals/document_form.py:34
|
||||
msgid "Created on"
|
||||
msgstr "Erstellt"
|
||||
@@ -971,7 +993,7 @@ msgstr "Erstellt"
|
||||
#: compensation/templates/compensation/detail/compensation/includes/documents.html:63
|
||||
#: 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
|
||||
#: intervention/templates/intervention/detail/includes/documents.html:70
|
||||
#: konova/forms/modals/document_form.py:139
|
||||
msgid "Edit document"
|
||||
msgstr "Dokument bearbeiten"
|
||||
@@ -979,7 +1001,7 @@ msgstr "Dokument bearbeiten"
|
||||
#: compensation/templates/compensation/detail/compensation/includes/documents.html:66
|
||||
#: compensation/templates/compensation/detail/eco_account/includes/documents.html:64
|
||||
#: ema/templates/ema/detail/includes/documents.html:64
|
||||
#: intervention/templates/intervention/detail/includes/documents.html:68
|
||||
#: intervention/templates/intervention/detail/includes/documents.html:73
|
||||
msgid "Remove document"
|
||||
msgstr "Dokument löschen"
|
||||
|
||||
@@ -1186,7 +1208,7 @@ msgid "Recorded on"
|
||||
msgstr "Verzeichnet am"
|
||||
|
||||
#: compensation/templates/compensation/detail/eco_account/includes/deductions.html:65
|
||||
#: intervention/forms/modals/deduction.py:173
|
||||
#: intervention/forms/modals/deduction.py:177
|
||||
#: intervention/templates/intervention/detail/includes/deductions.html:60
|
||||
msgid "Edit Deduction"
|
||||
msgstr "Abbuchung bearbeiten"
|
||||
@@ -1200,25 +1222,6 @@ msgstr "Abbuchung entfernen"
|
||||
msgid "No surface deductable"
|
||||
msgstr "Keine Flächenmenge für Abbuchungen eingegeben. Bitte bearbeiten."
|
||||
|
||||
#: compensation/templates/compensation/detail/eco_account/view.html:58
|
||||
#: compensation/templates/compensation/detail/eco_account/view.html:62
|
||||
#: compensation/templates/compensation/detail/eco_account/view.html:66
|
||||
#: compensation/templates/compensation/detail/eco_account/view.html:70
|
||||
#: ema/templates/ema/detail/view.html:48 ema/templates/ema/detail/view.html:52
|
||||
#: ema/templates/ema/detail/view.html:56
|
||||
#: intervention/templates/intervention/detail/view.html:30
|
||||
#: intervention/templates/intervention/detail/view.html:34
|
||||
#: intervention/templates/intervention/detail/view.html:38
|
||||
#: intervention/templates/intervention/detail/view.html:47
|
||||
#: intervention/templates/intervention/detail/view.html:51
|
||||
#: 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:100
|
||||
#: intervention/templates/intervention/detail/view.html:104
|
||||
msgid "Missing"
|
||||
msgstr "fehlt"
|
||||
|
||||
#: compensation/templates/compensation/detail/eco_account/view.html:71
|
||||
#: ema/templates/ema/detail/view.html:57
|
||||
msgid "Action handler"
|
||||
@@ -1274,8 +1277,8 @@ msgid "Compensation {} edited"
|
||||
msgstr "Kompensation {} bearbeitet"
|
||||
|
||||
#: compensation/views/compensation/compensation.py:185
|
||||
#: compensation/views/eco_account/eco_account.py:159 ema/views/ema.py:211
|
||||
#: intervention/views/intervention.py:225
|
||||
#: compensation/views/eco_account/eco_account.py:159 ema/views/ema.py:210
|
||||
#: intervention/views/intervention.py:228
|
||||
msgid "Edit {}"
|
||||
msgstr "Bearbeite {}"
|
||||
|
||||
@@ -1297,11 +1300,11 @@ msgstr "Ökokonto {} hinzugefügt"
|
||||
msgid "Eco-Account {} edited"
|
||||
msgstr "Ökokonto {} bearbeitet"
|
||||
|
||||
#: compensation/views/eco_account/eco_account.py:263
|
||||
#: compensation/views/eco_account/eco_account.py:265
|
||||
msgid "Eco-account removed"
|
||||
msgstr "Ökokonto entfernt"
|
||||
|
||||
#: ema/forms.py:42 ema/views/ema.py:94
|
||||
#: ema/forms.py:42 ema/views/ema.py:93
|
||||
msgid "New EMA"
|
||||
msgstr "Neue EMA hinzufügen"
|
||||
|
||||
@@ -1309,11 +1312,11 @@ msgstr "Neue EMA hinzufügen"
|
||||
msgid "Edit EMA"
|
||||
msgstr "Bearbeite EMA"
|
||||
|
||||
#: ema/tables.py:65 templates/navbars/navbar.html:43
|
||||
#: ema/tables.py:62 templates/navbars/navbar.html:43
|
||||
msgid "Payment funded compensations"
|
||||
msgstr "Ersatzzahlungsmaßnahmen (EMA)"
|
||||
|
||||
#: ema/tables.py:66
|
||||
#: ema/tables.py:63
|
||||
msgid "EMA explanation"
|
||||
msgstr ""
|
||||
"EMA sind Kompensationen, die durch Ersatzzahlungen finanziert wurden. "
|
||||
@@ -1321,7 +1324,7 @@ msgstr ""
|
||||
"Maßnahmen aus Ersatzzahlungen, die nach 2015 rechtskräftig wurden, werden "
|
||||
"durch die Stiftung Natur und Umwelt verwaltet."
|
||||
|
||||
#: ema/tables.py:89 templates/navbars/navbar.html:43
|
||||
#: ema/tables.py:86 templates/navbars/navbar.html:43
|
||||
msgid "EMA"
|
||||
msgstr ""
|
||||
|
||||
@@ -1329,15 +1332,15 @@ msgstr ""
|
||||
msgid "Payment funded compensation"
|
||||
msgstr "Ersatzzahlungsmaßnahme"
|
||||
|
||||
#: ema/views/ema.py:51
|
||||
#: ema/views/ema.py:50
|
||||
msgid "EMAs - Overview"
|
||||
msgstr "EMAs - Übersicht"
|
||||
|
||||
#: ema/views/ema.py:84
|
||||
#: ema/views/ema.py:83
|
||||
msgid "EMA {} added"
|
||||
msgstr "EMA {} hinzugefügt"
|
||||
|
||||
#: ema/views/ema.py:201
|
||||
#: ema/views/ema.py:200
|
||||
msgid "EMA {} edited"
|
||||
msgstr "EMA {} bearbeitet"
|
||||
|
||||
@@ -1551,6 +1554,13 @@ msgstr "Ökokonto gelöscht! Abbuchung ungültig!"
|
||||
msgid "Eco-account not recorded! Deduction invalid!"
|
||||
msgstr "Ökokonto nicht verzeichnet! Abbuchung ungültig!"
|
||||
|
||||
#: intervention/templates/intervention/detail/includes/documents.html:25
|
||||
msgid ""
|
||||
"You entered a payment. Please upload the legal document which defines the "
|
||||
"payment`s amount."
|
||||
msgstr ""
|
||||
"Sie haben Ersatzzahlungen angegeben. Laden Sie bitte den Zahlungsbescheid als Dokument hoch."
|
||||
|
||||
#: intervention/templates/intervention/detail/includes/payments.html:8
|
||||
#: intervention/templates/intervention/report/report.html:69
|
||||
msgid "Payments"
|
||||
@@ -1635,23 +1645,23 @@ msgstr "Eingriffe - Übersicht"
|
||||
msgid "Intervention {} added"
|
||||
msgstr "Eingriff {} hinzugefügt"
|
||||
|
||||
#: intervention/views/intervention.py:213
|
||||
#: intervention/views/intervention.py:216
|
||||
msgid "Intervention {} edited"
|
||||
msgstr "Eingriff {} bearbeitet"
|
||||
|
||||
#: intervention/views/intervention.py:249
|
||||
#: intervention/views/intervention.py:253
|
||||
msgid "{} removed"
|
||||
msgstr "{} entfernt"
|
||||
|
||||
#: konova/decorators.py:33
|
||||
#: konova/decorators.py:30
|
||||
msgid "You need to be staff to perform this action!"
|
||||
msgstr "Hierfür müssen Sie Mitarbeiter sein!"
|
||||
|
||||
#: konova/decorators.py:48
|
||||
#: konova/decorators.py:45
|
||||
msgid "You need to be administrator to perform this action!"
|
||||
msgstr "Hierfür müssen Sie Administrator sein!"
|
||||
|
||||
#: konova/decorators.py:66
|
||||
#: konova/decorators.py:63
|
||||
msgid ""
|
||||
"+++ Attention: You are not part of any group. You won't be able to create, "
|
||||
"edit or do anything. Please contact an administrator. +++"
|
||||
@@ -1673,7 +1683,7 @@ msgid "Search for file number"
|
||||
msgstr "Nach Aktenzeichen suchen"
|
||||
|
||||
#: konova/filters/mixins/geo_reference.py:29
|
||||
#: konova/templates/konova/includes/parcels/parcel_table_frame.html:18
|
||||
#: konova/templates/konova/includes/parcels/parcel_table_frame.html:19
|
||||
msgid "District"
|
||||
msgstr "Kreis"
|
||||
|
||||
@@ -1686,7 +1696,7 @@ msgid "Search for parcel gmrkng"
|
||||
msgstr "Nach Gemarkung suchen"
|
||||
|
||||
#: konova/filters/mixins/geo_reference.py:55
|
||||
#: konova/templates/konova/includes/parcels/parcel_table_frame.html:39
|
||||
#: konova/templates/konova/includes/parcels/parcel_table_frame.html:40
|
||||
msgid "Parcel"
|
||||
msgstr "Flur"
|
||||
|
||||
@@ -1695,7 +1705,7 @@ msgid "Search for parcel"
|
||||
msgstr "Nach Flur suchen"
|
||||
|
||||
#: konova/filters/mixins/geo_reference.py:68
|
||||
#: konova/templates/konova/includes/parcels/parcel_table_frame.html:40
|
||||
#: konova/templates/konova/includes/parcels/parcel_table_frame.html:41
|
||||
msgid "Parcel counter"
|
||||
msgstr "Flurstückzähler"
|
||||
|
||||
@@ -1704,7 +1714,7 @@ msgid "Search for parcel counter"
|
||||
msgstr "Nach Flurstückzähler suchen"
|
||||
|
||||
#: konova/filters/mixins/geo_reference.py:82
|
||||
#: konova/templates/konova/includes/parcels/parcel_table_frame.html:41
|
||||
#: konova/templates/konova/includes/parcels/parcel_table_frame.html:42
|
||||
msgid "Parcel number"
|
||||
msgstr "Flurstücknenner"
|
||||
|
||||
@@ -1860,37 +1870,37 @@ msgstr ""
|
||||
msgid "English"
|
||||
msgstr ""
|
||||
|
||||
#: konova/templates/konova/includes/parcels/parcel_table_frame.html:5
|
||||
#: konova/templates/konova/includes/parcels/parcel_table_frame.html:6
|
||||
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/parcels/parcel_table_frame.html:11
|
||||
#: konova/templates/konova/includes/parcels/parcel_table_frame.html:12
|
||||
msgid "Parcels found"
|
||||
msgstr "Flurstücke"
|
||||
|
||||
#: konova/templates/konova/includes/parcels/parcel_table_frame.html:16
|
||||
#: konova/templates/konova/includes/parcels/parcel_table_frame.html:17
|
||||
msgid "Municipal"
|
||||
msgstr "Gemeinde"
|
||||
|
||||
#: konova/templates/konova/includes/parcels/parcel_table_frame.html:17
|
||||
#: konova/templates/konova/includes/parcels/parcel_table_frame.html:18
|
||||
msgid "Municipal key"
|
||||
msgstr "Gemeindeschlüssel"
|
||||
|
||||
#: konova/templates/konova/includes/parcels/parcel_table_frame.html:19
|
||||
#: konova/templates/konova/includes/parcels/parcel_table_frame.html:20
|
||||
msgid "District key"
|
||||
msgstr "Kreisschlüssel"
|
||||
|
||||
#: konova/templates/konova/includes/parcels/parcel_table_frame.html:37
|
||||
#: konova/templates/konova/includes/parcels/parcel_table_frame.html:38
|
||||
msgid "Parcel group"
|
||||
msgstr "Gemarkung"
|
||||
|
||||
#: konova/templates/konova/includes/parcels/parcel_table_frame.html:38
|
||||
#: konova/templates/konova/includes/parcels/parcel_table_frame.html:39
|
||||
msgid "Parcel group key"
|
||||
msgstr "Gemarkungsschlüssel"
|
||||
|
||||
#: konova/templates/konova/includes/parcels/parcels.html:7
|
||||
#: konova/templates/konova/includes/parcels/parcels.html:9
|
||||
msgid "Spatial reference"
|
||||
msgstr "Raumreferenz"
|
||||
|
||||
@@ -1938,39 +1948,39 @@ msgstr "In Zwischenablage kopiert"
|
||||
msgid "Search"
|
||||
msgstr "Suchen"
|
||||
|
||||
#: konova/utils/mailer.py:68 konova/utils/mailer.py:137
|
||||
#: konova/utils/mailer.py:68 konova/utils/mailer.py:143
|
||||
msgid "{} - Shared access removed"
|
||||
msgstr "{} - Zugriff entzogen"
|
||||
|
||||
#: konova/utils/mailer.py:91 konova/utils/mailer.py:114
|
||||
#: konova/utils/mailer.py:91 konova/utils/mailer.py:117
|
||||
msgid "{} - Shared access given"
|
||||
msgstr "{} - Zugriff freigegeben"
|
||||
|
||||
#: konova/utils/mailer.py:160 konova/utils/mailer.py:302
|
||||
#: konova/utils/mailer.py:169 konova/utils/mailer.py:317
|
||||
msgid "{} - Shared data unrecorded"
|
||||
msgstr "{} - Freigegebene Daten entzeichnet"
|
||||
|
||||
#: konova/utils/mailer.py:183 konova/utils/mailer.py:279
|
||||
#: konova/utils/mailer.py:195 konova/utils/mailer.py:294
|
||||
msgid "{} - Shared data recorded"
|
||||
msgstr "{} - Freigegebene Daten verzeichnet"
|
||||
|
||||
#: konova/utils/mailer.py:206 konova/utils/mailer.py:348
|
||||
#: konova/utils/mailer.py:221 konova/utils/mailer.py:363
|
||||
msgid "{} - Shared data checked"
|
||||
msgstr "{} - Freigegebene Daten geprüft"
|
||||
|
||||
#: konova/utils/mailer.py:233 konova/utils/mailer.py:376
|
||||
#: konova/utils/mailer.py:248 konova/utils/mailer.py:391
|
||||
msgid "{} - Deduction changed"
|
||||
msgstr "{} - Abbuchung geändert"
|
||||
|
||||
#: konova/utils/mailer.py:256 konova/utils/mailer.py:325
|
||||
#: konova/utils/mailer.py:271 konova/utils/mailer.py:340
|
||||
msgid "{} - Shared data deleted"
|
||||
msgstr "{} - Freigegebene Daten gelöscht"
|
||||
|
||||
#: konova/utils/mailer.py:397 templates/email/api/verify_token.html:4
|
||||
#: konova/utils/mailer.py:412 templates/email/api/verify_token.html:4
|
||||
msgid "Request for new API token"
|
||||
msgstr "Anfrage für neuen API Token"
|
||||
|
||||
#: konova/utils/mailer.py:420
|
||||
#: konova/utils/mailer.py:435
|
||||
msgid "Resubmission - {}"
|
||||
msgstr "Wiedervorlage - {}"
|
||||
|
||||
@@ -2210,9 +2220,17 @@ msgstr "{} wurde erfolgreich vom Nutzer {} geprüft! {}"
|
||||
|
||||
#: konova/utils/quality.py:32
|
||||
msgid "missing"
|
||||
msgstr "fehlt"
|
||||
msgstr "fehlend"
|
||||
|
||||
#: konova/views/home.py:78 templates/navbars/navbar.html:16
|
||||
#: konova/utils/tables.py:218
|
||||
msgid "Full access granted"
|
||||
msgstr "Für Sie freigegeben - Datensatz kann bearbeitet werden"
|
||||
|
||||
#: konova/utils/tables.py:218
|
||||
msgid "Access not granted"
|
||||
msgstr "Nicht freigegeben - Datensatz nur lesbar"
|
||||
|
||||
#: konova/views/home.py:79 templates/navbars/navbar.html:16
|
||||
msgid "Home"
|
||||
msgstr "Home"
|
||||
|
||||
@@ -2289,8 +2307,8 @@ msgid "Server Error"
|
||||
msgstr ""
|
||||
|
||||
#: templates/500.html:10
|
||||
msgid "Something happened. We are working on it!"
|
||||
msgstr "Irgendetwas ist passiert. Wir arbeiten daran!"
|
||||
msgid "Something happened. Admins have been informed. We are working on it!"
|
||||
msgstr "Irgendetwas ist passiert. Die Administratoren wurden informiert. Wir arbeiten daran!"
|
||||
|
||||
#: templates/email/api/verify_token.html:7
|
||||
msgid "Hello support"
|
||||
@@ -2657,13 +2675,15 @@ msgstr ""
|
||||
msgid "User"
|
||||
msgstr "Nutzer"
|
||||
|
||||
#: templates/map/geom_form.html:9
|
||||
#: templates/map/geom_form.html:11 templates/table/gmrkng_col.html:4
|
||||
msgid "No geometry added, yet."
|
||||
msgstr "Keine Geometrie vorhanden"
|
||||
|
||||
#: templates/modal/modal_session_timed_out.html:3
|
||||
msgid "Your session has timed out. Please reload the page to login."
|
||||
msgstr "Ihre Sitzung ist aufgrund von Inaktivität abgelaufen. Laden Sie die Seite erneut, um sich wieder einzuloggen."
|
||||
msgstr ""
|
||||
"Ihre Sitzung ist aufgrund von Inaktivität abgelaufen. Laden Sie die Seite "
|
||||
"erneut, um sich wieder einzuloggen."
|
||||
|
||||
#: templates/navbars/navbar.html:4
|
||||
msgid "Kompensationsverzeichnis Service Portal"
|
||||
@@ -2710,7 +2730,7 @@ msgstr ""
|
||||
"vorbei. \n"
|
||||
" "
|
||||
|
||||
#: templates/table/gmrkng_col.html:6
|
||||
#: templates/table/gmrkng_col.html:12
|
||||
msgid ""
|
||||
"If the geometry is not empty, the parcels are currently recalculated. Please "
|
||||
"refresh this page in a few moments."
|
||||
@@ -2882,35 +2902,27 @@ msgstr ""
|
||||
" "
|
||||
|
||||
#: user/templates/user/index.html:42
|
||||
msgid "Change default configuration for your KSP map"
|
||||
msgstr "Karteneinstellungen ändern"
|
||||
|
||||
#: user/templates/user/index.html:45
|
||||
msgid "Map settings"
|
||||
msgstr "Karte"
|
||||
|
||||
#: user/templates/user/index.html:50
|
||||
msgid "Change notification configurations"
|
||||
msgstr "Benachrichtigungseinstellungen ändern"
|
||||
|
||||
#: user/templates/user/index.html:53
|
||||
#: user/templates/user/index.html:45
|
||||
msgid "Notification settings"
|
||||
msgstr "Benachrichtigungen"
|
||||
|
||||
#: user/templates/user/index.html:58
|
||||
#: user/templates/user/index.html:50
|
||||
msgid "Manage teams"
|
||||
msgstr ""
|
||||
|
||||
#: user/templates/user/index.html:61 user/templates/user/team/index.html:19
|
||||
#: user/views.py:169
|
||||
#: user/templates/user/index.html:53 user/templates/user/team/index.html:19
|
||||
#: user/views.py:171
|
||||
msgid "Teams"
|
||||
msgstr ""
|
||||
|
||||
#: user/templates/user/index.html:66
|
||||
#: 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:69
|
||||
#: user/templates/user/index.html:61
|
||||
msgid "API"
|
||||
msgstr ""
|
||||
|
||||
@@ -2974,23 +2986,23 @@ msgstr "Neuer Token generiert. Administratoren sind informiert."
|
||||
msgid "User API token"
|
||||
msgstr "API Nutzer Token"
|
||||
|
||||
#: user/views.py:180
|
||||
#: user/views.py:183
|
||||
msgid "New team added"
|
||||
msgstr "Neues Team hinzugefügt"
|
||||
|
||||
#: user/views.py:194
|
||||
#: user/views.py:198
|
||||
msgid "Team edited"
|
||||
msgstr "Team bearbeitet"
|
||||
|
||||
#: user/views.py:208
|
||||
#: user/views.py:213
|
||||
msgid "Team removed"
|
||||
msgstr "Team gelöscht"
|
||||
|
||||
#: user/views.py:222
|
||||
#: user/views.py:228
|
||||
msgid "You are not a member of this team"
|
||||
msgstr "Sie sind kein Mitglied dieses Teams"
|
||||
|
||||
#: user/views.py:229
|
||||
#: user/views.py:235
|
||||
msgid "Left Team"
|
||||
msgstr "Team verlassen"
|
||||
|
||||
@@ -4493,6 +4505,12 @@ msgstr ""
|
||||
msgid "Unable to connect to qpid with SASL mechanism %s"
|
||||
msgstr ""
|
||||
|
||||
#~ msgid "Change default configuration for your KSP map"
|
||||
#~ msgstr "Karteneinstellungen ändern"
|
||||
|
||||
#~ msgid "Map settings"
|
||||
#~ msgstr "Karte"
|
||||
|
||||
#~ msgid "There are errors on this intervention:"
|
||||
#~ msgstr "Es liegen Fehler in diesem Eingriff vor:"
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<h1 class="display-4">{% trans 'Server Error' %}</h1>
|
||||
<hr>
|
||||
<p class="lead">
|
||||
{% trans 'Something happened. We are working on it!' %}
|
||||
{% trans 'Something happened. Admins have been informed. We are working on it!' %}
|
||||
</p>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -36,11 +36,13 @@
|
||||
{ "folder": 2, "type": "WMS", "title": "Verbandsgemeinden", "url": "http://geo5.service24.rlp.de/wms/verwaltungsgrenzen_rp.fcgi?", "name": "Verbandsgemeinden" },
|
||||
{ "folder": 2, "type": "WMS", "title": "Gemeinden", "url": "http://geo5.service24.rlp.de/wms/verwaltungsgrenzen_rp.fcgi?", "name": "Gemeinden" },
|
||||
|
||||
{ "folder": 0, "type": "WMS", "title": "Webatlas farbig", "attribution": "LVermGeo", "url": "https://maps.service24.rlp.de/gisserver/services/RP/RP_WebAtlasRP/MapServer/WmsServer?", "name": "RP_WebAtlasRP", "active": true },
|
||||
{ "folder": 0, "type": "WMS", "title": "Webatlas grau", "attribution": "LVermGeo", "url": "https://maps.service24.rlp.de/gisserver/services/RP/RP_ETRS_Gt/MapServer/WmsServer?", "name": "0", "active": false },
|
||||
{ "folder": 15, "type": "WMS", "title": "farbig", "attribution": "LVermGeo", "url": "https://maps.service24.rlp.de/gisserver/services/RP/RP_WebAtlasRP/MapServer/WmsServer?", "name": "RP_WebAtlasRP", "active": true },
|
||||
{ "folder": 15, "type": "WMS", "title": "grau", "attribution": "LVermGeo", "url": "https://maps.service24.rlp.de/gisserver/services/RP/RP_ETRS_Gt/MapServer/WmsServer?", "name": "0", "active": false },
|
||||
{ "folder": 0, "type": "WMS", "title": "Luftbilder", "attribution": "LVermGeo", "url": "http://geo4.service24.rlp.de/wms/dop_basis.fcgi?", "name": "rp_dop", "active": false },
|
||||
{ "folder": 0, "type": "WMS", "title": "TopPlusOpen", "attribution": "BKG", "url": "https://sgx.geodatenzentrum.de/wms_topplus_open?", "name": "web", "active": false },
|
||||
{ "folder": 0, "type": "OSM", "title": "Open Street Map", "attribution": "OSM", "active": false }
|
||||
{ "folder": 14, "type": "WMS", "title": "farbig", "attribution": "BKG", "url": "https://sgx.geodatenzentrum.de/wms_basemapde?", "name": "de_basemapde_web_raster_farbe", "active": false },
|
||||
{ "folder": 14, "type": "WMS", "title": "grau", "attribution": "BKG", "url": "https://sgx.geodatenzentrum.de/wms_basemapde?", "name": "de_basemapde_web_raster_grau", "active": false },
|
||||
{ "folder": 13, "type": "WMS", "title": "farbig", "attribution": "LVermGeo", "url": "https://geo4.service24.rlp.de/wms/dtk5_rp.fcgi?", "name": "rp_dtk5", "active": false },
|
||||
{ "folder": 13, "type": "WMS", "title": "grau", "attribution": "LVermGeo", "url": "https://geo4.service24.rlp.de/wms/dtk5_rp.fcgi?", "name": "rp_dtk5_grau", "active": false }
|
||||
],
|
||||
|
||||
"folders":
|
||||
@@ -57,7 +59,10 @@
|
||||
{ "title": "MAE", "parent": 4 },
|
||||
{ "title": "Schutzgebiete", "parent": 3 },
|
||||
{ "title": "Nationalparke", "parent": 10 },
|
||||
{ "title": "Naturräume", "parent": 10 }
|
||||
{ "title": "Naturräume", "parent": 10 },
|
||||
{ "title": "Topographisch (DTK5)", "parent": 0 },
|
||||
{ "title": "BaseMap", "parent": 0 },
|
||||
{ "title": "Webatlas", "parent": 0 }
|
||||
],
|
||||
|
||||
"projections":
|
||||
|
||||
@@ -394,16 +394,12 @@ netgis.MapOpenLayers.prototype.clearAll = function()
|
||||
{
|
||||
for ( var i = 0; i < this.layers.length; i++ )
|
||||
{
|
||||
if(this.layers[i] === this.editLayer){
|
||||
continue;
|
||||
};
|
||||
this.map.removeLayer( this.layers[ i ] );
|
||||
}
|
||||
|
||||
this.layers = [this.editLayer];
|
||||
this.layers = [];
|
||||
|
||||
this.snapFeatures.clear();
|
||||
this.snapFeatures.push(this.editLayer);
|
||||
};
|
||||
|
||||
netgis.MapOpenLayers.prototype.onUpdateStyle = function( e )
|
||||
@@ -1116,10 +1112,15 @@ netgis.MapOpenLayers.prototype.updateEditOutput = function()
|
||||
{
|
||||
var features = this.editLayer.getSource().getFeatures();
|
||||
|
||||
// Output
|
||||
var proj = this.client.config.map.projection;
|
||||
var format = new ol.format.GeoJSON();
|
||||
//var output = format.writeFeatures( features );
|
||||
var output = format.writeFeaturesObject( features );
|
||||
var output = format.writeFeaturesObject( features, { dataProjection: proj, featureProjection: proj } );
|
||||
|
||||
output[ "crs" ] =
|
||||
{
|
||||
"type": "name",
|
||||
"properties": { "name": "urn:ogc:def:crs:" + proj.replace( ":", "::" ) }
|
||||
};
|
||||
|
||||
if ( ! this.editEventsSilent )
|
||||
this.client.invoke( netgis.Events.EDIT_FEATURES_CHANGE, output );
|
||||
@@ -1139,15 +1140,9 @@ netgis.MapOpenLayers.prototype.updateEditLayerItem = function()
|
||||
|
||||
netgis.MapOpenLayers.prototype.onEditFeaturesLoaded = function( e )
|
||||
{
|
||||
var json = e;
|
||||
var format = new ol.format.GeoJSON();
|
||||
var features = format.readFeatures( json );
|
||||
|
||||
this.editLayer.getSource().addFeatures( features );
|
||||
//this.snapFeatures.push( e.feature );
|
||||
|
||||
if ( features.length > 0 )
|
||||
this.view.fit( this.editLayer.getSource().getExtent(), { padding: [ 40, 40, 40, 40 ] } );
|
||||
var json = e;
|
||||
var self = this;
|
||||
window.setTimeout( function() { self.createLayerGeoJSON( "Import", json ); }, 10 );
|
||||
};
|
||||
|
||||
netgis.MapOpenLayers.prototype.onDragEnter = function( e )
|
||||
@@ -1254,7 +1249,6 @@ netgis.MapOpenLayers.prototype.onImportShapefile = function( e )
|
||||
|
||||
netgis.MapOpenLayers.prototype.createLayerGeoJSON = function( title, data )
|
||||
{
|
||||
//var format = new ol.format.GeoJSON( { dataProjection: "EPSG:4326", featureProjection: this.client.config.map.projection /*"EPSG:3857"*/ } );
|
||||
var format = new ol.format.GeoJSON();
|
||||
var projection = format.readProjection( data );
|
||||
var features = format.readFeatures( data, { featureProjection: this.client.config.map.projection } );
|
||||
@@ -1262,7 +1256,26 @@ netgis.MapOpenLayers.prototype.createLayerGeoJSON = function( title, data )
|
||||
//NOTE: proj4.defs[ "EPSG:4326" ]
|
||||
//NOTE: netgis.util.foreach( proj4.defs, function( k,v ) { console.info( "DEF:", k, v ); } )
|
||||
|
||||
//console.info( "Projection:", projection.getCode() );
|
||||
var projcode = projection.getCode();
|
||||
|
||||
switch ( projcode )
|
||||
{
|
||||
case "EPSG:3857":
|
||||
case "EPSG:4326":
|
||||
case this.client.config.map.projection:
|
||||
{
|
||||
// Projection OK
|
||||
//console.info( "Import Projection:", projcode );
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
// Projection Not Supported
|
||||
console.warn( "Unsupported Import Projection:", projcode );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this.addImportedFeatures( features );
|
||||
};
|
||||
@@ -1274,19 +1287,142 @@ netgis.MapOpenLayers.prototype.createLayerGML = function( title, data )
|
||||
|
||||
console.warn( "GML support is experimental!" );
|
||||
|
||||
var format = new ol.format.WFS( /*{ srsName: "EPSG:4326", featureType: "ogr:RLP_OG_utf8_epsg4326" }*/ );
|
||||
//var format = new ol.format.GML3( { srsName: "EPSG::25832", featureType: "Test", featureNS: "http://www.opengis.net/gml" } );
|
||||
//var format = new ol.format.GML( { featureNS: "ogr" } );
|
||||
//var format = new ol.format.WFS( /*{ srsName: "EPSG:4326", featureType: "ogr:RLP_OG_utf8_epsg4326" }*/ );
|
||||
//var format = new ol.format.GML( { featureNS: "ogr", featureType: "ogr:RLP_OG_utf8_epsg4326" } );
|
||||
//var format = new ol.format.WFS();
|
||||
//var format = new ol.format.WFS( { featureNS: "ogr", featureType: "RLP_OG_utf8_epsg4326" } );
|
||||
var projection = format.readProjection( data );
|
||||
//var projection = format.readProjection( data );
|
||||
//var features = format.readFeatures( data, { dataProjection: "EPSG:4326", featureProjection: "EPSG:3857" } );
|
||||
var features = format.readFeatures( data, { featureProjection: this.client.config.map.projection } );
|
||||
|
||||
//var features = format.readFeatures( data, { dataProjection: this.client.config.map.projection, featureProjection: this.client.config.map.projection } );
|
||||
|
||||
console.info( "GML:", projection, features );
|
||||
//console.info( "GML:", projection, features, features[ 0 ].getGeometry() );
|
||||
|
||||
var features = [];
|
||||
|
||||
var parser = new DOMParser();
|
||||
var xml = parser.parseFromString( data, "text/xml" );
|
||||
|
||||
// Features
|
||||
var featureMembers = xml.getElementsByTagName( "gml:featureMember" );
|
||||
|
||||
for ( var f = 0; f < featureMembers.length; f++ )
|
||||
{
|
||||
var props = {};
|
||||
|
||||
var node = featureMembers[ f ];
|
||||
var child = node.children[ 0 ];
|
||||
|
||||
// Attributes
|
||||
for ( var a = 0; a < child.attributes.length; a++ )
|
||||
{
|
||||
var attribute = child.attributes[ a ];
|
||||
props[ attribute.nodeName ] = attribute.nodeValue;
|
||||
}
|
||||
|
||||
for ( var c = 0; c < child.children.length; c++ )
|
||||
{
|
||||
var childNode = child.children[ c ];
|
||||
|
||||
if ( childNode.nodeName === "ogr:geometryProperty" ) continue;
|
||||
|
||||
var parts = childNode.nodeName.split( ":" );
|
||||
var k = parts[ parts.length - 1 ];
|
||||
var v = childNode.innerHTML;
|
||||
|
||||
props[ k ] = v;
|
||||
}
|
||||
|
||||
// Geometry
|
||||
var geomprop = child.getElementsByTagName( "ogr:geometryProperty" )[ 0 ];
|
||||
|
||||
//for ( var g = 0; g < geomprop.children.length; g++ )
|
||||
{
|
||||
var geom = geomprop.children[ 0 ];
|
||||
var proj = geom.getAttribute( "srsName" );
|
||||
|
||||
if ( proj && proj !== "EPSG:4326" && proj !== this.client.config.map.projection )
|
||||
console.warn( "Unsupported Import Projection:", proj );
|
||||
|
||||
switch ( geom.nodeName )
|
||||
{
|
||||
case "gml:Polygon":
|
||||
{
|
||||
props[ "geometry" ] = this.gmlParsePolygon( geom, proj );
|
||||
break;
|
||||
}
|
||||
|
||||
case "gml:MultiPolygon":
|
||||
{
|
||||
props[ "geometry" ] = this.gmlParseMultiPolygon( geom, proj );
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var feature = new ol.Feature( props );
|
||||
features.push( feature );
|
||||
}
|
||||
|
||||
this.addImportedFeatures( features );
|
||||
};
|
||||
|
||||
netgis.MapOpenLayers.prototype.gmlParsePolygon = function( node, proj )
|
||||
{
|
||||
var rings = [];
|
||||
|
||||
var linearRings = node.getElementsByTagName( "gml:LinearRing" );
|
||||
|
||||
for ( var r = 0; r < linearRings.length; r++ )
|
||||
{
|
||||
var ring = linearRings[ r ];
|
||||
var coords = ring.getElementsByTagName( "gml:coordinates" )[ 0 ].innerHTML;
|
||||
rings.push( this.gmlParseCoordinates( coords, proj ) );
|
||||
}
|
||||
|
||||
return new ol.geom.Polygon( rings );
|
||||
};
|
||||
|
||||
netgis.MapOpenLayers.prototype.gmlParseMultiPolygon = function( node, proj )
|
||||
{
|
||||
var polygons = [];
|
||||
|
||||
var polygonMembers = node.getElementsByTagName( "gml:polygonMember" );
|
||||
|
||||
for ( var p = 0; p < polygonMembers.length; p++ )
|
||||
{
|
||||
var polygonMember = polygonMembers[ p ];
|
||||
var polygonNode = polygonMember.getElementsByTagName( "gml:Polygon" )[ 0 ];
|
||||
polygons.push( this.gmlParsePolygon( polygonNode, proj ) );
|
||||
}
|
||||
|
||||
return new ol.geom.MultiPolygon( polygons );
|
||||
};
|
||||
|
||||
netgis.MapOpenLayers.prototype.gmlParseCoordinates = function( s, proj )
|
||||
{
|
||||
var coords = s.split( " " );
|
||||
|
||||
for ( var c = 0; c < coords.length; c++ )
|
||||
{
|
||||
// Split
|
||||
coords[ c ] = coords[ c ].split( "," );
|
||||
|
||||
// Parse
|
||||
for ( var xy = 0; xy < coords[ c ].length; xy++ )
|
||||
{
|
||||
coords[ c ][ xy ] = Number.parseFloat( coords[ c ][ xy ] );
|
||||
}
|
||||
|
||||
// Transform
|
||||
if ( proj ) coords[ c ] = ol.proj.transform( coords[ c ], proj, this.client.config.map.projection );
|
||||
}
|
||||
|
||||
return coords;
|
||||
};
|
||||
|
||||
netgis.MapOpenLayers.prototype.createLayerShapefile = function( title, shapeData )
|
||||
{
|
||||
var self = this;
|
||||
@@ -1312,22 +1448,20 @@ netgis.MapOpenLayers.prototype.addImportedFeatures = function( features )
|
||||
this.editLayer.getSource().addFeatures( features );
|
||||
this.editEventsSilent = false;
|
||||
this.updateEditOutput();
|
||||
|
||||
/*
|
||||
// Create New Layer
|
||||
var id = this.importLayerID;
|
||||
this.importLayerID += 1;
|
||||
|
||||
var layer = new ol.layer.Vector( { source: new ol.source.Vector( { features: features } ), zIndex: id } );
|
||||
this.map.addLayer( layer );
|
||||
this.layers[ id ] = layer;
|
||||
this.addSnapLayer( layer );
|
||||
|
||||
|
||||
// Zoom Imported Features
|
||||
if ( features.length > 0 )
|
||||
this.view.fit( layer.getSource().getExtent(), {} );
|
||||
|
||||
this.client.invoke( netgis.Events.LAYER_CREATED, { id: id, title: title, checked: true, folder: "import" } );
|
||||
*/
|
||||
{
|
||||
var extent = features[ 0 ].getGeometry().getExtent();
|
||||
|
||||
for ( var f = 1; f < features.length; f++ )
|
||||
{
|
||||
ol.extent.extend( extent, features[ f ].getGeometry().getExtent() );
|
||||
}
|
||||
|
||||
var padding = 40;
|
||||
this.view.fit( extent, { duration: 300, padding: [ padding, padding, padding, padding ] } );
|
||||
}
|
||||
};
|
||||
|
||||
netgis.MapOpenLayers.prototype.onImportWKT = function( e )
|
||||
|
||||
@@ -17,8 +17,6 @@
|
||||
line-height: 12mm;
|
||||
font-size: 0mm;
|
||||
white-space: nowrap;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
z-index: 1;
|
||||
|
||||
-webkit-transform: none;
|
||||
@@ -108,12 +106,8 @@
|
||||
|
||||
.netgis-toolbar .netgis-search-list
|
||||
{
|
||||
position: fixed;
|
||||
position: absolute;
|
||||
min-width: 68mm; /* 60mm + 4mm + 4mm ( input width + padding ) */
|
||||
/*osition: absolute;
|
||||
left: 0mm;
|
||||
min-width: 100%;*/
|
||||
/*height: 5.0em;*/
|
||||
padding: 0mm;
|
||||
margin: 0mm;
|
||||
margin-left: -4mm;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{% load i18n %}
|
||||
{% load i18n fontawesome_5 %}
|
||||
|
||||
{% comment %}
|
||||
Encapsules the rendering and initializing of a geometry view component, e.g. used in the detail views.
|
||||
@@ -6,7 +6,10 @@
|
||||
|
||||
{% if geom_form.empty %}
|
||||
<div class="w-100">
|
||||
<div class="alert alert-info">{% trans 'No geometry added, yet.' %}</div>
|
||||
<div class="alert alert-info">
|
||||
{% fa5_icon 'search-location' %}
|
||||
{% trans 'No geometry added, yet.' %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
{% load i18n fontawesome_5 %}
|
||||
|
||||
{% for entry in entries %}
|
||||
<span class="badge pill-badge rlp-r">{{entry}}</span>
|
||||
{% empty %}
|
||||
<span class="text-info" title="{% trans 'If the geometry is not empty, the parcels are currently recalculated. Please refresh this page in a few moments.' %}">
|
||||
{% fa5_icon 'hourglass-half' %}
|
||||
{% if geometry.geom is None or geometry.geom.empty %}
|
||||
<span class="text-info" title="{% translate 'No geometry added, yet.' %}">
|
||||
{% fa5_icon 'search-location' %}
|
||||
{% fa5_icon 'question' %}
|
||||
</span>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{% for entry in entries %}
|
||||
<span class="badge pill-badge rlp-r">{{entry}}</span>
|
||||
{% empty %}
|
||||
<span class="text-info" title="{% trans 'If the geometry is not empty, the parcels are currently recalculated. Please refresh this page in a few moments.' %}">
|
||||
{% fa5_icon 'hourglass-half' %}
|
||||
</span>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
@@ -17,15 +17,15 @@ class TeamAdminAutocomplete(Select2QuerySetView):
|
||||
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
|
||||
username__icontains=self.q
|
||||
)
|
||||
qs = qs.order_by(
|
||||
"username"
|
||||
|
||||
@@ -2,6 +2,7 @@ from django.db import models
|
||||
|
||||
from konova.models import UuidModel, DeletableObjectMixin
|
||||
from konova.utils.mailer import Mailer
|
||||
from user.enums import UserNotificationEnum
|
||||
from user.models import UserActionLogEntry
|
||||
|
||||
|
||||
@@ -41,7 +42,10 @@ class Team(UuidModel, DeletableObjectMixin):
|
||||
|
||||
"""
|
||||
mailer = Mailer()
|
||||
mailer.send_mail_shared_access_given_team(obj_identifier, obj_title, self)
|
||||
users_to_notify = self.users.filter(
|
||||
notifications__in=[UserNotificationEnum.NOTIFY_ON_SHARED_ACCESS_GAINED.value]
|
||||
)
|
||||
mailer.send_mail_shared_access_given_team(obj_identifier, obj_title, self, users_to_notify)
|
||||
|
||||
def send_mail_shared_access_removed(self, obj_identifier, obj_title):
|
||||
""" Sends a mail to the team members in case of removed shared access
|
||||
@@ -54,7 +58,10 @@ class Team(UuidModel, DeletableObjectMixin):
|
||||
|
||||
"""
|
||||
mailer = Mailer()
|
||||
mailer.send_mail_shared_access_removed_team(obj_identifier, obj_title, self)
|
||||
users_to_notify = self.users.filter(
|
||||
notifications__in=[UserNotificationEnum.NOTIFY_ON_SHARED_ACCESS_REMOVED.value]
|
||||
)
|
||||
mailer.send_mail_shared_access_removed_team(obj_identifier, obj_title, self, users_to_notify)
|
||||
|
||||
def send_mail_shared_data_unrecorded(self, obj_identifier, obj_title):
|
||||
""" Sends a mail to the team members in case of unrecorded data
|
||||
@@ -67,7 +74,10 @@ class Team(UuidModel, DeletableObjectMixin):
|
||||
|
||||
"""
|
||||
mailer = Mailer()
|
||||
mailer.send_mail_shared_data_unrecorded_team(obj_identifier, obj_title, self)
|
||||
users_to_notify = self.users.filter(
|
||||
notifications__in=[UserNotificationEnum.NOTIFY_ON_SHARED_DATA_RECORDED.value]
|
||||
)
|
||||
mailer.send_mail_shared_data_unrecorded_team(obj_identifier, obj_title, self, users_to_notify)
|
||||
|
||||
def send_mail_shared_data_recorded(self, obj_identifier, obj_title):
|
||||
""" Sends a mail to the team members in case of unrecorded data
|
||||
@@ -80,7 +90,10 @@ class Team(UuidModel, DeletableObjectMixin):
|
||||
|
||||
"""
|
||||
mailer = Mailer()
|
||||
mailer.send_mail_shared_data_recorded_team(obj_identifier, obj_title, self)
|
||||
users_to_notify = self.users.filter(
|
||||
notifications__in=[UserNotificationEnum.NOTIFY_ON_SHARED_DATA_RECORDED.value]
|
||||
)
|
||||
mailer.send_mail_shared_data_recorded_team(obj_identifier, obj_title, self, users_to_notify)
|
||||
|
||||
def send_mail_shared_data_checked(self, obj_identifier, obj_title):
|
||||
""" Sends a mail to the team members in case of checked data
|
||||
@@ -93,7 +106,10 @@ class Team(UuidModel, DeletableObjectMixin):
|
||||
|
||||
"""
|
||||
mailer = Mailer()
|
||||
mailer.send_mail_shared_data_checked_team(obj_identifier, obj_title, self)
|
||||
users_to_notify = self.users.filter(
|
||||
notifications__in=[UserNotificationEnum.NOTIFY_ON_SHARED_DATA_CHECKED.value]
|
||||
)
|
||||
mailer.send_mail_shared_data_checked_team(obj_identifier, obj_title, self, users_to_notify)
|
||||
|
||||
def send_mail_deduction_changed(self, obj_identifier, obj_title, data_changes):
|
||||
""" Sends a mail to the team members in case of changed deduction values
|
||||
@@ -107,7 +123,10 @@ class Team(UuidModel, DeletableObjectMixin):
|
||||
|
||||
"""
|
||||
mailer = Mailer()
|
||||
mailer.send_mail_deduction_changed_team(obj_identifier, obj_title, self, data_changes)
|
||||
users_to_notify = self.users.filter(
|
||||
notifications__in=[UserNotificationEnum.NOTIFY_ON_DEDUCTION_CHANGES.value]
|
||||
)
|
||||
mailer.send_mail_deduction_changed_team(obj_identifier, obj_title, self, data_changes, users_to_notify)
|
||||
|
||||
def send_mail_shared_data_deleted(self, obj_identifier, obj_title):
|
||||
""" Sends a mail to the team members in case of deleted data
|
||||
@@ -120,7 +139,10 @@ class Team(UuidModel, DeletableObjectMixin):
|
||||
|
||||
"""
|
||||
mailer = Mailer()
|
||||
mailer.send_mail_shared_data_deleted_team(obj_identifier, obj_title, self)
|
||||
users_to_notify = self.users.filter(
|
||||
notifications__in=[UserNotificationEnum.NOTIFY_ON_SHARED_DATA_DELETED.value]
|
||||
)
|
||||
mailer.send_mail_shared_data_deleted_team(obj_identifier, obj_title, self, users_to_notify)
|
||||
|
||||
def remove_user(self, user):
|
||||
""" Removes a user from the team
|
||||
|
||||
@@ -38,14 +38,6 @@
|
||||
</article>
|
||||
<hr>
|
||||
<div class="col-sm">
|
||||
<div class="row mb-2">
|
||||
<a href="{% url 'user:index' %}" title="{% trans 'Change default configuration for your KSP map' %}">
|
||||
<button class="btn btn-default">
|
||||
{% fa5_icon 'layer-group' %}
|
||||
<span>{% trans 'Map settings' %}</span>
|
||||
</button>
|
||||
</a>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<a href="{% url 'user:notifications' %}" title="{% trans 'Change notification configurations' %}">
|
||||
<button class="btn btn-default">
|
||||
|
||||
Reference in New Issue
Block a user