tables.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660
  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 *
  12. AVAILABLE_LABEL = mark_safe('<span class="badge bg-success">Available</span>')
  13. PREFIX_LINK = """
  14. {% load helpers %}
  15. {% for i in record.depth|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 }}{% if record.vlan_group %}&group={{ record.vlan_group.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. prefix_flat = tables.Column(
  240. accessor=Accessor('prefix'),
  241. linkify=True,
  242. verbose_name='Prefix (Flat)'
  243. )
  244. depth = tables.Column(
  245. accessor=Accessor('_depth'),
  246. verbose_name='Depth'
  247. )
  248. children = LinkedCountColumn(
  249. accessor=Accessor('_children'),
  250. viewname='ipam:prefix_list',
  251. url_params={
  252. 'vrf_id': 'vrf_id',
  253. 'within': 'prefix',
  254. },
  255. verbose_name='Children'
  256. )
  257. status = ChoiceFieldColumn(
  258. default=AVAILABLE_LABEL
  259. )
  260. vrf = tables.TemplateColumn(
  261. template_code=VRF_LINK,
  262. verbose_name='VRF'
  263. )
  264. tenant = TenantColumn()
  265. site = tables.Column(
  266. linkify=True
  267. )
  268. vlan = tables.Column(
  269. linkify=True,
  270. verbose_name='VLAN'
  271. )
  272. role = tables.TemplateColumn(
  273. template_code=PREFIX_ROLE_LINK
  274. )
  275. is_pool = BooleanColumn(
  276. verbose_name='Pool'
  277. )
  278. mark_utilized = BooleanColumn(
  279. verbose_name='Marked Utilized'
  280. )
  281. class Meta(BaseTable.Meta):
  282. model = Prefix
  283. fields = (
  284. 'pk', 'prefix', 'prefix_flat', 'status', 'depth', 'children', 'vrf', 'tenant', 'site', 'vlan', 'role',
  285. 'is_pool', 'mark_utilized', 'description',
  286. )
  287. default_columns = ('pk', 'prefix', 'status', 'vrf', 'tenant', 'site', 'vlan', 'role', 'description')
  288. row_attrs = {
  289. 'class': lambda record: 'success' if not record.pk else '',
  290. }
  291. class PrefixDetailTable(PrefixTable):
  292. utilization = PrefixUtilizationColumn(
  293. accessor='get_utilization',
  294. orderable=False
  295. )
  296. tags = TagColumn(
  297. url_name='ipam:prefix_list'
  298. )
  299. class Meta(PrefixTable.Meta):
  300. fields = (
  301. 'pk', 'prefix', 'prefix_flat', 'status', 'children', 'vrf', 'utilization', 'tenant', 'site', 'vlan', 'role',
  302. 'is_pool', 'mark_utilized', 'description', 'tags',
  303. )
  304. default_columns = (
  305. 'pk', 'prefix', 'status', 'children', 'vrf', 'utilization', 'tenant', 'site', 'vlan', 'role', 'description',
  306. )
  307. #
  308. # IP ranges
  309. #
  310. class IPRangeTable(BaseTable):
  311. pk = ToggleColumn()
  312. start_address = tables.Column(
  313. linkify=True
  314. )
  315. vrf = tables.TemplateColumn(
  316. template_code=VRF_LINK,
  317. verbose_name='VRF'
  318. )
  319. status = ChoiceFieldColumn(
  320. default=AVAILABLE_LABEL
  321. )
  322. role = tables.TemplateColumn(
  323. template_code=PREFIX_ROLE_LINK
  324. )
  325. tenant = TenantColumn()
  326. class Meta(BaseTable.Meta):
  327. model = IPRange
  328. fields = (
  329. 'pk', 'start_address', 'end_address', 'size', 'vrf', 'status', 'role', 'tenant', 'description',
  330. )
  331. default_columns = (
  332. 'pk', 'start_address', 'end_address', 'size', 'vrf', 'status', 'role', 'tenant', 'description',
  333. )
  334. row_attrs = {
  335. 'class': lambda record: 'success' if not record.pk else '',
  336. }
  337. #
  338. # IPAddresses
  339. #
  340. class IPAddressTable(BaseTable):
  341. pk = ToggleColumn()
  342. address = tables.TemplateColumn(
  343. template_code=IPADDRESS_LINK,
  344. verbose_name='IP Address'
  345. )
  346. vrf = tables.TemplateColumn(
  347. template_code=VRF_LINK,
  348. verbose_name='VRF'
  349. )
  350. status = ChoiceFieldColumn(
  351. default=AVAILABLE_LABEL
  352. )
  353. role = ChoiceFieldColumn()
  354. tenant = TenantColumn()
  355. assigned_object = tables.Column(
  356. linkify=True,
  357. orderable=False,
  358. verbose_name='Interface'
  359. )
  360. assigned_object_parent = tables.Column(
  361. accessor='assigned_object.parent_object',
  362. linkify=True,
  363. orderable=False,
  364. verbose_name='Device/VM'
  365. )
  366. class Meta(BaseTable.Meta):
  367. model = IPAddress
  368. fields = (
  369. 'pk', 'address', 'vrf', 'status', 'role', 'tenant', 'assigned_object', 'assigned_object_parent', 'dns_name',
  370. 'description',
  371. )
  372. row_attrs = {
  373. 'class': lambda record: 'success' if not isinstance(record, IPAddress) else '',
  374. }
  375. class IPAddressDetailTable(IPAddressTable):
  376. nat_inside = tables.Column(
  377. linkify=True,
  378. orderable=False,
  379. verbose_name='NAT (Inside)'
  380. )
  381. tenant = TenantColumn()
  382. assigned = BooleanColumn(
  383. accessor='assigned_object_id',
  384. verbose_name='Assigned'
  385. )
  386. tags = TagColumn(
  387. url_name='ipam:ipaddress_list'
  388. )
  389. class Meta(IPAddressTable.Meta):
  390. fields = (
  391. 'pk', 'address', 'vrf', 'status', 'role', 'tenant', 'nat_inside', 'assigned', 'dns_name',
  392. 'description', 'tags',
  393. )
  394. default_columns = (
  395. 'pk', 'address', 'vrf', 'status', 'role', 'tenant', 'assigned', 'dns_name', 'description',
  396. )
  397. class IPAddressAssignTable(BaseTable):
  398. address = tables.TemplateColumn(
  399. template_code=IPADDRESS_ASSIGN_LINK,
  400. verbose_name='IP Address'
  401. )
  402. status = ChoiceFieldColumn()
  403. assigned_object = tables.Column(
  404. orderable=False
  405. )
  406. class Meta(BaseTable.Meta):
  407. model = IPAddress
  408. fields = ('address', 'dns_name', 'vrf', 'status', 'role', 'tenant', 'assigned_object', 'description')
  409. orderable = False
  410. class InterfaceIPAddressTable(BaseTable):
  411. """
  412. List IP addresses assigned to a specific Interface.
  413. """
  414. address = tables.Column(
  415. linkify=True,
  416. verbose_name='IP Address'
  417. )
  418. vrf = tables.TemplateColumn(
  419. template_code=VRF_LINK,
  420. verbose_name='VRF'
  421. )
  422. status = ChoiceFieldColumn()
  423. tenant = TenantColumn()
  424. actions = ButtonsColumn(
  425. model=IPAddress
  426. )
  427. class Meta(BaseTable.Meta):
  428. model = IPAddress
  429. fields = ('address', 'vrf', 'status', 'role', 'tenant', 'description')
  430. #
  431. # VLAN groups
  432. #
  433. class VLANGroupTable(BaseTable):
  434. pk = ToggleColumn()
  435. name = tables.Column(linkify=True)
  436. scope_type = ContentTypeColumn()
  437. scope = tables.Column(
  438. linkify=True,
  439. orderable=False
  440. )
  441. vlan_count = LinkedCountColumn(
  442. viewname='ipam:vlan_list',
  443. url_params={'group_id': 'pk'},
  444. verbose_name='VLANs'
  445. )
  446. actions = ButtonsColumn(
  447. model=VLANGroup,
  448. prepend_template=VLANGROUP_ADD_VLAN
  449. )
  450. class Meta(BaseTable.Meta):
  451. model = VLANGroup
  452. fields = ('pk', 'name', 'scope_type', 'scope', 'vlan_count', 'slug', 'description', 'actions')
  453. default_columns = ('pk', 'name', 'scope_type', 'scope', 'vlan_count', 'description', 'actions')
  454. #
  455. # VLANs
  456. #
  457. class VLANTable(BaseTable):
  458. pk = ToggleColumn()
  459. vid = tables.TemplateColumn(
  460. template_code=VLAN_LINK,
  461. verbose_name='ID'
  462. )
  463. site = tables.Column(
  464. linkify=True
  465. )
  466. group = tables.Column(
  467. linkify=True
  468. )
  469. tenant = TenantColumn()
  470. status = ChoiceFieldColumn(
  471. default=AVAILABLE_LABEL
  472. )
  473. role = tables.TemplateColumn(
  474. template_code=VLAN_ROLE_LINK
  475. )
  476. class Meta(BaseTable.Meta):
  477. model = VLAN
  478. fields = ('pk', 'vid', 'site', 'group', 'name', 'tenant', 'status', 'role', 'description')
  479. row_attrs = {
  480. 'class': lambda record: 'success' if not isinstance(record, VLAN) else '',
  481. }
  482. class VLANDetailTable(VLANTable):
  483. prefixes = tables.TemplateColumn(
  484. template_code=VLAN_PREFIXES,
  485. orderable=False,
  486. verbose_name='Prefixes'
  487. )
  488. tenant = TenantColumn()
  489. tags = TagColumn(
  490. url_name='ipam:vlan_list'
  491. )
  492. class Meta(VLANTable.Meta):
  493. fields = ('pk', 'vid', 'site', 'group', 'name', 'prefixes', 'tenant', 'status', 'role', 'description', 'tags')
  494. default_columns = ('pk', 'vid', 'site', 'group', 'name', 'prefixes', 'tenant', 'status', 'role', 'description')
  495. class VLANMembersTable(BaseTable):
  496. """
  497. Base table for Interface and VMInterface assignments
  498. """
  499. name = tables.Column(
  500. linkify=True,
  501. verbose_name='Interface'
  502. )
  503. tagged = tables.TemplateColumn(
  504. template_code=VLAN_MEMBER_TAGGED,
  505. orderable=False
  506. )
  507. class VLANDevicesTable(VLANMembersTable):
  508. device = tables.Column(
  509. linkify=True
  510. )
  511. actions = ButtonsColumn(Interface, buttons=['edit'])
  512. class Meta(BaseTable.Meta):
  513. model = Interface
  514. fields = ('device', 'name', 'tagged', 'actions')
  515. class VLANVirtualMachinesTable(VLANMembersTable):
  516. virtual_machine = tables.Column(
  517. linkify=True
  518. )
  519. actions = ButtonsColumn(VMInterface, buttons=['edit'])
  520. class Meta(BaseTable.Meta):
  521. model = VMInterface
  522. fields = ('virtual_machine', 'name', 'tagged', 'actions')
  523. class InterfaceVLANTable(BaseTable):
  524. """
  525. List VLANs assigned to a specific Interface.
  526. """
  527. vid = tables.Column(
  528. linkify=True,
  529. verbose_name='ID'
  530. )
  531. tagged = BooleanColumn()
  532. site = tables.Column(
  533. linkify=True
  534. )
  535. group = tables.Column(
  536. accessor=Accessor('group__name'),
  537. verbose_name='Group'
  538. )
  539. tenant = TenantColumn()
  540. status = ChoiceFieldColumn()
  541. role = tables.TemplateColumn(
  542. template_code=VLAN_ROLE_LINK
  543. )
  544. class Meta(BaseTable.Meta):
  545. model = VLAN
  546. fields = ('vid', 'tagged', 'site', 'group', 'name', 'tenant', 'status', 'role', 'description')
  547. def __init__(self, interface, *args, **kwargs):
  548. self.interface = interface
  549. super().__init__(*args, **kwargs)
  550. #
  551. # Services
  552. #
  553. class ServiceTable(BaseTable):
  554. pk = ToggleColumn()
  555. name = tables.Column(
  556. linkify=True
  557. )
  558. parent = tables.Column(
  559. linkify=True,
  560. order_by=('device', 'virtual_machine')
  561. )
  562. ports = tables.TemplateColumn(
  563. template_code='{{ record.port_list }}',
  564. verbose_name='Ports'
  565. )
  566. tags = TagColumn(
  567. url_name='ipam:service_list'
  568. )
  569. class Meta(BaseTable.Meta):
  570. model = Service
  571. fields = ('pk', 'name', 'parent', 'protocol', 'ports', 'ipaddresses', 'description', 'tags')
  572. default_columns = ('pk', 'name', 'parent', 'protocol', 'ports', 'description')