many-to-many
定义一个many-to-many的关系,使用ManyToManyField.他需要一个位置参数,要关联的类。举个简单的例子:比如我们做披萨,可以使用很多原材料,当时呢,一个原材料也可以用于做很多种披萨。那么:
from django.db import models
class Topping(models.Model):
# ...
pass
class Pizza(models.Model):
# ...
toppings = models.ManyToManyField(Topping)
正如ForeignKey,你可以创建递归关系(一个对象many-to-many关联到自身)
注意;ManyToManyField放在哪个model中都没关系,但是不能两个Model中都放。
当我们处理一个简单的many-to-many关系时,标准的ManyToManyField就够用了。然而,有时候我们或许需要在两个model中使用relationship关联数据.举例来说吧,比如,跟踪一个音乐家属于哪个音乐组合,在person model和所属组group model存在一个many-to-many的关系,这个时候你可以使用ManyToManyField来代表这种关系,但是呢,在membership中又有很多细节想去收集。比如,谁什么时候加入到组合。对于这种情况,django允许我们指定model用于管理many-to-many关系。你可以在该中介Model中,添加额外的字段。该中介model使用通过ManyToManyField的through参数来指定中介model.举例:
from django.db import models
class Person(models.Model):
name = models.CharField(max_length=128)
def __str__(self): # __unicode__ on Python 2
return self.name
class Group(models.Model):
name = models.CharField(max_length=128)
members = models.ManyToManyField(Person, through='Membership')
def __str__(self): # __unicode__ on Python 2
return self.name
中间媒介model:
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)
对于中介model有几点限制之处,总结如下:
- 中介model必须包含也只能包含一个外键跟源model,或者你通过ManyToManyField.through_fields明确指定外键,如果在多于一个外键存在时,而你并未指定through_fields的值,就会报验证错误。同样的限制应用到目标model上。
- 对于一个和自身有many-to-many关系的model,和同一个model有两个外键是被允许的。但是他们被当做是两端不同的many-to-many关系。
- 当定义一个使用中介model并且和自身有many-to-many关系的model时,必须使用symmetrical=False
下面是具体的使用情况:
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>]>
不像一般的many-to-many字段,不使用add(),create(),set()来创建关系。如下的操场是行不通的:
beatles.members.add(john)
beatles.members.create(name="George Harrison")
beatles.members.set([john, paul, ringo, george])
同样,remove()方法也是不能正常使用的。但是,clear()方法可以正常使用,他会删除中介model所有的实例。
beatles.members.clear()
# Note that this deletes the intermediate model instances
Membership.objects.all()
<QuerySet []>
一旦通过中介model创建好了many-to-many关系,你就可以像普通的many-to-many查询一样。
# Find all the groups with a member whose name starts with 'Paul'
Group.objects.filter(members__name__startswith='Paul')
<QuerySet [<Group: The Beatles>]>
# Find all the members of the Beatles that joined after 1 Jan 1961
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.'