forms.py 6.0 KB

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