forms.py 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. from django import forms
  2. from django.db.models import Count
  3. from dcim.models import Site, Device, Interface, Rack, VIRTUAL_IFACE_TYPES
  4. from extras.forms import CustomFieldForm, CustomFieldBulkEditForm, CustomFieldFilterForm
  5. from tenancy.models import Tenant
  6. from utilities.forms import (
  7. APISelect, BootstrapMixin, BulkImportForm, CommentField, CSVDataField, FilterChoiceField, Livesearch, SmallTextarea,
  8. SlugField,
  9. )
  10. from .models import Circuit, CircuitTermination, CircuitType, Provider
  11. #
  12. # Providers
  13. #
  14. class ProviderForm(BootstrapMixin, CustomFieldForm):
  15. slug = SlugField()
  16. comments = CommentField()
  17. class Meta:
  18. model = Provider
  19. fields = ['name', 'slug', 'asn', 'account', 'portal_url', 'noc_contact', 'admin_contact', 'comments']
  20. widgets = {
  21. 'noc_contact': SmallTextarea(attrs={'rows': 5}),
  22. 'admin_contact': SmallTextarea(attrs={'rows': 5}),
  23. }
  24. help_texts = {
  25. 'name': "Full name of the provider",
  26. 'asn': "BGP autonomous system number (if applicable)",
  27. 'portal_url': "URL of the provider's customer support portal",
  28. 'noc_contact': "NOC email address and phone number",
  29. 'admin_contact': "Administrative contact email address and phone number",
  30. }
  31. class ProviderFromCSVForm(forms.ModelForm):
  32. class Meta:
  33. model = Provider
  34. fields = ['name', 'slug', 'asn', 'account', 'portal_url']
  35. class ProviderImportForm(BootstrapMixin, BulkImportForm):
  36. csv = CSVDataField(csv_form=ProviderFromCSVForm)
  37. class ProviderBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
  38. pk = forms.ModelMultipleChoiceField(queryset=Provider.objects.all(), widget=forms.MultipleHiddenInput)
  39. asn = forms.IntegerField(required=False, label='ASN')
  40. account = forms.CharField(max_length=30, required=False, label='Account number')
  41. portal_url = forms.URLField(required=False, label='Portal')
  42. noc_contact = forms.CharField(required=False, widget=SmallTextarea, label='NOC contact')
  43. admin_contact = forms.CharField(required=False, widget=SmallTextarea, label='Admin contact')
  44. comments = CommentField(widget=SmallTextarea)
  45. class Meta:
  46. nullable_fields = ['asn', 'account', 'portal_url', 'noc_contact', 'admin_contact', 'comments']
  47. class ProviderFilterForm(BootstrapMixin, CustomFieldFilterForm):
  48. model = Provider
  49. q = forms.CharField(required=False, label='Search')
  50. site = FilterChoiceField(queryset=Site.objects.all(), to_field_name='slug')
  51. asn = forms.IntegerField(required=False, label='ASN')
  52. #
  53. # Circuit types
  54. #
  55. class CircuitTypeForm(BootstrapMixin, forms.ModelForm):
  56. slug = SlugField()
  57. class Meta:
  58. model = CircuitType
  59. fields = ['name', 'slug']
  60. #
  61. # Circuits
  62. #
  63. class CircuitForm(BootstrapMixin, CustomFieldForm):
  64. comments = CommentField()
  65. class Meta:
  66. model = Circuit
  67. fields = ['cid', 'type', 'provider', 'tenant', 'install_date', 'commit_rate', 'description', 'comments']
  68. help_texts = {
  69. 'cid': "Unique circuit ID",
  70. 'install_date': "Format: YYYY-MM-DD",
  71. 'commit_rate': "Committed rate",
  72. }
  73. class CircuitFromCSVForm(forms.ModelForm):
  74. provider = forms.ModelChoiceField(Provider.objects.all(), to_field_name='name',
  75. error_messages={'invalid_choice': 'Provider not found.'})
  76. type = forms.ModelChoiceField(CircuitType.objects.all(), to_field_name='name',
  77. error_messages={'invalid_choice': 'Invalid circuit type.'})
  78. tenant = forms.ModelChoiceField(Tenant.objects.all(), to_field_name='name', required=False,
  79. error_messages={'invalid_choice': 'Tenant not found.'})
  80. class Meta:
  81. model = Circuit
  82. fields = ['cid', 'provider', 'type', 'tenant', 'install_date', 'commit_rate', 'description']
  83. class CircuitImportForm(BootstrapMixin, BulkImportForm):
  84. csv = CSVDataField(csv_form=CircuitFromCSVForm)
  85. class CircuitBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
  86. pk = forms.ModelMultipleChoiceField(queryset=Circuit.objects.all(), widget=forms.MultipleHiddenInput)
  87. type = forms.ModelChoiceField(queryset=CircuitType.objects.all(), required=False)
  88. provider = forms.ModelChoiceField(queryset=Provider.objects.all(), required=False)
  89. tenant = forms.ModelChoiceField(queryset=Tenant.objects.all(), required=False)
  90. commit_rate = forms.IntegerField(required=False, label='Commit rate (Kbps)')
  91. description = forms.CharField(max_length=100, required=False)
  92. comments = CommentField(widget=SmallTextarea)
  93. class Meta:
  94. nullable_fields = ['tenant', 'commit_rate', 'description', 'comments']
  95. class CircuitFilterForm(BootstrapMixin, CustomFieldFilterForm):
  96. model = Circuit
  97. q = forms.CharField(required=False, label='Search')
  98. type = FilterChoiceField(
  99. queryset=CircuitType.objects.annotate(filter_count=Count('circuits')),
  100. to_field_name='slug'
  101. )
  102. provider = FilterChoiceField(
  103. queryset=Provider.objects.annotate(filter_count=Count('circuits')),
  104. to_field_name='slug'
  105. )
  106. tenant = FilterChoiceField(
  107. queryset=Tenant.objects.annotate(filter_count=Count('circuits')),
  108. to_field_name='slug',
  109. null_option=(0, 'None')
  110. )
  111. site = FilterChoiceField(
  112. queryset=Site.objects.annotate(filter_count=Count('circuit_terminations')),
  113. to_field_name='slug'
  114. )
  115. #
  116. # Circuit terminations
  117. #
  118. class CircuitTerminationForm(BootstrapMixin, forms.ModelForm):
  119. site = forms.ModelChoiceField(
  120. queryset=Site.objects.all(),
  121. widget=forms.Select(
  122. attrs={'filter-for': 'rack'}
  123. )
  124. )
  125. rack = forms.ModelChoiceField(
  126. queryset=Rack.objects.all(),
  127. required=False,
  128. label='Rack',
  129. widget=APISelect(
  130. api_url='/api/dcim/racks/?site_id={{site}}',
  131. attrs={'filter-for': 'device', 'nullable': 'true'}
  132. )
  133. )
  134. device = forms.ModelChoiceField(
  135. queryset=Device.objects.all(),
  136. required=False,
  137. label='Device',
  138. widget=APISelect(
  139. api_url='/api/dcim/devices/?site_id={{site}}&rack_id={{rack}}',
  140. display_field='display_name',
  141. attrs={'filter-for': 'interface'}
  142. )
  143. )
  144. livesearch = forms.CharField(
  145. required=False,
  146. label='Device',
  147. widget=Livesearch(
  148. query_key='q',
  149. query_url='dcim-api:device-list',
  150. field_to_update='device'
  151. )
  152. )
  153. interface = forms.ModelChoiceField(
  154. queryset=Interface.objects.all(),
  155. required=False,
  156. label='Interface',
  157. widget=APISelect(
  158. api_url='/api/dcim/interfaces/?device_id={{device}}&type=physical',
  159. disabled_indicator='is_connected'
  160. )
  161. )
  162. class Meta:
  163. model = CircuitTermination
  164. fields = ['term_side', 'site', 'rack', 'device', 'livesearch', 'interface', 'port_speed', 'upstream_speed',
  165. 'xconnect_id', 'pp_info']
  166. help_texts = {
  167. 'port_speed': "Physical circuit speed",
  168. 'xconnect_id': "ID of the local cross-connect",
  169. 'pp_info': "Patch panel ID and port number(s)"
  170. }
  171. widgets = {
  172. 'term_side': forms.HiddenInput(),
  173. }
  174. def __init__(self, *args, **kwargs):
  175. super(CircuitTerminationForm, self).__init__(*args, **kwargs)
  176. # If an interface has been assigned, initialize rack and device
  177. if self.instance.interface:
  178. self.initial['rack'] = self.instance.interface.device.rack
  179. self.initial['device'] = self.instance.interface.device
  180. # Limit rack choices
  181. if self.is_bound:
  182. self.fields['rack'].queryset = Rack.objects.filter(site__pk=self.data['site'])
  183. elif self.initial.get('site'):
  184. self.fields['rack'].queryset = Rack.objects.filter(site=self.initial['site'])
  185. else:
  186. self.fields['rack'].choices = []
  187. # Limit device choices
  188. if self.is_bound and self.data.get('rack'):
  189. self.fields['device'].queryset = Device.objects.filter(rack=self.data['rack'])
  190. elif self.initial.get('rack'):
  191. self.fields['device'].queryset = Device.objects.filter(rack=self.initial['rack'])
  192. else:
  193. self.fields['device'].choices = []
  194. # Limit interface choices
  195. if self.is_bound and self.data.get('device'):
  196. interfaces = Interface.objects.filter(device=self.data['device']).exclude(
  197. form_factor__in=VIRTUAL_IFACE_TYPES
  198. ).select_related(
  199. 'circuit_termination', 'connected_as_a', 'connected_as_b'
  200. )
  201. self.fields['interface'].widget.attrs['initial'] = self.data.get('interface')
  202. elif self.initial.get('device'):
  203. interfaces = Interface.objects.filter(device=self.initial['device']).exclude(
  204. form_factor__in=VIRTUAL_IFACE_TYPES
  205. ).select_related(
  206. 'circuit_termination', 'connected_as_a', 'connected_as_b'
  207. )
  208. self.fields['interface'].widget.attrs['initial'] = self.initial.get('interface')
  209. else:
  210. interfaces = []
  211. self.fields['interface'].choices = [
  212. (iface.id, {
  213. 'label': iface.name,
  214. 'disabled': iface.is_connected and iface.id != self.fields['interface'].widget.attrs.get('initial'),
  215. }) for iface in interfaces
  216. ]