142

How do I serialize a many-to-many field into list of something, and return them through rest framework? In my example below, I try to return the post together with a list of tags associated with it.

models.py

class post(models.Model):
    tag = models.ManyToManyField(Tag)
    text = models.CharField(max_length=100)

serializers.py

class PostSerializer(serializers.ModelSerializer):
    class Meta:
        model = Post
        fields = ("text", "tag"??)

views.py

class PostViewSet(viewsets.ReadOnlyModelViewSet):
    queryset = Post.objects.all()
    serializer_class = PostSerializer
kengcc
  • 1,811
  • 3
  • 16
  • 17
  • Using help from @Brian I manage to list the items in this form: "tags": [{"name": "tag1"}]. I would like to simplify it to list, is it possible: "tags": ["tag1", "tag2",...] – kengcc Oct 17 '15 at 09:31
  • 2
    use ` tags = serializers.SlugRelatedField(many=True,read_only=True, slug_field='title', //tag's fireld you want to show allow_null=True)` in PostSerializers – M. Dhaouadi Aug 10 '17 at 13:20

11 Answers11

180

You will need a TagSerializer, whose class Meta has model = Tag. After TagSerializer is created, modify the PostSerializer with many=True for a ManyToManyField relation:

class PostSerializer(serializers.ModelSerializer):
    tag = TagSerializer(read_only=True, many=True)

    class Meta:
        model = Post
        fields = ('tag', 'text',)

Answer is for DRF 3

Brian
  • 7,394
  • 3
  • 25
  • 46
  • it works!!! :D Any idea how to turn this serializer into just a comma separated list? class TagSerializer(serializers.ModelSerializer): class Meta: model = Tag fields = ('name') – kengcc Oct 17 '15 at 03:38
  • 2
    Right now, I get: "tags": [{"name": "tag1"}] I would like to simplify it to: "tags": ["tag1", "tag2",...] – kengcc Oct 17 '15 at 03:45
  • tags = serializers.ListField(source='tag'). This will get you the list of the __str__ representation of each object of tag – Sachin Gupta Oct 17 '15 at 10:58
  • 6
    What if you want to be able to update the tag through the Post? (e.g. not read_only) I'm getting weird behavior when I take away the read_only and try to PATCH an update to the tag field (I get an error about the tag already existing) – getup8 Dec 26 '16 at 06:30
  • 4
    The `read_only=True` part is explained here: http://www.django-rest-framework.org/api-guide/relations/#writable-nested-serializers – Pavel Vergeev Jun 04 '17 at 18:53
  • Try `tags=serializers.CharSerializer(source='tag__name', many=True)`, note double underscore. – Vladimir Prudnikov Jun 06 '18 at 19:31
  • You also must include a source attribute in the tag for example `tag = TagSerializer(source="tag", read_only=True, many=True)` Where source is your many-to-many field in a Model. – Олег Войтинський Nov 26 '19 at 09:34
  • After doing this how it will update `tag ` array ??? – Piyush Dec 13 '21 at 04:56
  • You can directly use tag = serializers.StringRelatedField(many=True), it will return a list – Maaz Bin Mustaqeem Aug 14 '22 at 04:35
40

This is what I did, let´s suppose a Book can have more than one author and an Author can have more than one book: On Model:

class Author(models.Model):
    name = models.CharField(max_length=100, default="")
    last_name = models.IntegerField(default=0)

class Book(models.Model):
    authors = models.ManyToManyField(Author, related_name="book_list", blank=True)
    name = models.CharField(max_length=100, default="")
    published = models.BooleanField(default=True)

On Serializers:

class BookSerializer(serializers.ModelSerializer):
    authors = serializers.PrimaryKeyRelatedField(queryset=Author.objects.all(), many=True)

    class Meta:
        model = Book
        fields = ('id', 'name', 'published', 'authors')


class AuthorSerializer(serializers.ModelSerializer):
    book_list = BookSerializer(many=True, read_only=True)

    class Meta:
        model = Author
        fields = ('id', 'name', 'last_name', 'book_list')
18

Adding to @Brian's answer "tags": [{"name": "tag1"}] can be simplified to "tags": ["tag1", "tag2",...] in this way:

class TagListingField(serializers.RelatedField):
 
     def to_representation(self, value):
         return value.name

class PostSerializer(serializers.ModelSerializer):
    tag = TagListingField(many=True, read_only=True)

    class Meta:
        ...

More info here: https://www.django-rest-framework.org/api-guide/relations/#custom-relational-fields

theTypan
  • 5,471
  • 6
  • 24
  • 29
candyfoxxx
  • 181
  • 1
  • 4
12

The default ModelSerializer uses primary keys for relationships. However, you can easily generate nested representations using the Meta depth attribute:

class PostSerializer(serializers.ModelSerializer):
    class Meta:
        model = Post
        fields = ("text", "tag")
        depth = 1 

As mentioned in the documentation :

The depth option should be set to an integer value that indicates the depth of relationships that should be traversed before reverting to a flat representation.

Ismail Hachimi
  • 653
  • 6
  • 11
5

This works for me.

tag = TagSerializer(source="tag", read_only=True, many=True)
Windsooon
  • 6,864
  • 4
  • 31
  • 50
4

Django 2.0

For many to many field, if you want specific one:

class QuestionSerializer(serializers.ModelSerializer):

    topics_list = serializers.SerializerMethodField()

    def get_topics_list(self, instance):
        names = []
        a = instance.topics.get_queryset()
        for i in a:
            names.append(i.desc)
        return names
    class Meta:
        model = Question
        fields = ('topics_list',)
  • For `get_topics_list` you could simplify to `return list(instance.topics.values_list('desc', flat=True))` – bdoubleu Jun 24 '19 at 11:10
2

In the serializer on init method you can pass the queryset to the field and rest_framework valide the ids on that queryset

1) first extend your serializer from serializers.ModelSerializer

class YourSerializer(serializers.ModelSerializer):

2) include the field on the meta class

class YourSerializer(serializers.ModelSerializer):
  class Meta:
        fields = (..., 'your_field',)

3) in the init method:

def __init__(self, *args, **kwargs):
    super(YourSerializer, self).__init__(*args, **kwargs)
    self.fields['your_field].queryset = <the queryset of your field>

You can limit the queryset for that field under any argument using filter or exclude like normally you do. In case that you want include all just use .objects.all()

rodrigomd
  • 171
  • 1
  • 3
2

models.py

class Tag(models.Model):
    name = models.CharField(max_length=100)
    # ===============
    # ... rest of the fields ...

class Post(models.Model):
    tag = models.ManyToManyField(Tag)
    text = models.CharField(max_length=100)

serialiazers.py

class TagSerializer(serializers.ModelSerializer):
    class Meta:
        model = Tag
        fields = '__all__'


class PostSerializer(serializers.ModelSerializer):
    tags = TagSerializer(many=True, read_only=True)

    class Meta:
        model = Post
        fields = ("text", "tag")

views.py

## FUNCTION BASED VIEW
def fbvPost_ListView(request):
    # list
    if request.method == "GET":
        posts = Post.objects.all()
        serializer = PostSerializer(instance=posts, many=True)
        return JsonResponse(serializer.data, safe=False)

    return JsonResponse({"success": False})

# ===========================================================

## CLASS BASED VIEW
class cbvPost_ListView(viewsets.ReadOnlyModelViewSet):
    queryset = Post.objects.all()
    serializer_class = PostSerializer

NB: Tag, Post are two models & we need to serialize them. Here, Post model have a dependency of Tag models, so here we explicitly mention it, [tags = TagSerializer(many=True, read_only=True)] or its return it's primary field value.

DETAILS HERE

iamdipta
  • 346
  • 3
  • 7
  • The link at the end of your response has moved [OVER HERE](https://github.com/rebornbd/my-note/blob/master/backend/django/study/serializer-deserializer.md) – Brian K Jun 28 '23 at 23:33
0

Hi I will be showing many to many for update and create. The context is the event can have many dances and dances can have many event.

The request will be as followed.

 {
     "competition": 2,
     "title": "the title",
     "dances":[ {"id":1},{"id":2}],
     "description": "the desc"            
 }

The Create Function will be as followed.
def create(self, validated_data):
    try:
        dance_ids = []
        for dance in self.initial_data['dances']:
            if 'id' not in dance:
                raise serializers.ValidationError({'detail': 'key error'})
            dance_ids.append(dance['id'])

        new_event = models.Event.objects.create(**validated_data)
        
        if dance_ids:
            for dance_id in dance_ids:
                new_event.dances.add(dance_id)
        new_event.save()
        return new_event

    except Exception as e:
        raise serializers.ValidationError({'detail': e})

The Update Function will be as followed.
def update(self, instance, validated_data):
    # Delete all records of genres.
    try:
        for current_genre in instance.dances.all():
            instance.dances.remove(current_genre)

        # Repopulate genres into instance.
        for dance in self.initial_data['dances']:
            if 'id' not in dance:
                raise serializers.ValidationError({'detail': 'key error'})
            dance_obj = models.Dance.objects.get(pk=dance['id'])
            instance.dances.add(dance_obj)

            event_updated = super().update(instance, validated_data)

        return event_updated
    except Exception as e:
        raise serializers.ValidationError({'detail': e})

If you want to just do "dances":[1,2] instead, just make some amendments to the

for dance in self.initial_data['dances']:
        if 'id' not in dance:
            raise serializers.ValidationError({'detail': 'key error'})
        dance_ids.append(dance['id'])

part. I hope this will be able to help yall out! :)

Gerald H
  • 454
  • 4
  • 13
0

First, Tag needs its own serializer too

class TagSerializer(serializers.ModelSerializer):
    class Meta:
    model = Tag
    fields = '__all__'

Then in your PostSerializer, add one line

class PostSerializer(serializers.ModelSerializer):
tag = TagSerializer(read_only=True, many=True).data
class Meta:
    model = Post
    fields = ("text", "tag")

This will make it so your Tag field in Post is an array of tag ids. if you don't put the ".data" part, it will put all of the attributes of tag, which is probably too much in most cases

michaels234
  • 119
  • 1
  • 3
0

You can use serializers.SlugRelatedField() or serializers.StringRelatedField(many=True) Serializer relations

In your case :

class PostSerializer(serializers.ModelSerializer):
    tag = serializers.StringRelatedField(many=True) # this will return a list


    class Meta:
        model = Post
        fields = ('tag', 'text',)