如何在序列化程序类中创建带有中间表字段的数据库条目?

时间:2019-01-19 15:07:55

标签: python django django-rest-framework

我们的项目是一个虚构的网上商店,您可以在其中将小狗放入购物车中并订购。我们拥有的三个主要模型是“用户”,“订单”和“小狗”。因为每个订单都有一个幼犬列表,每个幼犬都有自己的数量,所以我们需要两者的中介模型,因为简单的ManyToManyField显然无法处理额外的列。这使事情变得更加复杂(至少对于Django新手而言)。 同样,我们的授权是通过JWT令牌进行的,这就是为什么self.request.user显然必须手动设置而不能自动设置的原因。 通过POST唯一可能创建的是新订单。这些应自动获取日期,发布日期的用户也应自动设置。还应计算订单的total_price,以使客户唯一需要发送的是幼犬及其各自数量的列表。

React中的POST:

async function createOrder(auth, puppies) {
  auth = auth.token
  puppies = puppies.map(element =>
    element = {'id' : element.puppy.id, 'amount': element.amount}
  )
  const res = await fetch(REACT_APP_BACKEND_URL + `/shop/orders/`, {
      method: 'POST',
      headers: {
        "Content-Type": "application/json",
        "Authorization": "JWT " +  auth
      },
      body: JSON.stringify({ puppies })
    })
  return res.json()

console.log结果如下:[{“ id”:2,“ amount”:1},{“ id”:3,“ amount”:1}]

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, null=True)
    puppies = models.ManyToManyField(Puppy, through='PuppyOrder')
    date = models.DateTimeField(auto_now_add=True, blank=True)
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='orders')

    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 str(self.id)


class Address(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    country = models.CharField(max_length=30, blank=True)
    street = models.CharField(max_length=30, blank=True)
    zip = models.CharField(max_length=10, blank=True)
    city = models.CharField(max_length=30, blank=True)


@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 shop.models import Puppy, Order
from django.contrib.auth.models import User
from rest_framework import permissions, status, viewsets, generics
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):
    queryset = Order.objects.all()
    permission_classes = (permissions.IsAuthenticated,)
    serializer_class = OrderSerializer

    def get_queryset(self):
        return Order.objects.all().filter(user=self.request.user)

    def perform_create(self, serializer):
        print('Creating new order...')
        serializer.save(user=self.request.user)
        return Response(serializer.data)


class OrderDetail(generics.RetrieveAPIView):
    permission_classes = (permissions.IsAuthenticated,)
    queryset = Order.objects.all()
    serializer_class = OrderSerializer

    def get_queryset(self):
        return Order.objects.all().filter(user=self.request.user)


class UserList(generics.ListAPIView):
    queryset = User.objects.all().select_related('address')
    serializer_class = UserSerializer


class UserDetail(generics.RetrieveAPIView):
    queryset = User.objects.all().select_related('address')
    serializer_class = UserSerializer


class PuppyList(generics.ListAPIView):
    permission_classes = (permissions.AllowAny,)
    queryset = Puppy.objects.all()
    serializer_class = PuppySerializer


class PuppyDetail(generics.RetrieveAPIView):
    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 django.db import models
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):
    # Code to generate token on login ...

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='puppy.id')
    order = serializers.ReadOnlyField(source='order.id')

    class Meta:
        model = PuppyOrder
        fields = ('order', 'puppy', 'amount')


class OrderSerializer(serializers.ModelSerializer):
    user = serializers.ReadOnlyField(source='user.username')
    puppies = PuppyOrderSerializer(source='puppyorder_set', many=True)

        def create(self, validated_data):
        print(validated_data)
        puppies = validated_data.pop("puppyorder_set")
        order = Order.objects.create(**validated_data)
        total_price = 0
        for puppy in puppies:
            puppy_id = puppy.get("id")
            amount = puppy.get("amount")
            puppy_instance = Puppy.objects.get(pk=puppy_id)
            total_price += puppy_instance.price
            PuppyOrder(order=order, puppy=puppy_instance, amount=amount).save()
        order.save()
        return Order

    class Meta:
        model = Order
        fields = ('id', 'total_price', 'puppies', 'date', 'user')

此代码会导致错误:“ 禁止直接分配给多对多集的前侧。请改用puppies.set()。” 请告诉我我在做什么错

1 个答案:

答案 0 :(得分:0)

很多事情你做得都不对。首先,认证。请检出DRF-JWT或类似的库,并使用它们的身份验证类,或者至少了解如何创建自己的身份验证类,以使身份验证不会遍及整个代码。

至于您认为的perform_create,您实际上不必在那里查看serializer.is_valid()。在调用create之前,已经在perform_create中对其进行了检查。据我所知,该方法并不意味着返回API响应。这只是为了在调用serializer.save()之前执行您想要的其他任何其他操作。对于大多数基本用法,它永远不会被覆盖。您还可以将用户及其日期传递给save方法,或在序列化程序中进行。

现在,在序列化程序的create方法中,您不需要访问initial_data。您只需要在validated_data中。首先,您应该在调用puppies之前从validated_data中弹出objects.create,否则它将无法处理。然后,您可以使用前面弹出的小狗来创建PuppyOrder对象。

这是在视图的perform_create中向序列化器的validated_data添加额外数据的方式:

serializer.save(date=datetime.today(), user=self.request.user, total_price=total_price)

您只需在验证的数据中直接添加项目,即可在序列化程序的create方法中执行相同的操作。毕竟这只是一本普通的字典:

validated_data['user'] = self.context['request'].user
validated_data['date'] = datetime.today()
validated_data['total_price'] = total_price

编辑:对PuppyOrder使用非模型序列化器

由于在发送请求时尚未创建订单和PuppyOrder,因此OrderSerializer中的以下行将导致错误,因为序列化程序需要实际的PuppyOrders

puppies = PuppyOrderSerializer(source='puppyorder_set', many=True)

相反,您可以使用这样的非模型序列化器(假设小狗已经存在):

class PuppyOrderCreateSerializer(serializers.Serializer):
    puppy = serializers.PrimaryKeyRelatedField(queryset=Puppy.objects.all())
    amount = serializers.IntegerField()

然后您可以在yuur OrderSerializer中使用此序列化器:

puppies_data = PuppyOrderCreateSerializer(many=True)