Skip to content

Commit

Permalink
NetBox 4.0 Compatibility Fix (#144)
Browse files Browse the repository at this point in the history
  • Loading branch information
iDebugAll authored Jun 8, 2024
1 parent 2c8afab commit c8369fa
Show file tree
Hide file tree
Showing 7 changed files with 102 additions and 21 deletions.
11 changes: 9 additions & 2 deletions nextbox_ui_plugin/__init__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
from extras.plugins import PluginConfig
from packaging import version
from django.conf import settings
NETBOX_CURRENT_VERSION = version.parse(settings.VERSION)

if NETBOX_CURRENT_VERSION >= version.parse("4.0.0"):
from netbox.plugins import PluginConfig
else:
from extras.plugins import PluginConfig

class NextBoxUIConfig(PluginConfig):
name = 'nextbox_ui_plugin'
verbose_name = 'NextBox UI'
description = 'A topology visualization plugin for Netbox powered by NextUI Toolkit.'
version = '0.14.0'
version = '0.15.0'
author = 'Igor Korotchenkov'
author_email = '[email protected]'
base_url = 'nextbox-ui'
Expand Down
13 changes: 8 additions & 5 deletions nextbox_ui_plugin/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,24 @@
if NETBOX_CURRENT_VERSION >= version.parse("3.0") :
from django.utils.translation import gettext as _

if NETBOX_CURRENT_VERSION < version.parse("3.5"):
if NETBOX_CURRENT_VERSION >= version.parse("4.0"):
from utilities.forms.fields import (
DynamicModelMultipleChoiceField,
DynamicModelChoiceField
)
elif NETBOX_CURRENT_VERSION < version.parse("3.5"):
from utilities.forms import (
BootstrapMixin,
DynamicModelMultipleChoiceField,
DynamicModelChoiceField
)
else:
from utilities.forms import BootstrapMixin
from utilities.forms.fields import (
DynamicModelMultipleChoiceField,
DynamicModelChoiceField
)


class TopologyFilterForm(BootstrapMixin, forms.Form):
class TopologyFilterForm(forms.Form):

model = Device

Expand Down Expand Up @@ -71,7 +74,7 @@ class TopologyFilterForm(BootstrapMixin, forms.Form):
region_id.label = _('Regions')


class LoadSavedTopologyFilterForm(BootstrapMixin, forms.Form):
class LoadSavedTopologyFilterForm(forms.Form):

def __init__(self, *args, **kwargs):
user = kwargs.pop('user', None)
Expand Down
45 changes: 44 additions & 1 deletion nextbox_ui_plugin/migrations/0001_initial.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,49 @@

from django.db import migrations, models
import django.db.models.deletion
import netbox
from django.db import connection
from django.conf import settings
from packaging import version

NETBOX_CURRENT_VERSION = version.parse(settings.VERSION)

def get_user_model():
if NETBOX_CURRENT_VERSION >= version.parse("4.0.0"):
return 'users.User'
else:
return 'users.NetBoxUser'

def update_foreign_key(apps, schema_editor):
if NETBOX_CURRENT_VERSION < version.parse("4.0.0"):
return
UserModel = get_user_model()
table_name = 'nextbox_ui_plugin_savedtopology'

with connection.cursor() as cursor:
# Check the existing ForeignKey reference
cursor.execute("""
SELECT conname AS constraint_name, conrelid::regclass AS table_name, a.attname AS column_name,
confrelid::regclass AS referenced_table_name, af.attname AS referenced_column_name
FROM pg_constraint AS c
JOIN pg_attribute AS a ON a.attnum = ANY(c.conkey)
JOIN pg_class AS cl ON cl.oid = c.conrelid
JOIN pg_namespace AS ns ON ns.oid = cl.relnamespace
JOIN pg_attribute AS af ON af.attnum = ANY(c.confkey)
WHERE ns.nspname = 'public' AND cl.relname = %s AND a.attname = 'created_by_id' AND c.contype = 'f';
""", [table_name])
row = cursor.fetchone()

if row and row[3] != UserModel.split('.')[1].lower():
# Drop the existing ForeignKey constraint
cursor.execute(f"ALTER TABLE {table_name} DROP CONSTRAINT {row[0]}")

# Add the new ForeignKey constraint
cursor.execute(f"""
ALTER TABLE {table_name}
ADD CONSTRAINT {row[0]}
FOREIGN KEY (created_by_id) REFERENCES {UserModel.split('.')[0]}({UserModel.split('.')[1]});
""")

class Migration(migrations.Migration):

Expand All @@ -21,7 +63,8 @@ class Migration(migrations.Migration):
('topology', models.JSONField()),
('layout_context', models.JSONField(blank=True, null=True)),
('timestamp', models.DateTimeField()),
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='users.netboxuser')),
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=get_user_model())),
],
),
migrations.RunPython(update_foreign_key),
]
11 changes: 10 additions & 1 deletion nextbox_ui_plugin/models.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,23 @@
from django.db import models
from utilities.querysets import RestrictedQuerySet
from django.conf import settings
from packaging import version

NETBOX_CURRENT_VERSION = version.parse(settings.VERSION)

def get_user_model():
if NETBOX_CURRENT_VERSION >= version.parse("4.0.0"):
return 'users.User'
else:
return 'users.NetBoxUser'

class SavedTopology(models.Model):

name = models.CharField(max_length=100, blank=True)
topology = models.JSONField()
layout_context = models.JSONField(null=True, blank=True)
created_by = models.ForeignKey(
to="users.NetBoxUser",
to=get_user_model(),
on_delete=models.CASCADE,
blank=False,
null=False,
Expand Down
9 changes: 8 additions & 1 deletion nextbox_ui_plugin/navigation.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
from extras.plugins import PluginMenuItem
from packaging import version
from django.conf import settings
NETBOX_CURRENT_VERSION = version.parse(settings.VERSION)

if NETBOX_CURRENT_VERSION >= version.parse("4.0.0"):
from netbox.plugins import PluginMenuItem
else:
from extras.plugins import PluginMenuItem


menu_items = (
Expand Down
30 changes: 21 additions & 9 deletions nextbox_ui_plugin/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,10 @@ def get_icon_type(device_id):
4. Default 'undefined'
"""
nb_device = Device.objects.get(id=device_id)
if NETBOX_CURRENT_VERSION >= version.parse("4.0.0"):
device_role_obj = nb_device.role
else:
device_role_obj = nb_device.device_role
if not nb_device:
return 'unknown'
for tag in nb_device.tags.names():
Expand All @@ -194,7 +198,7 @@ def get_icon_type(device_id):
if model_base in str(nb_device.device_type.model):
return icon_type
for role_slug, icon_type in ICON_ROLE_MAP.items():
if str(nb_device.device_role.slug) == role_slug:
if str(device_role_obj.slug) == role_slug:
return icon_type
return 'unknown'

Expand Down Expand Up @@ -268,6 +272,10 @@ def get_vlan_topology(nb_devices_qs, vlans):
for device in devices:
device_is_passive = False
device_url = device.get_absolute_url()
if NETBOX_CURRENT_VERSION >= version.parse("4.0.0"):
device_role_obj = device.role
else:
device_role_obj = device.device_role
primary_ip = ''
if device.primary_ip:
primary_ip = str(device.primary_ip.address)
Expand All @@ -281,18 +289,18 @@ def get_vlan_topology(nb_devices_qs, vlans):
'primaryIP': primary_ip,
'serial_number': device.serial,
'model': device.device_type.model,
'deviceRole': device.device_role.slug,
'deviceRole': device_role_obj.slug,
'layerSortPreference': get_node_layer_sort_preference(
device.device_role.slug
device_role_obj.slug
),
'icon': get_icon_type(
device.id
),
'isPassive': device_is_passive,
'tags': tags,
})
is_visible = not (device.device_role.slug in UNDISPLAYED_DEVICE_ROLE_SLUGS)
device_roles.add((device.device_role.slug, device.device_role.name, is_visible))
is_visible = not (device_role_obj.slug in UNDISPLAYED_DEVICE_ROLE_SLUGS)
device_roles.add((device_role_obj.slug, device_role_obj.name, is_visible))

mapped_links = []
for interface in filtred_interfaces:
Expand Down Expand Up @@ -333,6 +341,10 @@ def get_topology(nb_devices_qs):
device_is_passive = False
device_url = nb_device.get_absolute_url()
primary_ip = ''
if NETBOX_CURRENT_VERSION >= version.parse("4.0.0"):
device_role_obj = nb_device.role
else:
device_role_obj = nb_device.device_role
if nb_device.primary_ip:
primary_ip = str(nb_device.primary_ip.address)
tags = [str(tag) for tag in nb_device.tags.names()] or []
Expand Down Expand Up @@ -382,18 +394,18 @@ def get_topology(nb_devices_qs):
'primaryIP': primary_ip,
'serial_number': nb_device.serial,
'model': nb_device.device_type.model,
'deviceRole': nb_device.device_role.slug,
'deviceRole': device_role_obj.slug,
'layerSortPreference': get_node_layer_sort_preference(
nb_device.device_role.slug
device_role_obj.slug
),
'icon': get_icon_type(
nb_device.id
),
'isPassive': device_is_passive,
'tags': tags,
})
is_visible = not (nb_device.device_role.slug in UNDISPLAYED_DEVICE_ROLE_SLUGS)
device_roles.add((nb_device.device_role.slug, nb_device.device_role.name, is_visible))
is_visible = not (device_role_obj.slug in UNDISPLAYED_DEVICE_ROLE_SLUGS)
device_roles.add((device_role_obj.slug, device_role_obj.name, is_visible))
if not links_from_device:
continue
for link in links_from_device:
Expand Down
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@

setup(
name='nextbox_ui_plugin',
version='0.14.0',
version='0.15.0',
url='https://github.com/iDebugAll/nextbox-ui-plugin',
download_url='https://github.com/iDebugAll/nextbox-ui-plugin/archive/v0.14.0.tar.gz',
download_url='https://github.com/iDebugAll/nextbox-ui-plugin/archive/v0.15.0.tar.gz',
description='A topology visualization plugin for Netbox powered by NextUI Toolkit.',
long_description=long_description,
long_description_content_type='text/markdown',
Expand Down

0 comments on commit c8369fa

Please sign in to comment.