Closes #10500: Introduce support for nested modules (#16983)

* 10500 add ModularComponentModel

* 10500 add ModularComponentModel

* 10500 add to forms

* 10500 add to serializer, tables

* 10500 template

* 10500 add docs

* 10500 check recursion

* 10500 fix graphql

* 10500 fix conflicting migration from merge

* 10500 token resolution

* 10500 don't return reverse

* 10500 don't return reverse / optimize

* Add ModuleTypeModuleBaysView

* Fix replication of module bays on new modules

* Clean up tables & templates

* Adjust uniqueness constraints

* Correct URL

* Clean up docs

* Fix up serializers

* 10500 add filterset tests

* 10500 add nested validation to Module

* Misc cleanup

* 10500 ModuleBay recursion Test

* 10500 ModuleBay recursion Test

* 10500 ModuleBay recursion Test

* 10500 ModuleBay recursion Test

* Enable MPTT for module bays

* Fix tests

* Fix validation of module token in component names

* Misc cleanup

* Merge migrations

* Fix table ordering

---------

Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>
This commit is contained in:
Arthur Hanson
2024-08-06 00:13:59 +07:00
committed by GitHub
parent 57fe2071a4
commit 796b9e84af
21 changed files with 475 additions and 86 deletions

View File

@@ -4,7 +4,7 @@ from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelatio
from django.core.exceptions import ValidationError
from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import models
from django.db.models import Sum
from django.db.models import F, Sum
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from mptt.models import MPTTModel, TreeForeignKey
@@ -1087,10 +1087,19 @@ class RearPort(ModularComponentModel, CabledObjectModel, TrackingModelMixin):
# Bays
#
class ModuleBay(ComponentModel, TrackingModelMixin):
class ModuleBay(ModularComponentModel, TrackingModelMixin, MPTTModel):
"""
An empty space within a Device which can house a child device
"""
parent = TreeForeignKey(
to='self',
on_delete=models.CASCADE,
related_name='children',
blank=True,
null=True,
editable=False,
db_index=True
)
position = models.CharField(
verbose_name=_('position'),
max_length=30,
@@ -1098,15 +1107,45 @@ class ModuleBay(ComponentModel, TrackingModelMixin):
help_text=_('Identifier to reference when renaming installed components')
)
objects = TreeManager()
clone_fields = ('device',)
class Meta(ComponentModel.Meta):
class Meta(ModularComponentModel.Meta):
constraints = (
models.UniqueConstraint(
fields=('device', 'module', 'name'),
name='%(app_label)s_%(class)s_unique_device_module_name'
),
)
verbose_name = _('module bay')
verbose_name_plural = _('module bays')
class MPTTMeta:
order_insertion_by = ('module',)
def get_absolute_url(self):
return reverse('dcim:modulebay', kwargs={'pk': self.pk})
def clean(self):
super().clean()
# Check for recursion
if module := self.module:
module_bays = [self.pk]
modules = []
while module:
if module.pk in modules or module.module_bay.pk in module_bays:
raise ValidationError(_("A module bay cannot belong to a module installed within it."))
modules.append(module.pk)
module_bays.append(module.module_bay.pk)
module = module.module_bay.module if module.module_bay else None
def save(self, *args, **kwargs):
if self.module:
self.parent = self.module.module_bay
super().save(*args, **kwargs)
class DeviceBay(ComponentModel, TrackingModelMixin):
"""