Migrations + Cleanup
* adds needed migrations
* refactors forms.py (700+ lines) in main konova app
    * splits into forms/ and forms/modals and single class/topic-files for better maintainability and overview
* fixes bug in main konova app migration which could occur if a certain compensation migration did not run before
			
			
This commit is contained in:
		
							parent
							
								
									8a44681803
								
							
						
					
					
						commit
						0bf2051bdf
					
				@ -20,7 +20,7 @@ from compensation.models import CompensationDocument, EcoAccountDocument
 | 
				
			|||||||
from intervention.inputs import CompensationActionTreeCheckboxSelectMultiple, \
 | 
					from intervention.inputs import CompensationActionTreeCheckboxSelectMultiple, \
 | 
				
			||||||
    CompensationStateTreeRadioSelect
 | 
					    CompensationStateTreeRadioSelect
 | 
				
			||||||
from konova.contexts import BaseContext
 | 
					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.models import DeadlineType
 | 
				
			||||||
from konova.utils.message_templates import FORM_INVALID, ADDED_COMPENSATION_STATE, \
 | 
					from konova.utils.message_templates import FORM_INVALID, ADDED_COMPENSATION_STATE, \
 | 
				
			||||||
    ADDED_COMPENSATION_ACTION, PAYMENT_EDITED, COMPENSATION_STATE_EDITED, COMPENSATION_ACTION_EDITED, DEADLINE_EDITED
 | 
					    ADDED_COMPENSATION_ACTION, PAYMENT_EDITED, COMPENSATION_STATE_EDITED, COMPENSATION_ACTION_EDITED, DEADLINE_EDITED
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										24
									
								
								compensation/migrations/0008_auto_20220815_0803.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								compensation/migrations/0008_auto_20220815_0803.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,24 @@
 | 
				
			|||||||
 | 
					# Generated by Django 3.1.3 on 2022-08-15 06:03
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from django.db import migrations, models
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Migration(migrations.Migration):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    dependencies = [
 | 
				
			||||||
 | 
					        ('konova', '0014_resubmission'),
 | 
				
			||||||
 | 
					        ('compensation', '0007_auto_20220531_1245'),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    operations = [
 | 
				
			||||||
 | 
					        migrations.AddField(
 | 
				
			||||||
 | 
					            model_name='compensation',
 | 
				
			||||||
 | 
					            name='resubmission',
 | 
				
			||||||
 | 
					            field=models.ManyToManyField(blank=True, null=True, related_name='_compensation_resubmission_+', to='konova.Resubmission'),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					        migrations.AddField(
 | 
				
			||||||
 | 
					            model_name='ecoaccount',
 | 
				
			||||||
 | 
					            name='resubmission',
 | 
				
			||||||
 | 
					            field=models.ManyToManyField(blank=True, null=True, related_name='_ecoaccount_resubmission_+', to='konova.Resubmission'),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
							
								
								
									
										32
									
								
								compensation/migrations/0009_auto_20220815_0803.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								compensation/migrations/0009_auto_20220815_0803.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,32 @@
 | 
				
			|||||||
 | 
					# Generated by Django 3.1.3 on 2022-08-15 06:03
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from django.db import migrations, models
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Migration(migrations.Migration):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    dependencies = [
 | 
				
			||||||
 | 
					        ('konova', '0014_resubmission'),
 | 
				
			||||||
 | 
					        ('compensation', '0008_auto_20220815_0803'),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    operations = [
 | 
				
			||||||
 | 
					        migrations.RemoveField(
 | 
				
			||||||
 | 
					            model_name='compensation',
 | 
				
			||||||
 | 
					            name='resubmission',
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					        migrations.RemoveField(
 | 
				
			||||||
 | 
					            model_name='ecoaccount',
 | 
				
			||||||
 | 
					            name='resubmission',
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					        migrations.AddField(
 | 
				
			||||||
 | 
					            model_name='compensation',
 | 
				
			||||||
 | 
					            name='resubmissions',
 | 
				
			||||||
 | 
					            field=models.ManyToManyField(blank=True, null=True, related_name='_compensation_resubmissions_+', to='konova.Resubmission'),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					        migrations.AddField(
 | 
				
			||||||
 | 
					            model_name='ecoaccount',
 | 
				
			||||||
 | 
					            name='resubmissions',
 | 
				
			||||||
 | 
					            field=models.ManyToManyField(blank=True, null=True, related_name='_ecoaccount_resubmissions_+', to='konova.Resubmission'),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
							
								
								
									
										24
									
								
								compensation/migrations/0010_auto_20220815_1030.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								compensation/migrations/0010_auto_20220815_1030.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,24 @@
 | 
				
			|||||||
 | 
					# Generated by Django 3.1.3 on 2022-08-15 08:30
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from django.db import migrations, models
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Migration(migrations.Migration):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    dependencies = [
 | 
				
			||||||
 | 
					        ('konova', '0014_resubmission'),
 | 
				
			||||||
 | 
					        ('compensation', '0009_auto_20220815_0803'),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    operations = [
 | 
				
			||||||
 | 
					        migrations.AlterField(
 | 
				
			||||||
 | 
					            model_name='compensation',
 | 
				
			||||||
 | 
					            name='resubmissions',
 | 
				
			||||||
 | 
					            field=models.ManyToManyField(blank=True, related_name='_compensation_resubmissions_+', to='konova.Resubmission'),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					        migrations.AlterField(
 | 
				
			||||||
 | 
					            model_name='ecoaccount',
 | 
				
			||||||
 | 
					            name='resubmissions',
 | 
				
			||||||
 | 
					            field=models.ManyToManyField(blank=True, related_name='_ecoaccount_resubmissions_+', to='konova.Resubmission'),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
@ -14,8 +14,9 @@ from compensation.tables import CompensationTable
 | 
				
			|||||||
from intervention.models import Intervention
 | 
					from intervention.models import Intervention
 | 
				
			||||||
from konova.contexts import BaseContext
 | 
					from konova.contexts import BaseContext
 | 
				
			||||||
from konova.decorators import *
 | 
					from konova.decorators import *
 | 
				
			||||||
from konova.forms import RemoveModalForm, SimpleGeomForm, RemoveDeadlineModalForm, EditDocumentModalForm, \
 | 
					from konova.forms.modals import RemoveModalForm,RemoveDeadlineModalForm, EditDocumentModalForm, \
 | 
				
			||||||
    ResubmissionModalForm
 | 
					    ResubmissionModalForm
 | 
				
			||||||
 | 
					from konova.forms import SimpleGeomForm
 | 
				
			||||||
from konova.models import Deadline
 | 
					from konova.models import Deadline
 | 
				
			||||||
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
 | 
					from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
 | 
				
			||||||
from konova.utils.documents import get_document, remove_document
 | 
					from konova.utils.documents import get_document, remove_document
 | 
				
			||||||
 | 
				
			|||||||
@ -25,14 +25,15 @@ from intervention.forms.modalForms import NewDeductionModalForm, ShareModalForm,
 | 
				
			|||||||
from konova.contexts import BaseContext
 | 
					from konova.contexts import BaseContext
 | 
				
			||||||
from konova.decorators import any_group_check, default_group_required, conservation_office_group_required, \
 | 
					from konova.decorators import any_group_check, default_group_required, conservation_office_group_required, \
 | 
				
			||||||
    shared_access_required
 | 
					    shared_access_required
 | 
				
			||||||
from konova.forms import RemoveModalForm, SimpleGeomForm, NewDocumentModalForm, RecordModalForm, \
 | 
					from konova.forms.modals import RemoveModalForm, RecordModalForm, \
 | 
				
			||||||
    RemoveDeadlineModalForm, EditDocumentModalForm, ResubmissionModalForm
 | 
					    RemoveDeadlineModalForm, EditDocumentModalForm, ResubmissionModalForm
 | 
				
			||||||
 | 
					from konova.forms import SimpleGeomForm
 | 
				
			||||||
from konova.models import Deadline
 | 
					from konova.models import Deadline
 | 
				
			||||||
from konova.settings import DEFAULT_GROUP, ZB_GROUP, ETS_GROUP
 | 
					from konova.settings import DEFAULT_GROUP, ZB_GROUP, ETS_GROUP
 | 
				
			||||||
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
 | 
					from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
 | 
				
			||||||
from konova.utils.documents import get_document, remove_document
 | 
					from konova.utils.documents import get_document, remove_document
 | 
				
			||||||
from konova.utils.generators import generate_qr_code
 | 
					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, \
 | 
					    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, \
 | 
					    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, \
 | 
					    DEDUCTION_EDITED, DOCUMENT_EDITED, COMPENSATION_STATE_EDITED, COMPENSATION_ACTION_EDITED, DEADLINE_EDITED, \
 | 
				
			||||||
 | 
				
			|||||||
@ -15,7 +15,6 @@ from compensation.forms.modalForms import NewPaymentForm, RemovePaymentModalForm
 | 
				
			|||||||
from compensation.models import Payment
 | 
					from compensation.models import Payment
 | 
				
			||||||
from intervention.models import Intervention
 | 
					from intervention.models import Intervention
 | 
				
			||||||
from konova.decorators import default_group_required, shared_access_required
 | 
					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
 | 
					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
 | 
					Created on: 06.10.21
 | 
				
			||||||
 | 
					
 | 
				
			||||||
"""
 | 
					"""
 | 
				
			||||||
from dal import autocomplete
 | 
					 | 
				
			||||||
from django import forms
 | 
					 | 
				
			||||||
from user.models import User
 | 
					from user.models import User
 | 
				
			||||||
from django.db import transaction
 | 
					from django.db import transaction
 | 
				
			||||||
from django.urls import reverse, reverse_lazy
 | 
					from django.urls import reverse, reverse_lazy
 | 
				
			||||||
@ -16,7 +14,8 @@ from compensation.forms.forms import AbstractCompensationForm, CompensationRespo
 | 
				
			|||||||
    PikCompensationFormMixin
 | 
					    PikCompensationFormMixin
 | 
				
			||||||
from ema.models import Ema, EmaDocument
 | 
					from ema.models import Ema, EmaDocument
 | 
				
			||||||
from intervention.models import Responsibility, Handler
 | 
					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
 | 
					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'),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
@ -16,8 +16,9 @@ from intervention.forms.modalForms import ShareModalForm
 | 
				
			|||||||
from konova.contexts import BaseContext
 | 
					from konova.contexts import BaseContext
 | 
				
			||||||
from konova.decorators import conservation_office_group_required, shared_access_required
 | 
					from konova.decorators import conservation_office_group_required, shared_access_required
 | 
				
			||||||
from ema.models import Ema, EmaDocument
 | 
					from ema.models import Ema, EmaDocument
 | 
				
			||||||
from konova.forms import RemoveModalForm, SimpleGeomForm, RecordModalForm, RemoveDeadlineModalForm, \
 | 
					from konova.forms.modals import RemoveModalForm, RecordModalForm, RemoveDeadlineModalForm, \
 | 
				
			||||||
    EditDocumentModalForm, ResubmissionModalForm
 | 
					    EditDocumentModalForm, ResubmissionModalForm
 | 
				
			||||||
 | 
					from konova.forms import SimpleGeomForm
 | 
				
			||||||
from konova.models import Deadline
 | 
					from konova.models import Deadline
 | 
				
			||||||
from konova.settings import DEFAULT_GROUP, ZB_GROUP, ETS_GROUP
 | 
					from konova.settings import DEFAULT_GROUP, ZB_GROUP, ETS_GROUP
 | 
				
			||||||
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
 | 
					from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
 | 
				
			||||||
 | 
				
			|||||||
@ -8,6 +8,7 @@ Created on: 02.12.20
 | 
				
			|||||||
from dal import autocomplete
 | 
					from dal import autocomplete
 | 
				
			||||||
from django import forms
 | 
					from django import forms
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from konova.forms.base_form import BaseForm
 | 
				
			||||||
from konova.utils.message_templates import EDITED_GENERAL_DATA
 | 
					from konova.utils.message_templates import EDITED_GENERAL_DATA
 | 
				
			||||||
from user.models import User
 | 
					from user.models import User
 | 
				
			||||||
from django.db import transaction
 | 
					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
 | 
					    CODELIST_REGISTRATION_OFFICE_ID, CODELIST_CONSERVATION_OFFICE_ID, CODELIST_HANDLER_ID
 | 
				
			||||||
from intervention.inputs import GenerateInput
 | 
					from intervention.inputs import GenerateInput
 | 
				
			||||||
from intervention.models import Intervention, Legal, Responsibility, Handler
 | 
					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
 | 
					from user.models import UserActionLogEntry
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -19,7 +19,8 @@ from django.utils.translation import gettext_lazy as _
 | 
				
			|||||||
from compensation.models import EcoAccount, EcoAccountDeduction
 | 
					from compensation.models import EcoAccount, EcoAccountDeduction
 | 
				
			||||||
from intervention.inputs import TextToClipboardInput
 | 
					from intervention.inputs import TextToClipboardInput
 | 
				
			||||||
from intervention.models import Intervention, InterventionDocument, RevocationDocument
 | 
					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.general import format_german_float
 | 
				
			||||||
from konova.utils.user_checks import is_default_group_only
 | 
					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'),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
@ -12,7 +12,8 @@ from intervention.models import Intervention, Revocation, InterventionDocument,
 | 
				
			|||||||
from intervention.tables import InterventionTable
 | 
					from intervention.tables import InterventionTable
 | 
				
			||||||
from konova.contexts import BaseContext
 | 
					from konova.contexts import BaseContext
 | 
				
			||||||
from konova.decorators import *
 | 
					from konova.decorators import *
 | 
				
			||||||
from konova.forms import SimpleGeomForm, RemoveModalForm, RecordModalForm, EditDocumentModalForm, ResubmissionModalForm
 | 
					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.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
 | 
				
			||||||
from konova.utils.documents import remove_document, get_document
 | 
					from konova.utils.documents import remove_document, get_document
 | 
				
			||||||
from konova.utils.generators import generate_qr_code
 | 
					from konova.utils.generators import generate_qr_code
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										760
									
								
								konova/forms.py
									
									
									
									
									
								
							
							
						
						
									
										760
									
								
								konova/forms.py
									
									
									
									
									
								
							@ -1,760 +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.core.exceptions import ObjectDoesNotExist
 | 
					 | 
				
			||||||
from django.db.models.fields.files import FieldFile
 | 
					 | 
				
			||||||
from django.utils.timezone import now
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
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, Resubmission
 | 
					 | 
				
			||||||
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,
 | 
					 | 
				
			||||||
                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"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
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
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
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 = now().today().date()
 | 
					 | 
				
			||||||
        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
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
							
								
								
									
										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
 | 
				
			||||||
@ -33,6 +33,7 @@ class Migration(migrations.Migration):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    dependencies = [
 | 
					    dependencies = [
 | 
				
			||||||
        ('konova', '0004_auto_20220209_0839'),
 | 
					        ('konova', '0004_auto_20220209_0839'),
 | 
				
			||||||
 | 
					        ('compensation', '0002_auto_20220114_0936'),
 | 
				
			||||||
    ]
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    operations = [
 | 
					    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,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
@ -749,7 +749,6 @@ class GeoReferencedMixin(models.Model):
 | 
				
			|||||||
class ResubmitableObjectMixin(models.Model):
 | 
					class ResubmitableObjectMixin(models.Model):
 | 
				
			||||||
    resubmissions = models.ManyToManyField(
 | 
					    resubmissions = models.ManyToManyField(
 | 
				
			||||||
        "konova.Resubmission",
 | 
					        "konova.Resubmission",
 | 
				
			||||||
        null=True,
 | 
					 | 
				
			||||||
        blank=True,
 | 
					        blank=True,
 | 
				
			||||||
        related_name="+",
 | 
					        related_name="+",
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
				
			|||||||
@ -5,10 +5,9 @@ Contact: michel.peltriaux@sgdnord.rlp.de
 | 
				
			|||||||
Created on: 01.09.21
 | 
					Created on: 01.09.21
 | 
				
			||||||
 | 
					
 | 
				
			||||||
"""
 | 
					"""
 | 
				
			||||||
from django.http import FileResponse, HttpRequest, HttpResponse, Http404
 | 
					from django.http import FileResponse, HttpRequest, Http404
 | 
				
			||||||
from django.utils.translation import gettext_lazy as _
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
from konova.forms import RemoveModalForm
 | 
					from konova.forms.modals import RemoveModalForm
 | 
				
			||||||
from konova.models import AbstractDocument
 | 
					from konova.models import AbstractDocument
 | 
				
			||||||
from konova.utils.message_templates import DOCUMENT_REMOVED_TEMPLATE
 | 
					from konova.utils.message_templates import DOCUMENT_REMOVED_TEMPLATE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -15,7 +15,8 @@ from api.models import APIUserToken
 | 
				
			|||||||
from intervention.inputs import GenerateInput
 | 
					from intervention.inputs import GenerateInput
 | 
				
			||||||
from user.models import User, UserNotification, Team
 | 
					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):
 | 
					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