mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-23 04:22:01 -06:00
Merge pull request #4861 from netbox-community/django-31
Upgrade to Django 3.1 (v2.9)
This commit is contained in:
commit
de6202c160
@ -19,7 +19,7 @@ from . import serializers
|
||||
class ProviderViewSet(CustomFieldModelViewSet):
|
||||
queryset = Provider.objects.prefetch_related('tags').annotate(
|
||||
circuit_count=Count('circuits')
|
||||
)
|
||||
).order_by(*Provider._meta.ordering)
|
||||
serializer_class = serializers.ProviderSerializer
|
||||
filterset_class = filters.ProviderFilterSet
|
||||
|
||||
@ -41,7 +41,7 @@ class ProviderViewSet(CustomFieldModelViewSet):
|
||||
class CircuitTypeViewSet(ModelViewSet):
|
||||
queryset = CircuitType.objects.annotate(
|
||||
circuit_count=Count('circuits')
|
||||
)
|
||||
).order_by(*CircuitType._meta.ordering)
|
||||
serializer_class = serializers.CircuitTypeSerializer
|
||||
filterset_class = filters.CircuitTypeFilterSet
|
||||
|
||||
|
@ -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),
|
||||
),
|
||||
]
|
@ -275,9 +275,10 @@ class CircuitTermination(CableTermination):
|
||||
blank=True,
|
||||
null=True
|
||||
)
|
||||
connection_status = models.NullBooleanField(
|
||||
connection_status = models.BooleanField(
|
||||
choices=CONNECTION_STATUS_CHOICES,
|
||||
blank=True
|
||||
blank=True,
|
||||
null=True
|
||||
)
|
||||
port_speed = models.PositiveIntegerField(
|
||||
verbose_name='Port speed (Kbps)'
|
||||
|
@ -21,7 +21,7 @@ from .models import Circuit, CircuitTermination, CircuitType, Provider
|
||||
#
|
||||
|
||||
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_form = forms.ProviderFilterForm
|
||||
table = tables.ProviderTable
|
||||
@ -73,14 +73,14 @@ class ProviderBulkImportView(BulkImportView):
|
||||
|
||||
|
||||
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
|
||||
table = tables.ProviderTable
|
||||
form = forms.ProviderBulkEditForm
|
||||
|
||||
|
||||
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
|
||||
table = tables.ProviderTable
|
||||
|
||||
@ -90,7 +90,7 @@ class ProviderBulkDeleteView(BulkDeleteView):
|
||||
#
|
||||
|
||||
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
|
||||
|
||||
|
||||
@ -110,7 +110,7 @@ class CircuitTypeBulkImportView(BulkImportView):
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
|
@ -74,8 +74,12 @@ class CableTraceMixin(object):
|
||||
#
|
||||
|
||||
class RegionViewSet(ModelViewSet):
|
||||
queryset = Region.objects.annotate(
|
||||
site_count=Count('sites')
|
||||
queryset = Region.objects.add_related_count(
|
||||
Region.objects.all(),
|
||||
Site,
|
||||
'region',
|
||||
'site_count',
|
||||
cumulative=True
|
||||
)
|
||||
serializer_class = serializers.RegionSerializer
|
||||
filterset_class = filters.RegionFilterSet
|
||||
@ -95,7 +99,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
|
||||
|
||||
@ -115,9 +119,13 @@ class SiteViewSet(CustomFieldModelViewSet):
|
||||
#
|
||||
|
||||
class RackGroupViewSet(ModelViewSet):
|
||||
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')
|
||||
serializer_class = serializers.RackGroupSerializer
|
||||
filterset_class = filters.RackGroupFilterSet
|
||||
|
||||
@ -129,7 +137,7 @@ class RackGroupViewSet(ModelViewSet):
|
||||
class RackRoleViewSet(ModelViewSet):
|
||||
queryset = RackRole.objects.annotate(
|
||||
rack_count=Count('racks')
|
||||
)
|
||||
).order_by(*RackRole._meta.ordering)
|
||||
serializer_class = serializers.RackRoleSerializer
|
||||
filterset_class = filters.RackRoleFilterSet
|
||||
|
||||
@ -144,7 +152,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
|
||||
|
||||
@ -217,7 +225,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
|
||||
|
||||
@ -229,7 +237,7 @@ class ManufacturerViewSet(ModelViewSet):
|
||||
class DeviceTypeViewSet(CustomFieldModelViewSet):
|
||||
queryset = DeviceType.objects.prefetch_related('manufacturer', 'tags').annotate(
|
||||
device_count=Count('instances')
|
||||
)
|
||||
).order_by(*DeviceType._meta.ordering)
|
||||
serializer_class = serializers.DeviceTypeSerializer
|
||||
filterset_class = filters.DeviceTypeFilterSet
|
||||
|
||||
@ -294,7 +302,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
|
||||
|
||||
@ -307,7 +315,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
|
||||
|
||||
@ -583,7 +591,7 @@ class CableViewSet(ModelViewSet):
|
||||
class VirtualChassisViewSet(ModelViewSet):
|
||||
queryset = VirtualChassis.objects.prefetch_related('tags').annotate(
|
||||
member_count=Count('members')
|
||||
)
|
||||
).order_by(*VirtualChassis._meta.ordering)
|
||||
serializer_class = serializers.VirtualChassisSerializer
|
||||
filterset_class = filters.VirtualChassisFilterSet
|
||||
|
||||
@ -597,7 +605,7 @@ class PowerPanelViewSet(ModelViewSet):
|
||||
'site', 'rack_group'
|
||||
).annotate(
|
||||
powerfeed_count=Count('powerfeeds')
|
||||
)
|
||||
).order_by(*PowerPanel._meta.ordering)
|
||||
serializer_class = serializers.PowerPanelSerializer
|
||||
filterset_class = filters.PowerPanelFilterSet
|
||||
|
||||
|
@ -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),
|
||||
),
|
||||
]
|
23
netbox/dcim/migrations/0114_update_jsonfield.py
Normal file
23
netbox/dcim/migrations/0114_update_jsonfield.py
Normal 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),
|
||||
),
|
||||
]
|
@ -6,7 +6,7 @@ from django.conf import settings
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
|
||||
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.validators import MaxValueValidator, MinValueValidator
|
||||
from django.db import models
|
||||
@ -1280,7 +1280,7 @@ class Platform(ChangeLoggedModel):
|
||||
verbose_name='NAPALM driver',
|
||||
help_text='The name of the NAPALM driver to use when interacting with devices'
|
||||
)
|
||||
napalm_args = JSONField(
|
||||
napalm_args = models.JSONField(
|
||||
blank=True,
|
||||
null=True,
|
||||
verbose_name='NAPALM arguments',
|
||||
@ -1905,9 +1905,10 @@ class PowerFeed(ChangeLoggedModel, CableTermination, CustomFieldModel):
|
||||
blank=True,
|
||||
null=True
|
||||
)
|
||||
connection_status = models.NullBooleanField(
|
||||
connection_status = models.BooleanField(
|
||||
choices=CONNECTION_STATUS_CHOICES,
|
||||
blank=True
|
||||
blank=True,
|
||||
null=True
|
||||
)
|
||||
name = models.CharField(
|
||||
max_length=50
|
||||
|
@ -264,9 +264,10 @@ class ConsolePort(CableTermination, ComponentModel):
|
||||
blank=True,
|
||||
null=True
|
||||
)
|
||||
connection_status = models.NullBooleanField(
|
||||
connection_status = models.BooleanField(
|
||||
choices=CONNECTION_STATUS_CHOICES,
|
||||
blank=True
|
||||
blank=True,
|
||||
null=True
|
||||
)
|
||||
tags = TaggableManager(through=TaggedItem)
|
||||
|
||||
@ -304,9 +305,10 @@ class ConsoleServerPort(CableTermination, ComponentModel):
|
||||
blank=True,
|
||||
help_text='Physical port type'
|
||||
)
|
||||
connection_status = models.NullBooleanField(
|
||||
connection_status = models.BooleanField(
|
||||
choices=CONNECTION_STATUS_CHOICES,
|
||||
blank=True
|
||||
blank=True,
|
||||
null=True
|
||||
)
|
||||
tags = TaggableManager(through=TaggedItem)
|
||||
|
||||
@ -370,9 +372,10 @@ class PowerPort(CableTermination, ComponentModel):
|
||||
blank=True,
|
||||
null=True
|
||||
)
|
||||
connection_status = models.NullBooleanField(
|
||||
connection_status = models.BooleanField(
|
||||
choices=CONNECTION_STATUS_CHOICES,
|
||||
blank=True
|
||||
blank=True,
|
||||
null=True
|
||||
)
|
||||
tags = TaggableManager(through=TaggedItem)
|
||||
|
||||
@ -505,9 +508,10 @@ class PowerOutlet(CableTermination, ComponentModel):
|
||||
blank=True,
|
||||
help_text="Phase (for three-phase feeds)"
|
||||
)
|
||||
connection_status = models.NullBooleanField(
|
||||
connection_status = models.BooleanField(
|
||||
choices=CONNECTION_STATUS_CHOICES,
|
||||
blank=True
|
||||
blank=True,
|
||||
null=True
|
||||
)
|
||||
tags = TaggableManager(through=TaggedItem)
|
||||
|
||||
@ -598,9 +602,10 @@ class Interface(CableTermination, ComponentModel, BaseInterface):
|
||||
blank=True,
|
||||
null=True
|
||||
)
|
||||
connection_status = models.NullBooleanField(
|
||||
connection_status = models.BooleanField(
|
||||
choices=CONNECTION_STATUS_CHOICES,
|
||||
blank=True
|
||||
blank=True,
|
||||
null=True
|
||||
)
|
||||
lag = models.ForeignKey(
|
||||
to='self',
|
||||
|
@ -133,7 +133,13 @@ class RegionBulkImportView(BulkImportView):
|
||||
|
||||
|
||||
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
|
||||
table = tables.RegionTable
|
||||
|
||||
@ -238,7 +244,13 @@ class RackGroupBulkImportView(BulkImportView):
|
||||
|
||||
|
||||
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
|
||||
table = tables.RackGroupTable
|
||||
|
||||
@ -248,7 +260,7 @@ class RackGroupBulkDeleteView(BulkDeleteView):
|
||||
#
|
||||
|
||||
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
|
||||
|
||||
|
||||
@ -268,7 +280,7 @@ class RackRoleBulkImportView(BulkImportView):
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
@ -281,7 +293,7 @@ class RackListView(ObjectListView):
|
||||
'site', 'group', 'tenant', 'role', 'devices__device_type'
|
||||
).annotate(
|
||||
device_count=Count('devices')
|
||||
)
|
||||
).order_by(*Rack._meta.ordering)
|
||||
filterset = filters.RackFilterSet
|
||||
filterset_form = forms.RackFilterForm
|
||||
table = tables.RackDetailTable
|
||||
@ -465,7 +477,7 @@ class ManufacturerListView(ObjectListView):
|
||||
devicetype_count=Count('device_types', distinct=True),
|
||||
inventoryitem_count=Count('inventory_items', distinct=True),
|
||||
platform_count=Count('platforms', distinct=True),
|
||||
)
|
||||
).order_by(*Manufacturer._meta.ordering)
|
||||
table = tables.ManufacturerTable
|
||||
|
||||
|
||||
@ -485,7 +497,9 @@ class ManufacturerBulkImportView(BulkImportView):
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
@ -494,7 +508,9 @@ class ManufacturerBulkDeleteView(BulkDeleteView):
|
||||
#
|
||||
|
||||
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_form = forms.DeviceTypeFilterForm
|
||||
table = tables.DeviceTypeTable
|
||||
@ -602,14 +618,18 @@ class DeviceTypeImportView(ObjectImportView):
|
||||
|
||||
|
||||
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
|
||||
table = tables.DeviceTypeTable
|
||||
form = forms.DeviceTypeBulkEditForm
|
||||
|
||||
|
||||
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
|
||||
table = tables.DeviceTypeTable
|
||||
|
||||
@ -2152,7 +2172,9 @@ class InterfaceConnectionsListView(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
|
||||
filterset = filters.VirtualChassisFilterSet
|
||||
filterset_form = forms.VirtualChassisFilterForm
|
||||
@ -2385,7 +2407,7 @@ class PowerPanelListView(ObjectListView):
|
||||
'site', 'rack_group'
|
||||
).annotate(
|
||||
powerfeed_count=Count('powerfeeds')
|
||||
)
|
||||
).order_by(*PowerPanel._meta.ordering)
|
||||
filterset = filters.PowerPanelFilterSet
|
||||
filterset_form = forms.PowerPanelFilterForm
|
||||
table = tables.PowerPanelTable
|
||||
@ -2437,7 +2459,7 @@ class PowerPanelBulkDeleteView(BulkDeleteView):
|
||||
'site', 'rack_group'
|
||||
).annotate(
|
||||
rack_count=Count('powerfeeds')
|
||||
)
|
||||
).order_by(*PowerPanel._meta.ordering)
|
||||
filterset = filters.PowerPanelFilterSet
|
||||
table = tables.PowerPanelTable
|
||||
|
||||
|
28
netbox/extras/migrations/0046_update_jsonfield.py
Normal file
28
netbox/extras/migrations/0046_update_jsonfield.py
Normal 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),
|
||||
),
|
||||
]
|
@ -1,7 +1,6 @@
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.contrib.postgres.fields import JSONField
|
||||
from django.db import models
|
||||
from django.urls import reverse
|
||||
|
||||
@ -104,7 +103,7 @@ class ObjectChange(models.Model):
|
||||
max_length=200,
|
||||
editable=False
|
||||
)
|
||||
object_data = JSONField(
|
||||
object_data = models.JSONField(
|
||||
editable=False
|
||||
)
|
||||
|
||||
|
@ -5,7 +5,6 @@ from collections import OrderedDict
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.contrib.postgres.fields import JSONField
|
||||
from django.core.validators import ValidationError
|
||||
from django.db import models
|
||||
from django.http import HttpResponse
|
||||
@ -499,7 +498,7 @@ class ConfigContext(ChangeLoggedModel):
|
||||
related_name='+',
|
||||
blank=True
|
||||
)
|
||||
data = JSONField()
|
||||
data = models.JSONField()
|
||||
|
||||
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
|
||||
ConfigContexts.
|
||||
"""
|
||||
local_context_data = JSONField(
|
||||
local_context_data = models.JSONField(
|
||||
blank=True,
|
||||
null=True,
|
||||
)
|
||||
@ -627,7 +626,7 @@ class JobResult(models.Model):
|
||||
choices=JobResultStatusChoices,
|
||||
default=JobResultStatusChoices.STATUS_PENDING
|
||||
)
|
||||
data = JSONField(
|
||||
data = models.JSONField(
|
||||
null=True,
|
||||
blank=True
|
||||
)
|
||||
|
@ -11,7 +11,7 @@ from django import forms
|
||||
from django.conf import settings
|
||||
from django.core.validators import RegexValidator
|
||||
from django.db import transaction
|
||||
from django.utils.decorators import classproperty
|
||||
from django.utils.functional import classproperty
|
||||
from django_rq import job
|
||||
|
||||
from extras.api.serializers import ScriptOutputSerializer
|
||||
|
@ -24,7 +24,7 @@ class VRFViewSet(CustomFieldModelViewSet):
|
||||
queryset = VRF.objects.prefetch_related('tenant').prefetch_related('tags').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
|
||||
|
||||
@ -36,7 +36,7 @@ class VRFViewSet(CustomFieldModelViewSet):
|
||||
class RIRViewSet(ModelViewSet):
|
||||
queryset = RIR.objects.annotate(
|
||||
aggregate_count=Count('aggregates')
|
||||
)
|
||||
).order_by(*RIR._meta.ordering)
|
||||
serializer_class = serializers.RIRSerializer
|
||||
filterset_class = filters.RIRFilterSet
|
||||
|
||||
@ -59,7 +59,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
|
||||
|
||||
@ -246,7 +246,7 @@ class IPAddressViewSet(CustomFieldModelViewSet):
|
||||
class VLANGroupViewSet(ModelViewSet):
|
||||
queryset = VLANGroup.objects.prefetch_related('site').annotate(
|
||||
vlan_count=Count('vlans')
|
||||
)
|
||||
).order_by(*VLANGroup._meta.ordering)
|
||||
serializer_class = serializers.VLANGroupSerializer
|
||||
filterset_class = filters.VLANGroupFilterSet
|
||||
|
||||
@ -260,7 +260,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
|
||||
|
||||
|
@ -78,7 +78,7 @@ class VRFBulkDeleteView(BulkDeleteView):
|
||||
#
|
||||
|
||||
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_form = forms.RIRFilterForm
|
||||
table = tables.RIRDetailTable
|
||||
@ -171,7 +171,7 @@ class RIRBulkImportView(BulkImportView):
|
||||
|
||||
|
||||
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
|
||||
table = tables.RIRTable
|
||||
|
||||
@ -183,7 +183,7 @@ class RIRBulkDeleteView(BulkDeleteView):
|
||||
class AggregateListView(ObjectListView):
|
||||
queryset = Aggregate.objects.prefetch_related('rir').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
|
||||
@ -650,7 +650,9 @@ class IPAddressBulkDeleteView(BulkDeleteView):
|
||||
#
|
||||
|
||||
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_form = forms.VLANGroupFilterForm
|
||||
table = tables.VLANGroupTable
|
||||
@ -672,7 +674,9 @@ class VLANGroupBulkImportView(BulkImportView):
|
||||
|
||||
|
||||
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
|
||||
table = tables.VLANGroupTable
|
||||
|
||||
|
@ -46,7 +46,9 @@ SEARCH_MAX_RESULTS = 15
|
||||
SEARCH_TYPES = OrderedDict((
|
||||
# Circuits
|
||||
('provider', {
|
||||
'queryset': Provider.objects.annotate(count_circuits=Count('circuits')),
|
||||
'queryset': Provider.objects.annotate(
|
||||
count_circuits=Count('circuits')
|
||||
).order_by(*Provider._meta.ordering),
|
||||
'filterset': ProviderFilterSet,
|
||||
'table': ProviderTable,
|
||||
'url': 'circuits:provider_list',
|
||||
@ -73,13 +75,17 @@ SEARCH_TYPES = OrderedDict((
|
||||
'url': 'dcim:rack_list',
|
||||
}),
|
||||
('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,
|
||||
'table': RackGroupTable,
|
||||
'url': 'dcim:rackgroup_list',
|
||||
}),
|
||||
('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,
|
||||
'table': DeviceTypeTable,
|
||||
'url': 'dcim:devicetype_list',
|
||||
@ -93,7 +99,9 @@ SEARCH_TYPES = OrderedDict((
|
||||
'url': 'dcim:device_list',
|
||||
}),
|
||||
('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,
|
||||
'table': VirtualChassisTable,
|
||||
'url': 'dcim:virtualchassis_list',
|
||||
|
@ -27,7 +27,7 @@ ERR_PRIVKEY_INVALID = "Invalid private key."
|
||||
class SecretRoleViewSet(ModelViewSet):
|
||||
queryset = SecretRole.objects.annotate(
|
||||
secret_count=Count('secrets')
|
||||
)
|
||||
).order_by(*SecretRole._meta.ordering)
|
||||
serializer_class = serializers.SecretRoleSerializer
|
||||
filterset_class = filters.SecretRoleFilterSet
|
||||
|
||||
|
@ -29,7 +29,7 @@ def get_session_key(request):
|
||||
#
|
||||
|
||||
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
|
||||
|
||||
|
||||
@ -49,7 +49,7 @@ class SecretRoleBulkImportView(BulkImportView):
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
|
@ -9,7 +9,7 @@
|
||||
{% endif %}
|
||||
{% for p in page.smart_pages %}
|
||||
{% 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 %}
|
||||
<li class="disabled"><span>…</span></li>
|
||||
{% endif %}
|
||||
|
@ -9,21 +9,21 @@
|
||||
<div class="row">
|
||||
<div class="col-sm-3 col-md-2 col-md-offset-1">
|
||||
<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>
|
||||
</li>
|
||||
<li{% ifequal active_tab "preferences" %} class="active"{% endifequal %}>
|
||||
<li{% if active_tab == "preferences" %} class="active"{% endif %}>
|
||||
<a href="{% url 'user:preferences' %}">Preferences</a>
|
||||
</li>
|
||||
{% 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>
|
||||
</li>
|
||||
{% 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>
|
||||
</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>
|
||||
</li>
|
||||
</ul>
|
||||
|
@ -15,8 +15,12 @@ from . import serializers
|
||||
#
|
||||
|
||||
class TenantGroupViewSet(ModelViewSet):
|
||||
queryset = TenantGroup.objects.annotate(
|
||||
tenant_count=get_subquery(Tenant, 'group')
|
||||
queryset = TenantGroup.objects.add_related_count(
|
||||
TenantGroup.objects.all(),
|
||||
Tenant,
|
||||
'group',
|
||||
'tenant_count',
|
||||
cumulative=True
|
||||
)
|
||||
serializer_class = serializers.TenantGroupSerializer
|
||||
filterset_class = filters.TenantGroupFilterSet
|
||||
|
@ -43,7 +43,13 @@ class TenantGroupBulkImportView(BulkImportView):
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
|
23
netbox/users/migrations/0010_update_jsonfield.py
Normal file
23
netbox/users/migrations/0010_update_jsonfield.py
Normal 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),
|
||||
),
|
||||
]
|
@ -3,7 +3,7 @@ import os
|
||||
|
||||
from django.contrib.auth.models import Group, User
|
||||
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.db import models
|
||||
from django.db.models.signals import post_save
|
||||
@ -56,7 +56,7 @@ class UserConfig(models.Model):
|
||||
on_delete=models.CASCADE,
|
||||
related_name='config'
|
||||
)
|
||||
data = JSONField(
|
||||
data = models.JSONField(
|
||||
default=dict
|
||||
)
|
||||
|
||||
@ -265,7 +265,7 @@ class ObjectPermission(models.Model):
|
||||
base_field=models.CharField(max_length=30),
|
||||
help_text="The list of actions granted by this permission"
|
||||
)
|
||||
constraints = JSONField(
|
||||
constraints = models.JSONField(
|
||||
blank=True,
|
||||
null=True,
|
||||
help_text="Queryset filter matching the applicable objects of the selected type(s)"
|
||||
|
@ -345,10 +345,9 @@ class ModelViewSet(_ModelViewSet):
|
||||
try:
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
except ProtectedError as e:
|
||||
models = [
|
||||
'{} ({})'.format(o, o._meta) for o in e.protected_objects.all()
|
||||
]
|
||||
msg = 'Unable to delete object. The following dependent objects were found: {}'.format(', '.join(models))
|
||||
protected_objects = list(e.protected_objects)
|
||||
msg = f'Unable to delete object. {len(protected_objects)} dependent objects were found: '
|
||||
msg += ', '.join([f'{obj} ({obj.pk})' for obj in protected_objects])
|
||||
logger.warning(msg)
|
||||
return self.finalize_response(
|
||||
request,
|
||||
|
@ -7,31 +7,17 @@ def handle_protectederror(obj, request, e):
|
||||
"""
|
||||
Generate a user-friendly error message in response to a ProtectedError exception.
|
||||
"""
|
||||
try:
|
||||
dep_class = e.protected_objects[0]._meta.verbose_name_plural
|
||||
except IndexError:
|
||||
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,
|
||||
)
|
||||
protected_objects = list(e.protected_objects)
|
||||
err_message = f"Unable to delete {obj._meta.verbose_name} <strong>{obj}</strong>. " \
|
||||
f"{len(protected_objects)} dependent objects were found: "
|
||||
|
||||
# Append dependent objects to error message
|
||||
dependent_objects = []
|
||||
for obj in e.protected_objects:
|
||||
for dependent in protected_objects:
|
||||
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:
|
||||
dependent_objects.append(str(obj))
|
||||
dependent_objects.append(str(dependent))
|
||||
err_message += ', '.join(dependent_objects)
|
||||
|
||||
messages.error(request, mark_safe(err_message))
|
||||
|
@ -8,7 +8,7 @@ import yaml
|
||||
from django import forms
|
||||
from django.conf import settings
|
||||
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.db.models import Count
|
||||
from django.forms import BoundField
|
||||
|
@ -22,7 +22,7 @@ from . import serializers
|
||||
class ClusterTypeViewSet(ModelViewSet):
|
||||
queryset = ClusterType.objects.annotate(
|
||||
cluster_count=Count('clusters')
|
||||
)
|
||||
).order_by(*ClusterType._meta.ordering)
|
||||
serializer_class = serializers.ClusterTypeSerializer
|
||||
filterset_class = filters.ClusterTypeFilterSet
|
||||
|
||||
@ -30,7 +30,7 @@ class ClusterTypeViewSet(ModelViewSet):
|
||||
class ClusterGroupViewSet(ModelViewSet):
|
||||
queryset = ClusterGroup.objects.annotate(
|
||||
cluster_count=Count('clusters')
|
||||
)
|
||||
).order_by(*ClusterGroup._meta.ordering)
|
||||
serializer_class = serializers.ClusterGroupSerializer
|
||||
filterset_class = filters.ClusterGroupFilterSet
|
||||
|
||||
@ -41,7 +41,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
|
||||
|
||||
|
18
netbox/virtualization/migrations/0017_update_jsonfield.py
Normal file
18
netbox/virtualization/migrations/0017_update_jsonfield.py
Normal 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),
|
||||
),
|
||||
]
|
@ -22,7 +22,7 @@ from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterf
|
||||
#
|
||||
|
||||
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
|
||||
|
||||
|
||||
@ -42,7 +42,7 @@ class ClusterTypeBulkImportView(BulkImportView):
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
@ -51,7 +51,7 @@ class ClusterTypeBulkDeleteView(BulkDeleteView):
|
||||
#
|
||||
|
||||
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
|
||||
|
||||
|
||||
@ -71,7 +71,7 @@ class ClusterGroupBulkImportView(BulkImportView):
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
Django>=3.0,<3.1
|
||||
Django==3.1rc1
|
||||
django-cacheops==5.0.1
|
||||
django-cors-headers==3.4.0
|
||||
django-debug-toolbar==2.2
|
||||
|
Loading…
Reference in New Issue
Block a user