Merge pull request '197_Resubmission' (#198) from 197_Resubmission into master

Reviewed-on: SGD-Nord/konova#198
pull/199/head v0.6
mpeltriaux 2 years ago
commit 180ee293ca

@ -20,7 +20,7 @@ from compensation.models import CompensationDocument, EcoAccountDocument
from intervention.inputs import CompensationActionTreeCheckboxSelectMultiple, \
CompensationStateTreeRadioSelect
from konova.contexts import BaseContext
from konova.forms import BaseModalForm, NewDocumentModalForm, RemoveModalForm
from konova.forms.modals import BaseModalForm, NewDocumentModalForm, RemoveModalForm
from konova.models import DeadlineType
from konova.utils.message_templates import FORM_INVALID, ADDED_COMPENSATION_STATE, \
ADDED_COMPENSATION_ACTION, PAYMENT_EDITED, COMPENSATION_STATE_EDITED, COMPENSATION_ACTION_EDITED, DEADLINE_EDITED

@ -0,0 +1,24 @@
# Generated by Django 3.1.3 on 2022-08-15 06:03
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('konova', '0014_resubmission'),
('compensation', '0007_auto_20220531_1245'),
]
operations = [
migrations.AddField(
model_name='compensation',
name='resubmission',
field=models.ManyToManyField(blank=True, null=True, related_name='_compensation_resubmission_+', to='konova.Resubmission'),
),
migrations.AddField(
model_name='ecoaccount',
name='resubmission',
field=models.ManyToManyField(blank=True, null=True, related_name='_ecoaccount_resubmission_+', to='konova.Resubmission'),
),
]

@ -0,0 +1,32 @@
# Generated by Django 3.1.3 on 2022-08-15 06:03
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('konova', '0014_resubmission'),
('compensation', '0008_auto_20220815_0803'),
]
operations = [
migrations.RemoveField(
model_name='compensation',
name='resubmission',
),
migrations.RemoveField(
model_name='ecoaccount',
name='resubmission',
),
migrations.AddField(
model_name='compensation',
name='resubmissions',
field=models.ManyToManyField(blank=True, null=True, related_name='_compensation_resubmissions_+', to='konova.Resubmission'),
),
migrations.AddField(
model_name='ecoaccount',
name='resubmissions',
field=models.ManyToManyField(blank=True, null=True, related_name='_ecoaccount_resubmissions_+', to='konova.Resubmission'),
),
]

@ -0,0 +1,24 @@
# Generated by Django 3.1.3 on 2022-08-15 08:30
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('konova', '0014_resubmission'),
('compensation', '0009_auto_20220815_0803'),
]
operations = [
migrations.AlterField(
model_name='compensation',
name='resubmissions',
field=models.ManyToManyField(blank=True, related_name='_compensation_resubmissions_+', to='konova.Resubmission'),
),
migrations.AlterField(
model_name='ecoaccount',
name='resubmissions',
field=models.ManyToManyField(blank=True, related_name='_ecoaccount_resubmissions_+', to='konova.Resubmission'),
),
]

@ -19,14 +19,17 @@ from compensation.managers import CompensationManager
from compensation.models import CompensationState, CompensationAction
from compensation.utils.quality import CompensationQualityChecker
from konova.models import BaseObject, AbstractDocument, Deadline, generate_document_file_upload_path, \
GeoReferencedMixin, DeadlineType
GeoReferencedMixin, DeadlineType, ResubmitableObjectMixin
from konova.utils.message_templates import DATA_UNSHARED_EXPLANATION, COMPENSATION_REMOVED_TEMPLATE, \
DOCUMENT_REMOVED_TEMPLATE, DEADLINE_REMOVED, ADDED_DEADLINE, \
COMPENSATION_ACTION_REMOVED, COMPENSATION_STATE_REMOVED, INTERVENTION_HAS_REVOCATIONS_TEMPLATE
from user.models import UserActionLogEntry
class AbstractCompensation(BaseObject, GeoReferencedMixin):
class AbstractCompensation(BaseObject,
GeoReferencedMixin,
ResubmitableObjectMixin
):
"""
Abstract compensation model which holds basic attributes, shared by subclasses like the regular Compensation,
EMA or EcoAccount.

@ -12,6 +12,9 @@
</button>
</a>
{% if has_access %}
<button class="btn btn-default btn-modal mr-2" title="{% trans 'Resubmission' %}" data-form-url="{% url 'compensation:resubmission-create' obj.id %}">
{% fa5_icon 'bell' %}
</button>
{% if is_default_member %}
<a href="{% url 'compensation:edit' obj.id %}" class="mr-2">
<button class="btn btn-default" title="{% trans 'Edit' %}">

@ -12,6 +12,9 @@
</button>
</a>
{% if has_access %}
<button class="btn btn-default btn-modal mr-2" title="{% trans 'Resubmission' %}" data-form-url="{% url 'compensation:acc:resubmission-create' obj.id %}">
{% fa5_icon 'bell' %}
</button>
<button class="btn btn-default btn-modal mr-2" title="{% trans 'Share' %}" data-form-url="{% url 'compensation:acc:share-create' obj.id %}">
{% fa5_icon 'share-alt' %}
</button>

@ -31,6 +31,7 @@ urlpatterns = [
path('<id>/deadline/<deadline_id>/edit', deadline_edit_view, name='deadline-edit'),
path('<id>/deadline/<deadline_id>/remove', deadline_remove_view, name='deadline-remove'),
path('<id>/report', report_view, name='report'),
path('<id>/resub', create_resubmission_view, name='resubmission-create'),
# Documents
path('<id>/document/new/', new_document_view, name='new-doc'),

@ -19,6 +19,7 @@ urlpatterns = [
path('<id>/report', report_view, name='report'),
path('<id>/edit', edit_view, name='edit'),
path('<id>/remove', remove_view, name='remove'),
path('<id>/resub', create_resubmission_view, name='resubmission-create'),
path('<id>/state/new', state_new_view, name='new-state'),
path('<id>/state/<state_id>/edit', state_edit_view, name='state-edit'),

@ -14,7 +14,9 @@ from compensation.tables import CompensationTable
from intervention.models import Intervention
from konova.contexts import BaseContext
from konova.decorators import *
from konova.forms import RemoveModalForm, SimpleGeomForm, RemoveDeadlineModalForm, EditDocumentModalForm
from konova.forms.modals import RemoveModalForm,RemoveDeadlineModalForm, EditDocumentModalForm, \
ResubmissionModalForm
from konova.forms import SimpleGeomForm
from konova.models import Deadline
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
from konova.utils.documents import get_document, remove_document
@ -656,3 +658,26 @@ def report_view(request: HttpRequest, id: str):
}
context = BaseContext(request, context).context
return render(request, template, context)
@login_required
@default_group_required
@shared_access_required(Compensation, "id")
def create_resubmission_view(request: HttpRequest, id: str):
""" Renders resubmission form for a compensation
Args:
request (HttpRequest): The incoming request
id (str): Compensation's id
Returns:
"""
com = get_object_or_404(Compensation, id=id)
form = ResubmissionModalForm(request.POST or None, instance=com, request=request)
form.action_url = reverse("compensation:resubmission-create", args=(id,))
return form.process_request(
request,
msg_success=_("Resubmission set"),
redirect_url=reverse("compensation:detail", args=(id,))
)

@ -25,14 +25,15 @@ from intervention.forms.modalForms import NewDeductionModalForm, ShareModalForm,
from konova.contexts import BaseContext
from konova.decorators import any_group_check, default_group_required, conservation_office_group_required, \
shared_access_required
from konova.forms import RemoveModalForm, SimpleGeomForm, NewDocumentModalForm, RecordModalForm, \
RemoveDeadlineModalForm, EditDocumentModalForm
from konova.forms.modals import RemoveModalForm, RecordModalForm, \
RemoveDeadlineModalForm, EditDocumentModalForm, ResubmissionModalForm
from konova.forms import SimpleGeomForm
from konova.models import Deadline
from konova.settings import DEFAULT_GROUP, ZB_GROUP, ETS_GROUP
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
from konova.utils.documents import get_document, remove_document
from konova.utils.generators import generate_qr_code
from konova.utils.message_templates import IDENTIFIER_REPLACED, FORM_INVALID, DATA_UNSHARED, DATA_UNSHARED_EXPLANATION, \
from konova.utils.message_templates import IDENTIFIER_REPLACED, FORM_INVALID, \
CANCEL_ACC_RECORDED_OR_DEDUCTED, DEDUCTION_REMOVED, DEDUCTION_ADDED, DOCUMENT_ADDED, COMPENSATION_STATE_REMOVED, \
COMPENSATION_STATE_ADDED, COMPENSATION_ACTION_REMOVED, COMPENSATION_ACTION_ADDED, DEADLINE_ADDED, DEADLINE_REMOVED, \
DEDUCTION_EDITED, DOCUMENT_EDITED, COMPENSATION_STATE_EDITED, COMPENSATION_ACTION_EDITED, DEADLINE_EDITED, \
@ -838,4 +839,27 @@ def create_share_view(request: HttpRequest, id: str):
return form.process_request(
request,
msg_success=_("Share settings updated")
)
@login_required
@default_group_required
@shared_access_required(EcoAccount, "id")
def create_resubmission_view(request: HttpRequest, id: str):
""" Renders resubmission form for an eco account
Args:
request (HttpRequest): The incoming request
id (str): EcoAccount's id
Returns:
"""
acc = get_object_or_404(EcoAccount, id=id)
form = ResubmissionModalForm(request.POST or None, instance=acc, request=request)
form.action_url = reverse("compensation:acc:resubmission-create", args=(id,))
return form.process_request(
request,
msg_success=_("Resubmission set"),
redirect_url=reverse("compensation:acc:detail", args=(id,))
)

@ -15,7 +15,6 @@ from compensation.forms.modalForms import NewPaymentForm, RemovePaymentModalForm
from compensation.models import Payment
from intervention.models import Intervention
from konova.decorators import default_group_required, shared_access_required
from konova.forms import RemoveModalForm
from konova.utils.message_templates import PAYMENT_ADDED, PAYMENT_REMOVED, PAYMENT_EDITED

@ -5,8 +5,6 @@ Contact: michel.peltriaux@sgdnord.rlp.de
Created on: 06.10.21
"""
from dal import autocomplete
from django import forms
from user.models import User
from django.db import transaction
from django.urls import reverse, reverse_lazy
@ -16,7 +14,8 @@ from compensation.forms.forms import AbstractCompensationForm, CompensationRespo
PikCompensationFormMixin
from ema.models import Ema, EmaDocument
from intervention.models import Responsibility, Handler
from konova.forms import SimpleGeomForm, NewDocumentModalForm
from konova.forms import SimpleGeomForm
from konova.forms.modals import NewDocumentModalForm
from user.models import UserActionLogEntry

@ -0,0 +1,19 @@
# Generated by Django 3.1.3 on 2022-08-15 06:03
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('konova', '0014_resubmission'),
('ema', '0004_ema_is_pik'),
]
operations = [
migrations.AddField(
model_name='ema',
name='resubmission',
field=models.ManyToManyField(blank=True, null=True, related_name='_ema_resubmission_+', to='konova.Resubmission'),
),
]

@ -0,0 +1,23 @@
# Generated by Django 3.1.3 on 2022-08-15 06:03
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('konova', '0014_resubmission'),
('ema', '0005_ema_resubmission'),
]
operations = [
migrations.RemoveField(
model_name='ema',
name='resubmission',
),
migrations.AddField(
model_name='ema',
name='resubmissions',
field=models.ManyToManyField(blank=True, null=True, related_name='_ema_resubmissions_+', to='konova.Resubmission'),
),
]

@ -0,0 +1,19 @@
# Generated by Django 3.1.3 on 2022-08-15 08:30
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('konova', '0014_resubmission'),
('ema', '0006_auto_20220815_0803'),
]
operations = [
migrations.AlterField(
model_name='ema',
name='resubmissions',
field=models.ManyToManyField(blank=True, related_name='_ema_resubmissions_+', to='konova.Resubmission'),
),
]

@ -12,6 +12,9 @@
</button>
</a>
{% if has_access %}
<button class="btn btn-default btn-modal mr-2" title="{% trans 'Resubmission' %}" data-form-url="{% url 'ema:resubmission-create' obj.id %}">
{% fa5_icon 'bell' %}
</button>
<button class="btn btn-default btn-modal mr-2" title="{% trans 'Share' %}" data-form-url="{% url 'ema:share-create' obj.id %}">
{% fa5_icon 'share-alt' %}
</button>

@ -19,6 +19,7 @@ urlpatterns = [
path('<id>/remove', remove_view, name='remove'),
path('<id>/record', record_view, name='record'),
path('<id>/report', report_view, name='report'),
path('<id>/resub', create_resubmission_view, name='resubmission-create'),
path('<id>/state/new', state_new_view, name='new-state'),
path('<id>/state/<state_id>/remove', state_remove_view, name='state-remove'),

@ -16,8 +16,9 @@ from intervention.forms.modalForms import ShareModalForm
from konova.contexts import BaseContext
from konova.decorators import conservation_office_group_required, shared_access_required
from ema.models import Ema, EmaDocument
from konova.forms import RemoveModalForm, SimpleGeomForm, RecordModalForm, RemoveDeadlineModalForm, \
EditDocumentModalForm
from konova.forms.modals import RemoveModalForm, RecordModalForm, RemoveDeadlineModalForm, \
EditDocumentModalForm, ResubmissionModalForm
from konova.forms import SimpleGeomForm
from konova.models import Deadline
from konova.settings import DEFAULT_GROUP, ZB_GROUP, ETS_GROUP
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
@ -710,4 +711,27 @@ def deadline_remove_view(request: HttpRequest, id: str, deadline_id: str):
request,
msg_success=DEADLINE_REMOVED,
redirect_url=reverse("ema:detail", args=(id,)) + "#related_data"
)
)
@login_required
@conservation_office_group_required
@shared_access_required(Ema, "id")
def create_resubmission_view(request: HttpRequest, id: str):
""" Renders resubmission form for an EMA
Args:
request (HttpRequest): The incoming request
id (str): EMA's id
Returns:
"""
ema = get_object_or_404(Ema, id=id)
form = ResubmissionModalForm(request.POST or None, instance=ema, request=request)
form.action_url = reverse("ema:resubmission-create", args=(id,))
return form.process_request(
request,
msg_success=_("Resubmission set"),
redirect_url=reverse("ema:detail", args=(id,))
)

@ -8,6 +8,7 @@ Created on: 02.12.20
from dal import autocomplete
from django import forms
from konova.forms.base_form import BaseForm
from konova.utils.message_templates import EDITED_GENERAL_DATA
from user.models import User
from django.db import transaction
@ -19,7 +20,7 @@ from codelist.settings import CODELIST_PROCESS_TYPE_ID, CODELIST_LAW_ID, \
CODELIST_REGISTRATION_OFFICE_ID, CODELIST_CONSERVATION_OFFICE_ID, CODELIST_HANDLER_ID
from intervention.inputs import GenerateInput
from intervention.models import Intervention, Legal, Responsibility, Handler
from konova.forms import BaseForm, SimpleGeomForm
from konova.forms.geometry_form import SimpleGeomForm
from user.models import UserActionLogEntry

@ -19,7 +19,8 @@ from django.utils.translation import gettext_lazy as _
from compensation.models import EcoAccount, EcoAccountDeduction
from intervention.inputs import TextToClipboardInput
from intervention.models import Intervention, InterventionDocument, RevocationDocument
from konova.forms import BaseModalForm, NewDocumentModalForm, RemoveModalForm
from konova.forms.modals import BaseModalForm
from konova.forms.modals import NewDocumentModalForm, RemoveModalForm
from konova.utils.general import format_german_float
from konova.utils.user_checks import is_default_group_only

@ -0,0 +1,19 @@
# Generated by Django 3.1.3 on 2022-08-15 06:03
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('konova', '0014_resubmission'),
('intervention', '0004_auto_20220303_0956'),
]
operations = [
migrations.AddField(
model_name='intervention',
name='resubmission',
field=models.ManyToManyField(blank=True, null=True, related_name='_intervention_resubmission_+', to='konova.Resubmission'),
),
]

@ -0,0 +1,23 @@
# Generated by Django 3.1.3 on 2022-08-15 06:03
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('konova', '0014_resubmission'),
('intervention', '0005_intervention_resubmission'),
]
operations = [
migrations.RemoveField(
model_name='intervention',
name='resubmission',
),
migrations.AddField(
model_name='intervention',
name='resubmissions',
field=models.ManyToManyField(blank=True, null=True, related_name='_intervention_resubmissions_+', to='konova.Resubmission'),
),
]

@ -0,0 +1,19 @@
# Generated by Django 3.1.3 on 2022-08-15 08:30
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('konova', '0014_resubmission'),
('intervention', '0006_auto_20220815_0803'),
]
operations = [
migrations.AlterField(
model_name='intervention',
name='resubmissions',
field=models.ManyToManyField(blank=True, related_name='_intervention_resubmissions_+', to='konova.Resubmission'),
),
]

@ -26,14 +26,19 @@ from intervention.models.revocation import RevocationDocument, Revocation
from intervention.utils.quality import InterventionQualityChecker
from konova.models import generate_document_file_upload_path, AbstractDocument, BaseObject, \
ShareableObjectMixin, \
RecordableObjectMixin, CheckableObjectMixin, GeoReferencedMixin
from konova.settings import LANIS_LINK_TEMPLATE, LANIS_ZOOM_LUT, DEFAULT_SRID_RLP
RecordableObjectMixin, CheckableObjectMixin, GeoReferencedMixin, ResubmitableObjectMixin
from konova.utils.message_templates import DATA_UNSHARED_EXPLANATION, DOCUMENT_REMOVED_TEMPLATE, \
PAYMENT_REMOVED, PAYMENT_ADDED, REVOCATION_REMOVED, INTERVENTION_HAS_REVOCATIONS_TEMPLATE
from user.models import UserActionLogEntry
class Intervention(BaseObject, ShareableObjectMixin, RecordableObjectMixin, CheckableObjectMixin, GeoReferencedMixin):
class Intervention(BaseObject,
ShareableObjectMixin,
RecordableObjectMixin,
CheckableObjectMixin,
GeoReferencedMixin,
ResubmitableObjectMixin
):
"""
Interventions are e.g. construction sites where nature used to be.
"""

@ -12,6 +12,9 @@
</button>
</a>
{% if has_access %}
<button class="btn btn-default btn-modal mr-2" title="{% trans 'Resubmission' %}" data-form-url="{% url 'intervention:resubmission-create' obj.id %}">
{% fa5_icon 'bell' %}
</button>
<button class="btn btn-default btn-modal mr-2" title="{% trans 'Share' %}" data-form-url="{% url 'intervention:share-create' obj.id %}">
{% fa5_icon 'share-alt' %}
</button>

@ -10,7 +10,8 @@ from django.urls import path
from intervention.views import index_view, new_view, detail_view, edit_view, remove_view, new_document_view, share_view, \
create_share_view, remove_revocation_view, new_revocation_view, check_view, log_view, new_deduction_view, \
record_view, remove_document_view, get_document_view, get_revocation_view, new_id_view, report_view, \
remove_deduction_view, remove_compensation_view, edit_deduction_view, edit_revocation_view, edit_document_view
remove_deduction_view, remove_compensation_view, edit_deduction_view, edit_revocation_view, edit_document_view, \
create_resubmission_view
app_name = "intervention"
urlpatterns = [
@ -26,6 +27,7 @@ urlpatterns = [
path('<id>/check', check_view, name='check'),
path('<id>/record', record_view, name='record'),
path('<id>/report', report_view, name='report'),
path('<id>/resub', create_resubmission_view, name='resubmission-create'),
# Compensations
path('<id>/compensation/<comp_id>/remove', remove_compensation_view, name='remove-compensation'),

@ -12,7 +12,8 @@ from intervention.models import Intervention, Revocation, InterventionDocument,
from intervention.tables import InterventionTable
from konova.contexts import BaseContext
from konova.decorators import *
from konova.forms import SimpleGeomForm, RemoveModalForm, RecordModalForm, EditDocumentModalForm
from konova.forms import SimpleGeomForm
from konova.forms.modals import RemoveModalForm, RecordModalForm, EditDocumentModalForm, ResubmissionModalForm
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
from konova.utils.documents import remove_document, get_document
from konova.utils.generators import generate_qr_code
@ -475,6 +476,29 @@ def create_share_view(request: HttpRequest, id: str):
)
@login_required
@default_group_required
@shared_access_required(Intervention, "id")
def create_resubmission_view(request: HttpRequest, id: str):
""" Renders resubmission form for an intervention
Args:
request (HttpRequest): The incoming request
id (str): Intervention's id
Returns:
"""
intervention = get_object_or_404(Intervention, id=id)
form = ResubmissionModalForm(request.POST or None, instance=intervention, request=request)
form.action_url = reverse("intervention:resubmission-create", args=(id,))
return form.process_request(
request,
msg_success=_("Resubmission set"),
redirect_url=reverse("intervention:detail", args=(id,))
)
@login_required
@registration_office_group_required
@shared_access_required(Intervention, "id")

@ -7,7 +7,7 @@ Created on: 22.07.21
"""
from django.contrib import admin
from konova.models import Geometry, Deadline, GeometryConflict, Parcel, District, Municipal, ParcelGroup
from konova.models import Geometry, Deadline, GeometryConflict, Parcel, District, Municipal, ParcelGroup, Resubmission
from konova.sub_settings.lanis_settings import DEFAULT_SRID_RLP
from konova.utils.message_templates import COMPENSATION_REMOVED_TEMPLATE
from user.models import UserAction
@ -139,6 +139,16 @@ class BaseObjectAdmin(BaseResourceAdmin, DeletableObjectMixinAdmin):
]
class ResubmissionAdmin(BaseResourceAdmin):
list_display = [
"resubmit_on"
]
fields = [
"comment",
"resubmit_on",
"resubmission_sent",
]
# Outcommented for a cleaner admin backend on production
#admin.site.register(Geometry, GeometryAdmin)
@ -148,3 +158,4 @@ class BaseObjectAdmin(BaseResourceAdmin, DeletableObjectMixinAdmin):
#admin.site.register(ParcelGroup, ParcelGroupAdmin)
#admin.site.register(GeometryConflict, GeometryConflictAdmin)
#admin.site.register(Deadline, DeadlineAdmin)
#admin.site.register(Resubmission, ResubmissionAdmin)

@ -1,688 +0,0 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: michel.peltriaux@sgdnord.rlp.de
Created on: 16.11.20
"""
import json
from abc import abstractmethod
from bootstrap_modal_forms.forms import BSModalForm
from bootstrap_modal_forms.utils import is_ajax
from django import forms
from django.contrib import messages
from django.contrib.gis import gdal
from django.db.models.fields.files import FieldFile
from compensation.models import EcoAccount
from konova.sub_settings.lanis_settings import DEFAULT_SRID_RLP
from user.models import User
from django.contrib.gis.forms import MultiPolygonField
from django.contrib.gis.geos import MultiPolygon, Polygon
from django.db import transaction
from django.http import HttpRequest, HttpResponseRedirect
from django.shortcuts import render
from django.utils.translation import gettext_lazy as _
from konova.contexts import BaseContext
from konova.models import BaseObject, Geometry, RecordableObjectMixin, AbstractDocument
from konova.settings import DEFAULT_SRID
from konova.tasks import celery_update_parcels
from konova.utils.message_templates import FORM_INVALID, FILE_TYPE_UNSUPPORTED, FILE_SIZE_TOO_LARGE, DOCUMENT_EDITED
from user.models import UserActionLogEntry
class BaseForm(forms.Form):
"""
Basic form for that holds attributes needed in all other forms
"""
template = None
action_url = None
action_btn_label = _("Save")
form_title = None
cancel_redirect = None
form_caption = None
instance = None # The data holding model object
request = None
form_attrs = {} # Holds additional attributes, that can be used in the template
has_required_fields = False # Automatically set. Triggers hint rendering in templates
show_cancel_btn = True
def __init__(self, *args, **kwargs):
self.instance = kwargs.pop("instance", None)
super().__init__(*args, **kwargs)
if self.request is not None:
self.user = self.request.user
# Check for required fields
for _field_name, _field_val in self.fields.items():
if _field_val.required:
self.has_required_fields = True
break
self.check_for_recorded_instance()
@abstractmethod
def save(self):
# To be implemented in subclasses!
pass
def disable_form_field(self, field: str):
"""
Disables a form field for user editing
"""
self.fields[field].widget.attrs["readonly"] = True
self.fields[field].disabled = True
self.fields[field].widget.attrs["title"] = _("Not editable")
def initialize_form_field(self, field: str, val):
"""
Initializes a form field with a value
"""
self.fields[field].initial = val
def add_placeholder_for_field(self, field: str, val):
"""
Adds a placeholder to a field after initialization without the need to redefine the form widget
Args:
field (str): Field name
val (str): Placeholder
Returns:
"""
self.fields[field].widget.attrs["placeholder"] = val
def load_initial_data(self, form_data: dict, disabled_fields: list = None):
""" Initializes form data from instance
Inserts instance data into form and disables form fields
Returns:
"""
if self.instance is None:
return
for k, v in form_data.items():
self.initialize_form_field(k, v)
if disabled_fields:
for field in disabled_fields:
self.disable_form_field(field)
def add_widget_html_class(self, field: str, cls: str):
""" Adds a HTML class string to the widget of a field
Args:
field (str): The field's name
cls (str): The new class string
Returns:
"""
set_class = self.fields[field].widget.attrs.get("class", "")
if cls in set_class:
return
else:
set_class += " " + cls
self.fields[field].widget.attrs["class"] = set_class
def remove_widget_html_class(self, field: str, cls: str):
""" Removes a HTML class string from the widget of a field
Args:
field (str): The field's name
cls (str): The new class string
Returns:
"""
set_class = self.fields[field].widget.attrs.get("class", "")
set_class = set_class.replace(cls, "")
self.fields[field].widget.attrs["class"] = set_class
def check_for_recorded_instance(self):
""" Checks if the instance is recorded and runs some special logic if yes
If the instance is recorded, the form shall not display any possibility to
edit any data. Instead, the users should get some information about why they can not edit anything.
There are situations where the form should be rendered regularly,
e.g deduction forms for (recorded) eco accounts.
Returns:
"""
from intervention.forms.modalForms import NewDeductionModalForm, EditEcoAccountDeductionModalForm, \
RemoveEcoAccountDeductionModalForm
is_none = self.instance is None
is_other_data_type = not isinstance(self.instance, BaseObject)
is_deduction_form_from_account = isinstance(
self,
(
NewDeductionModalForm,
EditEcoAccountDeductionModalForm,
RemoveEcoAccountDeductionModalForm,
)
) and isinstance(self.instance, EcoAccount)
if is_none or is_other_data_type or is_deduction_form_from_account:
# Do nothing
return
if self.instance.is_recorded:
self.template = "form/recorded_no_edit.html"
class RemoveForm(BaseForm):
check = forms.BooleanField(
label=_("Confirm"),
label_suffix=_(""),
required=True,
)
def __init__(self, *args, **kwargs):
self.object_to_remove = kwargs.pop("object_to_remove", None)
self.remove_post_url = kwargs.pop("remove_post_url", "")
self.cancel_url = kwargs.pop("cancel_url", "")
super().__init__(*args, **kwargs)
self.form_title = _("Remove")
if self.object_to_remove is not None:
self.form_caption = _("You are about to remove {} {}").format(self.object_to_remove.__class__.__name__, self.object_to_remove)
self.action_url = self.remove_post_url
self.cancel_redirect = self.cancel_url
def is_checked(self) -> bool:
return self.cleaned_data.get("check", False)
def save(self, user: User):
""" Perform generic removing by running the form typical 'save()' method
Args:
user (User): The performing user
Returns:
"""
if self.object_to_remove is not None and self.is_checked():
with transaction.atomic():
self.object_to_remove.is_active = False
action = UserActionLogEntry.get_deleted_action(user)
self.object_to_remove.deleted = action
self.object_to_remove.save()
return self.object_to_remove
class BaseModalForm(BaseForm, BSModalForm):
""" A specialzed form class for modal form handling
"""
is_modal_form = True
render_submit = True
template = "modal/modal_form.html"
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.action_btn_label = _("Continue")
def process_request(self, request: HttpRequest, msg_success: str = _("Object removed"), msg_error: str = FORM_INVALID, redirect_url: str = None):
""" Generic processing of request
Wraps the request processing logic, so we don't need the same code everywhere a RemoveModalForm is being used
Args:
request (HttpRequest): The incoming request
msg_success (str): The message in case of successful removing
msg_error (str): The message in case of an error
Returns:
"""
redirect_url = redirect_url if redirect_url is not None else request.META.get("HTTP_REFERER", "home")
template = self.template
if request.method == "POST":
if self.is_valid():
if not is_ajax(request.META):
# Modal forms send one POST for checking on data validity. This can be used to return possible errors
# on the form. A second POST (if no errors occured) is sent afterwards and needs to process the
# saving/commiting of the data to the database. is_ajax() performs this check. The first request is
# an ajax call, the second is a regular form POST.
self.save()
messages.success(
request,
msg_success
)
return HttpResponseRedirect(redirect_url)
else:
context = {
"form": self,
}
context = BaseContext(request, context).context
return render(request, template, context)
elif request.method == "GET":
context = {
"form": self,
}
context = BaseContext(request, context).context
return render(request, template, context)
else:
raise NotImplementedError
class SimpleGeomForm(BaseForm):
""" A geometry form for rendering geometry read-only using a widget
"""
read_only = True
geom = MultiPolygonField(
srid=DEFAULT_SRID_RLP,
label=_("Geometry"),
help_text=_(""),
label_suffix="",
required=False,
disabled=False,
)
def __init__(self, *args, **kwargs):
self.read_only = kwargs.pop("read_only", True)
super().__init__(*args, **kwargs)
# Initialize geometry
try:
geom = self.instance.geometry.geom
self.empty = geom.empty
if self.empty:
raise AttributeError
geojson = self.instance.geometry.as_feature_collection(srid=DEFAULT_SRID_RLP)
geom = json.dumps(geojson)
except AttributeError:
# If no geometry exists for this form, we simply set the value to None and zoom to the maximum level
geom = ""
self.empty = True
self.initialize_form_field("geom", geom)
def is_valid(self):
super().is_valid()
is_valid = True
# Get geojson from form
geom = self.data["geom"]
if geom is None or len(geom) == 0:
# empty geometry is a valid geometry
return is_valid
geom = json.loads(geom)
# Write submitted data back into form field to make sure invalid geometry
# will be rendered again on failed submit
self.initialize_form_field("geom", self.data["geom"])
# Read geojson into gdal geometry
# HINT: This can be simplified if the geojson format holds data in epsg:4326 (GDAL provides direct creation for
# this case)
features = []
features_json = geom.get("features", [])
for feature in features_json:
g = gdal.OGRGeometry(json.dumps(feature.get("geometry", feature)), srs=DEFAULT_SRID_RLP)
if g.geom_type not in ["Polygon", "MultiPolygon"]:
self.add_error("geom", _("Only surfaces allowed. Points or lines must be buffered."))
is_valid = False
return is_valid
polygon = Polygon.from_ewkt(g.ewkt)
is_valid = polygon.valid
if not is_valid:
self.add_error("geom", polygon.valid_reason)
return is_valid
features.append(polygon)
form_geom = MultiPolygon(srid=DEFAULT_SRID_RLP)
for feature in features:
form_geom = form_geom.union(feature)
# Make sure to convert into a MultiPolygon. Relevant if a single Polygon is provided.
if form_geom.geom_type != "MultiPolygon":
form_geom = MultiPolygon(form_geom, srid=DEFAULT_SRID_RLP)
# Write unioned Multipolygon into cleaned data
if self.cleaned_data is None:
self.cleaned_data = {}
self.cleaned_data["geom"] = form_geom.ewkt
return is_valid
def save(self, action: UserActionLogEntry):
""" Saves the form's geometry
Creates a new geometry entry if none is set, yet
Args:
action ():
Returns:
"""
try:
if self.instance is None or self.instance.geometry is None:
raise LookupError
geometry = self.instance.geometry
geometry.geom = self.cleaned_data.get("geom", MultiPolygon(srid=DEFAULT_SRID_RLP))
geometry.modified = action
geometry.save()
except LookupError:
# No geometry or linked instance holding a geometry exist --> create a new one!
geometry = Geometry.objects.create(
geom=self.cleaned_data.get("geom", MultiPolygon(srid=DEFAULT_SRID_RLP)),
created=action,
)
# Start the parcel update procedure in a background process
celery_update_parcels.delay(geometry.id)
return geometry
class RemoveModalForm(BaseModalForm):
""" Generic removing modal form
Can be used for anything, where removing shall be confirmed by the user a second time.
"""
confirm = forms.BooleanField(
label=_("Confirm"),
label_suffix=_(""),
widget=forms.CheckboxInput(),
required=True,
)
def __init__(self, *args, **kwargs):
self.template = "modal/modal_form.html"
super().__init__(*args, **kwargs)
self.form_title = _("Remove")
self.form_caption = _("Are you sure?")
# Disable automatic w-100 setting for this type of modal form. Looks kinda strange
self.fields["confirm"].widget.attrs["class"] = ""
def save(self):
if isinstance(self.instance, BaseObject):
self.instance.mark_as_deleted(self.user)
else:
# If the class does not provide restorable delete functionality, we must delete the entry finally
self.instance.delete()
class RemoveDeadlineModalForm(RemoveModalForm):
""" Removing modal form for deadlines
Can be used for anything, where removing shall be confirmed by the user a second time.
"""
deadline = None
def __init__(self, *args, **kwargs):
deadline = kwargs.pop("deadline", None)
self.deadline = deadline
super().__init__(*args, **kwargs)
def save(self):
self.instance.remove_deadline(self)
class NewDocumentModalForm(BaseModalForm):
""" Modal form for new documents
"""
title = forms.CharField(
label=_("Title"),
label_suffix=_(""),
max_length=500,
widget=forms.TextInput(
attrs={
"class": "form-control",
}
)
)
creation_date = forms.DateField(
label=_("Created on"),
label_suffix=_(""),
help_text=_("When has this file been created? Important for photos."),
widget=forms.DateInput(
attrs={
"type": "date",
"data-provide": "datepicker",
"class": "form-control",
},
format="%d.%m.%Y"
)
)
file = forms.FileField(
label=_("File"),
label_suffix=_(""),
help_text=_("Allowed formats: pdf, jpg, png. Max size 15 MB."),
widget=forms.FileInput(
attrs={
"class": "form-control-file",
}
),
)
comment = forms.CharField(
required=False,
max_length=200,
label=_("Comment"),
label_suffix=_(""),
help_text=_("Additional comment, maximum {} letters").format(200),
widget=forms.Textarea(
attrs={
"cols": 30,
"rows": 5,
"class": "form-control",
}
)
)
document_model = None
class Meta:
abstract = True
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.form_title = _("Add new document")
self.form_caption = _("")
self.form_attrs = {
"enctype": "multipart/form-data", # important for file upload
}
if not self.document_model:
raise NotImplementedError("Unsupported document type for {}".format(self.instance.__class__))
def is_valid(self):
super_valid = super().is_valid()
_file = self.cleaned_data.get("file", None)
if _file is None or isinstance(_file, FieldFile):
# FieldFile declares that no new file has been uploaded and we do not need to check on the file again
return super_valid
mime_type_valid = self.document_model.is_mime_type_valid(_file)
if not mime_type_valid:
self.add_error(
"file",
FILE_TYPE_UNSUPPORTED
)
file_size_valid = self.document_model.is_file_size_valid(_file)
if not file_size_valid:
self.add_error(
"file",
FILE_SIZE_TOO_LARGE
)
file_valid = mime_type_valid and file_size_valid
return super_valid and file_valid
def save(self):
with transaction.atomic():
action = UserActionLogEntry.get_created_action(self.user)
edited_action = UserActionLogEntry.get_edited_action(self.user, _("Added document"))
doc = self.document_model.objects.create(
created=action,
title=self.cleaned_data["title"],
comment=self.cleaned_data["comment"],
file=self.cleaned_data["file"],
date_of_creation=self.cleaned_data["creation_date"],
instance=self.instance,
)
self.instance.log.add(edited_action)
self.instance.modified = edited_action
self.instance.save()
return doc
class EditDocumentModalForm(NewDocumentModalForm):
document = None
document_model = AbstractDocument
def __init__(self, *args, **kwargs):
self.document = kwargs.pop("document", None)
super().__init__(*args, **kwargs)
self.form_title = _("Edit document")
form_data = {
"title": self.document.title,
"comment": self.document.comment,
"creation_date": str(self.document.date_of_creation),
"file": self.document.file,
}
self.load_initial_data(form_data)
def save(self):
with transaction.atomic():
document = self.document
file = self.cleaned_data.get("file", None)
document.title = self.cleaned_data.get("title", None)
document.comment = self.cleaned_data.get("comment", None)
document.date_of_creation = self.cleaned_data.get("creation_date", None)
if not isinstance(file, FieldFile):
document.replace_file(file)
document.save()
self.instance.mark_as_edited(self.user, self.request, edit_comment=DOCUMENT_EDITED)
return document
class RecordModalForm(BaseModalForm):
""" Modal form for recording data
"""
confirm = forms.BooleanField(
label=_("Confirm record"),
label_suffix="",
widget=forms.CheckboxInput(),
required=True,
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.form_title = _("Record data")
self.form_caption = _("I, {} {}, confirm that all necessary control steps have been performed by myself.").format(self.user.first_name, self.user.last_name)
# Disable automatic w-100 setting for this type of modal form. Looks kinda strange
self.fields["confirm"].widget.attrs["class"] = ""
if self.instance.recorded:
# unrecord!
self.fields["confirm"].label = _("Confirm unrecord")
self.form_title = _("Unrecord data")
self.form_caption = _("I, {} {}, confirm that this data must be unrecorded.").format(self.user.first_name, self.user.last_name)
if not isinstance(self.instance, RecordableObjectMixin):
raise NotImplementedError
def is_valid(self):
""" Checks for instance's validity and data quality
Returns:
"""
from intervention.models import Intervention
super_val = super().is_valid()
if self.instance.recorded:
# If user wants to unrecord an already recorded dataset, we do not need to perform custom checks
return super_val
checker = self.instance.quality_check()
for msg in checker.messages:
self.add_error(
"confirm",
msg
)
valid = checker.valid
# Special case: Intervention
# Add direct checks for related compensations
if isinstance(self.instance, Intervention):
comps_valid = self._are_compensations_valid()
valid = valid and comps_valid
return super_val and valid
def _are_deductions_valid(self):
""" Performs validity checks on deductions and their eco-account
Returns:
"""
deductions = self.instance.deductions.all()
for deduction in deductions:
checker = deduction.account.quality_check()
for msg in checker.messages:
self.add_error(
"confirm",
f"{deduction.account.identifier}: {msg}"
)
return checker.valid
return True
def _are_compensations_valid(self):
""" Runs a special case for intervention-compensations validity
Returns:
"""
comps = self.instance.compensations.filter(
deleted=None,
)
comps_valid = True
for comp in comps:
checker = comp.quality_check()
comps_valid = comps_valid and checker.valid
for msg in checker.messages:
self.add_error(
"confirm",
f"{comp.identifier}: {msg}"
)
deductions_valid = self._are_deductions_valid()
return comps_valid and deductions_valid
def save(self):
with transaction.atomic():
if self.cleaned_data["confirm"]:
if self.instance.recorded:
self.instance.set_unrecorded(self.user)
else:
self.instance.set_recorded(self.user)
return self.instance
def check_for_recorded_instance(self):
""" Overwrite the check method for doing nothing on the RecordModalForm
Returns:
"""
pass

@ -0,0 +1,11 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 15.08.22
"""
from .base_form import *
from .geometry_form import *
from .remove_form import *

@ -0,0 +1,157 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 15.08.22
"""
from abc import abstractmethod
from django import forms
from django.utils.translation import gettext_lazy as _
from compensation.models import EcoAccount
from konova.models import BaseObject
class BaseForm(forms.Form):
"""
Basic form for that holds attributes needed in all other forms
"""
template = None
action_url = None
action_btn_label = _("Save")
form_title = None
cancel_redirect = None
form_caption = None
instance = None # The data holding model object
request = None
form_attrs = {} # Holds additional attributes, that can be used in the template
has_required_fields = False # Automatically set. Triggers hint rendering in templates
show_cancel_btn = True
def __init__(self, *args, **kwargs):
self.instance = kwargs.pop("instance", None)
super().__init__(*args, **kwargs)
if self.request is not None:
self.user = self.request.user
# Check for required fields
for _field_name, _field_val in self.fields.items():
if _field_val.required:
self.has_required_fields = True
break
self.check_for_recorded_instance()
@abstractmethod
def save(self):
# To be implemented in subclasses!
pass
def disable_form_field(self, field: str):
"""
Disables a form field for user editing
"""
self.fields[field].widget.attrs["readonly"] = True
self.fields[field].disabled = True
self.fields[field].widget.attrs["title"] = _("Not editable")
def initialize_form_field(self, field: str, val):
"""
Initializes a form field with a value
"""
self.fields[field].initial = val
def add_placeholder_for_field(self, field: str, val):
"""
Adds a placeholder to a field after initialization without the need to redefine the form widget
Args:
field (str): Field name
val (str): Placeholder
Returns:
"""
self.fields[field].widget.attrs["placeholder"] = val
def load_initial_data(self, form_data: dict, disabled_fields: list = None):
""" Initializes form data from instance
Inserts instance data into form and disables form fields
Returns:
"""
if self.instance is None:
return
for k, v in form_data.items():
self.initialize_form_field(k, v)
if disabled_fields:
for field in disabled_fields:
self.disable_form_field(field)
def add_widget_html_class(self, field: str, cls: str):
""" Adds a HTML class string to the widget of a field
Args:
field (str): The field's name
cls (str): The new class string
Returns:
"""
set_class = self.fields[field].widget.attrs.get("class", "")
if cls in set_class:
return
else:
set_class += " " + cls
self.fields[field].widget.attrs["class"] = set_class
def remove_widget_html_class(self, field: str, cls: str):
""" Removes a HTML class string from the widget of a field
Args:
field (str): The field's name
cls (str): The new class string
Returns:
"""
set_class = self.fields[field].widget.attrs.get("class", "")
set_class = set_class.replace(cls, "")
self.fields[field].widget.attrs["class"] = set_class
def check_for_recorded_instance(self):
""" Checks if the instance is recorded and runs some special logic if yes
If the instance is recorded, the form shall not display any possibility to
edit any data. Instead, the users should get some information about why they can not edit anything.
There are situations where the form should be rendered regularly,
e.g deduction forms for (recorded) eco accounts.
Returns:
"""
from intervention.forms.modalForms import NewDeductionModalForm, EditEcoAccountDeductionModalForm, \
RemoveEcoAccountDeductionModalForm
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,
(
NewDeductionModalForm,
ResubmissionModalForm,
EditEcoAccountDeductionModalForm,
RemoveEcoAccountDeductionModalForm,
)
) and isinstance(self.instance, EcoAccount)
if is_none or is_other_data_type or is_deduction_form_from_account:
# Do nothing
return
if self.instance.is_recorded:
self.template = "form/recorded_no_edit.html"

@ -0,0 +1,133 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 15.08.22
"""
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.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.sub_settings.lanis_settings import DEFAULT_SRID_RLP
from user.models import UserActionLogEntry
class SimpleGeomForm(BaseForm):
""" A geometry form for rendering geometry read-only using a widget
"""
read_only = True
geom = MultiPolygonField(
srid=DEFAULT_SRID_RLP,
label=_("Geometry"),
help_text=_(""),
label_suffix="",
required=False,
disabled=False,
)
def __init__(self, *args, **kwargs):
self.read_only = kwargs.pop("read_only", True)
super().__init__(*args, **kwargs)
# Initialize geometry
try:
geom = self.instance.geometry.geom
self.empty = geom.empty
if self.empty:
raise AttributeError
geojson = self.instance.geometry.as_feature_collection(srid=DEFAULT_SRID_RLP)
geom = json.dumps(geojson)
except AttributeError:
# If no geometry exists for this form, we simply set the value to None and zoom to the maximum level
geom = ""
self.empty = True
self.initialize_form_field("geom", geom)
def is_valid(self):
super().is_valid()
is_valid = True
# Get geojson from form
geom = self.data["geom"]
if geom is None or len(geom) == 0:
# empty geometry is a valid geometry
return is_valid
geom = json.loads(geom)
# Write submitted data back into form field to make sure invalid geometry
# will be rendered again on failed submit
self.initialize_form_field("geom", self.data["geom"])
# Read geojson into gdal geometry
# HINT: This can be simplified if the geojson format holds data in epsg:4326 (GDAL provides direct creation for
# this case)
features = []
features_json = geom.get("features", [])
for feature in features_json:
g = gdal.OGRGeometry(json.dumps(feature.get("geometry", feature)), srs=DEFAULT_SRID_RLP)
if g.geom_type not in ["Polygon", "MultiPolygon"]:
self.add_error("geom", _("Only surfaces allowed. Points or lines must be buffered."))
is_valid = False
return is_valid
polygon = Polygon.from_ewkt(g.ewkt)
is_valid = polygon.valid
if not is_valid:
self.add_error("geom", polygon.valid_reason)
return is_valid
features.append(polygon)
form_geom = MultiPolygon(srid=DEFAULT_SRID_RLP)
for feature in features:
form_geom = form_geom.union(feature)
# Make sure to convert into a MultiPolygon. Relevant if a single Polygon is provided.
if form_geom.geom_type != "MultiPolygon":
form_geom = MultiPolygon(form_geom, srid=DEFAULT_SRID_RLP)
# Write unioned Multipolygon into cleaned data
if self.cleaned_data is None:
self.cleaned_data = {}
self.cleaned_data["geom"] = form_geom.ewkt
return is_valid
def save(self, action: UserActionLogEntry):
""" Saves the form's geometry
Creates a new geometry entry if none is set, yet
Args:
action ():
Returns:
"""
try:
if self.instance is None or self.instance.geometry is None:
raise LookupError
geometry = self.instance.geometry
geometry.geom = self.cleaned_data.get("geom", MultiPolygon(srid=DEFAULT_SRID_RLP))
geometry.modified = action
geometry.save()
except LookupError:
# No geometry or linked instance holding a geometry exist --> create a new one!
geometry = Geometry.objects.create(
geom=self.cleaned_data.get("geom", MultiPolygon(srid=DEFAULT_SRID_RLP)),
created=action,
)
# Start the parcel update procedure in a background process
celery_update_parcels.delay(geometry.id)
return geometry

@ -0,0 +1,12 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 15.08.22
"""
from .base_form import *
from .document_form import *
from .record_form import *
from .remove_form import *
from .resubmission_form import *

@ -0,0 +1,73 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 15.08.22
"""
from bootstrap_modal_forms.forms import BSModalForm
from bootstrap_modal_forms.utils import is_ajax
from django.contrib import messages
from django.http import HttpResponseRedirect, HttpRequest
from django.shortcuts import render
from django.utils.translation import gettext_lazy as _
from konova.contexts import BaseContext
from konova.forms.base_form import BaseForm
from konova.utils.message_templates import FORM_INVALID
class BaseModalForm(BaseForm, BSModalForm):
""" A specialzed form class for modal form handling
"""
is_modal_form = True
render_submit = True
template = "modal/modal_form.html"
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.action_btn_label = _("Continue")
def process_request(self, request: HttpRequest, msg_success: str = _("Object removed"), msg_error: str = FORM_INVALID, redirect_url: str = None):
""" Generic processing of request
Wraps the request processing logic, so we don't need the same code everywhere a RemoveModalForm is being used
Args:
request (HttpRequest): The incoming request
msg_success (str): The message in case of successful removing
msg_error (str): The message in case of an error
Returns:
"""
redirect_url = redirect_url if redirect_url is not None else request.META.get("HTTP_REFERER", "home")
template = self.template
if request.method == "POST":
if self.is_valid():
if not is_ajax(request.META):
# Modal forms send one POST for checking on data validity. This can be used to return possible errors
# on the form. A second POST (if no errors occured) is sent afterwards and needs to process the
# saving/commiting of the data to the database. is_ajax() performs this check. The first request is
# an ajax call, the second is a regular form POST.
self.save()
messages.success(
request,
msg_success
)
return HttpResponseRedirect(redirect_url)
else:
context = {
"form": self,
}
context = BaseContext(request, context).context
return render(request, template, context)
elif request.method == "GET":
context = {
"form": self,
}
context = BaseContext(request, context).context
return render(request, template, context)
else:
raise NotImplementedError

@ -0,0 +1,163 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 15.08.22
"""
from django import forms
from django.db import transaction
from django.db.models.fields.files import FieldFile
from django.utils.translation import gettext_lazy as _
from konova.forms.modals.base_form import BaseModalForm
from konova.models import AbstractDocument
from konova.utils.message_templates import DOCUMENT_EDITED, FILE_SIZE_TOO_LARGE, FILE_TYPE_UNSUPPORTED
from user.models import UserActionLogEntry
class NewDocumentModalForm(BaseModalForm):
""" Modal form for new documents
"""
title = forms.CharField(
label=_("Title"),
label_suffix=_(""),
max_length=500,
widget=forms.TextInput(
attrs={
"class": "form-control",
}
)
)
creation_date = forms.DateField(
label=_("Created on"),
label_suffix=_(""),
help_text=_("When has this file been created? Important for photos."),
widget=forms.DateInput(
attrs={
"type": "date",
"data-provide": "datepicker",
"class": "form-control",
},
format="%d.%m.%Y"
)
)
file = forms.FileField(
label=_("File"),
label_suffix=_(""),
help_text=_("Allowed formats: pdf, jpg, png. Max size 15 MB."),
widget=forms.FileInput(
attrs={
"class": "form-control-file",
}
),
)
comment = forms.CharField(
required=False,
max_length=200,
label=_("Comment"),
label_suffix=_(""),
help_text=_("Additional comment, maximum {} letters").format(200),
widget=forms.Textarea(
attrs={
"cols": 30,
"rows": 5,
"class": "form-control",
}
)
)
document_model = None
class Meta:
abstract = True
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.form_title = _("Add new document")
self.form_caption = _("")
self.form_attrs = {
"enctype": "multipart/form-data", # important for file upload
}
if not self.document_model:
raise NotImplementedError("Unsupported document type for {}".format(self.instance.__class__))
def is_valid(self):
super_valid = super().is_valid()
_file = self.cleaned_data.get("file", None)
if _file is None or isinstance(_file, FieldFile):
# FieldFile declares that no new file has been uploaded and we do not need to check on the file again
return super_valid
mime_type_valid = self.document_model.is_mime_type_valid(_file)
if not mime_type_valid:
self.add_error(
"file",
FILE_TYPE_UNSUPPORTED
)
file_size_valid = self.document_model.is_file_size_valid(_file)
if not file_size_valid:
self.add_error(
"file",
FILE_SIZE_TOO_LARGE
)
file_valid = mime_type_valid and file_size_valid
return super_valid and file_valid
def save(self):
with transaction.atomic():
action = UserActionLogEntry.get_created_action(self.user)
edited_action = UserActionLogEntry.get_edited_action(self.user, _("Added document"))
doc = self.document_model.objects.create(
created=action,
title=self.cleaned_data["title"],
comment=self.cleaned_data["comment"],
file=self.cleaned_data["file"],
date_of_creation=self.cleaned_data["creation_date"],
instance=self.instance,
)
self.instance.log.add(edited_action)
self.instance.modified = edited_action
self.instance.save()
return doc
class EditDocumentModalForm(NewDocumentModalForm):
document = None
document_model = AbstractDocument
def __init__(self, *args, **kwargs):
self.document = kwargs.pop("document", None)
super().__init__(*args, **kwargs)
self.form_title = _("Edit document")
form_data = {
"title": self.document.title,
"comment": self.document.comment,
"creation_date": str(self.document.date_of_creation),
"file": self.document.file,
}
self.load_initial_data(form_data)
def save(self):
with transaction.atomic():
document = self.document
file = self.cleaned_data.get("file", None)
document.title = self.cleaned_data.get("title", None)
document.comment = self.cleaned_data.get("comment", None)
document.date_of_creation = self.cleaned_data.get("creation_date", None)
if not isinstance(file, FieldFile):
document.replace_file(file)
document.save()
self.instance.mark_as_edited(self.user, self.request, edit_comment=DOCUMENT_EDITED)
return document

@ -0,0 +1,123 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 15.08.22
"""
from django import forms
from django.db import transaction
from django.utils.translation import gettext_lazy as _
from konova.forms.modals.base_form import BaseModalForm
from konova.models import RecordableObjectMixin
class RecordModalForm(BaseModalForm):
""" Modal form for recording data
"""
confirm = forms.BooleanField(
label=_("Confirm record"),
label_suffix="",
widget=forms.CheckboxInput(),
required=True,
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.form_title = _("Record data")
self.form_caption = _("I, {} {}, confirm that all necessary control steps have been performed by myself.").format(self.user.first_name, self.user.last_name)
# Disable automatic w-100 setting for this type of modal form. Looks kinda strange
self.fields["confirm"].widget.attrs["class"] = ""
if self.instance.recorded:
# unrecord!
self.fields["confirm"].label = _("Confirm unrecord")
self.form_title = _("Unrecord data")
self.form_caption = _("I, {} {}, confirm that this data must be unrecorded.").format(self.user.first_name, self.user.last_name)
if not isinstance(self.instance, RecordableObjectMixin):
raise NotImplementedError
def is_valid(self):
""" Checks for instance's validity and data quality
Returns:
"""
from intervention.models import Intervention
super_val = super().is_valid()
if self.instance.recorded:
# If user wants to unrecord an already recorded dataset, we do not need to perform custom checks
return super_val
checker = self.instance.quality_check()
for msg in checker.messages:
self.add_error(
"confirm",
msg
)
valid = checker.valid
# Special case: Intervention
# Add direct checks for related compensations
if isinstance(self.instance, Intervention):
comps_valid = self._are_compensations_valid()
valid = valid and comps_valid
return super_val and valid
def _are_deductions_valid(self):
""" Performs validity checks on deductions and their eco-account
Returns:
"""
deductions = self.instance.deductions.all()
for deduction in deductions:
checker = deduction.account.quality_check()
for msg in checker.messages:
self.add_error(
"confirm",
f"{deduction.account.identifier}: {msg}"
)
return checker.valid
return True
def _are_compensations_valid(self):
""" Runs a special case for intervention-compensations validity
Returns:
"""
comps = self.instance.compensations.filter(
deleted=None,
)
comps_valid = True
for comp in comps:
checker = comp.quality_check()
comps_valid = comps_valid and checker.valid
for msg in checker.messages:
self.add_error(
"confirm",
f"{comp.identifier}: {msg}"
)
deductions_valid = self._are_deductions_valid()
return comps_valid and deductions_valid
def save(self):
with transaction.atomic():
if self.cleaned_data["confirm"]:
if self.instance.recorded:
self.instance.set_unrecorded(self.user)
else:
self.instance.set_recorded(self.user)
return self.instance
def check_for_recorded_instance(self):
""" Overwrite the check method for doing nothing on the RecordModalForm
Returns:
"""
pass

@ -0,0 +1,58 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 15.08.22
"""
from django import forms
from django.utils.translation import gettext_lazy as _
from konova.forms.modals.base_form import BaseModalForm
from konova.models import BaseObject
class RemoveModalForm(BaseModalForm):
""" Generic removing modal form
Can be used for anything, where removing shall be confirmed by the user a second time.
"""
confirm = forms.BooleanField(
label=_("Confirm"),
label_suffix=_(""),
widget=forms.CheckboxInput(),
required=True,
)
def __init__(self, *args, **kwargs):
self.template = "modal/modal_form.html"
super().__init__(*args, **kwargs)
self.form_title = _("Remove")
self.form_caption = _("Are you sure?")
# Disable automatic w-100 setting for this type of modal form. Looks kinda strange
self.fields["confirm"].widget.attrs["class"] = ""
def save(self):
if isinstance(self.instance, BaseObject):
self.instance.mark_as_deleted(self.user)
else:
# If the class does not provide restorable delete functionality, we must delete the entry finally
self.instance.delete()
class RemoveDeadlineModalForm(RemoveModalForm):
""" Removing modal form for deadlines
Can be used for anything, where removing shall be confirmed by the user a second time.
"""
deadline = None
def __init__(self, *args, **kwargs):
deadline = kwargs.pop("deadline", None)
self.deadline = deadline
super().__init__(*args, **kwargs)
def save(self):
self.instance.remove_deadline(self)

@ -0,0 +1,85 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 15.08.22
"""
import datetime
from django import forms
from django.core.exceptions import ObjectDoesNotExist
from django.db import transaction
from django.utils.translation import gettext_lazy as _
from konova.forms.modals.base_form import BaseModalForm
from konova.models import Resubmission
class ResubmissionModalForm(BaseModalForm):
date = forms.DateField(
label_suffix=_(""),
label=_("Date"),
help_text=_("When do you want to be reminded?"),
widget=forms.DateInput(
attrs={
"type": "date",
"data-provide": "datepicker",
"class": "form-control",
},
format="%d.%m.%Y"
)
)
comment = forms.CharField(
required=False,
label=_("Comment"),
label_suffix=_(""),
help_text=_("Additional comment"),
widget=forms.Textarea(
attrs={
"cols": 30,
"rows": 5,
"class": "form-control",
}
)
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.form_title = _("Resubmission")
self.form_caption = _("Set your resubmission for this entry.")
self.action_url = None
try:
self.resubmission = self.instance.resubmissions.get(
user=self.user
)
self.initialize_form_field("date", str(self.resubmission.resubmit_on))
self.initialize_form_field("comment", self.resubmission.comment)
except ObjectDoesNotExist:
self.resubmission = Resubmission()
def is_valid(self):
super_valid = super().is_valid()
self_valid = True
date = self.cleaned_data.get("date")
today = datetime.date.today()
if date <= today:
self.add_error(
"date",
_("The date should be in the future")
)
self_valid = False
return super_valid and self_valid
def save(self):
with transaction.atomic():
self.resubmission.user = self.user
self.resubmission.resubmit_on = self.cleaned_data.get("date")
self.resubmission.comment = self.cleaned_data.get("comment")
self.resubmission.save()
self.instance.resubmissions.add(self.resubmission)
return self.resubmission

@ -0,0 +1,54 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 15.08.22
"""
from django import forms
from django.db import transaction
from django.utils.translation import gettext_lazy as _
from konova.forms.base_form import BaseForm
from user.models import UserActionLogEntry, User
class RemoveForm(BaseForm):
check = forms.BooleanField(
label=_("Confirm"),
label_suffix=_(""),
required=True,
)
def __init__(self, *args, **kwargs):
self.object_to_remove = kwargs.pop("object_to_remove", None)
self.remove_post_url = kwargs.pop("remove_post_url", "")
self.cancel_url = kwargs.pop("cancel_url", "")
super().__init__(*args, **kwargs)
self.form_title = _("Remove")
if self.object_to_remove is not None:
self.form_caption = _("You are about to remove {} {}").format(self.object_to_remove.__class__.__name__, self.object_to_remove)
self.action_url = self.remove_post_url
self.cancel_redirect = self.cancel_url
def is_checked(self) -> bool:
return self.cleaned_data.get("check", False)
def save(self, user: User):
""" Perform generic removing by running the form typical 'save()' method
Args:
user (User): The performing user
Returns:
"""
if self.object_to_remove is not None and self.is_checked():
with transaction.atomic():
self.object_to_remove.is_active = False
action = UserActionLogEntry.get_deleted_action(user)
self.object_to_remove.deleted = action
self.object_to_remove.save()
return self.object_to_remove

@ -0,0 +1,46 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 15.08.22
"""
import datetime
from compensation.models import Compensation, EcoAccount
from ema.models import Ema
from intervention.models import Intervention
from konova.management.commands.setup import BaseKonovaCommand
from konova.models import Resubmission
class Command(BaseKonovaCommand):
help = "Checks for resubmissions due now"
def handle(self, *args, **options):
try:
resubmitable_models = [
Intervention,
Compensation,
Ema,
EcoAccount,
]
today = datetime.date.today()
resubmissions = Resubmission.objects.filter(
resubmit_on__lte=today,
resubmission_sent=False,
)
self._write_warning(f"Found {resubmissions.count()} resubmission. Process now...")
for model in resubmitable_models:
all_objs = model.objects.filter(
resubmissions__in=resubmissions
)
self._write_warning(f"Process resubmissions for {all_objs.count()} {model.__name__} entries")
for obj in all_objs:
obj.resubmit()
self._write_success("Mails have been sent.")
resubmissions.delete()
self._write_success("Resubmissions have been deleted.")
except KeyboardInterrupt:
self._break_line()
exit(-1)

@ -33,6 +33,7 @@ class Migration(migrations.Migration):
dependencies = [
('konova', '0004_auto_20220209_0839'),
('compensation', '0002_auto_20220114_0936'),
]
operations = [

@ -0,0 +1,33 @@
# Generated by Django 3.1.3 on 2022-08-15 06:03
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import uuid
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('user', '0006_auto_20220815_0759'),
('konova', '0013_auto_20220713_0814'),
]
operations = [
migrations.CreateModel(
name='Resubmission',
fields=[
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('resubmit_on', models.DateField(help_text='On which date the resubmission should be performed')),
('resubmission_sent', models.BooleanField(default=False, help_text='Whether a resubmission has been sent or not')),
('comment', models.TextField(blank=True, help_text='Optional comment for the user itself', null=True)),
('created', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='user.useractionlogentry')),
('modified', models.ForeignKey(blank=True, help_text='Last modified', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='user.useractionlogentry')),
('user', models.ForeignKey(help_text='The user who wants to be notifed', on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
options={
'abstract': False,
},
),
]

@ -10,3 +10,4 @@ from .deadline import *
from .document import *
from .geometry import *
from .parcel import *
from .resubmission import *

@ -743,4 +743,23 @@ class GeoReferencedMixin(models.Model):
zoom_lvl,
x,
y,
)
)
class ResubmitableObjectMixin(models.Model):
resubmissions = models.ManyToManyField(
"konova.Resubmission",
blank=True,
related_name="+",
)
class Meta:
abstract = True
def resubmit(self):
""" Run resubmit check and run for all related resubmissions
"""
resubmissions = self.resubmissions.all()
for resubmission in resubmissions:
resubmission.send_resubmission_mail(self.identifier)

@ -0,0 +1,46 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 15.08.22
"""
from dateutil.utils import today
from django.db import models
from konova.models import BaseResource
from konova.utils.mailer import Mailer
class Resubmission(BaseResource):
user = models.ForeignKey(
"user.User",
on_delete=models.CASCADE,
help_text="The user who wants to be notifed"
)
resubmit_on = models.DateField(
help_text="On which date the resubmission should be performed"
)
resubmission_sent = models.BooleanField(
default=False,
help_text="Whether a resubmission has been sent or not"
)
comment = models.TextField(
null=True,
blank=True,
help_text="Optional comment for the user itself"
)
def send_resubmission_mail(self, obj_identifier):
""" Sends a resubmission mail
"""
_today = today().date()
resubmission_handled = _today.__ge__(self.resubmit_on) and self.resubmission_sent
if resubmission_handled:
return
mailer = Mailer()
mailer.send_mail_resubmission(obj_identifier, self)
self.resubmission_sent = True
self.save()

@ -5,10 +5,9 @@ Contact: michel.peltriaux@sgdnord.rlp.de
Created on: 01.09.21
"""
from django.http import FileResponse, HttpRequest, HttpResponse, Http404
from django.utils.translation import gettext_lazy as _
from django.http import FileResponse, HttpRequest, Http404
from konova.forms import RemoveModalForm
from konova.forms.modals import RemoveModalForm
from konova.models import AbstractDocument
from konova.utils.message_templates import DOCUMENT_REMOVED_TEMPLATE

@ -398,3 +398,26 @@ class Mailer:
msg
)
def send_mail_resubmission(self, obj_identifier, resubmission):
""" Send a resubmission mail for a user
Args:
obj_identifier (str): The (resubmitted) object's identifier
resubmission (Resubmission): The resubmission
Returns:
"""
context = {
"obj_identifier": obj_identifier,
"resubmission": resubmission,
"EMAIL_REPLY_TO": EMAIL_REPLY_TO,
}
msg = render_to_string("email/resubmission/resubmission.html", context)
user_mail_address = [SUPPORT_MAIL_RECIPIENT]
self.send(
user_mail_address,
_("Resubmission - {}").format(obj_identifier),
msg
)

Binary file not shown.

@ -18,15 +18,16 @@
#: konova/filters/mixins.py:277 konova/filters/mixins.py:323
#: konova/filters/mixins.py:361 konova/filters/mixins.py:362
#: konova/filters/mixins.py:393 konova/filters/mixins.py:394
#: konova/forms.py:179 konova/forms.py:281 konova/forms.py:395
#: konova/forms.py:439 konova/forms.py:449 konova/forms.py:462
#: konova/forms.py:474 konova/forms.py:492 user/forms.py:42
#: konova/forms.py:183 konova/forms.py:285 konova/forms.py:399
#: konova/forms.py:443 konova/forms.py:453 konova/forms.py:466
#: konova/forms.py:478 konova/forms.py:496 konova/forms.py:696
#: konova/forms.py:711 user/forms.py:42
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-08-10 08:37+0200\n"
"POT-Creation-Date: 2022-08-15 09:39+0200\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"
@ -85,7 +86,7 @@ msgstr "Bericht generieren"
msgid "Select a timespan and the desired conservation office"
msgstr "Wählen Sie die Zeitspanne und die gewünschte Eintragungsstelle"
#: analysis/forms.py:71 konova/forms.py:227
#: analysis/forms.py:71 konova/forms.py:231
msgid "Continue"
msgstr "Weiter"
@ -241,7 +242,8 @@ msgstr ""
#: ema/templates/ema/detail/includes/states-after.html:36
#: ema/templates/ema/detail/includes/states-before.html:36
#: intervention/forms/modalForms.py:364
#: templates/email/other/deduction_changed.html:29
#: templates/email/other/deduction_changed.html:31
#: templates/email/other/deduction_changed_team.html:31
msgid "Surface"
msgstr "Fläche"
@ -308,7 +310,8 @@ msgstr "Typ"
#: intervention/forms/modalForms.py:382 intervention/tables.py:87
#: intervention/templates/intervention/detail/view.html:19
#: konova/templates/konova/includes/quickstart/interventions.html:4
#: templates/email/other/deduction_changed.html:24
#: templates/email/other/deduction_changed.html:26
#: templates/email/other/deduction_changed_team.html:26
#: templates/navbars/navbar.html:22
msgid "Intervention"
msgstr "Eingriff"
@ -362,7 +365,7 @@ msgstr "Automatisch generiert"
#: intervention/templates/intervention/detail/includes/documents.html:28
#: intervention/templates/intervention/detail/view.html:31
#: intervention/templates/intervention/report/report.html:12
#: konova/forms.py:438
#: konova/forms.py:442
msgid "Title"
msgstr "Bezeichnung"
@ -389,12 +392,13 @@ msgstr "Kompensation XY; Flur ABC"
#: intervention/templates/intervention/detail/includes/documents.html:34
#: intervention/templates/intervention/detail/includes/payments.html:34
#: intervention/templates/intervention/detail/includes/revocation.html:38
#: konova/forms.py:473 konova/templates/konova/includes/comment_card.html:16
#: konova/forms.py:477 konova/forms.py:710
#: konova/templates/konova/includes/comment_card.html:16
msgid "Comment"
msgstr "Kommentar"
#: compensation/forms/forms.py:59 compensation/forms/modalForms.py:471
#: intervention/forms/forms.py:200
#: intervention/forms/forms.py:200 konova/forms.py:712
msgid "Additional comment"
msgstr "Zusätzlicher Kommentar"
@ -479,7 +483,7 @@ msgstr "kompensiert Eingriff"
msgid "Select the intervention for which this compensation compensates"
msgstr "Wählen Sie den Eingriff, für den diese Kompensation bestimmt ist"
#: compensation/forms/forms.py:219 compensation/views/compensation.py:110
#: compensation/forms/forms.py:219 compensation/views/compensation.py:111
msgid "New compensation"
msgstr "Neue Kompensation"
@ -531,7 +535,7 @@ msgid "Due on which date"
msgstr "Zahlung wird an diesem Datum erwartet"
#: compensation/forms/modalForms.py:65 compensation/forms/modalForms.py:363
#: intervention/forms/modalForms.py:177 konova/forms.py:475
#: intervention/forms/modalForms.py:177 konova/forms.py:479
msgid "Additional comment, maximum {} letters"
msgstr "Zusätzlicher Kommentar, maximal {} Zeichen"
@ -576,7 +580,7 @@ msgstr "Neuer Zustand"
msgid "Insert data for the new state"
msgstr "Geben Sie die Daten des neuen Zustandes ein"
#: compensation/forms/modalForms.py:219 konova/forms.py:229
#: compensation/forms/modalForms.py:219 konova/forms.py:233
msgid "Object removed"
msgstr "Objekt entfernt"
@ -602,7 +606,7 @@ msgstr "Fristart wählen"
#: compensation/templates/compensation/detail/compensation/includes/deadlines.html:36
#: compensation/templates/compensation/detail/eco_account/includes/deadlines.html:36
#: ema/templates/ema/detail/includes/deadlines.html:36
#: intervention/forms/modalForms.py:149
#: intervention/forms/modalForms.py:149 konova/forms.py:697
msgid "Date"
msgstr "Datum"
@ -850,24 +854,32 @@ msgstr "In LANIS öffnen"
msgid "Public report"
msgstr "Öffentlicher Bericht"
#: compensation/templates/compensation/detail/compensation/includes/controls.html:17
#: compensation/templates/compensation/detail/eco_account/includes/controls.html:31
#: ema/templates/ema/detail/includes/controls.html:31
#: intervention/templates/intervention/detail/includes/controls.html:36
#: compensation/templates/compensation/detail/compensation/includes/controls.html:15
#: compensation/templates/compensation/detail/eco_account/includes/controls.html:15
#: ema/templates/ema/detail/includes/controls.html:15
#: intervention/templates/intervention/detail/includes/controls.html:15
#: konova/forms.py:724 templates/email/resubmission/resubmission.html:4
msgid "Resubmission"
msgstr "Wiedervorlage"
#: compensation/templates/compensation/detail/compensation/includes/controls.html:20
#: compensation/templates/compensation/detail/eco_account/includes/controls.html:34
#: ema/templates/ema/detail/includes/controls.html:34
#: intervention/templates/intervention/detail/includes/controls.html:39
msgid "Edit"
msgstr "Bearbeiten"
#: compensation/templates/compensation/detail/compensation/includes/controls.html:21
#: compensation/templates/compensation/detail/eco_account/includes/controls.html:35
#: ema/templates/ema/detail/includes/controls.html:35
#: intervention/templates/intervention/detail/includes/controls.html:40
msgid "Show log"
msgstr "Log anzeigen"
#: compensation/templates/compensation/detail/compensation/includes/controls.html:24
#: compensation/templates/compensation/detail/eco_account/includes/controls.html:38
#: ema/templates/ema/detail/includes/controls.html:38
#: intervention/templates/intervention/detail/includes/controls.html:43
msgid "Show log"
msgstr "Log anzeigen"
#: compensation/templates/compensation/detail/compensation/includes/controls.html:27
#: compensation/templates/compensation/detail/eco_account/includes/controls.html:41
#: ema/templates/ema/detail/includes/controls.html:41
#: intervention/templates/intervention/detail/includes/controls.html:46
#: venv/lib/python3.7/site-packages/django/forms/formsets.py:391
msgid "Delete"
msgstr "Löschen"
@ -907,7 +919,7 @@ msgstr "Dokumente"
#: compensation/templates/compensation/detail/eco_account/includes/documents.html:14
#: ema/templates/ema/detail/includes/documents.html:14
#: intervention/templates/intervention/detail/includes/documents.html:14
#: konova/forms.py:491
#: konova/forms.py:495
msgid "Add new document"
msgstr "Neues Dokument hinzufügen"
@ -915,7 +927,7 @@ msgstr "Neues Dokument hinzufügen"
#: compensation/templates/compensation/detail/eco_account/includes/documents.html:31
#: ema/templates/ema/detail/includes/documents.html:31
#: intervention/templates/intervention/detail/includes/documents.html:31
#: konova/forms.py:448
#: konova/forms.py:452
msgid "Created on"
msgstr "Erstellt"
@ -923,7 +935,7 @@ msgstr "Erstellt"
#: compensation/templates/compensation/detail/eco_account/includes/documents.html:61
#: ema/templates/ema/detail/includes/documents.html:61
#: intervention/templates/intervention/detail/includes/documents.html:65
#: konova/forms.py:553
#: konova/forms.py:557
msgid "Edit document"
msgstr "Dokument bearbeiten"
@ -1093,22 +1105,22 @@ msgstr ""
msgid "other users"
msgstr "weitere Nutzer"
#: compensation/templates/compensation/detail/eco_account/includes/controls.html:15
#: ema/templates/ema/detail/includes/controls.html:15
#: compensation/templates/compensation/detail/eco_account/includes/controls.html:18
#: ema/templates/ema/detail/includes/controls.html:18
#: intervention/forms/modalForms.py:71
#: intervention/templates/intervention/detail/includes/controls.html:15
#: intervention/templates/intervention/detail/includes/controls.html:18
msgid "Share"
msgstr "Freigabe"
#: compensation/templates/compensation/detail/eco_account/includes/controls.html:20
#: ema/templates/ema/detail/includes/controls.html:20
#: intervention/templates/intervention/detail/includes/controls.html:25
#: compensation/templates/compensation/detail/eco_account/includes/controls.html:23
#: ema/templates/ema/detail/includes/controls.html:23
#: intervention/templates/intervention/detail/includes/controls.html:28
msgid "Unrecord"
msgstr "Entzeichnen"
#: compensation/templates/compensation/detail/eco_account/includes/controls.html:24
#: ema/templates/ema/detail/includes/controls.html:24
#: intervention/templates/intervention/detail/includes/controls.html:29
#: compensation/templates/compensation/detail/eco_account/includes/controls.html:27
#: ema/templates/ema/detail/includes/controls.html:27
#: intervention/templates/intervention/detail/includes/controls.html:32
msgid "Record"
msgstr "Verzeichnen"
@ -1215,29 +1227,34 @@ msgstr ""
msgid "Responsible data"
msgstr "Daten zu den verantwortlichen Stellen"
#: compensation/views/compensation.py:53
#: compensation/views/compensation.py:54
msgid "Compensations - Overview"
msgstr "Kompensationen - Übersicht"
#: compensation/views/compensation.py:172 konova/utils/message_templates.py:36
#: compensation/views/compensation.py:173 konova/utils/message_templates.py:36
msgid "Compensation {} edited"
msgstr "Kompensation {} bearbeitet"
#: compensation/views/compensation.py:182 compensation/views/eco_account.py:173
#: compensation/views/compensation.py:183 compensation/views/eco_account.py:173
#: ema/views.py:241 intervention/views.py:338
msgid "Edit {}"
msgstr "Bearbeite {}"
#: compensation/views/compensation.py:269 compensation/views/eco_account.py:360
#: ema/views.py:195 intervention/views.py:542
#: compensation/views/compensation.py:270 compensation/views/eco_account.py:360
#: ema/views.py:195 intervention/views.py:565
msgid "Log"
msgstr "Log"
#: compensation/views/compensation.py:613 compensation/views/eco_account.py:728
#: ema/views.py:559 intervention/views.py:688
#: compensation/views/compensation.py:614 compensation/views/eco_account.py:728
#: ema/views.py:559 intervention/views.py:711
msgid "Report {}"
msgstr "Bericht {}"
#: compensation/views/compensation.py:680 compensation/views/eco_account.py:862
#: ema/views.py:734 intervention/views.py:496
msgid "Resubmission set"
msgstr "Wiedervorlage gesetzt"
#: compensation/views/eco_account.py:65
msgid "Eco-account - Overview"
msgstr "Ökokonten - Übersicht"
@ -1255,12 +1272,12 @@ msgid "Eco-account removed"
msgstr "Ökokonto entfernt"
#: compensation/views/eco_account.py:381 ema/views.py:283
#: intervention/views.py:641
#: intervention/views.py:664
msgid "{} unrecorded"
msgstr "{} entzeichnet"
#: compensation/views/eco_account.py:381 ema/views.py:283
#: intervention/views.py:641
#: intervention/views.py:664
msgid "{} recorded"
msgstr "{} verzeichnet"
@ -1462,11 +1479,11 @@ msgid "Checked compensations data and payments"
msgstr "Kompensationen und Zahlungen geprüft"
#: intervention/forms/modalForms.py:263
#: intervention/templates/intervention/detail/includes/controls.html:19
#: intervention/templates/intervention/detail/includes/controls.html:22
msgid "Run check"
msgstr "Prüfung vornehmen"
#: intervention/forms/modalForms.py:264 konova/forms.py:594
#: intervention/forms/modalForms.py:264 konova/forms.py:598
msgid ""
"I, {} {}, confirm that all necessary control steps have been performed by "
"myself."
@ -1622,11 +1639,11 @@ msgstr "Eingriff {} bearbeitet"
msgid "{} removed"
msgstr "{} entfernt"
#: intervention/views.py:495
#: intervention/views.py:518
msgid "Check performed"
msgstr "Prüfung durchgeführt"
#: intervention/views.py:646
#: intervention/views.py:669
msgid "There are errors on this intervention:"
msgstr "Es liegen Fehler in diesem Eingriff vor:"
@ -1711,78 +1728,90 @@ msgstr "Nach Zulassungsbehörde suchen"
msgid "Search for conservation office"
msgstr "Nch Eintragungsstelle suchen"
#: konova/forms.py:41 templates/form/collapsable/form.html:62
#: konova/forms.py:44 templates/form/collapsable/form.html:62
msgid "Save"
msgstr "Speichern"
#: konova/forms.py:75
#: konova/forms.py:78
msgid "Not editable"
msgstr "Nicht editierbar"
#: konova/forms.py:178 konova/forms.py:394
#: konova/forms.py:182 konova/forms.py:398
msgid "Confirm"
msgstr "Bestätige"
#: konova/forms.py:190 konova/forms.py:403
#: konova/forms.py:194 konova/forms.py:407
msgid "Remove"
msgstr "Löschen"
#: konova/forms.py:192
#: konova/forms.py:196
msgid "You are about to remove {} {}"
msgstr "Sie sind dabei {} {} zu löschen"
#: konova/forms.py:280 konova/utils/quality.py:44 konova/utils/quality.py:46
#: konova/forms.py:284 konova/utils/quality.py:44 konova/utils/quality.py:46
#: templates/form/collapsable/form.html:45
msgid "Geometry"
msgstr "Geometrie"
#: konova/forms.py:331
#: konova/forms.py:335
msgid "Only surfaces allowed. Points or lines must be buffered."
msgstr ""
"Nur Flächen erlaubt. Punkte oder Linien müssen zu Flächen gepuffert werden."
#: konova/forms.py:404
#: konova/forms.py:408
msgid "Are you sure?"
msgstr "Sind Sie sicher?"
#: konova/forms.py:450
#: konova/forms.py:454
msgid "When has this file been created? Important for photos."
msgstr "Wann wurde diese Datei erstellt oder das Foto aufgenommen?"
#: konova/forms.py:461
#: konova/forms.py:465
#: venv/lib/python3.7/site-packages/django/db/models/fields/files.py:231
msgid "File"
msgstr "Datei"
#: konova/forms.py:463
#: konova/forms.py:467
msgid "Allowed formats: pdf, jpg, png. Max size 15 MB."
msgstr "Formate: pdf, jpg, png. Maximal 15 MB."
#: konova/forms.py:528
#: konova/forms.py:532
msgid "Added document"
msgstr "Dokument hinzugefügt"
#: konova/forms.py:585
#: konova/forms.py:589
msgid "Confirm record"
msgstr "Verzeichnen bestätigen"
#: konova/forms.py:593
#: konova/forms.py:597
msgid "Record data"
msgstr "Daten verzeichnen"
#: konova/forms.py:600
#: konova/forms.py:604
msgid "Confirm unrecord"
msgstr "Entzeichnen bestätigen"
#: konova/forms.py:601
#: konova/forms.py:605
msgid "Unrecord data"
msgstr "Daten entzeichnen"
#: konova/forms.py:602
#: konova/forms.py:606
msgid "I, {} {}, confirm that this data must be unrecorded."
msgstr ""
"Ich, {} {}, bestätige, dass diese Daten wieder entzeichnet werden müssen."
#: konova/forms.py:698
msgid "When do you want to be reminded?"
msgstr "Wann wollen Sie erinnert werden?"
#: konova/forms.py:725
msgid "Set your resubmission for this entry."
msgstr "Setzen Sie eine Wiedervorlage für diesen Eintrag."
#: konova/forms.py:746
msgid "The date should be in the future"
msgstr "Das Datum sollte in der Zukunft liegen"
#: konova/management/commands/setup_data.py:26
msgid "On shared access gained"
msgstr "Wenn mir eine Freigabe zu Daten erteilt wird"
@ -1929,7 +1958,7 @@ msgstr "{} - Freigegebene Daten verzeichnet"
msgid "{} - Shared data checked"
msgstr "{} - Freigegebene Daten geprüft"
#: konova/utils/mailer.py:233 konova/utils/mailer.py:372
#: konova/utils/mailer.py:233 konova/utils/mailer.py:376
msgid "{} - Deduction changed"
msgstr "{} - Abbuchung geändert"
@ -1937,10 +1966,14 @@ msgstr "{} - Abbuchung geändert"
msgid "{} - Shared data deleted"
msgstr "{} - Freigegebene Daten gelöscht"
#: konova/utils/mailer.py:393 templates/email/api/verify_token.html:4
#: konova/utils/mailer.py:397 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
msgid "Resubmission - {}"
msgstr "Wiedervorlage - {}"
#: konova/utils/message_templates.py:10
msgid "no further details"
msgstr "keine weitere Angabe"
@ -2223,11 +2256,11 @@ msgstr "Irgendetwas ist passiert. Wir arbeiten daran!"
msgid "Hello support"
msgstr "Hallo Support"
#: templates/email/api/verify_token.html:9
#: templates/email/api/verify_token.html:10
msgid "you need to verify the API token for user"
msgstr "Sie müssen einen API Token für folgenden Nutzer freischalten"
#: templates/email/api/verify_token.html:15
#: templates/email/api/verify_token.html:16
msgid ""
"If unsure, please contact the user. The API token can not be used until you "
"activated it in the admin backend."
@ -2236,20 +2269,22 @@ msgstr ""
"Token kann so lange nicht verwendet werden, wie er noch nicht von Ihnen im "
"Admin Backend aktiviert worden ist."
#: templates/email/api/verify_token.html:18
#: templates/email/checking/shared_data_checked.html:19
#: templates/email/checking/shared_data_checked_team.html:19
#: templates/email/deleting/shared_data_deleted.html:19
#: templates/email/deleting/shared_data_deleted_team.html:19
#: templates/email/other/deduction_changed.html:38
#: templates/email/recording/shared_data_recorded.html:19
#: templates/email/recording/shared_data_recorded_team.html:19
#: templates/email/recording/shared_data_unrecorded.html:19
#: templates/email/recording/shared_data_unrecorded_team.html:19
#: templates/email/sharing/shared_access_given.html:20
#: templates/email/sharing/shared_access_given_team.html:20
#: templates/email/sharing/shared_access_removed.html:20
#: templates/email/sharing/shared_access_removed_team.html:20
#: templates/email/api/verify_token.html:19
#: templates/email/checking/shared_data_checked.html:20
#: templates/email/checking/shared_data_checked_team.html:20
#: templates/email/deleting/shared_data_deleted.html:20
#: templates/email/deleting/shared_data_deleted_team.html:20
#: templates/email/other/deduction_changed.html:41
#: templates/email/other/deduction_changed_team.html:41
#: templates/email/recording/shared_data_recorded.html:20
#: templates/email/recording/shared_data_recorded_team.html:20
#: templates/email/recording/shared_data_unrecorded.html:20
#: templates/email/recording/shared_data_unrecorded_team.html:20
#: templates/email/resubmission/resubmission.html:21
#: templates/email/sharing/shared_access_given.html:21
#: templates/email/sharing/shared_access_given_team.html:21
#: templates/email/sharing/shared_access_removed.html:21
#: templates/email/sharing/shared_access_removed_team.html:21
msgid "Best regards"
msgstr "Beste Grüße"
@ -2263,18 +2298,19 @@ msgstr "Freigegebene Daten geprüft"
#: templates/email/other/deduction_changed.html:8
#: templates/email/recording/shared_data_recorded.html:8
#: templates/email/recording/shared_data_unrecorded.html:8
#: templates/email/resubmission/resubmission.html:8
#: templates/email/sharing/shared_access_given.html:8
#: templates/email/sharing/shared_access_removed.html:8
msgid "Hello "
msgstr "Hallo "
#: templates/email/checking/shared_data_checked.html:10
#: templates/email/checking/shared_data_checked_team.html:10
#: templates/email/checking/shared_data_checked.html:11
#: templates/email/checking/shared_data_checked_team.html:11
msgid "the following dataset has just been checked"
msgstr "der folgende Datensatz wurde soeben geprüft "
#: templates/email/checking/shared_data_checked.html:16
#: templates/email/checking/shared_data_checked_team.html:16
#: templates/email/checking/shared_data_checked.html:17
#: templates/email/checking/shared_data_checked_team.html:17
msgid ""
"This means, the responsible registration office just confirmed the "
"correctness of this dataset."
@ -2284,6 +2320,7 @@ msgstr ""
#: templates/email/checking/shared_data_checked_team.html:8
#: templates/email/deleting/shared_data_deleted_team.html:8
#: templates/email/other/deduction_changed_team.html:8
#: templates/email/recording/shared_data_recorded_team.html:8
#: templates/email/recording/shared_data_unrecorded_team.html:8
#: templates/email/sharing/shared_access_given_team.html:8
@ -2296,14 +2333,15 @@ msgstr "Hallo Team"
msgid "Shared data deleted"
msgstr "Freigegebene Daten gelöscht"
#: templates/email/deleting/shared_data_deleted.html:10
#: templates/email/deleting/shared_data_deleted_team.html:10
#: templates/email/deleting/shared_data_deleted.html:11
#: templates/email/deleting/shared_data_deleted_team.html:11
msgid "the following dataset has just been deleted"
msgstr "der folgende Datensatz wurde soeben gelöscht "
#: templates/email/deleting/shared_data_deleted.html:16
#: templates/email/deleting/shared_data_deleted_team.html:16
#: templates/email/other/deduction_changed.html:35
#: templates/email/deleting/shared_data_deleted.html:17
#: templates/email/deleting/shared_data_deleted_team.html:17
#: templates/email/other/deduction_changed.html:38
#: templates/email/other/deduction_changed_team.html:38
msgid ""
"If this should not have been happened, please contact us. See the signature "
"for details."
@ -2312,27 +2350,33 @@ msgstr ""
"mail Signatur finden Sie weitere Kontaktinformationen."
#: templates/email/other/deduction_changed.html:4
#: templates/email/other/deduction_changed_team.html:4
msgid "Deduction changed"
msgstr "Abbuchung geändert"
#: templates/email/other/deduction_changed.html:10
#: templates/email/other/deduction_changed.html:11
#: templates/email/other/deduction_changed_team.html:11
msgid "a deduction of this eco account has changed:"
msgstr "eine Abbuchung des Ökokontos hat sich geändert:"
#: templates/email/other/deduction_changed.html:14
#: templates/email/other/deduction_changed.html:16
#: templates/email/other/deduction_changed_team.html:16
msgid "Attribute"
msgstr "Attribute"
#: templates/email/other/deduction_changed.html:15
#: templates/email/other/deduction_changed.html:17
#: templates/email/other/deduction_changed_team.html:17
msgid "Old"
msgstr "Alt"
#: templates/email/other/deduction_changed.html:16
#: templates/email/other/deduction_changed.html:18
#: templates/email/other/deduction_changed_team.html:18
#: templates/generic_index.html:43 user/templates/user/team/index.html:22
msgid "New"
msgstr "Neu"
#: templates/email/other/deduction_changed.html:19
#: templates/email/other/deduction_changed.html:21
#: templates/email/other/deduction_changed_team.html:21
msgid "EcoAccount"
msgstr "Ökokonto"
@ -2341,19 +2385,19 @@ msgstr "Ökokonto"
msgid "Shared data recorded"
msgstr "Freigegebene Daten verzeichnet"
#: templates/email/recording/shared_data_recorded.html:10
#: templates/email/recording/shared_data_recorded_team.html:10
#: templates/email/recording/shared_data_recorded.html:11
#: templates/email/recording/shared_data_recorded_team.html:11
msgid "the following dataset has just been recorded"
msgstr "der folgende Datensatz wurde soeben verzeichnet "
#: templates/email/recording/shared_data_recorded.html:16
#: templates/email/recording/shared_data_recorded_team.html:16
#: templates/email/recording/shared_data_recorded.html:17
#: templates/email/recording/shared_data_recorded_team.html:17
msgid "This means the data is now publicly available, e.g. in LANIS"
msgstr ""
"Das bedeutet, dass die Daten nun öffentlich verfügbar sind, z.B. im LANIS."
#: templates/email/recording/shared_data_recorded.html:26
#: templates/email/recording/shared_data_recorded_team.html:26
#: templates/email/recording/shared_data_recorded.html:27
#: templates/email/recording/shared_data_recorded_team.html:27
msgid ""
"Please note: Recorded intervention means the compensations are recorded as "
"well."
@ -2366,18 +2410,18 @@ msgstr ""
msgid "Shared data unrecorded"
msgstr "Freigegebene Daten entzeichnet"
#: templates/email/recording/shared_data_unrecorded.html:10
#: templates/email/recording/shared_data_unrecorded_team.html:10
#: templates/email/recording/shared_data_unrecorded.html:11
#: templates/email/recording/shared_data_unrecorded_team.html:11
msgid "the following dataset has just been unrecorded"
msgstr "der folgende Datensatz wurde soeben entzeichnet "
#: templates/email/recording/shared_data_unrecorded.html:16
#: templates/email/recording/shared_data_unrecorded_team.html:16
#: templates/email/recording/shared_data_unrecorded.html:17
#: templates/email/recording/shared_data_unrecorded_team.html:17
msgid "This means the data is no longer publicly available."
msgstr "Das bedeutet, dass die Daten nicht länger öffentlich verfügbar sind."
#: templates/email/recording/shared_data_unrecorded.html:26
#: templates/email/recording/shared_data_unrecorded_team.html:26
#: templates/email/recording/shared_data_unrecorded.html:27
#: templates/email/recording/shared_data_unrecorded_team.html:27
msgid ""
"Please note: Unrecorded intervention means the compensations are unrecorded "
"as well."
@ -2385,22 +2429,30 @@ msgstr ""
"Bitte beachten Sie: Entzeichnete Eingriffe bedeuten, dass auch die "
"zugehörigen Kompensationen automatisch entzeichnet worden sind."
#: templates/email/resubmission/resubmission.html:11
msgid "you wanted to be reminded on this entry."
msgstr "Sie wollten an diesen Eintrag erinnert werden."
#: templates/email/resubmission/resubmission.html:15
msgid "Your personal comment:"
msgstr "Ihr Kommentar:"
#: templates/email/sharing/shared_access_given.html:4
#: templates/email/sharing/shared_access_given_team.html:4
msgid "Access shared"
msgstr "Zugriff freigegeben"
#: templates/email/sharing/shared_access_given.html:10
#: templates/email/sharing/shared_access_given.html:11
msgid "the following dataset has just been shared with you"
msgstr "der folgende Datensatz wurde soeben für Sie freigegeben "
#: templates/email/sharing/shared_access_given.html:16
#: templates/email/sharing/shared_access_given_team.html:16
#: templates/email/sharing/shared_access_given.html:17
#: templates/email/sharing/shared_access_given_team.html:17
msgid "This means you can now edit this dataset."
msgstr "Das bedeutet, dass Sie diesen Datensatz nun auch bearbeiten können."
#: templates/email/sharing/shared_access_given.html:17
#: templates/email/sharing/shared_access_given_team.html:17
#: templates/email/sharing/shared_access_given.html:18
#: templates/email/sharing/shared_access_given_team.html:18
msgid ""
"The shared dataset appears now by default on your overview for this dataset "
"type."
@ -2408,8 +2460,8 @@ msgstr ""
"Der freigegebene Datensatz ist nun standardmäßig in Ihrer Übersicht für den "
"Datensatztyp im KSP gelistet."
#: templates/email/sharing/shared_access_given.html:27
#: templates/email/sharing/shared_access_given_team.html:27
#: templates/email/sharing/shared_access_given.html:28
#: templates/email/sharing/shared_access_given_team.html:28
msgid ""
"Please note: Shared access on an intervention means you automatically have "
"editing access to related compensations."
@ -2418,7 +2470,7 @@ msgstr ""
"Sie automatisch auch Zugriff auf die zugehörigen Kompensationen erhalten "
"haben."
#: templates/email/sharing/shared_access_given_team.html:10
#: templates/email/sharing/shared_access_given_team.html:11
msgid "the following dataset has just been shared with your team"
msgstr "der folgende Datensatz wurde soeben für Ihr Team freigegeben "
@ -2427,20 +2479,20 @@ msgstr "der folgende Datensatz wurde soeben für Ihr Team freigegeben "
msgid "Shared access removed"
msgstr "Freigegebener Zugriff entzogen"
#: templates/email/sharing/shared_access_removed.html:10
#: templates/email/sharing/shared_access_removed.html:11
msgid ""
"your shared access, including editing, has been revoked for the dataset "
msgstr ""
"Ihnen wurde soeben der bearbeitende Zugriff auf den folgenden Datensatz "
"entzogen: "
#: templates/email/sharing/shared_access_removed.html:16
#: templates/email/sharing/shared_access_removed_team.html:16
#: templates/email/sharing/shared_access_removed.html:17
#: templates/email/sharing/shared_access_removed_team.html:17
msgid "However, you are still able to view the dataset content."
msgstr "Sie können den Datensatz aber immer noch im KSP einsehen."
#: templates/email/sharing/shared_access_removed.html:17
#: templates/email/sharing/shared_access_removed_team.html:17
#: templates/email/sharing/shared_access_removed.html:18
#: templates/email/sharing/shared_access_removed_team.html:18
msgid ""
"Please use the provided search filter on the dataset`s overview pages to "
"find them."
@ -2448,7 +2500,7 @@ msgstr ""
"Nutzen Sie hierzu einfach die entsprechenden Suchfilter auf den "
"Übersichtsseiten"
#: templates/email/sharing/shared_access_removed_team.html:10
#: templates/email/sharing/shared_access_removed_team.html:11
msgid ""
"your teams shared access, including editing, has been revoked for the "
"dataset "

@ -0,0 +1,29 @@
{% load i18n %}
<div>
<h2>{% trans 'Resubmission' %}</h2>
<h4>{{obj_identifier}}</h4>
<hr>
<article>
{% trans 'Hello ' %} {{resubmission.user.username}},
<br>
<br>
{% trans 'you wanted to be reminded on this entry.' %}
<br>
{% if resubmission.comment %}
<br>
{% trans 'Your personal comment:' %}
<br>
<article style="font: italic">"{{resubmission.comment}}"</article>
{% endif %}
<br>
<br>
{% trans 'Best regards' %}
<br>
KSP
<br>
<br>
{% include 'email/signature.html' %}
</article>
</div>

@ -15,7 +15,8 @@ from api.models import APIUserToken
from intervention.inputs import GenerateInput
from user.models import User, UserNotification, Team
from konova.forms import BaseForm, BaseModalForm, RemoveModalForm
from konova.forms.modals import BaseModalForm, RemoveModalForm
from konova.forms import BaseForm
class UserNotificationForm(BaseForm):

@ -0,0 +1,18 @@
# Generated by Django 3.1.3 on 2022-08-15 05:59
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('user', '0005_team_deleted'),
]
operations = [
migrations.AlterField(
model_name='usernotification',
name='id',
field=models.CharField(choices=[('NOTIFY_ON_SHARED_ACCESS_REMOVED', 'NOTIFY_ON_SHARED_ACCESS_REMOVED'), ('NOTIFY_ON_SHARED_DATA_RECORDED', 'NOTIFY_ON_SHARED_DATA_RECORDED'), ('NOTIFY_ON_SHARED_DATA_DELETED', 'NOTIFY_ON_SHARED_DATA_DELETED'), ('NOTIFY_ON_SHARED_DATA_CHECKED', 'NOTIFY_ON_SHARED_DATA_CHECKED'), ('NOTIFY_ON_SHARED_ACCESS_GAINED', 'NOTIFY_ON_SHARED_ACCESS_GAINED'), ('NOTIFY_ON_DEDUCTION_CHANGES', 'NOTIFY_ON_DEDUCTION_CHANGES')], max_length=500, primary_key=True, serialize=False),
),
]
Loading…
Cancel
Save