mirror of https://github.com/django/django.git
Fixed #35198 -- Fixed facet filters crash on querysets with no primary key.
Thanks Simon Alef for the report.
Regression in 868e2fcdda
.
This commit is contained in:
parent
3cb1ba50cc
commit
a738281265
|
@ -140,7 +140,7 @@ class SimpleListFilter(FacetsMixin, ListFilter):
|
||||||
if lookup_qs is not None:
|
if lookup_qs is not None:
|
||||||
counts[f"{i}__c"] = models.Count(
|
counts[f"{i}__c"] = models.Count(
|
||||||
pk_attname,
|
pk_attname,
|
||||||
filter=lookup_qs.query.where,
|
filter=models.Q(pk__in=lookup_qs),
|
||||||
)
|
)
|
||||||
self.used_parameters[self.parameter_name] = original_value
|
self.used_parameters[self.parameter_name] = original_value
|
||||||
return counts
|
return counts
|
||||||
|
|
|
@ -29,3 +29,6 @@ Bugfixes
|
||||||
* Fixed a regression in Django 5.0 that caused a crash when reloading a test
|
* Fixed a regression in Django 5.0 that caused a crash when reloading a test
|
||||||
database and a base queryset for a base manager used ``prefetch_related()``
|
database and a base queryset for a base manager used ``prefetch_related()``
|
||||||
(:ticket:`35238`).
|
(:ticket:`35238`).
|
||||||
|
|
||||||
|
* Fixed a bug in Django 5.0 where facet filters in the admin would crash on a
|
||||||
|
``SimpleListFilter`` using a queryset without primary keys (:ticket:`35198`).
|
||||||
|
|
|
@ -17,7 +17,7 @@ from django.contrib.admin.options import IncorrectLookupParameters, ShowFacets
|
||||||
from django.contrib.auth.admin import UserAdmin
|
from django.contrib.auth.admin import UserAdmin
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
from django.db import connection
|
from django.db import connection, models
|
||||||
from django.test import RequestFactory, SimpleTestCase, TestCase, override_settings
|
from django.test import RequestFactory, SimpleTestCase, TestCase, override_settings
|
||||||
|
|
||||||
from .models import Book, Bookmark, Department, Employee, ImprovedBook, TaggedItem
|
from .models import Book, Bookmark, Department, Employee, ImprovedBook, TaggedItem
|
||||||
|
@ -154,6 +154,30 @@ class EmployeeNameCustomDividerFilter(FieldListFilter):
|
||||||
return [self.lookup_kwarg]
|
return [self.lookup_kwarg]
|
||||||
|
|
||||||
|
|
||||||
|
class DepartmentOwnershipListFilter(SimpleListFilter):
|
||||||
|
title = "Department Ownership"
|
||||||
|
parameter_name = "department_ownership"
|
||||||
|
|
||||||
|
def lookups(self, request, model_admin):
|
||||||
|
return [
|
||||||
|
("DEV_OWNED", "Owned by Dev Department"),
|
||||||
|
("OTHER", "Other"),
|
||||||
|
]
|
||||||
|
|
||||||
|
def queryset(self, request, queryset):
|
||||||
|
queryset = queryset.annotate(
|
||||||
|
owned_book_count=models.Count(
|
||||||
|
"employee__department",
|
||||||
|
filter=models.Q(employee__department__code="DEV"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
if self.value() == "DEV_OWNED":
|
||||||
|
return queryset.filter(owned_book_count__gt=0)
|
||||||
|
elif self.value() == "OTHER":
|
||||||
|
return queryset.filter(owned_book_count=0)
|
||||||
|
|
||||||
|
|
||||||
class CustomUserAdmin(UserAdmin):
|
class CustomUserAdmin(UserAdmin):
|
||||||
list_filter = ("books_authored", "books_contributed")
|
list_filter = ("books_authored", "books_contributed")
|
||||||
|
|
||||||
|
@ -229,6 +253,7 @@ class DecadeFilterBookAdmin(ModelAdmin):
|
||||||
("author__email", AllValuesFieldListFilter),
|
("author__email", AllValuesFieldListFilter),
|
||||||
("contributors", RelatedOnlyFieldListFilter),
|
("contributors", RelatedOnlyFieldListFilter),
|
||||||
("category", EmptyFieldListFilter),
|
("category", EmptyFieldListFilter),
|
||||||
|
DepartmentOwnershipListFilter,
|
||||||
)
|
)
|
||||||
ordering = ("-id",)
|
ordering = ("-id",)
|
||||||
|
|
||||||
|
@ -336,6 +361,14 @@ class ListFiltersTests(TestCase):
|
||||||
cls.bob = User.objects.create_user("bob", "bob@example.com")
|
cls.bob = User.objects.create_user("bob", "bob@example.com")
|
||||||
cls.lisa = User.objects.create_user("lisa", "lisa@example.com")
|
cls.lisa = User.objects.create_user("lisa", "lisa@example.com")
|
||||||
|
|
||||||
|
# Departments
|
||||||
|
cls.dev = Department.objects.create(code="DEV", description="Development")
|
||||||
|
cls.design = Department.objects.create(code="DSN", description="Design")
|
||||||
|
|
||||||
|
# Employees
|
||||||
|
cls.john = Employee.objects.create(name="John Blue", department=cls.dev)
|
||||||
|
cls.jack = Employee.objects.create(name="Jack Red", department=cls.design)
|
||||||
|
|
||||||
# Books
|
# Books
|
||||||
cls.djangonaut_book = Book.objects.create(
|
cls.djangonaut_book = Book.objects.create(
|
||||||
title="Djangonaut: an art of living",
|
title="Djangonaut: an art of living",
|
||||||
|
@ -345,6 +378,7 @@ class ListFiltersTests(TestCase):
|
||||||
date_registered=cls.today,
|
date_registered=cls.today,
|
||||||
availability=True,
|
availability=True,
|
||||||
category="non-fiction",
|
category="non-fiction",
|
||||||
|
employee=cls.john,
|
||||||
)
|
)
|
||||||
cls.bio_book = Book.objects.create(
|
cls.bio_book = Book.objects.create(
|
||||||
title="Django: a biography",
|
title="Django: a biography",
|
||||||
|
@ -354,6 +388,7 @@ class ListFiltersTests(TestCase):
|
||||||
no=207,
|
no=207,
|
||||||
availability=False,
|
availability=False,
|
||||||
category="fiction",
|
category="fiction",
|
||||||
|
employee=cls.john,
|
||||||
)
|
)
|
||||||
cls.django_book = Book.objects.create(
|
cls.django_book = Book.objects.create(
|
||||||
title="The Django Book",
|
title="The Django Book",
|
||||||
|
@ -363,6 +398,7 @@ class ListFiltersTests(TestCase):
|
||||||
date_registered=cls.today,
|
date_registered=cls.today,
|
||||||
no=103,
|
no=103,
|
||||||
availability=True,
|
availability=True,
|
||||||
|
employee=cls.jack,
|
||||||
)
|
)
|
||||||
cls.guitar_book = Book.objects.create(
|
cls.guitar_book = Book.objects.create(
|
||||||
title="Guitar for dummies",
|
title="Guitar for dummies",
|
||||||
|
@ -374,14 +410,6 @@ class ListFiltersTests(TestCase):
|
||||||
)
|
)
|
||||||
cls.guitar_book.contributors.set([cls.bob, cls.lisa])
|
cls.guitar_book.contributors.set([cls.bob, cls.lisa])
|
||||||
|
|
||||||
# Departments
|
|
||||||
cls.dev = Department.objects.create(code="DEV", description="Development")
|
|
||||||
cls.design = Department.objects.create(code="DSN", description="Design")
|
|
||||||
|
|
||||||
# Employees
|
|
||||||
cls.john = Employee.objects.create(name="John Blue", department=cls.dev)
|
|
||||||
cls.jack = Employee.objects.create(name="Jack Red", department=cls.design)
|
|
||||||
|
|
||||||
def assertChoicesDisplay(self, choices, expected_displays):
|
def assertChoicesDisplay(self, choices, expected_displays):
|
||||||
for choice, expected_display in zip(choices, expected_displays, strict=True):
|
for choice, expected_display in zip(choices, expected_displays, strict=True):
|
||||||
self.assertEqual(choice["display"], expected_display)
|
self.assertEqual(choice["display"], expected_display)
|
||||||
|
@ -905,6 +933,7 @@ class ListFiltersTests(TestCase):
|
||||||
filterspec.lookup_choices,
|
filterspec.lookup_choices,
|
||||||
[
|
[
|
||||||
(self.djangonaut_book.pk, "Djangonaut: an art of living"),
|
(self.djangonaut_book.pk, "Djangonaut: an art of living"),
|
||||||
|
(self.bio_book.pk, "Django: a biography"),
|
||||||
(self.django_book.pk, "The Django Book"),
|
(self.django_book.pk, "The Django Book"),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
@ -1407,6 +1436,8 @@ class ListFiltersTests(TestCase):
|
||||||
["All", "bob (1)", "lisa (1)", "??? (3)"],
|
["All", "bob (1)", "lisa (1)", "??? (3)"],
|
||||||
# EmptyFieldListFilter.
|
# EmptyFieldListFilter.
|
||||||
["All", "Empty (2)", "Not empty (2)"],
|
["All", "Empty (2)", "Not empty (2)"],
|
||||||
|
# SimpleListFilter with join relations.
|
||||||
|
["All", "Owned by Dev Department (2)", "Other (2)"],
|
||||||
]
|
]
|
||||||
for filterspec, expected_displays in zip(filters, tests, strict=True):
|
for filterspec, expected_displays in zip(filters, tests, strict=True):
|
||||||
with self.subTest(filterspec.__class__.__name__):
|
with self.subTest(filterspec.__class__.__name__):
|
||||||
|
@ -1482,6 +1513,8 @@ class ListFiltersTests(TestCase):
|
||||||
["All", "bob (0)", "lisa (0)", "??? (2)"],
|
["All", "bob (0)", "lisa (0)", "??? (2)"],
|
||||||
# EmptyFieldListFilter.
|
# EmptyFieldListFilter.
|
||||||
["All", "Empty (0)", "Not empty (2)"],
|
["All", "Empty (0)", "Not empty (2)"],
|
||||||
|
# SimpleListFilter with join relations.
|
||||||
|
["All", "Owned by Dev Department (2)", "Other (0)"],
|
||||||
]
|
]
|
||||||
for filterspec, expected_displays in zip(filters, tests, strict=True):
|
for filterspec, expected_displays in zip(filters, tests, strict=True):
|
||||||
with self.subTest(filterspec.__class__.__name__):
|
with self.subTest(filterspec.__class__.__name__):
|
||||||
|
@ -1525,6 +1558,8 @@ class ListFiltersTests(TestCase):
|
||||||
["All", "bob", "lisa", "???"],
|
["All", "bob", "lisa", "???"],
|
||||||
# EmptyFieldListFilter.
|
# EmptyFieldListFilter.
|
||||||
["All", "Empty", "Not empty"],
|
["All", "Empty", "Not empty"],
|
||||||
|
# SimpleListFilter with join relations.
|
||||||
|
["All", "Owned by Dev Department", "Other"],
|
||||||
]
|
]
|
||||||
for filterspec, expected_displays in zip(filters, tests, strict=True):
|
for filterspec, expected_displays in zip(filters, tests, strict=True):
|
||||||
with self.subTest(filterspec.__class__.__name__):
|
with self.subTest(filterspec.__class__.__name__):
|
||||||
|
|
Loading…
Reference in New Issue