I am in the process of learning about the Django Rest Framework at the moment and I stumbled unto a tricky part that I can't seem to get working in our project. Our project is a fictional webshop where you can put dog babies in your cart and order them. Our Frontend is communicating with the backend via Django Rest Framework. The three main models that we have are Users, Orders and Puppies. Because every order has a list of puppies and every puppy has its own amount we needed an intermediary model for the two as a simple ManyToManyField apparently can't handle extra columns. This makes things much more complicated (at least for someone who is new to Django).
How can I reflect the relationship in the serializers and the views and how do I have to form the JSON Object that is sent from the frontend?
also:
Where would I calculate the total price of an order?
Do I need the OrderDetail view even if I don't want to edit or get one specific order or is the OrderList view enough?
There is a question on here which is solving a similar problem: Include intermediary (through model) in responses in Django Rest Framework I implemented it in the code and got it working now, thanks to Daniel Roseman's comment. Creating the orders manually over /admin and getting them works now as it should. I still can't create orders via the API.
This is my code:
models.py:
from django.db import models
from django.conf import settings
from django.contrib.auth.models import User
from django.db.models.signals import post_save
from django.dispatch import receiver
class Puppy(models.Model):
name = models.CharField(max_length=30)
price = models.DecimalField(max_digits=6, decimal_places=2)
image_url = models.CharField(max_length=200)
age = models.IntegerField(null=True)
weight = models.IntegerField(null=True)
description_de = models.CharField(max_length=500, null=True)
description_en = models.CharField(max_length=500, null=True)
def __str__(self):
return self.name
class Order(models.Model):
total_price = models.DecimalField(max_digits=9, decimal_places=2)
puppies = models.ManyToManyField(Puppy, through='PuppyOrder')
date = models.DateTimeField()
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='orders')
def save(self, *args, **kwargs):
"""
Create a new Order
"""
print("An order is being saved: " + str(Order.id))
super(Order, self).save(*args, **kwargs)
def __str__(self):
return str(self.id)
class PuppyOrder(models.Model):
order = models.ForeignKey(Order, on_delete=models.CASCADE)
puppy = models.ForeignKey(Puppy, on_delete=models.CASCADE)
amount = models.IntegerField()
def __str__(self):
return self.id
class Address(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
# Some more address fields ...
@receiver(post_save, sender=User)
def create_user_address(sender, instance, created, **kwargs):
if created:
Address.objects.create(user=instance)
@receiver(post_save, sender=User)
def save_user_address(sender, instance, **kwargs):
instance.address.save()
views.py:
from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework.reverse import reverse
from rest_framework.exceptions import ValidationError
from rest_framework_jwt.serializers import VerifyJSONWebTokenSerializer
from .serializers import UserSerializer, UserSerializerWithToken, OrderSerializer, PuppySerializer
from .permissions import IsOwner
@api_view(['GET'])
def api_root(request, format=None):
return Response({
'users': reverse('user-list', request=request, format=format),
'orders': reverse('order-list', request=request, format=format),
'puppies': reverse('puppy-list', request=request, format=format),
})
class OrderList(generics.ListCreateAPIView):
http_method_names = ["get", "post"]
queryset = Order.objects.all()
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
serializer_class = OrderSerializer
def get_queryset(self):
# Validation Code and getting self.request.user from jwt token ...
if self.request.user.is_anonymous:
return None
return Order.objects.all().filter(user=self.request.user)
def perform_create(self, serializer):
# Validation Code and getting self.request.user from jwt token ...
serializer.save(user=self.request.user)
class OrderDetail(generics.RetrieveUpdateDestroyAPIView):
http_method_names = ["get", "post"]
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
queryset = Order.objects.all()
serializer_class = OrderSerializer
class UserList(generics.ListAPIView):
http_method_names = ["get", "post"]
queryset = User.objects.all().select_related('address')
serializer_class = UserSerializer
class UserDetail(generics.RetrieveAPIView):
http_method_names = ["get", "post"]
queryset = User.objects.all().select_related('address')
serializer_class = UserSerializer
class PuppyList(generics.ListAPIView):
http_method_names = ["get"]
permission_classes = (permissions.AllowAny,)
queryset = Puppy.objects.all()
serializer_class = PuppySerializer
class PuppyDetail(generics.RetrieveAPIView):
http_method_names = ["get"]
permission_classes = (permissions.AllowAny,)
queryset = Puppy.objects.all()
serializer_class = PuppySerializer
serializers.py:
from rest_framework import serializers
from rest_framework_jwt.settings import api_settings
from django.contrib.auth.models import User
from .models import Order, Puppy, PuppyOrder
class UserSerializer(serializers.ModelSerializer):
orders = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
class Meta:
model = User
fields = ('id', 'username', 'orders')
class UserSerializerWithToken(serializers.ModelSerializer):
# Neccessary code for generating a JWT token ...
class PuppySerializer(serializers.ModelSerializer):
description = serializers.SerializerMethodField()
def get_description(self, puppy):
return {'DE': puppy.description_de, 'EN': puppy.description_en}
class Meta:
model = Puppy
fields = ('id', 'name', 'price', 'image_url', 'age', 'weight', 'description')
class PuppyOrderSerializer(serializers.ModelSerializer):
puppy = serializers.ReadOnlyField(source='order.puppy')
order = serializers.ReadOnlyField(source='order.order')
class Meta:
model = PuppyOrder
fields = ('id', 'puppy', 'amount')
class OrderSerializer(serializers.ModelSerializer):
user = serializers.ReadOnlyField(source='user.username')
puppies = PuppyOrderSerializer(source='puppyorder_set', many=True)
# This is currently producing an error
# puppies = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
class Meta:
model = Order
fields = ('id', 'total_price', 'puppies', 'date', 'user')
Feel free to tell me if you see something in my code that could be improved or doesn't make sense. Thanks in advance, any help would be much appreciated!