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

NoReverseMatch exceptions for plugins without REST API #17722

Open
alehaa opened this issue Oct 10, 2024 · 3 comments
Open

NoReverseMatch exceptions for plugins without REST API #17722

alehaa opened this issue Oct 10, 2024 · 3 comments
Labels
netbox status: under review Further discussion is needed to determine this issue's scope and/or implementation type: bug A confirmed report of unexpected behavior in the application

Comments

@alehaa
Copy link
Contributor

alehaa commented Oct 10, 2024

Deployment Type

Self-hosted

Triage priority

I volunteer to perform this work (if approved)

NetBox Version

v4.1.3

Python Version

3.11

Steps to Reproduce

  1. Install a NetBox plugin.
  2. Remove API URLs by replacing api/urls.py by this code:
    from netbox.api.routers import NetBoxRouter
    
    router = NetBoxRouter()
    urlpatterns = router.urls
  3. Edit a model of the plugin via UI and save.

Expected Behavior

  • Model changes will be saved to the database.
  • REST API for this plugin is not available. According to the documentation this should be valid behavior, as it's just a "can".

Observed Behavior

An exception is raised.

Traceback (most recent call last):
  File "/opt/netbox/venv/lib/python3.12/site-packages/rest_framework/relations.py", line 396, in to_representation
    url = self.get_url(value, self.view_name, request, format)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/netbox/netbox/netbox/api/serializers/fields.py", line 37, in get_url
    return self.reverse(view_name, kwargs=kwargs, request=request, format=format)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/netbox/venv/lib/python3.12/site-packages/rest_framework/reverse.py", line 47, in reverse
    url = _reverse(viewname, args, kwargs, request, format, **extra)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/netbox/venv/lib/python3.12/site-packages/rest_framework/reverse.py", line 60, in _reverse
    url = django_reverse(viewname, args=args, kwargs=kwargs, **extra)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/netbox/venv/lib/python3.12/site-packages/django/urls/base.py", line 88, in reverse
    return resolver._reverse_with_prefix(view, prefix, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/netbox/venv/lib/python3.12/site-packages/django/urls/resolvers.py", line 851, in _reverse_with_prefix
    raise NoReverseMatch(msg)
    ^^^^^^^^^^^^^^^^^^^^^^^^^

During handling of the above exception (Reverse for 'domainlistentry-detail' not found. 'domainlistentry-detail' is not a valid view function or pattern name.), another exception occurred:
  File "/opt/netbox/venv/lib/python3.12/site-packages/django/core/handlers/exception.py", line 55, in inner
    response = get_response(request)
               ^^^^^^^^^^^^^^^^^^^^^
  File "/opt/netbox/venv/lib/python3.12/site-packages/django/core/handlers/base.py", line 197, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/netbox/venv/lib/python3.12/site-packages/django/views/generic/base.py", line 104, in view
    return self.dispatch(request, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/netbox/netbox/netbox/views/generic/object_views.py", line 182, in dispatch
    return super().dispatch(request, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/netbox/netbox/netbox/views/generic/base.py", line 26, in dispatch
    return super().dispatch(request, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/netbox/netbox/utilities/views.py", line 125, in dispatch
    return super().dispatch(request, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/netbox/netbox/utilities/views.py", line 39, in dispatch
    return super().dispatch(request, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/netbox/venv/lib/python3.12/site-packages/django/views/generic/base.py", line 143, in dispatch
    return handler(request, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/netbox/netbox/netbox/views/generic/object_views.py", line 278, in post
    obj = form.save()
          ^^^^^^^^^^^
  File "/opt/netbox/venv/lib/python3.12/site-packages/django/forms/models.py", line 552, in save
    self.instance.save()
    ^^^^^^^^^^^^^^^^^^^^
  File "/opt/netbox/venv/lib/python3.12/site-packages/django/db/models/base.py", line 822, in save
    self.save_base(
    ^
  File "/opt/netbox/venv/lib/python3.12/site-packages/django/db/models/base.py", line 924, in save_base
    post_save.send(
    ^
  File "/opt/netbox/venv/lib/python3.12/site-packages/django/dispatch/dispatcher.py", line 189, in send
    response = receiver(signal=self, sender=sender, **named)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/netbox/netbox/core/signals.py", line 102, in handle_changed_object
    enqueue_event(queue, instance, request.user, request.id, event_type)
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/netbox/netbox/extras/events.py", line 77, in enqueue_event
    'data': serialize_for_event(instance),
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/netbox/netbox/extras/events.py", line 35, in serialize_for_event
    return serializer.data
           ^^^^^^^^^^^^^^^
  File "/opt/netbox/venv/lib/python3.12/site-packages/rest_framework/serializers.py", line 571, in data
    ret = super().data
          ^^^^^^^^^^^^
  File "/opt/netbox/venv/lib/python3.12/site-packages/rest_framework/serializers.py", line 249, in data
    self._data = self.to_representation(self.instance)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/netbox/venv/lib/python3.12/site-packages/rest_framework/serializers.py", line 538, in to_representation
    ret[field.field_name] = field.to_representation(attribute)
                            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/netbox/venv/lib/python3.12/site-packages/rest_framework/relations.py", line 411, in to_representation
    raise ImproperlyConfigured(msg % self.view_name)
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Exception Type: ImproperlyConfigured at /plugins/blocklists/domain/lists/entries/add/
Exception Value: Could not resolve URL for hyperlinked relationship using view name "". You may have failed to include the related model in your API, or incorrectly configured the `lookup_field` attribute on this field.
@alehaa alehaa added status: needs triage This issue is awaiting triage by a maintainer type: bug A confirmed report of unexpected behavior in the application labels Oct 10, 2024
@alehaa
Copy link
Contributor Author

alehaa commented Oct 10, 2024

I believe I can track this down to #15156, where the serializers got automatic URL lookup. The new BaseNetBoxHyperlinkedIdentityField.get_url() method even states:

May raise a NoReverseMatch if the view_name and lookup_field attributes are not configured to correctly match the URL conf.

To solve this easily, I suggest to change the documentation. The serializers are indeed required for NetBox to work properly. However, if someone doesn't want to provide the REST API (or hasn't developed it yet), it is sufficient to simply add url = None to the serializer.

@jeremystretch jeremystretch added status: under review Further discussion is needed to determine this issue's scope and/or implementation and removed status: needs triage This issue is awaiting triage by a maintainer labels Oct 10, 2024
@jeremystretch
Copy link
Member

The serializers are indeed required for NetBox to work properly

Maybe we could tweak serialize_for_event() to fall back to using a generic JSON serializer (i.e. serialize_object()) when a custom serializer does not exist for a model.

@alehaa
Copy link
Contributor Author

alehaa commented Oct 10, 2024

This would be possible. However events may require a defined set of fields that are not provided by the default serialize_object() (e.g. display). Switching the serializer could make things worse if administrators have to worry about what type of payload is being passed.

Maybe we could catch the NoReverseMatch exception in BaseNetBoxHyperlinkedIdentityField.get_url() simply returning no or an empty url if no REST API endpoint is available for this model.

@jeremystretch jeremystretch added the netbox label Nov 1, 2024 — with Linear
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
netbox status: under review Further discussion is needed to determine this issue's scope and/or implementation type: bug A confirmed report of unexpected behavior in the application
Projects
None yet
Development

No branches or pull requests

2 participants