diff --git a/netbox/dcim/migrations/0226_add_mptt_tree_indexes.py b/netbox/dcim/migrations/0226_add_mptt_tree_indexes.py new file mode 100644 index 000000000..7faf86190 --- /dev/null +++ b/netbox/dcim/migrations/0226_add_mptt_tree_indexes.py @@ -0,0 +1,49 @@ +# Generated by Django 5.2.10 on 2026-02-13 13:36 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('contenttypes', '0002_remove_content_type_name'), + ('dcim', '0225_gfk_indexes'), + ('extras', '0134_owner'), + ('tenancy', '0022_add_comments_to_organizationalmodel'), + ('users', '0015_owner'), + ] + + operations = [ + migrations.AddIndex( + model_name='devicerole', + index=models.Index(fields=['tree_id', 'lft'], name='dcim_devicerole_tree_id_lfbf11'), + ), + migrations.AddIndex( + model_name='inventoryitem', + index=models.Index(fields=['tree_id', 'lft'], name='dcim_inventoryitem_tree_id975c'), + ), + migrations.AddIndex( + model_name='inventoryitemtemplate', + index=models.Index(fields=['tree_id', 'lft'], name='dcim_inventoryitemtemplatedee0'), + ), + migrations.AddIndex( + model_name='location', + index=models.Index(fields=['tree_id', 'lft'], name='dcim_location_tree_id_lft_idx'), + ), + migrations.AddIndex( + model_name='modulebay', + index=models.Index(fields=['tree_id', 'lft'], name='dcim_modulebay_tree_id_lft_idx'), + ), + migrations.AddIndex( + model_name='platform', + index=models.Index(fields=['tree_id', 'lft'], name='dcim_platform_tree_id_lft_idx'), + ), + migrations.AddIndex( + model_name='region', + index=models.Index(fields=['tree_id', 'lft'], name='dcim_region_tree_id_lft_idx'), + ), + migrations.AddIndex( + model_name='sitegroup', + index=models.Index(fields=['tree_id', 'lft'], name='dcim_sitegroup_tree_id_lft_idx'), + ), + ] diff --git a/netbox/dcim/models/device_components.py b/netbox/dcim/models/device_components.py index a16daa1e5..dd5696bc0 100644 --- a/netbox/dcim/models/device_components.py +++ b/netbox/dcim/models/device_components.py @@ -1263,6 +1263,9 @@ class ModuleBay(ModularComponentModel, TrackingModelMixin, MPTTModel): clone_fields = ('device',) class Meta(ModularComponentModel.Meta): + # Empty tuple triggers Django migration detection for MPTT indexes + # (see #21016, django-mptt/django-mptt#682) + indexes = () constraints = ( models.UniqueConstraint( fields=('device', 'module', 'name'), diff --git a/netbox/dcim/models/devices.py b/netbox/dcim/models/devices.py index 65a5282b0..12de3f477 100644 --- a/netbox/dcim/models/devices.py +++ b/netbox/dcim/models/devices.py @@ -401,6 +401,9 @@ class DeviceRole(NestedGroupModel): class Meta: ordering = ('name',) + # Empty tuple triggers Django migration detection for MPTT indexes + # (see #21016, django-mptt/django-mptt#682) + indexes = () constraints = ( models.UniqueConstraint( fields=('parent', 'name'), @@ -452,6 +455,9 @@ class Platform(NestedGroupModel): class Meta: ordering = ('name',) + # Empty tuple triggers Django migration detection for MPTT indexes + # (see #21016, django-mptt/django-mptt#682) + indexes = () verbose_name = _('platform') verbose_name_plural = _('platforms') constraints = ( diff --git a/netbox/dcim/models/sites.py b/netbox/dcim/models/sites.py index d18c22c7e..00bc9a240 100644 --- a/netbox/dcim/models/sites.py +++ b/netbox/dcim/models/sites.py @@ -44,6 +44,9 @@ class Region(ContactsMixin, NestedGroupModel): ) class Meta: + # Empty tuple triggers Django migration detection for MPTT indexes + # (see #21016, django-mptt/django-mptt#682) + indexes = () constraints = ( models.UniqueConstraint( fields=('parent', 'name'), @@ -100,6 +103,9 @@ class SiteGroup(ContactsMixin, NestedGroupModel): ) class Meta: + # Empty tuple triggers Django migration detection for MPTT indexes + # (see #21016, django-mptt/django-mptt#682) + indexes = () constraints = ( models.UniqueConstraint( fields=('parent', 'name'), @@ -318,6 +324,9 @@ class Location(ContactsMixin, ImageAttachmentsMixin, NestedGroupModel): class Meta: ordering = ['site', 'name'] + # Empty tuple triggers Django migration detection for MPTT indexes + # (see #21016, django-mptt/django-mptt#682) + indexes = () constraints = ( models.UniqueConstraint( fields=('site', 'parent', 'name'), diff --git a/netbox/netbox/models/__init__.py b/netbox/netbox/models/__init__.py index 496b4c4db..2839ea802 100644 --- a/netbox/netbox/models/__init__.py +++ b/netbox/netbox/models/__init__.py @@ -143,6 +143,10 @@ class NestedGroupModel(OwnerMixin, NetBoxModel, MPTTModel): """ 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. + + Note: django-mptt injects the (tree_id, lft) index dynamically, but Django's migration autodetector won't + detect it unless concrete subclasses explicitly declare Meta.indexes (even as an empty tuple). See #21016 + and django-mptt/django-mptt#682. """ parent = TreeForeignKey( to='self', diff --git a/netbox/tenancy/migrations/0023_add_mptt_tree_indexes.py b/netbox/tenancy/migrations/0023_add_mptt_tree_indexes.py new file mode 100644 index 000000000..bebe7b218 --- /dev/null +++ b/netbox/tenancy/migrations/0023_add_mptt_tree_indexes.py @@ -0,0 +1,23 @@ +# Generated by Django 5.2.10 on 2026-02-13 13:36 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('extras', '0134_owner'), + ('tenancy', '0022_add_comments_to_organizationalmodel'), + ('users', '0015_owner'), + ] + + operations = [ + migrations.AddIndex( + model_name='contactgroup', + index=models.Index(fields=['tree_id', 'lft'], name='tenancy_contactgroup_tree_d2ce'), + ), + migrations.AddIndex( + model_name='tenantgroup', + index=models.Index(fields=['tree_id', 'lft'], name='tenancy_tenantgroup_tree_ifebc'), + ), + ] diff --git a/netbox/tenancy/models/contacts.py b/netbox/tenancy/models/contacts.py index 19ffb2b0b..25fd72e21 100644 --- a/netbox/tenancy/models/contacts.py +++ b/netbox/tenancy/models/contacts.py @@ -22,6 +22,9 @@ class ContactGroup(NestedGroupModel): """ class Meta: ordering = ['name'] + # Empty tuple triggers Django migration detection for MPTT indexes + # (see #21016, django-mptt/django-mptt#682) + indexes = () constraints = ( models.UniqueConstraint( fields=('parent', 'name'), diff --git a/netbox/tenancy/models/tenants.py b/netbox/tenancy/models/tenants.py index 55f0c5933..923c9a9ec 100644 --- a/netbox/tenancy/models/tenants.py +++ b/netbox/tenancy/models/tenants.py @@ -29,6 +29,9 @@ class TenantGroup(NestedGroupModel): class Meta: ordering = ['name'] + # Empty tuple triggers Django migration detection for MPTT indexes + # (see #21016, django-mptt/django-mptt#682) + indexes = () verbose_name = _('tenant group') verbose_name_plural = _('tenant groups') diff --git a/netbox/wireless/migrations/0018_add_mptt_tree_indexes.py b/netbox/wireless/migrations/0018_add_mptt_tree_indexes.py new file mode 100644 index 000000000..d52c3060b --- /dev/null +++ b/netbox/wireless/migrations/0018_add_mptt_tree_indexes.py @@ -0,0 +1,19 @@ +# Generated by Django 5.2.10 on 2026-02-13 13:36 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('extras', '0134_owner'), + ('users', '0015_owner'), + ('wireless', '0017_gfk_indexes'), + ] + + operations = [ + migrations.AddIndex( + model_name='wirelesslangroup', + index=models.Index(fields=['tree_id', 'lft'], name='wireless_wirelesslangroup_fbcd'), + ), + ] diff --git a/netbox/wireless/models.py b/netbox/wireless/models.py index d8ebd8197..060f83f13 100644 --- a/netbox/wireless/models.py +++ b/netbox/wireless/models.py @@ -63,6 +63,9 @@ class WirelessLANGroup(NestedGroupModel): class Meta: ordering = ('name', 'pk') + # Empty tuple triggers Django migration detection for MPTT indexes + # (see #21016, django-mptt/django-mptt#682) + indexes = () constraints = ( models.UniqueConstraint( fields=('parent', 'name'), diff --git a/requirements.txt b/requirements.txt index c60ac31e9..f32e07837 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,7 @@ django-debug-toolbar==6.2.0 django-filter==25.2 django-graphiql-debug-toolbar==0.2.0 django-htmx==1.27.0 -django-mptt==0.17.0 +django-mptt==0.18.0 django-pglocks==1.0.4 django-prometheus==2.4.1 django-redis==6.0.0