Django REST Framework序列化器中外键对象创建问题求助
你遇到的问题根源在于SlugRelatedField的默认验证逻辑:它会在序列化验证阶段就检查传入的town值是否存在于Town.objects.all()中,如果找不到就直接抛出Object does not exist异常,根本不会进入你自定义的create方法。
要实现"不存在则自动创建Town"的需求,有两种常用方案,我推荐第一种,因为它能保持API接口的一致性:
方案一:自定义支持自动创建的SlugRelatedField
我们可以继承SlugRelatedField,重写它的to_internal_value方法,把默认的get()查询改成get_or_create(),这样在验证阶段就会自动创建不存在的Town:
from rest_framework import serializers class CreateSlugRelatedField(serializers.SlugRelatedField): def to_internal_value(self, data): try: # 用get_or_create替代默认的get,返回创建或查询到的对象 return self.get_queryset().get_or_create(**{self.slug_field: data})[0] except (TypeError, ValueError): self.fail('invalid')
然后修改你的ShopSerializer,替换原来的town字段:
class ShopSerializer(serializers.HyperlinkedModelSerializer): # 使用自定义的字段替代默认的SlugRelatedField town = CreateSlugRelatedField(slug_field='town', queryset=Town.objects.all()) class Meta: model = Shop fields = ('name', 'address', 'opening_hour', 'closing_hour', 'owner', 'geo_point', 'town', 'url') read_only_fields = ('owner',) def create(self, validated_data): request = self.context['request'] owner = request.user # 现在validated_data里的town已经是Town对象了,无需再处理 shop = Shop.objects.create(owner=owner, **validated_data) return shop
这样修改后,客户端传入已存在的town名称时,会直接关联到现有Town;传入新名称时,会自动创建对应的Town,再关联到新创建的Shop。
方案二:拆分读写字段(备选)
如果你觉得自定义字段有点复杂,也可以拆分字段:用一个write_only的CharField接收town名称,再用一个read_only的SlugRelatedField返回town信息:
class ShopSerializer(serializers.HyperlinkedModelSerializer): # 读取时返回town的slug town = serializers.SlugRelatedField(slug_field='town', queryset=Town.objects.all(), read_only=True) # 写入时接收town名称 town_name = serializers.CharField(max_length=50, write_only=True) class Meta: model = Shop fields = ('name', 'address', 'opening_hour', 'closing_hour', 'owner', 'geo_point', 'town', 'town_name', 'url') read_only_fields = ('owner',) def create(self, validated_data): request = self.context['request'] owner = request.user town_name = validated_data.pop('town_name') town, _ = Town.objects.get_or_create(town=town_name) shop = Shop.objects.create(owner=owner, town=town, **validated_data) return shop
这种方案需要客户端在创建Shop时传入town_name字段,而不是原来的town字段,适合不需要保持原有接口结构的场景。
为什么原来的代码不生效?
再强调一下:DRF的序列化流程是先验证数据,再执行create/update方法。你原来的create方法里的get_or_create逻辑永远不会被执行,因为在验证阶段,SlugRelatedField就已经因为找不到对应的Town而抛出异常了。自定义字段的核心就是把创建逻辑提前到验证阶段,让后续的create方法能拿到合法的Town对象。
内容的提问来源于stack exchange,提问作者Bartłomiej Paluch

