diff --git a/README.md b/README.md index 213fad1..c4090c3 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ at [https://netboxlabs.com/blog/introducing-diode-streamlining-data-ingestion-in | >= 4.2.3 | 1.1.0 | | >= 4.2.3 | 1.2.0 | | >= 4.4.0 | 1.4.0 | +| >= 4.4.0 | 1.4.1 | ## Installation diff --git a/docker/Dockerfile-diode-netbox-plugin b/docker/Dockerfile-diode-netbox-plugin index c488e63..368e935 100644 --- a/docker/Dockerfile-diode-netbox-plugin +++ b/docker/Dockerfile-diode-netbox-plugin @@ -1,4 +1,4 @@ -FROM netboxcommunity/netbox:v4.3.3-3.3.0 +FROM netboxcommunity/netbox:v4.4.2-3.4.1 COPY ./netbox/configuration/ /etc/netbox/config/ RUN chmod 755 /etc/netbox/config/* && \ diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml index 7cea9c6..af8e518 100644 --- a/docker/docker-compose.yaml +++ b/docker/docker-compose.yaml @@ -1,7 +1,7 @@ name: diode-netbox-plugin services: netbox: &netbox - image: netboxcommunity/netbox:v4.3.3-3.3.0-diode-netbox-plugin + image: netboxcommunity/netbox:v4.4.2-3.4.1-diode-netbox-plugin build: context: . dockerfile: Dockerfile-diode-netbox-plugin diff --git a/docker/requirements-diode-netbox-plugin.txt b/docker/requirements-diode-netbox-plugin.txt index 3340d33..47ca451 100644 --- a/docker/requirements-diode-netbox-plugin.txt +++ b/docker/requirements-diode-netbox-plugin.txt @@ -4,4 +4,4 @@ coverage==7.6.0 grpcio==1.62.1 protobuf==5.29.5 pytest==8.0.2 -netboxlabs-netbox-branching==0.6.0 \ No newline at end of file +netboxlabs-netbox-branching==0.7.1 \ No newline at end of file diff --git a/netbox_diode_plugin/api/matcher.py b/netbox_diode_plugin/api/matcher.py index 2e9cc97..bb0d0f9 100644 --- a/netbox_diode_plugin/api/matcher.py +++ b/netbox_diode_plugin/api/matcher.py @@ -449,7 +449,11 @@ def _prepare_data(self, data: dict) -> dict: field = self.model_class._meta.get_field(field_name) # special handling for object type -> content type id if field.is_relation and hasattr(field, "related_model") and field.related_model == ContentType: - prepared[field_name] = content_type_id(value) + # Handle ManyToMany fields (list of object types) and ForeignKey fields (single object type) + if isinstance(value, list): + prepared[field_name] = [content_type_id(v) for v in value] + else: + prepared[field_name] = content_type_id(value) else: prepared[field_name] = value diff --git a/netbox_diode_plugin/migrations/0006_clientcredentials_alter_setting_diode_target.py b/netbox_diode_plugin/migrations/0006_clientcredentials_alter_setting_diode_target.py new file mode 100644 index 0000000..80f90ed --- /dev/null +++ b/netbox_diode_plugin/migrations/0006_clientcredentials_alter_setting_diode_target.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python +# Copyright 2025 NetBox Labs, Inc. +"""Diode NetBox Plugin - Database migrations.""" + +from django.db import migrations, models + +import netbox_diode_plugin.models + + +class Migration(migrations.Migration): + """Create ClientCredentials model and alter Setting.diode_target field.""" + + dependencies = [ + ("netbox_diode_plugin", "0001_squashed_0005"), + ] + + operations = [ + migrations.CreateModel( + name="ClientCredentials", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, primary_key=True, serialize=False + ), + ), + ], + options={ + "permissions": ( + ("view_clientcredentials", "Can view Client Credentials"), + ( + "add_clientcredentials", + "Can perform actions on Client Credentials", + ), + ), + "managed": False, + "default_permissions": (), + }, + ), + migrations.AlterField( + model_name="setting", + name="diode_target", + field=models.CharField( + max_length=255, + validators=[netbox_diode_plugin.models.diode_target_validator], + ), + ), + ] diff --git a/netbox_diode_plugin/models.py b/netbox_diode_plugin/models.py index bfe8fc9..9a473f8 100644 --- a/netbox_diode_plugin/models.py +++ b/netbox_diode_plugin/models.py @@ -41,9 +41,19 @@ def get_absolute_url(self): return reverse("plugins:netbox_diode_plugin:settings") +class UnmanagedModelManager(models.Manager): + """Manager for unmanaged models that prevents database queries.""" + + def get_queryset(self): + """Return an empty queryset without hitting the database.""" + return super().get_queryset().none() + + class ClientCredentials(models.Model): """Dummy model to allow for permissions, saved filters, etc..""" + objects = UnmanagedModelManager() + class Meta: """Meta class."""