forms.py 6.0 KB

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