Django ManyToMany relationship

Question:

In models.py:

from django.db import models
from django.utils.translation import ugettext as _

# Create your models here.
class Category(models.Model):
    name            = models.CharField(_(u"Name"), max_length=250)
    products        = models.ManyToManyField("Product", verbose_name=_(u"Products"), 
                      blank=True, null=True, related_name="+")
        
class Product(models.Model):
    name            = models.CharField(_(u"Name"), max_length=250)    
    category        = models.ManyToManyField("Category", verbose_name=_(u"Category"), 
                      blank=True, null=True, related_name="+")

In admin page:

m2m relationships

The question:
How can the relations between products and category m2m fields in models.py be set so that in admin-page, as it can be seen at the picture, b2(the product) was marked as it belongs to a2 (the category).
Any advices for implementation of [products, categories] are welcomed, thank you.

P.S.
I’m newbie in Django. Sorry for my English.

Asked By: ted

||

Answers:

The problem is that you have two ManyToMany fields. As you’ve noted, when the relationship is set in one of them, it isn’t in the other.

The solution is simple: drop one of the fields. You only need a ManyToManyField on one side of the relationship. Django gives you access to the other side automatically. So, if you kept the categories field on the Product model, you can do my_product.categories.all() to get the categories a product is associated with; and my_category.product_set.all() to get the products that belong to a category.

You’ll also need to drop the related_name="+" attribute: you’ve presumably put that in because you were getting conflicts, which should have been a clue.

Answered By: Daniel Roseman

Mar, 2022 Update:

There are 2 ways to create "many-to-many" relationship in Django. One doesn’t use "ManyToManyField()" and one uses "ManyToManyField()". First of all, I’ll show you the way which doesn’t use "ManyToManyField()".

<The way without “ManyToManyField()”>

To create many-to-many relationship, there must be the middle table between 2 other tables and it must have each foreign key from 2 other tables and these foreign keys must be unique together. So, I created the middle table "CategoryProduct" between "Category" and "Product" tables and it has each foreign key from "Category" and "Product" tables and these foreign keys are unique together as shown below.

"models.py":

from django.db import models

class Category(models.Model):
    name = models.CharField(max_length=255)
    
    def __str__(self):
        return self.name

class Product(models.Model):
    name = models.CharField(max_length=255)    
    
    def __str__(self):
        return self.name

# The middle table between "Category" and "Product" tables
class CategoryProduct(models.Model): 
    category = models.ForeignKey(Category, on_delete=models.CASCADE)
    product = models.ForeignKey(Product, on_delete=models.CASCADE)

    class Meta:
        unique_together = [['category', 'product']]

And this is how you inline the middle table "CategoryProduct" to "Product" table as shown below.

"admin.py":

from django.contrib import admin
from .models import CategoryProduct, Product

class CategoryProductInline(admin.TabularInline):
    model = CategoryProduct
    min_num = 1 
    extra = 2
    max_num = 5

@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
    inlines = [CategoryProductInline]

This is how it looks like:

enter image description here

And this is how you inline the middle table "CategoryProduct" to "Category" table as shown below.

"admin.py":

from django.contrib import admin
from .models import CategoryProduct, Category

class CategoryProductInline(admin.TabularInline):
    model = CategoryProduct
    min_num = 1 # 1 required inline field displayed
    extra = 2   # 2 unrequired inline fields displayed
    max_num = 5 # 5 inline fields as a maximum

@admin.register(Category)
class CategoryAdmin(admin.ModelAdmin):
    inlines = [CategoryProductInline]

This is how it looks like:

enter image description here

<The way with “ManyToManyField()”>

This is the way with "ManyToManyField()" as shown below. The difference between the way with "ManyToManyField()" and the way without "ManyToManyField()" above is in this case of the way with "ManyToManyField()", "categories" field with "ManyToManyField()" is added to "Product" class as you can see below and there is the only code difference between them so other things are the same.

"models.py":

from django.db import models

class Category(models.Model):
    name = models.CharField(max_length=255)

    def __str__(self):
        return self.name
    
class Product(models.Model):
    name = models.CharField(max_length=255)
    categories = models.ManyToManyField(
        Category, 
        through='CategoryProduct'
    ) # "categories" field is added

    def __str__(self):
        return self.name

class CategoryProduct(models.Model):
    category = models.ForeignKey(Category, on_delete=models.CASCADE)
    product = models.ForeignKey(Product, on_delete=models.CASCADE)

    class Meta:
        unique_together = [['category', 'product']]

And you can add "products" field with "ManyToManyField()" to "Category" class and there is only code difference as well so other things are the same as well.

"models.py":

from django.db import models

class Product(models.Model):
    name = models.CharField(max_length=255)

    def __str__(self):
        return self.name

class Category(models.Model):
    name = models.CharField(max_length=255)
    products = models.ManyToManyField(
        Product, 
        through='CategoryProduct'
    ) # "products" field is added

    def __str__(self):
        return self.name

class CategoryProduct(models.Model):
    category = models.ForeignKey(Category, on_delete=models.CASCADE)
    product = models.ForeignKey(Product, on_delete=models.CASCADE)

    class Meta:
        unique_together = [['category', 'product']]

So, how you inline the middle table "CategoryProduct" to "Product" table is also the same as the way without "ManyToManyField()" as shown below.

"admin.py":

from django.contrib import admin
from .models import CategoryProduct, Product

class CategoryProductInline(admin.TabularInline):
    model = CategoryProduct
    min_num = 1 
    extra = 2
    max_num = 5

@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
    inlines = [CategoryProductInline]

This is how it looks like:

enter image description here

And, how you inline the middle table "CategoryProduct" to "Category" table is the same as the way without "ManyToManyField()" as shown below.

"admin.py":

from django.contrib import admin
from .models import CategoryProduct, Category

class CategoryProductInline(admin.TabularInline):
    model = CategoryProduct
    min_num = 1 # 1 required inline field displayed
    extra = 2   # 2 unrequired inline fields displayed
    max_num = 5 # 5 inline fields as a maximum

@admin.register(Category)
class CategoryAdmin(admin.ModelAdmin):
    inlines = [CategoryProductInline]

This is how it looks like:

enter image description here

In addition, when you use "ManyToManyField()", you can remove the code for the middle table "CategoryProduct" which is customizable. In this case you remove the code for the middle table "CategoryProduct", the default middle table which is not customizable is created implicitly. So, I removed the code for the middle table "CategoryProduct" and "through=’CategoryProduct’" from "ManyToManyField()" as shown below.

"models.py":

from django.db import models

class Category(models.Model):
    name = models.CharField(max_length=255)

    def __str__(self):
        return self.name

class Product(models.Model):
    name = models.CharField(max_length=255)
    categories = models.ManyToManyField(Category)
    categories = models.ManyToManyField(
        Category, 
        # through='CategoryProduct'
    )

    def __str__(self):
        return self.name

# class CategoryProduct(models.Model):
#     category = models.ForeignKey(Category, on_delete=models.CASCADE)
#     product = models.ForeignKey(Product, on_delete=models.CASCADE)

#     class Meta:
#         unique_together = [['category', 'product']]

And by only registering "Product" as shown below, you can create the form of "Product" table with the inline of the default middle table.

"admin.py":

from django.contrib import admin
from .models import Product

admin.site.register(Product)

This is how it looks like. As you can see, the UI(User Interface) of "Categories" is different from when having the custom middle table "CategoryProduct". For me, the UI of when having the custom middle table "CategoryProduct" is better:

enter image description here

And even if you register "Category" table as shown below.

"admin.py":

from django.contrib import admin
from .models import Category

admin.site.register(Category)

The default middle table is not inlined to "Category" table as shown below:

enter image description here

To inline the default middle table to "Category" table, "Category" table must have ManyToManyField() as shown below.

"models.py":

from django.db import models

class Product(models.Model):
    name = models.CharField(max_length=255)

    def __str__(self):
        return self.name

class Category(models.Model):
    name = models.CharField(max_length=255)
    products = models.ManyToManyField( # Here
        Product, 
        # through='CategoryProduct'
    )

    def __str__(self):
        return self.name

# class CategoryProduct(models.Model):
#     category = models.ForeignKey(Category, on_delete=models.CASCADE)
#     product = models.ForeignKey(Product, on_delete=models.CASCADE)

#     class Meta:
#         unique_together = [['category', 'product']]

Then, by only registering "Category" as shown below, you can create the form of "Category" table with the inline of the default middle table.

"admin.py":

from django.contrib import admin
from .models import Category

admin.site.register(Category)

This is how it looks like:

enter image description here

This is how you create "many-to-many" relationship in Django.

Buy me a coffee!!

Answered By: Kai – Kazuya Ito