Skip to content

Commit

Permalink
feat: multiple dropdown filter (#650)
Browse files Browse the repository at this point in the history
  • Loading branch information
lukasvinclav authored Aug 5, 2024
1 parent cb829bd commit 0572dd2
Show file tree
Hide file tree
Showing 5 changed files with 99 additions and 6 deletions.
11 changes: 10 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -621,7 +621,14 @@ The difference between them is that `ChoicesDropdownFilter` will collect a list
from django.contrib import admin
from django.contrib.auth.models import User
from unfold.admin import ModelAdmin
from unfold.contrib.filters.admin import ChoicesDropdownFilter, RelatedDropdownFilter, DropdownFilter
from unfold.contrib.filters.admin import (
ChoicesDropdownFilter,
MultipleChoicesDropdownFilter,
RelatedDropdownFilter,
MultipleRelatedDropdownFilter,
DropdownFilter,
MultipleDropdownFilter
)


class CustomDropdownFilter(DropdownFilter):
Expand All @@ -648,7 +655,9 @@ class MyAdmin(ModelAdmin):
list_filter = [
CustomDropdownFilter,
("modelfield_with_choices", ChoicesDropdownFilter),
("modelfield_with_choices_multiple", MultipleChoicesDropdownFilter),
("modelfield_with_foreign_key", RelatedDropdownFilter)
("modelfield_with_foreign_key_multiple", MultipleRelatedDropdownFilter)
]
```

Expand Down
28 changes: 27 additions & 1 deletion src/unfold/contrib/filters/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,23 @@ def value(self) -> Optional[str]:
)


class MultiValueMixin:
def value(self) -> Optional[List[str]]:
return (
self.lookup_val
if self.lookup_val not in EMPTY_VALUES
and isinstance(self.lookup_val, List)
and len(self.lookup_val) > 0
else self.lookup_val
)


class DropdownMixin:
template = "unfold/filters/filters_field.html"
form_class = DropdownForm
all_option = ["", _("All")]

def queryset(self, request, queryset) -> QuerySet:
def queryset(self, request: HttpRequest, queryset: QuerySet) -> QuerySet:
if self.value() not in EMPTY_VALUES:
return super().queryset(request, queryset)

Expand Down Expand Up @@ -112,11 +123,16 @@ def choices(self, changelist: ChangeList) -> Tuple[Dict[str, Any], ...]:
name=self.parameter_name,
choices=[self.all_option, *self.lookup_choices],
data={self.parameter_name: self.value()},
multiple=self.multiple if hasattr(self, "multiple") else False,
),
},
)


class MultipleDropdownFilter(MultiValueMixin, DropdownFilter):
multiple = True


class ChoicesDropdownFilter(ValueMixin, DropdownMixin, admin.ChoicesFieldListFilter):
def choices(self, changelist: ChangeList):
choices = [self.all_option, *self.field.flatchoices]
Expand All @@ -127,10 +143,15 @@ def choices(self, changelist: ChangeList):
name=self.lookup_kwarg,
choices=choices,
data={self.lookup_kwarg: self.value()},
multiple=self.multiple if hasattr(self, "multiple") else False,
),
}


class MultipleChoicesDropdownFilter(MultiValueMixin, ChoicesDropdownFilter):
multiple = True


class RelatedDropdownFilter(ValueMixin, DropdownMixin, admin.RelatedFieldListFilter):
def choices(self, changelist: ChangeList):
yield {
Expand All @@ -139,10 +160,15 @@ def choices(self, changelist: ChangeList):
name=self.lookup_kwarg,
choices=[self.all_option, *self.lookup_choices],
data={self.lookup_kwarg: self.value()},
multiple=self.multiple if hasattr(self, "multiple") else False,
),
}


class MultipleRelatedDropdownFilter(MultiValueMixin, RelatedDropdownFilter):
multiple = True


class SingleNumericFilter(admin.FieldListFilter):
request = None
parameter_name = None
Expand Down
39 changes: 36 additions & 3 deletions src/unfold/contrib/filters/forms.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
from django import forms
from django.forms import ChoiceField, MultipleChoiceField
from django.utils.translation import gettext_lazy as _

from ...widgets import (
INPUT_CLASSES,
UnfoldAdminSelectMultipleWidget,
UnfoldAdminSelectWidget,
UnfoldAdminSplitDateTimeVerticalWidget,
UnfoldAdminTextInputWidget,
Expand All @@ -21,15 +23,46 @@ def __init__(self, name, label, *args, **kwargs):


class DropdownForm(forms.Form):
def __init__(self, name, label, choices, *args, **kwargs):
widget = UnfoldAdminSelectWidget(
attrs={
"data-theme": "admin-autocomplete",
"class": "admin-autocomplete",
}
)
field = ChoiceField

def __init__(self, name, label, choices, multiple=False, *args, **kwargs):
super().__init__(*args, **kwargs)

self.fields[name] = forms.ChoiceField(
if multiple:
self.widget = UnfoldAdminSelectMultipleWidget(
attrs={
"data-theme": "admin-autocomplete",
"class": "admin-autocomplete",
}
)
self.field = MultipleChoiceField

self.fields[name] = self.field(
label=label,
required=False,
choices=choices,
widget=UnfoldAdminSelectWidget,
widget=self.widget,
)

class Media:
js = (
"admin/js/vendor/jquery/jquery.js",
"admin/js/vendor/select2/select2.full.js",
"admin/js/jquery.init.js",
"unfold/js/select2.init.js",
)
css = {
"screen": (
"admin/css/vendor/select2/select2.css",
"admin/css/autocomplete.css",
),
}


class SingleNumericForm(forms.Form):
Expand Down
15 changes: 15 additions & 0 deletions src/unfold/static/unfold/js/select2.init.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
"use strict";
{
const $ = django.jQuery;

$.fn.djangoCustomSelect2 = function () {
$.each(this, function (i, element) {
$(element).select2();
});
return this;
};

$(function () {
$(".admin-autocomplete").djangoCustomSelect2();
});
}
12 changes: 11 additions & 1 deletion src/unfold/widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
NumberInput,
PasswordInput,
Select,
SelectMultiple,
)
from django.utils.translation import gettext_lazy as _

Expand Down Expand Up @@ -480,7 +481,16 @@ def __init__(self, attrs=None, choices=()):
if attrs is None:
attrs = {}

attrs["class"] = " ".join(SELECT_CLASSES)
attrs["class"] = " ".join([*SELECT_CLASSES, attrs.get("class", "")])
super().__init__(attrs, choices)


class UnfoldAdminSelectMultipleWidget(SelectMultiple):
def __init__(self, attrs=None, choices=()):
if attrs is None:
attrs = {}

attrs["class"] = " ".join([*SELECT_CLASSES, attrs.get("class", "")])
super().__init__(attrs, choices)


Expand Down

0 comments on commit 0572dd2

Please sign in to comment.