Django连接AS400遗留库:关联实例不存在时添加含外键的模型实例
问题描述
我正在将Django项目连接到AS400系统中的遗留数据库,尝试合理配置模型并正确设置关联关系,但遇到了一个问题:当要添加含外键字段的模型实例时,被关联的模型实例可能尚未存在。
模型示例
class CLIENTPF(models.Model): id = models.BigAutoField(db_column='CLNTID', primary_key=True) fname = models.CharField(db_column='CLNTFNAME', max_length=30, unique=True, null=True) lname = models.CharField(db_column='CLNTLNAME', max_length=30) dob = models.CharField(db_column='CLNTDOB', max_length=10) class Meta: managed = False db_table = '"XPFLORIS"."CLIENTPF"' class MOVIEPF(models.Model): movie_name = models.CharField(db_column='MOVNAME', max_length=30, primary_key=True) star_fname = models.ForeignKey(CLIENTPF, to_field='fname', on_delete=models.PROTECT, db_column='STARACTF', max_length=30) star_lname = models.CharField(db_column='STARACTL', max_length=30) genere = models.CharField(db_column='GENERE', max_length=30) release_date = models.CharField(db_column='RELDATE', max_length=10) class Meta: managed = False db_table = '"XPFLORIS"."MOVIEPF"'
MOVIEPF中的star_fname字段是指向CLIENTPF中fname字段的外键。
报错场景
使用以下ORM代码添加MOVIEPF实例会报错,因为"Rowan"是字符串而非CLIENTPF模型实例:
new_movie = MOVIEPF(movie_name="Johnny English", star_fname="Rowan", star_lname="Atkinson", genere="Comedy", release_date="02/12/2003") new_movie.save()
正常运行的代码
而如下代码可以正常运行,但前提是CLIENTPF中已存在对应实例:
new_movie = MOVIEPF(movie_name="Johnny English", star_fname=CLIENTPF.objects.get(id=0), star_lname="Atkinson", genere="Comedy", release_date="02/12/2003") new_movie.save()
核心需求
即使CLIENTPF中不存在fname为"Rowan"的实例,也能向MOVIEPF中添加star_fname值为"Rowan"的实例,且后续当CLIENTPF添加对应fname的实例时,两者能自动关联。希望保留ForeignKey特性以提升ORM查询效率,不想放弃该字段属性。
解决方案
要实现这个需求,你可以利用Django ForeignKey的db_constraint=False参数,同时保留to_field指定关联字段的设置,具体调整如下:
调整MOVIEPF模型
修改star_fname字段的定义,添加db_constraint=False:
class MOVIEPF(models.Model): movie_name = models.CharField(db_column='MOVNAME', max_length=30, primary_key=True) star_fname = models.ForeignKey( CLIENTPF, to_field='fname', on_delete=models.PROTECT, db_column='STARACTF', max_length=30, db_constraint=False, # 关键:关闭数据库层面的外键约束 blank=True, null=True # 可选:如果允许字段为空则添加,否则保持原设置 ) star_lname = models.CharField(db_column='STARACTL', max_length=30) genere = models.CharField(db_column='GENERE', max_length=30) release_date = models.CharField(db_column='RELDATE', max_length=10) class Meta: managed = False db_table = '"XPFLORIS"."MOVIEPF"'
保存实例的正确方式
现在你可以通过两种方式保存实例:
- 直接传入关联字段的值:使用
_id后缀的字段名来直接赋值字符串,Django ORM会识别并保存对应的值,无需提前存在CLIENTPF实例:
new_movie = MOVIEPF( movie_name="Johnny English", star_fname_id="Rowan", # 用字段名+_id直接赋值关联字段的实际值 star_lname="Atkinson", genere="Comedy", release_date="02/12/2003" ) new_movie.save()
- 传入模型实例:当CLIENTPF中存在对应实例时,依然可以传入实例,不影响原有的ORM关联查询功能:
client = CLIENTPF.objects.get(fname="Rowan") new_movie = MOVIEPF( movie_name="Johnny English", star_fname=client, star_lname="Atkinson", genere="Comedy", release_date="02/12/2003" ) new_movie.save()
后续关联效果
当后续在CLIENTPF中添加fname为"Rowan"的实例后,你依然可以使用Django的ORM关联查询,比如:
# 查询所有star_fname关联到Rowan的电影 movies = MOVIEPF.objects.filter(star_fname__fname="Rowan") # 或者通过实例反向查询 client = CLIENTPF.objects.get(fname="Rowan") movies = client.moviepf_set.all()
注意事项
db_constraint=False只是关闭了数据库层面的外键约束,但Django ORM层面依然会维护关联关系逻辑,你依然能享受到ORM关联查询的便利。- 由于关闭了数据库约束,需要自行保证数据一致性,避免出现永远无法关联的无效值(比如
star_fname的值永远不会在CLIENTPF中出现的情况)。 - 因为模型设置了
managed=False,Django不会修改AS400数据库的原有结构,不用担心破坏遗留数据库的现有配置。
内容的提问来源于stack exchange,提问作者Floris Kruger
相关产品推荐
相关产品推荐

