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