Improperly configured nested resource using HyperlinkedModelSerializer - django

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)

Related

Why am I getting a circular import error with django?

This question seems to be asked before but none of the answers I came across solve my issue.
I'm getting the following error when I try running the server with python manage.py runserver:
django.core.exceptions.ImproperlyConfigured: The included URLconf 'tutorial.urls' does not appear to have any patterns in it. If you see valid patterns in the file then the issue is probably caused by a circular import.
The error goes away if I change models.py so that my Item class does not extend models.Model.
These are the relevant files:
models.py
from django.db import models
from django.contrib.auth.models import User
class Item(models.Model):
name = models.CharField(max_length=255)
owner = models.ForeignKey(User, on_delete=models.CASCADE)
price = models.DecimalField(decimal_places=2)
def __str__(self):
return self.name
serializers.py
from rest_framework import serializers
from django.contrib.auth.models import User, Group
from .models import Item
class UserSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = User
fields = ('url', 'username', 'email', 'groups')
class GroupSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Group
fields = ('url', 'name')
class ItemSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Item
fields = ('url', 'name', 'owner', 'price')
views.py
from django.contrib.auth.models import User, Group
from rest_framework import viewsets
from .serializers import UserSerializer, GroupSerializer, ItemSerializer
from .models import Item
class UserViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows users to be viewed or edited.
"""
queryset = User.objects.all().order_by('-date_joined')
serializer_class = UserSerializer
class GroupViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows groups to be viewed or edited.
"""
queryset = Group.objects.all()
serializer_class = GroupSerializer
class ItemViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows items to be viewed or edited.
"""
queryset = Item.objects.all()
serializer_class = ItemSerializer
urls.py
from django.urls import include, path
from rest_framework import routers
from tutorial.quickstart import views
router = routers.DefaultRouter()
router.register('users', views.UserViewSet)
router.register('groups', views.GroupViewSet)
router.register('items', views.ItemViewSet)
urlpatterns = [
path('', include(router.urls)),
path('api-auth/', include('rest_framework.urls', namespace='rest_framework'))
]

Issues with HyperlinkedModelSerializer with recursive model

Overview
I'm setting up a new Django application with Django REST Framework (DRF), and this is my first time using the HyperlinkedModelSerializer for the API endpoint.
I've overridden the get_queryset() method on the ModelViewSet, so I've also the basename argument to the application router and explicitly defined the url attribute in the serializer as suggested here. This fixed issues that I was having with the model's own url attribute.
However, I'm getting the following error message when trying to serialize a ForeignKey field of the same class as the parent model. It fails with the following message:
Could not resolve URL for hyperlinked relationship using view name "employee-detail". You may have failed to include the related model in your API, or incorrectly configured the lookup_field attribute on this field.
Is there something special in the serializer I need to do to support using recursive model relationships like this?
Example code
# app/models.py
from django.db import models
class AbstractBase(models.Model):
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
class Meta:
abstract = True
class Employee(AbstractBase):
name = models.CharField(max_length=254)
manager = models.ForeignKey('self', related_name='direct_reports',
on_delete=models.SET_NULL, blank=True, null=True)
...
def __str__(self):
return str(self.name)
# app/views.py
from rest_framework import viewsets
from rest_framework.pagination import PageNumberPagination
from app import models
from app import serializers
# pagination defaults
class StandardResultsSetPagination(PageNumberPagination):
page_size = 25
page_size_query_param = 'page_size'
max_page_size = 1000
class EmployeeViewSet(viewsets.ModelViewSet):
pagination_class = StandardResultsSetPagination
serializer_class = serializers.EmployeeSerializer
http_method_names = ['options', 'get']
def get_queryset(self):
params = self.request.query_params
queryset = models.Employee.objects.all()
# apply url query filters...
return queryset
# app/serializers.py
from app import models
from rest_framework import serializers
class EmployeeSerializer(serializers.HyperlinkedModelSerializer):
url = serializers.HyperlinkedRelatedField(
read_only=True, view_name='employees-detail')
manager = serializers.HyperlinkedRelatedField(
read_only=True, view_name='employees-detail')
class Meta:
model = models.Employee
fields = ('url', 'name', 'manager')
# project/urls.py
from django.conf.urls import url, include
from django.contrib import admin
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^api/', include('app.urls')),
]
# app/urls.py
from django.conf.urls import url, include
from rest_framework import routers
from app import views
router = routers.DefaultRouter(trailing_slash=False)
router.register(r'employees', views.EmployeeViewSet, basename='employees')
urlpatterns = [
url(r'^', include(router.urls)),
]
I have no idea why this worked, but changing the inherited serializer from serializers.HyperlinkedModelSerializer to serializers.ModelSerializer and removing the overridden url fixed things perfectly.
New serializer looks like this:
# app/serializers.py
from app import models
from rest_framework import serializers
class EmployeeSerializer(serializers.ModelSerializer):
manager = serializers.HyperlinkedRelatedField(
read_only=True, view_name='employees-detail')
class Meta:
model = models.Employee
fields = ('url', 'name', 'manager')

DRF: Could not resolve URL for hyperlinked relationship using view name "tickettype-detail"

We want our users to be able to define the options for various select fields in our site. Those options will go in their own table (model).
For the API, we have the "Ticket" model. which has a field "type" which is a ForeignKey to the "TicketType" model.
I'm getting this error:
Could not resolve URL for hyperlinked relationship using view
name "tickettype-detail". You may have failed to include the
related model in your API, or incorrectly configured the
`lookup_field` attribute on this field.
If I remove the type field from the Ticket model, the error goes away. I've read a lot of posts online about this, and tried a lot of different things, but so far haven't been able to fix it.
models.py:
from django.db import models
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
class Ticket(models.Model):
summary = models.CharField(
verbose_name=_('Summary'),
max_length=255,
)
description = models.TextField(
verbose_name=_('Description'),
blank=True,
)
type = models.ForeignKey(
'TicketType',
verbose_name=_('Type'),
on_delete=models.PROTECT,
)
...other fields omitted...
created = models.DateTimeField(
verbose_name=_('Created'),
default=timezone.now,
)
class TicketType(models.Model):
type = models.CharField(
verbose_name=_('Type'),
max_length=255,
)
serializers.py
from rest_framework import serializers, permissions
from tickets.models import Ticket, TicketType
class TicketSerializer(serializers.HyperlinkedModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name="tickets:ticket-detail")
permission_classes = (permissions.IsAuthenticated,)
class Meta:
model = Ticket
fields = (
'url',
'id',
'summary',
'description',
'type',
...other fields omitted...
'created',
)
class TicketTypeSerializer(serializers.HyperlinkedModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name="tickets:tickettype-detail")
permission_classes = (permissions.IsAuthenticated,)
class Meta:
model = TicketType
fields = (
'url',
'id',
'type',
)
views.py
from rest_framework import permissions
from rest_framework.generics import ListCreateAPIView, RetrieveUpdateAPIView
from tickets.models import Ticket, TicketType
from tickets.serializers import TicketSerializer, TicketTypeSerializer
class TicketList(ListCreateAPIView):
permission_classes = (permissions.IsAuthenticated,)
queryset = Ticket.objects.all()
serializer_class = TicketSerializer
class TicketDetail(RetrieveUpdateAPIView):
permission_classes = (permissions.IsAuthenticated,)
queryset = Ticket.objects.all()
serializer_class = TicketSerializer
class TicketTypeList(ListCreateAPIView):
permission_classes = (permissions.IsAuthenticated,)
queryset = TicketType.objects.all()
serializer_class = TicketTypeSerializer
class TicketTypeDetail(RetrieveUpdateAPIView):
permission_classes = (permissions.IsAuthenticated,)
queryset = TicketType.objects.all()
serializer_class = TicketTypeSerializer
Most of the things I tried related to adding lookkup_field in various places, but I guess I didn't hit on the right combination.
EDIT: Forgot to post my urls.py.
urls.py
from django.urls import path
from tickets import views
app_name = 'tickets'
urlpatterns = [
path('', views.TicketList.as_view(), name='ticket-list'),
path('<int:pk>/', views.TicketDetail.as_view(), name='ticket-detail'),
path('types/', views.TicketTypeList.as_view(), name='tickettype-list'),
path('types/<int:pk>/', views.TicketTypeDetail.as_view(), name='tickettype-detail'),
]
Solved!
It seems that, in this case, DRF isn't providing the fully-qualified view_name when trying to "reverse" to get the list of options for the field.
I added this to the TicketType serializer:
type = serializers.HyperlinkedRelatedField(
queryset=TicketType.objects.all(),
required=True,
view_name='tickets:tickettype-detail',
)
For view_name, DRF was using "tickettype-detail".
I then added this to the TicketType model:
def __str__(self):
return self.type

manyToMany with django rest framework

I am currently using the default CRUD operations provided by django-rest-framework. It works well with normal models but one of my model has many-many relation with another tag model. Here is the code for models
class ActivityType(models.Model):
title = models.CharField(max_length=200)
slug = models.CharField(max_length=250,unique=True)
def __unicode__(self):
return self.slug
class Activity(models.Model):
owner = models.ForeignKey('auth.user')
title = models.CharField(max_length=200)
slug = models.CharField(max_length=250,unique=True)
description = models.TextField()
tags = models.ManyToManyField(ActivityType)
created = models.DateTimeField(auto_now_add=True, blank=True)
def __unicode__(self):
return self.slug
What i want to know is what is the best method to integrate DRF with the same, if possible without writing all CRUD operations from scratch.
In your serializers.py
from rest_framework import serializers
from rest_framework import generics
from models import Activity
from models import ActivityType
class ActivityTypeSerializer(serializers.ModelSerializer):
class Meta:
model = ActivityType
fields = ('id', 'title', 'slug')
class ActivitySerializer(serializers.ModelSerializer):
tags = ActivityTypeSerializer(many=True, read_only=True)
class Meta:
model = Activity
fields = ('id', 'owner', 'title', 'slug', 'description', 'tags', 'created')
in your views.py
from rest_framework import viewsets
from serializers import ActivitySerializer
from serializers import ActivityTypeSerializer
from models import Activity
from models import ActivityType
class ActivityViewSet(viewsets.ModelViewSet):
queryset = Activity.objects.all()
serializer_class = ActivitySerializer
class ActivityTypeViewSet(viewsets.ModelViewSet):
queryset = ActivityType.objects.all()
serializer_class = ActivityTypeSerializer
and in your urls.py
from rest_framework.urlpatterns import format_suffix_patterns
from rest_framework import routers, serializers, viewsets
from rest_framework import generics
from rest_framework import viewsets, routers
from your_app.views import ActivityTypeViewSet
from your_app.views import ActivityViewSet
router = routers.DefaultRouter()
router.register(r'activitytypes', ActivityTypeViewSet)
router.register(r'activities', ActivityViewSet)
Also make sure the restframework urls are included as described in docs
urlpatterns = patterns('',
# your other urls
url(r'^api/$', include('rest_framework.urls', namespace='rest_framework')),
url(r'api/accounts/', include('rest_framework.urls', namespace='rest_framework')),
)

How to implement nested models/routes?

I have a Playlist Model and a Track model.
class Playlist(models.Model):
created = models.DateTimeField(auto_now_add=True)
user = models.ForeignKey(User, related_name="playlists")
class Track(models.Model):
playlist = models.ForeignKey(Playlist, related_name="tracks")
track_id = models.CharField(max_length=50)
And the serializers:
class TrackSerializer(serializers.ModelSerializer):
class Meta:
model = Track
fields = ("id", "track_id")
class PlaylistSerializer(serializers.ModelSerializer):
user = serializers.Field(source="user.username")
tracks = TrackSerializer(many=True)
class Meta:
model = Playlist
fields = ("id", "created", "user", "tracks")
How would I go about creating views (using viewsets preferably) that allow me list a playlist's tracks at playlists/<playlist_id> and also create tracks at the same url?
I currently get non_field_errors when I go to the above url.
Couldn't find much on how to do these nested views on the docs. Thanks.
You can ues drf-nested-routers (https://github.com/alanjds/drf-nested-routers).
The only interesting part in the code below is setting the track playlist in the pre_save method of the TrackViewSet.
views.py
class TrackViewSet(viewsets.ModelViewSet):
queryset = Track.objects.all()
serializer_class = TrackSerializer
def pre_save(self, obj):
obj.playlist = Playlist.objects.get(pk=self.kwargs['playlist_pk'])
class PlaylistViewSet(viewsets.ModelViewSet):
queryset = Playlist.objects.all()
serializer_class = PlaylistSerializer
def pre_save(self, obj):
obj.user = self.request.user
urls.py
from django.conf.urls import patterns, url, include
from rest_framework_nested import routers
from . import views
router = routers.SimpleRouter()
router.register(r'playlists', views.PlaylistViewSet)
playlists_router = routers.NestedSimpleRouter(router, r'playlists', lookup='playlist')
playlists_router.register(r'tracks', views.TrackViewSet)
urlpatterns = patterns('',
url(r'^', include(router.urls)),
url(r'^', include(playlists_router.urls)),
)