Compare commits
47 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5550b8aa67 | |||
| a6f7e605e6 | |||
| 8bce8b8e75 | |||
| 4f02e8ee1b | |||
| 117a4437fe | |||
| f9c23a8f29 | |||
| a5f0d7f8c6 | |||
| 982d9f1930 | |||
| 9840c8fa8f | |||
| 890911c2dc | |||
| 3d10e84852 | |||
| 05145673e1 | |||
| fd4c9b0e5e | |||
| 23f0567aef | |||
| 70555ee5a3 | |||
| 1c38acea25 | |||
| cf282c937f | |||
| ee9834c0da | |||
| dacfbd0504 | |||
| 0e7859e538 | |||
| 16107f93f6 | |||
| d669adf54f | |||
| 589a7aec60 | |||
| 3f3ae4e31b | |||
| 1bbede0d12 | |||
| 0801b3f6ab | |||
| 44ad156595 | |||
| 5c95bc7d85 | |||
| 3b045fea8e | |||
| 6e5237ab88 | |||
| f19ad5f639 | |||
| 766e3c8d37 | |||
| e90625c8b5 | |||
| 90e76bc8f5 | |||
| 5e84dfc4ae | |||
| 615eb65534 | |||
| bcde400096 | |||
| aa02dbab96 | |||
| 8b7c4a82aa | |||
| b668c562dd | |||
| 460011a5e8 | |||
| 76e018e084 | |||
| e00b050c8b | |||
| ab9023aad0 | |||
| 0dd1ac70d1 | |||
| 2fcc41bf4a | |||
| b4e75fa2cd |
@@ -22,6 +22,7 @@ class TimespanReportForm(BaseForm):
|
|||||||
date_from = forms.DateField(
|
date_from = forms.DateField(
|
||||||
label_suffix="",
|
label_suffix="",
|
||||||
label=_("From"),
|
label=_("From"),
|
||||||
|
help_text=_("Entries created from..."),
|
||||||
widget=forms.DateInput(
|
widget=forms.DateInput(
|
||||||
attrs={
|
attrs={
|
||||||
"type": "date",
|
"type": "date",
|
||||||
@@ -34,6 +35,7 @@ class TimespanReportForm(BaseForm):
|
|||||||
date_to = forms.DateField(
|
date_to = forms.DateField(
|
||||||
label_suffix="",
|
label_suffix="",
|
||||||
label=_("To"),
|
label=_("To"),
|
||||||
|
help_text=_("Entries created until..."),
|
||||||
widget=forms.DateInput(
|
widget=forms.DateInput(
|
||||||
attrs={
|
attrs={
|
||||||
"type": "date",
|
"type": "date",
|
||||||
|
|||||||
@@ -15,40 +15,40 @@
|
|||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="col">{% trans 'Area of responsibility' %}</th>
|
<th scope="col">{% trans 'Area of responsibility' %}</th>
|
||||||
|
<th scope="col">{% trans 'Total' %}</th>
|
||||||
|
<th scope="col">{% trans 'Number single areas' %}</th>
|
||||||
<th scope="col">{% fa5_icon 'star' %} {% trans 'Checked' %}</th>
|
<th scope="col">{% fa5_icon 'star' %} {% trans 'Checked' %}</th>
|
||||||
<th scope="col">{% fa5_icon 'bookmark' %} {% trans 'Recorded' %}</th>
|
<th scope="col">{% fa5_icon 'bookmark' %} {% trans 'Recorded' %}</th>
|
||||||
<th scope="col">{% trans 'Number single areas' %}</th>
|
|
||||||
<th scope="col">{% trans 'Total' %}</th>
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{% trans 'Conservation office by law' %}</td>
|
<td>{% trans 'Conservation office by law' %}</td>
|
||||||
|
<td>{{report.compensation_report.queryset_registration_office_unb_count|default_if_zero:"-"}}</td>
|
||||||
|
<td>{{report.compensation_report.num_single_surfaces_total_unb|default_if_zero:"-"}}</td>
|
||||||
<td>{{report.compensation_report.queryset_registration_office_unb_checked_count|default_if_zero:"-"}}</td>
|
<td>{{report.compensation_report.queryset_registration_office_unb_checked_count|default_if_zero:"-"}}</td>
|
||||||
<td>{{report.compensation_report.queryset_registration_office_unb_recorded_count|default_if_zero:"-"}}</td>
|
<td>{{report.compensation_report.queryset_registration_office_unb_recorded_count|default_if_zero:"-"}}</td>
|
||||||
<td>{{report.compensation_report.num_single_surfaces_total_unb|default_if_zero:"-"}}</td>
|
|
||||||
<td>{{report.compensation_report.queryset_registration_office_unb_count|default_if_zero:"-"}}</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{% trans 'Land-use planning' %}</td>
|
<td>{% trans 'Land-use planning' %}</td>
|
||||||
|
<td>{{report.compensation_report.queryset_registration_office_tbp_count|default_if_zero:"-"}}</td>
|
||||||
|
<td>{{report.compensation_report.num_single_surfaces_total_tbp|default_if_zero:"-"}}</td>
|
||||||
<td>{{report.compensation_report.queryset_registration_office_tbp_checked_count|default_if_zero:"-"}}</td>
|
<td>{{report.compensation_report.queryset_registration_office_tbp_checked_count|default_if_zero:"-"}}</td>
|
||||||
<td>{{report.compensation_report.queryset_registration_office_tbp_recorded_count|default_if_zero:"-"}}</td>
|
<td>{{report.compensation_report.queryset_registration_office_tbp_recorded_count|default_if_zero:"-"}}</td>
|
||||||
<td>{{report.compensation_report.num_single_surfaces_total_tbp|default_if_zero:"-"}}</td>
|
|
||||||
<td>{{report.compensation_report.queryset_registration_office_tbp_count|default_if_zero:"-"}}</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{% trans 'Other registration office' %}</td>
|
<td>{% trans 'Other registration office' %}</td>
|
||||||
|
<td>{{report.compensation_report.queryset_registration_office_other_count|default_if_zero:"-"}}</td>
|
||||||
|
<td>{{report.compensation_report.num_single_surfaces_total_other|default_if_zero:"-"}}</td>
|
||||||
<td>{{report.compensation_report.queryset_registration_office_other_checked_count|default_if_zero:"-"}}</td>
|
<td>{{report.compensation_report.queryset_registration_office_other_checked_count|default_if_zero:"-"}}</td>
|
||||||
<td>{{report.compensation_report.queryset_registration_office_other_recorded_count|default_if_zero:"-"}}</td>
|
<td>{{report.compensation_report.queryset_registration_office_other_recorded_count|default_if_zero:"-"}}</td>
|
||||||
<td>{{report.compensation_report.num_single_surfaces_total_other|default_if_zero:"-"}}</td>
|
|
||||||
<td>{{report.compensation_report.queryset_registration_office_other_count|default_if_zero:"-"}}</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><strong>{% trans 'Total' %}</strong></td>
|
<td><strong>{% trans 'Total' %}</strong></td>
|
||||||
|
<td><strong>{{report.compensation_report.queryset_count|default_if_zero:"-"}}</strong></td>
|
||||||
|
<td><strong>{{report.compensation_report.num_single_surfaces_total|default_if_zero:"-"}}</strong></td>
|
||||||
<td><strong>{{report.compensation_report.queryset_checked_count|default_if_zero:"-"}}</strong></td>
|
<td><strong>{{report.compensation_report.queryset_checked_count|default_if_zero:"-"}}</strong></td>
|
||||||
<td><strong>{{report.compensation_report.queryset_recorded_count|default_if_zero:"-"}}</strong></td>
|
<td><strong>{{report.compensation_report.queryset_recorded_count|default_if_zero:"-"}}</strong></td>
|
||||||
<td><strong>{{report.compensation_report.num_single_surfaces_total|default_if_zero:"-"}}</strong></td>
|
|
||||||
<td><strong>{{report.compensation_report.queryset_count|default_if_zero:"-"}}</strong></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@@ -10,14 +10,14 @@
|
|||||||
<table class="table table-hover">
|
<table class="table table-hover">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="col" class="w-25">{% fa5_icon 'bookmark' %} {% trans 'Recorded' %}</th>
|
|
||||||
<th scope="col">{% trans 'Total' %}</th>
|
<th scope="col">{% trans 'Total' %}</th>
|
||||||
|
<th scope="col" class="w-25">{% fa5_icon 'bookmark' %} {% trans 'Recorded' %}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{report.eco_account_report.queryset_recorded_count|default_if_zero:"-"}}</td>
|
|
||||||
<td>{{report.eco_account_report.queryset_count|default_if_zero:"-"}}</td>
|
<td>{{report.eco_account_report.queryset_count|default_if_zero:"-"}}</td>
|
||||||
|
<td>{{report.eco_account_report.queryset_recorded_count|default_if_zero:"-"}}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@@ -1,22 +1,37 @@
|
|||||||
{% load i18n fontawesome_5 ksp_filters %}
|
{% load i18n fontawesome_5 ksp_filters %}
|
||||||
|
|
||||||
<h3>{% trans 'Deductions' %}</h3>
|
<h3>{% trans 'Deductions' %}</h3>
|
||||||
|
<strong>
|
||||||
|
{% blocktrans %}
|
||||||
|
Recorded = Counts the deductions whose interventions have been recorded
|
||||||
|
{% endblocktrans %}
|
||||||
|
</strong>
|
||||||
<div class="table-container">
|
<div class="table-container">
|
||||||
<table class="table table-hover">
|
<table class="table table-hover">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="col">{% fa5_icon 'bookmark' %} {% trans 'Recorded' %}</th>
|
|
||||||
<th scope="col">{% fa5_icon 'bookmark' %} {% trans 'Recorded' %} {% trans 'Surface' %}</th>
|
|
||||||
<th scope="col" class="w-25">{% trans 'Total' %}</th>
|
<th scope="col" class="w-25">{% trans 'Total' %}</th>
|
||||||
<th scope="col" class="w-25">{% trans 'Total' %} {% trans 'Surface' %}</th>
|
<th scope="col" class="w-25">{% trans 'Total' %} {% trans 'Surface' %}</th>
|
||||||
|
<th scope="col">{% fa5_icon 'bookmark' %} {% trans 'Recorded' %}</th>
|
||||||
|
<th scope="col">{% fa5_icon 'bookmark' %} {% trans 'Recorded' %} {% trans 'Surface' %}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{report.eco_account_report.queryset_deductions_recorded_count|default_if_zero:"-"}}</td>
|
|
||||||
<td>{{report.eco_account_report.recorded_deductions_sq_m|default_if_zero:"-"}}m²</td>
|
|
||||||
<td>{{report.eco_account_report.queryset_deductions_count|default_if_zero:"-"}}</td>
|
<td>{{report.eco_account_report.queryset_deductions_count|default_if_zero:"-"}}</td>
|
||||||
<td>{{report.eco_account_report.deductions_sq_m|default_if_zero:"-"}}m²</td>
|
<td>
|
||||||
|
{{report.eco_account_report.deductions_sq_m|default_if_zero:"-"}}
|
||||||
|
{% if report.eco_account_report.deductions_sq_m > 0 %}
|
||||||
|
m²
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>{{report.eco_account_report.queryset_deductions_recorded_count|default_if_zero:"-"}}</td>
|
||||||
|
<td>
|
||||||
|
{{report.eco_account_report.recorded_deductions_sq_m|default_if_zero:"-"}}
|
||||||
|
{% if report.eco_account_report.recorded_deductions_sq_m > 0 %}
|
||||||
|
m²
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@@ -14,16 +14,16 @@
|
|||||||
<table class="table table-hover">
|
<table class="table table-hover">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
<th scope="col" class="w-25">{% trans 'Total' %}</th>
|
||||||
<th scope="col">{% fa5_icon 'star' %} {% trans 'Checked' %}</th>
|
<th scope="col">{% fa5_icon 'star' %} {% trans 'Checked' %}</th>
|
||||||
<th scope="col">{% fa5_icon 'bookmark' %} {% trans 'Recorded' %}</th>
|
<th scope="col">{% fa5_icon 'bookmark' %} {% trans 'Recorded' %}</th>
|
||||||
<th scope="col" class="w-25">{% trans 'Total' %}</th>
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
|
<td>{{report.intervention_report.queryset_count|default_if_zero:"-"}}</td>
|
||||||
<td>{{report.intervention_report.queryset_checked_count|default_if_zero:"-"}}</td>
|
<td>{{report.intervention_report.queryset_checked_count|default_if_zero:"-"}}</td>
|
||||||
<td>{{report.intervention_report.queryset_recorded_count|default_if_zero:"-"}}</td>
|
<td>{{report.intervention_report.queryset_recorded_count|default_if_zero:"-"}}</td>
|
||||||
<td>{{report.intervention_report.queryset_count|default_if_zero:"-"}}</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@@ -5,29 +5,29 @@
|
|||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="w-25" scope="col">{% trans 'Compensation type' %}</th>
|
<th class="w-25" scope="col">{% trans 'Compensation type' %}</th>
|
||||||
|
<th class="w-25" scope="col">{% trans 'Total' %}</th>
|
||||||
<th class="w-25" scope="col">{% fa5_icon 'star' %} {% trans 'Checked' %}</th>
|
<th class="w-25" scope="col">{% fa5_icon 'star' %} {% trans 'Checked' %}</th>
|
||||||
<th class="w-25" scope="col">{% fa5_icon 'bookmark' %} {% trans 'Recorded' %}</th>
|
<th class="w-25" scope="col">{% fa5_icon 'bookmark' %} {% trans 'Recorded' %}</th>
|
||||||
<th class="w-25" scope="col">{% trans 'Total' %}</th>
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{% trans 'Compensation' %}</th>
|
<th>{% trans 'Compensation' %}</th>
|
||||||
|
<td>{{report.intervention_report.compensation_sum|default_if_zero:"-"}}</td>
|
||||||
<td>{{report.intervention_report.compensation_sum_checked|default_if_zero:"-"}}</td>
|
<td>{{report.intervention_report.compensation_sum_checked|default_if_zero:"-"}}</td>
|
||||||
<td>{{report.intervention_report.compensation_sum_recorded|default_if_zero:"-"}}</td>
|
<td>{{report.intervention_report.compensation_sum_recorded|default_if_zero:"-"}}</td>
|
||||||
<td>{{report.intervention_report.compensation_sum|default_if_zero:"-"}}</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{% trans 'Payment' %}</th>
|
<th>{% trans 'Payment' %}</th>
|
||||||
|
<td>{{report.intervention_report.payment_sum|default_if_zero:"-"}}</td>
|
||||||
<td>{{report.intervention_report.payment_sum_checked|default_if_zero:"-"}}</td>
|
<td>{{report.intervention_report.payment_sum_checked|default_if_zero:"-"}}</td>
|
||||||
<td>{{report.intervention_report.payment_sum_recorded|default_if_zero:"-"}}</td>
|
<td>{{report.intervention_report.payment_sum_recorded|default_if_zero:"-"}}</td>
|
||||||
<td>{{report.intervention_report.payment_sum|default_if_zero:"-"}}</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{% trans 'Deductions' %}</th>
|
<th>{% trans 'Deductions' %}</th>
|
||||||
|
<td>{{report.intervention_report.deduction_sum|default_if_zero:"-"}}</td>
|
||||||
<td>{{report.intervention_report.deduction_sum_checked|default_if_zero:"-"}}</td>
|
<td>{{report.intervention_report.deduction_sum_checked|default_if_zero:"-"}}</td>
|
||||||
<td>{{report.intervention_report.deduction_sum_recorded|default_if_zero:"-"}}</td>
|
<td>{{report.intervention_report.deduction_sum_recorded|default_if_zero:"-"}}</td>
|
||||||
<td>{{report.intervention_report.deduction_sum|default_if_zero:"-"}}</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@@ -13,15 +13,15 @@
|
|||||||
<th class="w-25" scope="col">
|
<th class="w-25" scope="col">
|
||||||
{% trans 'Law' %}
|
{% trans 'Law' %}
|
||||||
</th>
|
</th>
|
||||||
|
<th scope="col">
|
||||||
|
{% trans 'Total' %}
|
||||||
|
</th>
|
||||||
<th scope="col">
|
<th scope="col">
|
||||||
{% fa5_icon 'star' %} {% trans 'Checked' %}
|
{% fa5_icon 'star' %} {% trans 'Checked' %}
|
||||||
</th>
|
</th>
|
||||||
<th scope="col">
|
<th scope="col">
|
||||||
{% fa5_icon 'bookmark' %} {% trans 'Recorded' %}
|
{% fa5_icon 'bookmark' %} {% trans 'Recorded' %}
|
||||||
</th>
|
</th>
|
||||||
<th scope="col">
|
|
||||||
{% trans 'Total' %}
|
|
||||||
</th>
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -34,16 +34,16 @@
|
|||||||
{{law.long_name}}
|
{{law.long_name}}
|
||||||
</small>
|
</small>
|
||||||
</td>
|
</td>
|
||||||
|
<td>{{law.num|default_if_zero:"-"}}</td>
|
||||||
<td>{{law.num_checked|default_if_zero:"-"}}</td>
|
<td>{{law.num_checked|default_if_zero:"-"}}</td>
|
||||||
<td>{{law.num_recorded|default_if_zero:"-"}}</td>
|
<td>{{law.num_recorded|default_if_zero:"-"}}</td>
|
||||||
<td>{{law.num|default_if_zero:"-"}}</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<tr>
|
<tr>
|
||||||
<td><strong>{% trans 'Total' %}</strong></td>
|
<td><strong>{% trans 'Total' %}</strong></td>
|
||||||
|
<td><strong>{{report.intervention_report.law_sum|default_if_zero:"-"}}</strong></td>
|
||||||
<td><strong>{{report.intervention_report.law_sum_checked|default_if_zero:"-"}}</strong></td>
|
<td><strong>{{report.intervention_report.law_sum_checked|default_if_zero:"-"}}</strong></td>
|
||||||
<td><strong>{{report.intervention_report.law_sum_recorded|default_if_zero:"-"}}</strong></td>
|
<td><strong>{{report.intervention_report.law_sum_recorded|default_if_zero:"-"}}</strong></td>
|
||||||
<td><strong>{{report.intervention_report.law_sum|default_if_zero:"-"}}</strong></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@@ -14,26 +14,26 @@
|
|||||||
<table class="table table-hover">
|
<table class="table table-hover">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="col" class="w-25">{% fa5_icon 'star' %} {% trans 'Type' %}</th>
|
<th scope="col" class="w-25">{% trans 'Type' %}</th>
|
||||||
<th scope="col">{% fa5_icon 'bookmark' %} {% trans 'Recorded' %}</th>
|
|
||||||
<th scope="col">{% trans 'Total' %}</th>
|
<th scope="col">{% trans 'Total' %}</th>
|
||||||
|
<th scope="col">{% fa5_icon 'bookmark' %} {% trans 'Recorded' %}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{% trans 'Intervention' %}</td>
|
<td>{% trans 'Intervention' %}</td>
|
||||||
<td>{{report.old_data_report.queryset_intervention_recorded_count|default_if_zero:"-"}}</td>
|
|
||||||
<td>{{report.old_data_report.queryset_intervention_count|default_if_zero:"-"}}</td>
|
<td>{{report.old_data_report.queryset_intervention_count|default_if_zero:"-"}}</td>
|
||||||
|
<td>{{report.old_data_report.queryset_intervention_recorded_count|default_if_zero:"-"}}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{% trans 'Compensation' %}</td>
|
<td>{% trans 'Compensation' %}</td>
|
||||||
<td>{{report.old_data_report.queryset_comps_recorded_count|default_if_zero:"-"}}</td>
|
|
||||||
<td>{{report.old_data_report.queryset_comps_count|default_if_zero:"-"}}</td>
|
<td>{{report.old_data_report.queryset_comps_count|default_if_zero:"-"}}</td>
|
||||||
|
<td>{{report.old_data_report.queryset_comps_recorded_count|default_if_zero:"-"}}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{% trans 'Eco-account' %}</td>
|
<td>{% trans 'Eco-account' %}</td>
|
||||||
<td>{{report.old_data_report.queryset_acc_recorded_count|default_if_zero:"-"}}</td>
|
|
||||||
<td>{{report.old_data_report.queryset_acc_count|default_if_zero:"-"}}</td>
|
<td>{{report.old_data_report.queryset_acc_count|default_if_zero:"-"}}</td>
|
||||||
|
<td>{{report.old_data_report.queryset_acc_recorded_count|default_if_zero:"-"}}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
{% fa5_icon 'pencil-ruler' %}
|
{% fa5_icon 'pencil-ruler' %}
|
||||||
{% trans 'Old interventions' %}
|
{% trans 'Old interventions' %}
|
||||||
</h5>
|
</h5>
|
||||||
<span>{% trans 'Before' %} 16.06.2018</span>
|
<span>{% trans 'Binding date before' %} 16.06.2018</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ class TempExcelFile:
|
|||||||
for _iter_entry in _iter_obj:
|
for _iter_entry in _iter_obj:
|
||||||
j = 0
|
j = 0
|
||||||
for _iter_attr in _attrs:
|
for _iter_attr in _attrs:
|
||||||
_new_cell = ws.cell(start_cell.row + i, start_cell.column + j, getattr(_iter_entry, _iter_attr))
|
_new_cell = ws.cell(start_cell.row + i, start_cell.column + j, _iter_entry.get(_iter_attr, "MISSING"))
|
||||||
_new_cell.border = border_style
|
_new_cell.border = border_style
|
||||||
j += 1
|
j += 1
|
||||||
i += 1
|
i += 1
|
||||||
|
|||||||
Binary file not shown.
@@ -137,22 +137,36 @@ class TimespanReport:
|
|||||||
).order_by(
|
).order_by(
|
||||||
"long_name"
|
"long_name"
|
||||||
)
|
)
|
||||||
# Fetch all law ids which are used by any .legal object of an intervention object
|
|
||||||
intervention_laws_total = self.queryset.values_list("legal__laws__id")
|
evaluated_laws = []
|
||||||
intervention_laws_checked = self.queryset.filter(checked__isnull=False).values_list("legal__laws__id")
|
sum_num_checked = 0
|
||||||
intervention_laws_recorded = self.queryset.filter(recorded__isnull=False).values_list(
|
sum_num_recorded = 0
|
||||||
"legal__laws__id")
|
sum_num = 0
|
||||||
# Count how often which law id appears in the above list, return only the long_name of the law and the resulting
|
for law in laws:
|
||||||
# count (here 'num'). This is for keeping the db fetch as small as possible
|
num = self.queryset.filter(
|
||||||
# Compute the sum for total, checked and recorded
|
legal__laws__atom_id=law.atom_id
|
||||||
self.evaluated_laws = laws.annotate(
|
).count()
|
||||||
num=Count("id", filter=Q(id__in=intervention_laws_total)),
|
num_checked = self.queryset_checked.filter(
|
||||||
num_checked=Count("id", filter=Q(id__in=intervention_laws_checked)),
|
legal__laws__atom_id=law.atom_id
|
||||||
num_recorded=Count("id", filter=Q(id__in=intervention_laws_recorded)),
|
).count()
|
||||||
).values_list("short_name", "long_name", "num_checked", "num_recorded", "num", named=True)
|
num_recorded = self.queryset_recorded.filter(
|
||||||
self.law_sum = self.evaluated_laws.aggregate(sum_num=Sum("num"))["sum_num"]
|
legal__laws__atom_id=law.atom_id
|
||||||
self.law_sum_checked = self.evaluated_laws.aggregate(sum_num_checked=Sum("num_checked"))["sum_num_checked"]
|
).count()
|
||||||
self.law_sum_recorded = self.evaluated_laws.aggregate(sum_num_recorded=Sum("num_recorded"))["sum_num_recorded"]
|
evaluated_laws.append({
|
||||||
|
"short_name": law.short_name,
|
||||||
|
"long_name": law.long_name,
|
||||||
|
"num": num,
|
||||||
|
"num_checked": num_checked,
|
||||||
|
"num_recorded": num_recorded,
|
||||||
|
})
|
||||||
|
sum_num += num
|
||||||
|
sum_num_checked += num_checked
|
||||||
|
sum_num_recorded += num_recorded
|
||||||
|
|
||||||
|
self.evaluated_laws = evaluated_laws
|
||||||
|
self.law_sum = sum_num
|
||||||
|
self.law_sum_checked = sum_num_checked
|
||||||
|
self.law_sum_recorded = sum_num_recorded
|
||||||
|
|
||||||
def _evaluate_compensations(self):
|
def _evaluate_compensations(self):
|
||||||
""" Analyzes the types of compensation distribution
|
""" Analyzes the types of compensation distribution
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ from django.contrib.gis import geos
|
|||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
|
||||||
from api.tests.v1.share.test_api_sharing import BaseAPIV1TestCase
|
from api.tests.v1.share.test_api_sharing import BaseAPIV1TestCase
|
||||||
|
from konova.sub_settings.lanis_settings import DEFAULT_SRID_RLP
|
||||||
|
|
||||||
|
|
||||||
class APIV1UpdateTestCase(BaseAPIV1TestCase):
|
class APIV1UpdateTestCase(BaseAPIV1TestCase):
|
||||||
@@ -63,6 +64,7 @@ class APIV1UpdateTestCase(BaseAPIV1TestCase):
|
|||||||
|
|
||||||
put_props = put_body["properties"]
|
put_props = put_body["properties"]
|
||||||
put_geom = geos.fromstr(json.dumps(put_body))
|
put_geom = geos.fromstr(json.dumps(put_body))
|
||||||
|
put_geom.transform(DEFAULT_SRID_RLP)
|
||||||
self.assertEqual(put_geom, self.intervention.geometry.geom)
|
self.assertEqual(put_geom, self.intervention.geometry.geom)
|
||||||
self.assertEqual(put_props["title"], self.intervention.title)
|
self.assertEqual(put_props["title"], self.intervention.title)
|
||||||
self.assertNotEqual(modified_on, self.intervention.modified)
|
self.assertNotEqual(modified_on, self.intervention.modified)
|
||||||
@@ -92,6 +94,7 @@ class APIV1UpdateTestCase(BaseAPIV1TestCase):
|
|||||||
|
|
||||||
put_props = put_body["properties"]
|
put_props = put_body["properties"]
|
||||||
put_geom = geos.fromstr(json.dumps(put_body))
|
put_geom = geos.fromstr(json.dumps(put_body))
|
||||||
|
put_geom.transform(DEFAULT_SRID_RLP)
|
||||||
self.assertEqual(put_geom, self.compensation.geometry.geom)
|
self.assertEqual(put_geom, self.compensation.geometry.geom)
|
||||||
self.assertEqual(put_props["title"], self.compensation.title)
|
self.assertEqual(put_props["title"], self.compensation.title)
|
||||||
self.assertNotEqual(modified_on, self.compensation.modified)
|
self.assertNotEqual(modified_on, self.compensation.modified)
|
||||||
@@ -121,6 +124,7 @@ class APIV1UpdateTestCase(BaseAPIV1TestCase):
|
|||||||
|
|
||||||
put_props = put_body["properties"]
|
put_props = put_body["properties"]
|
||||||
put_geom = geos.fromstr(json.dumps(put_body))
|
put_geom = geos.fromstr(json.dumps(put_body))
|
||||||
|
put_geom.transform(DEFAULT_SRID_RLP)
|
||||||
self.assertEqual(put_geom, self.eco_account.geometry.geom)
|
self.assertEqual(put_geom, self.eco_account.geometry.geom)
|
||||||
self.assertEqual(put_props["title"], self.eco_account.title)
|
self.assertEqual(put_props["title"], self.eco_account.title)
|
||||||
self.assertNotEqual(modified_on, self.eco_account.modified)
|
self.assertNotEqual(modified_on, self.eco_account.modified)
|
||||||
@@ -152,6 +156,7 @@ class APIV1UpdateTestCase(BaseAPIV1TestCase):
|
|||||||
|
|
||||||
put_props = put_body["properties"]
|
put_props = put_body["properties"]
|
||||||
put_geom = geos.fromstr(json.dumps(put_body))
|
put_geom = geos.fromstr(json.dumps(put_body))
|
||||||
|
put_geom.transform(DEFAULT_SRID_RLP)
|
||||||
self.assertEqual(put_geom, self.ema.geometry.geom)
|
self.assertEqual(put_geom, self.ema.geometry.geom)
|
||||||
self.assertEqual(put_props["title"], self.ema.title)
|
self.assertEqual(put_props["title"], self.ema.title)
|
||||||
self.assertNotEqual(modified_on, self.ema.modified)
|
self.assertNotEqual(modified_on, self.ema.modified)
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ from django.contrib.gis import geos
|
|||||||
from django.contrib.gis.geos import GEOSGeometry
|
from django.contrib.gis.geos import GEOSGeometry
|
||||||
from django.core.paginator import Paginator
|
from django.core.paginator import Paginator
|
||||||
|
|
||||||
|
from konova.sub_settings.lanis_settings import DEFAULT_SRID_RLP
|
||||||
from konova.utils.message_templates import DATA_UNSHARED
|
from konova.utils.message_templates import DATA_UNSHARED
|
||||||
|
|
||||||
|
|
||||||
@@ -133,6 +134,8 @@ class AbstractModelAPISerializer:
|
|||||||
if isinstance(geojson, dict):
|
if isinstance(geojson, dict):
|
||||||
geojson = json.dumps(geojson)
|
geojson = json.dumps(geojson)
|
||||||
geometry = geos.fromstr(geojson)
|
geometry = geos.fromstr(geojson)
|
||||||
|
if geometry.srid != DEFAULT_SRID_RLP:
|
||||||
|
geometry.transform(DEFAULT_SRID_RLP)
|
||||||
if geometry.empty:
|
if geometry.empty:
|
||||||
geometry = None
|
geometry = None
|
||||||
return geometry
|
return geometry
|
||||||
|
|||||||
@@ -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,21 +14,22 @@ from user.models import User, Team
|
|||||||
from django.db import models, transaction
|
from django.db import models, transaction
|
||||||
from django.db.models import QuerySet, Sum
|
from django.db.models import QuerySet, Sum
|
||||||
from django.http import HttpRequest
|
from django.http import HttpRequest
|
||||||
from django.utils.translation import gettext_lazy as _
|
|
||||||
|
|
||||||
from compensation.managers import CompensationManager
|
from compensation.managers import CompensationManager
|
||||||
from compensation.models import CompensationState, CompensationAction
|
from compensation.models import CompensationState, CompensationAction
|
||||||
from compensation.utils.quality import CompensationQualityChecker
|
from compensation.utils.quality import CompensationQualityChecker
|
||||||
from konova.models import BaseObject, AbstractDocument, Deadline, generate_document_file_upload_path, \
|
from konova.models import BaseObject, AbstractDocument, Deadline, generate_document_file_upload_path, \
|
||||||
GeoReferencedMixin
|
GeoReferencedMixin, DeadlineType, ResubmitableObjectMixin
|
||||||
from konova.settings import DEFAULT_SRID_RLP, LANIS_LINK_TEMPLATE
|
|
||||||
from konova.utils.message_templates import DATA_UNSHARED_EXPLANATION, COMPENSATION_REMOVED_TEMPLATE, \
|
from konova.utils.message_templates import DATA_UNSHARED_EXPLANATION, COMPENSATION_REMOVED_TEMPLATE, \
|
||||||
DOCUMENT_REMOVED_TEMPLATE, COMPENSATION_EDITED_TEMPLATE, DEADLINE_REMOVED, ADDED_DEADLINE, \
|
DOCUMENT_REMOVED_TEMPLATE, DEADLINE_REMOVED, ADDED_DEADLINE, \
|
||||||
COMPENSATION_ACTION_REMOVED, COMPENSATION_STATE_REMOVED, INTERVENTION_HAS_REVOCATIONS_TEMPLATE
|
COMPENSATION_ACTION_REMOVED, COMPENSATION_STATE_REMOVED, INTERVENTION_HAS_REVOCATIONS_TEMPLATE
|
||||||
from user.models import UserActionLogEntry
|
from user.models import UserActionLogEntry
|
||||||
|
|
||||||
|
|
||||||
class AbstractCompensation(BaseObject, GeoReferencedMixin):
|
class AbstractCompensation(BaseObject,
|
||||||
|
GeoReferencedMixin,
|
||||||
|
ResubmitableObjectMixin
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Abstract compensation model which holds basic attributes, shared by subclasses like the regular Compensation,
|
Abstract compensation model which holds basic attributes, shared by subclasses like the regular Compensation,
|
||||||
EMA or EcoAccount.
|
EMA or EcoAccount.
|
||||||
@@ -226,6 +227,15 @@ class AbstractCompensation(BaseObject, GeoReferencedMixin):
|
|||||||
request = self.set_geometry_conflict_message(request)
|
request = self.set_geometry_conflict_message(request)
|
||||||
return request
|
return request
|
||||||
|
|
||||||
|
def get_finished_deadlines(self):
|
||||||
|
""" Getter for FINISHED-deadlines
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
queryset (QuerySet): The finished deadlines
|
||||||
|
"""
|
||||||
|
return self.deadlines.filter(
|
||||||
|
type=DeadlineType.FINISHED
|
||||||
|
)
|
||||||
|
|
||||||
class CEFMixin(models.Model):
|
class CEFMixin(models.Model):
|
||||||
""" Provides CEF flag as Mixin
|
""" Provides CEF flag as Mixin
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ from compensation.models.compensation import AbstractCompensation, PikMixin
|
|||||||
from compensation.utils.quality import EcoAccountQualityChecker
|
from compensation.utils.quality import EcoAccountQualityChecker
|
||||||
from konova.models import ShareableObjectMixin, RecordableObjectMixin, AbstractDocument, BaseResource, \
|
from konova.models import ShareableObjectMixin, RecordableObjectMixin, AbstractDocument, BaseResource, \
|
||||||
generate_document_file_upload_path
|
generate_document_file_upload_path
|
||||||
|
from konova.tasks import celery_send_mail_deduction_changed, celery_send_mail_deduction_changed_team
|
||||||
|
|
||||||
|
|
||||||
class EcoAccount(AbstractCompensation, ShareableObjectMixin, RecordableObjectMixin, PikMixin):
|
class EcoAccount(AbstractCompensation, ShareableObjectMixin, RecordableObjectMixin, PikMixin):
|
||||||
@@ -161,6 +162,25 @@ class EcoAccount(AbstractCompensation, ShareableObjectMixin, RecordableObjectMix
|
|||||||
"""
|
"""
|
||||||
return reverse("compensation:acc:share", args=(self.id, self.access_token))
|
return reverse("compensation:acc:share", args=(self.id, self.access_token))
|
||||||
|
|
||||||
|
def send_notification_mail_on_deduction_change(self, data_change: dict):
|
||||||
|
""" Sends notification mails for changes on the deduction
|
||||||
|
|
||||||
|
Args:
|
||||||
|
data_change ():
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
# Send mail
|
||||||
|
shared_users = self.shared_users.values_list("id", flat=True)
|
||||||
|
for user_id in shared_users:
|
||||||
|
celery_send_mail_deduction_changed.delay(self.identifier, self.title, user_id, data_change)
|
||||||
|
|
||||||
|
# Send mail
|
||||||
|
shared_teams = self.shared_teams.values_list("id", flat=True)
|
||||||
|
for team_id in shared_teams:
|
||||||
|
celery_send_mail_deduction_changed_team.delay(self.identifier, self.title, team_id, data_change)
|
||||||
|
|
||||||
|
|
||||||
class EcoAccountDocument(AbstractDocument):
|
class EcoAccountDocument(AbstractDocument):
|
||||||
"""
|
"""
|
||||||
@@ -251,4 +271,4 @@ class EcoAccountDeduction(BaseResource):
|
|||||||
if user is not None:
|
if user is not None:
|
||||||
self.intervention.mark_as_edited(user, edit_comment=DEDUCTION_REMOVED)
|
self.intervention.mark_as_edited(user, edit_comment=DEDUCTION_REMOVED)
|
||||||
self.account.mark_as_edited(user, edit_comment=DEDUCTION_REMOVED)
|
self.account.mark_as_edited(user, edit_comment=DEDUCTION_REMOVED)
|
||||||
super().delete(*args, **kwargs)
|
super().delete(*args, **kwargs)
|
||||||
|
|||||||
@@ -12,6 +12,9 @@
|
|||||||
</button>
|
</button>
|
||||||
</a>
|
</a>
|
||||||
{% if has_access %}
|
{% if has_access %}
|
||||||
|
<button class="btn btn-default btn-modal mr-2" title="{% trans 'Resubmission' %}" data-form-url="{% url 'compensation:resubmission-create' obj.id %}">
|
||||||
|
{% fa5_icon 'bell' %}
|
||||||
|
</button>
|
||||||
{% if is_default_member %}
|
{% if is_default_member %}
|
||||||
<a href="{% url 'compensation:edit' obj.id %}" class="mr-2">
|
<a href="{% url 'compensation:edit' obj.id %}" class="mr-2">
|
||||||
<button class="btn btn-default" title="{% trans 'Edit' %}">
|
<button class="btn btn-default" title="{% trans 'Edit' %}">
|
||||||
|
|||||||
@@ -20,6 +20,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% if not has_finished_deadlines %}
|
||||||
|
<div class="alert alert-danger mb-0">
|
||||||
|
{% trans 'Missing finished deadline ' %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
<div class="card-body scroll-300 p-2">
|
<div class="card-body scroll-300 p-2">
|
||||||
<table class="table table-hover">
|
<table class="table table-hover">
|
||||||
<thead>
|
<thead>
|
||||||
|
|||||||
@@ -123,9 +123,16 @@
|
|||||||
{% include 'user/includes/team_data_modal_button.html' %}
|
{% include 'user/includes/team_data_modal_button.html' %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<hr>
|
<hr>
|
||||||
{% for user in obj.intervention.users.all %}
|
{% if has_access %}
|
||||||
{% include 'user/includes/contact_modal_button.html' %}
|
{% for user in obj.users.all %}
|
||||||
{% endfor %}
|
{% include 'user/includes/contact_modal_button.html' %}
|
||||||
|
{% endfor %}
|
||||||
|
{% else %}
|
||||||
|
<span title="{% trans 'The data must be shared with you, if you want to see which other users have shared access as well.' %}">
|
||||||
|
{% fa5_icon 'eye-slash' %}
|
||||||
|
{{obj.users.count}} {% trans 'other users' %}
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@@ -12,6 +12,9 @@
|
|||||||
</button>
|
</button>
|
||||||
</a>
|
</a>
|
||||||
{% if has_access %}
|
{% if has_access %}
|
||||||
|
<button class="btn btn-default btn-modal mr-2" title="{% trans 'Resubmission' %}" data-form-url="{% url 'compensation:acc:resubmission-create' obj.id %}">
|
||||||
|
{% fa5_icon 'bell' %}
|
||||||
|
</button>
|
||||||
<button class="btn btn-default btn-modal mr-2" title="{% trans 'Share' %}" data-form-url="{% url 'compensation:acc:share-create' obj.id %}">
|
<button class="btn btn-default btn-modal mr-2" title="{% trans 'Share' %}" data-form-url="{% url 'compensation:acc:share-create' obj.id %}">
|
||||||
{% fa5_icon 'share-alt' %}
|
{% fa5_icon 'share-alt' %}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -20,6 +20,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% if not has_finished_deadlines %}
|
||||||
|
<div class="alert alert-danger mb-0">
|
||||||
|
{% trans 'Missing finished deadline ' %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
<div class="card-body scroll-300 p-2">
|
<div class="card-body scroll-300 p-2">
|
||||||
<table class="table table-hover">
|
<table class="table table-hover">
|
||||||
<thead>
|
<thead>
|
||||||
|
|||||||
@@ -101,9 +101,16 @@
|
|||||||
{% include 'user/includes/team_data_modal_button.html' %}
|
{% include 'user/includes/team_data_modal_button.html' %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<hr>
|
<hr>
|
||||||
{% for user in obj.users.all %}
|
{% if has_access %}
|
||||||
{% include 'user/includes/contact_modal_button.html' %}
|
{% for user in obj.users.all %}
|
||||||
{% endfor %}
|
{% include 'user/includes/contact_modal_button.html' %}
|
||||||
|
{% endfor %}
|
||||||
|
{% else %}
|
||||||
|
<span title="{% trans 'The data must be shared with you, if you want to see which other users have shared access as well.' %}">
|
||||||
|
{% fa5_icon 'eye-slash' %}
|
||||||
|
{{obj.users.count}} {% trans 'other users' %}
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ urlpatterns = [
|
|||||||
path('<id>/deadline/<deadline_id>/edit', deadline_edit_view, name='deadline-edit'),
|
path('<id>/deadline/<deadline_id>/edit', deadline_edit_view, name='deadline-edit'),
|
||||||
path('<id>/deadline/<deadline_id>/remove', deadline_remove_view, name='deadline-remove'),
|
path('<id>/deadline/<deadline_id>/remove', deadline_remove_view, name='deadline-remove'),
|
||||||
path('<id>/report', report_view, name='report'),
|
path('<id>/report', report_view, name='report'),
|
||||||
|
path('<id>/resub', create_resubmission_view, name='resubmission-create'),
|
||||||
|
|
||||||
# Documents
|
# Documents
|
||||||
path('<id>/document/new/', new_document_view, name='new-doc'),
|
path('<id>/document/new/', new_document_view, name='new-doc'),
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ urlpatterns = [
|
|||||||
path('<id>/report', report_view, name='report'),
|
path('<id>/report', report_view, name='report'),
|
||||||
path('<id>/edit', edit_view, name='edit'),
|
path('<id>/edit', edit_view, name='edit'),
|
||||||
path('<id>/remove', remove_view, name='remove'),
|
path('<id>/remove', remove_view, name='remove'),
|
||||||
|
path('<id>/resub', create_resubmission_view, name='resubmission-create'),
|
||||||
|
|
||||||
path('<id>/state/new', state_new_view, name='new-state'),
|
path('<id>/state/new', state_new_view, name='new-state'),
|
||||||
path('<id>/state/<state_id>/edit', state_edit_view, name='state-edit'),
|
path('<id>/state/<state_id>/edit', state_edit_view, name='state-edit'),
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ class CompensationQualityChecker(AbstractQualityChecker):
|
|||||||
self._check_states()
|
self._check_states()
|
||||||
self._check_actions()
|
self._check_actions()
|
||||||
self._check_geometry()
|
self._check_geometry()
|
||||||
|
self._check_deadlines()
|
||||||
self.valid = len(self.messages) == 0
|
self.valid = len(self.messages) == 0
|
||||||
|
|
||||||
def _check_states(self):
|
def _check_states(self):
|
||||||
@@ -47,6 +48,16 @@ class CompensationQualityChecker(AbstractQualityChecker):
|
|||||||
if not self.obj.actions.all():
|
if not self.obj.actions.all():
|
||||||
self._add_missing_attr_name(_con("Compensation", "Actions"))
|
self._add_missing_attr_name(_con("Compensation", "Actions"))
|
||||||
|
|
||||||
|
def _check_deadlines(self):
|
||||||
|
""" Checks data quality for related Deadline objects
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
finished_deadlines = self.obj.get_finished_deadlines()
|
||||||
|
if not finished_deadlines.exists():
|
||||||
|
self._add_missing_attr_name(_("Finished deadlines"))
|
||||||
|
|
||||||
|
|
||||||
class EcoAccountQualityChecker(CompensationQualityChecker):
|
class EcoAccountQualityChecker(CompensationQualityChecker):
|
||||||
def run_check(self):
|
def run_check(self):
|
||||||
|
|||||||
@@ -14,7 +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
|
||||||
|
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
|
||||||
@@ -240,6 +242,7 @@ def detail_view(request: HttpRequest, id: str):
|
|||||||
"is_ets_member": in_group(_user, ETS_GROUP),
|
"is_ets_member": in_group(_user, ETS_GROUP),
|
||||||
"LANIS_LINK": comp.get_LANIS_link(),
|
"LANIS_LINK": comp.get_LANIS_link(),
|
||||||
TAB_TITLE_IDENTIFIER: f"{comp.identifier} - {comp.title}",
|
TAB_TITLE_IDENTIFIER: f"{comp.identifier} - {comp.title}",
|
||||||
|
"has_finished_deadlines": comp.get_finished_deadlines().exists(),
|
||||||
}
|
}
|
||||||
context = BaseContext(request, context).context
|
context = BaseContext(request, context).context
|
||||||
return render(request, template, context)
|
return render(request, template, context)
|
||||||
@@ -655,3 +658,26 @@ def report_view(request: HttpRequest, id: str):
|
|||||||
}
|
}
|
||||||
context = BaseContext(request, context).context
|
context = BaseContext(request, context).context
|
||||||
return render(request, template, context)
|
return render(request, template, context)
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
@default_group_required
|
||||||
|
@shared_access_required(Compensation, "id")
|
||||||
|
def create_resubmission_view(request: HttpRequest, id: str):
|
||||||
|
""" Renders resubmission form for a compensation
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request (HttpRequest): The incoming request
|
||||||
|
id (str): Compensation's id
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
com = get_object_or_404(Compensation, id=id)
|
||||||
|
form = ResubmissionModalForm(request.POST or None, instance=com, request=request)
|
||||||
|
form.action_url = reverse("compensation:resubmission-create", args=(id,))
|
||||||
|
return form.process_request(
|
||||||
|
request,
|
||||||
|
msg_success=_("Resubmission set"),
|
||||||
|
redirect_url=reverse("compensation:detail", args=(id,))
|
||||||
|
)
|
||||||
|
|||||||
@@ -25,14 +25,15 @@ from intervention.forms.modalForms import NewDeductionModalForm, ShareModalForm,
|
|||||||
from konova.contexts import BaseContext
|
from konova.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
|
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, \
|
||||||
@@ -242,6 +243,7 @@ def detail_view(request: HttpRequest, id: str):
|
|||||||
"deductions": deductions,
|
"deductions": deductions,
|
||||||
"actions": actions,
|
"actions": actions,
|
||||||
TAB_TITLE_IDENTIFIER: f"{acc.identifier} - {acc.title}",
|
TAB_TITLE_IDENTIFIER: f"{acc.identifier} - {acc.title}",
|
||||||
|
"has_finished_deadlines": acc.get_finished_deadlines().exists(),
|
||||||
}
|
}
|
||||||
context = BaseContext(request, context).context
|
context = BaseContext(request, context).context
|
||||||
return render(request, template, context)
|
return render(request, template, context)
|
||||||
@@ -837,4 +839,27 @@ def create_share_view(request: HttpRequest, id: str):
|
|||||||
return form.process_request(
|
return form.process_request(
|
||||||
request,
|
request,
|
||||||
msg_success=_("Share settings updated")
|
msg_success=_("Share settings updated")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
@default_group_required
|
||||||
|
@shared_access_required(EcoAccount, "id")
|
||||||
|
def create_resubmission_view(request: HttpRequest, id: str):
|
||||||
|
""" Renders resubmission form for an eco account
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request (HttpRequest): The incoming request
|
||||||
|
id (str): EcoAccount's id
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
acc = get_object_or_404(EcoAccount, id=id)
|
||||||
|
form = ResubmissionModalForm(request.POST or None, instance=acc, request=request)
|
||||||
|
form.action_url = reverse("compensation:acc:resubmission-create", args=(id,))
|
||||||
|
return form.process_request(
|
||||||
|
request,
|
||||||
|
msg_success=_("Resubmission set"),
|
||||||
|
redirect_url=reverse("compensation:acc:detail", args=(id,))
|
||||||
)
|
)
|
||||||
@@ -15,7 +15,6 @@ from compensation.forms.modalForms import NewPaymentForm, RemovePaymentModalForm
|
|||||||
from compensation.models import Payment
|
from 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'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -12,6 +12,9 @@
|
|||||||
</button>
|
</button>
|
||||||
</a>
|
</a>
|
||||||
{% if has_access %}
|
{% if has_access %}
|
||||||
|
<button class="btn btn-default btn-modal mr-2" title="{% trans 'Resubmission' %}" data-form-url="{% url 'ema:resubmission-create' obj.id %}">
|
||||||
|
{% fa5_icon 'bell' %}
|
||||||
|
</button>
|
||||||
<button class="btn btn-default btn-modal mr-2" title="{% trans 'Share' %}" data-form-url="{% url 'ema:share-create' obj.id %}">
|
<button class="btn btn-default btn-modal mr-2" title="{% trans 'Share' %}" data-form-url="{% url 'ema:share-create' obj.id %}">
|
||||||
{% fa5_icon 'share-alt' %}
|
{% fa5_icon 'share-alt' %}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -20,6 +20,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% if not has_finished_deadlines %}
|
||||||
|
<div class="alert alert-danger mb-0">
|
||||||
|
{% trans 'Missing finished deadline ' %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
<div class="card-body scroll-300 p-2">
|
<div class="card-body scroll-300 p-2">
|
||||||
<table class="table table-hover">
|
<table class="table table-hover">
|
||||||
<thead>
|
<thead>
|
||||||
|
|||||||
@@ -87,9 +87,16 @@
|
|||||||
{% include 'user/includes/team_data_modal_button.html' %}
|
{% include 'user/includes/team_data_modal_button.html' %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<hr>
|
<hr>
|
||||||
{% for user in obj.user.all %}
|
{% if has_access %}
|
||||||
{% include 'user/includes/contact_modal_button.html' %}
|
{% for user in obj.users.all %}
|
||||||
{% endfor %}
|
{% include 'user/includes/contact_modal_button.html' %}
|
||||||
|
{% endfor %}
|
||||||
|
{% else %}
|
||||||
|
<span title="{% trans 'The data must be shared with you, if you want to see which other users have shared access as well.' %}">
|
||||||
|
{% fa5_icon 'eye-slash' %}
|
||||||
|
{{obj.users.count}} {% trans 'other users' %}
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ urlpatterns = [
|
|||||||
path('<id>/remove', remove_view, name='remove'),
|
path('<id>/remove', remove_view, name='remove'),
|
||||||
path('<id>/record', record_view, name='record'),
|
path('<id>/record', record_view, name='record'),
|
||||||
path('<id>/report', report_view, name='report'),
|
path('<id>/report', report_view, name='report'),
|
||||||
|
path('<id>/resub', create_resubmission_view, name='resubmission-create'),
|
||||||
|
|
||||||
path('<id>/state/new', state_new_view, name='new-state'),
|
path('<id>/state/new', state_new_view, name='new-state'),
|
||||||
path('<id>/state/<state_id>/remove', state_remove_view, name='state-remove'),
|
path('<id>/state/<state_id>/remove', state_remove_view, name='state-remove'),
|
||||||
|
|||||||
31
ema/views.py
31
ema/views.py
@@ -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
|
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
|
||||||
@@ -166,6 +167,7 @@ def detail_view(request: HttpRequest, id: str):
|
|||||||
"is_ets_member": in_group(_user, ETS_GROUP),
|
"is_ets_member": in_group(_user, ETS_GROUP),
|
||||||
"LANIS_LINK": ema.get_LANIS_link(),
|
"LANIS_LINK": ema.get_LANIS_link(),
|
||||||
TAB_TITLE_IDENTIFIER: f"{ema.identifier} - {ema.title}",
|
TAB_TITLE_IDENTIFIER: f"{ema.identifier} - {ema.title}",
|
||||||
|
"has_finished_deadlines": ema.get_finished_deadlines().exists(),
|
||||||
}
|
}
|
||||||
context = BaseContext(request, context).context
|
context = BaseContext(request, context).context
|
||||||
return render(request, template, context)
|
return render(request, template, context)
|
||||||
@@ -709,4 +711,27 @@ def deadline_remove_view(request: HttpRequest, id: str, deadline_id: str):
|
|||||||
request,
|
request,
|
||||||
msg_success=DEADLINE_REMOVED,
|
msg_success=DEADLINE_REMOVED,
|
||||||
redirect_url=reverse("ema:detail", args=(id,)) + "#related_data"
|
redirect_url=reverse("ema:detail", args=(id,)) + "#related_data"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
@conservation_office_group_required
|
||||||
|
@shared_access_required(Ema, "id")
|
||||||
|
def create_resubmission_view(request: HttpRequest, id: str):
|
||||||
|
""" Renders resubmission form for an EMA
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request (HttpRequest): The incoming request
|
||||||
|
id (str): EMA's id
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
ema = get_object_or_404(Ema, id=id)
|
||||||
|
form = ResubmissionModalForm(request.POST or None, instance=ema, request=request)
|
||||||
|
form.action_url = reverse("ema:resubmission-create", args=(id,))
|
||||||
|
return form.process_request(
|
||||||
|
request,
|
||||||
|
msg_success=_("Resubmission set"),
|
||||||
|
redirect_url=reverse("ema:detail", args=(id,))
|
||||||
|
)
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ Created on: 02.12.20
|
|||||||
from dal import autocomplete
|
from 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
|
||||||
|
|
||||||
@@ -508,28 +509,44 @@ class EditEcoAccountDeductionModalForm(NewDeductionModalForm):
|
|||||||
deduction = self.deduction
|
deduction = self.deduction
|
||||||
form_account = self.cleaned_data.get("account", None)
|
form_account = self.cleaned_data.get("account", None)
|
||||||
form_intervention = self.cleaned_data.get("intervention", None)
|
form_intervention = self.cleaned_data.get("intervention", None)
|
||||||
current_account = deduction.account
|
old_account = deduction.account
|
||||||
current_intervention = deduction.intervention
|
old_intervention = deduction.intervention
|
||||||
|
old_surface = deduction.surface
|
||||||
|
|
||||||
# If account or intervention has been changed, we put that change in the logs just as if the deduction has
|
# If account or intervention has been changed, we put that change in the logs just as if the deduction has
|
||||||
# been removed for this entry. Act as if the deduction is newly created for the new entries
|
# been removed for this entry. Act as if the deduction is newly created for the new entries
|
||||||
if current_account != form_account:
|
if old_account != form_account:
|
||||||
current_account.mark_as_edited(self.user, self.request, edit_comment=DEDUCTION_REMOVED)
|
old_account.mark_as_edited(self.user, self.request, edit_comment=DEDUCTION_REMOVED)
|
||||||
form_account.mark_as_edited(self.user, self.request, edit_comment=DEDUCTION_ADDED)
|
form_account.mark_as_edited(self.user, self.request, edit_comment=DEDUCTION_ADDED)
|
||||||
else:
|
else:
|
||||||
current_account.mark_as_edited(self.user, self.request, edit_comment=DEDUCTION_EDITED)
|
old_account.mark_as_edited(self.user, self.request, edit_comment=DEDUCTION_EDITED)
|
||||||
|
|
||||||
if current_intervention != form_intervention:
|
if old_intervention != form_intervention:
|
||||||
current_intervention.mark_as_edited(self.user, self.request, edit_comment=DEDUCTION_REMOVED)
|
old_intervention.mark_as_edited(self.user, self.request, edit_comment=DEDUCTION_REMOVED)
|
||||||
form_intervention.mark_as_edited(self.user, self.request, edit_comment=DEDUCTION_ADDED)
|
form_intervention.mark_as_edited(self.user, self.request, edit_comment=DEDUCTION_ADDED)
|
||||||
else:
|
else:
|
||||||
current_intervention.mark_as_edited(self.user, self.request, edit_comment=DEDUCTION_EDITED)
|
old_intervention.mark_as_edited(self.user, self.request, edit_comment=DEDUCTION_EDITED)
|
||||||
|
|
||||||
deduction.account = form_account
|
deduction.account = form_account
|
||||||
deduction.intervention = self.cleaned_data.get("intervention", None)
|
deduction.intervention = self.cleaned_data.get("intervention", None)
|
||||||
deduction.surface = self.cleaned_data.get("surface", None)
|
deduction.surface = self.cleaned_data.get("surface", None)
|
||||||
deduction.save()
|
deduction.save()
|
||||||
|
|
||||||
|
data_changes = {
|
||||||
|
"surface": {
|
||||||
|
"old": old_surface,
|
||||||
|
"new": deduction.surface,
|
||||||
|
},
|
||||||
|
"intervention": {
|
||||||
|
"old": old_intervention.identifier,
|
||||||
|
"new": deduction.intervention.identifier,
|
||||||
|
},
|
||||||
|
"account": {
|
||||||
|
"old": old_account.identifier,
|
||||||
|
"new": deduction.account.identifier,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
old_account.send_notification_mail_on_deduction_change(data_changes)
|
||||||
return deduction
|
return deduction
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
19
intervention/migrations/0005_intervention_resubmission.py
Normal file
19
intervention/migrations/0005_intervention_resubmission.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# Generated by Django 3.1.3 on 2022-08-15 06:03
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('konova', '0014_resubmission'),
|
||||||
|
('intervention', '0004_auto_20220303_0956'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='intervention',
|
||||||
|
name='resubmission',
|
||||||
|
field=models.ManyToManyField(blank=True, null=True, related_name='_intervention_resubmission_+', to='konova.Resubmission'),
|
||||||
|
),
|
||||||
|
]
|
||||||
23
intervention/migrations/0006_auto_20220815_0803.py
Normal file
23
intervention/migrations/0006_auto_20220815_0803.py
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# Generated by Django 3.1.3 on 2022-08-15 06:03
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('konova', '0014_resubmission'),
|
||||||
|
('intervention', '0005_intervention_resubmission'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='intervention',
|
||||||
|
name='resubmission',
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='intervention',
|
||||||
|
name='resubmissions',
|
||||||
|
field=models.ManyToManyField(blank=True, null=True, related_name='_intervention_resubmissions_+', to='konova.Resubmission'),
|
||||||
|
),
|
||||||
|
]
|
||||||
19
intervention/migrations/0007_auto_20220815_1030.py
Normal file
19
intervention/migrations/0007_auto_20220815_1030.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# Generated by Django 3.1.3 on 2022-08-15 08:30
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('konova', '0014_resubmission'),
|
||||||
|
('intervention', '0006_auto_20220815_0803'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='intervention',
|
||||||
|
name='resubmissions',
|
||||||
|
field=models.ManyToManyField(blank=True, related_name='_intervention_resubmissions_+', to='konova.Resubmission'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -26,14 +26,19 @@ from intervention.models.revocation import RevocationDocument, Revocation
|
|||||||
from intervention.utils.quality import InterventionQualityChecker
|
from intervention.utils.quality import InterventionQualityChecker
|
||||||
from konova.models import generate_document_file_upload_path, AbstractDocument, BaseObject, \
|
from konova.models import generate_document_file_upload_path, AbstractDocument, BaseObject, \
|
||||||
ShareableObjectMixin, \
|
ShareableObjectMixin, \
|
||||||
RecordableObjectMixin, CheckableObjectMixin, GeoReferencedMixin
|
RecordableObjectMixin, CheckableObjectMixin, GeoReferencedMixin, ResubmitableObjectMixin
|
||||||
from konova.settings import LANIS_LINK_TEMPLATE, LANIS_ZOOM_LUT, DEFAULT_SRID_RLP
|
|
||||||
from konova.utils.message_templates import DATA_UNSHARED_EXPLANATION, DOCUMENT_REMOVED_TEMPLATE, \
|
from konova.utils.message_templates import DATA_UNSHARED_EXPLANATION, DOCUMENT_REMOVED_TEMPLATE, \
|
||||||
PAYMENT_REMOVED, PAYMENT_ADDED, REVOCATION_REMOVED, INTERVENTION_HAS_REVOCATIONS_TEMPLATE
|
PAYMENT_REMOVED, PAYMENT_ADDED, REVOCATION_REMOVED, INTERVENTION_HAS_REVOCATIONS_TEMPLATE
|
||||||
from user.models import UserActionLogEntry
|
from user.models import UserActionLogEntry
|
||||||
|
|
||||||
|
|
||||||
class Intervention(BaseObject, ShareableObjectMixin, RecordableObjectMixin, CheckableObjectMixin, GeoReferencedMixin):
|
class Intervention(BaseObject,
|
||||||
|
ShareableObjectMixin,
|
||||||
|
RecordableObjectMixin,
|
||||||
|
CheckableObjectMixin,
|
||||||
|
GeoReferencedMixin,
|
||||||
|
ResubmitableObjectMixin
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Interventions are e.g. construction sites where nature used to be.
|
Interventions are e.g. construction sites where nature used to be.
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -12,6 +12,9 @@
|
|||||||
</button>
|
</button>
|
||||||
</a>
|
</a>
|
||||||
{% if has_access %}
|
{% if has_access %}
|
||||||
|
<button class="btn btn-default btn-modal mr-2" title="{% trans 'Resubmission' %}" data-form-url="{% url 'intervention:resubmission-create' obj.id %}">
|
||||||
|
{% fa5_icon 'bell' %}
|
||||||
|
</button>
|
||||||
<button class="btn btn-default btn-modal mr-2" title="{% trans 'Share' %}" data-form-url="{% url 'intervention:share-create' obj.id %}">
|
<button class="btn btn-default btn-modal mr-2" title="{% trans 'Share' %}" data-form-url="{% url 'intervention:share-create' obj.id %}">
|
||||||
{% fa5_icon 'share-alt' %}
|
{% fa5_icon 'share-alt' %}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -129,9 +129,16 @@
|
|||||||
{% include 'user/includes/team_data_modal_button.html' %}
|
{% include 'user/includes/team_data_modal_button.html' %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<hr>
|
<hr>
|
||||||
{% for user in obj.users.all %}
|
{% if has_access %}
|
||||||
{% include 'user/includes/contact_modal_button.html' %}
|
{% for user in obj.users.all %}
|
||||||
{% endfor %}
|
{% include 'user/includes/contact_modal_button.html' %}
|
||||||
|
{% endfor %}
|
||||||
|
{% else %}
|
||||||
|
<span title="{% trans 'The data must be shared with you, if you want to see which other users have shared access as well.' %}">
|
||||||
|
{% fa5_icon 'eye-slash' %}
|
||||||
|
{{obj.users.count}} {% trans 'other users' %}
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ from django.urls import path
|
|||||||
from intervention.views import index_view, new_view, detail_view, edit_view, remove_view, new_document_view, share_view, \
|
from intervention.views import index_view, new_view, detail_view, edit_view, remove_view, new_document_view, share_view, \
|
||||||
create_share_view, remove_revocation_view, new_revocation_view, check_view, log_view, new_deduction_view, \
|
create_share_view, remove_revocation_view, new_revocation_view, check_view, log_view, new_deduction_view, \
|
||||||
record_view, remove_document_view, get_document_view, get_revocation_view, new_id_view, report_view, \
|
record_view, remove_document_view, get_document_view, get_revocation_view, new_id_view, report_view, \
|
||||||
remove_deduction_view, remove_compensation_view, edit_deduction_view, edit_revocation_view, edit_document_view
|
remove_deduction_view, remove_compensation_view, edit_deduction_view, edit_revocation_view, edit_document_view, \
|
||||||
|
create_resubmission_view
|
||||||
|
|
||||||
app_name = "intervention"
|
app_name = "intervention"
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
@@ -26,6 +27,7 @@ urlpatterns = [
|
|||||||
path('<id>/check', check_view, name='check'),
|
path('<id>/check', check_view, name='check'),
|
||||||
path('<id>/record', record_view, name='record'),
|
path('<id>/record', record_view, name='record'),
|
||||||
path('<id>/report', report_view, name='report'),
|
path('<id>/report', report_view, name='report'),
|
||||||
|
path('<id>/resub', create_resubmission_view, name='resubmission-create'),
|
||||||
|
|
||||||
# Compensations
|
# Compensations
|
||||||
path('<id>/compensation/<comp_id>/remove', remove_compensation_view, name='remove-compensation'),
|
path('<id>/compensation/<comp_id>/remove', remove_compensation_view, name='remove-compensation'),
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ from intervention.models import Intervention, Revocation, InterventionDocument,
|
|||||||
from intervention.tables import InterventionTable
|
from 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
|
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
|
||||||
@@ -475,6 +476,29 @@ def create_share_view(request: HttpRequest, id: str):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
@default_group_required
|
||||||
|
@shared_access_required(Intervention, "id")
|
||||||
|
def create_resubmission_view(request: HttpRequest, id: str):
|
||||||
|
""" Renders resubmission form for an intervention
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request (HttpRequest): The incoming request
|
||||||
|
id (str): Intervention's id
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
intervention = get_object_or_404(Intervention, id=id)
|
||||||
|
form = ResubmissionModalForm(request.POST or None, instance=intervention, request=request)
|
||||||
|
form.action_url = reverse("intervention:resubmission-create", args=(id,))
|
||||||
|
return form.process_request(
|
||||||
|
request,
|
||||||
|
msg_success=_("Resubmission set"),
|
||||||
|
redirect_url=reverse("intervention:detail", args=(id,))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@registration_office_group_required
|
@registration_office_group_required
|
||||||
@shared_access_required(Intervention, "id")
|
@shared_access_required(Intervention, "id")
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ Created on: 22.07.21
|
|||||||
"""
|
"""
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
|
||||||
from konova.models import Geometry, Deadline, GeometryConflict, Parcel, District, Municipal, ParcelGroup
|
from konova.models import Geometry, Deadline, GeometryConflict, Parcel, District, Municipal, ParcelGroup, Resubmission
|
||||||
from konova.sub_settings.lanis_settings import DEFAULT_SRID_RLP
|
from konova.sub_settings.lanis_settings import DEFAULT_SRID_RLP
|
||||||
from konova.utils.message_templates import COMPENSATION_REMOVED_TEMPLATE
|
from konova.utils.message_templates import COMPENSATION_REMOVED_TEMPLATE
|
||||||
from user.models import UserAction
|
from user.models import UserAction
|
||||||
@@ -139,6 +139,16 @@ class BaseObjectAdmin(BaseResourceAdmin, DeletableObjectMixinAdmin):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class ResubmissionAdmin(BaseResourceAdmin):
|
||||||
|
list_display = [
|
||||||
|
"resubmit_on"
|
||||||
|
]
|
||||||
|
fields = [
|
||||||
|
"comment",
|
||||||
|
"resubmit_on",
|
||||||
|
"resubmission_sent",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
# Outcommented for a cleaner admin backend on production
|
# Outcommented for a cleaner admin backend on production
|
||||||
#admin.site.register(Geometry, GeometryAdmin)
|
#admin.site.register(Geometry, GeometryAdmin)
|
||||||
@@ -148,3 +158,4 @@ class BaseObjectAdmin(BaseResourceAdmin, DeletableObjectMixinAdmin):
|
|||||||
#admin.site.register(ParcelGroup, ParcelGroupAdmin)
|
#admin.site.register(ParcelGroup, ParcelGroupAdmin)
|
||||||
#admin.site.register(GeometryConflict, GeometryConflictAdmin)
|
#admin.site.register(GeometryConflict, GeometryConflictAdmin)
|
||||||
#admin.site.register(Deadline, DeadlineAdmin)
|
#admin.site.register(Deadline, DeadlineAdmin)
|
||||||
|
#admin.site.register(Resubmission, ResubmissionAdmin)
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ Created on: 16.11.20
|
|||||||
"""
|
"""
|
||||||
from django.http import HttpRequest
|
from django.http import HttpRequest
|
||||||
|
|
||||||
from konova.sub_settings.context_settings import BASE_TITLE, HELP_LINK, BASE_FRONTEND_TITLE, TAB_TITLE_IDENTIFIER
|
from konova.sub_settings.context_settings import BASE_TITLE, HELP_LINK, BASE_FRONTEND_TITLE, TAB_TITLE_IDENTIFIER, \
|
||||||
|
IMPRESSUM_LINK
|
||||||
from konova.sub_settings.django_settings import EMAIL_REPLY_TO
|
from konova.sub_settings.django_settings import EMAIL_REPLY_TO
|
||||||
|
|
||||||
|
|
||||||
@@ -25,6 +26,7 @@ class BaseContext:
|
|||||||
"user": request.user,
|
"user": request.user,
|
||||||
"current_role": None,
|
"current_role": None,
|
||||||
"help_link": HELP_LINK,
|
"help_link": HELP_LINK,
|
||||||
|
"impressum_link": IMPRESSUM_LINK,
|
||||||
"CONTACT_MAIL": EMAIL_REPLY_TO,
|
"CONTACT_MAIL": EMAIL_REPLY_TO,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
687
konova/forms.py
687
konova/forms.py
@@ -1,687 +0,0 @@
|
|||||||
"""
|
|
||||||
Author: Michel Peltriaux
|
|
||||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
|
||||||
Contact: michel.peltriaux@sgdnord.rlp.de
|
|
||||||
Created on: 16.11.20
|
|
||||||
|
|
||||||
"""
|
|
||||||
import json
|
|
||||||
from abc import abstractmethod
|
|
||||||
|
|
||||||
from bootstrap_modal_forms.forms import BSModalForm
|
|
||||||
from bootstrap_modal_forms.utils import is_ajax
|
|
||||||
from django import forms
|
|
||||||
from django.contrib import messages
|
|
||||||
from django.contrib.gis import gdal
|
|
||||||
from django.db.models.fields.files import FieldFile
|
|
||||||
|
|
||||||
from konova.sub_settings.lanis_settings import DEFAULT_SRID_RLP
|
|
||||||
from user.models import User
|
|
||||||
from django.contrib.gis.forms import MultiPolygonField
|
|
||||||
from django.contrib.gis.geos import MultiPolygon, Polygon
|
|
||||||
from django.db import transaction
|
|
||||||
from django.http import HttpRequest, HttpResponseRedirect
|
|
||||||
from django.shortcuts import render
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
|
||||||
|
|
||||||
from konova.contexts import BaseContext
|
|
||||||
from konova.models import BaseObject, Geometry, RecordableObjectMixin, AbstractDocument
|
|
||||||
from konova.settings import DEFAULT_SRID
|
|
||||||
from konova.tasks import celery_update_parcels
|
|
||||||
from konova.utils.message_templates import FORM_INVALID, FILE_TYPE_UNSUPPORTED, FILE_SIZE_TOO_LARGE, DOCUMENT_EDITED
|
|
||||||
from user.models import UserActionLogEntry
|
|
||||||
|
|
||||||
|
|
||||||
class BaseForm(forms.Form):
|
|
||||||
"""
|
|
||||||
Basic form for that holds attributes needed in all other forms
|
|
||||||
"""
|
|
||||||
template = None
|
|
||||||
action_url = None
|
|
||||||
action_btn_label = _("Save")
|
|
||||||
form_title = None
|
|
||||||
cancel_redirect = None
|
|
||||||
form_caption = None
|
|
||||||
instance = None # The data holding model object
|
|
||||||
request = None
|
|
||||||
form_attrs = {} # Holds additional attributes, that can be used in the template
|
|
||||||
has_required_fields = False # Automatically set. Triggers hint rendering in templates
|
|
||||||
show_cancel_btn = True
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
self.instance = kwargs.pop("instance", None)
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
if self.request is not None:
|
|
||||||
self.user = self.request.user
|
|
||||||
# Check for required fields
|
|
||||||
for _field_name, _field_val in self.fields.items():
|
|
||||||
if _field_val.required:
|
|
||||||
self.has_required_fields = True
|
|
||||||
break
|
|
||||||
|
|
||||||
self.check_for_recorded_instance()
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def save(self):
|
|
||||||
# To be implemented in subclasses!
|
|
||||||
pass
|
|
||||||
|
|
||||||
def disable_form_field(self, field: str):
|
|
||||||
"""
|
|
||||||
Disables a form field for user editing
|
|
||||||
"""
|
|
||||||
self.fields[field].widget.attrs["readonly"] = True
|
|
||||||
self.fields[field].disabled = True
|
|
||||||
self.fields[field].widget.attrs["title"] = _("Not editable")
|
|
||||||
|
|
||||||
def initialize_form_field(self, field: str, val):
|
|
||||||
"""
|
|
||||||
Initializes a form field with a value
|
|
||||||
"""
|
|
||||||
self.fields[field].initial = val
|
|
||||||
|
|
||||||
def add_placeholder_for_field(self, field: str, val):
|
|
||||||
"""
|
|
||||||
Adds a placeholder to a field after initialization without the need to redefine the form widget
|
|
||||||
|
|
||||||
Args:
|
|
||||||
field (str): Field name
|
|
||||||
val (str): Placeholder
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
|
|
||||||
"""
|
|
||||||
self.fields[field].widget.attrs["placeholder"] = val
|
|
||||||
|
|
||||||
def load_initial_data(self, form_data: dict, disabled_fields: list = None):
|
|
||||||
""" Initializes form data from instance
|
|
||||||
|
|
||||||
Inserts instance data into form and disables form fields
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
|
|
||||||
"""
|
|
||||||
if self.instance is None:
|
|
||||||
return
|
|
||||||
for k, v in form_data.items():
|
|
||||||
self.initialize_form_field(k, v)
|
|
||||||
if disabled_fields:
|
|
||||||
for field in disabled_fields:
|
|
||||||
self.disable_form_field(field)
|
|
||||||
|
|
||||||
def add_widget_html_class(self, field: str, cls: str):
|
|
||||||
""" Adds a HTML class string to the widget of a field
|
|
||||||
|
|
||||||
Args:
|
|
||||||
field (str): The field's name
|
|
||||||
cls (str): The new class string
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
|
|
||||||
"""
|
|
||||||
set_class = self.fields[field].widget.attrs.get("class", "")
|
|
||||||
if cls in set_class:
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
set_class += " " + cls
|
|
||||||
self.fields[field].widget.attrs["class"] = set_class
|
|
||||||
|
|
||||||
def remove_widget_html_class(self, field: str, cls: str):
|
|
||||||
""" Removes a HTML class string from the widget of a field
|
|
||||||
|
|
||||||
Args:
|
|
||||||
field (str): The field's name
|
|
||||||
cls (str): The new class string
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
|
|
||||||
"""
|
|
||||||
set_class = self.fields[field].widget.attrs.get("class", "")
|
|
||||||
set_class = set_class.replace(cls, "")
|
|
||||||
self.fields[field].widget.attrs["class"] = set_class
|
|
||||||
|
|
||||||
def check_for_recorded_instance(self):
|
|
||||||
""" Checks if the instance is recorded and runs some special logic if yes
|
|
||||||
|
|
||||||
If the instance is recorded, the form shall not display any possibility to
|
|
||||||
edit any data. Instead, the users should get some information about why they can not edit anything.
|
|
||||||
|
|
||||||
There are situations where the form should be rendered regularly,
|
|
||||||
e.g deduction forms for (recorded) eco accounts.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
|
|
||||||
"""
|
|
||||||
from intervention.forms.modalForms import NewDeductionModalForm, EditEcoAccountDeductionModalForm, \
|
|
||||||
RemoveEcoAccountDeductionModalForm
|
|
||||||
is_none = self.instance is None
|
|
||||||
is_other_data_type = not isinstance(self.instance, BaseObject)
|
|
||||||
is_deduction_form = isinstance(
|
|
||||||
self,
|
|
||||||
(
|
|
||||||
NewDeductionModalForm,
|
|
||||||
EditEcoAccountDeductionModalForm,
|
|
||||||
RemoveEcoAccountDeductionModalForm,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if is_none or is_other_data_type or is_deduction_form:
|
|
||||||
# Do nothing
|
|
||||||
return
|
|
||||||
|
|
||||||
if self.instance.is_recorded:
|
|
||||||
self.template = "form/recorded_no_edit.html"
|
|
||||||
|
|
||||||
|
|
||||||
class RemoveForm(BaseForm):
|
|
||||||
check = forms.BooleanField(
|
|
||||||
label=_("Confirm"),
|
|
||||||
label_suffix=_(""),
|
|
||||||
required=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
self.object_to_remove = kwargs.pop("object_to_remove", None)
|
|
||||||
self.remove_post_url = kwargs.pop("remove_post_url", "")
|
|
||||||
self.cancel_url = kwargs.pop("cancel_url", "")
|
|
||||||
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
self.form_title = _("Remove")
|
|
||||||
if self.object_to_remove is not None:
|
|
||||||
self.form_caption = _("You are about to remove {} {}").format(self.object_to_remove.__class__.__name__, self.object_to_remove)
|
|
||||||
self.action_url = self.remove_post_url
|
|
||||||
self.cancel_redirect = self.cancel_url
|
|
||||||
|
|
||||||
def is_checked(self) -> bool:
|
|
||||||
return self.cleaned_data.get("check", False)
|
|
||||||
|
|
||||||
def save(self, user: User):
|
|
||||||
""" Perform generic removing by running the form typical 'save()' method
|
|
||||||
|
|
||||||
Args:
|
|
||||||
user (User): The performing user
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
|
|
||||||
"""
|
|
||||||
if self.object_to_remove is not None and self.is_checked():
|
|
||||||
with transaction.atomic():
|
|
||||||
self.object_to_remove.is_active = False
|
|
||||||
action = UserActionLogEntry.get_deleted_action(user)
|
|
||||||
self.object_to_remove.deleted = action
|
|
||||||
self.object_to_remove.save()
|
|
||||||
return self.object_to_remove
|
|
||||||
|
|
||||||
|
|
||||||
class BaseModalForm(BaseForm, BSModalForm):
|
|
||||||
""" A specialzed form class for modal form handling
|
|
||||||
|
|
||||||
"""
|
|
||||||
is_modal_form = True
|
|
||||||
render_submit = True
|
|
||||||
template = "modal/modal_form.html"
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
self.action_btn_label = _("Continue")
|
|
||||||
|
|
||||||
def process_request(self, request: HttpRequest, msg_success: str = _("Object removed"), msg_error: str = FORM_INVALID, redirect_url: str = None):
|
|
||||||
""" Generic processing of request
|
|
||||||
|
|
||||||
Wraps the request processing logic, so we don't need the same code everywhere a RemoveModalForm is being used
|
|
||||||
|
|
||||||
Args:
|
|
||||||
request (HttpRequest): The incoming request
|
|
||||||
msg_success (str): The message in case of successful removing
|
|
||||||
msg_error (str): The message in case of an error
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
|
|
||||||
"""
|
|
||||||
redirect_url = redirect_url if redirect_url is not None else request.META.get("HTTP_REFERER", "home")
|
|
||||||
template = self.template
|
|
||||||
if request.method == "POST":
|
|
||||||
if self.is_valid():
|
|
||||||
if not is_ajax(request.META):
|
|
||||||
# Modal forms send one POST for checking on data validity. This can be used to return possible errors
|
|
||||||
# on the form. A second POST (if no errors occured) is sent afterwards and needs to process the
|
|
||||||
# saving/commiting of the data to the database. is_ajax() performs this check. The first request is
|
|
||||||
# an ajax call, the second is a regular form POST.
|
|
||||||
self.save()
|
|
||||||
messages.success(
|
|
||||||
request,
|
|
||||||
msg_success
|
|
||||||
)
|
|
||||||
return HttpResponseRedirect(redirect_url)
|
|
||||||
else:
|
|
||||||
context = {
|
|
||||||
"form": self,
|
|
||||||
}
|
|
||||||
context = BaseContext(request, context).context
|
|
||||||
return render(request, template, context)
|
|
||||||
elif request.method == "GET":
|
|
||||||
context = {
|
|
||||||
"form": self,
|
|
||||||
}
|
|
||||||
context = BaseContext(request, context).context
|
|
||||||
return render(request, template, context)
|
|
||||||
else:
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
|
|
||||||
class SimpleGeomForm(BaseForm):
|
|
||||||
""" A geometry form for rendering geometry read-only using a widget
|
|
||||||
|
|
||||||
"""
|
|
||||||
read_only = True
|
|
||||||
geom = MultiPolygonField(
|
|
||||||
srid=DEFAULT_SRID_RLP,
|
|
||||||
label=_("Geometry"),
|
|
||||||
help_text=_(""),
|
|
||||||
label_suffix="",
|
|
||||||
required=False,
|
|
||||||
disabled=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
self.read_only = kwargs.pop("read_only", True)
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
# Initialize geometry
|
|
||||||
try:
|
|
||||||
geom = self.instance.geometry.geom
|
|
||||||
self.empty = geom.empty
|
|
||||||
|
|
||||||
if self.empty:
|
|
||||||
raise AttributeError
|
|
||||||
|
|
||||||
geojson = self.instance.geometry.as_feature_collection(srid=DEFAULT_SRID_RLP)
|
|
||||||
geom = json.dumps(geojson)
|
|
||||||
except AttributeError:
|
|
||||||
# If no geometry exists for this form, we simply set the value to None and zoom to the maximum level
|
|
||||||
geom = ""
|
|
||||||
self.empty = True
|
|
||||||
|
|
||||||
self.initialize_form_field("geom", geom)
|
|
||||||
|
|
||||||
def is_valid(self):
|
|
||||||
super().is_valid()
|
|
||||||
is_valid = True
|
|
||||||
|
|
||||||
# Get geojson from form
|
|
||||||
geom = self.data["geom"]
|
|
||||||
if geom is None or len(geom) == 0:
|
|
||||||
# empty geometry is a valid geometry
|
|
||||||
return is_valid
|
|
||||||
geom = json.loads(geom)
|
|
||||||
|
|
||||||
# Write submitted data back into form field to make sure invalid geometry
|
|
||||||
# will be rendered again on failed submit
|
|
||||||
self.initialize_form_field("geom", self.data["geom"])
|
|
||||||
|
|
||||||
# Read geojson into gdal geometry
|
|
||||||
# HINT: This can be simplified if the geojson format holds data in epsg:4326 (GDAL provides direct creation for
|
|
||||||
# this case)
|
|
||||||
features = []
|
|
||||||
features_json = geom.get("features", [])
|
|
||||||
for feature in features_json:
|
|
||||||
g = gdal.OGRGeometry(json.dumps(feature.get("geometry", feature)), srs=DEFAULT_SRID_RLP)
|
|
||||||
if g.geom_type not in ["Polygon", "MultiPolygon"]:
|
|
||||||
self.add_error("geom", _("Only surfaces allowed. Points or lines must be buffered."))
|
|
||||||
is_valid = False
|
|
||||||
return is_valid
|
|
||||||
|
|
||||||
polygon = Polygon.from_ewkt(g.ewkt)
|
|
||||||
is_valid = polygon.valid
|
|
||||||
if not is_valid:
|
|
||||||
self.add_error("geom", polygon.valid_reason)
|
|
||||||
return is_valid
|
|
||||||
|
|
||||||
features.append(polygon)
|
|
||||||
form_geom = MultiPolygon(srid=DEFAULT_SRID_RLP)
|
|
||||||
for feature in features:
|
|
||||||
form_geom = form_geom.union(feature)
|
|
||||||
|
|
||||||
# Make sure to convert into a MultiPolygon. Relevant if a single Polygon is provided.
|
|
||||||
if form_geom.geom_type != "MultiPolygon":
|
|
||||||
form_geom = MultiPolygon(form_geom, srid=DEFAULT_SRID_RLP)
|
|
||||||
|
|
||||||
# Write unioned Multipolygon into cleaned data
|
|
||||||
if self.cleaned_data is None:
|
|
||||||
self.cleaned_data = {}
|
|
||||||
self.cleaned_data["geom"] = form_geom.ewkt
|
|
||||||
|
|
||||||
return is_valid
|
|
||||||
|
|
||||||
def save(self, action: UserActionLogEntry):
|
|
||||||
""" Saves the form's geometry
|
|
||||||
|
|
||||||
Creates a new geometry entry if none is set, yet
|
|
||||||
|
|
||||||
Args:
|
|
||||||
action ():
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
if self.instance is None or self.instance.geometry is None:
|
|
||||||
raise LookupError
|
|
||||||
geometry = self.instance.geometry
|
|
||||||
geometry.geom = self.cleaned_data.get("geom", MultiPolygon(srid=DEFAULT_SRID))
|
|
||||||
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)),
|
|
||||||
created=action,
|
|
||||||
)
|
|
||||||
# Start the parcel update procedure in a background process
|
|
||||||
celery_update_parcels.delay(geometry.id)
|
|
||||||
return geometry
|
|
||||||
|
|
||||||
|
|
||||||
class RemoveModalForm(BaseModalForm):
|
|
||||||
""" Generic removing modal form
|
|
||||||
|
|
||||||
Can be used for anything, where removing shall be confirmed by the user a second time.
|
|
||||||
|
|
||||||
"""
|
|
||||||
confirm = forms.BooleanField(
|
|
||||||
label=_("Confirm"),
|
|
||||||
label_suffix=_(""),
|
|
||||||
widget=forms.CheckboxInput(),
|
|
||||||
required=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
self.template = "modal/modal_form.html"
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
self.form_title = _("Remove")
|
|
||||||
self.form_caption = _("Are you sure?")
|
|
||||||
# Disable automatic w-100 setting for this type of modal form. Looks kinda strange
|
|
||||||
self.fields["confirm"].widget.attrs["class"] = ""
|
|
||||||
|
|
||||||
def save(self):
|
|
||||||
if isinstance(self.instance, BaseObject):
|
|
||||||
self.instance.mark_as_deleted(self.user)
|
|
||||||
else:
|
|
||||||
# If the class does not provide restorable delete functionality, we must delete the entry finally
|
|
||||||
self.instance.delete()
|
|
||||||
|
|
||||||
|
|
||||||
class RemoveDeadlineModalForm(RemoveModalForm):
|
|
||||||
""" Removing modal form for deadlines
|
|
||||||
|
|
||||||
Can be used for anything, where removing shall be confirmed by the user a second time.
|
|
||||||
|
|
||||||
"""
|
|
||||||
deadline = None
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
deadline = kwargs.pop("deadline", None)
|
|
||||||
self.deadline = deadline
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
def save(self):
|
|
||||||
self.instance.remove_deadline(self)
|
|
||||||
|
|
||||||
|
|
||||||
class NewDocumentModalForm(BaseModalForm):
|
|
||||||
""" Modal form for new documents
|
|
||||||
|
|
||||||
"""
|
|
||||||
title = forms.CharField(
|
|
||||||
label=_("Title"),
|
|
||||||
label_suffix=_(""),
|
|
||||||
max_length=500,
|
|
||||||
widget=forms.TextInput(
|
|
||||||
attrs={
|
|
||||||
"class": "form-control",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
creation_date = forms.DateField(
|
|
||||||
label=_("Created on"),
|
|
||||||
label_suffix=_(""),
|
|
||||||
help_text=_("When has this file been created? Important for photos."),
|
|
||||||
widget=forms.DateInput(
|
|
||||||
attrs={
|
|
||||||
"type": "date",
|
|
||||||
"data-provide": "datepicker",
|
|
||||||
"class": "form-control",
|
|
||||||
},
|
|
||||||
format="%d.%m.%Y"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
file = forms.FileField(
|
|
||||||
label=_("File"),
|
|
||||||
label_suffix=_(""),
|
|
||||||
help_text=_("Allowed formats: pdf, jpg, png. Max size 15 MB."),
|
|
||||||
widget=forms.FileInput(
|
|
||||||
attrs={
|
|
||||||
"class": "form-control-file",
|
|
||||||
}
|
|
||||||
),
|
|
||||||
)
|
|
||||||
comment = forms.CharField(
|
|
||||||
required=False,
|
|
||||||
max_length=200,
|
|
||||||
label=_("Comment"),
|
|
||||||
label_suffix=_(""),
|
|
||||||
help_text=_("Additional comment, maximum {} letters").format(200),
|
|
||||||
widget=forms.Textarea(
|
|
||||||
attrs={
|
|
||||||
"cols": 30,
|
|
||||||
"rows": 5,
|
|
||||||
"class": "form-control",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
document_model = None
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
abstract = True
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
self.form_title = _("Add new document")
|
|
||||||
self.form_caption = _("")
|
|
||||||
self.form_attrs = {
|
|
||||||
"enctype": "multipart/form-data", # important for file upload
|
|
||||||
}
|
|
||||||
if not self.document_model:
|
|
||||||
raise NotImplementedError("Unsupported document type for {}".format(self.instance.__class__))
|
|
||||||
|
|
||||||
def is_valid(self):
|
|
||||||
super_valid = super().is_valid()
|
|
||||||
|
|
||||||
_file = self.cleaned_data.get("file", None)
|
|
||||||
|
|
||||||
if _file is None or isinstance(_file, FieldFile):
|
|
||||||
# FieldFile declares that no new file has been uploaded and we do not need to check on the file again
|
|
||||||
return super_valid
|
|
||||||
|
|
||||||
mime_type_valid = self.document_model.is_mime_type_valid(_file)
|
|
||||||
if not mime_type_valid:
|
|
||||||
self.add_error(
|
|
||||||
"file",
|
|
||||||
FILE_TYPE_UNSUPPORTED
|
|
||||||
)
|
|
||||||
|
|
||||||
file_size_valid = self.document_model.is_file_size_valid(_file)
|
|
||||||
if not file_size_valid:
|
|
||||||
self.add_error(
|
|
||||||
"file",
|
|
||||||
FILE_SIZE_TOO_LARGE
|
|
||||||
)
|
|
||||||
|
|
||||||
file_valid = mime_type_valid and file_size_valid
|
|
||||||
return super_valid and file_valid
|
|
||||||
|
|
||||||
def save(self):
|
|
||||||
with transaction.atomic():
|
|
||||||
action = UserActionLogEntry.get_created_action(self.user)
|
|
||||||
edited_action = UserActionLogEntry.get_edited_action(self.user, _("Added document"))
|
|
||||||
|
|
||||||
doc = self.document_model.objects.create(
|
|
||||||
created=action,
|
|
||||||
title=self.cleaned_data["title"],
|
|
||||||
comment=self.cleaned_data["comment"],
|
|
||||||
file=self.cleaned_data["file"],
|
|
||||||
date_of_creation=self.cleaned_data["creation_date"],
|
|
||||||
instance=self.instance,
|
|
||||||
)
|
|
||||||
|
|
||||||
self.instance.log.add(edited_action)
|
|
||||||
self.instance.modified = edited_action
|
|
||||||
self.instance.save()
|
|
||||||
|
|
||||||
return doc
|
|
||||||
|
|
||||||
|
|
||||||
class EditDocumentModalForm(NewDocumentModalForm):
|
|
||||||
document = None
|
|
||||||
document_model = AbstractDocument
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
self.document = kwargs.pop("document", None)
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
self.form_title = _("Edit document")
|
|
||||||
form_data = {
|
|
||||||
"title": self.document.title,
|
|
||||||
"comment": self.document.comment,
|
|
||||||
"creation_date": str(self.document.date_of_creation),
|
|
||||||
"file": self.document.file,
|
|
||||||
}
|
|
||||||
self.load_initial_data(form_data)
|
|
||||||
|
|
||||||
|
|
||||||
def save(self):
|
|
||||||
with transaction.atomic():
|
|
||||||
document = self.document
|
|
||||||
file = self.cleaned_data.get("file", None)
|
|
||||||
|
|
||||||
document.title = self.cleaned_data.get("title", None)
|
|
||||||
document.comment = self.cleaned_data.get("comment", None)
|
|
||||||
document.date_of_creation = self.cleaned_data.get("creation_date", None)
|
|
||||||
if not isinstance(file, FieldFile):
|
|
||||||
document.replace_file(file)
|
|
||||||
document.save()
|
|
||||||
|
|
||||||
self.instance.mark_as_edited(self.user, self.request, edit_comment=DOCUMENT_EDITED)
|
|
||||||
|
|
||||||
return document
|
|
||||||
|
|
||||||
|
|
||||||
class RecordModalForm(BaseModalForm):
|
|
||||||
""" Modal form for recording data
|
|
||||||
|
|
||||||
"""
|
|
||||||
confirm = forms.BooleanField(
|
|
||||||
label=_("Confirm record"),
|
|
||||||
label_suffix="",
|
|
||||||
widget=forms.CheckboxInput(),
|
|
||||||
required=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
self.form_title = _("Record data")
|
|
||||||
self.form_caption = _("I, {} {}, confirm that all necessary control steps have been performed by myself.").format(self.user.first_name, self.user.last_name)
|
|
||||||
# Disable automatic w-100 setting for this type of modal form. Looks kinda strange
|
|
||||||
self.fields["confirm"].widget.attrs["class"] = ""
|
|
||||||
|
|
||||||
if self.instance.recorded:
|
|
||||||
# unrecord!
|
|
||||||
self.fields["confirm"].label = _("Confirm unrecord")
|
|
||||||
self.form_title = _("Unrecord data")
|
|
||||||
self.form_caption = _("I, {} {}, confirm that this data must be unrecorded.").format(self.user.first_name, self.user.last_name)
|
|
||||||
|
|
||||||
if not isinstance(self.instance, RecordableObjectMixin):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def is_valid(self):
|
|
||||||
""" Checks for instance's validity and data quality
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
|
|
||||||
"""
|
|
||||||
from intervention.models import Intervention
|
|
||||||
super_val = super().is_valid()
|
|
||||||
if self.instance.recorded:
|
|
||||||
# If user wants to unrecord an already recorded dataset, we do not need to perform custom checks
|
|
||||||
return super_val
|
|
||||||
checker = self.instance.quality_check()
|
|
||||||
for msg in checker.messages:
|
|
||||||
self.add_error(
|
|
||||||
"confirm",
|
|
||||||
msg
|
|
||||||
)
|
|
||||||
valid = checker.valid
|
|
||||||
# Special case: Intervention
|
|
||||||
# Add direct checks for related compensations
|
|
||||||
if isinstance(self.instance, Intervention):
|
|
||||||
comps_valid = self._are_compensations_valid()
|
|
||||||
valid = valid and comps_valid
|
|
||||||
return super_val and valid
|
|
||||||
|
|
||||||
def _are_deductions_valid(self):
|
|
||||||
""" Performs validity checks on deductions and their eco-account
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
|
|
||||||
"""
|
|
||||||
deductions = self.instance.deductions.all()
|
|
||||||
for deduction in deductions:
|
|
||||||
checker = deduction.account.quality_check()
|
|
||||||
for msg in checker.messages:
|
|
||||||
self.add_error(
|
|
||||||
"confirm",
|
|
||||||
f"{deduction.account.identifier}: {msg}"
|
|
||||||
)
|
|
||||||
return checker.valid
|
|
||||||
return True
|
|
||||||
|
|
||||||
def _are_compensations_valid(self):
|
|
||||||
""" Runs a special case for intervention-compensations validity
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
|
|
||||||
"""
|
|
||||||
comps = self.instance.compensations.filter(
|
|
||||||
deleted=None,
|
|
||||||
)
|
|
||||||
comps_valid = True
|
|
||||||
for comp in comps:
|
|
||||||
checker = comp.quality_check()
|
|
||||||
comps_valid = comps_valid and checker.valid
|
|
||||||
for msg in checker.messages:
|
|
||||||
self.add_error(
|
|
||||||
"confirm",
|
|
||||||
f"{comp.identifier}: {msg}"
|
|
||||||
)
|
|
||||||
|
|
||||||
deductions_valid = self._are_deductions_valid()
|
|
||||||
|
|
||||||
return comps_valid and deductions_valid
|
|
||||||
|
|
||||||
def save(self):
|
|
||||||
with transaction.atomic():
|
|
||||||
if self.cleaned_data["confirm"]:
|
|
||||||
if self.instance.recorded:
|
|
||||||
self.instance.set_unrecorded(self.user)
|
|
||||||
else:
|
|
||||||
self.instance.set_recorded(self.user)
|
|
||||||
return self.instance
|
|
||||||
|
|
||||||
def check_for_recorded_instance(self):
|
|
||||||
""" Overwrite the check method for doing nothing on the RecordModalForm
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
11
konova/forms/__init__.py
Normal file
11
konova/forms/__init__.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
"""
|
||||||
|
Author: Michel Peltriaux
|
||||||
|
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||||
|
Contact: ksp-servicestelle@sgdnord.rlp.de
|
||||||
|
Created on: 15.08.22
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from .base_form import *
|
||||||
|
from .geometry_form import *
|
||||||
|
from .remove_form import *
|
||||||
157
konova/forms/base_form.py
Normal file
157
konova/forms/base_form.py
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
"""
|
||||||
|
Author: Michel Peltriaux
|
||||||
|
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||||
|
Contact: ksp-servicestelle@sgdnord.rlp.de
|
||||||
|
Created on: 15.08.22
|
||||||
|
|
||||||
|
"""
|
||||||
|
from abc import abstractmethod
|
||||||
|
|
||||||
|
from django import forms
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
from compensation.models import EcoAccount
|
||||||
|
from konova.models import BaseObject
|
||||||
|
|
||||||
|
|
||||||
|
class BaseForm(forms.Form):
|
||||||
|
"""
|
||||||
|
Basic form for that holds attributes needed in all other forms
|
||||||
|
"""
|
||||||
|
template = None
|
||||||
|
action_url = None
|
||||||
|
action_btn_label = _("Save")
|
||||||
|
form_title = None
|
||||||
|
cancel_redirect = None
|
||||||
|
form_caption = None
|
||||||
|
instance = None # The data holding model object
|
||||||
|
request = None
|
||||||
|
form_attrs = {} # Holds additional attributes, that can be used in the template
|
||||||
|
has_required_fields = False # Automatically set. Triggers hint rendering in templates
|
||||||
|
show_cancel_btn = True
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
self.instance = kwargs.pop("instance", None)
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
if self.request is not None:
|
||||||
|
self.user = self.request.user
|
||||||
|
# Check for required fields
|
||||||
|
for _field_name, _field_val in self.fields.items():
|
||||||
|
if _field_val.required:
|
||||||
|
self.has_required_fields = True
|
||||||
|
break
|
||||||
|
|
||||||
|
self.check_for_recorded_instance()
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def save(self):
|
||||||
|
# To be implemented in subclasses!
|
||||||
|
pass
|
||||||
|
|
||||||
|
def disable_form_field(self, field: str):
|
||||||
|
"""
|
||||||
|
Disables a form field for user editing
|
||||||
|
"""
|
||||||
|
self.fields[field].widget.attrs["readonly"] = True
|
||||||
|
self.fields[field].disabled = True
|
||||||
|
self.fields[field].widget.attrs["title"] = _("Not editable")
|
||||||
|
|
||||||
|
def initialize_form_field(self, field: str, val):
|
||||||
|
"""
|
||||||
|
Initializes a form field with a value
|
||||||
|
"""
|
||||||
|
self.fields[field].initial = val
|
||||||
|
|
||||||
|
def add_placeholder_for_field(self, field: str, val):
|
||||||
|
"""
|
||||||
|
Adds a placeholder to a field after initialization without the need to redefine the form widget
|
||||||
|
|
||||||
|
Args:
|
||||||
|
field (str): Field name
|
||||||
|
val (str): Placeholder
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
self.fields[field].widget.attrs["placeholder"] = val
|
||||||
|
|
||||||
|
def load_initial_data(self, form_data: dict, disabled_fields: list = None):
|
||||||
|
""" Initializes form data from instance
|
||||||
|
|
||||||
|
Inserts instance data into form and disables form fields
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
if self.instance is None:
|
||||||
|
return
|
||||||
|
for k, v in form_data.items():
|
||||||
|
self.initialize_form_field(k, v)
|
||||||
|
if disabled_fields:
|
||||||
|
for field in disabled_fields:
|
||||||
|
self.disable_form_field(field)
|
||||||
|
|
||||||
|
def add_widget_html_class(self, field: str, cls: str):
|
||||||
|
""" Adds a HTML class string to the widget of a field
|
||||||
|
|
||||||
|
Args:
|
||||||
|
field (str): The field's name
|
||||||
|
cls (str): The new class string
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
set_class = self.fields[field].widget.attrs.get("class", "")
|
||||||
|
if cls in set_class:
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
set_class += " " + cls
|
||||||
|
self.fields[field].widget.attrs["class"] = set_class
|
||||||
|
|
||||||
|
def remove_widget_html_class(self, field: str, cls: str):
|
||||||
|
""" Removes a HTML class string from the widget of a field
|
||||||
|
|
||||||
|
Args:
|
||||||
|
field (str): The field's name
|
||||||
|
cls (str): The new class string
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
set_class = self.fields[field].widget.attrs.get("class", "")
|
||||||
|
set_class = set_class.replace(cls, "")
|
||||||
|
self.fields[field].widget.attrs["class"] = set_class
|
||||||
|
|
||||||
|
def check_for_recorded_instance(self):
|
||||||
|
""" Checks if the instance is recorded and runs some special logic if yes
|
||||||
|
|
||||||
|
If the instance is recorded, the form shall not display any possibility to
|
||||||
|
edit any data. Instead, the users should get some information about why they can not edit anything.
|
||||||
|
|
||||||
|
There are situations where the form should be rendered regularly,
|
||||||
|
e.g deduction forms for (recorded) eco accounts.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
from intervention.forms.modalForms import NewDeductionModalForm, EditEcoAccountDeductionModalForm, \
|
||||||
|
RemoveEcoAccountDeductionModalForm
|
||||||
|
from konova.forms.modals.resubmission_form import ResubmissionModalForm
|
||||||
|
is_none = self.instance is None
|
||||||
|
is_other_data_type = not isinstance(self.instance, BaseObject)
|
||||||
|
is_deduction_form_from_account = isinstance(
|
||||||
|
self,
|
||||||
|
(
|
||||||
|
NewDeductionModalForm,
|
||||||
|
ResubmissionModalForm,
|
||||||
|
EditEcoAccountDeductionModalForm,
|
||||||
|
RemoveEcoAccountDeductionModalForm,
|
||||||
|
)
|
||||||
|
) and isinstance(self.instance, EcoAccount)
|
||||||
|
|
||||||
|
if is_none or is_other_data_type or is_deduction_form_from_account:
|
||||||
|
# Do nothing
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.instance.is_recorded:
|
||||||
|
self.template = "form/recorded_no_edit.html"
|
||||||
133
konova/forms/geometry_form.py
Normal file
133
konova/forms/geometry_form.py
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
"""
|
||||||
|
Author: Michel Peltriaux
|
||||||
|
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||||
|
Contact: ksp-servicestelle@sgdnord.rlp.de
|
||||||
|
Created on: 15.08.22
|
||||||
|
|
||||||
|
"""
|
||||||
|
import json
|
||||||
|
|
||||||
|
from django.contrib.gis import gdal
|
||||||
|
from django.contrib.gis.forms import MultiPolygonField
|
||||||
|
from django.contrib.gis.geos import MultiPolygon, Polygon
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
from konova.forms.base_form import BaseForm
|
||||||
|
from konova.models import Geometry
|
||||||
|
from konova.tasks import celery_update_parcels
|
||||||
|
from konova.sub_settings.lanis_settings import DEFAULT_SRID_RLP
|
||||||
|
from user.models import UserActionLogEntry
|
||||||
|
|
||||||
|
|
||||||
|
class SimpleGeomForm(BaseForm):
|
||||||
|
""" A geometry form for rendering geometry read-only using a widget
|
||||||
|
|
||||||
|
"""
|
||||||
|
read_only = True
|
||||||
|
geom = MultiPolygonField(
|
||||||
|
srid=DEFAULT_SRID_RLP,
|
||||||
|
label=_("Geometry"),
|
||||||
|
help_text=_(""),
|
||||||
|
label_suffix="",
|
||||||
|
required=False,
|
||||||
|
disabled=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
self.read_only = kwargs.pop("read_only", True)
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
# Initialize geometry
|
||||||
|
try:
|
||||||
|
geom = self.instance.geometry.geom
|
||||||
|
self.empty = geom.empty
|
||||||
|
|
||||||
|
if self.empty:
|
||||||
|
raise AttributeError
|
||||||
|
|
||||||
|
geojson = self.instance.geometry.as_feature_collection(srid=DEFAULT_SRID_RLP)
|
||||||
|
geom = json.dumps(geojson)
|
||||||
|
except AttributeError:
|
||||||
|
# If no geometry exists for this form, we simply set the value to None and zoom to the maximum level
|
||||||
|
geom = ""
|
||||||
|
self.empty = True
|
||||||
|
|
||||||
|
self.initialize_form_field("geom", geom)
|
||||||
|
|
||||||
|
def is_valid(self):
|
||||||
|
super().is_valid()
|
||||||
|
is_valid = True
|
||||||
|
|
||||||
|
# Get geojson from form
|
||||||
|
geom = self.data["geom"]
|
||||||
|
if geom is None or len(geom) == 0:
|
||||||
|
# empty geometry is a valid geometry
|
||||||
|
return is_valid
|
||||||
|
geom = json.loads(geom)
|
||||||
|
|
||||||
|
# Write submitted data back into form field to make sure invalid geometry
|
||||||
|
# will be rendered again on failed submit
|
||||||
|
self.initialize_form_field("geom", self.data["geom"])
|
||||||
|
|
||||||
|
# Read geojson into gdal geometry
|
||||||
|
# HINT: This can be simplified if the geojson format holds data in epsg:4326 (GDAL provides direct creation for
|
||||||
|
# this case)
|
||||||
|
features = []
|
||||||
|
features_json = geom.get("features", [])
|
||||||
|
for feature in features_json:
|
||||||
|
g = gdal.OGRGeometry(json.dumps(feature.get("geometry", feature)), srs=DEFAULT_SRID_RLP)
|
||||||
|
if g.geom_type not in ["Polygon", "MultiPolygon"]:
|
||||||
|
self.add_error("geom", _("Only surfaces allowed. Points or lines must be buffered."))
|
||||||
|
is_valid = False
|
||||||
|
return is_valid
|
||||||
|
|
||||||
|
polygon = Polygon.from_ewkt(g.ewkt)
|
||||||
|
is_valid = polygon.valid
|
||||||
|
if not is_valid:
|
||||||
|
self.add_error("geom", polygon.valid_reason)
|
||||||
|
return is_valid
|
||||||
|
|
||||||
|
features.append(polygon)
|
||||||
|
form_geom = MultiPolygon(srid=DEFAULT_SRID_RLP)
|
||||||
|
for feature in features:
|
||||||
|
form_geom = form_geom.union(feature)
|
||||||
|
|
||||||
|
# Make sure to convert into a MultiPolygon. Relevant if a single Polygon is provided.
|
||||||
|
if form_geom.geom_type != "MultiPolygon":
|
||||||
|
form_geom = MultiPolygon(form_geom, srid=DEFAULT_SRID_RLP)
|
||||||
|
|
||||||
|
# Write unioned Multipolygon into cleaned data
|
||||||
|
if self.cleaned_data is None:
|
||||||
|
self.cleaned_data = {}
|
||||||
|
self.cleaned_data["geom"] = form_geom.ewkt
|
||||||
|
|
||||||
|
return is_valid
|
||||||
|
|
||||||
|
def save(self, action: UserActionLogEntry):
|
||||||
|
""" Saves the form's geometry
|
||||||
|
|
||||||
|
Creates a new geometry entry if none is set, yet
|
||||||
|
|
||||||
|
Args:
|
||||||
|
action ():
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if self.instance is None or self.instance.geometry is None:
|
||||||
|
raise LookupError
|
||||||
|
geometry = self.instance.geometry
|
||||||
|
geometry.geom = self.cleaned_data.get("geom", MultiPolygon(srid=DEFAULT_SRID_RLP))
|
||||||
|
geometry.modified = action
|
||||||
|
|
||||||
|
geometry.save()
|
||||||
|
except LookupError:
|
||||||
|
# No geometry or linked instance holding a geometry exist --> create a new one!
|
||||||
|
geometry = Geometry.objects.create(
|
||||||
|
geom=self.cleaned_data.get("geom", MultiPolygon(srid=DEFAULT_SRID_RLP)),
|
||||||
|
created=action,
|
||||||
|
)
|
||||||
|
# Start the parcel update procedure in a background process
|
||||||
|
celery_update_parcels.delay(geometry.id)
|
||||||
|
return geometry
|
||||||
12
konova/forms/modals/__init__.py
Normal file
12
konova/forms/modals/__init__.py
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
"""
|
||||||
|
Author: Michel Peltriaux
|
||||||
|
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||||
|
Contact: ksp-servicestelle@sgdnord.rlp.de
|
||||||
|
Created on: 15.08.22
|
||||||
|
|
||||||
|
"""
|
||||||
|
from .base_form import *
|
||||||
|
from .document_form import *
|
||||||
|
from .record_form import *
|
||||||
|
from .remove_form import *
|
||||||
|
from .resubmission_form import *
|
||||||
73
konova/forms/modals/base_form.py
Normal file
73
konova/forms/modals/base_form.py
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
"""
|
||||||
|
Author: Michel Peltriaux
|
||||||
|
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||||
|
Contact: ksp-servicestelle@sgdnord.rlp.de
|
||||||
|
Created on: 15.08.22
|
||||||
|
|
||||||
|
"""
|
||||||
|
from bootstrap_modal_forms.forms import BSModalForm
|
||||||
|
from bootstrap_modal_forms.utils import is_ajax
|
||||||
|
from django.contrib import messages
|
||||||
|
from django.http import HttpResponseRedirect, HttpRequest
|
||||||
|
from django.shortcuts import render
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
from konova.contexts import BaseContext
|
||||||
|
from konova.forms.base_form import BaseForm
|
||||||
|
from konova.utils.message_templates import FORM_INVALID
|
||||||
|
|
||||||
|
|
||||||
|
class BaseModalForm(BaseForm, BSModalForm):
|
||||||
|
""" A specialzed form class for modal form handling
|
||||||
|
|
||||||
|
"""
|
||||||
|
is_modal_form = True
|
||||||
|
render_submit = True
|
||||||
|
template = "modal/modal_form.html"
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.action_btn_label = _("Continue")
|
||||||
|
|
||||||
|
def process_request(self, request: HttpRequest, msg_success: str = _("Object removed"), msg_error: str = FORM_INVALID, redirect_url: str = None):
|
||||||
|
""" Generic processing of request
|
||||||
|
|
||||||
|
Wraps the request processing logic, so we don't need the same code everywhere a RemoveModalForm is being used
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request (HttpRequest): The incoming request
|
||||||
|
msg_success (str): The message in case of successful removing
|
||||||
|
msg_error (str): The message in case of an error
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
redirect_url = redirect_url if redirect_url is not None else request.META.get("HTTP_REFERER", "home")
|
||||||
|
template = self.template
|
||||||
|
if request.method == "POST":
|
||||||
|
if self.is_valid():
|
||||||
|
if not is_ajax(request.META):
|
||||||
|
# Modal forms send one POST for checking on data validity. This can be used to return possible errors
|
||||||
|
# on the form. A second POST (if no errors occured) is sent afterwards and needs to process the
|
||||||
|
# saving/commiting of the data to the database. is_ajax() performs this check. The first request is
|
||||||
|
# an ajax call, the second is a regular form POST.
|
||||||
|
self.save()
|
||||||
|
messages.success(
|
||||||
|
request,
|
||||||
|
msg_success
|
||||||
|
)
|
||||||
|
return HttpResponseRedirect(redirect_url)
|
||||||
|
else:
|
||||||
|
context = {
|
||||||
|
"form": self,
|
||||||
|
}
|
||||||
|
context = BaseContext(request, context).context
|
||||||
|
return render(request, template, context)
|
||||||
|
elif request.method == "GET":
|
||||||
|
context = {
|
||||||
|
"form": self,
|
||||||
|
}
|
||||||
|
context = BaseContext(request, context).context
|
||||||
|
return render(request, template, context)
|
||||||
|
else:
|
||||||
|
raise NotImplementedError
|
||||||
163
konova/forms/modals/document_form.py
Normal file
163
konova/forms/modals/document_form.py
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
"""
|
||||||
|
Author: Michel Peltriaux
|
||||||
|
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||||
|
Contact: ksp-servicestelle@sgdnord.rlp.de
|
||||||
|
Created on: 15.08.22
|
||||||
|
|
||||||
|
"""
|
||||||
|
from django import forms
|
||||||
|
from django.db import transaction
|
||||||
|
from django.db.models.fields.files import FieldFile
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
from konova.forms.modals.base_form import BaseModalForm
|
||||||
|
from konova.models import AbstractDocument
|
||||||
|
from konova.utils.message_templates import DOCUMENT_EDITED, FILE_SIZE_TOO_LARGE, FILE_TYPE_UNSUPPORTED
|
||||||
|
from user.models import UserActionLogEntry
|
||||||
|
|
||||||
|
|
||||||
|
class NewDocumentModalForm(BaseModalForm):
|
||||||
|
""" Modal form for new documents
|
||||||
|
|
||||||
|
"""
|
||||||
|
title = forms.CharField(
|
||||||
|
label=_("Title"),
|
||||||
|
label_suffix=_(""),
|
||||||
|
max_length=500,
|
||||||
|
widget=forms.TextInput(
|
||||||
|
attrs={
|
||||||
|
"class": "form-control",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
creation_date = forms.DateField(
|
||||||
|
label=_("Created on"),
|
||||||
|
label_suffix=_(""),
|
||||||
|
help_text=_("When has this file been created? Important for photos."),
|
||||||
|
widget=forms.DateInput(
|
||||||
|
attrs={
|
||||||
|
"type": "date",
|
||||||
|
"data-provide": "datepicker",
|
||||||
|
"class": "form-control",
|
||||||
|
},
|
||||||
|
format="%d.%m.%Y"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
file = forms.FileField(
|
||||||
|
label=_("File"),
|
||||||
|
label_suffix=_(""),
|
||||||
|
help_text=_("Allowed formats: pdf, jpg, png. Max size 15 MB."),
|
||||||
|
widget=forms.FileInput(
|
||||||
|
attrs={
|
||||||
|
"class": "form-control-file",
|
||||||
|
}
|
||||||
|
),
|
||||||
|
)
|
||||||
|
comment = forms.CharField(
|
||||||
|
required=False,
|
||||||
|
max_length=200,
|
||||||
|
label=_("Comment"),
|
||||||
|
label_suffix=_(""),
|
||||||
|
help_text=_("Additional comment, maximum {} letters").format(200),
|
||||||
|
widget=forms.Textarea(
|
||||||
|
attrs={
|
||||||
|
"cols": 30,
|
||||||
|
"rows": 5,
|
||||||
|
"class": "form-control",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
document_model = None
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
abstract = True
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.form_title = _("Add new document")
|
||||||
|
self.form_caption = _("")
|
||||||
|
self.form_attrs = {
|
||||||
|
"enctype": "multipart/form-data", # important for file upload
|
||||||
|
}
|
||||||
|
if not self.document_model:
|
||||||
|
raise NotImplementedError("Unsupported document type for {}".format(self.instance.__class__))
|
||||||
|
|
||||||
|
def is_valid(self):
|
||||||
|
super_valid = super().is_valid()
|
||||||
|
|
||||||
|
_file = self.cleaned_data.get("file", None)
|
||||||
|
|
||||||
|
if _file is None or isinstance(_file, FieldFile):
|
||||||
|
# FieldFile declares that no new file has been uploaded and we do not need to check on the file again
|
||||||
|
return super_valid
|
||||||
|
|
||||||
|
mime_type_valid = self.document_model.is_mime_type_valid(_file)
|
||||||
|
if not mime_type_valid:
|
||||||
|
self.add_error(
|
||||||
|
"file",
|
||||||
|
FILE_TYPE_UNSUPPORTED
|
||||||
|
)
|
||||||
|
|
||||||
|
file_size_valid = self.document_model.is_file_size_valid(_file)
|
||||||
|
if not file_size_valid:
|
||||||
|
self.add_error(
|
||||||
|
"file",
|
||||||
|
FILE_SIZE_TOO_LARGE
|
||||||
|
)
|
||||||
|
|
||||||
|
file_valid = mime_type_valid and file_size_valid
|
||||||
|
return super_valid and file_valid
|
||||||
|
|
||||||
|
def save(self):
|
||||||
|
with transaction.atomic():
|
||||||
|
action = UserActionLogEntry.get_created_action(self.user)
|
||||||
|
edited_action = UserActionLogEntry.get_edited_action(self.user, _("Added document"))
|
||||||
|
|
||||||
|
doc = self.document_model.objects.create(
|
||||||
|
created=action,
|
||||||
|
title=self.cleaned_data["title"],
|
||||||
|
comment=self.cleaned_data["comment"],
|
||||||
|
file=self.cleaned_data["file"],
|
||||||
|
date_of_creation=self.cleaned_data["creation_date"],
|
||||||
|
instance=self.instance,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.instance.log.add(edited_action)
|
||||||
|
self.instance.modified = edited_action
|
||||||
|
self.instance.save()
|
||||||
|
|
||||||
|
return doc
|
||||||
|
|
||||||
|
|
||||||
|
class EditDocumentModalForm(NewDocumentModalForm):
|
||||||
|
document = None
|
||||||
|
document_model = AbstractDocument
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
self.document = kwargs.pop("document", None)
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.form_title = _("Edit document")
|
||||||
|
form_data = {
|
||||||
|
"title": self.document.title,
|
||||||
|
"comment": self.document.comment,
|
||||||
|
"creation_date": str(self.document.date_of_creation),
|
||||||
|
"file": self.document.file,
|
||||||
|
}
|
||||||
|
self.load_initial_data(form_data)
|
||||||
|
|
||||||
|
def save(self):
|
||||||
|
with transaction.atomic():
|
||||||
|
document = self.document
|
||||||
|
file = self.cleaned_data.get("file", None)
|
||||||
|
|
||||||
|
document.title = self.cleaned_data.get("title", None)
|
||||||
|
document.comment = self.cleaned_data.get("comment", None)
|
||||||
|
document.date_of_creation = self.cleaned_data.get("creation_date", None)
|
||||||
|
if not isinstance(file, FieldFile):
|
||||||
|
document.replace_file(file)
|
||||||
|
document.save()
|
||||||
|
|
||||||
|
self.instance.mark_as_edited(self.user, self.request, edit_comment=DOCUMENT_EDITED)
|
||||||
|
|
||||||
|
return document
|
||||||
|
|
||||||
123
konova/forms/modals/record_form.py
Normal file
123
konova/forms/modals/record_form.py
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
"""
|
||||||
|
Author: Michel Peltriaux
|
||||||
|
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||||
|
Contact: ksp-servicestelle@sgdnord.rlp.de
|
||||||
|
Created on: 15.08.22
|
||||||
|
|
||||||
|
"""
|
||||||
|
from django import forms
|
||||||
|
from django.db import transaction
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
from konova.forms.modals.base_form import BaseModalForm
|
||||||
|
from konova.models import RecordableObjectMixin
|
||||||
|
|
||||||
|
|
||||||
|
class RecordModalForm(BaseModalForm):
|
||||||
|
""" Modal form for recording data
|
||||||
|
|
||||||
|
"""
|
||||||
|
confirm = forms.BooleanField(
|
||||||
|
label=_("Confirm record"),
|
||||||
|
label_suffix="",
|
||||||
|
widget=forms.CheckboxInput(),
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.form_title = _("Record data")
|
||||||
|
self.form_caption = _("I, {} {}, confirm that all necessary control steps have been performed by myself.").format(self.user.first_name, self.user.last_name)
|
||||||
|
# Disable automatic w-100 setting for this type of modal form. Looks kinda strange
|
||||||
|
self.fields["confirm"].widget.attrs["class"] = ""
|
||||||
|
|
||||||
|
if self.instance.recorded:
|
||||||
|
# unrecord!
|
||||||
|
self.fields["confirm"].label = _("Confirm unrecord")
|
||||||
|
self.form_title = _("Unrecord data")
|
||||||
|
self.form_caption = _("I, {} {}, confirm that this data must be unrecorded.").format(self.user.first_name, self.user.last_name)
|
||||||
|
|
||||||
|
if not isinstance(self.instance, RecordableObjectMixin):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def is_valid(self):
|
||||||
|
""" Checks for instance's validity and data quality
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
from intervention.models import Intervention
|
||||||
|
super_val = super().is_valid()
|
||||||
|
if self.instance.recorded:
|
||||||
|
# If user wants to unrecord an already recorded dataset, we do not need to perform custom checks
|
||||||
|
return super_val
|
||||||
|
checker = self.instance.quality_check()
|
||||||
|
for msg in checker.messages:
|
||||||
|
self.add_error(
|
||||||
|
"confirm",
|
||||||
|
msg
|
||||||
|
)
|
||||||
|
valid = checker.valid
|
||||||
|
# Special case: Intervention
|
||||||
|
# Add direct checks for related compensations
|
||||||
|
if isinstance(self.instance, Intervention):
|
||||||
|
comps_valid = self._are_compensations_valid()
|
||||||
|
valid = valid and comps_valid
|
||||||
|
return super_val and valid
|
||||||
|
|
||||||
|
def _are_deductions_valid(self):
|
||||||
|
""" Performs validity checks on deductions and their eco-account
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
deductions = self.instance.deductions.all()
|
||||||
|
for deduction in deductions:
|
||||||
|
checker = deduction.account.quality_check()
|
||||||
|
for msg in checker.messages:
|
||||||
|
self.add_error(
|
||||||
|
"confirm",
|
||||||
|
f"{deduction.account.identifier}: {msg}"
|
||||||
|
)
|
||||||
|
return checker.valid
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _are_compensations_valid(self):
|
||||||
|
""" Runs a special case for intervention-compensations validity
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
comps = self.instance.compensations.filter(
|
||||||
|
deleted=None,
|
||||||
|
)
|
||||||
|
comps_valid = True
|
||||||
|
for comp in comps:
|
||||||
|
checker = comp.quality_check()
|
||||||
|
comps_valid = comps_valid and checker.valid
|
||||||
|
for msg in checker.messages:
|
||||||
|
self.add_error(
|
||||||
|
"confirm",
|
||||||
|
f"{comp.identifier}: {msg}"
|
||||||
|
)
|
||||||
|
|
||||||
|
deductions_valid = self._are_deductions_valid()
|
||||||
|
|
||||||
|
return comps_valid and deductions_valid
|
||||||
|
|
||||||
|
def save(self):
|
||||||
|
with transaction.atomic():
|
||||||
|
if self.cleaned_data["confirm"]:
|
||||||
|
if self.instance.recorded:
|
||||||
|
self.instance.set_unrecorded(self.user)
|
||||||
|
else:
|
||||||
|
self.instance.set_recorded(self.user)
|
||||||
|
return self.instance
|
||||||
|
|
||||||
|
def check_for_recorded_instance(self):
|
||||||
|
""" Overwrite the check method for doing nothing on the RecordModalForm
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
pass
|
||||||
58
konova/forms/modals/remove_form.py
Normal file
58
konova/forms/modals/remove_form.py
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
"""
|
||||||
|
Author: Michel Peltriaux
|
||||||
|
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||||
|
Contact: ksp-servicestelle@sgdnord.rlp.de
|
||||||
|
Created on: 15.08.22
|
||||||
|
|
||||||
|
"""
|
||||||
|
from django import forms
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
from konova.forms.modals.base_form import BaseModalForm
|
||||||
|
from konova.models import BaseObject
|
||||||
|
|
||||||
|
|
||||||
|
class RemoveModalForm(BaseModalForm):
|
||||||
|
""" Generic removing modal form
|
||||||
|
|
||||||
|
Can be used for anything, where removing shall be confirmed by the user a second time.
|
||||||
|
|
||||||
|
"""
|
||||||
|
confirm = forms.BooleanField(
|
||||||
|
label=_("Confirm"),
|
||||||
|
label_suffix=_(""),
|
||||||
|
widget=forms.CheckboxInput(),
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
self.template = "modal/modal_form.html"
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.form_title = _("Remove")
|
||||||
|
self.form_caption = _("Are you sure?")
|
||||||
|
# Disable automatic w-100 setting for this type of modal form. Looks kinda strange
|
||||||
|
self.fields["confirm"].widget.attrs["class"] = ""
|
||||||
|
|
||||||
|
def save(self):
|
||||||
|
if isinstance(self.instance, BaseObject):
|
||||||
|
self.instance.mark_as_deleted(self.user)
|
||||||
|
else:
|
||||||
|
# If the class does not provide restorable delete functionality, we must delete the entry finally
|
||||||
|
self.instance.delete()
|
||||||
|
|
||||||
|
|
||||||
|
class RemoveDeadlineModalForm(RemoveModalForm):
|
||||||
|
""" Removing modal form for deadlines
|
||||||
|
|
||||||
|
Can be used for anything, where removing shall be confirmed by the user a second time.
|
||||||
|
|
||||||
|
"""
|
||||||
|
deadline = None
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
deadline = kwargs.pop("deadline", None)
|
||||||
|
self.deadline = deadline
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
def save(self):
|
||||||
|
self.instance.remove_deadline(self)
|
||||||
85
konova/forms/modals/resubmission_form.py
Normal file
85
konova/forms/modals/resubmission_form.py
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
"""
|
||||||
|
Author: Michel Peltriaux
|
||||||
|
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||||
|
Contact: ksp-servicestelle@sgdnord.rlp.de
|
||||||
|
Created on: 15.08.22
|
||||||
|
|
||||||
|
"""
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
from django import forms
|
||||||
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
|
from django.db import transaction
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
from konova.forms.modals.base_form import BaseModalForm
|
||||||
|
from konova.models import Resubmission
|
||||||
|
|
||||||
|
|
||||||
|
class ResubmissionModalForm(BaseModalForm):
|
||||||
|
date = forms.DateField(
|
||||||
|
label_suffix=_(""),
|
||||||
|
label=_("Date"),
|
||||||
|
help_text=_("When do you want to be reminded?"),
|
||||||
|
widget=forms.DateInput(
|
||||||
|
attrs={
|
||||||
|
"type": "date",
|
||||||
|
"data-provide": "datepicker",
|
||||||
|
"class": "form-control",
|
||||||
|
},
|
||||||
|
format="%d.%m.%Y"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
comment = forms.CharField(
|
||||||
|
required=False,
|
||||||
|
label=_("Comment"),
|
||||||
|
label_suffix=_(""),
|
||||||
|
help_text=_("Additional comment"),
|
||||||
|
widget=forms.Textarea(
|
||||||
|
attrs={
|
||||||
|
"cols": 30,
|
||||||
|
"rows": 5,
|
||||||
|
"class": "form-control",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.form_title = _("Resubmission")
|
||||||
|
self.form_caption = _("Set your resubmission for this entry.")
|
||||||
|
self.action_url = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.resubmission = self.instance.resubmissions.get(
|
||||||
|
user=self.user
|
||||||
|
)
|
||||||
|
self.initialize_form_field("date", str(self.resubmission.resubmit_on))
|
||||||
|
self.initialize_form_field("comment", self.resubmission.comment)
|
||||||
|
except ObjectDoesNotExist:
|
||||||
|
self.resubmission = Resubmission()
|
||||||
|
|
||||||
|
def is_valid(self):
|
||||||
|
super_valid = super().is_valid()
|
||||||
|
self_valid = True
|
||||||
|
|
||||||
|
date = self.cleaned_data.get("date")
|
||||||
|
today = datetime.date.today()
|
||||||
|
if date <= today:
|
||||||
|
self.add_error(
|
||||||
|
"date",
|
||||||
|
_("The date should be in the future")
|
||||||
|
)
|
||||||
|
self_valid = False
|
||||||
|
|
||||||
|
return super_valid and self_valid
|
||||||
|
|
||||||
|
def save(self):
|
||||||
|
with transaction.atomic():
|
||||||
|
self.resubmission.user = self.user
|
||||||
|
self.resubmission.resubmit_on = self.cleaned_data.get("date")
|
||||||
|
self.resubmission.comment = self.cleaned_data.get("comment")
|
||||||
|
self.resubmission.save()
|
||||||
|
self.instance.resubmissions.add(self.resubmission)
|
||||||
|
return self.resubmission
|
||||||
|
|
||||||
54
konova/forms/remove_form.py
Normal file
54
konova/forms/remove_form.py
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
"""
|
||||||
|
Author: Michel Peltriaux
|
||||||
|
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||||
|
Contact: ksp-servicestelle@sgdnord.rlp.de
|
||||||
|
Created on: 15.08.22
|
||||||
|
|
||||||
|
"""
|
||||||
|
from django import forms
|
||||||
|
from django.db import transaction
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
from konova.forms.base_form import BaseForm
|
||||||
|
from user.models import UserActionLogEntry, User
|
||||||
|
|
||||||
|
|
||||||
|
class RemoveForm(BaseForm):
|
||||||
|
check = forms.BooleanField(
|
||||||
|
label=_("Confirm"),
|
||||||
|
label_suffix=_(""),
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
self.object_to_remove = kwargs.pop("object_to_remove", None)
|
||||||
|
self.remove_post_url = kwargs.pop("remove_post_url", "")
|
||||||
|
self.cancel_url = kwargs.pop("cancel_url", "")
|
||||||
|
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
self.form_title = _("Remove")
|
||||||
|
if self.object_to_remove is not None:
|
||||||
|
self.form_caption = _("You are about to remove {} {}").format(self.object_to_remove.__class__.__name__, self.object_to_remove)
|
||||||
|
self.action_url = self.remove_post_url
|
||||||
|
self.cancel_redirect = self.cancel_url
|
||||||
|
|
||||||
|
def is_checked(self) -> bool:
|
||||||
|
return self.cleaned_data.get("check", False)
|
||||||
|
|
||||||
|
def save(self, user: User):
|
||||||
|
""" Perform generic removing by running the form typical 'save()' method
|
||||||
|
|
||||||
|
Args:
|
||||||
|
user (User): The performing user
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
if self.object_to_remove is not None and self.is_checked():
|
||||||
|
with transaction.atomic():
|
||||||
|
self.object_to_remove.is_active = False
|
||||||
|
action = UserActionLogEntry.get_deleted_action(user)
|
||||||
|
self.object_to_remove.deleted = action
|
||||||
|
self.object_to_remove.save()
|
||||||
|
return self.object_to_remove
|
||||||
46
konova/management/commands/handle_resubmissions.py
Normal file
46
konova/management/commands/handle_resubmissions.py
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
"""
|
||||||
|
Author: Michel Peltriaux
|
||||||
|
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||||
|
Contact: ksp-servicestelle@sgdnord.rlp.de
|
||||||
|
Created on: 15.08.22
|
||||||
|
|
||||||
|
"""
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
from compensation.models import Compensation, EcoAccount
|
||||||
|
from ema.models import Ema
|
||||||
|
from intervention.models import Intervention
|
||||||
|
from konova.management.commands.setup import BaseKonovaCommand
|
||||||
|
from konova.models import Resubmission
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseKonovaCommand):
|
||||||
|
help = "Checks for resubmissions due now"
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
try:
|
||||||
|
resubmitable_models = [
|
||||||
|
Intervention,
|
||||||
|
Compensation,
|
||||||
|
Ema,
|
||||||
|
EcoAccount,
|
||||||
|
]
|
||||||
|
today = datetime.date.today()
|
||||||
|
resubmissions = Resubmission.objects.filter(
|
||||||
|
resubmit_on__lte=today,
|
||||||
|
resubmission_sent=False,
|
||||||
|
)
|
||||||
|
self._write_warning(f"Found {resubmissions.count()} resubmission. Process now...")
|
||||||
|
for model in resubmitable_models:
|
||||||
|
all_objs = model.objects.filter(
|
||||||
|
resubmissions__in=resubmissions
|
||||||
|
)
|
||||||
|
self._write_warning(f"Process resubmissions for {all_objs.count()} {model.__name__} entries")
|
||||||
|
for obj in all_objs:
|
||||||
|
obj.resubmit()
|
||||||
|
self._write_success("Mails have been sent.")
|
||||||
|
resubmissions.delete()
|
||||||
|
self._write_success("Resubmissions have been deleted.")
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
self._break_line()
|
||||||
|
exit(-1)
|
||||||
@@ -28,4 +28,5 @@ USER_NOTIFICATIONS_NAMES = {
|
|||||||
"NOTIFY_ON_SHARED_DATA_RECORDED": _("On shared data recorded"),
|
"NOTIFY_ON_SHARED_DATA_RECORDED": _("On shared data recorded"),
|
||||||
"NOTIFY_ON_SHARED_DATA_DELETED": _("On shared data deleted"),
|
"NOTIFY_ON_SHARED_DATA_DELETED": _("On shared data deleted"),
|
||||||
"NOTIFY_ON_SHARED_DATA_CHECKED": _("On shared data checked"),
|
"NOTIFY_ON_SHARED_DATA_CHECKED": _("On shared data checked"),
|
||||||
}
|
"NOTIFY_ON_DEDUCTION_CHANGES": _("On deduction changes"),
|
||||||
|
}
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ class Migration(migrations.Migration):
|
|||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('konova', '0004_auto_20220209_0839'),
|
('konova', '0004_auto_20220209_0839'),
|
||||||
|
('compensation', '0002_auto_20220114_0936'),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
|||||||
35
konova/migrations/0012_auto_20220713_0801.py
Normal file
35
konova/migrations/0012_auto_20220713_0801.py
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# Generated by Django 3.1.3 on 2022-07-13 06:01
|
||||||
|
import django
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
from konova.sub_settings.lanis_settings import DEFAULT_SRID_RLP
|
||||||
|
|
||||||
|
|
||||||
|
def migrate_geometry_srs(apps, schema_editor):
|
||||||
|
Geometry = apps.get_model("konova", "Geometry")
|
||||||
|
all_geoms = Geometry.objects.all()
|
||||||
|
|
||||||
|
# Transform all geoms and store in new geom field
|
||||||
|
for geometry in all_geoms:
|
||||||
|
geom = geometry.geom
|
||||||
|
if geom is None:
|
||||||
|
continue
|
||||||
|
geom.transform(DEFAULT_SRID_RLP)
|
||||||
|
geometry.geom_tmp = geom
|
||||||
|
geometry.save()
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('konova', '0011_auto_20220420_1101'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="geometry",
|
||||||
|
name="geom_tmp",
|
||||||
|
field=django.contrib.gis.db.models.fields.MultiPolygonField(blank=True, null=True, srid=DEFAULT_SRID_RLP)
|
||||||
|
),
|
||||||
|
migrations.RunPython(migrate_geometry_srs),
|
||||||
|
]
|
||||||
22
konova/migrations/0013_auto_20220713_0814.py
Normal file
22
konova/migrations/0013_auto_20220713_0814.py
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# Generated by Django 3.1.3 on 2022-07-13 06:14
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('konova', '0012_auto_20220713_0801'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name="geometry",
|
||||||
|
name="geom"
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name="geometry",
|
||||||
|
old_name="geom_tmp",
|
||||||
|
new_name="geom"
|
||||||
|
),
|
||||||
|
]
|
||||||
33
konova/migrations/0014_resubmission.py
Normal file
33
konova/migrations/0014_resubmission.py
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# Generated by Django 3.1.3 on 2022-08-15 06:03
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
('user', '0006_auto_20220815_0759'),
|
||||||
|
('konova', '0013_auto_20220713_0814'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Resubmission',
|
||||||
|
fields=[
|
||||||
|
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||||
|
('resubmit_on', models.DateField(help_text='On which date the resubmission should be performed')),
|
||||||
|
('resubmission_sent', models.BooleanField(default=False, help_text='Whether a resubmission has been sent or not')),
|
||||||
|
('comment', models.TextField(blank=True, help_text='Optional comment for the user itself', null=True)),
|
||||||
|
('created', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='user.useractionlogentry')),
|
||||||
|
('modified', models.ForeignKey(blank=True, help_text='Last modified', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='user.useractionlogentry')),
|
||||||
|
('user', models.ForeignKey(help_text='The user who wants to be notifed', on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -10,3 +10,4 @@ from .deadline import *
|
|||||||
from .document import *
|
from .document import *
|
||||||
from .geometry import *
|
from .geometry import *
|
||||||
from .parcel import *
|
from .parcel import *
|
||||||
|
from .resubmission import *
|
||||||
|
|||||||
@@ -21,8 +21,7 @@ class Geometry(BaseResource):
|
|||||||
"""
|
"""
|
||||||
Geometry model
|
Geometry model
|
||||||
"""
|
"""
|
||||||
from konova.settings import DEFAULT_SRID
|
geom = MultiPolygonField(null=True, blank=True, srid=DEFAULT_SRID_RLP)
|
||||||
geom = MultiPolygonField(null=True, blank=True, srid=DEFAULT_SRID)
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return str(self.id)
|
return str(self.id)
|
||||||
|
|||||||
@@ -743,4 +743,23 @@ class GeoReferencedMixin(models.Model):
|
|||||||
zoom_lvl,
|
zoom_lvl,
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ResubmitableObjectMixin(models.Model):
|
||||||
|
resubmissions = models.ManyToManyField(
|
||||||
|
"konova.Resubmission",
|
||||||
|
blank=True,
|
||||||
|
related_name="+",
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
abstract = True
|
||||||
|
|
||||||
|
def resubmit(self):
|
||||||
|
""" Run resubmit check and run for all related resubmissions
|
||||||
|
|
||||||
|
"""
|
||||||
|
resubmissions = self.resubmissions.all()
|
||||||
|
for resubmission in resubmissions:
|
||||||
|
resubmission.send_resubmission_mail(self.identifier)
|
||||||
|
|||||||
46
konova/models/resubmission.py
Normal file
46
konova/models/resubmission.py
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
"""
|
||||||
|
Author: Michel Peltriaux
|
||||||
|
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||||
|
Contact: ksp-servicestelle@sgdnord.rlp.de
|
||||||
|
Created on: 15.08.22
|
||||||
|
|
||||||
|
"""
|
||||||
|
from dateutil.utils import today
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
from konova.models import BaseResource
|
||||||
|
from konova.utils.mailer import Mailer
|
||||||
|
|
||||||
|
|
||||||
|
class Resubmission(BaseResource):
|
||||||
|
user = models.ForeignKey(
|
||||||
|
"user.User",
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
help_text="The user who wants to be notifed"
|
||||||
|
)
|
||||||
|
resubmit_on = models.DateField(
|
||||||
|
help_text="On which date the resubmission should be performed"
|
||||||
|
)
|
||||||
|
resubmission_sent = models.BooleanField(
|
||||||
|
default=False,
|
||||||
|
help_text="Whether a resubmission has been sent or not"
|
||||||
|
)
|
||||||
|
comment = models.TextField(
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
help_text="Optional comment for the user itself"
|
||||||
|
)
|
||||||
|
|
||||||
|
def send_resubmission_mail(self, obj_identifier):
|
||||||
|
""" Sends a resubmission mail
|
||||||
|
|
||||||
|
"""
|
||||||
|
_today = today().date()
|
||||||
|
resubmission_handled = _today.__ge__(self.resubmit_on) and self.resubmission_sent
|
||||||
|
if resubmission_handled:
|
||||||
|
return
|
||||||
|
|
||||||
|
mailer = Mailer()
|
||||||
|
mailer.send_mail_resubmission(obj_identifier, self)
|
||||||
|
self.resubmission_sent = True
|
||||||
|
self.save()
|
||||||
@@ -11,3 +11,4 @@ BASE_TITLE = "KSP - Kompensationsverzeichnis Service Portal"
|
|||||||
BASE_FRONTEND_TITLE = "Kompensationsverzeichnis Service Portal"
|
BASE_FRONTEND_TITLE = "Kompensationsverzeichnis Service Portal"
|
||||||
TAB_TITLE_IDENTIFIER = "tab_title"
|
TAB_TITLE_IDENTIFIER = "tab_title"
|
||||||
HELP_LINK = "https://dienste.naturschutz.rlp.de/doku/doku.php?id=ksp:start"
|
HELP_LINK = "https://dienste.naturschutz.rlp.de/doku/doku.php?id=ksp:start"
|
||||||
|
IMPRESSUM_LINK = "https://naturschutz.rlp.de/index.php?q=impressum"
|
||||||
|
|||||||
@@ -106,3 +106,17 @@ def celery_send_mail_shared_data_checked_team(obj_identifier, obj_title=None, te
|
|||||||
from user.models import Team
|
from user.models import Team
|
||||||
team = Team.objects.get(id=team_id)
|
team = Team.objects.get(id=team_id)
|
||||||
team.send_mail_shared_data_checked(obj_identifier, obj_title)
|
team.send_mail_shared_data_checked(obj_identifier, obj_title)
|
||||||
|
|
||||||
|
|
||||||
|
@shared_task
|
||||||
|
def celery_send_mail_deduction_changed_team(obj_identifier, obj_title=None, team_id=None, data_changes=None):
|
||||||
|
from user.models import Team
|
||||||
|
team = Team.objects.get(id=team_id)
|
||||||
|
team.send_mail_deduction_changed(obj_identifier, obj_title, data_changes)
|
||||||
|
|
||||||
|
|
||||||
|
@shared_task
|
||||||
|
def celery_send_mail_deduction_changed(obj_identifier, obj_title=None, user_id=None, data_changes=None):
|
||||||
|
from user.models import User
|
||||||
|
user = User.objects.get(id=user_id)
|
||||||
|
user.send_mail_deduction_changed(obj_identifier, obj_title, data_changes)
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ from codelist.models import KonovaCode, KonovaCodeList
|
|||||||
from compensation.models import Compensation, CompensationState, CompensationAction, EcoAccount, EcoAccountDeduction
|
from compensation.models import Compensation, CompensationState, CompensationAction, EcoAccount, EcoAccountDeduction
|
||||||
from intervention.models import Legal, Responsibility, Intervention, Handler
|
from intervention.models import Legal, Responsibility, Intervention, Handler
|
||||||
from konova.management.commands.setup_data import GROUPS_DATA
|
from konova.management.commands.setup_data import GROUPS_DATA
|
||||||
from konova.models import Geometry
|
from konova.models import Geometry, Deadline, DeadlineType
|
||||||
from konova.settings import DEFAULT_GROUP
|
from konova.settings import DEFAULT_GROUP
|
||||||
from konova.utils.generators import generate_random_string
|
from konova.utils.generators import generate_random_string
|
||||||
from user.models import UserActionLogEntry
|
from user.models import UserActionLogEntry
|
||||||
@@ -41,6 +41,7 @@ class BaseTestCase(TestCase):
|
|||||||
eco_account = None
|
eco_account = None
|
||||||
comp_state = None
|
comp_state = None
|
||||||
comp_action = None
|
comp_action = None
|
||||||
|
finished_deadline = None
|
||||||
codes = None
|
codes = None
|
||||||
|
|
||||||
superuser_pw = "root"
|
superuser_pw = "root"
|
||||||
@@ -69,6 +70,7 @@ class BaseTestCase(TestCase):
|
|||||||
self.create_dummy_action()
|
self.create_dummy_action()
|
||||||
self.codes = self.create_dummy_codes()
|
self.codes = self.create_dummy_codes()
|
||||||
self.team = self.create_dummy_team()
|
self.team = self.create_dummy_team()
|
||||||
|
self.finished_deadline = self.create_dummy_deadline()
|
||||||
|
|
||||||
# Set the default group as only group for the user
|
# Set the default group as only group for the user
|
||||||
default_group = self.groups.get(name=DEFAULT_GROUP)
|
default_group = self.groups.get(name=DEFAULT_GROUP)
|
||||||
@@ -279,6 +281,20 @@ class BaseTestCase(TestCase):
|
|||||||
|
|
||||||
return team
|
return team
|
||||||
|
|
||||||
|
def create_dummy_deadline(self, type: DeadlineType = DeadlineType.FINISHED):
|
||||||
|
""" Creates a dummy deadline.
|
||||||
|
|
||||||
|
If type is not specified, it defaults to DeadlineType.FINISHED
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
deadline (Deadline): A deadline
|
||||||
|
"""
|
||||||
|
deadline = Deadline.objects.create(
|
||||||
|
type=type,
|
||||||
|
date="1970-01-01"
|
||||||
|
)
|
||||||
|
return deadline
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def create_dummy_geometry() -> MultiPolygon:
|
def create_dummy_geometry() -> MultiPolygon:
|
||||||
""" Creates some geometry
|
""" Creates some geometry
|
||||||
@@ -361,6 +377,7 @@ class BaseTestCase(TestCase):
|
|||||||
compensation.before_states.add(self.comp_state)
|
compensation.before_states.add(self.comp_state)
|
||||||
compensation.actions.add(self.comp_action)
|
compensation.actions.add(self.comp_action)
|
||||||
compensation.geometry.geom = self.create_dummy_geometry()
|
compensation.geometry.geom = self.create_dummy_geometry()
|
||||||
|
compensation.deadlines.add(self.finished_deadline)
|
||||||
compensation.geometry.save()
|
compensation.geometry.save()
|
||||||
return compensation
|
return compensation
|
||||||
|
|
||||||
@@ -390,6 +407,7 @@ class BaseTestCase(TestCase):
|
|||||||
ema.before_states.add(self.comp_state)
|
ema.before_states.add(self.comp_state)
|
||||||
ema.actions.add(self.comp_action)
|
ema.actions.add(self.comp_action)
|
||||||
ema.geometry.geom = self.create_dummy_geometry()
|
ema.geometry.geom = self.create_dummy_geometry()
|
||||||
|
ema.deadlines.add(self.finished_deadline)
|
||||||
ema.geometry.save()
|
ema.geometry.save()
|
||||||
return ema
|
return ema
|
||||||
|
|
||||||
@@ -410,6 +428,7 @@ class BaseTestCase(TestCase):
|
|||||||
eco_account.geometry.geom = self.create_dummy_geometry()
|
eco_account.geometry.geom = self.create_dummy_geometry()
|
||||||
eco_account.geometry.save()
|
eco_account.geometry.save()
|
||||||
eco_account.deductable_surface = eco_account.get_state_after_surface_sum()
|
eco_account.deductable_surface = eco_account.get_state_after_surface_sum()
|
||||||
|
eco_account.deadlines.add(self.finished_deadline)
|
||||||
eco_account.save()
|
eco_account.save()
|
||||||
return eco_account
|
return eco_account
|
||||||
|
|
||||||
@@ -430,15 +449,13 @@ class BaseTestCase(TestCase):
|
|||||||
self.assertTrue(True)
|
self.assertTrue(True)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
tolerance = 0.001
|
||||||
if geom1.srid != geom2.srid:
|
if geom1.srid != geom2.srid:
|
||||||
tolerance = 0.001
|
|
||||||
# Due to prior possible transformation of any of these geometries, we need to make sure there exists a
|
# Due to prior possible transformation of any of these geometries, we need to make sure there exists a
|
||||||
# transformation from one coordinate system into the other, which is valid
|
# transformation from one coordinate system into the other, which is valid
|
||||||
geom1_t = geom1.transform(geom2.srid, clone=True)
|
geom1.transform(geom2.srid)
|
||||||
geom2_t = geom2.transform(geom1.srid, clone=True)
|
geom2.transform(geom1.srid)
|
||||||
self.assertTrue(geom1_t.equals_exact(geom2, tolerance) or geom2_t.equals_exact(geom1, tolerance))
|
self.assertTrue(geom1.equals_exact(geom2, tolerance) or geom2.equals_exact(geom1, tolerance))
|
||||||
else:
|
|
||||||
self.assertTrue(geom1.equals(geom2))
|
|
||||||
|
|
||||||
|
|
||||||
class BaseViewTestCase(BaseTestCase):
|
class BaseViewTestCase(BaseTestCase):
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -207,6 +207,33 @@ class Mailer:
|
|||||||
msg
|
msg
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def send_mail_deduction_changed_team(self, obj_identifier, obj_title, team, data_changes):
|
||||||
|
""" Send a mail if deduction has been changed
|
||||||
|
|
||||||
|
Args:
|
||||||
|
obj_identifier (str): Identifier of the main object
|
||||||
|
obj_title (str): Title of the main object
|
||||||
|
team (Team): Team to be notified
|
||||||
|
data_changes (dict): Contains the old|new changes of the deduction changes
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
context = {
|
||||||
|
"team": team,
|
||||||
|
"obj_identifier": obj_identifier,
|
||||||
|
"obj_title": obj_title,
|
||||||
|
"EMAIL_REPLY_TO": EMAIL_REPLY_TO,
|
||||||
|
"data_changes": data_changes,
|
||||||
|
}
|
||||||
|
msg = render_to_string("email/other/deduction_changed_team.html", context)
|
||||||
|
user_mail_address = team.users.values_list("email", flat=True)
|
||||||
|
self.send(
|
||||||
|
user_mail_address,
|
||||||
|
_("{} - Deduction changed").format(obj_identifier),
|
||||||
|
msg
|
||||||
|
)
|
||||||
|
|
||||||
def send_mail_shared_data_deleted_team(self, obj_identifier, obj_title, team):
|
def send_mail_shared_data_deleted_team(self, obj_identifier, obj_title, team):
|
||||||
""" Send a mail if data has just been deleted
|
""" Send a mail if data has just been deleted
|
||||||
|
|
||||||
@@ -322,6 +349,34 @@ class Mailer:
|
|||||||
msg
|
msg
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def send_mail_deduction_changed(self, obj_identifier, obj_title, user, data_changes):
|
||||||
|
""" Send a mail if deduction has been changed
|
||||||
|
|
||||||
|
Args:
|
||||||
|
obj_identifier (str): Identifier of the main object
|
||||||
|
obj_title (str): Title of the main object
|
||||||
|
user (User): User to be notified
|
||||||
|
data_changes (dict): Contains the old|new changes of the deduction changes
|
||||||
|
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
context = {
|
||||||
|
"user": user,
|
||||||
|
"obj_identifier": obj_identifier,
|
||||||
|
"obj_title": obj_title,
|
||||||
|
"EMAIL_REPLY_TO": EMAIL_REPLY_TO,
|
||||||
|
"data_changes": data_changes,
|
||||||
|
}
|
||||||
|
msg = render_to_string("email/other/deduction_changed.html", context)
|
||||||
|
user_mail_address = [user.email]
|
||||||
|
self.send(
|
||||||
|
user_mail_address,
|
||||||
|
_("{} - Deduction changed").format(obj_identifier),
|
||||||
|
msg
|
||||||
|
)
|
||||||
|
|
||||||
def send_mail_verify_api_token(self, user):
|
def send_mail_verify_api_token(self, user):
|
||||||
""" Send a mail if a user creates a new token
|
""" Send a mail if a user creates a new token
|
||||||
|
|
||||||
@@ -343,3 +398,26 @@ class Mailer:
|
|||||||
msg
|
msg
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def send_mail_resubmission(self, obj_identifier, resubmission):
|
||||||
|
""" Send a resubmission mail for a user
|
||||||
|
|
||||||
|
Args:
|
||||||
|
obj_identifier (str): The (resubmitted) object's identifier
|
||||||
|
resubmission (Resubmission): The resubmission
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
context = {
|
||||||
|
"obj_identifier": obj_identifier,
|
||||||
|
"resubmission": resubmission,
|
||||||
|
"EMAIL_REPLY_TO": EMAIL_REPLY_TO,
|
||||||
|
}
|
||||||
|
msg = render_to_string("email/resubmission/resubmission.html", context)
|
||||||
|
user_mail_address = [SUPPORT_MAIL_RECIPIENT]
|
||||||
|
self.send(
|
||||||
|
user_mail_address,
|
||||||
|
_("Resubmission - {}").format(obj_identifier),
|
||||||
|
msg
|
||||||
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -102,7 +102,6 @@ def home_view(request: HttpRequest):
|
|||||||
return render(request, template, context)
|
return render(request, template, context)
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
|
||||||
def get_geom_parcels(request: HttpRequest, id: str):
|
def get_geom_parcels(request: HttpRequest, id: str):
|
||||||
""" Getter for HTMX
|
""" Getter for HTMX
|
||||||
|
|
||||||
@@ -157,7 +156,6 @@ def get_geom_parcels(request: HttpRequest, id: str):
|
|||||||
return HttpResponse(None, status=404)
|
return HttpResponse(None, status=404)
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
|
||||||
def get_geom_parcels_content(request: HttpRequest, id: str, page: int):
|
def get_geom_parcels_content(request: HttpRequest, id: str, page: int):
|
||||||
""" Getter for infinite scroll of HTMX
|
""" Getter for infinite scroll of HTMX
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -31,7 +31,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
<div class="container-fluid mt-3 px-4">
|
<div class="container-fluid mt-3 mb-5 px-4">
|
||||||
{% comment %}
|
{% comment %}
|
||||||
The modal wrapper, which can be used on every view can stay on the base.html template
|
The modal wrapper, which can be used on every view can stay on the base.html template
|
||||||
{% endcomment %}
|
{% endcomment %}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
<article>
|
<article>
|
||||||
{% trans 'Hello support' %},
|
{% trans 'Hello support' %},
|
||||||
<br>
|
<br>
|
||||||
|
<br>
|
||||||
{% trans 'you need to verify the API token for user' %}:
|
{% trans 'you need to verify the API token for user' %}:
|
||||||
<br>
|
<br>
|
||||||
<br>
|
<br>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
<article>
|
<article>
|
||||||
{% trans 'Hello ' %} {{user.username}},
|
{% trans 'Hello ' %} {{user.username}},
|
||||||
<br>
|
<br>
|
||||||
|
<br>
|
||||||
{% trans 'the following dataset has just been checked' %}
|
{% trans 'the following dataset has just been checked' %}
|
||||||
<br>
|
<br>
|
||||||
<strong>{{obj_identifier}}</strong>
|
<strong>{{obj_identifier}}</strong>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
<article>
|
<article>
|
||||||
{% trans 'Hello team' %} {{team.name}},
|
{% trans 'Hello team' %} {{team.name}},
|
||||||
<br>
|
<br>
|
||||||
|
<br>
|
||||||
{% trans 'the following dataset has just been checked' %}
|
{% trans 'the following dataset has just been checked' %}
|
||||||
<br>
|
<br>
|
||||||
<strong>{{obj_identifier}}</strong>
|
<strong>{{obj_identifier}}</strong>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
<article>
|
<article>
|
||||||
{% trans 'Hello ' %} {{user.username}},
|
{% trans 'Hello ' %} {{user.username}},
|
||||||
<br>
|
<br>
|
||||||
|
<br>
|
||||||
{% trans 'the following dataset has just been deleted' %}
|
{% trans 'the following dataset has just been deleted' %}
|
||||||
<br>
|
<br>
|
||||||
<strong>{{obj_identifier}}</strong>
|
<strong>{{obj_identifier}}</strong>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
<article>
|
<article>
|
||||||
{% trans 'Hello team' %} {{team.name}},
|
{% trans 'Hello team' %} {{team.name}},
|
||||||
<br>
|
<br>
|
||||||
|
<br>
|
||||||
{% trans 'the following dataset has just been deleted' %}
|
{% trans 'the following dataset has just been deleted' %}
|
||||||
<br>
|
<br>
|
||||||
<strong>{{obj_identifier}}</strong>
|
<strong>{{obj_identifier}}</strong>
|
||||||
|
|||||||
50
templates/email/other/deduction_changed.html
Normal file
50
templates/email/other/deduction_changed.html
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h2>{% translate 'Deduction changed' %}</h2>
|
||||||
|
<h4>{{obj_identifier}}</h4>
|
||||||
|
<hr>
|
||||||
|
<article>
|
||||||
|
{% translate 'Hello ' %} {{user.username}},
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
{% translate 'a deduction of this eco account has changed:' %}
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">{% translate 'Attribute' %}</th>
|
||||||
|
<th scope="col">{% translate 'Old' %}</th>
|
||||||
|
<th scope="col">{% translate 'New' %}</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{% translate 'EcoAccount' %}</td>
|
||||||
|
<td>{{data_changes.account.old}}</td>
|
||||||
|
<td>{{data_changes.account.new}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{% translate 'Intervention' %}</td>
|
||||||
|
<td>{{data_changes.intervention.old}}</td>
|
||||||
|
<td>{{data_changes.intervention.new}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{% translate 'Surface' %}</td>
|
||||||
|
<td>{{data_changes.surface.old}} m²</td>
|
||||||
|
<td>{{data_changes.surface.new}} m²</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
{% translate 'If this should not have been happened, please contact us. See the signature for details.' %}
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
{% translate 'Best regards' %}
|
||||||
|
<br>
|
||||||
|
KSP
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
{% include 'email/signature.html' %}
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
|
||||||
50
templates/email/other/deduction_changed_team.html
Normal file
50
templates/email/other/deduction_changed_team.html
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h2>{% translate 'Deduction changed' %}</h2>
|
||||||
|
<h4>{{obj_identifier}}</h4>
|
||||||
|
<hr>
|
||||||
|
<article>
|
||||||
|
{% trans 'Hello team' %} {{team.name}},
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
{% translate 'a deduction of this eco account has changed:' %}
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">{% translate 'Attribute' %}</th>
|
||||||
|
<th scope="col">{% translate 'Old' %}</th>
|
||||||
|
<th scope="col">{% translate 'New' %}</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{% translate 'EcoAccount' %}</td>
|
||||||
|
<td>{{data_changes.account.old}}</td>
|
||||||
|
<td>{{data_changes.account.new}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{% translate 'Intervention' %}</td>
|
||||||
|
<td>{{data_changes.intervention.old}}</td>
|
||||||
|
<td>{{data_changes.intervention.new}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{% translate 'Surface' %}</td>
|
||||||
|
<td>{{data_changes.surface.old}} m²</td>
|
||||||
|
<td>{{data_changes.surface.new}} m²</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
{% translate 'If this should not have been happened, please contact us. See the signature for details.' %}
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
{% translate 'Best regards' %}
|
||||||
|
<br>
|
||||||
|
KSP
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
{% include 'email/signature.html' %}
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
<article>
|
<article>
|
||||||
{% trans 'Hello ' %} {{user.username}},
|
{% trans 'Hello ' %} {{user.username}},
|
||||||
<br>
|
<br>
|
||||||
|
<br>
|
||||||
{% trans 'the following dataset has just been recorded' %}
|
{% trans 'the following dataset has just been recorded' %}
|
||||||
<br>
|
<br>
|
||||||
<strong>{{obj_identifier}}</strong>
|
<strong>{{obj_identifier}}</strong>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
<article>
|
<article>
|
||||||
{% trans 'Hello team' %} {{team.name}},
|
{% trans 'Hello team' %} {{team.name}},
|
||||||
<br>
|
<br>
|
||||||
|
<br>
|
||||||
{% trans 'the following dataset has just been recorded' %}
|
{% trans 'the following dataset has just been recorded' %}
|
||||||
<br>
|
<br>
|
||||||
<strong>{{obj_identifier}}</strong>
|
<strong>{{obj_identifier}}</strong>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
<article>
|
<article>
|
||||||
{% trans 'Hello ' %} {{user.username}},
|
{% trans 'Hello ' %} {{user.username}},
|
||||||
<br>
|
<br>
|
||||||
|
<br>
|
||||||
{% trans 'the following dataset has just been unrecorded' %}
|
{% trans 'the following dataset has just been unrecorded' %}
|
||||||
<br>
|
<br>
|
||||||
<strong>{{obj_identifier}}</strong>
|
<strong>{{obj_identifier}}</strong>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
<article>
|
<article>
|
||||||
{% trans 'Hello team' %} {{team.name}},
|
{% trans 'Hello team' %} {{team.name}},
|
||||||
<br>
|
<br>
|
||||||
|
<br>
|
||||||
{% trans 'the following dataset has just been unrecorded' %}
|
{% trans 'the following dataset has just been unrecorded' %}
|
||||||
<br>
|
<br>
|
||||||
<strong>{{obj_identifier}}</strong>
|
<strong>{{obj_identifier}}</strong>
|
||||||
|
|||||||
29
templates/email/resubmission/resubmission.html
Normal file
29
templates/email/resubmission/resubmission.html
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h2>{% trans 'Resubmission' %}</h2>
|
||||||
|
<h4>{{obj_identifier}}</h4>
|
||||||
|
<hr>
|
||||||
|
<article>
|
||||||
|
{% trans 'Hello ' %} {{resubmission.user.username}},
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
{% trans 'you wanted to be reminded on this entry.' %}
|
||||||
|
<br>
|
||||||
|
{% if resubmission.comment %}
|
||||||
|
<br>
|
||||||
|
{% trans 'Your personal comment:' %}
|
||||||
|
<br>
|
||||||
|
<article style="font: italic">"{{resubmission.comment}}"</article>
|
||||||
|
{% endif %}
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
{% trans 'Best regards' %}
|
||||||
|
<br>
|
||||||
|
KSP
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
{% include 'email/signature.html' %}
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
<article>
|
<article>
|
||||||
{% trans 'Hello ' %} {{user.username}},
|
{% trans 'Hello ' %} {{user.username}},
|
||||||
<br>
|
<br>
|
||||||
|
<br>
|
||||||
{% trans 'the following dataset has just been shared with you' %}
|
{% trans 'the following dataset has just been shared with you' %}
|
||||||
<br>
|
<br>
|
||||||
<strong>{{obj_identifier}}</strong>
|
<strong>{{obj_identifier}}</strong>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
<article>
|
<article>
|
||||||
{% trans 'Hello team' %} {{team.name}},
|
{% trans 'Hello team' %} {{team.name}},
|
||||||
<br>
|
<br>
|
||||||
|
<br>
|
||||||
{% trans 'the following dataset has just been shared with your team' %}
|
{% trans 'the following dataset has just been shared with your team' %}
|
||||||
<br>
|
<br>
|
||||||
<strong>{{obj_identifier}}</strong>
|
<strong>{{obj_identifier}}</strong>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
<article>
|
<article>
|
||||||
{% trans 'Hello ' %} {{user.username}},
|
{% trans 'Hello ' %} {{user.username}},
|
||||||
<br>
|
<br>
|
||||||
|
<br>
|
||||||
{% trans 'your shared access, including editing, has been revoked for the dataset ' %}
|
{% trans 'your shared access, including editing, has been revoked for the dataset ' %}
|
||||||
<br>
|
<br>
|
||||||
<strong>{{obj_identifier}}</strong>
|
<strong>{{obj_identifier}}</strong>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
<article>
|
<article>
|
||||||
{% trans 'Hello team' %} {{team.name}},
|
{% trans 'Hello team' %} {{team.name}},
|
||||||
<br>
|
<br>
|
||||||
|
<br>
|
||||||
{% trans 'your teams shared access, including editing, has been revoked for the dataset ' %}
|
{% trans 'your teams shared access, including editing, has been revoked for the dataset ' %}
|
||||||
<br>
|
<br>
|
||||||
<strong>{{obj_identifier}}</strong>
|
<strong>{{obj_identifier}}</strong>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<a href="{{ help_link }}" target="_blank">{% trans 'Help' %}</a>
|
<a href="{{ help_link }}" target="_blank">{% trans 'Help' %}</a>
|
||||||
</span>
|
</span>
|
||||||
<span class="col-sm-auto footer-link">
|
<span class="col-sm-auto footer-link">
|
||||||
<a href="{% url 'home' %}">{% trans 'Impressum' %}</a>
|
<a href="{{ impressum_link }}" target="_blank">{% trans 'Impressum' %}</a>
|
||||||
</span>
|
</span>
|
||||||
<span class="col-sm-auto footer-link">
|
<span class="col-sm-auto footer-link">
|
||||||
<a href="mailto:{{CONTACT_MAIL}}">{% trans 'Contact' %}</a>
|
<a href="mailto:{{CONTACT_MAIL}}">{% trans 'Contact' %}</a>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user