Could not resolve URL for hyperlinked relationship using view name "<viewname>". ResourceRelatedField URL/pk problem (django+rest+json api)

Summary

Background

I am trying to implement a JSON:API-compliant general Django app to be used in various projects. I am able to generate decent JSON:API, but I only get "related"-links for one-to-one relationships (foreignkey in the model). I cannot get "related"-links to reverse relationships (other models with a foreign key pointing to my object).

Thank you! //S

Questions

  1. Is ResourceRelatedField the correct way to implement reverse relationship links (e.g. "related")?
  2. How do you properly use ResourceRelatedField with related_link_view_name and _url_kwarg?

Specifics

  • I use Django on a ubuntu server (local network) with Django restful framework and Django restful framework json api.
  • There are three models in the database: TopObject, Status and RelatedObject.
  • TopObject-objects has a foreignkey status pointing to Status
  • RelatedObject-objects has a foreignkey topobject pointing to its 'parent' TopObject-object.
  • All of the following url's work properly when entering them manually (e.g. browser or httpie):
    • localhost:8000/topobjects/1/relatedobjects/ (gives a list of related objects with topobject foreignkey = 1)
    • *localhost:8000/topobjects/ (gives list, currently without reverse links. The related link for Status [direct relationship] works).
    • *localhost:8000/topobjects/1/ (gives topobject 1, currently without reverse links. The related link for status [direct relationship] works).
    • localhost:8000/relatedobjects/
    • localhost:8000/relatedobjects/1/
    • localhost:8000/statuses
    • localhost:8000/statuses/1/
  • The view name topobject-relatedobject-list works from views when tested with print(reverse...)
  • When related_link_view_name='topobject-relatedobject-list' is set under ResourceRelatedField, localhost:8000/relatedobjects/ and localhost:8000/relatedobjects/1/ raises a Django error.
  • I have not figured out how to amend reverse relationship lists within routers/viewsets. Unless that has specific bearing on the problem at hand, I will treat that as a separate issue for later.

Supporting information

Target JSON output

            "relationships": {
                "relatedobjects": {
                    "meta": {
                        "count": 2
                    },
                    "data": [
                        {
                            "type": "relatedobject",
                            "id": "1"
                        },
                        {
                            "type": "relatedobject",
                            "id": "2"
                        }
                    ],
//***************MISSING**************//
                    "links": {
                      "related": "http://192.168.0.152:8000/topobjects/1/relatedobjects"
                    }
//***************END MISSING**************//
                },
                "status": {
                    "data": {
                        "type": "status",
                        "id": "1"
                    },
                    "links": {
                        "related": "http://192.168.0.152:8000/statuses/1/"
                    }
                }
            },

Django error page

ImproperlyConfigured at /topobjects/2/

Could not resolve URL for hyperlinked relationship using view name "topobject-relatedobject-list".

Request Method:     GET
Request URL:    http://192.168.0.152:8000/topobjects/2/
Django Version:     3.2.9
Exception Type:     ImproperlyConfigured
Exception Value:    

Could not resolve URL for hyperlinked relationship using view name "topobject-relatedobject-list".

Exception Location:     /django/api2/env/lib/python3.8/site-packages/rest_framework_json_api/relations.py, line 98, in get_url
Python Executable:  /django/api2/env/bin/python
Python Version:     3.8.10
Python Path:    

['/django/api2',
 '/usr/lib/python38.zip',
 '/usr/lib/python3.8',
 '/usr/lib/python3.8/lib-dynload',
 '/django/api2/env/lib/python3.8/site-packages']

Server time:    Tue, 07 Dec 2021 13:46:48 +0000

models.py

from django.db import models

class TopObject(models.Model):
    created = models.DateTimeField(auto_now_add=True)
    title = models.CharField(max_length=100, blank=True, default='')
    status = models.ForeignKey('Status', default=1, on_delete=models.CASCADE)
    content = models.TextField(blank=True)

    def __str__(self):
        return str(self.pk) + ': ' + self.title + ' (' + str(self.status.name) + ')'

    class JSONAPIMeta:
        resource_name = 'topobject'

class Status(models.Model):
    name = models.CharField(max_length=100)
    description = models.TextField(blank=True)

    def __str__(self):
        return str(self.pk) + ': ' + self.name

    class JSONAPIMeta:
        resource_name = 'status'

class RelatedObject(models.Model):
    topobject = models.ForeignKey('TopObject', blank=True, related_name='relatedobjects', null=True, on_delete=models.CASCADE)
    title = models.CharField(max_length=100, blank=True)
    a = models.BooleanField(default=False)
    b = models.BooleanField(default=False)

    def __str__(self):
        return str(self.pk) + ': ' + self.title

    class JSONAPIMeta:
        resource_name = 'relatedobject'

serializers.py

from rest_framework_json_api import serializers
from .models import TopObject, Status, RelatedObject
from rest_framework_json_api.relations import ResourceRelatedField

class RelatedObjectSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = RelatedObject
        fields = '__all__'

class TopObjectSerializer(serializers.HyperlinkedModelSerializer):
    related_serializers = {'relatedobjects': 'api.serializers.RelatedObjectSerializer', 'status': 'api.serializers.StatusSerializer'}

    relatedobjects = ResourceRelatedField(
        queryset=RelatedObject.objects,
        many=True,
        related_link_view_name='topobject-relatedobject-list',
        related_link_url_kwarg='pk'
    )

    class Meta:
        model = TopObject
        fields = '__all__'
        extra_fields = ['relatedobjects', 'testrelated']

class StatusSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = Status
        fields ='__all__'

views.py

from .models import TopObject, Status, RelatedObject
from .serializers import TopObjectSerializer, StatusSerializer, RelatedObjectSerializer
from rest_framework import viewsets
from rest_framework.decorators import action
from rest_framework.response import Response

class TopObjectViewSet(viewsets.ModelViewSet):
    queryset = TopObject.objects.all()
    serializer_class = TopObjectSerializer

class StatusViewSet(viewsets.ModelViewSet):
    queryset = Status.objects.all()
    serializer_class = StatusSerializer

class RelatedObjectViewSet(viewsets.ModelViewSet):
    queryset = RelatedObject.objects.all()
    serializer_class = RelatedObjectSerializer

class TopObjectRelatedObjectViewSet(viewsets.ModelViewSet):
    serializer_class = RelatedObjectSerializer

    def get_queryset(self):
        queryset = RelatedObject.objects.all().filter(topobject=self.kwargs['pk'])
        return queryset

    def perform_create(self, serializer):
        topobject = TopObject.objects.get(pk=self.kwargs['pk'])
        serializer.save(topobject=topobject)

urls.py

from django.urls import path, include
from rest_framework.routers import DefaultRouter
from . import views
from django.conf.urls import url

router = DefaultRouter()
router.register(r'topobjects', views.TopObjectViewSet)
router.register(r'statuses', views.StatusViewSet)
router.register(r'relatedobjects', views.RelatedObjectViewSet)

topobjectsrelatedobjects = views.TopObjectRelatedObjectViewSet.as_view({'get': 'list', 'post': 'create'})

urlpatterns = [
    path('topobjects/<int:pk>/relatedobjects/', topobjectsrelatedobjects, name='topobject-relatedobject-list'),
    path('', include(router.urls)),
]

settings.py

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',
    'rest_framework_json_api',
    'api',
]
REST_FRAMEWORK = {
    'PAGE_SIZE': 2,
    'EXCEPTION_HANDLER': 'rest_framework_json_api.exceptions.exception_handler',
    'DEFAULT_PAGINATION_CLASS':
        'rest_framework_json_api.pagination.JsonApiPageNumberPagination',
    'DEFAULT_PARSER_CLASSES': (
        'rest_framework_json_api.parsers.JSONParser',
        'rest_framework.parsers.FormParser',
        'rest_framework.parsers.MultiPartParser'
    ),
    'DEFAULT_RENDERER_CLASSES': (
        'rest_framework_json_api.renderers.JSONRenderer',
        'rest_framework_json_api.renderers.BrowsableAPIRenderer'
    ),
    'DEFAULT_METADATA_CLASS': 'rest_framework_json_api.metadata.JSONAPIMetadata',
    'DEFAULT_SCHEMA_CLASS': 'rest_framework_json_api.schemas.openapi.AutoSchema',
    'DEFAULT_FILTER_BACKENDS': (
        'rest_framework_json_api.filters.QueryParameterValidationFilter',
        'rest_framework_json_api.filters.OrderingFilter',
        'rest_framework_json_api.django_filters.DjangoFilterBackend',
        'rest_framework.filters.SearchFilter',
    ),
    'SEARCH_PARAM': 'filter[search]',
    'TEST_REQUEST_RENDERER_CLASSES': (
        'rest_framework_json_api.renderers.JSONRenderer',
    ),
    'TEST_REQUEST_DEFAULT_FORMAT': 'vnd.api+json',
}
  1. Is ResourceRelatedField the correct way to implement reverse relationship links (e.g. "related")?
    Yes. This is the standard in Django REST json api.
  2. How do you properly use ResourceRelatedField with related_link_view_name and _url_kwarg?
    As the framework uses the queryset model name (i.e. 'RelatedObject') as a basis for the kwarg called related_fields, the url.conf must accept the following:
path('topobjects/<int:pk>/<related_field>/', views.TopObjectView.as_view(), name='topobject-relatedobject-list'),

The following url.conf will not work in this example:

path('topobjects/<int:pk>/relatedobjects/', views.TopObjectView.as_view(), name='topobject-relatedobject-list'),

The forward url would work in the second example, but not the reverse that ResourceRelatedField relies on.

Back to Top