Как создать запись базы данных с полем промежуточной таблицы в моем классе сериализатора? - PullRequest
0 голосов
/ 19 января 2019

Наш проект - вымышленный интернет-магазин, где вы можете положить щенков в корзину и заказать их.У нас есть три основные модели: пользователи, заказы и щенки.Поскольку у каждого заказа есть список щенков, и у каждого щенка есть свое количество, нам была нужна промежуточная модель для двух, поскольку простой ManyToManyField, очевидно, не может обрабатывать дополнительные столбцы.Это значительно усложняет ситуацию (по крайней мере, для новичка в Django).Также наша авторизация осуществляется через токен JWT, поэтому self.request.user, видимо, должен быть установлен вручную, а не автоматически.Единственное, что должно быть возможно создать через POST - это новые заказы.Они должны автоматически получать дату, и пользователь, публикующий ее, также должен быть автоматически установлен.Итоговая цена заказа также должна быть рассчитана таким образом, чтобы клиент отправлял только список щенков и их соответствующие суммы.

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}]

моделям.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 голосов
/ 19 января 2019

Есть много вещей, которые вы не получаете совершенно правильно.Во-первых, аутентификация.Пожалуйста, проверьте DRF-JWT или аналогичные библиотеки и используйте их классы аутентификации или, по крайней мере, посмотрите, как создать свою, чтобы у вас не было засоренной аутентификации по всему коду.

Что касаетсяperform_create по вашему мнению, вам не нужно проверять serializer.is_valid() там.Он уже проверен в create до вызова perform_create.И этот метод не предназначен для возврата ответа API, насколько я знаю.Он просто предназначен для выполнения любых других дополнительных действий, которые вы хотите выполнить перед вызовом serializer.save().Для большинства основных применений оно никогда не переопределяется.Вы также можете передать своего пользователя и дату в метод сохранения или сделать это в сериализаторе.

Теперь в методе создания сериализатора вам не требуется доступ к initial_data.Все, что вам нужно, должно быть в validated_data.Во-первых, вы должны извлечь puppies из validated_data перед вызовом objects.create, иначе он не сможет с этим справиться.Затем вы можете теперь создавать PuppyOrder объекты, используя более ранних щенков.

Вот как вы добавляете дополнительные данные в validated_data сериализатора в представлении execute_create:

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

Вы можетесделайте то же самое в методе создания сериализатора, просто добавив элементы непосредственно в проверенные данные.Это всего лишь обычный словарь:

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)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...