Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Netbox 4 compatibility #165

Merged
merged 21 commits into from
May 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ jobs:
strategy:
fail-fast: false
matrix:
netbox-version: ["v3.7.5"]
netbox-version: ["v4.0.0"]
services:
redis:
image: redis
Expand Down
4 changes: 2 additions & 2 deletions netbox_inventory/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from extras.plugins import PluginConfig
from netbox.plugins import PluginConfig
from .version import __version__


Expand All @@ -10,7 +10,7 @@ class NetBoxInventoryConfig(PluginConfig):
author = 'Matej Vadnjal'
author_email = '[email protected]'
base_url = 'inventory'
min_version = '3.7.0'
min_version = '4.0.0'
default_settings = {
'top_level_menu': True,
'used_status_name': 'used',
Expand Down
60 changes: 1 addition & 59 deletions netbox_inventory/api/nested_serializers.py
Original file line number Diff line number Diff line change
@@ -1,71 +1,13 @@
from rest_framework import serializers

from dcim.api.serializers import NestedManufacturerSerializer
from netbox.api.serializers import WritableNestedSerializer
from ..models import Asset, Delivery, InventoryItemType, InventoryItemGroup, Purchase, Supplier
from ..models import InventoryItemGroup

__all__ = (
'NestedAssetSerializer',
'NestedSupplierSerializer',
'NestedPurchaseSerializer',
'NestedDeliverySerializer',
'NestedInventoryItemTypeSerializer',
'NestedInventoryItemGroupSerializer',
)


class NestedAssetSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(
view_name='plugins-api:netbox_inventory-api:asset-detail'
)

class Meta:
model = Asset
fields = ('id', 'url', 'display', 'serial')


class NestedSupplierSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(
view_name='plugins-api:netbox_inventory-api:supplier-detail'
)

class Meta:
model = Supplier
fields = ('id', 'url', 'display', 'name', 'slug')


class NestedPurchaseSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(
view_name='plugins-api:netbox_inventory-api:purchase-detail'
)
supplier = NestedSupplierSerializer(read_only=True)

class Meta:
model = Purchase
fields = ('id', 'url', 'display', 'supplier', 'name', 'status', 'date')


class NestedDeliverySerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(
view_name='plugins-api:netbox_inventory-api:delivery-detail'
)

class Meta:
model = Delivery
fields = ('id', 'url', 'display', 'name', 'date')


class NestedInventoryItemTypeSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(
view_name='plugins-api:netbox_inventory-api:inventoryitemtype-detail'
)
manufacturer = NestedManufacturerSerializer(read_only=True)

class Meta:
model = InventoryItemType
fields = ('id', 'url', 'display', 'manufacturer', 'model', 'slug')


class NestedInventoryItemGroupSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(
view_name='plugins-api:netbox_inventory-api:inventoryitemgroup-detail'
Expand Down
134 changes: 70 additions & 64 deletions netbox_inventory/api/serializers.py
Original file line number Diff line number Diff line change
@@ -1,62 +1,16 @@
from rest_framework import serializers

from dcim.api.serializers import (
NestedDeviceTypeSerializer, NestedDeviceSerializer,
NestedManufacturerSerializer,
NestedModuleTypeSerializer, NestedModuleSerializer,
NestedInventoryItemSerializer, NestedLocationSerializer
DeviceTypeSerializer, DeviceSerializer, ManufacturerSerializer,
ModuleTypeSerializer, ModuleSerializer, InventoryItemSerializer,
LocationSerializer
)
from tenancy.api.serializers import NestedContactSerializer, NestedTenantSerializer
from netbox.api.serializers import NetBoxModelSerializer
from tenancy.api.serializers import ContactSerializer, TenantSerializer
from netbox.api.serializers import NestedGroupModelSerializer, NetBoxModelSerializer
from .nested_serializers import *
from ..models import Asset, Delivery, InventoryItemType, InventoryItemGroup, Purchase, Supplier


class AssetSerializer(NetBoxModelSerializer):
url = serializers.HyperlinkedIdentityField(
view_name='plugins-api:netbox_inventory-api:asset-detail'
)
device_type = NestedDeviceTypeSerializer(required=False, allow_null=True, default=None)
device = NestedDeviceSerializer(required=False, allow_null=True, default=None)
module_type = NestedModuleTypeSerializer(required=False, allow_null=True, default=None)
module = NestedModuleSerializer(required=False, allow_null=True, default=None)
inventoryitem_type = NestedInventoryItemTypeSerializer(required=False, allow_null=True, default=None)
inventoryitem = NestedInventoryItemSerializer(required=False, allow_null=True, default=None)
storage_location = NestedLocationSerializer(required=False, allow_null=True, default=None)
delivery = NestedDeliverySerializer(required=False, allow_null=True, default=None)
purchase = NestedPurchaseSerializer(required=False, allow_null=True, default=None)
tenant = NestedTenantSerializer(required=False, allow_null=True, default=None)
contact = NestedContactSerializer(required=False, allow_null=True, default=None)
owner = NestedTenantSerializer(required=False, allow_null=True, default=None)

def to_internal_value(self, data):
ret = super().to_internal_value(data)
# if only delivery set, infer pruchase from it
if 'delivery' in ret and ret['delivery'] and not ret.get('purchase'):
ret['purchase'] = ret['delivery'].purchase
if 'asset_tag' in ret and ret['asset_tag'] == '':
ret['asset_tag'] = None
if 'serial' in ret and ret['serial'] == '':
ret['serial'] = None
return ret

class Meta:
model = Asset
fields = (
'id', 'url', 'display', 'name', 'asset_tag', 'serial', 'status',
'kind', 'device_type', 'device', 'module_type', 'module', 'inventoryitem_type','inventoryitem',
'tenant', 'contact', 'storage_location', 'owner', 'delivery', 'purchase',
'warranty_start', 'warranty_end',
'comments', 'tags', 'custom_fields', 'created', 'last_updated'
)
# DRF autiomatically creates validator from model's unique_together contraints
# that doesn't work if we allow some filelds in a unique_together to be null
# so we remove DRF's auto generated validators and rely on model's validation
# logic to handle validation
# see https://www.django-rest-framework.org/api-guide/validators/#optional-fields
validators = []


class SupplierSerializer(NetBoxModelSerializer):
url = serializers.HyperlinkedIdentityField(
view_name='plugins-api:netbox_inventory-api:supplier-detail'
Expand All @@ -72,13 +26,14 @@ class Meta:
'tags', 'custom_fields', 'created', 'last_updated', 'asset_count',
'purchase_count', 'delivery_count',
)
brief_fields = ('id', 'url', 'display', 'name', 'slug', 'description')


class PurchaseSerializer(NetBoxModelSerializer):
url = serializers.HyperlinkedIdentityField(
view_name='plugins-api:netbox_inventory-api:purchase-detail'
)
supplier = NestedSupplierSerializer()
supplier = SupplierSerializer(nested=True)
asset_count = serializers.IntegerField(read_only=True)
delivery_count = serializers.IntegerField(read_only=True)

Expand All @@ -89,14 +44,15 @@ class Meta:
'comments', 'tags', 'custom_fields', 'created', 'last_updated',
'asset_count', 'delivery_count',
)
brief_fields = ('id', 'url', 'display', 'supplier', 'name', 'status', 'date', 'description')


class DeliverySerializer(NetBoxModelSerializer):
url = serializers.HyperlinkedIdentityField(
view_name='plugins-api:netbox_inventory-api:delivery-detail'
)
purchase = NestedPurchaseSerializer()
receiving_contact = NestedContactSerializer(required=False, allow_null=True, default=None)
purchase = PurchaseSerializer(nested=True)
receiving_contact = ContactSerializer(nested=True, required=False, allow_null=True, default=None)
asset_count = serializers.IntegerField(read_only=True)

class Meta:
Expand All @@ -105,14 +61,31 @@ class Meta:
'id', 'url', 'display', 'purchase', 'name', 'date', 'description', 'comments',
'receiving_contact', 'tags', 'custom_fields', 'created', 'last_updated', 'asset_count',
)
brief_fields = ('id', 'url', 'display', 'name', 'date', 'description')


class InventoryItemGroupSerializer(NestedGroupModelSerializer):
url = serializers.HyperlinkedIdentityField(
view_name='plugins-api:netbox_inventory-api:inventoryitemgroup-detail'
)
parent = NestedInventoryItemGroupSerializer(required=False, allow_null=True, default=None)
asset_count = serializers.IntegerField(read_only=True)

class Meta:
model = InventoryItemGroup
fields = (
'id', 'url', 'display', 'name', 'parent', 'comments', 'tags', 'custom_fields',
'created', 'last_updated', 'asset_count', '_depth',
)
brief_fields = ('id', 'url', 'display', 'name', '_depth')


class InventoryItemTypeSerializer(NetBoxModelSerializer):
url = serializers.HyperlinkedIdentityField(
view_name='plugins-api:netbox_inventory-api:inventoryitemtype-detail'
)
manufacturer = NestedManufacturerSerializer()
inventoryitem_group = NestedInventoryItemGroupSerializer(required=False, allow_null=True, default=None)
manufacturer = ManufacturerSerializer(nested=True)
inventoryitem_group = InventoryItemGroupSerializer(nested=True, required=False, allow_null=True, default=None)
asset_count = serializers.IntegerField(read_only=True)

class Meta:
Expand All @@ -122,18 +95,51 @@ class Meta:
'inventoryitem_group', 'comments', 'tags', 'custom_fields', 'created',
'last_updated', 'asset_count',
)
brief_fields = ('id', 'url', 'display', 'manufacturer', 'model', 'slug')


class InventoryItemGroupSerializer(NetBoxModelSerializer):
class AssetSerializer(NetBoxModelSerializer):
url = serializers.HyperlinkedIdentityField(
view_name='plugins-api:netbox_inventory-api:inventoryitemgroup-detail'
view_name='plugins-api:netbox_inventory-api:asset-detail'
)
parent = NestedInventoryItemGroupSerializer(required=False, allow_null=True, default=None)
asset_count = serializers.IntegerField(read_only=True)

device_type = DeviceTypeSerializer(nested=True, required=False, allow_null=True, default=None)
device = DeviceSerializer(nested=True, required=False, allow_null=True, default=None)
module_type = ModuleTypeSerializer(nested=True, required=False, allow_null=True, default=None)
module = ModuleSerializer(nested=True, required=False, allow_null=True, default=None)
inventoryitem_type = InventoryItemTypeSerializer(nested=True, required=False, allow_null=True, default=None)
inventoryitem = InventoryItemSerializer(nested=True, required=False, allow_null=True, default=None)
storage_location = LocationSerializer(nested=True, required=False, allow_null=True, default=None)
delivery = DeliverySerializer(nested=True, required=False, allow_null=True, default=None)
purchase = PurchaseSerializer(nested=True, required=False, allow_null=True, default=None)
tenant = TenantSerializer(nested=True, required=False, allow_null=True, default=None)
contact = ContactSerializer(nested=True, required=False, allow_null=True, default=None)
owner = TenantSerializer(nested=True, required=False, allow_null=True, default=None)

def to_internal_value(self, data):
ret = super().to_internal_value(data)
# if only delivery set, infer pruchase from it
if 'delivery' in ret and ret['delivery'] and not ret.get('purchase'):
ret['purchase'] = ret['delivery'].purchase
if 'asset_tag' in ret and ret['asset_tag'] == '':
ret['asset_tag'] = None
if 'serial' in ret and ret['serial'] == '':
ret['serial'] = None
return ret

class Meta:
model = InventoryItemGroup
model = Asset
fields = (
'id', 'url', 'display', 'name', 'parent', 'comments', 'tags', 'custom_fields',
'created', 'last_updated', 'asset_count',
'id', 'url', 'display', 'name', 'asset_tag', 'serial', 'status',
'kind', 'device_type', 'device', 'module_type', 'module', 'inventoryitem_type','inventoryitem',
'tenant', 'contact', 'storage_location', 'owner', 'delivery', 'purchase',
'warranty_start', 'warranty_end',
'comments', 'tags', 'custom_fields', 'created', 'last_updated'
)
brief_fields = ('id', 'url', 'display', 'serial', 'name')
# DRF autiomatically creates validator from model's unique_together contraints
# that doesn't work if we allow some filelds in a unique_together to be null
# so we remove DRF's auto generated validators and rely on model's validation
# logic to handle validation
# see https://www.django-rest-framework.org/api-guide/validators/#optional-fields
validators = []

2 changes: 1 addition & 1 deletion netbox_inventory/api/views.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from dcim.api.views import DeviceViewSet, InventoryItemViewSet, ModuleViewSet
from netbox.api.viewsets import NetBoxModelViewSet
from utilities.utils import count_related
from utilities.query import count_related
from .. import filtersets, models
from .serializers import (
AssetSerializer, InventoryItemTypeSerializer, InventoryItemGroupSerializer,
Expand Down
19 changes: 10 additions & 9 deletions netbox_inventory/forms/assign.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from netbox.forms import NetBoxModelForm
from tenancy.models import Contact, Tenant
from utilities.forms.fields import DynamicModelChoiceField
from utilities.forms.rendering import FieldSet
from utilities.forms.widgets import APISelect
from ..models import Asset

Expand Down Expand Up @@ -93,9 +94,9 @@ class AssetDeviceAssignForm(AssetAssignMixin, NetBoxModelForm):
)

fieldsets = (
('Asset', ('device_type', 'name')),
('Device', ('site', 'device')),
('Assigned to', ('tenant', 'contact')),
FieldSet('device_type', 'name', name='Asset'),
FieldSet('site', 'device', name='Device'),
FieldSet('tenant', 'contact', name='Assigned to'),
)

class Meta:
Expand Down Expand Up @@ -131,9 +132,9 @@ class AssetModuleAssignForm(AssetAssignMixin, NetBoxModelForm):
)

fieldsets = (
('Asset', ('module_type', 'name')),
('Module', ('device', 'module',)),
('Tenancy', ('tenant', 'contact')),
FieldSet('module_type', 'name', name='Asset'),
FieldSet('device', 'module', name='Module'),
FieldSet('tenant', 'contact', name='Tenancy'),
)

class Meta:
Expand Down Expand Up @@ -175,9 +176,9 @@ class AssetInventoryItemAssignForm(AssetAssignMixin, NetBoxModelForm):
)

fieldsets = (
('Asset', ('inventoryitem_type', 'name')),
('Inventory Item', ('device', 'inventoryitem')),
('Tenancy', ('tenant', 'contact')),
FieldSet('inventoryitem_type', 'name', name='Asset'),
FieldSet('device', 'inventoryitem', name='Inventory Item'),
FieldSet('tenant', 'contact', name='Tenancy'),
)

class Meta:
Expand Down
Loading