Fixed #31952 -- Fixed EmptyFieldListFilter crash with reverse relationships.
Thanks dacotagh for the report.
This commit is contained in:
parent
f6405c0b8e
commit
179d9dc0c2
|
@ -33,6 +33,7 @@ class ForeignObjectRel(FieldCacheMixin):
|
||||||
# Reverse relations are always nullable (Django can't enforce that a
|
# Reverse relations are always nullable (Django can't enforce that a
|
||||||
# foreign key on the related model points to this model).
|
# foreign key on the related model points to this model).
|
||||||
null = True
|
null = True
|
||||||
|
empty_strings_allowed = False
|
||||||
|
|
||||||
def __init__(self, field, to, related_name=None, related_query_name=None,
|
def __init__(self, field, to, related_name=None, related_query_name=None,
|
||||||
limit_choices_to=None, parent_link=False, on_delete=None):
|
limit_choices_to=None, parent_link=False, on_delete=None):
|
||||||
|
|
|
@ -59,3 +59,6 @@ Bugfixes
|
||||||
* Fixed a ``QuerySet.delete()`` crash on MySQL, following a performance
|
* Fixed a ``QuerySet.delete()`` crash on MySQL, following a performance
|
||||||
regression in Django 3.1 on MariaDB 10.3.2+, when filtering against an
|
regression in Django 3.1 on MariaDB 10.3.2+, when filtering against an
|
||||||
aggregate function (:ticket:`31965`).
|
aggregate function (:ticket:`31965`).
|
||||||
|
|
||||||
|
* Fixed a ``django.contrib.admin.EmptyFieldListFilter`` crash when using on
|
||||||
|
reverse relations (:ticket:`31952`).
|
||||||
|
|
|
@ -38,6 +38,10 @@ class Book(models.Model):
|
||||||
return self.title
|
return self.title
|
||||||
|
|
||||||
|
|
||||||
|
class ImprovedBook(models.Model):
|
||||||
|
book = models.OneToOneField(Book, models.CASCADE)
|
||||||
|
|
||||||
|
|
||||||
class Department(models.Model):
|
class Department(models.Model):
|
||||||
code = models.CharField(max_length=4, unique=True)
|
code = models.CharField(max_length=4, unique=True)
|
||||||
description = models.CharField(max_length=50, blank=True, null=True)
|
description = models.CharField(max_length=50, blank=True, null=True)
|
||||||
|
|
|
@ -12,7 +12,9 @@ from django.contrib.auth.models import User
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
from django.test import RequestFactory, TestCase, override_settings
|
from django.test import RequestFactory, TestCase, override_settings
|
||||||
|
|
||||||
from .models import Book, Bookmark, Department, Employee, TaggedItem
|
from .models import (
|
||||||
|
Book, Bookmark, Department, Employee, ImprovedBook, TaggedItem,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def select_by(dictlist, key, value):
|
def select_by(dictlist, key, value):
|
||||||
|
@ -252,11 +254,15 @@ class BookAdminWithEmptyFieldListFilter(ModelAdmin):
|
||||||
list_filter = [
|
list_filter = [
|
||||||
('author', EmptyFieldListFilter),
|
('author', EmptyFieldListFilter),
|
||||||
('title', EmptyFieldListFilter),
|
('title', EmptyFieldListFilter),
|
||||||
|
('improvedbook', EmptyFieldListFilter),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class DepartmentAdminWithEmptyFieldListFilter(ModelAdmin):
|
class DepartmentAdminWithEmptyFieldListFilter(ModelAdmin):
|
||||||
list_filter = [('description', EmptyFieldListFilter)]
|
list_filter = [
|
||||||
|
('description', EmptyFieldListFilter),
|
||||||
|
('employee', EmptyFieldListFilter),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class ListFiltersTests(TestCase):
|
class ListFiltersTests(TestCase):
|
||||||
|
@ -1432,6 +1438,45 @@ class ListFiltersTests(TestCase):
|
||||||
queryset = changelist.get_queryset(request)
|
queryset = changelist.get_queryset(request)
|
||||||
self.assertCountEqual(queryset, expected_result)
|
self.assertCountEqual(queryset, expected_result)
|
||||||
|
|
||||||
|
def test_emptylistfieldfilter_reverse_relationships(self):
|
||||||
|
class UserAdminReverseRelationship(UserAdmin):
|
||||||
|
list_filter = (
|
||||||
|
('books_contributed', EmptyFieldListFilter),
|
||||||
|
)
|
||||||
|
|
||||||
|
ImprovedBook.objects.create(book=self.guitar_book)
|
||||||
|
no_employees = Department.objects.create(code='NONE', description=None)
|
||||||
|
|
||||||
|
book_admin = BookAdminWithEmptyFieldListFilter(Book, site)
|
||||||
|
department_admin = DepartmentAdminWithEmptyFieldListFilter(Department, site)
|
||||||
|
user_admin = UserAdminReverseRelationship(User, site)
|
||||||
|
|
||||||
|
tests = [
|
||||||
|
# Reverse one-to-one relationship.
|
||||||
|
(
|
||||||
|
book_admin,
|
||||||
|
{'improvedbook__isempty': '1'},
|
||||||
|
[self.django_book, self.bio_book, self.djangonaut_book],
|
||||||
|
),
|
||||||
|
(book_admin, {'improvedbook__isempty': '0'}, [self.guitar_book]),
|
||||||
|
# Reverse foreign key relationship.
|
||||||
|
(department_admin, {'employee__isempty': '1'}, [no_employees]),
|
||||||
|
(department_admin, {'employee__isempty': '0'}, [self.dev, self.design]),
|
||||||
|
# Reverse many-to-many relationship.
|
||||||
|
(user_admin, {'books_contributed__isempty': '1'}, [self.alfred]),
|
||||||
|
(user_admin, {'books_contributed__isempty': '0'}, [self.bob, self.lisa]),
|
||||||
|
]
|
||||||
|
for modeladmin, query_string, expected_result in tests:
|
||||||
|
with self.subTest(
|
||||||
|
modeladmin=modeladmin.__class__.__name__,
|
||||||
|
query_string=query_string,
|
||||||
|
):
|
||||||
|
request = self.request_factory.get('/', query_string)
|
||||||
|
request.user = self.alfred
|
||||||
|
changelist = modeladmin.get_changelist_instance(request)
|
||||||
|
queryset = changelist.get_queryset(request)
|
||||||
|
self.assertCountEqual(queryset, expected_result)
|
||||||
|
|
||||||
def test_emptylistfieldfilter_choices(self):
|
def test_emptylistfieldfilter_choices(self):
|
||||||
modeladmin = BookAdminWithEmptyFieldListFilter(Book, site)
|
modeladmin = BookAdminWithEmptyFieldListFilter(Book, site)
|
||||||
request = self.request_factory.get('/')
|
request = self.request_factory.get('/')
|
||||||
|
|
Loading…
Reference in New Issue