1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143
|
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.core.exceptions 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, obj, parent_model):
# There's no FK to exclude, so no exclusion checks are required.
return []
def _check_relation(self, obj, 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 obj.model._meta.private_fields
if isinstance(f, GenericForeignKey)
]
if not gfks:
return [
checks.Error(
"'%s' has no GenericForeignKey." % obj.model._meta.label,
obj=obj.__class__,
id="admin.E301",
)
]
else:
# Check that the ct_field and ct_fk_fields exist
try:
obj.model._meta.get_field(obj.ct_field)
except FieldDoesNotExist:
return [
checks.Error(
"'ct_field' references '%s', which is not a field on '%s'."
% (
obj.ct_field,
obj.model._meta.label,
),
obj=obj.__class__,
id="admin.E302",
)
]
try:
obj.model._meta.get_field(obj.ct_fk_field)
except FieldDoesNotExist:
return [
checks.Error(
"'ct_fk_field' references '%s', which is not a field on '%s'."
% (
obj.ct_fk_field,
obj.model._meta.label,
),
obj=obj.__class__,
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 == obj.ct_field and gfk.fk_field == obj.ct_fk_field:
return []
return [
checks.Error(
"'%s' has no GenericForeignKey using content type field '%s' and "
"object ID field '%s'."
% (
obj.model._meta.label,
obj.ct_field,
obj.ct_fk_field,
),
obj=obj.__class__,
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")
else:
fields = flatten_fieldsets(self.get_fieldsets(request, obj))
exclude = [*(self.exclude or []), *self.get_readonly_fields(request, obj)]
if (
self.exclude is None
and hasattr(self.form, "_meta")
and self.form._meta.exclude
):
# Take the custom ModelForm's Meta.exclude into account only if the
# GenericInlineModelAdmin doesn't define its own.
exclude.extend(self.form._meta.exclude)
exclude = exclude or None
can_delete = self.can_delete and self.has_delete_permission(request, obj)
defaults = {
"ct_field": self.ct_field,
"fk_field": self.ct_fk_field,
"form": self.form,
"formfield_callback": partial(self.formfield_for_dbfield, request=request),
"formset": self.formset,
"extra": self.get_extra(request, obj),
"can_delete": can_delete,
"can_order": False,
"fields": fields,
"min_num": self.get_min_num(request, obj),
"max_num": self.get_max_num(request, obj),
"exclude": exclude,
**kwargs,
}
if defaults["fields"] is None and not modelform_defines_fields(
defaults["form"]
):
defaults["fields"] = ALL_FIELDS
return generic_inlineformset_factory(self.model, **defaults)
class GenericStackedInline(GenericInlineModelAdmin):
template = "admin/edit_inline/stacked.html"
class GenericTabularInline(GenericInlineModelAdmin):
template = "admin/edit_inline/tabular.html"
|