I have two Django models (Customer
and CustomerAddress
) that both contain ForeignKey
s to each other. I am using factory-boy to manage creation of these models, and cannot save a child factory instance onto the parent factory (using relationships defined using the RelatedFactory
class).
My two models:
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='+')
I have two factories, one for each model:
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')
When creating a ExampleCustomerFactory
, default_billto
is None, even though a ExampleCustomerAddress
has been created:
In [14]: ec = ExampleCustomerFactory.build()
In [15]: ec.default_billto is None
Out[15]: True
(When using create()
, a new ExampleCustomerAddress
exists in the database. I am using build()
here to simplify the example).
Creating an ExampleCustomerAddress
works as expected, with the Customer
being automatically created:
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!`.
I feel like I am going crazy here, missing something very simple. I get the impression I am encountering this error because of how both models contain ForeignKeys
to each other.
First, a simple rule of thumb: when you're following a ForeignKey
, always prefer a SubFactory
; RelatedFactory
is intended to follow a reverse relationship.
Let's take each factory in turn.
ExampleCustomerAddressFactory
When we call this factory without a customer, we'll want to get an address, linked to a customer, and used as the default address for that customer.
However, when we call it with a customer, don't alter it.
The following would work:
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()
Here, we'll set the newly created customer's value to "us"; note that this logic could also be moved to ExampleCustomerAddress.save()
.
ExampleCustomerFactory
For this factory, the rules are simpler: when creating a customer, create a default billing address (unless a value has been provided).
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',
)
This factory will run as follows:
ExampleCustomer
instance with default_billto=None
;ExampleCustomerAddressFactory(customer=obj)
with the newly created customer;ExampleCustomerAddress
with that customer;default_billto
, and will override it..save()
method.If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With