Фабричный мальчик, дающий максимальную глубину рекурсии, превышает ошибку

Я хотел бы определить две модели, Company и Package. Каждый Package имеет только один Company, но Company может иметь несколько Packages. Однако у каждой компании может быть только один default_package (который может быть нулевым). Я установил это следующим образом:

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'...

Любая идея, почему это не работает? Кажется, я следил за документацией (http://factoryboy.readthedocs.io/en/latest/reference.html#circular-imports), указав полный путь к подфабрике CompanyFactory.

Обновить

Похоже, что этот вариант использования решается постгенерации Factory Boy. крючки. Что кажется многообещающим, так это 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')

но я все еще получаю ту же ошибку.


person Kurt Peek    schedule 16.03.2018    source источник
comment
Я думаю, вы следовали первой части документации, касающейся кругового импорта, но не последнему совету. Что, если вы создадите компанию с company = CompanyFactory(default_package=None), а затем с CompanyFactory(default_package__company=company)?   -  person Paulo Almeida    schedule 17.03.2018
comment
Я заметил, что если я попытаюсь это сделать, созданный таким образом экземпляр Company не будет иметь атрибута default_package. Что я на самом деле хотел бы сделать, так это когда я создаю компанию с CompanyFactory, также создаю объект Package, default_for_company которого является этой компанией. Как я могу этого добиться?   -  person Kurt Peek    schedule 17.03.2018
comment
У меня нет здесь factory_boy для тестирования, но теперь, когда у вас есть company, не могли бы вы создать пакет с этой компанией, а затем установить default_package company в этот пакет?   -  person Paulo Almeida    schedule 17.03.2018


Ответы (1)


Итак, я добился этого, реализовав функцию 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 останавливает рекурсию, и фабрика правильно создается с нужным атрибутом пакета по умолчанию.

person Gustavo Santiago    schedule 08.06.2019