内容简介:我们将给项目添加model间的关系。movie中的人物关系可以构成一个很复杂的数据模型。 同一个人,可以是演员actor,编剧writer,导演director等角色。即使脱离剧组和创作团队,简化一点, 数据模型也将包括 通过ForiengKey字段形成的一对多的关系, 通过nyToManyField字段形成的多对多的关系; 通过在ManyToManyField中使用through类来形成的多对多的关系,用来新增额外信息;顺势,我们需要一个Person类,来描述和存储电影中的人物person 编辑core
我们将给项目添加model间的关系。movie中的人物关系可以构成一个很复杂的数据模型。 同一个人,可以是演员actor,编剧writer,导演director等角色。即使脱离剧组和创作团队,简化一点, 数据模型也将包括 通过ForiengKey字段形成的一对多的关系, 通过nyToManyField字段形成的多对多的关系; 通过在ManyToManyField中使用through类来形成的多对多的关系,用来新增额外信息;
顺势,
- 创建一个Person模型
- 添加一个ForeignKey字段,从Movie到Person,追踪导演director
- 添加一个ManyToMany字段,从Movie到Person,追踪编剧writers
- 添加一个ManyToMany字段,通过一个through类(演员Actor)来追踪,谁在电影中演出以及什么角色。
- 改动model之后,进行数据库迁移
- 往电影movie详情模版中添加导演director,编剧writer,演员actors
- 添加一个PersonDetail视图,显示出电影是谁导演的,谁编剧的,以及谁出演的。
添加一个关系模型
我们需要一个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(),方便输出对象内容。
现在,Person与Movies 有着各种各样的关系。
不同类型的关系字段
Django的ORM支持映射模型之间关系的字段,包括 一对多
, 多对多
,以及 包含内部模型的多对多
。 当两个model间是 一对多
关系, 用 ForeignKey
字段,该字段可以生成一个两数据库表之间的约束。 若model中没有ForeignKey字段,Django会自动添加一个RelatedManager对象,作为属性实例。 RelatedManager类使得查询关系对象更简单。
当两个model间是多对多关系,两者都可以使用ManyToManyField();Django会在另一方给你创建一个 RelatedManager
。 你知道,关系型数据库在两表之间不能有多对多的关系。关系型数据库需要一个拥有外键的bridging桥接表,来访问相关的表。假设我们不想添加任何属性来描述这个关系,Django会自动创建和管理这个桥接表。
有时候我们想用 额外的字段
来描述一段多对多的关系,我们可以用包含 through
模型的字段:ManyToManyField(在UML中称为association联系)。这个模型有一个ForeignKey,可以到达关系的每一边,以及获取任何想获取的额外字段。
模型关系 | 对应字段 |
---|---|
一对多 | ForeignKey |
多对多 | ManyToManyField |
有内部模型的多对多 | ManyToManyField中使用through字段 借助其他模型描述模型关系 |
那,可以开始创建模型了,仔细体验下之间的关系。
Director - ForeignKey
模型中,每个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 ) 复制代码
- to='Person', Django的所有关系字段,都可以使用字符串引用以及相关模型的引用。这个参数是必须的。
- on_delete=models.SET_NULL,当删除引用的模型(实例/行)时,Django需要知道接下来要执行什么指令。SET_NULL,将会设置所有Movie实例当删除Person时,director字段设置为NULL。如果想级联删除,那么用这个对象: models.CASCADE。
- related_name='directed', 可选参数,表明这是
其他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。
Writers - ManyToManyField
两个模型之间可能存在多对多的关系。比如,一个人可以写多个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) 复制代码
Role - ManyToManyField 用一个through类
当我们想用 内部模型
来描述两个具有多对多关系的模型之间的关系时,这种类型就派上用场了。 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实例;
- movie.actors 是与Person相关的manager
- person.acting_credits 是与Movie相关的manager
- movie.role_set 是与Role相关的manager
- person.role_set 是与Role相关的manager
你可以用上面任意一个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中的人物相关联。
创建PersonView 更新MovieList
新增一个PersonDetail视图,使得movie_detail.html可以链接到。 为了创建该视图,可以用4步来完成:
- 创建一个manager,限制数据库查询次数
- 创建view
- 创建template
- 创建URL来关联view
创建一个自定义manager - PersonManager
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模型实例。
创建一个PersonDetail视图和template
现在写一个非常简单的视图, 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 不需要特意做啥就能用预处理数据。
创建MovieManager
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
。
以上所述就是小编给大家介绍的《Django2 Web实战01-启动项目-model 扩展》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- ECMASCRIPT 6 实战之 扩展运算符
- 实战开发一个Nginx扩展 (Nginx Module)
- Thinkphp实战利用钩子使用行为扩展 (Hook)
- 【php 扩展开发】扩展生成器
- 喧喧发布 1.6.0 版本,扩展机制增强,支持服务器扩展
- 为vscode编写扩展
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。