Type error to create and update my list in django

2020-05-06 04:44发布

问题:

I'm trying to use my api to create and update products in a bundle. I did so:

model.py

class Business(models.Model):
    name = models.CharField(max_length=155)

class Product(models.Model):
    business = models.ForeignKey(
        Business,
        on_delete=models.CASCADE,
        blank=True,
        null=True,
    )
    name = models.CharField(max_length=200)
    description = models.TextField(null=True, blank=True)
    price = models.DecimalField(max_digits=10, decimal_places=2)

    def __str__(self):
        return self.name

    class Meta:
        verbose_name = "Product"



class Bundle(models.Model):
    business = models.ForeignKey(
        Business,
        on_delete=models.CASCADE,
        blank=True,
        null=True,
    )
    name = models.CharField(max_length=100)
    description = models.TextField(null=True, blank=True)
    price = models.DecimalField(max_digits=10, decimal_places=2)
    products = models.ManyToManyField(Product, related_name="bundles",blank=True, null=True, through="BundleProduct")

    class Meta:
        verbose_name = "Bundle"


    def __str__(self):
        return self.name

class BundleProduct(models.Model):

    bundle = models.ForeignKey(Bundle, on_delete=models.CASCADE, related_name="bundleproducts")
    product = models.ForeignKey(Product, on_delete=models.CASCADE, related_name="bundleproducts")
    number = models.IntegerField(default=1)

    class Meta:
        verbose_name = "Bundle of Product"


    def __str__(self):
        return str(self.product.name) + " do " + self.bundle.name

    def get_absolute_url(self):
        return reverse("BundleProduct_detail", kwargs={"pk": self.pk})

And here is my serializers.py:

class ProductSerializer(serializers.ModelSerializer):

    class Meta:
        model = Product
        fields = "__all__"        

class BundleProductSerializer(serializers.ModelSerializer):

    class Meta:
        model = BundleProduct
        fields = "__all__"


class BundleSerializer(serializers.ModelSerializer):

    class Meta:
        model = Bundle
        fields = "__all__"

My viewset.py

class ProductViewSet(viewsets.ModelViewSet):

    queryset = Product.objects.all()
    serializer_class = ProductSerializer
    model = Product


class BundleProductViewSet(viewsets.ModelViewSet):

    queryset = BundleProduct.objects.all()
    serializer_class = BundleProductSerializer
    model = BundleProduct


class BundleViewSet(viewsets.ModelViewSet):

    queryset = Bundle.objects.all()
    serializer_class = BundleSerializer
    model = Bundle

When I try to post some products in bundleproducts I receive "Incorrect type. Expected pk value, received list."

Reading about this error, I found some issues relating to PrimaryKeyRelatedField and SlugRelatedField. I know I need to override but I have no idea how to do it.

It's an example of how to post would works:

{
    "number": 1,
    "bundle": 2,
    "product": 
         [
            1,
            2
         ]
}

After watching the video commented by Neil, I created the following method:

class BundleSerializer(
    serializers.ModelSerializer
):
    products = ProductSerializer(many=True)

    def create(self, validated_data):
        products = validated_data.pop('products')
        bundle = BundleProduct.objects.create(**validated_data)
        for product in products:
            BundleProduct.objects.create(**product, bundle=bundle)
        return Bundle


    class Meta:
        model = Bundle
        fields = "__all__"

But doesn't work. I receive this error: "TypeError at /api/v1/bundle/

'name' is an invalid keyword argument for this function"

回答1:

If you are making post via BundleSerializer you need to pass products with list of ProductSerializer data not just id since products in BundleSerializer is accepting productsSerializer data. You are getting type error 'name' is an invalid keyword argument for this function" because your validated_data contain name and BundleProduct object Does not have name field.And you are creating BundleProduct objects with validated_data.

Create bundle object and pass id of bundle object to BundleProduct object.

  • If you do not want to create product and just pass existing product id you need to make ListField

  • You need to Override get_fields and check the requests

  • override to_representation to return always List of ProdutSerializer Data
  • Override create for POST request
  • Override update for PUT and PATCH Request

Below is solution for POST Request

For PATCH AND PUT Request you need to override update method of ModelSerializer and handle the products accordingly.


class BundleSerializer(serializers.ModelSerializer):

    def create(self, validated_data):
        products = validated_data.pop('products')
        bundle = Bundle.objects.create(**validated_data)
        for product_id in products:
            product = get_object_or_404(Product, pk=product_id)
            BundleProduct.objects.create(product=product, bundle=bundle)
        return bundle

    class Meta:
        model = Bundle
        fields = "__all__"

    def to_representation(self, instance):
        repr = super().to_representation(instance)
        repr['products'] = ProductSerializer(instance.products.all(), many=True).data
        return repr

    def get_fields(self):
        fields = super().get_fields()
        if self.context['request'].method in ['POST', "PATCH","PUT"]:
            fields['products'] = serializers.ListField(
                write_only=True,
                child=serializers.IntegerField()
            )
        return fields

sample POST data to BundleSerializer

{
    "products":[1,2],
    "name":"Offer One",
    "description":"description",
    "price":1212,
    "business":1

}


回答2:

In my experience, if you want to update a model and a related model in one request, with DRF, the easiest way to do this is to override the "create" method of a serializer. There's a good video on this here which I used as my reference: https://www.youtube.com/watch?v=EyMFf9O6E60



回答3:

The issue here is that you are posting a list to BundleProduct's product field yet it is an ForeignKey. To join Bundle to a Product, simply POST:

{
  "bundle": 2,
  "product" 1,
  "number": 1
}

You can repeat this:

{
  "bundle": 2,
  "product" 4,
  "number": 1
}

to add yet another product 4 to the same bundle and so on. Just make sure you do them one by one and not in a list as you had done earlier.