Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create factory-boy factories for Django models with the same foreign key

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?

like image 985
Björn Kristinsson Avatar asked Sep 06 '16 09:09

Björn Kristinsson


People also ask

Can a model have two foreign keys in Django?

First of all, anything is possible. Models can have multiple foreign keys.

Can a model have a foreign key to itself?

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.

How does Django foreign key work?

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.

What is DjangoModelFactory?

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.


1 Answers

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'))
like image 139
Xelnor Avatar answered Sep 28 '22 07:09

Xelnor