关联关系
不同对象之间的关联关系有三种。
一个对象单独对应另一个类的对象(在某些场景非常有用,比如与Django内置的用户模型相关联)。从概念上讲,这与
ForeignKey
+
unique=True
相似。
反向名称:
指定
related_name
参数,即可用
关联的对象.related_name
返回本对象。这在三种关联关系中都适用。
如果未指定
related_name
参数,则Django会将当前模型的小写名称用作默认值。
如果您希望Django不要创建反向关系,请将设置
related_name
为'+'或以'+'结束。
related_query_name
用法与
related_name
类似,只是使用时需要补写
_set
[1]
from django.conf import settings
from django.db import models
class MySpecialUser(models.Model):
user = models.OneToOneField(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
supervisor = models.OneToOneField(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name='supervisor_of',
以上,将自定义的models类和内置后台管理员账户User做了关联。使用user.myspecialuser
或user.supervisor_of
皆可查询到这边的对象。
一个问题有多个选项,一个选项只属于一个问题,那么就给选项添加外键:
from django.db import models
class Question(models.Model):
question_text = models.CharField(max_length=200)
pub_date = models.DateTimeField('date published')
class Choice(models.Model):
question = models.ForeignKey(Question, on_delete=models.CASCADE)
choice_text = models.CharField(max_length=200)
votes = models.IntegerField(default=0)
半路添加外键可能出问题,可以把数据库和迁移文件都删掉,再强制迁移。
更新外键,直接把对象赋值到外键字段:
>>> from blog.models import Blog, Entry
>>> entry = Entry.objects.get(pk=1)
>>> cheese_blog = Blog.objects.get(name="Cheddar Talk")
>>> entry.blog = cheese_blog
>>> entry.save()
一个用户可以买多个课程,一个课程可以被多个用户购买,这就是多对多关系。
class User(models.Model):
bought = models.ManyToManyField(Course, related_name='purchasers')
ManyToMany
字段可以存放多个其他表或自己表的对象。
多对多关系中的额外字段[2]
一个人与其所属的组之间存在多对多关系,因此您可以使用ManyToManyField
表示这种关系。但是,您可能希望收集很多详细信息,例如该人借该书的日期。Django允许您指定将用于管理多对多关系的模型。然后,您可以在中间模型上放置额外的字段。中间模型与ManyToManyField
使用 through
参数关联以指向将充当中间模型的模型。
from django.db import models
class User(models.Model):
name = models.CharField(max_length=20)
borrow = models.ManyToManyField(Book, related_name='borrowers', through='BorrowShip')
class Book(models.Model):
name = models.CharField(max_length=60)
class BorrowShip(models.Model):
book = models.ForeignKey(Book, on_delete=models.CASCADE)
person = models.ForeignKey(User, on_delete=models.CASCADE, related_name='borrow_set')
borrow_time = models.DateTimeField()
return_date = models.DateTimeField()
多关联的操作
多关联的添加[3]
对于多对多,使用add()
方法,单参数或多参数都可以:
>>> from blog.models import Author
>>> joe = Author.objects.create(name="Joe")
>>> entry.authors.add(joe)
>>> john = Author.objects.create(name="John")
>>> paul = Author.objects.create(name="Paul")
>>> george = Author.objects.create(name="George")
>>> ringo = Author.objects.create(name="Ringo")
>>> entry.authors.add(john, paul, george, ringo)
带额外字段的多对多关系,可以通过创建中间类来添加关系,也可以在原模型类直接创建。
创建中间类,创建时必要属性都要指定:
>>> 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()
或 set()
,使用through_defaults
设置中间类的属性:
# 添加现有的:
>>> beatles.members.add(john, through_defaults={'date_joined': date(1960, 8, 1)})
# 添加一个新创建的:
>>> beatles.members.create(name="George Harrison", through_defaults={'date_joined': date(1960, 8, 1)})
>>> beatles.members.set([john, paul, ringo, george], through_defaults={'date_joined': date(1960, 8, 1)})
多关联的查询
多对多关系中,每一个多对多字段都相当于存了一张数据列表,对象.字段
可以得到objects
:
user = User.objects.get(pk=1)
courses = user.bought.all()
bought是一个多对多字段,产生的courses就是一个QuerySet
,可以继续进行筛选或迭代操作。
以上是此表对彼表的查询。此表中设置的related_name
参数,用法与多对一和一对一关系中的相似,代表在相关联的彼表的对象中可以用彼表对象.related_name
,来查询与某彼表对象关联的此表对象列表。
对中间类额外字段的查询,可以先查到所有外键的对象,再从关联中间表查询键匹配的对象。若想从原模型类直接查到关联关系中间表的额外字段,需使用中间表设置的related_name
。
user = User.objects.get(tempid=tempid)
userBorrow1 = BorrowShip.objects.filter(person=user)
userBorrow2 = user.borrow_set.filter(person=user)
多关联的移除
使用remove()
方法移除一个关联,使用clear()
方法清空所有多关联:
ringo = Person.objects.get(name='ringo')
beatles.members.remove(ringo) # 移除一个
beatles.members.clear() # 清空所有
使用remove()
时需要注意的是,如果有多个ringo都在这个关联中,所有的ringo都会移除。若要精确移除,需要针对中间表操作,并添加限定条件:
book = Book.objects.get(id=data['bookid'])
user = User.objects.get(tempid=data['tempid'])
ships = BorrowShip.objects.all()
if data['opt']=='unfavor':
ships.remove(book=book,person=user,borrow_state='favor')
elif data['opt']=='unbooking':
ships.remove(book=book,person=user,borrow_state='booking')
约束 Meta.constraints
Meta用于设定模型中的非字段的信息,如排序方式、数据库表名、约束关系等。可用的约束关系有:
检查约束:限制字段的值或大小
唯一约束:实现联合唯一性约束,可以设置条件
一般用法举例:
class BookingShip(models.Model):
room = models.ForeignKey(Room, on_delete=models.CASCADE)
person = models.ForeignKey(User, on_delete=models.CASCADE)
date = models.DateTimeField()
class Meta:
constraints = [
# 18岁以上才能订房间
models.CheckConstraint(check=Q(age__gte=18), name='age_gte_18'),
# 每个房间每天只能被一个人订
models.UniqueConstraint(fields=['room', 'date', 'person'], name='unique_booking')
唯一性约束UniqueConstraint
可以添加condition
参数实现比较复杂的逻辑约束:
class Options(Answer):
correct = models.BooleanField()
question = models.ForeignKey(Question,models.CASCADE,verbose_name='所属问题',related_name='answers',null=True)
# 题目有三种:单选题有一个正确答案和多个错误选项,多选题不限制,判断题有且仅有一个正确选项
class Meta:
constraints = [
# 题目是单选且此选项正确,这样的选项只能有一个
models.UniqueConstraint(
fields=['question'],name='single_unique_correct',
condition=(Q(correct=True)&Q(question__ques_type='single'))),
# 如果题目是判断,题目和正误符合联合唯一性
models.UniqueConstraint(
fields=['question','correct'],name='single_unique_correct',
condition=Q(question__ques_type='true_false'))
功能更强大的UniqueConstraint
用于全面代替unique_together
,今后可能会将unique_together
淘汰。