You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

admin.py 5.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. from functools import partial
  2. from django.contrib.admin.checks import InlineModelAdminChecks
  3. from django.contrib.admin.options import InlineModelAdmin, flatten_fieldsets
  4. from django.contrib.contenttypes.fields import GenericForeignKey
  5. from django.contrib.contenttypes.forms import (
  6. BaseGenericInlineFormSet, generic_inlineformset_factory,
  7. )
  8. from django.core import checks
  9. from django.core.exceptions import FieldDoesNotExist
  10. from django.forms import ALL_FIELDS
  11. from django.forms.models import modelform_defines_fields
  12. class GenericInlineModelAdminChecks(InlineModelAdminChecks):
  13. def _check_exclude_of_parent_model(self, obj, parent_model):
  14. # There's no FK to exclude, so no exclusion checks are required.
  15. return []
  16. def _check_relation(self, obj, parent_model):
  17. # There's no FK, but we do need to confirm that the ct_field and ct_fk_field are valid,
  18. # and that they are part of a GenericForeignKey.
  19. gfks = [
  20. f for f in obj.model._meta.private_fields
  21. if isinstance(f, GenericForeignKey)
  22. ]
  23. if not gfks:
  24. return [
  25. checks.Error(
  26. "'%s.%s' has no GenericForeignKey." % (
  27. obj.model._meta.app_label, obj.model._meta.object_name
  28. ),
  29. obj=obj.__class__,
  30. id='admin.E301'
  31. )
  32. ]
  33. else:
  34. # Check that the ct_field and ct_fk_fields exist
  35. try:
  36. obj.model._meta.get_field(obj.ct_field)
  37. except FieldDoesNotExist:
  38. return [
  39. checks.Error(
  40. "'ct_field' references '%s', which is not a field on '%s.%s'." % (
  41. obj.ct_field, obj.model._meta.app_label, obj.model._meta.object_name
  42. ),
  43. obj=obj.__class__,
  44. id='admin.E302'
  45. )
  46. ]
  47. try:
  48. obj.model._meta.get_field(obj.ct_fk_field)
  49. except FieldDoesNotExist:
  50. return [
  51. checks.Error(
  52. "'ct_fk_field' references '%s', which is not a field on '%s.%s'." % (
  53. obj.ct_fk_field, obj.model._meta.app_label, obj.model._meta.object_name
  54. ),
  55. obj=obj.__class__,
  56. id='admin.E303'
  57. )
  58. ]
  59. # There's one or more GenericForeignKeys; make sure that one of them
  60. # uses the right ct_field and ct_fk_field.
  61. for gfk in gfks:
  62. if gfk.ct_field == obj.ct_field and gfk.fk_field == obj.ct_fk_field:
  63. return []
  64. return [
  65. checks.Error(
  66. "'%s.%s' has no GenericForeignKey using content type field '%s' and object ID field '%s'." % (
  67. obj.model._meta.app_label, obj.model._meta.object_name, obj.ct_field, obj.ct_fk_field
  68. ),
  69. obj=obj.__class__,
  70. id='admin.E304'
  71. )
  72. ]
  73. class GenericInlineModelAdmin(InlineModelAdmin):
  74. ct_field = "content_type"
  75. ct_fk_field = "object_id"
  76. formset = BaseGenericInlineFormSet
  77. checks_class = GenericInlineModelAdminChecks
  78. def get_formset(self, request, obj=None, **kwargs):
  79. if 'fields' in kwargs:
  80. fields = kwargs.pop('fields')
  81. else:
  82. fields = flatten_fieldsets(self.get_fieldsets(request, obj))
  83. exclude = [*(self.exclude or []), *self.get_readonly_fields(request, obj)]
  84. if self.exclude is None and hasattr(self.form, '_meta') and self.form._meta.exclude:
  85. # Take the custom ModelForm's Meta.exclude into account only if the
  86. # GenericInlineModelAdmin doesn't define its own.
  87. exclude.extend(self.form._meta.exclude)
  88. exclude = exclude or None
  89. can_delete = self.can_delete and self.has_delete_permission(request, obj)
  90. defaults = {
  91. 'ct_field': self.ct_field,
  92. 'fk_field': self.ct_fk_field,
  93. 'form': self.form,
  94. 'formfield_callback': partial(self.formfield_for_dbfield, request=request),
  95. 'formset': self.formset,
  96. 'extra': self.get_extra(request, obj),
  97. 'can_delete': can_delete,
  98. 'can_order': False,
  99. 'fields': fields,
  100. 'min_num': self.get_min_num(request, obj),
  101. 'max_num': self.get_max_num(request, obj),
  102. 'exclude': exclude,
  103. **kwargs,
  104. }
  105. if defaults['fields'] is None and not modelform_defines_fields(defaults['form']):
  106. defaults['fields'] = ALL_FIELDS
  107. return generic_inlineformset_factory(self.model, **defaults)
  108. class GenericStackedInline(GenericInlineModelAdmin):
  109. template = 'admin/edit_inline/stacked.html'
  110. class GenericTabularInline(GenericInlineModelAdmin):
  111. template = 'admin/edit_inline/tabular.html'