我正在尝试使用Factory Boy链接两个Django模型,但我找不到这个问题的简单解决方案。这些是与相应工厂的模型:
class Currency(models.Model):
id = models.CharField(max_length=3, primary_key=True)
class ConversionRate(models.Model):
currency = models.ForeignKey(Currency, null=False, on_delete=models.CASCADE)
quote = models.ForeignKey(Currency, null=False, on_delete=models.CASCADE)
rate = models.DecimalField(max_digits=6, decimal_places=2)
class CurrencyFactory(factory.django.DjangoModelFactory):
class Meta:
model = Currency
id = factory.Sequence(lambda n: ['EUR', 'USD'][n%2])
conversion_rate = factory.RelatedFactory('my_app.factories.ConversionRateFactory', 'currency')
class ConversionRateFactory(factory.django.DjangoModelFactory):
class Meta:
model = ConversionRate
currency = factory.SubFactory(CurrencyFactory)
quote = factory.SubFactory(CurrencyFactory, id='EUR')
rate = 1.2
这是用于测试的表的默认内容:
+--------+ +--------------------------+
|Currency| | ConversionRate |
+--------+ +----------+--------+------+
| id | | currency | quote | rate |
+--------+ +----------+--------+------+
| EUR | | USD | EUR | 1.2 |
+--------+ +----------+--------+------+
| USD | | EUR | EUR | 1 |
+--------+ +----------+--------+------+
当我尝试构建工厂时会抛出完整性错误:
CurrencyFactory.create()
# Error: UNIQUE constraint failed: Currency.id
我还尝试在 CurrencyFactory “Meta”部分中添加django_get_or_create = ('id',)
,但这会产生无限循环。
过去有人遇到过这样的问题吗?有什么建议吗?
这是使用django_get_or_create = ('id',)
时的追溯:
env/lib/python3.6/site-packages/factory/builder.py:272: in build
step.resolve(pre)
env/lib/python3.6/site-packages/factory/builder.py:221: in resolve
self.attributes[field_name] = getattr(self.stub, field_name)
env/lib/python3.6/site-packages/factory/builder.py:375: in __getattr__
extra=context,
env/lib/python3.6/site-packages/factory/declarations.py:324: in evaluate
return self.generate(step, defaults)
env/lib/python3.6/site-packages/factory/declarations.py:414: in generate
return step.recurse(subfactory, params, force_sequence=force_sequence)
env/lib/python3.6/site-packages/factory/builder.py:233: in recurse
return builder.build(parent_step=self, force_sequence=force_sequence)
env/lib/python3.6/site-packages/factory/builder.py:299: in build
context=postgen_context,
env/lib/python3.6/site-packages/factory/declarations.py:675: in call
return step.recurse(factory, passed_kwargs)
env/lib/python3.6/site-packages/factory/builder.py:233: in recurse
return builder.build(parent_step=self, force_sequence=force_sequence)
env/lib/python3.6/site-packages/factory/builder.py:272: in build
step.resolve(pre)
env/lib/python3.6/site-packages/factory/builder.py:221: in resolve
self.attributes[field_name] = getattr(self.stub, field_name)
env/lib/python3.6/site-packages/factory/builder.py:375: in __getattr__
extra=context,
env/lib/python3.6/site-packages/factory/declarations.py:324: in evaluate
return self.generate(step, defaults)
env/lib/python3.6/site-packages/factory/declarations.py:414: in generate
return step.recurse(subfactory, params, force_sequence=force_sequence)
env/lib/python3.6/site-packages/factory/builder.py:233: in recurse
return builder.build(parent_step=self, force_sequence=force_sequence)
env/lib/python3.6/site-packages/factory/builder.py:279: in build
kwargs=kwargs,
env/lib/python3.6/site-packages/factory/base.py:314: in instantiate
return self.factory._create(model, *args, **kwargs)
env/lib/python3.6/site-packages/factory/django.py:163: in _create
return cls._get_or_create(model_class, *args, **kwargs)
env/lib/python3.6/site-packages/factory/django.py:154: in _get_or_create
instance, _created = manager.get_or_create(*args, **key_fields)
env/lib/python3.6/site-packages/django/db/models/manager.py:82: in manager_method
return getattr(self.get_queryset(), name)(*args, **kwargs)
env/lib/python3.6/site-packages/django/db/models/query.py:487: in get_or_create
return self.get(**lookup), False
env/lib/python3.6/site-packages/django/db/models/query.py:394: in get
clone = self.filter(*args, **kwargs)
env/lib/python3.6/site-packages/django/db/models/query.py:836: in filter
return self._filter_or_exclude(False, *args, **kwargs)
env/lib/python3.6/site-packages/django/db/models/query.py:850: in _filter_or_exclude
clone = self._chain()
env/lib/python3.6/site-packages/django/db/models/query.py:1156: in _chain
obj = self._clone()
env/lib/python3.6/site-packages/django/db/models/query.py:1168: in _clone
c = self.__class__(model=self.model, query=self.query.chain(), using=self._db, hints=self._hints)
env/lib/python3.6/site-packages/django/db/models/sql/query.py:337: in chain
obj = self.clone()
env/lib/python3.6/site-packages/django/db/models/sql/query.py:300: in clone
obj.where = self.where.clone()
env/lib/python3.6/site-packages/django/db/models/sql/where.py:148: in clone
children=[], connector=self.connector, negated=self.negated)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
cls = <class 'django.db.models.sql.where.WhereNode'>, children = [], connector = 'AND', negated = False
@classmethod
def _new_instance(cls, children=None, connector=None, negated=False):
"""
Create a new instance of this class when new Nodes (or subclasses) are
needed in the internal code in this class. Normally, it just shadows
__init__(). However, subclasses with an __init__ signature that aren't
an extension of Node.__init__ might need to implement this method to
allow a Node to create a new instance of them (if they have any extra
setting up to do).
"""
> obj = Node(children, connector, negated)
E RecursionError: maximum recursion depth exceeded while calling a Python object
答案 0 :(得分:2)
正如您所指出的,问题来自CurrencyFactory
创建ConversionRateFactory
,而CurrencyFactory
又会创建 2 factory.Trait
。
我建议使用class Params
通过递归禁用它:
True
部分定义特征:启用时(通过设置布尔标志),它将添加附加的声明CurrencyFactory
:直接调用ConversionRate
将添加ConversionRateFactory
class CurrencyFactory(factory.django.DjangoModelFactory):
class Meta:
model = models.Currency
django_get_or_create = ['id']
class Params:
with_conversion_rate = factory.Trait(
conversion_rate=factory.RelatedFactory('my_app.factories.ConversionRateFactory', 'currency'),
)
# Small improvement: use a `factory.Iterator` to cycle between value
id = factory.Iterator(['EUR', 'USD'])
# By default, force each CurrencyFactory to create a ConversionRate.
with_conversion_rate = True
class ConversionRateFactory(factory.django.DjangoModelFactory):
class Meta:
model = models.ConversionRate
rate = 1.2
currency = factory.SubFactory(
CurrencyFactory,
with_conversion_rate=False,
)
quote = factory.SubFactory(
CurrencyFactory,
id='EUR',
with_conversion_rate=False,
)
中,禁用特征 - 阻止循环触发。请参阅以下代码:
ktpass ... -crypto all ...