diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index bf8f2717c2..04a3552780 100644 --- a/django/contrib/admin/options.py +++ b/django/contrib/admin/options.py @@ -417,7 +417,10 @@ class BaseModelAdmin(six.with_metaclass(forms.MediaDefiningClass)): # since it's ignored in ChangeList.get_filters(). return True model = field.rel.to - rel_name = field.rel.get_related_field().name + if hasattr(field.rel, 'get_related_field'): + rel_name = field.rel.get_related_field().name + else: + rel_name = None elif isinstance(field, RelatedObject): model = field.model rel_name = model._meta.pk.name diff --git a/docs/releases/1.7.1.txt b/docs/releases/1.7.1.txt index bf12d81a34..f8d3936719 100644 --- a/docs/releases/1.7.1.txt +++ b/docs/releases/1.7.1.txt @@ -113,3 +113,5 @@ Bugfixes * Added a prompt to the migrations questioner when removing the null constraint from a field to prevent an IntegrityError on existing NULL rows (:ticket:`23609`). + +* Fixed generic relations in ``ModelAdmin.list_filter`` (:ticket:`23616`). diff --git a/tests/admin_filters/models.py b/tests/admin_filters/models.py index 3025d1b4a4..36e098e62a 100644 --- a/tests/admin_filters/models.py +++ b/tests/admin_filters/models.py @@ -1,6 +1,8 @@ from __future__ import unicode_literals from django.contrib.auth.models import User +from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation +from django.contrib.contenttypes.models import ContentType from django.db import models from django.utils.encoding import python_2_unicode_compatible @@ -35,3 +37,23 @@ class Employee(models.Model): def __str__(self): return self.name + + +@python_2_unicode_compatible +class TaggedItem(models.Model): + tag = models.SlugField() + content_type = models.ForeignKey(ContentType, related_name='tagged_items') + object_id = models.PositiveIntegerField() + content_object = GenericForeignKey('content_type', 'object_id') + + def __str__(self): + return self.tag + + +@python_2_unicode_compatible +class Bookmark(models.Model): + url = models.URLField() + tags = GenericRelation(TaggedItem) + + def __str__(self): + return self.url diff --git a/tests/admin_filters/tests.py b/tests/admin_filters/tests.py index dcd4236776..2b9836f5d1 100644 --- a/tests/admin_filters/tests.py +++ b/tests/admin_filters/tests.py @@ -12,7 +12,7 @@ from django.test import TestCase, RequestFactory, override_settings from django.utils.encoding import force_text from django.utils import six -from .models import Book, Department, Employee +from .models import Book, Department, Employee, Bookmark, TaggedItem def select_by(dictlist, key, value): @@ -205,6 +205,10 @@ class DepartmentFilterDynamicValueBookAdmin(EmployeeAdmin): list_filter = [DepartmentListFilterLookupWithDynamicValue, ] +class BookmarkAdminGenericRelation(ModelAdmin): + list_filter = ['tags__tag'] + + class ListFiltersTests(TestCase): def setUp(self): @@ -539,6 +543,24 @@ class ListFiltersTests(TestCase): expected = [(self.bob.pk, 'bob'), (self.lisa.pk, 'lisa')] self.assertEqual(sorted(filterspec.lookup_choices), sorted(expected)) + def test_listfilter_genericrelation(self): + django_bookmark = Bookmark.objects.create(url='https://www.djangoproject.com/') + python_bookmark = Bookmark.objects.create(url='https://www.python.org/') + kernel_bookmark = Bookmark.objects.create(url='https://www.kernel.org/') + + TaggedItem.objects.create(content_object=django_bookmark, tag='python') + TaggedItem.objects.create(content_object=python_bookmark, tag='python') + TaggedItem.objects.create(content_object=kernel_bookmark, tag='linux') + + modeladmin = BookmarkAdminGenericRelation(Bookmark, site) + + request = self.request_factory.get('/', {'tags__tag': 'python'}) + changelist = self.get_changelist(request, Bookmark, modeladmin) + queryset = changelist.get_queryset(request) + + expected = [python_bookmark, django_bookmark] + self.assertEqual(list(queryset), expected) + def test_booleanfieldlistfilter(self): modeladmin = BookAdmin(Book, site) self.verify_booleanfieldlistfilter(modeladmin)