This commit is contained in:
mipel
2021-07-01 13:36:07 +02:00
commit a5e8bcfa8c
91 changed files with 22395 additions and 0 deletions

0
intervention/__init__.py Normal file
View File

18
intervention/admin.py Normal file
View File

@@ -0,0 +1,18 @@
from django.contrib import admin
from intervention.models import Intervention
class InterventionAdmin(admin.ModelAdmin):
list_display = [
"id",
"title",
"type",
"handler",
"created_on",
"is_active",
"is_deleted",
]
admin.site.register(Intervention, InterventionAdmin)

5
intervention/apps.py Normal file
View File

@@ -0,0 +1,5 @@
from django.apps import AppConfig
class InterventionConfig(AppConfig):
name = 'intervention'

220
intervention/forms.py Normal file
View File

@@ -0,0 +1,220 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: michel.peltriaux@sgdnord.rlp.de
Created on: 02.12.20
"""
from dal import autocomplete
from django import forms
from django.contrib.auth.models import User
from django.contrib.gis import forms as gis_forms
from django.contrib.gis.geos import Polygon
from django.db import transaction
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from intervention.models import Intervention
from konova.forms import BaseForm
from konova.models import Document
from konova.settings import DEFAULT_LAT, DEFAULT_LON, DEFAULT_ZOOM
from organisation.models import Organisation
class NewInterventionForm(BaseForm):
identifier = forms.CharField(
label=_("Identifier"),
label_suffix="",
max_length=255,
help_text=_("Generated automatically if none was given"),
required=False,
)
title = forms.CharField(
label=_("Title"),
label_suffix="",
max_length=255,
)
type = forms.CharField(
label=_("Type"),
label_suffix="",
max_length=255,
help_text=_("Which intervention type is this"),
)
law = forms.CharField(
label=_("Law"),
label_suffix="",
max_length=255,
help_text=_("Based on which law"),
)
handler = forms.CharField(
label=_("Intervention handler"),
label_suffix="",
max_length=255,
help_text=_("Who performs the intervention"),
)
data_provider = forms.ModelChoiceField(
label=_("Data provider"),
label_suffix="",
help_text=_("Who provides the data for the intervention"),
queryset=Organisation.objects.all(),
widget=autocomplete.ModelSelect2(
url="other-orgs-autocomplete",
attrs={
"data-placeholder": _("Organization"),
"data-minimum-input-length": 3,
}
),
)
data_provider_detail = forms.CharField(
label=_("Data provider details"),
label_suffix="",
max_length=255,
help_text=_("Further details"),
required=False,
)
geometry = gis_forms.MultiPolygonField(
widget=gis_forms.OSMWidget(
attrs={
"default_lat": DEFAULT_LAT,
"default_lon": DEFAULT_LON,
"default_zoom": DEFAULT_ZOOM,
'map_width': 800,
'map_height': 500
},
),
label=_("Map"),
label_suffix="",
help_text=_("Where does the intervention take place")
)
documents = forms.FileField(
widget=forms.ClearableFileInput(
attrs={
"multiple": True,
}
),
label=_("Files"),
label_suffix="",
required=False,
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.form_title = _("New intervention")
self.action_url = reverse("intervention:new")
self.cancel_redirect = reverse("intervention:index")
def save(self, user: User):
with transaction.atomic():
identifier = self.cleaned_data.get("identifier", None)
title = self.cleaned_data.get("title", None)
_type = self.cleaned_data.get("type", None)
law = self.cleaned_data.get("law", None)
handler = self.cleaned_data.get("handler", None)
data_provider = self.cleaned_data.get("data_provider", None)
data_provider_detail = self.cleaned_data.get("data_provider_detail", None)
geometry = self.cleaned_data.get("geometry", Polygon())
documents = self.cleaned_data.get("documents", []) or []
intervention = Intervention(
identifier=identifier,
title=title,
type=_type,
law=law,
handler=handler,
data_provider=data_provider,
data_provider_detail=data_provider_detail,
geometry=geometry,
created_by=user,
)
intervention.save()
for doc in documents:
doc_obj = Document()
doc_obj.document = doc
# ToDo Add functionality for other attributes
doc_obj.save()
intervention.documents.add(doc_obj)
return intervention
class EditInterventionForm(NewInterventionForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.instance is not None:
self.action_url = reverse("intervention:edit", args=(self.instance.id,))
self.cancel_redirect = reverse("intervention:index")
self.form_title = _("Edit intervention")
self.form_caption = ""
# Initialize form data
form_data = {
"identifier": self.instance.identifier,
"title": self.instance.title,
"type": self.instance.type,
"law": self.instance.law,
"handler": self.instance.handler,
"data_provider": self.instance.data_provider,
"data_provider_detail": self.instance.data_provider_detail,
"geometry": self.instance.geometry,
"documents": self.instance.documents.all(),
}
disabled_fields = [
"identifier",
]
self.load_initial_data(
form_data,
disabled_fields,
)
def save(self, user: User):
""" Overwrite instance with new form data
Args:
user ():
Returns:
"""
with transaction.atomic():
identifier = self.cleaned_data.get("identifier", None)
title = self.cleaned_data.get("title", None)
_type = self.cleaned_data.get("type", None)
law = self.cleaned_data.get("law", None)
handler = self.cleaned_data.get("handler", None)
data_provider = self.cleaned_data.get("data_provider", None)
data_provider_detail = self.cleaned_data.get("data_provider_detail", None)
geometry = self.cleaned_data.get("geometry", Polygon())
documents = self.cleaned_data.get("documents", []) or []
self.instance.identifier = identifier
self.instance.title = title
self.instance.type = _type
self.instance.law = law
self.instance.handler = handler
self.instance.data_provider = data_provider
self.instance.data_provider_detail = data_provider_detail
self.instance.geometry = geometry
self.instance.save()
for doc in documents:
doc_obj = Document()
doc_obj.document = doc
# ToDo Add functionality for other attributes
doc_obj.save()
self.instance.documents.add(doc_obj)
return self.instance
class OpenInterventionForm(EditInterventionForm):
"""
This form is not intended to be used as data-input form. It's used to simplify the rendering of intervention:open
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Resize map
self.fields["geometry"].widget.attrs["map_width"] = 500
self.fields["geometry"].widget.attrs["map_height"] = 300
# Disable all form fields
for field in self.fields:
self.disable_form_field(field)

91
intervention/models.py Normal file
View File

@@ -0,0 +1,91 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: michel.peltriaux@sgdnord.rlp.de
Created on: 17.11.20
"""
from django.contrib.auth.models import User
from django.contrib.gis.db import models
from django.utils.timezone import now
from intervention.settings import INTERVENTION_IDENTIFIER_LENGTH, INTERVENTION_IDENTIFIER_TEMPLATE
from konova.models import BaseObject
from konova.utils.generators import generate_random_string
from organisation.enums import RoleTypeEnum
from process.models import Process
class Intervention(BaseObject):
"""
Interventions are e.g. construction sites where nature used to be.
A process consists of exactly one intervention and one or more compensation
"""
type = models.CharField(max_length=500, null=True, blank=True)
law = models.CharField(max_length=500, null=True, blank=True)
handler = models.CharField(max_length=500, null=True, blank=True)
data_provider = models.ForeignKey("organisation.Organisation", on_delete=models.SET_NULL, null=True, blank=True)
data_provider_detail = models.CharField(max_length=500, null=True, blank=True)
geometry = models.MultiPolygonField(null=True, blank=True)
process = models.OneToOneField("process.Process", on_delete=models.CASCADE, null=True, blank=True, related_name="intervention")
documents = models.ManyToManyField("konova.Document", blank=True)
def __str__(self):
return "{} by {}".format(self.type, self.handler)
def delete(self, *args, **kwargs):
if self.process is not None:
self.process.delete()
super().delete(*args, **kwargs)
@staticmethod
def __generate_new_identifier() -> str:
""" Generates a new identifier for the intervention object
Returns:
str
"""
curr_month = str(now().month)
curr_year = str(now().year)
rand_str = generate_random_string(
length=INTERVENTION_IDENTIFIER_LENGTH,
only_numbers=True,
)
_str = "{}{}{}".format(curr_month, curr_year, rand_str)
return INTERVENTION_IDENTIFIER_TEMPLATE.format(_str)
def save(self, *args, **kwargs):
if self.identifier is None or len(self.identifier) == 0:
# Create new identifier
new_id = self.__generate_new_identifier()
while Intervention.objects.filter(identifier=new_id).exists():
new_id = self.__generate_new_identifier()
self.identifier = new_id
super().save(*args, **kwargs)
@staticmethod
def get_role_objects(user: User, order_by: str = "-created_on") -> list:
""" Returns objects depending on the currently selected role of the user
* REGISTRATIONOFFICE
* User can see the processes where registration_office is set to the organisation of the currently selected role
* User can see self-created processes
* LICENSINGOFFICE
* same
* DATAPROVIDER
* User can see only self-created processes
Args:
user (User): The performing user
order_by (str): Order by which Process attribute
Returns:
"""
role = user.current_role
if role is None:
return Intervention.objects.none()
processes = Process.get_role_objects(user, order_by)
interventions = [process.intervention for process in processes]
return interventions

9
intervention/settings.py Normal file
View File

@@ -0,0 +1,9 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: michel.peltriaux@sgdnord.rlp.de
Created on: 30.11.20
"""
INTERVENTION_IDENTIFIER_LENGTH = 10
INTERVENTION_IDENTIFIER_TEMPLATE = "EIV-{}"

87
intervention/tables.py Normal file
View File

@@ -0,0 +1,87 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: michel.peltriaux@sgdnord.rlp.de
Created on: 01.12.20
"""
from django.urls import reverse
from django.utils.html import format_html
from django.utils.translation import gettext_lazy as _
from intervention.models import Intervention
from konova.utils.tables import BaseTable
import django_tables2 as tables
class InterventionTable(BaseTable):
id = tables.Column(
verbose_name=_("Identifier"),
orderable=True,
accessor="identifier",
)
t = tables.Column(
verbose_name=_("Title"),
orderable=True,
accessor="title",
)
p = tables.Column(
verbose_name=_("Process"),
orderable=True,
accessor="process",
)
d = tables.Column(
verbose_name=_("Created on"),
orderable=True,
accessor="created_on",
)
ac = tables.Column(
verbose_name=_("Actions"),
orderable=False,
empty_values=[],
attrs={"td": {"class": "action-col"}}
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.title = _("Interventions")
self.add_new_url = reverse("intervention:new")
def render_id(self, value, record: Intervention):
""" Renders the id column for an intervention
Args:
value (str): The identifier value
record (Intervention): The intervention record
Returns:
"""
html = ""
html += self.render_link(
tooltip=_("Open {}").format(_("Intervention")),
href=reverse("intervention:open", args=(record.id,)),
txt=value,
new_tab=False,
)
return format_html(html)
def render_ac(self, value, record):
"""
Renders possible actions for this record, such as delete.
"""
intervention = _("Intervention")
html = ""
html += self.render_open_btn(
_("Open {}").format(intervention),
reverse("intervention:open", args=(record.id,))
)
html += self.render_edit_btn(
_("Edit {}").format(intervention),
reverse("intervention:edit", args=(record.id,)),
)
html += self.render_delete_btn(
_("Delete {}").format(intervention),
reverse("intervention:remove", args=(record.id,)),
)
return format_html(html)

View File

@@ -0,0 +1,47 @@
{% extends 'base.html' %}
{% load i18n static %}
{% block body %}
<div class="rows">
<div class="columns">
<div class="large-10 column">
<h3>{% trans 'Intervention' %}: {{ intervention.title }}</h3>
</div>
<div class="large-2 column">
<a href="{% url 'intervention:edit' intervention.id %}">
<button class="button small" role="button" value="{% trans 'Edit' %}"><i class='fas fa-edit'></i> {% trans 'Edit' %}</button>
</a>
</div>
</div>
<hr>
{% comment %}
form.media needs to be loaded to ensure the openlayers client will be loaded properly
{% endcomment %}
{{ form.media }}
<div class="columns">
<div class="small-6 columns">
<table>
<tbody>
{% for field in form %}
{% if field != form.geometry %}
<tr title="{{ field.help_text }}" class="{% if field.errors %}error{% endif %}">
<th scope="row" class="small-3">
<div>{{ field.label }}</div>
<small>{{ field.help_text }}</small>
</th>
<td class="small-12">
{{ field }}
</td>
</tr>
{% endif %}
{% endfor %}
</tbody>
</table>
</div>
<div class="small-6 columns">
{{ form.geometry }}
</div>
</div>
</div>
{% endblock %}

3
intervention/tests.py Normal file
View File

@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

19
intervention/urls.py Normal file
View File

@@ -0,0 +1,19 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: michel.peltriaux@sgdnord.rlp.de
Created on: 30.11.20
"""
from django.urls import path
from intervention.views import index_view, new_view, open_view, edit_view, remove_view
app_name = "intervention"
urlpatterns = [
path("", index_view, name="index"),
path('new/', new_view, name='new'),
path('open/<id>', open_view, name='open'),
path('edit/<id>', edit_view, name='edit'),
path('remove/<id>', remove_view, name='remove'),
]

171
intervention/views.py Normal file
View File

@@ -0,0 +1,171 @@
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.utils.translation import gettext_lazy as _
from django.http import HttpRequest
from django.shortcuts import render, get_object_or_404, redirect
from django.urls import reverse
from intervention.forms import NewInterventionForm, EditInterventionForm, OpenInterventionForm
from intervention.models import Intervention
from intervention.tables import InterventionTable
from konova.contexts import BaseContext
from konova.decorators import *
from konova.forms import RemoveForm
from process.models import Process
@login_required
@resolve_user_role
def index_view(request: HttpRequest):
"""
Renders the index view for process
Args:
request (HttpRequest): The incoming request
Returns:
A rendered view
"""
template = "generic_index.html"
user = request.user
interventions = Intervention.get_role_objects(user)
table = InterventionTable(
request=request,
queryset=interventions
)
context = {
"table": table,
}
context = BaseContext(request, context).context
return render(request, template, context)
@login_required
def new_view(request: HttpRequest):
"""
Renders a view for a new intervention creation
Args:
request (HttpRequest): The incoming request
Returns:
"""
template = "konova/form.html"
form = NewInterventionForm(request.POST or None)
if request.method == "POST":
if form.is_valid():
intervention = form.save(request.user)
if intervention.process is None:
# An intervention can not be created without a process -> automatically create a new process
process = Process.create_from_intervention(intervention)
messages.info(request, _("Interventions must be part of a process. Please fill in the missing data for the process"))
return redirect("process:edit", id=process.id)
else:
messages.success(request, _("Intervention {} added").format(intervention.title))
return redirect("intervention:index")
else:
messages.error(request, _("Invalid input"))
else:
# For clarification: nothing in this case
pass
context = {
"form": form,
}
context = BaseContext(request, context).context
return render(request, template, context)
@login_required
def open_view(request: HttpRequest, id: str):
""" Renders a view for viewing an intervention's data
Args:
request (HttpRequest): The incoming request
id (str): The intervention's id
Returns:
"""
template = "intervention/open.html"
intervention = get_object_or_404(Intervention, id=id)
form = OpenInterventionForm(instance=intervention)
context = {
"intervention": intervention,
"form": form,
}
context = BaseContext(request, context).context
return render(request, template, context)
@login_required
def edit_view(request: HttpRequest, id: str):
"""
Renders a view for editing interventions
Args:
request (HttpRequest): The incoming request
Returns:
"""
template = "konova/form.html"
intervention = get_object_or_404(Intervention, id=id)
if request.method == "POST":
form = EditInterventionForm(request.POST or None, instance=intervention)
if form.is_valid():
intervention = form.save(request.user)
messages.success(request, _("{} edited").format(intervention))
return redirect("intervention:index")
else:
messages.error(request, _("Invalid input"))
form = EditInterventionForm(instance=intervention)
context = {
"form": form,
}
context = BaseContext(request, context).context
return render(request, template, context)
@login_required
def remove_view(request: HttpRequest, id: str):
""" Renders a remove view for this process
Args:
request (HttpRequest): The incoming request
id (str): The uuid id as string
Returns:
"""
template = "konova/form.html"
# Since an intervention is always organized inside a process, we will call the process removing routine, which
# disables all related elements by default
obj = get_object_or_404(Intervention, id=id)
process = obj.process
if request.method == "POST":
form = RemoveForm(
request.POST or None,
object_to_remove=obj,
remove_post_url=reverse("process:remove", args=(process.id,)),
cancel_url=reverse("intervention:index"),
)
if form.is_valid():
confirmed = form.is_checked()
if confirmed:
process.deactivate()
messages.success(request, _("Intervention {} removed").format(obj))
return redirect("intervention:index")
else:
messages.error(request, _("Invalid input"))
form = RemoveForm(
object_to_remove=obj,
remove_post_url=reverse("process:remove", args=(process.id,)),
cancel_url=reverse("intervention:index"),
)
context = {
"form": form,
}
context = BaseContext(request, context).context
return render(request, template, context)