Django基于模板模型批量生成关联实例的方案咨询
架构优化建议
- 现有三层模型拆分逻辑完全合理:
Exercise作为基础动作库(支持后续用户自定义动作)、ExerciseTemplate+WorkoutTemplate作为可复用的训练计划模板、ExerciseInstance+WorkoutInstance作为每次实际训练的记录,完全匹配健身追踪的业务流程,后续要加训练数据统计、模板分享、动作PR记录都不用重构,没必要简化层级。 - 修正现有模型的关联错误:当前
ExerciseTemplate和ExerciseInstance里的workout外键指向不存在的Workout模型,需要分别改成指向WorkoutTemplate和WorkoutInstance,否则关联查询会直接报错。 - 补全必要业务字段:
ExerciseInstance增加set_number字段区分同个动作的不同组;ExerciseTemplate增加推荐次数、推荐重量字段,生成实例时可以预填默认值;WorkoutInstance增加训练开始/结束时间、备注字段,让实例有实际业务意义。 - 修复多对多字段配置:原
WorkoutTemplate里的exercises多对多字段因为中间表外键名不是Django默认值,要么补充through_fields参数,要么直接删掉,通过反向关联查询模板下的动作更直观。
代码实现
修正后的核心模型定义
from django.db import models class Exercise(models.Model): name = models.CharField(max_length=128) description = models.TextField(null=True, blank=True) def __str__(self): return self.name class ExerciseTemplate(models.Model): # 修正外键:关联所属训练模板 workout_template = models.ForeignKey( 'WorkoutTemplate', on_delete=models.CASCADE, related_name="exercise_templates" ) exercise = models.ForeignKey( Exercise, on_delete=models.SET_NULL, null=True, blank=True ) recommended_sets = models.PositiveIntegerField(default=3) recommended_reps = models.PositiveIntegerField(default=12, null=True, blank=True) recommended_weight = models.PositiveIntegerField(null=True, blank=True) def __str__(self): return f"{self.exercise.name}(模板)" class ExerciseInstance(models.Model): exercise_template = models.ForeignKey( ExerciseTemplate, on_delete=models.SET_NULL, null=True, blank=True ) # 修正外键:关联所属实际训练记录 workout_instance = models.ForeignKey( 'WorkoutInstance', on_delete=models.CASCADE, related_name="exercise_instances" ) # 新增组数字段 set_number = models.PositiveIntegerField() weight = models.PositiveIntegerField(null=True, blank=True) reps = models.PositiveIntegerField(null=True, blank=True) is_completed = models.BooleanField(default=False) class WorkoutTemplate(models.Model): name = models.CharField(max_length=128) description = models.TextField(null=True, blank=True) date = models.DateTimeField(null=True, blank=True) # 原多对多字段可保留,需补充through_fields,或直接删除用反向关联查询 # exercises = models.ManyToManyField( # Exercise, # through=ExerciseTemplate, # through_fields=('workout_template', 'exercise') # ) def __str__(self): return self.name # 新增:从模板生成训练实例的自定义方法 def create_workout_instance(self, user_note=None): # 先创建训练记录主条目 workout_instance = WorkoutInstance.objects.create( workout_template=self, note=user_note ) # 准备批量生成的动作实例列表 instance_list = [] # 遍历模板下所有动作,按推荐组数生成对应实例 for ex_template in self.exercise_templates.all(): for set_idx in range(1, ex_template.recommended_sets + 1): instance_list.append( ExerciseInstance( exercise_template=ex_template, workout_instance=workout_instance, set_number=set_idx, # 预填模板推荐值,用户训练时可直接修改 reps=ex_template.recommended_reps, weight=ex_template.recommended_weight ) ) # 批量写入数据库,减少IO次数 ExerciseInstance.objects.bulk_create(instance_list) return workout_instance class WorkoutInstance(models.Model): workout_template = models.ForeignKey( WorkoutTemplate, on_delete=models.SET_NULL, null=True, blank=True ) # 新增训练记录基础字段 start_time = models.DateTimeField(auto_now_add=True) end_time = models.DateTimeField(null=True, blank=True) note = models.TextField(null=True, blank=True)
调用方式
- 从动作库创建训练模板时,直接新建
ExerciseTemplate实例绑定对应的Exercise和WorkoutTemplate即可,后续支持用户自定义Exercise时逻辑完全不用改。 - 从模板生成实际训练记录只需要一行调用:
# 例:获取id为1的练腿模板 leg_day_template = WorkoutTemplate.objects.get(id=1) # 一键生成预填好所有动作组的训练记录 today_workout = leg_day_template.create_workout_instance(user_note="今天减载")
- 生成后通过
today_workout.exercise_instances.all()即可拿到所有预生成的动作组,用户修改重量、次数、标记完成都直接操作对应的ExerciseInstance,不会修改原模板的配置。
内容的提问来源于stack exchange,提问作者lcardona89
相关产品推荐
相关产品推荐

