Initial
This commit is contained in:
commit
c14e9466fb
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# Project exclude paths
|
||||||
|
/venv/
|
||||||
|
/.idea/
|
||||||
|
*/migrations/
|
0
compensation/__init__.py
Normal file
0
compensation/__init__.py
Normal file
46
compensation/admin.py
Normal file
46
compensation/admin.py
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
from compensation.models import Compensation, CompensationAction, CompensationState, CompensationControl
|
||||||
|
|
||||||
|
|
||||||
|
class CompensationControlAdmin(admin.ModelAdmin):
|
||||||
|
list_display = [
|
||||||
|
"id",
|
||||||
|
"type",
|
||||||
|
"deadline",
|
||||||
|
"expected_result",
|
||||||
|
"by_authority",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class CompensationStateAdmin(admin.ModelAdmin):
|
||||||
|
list_display = [
|
||||||
|
"id",
|
||||||
|
"biotope_type",
|
||||||
|
"amount",
|
||||||
|
"unit",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class CompensationActionAdmin(admin.ModelAdmin):
|
||||||
|
list_display = [
|
||||||
|
"id",
|
||||||
|
"action_type",
|
||||||
|
"amount",
|
||||||
|
"unit",
|
||||||
|
"control",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class CompensationAdmin(admin.ModelAdmin):
|
||||||
|
list_display = [
|
||||||
|
"id",
|
||||||
|
"type",
|
||||||
|
"created_on",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
admin.site.register(Compensation, CompensationAdmin)
|
||||||
|
admin.site.register(CompensationAction, CompensationActionAdmin)
|
||||||
|
admin.site.register(CompensationState, CompensationStateAdmin)
|
||||||
|
admin.site.register(CompensationControl, CompensationControlAdmin)
|
5
compensation/apps.py
Normal file
5
compensation/apps.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class CompensationConfig(AppConfig):
|
||||||
|
name = 'compensation'
|
14
compensation/forms.py
Normal file
14
compensation/forms.py
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
"""
|
||||||
|
Author: Michel Peltriaux
|
||||||
|
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||||
|
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||||
|
Created on: 04.12.20
|
||||||
|
|
||||||
|
"""
|
||||||
|
from konova.forms import BaseForm
|
||||||
|
|
||||||
|
|
||||||
|
class NewCompensationForm(BaseForm):
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
145
compensation/models.py
Normal file
145
compensation/models.py
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
"""
|
||||||
|
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 compensation.settings import COMPENSATION_IDENTIFIER_LENGTH, COMPENSATION_IDENTIFIER_TEMPLATE
|
||||||
|
from konova.models import BaseObject, BaseResource
|
||||||
|
from konova.utils.generators import generate_random_string
|
||||||
|
from process.models import Process
|
||||||
|
|
||||||
|
|
||||||
|
class CompensationControl(BaseResource):
|
||||||
|
"""
|
||||||
|
Holds data on how a compensation shall be controlled
|
||||||
|
"""
|
||||||
|
deadline = models.ForeignKey("konova.Deadline", on_delete=models.SET_NULL, null=True, blank=True)
|
||||||
|
type = models.CharField(max_length=500, null=True, blank=True)
|
||||||
|
expected_result = models.CharField(max_length=500, null=True, blank=True, help_text="The expected outcome, that needs to be controlled")
|
||||||
|
by_authority = models.CharField(max_length=500, null=True, blank=True)
|
||||||
|
comment = models.TextField()
|
||||||
|
|
||||||
|
|
||||||
|
class CompensationState(models.Model):
|
||||||
|
"""
|
||||||
|
Compensations must define the state of an area before and after the compensation.
|
||||||
|
"""
|
||||||
|
biotope_type = models.CharField(max_length=500, null=True, blank=True)
|
||||||
|
amount = models.FloatField()
|
||||||
|
unit = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
|
||||||
|
|
||||||
|
class CompensationAction(BaseResource):
|
||||||
|
"""
|
||||||
|
Compensations include actions like planting trees, refreshing rivers and so on.
|
||||||
|
"""
|
||||||
|
action_type = models.CharField(max_length=500, null=True, blank=True)
|
||||||
|
amount = models.FloatField()
|
||||||
|
unit = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
control = models.ForeignKey(CompensationControl, on_delete=models.SET_NULL, null=True, blank=True)
|
||||||
|
|
||||||
|
|
||||||
|
class Compensation(BaseObject):
|
||||||
|
"""
|
||||||
|
The compensation holds information about which actions have to be performed until which date, who is in charge
|
||||||
|
of this, which legal authority is the point of contact, and so on.
|
||||||
|
"""
|
||||||
|
is_old_law = models.BooleanField(default=False)
|
||||||
|
type = models.CharField(max_length=500, null=True, blank=True)
|
||||||
|
registration_office = models.CharField(max_length=500, null=True, blank=True) # ToDo: Really needed?
|
||||||
|
process = models.ForeignKey("process.Process", related_name="compensations", on_delete=models.CASCADE)
|
||||||
|
ground_definitions = models.CharField(max_length=500, null=True, blank=True) # ToDo: Need to be M2M to laws!
|
||||||
|
action_definitions = models.CharField(max_length=500, null=True, blank=True) # ToDo: Need to be M2M to laws!
|
||||||
|
actions = models.ManyToManyField(CompensationAction)
|
||||||
|
deadline_creation = models.ForeignKey("konova.Deadline", on_delete=models.SET_NULL, null=True, blank=True, related_name="deadline_creation")
|
||||||
|
deadline_maintaining = models.ForeignKey("konova.Deadline", on_delete=models.SET_NULL, null=True, blank=True, related_name="deadline_maintaining")
|
||||||
|
initial_states = models.ManyToManyField(CompensationState, blank=True, related_name='+')
|
||||||
|
final_states = models.ManyToManyField(CompensationState, blank=True, related_name='+')
|
||||||
|
geometry = models.MultiPolygonField(null=True, blank=True)
|
||||||
|
documents = models.ManyToManyField("konova.Document", blank=True)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "{} of {}".format(self.type, self.process)
|
||||||
|
|
||||||
|
@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=COMPENSATION_IDENTIFIER_LENGTH,
|
||||||
|
only_numbers=True,
|
||||||
|
)
|
||||||
|
_str = "{}{}{}".format(curr_month, curr_year, rand_str)
|
||||||
|
return COMPENSATION_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 Compensation.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"):
|
||||||
|
""" 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 Compensation.objects.none()
|
||||||
|
processes = Process.get_role_objects(user, order_by)
|
||||||
|
processes.prefetch_related("compensations")
|
||||||
|
compensations = []
|
||||||
|
[compensations.extend(process.compensations.all()) for process in processes]
|
||||||
|
return compensations
|
||||||
|
|
||||||
|
|
||||||
|
class EcoAccount(BaseResource):
|
||||||
|
"""
|
||||||
|
An eco account is a kind of 'prepaid' compensation. It can be compared to an account that already has been filled
|
||||||
|
with some kind of currency. From this account one is able to 'withdraw' currency for current projects.
|
||||||
|
|
||||||
|
'Withdrawing' can only be applied by shrinking the size of the available geometry and declaring the withdrawed
|
||||||
|
geometry as a compensation for a process.
|
||||||
|
"""
|
||||||
|
is_old_law = models.BooleanField(default=False)
|
||||||
|
type = models.CharField(max_length=500, null=True, blank=True)
|
||||||
|
licensing_authority_document_identifier = models.CharField(max_length=500, null=True, blank=True)
|
||||||
|
registration_office = models.CharField(max_length=500, null=True, blank=True)
|
||||||
|
handler = models.CharField(max_length=500, null=True, blank=True)
|
||||||
|
handler_comments = models.TextField()
|
||||||
|
geometry = models.GeometryCollectionField()
|
||||||
|
documents = models.ManyToManyField("konova.Document")
|
||||||
|
initial_states = models.ManyToManyField(CompensationState, blank=True, related_name='+')
|
||||||
|
final_states = models.ManyToManyField(CompensationState, blank=True, related_name='+')
|
||||||
|
actions = models.ManyToManyField(CompensationAction)
|
||||||
|
deadline_maintaining = models.ForeignKey("konova.Deadline", on_delete=models.SET_NULL, null=True, blank=True)
|
||||||
|
deadline_other = models.ManyToManyField("konova.Deadline", blank=True, related_name='+')
|
||||||
|
comments = models.TextField()
|
9
compensation/settings.py
Normal file
9
compensation/settings.py
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
"""
|
||||||
|
Author: Michel Peltriaux
|
||||||
|
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||||
|
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||||
|
Created on: 18.12.20
|
||||||
|
|
||||||
|
"""
|
||||||
|
COMPENSATION_IDENTIFIER_LENGTH = 10
|
||||||
|
COMPENSATION_IDENTIFIER_TEMPLATE = "KOM-{}"
|
118
compensation/tables.py
Normal file
118
compensation/tables.py
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
"""
|
||||||
|
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 konova.utils.tables import BaseTable
|
||||||
|
import django_tables2 as tables
|
||||||
|
|
||||||
|
|
||||||
|
class CompensationTable(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 = _("Compensations")
|
||||||
|
self.add_new_url = reverse("compensation:new")
|
||||||
|
|
||||||
|
def render_ac(self, value, record):
|
||||||
|
"""
|
||||||
|
Renders possible actions for this record, such as delete.
|
||||||
|
"""
|
||||||
|
intervention = _("Compensation")
|
||||||
|
html = ""
|
||||||
|
html += self.render_open_btn(
|
||||||
|
_("Open {}").format(intervention),
|
||||||
|
reverse("compensation:open", args=(record.id,)),
|
||||||
|
new_tab=True
|
||||||
|
)
|
||||||
|
html += self.render_edit_btn(
|
||||||
|
_("Edit {}").format(intervention),
|
||||||
|
reverse("compensation:edit", args=(record.id,)),
|
||||||
|
)
|
||||||
|
html += self.render_delete_btn(
|
||||||
|
_("Delete {}").format(intervention),
|
||||||
|
reverse("compensation:remove", args=(record.id,)),
|
||||||
|
)
|
||||||
|
return format_html(html)
|
||||||
|
|
||||||
|
|
||||||
|
class EcoAccountTable(BaseTable):
|
||||||
|
id = tables.Column(
|
||||||
|
verbose_name=_("Identifier"),
|
||||||
|
orderable=True,
|
||||||
|
accessor="identifier",
|
||||||
|
)
|
||||||
|
t = tables.Column(
|
||||||
|
verbose_name=_("Title"),
|
||||||
|
orderable=True,
|
||||||
|
accessor="title",
|
||||||
|
)
|
||||||
|
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 = _("Eco Accounts")
|
||||||
|
self.add_new_url = reverse("compensation:account-new")
|
||||||
|
|
||||||
|
def render_ac(self, value, record):
|
||||||
|
"""
|
||||||
|
Renders possible actions for this record, such as delete.
|
||||||
|
"""
|
||||||
|
intervention = _("Compensation")
|
||||||
|
html = ""
|
||||||
|
html += self.render_open_btn(
|
||||||
|
_("Open {}").format(intervention),
|
||||||
|
reverse("compensation:open", args=(record.id,)),
|
||||||
|
new_tab=True
|
||||||
|
)
|
||||||
|
html += self.render_edit_btn(
|
||||||
|
_("Edit {}").format(intervention),
|
||||||
|
reverse("compensation:edit", args=(record.id,)),
|
||||||
|
)
|
||||||
|
html += self.render_delete_btn(
|
||||||
|
_("Delete {}").format(intervention),
|
||||||
|
reverse("compensation:remove", args=(record.id,)),
|
||||||
|
)
|
||||||
|
return format_html(html)
|
3
compensation/tests.py
Normal file
3
compensation/tests.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
27
compensation/urls.py
Normal file
27
compensation/urls.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
"""
|
||||||
|
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 compensation.views import *
|
||||||
|
|
||||||
|
app_name = "compensation"
|
||||||
|
urlpatterns = [
|
||||||
|
# Main compensation
|
||||||
|
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'),
|
||||||
|
|
||||||
|
# Eco-account
|
||||||
|
path("account/", account_index_view, name="account-index"),
|
||||||
|
path('account/new/', account_new_view, name='account-new'),
|
||||||
|
path('account/open/<id>', account_open_view, name='account-open'),
|
||||||
|
path('account/edit/<id>', account_edit_view, name='account-edit'),
|
||||||
|
path('account/remove/<id>', account_remove_view, name='account-remove'),
|
||||||
|
]
|
110
compensation/views.py
Normal file
110
compensation/views.py
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
from django.contrib.auth.decorators import login_required
|
||||||
|
from django.http import HttpRequest
|
||||||
|
from django.shortcuts import render
|
||||||
|
|
||||||
|
from compensation.models import Compensation, EcoAccount
|
||||||
|
from compensation.tables import CompensationTable, EcoAccountTable
|
||||||
|
from konova.contexts import BaseContext
|
||||||
|
from konova.decorators import *
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
@resolve_user_role
|
||||||
|
def index_view(request: HttpRequest):
|
||||||
|
"""
|
||||||
|
Renders the index view for compensation
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request (HttpRequest): The incoming request
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A rendered view
|
||||||
|
"""
|
||||||
|
template = "generic_index.html"
|
||||||
|
user = request.user
|
||||||
|
compensations = Compensation.get_role_objects(user)
|
||||||
|
table = CompensationTable(
|
||||||
|
request=request,
|
||||||
|
queryset=compensations
|
||||||
|
)
|
||||||
|
context = {
|
||||||
|
"table": table,
|
||||||
|
}
|
||||||
|
context = BaseContext(request, context).context
|
||||||
|
return render(request, template, context)
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def new_view(request: HttpRequest):
|
||||||
|
# ToDo
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def edit_view(request: HttpRequest, id: str):
|
||||||
|
# ToDo
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def open_view(request: HttpRequest, id: str):
|
||||||
|
# ToDo
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def remove_view(request: HttpRequest, id: str):
|
||||||
|
# ToDo
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def account_index_view(request: HttpRequest):
|
||||||
|
"""
|
||||||
|
Renders the index view for eco accounts
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request (HttpRequest): The incoming request
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A rendered view
|
||||||
|
"""
|
||||||
|
template = "generic_index.html"
|
||||||
|
user = request.user
|
||||||
|
eco_accounts = EcoAccount.objects.filter(
|
||||||
|
created_by=user,
|
||||||
|
is_deleted=False,
|
||||||
|
)
|
||||||
|
table = EcoAccountTable(
|
||||||
|
request=request,
|
||||||
|
queryset=eco_accounts
|
||||||
|
)
|
||||||
|
context = {
|
||||||
|
"table": table,
|
||||||
|
}
|
||||||
|
context = BaseContext(request, context).context
|
||||||
|
return render(request, template, context)
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def account_new_view(request: HttpRequest):
|
||||||
|
# ToDo
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def account_edit_view(request: HttpRequest, id: str):
|
||||||
|
# ToDo
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def account_open_view(request: HttpRequest, id: str):
|
||||||
|
# ToDo
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def account_remove_view(request: HttpRequest, id: str):
|
||||||
|
# ToDo
|
||||||
|
pass
|
0
intervention/__init__.py
Normal file
0
intervention/__init__.py
Normal file
18
intervention/admin.py
Normal file
18
intervention/admin.py
Normal 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
5
intervention/apps.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class InterventionConfig(AppConfig):
|
||||||
|
name = 'intervention'
|
220
intervention/forms.py
Normal file
220
intervention/forms.py
Normal 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
91
intervention/models.py
Normal 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
9
intervention/settings.py
Normal 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
87
intervention/tables.py
Normal 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)
|
47
intervention/templates/intervention/open.html
Normal file
47
intervention/templates/intervention/open.html
Normal 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
3
intervention/tests.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
19
intervention/urls.py
Normal file
19
intervention/urls.py
Normal 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
171
intervention/views.py
Normal 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)
|
0
kspneo/__init__.py
Normal file
0
kspneo/__init__.py
Normal file
32
kspneo/admin.py
Normal file
32
kspneo/admin.py
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
"""
|
||||||
|
Author: Michel Peltriaux
|
||||||
|
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||||
|
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||||
|
Created on: 07.12.20
|
||||||
|
|
||||||
|
"""
|
||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
from konova.models import RoleType, RoleGroup
|
||||||
|
|
||||||
|
|
||||||
|
class RoleTypeAdmin(admin.ModelAdmin):
|
||||||
|
list_display = [
|
||||||
|
"type",
|
||||||
|
"created_on",
|
||||||
|
"created_by",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class RoleGroupAdmin(admin.ModelAdmin):
|
||||||
|
list_display = [
|
||||||
|
"name",
|
||||||
|
"organisation",
|
||||||
|
"role",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
admin.site.register(RoleType, RoleTypeAdmin)
|
||||||
|
admin.site.register(RoleGroup, RoleGroupAdmin)
|
||||||
|
|
||||||
|
|
16
kspneo/asgi.py
Normal file
16
kspneo/asgi.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
"""
|
||||||
|
ASGI config for konova project.
|
||||||
|
|
||||||
|
It exposes the ASGI callable as a module-level variable named ``application``.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/3.1/howto/deployment/asgi/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.core.asgi import get_asgi_application
|
||||||
|
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'konova.settings')
|
||||||
|
|
||||||
|
application = get_asgi_application()
|
42
kspneo/autocompletes.py
Normal file
42
kspneo/autocompletes.py
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
"""
|
||||||
|
Author: Michel Peltriaux
|
||||||
|
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||||
|
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||||
|
Created on: 07.12.20
|
||||||
|
|
||||||
|
"""
|
||||||
|
from dal_select2.views import Select2QuerySetView
|
||||||
|
|
||||||
|
from organisation.enums import OrganisationTypeEnum
|
||||||
|
from organisation.models import Organisation
|
||||||
|
|
||||||
|
|
||||||
|
class OrganisationAutocomplete(Select2QuerySetView):
|
||||||
|
def get_queryset(self):
|
||||||
|
if self.request.user.is_anonymous:
|
||||||
|
return Organisation.objects.none()
|
||||||
|
qs = Organisation.objects.all()
|
||||||
|
if self.q:
|
||||||
|
qs = qs.filter(name__icontains=self.q)
|
||||||
|
qs = qs.order_by(
|
||||||
|
"name"
|
||||||
|
)
|
||||||
|
return qs
|
||||||
|
|
||||||
|
|
||||||
|
class NonOfficialOrganisationAutocomplete(Select2QuerySetView):
|
||||||
|
def get_queryset(self):
|
||||||
|
if self.request.user.is_anonymous:
|
||||||
|
return Organisation.objects.none()
|
||||||
|
qs = Organisation.objects.all()
|
||||||
|
if self.q:
|
||||||
|
qs = qs.filter(
|
||||||
|
name__icontains=self.q,
|
||||||
|
)
|
||||||
|
qs = qs.exclude(
|
||||||
|
type=OrganisationTypeEnum.OFFICIAL.value
|
||||||
|
)
|
||||||
|
qs = qs.order_by(
|
||||||
|
"name"
|
||||||
|
)
|
||||||
|
return qs
|
53
kspneo/contexts.py
Normal file
53
kspneo/contexts.py
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
"""
|
||||||
|
Author: Michel Peltriaux
|
||||||
|
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||||
|
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||||
|
Created on: 16.11.20
|
||||||
|
|
||||||
|
"""
|
||||||
|
from django.http import HttpRequest
|
||||||
|
|
||||||
|
from konova.models import RoleGroup
|
||||||
|
from konova.sub_settings.context_settings import BASE_TITLE, WIKI_URL, BASE_FRONTEND_TITLE, RENDER_HEADER
|
||||||
|
from konova.utils.session import set_session_user_role
|
||||||
|
|
||||||
|
|
||||||
|
class BaseContext:
|
||||||
|
"""
|
||||||
|
Holds all base data which is needed for every context rendering
|
||||||
|
"""
|
||||||
|
context = {
|
||||||
|
"base_title": BASE_TITLE,
|
||||||
|
"base_frontend_title": BASE_FRONTEND_TITLE,
|
||||||
|
"language": "en",
|
||||||
|
"wiki_url": WIKI_URL,
|
||||||
|
"user": None,
|
||||||
|
"render_header": RENDER_HEADER,
|
||||||
|
"current_role": None,
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, request: HttpRequest, additional_context: dict = {}):
|
||||||
|
self.context["language"] = request.LANGUAGE_CODE
|
||||||
|
self.context["user"] = request.user
|
||||||
|
self.__handle_current_role(request)
|
||||||
|
|
||||||
|
# Add additional context, derived from given parameters
|
||||||
|
self.context.update(additional_context)
|
||||||
|
|
||||||
|
def __handle_current_role(self, request: HttpRequest):
|
||||||
|
""" Reads/Writes current role from/to session
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request (HttpRequest): The incoming request
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
# Store current role in session object to reduce amount of db access
|
||||||
|
current_role = request.session.get("current_role", {})
|
||||||
|
|
||||||
|
if len(current_role) == 0 and request.user.is_authenticated:
|
||||||
|
role_group = RoleGroup.get_users_role_groups(request.user).first()
|
||||||
|
current_role = set_session_user_role(request, role_group)
|
||||||
|
|
||||||
|
self.context["current_role"] = current_role
|
94
kspneo/decorators.py
Normal file
94
kspneo/decorators.py
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
"""
|
||||||
|
Author: Michel Peltriaux
|
||||||
|
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||||
|
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||||
|
Created on: 16.11.20
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from functools import wraps
|
||||||
|
|
||||||
|
from django.contrib import messages
|
||||||
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
|
from django.shortcuts import redirect
|
||||||
|
from django.urls import reverse
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
from konova.models import RoleGroup
|
||||||
|
from konova.utils.session import get_session_user_role
|
||||||
|
from organisation.enums import RoleTypeEnum
|
||||||
|
from process.enums import PROCESS_EDITABLE_STATE
|
||||||
|
from process.models import Process
|
||||||
|
|
||||||
|
|
||||||
|
def staff_required(function):
|
||||||
|
"""
|
||||||
|
A decorator for functions which shall only be usable for staff members of the system
|
||||||
|
"""
|
||||||
|
@wraps(function)
|
||||||
|
def wrap(request, *args, **kwargs):
|
||||||
|
user = request.user
|
||||||
|
if user.is_staff:
|
||||||
|
return function(request, *args, **kwargs)
|
||||||
|
else:
|
||||||
|
messages.info(request, _("You need to be staff to perform this action!"))
|
||||||
|
return redirect(request.META.get("HTTP_REFERER", reverse("home")))
|
||||||
|
return wrap
|
||||||
|
|
||||||
|
|
||||||
|
def superuser_required(function):
|
||||||
|
"""
|
||||||
|
A decorator for functions which shall only be usable for superusers of the system
|
||||||
|
"""
|
||||||
|
@wraps(function)
|
||||||
|
def wrap(request, *args, **kwargs):
|
||||||
|
user = request.user
|
||||||
|
if user.is_superuser:
|
||||||
|
return function(request, *args, **kwargs)
|
||||||
|
else:
|
||||||
|
messages.info(request, _("You need to be administrator to perform this action!"))
|
||||||
|
return redirect(request.META.get("HTTP_REFERER", reverse("home")))
|
||||||
|
return wrap
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_user_role(function):
|
||||||
|
"""
|
||||||
|
A decorator for functions to resolve the current user role and store it in the user object
|
||||||
|
"""
|
||||||
|
@wraps(function)
|
||||||
|
def wrap(request, *args, **kwargs):
|
||||||
|
user = request.user
|
||||||
|
role = get_session_user_role(request)
|
||||||
|
try:
|
||||||
|
role = RoleGroup.objects.get(id=role.get("id", -1))
|
||||||
|
user.current_role = role
|
||||||
|
except ObjectDoesNotExist:
|
||||||
|
user.current_role = None
|
||||||
|
return function(request, *args, **kwargs)
|
||||||
|
return wrap
|
||||||
|
|
||||||
|
|
||||||
|
def valid_process_role_required(function):
|
||||||
|
"""
|
||||||
|
A decorator for functions to check whether the user has a valid role selected
|
||||||
|
"""
|
||||||
|
@wraps(function)
|
||||||
|
def wrap(request, *args, **kwargs):
|
||||||
|
user = request.user
|
||||||
|
if user.current_role is None:
|
||||||
|
role = get_session_user_role(request)
|
||||||
|
else:
|
||||||
|
role = user.current_role
|
||||||
|
try:
|
||||||
|
process = Process.objects.get(id=kwargs.get("id"))
|
||||||
|
editable = PROCESS_EDITABLE_STATE.get(process.state)
|
||||||
|
role_enum = RoleTypeEnum[role.role.type]
|
||||||
|
if role_enum in editable:
|
||||||
|
return function(request, *args, **kwargs)
|
||||||
|
else:
|
||||||
|
messages.error(request, _("Your current role is not allowed to do this"))
|
||||||
|
return redirect(request.META.get("HTTP_REFERER", "home"))
|
||||||
|
except ObjectDoesNotExist:
|
||||||
|
process = None
|
||||||
|
return function(request, *args, **kwargs)
|
||||||
|
return wrap
|
40
kspneo/enums.py
Normal file
40
kspneo/enums.py
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
"""
|
||||||
|
Author: Michel Peltriaux
|
||||||
|
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||||
|
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||||
|
Created on: 17.11.20
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
|
||||||
|
class BaseEnum(Enum):
|
||||||
|
""" Provides basic functionality for Enums
|
||||||
|
|
||||||
|
"""
|
||||||
|
@classmethod
|
||||||
|
def as_choices(cls, drop_empty_choice: bool = False):
|
||||||
|
empty_choice = [] if drop_empty_choice else [(None, "---")]
|
||||||
|
choices = empty_choice + [(enum.value, enum.name) for enum in cls]
|
||||||
|
return choices
|
||||||
|
|
||||||
|
|
||||||
|
class UnitEnum(BaseEnum):
|
||||||
|
"""
|
||||||
|
Predefines units for selection
|
||||||
|
"""
|
||||||
|
mm = "mm"
|
||||||
|
dm = "dm"
|
||||||
|
cm = "cm"
|
||||||
|
m = "m"
|
||||||
|
km = "km"
|
||||||
|
|
||||||
|
qmm = "qmm"
|
||||||
|
qdm = "qdm"
|
||||||
|
qcm = "qcm"
|
||||||
|
qm = "qm"
|
||||||
|
qkm = "qkm"
|
||||||
|
ha = "ha"
|
||||||
|
|
||||||
|
st = "St." # pieces
|
141
kspneo/forms.py
Normal file
141
kspneo/forms.py
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
"""
|
||||||
|
Author: Michel Peltriaux
|
||||||
|
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||||
|
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||||
|
Created on: 16.11.20
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from abc import abstractmethod
|
||||||
|
|
||||||
|
from django import forms
|
||||||
|
from django.http import HttpRequest
|
||||||
|
from django.urls import reverse
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
from konova.models import RoleGroup
|
||||||
|
from konova.utils.session import set_session_user_role
|
||||||
|
from organisation.settings import ROLE_TYPE_STRINGS
|
||||||
|
|
||||||
|
|
||||||
|
class BaseForm(forms.Form):
|
||||||
|
"""
|
||||||
|
Basic form for that holds attributes needed in all other forms
|
||||||
|
"""
|
||||||
|
action_url = None
|
||||||
|
form_title = None
|
||||||
|
cancel_redirect = None
|
||||||
|
form_caption = None
|
||||||
|
instance = None # The data holding model object
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
self.instance = kwargs.pop("instance", None)
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def save(self):
|
||||||
|
# To be implemented in subclasses!
|
||||||
|
pass
|
||||||
|
|
||||||
|
def disable_form_field(self, field: str):
|
||||||
|
"""
|
||||||
|
Disables a form field for user editing
|
||||||
|
"""
|
||||||
|
self.fields[field].widget.attrs["readonly"] = True
|
||||||
|
self.fields[field].disabled = True
|
||||||
|
self.fields[field].widget.attrs["title"] = _("Not editable")
|
||||||
|
|
||||||
|
def initialize_form_field(self, field: str, val):
|
||||||
|
"""
|
||||||
|
Initializes a form field with a value
|
||||||
|
"""
|
||||||
|
self.fields[field].initial = val
|
||||||
|
|
||||||
|
def load_initial_data(self, form_data: dict, disabled_fields: list):
|
||||||
|
""" Initializes form data from instance
|
||||||
|
|
||||||
|
Inserts instance data into form and disables form fields
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
if self.instance is None:
|
||||||
|
return
|
||||||
|
for k, v in form_data.items():
|
||||||
|
self.initialize_form_field(k, v)
|
||||||
|
for field in disabled_fields:
|
||||||
|
self.disable_form_field(field)
|
||||||
|
|
||||||
|
|
||||||
|
class RemoveForm(BaseForm):
|
||||||
|
check = forms.BooleanField(
|
||||||
|
label=_("Confirm"),
|
||||||
|
label_suffix=_(""),
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
self.object_to_remove = kwargs.pop("object_to_remove", None)
|
||||||
|
self.remove_post_url = kwargs.pop("remove_post_url", "")
|
||||||
|
self.cancel_url = kwargs.pop("cancel_url", "")
|
||||||
|
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
self.form_title = _("Remove")
|
||||||
|
if self.object_to_remove is not None:
|
||||||
|
self.form_caption = _("You are about to remove {} {}").format(self.object_to_remove.__class__.__name__, self.object_to_remove)
|
||||||
|
self.action_url = self.remove_post_url
|
||||||
|
self.cancel_redirect = self.cancel_url
|
||||||
|
|
||||||
|
def is_checked(self) -> bool:
|
||||||
|
return self.cleaned_data.get("check", False)
|
||||||
|
|
||||||
|
def save(self):
|
||||||
|
if self.object_to_remove is not None and self.is_checked():
|
||||||
|
self.object_to_remove.is_active = False
|
||||||
|
self.object_to_remove.is_deleted = True
|
||||||
|
self.object_to_remove.save()
|
||||||
|
return self.object_to_remove
|
||||||
|
|
||||||
|
|
||||||
|
class ChangeUserRoleForm(BaseForm):
|
||||||
|
"""
|
||||||
|
Form for a user to change the current role
|
||||||
|
"""
|
||||||
|
role = forms.ChoiceField(
|
||||||
|
label=_("You are working as"),
|
||||||
|
label_suffix="",
|
||||||
|
choices=[],
|
||||||
|
widget=forms.Select(
|
||||||
|
attrs={
|
||||||
|
"onchange": "submit();",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
user = kwargs.pop("user", None)
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.action_url = reverse("home")
|
||||||
|
self.cancel_redirect = reverse("home")
|
||||||
|
|
||||||
|
role_groups = RoleGroup.get_users_role_groups(user)
|
||||||
|
choices = []
|
||||||
|
for group in role_groups:
|
||||||
|
choices.append(
|
||||||
|
(group.id, "{} ({})".format(ROLE_TYPE_STRINGS.get(group.role.type, None), group.organisation))
|
||||||
|
)
|
||||||
|
self.fields["role"].choices = choices
|
||||||
|
|
||||||
|
def save(self, request: HttpRequest) -> RoleGroup:
|
||||||
|
""" Custom save method for storing the newly selected role
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request (HttpRequest):
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
role_group = RoleGroup.get_users_role_groups(request.user).get(id=self.cleaned_data.get("role", -1))
|
||||||
|
set_session_user_role(request, role_group)
|
||||||
|
return role_group
|
153
kspneo/management/commands/setup.py
Normal file
153
kspneo/management/commands/setup.py
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
"""
|
||||||
|
Author: Michel Peltriaux
|
||||||
|
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||||
|
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||||
|
Created on: 15.12.20
|
||||||
|
|
||||||
|
"""
|
||||||
|
from getpass import getpass
|
||||||
|
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.core.management import BaseCommand
|
||||||
|
from django.db import transaction
|
||||||
|
|
||||||
|
from konova.management.commands.setup_test_data import TEST_ORGANISATION_DATA, TEST_ROLE_GROUPS_DATA
|
||||||
|
from konova.models import RoleType, RoleGroup
|
||||||
|
from organisation.enums import RoleTypeEnum
|
||||||
|
from organisation.models import Organisation
|
||||||
|
|
||||||
|
CREATED_TEMPLATE = "{} created"
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = "Initializes database with basic data"
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
try:
|
||||||
|
with transaction.atomic():
|
||||||
|
self.__init_superuser()
|
||||||
|
self.__init_test_organisation()
|
||||||
|
self.__init_role_types()
|
||||||
|
self.__init_role_groups()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
self.__break_line()
|
||||||
|
exit(-1)
|
||||||
|
|
||||||
|
def __init_superuser(self):
|
||||||
|
""" Create a superuser by user prompt input
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
self.stdout.write(
|
||||||
|
self.style.WARNING(
|
||||||
|
"--- Superuser ---",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
username = input("Superuser name: ")
|
||||||
|
if User.objects.filter(username=username).exists():
|
||||||
|
self.stdout.write(
|
||||||
|
self.style.ERROR(
|
||||||
|
"Name already taken!"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
exit(-1)
|
||||||
|
pw = getpass("Password: ")
|
||||||
|
pw_confirm = getpass("Confirm password : ")
|
||||||
|
if pw != pw_confirm:
|
||||||
|
self.stdout.write(
|
||||||
|
self.style.ERROR(
|
||||||
|
"Passwords did not match!"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
exit(-1)
|
||||||
|
|
||||||
|
# Create superuser
|
||||||
|
superuser = User()
|
||||||
|
superuser.username = username
|
||||||
|
superuser.is_superuser = True
|
||||||
|
superuser.is_staff = True
|
||||||
|
superuser.set_password(pw)
|
||||||
|
superuser.save()
|
||||||
|
self.stdout.write(
|
||||||
|
self.style.SUCCESS(
|
||||||
|
"Superuser {} created".format(username)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.__break_line()
|
||||||
|
|
||||||
|
def __init_role_types(self):
|
||||||
|
""" Initializes available role types according to RoleTypeEnum
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
self.stdout.write(
|
||||||
|
self.style.WARNING(
|
||||||
|
"--- Role types ---"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
for role_type_enum in RoleTypeEnum:
|
||||||
|
role_type = RoleType.objects.get_or_create(
|
||||||
|
type=role_type_enum.value
|
||||||
|
)[0]
|
||||||
|
self.stdout.write(
|
||||||
|
self.style.SUCCESS(
|
||||||
|
CREATED_TEMPLATE.format(role_type.type)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.__break_line()
|
||||||
|
|
||||||
|
def __init_test_organisation(self):
|
||||||
|
""" Creates test organisations from predefined data
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
self.stdout.write(
|
||||||
|
self.style.WARNING(
|
||||||
|
"--- Organisations ---"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
for org in TEST_ORGANISATION_DATA:
|
||||||
|
db_org = Organisation.objects.get_or_create(
|
||||||
|
**org
|
||||||
|
)[0]
|
||||||
|
self.stdout.write(
|
||||||
|
self.style.SUCCESS(
|
||||||
|
CREATED_TEMPLATE.format(db_org.name)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.__break_line()
|
||||||
|
|
||||||
|
def __init_role_groups(self):
|
||||||
|
""" Creates test role groups from predefined data
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
self.stdout.write(
|
||||||
|
self.style.WARNING(
|
||||||
|
"--- Role Groups ---"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
for group_data in TEST_ROLE_GROUPS_DATA:
|
||||||
|
group_data["organisation"] = Organisation.objects.get(name=group_data["organisation"])
|
||||||
|
group_data["role"] = RoleType.objects.get(type=group_data["role"])
|
||||||
|
group = RoleGroup.objects.get_or_create(
|
||||||
|
**group_data
|
||||||
|
)[0]
|
||||||
|
self.stdout.write(
|
||||||
|
self.style.SUCCESS(
|
||||||
|
CREATED_TEMPLATE.format(group.name)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.__break_line()
|
||||||
|
|
||||||
|
def __break_line(self):
|
||||||
|
""" Simply prints a line break
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
self.stdout.write("\n")
|
58
kspneo/management/commands/setup_test_data.py
Normal file
58
kspneo/management/commands/setup_test_data.py
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
"""
|
||||||
|
Author: Michel Peltriaux
|
||||||
|
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||||
|
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||||
|
Created on: 15.12.20
|
||||||
|
|
||||||
|
"""
|
||||||
|
from organisation.enums import OrganisationTypeEnum, RoleTypeEnum
|
||||||
|
|
||||||
|
TEST_ORGANISATION_DATA = [
|
||||||
|
{
|
||||||
|
"name": "Test_Official_1",
|
||||||
|
"is_active": True,
|
||||||
|
"is_deleted": False,
|
||||||
|
"type": OrganisationTypeEnum.OFFICIAL.value,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Test_Official_2",
|
||||||
|
"is_active": True,
|
||||||
|
"is_deleted": False,
|
||||||
|
"type": OrganisationTypeEnum.OFFICIAL.value,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Test_NGO_1",
|
||||||
|
"is_active": True,
|
||||||
|
"is_deleted": False,
|
||||||
|
"type": OrganisationTypeEnum.NGO.value,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Test_Company_1",
|
||||||
|
"is_active": True,
|
||||||
|
"is_deleted": False,
|
||||||
|
"type": OrganisationTypeEnum.COMPANY.value,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
TEST_ROLE_GROUPS_DATA = [
|
||||||
|
{
|
||||||
|
"name": "Registration office Test_Official_1",
|
||||||
|
"organisation": "Test_Official_1",
|
||||||
|
"role": RoleTypeEnum.REGISTRATIONOFFICE.value,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Licensing authority Test_Official_1",
|
||||||
|
"organisation": "Test_Official_1",
|
||||||
|
"role": RoleTypeEnum.LICENSINGAUTHORITY.value,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Dataprovider Test_Official_2",
|
||||||
|
"organisation": "Test_Official_2",
|
||||||
|
"role": RoleTypeEnum.LICENSINGAUTHORITY.value,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Dataprovider Test_Company_1",
|
||||||
|
"organisation": "Test_Company_1",
|
||||||
|
"role": RoleTypeEnum.DATAPROVIDER.value,
|
||||||
|
},
|
||||||
|
]
|
104
kspneo/models.py
Normal file
104
kspneo/models.py
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
"""
|
||||||
|
Author: Michel Peltriaux
|
||||||
|
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||||
|
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||||
|
Created on: 17.11.20
|
||||||
|
|
||||||
|
"""
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
from django.contrib.auth.models import User, Group
|
||||||
|
from django.db import models
|
||||||
|
from django.db.models import QuerySet
|
||||||
|
|
||||||
|
from organisation.enums import RoleTypeEnum
|
||||||
|
|
||||||
|
|
||||||
|
class BaseResource(models.Model):
|
||||||
|
"""
|
||||||
|
A basic resource model, which defines attributes for every derived model
|
||||||
|
"""
|
||||||
|
id = models.UUIDField(
|
||||||
|
primary_key=True,
|
||||||
|
default=uuid.uuid4,
|
||||||
|
)
|
||||||
|
is_active = models.BooleanField(default=True)
|
||||||
|
is_deleted = models.BooleanField(default=False)
|
||||||
|
created_on = models.DateTimeField(auto_now_add=True, null=True)
|
||||||
|
created_by = models.ForeignKey(User, null=True, on_delete=models.SET_NULL)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
abstract = True
|
||||||
|
|
||||||
|
|
||||||
|
class BaseObject(BaseResource):
|
||||||
|
"""
|
||||||
|
A basic object model, which specifies BaseResource.
|
||||||
|
|
||||||
|
Mainly used for intervention, compensation, ecoaccount
|
||||||
|
"""
|
||||||
|
identifier = models.CharField(max_length=1000, null=True, blank=True)
|
||||||
|
title = models.CharField(max_length=1000, null=True, blank=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
abstract = True
|
||||||
|
|
||||||
|
|
||||||
|
class Deadline(BaseResource):
|
||||||
|
"""
|
||||||
|
Defines a deadline, which can be used to define dates with a semantic meaning
|
||||||
|
"""
|
||||||
|
type = models.CharField(max_length=500, null=True, blank=True)
|
||||||
|
date = models.DateField(null=True, blank=True)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.type
|
||||||
|
|
||||||
|
|
||||||
|
class Document(BaseResource):
|
||||||
|
"""
|
||||||
|
Documents can be attached to process, compensation or intervention for uploading legal documents or pictures.
|
||||||
|
"""
|
||||||
|
date_of_creation = models.DateField()
|
||||||
|
document = models.FileField()
|
||||||
|
comment = models.TextField()
|
||||||
|
|
||||||
|
|
||||||
|
class RoleType(BaseResource):
|
||||||
|
"""
|
||||||
|
Defines different role types
|
||||||
|
"""
|
||||||
|
type = models.CharField(max_length=255, choices=RoleTypeEnum.as_choices(drop_empty_choice=True), unique=True)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.type
|
||||||
|
|
||||||
|
|
||||||
|
class RoleGroup(Group):
|
||||||
|
"""
|
||||||
|
Role groups are specialized groups which hold information on which users are related to a certain organisation and
|
||||||
|
a role
|
||||||
|
"""
|
||||||
|
organisation = models.ForeignKey("organisation.Organisation", on_delete=models.CASCADE)
|
||||||
|
role = models.ForeignKey(RoleType, on_delete=models.CASCADE)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
unique_together = [
|
||||||
|
["organisation", "role", ]
|
||||||
|
]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_users_role_groups(user: User) -> QuerySet:
|
||||||
|
""" Get all role groups of a given user
|
||||||
|
|
||||||
|
Args:
|
||||||
|
user (User): The user
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
qs (QuerySet)
|
||||||
|
"""
|
||||||
|
if user.is_anonymous:
|
||||||
|
return RoleGroup.objects.none()
|
||||||
|
return RoleGroup.objects.filter(
|
||||||
|
user=user
|
||||||
|
)
|
50
kspneo/settings.py
Normal file
50
kspneo/settings.py
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
"""
|
||||||
|
Django settings for konova project.
|
||||||
|
|
||||||
|
Generated by 'django-admin startproject' using Django 3.1.2.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/3.1/topics/settings/
|
||||||
|
|
||||||
|
For the full list of settings and their values, see
|
||||||
|
https://docs.djangoproject.com/en/3.1/ref/settings/
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Load other settings
|
||||||
|
from konova.sub_settings.django_settings import *
|
||||||
|
|
||||||
|
# Num of days if user enables Remember-me on login
|
||||||
|
KEEP_LOGGED_DURATION = 30
|
||||||
|
|
||||||
|
# German DateTime string format
|
||||||
|
STRF_DATE_TIME = "%d.%m.%Y %H:%M:%S"
|
||||||
|
|
||||||
|
# Tables
|
||||||
|
RESULTS_PER_PAGE_PARAM = "rpp"
|
||||||
|
PAGE_PARAM = "page"
|
||||||
|
PAGE_SIZE_OPTIONS = [5, 10, 15, 20, 25, 30, 50, 75, 100]
|
||||||
|
PAGE_SIZE_OPTIONS_TUPLES = [
|
||||||
|
(5, 5),
|
||||||
|
(10, 10),
|
||||||
|
(15, 15),
|
||||||
|
(20, 20),
|
||||||
|
(25, 25),
|
||||||
|
(30, 30),
|
||||||
|
(50, 50),
|
||||||
|
(75, 75),
|
||||||
|
(100, 100),
|
||||||
|
]
|
||||||
|
PAGE_SIZE_DEFAULT = 5
|
||||||
|
PAGE_SIZE_MAX = 100
|
||||||
|
PAGE_DEFAULT = 1
|
||||||
|
|
||||||
|
# SSO settings
|
||||||
|
SSO_SERVER_BASE = "http://127.0.0.1:8000/"
|
||||||
|
SSO_SERVER = "{}sso/".format(SSO_SERVER_BASE)
|
||||||
|
SSO_PRIVATE_KEY = "CHANGE_ME"
|
||||||
|
SSO_PUBLIC_KEY = "CHANGE_ME"
|
||||||
|
|
||||||
|
# MAPS
|
||||||
|
DEFAULT_LAT = 50.00
|
||||||
|
DEFAULT_LON = 7.00
|
||||||
|
DEFAULT_ZOOM = 8.0
|
57
kspneo/static/css/kspneo.css
Normal file
57
kspneo/static/css/kspneo.css
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
.body-content{
|
||||||
|
min-height: 75vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note{
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: lightgray;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label-required{
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
table{
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer{
|
||||||
|
width: 100%;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error{
|
||||||
|
color: #D8000C !important;
|
||||||
|
background-color: #FFBABA !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
i.true{
|
||||||
|
color: green;
|
||||||
|
}
|
||||||
|
i.false{
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button{
|
||||||
|
margin: 0.5625rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-col{
|
||||||
|
max-width: 8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-col .button{
|
||||||
|
margin: 0.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-role{
|
||||||
|
background: url('../images/menu-bg.png') repeat #871d33;
|
||||||
|
color: white;
|
||||||
|
padding: 0.5rem 0;
|
||||||
|
border-bottom: 4px solid #8e8e8e;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-role > a {
|
||||||
|
color: white;
|
||||||
|
}
|
43
kspneo/static/css/messages.css
Normal file
43
kspneo/static/css/messages.css
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
.info, .success, .warning, .error, .validation {
|
||||||
|
border: 1px solid;
|
||||||
|
margin: 10px 0px;
|
||||||
|
padding:15px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info:hover,
|
||||||
|
.success:hover,
|
||||||
|
.warning:hover,
|
||||||
|
.error:hover,
|
||||||
|
.validation:hover {
|
||||||
|
cursor: default;
|
||||||
|
filter: brightness(1.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.info {
|
||||||
|
color: #00529B;
|
||||||
|
background-color: #BDE5F8;
|
||||||
|
/*
|
||||||
|
background-image: url('../images/knobs/info.png');
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
.success {
|
||||||
|
color: #4F8A10;
|
||||||
|
background-color: #DFF2BF;
|
||||||
|
/*
|
||||||
|
background-image:url('../images/knobs/success.png');
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
.warning {
|
||||||
|
color: #9F6000;
|
||||||
|
background-color: #FEEFB3;
|
||||||
|
/*
|
||||||
|
background-image: url('../images/knobs/warning.png');
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
.error {
|
||||||
|
color: #D8000C;
|
||||||
|
background-color: #FFBABA;
|
||||||
|
/*
|
||||||
|
background-image: url('../images/knobs/error.png');
|
||||||
|
*/
|
||||||
|
}
|
9877
kspneo/static/css/mulewf.css
Normal file
9877
kspneo/static/css/mulewf.css
Normal file
File diff suppressed because it is too large
Load Diff
BIN
kspneo/static/fonts/rlp-icons.woff
Normal file
BIN
kspneo/static/fonts/rlp-icons.woff
Normal file
Binary file not shown.
BIN
kspneo/static/images/csm_rlp-logo_8de1241483.png
Normal file
BIN
kspneo/static/images/csm_rlp-logo_8de1241483.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.3 KiB |
BIN
kspneo/static/images/header-bg.png
Normal file
BIN
kspneo/static/images/header-bg.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.2 KiB |
BIN
kspneo/static/images/menu-bg.png
Normal file
BIN
kspneo/static/images/menu-bg.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 935 B |
BIN
kspneo/static/images/rlp-logos-MUEEF.png
Normal file
BIN
kspneo/static/images/rlp-logos-MUEEF.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 10 KiB |
2
kspneo/static/js/jquery-3.5.1.min.js
vendored
Normal file
2
kspneo/static/js/jquery-3.5.1.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
13
kspneo/static/js/jquery-ui.min.js
vendored
Normal file
13
kspneo/static/js/jquery-ui.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
6445
kspneo/static/js/mulewf.min.js
vendored
Normal file
6445
kspneo/static/js/mulewf.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
12
kspneo/sub_settings/context_settings.py
Normal file
12
kspneo/sub_settings/context_settings.py
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
"""
|
||||||
|
Author: Michel Peltriaux
|
||||||
|
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||||
|
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||||
|
Created on: 16.11.20
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
BASE_TITLE = "konova"
|
||||||
|
BASE_FRONTEND_TITLE = "Kompensationsverzeichnis Service Portal"
|
||||||
|
WIKI_URL = "https://dienste.naturschutz.rlp.de/doku/doku.php?id=ksp:start"
|
||||||
|
RENDER_HEADER = False
|
230
kspneo/sub_settings/django_settings.py
Normal file
230
kspneo/sub_settings/django_settings.py
Normal file
@ -0,0 +1,230 @@
|
|||||||
|
"""
|
||||||
|
Django settings for konova project.
|
||||||
|
|
||||||
|
Generated by 'django-admin startproject' using Django 3.1.3.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/3.1/topics/settings/
|
||||||
|
|
||||||
|
For the full list of settings and their values, see
|
||||||
|
https://docs.djangoproject.com/en/3.1/ref/settings/
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||||
|
BASE_DIR = os.path.dirname(
|
||||||
|
os.path.dirname(
|
||||||
|
os.path.dirname(
|
||||||
|
os.path.abspath(
|
||||||
|
__file__
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Quick-start development settings - unsuitable for production
|
||||||
|
# See https://docs.djangoproject.com/en/3.1/howto/deployment/checklist/
|
||||||
|
|
||||||
|
# SECURITY WARNING: keep the secret key used in production secret!
|
||||||
|
SECRET_KEY = '5=9-)2)h$u9=!zrhia9=lj-2#cpcb8=#$7y+)l$5tto$3q(n_+'
|
||||||
|
|
||||||
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
|
DEBUG = True
|
||||||
|
|
||||||
|
ALLOWED_HOSTS = []
|
||||||
|
|
||||||
|
# Authentication settings
|
||||||
|
LOGIN_URL = "/login/"
|
||||||
|
|
||||||
|
# Session settings
|
||||||
|
SESSION_COOKIE_AGE = 30 * 60 # 30 minutes
|
||||||
|
SESSION_SAVE_EVERY_REQUEST = True
|
||||||
|
|
||||||
|
# Application definition
|
||||||
|
|
||||||
|
INSTALLED_APPS = [
|
||||||
|
'dal',
|
||||||
|
'dal_select2',
|
||||||
|
'django.contrib.admin',
|
||||||
|
'django.contrib.auth',
|
||||||
|
'django.contrib.contenttypes',
|
||||||
|
'django.contrib.sessions',
|
||||||
|
'django.contrib.messages',
|
||||||
|
'django.contrib.staticfiles',
|
||||||
|
'django.contrib.gis',
|
||||||
|
'simple_sso.sso_server',
|
||||||
|
'django_tables2',
|
||||||
|
'fontawesome_5',
|
||||||
|
'konova',
|
||||||
|
'compensation',
|
||||||
|
'intervention',
|
||||||
|
'process',
|
||||||
|
'organisation',
|
||||||
|
]
|
||||||
|
if DEBUG:
|
||||||
|
INSTALLED_APPS += [
|
||||||
|
'debug_toolbar',
|
||||||
|
]
|
||||||
|
|
||||||
|
MIDDLEWARE = [
|
||||||
|
'django.middleware.security.SecurityMiddleware',
|
||||||
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||||
|
'django.middleware.common.CommonMiddleware',
|
||||||
|
'django.middleware.csrf.CsrfViewMiddleware',
|
||||||
|
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||||
|
'django.contrib.messages.middleware.MessageMiddleware',
|
||||||
|
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||||
|
"django.middleware.locale.LocaleMiddleware",
|
||||||
|
]
|
||||||
|
if DEBUG:
|
||||||
|
MIDDLEWARE += [
|
||||||
|
"debug_toolbar.middleware.DebugToolbarMiddleware",
|
||||||
|
]
|
||||||
|
|
||||||
|
ROOT_URLCONF = 'konova.urls'
|
||||||
|
|
||||||
|
TEMPLATES = [
|
||||||
|
{
|
||||||
|
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||||
|
'DIRS': [
|
||||||
|
os.path.join(BASE_DIR, "templates"),
|
||||||
|
],
|
||||||
|
'APP_DIRS': True,
|
||||||
|
'OPTIONS': {
|
||||||
|
'context_processors': [
|
||||||
|
'django.template.context_processors.debug',
|
||||||
|
'django.template.context_processors.request',
|
||||||
|
'django.contrib.auth.context_processors.auth',
|
||||||
|
'django.contrib.messages.context_processors.messages',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
WSGI_APPLICATION = 'konova.wsgi.application'
|
||||||
|
|
||||||
|
|
||||||
|
# Database
|
||||||
|
# https://docs.djangoproject.com/en/3.1/ref/settings/#databases
|
||||||
|
DATABASES = {
|
||||||
|
'default': {
|
||||||
|
'ENGINE': 'django.contrib.gis.db.backends.postgis',
|
||||||
|
'NAME': 'konova',
|
||||||
|
'USER': 'postgres',
|
||||||
|
'HOST': '127.0.0.1',
|
||||||
|
'PORT': '5432',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Password validation
|
||||||
|
# https://docs.djangoproject.com/en/3.1/ref/settings/#auth-password-validators
|
||||||
|
|
||||||
|
AUTH_PASSWORD_VALIDATORS = [
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# Internationalization
|
||||||
|
# https://docs.djangoproject.com/en/3.1/topics/i18n/
|
||||||
|
|
||||||
|
LANGUAGE_CODE = 'en-us'
|
||||||
|
|
||||||
|
DEFAULT_DATE_TIME_FORMAT = 'YYYY-MM-DD hh:mm:ss'
|
||||||
|
|
||||||
|
TIME_ZONE = 'Europe/Berlin'
|
||||||
|
|
||||||
|
USE_I18N = True
|
||||||
|
|
||||||
|
USE_L10N = True
|
||||||
|
|
||||||
|
USE_TZ = True
|
||||||
|
|
||||||
|
LOCALE_PATHS = (
|
||||||
|
os.path.join(BASE_DIR, 'locale'),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Static files (CSS, JavaScript, Images)
|
||||||
|
# https://docs.djangoproject.com/en/3.1/howto/static-files/
|
||||||
|
|
||||||
|
STATIC_URL = '/static/'
|
||||||
|
STATIC_ROOT = os.path.join(BASE_DIR, "static")
|
||||||
|
STATICFILES_DIRS = [
|
||||||
|
os.path.join(BASE_DIR, 'konova/static'),
|
||||||
|
]
|
||||||
|
|
||||||
|
# DJANGO DEBUG TOOLBAR
|
||||||
|
INTERNAL_IPS = [
|
||||||
|
"127.0.0.1"
|
||||||
|
]
|
||||||
|
DEBUG_TOOLBAR_CONFIG = {
|
||||||
|
"DISABLE_PANELS": {
|
||||||
|
'debug_toolbar.panels.versions.VersionsPanel',
|
||||||
|
'debug_toolbar.panels.timer.TimerPanel',
|
||||||
|
'debug_toolbar.panels.settings.SettingsPanel',
|
||||||
|
'debug_toolbar.panels.headers.HeadersPanel',
|
||||||
|
'debug_toolbar.panels.request.RequestPanel',
|
||||||
|
'debug_toolbar.panels.sql.SQLPanel',
|
||||||
|
'debug_toolbar.panels.staticfiles.StaticFilesPanel',
|
||||||
|
'debug_toolbar.panels.templates.TemplatesPanel',
|
||||||
|
'debug_toolbar.panels.cache.CachePanel',
|
||||||
|
'debug_toolbar.panels.signals.SignalsPanel',
|
||||||
|
'debug_toolbar.panels.logging.LoggingPanel',
|
||||||
|
'debug_toolbar.panels.redirects.RedirectsPanel',
|
||||||
|
'debug_toolbar.panels.profiling.ProfilingPanel',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# EMAIL (see https://docs.djangoproject.com/en/dev/topics/email/)
|
||||||
|
DEFAULT_FROM_EMAIL = "bot@arneo.de" # The default email address for the 'from' element
|
||||||
|
EMAIL_HOST = "localhost"
|
||||||
|
EMAIL_PORT = "1025"
|
||||||
|
#EMAIL_HOST_USER = ""
|
||||||
|
#EMAIL_HOST_PASSWORD = ""
|
||||||
|
EMAIL_USE_TLS = False
|
||||||
|
EMAIL_USE_SSL = False
|
||||||
|
|
||||||
|
# LOGGING
|
||||||
|
BASIC_LOGGER = "logger"
|
||||||
|
LOGGING = {
|
||||||
|
'version': 1,
|
||||||
|
'disable_existing_loggers': False,
|
||||||
|
'formatters': {
|
||||||
|
'verbose': {
|
||||||
|
'format': '{levelname} {asctime} {module}: {message}',
|
||||||
|
'style': '{',
|
||||||
|
},
|
||||||
|
'simple': {
|
||||||
|
'format': '{levelname} {message}',
|
||||||
|
'style': '{',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'handlers': {
|
||||||
|
'log_to_file': {
|
||||||
|
'level': 'DEBUG',
|
||||||
|
'class': 'logging.handlers.RotatingFileHandler',
|
||||||
|
'filename': '{}/logs/error.log'.format(BASE_DIR),
|
||||||
|
'maxBytes': 1024*1024*5, # 5 MB
|
||||||
|
'backupCount': 5,
|
||||||
|
'formatter': 'verbose',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'loggers': {
|
||||||
|
BASIC_LOGGER: {
|
||||||
|
'handlers': ['log_to_file'],
|
||||||
|
'level': 'INFO',
|
||||||
|
'propagate': True,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
5
kspneo/templates/kspneo/choiceColumnForm.html
Normal file
5
kspneo/templates/kspneo/choiceColumnForm.html
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
|
||||||
|
<form action="{{ form.action_url }}" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ form.as_p }}
|
||||||
|
</form>
|
8
kspneo/templates/kspneo/form.html
Normal file
8
kspneo/templates/kspneo/form.html
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
<div class="column">
|
||||||
|
{% include 'generic_table_form.html' %}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
44
kspneo/templates/kspneo/home.html
Normal file
44
kspneo/templates/kspneo/home.html
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block body_middle %}
|
||||||
|
<h1>Kompensationsverzeichnis</h1>
|
||||||
|
<h2>Service Portal</h2>
|
||||||
|
<hr>
|
||||||
|
{% if user.is_anonymous %}
|
||||||
|
<a href="{% url 'simple-sso-login' %}">
|
||||||
|
<button class="button middle">
|
||||||
|
{% trans 'Proceed with login' %}
|
||||||
|
</button>
|
||||||
|
</a>
|
||||||
|
{% else %}
|
||||||
|
<article>
|
||||||
|
{% trans 'Logged in as' %} <strong>{{ user.username }}</strong>
|
||||||
|
<br>
|
||||||
|
{% trans 'Last login on' %} {{ user.last_login }}
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<form action="{{form.action_url}}" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<table>
|
||||||
|
{% comment %}
|
||||||
|
This is an alternative to using the <article></article>
|
||||||
|
<tr>
|
||||||
|
<td>{% trans 'Logged in as' %}</td>
|
||||||
|
<td><strong>{{ user.username }}</strong></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{% trans 'Last login on' %}</td>
|
||||||
|
<td><strong>{{ user.last_login }}</strong></td>
|
||||||
|
</tr>
|
||||||
|
{% endcomment %}
|
||||||
|
{% for field in form %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ field.label }}</td>
|
||||||
|
<td>{{ field }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
7
kspneo/templatetags/__init__.py
Normal file
7
kspneo/templatetags/__init__.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
"""
|
||||||
|
Author: Michel Peltriaux
|
||||||
|
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||||
|
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||||
|
Created on: 04.12.20
|
||||||
|
|
||||||
|
"""
|
17
kspneo/templatetags/custom_tags.py
Normal file
17
kspneo/templatetags/custom_tags.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
"""
|
||||||
|
Author: Michel Peltriaux
|
||||||
|
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||||
|
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||||
|
Created on: 04.12.20
|
||||||
|
|
||||||
|
"""
|
||||||
|
from django import template
|
||||||
|
|
||||||
|
from process.settings import PROCESS_STATE_STRINGS
|
||||||
|
|
||||||
|
register = template.Library()
|
||||||
|
|
||||||
|
|
||||||
|
@register.filter
|
||||||
|
def resolve_process_state(value):
|
||||||
|
return PROCESS_STATE_STRINGS.get(value, None)
|
47
kspneo/urls.py
Normal file
47
kspneo/urls.py
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
"""konova URL Configuration
|
||||||
|
|
||||||
|
The `urlpatterns` list routes URLs to views. For more information please see:
|
||||||
|
https://docs.djangoproject.com/en/3.1/topics/http/urls/
|
||||||
|
Examples:
|
||||||
|
Function views
|
||||||
|
1. Add an import: from my_app import views
|
||||||
|
2. Add a URL to urlpatterns: path('', views.home, name='home')
|
||||||
|
Class-based views
|
||||||
|
1. Add an import: from other_app.views import Home
|
||||||
|
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
|
||||||
|
Including another URLconf
|
||||||
|
1. Import the include() function: from django.urls import include, path
|
||||||
|
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||||
|
"""
|
||||||
|
import debug_toolbar
|
||||||
|
from django.contrib import admin
|
||||||
|
from django.urls import path, include
|
||||||
|
from simple_sso.sso_client.client import Client
|
||||||
|
|
||||||
|
from konova.autocompletes import OrganisationAutocomplete, NonOfficialOrganisationAutocomplete
|
||||||
|
from konova.settings import SSO_SERVER, SSO_PUBLIC_KEY, SSO_PRIVATE_KEY, DEBUG
|
||||||
|
from konova.views import logout_view, home_view
|
||||||
|
|
||||||
|
sso_client = Client(SSO_SERVER, SSO_PUBLIC_KEY, SSO_PRIVATE_KEY)
|
||||||
|
urlpatterns = [
|
||||||
|
path('admin/', admin.site.urls),
|
||||||
|
path('login/', include(sso_client.get_urls())),
|
||||||
|
path('logout/', logout_view, name="logout"),
|
||||||
|
path('', home_view, name="home"),
|
||||||
|
path('process/', include("process.urls")),
|
||||||
|
path('intervention/', include("intervention.urls")),
|
||||||
|
path('compensation/', include("compensation.urls")),
|
||||||
|
path('eco-account/', include("process.urls")),
|
||||||
|
path('ema/', include("process.urls")),
|
||||||
|
path('organisation/', include("organisation.urls")),
|
||||||
|
path('user/', include("process.urls")),
|
||||||
|
|
||||||
|
# Autocomplete paths
|
||||||
|
path("atcmplt/orgs", OrganisationAutocomplete.as_view(), name="orgs-autocomplete"),
|
||||||
|
path("atcmplt/orgs/other", NonOfficialOrganisationAutocomplete.as_view(), name="other-orgs-autocomplete"),
|
||||||
|
]
|
||||||
|
|
||||||
|
if DEBUG:
|
||||||
|
urlpatterns += [
|
||||||
|
path('__debug__/', include(debug_toolbar.urls)),
|
||||||
|
]
|
22
kspneo/utils/generators.py
Normal file
22
kspneo/utils/generators.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
"""
|
||||||
|
Author: Michel Peltriaux
|
||||||
|
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||||
|
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||||
|
Created on: 09.11.20
|
||||||
|
|
||||||
|
"""
|
||||||
|
import random
|
||||||
|
import string
|
||||||
|
|
||||||
|
|
||||||
|
def generate_random_string(length: int, only_numbers: bool = False) -> str:
|
||||||
|
"""
|
||||||
|
Generates a random string of variable length
|
||||||
|
"""
|
||||||
|
if only_numbers:
|
||||||
|
elements = string.digits
|
||||||
|
else:
|
||||||
|
elements = string.ascii_letters
|
||||||
|
|
||||||
|
ret_val = "".join(random.choice(elements) for i in range(length))
|
||||||
|
return ret_val
|
52
kspneo/utils/mailer.py
Normal file
52
kspneo/utils/mailer.py
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
"""
|
||||||
|
Author: Michel Peltriaux
|
||||||
|
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||||
|
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||||
|
Created on: 09.11.20
|
||||||
|
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from django.core.mail import send_mail
|
||||||
|
|
||||||
|
from konova.sub_settings.django_settings import DEFAULT_FROM_EMAIL
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class Mailer:
|
||||||
|
"""
|
||||||
|
A wrapper for the django internal mailing functionality
|
||||||
|
"""
|
||||||
|
from_mail = None
|
||||||
|
to_mail = []
|
||||||
|
fail_silently = False
|
||||||
|
|
||||||
|
# Optional. Can be changed using the constructor to authenticate on the smtp server using other credentials
|
||||||
|
auth_user = None
|
||||||
|
auth_password = None
|
||||||
|
|
||||||
|
def __init__(self, to_mail: list, from_mail: str = DEFAULT_FROM_EMAIL, auth_user: str = None, auth_password: str = None, fail_silently: bool = False):
|
||||||
|
# Make sure given to_mail parameter is a list
|
||||||
|
if isinstance(to_mail, str):
|
||||||
|
to_mail = [to_mail]
|
||||||
|
|
||||||
|
self.from_mail = from_mail
|
||||||
|
self.to_mail = to_mail
|
||||||
|
self.fail_silently = fail_silently
|
||||||
|
self.auth_user = auth_user
|
||||||
|
self.auth_password = auth_password
|
||||||
|
|
||||||
|
def send(self, subject: str, msg: str):
|
||||||
|
"""
|
||||||
|
Sends a mail with subject and message
|
||||||
|
"""
|
||||||
|
return send_mail(
|
||||||
|
subject=subject,
|
||||||
|
message=msg,
|
||||||
|
from_email=self.from_mail,
|
||||||
|
recipient_list=self.to_mail,
|
||||||
|
fail_silently=self.fail_silently,
|
||||||
|
auth_user=self.auth_user,
|
||||||
|
auth_password=self.auth_password
|
||||||
|
)
|
45
kspneo/utils/session.py
Normal file
45
kspneo/utils/session.py
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
"""
|
||||||
|
Author: Michel Peltriaux
|
||||||
|
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||||
|
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||||
|
Created on: 09.12.20
|
||||||
|
|
||||||
|
"""
|
||||||
|
from django.http import HttpRequest
|
||||||
|
from idna import unicode
|
||||||
|
|
||||||
|
from konova.models import RoleGroup
|
||||||
|
from organisation.settings import ROLE_TYPE_STRINGS
|
||||||
|
|
||||||
|
CURRENT_ROLE_ID = "current_role"
|
||||||
|
|
||||||
|
|
||||||
|
def set_session_user_role(request: HttpRequest, role_group: RoleGroup) -> dict:
|
||||||
|
""" Set the user session to an active role
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request (HttpRequest): The user request
|
||||||
|
role_group (RoleGroup): The selected role group
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
current_role = {}
|
||||||
|
if role_group is not None:
|
||||||
|
current_role["type"] = unicode(ROLE_TYPE_STRINGS.get(role_group.role.type))
|
||||||
|
current_role["org"] = role_group.organisation.__str__()
|
||||||
|
current_role["id"] = role_group.id
|
||||||
|
request.session[CURRENT_ROLE_ID] = current_role
|
||||||
|
return current_role
|
||||||
|
|
||||||
|
|
||||||
|
def get_session_user_role(request: HttpRequest) -> dict:
|
||||||
|
""" Returns the current role chosen by a user for this session
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request (HttpRequest): The used request
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
return request.session.get(CURRENT_ROLE_ID, {})
|
121
kspneo/utils/tables.py
Normal file
121
kspneo/utils/tables.py
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
"""
|
||||||
|
Author: Michel Peltriaux
|
||||||
|
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||||
|
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||||
|
Created on: 25.11.20
|
||||||
|
|
||||||
|
"""
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
from django import forms
|
||||||
|
from django.core.paginator import PageNotAnInteger, EmptyPage
|
||||||
|
from django.http import HttpRequest
|
||||||
|
from django.utils.html import format_html
|
||||||
|
import django_tables2 as tables
|
||||||
|
|
||||||
|
from konova.forms import BaseForm
|
||||||
|
from konova.settings import PAGE_SIZE_DEFAULT, PAGE_PARAM, RESULTS_PER_PAGE_PARAM, PAGE_SIZE_OPTIONS
|
||||||
|
|
||||||
|
|
||||||
|
class BaseTable(tables.tables.Table):
|
||||||
|
results_per_page_choices = PAGE_SIZE_OPTIONS
|
||||||
|
results_per_page_chosen = None
|
||||||
|
results_per_page_parameter = RESULTS_PER_PAGE_PARAM
|
||||||
|
add_new_entries = True
|
||||||
|
add_new_url = None
|
||||||
|
title = None
|
||||||
|
|
||||||
|
def __init__(self, request: HttpRequest = None, filter_set=None, queryset=None, *args, **kwargs):
|
||||||
|
self.user = request.user or None
|
||||||
|
if filter_set is not None:
|
||||||
|
queryset = filter_set.qs
|
||||||
|
kwargs["data"] = queryset
|
||||||
|
kwargs["request"] = request
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.results_per_page_chosen = int(request.GET.get(RESULTS_PER_PAGE_PARAM, PAGE_SIZE_DEFAULT))
|
||||||
|
try:
|
||||||
|
self.paginate(
|
||||||
|
page=request.GET.get(PAGE_PARAM, 1),
|
||||||
|
per_page=self.results_per_page_chosen,
|
||||||
|
)
|
||||||
|
except (PageNotAnInteger, EmptyPage) as e:
|
||||||
|
self.paginate(
|
||||||
|
page=1,
|
||||||
|
per_page=self.results_per_page_chosen,
|
||||||
|
)
|
||||||
|
|
||||||
|
def render_link(self, tooltip: str, href: str, txt: str, new_tab: bool = False):
|
||||||
|
"""
|
||||||
|
Returns an <a> html element using given parameters
|
||||||
|
"""
|
||||||
|
new_tab = "_blank" if new_tab else "_self"
|
||||||
|
return format_html(
|
||||||
|
"<a href={} target='{}' title='{}'>{}</a>",
|
||||||
|
href,
|
||||||
|
new_tab,
|
||||||
|
tooltip,
|
||||||
|
txt,
|
||||||
|
)
|
||||||
|
|
||||||
|
def render_delete_btn(self, tooltip: str = None, href: str = None):
|
||||||
|
"""
|
||||||
|
Returns a remover icon with <a> support as html element using given parameters
|
||||||
|
"""
|
||||||
|
return format_html(
|
||||||
|
"<a href={} title='{}'><button class='button small'><em class='fas fa-trash-alt'></em></button></a>",
|
||||||
|
href,
|
||||||
|
tooltip,
|
||||||
|
)
|
||||||
|
|
||||||
|
def render_edit_btn(self, tooltip: str = None, href: str = None):
|
||||||
|
"""
|
||||||
|
Returns a remover icon with <a> support as html element using given parameters
|
||||||
|
"""
|
||||||
|
return format_html(
|
||||||
|
"<a href={} title='{}'><button class='button small'><em class='fas fa-edit'></em></button></a>",
|
||||||
|
href,
|
||||||
|
tooltip,
|
||||||
|
)
|
||||||
|
|
||||||
|
def render_open_btn(self, tooltip: str = None, href: str = None, new_tab: bool = False):
|
||||||
|
"""
|
||||||
|
Returns a remover icon with <a> support as html element using given parameters
|
||||||
|
"""
|
||||||
|
return format_html(
|
||||||
|
"<a href={} title='{}' target='{}'><button class='button small'><em class='fas fa-sign-in-alt'></em></button></a>",
|
||||||
|
href,
|
||||||
|
tooltip,
|
||||||
|
"_blank" if new_tab else ""
|
||||||
|
)
|
||||||
|
|
||||||
|
def render_boolean(self, tooltip: str = None, val: bool = False):
|
||||||
|
"""
|
||||||
|
Returns a remover icon with <a> support as html element using given parameters
|
||||||
|
"""
|
||||||
|
icon = "fas fa-check-circle true" if val else "fas fa-times-circle false"
|
||||||
|
return format_html(
|
||||||
|
"<em title='{}' class='{}'></em>",
|
||||||
|
tooltip,
|
||||||
|
icon
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ChoicesColumnForm(BaseForm):
|
||||||
|
select = forms.ChoiceField(
|
||||||
|
choices=[],
|
||||||
|
label="",
|
||||||
|
label_suffix="",
|
||||||
|
widget=forms.Select(
|
||||||
|
attrs={
|
||||||
|
"onchange": "submit();",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
self.action_url = kwargs.pop("action_url", None)
|
||||||
|
self.choices = kwargs.pop("choices", [])
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.auto_id += "_" + str(uuid.uuid4())
|
||||||
|
if len(self.choices) > 0:
|
||||||
|
self.fields["select"].choices = self.choices
|
67
kspneo/views.py
Normal file
67
kspneo/views.py
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
"""
|
||||||
|
Author: Michel Peltriaux
|
||||||
|
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||||
|
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||||
|
Created on: 16.11.20
|
||||||
|
|
||||||
|
"""
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from django.contrib import messages
|
||||||
|
from django.contrib.auth import logout
|
||||||
|
from django.http import HttpRequest
|
||||||
|
from django.shortcuts import redirect, render
|
||||||
|
|
||||||
|
from konova.contexts import BaseContext
|
||||||
|
from konova.forms import ChangeUserRoleForm
|
||||||
|
from konova.settings import SSO_SERVER_BASE
|
||||||
|
from konova.utils.session import get_session_user_role
|
||||||
|
|
||||||
|
|
||||||
|
def logout_view(request: HttpRequest):
|
||||||
|
"""
|
||||||
|
Logout route for ending the session manually.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request (HttpRequest): The used request object
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A redirect
|
||||||
|
"""
|
||||||
|
logout(request)
|
||||||
|
return redirect(SSO_SERVER_BASE)
|
||||||
|
|
||||||
|
|
||||||
|
def home_view(request: HttpRequest):
|
||||||
|
"""
|
||||||
|
Renders the landing page
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request (HttpRequest): The used request object
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A redirect
|
||||||
|
"""
|
||||||
|
template = "konova/home.html"
|
||||||
|
|
||||||
|
if request.method == "POST":
|
||||||
|
form = ChangeUserRoleForm(
|
||||||
|
request.POST or None,
|
||||||
|
user=request.user,
|
||||||
|
)
|
||||||
|
if form.is_valid():
|
||||||
|
role = form.save(request)
|
||||||
|
messages.success(request, _("Role changed"))
|
||||||
|
else:
|
||||||
|
messages.error(request, _("Invalid role"))
|
||||||
|
return redirect("home")
|
||||||
|
else:
|
||||||
|
# GET
|
||||||
|
form = ChangeUserRoleForm(
|
||||||
|
user=request.user,
|
||||||
|
initial={"role": int(get_session_user_role(request).get("id", -1))},
|
||||||
|
)
|
||||||
|
additional_context = {
|
||||||
|
"form": form,
|
||||||
|
}
|
||||||
|
context = BaseContext(request, additional_context).context
|
||||||
|
return render(request, template, context)
|
16
kspneo/wsgi.py
Normal file
16
kspneo/wsgi.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
"""
|
||||||
|
WSGI config for konova project.
|
||||||
|
|
||||||
|
It exposes the WSGI callable as a module-level variable named ``application``.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/3.1/howto/deployment/wsgi/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.core.wsgi import get_wsgi_application
|
||||||
|
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'konova.settings')
|
||||||
|
|
||||||
|
application = get_wsgi_application()
|
BIN
locale/de/LC_MESSAGES/django.mo
Normal file
BIN
locale/de/LC_MESSAGES/django.mo
Normal file
Binary file not shown.
1723
locale/de/LC_MESSAGES/django.po
Normal file
1723
locale/de/LC_MESSAGES/django.po
Normal file
File diff suppressed because it is too large
Load Diff
0
logs/error.log
Normal file
0
logs/error.log
Normal file
22
manage.py
Executable file
22
manage.py
Executable file
@ -0,0 +1,22 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
"""Django's command-line utility for administrative tasks."""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Run administrative tasks."""
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'konova.settings')
|
||||||
|
try:
|
||||||
|
from django.core.management import execute_from_command_line
|
||||||
|
except ImportError as exc:
|
||||||
|
raise ImportError(
|
||||||
|
"Couldn't import Django. Are you sure it's installed and "
|
||||||
|
"available on your PYTHONPATH environment variable? Did you "
|
||||||
|
"forget to activate a virtual environment?"
|
||||||
|
) from exc
|
||||||
|
execute_from_command_line(sys.argv)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
0
organisation/__init__.py
Normal file
0
organisation/__init__.py
Normal file
15
organisation/admin.py
Normal file
15
organisation/admin.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
from organisation.models import Organisation
|
||||||
|
|
||||||
|
|
||||||
|
class OrganisationAdmin(admin.ModelAdmin):
|
||||||
|
list_display = [
|
||||||
|
"name",
|
||||||
|
"type",
|
||||||
|
"created_on",
|
||||||
|
"created_by",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
admin.site.register(Organisation, OrganisationAdmin)
|
5
organisation/apps.py
Normal file
5
organisation/apps.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class OrganisationConfig(AppConfig):
|
||||||
|
name = 'organisation'
|
26
organisation/enums.py
Normal file
26
organisation/enums.py
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
"""
|
||||||
|
Author: Michel Peltriaux
|
||||||
|
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||||
|
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||||
|
Created on: 07.12.20
|
||||||
|
|
||||||
|
"""
|
||||||
|
from konova.enums import BaseEnum
|
||||||
|
|
||||||
|
|
||||||
|
class RoleTypeEnum(BaseEnum):
|
||||||
|
"""
|
||||||
|
Defines the possible role types for organisation and users
|
||||||
|
"""
|
||||||
|
DATAPROVIDER = "DATAPROVIDER"
|
||||||
|
LICENSINGAUTHORITY = "LICENSINGAUTHORITY"
|
||||||
|
REGISTRATIONOFFICE = "REGISTRATIONOFFICE"
|
||||||
|
|
||||||
|
|
||||||
|
class OrganisationTypeEnum(BaseEnum):
|
||||||
|
"""
|
||||||
|
Defines the possible role types for organisation and users
|
||||||
|
"""
|
||||||
|
OFFICIAL = "OFFICIAL"
|
||||||
|
COMPANY = "COMPANY"
|
||||||
|
NGO = "NGO"
|
18
organisation/models.py
Normal file
18
organisation/models.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
from django.db import models
|
||||||
|
|
||||||
|
from konova.models import BaseResource
|
||||||
|
from organisation.enums import OrganisationTypeEnum
|
||||||
|
|
||||||
|
|
||||||
|
class Organisation(BaseResource):
|
||||||
|
name = models.CharField(max_length=500, unique=True)
|
||||||
|
address = models.CharField(max_length=500, null=True, blank=True)
|
||||||
|
city = models.CharField(max_length=500, null=True, blank=True)
|
||||||
|
postal_code = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
phone = models.CharField(max_length=500, null=True, blank=True)
|
||||||
|
email = models.EmailField(max_length=500, null=True, blank=True)
|
||||||
|
facsimile = models.CharField(max_length=500, null=True, blank=True)
|
||||||
|
type = models.CharField(max_length=255, choices=OrganisationTypeEnum.as_choices(drop_empty_choice=True), null=True, blank=True)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
22
organisation/settings.py
Normal file
22
organisation/settings.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
"""
|
||||||
|
Author: Michel Peltriaux
|
||||||
|
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||||
|
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||||
|
Created on: 07.12.20
|
||||||
|
|
||||||
|
"""
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from organisation.enums import OrganisationTypeEnum as ote
|
||||||
|
from organisation.enums import RoleTypeEnum as rte
|
||||||
|
|
||||||
|
ORGANISATION_ROLE_STRINGS = {
|
||||||
|
ote.OFFICIAL.value: _("Official"),
|
||||||
|
ote.COMPANY.value: _("Company"),
|
||||||
|
ote.NGO.value: _("NGO"),
|
||||||
|
}
|
||||||
|
|
||||||
|
ROLE_TYPE_STRINGS = {
|
||||||
|
rte.DATAPROVIDER.value: _("Data provider"),
|
||||||
|
rte.LICENSINGAUTHORITY.value: _("Licencing Authority"),
|
||||||
|
rte.REGISTRATIONOFFICE.value: _("Registration office"),
|
||||||
|
}
|
3
organisation/tests.py
Normal file
3
organisation/tests.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
12
organisation/urls.py
Normal file
12
organisation/urls.py
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
"""
|
||||||
|
Author: Michel Peltriaux
|
||||||
|
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||||
|
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||||
|
Created on: 07.12.20
|
||||||
|
|
||||||
|
"""
|
||||||
|
from django.urls import path
|
||||||
|
|
||||||
|
app_name = "organisation"
|
||||||
|
urlpatterns = [
|
||||||
|
]
|
3
organisation/views.py
Normal file
3
organisation/views.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.shortcuts import render
|
||||||
|
|
||||||
|
# Create your views here.
|
0
process/__init__.py
Normal file
0
process/__init__.py
Normal file
38
process/admin.py
Normal file
38
process/admin.py
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
from process.models import Process
|
||||||
|
|
||||||
|
|
||||||
|
def activate_process(modeladmin, request, queryset):
|
||||||
|
for process in queryset:
|
||||||
|
process.activate()
|
||||||
|
|
||||||
|
|
||||||
|
def deactivate_process(modeladmin, request, queryset):
|
||||||
|
for process in queryset:
|
||||||
|
process.deactivate()
|
||||||
|
|
||||||
|
|
||||||
|
activate_process.short_description = "Activate selected process"
|
||||||
|
deactivate_process.short_description = "Deactivate selected process"
|
||||||
|
|
||||||
|
|
||||||
|
class ProcessAdmin(admin.ModelAdmin):
|
||||||
|
list_display = [
|
||||||
|
"id",
|
||||||
|
"licensing_authority",
|
||||||
|
"licensing_authority_document_identifier",
|
||||||
|
"registration_office",
|
||||||
|
"registration_office_document_identifier",
|
||||||
|
"is_active",
|
||||||
|
"is_deleted",
|
||||||
|
]
|
||||||
|
actions = [
|
||||||
|
activate_process,
|
||||||
|
deactivate_process,
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
admin.site.register(Process, ProcessAdmin)
|
5
process/apps.py
Normal file
5
process/apps.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class ProcessConfig(AppConfig):
|
||||||
|
name = 'process'
|
202
process/enums.py
Normal file
202
process/enums.py
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
"""
|
||||||
|
Author: Michel Peltriaux
|
||||||
|
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||||
|
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||||
|
Created on: 25.11.20
|
||||||
|
|
||||||
|
"""
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
|
||||||
|
from konova.enums import BaseEnum
|
||||||
|
from organisation.enums import RoleTypeEnum
|
||||||
|
|
||||||
|
|
||||||
|
class ProcessStateEnum(BaseEnum):
|
||||||
|
"""
|
||||||
|
ProcessStates define in which state a process can be:
|
||||||
|
|
||||||
|
private
|
||||||
|
Only the user can see and edit this process. Not viewable by any others
|
||||||
|
accessible
|
||||||
|
The user and all participated users can see this process. Only the responsible next user can edit
|
||||||
|
the process
|
||||||
|
licensed
|
||||||
|
The user and all participated users can see this process. Only the responsible next user can edit
|
||||||
|
the process
|
||||||
|
official
|
||||||
|
The user and all participated users can see this process. Only the registration office user can now
|
||||||
|
change details. If a process is changed in this state, the state will be set back to accessible, so all
|
||||||
|
participated users need to take a look on the changed data again.
|
||||||
|
recorded [Will be set automatically after certain time]
|
||||||
|
The user and all participated users can see this process. No one can edit data. To change any details,
|
||||||
|
the process has to be set to accessible manually again.
|
||||||
|
"""
|
||||||
|
PRIVATE = 0
|
||||||
|
ACCESSIBLE = 1
|
||||||
|
LICENSED = 2
|
||||||
|
OFFICIAL = 3
|
||||||
|
RECORDED = 4
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def as_choices(cls, drop_empty_choice: bool = False) -> list:
|
||||||
|
""" Extends as_choices, so choices will be translated
|
||||||
|
|
||||||
|
Args:
|
||||||
|
drop_empty_choice (bool): Whether the empty choice shall be dropped or not
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
trans_choices (list): Translated choices
|
||||||
|
"""
|
||||||
|
choices = super().as_choices(drop_empty_choice)
|
||||||
|
return ProcessStateEnum.__translate_choices(choices)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def __translate_choices(choices: list) -> list:
|
||||||
|
""" Translates a list of prepared but untranslated choices
|
||||||
|
|
||||||
|
Args:
|
||||||
|
choices (list): A list of tuple chocies
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
choices (list): The same list but translated
|
||||||
|
"""
|
||||||
|
from process.settings import PROCESS_STATE_STRINGS
|
||||||
|
trans_choices = []
|
||||||
|
# Translate
|
||||||
|
for choice in choices:
|
||||||
|
if choice[0] is not None:
|
||||||
|
choice = list(choice)
|
||||||
|
trans = PROCESS_STATE_STRINGS.get(choice[0])
|
||||||
|
choice[1] = trans
|
||||||
|
choice = tuple(choice)
|
||||||
|
trans_choices.append(choice)
|
||||||
|
return trans_choices
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def is_state(cls, state: int) -> bool:
|
||||||
|
""" Checks whether the given state is a valid Enum
|
||||||
|
|
||||||
|
Args:
|
||||||
|
state (int): The state to be checked
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
is_valid (bool)
|
||||||
|
"""
|
||||||
|
valid_vals = {enum.value for enum in cls}
|
||||||
|
return state in valid_vals
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def as_role_choices(cls, user: User) -> list:
|
||||||
|
""" Checks whether the given state is a valid Enum
|
||||||
|
|
||||||
|
Args:
|
||||||
|
user (User): The performing user
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
is_valid (bool)
|
||||||
|
"""
|
||||||
|
role = user.current_role
|
||||||
|
if role is None:
|
||||||
|
return []
|
||||||
|
role_type = role.role.type
|
||||||
|
role_type_enum = RoleTypeEnum[role_type]
|
||||||
|
choices = PROCESS_ROLE_STATES.get(role_type_enum)
|
||||||
|
choices = [(enum.value, enum.name) for enum in choices]
|
||||||
|
choices = ProcessStateEnum.__translate_choices(choices)
|
||||||
|
return choices
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def as_next_role_choices(cls, user: User, current_state: int, is_owner: bool = False) -> list:
|
||||||
|
""" Returns a list of valid choices depending on the current role of the user and the current state
|
||||||
|
|
||||||
|
Args:
|
||||||
|
user (User): The performing user
|
||||||
|
current_state (int): The current state of the process
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
choices (list): A list of valid choices
|
||||||
|
"""
|
||||||
|
role = user.current_role
|
||||||
|
if role is None:
|
||||||
|
return []
|
||||||
|
role_type = role.role.type
|
||||||
|
role_type_enum = RoleTypeEnum[role_type]
|
||||||
|
|
||||||
|
# Merge the possible choices depending on the current user role
|
||||||
|
# with the possible choices depending on the process state
|
||||||
|
role_choices = PROCESS_ROLE_STATES.get(role_type_enum)
|
||||||
|
status_choices = PROCESS_STATE_NEXT_CHOICES.get(current_state)
|
||||||
|
current_choice = {ProcessStateEnum(current_state)}
|
||||||
|
choices = (status_choices & role_choices) | current_choice
|
||||||
|
|
||||||
|
# If user is owner of this process, we shall add the private choice if not existing, yet
|
||||||
|
if is_owner:
|
||||||
|
choices = {ProcessStateEnum.PRIVATE} | choices
|
||||||
|
|
||||||
|
# Make sure enums are ordered by numerical value
|
||||||
|
choices = sorted(choices, key=lambda _enum: _enum.value)
|
||||||
|
|
||||||
|
# Create selectable and translated choices from enum list
|
||||||
|
choices = [(enum.value, enum.name) for enum in choices]
|
||||||
|
choices = ProcessStateEnum.__translate_choices(choices)
|
||||||
|
return choices
|
||||||
|
|
||||||
|
|
||||||
|
# DEFINES THE AVAILABLE STATES FOR EACH ROLE
|
||||||
|
PROCESS_ROLE_STATES = {
|
||||||
|
RoleTypeEnum.DATAPROVIDER: {
|
||||||
|
ProcessStateEnum.PRIVATE,
|
||||||
|
ProcessStateEnum.ACCESSIBLE,
|
||||||
|
},
|
||||||
|
RoleTypeEnum.LICENSINGAUTHORITY: {
|
||||||
|
#ProcessStateEnum.PRIVATE,
|
||||||
|
ProcessStateEnum.ACCESSIBLE,
|
||||||
|
ProcessStateEnum.LICENSED,
|
||||||
|
},
|
||||||
|
RoleTypeEnum.REGISTRATIONOFFICE: {
|
||||||
|
#ProcessStateEnum.PRIVATE,
|
||||||
|
ProcessStateEnum.ACCESSIBLE,
|
||||||
|
ProcessStateEnum.OFFICIAL,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
# DEFINES POSSIBLE NEXT STATES FOR EACH PROCESS STATE
|
||||||
|
PROCESS_STATE_NEXT_CHOICES = {
|
||||||
|
ProcessStateEnum.PRIVATE.value: {
|
||||||
|
ProcessStateEnum.PRIVATE,
|
||||||
|
ProcessStateEnum.ACCESSIBLE,
|
||||||
|
},
|
||||||
|
ProcessStateEnum.ACCESSIBLE.value: {
|
||||||
|
ProcessStateEnum.PRIVATE,
|
||||||
|
ProcessStateEnum.ACCESSIBLE,
|
||||||
|
ProcessStateEnum.LICENSED,
|
||||||
|
},
|
||||||
|
ProcessStateEnum.LICENSED.value: {
|
||||||
|
ProcessStateEnum.PRIVATE,
|
||||||
|
ProcessStateEnum.ACCESSIBLE,
|
||||||
|
ProcessStateEnum.LICENSED,
|
||||||
|
ProcessStateEnum.OFFICIAL,
|
||||||
|
},
|
||||||
|
ProcessStateEnum.OFFICIAL.value: {
|
||||||
|
ProcessStateEnum.ACCESSIBLE,
|
||||||
|
ProcessStateEnum.OFFICIAL,
|
||||||
|
ProcessStateEnum.RECORDED,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
# DEFINES FOR EACH STATE WHICH ROLE CAN EDIT THE PROCESS
|
||||||
|
PROCESS_EDITABLE_STATE = {
|
||||||
|
ProcessStateEnum.PRIVATE.value: {
|
||||||
|
RoleTypeEnum.DATAPROVIDER,
|
||||||
|
RoleTypeEnum.LICENSINGAUTHORITY,
|
||||||
|
},
|
||||||
|
ProcessStateEnum.ACCESSIBLE.value: {
|
||||||
|
RoleTypeEnum.LICENSINGAUTHORITY,
|
||||||
|
},
|
||||||
|
ProcessStateEnum.LICENSED.value: {
|
||||||
|
RoleTypeEnum.REGISTRATIONOFFICE,
|
||||||
|
},
|
||||||
|
ProcessStateEnum.OFFICIAL.value: {
|
||||||
|
RoleTypeEnum.REGISTRATIONOFFICE,
|
||||||
|
}
|
||||||
|
}
|
218
process/forms.py
Normal file
218
process/forms.py
Normal file
@ -0,0 +1,218 @@
|
|||||||
|
"""
|
||||||
|
Author: Michel Peltriaux
|
||||||
|
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||||
|
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||||
|
Created on: 30.11.20
|
||||||
|
|
||||||
|
"""
|
||||||
|
from dal import autocomplete
|
||||||
|
from django import forms
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
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 organisation.models import Organisation
|
||||||
|
from process.enums import ProcessStateEnum
|
||||||
|
from process.models import Process
|
||||||
|
|
||||||
|
|
||||||
|
class NewProcessForm(BaseForm):
|
||||||
|
"""
|
||||||
|
A form for new process
|
||||||
|
"""
|
||||||
|
identifier = forms.CharField(
|
||||||
|
max_length=255,
|
||||||
|
label=_("Identifier"),
|
||||||
|
label_suffix=_(""),
|
||||||
|
help_text=_("Generated automatically if none was given"),
|
||||||
|
required=False,
|
||||||
|
)
|
||||||
|
title = forms.CharField(
|
||||||
|
max_length=255,
|
||||||
|
label=_("Title"),
|
||||||
|
label_suffix=_(""),
|
||||||
|
help_text=_("Proper title of the process"),
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
|
type = forms.CharField(
|
||||||
|
max_length=255,
|
||||||
|
label=_("Type"),
|
||||||
|
label_suffix=_(""),
|
||||||
|
help_text=_("Which process type is this"),
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
licensing_authority = forms.ModelChoiceField(
|
||||||
|
label=_("Licencing Authority"),
|
||||||
|
label_suffix=_(""),
|
||||||
|
required=True,
|
||||||
|
queryset=Organisation.objects.all(),
|
||||||
|
widget=autocomplete.ModelSelect2(
|
||||||
|
url="orgs-autocomplete",
|
||||||
|
attrs={
|
||||||
|
"data-placeholder": _("Organization"),
|
||||||
|
"data-minimum-input-length": 3,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
)
|
||||||
|
licensing_authority_document_identifier = forms.CharField(
|
||||||
|
max_length=255,
|
||||||
|
label=_("Licencing document identifier"),
|
||||||
|
label_suffix=_(""),
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
|
comment_licensing_authority = forms.CharField(
|
||||||
|
widget=forms.Textarea,
|
||||||
|
label=_("Comment licensing authority"),
|
||||||
|
label_suffix=_(""),
|
||||||
|
required=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
registration_office = forms.ModelChoiceField(
|
||||||
|
label=_("Registration office"),
|
||||||
|
label_suffix=_(""),
|
||||||
|
required=True,
|
||||||
|
queryset=Organisation.objects.all(),
|
||||||
|
widget=autocomplete.ModelSelect2(
|
||||||
|
url="orgs-autocomplete",
|
||||||
|
attrs={
|
||||||
|
"data-placeholder": _("Organization"),
|
||||||
|
"data-minimum-input-length": 3,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
)
|
||||||
|
registration_office_document_identifier = forms.CharField(
|
||||||
|
max_length=255,
|
||||||
|
label=_("Registration document identifier"),
|
||||||
|
label_suffix=_(""),
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
|
comment_registration_office = forms.CharField(
|
||||||
|
widget=forms.Textarea,
|
||||||
|
label=_("Comment registration office"),
|
||||||
|
label_suffix=_(""),
|
||||||
|
required=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.action_url = reverse("process:new")
|
||||||
|
self.cancel_redirect = reverse("process:index")
|
||||||
|
self.form_title = _("Add new process")
|
||||||
|
self.form_caption = _("Enter these basic information for the new process.")
|
||||||
|
|
||||||
|
def save(self, user: User):
|
||||||
|
"""
|
||||||
|
Persists process objects into database
|
||||||
|
"""
|
||||||
|
with transaction.atomic():
|
||||||
|
identifier = self.cleaned_data.get("identifier", None)
|
||||||
|
title = self.cleaned_data.get("title", None)
|
||||||
|
_type = self.cleaned_data.get("type", None)
|
||||||
|
licencing_auth = self.cleaned_data.get("licensing_authority", None)
|
||||||
|
licencing_auth_doc_id = self.cleaned_data.get("licensing_authority_document_identifier", None)
|
||||||
|
reg_off = self.cleaned_data.get("registration_office", None)
|
||||||
|
reg_off_doc_id = self.cleaned_data.get("registration_office_document_identifier", None)
|
||||||
|
comment_license = self.cleaned_data.get("comment_licensing_authority", None)
|
||||||
|
comment_registration = self.cleaned_data.get("comment_registration_office", None)
|
||||||
|
|
||||||
|
process = Process()
|
||||||
|
process.licensing_authority = licencing_auth
|
||||||
|
process.licensing_authority_document_identifier = licencing_auth_doc_id
|
||||||
|
process.registration_office = reg_off
|
||||||
|
process.registration_office_document_identifier = reg_off_doc_id
|
||||||
|
process.state = ProcessStateEnum.PRIVATE.value
|
||||||
|
process.created_by = user
|
||||||
|
process.licensing_authority_comment = comment_license
|
||||||
|
process.registration_office_comment = comment_registration
|
||||||
|
process.save()
|
||||||
|
|
||||||
|
intervention = Intervention()
|
||||||
|
intervention.title = title
|
||||||
|
intervention.type = _type
|
||||||
|
intervention.created_by = user
|
||||||
|
intervention.process = process
|
||||||
|
intervention.identifier = identifier
|
||||||
|
intervention.save()
|
||||||
|
|
||||||
|
return process
|
||||||
|
|
||||||
|
|
||||||
|
class EditProcessForm(NewProcessForm):
|
||||||
|
status = forms.ChoiceField(
|
||||||
|
label=_("Status"),
|
||||||
|
label_suffix="",
|
||||||
|
choices=[],
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
self.user = kwargs.pop("user", None)
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
if self.instance is not None:
|
||||||
|
self.action_url = reverse("process:edit", args=(self.instance.id,))
|
||||||
|
self.cancel_redirect = reverse("process:index")
|
||||||
|
self.form_title = _("Edit process")
|
||||||
|
self.form_caption = ""
|
||||||
|
self.fields["status"].choices = ProcessStateEnum.as_next_role_choices(self.user, self.instance.state)
|
||||||
|
|
||||||
|
# Initialize form data
|
||||||
|
form_data = {
|
||||||
|
"identifier": self.instance.intervention.identifier,
|
||||||
|
"title": self.instance.intervention.title,
|
||||||
|
"type": self.instance.intervention.type,
|
||||||
|
"licensing_authority": self.instance.licensing_authority,
|
||||||
|
"licensing_authority_document_identifier": self.instance.licensing_authority_document_identifier,
|
||||||
|
"registration_office": self.instance.registration_office,
|
||||||
|
"registration_office_document_identifier": self.instance.registration_office_document_identifier,
|
||||||
|
"comment_licensing_authority": self.instance.licensing_authority_comment,
|
||||||
|
"comment_registration_office": self.instance.registration_office_comment,
|
||||||
|
"status": self.instance.state,
|
||||||
|
}
|
||||||
|
disabled_fields = [
|
||||||
|
"identifier",
|
||||||
|
]
|
||||||
|
self.load_initial_data(
|
||||||
|
form_data,
|
||||||
|
disabled_fields,
|
||||||
|
)
|
||||||
|
|
||||||
|
def save(self, user: User):
|
||||||
|
""" Persists changes from form to instance
|
||||||
|
|
||||||
|
Args:
|
||||||
|
user (User): The performing user
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
process (Process): The edited process instance
|
||||||
|
"""
|
||||||
|
with transaction.atomic():
|
||||||
|
title = self.cleaned_data.get("title", None)
|
||||||
|
_type = self.cleaned_data.get("type", None)
|
||||||
|
licencing_auth = self.cleaned_data.get("licensing_authority", None)
|
||||||
|
licencing_auth_doc_id = self.cleaned_data.get("licensing_authority_document_identifier", None)
|
||||||
|
reg_off = self.cleaned_data.get("registration_office", None)
|
||||||
|
reg_off_doc_id = self.cleaned_data.get("registration_office_document_identifier", None)
|
||||||
|
comment_license = self.cleaned_data.get("comment_licensing_authority", None)
|
||||||
|
comment_registration = self.cleaned_data.get("comment_registration_office", None)
|
||||||
|
new_state = self.cleaned_data.get("status", None)
|
||||||
|
|
||||||
|
self.instance.licensing_authority = licencing_auth
|
||||||
|
self.instance.licensing_authority_document_identifier = licencing_auth_doc_id
|
||||||
|
self.instance.registration_office = reg_off
|
||||||
|
self.instance.registration_office_document_identifier = reg_off_doc_id
|
||||||
|
self.instance.state = ProcessStateEnum.PRIVATE.value
|
||||||
|
self.instance.created_by = user
|
||||||
|
self.instance.licensing_authority_comment = comment_license
|
||||||
|
self.instance.registration_office_comment = comment_registration
|
||||||
|
self.instance.state = new_state
|
||||||
|
self.instance.save()
|
||||||
|
|
||||||
|
self.instance.intervention.title = title
|
||||||
|
self.instance.intervention.type = _type
|
||||||
|
self.instance.intervention.created_by = user
|
||||||
|
self.instance.intervention.save()
|
||||||
|
|
||||||
|
return self.instance
|
158
process/models.py
Normal file
158
process/models.py
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
"""
|
||||||
|
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.db import models, transaction
|
||||||
|
|
||||||
|
from konova.models import BaseResource
|
||||||
|
from organisation.enums import RoleTypeEnum
|
||||||
|
from organisation.models import Organisation
|
||||||
|
from process.enums import ProcessStateEnum
|
||||||
|
from process.settings import PROCESS_STATE_STRINGS
|
||||||
|
|
||||||
|
|
||||||
|
class Process(BaseResource):
|
||||||
|
"""
|
||||||
|
Process links compensation, intervention and eco accounts. These links are realized as ForeignKeys in their models.
|
||||||
|
|
||||||
|
This process model therefore just holds further information on participated legal organizations.
|
||||||
|
|
||||||
|
Attribute 'state' holds information on the current state of the process, which are defined in ProcessStateEnum
|
||||||
|
"""
|
||||||
|
licensing_authority = models.ForeignKey(Organisation, on_delete=models.SET_NULL, null=True, blank=True, related_name="+")
|
||||||
|
licensing_authority_document_identifier = models.CharField(max_length=500, null=True, blank=True)
|
||||||
|
licensing_authority_comment = models.CharField(max_length=500, null=True, blank=True)
|
||||||
|
registration_office_document_identifier = models.CharField(max_length=500, null=True, blank=True)
|
||||||
|
registration_office = models.ForeignKey(Organisation, on_delete=models.SET_NULL, null=True, blank=True, related_name="+")
|
||||||
|
registration_office_comment = models.CharField(max_length=500, null=True, blank=True)
|
||||||
|
state = models.PositiveIntegerField(choices=ProcessStateEnum.as_choices(drop_empty_choice=True), default=0)
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
try:
|
||||||
|
intervention = self.intervention
|
||||||
|
title = intervention.title
|
||||||
|
except AttributeError:
|
||||||
|
title = "NO TITLE"
|
||||||
|
return title
|
||||||
|
|
||||||
|
def get_state_str(self):
|
||||||
|
"""
|
||||||
|
Translates the numeric state into a string
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
return PROCESS_STATE_STRINGS.get(self.state, None)
|
||||||
|
|
||||||
|
def deactivate(self):
|
||||||
|
""" Deactivates a process and it's related elements
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
if self.is_active and not self.is_deleted:
|
||||||
|
self.toggle_deletion()
|
||||||
|
|
||||||
|
def activate(self):
|
||||||
|
""" Activates a process and it's related elements
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
if not self.is_active and self.is_deleted:
|
||||||
|
self.toggle_deletion()
|
||||||
|
|
||||||
|
def toggle_deletion(self):
|
||||||
|
""" Enables or disables a process
|
||||||
|
|
||||||
|
Processes are not truly removed from the database, just toggled in their flags 'is_active' and 'is_deleted'
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
with transaction.atomic():
|
||||||
|
self.is_active = not self.is_active
|
||||||
|
self.is_deleted = not self.is_deleted
|
||||||
|
self.save()
|
||||||
|
|
||||||
|
# toggle related elements
|
||||||
|
comps = self.compensation.all()
|
||||||
|
elements = list(comps)
|
||||||
|
if self.intervention is not None:
|
||||||
|
elements.append(self.intervention)
|
||||||
|
|
||||||
|
for elem in elements:
|
||||||
|
elem.is_active = self.is_active
|
||||||
|
elem.is_deleted = self.is_deleted
|
||||||
|
elem.save()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_role_objects(user: User, order_by: str = "-created_on"):
|
||||||
|
""" Returns processes 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 Process.objects.none()
|
||||||
|
_filter = {
|
||||||
|
"is_deleted": False,
|
||||||
|
}
|
||||||
|
if role.role.type == RoleTypeEnum.REGISTRATIONOFFICE.value:
|
||||||
|
_filter["registration_office"] = role.organisation
|
||||||
|
elif role.role.type == RoleTypeEnum.LICENSINGAUTHORITY.value:
|
||||||
|
_filter["licensing_authority"] = role.organisation
|
||||||
|
elif role.role.type == RoleTypeEnum.DATAPROVIDER.value:
|
||||||
|
# Nothing special
|
||||||
|
_filter["created_by"] = user
|
||||||
|
else:
|
||||||
|
# None of the above
|
||||||
|
pass
|
||||||
|
|
||||||
|
other_processes = Process.objects.filter(
|
||||||
|
**_filter
|
||||||
|
)
|
||||||
|
user_created_processes = Process.objects.filter(
|
||||||
|
created_by=user,
|
||||||
|
is_deleted=False,
|
||||||
|
)
|
||||||
|
qs = (other_processes | user_created_processes).distinct()
|
||||||
|
qs = qs.order_by(order_by)
|
||||||
|
return qs
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def create_from_intervention(intervention):
|
||||||
|
""" Creates a process for an intervention, in case an intervention has been created without a process
|
||||||
|
|
||||||
|
Args:
|
||||||
|
intervention (Intervention): The intervention
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
process (Process)
|
||||||
|
"""
|
||||||
|
process = Process()
|
||||||
|
process.identifier = intervention.identifier
|
||||||
|
process.title = intervention.title
|
||||||
|
process.created_by = intervention.created_by
|
||||||
|
process.save()
|
||||||
|
|
||||||
|
intervention.process = process
|
||||||
|
intervention.save()
|
||||||
|
return process
|
19
process/settings.py
Normal file
19
process/settings.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
"""
|
||||||
|
Author: Michel Peltriaux
|
||||||
|
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||||
|
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||||
|
Created on: 25.11.20
|
||||||
|
|
||||||
|
"""
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
from process.enums import ProcessStateEnum
|
||||||
|
|
||||||
|
# DEFINES TRANSLATIONS FOR EACH PROCESSSTATEENUM
|
||||||
|
PROCESS_STATE_STRINGS = {
|
||||||
|
ProcessStateEnum.PRIVATE.value: _("private"),
|
||||||
|
ProcessStateEnum.ACCESSIBLE.value: _("accessible"),
|
||||||
|
ProcessStateEnum.LICENSED.value: _("licensed"),
|
||||||
|
ProcessStateEnum.OFFICIAL.value: _("official"),
|
||||||
|
ProcessStateEnum.RECORDED.value: _("recorded"),
|
||||||
|
}
|
130
process/tables.py
Normal file
130
process/tables.py
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
"""
|
||||||
|
Author: Michel Peltriaux
|
||||||
|
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||||
|
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||||
|
Created on: 25.11.20
|
||||||
|
|
||||||
|
"""
|
||||||
|
from django.template.loader import render_to_string
|
||||||
|
from django.urls import reverse
|
||||||
|
from django.utils.html import format_html
|
||||||
|
|
||||||
|
from konova.utils.tables import BaseTable, ChoicesColumnForm
|
||||||
|
import django_tables2 as tables
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
from process.enums import ProcessStateEnum
|
||||||
|
from process.models import Process
|
||||||
|
from process.settings import PROCESS_STATE_STRINGS
|
||||||
|
|
||||||
|
|
||||||
|
class ProcessTable(BaseTable):
|
||||||
|
id = tables.Column(
|
||||||
|
verbose_name=_("Intervention identifier"),
|
||||||
|
orderable=True,
|
||||||
|
accessor="intervention.identifier",
|
||||||
|
)
|
||||||
|
t = tables.Column(
|
||||||
|
verbose_name=_("Title"),
|
||||||
|
orderable=True,
|
||||||
|
accessor="intervention.title",
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
THESE COLUMNS MIGHT NOT BE OF INTEREST. TO REDUCE TABLE WIDTH THEY CAN BE REMOVED
|
||||||
|
|
||||||
|
dila = tables.Column(
|
||||||
|
verbose_name=_("Licensing authority document identifier"),
|
||||||
|
orderable=True,
|
||||||
|
accessor="licensing_authority_document_identifier",
|
||||||
|
)
|
||||||
|
diro = tables.Column(
|
||||||
|
verbose_name=_("Registration office document identifier"),
|
||||||
|
orderable=True,
|
||||||
|
accessor="registration_office_document_identifier",
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
s = tables.Column(
|
||||||
|
verbose_name=_("Status"),
|
||||||
|
orderable=True,
|
||||||
|
accessor="state",
|
||||||
|
)
|
||||||
|
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 = _("Processes")
|
||||||
|
self.add_new_url = reverse("process:new")
|
||||||
|
|
||||||
|
def render_id(self, value, record: Process):
|
||||||
|
""" Renders the id column for an intervention
|
||||||
|
|
||||||
|
Args:
|
||||||
|
value (str): The identifier value
|
||||||
|
record (Process): The process record
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
html = ""
|
||||||
|
html += self.render_link(
|
||||||
|
tooltip=_("Open {}").format(_("Process")),
|
||||||
|
href=reverse("process:open", args=(record.id,)),
|
||||||
|
txt=value,
|
||||||
|
new_tab=False,
|
||||||
|
)
|
||||||
|
return format_html(html)
|
||||||
|
|
||||||
|
def render_s(self, value, record, column) -> str:
|
||||||
|
""" Translates the record state to a desired language
|
||||||
|
|
||||||
|
Args:
|
||||||
|
value (str): The value of state inside record
|
||||||
|
record (Process): The whole record itself
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str
|
||||||
|
"""
|
||||||
|
state = record.state
|
||||||
|
is_owner = record.created_by == self.request.user
|
||||||
|
choices = ProcessStateEnum.as_next_role_choices(self.request.user, state, is_owner)
|
||||||
|
valid_choices = [choice[0] for choice in choices]
|
||||||
|
if state in valid_choices:
|
||||||
|
form = ChoicesColumnForm(
|
||||||
|
action_url=reverse("process:edit-status", args=(record.id,)),
|
||||||
|
choices=choices,
|
||||||
|
initial={"select": state},
|
||||||
|
)
|
||||||
|
rendered = render_to_string("konova/choiceColumnForm.html", context={"form": form}, request=self.request)
|
||||||
|
else:
|
||||||
|
rendered = PROCESS_STATE_STRINGS.get(state)
|
||||||
|
return rendered
|
||||||
|
|
||||||
|
def render_ac(self, value, record):
|
||||||
|
"""
|
||||||
|
Renders possible actions for this record, such as delete.
|
||||||
|
"""
|
||||||
|
process = _("Process")
|
||||||
|
html = ""
|
||||||
|
html += self.render_open_btn(
|
||||||
|
_("Open {}").format(process),
|
||||||
|
reverse("process:open", args=(record.id,)),
|
||||||
|
)
|
||||||
|
html += self.render_edit_btn(
|
||||||
|
_("Edit {}").format(process),
|
||||||
|
reverse("process:edit", args=(record.id,)),
|
||||||
|
)
|
||||||
|
html += self.render_delete_btn(
|
||||||
|
_("Delete {}").format(process),
|
||||||
|
reverse("process:remove", args=(record.id,)),
|
||||||
|
)
|
||||||
|
return format_html(html)
|
95
process/templates/process/open.html
Normal file
95
process/templates/process/open.html
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load i18n static custom_tags %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
<div class="rows">
|
||||||
|
<div class="columns">
|
||||||
|
<div class="large-10 column">
|
||||||
|
<h3>{% trans 'Process' %}: {{process.intervention.identifier}}</h3>
|
||||||
|
<h4>{{process.intervention.title}}</h4>
|
||||||
|
</div>
|
||||||
|
<div class="large-2 column">
|
||||||
|
<a href="{% url 'process:edit' process.id %}">
|
||||||
|
<button class="button small" role="button" value="{% trans 'Edit' %}"><em class='fas fa-edit'></em> {% trans 'Edit' %}</button>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="rows">
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">{% trans 'Licencing Authority' %}</th>
|
||||||
|
<td>{{ process.licensing_authority }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">{% trans 'Licencing document identifier' %}</th>
|
||||||
|
<td>{{ process.licensing_authority_document_identifier }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">{% trans 'Registration office' %}</th>
|
||||||
|
<td>{{ process.registration_office }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">{% trans 'Registration document identifier' %}</th>
|
||||||
|
<td>{{ process.registration_office_document_identifier }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">{% trans 'Status' %}</th>
|
||||||
|
<td>
|
||||||
|
<span class="small info" title="{% trans 'Status' %}">
|
||||||
|
<em class="fas fa-exclamation-triangle"></em>
|
||||||
|
{{process.state|resolve_process_state}}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">{% trans 'Intervention' %}</th>
|
||||||
|
<td>
|
||||||
|
<a href="{% url 'intervention:open' process.intervention.id %}" target="_blank">
|
||||||
|
<button class="button small" role="button" title="{{process.intervention.title}} - {{process.intervention.type}}">{{process.intervention.identifier}}</button>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row" style="vertical-align: baseline">
|
||||||
|
{% trans 'Compensations' %}
|
||||||
|
<br>
|
||||||
|
<a href="{% url 'process:add-compensation' process.id %}" target="_blank">
|
||||||
|
<button class="button small" role="button" title="{% trans 'Add a new compensation' %}">
|
||||||
|
<em class="fas fa-plus-circle"></em>
|
||||||
|
{% trans 'New' %}
|
||||||
|
</button>
|
||||||
|
</a>
|
||||||
|
</th>
|
||||||
|
<td>
|
||||||
|
<table>
|
||||||
|
<tbody>
|
||||||
|
{% for compensation in process.compensations.all %}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<a href="{% url 'compensation:open' compensation.id %}" target="_blank">
|
||||||
|
<button class="button small" role="button" title="{{compensation.title}} - {{compensation.type}}">{{compensation.identifier}}</button>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td class="action-col">
|
||||||
|
<a href="{% url 'compensation:edit' compensation.id %}" title="{% trans 'Edit' %}"><button class="button small" title="{% trans 'Edit' %}"><em class='fas fa-edit'></em></button></a>
|
||||||
|
<a href="{% url 'compensation:remove' compensation.id %}" title="{% trans 'Remove' %}"><button class="button small" title="{% trans 'Remove' %}"><em class='fas fa-trash-alt'></em></button></a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% empty %}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
{% trans 'No compensation' %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
3
process/tests.py
Normal file
3
process/tests.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
22
process/urls.py
Normal file
22
process/urls.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
"""
|
||||||
|
Author: Michel Peltriaux
|
||||||
|
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||||
|
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||||
|
Created on: 25.11.20
|
||||||
|
|
||||||
|
"""
|
||||||
|
from django.urls import path
|
||||||
|
|
||||||
|
from process.views import index_view, new_view, open_view, edit_view, remove_view, edit_status_view, \
|
||||||
|
add_compensation_view
|
||||||
|
|
||||||
|
app_name = "process"
|
||||||
|
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('edit/<id>/status', edit_status_view, name='edit-status'),
|
||||||
|
path('remove/<id>', remove_view, name='remove'),
|
||||||
|
path('ac/<id>', add_compensation_view, name='add-compensation'),
|
||||||
|
]
|
217
process/views.py
Normal file
217
process/views.py
Normal file
@ -0,0 +1,217 @@
|
|||||||
|
from django.contrib import messages
|
||||||
|
from django.contrib.auth.decorators import login_required
|
||||||
|
from django.http import HttpRequest
|
||||||
|
from django.shortcuts import render, redirect, get_object_or_404
|
||||||
|
from django.urls import reverse
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
from compensation.models import Compensation
|
||||||
|
from konova.contexts import BaseContext
|
||||||
|
from konova.decorators import resolve_user_role, valid_process_role_required
|
||||||
|
from konova.forms import RemoveForm
|
||||||
|
from konova.utils.tables import ChoicesColumnForm
|
||||||
|
from process.enums import ProcessStateEnum
|
||||||
|
from process.forms import NewProcessForm, EditProcessForm
|
||||||
|
from process.models import Process
|
||||||
|
from process.settings import PROCESS_STATE_STRINGS
|
||||||
|
from process.tables import ProcessTable
|
||||||
|
|
||||||
|
# URL definitions
|
||||||
|
PROCESS_INDEX_URL = "process:index"
|
||||||
|
|
||||||
|
|
||||||
|
@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
|
||||||
|
processes = Process.get_role_objects(user)
|
||||||
|
table = ProcessTable(
|
||||||
|
request=request,
|
||||||
|
queryset=processes
|
||||||
|
)
|
||||||
|
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 process creation
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request (HttpRequest): The incoming request
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
template = "konova/form.html"
|
||||||
|
form = NewProcessForm(request.POST or None)
|
||||||
|
if request.method == "POST":
|
||||||
|
if form.is_valid():
|
||||||
|
process = form.save(request.user)
|
||||||
|
intervention = process.intervention
|
||||||
|
messages.info(request, _("A process is based on an intervention. Please fill in the missing data for this intervention"))
|
||||||
|
return redirect("intervention:edit", id=intervention.id)
|
||||||
|
else:
|
||||||
|
messages.error(request, _("Invalid input"))
|
||||||
|
else:
|
||||||
|
# For clarification: Nothing to do here
|
||||||
|
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 detail view for this process
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request (HttpRequest): The incoming request
|
||||||
|
id (str): The uuid id as string
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
template = "process/open.html"
|
||||||
|
process = get_object_or_404(Process, id=id)
|
||||||
|
context = {
|
||||||
|
"process": process,
|
||||||
|
}
|
||||||
|
context = BaseContext(request, context).context
|
||||||
|
return render(request, template, context)
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
@resolve_user_role
|
||||||
|
def edit_view(request: HttpRequest, id: str):
|
||||||
|
""" Renders an edit view for this process
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request (HttpRequest): The incoming request
|
||||||
|
id (str): The uuid id as string
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
template = "konova/form.html"
|
||||||
|
process = get_object_or_404(Process, id=id)
|
||||||
|
if request.method == "POST":
|
||||||
|
form = EditProcessForm(request.POST or None, instance=process, user=request.user)
|
||||||
|
if form.is_valid():
|
||||||
|
process = form.save(request.user)
|
||||||
|
messages.success(request, _("{} edited").format(process))
|
||||||
|
return redirect("process:index")
|
||||||
|
else:
|
||||||
|
messages.error(request, _("Invalid input"))
|
||||||
|
form = EditProcessForm(instance=process, user=request.user)
|
||||||
|
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"
|
||||||
|
process = get_object_or_404(Process, id=id)
|
||||||
|
if request.method == "POST":
|
||||||
|
form = RemoveForm(
|
||||||
|
request.POST or None,
|
||||||
|
object_to_remove=process,
|
||||||
|
remove_post_url=reverse("process:remove", args=(id,)),
|
||||||
|
cancel_url=reverse("process:index"),
|
||||||
|
)
|
||||||
|
if form.is_valid():
|
||||||
|
confirmed = form.is_checked()
|
||||||
|
if confirmed:
|
||||||
|
process.deactivate()
|
||||||
|
messages.success(request, _("Process {} removed").format(process))
|
||||||
|
return redirect("process:index")
|
||||||
|
else:
|
||||||
|
messages.error(request, _("Invalid input"))
|
||||||
|
|
||||||
|
form = RemoveForm(
|
||||||
|
object_to_remove=process,
|
||||||
|
remove_post_url=reverse("process:remove", args=(id,)),
|
||||||
|
cancel_url=reverse("process:index"),
|
||||||
|
)
|
||||||
|
context = {
|
||||||
|
"form": form,
|
||||||
|
}
|
||||||
|
context = BaseContext(request, context).context
|
||||||
|
return render(request, template, context)
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def edit_status_view(request: HttpRequest, id: str):
|
||||||
|
""" Changes only the status of a process
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request (The incoming request):
|
||||||
|
id ():
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
process = get_object_or_404(Process, id=id)
|
||||||
|
old_state = process.state
|
||||||
|
form = ChoicesColumnForm(request.POST or None, choices=ProcessStateEnum.as_choices(drop_empty_choice=True))
|
||||||
|
if request.method == "POST":
|
||||||
|
if form.is_valid():
|
||||||
|
process.state = int(form.cleaned_data.get("select", -1))
|
||||||
|
process.save()
|
||||||
|
_from = PROCESS_STATE_STRINGS.get(old_state)
|
||||||
|
_to = PROCESS_STATE_STRINGS.get(process.state)
|
||||||
|
messages.info(request, _("{} status changed from {} to {}").format(process.intervention.title, _from, _to))
|
||||||
|
else:
|
||||||
|
messages.error(request, form.errors)
|
||||||
|
return redirect("process:index")
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def add_compensation_view(request: HttpRequest, id: str):
|
||||||
|
""" Adds a new compensation to a process
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request (HttpRequest): The incoming request
|
||||||
|
id (str): The process' id
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
process = get_object_or_404(Process, id=id)
|
||||||
|
comp = Compensation()
|
||||||
|
comp.process = process
|
||||||
|
comp.save()
|
||||||
|
messages.info(request, _("Please fill in the data for this compensation"))
|
||||||
|
return redirect("compensation:edit", id=comp.id)
|
||||||
|
#template = ""
|
||||||
|
#context = {}
|
||||||
|
#context = BaseContext(request, context).context
|
||||||
|
#return render(request, template, context)
|
20
requirements.txt
Normal file
20
requirements.txt
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
asgiref==3.3.1
|
||||||
|
certifi==2020.11.8
|
||||||
|
chardet==3.0.4
|
||||||
|
Django==3.1.3
|
||||||
|
django-autocomplete-light==3.8.1
|
||||||
|
django-debug-toolbar==3.1.1
|
||||||
|
django-filter==2.4.0
|
||||||
|
django-fontawesome-5==1.0.18
|
||||||
|
django-simple-sso==0.14.1
|
||||||
|
django-tables2==2.3.3
|
||||||
|
idna==2.10
|
||||||
|
itsdangerous==1.1.0
|
||||||
|
pkg-resources==0.0.0
|
||||||
|
psycopg2==2.8.6
|
||||||
|
pytz==2020.4
|
||||||
|
requests==2.25.0
|
||||||
|
six==1.15.0
|
||||||
|
sqlparse==0.4.1
|
||||||
|
urllib3==1.26.2
|
||||||
|
webservices==0.7
|
22
templates/anonymous-user-navbar.html
Normal file
22
templates/anonymous-user-navbar.html
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{% load i18n %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="small-12 columns menu-container">
|
||||||
|
<div class="mobile-menu">
|
||||||
|
<ul>
|
||||||
|
<li class="menu-trigger">
|
||||||
|
<button>{% trans 'Menu' %}</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<ul class="dkd_mm_section_list" data-level="1">
|
||||||
|
<li class="dkd_mm_entry">
|
||||||
|
<span class="dkd_mm_link" role=button>
|
||||||
|
<a href="{% url 'home' %}" target="_self" title="{% trans 'Home' %}">
|
||||||
|
{% trans 'Home' %}
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
128
templates/authenticated-user-navbar.html
Normal file
128
templates/authenticated-user-navbar.html
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="small-12 columns menu-container">
|
||||||
|
<div class="mobile-menu">
|
||||||
|
<ul>
|
||||||
|
<li class="menu-trigger">
|
||||||
|
<button>{% trans 'Menu' %}</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<ul class="dkd_mm_section_list" data-level="1">
|
||||||
|
<li class="dkd_mm_entry">
|
||||||
|
<span class="dkd_mm_link" role=button><a href="{% url 'home' %}" target="_self">{% trans 'Home' %}</a></span>
|
||||||
|
</li>
|
||||||
|
<li class="dkd_mm_entry dkd_mm_sub_link">
|
||||||
|
<span class="dkd_mm_link" role=button><a href="{% url 'process:index' %}" target="_self">{% trans 'Process' %}</a></span>
|
||||||
|
<ul class="dkd_mm_section_list" data-level="2">
|
||||||
|
<li class="dkd_mm_section_title">
|
||||||
|
<span class="dkd_mm_section_title_link">{% trans 'Process management' %}</span>
|
||||||
|
</li>
|
||||||
|
<li class="dkd_mm_entry">
|
||||||
|
<span class="dkd_mm_link" role=button><a href="{% url 'process:index' %}" target="_self">{% trans 'Show process' %}</a></span>
|
||||||
|
</li>
|
||||||
|
<li class="dkd_mm_entry">
|
||||||
|
<span class="dkd_mm_link" role=button><a href="{% url 'process:new' %}" target="_self">{% trans 'New process' %}</a></span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li class="dkd_mm_entry dkd_mm_sub_link">
|
||||||
|
<span class="dkd_mm_link" role=button><a href="{% url 'intervention:index' %}" target="_self">{% trans 'Intervention' %}</a></span>
|
||||||
|
<ul class="dkd_mm_section_list" data-level="2">
|
||||||
|
<li class="dkd_mm_section_title">
|
||||||
|
<span class="dkd_mm_section_title_link">{% trans 'Intervention management' %}</span>
|
||||||
|
</li>
|
||||||
|
<li class="dkd_mm_entry">
|
||||||
|
<span class="dkd_mm_link" role=button><a href="{% url 'intervention:index' %}" target="_self">{% trans 'Show intervention' %}</a></span>
|
||||||
|
</li>
|
||||||
|
<li class="dkd_mm_entry">
|
||||||
|
<span class="dkd_mm_link" role=button><a href="{% url 'intervention:new' %}" target="_self">{% trans 'New intervention' %}</a></span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li class="dkd_mm_entry dkd_mm_sub_link">
|
||||||
|
<span class="dkd_mm_link" role=button><a href="{% url 'compensation:index' %}" target="_self">{% trans 'Compensation' %}</a></span>
|
||||||
|
<ul class="dkd_mm_section_list" data-level="2">
|
||||||
|
<li class="dkd_mm_section_title">
|
||||||
|
<span class="dkd_mm_section_title_link">{% trans 'Compensation management' %}</span>
|
||||||
|
</li>
|
||||||
|
<li class="dkd_mm_entry">
|
||||||
|
<span class="dkd_mm_link" role=button><a href="{% url 'compensation:index' %}" target="_self">{% trans 'Show compensation' %}</a></span>
|
||||||
|
</li>
|
||||||
|
<li class="dkd_mm_entry">
|
||||||
|
<span class="dkd_mm_link" role=button><a href="{% url 'compensation:new' %}" target="_self">{% trans 'New compensation' %}</a></span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li class="dkd_mm_entry dkd_mm_sub_link">
|
||||||
|
<span class="dkd_mm_link" role=button><a href="{% url 'compensation:account-index' %}" target="_self">{% trans 'Eco-account' %}</a></span>
|
||||||
|
<ul class="dkd_mm_section_list" data-level="2">
|
||||||
|
<li class="dkd_mm_section_title">
|
||||||
|
<span class="dkd_mm_section_title_link">{% trans 'Eco-account management' %}</span>
|
||||||
|
</li>
|
||||||
|
<li class="dkd_mm_entry">
|
||||||
|
<span class="dkd_mm_link" role=button><a href="{% url 'compensation:account-index' %}" target="_self">{% trans 'Show eco-accounts' %}</a></span>
|
||||||
|
</li>
|
||||||
|
<li class="dkd_mm_entry">
|
||||||
|
<span class="dkd_mm_link" role=button><a href="{% url 'compensation:account-new' %}" target="_self">{% trans 'New eco-account' %}</a></span>
|
||||||
|
</li>
|
||||||
|
<li class="dkd_mm_entry">
|
||||||
|
<span class="dkd_mm_link" role=button><a href="{% url 'compensation:account-index' %}" target="_self">{% trans 'Withdraw from eco-account' %}</a></span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li class="dkd_mm_entry dkd_mm_sub_link">
|
||||||
|
<span class="dkd_mm_link" role=button><a href="{% url 'compensation:index' %}" target="_self">EMA Alte Rechtslage</a></span>
|
||||||
|
<ul class="dkd_mm_section_list" data-level="2">
|
||||||
|
<li class="dkd_mm_section_title">
|
||||||
|
<span class="dkd_mm_section_title_link">Maßnahmen aus Ersatzzahlungen - alte Rechtslage (EMA)</span>
|
||||||
|
</li>
|
||||||
|
<li class="dkd_mm_entry">
|
||||||
|
<span class="dkd_mm_link" role=button><a href="{% url 'compensation:index' %}" target="_self">{% trans 'Show actions' %}</a></span>
|
||||||
|
</li>
|
||||||
|
<li class="dkd_mm_entry">
|
||||||
|
<span class="dkd_mm_link" role=button><a href="{% url 'compensation:new' %}" target="_self">{% trans 'New action' %}</a></span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li class="dkd_mm_entry dkd_mm_sub_link">
|
||||||
|
<span class="dkd_mm_link" role=button><a href="{% url 'compensation:index' %}" target="_self">{% trans 'Organization' %}</a></span>
|
||||||
|
<ul class="dkd_mm_section_list" data-level="2">
|
||||||
|
<li class="dkd_mm_section_title">
|
||||||
|
<span class="dkd_mm_section_title_link">{% trans 'Organization' %}</span>
|
||||||
|
</li>
|
||||||
|
<li class="dkd_mm_entry">
|
||||||
|
<span class="dkd_mm_link" role=button><a href="" target="_self">{% trans 'Import / Export' %}</a></span>
|
||||||
|
</li>
|
||||||
|
<li class="dkd_mm_entry">
|
||||||
|
<span class="dkd_mm_link" role=button><a href="" target="_self">{% trans 'Annual report' %}</a></span>
|
||||||
|
</li>
|
||||||
|
<li class="dkd_mm_entry">
|
||||||
|
<span class="dkd_mm_link" role=button><a href="" target="_self">{% trans 'Settings' %}</a></span>
|
||||||
|
</li>
|
||||||
|
<li class="dkd_mm_entry">
|
||||||
|
<span class="dkd_mm_link" role=button><a href="{{ wiki_url }}" target="_blank">{% trans 'Help' %}</a></span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li class="dkd_mm_entry dkd_mm_sub_link">
|
||||||
|
<span class="dkd_mm_link" role=button><a href="{% url 'compensation:index' %}" target="_self">{% trans 'User' %}: {{ user }}</a></span>
|
||||||
|
<ul class="dkd_mm_section_list" data-level="2">
|
||||||
|
<li class="dkd_mm_section_title">
|
||||||
|
<span class="dkd_mm_section_title_link">{% trans 'User' %}: {{ user }}</span>
|
||||||
|
</li>
|
||||||
|
<li class="dkd_mm_entry">
|
||||||
|
<span class="dkd_mm_link" role=button><a href="{% url 'logout' %}" target="_self">{% trans 'Logout' %}</a></span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="user-role">
|
||||||
|
{% trans 'You are currently working as ' %}
|
||||||
|
<strong>{{ current_role.type }} ({{ current_role.org }})</strong>
|
||||||
|
<a href="{% url 'home' %}">{% trans 'Change...' %}</a>
|
||||||
|
</div>
|
100
templates/base.html
Normal file
100
templates/base.html
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
{% load static i18n fontawesome_5 %}
|
||||||
|
<html lang="{{ language }}">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>{{ base_title }}</title>
|
||||||
|
<link rel="stylesheet" href="{% static 'css/messages.css' %}">
|
||||||
|
<link rel="stylesheet" href="{% static 'css/mulewf.css' %}">
|
||||||
|
<link rel="stylesheet" href="{% static 'css/konova.css' %}">
|
||||||
|
<script type="text/javascript" src="{% static 'js/jquery-3.5.1.min.js' %}"></script>
|
||||||
|
<script type="text/javascript" src="{% static 'js/jquery-ui.min.js' %}"></script>
|
||||||
|
<script type="text/javascript" src="{% static 'js/mulewf.min.js' %}"></script>
|
||||||
|
{% fontawesome_5_static %}
|
||||||
|
{% block head %}
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{% if render_header %}
|
||||||
|
<header class="header">
|
||||||
|
<div class="background-wrap gray-600">
|
||||||
|
<div class="row logo-search-area">
|
||||||
|
<div class="small-6 medium-4 columns">
|
||||||
|
<h3>{{ base_frontend_title }}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="medium-4 columns hide-for-small-only"></div>
|
||||||
|
<div class="small-6 medium-4 columns last">
|
||||||
|
<a href="{% url 'home' %}">
|
||||||
|
<img class="logo" alt="{% trans 'Home' %}"
|
||||||
|
title="{% trans 'Home' %}"
|
||||||
|
src="{% static 'images/rlp-logos-MUEEF.png' %}"/>
|
||||||
|
<noscript>
|
||||||
|
<img title="rlp" alt="rlp-logo" src="{% static 'images/rlp-logos-MUEEF.png' %}" width="192" height="84" />
|
||||||
|
</noscript>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
{% endif %}
|
||||||
|
<div class="body-content">
|
||||||
|
|
||||||
|
<nav class="main-menu static" role="navigation">
|
||||||
|
{% block navbar %}
|
||||||
|
{% if user.is_authenticated %}
|
||||||
|
{% include 'authenticated-user-navbar.html' %}
|
||||||
|
{% else %}
|
||||||
|
{% include 'anonymous-user-navbar.html' %}
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
|
</nav>
|
||||||
|
<div class="row">
|
||||||
|
<div class="large-12 columns">
|
||||||
|
{% for message in messages %}
|
||||||
|
<div class="{{ message.tags }}">
|
||||||
|
{{ message }}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="small-12 columns">
|
||||||
|
<div class="column" style="margin-top: 50px;">
|
||||||
|
{% block body %}
|
||||||
|
<div class="small-12 medium-3 columns">
|
||||||
|
{% block body_left %}
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
</div>
|
||||||
|
<div class="small-12 medium-6 columns">
|
||||||
|
{% block body_middle %}
|
||||||
|
{% endblock %}
|
||||||
|
</div>
|
||||||
|
<div class="small-12 medium-3 columns">
|
||||||
|
{% block body_right %}
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<footer class="footer">
|
||||||
|
{% block footer %}
|
||||||
|
<div class="background-wrap gray-500 line"></div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="large-6 medium-6 columns">
|
||||||
|
<h2 class="h6">{% trans 'About this site' %}</h2>
|
||||||
|
<ul class="no-bullet">
|
||||||
|
<li><a href="" target="_self">Impressum</a></li>
|
||||||
|
<li><a href="" target="_self">{% trans 'Privacy policy' %}</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
</footer>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
|
||||||
|
</html>
|
7
templates/generic_index.html
Normal file
7
templates/generic_index.html
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
<div class="style-2">
|
||||||
|
{% include 'table.html' %}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
54
templates/generic_table_form.html
Normal file
54
templates/generic_table_form.html
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block head %}
|
||||||
|
{{ form.media }}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
<div class="table-container">
|
||||||
|
<h4>
|
||||||
|
{{ form.form_title }}
|
||||||
|
</h4>
|
||||||
|
{% if form.form_caption is not None %}
|
||||||
|
<div>
|
||||||
|
{{ form.form_caption }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<form method="post" action="{{ form.action_url }}">
|
||||||
|
{% csrf_token %}
|
||||||
|
<table>
|
||||||
|
<tbody>
|
||||||
|
{% for field in form %}
|
||||||
|
<tr title="{{ field.help_text }}" class="{% if field.errors %}error{% endif %}">
|
||||||
|
<th scope="row" class="small-3">
|
||||||
|
<div>{{ field.label }}<span class="label-required">{% if field.field.required %}*{% endif %}</span></div>
|
||||||
|
<small>{{ field.help_text }}</small>
|
||||||
|
</th>
|
||||||
|
<td class="small-12">
|
||||||
|
{{ field }}
|
||||||
|
{% for error in field.errors %}
|
||||||
|
<b>{{ error }}</b>
|
||||||
|
{% endfor %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div class="label-required">{% trans 'Fields with * are required.' %}</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="large-1 columns">
|
||||||
|
<a href="{{ form.cancel_redirect }}">
|
||||||
|
<button class="button small append-value" type="button" title="{% trans 'Cancel' %}">{% trans 'Cancel' %}</button>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="large-10 columns">
|
||||||
|
<button class="button small append-value right" type="submit" title="{% trans 'Save' %}">{% trans 'Save' %}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block footer %}
|
||||||
|
{{ form.media }}
|
||||||
|
{% endblock %}
|
44
templates/table.html
Normal file
44
templates/table.html
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
{% load django_tables2 %}
|
||||||
|
{% load i18n static %}
|
||||||
|
<div class="rows">
|
||||||
|
{% if table.title is not None %}
|
||||||
|
<div class="rows">
|
||||||
|
<h3>
|
||||||
|
{{ table.title }}
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<div class="column large-2">
|
||||||
|
{% if table.user.is_superuser and table.add_new_entries %}
|
||||||
|
<a href="{{ table.add_new_url }}">
|
||||||
|
<button class="button small" title="{% trans 'New entry' %}">
|
||||||
|
<i class="fas fa-plus"></i>
|
||||||
|
{% trans 'New' %}
|
||||||
|
</button>
|
||||||
|
</a>
|
||||||
|
{% else %}
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="column large-8">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="column large-2 dropdown-area">
|
||||||
|
<form method="get">
|
||||||
|
{{ table.filter.form.as_p }}
|
||||||
|
</form>
|
||||||
|
<div class="header-meta-dropdown">
|
||||||
|
<button data-dropdown="rpp-choice" aria-controls="rpp-choice" aria-expanded="false" class="custom-dropdown left">{% trans 'Results per page' %}</button>
|
||||||
|
<ul id="rpp-choice" class="custom-dropdown-content" data-dropdown-content aria-hidden="true" style="position: absolute; left: -99999px; top: 25px; right: auto;">
|
||||||
|
{% for rpp_option in table.results_per_page_choices %}
|
||||||
|
<li class="{% if table.results_per_page_chosen == rpp_option %}selected{% endif %}">
|
||||||
|
<a class="" href="{% querystring table.results_per_page_parameter=rpp_option %}">{{ rpp_option }}</a>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
{% render_table table %}
|
||||||
|
</div>
|
Loading…
Reference in New Issue
Block a user