diff --git a/nextbox_ui_plugin/__init__.py b/nextbox_ui_plugin/__init__.py index 9ff53c6..c9d72cf 100644 --- a/nextbox_ui_plugin/__init__.py +++ b/nextbox_ui_plugin/__init__.py @@ -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 = 'iDebugAll@gmail.com' base_url = 'nextbox-ui' diff --git a/nextbox_ui_plugin/forms.py b/nextbox_ui_plugin/forms.py index 4fb3d1b..7700ba1 100644 --- a/nextbox_ui_plugin/forms.py +++ b/nextbox_ui_plugin/forms.py @@ -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 @@ -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) diff --git a/nextbox_ui_plugin/migrations/0001_initial.py b/nextbox_ui_plugin/migrations/0001_initial.py index dac3417..a7d6003 100644 --- a/nextbox_ui_plugin/migrations/0001_initial.py +++ b/nextbox_ui_plugin/migrations/0001_initial.py @@ -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): @@ -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), ] diff --git a/nextbox_ui_plugin/models.py b/nextbox_ui_plugin/models.py index 07fbee9..0ed2975 100644 --- a/nextbox_ui_plugin/models.py +++ b/nextbox_ui_plugin/models.py @@ -1,6 +1,15 @@ 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): @@ -8,7 +17,7 @@ class SavedTopology(models.Model): 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, diff --git a/nextbox_ui_plugin/navigation.py b/nextbox_ui_plugin/navigation.py index 183d124..e62a6e6 100644 --- a/nextbox_ui_plugin/navigation.py +++ b/nextbox_ui_plugin/navigation.py @@ -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 = ( diff --git a/nextbox_ui_plugin/views.py b/nextbox_ui_plugin/views.py index ee3a04e..9a73ca6 100644 --- a/nextbox_ui_plugin/views.py +++ b/nextbox_ui_plugin/views.py @@ -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(): @@ -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' @@ -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) @@ -281,9 +289,9 @@ 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 @@ -291,8 +299,8 @@ def get_vlan_topology(nb_devices_qs, vlans): '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: @@ -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 [] @@ -382,9 +394,9 @@ 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 @@ -392,8 +404,8 @@ def get_topology(nb_devices_qs): '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: diff --git a/setup.py b/setup.py index 5f7b84c..09f5b90 100644 --- a/setup.py +++ b/setup.py @@ -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',