'How to create pivot tables with model_bakery

I want to create a baker recipe who create one object, objects from a pivot table and links everything well.

An incident is created when cells of a technology (2g, 3g, 4g, 5g) are down, but not all technologies are impacted. So we have a pivot table to manage this.

My goal is to avoid writing code duplicates and manage this with a simple baker.make_recipe("incidents.tests.incident_with_multiple_impacts") to create an incident and 4 different impacts (technologies are created in fixtures)

# incident/models/incident.py
class Incident(models.Model):
    impacted_technologies = models.ManyToManyField(
        Technology, through="IncidentIncidentImpactedTechnologies"
    )

# network/models/technology.py
class Technology(models.Model):
    alias = models.CharField(max_length=2, unique=True, blank=False) # ie: "2G", "3G"...
    name = models.CharField(max_length=4, unique=True, blank=False)

# incident/models/incident_technologies.py
class IncidentIncidentImpactedTechnologies(models.Model):
    incident = models.ForeignKey(
        to="incident.Incident",
        on_delete=models.CASCADE,
        related_name="technology_impacts",
    )
    technology = models.ForeignKey(
        to="network.Technology",
        on_delete=models.CASCADE,
        related_name="impacting_incidents",
    )

During my researches, I writed code above (I want to keep first recipes to reuse them later):

from model_bakery.recipe import Recipe, foreign_key, related
 
def get_technology(key=None):
    if key:
        return Technology.objects.get_by_alias(key) # to get it with "2G"
    return random.choice(Technology.objects.all())

incident = Recipe(
    Incident,
    # few other fields
)

impacted_techno = Recipe(
    IncidentIncidentImpactedTechnologies,
    incident=foreign_key(incident),
    technology=get_technology, # Default, take random technology
    # Fields describing a NO_IMPACT on this techno
)

impacted_techno_cell_down = impacted_techno.extend(
    # Fields describing a CELL_DOWN impact on this techno
)

impacted_techno_degraded = impacted_techno.extend(
    # Fields describing a DEGRADED impact on this techno
)

impacted_techno_down = impacted_techno.extend(
    # Fields describing a full TECHNO_DOWN on this techno
)

# Below code is not working, because in incident, impacted_technologies is a O2M field pointing on Technology, not on IncidentIncidentImpactedTechnology.
incident_with_multiple_impacted_technos = incident.extend(
    impacted_technologies=related(
        impacted_techno.extend(technology=lambda: get_technology("2G")),
        impacted_techno_degraded.extend(technology=lambda: get_technology("3G")),
        impacted_techno_down.extend(technology=lambda: get_technology("4G")),
        impacted_techno_cell_down.extend(technology=lambda: get_technology("5G")),
    )
)

So how can I make a recipe create all the object tree?

Incident # Only one
 |      \
 |        [IncidentIncidentImpactedTechnology] * 4
 |      /    
[Technology] * 4 # already created

avoiding doing this:

    @staticmethod
    def _get_technology(key):
        return Technology.objects.get_by_natural_key(key)

    def _create_incident_with_multiple_impacted_technos(self):
        incident = baker.make_recipe("incident.tests.incident")
        baker.make_recipe(
            "incident.tests.impacted_techno",
            incident=incident,
            technology=self._get_technology("2G"),
        )
        baker.make_recipe(
            "incident.tests.impacted_techno_cell_down",
            incident=incident,
            technology=self._get_technology("3G"),
        )
        baker.make_recipe(
            "incident.tests.impacted_techno_degraded",
            incident=incident,
            technology=self._get_technology("4G"),
        )
        baker.make_recipe(
            "incident.tests.impacted_techno_down",
            incident=incident,
            technology=self._get_technology("5G"),
        )
        return incident
    
    def test_my_test(self):
        incident = self._create_incident_with_multiple_impacted_technos()
        # test stuff


Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source