Skip to content

Add choices filters #3

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
__pycache__/
.env/
.python-version
.coverage
.coveralls.yml
Expand Down
6 changes: 6 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ matrix:
env: REQ="Django>=3.0,<3.1"
- python: "3.6"
env: REQ="Django>=3.1,<3.2"
- python: "3.6"
env: REQ="Django>=3.2,<4.0"
- python: "3.7"
env: REQ="Django>=1.11,<2.0"
- python: "3.7"
Expand All @@ -43,12 +45,16 @@ matrix:
env: REQ="Django>=3.0,<3.1"
- python: "3.7"
env: REQ="Django>=3.1,<3.2"
- python: "3.7"
env: REQ="Django>=3.2,<4.0"
- python: "3.8"
env: REQ="Django>=2.2,<3.0"
- python: "3.8"
env: REQ="Django>=3.0,<3.1"
- python: "3.8"
env: REQ="Django>=3.1,<3.2"
- python: "3.8"
env: REQ="Django>=3.2,<4.0"
before_install:
- sudo apt-get -y install firefox-geckodriver
install:
Expand Down
8 changes: 6 additions & 2 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Welcome to django-more-admin-filters

.. image:: https://travis-ci.com/thomst/django-more-admin-filters.svg?branch=master
:target: https://travis-ci.com/thomst/django-more-admin-filters

.. image:: https://coveralls.io/repos/github/thomst/django-more-admin-filters/badge.svg?branch=master
:target: https://coveralls.io/github/thomst/django-more-admin-filters?branch=master

Expand All @@ -14,7 +14,7 @@ Welcome to django-more-admin-filters

.. image:: https://img.shields.io/badge/django-1.11%20%7C%202.0%20%7C%202.1%20%7C%202.2%20%7C%203.0%20%7C%203.1-orange
:target: https://img.shields.io/badge/django-1.11%20%7C%202.0%20%7C%202.1%20%7C%202.2%20%7C%203.0%20%7C%203.1-orange
:alt: django: 1.11, 2.0, 2.1, 2.2, 3.0, 3.1
:alt: django: 1.11, 2.0, 2.1, 2.2, 3.0, 3.1, 3.2


Description
Expand Down Expand Up @@ -59,6 +59,10 @@ Filter classes
Dropdown filter for relation fields.
* **RelatedOnlyDropdownFilter**
Dropdown filter for relation fields using limit_choices_to.
* **MultiSelectChoicesFilter**
Multi select filter for choices.
* **MultiSelectChoicesDropdownFilter**
Multi select dropdown filter for choices.
* **MultiSelectFilter**
Multi select filter for all kind of fields.
* **MultiSelectRelatedFilter**
Expand Down
3 changes: 2 additions & 1 deletion more_admin_filters/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@


from .filters import (
MultiSelectChoicesFilter, MultiSelectChoicesDropdownFilter,
MultiSelectFilter, MultiSelectRelatedFilter, MultiSelectDropdownFilter,
MultiSelectRelatedDropdownFilter, DropdownFilter, ChoicesDropdownFilter,
RelatedDropdownFilter, BooleanAnnotationFilter
)
)
101 changes: 101 additions & 0 deletions more_admin_filters/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,107 @@ def has_output(self):
return len(self.lookup_choices) > 1


class MultiSelectChoicesFilter(MultiSelectMixin, ChoicesFieldListFilter):
"""
Multi select filter for choices.
"""

def __init__(self, field, request, params, model, model_admin, field_path):
self.lookup_kwarg = '%s__in' % field_path
self.lookup_kwarg_isnull = '%s__isnull' % field_path
lookup_vals = request.GET.get(self.lookup_kwarg)
self.lookup_vals = lookup_vals.split(',') if lookup_vals else list()
self.lookup_val_isnull = request.GET.get(self.lookup_kwarg_isnull)
self.empty_value_display = model_admin.get_empty_value_display()
parent_model, reverse_path = reverse_field_path(model, field_path)
# Obey parent ModelAdmin queryset when deciding which options to show
if model == parent_model:
queryset = model_admin.get_queryset(request)
else:
queryset = parent_model._default_manager.all()
self.lookup_choices = (queryset
.distinct()
.order_by(field.name)
.values_list(field.name, flat=True))
super(ChoicesFieldListFilter, self).__init__(field, request, params, model, model_admin, field_path)
self.used_parameters = self.prepare_used_parameters(self.used_parameters)

def prepare_querystring_value(self, value):
# mask all commas or these values will be used
# in a comma-seperated-list as get-parameter
return str(value).replace(',', '%~')

def prepare_used_parameters(self, used_parameters):
# remove comma-mask from list-values for __in-lookups
for key, value in used_parameters.items():
if not key.endswith('__in'): continue
used_parameters[key] = [v.replace('%~', ',') for v in value]
return used_parameters

def choices(self, changelist):
yield {
'selected': not self.lookup_vals and self.lookup_val_isnull is None,
'query_string': changelist.get_query_string({}, [self.lookup_kwarg, self.lookup_kwarg_isnull]),
'display': _('All'),
}
include_none = False
for val, title in self.field.flatchoices:
if val is None:
include_none = True
continue
val = str(val)
qval = self.prepare_querystring_value(val)
yield {
'selected': qval in self.lookup_vals,
'query_string': self.querystring_for_choices(qval, changelist),
'display': title,
}
if include_none:
yield {
'selected': bool(self.lookup_val_isnull),
'query_string': self.querystring_for_isnull(changelist),
'display': self.empty_value_display,
}


class MultiSelectChoicesDropdownFilter(MultiSelectChoicesFilter):
"""
Multi select dropdown filter for choices.
"""
template = 'more_admin_filters/multiselectdropdownfilter.html'

def choices(self, changelist):
query_string = changelist.get_query_string({}, [self.lookup_kwarg, self.lookup_kwarg_isnull])
yield {
'selected': not self.lookup_vals and self.lookup_val_isnull is None,
'query_string': query_string,
'display': _('All'),
}
include_none = False
for val, title in self.field.flatchoices:
if val is None:
include_none = True
continue

val = str(val)
qval = self.prepare_querystring_value(val)
yield {
'selected': qval in self.lookup_vals,
'query_string': query_string,
'display': title,
'value': val,
'key': self.lookup_kwarg,
}
if include_none:
yield {
'selected': bool(self.lookup_val_isnull),
'query_string': query_string,
'display': self.empty_value_display,
'value': 'True',
'key': self.lookup_kwarg_isnull,
}


class MultiSelectFilter(MultiSelectMixin, admin.AllValuesFieldListFilter):
"""
Multi select filter for all kind of fields.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ <h3>{% blocktrans with filter_title=title %} By {{ filter_title }} {% endblocktr
</li>
<li>
<a id="{{ title|slugify }}_submit" href="" title="filter" class="button"
style="background-color:buttonface;color:#666">filter</a>
style="background-color:buttonface;color:#666">{% trans 'Filter' %}</a>
</li>
</ul>

Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def read(filename):
packages=find_packages(exclude=["tests"]),
include_package_data=True,
install_requires=[
"Django>=1.11,<=3.1",
"Django>=1.11,<=3.2",
],
classifiers=[
dev_status,
Expand Down
2 changes: 2 additions & 0 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ envlist =
{py35,py36,py37,py38}-django22,
{py36,py37,py38}-django30,
{py36,py37,py38}-django31
{py36,py37,py38}-django32
skip_missing_interpreters = true

[testenv]
Expand All @@ -21,6 +22,7 @@ deps =
django22: Django>=2.2,<3.0
django30: Django>=3.0,<3.1
django31: Django>=3.1,<3.2
django32: Django>=3.2,<4.0
selenium

commands = {envpython} tests/manage.py test testapp {posargs}
Expand Down