I have chosen the Django REST Framework and drf-nested-routers to build an API for Products and ProductReports. Here are the relevant classes:
# models.py
from django.db import models
class Product(models.Model):
created_at = models.DateTimeField(auto_now_add=True)
class ProductReport(models.Model):
user_name = models.CharField(max_length=256, blank=False)
created_at = models.DateTimeField(auto_now_add=True)
product = models.ForeignKey('Product', default=1)
...
# serializers.py
from rest_framework import serializers
from models import Product, ProductReport
class ProductSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Product
class ProductReportSerializer(serializers.HyperlinkedModelSerializer):
product = ProductSerializer()
class Meta:
model = ProductReport
...
# views.py
from django.shortcuts import render
from rest_framework import viewsets, mixins
from rest_framework.response import Response
from models import Product, ProductReport
from serializers import ProductSerializer, ProductReportSerializer
class ProductViewSet(mixins.RetrieveModelMixin,
mixins.ListModelMixin,
viewsets.GenericViewSet):
queryset = Product.objects.all()
serializer_class = ProductSerializer
class ProductReportViewSet(viewsets.ViewSet):
queryset = ProductReport.objects.all()
serializer_class = ProductReportSerializer
def list(self, request, product_pk=None):
queryset = self.queryset.filter(product=product_pk)
serializer = ProductReportSerializer(queryset, many=True, context={'request': request})
return Response(serializer.data)
def retrieve(self, request, pk=None, product_pk=None):
queryset = self.queryset.get(pk=pk, product=product_pk)
serializer = ProductReportSerializer(queryset)
return Response(serializer.data)
...
# urls.py
from django.conf.urls import patterns, include, url
from django.contrib import admin
from rest_framework import routers
from rest_framework_nested.routers import SimpleRouter, NestedSimpleRouter
from productreports import views
router = routers.SimpleRouter()
router.register(r'products', views.ProductViewSet)
products_router = NestedSimpleRouter(router, r'products', lookup='product')
products_router.register(r'productreports', views.ProductReportViewSet)
urlpatterns = patterns('',
url(r'^', include(router.urls)),
url(r'^', include(products_router.urls)),
url(r'^admin/', include(admin.site.urls)),
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')),
)
When I visit http://localhost:8000/products/1/productreports/
the following error is shown:
ImproperlyConfigured at /products/1/productreports/
Could not resolve URL for hyperlinked relationship using view name "productreport-detail". You may have failed to include the related model in your API, or incorrectly configured the lookup_field
attribute on this field.
There are a few things I am unsure about if they are correctly set up:
product = models.ForeignKey('Product', default=1)
many=True
in serializer = ProductReportSerializer(queryset, many=True, context={'request': request})
... each product can have many product reports
- The
ProductReportSerializer
in general
lookup='product'
in products_router = NestedSimpleRouter(router, r'products', lookup='product')
Update
I noticed that the error only occurs when there is at least one associated productreport for a product. If the productreports table contains no entry for a particular product then the API response is fine.
When I inspect the serializer
in ProductReportViewSet#list
I get:
ProductReportSerializer([
<ProductReport: ProductReport object>, <ProductReport: ProductReport object>,
<ProductReport: ProductReport object>, <ProductReport: ProductReport object>,
<ProductReport: ProductReport object>, <ProductReport: ProductReport object>,
'...(remaining elements truncated)...'], context={'request': <rest_framework.request.Request object>}, many=True):
url = HyperlinkedIdentityField(view_name='productreport-detail')
product = ProductSerializer():
url = HyperlinkedIdentityField(view_name='product-detail')
created_at = DateTimeField(read_only=True)
user_name = CharField(max_length=256)
created_at = DateTimeField(read_only=True)
See Question&Answers more detail:
os