forms.py 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  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 django.core.exceptions import ObjectDoesNotExist
  6. from utilities.forms import BootstrapMixin, BulkEditForm, LaxURLField
  7. from .constants import CF_FILTER_DISABLED, CF_TYPE_BOOLEAN, CF_TYPE_DATE, CF_TYPE_INTEGER, CF_TYPE_SELECT, CF_TYPE_URL
  8. from .models import CustomField, CustomFieldValue, ImageAttachment
  9. def get_custom_fields_for_model(content_type, filterable_only=False, bulk_edit=False):
  10. """
  11. Retrieve all CustomFields applicable to the given ContentType
  12. """
  13. field_dict = OrderedDict()
  14. custom_fields = CustomField.objects.filter(obj_type=content_type)
  15. if filterable_only:
  16. custom_fields = custom_fields.exclude(filter_logic=CF_FILTER_DISABLED)
  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 is not None and initial.lower() in ['true', 'yes', '1']:
  31. initial = 1
  32. elif initial is not None and 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. # Check for a default choice
  48. default_choice = None
  49. if initial:
  50. try:
  51. default_choice = cf.choices.get(value=initial).pk
  52. except ObjectDoesNotExist:
  53. pass
  54. field = forms.TypedChoiceField(choices=choices, coerce=int, required=cf.required, initial=default_choice)
  55. # URL
  56. elif cf.type == CF_TYPE_URL:
  57. field = LaxURLField(required=cf.required, initial=initial)
  58. # Text
  59. else:
  60. field = forms.CharField(max_length=255, required=cf.required, initial=initial)
  61. field.model = cf
  62. field.label = cf.label if cf.label else cf.name.replace('_', ' ').capitalize()
  63. if cf.description:
  64. field.help_text = cf.description
  65. field_dict[field_name] = field
  66. return field_dict
  67. class CustomFieldForm(forms.ModelForm):
  68. def __init__(self, *args, **kwargs):
  69. self.custom_fields = []
  70. self.obj_type = ContentType.objects.get_for_model(self._meta.model)
  71. super(CustomFieldForm, self).__init__(*args, **kwargs)
  72. # Add all applicable CustomFields to the form
  73. custom_fields = []
  74. for name, field in get_custom_fields_for_model(self.obj_type).items():
  75. self.fields[name] = field
  76. custom_fields.append(name)
  77. self.custom_fields = custom_fields
  78. # If editing an existing object, initialize values for all custom fields
  79. if self.instance.pk:
  80. existing_values = CustomFieldValue.objects.filter(obj_type=self.obj_type, obj_id=self.instance.pk)\
  81. .select_related('field')
  82. for cfv in existing_values:
  83. self.initial['cf_{}'.format(str(cfv.field.name))] = cfv.serialized_value
  84. def _save_custom_fields(self):
  85. for field_name in self.custom_fields:
  86. try:
  87. cfv = CustomFieldValue.objects.select_related('field').get(field=self.fields[field_name].model,
  88. obj_type=self.obj_type,
  89. obj_id=self.instance.pk)
  90. except CustomFieldValue.DoesNotExist:
  91. # Skip this field if none exists already and its value is empty
  92. if self.cleaned_data[field_name] in [None, '']:
  93. continue
  94. cfv = CustomFieldValue(
  95. field=self.fields[field_name].model,
  96. obj_type=self.obj_type,
  97. obj_id=self.instance.pk
  98. )
  99. cfv.value = self.cleaned_data[field_name]
  100. cfv.save()
  101. def save(self, commit=True):
  102. obj = super(CustomFieldForm, self).save(commit)
  103. # Handle custom fields the same way we do M2M fields
  104. if commit:
  105. self._save_custom_fields()
  106. else:
  107. self.save_custom_fields = self._save_custom_fields
  108. return obj
  109. class CustomFieldBulkEditForm(BulkEditForm):
  110. def __init__(self, *args, **kwargs):
  111. super(CustomFieldBulkEditForm, self).__init__(*args, **kwargs)
  112. self.custom_fields = []
  113. self.obj_type = ContentType.objects.get_for_model(self.model)
  114. # Add all applicable CustomFields to the form
  115. custom_fields = get_custom_fields_for_model(self.obj_type, bulk_edit=True).items()
  116. for name, field in custom_fields:
  117. # Annotate non-required custom fields as nullable
  118. if not field.required:
  119. self.nullable_fields.append(name)
  120. field.required = False
  121. self.fields[name] = field
  122. # Annotate this as a custom field
  123. self.custom_fields.append(name)
  124. class CustomFieldFilterForm(forms.Form):
  125. def __init__(self, *args, **kwargs):
  126. self.obj_type = ContentType.objects.get_for_model(self.model)
  127. super(CustomFieldFilterForm, self).__init__(*args, **kwargs)
  128. # Add all applicable CustomFields to the form
  129. custom_fields = get_custom_fields_for_model(self.obj_type, filterable_only=True).items()
  130. for name, field in custom_fields:
  131. field.required = False
  132. self.fields[name] = field
  133. class ImageAttachmentForm(BootstrapMixin, forms.ModelForm):
  134. class Meta:
  135. model = ImageAttachment
  136. fields = ['name', 'image']