Compare commits

...

4 Commits

Author SHA1 Message Date
96f2e77972 # 61 EcoAccount Filter
* adds specialized SelectionTableFilter for EcoAccount (and EMA) which only provides filtering by conservation office
2022-01-12 10:15:57 +01:00
4c5e170b85 # 61 General table enhancements
* enhances rendering of tables
* enhances rendering of filter section
* reorganizes table filter codes into konova/filters/ folder and splits into mixins and table_filters
2022-01-12 10:11:47 +01:00
d57478ee09 # 61 Filter code enhancement
* reorganizes code
2022-01-12 09:23:22 +01:00
834fa8dbd4 # 61 Filter offices frontend/backend
* adds filters for registration and conservation offices
* adds SelectionTableFilter as new holder for these types of filter components
2022-01-12 08:56:49 +01:00
12 changed files with 242 additions and 99 deletions

View File

@ -10,7 +10,23 @@ from django.utils.translation import gettext_lazy as _
from django import forms
from django.db.models import QuerySet, Q
from konova.filters import QueryTableFilter, CheckboxTableFilter
from konova.filters.mixins import ConservationOfficeTableFilterMixin
from konova.filters.table_filters import QueryTableFilter, CheckboxTableFilter, SelectionTableFilter, AbstractTableFilter
class SelectionCompensationTableFilter(SelectionTableFilter):
""" Specialization of regular SelectionTableFilter for compensation model
"""
def filter_reg_office(self, queryset, name, value):
return queryset.filter(
intervention__responsible__registration_office=value
)
def filter_cons_office(self, queryset, name, value):
return queryset.filter(
intervention__responsible__conservation_office=value
)
class QueryCompensationTableFilter(QueryTableFilter):
@ -67,32 +83,35 @@ class CheckboxCompensationTableFilter(CheckboxTableFilter):
return queryset
class CompensationTableFilter:
class CompensationTableFilter(AbstractTableFilter):
""" TableFilter for compensations
Based widely on InterventionTableFilter.
Just some minor changes for Compensation model.
"""
query_filter = None
checkbox_filter = None
qs = None
def __init__(self, user=None, *args, **kwargs):
super().__init__(*args, **kwargs)
def __init__(self, *args, **kwargs):
user = kwargs.get("user", None)
self.user = user
qs = kwargs.get("queryset", None)
request_data = kwargs.get("data", None)
self.query_filter = QueryCompensationTableFilter(
user=user,
# Overwrite all filters for special needs of compensations
self.selection_filter = SelectionCompensationTableFilter(
data=request_data,
queryset=qs,
)
self.query_filter = QueryCompensationTableFilter(
data=request_data,
queryset=self.selection_filter.qs,
)
self.checkbox_filter = CheckboxCompensationTableFilter(
user=user,
data=request_data,
queryset=self.query_filter.qs,
)
# Overwrite final queryset as well
self.qs = self.checkbox_filter.qs
@ -145,27 +164,38 @@ class CheckboxEcoAccountTableFilter(CheckboxTableFilter):
return queryset
class EcoAccountTableFilter:
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
"""
query_filter = None
checkbox_filter = None
qs = None
def __init__(self, *args, **kwargs):
user = kwargs.get("user", None)
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)
self.query_filter = QueryTableFilter(
user=user,
# 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

View File

@ -16,11 +16,11 @@ from django.utils.translation import gettext_lazy as _
from compensation.filters import CompensationTableFilter, EcoAccountTableFilter
from compensation.models import Compensation, EcoAccount
from konova.sub_settings.django_settings import DEFAULT_DATE_TIME_FORMAT
from konova.utils.tables import BaseTable
from konova.utils.tables import BaseTable, TableRenderMixin
import django_tables2 as tables
class CompensationTable(BaseTable):
class CompensationTable(BaseTable, TableRenderMixin):
id = tables.Column(
verbose_name=_("Identifier"),
orderable=True,
@ -162,7 +162,7 @@ class CompensationTable(BaseTable):
return format_html(html)
class EcoAccountTable(BaseTable):
class EcoAccountTable(BaseTable, TableRenderMixin):
id = tables.Column(
verbose_name=_("Identifier"),
orderable=True,

View File

@ -5,8 +5,6 @@ Contact: michel.peltriaux@sgdnord.rlp.de
Created on: 19.08.21
"""
from django.db.models import QuerySet
from compensation.filters import EcoAccountTableFilter

View File

@ -14,12 +14,12 @@ from django.urls import reverse
import django_tables2 as tables
from konova.sub_settings.django_settings import DEFAULT_DATE_TIME_FORMAT
from konova.utils.tables import BaseTable
from konova.utils.tables import BaseTable, TableRenderMixin
from ema.filters import EmaTableFilter
from ema.models import Ema
class EmaTable(BaseTable):
class EmaTable(BaseTable, TableRenderMixin):
"""
Since EMA and compensation are basically the same, we can reuse CompensationTableFilter and extend the EMA filter
in the future by inheriting.

View File

@ -5,27 +5,29 @@ Contact: michel.peltriaux@sgdnord.rlp.de
Created on: 22.07.21
"""
from konova.filters import QueryTableFilter, CheckboxTableFilter
from konova.filters.table_filters import AbstractTableFilter, SelectionTableFilter, QueryTableFilter, CheckboxTableFilter
class InterventionTableFilter:
query_filter = None
checkbox_filter = None
qs = None
def __init__(self, *args, **kwargs):
user = kwargs.get("user", None)
class InterventionTableFilter(AbstractTableFilter):
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)
self.query_filter = QueryTableFilter(
user=user,
# Pipe the queryset through all needed filters
self.selection_filter = SelectionTableFilter(
data=request_data,
queryset=qs,
)
self.query_filter = QueryTableFilter(
data=request_data,
queryset=self.selection_filter.qs,
)
self.checkbox_filter = CheckboxTableFilter(
user=user,
data=request_data,
queryset=self.query_filter.qs,
)
self.qs = self.checkbox_filter.qs

View File

@ -14,11 +14,11 @@ from django.utils.translation import gettext_lazy as _
from intervention.filters import InterventionTableFilter
from intervention.models import Intervention
from konova.sub_settings.django_settings import DEFAULT_DATE_TIME_FORMAT, DEFAULT_DATE_FORMAT
from konova.utils.tables import BaseTable
from konova.utils.tables import BaseTable, TableRenderMixin
import django_tables2 as tables
class InterventionTable(BaseTable):
class InterventionTable(BaseTable, TableRenderMixin):
id = tables.Column(
verbose_name=_("Identifier"),
orderable=True,

View File

@ -0,0 +1,7 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: michel.peltriaux@sgdnord.rlp.de
Created on: 12.01.22
"""

View File

@ -2,31 +2,21 @@
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: michel.peltriaux@sgdnord.rlp.de
Created on: 11.01.22
Created on: 12.01.22
"""
import django_filters
from django import forms
from django.utils.translation import gettext_lazy as _
from django.contrib.auth.models import User
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 AbstractTableFilter(django_filters.FilterSet):
""" TableFilter for Intervention model
"""
class Meta:
abstract = True
def __init__(self, user: User, *args, **kwargs):
self.user = user
super().__init__(*args, **kwargs)
class KeywordTableFilterMixin(django_filters.FilterSet):
q = django_filters.Filter(
method='filter_by_keyword',
@ -289,9 +279,10 @@ class ShareableTableFilterMixin(django_filters.FilterSet):
abstract = True
def __init__(self, *args, **kwargs):
super().__init__(*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
@ -351,18 +342,65 @@ class RecordableTableFilterMixin(django_filters.FilterSet):
return queryset
class QueryTableFilter(AbstractTableFilter,
KeywordTableFilterMixin,
FileNumberTableFilterMixin,
GeoReferencedTableFilterMixin):
""" TableFilter holding different filter options for query related filtering
class RegistrationOfficeTableFilterMixin(django_filters.FilterSet):
""" A mixin for AbstractTableFilter
Specialized on filtering for related registration offices
"""
pass
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 CheckboxTableFilter(AbstractTableFilter, ShareableTableFilterMixin, RecordableTableFilterMixin):
""" TableFilter holding different filter options for checkbox related filtering
class ConservationOfficeTableFilterMixin(django_filters.FilterSet):
""" A mixin for AbstractTableFilter
Specialized on filtering for related conservation offices
"""
pass
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

View File

@ -0,0 +1,50 @@
"""
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 konova.filters.mixins import RegistrationOfficeTableFilterMixin, ConservationOfficeTableFilterMixin, \
KeywordTableFilterMixin, FileNumberTableFilterMixin, GeoReferencedTableFilterMixin, ShareableTableFilterMixin, \
RecordableTableFilterMixin
class AbstractTableFilter(django_filters.FilterSet):
""" Base TableFilter for all models
"""
selection_filter = None
query_filter = None
checkbox_filter = None
qs = None
user = None
class Meta:
abstract = True
class SelectionTableFilter(RegistrationOfficeTableFilterMixin,
ConservationOfficeTableFilterMixin):
""" TableFilter holding different filter options for selection related filtering
"""
pass
class QueryTableFilter(KeywordTableFilterMixin,
FileNumberTableFilterMixin,
GeoReferencedTableFilterMixin):
""" TableFilter holding different filter options for query related filtering
"""
pass
class CheckboxTableFilter(ShareableTableFilterMixin, RecordableTableFilterMixin):
""" TableFilter holding different filter options for checkbox related filtering
"""
pass

View File

@ -404,6 +404,7 @@ class AutocompleteTestCase(BaseViewTestCase):
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")
def _test_views_anonymous_user(self):
# ATTENTION: As of the current state of django-autocomplete-light, there is no way to check on authenticated
@ -421,6 +422,7 @@ class AutocompleteTestCase(BaseViewTestCase):
self.atcmplt_code_comp_process,
self.atcmplt_code_comp_reg_off,
self.atcmplt_code_comp_cons_off,
self.atcmplt_code_share_user,
]
self.assert_url_fail(client, urls)

View File

@ -5,15 +5,13 @@ Contact: michel.peltriaux@sgdnord.rlp.de
Created on: 25.11.20
"""
import uuid
from django import forms
from django.core.paginator import PageNotAnInteger, EmptyPage
from django.http import HttpRequest
from django.utils.html import format_html
import django_tables2 as tables
from konova.forms import BaseForm
from konova.models import BaseObject
from konova.settings import PAGE_SIZE_DEFAULT, PAGE_PARAM, RESULTS_PER_PAGE_PARAM, PAGE_SIZE_OPTIONS
@ -147,22 +145,21 @@ class BaseTable(tables.tables.Table):
)
class ChoicesColumnForm(BaseForm):
select = forms.ChoiceField(
choices=[],
label="",
label_suffix="",
widget=forms.Select(
attrs={
"onchange": "submit();",
}
)
)
class TableRenderMixin:
""" Holds different render methods for general purposes
def __init__(self, *args, **kwargs):
self.action_url = kwargs.pop("action_url", None)
self.choices = kwargs.pop("choices", [])
super().__init__(*args, **kwargs)
self.auto_id += "_" + str(uuid.uuid4())
if len(self.choices) > 0:
self.fields["select"].choices = self.choices
"""
def render_t(self, value, record: BaseObject):
""" Renders a BaseObject title
Args:
value ():
record ():
Returns:
"""
max_length = 75
if len(value) > max_length:
value = f"{value[:max_length]}..."
return value

View File

@ -3,6 +3,16 @@
{% load django_tables2 %}
{% load i18n static fontawesome_5 %}
{% block head %}
{% comment %}
dal documentation (django-autocomplete-light) states using form.media for adding needed scripts.
This does not work properly with modal forms, as the scripts are not loaded properly inside the modal.
Therefore the script linkages from form.media have been extracted and put inside dal/scripts.html to ensure
these scripts are loaded when needed.
{% endcomment %}
{% include 'dal/scripts.html' %}
{% endblock %}
{% block body %}
<div class="col-md">
{% if table.title %}
@ -82,22 +92,31 @@
</div>
<div id="filter" class="collapse" aria-labelledby="filterHeader">
<div class="card-body">
<div class="row">
{% with table.filter.query_filter.form as form %}
{% include 'filter/query_filter.html' %}
{% endwith %}
</div>
<div class="row mt-3">
{% with table.filter.checkbox_filter.form as form %}
{% include 'filter/checkbox_filter.html' %}
{% endwith %}
</div>
<hr>
<div class="row mt-3">
<button class="btn btn-default" title="{% trans 'Filter' %}">
{% fa5_icon 'filter' %}
{% trans 'Apply filter' %}
</button>
<div class="">
<div class="">
{% with table.filter.selection_filter.form as form %}
{% include 'filter/query_filter.html' %}
{% endwith %}
</div>
<hr>
<div class="mt-3">
{% with table.filter.query_filter.form as form %}
{% include 'filter/query_filter.html' %}
{% endwith %}
</div>
<hr>
<div class="mt-3">
{% with table.filter.checkbox_filter.form as form %}
{% include 'filter/checkbox_filter.html' %}
{% endwith %}
</div>
<hr>
<div class="mt-3">
<button class="btn btn-default" title="{% trans 'Filter' %}">
{% fa5_icon 'filter' %}
{% trans 'Apply filter' %}
</button>
</div>
</div>
</div>
</div>