From c07933a9bf181f33aa608ed00de649f9ea68e9d7 Mon Sep 17 00:00:00 2001 From: mpeltriaux Date: Thu, 18 Aug 2022 10:45:15 +0200 Subject: [PATCH] Konova filter mixins * refactors konova/mixins.py into individual files in konova/mixins/... --- compensation/filters.py | 2 +- konova/filters/mixins.py | 414 ------------------------- konova/filters/mixins/__init__.py | 7 + konova/filters/mixins/file_number.py | 33 ++ konova/filters/mixins/geo_reference.py | 209 +++++++++++++ konova/filters/mixins/keyword.py | 41 +++ konova/filters/mixins/office.py | 77 +++++ konova/filters/mixins/record.py | 51 +++ konova/filters/mixins/share.py | 57 ++++ konova/filters/table_filters.py | 9 +- 10 files changed, 482 insertions(+), 418 deletions(-) delete mode 100644 konova/filters/mixins.py create mode 100644 konova/filters/mixins/__init__.py create mode 100644 konova/filters/mixins/file_number.py create mode 100644 konova/filters/mixins/geo_reference.py create mode 100644 konova/filters/mixins/keyword.py create mode 100644 konova/filters/mixins/office.py create mode 100644 konova/filters/mixins/record.py create mode 100644 konova/filters/mixins/share.py diff --git a/compensation/filters.py b/compensation/filters.py index c9bf9b7..186a721 100644 --- a/compensation/filters.py +++ b/compensation/filters.py @@ -10,7 +10,7 @@ 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.mixins.office import ConservationOfficeTableFilterMixin from konova.filters.table_filters import QueryTableFilter, CheckboxTableFilter, SelectionTableFilter, AbstractTableFilter diff --git a/konova/filters/mixins.py b/konova/filters/mixins.py deleted file mode 100644 index cfd9cc4..0000000 --- a/konova/filters/mixins.py +++ /dev/null @@ -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 diff --git a/konova/filters/mixins/__init__.py b/konova/filters/mixins/__init__.py new file mode 100644 index 0000000..ca97853 --- /dev/null +++ b/konova/filters/mixins/__init__.py @@ -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 + +""" diff --git a/konova/filters/mixins/file_number.py b/konova/filters/mixins/file_number.py new file mode 100644 index 0000000..fa14b93 --- /dev/null +++ b/konova/filters/mixins/file_number.py @@ -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 diff --git a/konova/filters/mixins/geo_reference.py b/konova/filters/mixins/geo_reference.py new file mode 100644 index 0000000..eacf5c5 --- /dev/null +++ b/konova/filters/mixins/geo_reference.py @@ -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 diff --git a/konova/filters/mixins/keyword.py b/konova/filters/mixins/keyword.py new file mode 100644 index 0000000..212599c --- /dev/null +++ b/konova/filters/mixins/keyword.py @@ -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 + +""" +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() + # build filter expression + q = Q(title__icontains=value) | Q(identifier__icontains=value) + return queryset.filter(q) diff --git a/konova/filters/mixins/office.py b/konova/filters/mixins/office.py new file mode 100644 index 0000000..856ff6f --- /dev/null +++ b/konova/filters/mixins/office.py @@ -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="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 + + +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 diff --git a/konova/filters/mixins/record.py b/konova/filters/mixins/record.py new file mode 100644 index 0000000..4547607 --- /dev/null +++ b/konova/filters/mixins/record.py @@ -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 + diff --git a/konova/filters/mixins/share.py b/konova/filters/mixins/share.py new file mode 100644 index 0000000..562263c --- /dev/null +++ b/konova/filters/mixins/share.py @@ -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 \ No newline at end of file diff --git a/konova/filters/table_filters.py b/konova/filters/table_filters.py index 8dca6d5..5b8a4cc 100644 --- a/konova/filters/table_filters.py +++ b/konova/filters/table_filters.py @@ -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):