Codelist integration
* adds codelist app * adds KonovaCodeList and KonovaCode model for fetching and storing OSIRIS Codelisten entries * adds update_codelist command for updating and fetching codes * adds autocomplete route for using codelists in forms
This commit is contained in:
parent
c94d73c037
commit
49f7f3db53
0
codelist/__init__.py
Normal file
0
codelist/__init__.py
Normal file
36
codelist/admin.py
Normal file
36
codelist/admin.py
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
from codelist.models import KonovaCode, KonovaCodeList
|
||||||
|
|
||||||
|
|
||||||
|
class KonovaCodeListAdmin(admin.ModelAdmin):
|
||||||
|
list_display = [
|
||||||
|
"id",
|
||||||
|
]
|
||||||
|
readonly_fields = [
|
||||||
|
"id",
|
||||||
|
"codes",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class KonovaCodeAdmin(admin.ModelAdmin):
|
||||||
|
list_display = [
|
||||||
|
"id",
|
||||||
|
"parent",
|
||||||
|
"short_name",
|
||||||
|
"long_name",
|
||||||
|
"is_leaf",
|
||||||
|
"is_active",
|
||||||
|
]
|
||||||
|
|
||||||
|
readonly_fields = [
|
||||||
|
"id",
|
||||||
|
"short_name",
|
||||||
|
"long_name",
|
||||||
|
"is_leaf",
|
||||||
|
"parent",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
#admin.site.register(KonovaCodeList, KonovaCodeListAdmin)
|
||||||
|
admin.site.register(KonovaCode, KonovaCodeAdmin)
|
5
codelist/apps.py
Normal file
5
codelist/apps.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class CodelistConfig(AppConfig):
|
||||||
|
name = 'codelist'
|
157
codelist/management/commands/update_codelist.py
Normal file
157
codelist/management/commands/update_codelist.py
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
"""
|
||||||
|
Author: Michel Peltriaux
|
||||||
|
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||||
|
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||||
|
Created on: 23.08.21
|
||||||
|
|
||||||
|
"""
|
||||||
|
import requests
|
||||||
|
from django.core.management import BaseCommand
|
||||||
|
from xml.etree import ElementTree as etree
|
||||||
|
|
||||||
|
from codelist.models import KonovaCode, KonovaCodeList
|
||||||
|
from codelist.settings import CODELIST_INTERVENTION_HANDLER_ID, CODELIST_CONSERVATION_OFFICE_ID, \
|
||||||
|
CODELIST_REGISTRATION_OFFICE_ID, CODELIST_BIOTOPES_ID, CODELIST_LAW_ID, CODELIST_COMPENSATION_HANDLER_ID, \
|
||||||
|
CODELIST_COMPENSATION_ACTION_ID, CODELIST_COMPENSATION_ACTION_CLASS_ID, CODELIST_COMPENSATION_ADDITIONAL_TYPE_ID, \
|
||||||
|
CODELIST_COMPENSATION_COMBINATION_ID, CODELIST_BASE_URL
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = "Performs test on collisions using the identifier generation"
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
try:
|
||||||
|
codelist_ids = [
|
||||||
|
CODELIST_INTERVENTION_HANDLER_ID,
|
||||||
|
CODELIST_CONSERVATION_OFFICE_ID,
|
||||||
|
CODELIST_REGISTRATION_OFFICE_ID,
|
||||||
|
CODELIST_BIOTOPES_ID,
|
||||||
|
CODELIST_LAW_ID,
|
||||||
|
CODELIST_COMPENSATION_HANDLER_ID,
|
||||||
|
CODELIST_COMPENSATION_ACTION_ID,
|
||||||
|
CODELIST_COMPENSATION_ACTION_CLASS_ID,
|
||||||
|
CODELIST_COMPENSATION_ADDITIONAL_TYPE_ID,
|
||||||
|
CODELIST_COMPENSATION_COMBINATION_ID,
|
||||||
|
]
|
||||||
|
self._write_warning("Fetching codes...")
|
||||||
|
|
||||||
|
for list_id in codelist_ids:
|
||||||
|
_url = CODELIST_BASE_URL + "/" + str(list_id)
|
||||||
|
result = requests.get(url=_url)
|
||||||
|
|
||||||
|
if result.status_code != 200:
|
||||||
|
self._write_error("Error on codelist url '{}'".format(_url))
|
||||||
|
continue
|
||||||
|
self._write_warning("Fetch codes for list id {}".format(list_id))
|
||||||
|
content = result.content.decode("utf-8")
|
||||||
|
root = etree.fromstring(content)
|
||||||
|
items = root.findall("item")
|
||||||
|
self._write_warning("Found {} codes. Process now...".format(len(items)))
|
||||||
|
|
||||||
|
code_list = KonovaCodeList.objects.get_or_create(
|
||||||
|
id=list_id,
|
||||||
|
)[0]
|
||||||
|
self._rec_xml_code_fetch(
|
||||||
|
items=items,
|
||||||
|
code_list=code_list,
|
||||||
|
parent=None,
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
for element in items:
|
||||||
|
atom_id = element.find("atomid").text
|
||||||
|
parent = element.find("vaterid").text
|
||||||
|
short_name = element.find("shortname").text
|
||||||
|
long_name = element.find("longname").text
|
||||||
|
is_archived = bool(element.find("archive").text)
|
||||||
|
|
||||||
|
# If a parent has been set, we need to fetch/create this entry. Otherwise ("0") we ignore it.
|
||||||
|
if parent == "0":
|
||||||
|
parent = None
|
||||||
|
else:
|
||||||
|
parent = KonovaCode.objects.get_or_create(
|
||||||
|
id=parent,
|
||||||
|
)[0]
|
||||||
|
|
||||||
|
code = KonovaCode.objects.get_or_create(
|
||||||
|
id=atom_id,
|
||||||
|
)
|
||||||
|
created = code[1]
|
||||||
|
if created:
|
||||||
|
num_created += 1
|
||||||
|
else:
|
||||||
|
num_updated += 1
|
||||||
|
code = code[0]
|
||||||
|
code.short_name = short_name
|
||||||
|
code.long_name = long_name
|
||||||
|
code.parent = parent
|
||||||
|
code.is_active = is_archived
|
||||||
|
|
||||||
|
code.save()
|
||||||
|
if code not in code_list.codes.all():
|
||||||
|
code_list.codes.add(code)
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
self._break_line()
|
||||||
|
exit(-1)
|
||||||
|
|
||||||
|
def _rec_xml_code_fetch(self, items: list, code_list: KonovaCodeList, parent: KonovaCode):
|
||||||
|
if items is None:
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
for element in items:
|
||||||
|
children = element.find("items")
|
||||||
|
atom_id = element.find("atomid").text
|
||||||
|
short_name = element.find("shortname").text
|
||||||
|
long_name = element.find("longname").text
|
||||||
|
is_archived = bool(element.find("archive").text)
|
||||||
|
|
||||||
|
code = KonovaCode.objects.get_or_create(
|
||||||
|
id=atom_id,
|
||||||
|
)
|
||||||
|
code = code[0]
|
||||||
|
code.short_name = short_name
|
||||||
|
code.long_name = long_name
|
||||||
|
code.parent = parent
|
||||||
|
code.is_active = is_archived
|
||||||
|
code.is_leaf = children is None
|
||||||
|
|
||||||
|
code.save()
|
||||||
|
if code not in code_list.codes.all():
|
||||||
|
code_list.codes.add(code)
|
||||||
|
|
||||||
|
self._rec_xml_code_fetch(
|
||||||
|
items=children,
|
||||||
|
code_list=code_list,
|
||||||
|
parent=code
|
||||||
|
)
|
||||||
|
|
||||||
|
def _break_line(self):
|
||||||
|
""" Simply prints a line break
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
self.stdout.write("\n")
|
||||||
|
|
||||||
|
def _write_warning(self, txt: str):
|
||||||
|
self.stdout.write(
|
||||||
|
self.style.WARNING(
|
||||||
|
txt
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def _write_success(self, txt: str):
|
||||||
|
self.stdout.write(
|
||||||
|
self.style.SUCCESS(
|
||||||
|
txt
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def _write_error(self, txt: str):
|
||||||
|
self.stdout.write(
|
||||||
|
self.style.ERROR(
|
||||||
|
txt
|
||||||
|
)
|
||||||
|
)
|
65
codelist/models.py
Normal file
65
codelist/models.py
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
from django.db import models
|
||||||
|
|
||||||
|
|
||||||
|
class KonovaCode(models.Model):
|
||||||
|
"""
|
||||||
|
Representation of the OSIRIS Codelisten codes.
|
||||||
|
|
||||||
|
KonovaCodes will be updated using a command regularly to provide all codes, which are provided by the
|
||||||
|
Codelisten application itself. To reduce traffic, we store them in our own database.
|
||||||
|
"""
|
||||||
|
id = models.IntegerField(
|
||||||
|
primary_key=True,
|
||||||
|
help_text="AtomId; Identifies this code uniquely over all NatIT projects"
|
||||||
|
)
|
||||||
|
parent = models.ForeignKey(
|
||||||
|
"KonovaCode",
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
)
|
||||||
|
short_name = models.CharField(
|
||||||
|
max_length=500,
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
help_text="Short version of long name",
|
||||||
|
)
|
||||||
|
long_name = models.CharField(
|
||||||
|
max_length=1000,
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
help_text="",
|
||||||
|
)
|
||||||
|
is_leaf = models.BooleanField(
|
||||||
|
default=False,
|
||||||
|
help_text="Whether this code has children or not"
|
||||||
|
)
|
||||||
|
is_active = models.BooleanField(
|
||||||
|
default=False,
|
||||||
|
help_text="Whether this code is archived or not"
|
||||||
|
)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
if self.is_leaf and self.parent:
|
||||||
|
return "{} | {}".format(self.parent.long_name, self.long_name)
|
||||||
|
return self.long_name
|
||||||
|
|
||||||
|
|
||||||
|
class KonovaCodeList(models.Model):
|
||||||
|
"""
|
||||||
|
The codelist itself
|
||||||
|
"""
|
||||||
|
id = models.IntegerField(
|
||||||
|
primary_key=True,
|
||||||
|
help_text="List identifier",
|
||||||
|
)
|
||||||
|
codes = models.ManyToManyField(
|
||||||
|
KonovaCode,
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
help_text="Codes for this list",
|
||||||
|
related_name="code_lists"
|
||||||
|
)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return str(self.id)
|
23
codelist/settings.py
Normal file
23
codelist/settings.py
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
"""
|
||||||
|
Author: Michel Peltriaux
|
||||||
|
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||||
|
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||||
|
Created on: 23.08.21
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Codelist definitions
|
||||||
|
CODELIST_BASE_URL = "https://codelisten.naturschutz.rlp.de/repository/referenzliste"
|
||||||
|
|
||||||
|
# Identifier
|
||||||
|
CODELIST_INTERVENTION_HANDLER_ID = 903 # CLMassnahmeträger
|
||||||
|
CODELIST_CONSERVATION_OFFICE_ID = 907 # CLNaturschutzbehörden
|
||||||
|
CODELIST_REGISTRATION_OFFICE_ID = 1053 # CLZulassungsbehörden
|
||||||
|
CODELIST_BIOTOPES_ID = 974 # CL_EIV_Biotoptypen
|
||||||
|
CODELIST_LAW_ID = 1048 # CLVerfahrensrecht
|
||||||
|
|
||||||
|
CODELIST_COMPENSATION_HANDLER_ID = 1052 # CLEingreifer
|
||||||
|
CODELIST_COMPENSATION_ACTION_ID = 1026 # CLMassnahmedetail
|
||||||
|
CODELIST_COMPENSATION_ACTION_CLASS_ID = 1034 # CLMassnahmeklasse
|
||||||
|
CODELIST_COMPENSATION_ADDITIONAL_TYPE_ID = 1028 # CLMassnahmetyp
|
||||||
|
CODELIST_COMPENSATION_COMBINATION_ID = 1049 # CLKombimassnahme
|
3
codelist/tests.py
Normal file
3
codelist/tests.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
14
codelist/urls.py
Normal file
14
codelist/urls.py
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
"""
|
||||||
|
Author: Michel Peltriaux
|
||||||
|
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||||
|
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||||
|
Created on: 23.08.21
|
||||||
|
|
||||||
|
"""
|
||||||
|
from django.urls import path
|
||||||
|
|
||||||
|
|
||||||
|
app_name = "codelist"
|
||||||
|
urlpatterns = [
|
||||||
|
|
||||||
|
]
|
0
codelist/views.py
Normal file
0
codelist/views.py
Normal file
@ -7,6 +7,7 @@ Created on: 07.12.20
|
|||||||
"""
|
"""
|
||||||
from dal_select2.views import Select2QuerySetView
|
from dal_select2.views import Select2QuerySetView
|
||||||
|
|
||||||
|
from codelist.models import KonovaCode
|
||||||
from compensation.models import EcoAccount
|
from compensation.models import EcoAccount
|
||||||
from intervention.models import Intervention
|
from intervention.models import Intervention
|
||||||
from organisation.models import Organisation
|
from organisation.models import Organisation
|
||||||
@ -74,3 +75,38 @@ class InterventionAutocomplete(Select2QuerySetView):
|
|||||||
"identifier"
|
"identifier"
|
||||||
)
|
)
|
||||||
return qs
|
return qs
|
||||||
|
|
||||||
|
|
||||||
|
class KonovaCodeAutocomplete(Select2QuerySetView):
|
||||||
|
"""
|
||||||
|
Provides simple autocomplete functionality for codes
|
||||||
|
|
||||||
|
Parameter support:
|
||||||
|
* q: Search for a word inside long_name of a code
|
||||||
|
* c: Search inside a special codelist
|
||||||
|
|
||||||
|
"""
|
||||||
|
def dispatch(self, request, *args, **kwargs):
|
||||||
|
# Retrieve 'c' for 'code' from request
|
||||||
|
self.c = request.GET.get("c", "")
|
||||||
|
return super().dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
if self.request.user.is_anonymous:
|
||||||
|
return KonovaCode.objects.none()
|
||||||
|
qs = KonovaCode.objects.filter(
|
||||||
|
is_active=True,
|
||||||
|
is_leaf=True,
|
||||||
|
)
|
||||||
|
if self.c:
|
||||||
|
qs = qs.filter(
|
||||||
|
code_lists__in=[self.c]
|
||||||
|
)
|
||||||
|
if self.q:
|
||||||
|
qs = qs.filter(
|
||||||
|
long_name__icontains=self.q
|
||||||
|
)
|
||||||
|
qs.order_by(
|
||||||
|
"long_name"
|
||||||
|
)
|
||||||
|
return qs
|
@ -69,6 +69,7 @@ INSTALLED_APPS = [
|
|||||||
'news',
|
'news',
|
||||||
'user',
|
'user',
|
||||||
'ema',
|
'ema',
|
||||||
|
'codelist',
|
||||||
]
|
]
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
INSTALLED_APPS += [
|
INSTALLED_APPS += [
|
||||||
|
@ -18,7 +18,7 @@ from django.contrib import admin
|
|||||||
from django.urls import path, include
|
from django.urls import path, include
|
||||||
|
|
||||||
from konova.autocompletes import OrganisationAutocomplete, NonOfficialOrganisationAutocomplete, EcoAccountAutocomplete, \
|
from konova.autocompletes import OrganisationAutocomplete, NonOfficialOrganisationAutocomplete, EcoAccountAutocomplete, \
|
||||||
InterventionAutocomplete
|
InterventionAutocomplete, KonovaCodeAutocomplete
|
||||||
from konova.settings import SSO_SERVER, SSO_PUBLIC_KEY, SSO_PRIVATE_KEY, DEBUG
|
from konova.settings import SSO_SERVER, SSO_PUBLIC_KEY, SSO_PRIVATE_KEY, DEBUG
|
||||||
from konova.sso.sso import KonovaSSOClient
|
from konova.sso.sso import KonovaSSOClient
|
||||||
from konova.views import logout_view, home_view, get_document_view, remove_document_view, remove_deadline_view
|
from konova.views import logout_view, home_view, get_document_view, remove_document_view, remove_deadline_view
|
||||||
@ -35,6 +35,7 @@ urlpatterns = [
|
|||||||
path('organisation/', include("organisation.urls")),
|
path('organisation/', include("organisation.urls")),
|
||||||
path('user/', include("user.urls")),
|
path('user/', include("user.urls")),
|
||||||
path('news/', include("news.urls")),
|
path('news/', include("news.urls")),
|
||||||
|
path('news/', include("codelist.urls")),
|
||||||
|
|
||||||
# Generic documents routes
|
# Generic documents routes
|
||||||
path('document/<id>', get_document_view, name="doc-open"),
|
path('document/<id>', get_document_view, name="doc-open"),
|
||||||
@ -43,11 +44,12 @@ urlpatterns = [
|
|||||||
# Generic deadline routes
|
# Generic deadline routes
|
||||||
path('deadline/<id>/remove', remove_deadline_view, name="deadline-remove"),
|
path('deadline/<id>/remove', remove_deadline_view, name="deadline-remove"),
|
||||||
|
|
||||||
# Autocomplete paths
|
# Autocomplete paths for all apps
|
||||||
path("atcmplt/orgs", OrganisationAutocomplete.as_view(), name="orgs-autocomplete"),
|
path("atcmplt/orgs", OrganisationAutocomplete.as_view(), name="orgs-autocomplete"),
|
||||||
path("atcmplt/orgs/other", NonOfficialOrganisationAutocomplete.as_view(), name="other-orgs-autocomplete"),
|
path("atcmplt/orgs/other", NonOfficialOrganisationAutocomplete.as_view(), name="other-orgs-autocomplete"),
|
||||||
path("atcmplt/eco-accounts", EcoAccountAutocomplete.as_view(), name="accounts-autocomplete"),
|
path("atcmplt/eco-accounts", EcoAccountAutocomplete.as_view(), name="accounts-autocomplete"),
|
||||||
path("atcmplt/interventions", InterventionAutocomplete.as_view(), name="interventions-autocomplete"),
|
path("atcmplt/interventions", InterventionAutocomplete.as_view(), name="interventions-autocomplete"),
|
||||||
|
path("atcmplt/codes", KonovaCodeAutocomplete.as_view(), name="codes-autocomplete"),
|
||||||
]
|
]
|
||||||
|
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
|
Loading…
Reference in New Issue
Block a user