Add dedicated views for nested group models

This commit is contained in:
Jeremy Stretch 2021-03-26 15:07:29 -04:00
parent b7e44a744d
commit 2820d26a0f
13 changed files with 382 additions and 11 deletions

View File

@ -56,7 +56,7 @@ class Region(NestedGroupModel):
csv_headers = ['name', 'slug', 'parent', 'description'] csv_headers = ['name', 'slug', 'parent', 'description']
def get_absolute_url(self): def get_absolute_url(self):
return "{}?region={}".format(reverse('dcim:site_list'), self.slug) return reverse('dcim:region', args=[self.pk])
def to_csv(self): def to_csv(self):
return ( return (
@ -108,7 +108,7 @@ class SiteGroup(NestedGroupModel):
csv_headers = ['name', 'slug', 'parent', 'description'] csv_headers = ['name', 'slug', 'parent', 'description']
def get_absolute_url(self): def get_absolute_url(self):
return "{}?group={}".format(reverse('dcim:site_list'), self.slug) return reverse('dcim:sitegroup', args=[self.pk])
def to_csv(self): def to_csv(self):
return ( return (
@ -324,7 +324,7 @@ class Location(NestedGroupModel):
] ]
def get_absolute_url(self): def get_absolute_url(self):
return "{}?location_id={}".format(reverse('dcim:rack_list'), self.pk) return reverse('dcim:location', args=[self.pk])
def to_csv(self): def to_csv(self):
return ( return (

View File

@ -19,12 +19,14 @@ __all__ = (
# #
# Rack groups # Locations
# #
class LocationTable(BaseTable): class LocationTable(BaseTable):
pk = ToggleColumn() pk = ToggleColumn()
name = MPTTColumn() name = MPTTColumn(
linkify=True
)
site = tables.Column( site = tables.Column(
linkify=True linkify=True
) )

View File

@ -17,7 +17,9 @@ __all__ = (
class RegionTable(BaseTable): class RegionTable(BaseTable):
pk = ToggleColumn() pk = ToggleColumn()
name = MPTTColumn() name = MPTTColumn(
linkify=True
)
site_count = tables.Column( site_count = tables.Column(
verbose_name='Sites' verbose_name='Sites'
) )
@ -35,7 +37,9 @@ class RegionTable(BaseTable):
class SiteGroupTable(BaseTable): class SiteGroupTable(BaseTable):
pk = ToggleColumn() pk = ToggleColumn()
name = MPTTColumn() name = MPTTColumn(
linkify=True
)
site_count = tables.Column( site_count = tables.Column(
verbose_name='Sites' verbose_name='Sites'
) )

View File

@ -14,6 +14,7 @@ urlpatterns = [
path('regions/import/', views.RegionBulkImportView.as_view(), name='region_import'), path('regions/import/', views.RegionBulkImportView.as_view(), name='region_import'),
path('regions/edit/', views.RegionBulkEditView.as_view(), name='region_bulk_edit'), path('regions/edit/', views.RegionBulkEditView.as_view(), name='region_bulk_edit'),
path('regions/delete/', views.RegionBulkDeleteView.as_view(), name='region_bulk_delete'), path('regions/delete/', views.RegionBulkDeleteView.as_view(), name='region_bulk_delete'),
path('regions/<int:pk>/', views.RegionView.as_view(), name='region'),
path('regions/<int:pk>/edit/', views.RegionEditView.as_view(), name='region_edit'), path('regions/<int:pk>/edit/', views.RegionEditView.as_view(), name='region_edit'),
path('regions/<int:pk>/delete/', views.RegionDeleteView.as_view(), name='region_delete'), path('regions/<int:pk>/delete/', views.RegionDeleteView.as_view(), name='region_delete'),
path('regions/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='region_changelog', kwargs={'model': Region}), path('regions/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='region_changelog', kwargs={'model': Region}),
@ -24,6 +25,7 @@ urlpatterns = [
path('site-groups/import/', views.SiteGroupBulkImportView.as_view(), name='sitegroup_import'), path('site-groups/import/', views.SiteGroupBulkImportView.as_view(), name='sitegroup_import'),
path('site-groups/edit/', views.SiteGroupBulkEditView.as_view(), name='sitegroup_bulk_edit'), path('site-groups/edit/', views.SiteGroupBulkEditView.as_view(), name='sitegroup_bulk_edit'),
path('site-groups/delete/', views.SiteGroupBulkDeleteView.as_view(), name='sitegroup_bulk_delete'), path('site-groups/delete/', views.SiteGroupBulkDeleteView.as_view(), name='sitegroup_bulk_delete'),
path('site-groups/<int:pk>/', views.SiteGroupView.as_view(), name='sitegroup'),
path('site-groups/<int:pk>/edit/', views.SiteGroupEditView.as_view(), name='sitegroup_edit'), path('site-groups/<int:pk>/edit/', views.SiteGroupEditView.as_view(), name='sitegroup_edit'),
path('site-groups/<int:pk>/delete/', views.SiteGroupDeleteView.as_view(), name='sitegroup_delete'), path('site-groups/<int:pk>/delete/', views.SiteGroupDeleteView.as_view(), name='sitegroup_delete'),
path('site-groups/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='sitegroup_changelog', kwargs={'model': SiteGroup}), path('site-groups/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='sitegroup_changelog', kwargs={'model': SiteGroup}),
@ -47,6 +49,7 @@ urlpatterns = [
path('locations/import/', views.LocationBulkImportView.as_view(), name='location_import'), path('locations/import/', views.LocationBulkImportView.as_view(), name='location_import'),
path('locations/edit/', views.LocationBulkEditView.as_view(), name='location_bulk_edit'), path('locations/edit/', views.LocationBulkEditView.as_view(), name='location_bulk_edit'),
path('locations/delete/', views.LocationBulkDeleteView.as_view(), name='location_bulk_delete'), path('locations/delete/', views.LocationBulkDeleteView.as_view(), name='location_bulk_delete'),
path('locations/<int:pk>/', views.LocationView.as_view(), name='location'),
path('locations/<int:pk>/edit/', views.LocationEditView.as_view(), name='location_edit'), path('locations/<int:pk>/edit/', views.LocationEditView.as_view(), name='location_edit'),
path('locations/<int:pk>/delete/', views.LocationDeleteView.as_view(), name='location_delete'), path('locations/<int:pk>/delete/', views.LocationDeleteView.as_view(), name='location_delete'),
path('locations/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='location_changelog', kwargs={'model': Location}), path('locations/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='location_changelog', kwargs={'model': Location}),

View File

@ -112,6 +112,23 @@ class RegionListView(generic.ObjectListView):
table = tables.RegionTable table = tables.RegionTable
class RegionView(generic.ObjectView):
queryset = Region.objects.all()
def get_extra_context(self, request, instance):
sites = Site.objects.restrict(request.user, 'view').filter(
region=instance
)
sites_table = tables.SiteTable(sites)
sites_table.columns.hide('region')
paginate_table(sites_table, request)
return {
'sites_table': sites_table,
}
class RegionEditView(generic.ObjectEditView): class RegionEditView(generic.ObjectEditView):
queryset = Region.objects.all() queryset = Region.objects.all()
model_form = forms.RegionForm model_form = forms.RegionForm
@ -169,6 +186,23 @@ class SiteGroupListView(generic.ObjectListView):
table = tables.SiteGroupTable table = tables.SiteGroupTable
class SiteGroupView(generic.ObjectView):
queryset = SiteGroup.objects.all()
def get_extra_context(self, request, instance):
sites = Site.objects.restrict(request.user, 'view').filter(
group=instance
)
sites_table = tables.SiteTable(sites)
sites_table.columns.hide('group')
paginate_table(sites_table, request)
return {
'sites_table': sites_table,
}
class SiteGroupEditView(generic.ObjectEditView): class SiteGroupEditView(generic.ObjectEditView):
queryset = SiteGroup.objects.all() queryset = SiteGroup.objects.all()
model_form = forms.SiteGroupForm model_form = forms.SiteGroupForm
@ -291,6 +325,23 @@ class LocationListView(generic.ObjectListView):
table = tables.LocationTable table = tables.LocationTable
class LocationView(generic.ObjectView):
queryset = Location.objects.all()
def get_extra_context(self, request, instance):
devices = Device.objects.restrict(request.user, 'view').filter(
location=instance
)
devices_table = tables.DeviceTable(devices)
devices_table.columns.hide('location')
paginate_table(devices_table, request)
return {
'devices_table': devices_table,
}
class LocationEditView(generic.ObjectEditView): class LocationEditView(generic.ObjectEditView):
queryset = Location.objects.all() queryset = Location.objects.all()
model_form = forms.LocationForm model_form = forms.LocationForm

View File

@ -0,0 +1,73 @@
{% extends 'generic/object.html' %}
{% load helpers %}
{% load plugins %}
{% block breadcrumbs %}
<li><a href="{% url 'dcim:location_list' %}">Location</a></li>
{% for location in object.get_ancestors %}
<li><a href="{{ location.get_absolute_url }}">{{ location }}</a></li>
{% endfor %}
<li>{{ object }}</li>
{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-6">
<div class="panel panel-default">
<div class="panel-heading">
<strong>Location</strong>
</div>
<table class="table table-hover panel-body attr-table">
<tr>
<td>Name</td>
<td>{{ object.name }}</td>
</tr>
<tr>
<td>Description</td>
<td>{{ object.description|placeholder }}</td>
</tr>
<tr>
<td>Parent</td>
<td>
{% if object.parent %}
<a href="{{ object.parent.get_absolute_url }}">{{ object.parent }}</a>
{% else %}
<span class="text-muted">&mdash;</span>
{% endif %}
</td>
</tr>
<tr>
<td>Devices</td>
<td>
<a href="{% url 'dcim:device_list' %}?location_id={{ object.pk }}">{{ devices_table.rows|length }}</a>
</td>
</tr>
</table>
</div>
{% plugin_left_page object %}
</div>
<div class="col-md-6">
{% include 'inc/custom_fields_panel.html' %}
{% plugin_right_page object %}
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-heading">
<strong>Devices</strong>
</div>
{% include 'inc/table.html' with table=devices_table %}
{% if perms.dcim.add_device %}
<div class="panel-footer text-right noprint">
<a href="{% url 'dcim:device_add' %}?location={{ object.pk }}" class="btn btn-xs btn-primary">
<span class="mdi mdi-plus-thick" aria-hidden="true"></span> Add device
</a>
</div>
{% endif %}
</div>
{% include 'inc/paginator.html' with paginator=devices_table.paginator page=devices_table.page %}
{% plugin_full_width_page object %}
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,73 @@
{% extends 'generic/object.html' %}
{% load helpers %}
{% load plugins %}
{% block breadcrumbs %}
<li><a href="{% url 'dcim:region_list' %}">Region</a></li>
{% for region in object.get_ancestors %}
<li><a href="{{ region.get_absolute_url }}">{{ region }}</a></li>
{% endfor %}
<li>{{ object }}</li>
{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-6">
<div class="panel panel-default">
<div class="panel-heading">
<strong>Region</strong>
</div>
<table class="table table-hover panel-body attr-table">
<tr>
<td>Name</td>
<td>{{ object.name }}</td>
</tr>
<tr>
<td>Description</td>
<td>{{ object.description|placeholder }}</td>
</tr>
<tr>
<td>Parent</td>
<td>
{% if object.parent %}
<a href="{{ object.parent.get_absolute_url }}">{{ object.parent }}</a>
{% else %}
<span class="text-muted">&mdash;</span>
{% endif %}
</td>
</tr>
<tr>
<td>Sites</td>
<td>
<a href="{% url 'dcim:site_list' %}?region_id={{ object.pk }}">{{ sites_table.rows|length }}</a>
</td>
</tr>
</table>
</div>
{% plugin_left_page object %}
</div>
<div class="col-md-6">
{% include 'inc/custom_fields_panel.html' %}
{% plugin_right_page object %}
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-heading">
<strong>Sites</strong>
</div>
{% include 'inc/table.html' with table=sites_table %}
{% if perms.dcim.add_site %}
<div class="panel-footer text-right noprint">
<a href="{% url 'dcim:site_add' %}?region={{ object.pk }}" class="btn btn-xs btn-primary">
<span class="mdi mdi-plus-thick" aria-hidden="true"></span> Add site
</a>
</div>
{% endif %}
</div>
{% include 'inc/paginator.html' with paginator=sites_table.paginator page=sites_table.page %}
{% plugin_full_width_page object %}
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,73 @@
{% extends 'generic/object.html' %}
{% load helpers %}
{% load plugins %}
{% block breadcrumbs %}
<li><a href="{% url 'dcim:sitegroup_list' %}">Site Groups</a></li>
{% for sitegroup in object.get_ancestors %}
<li><a href="{{ sitegroup.get_absolute_url }}">{{ sitegroup }}</a></li>
{% endfor %}
<li>{{ object }}</li>
{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-6">
<div class="panel panel-default">
<div class="panel-heading">
<strong>Site Group</strong>
</div>
<table class="table table-hover panel-body attr-table">
<tr>
<td>Name</td>
<td>{{ object.name }}</td>
</tr>
<tr>
<td>Description</td>
<td>{{ object.description|placeholder }}</td>
</tr>
<tr>
<td>Parent</td>
<td>
{% if object.parent %}
<a href="{{ object.parent.get_absolute_url }}">{{ object.parent }}</a>
{% else %}
<span class="text-muted">&mdash;</span>
{% endif %}
</td>
</tr>
<tr>
<td>Sites</td>
<td>
<a href="{% url 'dcim:site_list' %}?group_id={{ object.pk }}">{{ sites_table.rows|length }}</a>
</td>
</tr>
</table>
</div>
{% plugin_left_page object %}
</div>
<div class="col-md-6">
{% include 'inc/custom_fields_panel.html' %}
{% plugin_right_page object %}
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-heading">
<strong>Sites</strong>
</div>
{% include 'inc/table.html' with table=sites_table %}
{% if perms.dcim.add_site %}
<div class="panel-footer text-right noprint">
<a href="{% url 'dcim:site_add' %}?group={{ object.pk }}" class="btn btn-xs btn-primary">
<span class="mdi mdi-plus-thick" aria-hidden="true"></span> Add site
</a>
</div>
{% endif %}
</div>
{% include 'inc/paginator.html' with paginator=sites_table.paginator page=sites_table.page %}
{% plugin_full_width_page object %}
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,73 @@
{% extends 'generic/object.html' %}
{% load helpers %}
{% load plugins %}
{% block breadcrumbs %}
<li><a href="{% url 'tenancy:tenantgroup_list' %}">Tenant Groups</a></li>
{% for tenantgroup in object.get_ancestors %}
<li><a href="{{ tenantgroup.get_absolute_url }}">{{ tenantgroup }}</a></li>
{% endfor %}
<li>{{ object }}</li>
{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-6">
<div class="panel panel-default">
<div class="panel-heading">
<strong>Tenant Group</strong>
</div>
<table class="table table-hover panel-body attr-table">
<tr>
<td>Name</td>
<td>{{ object.name }}</td>
</tr>
<tr>
<td>Description</td>
<td>{{ object.description|placeholder }}</td>
</tr>
<tr>
<td>Parent</td>
<td>
{% if object.parent %}
<a href="{{ object.parent.get_absolute_url }}">{{ object.parent }}</a>
{% else %}
<span class="text-muted">&mdash;</span>
{% endif %}
</td>
</tr>
<tr>
<td>Sites</td>
<td>
<a href="{% url 'tenancy:tenant_list' %}?group_id={{ object.pk }}">{{ tenants_table.rows|length }}</a>
</td>
</tr>
</table>
</div>
{% plugin_left_page object %}
</div>
<div class="col-md-6">
{% include 'inc/custom_fields_panel.html' %}
{% plugin_right_page object %}
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-heading">
<strong>Tenants</strong>
</div>
{% include 'inc/table.html' with table=tenants_table %}
{% if perms.tenancy.add_tenant %}
<div class="panel-footer text-right noprint">
<a href="{% url 'tenancy:tenant_add' %}?group={{ object.pk }}" class="btn btn-xs btn-primary">
<span class="mdi mdi-plus-thick" aria-hidden="true"></span> Add tenant
</a>
</div>
{% endif %}
</div>
{% include 'inc/paginator.html' with paginator=tenants_table.paginator page=tenants_table.page %}
{% plugin_full_width_page object %}
</div>
</div>
{% endblock %}

View File

@ -45,7 +45,7 @@ class TenantGroup(NestedGroupModel):
ordering = ['name'] ordering = ['name']
def get_absolute_url(self): def get_absolute_url(self):
return "{}?group={}".format(reverse('tenancy:tenant_list'), self.slug) return reverse('tenancy:tenantgroup', args=[self.pk])
def to_csv(self): def to_csv(self):
return ( return (

View File

@ -35,7 +35,9 @@ class TenantColumn(tables.TemplateColumn):
class TenantGroupTable(BaseTable): class TenantGroupTable(BaseTable):
pk = ToggleColumn() pk = ToggleColumn()
name = MPTTColumn() name = MPTTColumn(
linkify=True
)
tenant_count = LinkedCountColumn( tenant_count = LinkedCountColumn(
viewname='tenancy:tenant_list', viewname='tenancy:tenant_list',
url_params={'group': 'slug'}, url_params={'group': 'slug'},

View File

@ -13,6 +13,7 @@ urlpatterns = [
path('tenant-groups/import/', views.TenantGroupBulkImportView.as_view(), name='tenantgroup_import'), path('tenant-groups/import/', views.TenantGroupBulkImportView.as_view(), name='tenantgroup_import'),
path('tenant-groups/edit/', views.TenantGroupBulkEditView.as_view(), name='tenantgroup_bulk_edit'), path('tenant-groups/edit/', views.TenantGroupBulkEditView.as_view(), name='tenantgroup_bulk_edit'),
path('tenant-groups/delete/', views.TenantGroupBulkDeleteView.as_view(), name='tenantgroup_bulk_delete'), path('tenant-groups/delete/', views.TenantGroupBulkDeleteView.as_view(), name='tenantgroup_bulk_delete'),
path('tenant-groups/<int:pk>/', views.TenantGroupView.as_view(), name='tenantgroup'),
path('tenant-groups/<int:pk>/edit/', views.TenantGroupEditView.as_view(), name='tenantgroup_edit'), path('tenant-groups/<int:pk>/edit/', views.TenantGroupEditView.as_view(), name='tenantgroup_edit'),
path('tenant-groups/<int:pk>/delete/', views.TenantGroupDeleteView.as_view(), name='tenantgroup_delete'), path('tenant-groups/<int:pk>/delete/', views.TenantGroupDeleteView.as_view(), name='tenantgroup_delete'),
path('tenant-groups/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='tenantgroup_changelog', kwargs={'model': TenantGroup}), path('tenant-groups/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='tenantgroup_changelog', kwargs={'model': TenantGroup}),

View File

@ -1,9 +1,8 @@
from django.shortcuts import get_object_or_404, render
from circuits.models import Circuit from circuits.models import Circuit
from dcim.models import Site, Rack, Device, RackReservation from dcim.models import Site, Rack, Device, RackReservation
from ipam.models import IPAddress, Prefix, VLAN, VRF from ipam.models import IPAddress, Prefix, VLAN, VRF
from netbox.views import generic from netbox.views import generic
from utilities.tables import paginate_table
from virtualization.models import VirtualMachine, Cluster from virtualization.models import VirtualMachine, Cluster
from . import filters, forms, tables from . import filters, forms, tables
from .models import Tenant, TenantGroup from .models import Tenant, TenantGroup
@ -24,6 +23,23 @@ class TenantGroupListView(generic.ObjectListView):
table = tables.TenantGroupTable table = tables.TenantGroupTable
class TenantGroupView(generic.ObjectView):
queryset = TenantGroup.objects.all()
def get_extra_context(self, request, instance):
tenants = Tenant.objects.restrict(request.user, 'view').filter(
group=instance
)
tenants_table = tables.TenantTable(tenants)
tenants_table.columns.hide('group')
paginate_table(tenants_table, request)
return {
'tenants_table': tenants_table,
}
class TenantGroupEditView(generic.ObjectEditView): class TenantGroupEditView(generic.ObjectEditView):
queryset = TenantGroup.objects.all() queryset = TenantGroup.objects.all()
model_form = forms.TenantGroupForm model_form = forms.TenantGroupForm