Не удалось разрешить URL для гиперссылочного отношения с использованием имени представления "<viewname>". Проблема URL/pk поля ResourceRelatedField (django+rest+json api)
Summary
Фон
Я пытаюсь реализовать JSON:API-совместимое приложение Django общего назначения для использования в различных проектах. Мне удается сгенерировать приличный JSON:API, но я получаю только "связанные"-ссылки для отношений один-к-одному (foreignkey в модели). Я не могу получить "related"-ссылки для обратных отношений (другие модели с внешним ключом, указывающим на мой объект).
Спасибо! //S
Вопросы
- Is ResourceRelatedField the correct way to implement reverse relationship links (e.g. "related")?
- 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',
}
- Является ли ResourceRelatedField правильным способом реализации обратной связи (например, "related")?
Да. Это стандарт в Django REST json api. .
- Как правильно использовать 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.