tables.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609
  1. import django_tables2 as tables
  2. from django.utils.safestring import mark_safe
  3. from django_tables2.utils import Accessor
  4. from dcim.models import Interface
  5. from tenancy.tables import TenantColumn
  6. from utilities.tables import (
  7. BaseTable, BooleanColumn, ButtonsColumn, ChoiceFieldColumn, ContentTypeColumn, LinkedCountColumn, TagColumn,
  8. ToggleColumn, UtilizationColumn,
  9. )
  10. from virtualization.models import VMInterface
  11. from .models import Aggregate, IPAddress, Prefix, RIR, Role, RouteTarget, Service, VLAN, VLANGroup, VRF
  12. AVAILABLE_LABEL = mark_safe('<span class="badge bg-success">Available</span>')
  13. PREFIX_LINK = """
  14. {% load helpers %}
  15. {% for i in record.parents|as_range %}
  16. <i class="mdi mdi-circle-small"></i>
  17. {% endfor %}
  18. <a href="{% if record.pk %}{% url 'ipam:prefix' pk=record.pk %}{% else %}{% url 'ipam:prefix_add' %}?prefix={{ record }}{% if object.vrf %}&vrf={{ object.vrf.pk }}{% endif %}{% if object.site %}&site={{ object.site.pk }}{% endif %}{% if object.tenant %}&tenant_group={{ object.tenant.group.pk }}&tenant={{ object.tenant.pk }}{% endif %}{% endif %}">{{ record.prefix }}</a>
  19. """
  20. PREFIX_ROLE_LINK = """
  21. {% if record.role %}
  22. <a href="{% url 'ipam:prefix_list' %}?role={{ record.role.slug }}">{{ record.role }}</a>
  23. {% else %}
  24. &mdash;
  25. {% endif %}
  26. """
  27. IPADDRESS_LINK = """
  28. {% if record.pk %}
  29. <a href="{{ record.get_absolute_url }}">{{ record.address }}</a>
  30. {% elif perms.ipam.add_ipaddress %}
  31. <a href="{% url 'ipam:ipaddress_add' %}?address={{ record.1 }}{% if object.vrf %}&vrf={{ object.vrf.pk }}{% endif %}{% if object.tenant %}&tenant={{ object.tenant.pk }}{% endif %}" class="btn btn-sm btn-success">{% if record.0 <= 65536 %}{{ record.0 }}{% else %}Many{% endif %} IP{{ record.0|pluralize }} available</a>
  32. {% else %}
  33. {% if record.0 <= 65536 %}{{ record.0 }}{% else %}Many{% endif %} IP{{ record.0|pluralize }} available
  34. {% endif %}
  35. """
  36. IPADDRESS_ASSIGN_LINK = """
  37. <a href="{% url 'ipam:ipaddress_edit' pk=record.pk %}?{% if request.GET.interface %}interface={{ request.GET.interface }}{% elif request.GET.vminterface %}vminterface={{ request.GET.vminterface }}{% endif %}&return_url={{ request.GET.return_url }}">{{ record }}</a>
  38. """
  39. VRF_LINK = """
  40. {% if record.vrf %}
  41. <a href="{{ record.vrf.get_absolute_url }}">{{ record.vrf }}</a>
  42. {% elif object.vrf %}
  43. <a href="{{ object.vrf.get_absolute_url }}">{{ object.vrf }}</a>
  44. {% else %}
  45. Global
  46. {% endif %}
  47. """
  48. VRF_TARGETS = """
  49. {% for rt in value.all %}
  50. <a href="{{ rt.get_absolute_url }}">{{ rt }}</a>{% if not forloop.last %}<br />{% endif %}
  51. {% empty %}
  52. &mdash;
  53. {% endfor %}
  54. """
  55. VLAN_LINK = """
  56. {% if record.pk %}
  57. <a href="{{ record.get_absolute_url }}">{{ record.vid }}</a>
  58. {% elif perms.ipam.add_vlan %}
  59. <a href="{% url 'ipam:vlan_add' %}?vid={{ record.vid }}&group={{ vlan_group.pk }}{% if vlan_group.site %}&site={{ vlan_group.site.pk }}{% endif %}" class="btn btn-sm btn-success">{{ record.available }} VLAN{{ record.available|pluralize }} available</a>
  60. {% else %}
  61. {{ record.available }} VLAN{{ record.available|pluralize }} available
  62. {% endif %}
  63. """
  64. VLAN_PREFIXES = """
  65. {% for prefix in record.prefixes.all %}
  66. <a href="{% url 'ipam:prefix' pk=prefix.pk %}">{{ prefix }}</a>{% if not forloop.last %}<br />{% endif %}
  67. {% empty %}
  68. &mdash;
  69. {% endfor %}
  70. """
  71. VLAN_ROLE_LINK = """
  72. {% if record.role %}
  73. <a href="{% url 'ipam:vlan_list' %}?role={{ record.role.slug }}">{{ record.role }}</a>
  74. {% else %}
  75. &mdash;
  76. {% endif %}
  77. """
  78. VLANGROUP_ADD_VLAN = """
  79. {% with next_vid=record.get_next_available_vid %}
  80. {% if next_vid and perms.ipam.add_vlan %}
  81. <a href="{% url 'ipam:vlan_add' %}?group={{ record.pk }}&vid={{ next_vid }}" title="Add VLAN" class="btn btn-sm btn-success">
  82. <i class="mdi mdi-plus-thick" aria-hidden="true"></i>
  83. </a>
  84. {% endif %}
  85. {% endwith %}
  86. """
  87. VLAN_MEMBER_TAGGED = """
  88. {% if record.untagged_vlan_id == object.pk %}
  89. <span class="text-danger"><i class="mdi mdi-close-thick"></i></span>
  90. {% else %}
  91. <span class="text-success"><i class="mdi mdi-check-bold"></i></span>
  92. {% endif %}
  93. """
  94. #
  95. # VRFs
  96. #
  97. class VRFTable(BaseTable):
  98. pk = ToggleColumn()
  99. name = tables.Column(
  100. linkify=True
  101. )
  102. rd = tables.Column(
  103. verbose_name='RD'
  104. )
  105. tenant = TenantColumn()
  106. enforce_unique = BooleanColumn(
  107. verbose_name='Unique'
  108. )
  109. import_targets = tables.TemplateColumn(
  110. template_code=VRF_TARGETS,
  111. orderable=False
  112. )
  113. export_targets = tables.TemplateColumn(
  114. template_code=VRF_TARGETS,
  115. orderable=False
  116. )
  117. tags = TagColumn(
  118. url_name='ipam:vrf_list'
  119. )
  120. class Meta(BaseTable.Meta):
  121. model = VRF
  122. fields = (
  123. 'pk', 'name', 'rd', 'tenant', 'enforce_unique', 'description', 'import_targets', 'export_targets', 'tags',
  124. )
  125. default_columns = ('pk', 'name', 'rd', 'tenant', 'description')
  126. #
  127. # Route targets
  128. #
  129. class RouteTargetTable(BaseTable):
  130. pk = ToggleColumn()
  131. name = tables.Column(
  132. linkify=True
  133. )
  134. tenant = TenantColumn()
  135. tags = TagColumn(
  136. url_name='ipam:vrf_list'
  137. )
  138. class Meta(BaseTable.Meta):
  139. model = RouteTarget
  140. fields = ('pk', 'name', 'tenant', 'description', 'tags')
  141. default_columns = ('pk', 'name', 'tenant', 'description')
  142. #
  143. # RIRs
  144. #
  145. class RIRTable(BaseTable):
  146. pk = ToggleColumn()
  147. name = tables.Column(
  148. linkify=True
  149. )
  150. is_private = BooleanColumn(
  151. verbose_name='Private'
  152. )
  153. aggregate_count = LinkedCountColumn(
  154. viewname='ipam:aggregate_list',
  155. url_params={'rir_id': 'pk'},
  156. verbose_name='Aggregates'
  157. )
  158. actions = ButtonsColumn(RIR)
  159. class Meta(BaseTable.Meta):
  160. model = RIR
  161. fields = ('pk', 'name', 'slug', 'is_private', 'aggregate_count', 'description', 'actions')
  162. default_columns = ('pk', 'name', 'is_private', 'aggregate_count', 'description', 'actions')
  163. #
  164. # Aggregates
  165. #
  166. class AggregateTable(BaseTable):
  167. pk = ToggleColumn()
  168. prefix = tables.Column(
  169. linkify=True,
  170. verbose_name='Aggregate'
  171. )
  172. tenant = TenantColumn()
  173. date_added = tables.DateColumn(
  174. format="Y-m-d",
  175. verbose_name='Added'
  176. )
  177. class Meta(BaseTable.Meta):
  178. model = Aggregate
  179. fields = ('pk', 'prefix', 'rir', 'tenant', 'date_added', 'description')
  180. class AggregateDetailTable(AggregateTable):
  181. child_count = tables.Column(
  182. verbose_name='Prefixes'
  183. )
  184. utilization = UtilizationColumn(
  185. accessor='get_utilization',
  186. orderable=False
  187. )
  188. tags = TagColumn(
  189. url_name='ipam:aggregate_list'
  190. )
  191. class Meta(AggregateTable.Meta):
  192. fields = ('pk', 'prefix', 'rir', 'tenant', 'child_count', 'utilization', 'date_added', 'description', 'tags')
  193. default_columns = ('pk', 'prefix', 'rir', 'tenant', 'child_count', 'utilization', 'date_added', 'description')
  194. #
  195. # Roles
  196. #
  197. class RoleTable(BaseTable):
  198. pk = ToggleColumn()
  199. name = tables.Column(
  200. linkify=True
  201. )
  202. prefix_count = LinkedCountColumn(
  203. viewname='ipam:prefix_list',
  204. url_params={'role_id': 'pk'},
  205. verbose_name='Prefixes'
  206. )
  207. vlan_count = LinkedCountColumn(
  208. viewname='ipam:vlan_list',
  209. url_params={'role_id': 'pk'},
  210. verbose_name='VLANs'
  211. )
  212. actions = ButtonsColumn(Role)
  213. class Meta(BaseTable.Meta):
  214. model = Role
  215. fields = ('pk', 'name', 'slug', 'prefix_count', 'vlan_count', 'description', 'weight', 'actions')
  216. default_columns = ('pk', 'name', 'prefix_count', 'vlan_count', 'description', 'actions')
  217. #
  218. # Prefixes
  219. #
  220. class PrefixUtilizationColumn(UtilizationColumn):
  221. """
  222. Extend UtilizationColumn to allow disabling the warning & danger thresholds for prefixes
  223. marked as fully utilized.
  224. """
  225. template_code = """
  226. {% load helpers %}
  227. {% if record.pk and record.mark_utilized %}
  228. {% utilization_graph value warning_threshold=0 danger_threshold=0 %}
  229. {% elif record.pk %}
  230. {% utilization_graph value %}
  231. {% endif %}
  232. """
  233. class PrefixTable(BaseTable):
  234. pk = ToggleColumn()
  235. prefix = tables.TemplateColumn(
  236. template_code=PREFIX_LINK,
  237. attrs={'td': {'class': 'text-nowrap'}}
  238. )
  239. status = ChoiceFieldColumn(
  240. default=AVAILABLE_LABEL
  241. )
  242. vrf = tables.TemplateColumn(
  243. template_code=VRF_LINK,
  244. verbose_name='VRF'
  245. )
  246. tenant = TenantColumn()
  247. site = tables.Column(
  248. linkify=True
  249. )
  250. vlan = tables.Column(
  251. linkify=True,
  252. verbose_name='VLAN'
  253. )
  254. role = tables.TemplateColumn(
  255. template_code=PREFIX_ROLE_LINK
  256. )
  257. is_pool = BooleanColumn(
  258. verbose_name='Pool'
  259. )
  260. mark_utilized = BooleanColumn(
  261. verbose_name='Marked Utilized'
  262. )
  263. class Meta(BaseTable.Meta):
  264. model = Prefix
  265. fields = (
  266. 'pk', 'prefix', 'status', 'children', 'vrf', 'tenant', 'site', 'vlan', 'role', 'is_pool', 'mark_utilized',
  267. 'description',
  268. )
  269. default_columns = ('pk', 'prefix', 'status', 'vrf', 'tenant', 'site', 'vlan', 'role', 'description')
  270. row_attrs = {
  271. 'class': lambda record: 'success' if not record.pk else '',
  272. }
  273. class PrefixDetailTable(PrefixTable):
  274. utilization = PrefixUtilizationColumn(
  275. accessor='get_utilization',
  276. orderable=False
  277. )
  278. tenant = TenantColumn()
  279. tags = TagColumn(
  280. url_name='ipam:prefix_list'
  281. )
  282. class Meta(PrefixTable.Meta):
  283. fields = (
  284. 'pk', 'prefix', 'status', 'children', 'vrf', 'utilization', 'tenant', 'site', 'vlan', 'role', 'is_pool',
  285. 'mark_utilized', 'description', 'tags',
  286. )
  287. default_columns = (
  288. 'pk', 'prefix', 'status', 'children', 'vrf', 'utilization', 'tenant', 'site', 'vlan', 'role', 'description',
  289. )
  290. #
  291. # IPAddresses
  292. #
  293. class IPAddressTable(BaseTable):
  294. pk = ToggleColumn()
  295. address = tables.TemplateColumn(
  296. template_code=IPADDRESS_LINK,
  297. verbose_name='IP Address'
  298. )
  299. vrf = tables.TemplateColumn(
  300. template_code=VRF_LINK,
  301. verbose_name='VRF'
  302. )
  303. status = ChoiceFieldColumn(
  304. default=AVAILABLE_LABEL
  305. )
  306. role = ChoiceFieldColumn()
  307. tenant = TenantColumn()
  308. assigned_object = tables.Column(
  309. linkify=True,
  310. orderable=False,
  311. verbose_name='Interface'
  312. )
  313. assigned_object_parent = tables.Column(
  314. accessor='assigned_object.parent_object',
  315. linkify=True,
  316. orderable=False,
  317. verbose_name='Device/VM'
  318. )
  319. class Meta(BaseTable.Meta):
  320. model = IPAddress
  321. fields = (
  322. 'pk', 'address', 'vrf', 'status', 'role', 'tenant', 'assigned_object', 'assigned_object_parent', 'dns_name',
  323. 'description',
  324. )
  325. row_attrs = {
  326. 'class': lambda record: 'success' if not isinstance(record, IPAddress) else '',
  327. }
  328. class IPAddressDetailTable(IPAddressTable):
  329. nat_inside = tables.Column(
  330. linkify=True,
  331. orderable=False,
  332. verbose_name='NAT (Inside)'
  333. )
  334. tenant = TenantColumn()
  335. assigned = BooleanColumn(
  336. accessor='assigned_object_id',
  337. verbose_name='Assigned'
  338. )
  339. tags = TagColumn(
  340. url_name='ipam:ipaddress_list'
  341. )
  342. class Meta(IPAddressTable.Meta):
  343. fields = (
  344. 'pk', 'address', 'vrf', 'status', 'role', 'tenant', 'nat_inside', 'assigned', 'dns_name',
  345. 'description', 'tags',
  346. )
  347. default_columns = (
  348. 'pk', 'address', 'vrf', 'status', 'role', 'tenant', 'assigned', 'dns_name', 'description',
  349. )
  350. class IPAddressAssignTable(BaseTable):
  351. address = tables.TemplateColumn(
  352. template_code=IPADDRESS_ASSIGN_LINK,
  353. verbose_name='IP Address'
  354. )
  355. status = ChoiceFieldColumn()
  356. assigned_object = tables.Column(
  357. orderable=False
  358. )
  359. class Meta(BaseTable.Meta):
  360. model = IPAddress
  361. fields = ('address', 'dns_name', 'vrf', 'status', 'role', 'tenant', 'assigned_object', 'description')
  362. orderable = False
  363. class InterfaceIPAddressTable(BaseTable):
  364. """
  365. List IP addresses assigned to a specific Interface.
  366. """
  367. address = tables.Column(
  368. linkify=True,
  369. verbose_name='IP Address'
  370. )
  371. vrf = tables.TemplateColumn(
  372. template_code=VRF_LINK,
  373. verbose_name='VRF'
  374. )
  375. status = ChoiceFieldColumn()
  376. tenant = TenantColumn()
  377. actions = ButtonsColumn(
  378. model=IPAddress
  379. )
  380. class Meta(BaseTable.Meta):
  381. model = IPAddress
  382. fields = ('address', 'vrf', 'status', 'role', 'tenant', 'description')
  383. #
  384. # VLAN groups
  385. #
  386. class VLANGroupTable(BaseTable):
  387. pk = ToggleColumn()
  388. name = tables.Column(linkify=True)
  389. scope_type = ContentTypeColumn()
  390. scope = tables.Column(
  391. linkify=True
  392. )
  393. vlan_count = LinkedCountColumn(
  394. viewname='ipam:vlan_list',
  395. url_params={'group_id': 'pk'},
  396. verbose_name='VLANs'
  397. )
  398. actions = ButtonsColumn(
  399. model=VLANGroup,
  400. prepend_template=VLANGROUP_ADD_VLAN
  401. )
  402. class Meta(BaseTable.Meta):
  403. model = VLANGroup
  404. fields = ('pk', 'name', 'scope_type', 'scope', 'vlan_count', 'slug', 'description', 'actions')
  405. default_columns = ('pk', 'name', 'scope_type', 'scope', 'vlan_count', 'description', 'actions')
  406. #
  407. # VLANs
  408. #
  409. class VLANTable(BaseTable):
  410. pk = ToggleColumn()
  411. vid = tables.TemplateColumn(
  412. template_code=VLAN_LINK,
  413. verbose_name='ID'
  414. )
  415. site = tables.Column(
  416. linkify=True
  417. )
  418. group = tables.Column(
  419. linkify=True
  420. )
  421. tenant = TenantColumn()
  422. status = ChoiceFieldColumn(
  423. default=AVAILABLE_LABEL
  424. )
  425. role = tables.TemplateColumn(
  426. template_code=VLAN_ROLE_LINK
  427. )
  428. class Meta(BaseTable.Meta):
  429. model = VLAN
  430. fields = ('pk', 'vid', 'site', 'group', 'name', 'tenant', 'status', 'role', 'description')
  431. row_attrs = {
  432. 'class': lambda record: 'success' if not isinstance(record, VLAN) else '',
  433. }
  434. class VLANDetailTable(VLANTable):
  435. prefixes = tables.TemplateColumn(
  436. template_code=VLAN_PREFIXES,
  437. orderable=False,
  438. verbose_name='Prefixes'
  439. )
  440. tenant = TenantColumn()
  441. tags = TagColumn(
  442. url_name='ipam:vlan_list'
  443. )
  444. class Meta(VLANTable.Meta):
  445. fields = ('pk', 'vid', 'site', 'group', 'name', 'prefixes', 'tenant', 'status', 'role', 'description', 'tags')
  446. default_columns = ('pk', 'vid', 'site', 'group', 'name', 'prefixes', 'tenant', 'status', 'role', 'description')
  447. class VLANMembersTable(BaseTable):
  448. """
  449. Base table for Interface and VMInterface assignments
  450. """
  451. name = tables.Column(
  452. linkify=True,
  453. verbose_name='Interface'
  454. )
  455. tagged = tables.TemplateColumn(
  456. template_code=VLAN_MEMBER_TAGGED,
  457. orderable=False
  458. )
  459. class VLANDevicesTable(VLANMembersTable):
  460. device = tables.Column(
  461. linkify=True
  462. )
  463. actions = ButtonsColumn(Interface, buttons=['edit'])
  464. class Meta(BaseTable.Meta):
  465. model = Interface
  466. fields = ('device', 'name', 'tagged', 'actions')
  467. class VLANVirtualMachinesTable(VLANMembersTable):
  468. virtual_machine = tables.Column(
  469. linkify=True
  470. )
  471. actions = ButtonsColumn(VMInterface, buttons=['edit'])
  472. class Meta(BaseTable.Meta):
  473. model = VMInterface
  474. fields = ('virtual_machine', 'name', 'tagged', 'actions')
  475. class InterfaceVLANTable(BaseTable):
  476. """
  477. List VLANs assigned to a specific Interface.
  478. """
  479. vid = tables.Column(
  480. linkify=True,
  481. verbose_name='ID'
  482. )
  483. tagged = BooleanColumn()
  484. site = tables.Column(
  485. linkify=True
  486. )
  487. group = tables.Column(
  488. accessor=Accessor('group__name'),
  489. verbose_name='Group'
  490. )
  491. tenant = TenantColumn()
  492. status = ChoiceFieldColumn()
  493. role = tables.TemplateColumn(
  494. template_code=VLAN_ROLE_LINK
  495. )
  496. class Meta(BaseTable.Meta):
  497. model = VLAN
  498. fields = ('vid', 'tagged', 'site', 'group', 'name', 'tenant', 'status', 'role', 'description')
  499. def __init__(self, interface, *args, **kwargs):
  500. self.interface = interface
  501. super().__init__(*args, **kwargs)
  502. #
  503. # Services
  504. #
  505. class ServiceTable(BaseTable):
  506. pk = ToggleColumn()
  507. name = tables.Column(
  508. linkify=True
  509. )
  510. parent = tables.Column(
  511. linkify=True,
  512. order_by=('device', 'virtual_machine')
  513. )
  514. ports = tables.TemplateColumn(
  515. template_code='{{ record.port_list }}',
  516. verbose_name='Ports'
  517. )
  518. tags = TagColumn(
  519. url_name='ipam:service_list'
  520. )
  521. class Meta(BaseTable.Meta):
  522. model = Service
  523. fields = ('pk', 'name', 'parent', 'protocol', 'ports', 'ipaddresses', 'description', 'tags')
  524. default_columns = ('pk', 'name', 'parent', 'protocol', 'ports', 'description')