DRF使用嵌套序列化器和外键创建对象

Pan*_*nos 4 python django django-rest-framework

我正在使用 DRF,并尝试创建一个具有多个外键的对象以及需要在此过程中创建的相关对象。

这是我的模型的简化版本:

class Race(models.Model):
    name = models.CharField(max_length=200)
    owner = models.ForeignKey(User, on_delete=models.CASCADE, related_name='races')
    type = models.ForeignKey(Type, on_delete=models.SET_NULL, related_name='races', null=True)
    region = models.ForeignKey(Region, on_delete=models.CASCADE, verbose_name=_('region'), related_name='races')
    country = models.ForeignKey(Country, on_delete=models.CASCADE, related_name='races')
    timezone = models.ForeignKey(Timezone, on_delete=models.SET_NULL, null=True)

class Event(models.Model):
    name = models.CharField(max_length=200)
    race = models.ForeignKey(Race, on_delete=models.CASCADE, related_name='events')
Run Code Online (Sandbox Code Playgroud)

这是我的 Race 序列化器:

class RaceSerializer(serializers.ModelSerializer):
    owner = UserSerializer(read_only=True)
    type = TypeSerializer(read_only=True)
    events = EventSerializer(many=True)
    country = CountrySerializer()
    region = RegionSerializer(read_only=True)
    timezone = TimezoneSerializer(read_only=True)

    def create(self, validated_data):
        with transaction.atomic():
            events = validated_data.pop('events', None)
            race = Race(**validated_data)
            race.save()
            for event in events:
                Event.objects.create(race=race, **event)
        return race
Run Code Online (Sandbox Code Playgroud)

以及我的看法:

class AddRaceView(CreateAPIView):
    serializer_class = RaceSerializer
    permission_classes = (IsAuthenticated,)

    def perform_create(self, serializer):
        serializer.save(owner=self.request.user)
Run Code Online (Sandbox Code Playgroud)

这是我通过 POST 请求发送的一些测试数据:

{
    "name": "ABC Marathon",
    "country": {
        "pk": 1,
        "name": "United States"
    },
    "region": {
        "pk": 1,
        "code": "ME"
    },
    "timezone": {
        "pk": 1,
        "code": "EST"
    },
    "events": [
        {
            "name": "Marathon"
        },
        {
            "name": "Half Marathon"
        }
    ]
}
Run Code Online (Sandbox Code Playgroud)

所以我遇到的问题是将有效数据传递给外键的序列化器。我不想为TypeRegionCountry、创建新对象Timezone,只传递适当的数据,以便新创建的 Race 对象正确引用现有外键。

这是我尝试过的:

1) 未设置read_only=True外键序列化器。这试图创建我不想要的新对象。

2)read_only=True外键序列化器的设置(如上面的代码)。这有助于不尝试创建新的Type等对象,而是从序列化器创建方法中Region删除相应的字段。validated_data所以我无法在创建时将现有对象添加到 Race 外键中。

3)使用 PrimaryKeyForeignField 而不是等TypeSerializerRegionSerializer但是当我用来RaceSerializer检索竞赛数据时,我只有一个pkundertyperegion,我真的希望能够检索外键的所有字段。

您能否建议一下对于这样的事情,正确的设置会是什么样子?我觉得这不应该这么难。

谢谢你!

Pan*_*nos 7

所以最后我通过对RelatedField每个外键使用而不是单独的序列化器来解决这个问题,除了EventSerializer编写嵌套对象真正需要的嵌套之外Event

这是RaceSerializer

class RaceSerializer(serializers.ModelSerializer):
    owner = UserSerializer(read_only=True)
    type = TypeField()
    country = CountryField()
    region = RegionField()
    timezone = TimezoneField()
    events = EventSerializer(many=True)
    race_cal_types = serializers.SerializerMethodField()

    def create(self, validated_data):
        with transaction.atomic():
            events = validated_data.pop('events', None)
            race = Race(**validated_data)
            race.save()
            for event in events:
                Event.objects.create(race=race, **event)
        return race
Run Code Online (Sandbox Code Playgroud)

这是我在 my 中的每个字段使用的RelatedField和的组合,例如外键:ModelSerializerRaceSerializerregion

class RegionSerializer(serializers.ModelSerializer):

    class Meta:
        model = Region
        fields = ('pk', 'name', 'code')


class RegionField(RelatedField):

    def get_queryset(self):
        return Region.objects.all()

    def to_internal_value(self, data):
        return self.get_queryset().get(**data)

    def to_representation(self, value):
        return RegionSerializer(value).data
Run Code Online (Sandbox Code Playgroud)

每个字段(type、、、、)都有自己的方法,region可以按照我需要的方式序列化/反序列化数据。countrytimezoneto_internal_valueto_representation