做者:Hubery 时间:2018.8.31html
接上文: Django2 web实战01-启动项目java
咱们将给项目添加model间的关系。movie中的人物关系能够构成一个很复杂的数据模型。 同一我的,能够是演员actor,编剧writer,导演director等角色。即便脱离剧组和创做团队,简化一点, 数据模型也将包括 经过ForiengKey字段造成的一对多的关系, 经过nyToManyField字段造成的多对多的关系; 经过在ManyToManyField中使用through类来造成的多对多的关系,用来新增额外信息;python
顺势,web
咱们须要一个Person类,来描述和存储电影中的人物person 编辑core/models.py数据库
from django.db import models
class Movie(models.Model):
NOT_RATED = 0
RATED_G = 1
RATED_PG = 2
RATED_R = 3
RATINGS = (
(NOT_RATED, 'NR - 没有评分'),
(RATED_G, 'G - 普通观众'),
(RATED_PG, 'PG - 父母的引导和规范'),
(RATED_R, 'R - 限制级'),
)
title = models.CharField(max_length=140)
plot = models.TextField()
year = models.PositiveIntegerField()
rating = models.IntegerField(
choices=RATINGS,
default=NOT_RATED
)
runtime = models.PositiveIntegerField()
website = models.URLField(blank=True)
class Meta:
ordering = ('-year', 'title')
def __str__(self):
return '{} ({})'.format(self.title, self.year)
class Person(models.Model):
first_name = models.CharField(max_length=140)
last_name = models.CharField(max_length=140)
born = models.DateField()
died = models.DateField(null=True, blank=True)
class Meta:
ordering = (
'last_name', 'first_name'
)
def __str__(self):
if self.died:
return '{}, {} ({}-{})'.format(
self.last_name,
self.first_name,
self.born,
self.died)
return '{}, {} ({})'.format(
self.last_name,
self.first_name,
self.born)
复制代码
Person中,有DataField
字段,用适当的数据库列类型来记录时间类型数据。 全部字段都支持null参数,表示列字段是否应该支持NULL类型的值。 died字段设置了null=True,代表咱们能够记录此person是否died。 __str__
方法相似java的toString(),方便输出对象内容。django
如今,Person与Movies 有着各类各样的关系。bash
Django的ORM支持映射模型之间关系的字段,包括一对多
,多对多
,以及包含内部模型的多对多
。 当两个model间是一对多
关系, 用ForeignKey
字段,该字段能够生成一个两数据库表之间的约束。 若model中没有ForeignKey字段,Django会自动添加一个RelatedManager对象,做为属性实例。 RelatedManager类使得查询关系对象更简单。post
当两个model间是多对多关系,二者均可以使用ManyToManyField();Django会在另外一方给你建立一个RelatedManager
。 你知道,关系型数据库在两表之间不能有多对多的关系。关系型数据库须要一个拥有外键的bridging桥接表,来访问相关的表。假设咱们不想添加任何属性来描述这个关系,Django会自动建立和管理这个桥接表。fetch
有时候咱们想用额外的字段
来描述一段多对多的关系,咱们能够用包含through
模型的字段:ManyToManyField(在UML中称为association联系)。这个模型有一个ForeignKey,能够到达关系的每一边,以及获取任何想获取的额外字段。ui
模型关系 | 对应字段 |
---|---|
一对多 | ForeignKey |
多对多 | ManyToManyField |
有内部模型的多对多 | ManyToManyField中使用through字段 借助其余模型描述模型关系 |
那,能够开始建立模型了,仔细体验下之间的关系。
模型中,每一个movie都有一个director导演,但每一个导演能够拍过不少做品。那么,用ForeignKey字段来为movie添加一个导演director; 编辑core/models.py Movie类中新增这一段
director = models.ForeignKey(
to='Person',
related_name='directed',
on_delete=models.SET_NULL,
null=True,
blank=True
)
复制代码
其余model的RelatedManager实例的名字
。这个表示:让咱们查询这我的所拍过的全部Movie实例。 若是没有这个参数,那么Person会有个叫movie_set的属性。咱们将会得到多个不一样的关系,Movie和Person(writer/director/actors),因此movie_set会变得隐晦不清,所以咱们必须提供一个related_name
。这是首次向已经存在的model中添加字段。咱们必须设置null=True或者提供一个默认参数。若是没有,Django在执行migration的时候会强制咱们这么作。由于,当咱们作数据库迁移的时候,Django必须确保这个实例在数据库表中存在。当数据库添加这个字段后,须要知道插入已经存在的行rows的数据是啥。上面代码中的director字段,咱们能够接受NULL值。
咱们已经向Movie模型中添加了一个字段director,向Person实例中添加了一个叫directed的属性。这个directed是RelatedManager
类型。
RelatedManager是个颇有用的类,相似于模型的默认Manager,objects,能够跨表自动处理之间的关系。至关于一个模型持有了另外一个模型的引用/句柄,能够操做另外一个模型。
对比一下:
person.directed.create()
复制代码
Movie.objects.create()
复制代码
这俩方法都会建立一个Movie,但person.directed.create()会确保新Movie做为director.RelatedManager,同时还提供了add和remove方法,以便咱们能够经过调用person.directed.add(movie)将一个Movie添加到一个directed的Person集合。 相同的,remove()方法差很少意思,会从关系中移除一个model。
两个模型之间可能存在多对多的关系。好比,一我的能够写多个movies,一样一个movie能够有多我的来写完。 向Movie模型中添加一个writers字段,处理多对多: core/models.py
writers = models.ManyToManyField(
to='Person',
related_name='writing_credits',
blank=True
)
复制代码
一个ManyToManyField建立了一个多对多的关系,充当了RelatedManager角色,保证了用户查询和建立model。 再次用到了related_name,避免给Person一个movie_set属性,直接给赋值一个writing_credits
属性充当
一个RelatedManager
,不然可能会形成混乱。 上面代码中,两端都有RelatedManager,因此这俩操做等效:
person.writing_credits.add(movie)
复制代码
movie.writers.add(person)
复制代码
当咱们想用内部模型
来描述两个具备多对多关系的模型之间的关系时,这种类型就派上用场了。 Django容许咱们经过建立一个模型来实现这一点,该模型描述了多对多关系中两个模型之间的链接表
join table。 新增一个Role中间类,与Movie平级; Movie中新增一个actors
actors = models.ManyToManyField(
to='Person',
through='Role',
related_name='acting_credits',
blank=True
)
复制代码
class Role(models.Model):
movie = models.ForeignKey(Movie, on_delete=models.DO_NOTHING)
person = models.ForeignKey(Person, on_delete=models.DO_NOTHING)
name = models.CharField(max_length=140)
class Meta:
unique_together = ('movie', 'person', 'name')
def __str__(self):
return "{} {} {}".format(self.movie_id, self.person_id, self.name)
复制代码
这看起来很像以前的ManyToManyField,除了咱们同时设置了to和through参数。 Role模型看起来很是像是要涉及一个链接表,join table;有着与每个表的多对多关系。同时还有个name字段用来描述Role。
Role有一个独一无二的约束。须要movie/person/name一块儿组成,在Role的内部类Meta上设置unique_together属性。
此时,ManyToManyField会建立4个新的RelatedManager实例;
你能够用上面任意一个manager来查询model,不过只有role_set managers才能建立model或者修改模型关系,由于role_set有内部类。若是你尝试着运行movie.actors.add(person),Django会抛IntegrityError
异常,由于没有任何方式能够给Role.name字段赋值。然而,你能够这样:movie.role_set.add(person=person, name='hubery')。
python manage.py makemigrations
python manage.py migrates
复制代码
接下来,咱们就可让movie页和movie中的人物相关联。
新增一个PersonDetail视图,使得movie_detail.html能够连接到。 为了建立该视图,能够用4步来完成:
PersonDetail视图会列出 一我的在表演,写做,或导演过的全部电影。在template中,咱们会打印出每一个演员表中的每部电影的名称。 为了不数据库出现大量查询,给models建立新的managers,返回QuerySets。 Django中,每次从一个关系中访问一个属性,Django都会经过查询数据库来获取相关的数据项。 在一个Person出如今N个movies中的状况下,会出现N次数据库查询。咱们能够经过prefetch_related()方法来避免这种状况。经过prefetch_related()方法,Django将会经过一个额外的查询来获取单个关系中的相关数据。 这其中有个问题,若是咱们最终没有使用预处理的数据,那么此次查询会浪费时间和内存。
接下来,建立一个PersonManager,有个新方法all_with_prefetch_movies(),让PersonManager成为Person的默认manager: core/models.py
class PersonManager(models.Manager):
def all_with_prefetch_movies(self):
qs = self.get_queryset()
return qs.prefetch_related(
'directed',
'writing_credits',
'role_set__movie')
class Person(models.Model): objects = PersonManager()
复制代码
PersonManager提供了默认manager一样的方法,应为其继承自models.Manager。定义了一个新方法,用get_queryset()来获取QuerySet,通知它来预获取相关的model。 QuerySets是惰性的,在评估查询集以前,不会与数据库进行交互。
DetailView经过PK调用get()方法获取model以前不会评估查询。
prefetch_related()方法须要一次或屡次查询,查询初始化后,会自动查询相关models。当你从相关的QuerySet中查询model时,Django不会去查询它,由于它已经在QuerySet中。
一次查询是一个Django的QuerySet用来表述模型中的字段或RelatedManager。甚至能够经过将关系字段或RelatedManager的名称与相关模型字段分割为两个下划线,实现跨越关系:
Movie.objects.all().filter(actors__last_name='Freeman',
actors__first_name='Morgan')
复制代码
这个调用会返回一个QuerySet,包含演员Morgan Freeman的全部Movie模型实例。
如今写一个很是简单的视图, core/views.py
class PersonDetail(DetailView):
queryset = Person.objects.all_with_prefetch_movies()
复制代码
DetailView比较特殊,没有提供mode属性。 相反,咱们给他传入一个PersonManager类的QuerySet对象。当DetailView用filter()方法和get()方法来获取model实例时,DetailView会从model的实例类名中,派生出模版template的名称,就像咱们在模型类中提供了模型类做为属性同样。
那, 建立一个template: core/template/core/person_detail.html
{% extends 'base.html' %}
{% block title %}
{{ object.first_name }}
{{ object.last_name }}
{% endblock %}
{% block main %}
<h1>{{ object }}</h1>
<h2>Actor</h2>
<ul>
{% for role in object.role_set.all %}
<li>
<a href="{% url 'core:MovieDetail' role.movie.id %}">
{{ role.movie }}
</a>
{{ role.name }}
</li>
{% endfor %}
</ul>
<h2>Writer</h2>
<ul>
{% for movie in object.writing_credits.all %}
<li>
<a href="{% url 'core:MovieDetail' movie.id %}">
{{ movie }}
</a>
</li>
{% endfor %}
</ul>
<h2>Director</h2>
<ul>
{% for movie in object.directed.all %}
<li>
<a href="{% url 'core:MovieDetail' movie.id %}">
{{ movie }}
</a>
</li>
{% endfor %}
</ul>
{% endblock %}
复制代码
template 不须要特地作啥就能用预处理数据。
class MovieManager(models.Manager):
def all_with_related_persons(self):
qs = self.get_queryset()
qs = qs.select_related(
'director')
qs.prefetch_related(
'writers', 'actors')
return qs
复制代码
设置默认manager
class Movie(models.Model):
objects = MovieManager()
复制代码
MovieManager介绍了另一个方法:select_related。与prefetch_related()方法很像,但只有一个关系模型存在时采用select_related ,好比只有一个ForeignKey字段。
QuerySet方法 | 说明 |
---|---|
prefetch_related() | 当关系可能涉及多个模型时采用,如只有一个ForeignKEy |
select_related() | 只有一个关系模型存在时采用,若有MaynToMany或RelatedManager |
如今,能够直接使用查询结果来更新MovieDetail,而不是直接使用model:
class MovieDetail(DetailView):
queryset = (
Movie.objects.all_with_related_persons())
复制代码
建立Person模型,并在Movie和Person之间创建了许多种关系。 ForeignKey 创建一对多的关系; ManyToManyField 创建多对多关系; ManyToManyField 中经过提供through模型来建立多对多关系,用中介类来给多对多关系提供额外的信息;
建立一个PersonDetail视图来显示Person模型实例;用一个自定义模型manager来控制数据库的查询次数。
天星技术团QQ:557247785
。