ManyToMany Through Field Serialization

126 Views Asked by At

I am having problems serializing my items/orders with quantity.

Results I am getting:

{
"id": 1,
"_current_status": null,
"orderitem_set": [
    {
        "item_id": 2,
        "quantity": 5
    },
    {
        "item_id": 1,
        "quantity": 1
    }
],
"items": [
    {
        "id": 2,
        "name": "Blue Shoe",
        "description": "Sweet blue shoe bro",
        "weight": "99.99",
        "price": "99.99"
    },
    {
        "id": 1,
        "name": "Red Shoe",
        "description": "sweet red shoe bro",
        "weight": "1.00",
        "price": "100.00"
    }
],
"_last_modified": "2015-06-10T22:32:08.007833Z",
"_weight": "500.95",
"_total_price": "599.95",
"placed": false,
"date_placed": null
}

Results I want:

{
"id": 1,
"_current_status": null,
"items": [
    {
        "id": 2,
        "name": "Blue Shoe",
        "description": "Sweet blue shoe bro",
        "weight": "99.99",
        "price": "99.99",
        "quantity": 5
    },
    {
        "id": 1,
        "name": "Red Shoe",
        "description": "sweet red shoe bro",
        "weight": "1.00",
        "price": "100.00",
        "quantity": 1
    }
],
"_last_modified": "2015-06-10T22:32:08.007833Z",
"_weight": "500.95",
"_total_price": "599.95",
"placed": false,
"date_placed": null
}

Classes

class Order(models.Model):
    _current_status = models.ForeignKey("main.Status", blank=True, null=True)
    _last_modified = models.DateTimeField(auto_now=True)
    _weight = models.DecimalField(max_digits=10, decimal_places=2, blank=True, null=True)
    _total_price = models.DecimalField(max_digits=10, decimal_places=2, blank=True, null=True)

    user = models.ForeignKey("main.ShopUser")
    items = models.ManyToManyField("main.Item", through='main.OrderItem')
    placed = models.BooleanField(default=False)
    date_placed = models.DateTimeField(null=True, blank=True)


class OrderItem(models.Model):
    order = models.ForeignKey('main.Order')
    item = models.ForeignKey('main.Item')
    quantity = models.IntegerField(default=1)

Serializers

I realize that my serializer is what is putting the orderitem_set into my json but I couldn't figure out how to get the quantity data into items other than in a custom view that loops through the items and the orderitem_set and adds a quantity to items. But that is just terrible and I was hoping there was a way to do this with the built in stuff with the framework or at least make it so I don't have to do that for every view.

class OrderItemField(serializers.RelatedField):
    def to_representation(self, value):
        return {'item_id': value.item_id, 'quantity': value.quantity}


class OrderSerializer(serializers.ModelSerializer):
    _current_status = StatusSerializer(required=False, many=False)
    orderitem_set = OrderItemField(read_only=True, many=True)
    items = ItemSerializer(required=True, many=True)

    class Meta:
        model = Order
        exclude = ('user',)

The view

This view gives me what I am looking for, but it is just terrible. Especially when I have other views that would be giving me a list of orders vs just a single order.

def set_quantities(data):
    # Gets the quantity for each item and then appends it to each item instead of having 'orderitem_set'
    for order_item in data['orderitem_set']:
        for item in data['items']:
            if item['id'] == order_item['item_id']:
                item['quantity'] = order_item['quantity']
    del data['orderitem_set']
    return data

def get(self, request, *args, **kwargs):
    serializer = self.get_serializer(self.get_object())

    data = set_quantities(serializer.data)
    return Response(data, status=status.HTTP_200_OK)
2

There are 2 best solutions below

0
On BEST ANSWER

By changing the OrderSerializer items field to be an OrderItemField I was able to get back to root Order by using self.root.instance and then using that in conjuction with the value, which is an Item instance, build a query to find the OrderItem object to get the quantity. Then I can get a serialized Item using my ItemSerializer and just append the quantity to that data.

Doing it this way applies to all of my order views and it is a lot less messy. If there is a better way please let me know!

Serializers

class OrderItemField(serializers.RelatedField):
    def to_representation(self, value):
        if type(self.root.instance) == list:
            self.root.instance = self.root.instance[0]
        order_item = OrderItem.objects.filter(item=value, order=self.root.instance).first()
        quantity = order_item.quantity
        value = ItemSerializer(value).data
        value['quantity'] = quantity
        return value


class OrderSerializer(serializers.ModelSerializer):
    _current_status = StatusSerializer(required=False, many=False)
    items = OrderItemField(many=True, read_only=True)

    class Meta:
        model = Order
        exclude = ('user',)
0
On

You can just use it like this:

class OrderItemSerializer(serializers.ModelSerializer):
    class Meta:
        model = Order.items.through
        fields = ('order', 'item', 'quantity')


class OrderSerializer(serializers.ModelSerializer):
    items = OrderItemSerializer(source='orderitem_set', many=True)
    class Meta:
        model = Order
        fields = ('your_order_fields', 'items')

Note: you have to add items field into OrderSerializer.