在 FactoryBoy 中,如何使用空的多对多成员字段设置我的工厂?
In FactoryBoy, how do I setup my factory with an empty many-to-many member field?
我在 Python 3.8 中使用 Django 3。我有以下型号 ...
class Coop(models.Model):
objects = CoopManager()
name = models.CharField(max_length=250, null=False)
types = models.ManyToManyField(CoopType, blank=False)
addresses = models.ManyToManyField(Address)
enabled = models.BooleanField(default=True, null=False)
phone = models.ForeignKey(ContactMethod, on_delete=models.CASCADE, null=True, related_name='contact_phone')
email = models.ForeignKey(ContactMethod, on_delete=models.CASCADE, null=True, related_name='contact_email')
web_site = models.TextField()
我创建了以下工厂(使用 Factory boy)来尝试在测试中创建模型...
class CoopFactory(factory.DjangoModelFactory):
"""
Define Coop Factory
"""
class Meta:
model = Coop
name = "test model"
enabled = True
phone = factory.SubFactory(PhoneContactMethodFactory)
email = factory.SubFactory(EmailContactMethodFactory)
web_site = "http://www.hello.com"
@factory.post_generation
def addresses(self, create, extracted, **kwargs):
if not create:
# Simple build, do nothing.
return
if extracted:
# A list of types were passed in, use them
for address in extracted:
self.addresses.add(address)
else:
address = AddressFactory()
self.addresses.add( address )
@factory.post_generation
def types(self, create, extracted, **kwargs):
if not create:
# Simple build, do nothing.
return
if extracted:
# A list of types were passed in, use them
for type in extracted:
self.types.add(type)
else:
print("Creating type ...\n")
type = CoopTypeFactory()
self.types.add( type )
但是我在创建具有空的多对多字段(类型)的工厂时遇到了问题。我尝试了以下
@pytest.mark.django_db
def test_coop_create_with_no_types(self):
""" Test customer model """ # create customer model instance
coop = CoopFactory.create(types=[])
print("size: ", coop.types.all().count())
self.assertIsNotNone(coop)
self.assertIsNotNone( coop.id )
但 types.all().count()
的值始终等于 1。如何正确设置具有空的多对多字段的工厂?
编辑:针对给出的答案,传递工厂使用的成员字段的正确方法是什么?我试过了
@pytest.mark.django_db
def test_coop_create_with_existing_type(self):
""" Test customer model """ # create customer model instance
coop_from_factory = CoopFactory()
self.assertIsNotNone(coop_from_factory)
coop_types = coop_from_factory.types
coop = CoopFactory.create(types=[coop_types.all().first()], addresses=coop_from_factory.addresses.all())
self.assertIsNotNone(coop)
但出现此错误,对于行“for _ in range(extracted):”行 ...
Traceback (most recent call last):
File "/Users/davea/Documents/workspace/chicommons/maps/web/tests/test_models.py", line 48, in test_coop_create_with_existing_type
coop = CoopFactory.create(types=coop_types, addresses=coop_from_factory.addresses.all())
File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/factory/base.py", line 564, in create
return cls._generate(enums.CREATE_STRATEGY, kwargs)
File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/factory/django.py", line 141, in _generate
return super(DjangoModelFactory, cls)._generate(strategy, params)
File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/factory/base.py", line 501, in _generate
return step.build()
File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/factory/builder.py", line 296, in build
postgen_results[declaration_name] = declaration.declaration.call(
File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/factory/declarations.py", line 622, in call
return self.function(
File "/Users/davea/Documents/workspace/chicommons/maps/web/tests/factories.py", line 128, in types
for _ in range(extracted):
TypeError: 'ManyRelatedManager' object cannot be interpreted as an integer
跳过
else:
print("Creating type ...\n")
type = CoopTypeFactory()
self.types.add( type )
它总是会默认创建 CoopType。
文档中可能不清楚的一件事是 @factory.post_generation 挂钩总是被调用。这意味着将始终调用示例代码中所有 post_generation 挂钩中的 else 语句。
更多信息:Simple Many-to-many relationship
如果我想直接创建默认值,我经常使用的模式是
向工厂添加一个函数,在此示例中,将转换为:
@factory.post_generation
def create_types(self, create, extracted, **kwargs):
if not create:
# Simple build, do nothing.
return
if extracted:
for _ in range(extracted):
self.types.add(CoopTypeFactory())
允许使用 CoopFactory(create_types=3)
。
这是我的完整示例:
@factory.post_generation
def types(self, create, extracted, **kwargs):
if not create:
# Simple build, do nothing.
return
if extracted:
# A list of types were passed in, use them
for type in extracted:
self.types.add(type)
# Removed this because it always creates 1 CoopType as default and
# it may not be the desired behaviour for all tests.
# else:
# print("Creating type ...\n")
# type = CoopTypeFactory()
# self.types.add( type )
# Adding this function to have a simple way of just adding default CoopTypes
@factory.post_generation
def create_types(self, create, extracted, **kwargs):
if not create:
# Simple build, do nothing.
return
if extracted: # This must be an integer
for _ in range(extracted):
self.types.add(CoopTypeFactory())
这给出了可选的用法:
CoopFactory(create_types=3)
将调用 create_types 并将 int 3 放入提取的参数中,并创建 3 个默认 CoopTypes。 (这样可以简单使用)
CoopFactory(types=[CoopTypeFactory()])
将调用类型并将 1 个 CoopType 的列表放入提取的参数中。 (如果这些对象需要一些特定值,这可以更好地控制 CoopTypes 的创建方式)
解决方法是将 if extracted
更改为 if extracted is not None
。
说明
在 Python 中,空列表是假的1 但不是 None
.
coop = CoopFactory.create(types=[])
空列表[]
作为参数extracted
.
传递给post-generation hooktypes
@factory.post_generation
def types(self, create, extracted, **kwargs):
if not create:
# Simple build, do nothing.
return
if extracted:
# A list of types were passed in, use them
for type in extracted:
self.types.add(type)
else:
print("Creating type ...\n")
type = CoopTypeFactory()
self.types.add( type )
因为if extracted
是一个真值测试1,虚假的空列表落在创建type
的else
块.所以,types.all().count()
的值等于1。
1 https://docs.python.org/3/library/stdtypes.html#truth-value-testing
我在 Python 3.8 中使用 Django 3。我有以下型号 ...
class Coop(models.Model):
objects = CoopManager()
name = models.CharField(max_length=250, null=False)
types = models.ManyToManyField(CoopType, blank=False)
addresses = models.ManyToManyField(Address)
enabled = models.BooleanField(default=True, null=False)
phone = models.ForeignKey(ContactMethod, on_delete=models.CASCADE, null=True, related_name='contact_phone')
email = models.ForeignKey(ContactMethod, on_delete=models.CASCADE, null=True, related_name='contact_email')
web_site = models.TextField()
我创建了以下工厂(使用 Factory boy)来尝试在测试中创建模型...
class CoopFactory(factory.DjangoModelFactory):
"""
Define Coop Factory
"""
class Meta:
model = Coop
name = "test model"
enabled = True
phone = factory.SubFactory(PhoneContactMethodFactory)
email = factory.SubFactory(EmailContactMethodFactory)
web_site = "http://www.hello.com"
@factory.post_generation
def addresses(self, create, extracted, **kwargs):
if not create:
# Simple build, do nothing.
return
if extracted:
# A list of types were passed in, use them
for address in extracted:
self.addresses.add(address)
else:
address = AddressFactory()
self.addresses.add( address )
@factory.post_generation
def types(self, create, extracted, **kwargs):
if not create:
# Simple build, do nothing.
return
if extracted:
# A list of types were passed in, use them
for type in extracted:
self.types.add(type)
else:
print("Creating type ...\n")
type = CoopTypeFactory()
self.types.add( type )
但是我在创建具有空的多对多字段(类型)的工厂时遇到了问题。我尝试了以下
@pytest.mark.django_db
def test_coop_create_with_no_types(self):
""" Test customer model """ # create customer model instance
coop = CoopFactory.create(types=[])
print("size: ", coop.types.all().count())
self.assertIsNotNone(coop)
self.assertIsNotNone( coop.id )
但 types.all().count()
的值始终等于 1。如何正确设置具有空的多对多字段的工厂?
编辑:针对给出的答案,传递工厂使用的成员字段的正确方法是什么?我试过了
@pytest.mark.django_db
def test_coop_create_with_existing_type(self):
""" Test customer model """ # create customer model instance
coop_from_factory = CoopFactory()
self.assertIsNotNone(coop_from_factory)
coop_types = coop_from_factory.types
coop = CoopFactory.create(types=[coop_types.all().first()], addresses=coop_from_factory.addresses.all())
self.assertIsNotNone(coop)
但出现此错误,对于行“for _ in range(extracted):”行 ...
Traceback (most recent call last):
File "/Users/davea/Documents/workspace/chicommons/maps/web/tests/test_models.py", line 48, in test_coop_create_with_existing_type
coop = CoopFactory.create(types=coop_types, addresses=coop_from_factory.addresses.all())
File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/factory/base.py", line 564, in create
return cls._generate(enums.CREATE_STRATEGY, kwargs)
File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/factory/django.py", line 141, in _generate
return super(DjangoModelFactory, cls)._generate(strategy, params)
File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/factory/base.py", line 501, in _generate
return step.build()
File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/factory/builder.py", line 296, in build
postgen_results[declaration_name] = declaration.declaration.call(
File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/factory/declarations.py", line 622, in call
return self.function(
File "/Users/davea/Documents/workspace/chicommons/maps/web/tests/factories.py", line 128, in types
for _ in range(extracted):
TypeError: 'ManyRelatedManager' object cannot be interpreted as an integer
跳过
else:
print("Creating type ...\n")
type = CoopTypeFactory()
self.types.add( type )
它总是会默认创建 CoopType。
文档中可能不清楚的一件事是 @factory.post_generation 挂钩总是被调用。这意味着将始终调用示例代码中所有 post_generation 挂钩中的 else 语句。
更多信息:Simple Many-to-many relationship
如果我想直接创建默认值,我经常使用的模式是 向工厂添加一个函数,在此示例中,将转换为:
@factory.post_generation
def create_types(self, create, extracted, **kwargs):
if not create:
# Simple build, do nothing.
return
if extracted:
for _ in range(extracted):
self.types.add(CoopTypeFactory())
允许使用 CoopFactory(create_types=3)
。
这是我的完整示例:
@factory.post_generation
def types(self, create, extracted, **kwargs):
if not create:
# Simple build, do nothing.
return
if extracted:
# A list of types were passed in, use them
for type in extracted:
self.types.add(type)
# Removed this because it always creates 1 CoopType as default and
# it may not be the desired behaviour for all tests.
# else:
# print("Creating type ...\n")
# type = CoopTypeFactory()
# self.types.add( type )
# Adding this function to have a simple way of just adding default CoopTypes
@factory.post_generation
def create_types(self, create, extracted, **kwargs):
if not create:
# Simple build, do nothing.
return
if extracted: # This must be an integer
for _ in range(extracted):
self.types.add(CoopTypeFactory())
这给出了可选的用法:
CoopFactory(create_types=3)
将调用 create_types 并将 int 3 放入提取的参数中,并创建 3 个默认 CoopTypes。 (这样可以简单使用)
CoopFactory(types=[CoopTypeFactory()])
将调用类型并将 1 个 CoopType 的列表放入提取的参数中。 (如果这些对象需要一些特定值,这可以更好地控制 CoopTypes 的创建方式)
解决方法是将 if extracted
更改为 if extracted is not None
。
说明
在 Python 中,空列表是假的1 但不是 None
.
coop = CoopFactory.create(types=[])
空列表[]
作为参数extracted
.
types
@factory.post_generation def types(self, create, extracted, **kwargs): if not create: # Simple build, do nothing. return if extracted: # A list of types were passed in, use them for type in extracted: self.types.add(type) else: print("Creating type ...\n") type = CoopTypeFactory() self.types.add( type )
因为if extracted
是一个真值测试1,虚假的空列表落在创建type
的else
块.所以,types.all().count()
的值等于1。
1 https://docs.python.org/3/library/stdtypes.html#truth-value-testing