Django 多对多(ManyToManyField)模型关系终极指南

一、核心特点

1. 中间表机制

  • 自动隐式表:Django 自动创建 <model1>_<model2> 中间表
  • 显式控制:通过 through 参数自定义中间模型
  • 双向对称性:默认对称关系(可设置 symmetrical=False 关闭)

2. 查询特性

# 正向查询
article.tags.all()

# 反向查询
tag.article_set.all()

# 双向过滤
Article.objects.filter(tags__name='Python')
Tag.objects.filter(article__title__icontains='django')

3. 性能优化点

  • prefetch_related 预加载关联集合
  • 中间表联合索引优化
  • 批量操作减少查询次数

二、应用场景

1. 标签系统

class Article(models.Model):
    title = models.CharField(max_length=200)
    tags = models.ManyToManyField(
        'Tag',
        related_name='articles'
    )

class Tag(models.Model):
    name = models.CharField(max_length=50, unique=True)
    slug = models.SlugField(unique=True)

2. 社交关系

class User(models.Model):
    followers = models.ManyToManyField(
        'self',
        symmetrical=False,
        through='FollowRelationship',
        related_name='following'
    )

class FollowRelationship(models.Model):
    from_user = models.ForeignKey(User, related_name='following_set', on_delete=models.CASCADE)
    to_user = models.ForeignKey(User, related_name='follower_set', on_delete=models.CASCADE)
    created_at = models.DateTimeField(auto_now_add=True)
    
    class Meta:
        unique_together = [('from_user', 'to_user')]

3. 权限系统

class Group(models.Model):
    name = models.CharField(max_length=80, unique=True)
    permissions = models.ManyToManyField(
        'Permission',
        through='GroupPermission',
        related_name='groups'
    )

class Permission(models.Model):
    codename = models.CharField(max_length=100)
    
class GroupPermission(models.Model):
    group = models.ForeignKey(Group, on_delete=models.CASCADE)
    permission = models.ForeignKey(Permission, on_delete=models.CASCADE)
    grant_date = models.DateField()

三、ManyToManyField 参数精解

核心参数配置

models.ManyToManyField(
    to='Tag',
    through='ArticleTag',  # 自定义中间模型
    related_name='tagged_articles',  # 反向查询名称
    related_query_name='tag',  # 反向过滤前缀
    symmetrical=False,  # 关闭对称关系
    through_fields=('article', 'tag'),  # 指定中间表字段
    limit_choices_to={'is_published': True},  # 限制可选对象
    db_table='custom_m2m_table',  # 自定义表名
    db_constraint=False,  # 禁用数据库约束
)

删除策略对照表

操作影响范围
删除主对象中间表记录自动删除
删除关联对象中间表记录自动删除
清空关联关系使用 clear() 方法删除中间表记录

四、CRUD 操作大全

1. 创建关联

# 基础创建
article = Article.objects.create(title="Django高级技巧")
python_tag = Tag.objects.create(name="Python")

# 添加关联(自动提交)
article.tags.add(python_tag) 

# 批量添加
tags = [Tag(name="Web"), Tag(name="后端")]
Tag.objects.bulk_create(tags)
article.tags.add(*tags)

# 使用中间模型
relationship = FollowRelationship.objects.create(
    from_user=user1,
    to_user=user2
)

2. 查询操作

# 基础查询
python_articles = Article.objects.filter(tags__name='Python')

# 反向聚合查询
from django.db.models import Count
tags = Tag.objects.annotate(
    article_count=Count('articles')
).filter(article_count__gt=10)

# 中间表条件查询
latest_follows = User.objects.filter(
    following_set__created_at__gte='2023-01-01'
)

# 多级嵌套查询
Article.objects.filter(
    tags__in=Tag.objects.filter(
        articles__author__username='admin'
    )
)

3. 更新操作

# 替换全部关联
article.tags.set([tag1, tag2, tag3])

# 增量更新
article.tags.add(tag4, through_defaults={'weight': 0.8})

# 中间表更新
FollowRelationship.objects.filter(
    from_user=user1
).update(created_at=timezone.now())

4. 删除操作

# 移除单个关联
article.tags.remove(python_tag)

# 清空所有关联
user.followers.clear()

# 条件删除中间表记录
FollowRelationship.objects.filter(
    created_at__lt='2020-01-01'
).delete()

五、查询优化策略

1. 预加载技术

# 基础预加载
articles = Article.objects.prefetch_related('tags')

# 嵌套预加载
users = User.objects.prefetch_related(
    Prefetch('following_set', queryset=FollowRelationship.objects.select_related('to_user'))
)

# 自定义预加载查询
tags_prefetch = Prefetch(
    'tags',
    queryset=Tag.objects.only('name')
)
Article.objects.prefetch_related(tags_prefetch)

2. 索引优化方案

class ArticleTag(models.Model):
    article = models.ForeignKey(Article, on_delete=models.CASCADE)
    tag = models.ForeignKey(Tag, on_delete=models.CASCADE)
    weight = models.FloatField(default=1.0)

    class Meta:
        indexes = [
            models.Index(fields=['article', 'tag']),  # 联合索引
            models.Index(fields=['-weight', 'tag']),  # 排序优化
            models.Index(fields=['tag', 'article'], name='reverse_idx')  # 反向查询优化
        ]

六、高级应用场景

1. 复合关联模型

class Student(models.Model):
    name = models.CharField(max_length=100)

class Course(models.Model):
    title = models.CharField(max_length=200)
    participants = models.ManyToManyField(
        Student,
        through='Enrollment',
        through_fields=('course', 'student')
    )

class Enrollment(models.Model):
    student = models.ForeignKey(Student, on_delete=models.CASCADE)
    course = models.ForeignKey(Course, on_delete=models.CASCADE)
    semester = models.CharField(max_length=20)
    grade = models.CharField(max_length=2)
    
    class Meta:
        unique_together = [('student', 'course', 'semester')]

2. 自关联网络

class Person(models.Model):
    name = models.CharField(max_length=100)
    friends = models.ManyToManyField(
        'self',
        through='Friendship',
        symmetrical=False
    )

class Friendship(models.Model):
    from_person = models.ForeignKey(
        Person,
        related_name='from_friendships',
        on_delete=models.CASCADE
    )
    to_person = models.ForeignKey(
        Person,
        related_name='to_friendships',
        on_delete=models.CASCADE
    )
    intimacy = models.IntegerField(default=50)
    
    class Meta:
        constraints = [
            models.UniqueConstraint(
                fields=['from_person', 'to_person'],
                name='unique_friendship'
            )
        ]

3. 动态过滤关联

class ActiveTagManager(models.Manager):
    def get_queryset(self):
        return super().get_queryset().filter(is_active=True)

class Article(models.Model):
    title = models.CharField(max_length=200)
    all_tags = models.ManyToManyField(Tag)
    active_tags = models.ManyToManyField(
        Tag,
        through='ActiveTag',
        related_name='active_articles'
    )

class ActiveTag(models.Model):
    article = models.ForeignKey(Article, on_delete=models.CASCADE)
    tag = models.ForeignKey(Tag, on_delete=models.CASCADE)
    is_active = models.BooleanField(default=True)
    
    objects = ActiveTagManager()

七、常见问题解决方案

1. 性能优化方案

# 批量创建关联
def bulk_add_tags(article_ids, tag_ids):
    m2m_relations = [
        Article.tags.through(
            article_id=article_id,
            tag_id=tag_id
        )
        for article_id in article_ids
        for tag_id in tag_ids
    ]
    Article.tags.through.objects.bulk_create(m2m_relations)

# 使用 iterator()
for tag in Tag.objects.iterator():
    process_tag(tag)

2. 循环依赖处理

# 字符串引用解决
class ModelA(models.Model):
    related_bs = models.ManyToManyField('ModelB')

class ModelB(models.Model):
    related_as = models.ManyToManyField(ModelA)

# 延迟添加字段
ModelA.add_to_class(
    'new_relation',
    models.ManyToManyField('ModelC')
)

3. 数据迁移策略

def migrate_old_relationships(apps, schema_editor):
    OldRelation = apps.get_model('old_app', 'OldM2M')
    NewRelation = apps.get_model('new_app', 'NewM2M')
    
    batch = []
    for old in OldRelation.objects.iterator():
        batch.append(NewRelation(
            source_id=old.source_id,
            target_id=old.target_id,
            extra_field=old.legacy_data
        ))
        if len(batch) >= 1000:
            NewRelation.objects.bulk_create(batch)
            batch = []
    if batch:
        NewRelation.objects.bulk_create(batch)

class Migration(migrations.Migration):
    dependencies = [...]
    operations = [migrations.RunPython(migrate_old_relationships)]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Yant224

点滴鼓励,汇成前行星光🌟

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值