Compare commits
66 Commits
97fbe02742
..
1.19
| Author | SHA1 | Date | |
|---|---|---|---|
| 6f2b6c44d9 | |||
| 494e80a4ac | |||
| 1f6c81874b | |||
| 9ee016a8bb | |||
| 93d29982a6 | |||
| 59a1bdfb1c | |||
| 056a92b068 | |||
| c795d22e68 | |||
| 591527b048 | |||
| 3de6a905e1 | |||
| 9632e59456 | |||
| d65f60c07c | |||
| 3ae0dc0cc1 | |||
| b721e9c51c | |||
| d26e363f8b | |||
| 2df178f4e1 | |||
| 25471b6de7 | |||
| 9863807ad6 | |||
| 62e02d745f | |||
| 1a9de7f874 | |||
| 46b66eb95d | |||
| 3dd6c6ae8d | |||
| 64a4750187 | |||
| 6c6b3293fb | |||
| 09246616aa | |||
| f146aa983a | |||
| 60e9430542 | |||
| 970d0e79fa | |||
| 3f33de3626 | |||
| 9e5bb84ab4 | |||
| 4c372c1a04 | |||
| ee2c859a9e | |||
| 328f672ec0 | |||
| 88058d7caf | |||
| 0e6f8d5b55 | |||
| 047c9489fe | |||
| 38b81996ed | |||
| 3966521cd4 | |||
| e70a8b51d1 | |||
| 02dc0d0a59 | |||
| 0b84d418db | |||
| 6aad76866f | |||
| 1af807deae | |||
| a2bda8d230 | |||
| e4c459f92e | |||
| 2da6f1dc6f | |||
| 72914bab9d | |||
| fdf3adf5ae | |||
| 4c4d64cc3d | |||
| fbde03caec | |||
| 43eb598d3f | |||
| b7fac0ae03 | |||
| 447ba942b5 | |||
| 6df47f1615 | |||
| e25d549a97 | |||
| 5e65b8f4dc | |||
| 22cddb9902 | |||
| c986bd0b92 | |||
| 2c60d86177 | |||
| b7792ececc | |||
| 210f3fcafa | |||
| e7d67560f2 | |||
| b5e991fb95 | |||
| d3a555d406 | |||
| 9a374f50de | |||
| ce63dd30bc |
+2
-2
@@ -10,6 +10,6 @@ from analysis.views import *
|
|||||||
|
|
||||||
app_name = "analysis"
|
app_name = "analysis"
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("reports/", index_reports_view, name="reports"),
|
path("reports/", ReportIndexView.as_view(), name="reports"),
|
||||||
path("reports/<id>", detail_report_view, name="report-detail"),
|
path("reports/<id>", ReportDetailView.as_view(), name="report-detail"),
|
||||||
]
|
]
|
||||||
+94
-35
@@ -1,8 +1,12 @@
|
|||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
from django.http import HttpRequest, HttpResponse
|
from django.http import HttpRequest, HttpResponse
|
||||||
from django.shortcuts import render, redirect, get_object_or_404
|
from django.shortcuts import render, redirect, get_object_or_404
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
from django.utils.decorators import method_decorator
|
||||||
|
from django.views import View
|
||||||
|
from django.views.generic import DetailView
|
||||||
|
|
||||||
from analysis.forms import TimespanReportForm
|
from analysis.forms import TimespanReportForm
|
||||||
from analysis.utils.excel.excel import TempExcelFile
|
from analysis.utils.excel.excel import TempExcelFile
|
||||||
@@ -42,57 +46,112 @@ def index_reports_view(request: HttpRequest):
|
|||||||
context = BaseContext(request, context).context
|
context = BaseContext(request, context).context
|
||||||
return render(request, template, context)
|
return render(request, template, context)
|
||||||
|
|
||||||
|
class ReportIndexView(LoginRequiredMixin, View):
|
||||||
|
@method_decorator(conservation_office_group_required)
|
||||||
|
def get(self, request: HttpRequest) -> HttpResponse:
|
||||||
|
|
||||||
@login_required
|
"""
|
||||||
@conservation_office_group_required
|
|
||||||
def detail_report_view(request: HttpRequest, id: str):
|
|
||||||
""" Renders the detailed report for a conservation office
|
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
request (HttpRequest): The incoming request
|
request (HttpRequest): The incoming request
|
||||||
id (str): The conservation_office KonovaCode id
|
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# Try to resolve the requested office id
|
template = "analysis/reports/index.html"
|
||||||
cons_office = get_object_or_404(
|
form = TimespanReportForm(None)
|
||||||
KonovaCode,
|
context = {
|
||||||
id=id
|
"form": form
|
||||||
)
|
}
|
||||||
# Try to resolve the date parameters into Date objects -> redirect if this fails
|
context = BaseContext(request, context).context
|
||||||
try:
|
return render(request, template, context)
|
||||||
df = request.GET.get("df", None)
|
|
||||||
dt = request.GET.get("dt", None)
|
@method_decorator(conservation_office_group_required)
|
||||||
date_from = timezone.make_aware(timezone.datetime.fromisoformat(df))
|
def post(self, request: HttpRequest) -> HttpResponse:
|
||||||
date_to = timezone.make_aware(timezone.datetime.fromisoformat(dt))
|
|
||||||
except ValueError:
|
"""
|
||||||
messages.error(
|
|
||||||
request,
|
Args:
|
||||||
PARAMS_INVALID,
|
request (HttpRequest): The incoming request
|
||||||
extra_tags="danger",
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
template = "analysis/reports/index.html"
|
||||||
|
form = TimespanReportForm(request.POST or None)
|
||||||
|
if form.is_valid():
|
||||||
|
redirect_url = form.save()
|
||||||
|
return redirect(redirect_url)
|
||||||
|
else:
|
||||||
|
messages.error(
|
||||||
|
request,
|
||||||
|
FORM_INVALID,
|
||||||
|
extra_tags="danger",
|
||||||
|
)
|
||||||
|
context = {
|
||||||
|
"form": form
|
||||||
|
}
|
||||||
|
context = BaseContext(request, context).context
|
||||||
|
return render(request, template, context)
|
||||||
|
|
||||||
|
|
||||||
|
class ReportDetailView(LoginRequiredMixin, DetailView):
|
||||||
|
|
||||||
|
@method_decorator(conservation_office_group_required)
|
||||||
|
def get(self, request: HttpRequest, id: str):
|
||||||
|
""" Renders the detailed report for a conservation office
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request (HttpRequest): The incoming request
|
||||||
|
id (str): The conservation_office KonovaCode id
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
# Try to resolve the requested office id
|
||||||
|
cons_office = get_object_or_404(
|
||||||
|
KonovaCode,
|
||||||
|
id=id
|
||||||
)
|
)
|
||||||
return redirect("analysis:reports")
|
# Try to resolve the date parameters into Date objects -> redirect if this fails
|
||||||
|
try:
|
||||||
|
df = request.GET.get("df", None)
|
||||||
|
dt = request.GET.get("dt", None)
|
||||||
|
date_from = timezone.make_aware(timezone.datetime.fromisoformat(df))
|
||||||
|
date_to = timezone.make_aware(timezone.datetime.fromisoformat(dt))
|
||||||
|
except ValueError:
|
||||||
|
messages.error(
|
||||||
|
request,
|
||||||
|
PARAMS_INVALID,
|
||||||
|
extra_tags="danger",
|
||||||
|
)
|
||||||
|
return redirect("analysis:reports")
|
||||||
|
|
||||||
# Check whether the html default rendering is requested or an alternative
|
# Check whether the html default rendering is requested or an alternative
|
||||||
format_param = request.GET.get("format", "html")
|
format_param = request.GET.get("format", "html")
|
||||||
report = TimespanReport(id, date_from, date_to)
|
report = TimespanReport(id, date_from, date_to)
|
||||||
|
|
||||||
if format_param == "html":
|
if format_param == "html":
|
||||||
|
return self.__handle_html_format(request, report, cons_office)
|
||||||
|
elif format_param == "excel":
|
||||||
|
return self.__handle_excel_format(report, cons_office, df, dt)
|
||||||
|
else:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def __handle_html_format(self, request, report: TimespanReport, office: KonovaCode):
|
||||||
template = "analysis/reports/detail.html"
|
template = "analysis/reports/detail.html"
|
||||||
context = {
|
context = {
|
||||||
"office": cons_office,
|
"office": office,
|
||||||
"report": report,
|
"report": report,
|
||||||
}
|
}
|
||||||
context = BaseContext(request, context).context
|
context = BaseContext(request, context).context
|
||||||
return render(request, template, context)
|
return render(request, template, context)
|
||||||
elif format_param == "excel":
|
|
||||||
|
def __handle_excel_format(self, report: TimespanReport, office: KonovaCode, df: str, dt: str):
|
||||||
file = TempExcelFile(report.excel_template_path, report.excel_map)
|
file = TempExcelFile(report.excel_template_path, report.excel_map)
|
||||||
response = HttpResponse(
|
response = HttpResponse(
|
||||||
content=file.stream,
|
content=file.stream,
|
||||||
content_type="application/ms-excel",
|
content_type="application/ms-excel",
|
||||||
)
|
)
|
||||||
response['Content-Disposition'] = f'attachment; filename={cons_office.long_name}_{df}_{dt}.xlsx'
|
response['Content-Disposition'] = f'attachment; filename={office.long_name}_{df}_{dt}.xlsx'
|
||||||
return response
|
return response
|
||||||
else:
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# Generated by Django 6.0.5 on 2026-05-10 07:18
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('api', '0003_oauthtoken'),
|
||||||
|
('user', '0010_user_sso_identifier'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ExternalIdentifier',
|
||||||
|
fields=[
|
||||||
|
('external_id', models.CharField(db_comment='Identifier from a source system', max_length=255, primary_key=True, serialize=False)),
|
||||||
|
('internal_id', models.UUIDField(db_comment='Identifier in konova')),
|
||||||
|
('created', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='user.useractionlogentry')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -6,3 +6,4 @@ Created on: 21.01.22
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
from .token import *
|
from .token import *
|
||||||
|
from .external_identifier import *
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
"""
|
||||||
|
Author: Michel Peltriaux
|
||||||
|
Created on: 10.05.26
|
||||||
|
|
||||||
|
"""
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
|
||||||
|
class ExternalIdentifier(models.Model):
|
||||||
|
""" Holds a lookup to match a given external identifier against the internal identifier in konova.
|
||||||
|
|
||||||
|
Relevant in cases of API transmitted entries, which are updates using external identifiers instead of
|
||||||
|
the internal ones directly.
|
||||||
|
|
||||||
|
"""
|
||||||
|
external_id = models.CharField(
|
||||||
|
max_length=255,
|
||||||
|
primary_key=True,
|
||||||
|
db_comment="Identifier from a source system"
|
||||||
|
)
|
||||||
|
internal_id = models.UUIDField(
|
||||||
|
db_comment="Identifier in konova"
|
||||||
|
)
|
||||||
|
created = models.ForeignKey(
|
||||||
|
"user.UserActionLogEntry",
|
||||||
|
on_delete=models.SET_NULL,
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
related_name='+'
|
||||||
|
)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.external_id} -> {self.internal_id}"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def resolve_external_identifier(external_identifier: str):
|
||||||
|
""" Returns a ExternalIdentifier object, if the given parameter could be resolved as an external identifier.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
external_identifier (str): An external identifier.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
ExternalIdentifier | None
|
||||||
|
"""
|
||||||
|
if external_identifier:
|
||||||
|
try:
|
||||||
|
obj = ExternalIdentifier.objects.get(external_id=external_identifier)
|
||||||
|
return obj
|
||||||
|
except ExternalIdentifier.DoesNotExist:
|
||||||
|
pass
|
||||||
|
return None
|
||||||
@@ -8,6 +8,7 @@
|
|||||||
"is_coherence_keeping": false,
|
"is_coherence_keeping": false,
|
||||||
"is_pik": false,
|
"is_pik": false,
|
||||||
"intervention": "MUST_BE_SET_IN_TEST",
|
"intervention": "MUST_BE_SET_IN_TEST",
|
||||||
|
"external_identifier": "LOREMIPSUM-123",
|
||||||
"before_states": [
|
"before_states": [
|
||||||
],
|
],
|
||||||
"after_states": [
|
"after_states": [
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"title": "Test_ecoaccount",
|
"title": "Test_ecoaccount",
|
||||||
|
"external_identifier": "LOREMIPSUM-1234",
|
||||||
"deductable_surface": 10000.0,
|
"deductable_surface": 10000.0,
|
||||||
"is_pik": false,
|
"is_pik": false,
|
||||||
"responsible": {
|
"responsible": {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"title": "Test_ema",
|
"title": "Test_ema",
|
||||||
|
"external_identifier": "LOREMIPSUM-1235",
|
||||||
"is_pik": false,
|
"is_pik": false,
|
||||||
"responsible": {
|
"responsible": {
|
||||||
"conservation_office": null,
|
"conservation_office": null,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"title": "Test_intervention",
|
"title": "Test_intervention",
|
||||||
|
"external_identifier": "LOREMIPSUM-1236",
|
||||||
"responsible": {
|
"responsible": {
|
||||||
"registration_office": null,
|
"registration_office": null,
|
||||||
"registration_file_number": null,
|
"registration_file_number": null,
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import json
|
|||||||
|
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
|
||||||
|
from api.models import ExternalIdentifier
|
||||||
from api.tests.v1.share.test_api_sharing import BaseAPIV1TestCase
|
from api.tests.v1.share.test_api_sharing import BaseAPIV1TestCase
|
||||||
|
|
||||||
|
|
||||||
@@ -42,7 +43,22 @@ class APIV1CreateTestCase(BaseAPIV1TestCase):
|
|||||||
response = self._run_create_request(url, post_body)
|
response = self._run_create_request(url, post_body)
|
||||||
self.assertEqual(response.status_code, 200, msg=response.content)
|
self.assertEqual(response.status_code, 200, msg=response.content)
|
||||||
content = json.loads(response.content)
|
content = json.loads(response.content)
|
||||||
self.assertIsNotNone(content.get("id", None), msg=response.content)
|
_id = content.get("id", None)
|
||||||
|
self.assertIsNotNone(_id, msg=response.content)
|
||||||
|
return _id
|
||||||
|
|
||||||
|
def _test_external_identifier_created(self, internal_id, external_id):
|
||||||
|
""" Tests whether an external identifier has been created
|
||||||
|
|
||||||
|
Args:
|
||||||
|
internal_id ():
|
||||||
|
external_id ():
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
external_identifier = ExternalIdentifier.objects.get(internal_id=internal_id)
|
||||||
|
self.assertEqual(external_identifier.external_id, external_id)
|
||||||
|
|
||||||
def test_create_intervention(self):
|
def test_create_intervention(self):
|
||||||
""" Tests api creation
|
""" Tests api creation
|
||||||
@@ -54,7 +70,8 @@ class APIV1CreateTestCase(BaseAPIV1TestCase):
|
|||||||
json_file_path = "api/tests/v1/create/intervention_create_post_body.json"
|
json_file_path = "api/tests/v1/create/intervention_create_post_body.json"
|
||||||
with open(json_file_path) as json_file:
|
with open(json_file_path) as json_file:
|
||||||
post_body = json.load(fp=json_file)
|
post_body = json.load(fp=json_file)
|
||||||
self._test_create_object(url, post_body)
|
internal_id = self._test_create_object(url, post_body)
|
||||||
|
self._test_external_identifier_created(internal_id, post_body["properties"]["external_identifier"])
|
||||||
|
|
||||||
def test_create_compensation(self):
|
def test_create_compensation(self):
|
||||||
""" Tests api creation
|
""" Tests api creation
|
||||||
@@ -71,13 +88,14 @@ class APIV1CreateTestCase(BaseAPIV1TestCase):
|
|||||||
# Expect this first request to fail, since user has no shared access on the intervention, we want to create
|
# Expect this first request to fail, since user has no shared access on the intervention, we want to create
|
||||||
# a compensation for
|
# a compensation for
|
||||||
response = self._run_create_request(url, post_body)
|
response = self._run_create_request(url, post_body)
|
||||||
self.assertEqual(response.status_code, 500, msg=response.content)
|
self.assertEqual(response.status_code, 400, msg=response.content)
|
||||||
content = json.loads(response.content)
|
content = json.loads(response.content)
|
||||||
self.assertGreater(len(content.get("errors", [])), 0, msg=response.content)
|
self.assertGreater(len(content.get("errors", [])), 0, msg=response.content)
|
||||||
|
|
||||||
# Add the user to the shared users of the intervention and try again! Now everything should work as expected.
|
# Add the user to the shared users of the intervention and try again! Now everything should work as expected.
|
||||||
self.intervention.users.add(self.superuser)
|
self.intervention.users.add(self.superuser)
|
||||||
self._test_create_object(url, post_body)
|
internal_id = self._test_create_object(url, post_body)
|
||||||
|
self._test_external_identifier_created(internal_id, post_body["properties"]["external_identifier"])
|
||||||
|
|
||||||
def test_create_eco_account(self):
|
def test_create_eco_account(self):
|
||||||
""" Tests api creation
|
""" Tests api creation
|
||||||
@@ -89,7 +107,8 @@ class APIV1CreateTestCase(BaseAPIV1TestCase):
|
|||||||
json_file_path = "api/tests/v1/create/ecoaccount_create_post_body.json"
|
json_file_path = "api/tests/v1/create/ecoaccount_create_post_body.json"
|
||||||
with open(json_file_path) as json_file:
|
with open(json_file_path) as json_file:
|
||||||
post_body = json.load(fp=json_file)
|
post_body = json.load(fp=json_file)
|
||||||
self._test_create_object(url, post_body)
|
internal_id = self._test_create_object(url, post_body)
|
||||||
|
self._test_external_identifier_created(internal_id, post_body["properties"]["external_identifier"])
|
||||||
|
|
||||||
def test_create_ema(self):
|
def test_create_ema(self):
|
||||||
""" Tests api creation
|
""" Tests api creation
|
||||||
@@ -101,7 +120,8 @@ class APIV1CreateTestCase(BaseAPIV1TestCase):
|
|||||||
json_file_path = "api/tests/v1/create/ema_create_post_body.json"
|
json_file_path = "api/tests/v1/create/ema_create_post_body.json"
|
||||||
with open(json_file_path) as json_file:
|
with open(json_file_path) as json_file:
|
||||||
post_body = json.load(fp=json_file)
|
post_body = json.load(fp=json_file)
|
||||||
self._test_create_object(url, post_body)
|
internal_id = self._test_create_object(url, post_body)
|
||||||
|
self._test_external_identifier_created(internal_id, post_body["properties"]["external_identifier"])
|
||||||
|
|
||||||
def test_create_deduction(self):
|
def test_create_deduction(self):
|
||||||
""" Tests api creation
|
""" Tests api creation
|
||||||
|
|||||||
@@ -31,9 +31,10 @@ class APIV1SharingTestCase(BaseAPIV1TestCase):
|
|||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
super().setUpTestData()
|
super().setUpTestData()
|
||||||
|
|
||||||
def _run_share_request(self, url, user_list: list):
|
def _run_share_request(self, url, user_list: list, team_list: list):
|
||||||
data = {
|
data = {
|
||||||
"users": user_list
|
"users": user_list,
|
||||||
|
"teams": team_list
|
||||||
}
|
}
|
||||||
data = json.dumps(data)
|
data = json.dumps(data)
|
||||||
response = self.client.put(
|
response = self.client.put(
|
||||||
@@ -58,16 +59,22 @@ class APIV1SharingTestCase(BaseAPIV1TestCase):
|
|||||||
self.superuser.username,
|
self.superuser.username,
|
||||||
self.user.username,
|
self.user.username,
|
||||||
]
|
]
|
||||||
|
team_list = [
|
||||||
|
str(self.team.id),
|
||||||
|
]
|
||||||
|
|
||||||
response = self._run_share_request(url, user_list)
|
response = self._run_share_request(url, user_list, team_list)
|
||||||
|
|
||||||
# Must fail, since performing user has no access on requested object
|
# Must fail, since performing user has no access on requested object
|
||||||
self.assertEqual(response.status_code, 500)
|
self.assertEqual(response.status_code, 500)
|
||||||
self.assertTrue(len(json.loads(response.content.decode("utf-8")).get("errors", [])) > 0)
|
self.assertTrue(len(json.loads(response.content.decode("utf-8")).get("errors", [])) > 0)
|
||||||
|
|
||||||
# Add performing user to shared access users and rerun the request
|
# Add performing user to shared access users, switch from team id to team name and rerun the request
|
||||||
obj.users.add(self.superuser)
|
obj.users.add(self.superuser)
|
||||||
response = self._run_share_request(url, user_list)
|
team_list = [
|
||||||
|
self.team.name
|
||||||
|
]
|
||||||
|
response = self._run_share_request(url, user_list, team_list)
|
||||||
|
|
||||||
shared_users = obj.shared_users
|
shared_users = obj.shared_users
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
@@ -84,14 +91,14 @@ class APIV1SharingTestCase(BaseAPIV1TestCase):
|
|||||||
share_url = reverse("api:v1:intervention-share", args=(self.intervention.id,))
|
share_url = reverse("api:v1:intervention-share", args=(self.intervention.id,))
|
||||||
# Expect the first request to work properly
|
# Expect the first request to work properly
|
||||||
self.intervention.users.add(self.superuser)
|
self.intervention.users.add(self.superuser)
|
||||||
response = self._run_share_request(share_url, [self.superuser.username])
|
response = self._run_share_request(share_url, [self.superuser.username], [str(self.team.id)])
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
# Change the token
|
# Change the token
|
||||||
self.header_data["HTTP_ksptoken"] = f"{self.superuser.api_token.token}__X"
|
self.header_data["HTTP_ksptoken"] = f"{self.superuser.api_token.token}__X"
|
||||||
|
|
||||||
# Expect the request to fail now
|
# Expect the request to fail now
|
||||||
response = self._run_share_request(share_url, [self.superuser.username])
|
response = self._run_share_request(share_url, [self.superuser.username], [])
|
||||||
self.assertEqual(response.status_code, 403)
|
self.assertEqual(response.status_code, 403)
|
||||||
|
|
||||||
def test_api_intervention_sharing(self):
|
def test_api_intervention_sharing(self):
|
||||||
@@ -144,11 +151,11 @@ class APIV1SharingTestCase(BaseAPIV1TestCase):
|
|||||||
self.assertEqual(self.intervention.users.count(), 1)
|
self.assertEqual(self.intervention.users.count(), 1)
|
||||||
|
|
||||||
# Try to add another user via API -> must work!
|
# Try to add another user via API -> must work!
|
||||||
response = self._run_share_request(share_url, [self.superuser.username, self.user.username])
|
response = self._run_share_request(share_url, [self.superuser.username, self.user.username], [])
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertEqual(self.intervention.users.count(), 2)
|
self.assertEqual(self.intervention.users.count(), 2)
|
||||||
|
|
||||||
# Now try to remove the user again -> expect no changes at all to the shared user list
|
# Now try to remove the user again -> expect no changes at all to the shared user list
|
||||||
response = self._run_share_request(share_url, [self.superuser.username])
|
response = self._run_share_request(share_url, [self.superuser.username], [])
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertEqual(self.intervention.users.count(), 2)
|
self.assertEqual(self.intervention.users.count(), 2)
|
||||||
|
|||||||
@@ -44,6 +44,7 @@
|
|||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"title": "TEST_compensation_CHANGED",
|
"title": "TEST_compensation_CHANGED",
|
||||||
|
"external_identifier": "LOREMIPSUM-123_CHANGED",
|
||||||
"is_cef": true,
|
"is_cef": true,
|
||||||
"is_coherence_keeping": true,
|
"is_coherence_keeping": true,
|
||||||
"is_pik": true,
|
"is_pik": true,
|
||||||
|
|||||||
@@ -44,6 +44,7 @@
|
|||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"title": "TEST_account_CHANGED",
|
"title": "TEST_account_CHANGED",
|
||||||
|
"external_identifier": "LOREMIPSUM-1234_CHANGED",
|
||||||
"deductable_surface": "100000.0",
|
"deductable_surface": "100000.0",
|
||||||
"is_pik": true,
|
"is_pik": true,
|
||||||
"responsible": {
|
"responsible": {
|
||||||
|
|||||||
@@ -44,6 +44,7 @@
|
|||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"title": "TEST_EMA_CHANGED",
|
"title": "TEST_EMA_CHANGED",
|
||||||
|
"external_identifier": "LOREMIPSUM-1235_CHANGED",
|
||||||
"responsible": {
|
"responsible": {
|
||||||
"conservation_office": null,
|
"conservation_office": null,
|
||||||
"conservation_file_number": "TEST_CHANGED",
|
"conservation_file_number": "TEST_CHANGED",
|
||||||
|
|||||||
@@ -44,6 +44,7 @@
|
|||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"title": "Test_intervention_CHANGED",
|
"title": "Test_intervention_CHANGED",
|
||||||
|
"external_identifier": "LOREMIPSUM-1236_CHANGED",
|
||||||
"responsible": {
|
"responsible": {
|
||||||
"registration_office": null,
|
"registration_office": null,
|
||||||
"registration_file_number": "CHANGED",
|
"registration_file_number": "CHANGED",
|
||||||
|
|||||||
@@ -7,11 +7,8 @@ Created on: 21.01.22
|
|||||||
"""
|
"""
|
||||||
from django.urls import path, include
|
from django.urls import path, include
|
||||||
|
|
||||||
from api.views.method_views import generate_new_token_view
|
|
||||||
|
|
||||||
app_name = "api"
|
app_name = "api"
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("v1/", include("api.urls.v1.urls", namespace="v1")),
|
path("v1/", include("api.urls.v1.urls", namespace="v1")),
|
||||||
path("token/generate", generate_new_token_view, name="generate-new-token"),
|
|
||||||
]
|
]
|
||||||
@@ -10,9 +10,11 @@ from abc import abstractmethod
|
|||||||
|
|
||||||
from django.contrib.gis import geos
|
from django.contrib.gis import geos
|
||||||
from django.contrib.gis.geos import GEOSGeometry
|
from django.contrib.gis.geos import GEOSGeometry
|
||||||
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
from django.core.paginator import Paginator
|
from django.core.paginator import Paginator
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
|
||||||
|
from api.models import ExternalIdentifier
|
||||||
from konova.models import Geometry
|
from konova.models import Geometry
|
||||||
from konova.utils.message_templates import DATA_UNSHARED
|
from konova.utils.message_templates import DATA_UNSHARED
|
||||||
|
|
||||||
@@ -76,6 +78,14 @@ class AbstractModelAPISerializer:
|
|||||||
del self.lookup["id"]
|
del self.lookup["id"]
|
||||||
else:
|
else:
|
||||||
# Return certain object
|
# Return certain object
|
||||||
|
## But first check, whether this is an external identifier ...
|
||||||
|
try:
|
||||||
|
## If we can find this _id on our ExternalIdentifier model, we need to map it on the internal id
|
||||||
|
ext_id = ExternalIdentifier.objects.get(external_id=_id)
|
||||||
|
_id = ext_id.internal_id
|
||||||
|
except ObjectDoesNotExist:
|
||||||
|
# If we did not find it, we assume that this is already an internal id. (Or it does not exist at all)
|
||||||
|
pass
|
||||||
self.lookup["id"] = _id
|
self.lookup["id"] = _id
|
||||||
|
|
||||||
self.shared_lookup = Q(
|
self.shared_lookup = Q(
|
||||||
@@ -161,6 +171,14 @@ class AbstractModelAPISerializer:
|
|||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
# First if there is an external identifier linked to an internal one, so we can continue with the internal
|
||||||
|
try:
|
||||||
|
ext_id = ExternalIdentifier.objects.get(external_id=id)
|
||||||
|
id = ext_id.internal_id
|
||||||
|
except ObjectDoesNotExist:
|
||||||
|
# No external id found - let's hope the given id exists internally
|
||||||
|
pass
|
||||||
|
|
||||||
obj = self.model.objects.get(
|
obj = self.model.objects.get(
|
||||||
id=id,
|
id=id,
|
||||||
deleted__isnull=True,
|
deleted__isnull=True,
|
||||||
|
|||||||
@@ -88,6 +88,11 @@ class CompensationAPISerializerV1(AbstractModelAPISerializerV1, AbstractCompensa
|
|||||||
# Nothing to do here
|
# Nothing to do here
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
|
# Transform a potential external identifier into an internal one
|
||||||
|
intervention_ext_id = self._get_external_identifier(intervention_id)
|
||||||
|
if intervention_ext_id:
|
||||||
|
intervention_id = intervention_ext_id.internal_id
|
||||||
|
|
||||||
intervention = Intervention.objects.get(
|
intervention = Intervention.objects.get(
|
||||||
id=intervention_id,
|
id=intervention_id,
|
||||||
)
|
)
|
||||||
@@ -114,6 +119,10 @@ class CompensationAPISerializerV1(AbstractModelAPISerializerV1, AbstractCompensa
|
|||||||
|
|
||||||
# Fill in data to objects
|
# Fill in data to objects
|
||||||
properties = json_model["properties"]
|
properties = json_model["properties"]
|
||||||
|
|
||||||
|
external_identifier = properties.get("external_identifier", None)
|
||||||
|
self._check_external_identifier_on_entry_creation(external_identifier)
|
||||||
|
|
||||||
obj.identifier = obj.generate_new_identifier()
|
obj.identifier = obj.generate_new_identifier()
|
||||||
obj.title = properties["title"]
|
obj.title = properties["title"]
|
||||||
obj.is_cef = properties["is_cef"]
|
obj.is_cef = properties["is_cef"]
|
||||||
@@ -129,6 +138,7 @@ class CompensationAPISerializerV1(AbstractModelAPISerializerV1, AbstractCompensa
|
|||||||
obj = self._set_compensation_states(obj, properties["after_states"], obj.after_states)
|
obj = self._set_compensation_states(obj, properties["after_states"], obj.after_states)
|
||||||
obj = self._set_deadlines(obj, properties["deadlines"])
|
obj = self._set_deadlines(obj, properties["deadlines"])
|
||||||
|
|
||||||
|
self._set_external_identifier(obj.id, properties.get("external_identifier", None), obj.created)
|
||||||
obj.log.add(obj.created)
|
obj.log.add(obj.created)
|
||||||
|
|
||||||
celery_update_parcels.delay(obj.geometry.id)
|
celery_update_parcels.delay(obj.geometry.id)
|
||||||
@@ -170,6 +180,7 @@ class CompensationAPISerializerV1(AbstractModelAPISerializerV1, AbstractCompensa
|
|||||||
obj = self._set_compensation_states(obj, properties["after_states"], obj.after_states)
|
obj = self._set_compensation_states(obj, properties["after_states"], obj.after_states)
|
||||||
obj = self._set_deadlines(obj, properties["deadlines"])
|
obj = self._set_deadlines(obj, properties["deadlines"])
|
||||||
|
|
||||||
|
self._set_external_identifier(obj.id, properties.get("external_identifier", None), update_action)
|
||||||
obj.log.add(update_action)
|
obj.log.add(update_action)
|
||||||
|
|
||||||
celery_update_parcels.delay(obj.geometry.id)
|
celery_update_parcels.delay(obj.geometry.id)
|
||||||
|
|||||||
@@ -62,6 +62,14 @@ class DeductionAPISerializerV1(AbstractModelAPISerializerV1,
|
|||||||
if surface <= 0:
|
if surface <= 0:
|
||||||
raise ValueError("Surface must be > 0 m²")
|
raise ValueError("Surface must be > 0 m²")
|
||||||
|
|
||||||
|
# Check if external identifiers need to be mapped onto internal ones
|
||||||
|
acc_ext_id = self._get_external_identifier(acc_id)
|
||||||
|
intervention_ext_id = self._get_external_identifier(intervention_id)
|
||||||
|
if acc_ext_id:
|
||||||
|
acc_id = acc_ext_id.internal_id
|
||||||
|
if intervention_ext_id:
|
||||||
|
intervention_id = intervention_ext_id.internal_id
|
||||||
|
|
||||||
acc = EcoAccount.objects.get(
|
acc = EcoAccount.objects.get(
|
||||||
id=acc_id,
|
id=acc_id,
|
||||||
deleted__isnull=True,
|
deleted__isnull=True,
|
||||||
|
|||||||
@@ -121,7 +121,9 @@ class EcoAccountAPISerializerV1(AbstractModelAPISerializerV1,
|
|||||||
obj = self._initialize_objects(json_model, user)
|
obj = self._initialize_objects(json_model, user)
|
||||||
|
|
||||||
# Fill in data to objects
|
# Fill in data to objects
|
||||||
properties = json_model["properties"]
|
properties = json_model.get("properties", None)
|
||||||
|
if not properties:
|
||||||
|
raise AssertionError("No 'properties' found in payload!")
|
||||||
obj.identifier = obj.generate_new_identifier()
|
obj.identifier = obj.generate_new_identifier()
|
||||||
obj.title = properties["title"]
|
obj.title = properties["title"]
|
||||||
obj.is_pik = properties.get("is_pik", False)
|
obj.is_pik = properties.get("is_pik", False)
|
||||||
@@ -147,6 +149,7 @@ class EcoAccountAPISerializerV1(AbstractModelAPISerializerV1,
|
|||||||
obj = self._set_compensation_states(obj, properties["after_states"], obj.after_states)
|
obj = self._set_compensation_states(obj, properties["after_states"], obj.after_states)
|
||||||
obj = self._set_deadlines(obj, properties["deadlines"])
|
obj = self._set_deadlines(obj, properties["deadlines"])
|
||||||
|
|
||||||
|
self._set_external_identifier(obj.id, properties.get("external_identifier", None), obj.created)
|
||||||
obj.log.add(obj.created)
|
obj.log.add(obj.created)
|
||||||
obj.users.add(user)
|
obj.users.add(user)
|
||||||
|
|
||||||
@@ -172,6 +175,10 @@ class EcoAccountAPISerializerV1(AbstractModelAPISerializerV1,
|
|||||||
|
|
||||||
# Fill in data to objects
|
# Fill in data to objects
|
||||||
properties = json_model["properties"]
|
properties = json_model["properties"]
|
||||||
|
|
||||||
|
external_identifier = properties.get("external_identifier", None)
|
||||||
|
self._check_external_identifier_on_entry_creation(external_identifier)
|
||||||
|
|
||||||
obj.title = properties["title"]
|
obj.title = properties["title"]
|
||||||
obj.is_pik = properties.get("is_pik", False)
|
obj.is_pik = properties.get("is_pik", False)
|
||||||
obj.deductable_surface = float(properties["deductable_surface"])
|
obj.deductable_surface = float(properties["deductable_surface"])
|
||||||
@@ -192,6 +199,7 @@ class EcoAccountAPISerializerV1(AbstractModelAPISerializerV1,
|
|||||||
obj = self._set_compensation_states(obj, properties["after_states"], obj.after_states)
|
obj = self._set_compensation_states(obj, properties["after_states"], obj.after_states)
|
||||||
obj = self._set_deadlines(obj, properties["deadlines"])
|
obj = self._set_deadlines(obj, properties["deadlines"])
|
||||||
|
|
||||||
|
self._set_external_identifier(obj.id, external_identifier, update_action)
|
||||||
obj.log.add(update_action)
|
obj.log.add(update_action)
|
||||||
|
|
||||||
celery_update_parcels.delay(obj.geometry.id)
|
celery_update_parcels.delay(obj.geometry.id)
|
||||||
|
|||||||
@@ -104,6 +104,10 @@ class EmaAPISerializerV1(AbstractModelAPISerializerV1, AbstractCompensationAPISe
|
|||||||
|
|
||||||
# Fill in data to objects
|
# Fill in data to objects
|
||||||
properties = json_model["properties"]
|
properties = json_model["properties"]
|
||||||
|
|
||||||
|
external_identifier = properties.get("external_identifier", None)
|
||||||
|
self._check_external_identifier_on_entry_creation(external_identifier)
|
||||||
|
|
||||||
obj.identifier = obj.generate_new_identifier()
|
obj.identifier = obj.generate_new_identifier()
|
||||||
obj.title = properties["title"]
|
obj.title = properties["title"]
|
||||||
obj.is_pik = properties.get("is_pik", False)
|
obj.is_pik = properties.get("is_pik", False)
|
||||||
@@ -119,6 +123,7 @@ class EmaAPISerializerV1(AbstractModelAPISerializerV1, AbstractCompensationAPISe
|
|||||||
obj = self._set_compensation_states(obj, properties["after_states"], obj.after_states)
|
obj = self._set_compensation_states(obj, properties["after_states"], obj.after_states)
|
||||||
obj = self._set_deadlines(obj, properties["deadlines"])
|
obj = self._set_deadlines(obj, properties["deadlines"])
|
||||||
|
|
||||||
|
self._set_external_identifier(obj.id, external_identifier, obj.created)
|
||||||
obj.log.add(obj.created)
|
obj.log.add(obj.created)
|
||||||
obj.users.add(user)
|
obj.users.add(user)
|
||||||
|
|
||||||
@@ -161,6 +166,7 @@ class EmaAPISerializerV1(AbstractModelAPISerializerV1, AbstractCompensationAPISe
|
|||||||
obj = self._set_compensation_states(obj, properties["after_states"], obj.after_states)
|
obj = self._set_compensation_states(obj, properties["after_states"], obj.after_states)
|
||||||
obj = self._set_deadlines(obj, properties["deadlines"])
|
obj = self._set_deadlines(obj, properties["deadlines"])
|
||||||
|
|
||||||
|
self._set_external_identifier(obj.id, properties.get("external_identifier", None), update_action)
|
||||||
obj.log.add(update_action)
|
obj.log.add(update_action)
|
||||||
|
|
||||||
celery_update_parcels.delay(obj.geometry.id)
|
celery_update_parcels.delay(obj.geometry.id)
|
||||||
|
|||||||
@@ -150,10 +150,14 @@ class InterventionAPISerializerV1(AbstractModelAPISerializerV1,
|
|||||||
|
|
||||||
# Fill in data to objects
|
# Fill in data to objects
|
||||||
properties = json_model["properties"]
|
properties = json_model["properties"]
|
||||||
|
|
||||||
|
external_identifier = properties.get("external_identifier", None)
|
||||||
|
self._check_external_identifier_on_entry_creation(external_identifier)
|
||||||
|
|
||||||
obj.identifier = obj.generate_new_identifier()
|
obj.identifier = obj.generate_new_identifier()
|
||||||
obj.title = properties["title"]
|
obj.title = properties.get("title", None)
|
||||||
self._set_responsibility(obj, properties["responsible"])
|
self._set_responsibility(obj, properties.get("responsible", None))
|
||||||
self._set_legal(obj, properties["legal"])
|
self._set_legal(obj, properties.get("legal", None))
|
||||||
|
|
||||||
obj.responsible.handler.save()
|
obj.responsible.handler.save()
|
||||||
obj.responsible.save()
|
obj.responsible.save()
|
||||||
@@ -161,6 +165,7 @@ class InterventionAPISerializerV1(AbstractModelAPISerializerV1,
|
|||||||
obj.legal.save()
|
obj.legal.save()
|
||||||
obj.save()
|
obj.save()
|
||||||
|
|
||||||
|
self._set_external_identifier(obj.id, external_identifier, obj.created)
|
||||||
obj.users.add(user)
|
obj.users.add(user)
|
||||||
obj.log.add(obj.created)
|
obj.log.add(obj.created)
|
||||||
|
|
||||||
@@ -186,7 +191,7 @@ class InterventionAPISerializerV1(AbstractModelAPISerializerV1,
|
|||||||
|
|
||||||
# Fill in data to objects
|
# Fill in data to objects
|
||||||
properties = json_model["properties"]
|
properties = json_model["properties"]
|
||||||
obj.title = properties["title"]
|
obj.title = properties.get("title")
|
||||||
self._set_responsibility(obj, properties.get("responsible", None))
|
self._set_responsibility(obj, properties.get("responsible", None))
|
||||||
self._set_legal(obj, properties.get("legal", None))
|
self._set_legal(obj, properties.get("legal", None))
|
||||||
self._set_payments(obj, properties.get("payments", None))
|
self._set_payments(obj, properties.get("payments", None))
|
||||||
@@ -200,6 +205,7 @@ class InterventionAPISerializerV1(AbstractModelAPISerializerV1,
|
|||||||
obj.save()
|
obj.save()
|
||||||
|
|
||||||
obj.mark_as_edited(user, edit_comment="API update")
|
obj.mark_as_edited(user, edit_comment="API update")
|
||||||
|
self._set_external_identifier(obj.id, properties.get("external_identifier", None), update_action)
|
||||||
obj.send_data_to_egon()
|
obj.send_data_to_egon()
|
||||||
|
|
||||||
celery_update_parcels.delay(obj.geometry.id)
|
celery_update_parcels.delay(obj.geometry.id)
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ from django.contrib.gis.geos import MultiPolygon
|
|||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
from django.db.models import QuerySet
|
from django.db.models import QuerySet
|
||||||
|
|
||||||
|
from api.models import ExternalIdentifier
|
||||||
from api.utils.serializer.serializer import AbstractModelAPISerializer
|
from api.utils.serializer.serializer import AbstractModelAPISerializer
|
||||||
from codelist.models import KonovaCode
|
from codelist.models import KonovaCode
|
||||||
from codelist.settings import CODELIST_COMPENSATION_ACTION_ID, CODELIST_BIOTOPES_ID, CODELIST_PROCESS_TYPE_ID, \
|
from codelist.settings import CODELIST_COMPENSATION_ACTION_ID, CODELIST_BIOTOPES_ID, CODELIST_PROCESS_TYPE_ID, \
|
||||||
@@ -39,12 +40,20 @@ class AbstractModelAPISerializerV1(AbstractModelAPISerializer):
|
|||||||
else:
|
else:
|
||||||
geom = MultiPolygon().geojson
|
geom = MultiPolygon().geojson
|
||||||
geo_json = json.loads(geom)
|
geo_json = json.loads(geom)
|
||||||
|
ext_ids = list(
|
||||||
|
ExternalIdentifier.objects.filter(
|
||||||
|
internal_id=entry.id
|
||||||
|
).values_list(
|
||||||
|
"external_id", flat=True
|
||||||
|
)
|
||||||
|
)
|
||||||
self.properties_data = {
|
self.properties_data = {
|
||||||
"id": entry.id,
|
"id": entry.id,
|
||||||
"identifier": entry.identifier,
|
"identifier": entry.identifier,
|
||||||
"title": entry.title,
|
"title": entry.title,
|
||||||
"created_on": self._created_on_to_json(entry),
|
"created_on": self._created_on_to_json(entry),
|
||||||
"modified_on": self._modified_on_to_json(entry),
|
"modified_on": self._modified_on_to_json(entry),
|
||||||
|
"external_identifiers": ext_ids,
|
||||||
}
|
}
|
||||||
self._extend_properties_data(entry)
|
self._extend_properties_data(entry)
|
||||||
geo_json["properties"] = self.properties_data
|
geo_json["properties"] = self.properties_data
|
||||||
@@ -137,6 +146,57 @@ class AbstractModelAPISerializerV1(AbstractModelAPISerializer):
|
|||||||
success = entry.deleted is not None
|
success = entry.deleted is not None
|
||||||
return success
|
return success
|
||||||
|
|
||||||
|
def _set_external_identifier(self, internal_identifier, external_identifier, log_entry):
|
||||||
|
""" If an external identifier was provided in the payload, we set it
|
||||||
|
in the database
|
||||||
|
|
||||||
|
Args:
|
||||||
|
internal_identifier (BaseObject): The already processed konova object (EIV, KOM, ...)
|
||||||
|
external_identifier (any): The external identifier taken from the payload
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
if external_identifier is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
ext_id_obj = ExternalIdentifier.objects.get_or_create(
|
||||||
|
internal_id=internal_identifier,
|
||||||
|
external_id=external_identifier
|
||||||
|
)[0]
|
||||||
|
if not ext_id_obj.created:
|
||||||
|
ext_id_obj.created = log_entry
|
||||||
|
ext_id_obj.save()
|
||||||
|
|
||||||
|
return ext_id_obj
|
||||||
|
|
||||||
|
def _get_external_identifier(self, external_identifier):
|
||||||
|
""" Checks whether a linkage based on an external identifier already exists and returns it if so.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
external_identifier (any): The external identifier according to payload
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
ExternalIdentifier | None
|
||||||
|
"""
|
||||||
|
return ExternalIdentifier.resolve_external_identifier(external_identifier)
|
||||||
|
|
||||||
|
def _check_external_identifier_on_entry_creation(self, external_identifier):
|
||||||
|
""" Special check for POST processing:
|
||||||
|
Checks whether an external identifier already exists on the database. This hints that
|
||||||
|
the entry already has been created in the past. Instead of POST, the PUT method shall be used
|
||||||
|
to avoid creating duplicates.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
external_identifier (any): The external identifier according to payload
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
persisted_external_identifier = self._get_external_identifier(external_identifier)
|
||||||
|
if persisted_external_identifier:
|
||||||
|
raise AssertionError(f"{external_identifier} has already been initially created! Use PUT for updates!")
|
||||||
|
|
||||||
|
|
||||||
class DeductableAPISerializerV1Mixin:
|
class DeductableAPISerializerV1Mixin:
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
"""
|
|
||||||
Author: Michel Peltriaux
|
|
||||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
|
||||||
Contact: michel.peltriaux@sgdnord.rlp.de
|
|
||||||
Created on: 27.01.22
|
|
||||||
|
|
||||||
"""
|
|
||||||
from django.contrib.auth.decorators import login_required
|
|
||||||
from django.http import HttpRequest, JsonResponse
|
|
||||||
|
|
||||||
from api.models import APIUserToken
|
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
|
||||||
def generate_new_token_view(request: HttpRequest):
|
|
||||||
""" Handles request for fetching
|
|
||||||
|
|
||||||
Args:
|
|
||||||
request (HttpRequest): The incoming request
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
if request.method == "GET":
|
|
||||||
token = APIUserToken()
|
|
||||||
while APIUserToken.objects.filter(token=token.token).exists():
|
|
||||||
token = APIUserToken()
|
|
||||||
return JsonResponse(
|
|
||||||
data={
|
|
||||||
"gen_data": token.token
|
|
||||||
}
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
raise NotImplementedError
|
|
||||||
@@ -6,7 +6,9 @@ Created on: 21.01.22
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
import json
|
import json
|
||||||
|
from json import JSONDecodeError
|
||||||
|
|
||||||
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
from django.http import JsonResponse, HttpRequest
|
from django.http import JsonResponse, HttpRequest
|
||||||
|
|
||||||
from api.utils.serializer.v1.compensation import CompensationAPISerializerV1
|
from api.utils.serializer.v1.compensation import CompensationAPISerializerV1
|
||||||
@@ -66,8 +68,12 @@ class AbstractAPIViewV1(AbstractAPIView):
|
|||||||
body = request.body.decode("utf-8")
|
body = request.body.decode("utf-8")
|
||||||
body = json.loads(body)
|
body = json.loads(body)
|
||||||
created_id = self.serializer.create_model_from_json(body, self.user)
|
created_id = self.serializer.create_model_from_json(body, self.user)
|
||||||
except Exception as e:
|
except (JSONDecodeError,
|
||||||
return self._return_error_response(e, 500)
|
AssertionError,
|
||||||
|
ValueError,
|
||||||
|
PermissionError,
|
||||||
|
ObjectDoesNotExist) as e:
|
||||||
|
return self._return_error_response(e, 400)
|
||||||
return JsonResponse({"id": created_id})
|
return JsonResponse({"id": created_id})
|
||||||
|
|
||||||
def put(self, request: HttpRequest, id=None):
|
def put(self, request: HttpRequest, id=None):
|
||||||
|
|||||||
+69
-22
@@ -6,13 +6,14 @@ Created on: 21.01.22
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
import json
|
import json
|
||||||
|
import uuid
|
||||||
|
|
||||||
from django.db.models import QuerySet
|
from django.db.models import QuerySet, Q
|
||||||
from django.http import JsonResponse, HttpRequest
|
from django.http import JsonResponse, HttpRequest
|
||||||
from django.views import View
|
from django.views import View
|
||||||
from django.views.decorators.csrf import csrf_exempt
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
|
|
||||||
from api.models import APIUserToken
|
from api.models import APIUserToken, ExternalIdentifier
|
||||||
from api.settings import KSP_TOKEN_HEADER_IDENTIFIER, KSP_USER_HEADER_IDENTIFIER
|
from api.settings import KSP_TOKEN_HEADER_IDENTIFIER, KSP_USER_HEADER_IDENTIFIER
|
||||||
from compensation.models import EcoAccount
|
from compensation.models import EcoAccount
|
||||||
from ema.models import Ema
|
from ema.models import Ema
|
||||||
@@ -81,9 +82,7 @@ class AbstractAPIView(View):
|
|||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
content = [error.__str__()]
|
content = [f"{error.__class__.__name__}: {str(error)}"]
|
||||||
if hasattr(error, "messages"):
|
|
||||||
content = error.messages
|
|
||||||
return JsonResponse(
|
return JsonResponse(
|
||||||
{
|
{
|
||||||
"errors": content
|
"errors": content
|
||||||
@@ -207,6 +206,10 @@ class AbstractModelShareAPIView(AbstractAPIView):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
|
external_identifier = ExternalIdentifier.resolve_external_identifier(id)
|
||||||
|
if external_identifier:
|
||||||
|
id = external_identifier.internal_id
|
||||||
|
|
||||||
users = self._get_shared_users_of_object(id)
|
users = self._get_shared_users_of_object(id)
|
||||||
teams = self._get_shared_teams_of_object(id)
|
teams = self._get_shared_teams_of_object(id)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -239,6 +242,9 @@ class AbstractModelShareAPIView(AbstractAPIView):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
external_identifier = ExternalIdentifier.resolve_external_identifier(id)
|
||||||
|
if external_identifier:
|
||||||
|
id = external_identifier.internal_id
|
||||||
success = self._process_put_body(request.body, id)
|
success = self._process_put_body(request.body, id)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return self._return_error_response(e)
|
return self._return_error_response(e)
|
||||||
@@ -311,22 +317,36 @@ class AbstractModelShareAPIView(AbstractAPIView):
|
|||||||
raise ValueError("Shared user list must not be empty!")
|
raise ValueError("Shared user list must not be empty!")
|
||||||
new_teams = content.get("teams", [])
|
new_teams = content.get("teams", [])
|
||||||
|
|
||||||
|
self.__process_user_sharing(new_users, obj)
|
||||||
|
self.__process_team_sharing(new_teams, obj)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def __process_user_sharing(self, user_list: list, obj):
|
||||||
|
""" Processes API sharing for user payload
|
||||||
|
|
||||||
|
Args:
|
||||||
|
user_list (list): A list of users to share the obj with
|
||||||
|
obj (BaseObject): The shareable object
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
# Eliminate duplicates
|
# Eliminate duplicates
|
||||||
new_users = list(dict.fromkeys(new_users))
|
new_users = list(dict.fromkeys(user_list))
|
||||||
new_teams = list(dict.fromkeys(new_teams))
|
|
||||||
|
|
||||||
# Make sure each of these names exist as a user
|
# Make sure each of these names exist as a user
|
||||||
new_users_objs = []
|
new_users_objs = []
|
||||||
for user in new_users:
|
for user in new_users:
|
||||||
new_users_objs.append(User.objects.get(username=user))
|
try:
|
||||||
|
user_obj = User.objects.get(username=user)
|
||||||
# Make sure each of these names exist as a user
|
except User.DoesNotExist:
|
||||||
new_teams_objs = []
|
raise AssertionError(f"User with username {user} does not exist")
|
||||||
for team_name in new_teams:
|
new_users_objs.append(user_obj)
|
||||||
new_teams_objs.append(Team.objects.get(name=team_name))
|
|
||||||
|
|
||||||
if self.user.is_default_group_only():
|
if self.user.is_default_group_only():
|
||||||
# Default only users are not allowed to remove other users from having access. They can only add new ones!
|
# Default only users are not allowed to remove other users from having access. They can only add new ones!
|
||||||
|
# So we need to keep the ones that already have access from being removed!
|
||||||
new_users_to_be_added = User.objects.filter(
|
new_users_to_be_added = User.objects.filter(
|
||||||
username__in=new_users
|
username__in=new_users
|
||||||
).exclude(
|
).exclude(
|
||||||
@@ -334,17 +354,44 @@ class AbstractModelShareAPIView(AbstractAPIView):
|
|||||||
)
|
)
|
||||||
new_users_objs = obj.shared_users.union(new_users_to_be_added)
|
new_users_objs = obj.shared_users.union(new_users_to_be_added)
|
||||||
|
|
||||||
new_teams_to_be_added = Team.objects.filter(
|
|
||||||
name__in=new_teams
|
|
||||||
).exclude(
|
|
||||||
id__in=obj.shared_teams
|
|
||||||
)
|
|
||||||
new_teams_objs = obj.shared_teams.union(new_teams_to_be_added)
|
|
||||||
|
|
||||||
obj.share_with_user_list(new_users_objs)
|
obj.share_with_user_list(new_users_objs)
|
||||||
obj.share_with_team_list(new_teams_objs)
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
def __process_team_sharing(self, team_list: list, obj):
|
||||||
|
""" Processes API sharing for team payload
|
||||||
|
|
||||||
|
Args:
|
||||||
|
team_list (list): A list of teams to share the obj with
|
||||||
|
obj (BaseObject): The shareable object
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
# Eliminate duplicates
|
||||||
|
new_teams = list(dict.fromkeys(team_list))
|
||||||
|
|
||||||
|
# Resolve team names or ids into objects
|
||||||
|
new_team_ids = []
|
||||||
|
for team in new_teams:
|
||||||
|
try:
|
||||||
|
uuid.UUID(team)
|
||||||
|
try:
|
||||||
|
new_team_ids.append(Team.objects.get(id=team).id)
|
||||||
|
except Team.DoesNotExist:
|
||||||
|
raise AssertionError(f"Team with id {team} does not exist!")
|
||||||
|
except ValueError:
|
||||||
|
# entry is a name and not a uuid -> try to resolve as name!
|
||||||
|
try:
|
||||||
|
new_team_ids.append(Team.objects.get(name=team).id)
|
||||||
|
except Team.DoesNotExist:
|
||||||
|
raise AssertionError(f"Team with name {team} does not exist!")
|
||||||
|
|
||||||
|
new_team_objs = Team.objects.filter(id__in=new_team_ids)
|
||||||
|
if self.user.is_default_group_only():
|
||||||
|
# Default only users are not allowed to remove other users from having access. They can only add new ones!
|
||||||
|
# So we need to keep the ones that already have access from being removed!
|
||||||
|
new_team_objs = obj.shared_teams.union(new_team_objs)
|
||||||
|
|
||||||
|
obj.share_with_team_list(new_team_objs)
|
||||||
|
|
||||||
class InterventionAPIShareView(AbstractModelShareAPIView):
|
class InterventionAPIShareView(AbstractModelShareAPIView):
|
||||||
model = Intervention
|
model = Intervention
|
||||||
|
|||||||
@@ -45,6 +45,14 @@ class AbstractCompensationAdmin(BaseObjectAdmin):
|
|||||||
states = "\n".join(states)
|
states = "\n".join(states)
|
||||||
return states
|
return states
|
||||||
|
|
||||||
|
def get_actions(self, request):
|
||||||
|
DELETE_ACTION_IDENTIFIER = "delete_selected"
|
||||||
|
actions = super().get_actions(request)
|
||||||
|
|
||||||
|
if DELETE_ACTION_IDENTIFIER in actions:
|
||||||
|
del actions[DELETE_ACTION_IDENTIFIER]
|
||||||
|
|
||||||
|
return actions
|
||||||
|
|
||||||
class CompensationAdmin(AbstractCompensationAdmin):
|
class CompensationAdmin(AbstractCompensationAdmin):
|
||||||
autocomplete_fields = [
|
autocomplete_fields = [
|
||||||
|
|||||||
@@ -168,17 +168,6 @@ class NewCompensationForm(AbstractCompensationForm,
|
|||||||
comp.log.add(action)
|
comp.log.add(action)
|
||||||
return comp, action
|
return comp, action
|
||||||
|
|
||||||
def is_valid(self):
|
|
||||||
valid = super().is_valid()
|
|
||||||
intervention = self.cleaned_data.get("intervention", None)
|
|
||||||
if intervention.is_recorded:
|
|
||||||
valid &= False
|
|
||||||
self.add_error(
|
|
||||||
"intervention",
|
|
||||||
_("This intervention is currently recorded. You cannot add further compensations as long as it is recorded.")
|
|
||||||
)
|
|
||||||
return valid
|
|
||||||
|
|
||||||
def save(self, user: User, geom_form: SimpleGeomForm):
|
def save(self, user: User, geom_form: SimpleGeomForm):
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
comp, action = self.__create_comp(user)
|
comp, action = self.__create_comp(user)
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ from compensation.models import EcoAccount
|
|||||||
from intervention.models import Handler, Responsibility, Legal
|
from intervention.models import Handler, Responsibility, Legal
|
||||||
from konova.forms import SimpleGeomForm
|
from konova.forms import SimpleGeomForm
|
||||||
from konova.forms.modals import RemoveModalForm
|
from konova.forms.modals import RemoveModalForm
|
||||||
|
from konova.settings import ETS_GROUP
|
||||||
from konova.utils import validators
|
from konova.utils import validators
|
||||||
from user.models import User, UserActionLogEntry
|
from user.models import User, UserActionLogEntry
|
||||||
|
|
||||||
@@ -246,4 +247,13 @@ class RemoveEcoAccountModalForm(RemoveModalForm):
|
|||||||
"confirm",
|
"confirm",
|
||||||
_("The account can not be removed, since there are still deductions.")
|
_("The account can not be removed, since there are still deductions.")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# If there are deductions but the performing user is not part of an ETS group, we assume this poor
|
||||||
|
# fella does not know what he/she does -> give a hint that they should contact someone in charge...
|
||||||
|
user_is_ets_user = self.user.in_group(ETS_GROUP)
|
||||||
|
if not user_is_ets_user:
|
||||||
|
self.add_error(
|
||||||
|
"confirm",
|
||||||
|
_("Please contact the responsible conservation office to find a solution!")
|
||||||
|
)
|
||||||
return super_valid and not has_deductions
|
return super_valid and not has_deductions
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ class CompensationTable(BaseTable, TableRenderMixin, TableOrderMixin):
|
|||||||
html += self.render_previously_checked_star(
|
html += self.render_previously_checked_star(
|
||||||
tooltip=tooltip,
|
tooltip=tooltip,
|
||||||
)
|
)
|
||||||
return format_html(html)
|
return format_html(html, None)
|
||||||
|
|
||||||
def render_r(self, value, record: Compensation):
|
def render_r(self, value, record: Compensation):
|
||||||
""" Renders the registered column for a compensation
|
""" Renders the registered column for a compensation
|
||||||
@@ -146,5 +146,5 @@ class CompensationTable(BaseTable, TableRenderMixin, TableOrderMixin):
|
|||||||
tooltip=tooltip,
|
tooltip=tooltip,
|
||||||
icn_filled=recorded,
|
icn_filled=recorded,
|
||||||
)
|
)
|
||||||
return format_html(html)
|
return format_html(html, None)
|
||||||
|
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ class EcoAccountTable(BaseTable, TableRenderMixin, TableOrderMixin):
|
|||||||
txt=value,
|
txt=value,
|
||||||
new_tab=False,
|
new_tab=False,
|
||||||
)
|
)
|
||||||
return format_html(html)
|
return format_html(html, None)
|
||||||
|
|
||||||
def render_av(self, value, record: EcoAccount):
|
def render_av(self, value, record: EcoAccount):
|
||||||
""" Renders the available column for an eco account
|
""" Renders the available column for an eco account
|
||||||
@@ -113,7 +113,7 @@ class EcoAccountTable(BaseTable, TableRenderMixin, TableOrderMixin):
|
|||||||
value_relative = 0
|
value_relative = 0
|
||||||
html = render_to_string("konova/widgets/progressbar.html", {"value": value_relative})
|
html = render_to_string("konova/widgets/progressbar.html", {"value": value_relative})
|
||||||
html += f"{number_format(record.deductable_rest, decimal_pos=2)} m²"
|
html += f"{number_format(record.deductable_rest, decimal_pos=2)} m²"
|
||||||
return format_html(html)
|
return format_html(html, None)
|
||||||
|
|
||||||
def render_r(self, value, record: EcoAccount):
|
def render_r(self, value, record: EcoAccount):
|
||||||
""" Renders the recorded column for an eco account
|
""" Renders the recorded column for an eco account
|
||||||
@@ -135,4 +135,4 @@ class EcoAccountTable(BaseTable, TableRenderMixin, TableOrderMixin):
|
|||||||
tooltip=tooltip,
|
tooltip=tooltip,
|
||||||
icn_filled=checked,
|
icn_filled=checked,
|
||||||
)
|
)
|
||||||
return format_html(html)
|
return format_html(html, None)
|
||||||
|
|||||||
@@ -53,7 +53,7 @@
|
|||||||
</td>
|
</td>
|
||||||
<td class="align-middle">
|
<td class="align-middle">
|
||||||
{% if deduction.intervention.recorded %}
|
{% if deduction.intervention.recorded %}
|
||||||
<em title="{% trans 'Recorded on' %} {{obj.recorded.timestamp}} {% trans 'by' %} {{obj.recorded.user}}" class='fas fa-bookmark registered-bookmark'></em>
|
<em title="{% trans 'Recorded on' %} {{deduction.intervention.recorded.timestamp}} {% trans 'by' %} {{deduction.intervention.recorded.user}}" class='fas fa-bookmark registered-bookmark'></em>
|
||||||
{% else %}
|
{% else %}
|
||||||
<em title="{% trans 'Not recorded yet' %}" class='far fa-bookmark'></em>
|
<em title="{% trans 'Not recorded yet' %}" class='far fa-bookmark'></em>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -7,31 +7,32 @@ Created on: 24.08.21
|
|||||||
"""
|
"""
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
|
from compensation.views.compensation.detail import DetailCompensationView
|
||||||
from compensation.views.compensation.document import EditCompensationDocumentView, NewCompensationDocumentView, \
|
from compensation.views.compensation.document import EditCompensationDocumentView, NewCompensationDocumentView, \
|
||||||
GetCompensationDocumentView, RemoveCompensationDocumentView
|
GetCompensationDocumentView, RemoveCompensationDocumentView
|
||||||
|
from compensation.views.compensation.remove import RemoveCompensationView
|
||||||
from compensation.views.compensation.resubmission import CompensationResubmissionView
|
from compensation.views.compensation.resubmission import CompensationResubmissionView
|
||||||
from compensation.views.compensation.report import CompensationReportView
|
from compensation.views.compensation.report import CompensationPublicReportView
|
||||||
from compensation.views.compensation.deadline import NewCompensationDeadlineView, EditCompensationDeadlineView, \
|
from compensation.views.compensation.deadline import NewCompensationDeadlineView, EditCompensationDeadlineView, \
|
||||||
RemoveCompensationDeadlineView
|
RemoveCompensationDeadlineView
|
||||||
from compensation.views.compensation.action import NewCompensationActionView, EditCompensationActionView, \
|
from compensation.views.compensation.action import NewCompensationActionView, EditCompensationActionView, \
|
||||||
RemoveCompensationActionView
|
RemoveCompensationActionView
|
||||||
from compensation.views.compensation.state import NewCompensationStateView, EditCompensationStateView, \
|
from compensation.views.compensation.state import NewCompensationStateView, EditCompensationStateView, \
|
||||||
RemoveCompensationStateView
|
RemoveCompensationStateView
|
||||||
from compensation.views.compensation.compensation import \
|
from compensation.views.compensation.compensation import IndexCompensationView, CompensationIdentifierGeneratorView, \
|
||||||
remove_view, CompensationIndexView, CompensationIdentifierGeneratorView, CompensationDetailView, \
|
EditCompensationView, NewCompensationView
|
||||||
NewCompensationFormView, EditCompensationFormView
|
|
||||||
from compensation.views.compensation.log import CompensationLogView
|
from compensation.views.compensation.log import CompensationLogView
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
# Main compensation
|
# Main compensation
|
||||||
path("", CompensationIndexView.as_view(), name="index"),
|
path("", IndexCompensationView.as_view(), name="index"),
|
||||||
path('new/id', CompensationIdentifierGeneratorView.as_view(), name='new-id'),
|
path('new/id', CompensationIdentifierGeneratorView.as_view(), name='new-id'),
|
||||||
path('new/<intervention_id>', NewCompensationFormView.as_view(), name='new'),
|
path('new/<intervention_id>', NewCompensationView.as_view(), name='new'),
|
||||||
path('new', NewCompensationFormView.as_view(), name='new'),
|
path('new', NewCompensationView.as_view(), name='new'),
|
||||||
path('<id>', CompensationDetailView.as_view(), name='detail'),
|
path('<id>', DetailCompensationView.as_view(), name='detail'),
|
||||||
path('<id>/log', CompensationLogView.as_view(), name='log'),
|
path('<id>/log', CompensationLogView.as_view(), name='log'),
|
||||||
path('<id>/edit', EditCompensationFormView.as_view(), name='edit'),
|
path('<id>/edit', EditCompensationView.as_view(), name='edit'),
|
||||||
path('<id>/remove', remove_view, name='remove'),
|
path('<id>/remove', RemoveCompensationView.as_view(), name='remove'),
|
||||||
|
|
||||||
path('<id>/state/new', NewCompensationStateView.as_view(), name='new-state'),
|
path('<id>/state/new', NewCompensationStateView.as_view(), name='new-state'),
|
||||||
path('<id>/state/<state_id>/edit', EditCompensationStateView.as_view(), name='state-edit'),
|
path('<id>/state/<state_id>/edit', EditCompensationStateView.as_view(), name='state-edit'),
|
||||||
@@ -44,7 +45,7 @@ urlpatterns = [
|
|||||||
path('<id>/deadline/new', NewCompensationDeadlineView.as_view(), name="new-deadline"),
|
path('<id>/deadline/new', NewCompensationDeadlineView.as_view(), name="new-deadline"),
|
||||||
path('<id>/deadline/<deadline_id>/edit', EditCompensationDeadlineView.as_view(), name='deadline-edit'),
|
path('<id>/deadline/<deadline_id>/edit', EditCompensationDeadlineView.as_view(), name='deadline-edit'),
|
||||||
path('<id>/deadline/<deadline_id>/remove', RemoveCompensationDeadlineView.as_view(), name='deadline-remove'),
|
path('<id>/deadline/<deadline_id>/remove', RemoveCompensationDeadlineView.as_view(), name='deadline-remove'),
|
||||||
path('<id>/report', CompensationReportView.as_view(), name='report'),
|
path('<id>/report', CompensationPublicReportView.as_view(), name='report'),
|
||||||
path('<id>/resub', CompensationResubmissionView.as_view(), name='resubmission-create'),
|
path('<id>/resub', CompensationResubmissionView.as_view(), name='resubmission-create'),
|
||||||
|
|
||||||
# Documents
|
# Documents
|
||||||
|
|||||||
@@ -8,12 +8,13 @@ Created on: 24.08.21
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from compensation.autocomplete.eco_account import EcoAccountAutocomplete
|
from compensation.autocomplete.eco_account import EcoAccountAutocomplete
|
||||||
from compensation.views.eco_account.eco_account import remove_view, \
|
from compensation.views.eco_account.detail import DetailEcoAccountView
|
||||||
EcoAccountIndexView, EcoAccountIdentifierGeneratorView, EcoAccountDetailView, NewEcoAccountFormView, \
|
from compensation.views.eco_account.eco_account import IndexEcoAccountView, EcoAccountIdentifierGeneratorView, \
|
||||||
EditEcoAccountFormView
|
NewEcoAccountView, EditEcoAccountView
|
||||||
from compensation.views.eco_account.log import EcoAccountLogView
|
from compensation.views.eco_account.log import EcoAccountLogView
|
||||||
from compensation.views.eco_account.record import EcoAccountRecordView
|
from compensation.views.eco_account.record import EcoAccountRecordView
|
||||||
from compensation.views.eco_account.report import EcoAccountReportView
|
from compensation.views.eco_account.remove import RemoveEcoAccountView
|
||||||
|
from compensation.views.eco_account.report import EcoAccountPublicReportView
|
||||||
from compensation.views.eco_account.resubmission import EcoAccountResubmissionView
|
from compensation.views.eco_account.resubmission import EcoAccountResubmissionView
|
||||||
from compensation.views.eco_account.state import NewEcoAccountStateView, EditEcoAccountStateView, \
|
from compensation.views.eco_account.state import NewEcoAccountStateView, EditEcoAccountStateView, \
|
||||||
RemoveEcoAccountStateView
|
RemoveEcoAccountStateView
|
||||||
@@ -29,15 +30,15 @@ from compensation.views.eco_account.deduction import NewEcoAccountDeductionView,
|
|||||||
|
|
||||||
app_name = "acc"
|
app_name = "acc"
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("", EcoAccountIndexView.as_view(), name="index"),
|
path("", IndexEcoAccountView.as_view(), name="index"),
|
||||||
path('new/', NewEcoAccountFormView.as_view(), name='new'),
|
path('new/', NewEcoAccountView.as_view(), name='new'),
|
||||||
path('new/id', EcoAccountIdentifierGeneratorView.as_view(), name='new-id'),
|
path('new/id', EcoAccountIdentifierGeneratorView.as_view(), name='new-id'),
|
||||||
path('<id>', EcoAccountDetailView.as_view(), name='detail'),
|
path('<id>', DetailEcoAccountView.as_view(), name='detail'),
|
||||||
path('<id>/log', EcoAccountLogView.as_view(), name='log'),
|
path('<id>/log', EcoAccountLogView.as_view(), name='log'),
|
||||||
path('<id>/record', EcoAccountRecordView.as_view(), name='record'),
|
path('<id>/record', EcoAccountRecordView.as_view(), name='record'),
|
||||||
path('<id>/report', EcoAccountReportView.as_view(), name='report'),
|
path('<id>/report', EcoAccountPublicReportView.as_view(), name='report'),
|
||||||
path('<id>/edit', EditEcoAccountFormView.as_view(), name='edit'),
|
path('<id>/edit', EditEcoAccountView.as_view(), name='edit'),
|
||||||
path('<id>/remove', remove_view, name='remove'),
|
path('<id>/remove', RemoveEcoAccountView.as_view(), name='remove'),
|
||||||
path('<id>/resub', EcoAccountResubmissionView.as_view(), name='resubmission-create'),
|
path('<id>/resub', EcoAccountResubmissionView.as_view(), name='resubmission-create'),
|
||||||
|
|
||||||
path('<id>/state/new', NewEcoAccountStateView.as_view(), name='new-state'),
|
path('<id>/state/new', NewEcoAccountStateView.as_view(), name='new-state'),
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ from compensation.views.payment import *
|
|||||||
|
|
||||||
app_name = "pay"
|
app_name = "pay"
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('<id>/new', new_payment_view, name='new'),
|
path('<id>/new', NewPaymentView.as_view(), name='new'),
|
||||||
path('<id>/remove/<payment_id>', payment_remove_view, name='remove'),
|
path('<id>/remove/<payment_id>', RemovePaymentView.as_view(), name='remove'),
|
||||||
path('<id>/edit/<payment_id>', payment_edit_view, name='edit'),
|
path('<id>/edit/<payment_id>', EditPaymentView.as_view(), name='edit'),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -6,183 +6,260 @@ Created on: 19.08.22
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.contrib.auth.decorators import login_required
|
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.http import HttpRequest, HttpResponse
|
||||||
from django.http import HttpRequest, JsonResponse
|
|
||||||
from django.shortcuts import get_object_or_404, render, redirect
|
from django.shortcuts import get_object_or_404, render, redirect
|
||||||
from django.urls import reverse
|
from django.utils.decorators import method_decorator
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from django.views import View
|
||||||
|
|
||||||
from compensation.forms.compensation import EditCompensationForm, NewCompensationForm
|
from compensation.forms.compensation import EditCompensationForm, NewCompensationForm
|
||||||
from compensation.models import Compensation
|
from compensation.models import Compensation
|
||||||
from compensation.tables.compensation import CompensationTable
|
from compensation.tables.compensation import CompensationTable
|
||||||
from intervention.models import Intervention
|
from intervention.models import Intervention
|
||||||
from konova.decorators import shared_access_required, default_group_required, login_required_modal
|
from konova.contexts import BaseContext
|
||||||
from konova.forms.modals import RemoveModalForm
|
from konova.decorators import shared_access_required, default_group_required
|
||||||
from konova.utils.message_templates import COMPENSATION_REMOVED_TEMPLATE, DATA_CHECKED_PREVIOUSLY_TEMPLATE, \
|
from konova.forms import SimpleGeomForm
|
||||||
RECORDED_BLOCKS_EDIT, PARAMS_INVALID
|
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
|
||||||
from konova.views.base import BaseIndexView, BaseIdentifierGeneratorView, BaseNewSpatialLocatedObjectFormView, \
|
from konova.utils.message_templates import RECORDED_BLOCKS_EDIT, CHECK_STATE_RESET, FORM_INVALID, PARAMS_INVALID, \
|
||||||
BaseEditSpatialLocatedObjectFormView
|
IDENTIFIER_REPLACED, COMPENSATION_ADDED_TEMPLATE, GEOMETRY_SIMPLIFIED, GEOMETRIES_IGNORED_TEMPLATE
|
||||||
from konova.views.detail import BaseDetailView
|
from konova.views.identifier import AbstractIdentifierGeneratorView
|
||||||
|
from konova.views.index import AbstractIndexView
|
||||||
|
|
||||||
|
|
||||||
class CompensationIndexView(LoginRequiredMixin, BaseIndexView):
|
class IndexCompensationView(AbstractIndexView):
|
||||||
_TAB_TITLE = _("Compensations - Overview")
|
def get(self, request, *args, **kwargs) -> HttpResponse:
|
||||||
_INDEX_TABLE_CLS = CompensationTable
|
"""
|
||||||
|
Renders the index view for compensation
|
||||||
|
|
||||||
def _get_queryset(self):
|
Args:
|
||||||
qs = Compensation.objects.filter(
|
request (HttpRequest): The incoming request
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A rendered view
|
||||||
|
"""
|
||||||
|
compensations = Compensation.objects.filter(
|
||||||
deleted=None, # only show those which are not deleted individually
|
deleted=None, # only show those which are not deleted individually
|
||||||
intervention__deleted=None, # and don't show the ones whose intervention has been deleted
|
intervention__deleted=None, # and don't show the ones whose intervention has been deleted
|
||||||
).order_by(
|
).order_by(
|
||||||
"-modified__timestamp"
|
"-modified__timestamp"
|
||||||
)
|
)
|
||||||
return qs
|
table = CompensationTable(
|
||||||
|
request=request,
|
||||||
|
queryset=compensations
|
||||||
class NewCompensationFormView(BaseNewSpatialLocatedObjectFormView):
|
|
||||||
_FORM_CLS = NewCompensationForm
|
|
||||||
_MODEL_CLS = Compensation
|
|
||||||
_TEMPLATE = "compensation/form/view.html"
|
|
||||||
_TAB_TITLE = _("New Compensation")
|
|
||||||
_REDIRECT_URL = "compensation:detail"
|
|
||||||
|
|
||||||
def _user_has_shared_access(self, user, **kwargs):
|
|
||||||
# On a new compensation make sure the intervention (if call came directly through an intervention's detail
|
|
||||||
# view) is shared with the user
|
|
||||||
intervention_id = kwargs.get("intervention_id", None)
|
|
||||||
if not intervention_id:
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
intervention = get_object_or_404(Intervention, id=intervention_id)
|
|
||||||
return intervention.is_shared_with(user)
|
|
||||||
|
|
||||||
def _user_has_permission(self, user):
|
|
||||||
# User has to be an ets user
|
|
||||||
return user.is_default_user()
|
|
||||||
|
|
||||||
def dispatch(self, request, *args, **kwargs):
|
|
||||||
# Make sure there is an existing intervention based on the given id
|
|
||||||
# Compensations can not exist without an intervention
|
|
||||||
intervention_id = kwargs.get("intervention_id", None)
|
|
||||||
if intervention_id:
|
|
||||||
try:
|
|
||||||
intervention = Intervention.objects.get(id=intervention_id)
|
|
||||||
if intervention.is_recorded:
|
|
||||||
messages.info(
|
|
||||||
request,
|
|
||||||
RECORDED_BLOCKS_EDIT
|
|
||||||
)
|
|
||||||
return redirect("intervention:detail", id=intervention_id)
|
|
||||||
except ObjectDoesNotExist:
|
|
||||||
messages.error(request, PARAMS_INVALID, extra_tags="danger")
|
|
||||||
return redirect("home")
|
|
||||||
return super().dispatch(request, *args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class EditCompensationFormView(BaseEditSpatialLocatedObjectFormView):
|
|
||||||
_MODEL_CLS = Compensation
|
|
||||||
_FORM_CLS = EditCompensationForm
|
|
||||||
_TEMPLATE = "compensation/form/view.html"
|
|
||||||
_REDIRECT_URL = "compensation:detail"
|
|
||||||
|
|
||||||
def _user_has_permission(self, user):
|
|
||||||
# User has to be a default user
|
|
||||||
return user.is_default_user()
|
|
||||||
|
|
||||||
|
|
||||||
class CompensationIdentifierGeneratorView(LoginRequiredMixin, BaseIdentifierGeneratorView):
|
|
||||||
_MODEL_CLS = Compensation
|
|
||||||
_REDIRECT_URL = "compensation:index"
|
|
||||||
|
|
||||||
|
|
||||||
class CompensationDetailView(BaseDetailView):
|
|
||||||
_MODEL_CLS = Compensation
|
|
||||||
_TEMPLATE = "compensation/detail/compensation/view.html"
|
|
||||||
|
|
||||||
def _get_object(self, id: str):
|
|
||||||
""" Returns the compensation
|
|
||||||
|
|
||||||
Args:
|
|
||||||
id (str): The compensation's id
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
obj (Compensation): The compensation
|
|
||||||
"""
|
|
||||||
comp = get_object_or_404(
|
|
||||||
Compensation.objects.select_related(
|
|
||||||
"modified",
|
|
||||||
"created",
|
|
||||||
"geometry"
|
|
||||||
),
|
|
||||||
id=id,
|
|
||||||
deleted=None,
|
|
||||||
intervention__deleted=None,
|
|
||||||
)
|
)
|
||||||
return comp
|
context = {
|
||||||
|
"table": table,
|
||||||
|
TAB_TITLE_IDENTIFIER: _("Compensations - Overview"),
|
||||||
|
}
|
||||||
|
context = BaseContext(request, context).context
|
||||||
|
return render(request, self._TEMPLATE, context)
|
||||||
|
|
||||||
def _get_detail_context(self, obj: Compensation):
|
|
||||||
""" Generate object specific detail context for view
|
class NewCompensationView(LoginRequiredMixin, View):
|
||||||
|
_TEMPLATE = "compensation/form/view.html"
|
||||||
|
|
||||||
|
@method_decorator(default_group_required)
|
||||||
|
@method_decorator(shared_access_required(Intervention, "intervention_id"))
|
||||||
|
def get(self, request: HttpRequest, intervention_id: str = None, *args, **kwargs) -> HttpResponse:
|
||||||
|
"""
|
||||||
|
Renders a view for new compensation
|
||||||
|
|
||||||
|
A compensation creation may be called directly from the parent-intervention object. If so - we may take
|
||||||
|
the intervention's id and directly link the compensation to it.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
obj (): The record
|
request (HttpRequest): The incoming request
|
||||||
|
intervention_id (str): The intervention identifier
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# Order states according to surface
|
if intervention_id:
|
||||||
before_states = obj.before_states.all().prefetch_related("biotope_type").order_by("-surface")
|
# If the parent-intervention is recorded, we are not allowed to change anything on it's data.
|
||||||
after_states = obj.after_states.all().prefetch_related("biotope_type").order_by("-surface")
|
# Not even adding new child elements like compensations!
|
||||||
actions = obj.actions.all().prefetch_related("action_type")
|
intervention = get_object_or_404(Intervention, id=intervention_id)
|
||||||
|
recording_state_blocks_actions = intervention.is_recorded
|
||||||
|
if recording_state_blocks_actions:
|
||||||
|
messages.info(
|
||||||
|
request,
|
||||||
|
RECORDED_BLOCKS_EDIT
|
||||||
|
)
|
||||||
|
return redirect("intervention:detail", id=intervention_id)
|
||||||
|
|
||||||
# Precalculate logical errors between before- and after-states
|
data_form = NewCompensationForm(request.POST or None, intervention_id=intervention_id)
|
||||||
# Sum() returns None in case of no states, so we catch that and replace it with 0 for easier handling
|
geom_form = SimpleGeomForm(request.POST or None, read_only=False)
|
||||||
sum_before_states = obj.get_surface_before_states()
|
|
||||||
sum_after_states = obj.get_surface_after_states()
|
|
||||||
diff_states = abs(sum_before_states - sum_after_states)
|
|
||||||
|
|
||||||
last_checked = obj.intervention.get_last_checked_action()
|
|
||||||
last_checked_tooltip = ""
|
|
||||||
if last_checked:
|
|
||||||
last_checked_tooltip = DATA_CHECKED_PREVIOUSLY_TEMPLATE.format(
|
|
||||||
last_checked.get_timestamp_str_formatted(),
|
|
||||||
last_checked.user
|
|
||||||
)
|
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
"last_checked": last_checked,
|
"form": data_form,
|
||||||
"last_checked_tooltip": last_checked_tooltip,
|
"geom_form": geom_form,
|
||||||
"actions": actions,
|
TAB_TITLE_IDENTIFIER: _("New compensation"),
|
||||||
"before_states": before_states,
|
|
||||||
"after_states": after_states,
|
|
||||||
"sum_before_states": sum_before_states,
|
|
||||||
"sum_after_states": sum_after_states,
|
|
||||||
"diff_states": diff_states,
|
|
||||||
"has_finished_deadlines": obj.get_finished_deadlines().exists(),
|
|
||||||
}
|
}
|
||||||
return context
|
context = BaseContext(request, context).context
|
||||||
|
|
||||||
|
return render(request, self._TEMPLATE, context)
|
||||||
|
|
||||||
|
@method_decorator(default_group_required)
|
||||||
|
@method_decorator(shared_access_required(Intervention, "intervention_id"))
|
||||||
|
def post(self, request: HttpRequest, intervention_id: str = None, *args, **kwargs) -> HttpResponse:
|
||||||
|
|
||||||
|
"""
|
||||||
|
Renders a view for a new compensation creation
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request (HttpRequest): The incoming request
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
if intervention_id:
|
||||||
|
# If the parent-intervention is recorded, we are not allowed to change anything on it's data.
|
||||||
|
# Not even adding new child elements like compensations!
|
||||||
|
intervention = get_object_or_404(Intervention, id=intervention_id)
|
||||||
|
recording_state_blocks_actions = intervention.is_recorded
|
||||||
|
if recording_state_blocks_actions:
|
||||||
|
messages.info(
|
||||||
|
request,
|
||||||
|
RECORDED_BLOCKS_EDIT
|
||||||
|
)
|
||||||
|
return redirect("intervention:detail", id=intervention_id)
|
||||||
|
|
||||||
|
data_form = NewCompensationForm(request.POST or None, intervention_id=intervention_id)
|
||||||
|
geom_form = SimpleGeomForm(request.POST or None, read_only=False)
|
||||||
|
|
||||||
|
if data_form.is_valid() and geom_form.is_valid():
|
||||||
|
generated_identifier = data_form.cleaned_data.get("identifier", None)
|
||||||
|
comp = data_form.save(request.user, geom_form)
|
||||||
|
if generated_identifier != comp.identifier:
|
||||||
|
messages.info(
|
||||||
|
request,
|
||||||
|
IDENTIFIER_REPLACED.format(
|
||||||
|
generated_identifier,
|
||||||
|
comp.identifier
|
||||||
|
)
|
||||||
|
)
|
||||||
|
messages.success(request, COMPENSATION_ADDED_TEMPLATE.format(comp.identifier))
|
||||||
|
if geom_form.has_geometry_simplified():
|
||||||
|
messages.info(
|
||||||
|
request,
|
||||||
|
GEOMETRY_SIMPLIFIED
|
||||||
|
)
|
||||||
|
num_ignored_geometries = geom_form.get_num_geometries_ignored()
|
||||||
|
if num_ignored_geometries > 0:
|
||||||
|
messages.info(
|
||||||
|
request,
|
||||||
|
GEOMETRIES_IGNORED_TEMPLATE.format(num_ignored_geometries)
|
||||||
|
)
|
||||||
|
return redirect("compensation:detail", id=comp.id)
|
||||||
|
else:
|
||||||
|
messages.error(request, FORM_INVALID, extra_tags="danger", )
|
||||||
|
|
||||||
|
context = {
|
||||||
|
"form": data_form,
|
||||||
|
"geom_form": geom_form,
|
||||||
|
TAB_TITLE_IDENTIFIER: _("New compensation"),
|
||||||
|
}
|
||||||
|
context = BaseContext(request, context).context
|
||||||
|
|
||||||
|
return render(request, self._TEMPLATE, context)
|
||||||
|
|
||||||
|
|
||||||
@login_required_modal
|
class CompensationIdentifierGeneratorView(AbstractIdentifierGeneratorView):
|
||||||
@login_required
|
_MODEL = Compensation
|
||||||
@default_group_required
|
|
||||||
@shared_access_required(Compensation, "id")
|
|
||||||
def remove_view(request: HttpRequest, id: str):
|
|
||||||
""" Renders a modal view for removing the compensation
|
|
||||||
|
|
||||||
Args:
|
|
||||||
request (HttpRequest): The incoming request
|
|
||||||
id (str): The compensation's id
|
|
||||||
|
|
||||||
Returns:
|
class EditCompensationView(LoginRequiredMixin, View):
|
||||||
|
_TEMPLATE = "compensation/form/view.html"
|
||||||
|
|
||||||
"""
|
@method_decorator(default_group_required)
|
||||||
comp = get_object_or_404(Compensation, id=id)
|
@method_decorator(shared_access_required(Compensation, "id"))
|
||||||
form = RemoveModalForm(request.POST or None, instance=comp, request=request)
|
def get(self, request: HttpRequest, id: str, *args, **kwargs) -> HttpResponse:
|
||||||
return form.process_request(
|
|
||||||
request=request,
|
|
||||||
msg_success=COMPENSATION_REMOVED_TEMPLATE.format(comp.identifier),
|
|
||||||
redirect_url=reverse("compensation:index"),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
Renders a view for editing compensations
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request (HttpRequest): The incoming request
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
# Get object from db
|
||||||
|
comp = get_object_or_404(Compensation, id=id)
|
||||||
|
if comp.is_recorded:
|
||||||
|
messages.info(
|
||||||
|
request,
|
||||||
|
RECORDED_BLOCKS_EDIT
|
||||||
|
)
|
||||||
|
return redirect("compensation:detail", id=id)
|
||||||
|
|
||||||
|
# Create forms, initialize with values from db/from POST request
|
||||||
|
data_form = EditCompensationForm(request.POST or None, instance=comp)
|
||||||
|
geom_form = SimpleGeomForm(request.POST or None, read_only=False, instance=comp)
|
||||||
|
|
||||||
|
context = {
|
||||||
|
"form": data_form,
|
||||||
|
"geom_form": geom_form,
|
||||||
|
TAB_TITLE_IDENTIFIER: _("Edit {}").format(comp.identifier),
|
||||||
|
}
|
||||||
|
context = BaseContext(request, context).context
|
||||||
|
|
||||||
|
return render(request, self._TEMPLATE, context)
|
||||||
|
|
||||||
|
@method_decorator(default_group_required)
|
||||||
|
@method_decorator(shared_access_required(Compensation, "id"))
|
||||||
|
def post(self, request: HttpRequest, id: str, *args, **kwargs) -> HttpResponse:
|
||||||
|
|
||||||
|
"""
|
||||||
|
Renders a view for editing compensations
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request (HttpRequest): The incoming request
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
# Get object from db
|
||||||
|
comp = get_object_or_404(Compensation, id=id)
|
||||||
|
if comp.is_recorded:
|
||||||
|
messages.info(
|
||||||
|
request,
|
||||||
|
RECORDED_BLOCKS_EDIT
|
||||||
|
)
|
||||||
|
return redirect("compensation:detail", id=id)
|
||||||
|
|
||||||
|
# Create forms, initialize with values from db/from POST request
|
||||||
|
data_form = EditCompensationForm(request.POST or None, instance=comp)
|
||||||
|
geom_form = SimpleGeomForm(request.POST or None, read_only=False, instance=comp)
|
||||||
|
if data_form.is_valid() and geom_form.is_valid():
|
||||||
|
# Preserve state of intervention checked to determine whether the user must be informed or not
|
||||||
|
# about a change of the check state
|
||||||
|
intervention_is_checked = comp.intervention.checked is not None
|
||||||
|
# The data form takes the geom form for processing, as well as the performing user
|
||||||
|
comp = data_form.save(request.user, geom_form)
|
||||||
|
if intervention_is_checked:
|
||||||
|
messages.info(request, CHECK_STATE_RESET)
|
||||||
|
messages.success(request, _("Compensation {} edited").format(comp.identifier))
|
||||||
|
if geom_form.has_geometry_simplified():
|
||||||
|
messages.info(
|
||||||
|
request,
|
||||||
|
GEOMETRY_SIMPLIFIED
|
||||||
|
)
|
||||||
|
num_ignored_geometries = geom_form.get_num_geometries_ignored()
|
||||||
|
if num_ignored_geometries > 0:
|
||||||
|
messages.info(
|
||||||
|
request,
|
||||||
|
GEOMETRIES_IGNORED_TEMPLATE.format(num_ignored_geometries)
|
||||||
|
)
|
||||||
|
return redirect("compensation:detail", id=comp.id)
|
||||||
|
else:
|
||||||
|
messages.error(request, FORM_INVALID, extra_tags="danger", )
|
||||||
|
|
||||||
|
context = {
|
||||||
|
"form": data_form,
|
||||||
|
"geom_form": geom_form,
|
||||||
|
TAB_TITLE_IDENTIFIER: _("Edit {}").format(comp.identifier),
|
||||||
|
}
|
||||||
|
context = BaseContext(request, context).context
|
||||||
|
|
||||||
|
return render(request, self._TEMPLATE, context)
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
"""
|
||||||
|
Author: Michel Peltriaux
|
||||||
|
Created on: 14.12.25
|
||||||
|
|
||||||
|
"""
|
||||||
|
from django.contrib import messages
|
||||||
|
from django.http import HttpRequest, HttpResponse
|
||||||
|
from django.shortcuts import render, get_object_or_404
|
||||||
|
|
||||||
|
from compensation.models import Compensation
|
||||||
|
from konova.contexts import BaseContext
|
||||||
|
from konova.forms import SimpleGeomForm
|
||||||
|
from konova.settings import ETS_GROUP, ZB_GROUP, DEFAULT_GROUP
|
||||||
|
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
|
||||||
|
from konova.utils.message_templates import DO_NOT_FORGET_TO_SHARE, DATA_CHECKED_PREVIOUSLY_TEMPLATE
|
||||||
|
from konova.views.detail import AbstractDetailView
|
||||||
|
|
||||||
|
|
||||||
|
class DetailCompensationView(AbstractDetailView):
|
||||||
|
_TEMPLATE = "compensation/detail/compensation/view.html"
|
||||||
|
|
||||||
|
def get(self, request: HttpRequest, id: str, *args, **kwargs) -> HttpResponse:
|
||||||
|
|
||||||
|
""" Renders a detail view for a compensation
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request (HttpRequest): The incoming request
|
||||||
|
id (str): The compensation's id
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
comp = get_object_or_404(
|
||||||
|
Compensation.objects.select_related(
|
||||||
|
"modified",
|
||||||
|
"created",
|
||||||
|
"geometry"
|
||||||
|
),
|
||||||
|
id=id,
|
||||||
|
deleted=None,
|
||||||
|
intervention__deleted=None,
|
||||||
|
)
|
||||||
|
geom_form = SimpleGeomForm(instance=comp)
|
||||||
|
parcels = comp.get_underlying_parcels()
|
||||||
|
_user = request.user
|
||||||
|
is_data_shared = comp.intervention.is_shared_with(_user)
|
||||||
|
|
||||||
|
# Order states according to surface
|
||||||
|
before_states = comp.before_states.all().prefetch_related("biotope_type").order_by("-surface")
|
||||||
|
after_states = comp.after_states.all().prefetch_related("biotope_type").order_by("-surface")
|
||||||
|
actions = comp.actions.all().prefetch_related("action_type")
|
||||||
|
|
||||||
|
# Precalculate logical errors between before- and after-states
|
||||||
|
# Sum() returns None in case of no states, so we catch that and replace it with 0 for easier handling
|
||||||
|
sum_before_states = comp.get_surface_before_states()
|
||||||
|
sum_after_states = comp.get_surface_after_states()
|
||||||
|
diff_states = abs(sum_before_states - sum_after_states)
|
||||||
|
|
||||||
|
request = comp.set_status_messages(request)
|
||||||
|
|
||||||
|
last_checked = comp.intervention.get_last_checked_action()
|
||||||
|
last_checked_tooltip = ""
|
||||||
|
if last_checked:
|
||||||
|
last_checked_tooltip = DATA_CHECKED_PREVIOUSLY_TEMPLATE.format(
|
||||||
|
last_checked.get_timestamp_str_formatted(),
|
||||||
|
last_checked.user
|
||||||
|
)
|
||||||
|
|
||||||
|
requesting_user_is_only_shared_user = comp.is_only_shared_with(_user)
|
||||||
|
if requesting_user_is_only_shared_user:
|
||||||
|
messages.info(
|
||||||
|
request,
|
||||||
|
DO_NOT_FORGET_TO_SHARE
|
||||||
|
)
|
||||||
|
|
||||||
|
context = {
|
||||||
|
"obj": comp,
|
||||||
|
"last_checked": last_checked,
|
||||||
|
"last_checked_tooltip": last_checked_tooltip,
|
||||||
|
"geom_form": geom_form,
|
||||||
|
"parcels": parcels,
|
||||||
|
"is_entry_shared": is_data_shared,
|
||||||
|
"actions": actions,
|
||||||
|
"before_states": before_states,
|
||||||
|
"after_states": after_states,
|
||||||
|
"sum_before_states": sum_before_states,
|
||||||
|
"sum_after_states": sum_after_states,
|
||||||
|
"diff_states": diff_states,
|
||||||
|
"is_default_member": _user.in_group(DEFAULT_GROUP),
|
||||||
|
"is_zb_member": _user.in_group(ZB_GROUP),
|
||||||
|
"is_ets_member": _user.in_group(ETS_GROUP),
|
||||||
|
"LANIS_LINK": comp.get_LANIS_link(),
|
||||||
|
TAB_TITLE_IDENTIFIER: f"{comp.identifier} - {comp.title}",
|
||||||
|
"has_finished_deadlines": comp.get_finished_deadlines().exists(),
|
||||||
|
}
|
||||||
|
context = BaseContext(request, context).context
|
||||||
|
return render(request, self._TEMPLATE, context)
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
"""
|
||||||
|
Author: Michel Peltriaux
|
||||||
|
Created on: 14.12.25
|
||||||
|
|
||||||
|
"""
|
||||||
|
from django.http import HttpRequest, HttpResponse
|
||||||
|
from django.utils.decorators import method_decorator
|
||||||
|
|
||||||
|
from compensation.models import Compensation
|
||||||
|
from konova.decorators import shared_access_required
|
||||||
|
from konova.views.remove import AbstractRemoveView
|
||||||
|
|
||||||
|
|
||||||
|
class RemoveCompensationView(AbstractRemoveView):
|
||||||
|
_MODEL = Compensation
|
||||||
|
_REDIRECT_URL = "compensation:index"
|
||||||
|
|
||||||
|
@method_decorator(shared_access_required(Compensation, "id"))
|
||||||
|
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
||||||
|
return super().get(request, *args, **kwargs)
|
||||||
@@ -5,48 +5,81 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
|
|||||||
Created on: 19.08.22
|
Created on: 19.08.22
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
from django.http import HttpRequest, HttpResponse
|
||||||
|
from django.shortcuts import get_object_or_404, render
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from compensation.models import Compensation
|
from compensation.models import Compensation
|
||||||
from konova.sub_settings.django_settings import BASE_URL
|
from konova.contexts import BaseContext
|
||||||
|
from konova.forms import SimpleGeomForm
|
||||||
|
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
|
||||||
from konova.utils.qrcode import QrCode
|
from konova.utils.qrcode import QrCode
|
||||||
from konova.views.report import BaseReportView
|
from konova.views.report import AbstractPublicReportView
|
||||||
|
|
||||||
|
|
||||||
class BaseCompensationReportView(BaseReportView):
|
class CompensationPublicReportView(AbstractPublicReportView):
|
||||||
def _get_compensation_report_context(self, obj):
|
|
||||||
# Order states by surface
|
|
||||||
before_states = obj.before_states.all().order_by("-surface").prefetch_related("biotope_type")
|
|
||||||
after_states = obj.after_states.all().order_by("-surface").prefetch_related("biotope_type")
|
|
||||||
actions = obj.actions.all().prefetch_related("action_type")
|
|
||||||
|
|
||||||
return {
|
|
||||||
"before_states": before_states,
|
|
||||||
"after_states": after_states,
|
|
||||||
"actions": actions,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class CompensationReportView(BaseCompensationReportView):
|
|
||||||
_MODEL = Compensation
|
|
||||||
_TEMPLATE = "compensation/report/compensation/report.html"
|
_TEMPLATE = "compensation/report/compensation/report.html"
|
||||||
|
|
||||||
def _get_report_context(self, obj):
|
def get(self, request: HttpRequest, id: str, *args, **kwargs) -> HttpResponse:
|
||||||
report_url = BASE_URL + reverse("compensation:report", args=(obj.id,))
|
""" Renders the public report view
|
||||||
qrcode_report = QrCode(report_url, 10)
|
|
||||||
qrcode_lanis = QrCode(obj.get_LANIS_link(), 7)
|
|
||||||
|
|
||||||
report_context = {
|
Args:
|
||||||
|
request (HttpRequest): The incoming request
|
||||||
|
id (str): The id of the intervention
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
comp = get_object_or_404(Compensation, id=id)
|
||||||
|
tab_title = _("Report {}").format(comp.identifier)
|
||||||
|
# If intervention is not recorded (yet or currently) we need to render another template without any data
|
||||||
|
if not comp.is_ready_for_publish():
|
||||||
|
template = "report/unavailable.html"
|
||||||
|
context = {
|
||||||
|
TAB_TITLE_IDENTIFIER: tab_title,
|
||||||
|
}
|
||||||
|
context = BaseContext(request, context).context
|
||||||
|
return render(request, template, context)
|
||||||
|
|
||||||
|
# Prepare data for map viewer
|
||||||
|
geom_form = SimpleGeomForm(
|
||||||
|
instance=comp
|
||||||
|
)
|
||||||
|
parcels = comp.get_underlying_parcels()
|
||||||
|
|
||||||
|
qrcode = QrCode(
|
||||||
|
content=request.build_absolute_uri(reverse("compensation:report", args=(id,))),
|
||||||
|
size=10
|
||||||
|
)
|
||||||
|
qrcode_lanis = QrCode(
|
||||||
|
content=comp.get_LANIS_link(),
|
||||||
|
size=7
|
||||||
|
)
|
||||||
|
|
||||||
|
# Order states by surface
|
||||||
|
before_states = comp.before_states.all().order_by("-surface").prefetch_related("biotope_type")
|
||||||
|
after_states = comp.after_states.all().order_by("-surface").prefetch_related("biotope_type")
|
||||||
|
actions = comp.actions.all().prefetch_related("action_type")
|
||||||
|
|
||||||
|
context = {
|
||||||
|
"obj": comp,
|
||||||
"qrcode": {
|
"qrcode": {
|
||||||
"img": qrcode_report.get_img(),
|
"img": qrcode.get_img(),
|
||||||
"url": qrcode_report.get_content(),
|
"url": qrcode.get_content(),
|
||||||
},
|
},
|
||||||
"qrcode_lanis": {
|
"qrcode_lanis": {
|
||||||
"img": qrcode_lanis.get_img(),
|
"img": qrcode_lanis.get_img(),
|
||||||
"url": qrcode_lanis.get_content(),
|
"url": qrcode_lanis.get_content(),
|
||||||
},
|
},
|
||||||
"is_entry_shared": False, # disables action buttons during rendering
|
"is_entry_shared": False, # disables action buttons during rendering
|
||||||
|
"before_states": before_states,
|
||||||
|
"after_states": after_states,
|
||||||
|
"geom_form": geom_form,
|
||||||
|
"parcels": parcels,
|
||||||
|
"actions": actions,
|
||||||
"tables_scrollable": False,
|
"tables_scrollable": False,
|
||||||
|
TAB_TITLE_IDENTIFIER: tab_title,
|
||||||
}
|
}
|
||||||
report_context.update(self._get_compensation_report_context(obj))
|
context = BaseContext(request, context).context
|
||||||
return report_context
|
return render(request, self._TEMPLATE, context)
|
||||||
|
|||||||
@@ -5,29 +5,54 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
|
|||||||
Created on: 19.08.22
|
Created on: 19.08.22
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.http import Http404
|
from django.http import Http404
|
||||||
|
from django.utils.decorators import method_decorator
|
||||||
|
|
||||||
from compensation.models import EcoAccount
|
from compensation.models import EcoAccount
|
||||||
|
from konova.decorators import default_group_required, login_required_modal
|
||||||
from konova.views.deduction import AbstractNewDeductionView, AbstractEditDeductionView, AbstractRemoveDeductionView
|
from konova.views.deduction import AbstractNewDeductionView, AbstractEditDeductionView, AbstractRemoveDeductionView
|
||||||
|
|
||||||
_ECO_ACCOUNT_DETAIl_URL_NAME = "compensation:acc:detail"
|
|
||||||
|
|
||||||
class NewEcoAccountDeductionView(LoginRequiredMixin, AbstractNewDeductionView):
|
class NewEcoAccountDeductionView(AbstractNewDeductionView):
|
||||||
_MODEL_CLS = EcoAccount
|
model = EcoAccount
|
||||||
_REDIRECT_URL = _ECO_ACCOUNT_DETAIl_URL_NAME
|
redirect_url = "compensation:acc:detail"
|
||||||
|
|
||||||
|
@method_decorator(login_required_modal)
|
||||||
|
@method_decorator(login_required)
|
||||||
|
@method_decorator(default_group_required)
|
||||||
|
def dispatch(self, request, *args, **kwargs):
|
||||||
|
return super().dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
def _custom_check(self, obj):
|
def _custom_check(self, obj):
|
||||||
# New deductions can only be created if the eco account has been recorded
|
|
||||||
if not obj.recorded:
|
if not obj.recorded:
|
||||||
raise Http404()
|
raise Http404()
|
||||||
|
|
||||||
|
|
||||||
class EditEcoAccountDeductionView(LoginRequiredMixin, AbstractEditDeductionView):
|
class EditEcoAccountDeductionView(AbstractEditDeductionView):
|
||||||
_MODEL_CLS = EcoAccount
|
def _custom_check(self, obj):
|
||||||
_REDIRECT_URL = _ECO_ACCOUNT_DETAIl_URL_NAME
|
pass
|
||||||
|
|
||||||
|
model = EcoAccount
|
||||||
|
redirect_url = "compensation:acc:detail"
|
||||||
|
|
||||||
|
@method_decorator(login_required_modal)
|
||||||
|
@method_decorator(login_required)
|
||||||
|
@method_decorator(default_group_required)
|
||||||
|
def dispatch(self, request, *args, **kwargs):
|
||||||
|
return super().dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class RemoveEcoAccountDeductionView(LoginRequiredMixin, AbstractRemoveDeductionView):
|
class RemoveEcoAccountDeductionView(AbstractRemoveDeductionView):
|
||||||
_MODEL_CLS = EcoAccount
|
def _custom_check(self, obj):
|
||||||
_REDIRECT_URL = _ECO_ACCOUNT_DETAIl_URL_NAME
|
pass
|
||||||
|
|
||||||
|
model = EcoAccount
|
||||||
|
redirect_url = "compensation:acc:detail"
|
||||||
|
|
||||||
|
@method_decorator(login_required_modal)
|
||||||
|
@method_decorator(login_required)
|
||||||
|
@method_decorator(default_group_required)
|
||||||
|
def dispatch(self, request, *args, **kwargs):
|
||||||
|
return super().dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,97 @@
|
|||||||
|
"""
|
||||||
|
Author: Michel Peltriaux
|
||||||
|
Created on: 14.12.25
|
||||||
|
|
||||||
|
"""
|
||||||
|
from django.contrib import messages
|
||||||
|
from django.http import HttpRequest, HttpResponse
|
||||||
|
from django.shortcuts import render, get_object_or_404
|
||||||
|
|
||||||
|
from compensation.models import EcoAccount
|
||||||
|
from konova.contexts import BaseContext
|
||||||
|
from konova.forms import SimpleGeomForm
|
||||||
|
from konova.settings import ETS_GROUP, ZB_GROUP, DEFAULT_GROUP
|
||||||
|
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
|
||||||
|
from konova.utils.message_templates import DO_NOT_FORGET_TO_SHARE
|
||||||
|
from konova.views.detail import AbstractDetailView
|
||||||
|
|
||||||
|
|
||||||
|
class DetailEcoAccountView(AbstractDetailView):
|
||||||
|
_TEMPLATE = "compensation/detail/eco_account/view.html"
|
||||||
|
|
||||||
|
def get(self, request: HttpRequest, id: str, *args, **kwargs) -> HttpResponse:
|
||||||
|
""" Renders a detail view for a compensation
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request (HttpRequest): The incoming request
|
||||||
|
id (str): The compensation's id
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
acc = get_object_or_404(
|
||||||
|
EcoAccount.objects.prefetch_related(
|
||||||
|
"deadlines",
|
||||||
|
).select_related(
|
||||||
|
'geometry',
|
||||||
|
'responsible',
|
||||||
|
),
|
||||||
|
id=id,
|
||||||
|
deleted=None,
|
||||||
|
)
|
||||||
|
geom_form = SimpleGeomForm(instance=acc)
|
||||||
|
parcels = acc.get_underlying_parcels()
|
||||||
|
_user = request.user
|
||||||
|
is_data_shared = acc.is_shared_with(_user)
|
||||||
|
|
||||||
|
# Order states according to surface
|
||||||
|
before_states = acc.before_states.order_by("-surface")
|
||||||
|
after_states = acc.after_states.order_by("-surface")
|
||||||
|
|
||||||
|
# Precalculate logical errors between before- and after-states
|
||||||
|
# Sum() returns None in case of no states, so we catch that and replace it with 0 for easier handling
|
||||||
|
sum_before_states = acc.get_surface_before_states()
|
||||||
|
sum_after_states = acc.get_surface_after_states()
|
||||||
|
diff_states = abs(sum_before_states - sum_after_states)
|
||||||
|
# Calculate rest of available surface for deductions
|
||||||
|
available_total = acc.deductable_rest
|
||||||
|
available_relative = acc.get_deductable_rest_relative()
|
||||||
|
|
||||||
|
# Prefetch related data to decrease the amount of db connections
|
||||||
|
deductions = acc.deductions.filter(
|
||||||
|
intervention__deleted=None,
|
||||||
|
)
|
||||||
|
actions = acc.actions.all()
|
||||||
|
|
||||||
|
request = acc.set_status_messages(request)
|
||||||
|
|
||||||
|
requesting_user_is_only_shared_user = acc.is_only_shared_with(_user)
|
||||||
|
if requesting_user_is_only_shared_user:
|
||||||
|
messages.info(
|
||||||
|
request,
|
||||||
|
DO_NOT_FORGET_TO_SHARE
|
||||||
|
)
|
||||||
|
|
||||||
|
context = {
|
||||||
|
"obj": acc,
|
||||||
|
"geom_form": geom_form,
|
||||||
|
"parcels": parcels,
|
||||||
|
"is_entry_shared": is_data_shared,
|
||||||
|
"before_states": before_states,
|
||||||
|
"after_states": after_states,
|
||||||
|
"sum_before_states": sum_before_states,
|
||||||
|
"sum_after_states": sum_after_states,
|
||||||
|
"diff_states": diff_states,
|
||||||
|
"available": available_relative,
|
||||||
|
"available_total": available_total,
|
||||||
|
"is_default_member": _user.in_group(DEFAULT_GROUP),
|
||||||
|
"is_zb_member": _user.in_group(ZB_GROUP),
|
||||||
|
"is_ets_member": _user.in_group(ETS_GROUP),
|
||||||
|
"LANIS_LINK": acc.get_LANIS_link(),
|
||||||
|
"deductions": deductions,
|
||||||
|
"actions": actions,
|
||||||
|
TAB_TITLE_IDENTIFIER: f"{acc.identifier} - {acc.title}",
|
||||||
|
"has_finished_deadlines": acc.get_finished_deadlines().exists(),
|
||||||
|
}
|
||||||
|
context = BaseContext(request, context).context
|
||||||
|
return render(request, self._TEMPLATE, context)
|
||||||
@@ -6,80 +6,94 @@ Created on: 19.08.22
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.contrib.auth.decorators import login_required
|
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
from django.http import HttpRequest
|
from django.http import HttpRequest, HttpResponse
|
||||||
from django.shortcuts import get_object_or_404, redirect, render
|
from django.shortcuts import get_object_or_404, redirect, render
|
||||||
from django.urls import reverse
|
from django.utils.decorators import method_decorator
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from django.views import View
|
||||||
|
|
||||||
from compensation.forms.eco_account import EditEcoAccountForm, NewEcoAccountForm, RemoveEcoAccountModalForm
|
from compensation.forms.eco_account import EditEcoAccountForm, NewEcoAccountForm
|
||||||
from compensation.models import EcoAccount
|
from compensation.models import EcoAccount
|
||||||
from compensation.tables.eco_account import EcoAccountTable
|
from compensation.tables.eco_account import EcoAccountTable
|
||||||
from konova.contexts import BaseContext
|
from konova.contexts import BaseContext
|
||||||
from konova.decorators import shared_access_required, default_group_required, login_required_modal
|
from konova.decorators import shared_access_required, default_group_required
|
||||||
from konova.forms import SimpleGeomForm
|
from konova.forms import SimpleGeomForm
|
||||||
from konova.settings import 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.message_templates import CANCEL_ACC_RECORDED_OR_DEDUCTED, RECORDED_BLOCKS_EDIT, FORM_INVALID, \
|
from konova.utils.message_templates import RECORDED_BLOCKS_EDIT, FORM_INVALID, \
|
||||||
IDENTIFIER_REPLACED, GEOMETRY_SIMPLIFIED, GEOMETRIES_IGNORED_TEMPLATE
|
IDENTIFIER_REPLACED, GEOMETRY_SIMPLIFIED, GEOMETRIES_IGNORED_TEMPLATE
|
||||||
from konova.views.base import BaseIndexView, BaseIdentifierGeneratorView, BaseNewSpatialLocatedObjectFormView, \
|
from konova.views.identifier import AbstractIdentifierGeneratorView
|
||||||
BaseEditSpatialLocatedObjectFormView
|
from konova.views.index import AbstractIndexView
|
||||||
from konova.views.detail import BaseDetailView
|
|
||||||
|
|
||||||
|
|
||||||
class EcoAccountIndexView(LoginRequiredMixin, BaseIndexView):
|
class IndexEcoAccountView(AbstractIndexView):
|
||||||
_INDEX_TABLE_CLS = EcoAccountTable
|
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
||||||
_TAB_TITLE = _("Eco-account - Overview")
|
"""
|
||||||
|
Renders the index view for eco accounts
|
||||||
|
|
||||||
def _get_queryset(self):
|
Args:
|
||||||
qs = EcoAccount.objects.filter(
|
request (HttpRequest): The incoming request
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A rendered view
|
||||||
|
"""
|
||||||
|
eco_accounts = EcoAccount.objects.filter(
|
||||||
deleted=None,
|
deleted=None,
|
||||||
).order_by(
|
).order_by(
|
||||||
"-modified__timestamp"
|
"-modified__timestamp"
|
||||||
)
|
)
|
||||||
return qs
|
table = EcoAccountTable(
|
||||||
|
request=request,
|
||||||
|
queryset=eco_accounts
|
||||||
|
)
|
||||||
|
context = {
|
||||||
|
"table": table,
|
||||||
|
TAB_TITLE_IDENTIFIER: _("Eco-account - Overview"),
|
||||||
|
}
|
||||||
|
context = BaseContext(request, context).context
|
||||||
|
return render(request, self._TEMPLATE, context)
|
||||||
|
|
||||||
|
|
||||||
class NewEcoAccountFormView(BaseNewSpatialLocatedObjectFormView):
|
class NewEcoAccountView(LoginRequiredMixin, View):
|
||||||
_FORM_CLS = NewEcoAccountForm
|
|
||||||
_MODEL_CLS = EcoAccount
|
|
||||||
_TEMPLATE = "compensation/form/view.html"
|
_TEMPLATE = "compensation/form/view.html"
|
||||||
_TAB_TITLE = _("New Eco-Account")
|
|
||||||
_REDIRECT_URL = "compensation:acc:detail"
|
|
||||||
|
|
||||||
def _user_has_permission(self, user):
|
@method_decorator(default_group_required)
|
||||||
# User has to be a default user
|
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
||||||
return user.is_default_user()
|
"""
|
||||||
|
Renders a view for a new eco account creation
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request (HttpRequest): The incoming request
|
||||||
|
|
||||||
class EditEcoAccountFormView(BaseEditSpatialLocatedObjectFormView):
|
Returns:
|
||||||
_FORM_CLS = EditEcoAccountForm
|
|
||||||
_MODEL_CLS = EcoAccount
|
|
||||||
_TEMPLATE = "compensation/form/view.html"
|
|
||||||
_REDIRECT_URL = "compensation:acc:detail"
|
|
||||||
|
|
||||||
def _user_has_permission(self, user):
|
"""
|
||||||
# User has to be a default user
|
data_form = NewEcoAccountForm(request.POST or None)
|
||||||
return user.is_default_user()
|
geom_form = SimpleGeomForm(request.POST or None, read_only=False)
|
||||||
|
|
||||||
|
context = {
|
||||||
|
"form": data_form,
|
||||||
|
"geom_form": geom_form,
|
||||||
|
TAB_TITLE_IDENTIFIER: _("New Eco-Account"),
|
||||||
|
}
|
||||||
|
context = BaseContext(request, context).context
|
||||||
|
|
||||||
@login_required
|
return render(request, self._TEMPLATE, context)
|
||||||
@default_group_required
|
|
||||||
def new_view(request: HttpRequest):
|
|
||||||
"""
|
|
||||||
Renders a view for a new eco account creation
|
|
||||||
|
|
||||||
Args:
|
@method_decorator(default_group_required)
|
||||||
request (HttpRequest): The incoming request
|
def post(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
||||||
|
|
||||||
Returns:
|
"""
|
||||||
|
Renders a view for a new eco account creation
|
||||||
|
|
||||||
"""
|
Args:
|
||||||
template = "compensation/form/view.html"
|
request (HttpRequest): The incoming request
|
||||||
data_form = NewEcoAccountForm(request.POST or None)
|
|
||||||
geom_form = SimpleGeomForm(request.POST or None, read_only=False)
|
Returns:
|
||||||
if request.method == "POST":
|
|
||||||
|
"""
|
||||||
|
data_form = NewEcoAccountForm(request.POST or None)
|
||||||
|
geom_form = SimpleGeomForm(request.POST or None, read_only=False)
|
||||||
if data_form.is_valid() and geom_form.is_valid():
|
if data_form.is_valid() and geom_form.is_valid():
|
||||||
generated_identifier = data_form.cleaned_data.get("identifier", None)
|
generated_identifier = data_form.cleaned_data.get("identifier", None)
|
||||||
acc = data_form.save(request.user, geom_form)
|
acc = data_form.save(request.user, geom_form)
|
||||||
@@ -97,61 +111,92 @@ def new_view(request: HttpRequest):
|
|||||||
request,
|
request,
|
||||||
GEOMETRY_SIMPLIFIED
|
GEOMETRY_SIMPLIFIED
|
||||||
)
|
)
|
||||||
|
|
||||||
num_ignored_geometries = geom_form.get_num_geometries_ignored()
|
num_ignored_geometries = geom_form.get_num_geometries_ignored()
|
||||||
if num_ignored_geometries > 0:
|
if num_ignored_geometries > 0:
|
||||||
messages.info(
|
messages.info(
|
||||||
request,
|
request,
|
||||||
GEOMETRIES_IGNORED_TEMPLATE.format(num_ignored_geometries)
|
GEOMETRIES_IGNORED_TEMPLATE.format(num_ignored_geometries)
|
||||||
)
|
)
|
||||||
|
|
||||||
return redirect("compensation:acc:detail", id=acc.id)
|
return redirect("compensation:acc:detail", id=acc.id)
|
||||||
else:
|
else:
|
||||||
messages.error(request, FORM_INVALID, extra_tags="danger",)
|
messages.error(request, FORM_INVALID, extra_tags="danger", )
|
||||||
else:
|
|
||||||
# For clarification: nothing in this case
|
context = {
|
||||||
pass
|
"form": data_form,
|
||||||
context = {
|
"geom_form": geom_form,
|
||||||
"form": data_form,
|
TAB_TITLE_IDENTIFIER: _("New Eco-Account"),
|
||||||
"geom_form": geom_form,
|
}
|
||||||
TAB_TITLE_IDENTIFIER: _("New Eco-Account"),
|
context = BaseContext(request, context).context
|
||||||
}
|
|
||||||
context = BaseContext(request, context).context
|
return render(request, self._TEMPLATE, context)
|
||||||
return render(request, template, context)
|
|
||||||
|
class EcoAccountIdentifierGeneratorView(AbstractIdentifierGeneratorView):
|
||||||
|
_MODEL = EcoAccount
|
||||||
|
|
||||||
|
|
||||||
class EcoAccountIdentifierGeneratorView(LoginRequiredMixin, BaseIdentifierGeneratorView):
|
class EditEcoAccountView(LoginRequiredMixin, View):
|
||||||
_MODEL_CLS = EcoAccount
|
_TEMPLATE = "compensation/form/view.html"
|
||||||
_REDIRECT_URL = "compensation:acc:index"
|
|
||||||
|
|
||||||
|
@method_decorator(default_group_required)
|
||||||
|
@method_decorator(shared_access_required(EcoAccount, "id"))
|
||||||
|
def get(self, request: HttpRequest, id: str, *args, **kwargs) -> HttpResponse:
|
||||||
|
|
||||||
@login_required
|
"""
|
||||||
@default_group_required
|
Renders a view for editing compensations
|
||||||
@shared_access_required(EcoAccount, "id")
|
|
||||||
def edit_view(request: HttpRequest, id: str):
|
|
||||||
"""
|
|
||||||
Renders a view for editing compensations
|
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
request (HttpRequest): The incoming request
|
request (HttpRequest): The incoming request
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
template = "compensation/form/view.html"
|
# Get object from db
|
||||||
# Get object from db
|
acc = get_object_or_404(EcoAccount, id=id)
|
||||||
acc = get_object_or_404(EcoAccount, id=id)
|
if acc.is_recorded:
|
||||||
if acc.is_recorded:
|
messages.info(
|
||||||
messages.info(
|
request,
|
||||||
request,
|
RECORDED_BLOCKS_EDIT
|
||||||
RECORDED_BLOCKS_EDIT
|
)
|
||||||
)
|
return redirect("compensation:acc:detail", id=id)
|
||||||
return redirect("compensation:acc:detail", id=id)
|
|
||||||
|
# Create forms, initialize with values from db/from POST request
|
||||||
|
data_form = EditEcoAccountForm(request.POST or None, instance=acc)
|
||||||
|
geom_form = SimpleGeomForm(request.POST or None, read_only=False, instance=acc)
|
||||||
|
|
||||||
|
context = {
|
||||||
|
"form": data_form,
|
||||||
|
"geom_form": geom_form,
|
||||||
|
TAB_TITLE_IDENTIFIER: _("Edit {}").format(acc.identifier),
|
||||||
|
}
|
||||||
|
context = BaseContext(request, context).context
|
||||||
|
return render(request, self._TEMPLATE, context)
|
||||||
|
|
||||||
|
@method_decorator(default_group_required)
|
||||||
|
@method_decorator(shared_access_required(EcoAccount, "id"))
|
||||||
|
def post(self, request: HttpRequest, id: str, *args, **kwargs) -> HttpResponse:
|
||||||
|
|
||||||
|
"""
|
||||||
|
Renders a view for editing compensations
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request (HttpRequest): The incoming request
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
# Get object from db
|
||||||
|
acc = get_object_or_404(EcoAccount, id=id)
|
||||||
|
if acc.is_recorded:
|
||||||
|
messages.info(
|
||||||
|
request,
|
||||||
|
RECORDED_BLOCKS_EDIT
|
||||||
|
)
|
||||||
|
return redirect("compensation:acc:detail", id=id)
|
||||||
|
|
||||||
|
# Create forms, initialize with values from db/from POST request
|
||||||
|
data_form = EditEcoAccountForm(request.POST or None, instance=acc)
|
||||||
|
geom_form = SimpleGeomForm(request.POST or None, read_only=False, instance=acc)
|
||||||
|
|
||||||
# Create forms, initialize with values from db/from POST request
|
|
||||||
data_form = EditEcoAccountForm(request.POST or None, instance=acc)
|
|
||||||
geom_form = SimpleGeomForm(request.POST or None, read_only=False, instance=acc)
|
|
||||||
if request.method == "POST":
|
|
||||||
data_form_valid = data_form.is_valid()
|
data_form_valid = data_form.is_valid()
|
||||||
geom_form_valid = geom_form.is_valid()
|
geom_form_valid = geom_form.is_valid()
|
||||||
if data_form_valid and geom_form_valid:
|
if data_form_valid and geom_form_valid:
|
||||||
@@ -163,125 +208,21 @@ def edit_view(request: HttpRequest, id: str):
|
|||||||
request,
|
request,
|
||||||
GEOMETRY_SIMPLIFIED
|
GEOMETRY_SIMPLIFIED
|
||||||
)
|
)
|
||||||
|
|
||||||
num_ignored_geometries = geom_form.get_num_geometries_ignored()
|
num_ignored_geometries = geom_form.get_num_geometries_ignored()
|
||||||
if num_ignored_geometries > 0:
|
if num_ignored_geometries > 0:
|
||||||
messages.info(
|
messages.info(
|
||||||
request,
|
request,
|
||||||
GEOMETRIES_IGNORED_TEMPLATE.format(num_ignored_geometries)
|
GEOMETRIES_IGNORED_TEMPLATE.format(num_ignored_geometries)
|
||||||
)
|
)
|
||||||
|
|
||||||
return redirect("compensation:acc:detail", id=acc.id)
|
return redirect("compensation:acc:detail", id=acc.id)
|
||||||
else:
|
else:
|
||||||
messages.error(request, FORM_INVALID, extra_tags="danger",)
|
messages.error(request, FORM_INVALID, extra_tags="danger", )
|
||||||
else:
|
|
||||||
# For clarification: nothing in this case
|
|
||||||
pass
|
|
||||||
context = {
|
|
||||||
"form": data_form,
|
|
||||||
"geom_form": geom_form,
|
|
||||||
TAB_TITLE_IDENTIFIER: _("Edit {}").format(acc.identifier),
|
|
||||||
}
|
|
||||||
context = BaseContext(request, context).context
|
|
||||||
return render(request, template, context)
|
|
||||||
|
|
||||||
|
|
||||||
class EcoAccountDetailView(BaseDetailView):
|
|
||||||
_MODEL_CLS = EcoAccount
|
|
||||||
_TEMPLATE = "compensation/detail/eco_account/view.html"
|
|
||||||
|
|
||||||
def _get_object(self, id: str):
|
|
||||||
""" Fetch object for detail view
|
|
||||||
|
|
||||||
Args:
|
|
||||||
id (str): The record's id'
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
|
|
||||||
"""
|
|
||||||
acc = get_object_or_404(
|
|
||||||
EcoAccount.objects.prefetch_related(
|
|
||||||
"deadlines",
|
|
||||||
).select_related(
|
|
||||||
'geometry',
|
|
||||||
'responsible',
|
|
||||||
),
|
|
||||||
id=id,
|
|
||||||
deleted=None,
|
|
||||||
)
|
|
||||||
return acc
|
|
||||||
|
|
||||||
def _get_detail_context(self, obj: EcoAccount):
|
|
||||||
""" Generate object specific detail context for view
|
|
||||||
|
|
||||||
Args:
|
|
||||||
obj (): The record
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
|
|
||||||
"""
|
|
||||||
# Order states according to surface
|
|
||||||
before_states = obj.before_states.order_by("-surface")
|
|
||||||
after_states = obj.after_states.order_by("-surface")
|
|
||||||
|
|
||||||
# Precalculate logical errors between before- and after-states
|
|
||||||
# Sum() returns None in case of no states, so we catch that and replace it with 0 for easier handling
|
|
||||||
sum_before_states = obj.get_surface_before_states()
|
|
||||||
sum_after_states = obj.get_surface_after_states()
|
|
||||||
diff_states = abs(sum_before_states - sum_after_states)
|
|
||||||
# Calculate rest of available surface for deductions
|
|
||||||
available_total = obj.deductable_rest
|
|
||||||
available_relative = obj.get_deductable_rest_relative()
|
|
||||||
|
|
||||||
# Prefetch related data to decrease the amount of db connections
|
|
||||||
deductions = obj.deductions.filter(
|
|
||||||
intervention__deleted=None,
|
|
||||||
)
|
|
||||||
actions = obj.actions.all()
|
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
"before_states": before_states,
|
"form": data_form,
|
||||||
"after_states": after_states,
|
"geom_form": geom_form,
|
||||||
"sum_before_states": sum_before_states,
|
TAB_TITLE_IDENTIFIER: _("Edit {}").format(acc.identifier),
|
||||||
"sum_after_states": sum_after_states,
|
|
||||||
"diff_states": diff_states,
|
|
||||||
"available": available_relative,
|
|
||||||
"available_total": available_total,
|
|
||||||
"deductions": deductions,
|
|
||||||
"actions": actions,
|
|
||||||
"has_finished_deadlines": obj.get_finished_deadlines().exists(),
|
|
||||||
}
|
}
|
||||||
return context
|
context = BaseContext(request, context).context
|
||||||
|
|
||||||
|
|
||||||
@login_required_modal
|
|
||||||
@login_required
|
|
||||||
@default_group_required
|
|
||||||
@shared_access_required(EcoAccount, "id")
|
|
||||||
def remove_view(request: HttpRequest, id: str):
|
|
||||||
""" Renders a modal view for removing the eco account
|
|
||||||
|
|
||||||
Args:
|
|
||||||
request (HttpRequest): The incoming request
|
|
||||||
id (str): The account's id
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
|
|
||||||
"""
|
|
||||||
acc = get_object_or_404(EcoAccount, id=id)
|
|
||||||
|
|
||||||
# If the eco account has already been recorded OR there are already deductions, it can not be deleted by a regular
|
|
||||||
# default group user
|
|
||||||
if acc.recorded is not None or acc.deductions.exists():
|
|
||||||
user = request.user
|
|
||||||
if not user.in_group(ETS_GROUP):
|
|
||||||
messages.info(request, CANCEL_ACC_RECORDED_OR_DEDUCTED)
|
|
||||||
return redirect("compensation:acc:detail", id=id)
|
|
||||||
|
|
||||||
form = RemoveEcoAccountModalForm(request.POST or None, instance=acc, request=request)
|
|
||||||
return form.process_request(
|
|
||||||
request=request,
|
|
||||||
msg_success=_("Eco-account removed"),
|
|
||||||
redirect_url=reverse("compensation:acc:index"),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
return render(request, self._TEMPLATE, context)
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
"""
|
||||||
|
Author: Michel Peltriaux
|
||||||
|
Created on: 14.12.25
|
||||||
|
|
||||||
|
"""
|
||||||
|
from django.http import HttpRequest, HttpResponse
|
||||||
|
from django.utils.decorators import method_decorator
|
||||||
|
|
||||||
|
from compensation.forms.eco_account import RemoveEcoAccountModalForm
|
||||||
|
from compensation.models import EcoAccount
|
||||||
|
from konova.decorators import shared_access_required
|
||||||
|
from konova.views.remove import AbstractRemoveView
|
||||||
|
|
||||||
|
|
||||||
|
class RemoveEcoAccountView(AbstractRemoveView):
|
||||||
|
_MODEL = EcoAccount
|
||||||
|
_REDIRECT_URL = "compensation:acc:index"
|
||||||
|
_FORM = RemoveEcoAccountModalForm
|
||||||
|
|
||||||
|
@method_decorator(shared_access_required(EcoAccount, "id"))
|
||||||
|
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
||||||
|
return super().get(request, *args, **kwargs)
|
||||||
@@ -5,41 +5,88 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
|
|||||||
Created on: 19.08.22
|
Created on: 19.08.22
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
from django.http import HttpRequest, HttpResponse
|
||||||
|
from django.shortcuts import get_object_or_404, render
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from compensation.models import EcoAccount
|
from compensation.models import EcoAccount
|
||||||
from compensation.views.compensation.report import BaseCompensationReportView
|
from konova.contexts import BaseContext
|
||||||
from konova.sub_settings.django_settings import BASE_URL
|
from konova.forms import SimpleGeomForm
|
||||||
|
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
|
||||||
from konova.utils.qrcode import QrCode
|
from konova.utils.qrcode import QrCode
|
||||||
|
from konova.views.report import AbstractPublicReportView
|
||||||
|
|
||||||
|
|
||||||
class EcoAccountReportView(BaseCompensationReportView):
|
class EcoAccountPublicReportView(AbstractPublicReportView):
|
||||||
_MODEL = EcoAccount
|
|
||||||
_TEMPLATE = "compensation/report/eco_account/report.html"
|
_TEMPLATE = "compensation/report/eco_account/report.html"
|
||||||
|
|
||||||
def _get_report_context(self, obj):
|
def get(self, request: HttpRequest, id: str, *args, **kwargs) -> HttpResponse:
|
||||||
report_url = BASE_URL + reverse("compensation:acc:report", args=(obj.id,))
|
""" Renders the public report view
|
||||||
qrcode_report = QrCode(report_url, 10)
|
|
||||||
qrcode_lanis = QrCode(obj.get_LANIS_link(), 7)
|
Args:
|
||||||
|
request (HttpRequest): The incoming request
|
||||||
|
id (str): The id of the intervention
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
acc = get_object_or_404(EcoAccount, id=id)
|
||||||
|
tab_title = _("Report {}").format(acc.identifier)
|
||||||
|
# If intervention is not recorded (yet or currently) we need to render another template without any data
|
||||||
|
if not acc.is_ready_for_publish():
|
||||||
|
template = "report/unavailable.html"
|
||||||
|
context = {
|
||||||
|
TAB_TITLE_IDENTIFIER: tab_title,
|
||||||
|
}
|
||||||
|
context = BaseContext(request, context).context
|
||||||
|
return render(request, template, context)
|
||||||
|
|
||||||
|
# Prepare data for map viewer
|
||||||
|
geom_form = SimpleGeomForm(
|
||||||
|
instance=acc
|
||||||
|
)
|
||||||
|
parcels = acc.get_underlying_parcels()
|
||||||
|
|
||||||
|
qrcode = QrCode(
|
||||||
|
content=request.build_absolute_uri(reverse("compensation:acc:report", args=(id,))),
|
||||||
|
size=10
|
||||||
|
)
|
||||||
|
qrcode_lanis = QrCode(
|
||||||
|
content=acc.get_LANIS_link(),
|
||||||
|
size=7
|
||||||
|
)
|
||||||
|
|
||||||
|
# Order states by surface
|
||||||
|
before_states = acc.before_states.all().order_by("-surface").select_related("biotope_type__parent")
|
||||||
|
after_states = acc.after_states.all().order_by("-surface").select_related("biotope_type__parent")
|
||||||
|
actions = acc.actions.all().prefetch_related("action_type__parent")
|
||||||
|
|
||||||
# Reduce amount of db fetched data to the bare minimum we need in the template (deduction's intervention id and identifier)
|
# Reduce amount of db fetched data to the bare minimum we need in the template (deduction's intervention id and identifier)
|
||||||
deductions = obj.deductions.all() \
|
deductions = acc.deductions.all() \
|
||||||
.distinct("intervention") \
|
.distinct("intervention") \
|
||||||
.select_related("intervention") \
|
.select_related("intervention") \
|
||||||
.values_list("intervention__id", "intervention__identifier", "intervention__title", named=True)
|
.values_list("intervention__id", "intervention__identifier", "intervention__title", named=True)
|
||||||
|
|
||||||
report_context = {
|
context = {
|
||||||
|
"obj": acc,
|
||||||
"qrcode": {
|
"qrcode": {
|
||||||
"img": qrcode_report.get_img(),
|
"img": qrcode.get_img(),
|
||||||
"url": qrcode_report.get_content(),
|
"url": qrcode.get_content(),
|
||||||
},
|
},
|
||||||
"qrcode_lanis": {
|
"qrcode_lanis": {
|
||||||
"img": qrcode_lanis.get_img(),
|
"img": qrcode_lanis.get_img(),
|
||||||
"url": qrcode_lanis.get_content(),
|
"url": qrcode_lanis.get_content(),
|
||||||
},
|
},
|
||||||
"is_entry_shared": False, # disables action buttons during rendering
|
"is_entry_shared": False, # disables action buttons during rendering
|
||||||
|
"before_states": before_states,
|
||||||
|
"after_states": after_states,
|
||||||
|
"geom_form": geom_form,
|
||||||
|
"parcels": parcels,
|
||||||
|
"actions": actions,
|
||||||
"deductions": deductions,
|
"deductions": deductions,
|
||||||
"tables_scrollable": False,
|
"tables_scrollable": False,
|
||||||
|
TAB_TITLE_IDENTIFIER: tab_title,
|
||||||
}
|
}
|
||||||
report_context.update(self._get_compensation_report_context(obj))
|
context = BaseContext(request, context).context
|
||||||
return report_context
|
return render(request, self._TEMPLATE, context)
|
||||||
|
|||||||
@@ -5,10 +5,12 @@ Contact: michel.peltriaux@sgdnord.rlp.de
|
|||||||
Created on: 09.08.21
|
Created on: 09.08.21
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.contrib.auth.decorators import login_required
|
|
||||||
from django.http import HttpRequest
|
from django.http import HttpRequest
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
|
from django.utils.decorators import method_decorator
|
||||||
|
from django.views import View
|
||||||
|
|
||||||
from compensation.forms.modals.payment import NewPaymentForm, RemovePaymentModalForm, EditPaymentModalForm
|
from compensation.forms.modals.payment import NewPaymentForm, RemovePaymentModalForm, EditPaymentModalForm
|
||||||
from compensation.models import Payment
|
from compensation.models import Payment
|
||||||
@@ -17,72 +19,97 @@ from konova.decorators import default_group_required, shared_access_required
|
|||||||
from konova.utils.message_templates import PAYMENT_ADDED, PAYMENT_REMOVED, PAYMENT_EDITED
|
from konova.utils.message_templates import PAYMENT_ADDED, PAYMENT_REMOVED, PAYMENT_EDITED
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
class NewPaymentView(LoginRequiredMixin, View):
|
||||||
@default_group_required
|
|
||||||
@shared_access_required(Intervention, "id")
|
|
||||||
def new_payment_view(request: HttpRequest, id: str):
|
|
||||||
""" Renders a modal view for adding new payments
|
|
||||||
|
|
||||||
Args:
|
def __process_request(self, request: HttpRequest, id: str):
|
||||||
request (HttpRequest): The incoming request
|
""" Renders a modal view for adding new payments
|
||||||
id (str): The intervention's id for which a new payment shall be added
|
|
||||||
|
|
||||||
Returns:
|
Args:
|
||||||
|
request (HttpRequest): The incoming request
|
||||||
|
id (str): The intervention's id for which a new payment shall be added
|
||||||
|
|
||||||
"""
|
Returns:
|
||||||
intervention = get_object_or_404(Intervention, id=id)
|
|
||||||
form = NewPaymentForm(request.POST or None, instance=intervention, request=request)
|
"""
|
||||||
return form.process_request(
|
intervention = get_object_or_404(Intervention, id=id)
|
||||||
request,
|
form = NewPaymentForm(request.POST or None, instance=intervention, request=request)
|
||||||
msg_success=PAYMENT_ADDED,
|
return form.process_request(
|
||||||
redirect_url=reverse("intervention:detail", args=(id,)) + "#related_data"
|
request,
|
||||||
)
|
msg_success=PAYMENT_ADDED,
|
||||||
|
redirect_url=reverse("intervention:detail", args=(id,)) + "#related_data"
|
||||||
|
)
|
||||||
|
|
||||||
|
@method_decorator(default_group_required)
|
||||||
|
@method_decorator(shared_access_required(Intervention, "id"))
|
||||||
|
def get(self, request: HttpRequest, id: str):
|
||||||
|
return self.__process_request(request, id=id)
|
||||||
|
|
||||||
|
@method_decorator(default_group_required)
|
||||||
|
@method_decorator(shared_access_required(Intervention, "id"))
|
||||||
|
def post(self, request: HttpRequest, id: str):
|
||||||
|
return self.__process_request(request, id=id)
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
class RemovePaymentView(LoginRequiredMixin, View):
|
||||||
@default_group_required
|
|
||||||
@shared_access_required(Intervention, "id")
|
|
||||||
def payment_remove_view(request: HttpRequest, id: str, payment_id: str):
|
|
||||||
""" Renders a modal view for removing payments
|
|
||||||
|
|
||||||
Args:
|
def __process_request(self, request: HttpRequest, id: str, payment_id: str):
|
||||||
request (HttpRequest): The incoming request
|
""" Renders a modal view for removing payments
|
||||||
id (str): The intervention's id
|
|
||||||
payment_id (str): The payment's id
|
|
||||||
|
|
||||||
Returns:
|
Args:
|
||||||
|
request (HttpRequest): The incoming request
|
||||||
|
id (str): The intervention's id
|
||||||
|
payment_id (str): The payment's id
|
||||||
|
|
||||||
"""
|
Returns:
|
||||||
intervention = get_object_or_404(Intervention, id=id)
|
|
||||||
payment = get_object_or_404(Payment, id=payment_id)
|
"""
|
||||||
form = RemovePaymentModalForm(request.POST or None, instance=intervention, payment=payment, request=request)
|
intervention = get_object_or_404(Intervention, id=id)
|
||||||
return form.process_request(
|
payment = get_object_or_404(Payment, id=payment_id)
|
||||||
request=request,
|
form = RemovePaymentModalForm(request.POST or None, instance=intervention, payment=payment, request=request)
|
||||||
msg_success=PAYMENT_REMOVED,
|
return form.process_request(
|
||||||
redirect_url=reverse("intervention:detail", args=(payment.intervention_id,)) + "#related_data"
|
request=request,
|
||||||
)
|
msg_success=PAYMENT_REMOVED,
|
||||||
|
redirect_url=reverse("intervention:detail", args=(payment.intervention_id,)) + "#related_data"
|
||||||
|
)
|
||||||
|
|
||||||
|
@method_decorator(default_group_required)
|
||||||
|
@method_decorator(shared_access_required(Intervention, "id"))
|
||||||
|
def get(self, request: HttpRequest, id: str, payment_id: str):
|
||||||
|
return self.__process_request(request, id=id, payment_id=payment_id)
|
||||||
|
|
||||||
|
@method_decorator(default_group_required)
|
||||||
|
@method_decorator(shared_access_required(Intervention, "id"))
|
||||||
|
def post(self, request: HttpRequest, id: str, payment_id: str):
|
||||||
|
return self.__process_request(request, id=id, payment_id=payment_id)
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
class EditPaymentView(LoginRequiredMixin, View):
|
||||||
@default_group_required
|
def __process_request(self, request: HttpRequest, id: str, payment_id: str):
|
||||||
@shared_access_required(Intervention, "id")
|
""" Renders a modal view for editing payments
|
||||||
def payment_edit_view(request: HttpRequest, id: str, payment_id: str):
|
|
||||||
""" Renders a modal view for editing payments
|
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
request (HttpRequest): The incoming request
|
request (HttpRequest): The incoming request
|
||||||
id (str): The intervention's id
|
id (str): The intervention's id
|
||||||
payment_id (str): The payment's id
|
payment_id (str): The payment's id
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
intervention = get_object_or_404(Intervention, id=id)
|
intervention = get_object_or_404(Intervention, id=id)
|
||||||
payment = get_object_or_404(Payment, id=payment_id)
|
payment = get_object_or_404(Payment, id=payment_id)
|
||||||
form = EditPaymentModalForm(request.POST or None, instance=intervention, payment=payment, request=request)
|
form = EditPaymentModalForm(request.POST or None, instance=intervention, payment=payment, request=request)
|
||||||
return form.process_request(
|
return form.process_request(
|
||||||
request=request,
|
request=request,
|
||||||
msg_success=PAYMENT_EDITED,
|
msg_success=PAYMENT_EDITED,
|
||||||
redirect_url=reverse("intervention:detail", args=(payment.intervention_id,)) + "#related_data"
|
redirect_url=reverse("intervention:detail", args=(payment.intervention_id,)) + "#related_data"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@method_decorator(default_group_required)
|
||||||
|
@method_decorator(shared_access_required(Intervention, "id"))
|
||||||
|
def get(self, request: HttpRequest, id: str, payment_id: str):
|
||||||
|
return self.__process_request(request, id=id, payment_id=payment_id)
|
||||||
|
|
||||||
|
@method_decorator(default_group_required)
|
||||||
|
@method_decorator(shared_access_required(Intervention, "id"))
|
||||||
|
def post(self, request: HttpRequest, id: str, payment_id: str):
|
||||||
|
return self.__process_request(request, id=id, payment_id=payment_id)
|
||||||
|
|||||||
+2
-2
@@ -88,7 +88,7 @@ class EmaTable(BaseTable, TableRenderMixin, TableOrderMixin):
|
|||||||
txt=value,
|
txt=value,
|
||||||
new_tab=False,
|
new_tab=False,
|
||||||
)
|
)
|
||||||
return format_html(html)
|
return format_html(html, None)
|
||||||
|
|
||||||
def render_r(self, value, record: Ema):
|
def render_r(self, value, record: Ema):
|
||||||
""" Renders the registered column for a EMA
|
""" Renders the registered column for a EMA
|
||||||
@@ -110,4 +110,4 @@ class EmaTable(BaseTable, TableRenderMixin, TableOrderMixin):
|
|||||||
tooltip=tooltip,
|
tooltip=tooltip,
|
||||||
icn_filled=recorded,
|
icn_filled=recorded,
|
||||||
)
|
)
|
||||||
return format_html(html)
|
return format_html(html, None)
|
||||||
|
|||||||
@@ -15,10 +15,10 @@
|
|||||||
<button class="btn btn-default btn-modal mr-2" title="{% trans 'Resubmission' %}" data-form-url="{% url 'ema:resubmission-create' obj.id %}">
|
<button class="btn btn-default btn-modal mr-2" title="{% trans 'Resubmission' %}" data-form-url="{% url 'ema:resubmission-create' obj.id %}">
|
||||||
{% fa5_icon 'bell' %}
|
{% fa5_icon 'bell' %}
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-default btn-modal mr-2" title="{% trans 'Share' %}" data-form-url="{% url 'ema:share-form' obj.id %}">
|
|
||||||
{% fa5_icon 'share-alt' %}
|
|
||||||
</button>
|
|
||||||
{% if is_ets_member %}
|
{% if is_ets_member %}
|
||||||
|
<button class="btn btn-default btn-modal mr-2" title="{% trans 'Share' %}" data-form-url="{% url 'ema:share-form' obj.id %}">
|
||||||
|
{% fa5_icon 'share-alt' %}
|
||||||
|
</button>
|
||||||
{% if obj.recorded %}
|
{% if obj.recorded %}
|
||||||
<button class="btn btn-default btn-modal mr-2" title="{% trans 'Unrecord' %}" data-form-url="{% url 'ema:record' obj.id %}">
|
<button class="btn btn-default btn-modal mr-2" title="{% trans 'Unrecord' %}" data-form-url="{% url 'ema:record' obj.id %}">
|
||||||
{% fa5_icon 'bookmark' 'far' %}
|
{% fa5_icon 'bookmark' 'far' %}
|
||||||
@@ -28,19 +28,21 @@
|
|||||||
{% fa5_icon 'bookmark' %}
|
{% fa5_icon 'bookmark' %}
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
<a href="{% url 'ema:edit' obj.id %}" class="mr-2">
|
||||||
|
<button class="btn btn-default" title="{% trans 'Edit' %}">
|
||||||
|
{% fa5_icon 'edit' %}
|
||||||
|
</button>
|
||||||
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if is_default_member %}
|
{% if is_default_member %}
|
||||||
<a href="{% url 'ema:edit' obj.id %}" class="mr-2">
|
<button class="btn btn-default btn-modal mr-2" data-form-url="{% url 'ema:log' obj.id %}" title="{% trans 'Show log' %}">
|
||||||
<button class="btn btn-default" title="{% trans 'Edit' %}">
|
{% fa5_icon 'history' %}
|
||||||
{% fa5_icon 'edit' %}
|
</button>
|
||||||
|
{% endif %}
|
||||||
|
{% if is_ets_member %}
|
||||||
|
<button class="btn btn-default btn-modal" data-form-url="{% url 'ema:remove' obj.id %}" title="{% trans 'Delete' %}">
|
||||||
|
{% fa5_icon 'trash' %}
|
||||||
</button>
|
</button>
|
||||||
</a>
|
|
||||||
<button class="btn btn-default btn-modal mr-2" data-form-url="{% url 'ema:log' obj.id %}" title="{% trans 'Show log' %}">
|
|
||||||
{% fa5_icon 'history' %}
|
|
||||||
</button>
|
|
||||||
<button class="btn btn-default btn-modal" data-form-url="{% url 'ema:remove' obj.id %}" title="{% trans 'Delete' %}">
|
|
||||||
{% fa5_icon 'trash' %}
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
@@ -118,6 +118,7 @@ class EmaViewTestCase(CompensationViewTestCase):
|
|||||||
self.index_url,
|
self.index_url,
|
||||||
self.detail_url,
|
self.detail_url,
|
||||||
self.report_url,
|
self.report_url,
|
||||||
|
self.log_url,
|
||||||
]
|
]
|
||||||
fail_urls = [
|
fail_urls = [
|
||||||
self.new_url,
|
self.new_url,
|
||||||
@@ -133,7 +134,6 @@ class EmaViewTestCase(CompensationViewTestCase):
|
|||||||
self.action_remove_url,
|
self.action_remove_url,
|
||||||
self.action_new_url,
|
self.action_new_url,
|
||||||
self.new_doc_url,
|
self.new_doc_url,
|
||||||
self.log_url,
|
|
||||||
self.remove_url,
|
self.remove_url,
|
||||||
]
|
]
|
||||||
self.assert_url_fail(client, fail_urls)
|
self.assert_url_fail(client, fail_urls)
|
||||||
|
|||||||
+10
-9
@@ -9,27 +9,28 @@ from django.urls import path
|
|||||||
|
|
||||||
from ema.views.action import NewEmaActionView, EditEmaActionView, RemoveEmaActionView
|
from ema.views.action import NewEmaActionView, EditEmaActionView, RemoveEmaActionView
|
||||||
from ema.views.deadline import NewEmaDeadlineView, EditEmaDeadlineView, RemoveEmaDeadlineView
|
from ema.views.deadline import NewEmaDeadlineView, EditEmaDeadlineView, RemoveEmaDeadlineView
|
||||||
|
from ema.views.detail import DetailEmaView
|
||||||
from ema.views.document import NewEmaDocumentView, EditEmaDocumentView, RemoveEmaDocumentView, GetEmaDocumentView
|
from ema.views.document import NewEmaDocumentView, EditEmaDocumentView, RemoveEmaDocumentView, GetEmaDocumentView
|
||||||
from ema.views.ema import remove_view, EmaIndexView, \
|
from ema.views.ema import IndexEmaView, EmaIdentifierGeneratorView, EditEmaView, NewEmaView
|
||||||
EmaIdentifierGeneratorView, EmaDetailView, EditEmaFormView, NewEmaFormView
|
|
||||||
from ema.views.log import EmaLogView
|
from ema.views.log import EmaLogView
|
||||||
from ema.views.record import EmaRecordView
|
from ema.views.record import EmaRecordView
|
||||||
from ema.views.report import EmaReportView
|
from ema.views.remove import RemoveEmaView
|
||||||
|
from ema.views.report import EmaPublicReportView
|
||||||
from ema.views.resubmission import EmaResubmissionView
|
from ema.views.resubmission import EmaResubmissionView
|
||||||
from ema.views.share import EmaShareFormView, EmaShareByTokenView
|
from ema.views.share import EmaShareFormView, EmaShareByTokenView
|
||||||
from ema.views.state import NewEmaStateView, EditEmaStateView, RemoveEmaStateView
|
from ema.views.state import NewEmaStateView, EditEmaStateView, RemoveEmaStateView
|
||||||
|
|
||||||
app_name = "ema"
|
app_name = "ema"
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("", EmaIndexView.as_view(), name="index"),
|
path("", IndexEmaView.as_view(), name="index"),
|
||||||
path("new/", NewEmaFormView.as_view(), name="new"),
|
path("new/", NewEmaView.as_view(), name="new"),
|
||||||
path("new/id", EmaIdentifierGeneratorView.as_view(), name="new-id"),
|
path("new/id", EmaIdentifierGeneratorView.as_view(), name="new-id"),
|
||||||
path("<id>", EmaDetailView.as_view(), name="detail"),
|
path("<id>", DetailEmaView.as_view(), name="detail"),
|
||||||
path('<id>/log', EmaLogView.as_view(), name='log'),
|
path('<id>/log', EmaLogView.as_view(), name='log'),
|
||||||
path('<id>/edit', EditEmaFormView.as_view(), name='edit'),
|
path('<id>/edit', EditEmaView.as_view(), name='edit'),
|
||||||
path('<id>/remove', remove_view, name='remove'),
|
path('<id>/remove', RemoveEmaView.as_view(), name='remove'),
|
||||||
path('<id>/record', EmaRecordView.as_view(), name='record'),
|
path('<id>/record', EmaRecordView.as_view(), name='record'),
|
||||||
path('<id>/report', EmaReportView.as_view(), name='report'),
|
path('<id>/report', EmaPublicReportView.as_view(), name='report'),
|
||||||
path('<id>/resub', EmaResubmissionView.as_view(), name='resubmission-create'),
|
path('<id>/resub', EmaResubmissionView.as_view(), name='resubmission-create'),
|
||||||
|
|
||||||
path('<id>/state/new', NewEmaStateView.as_view(), name='new-state'),
|
path('<id>/state/new', NewEmaStateView.as_view(), name='new-state'),
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
"""
|
||||||
|
Author: Michel Peltriaux
|
||||||
|
Created on: 14.12.25
|
||||||
|
|
||||||
|
"""
|
||||||
|
from django.contrib import messages
|
||||||
|
from django.http import HttpResponse, HttpRequest
|
||||||
|
from django.shortcuts import get_object_or_404, render
|
||||||
|
|
||||||
|
from ema.models import Ema
|
||||||
|
from konova.contexts import BaseContext
|
||||||
|
from konova.forms import SimpleGeomForm
|
||||||
|
from konova.settings import DEFAULT_GROUP, ZB_GROUP, ETS_GROUP
|
||||||
|
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
|
||||||
|
from konova.utils.message_templates import DO_NOT_FORGET_TO_SHARE
|
||||||
|
from konova.views.detail import AbstractDetailView
|
||||||
|
|
||||||
|
|
||||||
|
class DetailEmaView(AbstractDetailView):
|
||||||
|
_TEMPLATE = "ema/detail/view.html"
|
||||||
|
|
||||||
|
def get(self, request: HttpRequest, id: str, *args, **kwargs) -> HttpResponse:
|
||||||
|
""" Renders the detail view of an EMA
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request (HttpRequest): The incoming request
|
||||||
|
id (str): The EMA id
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
ema = get_object_or_404(Ema, id=id, deleted=None)
|
||||||
|
|
||||||
|
geom_form = SimpleGeomForm(instance=ema)
|
||||||
|
parcels = ema.get_underlying_parcels()
|
||||||
|
_user = request.user
|
||||||
|
is_entry_shared = ema.is_shared_with(_user)
|
||||||
|
|
||||||
|
# Order states according to surface
|
||||||
|
before_states = ema.before_states.all().order_by("-surface")
|
||||||
|
after_states = ema.after_states.all().order_by("-surface")
|
||||||
|
|
||||||
|
# Precalculate logical errors between before- and after-states
|
||||||
|
# Sum() returns None in case of no states, so we catch that and replace it with 0 for easier handling
|
||||||
|
sum_before_states = ema.get_surface_before_states()
|
||||||
|
sum_after_states = ema.get_surface_after_states()
|
||||||
|
diff_states = abs(sum_before_states - sum_after_states)
|
||||||
|
|
||||||
|
ema.set_status_messages(request)
|
||||||
|
|
||||||
|
requesting_user_is_only_shared_user = ema.is_only_shared_with(_user)
|
||||||
|
if requesting_user_is_only_shared_user:
|
||||||
|
messages.info(
|
||||||
|
request,
|
||||||
|
DO_NOT_FORGET_TO_SHARE
|
||||||
|
)
|
||||||
|
|
||||||
|
context = {
|
||||||
|
"obj": ema,
|
||||||
|
"geom_form": geom_form,
|
||||||
|
"parcels": parcels,
|
||||||
|
"is_entry_shared": is_entry_shared,
|
||||||
|
"before_states": before_states,
|
||||||
|
"after_states": after_states,
|
||||||
|
"sum_before_states": sum_before_states,
|
||||||
|
"sum_after_states": sum_after_states,
|
||||||
|
"diff_states": diff_states,
|
||||||
|
"is_default_member": _user.in_group(DEFAULT_GROUP),
|
||||||
|
"is_zb_member": _user.in_group(ZB_GROUP),
|
||||||
|
"is_ets_member": _user.in_group(ETS_GROUP),
|
||||||
|
"LANIS_LINK": ema.get_LANIS_link(),
|
||||||
|
TAB_TITLE_IDENTIFIER: f"{ema.identifier} - {ema.title}",
|
||||||
|
"has_finished_deadlines": ema.get_finished_deadlines().exists(),
|
||||||
|
}
|
||||||
|
context = BaseContext(request, context).context
|
||||||
|
return render(request, self._TEMPLATE, context)
|
||||||
+202
-107
@@ -5,133 +5,228 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
|
|||||||
Created on: 19.08.22
|
Created on: 19.08.22
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
from django.contrib import messages
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
from django.http import HttpRequest
|
from django.http import HttpRequest, HttpResponse
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404, redirect, render
|
||||||
from django.urls import reverse
|
from django.utils.decorators import method_decorator
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from django.views.generic.base import View
|
||||||
|
|
||||||
from ema.forms import NewEmaForm, EditEmaForm
|
from ema.forms import NewEmaForm, EditEmaForm
|
||||||
from ema.models import Ema
|
from ema.models import Ema
|
||||||
from ema.tables import EmaTable
|
from ema.tables import EmaTable
|
||||||
from konova.decorators import shared_access_required, conservation_office_group_required, login_required_modal
|
from konova.contexts import BaseContext
|
||||||
from konova.forms.modals import RemoveModalForm
|
from konova.decorators import shared_access_required, conservation_office_group_required
|
||||||
from konova.views.base import BaseIndexView, BaseIdentifierGeneratorView, BaseNewSpatialLocatedObjectFormView, \
|
from konova.forms import SimpleGeomForm
|
||||||
BaseEditSpatialLocatedObjectFormView
|
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
|
||||||
from konova.views.detail import BaseDetailView
|
from konova.utils.message_templates import RECORDED_BLOCKS_EDIT, IDENTIFIER_REPLACED, FORM_INVALID, \
|
||||||
|
GEOMETRY_SIMPLIFIED, GEOMETRIES_IGNORED_TEMPLATE
|
||||||
|
from konova.views.identifier import AbstractIdentifierGeneratorView
|
||||||
|
from konova.views.index import AbstractIndexView
|
||||||
|
|
||||||
|
|
||||||
class EmaIndexView(LoginRequiredMixin, BaseIndexView):
|
class IndexEmaView(AbstractIndexView):
|
||||||
_TAB_TITLE = _("EMAs - Overview")
|
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
||||||
_INDEX_TABLE_CLS = EmaTable
|
""" Renders the index view for EMAs
|
||||||
|
|
||||||
def _get_queryset(self):
|
Args:
|
||||||
qs = Ema.objects.filter(
|
request (HttpRequest): The incoming request
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
emas = Ema.objects.filter(
|
||||||
deleted=None,
|
deleted=None,
|
||||||
).order_by(
|
).order_by(
|
||||||
"-modified__timestamp"
|
"-modified__timestamp"
|
||||||
)
|
)
|
||||||
return qs
|
|
||||||
|
|
||||||
|
|
||||||
class NewEmaFormView(BaseNewSpatialLocatedObjectFormView):
|
|
||||||
_FORM_CLS = NewEmaForm
|
|
||||||
_MODEL_CLS = Ema
|
|
||||||
_TEMPLATE = "ema/form/view.html"
|
|
||||||
_TAB_TITLE = _("New EMA")
|
|
||||||
_REDIRECT_URL = "ema:detail"
|
|
||||||
|
|
||||||
def _user_has_permission(self, user):
|
|
||||||
# User has to be an ets user
|
|
||||||
return user.is_ets_user()
|
|
||||||
|
|
||||||
|
|
||||||
class EditEmaFormView(BaseEditSpatialLocatedObjectFormView):
|
|
||||||
_MODEL_CLS = Ema
|
|
||||||
_FORM_CLS = EditEmaForm
|
|
||||||
_TEMPLATE = "ema/form/view.html"
|
|
||||||
_REDIRECT_URL = "ema:detail"
|
|
||||||
_TAB_TITLE = _("Edit {}")
|
|
||||||
|
|
||||||
def _user_has_permission(self, user):
|
|
||||||
# User has to be an ets user
|
|
||||||
return user.is_ets_user()
|
|
||||||
|
|
||||||
|
|
||||||
class EmaIdentifierGeneratorView(LoginRequiredMixin, BaseIdentifierGeneratorView):
|
|
||||||
_MODEL_CLS = Ema
|
|
||||||
_REDIRECT_URL = "ema:index"
|
|
||||||
|
|
||||||
def _user_has_permission(self, user):
|
|
||||||
return user.is_ets_user()
|
|
||||||
|
|
||||||
|
|
||||||
class EmaDetailView(BaseDetailView):
|
|
||||||
_MODEL_CLS = Ema
|
|
||||||
_TEMPLATE = "ema/detail/view.html"
|
|
||||||
|
|
||||||
def _get_object(self, id: str):
|
|
||||||
""" Fetch object for detail view
|
|
||||||
|
|
||||||
Args:
|
|
||||||
id (str): The record's id'
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
|
|
||||||
"""
|
|
||||||
ema = get_object_or_404(Ema, id=id, deleted=None)
|
|
||||||
return ema
|
|
||||||
|
|
||||||
def _get_detail_context(self, obj: Ema):
|
|
||||||
""" Generate object specific detail context for view
|
|
||||||
|
|
||||||
Args:
|
|
||||||
obj (): The record
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
|
|
||||||
"""
|
|
||||||
# Order states according to surface
|
|
||||||
before_states = obj.before_states.all().order_by("-surface")
|
|
||||||
after_states = obj.after_states.all().order_by("-surface")
|
|
||||||
|
|
||||||
# Precalculate logical errors between before- and after-states
|
|
||||||
# Sum() returns None in case of no states, so we catch that and replace it with 0 for easier handling
|
|
||||||
sum_before_states = obj.get_surface_before_states()
|
|
||||||
sum_after_states = obj.get_surface_after_states()
|
|
||||||
diff_states = abs(sum_before_states - sum_after_states)
|
|
||||||
|
|
||||||
|
table = EmaTable(
|
||||||
|
request,
|
||||||
|
queryset=emas
|
||||||
|
)
|
||||||
context = {
|
context = {
|
||||||
"before_states": before_states,
|
"table": table,
|
||||||
"after_states": after_states,
|
TAB_TITLE_IDENTIFIER: _("EMAs - Overview"),
|
||||||
"sum_before_states": sum_before_states,
|
|
||||||
"sum_after_states": sum_after_states,
|
|
||||||
"diff_states": diff_states,
|
|
||||||
"has_finished_deadlines": obj.get_finished_deadlines().exists(),
|
|
||||||
}
|
}
|
||||||
return context
|
context = BaseContext(request, context).context
|
||||||
|
return render(request, self._TEMPLATE, context)
|
||||||
|
|
||||||
|
class NewEmaView(LoginRequiredMixin, View):
|
||||||
|
_TEMPLATE = "ema/form/view.html"
|
||||||
|
|
||||||
@login_required_modal
|
@method_decorator(conservation_office_group_required)
|
||||||
@login_required
|
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
||||||
@conservation_office_group_required
|
""" GET endpoint
|
||||||
@shared_access_required(Ema, "id")
|
|
||||||
def remove_view(request: HttpRequest, id: str):
|
|
||||||
""" Renders a modal view for removing the EMA
|
|
||||||
|
|
||||||
Args:
|
Renders form for new EMA
|
||||||
request (HttpRequest): The incoming request
|
|
||||||
id (str): The EMA's id
|
|
||||||
|
|
||||||
Returns:
|
Args:
|
||||||
|
request (HttpRequest): The incoming request
|
||||||
|
*args ():
|
||||||
|
**kwargs ():
|
||||||
|
|
||||||
"""
|
Returns:
|
||||||
ema = get_object_or_404(Ema, id=id)
|
|
||||||
form = RemoveModalForm(request.POST or None, instance=ema, request=request)
|
|
||||||
return form.process_request(
|
|
||||||
request=request,
|
|
||||||
msg_success=_("EMA removed"),
|
|
||||||
redirect_url=reverse("ema:index"),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
data_form = NewEmaForm(request.POST or None)
|
||||||
|
geom_form = SimpleGeomForm(request.POST or None, read_only=False)
|
||||||
|
context = {
|
||||||
|
"form": data_form,
|
||||||
|
"geom_form": geom_form,
|
||||||
|
TAB_TITLE_IDENTIFIER: _("New EMA"),
|
||||||
|
}
|
||||||
|
context = BaseContext(request, context).context
|
||||||
|
return render(request, self._TEMPLATE, context)
|
||||||
|
|
||||||
|
@method_decorator(conservation_office_group_required)
|
||||||
|
def post(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
||||||
|
""" POST endpoint
|
||||||
|
|
||||||
|
Processes submitted form
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request (HttpRequest): The incoming request
|
||||||
|
*args ():
|
||||||
|
**kwargs ():
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
data_form = NewEmaForm(request.POST or None)
|
||||||
|
geom_form = SimpleGeomForm(request.POST or None, read_only=False)
|
||||||
|
if data_form.is_valid() and geom_form.is_valid():
|
||||||
|
generated_identifier = data_form.cleaned_data.get("identifier", None)
|
||||||
|
ema = data_form.save(request.user, geom_form)
|
||||||
|
if generated_identifier != ema.identifier:
|
||||||
|
messages.info(
|
||||||
|
request,
|
||||||
|
IDENTIFIER_REPLACED.format(
|
||||||
|
generated_identifier,
|
||||||
|
ema.identifier
|
||||||
|
)
|
||||||
|
)
|
||||||
|
messages.success(request, _("EMA {} added").format(ema.identifier))
|
||||||
|
if geom_form.has_geometry_simplified():
|
||||||
|
messages.info(
|
||||||
|
request,
|
||||||
|
GEOMETRY_SIMPLIFIED
|
||||||
|
)
|
||||||
|
num_ignored_geometries = geom_form.get_num_geometries_ignored()
|
||||||
|
if num_ignored_geometries > 0:
|
||||||
|
messages.info(
|
||||||
|
request,
|
||||||
|
GEOMETRIES_IGNORED_TEMPLATE.format(num_ignored_geometries)
|
||||||
|
)
|
||||||
|
return redirect("ema:detail", id=ema.id)
|
||||||
|
else:
|
||||||
|
messages.error(request, FORM_INVALID, extra_tags="danger",)
|
||||||
|
context = {
|
||||||
|
"form": data_form,
|
||||||
|
"geom_form": geom_form,
|
||||||
|
TAB_TITLE_IDENTIFIER: _("New EMA"),
|
||||||
|
}
|
||||||
|
context = BaseContext(request, context).context
|
||||||
|
return render(request, self._TEMPLATE, context)
|
||||||
|
|
||||||
|
class EmaIdentifierGeneratorView(AbstractIdentifierGeneratorView):
|
||||||
|
_MODEL = Ema
|
||||||
|
|
||||||
|
@method_decorator(conservation_office_group_required)
|
||||||
|
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
||||||
|
return super().get(request, *args, **kwargs)
|
||||||
|
|
||||||
|
class EditEmaView(LoginRequiredMixin, View):
|
||||||
|
_TEMPLATE = "compensation/form/view.html"
|
||||||
|
|
||||||
|
@method_decorator(conservation_office_group_required)
|
||||||
|
@method_decorator(shared_access_required(Ema, "id"))
|
||||||
|
def get(self, request: HttpRequest, id: str, *args, **kwargs) -> HttpResponse:
|
||||||
|
""" GET endpoint
|
||||||
|
|
||||||
|
Renders form
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request (HttpRequest): The incoming request
|
||||||
|
id (str): The ema identifier
|
||||||
|
*args ():
|
||||||
|
**kwargs ():
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
# Get object from db
|
||||||
|
ema = get_object_or_404(Ema, id=id)
|
||||||
|
if ema.is_recorded:
|
||||||
|
messages.info(
|
||||||
|
request,
|
||||||
|
RECORDED_BLOCKS_EDIT
|
||||||
|
)
|
||||||
|
return redirect("ema:detail", id=id)
|
||||||
|
|
||||||
|
# Create forms, initialize with values from db/from POST request
|
||||||
|
data_form = EditEmaForm(instance=ema)
|
||||||
|
geom_form = SimpleGeomForm(read_only=False, instance=ema)
|
||||||
|
context = {
|
||||||
|
"form": data_form,
|
||||||
|
"geom_form": geom_form,
|
||||||
|
TAB_TITLE_IDENTIFIER: _("Edit {}").format(ema.identifier),
|
||||||
|
}
|
||||||
|
context = BaseContext(request, context).context
|
||||||
|
return render(request, self._TEMPLATE, context)
|
||||||
|
|
||||||
|
@method_decorator(conservation_office_group_required)
|
||||||
|
@method_decorator(shared_access_required(Ema, "id"))
|
||||||
|
def post(self, request: HttpRequest, id:str, *args, **kwargs) -> HttpResponse:
|
||||||
|
""" POST endpoint
|
||||||
|
|
||||||
|
Process submitted forms
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request (HttpRequest): The incoming request
|
||||||
|
id (str): The id of the ema
|
||||||
|
*args ():
|
||||||
|
**kwargs ():
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
# Get object from db
|
||||||
|
ema = get_object_or_404(Ema, id=id)
|
||||||
|
if ema.is_recorded:
|
||||||
|
messages.info(
|
||||||
|
request,
|
||||||
|
RECORDED_BLOCKS_EDIT
|
||||||
|
)
|
||||||
|
return redirect("ema:detail", id=id)
|
||||||
|
|
||||||
|
# Create forms, initialize with values from db/from POST request
|
||||||
|
data_form = EditEmaForm(request.POST or None, instance=ema)
|
||||||
|
geom_form = SimpleGeomForm(request.POST or None, read_only=False, instance=ema)
|
||||||
|
if data_form.is_valid() and geom_form.is_valid():
|
||||||
|
# The data form takes the geom form for processing, as well as the performing user
|
||||||
|
ema = data_form.save(request.user, geom_form)
|
||||||
|
messages.success(request, _("EMA {} edited").format(ema.identifier))
|
||||||
|
if geom_form.has_geometry_simplified():
|
||||||
|
messages.info(
|
||||||
|
request,
|
||||||
|
GEOMETRY_SIMPLIFIED
|
||||||
|
)
|
||||||
|
num_ignored_geometries = geom_form.get_num_geometries_ignored()
|
||||||
|
if num_ignored_geometries > 0:
|
||||||
|
messages.info(
|
||||||
|
request,
|
||||||
|
GEOMETRIES_IGNORED_TEMPLATE.format(num_ignored_geometries)
|
||||||
|
)
|
||||||
|
return redirect("ema:detail", id=ema.id)
|
||||||
|
else:
|
||||||
|
messages.error(request, FORM_INVALID, extra_tags="danger", )
|
||||||
|
context = {
|
||||||
|
"form": data_form,
|
||||||
|
"geom_form": geom_form,
|
||||||
|
TAB_TITLE_IDENTIFIER: _("Edit {}").format(ema.identifier),
|
||||||
|
}
|
||||||
|
context = BaseContext(request, context).context
|
||||||
|
return render(request, self._TEMPLATE, context)
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ class EmaLogView(AbstractLogView):
|
|||||||
|
|
||||||
@method_decorator(login_required_modal)
|
@method_decorator(login_required_modal)
|
||||||
@method_decorator(login_required)
|
@method_decorator(login_required)
|
||||||
@method_decorator(conservation_office_group_required)
|
|
||||||
@method_decorator(shared_access_required(Ema, "id"))
|
@method_decorator(shared_access_required(Ema, "id"))
|
||||||
def dispatch(self, request, *args, **kwargs):
|
def dispatch(self, request, *args, **kwargs):
|
||||||
return super().dispatch(request, *args, **kwargs)
|
return super().dispatch(request, *args, **kwargs)
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
"""
|
||||||
|
Author: Michel Peltriaux
|
||||||
|
Created on: 14.12.25
|
||||||
|
|
||||||
|
"""
|
||||||
|
from django.http import HttpRequest, HttpResponse
|
||||||
|
from django.utils.decorators import method_decorator
|
||||||
|
|
||||||
|
from ema.models import Ema
|
||||||
|
from konova.decorators import shared_access_required, conservation_office_group_required
|
||||||
|
from konova.views.remove import AbstractRemoveView
|
||||||
|
|
||||||
|
|
||||||
|
class RemoveEmaView(AbstractRemoveView):
|
||||||
|
_MODEL = Ema
|
||||||
|
_REDIRECT_URL = "ema:index"
|
||||||
|
|
||||||
|
@method_decorator(conservation_office_group_required)
|
||||||
|
@method_decorator(shared_access_required(Ema, "id"))
|
||||||
|
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
||||||
|
return super().get(request, *args, **kwargs)
|
||||||
+59
-14
@@ -5,36 +5,81 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
|
|||||||
Created on: 19.08.22
|
Created on: 19.08.22
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
from django.http import HttpRequest, HttpResponse
|
||||||
|
from django.shortcuts import get_object_or_404, render
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from compensation.views.compensation.report import BaseCompensationReportView
|
|
||||||
from ema.models import Ema
|
from ema.models import Ema
|
||||||
from konova.sub_settings.django_settings import BASE_URL
|
from konova.contexts import BaseContext
|
||||||
|
from konova.forms import SimpleGeomForm
|
||||||
|
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
|
||||||
from konova.utils.qrcode import QrCode
|
from konova.utils.qrcode import QrCode
|
||||||
|
from konova.views.report import AbstractPublicReportView
|
||||||
|
|
||||||
|
|
||||||
class EmaReportView(BaseCompensationReportView):
|
class EmaPublicReportView(AbstractPublicReportView):
|
||||||
_TEMPLATE = "ema/report/report.html"
|
_TEMPLATE = "ema/report/report.html"
|
||||||
_MODEL = Ema
|
|
||||||
|
|
||||||
def _get_report_context(self, obj):
|
def get(self, request: HttpRequest, id: str, *args, **kwargs) -> HttpResponse:
|
||||||
report_url = BASE_URL + reverse("ema:report", args=(obj.id,))
|
""" Renders the public report view
|
||||||
qrcode_report = QrCode(report_url, 10)
|
|
||||||
qrcode_lanis = QrCode(obj.get_LANIS_link(), 7)
|
|
||||||
|
|
||||||
generic_compensation_report_context = self._get_compensation_report_context(obj)
|
Args:
|
||||||
|
request (HttpRequest): The incoming request
|
||||||
|
id (str): The id of the intervention
|
||||||
|
|
||||||
report_context = {
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
ema = get_object_or_404(Ema, id=id)
|
||||||
|
tab_title = _("Report {}").format(ema.identifier)
|
||||||
|
# If intervention is not recorded (yet or currently) we need to render another template without any data
|
||||||
|
if not ema.is_ready_for_publish():
|
||||||
|
template = "report/unavailable.html"
|
||||||
|
context = {
|
||||||
|
TAB_TITLE_IDENTIFIER: tab_title,
|
||||||
|
}
|
||||||
|
context = BaseContext(request, context).context
|
||||||
|
return render(request, template, context)
|
||||||
|
|
||||||
|
# Prepare data for map viewer
|
||||||
|
geom_form = SimpleGeomForm(
|
||||||
|
instance=ema,
|
||||||
|
)
|
||||||
|
parcels = ema.get_underlying_parcels()
|
||||||
|
|
||||||
|
qrcode = QrCode(
|
||||||
|
content=request.build_absolute_uri(reverse("ema:report", args=(id,))),
|
||||||
|
size=10
|
||||||
|
)
|
||||||
|
qrcode_lanis = QrCode(
|
||||||
|
content=ema.get_LANIS_link(),
|
||||||
|
size=7
|
||||||
|
)
|
||||||
|
|
||||||
|
# Order states by surface
|
||||||
|
before_states = ema.before_states.all().order_by("-surface").prefetch_related("biotope_type")
|
||||||
|
after_states = ema.after_states.all().order_by("-surface").prefetch_related("biotope_type")
|
||||||
|
actions = ema.actions.all().prefetch_related("action_type")
|
||||||
|
|
||||||
|
context = {
|
||||||
|
"obj": ema,
|
||||||
"qrcode": {
|
"qrcode": {
|
||||||
"img": qrcode_report.get_img(),
|
"img": qrcode.get_img(),
|
||||||
"url": qrcode_report.get_content(),
|
"url": qrcode.get_content(),
|
||||||
},
|
},
|
||||||
"qrcode_lanis": {
|
"qrcode_lanis": {
|
||||||
"img": qrcode_lanis.get_img(),
|
"img": qrcode_lanis.get_img(),
|
||||||
"url": qrcode_lanis.get_content(),
|
"url": qrcode_lanis.get_content(),
|
||||||
},
|
},
|
||||||
"is_entry_shared": False, # disables action buttons during rendering
|
"is_entry_shared": False, # disables action buttons during rendering
|
||||||
|
"before_states": before_states,
|
||||||
|
"after_states": after_states,
|
||||||
|
"geom_form": geom_form,
|
||||||
|
"parcels": parcels,
|
||||||
|
"actions": actions,
|
||||||
"tables_scrollable": False,
|
"tables_scrollable": False,
|
||||||
|
TAB_TITLE_IDENTIFIER: tab_title,
|
||||||
}
|
}
|
||||||
report_context.update(generic_compensation_report_context)
|
context = BaseContext(request, context).context
|
||||||
return report_context
|
return render(request, self._TEMPLATE, context)
|
||||||
@@ -37,6 +37,14 @@ class InterventionAdmin(BaseObjectAdmin):
|
|||||||
"geometry",
|
"geometry",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def get_actions(self, request):
|
||||||
|
DELETE_ACTION_IDENTIFIER = "delete_selected"
|
||||||
|
actions = super().get_actions(request)
|
||||||
|
|
||||||
|
if DELETE_ACTION_IDENTIFIER in actions:
|
||||||
|
del actions[DELETE_ACTION_IDENTIFIER]
|
||||||
|
|
||||||
|
return actions
|
||||||
|
|
||||||
class InterventionDocumentAdmin(AbstractDocumentAdmin):
|
class InterventionDocumentAdmin(AbstractDocumentAdmin):
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -172,8 +172,7 @@ class EditEcoAccountDeductionModalForm(NewEcoAccountDeductionModalForm):
|
|||||||
deduction = None
|
deduction = None
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
deduction_id = kwargs.pop("deduction_id", None)
|
self.deduction = kwargs.pop("deduction", None)
|
||||||
self.deduction = EcoAccountDeduction.objects.get(id=deduction_id)
|
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.form_title = _("Edit Deduction")
|
self.form_title = _("Edit Deduction")
|
||||||
form_data = {
|
form_data = {
|
||||||
@@ -253,20 +252,19 @@ class RemoveEcoAccountDeductionModalForm(RemoveModalForm):
|
|||||||
Can be used for anything, where removing shall be confirmed by the user a second time.
|
Can be used for anything, where removing shall be confirmed by the user a second time.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
_DEDUCTION_OBJ = None
|
deduction = None
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
deduction_id = kwargs.pop("deduction_id", None)
|
deduction = kwargs.pop("deduction", None)
|
||||||
deduction = EcoAccountDeduction.objects.get(id=deduction_id)
|
self.deduction = deduction
|
||||||
self._DEDUCTION_OBJ = deduction
|
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
def save(self):
|
def save(self):
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
self._DEDUCTION_OBJ.intervention.mark_as_edited(self.user, edit_comment=DEDUCTION_REMOVED)
|
self.deduction.intervention.mark_as_edited(self.user, edit_comment=DEDUCTION_REMOVED)
|
||||||
self._DEDUCTION_OBJ.account.mark_as_edited(self.user, edit_comment=DEDUCTION_REMOVED)
|
self.deduction.account.mark_as_edited(self.user, edit_comment=DEDUCTION_REMOVED)
|
||||||
self._DEDUCTION_OBJ.delete()
|
self.deduction.delete()
|
||||||
|
|
||||||
def check_for_recorded_instance(self):
|
def check_for_recorded_instance(self):
|
||||||
if self._DEDUCTION_OBJ.intervention.is_recorded:
|
if self.deduction.intervention.is_recorded:
|
||||||
self.block_form()
|
self.block_form()
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ class InterventionTable(BaseTable, TableRenderMixin, TableOrderMixin):
|
|||||||
html += self.render_previously_checked_star(
|
html += self.render_previously_checked_star(
|
||||||
tooltip=tooltip,
|
tooltip=tooltip,
|
||||||
)
|
)
|
||||||
return format_html(html)
|
return format_html(html, None)
|
||||||
|
|
||||||
def render_r(self, value, record: Intervention):
|
def render_r(self, value, record: Intervention):
|
||||||
""" Renders the recorded column for an intervention
|
""" Renders the recorded column for an intervention
|
||||||
@@ -149,5 +149,5 @@ class InterventionTable(BaseTable, TableRenderMixin, TableOrderMixin):
|
|||||||
tooltip=tooltip,
|
tooltip=tooltip,
|
||||||
icn_filled=checked,
|
icn_filled=checked,
|
||||||
)
|
)
|
||||||
return format_html(html)
|
return format_html(html, None)
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ class InterventionViewTestCase(BaseViewTestCase):
|
|||||||
self.run_check_url = reverse("intervention:check", args=(self.intervention.id,))
|
self.run_check_url = reverse("intervention:check", args=(self.intervention.id,))
|
||||||
self.record_url = reverse("intervention:record", args=(self.intervention.id,))
|
self.record_url = reverse("intervention:record", args=(self.intervention.id,))
|
||||||
self.report_url = reverse("intervention:report", args=(self.intervention.id,))
|
self.report_url = reverse("intervention:report", args=(self.intervention.id,))
|
||||||
|
self.compensation_remove_url = reverse("intervention:remove-compensation", args=(self.compensation.intervention.id, self.compensation.id))
|
||||||
|
|
||||||
self.deduction.intervention = self.intervention
|
self.deduction.intervention = self.intervention
|
||||||
self.deduction.save()
|
self.deduction.save()
|
||||||
@@ -83,6 +84,7 @@ class InterventionViewTestCase(BaseViewTestCase):
|
|||||||
self.revocation_new_url: f"{login_redirect_base}{self.revocation_new_url}",
|
self.revocation_new_url: f"{login_redirect_base}{self.revocation_new_url}",
|
||||||
self.revocation_edit_url: f"{login_redirect_base}{self.revocation_edit_url}",
|
self.revocation_edit_url: f"{login_redirect_base}{self.revocation_edit_url}",
|
||||||
self.revocation_remove_url: f"{login_redirect_base}{self.revocation_remove_url}",
|
self.revocation_remove_url: f"{login_redirect_base}{self.revocation_remove_url}",
|
||||||
|
self.compensation_remove_url: f"{login_redirect_base}{self.compensation_remove_url}",
|
||||||
}
|
}
|
||||||
|
|
||||||
self.assert_url_success(client, success_urls)
|
self.assert_url_success(client, success_urls)
|
||||||
@@ -124,6 +126,7 @@ class InterventionViewTestCase(BaseViewTestCase):
|
|||||||
self.deduction_new_url,
|
self.deduction_new_url,
|
||||||
self.deduction_edit_url,
|
self.deduction_edit_url,
|
||||||
self.deduction_remove_url,
|
self.deduction_remove_url,
|
||||||
|
self.compensation_remove_url,
|
||||||
]
|
]
|
||||||
|
|
||||||
self.assert_url_success(client, success_urls)
|
self.assert_url_success(client, success_urls)
|
||||||
@@ -162,6 +165,7 @@ class InterventionViewTestCase(BaseViewTestCase):
|
|||||||
self.deduction_new_url,
|
self.deduction_new_url,
|
||||||
self.deduction_edit_url,
|
self.deduction_edit_url,
|
||||||
self.deduction_remove_url,
|
self.deduction_remove_url,
|
||||||
|
self.compensation_remove_url,
|
||||||
]
|
]
|
||||||
fail_urls = [
|
fail_urls = [
|
||||||
self.run_check_url,
|
self.run_check_url,
|
||||||
@@ -212,6 +216,7 @@ class InterventionViewTestCase(BaseViewTestCase):
|
|||||||
self.deduction_new_url,
|
self.deduction_new_url,
|
||||||
self.deduction_edit_url,
|
self.deduction_edit_url,
|
||||||
self.deduction_remove_url,
|
self.deduction_remove_url,
|
||||||
|
self.compensation_remove_url,
|
||||||
]
|
]
|
||||||
success_urls_redirect = {
|
success_urls_redirect = {
|
||||||
self.share_url: self.detail_url
|
self.share_url: self.detail_url
|
||||||
@@ -258,6 +263,7 @@ class InterventionViewTestCase(BaseViewTestCase):
|
|||||||
self.deduction_new_url,
|
self.deduction_new_url,
|
||||||
self.deduction_edit_url,
|
self.deduction_edit_url,
|
||||||
self.deduction_remove_url,
|
self.deduction_remove_url,
|
||||||
|
self.compensation_remove_url,
|
||||||
]
|
]
|
||||||
success_urls_redirect = {
|
success_urls_redirect = {
|
||||||
self.share_url: self.detail_url
|
self.share_url: self.detail_url
|
||||||
@@ -304,6 +310,7 @@ class InterventionViewTestCase(BaseViewTestCase):
|
|||||||
self.deduction_new_url,
|
self.deduction_new_url,
|
||||||
self.deduction_edit_url,
|
self.deduction_edit_url,
|
||||||
self.deduction_remove_url,
|
self.deduction_remove_url,
|
||||||
|
self.compensation_remove_url,
|
||||||
]
|
]
|
||||||
success_urls_redirect = {
|
success_urls_redirect = {
|
||||||
self.share_url: self.detail_url
|
self.share_url: self.detail_url
|
||||||
@@ -350,6 +357,7 @@ class InterventionViewTestCase(BaseViewTestCase):
|
|||||||
self.deduction_new_url,
|
self.deduction_new_url,
|
||||||
self.deduction_edit_url,
|
self.deduction_edit_url,
|
||||||
self.deduction_remove_url,
|
self.deduction_remove_url,
|
||||||
|
self.compensation_remove_url,
|
||||||
]
|
]
|
||||||
success_urls_redirect = {
|
success_urls_redirect = {
|
||||||
self.share_url: self.detail_url
|
self.share_url: self.detail_url
|
||||||
@@ -396,6 +404,7 @@ class InterventionViewTestCase(BaseViewTestCase):
|
|||||||
self.deduction_new_url,
|
self.deduction_new_url,
|
||||||
self.deduction_edit_url,
|
self.deduction_edit_url,
|
||||||
self.deduction_remove_url,
|
self.deduction_remove_url,
|
||||||
|
self.compensation_remove_url,
|
||||||
]
|
]
|
||||||
# Define urls where a redirect to a specific location is the proper response
|
# Define urls where a redirect to a specific location is the proper response
|
||||||
success_urls_redirect = {
|
success_urls_redirect = {
|
||||||
|
|||||||
+19
-18
@@ -9,40 +9,41 @@ from django.urls import path
|
|||||||
|
|
||||||
from intervention.autocomplete.intervention import InterventionAutocomplete
|
from intervention.autocomplete.intervention import InterventionAutocomplete
|
||||||
from intervention.views.check import InterventionCheckView
|
from intervention.views.check import InterventionCheckView
|
||||||
from intervention.views.compensation import remove_compensation_view
|
from intervention.views.compensation import RemoveCompensationFromInterventionView
|
||||||
from intervention.views.deduction import NewInterventionDeductionView, EditInterventionDeductionView, \
|
from intervention.views.deduction import NewInterventionDeductionView, EditInterventionDeductionView, \
|
||||||
RemoveInterventionDeductionView
|
RemoveInterventionDeductionView
|
||||||
from intervention.views.document import NewInterventionDocumentView, GetInterventionDocumentView, \
|
from intervention.views.document import NewInterventionDocumentView, GetInterventionDocumentView, \
|
||||||
RemoveInterventionDocumentView, EditInterventionDocumentView
|
RemoveInterventionDocumentView, EditInterventionDocumentView
|
||||||
from intervention.views.intervention import remove_view, \
|
from intervention.views.intervention import IndexInterventionView, InterventionIdentifierGeneratorView, \
|
||||||
InterventionIndexView, InterventionIdentifierGeneratorView, InterventionDetailView, NewInterventionFormView, \
|
NewInterventionView, EditInterventionView
|
||||||
EditInterventionFormView
|
from intervention.views.remove import RemoveInterventionView
|
||||||
|
from intervention.views.detail import DetailInterventionView
|
||||||
from intervention.views.log import InterventionLogView
|
from intervention.views.log import InterventionLogView
|
||||||
from intervention.views.record import InterventionRecordView
|
from intervention.views.record import InterventionRecordView
|
||||||
from intervention.views.report import InterventionReportView
|
from intervention.views.report import InterventionPublicReportView
|
||||||
from intervention.views.resubmission import InterventionResubmissionView
|
from intervention.views.resubmission import InterventionResubmissionView
|
||||||
from intervention.views.revocation import new_revocation_view, edit_revocation_view, remove_revocation_view, \
|
from intervention.views.revocation import NewInterventionRevocationView, GetInterventionRevocationView, \
|
||||||
get_revocation_view
|
EditInterventionRevocationView, RemoveInterventionRevocationView
|
||||||
from intervention.views.share import InterventionShareFormView, InterventionShareByTokenView
|
from intervention.views.share import InterventionShareFormView, InterventionShareByTokenView
|
||||||
|
|
||||||
app_name = "intervention"
|
app_name = "intervention"
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("", InterventionIndexView.as_view(), name="index"),
|
path("", IndexInterventionView.as_view(), name="index"),
|
||||||
path('new/', NewInterventionFormView.as_view(), name='new'),
|
path('new/', NewInterventionView.as_view(), name='new'),
|
||||||
path('new/id', InterventionIdentifierGeneratorView.as_view(), name='new-id'),
|
path('new/id', InterventionIdentifierGeneratorView.as_view(), name='new-id'),
|
||||||
path('<id>', InterventionDetailView.as_view(), name='detail'),
|
path('<id>', DetailInterventionView.as_view(), name='detail'),
|
||||||
path('<id>/log', InterventionLogView.as_view(), name='log'),
|
path('<id>/log', InterventionLogView.as_view(), name='log'),
|
||||||
path('<id>/edit', EditInterventionFormView.as_view(), name='edit'),
|
path('<id>/edit', EditInterventionView.as_view(), name='edit'),
|
||||||
path('<id>/remove', remove_view, name='remove'),
|
path('<id>/remove', RemoveInterventionView.as_view(), name='remove'),
|
||||||
path('<id>/share/<token>', InterventionShareByTokenView.as_view(), name='share-token'),
|
path('<id>/share/<token>', InterventionShareByTokenView.as_view(), name='share-token'),
|
||||||
path('<id>/share', InterventionShareFormView.as_view(), name='share-form'),
|
path('<id>/share', InterventionShareFormView.as_view(), name='share-form'),
|
||||||
path('<id>/check', InterventionCheckView.as_view(), name='check'),
|
path('<id>/check', InterventionCheckView.as_view(), name='check'),
|
||||||
path('<id>/record', InterventionRecordView.as_view(), name='record'),
|
path('<id>/record', InterventionRecordView.as_view(), name='record'),
|
||||||
path('<id>/report', InterventionReportView.as_view(), name='report'),
|
path('<id>/report', InterventionPublicReportView.as_view(), name='report'),
|
||||||
path('<id>/resub', InterventionResubmissionView.as_view(), name='resubmission-create'),
|
path('<id>/resub', InterventionResubmissionView.as_view(), name='resubmission-create'),
|
||||||
|
|
||||||
# Compensations
|
# Compensations
|
||||||
path('<id>/compensation/<comp_id>/remove', remove_compensation_view, name='remove-compensation'),
|
path('<id>/compensation/<comp_id>/remove', RemoveCompensationFromInterventionView.as_view(), name='remove-compensation'),
|
||||||
|
|
||||||
# Documents
|
# Documents
|
||||||
path('<id>/document/new/', NewInterventionDocumentView.as_view(), name='new-doc'),
|
path('<id>/document/new/', NewInterventionDocumentView.as_view(), name='new-doc'),
|
||||||
@@ -56,10 +57,10 @@ urlpatterns = [
|
|||||||
path('<id>/deduction/<deduction_id>/remove', RemoveInterventionDeductionView.as_view(), name='remove-deduction'),
|
path('<id>/deduction/<deduction_id>/remove', RemoveInterventionDeductionView.as_view(), name='remove-deduction'),
|
||||||
|
|
||||||
# Revocation routes
|
# Revocation routes
|
||||||
path('<id>/revocation/new', new_revocation_view, name='new-revocation'),
|
path('<id>/revocation/new', NewInterventionRevocationView.as_view(), name='new-revocation'),
|
||||||
path('<id>/revocation/<revocation_id>/edit', edit_revocation_view, name='edit-revocation'),
|
path('<id>/revocation/<revocation_id>/edit', EditInterventionRevocationView.as_view(), name='edit-revocation'),
|
||||||
path('<id>/revocation/<revocation_id>/remove', remove_revocation_view, name='remove-revocation'),
|
path('<id>/revocation/<revocation_id>/remove', RemoveInterventionRevocationView.as_view(), name='remove-revocation'),
|
||||||
path('revocation/<doc_id>', get_revocation_view, name='get-doc-revocation'),
|
path('revocation/<doc_id>', GetInterventionRevocationView.as_view(), name='get-doc-revocation'),
|
||||||
|
|
||||||
# Autocomplete
|
# Autocomplete
|
||||||
path("atcmplt/interventions", InterventionAutocomplete.as_view(), name="autocomplete"),
|
path("atcmplt/interventions", InterventionAutocomplete.as_view(), name="autocomplete"),
|
||||||
|
|||||||
+31
-13
@@ -6,25 +6,43 @@ Created on: 19.08.22
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
|
from django.http import HttpRequest, HttpResponse
|
||||||
|
from django.shortcuts import get_object_or_404
|
||||||
|
from django.utils.decorators import method_decorator
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from django.views import View
|
||||||
|
|
||||||
from intervention.forms.modals.check import CheckModalForm
|
from intervention.forms.modals.check import CheckModalForm
|
||||||
from intervention.models import Intervention
|
from intervention.models import Intervention
|
||||||
|
from konova.decorators import registration_office_group_required, shared_access_required
|
||||||
from konova.utils.message_templates import INTERVENTION_INVALID
|
from konova.utils.message_templates import INTERVENTION_INVALID
|
||||||
from konova.views.base import BaseModalFormView
|
|
||||||
|
|
||||||
|
class InterventionCheckView(LoginRequiredMixin, View):
|
||||||
|
|
||||||
class InterventionCheckView(LoginRequiredMixin, BaseModalFormView):
|
def __process_request(self, request: HttpRequest, id: str, *args, **kwargs) -> HttpResponse:
|
||||||
_MODEL_CLS = Intervention
|
""" Renders check form for an intervention
|
||||||
_FORM_CLS = CheckModalForm
|
|
||||||
_MSG_SUCCESS = _("Check performed")
|
|
||||||
_MSG_ERROR = INTERVENTION_INVALID
|
|
||||||
_REDIRECT_URL = "intervention:detail"
|
|
||||||
|
|
||||||
def _user_has_permission(self, user):
|
Args:
|
||||||
return user.is_zb_user()
|
request (HttpRequest): The incoming request
|
||||||
|
id (str): Intervention's id
|
||||||
|
|
||||||
def _get_redirect_url(self, *args, **kwargs):
|
Returns:
|
||||||
redirect_url = super()._get_redirect_url(*args, **kwargs)
|
|
||||||
redirect_url += "#related_data"
|
"""
|
||||||
return redirect_url
|
intervention = get_object_or_404(Intervention, id=id)
|
||||||
|
form = CheckModalForm(request.POST or None, instance=intervention, request=request)
|
||||||
|
return form.process_request(
|
||||||
|
request,
|
||||||
|
msg_success=_("Check performed"),
|
||||||
|
msg_error=INTERVENTION_INVALID
|
||||||
|
)
|
||||||
|
|
||||||
|
@method_decorator(registration_office_group_required)
|
||||||
|
@method_decorator(shared_access_required(Intervention, "id"))
|
||||||
|
def get(self, request: HttpRequest, id: str, *args, **kwargs) -> HttpResponse:
|
||||||
|
return self.__process_request(request, id, *args, **kwargs)
|
||||||
|
|
||||||
|
@method_decorator(registration_office_group_required)
|
||||||
|
@method_decorator(shared_access_required(Intervention, "id"))
|
||||||
|
def post(self, request: HttpRequest, id: str, *args, **kwargs) -> HttpResponse:
|
||||||
|
return self.__process_request(request, id, *args, **kwargs)
|
||||||
|
|||||||
@@ -5,42 +5,52 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
|
|||||||
Created on: 19.08.22
|
Created on: 19.08.22
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
from django.http import HttpRequest, Http404
|
from django.http import HttpRequest, Http404, HttpResponse
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
from django.utils.decorators import method_decorator
|
||||||
|
from django.views import View
|
||||||
|
|
||||||
from intervention.models import Intervention
|
from intervention.models import Intervention
|
||||||
from konova.decorators import shared_access_required, login_required_modal
|
from konova.decorators import shared_access_required, default_group_required
|
||||||
from konova.forms.modals import RemoveModalForm
|
from konova.forms.modals import RemoveModalForm
|
||||||
from konova.utils.message_templates import COMPENSATION_REMOVED_TEMPLATE
|
from konova.utils.message_templates import COMPENSATION_REMOVED_TEMPLATE
|
||||||
|
|
||||||
|
|
||||||
@login_required_modal
|
class RemoveCompensationFromInterventionView(LoginRequiredMixin, View):
|
||||||
@login_required
|
|
||||||
@shared_access_required(Intervention, "id")
|
|
||||||
def remove_compensation_view(request: HttpRequest, id: str, comp_id: str):
|
|
||||||
""" Renders a modal view for removing the compensation
|
|
||||||
|
|
||||||
Args:
|
def __process_request(self, request: HttpRequest, id: str, comp_id: str, *args, **kwargs) -> HttpResponse:
|
||||||
request (HttpRequest): The incoming request
|
""" Renders a modal view for removing the compensation
|
||||||
id (str): The compensation's id
|
|
||||||
|
|
||||||
Returns:
|
Args:
|
||||||
|
request (HttpRequest): The incoming request
|
||||||
|
id (str): The compensation's id
|
||||||
|
|
||||||
"""
|
Returns:
|
||||||
intervention = get_object_or_404(Intervention, id=id)
|
|
||||||
try:
|
"""
|
||||||
comp = intervention.compensations.get(
|
intervention = get_object_or_404(Intervention, id=id)
|
||||||
id=comp_id
|
try:
|
||||||
|
comp = intervention.compensations.get(
|
||||||
|
id=comp_id
|
||||||
|
)
|
||||||
|
except ObjectDoesNotExist:
|
||||||
|
raise Http404("Unknown compensation")
|
||||||
|
form = RemoveModalForm(request.POST or None, instance=comp, request=request)
|
||||||
|
return form.process_request(
|
||||||
|
request=request,
|
||||||
|
msg_success=COMPENSATION_REMOVED_TEMPLATE.format(comp.identifier),
|
||||||
|
redirect_url=reverse("intervention:detail", args=(id,)) + "#related_data",
|
||||||
)
|
)
|
||||||
except ObjectDoesNotExist:
|
|
||||||
raise Http404("Unknown compensation")
|
|
||||||
form = RemoveModalForm(request.POST or None, instance=comp, request=request)
|
|
||||||
return form.process_request(
|
|
||||||
request=request,
|
|
||||||
msg_success=COMPENSATION_REMOVED_TEMPLATE.format(comp.identifier),
|
|
||||||
redirect_url=reverse("intervention:detail", args=(id,)) + "#related_data",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
@method_decorator(default_group_required)
|
||||||
|
@method_decorator(shared_access_required(Intervention, "id"))
|
||||||
|
def get(self, request, id: str, comp_id: str, *args, **kwargs) -> HttpResponse:
|
||||||
|
return self.__process_request(request, id, comp_id, *args, **kwargs)
|
||||||
|
|
||||||
|
@method_decorator(default_group_required)
|
||||||
|
@method_decorator(shared_access_required(Intervention, "id"))
|
||||||
|
def post(self, request, id: str, comp_id: str, *args, **kwargs) -> HttpResponse:
|
||||||
|
return self.__process_request(request, id, comp_id, *args, **kwargs)
|
||||||
@@ -5,27 +5,51 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
|
|||||||
Created on: 19.08.22
|
Created on: 19.08.22
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.decorators import login_required
|
||||||
|
from django.utils.decorators import method_decorator
|
||||||
|
|
||||||
from intervention.models import Intervention
|
from intervention.models import Intervention
|
||||||
from konova.utils.message_templates import DEDUCTION_ADDED, DEDUCTION_EDITED, DEDUCTION_REMOVED
|
from konova.decorators import default_group_required, shared_access_required
|
||||||
from konova.views.deduction import AbstractNewDeductionView, AbstractEditDeductionView, AbstractRemoveDeductionView
|
from konova.views.deduction import AbstractNewDeductionView, AbstractEditDeductionView, AbstractRemoveDeductionView
|
||||||
|
|
||||||
_INTERVENTION_DETAIL_URL_NAME = "intervention:detail"
|
|
||||||
|
|
||||||
class NewInterventionDeductionView(LoginRequiredMixin, AbstractNewDeductionView):
|
class NewInterventionDeductionView(AbstractNewDeductionView):
|
||||||
_MODEL_CLS = Intervention
|
def _custom_check(self, obj):
|
||||||
_MSG_SUCCESS = DEDUCTION_ADDED
|
pass
|
||||||
_REDIRECT_URL = _INTERVENTION_DETAIL_URL_NAME
|
|
||||||
|
model = Intervention
|
||||||
|
redirect_url = "intervention:detail"
|
||||||
|
|
||||||
|
@method_decorator(login_required)
|
||||||
|
@method_decorator(default_group_required)
|
||||||
|
@method_decorator(shared_access_required(Intervention, "id"))
|
||||||
|
def dispatch(self, request, *args, **kwargs):
|
||||||
|
return super().dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class EditInterventionDeductionView(LoginRequiredMixin, AbstractEditDeductionView):
|
class EditInterventionDeductionView(AbstractEditDeductionView):
|
||||||
_MODEL_CLS = Intervention
|
def _custom_check(self, obj):
|
||||||
_MSG_SUCCESS = DEDUCTION_EDITED
|
pass
|
||||||
_REDIRECT_URL = _INTERVENTION_DETAIL_URL_NAME
|
|
||||||
|
model = Intervention
|
||||||
|
redirect_url = "intervention:detail"
|
||||||
|
|
||||||
|
@method_decorator(login_required)
|
||||||
|
@method_decorator(default_group_required)
|
||||||
|
@method_decorator(shared_access_required(Intervention, "id"))
|
||||||
|
def dispatch(self, request, *args, **kwargs):
|
||||||
|
return super().dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class RemoveInterventionDeductionView(LoginRequiredMixin, AbstractRemoveDeductionView):
|
class RemoveInterventionDeductionView(AbstractRemoveDeductionView):
|
||||||
_MODEL_CLS = Intervention
|
def _custom_check(self, obj):
|
||||||
_MSG_SUCCESS = DEDUCTION_REMOVED
|
pass
|
||||||
_REDIRECT_URL = _INTERVENTION_DETAIL_URL_NAME
|
|
||||||
|
model = Intervention
|
||||||
|
redirect_url = "intervention:detail"
|
||||||
|
|
||||||
|
@method_decorator(login_required)
|
||||||
|
@method_decorator(default_group_required)
|
||||||
|
@method_decorator(shared_access_required(Intervention, "id"))
|
||||||
|
def dispatch(self, request, *args, **kwargs):
|
||||||
|
return super().dispatch(request, *args, **kwargs)
|
||||||
|
|||||||
@@ -0,0 +1,79 @@
|
|||||||
|
"""
|
||||||
|
Author: Michel Peltriaux
|
||||||
|
Created on: 14.12.25
|
||||||
|
|
||||||
|
"""
|
||||||
|
from django.contrib import messages
|
||||||
|
from django.http import HttpResponse
|
||||||
|
from django.shortcuts import get_object_or_404, render
|
||||||
|
|
||||||
|
from intervention.models import Intervention
|
||||||
|
from konova.contexts import BaseContext
|
||||||
|
from konova.forms import SimpleGeomForm
|
||||||
|
from konova.settings import DEFAULT_GROUP, ZB_GROUP, ETS_GROUP
|
||||||
|
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
|
||||||
|
from konova.utils.message_templates import DATA_CHECKED_PREVIOUSLY_TEMPLATE, DO_NOT_FORGET_TO_SHARE
|
||||||
|
from konova.views.detail import AbstractDetailView
|
||||||
|
|
||||||
|
|
||||||
|
class DetailInterventionView(AbstractDetailView):
|
||||||
|
_TEMPLATE = "intervention/detail/view.html"
|
||||||
|
|
||||||
|
def get(self, request, id: str, *args, **kwargs) -> HttpResponse:
|
||||||
|
# Fetch data, filter out deleted related data
|
||||||
|
intervention = get_object_or_404(
|
||||||
|
Intervention.objects.select_related(
|
||||||
|
"geometry",
|
||||||
|
"legal",
|
||||||
|
"responsible",
|
||||||
|
).prefetch_related(
|
||||||
|
"legal__revocations",
|
||||||
|
),
|
||||||
|
id=id,
|
||||||
|
deleted=None
|
||||||
|
)
|
||||||
|
compensations = intervention.compensations.filter(
|
||||||
|
deleted=None,
|
||||||
|
)
|
||||||
|
_user = request.user
|
||||||
|
is_data_shared = intervention.is_shared_with(user=_user)
|
||||||
|
|
||||||
|
geom_form = SimpleGeomForm(
|
||||||
|
instance=intervention,
|
||||||
|
)
|
||||||
|
last_checked = intervention.get_last_checked_action()
|
||||||
|
last_checked_tooltip = ""
|
||||||
|
if last_checked:
|
||||||
|
last_checked_tooltip = DATA_CHECKED_PREVIOUSLY_TEMPLATE.format(
|
||||||
|
last_checked.get_timestamp_str_formatted(),
|
||||||
|
last_checked.user
|
||||||
|
)
|
||||||
|
|
||||||
|
has_payment_without_document = intervention.payments.exists() and not intervention.get_documents()[1].exists()
|
||||||
|
|
||||||
|
requesting_user_is_only_shared_user = intervention.is_only_shared_with(_user)
|
||||||
|
if requesting_user_is_only_shared_user:
|
||||||
|
messages.info(
|
||||||
|
request,
|
||||||
|
DO_NOT_FORGET_TO_SHARE
|
||||||
|
)
|
||||||
|
|
||||||
|
context = {
|
||||||
|
"obj": intervention,
|
||||||
|
"last_checked": last_checked,
|
||||||
|
"last_checked_tooltip": last_checked_tooltip,
|
||||||
|
"compensations": compensations,
|
||||||
|
"is_entry_shared": is_data_shared,
|
||||||
|
"geom_form": geom_form,
|
||||||
|
"is_default_member": _user.in_group(DEFAULT_GROUP),
|
||||||
|
"is_zb_member": _user.in_group(ZB_GROUP),
|
||||||
|
"is_ets_member": _user.in_group(ETS_GROUP),
|
||||||
|
"LANIS_LINK": intervention.get_LANIS_link(),
|
||||||
|
"has_payment_without_document": has_payment_without_document,
|
||||||
|
TAB_TITLE_IDENTIFIER: f"{intervention.identifier} - {intervention.title}",
|
||||||
|
}
|
||||||
|
|
||||||
|
request = intervention.set_status_messages(request)
|
||||||
|
|
||||||
|
context = BaseContext(request, context).context
|
||||||
|
return render(request, self._TEMPLATE, context)
|
||||||
+177
-150
@@ -8,144 +8,202 @@ Created on: 19.08.22
|
|||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
from django.http import HttpRequest
|
from django.http import HttpRequest, HttpResponse
|
||||||
from django.shortcuts import get_object_or_404, render, redirect
|
from django.shortcuts import get_object_or_404, render, redirect
|
||||||
from django.urls import reverse
|
from django.utils.decorators import method_decorator
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from django.views import View
|
||||||
|
|
||||||
from intervention.forms.intervention import EditInterventionForm, NewInterventionForm
|
from intervention.forms.intervention import EditInterventionForm, NewInterventionForm
|
||||||
from intervention.models import Intervention
|
from intervention.models import Intervention
|
||||||
from intervention.tables import InterventionTable
|
from intervention.tables import InterventionTable
|
||||||
from konova.contexts import BaseContext
|
from konova.contexts import BaseContext
|
||||||
from konova.decorators import default_group_required, shared_access_required, login_required_modal
|
from konova.decorators import default_group_required, shared_access_required
|
||||||
from konova.forms import SimpleGeomForm
|
from konova.forms import SimpleGeomForm
|
||||||
from konova.forms.modals import RemoveModalForm
|
|
||||||
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
|
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
|
||||||
from konova.utils.message_templates import DATA_CHECKED_PREVIOUSLY_TEMPLATE, RECORDED_BLOCKS_EDIT, \
|
from konova.utils.message_templates import RECORDED_BLOCKS_EDIT, \
|
||||||
CHECK_STATE_RESET, FORM_INVALID, GEOMETRY_SIMPLIFIED, GEOMETRIES_IGNORED_TEMPLATE
|
CHECK_STATE_RESET, FORM_INVALID, IDENTIFIER_REPLACED, GEOMETRY_SIMPLIFIED, \
|
||||||
from konova.views.base import BaseIndexView, BaseIdentifierGeneratorView, BaseNewSpatialLocatedObjectFormView, \
|
GEOMETRIES_IGNORED_TEMPLATE
|
||||||
BaseEditSpatialLocatedObjectFormView
|
from konova.views.identifier import AbstractIdentifierGeneratorView
|
||||||
from konova.views.detail import BaseDetailView
|
from konova.views.index import AbstractIndexView
|
||||||
|
|
||||||
|
|
||||||
class InterventionIndexView(LoginRequiredMixin, BaseIndexView):
|
class IndexInterventionView(AbstractIndexView):
|
||||||
_INDEX_TABLE_CLS = InterventionTable
|
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
||||||
_TAB_TITLE = _("Interventions - Overview")
|
"""
|
||||||
|
Renders the index view for Interventions
|
||||||
|
|
||||||
def _get_queryset(self):
|
Args:
|
||||||
qs = Intervention.objects.filter(
|
request (HttpRequest): The incoming request
|
||||||
deleted=None,
|
|
||||||
|
Returns:
|
||||||
|
A rendered view
|
||||||
|
"""
|
||||||
|
# Filtering by user access is performed in table filter inside InterventionTableFilter class
|
||||||
|
interventions = Intervention.objects.filter(
|
||||||
|
deleted=None, # not deleted
|
||||||
).select_related(
|
).select_related(
|
||||||
"legal"
|
"legal"
|
||||||
).order_by(
|
).order_by(
|
||||||
"-modified__timestamp"
|
"-modified__timestamp"
|
||||||
)
|
)
|
||||||
return qs
|
table = InterventionTable(
|
||||||
|
request=request,
|
||||||
|
queryset=interventions
|
||||||
class NewInterventionFormView(BaseNewSpatialLocatedObjectFormView):
|
|
||||||
_MODEL_CLS = Intervention
|
|
||||||
_FORM_CLS = NewInterventionForm
|
|
||||||
_TEMPLATE = "intervention/form/view.html"
|
|
||||||
_REDIRECT_URL = "intervention:detail"
|
|
||||||
_TAB_TITLE = _("New intervention")
|
|
||||||
|
|
||||||
|
|
||||||
class EditInterventionFormView(BaseEditSpatialLocatedObjectFormView):
|
|
||||||
_MODEL_CLS = Intervention
|
|
||||||
_FORM_CLS = EditInterventionForm
|
|
||||||
_TEMPLATE = "intervention/form/view.html"
|
|
||||||
_REDIRECT_URL = "intervention:detail"
|
|
||||||
_TAB_TITLE = _("Edit {}")
|
|
||||||
|
|
||||||
|
|
||||||
class InterventionIdentifierGeneratorView(LoginRequiredMixin, BaseIdentifierGeneratorView):
|
|
||||||
_MODEL_CLS = Intervention
|
|
||||||
_REDIRECT_URL = "intervention:index"
|
|
||||||
|
|
||||||
|
|
||||||
class InterventionDetailView(BaseDetailView):
|
|
||||||
_MODEL_CLS = Intervention
|
|
||||||
_TEMPLATE = "intervention/detail/view.html"
|
|
||||||
|
|
||||||
def _get_object(self, id: str):
|
|
||||||
""" Returns the intervention
|
|
||||||
|
|
||||||
Args:
|
|
||||||
id (str): The intervention's id
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
obj (Intervention): The intervention
|
|
||||||
"""
|
|
||||||
# Fetch data, filter out deleted related data
|
|
||||||
obj = get_object_or_404(
|
|
||||||
self._MODEL_CLS.objects.select_related(
|
|
||||||
"geometry",
|
|
||||||
"legal",
|
|
||||||
"responsible",
|
|
||||||
).prefetch_related(
|
|
||||||
"legal__revocations",
|
|
||||||
),
|
|
||||||
id=id,
|
|
||||||
deleted=None
|
|
||||||
)
|
)
|
||||||
return obj
|
|
||||||
|
|
||||||
def _get_detail_context(self, obj: Intervention):
|
|
||||||
""" Generate object specific detail context for view
|
|
||||||
|
|
||||||
Args:
|
|
||||||
obj (): The record
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
|
|
||||||
"""
|
|
||||||
compensations = obj.compensations.filter(deleted=None)
|
|
||||||
last_checked = obj.get_last_checked_action()
|
|
||||||
last_checked_tooltip = ""
|
|
||||||
if last_checked:
|
|
||||||
last_checked_tooltip = DATA_CHECKED_PREVIOUSLY_TEMPLATE.format(
|
|
||||||
last_checked.get_timestamp_str_formatted(),
|
|
||||||
last_checked.user
|
|
||||||
)
|
|
||||||
|
|
||||||
has_payment_without_document = obj.payments.exists() and not obj.get_documents()[1].exists()
|
|
||||||
context = {
|
context = {
|
||||||
"last_checked": last_checked,
|
"table": table,
|
||||||
"last_checked_tooltip": last_checked_tooltip,
|
TAB_TITLE_IDENTIFIER: _("Interventions - Overview"),
|
||||||
"compensations": compensations,
|
|
||||||
"has_payment_without_document": has_payment_without_document,
|
|
||||||
}
|
}
|
||||||
return context
|
context = BaseContext(request, context).context
|
||||||
|
return render(request, self._TEMPLATE, context)
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
class NewInterventionView(LoginRequiredMixin, View):
|
||||||
@default_group_required
|
_TEMPLATE = "intervention/form/view.html"
|
||||||
@shared_access_required(Intervention, "id")
|
|
||||||
def edit_view(request: HttpRequest, id: str):
|
|
||||||
"""
|
|
||||||
Renders a view for editing interventions
|
|
||||||
|
|
||||||
Args:
|
@method_decorator(default_group_required)
|
||||||
request (HttpRequest): The incoming request
|
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
||||||
|
|
||||||
Returns:
|
"""
|
||||||
|
Renders a view for a new intervention creation
|
||||||
|
|
||||||
"""
|
Args:
|
||||||
template = "intervention/form/view.html"
|
request (HttpRequest): The incoming request
|
||||||
# Get object from db
|
|
||||||
intervention = get_object_or_404(Intervention, id=id)
|
|
||||||
if intervention.is_recorded:
|
|
||||||
messages.info(
|
|
||||||
request,
|
|
||||||
RECORDED_BLOCKS_EDIT
|
|
||||||
)
|
|
||||||
return redirect("intervention:detail", id=id)
|
|
||||||
|
|
||||||
# Create forms, initialize with values from db/from POST request
|
Returns:
|
||||||
data_form = EditInterventionForm(request.POST or None, instance=intervention)
|
|
||||||
geom_form = SimpleGeomForm(request.POST or None, read_only=False, instance=intervention)
|
"""
|
||||||
if request.method == "POST":
|
data_form = NewInterventionForm()
|
||||||
|
geom_form = SimpleGeomForm(read_only=False)
|
||||||
|
context = {
|
||||||
|
"form": data_form,
|
||||||
|
"geom_form": geom_form,
|
||||||
|
TAB_TITLE_IDENTIFIER: _("New intervention"),
|
||||||
|
}
|
||||||
|
context = BaseContext(request, context).context
|
||||||
|
return render(request, self._TEMPLATE, context)
|
||||||
|
|
||||||
|
@method_decorator(default_group_required)
|
||||||
|
def post(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
||||||
|
|
||||||
|
"""
|
||||||
|
Renders a view for a new intervention creation
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request (HttpRequest): The incoming request
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
data_form = NewInterventionForm(request.POST or None)
|
||||||
|
geom_form = SimpleGeomForm(request.POST or None, read_only=False)
|
||||||
|
|
||||||
|
if data_form.is_valid() and geom_form.is_valid():
|
||||||
|
generated_identifier = data_form.cleaned_data.get("identifier", None)
|
||||||
|
intervention = data_form.save(request.user, geom_form)
|
||||||
|
if generated_identifier != intervention.identifier:
|
||||||
|
messages.info(
|
||||||
|
request,
|
||||||
|
IDENTIFIER_REPLACED.format(
|
||||||
|
generated_identifier,
|
||||||
|
intervention.identifier
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
messages.success(request, _("Intervention {} added").format(intervention.identifier))
|
||||||
|
if geom_form.has_geometry_simplified():
|
||||||
|
messages.info(
|
||||||
|
request,
|
||||||
|
GEOMETRY_SIMPLIFIED
|
||||||
|
)
|
||||||
|
|
||||||
|
num_ignored_geometries = geom_form.get_num_geometries_ignored()
|
||||||
|
if num_ignored_geometries > 0:
|
||||||
|
messages.info(
|
||||||
|
request,
|
||||||
|
GEOMETRIES_IGNORED_TEMPLATE.format(num_ignored_geometries)
|
||||||
|
)
|
||||||
|
return redirect("intervention:detail", id=intervention.id)
|
||||||
|
|
||||||
|
else:
|
||||||
|
messages.error(request, FORM_INVALID, extra_tags="danger", )
|
||||||
|
context = {
|
||||||
|
"form": data_form,
|
||||||
|
"geom_form": geom_form,
|
||||||
|
TAB_TITLE_IDENTIFIER: _("New intervention"),
|
||||||
|
}
|
||||||
|
context = BaseContext(request, context).context
|
||||||
|
return render(request, self._TEMPLATE, context)
|
||||||
|
|
||||||
|
|
||||||
|
class InterventionIdentifierGeneratorView(AbstractIdentifierGeneratorView):
|
||||||
|
_MODEL = Intervention
|
||||||
|
|
||||||
|
|
||||||
|
class EditInterventionView(LoginRequiredMixin, View):
|
||||||
|
_TEMPLATE = "intervention/form/view.html"
|
||||||
|
|
||||||
|
@method_decorator(default_group_required)
|
||||||
|
@method_decorator(shared_access_required(Intervention, "id"))
|
||||||
|
def get(self, request: HttpRequest, id: str, *args, **kwargs) -> HttpResponse:
|
||||||
|
"""
|
||||||
|
Renders a view for editing interventions
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request (HttpRequest): The incoming request
|
||||||
|
id (str): The intervention identifier
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
HttpResponse: The rendered view
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Get object from db
|
||||||
|
intervention = get_object_or_404(Intervention, id=id)
|
||||||
|
if intervention.is_recorded:
|
||||||
|
messages.info(
|
||||||
|
request,
|
||||||
|
RECORDED_BLOCKS_EDIT
|
||||||
|
)
|
||||||
|
return redirect("intervention:detail", id=id)
|
||||||
|
|
||||||
|
# Create forms, initialize with values from db/from POST request
|
||||||
|
data_form = EditInterventionForm(request.POST or None, instance=intervention)
|
||||||
|
geom_form = SimpleGeomForm(request.POST or None, read_only=False, instance=intervention)
|
||||||
|
context = {
|
||||||
|
"form": data_form,
|
||||||
|
"geom_form": geom_form,
|
||||||
|
TAB_TITLE_IDENTIFIER: _("Edit {}").format(intervention.identifier),
|
||||||
|
}
|
||||||
|
context = BaseContext(request, context).context
|
||||||
|
return render(request, self._TEMPLATE, context)
|
||||||
|
|
||||||
|
|
||||||
|
@method_decorator(default_group_required)
|
||||||
|
@method_decorator(shared_access_required(Intervention, "id"))
|
||||||
|
def post(self, request: HttpRequest, id: str, *args, **kwargs) -> HttpResponse:
|
||||||
|
"""
|
||||||
|
Process saved form content
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request (HttpRequest): The incoming request
|
||||||
|
id (str): The intervention id
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
HttpResponse:
|
||||||
|
"""
|
||||||
|
# Get object from db
|
||||||
|
intervention = get_object_or_404(Intervention, id=id)
|
||||||
|
if intervention.is_recorded:
|
||||||
|
messages.info(
|
||||||
|
request,
|
||||||
|
RECORDED_BLOCKS_EDIT
|
||||||
|
)
|
||||||
|
return redirect("intervention:detail", id=id)
|
||||||
|
|
||||||
|
# Create forms, initialize with values from db/from POST request
|
||||||
|
data_form = EditInterventionForm(request.POST or None, instance=intervention)
|
||||||
|
geom_form = SimpleGeomForm(request.POST or None, read_only=False, instance=intervention)
|
||||||
if data_form.is_valid() and geom_form.is_valid():
|
if data_form.is_valid() and geom_form.is_valid():
|
||||||
# The data form takes the geom form for processing, as well as the performing user
|
# The data form takes the geom form for processing, as well as the performing user
|
||||||
# Save the current state of recorded|checked to inform the user in case of a status reset due to editing
|
# Save the current state of recorded|checked to inform the user in case of a status reset due to editing
|
||||||
@@ -159,48 +217,17 @@ def edit_view(request: HttpRequest, id: str):
|
|||||||
request,
|
request,
|
||||||
GEOMETRY_SIMPLIFIED
|
GEOMETRY_SIMPLIFIED
|
||||||
)
|
)
|
||||||
|
|
||||||
num_ignored_geometries = geom_form.get_num_geometries_ignored()
|
num_ignored_geometries = geom_form.get_num_geometries_ignored()
|
||||||
if num_ignored_geometries > 0:
|
if num_ignored_geometries > 0:
|
||||||
messages.info(
|
messages.info(
|
||||||
request,
|
request,
|
||||||
GEOMETRIES_IGNORED_TEMPLATE.format(num_ignored_geometries)
|
GEOMETRIES_IGNORED_TEMPLATE.format(num_ignored_geometries)
|
||||||
)
|
)
|
||||||
|
|
||||||
return redirect("intervention:detail", id=intervention.id)
|
return redirect("intervention:detail", id=intervention.id)
|
||||||
else:
|
context = {
|
||||||
messages.error(request, FORM_INVALID, extra_tags="danger",)
|
"form": data_form,
|
||||||
else:
|
"geom_form": geom_form,
|
||||||
# For clarification: nothing in this case
|
TAB_TITLE_IDENTIFIER: _("Edit {}").format(intervention.identifier),
|
||||||
pass
|
}
|
||||||
context = {
|
context = BaseContext(request, context).context
|
||||||
"form": data_form,
|
return render(request, self._TEMPLATE, context)
|
||||||
"geom_form": geom_form,
|
|
||||||
TAB_TITLE_IDENTIFIER: _("Edit {}").format(intervention.identifier),
|
|
||||||
}
|
|
||||||
context = BaseContext(request, context).context
|
|
||||||
return render(request, template, context)
|
|
||||||
|
|
||||||
|
|
||||||
@login_required_modal
|
|
||||||
@login_required
|
|
||||||
@default_group_required
|
|
||||||
@shared_access_required(Intervention, "id")
|
|
||||||
def remove_view(request: HttpRequest, id: str):
|
|
||||||
""" Renders a remove view for this intervention
|
|
||||||
|
|
||||||
Args:
|
|
||||||
request (HttpRequest): The incoming request
|
|
||||||
id (str): The uuid id as string
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
|
|
||||||
"""
|
|
||||||
obj = Intervention.objects.get(id=id)
|
|
||||||
identifier = obj.identifier
|
|
||||||
form = RemoveModalForm(request.POST or None, instance=obj, request=request)
|
|
||||||
return form.process_request(
|
|
||||||
request,
|
|
||||||
_("{} removed").format(identifier),
|
|
||||||
redirect_url=reverse("intervention:index")
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
"""
|
||||||
|
Author: Michel Peltriaux
|
||||||
|
Created on: 14.12.25
|
||||||
|
|
||||||
|
"""
|
||||||
|
from django.http import HttpRequest, HttpResponse
|
||||||
|
from django.utils.decorators import method_decorator
|
||||||
|
|
||||||
|
from intervention.models import Intervention
|
||||||
|
from konova.decorators import shared_access_required
|
||||||
|
from konova.views.remove import AbstractRemoveView
|
||||||
|
|
||||||
|
|
||||||
|
class RemoveInterventionView(AbstractRemoveView):
|
||||||
|
_MODEL = Intervention
|
||||||
|
_REDIRECT_URL = "intervention:index"
|
||||||
|
|
||||||
|
@method_decorator(shared_access_required(Intervention, "id"))
|
||||||
|
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
||||||
|
return super().get(request, *args, **kwargs)
|
||||||
@@ -5,41 +5,78 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
|
|||||||
Created on: 19.08.22
|
Created on: 19.08.22
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
from django.http import HttpRequest, HttpResponse
|
||||||
|
from django.shortcuts import get_object_or_404, render
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from intervention.models import Intervention
|
from intervention.models import Intervention
|
||||||
from konova.sub_settings.django_settings import BASE_URL
|
from konova.contexts import BaseContext
|
||||||
|
from konova.forms import SimpleGeomForm
|
||||||
|
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
|
||||||
from konova.utils.qrcode import QrCode
|
from konova.utils.qrcode import QrCode
|
||||||
from konova.views.report import BaseReportView
|
from konova.views.report import AbstractPublicReportView
|
||||||
|
|
||||||
|
|
||||||
class InterventionReportView(BaseReportView):
|
class InterventionPublicReportView(AbstractPublicReportView):
|
||||||
_TEMPLATE = 'intervention/report/report.html'
|
_TEMPLATE = "intervention/report/report.html"
|
||||||
_MODEL = Intervention
|
|
||||||
|
|
||||||
def _get_report_context(self, obj: Intervention):
|
def get(self, request: HttpRequest, id: str, *args, **kwargs) -> HttpResponse:
|
||||||
""" Returns the specific context needed for an intervention report
|
""" Renders the public report view
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
obj (Intervention): The object for the report
|
request (HttpRequest): The incoming request
|
||||||
|
id (str): The id of the intervention
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
dict: The object specific context for rendering the report
|
|
||||||
"""
|
|
||||||
distinct_deductions = obj.deductions.all().distinct("account")
|
|
||||||
report_url = BASE_URL + reverse("intervention:report", args=(obj.id,))
|
|
||||||
qrcode_report = QrCode(report_url, 10)
|
|
||||||
qrcode_lanis = QrCode(obj.get_LANIS_link(), 7)
|
|
||||||
|
|
||||||
return {
|
"""
|
||||||
|
intervention = get_object_or_404(Intervention, id=id)
|
||||||
|
|
||||||
|
tab_title = _("Report {}").format(intervention.identifier)
|
||||||
|
# If intervention is not recorded (yet or currently) we need to render another template without any data
|
||||||
|
if not intervention.is_ready_for_publish():
|
||||||
|
template = "report/unavailable.html"
|
||||||
|
context = {
|
||||||
|
TAB_TITLE_IDENTIFIER: tab_title,
|
||||||
|
}
|
||||||
|
context = BaseContext(request, context).context
|
||||||
|
return render(request, template, context)
|
||||||
|
|
||||||
|
# Prepare data for map viewer
|
||||||
|
geom_form = SimpleGeomForm(
|
||||||
|
instance=intervention
|
||||||
|
)
|
||||||
|
parcels = intervention.get_underlying_parcels()
|
||||||
|
|
||||||
|
distinct_deductions = intervention.deductions.all().distinct(
|
||||||
|
"account"
|
||||||
|
)
|
||||||
|
|
||||||
|
qrcode = QrCode(
|
||||||
|
content=request.build_absolute_uri(reverse("intervention:report", args=(id,))),
|
||||||
|
size=10
|
||||||
|
)
|
||||||
|
qrcode_lanis = QrCode(
|
||||||
|
content=intervention.get_LANIS_link(),
|
||||||
|
size=7
|
||||||
|
)
|
||||||
|
|
||||||
|
context = {
|
||||||
|
"obj": intervention,
|
||||||
"deductions": distinct_deductions,
|
"deductions": distinct_deductions,
|
||||||
"qrcode": {
|
"qrcode": {
|
||||||
"img": qrcode_report.get_img(),
|
"img": qrcode.get_img(),
|
||||||
"url": qrcode_report.get_content(),
|
"url": qrcode.get_content(),
|
||||||
},
|
},
|
||||||
"qrcode_lanis": {
|
"qrcode_lanis": {
|
||||||
"img": qrcode_lanis.get_img(),
|
"img": qrcode_lanis.get_img(),
|
||||||
"url": qrcode_lanis.get_content(),
|
"url": qrcode_lanis.get_content(),
|
||||||
},
|
},
|
||||||
|
"geom_form": geom_form,
|
||||||
|
"parcels": parcels,
|
||||||
"tables_scrollable": False,
|
"tables_scrollable": False,
|
||||||
|
TAB_TITLE_IDENTIFIER: tab_title,
|
||||||
}
|
}
|
||||||
|
context = BaseContext(request, context).context
|
||||||
|
return render(request, self._TEMPLATE, context)
|
||||||
@@ -6,10 +6,12 @@ Created on: 19.08.22
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
from django.http import HttpRequest
|
from django.http import HttpRequest, HttpResponse
|
||||||
from django.shortcuts import get_object_or_404, redirect
|
from django.shortcuts import get_object_or_404, redirect
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
from django.utils.decorators import method_decorator
|
||||||
|
from django.views import View
|
||||||
|
|
||||||
from intervention.forms.modals.revocation import NewRevocationModalForm, EditRevocationModalForm, \
|
from intervention.forms.modals.revocation import NewRevocationModalForm, EditRevocationModalForm, \
|
||||||
RemoveRevocationModalForm
|
RemoveRevocationModalForm
|
||||||
@@ -19,100 +21,125 @@ from konova.utils.documents import get_document
|
|||||||
from konova.utils.message_templates import REVOCATION_ADDED, DATA_UNSHARED, REVOCATION_EDITED, REVOCATION_REMOVED
|
from konova.utils.message_templates import REVOCATION_ADDED, DATA_UNSHARED, REVOCATION_EDITED, REVOCATION_REMOVED
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
class NewInterventionRevocationView(LoginRequiredMixin, View):
|
||||||
@default_group_required
|
def __process_request(self, request: HttpRequest, id: str, *args, **kwargs) -> HttpResponse:
|
||||||
@shared_access_required(Intervention, "id")
|
""" Renders sharing form for an intervention
|
||||||
def new_revocation_view(request: HttpRequest, id: str):
|
|
||||||
""" Renders sharing form for an intervention
|
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
request (HttpRequest): The incoming request
|
request (HttpRequest): The incoming request
|
||||||
id (str): Intervention's id
|
id (str): Intervention's id
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
intervention = get_object_or_404(Intervention, id=id)
|
intervention = get_object_or_404(Intervention, id=id)
|
||||||
form = NewRevocationModalForm(request.POST or None, request.FILES or None, instance=intervention, request=request)
|
form = NewRevocationModalForm(request.POST or None, request.FILES or None, instance=intervention,
|
||||||
return form.process_request(
|
request=request)
|
||||||
request,
|
return form.process_request(
|
||||||
msg_success=REVOCATION_ADDED,
|
|
||||||
redirect_url=reverse("intervention:detail", args=(id,)) + "#related_data"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
|
||||||
@default_group_required
|
|
||||||
def get_revocation_view(request: HttpRequest, doc_id: str):
|
|
||||||
""" Returns the revocation document as downloadable file
|
|
||||||
|
|
||||||
Wraps the generic document fetcher function from konova.utils.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
request (HttpRequest): The incoming request
|
|
||||||
doc_id (str): The document id
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
|
|
||||||
"""
|
|
||||||
doc = get_object_or_404(RevocationDocument, id=doc_id)
|
|
||||||
# File download only possible if related instance is shared with user
|
|
||||||
if not doc.instance.legal.intervention.users.filter(id=request.user.id):
|
|
||||||
messages.info(
|
|
||||||
request,
|
request,
|
||||||
DATA_UNSHARED
|
msg_success=REVOCATION_ADDED,
|
||||||
|
redirect_url=reverse("intervention:detail", args=(id,)) + "#related_data"
|
||||||
)
|
)
|
||||||
return redirect("intervention:detail", id=doc.instance.id)
|
|
||||||
return get_document(doc)
|
@method_decorator(default_group_required)
|
||||||
|
@method_decorator(shared_access_required(Intervention, "id"))
|
||||||
|
def get(self, request: HttpRequest, id: str, *args, **kwargs) -> HttpResponse:
|
||||||
|
return self.__process_request(request, id, *args, **kwargs)
|
||||||
|
|
||||||
|
@method_decorator(default_group_required)
|
||||||
|
@method_decorator(shared_access_required(Intervention, "id"))
|
||||||
|
def post(self, request: HttpRequest, id: str, *args, **kwargs) -> HttpResponse:
|
||||||
|
return self.__process_request(request, id, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
class GetInterventionRevocationView(LoginRequiredMixin, View):
|
||||||
@default_group_required
|
@method_decorator(default_group_required)
|
||||||
@shared_access_required(Intervention, "id")
|
def get(self, request: HttpRequest, doc_id: str, *args, **kwargs) -> HttpResponse:
|
||||||
def edit_revocation_view(request: HttpRequest, id: str, revocation_id: str):
|
""" Returns the revocation document as downloadable file
|
||||||
""" Renders a edit view for a revocation
|
|
||||||
|
|
||||||
Args:
|
Wraps the generic document fetcher function from konova.utils.
|
||||||
request (HttpRequest): The incoming request
|
|
||||||
id (str): The intervention's id as string
|
|
||||||
revocation_id (str): The revocation's id as string
|
|
||||||
|
|
||||||
Returns:
|
Args:
|
||||||
|
request (HttpRequest): The incoming request
|
||||||
|
doc_id (str): The document id
|
||||||
|
|
||||||
"""
|
Returns:
|
||||||
intervention = get_object_or_404(Intervention, id=id)
|
|
||||||
revocation = get_object_or_404(Revocation, id=revocation_id)
|
|
||||||
|
|
||||||
form = EditRevocationModalForm(request.POST or None, request.FILES or None, instance=intervention, revocation=revocation, request=request)
|
"""
|
||||||
return form.process_request(
|
doc = get_object_or_404(RevocationDocument, id=doc_id)
|
||||||
request,
|
# File download only possible if related instance is shared with user
|
||||||
REVOCATION_EDITED,
|
if not doc.instance.legal.intervention.users.filter(id=request.user.id):
|
||||||
redirect_url=reverse("intervention:detail", args=(intervention.id,)) + "#related_data"
|
messages.info(
|
||||||
)
|
request,
|
||||||
|
DATA_UNSHARED
|
||||||
|
)
|
||||||
|
return redirect("intervention:detail", id=doc.instance.id)
|
||||||
|
return get_document(doc)
|
||||||
|
|
||||||
|
|
||||||
@login_required_modal
|
class EditInterventionRevocationView(LoginRequiredMixin, View):
|
||||||
@login_required
|
def __process_request(self, request: HttpRequest, id: str, revocation_id: str, *args, **kwargs) -> HttpResponse:
|
||||||
@default_group_required
|
""" Renders a edit view for a revocation
|
||||||
@shared_access_required(Intervention, "id")
|
|
||||||
def remove_revocation_view(request: HttpRequest, id: str, revocation_id: str):
|
|
||||||
""" Renders a remove view for a revocation
|
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
request (HttpRequest): The incoming request
|
request (HttpRequest): The incoming request
|
||||||
id (str): The intervention's id as string
|
id (str): The intervention's id as string
|
||||||
revocation_id (str): The revocation's id as string
|
revocation_id (str): The revocation's id as string
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
intervention = get_object_or_404(Intervention, id=id)
|
intervention = get_object_or_404(Intervention, id=id)
|
||||||
revocation = get_object_or_404(Revocation, id=revocation_id)
|
revocation = get_object_or_404(Revocation, id=revocation_id)
|
||||||
|
|
||||||
form = RemoveRevocationModalForm(request.POST or None, instance=intervention, revocation=revocation, request=request)
|
form = EditRevocationModalForm(request.POST or None, request.FILES or None, instance=intervention,
|
||||||
return form.process_request(
|
revocation=revocation, request=request)
|
||||||
request,
|
return form.process_request(
|
||||||
REVOCATION_REMOVED,
|
request,
|
||||||
redirect_url=reverse("intervention:detail", args=(intervention.id,)) + "#related_data"
|
REVOCATION_EDITED,
|
||||||
)
|
redirect_url=reverse("intervention:detail", args=(intervention.id,)) + "#related_data"
|
||||||
|
)
|
||||||
|
|
||||||
|
@method_decorator(default_group_required)
|
||||||
|
@method_decorator(shared_access_required(Intervention, "id"))
|
||||||
|
def get(self, request: HttpRequest, id: str, revocation_id: str, *args, **kwargs) -> HttpResponse:
|
||||||
|
return self.__process_request(request, id, revocation_id, *args, **kwargs)
|
||||||
|
|
||||||
|
@method_decorator(default_group_required)
|
||||||
|
@method_decorator(shared_access_required(Intervention, "id"))
|
||||||
|
def post(self, request: HttpRequest, id: str, revocation_id: str, *args, **kwargs) -> HttpResponse:
|
||||||
|
return self.__process_request(request, id, revocation_id, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class RemoveInterventionRevocationView(LoginRequiredMixin, View):
|
||||||
|
def __process_request(self, request, id: str, revocation_id: str, *args, **kwargs) -> HttpResponse:
|
||||||
|
""" Renders a remove view for a revocation
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request (HttpRequest): The incoming request
|
||||||
|
id (str): The intervention's id as string
|
||||||
|
revocation_id (str): The revocation's id as string
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
intervention = get_object_or_404(Intervention, id=id)
|
||||||
|
revocation = get_object_or_404(Revocation, id=revocation_id)
|
||||||
|
|
||||||
|
form = RemoveRevocationModalForm(request.POST or None, instance=intervention, revocation=revocation,
|
||||||
|
request=request)
|
||||||
|
return form.process_request(
|
||||||
|
request,
|
||||||
|
REVOCATION_REMOVED,
|
||||||
|
redirect_url=reverse("intervention:detail", args=(intervention.id,)) + "#related_data"
|
||||||
|
)
|
||||||
|
|
||||||
|
@method_decorator(default_group_required)
|
||||||
|
@method_decorator(shared_access_required(Intervention, "id"))
|
||||||
|
def get(self, request: HttpRequest, id: str, revocation_id: str, *args, **kwargs) -> HttpResponse:
|
||||||
|
return self.__process_request(request, id, revocation_id, *args, **kwargs)
|
||||||
|
|
||||||
|
@method_decorator(default_group_required)
|
||||||
|
@method_decorator(shared_access_required(Intervention, "id"))
|
||||||
|
def post(self, request, id: str, revocation_id: str, *args, **kwargs) -> HttpResponse:
|
||||||
|
return self.__process_request(request, id, revocation_id, *args, **kwargs)
|
||||||
@@ -25,7 +25,6 @@ class BaseForm(forms.Form):
|
|||||||
cancel_redirect = None
|
cancel_redirect = None
|
||||||
form_caption = None
|
form_caption = None
|
||||||
instance = None # The data holding model object
|
instance = None # The data holding model object
|
||||||
user = None # The performing user
|
|
||||||
request = None
|
request = None
|
||||||
form_attrs = {} # Holds additional attributes, that can be used in the template
|
form_attrs = {} # Holds additional attributes, that can be used in the template
|
||||||
has_required_fields = False # Automatically set. Triggers hint rendering in templates
|
has_required_fields = False # Automatically set. Triggers hint rendering in templates
|
||||||
@@ -34,7 +33,6 @@ class BaseForm(forms.Form):
|
|||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
self.instance = kwargs.pop("instance", None)
|
self.instance = kwargs.pop("instance", None)
|
||||||
self.user = kwargs.pop("user", None)
|
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
if self.request is not None:
|
if self.request is not None:
|
||||||
self.user = self.request.user
|
self.user = self.request.user
|
||||||
@@ -48,7 +46,7 @@ class BaseForm(forms.Form):
|
|||||||
self.__check_valid_label_input_ratio()
|
self.__check_valid_label_input_ratio()
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def save(self, *arg, **kwargs):
|
def save(self):
|
||||||
# To be implemented in subclasses!
|
# To be implemented in subclasses!
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ class SimpleGeomForm(BaseForm):
|
|||||||
disabled=False,
|
disabled=False,
|
||||||
)
|
)
|
||||||
_num_geometries_ignored: int = 0
|
_num_geometries_ignored: int = 0
|
||||||
|
empty = False
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
self.read_only = kwargs.pop("read_only", True)
|
self.read_only = kwargs.pop("read_only", True)
|
||||||
@@ -49,11 +50,11 @@ class SimpleGeomForm(BaseForm):
|
|||||||
raise AttributeError
|
raise AttributeError
|
||||||
|
|
||||||
geojson = self.instance.geometry.as_feature_collection(srid=DEFAULT_SRID_RLP)
|
geojson = self.instance.geometry.as_feature_collection(srid=DEFAULT_SRID_RLP)
|
||||||
self._set_geojson_properties(geojson, title=self.instance.identifier or None)
|
geojson = self._set_geojson_properties(geojson, title=self.instance.identifier or None)
|
||||||
geom = json.dumps(geojson)
|
geom = json.dumps(geojson)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
# If no geometry exists for this form, we simply set the value to None and zoom to the maximum level
|
# If no geometry exists for this form, we simply set the value to None and zoom to the maximum level
|
||||||
geom = ""
|
geom = json.dumps({})
|
||||||
self.empty = True
|
self.empty = True
|
||||||
|
|
||||||
self.initialize_form_field("output", geom)
|
self.initialize_form_field("output", geom)
|
||||||
@@ -62,17 +63,17 @@ class SimpleGeomForm(BaseForm):
|
|||||||
super().is_valid()
|
super().is_valid()
|
||||||
is_valid = True
|
is_valid = True
|
||||||
|
|
||||||
# Get geojson from form
|
# Make sure invalid geometry is properly rendered again to the user
|
||||||
geom = self.data.get("output", None)
|
# Therefore: write submitted data back into form field
|
||||||
if geom is None or len(geom) == 0:
|
# (does not matter whether we know if it is valid or invalid)
|
||||||
# empty geometry is a valid geometry
|
submitted_data = self.data["output"]
|
||||||
self.cleaned_data["output"] = MultiPolygon(srid=DEFAULT_SRID_RLP).ewkt
|
submitted_data = json.loads(submitted_data)
|
||||||
return is_valid
|
submitted_data = self._set_geojson_properties(submitted_data)
|
||||||
geom = json.loads(geom)
|
self.initialize_form_field("output", json.dumps(submitted_data))
|
||||||
|
|
||||||
# Write submitted data back into form field to make sure invalid geometry
|
# Get geojson from form for validity checking
|
||||||
# will be rendered again on failed submit
|
geom = self.data.get("output", json.dumps({}))
|
||||||
self.initialize_form_field("output", self.data["output"])
|
geom = json.loads(geom)
|
||||||
|
|
||||||
# Initialize features list with empty MultiPolygon, so that an empty input will result in a
|
# Initialize features list with empty MultiPolygon, so that an empty input will result in a
|
||||||
# proper empty MultiPolygon object
|
# proper empty MultiPolygon object
|
||||||
@@ -84,20 +85,23 @@ class SimpleGeomForm(BaseForm):
|
|||||||
"MultiPolygon",
|
"MultiPolygon",
|
||||||
"MultiPolygon25D",
|
"MultiPolygon25D",
|
||||||
]
|
]
|
||||||
|
# Check validity for each feature of the geometry
|
||||||
for feature in features_json:
|
for feature in features_json:
|
||||||
feature_geom = feature.get("geometry", feature)
|
feature_geom = feature.get("geometry", feature)
|
||||||
if feature_geom is None:
|
if feature_geom is None:
|
||||||
# Fallback for rare cases where a feature does not contain any geometry
|
# Fallback for rare cases where a feature does not contain any geometry
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
# Try to create a geometry object from the single feature
|
||||||
feature_geom = json.dumps(feature_geom)
|
feature_geom = json.dumps(feature_geom)
|
||||||
g = gdal.OGRGeometry(feature_geom, srs=DEFAULT_SRID_RLP)
|
g = gdal.OGRGeometry(feature_geom, srs=DEFAULT_SRID_RLP)
|
||||||
|
|
||||||
flatten_geometry = g.coord_dim > 2
|
geometry_has_unwanted_dimensions = g.coord_dim > 2
|
||||||
if flatten_geometry:
|
if geometry_has_unwanted_dimensions:
|
||||||
g = self.__flatten_geom_to_2D(g)
|
g = self.__flatten_geom_to_2D(g)
|
||||||
|
|
||||||
if g.geom_type not in accepted_ogr_types:
|
geometry_type_is_accepted = g.geom_type not in accepted_ogr_types
|
||||||
|
if geometry_type_is_accepted:
|
||||||
self.add_error("output", _("Only surfaces allowed. Points or lines must be buffered."))
|
self.add_error("output", _("Only surfaces allowed. Points or lines must be buffered."))
|
||||||
is_valid &= False
|
is_valid &= False
|
||||||
return is_valid
|
return is_valid
|
||||||
@@ -109,27 +113,33 @@ class SimpleGeomForm(BaseForm):
|
|||||||
self._num_geometries_ignored += 1
|
self._num_geometries_ignored += 1
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
# Whatever this geometry object is -> try to create a Polygon from it
|
||||||
|
# The resulting polygon object automatically detects whether a valid polygon has been created or not
|
||||||
g = Polygon.from_ewkt(g.ewkt)
|
g = Polygon.from_ewkt(g.ewkt)
|
||||||
is_valid &= g.valid
|
is_valid &= g.valid
|
||||||
if not g.valid:
|
if not g.valid:
|
||||||
self.add_error("output", g.valid_reason)
|
self.add_error("output", g.valid_reason)
|
||||||
return is_valid
|
return is_valid
|
||||||
|
|
||||||
|
# If the resulting polygon is just a single polygon, we add it to the list of properly casted features
|
||||||
if isinstance(g, Polygon):
|
if isinstance(g, Polygon):
|
||||||
features.append(g)
|
features.append(g)
|
||||||
elif isinstance(g, MultiPolygon):
|
elif isinstance(g, MultiPolygon):
|
||||||
|
# The resulting polygon could be of type MultiPolygon (due to multiple surfaces)
|
||||||
|
# If so, we extract all polygons from the MultiPolygon and extend the casted features list
|
||||||
features.extend(list(g))
|
features.extend(list(g))
|
||||||
|
|
||||||
# Unionize all geometry features into one new MultiPolygon
|
# Unionize all polygon features into one new MultiPolygon
|
||||||
if features:
|
if features:
|
||||||
form_geom = MultiPolygon(*features, srid=DEFAULT_SRID_RLP).unary_union
|
form_geom = MultiPolygon(*features, srid=DEFAULT_SRID_RLP).unary_union
|
||||||
else:
|
else:
|
||||||
|
# If no features have been processed, this indicates an empty geometry - so we store an empty geometry
|
||||||
form_geom = MultiPolygon(srid=DEFAULT_SRID_RLP)
|
form_geom = MultiPolygon(srid=DEFAULT_SRID_RLP)
|
||||||
|
|
||||||
# Make sure to convert into a MultiPolygon. Relevant if a single Polygon is provided.
|
# Make sure to convert into a MultiPolygon. Relevant if a single Polygon is provided.
|
||||||
form_geom = Geometry.cast_to_multipolygon(form_geom)
|
form_geom = Geometry.cast_to_multipolygon(form_geom)
|
||||||
|
|
||||||
# Write unioned Multipolygon into cleaned data
|
# Write unionized Multipolygon back into cleaned data
|
||||||
if self.cleaned_data is None:
|
if self.cleaned_data is None:
|
||||||
self.cleaned_data = {}
|
self.cleaned_data = {}
|
||||||
self.cleaned_data["output"] = form_geom.ewkt
|
self.cleaned_data["output"] = form_geom.ewkt
|
||||||
@@ -252,6 +262,8 @@ class SimpleGeomForm(BaseForm):
|
|||||||
"""
|
"""
|
||||||
features = geojson.get("features", [])
|
features = geojson.get("features", [])
|
||||||
for feature in features:
|
for feature in features:
|
||||||
|
if not feature.get("properties", None):
|
||||||
|
feature["properties"] = {}
|
||||||
feature["properties"]["editable"] = not self.read_only
|
feature["properties"]["editable"] = not self.read_only
|
||||||
if title:
|
if title:
|
||||||
feature["properties"]["title"] = title
|
feature["properties"]["title"] = title
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import json
|
|||||||
from django.contrib.gis.db.models import MultiPolygonField
|
from django.contrib.gis.db.models import MultiPolygonField
|
||||||
from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned
|
from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned
|
||||||
from django.db import models, transaction
|
from django.db import models, transaction
|
||||||
|
from django.db.models import Q
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.contrib.gis.geos import MultiPolygon
|
from django.contrib.gis.geos import MultiPolygon
|
||||||
|
|
||||||
@@ -102,24 +103,41 @@ class Geometry(BaseResource):
|
|||||||
resolved_conflicts = all_conflicted_by_conflicts.exclude(id__in=still_conflicting_conflicts)
|
resolved_conflicts = all_conflicted_by_conflicts.exclude(id__in=still_conflicting_conflicts)
|
||||||
resolved_conflicts.delete()
|
resolved_conflicts.delete()
|
||||||
|
|
||||||
def get_data_objects(self):
|
def get_data_objects(self, limit_to_attrs: list = None):
|
||||||
""" Getter for all objects which are related to this geometry
|
""" Getter for all objects which are related to this geometry
|
||||||
|
|
||||||
|
Using the limit_to_attrs we can limit the amount of returned data directly onto the data object attributes
|
||||||
|
we want to have. Reduces memory consumption and runtime.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
objs (list): The list of objects
|
objs (list): The list of objects
|
||||||
"""
|
"""
|
||||||
objs = []
|
objs = []
|
||||||
sets = [
|
|
||||||
|
# Some related data sets can be processed rather easily
|
||||||
|
regular_sets = [
|
||||||
self.intervention_set,
|
self.intervention_set,
|
||||||
self.compensation_set,
|
|
||||||
self.ema_set,
|
self.ema_set,
|
||||||
self.ecoaccount_set,
|
self.ecoaccount_set,
|
||||||
]
|
]
|
||||||
for _set in sets:
|
for _set in regular_sets:
|
||||||
set_objs = _set.filter(
|
set_objs = _set.filter(
|
||||||
deleted=None
|
deleted=None
|
||||||
)
|
)
|
||||||
objs += set_objs
|
if limit_to_attrs:
|
||||||
|
objs += set_objs.values_list(*limit_to_attrs, flat=True)
|
||||||
|
else:
|
||||||
|
objs += set_objs
|
||||||
|
|
||||||
|
# ... but we need a special treatment for compensations, since they can be deleted directly OR inherit their
|
||||||
|
# de-facto-deleted status from their deleted parent intervention
|
||||||
|
comp_objs = self.compensation_set.filter(
|
||||||
|
Q(deleted=None) & Q(intervention__deleted=None)
|
||||||
|
)
|
||||||
|
if limit_to_attrs:
|
||||||
|
objs += comp_objs.values_list(*limit_to_attrs, flat=True)
|
||||||
|
else:
|
||||||
|
objs += comp_objs
|
||||||
return objs
|
return objs
|
||||||
|
|
||||||
def get_data_object(self):
|
def get_data_object(self):
|
||||||
@@ -397,7 +415,10 @@ class Geometry(BaseResource):
|
|||||||
"""
|
"""
|
||||||
output_geom = input_geom
|
output_geom = input_geom
|
||||||
if not isinstance(input_geom, MultiPolygon):
|
if not isinstance(input_geom, MultiPolygon):
|
||||||
output_geom = MultiPolygon(input_geom, srid=DEFAULT_SRID_RLP)
|
try:
|
||||||
|
output_geom = MultiPolygon(input_geom, srid=DEFAULT_SRID_RLP)
|
||||||
|
except TypeError as e:
|
||||||
|
raise AssertionError(f"Only (Multi)Polygon allowed! Could not convert {input_geom.geom_type} to MultiPolygon")
|
||||||
return output_geom
|
return output_geom
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|||||||
+10
-6
@@ -677,19 +677,23 @@ class GeoReferencedMixin(models.Model):
|
|||||||
return request
|
return request
|
||||||
|
|
||||||
instance_objs = []
|
instance_objs = []
|
||||||
conflicts = self.geometry.conflicts_geometries.all()
|
needed_data_object_attrs = [
|
||||||
|
"identifier"
|
||||||
|
]
|
||||||
|
conflicts = self.geometry.conflicts_geometries.iterator()
|
||||||
|
|
||||||
for conflict in conflicts:
|
for conflict in conflicts:
|
||||||
instance_objs += conflict.affected_geometry.get_data_objects()
|
# Only check the affected geometry of this conflict, since we know the conflicting geometry is self.geometry
|
||||||
|
instance_objs += conflict.affected_geometry.get_data_objects(needed_data_object_attrs)
|
||||||
|
|
||||||
conflicts = self.geometry.conflicted_by_geometries.all()
|
conflicts = self.geometry.conflicted_by_geometries.iterator()
|
||||||
for conflict in conflicts:
|
for conflict in conflicts:
|
||||||
instance_objs += conflict.conflicting_geometry.get_data_objects()
|
# Only check the conflicting geometry of this conflict, since we know the affected geometry is self.geometry
|
||||||
|
instance_objs += conflict.conflicting_geometry.get_data_objects(needed_data_object_attrs)
|
||||||
|
|
||||||
add_message = len(instance_objs) > 0
|
add_message = len(instance_objs) > 0
|
||||||
if add_message:
|
if add_message:
|
||||||
instance_identifiers = [x.identifier for x in instance_objs]
|
instance_identifiers = ", ".join(instance_objs)
|
||||||
instance_identifiers = ", ".join(instance_identifiers)
|
|
||||||
message_str = GEOMETRY_CONFLICT_WITH_TEMPLATE.format(instance_identifiers)
|
message_str = GEOMETRY_CONFLICT_WITH_TEMPLATE.format(instance_identifiers)
|
||||||
messages.info(request, message_str)
|
messages.info(request, message_str)
|
||||||
return request
|
return request
|
||||||
|
|||||||
@@ -291,5 +291,5 @@ Overwrites netgis.css attributes
|
|||||||
}
|
}
|
||||||
|
|
||||||
.netgis-menu{
|
.netgis-menu{
|
||||||
z-index: 100 !important;
|
z-index: 1 !important;
|
||||||
}
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 47 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 51 KiB |
@@ -11,4 +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=ksp2:start"
|
HELP_LINK = "https://dienste.naturschutz.rlp.de/doku/doku.php?id=ksp2:start"
|
||||||
IMPRESSUM_LINK = "https://naturschutz.rlp.de/index.php?q=impressum"
|
IMPRESSUM_LINK = "https://naturschutz.rlp.de/ueber-uns/impressum"
|
||||||
|
|||||||
@@ -5,6 +5,9 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
|
|||||||
Created on: 11.12.23
|
Created on: 11.12.23
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
import json
|
||||||
|
from json import JSONDecodeError
|
||||||
|
|
||||||
from django.views.debug import ExceptionReporter
|
from django.views.debug import ExceptionReporter
|
||||||
|
|
||||||
|
|
||||||
@@ -30,7 +33,7 @@ class KonovaExceptionReporter(ExceptionReporter):
|
|||||||
"""
|
"""
|
||||||
whitelist = [
|
whitelist = [
|
||||||
"is_email",
|
"is_email",
|
||||||
"unicdoe_hint",
|
"unicode_hint",
|
||||||
"frames",
|
"frames",
|
||||||
"request",
|
"request",
|
||||||
"user_str",
|
"user_str",
|
||||||
@@ -39,6 +42,8 @@ class KonovaExceptionReporter(ExceptionReporter):
|
|||||||
"raising_view_name",
|
"raising_view_name",
|
||||||
"exception_type",
|
"exception_type",
|
||||||
"exception_value",
|
"exception_value",
|
||||||
|
"filtered_GET_items",
|
||||||
|
"filtered_POST_items",
|
||||||
]
|
]
|
||||||
clean_data = dict()
|
clean_data = dict()
|
||||||
for entry in whitelist:
|
for entry in whitelist:
|
||||||
@@ -56,7 +61,28 @@ class KonovaExceptionReporter(ExceptionReporter):
|
|||||||
"""
|
"""
|
||||||
tb_data = super().get_traceback_data()
|
tb_data = super().get_traceback_data()
|
||||||
|
|
||||||
|
return_data = tb_data
|
||||||
if self.is_email:
|
if self.is_email:
|
||||||
tb_data = self._filter_traceback_data(tb_data)
|
filtered_data = dict()
|
||||||
|
filtered_data.update(self._filter_traceback_data(tb_data))
|
||||||
|
filtered_data.update(self._filter_POST_body(tb_data))
|
||||||
|
return_data = filtered_data
|
||||||
|
return return_data
|
||||||
|
|
||||||
return tb_data
|
def _filter_POST_body(self, tb_data: dict):
|
||||||
|
""" Filters POST body from traceback data
|
||||||
|
|
||||||
|
"""
|
||||||
|
post_data = tb_data.get("request", None)
|
||||||
|
if post_data:
|
||||||
|
post_data = post_data.body
|
||||||
|
try:
|
||||||
|
post_data = json.loads(post_data)
|
||||||
|
except JSONDecodeError:
|
||||||
|
pass
|
||||||
|
post_data = {
|
||||||
|
"filtered_POST_items": [
|
||||||
|
("body", post_data),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
return post_data
|
||||||
@@ -5,11 +5,6 @@ Contact: michel.peltriaux@sgdnord.rlp.de
|
|||||||
Created on: 17.09.21
|
Created on: 17.09.21
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from uuid import UUID
|
|
||||||
|
|
||||||
from django.contrib import messages
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
|
||||||
from django.http import HttpRequest, Http404
|
|
||||||
|
|
||||||
|
|
||||||
def format_german_float(num) -> str:
|
def format_german_float(num) -> str:
|
||||||
@@ -24,27 +19,3 @@ def format_german_float(num) -> str:
|
|||||||
num (str): The number as german Gleitkommazahl
|
num (str): The number as german Gleitkommazahl
|
||||||
"""
|
"""
|
||||||
return format(num, "0,.2f").replace(",", "X").replace(".", ",").replace("X", ".")
|
return format(num, "0,.2f").replace(",", "X").replace(".", ",").replace("X", ".")
|
||||||
|
|
||||||
|
|
||||||
def check_user_is_in_any_group(request: HttpRequest):
|
|
||||||
"""
|
|
||||||
Checks for any group membership. Adds a message in case of having none.
|
|
||||||
|
|
||||||
"""
|
|
||||||
user = request.user
|
|
||||||
# Inform user about missing group privileges!
|
|
||||||
groups = user.groups.all()
|
|
||||||
if not groups:
|
|
||||||
messages.info(
|
|
||||||
request,
|
|
||||||
_("+++ Attention: You are not part of any group. You won't be able to create, edit or do anything. Please contact an administrator. +++")
|
|
||||||
)
|
|
||||||
return request
|
|
||||||
|
|
||||||
def check_id_is_valid_uuid(uuid: str):
|
|
||||||
if uuid:
|
|
||||||
try:
|
|
||||||
# Check whether the id is a proper uuid or something that would break a db fetch
|
|
||||||
UUID(uuid)
|
|
||||||
except ValueError:
|
|
||||||
raise Http404
|
|
||||||
|
|||||||
@@ -5,18 +5,18 @@ Contact: michel.peltriaux@sgdnord.rlp.de
|
|||||||
Created on: 09.11.20
|
Created on: 09.11.20
|
||||||
|
|
||||||
"""
|
"""
|
||||||
import random
|
import secrets
|
||||||
import string
|
import string
|
||||||
|
|
||||||
|
|
||||||
def generate_token() -> str:
|
def generate_token(length: int = 64) -> str:
|
||||||
""" Shortcut for default generating of e.g. API token
|
""" Shortcut for default generating of e.g. API token
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
token (str)
|
token (str)
|
||||||
"""
|
"""
|
||||||
return generate_random_string(
|
return generate_random_string(
|
||||||
length=64,
|
length=length,
|
||||||
use_numbers=True,
|
use_numbers=True,
|
||||||
use_letters_lc=True
|
use_letters_lc=True
|
||||||
)
|
)
|
||||||
@@ -35,6 +35,27 @@ def generate_random_string(length: int, use_numbers: bool = False, use_letters_l
|
|||||||
elements.append(string.ascii_uppercase)
|
elements.append(string.ascii_uppercase)
|
||||||
|
|
||||||
elements = "".join(elements)
|
elements = "".join(elements)
|
||||||
ret_val = "".join(random.choice(elements) for i in range(length))
|
ret_val = "".join(secrets.choice(elements) for i in range(length))
|
||||||
return ret_val
|
return ret_val
|
||||||
|
|
||||||
|
class IdentifierGenerator:
|
||||||
|
_MODEL = None
|
||||||
|
|
||||||
|
def __init__(self, model):
|
||||||
|
from konova.models import BaseObject
|
||||||
|
if not issubclass(model, BaseObject):
|
||||||
|
raise AssertionError("Model must be a subclass of BaseObject!")
|
||||||
|
|
||||||
|
self._MODEL = model
|
||||||
|
|
||||||
|
def generate_id(self) -> str:
|
||||||
|
""" Generates a unique identifier
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
unpersisted_object = self._MODEL()
|
||||||
|
identifier = unpersisted_object.generate_new_identifier()
|
||||||
|
while self._MODEL.objects.filter(identifier=identifier).exists():
|
||||||
|
identifier = unpersisted_object.generate_new_identifier()
|
||||||
|
return identifier
|
||||||
|
|||||||
+21
-8
@@ -5,7 +5,7 @@ Contact: michel.peltriaux@sgdnord.rlp.de
|
|||||||
Created on: 09.11.20
|
Created on: 09.11.20
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from django.core.mail import send_mail
|
from django.core.mail import send_mail, EmailMultiAlternatives
|
||||||
from django.template.loader import render_to_string
|
from django.template.loader import render_to_string
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
@@ -45,6 +45,19 @@ class Mailer:
|
|||||||
auth_password=self.auth_password
|
auth_password=self.auth_password
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def send_via_bcc(self, recipient_list: list, subject: str, msg: str):
|
||||||
|
"""
|
||||||
|
Sends a mail with subject and message where recipients will be masked via bcc
|
||||||
|
"""
|
||||||
|
email_obj = EmailMultiAlternatives(
|
||||||
|
subject,
|
||||||
|
msg,
|
||||||
|
self.from_mail,
|
||||||
|
bcc=recipient_list,
|
||||||
|
)
|
||||||
|
email_obj.attach_alternative(msg, "text/html")
|
||||||
|
return email_obj.send(fail_silently=self.fail_silently)
|
||||||
|
|
||||||
def send_mail_shared_access_removed(self, obj, user, municipals_names):
|
def send_mail_shared_access_removed(self, obj, user, municipals_names):
|
||||||
""" Send a mail if user has no access to the object anymore
|
""" Send a mail if user has no access to the object anymore
|
||||||
|
|
||||||
@@ -115,7 +128,7 @@ class Mailer:
|
|||||||
}
|
}
|
||||||
msg = render_to_string("email/sharing/shared_access_given_team.html", context)
|
msg = render_to_string("email/sharing/shared_access_given_team.html", context)
|
||||||
user_mail_address = users_to_notify.values_list("email", flat=True)
|
user_mail_address = users_to_notify.values_list("email", flat=True)
|
||||||
self.send(
|
self.send_via_bcc(
|
||||||
user_mail_address,
|
user_mail_address,
|
||||||
_("{} - Shared access given").format(obj.identifier),
|
_("{} - Shared access given").format(obj.identifier),
|
||||||
msg
|
msg
|
||||||
@@ -141,7 +154,7 @@ class Mailer:
|
|||||||
}
|
}
|
||||||
msg = render_to_string("email/sharing/shared_access_removed_team.html", context)
|
msg = render_to_string("email/sharing/shared_access_removed_team.html", context)
|
||||||
user_mail_address = users_to_notify.values_list("email", flat=True)
|
user_mail_address = users_to_notify.values_list("email", flat=True)
|
||||||
self.send(
|
self.send_via_bcc(
|
||||||
user_mail_address,
|
user_mail_address,
|
||||||
_("{} - Shared access removed").format(obj.identifier),
|
_("{} - Shared access removed").format(obj.identifier),
|
||||||
msg
|
msg
|
||||||
@@ -167,7 +180,7 @@ class Mailer:
|
|||||||
}
|
}
|
||||||
msg = render_to_string("email/recording/shared_data_unrecorded_team.html", context)
|
msg = render_to_string("email/recording/shared_data_unrecorded_team.html", context)
|
||||||
user_mail_address = users_to_notify.values_list("email", flat=True)
|
user_mail_address = users_to_notify.values_list("email", flat=True)
|
||||||
self.send(
|
self.send_via_bcc(
|
||||||
user_mail_address,
|
user_mail_address,
|
||||||
_("{} - Shared data unrecorded").format(obj.identifier),
|
_("{} - Shared data unrecorded").format(obj.identifier),
|
||||||
msg
|
msg
|
||||||
@@ -193,7 +206,7 @@ class Mailer:
|
|||||||
}
|
}
|
||||||
msg = render_to_string("email/recording/shared_data_recorded_team.html", context)
|
msg = render_to_string("email/recording/shared_data_recorded_team.html", context)
|
||||||
user_mail_address = users_to_notify.values_list("email", flat=True)
|
user_mail_address = users_to_notify.values_list("email", flat=True)
|
||||||
self.send(
|
self.send_via_bcc(
|
||||||
user_mail_address,
|
user_mail_address,
|
||||||
_("{} - Shared data recorded").format(obj.identifier),
|
_("{} - Shared data recorded").format(obj.identifier),
|
||||||
msg
|
msg
|
||||||
@@ -219,7 +232,7 @@ class Mailer:
|
|||||||
}
|
}
|
||||||
msg = render_to_string("email/checking/shared_data_checked_team.html", context)
|
msg = render_to_string("email/checking/shared_data_checked_team.html", context)
|
||||||
user_mail_address = users_to_notify.values_list("email", flat=True)
|
user_mail_address = users_to_notify.values_list("email", flat=True)
|
||||||
self.send(
|
self.send_via_bcc(
|
||||||
user_mail_address,
|
user_mail_address,
|
||||||
_("{} - Shared data checked").format(obj.identifier),
|
_("{} - Shared data checked").format(obj.identifier),
|
||||||
msg
|
msg
|
||||||
@@ -244,7 +257,7 @@ class Mailer:
|
|||||||
}
|
}
|
||||||
msg = render_to_string("email/other/deduction_changed_team.html", context)
|
msg = render_to_string("email/other/deduction_changed_team.html", context)
|
||||||
user_mail_address = users_to_notify.values_list("email", flat=True)
|
user_mail_address = users_to_notify.values_list("email", flat=True)
|
||||||
self.send(
|
self.send_via_bcc(
|
||||||
user_mail_address,
|
user_mail_address,
|
||||||
_("{} - Deduction changed").format(obj.identifier),
|
_("{} - Deduction changed").format(obj.identifier),
|
||||||
msg
|
msg
|
||||||
@@ -270,7 +283,7 @@ class Mailer:
|
|||||||
}
|
}
|
||||||
msg = render_to_string("email/deleting/shared_data_deleted_team.html", context)
|
msg = render_to_string("email/deleting/shared_data_deleted_team.html", context)
|
||||||
user_mail_address = users_to_notify.values_list("email", flat=True)
|
user_mail_address = users_to_notify.values_list("email", flat=True)
|
||||||
self.send(
|
self.send_via_bcc(
|
||||||
user_mail_address,
|
user_mail_address,
|
||||||
_("{} - Shared data deleted").format(obj.identifier),
|
_("{} - Shared data deleted").format(obj.identifier),
|
||||||
msg
|
msg
|
||||||
|
|||||||
+10
-8
@@ -1,6 +1,6 @@
|
|||||||
"""
|
"""
|
||||||
Author: Michel Peltriaux
|
Author: Michel Peltriaux
|
||||||
Created on: 17.10.25
|
Created on: 14.12.25
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
@@ -30,15 +30,17 @@ class QrCode:
|
|||||||
Returns:
|
Returns:
|
||||||
qrcode_svg (str): The qr code as svg
|
qrcode_svg (str): The qr code as svg
|
||||||
"""
|
"""
|
||||||
img_factory = svg.SvgImage
|
qr = qrcode.QRCode(
|
||||||
qrcode_img = qrcode.make(
|
image_factory=qrcode.image.svg.SvgPathImage,
|
||||||
content,
|
|
||||||
image_factory=img_factory,
|
|
||||||
box_size=size
|
box_size=size
|
||||||
)
|
)
|
||||||
stream = BytesIO()
|
qr.add_data(content)
|
||||||
qrcode_img.save(stream)
|
qr.make(
|
||||||
return stream.getvalue().decode()
|
fit=True
|
||||||
|
)
|
||||||
|
|
||||||
|
img = qr.make_image()
|
||||||
|
return img.to_string(encoding="unicode")
|
||||||
|
|
||||||
def get_img(self):
|
def get_img(self):
|
||||||
return self._img
|
return self._img
|
||||||
|
|||||||
@@ -178,7 +178,9 @@ class TableRenderMixin:
|
|||||||
if len(value) > max_length:
|
if len(value) > max_length:
|
||||||
value = f"{value[:max_length]}..."
|
value = f"{value[:max_length]}..."
|
||||||
value = format_html(
|
value = format_html(
|
||||||
f'<div title="{value_orig}">{value}</div>'
|
'<div title="{}">{}</div>',
|
||||||
|
value_orig,
|
||||||
|
value
|
||||||
)
|
)
|
||||||
return value
|
return value
|
||||||
|
|
||||||
@@ -222,7 +224,7 @@ class TableRenderMixin:
|
|||||||
tooltip=_("Full access granted") if is_entry_shared else _("Access not granted"),
|
tooltip=_("Full access granted") if is_entry_shared else _("Access not granted"),
|
||||||
icn_class="fas fa-edit rlp-r-inv" if is_entry_shared else "far fa-edit",
|
icn_class="fas fa-edit rlp-r-inv" if is_entry_shared else "far fa-edit",
|
||||||
)
|
)
|
||||||
return format_html(html)
|
return format_html(html, None)
|
||||||
|
|
||||||
|
|
||||||
class TableOrderMixin:
|
class TableOrderMixin:
|
||||||
|
|||||||
@@ -1,376 +0,0 @@
|
|||||||
"""
|
|
||||||
Author: Michel Peltriaux
|
|
||||||
Created on: 15.10.25
|
|
||||||
|
|
||||||
"""
|
|
||||||
from abc import abstractmethod
|
|
||||||
|
|
||||||
from bootstrap_modal_forms.mixins import is_ajax
|
|
||||||
from django.contrib import messages
|
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
|
||||||
from django.http import HttpRequest, JsonResponse, HttpResponseRedirect
|
|
||||||
from django.shortcuts import render, redirect, get_object_or_404
|
|
||||||
from django.urls import reverse
|
|
||||||
from django.views import View
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
|
||||||
|
|
||||||
from konova.contexts import BaseContext
|
|
||||||
from konova.forms import BaseForm, SimpleGeomForm
|
|
||||||
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
|
|
||||||
from konova.utils.general import check_user_is_in_any_group, check_id_is_valid_uuid
|
|
||||||
from konova.utils.message_templates import MISSING_GROUP_PERMISSION, DATA_UNSHARED, IDENTIFIER_REPLACED, \
|
|
||||||
GEOMETRY_SIMPLIFIED, GEOMETRIES_IGNORED_TEMPLATE, RECORDED_BLOCKS_EDIT, FORM_INVALID
|
|
||||||
|
|
||||||
|
|
||||||
class BaseView(View):
|
|
||||||
_TEMPLATE: str = "CHANGE_ME"
|
|
||||||
_TAB_TITLE: str = "CHANGE_ME"
|
|
||||||
_REDIRECT_URL: str = "CHANGE_ME"
|
|
||||||
_REDIRECT_URL_ERROR: str = "home"
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
abstract = True
|
|
||||||
|
|
||||||
def dispatch(self, request, *args, **kwargs):
|
|
||||||
request = check_user_is_in_any_group(request)
|
|
||||||
|
|
||||||
if not self._user_has_permission(request.user):
|
|
||||||
messages.info(request, MISSING_GROUP_PERMISSION)
|
|
||||||
return redirect(reverse(self._REDIRECT_URL_ERROR))
|
|
||||||
|
|
||||||
if not self._user_has_shared_access(request.user, **kwargs):
|
|
||||||
messages.info(request, DATA_UNSHARED)
|
|
||||||
return redirect(reverse(self._REDIRECT_URL_ERROR))
|
|
||||||
|
|
||||||
return super().dispatch(request, *args, **kwargs)
|
|
||||||
|
|
||||||
def _user_has_permission(self, user):
|
|
||||||
""" Has to be implemented properly by inheriting classes
|
|
||||||
|
|
||||||
Args:
|
|
||||||
user ():
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
|
|
||||||
"""
|
|
||||||
return False
|
|
||||||
|
|
||||||
def _user_has_shared_access(self, user, **kwargs):
|
|
||||||
""" Has to be implemented properly by inheriting classes
|
|
||||||
|
|
||||||
Args:
|
|
||||||
user ():
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
|
|
||||||
"""
|
|
||||||
return False
|
|
||||||
|
|
||||||
def _get_redirect_url(self, *args, **kwargs):
|
|
||||||
return self._REDIRECT_URL
|
|
||||||
|
|
||||||
def _get_redirect_url_error(self, *args, **kwargs):
|
|
||||||
return self._REDIRECT_URL_ERROR
|
|
||||||
|
|
||||||
class BaseModalFormView(BaseView):
|
|
||||||
_TEMPLATE = "modal/modal_form.html"
|
|
||||||
_MODEL_CLS = None
|
|
||||||
_FORM_CLS = None
|
|
||||||
_TAB_TITLE = None
|
|
||||||
_MSG_SUCCESS = None
|
|
||||||
_MSG_ERROR = None
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
abstract = True
|
|
||||||
|
|
||||||
def _user_has_shared_access(self, user, **kwargs):
|
|
||||||
obj = get_object_or_404(self._MODEL_CLS, id=kwargs.get("id"))
|
|
||||||
return obj.is_shared_with(user)
|
|
||||||
|
|
||||||
def get(self, request: HttpRequest, id: str, *args, **kwargs):
|
|
||||||
obj = self._MODEL_CLS.objects.get(id=id)
|
|
||||||
form = self._FORM_CLS(request.POST or None, instance=obj, request=request, **kwargs)
|
|
||||||
context = {
|
|
||||||
"form": form,
|
|
||||||
}
|
|
||||||
context = BaseContext(request, context).context
|
|
||||||
return render(request, self._TEMPLATE, context)
|
|
||||||
|
|
||||||
def post(self, request: HttpRequest, id: str, *args, **kwargs):
|
|
||||||
obj = self._MODEL_CLS.objects.get(id=id)
|
|
||||||
form = self._FORM_CLS(request.POST or None, instance=obj, request=request, **kwargs)
|
|
||||||
redirect_url = self._get_redirect_url(obj=obj)
|
|
||||||
if form.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.
|
|
||||||
form.save()
|
|
||||||
messages.success(
|
|
||||||
request,
|
|
||||||
self._MSG_SUCCESS
|
|
||||||
)
|
|
||||||
return HttpResponseRedirect(redirect_url)
|
|
||||||
else:
|
|
||||||
context = {
|
|
||||||
"form": form,
|
|
||||||
}
|
|
||||||
context = BaseContext(request, context).context
|
|
||||||
return render(request, self._TEMPLATE, context)
|
|
||||||
|
|
||||||
def _get_redirect_url(self, *args, **kwargs):
|
|
||||||
obj = kwargs.get("obj", None)
|
|
||||||
assert obj is not None
|
|
||||||
return reverse(self._REDIRECT_URL, args=(obj.id,))
|
|
||||||
|
|
||||||
|
|
||||||
class BaseIndexView(BaseView):
|
|
||||||
""" Base class for index views
|
|
||||||
|
|
||||||
"""
|
|
||||||
_TEMPLATE = "generic_index.html"
|
|
||||||
_INDEX_TABLE_CLS = None
|
|
||||||
_REDIRECT_URL = "home"
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
abstract = True
|
|
||||||
|
|
||||||
def get(self, request: HttpRequest):
|
|
||||||
qs = self._get_queryset()
|
|
||||||
table = self._INDEX_TABLE_CLS(
|
|
||||||
request=request,
|
|
||||||
queryset=qs
|
|
||||||
)
|
|
||||||
context = {
|
|
||||||
"table": table,
|
|
||||||
TAB_TITLE_IDENTIFIER: self._TAB_TITLE,
|
|
||||||
}
|
|
||||||
context = BaseContext(request, context).context
|
|
||||||
return render(request, self._TEMPLATE, context)
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def _get_queryset(self):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def _user_has_permission(self, user):
|
|
||||||
# No specific permissions needed for opening base index view
|
|
||||||
return True
|
|
||||||
|
|
||||||
def _user_has_shared_access(self, user, **kwargs):
|
|
||||||
# No specific constraints for shared access of index views
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
class BaseIdentifierGeneratorView(BaseView):
|
|
||||||
_MODEL_CLS = None
|
|
||||||
_REDIRECT_URL: str = "home"
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
abstract = True
|
|
||||||
|
|
||||||
def get(self, request: HttpRequest):
|
|
||||||
tmp_obj = self._MODEL_CLS()
|
|
||||||
identifier = tmp_obj.generate_new_identifier()
|
|
||||||
while self._MODEL_CLS.objects.filter(identifier=identifier).exists():
|
|
||||||
identifier = tmp_obj.generate_new_identifier()
|
|
||||||
return JsonResponse(
|
|
||||||
data={
|
|
||||||
"gen_data": identifier
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
def _user_has_permission(self, user):
|
|
||||||
""" Should be overwritten in inheriting classes!
|
|
||||||
|
|
||||||
Args:
|
|
||||||
user ():
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
|
|
||||||
"""
|
|
||||||
return user.is_default_user()
|
|
||||||
|
|
||||||
def _user_has_shared_access(self, user, **kwargs):
|
|
||||||
# No specific constraints for shared access
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
class BaseFormView(BaseView):
|
|
||||||
_MODEL_CLS = None
|
|
||||||
_FORM_CLS = None
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
abstract = True
|
|
||||||
|
|
||||||
def _get_additional_context(self, **kwargs):
|
|
||||||
"""
|
|
||||||
|
|
||||||
Args:
|
|
||||||
**kwargs ():
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
|
|
||||||
"""
|
|
||||||
return {}
|
|
||||||
|
|
||||||
|
|
||||||
class BaseSpatialLocatedObjectFormView(LoginRequiredMixin, BaseFormView):
|
|
||||||
_GEOMETRY_FORM_CLS = SimpleGeomForm
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
abstract = True
|
|
||||||
|
|
||||||
|
|
||||||
class BaseNewSpatialLocatedObjectFormView(BaseSpatialLocatedObjectFormView):
|
|
||||||
|
|
||||||
def _user_has_permission(self, user):
|
|
||||||
# User has to have default privilege to call this endpoint
|
|
||||||
return user.is_default_user()
|
|
||||||
|
|
||||||
def _user_has_shared_access(self, user, **kwargs):
|
|
||||||
# There is no shared access control since nothing exists yet
|
|
||||||
return True
|
|
||||||
|
|
||||||
def get(self, request: HttpRequest, **kwargs):
|
|
||||||
form: BaseForm = self._FORM_CLS(None, **kwargs, user=request.user)
|
|
||||||
geom_form: SimpleGeomForm = self._GEOMETRY_FORM_CLS(None, user=request.user, read_only=False)
|
|
||||||
|
|
||||||
context = self._get_additional_context()
|
|
||||||
context = BaseContext(request, additional_context=context).context
|
|
||||||
context.update(
|
|
||||||
{
|
|
||||||
"form": form,
|
|
||||||
"geom_form": geom_form,
|
|
||||||
TAB_TITLE_IDENTIFIER: self._TAB_TITLE,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
return render(request, self._TEMPLATE, context)
|
|
||||||
|
|
||||||
def post(self, request: HttpRequest, **kwargs):
|
|
||||||
form: BaseForm = self._FORM_CLS(request.POST or None, **kwargs, user=request.user)
|
|
||||||
geom_form: SimpleGeomForm = self._GEOMETRY_FORM_CLS(request.POST or None, user=request.user, read_only=False)
|
|
||||||
|
|
||||||
if form.is_valid() and geom_form.is_valid():
|
|
||||||
obj = form.save(request.user, geom_form)
|
|
||||||
obj_redirect_url = reverse(self._REDIRECT_URL, args=(obj.id,))
|
|
||||||
|
|
||||||
generated_identifier = form.cleaned_data.get("identifier", None)
|
|
||||||
|
|
||||||
if generated_identifier != obj.identifier:
|
|
||||||
messages.info(
|
|
||||||
request,
|
|
||||||
IDENTIFIER_REPLACED.format(
|
|
||||||
generated_identifier,
|
|
||||||
obj.identifier
|
|
||||||
)
|
|
||||||
)
|
|
||||||
messages.success(request, _("{} added").format(obj.identifier))
|
|
||||||
if geom_form.has_geometry_simplified():
|
|
||||||
messages.info(
|
|
||||||
request,
|
|
||||||
GEOMETRY_SIMPLIFIED
|
|
||||||
)
|
|
||||||
|
|
||||||
num_ignored_geometries = geom_form.get_num_geometries_ignored()
|
|
||||||
if num_ignored_geometries > 0:
|
|
||||||
messages.info(
|
|
||||||
request,
|
|
||||||
GEOMETRIES_IGNORED_TEMPLATE.format(num_ignored_geometries)
|
|
||||||
)
|
|
||||||
|
|
||||||
return redirect(obj_redirect_url)
|
|
||||||
else:
|
|
||||||
context = self._get_additional_context()
|
|
||||||
messages.error(request, FORM_INVALID, extra_tags="danger",)
|
|
||||||
|
|
||||||
context = BaseContext(request, additional_context=context).context
|
|
||||||
context.update(
|
|
||||||
{
|
|
||||||
"form": form,
|
|
||||||
"geom_form": geom_form,
|
|
||||||
TAB_TITLE_IDENTIFIER: self._TAB_TITLE,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
return render(request, self._TEMPLATE, context)
|
|
||||||
|
|
||||||
|
|
||||||
class BaseEditSpatialLocatedObjectFormView(BaseSpatialLocatedObjectFormView):
|
|
||||||
_TAB_TITLE = _("Edit {}")
|
|
||||||
|
|
||||||
def get(self, request: HttpRequest, id: str):
|
|
||||||
obj = get_object_or_404(
|
|
||||||
self._MODEL_CLS,
|
|
||||||
id=id
|
|
||||||
)
|
|
||||||
obj_redirect_url = reverse(self._REDIRECT_URL, args=(obj.id,))
|
|
||||||
|
|
||||||
if obj.is_recorded:
|
|
||||||
messages.info(
|
|
||||||
request,
|
|
||||||
RECORDED_BLOCKS_EDIT
|
|
||||||
)
|
|
||||||
return redirect(obj_redirect_url)
|
|
||||||
|
|
||||||
form: BaseForm = self._FORM_CLS(None, instance=obj, user=request.user)
|
|
||||||
geom_form: SimpleGeomForm = self._GEOMETRY_FORM_CLS(None, instance=obj, read_only=False)
|
|
||||||
|
|
||||||
context = self._get_additional_context()
|
|
||||||
context = BaseContext(request, additional_context=context).context
|
|
||||||
context.update(
|
|
||||||
{
|
|
||||||
"form": form,
|
|
||||||
"geom_form": geom_form,
|
|
||||||
TAB_TITLE_IDENTIFIER: self._TAB_TITLE.format(obj.identifier),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
return render(request, self._TEMPLATE, context)
|
|
||||||
|
|
||||||
def post(self, request: HttpRequest, id: str):
|
|
||||||
obj = get_object_or_404(
|
|
||||||
self._MODEL_CLS,
|
|
||||||
id=id
|
|
||||||
)
|
|
||||||
obj_redirect_url = reverse(self._REDIRECT_URL, args=(obj.id,))
|
|
||||||
|
|
||||||
form: BaseForm = self._FORM_CLS(request.POST or None, instance=obj, user=request.user)
|
|
||||||
geom_form: SimpleGeomForm = self._GEOMETRY_FORM_CLS(request.POST or None, instance=obj, read_only=False)
|
|
||||||
|
|
||||||
if form.is_valid() and geom_form.is_valid():
|
|
||||||
obj = form.save(request.user, geom_form)
|
|
||||||
messages.success(request, _("{} edited").format(obj.identifier))
|
|
||||||
|
|
||||||
if geom_form.has_geometry_simplified():
|
|
||||||
messages.info(
|
|
||||||
request,
|
|
||||||
GEOMETRY_SIMPLIFIED
|
|
||||||
)
|
|
||||||
|
|
||||||
num_ignored_geometries = geom_form.get_num_geometries_ignored()
|
|
||||||
if num_ignored_geometries > 0:
|
|
||||||
messages.info(
|
|
||||||
request,
|
|
||||||
GEOMETRIES_IGNORED_TEMPLATE.format(num_ignored_geometries)
|
|
||||||
)
|
|
||||||
|
|
||||||
return redirect(obj_redirect_url)
|
|
||||||
else:
|
|
||||||
context = self._get_additional_context()
|
|
||||||
messages.error(request, FORM_INVALID, extra_tags="danger",)
|
|
||||||
|
|
||||||
context = BaseContext(request, additional_context=context).context
|
|
||||||
context.update(
|
|
||||||
{
|
|
||||||
"form": form,
|
|
||||||
"geom_form": geom_form,
|
|
||||||
TAB_TITLE_IDENTIFIER: self._TAB_TITLE.format(obj.identifier),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
return render(request, self._TEMPLATE, context)
|
|
||||||
|
|
||||||
def _user_has_shared_access(self, user, **kwargs):
|
|
||||||
obj = get_object_or_404(self._MODEL_CLS, id=kwargs.get('id', None))
|
|
||||||
return obj.is_shared_with(user)
|
|
||||||
|
|
||||||
def _user_has_permission(self, user):
|
|
||||||
return user.is_default_user()
|
|
||||||
+92
-54
@@ -6,88 +6,126 @@ Created on: 22.08.22
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
|
from django.http import Http404
|
||||||
|
from django.shortcuts import get_object_or_404
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
from django.views import View
|
||||||
|
|
||||||
from intervention.forms.modals.deduction import NewEcoAccountDeductionModalForm, EditEcoAccountDeductionModalForm, \
|
from intervention.forms.modals.deduction import NewEcoAccountDeductionModalForm, EditEcoAccountDeductionModalForm, \
|
||||||
RemoveEcoAccountDeductionModalForm
|
RemoveEcoAccountDeductionModalForm
|
||||||
from konova.utils.general import check_id_is_valid_uuid
|
from konova.utils.message_templates import DEDUCTION_ADDED, DEDUCTION_EDITED, DEDUCTION_REMOVED, DEDUCTION_UNKNOWN
|
||||||
from konova.views.base import BaseModalFormView
|
|
||||||
|
|
||||||
|
|
||||||
class AbstractDeductionView(BaseModalFormView):
|
class AbstractDeductionView(View):
|
||||||
_REDIRECT_URL = None
|
model = None
|
||||||
|
redirect_url = None
|
||||||
def dispatch(self, request, *args, **kwargs):
|
|
||||||
check_id_is_valid_uuid(kwargs.get("id"))
|
|
||||||
return super().dispatch(request, *args, **kwargs)
|
|
||||||
|
|
||||||
def _custom_check(self, obj):
|
def _custom_check(self, obj):
|
||||||
"""
|
"""
|
||||||
Can be used by inheriting classes to provide custom checks before further processing
|
Can be used by inheriting classes to provide custom checks before further processing
|
||||||
|
|
||||||
"""
|
"""
|
||||||
pass
|
raise NotImplementedError("Must be implemented in subclasses")
|
||||||
|
|
||||||
def _user_has_permission(self, user) -> bool:
|
|
||||||
"""
|
|
||||||
|
|
||||||
Args:
|
|
||||||
user ():
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
|
|
||||||
"""
|
|
||||||
return user.is_default_user()
|
|
||||||
|
|
||||||
def _user_has_shared_access(self, user, **kwargs) -> bool:
|
|
||||||
""" A user has shared access on
|
|
||||||
|
|
||||||
Args:
|
|
||||||
user (User): The performing user
|
|
||||||
kwargs (dict): Parameters
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
bool: True if the user has access to the requested object, False otherwise
|
|
||||||
"""
|
|
||||||
ret_val: bool = False
|
|
||||||
try:
|
|
||||||
obj = self._MODEL_CLS.objects.get(
|
|
||||||
id=kwargs.get("id")
|
|
||||||
)
|
|
||||||
ret_val = obj.is_shared_with(user)
|
|
||||||
except ObjectDoesNotExist:
|
|
||||||
ret_val = False
|
|
||||||
return ret_val
|
|
||||||
|
|
||||||
def _get_redirect_url(self, *args, **kwargs):
|
|
||||||
obj = kwargs.get("obj", None)
|
|
||||||
assert obj is not None
|
|
||||||
return reverse(self._REDIRECT_URL, args=(obj.id,)) + "#related_data"
|
|
||||||
|
|
||||||
|
|
||||||
class AbstractNewDeductionView(AbstractDeductionView):
|
class AbstractNewDeductionView(AbstractDeductionView):
|
||||||
_FORM_CLS = NewEcoAccountDeductionModalForm
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
abstract = True
|
abstract = True
|
||||||
|
|
||||||
|
def get(self, request, id: str):
|
||||||
|
""" Renders a modal form view for creating deductions
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request (HttpRequest): The incoming request
|
||||||
|
id (str): The obj's id which shall benefit from this deduction
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
obj = get_object_or_404(self.model, id=id)
|
||||||
|
self._custom_check(obj)
|
||||||
|
form = NewEcoAccountDeductionModalForm(request.POST or None, instance=obj, request=request)
|
||||||
|
return form.process_request(
|
||||||
|
request,
|
||||||
|
msg_success=DEDUCTION_ADDED,
|
||||||
|
redirect_url=reverse(self.redirect_url, args=(id,)) + "#related_data",
|
||||||
|
)
|
||||||
|
|
||||||
|
def post(self, request, id: str):
|
||||||
|
return self.get(request, id)
|
||||||
|
|
||||||
|
|
||||||
class AbstractEditDeductionView(AbstractDeductionView):
|
class AbstractEditDeductionView(AbstractDeductionView):
|
||||||
_FORM_CLS = EditEcoAccountDeductionModalForm
|
|
||||||
|
|
||||||
def dispatch(self, request, *args, **kwargs):
|
def _custom_check(self, obj):
|
||||||
check_id_is_valid_uuid(kwargs.get("deduction_id"))
|
pass
|
||||||
return super().dispatch(request, *args, **kwargs)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
abstract = True
|
abstract = True
|
||||||
|
|
||||||
|
def get(self, request, id: str, deduction_id: str):
|
||||||
|
""" Renders a modal view for editing deductions
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request (HttpRequest): The incoming request
|
||||||
|
id (str): The object's id
|
||||||
|
deduction_id (str): The deduction's id
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
obj = get_object_or_404(self.model, id=id)
|
||||||
|
self._custom_check(obj)
|
||||||
|
try:
|
||||||
|
eco_deduction = obj.deductions.get(id=deduction_id)
|
||||||
|
except ObjectDoesNotExist:
|
||||||
|
raise Http404(DEDUCTION_UNKNOWN)
|
||||||
|
|
||||||
|
form = EditEcoAccountDeductionModalForm(request.POST or None, instance=obj, deduction=eco_deduction,
|
||||||
|
request=request)
|
||||||
|
return form.process_request(
|
||||||
|
request=request,
|
||||||
|
msg_success=DEDUCTION_EDITED,
|
||||||
|
redirect_url=reverse(self.redirect_url, args=(id,)) + "#related_data"
|
||||||
|
)
|
||||||
|
|
||||||
|
def post(self, request, id: str, deduction_id: str):
|
||||||
|
return self.get(request, id, deduction_id)
|
||||||
|
|
||||||
|
|
||||||
class AbstractRemoveDeductionView(AbstractDeductionView):
|
class AbstractRemoveDeductionView(AbstractDeductionView):
|
||||||
_FORM_CLS = RemoveEcoAccountDeductionModalForm
|
|
||||||
|
|
||||||
def dispatch(self, request, *args, **kwargs):
|
def _custom_check(self, obj):
|
||||||
check_id_is_valid_uuid(kwargs.get("deduction_id"))
|
pass
|
||||||
return super().dispatch(request, *args, **kwargs)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
abstract = True
|
abstract = True
|
||||||
|
|
||||||
|
def get(self, request, id: str, deduction_id: str):
|
||||||
|
""" Renders a modal view for removing deductions
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request (HttpRequest): The incoming request
|
||||||
|
id (str): The object's id
|
||||||
|
deduction_id (str): The deduction's id
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
obj = get_object_or_404(self.model, id=id)
|
||||||
|
self._custom_check(obj)
|
||||||
|
try:
|
||||||
|
eco_deduction = obj.deductions.get(id=deduction_id)
|
||||||
|
except ObjectDoesNotExist:
|
||||||
|
raise Http404(DEDUCTION_UNKNOWN)
|
||||||
|
form = RemoveEcoAccountDeductionModalForm(request.POST or None, instance=obj, deduction=eco_deduction,
|
||||||
|
request=request)
|
||||||
|
return form.process_request(
|
||||||
|
request=request,
|
||||||
|
msg_success=DEDUCTION_REMOVED,
|
||||||
|
redirect_url=reverse(self.redirect_url, args=(id,)) + "#related_data"
|
||||||
|
)
|
||||||
|
|
||||||
|
def post(self, request, id: str, deduction_id: str):
|
||||||
|
return self.get(request, id, deduction_id)
|
||||||
|
|||||||
+12
-94
@@ -1,107 +1,25 @@
|
|||||||
"""
|
"""
|
||||||
Author: Michel Peltriaux
|
Author: Michel Peltriaux
|
||||||
Created on: 17.10.25
|
Created on: 14.12.25
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from abc import abstractmethod
|
from abc import ABC
|
||||||
|
|
||||||
from django.contrib import messages
|
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
from django.http import HttpRequest
|
from django.http import HttpRequest, HttpResponse
|
||||||
from django.shortcuts import render
|
from django.utils.decorators import method_decorator
|
||||||
|
from django.views import View
|
||||||
|
|
||||||
from konova.contexts import BaseContext
|
from konova.decorators import uuid_required, any_group_check
|
||||||
from konova.forms import SimpleGeomForm
|
|
||||||
from konova.settings import DEFAULT_GROUP, ZB_GROUP, ETS_GROUP
|
|
||||||
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
|
|
||||||
from konova.utils.general import check_id_is_valid_uuid
|
|
||||||
from konova.utils.message_templates import DO_NOT_FORGET_TO_SHARE
|
|
||||||
from konova.views.base import BaseView
|
|
||||||
|
|
||||||
|
|
||||||
class BaseDetailView(LoginRequiredMixin, BaseView):
|
class AbstractDetailView(LoginRequiredMixin, View, ABC):
|
||||||
_MODEL_CLS = None
|
_TEMPLATE = None
|
||||||
|
|
||||||
class Meta:
|
|
||||||
abstract = True
|
|
||||||
|
|
||||||
|
@method_decorator(uuid_required)
|
||||||
def dispatch(self, request, *args, **kwargs):
|
def dispatch(self, request, *args, **kwargs):
|
||||||
check_id_is_valid_uuid(kwargs.get('id'))
|
|
||||||
return super().dispatch(request, *args, **kwargs)
|
return super().dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
def _user_has_shared_access(self, user, **kwargs):
|
@method_decorator(any_group_check)
|
||||||
""" Check if user has shared access to this object
|
def get(self, request: HttpRequest, id: str, *args, **kwargs) -> HttpResponse:
|
||||||
|
raise NotImplementedError()
|
||||||
Args:
|
|
||||||
user ():
|
|
||||||
**kwargs ():
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
|
|
||||||
"""
|
|
||||||
# Access to an entry's detail view is not restricted by the state of being-shared or not
|
|
||||||
return True
|
|
||||||
|
|
||||||
def _user_has_permission(self, user):
|
|
||||||
# Detail views have no restrictions
|
|
||||||
return True
|
|
||||||
|
|
||||||
def get(self, request: HttpRequest, id: str):
|
|
||||||
""" Get endpoint for detail view
|
|
||||||
|
|
||||||
Args:
|
|
||||||
request (HttpRequest): The incoming request
|
|
||||||
id (str): The record's id
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
|
|
||||||
"""
|
|
||||||
obj = self._get_object(id)
|
|
||||||
geom_form = SimpleGeomForm(instance=obj)
|
|
||||||
user = request.user
|
|
||||||
|
|
||||||
requesting_user_is_only_shared_user = obj.is_only_shared_with(user)
|
|
||||||
if requesting_user_is_only_shared_user:
|
|
||||||
messages.info(request, DO_NOT_FORGET_TO_SHARE)
|
|
||||||
|
|
||||||
obj.set_status_messages(request)
|
|
||||||
|
|
||||||
detail_context = self._get_detail_context(obj)
|
|
||||||
context = BaseContext(request, detail_context).context
|
|
||||||
context.update(
|
|
||||||
{
|
|
||||||
"obj": obj,
|
|
||||||
"geom_form": geom_form,
|
|
||||||
"is_default_member": user.in_group(DEFAULT_GROUP),
|
|
||||||
"is_zb_member": user.in_group(ZB_GROUP),
|
|
||||||
"is_ets_member": user.in_group(ETS_GROUP),
|
|
||||||
"LANIS_LINK": obj.get_LANIS_link(),
|
|
||||||
"is_entry_shared": obj.is_shared_with(user=user),
|
|
||||||
TAB_TITLE_IDENTIFIER: f"{obj.identifier} - {obj.title}"
|
|
||||||
}
|
|
||||||
)
|
|
||||||
return render(request,self._TEMPLATE, context)
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def _get_detail_context(self, obj):
|
|
||||||
""" Generate object specific detail context for view
|
|
||||||
|
|
||||||
Args:
|
|
||||||
obj (): The record
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
|
|
||||||
"""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def _get_object(self, id: str):
|
|
||||||
""" Fetch object for detail view
|
|
||||||
|
|
||||||
Args:
|
|
||||||
id (str): The record's id'
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
|
|
||||||
"""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|||||||
+8
-11
@@ -9,19 +9,21 @@ from django.contrib.auth.mixins import LoginRequiredMixin
|
|||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.http import HttpRequest
|
from django.http import HttpRequest
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
|
from django.utils.decorators import method_decorator
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from django.views import View
|
||||||
|
|
||||||
from compensation.models import EcoAccount, Compensation
|
from compensation.models import EcoAccount, Compensation
|
||||||
from intervention.models import Intervention
|
from intervention.models import Intervention
|
||||||
from konova.contexts import BaseContext
|
from konova.contexts import BaseContext
|
||||||
|
from konova.decorators import any_group_check
|
||||||
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
|
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
|
||||||
from konova.views.base import BaseView
|
|
||||||
from news.models import ServerMessage
|
from news.models import ServerMessage
|
||||||
|
|
||||||
|
|
||||||
class HomeView(LoginRequiredMixin, BaseView):
|
class HomeView(LoginRequiredMixin, View):
|
||||||
_TEMPLATE = "konova/home.html"
|
|
||||||
|
|
||||||
|
@method_decorator(any_group_check)
|
||||||
def get(self, request: HttpRequest):
|
def get(self, request: HttpRequest):
|
||||||
"""
|
"""
|
||||||
Renders the landing page
|
Renders the landing page
|
||||||
@@ -32,6 +34,7 @@ class HomeView(LoginRequiredMixin, BaseView):
|
|||||||
Returns:
|
Returns:
|
||||||
A redirect
|
A redirect
|
||||||
"""
|
"""
|
||||||
|
template = "konova/home.html"
|
||||||
user = request.user
|
user = request.user
|
||||||
user_teams = user.shared_teams
|
user_teams = user.shared_teams
|
||||||
|
|
||||||
@@ -50,6 +53,7 @@ class HomeView(LoginRequiredMixin, BaseView):
|
|||||||
# Repeat for other objects
|
# Repeat for other objects
|
||||||
comps = Compensation.objects.filter(
|
comps = Compensation.objects.filter(
|
||||||
deleted=None,
|
deleted=None,
|
||||||
|
intervention__deleted=None,
|
||||||
)
|
)
|
||||||
user_comps = comps.filter(
|
user_comps = comps.filter(
|
||||||
Q(intervention__users__in=[user]) | Q(intervention__teams__in=user_teams)
|
Q(intervention__users__in=[user]) | Q(intervention__teams__in=user_teams)
|
||||||
@@ -72,12 +76,5 @@ class HomeView(LoginRequiredMixin, BaseView):
|
|||||||
TAB_TITLE_IDENTIFIER: _("Home"),
|
TAB_TITLE_IDENTIFIER: _("Home"),
|
||||||
}
|
}
|
||||||
context = BaseContext(request, additional_context).context
|
context = BaseContext(request, additional_context).context
|
||||||
return render(request, self._TEMPLATE, context)
|
return render(request, template, context)
|
||||||
|
|
||||||
def _user_has_permission(self, user):
|
|
||||||
# No specific permission needed for home view
|
|
||||||
return True
|
|
||||||
|
|
||||||
def _user_has_shared_access(self, user, **kwargs):
|
|
||||||
# No specific constraint needed for home view
|
|
||||||
return True
|
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
"""
|
||||||
|
Author: Michel Peltriaux
|
||||||
|
Created on: 14.12.25
|
||||||
|
|
||||||
|
"""
|
||||||
|
from abc import ABC
|
||||||
|
|
||||||
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
|
from django.http import HttpRequest, JsonResponse
|
||||||
|
from django.utils.decorators import method_decorator
|
||||||
|
from django.views import View
|
||||||
|
|
||||||
|
from konova.decorators import default_group_required
|
||||||
|
from konova.utils.generators import IdentifierGenerator
|
||||||
|
|
||||||
|
|
||||||
|
class AbstractIdentifierGeneratorView(LoginRequiredMixin, View, ABC):
|
||||||
|
_MODEL = None
|
||||||
|
|
||||||
|
@method_decorator(default_group_required)
|
||||||
|
def get(self, request: HttpRequest, *args, **kwargs):
|
||||||
|
generator = IdentifierGenerator(model=self._MODEL)
|
||||||
|
identifier = generator.generate_id()
|
||||||
|
return JsonResponse(
|
||||||
|
data={
|
||||||
|
"gen_data": identifier
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
"""
|
||||||
|
Author: Michel Peltriaux
|
||||||
|
Created on: 14.12.25
|
||||||
|
|
||||||
|
"""
|
||||||
|
from abc import ABC
|
||||||
|
|
||||||
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
|
from django.http import HttpRequest, HttpResponse
|
||||||
|
from django.utils.decorators import method_decorator
|
||||||
|
from django.views import View
|
||||||
|
|
||||||
|
from konova.decorators import any_group_check
|
||||||
|
|
||||||
|
|
||||||
|
class AbstractIndexView(LoginRequiredMixin, View, ABC):
|
||||||
|
_TEMPLATE = "generic_index.html"
|
||||||
|
|
||||||
|
@method_decorator(any_group_check)
|
||||||
|
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
||||||
|
raise NotImplementedError()
|
||||||
@@ -10,8 +10,9 @@ from json import JSONDecodeError
|
|||||||
|
|
||||||
import requests
|
import requests
|
||||||
import urllib3.util
|
import urllib3.util
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.http import JsonResponse, HttpRequest, HttpResponse
|
from django.http import JsonResponse, HttpRequest, HttpResponse
|
||||||
|
from django.utils.decorators import method_decorator
|
||||||
from django.utils.http import urlencode
|
from django.utils.http import urlencode
|
||||||
from django.views import View
|
from django.views import View
|
||||||
|
|
||||||
@@ -21,13 +22,17 @@ from konova.sub_settings.lanis_settings import MAP_PROXY_HOST_WHITELIST
|
|||||||
from konova.sub_settings.proxy_settings import PROXIES, GEOPORTAL_RLP_USER, GEOPORTAL_RLP_PASSWORD
|
from konova.sub_settings.proxy_settings import PROXIES, GEOPORTAL_RLP_USER, GEOPORTAL_RLP_PASSWORD
|
||||||
|
|
||||||
|
|
||||||
class BaseClientProxyView(LoginRequiredMixin, View):
|
class BaseClientProxyView(View):
|
||||||
""" Provides proxy functionality for NETGIS map client.
|
""" Provides proxy functionality for NETGIS map client.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
class Meta:
|
class Meta:
|
||||||
abstract = True
|
abstract = True
|
||||||
|
|
||||||
|
@method_decorator(login_required)
|
||||||
|
def dispatch(self, request, *args, **kwargs):
|
||||||
|
return super().dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
def _check_with_whitelist(self, url):
|
def _check_with_whitelist(self, url):
|
||||||
parsed_url = urllib3.util.parse_url(url)
|
parsed_url = urllib3.util.parse_url(url)
|
||||||
parsed_url_host = parsed_url.host
|
parsed_url_host = parsed_url.host
|
||||||
@@ -62,6 +67,7 @@ class BaseClientProxyView(LoginRequiredMixin, View):
|
|||||||
|
|
||||||
|
|
||||||
class ClientProxyParcelSearch(BaseClientProxyView):
|
class ClientProxyParcelSearch(BaseClientProxyView):
|
||||||
|
|
||||||
def get(self, request: HttpRequest):
|
def get(self, request: HttpRequest):
|
||||||
url = request.META.get("QUERY_STRING")
|
url = request.META.get("QUERY_STRING")
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
"""
|
||||||
|
Author: Michel Peltriaux
|
||||||
|
Created on: 14.12.25
|
||||||
|
|
||||||
|
"""
|
||||||
|
from abc import ABC
|
||||||
|
|
||||||
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
|
from django.http import HttpRequest, HttpResponse
|
||||||
|
from django.urls import reverse
|
||||||
|
from django.utils.decorators import method_decorator
|
||||||
|
from django.views import View
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
from konova.decorators import default_group_required
|
||||||
|
from konova.forms.modals import RemoveModalForm
|
||||||
|
|
||||||
|
|
||||||
|
class AbstractRemoveView(LoginRequiredMixin, View, ABC):
|
||||||
|
_MODEL = None
|
||||||
|
_REDIRECT_URL = None
|
||||||
|
_FORM = RemoveModalForm
|
||||||
|
|
||||||
|
def dispatch(self, request, *args, **kwargs):
|
||||||
|
return super().dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
|
@method_decorator(default_group_required)
|
||||||
|
def __process_request(self, request: HttpRequest, id: str, *args, **kwargs) -> HttpResponse:
|
||||||
|
obj = self._MODEL.objects.get(id=id)
|
||||||
|
identifier = obj.identifier
|
||||||
|
form = self._FORM(request.POST or None, instance=obj, request=request)
|
||||||
|
return form.process_request(
|
||||||
|
request,
|
||||||
|
_("{} removed").format(identifier),
|
||||||
|
redirect_url=reverse(self._REDIRECT_URL)
|
||||||
|
)
|
||||||
|
|
||||||
|
def get(self, request: HttpRequest, id: str, *args, **kwargs) -> HttpResponse:
|
||||||
|
""" GET endpoint for removing via modal form
|
||||||
|
|
||||||
|
Due to the legacy logic of the form (which processes get and post requests directly), we simply need to pipe
|
||||||
|
the request from GET and POST endpoints directly into the same method.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request (HttpRequest): The incoming request
|
||||||
|
id (str): The uuid id as string
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
"""
|
||||||
|
return self.__process_request(request, id, *args, **kwargs)
|
||||||
|
|
||||||
|
def post(self, request: HttpRequest, id: str, *args, **kwargs) -> HttpResponse:
|
||||||
|
""" POST endpoint for removing via modal form
|
||||||
|
|
||||||
|
Due to the legacy logic of the form (which processes get and post requests directly), we simply need to pipe
|
||||||
|
the request from GET and POST endpoints directly into the same method.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request (HttpRequest): The incoming request
|
||||||
|
id (str): The uuid id as string
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
"""
|
||||||
|
return self.__process_request(request, id, *args, **kwargs)
|
||||||
+10
-92
@@ -1,106 +1,24 @@
|
|||||||
"""
|
"""
|
||||||
Author: Michel Peltriaux
|
Author: Michel Peltriaux
|
||||||
Created on: 17.10.25
|
Created on: 14.12.25
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from abc import abstractmethod
|
from abc import abstractmethod, ABC
|
||||||
from uuid import UUID
|
|
||||||
|
|
||||||
from django.http import HttpRequest, Http404
|
from django.http import HttpRequest, HttpResponse
|
||||||
from django.shortcuts import get_object_or_404, render
|
from django.utils.decorators import method_decorator
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.views import View
|
||||||
|
|
||||||
from konova.contexts import BaseContext
|
from konova.decorators import uuid_required
|
||||||
from konova.forms import SimpleGeomForm
|
|
||||||
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
|
|
||||||
from konova.views.base import BaseView
|
|
||||||
|
|
||||||
|
|
||||||
class BaseReportView(BaseView):
|
class AbstractPublicReportView(View, ABC):
|
||||||
_TEMPLATE = None
|
_TEMPLATE = None
|
||||||
_TAB_TITLE = _("Report {}")
|
|
||||||
_MODEL = None
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
abstract = True
|
|
||||||
|
|
||||||
|
@method_decorator(uuid_required)
|
||||||
def dispatch(self, request, *args, **kwargs):
|
def dispatch(self, request, *args, **kwargs):
|
||||||
# If the given id is not a uuid we act as the result was not found
|
|
||||||
try:
|
|
||||||
UUID(kwargs.get('id'))
|
|
||||||
except ValueError:
|
|
||||||
raise Http404()
|
|
||||||
return super().dispatch(request, *args, **kwargs)
|
return super().dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
def _return_unpublishable_content_response(self, request: HttpRequest, tab_title: str):
|
|
||||||
""" Handles HttpResponse return in case the object is not ready for publish
|
|
||||||
|
|
||||||
Args:
|
|
||||||
request ():
|
|
||||||
tab_title ():
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
|
|
||||||
"""
|
|
||||||
template = "report/unavailable.html"
|
|
||||||
context = {
|
|
||||||
TAB_TITLE_IDENTIFIER: tab_title,
|
|
||||||
}
|
|
||||||
context = BaseContext(request, context).context
|
|
||||||
return render(request, template, context)
|
|
||||||
|
|
||||||
def get(self, request: HttpRequest, id: str):
|
|
||||||
""" Renders the public report view
|
|
||||||
|
|
||||||
Args:
|
|
||||||
request (HttpRequest): The incoming request
|
|
||||||
id (str): The id of the intervention
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
|
|
||||||
"""
|
|
||||||
obj = get_object_or_404(self._MODEL, id=id)
|
|
||||||
tab_title = self._TAB_TITLE.format(obj.identifier)
|
|
||||||
|
|
||||||
# If object is not recorded we need to render another template without any data
|
|
||||||
if not obj.is_ready_for_publish():
|
|
||||||
return self._return_unpublishable_content_response(request, tab_title)
|
|
||||||
|
|
||||||
# First get specific report context for different types of objects due to inheritance
|
|
||||||
report_context = self._get_report_context(obj)
|
|
||||||
|
|
||||||
# Then generate and add default report context (the same for all models)
|
|
||||||
geom_form = SimpleGeomForm(instance=obj)
|
|
||||||
parcels = obj.get_underlying_parcels()
|
|
||||||
report_context.update(
|
|
||||||
{
|
|
||||||
TAB_TITLE_IDENTIFIER: tab_title,
|
|
||||||
"parcels": parcels,
|
|
||||||
"geom_form": geom_form,
|
|
||||||
"obj": obj
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
# Then generate the general context based on the report specific data
|
|
||||||
context = BaseContext(request, report_context).context
|
|
||||||
return render(request, self._TEMPLATE, context)
|
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def _get_report_context(self, obj):
|
def get(self, request: HttpRequest, id: str, *args, **kwargs) -> HttpResponse:
|
||||||
""" Returns the specific context needed for this report view
|
raise NotImplementedError()
|
||||||
|
|
||||||
Args:
|
|
||||||
obj (RecordableObjectMixin): The object for the report
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
dict: The object specific context for rendering the report
|
|
||||||
"""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def _user_has_permission(self, user):
|
|
||||||
# Reports do not need specific permissions to be callable
|
|
||||||
return True
|
|
||||||
|
|
||||||
def _user_has_shared_access(self, user, **kwargs):
|
|
||||||
# Reports do not need specific share states to be callable
|
|
||||||
return True
|
|
||||||
|
|||||||
Binary file not shown.
@@ -45,7 +45,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2025-10-19 13:56+0200\n"
|
"POT-Creation-Date: 2025-12-14 17:23+0100\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
@@ -448,54 +448,46 @@ msgid "Select the intervention for which this compensation compensates"
|
|||||||
msgstr "Wählen Sie den Eingriff, für den diese Kompensation bestimmt ist"
|
msgstr "Wählen Sie den Eingriff, für den diese Kompensation bestimmt ist"
|
||||||
|
|
||||||
#: compensation/forms/compensation.py:114
|
#: compensation/forms/compensation.py:114
|
||||||
#: compensation/views/compensation/compensation.py:161
|
#: compensation/views/compensation/compensation.py:121
|
||||||
msgid "New compensation"
|
msgid "New compensation"
|
||||||
msgstr "Neue Kompensation"
|
msgstr "Neue Kompensation"
|
||||||
|
|
||||||
#: compensation/forms/compensation.py:179
|
#: compensation/forms/compensation.py:190
|
||||||
msgid ""
|
|
||||||
"This intervention is currently recorded. You cannot add further "
|
|
||||||
"compensations as long as it is recorded."
|
|
||||||
msgstr ""
|
|
||||||
"Dieser Eingriff ist derzeit verzeichnet. "
|
|
||||||
"Sie können keine weiteren Kompensationen hinzufügen, so lange er verzeichnet ist."
|
|
||||||
|
|
||||||
#: compensation/forms/compensation.py:202
|
|
||||||
msgid "Edit compensation"
|
msgid "Edit compensation"
|
||||||
msgstr "Bearbeite Kompensation"
|
msgstr "Bearbeite Kompensation"
|
||||||
|
|
||||||
#: compensation/forms/eco_account.py:31 compensation/utils/quality.py:97
|
#: compensation/forms/eco_account.py:32 compensation/utils/quality.py:97
|
||||||
msgid "Available Surface"
|
msgid "Available Surface"
|
||||||
msgstr "Verfügbare Fläche"
|
msgstr "Verfügbare Fläche"
|
||||||
|
|
||||||
#: compensation/forms/eco_account.py:34
|
#: compensation/forms/eco_account.py:35
|
||||||
msgid "The amount that can be used for deductions"
|
msgid "The amount that can be used for deductions"
|
||||||
msgstr "Die für Abbuchungen zur Verfügung stehende Menge"
|
msgstr "Die für Abbuchungen zur Verfügung stehende Menge"
|
||||||
|
|
||||||
#: compensation/forms/eco_account.py:43
|
#: compensation/forms/eco_account.py:44
|
||||||
#: compensation/templates/compensation/detail/eco_account/view.html:67
|
#: compensation/templates/compensation/detail/eco_account/view.html:67
|
||||||
#: compensation/utils/quality.py:84
|
#: compensation/utils/quality.py:84
|
||||||
msgid "Agreement date"
|
msgid "Agreement date"
|
||||||
msgstr "Vereinbarungsdatum"
|
msgstr "Vereinbarungsdatum"
|
||||||
|
|
||||||
#: compensation/forms/eco_account.py:45
|
#: compensation/forms/eco_account.py:46
|
||||||
msgid "When did the parties agree on this?"
|
msgid "When did the parties agree on this?"
|
||||||
msgstr "Wann wurde dieses Ökokonto offiziell vereinbart?"
|
msgstr "Wann wurde dieses Ökokonto offiziell vereinbart?"
|
||||||
|
|
||||||
#: compensation/forms/eco_account.py:72
|
#: compensation/forms/eco_account.py:73
|
||||||
#: compensation/views/eco_account/eco_account.py:93
|
#: compensation/views/eco_account/eco_account.py:105
|
||||||
msgid "New Eco-Account"
|
msgid "New Eco-Account"
|
||||||
msgstr "Neues Ökokonto"
|
msgstr "Neues Ökokonto"
|
||||||
|
|
||||||
#: compensation/forms/eco_account.py:81
|
#: compensation/forms/eco_account.py:82
|
||||||
msgid "Eco-Account XY; Location ABC"
|
msgid "Eco-Account XY; Location ABC"
|
||||||
msgstr "Ökokonto XY; Flur ABC"
|
msgstr "Ökokonto XY; Flur ABC"
|
||||||
|
|
||||||
#: compensation/forms/eco_account.py:147
|
#: compensation/forms/eco_account.py:148
|
||||||
msgid "Edit Eco-Account"
|
msgid "Edit Eco-Account"
|
||||||
msgstr "Ökokonto bearbeiten"
|
msgstr "Ökokonto bearbeiten"
|
||||||
|
|
||||||
#: compensation/forms/eco_account.py:183
|
#: compensation/forms/eco_account.py:184
|
||||||
msgid ""
|
msgid ""
|
||||||
"{}m² have been deducted from this eco account so far. The given value of {} "
|
"{}m² have been deducted from this eco account so far. The given value of {} "
|
||||||
"would be too low."
|
"would be too low."
|
||||||
@@ -503,12 +495,16 @@ msgstr ""
|
|||||||
"{}n² wurden bereits von diesem Ökokonto abgebucht. Der eingegebene Wert von "
|
"{}n² wurden bereits von diesem Ökokonto abgebucht. Der eingegebene Wert von "
|
||||||
"{} wäre daher zu klein."
|
"{} wäre daher zu klein."
|
||||||
|
|
||||||
#: compensation/forms/eco_account.py:247
|
#: compensation/forms/eco_account.py:248
|
||||||
msgid "The account can not be removed, since there are still deductions."
|
msgid "The account can not be removed, since there are still deductions."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Das Ökokonto kann nicht entfernt werden, da hierzu noch Abbuchungen "
|
"Das Ökokonto kann nicht entfernt werden, da hierzu noch Abbuchungen "
|
||||||
"vorliegen."
|
"vorliegen."
|
||||||
|
|
||||||
|
#: compensation/forms/eco_account.py:257
|
||||||
|
msgid "Please contact the responsible conservation office to find a solution!"
|
||||||
|
msgstr "Kontaktieren Sie die zuständige Naturschutzbehörde um eine Lösung zu finden!"
|
||||||
|
|
||||||
#: compensation/forms/mixins.py:37
|
#: compensation/forms/mixins.py:37
|
||||||
#: compensation/templates/compensation/detail/eco_account/view.html:63
|
#: compensation/templates/compensation/detail/eco_account/view.html:63
|
||||||
#: compensation/templates/compensation/report/eco_account/report.html:20
|
#: compensation/templates/compensation/report/eco_account/report.html:20
|
||||||
@@ -1296,45 +1292,40 @@ msgstr ""
|
|||||||
msgid "Responsible data"
|
msgid "Responsible data"
|
||||||
msgstr "Daten zu den verantwortlichen Stellen"
|
msgstr "Daten zu den verantwortlichen Stellen"
|
||||||
|
|
||||||
#: compensation/views/compensation/compensation.py:35
|
#: compensation/views/compensation/compensation.py:52
|
||||||
msgid "Compensations - Overview"
|
msgid "Compensations - Overview"
|
||||||
msgstr "Kompensationen - Übersicht"
|
msgstr "Kompensationen - Übersicht"
|
||||||
|
|
||||||
#: compensation/views/compensation/compensation.py:52
|
#: compensation/views/compensation/compensation.py:167
|
||||||
#, fuzzy
|
|
||||||
#| msgid "New compensation"
|
|
||||||
msgid "New Compensation"
|
|
||||||
msgstr "Neue Kompensation"
|
|
||||||
|
|
||||||
#: compensation/views/compensation/compensation.py:208
|
|
||||||
#: konova/utils/message_templates.py:40
|
#: konova/utils/message_templates.py:40
|
||||||
msgid "Compensation {} edited"
|
msgid "Compensation {} edited"
|
||||||
msgstr "Kompensation {} bearbeitet"
|
msgstr "Kompensation {} bearbeitet"
|
||||||
|
|
||||||
#: compensation/views/compensation/compensation.py:231
|
#: compensation/views/compensation/compensation.py:190
|
||||||
#: compensation/views/eco_account/eco_account.py:159 ema/views/ema.py:59
|
#: compensation/views/eco_account/eco_account.py:168 ema/views/ema.py:173
|
||||||
#: intervention/views/intervention.py:59 intervention/views/intervention.py:179
|
#: intervention/views/intervention.py:175
|
||||||
#: konova/views/base.py:239
|
|
||||||
msgid "Edit {}"
|
msgid "Edit {}"
|
||||||
msgstr "Bearbeite {}"
|
msgstr "Bearbeite {}"
|
||||||
|
|
||||||
#: compensation/views/eco_account/eco_account.py:32
|
#: compensation/views/compensation/report.py:35
|
||||||
|
#: compensation/views/eco_account/report.py:35 ema/views/report.py:35
|
||||||
|
#: intervention/views/report.py:36
|
||||||
|
msgid "Report {}"
|
||||||
|
msgstr "Bericht {}"
|
||||||
|
|
||||||
|
#: compensation/views/eco_account/eco_account.py:49
|
||||||
msgid "Eco-account - Overview"
|
msgid "Eco-account - Overview"
|
||||||
msgstr "Ökokonten - Übersicht"
|
msgstr "Ökokonten - Übersicht"
|
||||||
|
|
||||||
#: compensation/views/eco_account/eco_account.py:70
|
#: compensation/views/eco_account/eco_account.py:82
|
||||||
msgid "Eco-Account {} added"
|
msgid "Eco-Account {} added"
|
||||||
msgstr "Ökokonto {} hinzugefügt"
|
msgstr "Ökokonto {} hinzugefügt"
|
||||||
|
|
||||||
#: compensation/views/eco_account/eco_account.py:136
|
#: compensation/views/eco_account/eco_account.py:145
|
||||||
msgid "Eco-Account {} edited"
|
msgid "Eco-Account {} edited"
|
||||||
msgstr "Ökokonto {} bearbeitet"
|
msgstr "Ökokonto {} bearbeitet"
|
||||||
|
|
||||||
#: compensation/views/eco_account/eco_account.py:260
|
#: ema/forms.py:42 ema/tests/unit/test_forms.py:27 ema/views/ema.py:107
|
||||||
msgid "Eco-account removed"
|
|
||||||
msgstr "Ökokonto entfernt"
|
|
||||||
|
|
||||||
#: ema/forms.py:42 ema/tests/unit/test_forms.py:27 ema/views/ema.py:42
|
|
||||||
msgid "New EMA"
|
msgid "New EMA"
|
||||||
msgstr "Neue EMA hinzufügen"
|
msgstr "Neue EMA hinzufügen"
|
||||||
|
|
||||||
@@ -1362,13 +1353,17 @@ msgstr ""
|
|||||||
msgid "Payment funded compensation"
|
msgid "Payment funded compensation"
|
||||||
msgstr "Ersatzzahlungsmaßnahme"
|
msgstr "Ersatzzahlungsmaßnahme"
|
||||||
|
|
||||||
#: ema/views/ema.py:26
|
#: ema/views/ema.py:52
|
||||||
msgid "EMAs - Overview"
|
msgid "EMAs - Overview"
|
||||||
msgstr "EMAs - Übersicht"
|
msgstr "EMAs - Übersicht"
|
||||||
|
|
||||||
#: ema/views/ema.py:138
|
#: ema/views/ema.py:85
|
||||||
msgid "EMA removed"
|
msgid "EMA {} added"
|
||||||
msgstr "EMA entfernt"
|
msgstr "EMA {} hinzugefügt"
|
||||||
|
|
||||||
|
#: ema/views/ema.py:150
|
||||||
|
msgid "EMA {} edited"
|
||||||
|
msgstr "EMA {} bearbeitet"
|
||||||
|
|
||||||
#: intervention/forms/intervention.py:49
|
#: intervention/forms/intervention.py:49
|
||||||
msgid "Construction XY; Location ABC"
|
msgid "Construction XY; Location ABC"
|
||||||
@@ -1430,7 +1425,7 @@ msgstr "Datum Bestandskraft bzw. Rechtskraft"
|
|||||||
|
|
||||||
#: intervention/forms/intervention.py:216
|
#: intervention/forms/intervention.py:216
|
||||||
#: intervention/tests/unit/test_forms.py:36
|
#: intervention/tests/unit/test_forms.py:36
|
||||||
#: intervention/views/intervention.py:51
|
#: intervention/views/intervention.py:109
|
||||||
msgid "New intervention"
|
msgid "New intervention"
|
||||||
msgstr "Neuer Eingriff"
|
msgstr "Neuer Eingriff"
|
||||||
|
|
||||||
@@ -1666,18 +1661,18 @@ msgstr ""
|
|||||||
msgid "Check performed"
|
msgid "Check performed"
|
||||||
msgstr "Prüfung durchgeführt"
|
msgstr "Prüfung durchgeführt"
|
||||||
|
|
||||||
#: intervention/views/intervention.py:33
|
#: intervention/views/intervention.py:53
|
||||||
msgid "Interventions - Overview"
|
msgid "Interventions - Overview"
|
||||||
msgstr "Eingriffe - Übersicht"
|
msgstr "Eingriffe - Übersicht"
|
||||||
|
|
||||||
#: intervention/views/intervention.py:154
|
#: intervention/views/intervention.py:86
|
||||||
|
msgid "Intervention {} added"
|
||||||
|
msgstr "Eingriff {} hinzugefügt"
|
||||||
|
|
||||||
|
#: intervention/views/intervention.py:150
|
||||||
msgid "Intervention {} edited"
|
msgid "Intervention {} edited"
|
||||||
msgstr "Eingriff {} bearbeitet"
|
msgstr "Eingriff {} bearbeitet"
|
||||||
|
|
||||||
#: intervention/views/intervention.py:204
|
|
||||||
msgid "{} removed"
|
|
||||||
msgstr "{} entfernt"
|
|
||||||
|
|
||||||
#: konova/decorators.py:32
|
#: konova/decorators.py:32
|
||||||
msgid "You need to be staff to perform this action!"
|
msgid "You need to be staff to perform this action!"
|
||||||
msgstr "Hierfür müssen Sie Mitarbeiter sein!"
|
msgstr "Hierfür müssen Sie Mitarbeiter sein!"
|
||||||
@@ -1686,7 +1681,7 @@ msgstr "Hierfür müssen Sie Mitarbeiter sein!"
|
|||||||
msgid "You need to be administrator to perform this action!"
|
msgid "You need to be administrator to perform this action!"
|
||||||
msgstr "Hierfür müssen Sie Administrator sein!"
|
msgstr "Hierfür müssen Sie Administrator sein!"
|
||||||
|
|
||||||
#: konova/decorators.py:65 konova/utils/general.py:40
|
#: konova/decorators.py:65
|
||||||
msgid ""
|
msgid ""
|
||||||
"+++ Attention: You are not part of any group. You won't be able to create, "
|
"+++ Attention: You are not part of any group. You won't be able to create, "
|
||||||
"edit or do anything. Please contact an administrator. +++"
|
"edit or do anything. Please contact an administrator. +++"
|
||||||
@@ -1798,7 +1793,7 @@ msgstr "Sucht nach Einträgen, an denen diese Person gearbeitet hat"
|
|||||||
msgid "Save"
|
msgid "Save"
|
||||||
msgstr "Speichern"
|
msgstr "Speichern"
|
||||||
|
|
||||||
#: konova/forms/base_form.py:74
|
#: konova/forms/base_form.py:72
|
||||||
msgid "Not editable"
|
msgid "Not editable"
|
||||||
msgstr "Nicht editierbar"
|
msgstr "Nicht editierbar"
|
||||||
|
|
||||||
@@ -1807,7 +1802,7 @@ msgstr "Nicht editierbar"
|
|||||||
msgid "Geometry"
|
msgid "Geometry"
|
||||||
msgstr "Geometrie"
|
msgstr "Geometrie"
|
||||||
|
|
||||||
#: konova/forms/geometry_form.py:101
|
#: konova/forms/geometry_form.py:105
|
||||||
msgid "Only surfaces allowed. Points or lines must be buffered."
|
msgid "Only surfaces allowed. Points or lines must be buffered."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Nur Flächen erlaubt. Punkte oder Linien müssen zu Flächen gepuffert werden."
|
"Nur Flächen erlaubt. Punkte oder Linien müssen zu Flächen gepuffert werden."
|
||||||
@@ -2308,15 +2303,7 @@ msgstr ""
|
|||||||
"Dieses Datum ist unrealistisch. Geben Sie bitte das korrekte Datum ein "
|
"Dieses Datum ist unrealistisch. Geben Sie bitte das korrekte Datum ein "
|
||||||
"(>1950)."
|
"(>1950)."
|
||||||
|
|
||||||
#: konova/views/base.py:209
|
#: konova/views/home.py:75 templates/navbars/navbar.html:16
|
||||||
msgid "{} added"
|
|
||||||
msgstr "{} hinzugefügt"
|
|
||||||
|
|
||||||
#: konova/views/base.py:281
|
|
||||||
msgid "{} edited"
|
|
||||||
msgstr "{} bearbeitet"
|
|
||||||
|
|
||||||
#: konova/views/home.py:72 templates/navbars/navbar.html:16
|
|
||||||
msgid "Home"
|
msgid "Home"
|
||||||
msgstr "Home"
|
msgstr "Home"
|
||||||
|
|
||||||
@@ -2336,9 +2323,9 @@ msgstr "{} verzeichnet"
|
|||||||
msgid "Errors found:"
|
msgid "Errors found:"
|
||||||
msgstr "Fehler gefunden:"
|
msgstr "Fehler gefunden:"
|
||||||
|
|
||||||
#: konova/views/report.py:21
|
#: konova/views/remove.py:35
|
||||||
msgid "Report {}"
|
msgid "{} removed"
|
||||||
msgstr "Bericht {}"
|
msgstr "{} entfernt"
|
||||||
|
|
||||||
#: konova/views/resubmission.py:39
|
#: konova/views/resubmission.py:39
|
||||||
msgid "Resubmission set"
|
msgid "Resubmission set"
|
||||||
@@ -3066,7 +3053,7 @@ msgid "Manage teams"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: user/templates/user/index.html:53 user/templates/user/team/index.html:19
|
#: user/templates/user/index.html:53 user/templates/user/team/index.html:19
|
||||||
#: user/views/views.py:134
|
#: user/views/views.py:135
|
||||||
msgid "Teams"
|
msgid "Teams"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -3126,40 +3113,34 @@ msgstr "Läuft ab am"
|
|||||||
msgid "User API token"
|
msgid "User API token"
|
||||||
msgstr "API Nutzer Token"
|
msgstr "API Nutzer Token"
|
||||||
|
|
||||||
#: user/views/views.py:31
|
#: user/views/views.py:33
|
||||||
msgid "User settings"
|
msgid "User settings"
|
||||||
msgstr "Einstellungen"
|
msgstr "Einstellungen"
|
||||||
|
|
||||||
#: user/views/views.py:44
|
#: user/views/views.py:59
|
||||||
msgid "User notifications"
|
|
||||||
msgstr "Benachrichtigungen"
|
|
||||||
|
|
||||||
#: user/views/views.py:64
|
|
||||||
msgid "Notifications edited"
|
msgid "Notifications edited"
|
||||||
msgstr "Benachrichtigungen bearbeitet"
|
msgstr "Benachrichtigungen bearbeitet"
|
||||||
|
|
||||||
#: user/views/views.py:152
|
#: user/views/views.py:71
|
||||||
|
msgid "User notifications"
|
||||||
|
msgstr "Benachrichtigungen"
|
||||||
|
|
||||||
|
#: user/views/views.py:147
|
||||||
msgid "New team added"
|
msgid "New team added"
|
||||||
msgstr "Neues Team hinzugefügt"
|
msgstr "Neues Team hinzugefügt"
|
||||||
|
|
||||||
#: user/views/views.py:167
|
#: user/views/views.py:162
|
||||||
msgid "Team edited"
|
msgid "Team edited"
|
||||||
msgstr "Team bearbeitet"
|
msgstr "Team bearbeitet"
|
||||||
|
|
||||||
#: user/views/views.py:182
|
#: user/views/views.py:177
|
||||||
msgid "Team removed"
|
msgid "Team removed"
|
||||||
msgstr "Team gelöscht"
|
msgstr "Team gelöscht"
|
||||||
|
|
||||||
#: user/views/views.py:197
|
#: user/views/views.py:192
|
||||||
msgid "You are not a member of this team"
|
msgid "You are not a member of this team"
|
||||||
msgstr "Sie sind kein Mitglied dieses Teams"
|
msgstr "Sie sind kein Mitglied dieses Teams"
|
||||||
|
|
||||||
#: user/views/views.py:204
|
#: user/views/views.py:199
|
||||||
msgid "Left Team"
|
msgid "Left Team"
|
||||||
msgstr "Team verlassen"
|
msgstr "Team verlassen"
|
||||||
|
|
||||||
#~ msgid "EMA {} added"
|
|
||||||
#~ msgstr "EMA {} hinzugefügt"
|
|
||||||
|
|
||||||
#~ msgid "EMA {} edited"
|
|
||||||
#~ msgstr "EMA {} bearbeitet"
|
|
||||||
|
|||||||
+52
-50
@@ -1,63 +1,65 @@
|
|||||||
amqp==5.3.1
|
amqp==5.3.1
|
||||||
asgiref==3.8.1
|
asgiref==3.11.1
|
||||||
async-timeout==5.0.1
|
async-timeout==5.0.1
|
||||||
beautifulsoup4==4.13.0b2
|
beautifulsoup4==4.14.3
|
||||||
billiard==4.2.1
|
billiard==4.2.4
|
||||||
cached-property==2.0.1
|
cached-property==2.0.1
|
||||||
celery==5.4.0
|
celery==5.6.3
|
||||||
certifi==2024.12.14
|
certifi==2026.4.22
|
||||||
cffi==1.17.1
|
cffi==2.0.0
|
||||||
chardet==5.2.0
|
chardet==7.4.3
|
||||||
charset-normalizer==3.4.0
|
charset-normalizer==3.4.7
|
||||||
click==8.1.8
|
click==8.3.3
|
||||||
click-didyoumean==0.3.1
|
click-didyoumean==0.3.1
|
||||||
click-plugins==1.1.1
|
click-plugins==1.1.1.2
|
||||||
click-repl==0.3.0
|
click-repl==0.3.0
|
||||||
coverage==7.6.9
|
coverage==7.13.5
|
||||||
cryptography==44.0.0
|
cryptography==48.0.0
|
||||||
Deprecated==1.2.15
|
Deprecated==1.3.1
|
||||||
Django==5.1.4
|
Django==6.0.5
|
||||||
django-autocomplete-light==3.11.0
|
django-autocomplete-light==4.0.0
|
||||||
django-bootstrap-modal-forms==3.0.5
|
django-bootstrap-modal-forms==3.0.5
|
||||||
django-bootstrap4==24.4
|
django-bootstrap4==26.1
|
||||||
django-environ==0.11.2
|
django-environ==0.13.0
|
||||||
django-filter==24.3
|
django-filter==25.2
|
||||||
django-fontawesome-5==1.0.18
|
django-fontawesome-5==1.0.18
|
||||||
django-oauth-toolkit==3.0.1
|
django-oauth-toolkit==3.2.0
|
||||||
django-tables2==2.7.1
|
django-tables2==3.0.0
|
||||||
et_xmlfile==2.0.0
|
et_xmlfile==2.0.0
|
||||||
gunicorn==23.0.0
|
gunicorn==26.0.0
|
||||||
idna==3.10
|
idna==3.13
|
||||||
importlib_metadata==8.5.0
|
importlib_metadata==9.0.0
|
||||||
jwcrypto==1.5.6
|
itsdangerous==2.2.0
|
||||||
kombu==5.4.0rc1
|
jwcrypto==1.5.7
|
||||||
oauthlib==3.2.2
|
kombu==5.6.2
|
||||||
|
oauthlib==3.3.1
|
||||||
openpyxl==3.2.0b1
|
openpyxl==3.2.0b1
|
||||||
packaging==24.2
|
packaging==26.2
|
||||||
pika==1.3.2
|
pika==1.4.0
|
||||||
pillow==11.0.0
|
pillow==12.2.0
|
||||||
prompt_toolkit==3.0.48
|
prompt_toolkit==3.0.52
|
||||||
psycopg==3.2.3
|
psycopg==3.3.4
|
||||||
psycopg-binary==3.2.3
|
psycopg-binary==3.3.4
|
||||||
pycparser==2.22
|
pycparser==3.0
|
||||||
pyparsing==3.2.0
|
pyparsing==3.3.2
|
||||||
pypng==0.20220715.0
|
pypng==0.20220715.0
|
||||||
pyproj==3.7.0
|
pyproj==3.7.2
|
||||||
python-dateutil==2.9.0.post0
|
python-dateutil==2.9.0.post0
|
||||||
pytz==2024.2
|
pytz==2026.2
|
||||||
PyYAML==6.0.2
|
PyYAML==6.0.3
|
||||||
qrcode==7.3.1
|
qrcode==8.2
|
||||||
redis==5.1.0b6
|
redis==7.4.0
|
||||||
requests==2.32.3
|
requests==2.33.1
|
||||||
six==1.16.0
|
six==1.17.0
|
||||||
soupsieve==2.5
|
soupsieve==2.8.3
|
||||||
sqlparse==0.5.1
|
sqlparse==0.5.5
|
||||||
typing_extensions==4.12.2
|
typing_extensions==4.15.0
|
||||||
tzdata==2024.2
|
tzdata==2026.2
|
||||||
urllib3==2.3.0
|
tzlocal==5.3.1
|
||||||
|
urllib3==2.7.0
|
||||||
vine==5.1.0
|
vine==5.1.0
|
||||||
wcwidth==0.2.13
|
wcwidth==0.7.0
|
||||||
webservices==0.7
|
webservices==0.7
|
||||||
wrapt==1.16.0
|
wrapt==2.1.2
|
||||||
xmltodict==0.14.2
|
xmltodict==1.0.4
|
||||||
zipp==3.21.0
|
zipp==3.23.1
|
||||||
|
|||||||
+1
-1
@@ -5,7 +5,7 @@
|
|||||||
<div class="jumbotron">
|
<div class="jumbotron">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-auto">
|
<div class="col-auto">
|
||||||
<img src="{% static 'images/error_imgs/croc_technician_400.png' %}" style="max-width: 150px">
|
<img src="{% static 'images/error_imgs/croc_technician_400_sm.png' %}" style="max-width: 150px">
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-12 col-md-9 col-lg-9 col-xl-10">
|
<div class="col-sm-12 col-md-9 col-lg-9 col-xl-10">
|
||||||
<h1 class="display-4">{% fa5_icon 'question-circle' %}400</h1>
|
<h1 class="display-4">{% fa5_icon 'question-circle' %}400</h1>
|
||||||
|
|||||||
+1
-1
@@ -5,7 +5,7 @@
|
|||||||
<div class="jumbotron">
|
<div class="jumbotron">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-auto">
|
<div class="col-auto">
|
||||||
<img src="{% static 'images/error_imgs/croc_technician_500.png' %}" style="max-width: 150px">
|
<img src="{% static 'images/error_imgs/croc_technician_500_sm.png' %}" style="max-width: 150px">
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-12 col-md-9 col-lg-9 col-xl-10">
|
<div class="col-sm-12 col-md-9 col-lg-9 col-xl-10">
|
||||||
<h1 class="display-4">{% fa5_icon 'fire-alt' %} 500</h1>
|
<h1 class="display-4">{% fa5_icon 'fire-alt' %} 500</h1>
|
||||||
|
|||||||
@@ -70,7 +70,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="dropdown-menu dropdown-menu-right">
|
<div class="dropdown-menu dropdown-menu-right">
|
||||||
{% for rpp_option in table.results_per_page_choices %}
|
{% for rpp_option in table.results_per_page_choices %}
|
||||||
<a class="dropdown-item {% if table.results_per_page_chosen == rpp_option %}selected{% endif %}" href="{% querystring table.results_per_page_parameter=rpp_option %}">
|
<a class="dropdown-item {% if table.results_per_page_chosen == rpp_option %}selected{% endif %}" href="{% querystring %}&{{ table.results_per_page_parameter}}={{rpp_option }}">
|
||||||
{{ rpp_option }}
|
{{ rpp_option }}
|
||||||
</a>
|
</a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
@@ -58,7 +58,7 @@
|
|||||||
[
|
[
|
||||||
{ "id": "webatlas_farbe", "folder": "bg", "type": "WMS", "order": -1, "title": "WebatlasRP farbig", "attribution": "LVermGeo", "url": "https://maps.service24.rlp.de/gisserver/services/RP/RP_WebAtlasRP/MapServer/WmsServer?", "name": "RP_WebAtlasRP", "active": true},
|
{ "id": "webatlas_farbe", "folder": "bg", "type": "WMS", "order": -1, "title": "WebatlasRP farbig", "attribution": "LVermGeo", "url": "https://maps.service24.rlp.de/gisserver/services/RP/RP_WebAtlasRP/MapServer/WmsServer?", "name": "RP_WebAtlasRP", "active": true},
|
||||||
{ "id": "webatlas_grau", "folder": "bg", "type": "WMS", "order": -1, "title": "WebatlasRP grau", "attribution": "LVermGeo", "url": "https://maps.service24.rlp.de/gisserver/services/RP/RP_ETRS_Gt/MapServer/WmsServer?", "name": "0", "active": false },
|
{ "id": "webatlas_grau", "folder": "bg", "type": "WMS", "order": -1, "title": "WebatlasRP grau", "attribution": "LVermGeo", "url": "https://maps.service24.rlp.de/gisserver/services/RP/RP_ETRS_Gt/MapServer/WmsServer?", "name": "0", "active": false },
|
||||||
{ "id": "luftbilder", "folder": "bg", "type": "WMS", "order": -1, "title": "Luftbilder", "attribution": "LVermGeo", "url": "https://geo4.service24.rlp.de/wms/dop_basis.fcgi?", "name": "rp_dop", "active": false },
|
{ "id": "luftbilder", "folder": "bg", "type": "WMS", "order": -1, "title": "Luftbilder", "attribution": "LVermGeo", "url": "https://geo4.service24.rlp.de/wms/rp_dop20.fcgi?", "name": "rp_dop20", "active": false },
|
||||||
{ "id": "basemap_farbe", "folder": "bg", "type": "WMS", "order": -1, "title": "BasemapDE farbig", "attribution": "BKG", "url": "https://sgx.geodatenzentrum.de/wms_basemapde?", "name": "de_basemapde_web_raster_farbe", "active": false },
|
{ "id": "basemap_farbe", "folder": "bg", "type": "WMS", "order": -1, "title": "BasemapDE farbig", "attribution": "BKG", "url": "https://sgx.geodatenzentrum.de/wms_basemapde?", "name": "de_basemapde_web_raster_farbe", "active": false },
|
||||||
{ "id": "basemap_grau", "folder": "bg", "type": "WMS", "order": -1, "title": "BasemapDE grau", "attribution": "BKG", "url": "https://sgx.geodatenzentrum.de/wms_basemapde?", "name": "de_basemapde_web_raster_grau", "active": false },
|
{ "id": "basemap_grau", "folder": "bg", "type": "WMS", "order": -1, "title": "BasemapDE grau", "attribution": "BKG", "url": "https://sgx.geodatenzentrum.de/wms_basemapde?", "name": "de_basemapde_web_raster_grau", "active": false },
|
||||||
{ "id": "dtk_farbe", "folder": "bg", "type": "WMS", "order": -1, "title": "DTK5 farbig", "attribution": "LVermGeo", "url": "https://geo4.service24.rlp.de/wms/dtk5_rp.fcgi?", "name": "rp_dtk5", "active": false },
|
{ "id": "dtk_farbe", "folder": "bg", "type": "WMS", "order": -1, "title": "DTK5 farbig", "attribution": "LVermGeo", "url": "https://geo4.service24.rlp.de/wms/dtk5_rp.fcgi?", "name": "rp_dtk5", "active": false },
|
||||||
@@ -188,6 +188,7 @@
|
|||||||
{
|
{
|
||||||
"title": "Ebene hinzufügen",
|
"title": "Ebene hinzufügen",
|
||||||
"preview": true,
|
"preview": true,
|
||||||
|
"editable": true,
|
||||||
"wms_options": [ "https://sgx.geodatenzentrum.de/wms_topplus_open" ],
|
"wms_options": [ "https://sgx.geodatenzentrum.de/wms_topplus_open" ],
|
||||||
"wfs_options": [ "http://213.139.159.34:80/geoserver/uesg/wfs" ],
|
"wfs_options": [ "http://213.139.159.34:80/geoserver/uesg/wfs" ],
|
||||||
"wfs_proxy": "/client/proxy?",
|
"wfs_proxy": "/client/proxy?",
|
||||||
|
|||||||
+1
-1
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user