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
相关产品推荐
相关产品推荐

