forms.py 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  1. from __future__ import unicode_literals
  2. from django import forms
  3. from django.db.models import Count
  4. from dcim.models import Site, Device, Interface, Rack
  5. from extras.forms import CustomFieldForm, CustomFieldBulkEditForm, CustomFieldFilterForm
  6. from tenancy.forms import TenancyForm
  7. from tenancy.models import Tenant
  8. from utilities.forms import (
  9. APISelect, add_blank_choice, BootstrapMixin, ChainedFieldsMixin, ChainedModelChoiceField, CommentField,
  10. CSVChoiceField, FilterChoiceField, SmallTextarea, SlugField,
  11. )
  12. from .constants import CIRCUIT_STATUS_CHOICES
  13. from .models import Circuit, CircuitTermination, CircuitType, Provider
  14. #
  15. # Providers
  16. #
  17. class ProviderForm(BootstrapMixin, CustomFieldForm):
  18. slug = SlugField()
  19. comments = CommentField()
  20. class Meta:
  21. model = Provider
  22. fields = ['name', 'slug', 'asn', 'account', 'portal_url', 'noc_contact', 'admin_contact', 'comments']
  23. widgets = {
  24. 'noc_contact': SmallTextarea(attrs={'rows': 5}),
  25. 'admin_contact': SmallTextarea(attrs={'rows': 5}),
  26. }
  27. help_texts = {
  28. 'name': "Full name of the provider",
  29. 'asn': "BGP autonomous system number (if applicable)",
  30. 'portal_url': "URL of the provider's customer support portal",
  31. 'noc_contact': "NOC email address and phone number",
  32. 'admin_contact': "Administrative contact email address and phone number",
  33. }
  34. class ProviderCSVForm(forms.ModelForm):
  35. slug = SlugField()
  36. class Meta:
  37. model = Provider
  38. fields = Provider.csv_headers
  39. help_texts = {
  40. 'name': 'Provider name',
  41. 'asn': '32-bit autonomous system number',
  42. 'portal_url': 'Portal URL',
  43. 'comments': 'Free-form comments',
  44. }
  45. class ProviderBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
  46. pk = forms.ModelMultipleChoiceField(queryset=Provider.objects.all(), widget=forms.MultipleHiddenInput)
  47. asn = forms.IntegerField(required=False, label='ASN')
  48. account = forms.CharField(max_length=30, required=False, label='Account number')
  49. portal_url = forms.URLField(required=False, label='Portal')
  50. noc_contact = forms.CharField(required=False, widget=SmallTextarea, label='NOC contact')
  51. admin_contact = forms.CharField(required=False, widget=SmallTextarea, label='Admin contact')
  52. comments = CommentField(widget=SmallTextarea)
  53. class Meta:
  54. nullable_fields = ['asn', 'account', 'portal_url', 'noc_contact', 'admin_contact', 'comments']
  55. class ProviderFilterForm(BootstrapMixin, CustomFieldFilterForm):
  56. model = Provider
  57. q = forms.CharField(required=False, label='Search')
  58. site = FilterChoiceField(queryset=Site.objects.all(), to_field_name='slug')
  59. asn = forms.IntegerField(required=False, label='ASN')
  60. #
  61. # Circuit types
  62. #
  63. class CircuitTypeForm(BootstrapMixin, forms.ModelForm):
  64. slug = SlugField()
  65. class Meta:
  66. model = CircuitType
  67. fields = ['name', 'slug']
  68. class CircuitTypeCSVForm(forms.ModelForm):
  69. slug = SlugField()
  70. class Meta:
  71. model = CircuitType
  72. fields = CircuitType.csv_headers
  73. help_texts = {
  74. 'name': 'Name of circuit type',
  75. }
  76. #
  77. # Circuits
  78. #
  79. class CircuitForm(BootstrapMixin, TenancyForm, CustomFieldForm):
  80. comments = CommentField()
  81. class Meta:
  82. model = Circuit
  83. fields = [
  84. 'cid', 'type', 'provider', 'status', 'install_date', 'commit_rate', 'description', 'tenant_group', 'tenant',
  85. 'comments',
  86. ]
  87. help_texts = {
  88. 'cid': "Unique circuit ID",
  89. 'install_date': "Format: YYYY-MM-DD",
  90. 'commit_rate': "Committed rate",
  91. }
  92. class CircuitCSVForm(forms.ModelForm):
  93. provider = forms.ModelChoiceField(
  94. queryset=Provider.objects.all(),
  95. to_field_name='name',
  96. help_text='Name of parent provider',
  97. error_messages={
  98. 'invalid_choice': 'Provider not found.'
  99. }
  100. )
  101. type = forms.ModelChoiceField(
  102. queryset=CircuitType.objects.all(),
  103. to_field_name='name',
  104. help_text='Type of circuit',
  105. error_messages={
  106. 'invalid_choice': 'Invalid circuit type.'
  107. }
  108. )
  109. status = CSVChoiceField(
  110. choices=CIRCUIT_STATUS_CHOICES,
  111. required=False,
  112. help_text='Operational status'
  113. )
  114. tenant = forms.ModelChoiceField(
  115. queryset=Tenant.objects.all(),
  116. required=False,
  117. to_field_name='name',
  118. help_text='Name of assigned tenant',
  119. error_messages={
  120. 'invalid_choice': 'Tenant not found.'
  121. }
  122. )
  123. class Meta:
  124. model = Circuit
  125. fields = [
  126. 'cid', 'provider', 'type', 'status', 'tenant', 'install_date', 'commit_rate', 'description', 'comments',
  127. ]
  128. class CircuitBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
  129. pk = forms.ModelMultipleChoiceField(queryset=Circuit.objects.all(), widget=forms.MultipleHiddenInput)
  130. type = forms.ModelChoiceField(queryset=CircuitType.objects.all(), required=False)
  131. provider = forms.ModelChoiceField(queryset=Provider.objects.all(), required=False)
  132. status = forms.ChoiceField(choices=add_blank_choice(CIRCUIT_STATUS_CHOICES), required=False, initial='')
  133. tenant = forms.ModelChoiceField(queryset=Tenant.objects.all(), required=False)
  134. commit_rate = forms.IntegerField(required=False, label='Commit rate (Kbps)')
  135. description = forms.CharField(max_length=100, required=False)
  136. comments = CommentField(widget=SmallTextarea)
  137. class Meta:
  138. nullable_fields = ['tenant', 'commit_rate', 'description', 'comments']
  139. def circuit_status_choices():
  140. status_counts = {}
  141. for status in Circuit.objects.values('status').annotate(count=Count('status')).order_by('status'):
  142. status_counts[status['status']] = status['count']
  143. return [(s[0], '{} ({})'.format(s[1], status_counts.get(s[0], 0))) for s in CIRCUIT_STATUS_CHOICES]
  144. class CircuitFilterForm(BootstrapMixin, CustomFieldFilterForm):
  145. model = Circuit
  146. q = forms.CharField(required=False, label='Search')
  147. type = FilterChoiceField(
  148. queryset=CircuitType.objects.annotate(filter_count=Count('circuits')),
  149. to_field_name='slug'
  150. )
  151. provider = FilterChoiceField(
  152. queryset=Provider.objects.annotate(filter_count=Count('circuits')),
  153. to_field_name='slug'
  154. )
  155. status = forms.MultipleChoiceField(choices=circuit_status_choices, required=False)
  156. tenant = FilterChoiceField(
  157. queryset=Tenant.objects.annotate(filter_count=Count('circuits')),
  158. to_field_name='slug',
  159. null_label='-- None --'
  160. )
  161. site = FilterChoiceField(
  162. queryset=Site.objects.annotate(filter_count=Count('circuit_terminations')),
  163. to_field_name='slug'
  164. )
  165. commit_rate = forms.IntegerField(required=False, min_value=0, label='Commit rate (Kbps)')
  166. #
  167. # Circuit terminations
  168. #
  169. class CircuitTerminationForm(BootstrapMixin, ChainedFieldsMixin, forms.ModelForm):
  170. site = forms.ModelChoiceField(
  171. queryset=Site.objects.all(),
  172. widget=forms.Select(
  173. attrs={'filter-for': 'rack'}
  174. )
  175. )
  176. rack = ChainedModelChoiceField(
  177. queryset=Rack.objects.all(),
  178. chains=(
  179. ('site', 'site'),
  180. ),
  181. required=False,
  182. label='Rack',
  183. widget=APISelect(
  184. api_url='/api/dcim/racks/?site_id={{site}}',
  185. attrs={'filter-for': 'device', 'nullable': 'true'}
  186. )
  187. )
  188. device = ChainedModelChoiceField(
  189. queryset=Device.objects.all(),
  190. chains=(
  191. ('site', 'site'),
  192. ('rack', 'rack'),
  193. ),
  194. required=False,
  195. label='Device',
  196. widget=APISelect(
  197. api_url='/api/dcim/devices/?site_id={{site}}&rack_id={{rack}}',
  198. display_field='display_name',
  199. attrs={'filter-for': 'interface'}
  200. )
  201. )
  202. interface = ChainedModelChoiceField(
  203. queryset=Interface.objects.connectable().select_related(
  204. 'circuit_termination', 'connected_as_a', 'connected_as_b'
  205. ),
  206. chains=(
  207. ('device', 'device'),
  208. ),
  209. required=False,
  210. label='Interface',
  211. widget=APISelect(
  212. api_url='/api/dcim/interfaces/?device_id={{device}}&type=physical',
  213. disabled_indicator='is_connected'
  214. )
  215. )
  216. class Meta:
  217. model = CircuitTermination
  218. fields = [
  219. 'term_side', 'site', 'rack', 'device', 'interface', 'port_speed', 'upstream_speed', 'xconnect_id',
  220. 'pp_info',
  221. ]
  222. help_texts = {
  223. 'port_speed': "Physical circuit speed",
  224. 'xconnect_id': "ID of the local cross-connect",
  225. 'pp_info': "Patch panel ID and port number(s)"
  226. }
  227. widgets = {
  228. 'term_side': forms.HiddenInput(),
  229. }
  230. def __init__(self, *args, **kwargs):
  231. # Initialize helper selectors
  232. instance = kwargs.get('instance')
  233. if instance and instance.interface is not None:
  234. initial = kwargs.get('initial', {}).copy()
  235. initial['rack'] = instance.interface.device.rack
  236. initial['device'] = instance.interface.device
  237. kwargs['initial'] = initial
  238. super(CircuitTerminationForm, self).__init__(*args, **kwargs)
  239. # Mark connected interfaces as disabled
  240. self.fields['interface'].choices = []
  241. for iface in self.fields['interface'].queryset:
  242. self.fields['interface'].choices.append(
  243. (iface.id, {
  244. 'label': iface.name,
  245. 'disabled': iface.is_connected and iface.pk != self.initial.get('interface'),
  246. })
  247. )