Не удалось разрешить URL для гиперссылочного отношения с использованием имени представления "<viewname>". Проблема URL/pk поля ResourceRelatedField (django+rest+json api)

Summary

Фон

Я пытаюсь реализовать JSON:API-совместимое приложение Django общего назначения для использования в различных проектах. Мне удается сгенерировать приличный JSON:API, но я получаю только "связанные"-ссылки для отношений один-к-одному (foreignkey в модели). Я не могу получить "related"-ссылки для обратных отношений (другие модели с внешним ключом, указывающим на мой объект).

Спасибо! //S

Вопросы

  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?

Специфика

  • Я использую Django на сервере ubuntu (локальная сеть) с Django restful framework и Django restful framework json api.
  • В базе данных есть три модели: TopObject, Status и RelatedObject.
  • TopObject-объект имеет внешний ключ status, указывающий на Status
  • .
  • RelatedObject-объект имеет внешний ключ topobject, указывающий на его "родителя" TopObject-объект.
  • Все следующие url работают правильно при вводе их вручную (например, в браузере или httpie):
    • localhost:8000/topobjects/1/relatedobjects/ (дает список связанных объектов с topobject foreignkey = 1)
    • *localhost:8000/topobjects/ (выдает список, в настоящее время без обратных ссылок. Связанная ссылка для статуса [прямая связь] работает).
    • *localhost:8000/topobjects/1/ (выдает топообъект 1, в настоящее время без обратных ссылок. Обратная ссылка для status [прямая связь] работает).
    • localhost:8000/relatedobjects/
    • .
    • localhost:8000/relatedobjects/1/
    • localhost:8000/statuses
    • localhost:8000/statuses/1/
  • Имя представления topobject-relatedobject-list работает из представлений при проверке с помощью print(reverse...)
  • Когда related_link_view_name='topobject-relatedobject-list' установлен в ResourceRelatedField, localhost:8000/relatedobjects/ и localhost:8000/relatedobjects/1/ выдает ошибку Django.
  • Я не понял, как изменить обратные списки отношений в маршрутизаторах/наборах представлений. Если это не имеет особого отношения к рассматриваемой проблеме, я вынесу это в отдельный вопрос на потом.
  • .

Вспомогательная информация

Целевой вывод JSON

            "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

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. Является ли ResourceRelatedField правильным способом реализации обратной связи (например, "related")?
    Да. Это стандарт в Django REST json api.
  2. .
  3. Как правильно использовать ResourceRelatedField с related_link_view_name и _url_kwarg?
    Поскольку фреймворк использует имя модели queryset (т.е. 'RelatedObject') в качестве основы для kwarg, называемого related_fields, url.conf должен принимать следующее:
path('topobjects/<int:pk>/<related_field>/', views.TopObjectView.as_view(), name='topobject-relatedobject-list'),

Следующий url.conf не будет работать в этом примере:

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

Прямой url будет работать во втором примере, но не обратный, на который опирается ResourceRelatedField.

Вернуться на верх