FactoryBoy / Django - OneToOneField 重复键错误
FactoryBoy / Django - OneToOneField duplicate key error
我正在为具有多个应用程序的大型 Django 应用程序编写测试。作为这个过程的一部分,我逐渐为 Django 项目中不同应用程序的所有模型创建工厂。
但是,我 运行 对 FactoryBoy
有一些令人困惑的行为
我们的应用程序使用 Profiles
,它链接到具有 OneToOneField
的默认 auth.models.User
模型
class Profile(models.Model):
user = models.OneToOneField(User)
birth_date = models.DateField(
verbose_name=_("Date of Birth"), null=True, blank=True)
( ... )
我为这两个模型创建了以下工厂:
@factory.django.mute_signals(post_save)
class ProfileFactory(factory.django.DjangoModelFactory):
class Meta:
model = profile_models.Profile
user = factory.SubFactory('yuza.factories.UserFactory')
birth_date = factory.Faker('date_of_birth')
street = factory.Faker('street_name')
house_number = factory.Faker('building_number')
city = factory.Faker('city')
country = factory.Faker('country')
avatar_file = factory.django.ImageField(color='blue')
tenant = factory.SubFactory(TenantFactory)
@factory.django.mute_signals(post_save)
class UserFactory(factory.django.DjangoModelFactory):
class Meta:
model = auth_models.User
username = factory.Faker('user_name')
first_name = factory.Faker('first_name')
last_name = factory.Faker('last_name')
email = factory.Faker('email')
is_staff = False
is_superuser = False
is_active = True
last_login = factory.LazyFunction(timezone.now)
profile = factory.RelatedFactory(ProfileFactory, 'user')
然后我 运行 进行以下测试:
class TestUser(TestCase):
def test_init(self):
""" Verify that the factory is able to initialize """
user = UserFactory()
self.assertTrue(user)
self.assertTrue(user.profile)
self.assertTrue(user.profile.tenant)
class TestProfile(TestCase):
def test_init(self):
""" Verify that the factory is able to initialize """
profile = ProfileFactory()
self.assertTrue(profile)
TestUser
中的所有测试都通过了,但是 TestProfile
在工厂初始化 (profile = ProfileFactory()
) 时失败并引发了以下错误:
IntegrityError: duplicate key value violates unique constraint "yuza_profile_user_id_key"
DETAIL: Key (user_id)=(1) already exists.
我不清楚为什么重复的用户已经存在,(应该只有一个调用来创建一个对吧?,特别是因为任何干扰信号都已被禁用)
我的代码基于 FactoryBoy 文档中的 the example,该文档还处理了通过 OneToOneKey
连接的用户/配置文件
有谁知道我做错了什么吗?
更新
根据 B运行o 和 ivissani 的建议,我将 ProfileFactory
中的 user
行更改为
user = factory.SubFactory('yuza.factories.UserFactory', profile=None)
现在上述所有测试都成功通过了!
不过我还是运行陷入了下面的问题——当其他工厂调用UserFactory
IntegrityError: duplicate key value violates unique constraint "yuza_profile_user_id_key"
DETAIL: Key (user_id)=(1) already exists.
还是returns。
我在下面包含了一个工厂调用 UserFactory
的示例,但它发生在每个具有 user
字段的工厂中。
class InvoiceFactory(factory.django.DjangoModelFactory):
class Meta:
model = Invoice
user = factory.SubFactory(UserFactory)
invoice_id = None
title = factory.Faker('catch_phrase')
price_paid = factory.LazyFunction(lambda: Decimal(0))
tax_rate = factory.LazyFunction(lambda: Decimal(1.21))
invoice_datetime = factory.LazyFunction(timezone.now)
将 InvoiceFactory
上的 user
字段更改为
user = factory.SubFactory(UserFactory, profile=None)
帮助它通过了一些测试,但最终 运行 遇到了麻烦,因为它不再有与之关联的配置文件。
奇怪的是以下(在工厂之前声明用户)确实有效:
self.user = UserFactory()
invoice_factory = InvoiceFactory(user=self.user)
我不清楚为什么我仍然保持 运行ning 进入 IntegrityError
这里,调用 UserFactory()
现在可以正常工作了。
我认为这是因为您的 ProfileFactory
使用 UserFactory
创建了一个 User
实例,而 UserFactory
本身尝试使用 [=14= 创建一个新的 Profile
实例].
你需要按照描述打破这个循环in the documentation you link to:
# We pass in profile=None to prevent UserFactory from
# creating another profile (this disables the RelatedFactory)
user = factory.SubFactory('yuza.factories.UserFactory', profile=None)
如果这对您不起作用并且您需要更高级的处理,那么我建议实施 post_generation
hook,您可以在其中执行更高级的操作。
编辑:
另一种选择是告诉 Factory Boy 如果 User
已经有 Profile
,则不要使用 django_get_or_create
option:
重新创建 Profile
@factory.django.mute_signals(post_save)
class ProfileFactory(factory.django.DjangoModelFactory):
class Meta:
model = profile_models.Profile
django_get_or_create = ('user',)
如果这样做,您也许可以删除我之前建议的 profile=None
。
编辑 2:
这也可能有帮助,使用 post_generation
钩子更改 UserFactory.profile
:
@factory.django.mute_signals(post_save)
class UserFactory(factory.django.DjangoModelFactory):
class Meta:
model = auth_models.User
...
# Change profile to a post_generation hook
@factory.post_generation
def profile(self, create, extracted):
if not create:
return
if extracted is None:
ProfileFactory(user=self)
编辑 3
我刚刚意识到你的 UserFactory
中的 username
字段与 factroy boy 文档中的不同,它是 Django 中的 unique
。我想知道这是否不会导致某些旧实例被重用,因为用户名相同。
您可能想尝试将此字段更改为工厂中的序列:
@factory.django.mute_signals(post_save)
class UserFactory(factory.django.DjangoModelFactory):
class Meta:
model = auth_models.User
# Change to sequence to avoid duplicates
username = factory.Sequence(lambda n: "user_%d" % n)
我正在为具有多个应用程序的大型 Django 应用程序编写测试。作为这个过程的一部分,我逐渐为 Django 项目中不同应用程序的所有模型创建工厂。
但是,我 运行 对 FactoryBoy
有一些令人困惑的行为我们的应用程序使用 Profiles
,它链接到具有 OneToOneField
auth.models.User
模型
class Profile(models.Model):
user = models.OneToOneField(User)
birth_date = models.DateField(
verbose_name=_("Date of Birth"), null=True, blank=True)
( ... )
我为这两个模型创建了以下工厂:
@factory.django.mute_signals(post_save)
class ProfileFactory(factory.django.DjangoModelFactory):
class Meta:
model = profile_models.Profile
user = factory.SubFactory('yuza.factories.UserFactory')
birth_date = factory.Faker('date_of_birth')
street = factory.Faker('street_name')
house_number = factory.Faker('building_number')
city = factory.Faker('city')
country = factory.Faker('country')
avatar_file = factory.django.ImageField(color='blue')
tenant = factory.SubFactory(TenantFactory)
@factory.django.mute_signals(post_save)
class UserFactory(factory.django.DjangoModelFactory):
class Meta:
model = auth_models.User
username = factory.Faker('user_name')
first_name = factory.Faker('first_name')
last_name = factory.Faker('last_name')
email = factory.Faker('email')
is_staff = False
is_superuser = False
is_active = True
last_login = factory.LazyFunction(timezone.now)
profile = factory.RelatedFactory(ProfileFactory, 'user')
然后我 运行 进行以下测试:
class TestUser(TestCase):
def test_init(self):
""" Verify that the factory is able to initialize """
user = UserFactory()
self.assertTrue(user)
self.assertTrue(user.profile)
self.assertTrue(user.profile.tenant)
class TestProfile(TestCase):
def test_init(self):
""" Verify that the factory is able to initialize """
profile = ProfileFactory()
self.assertTrue(profile)
TestUser
中的所有测试都通过了,但是 TestProfile
在工厂初始化 (profile = ProfileFactory()
) 时失败并引发了以下错误:
IntegrityError: duplicate key value violates unique constraint "yuza_profile_user_id_key"
DETAIL: Key (user_id)=(1) already exists.
我不清楚为什么重复的用户已经存在,(应该只有一个调用来创建一个对吧?,特别是因为任何干扰信号都已被禁用)
我的代码基于 FactoryBoy 文档中的 the example,该文档还处理了通过 OneToOneKey
有谁知道我做错了什么吗?
更新
根据 B运行o 和 ivissani 的建议,我将 ProfileFactory
中的 user
行更改为
user = factory.SubFactory('yuza.factories.UserFactory', profile=None)
现在上述所有测试都成功通过了!
不过我还是运行陷入了下面的问题——当其他工厂调用UserFactory
IntegrityError: duplicate key value violates unique constraint "yuza_profile_user_id_key"
DETAIL: Key (user_id)=(1) already exists.
还是returns。
我在下面包含了一个工厂调用 UserFactory
的示例,但它发生在每个具有 user
字段的工厂中。
class InvoiceFactory(factory.django.DjangoModelFactory):
class Meta:
model = Invoice
user = factory.SubFactory(UserFactory)
invoice_id = None
title = factory.Faker('catch_phrase')
price_paid = factory.LazyFunction(lambda: Decimal(0))
tax_rate = factory.LazyFunction(lambda: Decimal(1.21))
invoice_datetime = factory.LazyFunction(timezone.now)
将 InvoiceFactory
上的 user
字段更改为
user = factory.SubFactory(UserFactory, profile=None)
帮助它通过了一些测试,但最终 运行 遇到了麻烦,因为它不再有与之关联的配置文件。
奇怪的是以下(在工厂之前声明用户)确实有效:
self.user = UserFactory()
invoice_factory = InvoiceFactory(user=self.user)
我不清楚为什么我仍然保持 运行ning 进入 IntegrityError
这里,调用 UserFactory()
现在可以正常工作了。
我认为这是因为您的 ProfileFactory
使用 UserFactory
创建了一个 User
实例,而 UserFactory
本身尝试使用 [=14= 创建一个新的 Profile
实例].
你需要按照描述打破这个循环in the documentation you link to:
# We pass in profile=None to prevent UserFactory from
# creating another profile (this disables the RelatedFactory)
user = factory.SubFactory('yuza.factories.UserFactory', profile=None)
如果这对您不起作用并且您需要更高级的处理,那么我建议实施 post_generation
hook,您可以在其中执行更高级的操作。
编辑:
另一种选择是告诉 Factory Boy 如果 User
已经有 Profile
,则不要使用 django_get_or_create
option:
Profile
@factory.django.mute_signals(post_save)
class ProfileFactory(factory.django.DjangoModelFactory):
class Meta:
model = profile_models.Profile
django_get_or_create = ('user',)
如果这样做,您也许可以删除我之前建议的 profile=None
。
编辑 2:
这也可能有帮助,使用 post_generation
钩子更改 UserFactory.profile
:
@factory.django.mute_signals(post_save)
class UserFactory(factory.django.DjangoModelFactory):
class Meta:
model = auth_models.User
...
# Change profile to a post_generation hook
@factory.post_generation
def profile(self, create, extracted):
if not create:
return
if extracted is None:
ProfileFactory(user=self)
编辑 3
我刚刚意识到你的 UserFactory
中的 username
字段与 factroy boy 文档中的不同,它是 Django 中的 unique
。我想知道这是否不会导致某些旧实例被重用,因为用户名相同。
您可能想尝试将此字段更改为工厂中的序列:
@factory.django.mute_signals(post_save)
class UserFactory(factory.django.DjangoModelFactory):
class Meta:
model = auth_models.User
# Change to sequence to avoid duplicates
username = factory.Sequence(lambda n: "user_%d" % n)