Is Django many to many really necessary?
Asked Answered
A

2

11

I have been learning to use Django over the last year and Python is not my native programming language. Normally, when I want to create a many to many relationship, I would create 3 tables, one of which contained two foreign keys to each of the other two tables. For this reason, I find the Django manytomany field very unnatural. Does this many to many field create a third table in the database to store the relationship ? If this is the case, then using a many to many field will stop the class structure from directly representing the table structure.

What I am wondering is if there is a reason I should be using this manytomany field or could I get away with using 3 classes to represent a many to many relationship ?

Thanks

Mark

Aquitaine answered 13/10, 2020 at 15:6 Comment(0)
S
10

I would create 3 tables, one of which contained two foreign keys to each of the other two tables. For this reason, I find the Django manytomany field very unnatural.

This is exactly what Django is doing behind the curtains. If you inspect the database, you will see a junction table [wiki]. Django simply does this in the background. It simply presents it to the foreground as a field. As you can see in the database Representation of the ManyToManyField [Django-doc]:

Behind the scenes, Django creates an intermediary join table to represent the many-to-many relationship. By default, this table name is generated using the name of the many-to-many field and the name of the table for the model that contains it. Since some databases don't support table names above a certain length, these table names will be automatically truncated and a uniqueness hash will be used, e.g. author_books_9cdf. You can manually provide the name of the join table using the db_table option.

You can even add extra fields to this junction table, by defining the model in between explicitly, and specify this in the through=… parameter [Django-doc]. For example:

class Category(models.Model):
    name = models.CharField(
        max_length=128,
        unique=True
    )

class Item(models.Model):
    name = models.CharField(
        max_length=128,
        unique=True
    )
    categories = models.ManyToManyField(
        Category,
        through='ItemCategory'
        related_name='items'
    )

class ItemCategory(models.Model):
    category = models.ForeignKey(Category, on_delete=models.CASCADE)
    item = models.ForeignKey(Item, on_delete=models.CASCADE)
    validated = models.BooleanField(default=False)

A ManyToManyField is thus more a concept to make representing and querying more convenient.

Spear answered 13/10, 2020 at 15:12 Comment(5)
Thanks for your replay - it has helped with my understanding. In your example, if you deleted the piece of code saying categories = models.ManyToManyField( .... ), then wouldn't the code still represent a many to many relationship ? This makes the manytomany field feel a bit surplus to requirements. I guess to be fair you would have to say that the same applies if you use 3 classes in a situation where only 2 are necessary, but there is a definite loss of clarity as the class structure no longer represents the table structure directly.Aquitaine
@MarkyMark1000: yes it would still represent a many-to-many relation without the ManyToManyField. But then querying for the categories of an Item might be more cumbersome. The ManyToManyField thus adds a "conceptual layer" on top of it, to make it convenient to query with myitem.categories.all().Spear
Ah, ok, that's interesting, so you are saying that I might find it difficult to query in Django later on if I don't include the ManyToManyField. That is really useful to know. Just out of interest, could I also add an 'Items' field to the category table that used the same 'ItemCategory' class ? The idea would be so that I could also use my category.items.all()Aquitaine
Thanks, this has been really useful.Aquitaine
For anyone interested, I found that when I tried to add an items manytomany field, the migrations failed because the Item class didn't exist yet. I guess the ManyToMany field is one directional and you need to pick which class comes first carefully.Aquitaine
F
0

I know the question has been answered. I have added two more examples of django many-to-many.

If you need extra columns in joining table then you need to define your own joining model.

class Person(models.Model):
    name = models.CharField(max_length=128)

    def __str__(self):
        return self.name

class Group(models.Model):
    name = models.CharField(max_length=128)
    members = models.ManyToManyField(Person, through='Membership')

    def __str__(self):
        return self.name

class Membership(models.Model):
    person = models.ForeignKey(Person, on_delete=models.CASCADE)
    group = models.ForeignKey(Group, on_delete=models.CASCADE)
    date_joined = models.DateField()
    invite_reason = models.CharField(max_length=64)

reference

If you don't need any extra column in joining table then use below and after running migration, you will see Django has created a joining table for you.

class Feature(models.Model):
  feature_id= models.IntegerField(primary_key=True)
  name = models.CharField(max_length=100, unique=True, null=True)
  description = models.CharField(max_length=150, null=True)
  created_date=models.DateTimeField(auto_now_add=True)
  modified_date=models.DateTimeField(auto_now=True)

  def __str__(self):
    return f'id = {self.menu_id} name = {self.name}'

  class Meta:
    db_table = 'features'


class Plan(models.Model):
  plan_id= models.IntegerField(primary_key=True)
  name = models.CharField(max_length=20)
  features = models.ManyToManyField(Feature)
  created_date=models.DateTimeField(auto_now_add=True)
  modified_date=models.DateTimeField(auto_now=True)

  class Meta:
    db_table = 'plans'

Easy!

Foredo answered 25/8 at 8:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.