如何将Django多用户类型数据库模型合并为单一模型?
问题描述
我有一个早期的Django项目,是初学阶段创建的。目前项目运行正常,但出于代码重构需求,希望修改原有数据库模型——最初为每种用户类型分别创建了不同模型,存在大量冗余代码。
旧模型代码
class CustomUser(AbstractUser): user_type_data = ( ('admin', 'Admin'), ('instructor', 'Instructor'), ('student', 'Student'), ('renter', 'Renter'), ) user_type = models.CharField( max_length=20, choices=user_type_data, default=1) class Admin(models.Model): user = models.OneToOneField(CustomUser, on_delete=models.CASCADE) first_name = models.CharField(max_length=200) last_name = models.CharField(max_length=200) date_of_birth = models.DateField(null=True, blank=True) fiscal_code = models.CharField(max_length=50, null=True, blank=True) phone = models.CharField(max_length=50, null=True, blank=True) picture = models.ImageField( blank=True, null=True, default='default.png') address = models.CharField(max_length=100, blank=True, null=True) cap = models.CharField(max_length=10, blank=True, null=True) city = models.CharField(max_length=100, blank=True, null=True) province = models.CharField( max_length=100, choices=PROVINCE_CHOICES, blank=True, null=True) country = CountryField(blank=True, null=True) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) is_active = models.BooleanField(default=True) def __str__(self): return self.user.username class Meta: ordering = ['last_name'] class Instructor(models.Model): user = models.OneToOneField(CustomUser, on_delete=models.CASCADE) first_name = models.CharField(max_length=200) last_name = models.CharField(max_length=200) date_of_birth = models.DateField(null=True, blank=True) fiscal_code = models.CharField(max_length=50, null=True, blank=True) phone = models.CharField(max_length=50, null=True, blank=True) picture = models.ImageField( blank=True, null=True, default='default.png') address = models.CharField(max_length=100, null=True, blank=True) cap = models.CharField(max_length=10, null=True, blank=True) city = models.CharField(max_length=100, null=True, blank=True) province = models.CharField( max_length=100, choices=PROVINCE_CHOICES, blank=True, null=True) country = CountryField(blank=True, null=True) is_active = models.BooleanField(default=True) flight_wage = models.FloatField(null=True, blank=True) theory_wage = models.FloatField(null=True, blank=True) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) def __str__(self): return self.user.last_name + ' ' + self.user.first_name class Meta: ordering = ['last_name']
目标新模型
希望重构为单一模型,消除冗余:
class CustomUser(AbstractUser): user_type_data = ( ('admin', 'Admin'), ('instructor', 'Instructor'), ('student', 'Student'), ('renter', 'Renter'), ) user_type = models.CharField( max_length=20, choices=user_type_data, default=1) first_name = models.CharField(max_length=200) last_name = models.CharField(max_length=200) date_of_birth = models.DateField(null=True, blank=True) fiscal_code = models.CharField(max_length=50, null=True, blank=True) phone = models.CharField(max_length=50, null=True, blank=True) picture = models.ImageField( blank=True, null=True, default='default.png') address = models.CharField(max_length=100, null=True, blank=True) cap = models.CharField(max_length=10, null=True, blank=True) city = models.CharField(max_length=100, null=True, blank=True) province = models.CharField( max_length=100, choices=PROVINCE_CHOICES, blank=True, null=True) country = CountryField(blank=True, null=True) is_active = models.BooleanField(default=True) flight_wage = models.FloatField(null=True, blank=True) theory_wage = models.FloatField(null=True, blank=True) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) def __str__(self): return self.user.last_name + ' ' + self.user.first_name class Meta: ordering = ['last_name']
目前已有两位客户在使用项目,请问能否完成这种模型适配?
解决方案
完全可以完成这种重构,核心是分步处理数据迁移,避免丢失现有用户数据。以下是具体操作步骤:
1. 修正新模型的错误
AbstractUser本身已包含first_name、last_name、is_active字段,重复定义会导致数据库冲突;同时__str__方法中的self.user是错误的(当前类就是用户模型)。修正后的模型如下:
class CustomUser(AbstractUser): user_type_data = ( ('admin', 'Admin'), ('instructor', 'Instructor'), ('student', 'Student'), ('renter', 'Renter'), ) user_type = models.CharField( max_length=20, choices=user_type_data, default='student') # default需对应选项字符串,原default=1无效 date_of_birth = models.DateField(null=True, blank=True) fiscal_code = models.CharField(max_length=50, null=True, blank=True) phone = models.CharField(max_length=50, null=True, blank=True) picture = models.ImageField( blank=True, null=True, default='default.png') address = models.CharField(max_length=100, null=True, blank=True) cap = models.CharField(max_length=10, null=True, blank=True) city = models.CharField(max_length=100, null=True, blank=True) province = models.CharField( max_length=100, choices=PROVINCE_CHOICES, blank=True, null=True) country = CountryField(blank=True, null=True) flight_wage = models.FloatField(null=True, blank=True) theory_wage = models.FloatField(null=True, blank=True) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) def __str__(self): return f"{self.last_name} {self.first_name}" class Meta: ordering = ['last_name']
2. 备份现有数据
操作前务必备份数据库,用Django命令导出全量数据:
python manage.py dumpdata > backup.json
3. 为CustomUser添加新字段并执行初始迁移
保留旧模型,先给CustomUser添加新字段,生成并执行迁移:
python manage.py makemigrations python manage.py migrate
4. 编写自定义迁移脚本同步旧数据
创建空迁移文件,编写数据迁移逻辑,将旧模型数据同步到CustomUser:
- 生成空迁移:
python manage.py makemigrations --empty yourappname
- 编辑生成的迁移文件(替换为你的应用名和依赖迁移):
from django.db import migrations def migrate_admin_data(apps, schema_editor): Admin = apps.get_model('yourappname', 'Admin') CustomUser = apps.get_model('yourappname', 'CustomUser') for admin in Admin.objects.all(): user = admin.user user.first_name = admin.first_name user.last_name = admin.last_name user.date_of_birth = admin.date_of_birth user.fiscal_code = admin.fiscal_code user.phone = admin.phone user.picture = admin.picture user.address = admin.address user.cap = admin.cap user.city = admin.city user.province = admin.province user.country = admin.country user.is_active = admin.is_active user.created_at = admin.created_at user.updated_at = admin.updated_at user.user_type = 'admin' user.save() def migrate_instructor_data(apps, schema_editor): Instructor = apps.get_model('yourappname', 'Instructor') CustomUser = apps.get_model('yourappname', 'CustomUser') for instructor in Instructor.objects.all(): user = instructor.user user.first_name = instructor.first_name user.last_name = instructor.last_name user.date_of_birth = instructor.date_of_birth user.fiscal_code = instructor.fiscal_code user.phone = instructor.phone user.picture = instructor.picture user.address = instructor.address user.cap = instructor.cap user.city = instructor.city user.province = instructor.province user.country = instructor.country user.is_active = instructor.is_active user.flight_wage = instructor.flight_wage user.theory_wage = instructor.theory_wage user.created_at = instructor.created_at user.updated_at = instructor.updated_at user.user_type = 'instructor' user.save() class Migration(migrations.Migration): dependencies = [ ('yourappname', '000x_xxxxxx'), # 替换为上一步生成的迁移文件名 ] operations = [ migrations.RunPython(migrate_admin_data), migrations.RunPython(migrate_instructor_data), # 同理添加student、renter的数据迁移函数 ]
- 执行迁移:
python manage.py migrate
5. 验证数据同步结果
通过Django Shell检查数据是否正确:
python manage.py shell
执行验证代码:
from yourappname.models import CustomUser # 检查管理员数据 print(CustomUser.objects.filter(user_type='admin').values('first_name', 'last_name')) # 检查讲师数据 print(CustomUser.objects.filter(user_type='instructor').values('flight_wage', 'theory_wage'))
6. 删除旧模型并清理数据库
确认数据无误后,删除Admin、Instructor等旧模型,生成并执行清理迁移:
python manage.py makemigrations python manage.py migrate
7. 调整业务代码
替换项目中所有旧模型的引用:
- 视图、表单、模板中,原
request.user.admin改为直接使用request.user - 权限控制、查询逻辑同步调整为基于
CustomUser.user_type判断
注意事项
- 原
CustomUser的default=1无效,需改为选项对应的字符串值(如default='student') - 若有依赖旧模型的第三方库或自定义逻辑,需同步调整
- 迁移完成后再次备份数据库,确保数据安全
内容的提问来源于stack exchange,提问作者Giorgio Scarso
相关产品推荐
相关产品推荐

