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

使用factory-boy创建Django模型时触发主键重复IntegrityError

Django测试中factory-boy生成UUID主键重复导致IntegrityError问题

运行测试时出现以下错误:

django.db.utils.IntegrityError: duplicate key value violates unique constraint "shops_shop_pkey"
DETAIL:  Key (id)=(cd84e095-7d1a-41fc-adb3-7f5db5e07d50) already exists.
========================================================================== short test summary info ===========================================================================
FAILED tests/models/test_shop.py::test_shop_creation - django.db.utils.IntegrityError: duplicate key value violates unique constraint "shops_shop_pkey"

我用factory-boy创建了ShopModelFactory工厂类,用来生成Django的Shop模型,字段用faker填充,之后用pytest编写测试。数据库是空的,测试前也删除了所有记录,后台管理面板能正常创建记录,但测试始终报主键重复错误。

我的Shop模型

class Shop(TimedBaseModel):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    first_name = models.CharField("First Name", max_length=20)
    last_name = models.CharField("Last Name", max_length=20)
    email = models.EmailField("Email", unique=True)
    phone = PhoneNumberField(
        "Phone Number",
        unique=True,
    )
    shop_name = models.CharField("Shop Name", max_length=50, unique=True)
    shop_url = models.URLField("Shop URL", unique=True)
    logo = models.ImageField("Logo", upload_to="logos/", blank=True, null=True)
    banner = models.ImageField(
        "Banner",
        upload_to="banners/",
        blank=True,
        null=True,
    )
    street1 = models.CharField("Street 1", max_length=50)
    street2 = models.CharField("Street 2", max_length=50, blank=True)
    city = models.CharField("Town / City", max_length=100)
    zip_code = models.CharField("Zip Code", max_length=10)
    country = CountryField("Country", blank_label="(select country)")
    state = models.CharField("State", max_length=100, blank=True, default="")
    password = models.CharField("Password")
    confirm_password = models.CharField("Confirm Password")
    is_visible = models.BooleanField(
        verbose_name="Is the shop visible in the shops catalog",
        default=True,
    )

    class Meta:
        verbose_name = "Խանութ"
        verbose_name_plural = "Խանութներ"
        app_label = "shops"

    def __str__(self):
        return self.shop_name

    def save(self, *args, **kwargs):
        instance = super().save(*args, **kwargs)
        if self.logo:
            instance = super().save(*args, **kwargs)
            logo_path = self.logo.path

            logo = Image.open(logo_path)
            logo_size = os.path.getsize(logo_path)  # in bytes
            thirty_mb = 30 * 1024 * 1024

            while logo_size > thirty_mb:
                logo = logo.convert("RGB")
                logo.save(
                    logo_path,
                    quality=60,
                    optimize=True,
                    format="JPEG",
                )
        return instance

我的工厂类

import factory
from factory.django import DjangoModelFactory, ImageField
from api.v1.apps.shops.models.shops import Shop


class ShopModelFactory(DjangoModelFactory):
    class Meta:
        model = Shop

    
    first_name = factory.Faker('first_name')
    last_name = factory.Faker('last_name')
    email = factory.Faker('email')
    phone = factory.Faker('phone_number')
    shop_name = factory.Faker('company')
    shop_url = factory.Faker('url')
    logo = ImageField(color='blue')
    banner = ImageField(color='red')
    street1 = factory.Faker('street_address')
    street2 = factory.Faker('secondary_address')
    city = factory.Faker('city')
    zip_code = factory.Faker('postcode')
    country = factory.Faker('country_code')
    state = factory.Faker('state')
    password = factory.Faker('password')
    confirm_password = password
    is_visible = factory.Faker('boolean')

我的测试代码

@pytest.mark.django_db(transaction=True)
def test_shop_creation():
    Shop.objects.all().delete()  # 生成实例前已删除所有数据库记录
    shop = ShopModelFactory()
    assert Shop.objects.count() == 1
    assert Shop.objects.first() == shop
    assert shop.first_name
    assert shop.last_name
    assert shop.email
    assert shop.phone
    assert shop.shop_name
    assert shop.shop_url
    assert shop.street1
    assert shop.city
    assert shop.zip_code
    assert shop.country
    assert shop.state
    assert shop.password
    assert shop.confirm_password
    assert isinstance(shop.is_visible, bool)

问题原因及解决办法

  1. save方法重复调用导致主键冲突
    你的Shop模型save方法中,处理logo时额外调用了一次super().save(),这会导致同一个实例被保存两次:第一次保存时已生成UUID主键,第二次保存时尝试用相同主键再次插入,触发唯一约束错误。
    修改后的save方法:

    def save(self, *args, **kwargs):
        instance = super().save(*args, **kwargs)
        if self.logo:
            logo_path = self.logo.path
    
            logo = Image.open(logo_path)
            logo_size = os.path.getsize(logo_path)  # in bytes
            thirty_mb = 30 * 1024 * 1024
    
            while logo_size > thirty_mb:
                logo = logo.convert("RGB")
                logo.save(
                    logo_path,
                    quality=60,
                    optimize=True,
                    format="JPEG",
                )
                # 重新获取压缩后的文件大小,避免无限循环
                logo_size = os.path.getsize(logo_path)
        return instance
    
  2. 显式指定工厂类的UUID生成逻辑
    虽然模型设置了default=uuid.uuid4,但factory-boy可能不会正确触发默认值生成。在工厂类中显式添加id字段,确保每次生成新实例时生成唯一UUID:

    import uuid
    # 在ShopModelFactory中添加
    id = factory.LazyFunction(uuid.uuid4)
    
  3. 调整测试事务模式
    使用@pytest.mark.django_db(transaction=True)会让测试在事务中运行,删除操作的效果可能受事务隔离影响。可以改用transaction=False,或使用pytest-django的默认django_db标记(默认不使用事务)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 04:41:18