Django REST Framework JSON API показывает пустой объект связи отношений при использовании relations.HyperRelatedField из rest_framework_json_api

Я создаю REST API для отчета о пространственной связке, я хочу, чтобы связка была дочерней для каждого отчета.

Мои модели:

from django.db import models
from django.utils import timezone

class Report(models.Model):
  class Meta:
    managed = False
    db_table = 'report'
    ordering = ['-id']

  predict_start = models.DateTimeField(null=True)
  predict_end = models.DateTimeField(null=True)
  process_duration = models.IntegerField(default=0, null=True)
  create_conjunction_date = models.DateTimeField(null=True)
  ephe_filename = models.CharField(max_length=100, null=True)

class Conjunction(models.Model):
  class Meta:
    managed = False
    db_table = 'conjunction'
    ordering = ['-conjunction_id']

  conjunction_id = models.IntegerField(primary_key=True)
  tca = models.DateTimeField(max_length=3, null=True)
  missdt = models.FloatField(null=True)
  probability = models.FloatField(null=True)
  prob_method = models.CharField(max_length=45, null=True)
  norad = models.OneToOneField(SatelliteCategory, to_field='norad_cat_id', db_column='norad', null=True, on_delete=models.DO_NOTHING)
  doy = models.FloatField(null=True)
  ephe_id = models.IntegerField(null=True)
  pri_obj = models.IntegerField(null=True)
  sec_obj = models.IntegerField(null=True)
  report = models.ForeignKey(Report, related_name='conjunctions', null=True, on_delete=models.DO_NOTHING)
  probability_foster = models.FloatField(null=True)
  probability_patera = models.FloatField(null=True)
  probability_alfano = models.FloatField(null=True)
  probability_chan = models.FloatField(null=True)

Мои сериализаторы:

class ConjunctionSerializer(serializers.ModelSerializer):
  class Meta:
    model = Conjunction
    fields = '__all__'

class ReportSerializer(serializers.ModelSerializer):
  conjunctions = relations.ResourceRelatedField(many=True, read_only=True)

  class Meta:
    model = Report
    fields = '__all__'

Мои взгляды:

from rest_framework import permissions
from rest_framework_json_api.views import viewsets
from .serializers import ReportSerializer, ConjunctionSerializer
from .models import Report, Conjunction

class ReportViewSet(viewsets.ModelViewSet):
  queryset = Report.objects.all()
  serializer_class = ReportSerializer
  permission_classes = [permissions.AllowAny]

class ConjunctionViewSet(viewsets.ModelViewSet):
  queryset = Conjunction.objects.all()
  serializer_class = ConjunctionSerializer
  permission_classes = [permissions.AllowAny]

Мой urls.py

from django.contrib import admin
from django.urls import include, path
from rest_framework import routers
from api.views import UserViewSet, GroupViewSet
from shared.views import ReportViewSet, SatelliteCategoryViewSet, ConjunctionViewSet, ReportSentViewSet

router = routers.DefaultRouter()
router.register(r'users', UserViewSet)
router.register(r'groups', GroupViewSet)
router.register(r'reports', ReportViewSet)
router.register(r'report_sent', ReportSentViewSet)
router.register(r'satellite_category', SatelliteCategoryViewSet)
router.register(r'conjunctions', ConjunctionViewSet)

# Wire up our API using automatic URL routing.
# Additionally, we include login URLs for the browsable API.
urlpatterns = [
  path('admin/', admin.site.urls),
  path('api/', include(router.urls)),
  path('api-auth/', include('rest_framework.urls', namespace='rest_framework'))
]

Когда я использую ResourceRelatedField вывод JSON будет выглядеть так:

{
    "links": {
        "first": "http://127.0.0.1:8000/api/reports/?page%5Bnumber%5D=1",
        "last": "http://127.0.0.1:8000/api/reports/?page%5Bnumber%5D=84",
        "next": "http://127.0.0.1:8000/api/reports/?page%5Bnumber%5D=2",
        "prev": null
    },
    "data": [
        {
            "type": "Report",
            "id": "838",
            "attributes": {
                "predict_start": "2023-01-26T12:00:00Z",
                "predict_end": "2023-02-02T12:00:00Z",
                "process_duration": 752,
                "create_conjunction_date": "2023-01-26T14:52:45Z",
                "ephe_filename": "Filename.txt"
            },
            "relationships": {
                "conjunctions": {
                    "meta": {
                        "count": 107
                    },
                    "data": [
                        {
                            "type": "Conjunction",
                            "id": "78728"
                        },
                        # ... more data ...
                        {
                            "type": "Conjunction",
                            "id": "78622"
                        }
                    ]
                }
            }
        }
    ],
    "meta": {
        "pagination": {
            "page": 1,
            "pages": 84,
            "count": 838
        }
    }
}

Но когда я использую HyperlinkedRelatedField, он выдает пустой объект conjunctions:


{
    "links": {
        "first": "http://127.0.0.1:8000/api/reports/?page%5Bnumber%5D=1",
        "last": "http://127.0.0.1:8000/api/reports/?page%5Bnumber%5D=84",
        "next": "http://127.0.0.1:8000/api/reports/?page%5Bnumber%5D=2",
        "prev": null
    },
    "data": [
        {
            "type": "Report",
            "id": "838",
            "attributes": {
                "predict_start": "2023-01-26T12:00:00Z",
                "predict_end": "2023-02-02T12:00:00Z",
                "process_duration": 752,
                "create_conjunction_date": "2023-01-26T14:52:45Z",
                "ephe_filename": "Filename.txt"
            },
            "relationships": {
                "conjunctions": {}
            }
        },
    ],
    "meta": {
        "pagination": {
            "page": 1,
            "pages": 84,
            "count": 838
        }
    }
}

Вот что я ожидаю:


{
    "links": {
        "first": "http://127.0.0.1:8000/api/reports/?page%5Bnumber%5D=1",
        "last": "http://127.0.0.1:8000/api/reports/?page%5Bnumber%5D=84",
        "next": "http://127.0.0.1:8000/api/reports/?page%5Bnumber%5D=2",
        "prev": null
    },
    "data": [
        {
            "type": "Report",
            "id": "838",
            "attributes": {
                "predict_start": "2023-01-26T12:00:00Z",
                "predict_end": "2023-02-02T12:00:00Z",
                "process_duration": 752,
                "create_conjunction_date": "2023-01-26T14:52:45Z",
                "ephe_filename": "Filename.txt"
            },
            "relationships": {
                "conjunctions": {
                    # Any links or something.
                }
            }
        },
    ],
    "meta": {
        "pagination": {
            "page": 1,
            "pages": 84,
            "count": 838
        }
    }
}

Я могу решить проблему сейчас.

Отныне я буду использовать drf-nested-routers для разрешения конъюнкций, связанных с ссылками на отчеты.

Вы можете установить по

pip install drf-nested-routers

Добавлять эту библиотеку в файл settings.py вашего проекта Django не нужно, так как она не содержит ни app, ни signal, ни model.

В urls.py добавьте следующие маршрутизаторы

from django.urls import path, include
from rest_framework_nested import routers
from api.views import UserViewSet, GroupViewSet
from shared.views import ReportViewSet, SatelliteCategoryViewSet, ConjunctionViewSet, ReportSentViewSet, SatelliteViewSet, DiscosObjectViewSet, CoordinateViewSet, ReportRelationshipView

router = routers.DefaultRouter()
router.register(r'users', UserViewSet)
router.register(r'groups', GroupViewSet)
router.register(r'reports', ReportViewSet)
router.register(r'report_sent', ReportSentViewSet)
router.register(r'satellite_categories', SatelliteCategoryViewSet)
router.register(r'conjunctions', ConjunctionViewSet)
router.register(r'satellites', SatelliteViewSet)
router.register(r'discos_objects', DiscosObjectViewSet)
router.register(r'coordinates', CoordinateViewSet)

reports_router = routers.NestedSimpleRouter(router, 'reports', lookup='report')
reports_router.register(r'conjunctions', ConjunctionViewSet, basename='report-conjunctions')

urlpatterns = [
  path('admin/', admin.site.urls),
  path('api-auth/', include('rest_framework.urls', namespace='rest_framework'))
] + [
  path('api/', include(router.urls)) for router in [router, reports_router]
]

В файле serializers.py добавьте HyperlinkedRelatedField в ReportSerializer класс.

class ReportSerializer(serializers.HyperlinkedModelSerializer):

  conjunctions = relations.HyperlinkedRelatedField(
    many=True, read_only=True, 
    related_link_view_name='report-conjunctions-list',
    related_link_url_kwarg='report_pk'
  )

  class Meta:
    model = Report
    fields = '__all__'

В views.py, из класса ConjunctionViewSet, переопределите метод get_queryset для фильтрации любых конъюнкций, связанных с отчетом.

class ConjunctionViewSet(viewsets.ModelViewSet):
  queryset = Conjunction.objects.all()
  serializer_class = ConjunctionSerializer
  permission_classes = [permissions.AllowAny]

  # Add code below
  # | | | | | | |
  # v v v v v v v

  def get_queryset(self):
    queryset = super().get_queryset()
    report_pk = self.kwargs.get('report_pk')
    if report_pk is not None:
      queryset = queryset.filter(report__pk=report_pk)

    return queryset

Данный результат вывода JSON

{
    "data": {
        "type": "Report",
        "id": "838",
        "attributes": {
            "predict_start": "2023-01-26T12:00:00Z",
            "predict_end": "2023-02-02T12:00:00Z",
            "process_duration": 752,
            "create_conjunction_date": "2023-01-26T14:52:45Z",
            "ephe_filename": "Filename.txt"
        },
        "relationships": {
            "conjunctions": {
                "links": {
                    "related": "http://127.0.0.1:8000/api/reports/838/conjunctions/"
                }
            }
        },
        "links": {
            "self": "http://127.0.0.1:8000/api/reports/838/"
        }
    }
}
Вернуться на верх