Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement a hierarchy of resources (eg. /parents/<id>/children) in Django REST Framework

I couldn't find any information on how to achieve this in the tutorial at the Django REST Framework website and I haven't managed to find it in the documentation, though I'm sure it's there somewhere.

I want issues to be the parent resource and pages to be the children so that /issues/1/pages returns all pages with issue_id of 1.

Is there a good way to achieve this using generic class based views?

Here's what I have so far.

restAPI/urls.py:

from django.conf.urls import patterns, url
from rest_framework.urlpatterns import format_suffix_patterns
from restAPI import views

urlpatterns = patterns('',
    url(r'^issues/$', views.IssueList.as_view()),
    url(r'^issues/(?P<pk>[0-9]+)/$', views.IssueDetail.as_view()),


    url(r'^issues/(?P<issue_id>[0-9]+)/pages/$', views.PageList.as_view()),    
    url(r'^pages/(?P<pk>[0-9]+)/$', views.PageDetail.as_view()),
)

urlpatterns = format_suffix_patterns(urlpatterns)

restAPI/models.py:

from django.db import models

class Issue(models.Model):
    created = models.DateTimeField(auto_now_add=True)
    revision = models.IntegerField(default = 1)
    issue_date = models.DateTimeField(auto_now_add=True)
    issue_image_url = models.CharField(max_length=100)

class Page(models.Model):
    created = models.DateTimeField(auto_now_add=True)
    page_number = models.IntegerField()
    standard_page_url = models.CharField(max_length=100, default='')
    large_page_url = models.CharField(max_length=100, default='')
    thumbnail_url = models.CharField(max_length=100, default='')

    issue = models.ForeignKey(Issue, related_name="pages")

    class Meta:
        ordering = ('page_number',)

restAPI/serializers.py:

from rest_framework import serializers
from restAPI.models import Page, Issue

class IssueSerializer(serializers.ModelSerializer):
    class Meta:
        model = Issue
        fields = ('id', 'created', 'revision', 'issue_date', 'issue_image_url')

class PageSerializer(serializers.ModelSerializer):       
    class Meta:
        model = Page
        fields = ('id', 'created', 'page_number', 'standard_page_url', 'large_page_url', 'thumbnail_url')

restAPI/views.py:

from restAPI.models import Page, Issue
from restAPI.serializers import PageSerializer, IssueSerializer
from rest_framework import mixins
from rest_framework import generics

class IssueList(mixins.ListModelMixin,
                  mixins.CreateModelMixin,
                  generics.GenericAPIView):
    queryset = Issue.objects.all()
    serializer_class = IssueSerializer

    def get(self, request, *args, **kwargs):
        return self.list(request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        return self.create(request, *args, **kwargs)

class IssueDetail(mixins.RetrieveModelMixin,
                    mixins.UpdateModelMixin,
                    mixins.DestroyModelMixin,
                    generics.GenericAPIView):
    queryset = Issue.objects.all()
    serializer_class = IssueSerializer

    def get(self, request, *args, **kwargs):
        return self.retrieve(request, *args, **kwargs)

    def put(self, request, *args, **kwargs):
        return self.update(request, *args, **kwargs)

    def delete(self, request, *args, **kwargs):
        return self.destroy(request, *args, **kwargs)  

class PageList(mixins.ListModelMixin,
                  mixins.CreateModelMixin,
                  generics.GenericAPIView):
    queryset = Page.objects.all()
    serializer_class = PageSerializer

    def get(self, request, *args, **kwargs):
        print kwargs
        return self.list(request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        return self.create(request, *args, **kwargs)

class PageDetail(mixins.RetrieveModelMixin,
                   mixins.UpdateModelMixin,
                   mixins.DestroyModelMixin,
                   generics.GenericAPIView):
    queryset = Page.objects.all()
    serializer_class = PageSerializer

    def get(self, request, *args, **kwargs):
        return self.retrieve(request, *args, **kwargs)

    def put(self, request, *args, **kwargs):
        return self.update(request, *args, **kwargs)

    def delete(self, request, *args, **kwargs):
        return self.destroy(request, *args, **kwargs)

How can I implement this sort of relationship between issues and pages?

like image 469
Joar Leth Avatar asked Jun 27 '13 08:06

Joar Leth


People also ask

What is Serializers in Django REST framework?

Serializers in Django REST Framework are responsible for converting objects into data types understandable by javascript and front-end frameworks. Serializers also provide deserialization, allowing parsed data to be converted back into complex types, after first validating the incoming data.

What is Django REST framework architecture?

Django REST framework is an open source, flexible and fully-featured library with modular and customizable architecture that aims at building sophisticated web APIs and uses Python and Django.


2 Answers

Here is another way I've done this:

views.py

from models import Customer, Order from serializers import CustomerSerializer, OrderSerializer  from rest_framework import generics  class CustomerList(generics.ListCreateAPIView):     queryset = Customer.objects.all()     serializer_class = CustomerSerializer  class CustomerDetail(generics.RetrieveUpdateDestroyAPIView)     queryset = Customer.objects.all()     serializer_class = CustomerSerializer  class OrdersByCustomer(generics.ListCreateAPIView):     queryset = Order.objects.all()     serializer_class = OrderSerializer      def get_queryset(self):         customer_pk = self.kwargs['customer_pk']         return self.queryset.filter(customer__pk=customer_pk)      def pre_save(self, obj):         obj.customer_id = self.kwargs['customer_pk']   class OrderDetail(generics.RetrieveUpdateDestroyAPIView):     queryset = Order.objects.all()     serializer_class = OrderSerializer 

serializers.py

from models import Customer, Order  from rest_framework import serializers from rest_framework.reverse import reverse  class OrderSerializer(serializers.HyperlinkedModelSerializer)      class Meta:         model = Order  class CustomerSerializer(serializers.HyperlinkedModelSerializer)      orders = serializers.SerializerMethodField('get_customer_orders')      def get_customer_orders(self, obj):         return reverse('ordersbycustomer-list',                 args=[obj.pk], request=self.context['request'])      class Meta:         model = Customer 

urls.py

from django.conf.urls import patterns, include, url from views import OrdersByCustomer, CustomerDetail, CustomerList  urlpatterns = patterns("",     url(r'^customers/(?P<customer_pk>.+)/orders/$', OrdersByCustomer.as_view(), name='ordersbycustomer-list'),     url(r'^customers/(?P<pk>.+)/$', CustomerDetail.as_view(), name='customer-detail'),     url(r'^customers/$', CustomerList.as_view(), name='customer-list'),     ) 

There is more code involved than with Viewsets/Routers but this gives you much more control over what is going on.

Here I have chosen to only expose orders as children of a customer. Since they are separated, you can use different serializer classes for list vs detail.

like image 84
Mike Drawback Avatar answered Sep 30 '22 07:09

Mike Drawback


I added def get_queryset(self): issue_id = self.kwargs['issue_id'] return Page.objects.filter(issue_id = issue_id) to PageList and now GET works for issue/ /pages. Now I just have to figure out how to post as well.

I added def pre_save(self, obj): obj.issue_id = self.kwargs['issue_id'] to PageList and now POST works too. Querying pages from an issue that doesn't exist returns an empty result rather than 404 not found though. If anyone knows of a better way to do this I'm very interested to hear about it.

If your method get_queryset(self) returns an empty list instead of 404 NOT FOUND, I would suggest to use the shortcut function get_list_or_404 from django. The method could look like this:

from django.shortcuts import get_list_or_404

def get_queryset(self):
    filter = {}
    filter['issue_id'] = self.kwargs['issue_id']
    return get_list_or_404(self.queryset, **filter)

I know this is an old post, but maybe this could help other people having the same or some similar problem.

like image 20
cezar Avatar answered Sep 30 '22 06:09

cezar