Loading django/contrib/admin/filters.py +10 −2 Original line number Diff line number Diff line Loading @@ -176,8 +176,16 @@ class RelatedFieldListFilter(FieldListFilter): self.title = self.lookup_title self.empty_value_display = model_admin.get_empty_value_display() @property def include_empty_choice(self): """ Return True if a "(None)" choice should be included, which filters out everything except empty relationships. """ return self.field.null or (self.field.is_relation and self.field.many_to_many) def has_output(self): if self.field.null: if self.include_empty_choice: extra = 1 else: extra = 0 Loading @@ -204,7 +212,7 @@ class RelatedFieldListFilter(FieldListFilter): }, [self.lookup_kwarg_isnull]), 'display': val, } if self.field.null: if self.include_empty_choice: yield { 'selected': bool(self.lookup_val_isnull), 'query_string': cl.get_query_string({ Loading django/db/models/fields/related.py +0 −3 Original line number Diff line number Diff line Loading @@ -2304,8 +2304,6 @@ class ManyToManyField(RelatedField): self.db_table = db_table self.swappable = swappable # Many-to-many fields are always nullable. self.null = True def check(self, **kwargs): errors = super(ManyToManyField, self).check(**kwargs) Loading Loading @@ -2552,7 +2550,6 @@ class ManyToManyField(RelatedField): def deconstruct(self): name, path, args, kwargs = super(ManyToManyField, self).deconstruct() # Handle the simpler arguments. del kwargs["null"] if self.db_table is not None: kwargs['db_table'] = self.db_table if self.remote_field.db_constraint is not True: Loading tests/admin_filters/tests.py +10 −0 Original line number Diff line number Diff line Loading @@ -523,6 +523,16 @@ class ListFiltersTests(TestCase): self.assertEqual(choice['selected'], True) self.assertEqual(choice['query_string'], '?books_contributed__id__exact=%d' % self.django_book.pk) # With one book, the list filter should appear because there is also a # (None) option. Book.objects.exclude(pk=self.djangonaut_book.pk).delete() filterspec = changelist.get_filters(request)[0] self.assertEqual(len(filterspec), 2) # With no books remaining, no list filters should appear. Book.objects.all().delete() filterspec = changelist.get_filters(request)[0] self.assertEqual(len(filterspec), 0) def test_relatedonlyfieldlistfilter_foreignkey(self): modeladmin = BookAdminRelatedOnlyFilter(Book, site) Loading tests/model_fields/test_field_flags.py +6 −0 Original line number Diff line number Diff line Loading @@ -216,3 +216,9 @@ class FieldFlagsTests(test.SimpleTestCase): reverse_field = field.remote_field self.assertEqual(field.model, reverse_field.related_model) self.assertEqual(field.related_model, reverse_field.model) def test_null(self): # null isn't well defined for a ManyToManyField, but changing it to # True causes backwards compatibility problems (#25320). self.assertFalse(AllFieldsModel._meta.get_field('m2m').null) self.assertTrue(AllFieldsModel._meta.get_field('reverse2').null) Loading
django/contrib/admin/filters.py +10 −2 Original line number Diff line number Diff line Loading @@ -176,8 +176,16 @@ class RelatedFieldListFilter(FieldListFilter): self.title = self.lookup_title self.empty_value_display = model_admin.get_empty_value_display() @property def include_empty_choice(self): """ Return True if a "(None)" choice should be included, which filters out everything except empty relationships. """ return self.field.null or (self.field.is_relation and self.field.many_to_many) def has_output(self): if self.field.null: if self.include_empty_choice: extra = 1 else: extra = 0 Loading @@ -204,7 +212,7 @@ class RelatedFieldListFilter(FieldListFilter): }, [self.lookup_kwarg_isnull]), 'display': val, } if self.field.null: if self.include_empty_choice: yield { 'selected': bool(self.lookup_val_isnull), 'query_string': cl.get_query_string({ Loading
django/db/models/fields/related.py +0 −3 Original line number Diff line number Diff line Loading @@ -2304,8 +2304,6 @@ class ManyToManyField(RelatedField): self.db_table = db_table self.swappable = swappable # Many-to-many fields are always nullable. self.null = True def check(self, **kwargs): errors = super(ManyToManyField, self).check(**kwargs) Loading Loading @@ -2552,7 +2550,6 @@ class ManyToManyField(RelatedField): def deconstruct(self): name, path, args, kwargs = super(ManyToManyField, self).deconstruct() # Handle the simpler arguments. del kwargs["null"] if self.db_table is not None: kwargs['db_table'] = self.db_table if self.remote_field.db_constraint is not True: Loading
tests/admin_filters/tests.py +10 −0 Original line number Diff line number Diff line Loading @@ -523,6 +523,16 @@ class ListFiltersTests(TestCase): self.assertEqual(choice['selected'], True) self.assertEqual(choice['query_string'], '?books_contributed__id__exact=%d' % self.django_book.pk) # With one book, the list filter should appear because there is also a # (None) option. Book.objects.exclude(pk=self.djangonaut_book.pk).delete() filterspec = changelist.get_filters(request)[0] self.assertEqual(len(filterspec), 2) # With no books remaining, no list filters should appear. Book.objects.all().delete() filterspec = changelist.get_filters(request)[0] self.assertEqual(len(filterspec), 0) def test_relatedonlyfieldlistfilter_foreignkey(self): modeladmin = BookAdminRelatedOnlyFilter(Book, site) Loading
tests/model_fields/test_field_flags.py +6 −0 Original line number Diff line number Diff line Loading @@ -216,3 +216,9 @@ class FieldFlagsTests(test.SimpleTestCase): reverse_field = field.remote_field self.assertEqual(field.model, reverse_field.related_model) self.assertEqual(field.related_model, reverse_field.model) def test_null(self): # null isn't well defined for a ManyToManyField, but changing it to # True causes backwards compatibility problems (#25320). self.assertFalse(AllFieldsModel._meta.get_field('m2m').null) self.assertTrue(AllFieldsModel._meta.get_field('reverse2').null)