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

Django模型迁移数据库时出现外键不匹配错误求助

Django迁移报错:foreign key mismatch - "user_auth_customer" referencing "user_auth_profile"

错误信息

return Database.Cursor.execute(self, query)
django.db.utils.OperationalError: foreign key mismatch - "user_auth_customer" referencing "user_auth_profile"

问题描述

迁移模型到数据库时出现上述外键不匹配错误,已检查ForeignKey但未发现明显问题,相关模型代码如下:

Customer模型

class Customer(AbstractUser):
    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = ['username']
    objects = UserManager()

    id = models.UUIDField(default=uuid.uuid4,  unique=True, primary_key=True, editable=False)

    profile = models.OneToOneField("Profile", related_name="user_profile",
                                   on_delete=models.CASCADE, null=True)
    first_name = models.CharField(max_length=50, null=True, blank=True)
    last_name = models.CharField(max_length=50, null=True, blank=True)
    username = models.CharField(max_length=30, null=True, blank=True)
    phone = models.CharField(max_length=10, default='', null=True,  blank=True)
    email = models.EmailField(validators=[validators.EmailValidator()],
                              unique=True)
    password = models.CharField(max_length=100, null=True, blank=True)
    date_created = models.DateTimeField(auto_now_add=True)

    @staticmethod
    def get_customer_by_email(email):
        try:
            return Customer.objects.get(email=email)
        except:
            return False

    def isExists(self):
        if Customer.objects.filter(email=self.email):
            return True

        return False

    class Meta:
        verbose_name = 'Customer'
        verbose_name_plural = 'Customers'

Profile模型

class Profile(models.Model):
    first_name = models.CharField(max_length=50, null=True, blank=True)
    last_name = models.CharField(max_length=50, null=True, blank=True)
    phone = models.CharField(max_length=10, default='', null=True, blank=True)
    email = models.EmailField(primary_key=True, unique=True, validators=[validators.EmailValidator()])
    password = models.CharField(max_length=100, null=True, blank=True)
    # Add a photo field
    owner = models.OneToOneField(Customer, related_name='profile_owner',
                                 on_delete=models.SET_NULL, null=True)
    username = models.CharField(max_length=30, null=True, blank=True,
                                validators=[UnicodeUsernameValidator()])
    date_created = models.DateTimeField(auto_now_add=True)

    class Meta:
        verbose_name = 'Profile'
        verbose_name_plural = 'Profiles'

解决方案

1. 移除AbstractUser重复定义的字段

Customer继承自AbstractUser,该基类已包含id、username、email、password、first_name、last_name等字段,重复定义会导致数据库表结构冲突,是引发错误的核心原因之一。修改后的Customer模型:

class Customer(AbstractUser):
    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = ['username']
    objects = UserManager()

    # 仅覆盖主键为UUID,其余字段使用AbstractUser自带的
    id = models.UUIDField(default=uuid.uuid4,  unique=True, primary_key=True, editable=False)
    phone = models.CharField(max_length=10, default='', null=True, blank=True)
    date_created = models.DateTimeField(auto_now_add=True)

    @staticmethod
    def get_customer_by_email(email):
        try:
            return Customer.objects.get(email=email)
        except:
            return False

    def isExists(self):
        return Customer.objects.filter(email=self.email).exists()

    class Meta:
        verbose_name = 'Customer'
        verbose_name_plural = 'Customers'

2. 解决循环OneToOne引用问题

当前两个模型互相定义OneToOneField,导致迁移时循环依赖,且完全没必要——Django的OneToOneField本身支持双向访问。只需保留其中一个模型的关联字段即可,推荐在Profile中保留关联:

修改Profile模型,同时不推荐用业务字段作为主键:

class Profile(models.Model):
    first_name = models.CharField(max_length=50, null=True, blank=True)
    last_name = models.CharField(max_length=50, null=True, blank=True)
    phone = models.CharField(max_length=10, default='', null=True, blank=True)
    # 改用UUID作为主键,避免用可变的email
    id = models.UUIDField(default=uuid.uuid4, unique=True, primary_key=True, editable=False)
    password = models.CharField(max_length=100, null=True, blank=True)
    # 关联Customer,related_name用于从Customer反向访问Profile
    owner = models.OneToOneField(Customer, related_name='profile',
                                 on_delete=models.SET_NULL, null=True)
    username = models.CharField(max_length=30, null=True, blank=True,
                                validators=[UnicodeUsernameValidator()])
    date_created = models.DateTimeField(auto_now_add=True)

    class Meta:
        verbose_name = 'Profile'
        verbose_name_plural = 'Profiles'

3. 清理旧迁移文件并重新生成

执行以下步骤重置迁移:

  • 删除app下的migrations文件夹中除__init__.py外的所有文件
  • 清空数据库(或手动删除对应的表)
  • 重新生成迁移文件:python manage.py makemigrations
  • 执行迁移:python manage.py migrate

关键说明

  • 避免用业务字段(如email)作为主键:主键应具备不可变性,email可能需要修改,会导致关联数据异常。
  • 继承AbstractUser时不要重复定义基类已有字段:容易引发字段类型、约束冲突,导致迁移失败。
  • 双向关联无需重复定义:通过related_name即可实现反向访问,减少循环依赖风险。

内容的提问来源于stack exchange,提问作者Vlad

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 13:48:22