oxan/djangorestframework-dataclasses

Use 'source' to determine which fields should be autogenerated

Opened this issue · 2 comments

It would be nice if one could use 'source' to change the name of a field in serialized data:

class MyModel(models.Model):
    id = models.AutoField(primary_key=True)


@dataclass
class MyDataclass:
    model: MyModel


class MyDataclassSerializer(DataclassSerializer):
    model_id = PrimaryKeyRelatedField(queryset=MyModel.objects.all(), source="model")

    class Meta:
        dataclass = MyDataclass

Right now this autogenerates another related field with the name 'model' and expects both to be in the serializer input data.

MyDataclassSerializer(data={"model_id": 1}).is_valid(raise_exception=True)
# raises rest_framework.exceptions.ValidationError: {'model': [ErrorDetail(string='This field is required.', code='required')]}

As a workaround I have to exclude the original like this:

class MyDataclassSerializer(DataclassSerializer):
    model_id = PrimaryKeyRelatedField(queryset=MyModel.objects.all(), source="model")

    class Meta:
        dataclass = MyDataclass
        exclude = ["model"]

Our use case is that we have both references and subserialized objects in our API, and we use '_id' as a postfix to identify references.

oxan commented

I'm not sure if suppressing the autogeneration of a field if another field uses it as source would be a sensible default. I can imagine usecases where source is used to add another representation, instead of replacing the default one all together, and it's also a breaking change. I'll have to check, but I also think that it'd be different behaviour from what DRF's built-in ModelSerializer does.

DRF built-in serializers are a bit different since they allow partial models by default. But when I set __all__ as fields I indeed get the same behaviour.

from django.db import models
from rest_framework import serializers


class MyModel(models.Model):
    id = models.AutoField(primary_key=True)


class MyModel2(models.Model):
    id = models.AutoField(primary_key=True)
    model = models.ForeignKey(MyModel, on_delete=models.CASCADE)


class MyModelSerializer(serializers.ModelSerializer):
    model_id = serializers.PrimaryKeyRelatedField(
        queryset=MyModel.objects.all(), source="model"
    )

    class Meta:
        model = MyModel2
        fields = "__all__"


MyModel(id=1).save()
MyModelSerializer(data={"model_id": 1}).is_valid(raise_exception=True)
# raises rest_framework.exceptions.ValidationError: {'model': [ErrorDetail(string='This field is required.', code='required')]}

I think probably it is more common that you have one representation of a field by 'source' and don't want to autogenerate another one, but seems hard to change now.