Compare commits
5 Commits
de8d79983d
...
75777d65c6
Author | SHA1 | Date | |
---|---|---|---|
75777d65c6 | |||
87b1da8fdd | |||
c07933a9bf | |||
ca6770ec54 | |||
05c1bf677c |
@ -55,7 +55,7 @@ class TimespanReportForm(BaseForm):
|
||||
code_lists__in=[CODELIST_CONSERVATION_OFFICE_ID],
|
||||
),
|
||||
widget=autocomplete.ModelSelect2(
|
||||
url="codes-conservation-office-autocomplete",
|
||||
url="codelist:conservation-office-autocomplete",
|
||||
attrs={
|
||||
"data-placeholder": _("Click for selection")
|
||||
}
|
||||
|
7
codelist/autocomplete/__init__.py
Normal file
7
codelist/autocomplete/__init__.py
Normal file
@ -0,0 +1,7 @@
|
||||
"""
|
||||
Author: Michel Peltriaux
|
||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||
Contact: ksp-servicestelle@sgdnord.rlp.de
|
||||
Created on: 18.08.22
|
||||
|
||||
"""
|
74
codelist/autocomplete/base.py
Normal file
74
codelist/autocomplete/base.py
Normal file
@ -0,0 +1,74 @@
|
||||
"""
|
||||
Author: Michel Peltriaux
|
||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||
Contact: ksp-servicestelle@sgdnord.rlp.de
|
||||
Created on: 18.08.22
|
||||
|
||||
"""
|
||||
from dal_select2.views import Select2GroupQuerySetView
|
||||
from django.db.models import Q
|
||||
|
||||
from codelist.models import KonovaCode
|
||||
|
||||
|
||||
class KonovaCodeAutocomplete(Select2GroupQuerySetView):
|
||||
"""
|
||||
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
|
||||
|
||||
"""
|
||||
paginate_by = 50
|
||||
|
||||
def order_by(self, qs):
|
||||
""" Orders by a predefined value
|
||||
|
||||
Wrapped in a function to provide inheritance-based different orders
|
||||
|
||||
Args:
|
||||
qs (QuerySet): The queryset to be ordered
|
||||
|
||||
Returns:
|
||||
qs (QuerySet): The ordered queryset
|
||||
"""
|
||||
return qs.order_by(
|
||||
"long_name"
|
||||
)
|
||||
|
||||
def get_queryset(self):
|
||||
if self.request.user.is_anonymous:
|
||||
return KonovaCode.objects.none()
|
||||
qs = KonovaCode.objects.filter(
|
||||
is_archived=False,
|
||||
is_selectable=True,
|
||||
is_leaf=True,
|
||||
)
|
||||
qs = self.order_by(qs)
|
||||
if self.c:
|
||||
qs = qs.filter(
|
||||
code_lists__in=[self.c]
|
||||
)
|
||||
if self.q:
|
||||
# Remove whitespaces from self.q and split input in all keywords (if multiple given)
|
||||
q = dict.fromkeys(self.q.strip().split(" "))
|
||||
# Create one filter looking up for all keys where all keywords can be found in the same result
|
||||
_filter = Q()
|
||||
for keyword in q:
|
||||
q_or = Q()
|
||||
q_or |= Q(long_name__icontains=keyword)
|
||||
q_or |= Q(short_name__icontains=keyword)
|
||||
q_or |= Q(parent__long_name__icontains=keyword)
|
||||
q_or |= Q(parent__short_name__icontains=keyword)
|
||||
q_or |= Q(parent__parent__long_name__icontains=keyword)
|
||||
q_or |= Q(parent__parent__short_name__icontains=keyword)
|
||||
_filter.add(q_or, Q.AND)
|
||||
qs = qs.filter(_filter).distinct()
|
||||
return qs
|
||||
|
||||
def get_result_label(self, result):
|
||||
return f"{result.long_name}"
|
||||
|
||||
def get_selected_result_label(self, result):
|
||||
return f"{result.__str__()}"
|
110
codelist/autocomplete/biotope.py
Normal file
110
codelist/autocomplete/biotope.py
Normal file
@ -0,0 +1,110 @@
|
||||
"""
|
||||
Author: Michel Peltriaux
|
||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||
Contact: ksp-servicestelle@sgdnord.rlp.de
|
||||
Created on: 18.08.22
|
||||
|
||||
"""
|
||||
import collections
|
||||
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
|
||||
from codelist.settings import CODELIST_BIOTOPES_ID, CODELIST_BIOTOPES_EXTRA_CODES_ID
|
||||
from codelist.autocomplete.base import KonovaCodeAutocomplete
|
||||
from konova.utils.message_templates import UNGROUPED
|
||||
|
||||
|
||||
class BiotopeCodeAutocomplete(KonovaCodeAutocomplete):
|
||||
"""
|
||||
Due to limitations of the django dal package, we need to subclass for each code list
|
||||
"""
|
||||
group_by_related = "parent"
|
||||
related_field_name = "long_name"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.c = CODELIST_BIOTOPES_ID
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def order_by(self, qs):
|
||||
""" Orders by a predefined value
|
||||
|
||||
Wrapped in a function to provide inheritance-based different orders
|
||||
|
||||
Args:
|
||||
qs (QuerySet): The queryset to be ordered
|
||||
|
||||
Returns:
|
||||
qs (QuerySet): The ordered queryset
|
||||
"""
|
||||
return qs.order_by(
|
||||
"short_name",
|
||||
)
|
||||
|
||||
def get_result_label(self, result):
|
||||
return f"{result.long_name} ({result.short_name})"
|
||||
|
||||
def get_results(self, context):
|
||||
"""Return the options grouped by a common related model.
|
||||
|
||||
Raises ImproperlyConfigured if self.group_by_name is not configured
|
||||
"""
|
||||
if not self.group_by_related:
|
||||
raise ImproperlyConfigured("Missing group_by_related.")
|
||||
|
||||
super_groups = collections.OrderedDict()
|
||||
|
||||
object_list = context['object_list']
|
||||
|
||||
for result in object_list:
|
||||
group = result.parent if result.parent else None
|
||||
group_name = f"{group.long_name} ({group.short_name})" if group else UNGROUPED
|
||||
super_group = result.parent.parent if result.parent else None
|
||||
super_group_name = f"{super_group.long_name} ({super_group.short_name})" if super_group else UNGROUPED
|
||||
super_groups.setdefault(super_group_name, {})
|
||||
super_groups[super_group_name].setdefault(group_name, [])
|
||||
super_groups[super_group_name][group_name].append(result)
|
||||
|
||||
return [{
|
||||
'id': None,
|
||||
'text': super_group,
|
||||
'children': [{
|
||||
"id": None,
|
||||
"text": group,
|
||||
"children": [{
|
||||
'id': self.get_result_value(result),
|
||||
'text': self.get_result_label(result),
|
||||
'selected_text': self.get_selected_result_label(result),
|
||||
} for result in results]
|
||||
} for group, results in groups.items()]
|
||||
} for super_group, groups in super_groups.items()]
|
||||
|
||||
|
||||
class BiotopeExtraCodeAutocomplete(KonovaCodeAutocomplete):
|
||||
"""
|
||||
Due to limitations of the django dal package, we need to subclass for each code list
|
||||
"""
|
||||
group_by_related = "parent"
|
||||
related_field_name = "long_name"
|
||||
paginate_by = 200
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.c = CODELIST_BIOTOPES_EXTRA_CODES_ID
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def order_by(self, qs):
|
||||
""" Orders by a predefined value
|
||||
|
||||
Wrapped in a function to provide inheritance-based different orders
|
||||
|
||||
Args:
|
||||
qs (QuerySet): The queryset to be ordered
|
||||
|
||||
Returns:
|
||||
qs (QuerySet): The ordered queryset
|
||||
"""
|
||||
return qs.order_by(
|
||||
"long_name",
|
||||
)
|
||||
|
||||
def get_result_label(self, result):
|
||||
return f"{result.long_name} ({result.short_name})"
|
45
codelist/autocomplete/compensation_action.py
Normal file
45
codelist/autocomplete/compensation_action.py
Normal file
@ -0,0 +1,45 @@
|
||||
"""
|
||||
Author: Michel Peltriaux
|
||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||
Contact: ksp-servicestelle@sgdnord.rlp.de
|
||||
Created on: 18.08.22
|
||||
|
||||
"""
|
||||
from codelist.settings import CODELIST_COMPENSATION_ACTION_ID, CODELIST_COMPENSATION_ACTION_DETAIL_ID
|
||||
from codelist.autocomplete.base import KonovaCodeAutocomplete
|
||||
|
||||
|
||||
class CompensationActionCodeAutocomplete(KonovaCodeAutocomplete):
|
||||
"""
|
||||
Due to limitations of the django dal package, we need to subclass for each code list
|
||||
"""
|
||||
group_by_related = "parent"
|
||||
related_field_name = "long_name"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.c = CODELIST_COMPENSATION_ACTION_ID
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def order_by(self, qs):
|
||||
return qs.order_by(
|
||||
"parent__long_name"
|
||||
)
|
||||
|
||||
|
||||
class CompensationActionDetailCodeAutocomplete(KonovaCodeAutocomplete):
|
||||
"""
|
||||
Due to limitations of the django dal package, we need to subclass for each code list
|
||||
"""
|
||||
group_by_related = "parent"
|
||||
related_field_name = "long_name"
|
||||
paginate_by = 200
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.c = CODELIST_COMPENSATION_ACTION_DETAIL_ID
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def order_by(self, qs):
|
||||
return qs.order_by(
|
||||
"long_name"
|
||||
)
|
||||
|
24
codelist/autocomplete/handler.py
Normal file
24
codelist/autocomplete/handler.py
Normal file
@ -0,0 +1,24 @@
|
||||
"""
|
||||
Author: Michel Peltriaux
|
||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||
Contact: ksp-servicestelle@sgdnord.rlp.de
|
||||
Created on: 18.08.22
|
||||
|
||||
"""
|
||||
from codelist.settings import CODELIST_HANDLER_ID
|
||||
from codelist.autocomplete.base import KonovaCodeAutocomplete
|
||||
|
||||
|
||||
class HandlerCodeAutocomplete(KonovaCodeAutocomplete):
|
||||
"""
|
||||
Due to limitations of the django dal package, we need to subclass for each code list
|
||||
"""
|
||||
group_by_related = "parent"
|
||||
related_field_name = "long_name"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.c = CODELIST_HANDLER_ID
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def get_result_label(self, result):
|
||||
return result.long_name
|
24
codelist/autocomplete/law.py
Normal file
24
codelist/autocomplete/law.py
Normal file
@ -0,0 +1,24 @@
|
||||
"""
|
||||
Author: Michel Peltriaux
|
||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||
Contact: ksp-servicestelle@sgdnord.rlp.de
|
||||
Created on: 18.08.22
|
||||
|
||||
"""
|
||||
from codelist.settings import CODELIST_LAW_ID
|
||||
from codelist.autocomplete.base import KonovaCodeAutocomplete
|
||||
|
||||
|
||||
class LawCodeAutocomplete(KonovaCodeAutocomplete):
|
||||
"""
|
||||
Due to limitations of the django dal package, we need to subclass for each code list
|
||||
"""
|
||||
group_by_related = "parent"
|
||||
related_field_name = "long_name"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.c = CODELIST_LAW_ID
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def get_result_label(self, result):
|
||||
return f"{result.long_name} ({result.short_name})"
|
41
codelist/autocomplete/office.py
Normal file
41
codelist/autocomplete/office.py
Normal file
@ -0,0 +1,41 @@
|
||||
"""
|
||||
Author: Michel Peltriaux
|
||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||
Contact: ksp-servicestelle@sgdnord.rlp.de
|
||||
Created on: 18.08.22
|
||||
|
||||
"""
|
||||
from codelist.settings import CODELIST_CONSERVATION_OFFICE_ID, CODELIST_REGISTRATION_OFFICE_ID
|
||||
from codelist.autocomplete.base import KonovaCodeAutocomplete
|
||||
|
||||
|
||||
class RegistrationOfficeCodeAutocomplete(KonovaCodeAutocomplete):
|
||||
"""
|
||||
Due to limitations of the django dal package, we need to subclass for each code list
|
||||
"""
|
||||
group_by_related = "parent"
|
||||
related_field_name = "long_name"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.c = CODELIST_REGISTRATION_OFFICE_ID
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def order_by(self, qs):
|
||||
return qs.order_by(
|
||||
"parent__long_name"
|
||||
)
|
||||
|
||||
|
||||
class ConservationOfficeCodeAutocomplete(KonovaCodeAutocomplete):
|
||||
"""
|
||||
Due to limitations of the django dal package, we need to subclass for each code list
|
||||
"""
|
||||
group_by_related = "parent"
|
||||
related_field_name = "long_name"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.c = CODELIST_CONSERVATION_OFFICE_ID
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def get_result_label(self, result):
|
||||
return f"{result.long_name} ({result.short_name})"
|
21
codelist/autocomplete/process_type.py
Normal file
21
codelist/autocomplete/process_type.py
Normal file
@ -0,0 +1,21 @@
|
||||
"""
|
||||
Author: Michel Peltriaux
|
||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||
Contact: ksp-servicestelle@sgdnord.rlp.de
|
||||
Created on: 18.08.22
|
||||
|
||||
"""
|
||||
from codelist.autocomplete.base import KonovaCodeAutocomplete
|
||||
from codelist.settings import CODELIST_PROCESS_TYPE_ID
|
||||
|
||||
|
||||
class ProcessTypeCodeAutocomplete(KonovaCodeAutocomplete):
|
||||
"""
|
||||
Due to limitations of the django dal package, we need to subclass for each code list
|
||||
"""
|
||||
group_by_related = "parent"
|
||||
related_field_name = "long_name"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.c = CODELIST_PROCESS_TYPE_ID
|
||||
super().__init__(*args, **kwargs)
|
@ -7,8 +7,24 @@ Created on: 23.08.21
|
||||
"""
|
||||
from django.urls import path
|
||||
|
||||
from codelist.autocomplete.biotope import BiotopeCodeAutocomplete, BiotopeExtraCodeAutocomplete
|
||||
from codelist.autocomplete.compensation_action import CompensationActionDetailCodeAutocomplete, \
|
||||
CompensationActionCodeAutocomplete
|
||||
from codelist.autocomplete.handler import HandlerCodeAutocomplete
|
||||
from codelist.autocomplete.law import LawCodeAutocomplete
|
||||
from codelist.autocomplete.office import ConservationOfficeCodeAutocomplete, RegistrationOfficeCodeAutocomplete
|
||||
from codelist.autocomplete.process_type import ProcessTypeCodeAutocomplete
|
||||
|
||||
app_name = "codelist"
|
||||
urlpatterns = [
|
||||
|
||||
path("atcmplt/codes/biotope", BiotopeCodeAutocomplete.as_view(), name="biotope-autocomplete"),
|
||||
path("atcmplt/codes/biotope/extra", BiotopeExtraCodeAutocomplete.as_view(),
|
||||
name="biotope-extra-type-autocomplete"),
|
||||
path("atcmplt/codes/law", LawCodeAutocomplete.as_view(), name="law-autocomplete"),
|
||||
path("atcmplt/codes/reg-off", RegistrationOfficeCodeAutocomplete.as_view(), name="registration-office-autocomplete"),
|
||||
path("atcmplt/codes/cons-off", ConservationOfficeCodeAutocomplete.as_view(), name="conservation-office-autocomplete"),
|
||||
path("atcmplt/codes/handler", HandlerCodeAutocomplete.as_view(), name="handler-autocomplete"),
|
||||
path("atcmplt/codes/comp/action", CompensationActionCodeAutocomplete.as_view(), name="compensation-action-autocomplete"),
|
||||
path("atcmplt/codes/comp/action/detail", CompensationActionDetailCodeAutocomplete.as_view(), name="compensation-action-detail-autocomplete"),
|
||||
path("atcmplt/codes/prc-type", ProcessTypeCodeAutocomplete.as_view(), name="process-type-autocomplete"),
|
||||
]
|
7
compensation/autocomplete/__init__.py
Normal file
7
compensation/autocomplete/__init__.py
Normal file
@ -0,0 +1,7 @@
|
||||
"""
|
||||
Author: Michel Peltriaux
|
||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||
Contact: ksp-servicestelle@sgdnord.rlp.de
|
||||
Created on: 18.08.22
|
||||
|
||||
"""
|
34
compensation/autocomplete/eco_account.py
Normal file
34
compensation/autocomplete/eco_account.py
Normal file
@ -0,0 +1,34 @@
|
||||
"""
|
||||
Author: Michel Peltriaux
|
||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||
Contact: ksp-servicestelle@sgdnord.rlp.de
|
||||
Created on: 18.08.22
|
||||
|
||||
"""
|
||||
from dal_select2.views import Select2QuerySetView
|
||||
from django.db.models import Q
|
||||
|
||||
from compensation.models import EcoAccount
|
||||
|
||||
|
||||
class EcoAccountAutocomplete(Select2QuerySetView):
|
||||
""" Autocomplete for ecoAccount entries
|
||||
|
||||
Only returns entries that are already recorded and not deleted
|
||||
|
||||
"""
|
||||
def get_queryset(self):
|
||||
if self.request.user.is_anonymous:
|
||||
return EcoAccount.objects.none()
|
||||
qs = EcoAccount.objects.filter(
|
||||
deleted=None,
|
||||
recorded__isnull=False,
|
||||
).order_by(
|
||||
"identifier"
|
||||
)
|
||||
if self.q:
|
||||
qs = qs.filter(
|
||||
Q(identifier__icontains=self.q) |
|
||||
Q(title__icontains=self.q)
|
||||
).distinct()
|
||||
return qs
|
7
compensation/filters/__init__.py
Normal file
7
compensation/filters/__init__.py
Normal file
@ -0,0 +1,7 @@
|
||||
"""
|
||||
Author: Michel Peltriaux
|
||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||
Contact: ksp-servicestelle@sgdnord.rlp.de
|
||||
Created on: 18.08.22
|
||||
|
||||
"""
|
@ -1,17 +1,14 @@
|
||||
"""
|
||||
Author: Michel Peltriaux
|
||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||
Created on: 29.07.21
|
||||
Contact: ksp-servicestelle@sgdnord.rlp.de
|
||||
Created on: 18.08.22
|
||||
|
||||
"""
|
||||
import django_filters
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django import forms
|
||||
from django.db.models import QuerySet, Q
|
||||
|
||||
from konova.filters.mixins import ConservationOfficeTableFilterMixin
|
||||
from konova.filters.table_filters import QueryTableFilter, CheckboxTableFilter, SelectionTableFilter, AbstractTableFilter
|
||||
from konova.filters.table_filters import AbstractTableFilter, CheckboxTableFilter, QueryTableFilter, \
|
||||
SelectionTableFilter
|
||||
|
||||
|
||||
class SelectionCompensationTableFilter(SelectionTableFilter):
|
||||
@ -114,71 +111,3 @@ class CompensationTableFilter(AbstractTableFilter):
|
||||
)
|
||||
# Overwrite final queryset as well
|
||||
self.qs = self.checkbox_filter.qs
|
||||
|
||||
|
||||
class CheckboxEcoAccountTableFilter(CheckboxTableFilter):
|
||||
sr = django_filters.BooleanFilter(
|
||||
method='filter_only_show_unrecorded',
|
||||
label=_("Show only unrecorded"),
|
||||
label_suffix=_(""),
|
||||
widget=forms.CheckboxInput(
|
||||
attrs={
|
||||
"class": "form-check-input",
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
def filter_only_show_unrecorded(self, queryset, name, value) -> QuerySet:
|
||||
""" Filters queryset depending on value of 'show_recorded' setting
|
||||
|
||||
Args:
|
||||
queryset ():
|
||||
name ():
|
||||
value ():
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
if value:
|
||||
return queryset.filter(
|
||||
recorded=None,
|
||||
)
|
||||
else:
|
||||
return queryset
|
||||
|
||||
|
||||
class SelectionEcoAccountTableFilter(ConservationOfficeTableFilterMixin):
|
||||
""" Special selection table filter for eco accounts
|
||||
|
||||
EcoAccounts only need a selection filter for conservation office
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class EcoAccountTableFilter(AbstractTableFilter):
|
||||
""" TableFilter for eco accounts
|
||||
|
||||
"""
|
||||
def __init__(self, user=None, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.user = user
|
||||
qs = kwargs.get("queryset", None)
|
||||
request_data = kwargs.get("data", None)
|
||||
|
||||
# Pipe the queryset through all needed filters
|
||||
self.selection_filter = SelectionEcoAccountTableFilter(
|
||||
data=request_data,
|
||||
queryset=qs,
|
||||
)
|
||||
self.query_filter = QueryTableFilter(
|
||||
data=request_data,
|
||||
queryset=self.selection_filter.qs,
|
||||
)
|
||||
self.checkbox_filter = CheckboxEcoAccountTableFilter(
|
||||
user=user,
|
||||
data=request_data,
|
||||
queryset=self.query_filter.qs,
|
||||
)
|
||||
# Overwrite the final queryset result
|
||||
self.qs = self.checkbox_filter.qs
|
82
compensation/filters/eco_account.py
Normal file
82
compensation/filters/eco_account.py
Normal file
@ -0,0 +1,82 @@
|
||||
"""
|
||||
Author: Michel Peltriaux
|
||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||
Contact: ksp-servicestelle@sgdnord.rlp.de
|
||||
Created on: 18.08.22
|
||||
|
||||
"""
|
||||
from django import forms
|
||||
from django.db.models import QuerySet
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
import django_filters
|
||||
|
||||
from konova.filters.mixins.office import ConservationOfficeTableFilterMixin
|
||||
from konova.filters.table_filters import AbstractTableFilter, CheckboxTableFilter, QueryTableFilter
|
||||
|
||||
|
||||
class CheckboxEcoAccountTableFilter(CheckboxTableFilter):
|
||||
sr = django_filters.BooleanFilter(
|
||||
method='filter_only_show_unrecorded',
|
||||
label=_("Show only unrecorded"),
|
||||
label_suffix=_(""),
|
||||
widget=forms.CheckboxInput(
|
||||
attrs={
|
||||
"class": "form-check-input",
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
def filter_only_show_unrecorded(self, queryset, name, value) -> QuerySet:
|
||||
""" Filters queryset depending on value of 'show_recorded' setting
|
||||
|
||||
Args:
|
||||
queryset ():
|
||||
name ():
|
||||
value ():
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
if value:
|
||||
return queryset.filter(
|
||||
recorded=None,
|
||||
)
|
||||
else:
|
||||
return queryset
|
||||
|
||||
|
||||
class SelectionEcoAccountTableFilter(ConservationOfficeTableFilterMixin):
|
||||
""" Special selection table filter for eco accounts
|
||||
|
||||
EcoAccounts only need a selection filter for conservation office
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class EcoAccountTableFilter(AbstractTableFilter):
|
||||
""" TableFilter for eco accounts
|
||||
|
||||
"""
|
||||
def __init__(self, user=None, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.user = user
|
||||
qs = kwargs.get("queryset", None)
|
||||
request_data = kwargs.get("data", None)
|
||||
|
||||
# Pipe the queryset through all needed filters
|
||||
self.selection_filter = SelectionEcoAccountTableFilter(
|
||||
data=request_data,
|
||||
queryset=qs,
|
||||
)
|
||||
self.query_filter = QueryTableFilter(
|
||||
data=request_data,
|
||||
queryset=self.selection_filter.qs,
|
||||
)
|
||||
self.checkbox_filter = CheckboxEcoAccountTableFilter(
|
||||
user=user,
|
||||
data=request_data,
|
||||
queryset=self.query_filter.qs,
|
||||
)
|
||||
# Overwrite the final queryset result
|
||||
self.qs = self.checkbox_filter.qs
|
@ -88,7 +88,7 @@ class NewCompensationForm(AbstractCompensationForm,
|
||||
deleted=None,
|
||||
),
|
||||
widget=autocomplete.ModelSelect2(
|
||||
url="interventions-autocomplete",
|
||||
url="intervention:autocomplete",
|
||||
attrs={
|
||||
"data-placeholder": _("Click for selection"),
|
||||
"data-minimum-input-length": 3,
|
||||
|
@ -27,7 +27,7 @@ class CompensationResponsibleFormMixin(forms.Form):
|
||||
code_lists__in=[CODELIST_CONSERVATION_OFFICE_ID],
|
||||
),
|
||||
widget=autocomplete.ModelSelect2(
|
||||
url="codes-conservation-office-autocomplete",
|
||||
url="codelist:conservation-office-autocomplete",
|
||||
attrs={
|
||||
"data-placeholder": _("Click for selection")
|
||||
}
|
||||
@ -57,7 +57,7 @@ class CompensationResponsibleFormMixin(forms.Form):
|
||||
code_lists__in=[CODELIST_HANDLER_ID],
|
||||
),
|
||||
widget=autocomplete.ModelSelect2(
|
||||
url="codes-handler-autocomplete",
|
||||
url="codelist:handler-autocomplete",
|
||||
attrs={
|
||||
"data-placeholder": _("Click for selection"),
|
||||
}
|
||||
|
@ -44,7 +44,7 @@ class NewCompensationActionModalForm(BaseModalForm):
|
||||
code_lists__in=[CODELIST_COMPENSATION_ACTION_DETAIL_ID],
|
||||
),
|
||||
widget=autocomplete.ModelSelect2Multiple(
|
||||
url="codes-compensation-action-detail-autocomplete",
|
||||
url="codelist:compensation-action-detail-autocomplete",
|
||||
attrs={
|
||||
"data-placeholder": _("Action Type detail"),
|
||||
}
|
||||
|
@ -46,7 +46,7 @@ class NewCompensationStateModalForm(BaseModalForm):
|
||||
code_lists__in=[CODELIST_BIOTOPES_EXTRA_CODES_ID],
|
||||
),
|
||||
widget=autocomplete.ModelSelect2Multiple(
|
||||
url="codes-biotope-extra-type-autocomplete",
|
||||
url="codelist:biotope-extra-type-autocomplete",
|
||||
attrs={
|
||||
"data-placeholder": _("Biotope additional type"),
|
||||
}
|
||||
|
7
compensation/tables/__init__.py
Normal file
7
compensation/tables/__init__.py
Normal file
@ -0,0 +1,7 @@
|
||||
"""
|
||||
Author: Michel Peltriaux
|
||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||
Contact: ksp-servicestelle@sgdnord.rlp.de
|
||||
Created on: 18.08.22
|
||||
|
||||
"""
|
@ -1,19 +1,19 @@
|
||||
"""
|
||||
Author: Michel Peltriaux
|
||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||
Created on: 01.12.20
|
||||
Contact: ksp-servicestelle@sgdnord.rlp.de
|
||||
Created on: 18.08.22
|
||||
|
||||
"""
|
||||
from konova.utils.message_templates import DATA_IS_UNCHECKED, DATA_CHECKED_ON_TEMPLATE, DATA_CHECKED_PREVIOUSLY_TEMPLATE
|
||||
from django.http import HttpRequest
|
||||
from django.template.loader import render_to_string
|
||||
from django.urls import reverse
|
||||
from django.utils.html import format_html
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from compensation.filters import CompensationTableFilter, EcoAccountTableFilter
|
||||
from compensation.models import Compensation, EcoAccount
|
||||
from compensation.filters.compensation import CompensationTableFilter
|
||||
from compensation.models import Compensation
|
||||
from konova.utils.message_templates import DATA_IS_UNCHECKED, DATA_CHECKED_ON_TEMPLATE, DATA_CHECKED_PREVIOUSLY_TEMPLATE
|
||||
from konova.utils.tables import BaseTable, TableRenderMixin
|
||||
import django_tables2 as tables
|
||||
|
||||
@ -187,160 +187,3 @@ class CompensationTable(BaseTable, TableRenderMixin):
|
||||
icn_class="fas fa-edit rlp-r-inv" if has_access else "far fa-edit",
|
||||
)
|
||||
return format_html(html)
|
||||
|
||||
|
||||
class EcoAccountTable(BaseTable, TableRenderMixin):
|
||||
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=_("Parcel gmrkng"),
|
||||
orderable=True,
|
||||
accessor="geometry",
|
||||
)
|
||||
av = tables.Column(
|
||||
verbose_name=_("Available"),
|
||||
orderable=True,
|
||||
empty_values=[],
|
||||
attrs={
|
||||
"th": {
|
||||
"class": "w-20",
|
||||
}
|
||||
}
|
||||
)
|
||||
r = tables.Column(
|
||||
verbose_name=_("Recorded"),
|
||||
orderable=True,
|
||||
empty_values=[],
|
||||
accessor="recorded",
|
||||
)
|
||||
e = tables.Column(
|
||||
verbose_name=_("Editable"),
|
||||
orderable=True,
|
||||
empty_values=[],
|
||||
accessor="users",
|
||||
)
|
||||
lm = tables.Column(
|
||||
verbose_name=_("Last edit"),
|
||||
orderable=True,
|
||||
accessor="modified__timestamp",
|
||||
)
|
||||
|
||||
class Meta(BaseTable.Meta):
|
||||
template_name = "django_tables2/bootstrap4.html"
|
||||
|
||||
def __init__(self, request: HttpRequest, *args, **kwargs):
|
||||
self.title = _("Eco Accounts")
|
||||
self.add_new_url = reverse("compensation:acc:new")
|
||||
qs = kwargs.get("queryset", None)
|
||||
self.filter = EcoAccountTableFilter(
|
||||
user=request.user,
|
||||
data=request.GET,
|
||||
queryset=qs,
|
||||
)
|
||||
kwargs["queryset"] = self.filter.qs
|
||||
super().__init__(request, *args, **kwargs)
|
||||
|
||||
def render_id(self, value, record: EcoAccount):
|
||||
""" Renders the id column for an eco account
|
||||
|
||||
Args:
|
||||
value (str): The identifier value
|
||||
record (EcoAccount): The eco account record
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
html = ""
|
||||
html += self.render_link(
|
||||
tooltip=_("Open {}").format(_("Eco-account")),
|
||||
href=reverse("compensation:acc:detail", args=(record.id,)),
|
||||
txt=value,
|
||||
new_tab=False,
|
||||
)
|
||||
return format_html(html)
|
||||
|
||||
def render_av(self, value, record: EcoAccount):
|
||||
""" Renders the available column for an eco account
|
||||
|
||||
Args:
|
||||
value (str): The identifier value
|
||||
record (EcoAccount): The eco account record
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
value_total, value_relative = record.get_available_rest()
|
||||
html = render_to_string("konova/widgets/progressbar.html", {"value": value_relative})
|
||||
return format_html(html)
|
||||
|
||||
def render_d(self, value, record: Compensation):
|
||||
""" Renders the parcel district column for a compensation
|
||||
|
||||
Args:
|
||||
value (str): The geometry
|
||||
record (Compensation): The compensation record
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
parcels = value.get_underlying_parcels().values_list(
|
||||
"parcel_group__name",
|
||||
flat=True
|
||||
).distinct()
|
||||
html = render_to_string(
|
||||
"table/gmrkng_col.html",
|
||||
{
|
||||
"entries": parcels
|
||||
}
|
||||
)
|
||||
return html
|
||||
|
||||
def render_r(self, value, record: EcoAccount):
|
||||
""" Renders the recorded column for an eco account
|
||||
|
||||
Args:
|
||||
value (str): The identifier value
|
||||
record (EcoAccount): The eco account record
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
html = ""
|
||||
checked = value is not None
|
||||
tooltip = _("Not recorded yet. Can not be used for deductions, yet.")
|
||||
if checked:
|
||||
on = value.get_timestamp_str_formatted()
|
||||
tooltip = _("Recorded on {} by {}").format(on, record.recorded.user)
|
||||
html += self.render_bookmark(
|
||||
tooltip=tooltip,
|
||||
icn_filled=checked,
|
||||
)
|
||||
return format_html(html)
|
||||
|
||||
def render_e(self, value, record: EcoAccount):
|
||||
""" Renders the editable column for an eco account
|
||||
|
||||
Args:
|
||||
value (str): The identifier value
|
||||
record (EcoAccount): The eco account record
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
html = ""
|
||||
# Do not use value in here, since value does use unprefetched 'users' manager, where record has already
|
||||
# prefetched users data
|
||||
has_access = record.is_shared_with(self.user)
|
||||
html += self.render_icn(
|
||||
tooltip=_("Full access granted") if has_access else _("Access not granted"),
|
||||
icn_class="fas fa-edit rlp-r-inv" if has_access else "far fa-edit",
|
||||
)
|
||||
return format_html(html)
|
175
compensation/tables/eco_account.py
Normal file
175
compensation/tables/eco_account.py
Normal file
@ -0,0 +1,175 @@
|
||||
"""
|
||||
Author: Michel Peltriaux
|
||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||
Contact: ksp-servicestelle@sgdnord.rlp.de
|
||||
Created on: 18.08.22
|
||||
|
||||
"""
|
||||
from django.http import HttpRequest
|
||||
from django.template.loader import render_to_string
|
||||
from django.urls import reverse
|
||||
from django.utils.html import format_html
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from compensation.filters.eco_account import EcoAccountTableFilter
|
||||
from compensation.models import EcoAccount
|
||||
from konova.utils.tables import TableRenderMixin, BaseTable
|
||||
|
||||
import django_tables2 as tables
|
||||
|
||||
|
||||
class EcoAccountTable(BaseTable, TableRenderMixin):
|
||||
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=_("Parcel gmrkng"),
|
||||
orderable=True,
|
||||
accessor="geometry",
|
||||
)
|
||||
av = tables.Column(
|
||||
verbose_name=_("Available"),
|
||||
orderable=True,
|
||||
empty_values=[],
|
||||
attrs={
|
||||
"th": {
|
||||
"class": "w-20",
|
||||
}
|
||||
}
|
||||
)
|
||||
r = tables.Column(
|
||||
verbose_name=_("Recorded"),
|
||||
orderable=True,
|
||||
empty_values=[],
|
||||
accessor="recorded",
|
||||
)
|
||||
e = tables.Column(
|
||||
verbose_name=_("Editable"),
|
||||
orderable=True,
|
||||
empty_values=[],
|
||||
accessor="users",
|
||||
)
|
||||
lm = tables.Column(
|
||||
verbose_name=_("Last edit"),
|
||||
orderable=True,
|
||||
accessor="modified__timestamp",
|
||||
)
|
||||
|
||||
class Meta(BaseTable.Meta):
|
||||
template_name = "django_tables2/bootstrap4.html"
|
||||
|
||||
def __init__(self, request: HttpRequest, *args, **kwargs):
|
||||
self.title = _("Eco Accounts")
|
||||
self.add_new_url = reverse("compensation:acc:new")
|
||||
qs = kwargs.get("queryset", None)
|
||||
self.filter = EcoAccountTableFilter(
|
||||
user=request.user,
|
||||
data=request.GET,
|
||||
queryset=qs,
|
||||
)
|
||||
kwargs["queryset"] = self.filter.qs
|
||||
super().__init__(request, *args, **kwargs)
|
||||
|
||||
def render_id(self, value, record: EcoAccount):
|
||||
""" Renders the id column for an eco account
|
||||
|
||||
Args:
|
||||
value (str): The identifier value
|
||||
record (EcoAccount): The eco account record
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
html = ""
|
||||
html += self.render_link(
|
||||
tooltip=_("Open {}").format(_("Eco-account")),
|
||||
href=reverse("compensation:acc:detail", args=(record.id,)),
|
||||
txt=value,
|
||||
new_tab=False,
|
||||
)
|
||||
return format_html(html)
|
||||
|
||||
def render_av(self, value, record: EcoAccount):
|
||||
""" Renders the available column for an eco account
|
||||
|
||||
Args:
|
||||
value (str): The identifier value
|
||||
record (EcoAccount): The eco account record
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
value_total, value_relative = record.get_available_rest()
|
||||
html = render_to_string("konova/widgets/progressbar.html", {"value": value_relative})
|
||||
return format_html(html)
|
||||
|
||||
def render_d(self, value, record):
|
||||
""" Renders the parcel district column for a compensation
|
||||
|
||||
Args:
|
||||
value (str): The geometry
|
||||
record (Compensation): The compensation record
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
parcels = value.get_underlying_parcels().values_list(
|
||||
"parcel_group__name",
|
||||
flat=True
|
||||
).distinct()
|
||||
html = render_to_string(
|
||||
"table/gmrkng_col.html",
|
||||
{
|
||||
"entries": parcels
|
||||
}
|
||||
)
|
||||
return html
|
||||
|
||||
def render_r(self, value, record: EcoAccount):
|
||||
""" Renders the recorded column for an eco account
|
||||
|
||||
Args:
|
||||
value (str): The identifier value
|
||||
record (EcoAccount): The eco account record
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
html = ""
|
||||
checked = value is not None
|
||||
tooltip = _("Not recorded yet. Can not be used for deductions, yet.")
|
||||
if checked:
|
||||
on = value.get_timestamp_str_formatted()
|
||||
tooltip = _("Recorded on {} by {}").format(on, record.recorded.user)
|
||||
html += self.render_bookmark(
|
||||
tooltip=tooltip,
|
||||
icn_filled=checked,
|
||||
)
|
||||
return format_html(html)
|
||||
|
||||
def render_e(self, value, record: EcoAccount):
|
||||
""" Renders the editable column for an eco account
|
||||
|
||||
Args:
|
||||
value (str): The identifier value
|
||||
record (EcoAccount): The eco account record
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
html = ""
|
||||
# Do not use value in here, since value does use unprefetched 'users' manager, where record has already
|
||||
# prefetched users data
|
||||
has_access = record.is_shared_with(self.user)
|
||||
html += self.render_icn(
|
||||
tooltip=_("Full access granted") if has_access else _("Access not granted"),
|
||||
icn_class="fas fa-edit rlp-r-inv" if has_access else "far fa-edit",
|
||||
)
|
||||
return format_html(html)
|
@ -6,6 +6,8 @@ Created on: 24.08.21
|
||||
|
||||
"""
|
||||
from django.urls import path
|
||||
|
||||
from compensation.autocomplete.eco_account import EcoAccountAutocomplete
|
||||
from compensation.views.eco_account import *
|
||||
|
||||
app_name = "acc"
|
||||
@ -47,4 +49,6 @@ urlpatterns = [
|
||||
path('<id>/deduction/<deduction_id>/edit', deduction_edit_view, name='edit-deduction'),
|
||||
path('<id>/deduct/new', new_deduction_view, name='new-deduction'),
|
||||
|
||||
# Autocomplete
|
||||
path("atcmplt/eco-accounts", EcoAccountAutocomplete.as_view(), name="autocomplete"),
|
||||
]
|
@ -12,7 +12,7 @@ from compensation.forms.modals.compensation_action import NewCompensationActionM
|
||||
EditCompensationActionModalForm, RemoveCompensationActionModalForm
|
||||
from compensation.forms.modals.deadline import NewDeadlineModalForm, EditDeadlineModalForm
|
||||
from compensation.models import Compensation, CompensationState, CompensationAction, CompensationDocument
|
||||
from compensation.tables import CompensationTable
|
||||
from compensation.tables.compensation import CompensationTable
|
||||
from intervention.models import Intervention
|
||||
from konova.contexts import BaseContext
|
||||
from konova.decorators import *
|
||||
|
@ -22,7 +22,7 @@ from compensation.forms.modals.document import NewEcoAccountDocumentModalForm
|
||||
from compensation.forms.modals.state import NewCompensationStateModalForm, RemoveCompensationStateModalForm, \
|
||||
EditCompensationStateModalForm
|
||||
from compensation.models import EcoAccount, EcoAccountDocument, CompensationState, CompensationAction
|
||||
from compensation.tables import EcoAccountTable
|
||||
from compensation.tables.eco_account import EcoAccountTable
|
||||
from intervention.forms.modals.deduction import RemoveEcoAccountDeductionModalForm, NewEcoAccountDeductionModalForm, \
|
||||
EditEcoAccountDeductionModalForm
|
||||
from intervention.forms.modals.share import ShareModalForm
|
||||
|
@ -5,7 +5,7 @@ Contact: michel.peltriaux@sgdnord.rlp.de
|
||||
Created on: 19.08.21
|
||||
|
||||
"""
|
||||
from compensation.filters import EcoAccountTableFilter
|
||||
from compensation.filters.eco_account import EcoAccountTableFilter
|
||||
|
||||
|
||||
class EmaTableFilter(EcoAccountTableFilter):
|
||||
|
7
intervention/autocomplete/__init__.py
Normal file
7
intervention/autocomplete/__init__.py
Normal file
@ -0,0 +1,7 @@
|
||||
"""
|
||||
Author: Michel Peltriaux
|
||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||
Contact: ksp-servicestelle@sgdnord.rlp.de
|
||||
Created on: 18.08.22
|
||||
|
||||
"""
|
36
intervention/autocomplete/intervention.py
Normal file
36
intervention/autocomplete/intervention.py
Normal file
@ -0,0 +1,36 @@
|
||||
"""
|
||||
Author: Michel Peltriaux
|
||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||
Contact: ksp-servicestelle@sgdnord.rlp.de
|
||||
Created on: 18.08.22
|
||||
|
||||
"""
|
||||
from dal_select2.views import Select2QuerySetView
|
||||
from django.db.models import Q
|
||||
|
||||
from intervention.models import Intervention
|
||||
|
||||
|
||||
class InterventionAutocomplete(Select2QuerySetView):
|
||||
""" Autocomplete for intervention entries
|
||||
|
||||
Only returns entries that are accessible for the requesting user
|
||||
|
||||
"""
|
||||
def get_queryset(self):
|
||||
user = self.request.user
|
||||
if user.is_anonymous:
|
||||
return Intervention.objects.none()
|
||||
qs = Intervention.objects.filter(
|
||||
Q(deleted=None) &
|
||||
Q(users__in=[user]) |
|
||||
Q(teams__in=user.teams.all())
|
||||
).order_by(
|
||||
"identifier"
|
||||
).distinct()
|
||||
if self.q:
|
||||
qs = qs.filter(
|
||||
Q(identifier__icontains=self.q) |
|
||||
Q(title__icontains=self.q)
|
||||
).distinct()
|
||||
return qs
|
@ -60,7 +60,7 @@ class NewInterventionForm(BaseForm):
|
||||
code_lists__in=[CODELIST_PROCESS_TYPE_ID],
|
||||
),
|
||||
widget=autocomplete.ModelSelect2(
|
||||
url="codes-process-type-autocomplete",
|
||||
url="codelist:process-type-autocomplete",
|
||||
attrs={
|
||||
"data-placeholder": _("Click for selection"),
|
||||
}
|
||||
@ -77,7 +77,7 @@ class NewInterventionForm(BaseForm):
|
||||
code_lists__in=[CODELIST_LAW_ID],
|
||||
),
|
||||
widget=autocomplete.ModelSelect2Multiple(
|
||||
url="codes-law-autocomplete",
|
||||
url="codelist:law-autocomplete",
|
||||
attrs={
|
||||
"data-placeholder": _("Click for selection"),
|
||||
}
|
||||
@ -93,7 +93,7 @@ class NewInterventionForm(BaseForm):
|
||||
code_lists__in=[CODELIST_REGISTRATION_OFFICE_ID],
|
||||
),
|
||||
widget=autocomplete.ModelSelect2(
|
||||
url="codes-registration-office-autocomplete",
|
||||
url="codelist:registration-office-autocomplete",
|
||||
attrs={
|
||||
"data-placeholder": _("Click for selection"),
|
||||
}
|
||||
@ -109,7 +109,7 @@ class NewInterventionForm(BaseForm):
|
||||
code_lists__in=[CODELIST_CONSERVATION_OFFICE_ID],
|
||||
),
|
||||
widget=autocomplete.ModelSelect2(
|
||||
url="codes-conservation-office-autocomplete",
|
||||
url="codelist:conservation-office-autocomplete",
|
||||
attrs={
|
||||
"data-placeholder": _("Click for selection"),
|
||||
}
|
||||
@ -150,7 +150,7 @@ class NewInterventionForm(BaseForm):
|
||||
code_lists__in=[CODELIST_HANDLER_ID],
|
||||
),
|
||||
widget=autocomplete.ModelSelect2(
|
||||
url="codes-handler-autocomplete",
|
||||
url="codelist:handler-autocomplete",
|
||||
attrs={
|
||||
"data-placeholder": _("Click for selection"),
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ class NewEcoAccountDeductionModalForm(BaseModalForm):
|
||||
help_text=_("Only recorded accounts can be selected for deductions"),
|
||||
queryset=EcoAccount.objects.filter(deleted=None),
|
||||
widget=autocomplete.ModelSelect2(
|
||||
url="accounts-autocomplete",
|
||||
url="compensation:acc:autocomplete",
|
||||
attrs={
|
||||
"data-placeholder": _("Eco-account"),
|
||||
"data-minimum-input-length": 3,
|
||||
@ -60,7 +60,7 @@ class NewEcoAccountDeductionModalForm(BaseModalForm):
|
||||
help_text=_("Only shared interventions can be selected"),
|
||||
queryset=Intervention.objects.filter(deleted=None),
|
||||
widget=autocomplete.ModelSelect2(
|
||||
url="interventions-autocomplete",
|
||||
url="intervention:autocomplete",
|
||||
attrs={
|
||||
"data-placeholder": _("Intervention"),
|
||||
"data-minimum-input-length": 3,
|
||||
|
@ -36,7 +36,7 @@ class ShareModalForm(BaseModalForm):
|
||||
required=False,
|
||||
queryset=Team.objects.all(),
|
||||
widget=autocomplete.ModelSelect2Multiple(
|
||||
url="share-team-autocomplete",
|
||||
url="user:share-team-autocomplete",
|
||||
attrs={
|
||||
"data-placeholder": _("Click for selection"),
|
||||
"data-minimum-input-length": 3,
|
||||
@ -50,7 +50,7 @@ class ShareModalForm(BaseModalForm):
|
||||
required=False,
|
||||
queryset=User.objects.all(),
|
||||
widget=autocomplete.ModelSelect2Multiple(
|
||||
url="share-user-autocomplete",
|
||||
url="user:share-user-autocomplete",
|
||||
attrs={
|
||||
"data-placeholder": _("Click for selection"),
|
||||
"data-minimum-input-length": 3,
|
||||
|
@ -7,6 +7,7 @@ Created on: 30.11.20
|
||||
"""
|
||||
from django.urls import path
|
||||
|
||||
from intervention.autocomplete.intervention import InterventionAutocomplete
|
||||
from intervention.views import index_view, new_view, detail_view, edit_view, remove_view, new_document_view, share_view, \
|
||||
create_share_view, remove_revocation_view, new_revocation_view, check_view, log_view, new_deduction_view, \
|
||||
record_view, remove_document_view, get_document_view, get_revocation_view, new_id_view, report_view, \
|
||||
@ -48,4 +49,7 @@ urlpatterns = [
|
||||
path('<id>/revocation/<revocation_id>/edit', edit_revocation_view, name='edit-revocation'),
|
||||
path('<id>/revocation/<revocation_id>/remove', remove_revocation_view, name='remove-revocation'),
|
||||
path('revocation/<doc_id>', get_revocation_view, name='get-doc-revocation'),
|
||||
|
||||
# Autocomplete
|
||||
path("atcmplt/interventions", InterventionAutocomplete.as_view(), name="autocomplete"),
|
||||
]
|
@ -1,401 +0,0 @@
|
||||
"""
|
||||
Author: Michel Peltriaux
|
||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||
Created on: 07.12.20
|
||||
|
||||
"""
|
||||
import collections
|
||||
|
||||
from dal_select2.views import Select2QuerySetView, Select2GroupQuerySetView
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
|
||||
from konova.utils.message_templates import UNGROUPED
|
||||
from user.models import User, Team
|
||||
from django.db.models import Q
|
||||
|
||||
from codelist.models import KonovaCode
|
||||
from codelist.settings import CODELIST_COMPENSATION_ACTION_ID, CODELIST_BIOTOPES_ID, CODELIST_LAW_ID, \
|
||||
CODELIST_REGISTRATION_OFFICE_ID, CODELIST_CONSERVATION_OFFICE_ID, CODELIST_PROCESS_TYPE_ID, \
|
||||
CODELIST_BIOTOPES_EXTRA_CODES_ID, CODELIST_COMPENSATION_ACTION_DETAIL_ID, CODELIST_HANDLER_ID
|
||||
from compensation.models import EcoAccount
|
||||
from intervention.models import Intervention
|
||||
|
||||
|
||||
class EcoAccountAutocomplete(Select2QuerySetView):
|
||||
""" Autocomplete for ecoAccount entries
|
||||
|
||||
Only returns entries that are already recorded and not deleted
|
||||
|
||||
"""
|
||||
def get_queryset(self):
|
||||
if self.request.user.is_anonymous:
|
||||
return EcoAccount.objects.none()
|
||||
qs = EcoAccount.objects.filter(
|
||||
deleted=None,
|
||||
recorded__isnull=False,
|
||||
).order_by(
|
||||
"identifier"
|
||||
)
|
||||
if self.q:
|
||||
qs = qs.filter(
|
||||
Q(identifier__icontains=self.q) |
|
||||
Q(title__icontains=self.q)
|
||||
).distinct()
|
||||
return qs
|
||||
|
||||
|
||||
class InterventionAutocomplete(Select2QuerySetView):
|
||||
""" Autocomplete for intervention entries
|
||||
|
||||
Only returns entries that are accessible for the requesting user
|
||||
|
||||
"""
|
||||
def get_queryset(self):
|
||||
user = self.request.user
|
||||
if user.is_anonymous:
|
||||
return Intervention.objects.none()
|
||||
qs = Intervention.objects.filter(
|
||||
Q(deleted=None) &
|
||||
Q(users__in=[user]) |
|
||||
Q(teams__in=user.teams.all())
|
||||
).order_by(
|
||||
"identifier"
|
||||
).distinct()
|
||||
if self.q:
|
||||
qs = qs.filter(
|
||||
Q(identifier__icontains=self.q) |
|
||||
Q(title__icontains=self.q)
|
||||
).distinct()
|
||||
return qs
|
||||
|
||||
|
||||
class ShareUserAutocomplete(Select2QuerySetView):
|
||||
""" Autocomplete for share with single users
|
||||
|
||||
|
||||
"""
|
||||
def get_queryset(self):
|
||||
if self.request.user.is_anonymous:
|
||||
return User.objects.none()
|
||||
qs = User.objects.all()
|
||||
if self.q:
|
||||
# Due to privacy concerns only a full username match will return the proper user entry
|
||||
qs = qs.filter(
|
||||
Q(username=self.q) |
|
||||
Q(email=self.q)
|
||||
).distinct()
|
||||
qs = qs.order_by("username")
|
||||
return qs
|
||||
|
||||
|
||||
class ShareTeamAutocomplete(Select2QuerySetView):
|
||||
""" Autocomplete for share with teams
|
||||
|
||||
"""
|
||||
def get_queryset(self):
|
||||
if self.request.user.is_anonymous:
|
||||
return Team.objects.none()
|
||||
qs = Team.objects.filter(
|
||||
deleted__isnull=True
|
||||
)
|
||||
if self.q:
|
||||
# Due to privacy concerns only a full username match will return the proper user entry
|
||||
qs = qs.filter(
|
||||
name__icontains=self.q
|
||||
)
|
||||
qs = qs.order_by(
|
||||
"name"
|
||||
)
|
||||
return qs
|
||||
|
||||
|
||||
class TeamAdminAutocomplete(Select2QuerySetView):
|
||||
""" Autocomplete for share with teams
|
||||
|
||||
"""
|
||||
def get_queryset(self):
|
||||
if self.request.user.is_anonymous:
|
||||
return User.objects.none()
|
||||
qs = User.objects.filter(
|
||||
id__in=self.forwarded.get("members", [])
|
||||
).exclude(
|
||||
id__in=self.forwarded.get("admins", [])
|
||||
)
|
||||
if self.q:
|
||||
# Due to privacy concerns only a full username match will return the proper user entry
|
||||
qs = qs.filter(
|
||||
name__icontains=self.q
|
||||
)
|
||||
qs = qs.order_by(
|
||||
"username"
|
||||
)
|
||||
return qs
|
||||
|
||||
|
||||
class KonovaCodeAutocomplete(Select2GroupQuerySetView):
|
||||
"""
|
||||
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
|
||||
|
||||
"""
|
||||
paginate_by = 50
|
||||
|
||||
def order_by(self, qs):
|
||||
""" Orders by a predefined value
|
||||
|
||||
Wrapped in a function to provide inheritance-based different orders
|
||||
|
||||
Args:
|
||||
qs (QuerySet): The queryset to be ordered
|
||||
|
||||
Returns:
|
||||
qs (QuerySet): The ordered queryset
|
||||
"""
|
||||
return qs.order_by(
|
||||
"long_name"
|
||||
)
|
||||
|
||||
def get_queryset(self):
|
||||
if self.request.user.is_anonymous:
|
||||
return KonovaCode.objects.none()
|
||||
qs = KonovaCode.objects.filter(
|
||||
is_archived=False,
|
||||
is_selectable=True,
|
||||
is_leaf=True,
|
||||
)
|
||||
qs = self.order_by(qs)
|
||||
if self.c:
|
||||
qs = qs.filter(
|
||||
code_lists__in=[self.c]
|
||||
)
|
||||
if self.q:
|
||||
# Remove whitespaces from self.q and split input in all keywords (if multiple given)
|
||||
q = dict.fromkeys(self.q.strip().split(" "))
|
||||
# Create one filter looking up for all keys where all keywords can be found in the same result
|
||||
_filter = Q()
|
||||
for keyword in q:
|
||||
q_or = Q()
|
||||
q_or |= Q(long_name__icontains=keyword)
|
||||
q_or |= Q(short_name__icontains=keyword)
|
||||
q_or |= Q(parent__long_name__icontains=keyword)
|
||||
q_or |= Q(parent__short_name__icontains=keyword)
|
||||
q_or |= Q(parent__parent__long_name__icontains=keyword)
|
||||
q_or |= Q(parent__parent__short_name__icontains=keyword)
|
||||
_filter.add(q_or, Q.AND)
|
||||
qs = qs.filter(_filter).distinct()
|
||||
return qs
|
||||
|
||||
def get_result_label(self, result):
|
||||
return f"{result.long_name}"
|
||||
|
||||
def get_selected_result_label(self, result):
|
||||
return f"{result.__str__()}"
|
||||
|
||||
|
||||
class CompensationActionCodeAutocomplete(KonovaCodeAutocomplete):
|
||||
"""
|
||||
Due to limitations of the django dal package, we need to subclass for each code list
|
||||
"""
|
||||
group_by_related = "parent"
|
||||
related_field_name = "long_name"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.c = CODELIST_COMPENSATION_ACTION_ID
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def order_by(self, qs):
|
||||
return qs.order_by(
|
||||
"parent__long_name"
|
||||
)
|
||||
|
||||
|
||||
class CompensationActionDetailCodeAutocomplete(KonovaCodeAutocomplete):
|
||||
"""
|
||||
Due to limitations of the django dal package, we need to subclass for each code list
|
||||
"""
|
||||
group_by_related = "parent"
|
||||
related_field_name = "long_name"
|
||||
paginate_by = 200
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.c = CODELIST_COMPENSATION_ACTION_DETAIL_ID
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def order_by(self, qs):
|
||||
return qs.order_by(
|
||||
"long_name"
|
||||
)
|
||||
|
||||
|
||||
class BiotopeCodeAutocomplete(KonovaCodeAutocomplete):
|
||||
"""
|
||||
Due to limitations of the django dal package, we need to subclass for each code list
|
||||
"""
|
||||
group_by_related = "parent"
|
||||
related_field_name = "long_name"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.c = CODELIST_BIOTOPES_ID
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def order_by(self, qs):
|
||||
""" Orders by a predefined value
|
||||
|
||||
Wrapped in a function to provide inheritance-based different orders
|
||||
|
||||
Args:
|
||||
qs (QuerySet): The queryset to be ordered
|
||||
|
||||
Returns:
|
||||
qs (QuerySet): The ordered queryset
|
||||
"""
|
||||
return qs.order_by(
|
||||
"short_name",
|
||||
)
|
||||
|
||||
def get_result_label(self, result):
|
||||
return f"{result.long_name} ({result.short_name})"
|
||||
|
||||
def get_results(self, context):
|
||||
"""Return the options grouped by a common related model.
|
||||
|
||||
Raises ImproperlyConfigured if self.group_by_name is not configured
|
||||
"""
|
||||
if not self.group_by_related:
|
||||
raise ImproperlyConfigured("Missing group_by_related.")
|
||||
|
||||
super_groups = collections.OrderedDict()
|
||||
|
||||
object_list = context['object_list']
|
||||
|
||||
for result in object_list:
|
||||
group = result.parent if result.parent else None
|
||||
group_name = f"{group.long_name} ({group.short_name})" if group else UNGROUPED
|
||||
super_group = result.parent.parent if result.parent else None
|
||||
super_group_name = f"{super_group.long_name} ({super_group.short_name})" if super_group else UNGROUPED
|
||||
super_groups.setdefault(super_group_name, {})
|
||||
super_groups[super_group_name].setdefault(group_name, [])
|
||||
super_groups[super_group_name][group_name].append(result)
|
||||
|
||||
return [{
|
||||
'id': None,
|
||||
'text': super_group,
|
||||
'children': [{
|
||||
"id": None,
|
||||
"text": group,
|
||||
"children": [{
|
||||
'id': self.get_result_value(result),
|
||||
'text': self.get_result_label(result),
|
||||
'selected_text': self.get_selected_result_label(result),
|
||||
} for result in results]
|
||||
} for group, results in groups.items()]
|
||||
} for super_group, groups in super_groups.items()]
|
||||
|
||||
|
||||
class BiotopeExtraCodeAutocomplete(KonovaCodeAutocomplete):
|
||||
"""
|
||||
Due to limitations of the django dal package, we need to subclass for each code list
|
||||
"""
|
||||
group_by_related = "parent"
|
||||
related_field_name = "long_name"
|
||||
paginate_by = 200
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.c = CODELIST_BIOTOPES_EXTRA_CODES_ID
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def order_by(self, qs):
|
||||
""" Orders by a predefined value
|
||||
|
||||
Wrapped in a function to provide inheritance-based different orders
|
||||
|
||||
Args:
|
||||
qs (QuerySet): The queryset to be ordered
|
||||
|
||||
Returns:
|
||||
qs (QuerySet): The ordered queryset
|
||||
"""
|
||||
return qs.order_by(
|
||||
"long_name",
|
||||
)
|
||||
|
||||
def get_result_label(self, result):
|
||||
return f"{result.long_name} ({result.short_name})"
|
||||
|
||||
|
||||
class LawCodeAutocomplete(KonovaCodeAutocomplete):
|
||||
"""
|
||||
Due to limitations of the django dal package, we need to subclass for each code list
|
||||
"""
|
||||
group_by_related = "parent"
|
||||
related_field_name = "long_name"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.c = CODELIST_LAW_ID
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def get_result_label(self, result):
|
||||
return f"{result.long_name} ({result.short_name})"
|
||||
|
||||
|
||||
class ProcessTypeCodeAutocomplete(KonovaCodeAutocomplete):
|
||||
"""
|
||||
Due to limitations of the django dal package, we need to subclass for each code list
|
||||
"""
|
||||
group_by_related = "parent"
|
||||
related_field_name = "long_name"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.c = CODELIST_PROCESS_TYPE_ID
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
|
||||
class RegistrationOfficeCodeAutocomplete(KonovaCodeAutocomplete):
|
||||
"""
|
||||
Due to limitations of the django dal package, we need to subclass for each code list
|
||||
"""
|
||||
group_by_related = "parent"
|
||||
related_field_name = "long_name"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.c = CODELIST_REGISTRATION_OFFICE_ID
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def order_by(self, qs):
|
||||
return qs.order_by(
|
||||
"parent__long_name"
|
||||
)
|
||||
|
||||
|
||||
class ConservationOfficeCodeAutocomplete(KonovaCodeAutocomplete):
|
||||
"""
|
||||
Due to limitations of the django dal package, we need to subclass for each code list
|
||||
"""
|
||||
group_by_related = "parent"
|
||||
related_field_name = "long_name"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.c = CODELIST_CONSERVATION_OFFICE_ID
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def get_result_label(self, result):
|
||||
return f"{result.long_name} ({result.short_name})"
|
||||
|
||||
|
||||
class HandlerCodeAutocomplete(KonovaCodeAutocomplete):
|
||||
"""
|
||||
Due to limitations of the django dal package, we need to subclass for each code list
|
||||
"""
|
||||
group_by_related = "parent"
|
||||
related_field_name = "long_name"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.c = CODELIST_HANDLER_ID
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def get_result_label(self, result):
|
||||
return result.long_name
|
@ -1,414 +0,0 @@
|
||||
"""
|
||||
Author: Michel Peltriaux
|
||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||
Created on: 12.01.22
|
||||
|
||||
"""
|
||||
import django_filters
|
||||
from django import forms
|
||||
from django.db.models import QuerySet, Q
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from dal_select2.widgets import ModelSelect2
|
||||
|
||||
from codelist.models import KonovaCode
|
||||
from codelist.settings import CODELIST_CONSERVATION_OFFICE_ID, CODELIST_REGISTRATION_OFFICE_ID
|
||||
from intervention.inputs import DummyFilterInput
|
||||
from konova.models import Parcel, District
|
||||
|
||||
|
||||
class KeywordTableFilterMixin(django_filters.FilterSet):
|
||||
q = django_filters.Filter(
|
||||
method='filter_by_keyword',
|
||||
# Since we use a custom search bar in the template, we need to 'render' this filter
|
||||
# as 'anonymous' HiddenInput (no id, no name). This way our custom search bar's id and name won't be
|
||||
# overwritten with these id and name (which would be equal)
|
||||
# This way we can use the simple filter method mapping for a parameter without using a predefined widget!
|
||||
widget=DummyFilterInput(),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
def filter_by_keyword(self, queryset, name, value) -> QuerySet:
|
||||
""" Filters queryset depending on value of search bar input
|
||||
|
||||
Args:
|
||||
queryset ():
|
||||
name ():
|
||||
value ():
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
value = value.strip()
|
||||
# build filter expression
|
||||
q = Q(title__icontains=value) | Q(identifier__icontains=value)
|
||||
return queryset.filter(q)
|
||||
|
||||
|
||||
class FileNumberTableFilterMixin(django_filters.FilterSet):
|
||||
rf = django_filters.CharFilter(
|
||||
method="filter_file_number",
|
||||
label=_(""),
|
||||
label_suffix=_(""),
|
||||
widget=forms.TextInput(
|
||||
attrs={
|
||||
"placeholder": _("File number"),
|
||||
"title": _("Search for file number"),
|
||||
"class": "form-control",
|
||||
}
|
||||
),
|
||||
)
|
||||
|
||||
def filter_file_number(self, queryset, name, value) -> QuerySet:
|
||||
queryset = queryset.filter(
|
||||
Q(responsible__registration_file_number__icontains=value) |
|
||||
Q(responsible__conservation_file_number__icontains=value)
|
||||
)
|
||||
return queryset
|
||||
|
||||
|
||||
class GeoReferencedTableFilterMixin(django_filters.FilterSet):
|
||||
""" A mixin for AbstractTableFilter
|
||||
|
||||
Specialized on filtering GeoReferenced model types
|
||||
|
||||
"""
|
||||
# Parcel gmrkng
|
||||
di = django_filters.CharFilter(
|
||||
method="filter_district",
|
||||
label=_(""),
|
||||
label_suffix=_(""),
|
||||
widget=forms.TextInput(
|
||||
attrs={
|
||||
"placeholder": _("District"),
|
||||
"title": _("Search for district"),
|
||||
"class": "form-control",
|
||||
}
|
||||
),
|
||||
)
|
||||
# Parcel gmrkng
|
||||
pg = django_filters.CharFilter(
|
||||
method="filter_gmrkng",
|
||||
label=_(""),
|
||||
label_suffix=_(""),
|
||||
widget=forms.TextInput(
|
||||
attrs={
|
||||
"placeholder": _("Parcel gmrkng"),
|
||||
"title": _("Search for parcel gmrkng"),
|
||||
"class": "form-control",
|
||||
}
|
||||
),
|
||||
)
|
||||
# Parcel
|
||||
p = django_filters.CharFilter(
|
||||
method="filter_parcel",
|
||||
label=_(""),
|
||||
label_suffix=_(""),
|
||||
widget=forms.TextInput(
|
||||
attrs={
|
||||
"placeholder": _("Parcel"),
|
||||
"title": _("Search for parcel"),
|
||||
"class": "form-control",
|
||||
}
|
||||
),
|
||||
)
|
||||
# Parcel counter
|
||||
pc = django_filters.CharFilter(
|
||||
method="filter_parcel_counter",
|
||||
label=_(""),
|
||||
label_suffix=_(""),
|
||||
widget=forms.TextInput(
|
||||
attrs={
|
||||
"placeholder": _("Parcel counter"),
|
||||
"title": _("Search for parcel counter"),
|
||||
"class": "form-control",
|
||||
}
|
||||
),
|
||||
)
|
||||
|
||||
# Parcel counter
|
||||
pn = django_filters.CharFilter(
|
||||
method="filter_parcel_number",
|
||||
label=_(""),
|
||||
label_suffix=_(""),
|
||||
widget=forms.TextInput(
|
||||
attrs={
|
||||
"placeholder": _("Parcel number"),
|
||||
"title": _("Search for parcel number"),
|
||||
"class": "form-control",
|
||||
}
|
||||
),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
def _filter_parcel_reference(self, queryset, filter_q) -> QuerySet:
|
||||
""" Filters the parcel entries by a given filter_q
|
||||
|
||||
Args:
|
||||
queryset (QuerySet): The queryset
|
||||
filter_q (Q): The Q-style filter expression
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
matching_parcels = Parcel.objects.filter(
|
||||
filter_q
|
||||
)
|
||||
|
||||
related_geoms = matching_parcels.values(
|
||||
"geometries"
|
||||
).distinct()
|
||||
queryset = queryset.filter(
|
||||
geometry__id__in=related_geoms
|
||||
)
|
||||
return queryset
|
||||
|
||||
def filter_district(self, queryset, name, value) -> QuerySet:
|
||||
""" Filters queryset depending on value for 'Gemarkung'
|
||||
|
||||
Args:
|
||||
queryset ():
|
||||
name ():
|
||||
value ():
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
matching_districts = District.objects.filter(
|
||||
Q(name__icontains=value) |
|
||||
Q(key__icontains=value)
|
||||
).distinct()
|
||||
matching_parcels = Parcel.objects.filter(
|
||||
district__in=matching_districts
|
||||
)
|
||||
related_geoms = matching_parcels.values(
|
||||
"geometries"
|
||||
).distinct()
|
||||
queryset = queryset.filter(
|
||||
geometry__id__in=related_geoms
|
||||
)
|
||||
return queryset
|
||||
|
||||
def filter_gmrkng(self, queryset, name, value) -> QuerySet:
|
||||
""" Filters queryset depending on value for 'Gemarkung'
|
||||
|
||||
Args:
|
||||
queryset ():
|
||||
name ():
|
||||
value ():
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
queryset = self._filter_parcel_reference(
|
||||
queryset,
|
||||
Q(parcel_group__name__icontains=value) | Q(parcel_group__key__icontains=value),
|
||||
)
|
||||
return queryset
|
||||
|
||||
def filter_parcel(self, queryset, name, value) -> QuerySet:
|
||||
""" Filters queryset depending on value for 'Parcel'
|
||||
|
||||
Args:
|
||||
queryset ():
|
||||
name ():
|
||||
value ():
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
value = value.replace("-", "")
|
||||
queryset = self._filter_parcel_reference(
|
||||
queryset,
|
||||
Q(flr=value),
|
||||
)
|
||||
return queryset
|
||||
|
||||
def filter_parcel_counter(self, queryset, name, value) -> QuerySet:
|
||||
""" Filters queryset depending on value for 'Parcel'
|
||||
|
||||
Args:
|
||||
queryset ():
|
||||
name ():
|
||||
value ():
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
value = value.replace("-", "")
|
||||
queryset = self._filter_parcel_reference(
|
||||
queryset,
|
||||
Q(flrstck_zhlr=value)
|
||||
)
|
||||
return queryset
|
||||
|
||||
def filter_parcel_number(self, queryset, name, value) -> QuerySet:
|
||||
""" Filters queryset depending on value for 'Parcel'
|
||||
|
||||
Args:
|
||||
queryset ():
|
||||
name ():
|
||||
value ():
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
value = value.replace("-", "")
|
||||
queryset = self._filter_parcel_reference(
|
||||
queryset,
|
||||
Q(flrstck_nnr=value),
|
||||
)
|
||||
return queryset
|
||||
|
||||
|
||||
class ShareableTableFilterMixin(django_filters.FilterSet):
|
||||
""" A mixin for AbstractTableFilter
|
||||
|
||||
Specialized on filtering shareable model types
|
||||
|
||||
"""
|
||||
sa = django_filters.BooleanFilter(
|
||||
method='filter_show_all',
|
||||
label=_("Show unshared"),
|
||||
label_suffix=_(""),
|
||||
widget=forms.CheckboxInput(
|
||||
attrs={
|
||||
"class": "form-check-input",
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.user = kwargs.pop("user", None)
|
||||
if self.user is None:
|
||||
raise AttributeError("User must be set for further filtering!")
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def filter_show_all(self, queryset, name, value) -> QuerySet:
|
||||
""" Filters queryset depending on value of 'show_all' setting
|
||||
|
||||
Args:
|
||||
queryset ():
|
||||
name ():
|
||||
value ():
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
if not value:
|
||||
return queryset.filter(
|
||||
Q(users__in=[self.user]) | # requesting user has access
|
||||
Q(teams__in=self.user.shared_teams)
|
||||
).distinct()
|
||||
else:
|
||||
return queryset
|
||||
|
||||
|
||||
class RecordableTableFilterMixin(django_filters.FilterSet):
|
||||
""" A mixin for AbstractTableFilter
|
||||
|
||||
Specialized on filtering recordable model types
|
||||
|
||||
"""
|
||||
sr = django_filters.BooleanFilter(
|
||||
method='filter_show_recorded',
|
||||
label=_("Show recorded"),
|
||||
label_suffix=_(""),
|
||||
widget=forms.CheckboxInput(
|
||||
attrs={
|
||||
"class": "form-check-input",
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
def filter_show_recorded(self, queryset, name, value) -> QuerySet:
|
||||
""" Filters queryset depending on value of 'show_recorded' setting
|
||||
|
||||
Args:
|
||||
queryset ():
|
||||
name ():
|
||||
value ():
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
if not value:
|
||||
return queryset.filter(
|
||||
recorded=None,
|
||||
)
|
||||
else:
|
||||
return queryset
|
||||
|
||||
|
||||
class RegistrationOfficeTableFilterMixin(django_filters.FilterSet):
|
||||
""" A mixin for AbstractTableFilter
|
||||
|
||||
Specialized on filtering for related registration offices
|
||||
|
||||
"""
|
||||
ro = django_filters.ModelChoiceFilter(
|
||||
method="filter_reg_office",
|
||||
label=_(""),
|
||||
label_suffix=_(""),
|
||||
queryset=KonovaCode.objects.filter(
|
||||
is_archived=False,
|
||||
is_leaf=True,
|
||||
code_lists__in=[CODELIST_REGISTRATION_OFFICE_ID],
|
||||
),
|
||||
widget=ModelSelect2(
|
||||
url="codes-registration-office-autocomplete",
|
||||
attrs={
|
||||
"data-placeholder": _("Registration office"),
|
||||
"title": _("Search for registration office"),
|
||||
"class": "",
|
||||
}
|
||||
),
|
||||
)
|
||||
|
||||
def filter_reg_office(self, queryset, name, value):
|
||||
qs = queryset.filter(
|
||||
responsible__registration_office=value
|
||||
)
|
||||
return qs
|
||||
|
||||
|
||||
class ConservationOfficeTableFilterMixin(django_filters.FilterSet):
|
||||
""" A mixin for AbstractTableFilter
|
||||
|
||||
Specialized on filtering for related conservation offices
|
||||
|
||||
"""
|
||||
co = django_filters.ModelChoiceFilter(
|
||||
method="filter_cons_office",
|
||||
label=_(""),
|
||||
label_suffix=_(""),
|
||||
queryset=KonovaCode.objects.filter(
|
||||
is_archived=False,
|
||||
is_leaf=True,
|
||||
code_lists__in=[CODELIST_CONSERVATION_OFFICE_ID],
|
||||
),
|
||||
widget=ModelSelect2(
|
||||
url="codes-conservation-office-autocomplete",
|
||||
attrs={
|
||||
"data-placeholder": _("Conservation office"),
|
||||
"title": _("Search for conservation office"),
|
||||
"class": "",
|
||||
}
|
||||
),
|
||||
)
|
||||
|
||||
def filter_cons_office(self, queryset, name, value):
|
||||
qs = queryset.filter(
|
||||
responsible__conservation_office=value
|
||||
)
|
||||
return qs
|
7
konova/filters/mixins/__init__.py
Normal file
7
konova/filters/mixins/__init__.py
Normal file
@ -0,0 +1,7 @@
|
||||
"""
|
||||
Author: Michel Peltriaux
|
||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||
Contact: ksp-servicestelle@sgdnord.rlp.de
|
||||
Created on: 18.08.22
|
||||
|
||||
"""
|
33
konova/filters/mixins/file_number.py
Normal file
33
konova/filters/mixins/file_number.py
Normal file
@ -0,0 +1,33 @@
|
||||
"""
|
||||
Author: Michel Peltriaux
|
||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||
Contact: ksp-servicestelle@sgdnord.rlp.de
|
||||
Created on: 18.08.22
|
||||
|
||||
"""
|
||||
from django import forms
|
||||
from django.db.models import QuerySet, Q
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
import django_filters
|
||||
|
||||
|
||||
class FileNumberTableFilterMixin(django_filters.FilterSet):
|
||||
rf = django_filters.CharFilter(
|
||||
method="filter_file_number",
|
||||
label=_(""),
|
||||
label_suffix=_(""),
|
||||
widget=forms.TextInput(
|
||||
attrs={
|
||||
"placeholder": _("File number"),
|
||||
"title": _("Search for file number"),
|
||||
"class": "form-control",
|
||||
}
|
||||
),
|
||||
)
|
||||
|
||||
def filter_file_number(self, queryset, name, value) -> QuerySet:
|
||||
queryset = queryset.filter(
|
||||
Q(responsible__registration_file_number__icontains=value) |
|
||||
Q(responsible__conservation_file_number__icontains=value)
|
||||
)
|
||||
return queryset
|
209
konova/filters/mixins/geo_reference.py
Normal file
209
konova/filters/mixins/geo_reference.py
Normal file
@ -0,0 +1,209 @@
|
||||
"""
|
||||
Author: Michel Peltriaux
|
||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||
Contact: ksp-servicestelle@sgdnord.rlp.de
|
||||
Created on: 18.08.22
|
||||
|
||||
"""
|
||||
from django import forms
|
||||
from django.db.models import QuerySet, Q
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
import django_filters
|
||||
|
||||
from konova.models import Parcel, District
|
||||
|
||||
|
||||
class GeoReferencedTableFilterMixin(django_filters.FilterSet):
|
||||
""" A mixin for AbstractTableFilter
|
||||
|
||||
Specialized on filtering GeoReferenced model types
|
||||
|
||||
"""
|
||||
# Parcel gmrkng
|
||||
di = django_filters.CharFilter(
|
||||
method="filter_district",
|
||||
label=_(""),
|
||||
label_suffix=_(""),
|
||||
widget=forms.TextInput(
|
||||
attrs={
|
||||
"placeholder": _("District"),
|
||||
"title": _("Search for district"),
|
||||
"class": "form-control",
|
||||
}
|
||||
),
|
||||
)
|
||||
# Parcel gmrkng
|
||||
pg = django_filters.CharFilter(
|
||||
method="filter_gmrkng",
|
||||
label=_(""),
|
||||
label_suffix=_(""),
|
||||
widget=forms.TextInput(
|
||||
attrs={
|
||||
"placeholder": _("Parcel gmrkng"),
|
||||
"title": _("Search for parcel gmrkng"),
|
||||
"class": "form-control",
|
||||
}
|
||||
),
|
||||
)
|
||||
# Parcel
|
||||
p = django_filters.CharFilter(
|
||||
method="filter_parcel",
|
||||
label=_(""),
|
||||
label_suffix=_(""),
|
||||
widget=forms.TextInput(
|
||||
attrs={
|
||||
"placeholder": _("Parcel"),
|
||||
"title": _("Search for parcel"),
|
||||
"class": "form-control",
|
||||
}
|
||||
),
|
||||
)
|
||||
# Parcel counter
|
||||
pc = django_filters.CharFilter(
|
||||
method="filter_parcel_counter",
|
||||
label=_(""),
|
||||
label_suffix=_(""),
|
||||
widget=forms.TextInput(
|
||||
attrs={
|
||||
"placeholder": _("Parcel counter"),
|
||||
"title": _("Search for parcel counter"),
|
||||
"class": "form-control",
|
||||
}
|
||||
),
|
||||
)
|
||||
|
||||
# Parcel counter
|
||||
pn = django_filters.CharFilter(
|
||||
method="filter_parcel_number",
|
||||
label=_(""),
|
||||
label_suffix=_(""),
|
||||
widget=forms.TextInput(
|
||||
attrs={
|
||||
"placeholder": _("Parcel number"),
|
||||
"title": _("Search for parcel number"),
|
||||
"class": "form-control",
|
||||
}
|
||||
),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
def _filter_parcel_reference(self, queryset, filter_q) -> QuerySet:
|
||||
""" Filters the parcel entries by a given filter_q
|
||||
|
||||
Args:
|
||||
queryset (QuerySet): The queryset
|
||||
filter_q (Q): The Q-style filter expression
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
matching_parcels = Parcel.objects.filter(
|
||||
filter_q
|
||||
)
|
||||
|
||||
related_geoms = matching_parcels.values(
|
||||
"geometries"
|
||||
).distinct()
|
||||
queryset = queryset.filter(
|
||||
geometry__id__in=related_geoms
|
||||
)
|
||||
return queryset
|
||||
|
||||
def filter_district(self, queryset, name, value) -> QuerySet:
|
||||
""" Filters queryset depending on value for 'Gemarkung'
|
||||
|
||||
Args:
|
||||
queryset ():
|
||||
name ():
|
||||
value ():
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
matching_districts = District.objects.filter(
|
||||
Q(name__icontains=value) |
|
||||
Q(key__icontains=value)
|
||||
).distinct()
|
||||
matching_parcels = Parcel.objects.filter(
|
||||
district__in=matching_districts
|
||||
)
|
||||
related_geoms = matching_parcels.values(
|
||||
"geometries"
|
||||
).distinct()
|
||||
queryset = queryset.filter(
|
||||
geometry__id__in=related_geoms
|
||||
)
|
||||
return queryset
|
||||
|
||||
def filter_gmrkng(self, queryset, name, value) -> QuerySet:
|
||||
""" Filters queryset depending on value for 'Gemarkung'
|
||||
|
||||
Args:
|
||||
queryset ():
|
||||
name ():
|
||||
value ():
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
queryset = self._filter_parcel_reference(
|
||||
queryset,
|
||||
Q(parcel_group__name__icontains=value) | Q(parcel_group__key__icontains=value),
|
||||
)
|
||||
return queryset
|
||||
|
||||
def filter_parcel(self, queryset, name, value) -> QuerySet:
|
||||
""" Filters queryset depending on value for 'Parcel'
|
||||
|
||||
Args:
|
||||
queryset ():
|
||||
name ():
|
||||
value ():
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
value = value.replace("-", "")
|
||||
queryset = self._filter_parcel_reference(
|
||||
queryset,
|
||||
Q(flr=value),
|
||||
)
|
||||
return queryset
|
||||
|
||||
def filter_parcel_counter(self, queryset, name, value) -> QuerySet:
|
||||
""" Filters queryset depending on value for 'Parcel'
|
||||
|
||||
Args:
|
||||
queryset ():
|
||||
name ():
|
||||
value ():
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
value = value.replace("-", "")
|
||||
queryset = self._filter_parcel_reference(
|
||||
queryset,
|
||||
Q(flrstck_zhlr=value)
|
||||
)
|
||||
return queryset
|
||||
|
||||
def filter_parcel_number(self, queryset, name, value) -> QuerySet:
|
||||
""" Filters queryset depending on value for 'Parcel'
|
||||
|
||||
Args:
|
||||
queryset ():
|
||||
name ():
|
||||
value ():
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
value = value.replace("-", "")
|
||||
queryset = self._filter_parcel_reference(
|
||||
queryset,
|
||||
Q(flrstck_nnr=value),
|
||||
)
|
||||
return queryset
|
44
konova/filters/mixins/keyword.py
Normal file
44
konova/filters/mixins/keyword.py
Normal file
@ -0,0 +1,44 @@
|
||||
"""
|
||||
Author: Michel Peltriaux
|
||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||
Contact: ksp-servicestelle@sgdnord.rlp.de
|
||||
Created on: 18.08.22
|
||||
|
||||
"""
|
||||
import django_filters
|
||||
from django.db.models import QuerySet, Q
|
||||
|
||||
from intervention.inputs import DummyFilterInput
|
||||
|
||||
|
||||
class KeywordTableFilterMixin(django_filters.FilterSet):
|
||||
q = django_filters.Filter(
|
||||
method='filter_by_keyword',
|
||||
# Since we use a custom search bar in the template, we need to 'render' this filter
|
||||
# as 'anonymous' HiddenInput (no id, no name). This way our custom search bar's id and name won't be
|
||||
# overwritten with these id and name (which would be equal)
|
||||
# This way we can use the simple filter method mapping for a parameter without using a predefined widget!
|
||||
widget=DummyFilterInput(),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
def filter_by_keyword(self, queryset, name, value) -> QuerySet:
|
||||
""" Filters queryset depending on value of search bar input
|
||||
|
||||
Args:
|
||||
queryset ():
|
||||
name ():
|
||||
value ():
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
value = value.strip()
|
||||
value = value.split(" ")
|
||||
# build filter expression
|
||||
q = Q()
|
||||
for val in value:
|
||||
q &= Q(title__icontains=val) | Q(identifier__icontains=val)
|
||||
return queryset.filter(q)
|
77
konova/filters/mixins/office.py
Normal file
77
konova/filters/mixins/office.py
Normal file
@ -0,0 +1,77 @@
|
||||
"""
|
||||
Author: Michel Peltriaux
|
||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||
Contact: ksp-servicestelle@sgdnord.rlp.de
|
||||
Created on: 18.08.22
|
||||
|
||||
"""
|
||||
from dal_select2.widgets import ModelSelect2
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
import django_filters
|
||||
|
||||
from codelist.models import KonovaCode
|
||||
from codelist.settings import CODELIST_CONSERVATION_OFFICE_ID, CODELIST_REGISTRATION_OFFICE_ID
|
||||
|
||||
|
||||
class ConservationOfficeTableFilterMixin(django_filters.FilterSet):
|
||||
""" A mixin for AbstractTableFilter
|
||||
|
||||
Specialized on filtering for related conservation offices
|
||||
|
||||
"""
|
||||
co = django_filters.ModelChoiceFilter(
|
||||
method="filter_cons_office",
|
||||
label=_(""),
|
||||
label_suffix=_(""),
|
||||
queryset=KonovaCode.objects.filter(
|
||||
is_archived=False,
|
||||
is_leaf=True,
|
||||
code_lists__in=[CODELIST_CONSERVATION_OFFICE_ID],
|
||||
),
|
||||
widget=ModelSelect2(
|
||||
url="codelist:conservation-office-autocomplete",
|
||||
attrs={
|
||||
"data-placeholder": _("Conservation office"),
|
||||
"title": _("Search for conservation office"),
|
||||
"class": "",
|
||||
}
|
||||
),
|
||||
)
|
||||
|
||||
def filter_cons_office(self, queryset, name, value):
|
||||
qs = queryset.filter(
|
||||
responsible__conservation_office=value
|
||||
)
|
||||
return qs
|
||||
|
||||
|
||||
class RegistrationOfficeTableFilterMixin(django_filters.FilterSet):
|
||||
""" A mixin for AbstractTableFilter
|
||||
|
||||
Specialized on filtering for related registration offices
|
||||
|
||||
"""
|
||||
ro = django_filters.ModelChoiceFilter(
|
||||
method="filter_reg_office",
|
||||
label=_(""),
|
||||
label_suffix=_(""),
|
||||
queryset=KonovaCode.objects.filter(
|
||||
is_archived=False,
|
||||
is_leaf=True,
|
||||
code_lists__in=[CODELIST_REGISTRATION_OFFICE_ID],
|
||||
),
|
||||
widget=ModelSelect2(
|
||||
url="codelist:registration-office-autocomplete",
|
||||
attrs={
|
||||
"data-placeholder": _("Registration office"),
|
||||
"title": _("Search for registration office"),
|
||||
"class": "",
|
||||
}
|
||||
),
|
||||
)
|
||||
|
||||
def filter_reg_office(self, queryset, name, value):
|
||||
qs = queryset.filter(
|
||||
responsible__registration_office=value
|
||||
)
|
||||
return qs
|
51
konova/filters/mixins/record.py
Normal file
51
konova/filters/mixins/record.py
Normal file
@ -0,0 +1,51 @@
|
||||
"""
|
||||
Author: Michel Peltriaux
|
||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||
Contact: ksp-servicestelle@sgdnord.rlp.de
|
||||
Created on: 18.08.22
|
||||
|
||||
"""
|
||||
from django import forms
|
||||
from django.db.models import QuerySet
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
import django_filters
|
||||
|
||||
|
||||
class RecordableTableFilterMixin(django_filters.FilterSet):
|
||||
""" A mixin for AbstractTableFilter
|
||||
|
||||
Specialized on filtering recordable model types
|
||||
|
||||
"""
|
||||
sr = django_filters.BooleanFilter(
|
||||
method='filter_show_recorded',
|
||||
label=_("Show recorded"),
|
||||
label_suffix=_(""),
|
||||
widget=forms.CheckboxInput(
|
||||
attrs={
|
||||
"class": "form-check-input",
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
def filter_show_recorded(self, queryset, name, value) -> QuerySet:
|
||||
""" Filters queryset depending on value of 'show_recorded' setting
|
||||
|
||||
Args:
|
||||
queryset ():
|
||||
name ():
|
||||
value ():
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
if not value:
|
||||
return queryset.filter(
|
||||
recorded=None,
|
||||
)
|
||||
else:
|
||||
return queryset
|
||||
|
57
konova/filters/mixins/share.py
Normal file
57
konova/filters/mixins/share.py
Normal file
@ -0,0 +1,57 @@
|
||||
"""
|
||||
Author: Michel Peltriaux
|
||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||
Contact: ksp-servicestelle@sgdnord.rlp.de
|
||||
Created on: 18.08.22
|
||||
|
||||
"""
|
||||
from django import forms
|
||||
from django.db.models import Q, QuerySet
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
import django_filters
|
||||
|
||||
|
||||
class ShareableTableFilterMixin(django_filters.FilterSet):
|
||||
""" A mixin for AbstractTableFilter
|
||||
|
||||
Specialized on filtering shareable model types
|
||||
|
||||
"""
|
||||
sa = django_filters.BooleanFilter(
|
||||
method='filter_show_all',
|
||||
label=_("Show unshared"),
|
||||
label_suffix=_(""),
|
||||
widget=forms.CheckboxInput(
|
||||
attrs={
|
||||
"class": "form-check-input",
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.user = kwargs.pop("user", None)
|
||||
if self.user is None:
|
||||
raise AttributeError("User must be set for further filtering!")
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def filter_show_all(self, queryset, name, value) -> QuerySet:
|
||||
""" Filters queryset depending on value of 'show_all' setting
|
||||
|
||||
Args:
|
||||
queryset ():
|
||||
name ():
|
||||
value ():
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
if not value:
|
||||
return queryset.filter(
|
||||
Q(users__in=[self.user]) | # requesting user has access
|
||||
Q(teams__in=self.user.shared_teams)
|
||||
).distinct()
|
||||
else:
|
||||
return queryset
|
@ -7,9 +7,12 @@ Created on: 12.01.22
|
||||
"""
|
||||
import django_filters
|
||||
|
||||
from konova.filters.mixins import RegistrationOfficeTableFilterMixin, ConservationOfficeTableFilterMixin, \
|
||||
KeywordTableFilterMixin, FileNumberTableFilterMixin, GeoReferencedTableFilterMixin, ShareableTableFilterMixin, \
|
||||
RecordableTableFilterMixin
|
||||
from konova.filters.mixins.file_number import FileNumberTableFilterMixin
|
||||
from konova.filters.mixins.geo_reference import GeoReferencedTableFilterMixin
|
||||
from konova.filters.mixins.keyword import KeywordTableFilterMixin
|
||||
from konova.filters.mixins.office import ConservationOfficeTableFilterMixin, RegistrationOfficeTableFilterMixin
|
||||
from konova.filters.mixins.record import RecordableTableFilterMixin
|
||||
from konova.filters.mixins.share import ShareableTableFilterMixin
|
||||
|
||||
|
||||
class AbstractTableFilter(django_filters.FilterSet):
|
||||
|
@ -21,7 +21,7 @@ class AutocompleteTestCase(BaseTestCase):
|
||||
|
||||
def test_user_autocomplete(self):
|
||||
self.client.login(username=self.superuser.username, password=self.superuser_pw)
|
||||
user_autocomplete_url = reverse("share-user-autocomplete")
|
||||
user_autocomplete_url = reverse("user:share-user-autocomplete")
|
||||
username = self.user.username
|
||||
|
||||
# Provide the full name --> success
|
||||
@ -60,19 +60,19 @@ class AutocompleteTestCase(BaseTestCase):
|
||||
|
||||
def test_all_autocompletes(self):
|
||||
tests = [
|
||||
"accounts-autocomplete",
|
||||
"interventions-autocomplete",
|
||||
"codes-compensation-action-autocomplete",
|
||||
"codes-compensation-action-detail-autocomplete",
|
||||
"codes-biotope-autocomplete",
|
||||
"codes-biotope-extra-type-autocomplete",
|
||||
"codes-law-autocomplete",
|
||||
"codes-process-type-autocomplete",
|
||||
"codes-registration-office-autocomplete",
|
||||
"codes-conservation-office-autocomplete",
|
||||
"share-user-autocomplete",
|
||||
"share-team-autocomplete",
|
||||
"team-admin-autocomplete",
|
||||
"compensation:acc:autocomplete",
|
||||
"intervention:autocomplete",
|
||||
"codelist:compensation-action-autocomplete",
|
||||
"codelist:compensation-action-detail-autocomplete",
|
||||
"codelist:biotope-autocomplete",
|
||||
"codelist:biotope-extra-type-autocomplete",
|
||||
"codelist:law-autocomplete",
|
||||
"codelist:process-type-autocomplete",
|
||||
"codelist:registration-office-autocomplete",
|
||||
"codelist:conservation-office-autocomplete",
|
||||
"user:share-user-autocomplete",
|
||||
"user:share-team-autocomplete",
|
||||
"user:team-admin-autocomplete",
|
||||
]
|
||||
for test in tests:
|
||||
self.client.login(username=self.superuser.username, password=self.superuser_pw)
|
||||
|
@ -590,15 +590,15 @@ class AutocompleteTestCase(BaseViewTestCase):
|
||||
@classmethod
|
||||
def setUpTestData(cls) -> None:
|
||||
super().setUpTestData()
|
||||
cls.atcmplt_accs = reverse("accounts-autocomplete")
|
||||
cls.atcmplt_interventions = reverse("interventions-autocomplete")
|
||||
cls.atcmplt_code_comp_action = reverse("codes-compensation-action-autocomplete")
|
||||
cls.atcmplt_code_comp_biotope = reverse("codes-biotope-autocomplete")
|
||||
cls.atcmplt_code_comp_law = reverse("codes-law-autocomplete")
|
||||
cls.atcmplt_code_comp_process = reverse("codes-process-type-autocomplete")
|
||||
cls.atcmplt_code_comp_reg_off = reverse("codes-registration-office-autocomplete")
|
||||
cls.atcmplt_code_comp_cons_off = reverse("codes-conservation-office-autocomplete")
|
||||
cls.atcmplt_code_share_user = reverse("share-user-autocomplete")
|
||||
cls.atcmplt_accs = reverse("compensation:acc:autocomplete")
|
||||
cls.atcmplt_interventions = reverse("intervention:autocomplete")
|
||||
cls.atcmplt_code_comp_action = reverse("codelist:compensation-action-autocomplete")
|
||||
cls.atcmplt_code_comp_biotope = reverse("codelist:biotope-autocomplete")
|
||||
cls.atcmplt_code_comp_law = reverse("codelist:law-autocomplete")
|
||||
cls.atcmplt_code_comp_process = reverse("codelist:process-type-autocomplete")
|
||||
cls.atcmplt_code_comp_reg_off = reverse("codelist:registration-office-autocomplete")
|
||||
cls.atcmplt_code_comp_cons_off = reverse("codelist:conservation-office-autocomplete")
|
||||
cls.atcmplt_code_share_user = reverse("user:share-user-autocomplete")
|
||||
|
||||
def _test_views_anonymous_user(self):
|
||||
# ATTENTION: As of the current state of django-autocomplete-light, there is no way to check on authenticated
|
||||
|
@ -17,11 +17,6 @@ import debug_toolbar
|
||||
from django.contrib import admin
|
||||
from django.urls import path, include
|
||||
|
||||
from konova.autocompletes import EcoAccountAutocomplete, \
|
||||
InterventionAutocomplete, CompensationActionCodeAutocomplete, BiotopeCodeAutocomplete, LawCodeAutocomplete, \
|
||||
RegistrationOfficeCodeAutocomplete, ConservationOfficeCodeAutocomplete, ProcessTypeCodeAutocomplete, \
|
||||
ShareUserAutocomplete, BiotopeExtraCodeAutocomplete, CompensationActionDetailCodeAutocomplete, \
|
||||
ShareTeamAutocomplete, HandlerCodeAutocomplete, TeamAdminAutocomplete
|
||||
from konova.settings import SSO_SERVER, SSO_PUBLIC_KEY, SSO_PRIVATE_KEY, DEBUG
|
||||
from konova.sso.sso import KonovaSSOClient
|
||||
from konova.views import logout_view, home_view, get_geom_parcels, get_geom_parcels_content, map_client_proxy_view
|
||||
@ -43,22 +38,6 @@ urlpatterns = [
|
||||
path('geom/<id>/parcels/', get_geom_parcels, name="geometry-parcels"),
|
||||
path('geom/<id>/parcels/<int:page>', get_geom_parcels_content, name="geometry-parcels-content"),
|
||||
path('client/proxy', map_client_proxy_view, name="map-client-proxy"),
|
||||
|
||||
# Autocomplete paths for all apps
|
||||
path("atcmplt/eco-accounts", EcoAccountAutocomplete.as_view(), name="accounts-autocomplete"),
|
||||
path("atcmplt/interventions", InterventionAutocomplete.as_view(), name="interventions-autocomplete"),
|
||||
path("atcmplt/codes/comp/action", CompensationActionCodeAutocomplete.as_view(), name="codes-compensation-action-autocomplete"),
|
||||
path("atcmplt/codes/comp/action/detail", CompensationActionDetailCodeAutocomplete.as_view(), name="codes-compensation-action-detail-autocomplete"),
|
||||
path("atcmplt/codes/biotope", BiotopeCodeAutocomplete.as_view(), name="codes-biotope-autocomplete"),
|
||||
path("atcmplt/codes/biotope/extra", BiotopeExtraCodeAutocomplete.as_view(), name="codes-biotope-extra-type-autocomplete"),
|
||||
path("atcmplt/codes/law", LawCodeAutocomplete.as_view(), name="codes-law-autocomplete"),
|
||||
path("atcmplt/codes/prc-type", ProcessTypeCodeAutocomplete.as_view(), name="codes-process-type-autocomplete"),
|
||||
path("atcmplt/codes/reg-off", RegistrationOfficeCodeAutocomplete.as_view(), name="codes-registration-office-autocomplete"),
|
||||
path("atcmplt/codes/cons-off", ConservationOfficeCodeAutocomplete.as_view(), name="codes-conservation-office-autocomplete"),
|
||||
path("atcmplt/codes/handler", HandlerCodeAutocomplete.as_view(), name="codes-handler-autocomplete"),
|
||||
path("atcmplt/share/u", ShareUserAutocomplete.as_view(), name="share-user-autocomplete"),
|
||||
path("atcmplt/share/t", ShareTeamAutocomplete.as_view(), name="share-team-autocomplete"),
|
||||
path("atcmplt/team/admin", TeamAdminAutocomplete.as_view(), name="team-admin-autocomplete"),
|
||||
]
|
||||
|
||||
if DEBUG:
|
||||
|
7
user/autocomplete/__init__.py
Normal file
7
user/autocomplete/__init__.py
Normal file
@ -0,0 +1,7 @@
|
||||
"""
|
||||
Author: Michel Peltriaux
|
||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||
Contact: ksp-servicestelle@sgdnord.rlp.de
|
||||
Created on: 18.08.22
|
||||
|
||||
"""
|
52
user/autocomplete/share.py
Normal file
52
user/autocomplete/share.py
Normal file
@ -0,0 +1,52 @@
|
||||
"""
|
||||
Author: Michel Peltriaux
|
||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||
Contact: ksp-servicestelle@sgdnord.rlp.de
|
||||
Created on: 18.08.22
|
||||
|
||||
"""
|
||||
from dal_select2.views import Select2QuerySetView
|
||||
from django.db.models import Q
|
||||
|
||||
from user.models import User, Team
|
||||
|
||||
|
||||
class ShareUserAutocomplete(Select2QuerySetView):
|
||||
""" Autocomplete for share with single users
|
||||
|
||||
|
||||
"""
|
||||
def get_queryset(self):
|
||||
if self.request.user.is_anonymous:
|
||||
return User.objects.none()
|
||||
qs = User.objects.all()
|
||||
if self.q:
|
||||
# Due to privacy concerns only a full username match will return the proper user entry
|
||||
qs = qs.filter(
|
||||
Q(username=self.q) |
|
||||
Q(email=self.q)
|
||||
).distinct()
|
||||
qs = qs.order_by("username")
|
||||
return qs
|
||||
|
||||
|
||||
class ShareTeamAutocomplete(Select2QuerySetView):
|
||||
""" Autocomplete for share with teams
|
||||
|
||||
"""
|
||||
def get_queryset(self):
|
||||
if self.request.user.is_anonymous:
|
||||
return Team.objects.none()
|
||||
qs = Team.objects.filter(
|
||||
deleted__isnull=True
|
||||
)
|
||||
if self.q:
|
||||
# Due to privacy concerns only a full username match will return the proper user entry
|
||||
qs = qs.filter(
|
||||
name__icontains=self.q
|
||||
)
|
||||
qs = qs.order_by(
|
||||
"name"
|
||||
)
|
||||
return qs
|
||||
|
34
user/autocomplete/team.py
Normal file
34
user/autocomplete/team.py
Normal file
@ -0,0 +1,34 @@
|
||||
"""
|
||||
Author: Michel Peltriaux
|
||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||
Contact: ksp-servicestelle@sgdnord.rlp.de
|
||||
Created on: 18.08.22
|
||||
|
||||
"""
|
||||
from dal_select2.views import Select2QuerySetView
|
||||
|
||||
from user.models import User
|
||||
|
||||
|
||||
class TeamAdminAutocomplete(Select2QuerySetView):
|
||||
""" Autocomplete for share with teams
|
||||
|
||||
"""
|
||||
def get_queryset(self):
|
||||
if self.request.user.is_anonymous:
|
||||
return User.objects.none()
|
||||
qs = User.objects.filter(
|
||||
id__in=self.forwarded.get("members", [])
|
||||
).exclude(
|
||||
id__in=self.forwarded.get("admins", [])
|
||||
)
|
||||
if self.q:
|
||||
# Due to privacy concerns only a full username match will return the proper user entry
|
||||
qs = qs.filter(
|
||||
name__icontains=self.q
|
||||
)
|
||||
qs = qs.order_by(
|
||||
"username"
|
||||
)
|
||||
return qs
|
||||
|
7
user/forms/__init__.py
Normal file
7
user/forms/__init__.py
Normal file
@ -0,0 +1,7 @@
|
||||
"""
|
||||
Author: Michel Peltriaux
|
||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||
Contact: ksp-servicestelle@sgdnord.rlp.de
|
||||
Created on: 18.08.22
|
||||
|
||||
"""
|
7
user/forms/modals/__init__.py
Normal file
7
user/forms/modals/__init__.py
Normal file
@ -0,0 +1,7 @@
|
||||
"""
|
||||
Author: Michel Peltriaux
|
||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||
Contact: ksp-servicestelle@sgdnord.rlp.de
|
||||
Created on: 18.08.22
|
||||
|
||||
"""
|
@ -1,166 +1,17 @@
|
||||
"""
|
||||
Author: Michel Peltriaux
|
||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||
Created on: 08.07.21
|
||||
Contact: ksp-servicestelle@sgdnord.rlp.de
|
||||
Created on: 18.08.22
|
||||
|
||||
"""
|
||||
from dal import autocomplete
|
||||
from django import forms
|
||||
from django.db import transaction
|
||||
from django.urls import reverse, reverse_lazy
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from api.models import APIUserToken
|
||||
from intervention.inputs import GenerateInput
|
||||
from user.models import User, UserNotification, Team
|
||||
|
||||
from konova.forms.modals import BaseModalForm, RemoveModalForm
|
||||
from konova.forms import BaseForm
|
||||
|
||||
|
||||
class UserNotificationForm(BaseForm):
|
||||
""" Form for changing the notification settings of a user
|
||||
|
||||
"""
|
||||
notifications = forms.MultipleChoiceField(
|
||||
label_suffix="",
|
||||
label=_("Notifications"),
|
||||
required=False, # allow total disabling of all notifications
|
||||
help_text=_("Select the situations when you want to receive a notification"),
|
||||
widget=forms.CheckboxSelectMultiple(
|
||||
attrs={
|
||||
"class": "list-unstyled",
|
||||
}
|
||||
),
|
||||
choices=[]
|
||||
)
|
||||
|
||||
def __init__(self, user: User, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.user = user
|
||||
self.form_title = _("Edit notifications")
|
||||
self.form_caption = _("")
|
||||
self.action_url = reverse("user:notifications")
|
||||
self.cancel_redirect = reverse("user:index")
|
||||
|
||||
# Insert all notifications into form field by creating choices as tuples
|
||||
notifications = UserNotification.objects.filter(
|
||||
is_active=True,
|
||||
)
|
||||
choices = []
|
||||
for n in notifications:
|
||||
choices.append(
|
||||
(n.id, _(n.name))
|
||||
)
|
||||
self.fields["notifications"].choices = choices
|
||||
|
||||
users_current_notifications = self.user.notifications.all()
|
||||
users_current_notifications = [str(n.id) for n in users_current_notifications]
|
||||
self.fields["notifications"].initial = users_current_notifications
|
||||
|
||||
def save(self):
|
||||
""" Stores the changes in the user konova_extension
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
selected_notification_ids = self.cleaned_data.get("notifications", [])
|
||||
notifications = UserNotification.objects.filter(
|
||||
id__in=selected_notification_ids,
|
||||
)
|
||||
self.user.notifications.set(notifications)
|
||||
|
||||
|
||||
class UserContactForm(BaseModalForm):
|
||||
name = forms.CharField(
|
||||
label=_("Username"),
|
||||
label_suffix="",
|
||||
required=False,
|
||||
widget=forms.TextInput(
|
||||
attrs={
|
||||
"readonly": True,
|
||||
"class": "form-control",
|
||||
}
|
||||
),
|
||||
)
|
||||
person_name = forms.CharField(
|
||||
label=_("Person name"),
|
||||
label_suffix="",
|
||||
required=False,
|
||||
widget=forms.TextInput(
|
||||
attrs={
|
||||
"readonly": True,
|
||||
"class": "form-control",
|
||||
}
|
||||
),
|
||||
)
|
||||
mail = forms.EmailField(
|
||||
label=_("E-Mail"),
|
||||
label_suffix="",
|
||||
required=False,
|
||||
widget=forms.TextInput(
|
||||
attrs={
|
||||
"readonly": True,
|
||||
"class": "form-control",
|
||||
}
|
||||
),
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.render_submit = False
|
||||
self.form_title = _("User contact data")
|
||||
self.form_caption = ""
|
||||
|
||||
self.initialize_form_field("name", self.instance.username)
|
||||
self.initialize_form_field("person_name", "{} {}".format(self.instance.first_name, self.instance.last_name))
|
||||
self.initialize_form_field("mail", self.instance.email)
|
||||
|
||||
|
||||
class UserAPITokenForm(BaseForm):
|
||||
token = forms.CharField(
|
||||
label=_("Token"),
|
||||
label_suffix="",
|
||||
max_length=255,
|
||||
required=True,
|
||||
help_text=_("Generated automatically"),
|
||||
widget=GenerateInput(
|
||||
attrs={
|
||||
"class": "form-control",
|
||||
"url": reverse_lazy("api:generate-new-token"),
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.form_title = _("Create new token")
|
||||
self.form_caption = _("A new token needs to be validated by an administrator!")
|
||||
|
||||
self.action_url = reverse("user:api-token")
|
||||
self.cancel_redirect = reverse("user:index")
|
||||
|
||||
# Make direct token editing by user impossible. Instead set the proper url for generating a new token
|
||||
self.initialize_form_field("token", None)
|
||||
self.fields["token"].widget.attrs["readonly"] = True
|
||||
|
||||
def save(self):
|
||||
""" Saves the form data
|
||||
|
||||
Returns:
|
||||
api_token (APIUserToken)
|
||||
"""
|
||||
user = self.instance
|
||||
new_token = self.cleaned_data["token"]
|
||||
if user.api_token is not None:
|
||||
user.api_token.delete()
|
||||
new_token = APIUserToken.objects.create(
|
||||
token=new_token
|
||||
)
|
||||
user.api_token = new_token
|
||||
user.save()
|
||||
return new_token
|
||||
from user.models import User, Team
|
||||
|
||||
|
||||
class NewTeamModalForm(BaseModalForm):
|
||||
@ -192,7 +43,7 @@ class NewTeamModalForm(BaseModalForm):
|
||||
required=True,
|
||||
queryset=User.objects.all(),
|
||||
widget=autocomplete.ModelSelect2Multiple(
|
||||
url="share-user-autocomplete",
|
||||
url="user:share-user-autocomplete",
|
||||
attrs={
|
||||
"data-placeholder": _("Click for selection"),
|
||||
"data-minimum-input-length": 3,
|
||||
@ -252,7 +103,7 @@ class EditTeamModalForm(NewTeamModalForm):
|
||||
required=True,
|
||||
queryset=User.objects.all(),
|
||||
widget=autocomplete.ModelSelect2Multiple(
|
||||
url="team-admin-autocomplete",
|
||||
url="user:team-admin-autocomplete",
|
||||
forward=[
|
||||
"members",
|
||||
"admins",
|
||||
@ -347,46 +198,3 @@ class LeaveTeamModalForm(RemoveModalForm):
|
||||
|
||||
def save(self):
|
||||
self.instance.remove_user(self.user)
|
||||
|
||||
|
||||
class TeamDataForm(BaseModalForm):
|
||||
name = forms.CharField(
|
||||
label_suffix="",
|
||||
label=_("Team name"),
|
||||
max_length=500,
|
||||
required=False,
|
||||
widget=forms.TextInput(
|
||||
attrs={
|
||||
"placeholder": _("Team name"),
|
||||
"class": "form-control",
|
||||
}
|
||||
)
|
||||
)
|
||||
description = forms.CharField(
|
||||
label_suffix="",
|
||||
required=False,
|
||||
label=_("Description"),
|
||||
widget=forms.Textarea(
|
||||
attrs={
|
||||
"rows": 5,
|
||||
"class": "form-control"
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.form_title = _("Team")
|
||||
self.form_caption = ""
|
||||
self.render_submit = False
|
||||
form_data = {
|
||||
"name": self.instance.name,
|
||||
"description": self.instance.description,
|
||||
}
|
||||
self.load_initial_data(
|
||||
form_data,
|
||||
[
|
||||
"name",
|
||||
"description"
|
||||
]
|
||||
)
|
62
user/forms/modals/user.py
Normal file
62
user/forms/modals/user.py
Normal file
@ -0,0 +1,62 @@
|
||||
"""
|
||||
Author: Michel Peltriaux
|
||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||
Contact: ksp-servicestelle@sgdnord.rlp.de
|
||||
Created on: 18.08.22
|
||||
|
||||
"""
|
||||
from django import forms
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from konova.forms.modals import BaseModalForm
|
||||
|
||||
|
||||
class UserContactForm(BaseModalForm):
|
||||
def save(self):
|
||||
# Readonly form. No saving needed
|
||||
pass
|
||||
|
||||
name = forms.CharField(
|
||||
label=_("Username"),
|
||||
label_suffix="",
|
||||
required=False,
|
||||
widget=forms.TextInput(
|
||||
attrs={
|
||||
"readonly": True,
|
||||
"class": "form-control",
|
||||
}
|
||||
),
|
||||
)
|
||||
person_name = forms.CharField(
|
||||
label=_("Person name"),
|
||||
label_suffix="",
|
||||
required=False,
|
||||
widget=forms.TextInput(
|
||||
attrs={
|
||||
"readonly": True,
|
||||
"class": "form-control",
|
||||
}
|
||||
),
|
||||
)
|
||||
mail = forms.EmailField(
|
||||
label=_("E-Mail"),
|
||||
label_suffix="",
|
||||
required=False,
|
||||
widget=forms.TextInput(
|
||||
attrs={
|
||||
"readonly": True,
|
||||
"class": "form-control",
|
||||
}
|
||||
),
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.render_submit = False
|
||||
self.form_title = _("User contact data")
|
||||
self.form_caption = ""
|
||||
|
||||
self.initialize_form_field("name", self.instance.username)
|
||||
self.initialize_form_field("person_name", "{} {}".format(self.instance.first_name, self.instance.last_name))
|
||||
self.initialize_form_field("mail", self.instance.email)
|
||||
|
54
user/forms/team.py
Normal file
54
user/forms/team.py
Normal file
@ -0,0 +1,54 @@
|
||||
"""
|
||||
Author: Michel Peltriaux
|
||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||
Contact: ksp-servicestelle@sgdnord.rlp.de
|
||||
Created on: 18.08.22
|
||||
|
||||
"""
|
||||
from django import forms
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from konova.forms.modals import BaseModalForm
|
||||
|
||||
|
||||
class TeamDataForm(BaseModalForm):
|
||||
name = forms.CharField(
|
||||
label_suffix="",
|
||||
label=_("Team name"),
|
||||
max_length=500,
|
||||
required=False,
|
||||
widget=forms.TextInput(
|
||||
attrs={
|
||||
"placeholder": _("Team name"),
|
||||
"class": "form-control",
|
||||
}
|
||||
)
|
||||
)
|
||||
description = forms.CharField(
|
||||
label_suffix="",
|
||||
required=False,
|
||||
label=_("Description"),
|
||||
widget=forms.Textarea(
|
||||
attrs={
|
||||
"rows": 5,
|
||||
"class": "form-control"
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.form_title = _("Team")
|
||||
self.form_caption = ""
|
||||
self.render_submit = False
|
||||
form_data = {
|
||||
"name": self.instance.name,
|
||||
"description": self.instance.description,
|
||||
}
|
||||
self.load_initial_data(
|
||||
form_data,
|
||||
[
|
||||
"name",
|
||||
"description"
|
||||
]
|
||||
)
|
113
user/forms/user.py
Normal file
113
user/forms/user.py
Normal file
@ -0,0 +1,113 @@
|
||||
"""
|
||||
Author: Michel Peltriaux
|
||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||
Contact: ksp-servicestelle@sgdnord.rlp.de
|
||||
Created on: 18.08.22
|
||||
|
||||
"""
|
||||
from django import forms
|
||||
from django.urls import reverse, reverse_lazy
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from api.models import APIUserToken
|
||||
from intervention.inputs import GenerateInput
|
||||
from konova.forms import BaseForm
|
||||
from user.models import User, UserNotification
|
||||
|
||||
|
||||
class UserNotificationForm(BaseForm):
|
||||
""" Form for changing the notification settings of a user
|
||||
|
||||
"""
|
||||
notifications = forms.MultipleChoiceField(
|
||||
label_suffix="",
|
||||
label=_("Notifications"),
|
||||
required=False, # allow total disabling of all notifications
|
||||
help_text=_("Select the situations when you want to receive a notification"),
|
||||
widget=forms.CheckboxSelectMultiple(
|
||||
attrs={
|
||||
"class": "list-unstyled",
|
||||
}
|
||||
),
|
||||
choices=[]
|
||||
)
|
||||
|
||||
def __init__(self, user: User, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.user = user
|
||||
self.form_title = _("Edit notifications")
|
||||
self.form_caption = _("")
|
||||
self.action_url = reverse("user:notifications")
|
||||
self.cancel_redirect = reverse("user:index")
|
||||
|
||||
# Insert all notifications into form field by creating choices as tuples
|
||||
notifications = UserNotification.objects.filter(
|
||||
is_active=True,
|
||||
)
|
||||
choices = []
|
||||
for n in notifications:
|
||||
choices.append(
|
||||
(n.id, _(n.name))
|
||||
)
|
||||
self.fields["notifications"].choices = choices
|
||||
|
||||
users_current_notifications = self.user.notifications.all()
|
||||
users_current_notifications = [str(n.id) for n in users_current_notifications]
|
||||
self.fields["notifications"].initial = users_current_notifications
|
||||
|
||||
def save(self):
|
||||
""" Stores the changes in the user konova_extension
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
selected_notification_ids = self.cleaned_data.get("notifications", [])
|
||||
notifications = UserNotification.objects.filter(
|
||||
id__in=selected_notification_ids,
|
||||
)
|
||||
self.user.notifications.set(notifications)
|
||||
|
||||
|
||||
class UserAPITokenForm(BaseForm):
|
||||
token = forms.CharField(
|
||||
label=_("Token"),
|
||||
label_suffix="",
|
||||
max_length=255,
|
||||
required=True,
|
||||
help_text=_("Generated automatically"),
|
||||
widget=GenerateInput(
|
||||
attrs={
|
||||
"class": "form-control",
|
||||
"url": reverse_lazy("api:generate-new-token"),
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.form_title = _("Create new token")
|
||||
self.form_caption = _("A new token needs to be validated by an administrator!")
|
||||
|
||||
self.action_url = reverse("user:api-token")
|
||||
self.cancel_redirect = reverse("user:index")
|
||||
|
||||
# Make direct token editing by user impossible. Instead set the proper url for generating a new token
|
||||
self.initialize_form_field("token", None)
|
||||
self.fields["token"].widget.attrs["readonly"] = True
|
||||
|
||||
def save(self):
|
||||
""" Saves the form data
|
||||
|
||||
Returns:
|
||||
api_token (APIUserToken)
|
||||
"""
|
||||
user = self.instance
|
||||
new_token = self.cleaned_data["token"]
|
||||
if user.api_token is not None:
|
||||
user.api_token.delete()
|
||||
new_token = APIUserToken.objects.create(
|
||||
token=new_token
|
||||
)
|
||||
user.api_token = new_token
|
||||
user.save()
|
||||
return new_token
|
@ -7,6 +7,8 @@ Created on: 08.07.21
|
||||
"""
|
||||
from django.urls import path
|
||||
|
||||
from user.autocomplete.share import ShareUserAutocomplete, ShareTeamAutocomplete
|
||||
from user.autocomplete.team import TeamAdminAutocomplete
|
||||
from user.views import *
|
||||
|
||||
app_name = "user"
|
||||
@ -22,4 +24,8 @@ urlpatterns = [
|
||||
path("team/<id>/remove", remove_team_view, name="team-remove"),
|
||||
path("team/<id>/leave", leave_team_view, name="team-leave"),
|
||||
|
||||
# Autocomplete urls
|
||||
path("atcmplt/share/u", ShareUserAutocomplete.as_view(), name="share-user-autocomplete"),
|
||||
path("atcmplt/share/t", ShareTeamAutocomplete.as_view(), name="share-team-autocomplete"),
|
||||
path("atcmplt/team/admin", TeamAdminAutocomplete.as_view(), name="team-admin-autocomplete"),
|
||||
]
|
@ -5,6 +5,10 @@ from django.urls import reverse
|
||||
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
|
||||
from konova.utils.mailer import Mailer
|
||||
from konova.utils.message_templates import FORM_INVALID
|
||||
from user.forms.modals.team import NewTeamModalForm, EditTeamModalForm, RemoveTeamModalForm, LeaveTeamModalForm
|
||||
from user.forms.modals.user import UserContactForm
|
||||
from user.forms.team import TeamDataForm
|
||||
from user.forms.user import UserNotificationForm, UserAPITokenForm
|
||||
from user.models import User, Team
|
||||
from django.http import HttpRequest, Http404
|
||||
from django.shortcuts import render, redirect, get_object_or_404
|
||||
@ -12,8 +16,6 @@ from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from konova.contexts import BaseContext
|
||||
from konova.decorators import any_group_check, default_group_required
|
||||
from user.forms import UserNotificationForm, UserContactForm, UserAPITokenForm, NewTeamModalForm, EditTeamModalForm, \
|
||||
RemoveTeamModalForm, TeamDataForm, LeaveTeamModalForm
|
||||
|
||||
|
||||
@login_required
|
||||
|
Loading…
Reference in New Issue
Block a user