Merge pull request '197_Resubmission' (#198) from 197_Resubmission into master
Reviewed-on: SGD-Nord/konova#198
This commit is contained in:
		
						commit
						180ee293ca
					
				@ -20,7 +20,7 @@ from compensation.models import CompensationDocument, EcoAccountDocument
 | 
			
		||||
from intervention.inputs import CompensationActionTreeCheckboxSelectMultiple, \
 | 
			
		||||
    CompensationStateTreeRadioSelect
 | 
			
		||||
from konova.contexts import BaseContext
 | 
			
		||||
from konova.forms import BaseModalForm, NewDocumentModalForm, RemoveModalForm
 | 
			
		||||
from konova.forms.modals import BaseModalForm, NewDocumentModalForm, RemoveModalForm
 | 
			
		||||
from konova.models import DeadlineType
 | 
			
		||||
from konova.utils.message_templates import FORM_INVALID, ADDED_COMPENSATION_STATE, \
 | 
			
		||||
    ADDED_COMPENSATION_ACTION, PAYMENT_EDITED, COMPENSATION_STATE_EDITED, COMPENSATION_ACTION_EDITED, DEADLINE_EDITED
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										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'),
 | 
			
		||||
        ),
 | 
			
		||||
    ]
 | 
			
		||||
@ -19,14 +19,17 @@ from compensation.managers import CompensationManager
 | 
			
		||||
from compensation.models import CompensationState, CompensationAction
 | 
			
		||||
from compensation.utils.quality import CompensationQualityChecker
 | 
			
		||||
from konova.models import BaseObject, AbstractDocument, Deadline, generate_document_file_upload_path, \
 | 
			
		||||
    GeoReferencedMixin, DeadlineType
 | 
			
		||||
    GeoReferencedMixin, DeadlineType, ResubmitableObjectMixin
 | 
			
		||||
from konova.utils.message_templates import DATA_UNSHARED_EXPLANATION, COMPENSATION_REMOVED_TEMPLATE, \
 | 
			
		||||
    DOCUMENT_REMOVED_TEMPLATE, DEADLINE_REMOVED, ADDED_DEADLINE, \
 | 
			
		||||
    COMPENSATION_ACTION_REMOVED, COMPENSATION_STATE_REMOVED, INTERVENTION_HAS_REVOCATIONS_TEMPLATE
 | 
			
		||||
from user.models import UserActionLogEntry
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AbstractCompensation(BaseObject, GeoReferencedMixin):
 | 
			
		||||
class AbstractCompensation(BaseObject,
 | 
			
		||||
                           GeoReferencedMixin,
 | 
			
		||||
                           ResubmitableObjectMixin
 | 
			
		||||
                           ):
 | 
			
		||||
    """
 | 
			
		||||
    Abstract compensation model which holds basic attributes, shared by subclasses like the regular Compensation,
 | 
			
		||||
    EMA or EcoAccount.
 | 
			
		||||
 | 
			
		||||
@ -12,6 +12,9 @@
 | 
			
		||||
        </button>
 | 
			
		||||
    </a>
 | 
			
		||||
    {% if has_access %}
 | 
			
		||||
        <button class="btn btn-default btn-modal mr-2" title="{% trans 'Resubmission' %}" data-form-url="{% url 'compensation:resubmission-create' obj.id %}">
 | 
			
		||||
            {% fa5_icon 'bell' %}
 | 
			
		||||
        </button>
 | 
			
		||||
        {% if is_default_member %}
 | 
			
		||||
        <a href="{% url 'compensation:edit' obj.id %}" class="mr-2">
 | 
			
		||||
            <button class="btn btn-default" title="{% trans 'Edit' %}">
 | 
			
		||||
 | 
			
		||||
@ -12,6 +12,9 @@
 | 
			
		||||
        </button>
 | 
			
		||||
    </a>
 | 
			
		||||
    {% if has_access %}
 | 
			
		||||
        <button class="btn btn-default btn-modal mr-2" title="{% trans 'Resubmission' %}" data-form-url="{% url 'compensation:acc:resubmission-create' obj.id %}">
 | 
			
		||||
            {% fa5_icon 'bell' %}
 | 
			
		||||
        </button>
 | 
			
		||||
        <button class="btn btn-default btn-modal mr-2" title="{% trans 'Share' %}" data-form-url="{% url 'compensation:acc:share-create' obj.id %}">
 | 
			
		||||
            {% fa5_icon 'share-alt' %}
 | 
			
		||||
        </button>
 | 
			
		||||
 | 
			
		||||
@ -31,6 +31,7 @@ urlpatterns = [
 | 
			
		||||
    path('<id>/deadline/<deadline_id>/edit', deadline_edit_view, name='deadline-edit'),
 | 
			
		||||
    path('<id>/deadline/<deadline_id>/remove', deadline_remove_view, name='deadline-remove'),
 | 
			
		||||
    path('<id>/report', report_view, name='report'),
 | 
			
		||||
    path('<id>/resub', create_resubmission_view, name='resubmission-create'),
 | 
			
		||||
 | 
			
		||||
    # Documents
 | 
			
		||||
    path('<id>/document/new/', new_document_view, name='new-doc'),
 | 
			
		||||
 | 
			
		||||
@ -19,6 +19,7 @@ urlpatterns = [
 | 
			
		||||
    path('<id>/report', report_view, name='report'),
 | 
			
		||||
    path('<id>/edit', edit_view, name='edit'),
 | 
			
		||||
    path('<id>/remove', remove_view, name='remove'),
 | 
			
		||||
    path('<id>/resub', create_resubmission_view, name='resubmission-create'),
 | 
			
		||||
 | 
			
		||||
    path('<id>/state/new', state_new_view, name='new-state'),
 | 
			
		||||
    path('<id>/state/<state_id>/edit', state_edit_view, name='state-edit'),
 | 
			
		||||
 | 
			
		||||
@ -14,7 +14,9 @@ from compensation.tables import CompensationTable
 | 
			
		||||
from intervention.models import Intervention
 | 
			
		||||
from konova.contexts import BaseContext
 | 
			
		||||
from konova.decorators import *
 | 
			
		||||
from konova.forms import RemoveModalForm, SimpleGeomForm, RemoveDeadlineModalForm, EditDocumentModalForm
 | 
			
		||||
from konova.forms.modals import RemoveModalForm,RemoveDeadlineModalForm, EditDocumentModalForm, \
 | 
			
		||||
    ResubmissionModalForm
 | 
			
		||||
from konova.forms import SimpleGeomForm
 | 
			
		||||
from konova.models import Deadline
 | 
			
		||||
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
 | 
			
		||||
from konova.utils.documents import get_document, remove_document
 | 
			
		||||
@ -656,3 +658,26 @@ def report_view(request: HttpRequest, id: str):
 | 
			
		||||
    }
 | 
			
		||||
    context = BaseContext(request, context).context
 | 
			
		||||
    return render(request, template, context)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@login_required
 | 
			
		||||
@default_group_required
 | 
			
		||||
@shared_access_required(Compensation, "id")
 | 
			
		||||
def create_resubmission_view(request: HttpRequest, id: str):
 | 
			
		||||
    """ Renders resubmission form for a compensation
 | 
			
		||||
 | 
			
		||||
    Args:
 | 
			
		||||
        request (HttpRequest): The incoming request
 | 
			
		||||
        id (str): Compensation's id
 | 
			
		||||
 | 
			
		||||
    Returns:
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    com = get_object_or_404(Compensation, id=id)
 | 
			
		||||
    form = ResubmissionModalForm(request.POST or None, instance=com, request=request)
 | 
			
		||||
    form.action_url = reverse("compensation:resubmission-create", args=(id,))
 | 
			
		||||
    return form.process_request(
 | 
			
		||||
        request,
 | 
			
		||||
        msg_success=_("Resubmission set"),
 | 
			
		||||
        redirect_url=reverse("compensation:detail", args=(id,))
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
@ -25,14 +25,15 @@ from intervention.forms.modalForms import NewDeductionModalForm, ShareModalForm,
 | 
			
		||||
from konova.contexts import BaseContext
 | 
			
		||||
from konova.decorators import any_group_check, default_group_required, conservation_office_group_required, \
 | 
			
		||||
    shared_access_required
 | 
			
		||||
from konova.forms import RemoveModalForm, SimpleGeomForm, NewDocumentModalForm, RecordModalForm, \
 | 
			
		||||
    RemoveDeadlineModalForm, EditDocumentModalForm
 | 
			
		||||
from konova.forms.modals import RemoveModalForm, RecordModalForm, \
 | 
			
		||||
    RemoveDeadlineModalForm, EditDocumentModalForm, ResubmissionModalForm
 | 
			
		||||
from konova.forms import SimpleGeomForm
 | 
			
		||||
from konova.models import Deadline
 | 
			
		||||
from konova.settings import DEFAULT_GROUP, ZB_GROUP, ETS_GROUP
 | 
			
		||||
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
 | 
			
		||||
from konova.utils.documents import get_document, remove_document
 | 
			
		||||
from konova.utils.generators import generate_qr_code
 | 
			
		||||
from konova.utils.message_templates import IDENTIFIER_REPLACED, FORM_INVALID, DATA_UNSHARED, DATA_UNSHARED_EXPLANATION, \
 | 
			
		||||
from konova.utils.message_templates import IDENTIFIER_REPLACED, FORM_INVALID, \
 | 
			
		||||
    CANCEL_ACC_RECORDED_OR_DEDUCTED, DEDUCTION_REMOVED, DEDUCTION_ADDED, DOCUMENT_ADDED, COMPENSATION_STATE_REMOVED, \
 | 
			
		||||
    COMPENSATION_STATE_ADDED, COMPENSATION_ACTION_REMOVED, COMPENSATION_ACTION_ADDED, DEADLINE_ADDED, DEADLINE_REMOVED, \
 | 
			
		||||
    DEDUCTION_EDITED, DOCUMENT_EDITED, COMPENSATION_STATE_EDITED, COMPENSATION_ACTION_EDITED, DEADLINE_EDITED, \
 | 
			
		||||
@ -838,4 +839,27 @@ def create_share_view(request: HttpRequest, id: str):
 | 
			
		||||
    return form.process_request(
 | 
			
		||||
        request,
 | 
			
		||||
        msg_success=_("Share settings updated")
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@login_required
 | 
			
		||||
@default_group_required
 | 
			
		||||
@shared_access_required(EcoAccount, "id")
 | 
			
		||||
def create_resubmission_view(request: HttpRequest, id: str):
 | 
			
		||||
    """ Renders resubmission form for an eco account
 | 
			
		||||
 | 
			
		||||
    Args:
 | 
			
		||||
        request (HttpRequest): The incoming request
 | 
			
		||||
        id (str): EcoAccount's id
 | 
			
		||||
 | 
			
		||||
    Returns:
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    acc = get_object_or_404(EcoAccount, id=id)
 | 
			
		||||
    form = ResubmissionModalForm(request.POST or None, instance=acc, request=request)
 | 
			
		||||
    form.action_url = reverse("compensation:acc:resubmission-create", args=(id,))
 | 
			
		||||
    return form.process_request(
 | 
			
		||||
        request,
 | 
			
		||||
        msg_success=_("Resubmission set"),
 | 
			
		||||
        redirect_url=reverse("compensation:acc:detail", args=(id,))
 | 
			
		||||
    )
 | 
			
		||||
@ -15,7 +15,6 @@ from compensation.forms.modalForms import NewPaymentForm, RemovePaymentModalForm
 | 
			
		||||
from compensation.models import Payment
 | 
			
		||||
from intervention.models import Intervention
 | 
			
		||||
from konova.decorators import default_group_required, shared_access_required
 | 
			
		||||
from konova.forms import RemoveModalForm
 | 
			
		||||
from konova.utils.message_templates import PAYMENT_ADDED, PAYMENT_REMOVED, PAYMENT_EDITED
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -5,8 +5,6 @@ Contact: michel.peltriaux@sgdnord.rlp.de
 | 
			
		||||
Created on: 06.10.21
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
from dal import autocomplete
 | 
			
		||||
from django import forms
 | 
			
		||||
from user.models import User
 | 
			
		||||
from django.db import transaction
 | 
			
		||||
from django.urls import reverse, reverse_lazy
 | 
			
		||||
@ -16,7 +14,8 @@ from compensation.forms.forms import AbstractCompensationForm, CompensationRespo
 | 
			
		||||
    PikCompensationFormMixin
 | 
			
		||||
from ema.models import Ema, EmaDocument
 | 
			
		||||
from intervention.models import Responsibility, Handler
 | 
			
		||||
from konova.forms import SimpleGeomForm, NewDocumentModalForm
 | 
			
		||||
from konova.forms import SimpleGeomForm
 | 
			
		||||
from konova.forms.modals import NewDocumentModalForm
 | 
			
		||||
from user.models import UserActionLogEntry
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										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>
 | 
			
		||||
 | 
			
		||||
@ -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'),
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										30
									
								
								ema/views.py
									
									
									
									
									
								
							
							
						
						
									
										30
									
								
								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
 | 
			
		||||
@ -710,4 +711,27 @@ def deadline_remove_view(request: HttpRequest, id: str, deadline_id: str):
 | 
			
		||||
        request,
 | 
			
		||||
        msg_success=DEADLINE_REMOVED,
 | 
			
		||||
        redirect_url=reverse("ema:detail", args=(id,)) + "#related_data"
 | 
			
		||||
    )
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@login_required
 | 
			
		||||
@conservation_office_group_required
 | 
			
		||||
@shared_access_required(Ema, "id")
 | 
			
		||||
def create_resubmission_view(request: HttpRequest, id: str):
 | 
			
		||||
    """ Renders resubmission form for an EMA
 | 
			
		||||
 | 
			
		||||
    Args:
 | 
			
		||||
        request (HttpRequest): The incoming request
 | 
			
		||||
        id (str): EMA's id
 | 
			
		||||
 | 
			
		||||
    Returns:
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    ema = get_object_or_404(Ema, id=id)
 | 
			
		||||
    form = ResubmissionModalForm(request.POST or None, instance=ema, request=request)
 | 
			
		||||
    form.action_url = reverse("ema:resubmission-create", args=(id,))
 | 
			
		||||
    return form.process_request(
 | 
			
		||||
        request,
 | 
			
		||||
        msg_success=_("Resubmission set"),
 | 
			
		||||
        redirect_url=reverse("ema:detail", args=(id,))
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
@ -8,6 +8,7 @@ Created on: 02.12.20
 | 
			
		||||
from dal import autocomplete
 | 
			
		||||
from django import forms
 | 
			
		||||
 | 
			
		||||
from konova.forms.base_form import BaseForm
 | 
			
		||||
from konova.utils.message_templates import EDITED_GENERAL_DATA
 | 
			
		||||
from user.models import User
 | 
			
		||||
from django.db import transaction
 | 
			
		||||
@ -19,7 +20,7 @@ from codelist.settings import CODELIST_PROCESS_TYPE_ID, CODELIST_LAW_ID, \
 | 
			
		||||
    CODELIST_REGISTRATION_OFFICE_ID, CODELIST_CONSERVATION_OFFICE_ID, CODELIST_HANDLER_ID
 | 
			
		||||
from intervention.inputs import GenerateInput
 | 
			
		||||
from intervention.models import Intervention, Legal, Responsibility, Handler
 | 
			
		||||
from konova.forms import BaseForm, SimpleGeomForm
 | 
			
		||||
from konova.forms.geometry_form import SimpleGeomForm
 | 
			
		||||
from user.models import UserActionLogEntry
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -19,7 +19,8 @@ from django.utils.translation import gettext_lazy as _
 | 
			
		||||
from compensation.models import EcoAccount, EcoAccountDeduction
 | 
			
		||||
from intervention.inputs import TextToClipboardInput
 | 
			
		||||
from intervention.models import Intervention, InterventionDocument, RevocationDocument
 | 
			
		||||
from konova.forms import BaseModalForm, NewDocumentModalForm, RemoveModalForm
 | 
			
		||||
from konova.forms.modals import BaseModalForm
 | 
			
		||||
from konova.forms.modals import NewDocumentModalForm, RemoveModalForm
 | 
			
		||||
from konova.utils.general import format_german_float
 | 
			
		||||
from konova.utils.user_checks import is_default_group_only
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										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)
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										688
									
								
								konova/forms.py
									
									
									
									
									
								
							
							
						
						
									
										688
									
								
								konova/forms.py
									
									
									
									
									
								
							@ -1,688 +0,0 @@
 | 
			
		||||
"""
 | 
			
		||||
Author: Michel Peltriaux
 | 
			
		||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
 | 
			
		||||
Contact: michel.peltriaux@sgdnord.rlp.de
 | 
			
		||||
Created on: 16.11.20
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
import json
 | 
			
		||||
from abc import abstractmethod
 | 
			
		||||
 | 
			
		||||
from bootstrap_modal_forms.forms import BSModalForm
 | 
			
		||||
from bootstrap_modal_forms.utils import is_ajax
 | 
			
		||||
from django import forms
 | 
			
		||||
from django.contrib import messages
 | 
			
		||||
from django.contrib.gis import gdal
 | 
			
		||||
from django.db.models.fields.files import FieldFile
 | 
			
		||||
 | 
			
		||||
from compensation.models import EcoAccount
 | 
			
		||||
from konova.sub_settings.lanis_settings import DEFAULT_SRID_RLP
 | 
			
		||||
from user.models import User
 | 
			
		||||
from django.contrib.gis.forms import MultiPolygonField
 | 
			
		||||
from django.contrib.gis.geos import MultiPolygon, Polygon
 | 
			
		||||
from django.db import transaction
 | 
			
		||||
from django.http import HttpRequest, HttpResponseRedirect
 | 
			
		||||
from django.shortcuts import render
 | 
			
		||||
from django.utils.translation import gettext_lazy as _
 | 
			
		||||
 | 
			
		||||
from konova.contexts import BaseContext
 | 
			
		||||
from konova.models import BaseObject, Geometry, RecordableObjectMixin, AbstractDocument
 | 
			
		||||
from konova.settings import DEFAULT_SRID
 | 
			
		||||
from konova.tasks import celery_update_parcels
 | 
			
		||||
from konova.utils.message_templates import FORM_INVALID, FILE_TYPE_UNSUPPORTED, FILE_SIZE_TOO_LARGE, DOCUMENT_EDITED
 | 
			
		||||
from user.models import UserActionLogEntry
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class BaseForm(forms.Form):
 | 
			
		||||
    """
 | 
			
		||||
    Basic form for that holds attributes needed in all other forms
 | 
			
		||||
    """
 | 
			
		||||
    template = None
 | 
			
		||||
    action_url = None
 | 
			
		||||
    action_btn_label = _("Save")
 | 
			
		||||
    form_title = None
 | 
			
		||||
    cancel_redirect = None
 | 
			
		||||
    form_caption = None
 | 
			
		||||
    instance = None  # The data holding model object
 | 
			
		||||
    request = None
 | 
			
		||||
    form_attrs = {}  # Holds additional attributes, that can be used in the template
 | 
			
		||||
    has_required_fields = False  # Automatically set. Triggers hint rendering in templates
 | 
			
		||||
    show_cancel_btn = True
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        self.instance = kwargs.pop("instance", None)
 | 
			
		||||
        super().__init__(*args, **kwargs)
 | 
			
		||||
        if self.request is not None:
 | 
			
		||||
            self.user = self.request.user
 | 
			
		||||
        # Check for required fields
 | 
			
		||||
        for _field_name, _field_val in self.fields.items():
 | 
			
		||||
            if _field_val.required:
 | 
			
		||||
                self.has_required_fields = True
 | 
			
		||||
                break
 | 
			
		||||
 | 
			
		||||
        self.check_for_recorded_instance()
 | 
			
		||||
 | 
			
		||||
    @abstractmethod
 | 
			
		||||
    def save(self):
 | 
			
		||||
        # To be implemented in subclasses!
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    def disable_form_field(self, field: str):
 | 
			
		||||
        """
 | 
			
		||||
        Disables a form field for user editing
 | 
			
		||||
        """
 | 
			
		||||
        self.fields[field].widget.attrs["readonly"] = True
 | 
			
		||||
        self.fields[field].disabled = True
 | 
			
		||||
        self.fields[field].widget.attrs["title"] = _("Not editable")
 | 
			
		||||
 | 
			
		||||
    def initialize_form_field(self, field: str, val):
 | 
			
		||||
        """
 | 
			
		||||
        Initializes a form field with a value
 | 
			
		||||
        """
 | 
			
		||||
        self.fields[field].initial = val
 | 
			
		||||
 | 
			
		||||
    def add_placeholder_for_field(self, field: str, val):
 | 
			
		||||
        """
 | 
			
		||||
        Adds a placeholder to a field after initialization without the need to redefine the form widget
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            field (str): Field name
 | 
			
		||||
            val (str): Placeholder
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        self.fields[field].widget.attrs["placeholder"] = val
 | 
			
		||||
 | 
			
		||||
    def load_initial_data(self, form_data: dict, disabled_fields: list = None):
 | 
			
		||||
        """ Initializes form data from instance
 | 
			
		||||
 | 
			
		||||
        Inserts instance data into form and disables form fields
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        if self.instance is None:
 | 
			
		||||
            return
 | 
			
		||||
        for k, v in form_data.items():
 | 
			
		||||
            self.initialize_form_field(k, v)
 | 
			
		||||
        if disabled_fields:
 | 
			
		||||
            for field in disabled_fields:
 | 
			
		||||
                self.disable_form_field(field)
 | 
			
		||||
 | 
			
		||||
    def add_widget_html_class(self, field: str, cls: str):
 | 
			
		||||
        """ Adds a HTML class string to the widget of a field
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            field (str): The field's name
 | 
			
		||||
            cls (str): The new class string
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        set_class = self.fields[field].widget.attrs.get("class", "")
 | 
			
		||||
        if cls in set_class:
 | 
			
		||||
            return
 | 
			
		||||
        else:
 | 
			
		||||
            set_class += " " + cls
 | 
			
		||||
        self.fields[field].widget.attrs["class"] = set_class
 | 
			
		||||
 | 
			
		||||
    def remove_widget_html_class(self, field: str, cls: str):
 | 
			
		||||
        """ Removes a HTML class string from the widget of a field
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            field (str): The field's name
 | 
			
		||||
            cls (str): The new class string
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        set_class = self.fields[field].widget.attrs.get("class", "")
 | 
			
		||||
        set_class = set_class.replace(cls, "")
 | 
			
		||||
        self.fields[field].widget.attrs["class"] = set_class
 | 
			
		||||
 | 
			
		||||
    def check_for_recorded_instance(self):
 | 
			
		||||
        """ Checks if the instance is recorded and runs some special logic if yes
 | 
			
		||||
 | 
			
		||||
        If the instance is recorded, the form shall not display any possibility to
 | 
			
		||||
        edit any data. Instead, the users should get some information about why they can not edit anything.
 | 
			
		||||
 | 
			
		||||
        There are situations where the form should be rendered regularly,
 | 
			
		||||
        e.g deduction forms for (recorded) eco accounts.
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        from intervention.forms.modalForms import NewDeductionModalForm, EditEcoAccountDeductionModalForm, \
 | 
			
		||||
            RemoveEcoAccountDeductionModalForm
 | 
			
		||||
        is_none = self.instance is None
 | 
			
		||||
        is_other_data_type = not isinstance(self.instance, BaseObject)
 | 
			
		||||
        is_deduction_form_from_account = isinstance(
 | 
			
		||||
            self,
 | 
			
		||||
            (
 | 
			
		||||
                NewDeductionModalForm,
 | 
			
		||||
                EditEcoAccountDeductionModalForm,
 | 
			
		||||
                RemoveEcoAccountDeductionModalForm,
 | 
			
		||||
            )
 | 
			
		||||
        ) and isinstance(self.instance, EcoAccount)
 | 
			
		||||
 | 
			
		||||
        if is_none or is_other_data_type or is_deduction_form_from_account:
 | 
			
		||||
            # Do nothing
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        if self.instance.is_recorded:
 | 
			
		||||
            self.template = "form/recorded_no_edit.html"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class RemoveForm(BaseForm):
 | 
			
		||||
    check = forms.BooleanField(
 | 
			
		||||
        label=_("Confirm"),
 | 
			
		||||
        label_suffix=_(""),
 | 
			
		||||
        required=True,
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        self.object_to_remove = kwargs.pop("object_to_remove", None)
 | 
			
		||||
        self.remove_post_url = kwargs.pop("remove_post_url", "")
 | 
			
		||||
        self.cancel_url = kwargs.pop("cancel_url", "")
 | 
			
		||||
 | 
			
		||||
        super().__init__(*args, **kwargs)
 | 
			
		||||
 | 
			
		||||
        self.form_title = _("Remove")
 | 
			
		||||
        if self.object_to_remove is not None:
 | 
			
		||||
            self.form_caption = _("You are about to remove {} {}").format(self.object_to_remove.__class__.__name__, self.object_to_remove)
 | 
			
		||||
        self.action_url = self.remove_post_url
 | 
			
		||||
        self.cancel_redirect = self.cancel_url
 | 
			
		||||
 | 
			
		||||
    def is_checked(self) -> bool:
 | 
			
		||||
        return self.cleaned_data.get("check", False)
 | 
			
		||||
 | 
			
		||||
    def save(self, user: User):
 | 
			
		||||
        """ Perform generic removing by running the form typical 'save()' method
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            user (User): The performing user
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        if self.object_to_remove is not None and self.is_checked():
 | 
			
		||||
            with transaction.atomic():
 | 
			
		||||
                self.object_to_remove.is_active = False
 | 
			
		||||
                action = UserActionLogEntry.get_deleted_action(user)
 | 
			
		||||
                self.object_to_remove.deleted = action
 | 
			
		||||
                self.object_to_remove.save()
 | 
			
		||||
        return self.object_to_remove
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class BaseModalForm(BaseForm, BSModalForm):
 | 
			
		||||
    """ A specialzed form class for modal form handling
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    is_modal_form = True
 | 
			
		||||
    render_submit = True
 | 
			
		||||
    template = "modal/modal_form.html"
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        super().__init__(*args, **kwargs)
 | 
			
		||||
        self.action_btn_label = _("Continue")
 | 
			
		||||
 | 
			
		||||
    def process_request(self, request: HttpRequest, msg_success: str = _("Object removed"), msg_error: str = FORM_INVALID, redirect_url: str = None):
 | 
			
		||||
        """ Generic processing of request
 | 
			
		||||
 | 
			
		||||
        Wraps the request processing logic, so we don't need the same code everywhere a RemoveModalForm is being used
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            request (HttpRequest): The incoming request
 | 
			
		||||
            msg_success (str): The message in case of successful removing
 | 
			
		||||
            msg_error (str): The message in case of an error
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        redirect_url = redirect_url if redirect_url is not None else request.META.get("HTTP_REFERER", "home")
 | 
			
		||||
        template = self.template
 | 
			
		||||
        if request.method == "POST":
 | 
			
		||||
            if self.is_valid():
 | 
			
		||||
                if not is_ajax(request.META):
 | 
			
		||||
                    # Modal forms send one POST for checking on data validity. This can be used to return possible errors
 | 
			
		||||
                    # on the form. A second POST (if no errors occured) is sent afterwards and needs to process the
 | 
			
		||||
                    # saving/commiting of the data to the database. is_ajax() performs this check. The first request is
 | 
			
		||||
                    # an ajax call, the second is a regular form POST.
 | 
			
		||||
                    self.save()
 | 
			
		||||
                    messages.success(
 | 
			
		||||
                        request,
 | 
			
		||||
                        msg_success
 | 
			
		||||
                    )
 | 
			
		||||
                return HttpResponseRedirect(redirect_url)
 | 
			
		||||
            else:
 | 
			
		||||
                context = {
 | 
			
		||||
                    "form": self,
 | 
			
		||||
                }
 | 
			
		||||
                context = BaseContext(request, context).context
 | 
			
		||||
                return render(request, template, context)
 | 
			
		||||
        elif request.method == "GET":
 | 
			
		||||
            context = {
 | 
			
		||||
                "form": self,
 | 
			
		||||
            }
 | 
			
		||||
            context = BaseContext(request, context).context
 | 
			
		||||
            return render(request, template, context)
 | 
			
		||||
        else:
 | 
			
		||||
            raise NotImplementedError
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SimpleGeomForm(BaseForm):
 | 
			
		||||
    """ A geometry form for rendering geometry read-only using a widget
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    read_only = True
 | 
			
		||||
    geom = MultiPolygonField(
 | 
			
		||||
        srid=DEFAULT_SRID_RLP,
 | 
			
		||||
        label=_("Geometry"),
 | 
			
		||||
        help_text=_(""),
 | 
			
		||||
        label_suffix="",
 | 
			
		||||
        required=False,
 | 
			
		||||
        disabled=False,
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        self.read_only = kwargs.pop("read_only", True)
 | 
			
		||||
        super().__init__(*args, **kwargs)
 | 
			
		||||
 | 
			
		||||
        # Initialize geometry
 | 
			
		||||
        try:
 | 
			
		||||
            geom = self.instance.geometry.geom
 | 
			
		||||
            self.empty = geom.empty
 | 
			
		||||
 | 
			
		||||
            if self.empty:
 | 
			
		||||
                raise AttributeError
 | 
			
		||||
 | 
			
		||||
            geojson = self.instance.geometry.as_feature_collection(srid=DEFAULT_SRID_RLP)
 | 
			
		||||
            geom = json.dumps(geojson)
 | 
			
		||||
        except AttributeError:
 | 
			
		||||
            # If no geometry exists for this form, we simply set the value to None and zoom to the maximum level
 | 
			
		||||
            geom = ""
 | 
			
		||||
            self.empty = True
 | 
			
		||||
 | 
			
		||||
        self.initialize_form_field("geom", geom)
 | 
			
		||||
 | 
			
		||||
    def is_valid(self):
 | 
			
		||||
        super().is_valid()
 | 
			
		||||
        is_valid = True
 | 
			
		||||
 | 
			
		||||
        # Get geojson from form
 | 
			
		||||
        geom = self.data["geom"]
 | 
			
		||||
        if geom is None or len(geom) == 0:
 | 
			
		||||
            # empty geometry is a valid geometry
 | 
			
		||||
            return is_valid
 | 
			
		||||
        geom = json.loads(geom)
 | 
			
		||||
 | 
			
		||||
        # Write submitted data back into form field to make sure invalid geometry
 | 
			
		||||
        # will be rendered again on failed submit
 | 
			
		||||
        self.initialize_form_field("geom", self.data["geom"])
 | 
			
		||||
 | 
			
		||||
        # Read geojson into gdal geometry
 | 
			
		||||
        # HINT: This can be simplified if the geojson format holds data in epsg:4326 (GDAL provides direct creation for
 | 
			
		||||
        # this case)
 | 
			
		||||
        features = []
 | 
			
		||||
        features_json = geom.get("features", [])
 | 
			
		||||
        for feature in features_json:
 | 
			
		||||
            g = gdal.OGRGeometry(json.dumps(feature.get("geometry", feature)), srs=DEFAULT_SRID_RLP)
 | 
			
		||||
            if g.geom_type not in ["Polygon", "MultiPolygon"]:
 | 
			
		||||
                self.add_error("geom", _("Only surfaces allowed. Points or lines must be buffered."))
 | 
			
		||||
                is_valid = False
 | 
			
		||||
                return is_valid
 | 
			
		||||
 | 
			
		||||
            polygon = Polygon.from_ewkt(g.ewkt)
 | 
			
		||||
            is_valid = polygon.valid
 | 
			
		||||
            if not is_valid:
 | 
			
		||||
                self.add_error("geom", polygon.valid_reason)
 | 
			
		||||
                return is_valid
 | 
			
		||||
 | 
			
		||||
            features.append(polygon)
 | 
			
		||||
        form_geom = MultiPolygon(srid=DEFAULT_SRID_RLP)
 | 
			
		||||
        for feature in features:
 | 
			
		||||
            form_geom = form_geom.union(feature)
 | 
			
		||||
 | 
			
		||||
        # Make sure to convert into a MultiPolygon. Relevant if a single Polygon is provided.
 | 
			
		||||
        if form_geom.geom_type != "MultiPolygon":
 | 
			
		||||
            form_geom = MultiPolygon(form_geom, srid=DEFAULT_SRID_RLP)
 | 
			
		||||
 | 
			
		||||
        # Write unioned Multipolygon into cleaned data
 | 
			
		||||
        if self.cleaned_data is None:
 | 
			
		||||
            self.cleaned_data = {}
 | 
			
		||||
        self.cleaned_data["geom"] = form_geom.ewkt
 | 
			
		||||
 | 
			
		||||
        return is_valid
 | 
			
		||||
 | 
			
		||||
    def save(self, action: UserActionLogEntry):
 | 
			
		||||
        """ Saves the form's geometry
 | 
			
		||||
 | 
			
		||||
        Creates a new geometry entry if none is set, yet
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            action ():
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        try:
 | 
			
		||||
            if self.instance is None or self.instance.geometry is None:
 | 
			
		||||
                raise LookupError
 | 
			
		||||
            geometry = self.instance.geometry
 | 
			
		||||
            geometry.geom = self.cleaned_data.get("geom", MultiPolygon(srid=DEFAULT_SRID_RLP))
 | 
			
		||||
            geometry.modified = action
 | 
			
		||||
 | 
			
		||||
            geometry.save()
 | 
			
		||||
        except LookupError:
 | 
			
		||||
            # No geometry or linked instance holding a geometry exist --> create a new one!
 | 
			
		||||
            geometry = Geometry.objects.create(
 | 
			
		||||
                geom=self.cleaned_data.get("geom", MultiPolygon(srid=DEFAULT_SRID_RLP)),
 | 
			
		||||
                created=action,
 | 
			
		||||
            )
 | 
			
		||||
        # Start the parcel update procedure in a background process
 | 
			
		||||
        celery_update_parcels.delay(geometry.id)
 | 
			
		||||
        return geometry
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class RemoveModalForm(BaseModalForm):
 | 
			
		||||
    """ Generic removing modal form
 | 
			
		||||
 | 
			
		||||
    Can be used for anything, where removing shall be confirmed by the user a second time.
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    confirm = forms.BooleanField(
 | 
			
		||||
        label=_("Confirm"),
 | 
			
		||||
        label_suffix=_(""),
 | 
			
		||||
        widget=forms.CheckboxInput(),
 | 
			
		||||
        required=True,
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        self.template = "modal/modal_form.html"
 | 
			
		||||
        super().__init__(*args, **kwargs)
 | 
			
		||||
        self.form_title = _("Remove")
 | 
			
		||||
        self.form_caption = _("Are you sure?")
 | 
			
		||||
        # Disable automatic w-100 setting for this type of modal form. Looks kinda strange
 | 
			
		||||
        self.fields["confirm"].widget.attrs["class"] = ""
 | 
			
		||||
 | 
			
		||||
    def save(self):
 | 
			
		||||
        if isinstance(self.instance, BaseObject):
 | 
			
		||||
            self.instance.mark_as_deleted(self.user)
 | 
			
		||||
        else:
 | 
			
		||||
            # If the class does not provide restorable delete functionality, we must delete the entry finally
 | 
			
		||||
            self.instance.delete()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class RemoveDeadlineModalForm(RemoveModalForm):
 | 
			
		||||
    """ Removing modal form for deadlines
 | 
			
		||||
 | 
			
		||||
    Can be used for anything, where removing shall be confirmed by the user a second time.
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    deadline = None
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        deadline = kwargs.pop("deadline", None)
 | 
			
		||||
        self.deadline = deadline
 | 
			
		||||
        super().__init__(*args, **kwargs)
 | 
			
		||||
 | 
			
		||||
    def save(self):
 | 
			
		||||
        self.instance.remove_deadline(self)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class NewDocumentModalForm(BaseModalForm):
 | 
			
		||||
    """ Modal form for new documents
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    title = forms.CharField(
 | 
			
		||||
        label=_("Title"),
 | 
			
		||||
        label_suffix=_(""),
 | 
			
		||||
        max_length=500,
 | 
			
		||||
        widget=forms.TextInput(
 | 
			
		||||
            attrs={
 | 
			
		||||
                "class": "form-control",
 | 
			
		||||
            }
 | 
			
		||||
        )
 | 
			
		||||
    )
 | 
			
		||||
    creation_date = forms.DateField(
 | 
			
		||||
        label=_("Created on"),
 | 
			
		||||
        label_suffix=_(""),
 | 
			
		||||
        help_text=_("When has this file been created? Important for photos."),
 | 
			
		||||
        widget=forms.DateInput(
 | 
			
		||||
            attrs={
 | 
			
		||||
                "type": "date",
 | 
			
		||||
                "data-provide": "datepicker",
 | 
			
		||||
                "class": "form-control",
 | 
			
		||||
            },
 | 
			
		||||
            format="%d.%m.%Y"
 | 
			
		||||
        )
 | 
			
		||||
    )
 | 
			
		||||
    file = forms.FileField(
 | 
			
		||||
        label=_("File"),
 | 
			
		||||
        label_suffix=_(""),
 | 
			
		||||
        help_text=_("Allowed formats: pdf, jpg, png. Max size 15 MB."),
 | 
			
		||||
        widget=forms.FileInput(
 | 
			
		||||
            attrs={
 | 
			
		||||
                "class": "form-control-file",
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
    )
 | 
			
		||||
    comment = forms.CharField(
 | 
			
		||||
        required=False,
 | 
			
		||||
        max_length=200,
 | 
			
		||||
        label=_("Comment"),
 | 
			
		||||
        label_suffix=_(""),
 | 
			
		||||
        help_text=_("Additional comment, maximum {} letters").format(200),
 | 
			
		||||
        widget=forms.Textarea(
 | 
			
		||||
            attrs={
 | 
			
		||||
                "cols": 30,
 | 
			
		||||
                "rows": 5,
 | 
			
		||||
                "class": "form-control",
 | 
			
		||||
            }
 | 
			
		||||
        )
 | 
			
		||||
    )
 | 
			
		||||
    document_model = None
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        abstract = True
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        super().__init__(*args, **kwargs)
 | 
			
		||||
        self.form_title = _("Add new document")
 | 
			
		||||
        self.form_caption = _("")
 | 
			
		||||
        self.form_attrs = {
 | 
			
		||||
            "enctype": "multipart/form-data",  # important for file upload
 | 
			
		||||
        }
 | 
			
		||||
        if not self.document_model:
 | 
			
		||||
            raise NotImplementedError("Unsupported document type for {}".format(self.instance.__class__))
 | 
			
		||||
 | 
			
		||||
    def is_valid(self):
 | 
			
		||||
        super_valid = super().is_valid()
 | 
			
		||||
 | 
			
		||||
        _file = self.cleaned_data.get("file", None)
 | 
			
		||||
 | 
			
		||||
        if _file is None or isinstance(_file, FieldFile):
 | 
			
		||||
            # FieldFile declares that no new file has been uploaded and we do not need to check on the file again
 | 
			
		||||
            return super_valid
 | 
			
		||||
 | 
			
		||||
        mime_type_valid = self.document_model.is_mime_type_valid(_file)
 | 
			
		||||
        if not mime_type_valid:
 | 
			
		||||
            self.add_error(
 | 
			
		||||
                "file",
 | 
			
		||||
                FILE_TYPE_UNSUPPORTED
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        file_size_valid = self.document_model.is_file_size_valid(_file)
 | 
			
		||||
        if not file_size_valid:
 | 
			
		||||
            self.add_error(
 | 
			
		||||
                "file",
 | 
			
		||||
                FILE_SIZE_TOO_LARGE
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        file_valid = mime_type_valid and file_size_valid
 | 
			
		||||
        return super_valid and file_valid
 | 
			
		||||
 | 
			
		||||
    def save(self):
 | 
			
		||||
        with transaction.atomic():
 | 
			
		||||
            action = UserActionLogEntry.get_created_action(self.user)
 | 
			
		||||
            edited_action = UserActionLogEntry.get_edited_action(self.user, _("Added document"))
 | 
			
		||||
 | 
			
		||||
            doc = self.document_model.objects.create(
 | 
			
		||||
                created=action,
 | 
			
		||||
                title=self.cleaned_data["title"],
 | 
			
		||||
                comment=self.cleaned_data["comment"],
 | 
			
		||||
                file=self.cleaned_data["file"],
 | 
			
		||||
                date_of_creation=self.cleaned_data["creation_date"],
 | 
			
		||||
                instance=self.instance,
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
            self.instance.log.add(edited_action)
 | 
			
		||||
            self.instance.modified = edited_action
 | 
			
		||||
            self.instance.save()
 | 
			
		||||
 | 
			
		||||
            return doc
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class EditDocumentModalForm(NewDocumentModalForm):
 | 
			
		||||
    document = None
 | 
			
		||||
    document_model = AbstractDocument
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        self.document = kwargs.pop("document", None)
 | 
			
		||||
        super().__init__(*args, **kwargs)
 | 
			
		||||
        self.form_title = _("Edit document")
 | 
			
		||||
        form_data = {
 | 
			
		||||
            "title": self.document.title,
 | 
			
		||||
            "comment": self.document.comment,
 | 
			
		||||
            "creation_date": str(self.document.date_of_creation),
 | 
			
		||||
            "file": self.document.file,
 | 
			
		||||
        }
 | 
			
		||||
        self.load_initial_data(form_data)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def save(self):
 | 
			
		||||
        with transaction.atomic():
 | 
			
		||||
            document = self.document
 | 
			
		||||
            file = self.cleaned_data.get("file", None)
 | 
			
		||||
 | 
			
		||||
            document.title = self.cleaned_data.get("title", None)
 | 
			
		||||
            document.comment = self.cleaned_data.get("comment", None)
 | 
			
		||||
            document.date_of_creation = self.cleaned_data.get("creation_date", None)
 | 
			
		||||
            if not isinstance(file, FieldFile):
 | 
			
		||||
                document.replace_file(file)
 | 
			
		||||
            document.save()
 | 
			
		||||
 | 
			
		||||
            self.instance.mark_as_edited(self.user, self.request, edit_comment=DOCUMENT_EDITED)
 | 
			
		||||
 | 
			
		||||
            return document
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class RecordModalForm(BaseModalForm):
 | 
			
		||||
    """ Modal form for recording data
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    confirm = forms.BooleanField(
 | 
			
		||||
        label=_("Confirm record"),
 | 
			
		||||
        label_suffix="",
 | 
			
		||||
        widget=forms.CheckboxInput(),
 | 
			
		||||
        required=True,
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        super().__init__(*args, **kwargs)
 | 
			
		||||
        self.form_title = _("Record data")
 | 
			
		||||
        self.form_caption = _("I, {} {}, confirm that all necessary control steps have been performed by myself.").format(self.user.first_name, self.user.last_name)
 | 
			
		||||
        # Disable automatic w-100 setting for this type of modal form. Looks kinda strange
 | 
			
		||||
        self.fields["confirm"].widget.attrs["class"] = ""
 | 
			
		||||
 | 
			
		||||
        if self.instance.recorded:
 | 
			
		||||
            # unrecord!
 | 
			
		||||
            self.fields["confirm"].label = _("Confirm unrecord")
 | 
			
		||||
            self.form_title = _("Unrecord data")
 | 
			
		||||
            self.form_caption = _("I, {} {}, confirm that this data must be unrecorded.").format(self.user.first_name, self.user.last_name)
 | 
			
		||||
 | 
			
		||||
        if not isinstance(self.instance, RecordableObjectMixin):
 | 
			
		||||
            raise NotImplementedError
 | 
			
		||||
 | 
			
		||||
    def is_valid(self):
 | 
			
		||||
        """ Checks for instance's validity and data quality
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        from intervention.models import Intervention
 | 
			
		||||
        super_val = super().is_valid()
 | 
			
		||||
        if self.instance.recorded:
 | 
			
		||||
            # If user wants to unrecord an already recorded dataset, we do not need to perform custom checks
 | 
			
		||||
            return super_val
 | 
			
		||||
        checker = self.instance.quality_check()
 | 
			
		||||
        for msg in checker.messages:
 | 
			
		||||
            self.add_error(
 | 
			
		||||
                "confirm",
 | 
			
		||||
                msg
 | 
			
		||||
            )
 | 
			
		||||
        valid = checker.valid
 | 
			
		||||
        # Special case: Intervention
 | 
			
		||||
        # Add direct checks for related compensations
 | 
			
		||||
        if isinstance(self.instance, Intervention):
 | 
			
		||||
            comps_valid = self._are_compensations_valid()
 | 
			
		||||
            valid = valid and comps_valid
 | 
			
		||||
        return super_val and valid
 | 
			
		||||
 | 
			
		||||
    def _are_deductions_valid(self):
 | 
			
		||||
        """ Performs validity checks on deductions and their eco-account
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        deductions = self.instance.deductions.all()
 | 
			
		||||
        for deduction in deductions:
 | 
			
		||||
            checker = deduction.account.quality_check()
 | 
			
		||||
            for msg in checker.messages:
 | 
			
		||||
                self.add_error(
 | 
			
		||||
                    "confirm",
 | 
			
		||||
                    f"{deduction.account.identifier}: {msg}"
 | 
			
		||||
                )
 | 
			
		||||
            return checker.valid
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
    def _are_compensations_valid(self):
 | 
			
		||||
        """ Runs a special case for intervention-compensations validity
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        comps = self.instance.compensations.filter(
 | 
			
		||||
            deleted=None,
 | 
			
		||||
        )
 | 
			
		||||
        comps_valid = True
 | 
			
		||||
        for comp in comps:
 | 
			
		||||
            checker = comp.quality_check()
 | 
			
		||||
            comps_valid = comps_valid and checker.valid
 | 
			
		||||
            for msg in checker.messages:
 | 
			
		||||
                self.add_error(
 | 
			
		||||
                    "confirm",
 | 
			
		||||
                    f"{comp.identifier}: {msg}"
 | 
			
		||||
                )
 | 
			
		||||
 | 
			
		||||
        deductions_valid = self._are_deductions_valid()
 | 
			
		||||
 | 
			
		||||
        return comps_valid and deductions_valid
 | 
			
		||||
 | 
			
		||||
    def save(self):
 | 
			
		||||
        with transaction.atomic():
 | 
			
		||||
            if self.cleaned_data["confirm"]:
 | 
			
		||||
                if self.instance.recorded:
 | 
			
		||||
                    self.instance.set_unrecorded(self.user)
 | 
			
		||||
                else:
 | 
			
		||||
                    self.instance.set_recorded(self.user)
 | 
			
		||||
        return self.instance
 | 
			
		||||
 | 
			
		||||
    def check_for_recorded_instance(self):
 | 
			
		||||
        """ Overwrite the check method for doing nothing on the RecordModalForm
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        pass
 | 
			
		||||
							
								
								
									
										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)
 | 
			
		||||
@ -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 *
 | 
			
		||||
 | 
			
		||||
@ -743,4 +743,23 @@ class GeoReferencedMixin(models.Model):
 | 
			
		||||
            zoom_lvl,
 | 
			
		||||
            x,
 | 
			
		||||
            y,
 | 
			
		||||
        )
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ResubmitableObjectMixin(models.Model):
 | 
			
		||||
    resubmissions = models.ManyToManyField(
 | 
			
		||||
        "konova.Resubmission",
 | 
			
		||||
        blank=True,
 | 
			
		||||
        related_name="+",
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        abstract = True
 | 
			
		||||
 | 
			
		||||
    def resubmit(self):
 | 
			
		||||
        """ Run resubmit check and run for all related resubmissions
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        resubmissions = self.resubmissions.all()
 | 
			
		||||
        for resubmission in resubmissions:
 | 
			
		||||
            resubmission.send_resubmission_mail(self.identifier)
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										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()
 | 
			
		||||
@ -5,10 +5,9 @@ Contact: michel.peltriaux@sgdnord.rlp.de
 | 
			
		||||
Created on: 01.09.21
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
from django.http import FileResponse, HttpRequest, HttpResponse, Http404
 | 
			
		||||
from django.utils.translation import gettext_lazy as _
 | 
			
		||||
from django.http import FileResponse, HttpRequest, Http404
 | 
			
		||||
 | 
			
		||||
from konova.forms import RemoveModalForm
 | 
			
		||||
from konova.forms.modals import RemoveModalForm
 | 
			
		||||
from konova.models import AbstractDocument
 | 
			
		||||
from konova.utils.message_templates import DOCUMENT_REMOVED_TEMPLATE
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -398,3 +398,26 @@ class Mailer:
 | 
			
		||||
            msg
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def send_mail_resubmission(self, obj_identifier, resubmission):
 | 
			
		||||
        """ Send a resubmission mail for a user
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            obj_identifier (str): The (resubmitted) object's identifier
 | 
			
		||||
            resubmission (Resubmission): The resubmission
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        context = {
 | 
			
		||||
            "obj_identifier": obj_identifier,
 | 
			
		||||
            "resubmission": resubmission,
 | 
			
		||||
            "EMAIL_REPLY_TO": EMAIL_REPLY_TO,
 | 
			
		||||
        }
 | 
			
		||||
        msg = render_to_string("email/resubmission/resubmission.html", context)
 | 
			
		||||
        user_mail_address = [SUPPORT_MAIL_RECIPIENT]
 | 
			
		||||
        self.send(
 | 
			
		||||
            user_mail_address,
 | 
			
		||||
            _("Resubmission - {}").format(obj_identifier),
 | 
			
		||||
            msg
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							@ -18,15 +18,16 @@
 | 
			
		||||
#: konova/filters/mixins.py:277 konova/filters/mixins.py:323
 | 
			
		||||
#: konova/filters/mixins.py:361 konova/filters/mixins.py:362
 | 
			
		||||
#: konova/filters/mixins.py:393 konova/filters/mixins.py:394
 | 
			
		||||
#: konova/forms.py:179 konova/forms.py:281 konova/forms.py:395
 | 
			
		||||
#: konova/forms.py:439 konova/forms.py:449 konova/forms.py:462
 | 
			
		||||
#: konova/forms.py:474 konova/forms.py:492 user/forms.py:42
 | 
			
		||||
#: konova/forms.py:183 konova/forms.py:285 konova/forms.py:399
 | 
			
		||||
#: konova/forms.py:443 konova/forms.py:453 konova/forms.py:466
 | 
			
		||||
#: konova/forms.py:478 konova/forms.py:496 konova/forms.py:696
 | 
			
		||||
#: konova/forms.py:711 user/forms.py:42
 | 
			
		||||
#, fuzzy
 | 
			
		||||
msgid ""
 | 
			
		||||
msgstr ""
 | 
			
		||||
"Project-Id-Version: PACKAGE VERSION\n"
 | 
			
		||||
"Report-Msgid-Bugs-To: \n"
 | 
			
		||||
"POT-Creation-Date: 2022-08-10 08:37+0200\n"
 | 
			
		||||
"POT-Creation-Date: 2022-08-15 09:39+0200\n"
 | 
			
		||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 | 
			
		||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 | 
			
		||||
"Language-Team: LANGUAGE <LL@li.org>\n"
 | 
			
		||||
@ -85,7 +86,7 @@ msgstr "Bericht generieren"
 | 
			
		||||
msgid "Select a timespan and the desired conservation office"
 | 
			
		||||
msgstr "Wählen Sie die Zeitspanne und die gewünschte Eintragungsstelle"
 | 
			
		||||
 | 
			
		||||
#: analysis/forms.py:71 konova/forms.py:227
 | 
			
		||||
#: analysis/forms.py:71 konova/forms.py:231
 | 
			
		||||
msgid "Continue"
 | 
			
		||||
msgstr "Weiter"
 | 
			
		||||
 | 
			
		||||
@ -241,7 +242,8 @@ msgstr ""
 | 
			
		||||
#: ema/templates/ema/detail/includes/states-after.html:36
 | 
			
		||||
#: ema/templates/ema/detail/includes/states-before.html:36
 | 
			
		||||
#: intervention/forms/modalForms.py:364
 | 
			
		||||
#: templates/email/other/deduction_changed.html:29
 | 
			
		||||
#: templates/email/other/deduction_changed.html:31
 | 
			
		||||
#: templates/email/other/deduction_changed_team.html:31
 | 
			
		||||
msgid "Surface"
 | 
			
		||||
msgstr "Fläche"
 | 
			
		||||
 | 
			
		||||
@ -308,7 +310,8 @@ msgstr "Typ"
 | 
			
		||||
#: intervention/forms/modalForms.py:382 intervention/tables.py:87
 | 
			
		||||
#: intervention/templates/intervention/detail/view.html:19
 | 
			
		||||
#: konova/templates/konova/includes/quickstart/interventions.html:4
 | 
			
		||||
#: templates/email/other/deduction_changed.html:24
 | 
			
		||||
#: templates/email/other/deduction_changed.html:26
 | 
			
		||||
#: templates/email/other/deduction_changed_team.html:26
 | 
			
		||||
#: templates/navbars/navbar.html:22
 | 
			
		||||
msgid "Intervention"
 | 
			
		||||
msgstr "Eingriff"
 | 
			
		||||
@ -362,7 +365,7 @@ msgstr "Automatisch generiert"
 | 
			
		||||
#: intervention/templates/intervention/detail/includes/documents.html:28
 | 
			
		||||
#: intervention/templates/intervention/detail/view.html:31
 | 
			
		||||
#: intervention/templates/intervention/report/report.html:12
 | 
			
		||||
#: konova/forms.py:438
 | 
			
		||||
#: konova/forms.py:442
 | 
			
		||||
msgid "Title"
 | 
			
		||||
msgstr "Bezeichnung"
 | 
			
		||||
 | 
			
		||||
@ -389,12 +392,13 @@ msgstr "Kompensation XY; Flur ABC"
 | 
			
		||||
#: intervention/templates/intervention/detail/includes/documents.html:34
 | 
			
		||||
#: intervention/templates/intervention/detail/includes/payments.html:34
 | 
			
		||||
#: intervention/templates/intervention/detail/includes/revocation.html:38
 | 
			
		||||
#: konova/forms.py:473 konova/templates/konova/includes/comment_card.html:16
 | 
			
		||||
#: konova/forms.py:477 konova/forms.py:710
 | 
			
		||||
#: konova/templates/konova/includes/comment_card.html:16
 | 
			
		||||
msgid "Comment"
 | 
			
		||||
msgstr "Kommentar"
 | 
			
		||||
 | 
			
		||||
#: compensation/forms/forms.py:59 compensation/forms/modalForms.py:471
 | 
			
		||||
#: intervention/forms/forms.py:200
 | 
			
		||||
#: intervention/forms/forms.py:200 konova/forms.py:712
 | 
			
		||||
msgid "Additional comment"
 | 
			
		||||
msgstr "Zusätzlicher Kommentar"
 | 
			
		||||
 | 
			
		||||
@ -479,7 +483,7 @@ msgstr "kompensiert Eingriff"
 | 
			
		||||
msgid "Select the intervention for which this compensation compensates"
 | 
			
		||||
msgstr "Wählen Sie den Eingriff, für den diese Kompensation bestimmt ist"
 | 
			
		||||
 | 
			
		||||
#: compensation/forms/forms.py:219 compensation/views/compensation.py:110
 | 
			
		||||
#: compensation/forms/forms.py:219 compensation/views/compensation.py:111
 | 
			
		||||
msgid "New compensation"
 | 
			
		||||
msgstr "Neue Kompensation"
 | 
			
		||||
 | 
			
		||||
@ -531,7 +535,7 @@ msgid "Due on which date"
 | 
			
		||||
msgstr "Zahlung wird an diesem Datum erwartet"
 | 
			
		||||
 | 
			
		||||
#: compensation/forms/modalForms.py:65 compensation/forms/modalForms.py:363
 | 
			
		||||
#: intervention/forms/modalForms.py:177 konova/forms.py:475
 | 
			
		||||
#: intervention/forms/modalForms.py:177 konova/forms.py:479
 | 
			
		||||
msgid "Additional comment, maximum {} letters"
 | 
			
		||||
msgstr "Zusätzlicher Kommentar, maximal {} Zeichen"
 | 
			
		||||
 | 
			
		||||
@ -576,7 +580,7 @@ msgstr "Neuer Zustand"
 | 
			
		||||
msgid "Insert data for the new state"
 | 
			
		||||
msgstr "Geben Sie die Daten des neuen Zustandes ein"
 | 
			
		||||
 | 
			
		||||
#: compensation/forms/modalForms.py:219 konova/forms.py:229
 | 
			
		||||
#: compensation/forms/modalForms.py:219 konova/forms.py:233
 | 
			
		||||
msgid "Object removed"
 | 
			
		||||
msgstr "Objekt entfernt"
 | 
			
		||||
 | 
			
		||||
@ -602,7 +606,7 @@ msgstr "Fristart wählen"
 | 
			
		||||
#: compensation/templates/compensation/detail/compensation/includes/deadlines.html:36
 | 
			
		||||
#: compensation/templates/compensation/detail/eco_account/includes/deadlines.html:36
 | 
			
		||||
#: ema/templates/ema/detail/includes/deadlines.html:36
 | 
			
		||||
#: intervention/forms/modalForms.py:149
 | 
			
		||||
#: intervention/forms/modalForms.py:149 konova/forms.py:697
 | 
			
		||||
msgid "Date"
 | 
			
		||||
msgstr "Datum"
 | 
			
		||||
 | 
			
		||||
@ -850,24 +854,32 @@ msgstr "In LANIS öffnen"
 | 
			
		||||
msgid "Public report"
 | 
			
		||||
msgstr "Öffentlicher Bericht"
 | 
			
		||||
 | 
			
		||||
#: compensation/templates/compensation/detail/compensation/includes/controls.html:17
 | 
			
		||||
#: compensation/templates/compensation/detail/eco_account/includes/controls.html:31
 | 
			
		||||
#: ema/templates/ema/detail/includes/controls.html:31
 | 
			
		||||
#: intervention/templates/intervention/detail/includes/controls.html:36
 | 
			
		||||
#: compensation/templates/compensation/detail/compensation/includes/controls.html:15
 | 
			
		||||
#: compensation/templates/compensation/detail/eco_account/includes/controls.html:15
 | 
			
		||||
#: ema/templates/ema/detail/includes/controls.html:15
 | 
			
		||||
#: intervention/templates/intervention/detail/includes/controls.html:15
 | 
			
		||||
#: konova/forms.py:724 templates/email/resubmission/resubmission.html:4
 | 
			
		||||
msgid "Resubmission"
 | 
			
		||||
msgstr "Wiedervorlage"
 | 
			
		||||
 | 
			
		||||
#: compensation/templates/compensation/detail/compensation/includes/controls.html:20
 | 
			
		||||
#: compensation/templates/compensation/detail/eco_account/includes/controls.html:34
 | 
			
		||||
#: ema/templates/ema/detail/includes/controls.html:34
 | 
			
		||||
#: intervention/templates/intervention/detail/includes/controls.html:39
 | 
			
		||||
msgid "Edit"
 | 
			
		||||
msgstr "Bearbeiten"
 | 
			
		||||
 | 
			
		||||
#: compensation/templates/compensation/detail/compensation/includes/controls.html:21
 | 
			
		||||
#: compensation/templates/compensation/detail/eco_account/includes/controls.html:35
 | 
			
		||||
#: ema/templates/ema/detail/includes/controls.html:35
 | 
			
		||||
#: intervention/templates/intervention/detail/includes/controls.html:40
 | 
			
		||||
msgid "Show log"
 | 
			
		||||
msgstr "Log anzeigen"
 | 
			
		||||
 | 
			
		||||
#: compensation/templates/compensation/detail/compensation/includes/controls.html:24
 | 
			
		||||
#: compensation/templates/compensation/detail/eco_account/includes/controls.html:38
 | 
			
		||||
#: ema/templates/ema/detail/includes/controls.html:38
 | 
			
		||||
#: intervention/templates/intervention/detail/includes/controls.html:43
 | 
			
		||||
msgid "Show log"
 | 
			
		||||
msgstr "Log anzeigen"
 | 
			
		||||
 | 
			
		||||
#: compensation/templates/compensation/detail/compensation/includes/controls.html:27
 | 
			
		||||
#: compensation/templates/compensation/detail/eco_account/includes/controls.html:41
 | 
			
		||||
#: ema/templates/ema/detail/includes/controls.html:41
 | 
			
		||||
#: intervention/templates/intervention/detail/includes/controls.html:46
 | 
			
		||||
#: venv/lib/python3.7/site-packages/django/forms/formsets.py:391
 | 
			
		||||
msgid "Delete"
 | 
			
		||||
msgstr "Löschen"
 | 
			
		||||
@ -907,7 +919,7 @@ msgstr "Dokumente"
 | 
			
		||||
#: compensation/templates/compensation/detail/eco_account/includes/documents.html:14
 | 
			
		||||
#: ema/templates/ema/detail/includes/documents.html:14
 | 
			
		||||
#: intervention/templates/intervention/detail/includes/documents.html:14
 | 
			
		||||
#: konova/forms.py:491
 | 
			
		||||
#: konova/forms.py:495
 | 
			
		||||
msgid "Add new document"
 | 
			
		||||
msgstr "Neues Dokument hinzufügen"
 | 
			
		||||
 | 
			
		||||
@ -915,7 +927,7 @@ msgstr "Neues Dokument hinzufügen"
 | 
			
		||||
#: compensation/templates/compensation/detail/eco_account/includes/documents.html:31
 | 
			
		||||
#: ema/templates/ema/detail/includes/documents.html:31
 | 
			
		||||
#: intervention/templates/intervention/detail/includes/documents.html:31
 | 
			
		||||
#: konova/forms.py:448
 | 
			
		||||
#: konova/forms.py:452
 | 
			
		||||
msgid "Created on"
 | 
			
		||||
msgstr "Erstellt"
 | 
			
		||||
 | 
			
		||||
@ -923,7 +935,7 @@ msgstr "Erstellt"
 | 
			
		||||
#: compensation/templates/compensation/detail/eco_account/includes/documents.html:61
 | 
			
		||||
#: ema/templates/ema/detail/includes/documents.html:61
 | 
			
		||||
#: intervention/templates/intervention/detail/includes/documents.html:65
 | 
			
		||||
#: konova/forms.py:553
 | 
			
		||||
#: konova/forms.py:557
 | 
			
		||||
msgid "Edit document"
 | 
			
		||||
msgstr "Dokument bearbeiten"
 | 
			
		||||
 | 
			
		||||
@ -1093,22 +1105,22 @@ msgstr ""
 | 
			
		||||
msgid "other users"
 | 
			
		||||
msgstr "weitere Nutzer"
 | 
			
		||||
 | 
			
		||||
#: compensation/templates/compensation/detail/eco_account/includes/controls.html:15
 | 
			
		||||
#: ema/templates/ema/detail/includes/controls.html:15
 | 
			
		||||
#: compensation/templates/compensation/detail/eco_account/includes/controls.html:18
 | 
			
		||||
#: ema/templates/ema/detail/includes/controls.html:18
 | 
			
		||||
#: intervention/forms/modalForms.py:71
 | 
			
		||||
#: intervention/templates/intervention/detail/includes/controls.html:15
 | 
			
		||||
#: intervention/templates/intervention/detail/includes/controls.html:18
 | 
			
		||||
msgid "Share"
 | 
			
		||||
msgstr "Freigabe"
 | 
			
		||||
 | 
			
		||||
#: compensation/templates/compensation/detail/eco_account/includes/controls.html:20
 | 
			
		||||
#: ema/templates/ema/detail/includes/controls.html:20
 | 
			
		||||
#: intervention/templates/intervention/detail/includes/controls.html:25
 | 
			
		||||
#: compensation/templates/compensation/detail/eco_account/includes/controls.html:23
 | 
			
		||||
#: ema/templates/ema/detail/includes/controls.html:23
 | 
			
		||||
#: intervention/templates/intervention/detail/includes/controls.html:28
 | 
			
		||||
msgid "Unrecord"
 | 
			
		||||
msgstr "Entzeichnen"
 | 
			
		||||
 | 
			
		||||
#: compensation/templates/compensation/detail/eco_account/includes/controls.html:24
 | 
			
		||||
#: ema/templates/ema/detail/includes/controls.html:24
 | 
			
		||||
#: intervention/templates/intervention/detail/includes/controls.html:29
 | 
			
		||||
#: compensation/templates/compensation/detail/eco_account/includes/controls.html:27
 | 
			
		||||
#: ema/templates/ema/detail/includes/controls.html:27
 | 
			
		||||
#: intervention/templates/intervention/detail/includes/controls.html:32
 | 
			
		||||
msgid "Record"
 | 
			
		||||
msgstr "Verzeichnen"
 | 
			
		||||
 | 
			
		||||
@ -1215,29 +1227,34 @@ msgstr ""
 | 
			
		||||
msgid "Responsible data"
 | 
			
		||||
msgstr "Daten zu den verantwortlichen Stellen"
 | 
			
		||||
 | 
			
		||||
#: compensation/views/compensation.py:53
 | 
			
		||||
#: compensation/views/compensation.py:54
 | 
			
		||||
msgid "Compensations - Overview"
 | 
			
		||||
msgstr "Kompensationen - Übersicht"
 | 
			
		||||
 | 
			
		||||
#: compensation/views/compensation.py:172 konova/utils/message_templates.py:36
 | 
			
		||||
#: compensation/views/compensation.py:173 konova/utils/message_templates.py:36
 | 
			
		||||
msgid "Compensation {} edited"
 | 
			
		||||
msgstr "Kompensation {} bearbeitet"
 | 
			
		||||
 | 
			
		||||
#: compensation/views/compensation.py:182 compensation/views/eco_account.py:173
 | 
			
		||||
#: compensation/views/compensation.py:183 compensation/views/eco_account.py:173
 | 
			
		||||
#: ema/views.py:241 intervention/views.py:338
 | 
			
		||||
msgid "Edit {}"
 | 
			
		||||
msgstr "Bearbeite {}"
 | 
			
		||||
 | 
			
		||||
#: compensation/views/compensation.py:269 compensation/views/eco_account.py:360
 | 
			
		||||
#: ema/views.py:195 intervention/views.py:542
 | 
			
		||||
#: compensation/views/compensation.py:270 compensation/views/eco_account.py:360
 | 
			
		||||
#: ema/views.py:195 intervention/views.py:565
 | 
			
		||||
msgid "Log"
 | 
			
		||||
msgstr "Log"
 | 
			
		||||
 | 
			
		||||
#: compensation/views/compensation.py:613 compensation/views/eco_account.py:728
 | 
			
		||||
#: ema/views.py:559 intervention/views.py:688
 | 
			
		||||
#: compensation/views/compensation.py:614 compensation/views/eco_account.py:728
 | 
			
		||||
#: ema/views.py:559 intervention/views.py:711
 | 
			
		||||
msgid "Report {}"
 | 
			
		||||
msgstr "Bericht {}"
 | 
			
		||||
 | 
			
		||||
#: compensation/views/compensation.py:680 compensation/views/eco_account.py:862
 | 
			
		||||
#: ema/views.py:734 intervention/views.py:496
 | 
			
		||||
msgid "Resubmission set"
 | 
			
		||||
msgstr "Wiedervorlage gesetzt"
 | 
			
		||||
 | 
			
		||||
#: compensation/views/eco_account.py:65
 | 
			
		||||
msgid "Eco-account - Overview"
 | 
			
		||||
msgstr "Ökokonten - Übersicht"
 | 
			
		||||
@ -1255,12 +1272,12 @@ msgid "Eco-account removed"
 | 
			
		||||
msgstr "Ökokonto entfernt"
 | 
			
		||||
 | 
			
		||||
#: compensation/views/eco_account.py:381 ema/views.py:283
 | 
			
		||||
#: intervention/views.py:641
 | 
			
		||||
#: intervention/views.py:664
 | 
			
		||||
msgid "{} unrecorded"
 | 
			
		||||
msgstr "{} entzeichnet"
 | 
			
		||||
 | 
			
		||||
#: compensation/views/eco_account.py:381 ema/views.py:283
 | 
			
		||||
#: intervention/views.py:641
 | 
			
		||||
#: intervention/views.py:664
 | 
			
		||||
msgid "{} recorded"
 | 
			
		||||
msgstr "{} verzeichnet"
 | 
			
		||||
 | 
			
		||||
@ -1462,11 +1479,11 @@ msgid "Checked compensations data and payments"
 | 
			
		||||
msgstr "Kompensationen und Zahlungen geprüft"
 | 
			
		||||
 | 
			
		||||
#: intervention/forms/modalForms.py:263
 | 
			
		||||
#: intervention/templates/intervention/detail/includes/controls.html:19
 | 
			
		||||
#: intervention/templates/intervention/detail/includes/controls.html:22
 | 
			
		||||
msgid "Run check"
 | 
			
		||||
msgstr "Prüfung vornehmen"
 | 
			
		||||
 | 
			
		||||
#: intervention/forms/modalForms.py:264 konova/forms.py:594
 | 
			
		||||
#: intervention/forms/modalForms.py:264 konova/forms.py:598
 | 
			
		||||
msgid ""
 | 
			
		||||
"I, {} {}, confirm that all necessary control steps have been performed by "
 | 
			
		||||
"myself."
 | 
			
		||||
@ -1622,11 +1639,11 @@ msgstr "Eingriff {} bearbeitet"
 | 
			
		||||
msgid "{} removed"
 | 
			
		||||
msgstr "{} entfernt"
 | 
			
		||||
 | 
			
		||||
#: intervention/views.py:495
 | 
			
		||||
#: intervention/views.py:518
 | 
			
		||||
msgid "Check performed"
 | 
			
		||||
msgstr "Prüfung durchgeführt"
 | 
			
		||||
 | 
			
		||||
#: intervention/views.py:646
 | 
			
		||||
#: intervention/views.py:669
 | 
			
		||||
msgid "There are errors on this intervention:"
 | 
			
		||||
msgstr "Es liegen Fehler in diesem Eingriff vor:"
 | 
			
		||||
 | 
			
		||||
@ -1711,78 +1728,90 @@ msgstr "Nach Zulassungsbehörde suchen"
 | 
			
		||||
msgid "Search for conservation office"
 | 
			
		||||
msgstr "Nch Eintragungsstelle suchen"
 | 
			
		||||
 | 
			
		||||
#: konova/forms.py:41 templates/form/collapsable/form.html:62
 | 
			
		||||
#: konova/forms.py:44 templates/form/collapsable/form.html:62
 | 
			
		||||
msgid "Save"
 | 
			
		||||
msgstr "Speichern"
 | 
			
		||||
 | 
			
		||||
#: konova/forms.py:75
 | 
			
		||||
#: konova/forms.py:78
 | 
			
		||||
msgid "Not editable"
 | 
			
		||||
msgstr "Nicht editierbar"
 | 
			
		||||
 | 
			
		||||
#: konova/forms.py:178 konova/forms.py:394
 | 
			
		||||
#: konova/forms.py:182 konova/forms.py:398
 | 
			
		||||
msgid "Confirm"
 | 
			
		||||
msgstr "Bestätige"
 | 
			
		||||
 | 
			
		||||
#: konova/forms.py:190 konova/forms.py:403
 | 
			
		||||
#: konova/forms.py:194 konova/forms.py:407
 | 
			
		||||
msgid "Remove"
 | 
			
		||||
msgstr "Löschen"
 | 
			
		||||
 | 
			
		||||
#: konova/forms.py:192
 | 
			
		||||
#: konova/forms.py:196
 | 
			
		||||
msgid "You are about to remove {} {}"
 | 
			
		||||
msgstr "Sie sind dabei {} {} zu löschen"
 | 
			
		||||
 | 
			
		||||
#: konova/forms.py:280 konova/utils/quality.py:44 konova/utils/quality.py:46
 | 
			
		||||
#: konova/forms.py:284 konova/utils/quality.py:44 konova/utils/quality.py:46
 | 
			
		||||
#: templates/form/collapsable/form.html:45
 | 
			
		||||
msgid "Geometry"
 | 
			
		||||
msgstr "Geometrie"
 | 
			
		||||
 | 
			
		||||
#: konova/forms.py:331
 | 
			
		||||
#: konova/forms.py:335
 | 
			
		||||
msgid "Only surfaces allowed. Points or lines must be buffered."
 | 
			
		||||
msgstr ""
 | 
			
		||||
"Nur Flächen erlaubt. Punkte oder Linien müssen zu Flächen gepuffert werden."
 | 
			
		||||
 | 
			
		||||
#: konova/forms.py:404
 | 
			
		||||
#: konova/forms.py:408
 | 
			
		||||
msgid "Are you sure?"
 | 
			
		||||
msgstr "Sind Sie sicher?"
 | 
			
		||||
 | 
			
		||||
#: konova/forms.py:450
 | 
			
		||||
#: konova/forms.py:454
 | 
			
		||||
msgid "When has this file been created? Important for photos."
 | 
			
		||||
msgstr "Wann wurde diese Datei erstellt oder das Foto aufgenommen?"
 | 
			
		||||
 | 
			
		||||
#: konova/forms.py:461
 | 
			
		||||
#: konova/forms.py:465
 | 
			
		||||
#: venv/lib/python3.7/site-packages/django/db/models/fields/files.py:231
 | 
			
		||||
msgid "File"
 | 
			
		||||
msgstr "Datei"
 | 
			
		||||
 | 
			
		||||
#: konova/forms.py:463
 | 
			
		||||
#: konova/forms.py:467
 | 
			
		||||
msgid "Allowed formats: pdf, jpg, png. Max size 15 MB."
 | 
			
		||||
msgstr "Formate: pdf, jpg, png. Maximal 15 MB."
 | 
			
		||||
 | 
			
		||||
#: konova/forms.py:528
 | 
			
		||||
#: konova/forms.py:532
 | 
			
		||||
msgid "Added document"
 | 
			
		||||
msgstr "Dokument hinzugefügt"
 | 
			
		||||
 | 
			
		||||
#: konova/forms.py:585
 | 
			
		||||
#: konova/forms.py:589
 | 
			
		||||
msgid "Confirm record"
 | 
			
		||||
msgstr "Verzeichnen bestätigen"
 | 
			
		||||
 | 
			
		||||
#: konova/forms.py:593
 | 
			
		||||
#: konova/forms.py:597
 | 
			
		||||
msgid "Record data"
 | 
			
		||||
msgstr "Daten verzeichnen"
 | 
			
		||||
 | 
			
		||||
#: konova/forms.py:600
 | 
			
		||||
#: konova/forms.py:604
 | 
			
		||||
msgid "Confirm unrecord"
 | 
			
		||||
msgstr "Entzeichnen bestätigen"
 | 
			
		||||
 | 
			
		||||
#: konova/forms.py:601
 | 
			
		||||
#: konova/forms.py:605
 | 
			
		||||
msgid "Unrecord data"
 | 
			
		||||
msgstr "Daten entzeichnen"
 | 
			
		||||
 | 
			
		||||
#: konova/forms.py:602
 | 
			
		||||
#: konova/forms.py:606
 | 
			
		||||
msgid "I, {} {}, confirm that this data must be unrecorded."
 | 
			
		||||
msgstr ""
 | 
			
		||||
"Ich, {} {}, bestätige, dass diese Daten wieder entzeichnet werden müssen."
 | 
			
		||||
 | 
			
		||||
#: konova/forms.py:698
 | 
			
		||||
msgid "When do you want to be reminded?"
 | 
			
		||||
msgstr "Wann wollen Sie erinnert werden?"
 | 
			
		||||
 | 
			
		||||
#: konova/forms.py:725
 | 
			
		||||
msgid "Set your resubmission for this entry."
 | 
			
		||||
msgstr "Setzen Sie eine Wiedervorlage für diesen Eintrag."
 | 
			
		||||
 | 
			
		||||
#: konova/forms.py:746
 | 
			
		||||
msgid "The date should be in the future"
 | 
			
		||||
msgstr "Das Datum sollte in der Zukunft liegen"
 | 
			
		||||
 | 
			
		||||
#: konova/management/commands/setup_data.py:26
 | 
			
		||||
msgid "On shared access gained"
 | 
			
		||||
msgstr "Wenn mir eine Freigabe zu Daten erteilt wird"
 | 
			
		||||
@ -1929,7 +1958,7 @@ msgstr "{} - Freigegebene Daten verzeichnet"
 | 
			
		||||
msgid "{} - Shared data checked"
 | 
			
		||||
msgstr "{} - Freigegebene Daten geprüft"
 | 
			
		||||
 | 
			
		||||
#: konova/utils/mailer.py:233 konova/utils/mailer.py:372
 | 
			
		||||
#: konova/utils/mailer.py:233 konova/utils/mailer.py:376
 | 
			
		||||
msgid "{} - Deduction changed"
 | 
			
		||||
msgstr "{} - Abbuchung geändert"
 | 
			
		||||
 | 
			
		||||
@ -1937,10 +1966,14 @@ msgstr "{} - Abbuchung geändert"
 | 
			
		||||
msgid "{} - Shared data deleted"
 | 
			
		||||
msgstr "{} - Freigegebene Daten gelöscht"
 | 
			
		||||
 | 
			
		||||
#: konova/utils/mailer.py:393 templates/email/api/verify_token.html:4
 | 
			
		||||
#: konova/utils/mailer.py:397 templates/email/api/verify_token.html:4
 | 
			
		||||
msgid "Request for new API token"
 | 
			
		||||
msgstr "Anfrage für neuen API Token"
 | 
			
		||||
 | 
			
		||||
#: konova/utils/mailer.py:420
 | 
			
		||||
msgid "Resubmission - {}"
 | 
			
		||||
msgstr "Wiedervorlage - {}"
 | 
			
		||||
 | 
			
		||||
#: konova/utils/message_templates.py:10
 | 
			
		||||
msgid "no further details"
 | 
			
		||||
msgstr "keine weitere Angabe"
 | 
			
		||||
@ -2223,11 +2256,11 @@ msgstr "Irgendetwas ist passiert. Wir arbeiten daran!"
 | 
			
		||||
msgid "Hello support"
 | 
			
		||||
msgstr "Hallo Support"
 | 
			
		||||
 | 
			
		||||
#: templates/email/api/verify_token.html:9
 | 
			
		||||
#: templates/email/api/verify_token.html:10
 | 
			
		||||
msgid "you need to verify the API token for user"
 | 
			
		||||
msgstr "Sie müssen einen API Token für folgenden Nutzer freischalten"
 | 
			
		||||
 | 
			
		||||
#: templates/email/api/verify_token.html:15
 | 
			
		||||
#: templates/email/api/verify_token.html:16
 | 
			
		||||
msgid ""
 | 
			
		||||
"If unsure, please contact the user. The API token can not be used until you "
 | 
			
		||||
"activated it in the admin backend."
 | 
			
		||||
@ -2236,20 +2269,22 @@ msgstr ""
 | 
			
		||||
"Token kann so lange nicht verwendet werden, wie er noch nicht von Ihnen im "
 | 
			
		||||
"Admin Backend aktiviert worden ist."
 | 
			
		||||
 | 
			
		||||
#: templates/email/api/verify_token.html:18
 | 
			
		||||
#: templates/email/checking/shared_data_checked.html:19
 | 
			
		||||
#: templates/email/checking/shared_data_checked_team.html:19
 | 
			
		||||
#: templates/email/deleting/shared_data_deleted.html:19
 | 
			
		||||
#: templates/email/deleting/shared_data_deleted_team.html:19
 | 
			
		||||
#: templates/email/other/deduction_changed.html:38
 | 
			
		||||
#: templates/email/recording/shared_data_recorded.html:19
 | 
			
		||||
#: templates/email/recording/shared_data_recorded_team.html:19
 | 
			
		||||
#: templates/email/recording/shared_data_unrecorded.html:19
 | 
			
		||||
#: templates/email/recording/shared_data_unrecorded_team.html:19
 | 
			
		||||
#: templates/email/sharing/shared_access_given.html:20
 | 
			
		||||
#: templates/email/sharing/shared_access_given_team.html:20
 | 
			
		||||
#: templates/email/sharing/shared_access_removed.html:20
 | 
			
		||||
#: templates/email/sharing/shared_access_removed_team.html:20
 | 
			
		||||
#: templates/email/api/verify_token.html:19
 | 
			
		||||
#: templates/email/checking/shared_data_checked.html:20
 | 
			
		||||
#: templates/email/checking/shared_data_checked_team.html:20
 | 
			
		||||
#: templates/email/deleting/shared_data_deleted.html:20
 | 
			
		||||
#: templates/email/deleting/shared_data_deleted_team.html:20
 | 
			
		||||
#: templates/email/other/deduction_changed.html:41
 | 
			
		||||
#: templates/email/other/deduction_changed_team.html:41
 | 
			
		||||
#: templates/email/recording/shared_data_recorded.html:20
 | 
			
		||||
#: templates/email/recording/shared_data_recorded_team.html:20
 | 
			
		||||
#: templates/email/recording/shared_data_unrecorded.html:20
 | 
			
		||||
#: templates/email/recording/shared_data_unrecorded_team.html:20
 | 
			
		||||
#: templates/email/resubmission/resubmission.html:21
 | 
			
		||||
#: templates/email/sharing/shared_access_given.html:21
 | 
			
		||||
#: templates/email/sharing/shared_access_given_team.html:21
 | 
			
		||||
#: templates/email/sharing/shared_access_removed.html:21
 | 
			
		||||
#: templates/email/sharing/shared_access_removed_team.html:21
 | 
			
		||||
msgid "Best regards"
 | 
			
		||||
msgstr "Beste Grüße"
 | 
			
		||||
 | 
			
		||||
@ -2263,18 +2298,19 @@ msgstr "Freigegebene Daten geprüft"
 | 
			
		||||
#: templates/email/other/deduction_changed.html:8
 | 
			
		||||
#: templates/email/recording/shared_data_recorded.html:8
 | 
			
		||||
#: templates/email/recording/shared_data_unrecorded.html:8
 | 
			
		||||
#: templates/email/resubmission/resubmission.html:8
 | 
			
		||||
#: templates/email/sharing/shared_access_given.html:8
 | 
			
		||||
#: templates/email/sharing/shared_access_removed.html:8
 | 
			
		||||
msgid "Hello "
 | 
			
		||||
msgstr "Hallo "
 | 
			
		||||
 | 
			
		||||
#: templates/email/checking/shared_data_checked.html:10
 | 
			
		||||
#: templates/email/checking/shared_data_checked_team.html:10
 | 
			
		||||
#: templates/email/checking/shared_data_checked.html:11
 | 
			
		||||
#: templates/email/checking/shared_data_checked_team.html:11
 | 
			
		||||
msgid "the following dataset has just been checked"
 | 
			
		||||
msgstr "der folgende Datensatz wurde soeben geprüft "
 | 
			
		||||
 | 
			
		||||
#: templates/email/checking/shared_data_checked.html:16
 | 
			
		||||
#: templates/email/checking/shared_data_checked_team.html:16
 | 
			
		||||
#: templates/email/checking/shared_data_checked.html:17
 | 
			
		||||
#: templates/email/checking/shared_data_checked_team.html:17
 | 
			
		||||
msgid ""
 | 
			
		||||
"This means, the responsible registration office just confirmed the "
 | 
			
		||||
"correctness of this dataset."
 | 
			
		||||
@ -2284,6 +2320,7 @@ msgstr ""
 | 
			
		||||
 | 
			
		||||
#: templates/email/checking/shared_data_checked_team.html:8
 | 
			
		||||
#: templates/email/deleting/shared_data_deleted_team.html:8
 | 
			
		||||
#: templates/email/other/deduction_changed_team.html:8
 | 
			
		||||
#: templates/email/recording/shared_data_recorded_team.html:8
 | 
			
		||||
#: templates/email/recording/shared_data_unrecorded_team.html:8
 | 
			
		||||
#: templates/email/sharing/shared_access_given_team.html:8
 | 
			
		||||
@ -2296,14 +2333,15 @@ msgstr "Hallo Team"
 | 
			
		||||
msgid "Shared data deleted"
 | 
			
		||||
msgstr "Freigegebene Daten gelöscht"
 | 
			
		||||
 | 
			
		||||
#: templates/email/deleting/shared_data_deleted.html:10
 | 
			
		||||
#: templates/email/deleting/shared_data_deleted_team.html:10
 | 
			
		||||
#: templates/email/deleting/shared_data_deleted.html:11
 | 
			
		||||
#: templates/email/deleting/shared_data_deleted_team.html:11
 | 
			
		||||
msgid "the following dataset has just been deleted"
 | 
			
		||||
msgstr "der folgende Datensatz wurde soeben gelöscht "
 | 
			
		||||
 | 
			
		||||
#: templates/email/deleting/shared_data_deleted.html:16
 | 
			
		||||
#: templates/email/deleting/shared_data_deleted_team.html:16
 | 
			
		||||
#: templates/email/other/deduction_changed.html:35
 | 
			
		||||
#: templates/email/deleting/shared_data_deleted.html:17
 | 
			
		||||
#: templates/email/deleting/shared_data_deleted_team.html:17
 | 
			
		||||
#: templates/email/other/deduction_changed.html:38
 | 
			
		||||
#: templates/email/other/deduction_changed_team.html:38
 | 
			
		||||
msgid ""
 | 
			
		||||
"If this should not have been happened, please contact us. See the signature "
 | 
			
		||||
"for details."
 | 
			
		||||
@ -2312,27 +2350,33 @@ msgstr ""
 | 
			
		||||
"mail Signatur finden Sie weitere Kontaktinformationen."
 | 
			
		||||
 | 
			
		||||
#: templates/email/other/deduction_changed.html:4
 | 
			
		||||
#: templates/email/other/deduction_changed_team.html:4
 | 
			
		||||
msgid "Deduction changed"
 | 
			
		||||
msgstr "Abbuchung geändert"
 | 
			
		||||
 | 
			
		||||
#: templates/email/other/deduction_changed.html:10
 | 
			
		||||
#: templates/email/other/deduction_changed.html:11
 | 
			
		||||
#: templates/email/other/deduction_changed_team.html:11
 | 
			
		||||
msgid "a deduction of this eco account has changed:"
 | 
			
		||||
msgstr "eine Abbuchung des Ökokontos hat sich geändert:"
 | 
			
		||||
 | 
			
		||||
#: templates/email/other/deduction_changed.html:14
 | 
			
		||||
#: templates/email/other/deduction_changed.html:16
 | 
			
		||||
#: templates/email/other/deduction_changed_team.html:16
 | 
			
		||||
msgid "Attribute"
 | 
			
		||||
msgstr "Attribute"
 | 
			
		||||
 | 
			
		||||
#: templates/email/other/deduction_changed.html:15
 | 
			
		||||
#: templates/email/other/deduction_changed.html:17
 | 
			
		||||
#: templates/email/other/deduction_changed_team.html:17
 | 
			
		||||
msgid "Old"
 | 
			
		||||
msgstr "Alt"
 | 
			
		||||
 | 
			
		||||
#: templates/email/other/deduction_changed.html:16
 | 
			
		||||
#: templates/email/other/deduction_changed.html:18
 | 
			
		||||
#: templates/email/other/deduction_changed_team.html:18
 | 
			
		||||
#: templates/generic_index.html:43 user/templates/user/team/index.html:22
 | 
			
		||||
msgid "New"
 | 
			
		||||
msgstr "Neu"
 | 
			
		||||
 | 
			
		||||
#: templates/email/other/deduction_changed.html:19
 | 
			
		||||
#: templates/email/other/deduction_changed.html:21
 | 
			
		||||
#: templates/email/other/deduction_changed_team.html:21
 | 
			
		||||
msgid "EcoAccount"
 | 
			
		||||
msgstr "Ökokonto"
 | 
			
		||||
 | 
			
		||||
@ -2341,19 +2385,19 @@ msgstr "Ökokonto"
 | 
			
		||||
msgid "Shared data recorded"
 | 
			
		||||
msgstr "Freigegebene Daten verzeichnet"
 | 
			
		||||
 | 
			
		||||
#: templates/email/recording/shared_data_recorded.html:10
 | 
			
		||||
#: templates/email/recording/shared_data_recorded_team.html:10
 | 
			
		||||
#: templates/email/recording/shared_data_recorded.html:11
 | 
			
		||||
#: templates/email/recording/shared_data_recorded_team.html:11
 | 
			
		||||
msgid "the following dataset has just been recorded"
 | 
			
		||||
msgstr "der folgende Datensatz wurde soeben verzeichnet "
 | 
			
		||||
 | 
			
		||||
#: templates/email/recording/shared_data_recorded.html:16
 | 
			
		||||
#: templates/email/recording/shared_data_recorded_team.html:16
 | 
			
		||||
#: templates/email/recording/shared_data_recorded.html:17
 | 
			
		||||
#: templates/email/recording/shared_data_recorded_team.html:17
 | 
			
		||||
msgid "This means the data is now publicly available, e.g. in LANIS"
 | 
			
		||||
msgstr ""
 | 
			
		||||
"Das bedeutet, dass die Daten nun öffentlich verfügbar sind, z.B. im LANIS."
 | 
			
		||||
 | 
			
		||||
#: templates/email/recording/shared_data_recorded.html:26
 | 
			
		||||
#: templates/email/recording/shared_data_recorded_team.html:26
 | 
			
		||||
#: templates/email/recording/shared_data_recorded.html:27
 | 
			
		||||
#: templates/email/recording/shared_data_recorded_team.html:27
 | 
			
		||||
msgid ""
 | 
			
		||||
"Please note: Recorded intervention means the compensations are recorded as "
 | 
			
		||||
"well."
 | 
			
		||||
@ -2366,18 +2410,18 @@ msgstr ""
 | 
			
		||||
msgid "Shared data unrecorded"
 | 
			
		||||
msgstr "Freigegebene Daten entzeichnet"
 | 
			
		||||
 | 
			
		||||
#: templates/email/recording/shared_data_unrecorded.html:10
 | 
			
		||||
#: templates/email/recording/shared_data_unrecorded_team.html:10
 | 
			
		||||
#: templates/email/recording/shared_data_unrecorded.html:11
 | 
			
		||||
#: templates/email/recording/shared_data_unrecorded_team.html:11
 | 
			
		||||
msgid "the following dataset has just been unrecorded"
 | 
			
		||||
msgstr "der folgende Datensatz wurde soeben entzeichnet "
 | 
			
		||||
 | 
			
		||||
#: templates/email/recording/shared_data_unrecorded.html:16
 | 
			
		||||
#: templates/email/recording/shared_data_unrecorded_team.html:16
 | 
			
		||||
#: templates/email/recording/shared_data_unrecorded.html:17
 | 
			
		||||
#: templates/email/recording/shared_data_unrecorded_team.html:17
 | 
			
		||||
msgid "This means the data is no longer publicly available."
 | 
			
		||||
msgstr "Das bedeutet, dass die Daten nicht länger öffentlich verfügbar sind."
 | 
			
		||||
 | 
			
		||||
#: templates/email/recording/shared_data_unrecorded.html:26
 | 
			
		||||
#: templates/email/recording/shared_data_unrecorded_team.html:26
 | 
			
		||||
#: templates/email/recording/shared_data_unrecorded.html:27
 | 
			
		||||
#: templates/email/recording/shared_data_unrecorded_team.html:27
 | 
			
		||||
msgid ""
 | 
			
		||||
"Please note: Unrecorded intervention means the compensations are unrecorded "
 | 
			
		||||
"as well."
 | 
			
		||||
@ -2385,22 +2429,30 @@ msgstr ""
 | 
			
		||||
"Bitte beachten Sie: Entzeichnete Eingriffe bedeuten, dass auch die "
 | 
			
		||||
"zugehörigen Kompensationen automatisch entzeichnet worden sind."
 | 
			
		||||
 | 
			
		||||
#: templates/email/resubmission/resubmission.html:11
 | 
			
		||||
msgid "you wanted to be reminded on this entry."
 | 
			
		||||
msgstr "Sie wollten an diesen Eintrag erinnert werden."
 | 
			
		||||
 | 
			
		||||
#: templates/email/resubmission/resubmission.html:15
 | 
			
		||||
msgid "Your personal comment:"
 | 
			
		||||
msgstr "Ihr Kommentar:"
 | 
			
		||||
 | 
			
		||||
#: templates/email/sharing/shared_access_given.html:4
 | 
			
		||||
#: templates/email/sharing/shared_access_given_team.html:4
 | 
			
		||||
msgid "Access shared"
 | 
			
		||||
msgstr "Zugriff freigegeben"
 | 
			
		||||
 | 
			
		||||
#: templates/email/sharing/shared_access_given.html:10
 | 
			
		||||
#: templates/email/sharing/shared_access_given.html:11
 | 
			
		||||
msgid "the following dataset has just been shared with you"
 | 
			
		||||
msgstr "der folgende Datensatz wurde soeben für Sie freigegeben "
 | 
			
		||||
 | 
			
		||||
#: templates/email/sharing/shared_access_given.html:16
 | 
			
		||||
#: templates/email/sharing/shared_access_given_team.html:16
 | 
			
		||||
#: templates/email/sharing/shared_access_given.html:17
 | 
			
		||||
#: templates/email/sharing/shared_access_given_team.html:17
 | 
			
		||||
msgid "This means you can now edit this dataset."
 | 
			
		||||
msgstr "Das bedeutet, dass Sie diesen Datensatz nun auch bearbeiten können."
 | 
			
		||||
 | 
			
		||||
#: templates/email/sharing/shared_access_given.html:17
 | 
			
		||||
#: templates/email/sharing/shared_access_given_team.html:17
 | 
			
		||||
#: templates/email/sharing/shared_access_given.html:18
 | 
			
		||||
#: templates/email/sharing/shared_access_given_team.html:18
 | 
			
		||||
msgid ""
 | 
			
		||||
"The shared dataset appears now by default on your overview for this dataset "
 | 
			
		||||
"type."
 | 
			
		||||
@ -2408,8 +2460,8 @@ msgstr ""
 | 
			
		||||
"Der freigegebene Datensatz ist nun standardmäßig in Ihrer Übersicht für den "
 | 
			
		||||
"Datensatztyp im KSP gelistet."
 | 
			
		||||
 | 
			
		||||
#: templates/email/sharing/shared_access_given.html:27
 | 
			
		||||
#: templates/email/sharing/shared_access_given_team.html:27
 | 
			
		||||
#: templates/email/sharing/shared_access_given.html:28
 | 
			
		||||
#: templates/email/sharing/shared_access_given_team.html:28
 | 
			
		||||
msgid ""
 | 
			
		||||
"Please note: Shared access on an intervention means you automatically have "
 | 
			
		||||
"editing access to related compensations."
 | 
			
		||||
@ -2418,7 +2470,7 @@ msgstr ""
 | 
			
		||||
"Sie automatisch auch Zugriff auf die zugehörigen Kompensationen erhalten "
 | 
			
		||||
"haben."
 | 
			
		||||
 | 
			
		||||
#: templates/email/sharing/shared_access_given_team.html:10
 | 
			
		||||
#: templates/email/sharing/shared_access_given_team.html:11
 | 
			
		||||
msgid "the following dataset has just been shared with your team"
 | 
			
		||||
msgstr "der folgende Datensatz wurde soeben für Ihr Team freigegeben "
 | 
			
		||||
 | 
			
		||||
@ -2427,20 +2479,20 @@ msgstr "der folgende Datensatz wurde soeben für Ihr Team freigegeben "
 | 
			
		||||
msgid "Shared access removed"
 | 
			
		||||
msgstr "Freigegebener Zugriff entzogen"
 | 
			
		||||
 | 
			
		||||
#: templates/email/sharing/shared_access_removed.html:10
 | 
			
		||||
#: templates/email/sharing/shared_access_removed.html:11
 | 
			
		||||
msgid ""
 | 
			
		||||
"your shared access, including editing, has been revoked for the dataset "
 | 
			
		||||
msgstr ""
 | 
			
		||||
"Ihnen wurde soeben der bearbeitende Zugriff auf den folgenden Datensatz "
 | 
			
		||||
"entzogen: "
 | 
			
		||||
 | 
			
		||||
#: templates/email/sharing/shared_access_removed.html:16
 | 
			
		||||
#: templates/email/sharing/shared_access_removed_team.html:16
 | 
			
		||||
#: templates/email/sharing/shared_access_removed.html:17
 | 
			
		||||
#: templates/email/sharing/shared_access_removed_team.html:17
 | 
			
		||||
msgid "However, you are still able to view the dataset content."
 | 
			
		||||
msgstr "Sie können den Datensatz aber immer noch im KSP einsehen."
 | 
			
		||||
 | 
			
		||||
#: templates/email/sharing/shared_access_removed.html:17
 | 
			
		||||
#: templates/email/sharing/shared_access_removed_team.html:17
 | 
			
		||||
#: templates/email/sharing/shared_access_removed.html:18
 | 
			
		||||
#: templates/email/sharing/shared_access_removed_team.html:18
 | 
			
		||||
msgid ""
 | 
			
		||||
"Please use the provided search filter on the dataset`s overview pages to "
 | 
			
		||||
"find them."
 | 
			
		||||
@ -2448,7 +2500,7 @@ msgstr ""
 | 
			
		||||
"Nutzen Sie hierzu einfach die entsprechenden Suchfilter auf den "
 | 
			
		||||
"Übersichtsseiten"
 | 
			
		||||
 | 
			
		||||
#: templates/email/sharing/shared_access_removed_team.html:10
 | 
			
		||||
#: templates/email/sharing/shared_access_removed_team.html:11
 | 
			
		||||
msgid ""
 | 
			
		||||
"your teams shared access, including editing, has been revoked for the "
 | 
			
		||||
"dataset "
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										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>
 | 
			
		||||
 | 
			
		||||
@ -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),
 | 
			
		||||
        ),
 | 
			
		||||
    ]
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user