forms.py 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. from __future__ import unicode_literals
  2. from collections import OrderedDict
  3. from django import forms
  4. from django.contrib.auth.models import User
  5. from django.contrib.contenttypes.models import ContentType
  6. from django.core.exceptions import ObjectDoesNotExist
  7. from mptt.forms import TreeNodeMultipleChoiceField
  8. from taggit.forms import TagField
  9. from taggit.models import Tag
  10. from dcim.models import Region
  11. from utilities.forms import add_blank_choice, BootstrapMixin, BulkEditForm, LaxURLField, JSONField, SlugField
  12. from .constants import (
  13. CF_FILTER_DISABLED, CF_TYPE_BOOLEAN, CF_TYPE_DATE, CF_TYPE_INTEGER, CF_TYPE_SELECT, CF_TYPE_URL,
  14. OBJECTCHANGE_ACTION_CHOICES,
  15. )
  16. from .models import ConfigContext, CustomField, CustomFieldValue, ImageAttachment, ObjectChange
  17. #
  18. # Custom fields
  19. #
  20. def get_custom_fields_for_model(content_type, filterable_only=False, bulk_edit=False):
  21. """
  22. Retrieve all CustomFields applicable to the given ContentType
  23. """
  24. field_dict = OrderedDict()
  25. custom_fields = CustomField.objects.filter(obj_type=content_type)
  26. if filterable_only:
  27. custom_fields = custom_fields.exclude(filter_logic=CF_FILTER_DISABLED)
  28. for cf in custom_fields:
  29. field_name = 'cf_{}'.format(str(cf.name))
  30. initial = cf.default if not bulk_edit else None
  31. # Integer
  32. if cf.type == CF_TYPE_INTEGER:
  33. field = forms.IntegerField(required=cf.required, initial=initial)
  34. # Boolean
  35. elif cf.type == CF_TYPE_BOOLEAN:
  36. choices = (
  37. (None, '---------'),
  38. (1, 'True'),
  39. (0, 'False'),
  40. )
  41. if initial is not None and initial.lower() in ['true', 'yes', '1']:
  42. initial = 1
  43. elif initial is not None and initial.lower() in ['false', 'no', '0']:
  44. initial = 0
  45. else:
  46. initial = None
  47. field = forms.NullBooleanField(
  48. required=cf.required, initial=initial, widget=forms.Select(choices=choices)
  49. )
  50. # Date
  51. elif cf.type == CF_TYPE_DATE:
  52. field = forms.DateField(required=cf.required, initial=initial, help_text="Date format: YYYY-MM-DD")
  53. # Select
  54. elif cf.type == CF_TYPE_SELECT:
  55. choices = [(cfc.pk, cfc) for cfc in cf.choices.all()]
  56. if not cf.required or bulk_edit or filterable_only:
  57. choices = [(None, '---------')] + choices
  58. # Check for a default choice
  59. default_choice = None
  60. if initial:
  61. try:
  62. default_choice = cf.choices.get(value=initial).pk
  63. except ObjectDoesNotExist:
  64. pass
  65. field = forms.TypedChoiceField(choices=choices, coerce=int, required=cf.required, initial=default_choice)
  66. # URL
  67. elif cf.type == CF_TYPE_URL:
  68. field = LaxURLField(required=cf.required, initial=initial)
  69. # Text
  70. else:
  71. field = forms.CharField(max_length=255, required=cf.required, initial=initial)
  72. field.model = cf
  73. field.label = cf.label if cf.label else cf.name.replace('_', ' ').capitalize()
  74. if cf.description:
  75. field.help_text = cf.description
  76. field_dict[field_name] = field
  77. return field_dict
  78. class CustomFieldForm(forms.ModelForm):
  79. def __init__(self, *args, **kwargs):
  80. self.custom_fields = []
  81. self.obj_type = ContentType.objects.get_for_model(self._meta.model)
  82. super(CustomFieldForm, self).__init__(*args, **kwargs)
  83. # Add all applicable CustomFields to the form
  84. custom_fields = []
  85. for name, field in get_custom_fields_for_model(self.obj_type).items():
  86. self.fields[name] = field
  87. custom_fields.append(name)
  88. self.custom_fields = custom_fields
  89. # If editing an existing object, initialize values for all custom fields
  90. if self.instance.pk:
  91. existing_values = CustomFieldValue.objects.filter(obj_type=self.obj_type, obj_id=self.instance.pk)\
  92. .select_related('field')
  93. for cfv in existing_values:
  94. self.initial['cf_{}'.format(str(cfv.field.name))] = cfv.serialized_value
  95. def _save_custom_fields(self):
  96. for field_name in self.custom_fields:
  97. try:
  98. cfv = CustomFieldValue.objects.select_related('field').get(field=self.fields[field_name].model,
  99. obj_type=self.obj_type,
  100. obj_id=self.instance.pk)
  101. except CustomFieldValue.DoesNotExist:
  102. # Skip this field if none exists already and its value is empty
  103. if self.cleaned_data[field_name] in [None, '']:
  104. continue
  105. cfv = CustomFieldValue(
  106. field=self.fields[field_name].model,
  107. obj_type=self.obj_type,
  108. obj_id=self.instance.pk
  109. )
  110. cfv.value = self.cleaned_data[field_name]
  111. cfv.save()
  112. def save(self, commit=True):
  113. obj = super(CustomFieldForm, self).save(commit)
  114. # Handle custom fields the same way we do M2M fields
  115. if commit:
  116. self._save_custom_fields()
  117. else:
  118. self.save_custom_fields = self._save_custom_fields
  119. return obj
  120. class CustomFieldBulkEditForm(BulkEditForm):
  121. def __init__(self, *args, **kwargs):
  122. super(CustomFieldBulkEditForm, self).__init__(*args, **kwargs)
  123. self.custom_fields = []
  124. self.obj_type = ContentType.objects.get_for_model(self.model)
  125. # Add all applicable CustomFields to the form
  126. custom_fields = get_custom_fields_for_model(self.obj_type, bulk_edit=True).items()
  127. for name, field in custom_fields:
  128. # Annotate non-required custom fields as nullable
  129. if not field.required:
  130. self.nullable_fields.append(name)
  131. field.required = False
  132. self.fields[name] = field
  133. # Annotate this as a custom field
  134. self.custom_fields.append(name)
  135. class CustomFieldFilterForm(forms.Form):
  136. def __init__(self, *args, **kwargs):
  137. self.obj_type = ContentType.objects.get_for_model(self.model)
  138. super(CustomFieldFilterForm, self).__init__(*args, **kwargs)
  139. # Add all applicable CustomFields to the form
  140. custom_fields = get_custom_fields_for_model(self.obj_type, filterable_only=True).items()
  141. for name, field in custom_fields:
  142. field.required = False
  143. self.fields[name] = field
  144. #
  145. # Tags
  146. #
  147. class TagForm(BootstrapMixin, forms.ModelForm):
  148. slug = SlugField()
  149. class Meta:
  150. model = Tag
  151. fields = ['name', 'slug']
  152. class AddRemoveTagsForm(forms.Form):
  153. def __init__(self, *args, **kwargs):
  154. super(AddRemoveTagsForm, self).__init__(*args, **kwargs)
  155. # Add add/remove tags fields
  156. self.fields['add_tags'] = TagField(required=False)
  157. self.fields['remove_tags'] = TagField(required=False)
  158. #
  159. # Config contexts
  160. #
  161. class ConfigContextForm(BootstrapMixin, forms.ModelForm):
  162. regions = TreeNodeMultipleChoiceField(
  163. queryset=Region.objects.all(),
  164. required=False
  165. )
  166. data = JSONField()
  167. class Meta:
  168. model = ConfigContext
  169. fields = [
  170. 'name', 'weight', 'description', 'is_active', 'regions', 'sites', 'roles', 'platforms', 'tenants', 'data',
  171. ]
  172. #
  173. # Image attachments
  174. #
  175. class ImageAttachmentForm(BootstrapMixin, forms.ModelForm):
  176. class Meta:
  177. model = ImageAttachment
  178. fields = ['name', 'image']
  179. #
  180. # Change logging
  181. #
  182. class ObjectChangeFilterForm(BootstrapMixin, CustomFieldFilterForm):
  183. model = ObjectChange
  184. q = forms.CharField(
  185. required=False,
  186. label='Search'
  187. )
  188. # TODO: Change time_0 and time_1 to time_after and time_before for django-filter==2.0
  189. time_0 = forms.DateTimeField(
  190. label='After',
  191. required=False,
  192. widget=forms.TextInput(
  193. attrs={'placeholder': 'YYYY-MM-DD hh:mm:ss'}
  194. )
  195. )
  196. time_1 = forms.DateTimeField(
  197. label='Before',
  198. required=False,
  199. widget=forms.TextInput(
  200. attrs={'placeholder': 'YYYY-MM-DD hh:mm:ss'}
  201. )
  202. )
  203. action = forms.ChoiceField(
  204. choices=add_blank_choice(OBJECTCHANGE_ACTION_CHOICES),
  205. required=False
  206. )
  207. user = forms.ModelChoiceField(
  208. queryset=User.objects.order_by('username'),
  209. required=False
  210. )