To ‘through’ or not to ‘through’? (updated 03.07)

Should I use a ‘through’ table for my Model to Model relation? It was never a question for me two years ago.

Why would you consider a ‘through’ table?

What if I need to add characteristics to the Model-To-Model relation??

It sounds all so magical – you have this tiny itty-bitty table with an additional property, say, to make the front-end design look good.

Why not make a ‘through’ table?

In reality, making redundant tables can work, but to an extent – your server’s RAM won’t last forever.

Once your client returns to this feature later, they will exclaim that you did something wrong, but in reality you didn’t change a bit. Now comes a process of simplification, that most likely means reducing this feature altogether and rewriting HTML on your own to fit your RAM limits.

What should I pick?

It there are quite a few design features that require me to use it, then I should use a through table.

If you see that the feature(s) are redundant – try to change your client’s mind. If you can’t – just do it and make it work fast.

What is the difference?

This list is being updated upon new discoveries.

1. Accesing data

For simple ManyToManyField you can use related_name='some_related_name' and access this Queryset as myinstance.some_related_name.all().

But if you have a through='some_table', you will have to add related_name to the original’s field in the through table and access these relations from that point. Your access will actually work backwards.

For families of models it is easier to use related_name='%(app_label)s_%(class)s'

2. Making changes to the ManyToMany field while changing it.

Say, you have a M2M field and a property One. Based on the value of the property One, you want to make changes to the M2M field. If you are changing M2M as well, say, if you are creating an instance and both values are having something in them. As far as I know, you can’t really do that.

3. Saving overall

Saving overall is much easier, as every relations is its own instance. But you have to curate it. You can’t forget to create one or update – make a QuerySet/Manager or do it manually. For example, a QuerySet override:

class CarQuerySet(models.QuerySet):

    def create(self, **kwargs):
        with transaction.atomic():
            car = self.model(**kwargs)
            car.save(force_insert=True)
            CarProfile.objects.create(car=car)
        return car

4. Ordering (upd. 03.07)

Say, you have some paired data like names and telephone numbers. You want to store them and use later. You may think, hey, Django says that ManyToManyField has a tuple, so I will just use two M2M fields for names and telephon numbers and get them later in the same order I added them in. WRONG! Think again.

I just encountered this issue and were pretty devastated about it. So, it is 100% correct that I am adding names and numbers in the right order.

names = ['Kate', 'Maria', 'Petra']
tels = ['Kate\'s number', 'Maria\'s number', 'Petra\'s number']

# my_obj has names and telephone_numbers M2M fields

[my_obj.names.add(x) for x in names]
[my_obj.telephone_numbers.add(x) for x in tels]

It may work on modest amounts of data. But at some point, say, even 10 units, you may discover that M2M objects don’t keep the order that you add them in.

Conclusion

Do not bend under unrealistic expectations. It is being said that your business logic must be neat and smart, but achieving that is highly unreasonable when the original design is excessive.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.