mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-24 09:28:38 -06:00
Merge branch 'develop' into develop-2.10
This commit is contained in:
commit
915cf3e715
@ -1,5 +1,13 @@
|
|||||||
# NetBox v2.9
|
# NetBox v2.9
|
||||||
|
|
||||||
|
## v2.9.11 (FUTURE)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* [#5383](https://github.com/netbox-community/netbox/issues/5383) - Fix setting user password via REST API
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## v2.9.10 (2020-11-24)
|
## v2.9.10 (2020-11-24)
|
||||||
|
|
||||||
### Enhancements
|
### Enhancements
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
from django.db.models import Count, Prefetch
|
from django.db.models import Prefetch
|
||||||
from rest_framework.routers import APIRootView
|
from rest_framework.routers import APIRootView
|
||||||
|
|
||||||
from circuits import filters
|
from circuits import filters
|
||||||
@ -6,6 +6,7 @@ from circuits.models import Provider, CircuitTermination, CircuitType, Circuit
|
|||||||
from dcim.api.views import PathEndpointMixin
|
from dcim.api.views import PathEndpointMixin
|
||||||
from extras.api.views import CustomFieldModelViewSet
|
from extras.api.views import CustomFieldModelViewSet
|
||||||
from netbox.api.views import ModelViewSet
|
from netbox.api.views import ModelViewSet
|
||||||
|
from utilities.utils import get_subquery
|
||||||
from . import serializers
|
from . import serializers
|
||||||
|
|
||||||
|
|
||||||
@ -23,8 +24,8 @@ class CircuitsRootView(APIRootView):
|
|||||||
|
|
||||||
class ProviderViewSet(CustomFieldModelViewSet):
|
class ProviderViewSet(CustomFieldModelViewSet):
|
||||||
queryset = Provider.objects.prefetch_related('tags').annotate(
|
queryset = Provider.objects.prefetch_related('tags').annotate(
|
||||||
circuit_count=Count('circuits')
|
circuit_count=get_subquery(Circuit, 'provider')
|
||||||
).order_by(*Provider._meta.ordering)
|
)
|
||||||
serializer_class = serializers.ProviderSerializer
|
serializer_class = serializers.ProviderSerializer
|
||||||
filterset_class = filters.ProviderFilterSet
|
filterset_class = filters.ProviderFilterSet
|
||||||
|
|
||||||
@ -35,8 +36,8 @@ class ProviderViewSet(CustomFieldModelViewSet):
|
|||||||
|
|
||||||
class CircuitTypeViewSet(ModelViewSet):
|
class CircuitTypeViewSet(ModelViewSet):
|
||||||
queryset = CircuitType.objects.annotate(
|
queryset = CircuitType.objects.annotate(
|
||||||
circuit_count=Count('circuits')
|
circuit_count=get_subquery(Circuit, 'type')
|
||||||
).order_by(*CircuitType._meta.ordering)
|
)
|
||||||
serializer_class = serializers.CircuitTypeSerializer
|
serializer_class = serializers.CircuitTypeSerializer
|
||||||
filterset_class = filters.CircuitTypeFilterSet
|
filterset_class = filters.CircuitTypeFilterSet
|
||||||
|
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.db.models import Count
|
|
||||||
from django.shortcuts import get_object_or_404, redirect, render
|
from django.shortcuts import get_object_or_404, redirect, render
|
||||||
from django_tables2 import RequestConfig
|
from django_tables2 import RequestConfig
|
||||||
|
|
||||||
from netbox.views import generic
|
from netbox.views import generic
|
||||||
from utilities.forms import ConfirmationForm
|
from utilities.forms import ConfirmationForm
|
||||||
from utilities.paginator import EnhancedPaginator, get_paginate_count
|
from utilities.paginator import EnhancedPaginator, get_paginate_count
|
||||||
|
from utilities.utils import get_subquery
|
||||||
from . import filters, forms, tables
|
from . import filters, forms, tables
|
||||||
from .choices import CircuitTerminationSideChoices
|
from .choices import CircuitTerminationSideChoices
|
||||||
from .models import Circuit, CircuitTermination, CircuitType, Provider
|
from .models import Circuit, CircuitTermination, CircuitType, Provider
|
||||||
@ -17,7 +17,9 @@ from .models import Circuit, CircuitTermination, CircuitType, Provider
|
|||||||
#
|
#
|
||||||
|
|
||||||
class ProviderListView(generic.ObjectListView):
|
class ProviderListView(generic.ObjectListView):
|
||||||
queryset = Provider.objects.annotate(count_circuits=Count('circuits')).order_by(*Provider._meta.ordering)
|
queryset = Provider.objects.annotate(
|
||||||
|
count_circuits=get_subquery(Circuit, 'provider')
|
||||||
|
)
|
||||||
filterset = filters.ProviderFilterSet
|
filterset = filters.ProviderFilterSet
|
||||||
filterset_form = forms.ProviderFilterForm
|
filterset_form = forms.ProviderFilterForm
|
||||||
table = tables.ProviderTable
|
table = tables.ProviderTable
|
||||||
@ -64,14 +66,18 @@ class ProviderBulkImportView(generic.BulkImportView):
|
|||||||
|
|
||||||
|
|
||||||
class ProviderBulkEditView(generic.BulkEditView):
|
class ProviderBulkEditView(generic.BulkEditView):
|
||||||
queryset = Provider.objects.annotate(count_circuits=Count('circuits')).order_by(*Provider._meta.ordering)
|
queryset = Provider.objects.annotate(
|
||||||
|
count_circuits=get_subquery(Circuit, 'provider')
|
||||||
|
)
|
||||||
filterset = filters.ProviderFilterSet
|
filterset = filters.ProviderFilterSet
|
||||||
table = tables.ProviderTable
|
table = tables.ProviderTable
|
||||||
form = forms.ProviderBulkEditForm
|
form = forms.ProviderBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
class ProviderBulkDeleteView(generic.BulkDeleteView):
|
class ProviderBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = Provider.objects.annotate(count_circuits=Count('circuits')).order_by(*Provider._meta.ordering)
|
queryset = Provider.objects.annotate(
|
||||||
|
count_circuits=get_subquery(Circuit, 'provider')
|
||||||
|
)
|
||||||
filterset = filters.ProviderFilterSet
|
filterset = filters.ProviderFilterSet
|
||||||
table = tables.ProviderTable
|
table = tables.ProviderTable
|
||||||
|
|
||||||
@ -81,7 +87,9 @@ class ProviderBulkDeleteView(generic.BulkDeleteView):
|
|||||||
#
|
#
|
||||||
|
|
||||||
class CircuitTypeListView(generic.ObjectListView):
|
class CircuitTypeListView(generic.ObjectListView):
|
||||||
queryset = CircuitType.objects.annotate(circuit_count=Count('circuits')).order_by(*CircuitType._meta.ordering)
|
queryset = CircuitType.objects.annotate(
|
||||||
|
circuit_count=get_subquery(Circuit, 'type')
|
||||||
|
)
|
||||||
table = tables.CircuitTypeTable
|
table = tables.CircuitTypeTable
|
||||||
|
|
||||||
|
|
||||||
@ -101,7 +109,9 @@ class CircuitTypeBulkImportView(generic.BulkImportView):
|
|||||||
|
|
||||||
|
|
||||||
class CircuitTypeBulkDeleteView(generic.BulkDeleteView):
|
class CircuitTypeBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = CircuitType.objects.annotate(circuit_count=Count('circuits')).order_by(*CircuitType._meta.ordering)
|
queryset = CircuitType.objects.annotate(
|
||||||
|
circuit_count=get_subquery(Circuit, 'type')
|
||||||
|
)
|
||||||
table = tables.CircuitTypeTable
|
table = tables.CircuitTypeTable
|
||||||
|
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ import socket
|
|||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db.models import Count, F
|
from django.db.models import F
|
||||||
from django.http import HttpResponseForbidden, HttpResponse
|
from django.http import HttpResponseForbidden, HttpResponse
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from drf_yasg import openapi
|
from drf_yasg import openapi
|
||||||
@ -125,7 +125,7 @@ class SiteViewSet(CustomFieldModelViewSet):
|
|||||||
vlan_count=get_subquery(VLAN, 'site'),
|
vlan_count=get_subquery(VLAN, 'site'),
|
||||||
circuit_count=get_subquery(Circuit, 'terminations__site'),
|
circuit_count=get_subquery(Circuit, 'terminations__site'),
|
||||||
virtualmachine_count=get_subquery(VirtualMachine, 'cluster__site'),
|
virtualmachine_count=get_subquery(VirtualMachine, 'cluster__site'),
|
||||||
).order_by(*Site._meta.ordering)
|
)
|
||||||
serializer_class = serializers.SiteSerializer
|
serializer_class = serializers.SiteSerializer
|
||||||
filterset_class = filters.SiteFilterSet
|
filterset_class = filters.SiteFilterSet
|
||||||
|
|
||||||
@ -152,8 +152,8 @@ class RackGroupViewSet(ModelViewSet):
|
|||||||
|
|
||||||
class RackRoleViewSet(ModelViewSet):
|
class RackRoleViewSet(ModelViewSet):
|
||||||
queryset = RackRole.objects.annotate(
|
queryset = RackRole.objects.annotate(
|
||||||
rack_count=Count('racks')
|
rack_count=get_subquery(Rack, 'role')
|
||||||
).order_by(*RackRole._meta.ordering)
|
)
|
||||||
serializer_class = serializers.RackRoleSerializer
|
serializer_class = serializers.RackRoleSerializer
|
||||||
filterset_class = filters.RackRoleFilterSet
|
filterset_class = filters.RackRoleFilterSet
|
||||||
|
|
||||||
@ -168,7 +168,7 @@ class RackViewSet(CustomFieldModelViewSet):
|
|||||||
).annotate(
|
).annotate(
|
||||||
device_count=get_subquery(Device, 'rack'),
|
device_count=get_subquery(Device, 'rack'),
|
||||||
powerfeed_count=get_subquery(PowerFeed, 'rack')
|
powerfeed_count=get_subquery(PowerFeed, 'rack')
|
||||||
).order_by(*Rack._meta.ordering)
|
)
|
||||||
serializer_class = serializers.RackSerializer
|
serializer_class = serializers.RackSerializer
|
||||||
filterset_class = filters.RackFilterSet
|
filterset_class = filters.RackFilterSet
|
||||||
|
|
||||||
@ -243,7 +243,7 @@ class ManufacturerViewSet(ModelViewSet):
|
|||||||
devicetype_count=get_subquery(DeviceType, 'manufacturer'),
|
devicetype_count=get_subquery(DeviceType, 'manufacturer'),
|
||||||
inventoryitem_count=get_subquery(InventoryItem, 'manufacturer'),
|
inventoryitem_count=get_subquery(InventoryItem, 'manufacturer'),
|
||||||
platform_count=get_subquery(Platform, 'manufacturer')
|
platform_count=get_subquery(Platform, 'manufacturer')
|
||||||
).order_by(*Manufacturer._meta.ordering)
|
)
|
||||||
serializer_class = serializers.ManufacturerSerializer
|
serializer_class = serializers.ManufacturerSerializer
|
||||||
filterset_class = filters.ManufacturerFilterSet
|
filterset_class = filters.ManufacturerFilterSet
|
||||||
|
|
||||||
@ -254,8 +254,8 @@ class ManufacturerViewSet(ModelViewSet):
|
|||||||
|
|
||||||
class DeviceTypeViewSet(CustomFieldModelViewSet):
|
class DeviceTypeViewSet(CustomFieldModelViewSet):
|
||||||
queryset = DeviceType.objects.prefetch_related('manufacturer', 'tags').annotate(
|
queryset = DeviceType.objects.prefetch_related('manufacturer', 'tags').annotate(
|
||||||
device_count=Count('instances')
|
device_count=get_subquery(Device, 'device_type')
|
||||||
).order_by(*DeviceType._meta.ordering)
|
)
|
||||||
serializer_class = serializers.DeviceTypeSerializer
|
serializer_class = serializers.DeviceTypeSerializer
|
||||||
filterset_class = filters.DeviceTypeFilterSet
|
filterset_class = filters.DeviceTypeFilterSet
|
||||||
|
|
||||||
@ -320,7 +320,7 @@ class DeviceRoleViewSet(ModelViewSet):
|
|||||||
queryset = DeviceRole.objects.annotate(
|
queryset = DeviceRole.objects.annotate(
|
||||||
device_count=get_subquery(Device, 'device_role'),
|
device_count=get_subquery(Device, 'device_role'),
|
||||||
virtualmachine_count=get_subquery(VirtualMachine, 'role')
|
virtualmachine_count=get_subquery(VirtualMachine, 'role')
|
||||||
).order_by(*DeviceRole._meta.ordering)
|
)
|
||||||
serializer_class = serializers.DeviceRoleSerializer
|
serializer_class = serializers.DeviceRoleSerializer
|
||||||
filterset_class = filters.DeviceRoleFilterSet
|
filterset_class = filters.DeviceRoleFilterSet
|
||||||
|
|
||||||
@ -333,7 +333,7 @@ class PlatformViewSet(ModelViewSet):
|
|||||||
queryset = Platform.objects.annotate(
|
queryset = Platform.objects.annotate(
|
||||||
device_count=get_subquery(Device, 'platform'),
|
device_count=get_subquery(Device, 'platform'),
|
||||||
virtualmachine_count=get_subquery(VirtualMachine, 'platform')
|
virtualmachine_count=get_subquery(VirtualMachine, 'platform')
|
||||||
).order_by(*Platform._meta.ordering)
|
)
|
||||||
serializer_class = serializers.PlatformSerializer
|
serializer_class = serializers.PlatformSerializer
|
||||||
filterset_class = filters.PlatformFilterSet
|
filterset_class = filters.PlatformFilterSet
|
||||||
|
|
||||||
@ -596,8 +596,8 @@ class CableViewSet(ModelViewSet):
|
|||||||
|
|
||||||
class VirtualChassisViewSet(ModelViewSet):
|
class VirtualChassisViewSet(ModelViewSet):
|
||||||
queryset = VirtualChassis.objects.prefetch_related('tags').annotate(
|
queryset = VirtualChassis.objects.prefetch_related('tags').annotate(
|
||||||
member_count=Count('members', distinct=True)
|
member_count=get_subquery(Device, 'virtual_chassis')
|
||||||
).order_by(*VirtualChassis._meta.ordering)
|
)
|
||||||
serializer_class = serializers.VirtualChassisSerializer
|
serializer_class = serializers.VirtualChassisSerializer
|
||||||
filterset_class = filters.VirtualChassisFilterSet
|
filterset_class = filters.VirtualChassisFilterSet
|
||||||
|
|
||||||
@ -610,8 +610,8 @@ class PowerPanelViewSet(ModelViewSet):
|
|||||||
queryset = PowerPanel.objects.prefetch_related(
|
queryset = PowerPanel.objects.prefetch_related(
|
||||||
'site', 'rack_group'
|
'site', 'rack_group'
|
||||||
).annotate(
|
).annotate(
|
||||||
powerfeed_count=Count('powerfeeds')
|
powerfeed_count=get_subquery(PowerFeed, 'power_panel')
|
||||||
).order_by(*PowerPanel._meta.ordering)
|
)
|
||||||
serializer_class = serializers.PowerPanelSerializer
|
serializer_class = serializers.PowerPanelSerializer
|
||||||
filterset_class = filters.PowerPanelFilterSet
|
filterset_class = filters.PowerPanelFilterSet
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ from django.contrib import messages
|
|||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.core.paginator import EmptyPage, PageNotAnInteger
|
from django.core.paginator import EmptyPage, PageNotAnInteger
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.db.models import Count, F, Prefetch
|
from django.db.models import F, Prefetch
|
||||||
from django.forms import ModelMultipleChoiceField, MultipleHiddenInput, modelformset_factory
|
from django.forms import ModelMultipleChoiceField, MultipleHiddenInput, modelformset_factory
|
||||||
from django.shortcuts import get_object_or_404, redirect, render
|
from django.shortcuts import get_object_or_404, redirect, render
|
||||||
from django.utils.html import escape
|
from django.utils.html import escape
|
||||||
@ -253,7 +253,9 @@ class RackGroupBulkDeleteView(generic.BulkDeleteView):
|
|||||||
#
|
#
|
||||||
|
|
||||||
class RackRoleListView(generic.ObjectListView):
|
class RackRoleListView(generic.ObjectListView):
|
||||||
queryset = RackRole.objects.annotate(rack_count=Count('racks')).order_by(*RackRole._meta.ordering)
|
queryset = RackRole.objects.annotate(
|
||||||
|
rack_count=get_subquery(Rack, 'role')
|
||||||
|
)
|
||||||
table = tables.RackRoleTable
|
table = tables.RackRoleTable
|
||||||
|
|
||||||
|
|
||||||
@ -273,7 +275,9 @@ class RackRoleBulkImportView(generic.BulkImportView):
|
|||||||
|
|
||||||
|
|
||||||
class RackRoleBulkDeleteView(generic.BulkDeleteView):
|
class RackRoleBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = RackRole.objects.annotate(rack_count=Count('racks')).order_by(*RackRole._meta.ordering)
|
queryset = RackRole.objects.annotate(
|
||||||
|
rack_count=get_subquery(Rack, 'role')
|
||||||
|
)
|
||||||
table = tables.RackRoleTable
|
table = tables.RackRoleTable
|
||||||
|
|
||||||
|
|
||||||
@ -282,9 +286,11 @@ class RackRoleBulkDeleteView(generic.BulkDeleteView):
|
|||||||
#
|
#
|
||||||
|
|
||||||
class RackListView(generic.ObjectListView):
|
class RackListView(generic.ObjectListView):
|
||||||
queryset = Rack.objects.annotate(
|
queryset = Rack.objects.prefetch_related(
|
||||||
device_count=Count('devices')
|
'site', 'group', 'tenant', 'role', 'devices__device_type'
|
||||||
).order_by(*Rack._meta.ordering)
|
).annotate(
|
||||||
|
device_count=get_subquery(Device, 'rack')
|
||||||
|
)
|
||||||
filterset = filters.RackFilterSet
|
filterset = filters.RackFilterSet
|
||||||
filterset_form = forms.RackFilterForm
|
filterset_form = forms.RackFilterForm
|
||||||
table = tables.RackDetailTable
|
table = tables.RackDetailTable
|
||||||
@ -488,8 +494,8 @@ class ManufacturerBulkImportView(generic.BulkImportView):
|
|||||||
|
|
||||||
class ManufacturerBulkDeleteView(generic.BulkDeleteView):
|
class ManufacturerBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = Manufacturer.objects.annotate(
|
queryset = Manufacturer.objects.annotate(
|
||||||
devicetype_count=Count('device_types')
|
devicetype_count=get_subquery(DeviceType, 'manufacturer')
|
||||||
).order_by(*Manufacturer._meta.ordering)
|
)
|
||||||
table = tables.ManufacturerTable
|
table = tables.ManufacturerTable
|
||||||
|
|
||||||
|
|
||||||
@ -498,9 +504,9 @@ class ManufacturerBulkDeleteView(generic.BulkDeleteView):
|
|||||||
#
|
#
|
||||||
|
|
||||||
class DeviceTypeListView(generic.ObjectListView):
|
class DeviceTypeListView(generic.ObjectListView):
|
||||||
queryset = DeviceType.objects.annotate(
|
queryset = DeviceType.objects.prefetch_related('manufacturer').annotate(
|
||||||
instance_count=Count('instances')
|
instance_count=get_subquery(Device, 'device_type')
|
||||||
).order_by(*DeviceType._meta.ordering)
|
)
|
||||||
filterset = filters.DeviceTypeFilterSet
|
filterset = filters.DeviceTypeFilterSet
|
||||||
filterset_form = forms.DeviceTypeFilterForm
|
filterset_form = forms.DeviceTypeFilterForm
|
||||||
table = tables.DeviceTypeTable
|
table = tables.DeviceTypeTable
|
||||||
@ -606,8 +612,8 @@ class DeviceTypeImportView(generic.ObjectImportView):
|
|||||||
|
|
||||||
class DeviceTypeBulkEditView(generic.BulkEditView):
|
class DeviceTypeBulkEditView(generic.BulkEditView):
|
||||||
queryset = DeviceType.objects.prefetch_related('manufacturer').annotate(
|
queryset = DeviceType.objects.prefetch_related('manufacturer').annotate(
|
||||||
instance_count=Count('instances')
|
instance_count=get_subquery(Device, 'device_type')
|
||||||
).order_by(*DeviceType._meta.ordering)
|
)
|
||||||
filterset = filters.DeviceTypeFilterSet
|
filterset = filters.DeviceTypeFilterSet
|
||||||
table = tables.DeviceTypeTable
|
table = tables.DeviceTypeTable
|
||||||
form = forms.DeviceTypeBulkEditForm
|
form = forms.DeviceTypeBulkEditForm
|
||||||
@ -615,8 +621,8 @@ class DeviceTypeBulkEditView(generic.BulkEditView):
|
|||||||
|
|
||||||
class DeviceTypeBulkDeleteView(generic.BulkDeleteView):
|
class DeviceTypeBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = DeviceType.objects.prefetch_related('manufacturer').annotate(
|
queryset = DeviceType.objects.prefetch_related('manufacturer').annotate(
|
||||||
instance_count=Count('instances')
|
instance_count=get_subquery(Device, 'device_type')
|
||||||
).order_by(*DeviceType._meta.ordering)
|
)
|
||||||
filterset = filters.DeviceTypeFilterSet
|
filterset = filters.DeviceTypeFilterSet
|
||||||
table = tables.DeviceTypeTable
|
table = tables.DeviceTypeTable
|
||||||
|
|
||||||
@ -2287,9 +2293,9 @@ class InterfaceConnectionsListView(generic.ObjectListView):
|
|||||||
#
|
#
|
||||||
|
|
||||||
class VirtualChassisListView(generic.ObjectListView):
|
class VirtualChassisListView(generic.ObjectListView):
|
||||||
queryset = VirtualChassis.objects.annotate(
|
queryset = VirtualChassis.objects.prefetch_related('master').annotate(
|
||||||
member_count=Count('members', distinct=True)
|
member_count=get_subquery(Device, 'virtual_chassis')
|
||||||
).order_by(*VirtualChassis._meta.ordering)
|
)
|
||||||
table = tables.VirtualChassisTable
|
table = tables.VirtualChassisTable
|
||||||
filterset = filters.VirtualChassisFilterSet
|
filterset = filters.VirtualChassisFilterSet
|
||||||
filterset_form = forms.VirtualChassisFilterForm
|
filterset_form = forms.VirtualChassisFilterForm
|
||||||
@ -2515,9 +2521,11 @@ class VirtualChassisBulkDeleteView(generic.BulkDeleteView):
|
|||||||
#
|
#
|
||||||
|
|
||||||
class PowerPanelListView(generic.ObjectListView):
|
class PowerPanelListView(generic.ObjectListView):
|
||||||
queryset = PowerPanel.objects.annotate(
|
queryset = PowerPanel.objects.prefetch_related(
|
||||||
powerfeed_count=Count('powerfeeds')
|
'site', 'rack_group'
|
||||||
).order_by(*PowerPanel._meta.ordering)
|
).annotate(
|
||||||
|
powerfeed_count=get_subquery(PowerFeed, 'power_panel')
|
||||||
|
)
|
||||||
filterset = filters.PowerPanelFilterSet
|
filterset = filters.PowerPanelFilterSet
|
||||||
filterset_form = forms.PowerPanelFilterForm
|
filterset_form = forms.PowerPanelFilterForm
|
||||||
table = tables.PowerPanelTable
|
table = tables.PowerPanelTable
|
||||||
@ -2566,8 +2574,8 @@ class PowerPanelBulkDeleteView(generic.BulkDeleteView):
|
|||||||
queryset = PowerPanel.objects.prefetch_related(
|
queryset = PowerPanel.objects.prefetch_related(
|
||||||
'site', 'rack_group'
|
'site', 'rack_group'
|
||||||
).annotate(
|
).annotate(
|
||||||
rack_count=Count('powerfeeds')
|
powerfeed_count=get_subquery(PowerFeed, 'power_panel')
|
||||||
).order_by(*PowerPanel._meta.ordering)
|
)
|
||||||
filterset = filters.PowerPanelFilterSet
|
filterset = filters.PowerPanelFilterSet
|
||||||
table = tables.PowerPanelTable
|
table = tables.PowerPanelTable
|
||||||
|
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.db.models import Count
|
|
||||||
from django.http import Http404
|
from django.http import Http404
|
||||||
from django_rq.queues import get_connection
|
from django_rq.queues import get_connection
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
@ -12,15 +11,17 @@ from rq import Worker
|
|||||||
|
|
||||||
from extras import filters
|
from extras import filters
|
||||||
from extras.choices import JobResultStatusChoices
|
from extras.choices import JobResultStatusChoices
|
||||||
from extras.models import ConfigContext, CustomField, ExportTemplate, ImageAttachment, ObjectChange, JobResult, Tag
|
from extras.models import (
|
||||||
|
ConfigContext, ExportTemplate, ImageAttachment, ObjectChange, JobResult, Tag, TaggedItem,
|
||||||
|
)
|
||||||
|
from extras.models import CustomField
|
||||||
from extras.reports import get_report, get_reports, run_report
|
from extras.reports import get_report, get_reports, run_report
|
||||||
from extras.scripts import get_script, get_scripts, run_script
|
from extras.scripts import get_script, get_scripts, run_script
|
||||||
from netbox.api.views import ModelViewSet
|
|
||||||
from netbox.api.authentication import IsAuthenticatedOrLoginNotRequired
|
from netbox.api.authentication import IsAuthenticatedOrLoginNotRequired
|
||||||
from netbox.api.metadata import ContentTypeMetadata
|
from netbox.api.metadata import ContentTypeMetadata
|
||||||
|
from netbox.api.views import ModelViewSet
|
||||||
from utilities.exceptions import RQWorkerNotRunningException
|
from utilities.exceptions import RQWorkerNotRunningException
|
||||||
from utilities.querysets import RestrictedQuerySet
|
from utilities.utils import copy_safe_request, get_subquery
|
||||||
from utilities.utils import copy_safe_request
|
|
||||||
from . import serializers
|
from . import serializers
|
||||||
|
|
||||||
|
|
||||||
@ -101,8 +102,8 @@ class ExportTemplateViewSet(ModelViewSet):
|
|||||||
|
|
||||||
class TagViewSet(ModelViewSet):
|
class TagViewSet(ModelViewSet):
|
||||||
queryset = Tag.objects.annotate(
|
queryset = Tag.objects.annotate(
|
||||||
tagged_items=Count('extras_taggeditem_items')
|
tagged_items=get_subquery(TaggedItem, 'tag')
|
||||||
).order_by(*Tag._meta.ordering)
|
)
|
||||||
serializer_class = serializers.TagSerializer
|
serializer_class = serializers.TagSerializer
|
||||||
filterset_class = filters.TagFilterSet
|
filterset_class = filters.TagFilterSet
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
from django import template
|
from django import template
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.db.models import Count, Q
|
from django.db.models import Q
|
||||||
from django.http import Http404, HttpResponseForbidden
|
from django.http import Http404, HttpResponseForbidden
|
||||||
from django.shortcuts import get_object_or_404, redirect, render
|
from django.shortcuts import get_object_or_404, redirect, render
|
||||||
from django.views.generic import View
|
from django.views.generic import View
|
||||||
@ -12,11 +12,11 @@ from rq import Worker
|
|||||||
from netbox.views import generic
|
from netbox.views import generic
|
||||||
from utilities.forms import ConfirmationForm
|
from utilities.forms import ConfirmationForm
|
||||||
from utilities.paginator import EnhancedPaginator, get_paginate_count
|
from utilities.paginator import EnhancedPaginator, get_paginate_count
|
||||||
from utilities.utils import copy_safe_request, shallow_compare_dict
|
from utilities.utils import copy_safe_request, get_subquery, shallow_compare_dict
|
||||||
from utilities.views import ContentTypePermissionRequiredMixin
|
from utilities.views import ContentTypePermissionRequiredMixin
|
||||||
from . import filters, forms, tables
|
from . import filters, forms, tables
|
||||||
from .choices import JobResultStatusChoices
|
from .choices import JobResultStatusChoices
|
||||||
from .models import ConfigContext, ImageAttachment, ObjectChange, JobResult, Tag
|
from .models import ConfigContext, ImageAttachment, ObjectChange, JobResult, Tag, TaggedItem
|
||||||
from .reports import get_report, get_reports, run_report
|
from .reports import get_report, get_reports, run_report
|
||||||
from .scripts import get_scripts, run_script
|
from .scripts import get_scripts, run_script
|
||||||
|
|
||||||
@ -27,8 +27,8 @@ from .scripts import get_scripts, run_script
|
|||||||
|
|
||||||
class TagListView(generic.ObjectListView):
|
class TagListView(generic.ObjectListView):
|
||||||
queryset = Tag.objects.annotate(
|
queryset = Tag.objects.annotate(
|
||||||
items=Count('extras_taggeditem_items')
|
items=get_subquery(TaggedItem, 'tag')
|
||||||
).order_by(*Tag._meta.ordering)
|
)
|
||||||
filterset = filters.TagFilterSet
|
filterset = filters.TagFilterSet
|
||||||
filterset_form = forms.TagFilterForm
|
filterset_form = forms.TagFilterForm
|
||||||
table = tables.TagTable
|
table = tables.TagTable
|
||||||
@ -52,16 +52,16 @@ class TagBulkImportView(generic.BulkImportView):
|
|||||||
|
|
||||||
class TagBulkEditView(generic.BulkEditView):
|
class TagBulkEditView(generic.BulkEditView):
|
||||||
queryset = Tag.objects.annotate(
|
queryset = Tag.objects.annotate(
|
||||||
items=Count('extras_taggeditem_items')
|
items=get_subquery(TaggedItem, 'tag')
|
||||||
).order_by(*Tag._meta.ordering)
|
)
|
||||||
table = tables.TagTable
|
table = tables.TagTable
|
||||||
form = forms.TagBulkEditForm
|
form = forms.TagBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
class TagBulkDeleteView(generic.BulkDeleteView):
|
class TagBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = Tag.objects.annotate(
|
queryset = Tag.objects.annotate(
|
||||||
items=Count('extras_taggeditem_items')
|
items=get_subquery(TaggedItem, 'tag')
|
||||||
).order_by(*Tag._meta.ordering)
|
)
|
||||||
table = tables.TagTable
|
table = tables.TagTable
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db.models import Count
|
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from django_pglocks import advisory_lock
|
from django_pglocks import advisory_lock
|
||||||
from drf_yasg.utils import swagger_auto_schema
|
from drf_yasg.utils import swagger_auto_schema
|
||||||
@ -35,7 +34,7 @@ class VRFViewSet(CustomFieldModelViewSet):
|
|||||||
).annotate(
|
).annotate(
|
||||||
ipaddress_count=get_subquery(IPAddress, 'vrf'),
|
ipaddress_count=get_subquery(IPAddress, 'vrf'),
|
||||||
prefix_count=get_subquery(Prefix, 'vrf')
|
prefix_count=get_subquery(Prefix, 'vrf')
|
||||||
).order_by(*VRF._meta.ordering)
|
)
|
||||||
serializer_class = serializers.VRFSerializer
|
serializer_class = serializers.VRFSerializer
|
||||||
filterset_class = filters.VRFFilterSet
|
filterset_class = filters.VRFFilterSet
|
||||||
|
|
||||||
@ -56,8 +55,8 @@ class RouteTargetViewSet(CustomFieldModelViewSet):
|
|||||||
|
|
||||||
class RIRViewSet(ModelViewSet):
|
class RIRViewSet(ModelViewSet):
|
||||||
queryset = RIR.objects.annotate(
|
queryset = RIR.objects.annotate(
|
||||||
aggregate_count=Count('aggregates')
|
aggregate_count=get_subquery(Aggregate, 'rir')
|
||||||
).order_by(*RIR._meta.ordering)
|
)
|
||||||
serializer_class = serializers.RIRSerializer
|
serializer_class = serializers.RIRSerializer
|
||||||
filterset_class = filters.RIRFilterSet
|
filterset_class = filters.RIRFilterSet
|
||||||
|
|
||||||
@ -80,7 +79,7 @@ class RoleViewSet(ModelViewSet):
|
|||||||
queryset = Role.objects.annotate(
|
queryset = Role.objects.annotate(
|
||||||
prefix_count=get_subquery(Prefix, 'role'),
|
prefix_count=get_subquery(Prefix, 'role'),
|
||||||
vlan_count=get_subquery(VLAN, 'role')
|
vlan_count=get_subquery(VLAN, 'role')
|
||||||
).order_by(*Role._meta.ordering)
|
)
|
||||||
serializer_class = serializers.RoleSerializer
|
serializer_class = serializers.RoleSerializer
|
||||||
filterset_class = filters.RoleFilterSet
|
filterset_class = filters.RoleFilterSet
|
||||||
|
|
||||||
@ -92,7 +91,7 @@ class RoleViewSet(ModelViewSet):
|
|||||||
class PrefixViewSet(CustomFieldModelViewSet):
|
class PrefixViewSet(CustomFieldModelViewSet):
|
||||||
queryset = Prefix.objects.prefetch_related(
|
queryset = Prefix.objects.prefetch_related(
|
||||||
'site', 'vrf__tenant', 'tenant', 'vlan', 'role', 'tags'
|
'site', 'vrf__tenant', 'tenant', 'vlan', 'role', 'tags'
|
||||||
).order_by(*Prefix._meta.ordering)
|
)
|
||||||
serializer_class = serializers.PrefixSerializer
|
serializer_class = serializers.PrefixSerializer
|
||||||
filterset_class = filters.PrefixFilterSet
|
filterset_class = filters.PrefixFilterSet
|
||||||
|
|
||||||
@ -262,7 +261,7 @@ class PrefixViewSet(CustomFieldModelViewSet):
|
|||||||
class IPAddressViewSet(CustomFieldModelViewSet):
|
class IPAddressViewSet(CustomFieldModelViewSet):
|
||||||
queryset = IPAddress.objects.prefetch_related(
|
queryset = IPAddress.objects.prefetch_related(
|
||||||
'vrf__tenant', 'tenant', 'nat_inside', 'nat_outside', 'tags', 'assigned_object'
|
'vrf__tenant', 'tenant', 'nat_inside', 'nat_outside', 'tags', 'assigned_object'
|
||||||
).order_by(*IPAddress._meta.ordering)
|
)
|
||||||
serializer_class = serializers.IPAddressSerializer
|
serializer_class = serializers.IPAddressSerializer
|
||||||
filterset_class = filters.IPAddressFilterSet
|
filterset_class = filters.IPAddressFilterSet
|
||||||
|
|
||||||
@ -273,8 +272,8 @@ class IPAddressViewSet(CustomFieldModelViewSet):
|
|||||||
|
|
||||||
class VLANGroupViewSet(ModelViewSet):
|
class VLANGroupViewSet(ModelViewSet):
|
||||||
queryset = VLANGroup.objects.prefetch_related('site').annotate(
|
queryset = VLANGroup.objects.prefetch_related('site').annotate(
|
||||||
vlan_count=Count('vlans')
|
vlan_count=get_subquery(VLAN, 'group')
|
||||||
).order_by(*VLANGroup._meta.ordering)
|
)
|
||||||
serializer_class = serializers.VLANGroupSerializer
|
serializer_class = serializers.VLANGroupSerializer
|
||||||
filterset_class = filters.VLANGroupFilterSet
|
filterset_class = filters.VLANGroupFilterSet
|
||||||
|
|
||||||
@ -288,7 +287,7 @@ class VLANViewSet(CustomFieldModelViewSet):
|
|||||||
'site', 'group', 'tenant', 'role', 'tags'
|
'site', 'group', 'tenant', 'role', 'tags'
|
||||||
).annotate(
|
).annotate(
|
||||||
prefix_count=get_subquery(Prefix, 'vlan')
|
prefix_count=get_subquery(Prefix, 'vlan')
|
||||||
).order_by(*VLAN._meta.ordering)
|
)
|
||||||
serializer_class = serializers.VLANSerializer
|
serializer_class = serializers.VLANSerializer
|
||||||
filterset_class = filters.VLANFilterSet
|
filterset_class = filters.VLANFilterSet
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
from django.db.models import Count, Prefetch
|
from django.db.models import Prefetch
|
||||||
from django.db.models.expressions import RawSQL
|
from django.db.models.expressions import RawSQL
|
||||||
from django.shortcuts import get_object_or_404, redirect, render
|
from django.shortcuts import get_object_or_404, redirect, render
|
||||||
from django_tables2 import RequestConfig
|
from django_tables2 import RequestConfig
|
||||||
@ -139,7 +139,9 @@ class RouteTargetBulkDeleteView(generic.BulkDeleteView):
|
|||||||
#
|
#
|
||||||
|
|
||||||
class RIRListView(generic.ObjectListView):
|
class RIRListView(generic.ObjectListView):
|
||||||
queryset = RIR.objects.annotate(aggregate_count=Count('aggregates')).order_by(*RIR._meta.ordering)
|
queryset = RIR.objects.annotate(
|
||||||
|
aggregate_count=get_subquery(Aggregate, 'rir')
|
||||||
|
)
|
||||||
filterset = filters.RIRFilterSet
|
filterset = filters.RIRFilterSet
|
||||||
filterset_form = forms.RIRFilterForm
|
filterset_form = forms.RIRFilterForm
|
||||||
table = tables.RIRTable
|
table = tables.RIRTable
|
||||||
@ -162,7 +164,9 @@ class RIRBulkImportView(generic.BulkImportView):
|
|||||||
|
|
||||||
|
|
||||||
class RIRBulkDeleteView(generic.BulkDeleteView):
|
class RIRBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = RIR.objects.annotate(aggregate_count=Count('aggregates')).order_by(*RIR._meta.ordering)
|
queryset = RIR.objects.annotate(
|
||||||
|
aggregate_count=get_subquery(Aggregate, 'rir')
|
||||||
|
)
|
||||||
filterset = filters.RIRFilterSet
|
filterset = filters.RIRFilterSet
|
||||||
table = tables.RIRTable
|
table = tables.RIRTable
|
||||||
|
|
||||||
@ -174,7 +178,7 @@ class RIRBulkDeleteView(generic.BulkDeleteView):
|
|||||||
class AggregateListView(generic.ObjectListView):
|
class AggregateListView(generic.ObjectListView):
|
||||||
queryset = Aggregate.objects.annotate(
|
queryset = Aggregate.objects.annotate(
|
||||||
child_count=RawSQL('SELECT COUNT(*) FROM ipam_prefix WHERE ipam_prefix.prefix <<= ipam_aggregate.prefix', ())
|
child_count=RawSQL('SELECT COUNT(*) FROM ipam_prefix WHERE ipam_prefix.prefix <<= ipam_aggregate.prefix', ())
|
||||||
).order_by(*Aggregate._meta.ordering)
|
)
|
||||||
filterset = filters.AggregateFilterSet
|
filterset = filters.AggregateFilterSet
|
||||||
filterset_form = forms.AggregateFilterForm
|
filterset_form = forms.AggregateFilterForm
|
||||||
table = tables.AggregateDetailTable
|
table = tables.AggregateDetailTable
|
||||||
@ -628,9 +632,9 @@ class IPAddressBulkDeleteView(generic.BulkDeleteView):
|
|||||||
#
|
#
|
||||||
|
|
||||||
class VLANGroupListView(generic.ObjectListView):
|
class VLANGroupListView(generic.ObjectListView):
|
||||||
queryset = VLANGroup.objects.annotate(
|
queryset = VLANGroup.objects.prefetch_related('site').annotate(
|
||||||
vlan_count=Count('vlans')
|
vlan_count=get_subquery(VLAN, 'group')
|
||||||
).order_by(*VLANGroup._meta.ordering)
|
)
|
||||||
filterset = filters.VLANGroupFilterSet
|
filterset = filters.VLANGroupFilterSet
|
||||||
filterset_form = forms.VLANGroupFilterForm
|
filterset_form = forms.VLANGroupFilterForm
|
||||||
table = tables.VLANGroupTable
|
table = tables.VLANGroupTable
|
||||||
@ -653,8 +657,8 @@ class VLANGroupBulkImportView(generic.BulkImportView):
|
|||||||
|
|
||||||
class VLANGroupBulkDeleteView(generic.BulkDeleteView):
|
class VLANGroupBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = VLANGroup.objects.prefetch_related('site').annotate(
|
queryset = VLANGroup.objects.prefetch_related('site').annotate(
|
||||||
vlan_count=Count('vlans')
|
vlan_count=get_subquery(VLAN, 'group')
|
||||||
).order_by(*VLANGroup._meta.ordering)
|
)
|
||||||
filterset = filters.VLANGroupFilterSet
|
filterset = filters.VLANGroupFilterSet
|
||||||
table = tables.VLANGroupTable
|
table = tables.VLANGroupTable
|
||||||
|
|
||||||
|
@ -33,8 +33,8 @@ SEARCH_TYPES = OrderedDict((
|
|||||||
# Circuits
|
# Circuits
|
||||||
('provider', {
|
('provider', {
|
||||||
'queryset': Provider.objects.annotate(
|
'queryset': Provider.objects.annotate(
|
||||||
count_circuits=Count('circuits')
|
count_circuits=get_subquery(Circuit, 'provider')
|
||||||
).order_by(*Provider._meta.ordering),
|
),
|
||||||
'filterset': ProviderFilterSet,
|
'filterset': ProviderFilterSet,
|
||||||
'table': ProviderTable,
|
'table': ProviderTable,
|
||||||
'url': 'circuits:provider_list',
|
'url': 'circuits:provider_list',
|
||||||
@ -61,17 +61,21 @@ SEARCH_TYPES = OrderedDict((
|
|||||||
'url': 'dcim:rack_list',
|
'url': 'dcim:rack_list',
|
||||||
}),
|
}),
|
||||||
('rackgroup', {
|
('rackgroup', {
|
||||||
'queryset': RackGroup.objects.prefetch_related('site').annotate(
|
'queryset': RackGroup.objects.add_related_count(
|
||||||
rack_count=Count('racks')
|
RackGroup.objects.all(),
|
||||||
).order_by(*RackGroup._meta.ordering),
|
Rack,
|
||||||
|
'group',
|
||||||
|
'rack_count',
|
||||||
|
cumulative=True
|
||||||
|
).prefetch_related('site'),
|
||||||
'filterset': RackGroupFilterSet,
|
'filterset': RackGroupFilterSet,
|
||||||
'table': RackGroupTable,
|
'table': RackGroupTable,
|
||||||
'url': 'dcim:rackgroup_list',
|
'url': 'dcim:rackgroup_list',
|
||||||
}),
|
}),
|
||||||
('devicetype', {
|
('devicetype', {
|
||||||
'queryset': DeviceType.objects.prefetch_related('manufacturer').annotate(
|
'queryset': DeviceType.objects.prefetch_related('manufacturer').annotate(
|
||||||
instance_count=Count('instances')
|
instance_count=get_subquery(Device, 'device_type')
|
||||||
).order_by(*DeviceType._meta.ordering),
|
),
|
||||||
'filterset': DeviceTypeFilterSet,
|
'filterset': DeviceTypeFilterSet,
|
||||||
'table': DeviceTypeTable,
|
'table': DeviceTypeTable,
|
||||||
'url': 'dcim:devicetype_list',
|
'url': 'dcim:devicetype_list',
|
||||||
@ -86,8 +90,8 @@ SEARCH_TYPES = OrderedDict((
|
|||||||
}),
|
}),
|
||||||
('virtualchassis', {
|
('virtualchassis', {
|
||||||
'queryset': VirtualChassis.objects.prefetch_related('master').annotate(
|
'queryset': VirtualChassis.objects.prefetch_related('master').annotate(
|
||||||
member_count=Count('members', distinct=True)
|
member_count=get_subquery(Device, 'virtual_chassis')
|
||||||
).order_by(*VirtualChassis._meta.ordering),
|
),
|
||||||
'filterset': VirtualChassisFilterSet,
|
'filterset': VirtualChassisFilterSet,
|
||||||
'table': VirtualChassisTable,
|
'table': VirtualChassisTable,
|
||||||
'url': 'dcim:virtualchassis_list',
|
'url': 'dcim:virtualchassis_list',
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import base64
|
import base64
|
||||||
|
|
||||||
from Crypto.PublicKey import RSA
|
from Crypto.PublicKey import RSA
|
||||||
from django.db.models import Count
|
|
||||||
from django.http import HttpResponseBadRequest
|
from django.http import HttpResponseBadRequest
|
||||||
from rest_framework.exceptions import ValidationError
|
from rest_framework.exceptions import ValidationError
|
||||||
from rest_framework.permissions import IsAuthenticated
|
from rest_framework.permissions import IsAuthenticated
|
||||||
@ -13,6 +12,7 @@ from netbox.api.views import ModelViewSet
|
|||||||
from secrets import filters
|
from secrets import filters
|
||||||
from secrets.exceptions import InvalidKey
|
from secrets.exceptions import InvalidKey
|
||||||
from secrets.models import Secret, SecretRole, SessionKey, UserKey
|
from secrets.models import Secret, SecretRole, SessionKey, UserKey
|
||||||
|
from utilities.utils import get_subquery
|
||||||
from . import serializers
|
from . import serializers
|
||||||
|
|
||||||
ERR_USERKEY_MISSING = "No UserKey found for the current user."
|
ERR_USERKEY_MISSING = "No UserKey found for the current user."
|
||||||
@ -35,8 +35,8 @@ class SecretsRootView(APIRootView):
|
|||||||
|
|
||||||
class SecretRoleViewSet(ModelViewSet):
|
class SecretRoleViewSet(ModelViewSet):
|
||||||
queryset = SecretRole.objects.annotate(
|
queryset = SecretRole.objects.annotate(
|
||||||
secret_count=Count('secrets')
|
secret_count=get_subquery(Secret, 'role')
|
||||||
).order_by(*SecretRole._meta.ordering)
|
)
|
||||||
serializer_class = serializers.SecretRoleSerializer
|
serializer_class = serializers.SecretRoleSerializer
|
||||||
filterset_class = filters.SecretRoleFilterSet
|
filterset_class = filters.SecretRoleFilterSet
|
||||||
|
|
||||||
|
@ -2,12 +2,12 @@ import base64
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.db.models import Count
|
|
||||||
from django.shortcuts import get_object_or_404, redirect, render
|
from django.shortcuts import get_object_or_404, redirect, render
|
||||||
from django.utils.html import escape
|
from django.utils.html import escape
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
|
|
||||||
from netbox.views import generic
|
from netbox.views import generic
|
||||||
|
from utilities.utils import get_subquery
|
||||||
from . import filters, forms, tables
|
from . import filters, forms, tables
|
||||||
from .models import SecretRole, Secret, SessionKey, UserKey
|
from .models import SecretRole, Secret, SessionKey, UserKey
|
||||||
|
|
||||||
@ -27,7 +27,9 @@ def get_session_key(request):
|
|||||||
#
|
#
|
||||||
|
|
||||||
class SecretRoleListView(generic.ObjectListView):
|
class SecretRoleListView(generic.ObjectListView):
|
||||||
queryset = SecretRole.objects.annotate(secret_count=Count('secrets')).order_by(*SecretRole._meta.ordering)
|
queryset = SecretRole.objects.annotate(
|
||||||
|
secret_count=get_subquery(Secret, 'role')
|
||||||
|
)
|
||||||
table = tables.SecretRoleTable
|
table = tables.SecretRoleTable
|
||||||
|
|
||||||
|
|
||||||
@ -47,7 +49,9 @@ class SecretRoleBulkImportView(generic.BulkImportView):
|
|||||||
|
|
||||||
|
|
||||||
class SecretRoleBulkDeleteView(generic.BulkDeleteView):
|
class SecretRoleBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = SecretRole.objects.annotate(secret_count=Count('secrets')).order_by(*SecretRole._meta.ordering)
|
queryset = SecretRole.objects.annotate(
|
||||||
|
secret_count=get_subquery(Secret, 'role')
|
||||||
|
)
|
||||||
table = tables.SecretRoleTable
|
table = tables.SecretRoleTable
|
||||||
|
|
||||||
|
|
||||||
|
@ -19,9 +19,23 @@ class UserSerializer(ValidatedModelSerializer):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = User
|
model = User
|
||||||
fields = (
|
fields = (
|
||||||
'id', 'url', 'username', 'first_name', 'last_name', 'email', 'is_staff', 'is_active', 'date_joined',
|
'id', 'url', 'username', 'password', 'first_name', 'last_name', 'email', 'is_staff', 'is_active',
|
||||||
'groups',
|
'date_joined', 'groups',
|
||||||
)
|
)
|
||||||
|
extra_kwargs = {
|
||||||
|
'password': {'write_only': True}
|
||||||
|
}
|
||||||
|
|
||||||
|
def create(self, validated_data):
|
||||||
|
"""
|
||||||
|
Extract the password from validated data and set it separately to ensure proper hash generation.
|
||||||
|
"""
|
||||||
|
password = validated_data.pop('password')
|
||||||
|
user = super().create(validated_data)
|
||||||
|
user.set_password(password)
|
||||||
|
user.save()
|
||||||
|
|
||||||
|
return user
|
||||||
|
|
||||||
|
|
||||||
class GroupSerializer(ValidatedModelSerializer):
|
class GroupSerializer(ValidatedModelSerializer):
|
||||||
|
@ -21,15 +21,19 @@ class UserTest(APIViewTestCases.APIViewTestCase):
|
|||||||
model = User
|
model = User
|
||||||
view_namespace = 'users'
|
view_namespace = 'users'
|
||||||
brief_fields = ['id', 'url', 'username']
|
brief_fields = ['id', 'url', 'username']
|
||||||
|
validation_excluded_fields = ['password']
|
||||||
create_data = [
|
create_data = [
|
||||||
{
|
{
|
||||||
'username': 'User_4',
|
'username': 'User_4',
|
||||||
|
'password': 'password4',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'username': 'User_5',
|
'username': 'User_5',
|
||||||
|
'password': 'password5',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'username': 'User_6',
|
'username': 'User_6',
|
||||||
|
'password': 'password6',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -174,6 +174,7 @@ class APIViewTestCases:
|
|||||||
|
|
||||||
class CreateObjectViewTestCase(APITestCase):
|
class CreateObjectViewTestCase(APITestCase):
|
||||||
create_data = []
|
create_data = []
|
||||||
|
validation_excluded_fields = []
|
||||||
|
|
||||||
def test_create_object_without_permission(self):
|
def test_create_object_without_permission(self):
|
||||||
"""
|
"""
|
||||||
@ -205,6 +206,7 @@ class APIViewTestCases:
|
|||||||
self.assertInstanceEqual(
|
self.assertInstanceEqual(
|
||||||
self._get_queryset().get(pk=response.data['id']),
|
self._get_queryset().get(pk=response.data['id']),
|
||||||
self.create_data[0],
|
self.create_data[0],
|
||||||
|
exclude=self.validation_excluded_fields,
|
||||||
api=True
|
api=True
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -232,12 +234,14 @@ class APIViewTestCases:
|
|||||||
self.assertInstanceEqual(
|
self.assertInstanceEqual(
|
||||||
self._get_queryset().get(pk=obj['id']),
|
self._get_queryset().get(pk=obj['id']),
|
||||||
self.create_data[i],
|
self.create_data[i],
|
||||||
|
exclude=self.validation_excluded_fields,
|
||||||
api=True
|
api=True
|
||||||
)
|
)
|
||||||
|
|
||||||
class UpdateObjectViewTestCase(APITestCase):
|
class UpdateObjectViewTestCase(APITestCase):
|
||||||
update_data = {}
|
update_data = {}
|
||||||
bulk_update_data = None
|
bulk_update_data = None
|
||||||
|
validation_excluded_fields = []
|
||||||
|
|
||||||
def test_update_object_without_permission(self):
|
def test_update_object_without_permission(self):
|
||||||
"""
|
"""
|
||||||
@ -270,7 +274,12 @@ class APIViewTestCases:
|
|||||||
response = self.client.patch(url, update_data, format='json', **self.header)
|
response = self.client.patch(url, update_data, format='json', **self.header)
|
||||||
self.assertHttpStatus(response, status.HTTP_200_OK)
|
self.assertHttpStatus(response, status.HTTP_200_OK)
|
||||||
instance.refresh_from_db()
|
instance.refresh_from_db()
|
||||||
self.assertInstanceEqual(instance, update_data, api=True)
|
self.assertInstanceEqual(
|
||||||
|
instance,
|
||||||
|
update_data,
|
||||||
|
exclude=self.validation_excluded_fields,
|
||||||
|
api=True
|
||||||
|
)
|
||||||
|
|
||||||
def test_bulk_update_objects(self):
|
def test_bulk_update_objects(self):
|
||||||
"""
|
"""
|
||||||
|
@ -126,20 +126,25 @@ class TestCase(_TestCase):
|
|||||||
err_message = f"Expected HTTP status {expected_status}; received {response.status_code}: {err}"
|
err_message = f"Expected HTTP status {expected_status}; received {response.status_code}: {err}"
|
||||||
self.assertEqual(response.status_code, expected_status, err_message)
|
self.assertEqual(response.status_code, expected_status, err_message)
|
||||||
|
|
||||||
def assertInstanceEqual(self, instance, data, api=False):
|
def assertInstanceEqual(self, instance, data, exclude=None, api=False):
|
||||||
"""
|
"""
|
||||||
Compare a model instance to a dictionary, checking that its attribute values match those specified
|
Compare a model instance to a dictionary, checking that its attribute values match those specified
|
||||||
in the dictionary.
|
in the dictionary.
|
||||||
|
|
||||||
:instance: Python object instance
|
:param instance: Python object instance
|
||||||
:data: Dictionary of test data used to define the instance
|
:param data: Dictionary of test data used to define the instance
|
||||||
:api: Set to True is the data is a JSON representation of the instance
|
:param exclude: List of fields to exclude from comparison (e.g. passwords, which get hashed)
|
||||||
|
:param api: Set to True is the data is a JSON representation of the instance
|
||||||
"""
|
"""
|
||||||
model_dict = self.model_to_dict(instance, fields=data.keys(), api=api)
|
if exclude is None:
|
||||||
|
exclude = []
|
||||||
|
|
||||||
# Omit any dictionary keys which are not instance attributes
|
fields = [k for k in data.keys() if k not in exclude]
|
||||||
|
model_dict = self.model_to_dict(instance, fields=fields, api=api)
|
||||||
|
|
||||||
|
# Omit any dictionary keys which are not instance attributes or have been excluded
|
||||||
relevant_data = {
|
relevant_data = {
|
||||||
k: v for k, v in data.items() if hasattr(instance, k)
|
k: v for k, v in data.items() if hasattr(instance, k) and k not in exclude
|
||||||
}
|
}
|
||||||
|
|
||||||
self.assertDictEqual(model_dict, relevant_data)
|
self.assertDictEqual(model_dict, relevant_data)
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
from django.db.models import Count
|
|
||||||
from rest_framework.routers import APIRootView
|
from rest_framework.routers import APIRootView
|
||||||
|
|
||||||
from dcim.models import Device
|
from dcim.models import Device
|
||||||
@ -23,16 +22,16 @@ class VirtualizationRootView(APIRootView):
|
|||||||
|
|
||||||
class ClusterTypeViewSet(ModelViewSet):
|
class ClusterTypeViewSet(ModelViewSet):
|
||||||
queryset = ClusterType.objects.annotate(
|
queryset = ClusterType.objects.annotate(
|
||||||
cluster_count=Count('clusters')
|
cluster_count=get_subquery(Cluster, 'type')
|
||||||
).order_by(*ClusterType._meta.ordering)
|
)
|
||||||
serializer_class = serializers.ClusterTypeSerializer
|
serializer_class = serializers.ClusterTypeSerializer
|
||||||
filterset_class = filters.ClusterTypeFilterSet
|
filterset_class = filters.ClusterTypeFilterSet
|
||||||
|
|
||||||
|
|
||||||
class ClusterGroupViewSet(ModelViewSet):
|
class ClusterGroupViewSet(ModelViewSet):
|
||||||
queryset = ClusterGroup.objects.annotate(
|
queryset = ClusterGroup.objects.annotate(
|
||||||
cluster_count=Count('clusters')
|
cluster_count=get_subquery(Cluster, 'group')
|
||||||
).order_by(*ClusterGroup._meta.ordering)
|
)
|
||||||
serializer_class = serializers.ClusterGroupSerializer
|
serializer_class = serializers.ClusterGroupSerializer
|
||||||
filterset_class = filters.ClusterGroupFilterSet
|
filterset_class = filters.ClusterGroupFilterSet
|
||||||
|
|
||||||
@ -43,7 +42,7 @@ class ClusterViewSet(CustomFieldModelViewSet):
|
|||||||
).annotate(
|
).annotate(
|
||||||
device_count=get_subquery(Device, 'cluster'),
|
device_count=get_subquery(Device, 'cluster'),
|
||||||
virtualmachine_count=get_subquery(VirtualMachine, 'cluster')
|
virtualmachine_count=get_subquery(VirtualMachine, 'cluster')
|
||||||
).order_by(*Cluster._meta.ordering)
|
)
|
||||||
serializer_class = serializers.ClusterSerializer
|
serializer_class = serializers.ClusterSerializer
|
||||||
filterset_class = filters.ClusterFilterSet
|
filterset_class = filters.ClusterFilterSet
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.db.models import Count, Prefetch
|
from django.db.models import Prefetch
|
||||||
from django.shortcuts import get_object_or_404, redirect, render
|
from django.shortcuts import get_object_or_404, redirect, render
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
|
||||||
@ -21,7 +21,9 @@ from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterf
|
|||||||
#
|
#
|
||||||
|
|
||||||
class ClusterTypeListView(generic.ObjectListView):
|
class ClusterTypeListView(generic.ObjectListView):
|
||||||
queryset = ClusterType.objects.annotate(cluster_count=Count('clusters')).order_by(*ClusterType._meta.ordering)
|
queryset = ClusterType.objects.annotate(
|
||||||
|
cluster_count=get_subquery(Cluster, 'type')
|
||||||
|
)
|
||||||
table = tables.ClusterTypeTable
|
table = tables.ClusterTypeTable
|
||||||
|
|
||||||
|
|
||||||
@ -41,7 +43,9 @@ class ClusterTypeBulkImportView(generic.BulkImportView):
|
|||||||
|
|
||||||
|
|
||||||
class ClusterTypeBulkDeleteView(generic.BulkDeleteView):
|
class ClusterTypeBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = ClusterType.objects.annotate(cluster_count=Count('clusters')).order_by(*ClusterType._meta.ordering)
|
queryset = ClusterType.objects.annotate(
|
||||||
|
cluster_count=get_subquery(Cluster, 'type')
|
||||||
|
)
|
||||||
table = tables.ClusterTypeTable
|
table = tables.ClusterTypeTable
|
||||||
|
|
||||||
|
|
||||||
@ -50,7 +54,9 @@ class ClusterTypeBulkDeleteView(generic.BulkDeleteView):
|
|||||||
#
|
#
|
||||||
|
|
||||||
class ClusterGroupListView(generic.ObjectListView):
|
class ClusterGroupListView(generic.ObjectListView):
|
||||||
queryset = ClusterGroup.objects.annotate(cluster_count=Count('clusters')).order_by(*ClusterGroup._meta.ordering)
|
queryset = ClusterGroup.objects.annotate(
|
||||||
|
cluster_count=get_subquery(Cluster, 'group')
|
||||||
|
)
|
||||||
table = tables.ClusterGroupTable
|
table = tables.ClusterGroupTable
|
||||||
|
|
||||||
|
|
||||||
@ -70,7 +76,9 @@ class ClusterGroupBulkImportView(generic.BulkImportView):
|
|||||||
|
|
||||||
|
|
||||||
class ClusterGroupBulkDeleteView(generic.BulkDeleteView):
|
class ClusterGroupBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = ClusterGroup.objects.annotate(cluster_count=Count('clusters')).order_by(*ClusterGroup._meta.ordering)
|
queryset = ClusterGroup.objects.annotate(
|
||||||
|
cluster_count=get_subquery(Cluster, 'group')
|
||||||
|
)
|
||||||
table = tables.ClusterGroupTable
|
table = tables.ClusterGroupTable
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user