Custom primary key with Django ManyToManyField
Define your own primary key with ManyToManyField in Django and gain fine-grain control over the auto-generated intermediary table.
I was recently working on a Django project where I had to deal with a many-to-many relationship. So I decided to go with a ManyToManyField
. While it did precisely what it was supposed to do, but not how I wanted it. It's probably better to demonstrate the problem with some code...
I had two models with a many-to-many relationship between them. My first model looks like this,
class BanglaWords(models.Model):
class Meta:
verbose_name_plural = 'Bangla Words'
bng_id = models.CharField(max_length=16, primary_key=True)
bangla_word = models.CharField(max_length=64)
def __str__(self):
return self.bangla_word
and the second one looks like,
class EnglishWords(models.Model):
class Meta:
verbose_name_plural = 'English Words'
eng_id = models.IntegerField(primary_key=True)
word = models.CharField(max_length=64)
bangla_word = models.ManyToManyField(BanglaWords)
def __str__(self):
return self.word
But the migration for this resulted in an intermediary table wordnet_englishwords_bangla_word
which looked like this,
|----------------------------------|
| wordnet_englishwords_bangla_word |
|----------------------------------|
| id |
| englishwords_id |
| banglawords_id |
|__________________________________|
I didn’t want this however, I wanted bng_id
to be the pk
for this table. I solved the problem with the through
argument of ManyToManyField
. What the through
argument allows you to do is to create the intermediary table (created automatically by ManyToManyField
) yourself. You get finer control of how the intermediary table should look like, what fields it should have, and what its behavior should be. You can also define extra fields if you want. Here is the link to the official documentation on this, https://docs.djangoproject.com/en/3.2/ref/models/fields/#django.db.models.ManyToManyField.through
I defined the intermediary model(table) myself and with the through
argument, I pointed to the new intermediary model I created and instructed Django to create the table the way I wanted it.
First I created the intermediary model,
class BanglaEnglishRelations(models.Model):
class Meta:
verbose_name_plural = 'Bangla English Relations'
bng_id = models.OneToOneField('BanglaWords', primary_key=True, on_delete=models.CASCADE)
eng_id = models.ForeignKey('EnglishWords', on_delete=models.CASCADE)
which defines bng_id
as the primary key as I desired.
Second, I told the ManyToManyField
in EnglishWords
to base the table on BanglaEnglishRelations
like,
bangla_word = models.ManyToManyField(BanglaWords, through=BanglaEnglishRelations)
This resulted in the table wordnet_banglaenglishrelations
which looked like,
|----------------------------------|
| wordnet_banglaenglishrelations |
|----------------------------------|
| bng_id_id |
| eng_id_id |
|__________________________________|
and served my purposes. This way you can promote whatever field to a pk
for a table created with ManyToManyField
in Django.
If you have a better idea or any suggestions for improvement, please don’t hesitate to leave a comment below.
Originally published at https://blog.shaphil.me on August 5, 2021.