2
0
Эх сурвалжийг харах

backwards compatability for NestedGroupModel

Arthur 4 цаг өмнө
parent
commit
8926313c43

+ 3 - 3
netbox/dcim/models/devices.py

@@ -24,7 +24,7 @@ from extras.models import ConfigContextModel, CustomField
 from extras.querysets import ConfigContextModelQuerySet
 from extras.querysets import ConfigContextModelQuerySet
 from netbox.choices import ColorChoices
 from netbox.choices import ColorChoices
 from netbox.config import ConfigItem
 from netbox.config import ConfigItem
-from netbox.models import NestedGroupModel, OrganizationalModel, PrimaryModel
+from netbox.models import NestedLtreeGroupModel, OrganizationalModel, PrimaryModel
 from netbox.models.features import ContactsMixin, ImageAttachmentsMixin
 from netbox.models.features import ContactsMixin, ImageAttachmentsMixin
 from netbox.models.mixins import WeightMixin
 from netbox.models.mixins import WeightMixin
 from utilities.exceptions import AbortRequest
 from utilities.exceptions import AbortRequest
@@ -386,7 +386,7 @@ class DeviceType(ImageAttachmentsMixin, PrimaryModel, WeightMixin):
 # Devices
 # Devices
 #
 #
 
 
-class DeviceRole(NestedGroupModel):
+class DeviceRole(NestedLtreeGroupModel):
     """
     """
     Devices are organized by functional role; for example, "Core Switch" or "File Server". Each DeviceRole is assigned a
     Devices are organized by functional role; for example, "Core Switch" or "File Server". Each DeviceRole is assigned a
     color to be used when displaying rack elevations. The vm_role field determines whether the role is applicable to
     color to be used when displaying rack elevations. The vm_role field determines whether the role is applicable to
@@ -443,7 +443,7 @@ class DeviceRole(NestedGroupModel):
         verbose_name_plural = _('device roles')
         verbose_name_plural = _('device roles')
 
 
 
 
-class Platform(NestedGroupModel):
+class Platform(NestedLtreeGroupModel):
     """
     """
     Platform refers to the software or firmware running on a Device. For example, "Cisco IOS-XR" or "Juniper Junos". A
     Platform refers to the software or firmware running on a Device. For example, "Cisco IOS-XR" or "Juniper Junos". A
     Platform may optionally be associated with a particular Manufacturer.
     Platform may optionally be associated with a particular Manufacturer.

+ 4 - 4
netbox/dcim/models/sites.py

@@ -10,7 +10,7 @@ from timezone_field import TimeZoneField
 
 
 from dcim.choices import *
 from dcim.choices import *
 from dcim.constants import *
 from dcim.constants import *
-from netbox.models import NestedGroupModel, PrimaryModel
+from netbox.models import NestedLtreeGroupModel, PrimaryModel
 from netbox.models.features import ContactsMixin, ImageAttachmentsMixin
 from netbox.models.features import ContactsMixin, ImageAttachmentsMixin
 
 
 __all__ = (
 __all__ = (
@@ -25,7 +25,7 @@ __all__ = (
 # Regions
 # Regions
 #
 #
 
 
-class Region(ContactsMixin, NestedGroupModel):
+class Region(ContactsMixin, NestedLtreeGroupModel):
     """
     """
     A region represents a geographic collection of sites. For example, you might create regions representing countries,
     A region represents a geographic collection of sites. For example, you might create regions representing countries,
     states, and/or cities. Regions are recursively nested into a hierarchy: all sites belonging to a child region are
     states, and/or cities. Regions are recursively nested into a hierarchy: all sites belonging to a child region are
@@ -86,7 +86,7 @@ class Region(ContactsMixin, NestedGroupModel):
 # Site groups
 # Site groups
 #
 #
 
 
-class SiteGroup(ContactsMixin, NestedGroupModel):
+class SiteGroup(ContactsMixin, NestedLtreeGroupModel):
     """
     """
     A site group is an arbitrary grouping of sites. For example, you might have corporate sites and customer sites; and
     A site group is an arbitrary grouping of sites. For example, you might have corporate sites and customer sites; and
     within corporate sites you might distinguish between offices and data centers. Like regions, site groups can be
     within corporate sites you might distinguish between offices and data centers. Like regions, site groups can be
@@ -278,7 +278,7 @@ class Site(ContactsMixin, ImageAttachmentsMixin, PrimaryModel):
 # Locations
 # Locations
 #
 #
 
 
-class Location(ContactsMixin, ImageAttachmentsMixin, NestedGroupModel):
+class Location(ContactsMixin, ImageAttachmentsMixin, NestedLtreeGroupModel):
     """
     """
     A Location represents a subgroup of Racks and/or Devices within a Site. A Location may represent a building within a
     A Location represents a subgroup of Racks and/or Devices within a Site. A Location may represent a building within a
     site, or a room within a building, for example.
     site, or a room within a building, for example.

+ 65 - 26
netbox/netbox/models/__init__.py

@@ -5,16 +5,20 @@ from django.core.validators import ValidationError
 from django.db import models
 from django.db import models
 from django.urls import reverse
 from django.urls import reverse
 from django.utils.translation import gettext_lazy as _
 from django.utils.translation import gettext_lazy as _
+from mptt.models import MPTTModel, TreeForeignKey
 
 
 from netbox.models.features import *
 from netbox.models.features import *
 from netbox.models.ltree import LtreeManager, LtreeModel
 from netbox.models.ltree import LtreeManager, LtreeModel
 from netbox.models.mixins import OwnerMixin
 from netbox.models.mixins import OwnerMixin
+from utilities.mptt import TreeManager
 from utilities.querysets import RestrictedQuerySet
 from utilities.querysets import RestrictedQuerySet
 
 
 __all__ = (
 __all__ = (
     'AdminModel',
     'AdminModel',
     'ChangeLoggedModel',
     'ChangeLoggedModel',
     'NestedGroupModel',
     'NestedGroupModel',
+    'NestedGroupModelMixin',
+    'NestedLtreeGroupModel',
     'NetBoxModel',
     'NetBoxModel',
     'OrganizationalModel',
     'OrganizationalModel',
     'PrimaryModel',
     'PrimaryModel',
@@ -158,24 +162,11 @@ class PrimaryModel(OwnerMixin, NetBoxModel):
         abstract = True
         abstract = True
 
 
 
 
-class NestedGroupModel(OwnerMixin, NetBoxModel, LtreeModel):
+class NestedGroupModelMixin(OwnerMixin, NetBoxModel):
     """
     """
-    Base model for objects which are used to form a hierarchy (regions, locations, etc.). These models nest
-    recursively using PostgreSQL ltree. Within each parent, each child instance must have a unique name.
-
-    `sort_path` is a trigger-maintained text column that mirrors MPTT's `order_insertion_by=('name',)`
-    semantics: at insert and reparent time it is set to a chr(1)-separated chain of ancestor names.
-    Ordering by `sort_path` yields tree-flatten output with siblings in their column's collation order.
-    Renaming a node does NOT update `sort_path` (matching MPTT behavior).
+    Shared field set and behavior for hierarchical group models. Concrete bases supply the
+    tree backend (MPTT or ltree) and the corresponding `parent` ForeignKey / manager.
     """
     """
-    parent = models.ForeignKey(
-        to='self',
-        on_delete=models.CASCADE,
-        related_name='children',
-        blank=True,
-        null=True,
-        db_index=True
-    )
     name = models.CharField(
     name = models.CharField(
         verbose_name=_('name'),
         verbose_name=_('name'),
         max_length=100
         max_length=100
@@ -193,19 +184,9 @@ class NestedGroupModel(OwnerMixin, NetBoxModel, LtreeModel):
         verbose_name=_('comments'),
         verbose_name=_('comments'),
         blank=True
         blank=True
     )
     )
-    sort_path = models.TextField(
-        editable=False,
-        blank=True,
-        default='',
-    )
-
-    # Re-declare so the LtreeManager wins over BaseModel's RestrictedQuerySet
-    # default manager via MRO resolution.
-    objects = LtreeManager()
 
 
     class Meta:
     class Meta:
         abstract = True
         abstract = True
-        ordering = ('sort_path',)
 
 
     def __str__(self):
     def __str__(self):
         return self.name
         return self.name
@@ -220,6 +201,64 @@ class NestedGroupModel(OwnerMixin, NetBoxModel, LtreeModel):
             })
             })
 
 
 
 
+class NestedGroupModel(NestedGroupModelMixin, MPTTModel):
+    """
+    Deprecated MPTT-backed nested group base, retained for backwards compatibility with plugins.
+
+    New code (in NetBox core and in plugins) should use `NestedLtreeGroupModel` instead. This
+    class will be removed in a future release once the deprecation period has elapsed.
+    """
+    parent = TreeForeignKey(
+        to='self',
+        on_delete=models.CASCADE,
+        related_name='children',
+        blank=True,
+        null=True,
+        db_index=True
+    )
+
+    objects = TreeManager()
+
+    class Meta:
+        abstract = True
+
+    class MPTTMeta:
+        order_insertion_by = ('name',)
+
+
+class NestedLtreeGroupModel(NestedGroupModelMixin, LtreeModel):
+    """
+    Base model for objects which are used to form a hierarchy (regions, locations, etc.). These models nest
+    recursively using PostgreSQL ltree. Within each parent, each child instance must have a unique name.
+
+    `sort_path` is a trigger-maintained text column that mirrors MPTT's `order_insertion_by=('name',)`
+    semantics: at insert and reparent time it is set to a chr(1)-separated chain of ancestor names.
+    Ordering by `sort_path` yields tree-flatten output with siblings in their column's collation order.
+    Renaming a node does NOT update `sort_path` (matching MPTT behavior).
+    """
+    parent = models.ForeignKey(
+        to='self',
+        on_delete=models.CASCADE,
+        related_name='children',
+        blank=True,
+        null=True,
+        db_index=True
+    )
+    sort_path = models.TextField(
+        editable=False,
+        blank=True,
+        default='',
+    )
+
+    # Re-declare so the LtreeManager wins over BaseModel's RestrictedQuerySet
+    # default manager via MRO resolution.
+    objects = LtreeManager()
+
+    class Meta:
+        abstract = True
+        ordering = ('sort_path',)
+
+
 class OrganizationalModel(OwnerMixin, NetBoxModel):
 class OrganizationalModel(OwnerMixin, NetBoxModel):
     """
     """
     Organizational models are those which are used solely to categorize and qualify other objects, and do not convey
     Organizational models are those which are used solely to categorize and qualify other objects, and do not convey

+ 5 - 2
netbox/netbox/models/features.py

@@ -408,13 +408,16 @@ class ContactsMixin(models.Model):
         """
         """
         from tenancy.models import ContactAssignment
         from tenancy.models import ContactAssignment
 
 
-        from . import NestedGroupModel
+        # TODO: Once the deprecated MPTT-backed NestedGroupModel is removed, narrow this
+        #  check to NestedLtreeGroupModel (or drop the mixin alias). NestedGroupModelMixin
+        #  exists only so both legacy MPTT and new ltree bases satisfy the inheritance check.
+        from . import NestedGroupModelMixin
 
 
         filter = Q(
         filter = Q(
             object_type=ObjectType.objects.get_for_model(self),
             object_type=ObjectType.objects.get_for_model(self),
             object_id__in=(
             object_id__in=(
                 self.get_ancestors(include_self=True)
                 self.get_ancestors(include_self=True)
-                if (isinstance(self, NestedGroupModel) and inherited)
+                if (isinstance(self, NestedGroupModelMixin) and inherited)
                 else [self.pk]
                 else [self.pk]
             ),
             ),
         )
         )

+ 9 - 9
netbox/netbox/tests/test_base_classes.py

@@ -44,7 +44,7 @@ from netbox.graphql.types import (
     OrganizationalObjectType,
     OrganizationalObjectType,
     PrimaryObjectType,
     PrimaryObjectType,
 )
 )
-from netbox.models import NestedGroupModel, NetBoxModel, OrganizationalModel, PrimaryModel
+from netbox.models import NestedGroupModelMixin, NetBoxModel, OrganizationalModel, PrimaryModel
 from netbox.registry import registry
 from netbox.registry import registry
 from netbox.tables import (
 from netbox.tables import (
     NestedGroupModelTable,
     NestedGroupModelTable,
@@ -76,7 +76,7 @@ class FormClassesTestCase(TestCase):
             return PrimaryModelForm
             return PrimaryModelForm
         if issubclass(model, OrganizationalModel):
         if issubclass(model, OrganizationalModel):
             return OrganizationalModelForm
             return OrganizationalModelForm
-        if issubclass(model, NestedGroupModel):
+        if issubclass(model, NestedGroupModelMixin):
             return NestedGroupModelForm
             return NestedGroupModelForm
         if issubclass(model, NetBoxModel):
         if issubclass(model, NetBoxModel):
             return NetBoxModelForm
             return NetBoxModelForm
@@ -93,7 +93,7 @@ class FormClassesTestCase(TestCase):
             return PrimaryModelBulkEditForm
             return PrimaryModelBulkEditForm
         if issubclass(model, OrganizationalModel):
         if issubclass(model, OrganizationalModel):
             return OrganizationalModelBulkEditForm
             return OrganizationalModelBulkEditForm
-        if issubclass(model, NestedGroupModel):
+        if issubclass(model, NestedGroupModelMixin):
             return NestedGroupModelBulkEditForm
             return NestedGroupModelBulkEditForm
         if issubclass(model, NetBoxModel):
         if issubclass(model, NetBoxModel):
             return NetBoxModelBulkEditForm
             return NetBoxModelBulkEditForm
@@ -110,7 +110,7 @@ class FormClassesTestCase(TestCase):
             return PrimaryModelImportForm
             return PrimaryModelImportForm
         if issubclass(model, OrganizationalModel):
         if issubclass(model, OrganizationalModel):
             return OrganizationalModelImportForm
             return OrganizationalModelImportForm
-        if issubclass(model, NestedGroupModel):
+        if issubclass(model, NestedGroupModelMixin):
             return NestedGroupModelImportForm
             return NestedGroupModelImportForm
         if issubclass(model, NetBoxModel):
         if issubclass(model, NetBoxModel):
             return NetBoxModelImportForm
             return NetBoxModelImportForm
@@ -127,7 +127,7 @@ class FormClassesTestCase(TestCase):
             return PrimaryModelFilterSetForm
             return PrimaryModelFilterSetForm
         if issubclass(model, OrganizationalModel):
         if issubclass(model, OrganizationalModel):
             return OrganizationalModelFilterSetForm
             return OrganizationalModelFilterSetForm
-        if issubclass(model, NestedGroupModel):
+        if issubclass(model, NestedGroupModelMixin):
             return NestedGroupModelFilterSetForm
             return NestedGroupModelFilterSetForm
         if issubclass(model, NetBoxModel):
         if issubclass(model, NetBoxModel):
             return NetBoxModelFilterSetForm
             return NetBoxModelFilterSetForm
@@ -191,7 +191,7 @@ class FilterSetClassesTestCase(TestCase):
             return PrimaryModelFilterSet
             return PrimaryModelFilterSet
         if issubclass(model, OrganizationalModel):
         if issubclass(model, OrganizationalModel):
             return OrganizationalModelFilterSet
             return OrganizationalModelFilterSet
-        if issubclass(model, NestedGroupModel):
+        if issubclass(model, NestedGroupModelMixin):
             return NestedGroupModelFilterSet
             return NestedGroupModelFilterSet
         if issubclass(model, NetBoxModel):
         if issubclass(model, NetBoxModel):
             return NetBoxModelFilterSet
             return NetBoxModelFilterSet
@@ -233,7 +233,7 @@ class TableClassesTestCase(TestCase):
             return PrimaryModelTable
             return PrimaryModelTable
         if issubclass(model, OrganizationalModel):
         if issubclass(model, OrganizationalModel):
             return OrganizationalModelTable
             return OrganizationalModelTable
-        if issubclass(model, NestedGroupModel):
+        if issubclass(model, NestedGroupModelMixin):
             return NestedGroupModelTable
             return NestedGroupModelTable
         if issubclass(model, NetBoxModel):
         if issubclass(model, NetBoxModel):
             return NetBoxTable
             return NetBoxTable
@@ -278,7 +278,7 @@ class SerializerClassesTestCase(TestCase):
             return PrimaryModelSerializer
             return PrimaryModelSerializer
         if issubclass(model, OrganizationalModel):
         if issubclass(model, OrganizationalModel):
             return OrganizationalModelSerializer
             return OrganizationalModelSerializer
-        if issubclass(model, NestedGroupModel):
+        if issubclass(model, NestedGroupModelMixin):
             return NestedGroupModelSerializer
             return NestedGroupModelSerializer
         if issubclass(model, NetBoxModel):
         if issubclass(model, NetBoxModel):
             return NetBoxModelSerializer
             return NetBoxModelSerializer
@@ -319,7 +319,7 @@ class GraphQLTypeClassesTestCase(TestCase):
             return PrimaryObjectType
             return PrimaryObjectType
         if issubclass(model, OrganizationalModel):
         if issubclass(model, OrganizationalModel):
             return OrganizationalObjectType
             return OrganizationalObjectType
-        if issubclass(model, NestedGroupModel):
+        if issubclass(model, NestedGroupModelMixin):
             return NestedGroupObjectType
             return NestedGroupObjectType
         if issubclass(model, NetBoxModel):
         if issubclass(model, NetBoxModel):
             return NetBoxObjectType
             return NetBoxObjectType

+ 2 - 2
netbox/tenancy/models/contacts.py

@@ -6,7 +6,7 @@ from django.db.models.expressions import RawSQL
 from django.urls import reverse
 from django.urls import reverse
 from django.utils.translation import gettext_lazy as _
 from django.utils.translation import gettext_lazy as _
 
 
-from netbox.models import ChangeLoggedModel, NestedGroupModel, OrganizationalModel, PrimaryModel
+from netbox.models import ChangeLoggedModel, NestedLtreeGroupModel, OrganizationalModel, PrimaryModel
 from netbox.models.features import CustomFieldsMixin, ExportTemplatesMixin, TagsMixin, has_feature
 from netbox.models.features import CustomFieldsMixin, ExportTemplatesMixin, TagsMixin, has_feature
 from netbox.models.ltree import LtreeManager
 from netbox.models.ltree import LtreeManager
 from tenancy.choices import *
 from tenancy.choices import *
@@ -40,7 +40,7 @@ class ContactGroupManager(LtreeManager):
         )
         )
 
 
 
 
-class ContactGroup(NestedGroupModel):
+class ContactGroup(NestedLtreeGroupModel):
     """
     """
     An arbitrary collection of Contacts.
     An arbitrary collection of Contacts.
     """
     """

+ 2 - 2
netbox/tenancy/models/tenants.py

@@ -3,7 +3,7 @@ from django.db import models
 from django.db.models import Q
 from django.db.models import Q
 from django.utils.translation import gettext_lazy as _
 from django.utils.translation import gettext_lazy as _
 
 
-from netbox.models import NestedGroupModel, PrimaryModel
+from netbox.models import NestedLtreeGroupModel, PrimaryModel
 from netbox.models.features import ContactsMixin
 from netbox.models.features import ContactsMixin
 
 
 __all__ = (
 __all__ = (
@@ -12,7 +12,7 @@ __all__ = (
 )
 )
 
 
 
 
-class TenantGroup(NestedGroupModel):
+class TenantGroup(NestedLtreeGroupModel):
     """
     """
     An arbitrary collection of Tenants.
     An arbitrary collection of Tenants.
     """
     """

+ 24 - 0
netbox/utilities/mptt.py

@@ -0,0 +1,24 @@
+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

+ 2 - 2
netbox/wireless/models.py

@@ -6,7 +6,7 @@ from django.utils.translation import gettext_lazy as _
 from dcim.choices import LinkStatusChoices
 from dcim.choices import LinkStatusChoices
 from dcim.constants import WIRELESS_IFACE_TYPES
 from dcim.constants import WIRELESS_IFACE_TYPES
 from dcim.models.mixins import CachedScopeMixin
 from dcim.models.mixins import CachedScopeMixin
-from netbox.models import NestedGroupModel, PrimaryModel
+from netbox.models import NestedLtreeGroupModel, PrimaryModel
 from netbox.models.mixins import DistanceMixin
 from netbox.models.mixins import DistanceMixin
 
 
 from .choices import *
 from .choices import *
@@ -47,7 +47,7 @@ class WirelessAuthenticationBase(models.Model):
         abstract = True
         abstract = True
 
 
 
 
-class WirelessLANGroup(NestedGroupModel):
+class WirelessLANGroup(NestedLtreeGroupModel):
     """
     """
     A nested grouping of WirelessLANs
     A nested grouping of WirelessLANs
     """
     """