Django 多对多中间表详解

摘要:
看看数据库中的实际内容。Django创建了三个数据表,其中app1是应用程序名称。接下来是Group和Person的ID列。这是Django默认情况下关联两个表的方式。Django提供了一个through参数来指定中间模型。您可以在此中间模型中放置其他字段,如组进入时间、邀请原因等。
一、默认中间表

  首先,模型是这样的:

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)

    def __str__(self):
        return self.name

  在Group模型中,通过members字段,以ManyToMany方式与Person模型建立了关系。

  到数据库内看一下实际的内容,Django创建了三张数据表,其中的app1是应用名。 Django 多对多中间表详解第1张

  然后在数据库中添加了下面的Person对象:   Django 多对多中间表详解第2张

  再添加下面的Group对象: Django 多对多中间表详解第3张

  来看看,中间表是个什么样子的: Django 多对多中间表详解第4张

  首先有一列id,这是Django默认添加的。然后是Group和Person的id列,这是默认情况下,Django关联两张表的方式。若要设置关联的列,可以使用to_field参数。

  可见在中间表中,并不是将两张表的数据都保存在一起,而是通过id的关联进行映射。

二、自定义中间表

  一般情况,普通的多对多已经够用,无需自己创建第三张关系表。但是某些情况可能更复杂一点,比如想保存某个人加入某个分组的时间呢?想保存进组的原因呢?

  Django提供了一个through参数,用于指定中间模型,可以将类似进组时间,邀请原因等其他字段放在这个中间模型内。例子如下:

from django.db import models

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)  # 邀请原因

  在中间表中,至少要编写两个外键字段,分别指向关联的两个模型。在本例中就是‘Person’和‘group’。 这里,我们额外增加了‘date_joined’字段,用于保存人员进组的时间,‘invite_reason’字段用于保存邀请进组的原因。

  下面依然在数据库中实际查看一下: Django 多对多中间表详解第5张

  注意中间表的名字已经变成"app2_membership"了。 Django 多对多中间表详解第6张Django 多对多中间表详解第7张

  Person和Group没有变化。 Django 多对多中间表详解第8张

  但是中间表就截然不同了!它完美的保存了我们需要的内容。

三、使用中间表

  针对上面的中间表,下面是一些使用例子:

>>> ringo = Person.objects.create(name="Ringo Starr")
>>> paul = Person.objects.create(name="Paul McCartney")
>>> beatles = Group.objects.create(name="The Beatles")
>>> m1 = Membership(person=ringo, group=beatles,
... date_joined=date(1962, 8, 16),
... invite_reason="Needed a new drummer.")
>>> m1.save()
>>> beatles.members.all()
<QuerySet [<Person: Ringo Starr>]>
>>> ringo.group_set.all()
<QuerySet [<Group: The Beatles>]>
>>> m2 = Membership.objects.create(person=paul, group=beatles,
... date_joined=date(1960, 8, 1),
... invite_reason="Wanted to form a band.")
>>> beatles.members.all()
<QuerySet [<Person: Ringo Starr>, <Person: Paul McCartney>]>

  与普通的多对多不一样,使用自定义中间表的多对多不能使用add(), create(),remove(),和set()方法来创建、删除关系,看下面:

>>> # 无效
>>> beatles.members.add(john)
>>> # 无效
>>> beatles.members.create(name="George Harrison")
>>> # 无效
>>> beatles.members.set([john, paul, ringo, george])

  为什么?因为上面的方法无法提供加入时间、邀请原因等中间模型需要的字段内容。唯一的办法只能是通过创建中间模型的实例来创建这种类型的多对多关联。但是,clear()方法是有效的,它能清空所有的多对多关系。

>>> beatles.members.clear()
>>> # 删除了中间模型的对象
>>> Membership.objects.all()
<QuerySet []>

  一旦你通过创建中间模型实例的方法建立了多对多的关联,立刻就可以像普通的多对多那样进行查询操作:

# 查找组内有Paul这个人的所有的组(以Paul开头的名字)
>>> Group.objects.filter(members__name__startswith='Paul')
<QuerySet [<Group: The Beatles>]>

  可以使用中间模型的属性进行查询:

>>> Person.objects.filter(
... group__name='The Beatles',
... membership__date_joined__gt=date(1961,1,1))
<QuerySet [<Person: Ringo Starr]>

  可以像普通模型一样使用中间模型:

>>> ringos_membership = Membership.objects.get(group=beatles, person=ringo)
>>> ringos_membership.date_joined
datetime.date(1962, 8, 16)
>>> ringos_membership.invite_reason
'Needed a new drummer.'
>>> ringos_membership = ringo.membership_set.get(group=beatles)
>>> ringos_membership.date_joined
datetime.date(1962, 8, 16)
>>> ringos_membership.invite_reason
'Needed a new drummer.'

  这一部分内容,需要结合后面的模型查询操作。

  对于中间表,有一点要注意,默认情况下,中间模型只能包含一个指向源模型的外键关系,上面例子中,也就是在Membership中只能有Person和Group外键关系各一个,不能多。否则,你必须显式的通过ManyToManyField.through_fields参数指定关联的对象。参考下面的例子:

from django.db import models

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

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

class Membership(models.Model):
    group = models.ForeignKey(Group, on_delete=models.CASCADE)
    person = models.ForeignKey(Person, on_delete=models.CASCADE)
    inviter = models.ForeignKey(
    Person,
    on_delete=models.CASCADE,
    related_name="membership_invites",
    )
    invite_reason = models.CharField(max_length=64)

免责声明:文章转载自《Django 多对多中间表详解》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇Golang的排序和查找配置之XML--读取XML文件 转存为Key-Value下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

django之数据库表的单表查询

一、添加表记录 对于单表有两种方式 # 添加数据的两种方式 # 方式一:实例化对象就是一条表记录 Frank_obj = models.Student(name ="海东",course="python",birth="2000-9-9",fenshu=80) Frank_obj.save() # 方式二: mo...

Django web框架之权限管理二

1. login登录 def login(request): if request.method=="GET": return render(request,'login.html') else: username=request.POST.get('user') password=reque...

django 后台格式化数据库查询出的日期

在项目中,我遇到这样的情况,使用ajax获取查询出来的数据,而这些数据中某个字段是日期datetime格式,在模板中显示的样式很怪异。由于前端使用了js控件,也不能使用django的模板过滤器。 所以这种情况下,我想将日期从数据库中查询出来就使用固定好的格式。 django 中直接执行sql语句查询 from django.db import connec...

Django不允许单用户多浏览器同时登录

#在扩展用户表内,多添加一个session字段,用于存放session_key models.py class UserInfo(AbstractUser): id = models.AutoField(primary_key=True) department = models.CharField(max_length=10) la...

django 菜单权限

一.什么是权限 能做哪些事情,不能做哪些事情,可以做的权限 二.设计权限 思路: web应用中,所谓的权限,其实就是一个用户能够访问的url,通过对用户访问的url进行控制,从而实现对用户权限的控制. 每个用户代表不同的的角色,每个角色具有不同的权限. 一个用户可以有多重角色,多个人也可以是一种角色(比如说一个公司可以有多个销售),所以说,用户与角色之间的...

8. Django系列之上传文件与下载-djang为服务端,requests为客户端

preface 运维平台新上线一个探测功能,需要上传文件到服务器上和下载文件从服务器上,那么我们就看看requests作为客户端,django作为服务器端怎么去处理? 对于静态文件我们不建议通过django下载,而是建议通过django返回一个重定向URL(下载文件的URL)给client,这个url是nginx提供下载,众所周知,nginx是非常牛逼的静...