在父工厂上存储factory-boy的RelatedFactory对象

Taj*_*ton 8 python django factory-boy

我有两个 Django 模型 (CustomerCustomerAddress),它们都ForeignKey彼此包含 s。我正在使用factory-boy 来管理这些模型的创建,并且无法将子工厂实例保存到父工厂上(使用使用类定义的关系RelatedFactory)。

我的两个模型:

class ExampleCustomerAddress(models.Model):
    # Every customer mailing address is assigned to a single Customer,
    # though Customers may have multiple addresses.
    customer = models.ForeignKey('ExampleCustomer', on_delete=models.CASCADE)

class ExampleCustomer(models.Model):
    # Each customer has a single (optional) default billing address:
    default_billto = models.ForeignKey(
        'ExampleCustomerAddress',
        on_delete=models.SET_NULL,
        blank=True,
        null=True,
        related_name='+')
Run Code Online (Sandbox Code Playgroud)

我有两个工厂,每个型号一个:

class ExampleCustomerAddressFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = ExampleCustomerAddress

    customer = factory.SubFactory(
        'ExampleCustomerFactory',
        default_billto=None)  # Set to None to prevent recursive address creation.

class ExampleCustomerFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = ExampleCustomer

    default_billto = factory.RelatedFactory(ExampleCustomerAddressFactory,
                                            'customer')
Run Code Online (Sandbox Code Playgroud)

创建 a 时ExampleCustomerFactory,即使 a已创建,也default_billtoNone :ExampleCustomerAddress

In [14]: ec = ExampleCustomerFactory.build()

In [15]: ec.default_billto is None
Out[15]: True
Run Code Online (Sandbox Code Playgroud)

(使用时create()ExampleCustomerAddress数据库中存在一个新的。我build()在这里使用是为了简化示例)。

ExampleCustomerAddress按预期创建作品,并Customer自动创建:

In [22]: eca = ExampleCustomerAddressFactory.build()

In [23]: eca.customer
Out[23]: <ExampleCustomer: ExampleCustomer object>

In [24]: eca.customer.default_billto is None
Out[24]: True  <-- I was expecting this to be set to an `ExampleCustomerAddress!`.
Run Code Online (Sandbox Code Playgroud)

我觉得我快要疯了,错过了一些非常简单的事情。我觉得我遇到这个错误是因为两个模型如何ForeignKeys相互包含。

小智 12

首先,一个简单的经验法则:当您遵循 a 时ForeignKey总是更喜欢SubFactory; RelatedFactory旨在遵循相反的关系。

让我们依次看看每个工厂。

ExampleCustomerAddressFactory

当我们在没有客户的情况下调用这家工厂时,我们需要获取一个链接到客户的地址,并将其用作该客户的默认地址。

但是,当我们与客户调用它时,不要更改它。

以下内容将起作用:

class ExampleCustomerAddressFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = ExampleCustomerAddress

    # Fill the Customer unless provided
    customer = factory.SubFactory(
        ExampleCustomerFactory,
        # We can't provide ourself there, since we aren't saved to the database yet.
        default_billto=None,
    )

    @factory.post_generation
    def set_customer_billto(obj, create, *args, **kwargs):
        """Set the default billto of the customer to ourselves if empty"""
        if obj.customer.default_billto is None:
            obj.customer.default_billto = obj
            if create:
                obj.customer.save()
Run Code Online (Sandbox Code Playgroud)

这里,我们将新创建的客户的价值设置为“我们”;请注意,此逻辑也可以移至ExampleCustomerAddress.save().

ExampleCustomerFactory

对于这个工厂,规则更简单:创建客户时,创建默认帐单地址(除非已提供值)。

class ExampleCustomerFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = ExampleCustomer

    # We can't use a SubFactory here, since that would be evaluated before
    # the Customer has been saved.
    default_billto = factory.RelatedFactory(
        ExampleCustomerAddressFactory,
        'customer',
    )
Run Code Online (Sandbox Code Playgroud)

该工厂将按如下方式运行:

  1. ExampleCustomer使用default_billto=None;创建实例
  2. ExampleCustomerAddressFactory(customer=obj)与新创建的客户通话;
  3. ExampleCustomerAddress该工厂将与该客户创建一个;
  4. 该工厂中的后生成挂钩将检测到客户没有default_billto,并将覆盖它。

笔记

  • 我没有对此进行测试,因此可能会出现一些拼写错误或小错误;
  • 由您决定首先声明哪个工厂,使用目标工厂的路径而不是直接引用;
  • 如上所述,当客户的默认帐单地址为空并且将地址添加到该客户时,设置该客户的默认帐单地址的逻辑可以移至模型的.save()方法中。

  • 非常感谢!这非常有效。由于某种原因,我无法将生成后的钩子添加到“CustomerFactory”上。我从来没有想过将它放在“CustomerAddressFactory”上。对您的答案进行快速编辑:“@factory.post_declaration”装饰器应该是“@factory.post_ Generation”。 (2认同)