Improve object list layout (#6907)

* Split object list and filters into tabs

* Use object_list template for connections, rack elevations

* Include custom field filters in grouped filter form

* Annotate number of applied filters on tab

* Rearrange table controls
This commit is contained in:
Jeremy Stretch 2021-08-06 15:35:14 -04:00 committed by GitHub
parent 6ce8dd5ac3
commit 939bcfec4b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 250 additions and 213 deletions

View File

@ -2541,6 +2541,7 @@ class ConsoleConnectionsListView(generic.ObjectListView):
filterset_form = forms.ConsoleConnectionFilterForm filterset_form = forms.ConsoleConnectionFilterForm
table = tables.ConsoleConnectionTable table = tables.ConsoleConnectionTable
template_name = 'dcim/connections_list.html' template_name = 'dcim/connections_list.html'
action_buttons = ('export',)
def extra_context(self): def extra_context(self):
return { return {
@ -2554,6 +2555,7 @@ class PowerConnectionsListView(generic.ObjectListView):
filterset_form = forms.PowerConnectionFilterForm filterset_form = forms.PowerConnectionFilterForm
table = tables.PowerConnectionTable table = tables.PowerConnectionTable
template_name = 'dcim/connections_list.html' template_name = 'dcim/connections_list.html'
action_buttons = ('export',)
def extra_context(self): def extra_context(self):
return { return {
@ -2567,6 +2569,7 @@ class InterfaceConnectionsListView(generic.ObjectListView):
filterset_form = forms.InterfaceConnectionFilterForm filterset_form = forms.InterfaceConnectionFilterForm
table = tables.InterfaceConnectionTable table = tables.InterfaceConnectionTable
template_name = 'dcim/connections_list.html' template_name = 'dcim/connections_list.html'
action_buttons = ('export',)
def extra_context(self): def extra_context(self):
return { return {

View File

@ -519,12 +519,14 @@ class CustomFieldModelFilterForm(forms.Form):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
# Add all applicable CustomFields to the form # Add all applicable CustomFields to the form
self.custom_field_filters = []
custom_fields = CustomField.objects.filter(content_types=self.obj_type).exclude( custom_fields = CustomField.objects.filter(content_types=self.obj_type).exclude(
filter_logic=CustomFieldFilterLogicChoices.FILTER_DISABLED filter_logic=CustomFieldFilterLogicChoices.FILTER_DISABLED
) )
for cf in custom_fields: for cf in custom_fields:
field_name = 'cf_{}'.format(cf.name) field_name = 'cf_{}'.format(cf.name)
self.fields[field_name] = cf.to_form_field(set_initial=True, enforce_required=False) self.fields[field_name] = cf.to_form_field(set_initial=True, enforce_required=False)
self.custom_field_filters.append(field_name)
# #

View File

@ -1,24 +1,34 @@
{% extends 'base/layout.html' %} {% extends 'generic/object_list.html' %}
{% load buttons %} {% load buttons %}
{% load helpers %}
{% load render_table from django_tables2 %} {% load render_table from django_tables2 %}
{% block title %}{{ title }}{% endblock %} {% block title %}{{ title }}{% endblock %}
{% block extra_controls %}{% export_button content_type %}{% endblock %} {% block content-wrapper %}
<div class="tab-content">
{% block content %} {# Conncetions list #}
<div class="row mb-3"> <div class="tab-pane show active" id="object-list" role="tabpanel" aria-labelledby="object-list-tab">
<div class="col col-md-7 col-lg-8 col-xl-9 col-xxl-10"> {% include 'inc/table_controls.html' %}
{% include 'inc/table_controls.html' %}
<div class="table-responsive"> <div class="card">
{% render_table table 'inc/table.html' %} <div class="card-body">
</div> <div class="table-responsive">
{% render_table table 'inc/table.html' %}
{% include 'inc/paginator.html' with paginator=table.paginator page=table.page %} </div>
</div> </div>
{% if filter_form %} </div>
{% include 'inc/filter_list.html' %}
{% endif %} {% include 'inc/paginator.html' with paginator=table.paginator page=table.page %}
</div> </div>
{% endblock %}
{# Filter form #}
{% if filter_form %}
<div class="tab-pane show" id="filters-form" role="tabpanel" aria-labelledby="filters-form-tab">
{% include 'inc/filter_list.html' %}
</div>
{% endif %}
</div>
{% endblock content-wrapper %}

View File

@ -1,4 +1,4 @@
{% extends 'base/layout.html' %} {% extends 'generic/object_list.html' %}
{% load helpers %} {% load helpers %}
{% load static %} {% load static %}
@ -22,41 +22,50 @@
</div> </div>
{% endblock %} {% endblock %}
{% block content %} {% block content-wrapper %}
<div class="row"> <div class="tab-content">
<div class="col col-md-7 col-lg-8 col-xl-9 col-xxl-10">
{% if page %} {# Rack elevations #}
<div style="white-space: nowrap; overflow-x: scroll;"> <div class="tab-pane show active" id="object-list" role="tabpanel" aria-labelledby="object-list-tab">
{% for rack in page %} {% if page %}
<div style="display: inline-block; margin-right: 12px; width: 254px"> <div style="white-space: nowrap; overflow-x: scroll;">
<div style="margin-left: 30px"> {% for rack in page %}
<div class="text-center"> <div style="display: inline-block; margin-right: 12px; width: 254px">
<strong><a href="{% url 'dcim:rack' pk=rack.pk %}">{{ rack.name }}</a></strong> <div style="margin-left: 30px">
{% if rack.role %} <div class="text-center">
<br /><span class="badge my-3" style="color: {{ rack.role.color|fgcolor }}; background-color: #{{ rack.role.color }}">{{ rack.role }}</span> <strong><a href="{% url 'dcim:rack' pk=rack.pk %}">{{ rack.name }}</a></strong>
{% endif %} {% if rack.role %}
{% if rack.facility_id %} <br /><span class="badge my-3" style="color: {{ rack.role.color|fgcolor }}; background-color: #{{ rack.role.color }}">{{ rack.role }}</span>
<br /><small class="text-muted">{{ rack.facility_id }}</small> {% endif %}
{% endif %} {% if rack.facility_id %}
</div> <br /><small class="text-muted">{{ rack.facility_id }}</small>
{% include 'dcim/inc/rack_elevation.html' with object=rack face=rack_face %} {% endif %}
<div class="clearfix"></div> </div>
<div class="text-center"> {% include 'dcim/inc/rack_elevation.html' with object=rack face=rack_face %}
<strong><a href="{% url 'dcim:rack' pk=rack.pk %}">{{ rack.name }}</a></strong> <div class="clearfix"></div>
{% if rack.facility_id %} <div class="text-center">
<small class="text-muted">({{ rack.facility_id }})</small> <strong><a href="{% url 'dcim:rack' pk=rack.pk %}">{{ rack.name }}</a></strong>
{% endif %} {% if rack.facility_id %}
</div> <small class="text-muted">({{ rack.facility_id }})</small>
</div> {% endif %}
</div> </div>
{% endfor %} </div>
</div> </div>
<br /> {% endfor %}
{% include 'inc/paginator.html' %} </div>
{% else %} <br />
<p>No Racks Found</p> {% include 'inc/paginator.html' %}
{% endif %} {% else %}
</div> <p>No Racks Found</p>
{% include 'inc/filter_list.html' %} {% endif %}
</div> </div>
{% endblock %}
{# Filter form #}
{% if filter_form %}
<div class="tab-pane show" id="filters-form" role="tabpanel" aria-labelledby="filters-form-tab">
{% include 'inc/filter_list.html' %}
</div>
{% endif %}
</div>
{% endblock content-wrapper %}

View File

@ -4,97 +4,132 @@
{% load render_table from django_tables2 %} {% load render_table from django_tables2 %}
{% load static %} {% load static %}
{% block title %}{{ content_type.model_class|meta:"verbose_name_plural"|bettertitle }}{% endblock %}
{% block controls %} {% block controls %}
<div class="controls"> <div class="controls">
<div class="control-group"> <div class="control-group">
{% block extra_controls %}{% endblock %} {% block extra_controls %}{% endblock %}
{% if permissions.add and 'add' in action_buttons %} {% if permissions.add and 'add' in action_buttons %}
{% add_button content_type.model_class|validated_viewname:"add" %} {% add_button content_type.model_class|validated_viewname:"add" %}
{% endif %} {% endif %}
{% if permissions.add and 'import' in action_buttons %} {% if permissions.add and 'import' in action_buttons %}
{% import_button content_type.model_class|validated_viewname:"import" %} {% import_button content_type.model_class|validated_viewname:"import" %}
{% endif %} {% endif %}
{% if 'export' in action_buttons %} {% if 'export' in action_buttons %}
{% export_button content_type %} {% export_button content_type %}
{% endif %} {% endif %}
</div>
</div> </div>
</div>
{% endblock controls %} {% endblock controls %}
{% block content %} {% block tabs %}
{% if table.paginator.num_pages > 1 %} <ul class="nav nav-tabs px-3">
{% with bulk_edit_url=content_type.model_class|validated_viewname:"bulk_edit" bulk_delete_url=content_type.model_class|validated_viewname:"bulk_delete" %} {% block tab_items %}
<div id="select-all-box" class="d-none card noprint"> <li class="nav-item" role="presentation">
<form method="post" class="form col-md-12"> <button class="nav-link active" id="object-list-tab" data-bs-toggle="tab" data-bs-target="#object-list" type="button" role="tab" aria-controls="edit-form" aria-selected="true">
{% csrf_token %} {% block title %}{{ content_type.model_class|meta:"verbose_name_plural"|bettertitle }}{% endblock %}
<div class="card-body"> {% badge table.page.paginator.count %}
<div class="float-end"> </button>
{% if bulk_edit_url and permissions.change %} </li>
<button type="submit" name="_edit" formaction="{% url bulk_edit_url %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="btn btn-warning btn-sm" disabled> {% if filter_form %}
<span class="mdi mdi-pencil" aria-hidden="true"></span> Edit All <li class="nav-item" role="presentation">
</button> <button class="nav-link" id="filters-form-tab" data-bs-toggle="tab" data-bs-target="#filters-form" type="button" role="tab" aria-controls="object-list" aria-selected="false">
{% endif %} Filters
{% if bulk_delete_url and permissions.delete %} {% if filter_form %}{% badge filter_form.changed_data|length %}{% endif %}
<button type="submit" name="_delete" formaction="{% url bulk_delete_url %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="btn btn-danger btn-sm" disabled> </button>
<span class="mdi mdi-trash-can-outline" aria-hidden="true"></span> Delete All </li>
</button> {% endif %}
{% endif %} {% endblock tab_items %}
</div> </ul>
<div class="form-check"> {% endblock tabs %}
<input type="checkbox" id="select-all" name="_all" class="form-check-input" />
<label for="select-all" class="form-check-label">
Select <strong>all {{ table.rows|length }} {{ table.data.verbose_name_plural }}</strong> matching query
</label>
</div>
</div>
</form>
</div>
{% endwith %}
{% endif %}
{# Object table #} {% block content-wrapper %}
<div class="row"> <div class="tab-content">
<div class="col {% if filter_form %}col-md-7 col-lg-8 col-xl-9 col-xxl-10{% else %}col-12{% endif %}">
{# Object list filter, table config #}
{% include 'inc/table_controls.html' with table_modal="ObjectTable_config" %}
{% with bulk_edit_url=content_type.model_class|validated_viewname:"bulk_edit" bulk_delete_url=content_type.model_class|validated_viewname:"bulk_delete" %} {# Object list #}
{% if permissions.change or permissions.delete %} <div class="tab-pane show active" id="object-list" role="tabpanel" aria-labelledby="object-list-tab">
<form method="post" class="form form-horizontal"> {% if table.paginator.num_pages > 1 %}
{% csrf_token %}
<input type="hidden" name="return_url" value="{% if return_url %}{{ return_url }}{% else %}{{ request.path }}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}{% endif %}" /> {# "Select all" form #}
<div class="table-responsive"> {% with bulk_edit_url=content_type.model_class|validated_viewname:"bulk_edit" bulk_delete_url=content_type.model_class|validated_viewname:"bulk_delete" %}
{% render_table table 'inc/table.html' %} <div id="select-all-box" class="d-none card noprint">
<form method="post" class="form col-md-12">
{% csrf_token %}
<div class="card-body">
<div class="float-end">
{% if bulk_edit_url and permissions.change %}
<button type="submit" name="_edit" formaction="{% url bulk_edit_url %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="btn btn-warning btn-sm" disabled>
<span class="mdi mdi-pencil" aria-hidden="true"></span> Edit All
</button>
{% endif %}
{% if bulk_delete_url and permissions.delete %}
<button type="submit" name="_delete" formaction="{% url bulk_delete_url %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="btn btn-danger btn-sm" disabled>
<span class="mdi mdi-trash-can-outline" aria-hidden="true"></span> Delete All
</button>
{% endif %}
</div>
<div class="form-check">
<input type="checkbox" id="select-all" name="_all" class="form-check-input" />
<label for="select-all" class="form-check-label">
Select <strong>all {{ table.rows|length }} {{ table.data.verbose_name_plural }}</strong> matching query
</label>
</div>
</div>
</form>
</div> </div>
<div class="noprint bulk-buttons"> {% endwith %}
<div class="bulk-button-group"> {% endif %}
{% block bulk_buttons %}{% endblock %}
{% if bulk_edit_url and permissions.change %} {# Object table controls #}
<button type="submit" name="_edit" formaction="{% url bulk_edit_url %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="btn btn-warning btn-sm"> {% include 'inc/table_controls.html' with table_modal="ObjectTable_config" %}
<i class="mdi mdi-pencil" aria-hidden="true"></i> Edit Selected
</button> <form method="post" class="form form-horizontal">
{% endif %} {% csrf_token %}
{% if bulk_delete_url and permissions.delete %} <input type="hidden" name="return_url" value="{% if return_url %}{{ return_url }}{% else %}{{ request.path }}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}{% endif %}" />
<button type="submit" name="_delete" formaction="{% url bulk_delete_url %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="btn btn-danger btn-sm">
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete Selected {# Object table #}
</button> <div class="card">
{% endif %} <div class="card-body">
<div class="table-responsive">
{% render_table table 'inc/table.html' %}
</div> </div>
</div> </div>
</form>
{% else %}
<div class="table-responsive">
{% render_table table 'inc/table.html' %}
</div> </div>
{% endif %}
{% endwith %} {# Form buttons #}
{% include 'inc/paginator.html' with paginator=table.paginator page=table.page %} {% if permissions.change or permissions.delete %}
{% with bulk_edit_url=content_type.model_class|validated_viewname:"bulk_edit" bulk_delete_url=content_type.model_class|validated_viewname:"bulk_delete" %}
<div class="noprint bulk-buttons">
<div class="bulk-button-group">
{% block bulk_buttons %}{% endblock %}
{% if bulk_edit_url and permissions.change %}
<button type="submit" name="_edit" formaction="{% url bulk_edit_url %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="btn btn-warning btn-sm">
<i class="mdi mdi-pencil" aria-hidden="true"></i> Edit Selected
</button>
{% endif %}
{% if bulk_delete_url and permissions.delete %}
<button type="submit" name="_delete" formaction="{% url bulk_delete_url %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="btn btn-danger btn-sm">
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete Selected
</button>
{% endif %}
</div>
</div>
{% endwith %}
{% endif %}
</form>
{# Paginator #}
{% include 'inc/paginator.html' with paginator=table.paginator page=table.page %}
</div>
{# Filter form #}
{% if filter_form %}
<div class="tab-pane show" id="filters-form" role="tabpanel" aria-labelledby="filters-form-tab">
{% include 'inc/filter_list.html' %}
</div>
{% endif %}
</div> </div>
{% if filter_form %}
{% include 'inc/filter_list.html' %} {# Table config form #}
{% endif %} {% table_config_form table table_name="ObjectTable" %}
</div> {% endblock content-wrapper %}
{% table_config_form table table_name="ObjectTable" %}
{% endblock content %}

View File

@ -1,65 +1,45 @@
{% load form_helpers %} {% load form_helpers %}
{% load helpers %} {% load helpers %}
<div class="col col-md-5 col-lg-4 col-xl-3 col-xxl-2 noprint"> <form action="." method="get">
<form action="." method="get"> <div class="card">
<div class="card small"> <div class="card-body overflow-visible d-flex flex-wrap justify-content-between py-3">
<h5 class="card-header"> {% for field in filter_form.hidden_fields %}
Field Filters {{ field }}
</h5> {% endfor %}
<div class="card-body overflow-visible d-flex flex-wrap justify-content-between py-3"> {% if filter_form.field_groups %}
{% for field in filter_form.hidden_fields %} {% for group in filter_form.field_groups %}
{{ field }} <div class="col col-12">
{% endfor %} {% for name in group %}
{% if filter_form.field_groups %} {% with field=filter_form|get_item:name %}
{% for group in filter_form.field_groups %} {% render_field field %}
<div class="col col-12"> {% endwith %}
{% for name in group %} {% endfor %}
{% with field=filter_form|get_item:name %} </div>
{% if field|widget_type == 'checkboxinput' %} <hr class="card-divider mt-0" />
<div class="form-check mb-3"> {% endfor %}
<label class="form-check-label" for="{{ field.id_for_label }}">{{ field.label }}</label> {% for name in filter_form.custom_field_filters %}
{{ field }} <div class="col col-12">
</div> {% with field=filter_form|get_item:name %}
{% else %} {% render_field field %}
<div class="mb-3 px-2"> {% endwith %}
<label class="form-label" for="{{ field.id_for_label }}">{{ field.label }}</label> </div>
{{ field }} {% endfor %}
</div> {% else %}
{% endif %} {% for field in filter_form.visible_fields %}
{% endwith %} <div class="col col-12">
{% endfor %} {% render_field field %}
</div> </div>
{% if forloop.counter != filter_form.field_groups|length %} {% endfor %}
<hr class="card-divider mt-0" /> {% endif %}
{% endif %} </div>
{% endfor %} <div class="card-footer text-end noprint border-0">
{% else %} <button type="button" class="btn btn-sm btn-outline-danger m-1" data-reset-select>
{% for field in filter_form.visible_fields %} <i class="mdi mdi-backspace"></i> Reset
<div class="col col-12"> </button>
{% if field|widget_type == 'checkboxinput' %} <button type="submit" class="btn btn-sm btn-primary m-1">
<div class="form-check mb-3"> <i class="mdi mdi-filter-variant"></i> Filter
<label class="form-check-label" for="{{ field.id_for_label }}">{{ field.label }}</label> </button>
{{ field }} </div>
</div> </div>
{% else %} </form>
<div class="mb-3 px-2">
<label class="form-label" for="{{ field.id_for_label }}">{{ field.label }}</label>
{{ field }}
</div>
{% endif %}
</div>
{% endfor %}
{% endif %}
</div>
<div class="card-footer text-end noprint border-0">
<button type="button" class="btn btn-sm btn-outline-danger m-1" data-reset-select>
<i class="mdi mdi-backspace"></i> Reset
</button>
<button type="submit" class="btn btn-sm btn-primary m-1">
<i class="mdi mdi-filter-variant"></i> Filter
</button>
</div>
</div>
</form>
</div>

View File

@ -1,19 +1,4 @@
<div class="row mb-3 justify-content-between"> <div class="row mb-3 justify-content-between">
<div class="col col-md-2 mb-0 d-flex noprint table-controls">
{% if request.user.is_authenticated and table_modal %}
<div class="input-group input-group-sm">
<button
type="button"
data-bs-toggle="modal"
title="Configure Table"
data-bs-target="#{{ table_modal }}"
class="btn btn-sm btn-outline-dark"
>
<i class="mdi mdi-cog"></i> Configure Table
</button>
</div>
{% endif %}
</div>
<div class="col col-12 col-lg-4 my-3 my-lg-0 d-flex noprint table-controls"> <div class="col col-12 col-lg-4 my-3 my-lg-0 d-flex noprint table-controls">
<div class="input-group input-group-sm"> <div class="input-group input-group-sm">
<input <input
@ -24,4 +9,17 @@
/> />
</div> </div>
</div> </div>
<div class="col col-md-2 mb-0 d-flex justify-content-end noprint table-controls">
{% if request.user.is_authenticated and table_modal %}
<button
type="button"
data-bs-toggle="modal"
title="Configure Table"
data-bs-target="#{{ table_modal }}"
class="btn btn-sm btn-outline-dark"
>
<i class="mdi mdi-cog"></i> Configure Table
</button>
{% endif %}
</div>
</div> </div>