forms.py 8.3 KB

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