Initial
This commit is contained in:
commit
c14e9466fb
91 files changed
+22395
No files matched your search
@@ -0,0 +1,4 @@
|
||||
# Project exclude paths
|
||||
/venv/
|
||||
/.idea/
|
||||
*/migrations/
|
||||
Whitespace-only changes.
@@ -0,0 +1,46 @@
|
||||
from django.contrib import admin
|
||||
|
||||
from compensation.models import Compensation, CompensationAction, CompensationState, CompensationControl
|
||||
|
||||
|
||||
class CompensationControlAdmin(admin.ModelAdmin):
|
||||
list_display = [
|
||||
"id",
|
||||
"type",
|
||||
"deadline",
|
||||
"expected_result",
|
||||
"by_authority",
|
||||
]
|
||||
|
||||
|
||||
class CompensationStateAdmin(admin.ModelAdmin):
|
||||
list_display = [
|
||||
"id",
|
||||
"biotope_type",
|
||||
"amount",
|
||||
"unit",
|
||||
]
|
||||
|
||||
|
||||
class CompensationActionAdmin(admin.ModelAdmin):
|
||||
list_display = [
|
||||
"id",
|
||||
"action_type",
|
||||
"amount",
|
||||
"unit",
|
||||
"control",
|
||||
]
|
||||
|
||||
|
||||
class CompensationAdmin(admin.ModelAdmin):
|
||||
list_display = [
|
||||
"id",
|
||||
"type",
|
||||
"created_on",
|
||||
]
|
||||
|
||||
|
||||
admin.site.register(Compensation, CompensationAdmin)
|
||||
admin.site.register(CompensationAction, CompensationActionAdmin)
|
||||
admin.site.register(CompensationState, CompensationStateAdmin)
|
||||
admin.site.register(CompensationControl, CompensationControlAdmin)
|
||||
@@ -0,0 +1,5 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class CompensationConfig(AppConfig):
|
||||
name = 'compensation'
|
||||
@@ -0,0 +1,14 @@
|
||||
"""
|
||||
Author: Michel Peltriaux
|
||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||
Created on: 04.12.20
|
||||
|
||||
"""
|
||||
from konova.forms import BaseForm
|
||||
|
||||
|
||||
class NewCompensationForm(BaseForm):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
@@ -0,0 +1,145 @@
|
||||
"""
|
||||
Author: Michel Peltriaux
|
||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||
Created on: 17.11.20
|
||||
|
||||
"""
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.gis.db import models
|
||||
from django.utils.timezone import now
|
||||
|
||||
from compensation.settings import COMPENSATION_IDENTIFIER_LENGTH, COMPENSATION_IDENTIFIER_TEMPLATE
|
||||
from konova.models import BaseObject, BaseResource
|
||||
from konova.utils.generators import generate_random_string
|
||||
from process.models import Process
|
||||
|
||||
|
||||
class CompensationControl(BaseResource):
|
||||
"""
|
||||
Holds data on how a compensation shall be controlled
|
||||
"""
|
||||
deadline = models.ForeignKey("konova.Deadline", on_delete=models.SET_NULL, null=True, blank=True)
|
||||
type = models.CharField(max_length=500, null=True, blank=True)
|
||||
expected_result = models.CharField(max_length=500, null=True, blank=True, help_text="The expected outcome, that needs to be controlled")
|
||||
by_authority = models.CharField(max_length=500, null=True, blank=True)
|
||||
comment = models.TextField()
|
||||
|
||||
|
||||
class CompensationState(models.Model):
|
||||
"""
|
||||
Compensations must define the state of an area before and after the compensation.
|
||||
"""
|
||||
biotope_type = models.CharField(max_length=500, null=True, blank=True)
|
||||
amount = models.FloatField()
|
||||
unit = models.CharField(max_length=100, null=True, blank=True)
|
||||
|
||||
|
||||
class CompensationAction(BaseResource):
|
||||
"""
|
||||
Compensations include actions like planting trees, refreshing rivers and so on.
|
||||
"""
|
||||
action_type = models.CharField(max_length=500, null=True, blank=True)
|
||||
amount = models.FloatField()
|
||||
unit = models.CharField(max_length=100, null=True, blank=True)
|
||||
control = models.ForeignKey(CompensationControl, on_delete=models.SET_NULL, null=True, blank=True)
|
||||
|
||||
|
||||
class Compensation(BaseObject):
|
||||
"""
|
||||
The compensation holds information about which actions have to be performed until which date, who is in charge
|
||||
of this, which legal authority is the point of contact, and so on.
|
||||
"""
|
||||
is_old_law = models.BooleanField(default=False)
|
||||
type = models.CharField(max_length=500, null=True, blank=True)
|
||||
registration_office = models.CharField(max_length=500, null=True, blank=True) # ToDo: Really needed?
|
||||
process = models.ForeignKey("process.Process", related_name="compensations", on_delete=models.CASCADE)
|
||||
ground_definitions = models.CharField(max_length=500, null=True, blank=True) # ToDo: Need to be M2M to laws!
|
||||
action_definitions = models.CharField(max_length=500, null=True, blank=True) # ToDo: Need to be M2M to laws!
|
||||
actions = models.ManyToManyField(CompensationAction)
|
||||
deadline_creation = models.ForeignKey("konova.Deadline", on_delete=models.SET_NULL, null=True, blank=True, related_name="deadline_creation")
|
||||
deadline_maintaining = models.ForeignKey("konova.Deadline", on_delete=models.SET_NULL, null=True, blank=True, related_name="deadline_maintaining")
|
||||
initial_states = models.ManyToManyField(CompensationState, blank=True, related_name='+')
|
||||
final_states = models.ManyToManyField(CompensationState, blank=True, related_name='+')
|
||||
geometry = models.MultiPolygonField(null=True, blank=True)
|
||||
documents = models.ManyToManyField("konova.Document", blank=True)
|
||||
|
||||
def __str__(self):
|
||||
return "{} of {}".format(self.type, self.process)
|
||||
|
||||
@staticmethod
|
||||
def __generate_new_identifier() -> str:
|
||||
""" Generates a new identifier for the intervention object
|
||||
|
||||
Returns:
|
||||
str
|
||||
"""
|
||||
curr_month = str(now().month)
|
||||
curr_year = str(now().year)
|
||||
rand_str = generate_random_string(
|
||||
length=COMPENSATION_IDENTIFIER_LENGTH,
|
||||
only_numbers=True,
|
||||
)
|
||||
_str = "{}{}{}".format(curr_month, curr_year, rand_str)
|
||||
return COMPENSATION_IDENTIFIER_TEMPLATE.format(_str)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if self.identifier is None or len(self.identifier) == 0:
|
||||
# Create new identifier
|
||||
new_id = self.__generate_new_identifier()
|
||||
while Compensation.objects.filter(identifier=new_id).exists():
|
||||
new_id = self.__generate_new_identifier()
|
||||
self.identifier = new_id
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
@staticmethod
|
||||
def get_role_objects(user: User, order_by: str = "-created_on"):
|
||||
""" Returns objects depending on the currently selected role of the user
|
||||
|
||||
* REGISTRATIONOFFICE
|
||||
* User can see the processes where registration_office is set to the organisation of the currently selected role
|
||||
* User can see self-created processes
|
||||
* LICENSINGOFFICE
|
||||
* same
|
||||
* DATAPROVIDER
|
||||
* User can see only self-created processes
|
||||
|
||||
Args:
|
||||
user (User): The performing user
|
||||
order_by (str): Order by which Process attribute
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
role = user.current_role
|
||||
if role is None:
|
||||
return Compensation.objects.none()
|
||||
processes = Process.get_role_objects(user, order_by)
|
||||
processes.prefetch_related("compensations")
|
||||
compensations = []
|
||||
[compensations.extend(process.compensations.all()) for process in processes]
|
||||
return compensations
|
||||
|
||||
|
||||
class EcoAccount(BaseResource):
|
||||
"""
|
||||
An eco account is a kind of 'prepaid' compensation. It can be compared to an account that already has been filled
|
||||
with some kind of currency. From this account one is able to 'withdraw' currency for current projects.
|
||||
|
||||
'Withdrawing' can only be applied by shrinking the size of the available geometry and declaring the withdrawed
|
||||
geometry as a compensation for a process.
|
||||
"""
|
||||
is_old_law = models.BooleanField(default=False)
|
||||
type = models.CharField(max_length=500, null=True, blank=True)
|
||||
licensing_authority_document_identifier = models.CharField(max_length=500, null=True, blank=True)
|
||||
registration_office = models.CharField(max_length=500, null=True, blank=True)
|
||||
handler = models.CharField(max_length=500, null=True, blank=True)
|
||||
handler_comments = models.TextField()
|
||||
geometry = models.GeometryCollectionField()
|
||||
documents = models.ManyToManyField("konova.Document")
|
||||
initial_states = models.ManyToManyField(CompensationState, blank=True, related_name='+')
|
||||
final_states = models.ManyToManyField(CompensationState, blank=True, related_name='+')
|
||||
actions = models.ManyToManyField(CompensationAction)
|
||||
deadline_maintaining = models.ForeignKey("konova.Deadline", on_delete=models.SET_NULL, null=True, blank=True)
|
||||
deadline_other = models.ManyToManyField("konova.Deadline", blank=True, related_name='+')
|
||||
comments = models.TextField()
|
||||
@@ -0,0 +1,9 @@
|
||||
"""
|
||||
Author: Michel Peltriaux
|
||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||
Created on: 18.12.20
|
||||
|
||||
"""
|
||||
COMPENSATION_IDENTIFIER_LENGTH = 10
|
||||
COMPENSATION_IDENTIFIER_TEMPLATE = "KOM-{}"
|
||||
@@ -0,0 +1,118 @@
|
||||
"""
|
||||
Author: Michel Peltriaux
|
||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||
Created on: 01.12.20
|
||||
|
||||
"""
|
||||
from django.urls import reverse
|
||||
from django.utils.html import format_html
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from konova.utils.tables import BaseTable
|
||||
import django_tables2 as tables
|
||||
|
||||
|
||||
class CompensationTable(BaseTable):
|
||||
id = tables.Column(
|
||||
verbose_name=_("Identifier"),
|
||||
orderable=True,
|
||||
accessor="identifier",
|
||||
)
|
||||
t = tables.Column(
|
||||
verbose_name=_("Title"),
|
||||
orderable=True,
|
||||
accessor="title",
|
||||
)
|
||||
p = tables.Column(
|
||||
verbose_name=_("Process"),
|
||||
orderable=True,
|
||||
accessor="process",
|
||||
)
|
||||
d = tables.Column(
|
||||
verbose_name=_("Created on"),
|
||||
orderable=True,
|
||||
accessor="created_on",
|
||||
)
|
||||
ac = tables.Column(
|
||||
verbose_name=_("Actions"),
|
||||
orderable=False,
|
||||
empty_values=[],
|
||||
attrs={"td": {"class": "action-col"}}
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.title = _("Compensations")
|
||||
self.add_new_url = reverse("compensation:new")
|
||||
|
||||
def render_ac(self, value, record):
|
||||
"""
|
||||
Renders possible actions for this record, such as delete.
|
||||
"""
|
||||
intervention = _("Compensation")
|
||||
html = ""
|
||||
html += self.render_open_btn(
|
||||
_("Open {}").format(intervention),
|
||||
reverse("compensation:open", args=(record.id,)),
|
||||
new_tab=True
|
||||
)
|
||||
html += self.render_edit_btn(
|
||||
_("Edit {}").format(intervention),
|
||||
reverse("compensation:edit", args=(record.id,)),
|
||||
)
|
||||
html += self.render_delete_btn(
|
||||
_("Delete {}").format(intervention),
|
||||
reverse("compensation:remove", args=(record.id,)),
|
||||
)
|
||||
return format_html(html)
|
||||
|
||||
|
||||
class EcoAccountTable(BaseTable):
|
||||
id = tables.Column(
|
||||
verbose_name=_("Identifier"),
|
||||
orderable=True,
|
||||
accessor="identifier",
|
||||
)
|
||||
t = tables.Column(
|
||||
verbose_name=_("Title"),
|
||||
orderable=True,
|
||||
accessor="title",
|
||||
)
|
||||
d = tables.Column(
|
||||
verbose_name=_("Created on"),
|
||||
orderable=True,
|
||||
accessor="created_on",
|
||||
)
|
||||
ac = tables.Column(
|
||||
verbose_name=_("Actions"),
|
||||
orderable=False,
|
||||
empty_values=[],
|
||||
attrs={"td": {"class": "action-col"}}
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.title = _("Eco Accounts")
|
||||
self.add_new_url = reverse("compensation:account-new")
|
||||
|
||||
def render_ac(self, value, record):
|
||||
"""
|
||||
Renders possible actions for this record, such as delete.
|
||||
"""
|
||||
intervention = _("Compensation")
|
||||
html = ""
|
||||
html += self.render_open_btn(
|
||||
_("Open {}").format(intervention),
|
||||
reverse("compensation:open", args=(record.id,)),
|
||||
new_tab=True
|
||||
)
|
||||
html += self.render_edit_btn(
|
||||
_("Edit {}").format(intervention),
|
||||
reverse("compensation:edit", args=(record.id,)),
|
||||
)
|
||||
html += self.render_delete_btn(
|
||||
_("Delete {}").format(intervention),
|
||||
reverse("compensation:remove", args=(record.id,)),
|
||||
)
|
||||
return format_html(html)
|
||||
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
@@ -0,0 +1,27 @@
|
||||
"""
|
||||
Author: Michel Peltriaux
|
||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||
Created on: 30.11.20
|
||||
|
||||
"""
|
||||
from django.urls import path
|
||||
|
||||
from compensation.views import *
|
||||
|
||||
app_name = "compensation"
|
||||
urlpatterns = [
|
||||
# Main compensation
|
||||
path("", index_view, name="index"),
|
||||
path('new/', new_view, name='new'),
|
||||
path('open/<id>', open_view, name='open'),
|
||||
path('edit/<id>', edit_view, name='edit'),
|
||||
path('remove/<id>', remove_view, name='remove'),
|
||||
|
||||
# Eco-account
|
||||
path("account/", account_index_view, name="account-index"),
|
||||
path('account/new/', account_new_view, name='account-new'),
|
||||
path('account/open/<id>', account_open_view, name='account-open'),
|
||||
path('account/edit/<id>', account_edit_view, name='account-edit'),
|
||||
path('account/remove/<id>', account_remove_view, name='account-remove'),
|
||||
]
|
||||
@@ -0,0 +1,110 @@
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.http import HttpRequest
|
||||
from django.shortcuts import render
|
||||
|
||||
from compensation.models import Compensation, EcoAccount
|
||||
from compensation.tables import CompensationTable, EcoAccountTable
|
||||
from konova.contexts import BaseContext
|
||||
from konova.decorators import *
|
||||
|
||||
|
||||
@login_required
|
||||
@resolve_user_role
|
||||
def index_view(request: HttpRequest):
|
||||
"""
|
||||
Renders the index view for compensation
|
||||
|
||||
Args:
|
||||
request (HttpRequest): The incoming request
|
||||
|
||||
Returns:
|
||||
A rendered view
|
||||
"""
|
||||
template = "generic_index.html"
|
||||
user = request.user
|
||||
compensations = Compensation.get_role_objects(user)
|
||||
table = CompensationTable(
|
||||
request=request,
|
||||
queryset=compensations
|
||||
)
|
||||
context = {
|
||||
"table": table,
|
||||
}
|
||||
context = BaseContext(request, context).context
|
||||
return render(request, template, context)
|
||||
|
||||
|
||||
@login_required
|
||||
def new_view(request: HttpRequest):
|
||||
# ToDo
|
||||
pass
|
||||
|
||||
|
||||
@login_required
|
||||
def edit_view(request: HttpRequest, id: str):
|
||||
# ToDo
|
||||
pass
|
||||
|
||||
|
||||
@login_required
|
||||
def open_view(request: HttpRequest, id: str):
|
||||
# ToDo
|
||||
pass
|
||||
|
||||
|
||||
@login_required
|
||||
def remove_view(request: HttpRequest, id: str):
|
||||
# ToDo
|
||||
pass
|
||||
|
||||
|
||||
@login_required
|
||||
def account_index_view(request: HttpRequest):
|
||||
"""
|
||||
Renders the index view for eco accounts
|
||||
|
||||
Args:
|
||||
request (HttpRequest): The incoming request
|
||||
|
||||
Returns:
|
||||
A rendered view
|
||||
"""
|
||||
template = "generic_index.html"
|
||||
user = request.user
|
||||
eco_accounts = EcoAccount.objects.filter(
|
||||
created_by=user,
|
||||
is_deleted=False,
|
||||
)
|
||||
table = EcoAccountTable(
|
||||
request=request,
|
||||
queryset=eco_accounts
|
||||
)
|
||||
context = {
|
||||
"table": table,
|
||||
}
|
||||
context = BaseContext(request, context).context
|
||||
return render(request, template, context)
|
||||
|
||||
|
||||
@login_required
|
||||
def account_new_view(request: HttpRequest):
|
||||
# ToDo
|
||||
pass
|
||||
|
||||
|
||||
@login_required
|
||||
def account_edit_view(request: HttpRequest, id: str):
|
||||
# ToDo
|
||||
pass
|
||||
|
||||
|
||||
@login_required
|
||||
def account_open_view(request: HttpRequest, id: str):
|
||||
# ToDo
|
||||
pass
|
||||
|
||||
|
||||
@login_required
|
||||
def account_remove_view(request: HttpRequest, id: str):
|
||||
# ToDo
|
||||
pass
|
||||
Whitespace-only changes.
@@ -0,0 +1,18 @@
|
||||
from django.contrib import admin
|
||||
|
||||
from intervention.models import Intervention
|
||||
|
||||
|
||||
class InterventionAdmin(admin.ModelAdmin):
|
||||
list_display = [
|
||||
"id",
|
||||
"title",
|
||||
"type",
|
||||
"handler",
|
||||
"created_on",
|
||||
"is_active",
|
||||
"is_deleted",
|
||||
]
|
||||
|
||||
|
||||
admin.site.register(Intervention, InterventionAdmin)
|
||||
@@ -0,0 +1,5 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class InterventionConfig(AppConfig):
|
||||
name = 'intervention'
|
||||
@@ -0,0 +1,220 @@
|
||||
"""
|
||||
Author: Michel Peltriaux
|
||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||
Created on: 02.12.20
|
||||
|
||||
"""
|
||||
from dal import autocomplete
|
||||
from django import forms
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.gis import forms as gis_forms
|
||||
from django.contrib.gis.geos import Polygon
|
||||
from django.db import transaction
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from intervention.models import Intervention
|
||||
from konova.forms import BaseForm
|
||||
from konova.models import Document
|
||||
from konova.settings import DEFAULT_LAT, DEFAULT_LON, DEFAULT_ZOOM
|
||||
from organisation.models import Organisation
|
||||
|
||||
|
||||
class NewInterventionForm(BaseForm):
|
||||
identifier = forms.CharField(
|
||||
label=_("Identifier"),
|
||||
label_suffix="",
|
||||
max_length=255,
|
||||
help_text=_("Generated automatically if none was given"),
|
||||
required=False,
|
||||
)
|
||||
title = forms.CharField(
|
||||
label=_("Title"),
|
||||
label_suffix="",
|
||||
max_length=255,
|
||||
)
|
||||
type = forms.CharField(
|
||||
label=_("Type"),
|
||||
label_suffix="",
|
||||
max_length=255,
|
||||
help_text=_("Which intervention type is this"),
|
||||
)
|
||||
law = forms.CharField(
|
||||
label=_("Law"),
|
||||
label_suffix="",
|
||||
max_length=255,
|
||||
help_text=_("Based on which law"),
|
||||
)
|
||||
handler = forms.CharField(
|
||||
label=_("Intervention handler"),
|
||||
label_suffix="",
|
||||
max_length=255,
|
||||
help_text=_("Who performs the intervention"),
|
||||
)
|
||||
data_provider = forms.ModelChoiceField(
|
||||
label=_("Data provider"),
|
||||
label_suffix="",
|
||||
help_text=_("Who provides the data for the intervention"),
|
||||
queryset=Organisation.objects.all(),
|
||||
widget=autocomplete.ModelSelect2(
|
||||
url="other-orgs-autocomplete",
|
||||
attrs={
|
||||
"data-placeholder": _("Organization"),
|
||||
"data-minimum-input-length": 3,
|
||||
}
|
||||
),
|
||||
)
|
||||
data_provider_detail = forms.CharField(
|
||||
label=_("Data provider details"),
|
||||
label_suffix="",
|
||||
max_length=255,
|
||||
help_text=_("Further details"),
|
||||
required=False,
|
||||
)
|
||||
geometry = gis_forms.MultiPolygonField(
|
||||
widget=gis_forms.OSMWidget(
|
||||
attrs={
|
||||
"default_lat": DEFAULT_LAT,
|
||||
"default_lon": DEFAULT_LON,
|
||||
"default_zoom": DEFAULT_ZOOM,
|
||||
'map_width': 800,
|
||||
'map_height': 500
|
||||
},
|
||||
),
|
||||
label=_("Map"),
|
||||
label_suffix="",
|
||||
help_text=_("Where does the intervention take place")
|
||||
)
|
||||
documents = forms.FileField(
|
||||
widget=forms.ClearableFileInput(
|
||||
attrs={
|
||||
"multiple": True,
|
||||
}
|
||||
),
|
||||
label=_("Files"),
|
||||
label_suffix="",
|
||||
required=False,
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.form_title = _("New intervention")
|
||||
self.action_url = reverse("intervention:new")
|
||||
self.cancel_redirect = reverse("intervention:index")
|
||||
|
||||
def save(self, user: User):
|
||||
with transaction.atomic():
|
||||
identifier = self.cleaned_data.get("identifier", None)
|
||||
title = self.cleaned_data.get("title", None)
|
||||
_type = self.cleaned_data.get("type", None)
|
||||
law = self.cleaned_data.get("law", None)
|
||||
handler = self.cleaned_data.get("handler", None)
|
||||
data_provider = self.cleaned_data.get("data_provider", None)
|
||||
data_provider_detail = self.cleaned_data.get("data_provider_detail", None)
|
||||
geometry = self.cleaned_data.get("geometry", Polygon())
|
||||
documents = self.cleaned_data.get("documents", []) or []
|
||||
|
||||
intervention = Intervention(
|
||||
identifier=identifier,
|
||||
title=title,
|
||||
type=_type,
|
||||
law=law,
|
||||
handler=handler,
|
||||
data_provider=data_provider,
|
||||
data_provider_detail=data_provider_detail,
|
||||
geometry=geometry,
|
||||
created_by=user,
|
||||
)
|
||||
intervention.save()
|
||||
for doc in documents:
|
||||
doc_obj = Document()
|
||||
doc_obj.document = doc
|
||||
# ToDo Add functionality for other attributes
|
||||
doc_obj.save()
|
||||
intervention.documents.add(doc_obj)
|
||||
return intervention
|
||||
|
||||
|
||||
class EditInterventionForm(NewInterventionForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
if self.instance is not None:
|
||||
self.action_url = reverse("intervention:edit", args=(self.instance.id,))
|
||||
self.cancel_redirect = reverse("intervention:index")
|
||||
self.form_title = _("Edit intervention")
|
||||
self.form_caption = ""
|
||||
|
||||
# Initialize form data
|
||||
form_data = {
|
||||
"identifier": self.instance.identifier,
|
||||
"title": self.instance.title,
|
||||
"type": self.instance.type,
|
||||
"law": self.instance.law,
|
||||
"handler": self.instance.handler,
|
||||
"data_provider": self.instance.data_provider,
|
||||
"data_provider_detail": self.instance.data_provider_detail,
|
||||
"geometry": self.instance.geometry,
|
||||
"documents": self.instance.documents.all(),
|
||||
}
|
||||
disabled_fields = [
|
||||
"identifier",
|
||||
]
|
||||
self.load_initial_data(
|
||||
form_data,
|
||||
disabled_fields,
|
||||
)
|
||||
|
||||
def save(self, user: User):
|
||||
""" Overwrite instance with new form data
|
||||
|
||||
Args:
|
||||
user ():
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
with transaction.atomic():
|
||||
identifier = self.cleaned_data.get("identifier", None)
|
||||
title = self.cleaned_data.get("title", None)
|
||||
_type = self.cleaned_data.get("type", None)
|
||||
law = self.cleaned_data.get("law", None)
|
||||
handler = self.cleaned_data.get("handler", None)
|
||||
data_provider = self.cleaned_data.get("data_provider", None)
|
||||
data_provider_detail = self.cleaned_data.get("data_provider_detail", None)
|
||||
geometry = self.cleaned_data.get("geometry", Polygon())
|
||||
documents = self.cleaned_data.get("documents", []) or []
|
||||
|
||||
self.instance.identifier = identifier
|
||||
self.instance.title = title
|
||||
self.instance.type = _type
|
||||
self.instance.law = law
|
||||
self.instance.handler = handler
|
||||
self.instance.data_provider = data_provider
|
||||
self.instance.data_provider_detail = data_provider_detail
|
||||
self.instance.geometry = geometry
|
||||
self.instance.save()
|
||||
|
||||
for doc in documents:
|
||||
doc_obj = Document()
|
||||
doc_obj.document = doc
|
||||
# ToDo Add functionality for other attributes
|
||||
doc_obj.save()
|
||||
self.instance.documents.add(doc_obj)
|
||||
return self.instance
|
||||
|
||||
|
||||
class OpenInterventionForm(EditInterventionForm):
|
||||
"""
|
||||
This form is not intended to be used as data-input form. It's used to simplify the rendering of intervention:open
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# Resize map
|
||||
self.fields["geometry"].widget.attrs["map_width"] = 500
|
||||
self.fields["geometry"].widget.attrs["map_height"] = 300
|
||||
|
||||
# Disable all form fields
|
||||
for field in self.fields:
|
||||
self.disable_form_field(field)
|
||||
@@ -0,0 +1,91 @@
|
||||
"""
|
||||
Author: Michel Peltriaux
|
||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||
Created on: 17.11.20
|
||||
|
||||
"""
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.gis.db import models
|
||||
from django.utils.timezone import now
|
||||
|
||||
from intervention.settings import INTERVENTION_IDENTIFIER_LENGTH, INTERVENTION_IDENTIFIER_TEMPLATE
|
||||
from konova.models import BaseObject
|
||||
from konova.utils.generators import generate_random_string
|
||||
from organisation.enums import RoleTypeEnum
|
||||
from process.models import Process
|
||||
|
||||
|
||||
class Intervention(BaseObject):
|
||||
"""
|
||||
Interventions are e.g. construction sites where nature used to be.
|
||||
|
||||
A process consists of exactly one intervention and one or more compensation
|
||||
"""
|
||||
type = models.CharField(max_length=500, null=True, blank=True)
|
||||
law = models.CharField(max_length=500, null=True, blank=True)
|
||||
handler = models.CharField(max_length=500, null=True, blank=True)
|
||||
data_provider = models.ForeignKey("organisation.Organisation", on_delete=models.SET_NULL, null=True, blank=True)
|
||||
data_provider_detail = models.CharField(max_length=500, null=True, blank=True)
|
||||
geometry = models.MultiPolygonField(null=True, blank=True)
|
||||
process = models.OneToOneField("process.Process", on_delete=models.CASCADE, null=True, blank=True, related_name="intervention")
|
||||
documents = models.ManyToManyField("konova.Document", blank=True)
|
||||
|
||||
def __str__(self):
|
||||
return "{} by {}".format(self.type, self.handler)
|
||||
|
||||
def delete(self, *args, **kwargs):
|
||||
if self.process is not None:
|
||||
self.process.delete()
|
||||
super().delete(*args, **kwargs)
|
||||
|
||||
@staticmethod
|
||||
def __generate_new_identifier() -> str:
|
||||
""" Generates a new identifier for the intervention object
|
||||
|
||||
Returns:
|
||||
str
|
||||
"""
|
||||
curr_month = str(now().month)
|
||||
curr_year = str(now().year)
|
||||
rand_str = generate_random_string(
|
||||
length=INTERVENTION_IDENTIFIER_LENGTH,
|
||||
only_numbers=True,
|
||||
)
|
||||
_str = "{}{}{}".format(curr_month, curr_year, rand_str)
|
||||
return INTERVENTION_IDENTIFIER_TEMPLATE.format(_str)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if self.identifier is None or len(self.identifier) == 0:
|
||||
# Create new identifier
|
||||
new_id = self.__generate_new_identifier()
|
||||
while Intervention.objects.filter(identifier=new_id).exists():
|
||||
new_id = self.__generate_new_identifier()
|
||||
self.identifier = new_id
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
@staticmethod
|
||||
def get_role_objects(user: User, order_by: str = "-created_on") -> list:
|
||||
""" Returns objects depending on the currently selected role of the user
|
||||
|
||||
* REGISTRATIONOFFICE
|
||||
* User can see the processes where registration_office is set to the organisation of the currently selected role
|
||||
* User can see self-created processes
|
||||
* LICENSINGOFFICE
|
||||
* same
|
||||
* DATAPROVIDER
|
||||
* User can see only self-created processes
|
||||
|
||||
Args:
|
||||
user (User): The performing user
|
||||
order_by (str): Order by which Process attribute
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
role = user.current_role
|
||||
if role is None:
|
||||
return Intervention.objects.none()
|
||||
processes = Process.get_role_objects(user, order_by)
|
||||
interventions = [process.intervention for process in processes]
|
||||
return interventions
|
||||
@@ -0,0 +1,9 @@
|
||||
"""
|
||||
Author: Michel Peltriaux
|
||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||
Created on: 30.11.20
|
||||
|
||||
"""
|
||||
INTERVENTION_IDENTIFIER_LENGTH = 10
|
||||
INTERVENTION_IDENTIFIER_TEMPLATE = "EIV-{}"
|
||||
@@ -0,0 +1,87 @@
|
||||
"""
|
||||
Author: Michel Peltriaux
|
||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||
Created on: 01.12.20
|
||||
|
||||
"""
|
||||
from django.urls import reverse
|
||||
from django.utils.html import format_html
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from intervention.models import Intervention
|
||||
from konova.utils.tables import BaseTable
|
||||
import django_tables2 as tables
|
||||
|
||||
|
||||
class InterventionTable(BaseTable):
|
||||
id = tables.Column(
|
||||
verbose_name=_("Identifier"),
|
||||
orderable=True,
|
||||
accessor="identifier",
|
||||
)
|
||||
t = tables.Column(
|
||||
verbose_name=_("Title"),
|
||||
orderable=True,
|
||||
accessor="title",
|
||||
)
|
||||
p = tables.Column(
|
||||
verbose_name=_("Process"),
|
||||
orderable=True,
|
||||
accessor="process",
|
||||
)
|
||||
d = tables.Column(
|
||||
verbose_name=_("Created on"),
|
||||
orderable=True,
|
||||
accessor="created_on",
|
||||
)
|
||||
ac = tables.Column(
|
||||
verbose_name=_("Actions"),
|
||||
orderable=False,
|
||||
empty_values=[],
|
||||
attrs={"td": {"class": "action-col"}}
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.title = _("Interventions")
|
||||
self.add_new_url = reverse("intervention:new")
|
||||
|
||||
def render_id(self, value, record: Intervention):
|
||||
""" Renders the id column for an intervention
|
||||
|
||||
Args:
|
||||
value (str): The identifier value
|
||||
record (Intervention): The intervention record
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
html = ""
|
||||
html += self.render_link(
|
||||
tooltip=_("Open {}").format(_("Intervention")),
|
||||
href=reverse("intervention:open", args=(record.id,)),
|
||||
txt=value,
|
||||
new_tab=False,
|
||||
)
|
||||
return format_html(html)
|
||||
|
||||
def render_ac(self, value, record):
|
||||
"""
|
||||
Renders possible actions for this record, such as delete.
|
||||
"""
|
||||
intervention = _("Intervention")
|
||||
html = ""
|
||||
html += self.render_open_btn(
|
||||
_("Open {}").format(intervention),
|
||||
reverse("intervention:open", args=(record.id,))
|
||||
)
|
||||
html += self.render_edit_btn(
|
||||
_("Edit {}").format(intervention),
|
||||
reverse("intervention:edit", args=(record.id,)),
|
||||
)
|
||||
html += self.render_delete_btn(
|
||||
_("Delete {}").format(intervention),
|
||||
reverse("intervention:remove", args=(record.id,)),
|
||||
)
|
||||
return format_html(html)
|
||||
@@ -0,0 +1,47 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n static %}
|
||||
|
||||
{% block body %}
|
||||
<div class="rows">
|
||||
<div class="columns">
|
||||
<div class="large-10 column">
|
||||
<h3>{% trans 'Intervention' %}: {{ intervention.title }}</h3>
|
||||
</div>
|
||||
<div class="large-2 column">
|
||||
<a href="{% url 'intervention:edit' intervention.id %}">
|
||||
<button class="button small" role="button" value="{% trans 'Edit' %}"><i class='fas fa-edit'></i> {% trans 'Edit' %}</button>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<hr>
|
||||
{% comment %}
|
||||
form.media needs to be loaded to ensure the openlayers client will be loaded properly
|
||||
{% endcomment %}
|
||||
{{ form.media }}
|
||||
<div class="columns">
|
||||
<div class="small-6 columns">
|
||||
<table>
|
||||
<tbody>
|
||||
{% for field in form %}
|
||||
{% if field != form.geometry %}
|
||||
<tr title="{{ field.help_text }}" class="{% if field.errors %}error{% endif %}">
|
||||
<th scope="row" class="small-3">
|
||||
<div>{{ field.label }}</div>
|
||||
<small>{{ field.help_text }}</small>
|
||||
</th>
|
||||
<td class="small-12">
|
||||
{{ field }}
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="small-6 columns">
|
||||
{{ form.geometry }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
@@ -0,0 +1,19 @@
|
||||
"""
|
||||
Author: Michel Peltriaux
|
||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||
Created on: 30.11.20
|
||||
|
||||
"""
|
||||
from django.urls import path
|
||||
|
||||
from intervention.views import index_view, new_view, open_view, edit_view, remove_view
|
||||
|
||||
app_name = "intervention"
|
||||
urlpatterns = [
|
||||
path("", index_view, name="index"),
|
||||
path('new/', new_view, name='new'),
|
||||
path('open/<id>', open_view, name='open'),
|
||||
path('edit/<id>', edit_view, name='edit'),
|
||||
path('remove/<id>', remove_view, name='remove'),
|
||||
]
|
||||
@@ -0,0 +1,171 @@
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.http import HttpRequest
|
||||
from django.shortcuts import render, get_object_or_404, redirect
|
||||
from django.urls import reverse
|
||||
|
||||
from intervention.forms import NewInterventionForm, EditInterventionForm, OpenInterventionForm
|
||||
from intervention.models import Intervention
|
||||
from intervention.tables import InterventionTable
|
||||
from konova.contexts import BaseContext
|
||||
from konova.decorators import *
|
||||
from konova.forms import RemoveForm
|
||||
from process.models import Process
|
||||
|
||||
|
||||
@login_required
|
||||
@resolve_user_role
|
||||
def index_view(request: HttpRequest):
|
||||
"""
|
||||
Renders the index view for process
|
||||
|
||||
Args:
|
||||
request (HttpRequest): The incoming request
|
||||
|
||||
Returns:
|
||||
A rendered view
|
||||
"""
|
||||
template = "generic_index.html"
|
||||
user = request.user
|
||||
interventions = Intervention.get_role_objects(user)
|
||||
table = InterventionTable(
|
||||
request=request,
|
||||
queryset=interventions
|
||||
)
|
||||
context = {
|
||||
"table": table,
|
||||
}
|
||||
context = BaseContext(request, context).context
|
||||
return render(request, template, context)
|
||||
|
||||
|
||||
@login_required
|
||||
def new_view(request: HttpRequest):
|
||||
"""
|
||||
Renders a view for a new intervention creation
|
||||
|
||||
Args:
|
||||
request (HttpRequest): The incoming request
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
template = "konova/form.html"
|
||||
form = NewInterventionForm(request.POST or None)
|
||||
if request.method == "POST":
|
||||
if form.is_valid():
|
||||
intervention = form.save(request.user)
|
||||
if intervention.process is None:
|
||||
# An intervention can not be created without a process -> automatically create a new process
|
||||
process = Process.create_from_intervention(intervention)
|
||||
messages.info(request, _("Interventions must be part of a process. Please fill in the missing data for the process"))
|
||||
return redirect("process:edit", id=process.id)
|
||||
else:
|
||||
messages.success(request, _("Intervention {} added").format(intervention.title))
|
||||
return redirect("intervention:index")
|
||||
else:
|
||||
messages.error(request, _("Invalid input"))
|
||||
else:
|
||||
# For clarification: nothing in this case
|
||||
pass
|
||||
context = {
|
||||
"form": form,
|
||||
}
|
||||
context = BaseContext(request, context).context
|
||||
return render(request, template, context)
|
||||
|
||||
|
||||
@login_required
|
||||
def open_view(request: HttpRequest, id: str):
|
||||
""" Renders a view for viewing an intervention's data
|
||||
|
||||
Args:
|
||||
request (HttpRequest): The incoming request
|
||||
id (str): The intervention's id
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
template = "intervention/open.html"
|
||||
intervention = get_object_or_404(Intervention, id=id)
|
||||
form = OpenInterventionForm(instance=intervention)
|
||||
context = {
|
||||
"intervention": intervention,
|
||||
"form": form,
|
||||
}
|
||||
context = BaseContext(request, context).context
|
||||
return render(request, template, context)
|
||||
|
||||
|
||||
@login_required
|
||||
def edit_view(request: HttpRequest, id: str):
|
||||
"""
|
||||
Renders a view for editing interventions
|
||||
|
||||
Args:
|
||||
request (HttpRequest): The incoming request
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
template = "konova/form.html"
|
||||
intervention = get_object_or_404(Intervention, id=id)
|
||||
if request.method == "POST":
|
||||
form = EditInterventionForm(request.POST or None, instance=intervention)
|
||||
if form.is_valid():
|
||||
intervention = form.save(request.user)
|
||||
messages.success(request, _("{} edited").format(intervention))
|
||||
return redirect("intervention:index")
|
||||
else:
|
||||
messages.error(request, _("Invalid input"))
|
||||
form = EditInterventionForm(instance=intervention)
|
||||
context = {
|
||||
"form": form,
|
||||
}
|
||||
context = BaseContext(request, context).context
|
||||
return render(request, template, context)
|
||||
|
||||
|
||||
@login_required
|
||||
def remove_view(request: HttpRequest, id: str):
|
||||
""" Renders a remove view for this process
|
||||
|
||||
Args:
|
||||
request (HttpRequest): The incoming request
|
||||
id (str): The uuid id as string
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
template = "konova/form.html"
|
||||
# Since an intervention is always organized inside a process, we will call the process removing routine, which
|
||||
# disables all related elements by default
|
||||
obj = get_object_or_404(Intervention, id=id)
|
||||
process = obj.process
|
||||
if request.method == "POST":
|
||||
form = RemoveForm(
|
||||
request.POST or None,
|
||||
object_to_remove=obj,
|
||||
remove_post_url=reverse("process:remove", args=(process.id,)),
|
||||
cancel_url=reverse("intervention:index"),
|
||||
)
|
||||
if form.is_valid():
|
||||
confirmed = form.is_checked()
|
||||
if confirmed:
|
||||
process.deactivate()
|
||||
messages.success(request, _("Intervention {} removed").format(obj))
|
||||
return redirect("intervention:index")
|
||||
else:
|
||||
messages.error(request, _("Invalid input"))
|
||||
|
||||
form = RemoveForm(
|
||||
object_to_remove=obj,
|
||||
remove_post_url=reverse("process:remove", args=(process.id,)),
|
||||
cancel_url=reverse("intervention:index"),
|
||||
)
|
||||
context = {
|
||||
"form": form,
|
||||
}
|
||||
context = BaseContext(request, context).context
|
||||
return render(request, template, context)
|
||||
Whitespace-only changes.
@@ -0,0 +1,32 @@
|
||||
"""
|
||||
Author: Michel Peltriaux
|
||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||
Created on: 07.12.20
|
||||
|
||||
"""
|
||||
from django.contrib import admin
|
||||
|
||||
from konova.models import RoleType, RoleGroup
|
||||
|
||||
|
||||
class RoleTypeAdmin(admin.ModelAdmin):
|
||||
list_display = [
|
||||
"type",
|
||||
"created_on",
|
||||
"created_by",
|
||||
]
|
||||
|
||||
|
||||
class RoleGroupAdmin(admin.ModelAdmin):
|
||||
list_display = [
|
||||
"name",
|
||||
"organisation",
|
||||
"role",
|
||||
]
|
||||
|
||||
|
||||
admin.site.register(RoleType, RoleTypeAdmin)
|
||||
admin.site.register(RoleGroup, RoleGroupAdmin)
|
||||
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
"""
|
||||
ASGI config for konova project.
|
||||
|
||||
It exposes the ASGI callable as a module-level variable named ``application``.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/3.1/howto/deployment/asgi/
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from django.core.asgi import get_asgi_application
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'konova.settings')
|
||||
|
||||
application = get_asgi_application()
|
||||
@@ -0,0 +1,42 @@
|
||||
"""
|
||||
Author: Michel Peltriaux
|
||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||
Created on: 07.12.20
|
||||
|
||||
"""
|
||||
from dal_select2.views import Select2QuerySetView
|
||||
|
||||
from organisation.enums import OrganisationTypeEnum
|
||||
from organisation.models import Organisation
|
||||
|
||||
|
||||
class OrganisationAutocomplete(Select2QuerySetView):
|
||||
def get_queryset(self):
|
||||
if self.request.user.is_anonymous:
|
||||
return Organisation.objects.none()
|
||||
qs = Organisation.objects.all()
|
||||
if self.q:
|
||||
qs = qs.filter(name__icontains=self.q)
|
||||
qs = qs.order_by(
|
||||
"name"
|
||||
)
|
||||
return qs
|
||||
|
||||
|
||||
class NonOfficialOrganisationAutocomplete(Select2QuerySetView):
|
||||
def get_queryset(self):
|
||||
if self.request.user.is_anonymous:
|
||||
return Organisation.objects.none()
|
||||
qs = Organisation.objects.all()
|
||||
if self.q:
|
||||
qs = qs.filter(
|
||||
name__icontains=self.q,
|
||||
)
|
||||
qs = qs.exclude(
|
||||
type=OrganisationTypeEnum.OFFICIAL.value
|
||||
)
|
||||
qs = qs.order_by(
|
||||
"name"
|
||||
)
|
||||
return qs
|
||||
@@ -0,0 +1,53 @@
|
||||
"""
|
||||
Author: Michel Peltriaux
|
||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||
Created on: 16.11.20
|
||||
|
||||
"""
|
||||
from django.http import HttpRequest
|
||||
|
||||
from konova.models import RoleGroup
|
||||
from konova.sub_settings.context_settings import BASE_TITLE, WIKI_URL, BASE_FRONTEND_TITLE, RENDER_HEADER
|
||||
from konova.utils.session import set_session_user_role
|
||||
|
||||
|
||||
class BaseContext:
|
||||
"""
|
||||
Holds all base data which is needed for every context rendering
|
||||
"""
|
||||
context = {
|
||||
"base_title": BASE_TITLE,
|
||||
"base_frontend_title": BASE_FRONTEND_TITLE,
|
||||
"language": "en",
|
||||
"wiki_url": WIKI_URL,
|
||||
"user": None,
|
||||
"render_header": RENDER_HEADER,
|
||||
"current_role": None,
|
||||
}
|
||||
|
||||
def __init__(self, request: HttpRequest, additional_context: dict = {}):
|
||||
self.context["language"] = request.LANGUAGE_CODE
|
||||
self.context["user"] = request.user
|
||||
self.__handle_current_role(request)
|
||||
|
||||
# Add additional context, derived from given parameters
|
||||
self.context.update(additional_context)
|
||||
|
||||
def __handle_current_role(self, request: HttpRequest):
|
||||
""" Reads/Writes current role from/to session
|
||||
|
||||
Args:
|
||||
request (HttpRequest): The incoming request
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
# Store current role in session object to reduce amount of db access
|
||||
current_role = request.session.get("current_role", {})
|
||||
|
||||
if len(current_role) == 0 and request.user.is_authenticated:
|
||||
role_group = RoleGroup.get_users_role_groups(request.user).first()
|
||||
current_role = set_session_user_role(request, role_group)
|
||||
|
||||
self.context["current_role"] = current_role
|
||||
@@ -0,0 +1,94 @@
|
||||
"""
|
||||
Author: Michel Peltriaux
|
||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||
Created on: 16.11.20
|
||||
|
||||
"""
|
||||
|
||||
from functools import wraps
|
||||
|
||||
from django.contrib import messages
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.shortcuts import redirect
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from konova.models import RoleGroup
|
||||
from konova.utils.session import get_session_user_role
|
||||
from organisation.enums import RoleTypeEnum
|
||||
from process.enums import PROCESS_EDITABLE_STATE
|
||||
from process.models import Process
|
||||
|
||||
|
||||
def staff_required(function):
|
||||
"""
|
||||
A decorator for functions which shall only be usable for staff members of the system
|
||||
"""
|
||||
@wraps(function)
|
||||
def wrap(request, *args, **kwargs):
|
||||
user = request.user
|
||||
if user.is_staff:
|
||||
return function(request, *args, **kwargs)
|
||||
else:
|
||||
messages.info(request, _("You need to be staff to perform this action!"))
|
||||
return redirect(request.META.get("HTTP_REFERER", reverse("home")))
|
||||
return wrap
|
||||
|
||||
|
||||
def superuser_required(function):
|
||||
"""
|
||||
A decorator for functions which shall only be usable for superusers of the system
|
||||
"""
|
||||
@wraps(function)
|
||||
def wrap(request, *args, **kwargs):
|
||||
user = request.user
|
||||
if user.is_superuser:
|
||||
return function(request, *args, **kwargs)
|
||||
else:
|
||||
messages.info(request, _("You need to be administrator to perform this action!"))
|
||||
return redirect(request.META.get("HTTP_REFERER", reverse("home")))
|
||||
return wrap
|
||||
|
||||
|
||||
def resolve_user_role(function):
|
||||
"""
|
||||
A decorator for functions to resolve the current user role and store it in the user object
|
||||
"""
|
||||
@wraps(function)
|
||||
def wrap(request, *args, **kwargs):
|
||||
user = request.user
|
||||
role = get_session_user_role(request)
|
||||
try:
|
||||
role = RoleGroup.objects.get(id=role.get("id", -1))
|
||||
user.current_role = role
|
||||
except ObjectDoesNotExist:
|
||||
user.current_role = None
|
||||
return function(request, *args, **kwargs)
|
||||
return wrap
|
||||
|
||||
|
||||
def valid_process_role_required(function):
|
||||
"""
|
||||
A decorator for functions to check whether the user has a valid role selected
|
||||
"""
|
||||
@wraps(function)
|
||||
def wrap(request, *args, **kwargs):
|
||||
user = request.user
|
||||
if user.current_role is None:
|
||||
role = get_session_user_role(request)
|
||||
else:
|
||||
role = user.current_role
|
||||
try:
|
||||
process = Process.objects.get(id=kwargs.get("id"))
|
||||
editable = PROCESS_EDITABLE_STATE.get(process.state)
|
||||
role_enum = RoleTypeEnum[role.role.type]
|
||||
if role_enum in editable:
|
||||
return function(request, *args, **kwargs)
|
||||
else:
|
||||
messages.error(request, _("Your current role is not allowed to do this"))
|
||||
return redirect(request.META.get("HTTP_REFERER", "home"))
|
||||
except ObjectDoesNotExist:
|
||||
process = None
|
||||
return function(request, *args, **kwargs)
|
||||
return wrap
|
||||
@@ -0,0 +1,40 @@
|
||||
"""
|
||||
Author: Michel Peltriaux
|
||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||
Created on: 17.11.20
|
||||
|
||||
"""
|
||||
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class BaseEnum(Enum):
|
||||
""" Provides basic functionality for Enums
|
||||
|
||||
"""
|
||||
@classmethod
|
||||
def as_choices(cls, drop_empty_choice: bool = False):
|
||||
empty_choice = [] if drop_empty_choice else [(None, "---")]
|
||||
choices = empty_choice + [(enum.value, enum.name) for enum in cls]
|
||||
return choices
|
||||
|
||||
|
||||
class UnitEnum(BaseEnum):
|
||||
"""
|
||||
Predefines units for selection
|
||||
"""
|
||||
mm = "mm"
|
||||
dm = "dm"
|
||||
cm = "cm"
|
||||
m = "m"
|
||||
km = "km"
|
||||
|
||||
qmm = "qmm"
|
||||
qdm = "qdm"
|
||||
qcm = "qcm"
|
||||
qm = "qm"
|
||||
qkm = "qkm"
|
||||
ha = "ha"
|
||||
|
||||
st = "St." # pieces
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
"""
|
||||
Author: Michel Peltriaux
|
||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||
Created on: 16.11.20
|
||||
|
||||
"""
|
||||
|
||||
from abc import abstractmethod
|
||||
|
||||
from django import forms
|
||||
from django.http import HttpRequest
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from konova.models import RoleGroup
|
||||
from konova.utils.session import set_session_user_role
|
||||
from organisation.settings import ROLE_TYPE_STRINGS
|
||||
|
||||
|
||||
class BaseForm(forms.Form):
|
||||
"""
|
||||
Basic form for that holds attributes needed in all other forms
|
||||
"""
|
||||
action_url = None
|
||||
form_title = None
|
||||
cancel_redirect = None
|
||||
form_caption = None
|
||||
instance = None # The data holding model object
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.instance = kwargs.pop("instance", None)
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
@abstractmethod
|
||||
def save(self):
|
||||
# To be implemented in subclasses!
|
||||
pass
|
||||
|
||||
def disable_form_field(self, field: str):
|
||||
"""
|
||||
Disables a form field for user editing
|
||||
"""
|
||||
self.fields[field].widget.attrs["readonly"] = True
|
||||
self.fields[field].disabled = True
|
||||
self.fields[field].widget.attrs["title"] = _("Not editable")
|
||||
|
||||
def initialize_form_field(self, field: str, val):
|
||||
"""
|
||||
Initializes a form field with a value
|
||||
"""
|
||||
self.fields[field].initial = val
|
||||
|
||||
def load_initial_data(self, form_data: dict, disabled_fields: list):
|
||||
""" Initializes form data from instance
|
||||
|
||||
Inserts instance data into form and disables form fields
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
if self.instance is None:
|
||||
return
|
||||
for k, v in form_data.items():
|
||||
self.initialize_form_field(k, v)
|
||||
for field in disabled_fields:
|
||||
self.disable_form_field(field)
|
||||
|
||||
|
||||
class RemoveForm(BaseForm):
|
||||
check = forms.BooleanField(
|
||||
label=_("Confirm"),
|
||||
label_suffix=_(""),
|
||||
required=True,
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.object_to_remove = kwargs.pop("object_to_remove", None)
|
||||
self.remove_post_url = kwargs.pop("remove_post_url", "")
|
||||
self.cancel_url = kwargs.pop("cancel_url", "")
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.form_title = _("Remove")
|
||||
if self.object_to_remove is not None:
|
||||
self.form_caption = _("You are about to remove {} {}").format(self.object_to_remove.__class__.__name__, self.object_to_remove)
|
||||
self.action_url = self.remove_post_url
|
||||
self.cancel_redirect = self.cancel_url
|
||||
|
||||
def is_checked(self) -> bool:
|
||||
return self.cleaned_data.get("check", False)
|
||||
|
||||
def save(self):
|
||||
if self.object_to_remove is not None and self.is_checked():
|
||||
self.object_to_remove.is_active = False
|
||||
self.object_to_remove.is_deleted = True
|
||||
self.object_to_remove.save()
|
||||
return self.object_to_remove
|
||||
|
||||
|
||||
class ChangeUserRoleForm(BaseForm):
|
||||
"""
|
||||
Form for a user to change the current role
|
||||
"""
|
||||
role = forms.ChoiceField(
|
||||
label=_("You are working as"),
|
||||
label_suffix="",
|
||||
choices=[],
|
||||
widget=forms.Select(
|
||||
attrs={
|
||||
"onchange": "submit();",
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
user = kwargs.pop("user", None)
|
||||
super().__init__(*args, **kwargs)
|
||||
self.action_url = reverse("home")
|
||||
self.cancel_redirect = reverse("home")
|
||||
|
||||
role_groups = RoleGroup.get_users_role_groups(user)
|
||||
choices = []
|
||||
for group in role_groups:
|
||||
choices.append(
|
||||
(group.id, "{} ({})".format(ROLE_TYPE_STRINGS.get(group.role.type, None), group.organisation))
|
||||
)
|
||||
self.fields["role"].choices = choices
|
||||
|
||||
def save(self, request: HttpRequest) -> RoleGroup:
|
||||
""" Custom save method for storing the newly selected role
|
||||
|
||||
Args:
|
||||
request (HttpRequest):
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
role_group = RoleGroup.get_users_role_groups(request.user).get(id=self.cleaned_data.get("role", -1))
|
||||
set_session_user_role(request, role_group)
|
||||
return role_group
|
||||
@@ -0,0 +1,153 @@
|
||||
"""
|
||||
Author: Michel Peltriaux
|
||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||
Created on: 15.12.20
|
||||
|
||||
"""
|
||||
from getpass import getpass
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.management import BaseCommand
|
||||
from django.db import transaction
|
||||
|
||||
from konova.management.commands.setup_test_data import TEST_ORGANISATION_DATA, TEST_ROLE_GROUPS_DATA
|
||||
from konova.models import RoleType, RoleGroup
|
||||
from organisation.enums import RoleTypeEnum
|
||||
from organisation.models import Organisation
|
||||
|
||||
CREATED_TEMPLATE = "{} created"
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Initializes database with basic data"
|
||||
|
||||
def handle(self, *args, **options):
|
||||
try:
|
||||
with transaction.atomic():
|
||||
self.__init_superuser()
|
||||
self.__init_test_organisation()
|
||||
self.__init_role_types()
|
||||
self.__init_role_groups()
|
||||
except KeyboardInterrupt:
|
||||
self.__break_line()
|
||||
exit(-1)
|
||||
|
||||
def __init_superuser(self):
|
||||
""" Create a superuser by user prompt input
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
self.stdout.write(
|
||||
self.style.WARNING(
|
||||
"--- Superuser ---",
|
||||
)
|
||||
)
|
||||
username = input("Superuser name: ")
|
||||
if User.objects.filter(username=username).exists():
|
||||
self.stdout.write(
|
||||
self.style.ERROR(
|
||||
"Name already taken!"
|
||||
)
|
||||
)
|
||||
exit(-1)
|
||||
pw = getpass("Password: ")
|
||||
pw_confirm = getpass("Confirm password : ")
|
||||
if pw != pw_confirm:
|
||||
self.stdout.write(
|
||||
self.style.ERROR(
|
||||
"Passwords did not match!"
|
||||
)
|
||||
)
|
||||
exit(-1)
|
||||
|
||||
# Create superuser
|
||||
superuser = User()
|
||||
superuser.username = username
|
||||
superuser.is_superuser = True
|
||||
superuser.is_staff = True
|
||||
superuser.set_password(pw)
|
||||
superuser.save()
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS(
|
||||
"Superuser {} created".format(username)
|
||||
)
|
||||
)
|
||||
self.__break_line()
|
||||
|
||||
def __init_role_types(self):
|
||||
""" Initializes available role types according to RoleTypeEnum
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
self.stdout.write(
|
||||
self.style.WARNING(
|
||||
"--- Role types ---"
|
||||
)
|
||||
)
|
||||
for role_type_enum in RoleTypeEnum:
|
||||
role_type = RoleType.objects.get_or_create(
|
||||
type=role_type_enum.value
|
||||
)[0]
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS(
|
||||
CREATED_TEMPLATE.format(role_type.type)
|
||||
)
|
||||
)
|
||||
self.__break_line()
|
||||
|
||||
def __init_test_organisation(self):
|
||||
""" Creates test organisations from predefined data
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
self.stdout.write(
|
||||
self.style.WARNING(
|
||||
"--- Organisations ---"
|
||||
)
|
||||
)
|
||||
for org in TEST_ORGANISATION_DATA:
|
||||
db_org = Organisation.objects.get_or_create(
|
||||
**org
|
||||
)[0]
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS(
|
||||
CREATED_TEMPLATE.format(db_org.name)
|
||||
)
|
||||
)
|
||||
self.__break_line()
|
||||
|
||||
def __init_role_groups(self):
|
||||
""" Creates test role groups from predefined data
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
self.stdout.write(
|
||||
self.style.WARNING(
|
||||
"--- Role Groups ---"
|
||||
)
|
||||
)
|
||||
for group_data in TEST_ROLE_GROUPS_DATA:
|
||||
group_data["organisation"] = Organisation.objects.get(name=group_data["organisation"])
|
||||
group_data["role"] = RoleType.objects.get(type=group_data["role"])
|
||||
group = RoleGroup.objects.get_or_create(
|
||||
**group_data
|
||||
)[0]
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS(
|
||||
CREATED_TEMPLATE.format(group.name)
|
||||
)
|
||||
)
|
||||
self.__break_line()
|
||||
|
||||
def __break_line(self):
|
||||
""" Simply prints a line break
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
self.stdout.write("\n")
|
||||
@@ -0,0 +1,58 @@
|
||||
"""
|
||||
Author: Michel Peltriaux
|
||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||
Created on: 15.12.20
|
||||
|
||||
"""
|
||||
from organisation.enums import OrganisationTypeEnum, RoleTypeEnum
|
||||
|
||||
TEST_ORGANISATION_DATA = [
|
||||
{
|
||||
"name": "Test_Official_1",
|
||||
"is_active": True,
|
||||
"is_deleted": False,
|
||||
"type": OrganisationTypeEnum.OFFICIAL.value,
|
||||
},
|
||||
{
|
||||
"name": "Test_Official_2",
|
||||
"is_active": True,
|
||||
"is_deleted": False,
|
||||
"type": OrganisationTypeEnum.OFFICIAL.value,
|
||||
},
|
||||
{
|
||||
"name": "Test_NGO_1",
|
||||
"is_active": True,
|
||||
"is_deleted": False,
|
||||
"type": OrganisationTypeEnum.NGO.value,
|
||||
},
|
||||
{
|
||||
"name": "Test_Company_1",
|
||||
"is_active": True,
|
||||
"is_deleted": False,
|
||||
"type": OrganisationTypeEnum.COMPANY.value,
|
||||
},
|
||||
]
|
||||
|
||||
TEST_ROLE_GROUPS_DATA = [
|
||||
{
|
||||
"name": "Registration office Test_Official_1",
|
||||
"organisation": "Test_Official_1",
|
||||
"role": RoleTypeEnum.REGISTRATIONOFFICE.value,
|
||||
},
|
||||
{
|
||||
"name": "Licensing authority Test_Official_1",
|
||||
"organisation": "Test_Official_1",
|
||||
"role": RoleTypeEnum.LICENSINGAUTHORITY.value,
|
||||
},
|
||||
{
|
||||
"name": "Dataprovider Test_Official_2",
|
||||
"organisation": "Test_Official_2",
|
||||
"role": RoleTypeEnum.LICENSINGAUTHORITY.value,
|
||||
},
|
||||
{
|
||||
"name": "Dataprovider Test_Company_1",
|
||||
"organisation": "Test_Company_1",
|
||||
"role": RoleTypeEnum.DATAPROVIDER.value,
|
||||
},
|
||||
]
|
||||
@@ -0,0 +1,104 @@
|
||||
"""
|
||||
Author: Michel Peltriaux
|
||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||
Created on: 17.11.20
|
||||
|
||||
"""
|
||||
import uuid
|
||||
|
||||
from django.contrib.auth.models import User, Group
|
||||
from django.db import models
|
||||
from django.db.models import QuerySet
|
||||
|
||||
from organisation.enums import RoleTypeEnum
|
||||
|
||||
|
||||
class BaseResource(models.Model):
|
||||
"""
|
||||
A basic resource model, which defines attributes for every derived model
|
||||
"""
|
||||
id = models.UUIDField(
|
||||
primary_key=True,
|
||||
default=uuid.uuid4,
|
||||
)
|
||||
is_active = models.BooleanField(default=True)
|
||||
is_deleted = models.BooleanField(default=False)
|
||||
created_on = models.DateTimeField(auto_now_add=True, null=True)
|
||||
created_by = models.ForeignKey(User, null=True, on_delete=models.SET_NULL)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
|
||||
class BaseObject(BaseResource):
|
||||
"""
|
||||
A basic object model, which specifies BaseResource.
|
||||
|
||||
Mainly used for intervention, compensation, ecoaccount
|
||||
"""
|
||||
identifier = models.CharField(max_length=1000, null=True, blank=True)
|
||||
title = models.CharField(max_length=1000, null=True, blank=True)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
|
||||
class Deadline(BaseResource):
|
||||
"""
|
||||
Defines a deadline, which can be used to define dates with a semantic meaning
|
||||
"""
|
||||
type = models.CharField(max_length=500, null=True, blank=True)
|
||||
date = models.DateField(null=True, blank=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.type
|
||||
|
||||
|
||||
class Document(BaseResource):
|
||||
"""
|
||||
Documents can be attached to process, compensation or intervention for uploading legal documents or pictures.
|
||||
"""
|
||||
date_of_creation = models.DateField()
|
||||
document = models.FileField()
|
||||
comment = models.TextField()
|
||||
|
||||
|
||||
class RoleType(BaseResource):
|
||||
"""
|
||||
Defines different role types
|
||||
"""
|
||||
type = models.CharField(max_length=255, choices=RoleTypeEnum.as_choices(drop_empty_choice=True), unique=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.type
|
||||
|
||||
|
||||
class RoleGroup(Group):
|
||||
"""
|
||||
Role groups are specialized groups which hold information on which users are related to a certain organisation and
|
||||
a role
|
||||
"""
|
||||
organisation = models.ForeignKey("organisation.Organisation", on_delete=models.CASCADE)
|
||||
role = models.ForeignKey(RoleType, on_delete=models.CASCADE)
|
||||
|
||||
class Meta:
|
||||
unique_together = [
|
||||
["organisation", "role", ]
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def get_users_role_groups(user: User) -> QuerySet:
|
||||
""" Get all role groups of a given user
|
||||
|
||||
Args:
|
||||
user (User): The user
|
||||
|
||||
Returns:
|
||||
qs (QuerySet)
|
||||
"""
|
||||
if user.is_anonymous:
|
||||
return RoleGroup.objects.none()
|
||||
return RoleGroup.objects.filter(
|
||||
user=user
|
||||
)
|
||||
@@ -0,0 +1,50 @@
|
||||
"""
|
||||
Django settings for konova project.
|
||||
|
||||
Generated by 'django-admin startproject' using Django 3.1.2.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/3.1/topics/settings/
|
||||
|
||||
For the full list of settings and their values, see
|
||||
https://docs.djangoproject.com/en/3.1/ref/settings/
|
||||
"""
|
||||
|
||||
# Load other settings
|
||||
from konova.sub_settings.django_settings import *
|
||||
|
||||
# Num of days if user enables Remember-me on login
|
||||
KEEP_LOGGED_DURATION = 30
|
||||
|
||||
# German DateTime string format
|
||||
STRF_DATE_TIME = "%d.%m.%Y %H:%M:%S"
|
||||
|
||||
# Tables
|
||||
RESULTS_PER_PAGE_PARAM = "rpp"
|
||||
PAGE_PARAM = "page"
|
||||
PAGE_SIZE_OPTIONS = [5, 10, 15, 20, 25, 30, 50, 75, 100]
|
||||
PAGE_SIZE_OPTIONS_TUPLES = [
|
||||
(5, 5),
|
||||
(10, 10),
|
||||
(15, 15),
|
||||
(20, 20),
|
||||
(25, 25),
|
||||
(30, 30),
|
||||
(50, 50),
|
||||
(75, 75),
|
||||
(100, 100),
|
||||
]
|
||||
PAGE_SIZE_DEFAULT = 5
|
||||
PAGE_SIZE_MAX = 100
|
||||
PAGE_DEFAULT = 1
|
||||
|
||||
# SSO settings
|
||||
SSO_SERVER_BASE = "http://127.0.0.1:8000/"
|
||||
SSO_SERVER = "{}sso/".format(SSO_SERVER_BASE)
|
||||
SSO_PRIVATE_KEY = "CHANGE_ME"
|
||||
SSO_PUBLIC_KEY = "CHANGE_ME"
|
||||
|
||||
# MAPS
|
||||
DEFAULT_LAT = 50.00
|
||||
DEFAULT_LON = 7.00
|
||||
DEFAULT_ZOOM = 8.0
|
||||
@@ -0,0 +1,57 @@
|
||||
.body-content{
|
||||
min-height: 75vh;
|
||||
}
|
||||
|
||||
.note{
|
||||
font-size: 0.75rem;
|
||||
color: lightgray;
|
||||
}
|
||||
|
||||
.label-required{
|
||||
color: red;
|
||||
}
|
||||
|
||||
table{
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.footer{
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.error{
|
||||
color: #D8000C !important;
|
||||
background-color: #FFBABA !important;
|
||||
}
|
||||
|
||||
i.true{
|
||||
color: green;
|
||||
}
|
||||
i.false{
|
||||
color: red;
|
||||
}
|
||||
|
||||
.button{
|
||||
margin: 0.5625rem;
|
||||
}
|
||||
|
||||
.action-col{
|
||||
max-width: 8rem;
|
||||
}
|
||||
|
||||
.action-col .button{
|
||||
margin: 0.1rem;
|
||||
}
|
||||
|
||||
.user-role{
|
||||
background: url('../images/menu-bg.png') repeat #871d33;
|
||||
color: white;
|
||||
padding: 0.5rem 0;
|
||||
border-bottom: 4px solid #8e8e8e;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.user-role > a {
|
||||
color: white;
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
.info, .success, .warning, .error, .validation {
|
||||
border: 1px solid;
|
||||
margin: 10px 0px;
|
||||
padding:15px 10px;
|
||||
}
|
||||
|
||||
.info:hover,
|
||||
.success:hover,
|
||||
.warning:hover,
|
||||
.error:hover,
|
||||
.validation:hover {
|
||||
cursor: default;
|
||||
filter: brightness(1.2);
|
||||
}
|
||||
|
||||
.info {
|
||||
color: #00529B;
|
||||
background-color: #BDE5F8;
|
||||
/*
|
||||
background-image: url('../images/knobs/info.png');
|
||||
*/
|
||||
}
|
||||
.success {
|
||||
color: #4F8A10;
|
||||
background-color: #DFF2BF;
|
||||
/*
|
||||
background-image:url('../images/knobs/success.png');
|
||||
*/
|
||||
}
|
||||
.warning {
|
||||
color: #9F6000;
|
||||
background-color: #FEEFB3;
|
||||
/*
|
||||
background-image: url('../images/knobs/warning.png');
|
||||
*/
|
||||
}
|
||||
.error {
|
||||
color: #D8000C;
|
||||
background-color: #FFBABA;
|
||||
/*
|
||||
background-image: url('../images/knobs/error.png');
|
||||
*/
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 6.3 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.2 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 935 B |
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
Vendored
+2
@@ -0,0 +1,2 @@
|
||||
/*! jQuery v3.5.1 | (c) JS Foundation and other contributors | jquery.org/license */
|
||||
!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.5.1",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0<t&&t-1 in e)}S.fn=S.prototype={jquery:f,constructor:S,length:0,toArray:function(){return s.call(this)},get:function(e){return null==e?s.call(this):e<0?this[e+this.length]:this[e]},pushStack:function(e){var t=S.merge(this.constructor(),e);return t.prevObject=this,t},each:function(e){return S.each(this,e)},map:function(n){return this.pushStack(S.map(this,function(e,t){return n.call(e,t,e)}))},slice:function(){return this.pushStack(s.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},even:function(){return this.pushStack(S.grep(this,function(e,t){return(t+1)%2}))},odd:function(){return this.pushStack(S.grep(this,function(e,t){return t%2}))},eq:function(e){var t=this.length,n=+e+(e<0?t:0);return this.pushStack(0<=n&&n<t?[this[n]]:[])},end:function(){return this.prevObject||this.constructor()},push:u,sort:t.sort,splice:t.splice},S.extend=S.fn.extend=function(){var e,t,n,r,i,o,a=arguments[0]||{},s=1,u=arguments.length,l=!1;for("boolean"==typeof a&&(l=a,a=arguments[s]||{},s++),"object"==typeof a||m(a)||(a={}),s===u&&(a=this,s--);s<u;s++)if(null!=(e=arguments[s]))for(t in e)r=e[t],"__proto__"!==t&&a!==r&&(l&&r&&(S.isPlainObject(r)||(i=Array.isArray(r)))?(n=a[t],o=i&&!Array.isArray(n)?[]:i||S.isPlainObject(n)?n:{},i=!1,a[t]=S.extend(l,o,r)):void 0!==r&&(a[t]=r));return a},S.extend({expando:"jQuery"+(f+Math.random()).replace(/\D/g,""),isReady:!0,error:function(e){throw new Error(e)},noop:function(){},isPlainObject:function(e){var t,n;return!(!e||"[object Object]"!==o.call(e))&&(!(t=r(e))||"function"==typeof(n=v.call(t,"constructor")&&t.constructor)&&a.call(n)===l)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},globalEval:function(e,t,n){b(e,{nonce:t&&t.nonce},n)},each:function(e,t){var n,r=0;if(p(e)){for(n=e.length;r<n;r++)if(!1===t.call(e[r],r,e[r]))break}else for(r in e)if(!1===t.call(e[r],r,e[r]))break;return e},makeArray:function(e,t){var n=t||[];return null!=e&&(p(Object(e))?S.merge(n,"string"==typeof e?[e]:e):u.call(n,e)),n},inArray:function(e,t,n){return null==t?-1:i.call(t,e,n)},merge:function(e,t){for(var n=+t.length,r=0,i=e.length;r<n;r++)e[i++]=t[r];return e.length=i,e},grep:function(e,t,n){for(var r=[],i=0,o=e.length,a=!n;i<o;i++)!t(e[i],i)!==a&&r.push(e[i]);return r},map:function(e,t,n){var r,i,o=0,a=[];if(p(e))for(r=e.length;o<r;o++)null!=(i=t(e[o],o,n))&&a.push(i);else for(o in e)null!=(i=t(e[o],o,n))&&a.push(i);return g(a)},guid:1,support:y}),"function"==typeof Symbol&&(S.fn[Symbol.iterator]=t[Symbol.iterator]),S.each("Boolean Number String Function Array Date RegExp Object Error Symbol".split(" "),function(e,t){n["[object "+t+"]"]=t.toLowerCase()});var d=function(n){var e,d,b,o,i,h,f,g,w,u,l,T,C,a,E,v,s,c,y,S="sizzle"+1*new Date,p=n.document,k=0,r=0,m=ue(),x=ue(),A=ue(),N=ue(),D=function(e,t){return e===t&&(l=!0),0},j={}.hasOwnProperty,t=[],q=t.pop,L=t.push,H=t.push,O=t.slice,P=function(e,t){for(var n=0,r=e.length;n<r;n++)if(e[n]===t)return n;return-1},R="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",M="[\\x20\\t\\r\\n\\f]",I="(?:\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\[^\\r\\n\\f]|[\\w-]|[^\0-\\x7f])+",W="\\["+M+"*("+I+")(?:"+M+"*([*^$|!~]?=)"+M+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+I+"))|)"+M+"*\\]",F=":("+I+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+W+")*)|.*)\\)|)",B=new RegExp(M+"+","g"),$=new RegExp("^"+M+"+|((?:^|[^\\\\])(?:\\\\.)*)"+M+"+$","g"),_=new RegExp("^"+M+"*,"+M+"*"),z=new RegExp("^"+M+"*([>+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-lLine truncated
|
||||
Vendored
+13
@@ -0,0 +1,13 @@
|
||||
/*! jQuery UI - v1.12.1 - 2016-09-14
|
||||
* http://jqueryui.com
|
||||
* Includes: widget.js, position.js, data.js, disable-selection.js, effect.js, effects/effect-blind.js, effects/effect-bounce.js, effects/effect-clip.js, effects/effect-drop.js, effects/effect-explode.js, effects/effect-fade.js, effects/effect-fold.js, effects/effect-highlight.js, effects/effect-puff.js, effects/effect-pulsate.js, effects/effect-scale.js, effects/effect-shake.js, effects/effect-size.js, effects/effect-slide.js, effects/effect-transfer.js, focusable.js, form-reset-mixin.js, jquery-1-7.js, keycode.js, labels.js, scroll-parent.js, tabbable.js, unique-id.js, widgets/accordion.js, widgets/autocomplete.js, widgets/button.js, widgets/checkboxradio.js, widgets/controlgroup.js, widgets/datepicker.js, widgets/dialog.js, widgets/draggable.js, widgets/droppable.js, widgets/menu.js, widgets/mouse.js, widgets/progressbar.js, widgets/resizable.js, widgets/selectable.js, widgets/selectmenu.js, widgets/slider.js, widgets/sortable.js, widgets/spinner.js, widgets/tabs.js, widgets/tooltip.js
|
||||
* Copyright jQuery Foundation and other contributors; Licensed MIT */
|
||||
|
||||
(function(t){"function"==typeof define&&define.amd?define(["jquery"],t):t(jQuery)})(function(t){function e(t){for(var e=t.css("visibility");"inherit"===e;)t=t.parent(),e=t.css("visibility");return"hidden"!==e}function i(t){for(var e,i;t.length&&t[0]!==document;){if(e=t.css("position"),("absolute"===e||"relative"===e||"fixed"===e)&&(i=parseInt(t.css("zIndex"),10),!isNaN(i)&&0!==i))return i;t=t.parent()}return 0}function s(){this._curInst=null,this._keyEvent=!1,this._disabledInputs=[],this._datepickerShowing=!1,this._inDialog=!1,this._mainDivId="ui-datepicker-div",this._inlineClass="ui-datepicker-inline",this._appendClass="ui-datepicker-append",this._triggerClass="ui-datepicker-trigger",this._dialogClass="ui-datepicker-dialog",this._disableClass="ui-datepicker-disabled",this._unselectableClass="ui-datepicker-unselectable",this._currentClass="ui-datepicker-current-day",this._dayOverClass="ui-datepicker-days-cell-over",this.regional=[],this.regional[""]={closeText:"Done",prevText:"Prev",nextText:"Next",currentText:"Today",monthNames:["January","February","March","April","May","June","July","August","September","October","November","December"],monthNamesShort:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],dayNames:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],dayNamesShort:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],dayNamesMin:["Su","Mo","Tu","We","Th","Fr","Sa"],weekHeader:"Wk",dateFormat:"mm/dd/yy",firstDay:0,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""},this._defaults={showOn:"focus",showAnim:"fadeIn",showOptions:{},defaultDate:null,appendText:"",buttonText:"...",buttonImage:"",buttonImageOnly:!1,hideIfNoPrevNext:!1,navigationAsDateFormat:!1,gotoCurrent:!1,changeMonth:!1,changeYear:!1,yearRange:"c-10:c+10",showOtherMonths:!1,selectOtherMonths:!1,showWeek:!1,calculateWeek:this.iso8601Week,shortYearCutoff:"+10",minDate:null,maxDate:null,duration:"fast",beforeShowDay:null,beforeShow:null,onSelect:null,onChangeMonthYear:null,onClose:null,numberOfMonths:1,showCurrentAtPos:0,stepMonths:1,stepBigMonths:12,altField:"",altFormat:"",constrainInput:!0,showButtonPanel:!1,autoSize:!1,disabled:!1},t.extend(this._defaults,this.regional[""]),this.regional.en=t.extend(!0,{},this.regional[""]),this.regional["en-US"]=t.extend(!0,{},this.regional.en),this.dpDiv=n(t("<div id='"+this._mainDivId+"' class='ui-datepicker ui-widget ui-widget-content ui-helper-clearfix ui-corner-all'></div>"))}function n(e){var i="button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a";return e.on("mouseout",i,function(){t(this).removeClass("ui-state-hover"),-1!==this.className.indexOf("ui-datepicker-prev")&&t(this).removeClass("ui-datepicker-prev-hover"),-1!==this.className.indexOf("ui-datepicker-next")&&t(this).removeClass("ui-datepicker-next-hover")}).on("mouseover",i,o)}function o(){t.datepicker._isDisabledDatepicker(m.inline?m.dpDiv.parent()[0]:m.input[0])||(t(this).parents(".ui-datepicker-calendar").find("a").removeClass("ui-state-hover"),t(this).addClass("ui-state-hover"),-1!==this.className.indexOf("ui-datepicker-prev")&&t(this).addClass("ui-datepicker-prev-hover"),-1!==this.className.indexOf("ui-datepicker-next")&&t(this).addClass("ui-datepicker-next-hover"))}function a(e,i){t.extend(e,i);for(var s in i)null==i[s]&&(e[s]=i[s]);return e}function r(t){return function(){var e=this.element.val();t.apply(this,arguments),this._refresh(),e!==this.element.val()&&this._trigger("change")}}t.ui=t.ui||{},t.ui.version="1.12.1";var h=0,l=Array.prototype.slice;t.cleanData=function(e){return function(i){var s,n,o;for(o=0;null!=(n=i[o]);o++)try{s=t._data(n,"events"),s&&s.remove&&t(n).triggerHandler("remove")}catch(a){}e(i)}}(t.cleanData),t.widget=function(e,i,s){var n,o,a,r={},h=e.split(".")[0];e=e.split(".")[1];var l=h+"-"+e;return s||(s=i,i=t.Widget),t.isArray(s)&&(s=t.extend.apply(null,[{}].concat(s))),t.expr[":"][l.toLowerCase()]=function(e){return!!t.data(e,l)},t[h]=t[h]||{},n=t[h][e],o=t[h][e]=function(t,e){return this._createWidget?(arguments.length&&this._createWidget(t,e),void 0):new o(t,e)},t.extend(o,n,{version:s.version,_proto:t.extend({},s),_childConstructors:[]}),a=new i,a.options=t.widget.extend({},a.options),t.each(s,function(e,s){return t.isFunction(s)?(r[e]=function(){function t(){return i.prototype[e].apply(this,arguments)}function n(t){return i.prototype[e].apply(this,t)}return function(){var e,i=this._super,o=this._superApply;return this._super=t,this._superApply=n,e=s.apply(this,arguments),this._super=i,this._superApply=o,e}}(),void 0):(r[e]=s,void 0)}),o.prototype=t.widget.extend(a,{widgetEventPrefix:n?a.widgetEventPrefix||e:e},r,{constructor:o,namespace:h,widgetName:e,widgetFullName:l}),n?(t.each(n._childConstructors,function(e,i){var s=i.prototype;t.widget(s.namespace+"."+s.widgetName,o,i._proto)}),delete n._childConstructors):i._childConstructors.push(o),t.widget.bridge(e,o),o},t.widget.extend=function(e){for(var i,s,n=l.call(arguments,1),o=0,a=n.length;a>Line truncated
|
||||
}}(t.fn.show),hide:function(t){return function(s){if(i(s))return t.apply(this,arguments);var n=e.apply(this,arguments);return n.mode="hide",this.effect.call(this,n)}}(t.fn.hide),toggle:function(t){return function(s){if(i(s)||"boolean"==typeof s)return t.apply(this,arguments);var n=e.apply(this,arguments);return n.mode="toggle",this.effect.call(this,n)}}(t.fn.toggle),cssUnit:function(e){var i=this.css(e),s=[];return t.each(["em","px","%","pt"],function(t,e){i.indexOf(e)>0&&(s=[parseFloat(i),e])}),s},cssClip:function(t){return t?this.css("clip","rect("+t.top+"px "+t.right+"px "+t.bottom+"px "+t.left+"px)"):s(this.css("clip"),this)},transfer:function(e,i){var s=t(this),n=t(e.to),o="fixed"===n.css("position"),a=t("body"),r=o?a.scrollTop():0,h=o?a.scrollLeft():0,l=n.offset(),c={top:l.top-r,left:l.left-h,height:n.innerHeight(),width:n.innerWidth()},u=s.offset(),d=t("<div class='ui-effects-transfer'></div>").appendTo("body").addClass(e.className).css({top:u.top-r,left:u.left-h,height:s.innerHeight(),width:s.innerWidth(),position:o?"fixed":"absolute"}).animate(c,e.duration,e.easing,function(){d.remove(),t.isFunction(i)&&i()})}}),t.fx.step.clip=function(e){e.clipInit||(e.start=t(e.elem).cssClip(),"string"==typeof e.end&&(e.end=s(e.end,e.elem)),e.clipInit=!0),t(e.elem).cssClip({top:e.pos*(e.end.top-e.start.top)+e.start.top,right:e.pos*(e.end.right-e.start.right)+e.start.right,bottom:e.pos*(e.end.bottom-e.start.bottom)+e.start.bottom,left:e.pos*(e.end.left-e.start.left)+e.start.left})}}(),function(){var e={};t.each(["Quad","Cubic","Quart","Quint","Expo"],function(t,i){e[i]=function(e){return Math.pow(e,t+2)}}),t.extend(e,{Sine:function(t){return 1-Math.cos(t*Math.PI/2)},Circ:function(t){return 1-Math.sqrt(1-t*t)},Elastic:function(t){return 0===t||1===t?t:-Math.pow(2,8*(t-1))*Math.sin((80*(t-1)-7.5)*Math.PI/15)},Back:function(t){return t*t*(3*t-2)},Bounce:function(t){for(var e,i=4;((e=Math.pow(2,--i))-1)/11>t;);return 1/Math.pow(4,3-i)-7.5625*Math.pow((3*e-2)/22-t,2)}}),t.each(e,function(e,i){t.easing["easeIn"+e]=i,t.easing["easeOut"+e]=function(t){return 1-i(1-t)},t.easing["easeInOut"+e]=function(t){return.5>t?i(2*t)/2:1-i(-2*t+2)/2}})}();var f=t.effects;t.effects.define("blind","hide",function(e,i){var s={up:["bottom","top"],vertical:["bottom","top"],down:["top","bottom"],left:["right","left"],horizontal:["right","left"],right:["left","right"]},n=t(this),o=e.direction||"up",a=n.cssClip(),r={clip:t.extend({},a)},h=t.effects.createPlaceholder(n);r.clip[s[o][0]]=r.clip[s[o][1]],"show"===e.mode&&(n.cssClip(r.clip),h&&h.css(t.effects.clipToBox(r)),r.clip=a),h&&h.animate(t.effects.clipToBox(r),e.duration,e.easing),n.animate(r,{queue:!1,duration:e.duration,easing:e.easing,complete:i})}),t.effects.define("bounce",function(e,i){var s,n,o,a=t(this),r=e.mode,h="hide"===r,l="show"===r,c=e.direction||"up",u=e.distance,d=e.times||5,p=2*d+(l||h?1:0),f=e.duration/p,g=e.easing,m="up"===c||"down"===c?"top":"left",_="up"===c||"left"===c,v=0,b=a.queue().length;for(t.effects.createPlaceholder(a),o=a.css(m),u||(u=a["top"===m?"outerHeight":"outerWidth"]()/3),l&&(n={opacity:1},n[m]=o,a.css("opacity",0).css(m,_?2*-u:2*u).animate(n,f,g)),h&&(u/=Math.pow(2,d-1)),n={},n[m]=o;d>v;v++)s={},s[m]=(_?"-=":"+=")+u,a.animate(s,f,g).animate(n,f,g),u=h?2*u:u/2;h&&(s={opacity:0},s[m]=(_?"-=":"+=")+u,a.animate(s,f,g)),a.queue(i),t.effects.unshift(a,b,p+1)}),t.effects.define("clip","hide",function(e,i){var s,n={},o=t(this),a=e.direction||"vertical",r="both"===a,h=r||"horizontal"===a,l=r||"vertical"===a;s=o.cssClip(),n.clip={top:l?(s.bottom-s.top)/2:s.top,right:h?(s.right-s.left)/2:s.right,bottom:l?(s.bottom-s.top)/2:s.bottom,left:h?(s.right-s.left)/2:s.left},t.effects.createPlaceholder(o),"show"===e.mode&&(o.cssClip(n.clip),n.clip=s),o.animate(n,{queue:!1,duration:e.duration,easing:e.easing,complete:i})}),t.effects.define("drop","hide",function(e,i){var s,n=t(this),o=e.mode,a="show"===o,r=e.direction||"left",h="up"===r||"down"===r?"top":"left",l="up"===r||"left"===r?"-=":"+=",c="+="===l?"-=":"+=",u={opacity:0};t.effects.createPlaceholder(n),s=e.distance||n["top"===h?"outerHeight":"outerWidth"](!0)/2,u[h]=l+s,a&&(n.css(u),u[h]=c+s,u.opacity=1),n.animate(u,{queue:!1,duration:e.duration,easing:e.easing,complete:i})}),t.effects.define("explode","hide",function(e,i){function s(){b.push(this),b.length===u*d&&n()}function n(){p.css({visibility:"visible"}),t(b).remove(),i()}var o,a,r,h,l,c,u=e.pieces?Math.round(Math.sqrt(e.pieces)):3,d=u,p=t(this),f=e.mode,g="show"===f,m=p.show().css("visibility","hidden").offset(),_=Math.ceil(p.outerWidth()/d),v=Math.ceil(p.outerHeight()/u),b=[];for(o=0;u>o;o++)for(h=m.top+o*v,c=o-(u-1)/2,a=0;d>a;a++)r=m.left+a*_,l=a-(d-1)/2,p.clone().appendTo("body").wrap("<div></div>").css({position:"absolute",visibility:"visible",left:-a*_,top:-o*v}).parent().addClass("ui-effects-explode").css({position:"absolute",overflow:"hidden",width:_,height:v,left:r+(g?l*_:0),top:h+(g?c*v:0),opacity:g?0:1}).animate({left:r+(g?0:l*_),top:h+(g?0:c*v),opacLine truncated
|
||||
this.isMultiLine=o||!a&&this._isContentEditable(this.element),this.valueMethod=this.element[o||a?"val":"text"],this.isNewMenu=!0,this._addClass("ui-autocomplete-input"),this.element.attr("autocomplete","off"),this._on(this.element,{keydown:function(n){if(this.element.prop("readOnly"))return e=!0,s=!0,i=!0,void 0;e=!1,s=!1,i=!1;var o=t.ui.keyCode;switch(n.keyCode){case o.PAGE_UP:e=!0,this._move("previousPage",n);break;case o.PAGE_DOWN:e=!0,this._move("nextPage",n);break;case o.UP:e=!0,this._keyEvent("previous",n);break;case o.DOWN:e=!0,this._keyEvent("next",n);break;case o.ENTER:this.menu.active&&(e=!0,n.preventDefault(),this.menu.select(n));break;case o.TAB:this.menu.active&&this.menu.select(n);break;case o.ESCAPE:this.menu.element.is(":visible")&&(this.isMultiLine||this._value(this.term),this.close(n),n.preventDefault());break;default:i=!0,this._searchTimeout(n)}},keypress:function(s){if(e)return e=!1,(!this.isMultiLine||this.menu.element.is(":visible"))&&s.preventDefault(),void 0;if(!i){var n=t.ui.keyCode;switch(s.keyCode){case n.PAGE_UP:this._move("previousPage",s);break;case n.PAGE_DOWN:this._move("nextPage",s);break;case n.UP:this._keyEvent("previous",s);break;case n.DOWN:this._keyEvent("next",s)}}},input:function(t){return s?(s=!1,t.preventDefault(),void 0):(this._searchTimeout(t),void 0)},focus:function(){this.selectedItem=null,this.previous=this._value()},blur:function(t){return this.cancelBlur?(delete this.cancelBlur,void 0):(clearTimeout(this.searching),this.close(t),this._change(t),void 0)}}),this._initSource(),this.menu=t("<ul>").appendTo(this._appendTo()).menu({role:null}).hide().menu("instance"),this._addClass(this.menu.element,"ui-autocomplete","ui-front"),this._on(this.menu.element,{mousedown:function(e){e.preventDefault(),this.cancelBlur=!0,this._delay(function(){delete this.cancelBlur,this.element[0]!==t.ui.safeActiveElement(this.document[0])&&this.element.trigger("focus")})},menufocus:function(e,i){var s,n;return this.isNewMenu&&(this.isNewMenu=!1,e.originalEvent&&/^mouse/.test(e.originalEvent.type))?(this.menu.blur(),this.document.one("mousemove",function(){t(e.target).trigger(e.originalEvent)}),void 0):(n=i.item.data("ui-autocomplete-item"),!1!==this._trigger("focus",e,{item:n})&&e.originalEvent&&/^key/.test(e.originalEvent.type)&&this._value(n.value),s=i.item.attr("aria-label")||n.value,s&&t.trim(s).length&&(this.liveRegion.children().hide(),t("<div>").text(s).appendTo(this.liveRegion)),void 0)},menuselect:function(e,i){var s=i.item.data("ui-autocomplete-item"),n=this.previous;this.element[0]!==t.ui.safeActiveElement(this.document[0])&&(this.element.trigger("focus"),this.previous=n,this._delay(function(){this.previous=n,this.selectedItem=s})),!1!==this._trigger("select",e,{item:s})&&this._value(s.value),this.term=this._value(),this.close(e),this.selectedItem=s}}),this.liveRegion=t("<div>",{role:"status","aria-live":"assertive","aria-relevant":"additions"}).appendTo(this.document[0].body),this._addClass(this.liveRegion,null,"ui-helper-hidden-accessible"),this._on(this.window,{beforeunload:function(){this.element.removeAttr("autocomplete")}})},_destroy:function(){clearTimeout(this.searching),this.element.removeAttr("autocomplete"),this.menu.element.remove(),this.liveRegion.remove()},_setOption:function(t,e){this._super(t,e),"source"===t&&this._initSource(),"appendTo"===t&&this.menu.element.appendTo(this._appendTo()),"disabled"===t&&e&&this.xhr&&this.xhr.abort()},_isEventTargetInWidget:function(e){var i=this.menu.element[0];return e.target===this.element[0]||e.target===i||t.contains(i,e.target)},_closeOnClickOutside:function(t){this._isEventTargetInWidget(t)||this.close()},_appendTo:function(){var e=this.options.appendTo;return e&&(e=e.jquery||e.nodeType?t(e):this.document.find(e).eq(0)),e&&e[0]||(e=this.element.closest(".ui-front, dialog")),e.length||(e=this.document[0].body),e},_initSource:function(){var e,i,s=this;t.isArray(this.options.source)?(e=this.options.source,this.source=function(i,s){s(t.ui.autocomplete.filter(e,i.term))}):"string"==typeof this.options.source?(i=this.options.source,this.source=function(e,n){s.xhr&&s.xhr.abort(),s.xhr=t.ajax({url:i,data:e,dataType:"json",success:function(t){n(t)},error:function(){n([])}})}):this.source=this.options.source},_searchTimeout:function(t){clearTimeout(this.searching),this.searching=this._delay(function(){var e=this.term===this._value(),i=this.menu.element.is(":visible"),s=t.altKey||t.ctrlKey||t.metaKey||t.shiftKey;(!e||e&&!i&&!s)&&(this.selectedItem=null,this.search(null,t))},this.options.delay)},search:function(t,e){return t=null!=t?t:this._value(),this.term=this._value(),t.length<this.options.minLength?this.close(e):this._trigger("search",e)!==!1?this._search(t):void 0},_search:function(t){this.pending++,this._addClass("ui-autocomplete-loading"),this.cancelSearch=!1,this.source({term:t},this._response())},_response:function(){var e=++this.requestIndex;return t.proxy(function(t){e===this.requestIndex&&this.__response(t),this.pendinLine truncated
|
||||
}},_updateDatepicker:function(e){this.maxRows=4,m=e,e.dpDiv.empty().append(this._generateHTML(e)),this._attachHandlers(e);var i,s=this._getNumberOfMonths(e),n=s[1],a=17,r=e.dpDiv.find("."+this._dayOverClass+" a");r.length>0&&o.apply(r.get(0)),e.dpDiv.removeClass("ui-datepicker-multi-2 ui-datepicker-multi-3 ui-datepicker-multi-4").width(""),n>1&&e.dpDiv.addClass("ui-datepicker-multi-"+n).css("width",a*n+"em"),e.dpDiv[(1!==s[0]||1!==s[1]?"add":"remove")+"Class"]("ui-datepicker-multi"),e.dpDiv[(this._get(e,"isRTL")?"add":"remove")+"Class"]("ui-datepicker-rtl"),e===t.datepicker._curInst&&t.datepicker._datepickerShowing&&t.datepicker._shouldFocusInput(e)&&e.input.trigger("focus"),e.yearshtml&&(i=e.yearshtml,setTimeout(function(){i===e.yearshtml&&e.yearshtml&&e.dpDiv.find("select.ui-datepicker-year:first").replaceWith(e.yearshtml),i=e.yearshtml=null},0))},_shouldFocusInput:function(t){return t.input&&t.input.is(":visible")&&!t.input.is(":disabled")&&!t.input.is(":focus")},_checkOffset:function(e,i,s){var n=e.dpDiv.outerWidth(),o=e.dpDiv.outerHeight(),a=e.input?e.input.outerWidth():0,r=e.input?e.input.outerHeight():0,h=document.documentElement.clientWidth+(s?0:t(document).scrollLeft()),l=document.documentElement.clientHeight+(s?0:t(document).scrollTop());return i.left-=this._get(e,"isRTL")?n-a:0,i.left-=s&&i.left===e.input.offset().left?t(document).scrollLeft():0,i.top-=s&&i.top===e.input.offset().top+r?t(document).scrollTop():0,i.left-=Math.min(i.left,i.left+n>h&&h>n?Math.abs(i.left+n-h):0),i.top-=Math.min(i.top,i.top+o>l&&l>o?Math.abs(o+r):0),i},_findPos:function(e){for(var i,s=this._getInst(e),n=this._get(s,"isRTL");e&&("hidden"===e.type||1!==e.nodeType||t.expr.filters.hidden(e));)e=e[n?"previousSibling":"nextSibling"];return i=t(e).offset(),[i.left,i.top]},_hideDatepicker:function(e){var i,s,n,o,a=this._curInst;!a||e&&a!==t.data(e,"datepicker")||this._datepickerShowing&&(i=this._get(a,"showAnim"),s=this._get(a,"duration"),n=function(){t.datepicker._tidyDialog(a)},t.effects&&(t.effects.effect[i]||t.effects[i])?a.dpDiv.hide(i,t.datepicker._get(a,"showOptions"),s,n):a.dpDiv["slideDown"===i?"slideUp":"fadeIn"===i?"fadeOut":"hide"](i?s:null,n),i||n(),this._datepickerShowing=!1,o=this._get(a,"onClose"),o&&o.apply(a.input?a.input[0]:null,[a.input?a.input.val():"",a]),this._lastInput=null,this._inDialog&&(this._dialogInput.css({position:"absolute",left:"0",top:"-100px"}),t.blockUI&&(t.unblockUI(),t("body").append(this.dpDiv))),this._inDialog=!1)},_tidyDialog:function(t){t.dpDiv.removeClass(this._dialogClass).off(".ui-datepicker-calendar")},_checkExternalClick:function(e){if(t.datepicker._curInst){var i=t(e.target),s=t.datepicker._getInst(i[0]);(i[0].id!==t.datepicker._mainDivId&&0===i.parents("#"+t.datepicker._mainDivId).length&&!i.hasClass(t.datepicker.markerClassName)&&!i.closest("."+t.datepicker._triggerClass).length&&t.datepicker._datepickerShowing&&(!t.datepicker._inDialog||!t.blockUI)||i.hasClass(t.datepicker.markerClassName)&&t.datepicker._curInst!==s)&&t.datepicker._hideDatepicker()}},_adjustDate:function(e,i,s){var n=t(e),o=this._getInst(n[0]);this._isDisabledDatepicker(n[0])||(this._adjustInstDate(o,i+("M"===s?this._get(o,"showCurrentAtPos"):0),s),this._updateDatepicker(o))},_gotoToday:function(e){var i,s=t(e),n=this._getInst(s[0]);this._get(n,"gotoCurrent")&&n.currentDay?(n.selectedDay=n.currentDay,n.drawMonth=n.selectedMonth=n.currentMonth,n.drawYear=n.selectedYear=n.currentYear):(i=new Date,n.selectedDay=i.getDate(),n.drawMonth=n.selectedMonth=i.getMonth(),n.drawYear=n.selectedYear=i.getFullYear()),this._notifyChange(n),this._adjustDate(s)},_selectMonthYear:function(e,i,s){var n=t(e),o=this._getInst(n[0]);o["selected"+("M"===s?"Month":"Year")]=o["draw"+("M"===s?"Month":"Year")]=parseInt(i.options[i.selectedIndex].value,10),this._notifyChange(o),this._adjustDate(n)},_selectDay:function(e,i,s,n){var o,a=t(e);t(n).hasClass(this._unselectableClass)||this._isDisabledDatepicker(a[0])||(o=this._getInst(a[0]),o.selectedDay=o.currentDay=t("a",n).html(),o.selectedMonth=o.currentMonth=i,o.selectedYear=o.currentYear=s,this._selectDate(e,this._formatDate(o,o.currentDay,o.currentMonth,o.currentYear)))},_clearDate:function(e){var i=t(e);this._selectDate(i,"")},_selectDate:function(e,i){var s,n=t(e),o=this._getInst(n[0]);i=null!=i?i:this._formatDate(o),o.input&&o.input.val(i),this._updateAlternate(o),s=this._get(o,"onSelect"),s?s.apply(o.input?o.input[0]:null,[i,o]):o.input&&o.input.trigger("change"),o.inline?this._updateDatepicker(o):(this._hideDatepicker(),this._lastInput=o.input[0],"object"!=typeof o.input[0]&&o.input.trigger("focus"),this._lastInput=null)},_updateAlternate:function(e){var i,s,n,o=this._get(e,"altField");o&&(i=this._get(e,"altFormat")||this._get(e,"dateFormat"),s=this._getDate(e),n=this.formatDate(i,s,this._getFormatConfig(e)),t(o).val(n))},noWeekends:function(t){var e=t.getDay();return[e>0&&6>e,""]},iso8601Week:function(t){var e,i=new Date(t.getTime());return i.setDate(i.getDate()+4-(i.getDay()||7))Line truncated
|
||||
},_cacheMargins:function(){this.margins={left:parseInt(this.element.css("marginLeft"),10)||0,top:parseInt(this.element.css("marginTop"),10)||0,right:parseInt(this.element.css("marginRight"),10)||0,bottom:parseInt(this.element.css("marginBottom"),10)||0}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var e,i,s,n=this.options,o=this.document[0];return this.relativeContainer=null,n.containment?"window"===n.containment?(this.containment=[t(window).scrollLeft()-this.offset.relative.left-this.offset.parent.left,t(window).scrollTop()-this.offset.relative.top-this.offset.parent.top,t(window).scrollLeft()+t(window).width()-this.helperProportions.width-this.margins.left,t(window).scrollTop()+(t(window).height()||o.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top],void 0):"document"===n.containment?(this.containment=[0,0,t(o).width()-this.helperProportions.width-this.margins.left,(t(o).height()||o.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top],void 0):n.containment.constructor===Array?(this.containment=n.containment,void 0):("parent"===n.containment&&(n.containment=this.helper[0].parentNode),i=t(n.containment),s=i[0],s&&(e=/(scroll|auto)/.test(i.css("overflow")),this.containment=[(parseInt(i.css("borderLeftWidth"),10)||0)+(parseInt(i.css("paddingLeft"),10)||0),(parseInt(i.css("borderTopWidth"),10)||0)+(parseInt(i.css("paddingTop"),10)||0),(e?Math.max(s.scrollWidth,s.offsetWidth):s.offsetWidth)-(parseInt(i.css("borderRightWidth"),10)||0)-(parseInt(i.css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left-this.margins.right,(e?Math.max(s.scrollHeight,s.offsetHeight):s.offsetHeight)-(parseInt(i.css("borderBottomWidth"),10)||0)-(parseInt(i.css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top-this.margins.bottom],this.relativeContainer=i),void 0):(this.containment=null,void 0)},_convertPositionTo:function(t,e){e||(e=this.position);var i="absolute"===t?1:-1,s=this._isRootNode(this.scrollParent[0]);return{top:e.top+this.offset.relative.top*i+this.offset.parent.top*i-("fixed"===this.cssPosition?-this.offset.scroll.top:s?0:this.offset.scroll.top)*i,left:e.left+this.offset.relative.left*i+this.offset.parent.left*i-("fixed"===this.cssPosition?-this.offset.scroll.left:s?0:this.offset.scroll.left)*i}},_generatePosition:function(t,e){var i,s,n,o,a=this.options,r=this._isRootNode(this.scrollParent[0]),h=t.pageX,l=t.pageY;return r&&this.offset.scroll||(this.offset.scroll={top:this.scrollParent.scrollTop(),left:this.scrollParent.scrollLeft()}),e&&(this.containment&&(this.relativeContainer?(s=this.relativeContainer.offset(),i=[this.containment[0]+s.left,this.containment[1]+s.top,this.containment[2]+s.left,this.containment[3]+s.top]):i=this.containment,t.pageX-this.offset.click.left<i[0]&&(h=i[0]+this.offset.click.left),t.pageY-this.offset.click.top<i[1]&&(l=i[1]+this.offset.click.top),t.pageX-this.offset.click.left>i[2]&&(h=i[2]+this.offset.click.left),t.pageY-this.offset.click.top>i[3]&&(l=i[3]+this.offset.click.top)),a.grid&&(n=a.grid[1]?this.originalPageY+Math.round((l-this.originalPageY)/a.grid[1])*a.grid[1]:this.originalPageY,l=i?n-this.offset.click.top>=i[1]||n-this.offset.click.top>i[3]?n:n-this.offset.click.top>=i[1]?n-a.grid[1]:n+a.grid[1]:n,o=a.grid[0]?this.originalPageX+Math.round((h-this.originalPageX)/a.grid[0])*a.grid[0]:this.originalPageX,h=i?o-this.offset.click.left>=i[0]||o-this.offset.click.left>i[2]?o:o-this.offset.click.left>=i[0]?o-a.grid[0]:o+a.grid[0]:o),"y"===a.axis&&(h=this.originalPageX),"x"===a.axis&&(l=this.originalPageY)),{top:l-this.offset.click.top-this.offset.relative.top-this.offset.parent.top+("fixed"===this.cssPosition?-this.offset.scroll.top:r?0:this.offset.scroll.top),left:h-this.offset.click.left-this.offset.relative.left-this.offset.parent.left+("fixed"===this.cssPosition?-this.offset.scroll.left:r?0:this.offset.scroll.left)}},_clear:function(){this._removeClass(this.helper,"ui-draggable-dragging"),this.helper[0]===this.element[0]||this.cancelHelperRemoval||this.helper.remove(),this.helper=null,this.cancelHelperRemoval=!1,this.destroyOnClear&&this.destroy()},_trigger:function(e,i,s){return s=s||this._uiHash(),t.ui.plugin.call(this,e,[i,s,this],!0),/^(drag|start|stop)/.test(e)&&(this.positionAbs=this._convertPositionTo("absolute"),s.offset=this.positionAbs),t.Widget.prototype._trigger.call(this,e,i,s)},plugins:{},_uiHash:function(){return{helper:this.helper,position:this.position,originalPosition:this.originalPosition,offset:this.positionAbs}}}),t.ui.plugin.add("draggable","connectToSortable",{start:function(e,i,s){var n=t.extend({},i,{item:s.element});s.sortables=[],t(s.options.connectToSortable).each(function(){var i=t(this).sortable("instance");i&&!i.options.disabled&&(s.sortables.push(i),i.refreshPositions(),i._trigger("activate",e,n))})},stop:function(e,i,s){var n=t.extend({},i,{item:s.eleLine truncated
|
||||
},disable:t.noop,enable:t.noop,close:function(e){var i=this;this._isOpen&&this._trigger("beforeClose",e)!==!1&&(this._isOpen=!1,this._focusedElement=null,this._destroyOverlay(),this._untrackInstance(),this.opener.filter(":focusable").trigger("focus").length||t.ui.safeBlur(t.ui.safeActiveElement(this.document[0])),this._hide(this.uiDialog,this.options.hide,function(){i._trigger("close",e)}))},isOpen:function(){return this._isOpen},moveToTop:function(){this._moveToTop()},_moveToTop:function(e,i){var s=!1,n=this.uiDialog.siblings(".ui-front:visible").map(function(){return+t(this).css("z-index")}).get(),o=Math.max.apply(null,n);return o>=+this.uiDialog.css("z-index")&&(this.uiDialog.css("z-index",o+1),s=!0),s&&!i&&this._trigger("focus",e),s},open:function(){var e=this;return this._isOpen?(this._moveToTop()&&this._focusTabbable(),void 0):(this._isOpen=!0,this.opener=t(t.ui.safeActiveElement(this.document[0])),this._size(),this._position(),this._createOverlay(),this._moveToTop(null,!0),this.overlay&&this.overlay.css("z-index",this.uiDialog.css("z-index")-1),this._show(this.uiDialog,this.options.show,function(){e._focusTabbable(),e._trigger("focus")}),this._makeFocusTarget(),this._trigger("open"),void 0)},_focusTabbable:function(){var t=this._focusedElement;t||(t=this.element.find("[autofocus]")),t.length||(t=this.element.find(":tabbable")),t.length||(t=this.uiDialogButtonPane.find(":tabbable")),t.length||(t=this.uiDialogTitlebarClose.filter(":tabbable")),t.length||(t=this.uiDialog),t.eq(0).trigger("focus")},_keepFocus:function(e){function i(){var e=t.ui.safeActiveElement(this.document[0]),i=this.uiDialog[0]===e||t.contains(this.uiDialog[0],e);i||this._focusTabbable()}e.preventDefault(),i.call(this),this._delay(i)},_createWrapper:function(){this.uiDialog=t("<div>").hide().attr({tabIndex:-1,role:"dialog"}).appendTo(this._appendTo()),this._addClass(this.uiDialog,"ui-dialog","ui-widget ui-widget-content ui-front"),this._on(this.uiDialog,{keydown:function(e){if(this.options.closeOnEscape&&!e.isDefaultPrevented()&&e.keyCode&&e.keyCode===t.ui.keyCode.ESCAPE)return e.preventDefault(),this.close(e),void 0;if(e.keyCode===t.ui.keyCode.TAB&&!e.isDefaultPrevented()){var i=this.uiDialog.find(":tabbable"),s=i.filter(":first"),n=i.filter(":last");e.target!==n[0]&&e.target!==this.uiDialog[0]||e.shiftKey?e.target!==s[0]&&e.target!==this.uiDialog[0]||!e.shiftKey||(this._delay(function(){n.trigger("focus")}),e.preventDefault()):(this._delay(function(){s.trigger("focus")}),e.preventDefault())}},mousedown:function(t){this._moveToTop(t)&&this._focusTabbable()}}),this.element.find("[aria-describedby]").length||this.uiDialog.attr({"aria-describedby":this.element.uniqueId().attr("id")})},_createTitlebar:function(){var e;this.uiDialogTitlebar=t("<div>"),this._addClass(this.uiDialogTitlebar,"ui-dialog-titlebar","ui-widget-header ui-helper-clearfix"),this._on(this.uiDialogTitlebar,{mousedown:function(e){t(e.target).closest(".ui-dialog-titlebar-close")||this.uiDialog.trigger("focus")}}),this.uiDialogTitlebarClose=t("<button type='button'></button>").button({label:t("<a>").text(this.options.closeText).html(),icon:"ui-icon-closethick",showLabel:!1}).appendTo(this.uiDialogTitlebar),this._addClass(this.uiDialogTitlebarClose,"ui-dialog-titlebar-close"),this._on(this.uiDialogTitlebarClose,{click:function(t){t.preventDefault(),this.close(t)}}),e=t("<span>").uniqueId().prependTo(this.uiDialogTitlebar),this._addClass(e,"ui-dialog-title"),this._title(e),this.uiDialogTitlebar.prependTo(this.uiDialog),this.uiDialog.attr({"aria-labelledby":e.attr("id")})},_title:function(t){this.options.title?t.text(this.options.title):t.html(" ")},_createButtonPane:function(){this.uiDialogButtonPane=t("<div>"),this._addClass(this.uiDialogButtonPane,"ui-dialog-buttonpane","ui-widget-content ui-helper-clearfix"),this.uiButtonSet=t("<div>").appendTo(this.uiDialogButtonPane),this._addClass(this.uiButtonSet,"ui-dialog-buttonset"),this._createButtons()},_createButtons:function(){var e=this,i=this.options.buttons;return this.uiDialogButtonPane.remove(),this.uiButtonSet.empty(),t.isEmptyObject(i)||t.isArray(i)&&!i.length?(this._removeClass(this.uiDialog,"ui-dialog-buttons"),void 0):(t.each(i,function(i,s){var n,o;s=t.isFunction(s)?{click:s,text:i}:s,s=t.extend({type:"button"},s),n=s.click,o={icon:s.icon,iconPosition:s.iconPosition,showLabel:s.showLabel,icons:s.icons,text:s.text},delete s.click,delete s.icon,delete s.iconPosition,delete s.showLabel,delete s.icons,"boolean"==typeof s.text&&delete s.text,t("<button></button>",s).button(o).appendTo(e.uiButtonSet).on("click",function(){n.apply(e.element[0],arguments)})}),this._addClass(this.uiDialog,"ui-dialog-buttons"),this.uiDialogButtonPane.appendTo(this.uiDialog),void 0)},_makeDraggable:function(){function e(t){return{position:t.position,offset:t.offset}}var i=this,s=this.options;this.uiDialog.draggable({cancel:".ui-dialog-content, .ui-dialog-titlebar-close",handle:".ui-dialog-titlebar",containment:"document",start:functionLine truncated
|
||||
},_refresh:function(){this._createRange(),this._createHandles(),this._setupEvents(),this._refreshValue()},_createHandles:function(){var e,i,s=this.options,n=this.element.find(".ui-slider-handle"),o="<span tabindex='0'></span>",a=[];for(i=s.values&&s.values.length||1,n.length>i&&(n.slice(i).remove(),n=n.slice(0,i)),e=n.length;i>e;e++)a.push(o);this.handles=n.add(t(a.join("")).appendTo(this.element)),this._addClass(this.handles,"ui-slider-handle","ui-state-default"),this.handle=this.handles.eq(0),this.handles.each(function(e){t(this).data("ui-slider-handle-index",e).attr("tabIndex",0)})},_createRange:function(){var e=this.options;e.range?(e.range===!0&&(e.values?e.values.length&&2!==e.values.length?e.values=[e.values[0],e.values[0]]:t.isArray(e.values)&&(e.values=e.values.slice(0)):e.values=[this._valueMin(),this._valueMin()]),this.range&&this.range.length?(this._removeClass(this.range,"ui-slider-range-min ui-slider-range-max"),this.range.css({left:"",bottom:""})):(this.range=t("<div>").appendTo(this.element),this._addClass(this.range,"ui-slider-range")),("min"===e.range||"max"===e.range)&&this._addClass(this.range,"ui-slider-range-"+e.range)):(this.range&&this.range.remove(),this.range=null)},_setupEvents:function(){this._off(this.handles),this._on(this.handles,this._handleEvents),this._hoverable(this.handles),this._focusable(this.handles)},_destroy:function(){this.handles.remove(),this.range&&this.range.remove(),this._mouseDestroy()},_mouseCapture:function(e){var i,s,n,o,a,r,h,l,c=this,u=this.options;return u.disabled?!1:(this.elementSize={width:this.element.outerWidth(),height:this.element.outerHeight()},this.elementOffset=this.element.offset(),i={x:e.pageX,y:e.pageY},s=this._normValueFromMouse(i),n=this._valueMax()-this._valueMin()+1,this.handles.each(function(e){var i=Math.abs(s-c.values(e));(n>i||n===i&&(e===c._lastChangedValue||c.values(e)===u.min))&&(n=i,o=t(this),a=e)}),r=this._start(e,a),r===!1?!1:(this._mouseSliding=!0,this._handleIndex=a,this._addClass(o,null,"ui-state-active"),o.trigger("focus"),h=o.offset(),l=!t(e.target).parents().addBack().is(".ui-slider-handle"),this._clickOffset=l?{left:0,top:0}:{left:e.pageX-h.left-o.width()/2,top:e.pageY-h.top-o.height()/2-(parseInt(o.css("borderTopWidth"),10)||0)-(parseInt(o.css("borderBottomWidth"),10)||0)+(parseInt(o.css("marginTop"),10)||0)},this.handles.hasClass("ui-state-hover")||this._slide(e,a,s),this._animateOff=!0,!0))},_mouseStart:function(){return!0},_mouseDrag:function(t){var e={x:t.pageX,y:t.pageY},i=this._normValueFromMouse(e);return this._slide(t,this._handleIndex,i),!1},_mouseStop:function(t){return this._removeClass(this.handles,null,"ui-state-active"),this._mouseSliding=!1,this._stop(t,this._handleIndex),this._change(t,this._handleIndex),this._handleIndex=null,this._clickOffset=null,this._animateOff=!1,!1},_detectOrientation:function(){this.orientation="vertical"===this.options.orientation?"vertical":"horizontal"},_normValueFromMouse:function(t){var e,i,s,n,o;return"horizontal"===this.orientation?(e=this.elementSize.width,i=t.x-this.elementOffset.left-(this._clickOffset?this._clickOffset.left:0)):(e=this.elementSize.height,i=t.y-this.elementOffset.top-(this._clickOffset?this._clickOffset.top:0)),s=i/e,s>1&&(s=1),0>s&&(s=0),"vertical"===this.orientation&&(s=1-s),n=this._valueMax()-this._valueMin(),o=this._valueMin()+s*n,this._trimAlignValue(o)},_uiHash:function(t,e,i){var s={handle:this.handles[t],handleIndex:t,value:void 0!==e?e:this.value()};return this._hasMultipleValues()&&(s.value=void 0!==e?e:this.values(t),s.values=i||this.values()),s},_hasMultipleValues:function(){return this.options.values&&this.options.values.length},_start:function(t,e){return this._trigger("start",t,this._uiHash(e))},_slide:function(t,e,i){var s,n,o=this.value(),a=this.values();this._hasMultipleValues()&&(n=this.values(e?0:1),o=this.values(e),2===this.options.values.length&&this.options.range===!0&&(i=0===e?Math.min(n,i):Math.max(n,i)),a[e]=i),i!==o&&(s=this._trigger("slide",t,this._uiHash(e,i,a)),s!==!1&&(this._hasMultipleValues()?this.values(e,i):this.value(i)))},_stop:function(t,e){this._trigger("stop",t,this._uiHash(e))},_change:function(t,e){this._keySliding||this._mouseSliding||(this._lastChangedValue=e,this._trigger("change",t,this._uiHash(e)))},value:function(t){return arguments.length?(this.options.value=this._trimAlignValue(t),this._refreshValue(),this._change(null,0),void 0):this._value()},values:function(e,i){var s,n,o;if(arguments.length>1)return this.options.values[e]=this._trimAlignValue(i),this._refreshValue(),this._change(null,e),void 0;if(!arguments.length)return this._values();if(!t.isArray(arguments[0]))return this._hasMultipleValues()?this._values(e):this.value();for(s=this.options.values,n=arguments[0],o=0;s.length>o;o+=1)s[o]=this._trimAlignValue(n[o]),this._change(null,o);this._refreshValue()},_setOption:function(e,i){var s,n=0;switch("range"===e&&this.options.range===!0&&("min"===i?(this.options.value=this._values(0),this.options.valuLine truncated
|
||||
this._delay(function(){n===this.counter&&this.refreshPositions(!s)})},_clear:function(t,e){function i(t,e,i){return function(s){i._trigger(t,s,e._uiHash(e))}}this.reverting=!1;var s,n=[];if(!this._noFinalSort&&this.currentItem.parent().length&&this.placeholder.before(this.currentItem),this._noFinalSort=null,this.helper[0]===this.currentItem[0]){for(s in this._storedCSS)("auto"===this._storedCSS[s]||"static"===this._storedCSS[s])&&(this._storedCSS[s]="");this.currentItem.css(this._storedCSS),this._removeClass(this.currentItem,"ui-sortable-helper")}else this.currentItem.show();for(this.fromOutside&&!e&&n.push(function(t){this._trigger("receive",t,this._uiHash(this.fromOutside))}),!this.fromOutside&&this.domPosition.prev===this.currentItem.prev().not(".ui-sortable-helper")[0]&&this.domPosition.parent===this.currentItem.parent()[0]||e||n.push(function(t){this._trigger("update",t,this._uiHash())}),this!==this.currentContainer&&(e||(n.push(function(t){this._trigger("remove",t,this._uiHash())}),n.push(function(t){return function(e){t._trigger("receive",e,this._uiHash(this))}}.call(this,this.currentContainer)),n.push(function(t){return function(e){t._trigger("update",e,this._uiHash(this))}}.call(this,this.currentContainer)))),s=this.containers.length-1;s>=0;s--)e||n.push(i("deactivate",this,this.containers[s])),this.containers[s].containerCache.over&&(n.push(i("out",this,this.containers[s])),this.containers[s].containerCache.over=0);if(this.storedCursor&&(this.document.find("body").css("cursor",this.storedCursor),this.storedStylesheet.remove()),this._storedOpacity&&this.helper.css("opacity",this._storedOpacity),this._storedZIndex&&this.helper.css("zIndex","auto"===this._storedZIndex?"":this._storedZIndex),this.dragging=!1,e||this._trigger("beforeStop",t,this._uiHash()),this.placeholder[0].parentNode.removeChild(this.placeholder[0]),this.cancelHelperRemoval||(this.helper[0]!==this.currentItem[0]&&this.helper.remove(),this.helper=null),!e){for(s=0;n.length>s;s++)n[s].call(this,t);this._trigger("stop",t,this._uiHash())}return this.fromOutside=!1,!this.cancelHelperRemoval},_trigger:function(){t.Widget.prototype._trigger.apply(this,arguments)===!1&&this.cancel()},_uiHash:function(e){var i=e||this;return{helper:i.helper,placeholder:i.placeholder||t([]),position:i.position,originalPosition:i.originalPosition,offset:i.positionAbs,item:i.currentItem,sender:e?e.element:null}}}),t.widget("ui.spinner",{version:"1.12.1",defaultElement:"<input>",widgetEventPrefix:"spin",options:{classes:{"ui-spinner":"ui-corner-all","ui-spinner-down":"ui-corner-br","ui-spinner-up":"ui-corner-tr"},culture:null,icons:{down:"ui-icon-triangle-1-s",up:"ui-icon-triangle-1-n"},incremental:!0,max:null,min:null,numberFormat:null,page:10,step:1,change:null,spin:null,start:null,stop:null},_create:function(){this._setOption("max",this.options.max),this._setOption("min",this.options.min),this._setOption("step",this.options.step),""!==this.value()&&this._value(this.element.val(),!0),this._draw(),this._on(this._events),this._refresh(),this._on(this.window,{beforeunload:function(){this.element.removeAttr("autocomplete")}})},_getCreateOptions:function(){var e=this._super(),i=this.element;return t.each(["min","max","step"],function(t,s){var n=i.attr(s);null!=n&&n.length&&(e[s]=n)}),e},_events:{keydown:function(t){this._start(t)&&this._keydown(t)&&t.preventDefault()},keyup:"_stop",focus:function(){this.previous=this.element.val()},blur:function(t){return this.cancelBlur?(delete this.cancelBlur,void 0):(this._stop(),this._refresh(),this.previous!==this.element.val()&&this._trigger("change",t),void 0)},mousewheel:function(t,e){if(e){if(!this.spinning&&!this._start(t))return!1;this._spin((e>0?1:-1)*this.options.step,t),clearTimeout(this.mousewheelTimer),this.mousewheelTimer=this._delay(function(){this.spinning&&this._stop(t)},100),t.preventDefault()}},"mousedown .ui-spinner-button":function(e){function i(){var e=this.element[0]===t.ui.safeActiveElement(this.document[0]);e||(this.element.trigger("focus"),this.previous=s,this._delay(function(){this.previous=s}))}var s;s=this.element[0]===t.ui.safeActiveElement(this.document[0])?this.previous:this.element.val(),e.preventDefault(),i.call(this),this.cancelBlur=!0,this._delay(function(){delete this.cancelBlur,i.call(this)}),this._start(e)!==!1&&this._repeat(null,t(e.currentTarget).hasClass("ui-spinner-up")?1:-1,e)},"mouseup .ui-spinner-button":"_stop","mouseenter .ui-spinner-button":function(e){return t(e.currentTarget).hasClass("ui-state-active")?this._start(e)===!1?!1:(this._repeat(null,t(e.currentTarget).hasClass("ui-spinner-up")?1:-1,e),void 0):void 0},"mouseleave .ui-spinner-button":"_stop"},_enhance:function(){this.uiSpinner=this.element.attr("autocomplete","off").wrap("<span>").parent().append("<a></a><a></a>")},_draw:function(){this._enhance(),this._addClass(this.uiSpinner,"ui-spinner","ui-widget ui-widget-content"),this._addClass("ui-spinner-input"),this.element.attr("role","spinbutton"),this.buttons=this.uiSpinnLine truncated
|
||||
Vendored
+6445
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,12 @@
|
||||
"""
|
||||
Author: Michel Peltriaux
|
||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||
Created on: 16.11.20
|
||||
|
||||
"""
|
||||
|
||||
BASE_TITLE = "konova"
|
||||
BASE_FRONTEND_TITLE = "Kompensationsverzeichnis Service Portal"
|
||||
WIKI_URL = "https://dienste.naturschutz.rlp.de/doku/doku.php?id=ksp:start"
|
||||
RENDER_HEADER = False
|
||||
@@ -0,0 +1,230 @@
|
||||
"""
|
||||
Django settings for konova project.
|
||||
|
||||
Generated by 'django-admin startproject' using Django 3.1.3.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/3.1/topics/settings/
|
||||
|
||||
For the full list of settings and their values, see
|
||||
https://docs.djangoproject.com/en/3.1/ref/settings/
|
||||
"""
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||
BASE_DIR = os.path.dirname(
|
||||
os.path.dirname(
|
||||
os.path.dirname(
|
||||
os.path.abspath(
|
||||
__file__
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
# Quick-start development settings - unsuitable for production
|
||||
# See https://docs.djangoproject.com/en/3.1/howto/deployment/checklist/
|
||||
|
||||
# SECURITY WARNING: keep the secret key used in production secret!
|
||||
SECRET_KEY = '5=9-)2)h$u9=!zrhia9=lj-2#cpcb8=#$7y+)l$5tto$3q(n_+'
|
||||
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = True
|
||||
|
||||
ALLOWED_HOSTS = []
|
||||
|
||||
# Authentication settings
|
||||
LOGIN_URL = "/login/"
|
||||
|
||||
# Session settings
|
||||
SESSION_COOKIE_AGE = 30 * 60 # 30 minutes
|
||||
SESSION_SAVE_EVERY_REQUEST = True
|
||||
|
||||
# Application definition
|
||||
|
||||
INSTALLED_APPS = [
|
||||
'dal',
|
||||
'dal_select2',
|
||||
'django.contrib.admin',
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
'django.contrib.gis',
|
||||
'simple_sso.sso_server',
|
||||
'django_tables2',
|
||||
'fontawesome_5',
|
||||
'konova',
|
||||
'compensation',
|
||||
'intervention',
|
||||
'process',
|
||||
'organisation',
|
||||
]
|
||||
if DEBUG:
|
||||
INSTALLED_APPS += [
|
||||
'debug_toolbar',
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
"django.middleware.locale.LocaleMiddleware",
|
||||
]
|
||||
if DEBUG:
|
||||
MIDDLEWARE += [
|
||||
"debug_toolbar.middleware.DebugToolbarMiddleware",
|
||||
]
|
||||
|
||||
ROOT_URLCONF = 'konova.urls'
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
'DIRS': [
|
||||
os.path.join(BASE_DIR, "templates"),
|
||||
],
|
||||
'APP_DIRS': True,
|
||||
'OPTIONS': {
|
||||
'context_processors': [
|
||||
'django.template.context_processors.debug',
|
||||
'django.template.context_processors.request',
|
||||
'django.contrib.auth.context_processors.auth',
|
||||
'django.contrib.messages.context_processors.messages',
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
WSGI_APPLICATION = 'konova.wsgi.application'
|
||||
|
||||
|
||||
# Database
|
||||
# https://docs.djangoproject.com/en/3.1/ref/settings/#databases
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.contrib.gis.db.backends.postgis',
|
||||
'NAME': 'konova',
|
||||
'USER': 'postgres',
|
||||
'HOST': '127.0.0.1',
|
||||
'PORT': '5432',
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Password validation
|
||||
# https://docs.djangoproject.com/en/3.1/ref/settings/#auth-password-validators
|
||||
|
||||
AUTH_PASSWORD_VALIDATORS = [
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
# Internationalization
|
||||
# https://docs.djangoproject.com/en/3.1/topics/i18n/
|
||||
|
||||
LANGUAGE_CODE = 'en-us'
|
||||
|
||||
DEFAULT_DATE_TIME_FORMAT = 'YYYY-MM-DD hh:mm:ss'
|
||||
|
||||
TIME_ZONE = 'Europe/Berlin'
|
||||
|
||||
USE_I18N = True
|
||||
|
||||
USE_L10N = True
|
||||
|
||||
USE_TZ = True
|
||||
|
||||
LOCALE_PATHS = (
|
||||
os.path.join(BASE_DIR, 'locale'),
|
||||
)
|
||||
|
||||
# Static files (CSS, JavaScript, Images)
|
||||
# https://docs.djangoproject.com/en/3.1/howto/static-files/
|
||||
|
||||
STATIC_URL = '/static/'
|
||||
STATIC_ROOT = os.path.join(BASE_DIR, "static")
|
||||
STATICFILES_DIRS = [
|
||||
os.path.join(BASE_DIR, 'konova/static'),
|
||||
]
|
||||
|
||||
# DJANGO DEBUG TOOLBAR
|
||||
INTERNAL_IPS = [
|
||||
"127.0.0.1"
|
||||
]
|
||||
DEBUG_TOOLBAR_CONFIG = {
|
||||
"DISABLE_PANELS": {
|
||||
'debug_toolbar.panels.versions.VersionsPanel',
|
||||
'debug_toolbar.panels.timer.TimerPanel',
|
||||
'debug_toolbar.panels.settings.SettingsPanel',
|
||||
'debug_toolbar.panels.headers.HeadersPanel',
|
||||
'debug_toolbar.panels.request.RequestPanel',
|
||||
'debug_toolbar.panels.sql.SQLPanel',
|
||||
'debug_toolbar.panels.staticfiles.StaticFilesPanel',
|
||||
'debug_toolbar.panels.templates.TemplatesPanel',
|
||||
'debug_toolbar.panels.cache.CachePanel',
|
||||
'debug_toolbar.panels.signals.SignalsPanel',
|
||||
'debug_toolbar.panels.logging.LoggingPanel',
|
||||
'debug_toolbar.panels.redirects.RedirectsPanel',
|
||||
'debug_toolbar.panels.profiling.ProfilingPanel',
|
||||
}
|
||||
}
|
||||
|
||||
# EMAIL (see https://docs.djangoproject.com/en/dev/topics/email/)
|
||||
DEFAULT_FROM_EMAIL = "bot@arneo.de" # The default email address for the 'from' element
|
||||
EMAIL_HOST = "localhost"
|
||||
EMAIL_PORT = "1025"
|
||||
#EMAIL_HOST_USER = ""
|
||||
#EMAIL_HOST_PASSWORD = ""
|
||||
EMAIL_USE_TLS = False
|
||||
EMAIL_USE_SSL = False
|
||||
|
||||
# LOGGING
|
||||
BASIC_LOGGER = "logger"
|
||||
LOGGING = {
|
||||
'version': 1,
|
||||
'disable_existing_loggers': False,
|
||||
'formatters': {
|
||||
'verbose': {
|
||||
'format': '{levelname} {asctime} {module}: {message}',
|
||||
'style': '{',
|
||||
},
|
||||
'simple': {
|
||||
'format': '{levelname} {message}',
|
||||
'style': '{',
|
||||
},
|
||||
},
|
||||
'handlers': {
|
||||
'log_to_file': {
|
||||
'level': 'DEBUG',
|
||||
'class': 'logging.handlers.RotatingFileHandler',
|
||||
'filename': '{}/logs/error.log'.format(BASE_DIR),
|
||||
'maxBytes': 1024*1024*5, # 5 MB
|
||||
'backupCount': 5,
|
||||
'formatter': 'verbose',
|
||||
},
|
||||
},
|
||||
'loggers': {
|
||||
BASIC_LOGGER: {
|
||||
'handlers': ['log_to_file'],
|
||||
'level': 'INFO',
|
||||
'propagate': True,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
|
||||
<form action="{{ form.action_url }}" method="post">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
</form>
|
||||
@@ -0,0 +1,8 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block body %}
|
||||
<div class="column">
|
||||
{% include 'generic_table_form.html' %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,44 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block body_middle %}
|
||||
<h1>Kompensationsverzeichnis</h1>
|
||||
<h2>Service Portal</h2>
|
||||
<hr>
|
||||
{% if user.is_anonymous %}
|
||||
<a href="{% url 'simple-sso-login' %}">
|
||||
<button class="button middle">
|
||||
{% trans 'Proceed with login' %}
|
||||
</button>
|
||||
</a>
|
||||
{% else %}
|
||||
<article>
|
||||
{% trans 'Logged in as' %} <strong>{{ user.username }}</strong>
|
||||
<br>
|
||||
{% trans 'Last login on' %} {{ user.last_login }}
|
||||
</article>
|
||||
|
||||
<form action="{{form.action_url}}" method="post">
|
||||
{% csrf_token %}
|
||||
<table>
|
||||
{% comment %}
|
||||
This is an alternative to using the <article></article>
|
||||
<tr>
|
||||
<td>{% trans 'Logged in as' %}</td>
|
||||
<td><strong>{{ user.username }}</strong></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Last login on' %}</td>
|
||||
<td><strong>{{ user.last_login }}</strong></td>
|
||||
</tr>
|
||||
{% endcomment %}
|
||||
{% for field in form %}
|
||||
<tr>
|
||||
<td>{{ field.label }}</td>
|
||||
<td>{{ field }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,7 @@
|
||||
"""
|
||||
Author: Michel Peltriaux
|
||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||
Created on: 04.12.20
|
||||
|
||||
"""
|
||||
@@ -0,0 +1,17 @@
|
||||
"""
|
||||
Author: Michel Peltriaux
|
||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||
Created on: 04.12.20
|
||||
|
||||
"""
|
||||
from django import template
|
||||
|
||||
from process.settings import PROCESS_STATE_STRINGS
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
@register.filter
|
||||
def resolve_process_state(value):
|
||||
return PROCESS_STATE_STRINGS.get(value, None)
|
||||
@@ -0,0 +1,47 @@
|
||||
"""konova URL Configuration
|
||||
|
||||
The `urlpatterns` list routes URLs to views. For more information please see:
|
||||
https://docs.djangoproject.com/en/3.1/topics/http/urls/
|
||||
Examples:
|
||||
Function views
|
||||
1. Add an import: from my_app import views
|
||||
2. Add a URL to urlpatterns: path('', views.home, name='home')
|
||||
Class-based views
|
||||
1. Add an import: from other_app.views import Home
|
||||
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
|
||||
Including another URLconf
|
||||
1. Import the include() function: from django.urls import include, path
|
||||
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||
"""
|
||||
import debug_toolbar
|
||||
from django.contrib import admin
|
||||
from django.urls import path, include
|
||||
from simple_sso.sso_client.client import Client
|
||||
|
||||
from konova.autocompletes import OrganisationAutocomplete, NonOfficialOrganisationAutocomplete
|
||||
from konova.settings import SSO_SERVER, SSO_PUBLIC_KEY, SSO_PRIVATE_KEY, DEBUG
|
||||
from konova.views import logout_view, home_view
|
||||
|
||||
sso_client = Client(SSO_SERVER, SSO_PUBLIC_KEY, SSO_PRIVATE_KEY)
|
||||
urlpatterns = [
|
||||
path('admin/', admin.site.urls),
|
||||
path('login/', include(sso_client.get_urls())),
|
||||
path('logout/', logout_view, name="logout"),
|
||||
path('', home_view, name="home"),
|
||||
path('process/', include("process.urls")),
|
||||
path('intervention/', include("intervention.urls")),
|
||||
path('compensation/', include("compensation.urls")),
|
||||
path('eco-account/', include("process.urls")),
|
||||
path('ema/', include("process.urls")),
|
||||
path('organisation/', include("organisation.urls")),
|
||||
path('user/', include("process.urls")),
|
||||
|
||||
# Autocomplete paths
|
||||
path("atcmplt/orgs", OrganisationAutocomplete.as_view(), name="orgs-autocomplete"),
|
||||
path("atcmplt/orgs/other", NonOfficialOrganisationAutocomplete.as_view(), name="other-orgs-autocomplete"),
|
||||
]
|
||||
|
||||
if DEBUG:
|
||||
urlpatterns += [
|
||||
path('__debug__/', include(debug_toolbar.urls)),
|
||||
]
|
||||
@@ -0,0 +1,22 @@
|
||||
"""
|
||||
Author: Michel Peltriaux
|
||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||
Created on: 09.11.20
|
||||
|
||||
"""
|
||||
import random
|
||||
import string
|
||||
|
||||
|
||||
def generate_random_string(length: int, only_numbers: bool = False) -> str:
|
||||
"""
|
||||
Generates a random string of variable length
|
||||
"""
|
||||
if only_numbers:
|
||||
elements = string.digits
|
||||
else:
|
||||
elements = string.ascii_letters
|
||||
|
||||
ret_val = "".join(random.choice(elements) for i in range(length))
|
||||
return ret_val
|
||||
@@ -0,0 +1,52 @@
|
||||
"""
|
||||
Author: Michel Peltriaux
|
||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||
Created on: 09.11.20
|
||||
|
||||
"""
|
||||
import logging
|
||||
|
||||
from django.core.mail import send_mail
|
||||
|
||||
from konova.sub_settings.django_settings import DEFAULT_FROM_EMAIL
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Mailer:
|
||||
"""
|
||||
A wrapper for the django internal mailing functionality
|
||||
"""
|
||||
from_mail = None
|
||||
to_mail = []
|
||||
fail_silently = False
|
||||
|
||||
# Optional. Can be changed using the constructor to authenticate on the smtp server using other credentials
|
||||
auth_user = None
|
||||
auth_password = None
|
||||
|
||||
def __init__(self, to_mail: list, from_mail: str = DEFAULT_FROM_EMAIL, auth_user: str = None, auth_password: str = None, fail_silently: bool = False):
|
||||
# Make sure given to_mail parameter is a list
|
||||
if isinstance(to_mail, str):
|
||||
to_mail = [to_mail]
|
||||
|
||||
self.from_mail = from_mail
|
||||
self.to_mail = to_mail
|
||||
self.fail_silently = fail_silently
|
||||
self.auth_user = auth_user
|
||||
self.auth_password = auth_password
|
||||
|
||||
def send(self, subject: str, msg: str):
|
||||
"""
|
||||
Sends a mail with subject and message
|
||||
"""
|
||||
return send_mail(
|
||||
subject=subject,
|
||||
message=msg,
|
||||
from_email=self.from_mail,
|
||||
recipient_list=self.to_mail,
|
||||
fail_silently=self.fail_silently,
|
||||
auth_user=self.auth_user,
|
||||
auth_password=self.auth_password
|
||||
)
|
||||
@@ -0,0 +1,45 @@
|
||||
"""
|
||||
Author: Michel Peltriaux
|
||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||
Created on: 09.12.20
|
||||
|
||||
"""
|
||||
from django.http import HttpRequest
|
||||
from idna import unicode
|
||||
|
||||
from konova.models import RoleGroup
|
||||
from organisation.settings import ROLE_TYPE_STRINGS
|
||||
|
||||
CURRENT_ROLE_ID = "current_role"
|
||||
|
||||
|
||||
def set_session_user_role(request: HttpRequest, role_group: RoleGroup) -> dict:
|
||||
""" Set the user session to an active role
|
||||
|
||||
Args:
|
||||
request (HttpRequest): The user request
|
||||
role_group (RoleGroup): The selected role group
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
current_role = {}
|
||||
if role_group is not None:
|
||||
current_role["type"] = unicode(ROLE_TYPE_STRINGS.get(role_group.role.type))
|
||||
current_role["org"] = role_group.organisation.__str__()
|
||||
current_role["id"] = role_group.id
|
||||
request.session[CURRENT_ROLE_ID] = current_role
|
||||
return current_role
|
||||
|
||||
|
||||
def get_session_user_role(request: HttpRequest) -> dict:
|
||||
""" Returns the current role chosen by a user for this session
|
||||
|
||||
Args:
|
||||
request (HttpRequest): The used request
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
return request.session.get(CURRENT_ROLE_ID, {})
|
||||
@@ -0,0 +1,121 @@
|
||||
"""
|
||||
Author: Michel Peltriaux
|
||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||
Created on: 25.11.20
|
||||
|
||||
"""
|
||||
import uuid
|
||||
|
||||
from django import forms
|
||||
from django.core.paginator import PageNotAnInteger, EmptyPage
|
||||
from django.http import HttpRequest
|
||||
from django.utils.html import format_html
|
||||
import django_tables2 as tables
|
||||
|
||||
from konova.forms import BaseForm
|
||||
from konova.settings import PAGE_SIZE_DEFAULT, PAGE_PARAM, RESULTS_PER_PAGE_PARAM, PAGE_SIZE_OPTIONS
|
||||
|
||||
|
||||
class BaseTable(tables.tables.Table):
|
||||
results_per_page_choices = PAGE_SIZE_OPTIONS
|
||||
results_per_page_chosen = None
|
||||
results_per_page_parameter = RESULTS_PER_PAGE_PARAM
|
||||
add_new_entries = True
|
||||
add_new_url = None
|
||||
title = None
|
||||
|
||||
def __init__(self, request: HttpRequest = None, filter_set=None, queryset=None, *args, **kwargs):
|
||||
self.user = request.user or None
|
||||
if filter_set is not None:
|
||||
queryset = filter_set.qs
|
||||
kwargs["data"] = queryset
|
||||
kwargs["request"] = request
|
||||
super().__init__(*args, **kwargs)
|
||||
self.results_per_page_chosen = int(request.GET.get(RESULTS_PER_PAGE_PARAM, PAGE_SIZE_DEFAULT))
|
||||
try:
|
||||
self.paginate(
|
||||
page=request.GET.get(PAGE_PARAM, 1),
|
||||
per_page=self.results_per_page_chosen,
|
||||
)
|
||||
except (PageNotAnInteger, EmptyPage) as e:
|
||||
self.paginate(
|
||||
page=1,
|
||||
per_page=self.results_per_page_chosen,
|
||||
)
|
||||
|
||||
def render_link(self, tooltip: str, href: str, txt: str, new_tab: bool = False):
|
||||
"""
|
||||
Returns an <a> html element using given parameters
|
||||
"""
|
||||
new_tab = "_blank" if new_tab else "_self"
|
||||
return format_html(
|
||||
"<a href={} target='{}' title='{}'>{}</a>",
|
||||
href,
|
||||
new_tab,
|
||||
tooltip,
|
||||
txt,
|
||||
)
|
||||
|
||||
def render_delete_btn(self, tooltip: str = None, href: str = None):
|
||||
"""
|
||||
Returns a remover icon with <a> support as html element using given parameters
|
||||
"""
|
||||
return format_html(
|
||||
"<a href={} title='{}'><button class='button small'><em class='fas fa-trash-alt'></em></button></a>",
|
||||
href,
|
||||
tooltip,
|
||||
)
|
||||
|
||||
def render_edit_btn(self, tooltip: str = None, href: str = None):
|
||||
"""
|
||||
Returns a remover icon with <a> support as html element using given parameters
|
||||
"""
|
||||
return format_html(
|
||||
"<a href={} title='{}'><button class='button small'><em class='fas fa-edit'></em></button></a>",
|
||||
href,
|
||||
tooltip,
|
||||
)
|
||||
|
||||
def render_open_btn(self, tooltip: str = None, href: str = None, new_tab: bool = False):
|
||||
"""
|
||||
Returns a remover icon with <a> support as html element using given parameters
|
||||
"""
|
||||
return format_html(
|
||||
"<a href={} title='{}' target='{}'><button class='button small'><em class='fas fa-sign-in-alt'></em></button></a>",
|
||||
href,
|
||||
tooltip,
|
||||
"_blank" if new_tab else ""
|
||||
)
|
||||
|
||||
def render_boolean(self, tooltip: str = None, val: bool = False):
|
||||
"""
|
||||
Returns a remover icon with <a> support as html element using given parameters
|
||||
"""
|
||||
icon = "fas fa-check-circle true" if val else "fas fa-times-circle false"
|
||||
return format_html(
|
||||
"<em title='{}' class='{}'></em>",
|
||||
tooltip,
|
||||
icon
|
||||
)
|
||||
|
||||
|
||||
class ChoicesColumnForm(BaseForm):
|
||||
select = forms.ChoiceField(
|
||||
choices=[],
|
||||
label="",
|
||||
label_suffix="",
|
||||
widget=forms.Select(
|
||||
attrs={
|
||||
"onchange": "submit();",
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.action_url = kwargs.pop("action_url", None)
|
||||
self.choices = kwargs.pop("choices", [])
|
||||
super().__init__(*args, **kwargs)
|
||||
self.auto_id += "_" + str(uuid.uuid4())
|
||||
if len(self.choices) > 0:
|
||||
self.fields["select"].choices = self.choices
|
||||
@@ -0,0 +1,67 @@
|
||||
"""
|
||||
Author: Michel Peltriaux
|
||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||
Created on: 16.11.20
|
||||
|
||||
"""
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth import logout
|
||||
from django.http import HttpRequest
|
||||
from django.shortcuts import redirect, render
|
||||
|
||||
from konova.contexts import BaseContext
|
||||
from konova.forms import ChangeUserRoleForm
|
||||
from konova.settings import SSO_SERVER_BASE
|
||||
from konova.utils.session import get_session_user_role
|
||||
|
||||
|
||||
def logout_view(request: HttpRequest):
|
||||
"""
|
||||
Logout route for ending the session manually.
|
||||
|
||||
Args:
|
||||
request (HttpRequest): The used request object
|
||||
|
||||
Returns:
|
||||
A redirect
|
||||
"""
|
||||
logout(request)
|
||||
return redirect(SSO_SERVER_BASE)
|
||||
|
||||
|
||||
def home_view(request: HttpRequest):
|
||||
"""
|
||||
Renders the landing page
|
||||
|
||||
Args:
|
||||
request (HttpRequest): The used request object
|
||||
|
||||
Returns:
|
||||
A redirect
|
||||
"""
|
||||
template = "konova/home.html"
|
||||
|
||||
if request.method == "POST":
|
||||
form = ChangeUserRoleForm(
|
||||
request.POST or None,
|
||||
user=request.user,
|
||||
)
|
||||
if form.is_valid():
|
||||
role = form.save(request)
|
||||
messages.success(request, _("Role changed"))
|
||||
else:
|
||||
messages.error(request, _("Invalid role"))
|
||||
return redirect("home")
|
||||
else:
|
||||
# GET
|
||||
form = ChangeUserRoleForm(
|
||||
user=request.user,
|
||||
initial={"role": int(get_session_user_role(request).get("id", -1))},
|
||||
)
|
||||
additional_context = {
|
||||
"form": form,
|
||||
}
|
||||
context = BaseContext(request, additional_context).context
|
||||
return render(request, template, context)
|
||||
@@ -0,0 +1,16 @@
|
||||
"""
|
||||
WSGI config for konova project.
|
||||
|
||||
It exposes the WSGI callable as a module-level variable named ``application``.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/3.1/howto/deployment/wsgi/
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'konova.settings')
|
||||
|
||||
application = get_wsgi_application()
|
||||
Binary file not shown.
File diff suppressed because it is too large.
Load diff
Whitespace-only changes.
@@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env python
|
||||
"""Django's command-line utility for administrative tasks."""
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
def main():
|
||||
"""Run administrative tasks."""
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'konova.settings')
|
||||
try:
|
||||
from django.core.management import execute_from_command_line
|
||||
except ImportError as exc:
|
||||
raise ImportError(
|
||||
"Couldn't import Django. Are you sure it's installed and "
|
||||
"available on your PYTHONPATH environment variable? Did you "
|
||||
"forget to activate a virtual environment?"
|
||||
) from exc
|
||||
execute_from_command_line(sys.argv)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Whitespace-only changes.
@@ -0,0 +1,15 @@
|
||||
from django.contrib import admin
|
||||
|
||||
from organisation.models import Organisation
|
||||
|
||||
|
||||
class OrganisationAdmin(admin.ModelAdmin):
|
||||
list_display = [
|
||||
"name",
|
||||
"type",
|
||||
"created_on",
|
||||
"created_by",
|
||||
]
|
||||
|
||||
|
||||
admin.site.register(Organisation, OrganisationAdmin)
|
||||
@@ -0,0 +1,5 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class OrganisationConfig(AppConfig):
|
||||
name = 'organisation'
|
||||
@@ -0,0 +1,26 @@
|
||||
"""
|
||||
Author: Michel Peltriaux
|
||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||
Created on: 07.12.20
|
||||
|
||||
"""
|
||||
from konova.enums import BaseEnum
|
||||
|
||||
|
||||
class RoleTypeEnum(BaseEnum):
|
||||
"""
|
||||
Defines the possible role types for organisation and users
|
||||
"""
|
||||
DATAPROVIDER = "DATAPROVIDER"
|
||||
LICENSINGAUTHORITY = "LICENSINGAUTHORITY"
|
||||
REGISTRATIONOFFICE = "REGISTRATIONOFFICE"
|
||||
|
||||
|
||||
class OrganisationTypeEnum(BaseEnum):
|
||||
"""
|
||||
Defines the possible role types for organisation and users
|
||||
"""
|
||||
OFFICIAL = "OFFICIAL"
|
||||
COMPANY = "COMPANY"
|
||||
NGO = "NGO"
|
||||
@@ -0,0 +1,18 @@
|
||||
from django.db import models
|
||||
|
||||
from konova.models import BaseResource
|
||||
from organisation.enums import OrganisationTypeEnum
|
||||
|
||||
|
||||
class Organisation(BaseResource):
|
||||
name = models.CharField(max_length=500, unique=True)
|
||||
address = models.CharField(max_length=500, null=True, blank=True)
|
||||
city = models.CharField(max_length=500, null=True, blank=True)
|
||||
postal_code = models.CharField(max_length=100, null=True, blank=True)
|
||||
phone = models.CharField(max_length=500, null=True, blank=True)
|
||||
email = models.EmailField(max_length=500, null=True, blank=True)
|
||||
facsimile = models.CharField(max_length=500, null=True, blank=True)
|
||||
type = models.CharField(max_length=255, choices=OrganisationTypeEnum.as_choices(drop_empty_choice=True), null=True, blank=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
@@ -0,0 +1,22 @@
|
||||
"""
|
||||
Author: Michel Peltriaux
|
||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||
Created on: 07.12.20
|
||||
|
||||
"""
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from organisation.enums import OrganisationTypeEnum as ote
|
||||
from organisation.enums import RoleTypeEnum as rte
|
||||
|
||||
ORGANISATION_ROLE_STRINGS = {
|
||||
ote.OFFICIAL.value: _("Official"),
|
||||
ote.COMPANY.value: _("Company"),
|
||||
ote.NGO.value: _("NGO"),
|
||||
}
|
||||
|
||||
ROLE_TYPE_STRINGS = {
|
||||
rte.DATAPROVIDER.value: _("Data provider"),
|
||||
rte.LICENSINGAUTHORITY.value: _("Licencing Authority"),
|
||||
rte.REGISTRATIONOFFICE.value: _("Registration office"),
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
@@ -0,0 +1,12 @@
|
||||
"""
|
||||
Author: Michel Peltriaux
|
||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||
Created on: 07.12.20
|
||||
|
||||
"""
|
||||
from django.urls import path
|
||||
|
||||
app_name = "organisation"
|
||||
urlpatterns = [
|
||||
]
|
||||
@@ -0,0 +1,3 @@
|
||||
from django.shortcuts import render
|
||||
|
||||
# Create your views here.
|
||||
Whitespace-only changes.
@@ -0,0 +1,38 @@
|
||||
from django.contrib import admin
|
||||
|
||||
from process.models import Process
|
||||
|
||||
|
||||
def activate_process(modeladmin, request, queryset):
|
||||
for process in queryset:
|
||||
process.activate()
|
||||
|
||||
|
||||
def deactivate_process(modeladmin, request, queryset):
|
||||
for process in queryset:
|
||||
process.deactivate()
|
||||
|
||||
|
||||
activate_process.short_description = "Activate selected process"
|
||||
deactivate_process.short_description = "Deactivate selected process"
|
||||
|
||||
|
||||
class ProcessAdmin(admin.ModelAdmin):
|
||||
list_display = [
|
||||
"id",
|
||||
"licensing_authority",
|
||||
"licensing_authority_document_identifier",
|
||||
"registration_office",
|
||||
"registration_office_document_identifier",
|
||||
"is_active",
|
||||
"is_deleted",
|
||||
]
|
||||
actions = [
|
||||
activate_process,
|
||||
deactivate_process,
|
||||
]
|
||||
|
||||
|
||||
|
||||
|
||||
admin.site.register(Process, ProcessAdmin)
|
||||
@@ -0,0 +1,5 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class ProcessConfig(AppConfig):
|
||||
name = 'process'
|
||||
@@ -0,0 +1,202 @@
|
||||
"""
|
||||
Author: Michel Peltriaux
|
||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||
Created on: 25.11.20
|
||||
|
||||
"""
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
from konova.enums import BaseEnum
|
||||
from organisation.enums import RoleTypeEnum
|
||||
|
||||
|
||||
class ProcessStateEnum(BaseEnum):
|
||||
"""
|
||||
ProcessStates define in which state a process can be:
|
||||
|
||||
private
|
||||
Only the user can see and edit this process. Not viewable by any others
|
||||
accessible
|
||||
The user and all participated users can see this process. Only the responsible next user can edit
|
||||
the process
|
||||
licensed
|
||||
The user and all participated users can see this process. Only the responsible next user can edit
|
||||
the process
|
||||
official
|
||||
The user and all participated users can see this process. Only the registration office user can now
|
||||
change details. If a process is changed in this state, the state will be set back to accessible, so all
|
||||
participated users need to take a look on the changed data again.
|
||||
recorded [Will be set automatically after certain time]
|
||||
The user and all participated users can see this process. No one can edit data. To change any details,
|
||||
the process has to be set to accessible manually again.
|
||||
"""
|
||||
PRIVATE = 0
|
||||
ACCESSIBLE = 1
|
||||
LICENSED = 2
|
||||
OFFICIAL = 3
|
||||
RECORDED = 4
|
||||
|
||||
@classmethod
|
||||
def as_choices(cls, drop_empty_choice: bool = False) -> list:
|
||||
""" Extends as_choices, so choices will be translated
|
||||
|
||||
Args:
|
||||
drop_empty_choice (bool): Whether the empty choice shall be dropped or not
|
||||
|
||||
Returns:
|
||||
trans_choices (list): Translated choices
|
||||
"""
|
||||
choices = super().as_choices(drop_empty_choice)
|
||||
return ProcessStateEnum.__translate_choices(choices)
|
||||
|
||||
@staticmethod
|
||||
def __translate_choices(choices: list) -> list:
|
||||
""" Translates a list of prepared but untranslated choices
|
||||
|
||||
Args:
|
||||
choices (list): A list of tuple chocies
|
||||
|
||||
Returns:
|
||||
choices (list): The same list but translated
|
||||
"""
|
||||
from process.settings import PROCESS_STATE_STRINGS
|
||||
trans_choices = []
|
||||
# Translate
|
||||
for choice in choices:
|
||||
if choice[0] is not None:
|
||||
choice = list(choice)
|
||||
trans = PROCESS_STATE_STRINGS.get(choice[0])
|
||||
choice[1] = trans
|
||||
choice = tuple(choice)
|
||||
trans_choices.append(choice)
|
||||
return trans_choices
|
||||
|
||||
@classmethod
|
||||
def is_state(cls, state: int) -> bool:
|
||||
""" Checks whether the given state is a valid Enum
|
||||
|
||||
Args:
|
||||
state (int): The state to be checked
|
||||
|
||||
Returns:
|
||||
is_valid (bool)
|
||||
"""
|
||||
valid_vals = {enum.value for enum in cls}
|
||||
return state in valid_vals
|
||||
|
||||
@classmethod
|
||||
def as_role_choices(cls, user: User) -> list:
|
||||
""" Checks whether the given state is a valid Enum
|
||||
|
||||
Args:
|
||||
user (User): The performing user
|
||||
|
||||
Returns:
|
||||
is_valid (bool)
|
||||
"""
|
||||
role = user.current_role
|
||||
if role is None:
|
||||
return []
|
||||
role_type = role.role.type
|
||||
role_type_enum = RoleTypeEnum[role_type]
|
||||
choices = PROCESS_ROLE_STATES.get(role_type_enum)
|
||||
choices = [(enum.value, enum.name) for enum in choices]
|
||||
choices = ProcessStateEnum.__translate_choices(choices)
|
||||
return choices
|
||||
|
||||
@classmethod
|
||||
def as_next_role_choices(cls, user: User, current_state: int, is_owner: bool = False) -> list:
|
||||
""" Returns a list of valid choices depending on the current role of the user and the current state
|
||||
|
||||
Args:
|
||||
user (User): The performing user
|
||||
current_state (int): The current state of the process
|
||||
|
||||
Returns:
|
||||
choices (list): A list of valid choices
|
||||
"""
|
||||
role = user.current_role
|
||||
if role is None:
|
||||
return []
|
||||
role_type = role.role.type
|
||||
role_type_enum = RoleTypeEnum[role_type]
|
||||
|
||||
# Merge the possible choices depending on the current user role
|
||||
# with the possible choices depending on the process state
|
||||
role_choices = PROCESS_ROLE_STATES.get(role_type_enum)
|
||||
status_choices = PROCESS_STATE_NEXT_CHOICES.get(current_state)
|
||||
current_choice = {ProcessStateEnum(current_state)}
|
||||
choices = (status_choices & role_choices) | current_choice
|
||||
|
||||
# If user is owner of this process, we shall add the private choice if not existing, yet
|
||||
if is_owner:
|
||||
choices = {ProcessStateEnum.PRIVATE} | choices
|
||||
|
||||
# Make sure enums are ordered by numerical value
|
||||
choices = sorted(choices, key=lambda _enum: _enum.value)
|
||||
|
||||
# Create selectable and translated choices from enum list
|
||||
choices = [(enum.value, enum.name) for enum in choices]
|
||||
choices = ProcessStateEnum.__translate_choices(choices)
|
||||
return choices
|
||||
|
||||
|
||||
# DEFINES THE AVAILABLE STATES FOR EACH ROLE
|
||||
PROCESS_ROLE_STATES = {
|
||||
RoleTypeEnum.DATAPROVIDER: {
|
||||
ProcessStateEnum.PRIVATE,
|
||||
ProcessStateEnum.ACCESSIBLE,
|
||||
},
|
||||
RoleTypeEnum.LICENSINGAUTHORITY: {
|
||||
#ProcessStateEnum.PRIVATE,
|
||||
ProcessStateEnum.ACCESSIBLE,
|
||||
ProcessStateEnum.LICENSED,
|
||||
},
|
||||
RoleTypeEnum.REGISTRATIONOFFICE: {
|
||||
#ProcessStateEnum.PRIVATE,
|
||||
ProcessStateEnum.ACCESSIBLE,
|
||||
ProcessStateEnum.OFFICIAL,
|
||||
},
|
||||
}
|
||||
|
||||
# DEFINES POSSIBLE NEXT STATES FOR EACH PROCESS STATE
|
||||
PROCESS_STATE_NEXT_CHOICES = {
|
||||
ProcessStateEnum.PRIVATE.value: {
|
||||
ProcessStateEnum.PRIVATE,
|
||||
ProcessStateEnum.ACCESSIBLE,
|
||||
},
|
||||
ProcessStateEnum.ACCESSIBLE.value: {
|
||||
ProcessStateEnum.PRIVATE,
|
||||
ProcessStateEnum.ACCESSIBLE,
|
||||
ProcessStateEnum.LICENSED,
|
||||
},
|
||||
ProcessStateEnum.LICENSED.value: {
|
||||
ProcessStateEnum.PRIVATE,
|
||||
ProcessStateEnum.ACCESSIBLE,
|
||||
ProcessStateEnum.LICENSED,
|
||||
ProcessStateEnum.OFFICIAL,
|
||||
},
|
||||
ProcessStateEnum.OFFICIAL.value: {
|
||||
ProcessStateEnum.ACCESSIBLE,
|
||||
ProcessStateEnum.OFFICIAL,
|
||||
ProcessStateEnum.RECORDED,
|
||||
},
|
||||
}
|
||||
|
||||
# DEFINES FOR EACH STATE WHICH ROLE CAN EDIT THE PROCESS
|
||||
PROCESS_EDITABLE_STATE = {
|
||||
ProcessStateEnum.PRIVATE.value: {
|
||||
RoleTypeEnum.DATAPROVIDER,
|
||||
RoleTypeEnum.LICENSINGAUTHORITY,
|
||||
},
|
||||
ProcessStateEnum.ACCESSIBLE.value: {
|
||||
RoleTypeEnum.LICENSINGAUTHORITY,
|
||||
},
|
||||
ProcessStateEnum.LICENSED.value: {
|
||||
RoleTypeEnum.REGISTRATIONOFFICE,
|
||||
},
|
||||
ProcessStateEnum.OFFICIAL.value: {
|
||||
RoleTypeEnum.REGISTRATIONOFFICE,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,218 @@
|
||||
"""
|
||||
Author: Michel Peltriaux
|
||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||
Created on: 30.11.20
|
||||
|
||||
"""
|
||||
from dal import autocomplete
|
||||
from django import forms
|
||||
from django.contrib.auth.models import User
|
||||
from django.db import transaction
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from intervention.models import Intervention
|
||||
from konova.forms import BaseForm
|
||||
from organisation.models import Organisation
|
||||
from process.enums import ProcessStateEnum
|
||||
from process.models import Process
|
||||
|
||||
|
||||
class NewProcessForm(BaseForm):
|
||||
"""
|
||||
A form for new process
|
||||
"""
|
||||
identifier = forms.CharField(
|
||||
max_length=255,
|
||||
label=_("Identifier"),
|
||||
label_suffix=_(""),
|
||||
help_text=_("Generated automatically if none was given"),
|
||||
required=False,
|
||||
)
|
||||
title = forms.CharField(
|
||||
max_length=255,
|
||||
label=_("Title"),
|
||||
label_suffix=_(""),
|
||||
help_text=_("Proper title of the process"),
|
||||
required=True,
|
||||
)
|
||||
type = forms.CharField(
|
||||
max_length=255,
|
||||
label=_("Type"),
|
||||
label_suffix=_(""),
|
||||
help_text=_("Which process type is this"),
|
||||
required=True,
|
||||
)
|
||||
|
||||
licensing_authority = forms.ModelChoiceField(
|
||||
label=_("Licencing Authority"),
|
||||
label_suffix=_(""),
|
||||
required=True,
|
||||
queryset=Organisation.objects.all(),
|
||||
widget=autocomplete.ModelSelect2(
|
||||
url="orgs-autocomplete",
|
||||
attrs={
|
||||
"data-placeholder": _("Organization"),
|
||||
"data-minimum-input-length": 3,
|
||||
}
|
||||
),
|
||||
)
|
||||
licensing_authority_document_identifier = forms.CharField(
|
||||
max_length=255,
|
||||
label=_("Licencing document identifier"),
|
||||
label_suffix=_(""),
|
||||
required=True,
|
||||
)
|
||||
comment_licensing_authority = forms.CharField(
|
||||
widget=forms.Textarea,
|
||||
label=_("Comment licensing authority"),
|
||||
label_suffix=_(""),
|
||||
required=False,
|
||||
)
|
||||
|
||||
registration_office = forms.ModelChoiceField(
|
||||
label=_("Registration office"),
|
||||
label_suffix=_(""),
|
||||
required=True,
|
||||
queryset=Organisation.objects.all(),
|
||||
widget=autocomplete.ModelSelect2(
|
||||
url="orgs-autocomplete",
|
||||
attrs={
|
||||
"data-placeholder": _("Organization"),
|
||||
"data-minimum-input-length": 3,
|
||||
}
|
||||
),
|
||||
)
|
||||
registration_office_document_identifier = forms.CharField(
|
||||
max_length=255,
|
||||
label=_("Registration document identifier"),
|
||||
label_suffix=_(""),
|
||||
required=True,
|
||||
)
|
||||
comment_registration_office = forms.CharField(
|
||||
widget=forms.Textarea,
|
||||
label=_("Comment registration office"),
|
||||
label_suffix=_(""),
|
||||
required=False,
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.action_url = reverse("process:new")
|
||||
self.cancel_redirect = reverse("process:index")
|
||||
self.form_title = _("Add new process")
|
||||
self.form_caption = _("Enter these basic information for the new process.")
|
||||
|
||||
def save(self, user: User):
|
||||
"""
|
||||
Persists process objects into database
|
||||
"""
|
||||
with transaction.atomic():
|
||||
identifier = self.cleaned_data.get("identifier", None)
|
||||
title = self.cleaned_data.get("title", None)
|
||||
_type = self.cleaned_data.get("type", None)
|
||||
licencing_auth = self.cleaned_data.get("licensing_authority", None)
|
||||
licencing_auth_doc_id = self.cleaned_data.get("licensing_authority_document_identifier", None)
|
||||
reg_off = self.cleaned_data.get("registration_office", None)
|
||||
reg_off_doc_id = self.cleaned_data.get("registration_office_document_identifier", None)
|
||||
comment_license = self.cleaned_data.get("comment_licensing_authority", None)
|
||||
comment_registration = self.cleaned_data.get("comment_registration_office", None)
|
||||
|
||||
process = Process()
|
||||
process.licensing_authority = licencing_auth
|
||||
process.licensing_authority_document_identifier = licencing_auth_doc_id
|
||||
process.registration_office = reg_off
|
||||
process.registration_office_document_identifier = reg_off_doc_id
|
||||
process.state = ProcessStateEnum.PRIVATE.value
|
||||
process.created_by = user
|
||||
process.licensing_authority_comment = comment_license
|
||||
process.registration_office_comment = comment_registration
|
||||
process.save()
|
||||
|
||||
intervention = Intervention()
|
||||
intervention.title = title
|
||||
intervention.type = _type
|
||||
intervention.created_by = user
|
||||
intervention.process = process
|
||||
intervention.identifier = identifier
|
||||
intervention.save()
|
||||
|
||||
return process
|
||||
|
||||
|
||||
class EditProcessForm(NewProcessForm):
|
||||
status = forms.ChoiceField(
|
||||
label=_("Status"),
|
||||
label_suffix="",
|
||||
choices=[],
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.user = kwargs.pop("user", None)
|
||||
super().__init__(*args, **kwargs)
|
||||
if self.instance is not None:
|
||||
self.action_url = reverse("process:edit", args=(self.instance.id,))
|
||||
self.cancel_redirect = reverse("process:index")
|
||||
self.form_title = _("Edit process")
|
||||
self.form_caption = ""
|
||||
self.fields["status"].choices = ProcessStateEnum.as_next_role_choices(self.user, self.instance.state)
|
||||
|
||||
# Initialize form data
|
||||
form_data = {
|
||||
"identifier": self.instance.intervention.identifier,
|
||||
"title": self.instance.intervention.title,
|
||||
"type": self.instance.intervention.type,
|
||||
"licensing_authority": self.instance.licensing_authority,
|
||||
"licensing_authority_document_identifier": self.instance.licensing_authority_document_identifier,
|
||||
"registration_office": self.instance.registration_office,
|
||||
"registration_office_document_identifier": self.instance.registration_office_document_identifier,
|
||||
"comment_licensing_authority": self.instance.licensing_authority_comment,
|
||||
"comment_registration_office": self.instance.registration_office_comment,
|
||||
"status": self.instance.state,
|
||||
}
|
||||
disabled_fields = [
|
||||
"identifier",
|
||||
]
|
||||
self.load_initial_data(
|
||||
form_data,
|
||||
disabled_fields,
|
||||
)
|
||||
|
||||
def save(self, user: User):
|
||||
""" Persists changes from form to instance
|
||||
|
||||
Args:
|
||||
user (User): The performing user
|
||||
|
||||
Returns:
|
||||
process (Process): The edited process instance
|
||||
"""
|
||||
with transaction.atomic():
|
||||
title = self.cleaned_data.get("title", None)
|
||||
_type = self.cleaned_data.get("type", None)
|
||||
licencing_auth = self.cleaned_data.get("licensing_authority", None)
|
||||
licencing_auth_doc_id = self.cleaned_data.get("licensing_authority_document_identifier", None)
|
||||
reg_off = self.cleaned_data.get("registration_office", None)
|
||||
reg_off_doc_id = self.cleaned_data.get("registration_office_document_identifier", None)
|
||||
comment_license = self.cleaned_data.get("comment_licensing_authority", None)
|
||||
comment_registration = self.cleaned_data.get("comment_registration_office", None)
|
||||
new_state = self.cleaned_data.get("status", None)
|
||||
|
||||
self.instance.licensing_authority = licencing_auth
|
||||
self.instance.licensing_authority_document_identifier = licencing_auth_doc_id
|
||||
self.instance.registration_office = reg_off
|
||||
self.instance.registration_office_document_identifier = reg_off_doc_id
|
||||
self.instance.state = ProcessStateEnum.PRIVATE.value
|
||||
self.instance.created_by = user
|
||||
self.instance.licensing_authority_comment = comment_license
|
||||
self.instance.registration_office_comment = comment_registration
|
||||
self.instance.state = new_state
|
||||
self.instance.save()
|
||||
|
||||
self.instance.intervention.title = title
|
||||
self.instance.intervention.type = _type
|
||||
self.instance.intervention.created_by = user
|
||||
self.instance.intervention.save()
|
||||
|
||||
return self.instance
|
||||
@@ -0,0 +1,158 @@
|
||||
"""
|
||||
Author: Michel Peltriaux
|
||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||
Created on: 17.11.20
|
||||
|
||||
"""
|
||||
from django.contrib.auth.models import User
|
||||
from django.db import models, transaction
|
||||
|
||||
from konova.models import BaseResource
|
||||
from organisation.enums import RoleTypeEnum
|
||||
from organisation.models import Organisation
|
||||
from process.enums import ProcessStateEnum
|
||||
from process.settings import PROCESS_STATE_STRINGS
|
||||
|
||||
|
||||
class Process(BaseResource):
|
||||
"""
|
||||
Process links compensation, intervention and eco accounts. These links are realized as ForeignKeys in their models.
|
||||
|
||||
This process model therefore just holds further information on participated legal organizations.
|
||||
|
||||
Attribute 'state' holds information on the current state of the process, which are defined in ProcessStateEnum
|
||||
"""
|
||||
licensing_authority = models.ForeignKey(Organisation, on_delete=models.SET_NULL, null=True, blank=True, related_name="+")
|
||||
licensing_authority_document_identifier = models.CharField(max_length=500, null=True, blank=True)
|
||||
licensing_authority_comment = models.CharField(max_length=500, null=True, blank=True)
|
||||
registration_office_document_identifier = models.CharField(max_length=500, null=True, blank=True)
|
||||
registration_office = models.ForeignKey(Organisation, on_delete=models.SET_NULL, null=True, blank=True, related_name="+")
|
||||
registration_office_comment = models.CharField(max_length=500, null=True, blank=True)
|
||||
state = models.PositiveIntegerField(choices=ProcessStateEnum.as_choices(drop_empty_choice=True), default=0)
|
||||
|
||||
def __str__(self) -> str:
|
||||
try:
|
||||
intervention = self.intervention
|
||||
title = intervention.title
|
||||
except AttributeError:
|
||||
title = "NO TITLE"
|
||||
return title
|
||||
|
||||
def get_state_str(self):
|
||||
"""
|
||||
Translates the numeric state into a string
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
return PROCESS_STATE_STRINGS.get(self.state, None)
|
||||
|
||||
def deactivate(self):
|
||||
""" Deactivates a process and it's related elements
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
if self.is_active and not self.is_deleted:
|
||||
self.toggle_deletion()
|
||||
|
||||
def activate(self):
|
||||
""" Activates a process and it's related elements
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
if not self.is_active and self.is_deleted:
|
||||
self.toggle_deletion()
|
||||
|
||||
def toggle_deletion(self):
|
||||
""" Enables or disables a process
|
||||
|
||||
Processes are not truly removed from the database, just toggled in their flags 'is_active' and 'is_deleted'
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
with transaction.atomic():
|
||||
self.is_active = not self.is_active
|
||||
self.is_deleted = not self.is_deleted
|
||||
self.save()
|
||||
|
||||
# toggle related elements
|
||||
comps = self.compensation.all()
|
||||
elements = list(comps)
|
||||
if self.intervention is not None:
|
||||
elements.append(self.intervention)
|
||||
|
||||
for elem in elements:
|
||||
elem.is_active = self.is_active
|
||||
elem.is_deleted = self.is_deleted
|
||||
elem.save()
|
||||
|
||||
@staticmethod
|
||||
def get_role_objects(user: User, order_by: str = "-created_on"):
|
||||
""" Returns processes depending on the currently selected role of the user
|
||||
|
||||
* REGISTRATIONOFFICE
|
||||
* User can see the processes where registration_office is set to the organisation of the currently selected role
|
||||
* User can see self-created processes
|
||||
* LICENSINGOFFICE
|
||||
* same
|
||||
* DATAPROVIDER
|
||||
* User can see only self-created processes
|
||||
|
||||
Args:
|
||||
user (User): The performing user
|
||||
order_by (str): Order by which Process attribute
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
role = user.current_role
|
||||
if role is None:
|
||||
return Process.objects.none()
|
||||
_filter = {
|
||||
"is_deleted": False,
|
||||
}
|
||||
if role.role.type == RoleTypeEnum.REGISTRATIONOFFICE.value:
|
||||
_filter["registration_office"] = role.organisation
|
||||
elif role.role.type == RoleTypeEnum.LICENSINGAUTHORITY.value:
|
||||
_filter["licensing_authority"] = role.organisation
|
||||
elif role.role.type == RoleTypeEnum.DATAPROVIDER.value:
|
||||
# Nothing special
|
||||
_filter["created_by"] = user
|
||||
else:
|
||||
# None of the above
|
||||
pass
|
||||
|
||||
other_processes = Process.objects.filter(
|
||||
**_filter
|
||||
)
|
||||
user_created_processes = Process.objects.filter(
|
||||
created_by=user,
|
||||
is_deleted=False,
|
||||
)
|
||||
qs = (other_processes | user_created_processes).distinct()
|
||||
qs = qs.order_by(order_by)
|
||||
return qs
|
||||
|
||||
@staticmethod
|
||||
def create_from_intervention(intervention):
|
||||
""" Creates a process for an intervention, in case an intervention has been created without a process
|
||||
|
||||
Args:
|
||||
intervention (Intervention): The intervention
|
||||
|
||||
Returns:
|
||||
process (Process)
|
||||
"""
|
||||
process = Process()
|
||||
process.identifier = intervention.identifier
|
||||
process.title = intervention.title
|
||||
process.created_by = intervention.created_by
|
||||
process.save()
|
||||
|
||||
intervention.process = process
|
||||
intervention.save()
|
||||
return process
|
||||
@@ -0,0 +1,19 @@
|
||||
"""
|
||||
Author: Michel Peltriaux
|
||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||
Created on: 25.11.20
|
||||
|
||||
"""
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from process.enums import ProcessStateEnum
|
||||
|
||||
# DEFINES TRANSLATIONS FOR EACH PROCESSSTATEENUM
|
||||
PROCESS_STATE_STRINGS = {
|
||||
ProcessStateEnum.PRIVATE.value: _("private"),
|
||||
ProcessStateEnum.ACCESSIBLE.value: _("accessible"),
|
||||
ProcessStateEnum.LICENSED.value: _("licensed"),
|
||||
ProcessStateEnum.OFFICIAL.value: _("official"),
|
||||
ProcessStateEnum.RECORDED.value: _("recorded"),
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
"""
|
||||
Author: Michel Peltriaux
|
||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||
Created on: 25.11.20
|
||||
|
||||
"""
|
||||
from django.template.loader import render_to_string
|
||||
from django.urls import reverse
|
||||
from django.utils.html import format_html
|
||||
|
||||
from konova.utils.tables import BaseTable, ChoicesColumnForm
|
||||
import django_tables2 as tables
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from process.enums import ProcessStateEnum
|
||||
from process.models import Process
|
||||
from process.settings import PROCESS_STATE_STRINGS
|
||||
|
||||
|
||||
class ProcessTable(BaseTable):
|
||||
id = tables.Column(
|
||||
verbose_name=_("Intervention identifier"),
|
||||
orderable=True,
|
||||
accessor="intervention.identifier",
|
||||
)
|
||||
t = tables.Column(
|
||||
verbose_name=_("Title"),
|
||||
orderable=True,
|
||||
accessor="intervention.title",
|
||||
)
|
||||
"""
|
||||
THESE COLUMNS MIGHT NOT BE OF INTEREST. TO REDUCE TABLE WIDTH THEY CAN BE REMOVED
|
||||
|
||||
dila = tables.Column(
|
||||
verbose_name=_("Licensing authority document identifier"),
|
||||
orderable=True,
|
||||
accessor="licensing_authority_document_identifier",
|
||||
)
|
||||
diro = tables.Column(
|
||||
verbose_name=_("Registration office document identifier"),
|
||||
orderable=True,
|
||||
accessor="registration_office_document_identifier",
|
||||
)
|
||||
"""
|
||||
s = tables.Column(
|
||||
verbose_name=_("Status"),
|
||||
orderable=True,
|
||||
accessor="state",
|
||||
)
|
||||
d = tables.Column(
|
||||
verbose_name=_("Created on"),
|
||||
orderable=True,
|
||||
accessor="created_on",
|
||||
)
|
||||
ac = tables.Column(
|
||||
verbose_name=_("Actions"),
|
||||
orderable=False,
|
||||
empty_values=[],
|
||||
attrs={"td": {"class": "action-col"}}
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.title = _("Processes")
|
||||
self.add_new_url = reverse("process:new")
|
||||
|
||||
def render_id(self, value, record: Process):
|
||||
""" Renders the id column for an intervention
|
||||
|
||||
Args:
|
||||
value (str): The identifier value
|
||||
record (Process): The process record
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
html = ""
|
||||
html += self.render_link(
|
||||
tooltip=_("Open {}").format(_("Process")),
|
||||
href=reverse("process:open", args=(record.id,)),
|
||||
txt=value,
|
||||
new_tab=False,
|
||||
)
|
||||
return format_html(html)
|
||||
|
||||
def render_s(self, value, record, column) -> str:
|
||||
""" Translates the record state to a desired language
|
||||
|
||||
Args:
|
||||
value (str): The value of state inside record
|
||||
record (Process): The whole record itself
|
||||
|
||||
Returns:
|
||||
str
|
||||
"""
|
||||
state = record.state
|
||||
is_owner = record.created_by == self.request.user
|
||||
choices = ProcessStateEnum.as_next_role_choices(self.request.user, state, is_owner)
|
||||
valid_choices = [choice[0] for choice in choices]
|
||||
if state in valid_choices:
|
||||
form = ChoicesColumnForm(
|
||||
action_url=reverse("process:edit-status", args=(record.id,)),
|
||||
choices=choices,
|
||||
initial={"select": state},
|
||||
)
|
||||
rendered = render_to_string("konova/choiceColumnForm.html", context={"form": form}, request=self.request)
|
||||
else:
|
||||
rendered = PROCESS_STATE_STRINGS.get(state)
|
||||
return rendered
|
||||
|
||||
def render_ac(self, value, record):
|
||||
"""
|
||||
Renders possible actions for this record, such as delete.
|
||||
"""
|
||||
process = _("Process")
|
||||
html = ""
|
||||
html += self.render_open_btn(
|
||||
_("Open {}").format(process),
|
||||
reverse("process:open", args=(record.id,)),
|
||||
)
|
||||
html += self.render_edit_btn(
|
||||
_("Edit {}").format(process),
|
||||
reverse("process:edit", args=(record.id,)),
|
||||
)
|
||||
html += self.render_delete_btn(
|
||||
_("Delete {}").format(process),
|
||||
reverse("process:remove", args=(record.id,)),
|
||||
)
|
||||
return format_html(html)
|
||||
@@ -0,0 +1,95 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n static custom_tags %}
|
||||
|
||||
{% block body %}
|
||||
<div class="rows">
|
||||
<div class="columns">
|
||||
<div class="large-10 column">
|
||||
<h3>{% trans 'Process' %}: {{process.intervention.identifier}}</h3>
|
||||
<h4>{{process.intervention.title}}</h4>
|
||||
</div>
|
||||
<div class="large-2 column">
|
||||
<a href="{% url 'process:edit' process.id %}">
|
||||
<button class="button small" role="button" value="{% trans 'Edit' %}"><em class='fas fa-edit'></em> {% trans 'Edit' %}</button>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rows">
|
||||
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row">{% trans 'Licencing Authority' %}</th>
|
||||
<td>{{ process.licensing_authority }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans 'Licencing document identifier' %}</th>
|
||||
<td>{{ process.licensing_authority_document_identifier }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans 'Registration office' %}</th>
|
||||
<td>{{ process.registration_office }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans 'Registration document identifier' %}</th>
|
||||
<td>{{ process.registration_office_document_identifier }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans 'Status' %}</th>
|
||||
<td>
|
||||
<span class="small info" title="{% trans 'Status' %}">
|
||||
<em class="fas fa-exclamation-triangle"></em>
|
||||
{{process.state|resolve_process_state}}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans 'Intervention' %}</th>
|
||||
<td>
|
||||
<a href="{% url 'intervention:open' process.intervention.id %}" target="_blank">
|
||||
<button class="button small" role="button" title="{{process.intervention.title}} - {{process.intervention.type}}">{{process.intervention.identifier}}</button>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row" style="vertical-align: baseline">
|
||||
{% trans 'Compensations' %}
|
||||
<br>
|
||||
<a href="{% url 'process:add-compensation' process.id %}" target="_blank">
|
||||
<button class="button small" role="button" title="{% trans 'Add a new compensation' %}">
|
||||
<em class="fas fa-plus-circle"></em>
|
||||
{% trans 'New' %}
|
||||
</button>
|
||||
</a>
|
||||
</th>
|
||||
<td>
|
||||
<table>
|
||||
<tbody>
|
||||
{% for compensation in process.compensations.all %}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{% url 'compensation:open' compensation.id %}" target="_blank">
|
||||
<button class="button small" role="button" title="{{compensation.title}} - {{compensation.type}}">{{compensation.identifier}}</button>
|
||||
</a>
|
||||
</td>
|
||||
<td class="action-col">
|
||||
<a href="{% url 'compensation:edit' compensation.id %}" title="{% trans 'Edit' %}"><button class="button small" title="{% trans 'Edit' %}"><em class='fas fa-edit'></em></button></a>
|
||||
<a href="{% url 'compensation:remove' compensation.id %}" title="{% trans 'Remove' %}"><button class="button small" title="{% trans 'Remove' %}"><em class='fas fa-trash-alt'></em></button></a>
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td>
|
||||
{% trans 'No compensation' %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
@@ -0,0 +1,22 @@
|
||||
"""
|
||||
Author: Michel Peltriaux
|
||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||
Created on: 25.11.20
|
||||
|
||||
"""
|
||||
from django.urls import path
|
||||
|
||||
from process.views import index_view, new_view, open_view, edit_view, remove_view, edit_status_view, \
|
||||
add_compensation_view
|
||||
|
||||
app_name = "process"
|
||||
urlpatterns = [
|
||||
path('', index_view, name='index'),
|
||||
path('new/', new_view, name='new'),
|
||||
path('open/<id>', open_view, name='open'),
|
||||
path('edit/<id>', edit_view, name='edit'),
|
||||
path('edit/<id>/status', edit_status_view, name='edit-status'),
|
||||
path('remove/<id>', remove_view, name='remove'),
|
||||
path('ac/<id>', add_compensation_view, name='add-compensation'),
|
||||
]
|
||||
@@ -0,0 +1,217 @@
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.http import HttpRequest
|
||||
from django.shortcuts import render, redirect, get_object_or_404
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from compensation.models import Compensation
|
||||
from konova.contexts import BaseContext
|
||||
from konova.decorators import resolve_user_role, valid_process_role_required
|
||||
from konova.forms import RemoveForm
|
||||
from konova.utils.tables import ChoicesColumnForm
|
||||
from process.enums import ProcessStateEnum
|
||||
from process.forms import NewProcessForm, EditProcessForm
|
||||
from process.models import Process
|
||||
from process.settings import PROCESS_STATE_STRINGS
|
||||
from process.tables import ProcessTable
|
||||
|
||||
# URL definitions
|
||||
PROCESS_INDEX_URL = "process:index"
|
||||
|
||||
|
||||
@login_required
|
||||
@resolve_user_role
|
||||
def index_view(request: HttpRequest):
|
||||
"""
|
||||
Renders the index view for process
|
||||
|
||||
Args:
|
||||
request (HttpRequest): The incoming request
|
||||
|
||||
Returns:
|
||||
A rendered view
|
||||
"""
|
||||
template = "generic_index.html"
|
||||
user = request.user
|
||||
processes = Process.get_role_objects(user)
|
||||
table = ProcessTable(
|
||||
request=request,
|
||||
queryset=processes
|
||||
)
|
||||
context = {
|
||||
"table": table,
|
||||
}
|
||||
context = BaseContext(request, context).context
|
||||
return render(request, template, context)
|
||||
|
||||
|
||||
@login_required
|
||||
def new_view(request: HttpRequest):
|
||||
"""
|
||||
Renders a view for a new process creation
|
||||
|
||||
Args:
|
||||
request (HttpRequest): The incoming request
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
template = "konova/form.html"
|
||||
form = NewProcessForm(request.POST or None)
|
||||
if request.method == "POST":
|
||||
if form.is_valid():
|
||||
process = form.save(request.user)
|
||||
intervention = process.intervention
|
||||
messages.info(request, _("A process is based on an intervention. Please fill in the missing data for this intervention"))
|
||||
return redirect("intervention:edit", id=intervention.id)
|
||||
else:
|
||||
messages.error(request, _("Invalid input"))
|
||||
else:
|
||||
# For clarification: Nothing to do here
|
||||
pass
|
||||
context = {
|
||||
"form": form,
|
||||
}
|
||||
context = BaseContext(request, context).context
|
||||
return render(request, template, context)
|
||||
|
||||
|
||||
@login_required
|
||||
def open_view(request: HttpRequest, id: str):
|
||||
""" Renders a detail view for this process
|
||||
|
||||
Args:
|
||||
request (HttpRequest): The incoming request
|
||||
id (str): The uuid id as string
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
template = "process/open.html"
|
||||
process = get_object_or_404(Process, id=id)
|
||||
context = {
|
||||
"process": process,
|
||||
}
|
||||
context = BaseContext(request, context).context
|
||||
return render(request, template, context)
|
||||
|
||||
|
||||
@login_required
|
||||
@resolve_user_role
|
||||
def edit_view(request: HttpRequest, id: str):
|
||||
""" Renders an edit view for this process
|
||||
|
||||
Args:
|
||||
request (HttpRequest): The incoming request
|
||||
id (str): The uuid id as string
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
template = "konova/form.html"
|
||||
process = get_object_or_404(Process, id=id)
|
||||
if request.method == "POST":
|
||||
form = EditProcessForm(request.POST or None, instance=process, user=request.user)
|
||||
if form.is_valid():
|
||||
process = form.save(request.user)
|
||||
messages.success(request, _("{} edited").format(process))
|
||||
return redirect("process:index")
|
||||
else:
|
||||
messages.error(request, _("Invalid input"))
|
||||
form = EditProcessForm(instance=process, user=request.user)
|
||||
context = {
|
||||
"form": form,
|
||||
}
|
||||
context = BaseContext(request, context).context
|
||||
return render(request, template, context)
|
||||
|
||||
|
||||
@login_required
|
||||
def remove_view(request: HttpRequest, id: str):
|
||||
""" Renders a remove view for this process
|
||||
|
||||
Args:
|
||||
request (HttpRequest): The incoming request
|
||||
id (str): The uuid id as string
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
template = "konova/form.html"
|
||||
process = get_object_or_404(Process, id=id)
|
||||
if request.method == "POST":
|
||||
form = RemoveForm(
|
||||
request.POST or None,
|
||||
object_to_remove=process,
|
||||
remove_post_url=reverse("process:remove", args=(id,)),
|
||||
cancel_url=reverse("process:index"),
|
||||
)
|
||||
if form.is_valid():
|
||||
confirmed = form.is_checked()
|
||||
if confirmed:
|
||||
process.deactivate()
|
||||
messages.success(request, _("Process {} removed").format(process))
|
||||
return redirect("process:index")
|
||||
else:
|
||||
messages.error(request, _("Invalid input"))
|
||||
|
||||
form = RemoveForm(
|
||||
object_to_remove=process,
|
||||
remove_post_url=reverse("process:remove", args=(id,)),
|
||||
cancel_url=reverse("process:index"),
|
||||
)
|
||||
context = {
|
||||
"form": form,
|
||||
}
|
||||
context = BaseContext(request, context).context
|
||||
return render(request, template, context)
|
||||
|
||||
|
||||
@login_required
|
||||
def edit_status_view(request: HttpRequest, id: str):
|
||||
""" Changes only the status of a process
|
||||
|
||||
Args:
|
||||
request (The incoming request):
|
||||
id ():
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
process = get_object_or_404(Process, id=id)
|
||||
old_state = process.state
|
||||
form = ChoicesColumnForm(request.POST or None, choices=ProcessStateEnum.as_choices(drop_empty_choice=True))
|
||||
if request.method == "POST":
|
||||
if form.is_valid():
|
||||
process.state = int(form.cleaned_data.get("select", -1))
|
||||
process.save()
|
||||
_from = PROCESS_STATE_STRINGS.get(old_state)
|
||||
_to = PROCESS_STATE_STRINGS.get(process.state)
|
||||
messages.info(request, _("{} status changed from {} to {}").format(process.intervention.title, _from, _to))
|
||||
else:
|
||||
messages.error(request, form.errors)
|
||||
return redirect("process:index")
|
||||
|
||||
|
||||
@login_required
|
||||
def add_compensation_view(request: HttpRequest, id: str):
|
||||
""" Adds a new compensation to a process
|
||||
|
||||
Args:
|
||||
request (HttpRequest): The incoming request
|
||||
id (str): The process' id
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
process = get_object_or_404(Process, id=id)
|
||||
comp = Compensation()
|
||||
comp.process = process
|
||||
comp.save()
|
||||
messages.info(request, _("Please fill in the data for this compensation"))
|
||||
return redirect("compensation:edit", id=comp.id)
|
||||
#template = ""
|
||||
#context = {}
|
||||
#context = BaseContext(request, context).context
|
||||
#return render(request, template, context)
|
||||
@@ -0,0 +1,20 @@
|
||||
asgiref==3.3.1
|
||||
certifi==2020.11.8
|
||||
chardet==3.0.4
|
||||
Django==3.1.3
|
||||
django-autocomplete-light==3.8.1
|
||||
django-debug-toolbar==3.1.1
|
||||
django-filter==2.4.0
|
||||
django-fontawesome-5==1.0.18
|
||||
django-simple-sso==0.14.1
|
||||
django-tables2==2.3.3
|
||||
idna==2.10
|
||||
itsdangerous==1.1.0
|
||||
pkg-resources==0.0.0
|
||||
psycopg2==2.8.6
|
||||
pytz==2020.4
|
||||
requests==2.25.0
|
||||
six==1.15.0
|
||||
sqlparse==0.4.1
|
||||
urllib3==1.26.2
|
||||
webservices==0.7
|
||||
@@ -0,0 +1,22 @@
|
||||
{% load i18n %}
|
||||
<div class="row">
|
||||
<div class="small-12 columns menu-container">
|
||||
<div class="mobile-menu">
|
||||
<ul>
|
||||
<li class="menu-trigger">
|
||||
<button>{% trans 'Menu' %}</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<ul class="dkd_mm_section_list" data-level="1">
|
||||
<li class="dkd_mm_entry">
|
||||
<span class="dkd_mm_link" role=button>
|
||||
<a href="{% url 'home' %}" target="_self" title="{% trans 'Home' %}">
|
||||
{% trans 'Home' %}
|
||||
</a>
|
||||
</span>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,128 @@
|
||||
{% load i18n %}
|
||||
|
||||
<div class="row">
|
||||
<div class="small-12 columns menu-container">
|
||||
<div class="mobile-menu">
|
||||
<ul>
|
||||
<li class="menu-trigger">
|
||||
<button>{% trans 'Menu' %}</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<ul class="dkd_mm_section_list" data-level="1">
|
||||
<li class="dkd_mm_entry">
|
||||
<span class="dkd_mm_link" role=button><a href="{% url 'home' %}" target="_self">{% trans 'Home' %}</a></span>
|
||||
</li>
|
||||
<li class="dkd_mm_entry dkd_mm_sub_link">
|
||||
<span class="dkd_mm_link" role=button><a href="{% url 'process:index' %}" target="_self">{% trans 'Process' %}</a></span>
|
||||
<ul class="dkd_mm_section_list" data-level="2">
|
||||
<li class="dkd_mm_section_title">
|
||||
<span class="dkd_mm_section_title_link">{% trans 'Process management' %}</span>
|
||||
</li>
|
||||
<li class="dkd_mm_entry">
|
||||
<span class="dkd_mm_link" role=button><a href="{% url 'process:index' %}" target="_self">{% trans 'Show process' %}</a></span>
|
||||
</li>
|
||||
<li class="dkd_mm_entry">
|
||||
<span class="dkd_mm_link" role=button><a href="{% url 'process:new' %}" target="_self">{% trans 'New process' %}</a></span>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="dkd_mm_entry dkd_mm_sub_link">
|
||||
<span class="dkd_mm_link" role=button><a href="{% url 'intervention:index' %}" target="_self">{% trans 'Intervention' %}</a></span>
|
||||
<ul class="dkd_mm_section_list" data-level="2">
|
||||
<li class="dkd_mm_section_title">
|
||||
<span class="dkd_mm_section_title_link">{% trans 'Intervention management' %}</span>
|
||||
</li>
|
||||
<li class="dkd_mm_entry">
|
||||
<span class="dkd_mm_link" role=button><a href="{% url 'intervention:index' %}" target="_self">{% trans 'Show intervention' %}</a></span>
|
||||
</li>
|
||||
<li class="dkd_mm_entry">
|
||||
<span class="dkd_mm_link" role=button><a href="{% url 'intervention:new' %}" target="_self">{% trans 'New intervention' %}</a></span>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="dkd_mm_entry dkd_mm_sub_link">
|
||||
<span class="dkd_mm_link" role=button><a href="{% url 'compensation:index' %}" target="_self">{% trans 'Compensation' %}</a></span>
|
||||
<ul class="dkd_mm_section_list" data-level="2">
|
||||
<li class="dkd_mm_section_title">
|
||||
<span class="dkd_mm_section_title_link">{% trans 'Compensation management' %}</span>
|
||||
</li>
|
||||
<li class="dkd_mm_entry">
|
||||
<span class="dkd_mm_link" role=button><a href="{% url 'compensation:index' %}" target="_self">{% trans 'Show compensation' %}</a></span>
|
||||
</li>
|
||||
<li class="dkd_mm_entry">
|
||||
<span class="dkd_mm_link" role=button><a href="{% url 'compensation:new' %}" target="_self">{% trans 'New compensation' %}</a></span>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="dkd_mm_entry dkd_mm_sub_link">
|
||||
<span class="dkd_mm_link" role=button><a href="{% url 'compensation:account-index' %}" target="_self">{% trans 'Eco-account' %}</a></span>
|
||||
<ul class="dkd_mm_section_list" data-level="2">
|
||||
<li class="dkd_mm_section_title">
|
||||
<span class="dkd_mm_section_title_link">{% trans 'Eco-account management' %}</span>
|
||||
</li>
|
||||
<li class="dkd_mm_entry">
|
||||
<span class="dkd_mm_link" role=button><a href="{% url 'compensation:account-index' %}" target="_self">{% trans 'Show eco-accounts' %}</a></span>
|
||||
</li>
|
||||
<li class="dkd_mm_entry">
|
||||
<span class="dkd_mm_link" role=button><a href="{% url 'compensation:account-new' %}" target="_self">{% trans 'New eco-account' %}</a></span>
|
||||
</li>
|
||||
<li class="dkd_mm_entry">
|
||||
<span class="dkd_mm_link" role=button><a href="{% url 'compensation:account-index' %}" target="_self">{% trans 'Withdraw from eco-account' %}</a></span>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="dkd_mm_entry dkd_mm_sub_link">
|
||||
<span class="dkd_mm_link" role=button><a href="{% url 'compensation:index' %}" target="_self">EMA Alte Rechtslage</a></span>
|
||||
<ul class="dkd_mm_section_list" data-level="2">
|
||||
<li class="dkd_mm_section_title">
|
||||
<span class="dkd_mm_section_title_link">Maßnahmen aus Ersatzzahlungen - alte Rechtslage (EMA)</span>
|
||||
</li>
|
||||
<li class="dkd_mm_entry">
|
||||
<span class="dkd_mm_link" role=button><a href="{% url 'compensation:index' %}" target="_self">{% trans 'Show actions' %}</a></span>
|
||||
</li>
|
||||
<li class="dkd_mm_entry">
|
||||
<span class="dkd_mm_link" role=button><a href="{% url 'compensation:new' %}" target="_self">{% trans 'New action' %}</a></span>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="dkd_mm_entry dkd_mm_sub_link">
|
||||
<span class="dkd_mm_link" role=button><a href="{% url 'compensation:index' %}" target="_self">{% trans 'Organization' %}</a></span>
|
||||
<ul class="dkd_mm_section_list" data-level="2">
|
||||
<li class="dkd_mm_section_title">
|
||||
<span class="dkd_mm_section_title_link">{% trans 'Organization' %}</span>
|
||||
</li>
|
||||
<li class="dkd_mm_entry">
|
||||
<span class="dkd_mm_link" role=button><a href="" target="_self">{% trans 'Import / Export' %}</a></span>
|
||||
</li>
|
||||
<li class="dkd_mm_entry">
|
||||
<span class="dkd_mm_link" role=button><a href="" target="_self">{% trans 'Annual report' %}</a></span>
|
||||
</li>
|
||||
<li class="dkd_mm_entry">
|
||||
<span class="dkd_mm_link" role=button><a href="" target="_self">{% trans 'Settings' %}</a></span>
|
||||
</li>
|
||||
<li class="dkd_mm_entry">
|
||||
<span class="dkd_mm_link" role=button><a href="{{ wiki_url }}" target="_blank">{% trans 'Help' %}</a></span>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="dkd_mm_entry dkd_mm_sub_link">
|
||||
<span class="dkd_mm_link" role=button><a href="{% url 'compensation:index' %}" target="_self">{% trans 'User' %}: {{ user }}</a></span>
|
||||
<ul class="dkd_mm_section_list" data-level="2">
|
||||
<li class="dkd_mm_section_title">
|
||||
<span class="dkd_mm_section_title_link">{% trans 'User' %}: {{ user }}</span>
|
||||
</li>
|
||||
<li class="dkd_mm_entry">
|
||||
<span class="dkd_mm_link" role=button><a href="{% url 'logout' %}" target="_self">{% trans 'Logout' %}</a></span>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="user-role">
|
||||
{% trans 'You are currently working as ' %}
|
||||
<strong>{{ current_role.type }} ({{ current_role.org }})</strong>
|
||||
<a href="{% url 'home' %}">{% trans 'Change...' %}</a>
|
||||
</div>
|
||||
@@ -0,0 +1,100 @@
|
||||
<!DOCTYPE html>
|
||||
{% load static i18n fontawesome_5 %}
|
||||
<html lang="{{ language }}">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>{{ base_title }}</title>
|
||||
<link rel="stylesheet" href="{% static 'css/messages.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'css/mulewf.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'css/konova.css' %}">
|
||||
<script type="text/javascript" src="{% static 'js/jquery-3.5.1.min.js' %}"></script>
|
||||
<script type="text/javascript" src="{% static 'js/jquery-ui.min.js' %}"></script>
|
||||
<script type="text/javascript" src="{% static 'js/mulewf.min.js' %}"></script>
|
||||
{% fontawesome_5_static %}
|
||||
{% block head %}
|
||||
|
||||
{% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
{% if render_header %}
|
||||
<header class="header">
|
||||
<div class="background-wrap gray-600">
|
||||
<div class="row logo-search-area">
|
||||
<div class="small-6 medium-4 columns">
|
||||
<h3>{{ base_frontend_title }}</h3>
|
||||
</div>
|
||||
<div class="medium-4 columns hide-for-small-only"></div>
|
||||
<div class="small-6 medium-4 columns last">
|
||||
<a href="{% url 'home' %}">
|
||||
<img class="logo" alt="{% trans 'Home' %}"
|
||||
title="{% trans 'Home' %}"
|
||||
src="{% static 'images/rlp-logos-MUEEF.png' %}"/>
|
||||
<noscript>
|
||||
<img title="rlp" alt="rlp-logo" src="{% static 'images/rlp-logos-MUEEF.png' %}" width="192" height="84" />
|
||||
</noscript>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
{% endif %}
|
||||
<div class="body-content">
|
||||
|
||||
<nav class="main-menu static" role="navigation">
|
||||
{% block navbar %}
|
||||
{% if user.is_authenticated %}
|
||||
{% include 'authenticated-user-navbar.html' %}
|
||||
{% else %}
|
||||
{% include 'anonymous-user-navbar.html' %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
</nav>
|
||||
<div class="row">
|
||||
<div class="large-12 columns">
|
||||
{% for message in messages %}
|
||||
<div class="{{ message.tags }}">
|
||||
{{ message }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<div class="small-12 columns">
|
||||
<div class="column" style="margin-top: 50px;">
|
||||
{% block body %}
|
||||
<div class="small-12 medium-3 columns">
|
||||
{% block body_left %}
|
||||
|
||||
{% endblock %}
|
||||
</div>
|
||||
<div class="small-12 medium-6 columns">
|
||||
{% block body_middle %}
|
||||
{% endblock %}
|
||||
</div>
|
||||
<div class="small-12 medium-3 columns">
|
||||
{% block body_right %}
|
||||
|
||||
{% endblock %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<footer class="footer">
|
||||
{% block footer %}
|
||||
<div class="background-wrap gray-500 line"></div>
|
||||
<div class="row">
|
||||
<div class="large-6 medium-6 columns">
|
||||
<h2 class="h6">{% trans 'About this site' %}</h2>
|
||||
<ul class="no-bullet">
|
||||
<li><a href="" target="_self">Impressum</a></li>
|
||||
<li><a href="" target="_self">{% trans 'Privacy policy' %}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
</footer>
|
||||
</body>
|
||||
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,7 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block body %}
|
||||
<div class="style-2">
|
||||
{% include 'table.html' %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,54 @@
|
||||
{% load i18n %}
|
||||
|
||||
{% block head %}
|
||||
{{ form.media }}
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="table-container">
|
||||
<h4>
|
||||
{{ form.form_title }}
|
||||
</h4>
|
||||
{% if form.form_caption is not None %}
|
||||
<div>
|
||||
{{ form.form_caption }}
|
||||
</div>
|
||||
{% endif %}
|
||||
<form method="post" action="{{ form.action_url }}">
|
||||
{% csrf_token %}
|
||||
<table>
|
||||
<tbody>
|
||||
{% for field in form %}
|
||||
<tr title="{{ field.help_text }}" class="{% if field.errors %}error{% endif %}">
|
||||
<th scope="row" class="small-3">
|
||||
<div>{{ field.label }}<span class="label-required">{% if field.field.required %}*{% endif %}</span></div>
|
||||
<small>{{ field.help_text }}</small>
|
||||
</th>
|
||||
<td class="small-12">
|
||||
{{ field }}
|
||||
{% for error in field.errors %}
|
||||
<b>{{ error }}</b>
|
||||
{% endfor %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="label-required">{% trans 'Fields with * are required.' %}</div>
|
||||
<div class="row">
|
||||
<div class="large-1 columns">
|
||||
<a href="{{ form.cancel_redirect }}">
|
||||
<button class="button small append-value" type="button" title="{% trans 'Cancel' %}">{% trans 'Cancel' %}</button>
|
||||
</a>
|
||||
</div>
|
||||
<div class="large-10 columns">
|
||||
<button class="button small append-value right" type="submit" title="{% trans 'Save' %}">{% trans 'Save' %}</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block footer %}
|
||||
{{ form.media }}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,44 @@
|
||||
{% load django_tables2 %}
|
||||
{% load i18n static %}
|
||||
<div class="rows">
|
||||
{% if table.title is not None %}
|
||||
<div class="rows">
|
||||
<h3>
|
||||
{{ table.title }}
|
||||
</h3>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="column large-2">
|
||||
{% if table.user.is_superuser and table.add_new_entries %}
|
||||
<a href="{{ table.add_new_url }}">
|
||||
<button class="button small" title="{% trans 'New entry' %}">
|
||||
<i class="fas fa-plus"></i>
|
||||
{% trans 'New' %}
|
||||
</button>
|
||||
</a>
|
||||
{% else %}
|
||||
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="column large-8">
|
||||
|
||||
</div>
|
||||
<div class="column large-2 dropdown-area">
|
||||
<form method="get">
|
||||
{{ table.filter.form.as_p }}
|
||||
</form>
|
||||
<div class="header-meta-dropdown">
|
||||
<button data-dropdown="rpp-choice" aria-controls="rpp-choice" aria-expanded="false" class="custom-dropdown left">{% trans 'Results per page' %}</button>
|
||||
<ul id="rpp-choice" class="custom-dropdown-content" data-dropdown-content aria-hidden="true" style="position: absolute; left: -99999px; top: 25px; right: auto;">
|
||||
{% for rpp_option in table.results_per_page_choices %}
|
||||
<li class="{% if table.results_per_page_chosen == rpp_option %}selected{% endif %}">
|
||||
<a class="" href="{% querystring table.results_per_page_parameter=rpp_option %}">{{ rpp_option }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
{% render_table table %}
|
||||
</div>
|
||||
Reference in new issue
Block a user