使用Django路由转储django-simple-history历史数据至其他数据库报错
解决django-simple-history跨库存储历史记录的关联表错误
问题描述
使用django-simple-history追踪模型变更记录,需求是将历史数据单独存储到另一数据库。配置Django数据库路由后,触发以下错误:
django.db.utils.OperationalError: (1824, "Failed to open the referenced table 'common_user'")
原因是common_user表仅存在于默认数据库,而历史数据库中无此表,但自动生成的历史模型包含指向该表的外键约束,导致读写历史记录时触发表不存在的错误。
解决方案
1. 自定义历史模型,禁用外键约束
django-simple-history生成的历史模型会继承原模型的外键字段,默认会创建数据库级外键约束。需要自定义历史模型,将所有外键的db_constraint设为False,避免历史数据库依赖默认库的关联表。
修改models.py:
from django.db import models from simple_history.models import HistoricalRecords, HistoricalModel from django.contrib.auth.models import User import uuid # 自定义历史模型 class HistoricalProfile(HistoricalModel): profile_type = models.ForeignKey('ProfileType', on_delete=models.PROTECT, db_constraint=False) user = models.ForeignKey(User, on_delete=models.CASCADE, db_constraint=False) org = models.ForeignKey('Org', null=True, on_delete=models.CASCADE, blank=True, related_name="historical_user_org", db_constraint=False) nickname = models.CharField(max_length=64, unique=True, blank=False, null=False) is_active = models.BooleanField(default=True) is_section_admin = models.BooleanField(default=False) is_organization_admin = models.BooleanField(default=False) is_organization_member = models.BooleanField(default=False) date_of_joining = models.DateField(auto_now_add=True, null=True, blank=True) referral = models.CharField(max_length=64, blank=True, null=True) referred = models.CharField(max_length=64, blank=False, null=False, unique=True, default=uuid.uuid4().hex) class Meta: proxy = True class Profile(models.Model): profile_type = models.ForeignKey('ProfileType', on_delete=models.PROTECT) user = models.ForeignKey(User, on_delete=models.CASCADE) nickname = models.CharField( max_length=64, unique=True, blank=False, null=False) org = models.ForeignKey( 'Org', null=True, on_delete=models.CASCADE, blank=True, related_name="user_org" ) address = models.ManyToManyField('Address', blank=True) is_active = models.BooleanField(default=True) is_section_admin = models.BooleanField(default=False) is_organization_admin = models.BooleanField(default=False) is_organization_member = models.BooleanField(default=False) date_of_joining = models.DateField(auto_now_add=True, null=True, blank=True) referral = models.CharField( max_length=64, blank=True, null=True) referred = models.CharField( max_length=64, blank=False, null=False, unique=True, default=uuid.uuid4().hex) # 指定自定义历史模型 history = HistoricalRecords(m2m_fields=(address, ), use_base_model_db=False, cascade_delete_history=True, history_model=HistoricalProfile)
2. 修正数据库路由逻辑
调整路由的allow_migrate方法,确保历史模型仅在history_db执行迁移,其他模型仅在default库迁移,同时处理django-simple-history的迁移提示。
修改routers.py:
class HistoryRouter: def db_for_read(self, model, **hints): if model._meta.model_name.startswith('historical'): return 'history_db' return 'default' def db_for_write(self, model, **hints): if model._meta.model_name.startswith('historical'): return 'history_db' return 'default' def allow_migrate(self, db, app_label, model_name=None, **hints): # 处理simple-history传递的模型提示 if hints.get('model') and hints['model']._meta.model_name.startswith('historical'): return db == 'history_db' # 匹配历史模型名称 if model_name and model_name.startswith('historical'): return db == 'history_db' # 非历史模型仅在默认库迁移 return db == 'default'
3. 重新执行数据库迁移
按顺序执行迁移,确保原模型和历史模型分别在对应数据库创建表:
- 迁移默认数据库:
python manage.py migrate --database default
- 迁移历史数据库:
python manage.py migrate --database history_db
若之前已在
history_db执行过错误迁移,需清空该库的迁移记录(删除django_migrations表中对应条目)后重新迁移。
关键说明
db_constraint=False仅禁用数据库级外键约束,Django ORM层面的关联查询仍可正常使用,且历史记录的外键值均来自原模型的合法数据,无需担心数据一致性问题。use_base_model_db=False确保历史记录的读写操作完全路由到history_db,符合单独存储历史数据的需求。
内容的提问来源于stack exchange,提问作者Reza Teymoori
相关产品推荐
相关产品推荐

