diff --git a/netbox/circuits/views.py b/netbox/circuits/views.py
index 0abe35b8d..2edb5a78e 100644
--- a/netbox/circuits/views.py
+++ b/netbox/circuits/views.py
@@ -4,11 +4,9 @@ from django.db.models import Count
from django.shortcuts import get_object_or_404, redirect, render
from django_tables2 import RequestConfig
+from netbox.views import generic
from utilities.forms import ConfirmationForm
from utilities.paginator import EnhancedPaginator, get_paginate_count
-from utilities.views import (
- BulkDeleteView, BulkEditView, BulkImportView, ObjectView, ObjectDeleteView, ObjectEditView, ObjectListView,
-)
from . import filters, forms, tables
from .choices import CircuitTerminationSideChoices
from .models import Circuit, CircuitTermination, CircuitType, Provider
@@ -18,14 +16,14 @@ from .models import Circuit, CircuitTermination, CircuitType, Provider
# Providers
#
-class ProviderListView(ObjectListView):
+class ProviderListView(generic.ObjectListView):
queryset = Provider.objects.annotate(count_circuits=Count('circuits')).order_by(*Provider._meta.ordering)
filterset = filters.ProviderFilterSet
filterset_form = forms.ProviderFilterForm
table = tables.ProviderTable
-class ProviderView(ObjectView):
+class ProviderView(generic.ObjectView):
queryset = Provider.objects.all()
def get(self, request, slug):
@@ -52,30 +50,30 @@ class ProviderView(ObjectView):
})
-class ProviderEditView(ObjectEditView):
+class ProviderEditView(generic.ObjectEditView):
queryset = Provider.objects.all()
model_form = forms.ProviderForm
template_name = 'circuits/provider_edit.html'
-class ProviderDeleteView(ObjectDeleteView):
+class ProviderDeleteView(generic.ObjectDeleteView):
queryset = Provider.objects.all()
-class ProviderBulkImportView(BulkImportView):
+class ProviderBulkImportView(generic.BulkImportView):
queryset = Provider.objects.all()
model_form = forms.ProviderCSVForm
table = tables.ProviderTable
-class ProviderBulkEditView(BulkEditView):
+class ProviderBulkEditView(generic.BulkEditView):
queryset = Provider.objects.annotate(count_circuits=Count('circuits')).order_by(*Provider._meta.ordering)
filterset = filters.ProviderFilterSet
table = tables.ProviderTable
form = forms.ProviderBulkEditForm
-class ProviderBulkDeleteView(BulkDeleteView):
+class ProviderBulkDeleteView(generic.BulkDeleteView):
queryset = Provider.objects.annotate(count_circuits=Count('circuits')).order_by(*Provider._meta.ordering)
filterset = filters.ProviderFilterSet
table = tables.ProviderTable
@@ -85,27 +83,27 @@ class ProviderBulkDeleteView(BulkDeleteView):
# Circuit Types
#
-class CircuitTypeListView(ObjectListView):
+class CircuitTypeListView(generic.ObjectListView):
queryset = CircuitType.objects.annotate(circuit_count=Count('circuits')).order_by(*CircuitType._meta.ordering)
table = tables.CircuitTypeTable
-class CircuitTypeEditView(ObjectEditView):
+class CircuitTypeEditView(generic.ObjectEditView):
queryset = CircuitType.objects.all()
model_form = forms.CircuitTypeForm
-class CircuitTypeDeleteView(ObjectDeleteView):
+class CircuitTypeDeleteView(generic.ObjectDeleteView):
queryset = CircuitType.objects.all()
-class CircuitTypeBulkImportView(BulkImportView):
+class CircuitTypeBulkImportView(generic.BulkImportView):
queryset = CircuitType.objects.all()
model_form = forms.CircuitTypeCSVForm
table = tables.CircuitTypeTable
-class CircuitTypeBulkDeleteView(BulkDeleteView):
+class CircuitTypeBulkDeleteView(generic.BulkDeleteView):
queryset = CircuitType.objects.annotate(circuit_count=Count('circuits')).order_by(*CircuitType._meta.ordering)
table = tables.CircuitTypeTable
@@ -114,7 +112,7 @@ class CircuitTypeBulkDeleteView(BulkDeleteView):
# Circuits
#
-class CircuitListView(ObjectListView):
+class CircuitListView(generic.ObjectListView):
queryset = Circuit.objects.prefetch_related(
'provider', 'type', 'tenant', 'terminations'
).annotate_sites()
@@ -123,7 +121,7 @@ class CircuitListView(ObjectListView):
table = tables.CircuitTable
-class CircuitView(ObjectView):
+class CircuitView(generic.ObjectView):
queryset = Circuit.objects.all()
def get(self, request, pk):
@@ -152,23 +150,23 @@ class CircuitView(ObjectView):
})
-class CircuitEditView(ObjectEditView):
+class CircuitEditView(generic.ObjectEditView):
queryset = Circuit.objects.all()
model_form = forms.CircuitForm
template_name = 'circuits/circuit_edit.html'
-class CircuitDeleteView(ObjectDeleteView):
+class CircuitDeleteView(generic.ObjectDeleteView):
queryset = Circuit.objects.all()
-class CircuitBulkImportView(BulkImportView):
+class CircuitBulkImportView(generic.BulkImportView):
queryset = Circuit.objects.all()
model_form = forms.CircuitCSVForm
table = tables.CircuitTable
-class CircuitBulkEditView(BulkEditView):
+class CircuitBulkEditView(generic.BulkEditView):
queryset = Circuit.objects.prefetch_related(
'provider', 'type', 'tenant', 'terminations'
)
@@ -177,7 +175,7 @@ class CircuitBulkEditView(BulkEditView):
form = forms.CircuitBulkEditForm
-class CircuitBulkDeleteView(BulkDeleteView):
+class CircuitBulkDeleteView(generic.BulkDeleteView):
queryset = Circuit.objects.prefetch_related(
'provider', 'type', 'tenant', 'terminations'
)
@@ -185,7 +183,7 @@ class CircuitBulkDeleteView(BulkDeleteView):
table = tables.CircuitTable
-class CircuitSwapTerminations(ObjectEditView):
+class CircuitSwapTerminations(generic.ObjectEditView):
"""
Swap the A and Z terminations of a circuit.
"""
@@ -258,7 +256,7 @@ class CircuitSwapTerminations(ObjectEditView):
# Circuit terminations
#
-class CircuitTerminationEditView(ObjectEditView):
+class CircuitTerminationEditView(generic.ObjectEditView):
queryset = CircuitTermination.objects.all()
model_form = forms.CircuitTerminationForm
template_name = 'circuits/circuittermination_edit.html'
@@ -272,5 +270,5 @@ class CircuitTerminationEditView(ObjectEditView):
return obj.circuit.get_absolute_url()
-class CircuitTerminationDeleteView(ObjectDeleteView):
+class CircuitTerminationDeleteView(generic.ObjectDeleteView):
queryset = CircuitTermination.objects.all()
diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py
index 72b691528..7b0c26be7 100644
--- a/netbox/dcim/views.py
+++ b/netbox/dcim/views.py
@@ -7,7 +7,6 @@ from django.db import transaction
from django.db.models import Count, F, Prefetch
from django.forms import ModelMultipleChoiceField, MultipleHiddenInput, modelformset_factory
from django.shortcuts import get_object_or_404, redirect, render
-from django.urls import reverse
from django.utils.html import escape
from django.utils.safestring import mark_safe
from django.views.generic import View
@@ -16,16 +15,13 @@ from circuits.models import Circuit
from extras.views import ObjectConfigContextView
from ipam.models import IPAddress, Prefix, Service, VLAN
from ipam.tables import InterfaceIPAddressTable, InterfaceVLANTable
+from netbox.views import generic
from secrets.models import Secret
from utilities.forms import ConfirmationForm
from utilities.paginator import EnhancedPaginator, get_paginate_count
from utilities.permissions import get_permission_for_model
from utilities.utils import csv_format, get_subquery
-from utilities.views import (
- BulkComponentCreateView, BulkDeleteView, BulkEditView, BulkImportView, BulkRenameView, ComponentCreateView,
- GetReturnURLMixin, ObjectView, ObjectImportView, ObjectDeleteView, ObjectEditView, ObjectListView,
- ObjectPermissionRequiredMixin,
-)
+from utilities.views import GetReturnURLMixin, ObjectPermissionRequiredMixin
from virtualization.models import VirtualMachine
from . import filters, forms, tables
from .choices import DeviceFaceChoices
@@ -102,7 +98,7 @@ class BulkDisconnectView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View)
# Regions
#
-class RegionListView(ObjectListView):
+class RegionListView(generic.ObjectListView):
queryset = Region.objects.add_related_count(
Region.objects.all(),
Site,
@@ -115,22 +111,22 @@ class RegionListView(ObjectListView):
table = tables.RegionTable
-class RegionEditView(ObjectEditView):
+class RegionEditView(generic.ObjectEditView):
queryset = Region.objects.all()
model_form = forms.RegionForm
-class RegionDeleteView(ObjectDeleteView):
+class RegionDeleteView(generic.ObjectDeleteView):
queryset = Region.objects.all()
-class RegionBulkImportView(BulkImportView):
+class RegionBulkImportView(generic.BulkImportView):
queryset = Region.objects.all()
model_form = forms.RegionCSVForm
table = tables.RegionTable
-class RegionBulkDeleteView(BulkDeleteView):
+class RegionBulkDeleteView(generic.BulkDeleteView):
queryset = Region.objects.add_related_count(
Region.objects.all(),
Site,
@@ -146,14 +142,14 @@ class RegionBulkDeleteView(BulkDeleteView):
# Sites
#
-class SiteListView(ObjectListView):
+class SiteListView(generic.ObjectListView):
queryset = Site.objects.all()
filterset = filters.SiteFilterSet
filterset_form = forms.SiteFilterForm
table = tables.SiteTable
-class SiteView(ObjectView):
+class SiteView(generic.ObjectView):
queryset = Site.objects.prefetch_related('region', 'tenant__group')
def get(self, request, slug):
@@ -182,30 +178,30 @@ class SiteView(ObjectView):
})
-class SiteEditView(ObjectEditView):
+class SiteEditView(generic.ObjectEditView):
queryset = Site.objects.all()
model_form = forms.SiteForm
template_name = 'dcim/site_edit.html'
-class SiteDeleteView(ObjectDeleteView):
+class SiteDeleteView(generic.ObjectDeleteView):
queryset = Site.objects.all()
-class SiteBulkImportView(BulkImportView):
+class SiteBulkImportView(generic.BulkImportView):
queryset = Site.objects.all()
model_form = forms.SiteCSVForm
table = tables.SiteTable
-class SiteBulkEditView(BulkEditView):
+class SiteBulkEditView(generic.BulkEditView):
queryset = Site.objects.prefetch_related('region', 'tenant')
filterset = filters.SiteFilterSet
table = tables.SiteTable
form = forms.SiteBulkEditForm
-class SiteBulkDeleteView(BulkDeleteView):
+class SiteBulkDeleteView(generic.BulkDeleteView):
queryset = Site.objects.prefetch_related('region', 'tenant')
filterset = filters.SiteFilterSet
table = tables.SiteTable
@@ -215,7 +211,7 @@ class SiteBulkDeleteView(BulkDeleteView):
# Rack groups
#
-class RackGroupListView(ObjectListView):
+class RackGroupListView(generic.ObjectListView):
queryset = RackGroup.objects.add_related_count(
RackGroup.objects.all(),
Rack,
@@ -228,22 +224,22 @@ class RackGroupListView(ObjectListView):
table = tables.RackGroupTable
-class RackGroupEditView(ObjectEditView):
+class RackGroupEditView(generic.ObjectEditView):
queryset = RackGroup.objects.all()
model_form = forms.RackGroupForm
-class RackGroupDeleteView(ObjectDeleteView):
+class RackGroupDeleteView(generic.ObjectDeleteView):
queryset = RackGroup.objects.all()
-class RackGroupBulkImportView(BulkImportView):
+class RackGroupBulkImportView(generic.BulkImportView):
queryset = RackGroup.objects.all()
model_form = forms.RackGroupCSVForm
table = tables.RackGroupTable
-class RackGroupBulkDeleteView(BulkDeleteView):
+class RackGroupBulkDeleteView(generic.BulkDeleteView):
queryset = RackGroup.objects.add_related_count(
RackGroup.objects.all(),
Rack,
@@ -259,27 +255,27 @@ class RackGroupBulkDeleteView(BulkDeleteView):
# Rack roles
#
-class RackRoleListView(ObjectListView):
+class RackRoleListView(generic.ObjectListView):
queryset = RackRole.objects.annotate(rack_count=Count('racks')).order_by(*RackRole._meta.ordering)
table = tables.RackRoleTable
-class RackRoleEditView(ObjectEditView):
+class RackRoleEditView(generic.ObjectEditView):
queryset = RackRole.objects.all()
model_form = forms.RackRoleForm
-class RackRoleDeleteView(ObjectDeleteView):
+class RackRoleDeleteView(generic.ObjectDeleteView):
queryset = RackRole.objects.all()
-class RackRoleBulkImportView(BulkImportView):
+class RackRoleBulkImportView(generic.BulkImportView):
queryset = RackRole.objects.all()
model_form = forms.RackRoleCSVForm
table = tables.RackRoleTable
-class RackRoleBulkDeleteView(BulkDeleteView):
+class RackRoleBulkDeleteView(generic.BulkDeleteView):
queryset = RackRole.objects.annotate(rack_count=Count('racks')).order_by(*RackRole._meta.ordering)
table = tables.RackRoleTable
@@ -288,7 +284,7 @@ class RackRoleBulkDeleteView(BulkDeleteView):
# Racks
#
-class RackListView(ObjectListView):
+class RackListView(generic.ObjectListView):
queryset = Rack.objects.annotate(
device_count=Count('devices')
).order_by(*Rack._meta.ordering)
@@ -297,7 +293,7 @@ class RackListView(ObjectListView):
table = tables.RackDetailTable
-class RackElevationListView(ObjectListView):
+class RackElevationListView(generic.ObjectListView):
"""
Display a set of rack elevations side-by-side.
"""
@@ -339,7 +335,7 @@ class RackElevationListView(ObjectListView):
})
-class RackView(ObjectView):
+class RackView(generic.ObjectView):
queryset = Rack.objects.prefetch_related('site__region', 'tenant__group', 'group', 'role')
def get(self, request, pk):
@@ -374,30 +370,30 @@ class RackView(ObjectView):
})
-class RackEditView(ObjectEditView):
+class RackEditView(generic.ObjectEditView):
queryset = Rack.objects.all()
model_form = forms.RackForm
template_name = 'dcim/rack_edit.html'
-class RackDeleteView(ObjectDeleteView):
+class RackDeleteView(generic.ObjectDeleteView):
queryset = Rack.objects.all()
-class RackBulkImportView(BulkImportView):
+class RackBulkImportView(generic.BulkImportView):
queryset = Rack.objects.all()
model_form = forms.RackCSVForm
table = tables.RackTable
-class RackBulkEditView(BulkEditView):
+class RackBulkEditView(generic.BulkEditView):
queryset = Rack.objects.prefetch_related('site', 'group', 'tenant', 'role')
filterset = filters.RackFilterSet
table = tables.RackTable
form = forms.RackBulkEditForm
-class RackBulkDeleteView(BulkDeleteView):
+class RackBulkDeleteView(generic.BulkDeleteView):
queryset = Rack.objects.prefetch_related('site', 'group', 'tenant', 'role')
filterset = filters.RackFilterSet
table = tables.RackTable
@@ -407,14 +403,14 @@ class RackBulkDeleteView(BulkDeleteView):
# Rack reservations
#
-class RackReservationListView(ObjectListView):
+class RackReservationListView(generic.ObjectListView):
queryset = RackReservation.objects.all()
filterset = filters.RackReservationFilterSet
filterset_form = forms.RackReservationFilterForm
table = tables.RackReservationTable
-class RackReservationView(ObjectView):
+class RackReservationView(generic.ObjectView):
queryset = RackReservation.objects.prefetch_related('rack')
def get(self, request, pk):
@@ -426,7 +422,7 @@ class RackReservationView(ObjectView):
})
-class RackReservationEditView(ObjectEditView):
+class RackReservationEditView(generic.ObjectEditView):
queryset = RackReservation.objects.all()
model_form = forms.RackReservationForm
template_name = 'dcim/rackreservation_edit.html'
@@ -439,11 +435,11 @@ class RackReservationEditView(ObjectEditView):
return obj
-class RackReservationDeleteView(ObjectDeleteView):
+class RackReservationDeleteView(generic.ObjectDeleteView):
queryset = RackReservation.objects.all()
-class RackReservationImportView(BulkImportView):
+class RackReservationImportView(generic.BulkImportView):
queryset = RackReservation.objects.all()
model_form = forms.RackReservationCSVForm
table = tables.RackReservationTable
@@ -459,14 +455,14 @@ class RackReservationImportView(BulkImportView):
return instance
-class RackReservationBulkEditView(BulkEditView):
+class RackReservationBulkEditView(generic.BulkEditView):
queryset = RackReservation.objects.prefetch_related('rack', 'user')
filterset = filters.RackReservationFilterSet
table = tables.RackReservationTable
form = forms.RackReservationBulkEditForm
-class RackReservationBulkDeleteView(BulkDeleteView):
+class RackReservationBulkDeleteView(generic.BulkDeleteView):
queryset = RackReservation.objects.prefetch_related('rack', 'user')
filterset = filters.RackReservationFilterSet
table = tables.RackReservationTable
@@ -476,7 +472,7 @@ class RackReservationBulkDeleteView(BulkDeleteView):
# Manufacturers
#
-class ManufacturerListView(ObjectListView):
+class ManufacturerListView(generic.ObjectListView):
queryset = Manufacturer.objects.annotate(
devicetype_count=get_subquery(DeviceType, 'manufacturer'),
inventoryitem_count=get_subquery(InventoryItem, 'manufacturer'),
@@ -485,22 +481,22 @@ class ManufacturerListView(ObjectListView):
table = tables.ManufacturerTable
-class ManufacturerEditView(ObjectEditView):
+class ManufacturerEditView(generic.ObjectEditView):
queryset = Manufacturer.objects.all()
model_form = forms.ManufacturerForm
-class ManufacturerDeleteView(ObjectDeleteView):
+class ManufacturerDeleteView(generic.ObjectDeleteView):
queryset = Manufacturer.objects.all()
-class ManufacturerBulkImportView(BulkImportView):
+class ManufacturerBulkImportView(generic.BulkImportView):
queryset = Manufacturer.objects.all()
model_form = forms.ManufacturerCSVForm
table = tables.ManufacturerTable
-class ManufacturerBulkDeleteView(BulkDeleteView):
+class ManufacturerBulkDeleteView(generic.BulkDeleteView):
queryset = Manufacturer.objects.annotate(
devicetype_count=Count('device_types')
).order_by(*Manufacturer._meta.ordering)
@@ -511,7 +507,7 @@ class ManufacturerBulkDeleteView(BulkDeleteView):
# Device types
#
-class DeviceTypeListView(ObjectListView):
+class DeviceTypeListView(generic.ObjectListView):
queryset = DeviceType.objects.annotate(
instance_count=Count('instances')
).order_by(*DeviceType._meta.ordering)
@@ -520,7 +516,7 @@ class DeviceTypeListView(ObjectListView):
table = tables.DeviceTypeTable
-class DeviceTypeView(ObjectView):
+class DeviceTypeView(generic.ObjectView):
queryset = DeviceType.objects.prefetch_related('manufacturer')
def get(self, request, pk):
@@ -585,17 +581,17 @@ class DeviceTypeView(ObjectView):
})
-class DeviceTypeEditView(ObjectEditView):
+class DeviceTypeEditView(generic.ObjectEditView):
queryset = DeviceType.objects.all()
model_form = forms.DeviceTypeForm
template_name = 'dcim/devicetype_edit.html'
-class DeviceTypeDeleteView(ObjectDeleteView):
+class DeviceTypeDeleteView(generic.ObjectDeleteView):
queryset = DeviceType.objects.all()
-class DeviceTypeImportView(ObjectImportView):
+class DeviceTypeImportView(generic.ObjectImportView):
additional_permissions = [
'dcim.add_devicetype',
'dcim.add_consoleporttemplate',
@@ -621,7 +617,7 @@ class DeviceTypeImportView(ObjectImportView):
))
-class DeviceTypeBulkEditView(BulkEditView):
+class DeviceTypeBulkEditView(generic.BulkEditView):
queryset = DeviceType.objects.prefetch_related('manufacturer').annotate(
instance_count=Count('instances')
).order_by(*DeviceType._meta.ordering)
@@ -630,7 +626,7 @@ class DeviceTypeBulkEditView(BulkEditView):
form = forms.DeviceTypeBulkEditForm
-class DeviceTypeBulkDeleteView(BulkDeleteView):
+class DeviceTypeBulkDeleteView(generic.BulkDeleteView):
queryset = DeviceType.objects.prefetch_related('manufacturer').annotate(
instance_count=Count('instances')
).order_by(*DeviceType._meta.ordering)
@@ -642,33 +638,33 @@ class DeviceTypeBulkDeleteView(BulkDeleteView):
# Console port templates
#
-class ConsolePortTemplateCreateView(ComponentCreateView):
+class ConsolePortTemplateCreateView(generic.ComponentCreateView):
queryset = ConsolePortTemplate.objects.all()
form = forms.ConsolePortTemplateCreateForm
model_form = forms.ConsolePortTemplateForm
template_name = 'dcim/device_component_add.html'
-class ConsolePortTemplateEditView(ObjectEditView):
+class ConsolePortTemplateEditView(generic.ObjectEditView):
queryset = ConsolePortTemplate.objects.all()
model_form = forms.ConsolePortTemplateForm
-class ConsolePortTemplateDeleteView(ObjectDeleteView):
+class ConsolePortTemplateDeleteView(generic.ObjectDeleteView):
queryset = ConsolePortTemplate.objects.all()
-class ConsolePortTemplateBulkEditView(BulkEditView):
+class ConsolePortTemplateBulkEditView(generic.BulkEditView):
queryset = ConsolePortTemplate.objects.all()
table = tables.ConsolePortTemplateTable
form = forms.ConsolePortTemplateBulkEditForm
-class ConsolePortTemplateBulkRenameView(BulkRenameView):
+class ConsolePortTemplateBulkRenameView(generic.BulkRenameView):
queryset = ConsolePortTemplate.objects.all()
-class ConsolePortTemplateBulkDeleteView(BulkDeleteView):
+class ConsolePortTemplateBulkDeleteView(generic.BulkDeleteView):
queryset = ConsolePortTemplate.objects.all()
table = tables.ConsolePortTemplateTable
@@ -677,33 +673,33 @@ class ConsolePortTemplateBulkDeleteView(BulkDeleteView):
# Console server port templates
#
-class ConsoleServerPortTemplateCreateView(ComponentCreateView):
+class ConsoleServerPortTemplateCreateView(generic.ComponentCreateView):
queryset = ConsoleServerPortTemplate.objects.all()
form = forms.ConsoleServerPortTemplateCreateForm
model_form = forms.ConsoleServerPortTemplateForm
template_name = 'dcim/device_component_add.html'
-class ConsoleServerPortTemplateEditView(ObjectEditView):
+class ConsoleServerPortTemplateEditView(generic.ObjectEditView):
queryset = ConsoleServerPortTemplate.objects.all()
model_form = forms.ConsoleServerPortTemplateForm
-class ConsoleServerPortTemplateDeleteView(ObjectDeleteView):
+class ConsoleServerPortTemplateDeleteView(generic.ObjectDeleteView):
queryset = ConsoleServerPortTemplate.objects.all()
-class ConsoleServerPortTemplateBulkEditView(BulkEditView):
+class ConsoleServerPortTemplateBulkEditView(generic.BulkEditView):
queryset = ConsoleServerPortTemplate.objects.all()
table = tables.ConsoleServerPortTemplateTable
form = forms.ConsoleServerPortTemplateBulkEditForm
-class ConsoleServerPortTemplateBulkRenameView(BulkRenameView):
+class ConsoleServerPortTemplateBulkRenameView(generic.BulkRenameView):
queryset = ConsoleServerPortTemplate.objects.all()
-class ConsoleServerPortTemplateBulkDeleteView(BulkDeleteView):
+class ConsoleServerPortTemplateBulkDeleteView(generic.BulkDeleteView):
queryset = ConsoleServerPortTemplate.objects.all()
table = tables.ConsoleServerPortTemplateTable
@@ -712,33 +708,33 @@ class ConsoleServerPortTemplateBulkDeleteView(BulkDeleteView):
# Power port templates
#
-class PowerPortTemplateCreateView(ComponentCreateView):
+class PowerPortTemplateCreateView(generic.ComponentCreateView):
queryset = PowerPortTemplate.objects.all()
form = forms.PowerPortTemplateCreateForm
model_form = forms.PowerPortTemplateForm
template_name = 'dcim/device_component_add.html'
-class PowerPortTemplateEditView(ObjectEditView):
+class PowerPortTemplateEditView(generic.ObjectEditView):
queryset = PowerPortTemplate.objects.all()
model_form = forms.PowerPortTemplateForm
-class PowerPortTemplateDeleteView(ObjectDeleteView):
+class PowerPortTemplateDeleteView(generic.ObjectDeleteView):
queryset = PowerPortTemplate.objects.all()
-class PowerPortTemplateBulkEditView(BulkEditView):
+class PowerPortTemplateBulkEditView(generic.BulkEditView):
queryset = PowerPortTemplate.objects.all()
table = tables.PowerPortTemplateTable
form = forms.PowerPortTemplateBulkEditForm
-class PowerPortTemplateBulkRenameView(BulkRenameView):
+class PowerPortTemplateBulkRenameView(generic.BulkRenameView):
queryset = PowerPortTemplate.objects.all()
-class PowerPortTemplateBulkDeleteView(BulkDeleteView):
+class PowerPortTemplateBulkDeleteView(generic.BulkDeleteView):
queryset = PowerPortTemplate.objects.all()
table = tables.PowerPortTemplateTable
@@ -747,33 +743,33 @@ class PowerPortTemplateBulkDeleteView(BulkDeleteView):
# Power outlet templates
#
-class PowerOutletTemplateCreateView(ComponentCreateView):
+class PowerOutletTemplateCreateView(generic.ComponentCreateView):
queryset = PowerOutletTemplate.objects.all()
form = forms.PowerOutletTemplateCreateForm
model_form = forms.PowerOutletTemplateForm
template_name = 'dcim/device_component_add.html'
-class PowerOutletTemplateEditView(ObjectEditView):
+class PowerOutletTemplateEditView(generic.ObjectEditView):
queryset = PowerOutletTemplate.objects.all()
model_form = forms.PowerOutletTemplateForm
-class PowerOutletTemplateDeleteView(ObjectDeleteView):
+class PowerOutletTemplateDeleteView(generic.ObjectDeleteView):
queryset = PowerOutletTemplate.objects.all()
-class PowerOutletTemplateBulkEditView(BulkEditView):
+class PowerOutletTemplateBulkEditView(generic.BulkEditView):
queryset = PowerOutletTemplate.objects.all()
table = tables.PowerOutletTemplateTable
form = forms.PowerOutletTemplateBulkEditForm
-class PowerOutletTemplateBulkRenameView(BulkRenameView):
+class PowerOutletTemplateBulkRenameView(generic.BulkRenameView):
queryset = PowerOutletTemplate.objects.all()
-class PowerOutletTemplateBulkDeleteView(BulkDeleteView):
+class PowerOutletTemplateBulkDeleteView(generic.BulkDeleteView):
queryset = PowerOutletTemplate.objects.all()
table = tables.PowerOutletTemplateTable
@@ -782,33 +778,33 @@ class PowerOutletTemplateBulkDeleteView(BulkDeleteView):
# Interface templates
#
-class InterfaceTemplateCreateView(ComponentCreateView):
+class InterfaceTemplateCreateView(generic.ComponentCreateView):
queryset = InterfaceTemplate.objects.all()
form = forms.InterfaceTemplateCreateForm
model_form = forms.InterfaceTemplateForm
template_name = 'dcim/device_component_add.html'
-class InterfaceTemplateEditView(ObjectEditView):
+class InterfaceTemplateEditView(generic.ObjectEditView):
queryset = InterfaceTemplate.objects.all()
model_form = forms.InterfaceTemplateForm
-class InterfaceTemplateDeleteView(ObjectDeleteView):
+class InterfaceTemplateDeleteView(generic.ObjectDeleteView):
queryset = InterfaceTemplate.objects.all()
-class InterfaceTemplateBulkEditView(BulkEditView):
+class InterfaceTemplateBulkEditView(generic.BulkEditView):
queryset = InterfaceTemplate.objects.all()
table = tables.InterfaceTemplateTable
form = forms.InterfaceTemplateBulkEditForm
-class InterfaceTemplateBulkRenameView(BulkRenameView):
+class InterfaceTemplateBulkRenameView(generic.BulkRenameView):
queryset = InterfaceTemplate.objects.all()
-class InterfaceTemplateBulkDeleteView(BulkDeleteView):
+class InterfaceTemplateBulkDeleteView(generic.BulkDeleteView):
queryset = InterfaceTemplate.objects.all()
table = tables.InterfaceTemplateTable
@@ -817,33 +813,33 @@ class InterfaceTemplateBulkDeleteView(BulkDeleteView):
# Front port templates
#
-class FrontPortTemplateCreateView(ComponentCreateView):
+class FrontPortTemplateCreateView(generic.ComponentCreateView):
queryset = FrontPortTemplate.objects.all()
form = forms.FrontPortTemplateCreateForm
model_form = forms.FrontPortTemplateForm
template_name = 'dcim/device_component_add.html'
-class FrontPortTemplateEditView(ObjectEditView):
+class FrontPortTemplateEditView(generic.ObjectEditView):
queryset = FrontPortTemplate.objects.all()
model_form = forms.FrontPortTemplateForm
-class FrontPortTemplateDeleteView(ObjectDeleteView):
+class FrontPortTemplateDeleteView(generic.ObjectDeleteView):
queryset = FrontPortTemplate.objects.all()
-class FrontPortTemplateBulkEditView(BulkEditView):
+class FrontPortTemplateBulkEditView(generic.BulkEditView):
queryset = FrontPortTemplate.objects.all()
table = tables.FrontPortTemplateTable
form = forms.FrontPortTemplateBulkEditForm
-class FrontPortTemplateBulkRenameView(BulkRenameView):
+class FrontPortTemplateBulkRenameView(generic.BulkRenameView):
queryset = FrontPortTemplate.objects.all()
-class FrontPortTemplateBulkDeleteView(BulkDeleteView):
+class FrontPortTemplateBulkDeleteView(generic.BulkDeleteView):
queryset = FrontPortTemplate.objects.all()
table = tables.FrontPortTemplateTable
@@ -852,33 +848,33 @@ class FrontPortTemplateBulkDeleteView(BulkDeleteView):
# Rear port templates
#
-class RearPortTemplateCreateView(ComponentCreateView):
+class RearPortTemplateCreateView(generic.ComponentCreateView):
queryset = RearPortTemplate.objects.all()
form = forms.RearPortTemplateCreateForm
model_form = forms.RearPortTemplateForm
template_name = 'dcim/device_component_add.html'
-class RearPortTemplateEditView(ObjectEditView):
+class RearPortTemplateEditView(generic.ObjectEditView):
queryset = RearPortTemplate.objects.all()
model_form = forms.RearPortTemplateForm
-class RearPortTemplateDeleteView(ObjectDeleteView):
+class RearPortTemplateDeleteView(generic.ObjectDeleteView):
queryset = RearPortTemplate.objects.all()
-class RearPortTemplateBulkEditView(BulkEditView):
+class RearPortTemplateBulkEditView(generic.BulkEditView):
queryset = RearPortTemplate.objects.all()
table = tables.RearPortTemplateTable
form = forms.RearPortTemplateBulkEditForm
-class RearPortTemplateBulkRenameView(BulkRenameView):
+class RearPortTemplateBulkRenameView(generic.BulkRenameView):
queryset = RearPortTemplate.objects.all()
-class RearPortTemplateBulkDeleteView(BulkDeleteView):
+class RearPortTemplateBulkDeleteView(generic.BulkDeleteView):
queryset = RearPortTemplate.objects.all()
table = tables.RearPortTemplateTable
@@ -887,33 +883,33 @@ class RearPortTemplateBulkDeleteView(BulkDeleteView):
# Device bay templates
#
-class DeviceBayTemplateCreateView(ComponentCreateView):
+class DeviceBayTemplateCreateView(generic.ComponentCreateView):
queryset = DeviceBayTemplate.objects.all()
form = forms.DeviceBayTemplateCreateForm
model_form = forms.DeviceBayTemplateForm
template_name = 'dcim/device_component_add.html'
-class DeviceBayTemplateEditView(ObjectEditView):
+class DeviceBayTemplateEditView(generic.ObjectEditView):
queryset = DeviceBayTemplate.objects.all()
model_form = forms.DeviceBayTemplateForm
-class DeviceBayTemplateDeleteView(ObjectDeleteView):
+class DeviceBayTemplateDeleteView(generic.ObjectDeleteView):
queryset = DeviceBayTemplate.objects.all()
-class DeviceBayTemplateBulkEditView(BulkEditView):
+class DeviceBayTemplateBulkEditView(generic.BulkEditView):
queryset = DeviceBayTemplate.objects.all()
table = tables.DeviceBayTemplateTable
form = forms.DeviceBayTemplateBulkEditForm
-class DeviceBayTemplateBulkRenameView(BulkRenameView):
+class DeviceBayTemplateBulkRenameView(generic.BulkRenameView):
queryset = DeviceBayTemplate.objects.all()
-class DeviceBayTemplateBulkDeleteView(BulkDeleteView):
+class DeviceBayTemplateBulkDeleteView(generic.BulkDeleteView):
queryset = DeviceBayTemplate.objects.all()
table = tables.DeviceBayTemplateTable
@@ -922,7 +918,7 @@ class DeviceBayTemplateBulkDeleteView(BulkDeleteView):
# Device roles
#
-class DeviceRoleListView(ObjectListView):
+class DeviceRoleListView(generic.ObjectListView):
queryset = DeviceRole.objects.annotate(
device_count=get_subquery(Device, 'device_role'),
vm_count=get_subquery(VirtualMachine, 'role')
@@ -930,22 +926,22 @@ class DeviceRoleListView(ObjectListView):
table = tables.DeviceRoleTable
-class DeviceRoleEditView(ObjectEditView):
+class DeviceRoleEditView(generic.ObjectEditView):
queryset = DeviceRole.objects.all()
model_form = forms.DeviceRoleForm
-class DeviceRoleDeleteView(ObjectDeleteView):
+class DeviceRoleDeleteView(generic.ObjectDeleteView):
queryset = DeviceRole.objects.all()
-class DeviceRoleBulkImportView(BulkImportView):
+class DeviceRoleBulkImportView(generic.BulkImportView):
queryset = DeviceRole.objects.all()
model_form = forms.DeviceRoleCSVForm
table = tables.DeviceRoleTable
-class DeviceRoleBulkDeleteView(BulkDeleteView):
+class DeviceRoleBulkDeleteView(generic.BulkDeleteView):
queryset = DeviceRole.objects.all()
table = tables.DeviceRoleTable
@@ -954,7 +950,7 @@ class DeviceRoleBulkDeleteView(BulkDeleteView):
# Platforms
#
-class PlatformListView(ObjectListView):
+class PlatformListView(generic.ObjectListView):
queryset = Platform.objects.annotate(
device_count=get_subquery(Device, 'platform'),
vm_count=get_subquery(VirtualMachine, 'platform')
@@ -962,22 +958,22 @@ class PlatformListView(ObjectListView):
table = tables.PlatformTable
-class PlatformEditView(ObjectEditView):
+class PlatformEditView(generic.ObjectEditView):
queryset = Platform.objects.all()
model_form = forms.PlatformForm
-class PlatformDeleteView(ObjectDeleteView):
+class PlatformDeleteView(generic.ObjectDeleteView):
queryset = Platform.objects.all()
-class PlatformBulkImportView(BulkImportView):
+class PlatformBulkImportView(generic.BulkImportView):
queryset = Platform.objects.all()
model_form = forms.PlatformCSVForm
table = tables.PlatformTable
-class PlatformBulkDeleteView(BulkDeleteView):
+class PlatformBulkDeleteView(generic.BulkDeleteView):
queryset = Platform.objects.all()
table = tables.PlatformTable
@@ -986,7 +982,7 @@ class PlatformBulkDeleteView(BulkDeleteView):
# Devices
#
-class DeviceListView(ObjectListView):
+class DeviceListView(generic.ObjectListView):
queryset = Device.objects.all()
filterset = filters.DeviceFilterSet
filterset_form = forms.DeviceFilterForm
@@ -994,7 +990,7 @@ class DeviceListView(ObjectListView):
template_name = 'dcim/device_list.html'
-class DeviceView(ObjectView):
+class DeviceView(generic.ObjectView):
queryset = Device.objects.prefetch_related(
'site__region', 'rack__group', 'tenant__group', 'device_role', 'platform', 'primary_ip4', 'primary_ip6'
)
@@ -1155,7 +1151,7 @@ class DeviceView(ObjectView):
})
-class DeviceStatusView(ObjectView):
+class DeviceStatusView(generic.ObjectView):
additional_permissions = ['dcim.napalm_read_device']
queryset = Device.objects.all()
@@ -1169,7 +1165,7 @@ class DeviceStatusView(ObjectView):
})
-class DeviceLLDPNeighborsView(ObjectView):
+class DeviceLLDPNeighborsView(generic.ObjectView):
additional_permissions = ['dcim.napalm_read_device']
queryset = Device.objects.all()
@@ -1187,7 +1183,7 @@ class DeviceLLDPNeighborsView(ObjectView):
})
-class DeviceConfigView(ObjectView):
+class DeviceConfigView(generic.ObjectView):
additional_permissions = ['dcim.napalm_read_device']
queryset = Device.objects.all()
@@ -1206,24 +1202,24 @@ class DeviceConfigContextView(ObjectConfigContextView):
base_template = 'dcim/device.html'
-class DeviceEditView(ObjectEditView):
+class DeviceEditView(generic.ObjectEditView):
queryset = Device.objects.all()
model_form = forms.DeviceForm
template_name = 'dcim/device_edit.html'
-class DeviceDeleteView(ObjectDeleteView):
+class DeviceDeleteView(generic.ObjectDeleteView):
queryset = Device.objects.all()
-class DeviceBulkImportView(BulkImportView):
+class DeviceBulkImportView(generic.BulkImportView):
queryset = Device.objects.all()
model_form = forms.DeviceCSVForm
table = tables.DeviceImportTable
template_name = 'dcim/device_import.html'
-class ChildDeviceBulkImportView(BulkImportView):
+class ChildDeviceBulkImportView(generic.BulkImportView):
queryset = Device.objects.all()
model_form = forms.ChildDeviceCSVForm
table = tables.DeviceImportTable
@@ -1241,14 +1237,14 @@ class ChildDeviceBulkImportView(BulkImportView):
return obj
-class DeviceBulkEditView(BulkEditView):
+class DeviceBulkEditView(generic.BulkEditView):
queryset = Device.objects.prefetch_related('tenant', 'site', 'rack', 'device_role', 'device_type__manufacturer')
filterset = filters.DeviceFilterSet
table = tables.DeviceTable
form = forms.DeviceBulkEditForm
-class DeviceBulkDeleteView(BulkDeleteView):
+class DeviceBulkDeleteView(generic.BulkDeleteView):
queryset = Device.objects.prefetch_related('tenant', 'site', 'rack', 'device_role', 'device_type__manufacturer')
filterset = filters.DeviceFilterSet
table = tables.DeviceTable
@@ -1258,7 +1254,7 @@ class DeviceBulkDeleteView(BulkDeleteView):
# Console ports
#
-class ConsolePortListView(ObjectListView):
+class ConsolePortListView(generic.ObjectListView):
queryset = ConsolePort.objects.all()
filterset = filters.ConsolePortFilterSet
filterset_form = forms.ConsolePortFilterForm
@@ -1266,41 +1262,41 @@ class ConsolePortListView(ObjectListView):
action_buttons = ('import', 'export')
-class ConsolePortView(ObjectView):
+class ConsolePortView(generic.ObjectView):
queryset = ConsolePort.objects.all()
-class ConsolePortCreateView(ComponentCreateView):
+class ConsolePortCreateView(generic.ComponentCreateView):
queryset = ConsolePort.objects.all()
form = forms.ConsolePortCreateForm
model_form = forms.ConsolePortForm
template_name = 'dcim/device_component_add.html'
-class ConsolePortEditView(ObjectEditView):
+class ConsolePortEditView(generic.ObjectEditView):
queryset = ConsolePort.objects.all()
model_form = forms.ConsolePortForm
template_name = 'dcim/device_component_edit.html'
-class ConsolePortDeleteView(ObjectDeleteView):
+class ConsolePortDeleteView(generic.ObjectDeleteView):
queryset = ConsolePort.objects.all()
-class ConsolePortBulkImportView(BulkImportView):
+class ConsolePortBulkImportView(generic.BulkImportView):
queryset = ConsolePort.objects.all()
model_form = forms.ConsolePortCSVForm
table = tables.ConsolePortTable
-class ConsolePortBulkEditView(BulkEditView):
+class ConsolePortBulkEditView(generic.BulkEditView):
queryset = ConsolePort.objects.all()
filterset = filters.ConsolePortFilterSet
table = tables.ConsolePortTable
form = forms.ConsolePortBulkEditForm
-class ConsolePortBulkRenameView(BulkRenameView):
+class ConsolePortBulkRenameView(generic.BulkRenameView):
queryset = ConsolePort.objects.all()
@@ -1308,7 +1304,7 @@ class ConsolePortBulkDisconnectView(BulkDisconnectView):
queryset = ConsolePort.objects.all()
-class ConsolePortBulkDeleteView(BulkDeleteView):
+class ConsolePortBulkDeleteView(generic.BulkDeleteView):
queryset = ConsolePort.objects.all()
filterset = filters.ConsolePortFilterSet
table = tables.ConsolePortTable
@@ -1318,7 +1314,7 @@ class ConsolePortBulkDeleteView(BulkDeleteView):
# Console server ports
#
-class ConsoleServerPortListView(ObjectListView):
+class ConsoleServerPortListView(generic.ObjectListView):
queryset = ConsoleServerPort.objects.all()
filterset = filters.ConsoleServerPortFilterSet
filterset_form = forms.ConsoleServerPortFilterForm
@@ -1326,41 +1322,41 @@ class ConsoleServerPortListView(ObjectListView):
action_buttons = ('import', 'export')
-class ConsoleServerPortView(ObjectView):
+class ConsoleServerPortView(generic.ObjectView):
queryset = ConsoleServerPort.objects.all()
-class ConsoleServerPortCreateView(ComponentCreateView):
+class ConsoleServerPortCreateView(generic.ComponentCreateView):
queryset = ConsoleServerPort.objects.all()
form = forms.ConsoleServerPortCreateForm
model_form = forms.ConsoleServerPortForm
template_name = 'dcim/device_component_add.html'
-class ConsoleServerPortEditView(ObjectEditView):
+class ConsoleServerPortEditView(generic.ObjectEditView):
queryset = ConsoleServerPort.objects.all()
model_form = forms.ConsoleServerPortForm
template_name = 'dcim/device_component_edit.html'
-class ConsoleServerPortDeleteView(ObjectDeleteView):
+class ConsoleServerPortDeleteView(generic.ObjectDeleteView):
queryset = ConsoleServerPort.objects.all()
-class ConsoleServerPortBulkImportView(BulkImportView):
+class ConsoleServerPortBulkImportView(generic.BulkImportView):
queryset = ConsoleServerPort.objects.all()
model_form = forms.ConsoleServerPortCSVForm
table = tables.ConsoleServerPortTable
-class ConsoleServerPortBulkEditView(BulkEditView):
+class ConsoleServerPortBulkEditView(generic.BulkEditView):
queryset = ConsoleServerPort.objects.all()
filterset = filters.ConsoleServerPortFilterSet
table = tables.ConsoleServerPortTable
form = forms.ConsoleServerPortBulkEditForm
-class ConsoleServerPortBulkRenameView(BulkRenameView):
+class ConsoleServerPortBulkRenameView(generic.BulkRenameView):
queryset = ConsoleServerPort.objects.all()
@@ -1368,7 +1364,7 @@ class ConsoleServerPortBulkDisconnectView(BulkDisconnectView):
queryset = ConsoleServerPort.objects.all()
-class ConsoleServerPortBulkDeleteView(BulkDeleteView):
+class ConsoleServerPortBulkDeleteView(generic.BulkDeleteView):
queryset = ConsoleServerPort.objects.all()
filterset = filters.ConsoleServerPortFilterSet
table = tables.ConsoleServerPortTable
@@ -1378,7 +1374,7 @@ class ConsoleServerPortBulkDeleteView(BulkDeleteView):
# Power ports
#
-class PowerPortListView(ObjectListView):
+class PowerPortListView(generic.ObjectListView):
queryset = PowerPort.objects.all()
filterset = filters.PowerPortFilterSet
filterset_form = forms.PowerPortFilterForm
@@ -1386,41 +1382,41 @@ class PowerPortListView(ObjectListView):
action_buttons = ('import', 'export')
-class PowerPortView(ObjectView):
+class PowerPortView(generic.ObjectView):
queryset = PowerPort.objects.all()
-class PowerPortCreateView(ComponentCreateView):
+class PowerPortCreateView(generic.ComponentCreateView):
queryset = PowerPort.objects.all()
form = forms.PowerPortCreateForm
model_form = forms.PowerPortForm
template_name = 'dcim/device_component_add.html'
-class PowerPortEditView(ObjectEditView):
+class PowerPortEditView(generic.ObjectEditView):
queryset = PowerPort.objects.all()
model_form = forms.PowerPortForm
template_name = 'dcim/device_component_edit.html'
-class PowerPortDeleteView(ObjectDeleteView):
+class PowerPortDeleteView(generic.ObjectDeleteView):
queryset = PowerPort.objects.all()
-class PowerPortBulkImportView(BulkImportView):
+class PowerPortBulkImportView(generic.BulkImportView):
queryset = PowerPort.objects.all()
model_form = forms.PowerPortCSVForm
table = tables.PowerPortTable
-class PowerPortBulkEditView(BulkEditView):
+class PowerPortBulkEditView(generic.BulkEditView):
queryset = PowerPort.objects.all()
filterset = filters.PowerPortFilterSet
table = tables.PowerPortTable
form = forms.PowerPortBulkEditForm
-class PowerPortBulkRenameView(BulkRenameView):
+class PowerPortBulkRenameView(generic.BulkRenameView):
queryset = PowerPort.objects.all()
@@ -1428,7 +1424,7 @@ class PowerPortBulkDisconnectView(BulkDisconnectView):
queryset = PowerPort.objects.all()
-class PowerPortBulkDeleteView(BulkDeleteView):
+class PowerPortBulkDeleteView(generic.BulkDeleteView):
queryset = PowerPort.objects.all()
filterset = filters.PowerPortFilterSet
table = tables.PowerPortTable
@@ -1438,7 +1434,7 @@ class PowerPortBulkDeleteView(BulkDeleteView):
# Power outlets
#
-class PowerOutletListView(ObjectListView):
+class PowerOutletListView(generic.ObjectListView):
queryset = PowerOutlet.objects.all()
filterset = filters.PowerOutletFilterSet
filterset_form = forms.PowerOutletFilterForm
@@ -1446,41 +1442,41 @@ class PowerOutletListView(ObjectListView):
action_buttons = ('import', 'export')
-class PowerOutletView(ObjectView):
+class PowerOutletView(generic.ObjectView):
queryset = PowerOutlet.objects.all()
-class PowerOutletCreateView(ComponentCreateView):
+class PowerOutletCreateView(generic.ComponentCreateView):
queryset = PowerOutlet.objects.all()
form = forms.PowerOutletCreateForm
model_form = forms.PowerOutletForm
template_name = 'dcim/device_component_add.html'
-class PowerOutletEditView(ObjectEditView):
+class PowerOutletEditView(generic.ObjectEditView):
queryset = PowerOutlet.objects.all()
model_form = forms.PowerOutletForm
template_name = 'dcim/device_component_edit.html'
-class PowerOutletDeleteView(ObjectDeleteView):
+class PowerOutletDeleteView(generic.ObjectDeleteView):
queryset = PowerOutlet.objects.all()
-class PowerOutletBulkImportView(BulkImportView):
+class PowerOutletBulkImportView(generic.BulkImportView):
queryset = PowerOutlet.objects.all()
model_form = forms.PowerOutletCSVForm
table = tables.PowerOutletTable
-class PowerOutletBulkEditView(BulkEditView):
+class PowerOutletBulkEditView(generic.BulkEditView):
queryset = PowerOutlet.objects.all()
filterset = filters.PowerOutletFilterSet
table = tables.PowerOutletTable
form = forms.PowerOutletBulkEditForm
-class PowerOutletBulkRenameView(BulkRenameView):
+class PowerOutletBulkRenameView(generic.BulkRenameView):
queryset = PowerOutlet.objects.all()
@@ -1488,7 +1484,7 @@ class PowerOutletBulkDisconnectView(BulkDisconnectView):
queryset = PowerOutlet.objects.all()
-class PowerOutletBulkDeleteView(BulkDeleteView):
+class PowerOutletBulkDeleteView(generic.BulkDeleteView):
queryset = PowerOutlet.objects.all()
filterset = filters.PowerOutletFilterSet
table = tables.PowerOutletTable
@@ -1498,7 +1494,7 @@ class PowerOutletBulkDeleteView(BulkDeleteView):
# Interfaces
#
-class InterfaceListView(ObjectListView):
+class InterfaceListView(generic.ObjectListView):
queryset = Interface.objects.all()
filterset = filters.InterfaceFilterSet
filterset_form = forms.InterfaceFilterForm
@@ -1506,7 +1502,7 @@ class InterfaceListView(ObjectListView):
action_buttons = ('import', 'export')
-class InterfaceView(ObjectView):
+class InterfaceView(generic.ObjectView):
queryset = Interface.objects.all()
def get(self, request, pk):
@@ -1540,37 +1536,37 @@ class InterfaceView(ObjectView):
})
-class InterfaceCreateView(ComponentCreateView):
+class InterfaceCreateView(generic.ComponentCreateView):
queryset = Interface.objects.all()
form = forms.InterfaceCreateForm
model_form = forms.InterfaceForm
template_name = 'dcim/device_component_add.html'
-class InterfaceEditView(ObjectEditView):
+class InterfaceEditView(generic.ObjectEditView):
queryset = Interface.objects.all()
model_form = forms.InterfaceForm
template_name = 'dcim/interface_edit.html'
-class InterfaceDeleteView(ObjectDeleteView):
+class InterfaceDeleteView(generic.ObjectDeleteView):
queryset = Interface.objects.all()
-class InterfaceBulkImportView(BulkImportView):
+class InterfaceBulkImportView(generic.BulkImportView):
queryset = Interface.objects.all()
model_form = forms.InterfaceCSVForm
table = tables.InterfaceTable
-class InterfaceBulkEditView(BulkEditView):
+class InterfaceBulkEditView(generic.BulkEditView):
queryset = Interface.objects.all()
filterset = filters.InterfaceFilterSet
table = tables.InterfaceTable
form = forms.InterfaceBulkEditForm
-class InterfaceBulkRenameView(BulkRenameView):
+class InterfaceBulkRenameView(generic.BulkRenameView):
queryset = Interface.objects.all()
@@ -1578,7 +1574,7 @@ class InterfaceBulkDisconnectView(BulkDisconnectView):
queryset = Interface.objects.all()
-class InterfaceBulkDeleteView(BulkDeleteView):
+class InterfaceBulkDeleteView(generic.BulkDeleteView):
queryset = Interface.objects.all()
filterset = filters.InterfaceFilterSet
table = tables.InterfaceTable
@@ -1588,7 +1584,7 @@ class InterfaceBulkDeleteView(BulkDeleteView):
# Front ports
#
-class FrontPortListView(ObjectListView):
+class FrontPortListView(generic.ObjectListView):
queryset = FrontPort.objects.all()
filterset = filters.FrontPortFilterSet
filterset_form = forms.FrontPortFilterForm
@@ -1596,41 +1592,41 @@ class FrontPortListView(ObjectListView):
action_buttons = ('import', 'export')
-class FrontPortView(ObjectView):
+class FrontPortView(generic.ObjectView):
queryset = FrontPort.objects.all()
-class FrontPortCreateView(ComponentCreateView):
+class FrontPortCreateView(generic.ComponentCreateView):
queryset = FrontPort.objects.all()
form = forms.FrontPortCreateForm
model_form = forms.FrontPortForm
template_name = 'dcim/device_component_add.html'
-class FrontPortEditView(ObjectEditView):
+class FrontPortEditView(generic.ObjectEditView):
queryset = FrontPort.objects.all()
model_form = forms.FrontPortForm
template_name = 'dcim/device_component_edit.html'
-class FrontPortDeleteView(ObjectDeleteView):
+class FrontPortDeleteView(generic.ObjectDeleteView):
queryset = FrontPort.objects.all()
-class FrontPortBulkImportView(BulkImportView):
+class FrontPortBulkImportView(generic.BulkImportView):
queryset = FrontPort.objects.all()
model_form = forms.FrontPortCSVForm
table = tables.FrontPortTable
-class FrontPortBulkEditView(BulkEditView):
+class FrontPortBulkEditView(generic.BulkEditView):
queryset = FrontPort.objects.all()
filterset = filters.FrontPortFilterSet
table = tables.FrontPortTable
form = forms.FrontPortBulkEditForm
-class FrontPortBulkRenameView(BulkRenameView):
+class FrontPortBulkRenameView(generic.BulkRenameView):
queryset = FrontPort.objects.all()
@@ -1638,7 +1634,7 @@ class FrontPortBulkDisconnectView(BulkDisconnectView):
queryset = FrontPort.objects.all()
-class FrontPortBulkDeleteView(BulkDeleteView):
+class FrontPortBulkDeleteView(generic.BulkDeleteView):
queryset = FrontPort.objects.all()
filterset = filters.FrontPortFilterSet
table = tables.FrontPortTable
@@ -1648,7 +1644,7 @@ class FrontPortBulkDeleteView(BulkDeleteView):
# Rear ports
#
-class RearPortListView(ObjectListView):
+class RearPortListView(generic.ObjectListView):
queryset = RearPort.objects.all()
filterset = filters.RearPortFilterSet
filterset_form = forms.RearPortFilterForm
@@ -1656,41 +1652,41 @@ class RearPortListView(ObjectListView):
action_buttons = ('import', 'export')
-class RearPortView(ObjectView):
+class RearPortView(generic.ObjectView):
queryset = RearPort.objects.all()
-class RearPortCreateView(ComponentCreateView):
+class RearPortCreateView(generic.ComponentCreateView):
queryset = RearPort.objects.all()
form = forms.RearPortCreateForm
model_form = forms.RearPortForm
template_name = 'dcim/device_component_add.html'
-class RearPortEditView(ObjectEditView):
+class RearPortEditView(generic.ObjectEditView):
queryset = RearPort.objects.all()
model_form = forms.RearPortForm
template_name = 'dcim/device_component_edit.html'
-class RearPortDeleteView(ObjectDeleteView):
+class RearPortDeleteView(generic.ObjectDeleteView):
queryset = RearPort.objects.all()
-class RearPortBulkImportView(BulkImportView):
+class RearPortBulkImportView(generic.BulkImportView):
queryset = RearPort.objects.all()
model_form = forms.RearPortCSVForm
table = tables.RearPortTable
-class RearPortBulkEditView(BulkEditView):
+class RearPortBulkEditView(generic.BulkEditView):
queryset = RearPort.objects.all()
filterset = filters.RearPortFilterSet
table = tables.RearPortTable
form = forms.RearPortBulkEditForm
-class RearPortBulkRenameView(BulkRenameView):
+class RearPortBulkRenameView(generic.BulkRenameView):
queryset = RearPort.objects.all()
@@ -1698,7 +1694,7 @@ class RearPortBulkDisconnectView(BulkDisconnectView):
queryset = RearPort.objects.all()
-class RearPortBulkDeleteView(BulkDeleteView):
+class RearPortBulkDeleteView(generic.BulkDeleteView):
queryset = RearPort.objects.all()
filterset = filters.RearPortFilterSet
table = tables.RearPortTable
@@ -1708,7 +1704,7 @@ class RearPortBulkDeleteView(BulkDeleteView):
# Device bays
#
-class DeviceBayListView(ObjectListView):
+class DeviceBayListView(generic.ObjectListView):
queryset = DeviceBay.objects.all()
filterset = filters.DeviceBayFilterSet
filterset_form = forms.DeviceBayFilterForm
@@ -1716,28 +1712,28 @@ class DeviceBayListView(ObjectListView):
action_buttons = ('import', 'export')
-class DeviceBayView(ObjectView):
+class DeviceBayView(generic.ObjectView):
queryset = DeviceBay.objects.all()
-class DeviceBayCreateView(ComponentCreateView):
+class DeviceBayCreateView(generic.ComponentCreateView):
queryset = DeviceBay.objects.all()
form = forms.DeviceBayCreateForm
model_form = forms.DeviceBayForm
template_name = 'dcim/device_component_add.html'
-class DeviceBayEditView(ObjectEditView):
+class DeviceBayEditView(generic.ObjectEditView):
queryset = DeviceBay.objects.all()
model_form = forms.DeviceBayForm
template_name = 'dcim/device_component_edit.html'
-class DeviceBayDeleteView(ObjectDeleteView):
+class DeviceBayDeleteView(generic.ObjectDeleteView):
queryset = DeviceBay.objects.all()
-class DeviceBayPopulateView(ObjectEditView):
+class DeviceBayPopulateView(generic.ObjectEditView):
queryset = DeviceBay.objects.all()
def get(self, request, pk):
@@ -1769,7 +1765,7 @@ class DeviceBayPopulateView(ObjectEditView):
})
-class DeviceBayDepopulateView(ObjectEditView):
+class DeviceBayDepopulateView(generic.ObjectEditView):
queryset = DeviceBay.objects.all()
def get(self, request, pk):
@@ -1802,24 +1798,24 @@ class DeviceBayDepopulateView(ObjectEditView):
})
-class DeviceBayBulkImportView(BulkImportView):
+class DeviceBayBulkImportView(generic.BulkImportView):
queryset = DeviceBay.objects.all()
model_form = forms.DeviceBayCSVForm
table = tables.DeviceBayTable
-class DeviceBayBulkEditView(BulkEditView):
+class DeviceBayBulkEditView(generic.BulkEditView):
queryset = DeviceBay.objects.all()
filterset = filters.DeviceBayFilterSet
table = tables.DeviceBayTable
form = forms.DeviceBayBulkEditForm
-class DeviceBayBulkRenameView(BulkRenameView):
+class DeviceBayBulkRenameView(generic.BulkRenameView):
queryset = DeviceBay.objects.all()
-class DeviceBayBulkDeleteView(BulkDeleteView):
+class DeviceBayBulkDeleteView(generic.BulkDeleteView):
queryset = DeviceBay.objects.all()
filterset = filters.DeviceBayFilterSet
table = tables.DeviceBayTable
@@ -1829,7 +1825,7 @@ class DeviceBayBulkDeleteView(BulkDeleteView):
# Inventory items
#
-class InventoryItemListView(ObjectListView):
+class InventoryItemListView(generic.ObjectListView):
queryset = InventoryItem.objects.all()
filterset = filters.InventoryItemFilterSet
filterset_form = forms.InventoryItemFilterForm
@@ -1837,44 +1833,44 @@ class InventoryItemListView(ObjectListView):
action_buttons = ('import', 'export')
-class InventoryItemView(ObjectView):
+class InventoryItemView(generic.ObjectView):
queryset = InventoryItem.objects.all()
-class InventoryItemEditView(ObjectEditView):
+class InventoryItemEditView(generic.ObjectEditView):
queryset = InventoryItem.objects.all()
model_form = forms.InventoryItemForm
-class InventoryItemCreateView(ComponentCreateView):
+class InventoryItemCreateView(generic.ComponentCreateView):
queryset = InventoryItem.objects.all()
form = forms.InventoryItemCreateForm
model_form = forms.InventoryItemForm
template_name = 'dcim/device_component_add.html'
-class InventoryItemDeleteView(ObjectDeleteView):
+class InventoryItemDeleteView(generic.ObjectDeleteView):
queryset = InventoryItem.objects.all()
-class InventoryItemBulkImportView(BulkImportView):
+class InventoryItemBulkImportView(generic.BulkImportView):
queryset = InventoryItem.objects.all()
model_form = forms.InventoryItemCSVForm
table = tables.InventoryItemTable
-class InventoryItemBulkEditView(BulkEditView):
+class InventoryItemBulkEditView(generic.BulkEditView):
queryset = InventoryItem.objects.prefetch_related('device', 'manufacturer')
filterset = filters.InventoryItemFilterSet
table = tables.InventoryItemTable
form = forms.InventoryItemBulkEditForm
-class InventoryItemBulkRenameView(BulkRenameView):
+class InventoryItemBulkRenameView(generic.BulkRenameView):
queryset = InventoryItem.objects.all()
-class InventoryItemBulkDeleteView(BulkDeleteView):
+class InventoryItemBulkDeleteView(generic.BulkDeleteView):
queryset = InventoryItem.objects.prefetch_related('device', 'manufacturer')
table = tables.InventoryItemTable
template_name = 'dcim/inventoryitem_bulk_delete.html'
@@ -1884,7 +1880,7 @@ class InventoryItemBulkDeleteView(BulkDeleteView):
# Bulk Device component creation
#
-class DeviceBulkAddConsolePortView(BulkComponentCreateView):
+class DeviceBulkAddConsolePortView(generic.BulkComponentCreateView):
parent_model = Device
parent_field = 'device'
form = forms.ConsolePortBulkCreateForm
@@ -1895,7 +1891,7 @@ class DeviceBulkAddConsolePortView(BulkComponentCreateView):
default_return_url = 'dcim:device_list'
-class DeviceBulkAddConsoleServerPortView(BulkComponentCreateView):
+class DeviceBulkAddConsoleServerPortView(generic.BulkComponentCreateView):
parent_model = Device
parent_field = 'device'
form = forms.ConsoleServerPortBulkCreateForm
@@ -1906,7 +1902,7 @@ class DeviceBulkAddConsoleServerPortView(BulkComponentCreateView):
default_return_url = 'dcim:device_list'
-class DeviceBulkAddPowerPortView(BulkComponentCreateView):
+class DeviceBulkAddPowerPortView(generic.BulkComponentCreateView):
parent_model = Device
parent_field = 'device'
form = forms.PowerPortBulkCreateForm
@@ -1917,7 +1913,7 @@ class DeviceBulkAddPowerPortView(BulkComponentCreateView):
default_return_url = 'dcim:device_list'
-class DeviceBulkAddPowerOutletView(BulkComponentCreateView):
+class DeviceBulkAddPowerOutletView(generic.BulkComponentCreateView):
parent_model = Device
parent_field = 'device'
form = forms.PowerOutletBulkCreateForm
@@ -1928,7 +1924,7 @@ class DeviceBulkAddPowerOutletView(BulkComponentCreateView):
default_return_url = 'dcim:device_list'
-class DeviceBulkAddInterfaceView(BulkComponentCreateView):
+class DeviceBulkAddInterfaceView(generic.BulkComponentCreateView):
parent_model = Device
parent_field = 'device'
form = forms.InterfaceBulkCreateForm
@@ -1939,7 +1935,7 @@ class DeviceBulkAddInterfaceView(BulkComponentCreateView):
default_return_url = 'dcim:device_list'
-# class DeviceBulkAddFrontPortView(BulkComponentCreateView):
+# class DeviceBulkAddFrontPortView(generic.BulkComponentCreateView):
# parent_model = Device
# parent_field = 'device'
# form = forms.FrontPortBulkCreateForm
@@ -1950,7 +1946,7 @@ class DeviceBulkAddInterfaceView(BulkComponentCreateView):
# default_return_url = 'dcim:device_list'
-class DeviceBulkAddRearPortView(BulkComponentCreateView):
+class DeviceBulkAddRearPortView(generic.BulkComponentCreateView):
parent_model = Device
parent_field = 'device'
form = forms.RearPortBulkCreateForm
@@ -1961,7 +1957,7 @@ class DeviceBulkAddRearPortView(BulkComponentCreateView):
default_return_url = 'dcim:device_list'
-class DeviceBulkAddDeviceBayView(BulkComponentCreateView):
+class DeviceBulkAddDeviceBayView(generic.BulkComponentCreateView):
parent_model = Device
parent_field = 'device'
form = forms.DeviceBayBulkCreateForm
@@ -1972,7 +1968,7 @@ class DeviceBulkAddDeviceBayView(BulkComponentCreateView):
default_return_url = 'dcim:device_list'
-class DeviceBulkAddInventoryItemView(BulkComponentCreateView):
+class DeviceBulkAddInventoryItemView(generic.BulkComponentCreateView):
parent_model = Device
parent_field = 'device'
form = forms.InventoryItemBulkCreateForm
@@ -1987,7 +1983,7 @@ class DeviceBulkAddInventoryItemView(BulkComponentCreateView):
# Cables
#
-class CableListView(ObjectListView):
+class CableListView(generic.ObjectListView):
queryset = Cable.objects.all()
filterset = filters.CableFilterSet
filterset_form = forms.CableFilterForm
@@ -1995,7 +1991,7 @@ class CableListView(ObjectListView):
action_buttons = ('import', 'export')
-class CableView(ObjectView):
+class CableView(generic.ObjectView):
queryset = Cable.objects.all()
def get(self, request, pk):
@@ -2007,7 +2003,7 @@ class CableView(ObjectView):
})
-class PathTraceView(ObjectView):
+class PathTraceView(generic.ObjectView):
"""
Trace a cable path beginning from the given path endpoint (origin).
"""
@@ -2048,7 +2044,7 @@ class PathTraceView(ObjectView):
})
-class CableCreateView(ObjectEditView):
+class CableCreateView(generic.ObjectEditView):
queryset = Cable.objects.all()
template_name = 'dcim/cable_connect.html'
@@ -2107,30 +2103,30 @@ class CableCreateView(ObjectEditView):
})
-class CableEditView(ObjectEditView):
+class CableEditView(generic.ObjectEditView):
queryset = Cable.objects.all()
model_form = forms.CableForm
template_name = 'dcim/cable_edit.html'
-class CableDeleteView(ObjectDeleteView):
+class CableDeleteView(generic.ObjectDeleteView):
queryset = Cable.objects.all()
-class CableBulkImportView(BulkImportView):
+class CableBulkImportView(generic.BulkImportView):
queryset = Cable.objects.all()
model_form = forms.CableCSVForm
table = tables.CableTable
-class CableBulkEditView(BulkEditView):
+class CableBulkEditView(generic.BulkEditView):
queryset = Cable.objects.prefetch_related('termination_a', 'termination_b')
filterset = filters.CableFilterSet
table = tables.CableTable
form = forms.CableBulkEditForm
-class CableBulkDeleteView(BulkDeleteView):
+class CableBulkDeleteView(generic.BulkDeleteView):
queryset = Cable.objects.prefetch_related('termination_a', 'termination_b')
filterset = filters.CableFilterSet
table = tables.CableTable
@@ -2140,7 +2136,7 @@ class CableBulkDeleteView(BulkDeleteView):
# Connections
#
-class ConsoleConnectionsListView(ObjectListView):
+class ConsoleConnectionsListView(generic.ObjectListView):
queryset = ConsolePort.objects.filter(_path__isnull=False).order_by('device')
filterset = filters.ConsoleConnectionFilterSet
filterset_form = forms.ConsoleConnectionFilterForm
@@ -2170,7 +2166,7 @@ class ConsoleConnectionsListView(ObjectListView):
}
-class PowerConnectionsListView(ObjectListView):
+class PowerConnectionsListView(generic.ObjectListView):
queryset = PowerPort.objects.filter(_path__isnull=False).order_by('device')
filterset = filters.PowerConnectionFilterSet
filterset_form = forms.PowerConnectionFilterForm
@@ -2200,7 +2196,7 @@ class PowerConnectionsListView(ObjectListView):
}
-class InterfaceConnectionsListView(ObjectListView):
+class InterfaceConnectionsListView(generic.ObjectListView):
queryset = Interface.objects.filter(
# Avoid duplicate connections by only selecting the lower PK in a connected pair
_path__isnull=False,
@@ -2240,7 +2236,7 @@ class InterfaceConnectionsListView(ObjectListView):
# Virtual chassis
#
-class VirtualChassisListView(ObjectListView):
+class VirtualChassisListView(generic.ObjectListView):
queryset = VirtualChassis.objects.annotate(
member_count=Count('members', distinct=True)
).order_by(*VirtualChassis._meta.ordering)
@@ -2249,7 +2245,7 @@ class VirtualChassisListView(ObjectListView):
filterset_form = forms.VirtualChassisFilterForm
-class VirtualChassisView(ObjectView):
+class VirtualChassisView(generic.ObjectView):
queryset = VirtualChassis.objects.all()
def get(self, request, pk):
@@ -2262,7 +2258,7 @@ class VirtualChassisView(ObjectView):
})
-class VirtualChassisCreateView(ObjectEditView):
+class VirtualChassisCreateView(generic.ObjectEditView):
queryset = VirtualChassis.objects.all()
model_form = forms.VirtualChassisCreateForm
template_name = 'dcim/virtualchassis_add.html'
@@ -2336,7 +2332,7 @@ class VirtualChassisEditView(ObjectPermissionRequiredMixin, GetReturnURLMixin, V
})
-class VirtualChassisDeleteView(ObjectDeleteView):
+class VirtualChassisDeleteView(generic.ObjectDeleteView):
queryset = VirtualChassis.objects.all()
@@ -2447,20 +2443,20 @@ class VirtualChassisRemoveMemberView(ObjectPermissionRequiredMixin, GetReturnURL
})
-class VirtualChassisBulkImportView(BulkImportView):
+class VirtualChassisBulkImportView(generic.BulkImportView):
queryset = VirtualChassis.objects.all()
model_form = forms.VirtualChassisCSVForm
table = tables.VirtualChassisTable
-class VirtualChassisBulkEditView(BulkEditView):
+class VirtualChassisBulkEditView(generic.BulkEditView):
queryset = VirtualChassis.objects.all()
filterset = filters.VirtualChassisFilterSet
table = tables.VirtualChassisTable
form = forms.VirtualChassisBulkEditForm
-class VirtualChassisBulkDeleteView(BulkDeleteView):
+class VirtualChassisBulkDeleteView(generic.BulkDeleteView):
queryset = VirtualChassis.objects.all()
filterset = filters.VirtualChassisFilterSet
table = tables.VirtualChassisTable
@@ -2470,7 +2466,7 @@ class VirtualChassisBulkDeleteView(BulkDeleteView):
# Power panels
#
-class PowerPanelListView(ObjectListView):
+class PowerPanelListView(generic.ObjectListView):
queryset = PowerPanel.objects.annotate(
powerfeed_count=Count('powerfeeds')
).order_by(*PowerPanel._meta.ordering)
@@ -2479,7 +2475,7 @@ class PowerPanelListView(ObjectListView):
table = tables.PowerPanelTable
-class PowerPanelView(ObjectView):
+class PowerPanelView(generic.ObjectView):
queryset = PowerPanel.objects.prefetch_related('site', 'rack_group')
def get(self, request, pk):
@@ -2498,30 +2494,30 @@ class PowerPanelView(ObjectView):
})
-class PowerPanelEditView(ObjectEditView):
+class PowerPanelEditView(generic.ObjectEditView):
queryset = PowerPanel.objects.all()
model_form = forms.PowerPanelForm
template_name = 'dcim/powerpanel_edit.html'
-class PowerPanelDeleteView(ObjectDeleteView):
+class PowerPanelDeleteView(generic.ObjectDeleteView):
queryset = PowerPanel.objects.all()
-class PowerPanelBulkImportView(BulkImportView):
+class PowerPanelBulkImportView(generic.BulkImportView):
queryset = PowerPanel.objects.all()
model_form = forms.PowerPanelCSVForm
table = tables.PowerPanelTable
-class PowerPanelBulkEditView(BulkEditView):
+class PowerPanelBulkEditView(generic.BulkEditView):
queryset = PowerPanel.objects.prefetch_related('site', 'rack_group')
filterset = filters.PowerPanelFilterSet
table = tables.PowerPanelTable
form = forms.PowerPanelBulkEditForm
-class PowerPanelBulkDeleteView(BulkDeleteView):
+class PowerPanelBulkDeleteView(generic.BulkDeleteView):
queryset = PowerPanel.objects.prefetch_related(
'site', 'rack_group'
).annotate(
@@ -2535,14 +2531,14 @@ class PowerPanelBulkDeleteView(BulkDeleteView):
# Power feeds
#
-class PowerFeedListView(ObjectListView):
+class PowerFeedListView(generic.ObjectListView):
queryset = PowerFeed.objects.all()
filterset = filters.PowerFeedFilterSet
filterset_form = forms.PowerFeedFilterForm
table = tables.PowerFeedTable
-class PowerFeedView(ObjectView):
+class PowerFeedView(generic.ObjectView):
queryset = PowerFeed.objects.prefetch_related('power_panel', 'rack')
def get(self, request, pk):
@@ -2554,30 +2550,30 @@ class PowerFeedView(ObjectView):
})
-class PowerFeedEditView(ObjectEditView):
+class PowerFeedEditView(generic.ObjectEditView):
queryset = PowerFeed.objects.all()
model_form = forms.PowerFeedForm
template_name = 'dcim/powerfeed_edit.html'
-class PowerFeedDeleteView(ObjectDeleteView):
+class PowerFeedDeleteView(generic.ObjectDeleteView):
queryset = PowerFeed.objects.all()
-class PowerFeedBulkImportView(BulkImportView):
+class PowerFeedBulkImportView(generic.BulkImportView):
queryset = PowerFeed.objects.all()
model_form = forms.PowerFeedCSVForm
table = tables.PowerFeedTable
-class PowerFeedBulkEditView(BulkEditView):
+class PowerFeedBulkEditView(generic.BulkEditView):
queryset = PowerFeed.objects.prefetch_related('power_panel', 'rack')
filterset = filters.PowerFeedFilterSet
table = tables.PowerFeedTable
form = forms.PowerFeedBulkEditForm
-class PowerFeedBulkDeleteView(BulkDeleteView):
+class PowerFeedBulkDeleteView(generic.BulkDeleteView):
queryset = PowerFeed.objects.prefetch_related('power_panel', 'rack')
filterset = filters.PowerFeedFilterSet
table = tables.PowerFeedTable
diff --git a/netbox/extras/views.py b/netbox/extras/views.py
index 8d09e0610..9fb6c6deb 100644
--- a/netbox/extras/views.py
+++ b/netbox/extras/views.py
@@ -10,14 +10,12 @@ from django_tables2 import RequestConfig
from rq import Worker
from dcim.models import DeviceRole, Platform, Region, Site
+from netbox.views import generic
from tenancy.models import Tenant, TenantGroup
from utilities.forms import ConfirmationForm
from utilities.paginator import EnhancedPaginator, get_paginate_count
from utilities.utils import copy_safe_request, shallow_compare_dict
-from utilities.views import (
- BulkDeleteView, BulkEditView, BulkImportView, ObjectView, ObjectDeleteView, ObjectEditView, ObjectListView,
- ContentTypePermissionRequiredMixin,
-)
+from utilities.views import ContentTypePermissionRequiredMixin
from virtualization.models import Cluster, ClusterGroup
from . import filters, forms, tables
from .choices import JobResultStatusChoices
@@ -30,7 +28,7 @@ from .scripts import get_scripts, run_script
# Tags
#
-class TagListView(ObjectListView):
+class TagListView(generic.ObjectListView):
queryset = Tag.objects.annotate(
items=Count('extras_taggeditem_items')
).order_by(*Tag._meta.ordering)
@@ -39,23 +37,23 @@ class TagListView(ObjectListView):
table = tables.TagTable
-class TagEditView(ObjectEditView):
+class TagEditView(generic.ObjectEditView):
queryset = Tag.objects.all()
model_form = forms.TagForm
template_name = 'extras/tag_edit.html'
-class TagDeleteView(ObjectDeleteView):
+class TagDeleteView(generic.ObjectDeleteView):
queryset = Tag.objects.all()
-class TagBulkImportView(BulkImportView):
+class TagBulkImportView(generic.BulkImportView):
queryset = Tag.objects.all()
model_form = forms.TagCSVForm
table = tables.TagTable
-class TagBulkEditView(BulkEditView):
+class TagBulkEditView(generic.BulkEditView):
queryset = Tag.objects.annotate(
items=Count('extras_taggeditem_items')
).order_by(*Tag._meta.ordering)
@@ -63,7 +61,7 @@ class TagBulkEditView(BulkEditView):
form = forms.TagBulkEditForm
-class TagBulkDeleteView(BulkDeleteView):
+class TagBulkDeleteView(generic.BulkDeleteView):
queryset = Tag.objects.annotate(
items=Count('extras_taggeditem_items')
).order_by(*Tag._meta.ordering)
@@ -74,7 +72,7 @@ class TagBulkDeleteView(BulkDeleteView):
# Config contexts
#
-class ConfigContextListView(ObjectListView):
+class ConfigContextListView(generic.ObjectListView):
queryset = ConfigContext.objects.all()
filterset = filters.ConfigContextFilterSet
filterset_form = forms.ConfigContextFilterForm
@@ -82,7 +80,7 @@ class ConfigContextListView(ObjectListView):
action_buttons = ('add',)
-class ConfigContextView(ObjectView):
+class ConfigContextView(generic.ObjectView):
queryset = ConfigContext.objects.all()
def get(self, request, pk):
@@ -116,29 +114,29 @@ class ConfigContextView(ObjectView):
})
-class ConfigContextEditView(ObjectEditView):
+class ConfigContextEditView(generic.ObjectEditView):
queryset = ConfigContext.objects.all()
model_form = forms.ConfigContextForm
template_name = 'extras/configcontext_edit.html'
-class ConfigContextBulkEditView(BulkEditView):
+class ConfigContextBulkEditView(generic.BulkEditView):
queryset = ConfigContext.objects.all()
filterset = filters.ConfigContextFilterSet
table = tables.ConfigContextTable
form = forms.ConfigContextBulkEditForm
-class ConfigContextDeleteView(ObjectDeleteView):
+class ConfigContextDeleteView(generic.ObjectDeleteView):
queryset = ConfigContext.objects.all()
-class ConfigContextBulkDeleteView(BulkDeleteView):
+class ConfigContextBulkDeleteView(generic.BulkDeleteView):
queryset = ConfigContext.objects.all()
table = tables.ConfigContextTable
-class ObjectConfigContextView(ObjectView):
+class ObjectConfigContextView(generic.ObjectView):
base_template = None
def get(self, request, pk):
@@ -172,7 +170,7 @@ class ObjectConfigContextView(ObjectView):
# Change logging
#
-class ObjectChangeListView(ObjectListView):
+class ObjectChangeListView(generic.ObjectListView):
queryset = ObjectChange.objects.all()
filterset = filters.ObjectChangeFilterSet
filterset_form = forms.ObjectChangeFilterForm
@@ -181,7 +179,7 @@ class ObjectChangeListView(ObjectListView):
action_buttons = ('export',)
-class ObjectChangeView(ObjectView):
+class ObjectChangeView(generic.ObjectView):
queryset = ObjectChange.objects.all()
def get(self, request, pk):
@@ -283,7 +281,7 @@ class ObjectChangeLogView(View):
# Image attachments
#
-class ImageAttachmentEditView(ObjectEditView):
+class ImageAttachmentEditView(generic.ObjectEditView):
queryset = ImageAttachment.objects.all()
model_form = forms.ImageAttachmentForm
@@ -298,7 +296,7 @@ class ImageAttachmentEditView(ObjectEditView):
return imageattachment.parent.get_absolute_url()
-class ImageAttachmentDeleteView(ObjectDeleteView):
+class ImageAttachmentDeleteView(generic.ObjectDeleteView):
queryset = ImageAttachment.objects.all()
def get_return_url(self, request, imageattachment):
diff --git a/netbox/ipam/views.py b/netbox/ipam/views.py
index 8fef56edf..d2248e84c 100644
--- a/netbox/ipam/views.py
+++ b/netbox/ipam/views.py
@@ -1,20 +1,14 @@
-import netaddr
-from django.conf import settings
from django.db.models import Count, Prefetch
from django.db.models.expressions import RawSQL
from django.shortcuts import get_object_or_404, redirect, render
from django_tables2 import RequestConfig
from dcim.models import Device, Interface
+from netbox.views import generic
from utilities.paginator import EnhancedPaginator, get_paginate_count
from utilities.utils import get_subquery
-from utilities.views import (
- BulkCreateView, BulkDeleteView, BulkEditView, BulkImportView, ObjectView, ObjectDeleteView, ObjectEditView,
- ObjectListView,
-)
from virtualization.models import VirtualMachine, VMInterface
from . import filters, forms, tables
-from .choices import *
from .constants import *
from .models import Aggregate, IPAddress, Prefix, RIR, Role, RouteTarget, Service, VLAN, VLANGroup, VRF
from .utils import add_available_ipaddresses, add_available_prefixes, add_available_vlans
@@ -24,14 +18,14 @@ from .utils import add_available_ipaddresses, add_available_prefixes, add_availa
# VRFs
#
-class VRFListView(ObjectListView):
+class VRFListView(generic.ObjectListView):
queryset = VRF.objects.all()
filterset = filters.VRFFilterSet
filterset_form = forms.VRFFilterForm
table = tables.VRFTable
-class VRFView(ObjectView):
+class VRFView(generic.ObjectView):
queryset = VRF.objects.all()
def get(self, request, pk):
@@ -56,30 +50,30 @@ class VRFView(ObjectView):
})
-class VRFEditView(ObjectEditView):
+class VRFEditView(generic.ObjectEditView):
queryset = VRF.objects.all()
model_form = forms.VRFForm
template_name = 'ipam/vrf_edit.html'
-class VRFDeleteView(ObjectDeleteView):
+class VRFDeleteView(generic.ObjectDeleteView):
queryset = VRF.objects.all()
-class VRFBulkImportView(BulkImportView):
+class VRFBulkImportView(generic.BulkImportView):
queryset = VRF.objects.all()
model_form = forms.VRFCSVForm
table = tables.VRFTable
-class VRFBulkEditView(BulkEditView):
+class VRFBulkEditView(generic.BulkEditView):
queryset = VRF.objects.prefetch_related('tenant')
filterset = filters.VRFFilterSet
table = tables.VRFTable
form = forms.VRFBulkEditForm
-class VRFBulkDeleteView(BulkDeleteView):
+class VRFBulkDeleteView(generic.BulkDeleteView):
queryset = VRF.objects.prefetch_related('tenant')
filterset = filters.VRFFilterSet
table = tables.VRFTable
@@ -89,14 +83,14 @@ class VRFBulkDeleteView(BulkDeleteView):
# Route targets
#
-class RouteTargetListView(ObjectListView):
+class RouteTargetListView(generic.ObjectListView):
queryset = RouteTarget.objects.all()
filterset = filters.RouteTargetFilterSet
filterset_form = forms.RouteTargetFilterForm
table = tables.RouteTargetTable
-class RouteTargetView(ObjectView):
+class RouteTargetView(generic.ObjectView):
queryset = RouteTarget.objects.all()
def get(self, request, pk):
@@ -118,29 +112,29 @@ class RouteTargetView(ObjectView):
})
-class RouteTargetEditView(ObjectEditView):
+class RouteTargetEditView(generic.ObjectEditView):
queryset = RouteTarget.objects.all()
model_form = forms.RouteTargetForm
-class RouteTargetDeleteView(ObjectDeleteView):
+class RouteTargetDeleteView(generic.ObjectDeleteView):
queryset = RouteTarget.objects.all()
-class RouteTargetBulkImportView(BulkImportView):
+class RouteTargetBulkImportView(generic.BulkImportView):
queryset = RouteTarget.objects.all()
model_form = forms.RouteTargetCSVForm
table = tables.RouteTargetTable
-class RouteTargetBulkEditView(BulkEditView):
+class RouteTargetBulkEditView(generic.BulkEditView):
queryset = RouteTarget.objects.prefetch_related('tenant')
filterset = filters.RouteTargetFilterSet
table = tables.RouteTargetTable
form = forms.RouteTargetBulkEditForm
-class RouteTargetBulkDeleteView(BulkDeleteView):
+class RouteTargetBulkDeleteView(generic.BulkDeleteView):
queryset = RouteTarget.objects.prefetch_related('tenant')
filterset = filters.RouteTargetFilterSet
table = tables.RouteTargetTable
@@ -150,7 +144,7 @@ class RouteTargetBulkDeleteView(BulkDeleteView):
# RIRs
#
-class RIRListView(ObjectListView):
+class RIRListView(generic.ObjectListView):
queryset = RIR.objects.annotate(aggregate_count=Count('aggregates')).order_by(*RIR._meta.ordering)
filterset = filters.RIRFilterSet
filterset_form = forms.RIRFilterForm
@@ -158,22 +152,22 @@ class RIRListView(ObjectListView):
template_name = 'ipam/rir_list.html'
-class RIREditView(ObjectEditView):
+class RIREditView(generic.ObjectEditView):
queryset = RIR.objects.all()
model_form = forms.RIRForm
-class RIRDeleteView(ObjectDeleteView):
+class RIRDeleteView(generic.ObjectDeleteView):
queryset = RIR.objects.all()
-class RIRBulkImportView(BulkImportView):
+class RIRBulkImportView(generic.BulkImportView):
queryset = RIR.objects.all()
model_form = forms.RIRCSVForm
table = tables.RIRTable
-class RIRBulkDeleteView(BulkDeleteView):
+class RIRBulkDeleteView(generic.BulkDeleteView):
queryset = RIR.objects.annotate(aggregate_count=Count('aggregates')).order_by(*RIR._meta.ordering)
filterset = filters.RIRFilterSet
table = tables.RIRTable
@@ -183,7 +177,7 @@ class RIRBulkDeleteView(BulkDeleteView):
# Aggregates
#
-class AggregateListView(ObjectListView):
+class AggregateListView(generic.ObjectListView):
queryset = Aggregate.objects.annotate(
child_count=RawSQL('SELECT COUNT(*) FROM ipam_prefix WHERE ipam_prefix.prefix <<= ipam_aggregate.prefix', ())
).order_by(*Aggregate._meta.ordering)
@@ -209,7 +203,7 @@ class AggregateListView(ObjectListView):
}
-class AggregateView(ObjectView):
+class AggregateView(generic.ObjectView):
queryset = Aggregate.objects.all()
def get(self, request, pk):
@@ -254,30 +248,30 @@ class AggregateView(ObjectView):
})
-class AggregateEditView(ObjectEditView):
+class AggregateEditView(generic.ObjectEditView):
queryset = Aggregate.objects.all()
model_form = forms.AggregateForm
template_name = 'ipam/aggregate_edit.html'
-class AggregateDeleteView(ObjectDeleteView):
+class AggregateDeleteView(generic.ObjectDeleteView):
queryset = Aggregate.objects.all()
-class AggregateBulkImportView(BulkImportView):
+class AggregateBulkImportView(generic.BulkImportView):
queryset = Aggregate.objects.all()
model_form = forms.AggregateCSVForm
table = tables.AggregateTable
-class AggregateBulkEditView(BulkEditView):
+class AggregateBulkEditView(generic.BulkEditView):
queryset = Aggregate.objects.prefetch_related('rir')
filterset = filters.AggregateFilterSet
table = tables.AggregateTable
form = forms.AggregateBulkEditForm
-class AggregateBulkDeleteView(BulkDeleteView):
+class AggregateBulkDeleteView(generic.BulkDeleteView):
queryset = Aggregate.objects.prefetch_related('rir')
filterset = filters.AggregateFilterSet
table = tables.AggregateTable
@@ -287,7 +281,7 @@ class AggregateBulkDeleteView(BulkDeleteView):
# Prefix/VLAN roles
#
-class RoleListView(ObjectListView):
+class RoleListView(generic.ObjectListView):
queryset = Role.objects.annotate(
prefix_count=get_subquery(Prefix, 'role'),
vlan_count=get_subquery(VLAN, 'role')
@@ -295,22 +289,22 @@ class RoleListView(ObjectListView):
table = tables.RoleTable
-class RoleEditView(ObjectEditView):
+class RoleEditView(generic.ObjectEditView):
queryset = Role.objects.all()
model_form = forms.RoleForm
-class RoleDeleteView(ObjectDeleteView):
+class RoleDeleteView(generic.ObjectDeleteView):
queryset = Role.objects.all()
-class RoleBulkImportView(BulkImportView):
+class RoleBulkImportView(generic.BulkImportView):
queryset = Role.objects.all()
model_form = forms.RoleCSVForm
table = tables.RoleTable
-class RoleBulkDeleteView(BulkDeleteView):
+class RoleBulkDeleteView(generic.BulkDeleteView):
queryset = Role.objects.all()
table = tables.RoleTable
@@ -319,7 +313,7 @@ class RoleBulkDeleteView(BulkDeleteView):
# Prefixes
#
-class PrefixListView(ObjectListView):
+class PrefixListView(generic.ObjectListView):
queryset = Prefix.objects.annotate_tree()
filterset = filters.PrefixFilterSet
filterset_form = forms.PrefixFilterForm
@@ -327,7 +321,7 @@ class PrefixListView(ObjectListView):
template_name = 'ipam/prefix_list.html'
-class PrefixView(ObjectView):
+class PrefixView(generic.ObjectView):
queryset = Prefix.objects.prefetch_related('vrf', 'site__region', 'tenant__group', 'vlan__group', 'role')
def get(self, request, pk):
@@ -371,7 +365,7 @@ class PrefixView(ObjectView):
})
-class PrefixPrefixesView(ObjectView):
+class PrefixPrefixesView(generic.ObjectView):
queryset = Prefix.objects.all()
def get(self, request, pk):
@@ -415,7 +409,7 @@ class PrefixPrefixesView(ObjectView):
})
-class PrefixIPAddressesView(ObjectView):
+class PrefixIPAddressesView(generic.ObjectView):
queryset = Prefix.objects.all()
def get(self, request, pk):
@@ -459,31 +453,31 @@ class PrefixIPAddressesView(ObjectView):
})
-class PrefixEditView(ObjectEditView):
+class PrefixEditView(generic.ObjectEditView):
queryset = Prefix.objects.all()
model_form = forms.PrefixForm
template_name = 'ipam/prefix_edit.html'
-class PrefixDeleteView(ObjectDeleteView):
+class PrefixDeleteView(generic.ObjectDeleteView):
queryset = Prefix.objects.all()
template_name = 'ipam/prefix_delete.html'
-class PrefixBulkImportView(BulkImportView):
+class PrefixBulkImportView(generic.BulkImportView):
queryset = Prefix.objects.all()
model_form = forms.PrefixCSVForm
table = tables.PrefixTable
-class PrefixBulkEditView(BulkEditView):
+class PrefixBulkEditView(generic.BulkEditView):
queryset = Prefix.objects.prefetch_related('site', 'vrf__tenant', 'tenant', 'vlan', 'role')
filterset = filters.PrefixFilterSet
table = tables.PrefixTable
form = forms.PrefixBulkEditForm
-class PrefixBulkDeleteView(BulkDeleteView):
+class PrefixBulkDeleteView(generic.BulkDeleteView):
queryset = Prefix.objects.prefetch_related('site', 'vrf__tenant', 'tenant', 'vlan', 'role')
filterset = filters.PrefixFilterSet
table = tables.PrefixTable
@@ -493,14 +487,14 @@ class PrefixBulkDeleteView(BulkDeleteView):
# IP addresses
#
-class IPAddressListView(ObjectListView):
+class IPAddressListView(generic.ObjectListView):
queryset = IPAddress.objects.all()
filterset = filters.IPAddressFilterSet
filterset_form = forms.IPAddressFilterForm
table = tables.IPAddressDetailTable
-class IPAddressView(ObjectView):
+class IPAddressView(generic.ObjectView):
queryset = IPAddress.objects.prefetch_related('vrf__tenant', 'tenant')
def get(self, request, pk):
@@ -553,7 +547,7 @@ class IPAddressView(ObjectView):
})
-class IPAddressEditView(ObjectEditView):
+class IPAddressEditView(generic.ObjectEditView):
queryset = IPAddress.objects.all()
model_form = forms.IPAddressForm
template_name = 'ipam/ipaddress_edit.html'
@@ -575,7 +569,7 @@ class IPAddressEditView(ObjectEditView):
return obj
-class IPAddressAssignView(ObjectView):
+class IPAddressAssignView(generic.ObjectView):
"""
Search for IPAddresses to be assigned to an Interface.
"""
@@ -615,11 +609,11 @@ class IPAddressAssignView(ObjectView):
})
-class IPAddressDeleteView(ObjectDeleteView):
+class IPAddressDeleteView(generic.ObjectDeleteView):
queryset = IPAddress.objects.all()
-class IPAddressBulkCreateView(BulkCreateView):
+class IPAddressBulkCreateView(generic.BulkCreateView):
queryset = IPAddress.objects.all()
form = forms.IPAddressBulkCreateForm
model_form = forms.IPAddressBulkAddForm
@@ -627,20 +621,20 @@ class IPAddressBulkCreateView(BulkCreateView):
template_name = 'ipam/ipaddress_bulk_add.html'
-class IPAddressBulkImportView(BulkImportView):
+class IPAddressBulkImportView(generic.BulkImportView):
queryset = IPAddress.objects.all()
model_form = forms.IPAddressCSVForm
table = tables.IPAddressTable
-class IPAddressBulkEditView(BulkEditView):
+class IPAddressBulkEditView(generic.BulkEditView):
queryset = IPAddress.objects.prefetch_related('vrf__tenant', 'tenant')
filterset = filters.IPAddressFilterSet
table = tables.IPAddressTable
form = forms.IPAddressBulkEditForm
-class IPAddressBulkDeleteView(BulkDeleteView):
+class IPAddressBulkDeleteView(generic.BulkDeleteView):
queryset = IPAddress.objects.prefetch_related('vrf__tenant', 'tenant')
filterset = filters.IPAddressFilterSet
table = tables.IPAddressTable
@@ -650,7 +644,7 @@ class IPAddressBulkDeleteView(BulkDeleteView):
# VLAN groups
#
-class VLANGroupListView(ObjectListView):
+class VLANGroupListView(generic.ObjectListView):
queryset = VLANGroup.objects.annotate(
vlan_count=Count('vlans')
).order_by(*VLANGroup._meta.ordering)
@@ -659,22 +653,22 @@ class VLANGroupListView(ObjectListView):
table = tables.VLANGroupTable
-class VLANGroupEditView(ObjectEditView):
+class VLANGroupEditView(generic.ObjectEditView):
queryset = VLANGroup.objects.all()
model_form = forms.VLANGroupForm
-class VLANGroupDeleteView(ObjectDeleteView):
+class VLANGroupDeleteView(generic.ObjectDeleteView):
queryset = VLANGroup.objects.all()
-class VLANGroupBulkImportView(BulkImportView):
+class VLANGroupBulkImportView(generic.BulkImportView):
queryset = VLANGroup.objects.all()
model_form = forms.VLANGroupCSVForm
table = tables.VLANGroupTable
-class VLANGroupBulkDeleteView(BulkDeleteView):
+class VLANGroupBulkDeleteView(generic.BulkDeleteView):
queryset = VLANGroup.objects.prefetch_related('site').annotate(
vlan_count=Count('vlans')
).order_by(*VLANGroup._meta.ordering)
@@ -682,7 +676,7 @@ class VLANGroupBulkDeleteView(BulkDeleteView):
table = tables.VLANGroupTable
-class VLANGroupVLANsView(ObjectView):
+class VLANGroupVLANsView(generic.ObjectView):
queryset = VLANGroup.objects.all()
def get(self, request, pk):
@@ -725,14 +719,14 @@ class VLANGroupVLANsView(ObjectView):
# VLANs
#
-class VLANListView(ObjectListView):
+class VLANListView(generic.ObjectListView):
queryset = VLAN.objects.all()
filterset = filters.VLANFilterSet
filterset_form = forms.VLANFilterForm
table = tables.VLANDetailTable
-class VLANView(ObjectView):
+class VLANView(generic.ObjectView):
queryset = VLAN.objects.prefetch_related('site__region', 'tenant__group', 'role')
def get(self, request, pk):
@@ -750,7 +744,7 @@ class VLANView(ObjectView):
})
-class VLANInterfacesView(ObjectView):
+class VLANInterfacesView(generic.ObjectView):
queryset = VLAN.objects.all()
def get(self, request, pk):
@@ -771,7 +765,7 @@ class VLANInterfacesView(ObjectView):
})
-class VLANVMInterfacesView(ObjectView):
+class VLANVMInterfacesView(generic.ObjectView):
queryset = VLAN.objects.all()
def get(self, request, pk):
@@ -792,30 +786,30 @@ class VLANVMInterfacesView(ObjectView):
})
-class VLANEditView(ObjectEditView):
+class VLANEditView(generic.ObjectEditView):
queryset = VLAN.objects.all()
model_form = forms.VLANForm
template_name = 'ipam/vlan_edit.html'
-class VLANDeleteView(ObjectDeleteView):
+class VLANDeleteView(generic.ObjectDeleteView):
queryset = VLAN.objects.all()
-class VLANBulkImportView(BulkImportView):
+class VLANBulkImportView(generic.BulkImportView):
queryset = VLAN.objects.all()
model_form = forms.VLANCSVForm
table = tables.VLANTable
-class VLANBulkEditView(BulkEditView):
+class VLANBulkEditView(generic.BulkEditView):
queryset = VLAN.objects.prefetch_related('site', 'group', 'tenant', 'role')
filterset = filters.VLANFilterSet
table = tables.VLANTable
form = forms.VLANBulkEditForm
-class VLANBulkDeleteView(BulkDeleteView):
+class VLANBulkDeleteView(generic.BulkDeleteView):
queryset = VLAN.objects.prefetch_related('site', 'group', 'tenant', 'role')
filterset = filters.VLANFilterSet
table = tables.VLANTable
@@ -825,7 +819,7 @@ class VLANBulkDeleteView(BulkDeleteView):
# Services
#
-class ServiceListView(ObjectListView):
+class ServiceListView(generic.ObjectListView):
queryset = Service.objects.all()
filterset = filters.ServiceFilterSet
filterset_form = forms.ServiceFilterForm
@@ -833,7 +827,7 @@ class ServiceListView(ObjectListView):
action_buttons = ('export',)
-class ServiceView(ObjectView):
+class ServiceView(generic.ObjectView):
queryset = Service.objects.prefetch_related('ipaddresses')
def get(self, request, pk):
@@ -845,7 +839,7 @@ class ServiceView(ObjectView):
})
-class ServiceEditView(ObjectEditView):
+class ServiceEditView(generic.ObjectEditView):
queryset = Service.objects.prefetch_related('ipaddresses')
model_form = forms.ServiceForm
template_name = 'ipam/service_edit.html'
@@ -864,24 +858,24 @@ class ServiceEditView(ObjectEditView):
return obj
-class ServiceBulkImportView(BulkImportView):
+class ServiceBulkImportView(generic.BulkImportView):
queryset = Service.objects.all()
model_form = forms.ServiceCSVForm
table = tables.ServiceTable
-class ServiceDeleteView(ObjectDeleteView):
+class ServiceDeleteView(generic.ObjectDeleteView):
queryset = Service.objects.all()
-class ServiceBulkEditView(BulkEditView):
+class ServiceBulkEditView(generic.BulkEditView):
queryset = Service.objects.prefetch_related('device', 'virtual_machine')
filterset = filters.ServiceFilterSet
table = tables.ServiceTable
form = forms.ServiceBulkEditForm
-class ServiceBulkDeleteView(BulkDeleteView):
+class ServiceBulkDeleteView(generic.BulkDeleteView):
queryset = Service.objects.prefetch_related('device', 'virtual_machine')
filterset = filters.ServiceFilterSet
table = tables.ServiceTable
diff --git a/netbox/netbox/middleware.py b/netbox/netbox/middleware.py
index 566d46eee..1395cbd1f 100644
--- a/netbox/netbox/middleware.py
+++ b/netbox/netbox/middleware.py
@@ -8,8 +8,8 @@ from django.http import Http404, HttpResponseRedirect
from django.urls import reverse
from extras.context_managers import change_logging
+from netbox.views import server_error
from utilities.api import is_api_request, rest_api_server_error
-from utilities.views import server_error
class LoginRequiredMiddleware(object):
diff --git a/netbox/netbox/urls.py b/netbox/netbox/urls.py
index dc24cc9eb..8215afe1c 100644
--- a/netbox/netbox/urls.py
+++ b/netbox/netbox/urls.py
@@ -94,4 +94,4 @@ urlpatterns = [
path('{}'.format(settings.BASE_PATH), include(_patterns))
]
-handler500 = 'utilities.views.server_error'
+handler500 = 'netbox.views.server_error'
diff --git a/netbox/netbox/views.py b/netbox/netbox/views/__init__.py
similarity index 84%
rename from netbox/netbox/views.py
rename to netbox/netbox/views/__init__.py
index 1b3e02b5b..5406e7206 100644
--- a/netbox/netbox/views.py
+++ b/netbox/netbox/views/__init__.py
@@ -1,22 +1,32 @@
+import platform
+import sys
+
from django.conf import settings
from django.contrib.contenttypes.models import ContentType
from django.db.models import F
+from django.http import HttpResponseServerError
from django.shortcuts import render
+from django.template import loader
+from django.template.exceptions import TemplateDoesNotExist
from django.urls import reverse
+from django.views.decorators.csrf import requires_csrf_token
+from django.views.defaults import ERROR_500_TEMPLATE_NAME
from django.views.generic import View
from packaging import version
from circuits.models import Circuit, Provider
-from dcim.models import Cable, ConsolePort, Device, DeviceType, Interface, PowerPanel, PowerFeed, PowerPort, Rack, Site
+from dcim.models import (
+ Cable, ConsolePort, Device, DeviceType, Interface, PowerPanel, PowerFeed, PowerPort, Rack, Site,
+)
from extras.choices import JobResultStatusChoices
from extras.models import ObjectChange, JobResult
from ipam.models import Aggregate, IPAddress, Prefix, VLAN, VRF
+from netbox.constants import SEARCH_MAX_RESULTS, SEARCH_TYPES
+from netbox.forms import SearchForm
from netbox.releases import get_latest_release
from secrets.models import Secret
from tenancy.models import Tenant
from virtualization.models import Cluster, VirtualMachine
-from .constants import SEARCH_MAX_RESULTS, SEARCH_TYPES
-from .forms import SearchForm
class HomeView(View):
@@ -157,3 +167,22 @@ class StaticMediaFailureView(View):
return render(request, 'media_failure.html', {
'filename': request.GET.get('filename')
})
+
+
+@requires_csrf_token
+def server_error(request, template_name=ERROR_500_TEMPLATE_NAME):
+ """
+ Custom 500 handler to provide additional context when rendering 500.html.
+ """
+ try:
+ template = loader.get_template(template_name)
+ except TemplateDoesNotExist:
+ return HttpResponseServerError('
Server Error (500)
', content_type='text/html')
+ type_, error, traceback = sys.exc_info()
+
+ return HttpResponseServerError(template.render({
+ 'error': error,
+ 'exception': str(type_),
+ 'netbox_version': settings.VERSION,
+ 'python_version': platform.python_version(),
+ }))
diff --git a/netbox/netbox/views/generic.py b/netbox/netbox/views/generic.py
new file mode 100644
index 000000000..782d03737
--- /dev/null
+++ b/netbox/netbox/views/generic.py
@@ -0,0 +1,1223 @@
+import logging
+import re
+from copy import deepcopy
+
+from django.contrib import messages
+from django.contrib.contenttypes.models import ContentType
+from django.core.exceptions import FieldDoesNotExist, ObjectDoesNotExist, ValidationError
+from django.db import transaction, IntegrityError
+from django.db.models import ManyToManyField, ProtectedError
+from django.forms import Form, ModelMultipleChoiceField, MultipleHiddenInput, Textarea
+from django.http import HttpResponse
+from django.shortcuts import get_object_or_404, redirect, render
+from django.utils.html import escape
+from django.utils.http import is_safe_url
+from django.utils.safestring import mark_safe
+from django.views.generic import View
+from django_tables2 import RequestConfig
+
+from extras.models import CustomField, ExportTemplate
+from utilities.error_handlers import handle_protectederror
+from utilities.exceptions import AbortTransaction
+from utilities.forms import (
+ BootstrapMixin, BulkRenameForm, ConfirmationForm, CSVDataField, ImportForm, TableConfigForm, restrict_form_fields,
+)
+from utilities.paginator import EnhancedPaginator, get_paginate_count
+from utilities.permissions import get_permission_for_model
+from utilities.utils import csv_format, normalize_querydict, prepare_cloned_fields
+from utilities.views import GetReturnURLMixin, ObjectPermissionRequiredMixin
+
+
+class ObjectView(ObjectPermissionRequiredMixin, View):
+ """
+ Retrieve a single object for display.
+
+ queryset: The base queryset for retrieving the object.
+ """
+ queryset = None
+
+ def get_required_permission(self):
+ return get_permission_for_model(self.queryset.model, 'view')
+
+ def get_template_name(self):
+ """
+ Return self.template_name if set. Otherwise, resolve the template path by model app_label and name.
+ """
+ if hasattr(self, 'template_name'):
+ return self.template_name
+ model_opts = self.queryset.model._meta
+ return f'{model_opts.app_label}/{model_opts.model_name}.html'
+
+ def get(self, request, pk):
+ """
+ Generic GET handler for accessing an object by PK
+ """
+ instance = get_object_or_404(self.queryset, pk=pk)
+
+ return render(request, self.get_template_name(), {
+ 'instance': instance,
+ })
+
+
+class ObjectListView(ObjectPermissionRequiredMixin, View):
+ """
+ List a series of objects.
+
+ queryset: The queryset of objects to display. Note: Prefetching related objects is not necessary, as the
+ table will prefetch objects as needed depending on the columns being displayed.
+ filter: A django-filter FilterSet that is applied to the queryset
+ filter_form: The form used to render filter options
+ table: The django-tables2 Table used to render the objects list
+ template_name: The name of the template
+ """
+ queryset = None
+ filterset = None
+ filterset_form = None
+ table = None
+ template_name = 'utilities/obj_list.html'
+ action_buttons = ('add', 'import', 'export')
+
+ def get_required_permission(self):
+ return get_permission_for_model(self.queryset.model, 'view')
+
+ def queryset_to_yaml(self):
+ """
+ Export the queryset of objects as concatenated YAML documents.
+ """
+ yaml_data = [obj.to_yaml() for obj in self.queryset]
+
+ return '---\n'.join(yaml_data)
+
+ def queryset_to_csv(self):
+ """
+ Export the queryset of objects as comma-separated value (CSV), using the model's to_csv() method.
+ """
+ csv_data = []
+ custom_fields = []
+
+ # Start with the column headers
+ headers = self.queryset.model.csv_headers.copy()
+
+ # Add custom field headers, if any
+ if hasattr(self.queryset.model, 'custom_field_data'):
+ for custom_field in CustomField.objects.get_for_model(self.queryset.model):
+ headers.append(custom_field.name)
+ custom_fields.append(custom_field.name)
+
+ csv_data.append(','.join(headers))
+
+ # Iterate through the queryset appending each object
+ for obj in self.queryset:
+ data = obj.to_csv()
+
+ for custom_field in custom_fields:
+ data += (obj.cf.get(custom_field, ''),)
+
+ csv_data.append(csv_format(data))
+
+ return '\n'.join(csv_data)
+
+ def get(self, request):
+
+ model = self.queryset.model
+ content_type = ContentType.objects.get_for_model(model)
+
+ if self.filterset:
+ self.queryset = self.filterset(request.GET, self.queryset).qs
+
+ # Check for export template rendering
+ if request.GET.get('export'):
+ et = get_object_or_404(ExportTemplate, content_type=content_type, name=request.GET.get('export'))
+ try:
+ return et.render_to_response(self.queryset)
+ except Exception as e:
+ messages.error(
+ request,
+ "There was an error rendering the selected export template ({}): {}".format(
+ et.name, e
+ )
+ )
+
+ # Check for YAML export support
+ elif 'export' in request.GET and hasattr(model, 'to_yaml'):
+ response = HttpResponse(self.queryset_to_yaml(), content_type='text/yaml')
+ filename = 'netbox_{}.yaml'.format(self.queryset.model._meta.verbose_name_plural)
+ response['Content-Disposition'] = 'attachment; filename="{}"'.format(filename)
+ return response
+
+ # Fall back to built-in CSV formatting if export requested but no template specified
+ elif 'export' in request.GET and hasattr(model, 'to_csv'):
+ response = HttpResponse(self.queryset_to_csv(), content_type='text/csv')
+ filename = 'netbox_{}.csv'.format(self.queryset.model._meta.verbose_name_plural)
+ response['Content-Disposition'] = 'attachment; filename="{}"'.format(filename)
+ return response
+
+ # Compile a dictionary indicating which permissions are available to the current user for this model
+ permissions = {}
+ for action in ('add', 'change', 'delete', 'view'):
+ perm_name = get_permission_for_model(model, action)
+ permissions[action] = request.user.has_perm(perm_name)
+
+ # Construct the objects table
+ table = self.table(self.queryset, user=request.user)
+ if 'pk' in table.base_columns and (permissions['change'] or permissions['delete']):
+ table.columns.show('pk')
+
+ # Apply the request context
+ paginate = {
+ 'paginator_class': EnhancedPaginator,
+ 'per_page': get_paginate_count(request)
+ }
+ RequestConfig(request, paginate).configure(table)
+
+ context = {
+ 'content_type': content_type,
+ 'table': table,
+ 'permissions': permissions,
+ 'action_buttons': self.action_buttons,
+ 'table_config_form': TableConfigForm(table=table),
+ 'filter_form': self.filterset_form(request.GET, label_suffix='') if self.filterset_form else None,
+ }
+ context.update(self.extra_context())
+
+ return render(request, self.template_name, context)
+
+ def extra_context(self):
+ return {}
+
+
+class ObjectEditView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View):
+ """
+ Create or edit a single object.
+
+ queryset: The base queryset for the object being modified
+ model_form: The form used to create or edit the object
+ template_name: The name of the template
+ """
+ queryset = None
+ model_form = None
+ template_name = 'utilities/obj_edit.html'
+
+ def get_required_permission(self):
+ # self._permission_action is set by dispatch() to either "add" or "change" depending on whether
+ # we are modifying an existing object or creating a new one.
+ return get_permission_for_model(self.queryset.model, self._permission_action)
+
+ def get_object(self, kwargs):
+ # Look up an existing object by slug or PK, if provided.
+ if 'slug' in kwargs:
+ return get_object_or_404(self.queryset, slug=kwargs['slug'])
+ elif 'pk' in kwargs:
+ return get_object_or_404(self.queryset, pk=kwargs['pk'])
+ # Otherwise, return a new instance.
+ return self.queryset.model()
+
+ def alter_obj(self, obj, request, url_args, url_kwargs):
+ # Allow views to add extra info to an object before it is processed. For example, a parent object can be defined
+ # given some parameter from the request URL.
+ return obj
+
+ def dispatch(self, request, *args, **kwargs):
+ # Determine required permission based on whether we are editing an existing object
+ self._permission_action = 'change' if kwargs else 'add'
+
+ return super().dispatch(request, *args, **kwargs)
+
+ def get(self, request, *args, **kwargs):
+ obj = self.alter_obj(self.get_object(kwargs), request, args, kwargs)
+
+ initial_data = normalize_querydict(request.GET)
+ form = self.model_form(instance=obj, initial=initial_data)
+ restrict_form_fields(form, request.user)
+
+ return render(request, self.template_name, {
+ 'obj': obj,
+ 'obj_type': self.queryset.model._meta.verbose_name,
+ 'form': form,
+ 'return_url': self.get_return_url(request, obj),
+ })
+
+ def post(self, request, *args, **kwargs):
+ logger = logging.getLogger('netbox.views.ObjectEditView')
+ obj = self.alter_obj(self.get_object(kwargs), request, args, kwargs)
+ form = self.model_form(
+ data=request.POST,
+ files=request.FILES,
+ instance=obj
+ )
+ restrict_form_fields(form, request.user)
+
+ if form.is_valid():
+ logger.debug("Form validation was successful")
+
+ try:
+ with transaction.atomic():
+ object_created = form.instance.pk is None
+ obj = form.save()
+
+ # Check that the new object conforms with any assigned object-level permissions
+ self.queryset.get(pk=obj.pk)
+
+ msg = '{} {}'.format(
+ 'Created' if object_created else 'Modified',
+ self.queryset.model._meta.verbose_name
+ )
+ logger.info(f"{msg} {obj} (PK: {obj.pk})")
+ if hasattr(obj, 'get_absolute_url'):
+ msg = '{} {}'.format(msg, obj.get_absolute_url(), escape(obj))
+ else:
+ msg = '{} {}'.format(msg, escape(obj))
+ messages.success(request, mark_safe(msg))
+
+ if '_addanother' in request.POST:
+
+ # If the object has clone_fields, pre-populate a new instance of the form
+ if hasattr(obj, 'clone_fields'):
+ url = '{}?{}'.format(request.path, prepare_cloned_fields(obj))
+ return redirect(url)
+
+ return redirect(request.get_full_path())
+
+ return_url = form.cleaned_data.get('return_url')
+ if return_url is not None and is_safe_url(url=return_url, allowed_hosts=request.get_host()):
+ return redirect(return_url)
+ else:
+ return redirect(self.get_return_url(request, obj))
+
+ except ObjectDoesNotExist:
+ msg = "Object save failed due to object-level permissions violation"
+ logger.debug(msg)
+ form.add_error(None, msg)
+
+ else:
+ logger.debug("Form validation failed")
+
+ return render(request, self.template_name, {
+ 'obj': obj,
+ 'obj_type': self.queryset.model._meta.verbose_name,
+ 'form': form,
+ 'return_url': self.get_return_url(request, obj),
+ })
+
+
+class ObjectDeleteView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View):
+ """
+ Delete a single object.
+
+ queryset: The base queryset for the object being deleted
+ template_name: The name of the template
+ """
+ queryset = None
+ template_name = 'utilities/obj_delete.html'
+
+ def get_required_permission(self):
+ return get_permission_for_model(self.queryset.model, 'delete')
+
+ def get_object(self, kwargs):
+ # Look up object by slug if one has been provided. Otherwise, use PK.
+ if 'slug' in kwargs:
+ return get_object_or_404(self.queryset, slug=kwargs['slug'])
+ else:
+ return get_object_or_404(self.queryset, pk=kwargs['pk'])
+
+ def get(self, request, **kwargs):
+ obj = self.get_object(kwargs)
+ form = ConfirmationForm(initial=request.GET)
+
+ return render(request, self.template_name, {
+ 'obj': obj,
+ 'form': form,
+ 'obj_type': self.queryset.model._meta.verbose_name,
+ 'return_url': self.get_return_url(request, obj),
+ })
+
+ def post(self, request, **kwargs):
+ logger = logging.getLogger('netbox.views.ObjectDeleteView')
+ obj = self.get_object(kwargs)
+ form = ConfirmationForm(request.POST)
+
+ if form.is_valid():
+ logger.debug("Form validation was successful")
+
+ try:
+ obj.delete()
+ except ProtectedError as e:
+ logger.info("Caught ProtectedError while attempting to delete object")
+ handle_protectederror([obj], request, e)
+ return redirect(obj.get_absolute_url())
+
+ msg = 'Deleted {} {}'.format(self.queryset.model._meta.verbose_name, obj)
+ logger.info(msg)
+ messages.success(request, msg)
+
+ return_url = form.cleaned_data.get('return_url')
+ if return_url is not None and is_safe_url(url=return_url, allowed_hosts=request.get_host()):
+ return redirect(return_url)
+ else:
+ return redirect(self.get_return_url(request, obj))
+
+ else:
+ logger.debug("Form validation failed")
+
+ return render(request, self.template_name, {
+ 'obj': obj,
+ 'form': form,
+ 'obj_type': self.queryset.model._meta.verbose_name,
+ 'return_url': self.get_return_url(request, obj),
+ })
+
+
+class BulkCreateView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View):
+ """
+ Create new objects in bulk.
+
+ queryset: Base queryset for the objects being created
+ form: Form class which provides the `pattern` field
+ model_form: The ModelForm used to create individual objects
+ pattern_target: Name of the field to be evaluated as a pattern (if any)
+ template_name: The name of the template
+ """
+ queryset = None
+ form = None
+ model_form = None
+ pattern_target = ''
+ template_name = None
+
+ def get_required_permission(self):
+ return get_permission_for_model(self.queryset.model, 'add')
+
+ def get(self, request):
+ # Set initial values for visible form fields from query args
+ initial = {}
+ for field in getattr(self.model_form._meta, 'fields', []):
+ if request.GET.get(field):
+ initial[field] = request.GET[field]
+
+ form = self.form()
+ model_form = self.model_form(initial=initial)
+
+ return render(request, self.template_name, {
+ 'obj_type': self.model_form._meta.model._meta.verbose_name,
+ 'form': form,
+ 'model_form': model_form,
+ 'return_url': self.get_return_url(request),
+ })
+
+ def post(self, request):
+ logger = logging.getLogger('netbox.views.BulkCreateView')
+ model = self.queryset.model
+ form = self.form(request.POST)
+ model_form = self.model_form(request.POST)
+
+ if form.is_valid():
+ logger.debug("Form validation was successful")
+ pattern = form.cleaned_data['pattern']
+ new_objs = []
+
+ try:
+ with transaction.atomic():
+
+ # Create objects from the expanded. Abort the transaction on the first validation error.
+ for value in pattern:
+
+ # Reinstantiate the model form each time to avoid overwriting the same instance. Use a mutable
+ # copy of the POST QueryDict so that we can update the target field value.
+ model_form = self.model_form(request.POST.copy())
+ model_form.data[self.pattern_target] = value
+
+ # Validate each new object independently.
+ if model_form.is_valid():
+ obj = model_form.save()
+ logger.debug(f"Created {obj} (PK: {obj.pk})")
+ new_objs.append(obj)
+ else:
+ # Copy any errors on the pattern target field to the pattern form.
+ errors = model_form.errors.as_data()
+ if errors.get(self.pattern_target):
+ form.add_error('pattern', errors[self.pattern_target])
+ # Raise an IntegrityError to break the for loop and abort the transaction.
+ raise IntegrityError()
+
+ # Enforce object-level permissions
+ if self.queryset.filter(pk__in=[obj.pk for obj in new_objs]).count() != len(new_objs):
+ raise ObjectDoesNotExist
+
+ # If we make it to this point, validation has succeeded on all new objects.
+ msg = "Added {} {}".format(len(new_objs), model._meta.verbose_name_plural)
+ logger.info(msg)
+ messages.success(request, msg)
+
+ if '_addanother' in request.POST:
+ return redirect(request.path)
+ return redirect(self.get_return_url(request))
+
+ except IntegrityError:
+ pass
+
+ except ObjectDoesNotExist:
+ msg = "Object creation failed due to object-level permissions violation"
+ logger.debug(msg)
+ form.add_error(None, msg)
+
+ else:
+ logger.debug("Form validation failed")
+
+ return render(request, self.template_name, {
+ 'form': form,
+ 'model_form': model_form,
+ 'obj_type': model._meta.verbose_name,
+ 'return_url': self.get_return_url(request),
+ })
+
+
+class ObjectImportView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View):
+ """
+ Import a single object (YAML or JSON format).
+
+ queryset: Base queryset for the objects being created
+ model_form: The ModelForm used to create individual objects
+ related_object_forms: A dictionary mapping of forms to be used for the creation of related (child) objects
+ template_name: The name of the template
+ """
+ queryset = None
+ model_form = None
+ related_object_forms = dict()
+ template_name = 'utilities/obj_import.html'
+
+ def get_required_permission(self):
+ return get_permission_for_model(self.queryset.model, 'add')
+
+ def get(self, request):
+ form = ImportForm()
+
+ return render(request, self.template_name, {
+ 'form': form,
+ 'obj_type': self.queryset.model._meta.verbose_name,
+ 'return_url': self.get_return_url(request),
+ })
+
+ def post(self, request):
+ logger = logging.getLogger('netbox.views.ObjectImportView')
+ form = ImportForm(request.POST)
+
+ if form.is_valid():
+ logger.debug("Import form validation was successful")
+
+ # Initialize model form
+ data = form.cleaned_data['data']
+ model_form = self.model_form(data)
+ restrict_form_fields(model_form, request.user)
+
+ # Assign default values for any fields which were not specified. We have to do this manually because passing
+ # 'initial=' to the form on initialization merely sets default values for the widgets. Since widgets are not
+ # used for YAML/JSON import, we first bind the imported data normally, then update the form's data with the
+ # applicable field defaults as needed prior to form validation.
+ for field_name, field in model_form.fields.items():
+ if field_name not in data and hasattr(field, 'initial'):
+ model_form.data[field_name] = field.initial
+
+ if model_form.is_valid():
+
+ try:
+ with transaction.atomic():
+
+ # Save the primary object
+ obj = model_form.save()
+
+ # Enforce object-level permissions
+ self.queryset.get(pk=obj.pk)
+
+ logger.debug(f"Created {obj} (PK: {obj.pk})")
+
+ # Iterate through the related object forms (if any), validating and saving each instance.
+ for field_name, related_object_form in self.related_object_forms.items():
+ logger.debug("Processing form for related objects: {related_object_form}")
+
+ related_obj_pks = []
+ for i, rel_obj_data in enumerate(data.get(field_name, list())):
+
+ f = related_object_form(obj, rel_obj_data)
+
+ for subfield_name, field in f.fields.items():
+ if subfield_name not in rel_obj_data and hasattr(field, 'initial'):
+ f.data[subfield_name] = field.initial
+
+ if f.is_valid():
+ related_obj = f.save()
+ related_obj_pks.append(related_obj.pk)
+ else:
+ # Replicate errors on the related object form to the primary form for display
+ for subfield_name, errors in f.errors.items():
+ for err in errors:
+ err_msg = "{}[{}] {}: {}".format(field_name, i, subfield_name, err)
+ model_form.add_error(None, err_msg)
+ raise AbortTransaction()
+
+ # Enforce object-level permissions on related objects
+ model = related_object_form.Meta.model
+ if model.objects.filter(pk__in=related_obj_pks).count() != len(related_obj_pks):
+ raise ObjectDoesNotExist
+
+ except AbortTransaction:
+ pass
+
+ except ObjectDoesNotExist:
+ msg = "Object creation failed due to object-level permissions violation"
+ logger.debug(msg)
+ form.add_error(None, msg)
+
+ if not model_form.errors:
+ logger.info(f"Import object {obj} (PK: {obj.pk})")
+ messages.success(request, mark_safe('Imported object: {}'.format(
+ obj.get_absolute_url(), obj
+ )))
+
+ if '_addanother' in request.POST:
+ return redirect(request.get_full_path())
+
+ return_url = form.cleaned_data.get('return_url')
+ if return_url is not None and is_safe_url(url=return_url, allowed_hosts=request.get_host()):
+ return redirect(return_url)
+ else:
+ return redirect(self.get_return_url(request, obj))
+
+ else:
+ logger.debug("Model form validation failed")
+
+ # Replicate model form errors for display
+ for field, errors in model_form.errors.items():
+ for err in errors:
+ if field == '__all__':
+ form.add_error(None, err)
+ else:
+ form.add_error(None, "{}: {}".format(field, err))
+
+ else:
+ logger.debug("Import form validation failed")
+
+ return render(request, self.template_name, {
+ 'form': form,
+ 'obj_type': self.queryset.model._meta.verbose_name,
+ 'return_url': self.get_return_url(request),
+ })
+
+
+class BulkImportView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View):
+ """
+ Import objects in bulk (CSV format).
+
+ queryset: Base queryset for the model
+ model_form: The form used to create each imported object
+ table: The django-tables2 Table used to render the list of imported objects
+ template_name: The name of the template
+ widget_attrs: A dict of attributes to apply to the import widget (e.g. to require a session key)
+ """
+ queryset = None
+ model_form = None
+ table = None
+ template_name = 'utilities/obj_bulk_import.html'
+ widget_attrs = {}
+
+ def _import_form(self, *args, **kwargs):
+
+ class ImportForm(BootstrapMixin, Form):
+ csv = CSVDataField(
+ from_form=self.model_form,
+ widget=Textarea(attrs=self.widget_attrs)
+ )
+
+ return ImportForm(*args, **kwargs)
+
+ def _save_obj(self, obj_form, request):
+ """
+ Provide a hook to modify the object immediately before saving it (e.g. to encrypt secret data).
+ """
+ return obj_form.save()
+
+ def get_required_permission(self):
+ return get_permission_for_model(self.queryset.model, 'add')
+
+ def get(self, request):
+
+ return render(request, self.template_name, {
+ 'form': self._import_form(),
+ 'fields': self.model_form().fields,
+ 'obj_type': self.model_form._meta.model._meta.verbose_name,
+ 'return_url': self.get_return_url(request),
+ })
+
+ def post(self, request):
+ logger = logging.getLogger('netbox.views.BulkImportView')
+ new_objs = []
+ form = self._import_form(request.POST)
+
+ if form.is_valid():
+ logger.debug("Form validation was successful")
+
+ try:
+ # Iterate through CSV data and bind each row to a new model form instance.
+ with transaction.atomic():
+ headers, records = form.cleaned_data['csv']
+ for row, data in enumerate(records, start=1):
+ obj_form = self.model_form(data, headers=headers)
+ restrict_form_fields(obj_form, request.user)
+
+ if obj_form.is_valid():
+ obj = self._save_obj(obj_form, request)
+ new_objs.append(obj)
+ else:
+ for field, err in obj_form.errors.items():
+ form.add_error('csv', "Row {} {}: {}".format(row, field, err[0]))
+ raise ValidationError("")
+
+ # Enforce object-level permissions
+ if self.queryset.filter(pk__in=[obj.pk for obj in new_objs]).count() != len(new_objs):
+ raise ObjectDoesNotExist
+
+ # Compile a table containing the imported objects
+ obj_table = self.table(new_objs)
+
+ if new_objs:
+ msg = 'Imported {} {}'.format(len(new_objs), new_objs[0]._meta.verbose_name_plural)
+ logger.info(msg)
+ messages.success(request, msg)
+
+ return render(request, "import_success.html", {
+ 'table': obj_table,
+ 'return_url': self.get_return_url(request),
+ })
+
+ except ValidationError:
+ pass
+
+ except ObjectDoesNotExist:
+ msg = "Object import failed due to object-level permissions violation"
+ logger.debug(msg)
+ form.add_error(None, msg)
+
+ else:
+ logger.debug("Form validation failed")
+
+ return render(request, self.template_name, {
+ 'form': form,
+ 'fields': self.model_form().fields,
+ 'obj_type': self.model_form._meta.model._meta.verbose_name,
+ 'return_url': self.get_return_url(request),
+ })
+
+
+class BulkEditView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View):
+ """
+ Edit objects in bulk.
+
+ queryset: Custom queryset to use when retrieving objects (e.g. to select related objects)
+ filter: FilterSet to apply when deleting by QuerySet
+ table: The table used to display devices being edited
+ form: The form class used to edit objects in bulk
+ template_name: The name of the template
+ """
+ queryset = None
+ filterset = None
+ table = None
+ form = None
+ template_name = 'utilities/obj_bulk_edit.html'
+
+ def get_required_permission(self):
+ return get_permission_for_model(self.queryset.model, 'change')
+
+ def get(self, request):
+ return redirect(self.get_return_url(request))
+
+ def post(self, request, **kwargs):
+ logger = logging.getLogger('netbox.views.BulkEditView')
+ model = self.queryset.model
+
+ # If we are editing *all* objects in the queryset, replace the PK list with all matched objects.
+ if request.POST.get('_all') and self.filterset is not None:
+ pk_list = [
+ obj.pk for obj in self.filterset(request.GET, self.queryset.only('pk')).qs
+ ]
+ else:
+ pk_list = request.POST.getlist('pk')
+
+ if '_apply' in request.POST:
+ form = self.form(model, request.POST)
+ restrict_form_fields(form, request.user)
+
+ if form.is_valid():
+ logger.debug("Form validation was successful")
+ custom_fields = form.custom_fields if hasattr(form, 'custom_fields') else []
+ standard_fields = [
+ field for field in form.fields if field not in custom_fields + ['pk']
+ ]
+ nullified_fields = request.POST.getlist('_nullify')
+
+ try:
+
+ with transaction.atomic():
+
+ updated_objects = []
+ for obj in self.queryset.filter(pk__in=form.cleaned_data['pk']):
+
+ # Update standard fields. If a field is listed in _nullify, delete its value.
+ for name in standard_fields:
+
+ try:
+ model_field = model._meta.get_field(name)
+ except FieldDoesNotExist:
+ # This form field is used to modify a field rather than set its value directly
+ model_field = None
+
+ # Handle nullification
+ if name in form.nullable_fields and name in nullified_fields:
+ if isinstance(model_field, ManyToManyField):
+ getattr(obj, name).set([])
+ else:
+ setattr(obj, name, None if model_field.null else '')
+
+ # ManyToManyFields
+ elif isinstance(model_field, ManyToManyField):
+ if form.cleaned_data[name]:
+ getattr(obj, name).set(form.cleaned_data[name])
+ # Normal fields
+ elif form.cleaned_data[name] not in (None, ''):
+ setattr(obj, name, form.cleaned_data[name])
+
+ # Update custom fields
+ for name in custom_fields:
+ if name in form.nullable_fields and name in nullified_fields:
+ obj.custom_field_data.pop(name, None)
+ else:
+ obj.custom_field_data[name] = form.cleaned_data[name]
+
+ obj.full_clean()
+ obj.save()
+ updated_objects.append(obj)
+ logger.debug(f"Saved {obj} (PK: {obj.pk})")
+
+ # Add/remove tags
+ if form.cleaned_data.get('add_tags', None):
+ obj.tags.add(*form.cleaned_data['add_tags'])
+ if form.cleaned_data.get('remove_tags', None):
+ obj.tags.remove(*form.cleaned_data['remove_tags'])
+
+ # Enforce object-level permissions
+ if self.queryset.filter(pk__in=[obj.pk for obj in updated_objects]).count() != len(updated_objects):
+ raise ObjectDoesNotExist
+
+ if updated_objects:
+ msg = 'Updated {} {}'.format(len(updated_objects), model._meta.verbose_name_plural)
+ logger.info(msg)
+ messages.success(self.request, msg)
+
+ return redirect(self.get_return_url(request))
+
+ except ValidationError as e:
+ messages.error(self.request, "{} failed validation: {}".format(obj, e))
+
+ except ObjectDoesNotExist:
+ msg = "Object update failed due to object-level permissions violation"
+ logger.debug(msg)
+ form.add_error(None, msg)
+
+ else:
+ logger.debug("Form validation failed")
+
+ else:
+ # Include the PK list as initial data for the form
+ initial_data = {'pk': pk_list}
+
+ # Check for other contextual data needed for the form. We avoid passing all of request.GET because the
+ # filter values will conflict with the bulk edit form fields.
+ # TODO: Find a better way to accomplish this
+ if 'device' in request.GET:
+ initial_data['device'] = request.GET.get('device')
+ elif 'device_type' in request.GET:
+ initial_data['device_type'] = request.GET.get('device_type')
+
+ form = self.form(model, initial=initial_data)
+ restrict_form_fields(form, request.user)
+
+ # Retrieve objects being edited
+ table = self.table(self.queryset.filter(pk__in=pk_list), orderable=False)
+ if not table.rows:
+ messages.warning(request, "No {} were selected.".format(model._meta.verbose_name_plural))
+ return redirect(self.get_return_url(request))
+
+ return render(request, self.template_name, {
+ 'form': form,
+ 'table': table,
+ 'obj_type_plural': model._meta.verbose_name_plural,
+ 'return_url': self.get_return_url(request),
+ })
+
+
+class BulkRenameView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View):
+ """
+ An extendable view for renaming objects in bulk.
+ """
+ queryset = None
+ template_name = 'utilities/obj_bulk_rename.html'
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+
+ # Create a new Form class from BulkRenameForm
+ class _Form(BulkRenameForm):
+ pk = ModelMultipleChoiceField(
+ queryset=self.queryset,
+ widget=MultipleHiddenInput()
+ )
+
+ self.form = _Form
+
+ def get_required_permission(self):
+ return get_permission_for_model(self.queryset.model, 'change')
+
+ def post(self, request):
+ logger = logging.getLogger('netbox.views.BulkRenameView')
+
+ if '_preview' in request.POST or '_apply' in request.POST:
+ form = self.form(request.POST, initial={'pk': request.POST.getlist('pk')})
+ selected_objects = self.queryset.filter(pk__in=form.initial['pk'])
+
+ if form.is_valid():
+ try:
+ with transaction.atomic():
+ renamed_pks = []
+ for obj in selected_objects:
+ find = form.cleaned_data['find']
+ replace = form.cleaned_data['replace']
+ if form.cleaned_data['use_regex']:
+ try:
+ obj.new_name = re.sub(find, replace, obj.name)
+ # Catch regex group reference errors
+ except re.error:
+ obj.new_name = obj.name
+ else:
+ obj.new_name = obj.name.replace(find, replace)
+ renamed_pks.append(obj.pk)
+
+ if '_apply' in request.POST:
+ for obj in selected_objects:
+ obj.name = obj.new_name
+ obj.save()
+
+ # Enforce constrained permissions
+ if self.queryset.filter(pk__in=renamed_pks).count() != len(selected_objects):
+ raise ObjectDoesNotExist
+
+ messages.success(request, "Renamed {} {}".format(
+ len(selected_objects),
+ self.queryset.model._meta.verbose_name_plural
+ ))
+ return redirect(self.get_return_url(request))
+
+ except ObjectDoesNotExist:
+ msg = "Object update failed due to object-level permissions violation"
+ logger.debug(msg)
+ form.add_error(None, msg)
+
+ else:
+ form = self.form(initial={'pk': request.POST.getlist('pk')})
+ selected_objects = self.queryset.filter(pk__in=form.initial['pk'])
+
+ return render(request, self.template_name, {
+ 'form': form,
+ 'obj_type_plural': self.queryset.model._meta.verbose_name_plural,
+ 'selected_objects': selected_objects,
+ 'return_url': self.get_return_url(request),
+ })
+
+
+class BulkDeleteView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View):
+ """
+ Delete objects in bulk.
+
+ queryset: Custom queryset to use when retrieving objects (e.g. to select related objects)
+ filter: FilterSet to apply when deleting by QuerySet
+ table: The table used to display devices being deleted
+ form: The form class used to delete objects in bulk
+ template_name: The name of the template
+ """
+ queryset = None
+ filterset = None
+ table = None
+ form = None
+ template_name = 'utilities/obj_bulk_delete.html'
+
+ def get_required_permission(self):
+ return get_permission_for_model(self.queryset.model, 'delete')
+
+ def get(self, request):
+ return redirect(self.get_return_url(request))
+
+ def post(self, request, **kwargs):
+ logger = logging.getLogger('netbox.views.BulkDeleteView')
+ model = self.queryset.model
+
+ # Are we deleting *all* objects in the queryset or just a selected subset?
+ if request.POST.get('_all'):
+ if self.filterset is not None:
+ pk_list = [obj.pk for obj in self.filterset(request.GET, model.objects.only('pk')).qs]
+ else:
+ pk_list = model.objects.values_list('pk', flat=True)
+ else:
+ pk_list = [int(pk) for pk in request.POST.getlist('pk')]
+
+ form_cls = self.get_form()
+
+ if '_confirm' in request.POST:
+ form = form_cls(request.POST)
+ if form.is_valid():
+ logger.debug("Form validation was successful")
+
+ # Delete objects
+ queryset = self.queryset.filter(pk__in=pk_list)
+ try:
+ deleted_count = queryset.delete()[1][model._meta.label]
+ except ProtectedError as e:
+ logger.info("Caught ProtectedError while attempting to delete objects")
+ handle_protectederror(queryset, request, e)
+ return redirect(self.get_return_url(request))
+
+ msg = 'Deleted {} {}'.format(deleted_count, model._meta.verbose_name_plural)
+ logger.info(msg)
+ messages.success(request, msg)
+ return redirect(self.get_return_url(request))
+
+ else:
+ logger.debug("Form validation failed")
+
+ else:
+ form = form_cls(initial={
+ 'pk': pk_list,
+ 'return_url': self.get_return_url(request),
+ })
+
+ # Retrieve objects being deleted
+ table = self.table(self.queryset.filter(pk__in=pk_list), orderable=False)
+ if not table.rows:
+ messages.warning(request, "No {} were selected for deletion.".format(model._meta.verbose_name_plural))
+ return redirect(self.get_return_url(request))
+
+ return render(request, self.template_name, {
+ 'form': form,
+ 'obj_type_plural': model._meta.verbose_name_plural,
+ 'table': table,
+ 'return_url': self.get_return_url(request),
+ })
+
+ def get_form(self):
+ """
+ Provide a standard bulk delete form if none has been specified for the view
+ """
+ class BulkDeleteForm(ConfirmationForm):
+ pk = ModelMultipleChoiceField(queryset=self.queryset, widget=MultipleHiddenInput)
+
+ if self.form:
+ return self.form
+
+ return BulkDeleteForm
+
+
+#
+# Device/VirtualMachine components
+#
+
+# TODO: Replace with BulkCreateView
+class ComponentCreateView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View):
+ """
+ Add one or more components (e.g. interfaces, console ports, etc.) to a Device or VirtualMachine.
+ """
+ queryset = None
+ form = None
+ model_form = None
+ template_name = None
+
+ def get_required_permission(self):
+ return get_permission_for_model(self.queryset.model, 'add')
+
+ def get(self, request):
+
+ form = self.form(initial=request.GET)
+
+ return render(request, self.template_name, {
+ 'component_type': self.queryset.model._meta.verbose_name,
+ 'form': form,
+ 'return_url': self.get_return_url(request),
+ })
+
+ def post(self, request):
+ logger = logging.getLogger('netbox.views.ComponentCreateView')
+ form = self.form(request.POST, initial=request.GET)
+
+ if form.is_valid():
+
+ new_components = []
+ data = deepcopy(request.POST)
+
+ names = form.cleaned_data['name_pattern']
+ labels = form.cleaned_data.get('label_pattern')
+ for i, name in enumerate(names):
+ label = labels[i] if labels else None
+ # Initialize the individual component form
+ data['name'] = name
+ data['label'] = label
+ if hasattr(form, 'get_iterative_data'):
+ data.update(form.get_iterative_data(i))
+ component_form = self.model_form(data)
+
+ if component_form.is_valid():
+ new_components.append(component_form)
+ else:
+ for field, errors in component_form.errors.as_data().items():
+ # Assign errors on the child form's name/label field to name_pattern/label_pattern on the parent form
+ if field == 'name':
+ field = 'name_pattern'
+ elif field == 'label':
+ field = 'label_pattern'
+ for e in errors:
+ form.add_error(field, '{}: {}'.format(name, ', '.join(e)))
+
+ if not form.errors:
+
+ try:
+
+ with transaction.atomic():
+
+ # Create the new components
+ new_objs = []
+ for component_form in new_components:
+ obj = component_form.save()
+ new_objs.append(obj)
+
+ # Enforce object-level permissions
+ if self.queryset.filter(pk__in=[obj.pk for obj in new_objs]).count() != len(new_objs):
+ raise ObjectDoesNotExist
+
+ messages.success(request, "Added {} {}".format(
+ len(new_components), self.queryset.model._meta.verbose_name_plural
+ ))
+ if '_addanother' in request.POST:
+ return redirect(request.get_full_path())
+ else:
+ return redirect(self.get_return_url(request))
+
+ except ObjectDoesNotExist:
+ msg = "Component creation failed due to object-level permissions violation"
+ logger.debug(msg)
+ form.add_error(None, msg)
+
+ return render(request, self.template_name, {
+ 'component_type': self.queryset.model._meta.verbose_name,
+ 'form': form,
+ 'return_url': self.get_return_url(request),
+ })
+
+
+class BulkComponentCreateView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View):
+ """
+ Add one or more components (e.g. interfaces, console ports, etc.) to a set of Devices or VirtualMachines.
+ """
+ parent_model = None
+ parent_field = None
+ form = None
+ queryset = None
+ model_form = None
+ filterset = None
+ table = None
+ template_name = 'utilities/obj_bulk_add_component.html'
+
+ def get_required_permission(self):
+ return f'dcim.add_{self.queryset.model._meta.model_name}'
+
+ def post(self, request):
+ logger = logging.getLogger('netbox.views.BulkComponentCreateView')
+ parent_model_name = self.parent_model._meta.verbose_name_plural
+ model_name = self.queryset.model._meta.verbose_name_plural
+
+ # Are we editing *all* objects in the queryset or just a selected subset?
+ if request.POST.get('_all') and self.filterset is not None:
+ pk_list = [obj.pk for obj in self.filterset(request.GET, self.parent_model.objects.only('pk')).qs]
+ else:
+ pk_list = [int(pk) for pk in request.POST.getlist('pk')]
+
+ selected_objects = self.parent_model.objects.filter(pk__in=pk_list)
+ if not selected_objects:
+ messages.warning(request, "No {} were selected.".format(self.parent_model._meta.verbose_name_plural))
+ return redirect(self.get_return_url(request))
+ table = self.table(selected_objects)
+
+ if '_create' in request.POST:
+ form = self.form(request.POST)
+
+ if form.is_valid():
+ logger.debug("Form validation was successful")
+
+ new_components = []
+ data = deepcopy(form.cleaned_data)
+
+ try:
+ with transaction.atomic():
+
+ for obj in data['pk']:
+
+ names = data['name_pattern']
+ labels = data['label_pattern'] if 'label_pattern' in data else None
+ for i, name in enumerate(names):
+ label = labels[i] if labels else None
+
+ component_data = {
+ self.parent_field: obj.pk,
+ 'name': name,
+ 'label': label
+ }
+ component_data.update(data)
+ component_form = self.model_form(component_data)
+ if component_form.is_valid():
+ instance = component_form.save()
+ logger.debug(f"Created {instance} on {instance.parent}")
+ new_components.append(instance)
+ else:
+ for field, errors in component_form.errors.as_data().items():
+ for e in errors:
+ form.add_error(field, '{} {}: {}'.format(obj, name, ', '.join(e)))
+
+ # Enforce object-level permissions
+ if self.queryset.filter(pk__in=[obj.pk for obj in new_components]).count() != len(new_components):
+ raise ObjectDoesNotExist
+
+ except IntegrityError:
+ pass
+
+ except ObjectDoesNotExist:
+ msg = "Component creation failed due to object-level permissions violation"
+ logger.debug(msg)
+ form.add_error(None, msg)
+
+ if not form.errors:
+ msg = "Added {} {} to {} {}.".format(
+ len(new_components),
+ model_name,
+ len(form.cleaned_data['pk']),
+ parent_model_name
+ )
+ logger.info(msg)
+ messages.success(request, msg)
+
+ return redirect(self.get_return_url(request))
+
+ else:
+ logger.debug("Form validation failed")
+
+ else:
+ form = self.form(initial={'pk': pk_list})
+
+ return render(request, self.template_name, {
+ 'form': form,
+ 'parent_model_name': parent_model_name,
+ 'model_name': model_name,
+ 'table': table,
+ 'return_url': self.get_return_url(request),
+ })
diff --git a/netbox/secrets/views.py b/netbox/secrets/views.py
index 4442341d7..539cbb160 100644
--- a/netbox/secrets/views.py
+++ b/netbox/secrets/views.py
@@ -7,9 +7,7 @@ from django.shortcuts import get_object_or_404, redirect, render
from django.utils.html import escape
from django.utils.safestring import mark_safe
-from utilities.views import (
- BulkDeleteView, BulkEditView, BulkImportView, ObjectView, ObjectDeleteView, ObjectEditView, ObjectListView,
-)
+from netbox.views import generic
from . import filters, forms, tables
from .models import SecretRole, Secret, SessionKey, UserKey
@@ -28,27 +26,27 @@ def get_session_key(request):
# Secret roles
#
-class SecretRoleListView(ObjectListView):
+class SecretRoleListView(generic.ObjectListView):
queryset = SecretRole.objects.annotate(secret_count=Count('secrets')).order_by(*SecretRole._meta.ordering)
table = tables.SecretRoleTable
-class SecretRoleEditView(ObjectEditView):
+class SecretRoleEditView(generic.ObjectEditView):
queryset = SecretRole.objects.all()
model_form = forms.SecretRoleForm
-class SecretRoleDeleteView(ObjectDeleteView):
+class SecretRoleDeleteView(generic.ObjectDeleteView):
queryset = SecretRole.objects.all()
-class SecretRoleBulkImportView(BulkImportView):
+class SecretRoleBulkImportView(generic.BulkImportView):
queryset = SecretRole.objects.all()
model_form = forms.SecretRoleCSVForm
table = tables.SecretRoleTable
-class SecretRoleBulkDeleteView(BulkDeleteView):
+class SecretRoleBulkDeleteView(generic.BulkDeleteView):
queryset = SecretRole.objects.annotate(secret_count=Count('secrets')).order_by(*SecretRole._meta.ordering)
table = tables.SecretRoleTable
@@ -57,7 +55,7 @@ class SecretRoleBulkDeleteView(BulkDeleteView):
# Secrets
#
-class SecretListView(ObjectListView):
+class SecretListView(generic.ObjectListView):
queryset = Secret.objects.all()
filterset = filters.SecretFilterSet
filterset_form = forms.SecretFilterForm
@@ -65,7 +63,7 @@ class SecretListView(ObjectListView):
action_buttons = ('import', 'export')
-class SecretView(ObjectView):
+class SecretView(generic.ObjectView):
queryset = Secret.objects.all()
def get(self, request, pk):
@@ -77,7 +75,7 @@ class SecretView(ObjectView):
})
-class SecretEditView(ObjectEditView):
+class SecretEditView(generic.ObjectEditView):
queryset = Secret.objects.all()
model_form = forms.SecretForm
template_name = 'secrets/secret_edit.html'
@@ -146,11 +144,11 @@ class SecretEditView(ObjectEditView):
})
-class SecretDeleteView(ObjectDeleteView):
+class SecretDeleteView(generic.ObjectDeleteView):
queryset = Secret.objects.all()
-class SecretBulkImportView(BulkImportView):
+class SecretBulkImportView(generic.BulkImportView):
queryset = Secret.objects.all()
model_form = forms.SecretCSVForm
table = tables.SecretTable
@@ -197,14 +195,14 @@ class SecretBulkImportView(BulkImportView):
})
-class SecretBulkEditView(BulkEditView):
+class SecretBulkEditView(generic.BulkEditView):
queryset = Secret.objects.prefetch_related('role')
filterset = filters.SecretFilterSet
table = tables.SecretTable
form = forms.SecretBulkEditForm
-class SecretBulkDeleteView(BulkDeleteView):
+class SecretBulkDeleteView(generic.BulkDeleteView):
queryset = Secret.objects.prefetch_related('role')
filterset = filters.SecretFilterSet
table = tables.SecretTable
diff --git a/netbox/tenancy/views.py b/netbox/tenancy/views.py
index 16c801580..87d2fe620 100644
--- a/netbox/tenancy/views.py
+++ b/netbox/tenancy/views.py
@@ -1,12 +1,9 @@
-from django.db.models import Count
from django.shortcuts import get_object_or_404, render
from circuits.models import Circuit
from dcim.models import Site, Rack, Device, RackReservation
from ipam.models import IPAddress, Prefix, VLAN, VRF
-from utilities.views import (
- BulkDeleteView, BulkEditView, BulkImportView, ObjectView, ObjectDeleteView, ObjectEditView, ObjectListView,
-)
+from netbox.views import generic
from virtualization.models import VirtualMachine, Cluster
from . import filters, forms, tables
from .models import Tenant, TenantGroup
@@ -16,7 +13,7 @@ from .models import Tenant, TenantGroup
# Tenant groups
#
-class TenantGroupListView(ObjectListView):
+class TenantGroupListView(generic.ObjectListView):
queryset = TenantGroup.objects.add_related_count(
TenantGroup.objects.all(),
Tenant,
@@ -27,22 +24,22 @@ class TenantGroupListView(ObjectListView):
table = tables.TenantGroupTable
-class TenantGroupEditView(ObjectEditView):
+class TenantGroupEditView(generic.ObjectEditView):
queryset = TenantGroup.objects.all()
model_form = forms.TenantGroupForm
-class TenantGroupDeleteView(ObjectDeleteView):
+class TenantGroupDeleteView(generic.ObjectDeleteView):
queryset = TenantGroup.objects.all()
-class TenantGroupBulkImportView(BulkImportView):
+class TenantGroupBulkImportView(generic.BulkImportView):
queryset = TenantGroup.objects.all()
model_form = forms.TenantGroupCSVForm
table = tables.TenantGroupTable
-class TenantGroupBulkDeleteView(BulkDeleteView):
+class TenantGroupBulkDeleteView(generic.BulkDeleteView):
queryset = TenantGroup.objects.add_related_count(
TenantGroup.objects.all(),
Tenant,
@@ -57,14 +54,14 @@ class TenantGroupBulkDeleteView(BulkDeleteView):
# Tenants
#
-class TenantListView(ObjectListView):
+class TenantListView(generic.ObjectListView):
queryset = Tenant.objects.all()
filterset = filters.TenantFilterSet
filterset_form = forms.TenantFilterForm
table = tables.TenantTable
-class TenantView(ObjectView):
+class TenantView(generic.ObjectView):
queryset = Tenant.objects.prefetch_related('group')
def get(self, request, slug):
@@ -90,30 +87,30 @@ class TenantView(ObjectView):
})
-class TenantEditView(ObjectEditView):
+class TenantEditView(generic.ObjectEditView):
queryset = Tenant.objects.all()
model_form = forms.TenantForm
template_name = 'tenancy/tenant_edit.html'
-class TenantDeleteView(ObjectDeleteView):
+class TenantDeleteView(generic.ObjectDeleteView):
queryset = Tenant.objects.all()
-class TenantBulkImportView(BulkImportView):
+class TenantBulkImportView(generic.BulkImportView):
queryset = Tenant.objects.all()
model_form = forms.TenantCSVForm
table = tables.TenantTable
-class TenantBulkEditView(BulkEditView):
+class TenantBulkEditView(generic.BulkEditView):
queryset = Tenant.objects.prefetch_related('group')
filterset = filters.TenantFilterSet
table = tables.TenantTable
form = forms.TenantBulkEditForm
-class TenantBulkDeleteView(BulkDeleteView):
+class TenantBulkDeleteView(generic.BulkDeleteView):
queryset = Tenant.objects.prefetch_related('group')
filterset = filters.TenantFilterSet
table = tables.TenantTable
diff --git a/netbox/utilities/views.py b/netbox/utilities/views.py
index d328d91e4..c291a3cf2 100644
--- a/netbox/utilities/views.py
+++ b/netbox/utilities/views.py
@@ -1,43 +1,14 @@
-import logging
-import platform
-import re
-import sys
-from copy import deepcopy
-
-from django.conf import settings
-from django.contrib import messages
from django.contrib.auth.mixins import AccessMixin
-from django.contrib.contenttypes.models import ContentType
-from django.core.exceptions import FieldDoesNotExist, ImproperlyConfigured, ObjectDoesNotExist, ValidationError
-from django.db import transaction, IntegrityError
-from django.db.models import ManyToManyField, ProtectedError
-from django.forms import Form, ModelMultipleChoiceField, MultipleHiddenInput, Textarea
-from django.http import HttpResponse, HttpResponseServerError
-from django.shortcuts import get_object_or_404, redirect, render
-from django.template import loader
-from django.template.exceptions import TemplateDoesNotExist
+from django.core.exceptions import ImproperlyConfigured
from django.urls import reverse
from django.urls.exceptions import NoReverseMatch
-from django.utils.html import escape
from django.utils.http import is_safe_url
-from django.utils.safestring import mark_safe
-from django.views.decorators.csrf import requires_csrf_token
-from django.views.defaults import ERROR_500_TEMPLATE_NAME
-from django.views.generic import View
-from django_tables2 import RequestConfig
-from extras.models import CustomField, ExportTemplate
-from utilities.exceptions import AbortTransaction
-from utilities.forms import BootstrapMixin, BulkRenameForm, CSVDataField, TableConfigForm, restrict_form_fields
-from utilities.permissions import get_permission_for_model, resolve_permission
-from utilities.utils import csv_format, normalize_querydict, prepare_cloned_fields
-from .error_handlers import handle_protectederror
-from .forms import ConfirmationForm, ImportForm
-from .paginator import EnhancedPaginator, get_paginate_count
+from .permissions import resolve_permission
#
-# Mixins
+# View Mixins
#
class ContentTypePermissionRequiredMixin(AccessMixin):
@@ -152,1221 +123,3 @@ class GetReturnURLMixin:
# If all else fails, return home. Ideally this should never happen.
return reverse('home')
-
-
-#
-# Generic views
-#
-
-class ObjectView(ObjectPermissionRequiredMixin, View):
- """
- Retrieve a single object for display.
-
- queryset: The base queryset for retrieving the object.
- """
- queryset = None
-
- def get_required_permission(self):
- return get_permission_for_model(self.queryset.model, 'view')
-
- def get_template_name(self):
- """
- Return self.template_name if set. Otherwise, resolve the template path by model app_label and name.
- """
- if hasattr(self, 'template_name'):
- return self.template_name
- model_opts = self.queryset.model._meta
- return f'{model_opts.app_label}/{model_opts.model_name}.html'
-
- def get(self, request, pk):
- """
- Generic GET handler for accessing an object by PK
- """
- instance = get_object_or_404(self.queryset, pk=pk)
-
- return render(request, self.get_template_name(), {
- 'instance': instance,
- })
-
-
-class ObjectListView(ObjectPermissionRequiredMixin, View):
- """
- List a series of objects.
-
- queryset: The queryset of objects to display. Note: Prefetching related objects is not necessary, as the
- table will prefetch objects as needed depending on the columns being displayed.
- filter: A django-filter FilterSet that is applied to the queryset
- filter_form: The form used to render filter options
- table: The django-tables2 Table used to render the objects list
- template_name: The name of the template
- """
- queryset = None
- filterset = None
- filterset_form = None
- table = None
- template_name = 'utilities/obj_list.html'
- action_buttons = ('add', 'import', 'export')
-
- def get_required_permission(self):
- return get_permission_for_model(self.queryset.model, 'view')
-
- def queryset_to_yaml(self):
- """
- Export the queryset of objects as concatenated YAML documents.
- """
- yaml_data = [obj.to_yaml() for obj in self.queryset]
-
- return '---\n'.join(yaml_data)
-
- def queryset_to_csv(self):
- """
- Export the queryset of objects as comma-separated value (CSV), using the model's to_csv() method.
- """
- csv_data = []
- custom_fields = []
-
- # Start with the column headers
- headers = self.queryset.model.csv_headers.copy()
-
- # Add custom field headers, if any
- if hasattr(self.queryset.model, 'custom_field_data'):
- for custom_field in CustomField.objects.get_for_model(self.queryset.model):
- headers.append(custom_field.name)
- custom_fields.append(custom_field.name)
-
- csv_data.append(','.join(headers))
-
- # Iterate through the queryset appending each object
- for obj in self.queryset:
- data = obj.to_csv()
-
- for custom_field in custom_fields:
- data += (obj.cf.get(custom_field, ''),)
-
- csv_data.append(csv_format(data))
-
- return '\n'.join(csv_data)
-
- def get(self, request):
-
- model = self.queryset.model
- content_type = ContentType.objects.get_for_model(model)
-
- if self.filterset:
- self.queryset = self.filterset(request.GET, self.queryset).qs
-
- # Check for export template rendering
- if request.GET.get('export'):
- et = get_object_or_404(ExportTemplate, content_type=content_type, name=request.GET.get('export'))
- try:
- return et.render_to_response(self.queryset)
- except Exception as e:
- messages.error(
- request,
- "There was an error rendering the selected export template ({}): {}".format(
- et.name, e
- )
- )
-
- # Check for YAML export support
- elif 'export' in request.GET and hasattr(model, 'to_yaml'):
- response = HttpResponse(self.queryset_to_yaml(), content_type='text/yaml')
- filename = 'netbox_{}.yaml'.format(self.queryset.model._meta.verbose_name_plural)
- response['Content-Disposition'] = 'attachment; filename="{}"'.format(filename)
- return response
-
- # Fall back to built-in CSV formatting if export requested but no template specified
- elif 'export' in request.GET and hasattr(model, 'to_csv'):
- response = HttpResponse(self.queryset_to_csv(), content_type='text/csv')
- filename = 'netbox_{}.csv'.format(self.queryset.model._meta.verbose_name_plural)
- response['Content-Disposition'] = 'attachment; filename="{}"'.format(filename)
- return response
-
- # Compile a dictionary indicating which permissions are available to the current user for this model
- permissions = {}
- for action in ('add', 'change', 'delete', 'view'):
- perm_name = get_permission_for_model(model, action)
- permissions[action] = request.user.has_perm(perm_name)
-
- # Construct the objects table
- table = self.table(self.queryset, user=request.user)
- if 'pk' in table.base_columns and (permissions['change'] or permissions['delete']):
- table.columns.show('pk')
-
- # Apply the request context
- paginate = {
- 'paginator_class': EnhancedPaginator,
- 'per_page': get_paginate_count(request)
- }
- RequestConfig(request, paginate).configure(table)
-
- context = {
- 'content_type': content_type,
- 'table': table,
- 'permissions': permissions,
- 'action_buttons': self.action_buttons,
- 'table_config_form': TableConfigForm(table=table),
- 'filter_form': self.filterset_form(request.GET, label_suffix='') if self.filterset_form else None,
- }
- context.update(self.extra_context())
-
- return render(request, self.template_name, context)
-
- def extra_context(self):
- return {}
-
-
-class ObjectEditView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View):
- """
- Create or edit a single object.
-
- queryset: The base queryset for the object being modified
- model_form: The form used to create or edit the object
- template_name: The name of the template
- """
- queryset = None
- model_form = None
- template_name = 'utilities/obj_edit.html'
-
- def get_required_permission(self):
- # self._permission_action is set by dispatch() to either "add" or "change" depending on whether
- # we are modifying an existing object or creating a new one.
- return get_permission_for_model(self.queryset.model, self._permission_action)
-
- def get_object(self, kwargs):
- # Look up an existing object by slug or PK, if provided.
- if 'slug' in kwargs:
- return get_object_or_404(self.queryset, slug=kwargs['slug'])
- elif 'pk' in kwargs:
- return get_object_or_404(self.queryset, pk=kwargs['pk'])
- # Otherwise, return a new instance.
- return self.queryset.model()
-
- def alter_obj(self, obj, request, url_args, url_kwargs):
- # Allow views to add extra info to an object before it is processed. For example, a parent object can be defined
- # given some parameter from the request URL.
- return obj
-
- def dispatch(self, request, *args, **kwargs):
- # Determine required permission based on whether we are editing an existing object
- self._permission_action = 'change' if kwargs else 'add'
-
- return super().dispatch(request, *args, **kwargs)
-
- def get(self, request, *args, **kwargs):
- obj = self.alter_obj(self.get_object(kwargs), request, args, kwargs)
-
- initial_data = normalize_querydict(request.GET)
- form = self.model_form(instance=obj, initial=initial_data)
- restrict_form_fields(form, request.user)
-
- return render(request, self.template_name, {
- 'obj': obj,
- 'obj_type': self.queryset.model._meta.verbose_name,
- 'form': form,
- 'return_url': self.get_return_url(request, obj),
- })
-
- def post(self, request, *args, **kwargs):
- logger = logging.getLogger('netbox.views.ObjectEditView')
- obj = self.alter_obj(self.get_object(kwargs), request, args, kwargs)
- form = self.model_form(
- data=request.POST,
- files=request.FILES,
- instance=obj
- )
- restrict_form_fields(form, request.user)
-
- if form.is_valid():
- logger.debug("Form validation was successful")
-
- try:
- with transaction.atomic():
- object_created = form.instance.pk is None
- obj = form.save()
-
- # Check that the new object conforms with any assigned object-level permissions
- self.queryset.get(pk=obj.pk)
-
- msg = '{} {}'.format(
- 'Created' if object_created else 'Modified',
- self.queryset.model._meta.verbose_name
- )
- logger.info(f"{msg} {obj} (PK: {obj.pk})")
- if hasattr(obj, 'get_absolute_url'):
- msg = '{} {}'.format(msg, obj.get_absolute_url(), escape(obj))
- else:
- msg = '{} {}'.format(msg, escape(obj))
- messages.success(request, mark_safe(msg))
-
- if '_addanother' in request.POST:
-
- # If the object has clone_fields, pre-populate a new instance of the form
- if hasattr(obj, 'clone_fields'):
- url = '{}?{}'.format(request.path, prepare_cloned_fields(obj))
- return redirect(url)
-
- return redirect(request.get_full_path())
-
- return_url = form.cleaned_data.get('return_url')
- if return_url is not None and is_safe_url(url=return_url, allowed_hosts=request.get_host()):
- return redirect(return_url)
- else:
- return redirect(self.get_return_url(request, obj))
-
- except ObjectDoesNotExist:
- msg = "Object save failed due to object-level permissions violation"
- logger.debug(msg)
- form.add_error(None, msg)
-
- else:
- logger.debug("Form validation failed")
-
- return render(request, self.template_name, {
- 'obj': obj,
- 'obj_type': self.queryset.model._meta.verbose_name,
- 'form': form,
- 'return_url': self.get_return_url(request, obj),
- })
-
-
-class ObjectDeleteView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View):
- """
- Delete a single object.
-
- queryset: The base queryset for the object being deleted
- template_name: The name of the template
- """
- queryset = None
- template_name = 'utilities/obj_delete.html'
-
- def get_required_permission(self):
- return get_permission_for_model(self.queryset.model, 'delete')
-
- def get_object(self, kwargs):
- # Look up object by slug if one has been provided. Otherwise, use PK.
- if 'slug' in kwargs:
- return get_object_or_404(self.queryset, slug=kwargs['slug'])
- else:
- return get_object_or_404(self.queryset, pk=kwargs['pk'])
-
- def get(self, request, **kwargs):
- obj = self.get_object(kwargs)
- form = ConfirmationForm(initial=request.GET)
-
- return render(request, self.template_name, {
- 'obj': obj,
- 'form': form,
- 'obj_type': self.queryset.model._meta.verbose_name,
- 'return_url': self.get_return_url(request, obj),
- })
-
- def post(self, request, **kwargs):
- logger = logging.getLogger('netbox.views.ObjectDeleteView')
- obj = self.get_object(kwargs)
- form = ConfirmationForm(request.POST)
-
- if form.is_valid():
- logger.debug("Form validation was successful")
-
- try:
- obj.delete()
- except ProtectedError as e:
- logger.info("Caught ProtectedError while attempting to delete object")
- handle_protectederror([obj], request, e)
- return redirect(obj.get_absolute_url())
-
- msg = 'Deleted {} {}'.format(self.queryset.model._meta.verbose_name, obj)
- logger.info(msg)
- messages.success(request, msg)
-
- return_url = form.cleaned_data.get('return_url')
- if return_url is not None and is_safe_url(url=return_url, allowed_hosts=request.get_host()):
- return redirect(return_url)
- else:
- return redirect(self.get_return_url(request, obj))
-
- else:
- logger.debug("Form validation failed")
-
- return render(request, self.template_name, {
- 'obj': obj,
- 'form': form,
- 'obj_type': self.queryset.model._meta.verbose_name,
- 'return_url': self.get_return_url(request, obj),
- })
-
-
-class BulkCreateView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View):
- """
- Create new objects in bulk.
-
- queryset: Base queryset for the objects being created
- form: Form class which provides the `pattern` field
- model_form: The ModelForm used to create individual objects
- pattern_target: Name of the field to be evaluated as a pattern (if any)
- template_name: The name of the template
- """
- queryset = None
- form = None
- model_form = None
- pattern_target = ''
- template_name = None
-
- def get_required_permission(self):
- return get_permission_for_model(self.queryset.model, 'add')
-
- def get(self, request):
- # Set initial values for visible form fields from query args
- initial = {}
- for field in getattr(self.model_form._meta, 'fields', []):
- if request.GET.get(field):
- initial[field] = request.GET[field]
-
- form = self.form()
- model_form = self.model_form(initial=initial)
-
- return render(request, self.template_name, {
- 'obj_type': self.model_form._meta.model._meta.verbose_name,
- 'form': form,
- 'model_form': model_form,
- 'return_url': self.get_return_url(request),
- })
-
- def post(self, request):
- logger = logging.getLogger('netbox.views.BulkCreateView')
- model = self.queryset.model
- form = self.form(request.POST)
- model_form = self.model_form(request.POST)
-
- if form.is_valid():
- logger.debug("Form validation was successful")
- pattern = form.cleaned_data['pattern']
- new_objs = []
-
- try:
- with transaction.atomic():
-
- # Create objects from the expanded. Abort the transaction on the first validation error.
- for value in pattern:
-
- # Reinstantiate the model form each time to avoid overwriting the same instance. Use a mutable
- # copy of the POST QueryDict so that we can update the target field value.
- model_form = self.model_form(request.POST.copy())
- model_form.data[self.pattern_target] = value
-
- # Validate each new object independently.
- if model_form.is_valid():
- obj = model_form.save()
- logger.debug(f"Created {obj} (PK: {obj.pk})")
- new_objs.append(obj)
- else:
- # Copy any errors on the pattern target field to the pattern form.
- errors = model_form.errors.as_data()
- if errors.get(self.pattern_target):
- form.add_error('pattern', errors[self.pattern_target])
- # Raise an IntegrityError to break the for loop and abort the transaction.
- raise IntegrityError()
-
- # Enforce object-level permissions
- if self.queryset.filter(pk__in=[obj.pk for obj in new_objs]).count() != len(new_objs):
- raise ObjectDoesNotExist
-
- # If we make it to this point, validation has succeeded on all new objects.
- msg = "Added {} {}".format(len(new_objs), model._meta.verbose_name_plural)
- logger.info(msg)
- messages.success(request, msg)
-
- if '_addanother' in request.POST:
- return redirect(request.path)
- return redirect(self.get_return_url(request))
-
- except IntegrityError:
- pass
-
- except ObjectDoesNotExist:
- msg = "Object creation failed due to object-level permissions violation"
- logger.debug(msg)
- form.add_error(None, msg)
-
- else:
- logger.debug("Form validation failed")
-
- return render(request, self.template_name, {
- 'form': form,
- 'model_form': model_form,
- 'obj_type': model._meta.verbose_name,
- 'return_url': self.get_return_url(request),
- })
-
-
-class ObjectImportView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View):
- """
- Import a single object (YAML or JSON format).
-
- queryset: Base queryset for the objects being created
- model_form: The ModelForm used to create individual objects
- related_object_forms: A dictionary mapping of forms to be used for the creation of related (child) objects
- template_name: The name of the template
- """
- queryset = None
- model_form = None
- related_object_forms = dict()
- template_name = 'utilities/obj_import.html'
-
- def get_required_permission(self):
- return get_permission_for_model(self.queryset.model, 'add')
-
- def get(self, request):
- form = ImportForm()
-
- return render(request, self.template_name, {
- 'form': form,
- 'obj_type': self.queryset.model._meta.verbose_name,
- 'return_url': self.get_return_url(request),
- })
-
- def post(self, request):
- logger = logging.getLogger('netbox.views.ObjectImportView')
- form = ImportForm(request.POST)
-
- if form.is_valid():
- logger.debug("Import form validation was successful")
-
- # Initialize model form
- data = form.cleaned_data['data']
- model_form = self.model_form(data)
- restrict_form_fields(model_form, request.user)
-
- # Assign default values for any fields which were not specified. We have to do this manually because passing
- # 'initial=' to the form on initialization merely sets default values for the widgets. Since widgets are not
- # used for YAML/JSON import, we first bind the imported data normally, then update the form's data with the
- # applicable field defaults as needed prior to form validation.
- for field_name, field in model_form.fields.items():
- if field_name not in data and hasattr(field, 'initial'):
- model_form.data[field_name] = field.initial
-
- if model_form.is_valid():
-
- try:
- with transaction.atomic():
-
- # Save the primary object
- obj = model_form.save()
-
- # Enforce object-level permissions
- self.queryset.get(pk=obj.pk)
-
- logger.debug(f"Created {obj} (PK: {obj.pk})")
-
- # Iterate through the related object forms (if any), validating and saving each instance.
- for field_name, related_object_form in self.related_object_forms.items():
- logger.debug("Processing form for related objects: {related_object_form}")
-
- related_obj_pks = []
- for i, rel_obj_data in enumerate(data.get(field_name, list())):
-
- f = related_object_form(obj, rel_obj_data)
-
- for subfield_name, field in f.fields.items():
- if subfield_name not in rel_obj_data and hasattr(field, 'initial'):
- f.data[subfield_name] = field.initial
-
- if f.is_valid():
- related_obj = f.save()
- related_obj_pks.append(related_obj.pk)
- else:
- # Replicate errors on the related object form to the primary form for display
- for subfield_name, errors in f.errors.items():
- for err in errors:
- err_msg = "{}[{}] {}: {}".format(field_name, i, subfield_name, err)
- model_form.add_error(None, err_msg)
- raise AbortTransaction()
-
- # Enforce object-level permissions on related objects
- model = related_object_form.Meta.model
- if model.objects.filter(pk__in=related_obj_pks).count() != len(related_obj_pks):
- raise ObjectDoesNotExist
-
- except AbortTransaction:
- pass
-
- except ObjectDoesNotExist:
- msg = "Object creation failed due to object-level permissions violation"
- logger.debug(msg)
- form.add_error(None, msg)
-
- if not model_form.errors:
- logger.info(f"Import object {obj} (PK: {obj.pk})")
- messages.success(request, mark_safe('Imported object: {}'.format(
- obj.get_absolute_url(), obj
- )))
-
- if '_addanother' in request.POST:
- return redirect(request.get_full_path())
-
- return_url = form.cleaned_data.get('return_url')
- if return_url is not None and is_safe_url(url=return_url, allowed_hosts=request.get_host()):
- return redirect(return_url)
- else:
- return redirect(self.get_return_url(request, obj))
-
- else:
- logger.debug("Model form validation failed")
-
- # Replicate model form errors for display
- for field, errors in model_form.errors.items():
- for err in errors:
- if field == '__all__':
- form.add_error(None, err)
- else:
- form.add_error(None, "{}: {}".format(field, err))
-
- else:
- logger.debug("Import form validation failed")
-
- return render(request, self.template_name, {
- 'form': form,
- 'obj_type': self.queryset.model._meta.verbose_name,
- 'return_url': self.get_return_url(request),
- })
-
-
-class BulkImportView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View):
- """
- Import objects in bulk (CSV format).
-
- queryset: Base queryset for the model
- model_form: The form used to create each imported object
- table: The django-tables2 Table used to render the list of imported objects
- template_name: The name of the template
- widget_attrs: A dict of attributes to apply to the import widget (e.g. to require a session key)
- """
- queryset = None
- model_form = None
- table = None
- template_name = 'utilities/obj_bulk_import.html'
- widget_attrs = {}
-
- def _import_form(self, *args, **kwargs):
-
- class ImportForm(BootstrapMixin, Form):
- csv = CSVDataField(
- from_form=self.model_form,
- widget=Textarea(attrs=self.widget_attrs)
- )
-
- return ImportForm(*args, **kwargs)
-
- def _save_obj(self, obj_form, request):
- """
- Provide a hook to modify the object immediately before saving it (e.g. to encrypt secret data).
- """
- return obj_form.save()
-
- def get_required_permission(self):
- return get_permission_for_model(self.queryset.model, 'add')
-
- def get(self, request):
-
- return render(request, self.template_name, {
- 'form': self._import_form(),
- 'fields': self.model_form().fields,
- 'obj_type': self.model_form._meta.model._meta.verbose_name,
- 'return_url': self.get_return_url(request),
- })
-
- def post(self, request):
- logger = logging.getLogger('netbox.views.BulkImportView')
- new_objs = []
- form = self._import_form(request.POST)
-
- if form.is_valid():
- logger.debug("Form validation was successful")
-
- try:
- # Iterate through CSV data and bind each row to a new model form instance.
- with transaction.atomic():
- headers, records = form.cleaned_data['csv']
- for row, data in enumerate(records, start=1):
- obj_form = self.model_form(data, headers=headers)
- restrict_form_fields(obj_form, request.user)
-
- if obj_form.is_valid():
- obj = self._save_obj(obj_form, request)
- new_objs.append(obj)
- else:
- for field, err in obj_form.errors.items():
- form.add_error('csv', "Row {} {}: {}".format(row, field, err[0]))
- raise ValidationError("")
-
- # Enforce object-level permissions
- if self.queryset.filter(pk__in=[obj.pk for obj in new_objs]).count() != len(new_objs):
- raise ObjectDoesNotExist
-
- # Compile a table containing the imported objects
- obj_table = self.table(new_objs)
-
- if new_objs:
- msg = 'Imported {} {}'.format(len(new_objs), new_objs[0]._meta.verbose_name_plural)
- logger.info(msg)
- messages.success(request, msg)
-
- return render(request, "import_success.html", {
- 'table': obj_table,
- 'return_url': self.get_return_url(request),
- })
-
- except ValidationError:
- pass
-
- except ObjectDoesNotExist:
- msg = "Object import failed due to object-level permissions violation"
- logger.debug(msg)
- form.add_error(None, msg)
-
- else:
- logger.debug("Form validation failed")
-
- return render(request, self.template_name, {
- 'form': form,
- 'fields': self.model_form().fields,
- 'obj_type': self.model_form._meta.model._meta.verbose_name,
- 'return_url': self.get_return_url(request),
- })
-
-
-class BulkEditView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View):
- """
- Edit objects in bulk.
-
- queryset: Custom queryset to use when retrieving objects (e.g. to select related objects)
- filter: FilterSet to apply when deleting by QuerySet
- table: The table used to display devices being edited
- form: The form class used to edit objects in bulk
- template_name: The name of the template
- """
- queryset = None
- filterset = None
- table = None
- form = None
- template_name = 'utilities/obj_bulk_edit.html'
-
- def get_required_permission(self):
- return get_permission_for_model(self.queryset.model, 'change')
-
- def get(self, request):
- return redirect(self.get_return_url(request))
-
- def post(self, request, **kwargs):
- logger = logging.getLogger('netbox.views.BulkEditView')
- model = self.queryset.model
-
- # If we are editing *all* objects in the queryset, replace the PK list with all matched objects.
- if request.POST.get('_all') and self.filterset is not None:
- pk_list = [
- obj.pk for obj in self.filterset(request.GET, self.queryset.only('pk')).qs
- ]
- else:
- pk_list = request.POST.getlist('pk')
-
- if '_apply' in request.POST:
- form = self.form(model, request.POST)
- restrict_form_fields(form, request.user)
-
- if form.is_valid():
- logger.debug("Form validation was successful")
- custom_fields = form.custom_fields if hasattr(form, 'custom_fields') else []
- standard_fields = [
- field for field in form.fields if field not in custom_fields + ['pk']
- ]
- nullified_fields = request.POST.getlist('_nullify')
-
- try:
-
- with transaction.atomic():
-
- updated_objects = []
- for obj in self.queryset.filter(pk__in=form.cleaned_data['pk']):
-
- # Update standard fields. If a field is listed in _nullify, delete its value.
- for name in standard_fields:
-
- try:
- model_field = model._meta.get_field(name)
- except FieldDoesNotExist:
- # This form field is used to modify a field rather than set its value directly
- model_field = None
-
- # Handle nullification
- if name in form.nullable_fields and name in nullified_fields:
- if isinstance(model_field, ManyToManyField):
- getattr(obj, name).set([])
- else:
- setattr(obj, name, None if model_field.null else '')
-
- # ManyToManyFields
- elif isinstance(model_field, ManyToManyField):
- if form.cleaned_data[name]:
- getattr(obj, name).set(form.cleaned_data[name])
- # Normal fields
- elif form.cleaned_data[name] not in (None, ''):
- setattr(obj, name, form.cleaned_data[name])
-
- # Update custom fields
- for name in custom_fields:
- if name in form.nullable_fields and name in nullified_fields:
- obj.custom_field_data.pop(name, None)
- else:
- obj.custom_field_data[name] = form.cleaned_data[name]
-
- obj.full_clean()
- obj.save()
- updated_objects.append(obj)
- logger.debug(f"Saved {obj} (PK: {obj.pk})")
-
- # Add/remove tags
- if form.cleaned_data.get('add_tags', None):
- obj.tags.add(*form.cleaned_data['add_tags'])
- if form.cleaned_data.get('remove_tags', None):
- obj.tags.remove(*form.cleaned_data['remove_tags'])
-
- # Enforce object-level permissions
- if self.queryset.filter(pk__in=[obj.pk for obj in updated_objects]).count() != len(updated_objects):
- raise ObjectDoesNotExist
-
- if updated_objects:
- msg = 'Updated {} {}'.format(len(updated_objects), model._meta.verbose_name_plural)
- logger.info(msg)
- messages.success(self.request, msg)
-
- return redirect(self.get_return_url(request))
-
- except ValidationError as e:
- messages.error(self.request, "{} failed validation: {}".format(obj, e))
-
- except ObjectDoesNotExist:
- msg = "Object update failed due to object-level permissions violation"
- logger.debug(msg)
- form.add_error(None, msg)
-
- else:
- logger.debug("Form validation failed")
-
- else:
- # Include the PK list as initial data for the form
- initial_data = {'pk': pk_list}
-
- # Check for other contextual data needed for the form. We avoid passing all of request.GET because the
- # filter values will conflict with the bulk edit form fields.
- # TODO: Find a better way to accomplish this
- if 'device' in request.GET:
- initial_data['device'] = request.GET.get('device')
- elif 'device_type' in request.GET:
- initial_data['device_type'] = request.GET.get('device_type')
-
- form = self.form(model, initial=initial_data)
- restrict_form_fields(form, request.user)
-
- # Retrieve objects being edited
- table = self.table(self.queryset.filter(pk__in=pk_list), orderable=False)
- if not table.rows:
- messages.warning(request, "No {} were selected.".format(model._meta.verbose_name_plural))
- return redirect(self.get_return_url(request))
-
- return render(request, self.template_name, {
- 'form': form,
- 'table': table,
- 'obj_type_plural': model._meta.verbose_name_plural,
- 'return_url': self.get_return_url(request),
- })
-
-
-class BulkRenameView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View):
- """
- An extendable view for renaming objects in bulk.
- """
- queryset = None
- template_name = 'utilities/obj_bulk_rename.html'
-
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
-
- # Create a new Form class from BulkRenameForm
- class _Form(BulkRenameForm):
- pk = ModelMultipleChoiceField(
- queryset=self.queryset,
- widget=MultipleHiddenInput()
- )
-
- self.form = _Form
-
- def get_required_permission(self):
- return get_permission_for_model(self.queryset.model, 'change')
-
- def post(self, request):
- logger = logging.getLogger('netbox.views.BulkRenameView')
-
- if '_preview' in request.POST or '_apply' in request.POST:
- form = self.form(request.POST, initial={'pk': request.POST.getlist('pk')})
- selected_objects = self.queryset.filter(pk__in=form.initial['pk'])
-
- if form.is_valid():
- try:
- with transaction.atomic():
- renamed_pks = []
- for obj in selected_objects:
- find = form.cleaned_data['find']
- replace = form.cleaned_data['replace']
- if form.cleaned_data['use_regex']:
- try:
- obj.new_name = re.sub(find, replace, obj.name)
- # Catch regex group reference errors
- except re.error:
- obj.new_name = obj.name
- else:
- obj.new_name = obj.name.replace(find, replace)
- renamed_pks.append(obj.pk)
-
- if '_apply' in request.POST:
- for obj in selected_objects:
- obj.name = obj.new_name
- obj.save()
-
- # Enforce constrained permissions
- if self.queryset.filter(pk__in=renamed_pks).count() != len(selected_objects):
- raise ObjectDoesNotExist
-
- messages.success(request, "Renamed {} {}".format(
- len(selected_objects),
- self.queryset.model._meta.verbose_name_plural
- ))
- return redirect(self.get_return_url(request))
-
- except ObjectDoesNotExist:
- msg = "Object update failed due to object-level permissions violation"
- logger.debug(msg)
- form.add_error(None, msg)
-
- else:
- form = self.form(initial={'pk': request.POST.getlist('pk')})
- selected_objects = self.queryset.filter(pk__in=form.initial['pk'])
-
- return render(request, self.template_name, {
- 'form': form,
- 'obj_type_plural': self.queryset.model._meta.verbose_name_plural,
- 'selected_objects': selected_objects,
- 'return_url': self.get_return_url(request),
- })
-
-
-class BulkDeleteView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View):
- """
- Delete objects in bulk.
-
- queryset: Custom queryset to use when retrieving objects (e.g. to select related objects)
- filter: FilterSet to apply when deleting by QuerySet
- table: The table used to display devices being deleted
- form: The form class used to delete objects in bulk
- template_name: The name of the template
- """
- queryset = None
- filterset = None
- table = None
- form = None
- template_name = 'utilities/obj_bulk_delete.html'
-
- def get_required_permission(self):
- return get_permission_for_model(self.queryset.model, 'delete')
-
- def get(self, request):
- return redirect(self.get_return_url(request))
-
- def post(self, request, **kwargs):
- logger = logging.getLogger('netbox.views.BulkDeleteView')
- model = self.queryset.model
-
- # Are we deleting *all* objects in the queryset or just a selected subset?
- if request.POST.get('_all'):
- if self.filterset is not None:
- pk_list = [obj.pk for obj in self.filterset(request.GET, model.objects.only('pk')).qs]
- else:
- pk_list = model.objects.values_list('pk', flat=True)
- else:
- pk_list = [int(pk) for pk in request.POST.getlist('pk')]
-
- form_cls = self.get_form()
-
- if '_confirm' in request.POST:
- form = form_cls(request.POST)
- if form.is_valid():
- logger.debug("Form validation was successful")
-
- # Delete objects
- queryset = self.queryset.filter(pk__in=pk_list)
- try:
- deleted_count = queryset.delete()[1][model._meta.label]
- except ProtectedError as e:
- logger.info("Caught ProtectedError while attempting to delete objects")
- handle_protectederror(queryset, request, e)
- return redirect(self.get_return_url(request))
-
- msg = 'Deleted {} {}'.format(deleted_count, model._meta.verbose_name_plural)
- logger.info(msg)
- messages.success(request, msg)
- return redirect(self.get_return_url(request))
-
- else:
- logger.debug("Form validation failed")
-
- else:
- form = form_cls(initial={
- 'pk': pk_list,
- 'return_url': self.get_return_url(request),
- })
-
- # Retrieve objects being deleted
- table = self.table(self.queryset.filter(pk__in=pk_list), orderable=False)
- if not table.rows:
- messages.warning(request, "No {} were selected for deletion.".format(model._meta.verbose_name_plural))
- return redirect(self.get_return_url(request))
-
- return render(request, self.template_name, {
- 'form': form,
- 'obj_type_plural': model._meta.verbose_name_plural,
- 'table': table,
- 'return_url': self.get_return_url(request),
- })
-
- def get_form(self):
- """
- Provide a standard bulk delete form if none has been specified for the view
- """
- class BulkDeleteForm(ConfirmationForm):
- pk = ModelMultipleChoiceField(queryset=self.queryset, widget=MultipleHiddenInput)
-
- if self.form:
- return self.form
-
- return BulkDeleteForm
-
-
-#
-# Device/VirtualMachine components
-#
-
-# TODO: Replace with BulkCreateView
-class ComponentCreateView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View):
- """
- Add one or more components (e.g. interfaces, console ports, etc.) to a Device or VirtualMachine.
- """
- queryset = None
- form = None
- model_form = None
- template_name = None
-
- def get_required_permission(self):
- return get_permission_for_model(self.queryset.model, 'add')
-
- def get(self, request):
-
- form = self.form(initial=request.GET)
-
- return render(request, self.template_name, {
- 'component_type': self.queryset.model._meta.verbose_name,
- 'form': form,
- 'return_url': self.get_return_url(request),
- })
-
- def post(self, request):
- logger = logging.getLogger('netbox.views.ComponentCreateView')
- form = self.form(request.POST, initial=request.GET)
-
- if form.is_valid():
-
- new_components = []
- data = deepcopy(request.POST)
-
- names = form.cleaned_data['name_pattern']
- labels = form.cleaned_data.get('label_pattern')
- for i, name in enumerate(names):
- label = labels[i] if labels else None
- # Initialize the individual component form
- data['name'] = name
- data['label'] = label
- if hasattr(form, 'get_iterative_data'):
- data.update(form.get_iterative_data(i))
- component_form = self.model_form(data)
-
- if component_form.is_valid():
- new_components.append(component_form)
- else:
- for field, errors in component_form.errors.as_data().items():
- # Assign errors on the child form's name/label field to name_pattern/label_pattern on the parent form
- if field == 'name':
- field = 'name_pattern'
- elif field == 'label':
- field = 'label_pattern'
- for e in errors:
- form.add_error(field, '{}: {}'.format(name, ', '.join(e)))
-
- if not form.errors:
-
- try:
-
- with transaction.atomic():
-
- # Create the new components
- new_objs = []
- for component_form in new_components:
- obj = component_form.save()
- new_objs.append(obj)
-
- # Enforce object-level permissions
- if self.queryset.filter(pk__in=[obj.pk for obj in new_objs]).count() != len(new_objs):
- raise ObjectDoesNotExist
-
- messages.success(request, "Added {} {}".format(
- len(new_components), self.queryset.model._meta.verbose_name_plural
- ))
- if '_addanother' in request.POST:
- return redirect(request.get_full_path())
- else:
- return redirect(self.get_return_url(request))
-
- except ObjectDoesNotExist:
- msg = "Component creation failed due to object-level permissions violation"
- logger.debug(msg)
- form.add_error(None, msg)
-
- return render(request, self.template_name, {
- 'component_type': self.queryset.model._meta.verbose_name,
- 'form': form,
- 'return_url': self.get_return_url(request),
- })
-
-
-class BulkComponentCreateView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View):
- """
- Add one or more components (e.g. interfaces, console ports, etc.) to a set of Devices or VirtualMachines.
- """
- parent_model = None
- parent_field = None
- form = None
- queryset = None
- model_form = None
- filterset = None
- table = None
- template_name = 'utilities/obj_bulk_add_component.html'
-
- def get_required_permission(self):
- return f'dcim.add_{self.queryset.model._meta.model_name}'
-
- def post(self, request):
- logger = logging.getLogger('netbox.views.BulkComponentCreateView')
- parent_model_name = self.parent_model._meta.verbose_name_plural
- model_name = self.queryset.model._meta.verbose_name_plural
-
- # Are we editing *all* objects in the queryset or just a selected subset?
- if request.POST.get('_all') and self.filterset is not None:
- pk_list = [obj.pk for obj in self.filterset(request.GET, self.parent_model.objects.only('pk')).qs]
- else:
- pk_list = [int(pk) for pk in request.POST.getlist('pk')]
-
- selected_objects = self.parent_model.objects.filter(pk__in=pk_list)
- if not selected_objects:
- messages.warning(request, "No {} were selected.".format(self.parent_model._meta.verbose_name_plural))
- return redirect(self.get_return_url(request))
- table = self.table(selected_objects)
-
- if '_create' in request.POST:
- form = self.form(request.POST)
-
- if form.is_valid():
- logger.debug("Form validation was successful")
-
- new_components = []
- data = deepcopy(form.cleaned_data)
-
- try:
- with transaction.atomic():
-
- for obj in data['pk']:
-
- names = data['name_pattern']
- labels = data['label_pattern'] if 'label_pattern' in data else None
- for i, name in enumerate(names):
- label = labels[i] if labels else None
-
- component_data = {
- self.parent_field: obj.pk,
- 'name': name,
- 'label': label
- }
- component_data.update(data)
- component_form = self.model_form(component_data)
- if component_form.is_valid():
- instance = component_form.save()
- logger.debug(f"Created {instance} on {instance.parent}")
- new_components.append(instance)
- else:
- for field, errors in component_form.errors.as_data().items():
- for e in errors:
- form.add_error(field, '{} {}: {}'.format(obj, name, ', '.join(e)))
-
- # Enforce object-level permissions
- if self.queryset.filter(pk__in=[obj.pk for obj in new_components]).count() != len(new_components):
- raise ObjectDoesNotExist
-
- except IntegrityError:
- pass
-
- except ObjectDoesNotExist:
- msg = "Component creation failed due to object-level permissions violation"
- logger.debug(msg)
- form.add_error(None, msg)
-
- if not form.errors:
- msg = "Added {} {} to {} {}.".format(
- len(new_components),
- model_name,
- len(form.cleaned_data['pk']),
- parent_model_name
- )
- logger.info(msg)
- messages.success(request, msg)
-
- return redirect(self.get_return_url(request))
-
- else:
- logger.debug("Form validation failed")
-
- else:
- form = self.form(initial={'pk': pk_list})
-
- return render(request, self.template_name, {
- 'form': form,
- 'parent_model_name': parent_model_name,
- 'model_name': model_name,
- 'table': table,
- 'return_url': self.get_return_url(request),
- })
-
-
-@requires_csrf_token
-def server_error(request, template_name=ERROR_500_TEMPLATE_NAME):
- """
- Custom 500 handler to provide additional context when rendering 500.html.
- """
- try:
- template = loader.get_template(template_name)
- except TemplateDoesNotExist:
- return HttpResponseServerError('Server Error (500)
', content_type='text/html')
- type_, error, traceback = sys.exc_info()
-
- return HttpResponseServerError(template.render({
- 'error': error,
- 'exception': str(type_),
- 'netbox_version': settings.VERSION,
- 'python_version': platform.python_version(),
- }))
diff --git a/netbox/virtualization/views.py b/netbox/virtualization/views.py
index 381b59896..fc030dad8 100644
--- a/netbox/virtualization/views.py
+++ b/netbox/virtualization/views.py
@@ -9,12 +9,9 @@ from dcim.tables import DeviceTable
from extras.views import ObjectConfigContextView
from ipam.models import IPAddress, Service
from ipam.tables import InterfaceIPAddressTable, InterfaceVLANTable
+from netbox.views import generic
from secrets.models import Secret
from utilities.utils import get_subquery
-from utilities.views import (
- BulkComponentCreateView, BulkDeleteView, BulkEditView, BulkImportView, BulkRenameView, ComponentCreateView,
- ObjectView, ObjectDeleteView, ObjectEditView, ObjectListView,
-)
from . import filters, forms, tables
from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface
@@ -23,27 +20,27 @@ from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterf
# Cluster types
#
-class ClusterTypeListView(ObjectListView):
+class ClusterTypeListView(generic.ObjectListView):
queryset = ClusterType.objects.annotate(cluster_count=Count('clusters')).order_by(*ClusterType._meta.ordering)
table = tables.ClusterTypeTable
-class ClusterTypeEditView(ObjectEditView):
+class ClusterTypeEditView(generic.ObjectEditView):
queryset = ClusterType.objects.all()
model_form = forms.ClusterTypeForm
-class ClusterTypeDeleteView(ObjectDeleteView):
+class ClusterTypeDeleteView(generic.ObjectDeleteView):
queryset = ClusterType.objects.all()
-class ClusterTypeBulkImportView(BulkImportView):
+class ClusterTypeBulkImportView(generic.BulkImportView):
queryset = ClusterType.objects.all()
model_form = forms.ClusterTypeCSVForm
table = tables.ClusterTypeTable
-class ClusterTypeBulkDeleteView(BulkDeleteView):
+class ClusterTypeBulkDeleteView(generic.BulkDeleteView):
queryset = ClusterType.objects.annotate(cluster_count=Count('clusters')).order_by(*ClusterType._meta.ordering)
table = tables.ClusterTypeTable
@@ -52,27 +49,27 @@ class ClusterTypeBulkDeleteView(BulkDeleteView):
# Cluster groups
#
-class ClusterGroupListView(ObjectListView):
+class ClusterGroupListView(generic.ObjectListView):
queryset = ClusterGroup.objects.annotate(cluster_count=Count('clusters')).order_by(*ClusterGroup._meta.ordering)
table = tables.ClusterGroupTable
-class ClusterGroupEditView(ObjectEditView):
+class ClusterGroupEditView(generic.ObjectEditView):
queryset = ClusterGroup.objects.all()
model_form = forms.ClusterGroupForm
-class ClusterGroupDeleteView(ObjectDeleteView):
+class ClusterGroupDeleteView(generic.ObjectDeleteView):
queryset = ClusterGroup.objects.all()
-class ClusterGroupBulkImportView(BulkImportView):
+class ClusterGroupBulkImportView(generic.BulkImportView):
queryset = ClusterGroup.objects.all()
model_form = forms.ClusterGroupCSVForm
table = tables.ClusterGroupTable
-class ClusterGroupBulkDeleteView(BulkDeleteView):
+class ClusterGroupBulkDeleteView(generic.BulkDeleteView):
queryset = ClusterGroup.objects.annotate(cluster_count=Count('clusters')).order_by(*ClusterGroup._meta.ordering)
table = tables.ClusterGroupTable
@@ -81,7 +78,7 @@ class ClusterGroupBulkDeleteView(BulkDeleteView):
# Clusters
#
-class ClusterListView(ObjectListView):
+class ClusterListView(generic.ObjectListView):
permission_required = 'virtualization.view_cluster'
queryset = Cluster.objects.annotate(
device_count=get_subquery(Device, 'cluster'),
@@ -92,7 +89,7 @@ class ClusterListView(ObjectListView):
filterset_form = forms.ClusterFilterForm
-class ClusterView(ObjectView):
+class ClusterView(generic.ObjectView):
queryset = Cluster.objects.all()
def get(self, request, pk):
@@ -114,36 +111,36 @@ class ClusterView(ObjectView):
})
-class ClusterEditView(ObjectEditView):
+class ClusterEditView(generic.ObjectEditView):
template_name = 'virtualization/cluster_edit.html'
queryset = Cluster.objects.all()
model_form = forms.ClusterForm
-class ClusterDeleteView(ObjectDeleteView):
+class ClusterDeleteView(generic.ObjectDeleteView):
queryset = Cluster.objects.all()
-class ClusterBulkImportView(BulkImportView):
+class ClusterBulkImportView(generic.BulkImportView):
queryset = Cluster.objects.all()
model_form = forms.ClusterCSVForm
table = tables.ClusterTable
-class ClusterBulkEditView(BulkEditView):
+class ClusterBulkEditView(generic.BulkEditView):
queryset = Cluster.objects.prefetch_related('type', 'group', 'site')
filterset = filters.ClusterFilterSet
table = tables.ClusterTable
form = forms.ClusterBulkEditForm
-class ClusterBulkDeleteView(BulkDeleteView):
+class ClusterBulkDeleteView(generic.BulkDeleteView):
queryset = Cluster.objects.prefetch_related('type', 'group', 'site')
filterset = filters.ClusterFilterSet
table = tables.ClusterTable
-class ClusterAddDevicesView(ObjectEditView):
+class ClusterAddDevicesView(generic.ObjectEditView):
queryset = Cluster.objects.all()
form = forms.ClusterAddDevicesForm
template_name = 'virtualization/cluster_add_devices.html'
@@ -184,7 +181,7 @@ class ClusterAddDevicesView(ObjectEditView):
})
-class ClusterRemoveDevicesView(ObjectEditView):
+class ClusterRemoveDevicesView(generic.ObjectEditView):
queryset = Cluster.objects.all()
form = forms.ClusterRemoveDevicesForm
template_name = 'utilities/obj_bulk_remove.html'
@@ -229,7 +226,7 @@ class ClusterRemoveDevicesView(ObjectEditView):
# Virtual machines
#
-class VirtualMachineListView(ObjectListView):
+class VirtualMachineListView(generic.ObjectListView):
queryset = VirtualMachine.objects.all()
filterset = filters.VirtualMachineFilterSet
filterset_form = forms.VirtualMachineFilterForm
@@ -237,7 +234,7 @@ class VirtualMachineListView(ObjectListView):
template_name = 'virtualization/virtualmachine_list.html'
-class VirtualMachineView(ObjectView):
+class VirtualMachineView(generic.ObjectView):
queryset = VirtualMachine.objects.prefetch_related('tenant__group')
def get(self, request, pk):
@@ -277,30 +274,30 @@ class VirtualMachineConfigContextView(ObjectConfigContextView):
base_template = 'virtualization/virtualmachine.html'
-class VirtualMachineEditView(ObjectEditView):
+class VirtualMachineEditView(generic.ObjectEditView):
queryset = VirtualMachine.objects.all()
model_form = forms.VirtualMachineForm
template_name = 'virtualization/virtualmachine_edit.html'
-class VirtualMachineDeleteView(ObjectDeleteView):
+class VirtualMachineDeleteView(generic.ObjectDeleteView):
queryset = VirtualMachine.objects.all()
-class VirtualMachineBulkImportView(BulkImportView):
+class VirtualMachineBulkImportView(generic.BulkImportView):
queryset = VirtualMachine.objects.all()
model_form = forms.VirtualMachineCSVForm
table = tables.VirtualMachineTable
-class VirtualMachineBulkEditView(BulkEditView):
+class VirtualMachineBulkEditView(generic.BulkEditView):
queryset = VirtualMachine.objects.prefetch_related('cluster', 'tenant', 'role')
filterset = filters.VirtualMachineFilterSet
table = tables.VirtualMachineTable
form = forms.VirtualMachineBulkEditForm
-class VirtualMachineBulkDeleteView(BulkDeleteView):
+class VirtualMachineBulkDeleteView(generic.BulkDeleteView):
queryset = VirtualMachine.objects.prefetch_related('cluster', 'tenant', 'role')
filterset = filters.VirtualMachineFilterSet
table = tables.VirtualMachineTable
@@ -310,7 +307,7 @@ class VirtualMachineBulkDeleteView(BulkDeleteView):
# VM interfaces
#
-class VMInterfaceListView(ObjectListView):
+class VMInterfaceListView(generic.ObjectListView):
queryset = VMInterface.objects.all()
filterset = filters.VMInterfaceFilterSet
filterset_form = forms.VMInterfaceFilterForm
@@ -318,7 +315,7 @@ class VMInterfaceListView(ObjectListView):
action_buttons = ('export',)
-class VMInterfaceView(ObjectView):
+class VMInterfaceView(generic.ObjectView):
queryset = VMInterface.objects.all()
def get(self, request, pk):
@@ -353,41 +350,41 @@ class VMInterfaceView(ObjectView):
# TODO: This should not use ComponentCreateView
-class VMInterfaceCreateView(ComponentCreateView):
+class VMInterfaceCreateView(generic.ComponentCreateView):
queryset = VMInterface.objects.all()
form = forms.VMInterfaceCreateForm
model_form = forms.VMInterfaceForm
template_name = 'virtualization/virtualmachine_component_add.html'
-class VMInterfaceEditView(ObjectEditView):
+class VMInterfaceEditView(generic.ObjectEditView):
queryset = VMInterface.objects.all()
model_form = forms.VMInterfaceForm
template_name = 'virtualization/vminterface_edit.html'
-class VMInterfaceDeleteView(ObjectDeleteView):
+class VMInterfaceDeleteView(generic.ObjectDeleteView):
queryset = VMInterface.objects.all()
-class VMInterfaceBulkImportView(BulkImportView):
+class VMInterfaceBulkImportView(generic.BulkImportView):
queryset = VMInterface.objects.all()
model_form = forms.VMInterfaceCSVForm
table = tables.VMInterfaceTable
-class VMInterfaceBulkEditView(BulkEditView):
+class VMInterfaceBulkEditView(generic.BulkEditView):
queryset = VMInterface.objects.all()
table = tables.VMInterfaceTable
form = forms.VMInterfaceBulkEditForm
-class VMInterfaceBulkRenameView(BulkRenameView):
+class VMInterfaceBulkRenameView(generic.BulkRenameView):
queryset = VMInterface.objects.all()
form = forms.VMInterfaceBulkRenameForm
-class VMInterfaceBulkDeleteView(BulkDeleteView):
+class VMInterfaceBulkDeleteView(generic.BulkDeleteView):
queryset = VMInterface.objects.all()
table = tables.VMInterfaceTable
@@ -396,7 +393,7 @@ class VMInterfaceBulkDeleteView(BulkDeleteView):
# Bulk Device component creation
#
-class VirtualMachineBulkAddInterfaceView(BulkComponentCreateView):
+class VirtualMachineBulkAddInterfaceView(generic.BulkComponentCreateView):
parent_model = VirtualMachine
parent_field = 'virtual_machine'
form = forms.VMInterfaceBulkCreateForm