I keep running into issues with my factories when two or more models have a common foreign key, and each one creates their own object when they should have the same.
To illustrate the problem, here is a simplified model structure:
class Language (models.Model):
code = models.CharField(max_length=3, unique=True)
class Audio(models.Model):
language = models.ForeignKey(Language)
soundfile = models.FileField()
class Subtitles(models.Model):
language = models.ForeignKey(Language)
text = models.TextField()
class Recording(models.Model):
audio = models.ForeignKey(Audio)
subtitles = models.ForeignKey(Subtitles)
So a Recording
has Audio
and Subtitles
, and both of those have a Language
which is unique for each language code.
Here are the factories for this structure.
class LanguageFactory(factory.django.DjangoModelFactory):
class Meta:
model = Language
class AudioFactory(factory.django.DjangoModelFactory):
class Meta:
model = Audio
language = factory.SubFactory(LanguageFactory, code='en1')
class SubtitlesFactory(factory.django.DjangoModelFactory):
class Meta:
model = Subtitles
language = factory.SubFactory(LanguageFactory, code='en1')
class RecordingFactory(factory.django.DjangoModelFactory):
class Meta:
model = Recording
audio = factory.SubFactory(AudioFactory)
subtitles = factory.SubFactory(SubtitlesFactory)
It's a very common case that the audio and subtitles have the same language, since generally it's just a transcript. So I want a default RecordingFactory to have audio and subtitles with a language of 'en1' as code, as reflected in the factories above.
But since each factory tries to create its own instance of language, instantiating a RecordingFactory with recording = RecordingFactory()
(which I do a lot) raises an exception:
IntegrityError: UNIQUE constraint failed: recordings_language.code
To solve it, I can do something like this:
language = LanguageFactory(code='en1')
recording = RecordingFactory(subtitles__language=language, audio__language=language)
But that's verbose. In my real project I have even more connections so sometimes I need to specify the language in three or four places, sometimes four levels deep. Instead, I would like to be able to specify a default that is either created or used if it already exists.
What is the correct way around this, if one exists?
First of all, anything is possible. Models can have multiple foreign keys.
Self-referencing foreign keys are used to model nested relationships or recursive relationships. They work similar to how One to Many relationships. But as the name suggests, the model references itself.
Introduction to Django Foreign Key. A foreign key is a process through which the fields of one table can be used in another table flexibly. So, two different tables can be easily linked by means of the foreign key. This linking of the two tables can be easily achieved by means of foreign key processes.
DjangoModelFactory is a basic interface from factory_boy that gives "ORM powers" to your factories. It's main feature here is that it provides you with a common "create" and "build" strategies that you can use to generate objects in your tests.
You can use the Params
option (http://factoryboy.readthedocs.io/en/latest/reference.html#parameters):
class RecordingFactory(factory.django.DjangoModelFactory):
class Meta:
model = models.Recording
class Params:
language = factory.SubFactory(Language)
subtitles = factory.SubFactory(SubtitlesFactory,
language=factory.SelfAttribute('language'))
audio = factory.SubFactory(AudioFactory,
language=factory.SelfAttribute('language'))
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