Skip to content

Commit

Permalink
Closes #1846: Enable MPTT for InventoryItem hierarchy
Browse files Browse the repository at this point in the history
  • Loading branch information
jeremystretch committed Sep 18, 2020
1 parent 0030fe1 commit 230e7bb
Show file tree
Hide file tree
Showing 12 changed files with 98 additions and 32 deletions.
1 change: 1 addition & 0 deletions docs/release-notes/version-2.10.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

### Other Changes

* [#1846](https://github.com/netbox-community/netbox/issues/1846) - Enable MPTT for InventoryItem hierarchy
* [#4349](https://github.com/netbox-community/netbox/issues/4349) - Dropped support for embedded graphs
* [#4360](https://github.com/netbox-community/netbox/issues/4360) - Remove support for the Django template language from export templates
* [#4878](https://github.com/netbox-community/netbox/issues/4878) - Custom field data is now stored directly on each object
Expand Down
3 changes: 2 additions & 1 deletion netbox/dcim/api/nested_serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -305,10 +305,11 @@ class Meta:
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)

class Meta:
model = models.InventoryItem
fields = ['id', 'url', 'device', 'name']
fields = ['id', 'url', 'device', 'name', '_depth']


#
Expand Down
3 changes: 2 additions & 1 deletion netbox/dcim/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -636,12 +636,13 @@ class InventoryItemSerializer(TaggedObjectSerializer, ValidatedModelSerializer):
# Provide a default value to satisfy UniqueTogetherValidator
parent = serializers.PrimaryKeyRelatedField(queryset=InventoryItem.objects.all(), allow_null=True, default=None)
manufacturer = NestedManufacturerSerializer(required=False, allow_null=True, default=None)
_depth = serializers.IntegerField(source='level', read_only=True)

class Meta:
model = InventoryItem
fields = [
'id', 'url', 'device', 'parent', 'name', 'label', 'manufacturer', 'part_id', 'serial', 'asset_tag',
'discovered', 'description', 'tags',
'discovered', 'description', 'tags', '_depth',
]


Expand Down
44 changes: 44 additions & 0 deletions netbox/dcim/migrations/0117_inventoryitem_mptt.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
from django.db import migrations, models
import django.db.models.deletion
import mptt.fields


class Migration(migrations.Migration):

dependencies = [
('dcim', '0116_custom_field_data'),
]

operations = [
# The MPTT will be rebuilt in the following migration. Using dummy values for now.
migrations.AddField(
model_name='inventoryitem',
name='level',
field=models.PositiveIntegerField(default=0, editable=False),
preserve_default=False,
),
migrations.AddField(
model_name='inventoryitem',
name='lft',
field=models.PositiveIntegerField(default=0, editable=False),
preserve_default=False,
),
migrations.AddField(
model_name='inventoryitem',
name='rght',
field=models.PositiveIntegerField(default=0, editable=False),
preserve_default=False,
),
migrations.AddField(
model_name='inventoryitem',
name='tree_id',
field=models.PositiveIntegerField(db_index=True, default=0, editable=False),
preserve_default=False,
),
# Convert ForeignKey to TreeForeignKey
migrations.AlterField(
model_name='inventoryitem',
name='parent',
field=mptt.fields.TreeForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='child_items', to='dcim.inventoryitem'),
),
]
26 changes: 26 additions & 0 deletions netbox/dcim/migrations/0118_inventoryitem_mptt_rebuild.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from django.db import migrations
import mptt
import mptt.managers


def rebuild_mptt(apps, schema_editor):
manager = mptt.managers.TreeManager()
InventoryItem = apps.get_model('dcim', 'InventoryItem')
manager.model = InventoryItem
mptt.register(InventoryItem)
manager.contribute_to_class(InventoryItem, 'objects')
manager.rebuild()


class Migration(migrations.Migration):

dependencies = [
('dcim', '0117_inventoryitem_mptt'),
]

operations = [
migrations.RunPython(
code=rebuild_mptt,
reverse_code=migrations.RunPython.noop
),
]
11 changes: 8 additions & 3 deletions netbox/dcim/models/device_components.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from django.db import models
from django.db.models import Sum
from django.urls import reverse
from mptt.models import MPTTModel, TreeForeignKey
from taggit.managers import TaggableManager

from dcim.choices import *
Expand All @@ -15,6 +16,7 @@
from extras.models import ObjectChange, TaggedItem
from extras.utils import extras_features
from utilities.fields import NaturalOrderingField
from utilities.mptt import TreeManager
from utilities.ordering import naturalize_interface
from utilities.querysets import RestrictedQuerySet
from utilities.query_functions import CollateAsChar
Expand Down Expand Up @@ -952,17 +954,18 @@ def clean(self):
#

@extras_features('export_templates', 'webhooks')
class InventoryItem(ComponentModel):
class InventoryItem(MPTTModel, 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 = models.ForeignKey(
parent = TreeForeignKey(
to='self',
on_delete=models.CASCADE,
related_name='child_items',
blank=True,
null=True
null=True,
db_index=True
)
manufacturer = models.ForeignKey(
to='dcim.Manufacturer',
Expand Down Expand Up @@ -997,6 +1000,8 @@ class InventoryItem(ComponentModel):

tags = TaggableManager(through=TaggedItem)

objects = TreeManager()

csv_headers = [
'device', 'name', 'label', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'discovered', 'description',
]
Expand Down
11 changes: 4 additions & 7 deletions netbox/dcim/tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -1273,7 +1273,7 @@ def setUpTestData(cls):

class InventoryItemTest(APIViewTestCases.APIViewTestCase):
model = InventoryItem
brief_fields = ['device', 'id', 'name', 'url']
brief_fields = ['_depth', 'device', 'id', 'name', 'url']

@classmethod
def setUpTestData(cls):
Expand All @@ -1283,12 +1283,9 @@ def setUpTestData(cls):
devicerole = DeviceRole.objects.create(name='Test Device Role 1', slug='test-device-role-1', color='ff0000')
device = Device.objects.create(device_type=devicetype, device_role=devicerole, name='Device 1', site=site)

inventory_items = (
InventoryItem(device=device, name='Inventory Item 1', manufacturer=manufacturer),
InventoryItem(device=device, name='Inventory Item 2', manufacturer=manufacturer),
InventoryItem(device=device, name='Inventory Item 3', manufacturer=manufacturer),
)
InventoryItem.objects.bulk_create(inventory_items)
InventoryItem.objects.create(device=device, name='Inventory Item 1', manufacturer=manufacturer)
InventoryItem.objects.create(device=device, name='Inventory Item 2', manufacturer=manufacturer)
InventoryItem.objects.create(device=device, name='Inventory Item 3', manufacturer=manufacturer)

cls.create_data = [
{
Expand Down
6 changes: 4 additions & 2 deletions netbox/dcim/tests/test_filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -2285,14 +2285,16 @@ def setUpTestData(cls):
InventoryItem(device=devices[1], manufacturer=manufacturers[1], name='Inventory Item 2', part_id='1002', serial='DEF', asset_tag='1002', discovered=True, description='Second'),
InventoryItem(device=devices[2], manufacturer=manufacturers[2], name='Inventory Item 3', part_id='1003', serial='GHI', asset_tag='1003', discovered=False, description='Third'),
)
InventoryItem.objects.bulk_create(inventory_items)
for i in inventory_items:
i.save()

child_inventory_items = (
InventoryItem(device=devices[0], name='Inventory Item 1A', parent=inventory_items[0]),
InventoryItem(device=devices[1], name='Inventory Item 2A', parent=inventory_items[1]),
InventoryItem(device=devices[2], name='Inventory Item 3A', parent=inventory_items[2]),
)
InventoryItem.objects.bulk_create(child_inventory_items)
for i in child_inventory_items:
i.save()

def test_id(self):
params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
Expand Down
8 changes: 3 additions & 5 deletions netbox/dcim/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -1430,11 +1430,9 @@ def setUpTestData(cls):
device = create_test_device('Device 1')
manufacturer, _ = Manufacturer.objects.get_or_create(name='Manufacturer 1', slug='manufacturer-1')

InventoryItem.objects.bulk_create([
InventoryItem(device=device, name='Inventory Item 1'),
InventoryItem(device=device, name='Inventory Item 2'),
InventoryItem(device=device, name='Inventory Item 3'),
])
InventoryItem.objects.create(device=device, name='Inventory Item 1')
InventoryItem.objects.create(device=device, name='Inventory Item 2')
InventoryItem.objects.create(device=device, name='Inventory Item 3')

tags = cls.create_tags('Alpha', 'Bravo', 'Charlie')

Expand Down
6 changes: 2 additions & 4 deletions netbox/dcim/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -1089,10 +1089,8 @@ def get(self, request, pk):

device = get_object_or_404(self.queryset, pk=pk)
inventory_items = InventoryItem.objects.restrict(request.user, 'view').filter(
device=device, parent=None
).prefetch_related(
'manufacturer', 'child_items'
)
device=device
).prefetch_related('manufacturer')

return render(request, 'dcim/device_inventory.html', {
'device': device,
Expand Down
4 changes: 1 addition & 3 deletions netbox/templates/dcim/device_inventory.html
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,7 @@
</thead>
<tbody>
{% for item in inventory_items %}
{% with template_name='dcim/inc/inventoryitem.html' indent=0 %}
{% include template_name %}
{% endwith %}
{% include 'dcim/inc/inventoryitem.html' %}
{% endfor %}
</tbody>
</table>
Expand Down
7 changes: 1 addition & 6 deletions netbox/templates/dcim/inc/inventoryitem.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
</td>
{% endif %}

<td style="padding-left: {{ indent|add:5 }}px">
<td style="padding-left: {{ item.level }}0px">
<a href="{{ item.get_absolute_url }}">{{ item }}</a>
</td>
<td>
Expand Down Expand Up @@ -38,8 +38,3 @@
{% endif %}
</td>
</tr>
{% for item in item.child_items.all %}
{% with template_name='dcim/inc/inventoryitem.html' indent=indent|add:20 %}
{% include template_name %}
{% endwith %}
{% endfor %}

0 comments on commit 230e7bb

Please sign in to comment.