You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Django:嵌套关系含继承子类型时如何序列化及事务处理?

问题背景

我们开发了一个房主保险应用,模型结构如下:

class Homeowner(models.Model):
    name = models.CharField(max_length = 100, null = True, blank = True)
    property = models.ForeignKey(Property, null = True)
    
class Property(models.Model):
    address = models.CharField(max_length = 500, null = True, blank = True)
    # lots more removed. Not an abstract model.

class House(Property):
    number_of_floors = models.IntegerField(default = 1)
    
class Condo(Property):
    has_doorman = models.BooleanField(default = False)

此前分两步手动录入房产和房主信息,二者为零或一对一关系。现需实现统一表单,让用户一次性提交房主及对应房产信息,验证失败则全部回滚,且无法调整现有模型结构。

已知普通Property的序列化写法如下:

class PolicySerializer(serializers.ModelSerializer):
    property = PropertySerializer()
    
    class Meta:
        model = Homeowner
        fields = ['name', 'property']

    def create(self, validated_data):
        property_data = validated_data.pop('property')
        property = Property.objects.create(**property_data)
        Homeowner.objects.create(property=property, **validated_data)
        return homeowner

核心问题:

  • 如何编写create()方法以路由到对应的Property子类型模型?
  • 若Homeowner验证失败,如何避免Property对象遗留?是否需用@transaction.atomic装饰create()方法?

解决方案

1. 路由到Property子类型模型

需先通过提交数据明确房产类型(比如新增property_type字段,值为house或condo),再在序列化器中根据类型选择对应模型创建实例:

步骤1:编写子类型序列化器

为House和Condo分别实现序列化器,确保子类型字段验证正确:

class HouseSerializer(serializers.ModelSerializer):
    class Meta:
        model = House
        fields = ['address', 'number_of_floors']

class CondoSerializer(serializers.ModelSerializer):
    class Meta:
        model = Condo
        fields = ['address', 'has_doorman']

步骤2:修改主序列化器动态处理数据

调整主序列化器,新增房产类型字段并在验证阶段匹配对应子类型序列化逻辑:

class PolicySerializer(serializers.ModelSerializer):
    property = serializers.DictField()
    property_type = serializers.ChoiceField(choices=['house', 'condo'])

    class Meta:
        model = Homeowner
        fields = ['name', 'property_type', 'property']

    def validate_property(self, value):
        property_type = self.initial_data.get('property_type')
        serializer_cls = HouseSerializer if property_type == 'house' else CondoSerializer
        serializer = serializer_cls(data=value)
        serializer.is_valid(raise_exception=True)
        return serializer.validated_data

步骤3:在create方法中路由创建子类型实例

根据房产类型选择对应的模型创建实例,再关联到Homeowner:

def create(self, validated_data):
    property_type = validated_data.pop('property_type')
    property_data = validated_data.pop('property')
    
    if property_type == 'house':
        property_instance = House.objects.create(**property_data)
    elif property_type == 'condo':
        property_instance = Condo.objects.create(**property_data)
    else:
        raise serializers.ValidationError("Invalid property type")
    
    homeowner = Homeowner.objects.create(property=property_instance, **validated_data)
    return homeowner

2. 避免Property对象遗留:使用事务原子性

必须用@transaction.atomic装饰create()方法。因为如果不包裹事务,创建Property实例后若Homeowner创建失败(验证不通过、数据库错误等),已创建的Property会留在数据库中,造成数据不一致。

带事务的最终create方法

from django.db import transaction

@transaction.atomic
def create(self, validated_data):
    property_type = validated_data.pop('property_type')
    property_data = validated_data.pop('property')
    
    if property_type == 'house':
        property_instance = House.objects.create(**property_data)
    elif property_type == 'condo':
        property_instance = Condo.objects.create(**property_data)
    else:
        raise serializers.ValidationError("Invalid property type")
    
    # 若Homeowner创建失败,整个事务回滚,Property实例也会被删除
    homeowner = Homeowner.objects.create(property=property_instance, **validated_data)
    return homeowner

补充说明

  • 事务将Property创建和Homeowner创建绑定为原子操作:要么两者都成功,要么全部回滚,不会留下孤立的Property记录。
  • 若在validate阶段出现错误,此时还未进入create方法的事务块,不会创建任何数据库对象,自然也不会有遗留问题。

内容的提问来源于stack exchange,提问作者Elf Sternberg

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 07:04:56