Chiefir
Chiefir

Reputation: 2671

Forward and reverse query to ManyToMany relation Django

I have this model:

class Task(MPTTModel, TimeStampedModel, StartFinishModel):
    name = models.CharField(max_length=256)
    start_after_end = models.ManyToManyField('self',                                    
                                             related_name='s2e_relation',
                                             blank=True) 
    #s2e means that task can't start before related tasks end

How can I get all tasks, which can't start before this task ends and all tasks, which have to end before this task can start.

Let's say 1 -> 2 -> 3

I have id 2. I want to find 3 and with another query 1.
To find 3 I can use Task.objects.get(id = 2).start_after_end.all()
How can I find 1?

UPDATE 1
Actually find out bigger problem - if I select 2 related to 1 - 1 immidiately changes relation to 1 is related to 2

Upvotes: 1

Views: 314

Answers (2)

willeM_ Van Onsem
willeM_ Van Onsem

Reputation: 476614

If you make a relation with 'self', by default Django will make the relation symmetrical. This means that if an object a has as friend b, then b automatically has as friend a. So this means that the relation here will look like:

# symmetrical relation
1  <---> 2 <---> 3

It thus means that for:

task1.start_after_end.all()  # ==> [2]
task2.start_after_end.all()  # ==> [1, 3]
task3.start_after_end.all()  # ==> [2]

So that here both 1 and 3 will be seen as Task2.start_after_end.all()s. You probably do not want that: you want the relation to be non-symetrical.

So you should edit the field to:

class Task(MPTTModel, TimeStampedModel, StartFinishModel):
    name = models.CharField(max_length=256)
    start_after_end = models.ManyToManyField(
        'self',                                    
        related_name='s2e_relation',
        symmetrical=False,
        blank=True
    )

Now the above will look like:

# asymmetrical relation
1   ---> 2  ---> 3

Note that non-symmetrical relations are not anti-symmetrical relations. It is still possible to add task1 to the start_after_end of task2, but it will not do this automatically in case you add task2 to the start_after_end of task1.

So now if that is done, it means that for some_task, we get the next tasks with:

my_task.start_after_end.all()  # the next ones

and we get the ones before with the related_name:

my_task.s2e_relation.all()     # the previous ones

So for example:

task1 = Task.objects.create(name='task1')
task2 = Task.objects.create(name='task2')
task3 = Task.objects.create(name='task3')

task1.start_after_end.add(task2)
task2.start_after_end.add(task3)

Then if we trigger the relations, we get:

task1.start_after_end.all()  # ==> [2]
task2.start_after_end.all()  # ==> [3]
task3.start_after_end.all()  # ==> []

task1.s2e_relation.all()  # ==> []
task2.s2e_relation.all()  # ==> [1]
task3.s2e_relation.all()  # ==> [2]

Upvotes: 4

Nitin Bhojwani
Nitin Bhojwani

Reputation: 712

Idea is to use related_name='s2e_relation' passed in ManyToManyField

You can do so by:

Task.objects.get(id=2).s2e_relation.all()

Upvotes: 0

Related Questions