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