0

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!

Niklas
  • 171
  • 1
  • 1
  • 10

1 Answers1

0

You've broken things by specifying related_names on your ForeignKeys in the PuppyOrder model. Remove those.

Daniel Roseman
  • 588,541
  • 66
  • 880
  • 895
  • That already fixed the error for me, thanks a lot! That's the problem with following too many different tutorials at once... I will try the other answer again later with your proposed changes and edit the question accordingly. Do you see any other things that might cause problems? Maybe I should post it to codereview.stackexchange after. – Niklas Jan 16 '19 at 21:13
  • I took back the accepted answer for now, so that the other questions can be answered. I wish you could accept multiple answers as correct. – Niklas Jan 16 '19 at 22:49