Django ManyToMany field as json format

Viewed 3868

I'm trying to get data as json format. I've one ManyToMany field which is returning just id. But I need that contents too. Here is my models.py

class Pricing(models.Model):
    name = models.CharField(max_length = 100)
    price = models.CharField(max_length = 100)

    def __str__(self):
        return self.name+' and '+self.price


class Service(models.Model):
    name = models.CharField(max_length=100)
    price = models.ManyToManyField(Pricing, blank=True)

    def __str__(self):
        return self.name 

And also the views.py which is returning json format data

def all_service_json(request, name):
    data = serializers.serialize("json", Service.objects.filter(name__icontains=name))
    return HttpResponse(data)

Now Getting the output like below

[
    {
        "model": "myapp.service", 
        "pk": 2, 
        "fields": 
        {
            "name": "Service name", 
            "price": [1, 2]
        }
    }
]

But want like below

[
    {
        "model": "myapp.service", 
        "pk": 2, 
        "fields": 
        {
            "name": "Service name", 
            "price": 
            {
                1: "Price 1", 
                2: "Price 2"
            }
        }
    }
]
4 Answers

Creating ModelSerializer objects from within Django Rest Framework will let you display nested object data:

http://www.django-rest-framework.org/api-guide/serializers/#dealing-with-nested-objects

# myapp/serializers.py
...
from rest_framework import serializers

class PricingSerializer(serializers.ModelSerializer):

    class Meta:
        fields = '__all__'
        model = Pricing

class ServiceSerializer(serializers.ModelSerializer):
    price = PricingSerializer(read_only=True, many=True)

    class Meta:
        fields = '__all__'
        model = Service

# myapp/views.py
def all_service_json(request, name):
    services = Service.objects.filter(name__icontains=name)
    data = ServiceSerializer(services, many=True).data
    return HttpResponse(data)

As @robert mentioned using nested serializers will fix your issue.

But note that by default nested serializers are read-only. So If you want to support write operations to a nested serializer field you'll need to add create() and/or update() methods in order to explicitly specify how the child relationships should be saved.

Writable Service Serializer

class ServiceSerializer(serializers.ModelSerializer):
    price = PricingSerializer(many=True)

    class Meta:
        fields = '__all__'
        model = Service

    # sample create 
    def create(self, validated_data):
        prices_data = validated_data.pop('price')
        service = Service.objects.create(**validated_data)
        for price_data in prices_data:
            Price.objects.create(service=service, **price_data)
        return service

     # add update here

myapp/views.py

def all_service_json(request, name):
    services = Service.objects.filter(name__icontains=name)
    serializer = ServiceSerializer(services)
    return HttpResponse(serializer.data)

So in your case all you have to do is to add depth = 1 and you will get nested representations.

Docs

The default ModelSerializer uses primary keys for relationships, but you can also easily generate nested representations using the depth option:

class AccountSerializer(serializers.ModelSerializer):
    class Meta:
        model = Account
        fields = ['id', 'account_name', 'users', 'created']
        depth = 1

I just start learning Django from last 8 hours and I stuck into this situation where many to many relationship is returning id instead of child data. I wrote some custom code. For me it solve my problem, hope this helps someone.

from django.core.serializers import serialize
import json 

def modelToDict(model):
    jsn = serialize("json", model) # convert to json
    mydict = json.loads(jsn) # again convert to dictionary
    return mydict

def all_service_json(request, name):
    data = Service.objects.filter(name__icontains=name)
    dictdata = modelToDict(data)
    for i in range(len(dictdata)):
         price = modelToDict(data[i].price.all())
         dictdata[i]['fields']['price'] = price
    return HttpResponse(json.dumps(`dictdata`), content_type="application/json")
Related