Search code examples
pythondjangodjango-rest-framework

How to serialize a related model at depth 2 without serializing intermediate model at depth 1?


I have three models with foreign keys in this direction Order -> Customer -> User. I would like to write a view set for orders that serializes the corresponding user without the intermediate customer.

Models:

class Order(models.Model):
    customer = ForeignKey("customers.Customer")

class Customer(models.Model):
    user = ForeignKey("users.User")

class User(models.Model):
    name = CharField(max_length=64)

Serializer:

class UserSerializer(ModelSerializer):
    class Meta:
        model = User
        fields = ["name"]

class OrderSerializer(ModelSerializer):
    user = UserSerializer(source="customer__user", read_only=True)

    class Meta:
        model = Order
        fields = ["id", "user"]

View set:

class OrderViewSet(ModelViewSet):
    queryset = Order.objects.all()
    serializer_class = OrderSerializer

Desired output:

[
    {"id": 1, "user": {"name": "John"}},
    {"id": 2, "user": {"name": "Mary"}},
]

Actual output:

[
    {"id": 1},
    {"id": 2},
]

It works fine if I go via the intermediate customer serializer:

class CustomerSerializer(ModelSerializer):
    user = UserSerializer(read_only=True)

    class Meta:
        model = User
        fields = ["user"]

class OrderSerializer(ModelSerializer):
    customer = CustomerSerializer(read_only=True)

    class Meta:
        model = Order
        fields = ["id", "customer"]

but then the output contains the intermediate customer object:

[
    {"id": 1, {"customer": {"user": {"name": "John"}}},
    {"id": 2, {"customer": {"user": {"name": "Mary"}}},
]

Solution

  • The source uses dot-syntax, so:

    class UserSerializer(ModelSerializer):
        class Meta:
            model = User
            fields = ['name']
    
    
    class OrderSerializer(ModelSerializer):
        user = UserSerializer(source='customer.user', read_only=True)
    
        class Meta:
            model = Order
            fields = ['id', 'user']

    I would also advise to use .select_related(..) in the viewset, to prevent fetching the Customer and User with additional queries, so:

    class OrderViewSet(ModelViewSet):
        queryset = Order.objects.select_related('customer__user')
        serializer_class = OrderSerializer