pythonjsondjangodjango-rest-frameworkjson-api

Django REST Framework JSON API show an empty object of relationships link when use relations.HyperlinkedRelatedField from rest_framework_json_api


I'm create the REST API for space conjunction report, I want the conjunction to be a child of each report.

My models:

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)

My serializers:

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__'

My views:

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]

My 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'))
]

When I'm use ResourceRelatedField the JSON output will be like:

{
    "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
        }
    }
}

But when I'm use HyperlinkedRelatedField, it gives the empty object of 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
        }
    }
}

This is what I'm expect:


{
    "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.
                    "data": [{
                        "type": "Conjunction",
                        "id": "1",
                    },{
                        "type": "Conjunction",
                        "id": "2",
                    }],
                    "links": {
                        "self": "http://localhost:8000/api/reports/838/relationships/conjunctions/",
                        "related": "http://localhost:8000/api/reports/838/conjunctions/"
                    }
                }
            }
        },
    ],
    "meta": {
        "pagination": {
            "page": 1,
            "pages": 84,
            "count": 838
        }
    }
}

Solution

  • I can solve the problem now.

    From now, I will using drf-nested-routers to resolve conjunctions related to report links.

    You can install by

    pip install drf-nested-routers
    

    It is not needed to add this library in your Django project's settings.py file, as it does not contain any app, signal or model.

    In urls.py, add the following routers

    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]
    ]
    
    

    In serializers.py, add HyperlinkedRelatedField into ReportSerializer class.

    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__'
    

    In views.py, from ConjunctionViewSet class, override get_queryset method to filter any conjunctions that are related with report.

    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
    

    The given JSON output result

    {
        "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/"
            }
        }
    }