models.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
  1. from django import forms
  2. from django.contrib.contenttypes.models import ContentType
  3. from django.core.exceptions import ValidationError
  4. from dcim.forms.common import InterfaceCommonForm
  5. from dcim.forms.models import INTERFACE_MODE_HELP_TEXT
  6. from dcim.models import Device, DeviceRole, Platform, Rack, Region, Site, SiteGroup
  7. from extras.forms import CustomFieldModelForm
  8. from extras.models import Tag
  9. from ipam.models import IPAddress, VLAN, VLANGroup
  10. from tenancy.forms import TenancyForm
  11. from utilities.forms import (
  12. BootstrapMixin, CommentField, ConfirmationForm, DynamicModelChoiceField, DynamicModelMultipleChoiceField,
  13. JSONField, SlugField, StaticSelect,
  14. )
  15. from virtualization.models import *
  16. __all__ = (
  17. 'ClusterAddDevicesForm',
  18. 'ClusterForm',
  19. 'ClusterGroupForm',
  20. 'ClusterRemoveDevicesForm',
  21. 'ClusterTypeForm',
  22. 'VirtualMachineForm',
  23. 'VMInterfaceForm',
  24. )
  25. class ClusterTypeForm(CustomFieldModelForm):
  26. slug = SlugField()
  27. tags = DynamicModelMultipleChoiceField(
  28. queryset=Tag.objects.all(),
  29. required=False
  30. )
  31. class Meta:
  32. model = ClusterType
  33. fields = (
  34. 'name', 'slug', 'description', 'tags',
  35. )
  36. class ClusterGroupForm(CustomFieldModelForm):
  37. slug = SlugField()
  38. tags = DynamicModelMultipleChoiceField(
  39. queryset=Tag.objects.all(),
  40. required=False
  41. )
  42. class Meta:
  43. model = ClusterGroup
  44. fields = (
  45. 'name', 'slug', 'description', 'tags',
  46. )
  47. class ClusterForm(TenancyForm, CustomFieldModelForm):
  48. type = DynamicModelChoiceField(
  49. queryset=ClusterType.objects.all()
  50. )
  51. group = DynamicModelChoiceField(
  52. queryset=ClusterGroup.objects.all(),
  53. required=False
  54. )
  55. region = DynamicModelChoiceField(
  56. queryset=Region.objects.all(),
  57. required=False,
  58. initial_params={
  59. 'sites': '$site'
  60. }
  61. )
  62. site_group = DynamicModelChoiceField(
  63. queryset=SiteGroup.objects.all(),
  64. required=False,
  65. initial_params={
  66. 'sites': '$site'
  67. }
  68. )
  69. site = DynamicModelChoiceField(
  70. queryset=Site.objects.all(),
  71. required=False,
  72. query_params={
  73. 'region_id': '$region',
  74. 'group_id': '$site_group',
  75. }
  76. )
  77. comments = CommentField()
  78. tags = DynamicModelMultipleChoiceField(
  79. queryset=Tag.objects.all(),
  80. required=False
  81. )
  82. class Meta:
  83. model = Cluster
  84. fields = (
  85. 'name', 'type', 'group', 'tenant', 'region', 'site_group', 'site', 'comments', 'tags',
  86. )
  87. fieldsets = (
  88. ('Cluster', ('name', 'type', 'group', 'region', 'site_group', 'site', 'tags')),
  89. ('Tenancy', ('tenant_group', 'tenant')),
  90. )
  91. class ClusterAddDevicesForm(BootstrapMixin, forms.Form):
  92. region = DynamicModelChoiceField(
  93. queryset=Region.objects.all(),
  94. required=False,
  95. null_option='None'
  96. )
  97. site_group = DynamicModelChoiceField(
  98. queryset=SiteGroup.objects.all(),
  99. required=False,
  100. null_option='None'
  101. )
  102. site = DynamicModelChoiceField(
  103. queryset=Site.objects.all(),
  104. required=False,
  105. query_params={
  106. 'region_id': '$region',
  107. 'group_id': '$site_group',
  108. }
  109. )
  110. rack = DynamicModelChoiceField(
  111. queryset=Rack.objects.all(),
  112. required=False,
  113. null_option='None',
  114. query_params={
  115. 'site_id': '$site'
  116. }
  117. )
  118. devices = DynamicModelMultipleChoiceField(
  119. queryset=Device.objects.all(),
  120. query_params={
  121. 'site_id': '$site',
  122. 'rack_id': '$rack',
  123. 'cluster_id': 'null',
  124. }
  125. )
  126. class Meta:
  127. fields = [
  128. 'region', 'site', 'rack', 'devices',
  129. ]
  130. def __init__(self, cluster, *args, **kwargs):
  131. self.cluster = cluster
  132. super().__init__(*args, **kwargs)
  133. self.fields['devices'].choices = []
  134. def clean(self):
  135. super().clean()
  136. # If the Cluster is assigned to a Site, all Devices must be assigned to that Site.
  137. if self.cluster.site is not None:
  138. for device in self.cleaned_data.get('devices', []):
  139. if device.site != self.cluster.site:
  140. raise ValidationError({
  141. 'devices': "{} belongs to a different site ({}) than the cluster ({})".format(
  142. device, device.site, self.cluster.site
  143. )
  144. })
  145. class ClusterRemoveDevicesForm(ConfirmationForm):
  146. pk = forms.ModelMultipleChoiceField(
  147. queryset=Device.objects.all(),
  148. widget=forms.MultipleHiddenInput()
  149. )
  150. class VirtualMachineForm(TenancyForm, CustomFieldModelForm):
  151. cluster_group = DynamicModelChoiceField(
  152. queryset=ClusterGroup.objects.all(),
  153. required=False,
  154. null_option='None',
  155. initial_params={
  156. 'clusters': '$cluster'
  157. }
  158. )
  159. cluster = DynamicModelChoiceField(
  160. queryset=Cluster.objects.all(),
  161. query_params={
  162. 'group_id': '$cluster_group'
  163. }
  164. )
  165. role = DynamicModelChoiceField(
  166. queryset=DeviceRole.objects.all(),
  167. required=False,
  168. query_params={
  169. "vm_role": "True"
  170. }
  171. )
  172. platform = DynamicModelChoiceField(
  173. queryset=Platform.objects.all(),
  174. required=False
  175. )
  176. local_context_data = JSONField(
  177. required=False,
  178. label=''
  179. )
  180. tags = DynamicModelMultipleChoiceField(
  181. queryset=Tag.objects.all(),
  182. required=False
  183. )
  184. class Meta:
  185. model = VirtualMachine
  186. fields = [
  187. 'name', 'status', 'cluster_group', 'cluster', 'role', 'tenant_group', 'tenant', 'platform', 'primary_ip4',
  188. 'primary_ip6', 'vcpus', 'memory', 'disk', 'comments', 'tags', 'local_context_data',
  189. ]
  190. fieldsets = (
  191. ('Virtual Machine', ('name', 'role', 'status', 'tags')),
  192. ('Cluster', ('cluster_group', 'cluster')),
  193. ('Tenancy', ('tenant_group', 'tenant')),
  194. ('Management', ('platform', 'primary_ip4', 'primary_ip6')),
  195. ('Resources', ('vcpus', 'memory', 'disk')),
  196. ('Config Context', ('local_context_data',)),
  197. )
  198. help_texts = {
  199. 'local_context_data': "Local config context data overwrites all sources contexts in the final rendered "
  200. "config context",
  201. }
  202. widgets = {
  203. "status": StaticSelect(),
  204. 'primary_ip4': StaticSelect(),
  205. 'primary_ip6': StaticSelect(),
  206. }
  207. def __init__(self, *args, **kwargs):
  208. super().__init__(*args, **kwargs)
  209. if self.instance.pk:
  210. # Compile list of choices for primary IPv4 and IPv6 addresses
  211. for family in [4, 6]:
  212. ip_choices = [(None, '---------')]
  213. # Gather PKs of all interfaces belonging to this VM
  214. interface_ids = self.instance.interfaces.values_list('pk', flat=True)
  215. # Collect interface IPs
  216. interface_ips = IPAddress.objects.filter(
  217. address__family=family,
  218. assigned_object_type=ContentType.objects.get_for_model(VMInterface),
  219. assigned_object_id__in=interface_ids
  220. )
  221. if interface_ips:
  222. ip_list = [(ip.id, f'{ip.address} ({ip.assigned_object})') for ip in interface_ips]
  223. ip_choices.append(('Interface IPs', ip_list))
  224. # Collect NAT IPs
  225. nat_ips = IPAddress.objects.prefetch_related('nat_inside').filter(
  226. address__family=family,
  227. nat_inside__assigned_object_type=ContentType.objects.get_for_model(VMInterface),
  228. nat_inside__assigned_object_id__in=interface_ids
  229. )
  230. if nat_ips:
  231. ip_list = [(ip.id, f'{ip.address} (NAT)') for ip in nat_ips]
  232. ip_choices.append(('NAT IPs', ip_list))
  233. self.fields['primary_ip{}'.format(family)].choices = ip_choices
  234. else:
  235. # An object that doesn't exist yet can't have any IPs assigned to it
  236. self.fields['primary_ip4'].choices = []
  237. self.fields['primary_ip4'].widget.attrs['readonly'] = True
  238. self.fields['primary_ip6'].choices = []
  239. self.fields['primary_ip6'].widget.attrs['readonly'] = True
  240. class VMInterfaceForm(InterfaceCommonForm, CustomFieldModelForm):
  241. parent = DynamicModelChoiceField(
  242. queryset=VMInterface.objects.all(),
  243. required=False,
  244. label='Parent interface',
  245. query_params={
  246. 'virtual_machine_id': '$virtual_machine',
  247. }
  248. )
  249. bridge = DynamicModelChoiceField(
  250. queryset=VMInterface.objects.all(),
  251. required=False,
  252. label='Bridged interface',
  253. query_params={
  254. 'virtual_machine_id': '$virtual_machine',
  255. }
  256. )
  257. vlan_group = DynamicModelChoiceField(
  258. queryset=VLANGroup.objects.all(),
  259. required=False,
  260. label='VLAN group'
  261. )
  262. untagged_vlan = DynamicModelChoiceField(
  263. queryset=VLAN.objects.all(),
  264. required=False,
  265. label='Untagged VLAN',
  266. query_params={
  267. 'group_id': '$vlan_group',
  268. 'available_on_virtualmachine': '$virtual_machine',
  269. }
  270. )
  271. tagged_vlans = DynamicModelMultipleChoiceField(
  272. queryset=VLAN.objects.all(),
  273. required=False,
  274. label='Tagged VLANs',
  275. query_params={
  276. 'group_id': '$vlan_group',
  277. 'available_on_virtualmachine': '$virtual_machine',
  278. }
  279. )
  280. tags = DynamicModelMultipleChoiceField(
  281. queryset=Tag.objects.all(),
  282. required=False
  283. )
  284. class Meta:
  285. model = VMInterface
  286. fields = [
  287. 'virtual_machine', 'name', 'parent', 'bridge', 'enabled', 'mac_address', 'mtu', 'description', 'mode',
  288. 'tags', 'untagged_vlan', 'tagged_vlans',
  289. ]
  290. widgets = {
  291. 'virtual_machine': forms.HiddenInput(),
  292. 'mode': StaticSelect()
  293. }
  294. labels = {
  295. 'mode': '802.1Q Mode',
  296. }
  297. help_texts = {
  298. 'mode': INTERFACE_MODE_HELP_TEXT,
  299. }