tables.py 19 KB

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