Add SavedTableConfig

This commit is contained in:
Jeremy Stretch 2025-04-04 13:41:39 -04:00
parent fbd6d8c7fc
commit 0033c08c25
19 changed files with 523 additions and 25 deletions

View File

@ -12,4 +12,5 @@ from .serializers_.configcontexts import *
from .serializers_.configtemplates import * from .serializers_.configtemplates import *
from .serializers_.savedfilters import * from .serializers_.savedfilters import *
from .serializers_.scripts import * from .serializers_.scripts import *
from .serializers_.tableconfigs import *
from .serializers_.tags import * from .serializers_.tags import *

View File

@ -0,0 +1,22 @@
from core.models import ObjectType
from extras.models import TableConfig
from netbox.api.fields import ContentTypeField
from netbox.api.serializers import ValidatedModelSerializer
__all__ = (
'TableConfigSerializer',
)
class TableConfigSerializer(ValidatedModelSerializer):
object_type = ContentTypeField(
queryset=ObjectType.objects.all()
)
class Meta:
model = TableConfig
fields = [
'id', 'url', 'display_url', 'display', 'object_type', 'table', 'name', 'slug', 'description', 'user',
'weight', 'enabled', 'shared', 'columns', 'ordering', 'created', 'last_updated',
]
brief_fields = ('id', 'url', 'display', 'name', 'slug', 'description')

View File

@ -14,6 +14,7 @@ router.register('custom-field-choice-sets', views.CustomFieldChoiceSetViewSet)
router.register('custom-links', views.CustomLinkViewSet) router.register('custom-links', views.CustomLinkViewSet)
router.register('export-templates', views.ExportTemplateViewSet) router.register('export-templates', views.ExportTemplateViewSet)
router.register('saved-filters', views.SavedFilterViewSet) router.register('saved-filters', views.SavedFilterViewSet)
router.register('table-configs', views.TableConfigViewSet)
router.register('bookmarks', views.BookmarkViewSet) router.register('bookmarks', views.BookmarkViewSet)
router.register('notifications', views.NotificationViewSet) router.register('notifications', views.NotificationViewSet)
router.register('notification-groups', views.NotificationGroupViewSet) router.register('notification-groups', views.NotificationGroupViewSet)

View File

@ -131,6 +131,17 @@ class SavedFilterViewSet(NetBoxModelViewSet):
filterset_class = filtersets.SavedFilterFilterSet filterset_class = filtersets.SavedFilterFilterSet
#
# Table Configs
#
class TableConfigViewSet(NetBoxModelViewSet):
metadata_class = ContentTypeMetadata
queryset = TableConfig.objects.all()
serializer_class = serializers.TableConfigSerializer
filterset_class = filtersets.TableConfigFilterSet
# #
# Bookmarks # Bookmarks
# #

View File

@ -32,6 +32,7 @@ __all__ = (
'ObjectTypeFilterSet', 'ObjectTypeFilterSet',
'SavedFilterFilterSet', 'SavedFilterFilterSet',
'ScriptFilterSet', 'ScriptFilterSet',
'TableConfigFilterSet',
'TagFilterSet', 'TagFilterSet',
'TaggedItemFilterSet', 'TaggedItemFilterSet',
'WebhookFilterSet', 'WebhookFilterSet',
@ -326,6 +327,58 @@ class SavedFilterFilterSet(ChangeLoggedModelFilterSet):
return queryset.filter(Q(enabled=False) | Q(Q(shared=False) & ~Q(user=user))) return queryset.filter(Q(enabled=False) | Q(Q(shared=False) & ~Q(user=user)))
class TableConfigFilterSet(ChangeLoggedModelFilterSet):
q = django_filters.CharFilter(
method='search',
label=_('Search'),
)
object_type_id = django_filters.ModelMultipleChoiceFilter(
queryset=ObjectType.objects.all(),
field_name='object_type'
)
object_type = ContentTypeFilter(
field_name='object_type'
)
user_id = django_filters.ModelMultipleChoiceFilter(
queryset=User.objects.all(),
label=_('User (ID)'),
)
user = django_filters.ModelMultipleChoiceFilter(
field_name='user__username',
queryset=User.objects.all(),
to_field_name='username',
label=_('User (name)'),
)
usable = django_filters.BooleanFilter(
method='_usable'
)
class Meta:
model = TableConfig
fields = ('id', 'name', 'slug', 'description', 'enabled', 'shared', 'weight')
def search(self, queryset, name, value):
if not value.strip():
return queryset
return queryset.filter(
Q(name__icontains=value) |
Q(description__icontains=value)
)
def _usable(self, queryset, name, value):
"""
Return only TableConfigs that are both enabled and are shared (or belong to the current user).
"""
user = self.request.user if self.request else None
if not user or user.is_anonymous:
if value:
return queryset.filter(enabled=True, shared=True)
return queryset.filter(Q(enabled=False) | Q(shared=False))
if value:
return queryset.filter(enabled=True).filter(Q(shared=True) | Q(user=user))
return queryset.filter(Q(enabled=False) | Q(Q(shared=False) & ~Q(user=user)))
class BookmarkFilterSet(BaseFilterSet): class BookmarkFilterSet(BaseFilterSet):
created = django_filters.DateTimeFilter() created = django_filters.DateTimeFilter()
object_type_id = MultiValueNumberFilter() object_type_id = MultiValueNumberFilter()

View File

@ -21,6 +21,7 @@ __all__ = (
'JournalEntryBulkEditForm', 'JournalEntryBulkEditForm',
'NotificationGroupBulkEditForm', 'NotificationGroupBulkEditForm',
'SavedFilterBulkEditForm', 'SavedFilterBulkEditForm',
'TableConfigBulkEditForm',
'TagBulkEditForm', 'TagBulkEditForm',
'WebhookBulkEditForm', 'WebhookBulkEditForm',
) )
@ -201,6 +202,34 @@ class SavedFilterBulkEditForm(BulkEditForm):
nullable_fields = ('description',) nullable_fields = ('description',)
class TableConfigBulkEditForm(BulkEditForm):
pk = forms.ModelMultipleChoiceField(
queryset=TableConfig.objects.all(),
widget=forms.MultipleHiddenInput
)
description = forms.CharField(
label=_('Description'),
max_length=200,
required=False
)
weight = forms.IntegerField(
label=_('Weight'),
required=False
)
enabled = forms.NullBooleanField(
label=_('Enabled'),
required=False,
widget=BulkEditNullBooleanSelect()
)
shared = forms.NullBooleanField(
label=_('Shared'),
required=False,
widget=BulkEditNullBooleanSelect()
)
nullable_fields = ('description',)
class WebhookBulkEditForm(NetBoxModelBulkEditForm): class WebhookBulkEditForm(NetBoxModelBulkEditForm):
model = Webhook model = Webhook

View File

@ -31,6 +31,7 @@ __all__ = (
'LocalConfigContextFilterForm', 'LocalConfigContextFilterForm',
'NotificationGroupFilterForm', 'NotificationGroupFilterForm',
'SavedFilterFilterForm', 'SavedFilterFilterForm',
'TableConfigFilterForm',
'TagFilterForm', 'TagFilterForm',
'WebhookFilterForm', 'WebhookFilterForm',
) )
@ -249,6 +250,36 @@ class SavedFilterFilterForm(SavedFiltersMixin, FilterForm):
) )
class TableConfigFilterForm(SavedFiltersMixin, FilterForm):
fieldsets = (
FieldSet('q', 'filter_id'),
FieldSet('object_type', 'enabled', 'shared', 'weight', name=_('Attributes')),
)
object_type = ContentTypeMultipleChoiceField(
label=_('Object types'),
queryset=ObjectType.objects.public(),
required=False
)
enabled = forms.NullBooleanField(
label=_('Enabled'),
required=False,
widget=forms.Select(
choices=BOOLEAN_WITH_BLANK_CHOICES
)
)
shared = forms.NullBooleanField(
label=_('Shared'),
required=False,
widget=forms.Select(
choices=BOOLEAN_WITH_BLANK_CHOICES
)
)
weight = forms.IntegerField(
label=_('Weight'),
required=False
)
class WebhookFilterForm(NetBoxModelFilterSetForm): class WebhookFilterForm(NetBoxModelFilterSetForm):
model = Webhook model = Webhook
fieldsets = ( fieldsets = (

View File

@ -37,6 +37,7 @@ __all__ = (
'NotificationGroupForm', 'NotificationGroupForm',
'SavedFilterForm', 'SavedFilterForm',
'SubscriptionForm', 'SubscriptionForm',
'TableConfigForm',
'TagForm', 'TagForm',
'WebhookForm', 'WebhookForm',
) )
@ -301,6 +302,22 @@ class SavedFilterForm(forms.ModelForm):
super().__init__(*args, initial=initial, **kwargs) super().__init__(*args, initial=initial, **kwargs)
class TableConfigForm(forms.ModelForm):
slug = SlugField()
object_type = ContentTypeChoiceField(
label=_('Object type'),
queryset=ObjectType.objects.all()
)
fieldsets = (
FieldSet('name', 'slug', 'object_type', 'description', 'weight', 'enabled', 'shared', name=_('Table Config')),
)
class Meta:
model = TableConfig
exclude = ('user',)
class BookmarkForm(forms.ModelForm): class BookmarkForm(forms.ModelForm):
object_type = ContentTypeChoiceField( object_type = ContentTypeChoiceField(
label=_('Object type'), label=_('Object type'),

View File

@ -34,6 +34,7 @@ __all__ = (
'JournalEntryFilter', 'JournalEntryFilter',
'NotificationGroupFilter', 'NotificationGroupFilter',
'SavedFilterFilter', 'SavedFilterFilter',
'TableConfigFilter',
'TagFilter', 'TagFilter',
'WebhookFilter', 'WebhookFilter',
) )
@ -262,6 +263,20 @@ class SavedFilterFilter(BaseObjectTypeFilterMixin, ChangeLogFilterMixin):
) )
@strawberry_django.filter(models.TableConfig, lookups=True)
class TableConfigFilter(BaseObjectTypeFilterMixin, ChangeLogFilterMixin):
name: FilterLookup[str] | None = strawberry_django.filter_field()
slug: FilterLookup[str] | None = strawberry_django.filter_field()
description: FilterLookup[str] | None = strawberry_django.filter_field()
user: Annotated['UserFilter', strawberry.lazy('users.graphql.filters')] | None = strawberry_django.filter_field()
user_id: ID | None = strawberry_django.filter_field()
weight: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
strawberry_django.filter_field()
)
enabled: FilterLookup[bool] | None = strawberry_django.filter_field()
shared: FilterLookup[bool] | None = strawberry_django.filter_field()
@strawberry_django.filter(models.Tag, lookups=True) @strawberry_django.filter(models.Tag, lookups=True)
class TagFilter(BaseObjectTypeFilterMixin, ChangeLogFilterMixin, TagBaseFilterMixin): class TagFilter(BaseObjectTypeFilterMixin, ChangeLogFilterMixin, TagBaseFilterMixin):
color: Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')] | None = strawberry_django.filter_field() color: Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')] | None = strawberry_django.filter_field()

View File

@ -32,6 +32,9 @@ class ExtrasQuery:
saved_filter: SavedFilterType = strawberry_django.field() saved_filter: SavedFilterType = strawberry_django.field()
saved_filter_list: List[SavedFilterType] = strawberry_django.field() saved_filter_list: List[SavedFilterType] = strawberry_django.field()
table_config: TableConfigType = strawberry_django.field()
table_config_list: List[TableConfigType] = strawberry_django.field()
journal_entry: JournalEntryType = strawberry_django.field() journal_entry: JournalEntryType = strawberry_django.field()
journal_entry_list: List[JournalEntryType] = strawberry_django.field() journal_entry_list: List[JournalEntryType] = strawberry_django.field()

View File

@ -38,6 +38,7 @@ __all__ = (
'NotificationType', 'NotificationType',
'SavedFilterType', 'SavedFilterType',
'SubscriptionType', 'SubscriptionType',
'TableConfigType',
'TagType', 'TagType',
'WebhookType', 'WebhookType',
) )
@ -186,6 +187,15 @@ class SubscriptionType(ObjectType):
user: Annotated["UserType", strawberry.lazy('users.graphql.types')] | None user: Annotated["UserType", strawberry.lazy('users.graphql.types')] | None
@strawberry_django.type(
models.TableConfig,
filters=TableConfigFilter,
pagination=True
)
class TableConfigType(ObjectType):
user: Annotated["UserType", strawberry.lazy('users.graphql.types')] | None
@strawberry_django.type( @strawberry_django.type(
models.Tag, models.Tag,
exclude=['extras_taggeditem_items', ], exclude=['extras_taggeditem_items', ],

View File

@ -0,0 +1,69 @@
# Generated by Django 5.2b1 on 2025-04-04 17:35
import django.contrib.postgres.fields
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0014_remove_redundant_indexes'),
('extras', '0125_exporttemplate_file_name'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='TableConfig',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)),
('created', models.DateTimeField(auto_now_add=True, null=True)),
('last_updated', models.DateTimeField(auto_now=True, null=True)),
('table', models.CharField(max_length=100)),
('name', models.CharField(max_length=100)),
('slug', models.SlugField(max_length=100)),
('description', models.CharField(blank=True, max_length=200)),
('weight', models.PositiveSmallIntegerField(default=100)),
('enabled', models.BooleanField(default=True)),
('shared', models.BooleanField(default=True)),
(
'columns',
django.contrib.postgres.fields.ArrayField(
base_field=django.contrib.postgres.fields.ArrayField(
base_field=models.CharField(max_length=100), size=None
),
size=None,
),
),
(
'ordering',
django.contrib.postgres.fields.ArrayField(
base_field=django.contrib.postgres.fields.ArrayField(
base_field=models.CharField(max_length=100), size=None
),
blank=True,
null=True,
size=None,
),
),
(
'object_type',
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, related_name='table_configs', to='core.objecttype'
),
),
(
'user',
models.ForeignKey(
blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL
),
),
],
options={
'verbose_name': 'table config',
'verbose_name_plural': 'table configs',
'ordering': ('weight', 'name'),
},
),
]

View File

@ -36,6 +36,7 @@ __all__ = (
'ImageAttachment', 'ImageAttachment',
'JournalEntry', 'JournalEntry',
'SavedFilter', 'SavedFilter',
'TableConfig',
'Webhook', 'Webhook',
) )
@ -524,6 +525,76 @@ class SavedFilter(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel):
return qd.urlencode() return qd.urlencode()
class TableConfig(ChangeLoggedModel):
"""
A saved configuration of columns and ordering which applies to a specific table.
"""
object_type = models.ForeignKey(
to='core.ObjectType',
on_delete=models.CASCADE,
related_name='table_configs',
help_text=_("The table's object type"),
)
table = models.CharField(
verbose_name=_('table'),
max_length=100,
)
name = models.CharField(
verbose_name=_('name'),
max_length=100,
)
slug = models.SlugField(
verbose_name=_('slug'),
max_length=100,
)
description = models.CharField(
verbose_name=_('description'),
max_length=200,
blank=True,
)
user = models.ForeignKey(
to=settings.AUTH_USER_MODEL,
on_delete=models.SET_NULL,
blank=True,
null=True,
)
weight = models.PositiveSmallIntegerField(
verbose_name=_('weight'),
default=100
)
enabled = models.BooleanField(
verbose_name=_('enabled'),
default=True
)
shared = models.BooleanField(
verbose_name=_('shared'),
default=True
)
columns = ArrayField(
ArrayField(base_field=models.CharField(max_length=100)),
)
ordering = ArrayField(
ArrayField(base_field=models.CharField(max_length=100)),
blank=True,
null=True
)
class Meta:
ordering = ('weight', 'name')
verbose_name = _('table config')
verbose_name_plural = _('table configs')
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('extras:tableconfig', args=[self.pk])
@property
def docs_url(self):
return f'{settings.STATIC_URL}docs/models/extras/tableconfig/'
class ImageAttachment(ChangeLoggedModel): class ImageAttachment(ChangeLoggedModel):
""" """
An uploaded image which is associated with an object. An uploaded image which is associated with an object.

View File

@ -27,6 +27,7 @@ __all__ = (
'ReportResultsTable', 'ReportResultsTable',
'ScriptResultsTable', 'ScriptResultsTable',
'SubscriptionTable', 'SubscriptionTable',
'TableConfigTable',
'TaggedItemTable', 'TaggedItemTable',
'TagTable', 'TagTable',
'WebhookTable', 'WebhookTable',
@ -281,6 +282,36 @@ class SavedFilterTable(NetBoxTable):
) )
class TableConfigTable(NetBoxTable):
name = tables.Column(
verbose_name=_('Name'),
linkify=True
)
object_type = columns.ContentTypesColumn(
verbose_name=_('Object Type'),
)
table = tables.Column(
verbose_name=_('Table Name')
)
enabled = columns.BooleanColumn(
verbose_name=_('Enabled'),
)
shared = columns.BooleanColumn(
verbose_name=_('Shared'),
false_mark=None
)
class Meta(NetBoxTable.Meta):
model = TableConfig
fields = (
'pk', 'id', 'name', 'slug', 'object_type', 'table', 'description', 'user', 'weight', 'enabled', 'shared',
'created', 'last_updated',
)
default_columns = (
'pk', 'name', 'object_type', 'table', 'user', 'description', 'enabled', 'shared',
)
class BookmarkTable(NetBoxTable): class BookmarkTable(NetBoxTable):
object_type = columns.ContentTypeColumn( object_type = columns.ContentTypeColumn(
verbose_name=_('Object Types'), verbose_name=_('Object Types'),

View File

@ -19,6 +19,9 @@ urlpatterns = [
path('export-templates/', include(get_model_urls('extras', 'exporttemplate', detail=False))), path('export-templates/', include(get_model_urls('extras', 'exporttemplate', detail=False))),
path('export-templates/<int:pk>/', include(get_model_urls('extras', 'exporttemplate'))), path('export-templates/<int:pk>/', include(get_model_urls('extras', 'exporttemplate'))),
path('table-configs/', include(get_model_urls('extras', 'tableconfig', detail=False))),
path('table-configs/<int:pk>/', include(get_model_urls('extras', 'tableconfig'))),
path('saved-filters/', include(get_model_urls('extras', 'savedfilter', detail=False))), path('saved-filters/', include(get_model_urls('extras', 'savedfilter', detail=False))),
path('saved-filters/<int:pk>/', include(get_model_urls('extras', 'savedfilter'))), path('saved-filters/<int:pk>/', include(get_model_urls('extras', 'savedfilter'))),

View File

@ -2,12 +2,14 @@ import importlib
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
from django.db import models from django.db import models
from django.db.models import Q
from taggit.managers import _TaggableManager from taggit.managers import _TaggableManager
from netbox.context import current_request from netbox.context import current_request
from .validators import CustomValidator from .validators import CustomValidator
__all__ = ( __all__ = (
'SharedObjectViewMixin',
'image_upload', 'image_upload',
'is_report', 'is_report',
'is_script', 'is_script',
@ -16,6 +18,24 @@ __all__ = (
) )
class SharedObjectViewMixin:
def get_queryset(self, request):
"""
Return only shared SavedFilters, or those owned by the current user, unless
this is a superuser.
"""
queryset = super().get_queryset(request)
user = request.user
if user.is_superuser:
return queryset
if user.is_anonymous:
return queryset.filter(shared=True)
return queryset.filter(
Q(shared=True) | Q(user=user)
)
def filename_from_model(model: models.Model) -> str: def filename_from_model(model: models.Model) -> str:
"""Standardises how we generate filenames from model class for exports""" """Standardises how we generate filenames from model class for exports"""
base = model._meta.verbose_name_plural.lower().replace(' ', '_') base = model._meta.verbose_name_plural.lower().replace(' ', '_')

View File

@ -18,6 +18,7 @@ from dcim.models import Device, DeviceRole, Platform
from extras.choices import LogLevelChoices from extras.choices import LogLevelChoices
from extras.dashboard.forms import DashboardWidgetAddForm, DashboardWidgetForm from extras.dashboard.forms import DashboardWidgetAddForm, DashboardWidgetForm
from extras.dashboard.utils import get_widget_class from extras.dashboard.utils import get_widget_class
from extras.utils import SharedObjectViewMixin
from netbox.constants import DEFAULT_ACTION_PERMISSIONS from netbox.constants import DEFAULT_ACTION_PERMISSIONS
from netbox.views import generic from netbox.views import generic
from netbox.views.generic.mixins import TableMixin from netbox.views.generic.mixins import TableMixin
@ -285,39 +286,22 @@ class ExportTemplateBulkSyncDataView(generic.BulkSyncDataView):
# Saved filters # Saved filters
# #
class SavedFilterMixin:
def get_queryset(self, request):
"""
Return only shared SavedFilters, or those owned by the current user, unless
this is a superuser.
"""
queryset = SavedFilter.objects.all()
user = request.user
if user.is_superuser:
return queryset
if user.is_anonymous:
return queryset.filter(shared=True)
return queryset.filter(
Q(shared=True) | Q(user=user)
)
@register_model_view(SavedFilter, 'list', path='', detail=False) @register_model_view(SavedFilter, 'list', path='', detail=False)
class SavedFilterListView(SavedFilterMixin, generic.ObjectListView): class SavedFilterListView(SharedObjectViewMixin, generic.ObjectListView):
queryset = SavedFilter.objects.all()
filterset = filtersets.SavedFilterFilterSet filterset = filtersets.SavedFilterFilterSet
filterset_form = forms.SavedFilterFilterForm filterset_form = forms.SavedFilterFilterForm
table = tables.SavedFilterTable table = tables.SavedFilterTable
@register_model_view(SavedFilter) @register_model_view(SavedFilter)
class SavedFilterView(SavedFilterMixin, generic.ObjectView): class SavedFilterView(SharedObjectViewMixin, generic.ObjectView):
queryset = SavedFilter.objects.all() queryset = SavedFilter.objects.all()
@register_model_view(SavedFilter, 'add', detail=False) @register_model_view(SavedFilter, 'add', detail=False)
@register_model_view(SavedFilter, 'edit') @register_model_view(SavedFilter, 'edit')
class SavedFilterEditView(SavedFilterMixin, generic.ObjectEditView): class SavedFilterEditView(SharedObjectViewMixin, generic.ObjectEditView):
queryset = SavedFilter.objects.all() queryset = SavedFilter.objects.all()
form = forms.SavedFilterForm form = forms.SavedFilterForm
@ -328,18 +312,18 @@ class SavedFilterEditView(SavedFilterMixin, generic.ObjectEditView):
@register_model_view(SavedFilter, 'delete') @register_model_view(SavedFilter, 'delete')
class SavedFilterDeleteView(SavedFilterMixin, generic.ObjectDeleteView): class SavedFilterDeleteView(SharedObjectViewMixin, generic.ObjectDeleteView):
queryset = SavedFilter.objects.all() queryset = SavedFilter.objects.all()
@register_model_view(SavedFilter, 'bulk_import', detail=False) @register_model_view(SavedFilter, 'bulk_import', detail=False)
class SavedFilterBulkImportView(SavedFilterMixin, generic.BulkImportView): class SavedFilterBulkImportView(SharedObjectViewMixin, generic.BulkImportView):
queryset = SavedFilter.objects.all() queryset = SavedFilter.objects.all()
model_form = forms.SavedFilterImportForm model_form = forms.SavedFilterImportForm
@register_model_view(SavedFilter, 'bulk_edit', path='edit', detail=False) @register_model_view(SavedFilter, 'bulk_edit', path='edit', detail=False)
class SavedFilterBulkEditView(SavedFilterMixin, generic.BulkEditView): class SavedFilterBulkEditView(SharedObjectViewMixin, generic.BulkEditView):
queryset = SavedFilter.objects.all() queryset = SavedFilter.objects.all()
filterset = filtersets.SavedFilterFilterSet filterset = filtersets.SavedFilterFilterSet
table = tables.SavedFilterTable table = tables.SavedFilterTable
@ -347,12 +331,61 @@ class SavedFilterBulkEditView(SavedFilterMixin, generic.BulkEditView):
@register_model_view(SavedFilter, 'bulk_delete', path='delete', detail=False) @register_model_view(SavedFilter, 'bulk_delete', path='delete', detail=False)
class SavedFilterBulkDeleteView(SavedFilterMixin, generic.BulkDeleteView): class SavedFilterBulkDeleteView(SharedObjectViewMixin, generic.BulkDeleteView):
queryset = SavedFilter.objects.all() queryset = SavedFilter.objects.all()
filterset = filtersets.SavedFilterFilterSet filterset = filtersets.SavedFilterFilterSet
table = tables.SavedFilterTable table = tables.SavedFilterTable
#
# Table configs
#
@register_model_view(TableConfig, 'list', path='', detail=False)
class TableConfigListView(SharedObjectViewMixin, generic.ObjectListView):
queryset = TableConfig.objects.all()
filterset = filtersets.TableConfigFilterSet
filterset_form = forms.TableConfigFilterForm
table = tables.TableConfigTable
@register_model_view(TableConfig)
class TableConfigView(SharedObjectViewMixin, generic.ObjectView):
queryset = TableConfig.objects.all()
@register_model_view(TableConfig, 'add', detail=False)
@register_model_view(TableConfig, 'edit')
class TableConfigEditView(SharedObjectViewMixin, generic.ObjectEditView):
queryset = TableConfig.objects.all()
form = forms.TableConfigForm
def alter_object(self, obj, request, url_args, url_kwargs):
if not obj.pk:
obj.user = request.user
return obj
@register_model_view(TableConfig, 'delete')
class TableConfigDeleteView(SharedObjectViewMixin, generic.ObjectDeleteView):
queryset = TableConfig.objects.all()
@register_model_view(TableConfig, 'bulk_edit', path='edit', detail=False)
class TableConfigBulkEditView(SharedObjectViewMixin, generic.BulkEditView):
queryset = TableConfig.objects.all()
filterset = filtersets.TableConfigFilterSet
table = tables.TableConfigTable
form = forms.TableConfigBulkEditForm
@register_model_view(TableConfig, 'bulk_delete', path='delete', detail=False)
class TableConfigBulkDeleteView(SharedObjectViewMixin, generic.BulkDeleteView):
queryset = TableConfig.objects.all()
filterset = filtersets.TableConfigFilterSet
table = tables.TableConfigTable
# #
# Bookmarks # Bookmarks
# #

View File

@ -349,6 +349,7 @@ CUSTOMIZATION_MENU = Menu(
get_model_item('extras', 'customlink', _('Custom Links')), get_model_item('extras', 'customlink', _('Custom Links')),
get_model_item('extras', 'exporttemplate', _('Export Templates')), get_model_item('extras', 'exporttemplate', _('Export Templates')),
get_model_item('extras', 'savedfilter', _('Saved Filters')), get_model_item('extras', 'savedfilter', _('Saved Filters')),
get_model_item('extras', 'tableconfig', _('Table Configs'), actions=('add',)),
get_model_item('extras', 'tag', 'Tags'), get_model_item('extras', 'tag', 'Tags'),
get_model_item('extras', 'imageattachment', _('Image Attachments'), actions=()), get_model_item('extras', 'imageattachment', _('Image Attachments'), actions=()),
), ),

View File

@ -0,0 +1,77 @@
{% extends 'generic/object.html' %}
{% load helpers %}
{% load plugins %}
{% load i18n %}
{% block content %}
<div class="row mb-3">
<div class="col col-md-6">
<div class="card">
<h2 class="card-header">{% trans "Table Config" %}</h2>
<table class="table table-hover attr-table">
<tr>
<th scope="row">{% trans "Name" %}</th>
<td>{{ object.name }}</td>
</tr>
<tr>
<th scope="row">{% trans "Description" %}</th>
<td>{{ object.description|placeholder }}</td>
</tr>
<tr>
<th scope="row">{% trans "Object Type" %}</th>
<td>{{ object.object_type }}</td>
</tr>
<tr>
<th scope="row">{% trans "Table" %}</th>
<td>{{ object.table }}</td>
</tr>
<tr>
<th scope="row">{% trans "User" %}</th>
<td>{{ object.user|placeholder }}</td>
</tr>
<tr>
<th scope="row">{% trans "Enabled" %}</th>
<td>{% checkmark object.enabled %}</td>
</tr>
<tr>
<th scope="row">{% trans "Shared" %}</th>
<td>{% checkmark object.shared %}</td>
</tr>
<tr>
<th scope="row">{% trans "Weight" %}</th>
<td>{{ object.weight }}</td>
</tr>
</table>
</div>
{% plugin_left_page object %}
</div>
<div class="col col-md-6">
<div class="card">
<h2 class="card-header">{% trans "Columns" %}</h2>
<div class="card-body">
<ul>
{% for column in object.columns %}
<li>{{ column }}</li>
{% endfor %}
</ul>
</div>
</div>
<div class="card">
<h2 class="card-header">{% trans "Ordering" %}</h2>
<div class="card-body">
<ul>
{% for column in object.ordering %}
<li>{{ column }}</li>
{% endfor %}
</ul>
</div>
</div>
{% plugin_right_page object %}
</div>
</div>
<div class="row">
<div class="col col-md-12">
{% plugin_full_width_page object %}
</div>
</div>
{% endblock %}