Refactoring to konova

pull/2/head
mipel 3 years ago
parent a5e8bcfa8c
commit 4084373e2b

@ -5,14 +5,13 @@ 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.models import BaseObject, BaseResource, Geometry
from konova.utils.generators import generate_random_string
from process.models import Process
from organisation.models import Organisation
class CompensationControl(BaseResource):
@ -22,7 +21,7 @@ class CompensationControl(BaseResource):
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)
by_authority = models.ForeignKey(Organisation, null=True, blank=True, on_delete=models.SET_NULL)
comment = models.TextField()
@ -31,8 +30,7 @@ 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)
surface = models.FloatField()
class CompensationAction(BaseResource):
@ -50,22 +48,21 @@ 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)
registration_office = models.ForeignKey(Organisation, on_delete=models.SET_NULL, null=True)
conservation_office = models.ForeignKey(Organisation, on_delete=models.SET_NULL, null=True)
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!
before_states = models.ManyToManyField(CompensationState, blank=True, related_name='+')
after_states = models.ManyToManyField(CompensationState, blank=True, related_name='+')
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)
geometry = models.ForeignKey(Geometry, null=True, blank=True, on_delete=models.SET_NULL)
documents = models.ManyToManyField("konova.Document", blank=True)
@staticmethod
def __generate_new_identifier() -> str:
@ -92,36 +89,8 @@ class Compensation(BaseObject):
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):
class EcoAccount(Compensation):
"""
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.
@ -129,17 +98,4 @@ class EcoAccount(BaseResource):
'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()
handler = models.CharField(max_length=500, null=True, blank=True, help_text="Who is responsible for handling the actions")

@ -9,7 +9,6 @@ from konova.decorators import *
@login_required
@resolve_user_role
def index_view(request: HttpRequest):
"""
Renders the index view for compensation
@ -22,7 +21,7 @@ def index_view(request: HttpRequest):
"""
template = "generic_index.html"
user = request.user
compensations = Compensation.get_role_objects(user)
compensations = None # ToDo
table = CompensationTable(
request=request,
queryset=compensations

@ -7,11 +7,11 @@ class InterventionAdmin(admin.ModelAdmin):
list_display = [
"id",
"title",
"type",
"process_type",
"handler",
"created_on",
"is_active",
"is_deleted",
"created_on",
"deleted_on",
]

@ -10,10 +10,9 @@ 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.models import BaseObject, Geometry
from konova.utils.generators import generate_random_string
from organisation.enums import RoleTypeEnum
from process.models import Process
from organisation.models import Organisation
class Intervention(BaseObject):
@ -22,21 +21,30 @@ class Intervention(BaseObject):
A process consists of exactly one intervention and one or more compensation
"""
type = models.CharField(max_length=500, null=True, blank=True)
registration_office = models.ForeignKey(Organisation, on_delete=models.SET_NULL, null=True)
registration_file_number = models.CharField(max_length=1000, blank=True, null=True)
registration_date = models.DateTimeField(null=True, blank=True)
conservation_office = models.ForeignKey(Organisation, on_delete=models.SET_NULL, null=True)
conservations_file_number = models.CharField(max_length=1000, blank=True, null=True)
process_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")
geometry = models.ForeignKey(Geometry, null=True, blank=True, on_delete=models.SET_NULL)
documents = models.ManyToManyField("konova.Document", blank=True)
# Refers to "verzeichnen"
recorded_on = models.DateTimeField(default=None)
recorded_by = models.ForeignKey(User, null=True, blank=True, on_delete=models.SET_NULL)
# Holds which intervention is simply a newer version of this dataset
next_version = models.ForeignKey("Intervention", null=True, on_delete=models.DO_NOTHING)
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
@ -62,30 +70,4 @@ class Intervention(BaseObject):
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
super().save(*args, **kwargs)

@ -15,7 +15,7 @@ from process.models import Process
@login_required
@resolve_user_role
def index_view(request: HttpRequest):
"""
Renders the index view for process
@ -28,7 +28,7 @@ def index_view(request: HttpRequest):
"""
template = "generic_index.html"
user = request.user
interventions = Intervention.get_role_objects(user)
interventions = Intervention # ToDo
table = InterventionTable(
request=request,
queryset=interventions

@ -7,9 +7,7 @@ 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
from konova.sub_settings.context_settings import BASE_TITLE, WIKI_URL, BASE_FRONTEND_TITLE
class BaseContext:
@ -22,7 +20,6 @@ class BaseContext:
"language": "en",
"wiki_url": WIKI_URL,
"user": None,
"render_header": RENDER_HEADER,
"current_role": None,
}
@ -33,21 +30,3 @@ class BaseContext:
# 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

@ -0,0 +1,44 @@
"""
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.shortcuts import redirect
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
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

@ -13,7 +13,6 @@ 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"
@ -27,7 +26,6 @@ class Command(BaseCommand):
with transaction.atomic():
self.__init_superuser()
self.__init_test_organisation()
self.__init_role_types()
self.__init_role_groups()
except KeyboardInterrupt:
self.__break_line()
@ -76,28 +74,6 @@ class Command(BaseCommand):
)
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

@ -5,32 +5,27 @@ 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,
},
]
@ -38,21 +33,17 @@ 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,
},
]

@ -7,11 +7,9 @@ Created on: 17.11.20
"""
import uuid
from django.contrib.auth.models import User, Group
from django.contrib.auth.models import User
from django.contrib.gis.db.models import MultiPolygonField
from django.db import models
from django.db.models import QuerySet
from organisation.enums import RoleTypeEnum
class BaseResource(models.Model):
@ -22,8 +20,6 @@ class BaseResource(models.Model):
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)
@ -39,6 +35,9 @@ class BaseObject(BaseResource):
"""
identifier = models.CharField(max_length=1000, null=True, blank=True)
title = models.CharField(max_length=1000, null=True, blank=True)
deleted_on = models.DateTimeField(null=True)
deleted_by = models.ForeignKey(User, null=True, on_delete=models.SET_NULL)
comment = models.TextField()
class Meta:
abstract = True
@ -64,41 +63,8 @@ class Document(BaseResource):
comment = models.TextField()
class RoleType(BaseResource):
class Geometry(BaseResource):
"""
Defines different role types
Outsourced geometry model so multiple versions of the same object can refer to the same geometry if it is not changed
"""
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
)
geom = MultiPolygonField(null=True, blank=True)

Before

Width:  |  Height:  |  Size: 6.3 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Before

Width:  |  Height:  |  Size: 935 B

After

Width:  |  Height:  |  Size: 935 B

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

@ -9,4 +9,3 @@ 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

@ -56,6 +56,7 @@ INSTALLED_APPS = [
'simple_sso.sso_server',
'django_tables2',
'fontawesome_5',
'bootstrap4',
'konova',
'compensation',
'intervention',

@ -28,7 +28,6 @@ urlpatterns = [
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")),

@ -43,25 +43,6 @@ def home_view(request: HttpRequest):
"""
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,
}
additional_context = {}
context = BaseContext(request, additional_context).context
return render(request, template, context)

@ -1,94 +0,0 @@
"""
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

@ -1,57 +0,0 @@
.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;
}

@ -1,43 +0,0 @@
.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');
*/
}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -6,21 +6,3 @@ 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"

@ -1,7 +1,6 @@
from django.db import models
from konova.models import BaseResource
from organisation.enums import OrganisationTypeEnum
class Organisation(BaseResource):
@ -12,7 +11,6 @@ class Organisation(BaseResource):
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
return self.name

@ -6,17 +6,3 @@ 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"),
}

@ -1,38 +0,0 @@
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)

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

@ -1,202 +0,0 @@
"""
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,
}
}

@ -1,218 +0,0 @@
"""
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

@ -1,158 +0,0 @@
"""
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

@ -1,19 +0,0 @@
"""
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"),
}

@ -1,130 +0,0 @@
"""
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)

@ -1,95 +0,0 @@
{% 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 %}

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

@ -1,22 +0,0 @@
"""
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'),
]

@ -1,217 +0,0 @@
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)

@ -1,20 +1,25 @@
asgiref==3.3.1
beautifulsoup4==4.9.3
certifi==2020.11.8
chardet==3.0.4
Django==3.1.3
django-autocomplete-light==3.8.1
django-bootstrap4==3.0.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
importlib-metadata==2.1.1
itsdangerous==1.1.0
pkg-resources==0.0.0
psycopg2==2.8.6
pytz==2020.4
requests==2.25.0
six==1.15.0
soupsieve==2.2.1
sqlparse==0.4.1
urllib3==1.26.2
webservices==0.7
zipp==3.4.1

@ -4,12 +4,8 @@
<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>
{% bootstrap_css %}
{% bootstrap_javascript jquery='full' %}
{% fontawesome_5_static %}
{% block head %}

Loading…
Cancel
Save