我想定义两个模型Company
和Package
。每个Package
只有一个Company
,但Company
可以有多个Packages
。但是,每个公司只能有一个default_package
(可以为null)。我把它设置如下:
class Company(models.Model):
default_package = models.OneToOneField(
'dashboard.Package',
on_delete=models.SET_NULL,
blank=True,
null=True,
related_name='default_for_%(class)s')
class Package(models.Model):
company = models.ForeignKey(Company, on_delete=models.CASCADE)
其中dashboard
是应用标签。
为了简化这些模型的测试,我使用factory_boy
为他们创建了测试工厂,如下所示:
import factory
from .models import Company, Package
class CompanyFactory(factory.Factory):
class Meta:
model = Company
default_package = factory.SubFactory('dashboard.test_factories.PackageFactory')
class PackageFactory(factory.Factory):
class Meta:
model = Package
company = factory.SubFactory(CompanyFactory)
现在我正在尝试两项测试:
class DefaultPackageTest(TestCase):
def test_1(self):
company = Company.objects.create()
def test_2(self):
company = CompanyFactory()
第一个只创建一个Company
,而第二个尝试使用CompanyFactory
执行相同的操作。
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/factory/builder.py", line 233, in recurse
return builder.build(parent_step=self, force_sequence=force_sequence)
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/factory/builder.py", line 272, in build
step.resolve(pre)
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/factory/builder.py", line 221, in resolve
self.attributes[field_name] = getattr(self.stub, field_name)
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/factory/builder.py", line 355, in __getattr__
declaration = self.__declarations[name]
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/factory/builder.py", line 121, in __getitem__
context=self.contexts[key],
RecursionError: maximum recursion depth exceeded
----------------------------------------------------------------------
Ran 2 tests in 0.012s
FAILED (errors=1)
Destroying test database for alias 'default'...
知道为什么这不起作用?我相信我已经通过提供CompanyFactory
子工厂的完整路径来跟踪文档(http://factoryboy.readthedocs.io/en/latest/reference.html#circular-imports)。
更新
这个用例似乎是由Factory Boy的post-generation hooks解决的。看起来很有希望的是RelatedFactory
,其中给出了以下示例:
class CityFactory(factory.Factory):
class Meta:
model = City
capital_of = None
name = "Toronto"
class CountryFactory(factory.Factory):
class Meta:
model = Country
lang = 'fr'
capital_city = factory.RelatedFactory(CityFactory, 'capital_of', name="Paris")
在Python REPL中测试如下:
>>> france = CountryFactory()
>>> City.objects.get(capital_of=france)
<City: Paris>
但是,我很难将这个例子应用到我的情况中。 (文档中没有City
和Country
模型的文本说明或代码,这没有任何帮助。在我的情况下,似乎capital_city
类似于default_package
,所以我尝试将其变成RelatedFactory
,就像这样,
default_package = factory.RelatedFactory('dashboard.test_factories.PackageFactory')
但我仍然得到同样的错误。
答案 0 :(得分:0)
因此,我通过实现post_generation
函数实现了这一点:
class PackageFactory(factory.Factory):
class Meta:
model = Package
company = factory.SubFactory('dashboard.test_factories.CompanyFactory')
class CompanyFactory(factory.Factory):
class Meta:
model = Company
@factory.post_generation
def default_package(self, create, _, **__):
PackageFactory(company=self)
company=self
kwarg停止了递归,并使用所需的默认包属性正确创建了工厂。