DynamicModelChoiceField traceback for plugin models #4183

Closed
opened 2025-12-29 18:33:41 +01:00 by adam · 1 comment
Owner

Originally created by @zn0k on GitHub (Oct 12, 2020).

Environment

  • Python version: 3.7.9 (via netbox-docker)
  • NetBox version: v2.9.3 (via netbox-docker)

Steps to Reproduce

  1. Create a plugin with a data model
  2. Create a form that uses DynamicModelChoiceField on the model

Expected Behavior

Form gets rendered as expected

Observed Behavior

Traceback:



Request Method: GET
Request URL: http://localhost:8000/plugins/netbox_interface_profiles/profilemappings/add/?profile_id=2

Django Version: 3.1
Python Version: 3.7.9
Installed Applications:
['django.contrib.admin',
 'django.contrib.auth',
 'django.contrib.contenttypes',
 'django.contrib.sessions',
 'django.contrib.messages',
 'django.contrib.staticfiles',
 'django.contrib.humanize',
 'cacheops',
 'corsheaders',
 'debug_toolbar',
 'django_filters',
 'django_tables2',
 'django_prometheus',
 'mptt',
 'rest_framework',
 'taggit',
 'timezone_field',
 'circuits',
 'dcim',
 'ipam',
 'extras',
 'secrets',
 'tenancy',
 'users',
 'utilities',
 'virtualization',
 'django_rq',
 'drf_yasg',
 'netbox_interface_profiles.InterfaceProfiles']
Installed Middleware:
['debug_toolbar.middleware.DebugToolbarMiddleware',
 'django_prometheus.middleware.PrometheusBeforeMiddleware',
 'corsheaders.middleware.CorsMiddleware',
 'django.contrib.sessions.middleware.SessionMiddleware',
 'django.middleware.common.CommonMiddleware',
 'django.middleware.csrf.CsrfViewMiddleware',
 'django.contrib.auth.middleware.AuthenticationMiddleware',
 'django.contrib.messages.middleware.MessageMiddleware',
 'django.middleware.clickjacking.XFrameOptionsMiddleware',
 'django.middleware.security.SecurityMiddleware',
 'utilities.middleware.ExceptionHandlingMiddleware',
 'utilities.middleware.RemoteUserMiddleware',
 'utilities.middleware.LoginRequiredMiddleware',
 'utilities.middleware.APIVersionMiddleware',
 'extras.middleware.ObjectChangeMiddleware',
 'django_prometheus.middleware.PrometheusAfterMiddleware']


Template error:
In template /opt/netbox/netbox/templates/base.html, error at line 0
   'netbox_interface_profiles-api' is not a registered namespace
   1 : {% load static %}
   2 : {% load helpers %}
   3 : <!DOCTYPE html>
   4 : <html lang="en">
   5 : <head>
   6 :     <title>{% block title %}Home{% endblock %} - NetBox</title>
   7 :     <link rel="stylesheet"
   8 :           href="{% static 'bootstrap-3.4.1-dist/css/bootstrap.min.css' %}"
   9 :           onerror="window.location='{% url 'media_failure' %}?filename=bootstrap-3.4.1-dist/css/bootstrap.min.css'">
   10 :     <link rel="stylesheet"


Traceback (most recent call last):
  File "/usr/local/lib/python3.7/site-packages/django/urls/base.py", line 72, in reverse
    extra, resolver = resolver.namespace_dict[ns]

During handling of the above exception ('netbox_interface_profiles-api'), another exception occurred:
  File "/usr/local/lib/python3.7/site-packages/django/core/handlers/exception.py", line 47, in inner
    response = get_response(request)
  File "/usr/local/lib/python3.7/site-packages/django/core/handlers/base.py", line 179, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/usr/local/lib/python3.7/site-packages/django/views/generic/base.py", line 73, in view
    return self.dispatch(request, *args, **kwargs)
  File "/usr/local/lib/python3.7/site-packages/django/contrib/auth/mixins.py", line 85, in dispatch
    return super().dispatch(request, *args, **kwargs)
  File "/opt/netbox/netbox/utilities/views.py", line 392, in dispatch
    return super().dispatch(request, *args, **kwargs)
  File "/opt/netbox/netbox/utilities/views.py", line 123, in dispatch
    return super().dispatch(request, *args, **kwargs)
  File "/usr/local/lib/python3.7/site-packages/django/views/generic/base.py", line 101, in dispatch
    return handler(request, *args, **kwargs)
  File "/opt/netbox/netbox/utilities/views.py", line 405, in get
    'return_url': self.get_return_url(request, obj),
  File "/usr/local/lib/python3.7/site-packages/django/shortcuts.py", line 19, in render
    content = loader.render_to_string(template_name, context, request, using=using)
  File "/usr/local/lib/python3.7/site-packages/django/template/loader.py", line 62, in render_to_string
    return template.render(context, request)
  File "/usr/local/lib/python3.7/site-packages/django/template/backends/django.py", line 61, in render
    return self.template.render(context)
  File "/usr/local/lib/python3.7/site-packages/django/template/base.py", line 170, in render
    return self._render(context)
  File "/usr/local/lib/python3.7/site-packages/django/test/utils.py", line 96, in instrumented_test_render
    return self.nodelist.render(context)
  File "/usr/local/lib/python3.7/site-packages/django/template/base.py", line 938, in render
    bit = node.render_annotated(context)
  File "/usr/local/lib/python3.7/site-packages/django/template/base.py", line 905, in render_annotated
    return self.render(context)
  File "/usr/local/lib/python3.7/site-packages/django/template/loader_tags.py", line 150, in render
    return compiled_parent._render(context)
  File "/usr/local/lib/python3.7/site-packages/django/test/utils.py", line 96, in instrumented_test_render
    return self.nodelist.render(context)
  File "/usr/local/lib/python3.7/site-packages/django/template/base.py", line 938, in render
    bit = node.render_annotated(context)
  File "/usr/local/lib/python3.7/site-packages/django/template/base.py", line 905, in render_annotated
    return self.render(context)
  File "/usr/local/lib/python3.7/site-packages/django/template/loader_tags.py", line 62, in render
    result = block.nodelist.render(context)
  File "/usr/local/lib/python3.7/site-packages/django/template/base.py", line 938, in render
    bit = node.render_annotated(context)
  File "/usr/local/lib/python3.7/site-packages/django/template/base.py", line 905, in render_annotated
    return self.render(context)
  File "/usr/local/lib/python3.7/site-packages/django/template/defaulttags.py", line 163, in render
    values = self.sequence.resolve(context, ignore_failures=True)
  File "/usr/local/lib/python3.7/site-packages/django/template/base.py", line 671, in resolve
    obj = self.var.resolve(context)
  File "/usr/local/lib/python3.7/site-packages/django/template/base.py", line 796, in resolve
    value = self._resolve_lookup(context)
  File "/usr/local/lib/python3.7/site-packages/django/template/base.py", line 858, in _resolve_lookup
    current = current()
  File "/usr/local/lib/python3.7/site-packages/django/forms/forms.py", line 473, in hidden_fields
    return [field for field in self if field.is_hidden]
  File "/usr/local/lib/python3.7/site-packages/django/forms/forms.py", line 473, in <listcomp>
    return [field for field in self if field.is_hidden]
  File "/usr/local/lib/python3.7/site-packages/django/forms/forms.py", line 150, in __iter__
    yield self[name]
  File "/usr/local/lib/python3.7/site-packages/django/forms/forms.py", line 165, in __getitem__
    self._bound_fields_cache[name] = field.get_bound_field(self, name)
  File "/opt/netbox/netbox/utilities/forms/fields.py", line 322, in get_bound_field
    data_url = reverse('{}-api:{}-list'.format(app_label, model_name))
  File "/usr/local/lib/python3.7/site-packages/django/urls/base.py", line 83, in reverse
    raise NoReverseMatch("%s is not a registered namespace" % key)

Exception Type: NoReverseMatch at /plugins/netbox_interface_profiles/profilemappings/add/
Exception Value: 'netbox_interface_profiles-api' is not a registered namespace

This appears to be because the get_bound_field method in DynamicModelChoiceMixin doesn't check whether the app label belongs to a plugin when generating the callback URL from which to load data. Plugin APIs live in the namespace "plugins-api". I can fix the problem either by deriving my own class from the Mixin one and prefixing the data URL with "plugins-api:", but this could apparently also more generally be fixed by the below diff for fields.py that checks whether an app_label belongs to a plugin and dynamically prefixes the data URL if required:

8d7
< from django.conf import settings
323,328c322
<             # check whether the app label is from an installed plugin
<             # if so, prefix the namespace accordingly
<             prefix = ''
<             if app_label in settings.PLUGINS:
<                 prefix = 'plugins-api:'
<             data_url = reverse('{}{}-api:{}-list'.format(prefix, app_label, model_name))
---
>             data_url = reverse('{}-api:{}-list'.format(app_label, model_name))

Happy to create a pull request if that's more useful.

Thanks for your time reading this.

Originally created by @zn0k on GitHub (Oct 12, 2020). ### Environment * Python version: 3.7.9 (via netbox-docker) * NetBox version: v2.9.3 (via netbox-docker) ### Steps to Reproduce 1. Create a plugin with a data model 2. Create a form that uses DynamicModelChoiceField on the model ### Expected Behavior Form gets rendered as expected ### Observed Behavior Traceback: ```Environment: Request Method: GET Request URL: http://localhost:8000/plugins/netbox_interface_profiles/profilemappings/add/?profile_id=2 Django Version: 3.1 Python Version: 3.7.9 Installed Applications: ['django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'django.contrib.humanize', 'cacheops', 'corsheaders', 'debug_toolbar', 'django_filters', 'django_tables2', 'django_prometheus', 'mptt', 'rest_framework', 'taggit', 'timezone_field', 'circuits', 'dcim', 'ipam', 'extras', 'secrets', 'tenancy', 'users', 'utilities', 'virtualization', 'django_rq', 'drf_yasg', 'netbox_interface_profiles.InterfaceProfiles'] Installed Middleware: ['debug_toolbar.middleware.DebugToolbarMiddleware', 'django_prometheus.middleware.PrometheusBeforeMiddleware', 'corsheaders.middleware.CorsMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', 'django.middleware.security.SecurityMiddleware', 'utilities.middleware.ExceptionHandlingMiddleware', 'utilities.middleware.RemoteUserMiddleware', 'utilities.middleware.LoginRequiredMiddleware', 'utilities.middleware.APIVersionMiddleware', 'extras.middleware.ObjectChangeMiddleware', 'django_prometheus.middleware.PrometheusAfterMiddleware'] Template error: In template /opt/netbox/netbox/templates/base.html, error at line 0 'netbox_interface_profiles-api' is not a registered namespace 1 : {% load static %} 2 : {% load helpers %} 3 : <!DOCTYPE html> 4 : <html lang="en"> 5 : <head> 6 : <title>{% block title %}Home{% endblock %} - NetBox</title> 7 : <link rel="stylesheet" 8 : href="{% static 'bootstrap-3.4.1-dist/css/bootstrap.min.css' %}" 9 : onerror="window.location='{% url 'media_failure' %}?filename=bootstrap-3.4.1-dist/css/bootstrap.min.css'"> 10 : <link rel="stylesheet" Traceback (most recent call last): File "/usr/local/lib/python3.7/site-packages/django/urls/base.py", line 72, in reverse extra, resolver = resolver.namespace_dict[ns] During handling of the above exception ('netbox_interface_profiles-api'), another exception occurred: File "/usr/local/lib/python3.7/site-packages/django/core/handlers/exception.py", line 47, in inner response = get_response(request) File "/usr/local/lib/python3.7/site-packages/django/core/handlers/base.py", line 179, in _get_response response = wrapped_callback(request, *callback_args, **callback_kwargs) File "/usr/local/lib/python3.7/site-packages/django/views/generic/base.py", line 73, in view return self.dispatch(request, *args, **kwargs) File "/usr/local/lib/python3.7/site-packages/django/contrib/auth/mixins.py", line 85, in dispatch return super().dispatch(request, *args, **kwargs) File "/opt/netbox/netbox/utilities/views.py", line 392, in dispatch return super().dispatch(request, *args, **kwargs) File "/opt/netbox/netbox/utilities/views.py", line 123, in dispatch return super().dispatch(request, *args, **kwargs) File "/usr/local/lib/python3.7/site-packages/django/views/generic/base.py", line 101, in dispatch return handler(request, *args, **kwargs) File "/opt/netbox/netbox/utilities/views.py", line 405, in get 'return_url': self.get_return_url(request, obj), File "/usr/local/lib/python3.7/site-packages/django/shortcuts.py", line 19, in render content = loader.render_to_string(template_name, context, request, using=using) File "/usr/local/lib/python3.7/site-packages/django/template/loader.py", line 62, in render_to_string return template.render(context, request) File "/usr/local/lib/python3.7/site-packages/django/template/backends/django.py", line 61, in render return self.template.render(context) File "/usr/local/lib/python3.7/site-packages/django/template/base.py", line 170, in render return self._render(context) File "/usr/local/lib/python3.7/site-packages/django/test/utils.py", line 96, in instrumented_test_render return self.nodelist.render(context) File "/usr/local/lib/python3.7/site-packages/django/template/base.py", line 938, in render bit = node.render_annotated(context) File "/usr/local/lib/python3.7/site-packages/django/template/base.py", line 905, in render_annotated return self.render(context) File "/usr/local/lib/python3.7/site-packages/django/template/loader_tags.py", line 150, in render return compiled_parent._render(context) File "/usr/local/lib/python3.7/site-packages/django/test/utils.py", line 96, in instrumented_test_render return self.nodelist.render(context) File "/usr/local/lib/python3.7/site-packages/django/template/base.py", line 938, in render bit = node.render_annotated(context) File "/usr/local/lib/python3.7/site-packages/django/template/base.py", line 905, in render_annotated return self.render(context) File "/usr/local/lib/python3.7/site-packages/django/template/loader_tags.py", line 62, in render result = block.nodelist.render(context) File "/usr/local/lib/python3.7/site-packages/django/template/base.py", line 938, in render bit = node.render_annotated(context) File "/usr/local/lib/python3.7/site-packages/django/template/base.py", line 905, in render_annotated return self.render(context) File "/usr/local/lib/python3.7/site-packages/django/template/defaulttags.py", line 163, in render values = self.sequence.resolve(context, ignore_failures=True) File "/usr/local/lib/python3.7/site-packages/django/template/base.py", line 671, in resolve obj = self.var.resolve(context) File "/usr/local/lib/python3.7/site-packages/django/template/base.py", line 796, in resolve value = self._resolve_lookup(context) File "/usr/local/lib/python3.7/site-packages/django/template/base.py", line 858, in _resolve_lookup current = current() File "/usr/local/lib/python3.7/site-packages/django/forms/forms.py", line 473, in hidden_fields return [field for field in self if field.is_hidden] File "/usr/local/lib/python3.7/site-packages/django/forms/forms.py", line 473, in <listcomp> return [field for field in self if field.is_hidden] File "/usr/local/lib/python3.7/site-packages/django/forms/forms.py", line 150, in __iter__ yield self[name] File "/usr/local/lib/python3.7/site-packages/django/forms/forms.py", line 165, in __getitem__ self._bound_fields_cache[name] = field.get_bound_field(self, name) File "/opt/netbox/netbox/utilities/forms/fields.py", line 322, in get_bound_field data_url = reverse('{}-api:{}-list'.format(app_label, model_name)) File "/usr/local/lib/python3.7/site-packages/django/urls/base.py", line 83, in reverse raise NoReverseMatch("%s is not a registered namespace" % key) Exception Type: NoReverseMatch at /plugins/netbox_interface_profiles/profilemappings/add/ Exception Value: 'netbox_interface_profiles-api' is not a registered namespace ``` This appears to be because the `get_bound_field` method in DynamicModelChoiceMixin doesn't check whether the app label belongs to a plugin when generating the callback URL from which to load data. Plugin APIs live in the namespace "plugins-api". I can fix the problem either by deriving my own class from the Mixin one and prefixing the data URL with "plugins-api:", but this could apparently also more generally be fixed by the below diff for [fields.py](https://github.com/netbox-community/netbox/blob/develop/netbox/utilities/forms/fields.py) that checks whether an app_label belongs to a plugin and dynamically prefixes the data URL if required: ``` 8d7 < from django.conf import settings 323,328c322 < # check whether the app label is from an installed plugin < # if so, prefix the namespace accordingly < prefix = '' < if app_label in settings.PLUGINS: < prefix = 'plugins-api:' < data_url = reverse('{}{}-api:{}-list'.format(prefix, app_label, model_name)) --- > data_url = reverse('{}-api:{}-list'.format(app_label, model_name)) ``` Happy to create a pull request if that's more useful. Thanks for your time reading this.
adam closed this issue 2025-12-29 18:33:41 +01:00
Author
Owner

@jeremystretch commented on GitHub (Oct 12, 2020):

DynamicModelChoiceField is an internal component of NetBox not suitable for use by plugins. Please see the documentation for the complete API.

If you'd like to propose specific functionality for plugins, please do so in a separate feature request.

@jeremystretch commented on GitHub (Oct 12, 2020): DynamicModelChoiceField is an internal component of NetBox not suitable for use by plugins. Please see [the documentation](https://netbox.readthedocs.io/en/stable/plugins/development/) for the complete API. If you'd like to propose specific functionality for plugins, please do so in a separate feature request.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/netbox#4183