Artem Ilin
Artem Ilin

Reputation: 375

ManyToManyField in django models

I need to create a set of models like this:

class Step(models.Model):
    field1 = models.CharField(max_length=50)
    field2 = models.CharField(max_length=50)

class Scenario(models.Model):
    name = models.CharField(max_length=50)
    steps = models.ManyToManyField(Step, related_name="scenarios")

The thing is I want the scenario to include several steps in an order that I will specify and even the same step several times. Like this:

Scenario1:
    step1
    step2
    step1
    step3

I want this step's order to be easy editable in admin site. I found out about filter_horizontal, it looks a lot like what I need in admin site, but it has no option to add a step one more time and to move steps up and down.

Upvotes: 1

Views: 615

Answers (1)

willeM_ Van Onsem
willeM_ Van Onsem

Reputation: 477607

There is a saying in computer science that says that: "All problems in computer science can be solved by another level of indirection" - David Wheeler.

In that case you should use a "through" table, like:

class Step(models.Model):
    field1 = models.CharField(max_length=50)
    field2 = models.CharField(max_length=50)

class Scenario(models.Model):
    name = models.CharField(max_length=50)
    steps = models.ManyToManyField(
        Step,
        through='ScenarioStep',
        related_name="scenarios"
    )

class ScenarioStep(models.Model):
    scenario = models.ForeignKey(
        'Scenario',
        on_delete=models.CASCADE,
        related_name='scenariostep'
    )
    step = models.ForeignKey(
        'Step'
        on_delete=models.CASCADE,
        related_name='scenariostep'
    )
    order = models.PositiveIntegerField()

    class Meta:
        ordering = ['order']

Here we thus introduce an extra model that replaces the hidden "through table" that was already constructed by the ManyToManyField.

A Scenario thus has a Step, given there is a ScenarioStep record, and we order this by an Order field.

We can, for example, add the given steps to the scenario with:

s1 = Step.objects.create(field1='step1')
s2 = Step.objects.create(field1='step1')
s3 = Step.objects.create(field1='step1')

sc1 = Scenario.objects.create(name='Scenario1')

ScenarioStep.objects.create(order=1, scenario=sc1, step=s1)
ScenarioStep.objects.create(order=2, scenario=sc1, step=s2)
ScenarioStep.objects.create(order=3, scenario=sc1, step=s1)
ScenarioStep.objects.create(order=4, scenario=sc1, step=s3)

We can then iterate over the steps with:

for step in sc1.steps.order_by('scenariostep'):
    # ... (do something with the step)
    pass

Upvotes: 1

Related Questions