customfields.py 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. from django import forms
  2. from django.contrib.contenttypes.models import ContentType
  3. from django.db.models import Q
  4. from extras.choices import *
  5. from extras.models import *
  6. from utilities.forms import BootstrapMixin, BulkEditBaseForm, CSVModelForm
  7. __all__ = (
  8. 'CustomFieldModelCSVForm',
  9. 'CustomFieldModelBulkEditForm',
  10. 'CustomFieldModelFilterForm',
  11. 'CustomFieldModelForm',
  12. 'CustomFieldsMixin',
  13. )
  14. class CustomFieldsMixin:
  15. """
  16. Extend a Form to include custom field support.
  17. """
  18. def __init__(self, *args, **kwargs):
  19. self.custom_fields = []
  20. super().__init__(*args, **kwargs)
  21. self._append_customfield_fields()
  22. def _get_content_type(self):
  23. """
  24. Return the ContentType of the form's model.
  25. """
  26. if not hasattr(self, 'model'):
  27. raise NotImplementedError(f"{self.__class__.__name__} must specify a model class.")
  28. return ContentType.objects.get_for_model(self.model)
  29. def _get_custom_fields(self, content_type):
  30. return CustomField.objects.filter(content_types=content_type)
  31. def _get_form_field(self, customfield):
  32. return customfield.to_form_field()
  33. def _append_customfield_fields(self):
  34. """
  35. Append form fields for all CustomFields assigned to this object type.
  36. """
  37. for customfield in self._get_custom_fields(self._get_content_type()):
  38. field_name = f'cf_{customfield.name}'
  39. self.fields[field_name] = self._get_form_field(customfield)
  40. # Annotate the field in the list of CustomField form fields
  41. self.custom_fields.append(field_name)
  42. class CustomFieldModelForm(BootstrapMixin, CustomFieldsMixin, forms.ModelForm):
  43. """
  44. Extend ModelForm to include custom field support.
  45. """
  46. def _get_content_type(self):
  47. return ContentType.objects.get_for_model(self._meta.model)
  48. def _get_form_field(self, customfield):
  49. if self.instance.pk:
  50. form_field = customfield.to_form_field(set_initial=False)
  51. form_field.initial = self.instance.custom_field_data.get(customfield.name, None)
  52. return form_field
  53. return customfield.to_form_field()
  54. def clean(self):
  55. # Save custom field data on instance
  56. for cf_name in self.custom_fields:
  57. key = cf_name[3:] # Strip "cf_" from field name
  58. value = self.cleaned_data.get(cf_name)
  59. empty_values = self.fields[cf_name].empty_values
  60. # Convert "empty" values to null
  61. self.instance.custom_field_data[key] = value if value not in empty_values else None
  62. return super().clean()
  63. class CustomFieldModelCSVForm(CSVModelForm, CustomFieldModelForm):
  64. def _get_form_field(self, customfield):
  65. return customfield.to_form_field(for_csv_import=True)
  66. class CustomFieldModelBulkEditForm(BootstrapMixin, CustomFieldsMixin, BulkEditBaseForm):
  67. def _get_form_field(self, customfield):
  68. return customfield.to_form_field(set_initial=False, enforce_required=False)
  69. def _append_customfield_fields(self):
  70. """
  71. Append form fields for all CustomFields assigned to this object type.
  72. """
  73. for customfield in self._get_custom_fields(self._get_content_type()):
  74. # Annotate non-required custom fields as nullable
  75. if not customfield.required:
  76. self.nullable_fields.append(customfield.name)
  77. self.fields[customfield.name] = self._get_form_field(customfield)
  78. # Annotate the field in the list of CustomField form fields
  79. self.custom_fields.append(customfield.name)
  80. class CustomFieldModelFilterForm(BootstrapMixin, CustomFieldsMixin, forms.Form):
  81. q = forms.CharField(
  82. required=False,
  83. label='Search'
  84. )
  85. def _get_custom_fields(self, content_type):
  86. return CustomField.objects.filter(content_types=content_type).exclude(
  87. Q(filter_logic=CustomFieldFilterLogicChoices.FILTER_DISABLED) |
  88. Q(type=CustomFieldTypeChoices.TYPE_JSON)
  89. )
  90. def _get_form_field(self, customfield):
  91. return customfield.to_form_field(set_initial=False, enforce_required=False)