Skip to content

Commit c4e5384

Browse files
jmfedericofelixxm
authored andcommitted
[3.1.x] Fixed #31952 -- Fixed EmptyFieldListFilter crash with reverse relationships.
Thanks dacotagh for the report. Backport of 179d9dc from master
1 parent 2986ec0 commit c4e5384

4 files changed

Lines changed: 55 additions & 2 deletions

File tree

django/db/models/fields/reverse_related.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ class ForeignObjectRel(FieldCacheMixin):
3333
# Reverse relations are always nullable (Django can't enforce that a
3434
# foreign key on the related model points to this model).
3535
null = True
36+
empty_strings_allowed = False
3637

3738
def __init__(self, field, to, related_name=None, related_query_name=None,
3839
limit_choices_to=None, parent_link=False, on_delete=None):

docs/releases/3.1.1.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,6 @@ Bugfixes
5959
* Fixed a ``QuerySet.delete()`` crash on MySQL, following a performance
6060
regression in Django 3.1 on MariaDB 10.3.2+, when filtering against an
6161
aggregate function (:ticket:`31965`).
62+
63+
* Fixed a ``django.contrib.admin.EmptyFieldListFilter`` crash when using on
64+
reverse relations (:ticket:`31952`).

tests/admin_filters/models.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ def __str__(self):
3838
return self.title
3939

4040

41+
class ImprovedBook(models.Model):
42+
book = models.OneToOneField(Book, models.CASCADE)
43+
44+
4145
class Department(models.Model):
4246
code = models.CharField(max_length=4, unique=True)
4347
description = models.CharField(max_length=50, blank=True, null=True)

tests/admin_filters/tests.py

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212
from django.core.exceptions import ImproperlyConfigured
1313
from django.test import RequestFactory, TestCase, override_settings
1414

15-
from .models import Book, Bookmark, Department, Employee, TaggedItem
15+
from .models import (
16+
Book, Bookmark, Department, Employee, ImprovedBook, TaggedItem,
17+
)
1618

1719

1820
def select_by(dictlist, key, value):
@@ -252,11 +254,15 @@ class BookAdminWithEmptyFieldListFilter(ModelAdmin):
252254
list_filter = [
253255
('author', EmptyFieldListFilter),
254256
('title', EmptyFieldListFilter),
257+
('improvedbook', EmptyFieldListFilter),
255258
]
256259

257260

258261
class DepartmentAdminWithEmptyFieldListFilter(ModelAdmin):
259-
list_filter = [('description', EmptyFieldListFilter)]
262+
list_filter = [
263+
('description', EmptyFieldListFilter),
264+
('employee', EmptyFieldListFilter),
265+
]
260266

261267

262268
class ListFiltersTests(TestCase):
@@ -1432,6 +1438,45 @@ def test_emptylistfieldfilter(self):
14321438
queryset = changelist.get_queryset(request)
14331439
self.assertCountEqual(queryset, expected_result)
14341440

1441+
def test_emptylistfieldfilter_reverse_relationships(self):
1442+
class UserAdminReverseRelationship(UserAdmin):
1443+
list_filter = (
1444+
('books_contributed', EmptyFieldListFilter),
1445+
)
1446+
1447+
ImprovedBook.objects.create(book=self.guitar_book)
1448+
no_employees = Department.objects.create(code='NONE', description=None)
1449+
1450+
book_admin = BookAdminWithEmptyFieldListFilter(Book, site)
1451+
department_admin = DepartmentAdminWithEmptyFieldListFilter(Department, site)
1452+
user_admin = UserAdminReverseRelationship(User, site)
1453+
1454+
tests = [
1455+
# Reverse one-to-one relationship.
1456+
(
1457+
book_admin,
1458+
{'improvedbook__isempty': '1'},
1459+
[self.django_book, self.bio_book, self.djangonaut_book],
1460+
),
1461+
(book_admin, {'improvedbook__isempty': '0'}, [self.guitar_book]),
1462+
# Reverse foreign key relationship.
1463+
(department_admin, {'employee__isempty': '1'}, [no_employees]),
1464+
(department_admin, {'employee__isempty': '0'}, [self.dev, self.design]),
1465+
# Reverse many-to-many relationship.
1466+
(user_admin, {'books_contributed__isempty': '1'}, [self.alfred]),
1467+
(user_admin, {'books_contributed__isempty': '0'}, [self.bob, self.lisa]),
1468+
]
1469+
for modeladmin, query_string, expected_result in tests:
1470+
with self.subTest(
1471+
modeladmin=modeladmin.__class__.__name__,
1472+
query_string=query_string,
1473+
):
1474+
request = self.request_factory.get('/', query_string)
1475+
request.user = self.alfred
1476+
changelist = modeladmin.get_changelist_instance(request)
1477+
queryset = changelist.get_queryset(request)
1478+
self.assertCountEqual(queryset, expected_result)
1479+
14351480
def test_emptylistfieldfilter_choices(self):
14361481
modeladmin = BookAdminWithEmptyFieldListFilter(Book, site)
14371482
request = self.request_factory.get('/')

0 commit comments

Comments
 (0)