forms.py 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. from django import forms
  2. from django.db.models import Count
  3. from dcim.models import Site, Device, Interface, Rack, IFACE_FF_VIRTUAL
  4. from tenancy.forms import bulkedit_tenant_choices
  5. from tenancy.models import Tenant
  6. from utilities.forms import (
  7. APISelect, BootstrapMixin, BulkImportForm, CommentField, CSVDataField, Livesearch, SmallTextarea, SlugField,
  8. )
  9. from .models import Circuit, CircuitType, Provider
  10. #
  11. # Providers
  12. #
  13. class ProviderForm(forms.ModelForm, BootstrapMixin):
  14. slug = SlugField()
  15. comments = CommentField()
  16. class Meta:
  17. model = Provider
  18. fields = ['name', 'slug', 'asn', 'account', 'portal_url', 'noc_contact', 'admin_contact', 'comments']
  19. widgets = {
  20. 'noc_contact': SmallTextarea(attrs={'rows': 5}),
  21. 'admin_contact': SmallTextarea(attrs={'rows': 5}),
  22. }
  23. help_texts = {
  24. 'name': "Full name of the provider",
  25. 'asn': "BGP autonomous system number (if applicable)",
  26. 'portal_url': "URL of the provider's customer support portal",
  27. 'noc_contact': "NOC email address and phone number",
  28. 'admin_contact': "Administrative contact email address and phone number",
  29. }
  30. class ProviderFromCSVForm(forms.ModelForm):
  31. class Meta:
  32. model = Provider
  33. fields = ['name', 'slug', 'asn', 'account', 'portal_url']
  34. class ProviderImportForm(BulkImportForm, BootstrapMixin):
  35. csv = CSVDataField(csv_form=ProviderFromCSVForm)
  36. class ProviderBulkEditForm(forms.Form, BootstrapMixin):
  37. pk = forms.ModelMultipleChoiceField(queryset=Provider.objects.all(), widget=forms.MultipleHiddenInput)
  38. asn = forms.IntegerField(required=False, label='ASN')
  39. account = forms.CharField(max_length=30, required=False, label='Account number')
  40. portal_url = forms.URLField(required=False, label='Portal')
  41. noc_contact = forms.CharField(required=False, widget=SmallTextarea, label='NOC contact')
  42. admin_contact = forms.CharField(required=False, widget=SmallTextarea, label='Admin contact')
  43. comments = CommentField()
  44. def provider_site_choices():
  45. site_choices = Site.objects.all()
  46. return [(s.slug, s.name) for s in site_choices]
  47. class ProviderFilterForm(forms.Form, BootstrapMixin):
  48. site = forms.MultipleChoiceField(required=False, choices=provider_site_choices,
  49. widget=forms.SelectMultiple(attrs={'size': 8}))
  50. #
  51. # Circuit types
  52. #
  53. class CircuitTypeForm(forms.ModelForm, BootstrapMixin):
  54. slug = SlugField()
  55. class Meta:
  56. model = CircuitType
  57. fields = ['name', 'slug']
  58. #
  59. # Circuits
  60. #
  61. class CircuitForm(forms.ModelForm, BootstrapMixin):
  62. site = forms.ModelChoiceField(queryset=Site.objects.all(), widget=forms.Select(attrs={'filter-for': 'rack'}))
  63. rack = forms.ModelChoiceField(queryset=Rack.objects.all(), required=False, label='Rack',
  64. widget=APISelect(api_url='/api/dcim/racks/?site_id={{site}}',
  65. attrs={'filter-for': 'device'}))
  66. device = forms.ModelChoiceField(queryset=Device.objects.all(), required=False, label='Device',
  67. widget=APISelect(api_url='/api/dcim/devices/?rack_id={{rack}}',
  68. attrs={'filter-for': 'interface'}))
  69. livesearch = forms.CharField(required=False, label='Device', widget=Livesearch(
  70. query_key='q', query_url='dcim-api:device_list', field_to_update='device')
  71. )
  72. interface = forms.ModelChoiceField(queryset=Interface.objects.all(), required=False, label='Interface',
  73. widget=APISelect(api_url='/api/dcim/devices/{{device}}/interfaces/?type=physical',
  74. disabled_indicator='is_connected'))
  75. comments = CommentField()
  76. class Meta:
  77. model = Circuit
  78. fields = [
  79. 'cid', 'type', 'provider', 'tenant', 'site', 'rack', 'device', 'livesearch', 'interface', 'install_date',
  80. 'port_speed', 'commit_rate', 'xconnect_id', 'pp_info', 'comments'
  81. ]
  82. help_texts = {
  83. 'cid': "Unique circuit ID",
  84. 'install_date': "Format: YYYY-MM-DD",
  85. 'port_speed': "Physical circuit speed",
  86. 'commit_rate': "Commited rate",
  87. 'xconnect_id': "ID of the local cross-connect",
  88. 'pp_info': "Patch panel ID and port number(s)"
  89. }
  90. def __init__(self, *args, **kwargs):
  91. super(CircuitForm, self).__init__(*args, **kwargs)
  92. # If this circuit has been assigned to an interface, initialize rack and device
  93. if self.instance.interface:
  94. self.initial['rack'] = self.instance.interface.device.rack
  95. self.initial['device'] = self.instance.interface.device
  96. # Limit rack choices
  97. if self.is_bound:
  98. self.fields['rack'].queryset = Rack.objects.filter(site__pk=self.data['site'])
  99. elif self.initial.get('site'):
  100. self.fields['rack'].queryset = Rack.objects.filter(site=self.initial['site'])
  101. else:
  102. self.fields['rack'].choices = []
  103. # Limit device choices
  104. if self.is_bound and self.data.get('rack'):
  105. self.fields['device'].queryset = Device.objects.filter(rack=self.data['rack'])
  106. elif self.initial.get('rack'):
  107. self.fields['device'].queryset = Device.objects.filter(rack=self.initial['rack'])
  108. else:
  109. self.fields['device'].choices = []
  110. # Limit interface choices
  111. if self.is_bound and self.data.get('device'):
  112. interfaces = Interface.objects.filter(device=self.data['device'])\
  113. .exclude(form_factor=IFACE_FF_VIRTUAL).select_related('circuit', 'connected_as_a', 'connected_as_b')
  114. self.fields['interface'].widget.attrs['initial'] = self.data.get('interface')
  115. elif self.initial.get('device'):
  116. interfaces = Interface.objects.filter(device=self.initial['device'])\
  117. .exclude(form_factor=IFACE_FF_VIRTUAL).select_related('circuit', 'connected_as_a', 'connected_as_b')
  118. self.fields['interface'].widget.attrs['initial'] = self.initial.get('interface')
  119. else:
  120. interfaces = []
  121. self.fields['interface'].choices = [
  122. (iface.id, {
  123. 'label': iface.name,
  124. 'disabled': iface.is_connected and iface.id != self.fields['interface'].widget.attrs.get('initial'),
  125. }) for iface in interfaces
  126. ]
  127. class CircuitFromCSVForm(forms.ModelForm):
  128. provider = forms.ModelChoiceField(Provider.objects.all(), to_field_name='name',
  129. error_messages={'invalid_choice': 'Provider not found.'})
  130. type = forms.ModelChoiceField(CircuitType.objects.all(), to_field_name='name',
  131. error_messages={'invalid_choice': 'Invalid circuit type.'})
  132. tenant = forms.ModelChoiceField(Tenant.objects.all(), to_field_name='name', required=False,
  133. error_messages={'invalid_choice': 'Tenant not found.'})
  134. site = forms.ModelChoiceField(Site.objects.all(), to_field_name='name',
  135. error_messages={'invalid_choice': 'Site not found.'})
  136. class Meta:
  137. model = Circuit
  138. fields = ['cid', 'provider', 'type', 'tenant', 'site', 'install_date', 'port_speed', 'commit_rate',
  139. 'xconnect_id', 'pp_info']
  140. class CircuitImportForm(BulkImportForm, BootstrapMixin):
  141. csv = CSVDataField(csv_form=CircuitFromCSVForm)
  142. class CircuitBulkEditForm(forms.Form, BootstrapMixin):
  143. pk = forms.ModelMultipleChoiceField(queryset=Circuit.objects.all(), widget=forms.MultipleHiddenInput)
  144. type = forms.ModelChoiceField(queryset=CircuitType.objects.all(), required=False)
  145. provider = forms.ModelChoiceField(queryset=Provider.objects.all(), required=False)
  146. tenant = forms.TypedChoiceField(choices=bulkedit_tenant_choices, coerce=int, required=False, label='Tenant')
  147. port_speed = forms.IntegerField(required=False, label='Port speed (Kbps)')
  148. commit_rate = forms.IntegerField(required=False, label='Commit rate (Kbps)')
  149. comments = CommentField()
  150. def circuit_type_choices():
  151. type_choices = CircuitType.objects.annotate(circuit_count=Count('circuits'))
  152. return [(t.slug, u'{} ({})'.format(t.name, t.circuit_count)) for t in type_choices]
  153. def circuit_provider_choices():
  154. provider_choices = Provider.objects.annotate(circuit_count=Count('circuits'))
  155. return [(p.slug, u'{} ({})'.format(p.name, p.circuit_count)) for p in provider_choices]
  156. def circuit_tenant_choices():
  157. tenant_choices = Tenant.objects.annotate(circuit_count=Count('circuits'))
  158. return [(t.slug, u'{} ({})'.format(t.name, t.circuit_count)) for t in tenant_choices]
  159. def circuit_site_choices():
  160. site_choices = Site.objects.annotate(circuit_count=Count('circuits'))
  161. return [(s.slug, u'{} ({})'.format(s.name, s.circuit_count)) for s in site_choices]
  162. class CircuitFilterForm(forms.Form, BootstrapMixin):
  163. type = forms.MultipleChoiceField(required=False, choices=circuit_type_choices)
  164. provider = forms.MultipleChoiceField(required=False, choices=circuit_provider_choices,
  165. widget=forms.SelectMultiple(attrs={'size': 8}))
  166. tenant = forms.MultipleChoiceField(required=False, choices=circuit_tenant_choices,
  167. widget=forms.SelectMultiple(attrs={'size': 8}))
  168. site = forms.MultipleChoiceField(required=False, choices=circuit_site_choices,
  169. widget=forms.SelectMultiple(attrs={'size': 8}))