Refactoring to konova
@ -5,14 +5,13 @@ Contact: michel.peltriaux@sgdnord.rlp.de
|
|||||||
Created on: 17.11.20
|
Created on: 17.11.20
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from django.contrib.auth.models import User
|
|
||||||
from django.contrib.gis.db import models
|
from django.contrib.gis.db import models
|
||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
|
|
||||||
from compensation.settings import COMPENSATION_IDENTIFIER_LENGTH, COMPENSATION_IDENTIFIER_TEMPLATE
|
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 konova.utils.generators import generate_random_string
|
||||||
from process.models import Process
|
from organisation.models import Organisation
|
||||||
|
|
||||||
|
|
||||||
class CompensationControl(BaseResource):
|
class CompensationControl(BaseResource):
|
||||||
@ -22,7 +21,7 @@ class CompensationControl(BaseResource):
|
|||||||
deadline = models.ForeignKey("konova.Deadline", on_delete=models.SET_NULL, null=True, blank=True)
|
deadline = models.ForeignKey("konova.Deadline", on_delete=models.SET_NULL, null=True, blank=True)
|
||||||
type = models.CharField(max_length=500, 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")
|
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()
|
comment = models.TextField()
|
||||||
|
|
||||||
|
|
||||||
@ -31,8 +30,7 @@ class CompensationState(models.Model):
|
|||||||
Compensations must define the state of an area before and after the compensation.
|
Compensations must define the state of an area before and after the compensation.
|
||||||
"""
|
"""
|
||||||
biotope_type = models.CharField(max_length=500, null=True, blank=True)
|
biotope_type = models.CharField(max_length=500, null=True, blank=True)
|
||||||
amount = models.FloatField()
|
surface = models.FloatField()
|
||||||
unit = models.CharField(max_length=100, null=True, blank=True)
|
|
||||||
|
|
||||||
|
|
||||||
class CompensationAction(BaseResource):
|
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
|
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.
|
of this, which legal authority is the point of contact, and so on.
|
||||||
"""
|
"""
|
||||||
is_old_law = models.BooleanField(default=False)
|
registration_office = models.ForeignKey(Organisation, on_delete=models.SET_NULL, null=True)
|
||||||
type = models.CharField(max_length=500, null=True, blank=True)
|
conservation_office = models.ForeignKey(Organisation, on_delete=models.SET_NULL, null=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!
|
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!
|
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)
|
actions = models.ManyToManyField(CompensationAction)
|
||||||
|
|
||||||
deadline_creation = models.ForeignKey("konova.Deadline", on_delete=models.SET_NULL, null=True, blank=True, related_name="deadline_creation")
|
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")
|
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):
|
geometry = models.ForeignKey(Geometry, null=True, blank=True, on_delete=models.SET_NULL)
|
||||||
return "{} of {}".format(self.type, self.process)
|
documents = models.ManyToManyField("konova.Document", blank=True)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def __generate_new_identifier() -> str:
|
def __generate_new_identifier() -> str:
|
||||||
@ -92,36 +89,8 @@ class Compensation(BaseObject):
|
|||||||
self.identifier = new_id
|
self.identifier = new_id
|
||||||
super().save(*args, **kwargs)
|
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
|
class EcoAccount(Compensation):
|
||||||
* 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
|
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.
|
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
|
'Withdrawing' can only be applied by shrinking the size of the available geometry and declaring the withdrawed
|
||||||
geometry as a compensation for a process.
|
geometry as a compensation for a process.
|
||||||
"""
|
"""
|
||||||
is_old_law = models.BooleanField(default=False)
|
handler = models.CharField(max_length=500, null=True, blank=True, help_text="Who is responsible for handling the actions")
|
||||||
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,7 +9,6 @@ from konova.decorators import *
|
|||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@resolve_user_role
|
|
||||||
def index_view(request: HttpRequest):
|
def index_view(request: HttpRequest):
|
||||||
"""
|
"""
|
||||||
Renders the index view for compensation
|
Renders the index view for compensation
|
||||||
@ -22,7 +21,7 @@ def index_view(request: HttpRequest):
|
|||||||
"""
|
"""
|
||||||
template = "generic_index.html"
|
template = "generic_index.html"
|
||||||
user = request.user
|
user = request.user
|
||||||
compensations = Compensation.get_role_objects(user)
|
compensations = None # ToDo
|
||||||
table = CompensationTable(
|
table = CompensationTable(
|
||||||
request=request,
|
request=request,
|
||||||
queryset=compensations
|
queryset=compensations
|
||||||
|
@ -7,11 +7,11 @@ class InterventionAdmin(admin.ModelAdmin):
|
|||||||
list_display = [
|
list_display = [
|
||||||
"id",
|
"id",
|
||||||
"title",
|
"title",
|
||||||
"type",
|
"process_type",
|
||||||
"handler",
|
"handler",
|
||||||
"created_on",
|
|
||||||
"is_active",
|
"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 django.utils.timezone import now
|
||||||
|
|
||||||
from intervention.settings import INTERVENTION_IDENTIFIER_LENGTH, INTERVENTION_IDENTIFIER_TEMPLATE
|
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 konova.utils.generators import generate_random_string
|
||||||
from organisation.enums import RoleTypeEnum
|
from organisation.models import Organisation
|
||||||
from process.models import Process
|
|
||||||
|
|
||||||
|
|
||||||
class Intervention(BaseObject):
|
class Intervention(BaseObject):
|
||||||
@ -22,21 +21,30 @@ class Intervention(BaseObject):
|
|||||||
|
|
||||||
A process consists of exactly one intervention and one or more compensation
|
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)
|
law = models.CharField(max_length=500, null=True, blank=True)
|
||||||
handler = 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)
|
geometry = models.ForeignKey(Geometry, null=True, blank=True, on_delete=models.SET_NULL)
|
||||||
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)
|
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):
|
def __str__(self):
|
||||||
return "{} by {}".format(self.type, self.handler)
|
return "{} by {}".format(self.type, self.handler)
|
||||||
|
|
||||||
def delete(self, *args, **kwargs):
|
def delete(self, *args, **kwargs):
|
||||||
if self.process is not None:
|
|
||||||
self.process.delete()
|
|
||||||
super().delete(*args, **kwargs)
|
super().delete(*args, **kwargs)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -63,29 +71,3 @@ class Intervention(BaseObject):
|
|||||||
new_id = self.__generate_new_identifier()
|
new_id = self.__generate_new_identifier()
|
||||||
self.identifier = new_id
|
self.identifier = new_id
|
||||||
super().save(*args, **kwargs)
|
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
|
|
@ -15,7 +15,7 @@ from process.models import Process
|
|||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@resolve_user_role
|
|
||||||
def index_view(request: HttpRequest):
|
def index_view(request: HttpRequest):
|
||||||
"""
|
"""
|
||||||
Renders the index view for process
|
Renders the index view for process
|
||||||
@ -28,7 +28,7 @@ def index_view(request: HttpRequest):
|
|||||||
"""
|
"""
|
||||||
template = "generic_index.html"
|
template = "generic_index.html"
|
||||||
user = request.user
|
user = request.user
|
||||||
interventions = Intervention.get_role_objects(user)
|
interventions = Intervention # ToDo
|
||||||
table = InterventionTable(
|
table = InterventionTable(
|
||||||
request=request,
|
request=request,
|
||||||
queryset=interventions
|
queryset=interventions
|
||||||
|
@ -7,9 +7,7 @@ Created on: 16.11.20
|
|||||||
"""
|
"""
|
||||||
from django.http import HttpRequest
|
from django.http import HttpRequest
|
||||||
|
|
||||||
from konova.models import RoleGroup
|
from konova.sub_settings.context_settings import BASE_TITLE, WIKI_URL, BASE_FRONTEND_TITLE
|
||||||
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:
|
class BaseContext:
|
||||||
@ -22,7 +20,6 @@ class BaseContext:
|
|||||||
"language": "en",
|
"language": "en",
|
||||||
"wiki_url": WIKI_URL,
|
"wiki_url": WIKI_URL,
|
||||||
"user": None,
|
"user": None,
|
||||||
"render_header": RENDER_HEADER,
|
|
||||||
"current_role": None,
|
"current_role": None,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -33,21 +30,3 @@ class BaseContext:
|
|||||||
|
|
||||||
# Add additional context, derived from given parameters
|
# Add additional context, derived from given parameters
|
||||||
self.context.update(additional_context)
|
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
|
|
44
konova/decorators.py
Normal file
@ -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.management.commands.setup_test_data import TEST_ORGANISATION_DATA, TEST_ROLE_GROUPS_DATA
|
||||||
from konova.models import RoleType, RoleGroup
|
from konova.models import RoleType, RoleGroup
|
||||||
from organisation.enums import RoleTypeEnum
|
|
||||||
from organisation.models import Organisation
|
from organisation.models import Organisation
|
||||||
|
|
||||||
CREATED_TEMPLATE = "{} created"
|
CREATED_TEMPLATE = "{} created"
|
||||||
@ -27,7 +26,6 @@ class Command(BaseCommand):
|
|||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
self.__init_superuser()
|
self.__init_superuser()
|
||||||
self.__init_test_organisation()
|
self.__init_test_organisation()
|
||||||
self.__init_role_types()
|
|
||||||
self.__init_role_groups()
|
self.__init_role_groups()
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
self.__break_line()
|
self.__break_line()
|
||||||
@ -76,28 +74,6 @@ class Command(BaseCommand):
|
|||||||
)
|
)
|
||||||
self.__break_line()
|
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):
|
def __init_test_organisation(self):
|
||||||
""" Creates test organisations from predefined data
|
""" Creates test organisations from predefined data
|
||||||
|
|
@ -5,32 +5,27 @@ Contact: michel.peltriaux@sgdnord.rlp.de
|
|||||||
Created on: 15.12.20
|
Created on: 15.12.20
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from organisation.enums import OrganisationTypeEnum, RoleTypeEnum
|
|
||||||
|
|
||||||
TEST_ORGANISATION_DATA = [
|
TEST_ORGANISATION_DATA = [
|
||||||
{
|
{
|
||||||
"name": "Test_Official_1",
|
"name": "Test_Official_1",
|
||||||
"is_active": True,
|
"is_active": True,
|
||||||
"is_deleted": False,
|
"is_deleted": False,
|
||||||
"type": OrganisationTypeEnum.OFFICIAL.value,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Test_Official_2",
|
"name": "Test_Official_2",
|
||||||
"is_active": True,
|
"is_active": True,
|
||||||
"is_deleted": False,
|
"is_deleted": False,
|
||||||
"type": OrganisationTypeEnum.OFFICIAL.value,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Test_NGO_1",
|
"name": "Test_NGO_1",
|
||||||
"is_active": True,
|
"is_active": True,
|
||||||
"is_deleted": False,
|
"is_deleted": False,
|
||||||
"type": OrganisationTypeEnum.NGO.value,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Test_Company_1",
|
"name": "Test_Company_1",
|
||||||
"is_active": True,
|
"is_active": True,
|
||||||
"is_deleted": False,
|
"is_deleted": False,
|
||||||
"type": OrganisationTypeEnum.COMPANY.value,
|
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -38,21 +33,17 @@ TEST_ROLE_GROUPS_DATA = [
|
|||||||
{
|
{
|
||||||
"name": "Registration office Test_Official_1",
|
"name": "Registration office Test_Official_1",
|
||||||
"organisation": "Test_Official_1",
|
"organisation": "Test_Official_1",
|
||||||
"role": RoleTypeEnum.REGISTRATIONOFFICE.value,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Licensing authority Test_Official_1",
|
"name": "Licensing authority Test_Official_1",
|
||||||
"organisation": "Test_Official_1",
|
"organisation": "Test_Official_1",
|
||||||
"role": RoleTypeEnum.LICENSINGAUTHORITY.value,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Dataprovider Test_Official_2",
|
"name": "Dataprovider Test_Official_2",
|
||||||
"organisation": "Test_Official_2",
|
"organisation": "Test_Official_2",
|
||||||
"role": RoleTypeEnum.LICENSINGAUTHORITY.value,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Dataprovider Test_Company_1",
|
"name": "Dataprovider Test_Company_1",
|
||||||
"organisation": "Test_Company_1",
|
"organisation": "Test_Company_1",
|
||||||
"role": RoleTypeEnum.DATAPROVIDER.value,
|
|
||||||
},
|
},
|
||||||
]
|
]
|
@ -7,11 +7,9 @@ Created on: 17.11.20
|
|||||||
"""
|
"""
|
||||||
import uuid
|
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 import models
|
||||||
from django.db.models import QuerySet
|
|
||||||
|
|
||||||
from organisation.enums import RoleTypeEnum
|
|
||||||
|
|
||||||
|
|
||||||
class BaseResource(models.Model):
|
class BaseResource(models.Model):
|
||||||
@ -22,8 +20,6 @@ class BaseResource(models.Model):
|
|||||||
primary_key=True,
|
primary_key=True,
|
||||||
default=uuid.uuid4,
|
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_on = models.DateTimeField(auto_now_add=True, null=True)
|
||||||
created_by = models.ForeignKey(User, null=True, on_delete=models.SET_NULL)
|
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)
|
identifier = models.CharField(max_length=1000, null=True, blank=True)
|
||||||
title = 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:
|
class Meta:
|
||||||
abstract = True
|
abstract = True
|
||||||
@ -64,41 +63,8 @@ class Document(BaseResource):
|
|||||||
comment = models.TextField()
|
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)
|
geom = MultiPolygonField(null=True, blank=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
|
|
||||||
)
|
|
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_TITLE = "konova"
|
||||||
BASE_FRONTEND_TITLE = "Kompensationsverzeichnis Service Portal"
|
BASE_FRONTEND_TITLE = "Kompensationsverzeichnis Service Portal"
|
||||||
WIKI_URL = "https://dienste.naturschutz.rlp.de/doku/doku.php?id=ksp:start"
|
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',
|
'simple_sso.sso_server',
|
||||||
'django_tables2',
|
'django_tables2',
|
||||||
'fontawesome_5',
|
'fontawesome_5',
|
||||||
|
'bootstrap4',
|
||||||
'konova',
|
'konova',
|
||||||
'compensation',
|
'compensation',
|
||||||
'intervention',
|
'intervention',
|
@ -28,7 +28,6 @@ urlpatterns = [
|
|||||||
path('login/', include(sso_client.get_urls())),
|
path('login/', include(sso_client.get_urls())),
|
||||||
path('logout/', logout_view, name="logout"),
|
path('logout/', logout_view, name="logout"),
|
||||||
path('', home_view, name="home"),
|
path('', home_view, name="home"),
|
||||||
path('process/', include("process.urls")),
|
|
||||||
path('intervention/', include("intervention.urls")),
|
path('intervention/', include("intervention.urls")),
|
||||||
path('compensation/', include("compensation.urls")),
|
path('compensation/', include("compensation.urls")),
|
||||||
path('eco-account/', include("process.urls")),
|
path('eco-account/', include("process.urls")),
|
@ -43,25 +43,6 @@ def home_view(request: HttpRequest):
|
|||||||
"""
|
"""
|
||||||
template = "konova/home.html"
|
template = "konova/home.html"
|
||||||
|
|
||||||
if request.method == "POST":
|
additional_context = {}
|
||||||
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
|
context = BaseContext(request, additional_context).context
|
||||||
return render(request, template, 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');
|
|
||||||
*/
|
|
||||||
}
|
|
2
kspneo/static/js/jquery-3.5.1.min.js
vendored
13
kspneo/static/js/jquery-ui.min.js
vendored
6445
kspneo/static/js/mulewf.min.js
vendored
@ -6,21 +6,3 @@ Created on: 07.12.20
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
from konova.enums import BaseEnum
|
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 django.db import models
|
||||||
|
|
||||||
from konova.models import BaseResource
|
from konova.models import BaseResource
|
||||||
from organisation.enums import OrganisationTypeEnum
|
|
||||||
|
|
||||||
|
|
||||||
class Organisation(BaseResource):
|
class Organisation(BaseResource):
|
||||||
@ -12,7 +11,6 @@ class Organisation(BaseResource):
|
|||||||
phone = models.CharField(max_length=500, null=True, blank=True)
|
phone = models.CharField(max_length=500, null=True, blank=True)
|
||||||
email = models.EmailField(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)
|
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):
|
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 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'
|
|
202
process/enums.py
@ -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,
|
|
||||||
}
|
|
||||||
}
|
|
218
process/forms.py
@ -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'),
|
|
||||||
]
|
|
217
process/views.py
@ -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
|
asgiref==3.3.1
|
||||||
|
beautifulsoup4==4.9.3
|
||||||
certifi==2020.11.8
|
certifi==2020.11.8
|
||||||
chardet==3.0.4
|
chardet==3.0.4
|
||||||
Django==3.1.3
|
Django==3.1.3
|
||||||
django-autocomplete-light==3.8.1
|
django-autocomplete-light==3.8.1
|
||||||
|
django-bootstrap4==3.0.1
|
||||||
django-debug-toolbar==3.1.1
|
django-debug-toolbar==3.1.1
|
||||||
django-filter==2.4.0
|
django-filter==2.4.0
|
||||||
django-fontawesome-5==1.0.18
|
django-fontawesome-5==1.0.18
|
||||||
django-simple-sso==0.14.1
|
django-simple-sso==0.14.1
|
||||||
django-tables2==2.3.3
|
django-tables2==2.3.3
|
||||||
idna==2.10
|
idna==2.10
|
||||||
|
importlib-metadata==2.1.1
|
||||||
itsdangerous==1.1.0
|
itsdangerous==1.1.0
|
||||||
pkg-resources==0.0.0
|
pkg-resources==0.0.0
|
||||||
psycopg2==2.8.6
|
psycopg2==2.8.6
|
||||||
pytz==2020.4
|
pytz==2020.4
|
||||||
requests==2.25.0
|
requests==2.25.0
|
||||||
six==1.15.0
|
six==1.15.0
|
||||||
|
soupsieve==2.2.1
|
||||||
sqlparse==0.4.1
|
sqlparse==0.4.1
|
||||||
urllib3==1.26.2
|
urllib3==1.26.2
|
||||||
webservices==0.7
|
webservices==0.7
|
||||||
|
zipp==3.4.1
|
||||||
|
@ -4,12 +4,8 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<title>{{ base_title }}</title>
|
<title>{{ base_title }}</title>
|
||||||
<link rel="stylesheet" href="{% static 'css/messages.css' %}">
|
{% bootstrap_css %}
|
||||||
<link rel="stylesheet" href="{% static 'css/mulewf.css' %}">
|
{% bootstrap_javascript jquery='full' %}
|
||||||
<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 %}
|
{% fontawesome_5_static %}
|
||||||
{% block head %}
|
{% block head %}
|
||||||
|
|
||||||
|