From da0c459e731e0c870699929f8b40e202f48216a0 Mon Sep 17 00:00:00 2001 From: Arthur Date: Thu, 18 May 2023 13:22:31 -0700 Subject: [PATCH] 12552 initial removal of mptt --- base_requirements.txt | 6 +- netbox/dcim/api/nested_serializers.py | 10 +- netbox/dcim/api/serializers.py | 4 +- netbox/dcim/api/views.py | 57 +++--- ...level_remove_inventoryitem_lft_and_more.py | 148 +++++++++++++++ .../dcim/models/device_component_templates.py | 14 +- netbox/dcim/models/device_components.py | 14 +- netbox/dcim/views.py | 176 +++++++++--------- netbox/extras/querysets.py | 6 +- netbox/netbox/api/serializers/__init__.py | 2 +- netbox/netbox/models/__init__.py | 18 +- netbox/tenancy/api/nested_serializers.py | 4 +- netbox/tenancy/api/views.py | 30 +-- ..._level_remove_contactgroup_lft_and_more.py | 67 +++++++ netbox/tenancy/views.py | 90 ++++----- netbox/utilities/mptt.py | 24 --- netbox/utilities/tree_queries.py | 25 +++ netbox/utilities/utils.py | 6 +- netbox/wireless/api/nested_serializers.py | 2 +- netbox/wireless/api/views.py | 15 +- ..._remove_wirelesslangroup_level_and_more.py | 40 ++++ netbox/wireless/models.py | 1 - netbox/wireless/views.py | 45 ++--- requirements.txt | 2 +- 24 files changed, 531 insertions(+), 275 deletions(-) create mode 100644 netbox/dcim/migrations/0174_remove_inventoryitem_level_remove_inventoryitem_lft_and_more.py create mode 100644 netbox/tenancy/migrations/0011_remove_contactgroup_level_remove_contactgroup_lft_and_more.py delete mode 100644 netbox/utilities/mptt.py create mode 100644 netbox/utilities/tree_queries.py create mode 100644 netbox/wireless/migrations/0009_remove_wirelesslangroup_level_and_more.py diff --git a/base_requirements.txt b/base_requirements.txt index 1e9a45048..34958fd3a 100644 --- a/base_requirements.txt +++ b/base_requirements.txt @@ -26,9 +26,9 @@ django-filter # https://github.com/flavors/django-graphiql-debug-toolbar/blob/main/CHANGES.rst django-graphiql-debug-toolbar -# Modified Preorder Tree Traversal (recursive nesting of objects) -# https://github.com/django-mptt/django-mptt/blob/main/CHANGELOG.rst -django-mptt +# Adjacency-list trees for Django using recursive common table expressions. +# https://github.com/matthiask/django-tree-queries/blob/main/CHANGELOG.rst +django-tree-queries # Context managers for PostgreSQL advisory locks # https://github.com/Xof/django-pglocks/blob/master/CHANGES.txt diff --git a/netbox/dcim/api/nested_serializers.py b/netbox/dcim/api/nested_serializers.py index c8440612d..a7764b087 100644 --- a/netbox/dcim/api/nested_serializers.py +++ b/netbox/dcim/api/nested_serializers.py @@ -60,7 +60,7 @@ __all__ = [ class NestedRegionSerializer(WritableNestedSerializer): url = serializers.HyperlinkedIdentityField(view_name='dcim-api:region-detail') 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: model = models.Region @@ -73,7 +73,7 @@ class NestedRegionSerializer(WritableNestedSerializer): class NestedSiteGroupSerializer(WritableNestedSerializer): url = serializers.HyperlinkedIdentityField(view_name='dcim-api:sitegroup-detail') 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: model = models.SiteGroup @@ -98,7 +98,7 @@ class NestedSiteSerializer(WritableNestedSerializer): class NestedLocationSerializer(WritableNestedSerializer): url = serializers.HyperlinkedIdentityField(view_name='dcim-api:location-detail') 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: model = models.Location @@ -258,7 +258,7 @@ class NestedDeviceBayTemplateSerializer(WritableNestedSerializer): class NestedInventoryItemTemplateSerializer(WritableNestedSerializer): 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: model = models.InventoryItemTemplate @@ -433,7 +433,7 @@ class NestedDeviceBaySerializer(WritableNestedSerializer): class NestedInventoryItemSerializer(WritableNestedSerializer): url = serializers.HyperlinkedIdentityField(view_name='dcim-api:inventoryitem-detail') device = NestedDeviceSerializer(read_only=True) - _depth = serializers.IntegerField(source='level', read_only=True) + _depth = serializers.IntegerField(source='tree_depth', read_only=True) class Meta: model = models.InventoryItem diff --git a/netbox/dcim/api/serializers.py b/netbox/dcim/api/serializers.py index 894a3f4f9..916325c03 100644 --- a/netbox/dcim/api/serializers.py +++ b/netbox/dcim/api/serializers.py @@ -589,7 +589,7 @@ class InventoryItemTemplateSerializer(ValidatedModelSerializer): allow_null=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: model = InventoryItemTemplate @@ -1023,7 +1023,7 @@ class InventoryItemSerializer(NetBoxModelSerializer): allow_null=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: model = InventoryItem diff --git a/netbox/dcim/api/views.py b/netbox/dcim/api/views.py index 0d9fcdc5f..71d35e1b9 100644 --- a/netbox/dcim/api/views.py +++ b/netbox/dcim/api/views.py @@ -99,13 +99,14 @@ class PassThroughPortMixin(object): # class RegionViewSet(NetBoxModelViewSet): - queryset = Region.objects.add_related_count( - Region.objects.all(), - Site, - 'region', - 'site_count', - cumulative=True - ).prefetch_related('tags') + queryset = Region.objects.all().prefetch_related('tags') + # queryset = Region.objects.add_related_count( + # Region.objects.all(), + # Site, + # 'region', + # 'site_count', + # cumulative=True + # ).prefetch_related('tags') serializer_class = serializers.RegionSerializer filterset_class = filtersets.RegionFilterSet @@ -115,13 +116,14 @@ class RegionViewSet(NetBoxModelViewSet): # class SiteGroupViewSet(NetBoxModelViewSet): - queryset = SiteGroup.objects.add_related_count( - SiteGroup.objects.all(), - Site, - 'group', - 'site_count', - cumulative=True - ).prefetch_related('tags') + queryset = SiteGroup.objects.all().prefetch_related('tags') + # queryset = SiteGroup.objects.add_related_count( + # SiteGroup.objects.all(), + # Site, + # 'group', + # 'site_count', + # cumulative=True + # ).prefetch_related('tags') serializer_class = serializers.SiteGroupSerializer filterset_class = filtersets.SiteGroupFilterSet @@ -150,19 +152,20 @@ class SiteViewSet(NetBoxModelViewSet): # class LocationViewSet(NetBoxModelViewSet): - queryset = Location.objects.add_related_count( - Location.objects.add_related_count( - Location.objects.all(), - Device, - 'location', - 'device_count', - cumulative=True - ), - Rack, - 'location', - 'rack_count', - cumulative=True - ).prefetch_related('site', 'tags') + queryset = Location.objects.all().prefetch_related('site', 'tags') + # queryset = Location.objects.add_related_count( + # Location.objects.add_related_count( + # Location.objects.all(), + # Device, + # 'location', + # 'device_count', + # cumulative=True + # ), + # Rack, + # 'location', + # 'rack_count', + # cumulative=True + # ).prefetch_related('site', 'tags') serializer_class = serializers.LocationSerializer filterset_class = filtersets.LocationFilterSet diff --git a/netbox/dcim/migrations/0174_remove_inventoryitem_level_remove_inventoryitem_lft_and_more.py b/netbox/dcim/migrations/0174_remove_inventoryitem_level_remove_inventoryitem_lft_and_more.py new file mode 100644 index 000000000..3052bddf6 --- /dev/null +++ b/netbox/dcim/migrations/0174_remove_inventoryitem_level_remove_inventoryitem_lft_and_more.py @@ -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', + ), + ), + ] diff --git a/netbox/dcim/models/device_component_templates.py b/netbox/dcim/models/device_component_templates.py index 6a89655b2..a36400fcb 100644 --- a/netbox/dcim/models/device_component_templates.py +++ b/netbox/dcim/models/device_component_templates.py @@ -4,14 +4,14 @@ from django.core.exceptions import ValidationError from django.core.validators import MaxValueValidator, MinValueValidator from django.db import models 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.constants import * from netbox.models import ChangeLoggedModel from utilities.fields import ColorField, NaturalOrderingField -from utilities.mptt import TreeManager from utilities.ordering import naturalize_interface +from utilities.tree_queries import TreeManager from .device_components import ( ConsolePort, ConsoleServerPort, DeviceBay, FrontPort, Interface, InventoryItem, ModuleBay, PowerOutlet, PowerPort, 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. """ - parent = TreeForeignKey( - to='self', - on_delete=models.CASCADE, - related_name='child_items', - blank=True, - null=True, - db_index=True - ) component_type = models.ForeignKey( to=ContentType, limit_choices_to=MODULAR_COMPONENT_TEMPLATE_MODELS, diff --git a/netbox/dcim/models/device_components.py b/netbox/dcim/models/device_components.py index 9f6837b92..f14c3018f 100644 --- a/netbox/dcim/models/device_components.py +++ b/netbox/dcim/models/device_components.py @@ -8,7 +8,7 @@ from django.db import models from django.db.models import Sum from django.urls import reverse 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.constants import * @@ -16,9 +16,9 @@ from dcim.fields import MACAddressField, WWNField from netbox.models import OrganizationalModel, NetBoxModel from utilities.choices import ColorChoices from utilities.fields import ColorField, NaturalOrderingField -from utilities.mptt import TreeManager from utilities.ordering import naturalize_interface from utilities.query_functions import CollateAsChar +from utilities.tree_queries import TreeManager from wireless.choices import * from wireless.utils import get_channel_attr @@ -1064,19 +1064,11 @@ class InventoryItemRole(OrganizationalModel): 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. 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( to=ContentType, limit_choices_to=MODULAR_COMPONENT_MODELS, diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index 0def4f4a8..67fcc528c 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -198,13 +198,14 @@ class PathTraceView(generic.ObjectView): # class RegionListView(generic.ObjectListView): - queryset = Region.objects.add_related_count( - Region.objects.all(), - Site, - 'region', - 'site_count', - cumulative=True - ) + queryset = Region.objects.all() + # queryset = Region.objects.add_related_count( + # Region.objects.all(), + # Site, + # 'region', + # 'site_count', + # cumulative=True + # ) filterset = filtersets.RegionFilterSet filterset_form = forms.RegionFilterForm table = tables.RegionTable @@ -244,26 +245,28 @@ class RegionBulkImportView(generic.BulkImportView): class RegionBulkEditView(generic.BulkEditView): - queryset = Region.objects.add_related_count( - Region.objects.all(), - Site, - 'region', - 'site_count', - cumulative=True - ) + queryset = Region.objects.all() + # queryset = Region.objects.add_related_count( + # Region.objects.all(), + # Site, + # 'region', + # 'site_count', + # cumulative=True + # ) filterset = filtersets.RegionFilterSet table = tables.RegionTable form = forms.RegionBulkEditForm class RegionBulkDeleteView(generic.BulkDeleteView): - queryset = Region.objects.add_related_count( - Region.objects.all(), - Site, - 'region', - 'site_count', - cumulative=True - ) + queryset = Region.objects.all() + # queryset = Region.objects.add_related_count( + # Region.objects.all(), + # Site, + # 'region', + # 'site_count', + # cumulative=True + # ) filterset = filtersets.RegionFilterSet table = tables.RegionTable @@ -278,13 +281,14 @@ class RegionContactsView(ObjectContactsView): # class SiteGroupListView(generic.ObjectListView): - queryset = SiteGroup.objects.add_related_count( - SiteGroup.objects.all(), - Site, - 'group', - 'site_count', - cumulative=True - ) + queryset = SiteGroup.objects.all() + # queryset = SiteGroup.objects.add_related_count( + # SiteGroup.objects.all(), + # Site, + # 'group', + # 'site_count', + # cumulative=True + # ) filterset = filtersets.SiteGroupFilterSet filterset_form = forms.SiteGroupFilterForm table = tables.SiteGroupTable @@ -324,26 +328,28 @@ class SiteGroupBulkImportView(generic.BulkImportView): class SiteGroupBulkEditView(generic.BulkEditView): - queryset = SiteGroup.objects.add_related_count( - SiteGroup.objects.all(), - Site, - 'group', - 'site_count', - cumulative=True - ) + queryset = SiteGroup.objects.all() + # queryset = SiteGroup.objects.add_related_count( + # SiteGroup.objects.all(), + # Site, + # 'group', + # 'site_count', + # cumulative=True + # ) filterset = filtersets.SiteGroupFilterSet table = tables.SiteGroupTable form = forms.SiteGroupBulkEditForm class SiteGroupBulkDeleteView(generic.BulkDeleteView): - queryset = SiteGroup.objects.add_related_count( - SiteGroup.objects.all(), - Site, - 'group', - 'site_count', - cumulative=True - ) + queryset = SiteGroup.objects.all() + # queryset = SiteGroup.objects.add_related_count( + # SiteGroup.objects.all(), + # Site, + # 'group', + # 'site_count', + # cumulative=True + # ) filterset = filtersets.SiteGroupFilterSet table = tables.SiteGroupTable @@ -388,20 +394,21 @@ class SiteView(generic.ObjectView): (Circuit.objects.restrict(request.user, 'view').filter(terminations__site=instance).distinct(), 'site_id'), ) - locations = Location.objects.add_related_count( - Location.objects.all(), - Rack, - 'location', - 'rack_count', - cumulative=True - ) - locations = Location.objects.add_related_count( - locations, - Device, - 'location', - 'device_count', - cumulative=True - ).restrict(request.user, 'view').filter(site=instance) + locations = Location.objects.all().restrict(request.user, 'view').filter(site=instance) + # locations = Location.objects.add_related_count( + # Location.objects.all(), + # Rack, + # 'location', + # 'rack_count', + # cumulative=True + # ) + # locations = Location.objects.add_related_count( + # locations, + # Device, + # 'location', + # 'device_count', + # cumulative=True + # ).restrict(request.user, 'view').filter(site=instance) nonracked_devices = Device.objects.filter( site=instance, @@ -456,19 +463,20 @@ class SiteContactsView(ObjectContactsView): # class LocationListView(generic.ObjectListView): - queryset = Location.objects.add_related_count( - Location.objects.add_related_count( - Location.objects.all(), - Device, - 'location', - 'device_count', - cumulative=True - ), - Rack, - 'location', - 'rack_count', - cumulative=True - ) + queryset = Location.objects.all() + # queryset = Location.objects.add_related_count( + # Location.objects.add_related_count( + # Location.objects.all(), + # Device, + # 'location', + # 'device_count', + # cumulative=True + # ), + # Rack, + # 'location', + # 'rack_count', + # cumulative=True + # ) filterset = filtersets.LocationFilterSet filterset_form = forms.LocationFilterForm table = tables.LocationTable @@ -515,26 +523,28 @@ class LocationBulkImportView(generic.BulkImportView): class LocationBulkEditView(generic.BulkEditView): - queryset = Location.objects.add_related_count( - Location.objects.all(), - Rack, - 'location', - 'rack_count', - cumulative=True - ).prefetch_related('site') + queryset = Location.objects.all() + # queryset = Location.objects.add_related_count( + # Location.objects.all(), + # Rack, + # 'location', + # 'rack_count', + # cumulative=True + # ).prefetch_related('site') filterset = filtersets.LocationFilterSet table = tables.LocationTable form = forms.LocationBulkEditForm class LocationBulkDeleteView(generic.BulkDeleteView): - queryset = Location.objects.add_related_count( - Location.objects.all(), - Rack, - 'location', - 'rack_count', - cumulative=True - ).prefetch_related('site') + queryset = Location.objects.all() + # queryset = Location.objects.add_related_count( + # Location.objects.all(), + # Rack, + # 'location', + # 'rack_count', + # cumulative=True + # ).prefetch_related('site') filterset = filtersets.LocationFilterSet table = tables.LocationTable diff --git a/netbox/extras/querysets.py b/netbox/extras/querysets.py index 2b97af0fb..0281bf847 100644 --- a/netbox/extras/querysets.py +++ b/netbox/extras/querysets.py @@ -130,10 +130,11 @@ class ConfigContextModelQuerySet(RestrictedQuerySet): region_field = 'cluster__site__region' sitegroup_field = 'cluster__site__group' + """ base_query.add( (Q( 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__rght__gte=OuterRef(f'{region_field}__rght'), ) | Q(regions=None)), @@ -143,11 +144,12 @@ class ConfigContextModelQuerySet(RestrictedQuerySet): base_query.add( (Q( 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__rght__gte=OuterRef(f'{sitegroup_field}__rght'), ) | Q(site_groups=None)), Q.AND ) + """ return base_query diff --git a/netbox/netbox/api/serializers/__init__.py b/netbox/netbox/api/serializers/__init__.py index 0ec3ab5f3..25f3d800a 100644 --- a/netbox/netbox/api/serializers/__init__.py +++ b/netbox/netbox/api/serializers/__init__.py @@ -21,7 +21,7 @@ class NestedGroupModelSerializer(NetBoxModelSerializer): """ 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): diff --git a/netbox/netbox/models/__init__.py b/netbox/netbox/models/__init__.py index c0f679e4f..fc59f15a5 100644 --- a/netbox/netbox/models/__init__.py +++ b/netbox/netbox/models/__init__.py @@ -2,11 +2,11 @@ from django.conf import settings from django.contrib.contenttypes.fields import GenericForeignKey from django.core.validators import ValidationError from django.db import models -from mptt.models import MPTTModel, TreeForeignKey +from tree_queries.models import TreeNode from netbox.models.features import * -from utilities.mptt import TreeManager from utilities.querysets import RestrictedQuerySet +from utilities.tree_queries import TreeManager __all__ = ( 'ChangeLoggedModel', @@ -103,19 +103,11 @@ class PrimaryModel(NetBoxModel): 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 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( max_length=100 ) @@ -131,9 +123,7 @@ class NestedGroupModel(CloningMixin, NetBoxFeatureSet, MPTTModel): class Meta: abstract = True - - class MPTTMeta: - order_insertion_by = ('name',) + ordering = ("name",) def __str__(self): return self.name diff --git a/netbox/tenancy/api/nested_serializers.py b/netbox/tenancy/api/nested_serializers.py index d2d76d96c..cc0e5bbb4 100644 --- a/netbox/tenancy/api/nested_serializers.py +++ b/netbox/tenancy/api/nested_serializers.py @@ -24,7 +24,7 @@ __all__ = [ class NestedTenantGroupSerializer(WritableNestedSerializer): url = serializers.HyperlinkedIdentityField(view_name='tenancy-api:tenantgroup-detail') 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: model = TenantGroup @@ -49,7 +49,7 @@ class NestedTenantSerializer(WritableNestedSerializer): class NestedContactGroupSerializer(WritableNestedSerializer): url = serializers.HyperlinkedIdentityField(view_name='tenancy-api:contactgroup-detail') 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: model = ContactGroup diff --git a/netbox/tenancy/api/views.py b/netbox/tenancy/api/views.py index 39c86d80e..a9ba9826b 100644 --- a/netbox/tenancy/api/views.py +++ b/netbox/tenancy/api/views.py @@ -24,13 +24,14 @@ class TenancyRootView(APIRootView): # class TenantGroupViewSet(NetBoxModelViewSet): - queryset = TenantGroup.objects.add_related_count( - TenantGroup.objects.all(), - Tenant, - 'group', - 'tenant_count', - cumulative=True - ).prefetch_related('tags') + queryset = TenantGroup.objects.all().prefetch_related('tags') + # queryset = TenantGroup.objects.add_related_count( + # TenantGroup.objects.all(), + # Tenant, + # 'group', + # 'tenant_count', + # cumulative=True + # ).prefetch_related('tags') serializer_class = serializers.TenantGroupSerializer filterset_class = filtersets.TenantGroupFilterSet @@ -59,13 +60,14 @@ class TenantViewSet(NetBoxModelViewSet): # class ContactGroupViewSet(NetBoxModelViewSet): - queryset = ContactGroup.objects.add_related_count( - ContactGroup.objects.all(), - Contact, - 'group', - 'contact_count', - cumulative=True - ).prefetch_related('tags') + queryset = ContactGroup.objects.all().prefetch_related('tags') + # queryset = ContactGroup.objects.add_related_count( + # ContactGroup.objects.all(), + # Contact, + # 'group', + # 'contact_count', + # cumulative=True + # ).prefetch_related('tags') serializer_class = serializers.ContactGroupSerializer filterset_class = filtersets.ContactGroupFilterSet diff --git a/netbox/tenancy/migrations/0011_remove_contactgroup_level_remove_contactgroup_lft_and_more.py b/netbox/tenancy/migrations/0011_remove_contactgroup_level_remove_contactgroup_lft_and_more.py new file mode 100644 index 000000000..a919cfdfc --- /dev/null +++ b/netbox/tenancy/migrations/0011_remove_contactgroup_level_remove_contactgroup_lft_and_more.py @@ -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', + ), + ), + ] diff --git a/netbox/tenancy/views.py b/netbox/tenancy/views.py index 5f8a7e314..8760dfa71 100644 --- a/netbox/tenancy/views.py +++ b/netbox/tenancy/views.py @@ -42,13 +42,14 @@ class ObjectContactsView(generic.ObjectChildrenView): class TenantGroupListView(generic.ObjectListView): - queryset = TenantGroup.objects.add_related_count( - TenantGroup.objects.all(), - Tenant, - 'group', - 'tenant_count', - cumulative=True - ) + queryset = TenantGroup.objects.all() + # queryset = TenantGroup.objects.add_related_count( + # TenantGroup.objects.all(), + # Tenant, + # 'group', + # 'tenant_count', + # cumulative=True + # ) filterset = filtersets.TenantGroupFilterSet filterset_form = forms.TenantGroupFilterForm table = tables.TenantGroupTable @@ -86,26 +87,28 @@ class TenantGroupBulkImportView(generic.BulkImportView): class TenantGroupBulkEditView(generic.BulkEditView): - queryset = TenantGroup.objects.add_related_count( - TenantGroup.objects.all(), - Tenant, - 'group', - 'tenant_count', - cumulative=True - ) + queryset = TenantGroup.objects.all() + # queryset = TenantGroup.objects.add_related_count( + # TenantGroup.objects.all(), + # Tenant, + # 'group', + # 'tenant_count', + # cumulative=True + # ) filterset = filtersets.TenantGroupFilterSet table = tables.TenantGroupTable form = forms.TenantGroupBulkEditForm class TenantGroupBulkDeleteView(generic.BulkDeleteView): - queryset = TenantGroup.objects.add_related_count( - TenantGroup.objects.all(), - Tenant, - 'group', - 'tenant_count', - cumulative=True - ) + queryset = TenantGroup.objects.all() + # queryset = TenantGroup.objects.add_related_count( + # TenantGroup.objects.all(), + # Tenant, + # 'group', + # 'tenant_count', + # cumulative=True + # ) filterset = filtersets.TenantGroupFilterSet table = tables.TenantGroupTable @@ -198,13 +201,14 @@ class TenantContactsView(ObjectContactsView): # class ContactGroupListView(generic.ObjectListView): - queryset = ContactGroup.objects.add_related_count( - ContactGroup.objects.all(), - Contact, - 'group', - 'contact_count', - cumulative=True - ) + queryset = ContactGroup.objects.all() + # queryset = ContactGroup.objects.add_related_count( + # ContactGroup.objects.all(), + # Contact, + # 'group', + # 'contact_count', + # cumulative=True + # ) filterset = filtersets.ContactGroupFilterSet filterset_form = forms.ContactGroupFilterForm table = tables.ContactGroupTable @@ -242,26 +246,28 @@ class ContactGroupBulkImportView(generic.BulkImportView): class ContactGroupBulkEditView(generic.BulkEditView): - queryset = ContactGroup.objects.add_related_count( - ContactGroup.objects.all(), - Contact, - 'group', - 'contact_count', - cumulative=True - ) + queryset = ContactGroup.objects.all() + # queryset = ContactGroup.objects.add_related_count( + # ContactGroup.objects.all(), + # Contact, + # 'group', + # 'contact_count', + # cumulative=True + # ) filterset = filtersets.ContactGroupFilterSet table = tables.ContactGroupTable form = forms.ContactGroupBulkEditForm class ContactGroupBulkDeleteView(generic.BulkDeleteView): - queryset = ContactGroup.objects.add_related_count( - ContactGroup.objects.all(), - Contact, - 'group', - 'contact_count', - cumulative=True - ) + queryset = ContactGroup.objects.all() + # queryset = ContactGroup.objects.add_related_count( + # ContactGroup.objects.all(), + # Contact, + # 'group', + # 'contact_count', + # cumulative=True + # ) filterset = filtersets.ContactGroupFilterSet table = tables.ContactGroupTable diff --git a/netbox/utilities/mptt.py b/netbox/utilities/mptt.py deleted file mode 100644 index 7ded5b72b..000000000 --- a/netbox/utilities/mptt.py +++ /dev/null @@ -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 diff --git a/netbox/utilities/tree_queries.py b/netbox/utilities/tree_queries.py new file mode 100644 index 000000000..155054cd9 --- /dev/null +++ b/netbox/utilities/tree_queries.py @@ -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 diff --git a/netbox/utilities/utils.py b/netbox/utilities/utils.py index b1504e62f..c4a02e861 100644 --- a/netbox/utilities/utils.py +++ b/netbox/utilities/utils.py @@ -15,7 +15,7 @@ from django.utils.html import escape from django.utils import timezone from django.utils.timezone import localtime from jinja2.sandbox import SandboxedEnvironment -from mptt.models import MPTTModel +from tree_queries.models import TreeNode from dcim.choices import CableLengthUnitChoices, WeightUnitChoices from extras.plugins import PluginConfig @@ -153,8 +153,8 @@ def serialize_object(obj, resolve_tags=True, extra=None): json_str = serializers.serialize('json', [obj]) data = json.loads(json_str)[0]['fields'] - # Exclude any MPTTModel fields - if issubclass(obj.__class__, MPTTModel): + # Exclude any TreeNode fields + if issubclass(obj.__class__, TreeNode): for field in ['level', 'lft', 'rght', 'tree_id']: data.pop(field) diff --git a/netbox/wireless/api/nested_serializers.py b/netbox/wireless/api/nested_serializers.py index 53f2a6354..d5b13fdb3 100644 --- a/netbox/wireless/api/nested_serializers.py +++ b/netbox/wireless/api/nested_serializers.py @@ -17,7 +17,7 @@ __all__ = ( class NestedWirelessLANGroupSerializer(WritableNestedSerializer): url = serializers.HyperlinkedIdentityField(view_name='wireless-api:wirelesslangroup-detail') 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: model = WirelessLANGroup diff --git a/netbox/wireless/api/views.py b/netbox/wireless/api/views.py index 1103cec37..3dd568951 100644 --- a/netbox/wireless/api/views.py +++ b/netbox/wireless/api/views.py @@ -15,13 +15,14 @@ class WirelessRootView(APIRootView): class WirelessLANGroupViewSet(NetBoxModelViewSet): - queryset = WirelessLANGroup.objects.add_related_count( - WirelessLANGroup.objects.all(), - WirelessLAN, - 'group', - 'wirelesslan_count', - cumulative=True - ) + queryset = WirelessLANGroup.objects.all() + # queryset = WirelessLANGroup.objects.add_related_count( + # WirelessLANGroup.objects.all(), + # WirelessLAN, + # 'group', + # 'wirelesslan_count', + # cumulative=True + # ) serializer_class = serializers.WirelessLANGroupSerializer filterset_class = filtersets.WirelessLANGroupFilterSet diff --git a/netbox/wireless/migrations/0009_remove_wirelesslangroup_level_and_more.py b/netbox/wireless/migrations/0009_remove_wirelesslangroup_level_and_more.py new file mode 100644 index 000000000..6612b63f3 --- /dev/null +++ b/netbox/wireless/migrations/0009_remove_wirelesslangroup_level_and_more.py @@ -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', + ), + ), + ] diff --git a/netbox/wireless/models.py b/netbox/wireless/models.py index 1f7d76961..23fcee308 100644 --- a/netbox/wireless/models.py +++ b/netbox/wireless/models.py @@ -1,7 +1,6 @@ from django.core.exceptions import ValidationError from django.db import models from django.urls import reverse -from mptt.models import MPTTModel from dcim.choices import LinkStatusChoices from dcim.constants import WIRELESS_IFACE_TYPES diff --git a/netbox/wireless/views.py b/netbox/wireless/views.py index e1eb6fd7d..049a68bba 100644 --- a/netbox/wireless/views.py +++ b/netbox/wireless/views.py @@ -11,13 +11,14 @@ from .models import * # class WirelessLANGroupListView(generic.ObjectListView): - queryset = WirelessLANGroup.objects.add_related_count( - WirelessLANGroup.objects.all(), - WirelessLAN, - 'group', - 'wirelesslan_count', - cumulative=True - ).prefetch_related('tags') + queryset = WirelessLANGroup.objects.all().prefetch_related('tags') + # queryset = WirelessLANGroup.objects.add_related_count( + # WirelessLANGroup.objects.all(), + # WirelessLAN, + # 'group', + # 'wirelesslan_count', + # cumulative=True + # ).prefetch_related('tags') filterset = filtersets.WirelessLANGroupFilterSet filterset_form = forms.WirelessLANGroupFilterForm table = tables.WirelessLANGroupTable @@ -55,26 +56,28 @@ class WirelessLANGroupBulkImportView(generic.BulkImportView): class WirelessLANGroupBulkEditView(generic.BulkEditView): - queryset = WirelessLANGroup.objects.add_related_count( - WirelessLANGroup.objects.all(), - WirelessLAN, - 'group', - 'wirelesslan_count', - cumulative=True - ) + queryset = WirelessLANGroup.objects.all() + # queryset = WirelessLANGroup.objects.add_related_count( + # WirelessLANGroup.objects.all(), + # WirelessLAN, + # 'group', + # 'wirelesslan_count', + # cumulative=True + # ) filterset = filtersets.WirelessLANGroupFilterSet table = tables.WirelessLANGroupTable form = forms.WirelessLANGroupBulkEditForm class WirelessLANGroupBulkDeleteView(generic.BulkDeleteView): - queryset = WirelessLANGroup.objects.add_related_count( - WirelessLANGroup.objects.all(), - WirelessLAN, - 'group', - 'wirelesslan_count', - cumulative=True - ) + queryset = WirelessLANGroup.objects.all() + # queryset = WirelessLANGroup.objects.add_related_count( + # WirelessLANGroup.objects.all(), + # WirelessLAN, + # 'group', + # 'wirelesslan_count', + # cumulative=True + # ) filterset = filtersets.WirelessLANGroupFilterSet table = tables.WirelessLANGroupTable diff --git a/requirements.txt b/requirements.txt index c3d9c8c38..9a501ea15 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,6 @@ django-cors-headers==3.14.0 django-debug-toolbar==4.0.0 django-filter==23.2 django-graphiql-debug-toolbar==0.2.0 -django-mptt==0.14 django-pglocks==1.0.4 django-prometheus==2.3.1 django-redis==5.2.0 @@ -14,6 +13,7 @@ django-rq==2.8.0 django-tables2==2.5.3 django-taggit==4.0.0 django-timezone-field==5.0 +django-tree-queries==0.14.0 djangorestframework==3.14.0 drf-spectacular==0.26.2 drf-spectacular-sidecar==2023.5.1