mirror of
https://github.com/netbox-community/netbox.git
synced 2024-05-10 07:54:54 +00:00
Merge branch 'feature' of https://github.com/netbox-community/netbox into feature
# Conflicts: # netbox/dcim/tables/template_code.py # netbox/netbox/views/__init__.py # netbox/project-static/css/base.css # netbox/templates/base.html # netbox/templates/circuits/circuit.html # netbox/templates/circuits/circuittermination_edit.html # netbox/templates/circuits/inc/circuit_termination.html # netbox/templates/circuits/provider.html # netbox/templates/dcim/device.html # netbox/templates/dcim/device/base.html # netbox/templates/dcim/device_component.html # netbox/templates/dcim/devicetype.html # netbox/templates/dcim/inc/device_napalm_tabs.html # netbox/templates/dcim/rack.html # netbox/templates/dcim/site.html # netbox/templates/dcim/virtualchassis.html # netbox/templates/extras/configcontext.html # netbox/templates/extras/object_journal.html # netbox/templates/extras/tag.html # netbox/templates/generic/object.html # netbox/templates/generic/object_list.html # netbox/templates/home.html # netbox/templates/inc/nav_menu.html # netbox/templates/ipam/aggregate.html # netbox/templates/ipam/ipaddress.html # netbox/templates/ipam/prefix.html # netbox/templates/ipam/vlan.html # netbox/templates/ipam/vlangroup_edit.html # netbox/templates/ipam/vlangroup_vlans.html # netbox/templates/secrets/secret.html # netbox/templates/tenancy/tenant.html # netbox/templates/users/api_tokens.html # netbox/templates/virtualization/cluster.html # netbox/templates/virtualization/vminterface_edit.html # netbox/utilities/forms/fields.py # netbox/utilities/templates/buttons/export.html
This commit is contained in:
@@ -1,11 +1,31 @@
|
||||
import django_tables2 as tables
|
||||
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.utils.html import strip_tags
|
||||
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 extras.models import CustomField
|
||||
from .paginator import EnhancedPaginator, get_paginate_count
|
||||
|
||||
|
||||
def stripped_value(self, **kwargs):
|
||||
"""
|
||||
Replaces TemplateColumn's value() method to both strip HTML tags and remove any leading/trailing whitespace.
|
||||
"""
|
||||
html = super(tables.TemplateColumn, self).value(**kwargs)
|
||||
return strip_tags(html).strip() if isinstance(html, str) else html
|
||||
|
||||
|
||||
# TODO: We're monkey-patching TemplateColumn here to strip leading/trailing whitespace. This will no longer
|
||||
# be necessary under django-tables2 v2.3.5+. (See #5926)
|
||||
tables.TemplateColumn.value = stripped_value
|
||||
|
||||
|
||||
class BaseTable(tables.Table):
|
||||
@@ -14,17 +34,23 @@ class BaseTable(tables.Table):
|
||||
|
||||
:param user: Personalize table display for the given user (optional). Has no effect if AnonymousUser is passed.
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
attrs = {
|
||||
'class': 'table table-hover table-headings',
|
||||
}
|
||||
|
||||
def __init__(self, *args, user=None, **kwargs):
|
||||
# Add custom field columns
|
||||
obj_type = ContentType.objects.get_for_model(self._meta.model)
|
||||
for cf in CustomField.objects.filter(content_types=obj_type):
|
||||
self.base_columns[f'cf_{cf.name}'] = CustomFieldColumn(cf)
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# Set default empty_text if none was provided
|
||||
if self.empty_text is None:
|
||||
self.empty_text = 'No {} found'.format(self._meta.model._meta.verbose_name_plural)
|
||||
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())
|
||||
@@ -57,6 +83,7 @@ class BaseTable(tables.Table):
|
||||
|
||||
# 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:
|
||||
@@ -80,19 +107,20 @@ class BaseTable(tables.Table):
|
||||
prefetch_fields.append('__'.join(prefetch_path))
|
||||
self.data.data = self.data.data.prefetch_related(None).prefetch_related(*prefetch_fields)
|
||||
|
||||
@property
|
||||
def configurable_columns(self):
|
||||
selected_columns = [
|
||||
(name, self.columns[name].verbose_name) for name in self.sequence if name not in ['pk', 'actions']
|
||||
]
|
||||
available_columns = [
|
||||
(name, column.verbose_name) for name, column in self.columns.items() if name not in self.sequence and name not in ['pk', 'actions']
|
||||
]
|
||||
return selected_columns + available_columns
|
||||
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 visible_columns(self):
|
||||
return [name for name in self.sequence if self.columns[name].visible]
|
||||
def available_columns(self):
|
||||
return self._get_columns(visible=False)
|
||||
|
||||
@property
|
||||
def selected_columns(self):
|
||||
return self._get_columns(visible=True)
|
||||
|
||||
|
||||
#
|
||||
@@ -212,6 +240,17 @@ class ChoiceFieldColumn(tables.Column):
|
||||
return value
|
||||
|
||||
|
||||
class ContentTypeColumn(tables.Column):
|
||||
"""
|
||||
Display a ContentType instance.
|
||||
"""
|
||||
def render(self, value):
|
||||
return value.name[0].upper() + value.name[1:]
|
||||
|
||||
def value(self, value):
|
||||
return f"{value.app_label}.{value.model}"
|
||||
|
||||
|
||||
class ColorColumn(tables.Column):
|
||||
"""
|
||||
Display a color (#RRGGBB).
|
||||
@@ -295,6 +334,24 @@ class TagColumn(tables.TemplateColumn):
|
||||
return ",".join([tag.name for tag in value.all()])
|
||||
|
||||
|
||||
class CustomFieldColumn(tables.Column):
|
||||
"""
|
||||
Display custom fields in the appropriate format.
|
||||
"""
|
||||
def __init__(self, customfield, *args, **kwargs):
|
||||
self.customfield = customfield
|
||||
kwargs['accessor'] = Accessor(f'custom_field_data__{customfield.name}')
|
||||
if 'verbose_name' not in kwargs:
|
||||
kwargs['verbose_name'] = customfield.label or customfield.name
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def render(self, value):
|
||||
if isinstance(value, list):
|
||||
return ', '.join(v for v in value)
|
||||
return value or self.default
|
||||
|
||||
|
||||
class MPTTColumn(tables.TemplateColumn):
|
||||
"""
|
||||
Display a nested hierarchy for MPTT-enabled models.
|
||||
@@ -326,3 +383,18 @@ class UtilizationColumn(tables.TemplateColumn):
|
||||
|
||||
def value(self, value):
|
||||
return f'{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)
|
||||
|
Reference in New Issue
Block a user