Merge pull request #4861 from netbox-community/django-31

Upgrade to Django 3.1 (v2.9)
This commit is contained in:
Jeremy Stretch 2020-07-20 12:47:49 -04:00 committed by GitHub
commit de6202c160
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 314 additions and 119 deletions

View File

@ -19,7 +19,7 @@ from . import serializers
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=Count('circuits')
) ).order_by(*Provider._meta.ordering)
serializer_class = serializers.ProviderSerializer serializer_class = serializers.ProviderSerializer
filterset_class = filters.ProviderFilterSet filterset_class = filters.ProviderFilterSet
@ -41,7 +41,7 @@ class ProviderViewSet(CustomFieldModelViewSet):
class CircuitTypeViewSet(ModelViewSet): class CircuitTypeViewSet(ModelViewSet):
queryset = CircuitType.objects.annotate( queryset = CircuitType.objects.annotate(
circuit_count=Count('circuits') circuit_count=Count('circuits')
) ).order_by(*CircuitType._meta.ordering)
serializer_class = serializers.CircuitTypeSerializer serializer_class = serializers.CircuitTypeSerializer
filterset_class = filters.CircuitTypeFilterSet filterset_class = filters.CircuitTypeFilterSet

View File

@ -0,0 +1,18 @@
# Generated by Django 3.1b1 on 2020-07-16 15:55
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('circuits', '0018_standardize_description'),
]
operations = [
migrations.AlterField(
model_name='circuittermination',
name='connection_status',
field=models.BooleanField(blank=True, null=True),
),
]

View File

@ -275,9 +275,10 @@ class CircuitTermination(CableTermination):
blank=True, blank=True,
null=True null=True
) )
connection_status = models.NullBooleanField( connection_status = models.BooleanField(
choices=CONNECTION_STATUS_CHOICES, choices=CONNECTION_STATUS_CHOICES,
blank=True blank=True,
null=True
) )
port_speed = models.PositiveIntegerField( port_speed = models.PositiveIntegerField(
verbose_name='Port speed (Kbps)' verbose_name='Port speed (Kbps)'

View File

@ -21,7 +21,7 @@ from .models import Circuit, CircuitTermination, CircuitType, Provider
# #
class ProviderListView(ObjectListView): class ProviderListView(ObjectListView):
queryset = Provider.objects.annotate(count_circuits=Count('circuits')) queryset = Provider.objects.annotate(count_circuits=Count('circuits')).order_by(*Provider._meta.ordering)
filterset = filters.ProviderFilterSet filterset = filters.ProviderFilterSet
filterset_form = forms.ProviderFilterForm filterset_form = forms.ProviderFilterForm
table = tables.ProviderTable table = tables.ProviderTable
@ -73,14 +73,14 @@ class ProviderBulkImportView(BulkImportView):
class ProviderBulkEditView(BulkEditView): class ProviderBulkEditView(BulkEditView):
queryset = Provider.objects.annotate(count_circuits=Count('circuits')) queryset = Provider.objects.annotate(count_circuits=Count('circuits')).order_by(*Provider._meta.ordering)
filterset = filters.ProviderFilterSet filterset = filters.ProviderFilterSet
table = tables.ProviderTable table = tables.ProviderTable
form = forms.ProviderBulkEditForm form = forms.ProviderBulkEditForm
class ProviderBulkDeleteView(BulkDeleteView): class ProviderBulkDeleteView(BulkDeleteView):
queryset = Provider.objects.annotate(count_circuits=Count('circuits')) queryset = Provider.objects.annotate(count_circuits=Count('circuits')).order_by(*Provider._meta.ordering)
filterset = filters.ProviderFilterSet filterset = filters.ProviderFilterSet
table = tables.ProviderTable table = tables.ProviderTable
@ -90,7 +90,7 @@ class ProviderBulkDeleteView(BulkDeleteView):
# #
class CircuitTypeListView(ObjectListView): class CircuitTypeListView(ObjectListView):
queryset = CircuitType.objects.annotate(circuit_count=Count('circuits')) queryset = CircuitType.objects.annotate(circuit_count=Count('circuits')).order_by(*CircuitType._meta.ordering)
table = tables.CircuitTypeTable table = tables.CircuitTypeTable
@ -110,7 +110,7 @@ class CircuitTypeBulkImportView(BulkImportView):
class CircuitTypeBulkDeleteView(BulkDeleteView): class CircuitTypeBulkDeleteView(BulkDeleteView):
queryset = CircuitType.objects.annotate(circuit_count=Count('circuits')) queryset = CircuitType.objects.annotate(circuit_count=Count('circuits')).order_by(*CircuitType._meta.ordering)
table = tables.CircuitTypeTable table = tables.CircuitTypeTable

View File

@ -74,8 +74,12 @@ class CableTraceMixin(object):
# #
class RegionViewSet(ModelViewSet): class RegionViewSet(ModelViewSet):
queryset = Region.objects.annotate( queryset = Region.objects.add_related_count(
site_count=Count('sites') Region.objects.all(),
Site,
'region',
'site_count',
cumulative=True
) )
serializer_class = serializers.RegionSerializer serializer_class = serializers.RegionSerializer
filterset_class = filters.RegionFilterSet filterset_class = filters.RegionFilterSet
@ -95,7 +99,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
@ -115,9 +119,13 @@ class SiteViewSet(CustomFieldModelViewSet):
# #
class RackGroupViewSet(ModelViewSet): class RackGroupViewSet(ModelViewSet):
queryset = RackGroup.objects.prefetch_related('site').annotate( queryset = RackGroup.objects.add_related_count(
rack_count=Count('racks') RackGroup.objects.all(),
) Rack,
'group',
'rack_count',
cumulative=True
).prefetch_related('site')
serializer_class = serializers.RackGroupSerializer serializer_class = serializers.RackGroupSerializer
filterset_class = filters.RackGroupFilterSet filterset_class = filters.RackGroupFilterSet
@ -129,7 +137,7 @@ class RackGroupViewSet(ModelViewSet):
class RackRoleViewSet(ModelViewSet): class RackRoleViewSet(ModelViewSet):
queryset = RackRole.objects.annotate( queryset = RackRole.objects.annotate(
rack_count=Count('racks') rack_count=Count('racks')
) ).order_by(*RackRole._meta.ordering)
serializer_class = serializers.RackRoleSerializer serializer_class = serializers.RackRoleSerializer
filterset_class = filters.RackRoleFilterSet filterset_class = filters.RackRoleFilterSet
@ -144,7 +152,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
@ -217,7 +225,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
@ -229,7 +237,7 @@ 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=Count('instances')
) ).order_by(*DeviceType._meta.ordering)
serializer_class = serializers.DeviceTypeSerializer serializer_class = serializers.DeviceTypeSerializer
filterset_class = filters.DeviceTypeFilterSet filterset_class = filters.DeviceTypeFilterSet
@ -294,7 +302,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
@ -307,7 +315,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
@ -583,7 +591,7 @@ 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') member_count=Count('members')
) ).order_by(*VirtualChassis._meta.ordering)
serializer_class = serializers.VirtualChassisSerializer serializer_class = serializers.VirtualChassisSerializer
filterset_class = filters.VirtualChassisFilterSet filterset_class = filters.VirtualChassisFilterSet
@ -597,7 +605,7 @@ class PowerPanelViewSet(ModelViewSet):
'site', 'rack_group' 'site', 'rack_group'
).annotate( ).annotate(
powerfeed_count=Count('powerfeeds') powerfeed_count=Count('powerfeeds')
) ).order_by(*PowerPanel._meta.ordering)
serializer_class = serializers.PowerPanelSerializer serializer_class = serializers.PowerPanelSerializer
filterset_class = filters.PowerPanelFilterSet filterset_class = filters.PowerPanelFilterSet

View File

@ -0,0 +1,43 @@
# Generated by Django 3.1b1 on 2020-07-16 15:55
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('dcim', '0112_standardize_components'),
]
operations = [
migrations.AlterField(
model_name='consoleport',
name='connection_status',
field=models.BooleanField(blank=True, null=True),
),
migrations.AlterField(
model_name='consoleserverport',
name='connection_status',
field=models.BooleanField(blank=True, null=True),
),
migrations.AlterField(
model_name='interface',
name='connection_status',
field=models.BooleanField(blank=True, null=True),
),
migrations.AlterField(
model_name='powerfeed',
name='connection_status',
field=models.BooleanField(blank=True, null=True),
),
migrations.AlterField(
model_name='poweroutlet',
name='connection_status',
field=models.BooleanField(blank=True, null=True),
),
migrations.AlterField(
model_name='powerport',
name='connection_status',
field=models.BooleanField(blank=True, null=True),
),
]

View File

@ -0,0 +1,23 @@
# Generated by Django 3.1b1 on 2020-07-16 16:01
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('dcim', '0113_nullbooleanfield_to_booleanfield'),
]
operations = [
migrations.AlterField(
model_name='device',
name='local_context_data',
field=models.JSONField(blank=True, null=True),
),
migrations.AlterField(
model_name='platform',
name='napalm_args',
field=models.JSONField(blank=True, null=True),
),
]

View File

@ -6,7 +6,7 @@ from django.conf import settings
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.contrib.postgres.fields import ArrayField, JSONField from django.contrib.postgres.fields import ArrayField
from django.core.exceptions import ObjectDoesNotExist, ValidationError from django.core.exceptions import ObjectDoesNotExist, ValidationError
from django.core.validators import MaxValueValidator, MinValueValidator from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import models from django.db import models
@ -1280,7 +1280,7 @@ class Platform(ChangeLoggedModel):
verbose_name='NAPALM driver', verbose_name='NAPALM driver',
help_text='The name of the NAPALM driver to use when interacting with devices' help_text='The name of the NAPALM driver to use when interacting with devices'
) )
napalm_args = JSONField( napalm_args = models.JSONField(
blank=True, blank=True,
null=True, null=True,
verbose_name='NAPALM arguments', verbose_name='NAPALM arguments',
@ -1905,9 +1905,10 @@ class PowerFeed(ChangeLoggedModel, CableTermination, CustomFieldModel):
blank=True, blank=True,
null=True null=True
) )
connection_status = models.NullBooleanField( connection_status = models.BooleanField(
choices=CONNECTION_STATUS_CHOICES, choices=CONNECTION_STATUS_CHOICES,
blank=True blank=True,
null=True
) )
name = models.CharField( name = models.CharField(
max_length=50 max_length=50

View File

@ -264,9 +264,10 @@ class ConsolePort(CableTermination, ComponentModel):
blank=True, blank=True,
null=True null=True
) )
connection_status = models.NullBooleanField( connection_status = models.BooleanField(
choices=CONNECTION_STATUS_CHOICES, choices=CONNECTION_STATUS_CHOICES,
blank=True blank=True,
null=True
) )
tags = TaggableManager(through=TaggedItem) tags = TaggableManager(through=TaggedItem)
@ -304,9 +305,10 @@ class ConsoleServerPort(CableTermination, ComponentModel):
blank=True, blank=True,
help_text='Physical port type' help_text='Physical port type'
) )
connection_status = models.NullBooleanField( connection_status = models.BooleanField(
choices=CONNECTION_STATUS_CHOICES, choices=CONNECTION_STATUS_CHOICES,
blank=True blank=True,
null=True
) )
tags = TaggableManager(through=TaggedItem) tags = TaggableManager(through=TaggedItem)
@ -370,9 +372,10 @@ class PowerPort(CableTermination, ComponentModel):
blank=True, blank=True,
null=True null=True
) )
connection_status = models.NullBooleanField( connection_status = models.BooleanField(
choices=CONNECTION_STATUS_CHOICES, choices=CONNECTION_STATUS_CHOICES,
blank=True blank=True,
null=True
) )
tags = TaggableManager(through=TaggedItem) tags = TaggableManager(through=TaggedItem)
@ -505,9 +508,10 @@ class PowerOutlet(CableTermination, ComponentModel):
blank=True, blank=True,
help_text="Phase (for three-phase feeds)" help_text="Phase (for three-phase feeds)"
) )
connection_status = models.NullBooleanField( connection_status = models.BooleanField(
choices=CONNECTION_STATUS_CHOICES, choices=CONNECTION_STATUS_CHOICES,
blank=True blank=True,
null=True
) )
tags = TaggableManager(through=TaggedItem) tags = TaggableManager(through=TaggedItem)
@ -598,9 +602,10 @@ class Interface(CableTermination, ComponentModel, BaseInterface):
blank=True, blank=True,
null=True null=True
) )
connection_status = models.NullBooleanField( connection_status = models.BooleanField(
choices=CONNECTION_STATUS_CHOICES, choices=CONNECTION_STATUS_CHOICES,
blank=True blank=True,
null=True
) )
lag = models.ForeignKey( lag = models.ForeignKey(
to='self', to='self',

View File

@ -133,7 +133,13 @@ class RegionBulkImportView(BulkImportView):
class RegionBulkDeleteView(BulkDeleteView): class RegionBulkDeleteView(BulkDeleteView):
queryset = Region.objects.all() queryset = Region.objects.add_related_count(
Region.objects.all(),
Site,
'region',
'site_count',
cumulative=True
)
filterset = filters.RegionFilterSet filterset = filters.RegionFilterSet
table = tables.RegionTable table = tables.RegionTable
@ -238,7 +244,13 @@ class RackGroupBulkImportView(BulkImportView):
class RackGroupBulkDeleteView(BulkDeleteView): class RackGroupBulkDeleteView(BulkDeleteView):
queryset = RackGroup.objects.prefetch_related('site').annotate(rack_count=Count('racks')) queryset = RackGroup.objects.add_related_count(
RackGroup.objects.all(),
Rack,
'group',
'rack_count',
cumulative=True
).prefetch_related('site')
filterset = filters.RackGroupFilterSet filterset = filters.RackGroupFilterSet
table = tables.RackGroupTable table = tables.RackGroupTable
@ -248,7 +260,7 @@ class RackGroupBulkDeleteView(BulkDeleteView):
# #
class RackRoleListView(ObjectListView): class RackRoleListView(ObjectListView):
queryset = RackRole.objects.annotate(rack_count=Count('racks')) queryset = RackRole.objects.annotate(rack_count=Count('racks')).order_by(*RackRole._meta.ordering)
table = tables.RackRoleTable table = tables.RackRoleTable
@ -268,7 +280,7 @@ class RackRoleBulkImportView(BulkImportView):
class RackRoleBulkDeleteView(BulkDeleteView): class RackRoleBulkDeleteView(BulkDeleteView):
queryset = RackRole.objects.annotate(rack_count=Count('racks')) queryset = RackRole.objects.annotate(rack_count=Count('racks')).order_by(*RackRole._meta.ordering)
table = tables.RackRoleTable table = tables.RackRoleTable
@ -281,7 +293,7 @@ class RackListView(ObjectListView):
'site', 'group', 'tenant', 'role', 'devices__device_type' 'site', 'group', 'tenant', 'role', 'devices__device_type'
).annotate( ).annotate(
device_count=Count('devices') device_count=Count('devices')
) ).order_by(*Rack._meta.ordering)
filterset = filters.RackFilterSet filterset = filters.RackFilterSet
filterset_form = forms.RackFilterForm filterset_form = forms.RackFilterForm
table = tables.RackDetailTable table = tables.RackDetailTable
@ -465,7 +477,7 @@ class ManufacturerListView(ObjectListView):
devicetype_count=Count('device_types', distinct=True), devicetype_count=Count('device_types', distinct=True),
inventoryitem_count=Count('inventory_items', distinct=True), inventoryitem_count=Count('inventory_items', distinct=True),
platform_count=Count('platforms', distinct=True), platform_count=Count('platforms', distinct=True),
) ).order_by(*Manufacturer._meta.ordering)
table = tables.ManufacturerTable table = tables.ManufacturerTable
@ -485,7 +497,9 @@ class ManufacturerBulkImportView(BulkImportView):
class ManufacturerBulkDeleteView(BulkDeleteView): class ManufacturerBulkDeleteView(BulkDeleteView):
queryset = Manufacturer.objects.annotate(devicetype_count=Count('device_types')) queryset = Manufacturer.objects.annotate(
devicetype_count=Count('device_types')
).order_by(*Manufacturer._meta.ordering)
table = tables.ManufacturerTable table = tables.ManufacturerTable
@ -494,7 +508,9 @@ class ManufacturerBulkDeleteView(BulkDeleteView):
# #
class DeviceTypeListView(ObjectListView): class DeviceTypeListView(ObjectListView):
queryset = DeviceType.objects.prefetch_related('manufacturer').annotate(instance_count=Count('instances')) queryset = DeviceType.objects.prefetch_related('manufacturer').annotate(
instance_count=Count('instances')
).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
@ -602,14 +618,18 @@ class DeviceTypeImportView(ObjectImportView):
class DeviceTypeBulkEditView(BulkEditView): class DeviceTypeBulkEditView(BulkEditView):
queryset = DeviceType.objects.prefetch_related('manufacturer').annotate(instance_count=Count('instances')) queryset = DeviceType.objects.prefetch_related('manufacturer').annotate(
instance_count=Count('instances')
).order_by(*DeviceType._meta.ordering)
filterset = filters.DeviceTypeFilterSet filterset = filters.DeviceTypeFilterSet
table = tables.DeviceTypeTable table = tables.DeviceTypeTable
form = forms.DeviceTypeBulkEditForm form = forms.DeviceTypeBulkEditForm
class DeviceTypeBulkDeleteView(BulkDeleteView): class DeviceTypeBulkDeleteView(BulkDeleteView):
queryset = DeviceType.objects.prefetch_related('manufacturer').annotate(instance_count=Count('instances')) queryset = DeviceType.objects.prefetch_related('manufacturer').annotate(
instance_count=Count('instances')
).order_by(*DeviceType._meta.ordering)
filterset = filters.DeviceTypeFilterSet filterset = filters.DeviceTypeFilterSet
table = tables.DeviceTypeTable table = tables.DeviceTypeTable
@ -2152,7 +2172,9 @@ class InterfaceConnectionsListView(ObjectListView):
# #
class VirtualChassisListView(ObjectListView): class VirtualChassisListView(ObjectListView):
queryset = VirtualChassis.objects.prefetch_related('master').annotate(member_count=Count('members')) queryset = VirtualChassis.objects.prefetch_related('master').annotate(
member_count=Count('members')
).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
@ -2385,7 +2407,7 @@ class PowerPanelListView(ObjectListView):
'site', 'rack_group' 'site', 'rack_group'
).annotate( ).annotate(
powerfeed_count=Count('powerfeeds') powerfeed_count=Count('powerfeeds')
) ).order_by(*PowerPanel._meta.ordering)
filterset = filters.PowerPanelFilterSet filterset = filters.PowerPanelFilterSet
filterset_form = forms.PowerPanelFilterForm filterset_form = forms.PowerPanelFilterForm
table = tables.PowerPanelTable table = tables.PowerPanelTable
@ -2437,7 +2459,7 @@ class PowerPanelBulkDeleteView(BulkDeleteView):
'site', 'rack_group' 'site', 'rack_group'
).annotate( ).annotate(
rack_count=Count('powerfeeds') rack_count=Count('powerfeeds')
) ).order_by(*PowerPanel._meta.ordering)
filterset = filters.PowerPanelFilterSet filterset = filters.PowerPanelFilterSet
table = tables.PowerPanelTable table = tables.PowerPanelTable

View File

@ -0,0 +1,28 @@
# Generated by Django 3.1b1 on 2020-07-16 16:01
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('extras', '0045_configcontext_changelog'),
]
operations = [
migrations.AlterField(
model_name='configcontext',
name='data',
field=models.JSONField(),
),
migrations.AlterField(
model_name='jobresult',
name='data',
field=models.JSONField(blank=True, null=True),
),
migrations.AlterField(
model_name='objectchange',
name='object_data',
field=models.JSONField(editable=False),
),
]

View File

@ -1,7 +1,6 @@
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.contrib.postgres.fields import JSONField
from django.db import models from django.db import models
from django.urls import reverse from django.urls import reverse
@ -104,7 +103,7 @@ class ObjectChange(models.Model):
max_length=200, max_length=200,
editable=False editable=False
) )
object_data = JSONField( object_data = models.JSONField(
editable=False editable=False
) )

View File

@ -5,7 +5,6 @@ from collections import OrderedDict
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.contrib.postgres.fields import JSONField
from django.core.validators import ValidationError from django.core.validators import ValidationError
from django.db import models from django.db import models
from django.http import HttpResponse from django.http import HttpResponse
@ -499,7 +498,7 @@ class ConfigContext(ChangeLoggedModel):
related_name='+', related_name='+',
blank=True blank=True
) )
data = JSONField() data = models.JSONField()
objects = ConfigContextQuerySet.as_manager() objects = ConfigContextQuerySet.as_manager()
@ -526,7 +525,7 @@ class ConfigContextModel(models.Model):
A model which includes local configuration context data. This local data will override any inherited data from A model which includes local configuration context data. This local data will override any inherited data from
ConfigContexts. ConfigContexts.
""" """
local_context_data = JSONField( local_context_data = models.JSONField(
blank=True, blank=True,
null=True, null=True,
) )
@ -627,7 +626,7 @@ class JobResult(models.Model):
choices=JobResultStatusChoices, choices=JobResultStatusChoices,
default=JobResultStatusChoices.STATUS_PENDING default=JobResultStatusChoices.STATUS_PENDING
) )
data = JSONField( data = models.JSONField(
null=True, null=True,
blank=True blank=True
) )

View File

@ -11,7 +11,7 @@ from django import forms
from django.conf import settings from django.conf import settings
from django.core.validators import RegexValidator from django.core.validators import RegexValidator
from django.db import transaction from django.db import transaction
from django.utils.decorators import classproperty from django.utils.functional import classproperty
from django_rq import job from django_rq import job
from extras.api.serializers import ScriptOutputSerializer from extras.api.serializers import ScriptOutputSerializer

View File

@ -24,7 +24,7 @@ class VRFViewSet(CustomFieldModelViewSet):
queryset = VRF.objects.prefetch_related('tenant').prefetch_related('tags').annotate( queryset = VRF.objects.prefetch_related('tenant').prefetch_related('tags').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
@ -36,7 +36,7 @@ class VRFViewSet(CustomFieldModelViewSet):
class RIRViewSet(ModelViewSet): class RIRViewSet(ModelViewSet):
queryset = RIR.objects.annotate( queryset = RIR.objects.annotate(
aggregate_count=Count('aggregates') aggregate_count=Count('aggregates')
) ).order_by(*RIR._meta.ordering)
serializer_class = serializers.RIRSerializer serializer_class = serializers.RIRSerializer
filterset_class = filters.RIRFilterSet filterset_class = filters.RIRFilterSet
@ -59,7 +59,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
@ -246,7 +246,7 @@ 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=Count('vlans')
) ).order_by(*VLANGroup._meta.ordering)
serializer_class = serializers.VLANGroupSerializer serializer_class = serializers.VLANGroupSerializer
filterset_class = filters.VLANGroupFilterSet filterset_class = filters.VLANGroupFilterSet
@ -260,7 +260,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

View File

@ -78,7 +78,7 @@ class VRFBulkDeleteView(BulkDeleteView):
# #
class RIRListView(ObjectListView): class RIRListView(ObjectListView):
queryset = RIR.objects.annotate(aggregate_count=Count('aggregates')) queryset = RIR.objects.annotate(aggregate_count=Count('aggregates')).order_by(*RIR._meta.ordering)
filterset = filters.RIRFilterSet filterset = filters.RIRFilterSet
filterset_form = forms.RIRFilterForm filterset_form = forms.RIRFilterForm
table = tables.RIRDetailTable table = tables.RIRDetailTable
@ -171,7 +171,7 @@ class RIRBulkImportView(BulkImportView):
class RIRBulkDeleteView(BulkDeleteView): class RIRBulkDeleteView(BulkDeleteView):
queryset = RIR.objects.annotate(aggregate_count=Count('aggregates')) queryset = RIR.objects.annotate(aggregate_count=Count('aggregates')).order_by(*RIR._meta.ordering)
filterset = filters.RIRFilterSet filterset = filters.RIRFilterSet
table = tables.RIRTable table = tables.RIRTable
@ -183,7 +183,7 @@ class RIRBulkDeleteView(BulkDeleteView):
class AggregateListView(ObjectListView): class AggregateListView(ObjectListView):
queryset = Aggregate.objects.prefetch_related('rir').annotate( queryset = Aggregate.objects.prefetch_related('rir').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
@ -650,7 +650,9 @@ class IPAddressBulkDeleteView(BulkDeleteView):
# #
class VLANGroupListView(ObjectListView): class VLANGroupListView(ObjectListView):
queryset = VLANGroup.objects.prefetch_related('site').annotate(vlan_count=Count('vlans')) queryset = VLANGroup.objects.prefetch_related('site').annotate(
vlan_count=Count('vlans')
).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
@ -672,7 +674,9 @@ class VLANGroupBulkImportView(BulkImportView):
class VLANGroupBulkDeleteView(BulkDeleteView): class VLANGroupBulkDeleteView(BulkDeleteView):
queryset = VLANGroup.objects.prefetch_related('site').annotate(vlan_count=Count('vlans')) queryset = VLANGroup.objects.prefetch_related('site').annotate(
vlan_count=Count('vlans')
).order_by(*VLANGroup._meta.ordering)
filterset = filters.VLANGroupFilterSet filterset = filters.VLANGroupFilterSet
table = tables.VLANGroupTable table = tables.VLANGroupTable

View File

@ -46,7 +46,9 @@ SEARCH_MAX_RESULTS = 15
SEARCH_TYPES = OrderedDict(( SEARCH_TYPES = OrderedDict((
# Circuits # Circuits
('provider', { ('provider', {
'queryset': Provider.objects.annotate(count_circuits=Count('circuits')), 'queryset': Provider.objects.annotate(
count_circuits=Count('circuits')
).order_by(*Provider._meta.ordering),
'filterset': ProviderFilterSet, 'filterset': ProviderFilterSet,
'table': ProviderTable, 'table': ProviderTable,
'url': 'circuits:provider_list', 'url': 'circuits:provider_list',
@ -73,13 +75,17 @@ SEARCH_TYPES = OrderedDict((
'url': 'dcim:rack_list', 'url': 'dcim:rack_list',
}), }),
('rackgroup', { ('rackgroup', {
'queryset': RackGroup.objects.prefetch_related('site').annotate(rack_count=Count('racks')), 'queryset': RackGroup.objects.prefetch_related('site').annotate(
rack_count=Count('racks')
).order_by(*RackGroup._meta.ordering),
'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(instance_count=Count('instances')), 'queryset': DeviceType.objects.prefetch_related('manufacturer').annotate(
instance_count=Count('instances')
).order_by(*DeviceType._meta.ordering),
'filterset': DeviceTypeFilterSet, 'filterset': DeviceTypeFilterSet,
'table': DeviceTypeTable, 'table': DeviceTypeTable,
'url': 'dcim:devicetype_list', 'url': 'dcim:devicetype_list',
@ -93,7 +99,9 @@ SEARCH_TYPES = OrderedDict((
'url': 'dcim:device_list', 'url': 'dcim:device_list',
}), }),
('virtualchassis', { ('virtualchassis', {
'queryset': VirtualChassis.objects.prefetch_related('master').annotate(member_count=Count('members')), 'queryset': VirtualChassis.objects.prefetch_related('master').annotate(
member_count=Count('members')
).order_by(*VirtualChassis._meta.ordering),
'filterset': VirtualChassisFilterSet, 'filterset': VirtualChassisFilterSet,
'table': VirtualChassisTable, 'table': VirtualChassisTable,
'url': 'dcim:virtualchassis_list', 'url': 'dcim:virtualchassis_list',

View File

@ -27,7 +27,7 @@ ERR_PRIVKEY_INVALID = "Invalid private key."
class SecretRoleViewSet(ModelViewSet): class SecretRoleViewSet(ModelViewSet):
queryset = SecretRole.objects.annotate( queryset = SecretRole.objects.annotate(
secret_count=Count('secrets') secret_count=Count('secrets')
) ).order_by(*SecretRole._meta.ordering)
serializer_class = serializers.SecretRoleSerializer serializer_class = serializers.SecretRoleSerializer
filterset_class = filters.SecretRoleFilterSet filterset_class = filters.SecretRoleFilterSet

View File

@ -29,7 +29,7 @@ def get_session_key(request):
# #
class SecretRoleListView(ObjectListView): class SecretRoleListView(ObjectListView):
queryset = SecretRole.objects.annotate(secret_count=Count('secrets')) queryset = SecretRole.objects.annotate(secret_count=Count('secrets')).order_by(*SecretRole._meta.ordering)
table = tables.SecretRoleTable table = tables.SecretRoleTable
@ -49,7 +49,7 @@ class SecretRoleBulkImportView(BulkImportView):
class SecretRoleBulkDeleteView(BulkDeleteView): class SecretRoleBulkDeleteView(BulkDeleteView):
queryset = SecretRole.objects.annotate(secret_count=Count('secrets')) queryset = SecretRole.objects.annotate(secret_count=Count('secrets')).order_by(*SecretRole._meta.ordering)
table = tables.SecretRoleTable table = tables.SecretRoleTable

View File

@ -9,7 +9,7 @@
{% endif %} {% endif %}
{% for p in page.smart_pages %} {% for p in page.smart_pages %}
{% if p %} {% if p %}
<li{% ifequal page.number p %} class="active"{% endifequal %}><a href="{% querystring request page=p %}">{{ p }}</a></li> <li{% if page.number == p %} class="active"{% endif %}><a href="{% querystring request page=p %}">{{ p }}</a></li>
{% else %} {% else %}
<li class="disabled"><span>&hellip;</span></li> <li class="disabled"><span>&hellip;</span></li>
{% endif %} {% endif %}

View File

@ -9,21 +9,21 @@
<div class="row"> <div class="row">
<div class="col-sm-3 col-md-2 col-md-offset-1"> <div class="col-sm-3 col-md-2 col-md-offset-1">
<ul class="nav nav-pills nav-stacked"> <ul class="nav nav-pills nav-stacked">
<li{% ifequal active_tab "profile" %} class="active"{% endifequal %}> <li{% if active_tab == "profile" %} class="active"{% endif %}>
<a href="{% url 'user:profile' %}">Profile</a> <a href="{% url 'user:profile' %}">Profile</a>
</li> </li>
<li{% ifequal active_tab "preferences" %} class="active"{% endifequal %}> <li{% if active_tab == "preferences" %} class="active"{% endif %}>
<a href="{% url 'user:preferences' %}">Preferences</a> <a href="{% url 'user:preferences' %}">Preferences</a>
</li> </li>
{% if not request.user.ldap_username %} {% if not request.user.ldap_username %}
<li{% ifequal active_tab "change_password" %} class="active"{% endifequal %}> <li{% if active_tab == "change_password" %} class="active"{% endif %}>
<a href="{% url 'user:change_password' %}">Change Password</a> <a href="{% url 'user:change_password' %}">Change Password</a>
</li> </li>
{% endif %} {% endif %}
<li{% ifequal active_tab "api_tokens" %} class="active"{% endifequal %}> <li{% if active_tab == "api_tokens" %} class="active"{% endif %}>
<a href="{% url 'user:token_list' %}">API Tokens</a> <a href="{% url 'user:token_list' %}">API Tokens</a>
</li> </li>
<li{% ifequal active_tab "userkey" %} class="active"{% endifequal %}> <li{% if active_tab == "userkey" %} class="active"{% endif %}>
<a href="{% url 'user:userkey' %}">User Key</a> <a href="{% url 'user:userkey' %}">User Key</a>
</li> </li>
</ul> </ul>

View File

@ -15,8 +15,12 @@ from . import serializers
# #
class TenantGroupViewSet(ModelViewSet): class TenantGroupViewSet(ModelViewSet):
queryset = TenantGroup.objects.annotate( queryset = TenantGroup.objects.add_related_count(
tenant_count=get_subquery(Tenant, 'group') TenantGroup.objects.all(),
Tenant,
'group',
'tenant_count',
cumulative=True
) )
serializer_class = serializers.TenantGroupSerializer serializer_class = serializers.TenantGroupSerializer
filterset_class = filters.TenantGroupFilterSet filterset_class = filters.TenantGroupFilterSet

View File

@ -43,7 +43,13 @@ class TenantGroupBulkImportView(BulkImportView):
class TenantGroupBulkDeleteView(BulkDeleteView): class TenantGroupBulkDeleteView(BulkDeleteView):
queryset = TenantGroup.objects.annotate(tenant_count=Count('tenants')) queryset = TenantGroup.objects.add_related_count(
TenantGroup.objects.all(),
Tenant,
'group',
'tenant_count',
cumulative=True
)
table = tables.TenantGroupTable table = tables.TenantGroupTable

View File

@ -0,0 +1,23 @@
# Generated by Django 3.1b1 on 2020-07-16 16:01
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('users', '0009_replicate_permissions'),
]
operations = [
migrations.AlterField(
model_name='objectpermission',
name='constraints',
field=models.JSONField(blank=True, null=True),
),
migrations.AlterField(
model_name='userconfig',
name='data',
field=models.JSONField(default=dict),
),
]

View File

@ -3,7 +3,7 @@ import os
from django.contrib.auth.models import Group, User from django.contrib.auth.models import Group, User
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.contrib.postgres.fields import ArrayField, JSONField from django.contrib.postgres.fields import ArrayField
from django.core.validators import MinLengthValidator from django.core.validators import MinLengthValidator
from django.db import models from django.db import models
from django.db.models.signals import post_save from django.db.models.signals import post_save
@ -56,7 +56,7 @@ class UserConfig(models.Model):
on_delete=models.CASCADE, on_delete=models.CASCADE,
related_name='config' related_name='config'
) )
data = JSONField( data = models.JSONField(
default=dict default=dict
) )
@ -265,7 +265,7 @@ class ObjectPermission(models.Model):
base_field=models.CharField(max_length=30), base_field=models.CharField(max_length=30),
help_text="The list of actions granted by this permission" help_text="The list of actions granted by this permission"
) )
constraints = JSONField( constraints = models.JSONField(
blank=True, blank=True,
null=True, null=True,
help_text="Queryset filter matching the applicable objects of the selected type(s)" help_text="Queryset filter matching the applicable objects of the selected type(s)"

View File

@ -345,10 +345,9 @@ class ModelViewSet(_ModelViewSet):
try: try:
return super().dispatch(request, *args, **kwargs) return super().dispatch(request, *args, **kwargs)
except ProtectedError as e: except ProtectedError as e:
models = [ protected_objects = list(e.protected_objects)
'{} ({})'.format(o, o._meta) for o in e.protected_objects.all() msg = f'Unable to delete object. {len(protected_objects)} dependent objects were found: '
] msg += ', '.join([f'{obj} ({obj.pk})' for obj in protected_objects])
msg = 'Unable to delete object. The following dependent objects were found: {}'.format(', '.join(models))
logger.warning(msg) logger.warning(msg)
return self.finalize_response( return self.finalize_response(
request, request,

View File

@ -7,31 +7,17 @@ def handle_protectederror(obj, request, e):
""" """
Generate a user-friendly error message in response to a ProtectedError exception. Generate a user-friendly error message in response to a ProtectedError exception.
""" """
try: protected_objects = list(e.protected_objects)
dep_class = e.protected_objects[0]._meta.verbose_name_plural err_message = f"Unable to delete {obj._meta.verbose_name} <strong>{obj}</strong>. " \
except IndexError: f"{len(protected_objects)} dependent objects were found: "
raise e
# Grammar for single versus multiple triggering objects
if type(obj) in (list, tuple):
err_message = "Unable to delete the requested {}. The following dependent {} were found: ".format(
obj[0]._meta.verbose_name_plural,
dep_class,
)
else:
err_message = "Unable to delete {} {}. The following dependent {} were found: ".format(
obj._meta.verbose_name,
obj,
dep_class,
)
# Append dependent objects to error message # Append dependent objects to error message
dependent_objects = [] dependent_objects = []
for obj in e.protected_objects: for dependent in protected_objects:
if hasattr(obj, 'get_absolute_url'): if hasattr(obj, 'get_absolute_url'):
dependent_objects.append('<a href="{}">{}</a>'.format(obj.get_absolute_url(), escape(obj))) dependent_objects.append(f'<a href="{dependent.get_absolute_url()}">{escape(dependent)}</a>')
else: else:
dependent_objects.append(str(obj)) dependent_objects.append(str(dependent))
err_message += ', '.join(dependent_objects) err_message += ', '.join(dependent_objects)
messages.error(request, mark_safe(err_message)) messages.error(request, mark_safe(err_message))

View File

@ -8,7 +8,7 @@ import yaml
from django import forms from django import forms
from django.conf import settings from django.conf import settings
from django.contrib.postgres.forms import SimpleArrayField from django.contrib.postgres.forms import SimpleArrayField
from django.contrib.postgres.forms.jsonb import JSONField as _JSONField, InvalidJSONInput from django.forms.fields import JSONField as _JSONField, InvalidJSONInput
from django.core.exceptions import MultipleObjectsReturned from django.core.exceptions import MultipleObjectsReturned
from django.db.models import Count from django.db.models import Count
from django.forms import BoundField from django.forms import BoundField

View File

@ -22,7 +22,7 @@ from . import serializers
class ClusterTypeViewSet(ModelViewSet): class ClusterTypeViewSet(ModelViewSet):
queryset = ClusterType.objects.annotate( queryset = ClusterType.objects.annotate(
cluster_count=Count('clusters') cluster_count=Count('clusters')
) ).order_by(*ClusterType._meta.ordering)
serializer_class = serializers.ClusterTypeSerializer serializer_class = serializers.ClusterTypeSerializer
filterset_class = filters.ClusterTypeFilterSet filterset_class = filters.ClusterTypeFilterSet
@ -30,7 +30,7 @@ class ClusterTypeViewSet(ModelViewSet):
class ClusterGroupViewSet(ModelViewSet): class ClusterGroupViewSet(ModelViewSet):
queryset = ClusterGroup.objects.annotate( queryset = ClusterGroup.objects.annotate(
cluster_count=Count('clusters') cluster_count=Count('clusters')
) ).order_by(*ClusterGroup._meta.ordering)
serializer_class = serializers.ClusterGroupSerializer serializer_class = serializers.ClusterGroupSerializer
filterset_class = filters.ClusterGroupFilterSet filterset_class = filters.ClusterGroupFilterSet
@ -41,7 +41,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

View File

@ -0,0 +1,18 @@
# Generated by Django 3.1b1 on 2020-07-16 16:01
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('virtualization', '0016_replicate_interfaces'),
]
operations = [
migrations.AlterField(
model_name='virtualmachine',
name='local_context_data',
field=models.JSONField(blank=True, null=True),
),
]

View File

@ -22,7 +22,7 @@ from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterf
# #
class ClusterTypeListView(ObjectListView): class ClusterTypeListView(ObjectListView):
queryset = ClusterType.objects.annotate(cluster_count=Count('clusters')) queryset = ClusterType.objects.annotate(cluster_count=Count('clusters')).order_by(*ClusterType._meta.ordering)
table = tables.ClusterTypeTable table = tables.ClusterTypeTable
@ -42,7 +42,7 @@ class ClusterTypeBulkImportView(BulkImportView):
class ClusterTypeBulkDeleteView(BulkDeleteView): class ClusterTypeBulkDeleteView(BulkDeleteView):
queryset = ClusterType.objects.annotate(cluster_count=Count('clusters')) queryset = ClusterType.objects.annotate(cluster_count=Count('clusters')).order_by(*ClusterType._meta.ordering)
table = tables.ClusterTypeTable table = tables.ClusterTypeTable
@ -51,7 +51,7 @@ class ClusterTypeBulkDeleteView(BulkDeleteView):
# #
class ClusterGroupListView(ObjectListView): class ClusterGroupListView(ObjectListView):
queryset = ClusterGroup.objects.annotate(cluster_count=Count('clusters')) queryset = ClusterGroup.objects.annotate(cluster_count=Count('clusters')).order_by(*ClusterGroup._meta.ordering)
table = tables.ClusterGroupTable table = tables.ClusterGroupTable
@ -71,7 +71,7 @@ class ClusterGroupBulkImportView(BulkImportView):
class ClusterGroupBulkDeleteView(BulkDeleteView): class ClusterGroupBulkDeleteView(BulkDeleteView):
queryset = ClusterGroup.objects.annotate(cluster_count=Count('clusters')) queryset = ClusterGroup.objects.annotate(cluster_count=Count('clusters')).order_by(*ClusterGroup._meta.ordering)
table = tables.ClusterGroupTable table = tables.ClusterGroupTable

View File

@ -1,4 +1,4 @@
Django>=3.0,<3.1 Django==3.1rc1
django-cacheops==5.0.1 django-cacheops==5.0.1
django-cors-headers==3.4.0 django-cors-headers==3.4.0
django-debug-toolbar==2.2 django-debug-toolbar==2.2