如何实现Django子模型mission_id按父实例重置自动编号?
问题描述
我在Django中定义了如下父子模型:
models.py
class Pallet_Mission(models.Model): code = models.AutoField(primary_key=True) load_type = models.CharField(max_length=20,null=True,blank=True) mission_no = models.CharField(max_length=255, null=True,blank=True) class PalletSubMission(models.Model): mission_id = models.CharField(max_length=255,null=True,blank=True) pallet_mission = models.ForeignKey(Pallet_Mission,on_delete=models.CASCADE,related_name='sub',null = True) point = models.CharField(max_length=255,null=True,blank=True)
当前输出示例:
{ "code": 52, "mission_no": "test1234", "load_type": "1234", "sub": [ { "id": 75, "mission_id": "s1", "point": null, "pallet_mission": 52 }, { "id": 76, "mission_id": "s2", "point": null, "pallet_mission": 52 } ] }
目前mission_id为手动输入,我希望子模型的mission_id实现自动编号:同一父实例下的子项依次为s1、s2……,切换到新的父实例(如code=53)时,子项重新从s1开始编号。现有序列化器代码如下:
serializers.py
class PalletSubSerializers(serializers.ModelSerializer): id = serializers.IntegerField(required=False) class Meta: model = PalletSubMission fields = '__all__' class Pallet_Mission_Serializer(serializers.ModelSerializer): sub = PalletSubSerializers(many=True) class Meta: model = Pallet_Mission fields = '__all__' def create(self, validated_data): sub = validated_data.pop('sub') mission = Pallet_Mission.objects.create(**validated_data) for i in sub: PalletSubMission.objects.create(pallet_mission=mission,**i) return mission def update(self,instance,validated_data): sub =validated_data.pop('sub') instance.load_type=validated_data.get('load_type',instance.load_type) instance.mission_no=validated_data.get('mission_no',instance.mission_no) instance.save() sub_mission = PalletSubMission.objects.filter(pallet_mission=instance.pk).values_list('id', flat=True) sub_mission_list =[] for i in sub: if "id" in i.keys(): if PalletSubMission.objects.filter(id=i['id']).exists(): sub_instance = PalletSubMission.objects.get(id=i['id']) sub_instance.mission_id = i.get('mission_id',sub_instance.mission_id) sub_instance.point = i.get('point',sub_instance.point) sub_instance.save() sub_mission_list.append(sub_instance.id) else: continue else: sub_instance = PalletSubMission.objects.create(pallet_mission=instance,**i) sub_mission_list.append(sub_instance.id) for i in sub_mission: if i not in sub_mission_list: PalletSubMission.objects.filter(pk=i).delete() return instance
请问该如何实现这一需求?
解决方案
要实现子模型mission_id按父实例自动编号,需要分别处理创建父实例时生成子项编号和更新父实例时维护子项编号连续性两种场景:
1. 修改创建逻辑
创建父实例及其子项时,直接按子项传入顺序生成sN格式的编号:
def create(self, validated_data): sub_data_list = validated_data.pop('sub') mission = Pallet_Mission.objects.create(**validated_data) # 按传入顺序生成mission_id for idx, sub_data in enumerate(sub_data_list, start=1): # 覆盖手动传入的mission_id,自动生成s1、s2... sub_data['mission_id'] = f"s{idx}" PalletSubMission.objects.create(pallet_mission=mission, **sub_data) return mission
2. 修改更新逻辑
更新时需处理子项增、删、改操作,操作完成后统一重置所有子项的编号,保证连续性:
def update(self, instance, validated_data): sub_data_list = validated_data.pop('sub') # 更新父实例基础字段 instance.load_type = validated_data.get('load_type', instance.load_type) instance.mission_no = validated_data.get('mission_no', instance.mission_no) instance.save() # 处理子项的增改 existing_sub_ids = PalletSubMission.objects.filter(pallet_mission=instance.pk).values_list('id', flat=True) updated_sub_ids = [] for sub_data in sub_data_list: sub_id = sub_data.get('id') if sub_id and sub_id in existing_sub_ids: # 更新已有子项(仅更新point,mission_id后续统一重置) sub_instance = PalletSubMission.objects.get(id=sub_id) sub_instance.point = sub_data.get('point', sub_instance.point) sub_instance.save() updated_sub_ids.append(sub_id) else: # 创建新子项(暂不设置mission_id) sub_instance = PalletSubMission.objects.create(pallet_mission=instance, point=sub_data.get('point')) updated_sub_ids.append(sub_instance.id) # 删除未在更新列表中的子项 PalletSubMission.objects.filter(pallet_mission=instance.pk).exclude(id__in=updated_sub_ids).delete() # 统一重置所有子项的mission_id,按id排序保证顺序稳定 sorted_subs = PalletSubMission.objects.filter(pallet_mission=instance.pk).order_by('id') for idx, sub_instance in enumerate(sorted_subs, start=1): sub_instance.mission_id = f"s{idx}" sub_instance.save() return instance
3. 可选:禁止手动传入mission_id
如果不需要用户手动设置mission_id,可以在子序列化器中将其设为只读字段,避免外部输入干扰:
class PalletSubSerializers(serializers.ModelSerializer): id = serializers.IntegerField(required=False) # 设为只读,完全由系统生成 mission_id = serializers.CharField(read_only=True) class Meta: model = PalletSubMission fields = '__all__'
注意事项
- 并发场景:如果有多进程同时操作同一父实例的子项,可能出现编号混乱。可以通过数据库锁避免,比如在查询子项时添加
select_for_update():sorted_subs = PalletSubMission.objects.filter(pallet_mission=instance.pk).select_for_update().order_by('id') - 排序逻辑:如果需要按用户传入的子项顺序编号,可直接遍历传入的
sub_data_list进行编号,而非查询数据库排序。
内容的提问来源于stack exchange,提问作者Wei Jian Tan
相关产品推荐
相关产品推荐

