Allow FilteredModelChoiceField to accept a callable or relation name as well as a property / method name for filter_accessor

pull/6257/head
Matt Westcott 2020-05-01 14:19:08 +01:00 zatwierdzone przez Matt Westcott
rodzic ad37867bc6
commit 07ebbc24a4
2 zmienionych plików z 98 dodań i 6 usunięć

Wyświetl plik

@ -1,4 +1,5 @@
import django_filters
from django.db import models
from django.utils.translation import gettext_lazy as _
from django_filters.widgets import SuffixedMultiWidget
@ -38,6 +39,16 @@ class FilteredModelChoiceIterator(django_filters.fields.ModelChoiceIterator):
class FilteredModelChoiceField(django_filters.fields.ModelChoiceField):
"""
A ModelChoiceField that uses FilteredSelect to dynamically show/hide options based on another
ModelChoiceField of related objects; an option will be shown whenever the selected related
object is present in the result of filter_accessor for that option.
filter_field - the HTML `id` of the related ModelChoiceField
filter_accessor - either the name of a relation, property or method on the model instance which
returns a queryset of related objects, or a function which accepts the model instance and
returns such a queryset.
"""
widget = FilteredSelect
iterator = FilteredModelChoiceIterator
@ -48,13 +59,19 @@ class FilteredModelChoiceField(django_filters.fields.ModelChoiceField):
self.widget.filter_field = filter_field
def get_filter_value(self, obj):
# filter_accessor identifies a property or method on the instances being listed here,
# which gives us a queryset of related objects. Turn this queryset into a list of IDs
# that will become the 'data-filter-value' used to filter this listing
queryset = getattr(obj, self.filter_accessor)
if callable(queryset):
queryset = queryset()
# Use filter_accessor to obtain a queryset of related objects
if callable(self.filter_accessor):
queryset = self.filter_accessor(obj)
else:
# treat filter_accessor as a method/property name of obj
queryset = getattr(obj, self.filter_accessor)
if isinstance(queryset, models.Manager):
queryset = queryset.all()
elif callable(queryset):
queryset = queryset()
# Turn this queryset into a list of IDs that will become the 'data-filter-value' used to
# filter this listing
return queryset.values_list('pk', flat=True)

Wyświetl plik

@ -0,0 +1,75 @@
from django import forms
from django.contrib.auth import get_user_model
from django.contrib.auth.models import Group
from django.test import TestCase
from wagtail.admin.filters import FilteredModelChoiceField
User = get_user_model()
class TestFilteredModelChoiceField(TestCase):
def setUp(self):
self.musicians = Group.objects.create(name="Musicians")
self.actors = Group.objects.create(name="Actors")
self.david = User.objects.create_user(
'david', 'david@example.com', 'kn1ghtr1der', first_name="David", last_name="Hasselhoff"
)
self.david.groups.set([self.musicians, self.actors])
self.kevin = User.objects.create_user(
'kevin', 'kevin@example.com', '6degrees', first_name="Kevin", last_name="Bacon"
)
self.kevin.groups.set([self.actors])
self.morten = User.objects.create_user(
'morten', 'morten@example.com', 't4ke0nm3', first_name="Morten", last_name="Harket"
)
self.morten.groups.set([self.musicians])
def test_with_relation(self):
class UserForm(forms.Form):
users = FilteredModelChoiceField(
queryset=User.objects.order_by('username'), filter_field='id_group', filter_accessor='groups'
)
form = UserForm()
html = str(form['users'])
expected_html = """
<select name="users" data-widget="filtered-select" data-filter-field="id_group" required id="id_users">
<option value="" selected>---------</option>
<option value="%(david)s" data-filter-value="%(musicians)s,%(actors)s">david</option>
<option value="%(kevin)s" data-filter-value="%(actors)s">kevin</option>
<option value="%(morten)s" data-filter-value="%(musicians)s">morten</option>
</select>
""" % {
'david': self.david.pk, 'kevin': self.kevin.pk, 'morten': self.morten.pk,
'musicians': self.musicians.pk, 'actors': self.actors.pk,
}
self.assertHTMLEqual(html, expected_html)
def test_with_callable(self):
class UserForm(forms.Form):
users = FilteredModelChoiceField(
queryset=User.objects.order_by('username'), filter_field='id_group',
filter_accessor=lambda user: user.groups.all()
)
form = UserForm()
html = str(form['users'])
expected_html = """
<select name="users" data-widget="filtered-select" data-filter-field="id_group" required id="id_users">
<option value="" selected>---------</option>
<option value="%(david)s" data-filter-value="%(musicians)s,%(actors)s">david</option>
<option value="%(kevin)s" data-filter-value="%(actors)s">kevin</option>
<option value="%(morten)s" data-filter-value="%(musicians)s">morten</option>
</select>
""" % {
'david': self.david.pk, 'kevin': self.kevin.pk, 'morten': self.morten.pk,
'musicians': self.musicians.pk, 'actors': self.actors.pk,
}
self.assertHTMLEqual(html, expected_html)