Django 多对多关系

6 投票
2 回答
8457 浏览
提问于 2025-04-17 12:20

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="+")

在管理页面:

m2m 关系

问题是:
models.py 中,如何设置 productscategory 之间的多对多关系(m2m),这样在管理页面上,就像图片中显示的那样,b2(这个产品)会被标记为属于 a2(这个类别)。
对于 [产品,类别] 的实现,任何建议都非常欢迎,谢谢。

附言:
我在 Django 方面还是个新手。抱歉我的英语不太好。

2 个回答

1

2022年3月更新:

Django中,有两种方法可以创建多对多关系。一种方法不使用“ManyToManyField()”,另一种方法使用“ManyToManyField()”。首先,我会展示不使用“ManyToManyField()”的方法。

<不使用“ManyToManyField()”的方法>

要创建多对多关系,必须在另外两个表之间有一个中间表,这个中间表需要包含来自两个表的外键,并且这两个外键的组合必须是唯一的。所以,我创建了一个名为“CategoryProduct”的中间表,它位于“Category”和“Product”表之间,并且包含来自“Category”和“Product”表的外键,这些外键的组合是唯一的,如下所示。

“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']]

接下来,这是如何将中间表“CategoryProduct”嵌入到“Product”表中的,如下所示。

“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]

效果如下:

这里输入图片描述

接下来,这是如何将中间表“CategoryProduct”嵌入到“Category”表中的,如下所示。

“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]

效果如下:

这里输入图片描述

<使用“ManyToManyField()”的方法>

这是使用“ManyToManyField()”的方法,如下所示。使用“ManyToManyField()”和不使用“ManyToManyField()”的区别在于,在使用“ManyToManyField()”的情况下,“Product”类中添加了一个“categories”字段,这个字段使用了“ManyToManyField()”,你可以看到下面的代码,其他部分是相同的。

“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']]

你也可以在“Category”类中添加一个“products”字段,同样使用“ManyToManyField()”,代码的其他部分也是相同的。

“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']]

因此,如何将中间表“CategoryProduct”嵌入到“Product”表中的方法与不使用“ManyToManyField()”的方法是一样的,如下所示。

“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]

效果如下:

这里输入图片描述

同样,如何将中间表“CategoryProduct”嵌入到“Category”表中的方法也是与不使用“ManyToManyField()”的方法相同,如下所示。

“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]

效果如下:

这里输入图片描述

此外,当你使用“ManyToManyField()”时,可以去掉中间表“CategoryProduct”的代码,这个中间表是可定制的。在这种情况下,你去掉了中间表“CategoryProduct”的代码,系统会自动创建一个不可定制的默认中间表。所以,我去掉了中间表“CategoryProduct”的代码“through='CategoryProduct'”,如下所示。

“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']]

只需注册“Product”,如下所示,就可以创建带有默认中间表的“Product”表单

“admin.py”:

from django.contrib import admin
from .models import Product

admin.site.register(Product)

效果如下。可以看到,“Categories”的用户界面与使用自定义中间表“CategoryProduct”时不同。对我来说,使用自定义中间表“CategoryProduct”的用户界面更好:

这里输入图片描述

即使你像下面这样注册“Category”表

“admin.py”:

from django.contrib import admin
from .models import Category

admin.site.register(Category)

默认中间表没有嵌入到“Category”表,如下所示:

这里输入图片描述

要将默认中间表嵌入到“Category”表“Category”表必须有ManyToManyField(),如下所示。

“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']]

然后,只需注册“Category”,如下所示,就可以创建带有默认中间表的“Category”表单

“admin.py”:

from django.contrib import admin
from .models import Category

admin.site.register(Category)

效果如下:

这里输入图片描述

这就是在Django中创建多对多关系的方法。

7

问题在于你有两个 ManyToMany 字段。正如你所注意到的,当在其中一个字段中设置了关系时,另一个字段却没有。

解决方法很简单:去掉其中一个字段。你只需要在关系的一侧保留一个 ManyToManyField。Django 会自动让你访问另一侧。所以,如果你在 Product 模型中保留了 categories 字段,你可以通过 my_product.categories.all() 来获取与某个产品相关的所有分类;而通过 my_category.product_set.all() 可以获取属于某个分类的所有产品。

你还需要去掉 related_name="+" 这个属性:你可能是因为出现了冲突才加上这个属性的,这应该是一个提示。

撰写回答