diff --git a/django/contrib/admin/checks.py b/django/contrib/admin/checks.py index 94e700cf68..a4d7066d10 100644 --- a/django/contrib/admin/checks.py +++ b/django/contrib/admin/checks.py @@ -1184,7 +1184,7 @@ class ModelAdminChecks(BaseModelAdminChecks): ) ] else: - if not isinstance(field, (models.DateField, models.DateTimeField)): + if field.get_internal_type() not in {"DateField", "DateTimeField"}: return must_be( "a DateField or DateTimeField", option="date_hierarchy", diff --git a/docs/releases/5.0.8.txt b/docs/releases/5.0.8.txt index df3bc9ae2a..31de9985c4 100644 --- a/docs/releases/5.0.8.txt +++ b/docs/releases/5.0.8.txt @@ -24,3 +24,7 @@ Bugfixes * Fixed a regression in Django 5.0.7 that caused a crash in ``LocaleMiddleware`` when processing a language code over 500 characters (:ticket:`35627`). + +* Fixed a bug in Django 5.0 that caused a system check crash when + ``ModelAdmin.date_hierarchy`` was a ``GeneratedField`` with an + ``output_field`` of ``DateField`` or ``DateTimeField`` (:ticket:`35628`). diff --git a/tests/modeladmin/test_checks.py b/tests/modeladmin/test_checks.py index f767a6c92b..94a80ca006 100644 --- a/tests/modeladmin/test_checks.py +++ b/tests/modeladmin/test_checks.py @@ -4,16 +4,17 @@ from django.contrib.admin import BooleanFieldListFilter, SimpleListFilter from django.contrib.admin.options import VERTICAL, ModelAdmin, TabularInline from django.contrib.admin.sites import AdminSite from django.core.checks import Error +from django.db import models from django.db.models import CASCADE, F, Field, ForeignKey, ManyToManyField, Model from django.db.models.functions import Upper from django.forms.models import BaseModelFormSet -from django.test import SimpleTestCase +from django.test import TestCase, skipUnlessDBFeature from django.test.utils import isolate_apps from .models import Band, Song, User, ValidationTestInlineModel, ValidationTestModel -class CheckTestCase(SimpleTestCase): +class CheckTestCase(TestCase): def assertIsInvalid( self, model_admin, @@ -97,6 +98,29 @@ class RawIdCheckTests(CheckTestCase): self.assertIsValid(TestModelAdmin, ValidationTestModel) + @isolate_apps("modeladmin") + def assertGeneratedDateTimeFieldIsValid(self, *, db_persist): + class TestModel(Model): + date = models.DateTimeField() + date_copy = models.GeneratedField( + expression=F("date"), + output_field=models.DateTimeField(), + db_persist=db_persist, + ) + + class TestModelAdmin(ModelAdmin): + date_hierarchy = "date_copy" + + self.assertIsValid(TestModelAdmin, TestModel) + + @skipUnlessDBFeature("supports_stored_generated_columns") + def test_valid_case_stored_generated_field(self): + self.assertGeneratedDateTimeFieldIsValid(db_persist=True) + + @skipUnlessDBFeature("supports_virtual_generated_columns") + def test_valid_case_virtual_generated_field(self): + self.assertGeneratedDateTimeFieldIsValid(db_persist=False) + def test_field_attname(self): class TestModelAdmin(ModelAdmin): raw_id_fields = ["band_id"] @@ -1029,6 +1053,33 @@ class DateHierarchyCheckTests(CheckTestCase): "admin.E128", ) + @isolate_apps("modeladmin") + def assertGeneratedIntegerFieldIsInvalid(self, *, db_persist): + class TestModel(Model): + generated = models.GeneratedField( + expression=models.Value(1), + output_field=models.IntegerField(), + db_persist=db_persist, + ) + + class TestModelAdmin(ModelAdmin): + date_hierarchy = "generated" + + self.assertIsInvalid( + TestModelAdmin, + TestModel, + "The value of 'date_hierarchy' must be a DateField or DateTimeField.", + "admin.E128", + ) + + @skipUnlessDBFeature("supports_stored_generated_columns") + def test_related_invalid_field_type_stored_generated_field(self): + self.assertGeneratedIntegerFieldIsInvalid(db_persist=True) + + @skipUnlessDBFeature("supports_virtual_generated_columns") + def test_related_invalid_field_type_virtual_generated_field(self): + self.assertGeneratedIntegerFieldIsInvalid(db_persist=False) + def test_valid_case(self): class TestModelAdmin(ModelAdmin): date_hierarchy = "pub_date"