12552 initial removal of mptt

This commit is contained in:
Arthur 2023-05-18 13:22:31 -07:00
parent 4208b79514
commit da0c459e73
24 changed files with 531 additions and 275 deletions

View File

@ -26,9 +26,9 @@ django-filter
# https://github.com/flavors/django-graphiql-debug-toolbar/blob/main/CHANGES.rst # https://github.com/flavors/django-graphiql-debug-toolbar/blob/main/CHANGES.rst
django-graphiql-debug-toolbar django-graphiql-debug-toolbar
# Modified Preorder Tree Traversal (recursive nesting of objects) # Adjacency-list trees for Django using recursive common table expressions.
# https://github.com/django-mptt/django-mptt/blob/main/CHANGELOG.rst # https://github.com/matthiask/django-tree-queries/blob/main/CHANGELOG.rst
django-mptt django-tree-queries
# Context managers for PostgreSQL advisory locks # Context managers for PostgreSQL advisory locks
# https://github.com/Xof/django-pglocks/blob/master/CHANGES.txt # https://github.com/Xof/django-pglocks/blob/master/CHANGES.txt

View File

@ -60,7 +60,7 @@ __all__ = [
class NestedRegionSerializer(WritableNestedSerializer): class NestedRegionSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:region-detail') url = serializers.HyperlinkedIdentityField(view_name='dcim-api:region-detail')
site_count = serializers.IntegerField(read_only=True) site_count = serializers.IntegerField(read_only=True)
_depth = serializers.IntegerField(source='level', read_only=True) _depth = serializers.IntegerField(source='tree_depth', read_only=True)
class Meta: class Meta:
model = models.Region model = models.Region
@ -73,7 +73,7 @@ class NestedRegionSerializer(WritableNestedSerializer):
class NestedSiteGroupSerializer(WritableNestedSerializer): class NestedSiteGroupSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:sitegroup-detail') url = serializers.HyperlinkedIdentityField(view_name='dcim-api:sitegroup-detail')
site_count = serializers.IntegerField(read_only=True) site_count = serializers.IntegerField(read_only=True)
_depth = serializers.IntegerField(source='level', read_only=True) _depth = serializers.IntegerField(source='tree_depth', read_only=True)
class Meta: class Meta:
model = models.SiteGroup model = models.SiteGroup
@ -98,7 +98,7 @@ class NestedSiteSerializer(WritableNestedSerializer):
class NestedLocationSerializer(WritableNestedSerializer): class NestedLocationSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:location-detail') url = serializers.HyperlinkedIdentityField(view_name='dcim-api:location-detail')
rack_count = serializers.IntegerField(read_only=True) rack_count = serializers.IntegerField(read_only=True)
_depth = serializers.IntegerField(source='level', read_only=True) _depth = serializers.IntegerField(source='tree_depth', read_only=True)
class Meta: class Meta:
model = models.Location model = models.Location
@ -258,7 +258,7 @@ class NestedDeviceBayTemplateSerializer(WritableNestedSerializer):
class NestedInventoryItemTemplateSerializer(WritableNestedSerializer): class NestedInventoryItemTemplateSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:inventoryitemtemplate-detail') url = serializers.HyperlinkedIdentityField(view_name='dcim-api:inventoryitemtemplate-detail')
_depth = serializers.IntegerField(source='level', read_only=True) _depth = serializers.IntegerField(source='tree_depth', read_only=True)
class Meta: class Meta:
model = models.InventoryItemTemplate model = models.InventoryItemTemplate
@ -433,7 +433,7 @@ class NestedDeviceBaySerializer(WritableNestedSerializer):
class NestedInventoryItemSerializer(WritableNestedSerializer): class NestedInventoryItemSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:inventoryitem-detail') url = serializers.HyperlinkedIdentityField(view_name='dcim-api:inventoryitem-detail')
device = NestedDeviceSerializer(read_only=True) device = NestedDeviceSerializer(read_only=True)
_depth = serializers.IntegerField(source='level', read_only=True) _depth = serializers.IntegerField(source='tree_depth', read_only=True)
class Meta: class Meta:
model = models.InventoryItem model = models.InventoryItem

View File

@ -589,7 +589,7 @@ class InventoryItemTemplateSerializer(ValidatedModelSerializer):
allow_null=True allow_null=True
) )
component = serializers.SerializerMethodField(read_only=True) component = serializers.SerializerMethodField(read_only=True)
_depth = serializers.IntegerField(source='level', read_only=True) _depth = serializers.IntegerField(source='tree_depth', read_only=True)
class Meta: class Meta:
model = InventoryItemTemplate model = InventoryItemTemplate
@ -1023,7 +1023,7 @@ class InventoryItemSerializer(NetBoxModelSerializer):
allow_null=True allow_null=True
) )
component = serializers.SerializerMethodField(read_only=True) component = serializers.SerializerMethodField(read_only=True)
_depth = serializers.IntegerField(source='level', read_only=True) _depth = serializers.IntegerField(source='tree_depth', read_only=True)
class Meta: class Meta:
model = InventoryItem model = InventoryItem

View File

@ -99,13 +99,14 @@ class PassThroughPortMixin(object):
# #
class RegionViewSet(NetBoxModelViewSet): class RegionViewSet(NetBoxModelViewSet):
queryset = Region.objects.add_related_count( queryset = Region.objects.all().prefetch_related('tags')
Region.objects.all(), # queryset = Region.objects.add_related_count(
Site, # Region.objects.all(),
'region', # Site,
'site_count', # 'region',
cumulative=True # 'site_count',
).prefetch_related('tags') # cumulative=True
# ).prefetch_related('tags')
serializer_class = serializers.RegionSerializer serializer_class = serializers.RegionSerializer
filterset_class = filtersets.RegionFilterSet filterset_class = filtersets.RegionFilterSet
@ -115,13 +116,14 @@ class RegionViewSet(NetBoxModelViewSet):
# #
class SiteGroupViewSet(NetBoxModelViewSet): class SiteGroupViewSet(NetBoxModelViewSet):
queryset = SiteGroup.objects.add_related_count( queryset = SiteGroup.objects.all().prefetch_related('tags')
SiteGroup.objects.all(), # queryset = SiteGroup.objects.add_related_count(
Site, # SiteGroup.objects.all(),
'group', # Site,
'site_count', # 'group',
cumulative=True # 'site_count',
).prefetch_related('tags') # cumulative=True
# ).prefetch_related('tags')
serializer_class = serializers.SiteGroupSerializer serializer_class = serializers.SiteGroupSerializer
filterset_class = filtersets.SiteGroupFilterSet filterset_class = filtersets.SiteGroupFilterSet
@ -150,19 +152,20 @@ class SiteViewSet(NetBoxModelViewSet):
# #
class LocationViewSet(NetBoxModelViewSet): class LocationViewSet(NetBoxModelViewSet):
queryset = Location.objects.add_related_count( queryset = Location.objects.all().prefetch_related('site', 'tags')
Location.objects.add_related_count( # queryset = Location.objects.add_related_count(
Location.objects.all(), # Location.objects.add_related_count(
Device, # Location.objects.all(),
'location', # Device,
'device_count', # 'location',
cumulative=True # 'device_count',
), # cumulative=True
Rack, # ),
'location', # Rack,
'rack_count', # 'location',
cumulative=True # 'rack_count',
).prefetch_related('site', 'tags') # cumulative=True
# ).prefetch_related('site', 'tags')
serializer_class = serializers.LocationSerializer serializer_class = serializers.LocationSerializer
filterset_class = filtersets.LocationFilterSet filterset_class = filtersets.LocationFilterSet

View File

@ -0,0 +1,148 @@
# Generated by Django 4.1.8 on 2023-05-18 20:20
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('dcim', '0173_remove_napalm_fields'),
]
operations = [
migrations.RemoveField(
model_name='inventoryitem',
name='level',
),
migrations.RemoveField(
model_name='inventoryitem',
name='lft',
),
migrations.RemoveField(
model_name='inventoryitem',
name='rght',
),
migrations.RemoveField(
model_name='inventoryitem',
name='tree_id',
),
migrations.RemoveField(
model_name='inventoryitemtemplate',
name='level',
),
migrations.RemoveField(
model_name='inventoryitemtemplate',
name='lft',
),
migrations.RemoveField(
model_name='inventoryitemtemplate',
name='rght',
),
migrations.RemoveField(
model_name='inventoryitemtemplate',
name='tree_id',
),
migrations.RemoveField(
model_name='location',
name='level',
),
migrations.RemoveField(
model_name='location',
name='lft',
),
migrations.RemoveField(
model_name='location',
name='rght',
),
migrations.RemoveField(
model_name='location',
name='tree_id',
),
migrations.RemoveField(
model_name='region',
name='level',
),
migrations.RemoveField(
model_name='region',
name='lft',
),
migrations.RemoveField(
model_name='region',
name='rght',
),
migrations.RemoveField(
model_name='region',
name='tree_id',
),
migrations.RemoveField(
model_name='sitegroup',
name='level',
),
migrations.RemoveField(
model_name='sitegroup',
name='lft',
),
migrations.RemoveField(
model_name='sitegroup',
name='rght',
),
migrations.RemoveField(
model_name='sitegroup',
name='tree_id',
),
migrations.AlterField(
model_name='inventoryitem',
name='parent',
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name='children',
to='dcim.inventoryitem',
),
),
migrations.AlterField(
model_name='inventoryitemtemplate',
name='parent',
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name='children',
to='dcim.inventoryitemtemplate',
),
),
migrations.AlterField(
model_name='location',
name='parent',
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name='children',
to='dcim.location',
),
),
migrations.AlterField(
model_name='region',
name='parent',
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name='children',
to='dcim.region',
),
),
migrations.AlterField(
model_name='sitegroup',
name='parent',
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name='children',
to='dcim.sitegroup',
),
),
]

View File

@ -4,14 +4,14 @@ from django.core.exceptions import 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
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from mptt.models import MPTTModel, TreeForeignKey from tree_queries.models import TreeNode
from dcim.choices import * from dcim.choices import *
from dcim.constants import * from dcim.constants import *
from netbox.models import ChangeLoggedModel from netbox.models import ChangeLoggedModel
from utilities.fields import ColorField, NaturalOrderingField from utilities.fields import ColorField, NaturalOrderingField
from utilities.mptt import TreeManager
from utilities.ordering import naturalize_interface from utilities.ordering import naturalize_interface
from utilities.tree_queries import TreeManager
from .device_components import ( from .device_components import (
ConsolePort, ConsoleServerPort, DeviceBay, FrontPort, Interface, InventoryItem, ModuleBay, PowerOutlet, PowerPort, ConsolePort, ConsoleServerPort, DeviceBay, FrontPort, Interface, InventoryItem, ModuleBay, PowerOutlet, PowerPort,
RearPort, RearPort,
@ -618,18 +618,10 @@ class DeviceBayTemplate(ComponentTemplateModel):
} }
class InventoryItemTemplate(MPTTModel, ComponentTemplateModel): class InventoryItemTemplate(TreeNode, ComponentTemplateModel):
""" """
A template for an InventoryItem to be created for a new parent Device. A template for an InventoryItem to be created for a new parent Device.
""" """
parent = TreeForeignKey(
to='self',
on_delete=models.CASCADE,
related_name='child_items',
blank=True,
null=True,
db_index=True
)
component_type = models.ForeignKey( component_type = models.ForeignKey(
to=ContentType, to=ContentType,
limit_choices_to=MODULAR_COMPONENT_TEMPLATE_MODELS, limit_choices_to=MODULAR_COMPONENT_TEMPLATE_MODELS,

View File

@ -8,7 +8,7 @@ from django.db import models
from django.db.models import Sum from django.db.models import Sum
from django.urls import reverse from django.urls import reverse
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from mptt.models import MPTTModel, TreeForeignKey from tree_queries.models import TreeNode
from dcim.choices import * from dcim.choices import *
from dcim.constants import * from dcim.constants import *
@ -16,9 +16,9 @@ from dcim.fields import MACAddressField, WWNField
from netbox.models import OrganizationalModel, NetBoxModel from netbox.models import OrganizationalModel, NetBoxModel
from utilities.choices import ColorChoices from utilities.choices import ColorChoices
from utilities.fields import ColorField, NaturalOrderingField from utilities.fields import ColorField, NaturalOrderingField
from utilities.mptt import TreeManager
from utilities.ordering import naturalize_interface from utilities.ordering import naturalize_interface
from utilities.query_functions import CollateAsChar from utilities.query_functions import CollateAsChar
from utilities.tree_queries import TreeManager
from wireless.choices import * from wireless.choices import *
from wireless.utils import get_channel_attr from wireless.utils import get_channel_attr
@ -1064,19 +1064,11 @@ class InventoryItemRole(OrganizationalModel):
return reverse('dcim:inventoryitemrole', args=[self.pk]) return reverse('dcim:inventoryitemrole', args=[self.pk])
class InventoryItem(MPTTModel, ComponentModel): class InventoryItem(TreeNode, ComponentModel):
""" """
An InventoryItem represents a serialized piece of hardware within a Device, such as a line card or power supply. An InventoryItem represents a serialized piece of hardware within a Device, such as a line card or power supply.
InventoryItems are used only for inventory purposes. InventoryItems are used only for inventory purposes.
""" """
parent = TreeForeignKey(
to='self',
on_delete=models.CASCADE,
related_name='child_items',
blank=True,
null=True,
db_index=True
)
component_type = models.ForeignKey( component_type = models.ForeignKey(
to=ContentType, to=ContentType,
limit_choices_to=MODULAR_COMPONENT_MODELS, limit_choices_to=MODULAR_COMPONENT_MODELS,

View File

@ -198,13 +198,14 @@ class PathTraceView(generic.ObjectView):
# #
class RegionListView(generic.ObjectListView): class RegionListView(generic.ObjectListView):
queryset = Region.objects.add_related_count( queryset = Region.objects.all()
Region.objects.all(), # queryset = Region.objects.add_related_count(
Site, # Region.objects.all(),
'region', # Site,
'site_count', # 'region',
cumulative=True # 'site_count',
) # cumulative=True
# )
filterset = filtersets.RegionFilterSet filterset = filtersets.RegionFilterSet
filterset_form = forms.RegionFilterForm filterset_form = forms.RegionFilterForm
table = tables.RegionTable table = tables.RegionTable
@ -244,26 +245,28 @@ class RegionBulkImportView(generic.BulkImportView):
class RegionBulkEditView(generic.BulkEditView): class RegionBulkEditView(generic.BulkEditView):
queryset = Region.objects.add_related_count( queryset = Region.objects.all()
Region.objects.all(), # queryset = Region.objects.add_related_count(
Site, # Region.objects.all(),
'region', # Site,
'site_count', # 'region',
cumulative=True # 'site_count',
) # cumulative=True
# )
filterset = filtersets.RegionFilterSet filterset = filtersets.RegionFilterSet
table = tables.RegionTable table = tables.RegionTable
form = forms.RegionBulkEditForm form = forms.RegionBulkEditForm
class RegionBulkDeleteView(generic.BulkDeleteView): class RegionBulkDeleteView(generic.BulkDeleteView):
queryset = Region.objects.add_related_count( queryset = Region.objects.all()
Region.objects.all(), # queryset = Region.objects.add_related_count(
Site, # Region.objects.all(),
'region', # Site,
'site_count', # 'region',
cumulative=True # 'site_count',
) # cumulative=True
# )
filterset = filtersets.RegionFilterSet filterset = filtersets.RegionFilterSet
table = tables.RegionTable table = tables.RegionTable
@ -278,13 +281,14 @@ class RegionContactsView(ObjectContactsView):
# #
class SiteGroupListView(generic.ObjectListView): class SiteGroupListView(generic.ObjectListView):
queryset = SiteGroup.objects.add_related_count( queryset = SiteGroup.objects.all()
SiteGroup.objects.all(), # queryset = SiteGroup.objects.add_related_count(
Site, # SiteGroup.objects.all(),
'group', # Site,
'site_count', # 'group',
cumulative=True # 'site_count',
) # cumulative=True
# )
filterset = filtersets.SiteGroupFilterSet filterset = filtersets.SiteGroupFilterSet
filterset_form = forms.SiteGroupFilterForm filterset_form = forms.SiteGroupFilterForm
table = tables.SiteGroupTable table = tables.SiteGroupTable
@ -324,26 +328,28 @@ class SiteGroupBulkImportView(generic.BulkImportView):
class SiteGroupBulkEditView(generic.BulkEditView): class SiteGroupBulkEditView(generic.BulkEditView):
queryset = SiteGroup.objects.add_related_count( queryset = SiteGroup.objects.all()
SiteGroup.objects.all(), # queryset = SiteGroup.objects.add_related_count(
Site, # SiteGroup.objects.all(),
'group', # Site,
'site_count', # 'group',
cumulative=True # 'site_count',
) # cumulative=True
# )
filterset = filtersets.SiteGroupFilterSet filterset = filtersets.SiteGroupFilterSet
table = tables.SiteGroupTable table = tables.SiteGroupTable
form = forms.SiteGroupBulkEditForm form = forms.SiteGroupBulkEditForm
class SiteGroupBulkDeleteView(generic.BulkDeleteView): class SiteGroupBulkDeleteView(generic.BulkDeleteView):
queryset = SiteGroup.objects.add_related_count( queryset = SiteGroup.objects.all()
SiteGroup.objects.all(), # queryset = SiteGroup.objects.add_related_count(
Site, # SiteGroup.objects.all(),
'group', # Site,
'site_count', # 'group',
cumulative=True # 'site_count',
) # cumulative=True
# )
filterset = filtersets.SiteGroupFilterSet filterset = filtersets.SiteGroupFilterSet
table = tables.SiteGroupTable table = tables.SiteGroupTable
@ -388,20 +394,21 @@ class SiteView(generic.ObjectView):
(Circuit.objects.restrict(request.user, 'view').filter(terminations__site=instance).distinct(), 'site_id'), (Circuit.objects.restrict(request.user, 'view').filter(terminations__site=instance).distinct(), 'site_id'),
) )
locations = Location.objects.add_related_count( locations = Location.objects.all().restrict(request.user, 'view').filter(site=instance)
Location.objects.all(), # locations = Location.objects.add_related_count(
Rack, # Location.objects.all(),
'location', # Rack,
'rack_count', # 'location',
cumulative=True # 'rack_count',
) # cumulative=True
locations = Location.objects.add_related_count( # )
locations, # locations = Location.objects.add_related_count(
Device, # locations,
'location', # Device,
'device_count', # 'location',
cumulative=True # 'device_count',
).restrict(request.user, 'view').filter(site=instance) # cumulative=True
# ).restrict(request.user, 'view').filter(site=instance)
nonracked_devices = Device.objects.filter( nonracked_devices = Device.objects.filter(
site=instance, site=instance,
@ -456,19 +463,20 @@ class SiteContactsView(ObjectContactsView):
# #
class LocationListView(generic.ObjectListView): class LocationListView(generic.ObjectListView):
queryset = Location.objects.add_related_count( queryset = Location.objects.all()
Location.objects.add_related_count( # queryset = Location.objects.add_related_count(
Location.objects.all(), # Location.objects.add_related_count(
Device, # Location.objects.all(),
'location', # Device,
'device_count', # 'location',
cumulative=True # 'device_count',
), # cumulative=True
Rack, # ),
'location', # Rack,
'rack_count', # 'location',
cumulative=True # 'rack_count',
) # cumulative=True
# )
filterset = filtersets.LocationFilterSet filterset = filtersets.LocationFilterSet
filterset_form = forms.LocationFilterForm filterset_form = forms.LocationFilterForm
table = tables.LocationTable table = tables.LocationTable
@ -515,26 +523,28 @@ class LocationBulkImportView(generic.BulkImportView):
class LocationBulkEditView(generic.BulkEditView): class LocationBulkEditView(generic.BulkEditView):
queryset = Location.objects.add_related_count( queryset = Location.objects.all()
Location.objects.all(), # queryset = Location.objects.add_related_count(
Rack, # Location.objects.all(),
'location', # Rack,
'rack_count', # 'location',
cumulative=True # 'rack_count',
).prefetch_related('site') # cumulative=True
# ).prefetch_related('site')
filterset = filtersets.LocationFilterSet filterset = filtersets.LocationFilterSet
table = tables.LocationTable table = tables.LocationTable
form = forms.LocationBulkEditForm form = forms.LocationBulkEditForm
class LocationBulkDeleteView(generic.BulkDeleteView): class LocationBulkDeleteView(generic.BulkDeleteView):
queryset = Location.objects.add_related_count( queryset = Location.objects.all()
Location.objects.all(), # queryset = Location.objects.add_related_count(
Rack, # Location.objects.all(),
'location', # Rack,
'rack_count', # 'location',
cumulative=True # 'rack_count',
).prefetch_related('site') # cumulative=True
# ).prefetch_related('site')
filterset = filtersets.LocationFilterSet filterset = filtersets.LocationFilterSet
table = tables.LocationTable table = tables.LocationTable

View File

@ -130,10 +130,11 @@ class ConfigContextModelQuerySet(RestrictedQuerySet):
region_field = 'cluster__site__region' region_field = 'cluster__site__region'
sitegroup_field = 'cluster__site__group' sitegroup_field = 'cluster__site__group'
"""
base_query.add( base_query.add(
(Q( (Q(
regions__tree_id=OuterRef(f'{region_field}__tree_id'), regions__tree_id=OuterRef(f'{region_field}__tree_id'),
regions__level__lte=OuterRef(f'{region_field}__level'), regions__tree_depth__lte=OuterRef(f'{region_field}__tree_depth'),
regions__lft__lte=OuterRef(f'{region_field}__lft'), regions__lft__lte=OuterRef(f'{region_field}__lft'),
regions__rght__gte=OuterRef(f'{region_field}__rght'), regions__rght__gte=OuterRef(f'{region_field}__rght'),
) | Q(regions=None)), ) | Q(regions=None)),
@ -143,11 +144,12 @@ class ConfigContextModelQuerySet(RestrictedQuerySet):
base_query.add( base_query.add(
(Q( (Q(
site_groups__tree_id=OuterRef(f'{sitegroup_field}__tree_id'), site_groups__tree_id=OuterRef(f'{sitegroup_field}__tree_id'),
site_groups__level__lte=OuterRef(f'{sitegroup_field}__level'), site_groups__tree_depth__lte=OuterRef(f'{sitegroup_field}__tree_depth'),
site_groups__lft__lte=OuterRef(f'{sitegroup_field}__lft'), site_groups__lft__lte=OuterRef(f'{sitegroup_field}__lft'),
site_groups__rght__gte=OuterRef(f'{sitegroup_field}__rght'), site_groups__rght__gte=OuterRef(f'{sitegroup_field}__rght'),
) | Q(site_groups=None)), ) | Q(site_groups=None)),
Q.AND Q.AND
) )
"""
return base_query return base_query

View File

@ -21,7 +21,7 @@ class NestedGroupModelSerializer(NetBoxModelSerializer):
""" """
Extends PrimaryModelSerializer to include MPTT support. Extends PrimaryModelSerializer to include MPTT support.
""" """
_depth = serializers.IntegerField(source='level', read_only=True) _depth = serializers.IntegerField(source='tree_depth', read_only=True)
class BulkOperationSerializer(serializers.Serializer): class BulkOperationSerializer(serializers.Serializer):

View File

@ -2,11 +2,11 @@ from django.conf import settings
from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.fields import GenericForeignKey
from django.core.validators import ValidationError from django.core.validators import ValidationError
from django.db import models from django.db import models
from mptt.models import MPTTModel, TreeForeignKey from tree_queries.models import TreeNode
from netbox.models.features import * from netbox.models.features import *
from utilities.mptt import TreeManager
from utilities.querysets import RestrictedQuerySet from utilities.querysets import RestrictedQuerySet
from utilities.tree_queries import TreeManager
__all__ = ( __all__ = (
'ChangeLoggedModel', 'ChangeLoggedModel',
@ -103,19 +103,11 @@ class PrimaryModel(NetBoxModel):
abstract = True abstract = True
class NestedGroupModel(CloningMixin, NetBoxFeatureSet, MPTTModel): class NestedGroupModel(CloningMixin, NetBoxFeatureSet, TreeNode):
""" """
Base model for objects which are used to form a hierarchy (regions, locations, etc.). These models nest Base model for objects which are used to form a hierarchy (regions, locations, etc.). These models nest
recursively using MPTT. Within each parent, each child instance must have a unique name. recursively using MPTT. Within each parent, each child instance must have a unique name.
""" """
parent = TreeForeignKey(
to='self',
on_delete=models.CASCADE,
related_name='children',
blank=True,
null=True,
db_index=True
)
name = models.CharField( name = models.CharField(
max_length=100 max_length=100
) )
@ -131,9 +123,7 @@ class NestedGroupModel(CloningMixin, NetBoxFeatureSet, MPTTModel):
class Meta: class Meta:
abstract = True abstract = True
ordering = ("name",)
class MPTTMeta:
order_insertion_by = ('name',)
def __str__(self): def __str__(self):
return self.name return self.name

View File

@ -24,7 +24,7 @@ __all__ = [
class NestedTenantGroupSerializer(WritableNestedSerializer): class NestedTenantGroupSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(view_name='tenancy-api:tenantgroup-detail') url = serializers.HyperlinkedIdentityField(view_name='tenancy-api:tenantgroup-detail')
tenant_count = serializers.IntegerField(read_only=True) tenant_count = serializers.IntegerField(read_only=True)
_depth = serializers.IntegerField(source='level', read_only=True) _depth = serializers.IntegerField(source='tree_depth', read_only=True)
class Meta: class Meta:
model = TenantGroup model = TenantGroup
@ -49,7 +49,7 @@ class NestedTenantSerializer(WritableNestedSerializer):
class NestedContactGroupSerializer(WritableNestedSerializer): class NestedContactGroupSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(view_name='tenancy-api:contactgroup-detail') url = serializers.HyperlinkedIdentityField(view_name='tenancy-api:contactgroup-detail')
contact_count = serializers.IntegerField(read_only=True) contact_count = serializers.IntegerField(read_only=True)
_depth = serializers.IntegerField(source='level', read_only=True) _depth = serializers.IntegerField(source='tree_depth', read_only=True)
class Meta: class Meta:
model = ContactGroup model = ContactGroup

View File

@ -24,13 +24,14 @@ class TenancyRootView(APIRootView):
# #
class TenantGroupViewSet(NetBoxModelViewSet): class TenantGroupViewSet(NetBoxModelViewSet):
queryset = TenantGroup.objects.add_related_count( queryset = TenantGroup.objects.all().prefetch_related('tags')
TenantGroup.objects.all(), # queryset = TenantGroup.objects.add_related_count(
Tenant, # TenantGroup.objects.all(),
'group', # Tenant,
'tenant_count', # 'group',
cumulative=True # 'tenant_count',
).prefetch_related('tags') # cumulative=True
# ).prefetch_related('tags')
serializer_class = serializers.TenantGroupSerializer serializer_class = serializers.TenantGroupSerializer
filterset_class = filtersets.TenantGroupFilterSet filterset_class = filtersets.TenantGroupFilterSet
@ -59,13 +60,14 @@ class TenantViewSet(NetBoxModelViewSet):
# #
class ContactGroupViewSet(NetBoxModelViewSet): class ContactGroupViewSet(NetBoxModelViewSet):
queryset = ContactGroup.objects.add_related_count( queryset = ContactGroup.objects.all().prefetch_related('tags')
ContactGroup.objects.all(), # queryset = ContactGroup.objects.add_related_count(
Contact, # ContactGroup.objects.all(),
'group', # Contact,
'contact_count', # 'group',
cumulative=True # 'contact_count',
).prefetch_related('tags') # cumulative=True
# ).prefetch_related('tags')
serializer_class = serializers.ContactGroupSerializer serializer_class = serializers.ContactGroupSerializer
filterset_class = filtersets.ContactGroupFilterSet filterset_class = filtersets.ContactGroupFilterSet

View File

@ -0,0 +1,67 @@
# Generated by Django 4.1.8 on 2023-05-18 20:20
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('tenancy', '0010_tenant_relax_uniqueness'),
]
operations = [
migrations.RemoveField(
model_name='contactgroup',
name='level',
),
migrations.RemoveField(
model_name='contactgroup',
name='lft',
),
migrations.RemoveField(
model_name='contactgroup',
name='rght',
),
migrations.RemoveField(
model_name='contactgroup',
name='tree_id',
),
migrations.RemoveField(
model_name='tenantgroup',
name='level',
),
migrations.RemoveField(
model_name='tenantgroup',
name='lft',
),
migrations.RemoveField(
model_name='tenantgroup',
name='rght',
),
migrations.RemoveField(
model_name='tenantgroup',
name='tree_id',
),
migrations.AlterField(
model_name='contactgroup',
name='parent',
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name='children',
to='tenancy.contactgroup',
),
),
migrations.AlterField(
model_name='tenantgroup',
name='parent',
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name='children',
to='tenancy.tenantgroup',
),
),
]

View File

@ -42,13 +42,14 @@ class ObjectContactsView(generic.ObjectChildrenView):
class TenantGroupListView(generic.ObjectListView): class TenantGroupListView(generic.ObjectListView):
queryset = TenantGroup.objects.add_related_count( queryset = TenantGroup.objects.all()
TenantGroup.objects.all(), # queryset = TenantGroup.objects.add_related_count(
Tenant, # TenantGroup.objects.all(),
'group', # Tenant,
'tenant_count', # 'group',
cumulative=True # 'tenant_count',
) # cumulative=True
# )
filterset = filtersets.TenantGroupFilterSet filterset = filtersets.TenantGroupFilterSet
filterset_form = forms.TenantGroupFilterForm filterset_form = forms.TenantGroupFilterForm
table = tables.TenantGroupTable table = tables.TenantGroupTable
@ -86,26 +87,28 @@ class TenantGroupBulkImportView(generic.BulkImportView):
class TenantGroupBulkEditView(generic.BulkEditView): class TenantGroupBulkEditView(generic.BulkEditView):
queryset = TenantGroup.objects.add_related_count( queryset = TenantGroup.objects.all()
TenantGroup.objects.all(), # queryset = TenantGroup.objects.add_related_count(
Tenant, # TenantGroup.objects.all(),
'group', # Tenant,
'tenant_count', # 'group',
cumulative=True # 'tenant_count',
) # cumulative=True
# )
filterset = filtersets.TenantGroupFilterSet filterset = filtersets.TenantGroupFilterSet
table = tables.TenantGroupTable table = tables.TenantGroupTable
form = forms.TenantGroupBulkEditForm form = forms.TenantGroupBulkEditForm
class TenantGroupBulkDeleteView(generic.BulkDeleteView): class TenantGroupBulkDeleteView(generic.BulkDeleteView):
queryset = TenantGroup.objects.add_related_count( queryset = TenantGroup.objects.all()
TenantGroup.objects.all(), # queryset = TenantGroup.objects.add_related_count(
Tenant, # TenantGroup.objects.all(),
'group', # Tenant,
'tenant_count', # 'group',
cumulative=True # 'tenant_count',
) # cumulative=True
# )
filterset = filtersets.TenantGroupFilterSet filterset = filtersets.TenantGroupFilterSet
table = tables.TenantGroupTable table = tables.TenantGroupTable
@ -198,13 +201,14 @@ class TenantContactsView(ObjectContactsView):
# #
class ContactGroupListView(generic.ObjectListView): class ContactGroupListView(generic.ObjectListView):
queryset = ContactGroup.objects.add_related_count( queryset = ContactGroup.objects.all()
ContactGroup.objects.all(), # queryset = ContactGroup.objects.add_related_count(
Contact, # ContactGroup.objects.all(),
'group', # Contact,
'contact_count', # 'group',
cumulative=True # 'contact_count',
) # cumulative=True
# )
filterset = filtersets.ContactGroupFilterSet filterset = filtersets.ContactGroupFilterSet
filterset_form = forms.ContactGroupFilterForm filterset_form = forms.ContactGroupFilterForm
table = tables.ContactGroupTable table = tables.ContactGroupTable
@ -242,26 +246,28 @@ class ContactGroupBulkImportView(generic.BulkImportView):
class ContactGroupBulkEditView(generic.BulkEditView): class ContactGroupBulkEditView(generic.BulkEditView):
queryset = ContactGroup.objects.add_related_count( queryset = ContactGroup.objects.all()
ContactGroup.objects.all(), # queryset = ContactGroup.objects.add_related_count(
Contact, # ContactGroup.objects.all(),
'group', # Contact,
'contact_count', # 'group',
cumulative=True # 'contact_count',
) # cumulative=True
# )
filterset = filtersets.ContactGroupFilterSet filterset = filtersets.ContactGroupFilterSet
table = tables.ContactGroupTable table = tables.ContactGroupTable
form = forms.ContactGroupBulkEditForm form = forms.ContactGroupBulkEditForm
class ContactGroupBulkDeleteView(generic.BulkDeleteView): class ContactGroupBulkDeleteView(generic.BulkDeleteView):
queryset = ContactGroup.objects.add_related_count( queryset = ContactGroup.objects.all()
ContactGroup.objects.all(), # queryset = ContactGroup.objects.add_related_count(
Contact, # ContactGroup.objects.all(),
'group', # Contact,
'contact_count', # 'group',
cumulative=True # 'contact_count',
) # cumulative=True
# )
filterset = filtersets.ContactGroupFilterSet filterset = filtersets.ContactGroupFilterSet
table = tables.ContactGroupTable table = tables.ContactGroupTable

View File

@ -1,24 +0,0 @@
from mptt.managers import TreeManager as TreeManager_
from mptt.querysets import TreeQuerySet as TreeQuerySet_
from django.db.models import Manager
from .querysets import RestrictedQuerySet
__all__ = (
'TreeManager',
'TreeQuerySet',
)
class TreeQuerySet(TreeQuerySet_, RestrictedQuerySet):
"""
Mate django-mptt's TreeQuerySet with our RestrictedQuerySet for permissions enforcement.
"""
pass
class TreeManager(Manager.from_queryset(TreeQuerySet), TreeManager_):
"""
Extend django-mptt's TreeManager to incorporate RestrictedQuerySet().
"""
pass

View File

@ -0,0 +1,25 @@
from tree_queries.query import TreeManager as TreeManager_
from tree_queries.query import TreeQuerySet as TreeQuerySet_
from django.db.models import Manager
from .querysets import RestrictedQuerySet
__all__ = (
'TreeManager',
'TreeQuerySet',
)
class TreeQuerySet(TreeQuerySet_, RestrictedQuerySet):
"""
Mate django-tree-queries TreeQuerySet with our RestrictedQuerySet for permissions enforcement.
"""
pass
class TreeManager(Manager.from_queryset(TreeQuerySet), TreeManager_):
"""
Extend django-tree-queries TreeManager to incorporate RestrictedQuerySet().
"""
_with_tree_fields = True

View File

@ -15,7 +15,7 @@ from django.utils.html import escape
from django.utils import timezone from django.utils import timezone
from django.utils.timezone import localtime from django.utils.timezone import localtime
from jinja2.sandbox import SandboxedEnvironment from jinja2.sandbox import SandboxedEnvironment
from mptt.models import MPTTModel from tree_queries.models import TreeNode
from dcim.choices import CableLengthUnitChoices, WeightUnitChoices from dcim.choices import CableLengthUnitChoices, WeightUnitChoices
from extras.plugins import PluginConfig from extras.plugins import PluginConfig
@ -153,8 +153,8 @@ def serialize_object(obj, resolve_tags=True, extra=None):
json_str = serializers.serialize('json', [obj]) json_str = serializers.serialize('json', [obj])
data = json.loads(json_str)[0]['fields'] data = json.loads(json_str)[0]['fields']
# Exclude any MPTTModel fields # Exclude any TreeNode fields
if issubclass(obj.__class__, MPTTModel): if issubclass(obj.__class__, TreeNode):
for field in ['level', 'lft', 'rght', 'tree_id']: for field in ['level', 'lft', 'rght', 'tree_id']:
data.pop(field) data.pop(field)

View File

@ -17,7 +17,7 @@ __all__ = (
class NestedWirelessLANGroupSerializer(WritableNestedSerializer): class NestedWirelessLANGroupSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(view_name='wireless-api:wirelesslangroup-detail') url = serializers.HyperlinkedIdentityField(view_name='wireless-api:wirelesslangroup-detail')
wirelesslan_count = serializers.IntegerField(read_only=True) wirelesslan_count = serializers.IntegerField(read_only=True)
_depth = serializers.IntegerField(source='level', read_only=True) _depth = serializers.IntegerField(source='tree_depth', read_only=True)
class Meta: class Meta:
model = WirelessLANGroup model = WirelessLANGroup

View File

@ -15,13 +15,14 @@ class WirelessRootView(APIRootView):
class WirelessLANGroupViewSet(NetBoxModelViewSet): class WirelessLANGroupViewSet(NetBoxModelViewSet):
queryset = WirelessLANGroup.objects.add_related_count( queryset = WirelessLANGroup.objects.all()
WirelessLANGroup.objects.all(), # queryset = WirelessLANGroup.objects.add_related_count(
WirelessLAN, # WirelessLANGroup.objects.all(),
'group', # WirelessLAN,
'wirelesslan_count', # 'group',
cumulative=True # 'wirelesslan_count',
) # cumulative=True
# )
serializer_class = serializers.WirelessLANGroupSerializer serializer_class = serializers.WirelessLANGroupSerializer
filterset_class = filtersets.WirelessLANGroupFilterSet filterset_class = filtersets.WirelessLANGroupFilterSet

View File

@ -0,0 +1,40 @@
# Generated by Django 4.1.8 on 2023-05-18 20:20
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('wireless', '0008_wirelesslan_status'),
]
operations = [
migrations.RemoveField(
model_name='wirelesslangroup',
name='level',
),
migrations.RemoveField(
model_name='wirelesslangroup',
name='lft',
),
migrations.RemoveField(
model_name='wirelesslangroup',
name='rght',
),
migrations.RemoveField(
model_name='wirelesslangroup',
name='tree_id',
),
migrations.AlterField(
model_name='wirelesslangroup',
name='parent',
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name='children',
to='wireless.wirelesslangroup',
),
),
]

View File

@ -1,7 +1,6 @@
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.db import models from django.db import models
from django.urls import reverse from django.urls import reverse
from mptt.models import MPTTModel
from dcim.choices import LinkStatusChoices from dcim.choices import LinkStatusChoices
from dcim.constants import WIRELESS_IFACE_TYPES from dcim.constants import WIRELESS_IFACE_TYPES

View File

@ -11,13 +11,14 @@ from .models import *
# #
class WirelessLANGroupListView(generic.ObjectListView): class WirelessLANGroupListView(generic.ObjectListView):
queryset = WirelessLANGroup.objects.add_related_count( queryset = WirelessLANGroup.objects.all().prefetch_related('tags')
WirelessLANGroup.objects.all(), # queryset = WirelessLANGroup.objects.add_related_count(
WirelessLAN, # WirelessLANGroup.objects.all(),
'group', # WirelessLAN,
'wirelesslan_count', # 'group',
cumulative=True # 'wirelesslan_count',
).prefetch_related('tags') # cumulative=True
# ).prefetch_related('tags')
filterset = filtersets.WirelessLANGroupFilterSet filterset = filtersets.WirelessLANGroupFilterSet
filterset_form = forms.WirelessLANGroupFilterForm filterset_form = forms.WirelessLANGroupFilterForm
table = tables.WirelessLANGroupTable table = tables.WirelessLANGroupTable
@ -55,26 +56,28 @@ class WirelessLANGroupBulkImportView(generic.BulkImportView):
class WirelessLANGroupBulkEditView(generic.BulkEditView): class WirelessLANGroupBulkEditView(generic.BulkEditView):
queryset = WirelessLANGroup.objects.add_related_count( queryset = WirelessLANGroup.objects.all()
WirelessLANGroup.objects.all(), # queryset = WirelessLANGroup.objects.add_related_count(
WirelessLAN, # WirelessLANGroup.objects.all(),
'group', # WirelessLAN,
'wirelesslan_count', # 'group',
cumulative=True # 'wirelesslan_count',
) # cumulative=True
# )
filterset = filtersets.WirelessLANGroupFilterSet filterset = filtersets.WirelessLANGroupFilterSet
table = tables.WirelessLANGroupTable table = tables.WirelessLANGroupTable
form = forms.WirelessLANGroupBulkEditForm form = forms.WirelessLANGroupBulkEditForm
class WirelessLANGroupBulkDeleteView(generic.BulkDeleteView): class WirelessLANGroupBulkDeleteView(generic.BulkDeleteView):
queryset = WirelessLANGroup.objects.add_related_count( queryset = WirelessLANGroup.objects.all()
WirelessLANGroup.objects.all(), # queryset = WirelessLANGroup.objects.add_related_count(
WirelessLAN, # WirelessLANGroup.objects.all(),
'group', # WirelessLAN,
'wirelesslan_count', # 'group',
cumulative=True # 'wirelesslan_count',
) # cumulative=True
# )
filterset = filtersets.WirelessLANGroupFilterSet filterset = filtersets.WirelessLANGroupFilterSet
table = tables.WirelessLANGroupTable table = tables.WirelessLANGroupTable

View File

@ -5,7 +5,6 @@ django-cors-headers==3.14.0
django-debug-toolbar==4.0.0 django-debug-toolbar==4.0.0
django-filter==23.2 django-filter==23.2
django-graphiql-debug-toolbar==0.2.0 django-graphiql-debug-toolbar==0.2.0
django-mptt==0.14
django-pglocks==1.0.4 django-pglocks==1.0.4
django-prometheus==2.3.1 django-prometheus==2.3.1
django-redis==5.2.0 django-redis==5.2.0
@ -14,6 +13,7 @@ django-rq==2.8.0
django-tables2==2.5.3 django-tables2==2.5.3
django-taggit==4.0.0 django-taggit==4.0.0
django-timezone-field==5.0 django-timezone-field==5.0
django-tree-queries==0.14.0
djangorestframework==3.14.0 djangorestframework==3.14.0
drf-spectacular==0.26.2 drf-spectacular==0.26.2
drf-spectacular-sidecar==2023.5.1 drf-spectacular-sidecar==2023.5.1