forms.py 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. from __future__ import unicode_literals
  2. from collections import OrderedDict
  3. from django import forms
  4. from django.contrib.contenttypes.models import ContentType
  5. from utilities.forms import BootstrapMixin, BulkEditForm, LaxURLField
  6. from .constants import CF_FILTER_DISABLED, CF_TYPE_BOOLEAN, CF_TYPE_DATE, CF_TYPE_INTEGER, CF_TYPE_SELECT, CF_TYPE_URL
  7. from .models import CustomField, CustomFieldValue, ImageAttachment
  8. def get_custom_fields_for_model(content_type, filterable_only=False, bulk_edit=False):
  9. """
  10. Retrieve all CustomFields applicable to the given ContentType
  11. """
  12. field_dict = OrderedDict()
  13. custom_fields = CustomField.objects.filter(obj_type=content_type)
  14. if filterable_only:
  15. custom_fields = custom_fields.exclude(filter_logic=CF_FILTER_DISABLED)
  16. for cf in custom_fields:
  17. field_name = 'cf_{}'.format(str(cf.name))
  18. initial = cf.default if not bulk_edit else None
  19. # Integer
  20. if cf.type == CF_TYPE_INTEGER:
  21. field = forms.IntegerField(required=cf.required, initial=initial)
  22. # Boolean
  23. elif cf.type == CF_TYPE_BOOLEAN:
  24. choices = (
  25. (None, '---------'),
  26. (1, 'True'),
  27. (0, 'False'),
  28. )
  29. if initial is not None and initial.lower() in ['true', 'yes', '1']:
  30. initial = 1
  31. elif initial is not None and initial.lower() in ['false', 'no', '0']:
  32. initial = 0
  33. else:
  34. initial = None
  35. field = forms.NullBooleanField(
  36. required=cf.required, initial=initial, widget=forms.Select(choices=choices)
  37. )
  38. # Date
  39. elif cf.type == CF_TYPE_DATE:
  40. field = forms.DateField(required=cf.required, initial=initial, help_text="Date format: YYYY-MM-DD")
  41. # Select
  42. elif cf.type == CF_TYPE_SELECT:
  43. choices = [(cfc.pk, cfc) for cfc in cf.choices.all()]
  44. if not cf.required or bulk_edit or filterable_only:
  45. choices = [(None, '---------')] + choices
  46. field = forms.TypedChoiceField(choices=choices, coerce=int, required=cf.required)
  47. # URL
  48. elif cf.type == CF_TYPE_URL:
  49. field = LaxURLField(required=cf.required, initial=initial)
  50. # Text
  51. else:
  52. field = forms.CharField(max_length=255, required=cf.required, initial=initial)
  53. field.model = cf
  54. field.label = cf.label if cf.label else cf.name.replace('_', ' ').capitalize()
  55. if cf.description:
  56. field.help_text = cf.description
  57. field_dict[field_name] = field
  58. return field_dict
  59. class CustomFieldForm(forms.ModelForm):
  60. def __init__(self, *args, **kwargs):
  61. self.custom_fields = []
  62. self.obj_type = ContentType.objects.get_for_model(self._meta.model)
  63. super(CustomFieldForm, self).__init__(*args, **kwargs)
  64. # Add all applicable CustomFields to the form
  65. custom_fields = []
  66. for name, field in get_custom_fields_for_model(self.obj_type).items():
  67. self.fields[name] = field
  68. custom_fields.append(name)
  69. self.custom_fields = custom_fields
  70. # If editing an existing object, initialize values for all custom fields
  71. if self.instance.pk:
  72. existing_values = CustomFieldValue.objects.filter(obj_type=self.obj_type, obj_id=self.instance.pk)\
  73. .select_related('field')
  74. for cfv in existing_values:
  75. self.initial['cf_{}'.format(str(cfv.field.name))] = cfv.serialized_value
  76. def _save_custom_fields(self):
  77. for field_name in self.custom_fields:
  78. try:
  79. cfv = CustomFieldValue.objects.select_related('field').get(field=self.fields[field_name].model,
  80. obj_type=self.obj_type,
  81. obj_id=self.instance.pk)
  82. except CustomFieldValue.DoesNotExist:
  83. # Skip this field if none exists already and its value is empty
  84. if self.cleaned_data[field_name] in [None, '']:
  85. continue
  86. cfv = CustomFieldValue(
  87. field=self.fields[field_name].model,
  88. obj_type=self.obj_type,
  89. obj_id=self.instance.pk
  90. )
  91. cfv.value = self.cleaned_data[field_name]
  92. cfv.save()
  93. def save(self, commit=True):
  94. obj = super(CustomFieldForm, self).save(commit)
  95. # Handle custom fields the same way we do M2M fields
  96. if commit:
  97. self._save_custom_fields()
  98. else:
  99. self.save_custom_fields = self._save_custom_fields
  100. return obj
  101. class CustomFieldBulkEditForm(BulkEditForm):
  102. def __init__(self, *args, **kwargs):
  103. super(CustomFieldBulkEditForm, self).__init__(*args, **kwargs)
  104. self.custom_fields = []
  105. self.obj_type = ContentType.objects.get_for_model(self.model)
  106. # Add all applicable CustomFields to the form
  107. custom_fields = get_custom_fields_for_model(self.obj_type, bulk_edit=True).items()
  108. for name, field in custom_fields:
  109. # Annotate non-required custom fields as nullable
  110. if not field.required:
  111. self.nullable_fields.append(name)
  112. field.required = False
  113. self.fields[name] = field
  114. # Annotate this as a custom field
  115. self.custom_fields.append(name)
  116. class CustomFieldFilterForm(forms.Form):
  117. def __init__(self, *args, **kwargs):
  118. self.obj_type = ContentType.objects.get_for_model(self.model)
  119. super(CustomFieldFilterForm, self).__init__(*args, **kwargs)
  120. # Add all applicable CustomFields to the form
  121. custom_fields = get_custom_fields_for_model(self.obj_type, filterable_only=True).items()
  122. for name, field in custom_fields:
  123. field.required = False
  124. self.fields[name] = field
  125. class ImageAttachmentForm(BootstrapMixin, forms.ModelForm):
  126. class Meta:
  127. model = ImageAttachment
  128. fields = ['name', 'image']