From 2e2fc09617f256ee2525e4b9f7b8a7b1a5831af0 Mon Sep 17 00:00:00 2001 From: Birger Schacht Date: Mon, 27 Jan 2025 16:25:04 +0100 Subject: [PATCH] feat(generic): add a collections filter Closes: #1557 --- apis_core/generic/filtersets.py | 51 +++++++++++++++++++ apis_core/generic/forms/fields.py | 29 ++++++++++- .../widgets/includeexclude_multiwidget.html | 4 ++ 3 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 apis_core/generic/templates/widgets/includeexclude_multiwidget.html diff --git a/apis_core/generic/filtersets.py b/apis_core/generic/filtersets.py index febcc39d8..72a8553e7 100644 --- a/apis_core/generic/filtersets.py +++ b/apis_core/generic/filtersets.py @@ -1,7 +1,47 @@ +import logging + +import django_filters +from django.apps import apps +from django.contrib.contenttypes.models import ContentType from django_filters.filterset import FilterSet +from apis_core.generic.forms.fields import IncludeExcludeField + from .forms import GenericFilterSetForm +logger = logging.getLogger(__name__) + + +class CollectionsFilter(django_filters.filters.ModelMultipleChoiceFilter): + """ + A simple filter for connections to collections. It provides an `include`/`exclude` + selector, which allows to define what the filter should do. + """ + + @property + def field(self): + return IncludeExcludeField(super().field, required=self.extra["required"]) + + def filter(self, queryset, value): + value, include_exclude = value + if value: + content_type = ContentType.objects.get_for_model(queryset.model) + try: + skoscollectioncontentobject = apps.get_model( + "collections.SkosCollectionContentObject" + ) + scco = skoscollectioncontentobject.objects.filter( + content_type=content_type, collection__in=value + ).values("object_id") + match include_exclude: + case "include": + return queryset.filter(id__in=scco) + case "exclude": + return queryset.exclude(id__in=scco) + except LookupError as e: + logger.debug("Not filtering for collections: %s", e) + return queryset + class GenericFilterSet(FilterSet): """ @@ -12,3 +52,14 @@ class GenericFilterSet(FilterSet): class Meta: form = GenericFilterSetForm + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + try: + skoscollection = apps.get_model("collections.SkosCollection") + if skoscollection.objects.exists(): + self.filters["collections"] = CollectionsFilter( + queryset=skoscollection.objects.all(), + ) + except LookupError as e: + logger.debug("Not adding collections filter to form: %s", e) diff --git a/apis_core/generic/forms/fields.py b/apis_core/generic/forms/fields.py index 42e39e2d9..e7567894a 100644 --- a/apis_core/generic/forms/fields.py +++ b/apis_core/generic/forms/fields.py @@ -1,5 +1,5 @@ from django.core.exceptions import ValidationError -from django.forms import ModelChoiceField +from django.forms import ChoiceField, ModelChoiceField, MultiValueField, MultiWidget from django.utils.translation import gettext as _ from apis_core.utils.helpers import create_object_from_uri @@ -16,3 +16,30 @@ def to_python(self, value): params={"value": value, "exception": e}, ) return result or super().to_python(value) + + +class IncludeExcludeMultiWidget(MultiWidget): + template_name = "widgets/includeexclude_multiwidget.html" + use_fieldset = False + + def decompress(self, value): + return [value, value] + + +class IncludeExcludeField(MultiValueField): + """ + This is a custom MultiValueField that adds a ChoiceField that only provides two + choices, namely `exclude` and `include`. It can be used for django-filter filters + to specify which action should be done with the filter. + """ + + def __init__(self, field, *args, **kwargs): + fields = ( + field, + ChoiceField(choices=[("include", "include"), ("exclude", "exclude")]), + ) + kwargs["widget"] = IncludeExcludeMultiWidget(widgets=[f.widget for f in fields]) + super().__init__(fields=fields, *args, **kwargs) + + def compress(self, data_list): + return data_list diff --git a/apis_core/generic/templates/widgets/includeexclude_multiwidget.html b/apis_core/generic/templates/widgets/includeexclude_multiwidget.html new file mode 100644 index 000000000..7500c2072 --- /dev/null +++ b/apis_core/generic/templates/widgets/includeexclude_multiwidget.html @@ -0,0 +1,4 @@ +
+
{% include widget.subwidgets.0.template_name with widget=widget.subwidgets.0 %}
+
{% include widget.subwidgets.1.template_name with widget=widget.subwidgets.1 %}
+