forms.py 6.3 KB

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