tables.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658
  1. import json
  2. import django_tables2 as tables
  3. from django.utils.html import format_html
  4. from django.utils.translation import gettext_lazy as _
  5. from extras.models import *
  6. from netbox.constants import EMPTY_TABLE_TEXT
  7. from netbox.events import get_event_text
  8. from netbox.tables import BaseTable, NetBoxTable, columns
  9. from .columns import NotificationActionsColumn
  10. __all__ = (
  11. 'BookmarkTable',
  12. 'ConfigContextTable',
  13. 'ConfigTemplateTable',
  14. 'CustomFieldChoiceSetTable',
  15. 'CustomFieldTable',
  16. 'CustomLinkTable',
  17. 'EventRuleTable',
  18. 'ExportTemplateTable',
  19. 'ImageAttachmentTable',
  20. 'JournalEntryTable',
  21. 'NotificationGroupTable',
  22. 'NotificationTable',
  23. 'SavedFilterTable',
  24. 'ReportResultsTable',
  25. 'ScriptResultsTable',
  26. 'SubscriptionTable',
  27. 'TaggedItemTable',
  28. 'TagTable',
  29. 'WebhookTable',
  30. )
  31. IMAGEATTACHMENT_IMAGE = """
  32. {% if record.image %}
  33. <a class="image-preview" href="{{ record.image.url }}" target="_blank">{{ record }}</a>
  34. {% else %}
  35. &mdash;
  36. {% endif %}
  37. """
  38. NOTIFICATION_ICON = """
  39. <span class="text-{{ value.color }} fs-3"><i class="{{ value.icon }}"></i></span>
  40. """
  41. class CustomFieldTable(NetBoxTable):
  42. name = tables.Column(
  43. verbose_name=_('Name'),
  44. linkify=True
  45. )
  46. object_types = columns.ContentTypesColumn(
  47. verbose_name=_('Object Types')
  48. )
  49. required = columns.BooleanColumn(
  50. verbose_name=_('Required')
  51. )
  52. ui_visible = columns.ChoiceFieldColumn(
  53. verbose_name=_('Visible')
  54. )
  55. ui_editable = columns.ChoiceFieldColumn(
  56. verbose_name=_('Editable')
  57. )
  58. description = columns.MarkdownColumn(
  59. verbose_name=_('Description')
  60. )
  61. related_object_type = columns.ContentTypeColumn(
  62. verbose_name=_('Related Object Type')
  63. )
  64. choice_set = tables.Column(
  65. linkify=True,
  66. verbose_name=_('Choice Set')
  67. )
  68. choices = columns.ChoicesColumn(
  69. max_items=10,
  70. orderable=False,
  71. verbose_name=_('Choices')
  72. )
  73. is_cloneable = columns.BooleanColumn(
  74. verbose_name=_('Is Cloneable'),
  75. )
  76. validation_minimum = tables.Column(
  77. verbose_name=_('Minimum Value'),
  78. )
  79. validation_maximum = tables.Column(
  80. verbose_name=_('Maximum Value'),
  81. )
  82. validation_regex = tables.Column(
  83. verbose_name=_('Validation Regex'),
  84. )
  85. validation_unique = columns.BooleanColumn(
  86. verbose_name=_('Validate Uniqueness'),
  87. )
  88. class Meta(NetBoxTable.Meta):
  89. model = CustomField
  90. fields = (
  91. 'pk', 'id', 'name', 'object_types', 'label', 'type', 'related_object_type', 'group_name', 'required',
  92. 'default', 'description', 'search_weight', 'filter_logic', 'ui_visible', 'ui_editable', 'is_cloneable',
  93. 'weight', 'choice_set', 'choices', 'validation_minimum', 'validation_maximum', 'validation_regex',
  94. 'validation_unique', 'comments', 'created', 'last_updated',
  95. )
  96. default_columns = ('pk', 'name', 'object_types', 'label', 'group_name', 'type', 'required', 'description')
  97. class CustomFieldChoiceSetTable(NetBoxTable):
  98. name = tables.Column(
  99. verbose_name=_('Name'),
  100. linkify=True
  101. )
  102. base_choices = columns.ChoiceFieldColumn()
  103. extra_choices = tables.TemplateColumn(
  104. template_code="""{% for k, v in value.items %}{{ v }}{% if not forloop.last %}, {% endif %}{% endfor %}"""
  105. )
  106. choices = columns.ChoicesColumn(
  107. max_items=10,
  108. orderable=False
  109. )
  110. choice_count = tables.TemplateColumn(
  111. accessor=tables.A('extra_choices'),
  112. template_code='{{ value|length }}',
  113. orderable=False,
  114. verbose_name=_('Count')
  115. )
  116. order_alphabetically = columns.BooleanColumn(
  117. verbose_name=_('Order Alphabetically'),
  118. )
  119. class Meta(NetBoxTable.Meta):
  120. model = CustomFieldChoiceSet
  121. fields = (
  122. 'pk', 'id', 'name', 'description', 'base_choices', 'extra_choices', 'choice_count', 'choices',
  123. 'order_alphabetically', 'created', 'last_updated',
  124. )
  125. default_columns = ('pk', 'name', 'base_choices', 'choice_count', 'description')
  126. class CustomLinkTable(NetBoxTable):
  127. name = tables.Column(
  128. verbose_name=_('Name'),
  129. linkify=True
  130. )
  131. object_types = columns.ContentTypesColumn(
  132. verbose_name=_('Object Types'),
  133. )
  134. enabled = columns.BooleanColumn(
  135. verbose_name=_('Enabled'),
  136. )
  137. new_window = columns.BooleanColumn(
  138. verbose_name=_('New Window'),
  139. )
  140. class Meta(NetBoxTable.Meta):
  141. model = CustomLink
  142. fields = (
  143. 'pk', 'id', 'name', 'object_types', 'enabled', 'link_text', 'link_url', 'weight', 'group_name',
  144. 'button_class', 'new_window', 'created', 'last_updated',
  145. )
  146. default_columns = ('pk', 'name', 'object_types', 'enabled', 'group_name', 'button_class', 'new_window')
  147. class ExportTemplateTable(NetBoxTable):
  148. name = tables.Column(
  149. verbose_name=_('Name'),
  150. linkify=True
  151. )
  152. object_types = columns.ContentTypesColumn(
  153. verbose_name=_('Object Types'),
  154. )
  155. as_attachment = columns.BooleanColumn(
  156. verbose_name=_('As Attachment'),
  157. )
  158. data_source = tables.Column(
  159. verbose_name=_('Data Source'),
  160. linkify=True
  161. )
  162. data_file = tables.Column(
  163. verbose_name=_('Data File'),
  164. linkify=True
  165. )
  166. is_synced = columns.BooleanColumn(
  167. orderable=False,
  168. verbose_name=_('Synced')
  169. )
  170. class Meta(NetBoxTable.Meta):
  171. model = ExportTemplate
  172. fields = (
  173. 'pk', 'id', 'name', 'object_types', 'description', 'mime_type', 'file_extension', 'as_attachment',
  174. 'data_source', 'data_file', 'data_synced', 'created', 'last_updated',
  175. )
  176. default_columns = (
  177. 'pk', 'name', 'object_types', 'description', 'mime_type', 'file_extension', 'as_attachment', 'is_synced',
  178. )
  179. class ImageAttachmentTable(NetBoxTable):
  180. id = tables.Column(
  181. verbose_name=_('ID'),
  182. linkify=False
  183. )
  184. object_type = columns.ContentTypeColumn(
  185. verbose_name=_('Object Type'),
  186. )
  187. parent = tables.Column(
  188. verbose_name=_('Parent'),
  189. linkify=True
  190. )
  191. image = tables.TemplateColumn(
  192. verbose_name=_('Image'),
  193. template_code=IMAGEATTACHMENT_IMAGE,
  194. )
  195. size = tables.Column(
  196. orderable=False,
  197. verbose_name=_('Size (Bytes)')
  198. )
  199. class Meta(NetBoxTable.Meta):
  200. model = ImageAttachment
  201. fields = (
  202. 'pk', 'object_type', 'parent', 'image', 'name', 'image_height', 'image_width', 'size', 'created',
  203. 'last_updated',
  204. )
  205. default_columns = ('object_type', 'parent', 'image', 'name', 'size', 'created')
  206. class SavedFilterTable(NetBoxTable):
  207. name = tables.Column(
  208. verbose_name=_('Name'),
  209. linkify=True
  210. )
  211. object_types = columns.ContentTypesColumn(
  212. verbose_name=_('Object Types'),
  213. )
  214. enabled = columns.BooleanColumn(
  215. verbose_name=_('Enabled'),
  216. )
  217. shared = columns.BooleanColumn(
  218. verbose_name=_('Shared'),
  219. )
  220. def value_parameters(self, value):
  221. return json.dumps(value)
  222. class Meta(NetBoxTable.Meta):
  223. model = SavedFilter
  224. fields = (
  225. 'pk', 'id', 'name', 'slug', 'object_types', 'description', 'user', 'weight', 'enabled', 'shared',
  226. 'created', 'last_updated', 'parameters'
  227. )
  228. default_columns = (
  229. 'pk', 'name', 'object_types', 'user', 'description', 'enabled', 'shared',
  230. )
  231. class BookmarkTable(NetBoxTable):
  232. object_type = columns.ContentTypeColumn(
  233. verbose_name=_('Object Types'),
  234. )
  235. object = tables.Column(
  236. verbose_name=_('Object'),
  237. linkify=True
  238. )
  239. actions = columns.ActionsColumn(
  240. actions=('delete',)
  241. )
  242. class Meta(NetBoxTable.Meta):
  243. model = Bookmark
  244. fields = ('pk', 'object', 'object_type', 'created')
  245. default_columns = ('object', 'object_type', 'created')
  246. class SubscriptionTable(NetBoxTable):
  247. object_type = columns.ContentTypeColumn(
  248. verbose_name=_('Object Type'),
  249. )
  250. object = tables.Column(
  251. verbose_name=_('Object'),
  252. linkify=True,
  253. orderable=False
  254. )
  255. user = tables.Column(
  256. verbose_name=_('User'),
  257. linkify=True
  258. )
  259. actions = columns.ActionsColumn(
  260. actions=('delete',)
  261. )
  262. class Meta(NetBoxTable.Meta):
  263. model = Subscription
  264. fields = ('pk', 'object', 'object_type', 'created', 'user')
  265. default_columns = ('object', 'object_type', 'created')
  266. class NotificationTable(NetBoxTable):
  267. icon = columns.TemplateColumn(
  268. template_code=NOTIFICATION_ICON,
  269. accessor=tables.A('event'),
  270. attrs={
  271. 'td': {'class': 'w-1'},
  272. 'th': {'class': 'w-1'},
  273. },
  274. verbose_name=''
  275. )
  276. object_type = columns.ContentTypeColumn(
  277. verbose_name=_('Object Type'),
  278. )
  279. object = tables.Column(
  280. verbose_name=_('Object'),
  281. linkify={
  282. 'viewname': 'extras:notification_read',
  283. 'args': [tables.A('pk')],
  284. },
  285. orderable=False
  286. )
  287. created = columns.DateTimeColumn(
  288. timespec='minutes',
  289. verbose_name=_('Created'),
  290. )
  291. read = columns.DateTimeColumn(
  292. timespec='minutes',
  293. verbose_name=_('Read'),
  294. )
  295. user = tables.Column(
  296. verbose_name=_('User'),
  297. linkify=True
  298. )
  299. actions = NotificationActionsColumn(
  300. actions=('dismiss',)
  301. )
  302. class Meta(NetBoxTable.Meta):
  303. model = Notification
  304. fields = ('pk', 'icon', 'object', 'object_type', 'event_type', 'created', 'read', 'user')
  305. default_columns = ('icon', 'object', 'object_type', 'event_type', 'created')
  306. row_attrs = {
  307. 'data-read': lambda record: bool(record.read),
  308. }
  309. class NotificationGroupTable(NetBoxTable):
  310. name = tables.Column(
  311. linkify=True,
  312. verbose_name=_('Name')
  313. )
  314. users = columns.ManyToManyColumn(
  315. linkify_item=True
  316. )
  317. groups = columns.ManyToManyColumn(
  318. linkify_item=True
  319. )
  320. class Meta(NetBoxTable.Meta):
  321. model = NotificationGroup
  322. fields = ('pk', 'name', 'description', 'groups', 'users')
  323. default_columns = ('name', 'description', 'groups', 'users')
  324. class WebhookTable(NetBoxTable):
  325. name = tables.Column(
  326. verbose_name=_('Name'),
  327. linkify=True
  328. )
  329. ssl_validation = columns.BooleanColumn(
  330. verbose_name=_('SSL Validation')
  331. )
  332. tags = columns.TagColumn(
  333. url_name='extras:webhook_list'
  334. )
  335. class Meta(NetBoxTable.Meta):
  336. model = Webhook
  337. fields = (
  338. 'pk', 'id', 'name', 'http_method', 'payload_url', 'http_content_type', 'secret', 'ssl_verification',
  339. 'ca_file_path', 'description', 'tags', 'created', 'last_updated',
  340. )
  341. default_columns = (
  342. 'pk', 'name', 'http_method', 'payload_url', 'description',
  343. )
  344. class EventRuleTable(NetBoxTable):
  345. name = tables.Column(
  346. verbose_name=_('Name'),
  347. linkify=True
  348. )
  349. action_type = tables.Column(
  350. verbose_name=_('Type'),
  351. )
  352. action_object = tables.Column(
  353. linkify=True,
  354. verbose_name=_('Object'),
  355. )
  356. object_types = columns.ContentTypesColumn(
  357. verbose_name=_('Object Types'),
  358. )
  359. enabled = columns.BooleanColumn(
  360. verbose_name=_('Enabled'),
  361. )
  362. event_types = columns.ArrayColumn(
  363. verbose_name=_('Event Types'),
  364. func=get_event_text,
  365. orderable=False
  366. )
  367. tags = columns.TagColumn(
  368. url_name='extras:webhook_list'
  369. )
  370. class Meta(NetBoxTable.Meta):
  371. model = EventRule
  372. fields = (
  373. 'pk', 'id', 'name', 'enabled', 'description', 'action_type', 'action_object', 'object_types',
  374. 'event_types', 'tags', 'created', 'last_updated',
  375. )
  376. default_columns = (
  377. 'pk', 'name', 'enabled', 'action_type', 'action_object', 'object_types', 'event_types',
  378. )
  379. class TagTable(NetBoxTable):
  380. name = tables.Column(
  381. verbose_name=_('Name'),
  382. linkify=True
  383. )
  384. color = columns.ColorColumn(
  385. verbose_name=_('Color'),
  386. )
  387. object_types = columns.ContentTypesColumn(
  388. verbose_name=_('Object Types'),
  389. )
  390. class Meta(NetBoxTable.Meta):
  391. model = Tag
  392. fields = (
  393. 'pk', 'id', 'name', 'items', 'slug', 'color', 'description', 'object_types', 'created', 'last_updated',
  394. 'actions',
  395. )
  396. default_columns = ('pk', 'name', 'items', 'slug', 'color', 'description')
  397. class TaggedItemTable(NetBoxTable):
  398. id = tables.Column(
  399. verbose_name=_('ID'),
  400. linkify=lambda record: record.content_object.get_absolute_url(),
  401. accessor='content_object__id'
  402. )
  403. content_type = columns.ContentTypeColumn(
  404. verbose_name=_('Type')
  405. )
  406. content_object = tables.Column(
  407. linkify=True,
  408. orderable=False,
  409. verbose_name=_('Object')
  410. )
  411. actions = columns.ActionsColumn(
  412. actions=()
  413. )
  414. class Meta(NetBoxTable.Meta):
  415. model = TaggedItem
  416. fields = ('id', 'content_type', 'content_object')
  417. class ConfigContextTable(NetBoxTable):
  418. data_source = tables.Column(
  419. verbose_name=_('Data Source'),
  420. linkify=True
  421. )
  422. data_file = tables.Column(
  423. verbose_name=_('Data File'),
  424. linkify=True
  425. )
  426. name = tables.Column(
  427. verbose_name=_('Name'),
  428. linkify=True
  429. )
  430. is_active = columns.BooleanColumn(
  431. verbose_name=_('Active')
  432. )
  433. is_synced = columns.BooleanColumn(
  434. orderable=False,
  435. verbose_name=_('Synced')
  436. )
  437. class Meta(NetBoxTable.Meta):
  438. model = ConfigContext
  439. fields = (
  440. 'pk', 'id', 'name', 'weight', 'is_active', 'is_synced', 'description', 'regions', 'sites', 'locations',
  441. 'roles', 'platforms', 'cluster_types', 'cluster_groups', 'clusters', 'tenant_groups', 'tenants',
  442. 'data_source', 'data_file', 'data_synced', 'created', 'last_updated',
  443. )
  444. default_columns = ('pk', 'name', 'weight', 'is_active', 'is_synced', 'description')
  445. class ConfigTemplateTable(NetBoxTable):
  446. name = tables.Column(
  447. verbose_name=_('Name'),
  448. linkify=True
  449. )
  450. data_source = tables.Column(
  451. verbose_name=_('Data Source'),
  452. linkify=True
  453. )
  454. data_file = tables.Column(
  455. verbose_name=_('Data File'),
  456. linkify=True
  457. )
  458. is_synced = columns.BooleanColumn(
  459. orderable=False,
  460. verbose_name=_('Synced')
  461. )
  462. tags = columns.TagColumn(
  463. url_name='extras:configtemplate_list'
  464. )
  465. role_count = columns.LinkedCountColumn(
  466. viewname='dcim:devicerole_list',
  467. url_params={'config_template_id': 'pk'},
  468. verbose_name=_('Device Roles')
  469. )
  470. platform_count = columns.LinkedCountColumn(
  471. viewname='dcim:platform_list',
  472. url_params={'config_template_id': 'pk'},
  473. verbose_name=_('Platforms')
  474. )
  475. device_count = columns.LinkedCountColumn(
  476. viewname='dcim:device_list',
  477. url_params={'config_template_id': 'pk'},
  478. verbose_name=_('Devices')
  479. )
  480. vm_count = columns.LinkedCountColumn(
  481. viewname='virtualization:virtualmachine_list',
  482. url_params={'config_template_id': 'pk'},
  483. verbose_name=_('Virtual Machines')
  484. )
  485. class Meta(NetBoxTable.Meta):
  486. model = ConfigTemplate
  487. fields = (
  488. 'pk', 'id', 'name', 'description', 'data_source', 'data_file', 'data_synced', 'role_count',
  489. 'platform_count', 'device_count', 'vm_count', 'created', 'last_updated', 'tags',
  490. )
  491. default_columns = (
  492. 'pk', 'name', 'description', 'is_synced', 'device_count', 'vm_count',
  493. )
  494. class JournalEntryTable(NetBoxTable):
  495. created = columns.DateTimeColumn(
  496. verbose_name=_('Created'),
  497. timespec='minutes',
  498. linkify=True
  499. )
  500. assigned_object_type = columns.ContentTypeColumn(
  501. verbose_name=_('Object Type')
  502. )
  503. assigned_object = tables.Column(
  504. linkify=True,
  505. orderable=False,
  506. verbose_name=_('Object')
  507. )
  508. kind = columns.ChoiceFieldColumn(
  509. verbose_name=_('Kind'),
  510. )
  511. comments = columns.MarkdownColumn(
  512. verbose_name=_('Comments'),
  513. )
  514. comments_short = tables.TemplateColumn(
  515. accessor=tables.A('comments'),
  516. template_code='{{ value|markdown|truncatewords_html:50 }}',
  517. verbose_name=_('Comments (Short)')
  518. )
  519. tags = columns.TagColumn(
  520. url_name='extras:journalentry_list'
  521. )
  522. class Meta(NetBoxTable.Meta):
  523. model = JournalEntry
  524. fields = (
  525. 'pk', 'id', 'created', 'created_by', 'assigned_object_type', 'assigned_object', 'kind', 'comments',
  526. 'comments_short', 'tags', 'actions',
  527. )
  528. default_columns = (
  529. 'pk', 'created', 'created_by', 'assigned_object_type', 'assigned_object', 'kind', 'comments'
  530. )
  531. class ScriptResultsTable(BaseTable):
  532. index = tables.Column(
  533. verbose_name=_('Line')
  534. )
  535. time = tables.Column(
  536. verbose_name=_('Time')
  537. )
  538. status = tables.TemplateColumn(
  539. template_code="""{% load log_levels %}{% log_level record.status %}""",
  540. verbose_name=_('Level')
  541. )
  542. object = tables.Column(
  543. verbose_name=_('Object')
  544. )
  545. message = columns.MarkdownColumn(
  546. verbose_name=_('Message')
  547. )
  548. class Meta(BaseTable.Meta):
  549. empty_text = _(EMPTY_TABLE_TEXT)
  550. fields = (
  551. 'index', 'time', 'status', 'object', 'message',
  552. )
  553. default_columns = (
  554. 'index', 'time', 'status', 'object', 'message',
  555. )
  556. def render_object(self, value, record):
  557. return format_html("<a href='{}'>{}</a>", record['url'], value)
  558. def render_url(self, value):
  559. return format_html("<a href='{}'>{}</a>", value, value)
  560. class ReportResultsTable(BaseTable):
  561. index = tables.Column(
  562. verbose_name=_('Line')
  563. )
  564. method = tables.Column(
  565. verbose_name=_('Method')
  566. )
  567. time = tables.Column(
  568. verbose_name=_('Time')
  569. )
  570. status = tables.TemplateColumn(
  571. template_code="""{% load log_levels %}{% log_level record.status %}""",
  572. verbose_name=_('Level')
  573. )
  574. object = tables.Column(
  575. verbose_name=_('Object')
  576. )
  577. url = tables.Column(
  578. verbose_name=_('URL')
  579. )
  580. message = columns.MarkdownColumn(
  581. verbose_name=_('Message')
  582. )
  583. class Meta(BaseTable.Meta):
  584. empty_text = _(EMPTY_TABLE_TEXT)
  585. fields = (
  586. 'index', 'method', 'time', 'status', 'object', 'url', 'message',
  587. )
  588. def render_object(self, value, record):
  589. return format_html("<a href='{}'>{}</a>", record['url'], value)
  590. def render_url(self, value):
  591. return format_html("<a href='{}'>{}</a>", value, value)