189

Let's say that we have the following model:

class Classroom(models.Model):
    room_number = [...]

class Teacher(models.Model):
    name = [...]
    tenure = [...]
    classroom = models.ForeignKey(Classroom)

Let's say that instead of getting a result like this per the ManyRelatedPrimaryKeyField function:

{
    "room_number": "42", 
    "teachers": [
        27, 
        24, 
        7
    ]
},

have it return something that includes the full related model representation like:

{
    "room_number": "42", 
    "teachers": [
        {
           'id': 27,
           'name': 'John',
           'tenure': True
        }, 
        {
           'id': 24,
           'name': 'Sally',
           'tenure': False
        }, 
    ]
},

Is this possible? If so, how? And is this a bad idea?

popcorn
  • 388
  • 1
  • 7
  • 28
Chaz
  • 3,232
  • 5
  • 19
  • 12

4 Answers4

290

The simplest way is to use the depth argument

class ClassroomSerializer(serializers.ModelSerializer):
    class Meta:
        model = Classroom
        depth = 1

However, that will only include relationships for forward relationships, which in this case isn't quite what you need, since the teachers field is a reverse relationship.

If you've got more complex requirements (eg. include reverse relationships, nest some fields, but not others, or nest only a specific subset of fields) you can nest serializers, eg...

class TeacherSerializer(serializers.ModelSerializer):
    class Meta:
        model = Teacher
        fields = ('id', 'name', 'tenure')

class ClassroomSerializer(serializers.ModelSerializer):
    teachers = TeacherSerializer(source='teacher_set')

    class Meta:
        model = Classroom

Note that we use the source argument on the serializer field to specify the attribute to use as the source of the field. We could drop the source argument by instead making sure the teachers attribute exists by using the related_name option on your Teacher model, ie. classroom = models.ForeignKey(Classroom, related_name='teachers')

One thing to keep in mind is that nested serializers do not currently support write operations. For writable representations, you should use regular flat representations, such as pk or hyperlinking.

Paolo
  • 20,112
  • 21
  • 72
  • 113
Tom Christie
  • 33,394
  • 7
  • 101
  • 86
  • When I tried the first solution I didn't receive the Teachers, however I did receive instances of the parent of the Classroom (which this example does not show). In the second solution I received an error - "'Classroom' object has no attribute 'teachers'" . Am I missing something? – Chaz Jan 29 '13 at 13:12
  • 1
    @Chaz Updated the answer to explain why `depth` wouldn't do what you need in this case, and to explain the exception you're seeing and how to deal with it. – Tom Christie Jan 29 '13 at 13:20
  • @TomChristie Is it possible to allow for "added" fields, that is, added to the default set of fields? I'd like to show reverse relationships in my Model from other models. I don't mind adding a field list of reverse relationships, but having to specify all of the normally default fields again seems rigid and fragile. – Will Bradley May 14 '13 at 18:20
  • @WillBradley Nope. Let's continue the discussion on the ticket. https://github.com/tomchristie/django-rest-framework/issues/837 – Tom Christie May 14 '13 at 20:21
  • Does the `depth` thing work across many to many relationships? – yellottyellott May 30 '13 at 05:00
  • 1
    I'm an idiot and was hitting the wrong server. It definitely works across many to many relationships. – yellottyellott May 30 '13 at 05:09
  • 22
    Nesting serializers is awesome! I had to do this and was using DRF 3.1.0. I had to include `many=True` like so `...TeacherSerializer(source='teacher_set', many=True)`. Otherwise I was getting the following error: `The serializer field might be named incorrectly and not match any attribute or key on the 'RelatedManager' instance. Original exception text was: 'RelatedManager' object has no attribute 'type'.` – Karthic Raghupathi Apr 08 '15 at 12:04
  • Hey @Tom Christie. I have kinda similar problem! But a bit different. I have two models, mealIngredient and Ingredient, mealIngredient has an fk to Ingredient, I want to be able to create a mealIngredient object by using id of the ingredient to be associated, but while retrieving a mealIngredient object via JSON I want to be able to see the complete ingredient object . Any idea how to go about it? – Shubham Aggarwal Apr 06 '16 at 13:54
  • 2
    Hi @TomChristie, thanks for this answer. I'm struggling with this problem myself but one thing I don't understand is where the `_set` came from in `teachers = TeacherSerializer(source='teacher_set')`. I tried searching the DRF docs and came up with nothing. Any hints? – daveslab Aug 11 '16 at 16:00
  • 4
    The reverse side of a ForeignKey will be named `..._set` by default. See the Django docs for more details: https://docs.djangoproject.com/en/1.10/ref/models/relations/#django.db.models.fields.related.RelatedManager – Tom Christie Aug 12 '16 at 09:01
  • What if I want depth on a specific field but not the other fields? – MiniGunnR Nov 11 '16 at 04:53
  • In that case include a nested serializer as an explicit field. – Tom Christie Nov 11 '16 at 13:36
  • You will need `many=True` with `source='teacher_set' ` if it has more than one value else it will give null – somsgod Dec 03 '19 at 07:30
  • Serializer also support write operations and there is a nice example in documents for writing nested serializers https://www.django-rest-framework.org/api-guide/relations/#writable-nested-serializers – Bijan Jun 23 '22 at 18:29
  • This solution will mean you can't create a classroom w/o including a teacher, what if i want to be able to create a class w/o including any teacher? – Abdul Giwa Jan 25 '23 at 14:43
50

Thank you @TomChristie!!! You helped me a lot! I would like to update that a little (because of a mistake I ran into)

class TeacherSerializer(serializers.ModelSerializer):
    class Meta:
        model = Teacher
        fields = ('id', 'name', 'tenure')

class ClassroomSerializer(serializers.ModelSerializer):
    teachers = TeacherSerializer(source='teacher_set', many=True)

    class Meta:
        model = Classroom
        field = ("teachers",)
Eliyahu Tauber
  • 521
  • 4
  • 5
  • 2
    What do you do if you want to filter, teachers_set – iamafasha Dec 05 '20 at 01:12
  • You can add a `list_serializer_class` in `TeacherSerializer`, and add filtering logic by overriding the `to_representation` function of the `list_serializer_class`. – ezvine Jan 13 '21 at 16:49
8

This can also be accomplished by using a pretty handy dandy django packaged called drf-flex-fields. We use it and it's pretty awesome. You just install it pip install drf-flex-fields, pass it through your serializer, add expandable_fields and bingo (example below). It also allows you to specify deep nested relationships by using dot notation.

from rest_flex_fields import FlexFieldsModelSerializer

class ClassroomSerializer(FlexFieldsModelSerializer):
    class Meta:
        model = Model
        fields = ("teacher_set",)
        expandable_fields = {"teacher_set": (TeacherSerializer, {"source": "teacher_set"})}

Then you add ?expand=teacher_set to your URL and it returns an expanded response. Hope this helps someone, someday. Cheers!

Paul Tuckett
  • 1,023
  • 11
  • 14
1

Thanks to @TomChristie and @Paolo

I would just like to add a clarification, the below code works fine but the person has to remember to add the related_name="teacher_set" attribute to the Model Teacher. In this case here is the complete code :

models.py

class Classroom(models.Model):
    room_number = [...]

class Teacher(models.Model):
    name = [...]
    tenure = [...]
    classroom = models.ForeignKey(Classroom, related_name='teacher_set')

Note: related_name='teacher_set' to the classroom field.

serializers.py

class TeacherSerializer(serializers.ModelSerializer):
    class Meta:
        model = Teacher
        fields = ('id', 'name', 'tenure')

class ClassroomSerializer(serializers.ModelSerializer):
    teachers = TeacherSerializer(source='teacher_set', many=True)

    class Meta:
        model = Classroom
        field = ("teachers",)
Gedeon Mutshipayi
  • 2,871
  • 3
  • 21
  • 42