from collections import namedtuple import django_tables2 as tables from django.conf import settings from django.urls import reverse from django.utils.safestring import mark_safe from django_tables2.utils import Accessor from extras.choices import CustomFieldTypeChoices from utilities.utils import content_type_identifier, content_type_name __all__ = ( 'ActionsColumn', 'BooleanColumn', 'ButtonsColumn', 'ChoiceFieldColumn', 'ColorColumn', 'ColoredLabelColumn', 'ContentTypeColumn', 'ContentTypesColumn', 'CustomFieldColumn', 'CustomLinkColumn', 'LinkedCountColumn', 'MarkdownColumn', 'MPTTColumn', 'TagColumn', 'TemplateColumn', 'ToggleColumn', 'UtilizationColumn', ) class ToggleColumn(tables.CheckBoxColumn): """ Extend CheckBoxColumn to add a "toggle all" checkbox in the column header. """ def __init__(self, *args, **kwargs): default = kwargs.pop('default', '') visible = kwargs.pop('visible', False) if 'attrs' not in kwargs: kwargs['attrs'] = { 'td': { 'class': 'min-width', }, 'input': { 'class': 'form-check-input' } } super().__init__(*args, default=default, visible=visible, **kwargs) @property def header(self): return mark_safe('') class BooleanColumn(tables.Column): """ Custom implementation of BooleanColumn to render a nicely-formatted checkmark or X icon instead of a Unicode character. """ def render(self, value): if value: rendered = '' elif value is None: rendered = '—' else: rendered = '' return mark_safe(rendered) def value(self, value): return str(value) class TemplateColumn(tables.TemplateColumn): """ Overrides the stock TemplateColumn to render a placeholder if the returned value is an empty string. """ PLACEHOLDER = mark_safe('—') def render(self, *args, **kwargs): ret = super().render(*args, **kwargs) if not ret.strip(): return self.PLACEHOLDER return ret def value(self, **kwargs): ret = super().value(**kwargs) if ret == self.PLACEHOLDER: return '' return ret ActionsMenuItem = namedtuple('ActionsMenuItem', ['title', 'icon']) class ActionsColumn(tables.Column): attrs = {'td': {'class': 'text-end noprint'}} empty_values = () _actions = { 'edit': ActionsMenuItem('Edit', 'pencil'), 'delete': ActionsMenuItem('Delete', 'trash-can-outline'), 'changelog': ActionsMenuItem('Changelog', 'history'), } def __init__(self, *args, actions=('edit', 'delete', 'changelog'), **kwargs): super().__init__(*args, **kwargs) # Determine which actions to enable self.actions = { name: self._actions[name] for name in actions } def header(self): return '' def render(self, record, table, **kwargs): # Skip dummy records (e.g. available VLANs) or those with no actions if not hasattr(record, 'pk') or not self.actions: return '' model = table.Meta.model viewname_base = f'{model._meta.app_label}:{model._meta.model_name}' request = getattr(table, 'context', {}).get('request') url_appendix = f'?return_url={request.path}' if request else '' menu = '
' return mark_safe(menu) class ButtonsColumn(tables.TemplateColumn): """ Render edit, delete, and changelog buttons for an object. :param model: Model class to use for calculating URL view names :param prepend_content: Additional template content to render in the column (optional) """ buttons = ('changelog', 'edit', 'delete') attrs = {'td': {'class': 'text-end text-nowrap noprint'}} # Note that braces are escaped to allow for string formatting prior to template rendering template_code = """ {{% if "changelog" in buttons %}} {{% endif %}} {{% if "edit" in buttons and perms.{app_label}.change_{model_name} %}} {{% endif %}} {{% if "delete" in buttons and perms.{app_label}.delete_{model_name} %}} {{% endif %}} """ def __init__(self, model, *args, buttons=None, prepend_template=None, **kwargs): if prepend_template: prepend_template = prepend_template.replace('{', '{{') prepend_template = prepend_template.replace('}', '}}') self.template_code = prepend_template + self.template_code template_code = self.template_code.format( app_label=model._meta.app_label, model_name=model._meta.model_name, buttons=buttons ) super().__init__(template_code=template_code, *args, **kwargs) # Exclude from export by default if 'exclude_from_export' not in kwargs: self.exclude_from_export = True self.extra_context.update({ 'buttons': buttons or self.buttons, }) def header(self): return '' class ChoiceFieldColumn(tables.Column): """ Render a ChoiceField value inside a indicating a particular CSS class. This is useful for displaying colored choices. The CSS class is derived by calling .get_FOO_class() on the row record. """ def render(self, record, bound_column, value): if value: name = bound_column.name css_class = getattr(record, f'get_{name}_class')() label = getattr(record, f'get_{name}_display')() return mark_safe( f'{label}' ) return self.default def value(self, value): return value class ContentTypeColumn(tables.Column): """ Display a ContentType instance. """ def render(self, value): if value is None: return None return content_type_name(value) def value(self, value): if value is None: return None return content_type_identifier(value) class ContentTypesColumn(tables.ManyToManyColumn): """ Display a list of ContentType instances. """ def __init__(self, separator=None, *args, **kwargs): # Use a line break as the default separator if separator is None: separator = mark_safe('