时间:2023-06-07 09:15:02 | 来源:网站运营
时间:2023-06-07 09:15:02 来源:网站运营
Django2.0创建电影评分网站(1):git checkout 0.1.0
即可查看本章完整代码Django
项目Django App
创建我们的第一个模型,视图,和模板requirements.dev.txt
,并写入pip install -r requeirements.dev.txt
。django-admin startproject config
mkdir django
mv config django
tree .
,显示如下:Django
大家常见的是用sqlite
,但现在postgresql
才是王道,所以我们也用它。DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), }}# 将其更改为DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql', 'NAME': 'mymdb', 'USER': 'mymdb', 'PASSWORD': 'development', 'HOST': '127.0.0.1', 'PORT': '5432', }}
$ cd django$ python manage.py startapp core
django/config/seetings.py
在INSTALLED_APPS
下面添加:INSTALLED_APPS = [ 'core', 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles',]
django/core/models.py
后添加:from django.db import modelsclass Movie(models.Model): NOT_RATED = 0 RATED_G = 1 RATED_PG = 2 RATED_R = 3 RATINGS = ( (NOT_RATED, 'NR - Not Rated'), (RATED_G, 'G - General Audiences'), (RATED_PG, 'PG - Parental Guidance ' 'Suggested'), (RATED_R, 'R - Restricted'), ) 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) def __str__(self): return '{} ({})'.format( self.title, self.year)
django模型是一个从Model派生出来的具有一个或多个的字段的类。在数据库中,model类类似于数据库,Field类对应于列,Model的实例对应于行。Django特有的ORM可以让我们直接使用django来操作数据库,而不是去写sql。varchar
列,长度为140.需要你填入最大长度text
列,通常无最大长度integer
列,django将在保存之前检查其是否为0或者更高VaildationError
。如果创建模型时未提供默认参数,则默认参数会提供默认值。varchar(200)
。URLField还附带验证,检查其值是否为有效的网络(http / https / ftp / ftps
)URL。 admin应用程序使用空白参数来知道是否需要一个值(它不会影响数据库)。__str__
模块是帮助Django将模型转换为字符串的方法。CREATE DATABASE mymdb;CREATE USER mymdb;GRANT ALL ON DATABASE mymdb to "mymdb";ALTER USER mymdb PASSWORD 'development';ALTER USER mymdb CREATEDB;
上述SQL语句将为我们的Django项目创建数据库和用户。 GRANT语句确保我们的mymdb用户可以访问数据库。 然后,我们在mymdb用户上设置密码(确保它与settings.py文件中的密码相同)。 最后,我们授予mymdb用户创建新数据库的权限,Django将在运行测试时使用这些数据库创建测试数据库。$ cd django$ python manage.py makemigrations coreMigrations for 'core': core/migrations/0001_initial.py - Create model Movie$ python manage.py migrate core Operations to perform: Apply all migrations: coreRunning migrations: Applying core.0001_initial... OK$ python manage.py dbshellpsql (9.6.1, server 9.6.3)Type "help" for help.mymdb=> /dt List of relations Schema | Name | Type | Owner --------+-------------------+-------+------- public | core_movie | table | mymdb public | django_migrations | table | mymdb(2 rows)mymdb=> /q$ python manage.py migrate Operations to perform: Apply all migrations: admin, auth, contenttypes, core, sessionsRunning migrations: Applying contenttypes.0001_initial... OK Applying auth.0001_initial... OK Applying admin.0001_initial... OK Applying admin.0002_logentry_remove_auto_add... OK Applying contenttypes.0002_remove_content_type_name... OK Applying auth.0002_alter_permission_name_max_length... OK Applying auth.0003_alter_user_email_max_length... OK Applying auth.0004_alter_user_username_opts... OK Applying auth.0005_alter_user_last_login_null... OK Applying auth.0006_require_contenttypes_0002... OK Applying auth.0007_alter_validators_add_error_messages... OK Applying auth.0008_alter_user_username_max_length... OK Applying sessions.0001_initial... OK
$ cd django$ python manage.py shellPython 3.4.6 (default, Aug 4 2017, 15:21:32) [GCC 4.2.1 Compatible Apple LLVM 8.1.0 (clang-802.0.42)] on darwinType "help", "copyright", "credits" or "license" for more information.(InteractiveConsole)>>> from core.models import Movie>>> sleuth = Movie.objects.create(... title='Sleuth',... plot='An snobbish writer who loves games'... ' invites his wife/'s lover for a battle of wits.',... year=1972,... runtime=138,... )>>> sleuth.id1>>> sleuth.get_rating_display()'NR - Not Rated'
objects
是模型的默认管理器。管理员是查询模型表格的界面。它还提供了用于创建和保存实例的create()方法。每个模型必须至少有一个管理者,并且Django提供了一个默认管理。通常建议创建一个自定义管理器; 我们稍后会在添加人员和模型关系部分中看到。id
是主键,Django自动创建get_rating_display()
是Django添加的一种方法,因为评分字段有一个选择元组。 我们不必在我们的create()调用中提供评分,因为评分字段具有默认值(0)。get_rating_display()方法查找该值并返回相应的显示值。Django将为每个Field属性生成一个像这样的方法,并带有一个choices参数。django/core/admin.py
from django.contrib import adminfrom core.models import Movieadmin.site.register(Movie)
然后执行$ cd django$ python manage.py createsuperuser Username (leave blank to use 'tomaratyn'): 这里填你想注册的名字Email address: 这里填邮箱Password: 密码Password (again): 密码Superuser created successfully.$ python manage.py runserverPerforming system checks...System check identified no issues (0 silenced).September 12, 2017 - 20:31:54Django version 1.11.5, using settings 'config.settings'Starting development server at http://127.0.0.1:8000/Quit the server with CONTROL-C.
打开浏览器进入http://127.0.0.1:8000/
,你将看到如下图django/core/views.py
中from django.views.generic import ListViewfrom core.models import Movieclass MovieList(ListView): model = Movie
ListView
至少需要一个模型属性。它会查询模型中所有行,将其传递给模板,并在响应中返回呈现的模板。它还提供一些钩子,我们可以用它来代替默认行为,这些行为已经完全被记录。ListView
是如何知道去查询在Movie
中所有对象? 每个模型都有一个默认管理器。Manager类主要用于通过提供返回QuerySet
的方法(如all()
)查询对象。ListView检查它是否具有模板属性,如果它存在,就知道模型类具有默认管理器,并且调用了all()
方法。ListView
还为我们提供了模板放置位置的惯例。如下:<app_name>/<model_name>_list.html
django/core/templates/core/movie_list.html
来写第一个模板:<!DOCTYPE html><html> <body> <ul> {% for movie in object_list %} <li>{{ movie }}</li> {% empty %} <li> No movies yet. </li> {% endfor %} </ul> <p> Using https? {{ request.is_secure|yesno }} </p> </body></html>
在这里查看详细的python模板知识。django/config/urls.py
,我们在我们的每个app中也创建一个路由,django/core/urls.py
,输入:from django.urls import pathfrom . import viewsapp_name = 'core'urlpatterns = [ path('movies', views.MovieList.as_view(), name='MovieList'),]
最基本的,URLConf
是一个带有urlpatterns
属性的模块,它是一个路径列表。路径由一个字符串组成,该字符串描述一个字符串,描述所讨论的路径和可调用的路径。CBV不可调用,所以基类View
有一个返回可调用的静态as_view()
方法。FBV可以作为回调传入(没有()运算符,它会执行它们)。URLConf
到根目录的URLConf
,更改django/config/urls.py
.from django.urls import path, includefrom django.contrib import adminimport core.urlsurlpatterns = [ path('admin/', admin.site.urls), path('', include( core.urls, namespace='core')),]
$ cd django$ python manage.py runserver
在浏览器中输入,http://127.0.0.1:8000/movies
MovieDetail
视图movie_detail.html
模板django/core/views.py
下写入:from django.views.generic import ( ListView, DetailView,)from core.models import Movieclass MovieDetail(DetailView): model = Movieclass MovieList(ListView): model = Movie
DetailView
要求path()
对象在路径字符串中包含pk
或slug
,以便DetailView
可以将该值传递给QuerySet
以查询特定的模型实例。 一个slu
是一个简短的URL
友好标签,经常用于内容较重的网站,因为它是SEO
友好的。$ mkdir django/templates
在settings.py中更改:TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [ os.path.join(BASE_DIR, 'templates'), ], 'APP_DIRS': True, 'OPTIONS': { # omittted for brevity }, },]
Django
中对模板的查找是从BASED_DIR
开始递归查找每一个名字是templates
的文件夹下的HTML
文件。django/templates/base.html
中写入:<!DOCTYPE html><html lang="en" ><head > <meta charset="UTF-8" > <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" > <link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css" integrity="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M" rel="stylesheet" crossorigin="anonymous" > <title > {% block title %}MyMDB{% endblock %} </title> <style> .mymdb-masthead { background-color: #EEEEEE; margin-bottom: 1em; } </style></head ><body ><div class="mymdb-masthead"> <div class="container"> <nav class="nav"> <div class="navbar-brand">MyMDB</div> <a class="nav-link" href="{% url 'core:MovieList' %}" > Movies </a> </nav> </div></div><div class="container"> <div class="row"> <div class="col-sm-8 mymdb-main"> {% block main %}{% endblock %} </div> <div class="col-sm-3 offset-sm-1 mymdb-sidebar" > {% block sidebar %}{% endblock %} </div> </div></div></body ></html >
大多数HTML实际上是bootstrap的样板,但我们在其中添加了一些python标记。{%block title%} MyMDB {%endblock%}
:这会创建一个其他模板可以替换的块。 如果该块未被替换,则将使用来自父模板的内容。 href =“{% url 'core:MovieList' %}“
:url标记将为指定的路径生成一个URL路径。 URL
名称应该被引用为<app_namespace>:<name>
; 在我们的例子中,core
是核心应用程序的名称空间(per django/core/urls.py
),MovieList
是MovieList
视图的URL
的名称。django/core/templates/core/movie_detail.html
中添加:{% extends 'base.html' %}{% block title %} {{ object.title }} - {{ block.super }}{% endblock %}{% block main %}<h1>{{ object }}</h1><p class="lead">{{ object.plot }}</p>{% endblock %}{% block sidebar %}<div>This movie is rated: <span class="badge badge-primary"> {{ object.get_rating_display }} </span></div>{% endblock %}
这个模板中有很多HTML
,因为base.html
已经有了。 所有MovieDetail.html
所要做的就是为base.html
定义的块提供值。 我们来看看一些新的标签:{%extends'base.html'%}
:如果一个模板想要扩展另一个模板,第一行必须是一个扩展标签。 Django
将查找基本模板(可以扩展另一个模板)并首先执行它,然后替换块。 扩展另一个模板的模板不能包含块以外的内容,因为它不明确放置该内容的位置。{{object.title}} - {{block.super}}
:我们引用block.super
在title
模板里面。block.super返回基本模板中标题模板块的内容。{{object.get_rating_display}}
:Django模板语言不使用()
来执行该方法,只是通过名称引用它将执行该方法。from django.urls import pathfrom . import viewsurlpatterns = [ path('movies', views.MovieList.as_view(), name='MovieList'), path('movie/<int:pk>', views.MovieDetail.as_view(), name='MovieDetail'),]
python manage.py runserver
启动,显示如下{% extends 'base.html' %}{% block title %}All The Movies{% endblock %}{% block main %}<ul> {% for movie in object_list %} <li> <a href="{% url 'core:MovieDetail' pk=movie.id %}"> {{ movie }} </a> </li> {% endfor %} </ul>{% endblock %}
我们也看到url
标记与一个命名参数pk
一起使用,因为MovieDetail URL
需要一个pk
参数。 如果没有提供参数,那么Django
会在渲染时引发NoReverseMatch
异常,导致500
错误。class Movie(models.Model): # constants and fields omitted for brevity class Meta: ordering = ('-year', 'title') def __str__(self): return '{} ({})'.format( self.title, self.year)
排序为一个字符串列表或者元组,其是字段名称组成,字符串可以选择以-表示降序的字符作为前缀。('-year', 'title')
等价于 ORDER BY year DESC, title
.Meta
类添加排序将意味着来自模型管理器的QuerySets
将被排序。mian
模板部分下边添加分页{% block main %} <ul > {% for movie in object_list %} <li > <a href="{% url 'core:MovieDetail' pk=movie.id %}" > {{ movie }} </a > </li > {% endfor %} </ul > {% if is_paginated %} <nav > <ul class="pagination" > <li class="page-item" > <a href="{% url 'core:MovieList' %}?page=1" class="page-link" > First </a > </li > {% if page_obj.has_previous %} <li class="page-item" > <a href="{% url 'core:MovieList' %}?page={{ page_obj.previous_page_number }}" class="page-link" > {{ page_obj.previous_page_number }} </a > </li > {% endif %} <li class="page-item active" > <a href="{% url 'core:MovieList' %}?page={{ page_obj.number }}" class="page-link" > {{ page_obj.number }} </a > </li > {% if page_obj.has_next %} <li class="page-item" > <a href="{% url 'core:MovieList' %}?page={{ page_obj.next_page_number }}" class="page-link" > {{ page_obj.next_page_number }} </a > </li > {% endif %} <li class="page-item" > <a href="{% url 'core:MovieList' %}?page=last" class="page-link" > Last </a > </li > </ul > </nav > {% endif %}{% endblock %}
我们现在来看下其中终于的点:page_obj
是一个pag
类型,它知道关于这个页面结果的信息。我们用它来检查是否有使用has_next()/ has_previous()
的下一个/上一个页面(我们不需要在Django模板语言中放置(),但has_next()
是一个方法而不是属性)。我们也使用它来获取next_page_number()/ previous_page_number()
。请注意,在检索它们之前,使用has _ *()
方法检查下一个/上一个页码的存在非常重要。 如果它们在检索时不存在,则会引发EmptyPage
异常。object_list
继续可用并保存正确的值。 尽管page_obj
在page_obj.object_list
中封装了此页面的结果,但ListView
确实可以继续使用object_list
,并且我们的模板不会中断。django/templates/404.html
{% extends "base.html" %}{% block title %}Not Found{% endblock %}{% block main %}<h1>Not Found</h1><p>Sorry that reel has gone missing.</p>{% endblock %}
目前,如果您有一个未使用的URL,例如http://localhost:8000/not-a-real-page
,您将看不到我们的自定义404模板,因为Django的DEBUG
设置在settings.py
中为True
。 要使我们的404模板可见,我们需要更改settings.py中的DEBUG
和ALLOWED_HOSTS
设置:DEBUG = FalseALLOWED_HOSTS = [ 'localhost', '127.0.0.1']
打开http://127.0.0.1:8000/not-a-real-page
,显示如下:from django.test import TestCasefrom django.test.client import / RequestFactoryfrom django.urls.base import reversefrom core.models import Moviefrom core.views import MovieListclass MovieListPaginationTestCase(TestCase): ACTIVE_PAGINATION_HTML = """ <li class="page-item active"> <a href="{}?page={}" class="page-link">{}</a> </li> """ def setUp(self): for n in range(15): Movie.objects.create( title='Title {}'.format(n), year=1990 + n, runtime=100, ) def testFirstPage(self): movie_list_path = reverse('core:MovieList') request = RequestFactory().get(path=movie_list_path) response = MovieList.as_view()(request) self.assertEqual(200, response.status_code) self.assertTrue(response.context_data['is_paginated']) self.assertInHTML( self.ACTIVE_PAGINATION_HTML.format( movie_list_path, 1, 1), response.rendered_content)
我们来看看其中有意思的点:class MovieListPaginationTestCase(TestCase)
:TestCase
是所有Django的基础测试集。它有许多内置的便利,包括更多的断言方法。def setUp(self)
像大多的测试框架一样,Django的TestCase类也遵循测试启动和测试结束。数据库在每次测试之间进行清理,所以我们不必担心删除我们添加的任何模型。def testFirstPage(self)
:一个方法是一个测试,只要其前缀有test。Movie_list_path = reverse('core:MovieList')
:reverse()
是之前提到的,并且是Django模板标签的Python等价物。 它会将名称解析为路径。request = RequestFactory().get(path = movie_list_path)
:RequestFactory是创建假HTTP请求的便利工厂。 一个RequestFactory有一些简便的方法来创建GET,POST和PUT请求,方法是以动词(例如GET请求的get())命名。 在我们的例子中,提供的路径对象并不重要,但其他视图可能需要检查请求的路径。$ cd django$ python manage.py test Creating test database for alias 'default'...System check identified no issues (0 silenced)..----------------------------------------------------------------------Ran 1 test in 0.035sOKDestroying test database for alias 'default'...
最后,我们已经可以确信我们的分页应用可以好好工作了。ForiengKey
字段的一对多关系,使用ManyToManyField
的多对多关系以及添加关于使用ManyToManyField
中的through
类实现多对多关系。Person
模型ForeignKey
字段用以从Movie
到Person
去追踪导演ManyToManyField
从Movie
到Person
去追踪作家ManyToManyField
和through
类去追踪哪些人表演以及他们的关系PersonDetail
视图添加到列表中,该视图表示导演,作家,和表演者的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)
DateField
用于跟踪基于日期的数据,在数据库中使用适当的列类型(Postgres上的日期)和Python中的datetime.dat
e。 Django还提供了一个DateTimeField
来存储日期和时间。null
参数(默认为False
),该参数指示列是否应接受NULL
SQL值(由Python中的None表示)。 我们标记死亡为支持NULL
,以便我们可以记录人们的生活或死亡。 然后,在__str __()方法中,如果有人存活或死亡,我们会打印出不同的字符串表示形式。ForeignKey
字段,该字段将在两个表之间创建具有外键(FK)约束(假设有数据库支持)的列。在没有ForeignKey
字段的模型中,Django会自动添加一个RelatedManager
对象作为实例属性。 RelatedManager
类可以更轻松地查询关系中的对象。我们将在下面的章节中看看这个例子。ManyToManyField()
; Django将为您创建一个RelatedManager
。如您所知,关系数据库实际上不能在两个表之间建立多对多的关系。相反,关系数据库要求每个相关表都带有外键的桥接表。假设我们不想添加任何描述关系的属性,Django会自动为我们创建并管理这个桥接表。through
模型的ManyToManyField
(有时称为UML / OO中的关联类)。这个模型将有一个ForeignKey
到关系的每一边以及我们想要的任何额外字段。class Movie(models.Model): # constants, methods, Meta class and other fields omitted for brevity. director = models.ForeignKey( to='Person', on_delete=models.SET_NULL, related_name='directed', null=True, blank=True)
to='Person
:Django的所有关系字段都可以接受字符串引用以及相关模型的引用。这个参数是必须的。on_delete=model.SET_NULL
:Django需要得到当引用的模型被删掉时应该怎么做的指示。SET_NULL
将把被删除的Person
指向的所有Movie
模型实例的director字段设置为NULL
。 如果我们想要级联删除,我们将使用models.CASCADE
对象。related_name='directed
,这是一个可选参数,指示另一个模型上的RelatedManager实例的名称。这可以让我们查询一个Person定向的所有Movie模型实例)null = True
或提供默认值。如果我们不这样做,那么migrations
就会迫使我们去。这个要求的存在是因为Django必须假定在迁移运行时表中存在行(即使没有)。当数据库添加新列时,它需要知道它应该插入到现有行中。在director字段的情况下,我们可以接受它有时可能为NULL。Movie
中,并将一个新属性添加到Person
实例,名为directed
(RelatedManager
类型)。 RelatedManager
是一个非常有用的类,它类似于模型的默认管理器,但会自动管理两个模型之间的关系。person.directed.create()
并将其与Movie.objects.create()
进行比较。两种方法都会创建一个新电影,但是person.directed.create()
将确保新电影有人作为其导演。 RelatedManager
还提供了添加和删除方法,以便我们可以通过调用person.directed.add(movie)
将电影添加到指定的Person
集。还有一个类似的remove()
方法,但从关系中删除了一个模型。class Movie(models.Model): # constants, methods, Meta class and other fields omitted for brevity. writers = models.ManyToManyField( to='Person', related_name='writing_credits', blank=True)
ManyToManyField
建立了一个多对多的关系,并且像一个RelatedManager
一样,允许用户查询和创建模型。 我们再次使用related_name
来避免给Person一个movie_set
属性,而是给它一个将成为RelatedManager
的writing_credits
属性。ManyToManyField
的情况下,关系的两边都有RelatedManager
,因此person.writing_credits.add(movie)
与movie.writers.add(person)
的效果相同。class Movie(models.Model): # constants, methods, Meta class and other fields omitted for brevity. 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) def __str__(self): return "{} {} {}".format(self.movie_id, self.person_id, self.name) class Meta: unique_together = ('movie', 'person', 'name')
这看起来像前面的ManyToManyField
,除了我们有一个to
(就像前面Person
)和一个through
(引用Role
)参数。Role
模型看起来很像设计一个连接表;它对多对多关系的每一方都有一个ForeignKey
。它还有一个名为name
的额外字段来描述角色。Meta
类的角色上设置unique_together
属性将防止重复的数据。ManyToManyField
的这个用户将创建四个新的RelatedManager
实例:movie.actors
将作为Person
的关联管理person.acting_credits
将作为Movie
的关联管理movie.role_set
将作为Role
的关联管理person.role_set
将作为Role
的关联管理role_set
管理者,以创建模型或修改中间类的关系。 如果您尝试运行movie.actors.add(person)
,Django将抛出IntegrityError异常,因为无法填充Role.name
的值。 但是,您可以编写movie.role_set.add(person = person,name ='Hamlet')
。$ python manage.py makemigrations coreMigrations for 'core': core/migrations/0002_auto_20170926_1650.py - Create model Person - Create model Role - Change Meta options on movie - Add field movie to role - Add field person to role - Add field actors to movie - Add field director to movie - Add field writers to movie - Alter unique_together for role (1 constraint(s))$ python manage.py migrate coreOperations to perform: Apply all migrations: coreRunning migrations: Applying core.0002_auto_20170926_1651... OK
movie_detail.html
模板可以链接到的PersonDetail
视图。 为了创造我们的视图,我们将通过四个步骤:PersonDetail
视图将列出一个Person
正在表演,写作或导演的所有电影。在我们的模板中,我们将打印出每部电影中的每部电影的名称(以及演出的Role.name)。为了避免向数据库发送大量查询,我们将为我们的模型创建新的管理器,以返回更智能的QuerySet
。person.role_set.all()
,每个相关角色一个)。对于N部电影中的人员,这将导致对数据库进行N次查询。我们可以用prefetch_related()
方法避免这种情况(稍后我们将看看select_related()
方法)。使用prefetch_related()
方法,Django将在单个附加查询中查询单个关系中的所有相关数据。但是,如果我们不最终使用预取数据,查询它会浪费时间和内存。all_with_prefetch_movies()
创建一个PersonManager,并使其成为Person的默认管理器: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): # fields omitted for brevity objects = PersonManager() class Meta: ordering = ( 'last_name', 'first_name') def __str__(self): # body omitted for brevity
我们的PersonManager
仍然提供所有与默认相同的方法,因为PersonManager
继承自models.Manager
。我们还定义了一个新方法,它使用get_queryset()
来获取QuerySet
,并告诉它预取相关模型。 QuerySets
是懒惰的,所以直到查询集被评估为止(例如,迭代,投射到布尔,切片或由if语句评估),才会与数据库进行通信。在使用get()通过PK获取模型之前,DetailView
不会评估查询。prefetch_related()
方法接受一个或多个查找,并在初始查询完成后自动查询这些相关模型。当您从QuerySet访问与某个模型相关的模型时,Django将不必查询它,因为您已经在QuerySet
中预取了它。RelatedManager
的内容。通过用两个下划线分隔关系字段(或RelatedManager
)和相关模型字段的名称,查找甚至可以跨越关系:Movie.objects.all().filteractors__last_name='Freeman', actors__first_name='Morgan')
前面的调用将返回一个查询集,用于1Morgan Freeman1一直扮演演员的所有Movie
模型实例。PersonManager
中,我们告诉Django预取一个Person已经执行,写入并且有角色的所有电影,以及预取它们自己的角色。 无论人员多么多产,使用all_with_prefetch_movies()
方法都会导致不断的查询次数。django/core/views.py
class PersonDetail(DetailView): queryset = Person.objects.all_with_prefetch_movies()
这个DetailView
是不同的,因为我们没有提供它的模型属性。 相反,我们从PersonManager
类中为它提供一个QuerySet
对象。 当DetailView
使用QuerySet的filter()
和get()
方法来检索模型实例时,DetailView
将从模型实例的类名中派生出模板的名称,就像我们已经提供了模型类作为视图上的属性一样。django/core/templates/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 %}
class MovieManager(models.Manager): def all_with_related_persons(self): qs = self.get_queryset() qs = qs.select_related( 'director') qs = qs.prefetch_related( 'writers', 'actors') return qsclass Movie(models.Model): # constants and fields omitted for brevity objects = MovieManager() class Meta: ordering = ('-year', 'title') def __str__(self): # method body omitted for brevity
MovieManager
引入了另一种名为select_related()
的新方法。 select_related()
方法与prefetch_related()
方法非常相似,但在关系仅导致一个相关模型(例如,使用ForeignKey
字段)时使用。 select_related()
方法通过使用JOIN SQL查询来检索一个查询中的两个模型。 当关系可能导致多个模型(例如,ManyToManyField
或RelatedManager
属性的任一侧)时,使用prefetch_related()
。MovieDetail
视图以直接使用查询集而不是模型:class MovieDetail(DetailView): queryset = ( Movie.objects .all_with_related_persons())
该视图完全相同,但每次需要相关的Person模型实例时都不需要查询数据库,因为它们都是预取的。关键词:评分,电影,创建