【问题标题】:Storing factory-boy RelatedFactory Object on Parent Factory在父工厂上存储 factory-boy RelatedFactory 对象
【发布时间】:2020-07-24 15:56:28
【问题描述】:

我有两个相互包含 ForeignKeys 的 Django 模型(CustomerCustomerAddress)。我正在使用 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='+')

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

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

创建ExampleCustomerFactory 时,default_billto,即使已创建ExampleCustomerAddress

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

In [15]: ec.default_billto is None
Out[15]: True

(使用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!`.

我觉得我在这里发疯了,错过了一些非常简单的东西。我得到的印象是我遇到了这个错误,因为两个模型都包含ForeignKeys

【问题讨论】:

    标签: python django factory-boy


    【解决方案1】:

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

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

    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_declaration
        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()
    

    在这里,我们将新创建的客户价值设置为“我们”;请注意,此逻辑也可以移至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',
        )
    

    这个工厂将运行如下:

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

    注意事项

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

    【讨论】:

    • 非常感谢!这非常有效。出于某种原因,我挂断了将后生成挂钩添加到CustomerFactory。我从来没有想过将它放在CustomerAddressFactory 上。快速编辑您的答案:@factory.post_declaration 装饰器应该是 @factory.post_generation
    猜你喜欢
    • 2012-10-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-05-08
    • 2020-08-21
    • 2020-10-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多