kopia lustrzana https://github.com/wagtail/wagtail
Allow FilteredModelChoiceField to accept a callable or relation name as well as a property / method name for filter_accessor
rodzic
ad37867bc6
commit
07ebbc24a4
|
@ -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
|
||||
# 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 callable(queryset):
|
||||
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)
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
Ładowanie…
Reference in New Issue