Merge pull request 'Docker Update' (#199) from master into Docker
Reviewed-on: SGD-Nord/konova#199
This commit is contained in:
commit
0a241305d3
@ -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
|
||||
|
24
compensation/migrations/0008_auto_20220815_0803.py
Normal file
24
compensation/migrations/0008_auto_20220815_0803.py
Normal file
@ -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'),
|
||||
),
|
||||
]
|
32
compensation/migrations/0009_auto_20220815_0803.py
Normal file
32
compensation/migrations/0009_auto_20220815_0803.py
Normal file
@ -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'),
|
||||
),
|
||||
]
|
24
compensation/migrations/0010_auto_20220815_1030.py
Normal file
24
compensation/migrations/0010_auto_20220815_1030.py
Normal file
@ -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'),
|
||||
),
|
||||
]
|
@ -14,21 +14,22 @@ from user.models import User, Team
|
||||
from django.db import models, transaction
|
||||
from django.db.models import QuerySet, Sum
|
||||
from django.http import HttpRequest
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
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
|
||||
from konova.settings import DEFAULT_SRID_RLP, LANIS_LINK_TEMPLATE
|
||||
GeoReferencedMixin, DeadlineType, ResubmitableObjectMixin
|
||||
from konova.utils.message_templates import DATA_UNSHARED_EXPLANATION, COMPENSATION_REMOVED_TEMPLATE, \
|
||||
DOCUMENT_REMOVED_TEMPLATE, COMPENSATION_EDITED_TEMPLATE, DEADLINE_REMOVED, ADDED_DEADLINE, \
|
||||
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.
|
||||
@ -226,6 +227,15 @@ class AbstractCompensation(BaseObject, GeoReferencedMixin):
|
||||
request = self.set_geometry_conflict_message(request)
|
||||
return request
|
||||
|
||||
def get_finished_deadlines(self):
|
||||
""" Getter for FINISHED-deadlines
|
||||
|
||||
Returns:
|
||||
queryset (QuerySet): The finished deadlines
|
||||
"""
|
||||
return self.deadlines.filter(
|
||||
type=DeadlineType.FINISHED
|
||||
)
|
||||
|
||||
class CEFMixin(models.Model):
|
||||
""" Provides CEF flag as Mixin
|
||||
|
@ -21,6 +21,7 @@ from compensation.models.compensation import AbstractCompensation, PikMixin
|
||||
from compensation.utils.quality import EcoAccountQualityChecker
|
||||
from konova.models import ShareableObjectMixin, RecordableObjectMixin, AbstractDocument, BaseResource, \
|
||||
generate_document_file_upload_path
|
||||
from konova.tasks import celery_send_mail_deduction_changed, celery_send_mail_deduction_changed_team
|
||||
|
||||
|
||||
class EcoAccount(AbstractCompensation, ShareableObjectMixin, RecordableObjectMixin, PikMixin):
|
||||
@ -161,6 +162,25 @@ class EcoAccount(AbstractCompensation, ShareableObjectMixin, RecordableObjectMix
|
||||
"""
|
||||
return reverse("compensation:acc:share", args=(self.id, self.access_token))
|
||||
|
||||
def send_notification_mail_on_deduction_change(self, data_change: dict):
|
||||
""" Sends notification mails for changes on the deduction
|
||||
|
||||
Args:
|
||||
data_change ():
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
# Send mail
|
||||
shared_users = self.shared_users.values_list("id", flat=True)
|
||||
for user_id in shared_users:
|
||||
celery_send_mail_deduction_changed.delay(self.identifier, self.title, user_id, data_change)
|
||||
|
||||
# Send mail
|
||||
shared_teams = self.shared_teams.values_list("id", flat=True)
|
||||
for team_id in shared_teams:
|
||||
celery_send_mail_deduction_changed_team.delay(self.identifier, self.title, team_id, data_change)
|
||||
|
||||
|
||||
class EcoAccountDocument(AbstractDocument):
|
||||
"""
|
||||
|
@ -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' %}">
|
||||
|
@ -20,6 +20,11 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% if not has_finished_deadlines %}
|
||||
<div class="alert alert-danger mb-0">
|
||||
{% trans 'Missing finished deadline ' %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="card-body scroll-300 p-2">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
|
@ -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>
|
||||
|
@ -20,6 +20,11 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% if not has_finished_deadlines %}
|
||||
<div class="alert alert-danger mb-0">
|
||||
{% trans 'Missing finished deadline ' %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="card-body scroll-300 p-2">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
|
@ -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'),
|
||||
|
@ -19,6 +19,7 @@ class CompensationQualityChecker(AbstractQualityChecker):
|
||||
self._check_states()
|
||||
self._check_actions()
|
||||
self._check_geometry()
|
||||
self._check_deadlines()
|
||||
self.valid = len(self.messages) == 0
|
||||
|
||||
def _check_states(self):
|
||||
@ -47,6 +48,16 @@ class CompensationQualityChecker(AbstractQualityChecker):
|
||||
if not self.obj.actions.all():
|
||||
self._add_missing_attr_name(_con("Compensation", "Actions"))
|
||||
|
||||
def _check_deadlines(self):
|
||||
""" Checks data quality for related Deadline objects
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
finished_deadlines = self.obj.get_finished_deadlines()
|
||||
if not finished_deadlines.exists():
|
||||
self._add_missing_attr_name(_("Finished deadlines"))
|
||||
|
||||
|
||||
class EcoAccountQualityChecker(CompensationQualityChecker):
|
||||
def run_check(self):
|
||||
|
@ -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
|
||||
@ -240,6 +242,7 @@ def detail_view(request: HttpRequest, id: str):
|
||||
"is_ets_member": in_group(_user, ETS_GROUP),
|
||||
"LANIS_LINK": comp.get_LANIS_link(),
|
||||
TAB_TITLE_IDENTIFIER: f"{comp.identifier} - {comp.title}",
|
||||
"has_finished_deadlines": comp.get_finished_deadlines().exists(),
|
||||
}
|
||||
context = BaseContext(request, context).context
|
||||
return render(request, template, context)
|
||||
@ -655,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, \
|
||||
@ -242,6 +243,7 @@ def detail_view(request: HttpRequest, id: str):
|
||||
"deductions": deductions,
|
||||
"actions": actions,
|
||||
TAB_TITLE_IDENTIFIER: f"{acc.identifier} - {acc.title}",
|
||||
"has_finished_deadlines": acc.get_finished_deadlines().exists(),
|
||||
}
|
||||
context = BaseContext(request, context).context
|
||||
return render(request, template, context)
|
||||
@ -838,3 +840,26 @@ def create_share_view(request: HttpRequest, id: str):
|
||||
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
|
||||
|
||||
|
||||
|
19
ema/migrations/0005_ema_resubmission.py
Normal file
19
ema/migrations/0005_ema_resubmission.py
Normal file
@ -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'),
|
||||
),
|
||||
]
|
23
ema/migrations/0006_auto_20220815_0803.py
Normal file
23
ema/migrations/0006_auto_20220815_0803.py
Normal file
@ -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'),
|
||||
),
|
||||
]
|
19
ema/migrations/0007_auto_20220815_1030.py
Normal file
19
ema/migrations/0007_auto_20220815_1030.py
Normal file
@ -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>
|
||||
|
@ -20,6 +20,11 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% if not has_finished_deadlines %}
|
||||
<div class="alert alert-danger mb-0">
|
||||
{% trans 'Missing finished deadline ' %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="card-body scroll-300 p-2">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
|
@ -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'),
|
||||
|
29
ema/views.py
29
ema/views.py
@ -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
|
||||
@ -166,6 +167,7 @@ def detail_view(request: HttpRequest, id: str):
|
||||
"is_ets_member": in_group(_user, ETS_GROUP),
|
||||
"LANIS_LINK": ema.get_LANIS_link(),
|
||||
TAB_TITLE_IDENTIFIER: f"{ema.identifier} - {ema.title}",
|
||||
"has_finished_deadlines": ema.get_finished_deadlines().exists(),
|
||||
}
|
||||
context = BaseContext(request, context).context
|
||||
return render(request, template, context)
|
||||
@ -710,3 +712,26 @@ def deadline_remove_view(request: HttpRequest, id: str, deadline_id: str):
|
||||
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
|
||||
|
||||
@ -508,28 +509,44 @@ class EditEcoAccountDeductionModalForm(NewDeductionModalForm):
|
||||
deduction = self.deduction
|
||||
form_account = self.cleaned_data.get("account", None)
|
||||
form_intervention = self.cleaned_data.get("intervention", None)
|
||||
current_account = deduction.account
|
||||
current_intervention = deduction.intervention
|
||||
|
||||
old_account = deduction.account
|
||||
old_intervention = deduction.intervention
|
||||
old_surface = deduction.surface
|
||||
|
||||
# If account or intervention has been changed, we put that change in the logs just as if the deduction has
|
||||
# been removed for this entry. Act as if the deduction is newly created for the new entries
|
||||
if current_account != form_account:
|
||||
current_account.mark_as_edited(self.user, self.request, edit_comment=DEDUCTION_REMOVED)
|
||||
if old_account != form_account:
|
||||
old_account.mark_as_edited(self.user, self.request, edit_comment=DEDUCTION_REMOVED)
|
||||
form_account.mark_as_edited(self.user, self.request, edit_comment=DEDUCTION_ADDED)
|
||||
else:
|
||||
current_account.mark_as_edited(self.user, self.request, edit_comment=DEDUCTION_EDITED)
|
||||
old_account.mark_as_edited(self.user, self.request, edit_comment=DEDUCTION_EDITED)
|
||||
|
||||
if current_intervention != form_intervention:
|
||||
current_intervention.mark_as_edited(self.user, self.request, edit_comment=DEDUCTION_REMOVED)
|
||||
if old_intervention != form_intervention:
|
||||
old_intervention.mark_as_edited(self.user, self.request, edit_comment=DEDUCTION_REMOVED)
|
||||
form_intervention.mark_as_edited(self.user, self.request, edit_comment=DEDUCTION_ADDED)
|
||||
else:
|
||||
current_intervention.mark_as_edited(self.user, self.request, edit_comment=DEDUCTION_EDITED)
|
||||
old_intervention.mark_as_edited(self.user, self.request, edit_comment=DEDUCTION_EDITED)
|
||||
|
||||
deduction.account = form_account
|
||||
deduction.intervention = self.cleaned_data.get("intervention", None)
|
||||
deduction.surface = self.cleaned_data.get("surface", None)
|
||||
deduction.save()
|
||||
|
||||
data_changes = {
|
||||
"surface": {
|
||||
"old": old_surface,
|
||||
"new": deduction.surface,
|
||||
},
|
||||
"intervention": {
|
||||
"old": old_intervention.identifier,
|
||||
"new": deduction.intervention.identifier,
|
||||
},
|
||||
"account": {
|
||||
"old": old_account.identifier,
|
||||
"new": deduction.account.identifier,
|
||||
}
|
||||
}
|
||||
old_account.send_notification_mail_on_deduction_change(data_changes)
|
||||
return deduction
|
||||
|
||||
|
||||
|
19
intervention/migrations/0005_intervention_resubmission.py
Normal file
19
intervention/migrations/0005_intervention_resubmission.py
Normal file
@ -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'),
|
||||
),
|
||||
]
|
23
intervention/migrations/0006_auto_20220815_0803.py
Normal file
23
intervention/migrations/0006_auto_20220815_0803.py
Normal file
@ -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'),
|
||||
),
|
||||
]
|
19
intervention/migrations/0007_auto_20220815_1030.py
Normal file
19
intervention/migrations/0007_auto_20220815_1030.py
Normal file
@ -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)
|
||||
|
687
konova/forms.py
687
konova/forms.py
@ -1,687 +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 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 = isinstance(
|
||||
self,
|
||||
(
|
||||
NewDeductionModalForm,
|
||||
EditEcoAccountDeductionModalForm,
|
||||
RemoveEcoAccountDeductionModalForm,
|
||||
)
|
||||
)
|
||||
|
||||
if is_none or is_other_data_type or is_deduction_form:
|
||||
# Do nothing
|
||||
return
|
||||
|
||||
if self.instance.is_recorded:
|
||||
self.template = "form/recorded_no_edit.html"
|
||||
|
||||
|
||||
class RemoveForm(BaseForm):
|
||||
check = forms.BooleanField(
|
||||
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
|
11
konova/forms/__init__.py
Normal file
11
konova/forms/__init__.py
Normal file
@ -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 *
|
157
konova/forms/base_form.py
Normal file
157
konova/forms/base_form.py
Normal file
@ -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"
|
133
konova/forms/geometry_form.py
Normal file
133
konova/forms/geometry_form.py
Normal file
@ -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
|
12
konova/forms/modals/__init__.py
Normal file
12
konova/forms/modals/__init__.py
Normal file
@ -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 *
|
73
konova/forms/modals/base_form.py
Normal file
73
konova/forms/modals/base_form.py
Normal file
@ -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
|
163
konova/forms/modals/document_form.py
Normal file
163
konova/forms/modals/document_form.py
Normal file
@ -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
|
||||
|
123
konova/forms/modals/record_form.py
Normal file
123
konova/forms/modals/record_form.py
Normal file
@ -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
|
58
konova/forms/modals/remove_form.py
Normal file
58
konova/forms/modals/remove_form.py
Normal file
@ -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)
|
85
konova/forms/modals/resubmission_form.py
Normal file
85
konova/forms/modals/resubmission_form.py
Normal file
@ -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
|
||||
|
54
konova/forms/remove_form.py
Normal file
54
konova/forms/remove_form.py
Normal file
@ -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
|
46
konova/management/commands/handle_resubmissions.py
Normal file
46
konova/management/commands/handle_resubmissions.py
Normal file
@ -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)
|
@ -28,4 +28,5 @@ USER_NOTIFICATIONS_NAMES = {
|
||||
"NOTIFY_ON_SHARED_DATA_RECORDED": _("On shared data recorded"),
|
||||
"NOTIFY_ON_SHARED_DATA_DELETED": _("On shared data deleted"),
|
||||
"NOTIFY_ON_SHARED_DATA_CHECKED": _("On shared data checked"),
|
||||
"NOTIFY_ON_DEDUCTION_CHANGES": _("On deduction changes"),
|
||||
}
|
@ -33,6 +33,7 @@ class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('konova', '0004_auto_20220209_0839'),
|
||||
('compensation', '0002_auto_20220114_0936'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
|
33
konova/migrations/0014_resubmission.py
Normal file
33
konova/migrations/0014_resubmission.py
Normal file
@ -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 *
|
||||
|
@ -744,3 +744,22 @@ class GeoReferencedMixin(models.Model):
|
||||
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)
|
||||
|
46
konova/models/resubmission.py
Normal file
46
konova/models/resubmission.py
Normal file
@ -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()
|
@ -106,3 +106,17 @@ def celery_send_mail_shared_data_checked_team(obj_identifier, obj_title=None, te
|
||||
from user.models import Team
|
||||
team = Team.objects.get(id=team_id)
|
||||
team.send_mail_shared_data_checked(obj_identifier, obj_title)
|
||||
|
||||
|
||||
@shared_task
|
||||
def celery_send_mail_deduction_changed_team(obj_identifier, obj_title=None, team_id=None, data_changes=None):
|
||||
from user.models import Team
|
||||
team = Team.objects.get(id=team_id)
|
||||
team.send_mail_deduction_changed(obj_identifier, obj_title, data_changes)
|
||||
|
||||
|
||||
@shared_task
|
||||
def celery_send_mail_deduction_changed(obj_identifier, obj_title=None, user_id=None, data_changes=None):
|
||||
from user.models import User
|
||||
user = User.objects.get(id=user_id)
|
||||
user.send_mail_deduction_changed(obj_identifier, obj_title, data_changes)
|
||||
|
@ -22,7 +22,7 @@ from codelist.models import KonovaCode, KonovaCodeList
|
||||
from compensation.models import Compensation, CompensationState, CompensationAction, EcoAccount, EcoAccountDeduction
|
||||
from intervention.models import Legal, Responsibility, Intervention, Handler
|
||||
from konova.management.commands.setup_data import GROUPS_DATA
|
||||
from konova.models import Geometry
|
||||
from konova.models import Geometry, Deadline, DeadlineType
|
||||
from konova.settings import DEFAULT_GROUP
|
||||
from konova.utils.generators import generate_random_string
|
||||
from user.models import UserActionLogEntry
|
||||
@ -41,6 +41,7 @@ class BaseTestCase(TestCase):
|
||||
eco_account = None
|
||||
comp_state = None
|
||||
comp_action = None
|
||||
finished_deadline = None
|
||||
codes = None
|
||||
|
||||
superuser_pw = "root"
|
||||
@ -69,6 +70,7 @@ class BaseTestCase(TestCase):
|
||||
self.create_dummy_action()
|
||||
self.codes = self.create_dummy_codes()
|
||||
self.team = self.create_dummy_team()
|
||||
self.finished_deadline = self.create_dummy_deadline()
|
||||
|
||||
# Set the default group as only group for the user
|
||||
default_group = self.groups.get(name=DEFAULT_GROUP)
|
||||
@ -279,6 +281,20 @@ class BaseTestCase(TestCase):
|
||||
|
||||
return team
|
||||
|
||||
def create_dummy_deadline(self, type: DeadlineType = DeadlineType.FINISHED):
|
||||
""" Creates a dummy deadline.
|
||||
|
||||
If type is not specified, it defaults to DeadlineType.FINISHED
|
||||
|
||||
Returns:
|
||||
deadline (Deadline): A deadline
|
||||
"""
|
||||
deadline = Deadline.objects.create(
|
||||
type=type,
|
||||
date="1970-01-01"
|
||||
)
|
||||
return deadline
|
||||
|
||||
@staticmethod
|
||||
def create_dummy_geometry() -> MultiPolygon:
|
||||
""" Creates some geometry
|
||||
@ -361,6 +377,7 @@ class BaseTestCase(TestCase):
|
||||
compensation.before_states.add(self.comp_state)
|
||||
compensation.actions.add(self.comp_action)
|
||||
compensation.geometry.geom = self.create_dummy_geometry()
|
||||
compensation.deadlines.add(self.finished_deadline)
|
||||
compensation.geometry.save()
|
||||
return compensation
|
||||
|
||||
@ -390,6 +407,7 @@ class BaseTestCase(TestCase):
|
||||
ema.before_states.add(self.comp_state)
|
||||
ema.actions.add(self.comp_action)
|
||||
ema.geometry.geom = self.create_dummy_geometry()
|
||||
ema.deadlines.add(self.finished_deadline)
|
||||
ema.geometry.save()
|
||||
return ema
|
||||
|
||||
@ -410,6 +428,7 @@ class BaseTestCase(TestCase):
|
||||
eco_account.geometry.geom = self.create_dummy_geometry()
|
||||
eco_account.geometry.save()
|
||||
eco_account.deductable_surface = eco_account.get_state_after_surface_sum()
|
||||
eco_account.deadlines.add(self.finished_deadline)
|
||||
eco_account.save()
|
||||
return eco_account
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -207,6 +207,33 @@ class Mailer:
|
||||
msg
|
||||
)
|
||||
|
||||
def send_mail_deduction_changed_team(self, obj_identifier, obj_title, team, data_changes):
|
||||
""" Send a mail if deduction has been changed
|
||||
|
||||
Args:
|
||||
obj_identifier (str): Identifier of the main object
|
||||
obj_title (str): Title of the main object
|
||||
team (Team): Team to be notified
|
||||
data_changes (dict): Contains the old|new changes of the deduction changes
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
context = {
|
||||
"team": team,
|
||||
"obj_identifier": obj_identifier,
|
||||
"obj_title": obj_title,
|
||||
"EMAIL_REPLY_TO": EMAIL_REPLY_TO,
|
||||
"data_changes": data_changes,
|
||||
}
|
||||
msg = render_to_string("email/other/deduction_changed_team.html", context)
|
||||
user_mail_address = team.users.values_list("email", flat=True)
|
||||
self.send(
|
||||
user_mail_address,
|
||||
_("{} - Deduction changed").format(obj_identifier),
|
||||
msg
|
||||
)
|
||||
|
||||
def send_mail_shared_data_deleted_team(self, obj_identifier, obj_title, team):
|
||||
""" Send a mail if data has just been deleted
|
||||
|
||||
@ -322,6 +349,34 @@ class Mailer:
|
||||
msg
|
||||
)
|
||||
|
||||
def send_mail_deduction_changed(self, obj_identifier, obj_title, user, data_changes):
|
||||
""" Send a mail if deduction has been changed
|
||||
|
||||
Args:
|
||||
obj_identifier (str): Identifier of the main object
|
||||
obj_title (str): Title of the main object
|
||||
user (User): User to be notified
|
||||
data_changes (dict): Contains the old|new changes of the deduction changes
|
||||
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
context = {
|
||||
"user": user,
|
||||
"obj_identifier": obj_identifier,
|
||||
"obj_title": obj_title,
|
||||
"EMAIL_REPLY_TO": EMAIL_REPLY_TO,
|
||||
"data_changes": data_changes,
|
||||
}
|
||||
msg = render_to_string("email/other/deduction_changed.html", context)
|
||||
user_mail_address = [user.email]
|
||||
self.send(
|
||||
user_mail_address,
|
||||
_("{} - Deduction changed").format(obj_identifier),
|
||||
msg
|
||||
)
|
||||
|
||||
def send_mail_verify_api_token(self, user):
|
||||
""" Send a mail if a user creates a new token
|
||||
|
||||
@ -343,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-06-27 14:23+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"
|
||||
@ -55,7 +56,7 @@ msgstr "Einträge erstellt bis..."
|
||||
#: analysis/forms.py:49 compensation/forms/forms.py:77
|
||||
#: compensation/templates/compensation/detail/eco_account/view.html:59
|
||||
#: compensation/templates/compensation/report/eco_account/report.html:16
|
||||
#: compensation/utils/quality.py:100 ema/templates/ema/detail/view.html:49
|
||||
#: compensation/utils/quality.py:111 ema/templates/ema/detail/view.html:49
|
||||
#: ema/templates/ema/report/report.html:16 ema/utils/quality.py:26
|
||||
#: intervention/forms/forms.py:102
|
||||
#: intervention/templates/intervention/detail/view.html:56
|
||||
@ -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,6 +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:31
|
||||
#: templates/email/other/deduction_changed_team.html:31
|
||||
msgid "Surface"
|
||||
msgstr "Fläche"
|
||||
|
||||
@ -296,9 +299,9 @@ msgid "Law"
|
||||
msgstr "Gesetz"
|
||||
|
||||
#: analysis/templates/analysis/reports/includes/old_data/amount.html:17
|
||||
#: compensation/templates/compensation/detail/compensation/includes/deadlines.html:28
|
||||
#: compensation/templates/compensation/detail/eco_account/includes/deadlines.html:28
|
||||
#: ema/templates/ema/detail/includes/deadlines.html:28
|
||||
#: compensation/templates/compensation/detail/compensation/includes/deadlines.html:33
|
||||
#: compensation/templates/compensation/detail/eco_account/includes/deadlines.html:33
|
||||
#: ema/templates/ema/detail/includes/deadlines.html:33
|
||||
msgid "Type"
|
||||
msgstr "Typ"
|
||||
|
||||
@ -307,6 +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:26
|
||||
#: templates/email/other/deduction_changed_team.html:26
|
||||
#: templates/navbars/navbar.html:22
|
||||
msgid "Intervention"
|
||||
msgstr "Eingriff"
|
||||
@ -360,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"
|
||||
|
||||
@ -375,31 +380,32 @@ msgstr "Kompensation XY; Flur ABC"
|
||||
#: compensation/forms/forms.py:57 compensation/forms/modalForms.py:63
|
||||
#: compensation/forms/modalForms.py:361 compensation/forms/modalForms.py:469
|
||||
#: compensation/templates/compensation/detail/compensation/includes/actions.html:35
|
||||
#: compensation/templates/compensation/detail/compensation/includes/deadlines.html:34
|
||||
#: compensation/templates/compensation/detail/compensation/includes/deadlines.html:39
|
||||
#: compensation/templates/compensation/detail/compensation/includes/documents.html:34
|
||||
#: compensation/templates/compensation/detail/eco_account/includes/actions.html:34
|
||||
#: compensation/templates/compensation/detail/eco_account/includes/deadlines.html:34
|
||||
#: compensation/templates/compensation/detail/eco_account/includes/deadlines.html:39
|
||||
#: compensation/templates/compensation/detail/eco_account/includes/documents.html:34
|
||||
#: ema/templates/ema/detail/includes/actions.html:34
|
||||
#: ema/templates/ema/detail/includes/deadlines.html:34
|
||||
#: ema/templates/ema/detail/includes/deadlines.html:39
|
||||
#: ema/templates/ema/detail/includes/documents.html:34
|
||||
#: intervention/forms/forms.py:198 intervention/forms/modalForms.py:175
|
||||
#: 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"
|
||||
|
||||
#: compensation/forms/forms.py:93
|
||||
#: compensation/templates/compensation/detail/eco_account/view.html:63
|
||||
#: compensation/templates/compensation/report/eco_account/report.html:20
|
||||
#: compensation/utils/quality.py:102 ema/templates/ema/detail/view.html:53
|
||||
#: compensation/utils/quality.py:113 ema/templates/ema/detail/view.html:53
|
||||
#: ema/templates/ema/report/report.html:20 ema/utils/quality.py:28
|
||||
#: intervention/forms/forms.py:130
|
||||
#: intervention/templates/intervention/detail/view.html:60
|
||||
@ -477,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"
|
||||
|
||||
@ -485,7 +491,7 @@ msgstr "Neue Kompensation"
|
||||
msgid "Edit compensation"
|
||||
msgstr "Bearbeite Kompensation"
|
||||
|
||||
#: compensation/forms/forms.py:356 compensation/utils/quality.py:84
|
||||
#: compensation/forms/forms.py:356 compensation/utils/quality.py:95
|
||||
msgid "Available Surface"
|
||||
msgstr "Verfügbare Fläche"
|
||||
|
||||
@ -495,7 +501,7 @@ msgstr "Die für Abbuchungen zur Verfügung stehende Menge"
|
||||
|
||||
#: compensation/forms/forms.py:368
|
||||
#: compensation/templates/compensation/detail/eco_account/view.html:67
|
||||
#: compensation/utils/quality.py:72
|
||||
#: compensation/utils/quality.py:83
|
||||
msgid "Agreement date"
|
||||
msgstr "Vereinbarungsdatum"
|
||||
|
||||
@ -529,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"
|
||||
|
||||
@ -574,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"
|
||||
|
||||
@ -597,10 +603,10 @@ msgid "Select the deadline type"
|
||||
msgstr "Fristart wählen"
|
||||
|
||||
#: compensation/forms/modalForms.py:345
|
||||
#: compensation/templates/compensation/detail/compensation/includes/deadlines.html:31
|
||||
#: compensation/templates/compensation/detail/eco_account/includes/deadlines.html:31
|
||||
#: ema/templates/ema/detail/includes/deadlines.html:31
|
||||
#: intervention/forms/modalForms.py:149
|
||||
#: 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 konova/forms.py:697
|
||||
msgid "Date"
|
||||
msgstr "Datum"
|
||||
|
||||
@ -617,9 +623,9 @@ msgid "Insert data for the new deadline"
|
||||
msgstr "Geben Sie die Daten der neuen Frist ein"
|
||||
|
||||
#: compensation/forms/modalForms.py:389
|
||||
#: compensation/templates/compensation/detail/compensation/includes/deadlines.html:59
|
||||
#: compensation/templates/compensation/detail/eco_account/includes/deadlines.html:57
|
||||
#: ema/templates/ema/detail/includes/deadlines.html:57
|
||||
#: compensation/templates/compensation/detail/compensation/includes/deadlines.html:64
|
||||
#: compensation/templates/compensation/detail/eco_account/includes/deadlines.html:62
|
||||
#: ema/templates/ema/detail/includes/deadlines.html:62
|
||||
msgid "Edit deadline"
|
||||
msgstr "Frist/Termin bearbeiten"
|
||||
|
||||
@ -696,14 +702,14 @@ msgstr ""
|
||||
msgid "Pieces"
|
||||
msgstr "Stück"
|
||||
|
||||
#: compensation/models/eco_account.py:55
|
||||
#: compensation/models/eco_account.py:56
|
||||
msgid ""
|
||||
"Deductable surface can not be larger than existing surfaces in after states"
|
||||
msgstr ""
|
||||
"Die abbuchbare Fläche darf die Gesamtfläche der Zielzustände nicht "
|
||||
"überschreiten"
|
||||
|
||||
#: compensation/models/eco_account.py:62
|
||||
#: compensation/models/eco_account.py:63
|
||||
msgid ""
|
||||
"Deductable surface can not be smaller than the sum of already existing "
|
||||
"deductions. Please contact the responsible users for the deductions!"
|
||||
@ -798,18 +804,18 @@ msgid "Amount"
|
||||
msgstr "Menge"
|
||||
|
||||
#: compensation/templates/compensation/detail/compensation/includes/actions.html:40
|
||||
#: compensation/templates/compensation/detail/compensation/includes/deadlines.html:39
|
||||
#: compensation/templates/compensation/detail/compensation/includes/deadlines.html:44
|
||||
#: compensation/templates/compensation/detail/compensation/includes/documents.html:39
|
||||
#: compensation/templates/compensation/detail/compensation/includes/states-after.html:41
|
||||
#: compensation/templates/compensation/detail/compensation/includes/states-before.html:41
|
||||
#: compensation/templates/compensation/detail/eco_account/includes/actions.html:39
|
||||
#: compensation/templates/compensation/detail/eco_account/includes/deadlines.html:38
|
||||
#: compensation/templates/compensation/detail/eco_account/includes/deadlines.html:43
|
||||
#: compensation/templates/compensation/detail/eco_account/includes/deductions.html:41
|
||||
#: compensation/templates/compensation/detail/eco_account/includes/documents.html:38
|
||||
#: compensation/templates/compensation/detail/eco_account/includes/states-after.html:41
|
||||
#: compensation/templates/compensation/detail/eco_account/includes/states-before.html:41
|
||||
#: ema/templates/ema/detail/includes/actions.html:38
|
||||
#: ema/templates/ema/detail/includes/deadlines.html:38
|
||||
#: ema/templates/ema/detail/includes/deadlines.html:43
|
||||
#: ema/templates/ema/detail/includes/documents.html:38
|
||||
#: ema/templates/ema/detail/includes/states-after.html:40
|
||||
#: ema/templates/ema/detail/includes/states-before.html:40
|
||||
@ -848,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"
|
||||
@ -882,9 +896,15 @@ msgstr "Termine und Fristen"
|
||||
msgid "Add new deadline"
|
||||
msgstr "Frist/Termin hinzufügen"
|
||||
|
||||
#: compensation/templates/compensation/detail/compensation/includes/deadlines.html:62
|
||||
#: compensation/templates/compensation/detail/eco_account/includes/deadlines.html:60
|
||||
#: ema/templates/ema/detail/includes/deadlines.html:60
|
||||
#: compensation/templates/compensation/detail/compensation/includes/deadlines.html:25
|
||||
#: compensation/templates/compensation/detail/eco_account/includes/deadlines.html:25
|
||||
#: ema/templates/ema/detail/includes/deadlines.html:25
|
||||
msgid "Missing finished deadline "
|
||||
msgstr "Umsetzungstermin fehlt"
|
||||
|
||||
#: compensation/templates/compensation/detail/compensation/includes/deadlines.html:67
|
||||
#: compensation/templates/compensation/detail/eco_account/includes/deadlines.html:65
|
||||
#: ema/templates/ema/detail/includes/deadlines.html:65
|
||||
msgid "Remove deadline"
|
||||
msgstr "Frist löschen"
|
||||
|
||||
@ -899,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"
|
||||
|
||||
@ -907,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"
|
||||
|
||||
@ -915,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"
|
||||
|
||||
@ -928,7 +948,7 @@ msgstr "Dokument löschen"
|
||||
|
||||
#: compensation/templates/compensation/detail/compensation/includes/states-after.html:8
|
||||
#: compensation/templates/compensation/detail/eco_account/includes/states-after.html:8
|
||||
#: compensation/utils/quality.py:39
|
||||
#: compensation/utils/quality.py:40
|
||||
#: ema/templates/ema/detail/includes/states-after.html:8
|
||||
msgid "States after"
|
||||
msgstr "Zielzustand"
|
||||
@ -974,7 +994,7 @@ msgstr "Zustand entfernen"
|
||||
|
||||
#: compensation/templates/compensation/detail/compensation/includes/states-before.html:8
|
||||
#: compensation/templates/compensation/detail/eco_account/includes/states-before.html:8
|
||||
#: compensation/utils/quality.py:37
|
||||
#: compensation/utils/quality.py:38
|
||||
#: ema/templates/ema/detail/includes/states-before.html:8
|
||||
msgid "States before"
|
||||
msgstr "Ausgangszustand"
|
||||
@ -1067,22 +1087,40 @@ msgstr "Zuletzt bearbeitet"
|
||||
msgid "Shared with"
|
||||
msgstr "Freigegeben für"
|
||||
|
||||
#: compensation/templates/compensation/detail/eco_account/includes/controls.html:15
|
||||
#: ema/templates/ema/detail/includes/controls.html:15
|
||||
#: compensation/templates/compensation/detail/compensation/view.html:132
|
||||
#: compensation/templates/compensation/detail/eco_account/view.html:110
|
||||
#: ema/templates/ema/detail/view.html:96
|
||||
#: intervention/templates/intervention/detail/view.html:138
|
||||
msgid ""
|
||||
"The data must be shared with you, if you want to see which other users have "
|
||||
"shared access as well."
|
||||
msgstr ""
|
||||
"Die Daten müssen für Sie freigegeben sein, damit Sie sehen können welche "
|
||||
"weiteren Nutzern ebenfalls Zugriff hierauf haben."
|
||||
|
||||
#: compensation/templates/compensation/detail/compensation/view.html:134
|
||||
#: compensation/templates/compensation/detail/eco_account/view.html:112
|
||||
#: ema/templates/ema/detail/view.html:98
|
||||
#: intervention/templates/intervention/detail/view.html:140
|
||||
msgid "other users"
|
||||
msgstr "weitere Nutzer"
|
||||
|
||||
#: 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"
|
||||
|
||||
@ -1166,48 +1204,57 @@ msgstr "Abbuchungen für"
|
||||
msgid "None"
|
||||
msgstr "-"
|
||||
|
||||
#: compensation/utils/quality.py:34
|
||||
#: compensation/utils/quality.py:35
|
||||
msgid "States unequal"
|
||||
msgstr "Ungleiche Zustandsflächenmengen"
|
||||
|
||||
#: compensation/utils/quality.py:74 intervention/utils/quality.py:84
|
||||
#: compensation/utils/quality.py:59
|
||||
msgid "Finished deadlines"
|
||||
msgstr "Umsetzungstermin"
|
||||
|
||||
#: compensation/utils/quality.py:85 intervention/utils/quality.py:84
|
||||
msgid "Legal data"
|
||||
msgstr "Rechtliche Daten"
|
||||
|
||||
#: compensation/utils/quality.py:88
|
||||
#: compensation/utils/quality.py:99
|
||||
msgid "Deductable surface can not be larger than state surface"
|
||||
msgstr ""
|
||||
"Die abbuchbare Fläche darf die Gesamtfläche der Zielzustände nicht "
|
||||
"überschreiten"
|
||||
|
||||
#: compensation/utils/quality.py:104 ema/utils/quality.py:30
|
||||
#: compensation/utils/quality.py:115 ema/utils/quality.py:30
|
||||
#: intervention/utils/quality.py:55
|
||||
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
|
||||
#: ema/views.py:240 intervention/views.py:338
|
||||
#: 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:268 compensation/views/eco_account.py:359
|
||||
#: ema/views.py:194 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:612 compensation/views/eco_account.py:727
|
||||
#: ema/views.py:558 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"
|
||||
@ -1220,36 +1267,36 @@ msgstr "Ökokonto {} hinzugefügt"
|
||||
msgid "Eco-Account {} edited"
|
||||
msgstr "Ökokonto {} bearbeitet"
|
||||
|
||||
#: compensation/views/eco_account.py:276
|
||||
#: compensation/views/eco_account.py:277
|
||||
msgid "Eco-account removed"
|
||||
msgstr "Ökokonto entfernt"
|
||||
|
||||
#: compensation/views/eco_account.py:380 ema/views.py:282
|
||||
#: intervention/views.py:641
|
||||
#: compensation/views/eco_account.py:381 ema/views.py:283
|
||||
#: intervention/views.py:664
|
||||
msgid "{} unrecorded"
|
||||
msgstr "{} entzeichnet"
|
||||
|
||||
#: compensation/views/eco_account.py:380 ema/views.py:282
|
||||
#: intervention/views.py:641
|
||||
#: compensation/views/eco_account.py:381 ema/views.py:283
|
||||
#: intervention/views.py:664
|
||||
msgid "{} recorded"
|
||||
msgstr "{} verzeichnet"
|
||||
|
||||
#: compensation/views/eco_account.py:804 ema/views.py:628
|
||||
#: compensation/views/eco_account.py:805 ema/views.py:629
|
||||
#: intervention/views.py:439
|
||||
msgid "{} has already been shared with you"
|
||||
msgstr "{} wurde bereits für Sie freigegeben"
|
||||
|
||||
#: compensation/views/eco_account.py:809 ema/views.py:633
|
||||
#: compensation/views/eco_account.py:810 ema/views.py:634
|
||||
#: intervention/views.py:444
|
||||
msgid "{} has been shared with you"
|
||||
msgstr "{} ist nun für Sie freigegeben"
|
||||
|
||||
#: compensation/views/eco_account.py:816 ema/views.py:640
|
||||
#: compensation/views/eco_account.py:817 ema/views.py:641
|
||||
#: intervention/views.py:451
|
||||
msgid "Share link invalid"
|
||||
msgstr "Freigabelink ungültig"
|
||||
|
||||
#: compensation/views/eco_account.py:839 ema/views.py:663
|
||||
#: compensation/views/eco_account.py:840 ema/views.py:664
|
||||
#: intervention/views.py:474
|
||||
msgid "Share settings updated"
|
||||
msgstr "Freigabe Einstellungen aktualisiert"
|
||||
@ -1290,11 +1337,11 @@ msgstr "EMAs - Übersicht"
|
||||
msgid "EMA {} added"
|
||||
msgstr "EMA {} hinzugefügt"
|
||||
|
||||
#: ema/views.py:230
|
||||
#: ema/views.py:231
|
||||
msgid "EMA {} edited"
|
||||
msgstr "EMA {} bearbeitet"
|
||||
|
||||
#: ema/views.py:263
|
||||
#: ema/views.py:264
|
||||
msgid "EMA removed"
|
||||
msgstr "EMA entfernt"
|
||||
|
||||
@ -1432,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."
|
||||
@ -1549,18 +1596,6 @@ msgstr "Eingriffsverursacher"
|
||||
msgid "Exists"
|
||||
msgstr "vorhanden"
|
||||
|
||||
#: intervention/templates/intervention/detail/view.html:138
|
||||
msgid ""
|
||||
"The data must be shared with you, if you want to see which other users have "
|
||||
"shared access as well."
|
||||
msgstr ""
|
||||
"Die Daten müssen für Sie freigegeben sein, damit Sie sehen können welche weiteren Nutzern "
|
||||
"ebenfalls Zugriff hierauf haben."
|
||||
|
||||
#: intervention/templates/intervention/detail/view.html:140
|
||||
msgid "other users"
|
||||
msgstr "weitere Nutzer"
|
||||
|
||||
#: intervention/templates/intervention/report/report.html:58
|
||||
msgid "Deductions of eco-accounts"
|
||||
msgstr "Abbuchungen von Ökokonten"
|
||||
@ -1604,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:"
|
||||
|
||||
@ -1693,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"
|
||||
@ -1785,6 +1832,10 @@ msgstr "Wenn meine freigegebenen Daten gelöscht wurden"
|
||||
msgid "On shared data checked"
|
||||
msgstr "Wenn meine freigegebenen Daten geprüft wurden"
|
||||
|
||||
#: konova/management/commands/setup_data.py:31
|
||||
msgid "On deduction changes"
|
||||
msgstr "Wenn eine Abbuchung zu meinem Ökokonto verändert wird"
|
||||
|
||||
#: konova/models/deadline.py:18
|
||||
msgid "Finished"
|
||||
msgstr "Umgesetzt bis"
|
||||
@ -1895,26 +1946,34 @@ msgstr "{} - Zugriff entzogen"
|
||||
msgid "{} - Shared access given"
|
||||
msgstr "{} - Zugriff freigegeben"
|
||||
|
||||
#: konova/utils/mailer.py:160 konova/utils/mailer.py:275
|
||||
#: konova/utils/mailer.py:160 konova/utils/mailer.py:302
|
||||
msgid "{} - Shared data unrecorded"
|
||||
msgstr "{} - Freigegebene Daten entzeichnet"
|
||||
|
||||
#: konova/utils/mailer.py:183 konova/utils/mailer.py:252
|
||||
#: konova/utils/mailer.py:183 konova/utils/mailer.py:279
|
||||
msgid "{} - Shared data recorded"
|
||||
msgstr "{} - Freigegebene Daten verzeichnet"
|
||||
|
||||
#: konova/utils/mailer.py:206 konova/utils/mailer.py:321
|
||||
#: konova/utils/mailer.py:206 konova/utils/mailer.py:348
|
||||
msgid "{} - Shared data checked"
|
||||
msgstr "{} - Freigegebene Daten geprüft"
|
||||
|
||||
#: konova/utils/mailer.py:229 konova/utils/mailer.py:298
|
||||
#: konova/utils/mailer.py:233 konova/utils/mailer.py:376
|
||||
msgid "{} - Deduction changed"
|
||||
msgstr "{} - Abbuchung geändert"
|
||||
|
||||
#: konova/utils/mailer.py:256 konova/utils/mailer.py:325
|
||||
msgid "{} - Shared data deleted"
|
||||
msgstr "{} - Freigegebene Daten gelöscht"
|
||||
|
||||
#: konova/utils/mailer.py:342 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"
|
||||
@ -2197,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."
|
||||
@ -2210,19 +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/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"
|
||||
|
||||
@ -2233,20 +2295,22 @@ msgstr "Freigegebene Daten geprüft"
|
||||
|
||||
#: templates/email/checking/shared_data_checked.html:8
|
||||
#: templates/email/deleting/shared_data_deleted.html:8
|
||||
#: 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."
|
||||
@ -2256,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
|
||||
@ -2268,13 +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/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."
|
||||
@ -2282,24 +2349,55 @@ msgstr ""
|
||||
"Falls das nicht hätte passieren dürfen, kontaktieren Sie uns bitte. In der E-"
|
||||
"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: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:16
|
||||
#: templates/email/other/deduction_changed_team.html:16
|
||||
msgid "Attribute"
|
||||
msgstr "Attribute"
|
||||
|
||||
#: 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: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:21
|
||||
#: templates/email/other/deduction_changed_team.html:21
|
||||
msgid "EcoAccount"
|
||||
msgstr "Ökokonto"
|
||||
|
||||
#: templates/email/recording/shared_data_recorded.html:4
|
||||
#: templates/email/recording/shared_data_recorded_team.html:4
|
||||
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."
|
||||
@ -2312,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."
|
||||
@ -2331,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."
|
||||
@ -2354,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."
|
||||
@ -2364,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 "
|
||||
|
||||
@ -2373,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."
|
||||
@ -2394,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 "
|
||||
@ -2479,10 +2585,6 @@ msgstr "* sind Pflichtfelder."
|
||||
msgid "New entry"
|
||||
msgstr "Neuer Eintrag"
|
||||
|
||||
#: templates/generic_index.html:43 user/templates/user/team/index.html:22
|
||||
msgid "New"
|
||||
msgstr "Neu"
|
||||
|
||||
#: templates/generic_index.html:58
|
||||
msgid "Search for keywords"
|
||||
msgstr "Nach Schlagwörtern suchen"
|
||||
@ -2753,7 +2855,7 @@ msgstr "Benachrichtigungen"
|
||||
msgid "Manage teams"
|
||||
msgstr ""
|
||||
|
||||
#: user/templates/user/index.html:61 user/templates/user/team/index.html:18
|
||||
#: user/templates/user/index.html:61 user/templates/user/team/index.html:19
|
||||
#: user/views.py:167
|
||||
msgid "Teams"
|
||||
msgstr ""
|
||||
|
@ -6,6 +6,7 @@
|
||||
<article>
|
||||
{% trans 'Hello support' %},
|
||||
<br>
|
||||
<br>
|
||||
{% trans 'you need to verify the API token for user' %}:
|
||||
<br>
|
||||
<br>
|
||||
|
@ -7,6 +7,7 @@
|
||||
<article>
|
||||
{% trans 'Hello ' %} {{user.username}},
|
||||
<br>
|
||||
<br>
|
||||
{% trans 'the following dataset has just been checked' %}
|
||||
<br>
|
||||
<strong>{{obj_identifier}}</strong>
|
||||
|
@ -7,6 +7,7 @@
|
||||
<article>
|
||||
{% trans 'Hello team' %} {{team.name}},
|
||||
<br>
|
||||
<br>
|
||||
{% trans 'the following dataset has just been checked' %}
|
||||
<br>
|
||||
<strong>{{obj_identifier}}</strong>
|
||||
|
@ -7,6 +7,7 @@
|
||||
<article>
|
||||
{% trans 'Hello ' %} {{user.username}},
|
||||
<br>
|
||||
<br>
|
||||
{% trans 'the following dataset has just been deleted' %}
|
||||
<br>
|
||||
<strong>{{obj_identifier}}</strong>
|
||||
|
@ -7,6 +7,7 @@
|
||||
<article>
|
||||
{% trans 'Hello team' %} {{team.name}},
|
||||
<br>
|
||||
<br>
|
||||
{% trans 'the following dataset has just been deleted' %}
|
||||
<br>
|
||||
<strong>{{obj_identifier}}</strong>
|
||||
|
50
templates/email/other/deduction_changed.html
Normal file
50
templates/email/other/deduction_changed.html
Normal file
@ -0,0 +1,50 @@
|
||||
{% load i18n %}
|
||||
|
||||
<div>
|
||||
<h2>{% translate 'Deduction changed' %}</h2>
|
||||
<h4>{{obj_identifier}}</h4>
|
||||
<hr>
|
||||
<article>
|
||||
{% translate 'Hello ' %} {{user.username}},
|
||||
<br>
|
||||
<br>
|
||||
{% translate 'a deduction of this eco account has changed:' %}
|
||||
<br>
|
||||
<br>
|
||||
<table>
|
||||
<tr>
|
||||
<th scope="col">{% translate 'Attribute' %}</th>
|
||||
<th scope="col">{% translate 'Old' %}</th>
|
||||
<th scope="col">{% translate 'New' %}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% translate 'EcoAccount' %}</td>
|
||||
<td>{{data_changes.account.old}}</td>
|
||||
<td>{{data_changes.account.new}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% translate 'Intervention' %}</td>
|
||||
<td>{{data_changes.intervention.old}}</td>
|
||||
<td>{{data_changes.intervention.new}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% translate 'Surface' %}</td>
|
||||
<td>{{data_changes.surface.old}} m²</td>
|
||||
<td>{{data_changes.surface.new}} m²</td>
|
||||
</tr>
|
||||
</table>
|
||||
<br>
|
||||
<br>
|
||||
{% translate 'If this should not have been happened, please contact us. See the signature for details.' %}
|
||||
<br>
|
||||
<br>
|
||||
{% translate 'Best regards' %}
|
||||
<br>
|
||||
KSP
|
||||
<br>
|
||||
<br>
|
||||
<br>
|
||||
{% include 'email/signature.html' %}
|
||||
</article>
|
||||
</div>
|
||||
|
50
templates/email/other/deduction_changed_team.html
Normal file
50
templates/email/other/deduction_changed_team.html
Normal file
@ -0,0 +1,50 @@
|
||||
{% load i18n %}
|
||||
|
||||
<div>
|
||||
<h2>{% translate 'Deduction changed' %}</h2>
|
||||
<h4>{{obj_identifier}}</h4>
|
||||
<hr>
|
||||
<article>
|
||||
{% trans 'Hello team' %} {{team.name}},
|
||||
<br>
|
||||
<br>
|
||||
{% translate 'a deduction of this eco account has changed:' %}
|
||||
<br>
|
||||
<br>
|
||||
<table>
|
||||
<tr>
|
||||
<th scope="col">{% translate 'Attribute' %}</th>
|
||||
<th scope="col">{% translate 'Old' %}</th>
|
||||
<th scope="col">{% translate 'New' %}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% translate 'EcoAccount' %}</td>
|
||||
<td>{{data_changes.account.old}}</td>
|
||||
<td>{{data_changes.account.new}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% translate 'Intervention' %}</td>
|
||||
<td>{{data_changes.intervention.old}}</td>
|
||||
<td>{{data_changes.intervention.new}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% translate 'Surface' %}</td>
|
||||
<td>{{data_changes.surface.old}} m²</td>
|
||||
<td>{{data_changes.surface.new}} m²</td>
|
||||
</tr>
|
||||
</table>
|
||||
<br>
|
||||
<br>
|
||||
{% translate 'If this should not have been happened, please contact us. See the signature for details.' %}
|
||||
<br>
|
||||
<br>
|
||||
{% translate 'Best regards' %}
|
||||
<br>
|
||||
KSP
|
||||
<br>
|
||||
<br>
|
||||
<br>
|
||||
{% include 'email/signature.html' %}
|
||||
</article>
|
||||
</div>
|
||||
|
@ -7,6 +7,7 @@
|
||||
<article>
|
||||
{% trans 'Hello ' %} {{user.username}},
|
||||
<br>
|
||||
<br>
|
||||
{% trans 'the following dataset has just been recorded' %}
|
||||
<br>
|
||||
<strong>{{obj_identifier}}</strong>
|
||||
|
@ -7,6 +7,7 @@
|
||||
<article>
|
||||
{% trans 'Hello team' %} {{team.name}},
|
||||
<br>
|
||||
<br>
|
||||
{% trans 'the following dataset has just been recorded' %}
|
||||
<br>
|
||||
<strong>{{obj_identifier}}</strong>
|
||||
|
@ -7,6 +7,7 @@
|
||||
<article>
|
||||
{% trans 'Hello ' %} {{user.username}},
|
||||
<br>
|
||||
<br>
|
||||
{% trans 'the following dataset has just been unrecorded' %}
|
||||
<br>
|
||||
<strong>{{obj_identifier}}</strong>
|
||||
|
@ -7,6 +7,7 @@
|
||||
<article>
|
||||
{% trans 'Hello team' %} {{team.name}},
|
||||
<br>
|
||||
<br>
|
||||
{% trans 'the following dataset has just been unrecorded' %}
|
||||
<br>
|
||||
<strong>{{obj_identifier}}</strong>
|
||||
|
29
templates/email/resubmission/resubmission.html
Normal file
29
templates/email/resubmission/resubmission.html
Normal file
@ -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>
|
||||
|
@ -7,6 +7,7 @@
|
||||
<article>
|
||||
{% trans 'Hello ' %} {{user.username}},
|
||||
<br>
|
||||
<br>
|
||||
{% trans 'the following dataset has just been shared with you' %}
|
||||
<br>
|
||||
<strong>{{obj_identifier}}</strong>
|
||||
|
@ -7,6 +7,7 @@
|
||||
<article>
|
||||
{% trans 'Hello team' %} {{team.name}},
|
||||
<br>
|
||||
<br>
|
||||
{% trans 'the following dataset has just been shared with your team' %}
|
||||
<br>
|
||||
<strong>{{obj_identifier}}</strong>
|
||||
|
@ -7,6 +7,7 @@
|
||||
<article>
|
||||
{% trans 'Hello ' %} {{user.username}},
|
||||
<br>
|
||||
<br>
|
||||
{% trans 'your shared access, including editing, has been revoked for the dataset ' %}
|
||||
<br>
|
||||
<strong>{{obj_identifier}}</strong>
|
||||
|
@ -7,6 +7,7 @@
|
||||
<article>
|
||||
{% trans 'Hello team' %} {{team.name}},
|
||||
<br>
|
||||
<br>
|
||||
{% trans 'your teams shared access, including editing, has been revoked for the dataset ' %}
|
||||
<br>
|
||||
<strong>{{obj_identifier}}</strong>
|
||||
|
@ -14,3 +14,4 @@ class UserNotificationEnum(BaseEnum):
|
||||
NOTIFY_ON_SHARED_DATA_DELETED = "NOTIFY_ON_SHARED_DATA_DELETED" # notifies in case data has been deleted
|
||||
NOTIFY_ON_SHARED_DATA_CHECKED = "NOTIFY_ON_SHARED_DATA_CHECKED" # notifies in case shared data has been checked
|
||||
NOTIFY_ON_SHARED_ACCESS_GAINED = "NOTIFY_ON_SHARED_ACCESS_GAINED" # notifies in case new access has been gained
|
||||
NOTIFY_ON_DEDUCTION_CHANGES = "NOTIFY_ON_DEDUCTION_CHANGES" # notifies in case any changes (edit|remove) have been performed on a deduction of the user's ecoaccounts
|
@ -7,7 +7,7 @@ Created on: 08.07.21
|
||||
"""
|
||||
from dal import autocomplete
|
||||
from django import forms
|
||||
from django.db import IntegrityError, transaction
|
||||
from django.db import transaction
|
||||
from django.urls import reverse, reverse_lazy
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
@ -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):
|
||||
|
18
user/migrations/0006_auto_20220815_0759.py
Normal file
18
user/migrations/0006_auto_20220815_0759.py
Normal file
@ -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),
|
||||
),
|
||||
]
|
@ -95,6 +95,20 @@ class Team(UuidModel, DeletableObjectMixin):
|
||||
mailer = Mailer()
|
||||
mailer.send_mail_shared_data_checked_team(obj_identifier, obj_title, self)
|
||||
|
||||
def send_mail_deduction_changed(self, obj_identifier, obj_title, data_changes):
|
||||
""" Sends a mail to the team members in case of changed deduction values
|
||||
|
||||
Args:
|
||||
obj_identifier (str): Identifier of the main object
|
||||
obj_title (str): Title of the main object
|
||||
data_changes (dict): Contains the old|new changes of the deduction changes
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
mailer = Mailer()
|
||||
mailer.send_mail_deduction_changed_team(obj_identifier, obj_title, self, data_changes)
|
||||
|
||||
def send_mail_shared_data_deleted(self, obj_identifier, obj_title):
|
||||
""" Sends a mail to the team members in case of deleted data
|
||||
|
||||
|
@ -145,6 +145,22 @@ class User(AbstractUser):
|
||||
mailer = Mailer()
|
||||
mailer.send_mail_shared_data_checked(obj_identifier, obj_title, self)
|
||||
|
||||
def send_mail_deduction_changed(self, obj_identifier, obj_title, data_changes):
|
||||
""" Sends a mail to the user in case of a changed deduction
|
||||
|
||||
Args:
|
||||
obj_identifier (str): Identifier of the main object
|
||||
obj_title (str): Title of the main object
|
||||
data_changes (dict): Contains the old|new changes of the deduction changes
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
notification_set = self.is_notification_setting_set(UserNotificationEnum.NOTIFY_ON_DEDUCTION_CHANGES)
|
||||
if notification_set:
|
||||
mailer = Mailer()
|
||||
mailer.send_mail_deduction_changed(obj_identifier, obj_title, self, data_changes)
|
||||
|
||||
def get_API_token(self):
|
||||
""" Getter for an API token
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user