diff --git a/django/contrib/admin/checks.py b/django/contrib/admin/checks.py index badc1514cd..dfe4cd04ec 100644 --- a/django/contrib/admin/checks.py +++ b/django/contrib/admin/checks.py @@ -846,7 +846,7 @@ class InlineModelAdminChecks(BaseModelAdminChecks): def check(self, cls, parent_model, **kwargs): errors = super(InlineModelAdminChecks, self).check(cls, model=cls.model, **kwargs) - errors.extend(self._check_fk_name(cls, parent_model)) + errors.extend(self._check_relation(cls, parent_model)) errors.extend(self._check_exclude_of_parent_model(cls, parent_model)) errors.extend(self._check_extra(cls)) errors.extend(self._check_max_num(cls)) @@ -861,7 +861,7 @@ class InlineModelAdminChecks(BaseModelAdminChecks): return [] # Skip if `fk_name` is invalid. - if self._check_fk_name(cls, parent_model): + if self._check_relation(cls, parent_model): return [] if cls.exclude is None: @@ -883,7 +883,7 @@ class InlineModelAdminChecks(BaseModelAdminChecks): else: return [] - def _check_fk_name(self, cls, parent_model): + def _check_relation(self, cls, parent_model): try: _get_foreign_key(parent_model, cls.model, fk_name=cls.fk_name) except ValueError as e: diff --git a/django/contrib/contenttypes/admin.py b/django/contrib/contenttypes/admin.py index c67fbca288..8da6546a21 100644 --- a/django/contrib/contenttypes/admin.py +++ b/django/contrib/contenttypes/admin.py @@ -2,19 +2,97 @@ from __future__ import unicode_literals from functools import partial +from django.contrib.admin.checks import InlineModelAdminChecks from django.contrib.admin.options import InlineModelAdmin, flatten_fieldsets +from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.forms import ( BaseGenericInlineFormSet, generic_inlineformset_factory ) +from django.core import checks +from django.db.models.fields import FieldDoesNotExist from django.forms import ALL_FIELDS from django.forms.models import modelform_defines_fields +class GenericInlineModelAdminChecks(InlineModelAdminChecks): + def _check_exclude_of_parent_model(self, cls, parent_model): + # There's no FK to exclude, so no exclusion checks are required. + return [] + + def _check_relation(self, cls, parent_model): + # There's no FK, but we do need to confirm that the ct_field and ct_fk_field are valid, + # and that they are part of a GenericForeignKey. + + gfks = [ + f for f in cls.model._meta.virtual_fields + if isinstance(f, GenericForeignKey) + ] + if len(gfks) == 0: + return [ + checks.Error( + "'%s.%s' has no GenericForeignKey." % ( + cls.model._meta.app_label, cls.model._meta.object_name + ), + hint=None, + obj=cls, + id='admin.E301' + ) + ] + else: + # Check that the ct_field and ct_fk_fields exist + try: + cls.model._meta.get_field(cls.ct_field) + except FieldDoesNotExist: + return [ + checks.Error( + "'ct_field' references '%s', which is not a field on '%s.%s'." % ( + cls.ct_field, cls.model._meta.app_label, cls.model._meta.object_name + ), + hint=None, + obj=cls, + id='admin.E302' + ) + ] + + try: + cls.model._meta.get_field(cls.ct_fk_field) + except FieldDoesNotExist: + return [ + checks.Error( + "'ct_fk_field' references '%s', which is not a field on '%s.%s'." % ( + cls.ct_fk_field, cls.model._meta.app_label, cls.model._meta.object_name + ), + hint=None, + obj=cls, + id='admin.E303' + ) + ] + + # There's one or more GenericForeignKeys; make sure that one of them + # uses the right ct_field and ct_fk_field. + for gfk in gfks: + if gfk.ct_field == cls.ct_field and gfk.fk_field == cls.ct_fk_field: + return [] + + return [ + checks.Error( + "'%s.%s' has no GenericForeignKey using content type field '%s' and object ID field '%s'." % ( + cls.model._meta.app_label, cls.model._meta.object_name, cls.ct_field, cls.ct_fk_field + ), + hint=None, + obj=cls, + id='admin.E304' + ) + ] + + class GenericInlineModelAdmin(InlineModelAdmin): ct_field = "content_type" ct_fk_field = "object_id" formset = BaseGenericInlineFormSet + checks_class = GenericInlineModelAdminChecks + def get_formset(self, request, obj=None, **kwargs): if 'fields' in kwargs: fields = kwargs.pop('fields') diff --git a/docs/ref/checks.txt b/docs/ref/checks.txt index 9262a461c5..f7c2e721cb 100644 --- a/docs/ref/checks.txt +++ b/docs/ref/checks.txt @@ -199,11 +199,23 @@ The following checks are performed on any inline on a :class:`~django.contrib.admin.ModelAdmin`. * **admin.E201**: Cannot exclude the field ````, because it is the foreign key to the parent model ``%s.%s``. -* **admin.E202**: ```` has more than one ForeignKey to ````. +* **admin.E202**: ```` has no ForeignKey to ````./```` has more than one ForeignKey to ````. * **admin.E203**: The value of ``extra`` must be an integer. * **admin.E204**: The value of ``max_num`` must be an integer. * **admin.E205**: The value of ``formset`` must inherit from ``BaseModelFormSet``. +GenericInlineModelAdmin +~~~~~~~~~~~~~~~~~~~~~~~ + +The following checks are performed on any +:class:`~django.contrib.contenttypes.admin.GenericInlineModelAdmin` that is +registered as an inline on a :class:`~django.contrib.admin.ModelAdmin`. + +* **admin.E301**: 'ct_field' references ``