|
@@ -2,151 +2,34 @@ from collections import namedtuple
|
|
|
|
|
|
|
|
import django_tables2 as tables
|
|
import django_tables2 as tables
|
|
|
from django.conf import settings
|
|
from django.conf import settings
|
|
|
-from django.contrib.auth.models import AnonymousUser
|
|
|
|
|
-from django.contrib.contenttypes.fields import GenericForeignKey
|
|
|
|
|
-from django.contrib.contenttypes.models import ContentType
|
|
|
|
|
-from django.core.exceptions import FieldDoesNotExist
|
|
|
|
|
-from django.db.models.fields.related import RelatedField
|
|
|
|
|
from django.urls import reverse
|
|
from django.urls import reverse
|
|
|
from django.utils.safestring import mark_safe
|
|
from django.utils.safestring import mark_safe
|
|
|
-from django_tables2 import RequestConfig
|
|
|
|
|
-from django_tables2.data import TableQuerysetData
|
|
|
|
|
from django_tables2.utils import Accessor
|
|
from django_tables2.utils import Accessor
|
|
|
|
|
|
|
|
from extras.choices import CustomFieldTypeChoices
|
|
from extras.choices import CustomFieldTypeChoices
|
|
|
-from extras.models import CustomField, CustomLink
|
|
|
|
|
-from .utils import content_type_identifier, content_type_name
|
|
|
|
|
-from .paginator import EnhancedPaginator, get_paginate_count
|
|
|
|
|
|
|
+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 BaseTable(tables.Table):
|
|
|
|
|
- """
|
|
|
|
|
- Default table for object lists
|
|
|
|
|
-
|
|
|
|
|
- :param user: Personalize table display for the given user (optional). Has no effect if AnonymousUser is passed.
|
|
|
|
|
- """
|
|
|
|
|
- id = tables.Column(
|
|
|
|
|
- linkify=True,
|
|
|
|
|
- verbose_name='ID'
|
|
|
|
|
- )
|
|
|
|
|
-
|
|
|
|
|
- class Meta:
|
|
|
|
|
- attrs = {
|
|
|
|
|
- 'class': 'table table-hover object-list',
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- def __init__(self, *args, user=None, extra_columns=None, **kwargs):
|
|
|
|
|
- if extra_columns is None:
|
|
|
|
|
- extra_columns = []
|
|
|
|
|
-
|
|
|
|
|
- # Add custom field columns
|
|
|
|
|
- obj_type = ContentType.objects.get_for_model(self._meta.model)
|
|
|
|
|
- cf_columns = [
|
|
|
|
|
- (f'cf_{cf.name}', CustomFieldColumn(cf)) for cf in CustomField.objects.filter(content_types=obj_type)
|
|
|
|
|
- ]
|
|
|
|
|
- cl_columns = [
|
|
|
|
|
- (f'cl_{cl.name}', CustomLinkColumn(cl)) for cl in CustomLink.objects.filter(content_type=obj_type)
|
|
|
|
|
- ]
|
|
|
|
|
- extra_columns.extend([*cf_columns, *cl_columns])
|
|
|
|
|
-
|
|
|
|
|
- super().__init__(*args, extra_columns=extra_columns, **kwargs)
|
|
|
|
|
-
|
|
|
|
|
- # Set default empty_text if none was provided
|
|
|
|
|
- if self.empty_text is None:
|
|
|
|
|
- self.empty_text = f"No {self._meta.model._meta.verbose_name_plural} found"
|
|
|
|
|
-
|
|
|
|
|
- # Hide non-default columns
|
|
|
|
|
- default_columns = getattr(self.Meta, 'default_columns', list())
|
|
|
|
|
- if default_columns:
|
|
|
|
|
- for column in self.columns:
|
|
|
|
|
- if column.name not in default_columns:
|
|
|
|
|
- self.columns.hide(column.name)
|
|
|
|
|
-
|
|
|
|
|
- # Apply custom column ordering for user
|
|
|
|
|
- if user is not None and not isinstance(user, AnonymousUser):
|
|
|
|
|
- selected_columns = user.config.get(f"tables.{self.__class__.__name__}.columns")
|
|
|
|
|
- if selected_columns:
|
|
|
|
|
-
|
|
|
|
|
- # Show only persistent or selected columns
|
|
|
|
|
- for name, column in self.columns.items():
|
|
|
|
|
- if name in ['pk', 'actions', *selected_columns]:
|
|
|
|
|
- self.columns.show(name)
|
|
|
|
|
- else:
|
|
|
|
|
- self.columns.hide(name)
|
|
|
|
|
-
|
|
|
|
|
- # Rearrange the sequence to list selected columns first, followed by all remaining columns
|
|
|
|
|
- # TODO: There's probably a more clever way to accomplish this
|
|
|
|
|
- self.sequence = [
|
|
|
|
|
- *[c for c in selected_columns if c in self.columns.names()],
|
|
|
|
|
- *[c for c in self.columns.names() if c not in selected_columns]
|
|
|
|
|
- ]
|
|
|
|
|
-
|
|
|
|
|
- # PK column should always come first
|
|
|
|
|
- if 'pk' in self.sequence:
|
|
|
|
|
- self.sequence.remove('pk')
|
|
|
|
|
- self.sequence.insert(0, 'pk')
|
|
|
|
|
-
|
|
|
|
|
- # Actions column should always come last
|
|
|
|
|
- if 'actions' in self.sequence:
|
|
|
|
|
- self.sequence.remove('actions')
|
|
|
|
|
- self.sequence.append('actions')
|
|
|
|
|
-
|
|
|
|
|
- # Dynamically update the table's QuerySet to ensure related fields are pre-fetched
|
|
|
|
|
- if isinstance(self.data, TableQuerysetData):
|
|
|
|
|
-
|
|
|
|
|
- prefetch_fields = []
|
|
|
|
|
- for column in self.columns:
|
|
|
|
|
- if column.visible:
|
|
|
|
|
- model = getattr(self.Meta, 'model')
|
|
|
|
|
- accessor = column.accessor
|
|
|
|
|
- prefetch_path = []
|
|
|
|
|
- for field_name in accessor.split(accessor.SEPARATOR):
|
|
|
|
|
- try:
|
|
|
|
|
- field = model._meta.get_field(field_name)
|
|
|
|
|
- except FieldDoesNotExist:
|
|
|
|
|
- break
|
|
|
|
|
- if isinstance(field, RelatedField):
|
|
|
|
|
- # Follow ForeignKeys to the related model
|
|
|
|
|
- prefetch_path.append(field_name)
|
|
|
|
|
- model = field.remote_field.model
|
|
|
|
|
- elif isinstance(field, GenericForeignKey):
|
|
|
|
|
- # Can't prefetch beyond a GenericForeignKey
|
|
|
|
|
- prefetch_path.append(field_name)
|
|
|
|
|
- break
|
|
|
|
|
- if prefetch_path:
|
|
|
|
|
- prefetch_fields.append('__'.join(prefetch_path))
|
|
|
|
|
- self.data.data = self.data.data.prefetch_related(None).prefetch_related(*prefetch_fields)
|
|
|
|
|
-
|
|
|
|
|
- def _get_columns(self, visible=True):
|
|
|
|
|
- columns = []
|
|
|
|
|
- for name, column in self.columns.items():
|
|
|
|
|
- if column.visible == visible and name not in ['pk', 'actions']:
|
|
|
|
|
- columns.append((name, column.verbose_name))
|
|
|
|
|
- return columns
|
|
|
|
|
-
|
|
|
|
|
- @property
|
|
|
|
|
- def available_columns(self):
|
|
|
|
|
- return self._get_columns(visible=False)
|
|
|
|
|
-
|
|
|
|
|
- @property
|
|
|
|
|
- def selected_columns(self):
|
|
|
|
|
- return self._get_columns(visible=True)
|
|
|
|
|
-
|
|
|
|
|
- @property
|
|
|
|
|
- def objects_count(self):
|
|
|
|
|
- """
|
|
|
|
|
- Return the total number of real objects represented by the Table. This is useful when dealing with
|
|
|
|
|
- prefixes/IP addresses/etc., where some table rows may represent available address space.
|
|
|
|
|
- """
|
|
|
|
|
- if not hasattr(self, '_objects_count'):
|
|
|
|
|
- self._objects_count = sum(1 for obj in self.data if hasattr(obj, 'pk'))
|
|
|
|
|
- return self._objects_count
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
-#
|
|
|
|
|
-# Table columns
|
|
|
|
|
-#
|
|
|
|
|
-
|
|
|
|
|
class ToggleColumn(tables.CheckBoxColumn):
|
|
class ToggleColumn(tables.CheckBoxColumn):
|
|
|
"""
|
|
"""
|
|
|
Extend CheckBoxColumn to add a "toggle all" checkbox in the column header.
|
|
Extend CheckBoxColumn to add a "toggle all" checkbox in the column header.
|
|
@@ -557,34 +440,3 @@ class MarkdownColumn(tables.TemplateColumn):
|
|
|
|
|
|
|
|
def value(self, value):
|
|
def value(self, value):
|
|
|
return value
|
|
return value
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
-#
|
|
|
|
|
-# Pagination
|
|
|
|
|
-#
|
|
|
|
|
-
|
|
|
|
|
-def paginate_table(table, request):
|
|
|
|
|
- """
|
|
|
|
|
- Paginate a table given a request context.
|
|
|
|
|
- """
|
|
|
|
|
- paginate = {
|
|
|
|
|
- 'paginator_class': EnhancedPaginator,
|
|
|
|
|
- 'per_page': get_paginate_count(request)
|
|
|
|
|
- }
|
|
|
|
|
- RequestConfig(request, paginate).configure(table)
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
-#
|
|
|
|
|
-# Callables
|
|
|
|
|
-#
|
|
|
|
|
-
|
|
|
|
|
-def linkify_email(value):
|
|
|
|
|
- if value is None:
|
|
|
|
|
- return None
|
|
|
|
|
- return f"mailto:{value}"
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
-def linkify_phone(value):
|
|
|
|
|
- if value is None:
|
|
|
|
|
- return None
|
|
|
|
|
- return f"tel:{value}"
|
|
|