mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-27 02:48:38 -06:00
Use multi-select widgets for column selection
This commit is contained in:
parent
00ab899604
commit
e6b2a03633
@ -2,6 +2,7 @@ import json
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
|
from django.contrib.postgres.forms import SimpleArrayField
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
@ -21,6 +22,7 @@ from utilities.forms.fields import (
|
|||||||
)
|
)
|
||||||
from utilities.forms.rendering import FieldSet, ObjectAttribute
|
from utilities.forms.rendering import FieldSet, ObjectAttribute
|
||||||
from utilities.forms.widgets import ChoicesWidget, HTMXSelect
|
from utilities.forms.widgets import ChoicesWidget, HTMXSelect
|
||||||
|
from utilities.tables import get_table_for_model
|
||||||
from virtualization.models import Cluster, ClusterGroup, ClusterType
|
from virtualization.models import Cluster, ClusterGroup, ClusterType
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
@ -308,6 +310,22 @@ class TableConfigForm(forms.ModelForm):
|
|||||||
label=_('Object type'),
|
label=_('Object type'),
|
||||||
queryset=ObjectType.objects.all()
|
queryset=ObjectType.objects.all()
|
||||||
)
|
)
|
||||||
|
available_columns = SimpleArrayField(
|
||||||
|
base_field=forms.CharField(),
|
||||||
|
required=False,
|
||||||
|
widget=forms.SelectMultiple(
|
||||||
|
attrs={'size': 10, 'class': 'form-select'}
|
||||||
|
),
|
||||||
|
label=_('Available Columns')
|
||||||
|
)
|
||||||
|
columns = SimpleArrayField(
|
||||||
|
base_field=forms.CharField(),
|
||||||
|
required=False,
|
||||||
|
widget=forms.SelectMultiple(
|
||||||
|
attrs={'size': 10, 'class': 'form-select'}
|
||||||
|
),
|
||||||
|
label=_('Selected Columns')
|
||||||
|
)
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
FieldSet(
|
FieldSet(
|
||||||
@ -321,6 +339,31 @@ class TableConfigForm(forms.ModelForm):
|
|||||||
model = TableConfig
|
model = TableConfig
|
||||||
exclude = ('user',)
|
exclude = ('user',)
|
||||||
|
|
||||||
|
def __init__(self, data=None, *args, **kwargs):
|
||||||
|
super().__init__(data, *args, **kwargs)
|
||||||
|
|
||||||
|
object_type = ObjectType.objects.get(pk=get_field_value(self, 'object_type'))
|
||||||
|
model = object_type.model_class()
|
||||||
|
table_name = get_field_value(self, 'table')
|
||||||
|
table_class = get_table_for_model(model, table_name)
|
||||||
|
table = table_class(model.objects.all())
|
||||||
|
|
||||||
|
if columns := self._get_columns():
|
||||||
|
table._set_columns(columns)
|
||||||
|
|
||||||
|
# Initialize columns field based on table attributes
|
||||||
|
self.fields['available_columns'].widget.choices = table.available_columns
|
||||||
|
self.fields['columns'].widget.choices = table.selected_columns
|
||||||
|
|
||||||
|
def _get_columns(self):
|
||||||
|
if self.is_bound and (columns := self.data.get('columns')):
|
||||||
|
return columns
|
||||||
|
if 'columns' in self.initial:
|
||||||
|
columns = self.get_initial_for_field(self.fields['columns'], 'columns')
|
||||||
|
return columns.split(',') if type(columns) is str else columns
|
||||||
|
if self.instance is not None:
|
||||||
|
return self.instance.columns
|
||||||
|
|
||||||
|
|
||||||
class BookmarkForm(forms.ModelForm):
|
class BookmarkForm(forms.ModelForm):
|
||||||
object_type = ContentTypeChoiceField(
|
object_type = ContentTypeChoiceField(
|
||||||
|
@ -27,6 +27,7 @@ from utilities.html import clean_html
|
|||||||
from utilities.jinja2 import render_jinja2
|
from utilities.jinja2 import render_jinja2
|
||||||
from utilities.querydict import dict_to_querydict
|
from utilities.querydict import dict_to_querydict
|
||||||
from utilities.querysets import RestrictedQuerySet
|
from utilities.querysets import RestrictedQuerySet
|
||||||
|
from utilities.tables import get_table_for_model
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'Bookmark',
|
'Bookmark',
|
||||||
@ -594,6 +595,10 @@ class TableConfig(ChangeLoggedModel):
|
|||||||
def docs_url(self):
|
def docs_url(self):
|
||||||
return f'{settings.STATIC_URL}docs/models/extras/tableconfig/'
|
return f'{settings.STATIC_URL}docs/models/extras/tableconfig/'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def table_class(self):
|
||||||
|
return get_table_for_model(self.object_type.model_class(), name=self.table)
|
||||||
|
|
||||||
|
|
||||||
class ImageAttachment(ChangeLoggedModel):
|
class ImageAttachment(ChangeLoggedModel):
|
||||||
"""
|
"""
|
||||||
|
@ -359,6 +359,7 @@ class TableConfigView(SharedObjectViewMixin, generic.ObjectView):
|
|||||||
class TableConfigEditView(SharedObjectViewMixin, generic.ObjectEditView):
|
class TableConfigEditView(SharedObjectViewMixin, generic.ObjectEditView):
|
||||||
queryset = TableConfig.objects.all()
|
queryset = TableConfig.objects.all()
|
||||||
form = forms.TableConfigForm
|
form = forms.TableConfigForm
|
||||||
|
template_name = 'extras/tableconfig_edit.html'
|
||||||
|
|
||||||
def alter_object(self, obj, request, url_args, url_kwargs):
|
def alter_object(self, obj, request, url_args, url_kwargs):
|
||||||
if not obj.pk:
|
if not obj.pk:
|
||||||
|
@ -349,7 +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', 'tableconfig', _('Table Configs'), actions=()),
|
||||||
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=()),
|
||||||
),
|
),
|
||||||
|
@ -4,74 +4,74 @@
|
|||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<div class="col col-md-6">
|
<div class="col col-md-6">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h2 class="card-header">{% trans "Table Config" %}</h2>
|
<h2 class="card-header">{% trans "Table Config" %}</h2>
|
||||||
<table class="table table-hover attr-table">
|
<table class="table table-hover attr-table">
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">{% trans "Name" %}</th>
|
<th scope="row">{% trans "Name" %}</th>
|
||||||
<td>{{ object.name }}</td>
|
<td>{{ object.name }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">{% trans "Description" %}</th>
|
<th scope="row">{% trans "Description" %}</th>
|
||||||
<td>{{ object.description|placeholder }}</td>
|
<td>{{ object.description|placeholder }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">{% trans "Object Type" %}</th>
|
<th scope="row">{% trans "Object Type" %}</th>
|
||||||
<td>{{ object.object_type }}</td>
|
<td>{{ object.object_type }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">{% trans "Table" %}</th>
|
<th scope="row">{% trans "Table" %}</th>
|
||||||
<td>{{ object.table }}</td>
|
<td>{{ object.table }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">{% trans "User" %}</th>
|
<th scope="row">{% trans "User" %}</th>
|
||||||
<td>{{ object.user|placeholder }}</td>
|
<td>{{ object.user|placeholder }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">{% trans "Enabled" %}</th>
|
<th scope="row">{% trans "Enabled" %}</th>
|
||||||
<td>{% checkmark object.enabled %}</td>
|
<td>{% checkmark object.enabled %}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">{% trans "Shared" %}</th>
|
<th scope="row">{% trans "Shared" %}</th>
|
||||||
<td>{% checkmark object.shared %}</td>
|
<td>{% checkmark object.shared %}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">{% trans "Weight" %}</th>
|
<th scope="row">{% trans "Weight" %}</th>
|
||||||
<td>{{ object.weight }}</td>
|
<td>{{ object.weight }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</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>
|
||||||
|
{% plugin_left_page object %}
|
||||||
</div>
|
</div>
|
||||||
<div class="card">
|
<div class="col col-md-6">
|
||||||
<h2 class="card-header">{% trans "Ordering" %}</h2>
|
<div class="card">
|
||||||
<div class="card-body">
|
<h2 class="card-header">{% trans "Columns" %}</h2>
|
||||||
<ul>
|
<div class="card-body">
|
||||||
{% for column in object.ordering %}
|
<ul>
|
||||||
<li>{{ column }}</li>
|
{% for column in object.columns %}
|
||||||
{% endfor %}
|
<li>{{ column }}</li>
|
||||||
</ul>
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
</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>
|
||||||
{% plugin_right_page object %}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="row">
|
||||||
<div class="row">
|
|
||||||
<div class="col col-md-12">
|
<div class="col col-md-12">
|
||||||
{% plugin_full_width_page object %}
|
{% plugin_full_width_page object %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
52
netbox/templates/extras/tableconfig_edit.html
Normal file
52
netbox/templates/extras/tableconfig_edit.html
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
{% extends 'generic/object_edit.html' %}
|
||||||
|
{% load form_helpers %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block form %}
|
||||||
|
{% render_errors form %}
|
||||||
|
|
||||||
|
<div class="field-group my-5">
|
||||||
|
<div class="row">
|
||||||
|
<h2 class="col-9 offset-3">{% trans "Device" %}</h2>
|
||||||
|
</div>
|
||||||
|
{% render_field form.name %}
|
||||||
|
{% render_field form.slug %}
|
||||||
|
{% render_field form.object_type %}
|
||||||
|
{% render_field form.table %}
|
||||||
|
{% render_field form.description %}
|
||||||
|
{% render_field form.weight %}
|
||||||
|
{% render_field form.enabled %}
|
||||||
|
{% render_field form.shared %}
|
||||||
|
{% render_field form.ordering %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field-group my-5">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-5 text-center">
|
||||||
|
{{ form.available_columns.label }}
|
||||||
|
{{ form.available_columns }}
|
||||||
|
</div>
|
||||||
|
<div class="col-2 d-flex align-items-center">
|
||||||
|
<div>
|
||||||
|
<a tabindex="0" class="btn btn-success btn-sm w-100 my-2" id="add_columns">
|
||||||
|
<i class="mdi mdi-arrow-right-bold"></i> {% trans "Add" %}
|
||||||
|
</a>
|
||||||
|
<a tabindex="0" class="btn btn-danger btn-sm w-100 my-2" id="remove_columns">
|
||||||
|
<i class="mdi mdi-arrow-left-bold"></i> {% trans "Remove" %}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-5 text-center">
|
||||||
|
{{ form.columns.label }}
|
||||||
|
{{ form.columns }}
|
||||||
|
<a tabindex="0" class="btn btn-primary btn-sm mt-2" id="move-option-up" data-target="id_columns">
|
||||||
|
<i class="mdi mdi-arrow-up-bold"></i> {% trans "Move Up" %}
|
||||||
|
</a>
|
||||||
|
<a tabindex="0" class="btn btn-primary btn-sm mt-2" id="move-option-down" data-target="id_columns">
|
||||||
|
<i class="mdi mdi-arrow-down-bold"></i> {% trans "Move Down" %}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
@ -1,13 +1,21 @@
|
|||||||
|
from django.utils.module_loading import import_string
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from netbox.registry import registry
|
from netbox.registry import registry
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
|
'get_table_for_model',
|
||||||
'get_table_ordering',
|
'get_table_ordering',
|
||||||
'linkify_phone',
|
'linkify_phone',
|
||||||
'register_table_column'
|
'register_table_column'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_table_for_model(model, name=None):
|
||||||
|
name = name or f'{model.__name__}Table'
|
||||||
|
return import_string(f'{model._meta.app_label}.tables.{name}')
|
||||||
|
|
||||||
|
|
||||||
def get_table_ordering(request, table):
|
def get_table_ordering(request, table):
|
||||||
"""
|
"""
|
||||||
Given a request, return the prescribed table ordering, if any. This may be necessary to determine prior to rendering
|
Given a request, return the prescribed table ordering, if any. This may be necessary to determine prior to rendering
|
||||||
|
Loading…
Reference in New Issue
Block a user