How to implement nested models/routes? - django

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)),
)

Related

Django rest framework "Like" functionality

in django, I want to write a function that deletes the likes if the current user has liked that post before and if so, how can I do this.
This is my models.py
class Like(models.Model):
created_by = models.ForeignKey(User, on_delete=models.CASCADE)
post = models.ForeignKey(Post, on_delete=models.CASCADE)
def __str__(self):
return self.created_by.username
This is my serializers.py
class LikeSerializer(serializers.ModelSerializer):
created_by = serializers.StringRelatedField()
post = serializers.StringRelatedField()
post_id = serializers.IntegerField()
class Meta:
model = Like
fields = ('__all__')
This is my views.py
class LikesView(viewsets.ModelViewSet):
queryset = Like.objects.all()
serializer_class = LikeSerializer
def perform_create(self, serializer):
serializer.save(created_by=self.request.user)
This is my urls.py
from django.urls import path,include
from .views import (
PostView,
PostView_View,
LikesView,
CommentView
)
from rest_framework import routers
router = routers.DefaultRouter()
router.register('likes', LikesView)
router.register('post', PostView)
router.register('comment', CommentView)
urlpatterns = [
] + router.urls
I try that but still nor working
class LikeSerializer(serializers.ModelSerializer):
created_by = serializers.StringRelatedField()
post = serializers.StringRelatedField()
post_id = serializers.IntegerField()
class Meta:
model = Like
fields = ('__all__')
def post(self, request, post_id):
post = Post.objects.get(pk=post_id)
if Like.objects.filter(post=post,
created_by=request.user).exists():
Like.objects.filter(post=post, created_by=request.user).delete()
else:
Like.objects.create(post=post, created_by=request.user)
For this action, you can override the serializer's save method. First, get an object from a table, and if it exists, delete it
https://www.django-rest-framework.org/api-guide/serializers/

Getting model instance by passing field name and not primary ID

I want to get a model instance using URL as http://127.0.0.1:8000/db/User/email (i.e. using email as a query) and not by http://127.0.0.1:8000/db/User/1/. How to approach this.
Model:
class Employee(models.Model):
firstname = models.CharField(max_length=100)
email = models.CharField(max_length=100)
serializers.py
class EmployeeSerializers(serializers.ModelSerializer):
field = NestedSerializers()
class Meta:
model = Employee
fields = '__all__'
def create(self, validated_data):
#overwrite this method for writable nested serializers.
view.py:
class UserView(viewsets.ModelViewSet):
queryset = Employee.objects.all()
serializer_class = EmployeeSerializers
urls.py:
router = routers.DefaultRouter()
router.register('User', views.UserView)
urlpatterns = [
path('', views.index, name='index'),
path('/', include(router.urls))
]
Is it possible to do using ModelViewSet?
I see you are using DRF viewset. If you only ever want to use the email and not the id then you can override the the retrieve function of the viewset like so:
from django.shortcuts import get_object_or_404
class UserView(viewsets.ModelViewSet):
queryset = Employee.objects.all()
serializer_class = EmployeeSerializers
def retrieve(self, request):
employee = get_object_or_404(
self.queryset,
email=self.kwargs['email']
)
serializer = self.serializer_class(employee)
return Response(serializer.data)
urls
router = routers.DefaultRouter()
router.register('^User/(?P<email>.+)/$', views.UserView)
urlpatterns = [
path('', views.index, name='index'),
path('/', include(router.urls))
]
retrieve is functionality already provided in the viewset class

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

django rest framework ListAPIView returning no value

I am using django reset framework to create an API.
Here is my Serializer.py
class ArticleSerializer(serializers.Serializer):
class Meta:
model = Article
fields = ("title", "content")
views.py
class ArticleListView(ListAPIView):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
class ArticleDetailView(RetrieveAPIView):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
urls.py
from .views import ArticleListView, ArticleDetailView
urlpatterns = [
path('', ArticleListView.as_view() ),
path('<pk>', ArticleDetailView.as_view() ),
]
When I pull up http://127.0.0.1:8000/api/, there are 3 objects of Article model in DB and all what I get is this:
[
{},
{},
{}
]
models.py
class Article(models.Model):
title = models.CharField(max_length=120)
content = models.TextField()
def __str__(self):
return self.title
Why can't I see the values of the title and content of my class ?
There's a small typo in your code.
class ArticleSerializer(serializers.Serializer):
should be:
class ArticleSerializer(serializers.ModelSerializer):
Note the "Model" that you are missing.
With regular Serializer you would need to define the fields explicitly while ModelSerializer will introspect the associated model.

Improperly configured nested resource using HyperlinkedModelSerializer

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)