forms.py 7.7 KB

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