You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将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:

  1. 生成空迁移:
python manage.py makemigrations --empty yourappname
  1. 编辑生成的迁移文件(替换为你的应用名和依赖迁移):
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的数据迁移函数
    ]
  1. 执行迁移:
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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 04:30:43