tables.py 17 KB

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