Open incidentist opened 4 years ago
Never mind. A duplicate of #684. I made a RelatedFactory when I should have made a SubFactory. Except I'm trying to pass the parent object to the SubFactory (not an attribute, but the whole object) and it's not clear how to do that, whereas it's quite clear how to do this with a RelatedFactory.
Hmm, after some investigation it seems that this is not possible. It is fine if the primary_phone attribute is set after the Student object is created. I guess I don't understand why running the RelatedFactory after the parent object creation results in the attribute being None.
It's also not clear from documentation that this is not possible, in either the CityFactory/CountryFactory example or the UserFactory/GroupFactory example. Because neither of those examples provide the class definition that the factories are based on, it is hard to tell.
Hey, thanks for the very detailed report!
This is indeed a common use case, tricky to build with factory_boy; and, as you've pointed, the docs could be improved.
For such complex cases, I always find it easier to start by thinking in terms of pure Django code; once the sequence of calls is clear, I then add them to the factory. Here, I guess you would go like this:
student = Student.objects.create(primary_phone=None)
phone = StudentPhone.objects.create(student=student)
student.primary_phone = phone; student.save()
The important bit is that third step; there are a few options to handle it:
You could decide that, whenever a StudentPhone
is created, if its student doesn't have a primary_phone
, it gets attached:
class StudentPhone(models.Model):
# Definitions here
def save(self, *args, **kwargs):
result = super().save(*args, **kwargs)
if self.student.primary_phone is None:
self.student.primary_phone = self
self.student.save()
return result
We've discovered that the phone should be attached to the student once the phone has been created. For that task, the right FactoryBoy tool is a post-generation declaration:
class StudentPhoneFactory(DjangoModelFactory):
# Usual declarations here
@factory.post_generation
def set_primary_phone(self, obj, *args, **kwargs):
if self.student.primary_phone is None:
self.student.primary_phone = self
self.student.save()
Maybe you have a helper on StudentPhone
to mark it as a primary? In that case, the factory code can be simplified:
class StudentPhone(models.Model):
...
def set_primary(self):
self.student.primary_phone = self
self.student.save()
class StudentPhoneFactory(DjangoModelFactory):
...
# Always set ourselves as the primary phone of the user
set_primary = factory.PostGenerationMethodCall('set_primary')
However, with this last option, the latest StudentPhoneFactory
call would always be defined as the primary phone.
This is very helpful, thank you. I went with option 2, and it's working great.
Description
I have two Django models that refer to eachother. StudentPhone has a ForeignKey relationship to Student, and a Student has a primary_phone field that refers to a StudentPhone. I want a StudentFactory that uses StudentPhoneFactory to create its primary_phone field. This is exactly the scenario shown in the example https://factoryboy.readthedocs.io/en/latest/reference.html#factory.RelatedFactory . With the following models.py and test.py, the assert at the end fails.
models.py:
tests.py:
Django 3.0.10 FactoryBoy 3.0.1
To Reproduce
See the attached django app. Run
./manage.py test main
. Theassert
should succeed but it fails. factorybug.zip