Python的WEB框架有Django、Tornado、Flask 等多种,Django是重量级选手中最有代表性的一位,它的优势为:大而全,框架本身集成了ORM、模型绑定、模板引擎、缓存、Session等诸多功能。许多成功的网站和APP都基于Django。
Django是一个开放源代码的Web应用框架,由Python写成。
Django遵守BSD版权,初次发布于2005年7月, 并于2008年9月发布了第一个正式版本1.0 。
Django采用了MVT的软件设计模式,即模型Model,视图View和模板Template。
本教程适合有Python基础的开发者学习。
学习本教程前你需要了解一些基础的Web知识及Python基础教程。
Django 版本与 Python 环境的对应表:
Django 版本 | Python 版本 |
1.5 | 2.6.5, 2.7, 3.2, 3.3. |
1.6 | 2.6, 2.7, 3.2, 3.3 |
1.7 | 2.7, 3.2, 3.3, 3.4 (2.6 不支持了) |
1.8 LTS | 2.7, 3.2, 3.3, 3.4, 3.5 (长期支持版本 LTS) |
1.9 | 2.7, 3.4, 3.5 (3.3 不支持了) |
1.10 | 2.7, 3.4, 3.5 |
1.11 LTS | 2.7, 3.4, 3.5, 3.6 (最后一个支持 Python 2.7 的版本 ) |
2.0 | 3.4, 3.5, 3.6 (注意,不再支持 Python 2) |
2.1 | 3.5, 3.6, 3.7 |
2.2 LTS | 3.5, 3.6, 3.7 |
3.0 | 3.6, 3.7, 3.8 |
按照上述对照表来选择Django和Python版本,以免造成不兼容等问题。
使用最新版本的问题就是,可能要用到的一些第三方插件没有及时更新,无法正常使用这些三方包。
Django 简介
Django 是用Python开发的一个免费开源的Web框架,可以用于快速搭建高性能,优雅的网站!采用了MVC的框架模式,即模型M,视图V和控制器C,也可以称为MVT模式,模型M,视图V,模板T。
它最初是被开发来用于管理劳伦斯出版集团旗下的一些以新闻内容为主的站点的, 并于2005年7月在BSD许可证下公布.
这套框架是以比利时的吉普赛爵士吉他手Django Reinhardt来命名的.
Django 的主要目标是使得开发复杂的、数据库驱动的网站变得简单。Django 注重组件的重用性和“可插拔性”,敏捷开发和 DRY 法则(Don’t Repeat Yourself)。在 Django 中 Python 被普遍使用,甚至包括配置文件和数据模型。
Django 于 2008 年 6 月 17 日正式成立基金会。
Django 框架的核心包括:
核心框架中还包括:
Django 包含了很多应用在它的 contrib 包中,这些包括:
在安装 Django 前,系统需要已经安装了Python的开发环境。
如果你还未安装Python环境需要先下载Python安装包。
1、Python 下载地址:https://www.python.org/downloads/
2、Django 下载地址:https://www.djangoproject.com/download/
安装Python你只需要下载python-x.x.x.msi文件,然后一直点击"Next"按钮即可。
为了检查我们的python是否安装成功,可以在命令窗口中输入python进行查询,如显示下图一的信息则表示成功了。
安装完成后你需要设置Python环境变量。 右击计算机->属性->高级->环境变量->修改系统变量path,添加Python安装地址,本文实例使用的是C:Python33,你需要根据你实际情况来安装。
安装说明会有所不同,具体取决于您是安装特定于发行版的软件包,下载最新的官方发行版还是获取最新的开发版本。
这是安装Django的推荐方法。
Linux/MAC: python -m pip install Django
Windows: py -m pip
查看特定于发行版的说明,以查看您的平台/发行版是否提供了官方的Django软件包/安装程序。发行版提供的软件包通常将允许自动安装依赖项和受支持的升级路径;但是,这些软件包很少包含最新版本的Django。
跟踪Django开发
如果您决定使用Django的最新开发版本,则需要密切注意开发时间表,并希望留意即将发布的发行说明。这将帮助您掌握可能要使用的所有新功能,以及在更新Django副本时需要对代码进行的任何更改。(对于稳定版本,任何必要的更改都记录在发行说明中。)
如果您希望能够偶尔通过最新的错误修复和改进来更新Django代码,请按照以下说明进行操作:
Linux/MAC: $ git clone https://github.com/django/django.git
Windows:... > git clone https://github.com/django/django.git
这将django
在当前目录中创建一个目录。
3. 确保Python解释器可以加载Django的代码。最方便的方法是使用虚拟环境和pip。该 贡献教程走过了如何创建一个虚拟的环境。
4. 设置并激活虚拟环境后,运行以下命令:
Linux/MAC:$ python -m pip install -e django/
Windows: ... > py -m pip install -e django
这将使Django的代码可导入,并使 django-admin
Utility命令可用。换句话说,您已经准备就绪!
当您想要更新Django源代码的副本时,请从目录中运行命令 。执行此操作时,Git将下载所有更改。git pulldjango
详情参考:https://docs.djangoproject.com/en/3.0/topics/install/
扫描下方二维码或打开微信搜一搜“51coolma编程狮”关注公众号回复关键词【Python123】或者【Python资料包】免费领取 Python 学习资料,包含软件安装包,电子书、思维导图等
本章我们将介绍如何使用 Django 来创建项目。
使用 django-admin.py 来创建名为***的项目:
django-admin startproject xxx
创建完成后我们可以查看下项目的目录结构:
[root@solar ~]# cd HelloWorld/[root@solar HelloWorld]# tree. manage.py 管理器|--*** | |-- __init__.py 包| |-- settings.py 设置文件| |-- urls.py 路由| `-- wsgi.py 部署
目录说明:
创建一个app模块会自动生成app文件夹,该文件夹包括几个文件:
python manage.py startapp app
各个目录的说明:
在目录中找到***包里面的setting.py,在INSTALLED_APPS当中注册APP模块:
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'app',
在包下输入命令,启动项目:
python manage.py runserver
在浏览器输入你服务器的ip及端口号,如果正常启动,会得到如下界面,则表示项目创建完成:
在Django框架中,模板是可以帮助开发者快速生成呈现给用户页面的工具。用于编写html代码,还可以嵌入模板代码转换更方便的完成页面开发,再通过在视图中渲染模板,将生成模板的设计实现了业务逻辑视图与显示内容模板的分离,一个视图可以使用任意一个模板,一个模板可以供多个视图使用。
注意:当前显示的页面=模板+数据
模板分为两部分:
一般是在视图函数当中通过模板语言去动态产生html页,然后将页面上的内容返回给客户端,进行显示。
模板文件渲染产生的html页面内容渲染,使用传递的数据替换相应的变量,产生一个替换后的表中html内容
from django.shortcuts import renderfrom django.template import loader,RequestContextfrom django.http import HttpResponse# Create your views here.def my_render(request,template_path,context={}): # 1.加载模板文件,获取一个模板对象 temp = loader.get_template(template_path) # 2.定义模板上下文,给模板传递数据 context = RequestContext(request, context) # 3.模板渲染,产生一个替换后的html内容 res_html = temp.render(context) # 4.返回应答 return HttpResponse(res_html)# /indexdef index(request): # return my_render(request,'booktest/index.html') 这是自己封装的render # 其实Django已经封装好了,可以直接使用 return render(request,'booktest/index.html')
模板语言
>模板变量名是由数字,字母,下划线和点组成
>注意:不能以下划线开头
3.模板标签
{% 代码段 %}#for循环:#遍历列表:{% for i in 列表 %}#列表不为空时执行{% empty %}#列表为空时执行{% endfor %}#若加上关键字reversed则倒序遍历:
{% for x in 列表 reversed %}{% endfor %}#在for循环中可以通过{{ forloop.counter }}得到for循环遍历到几次#判断语句:{% if %}{% elif %}{% else %}{% endif %}
4.关系比较操作符
> <> = <= ==!=
注意:在使用关系比较操作符的时候,比较符两边必须有空格
5.逻辑运算
不和
过滤器
>add:将值的值增加2。使用形式为:{{value | add:“ 2”}}
> cut:从给定值中删除所有arg的值。使用形式为:{{value | cut:arg}}
>date:格式化时间格式。使用形式为:{{value| date:“ Ymd H:M:S”}}
>default:如果value是False,那么输出给定的默认值。使用形式:{{value | default:“ nothing”}}。例如,如果值是“”,那么输出将是nothing
> first:返回列表/字符串中的第一个元素。使用形式:{{value | first}}
> length:返回值的长度。使用形式:{{value | length}}
自定义过滤器的步骤:
from django import template#导入模块register = template.Library() #标准语句都不能改#写函数装饰器@register.filterdef add_xx(value, arg): # 最多有两个 return '{}-{}'.format(value, arg)#返回两个值的拼接#在html使用方式{% load my_tags %}#引用模块{{ 'alex'|add_xx:'dsb' }}#通过函数名使用@register.filter(name = xxx)#可以直接通过name等于的xxx取引用def add_xx(value, arg): # 最多有两个 return '{}-{}'.format(value, arg)#返回两个值的拼接#在html使用方式{% load my_tags %}#引用模块{{'alex'|xxx:'dsb'}}#通过赋值的name引用
模板里编写{%block <demo>%}开头,{%endblock%}结尾处,代表可以被继承
例如如下新建的demo.html:
1.父模板
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>Title</title> <style> h1{ color: blue; } </style></head><body>{% block demo %}<h1>模板1</h1>{% endblock %}{% block demo1 %}<h1>模板2</h1>{% endblock %}{% block demo2 %}<h1>模板3</h1>{% endblock %}{% block demo3 %}<h1 style="color: red">模板4</h1>{% endblock %}{% block demo4 %}<h1>模板5</h1>{% endblock %}</body></html>
2.子模板
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>Title</title></head><body>{% extends 'demo.html' %} #继承模板{% block demo %} #对引入的模板块进行从写 <h1>这里是重写</h1>{% endblock %}</body></html>
模块约会完成,可以看到效果如下所示:
模型是有关数据的唯一确定的信息源。它包含要存储数据的基本字段和行为。通常,每个模型都映射到单个数据库表。
此示例模型定义了一个Person
,其中包含first_name
和 last_name
:
from django.db import modelsclass Person(models.Model): first_name = models.CharField(max_length=30) last_name = models.CharField(max_length=30)
first_name并且last_name是场模型。每个字段都指定为类属性,并且每个属性都映射到数据库列。
上面的Person模型将创建一个数据库表,如下所示:
CREATE TABLE myapp_person ( "id" serial NOT NULL PRIMARY KEY, "first_name" varchar(30) NOT NULL, "last_name" varchar(30) NOT NULL);
详情参考官网: https://docs.djangoproject.com/en/3.0/topics/db/models/
Django根据属性的类型确定以下信息:
django会为表创建自动增长的主键列,每个模型只能有一个主键列,如果使用选项设置某属性为主键列后django不会再创建自动增长的主键列。
默认创建的主键列属性为id,可以使用pk代替,pk全拼为primary key。
pk是主键的别名,若主键名为id2,那么pk是id2的别名。
属性命名限制:
具体语法如下:
属性=models.字段类型(选项)
字段类 | 默认小组件 | 说明 |
AutoField | N/A | 根据 ID 自动递增的 IntegerField |
BigIntegerField | NumberInput | 64 位整数,与 IntegerField 很像,但取值范围是 -9223372036854775808 到 9223372036854775807 。 |
BinaryField | N/A | 存储原始二进制数据的字段。只支持 bytes 类型。注意,这个字段的功能有限。 |
BooleanField | CheckboxInput | 真假值字段。如果想接受 null 值,使用 NullBooleanField 。 |
CharField | TextInput | 字符串字段,针对长度较小的字符串。大量文本应该使用 TextField 。有个额外的必须参数:max_length ,即字段的最大长度(字符个数)。 |
DateField | DateInput | 日期,在 Python 中使用 datetime.date 实例表示。有两个额外的可选参数: auto_now ,每次保存对象时自动设为当前日期 auto_now_add ,创建对象时自动设为当前日期。 |
DateTimeField | DateTimeInput | 日期和时间,在 Python 中使用 datetime.datetime 实例表示。与 DateField 具有相同的额外参数。 |
DecimalField | TextInput | 固定精度的小数,在 Python 中使用 Decimal 实例表示。有两个必须的参数: max_digits 和 decimal_places 。 |
DurationField | TextInput | 存储时间跨度,在 Python 中使用 timedelta 表示。 |
EmailField | TextInput | 一种 CharField ,使用 EmailValidator 验证输入。max_length 的默认值为 254 。 |
FileField | ClearableFileInput | 文件上传字段。详情见下面。 |
FilePathField | Select | 一种 CharField ,限定只能在文件系统中的特定目录里选择文件。 |
FloatField | NumberInput | 浮点数,在 Python 中使用 float 实例表示。注意, field.localize 的值为 False 时,默认的小组件是 TextInput 。 |
ImageField | ClearableFileInput | 所有属性和方法都继承自 FileField ,此外验证上传的对象是不是有效的图像。增加了 height 和 width 两个属性。需要 Pillow 库支持。 |
Django提供了定义了几种最常见的数据库关联关系的方法:多对一,多对多,一对一。
多对一关系,需要两个位置参数,一个是关联的模型,另一个是 on_delete
选项,外键要定义在多的一方,如一个汽车厂生产多种汽车,一辆汽车只有一个生产厂家
from django.db import models
class Manufacturer(models.Model):
# ...
pass
class Car(models.Model):
manufacturer = models.ForeignKey(Manufacturer, on_delete=models.CASCADE)
例如:这样一个应用,它记录音乐家所属的音乐小组。 我们可以用一个ManyToManyField 表示小组和成员之间的多对多关系。 但是,有时你可能想知道更多成员关系的细节,比如成员是何时加入小组的。
对于这些情况,Django 允许你指定一个中介模型来定义多对多关系。 你可以将其他字段放在中介模型里面。 源模型的ManyToManyField 字段将使用through 参数指向中介模型。 对于上面的音乐小组的例子,代码如下:
from django.db import modelsclass Person(models.Model): name = models.CharField(max_length=128) def __str__(self): # __unicode__ on Python 2 return self.nameclass Group(models.Model): name = models.CharField(max_length=128) members = models.ManyToManyField(Person, through='Membership') def __str__(self): # __unicode__ on Python 2 return self.nameclass 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)
您需要在设置中间模型的时候,显式地为多对多关系中涉及的中间模型指定外键。这种显式声明定义了这两个模型之间是如何关联的。
在中间模型当中有一些限制条件:
现在你已经通过中间模型完成你的ManyToManyField(示例中的Membership),可以开始创建一些多对多关系了。你通过实例化中间模型来创建关系:
>>> 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)})
你可能更潜在直接创造中间模型。
如果自定义中间模型没有强制对的唯一性,调用方法会删除所有中间模型的实例:(model1, model2)remove()
>>> Membership.objects.create(person=ringo, group=beatles,... date_joined=date(1968, 9, 4),... invite_reason="You've been gone for a month and we miss you.")>>> beatles.members.all()<QuerySet [<Person: Ringo Starr>, <Person: Paul McCartney>, <Person: Ringo Starr>]>>>> # This deletes both of the intermediate model instances for Ringo Starr>>> beatles.members.remove(ringo)>>> beatles.members.all()<QuerySet [<Person: Paul McCartney>]>
方法clear()
用于实例的所有多对多关系:
>>> # Beatles have broken up>>> beatles.members.clear()>>> # Note that this deletes the intermediate model instances>>> Membership.objects.all()<QuerySet []>
一旦你建立了自定义多对多关联关系,就可以执行查询操作。和一般的多对多关联关系一样,你可以使用多对多关联模型的属性来查询:
# 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]>
如果你想访问一个关系的信息时你可以直接查询Membership模型:
>>> ringos_membership = Membership.objects.get(group=beatles, person=ringo)>>> ringos_membership.date_joineddatetime.date(1962, 8, 16)>>> ringos_membership.invite_reason'Needed a new drummer.'
另一种访问同样信息的方法是通过Person对象来查询多对多递归关系:
>>> ringos_membership = ringo.membership_set.get(group=beatles)>>> ringos_membership.date_joineddatetime.date(1962, 8, 16)>>> ringos_membership.invite_reason'Needed a new drummer.'
使用OneToOneField来定义一对一关系。就像使用其他类型的Field一样:在模型属性中包含它。
当一个对象以某种方式“继承”另一个对象时,这那个对象的主键非常有用。
OneToOneField需要一个位置参数:与模型相关的类。
例如,当你要建立一个有关“位置”信息的数据库时,你可能会包含通常的地址,电话等分支。然后,如果你想接着建立一个关于关于餐厅的数据库,除了将位置数据库当中的一部分复制到Restaurant模型,你也可以将一个指向Place OneToOneField放到Restaurant当中(因为餐厅“是一个”地点);事实上,在处理这样的情况时最好使用模型继承,它隐含的包括了一个一对一关系。
和 ForeignKey一样,可以创建自关联关系也可以创建与尚未定义的模型的关系。
OneToOneField初步还接受一个可选的parent_link参数。
OneToOneField类通常自动的成为模型的主键,这条规则现在不再使用了(而你可以手动指定primary_key参数)。因此,现在可以在其中的模型当中指定多个OneToOneField分段。
详情参考官网: https://www.khan.pub/django3.0/index.html
除非您打算建立只发布内容的网站和应用程序,并且不接受访问者的输入,否则您将需要理解和使用表格。
Django提供了一系列工具和库,可帮助您构建表单以接受来自站点访问者的输入,然后处理并响应输入。
在HTML中,表单是内部元素的集合<form>...</form>,允许访问者执行诸如输入文本,选择选项,操作对象或控件等操作,然后将该信息发送回服务器。
其中一些表单界面元素(文本输入或复选框)内置于HTML本身。其他则要复杂得多。弹出日期选择器或允许您移动滑块或操纵控件的界面通常将使用JavaScript和CSS以及HTML表单<input>元素来实现这些效果。
<input>表单及其元素还必须指定两件事:
Django表单处理流程
Django 的表单处理:视图获取请求,执行所需的任何操作,包括从模型中读取数据,然后生成并返回HTML页面(从模板中),我们传递一个包含要显示的数据的上下文。使事情变得更复杂的是,服务器还需要能够处理用户提供的数据,并在出现任何错误时,重新显示页面。
GET方法是通过键值对的方式显示从用户那边获取的数据,然后通过“&”将其组合形成一个整体的字符串,最后加上“?”,将组合后的字符串拼接到URL内,生成一个url地址。它既不适用于大量数据,也不适合于二进制数据(例如图像)。使用GET
管理表单请求的Web应用程序存在安全风险:攻击者很容易模仿表单的请求来访问系统的敏感部分。GET仅应用于不影响系统状态的请求。诸如Web搜索表单类,它可以轻松对请求到的URL进行共享,提交等操作。
POST 方法
处理表格是一项复杂的业务。考虑一下Django的管理员,其中可能需要准备好几种不同类型的大量数据,以表格形式显示,呈现为HTML,使用便利的界面进行编辑,返回到服务器,进行验证和清理,然后保存或传递进行进一步处理。
Django的表单功能可以简化和自动化大部分工作,并且比大多数程序员在编写自己的代码中所能做到的更加安全。
Django处理涉及表单的工作的三个不同部分:
这是有可能到手动做这一切写代码,但Django的可以照顾这一切为您服务。
我们已经简要描述了HTML表单,但是HTML <form>只是所需机制的一部分。
在Web应用程序的上下文中,“表单”可能是指该HTML <form>或Form生成它的Django ,或者是提交时返回的结构化数据,或者是这些部分的端到端工作集合。
该组件系统的核心是Django的Form类。类与Django模型描述对象的逻辑结构,其行为以及向我们表示其部分的方式几乎相同,一个 Form类描述一种形式并确定其工作方式和外观。
就像模型类的字段映射到数据库字段一样,表单类的字段映射到HTML表单<input>元素。(A 通过;ModelForm 将模型类的字段映射到HTML表单<input>元素 Form,这是Django管理员所基于的。)
表单的字段本身就是类。他们管理表单数据并在提交表单时执行验证。一个DateField和 FileField手柄非常不同类型的数据,并有做不同的事情吧。
表单字段在浏览器中以HTML“窗口小部件”的形式向用户表示-一种用户界面机制。每个字段类型都有一个适当的默认 Widget类,但是可以根据需要覆盖它们。
在Django中渲染对象时,通常:
在模板中呈现表单与呈现任何其他类型的对象几乎涉及相同的工作,但是存在一些关键区别。
对于不包含数据的模型实例,在模板中执行任何操作几乎是没有用的。另一方面,呈现未填充的表单非常有意义-当我们希望用户填充它时,这就是我们要做的。
因此,当我们在视图中处理模型实例时,通常会从数据库中检索它。当我们处理表单时,通常在视图中实例化它。
实例化表单时,我们可以选择将其保留为空或预先填充,例如:
这些情况中的最后一个是最有趣的,因为它使用户不仅可以阅读网站,而且还可以向其发送信息。
假设您想在您的网站上创建一个简单的表单,以获得用户名。您在模板中需要这样的内容:
<form action="/your-name/" method="post"> <label for="your_name">Your name: </label> <input id="your_name" type="text" name="your_name" value="{{ current_name }}"> <input type="submit" value="OK"></form>
这告诉浏览器/your-name/使用POST方法将表单数据返回到URL 。它将显示一个文本字段,标记为“您的姓名:”,以及一个标记为“确定”的按钮。如果模板上下文包含一个current_name 变量,它将用于预填充该your_name字段。
您将需要一个视图来呈现包含HTML表单的模板,并且可以提供current_name适当的字段。
提交表单后,POST发送到服务器的请求将包含表单数据。
现在,您还将需要一个与该/your-name/URL 对应的视图,该视图将在请求中找到适当的键/值对,然后对其进行处理。
这是一个非常简单的形式。实际上,一个表单可能包含数十个或数百个字段,其中许多字段可能需要预先填充,并且我们可能希望用户在结束操作之前先完成几次编辑-提交循环。
甚至在提交表单之前,我们可能需要在浏览器中进行一些验证;我们可能想使用更复杂的字段,使用户可以执行诸如从日历中选择日期之类的操作。
在这一点上,让Django为我们完成大部分工作要容易得多。
我们已经知道我们想要HTML表单的外观了。我们在Django中的起点是:
的forms.pyfrom django import formsclass NameForm(forms.Form): your_name = forms.CharField(label='Your name', max_length=100)
这定义了一个Form具有单个字段(your_name)的类。我们在该字段上应用了一个人类友好的标签,该标签将在<label>呈现时显示在标签上(尽管在这种情况下,label 我们指定的标签实际上与省略该标签时会自动生成的标签 相同)。
字段的最大允许长度由定义 max_length。这有两件事。它放在 maxlength="100"HTML上<input>(因此浏览器应首先防止用户输入超过该数量的字符)。这也意味着,当Django从浏览器接收回表单时,它将验证数据的长度。
一个Form实例有一个is_valid()方法,它运行于所有的字段验证程序。调用此方法时,如果所有字段都包含有效数据,它将:
首次渲染时,整个表单将如下所示:
<label for="your_name">Your name: </label><input id="your_name" type="text" name="your_name" maxlength="100" required>
请注意,它不包含<form>标签或提交按钮。我们必须在模板中提供这些信息。
发送回Django网站的表单数据由视图处理,通常与发布表单的视图相同。这使我们可以重用某些相同的逻辑。
要处理表单,我们需要在视图中将其实例化的URL实例化为:
的views.pyfrom django.http import HttpResponseRedirectfrom django.shortcuts import renderfrom .forms import NameFormdef get_name(request): # if this is a POST request we need to process the form data if request.method == 'POST': # create a form instance and populate it with data from the request: form = NameForm(request.POST) # check whether it's valid: if form.is_valid(): # process the data in form.cleaned_data as required # ... # redirect to a new URL: return HttpResponseRedirect('/thanks/') # if a GET (or any other method) we'll create a blank form else: form = NameForm() return render(request, 'name.html', {'form': form})
如果我们通过GET请求到达此视图,它将创建一个空表单实例并将其放置在要呈现的模板上下文中。这是我们第一次访问URL时可以预期的情况。
如果表单是使用POST请求提交的,则视图将再次创建表单实例,并使用请求中的数据填充该表单实例:这称为“将数据绑定到表单”(现在是绑定表单)。form = NameForm(request.POST)
我们称为表单的is_valid()方法;如果不是True,我们返回带有表单的模板。这次,表单不再是空的(未绑定),因此将使用先前提交的数据填充HTML表单,并可以在其中根据需要对其进行编辑和更正。
如果is_valid()为True,我们现在将能够在其cleaned_data属性中找到所有经过验证的表单数据。我们可以使用此数据来更新数据库或进行其他处理,然后再将HTTP重定向发送到浏览器,告诉浏览器下一步该怎么做。
我们不需要在name.html模板中做很多事情:
<form action="/your-name/" method="post"> {% csrf_token %} {{ form }} <input type="submit" value="Submit"></form>
表单的所有字段及其属性将通过Django的模板语言从中解压缩为HTML标记。{{ form }}
表格和跨站点请求伪造保护
Django随附了易于使用的跨站点请求伪造保护。在POST启用CSRF保护的情况下提交表单时,必须csrf_token像前面的示例一样使用template标记。但是,由于CSRF保护并不直接与模板中的表单相关联,因此在本文档的以下示例中省略了此标签。
HTML5输入类型和浏览器验证
如果表单包括URLField,一个 EmailField或任何整数字段类型,Django会使用的url,email和numberHTML5输入类型。默认情况下,浏览器可以在这些字段上应用自己的验证,这可能比Django的验证更严格。如果您想禁用此行为,请novalidate在form标签上设置属性,或在字段上指定其他小部件,例如TextInput。
现在,我们有了一个工作的Web表单,该表单由Django描述Form,由视图处理并呈现为HTML <form>。
这就是您入门所需的全部内容,但是表单框架为您提供了更多便利。一旦了解了上述过程的基础,就应该准备了解表单系统的其他功能,并准备进一步了解基础机械。
所有表单类均作为django.forms.Form 或的子类创建django.forms.ModelForm。您可以将其ModelForm视为的子类Form。Form并ModelForm实际上从(私有)BaseForm类继承通用功能,但是这种实现细节很少很重要。
模型和形式
实际上,如果您的表单将用于直接添加或编辑Django模型,那么ModelForm可以节省大量的时间,精力和代码,因为它可以构建表单以及适当的字段及其属性,来自一Model堂课。
绑定形式和未绑定形式之间的区别很重要:
表单的is_bound属性将告诉您表单是否绑定了数据。
考虑一个比上面的最小示例更有用的形式,我们可以使用该形式在个人网站上实现“与我联系”功能:
的forms.pyfrom django import formsclass ContactForm(forms.Form): subject = forms.CharField(max_length=100) message = forms.CharField(widget=forms.Textarea) sender = forms.EmailField() cc_myself = forms.BooleanField(required=False)
我们之前的形式使用的单场,your_name,一CharField。在这种情况下,我们的表单有四个字段:subject,message,sender和 cc_myself。CharField,EmailField并且 BooleanField只有三个可用的字段类型; 完整列表可以在“ 表单”字段中找到。
每个表单字段都有一个对应的Widget类,该类又对应于HTML表单小部件,例如。<input type="text">
在大多数情况下,该字段将具有明智的默认小部件。例如,默认情况下,a CharField将具有在HTML TextInput中生成a的小部件。如果需要 ,可以在定义表单字段时指定适当的小部件,就像我们对字段所做的那样。<input type="text"><textarea>message
无论通过表单提交的数据是什么,一旦通过调用成功验证is_valid()(并is_valid()返回True),经过验证的表单数据都将位于form.cleaned_data字典中。这些数据将为您很好地转换为Python类型。
注意
此时,您仍然可以直接访问未经验证的数据request.POST,但是经过验证的数据更好。
在上面的联系表单示例中,cc_myself将为布尔值。同样,诸如IntegerField和FloatField将值分别转换为Python int和的字段float。
以下是在处理此表单的视图中如何处理表单数据的方法:
的views.pyfrom django.core.mail import send_mailif form.is_valid(): subject = form.cleaned_data['subject'] message = form.cleaned_data['message'] sender = form.cleaned_data['sender'] cc_myself = form.cleaned_data['cc_myself'] recipients = ['info@example.com'] if cc_myself: recipients.append(sender) send_mail(subject, message, sender, recipients) return HttpResponseRedirect('/thanks/')
小费
有关从Django发送电子邮件的更多信息,请参见发送电子邮件。
一些字段类型需要一些额外的处理。例如,使用表单上传的文件需要进行不同的处理(可以从而request.FILES不是从中检索它们 request.POST)。有关如何处理表单上载文件的详细信息,请参阅将上载的文件绑定到表单。
将表单放入模板所需要做的就是将表单实例放入模板上下文中。因此,如果您的表单是form在上下文中调用的,则将适当地呈现其和元素。{{ form }}<label><input>
附加表格模板家具
不要忘了,一个形式的输出并没有包括周围的 <form>标签,或窗体的submit控制。您必须自己提供这些。
<label>/ <input>对还有其他输出选项:
请注意,您必须自己提供周围环境<table>或<ul> 元素。
这是我们的实例的输出:{{ form.as_p }}ContactForm
<p><label for="id_subject">Subject:</label> <input id="id_subject" type="text" name="subject" maxlength="100" required></p><p><label for="id_message">Message:</label> <textarea name="message" id="id_message" required></textarea></p><p><label for="id_sender">Sender:</label> <input type="email" name="sender" id="id_sender" required></p><p><label for="id_cc_myself">Cc myself:</label> <input type="checkbox" name="cc_myself" id="id_cc_myself"></p>
请注意,每个表单字段的ID属性设置为id_<field-name>,由随附的标签标记引用。这对于确保辅助技术(例如屏幕阅读器软件)可访问表单很重要。您还可以自定义标签和ID的生成方式。
有关更多信息,请参见将表单输出为HTML。
我们不必让Django解压缩表单字段;我们可以根据需要手动进行操作(例如,允许我们对字段进行重新排序)。每个字段都可以使用用作表单的属性,并且在Django模板中将适当地呈现。例如:{{ form.name_of_field }}
{{ form.non_field_errors }}<div class="fieldWrapper"> {{ form.subject.errors }} <label for="{{ form.subject.id_for_label }}">Email subject:</label> {{ form.subject }}</div><div class="fieldWrapper"> {{ form.message.errors }} <label for="{{ form.message.id_for_label }}">Your message:</label> {{ form.message }}</div><div class="fieldWrapper"> {{ form.sender.errors }} <label for="{{ form.sender.id_for_label }}">Your email address:</label> {{ form.sender }}</div><div class="fieldWrapper"> {{ form.cc_myself.errors }} <label for="{{ form.cc_myself.id_for_label }}">CC yourself?</label> {{ form.cc_myself }}</div>
<label>也可以使用生成完整的元素 label_tag()。例如:
<div class="fieldWrapper"> {{ form.subject.errors }} {{ form.subject.label_tag }} {{ form.subject }}</div>
当然,这种灵活性的代价是更多的工作。到目前为止,我们不必担心如何显示表单错误,因为这已经为我们解决了。在此示例中,我们必须确保处理每个字段的所有错误以及整个表单的所有错误。请注意在表单和模板查找的顶部,以查找每个字段上的错误。{{ form.non_field_errors }}
使用显示格式错误列表,并显示为无序列表。可能看起来像:{{ form.name_of_field.errors }}
<ul class="errorlist"> <li>Sender is required.</li></ul>
该列表的CSS类errorlist允许您设置外观样式。如果您希望进一步自定义错误的显示,可以通过遍历它们来实现:
{% if form.subject.errors %} <ol> {% for error in form.subject.errors %} <li><strong>{{ error|escape }}</strong></li> {% endfor %} </ol>{% endif %}
非字段错误(和/或使用诸如的辅助工具时在表单顶部显示的隐藏字段错误form.as_p())将通过附加的类别呈现,nonfield以帮助将其与特定于字段的错误区分开。例如,如下所示:{{ form.non_field_errors }}
<ul class="errorlist nonfield"> <li>Generic validation error</li></ul>
有关错误,样式以及如何在模板中使用表单属性的更多信息,请参见Forms API。
如果您对每个表单字段使用相同的HTML,则可以通过使用 循环依次遍历每个字段来减少重复代码:{% for %}
{% for field in form %} <div class="fieldWrapper"> {{ field.errors }} {{ field.label_tag }} {{ field }} {% if field.help_text %} <p class="help">{{ field.help_text|safe }}</p> {% endif %} </div>{% endfor %}
有用的属性包括:{{ field }}
{{ field.label }}
Email address
{{ field.label_tag }}
字段的标签包装在适当的HTML <label>
标记中。这包括表格的label_suffix
。例如,默认label_suffix
值为冒号:
<label for="id_email">Email address:</label>
{{ field.id_for_label }}
id_email
在上面的示例中)。如果您是手动构建标签,则可能要使用它代替label_tag
。例如,如果您有一些内联JavaScript并希望避免对字段ID进行硬编码,它也很有用。{{ field.value }}
someone@example.com
。{{ field.html_name }}
{{ field.help_text }}
{{ field.errors }}
<ul class="errorlist">
{% for error in field.errors %}
{{ field.is_hidden }}
True
表单字段是否为隐藏字段,False
否则为隐藏字段 。它作为模板变量不是特别有用,但在条件测试中可能有用,例如:{% if field.is_hidden %} {# Do something special #}{% endif %}
{{ field.field }}
Field
的表单类中的实例BoundField
。您可以使用它来访问 Field
属性,例如 。{{ char_field.field.max_length }}
也可以看看
有关属性和方法的完整列表,请参见 BoundField。
如果您要手动在模板中布置表单,而不是依赖Django的默认表单布局,则可能需要将 字段与非隐藏字段区别对待。例如,由于隐藏字段不显示任何内容,因此将错误消息放在该字段旁边可能会给您的用户造成混乱-因此,应对这些字段的错误进行不同的处理。<input type="hidden">
Django在表单上提供了两种方法,可让您独立遍历隐藏字段和可见字段:hidden_fields()和 visible_fields()。这是对使用这两种方法的先前示例的修改:
{# Include the hidden fields #}{% for hidden in form.hidden_fields %}{{ hidden }}{% endfor %}{# Include the visible fields #}{% for field in form.visible_fields %} <div class="fieldWrapper"> {{ field.errors }} {{ field.label_tag }} {{ field }} </div>{% endfor %}
本示例不处理隐藏字段中的任何错误。通常,隐藏字段中的错误是表单被篡改的标志,因为正常的表单交互不会改变它们。但是,您也可以轻松地为这些表单错误插入一些错误显示。
如果您的站点在多个位置对表单使用相同的呈现逻辑,则可以通过将表单的循环保存在独立模板中并使用include标签在其他模板中重用它来减少重复:
# In your form template:{% include "form_snippet.html" %}# In form_snippet.html:{% for field in form %} <div class="fieldWrapper"> {{ field.errors }} {{ field.label_tag }} {{ field }} </div>{% endfor %}
如果传递给模板的表单对象在上下文中具有不同的名称,则可以使用 标记的with参数对其进行别名include:
{% include "form_snippet.html" with form=comment_form %}
如果您发现自己经常这样做,则可以考虑创建一个自定义 包含标签。
详情参考: https://docs.djangoproject.com/en/3.0/topics/forms/#working-with-form-templates
干净,优雅的URL方案是高质量Web应用程序中的重要细节。Django允许您根据需要设计URL,而无框架限制。
万维网创建者蒂姆·伯纳斯-李(Tim Berners-Lee)的文章“ Cool URIs not not change”中有关为什么URL应该干净和可用的出色论据,请参见。
要设计应用程序的URL,您可以创建一个非正式地称为URLconf(URL配置)的Python模块 。该模块是纯Python代码,并且是URL路径表达式到Python函数(您的视图)之间的映射。
该映射可以根据需要短或长。它可以引用其他映射。而且,由于它是纯Python代码,因此可以动态构建。
Django还提供了一种根据活动语言翻译URL的方法。有关更多信息,请参见国际化文档。
当用户从您的Django支持的网站请求页面时,系统将使用以下算法来确定要执行的Python代码:
这是一个示例URLconf:
from django.urls import path
from . import views
urlpatterns = [
path('articles/2003/', views.special_case_2003),
path('articles/<int:year>/', views.year_archive),
path('articles/<int:year>/<int:month>/', views.month_archive),
path('articles/<int:year>/<int:month>/<slug:slug>/', views.article_detail),
]
笔记:
请求示例:
默认情况下,以下路径转换器可用:
对于更复杂的匹配要求,您可以定义自己的路径转换器。
转换器是包含以下内容的类:
例如:
class FourDigitYearConverter:
regex = '[0-9]{4}'
def to_python(self, value):
return int(value)
def to_url(self, value):
return '%04d' % value
使用register_converter()以下命令在URLconf中注册自定义转换器类 :
from django.urls import path, register_converter
from . import converters, views
register_converter(converters.FourDigitYearConverter, 'yyyy')
urlpatterns = [
path('articles/2003/', views.special_case_2003),
path('articles/<yyyy:year>/', views.year_archive),
...
]
如果路径和转换器语法不足以定义URL模式,则还可以使用正则表达式。为此,请使用 re_path()代替path()。
在Python正则表达式中,命名正则表达式组的语法为(?Ppattern),其中name是组的名称,并且 pattern是匹配的某种模式。
这是前面的示例URLconf,使用正则表达式重写:
from django.urls import path, re_path
from . import views
urlpatterns = [
path('articles/2003/', views.special_case_2003),
re_path(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive),
re_path(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$', views.month_archive),
re_path(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/(?P<slug>[w-]+)/$', views.article_detail),
]
这可以完成与上一个示例大致相同的操作,除了:
当从使用切换为使用path(), re_path()反之亦然时,特别重要的是要注意视图参数的类型可能会更改,因此您可能需要调整视图。
除了命名组语法(例如)之外(?P[0-9]{4}),您还可以使用较短的未命名组(例如)([0-9]{4})。
不建议特别使用此用法,因为这样可以更轻松地在匹配的预期含义和视图的参数之间意外引入错误。
无论哪种情况,建议在给定的正则表达式中仅使用一种样式。当两种样式混合使用时,任何未命名的组都会被忽略,只有命名的组才会传递给视图函数。
正则表达式允许嵌套参数,而Django会解析它们并将其传递给视图。反转时,Django将尝试填写所有外部捕获的参数,而忽略任何嵌套的捕获参数。考虑以下URL模式,这些URL模式可以选择采用page参数:
from django.urls import re_path
urlpatterns = [
re_path(r'^blog/(page-(d+)/)?$', blog_articles), # bad
re_path(r'^comments/(?:page-(?P<page_number>d+)/)?$', comments), # good
]
两种模式都使用嵌套参数,并将解析:例如, blog/page-2/将导致与匹配blog_articles两个位置参数:page-2/和2。的第二个模式 comments将comments/page-2/与关键字参数 page_number设置为2 匹配。在这种情况下,外部参数是一个非捕获参数(?:...)。
该blog_articles视图需要最外层捕获的参数被反转,page-2/或者在这种情况下不需要参数,而视图 comments可以不带参数也没有值而被反转page_number。
嵌套的捕获参数在视图参数和URL之间建立了牢固的耦合,如下所示blog_articles:视图接收部分URL(page-2/),而不是仅接收视图感兴趣的值。这种反转在反转时更为明显,因为反转视图,我们需要传递该URL而不是页码。
根据经验,当正则表达式需要参数但视图将其忽略时,仅捕获视图需要使用的值,并使用非捕获参数。
URLconf按照正常的Python字符串搜索请求的URL。这不包括GET或POST参数或域名。
例如,在对的请求中https://www.example.com/myapp/,URLconf将寻找myapp/。
在请求中https://www.example.com/myapp/?page=3,URLconf将寻找myapp/。
URLconf不会查看请求方法。换句话说,所有的请求方法- ,,POST 等-将被路由到相同的URL相同的功能。GET``HEAD
一个方便的技巧是为视图的参数指定默认参数。这是一个示例URLconf和视图:
# URLconf
from django.urls import path
from . import views
urlpatterns = [
path('blog/', views.page),
path('blog/page<int:num>/', views.page),
]
# View (in blog/views.py)
def page(request, num=1):
# Output the appropriate page of blog entries, according to num.
...
在上面的示例中,两个URL模式都指向同一视图– views.page–但是第一个模式未从URL中捕获任何内容。如果第一个模式匹配,该page()函数将使用它的默认参数num,1。如果第二个模式匹配, page()将使用num捕获的任何值。
中的每个正则表达式urlpatterns都是在首次访问时进行编译。这使系统运行起来非常快。
urlpatterns应该是一个序列的path() 和/或re_path()实例。
当Django无法找到所请求URL的匹配项或引发异常时,Django会调用错误处理视图。
这些情况下使用的视图由四个变量指定。它们的默认值足以满足大多数项目的需要,但可以通过覆盖其默认值来进行进一步的自定义。
有关完整的详细信息,请参见有关自定义错误视图的文档。
可以在您的根URLconf中设置这些值。在任何其他URLconf中设置这些变量将无效。
值必须是可调用的,或者是表示视图的完整Python导入路径的字符串,应该调用该视图来处理当前的错误情况。
变量是:
在任何时候,您urlpatterns都可以“包括”其他URLconf模块。实质上,这会将“ URL”“植根”在其他URL之下。
例如,这是Django网站 本身的URLconf的摘录。它包括许多其他URLconf:
from django.urls import include, path
urlpatterns = [
# ... snip ...
path('community/', include('aggregator.urls')),
path('contact/', include('contact.urls')),
# ... snip ...
]
每当Django遇到时include(),它都会截断直到该时间点匹配的URL的任何部分,并将剩余的字符串发送到包含的URLconf中以进行进一步处理。
另一种可能性是通过使用path()实例列表包括其他URL模式 。例如,考虑以下URLconf:
from django.urls import include, path
from apps.main import views as main_views
from credit import views as credit_views
extra_patterns = [
path('reports/', credit_views.report),
path('reports/<int:id>/', credit_views.report),
path('charge/', credit_views.charge),
]
urlpatterns = [
path('', main_views.homepage),
path('help/', include('apps.help.urls')),
path('credit/', include(extra_patterns)),
]
在此示例中,/credit/reports/URL将由credit_views.report()Django视图处理 。
这可用于从URLconf中删除重复使用单个模式前缀的冗余。例如,考虑以下URLconf:
from django.urls import pathfrom . import viewsurlpatterns = [ path('<page_slug>-<page_id>/history/', views.history), path('<page_slug>-<page_id>/edit/', views.edit), path('<page_slug>-<page_id>/discuss/', views.discuss), path('<page_slug>-<page_id>/permissions/', views.permissions),]
我们可以通过只声明一次公共路径前缀并对不同的后缀进行分组来改善这一点:
from django.urls import include, pathfrom . import viewsurlpatterns = [ path('<page_slug>-<page_id>/', include([ path('history/', views.history), path('edit/', views.edit), path('discuss/', views.discuss), path('permissions/', views.permissions), ])),]
包含的URLconf从父URLconfs接收任何捕获的参数,因此以下示例有效:
# In settings/urls/main.pyfrom django.urls import include, pathurlpatterns = [ path('<username>/blog/', include('foo.urls.blog')),]# In foo/urls/blog.pyfrom django.urls import pathfrom . import viewsurlpatterns = [ path('', views.blog.index), path('archive/', views.blog.archive),]
在上面的示例中,捕获的"username"变量按预期传递给包含的URLconf。
URLconfs有一个钩子,可让您将额外的参数作为Python字典传递给视图函数。
该path()函数可以使用可选的第三个参数,该参数应该是传递给view函数的额外关键字参数的字典。
例如:
from django.urls import pathfrom . import viewsurlpatterns = [ path('blog/<int:year>/', views.year_archive, {'foo': 'bar'}),]
在此示例中,对于的请求/blog/2005/,Django将调用 。views.year_archive(request, year=2005, foo='bar')
该技术在 联合框架中用于将元数据和选项传递给视图。
处理冲突
URL模式可能会捕获命名的关键字参数,并在其额外参数字典中传递具有相同名称的参数。发生这种情况时,将使用字典中的参数代替URL中捕获的参数。
同样,您可以将额外选项传递给include(),所包含的URLconf中的每一行都将传递额外选项。
例如,这两个URLconf集在功能上是相同的:
设置一:
# main.pyfrom django.urls import include, pathurlpatterns = [ path('blog/', include('inner'), {'blog_id': 3}),]# inner.pyfrom django.urls import pathfrom mysite import viewsurlpatterns = [ path('archive/', views.archive), path('about/', views.about),]
设置二:
# main.pyfrom django.urls import include, pathfrom mysite import viewsurlpatterns = [ path('blog/', include('inner')),]# inner.pyfrom django.urls import pathurlpatterns = [ path('archive/', views.archive, {'blog_id': 3}), path('about/', views.about, {'blog_id': 3}),]
请注意,无论行的视图是否实际接受这些选项,额外的选项将始终传递到所包含的URLconf中的每一行。因此,仅当您确定所包含的URLconf中的每个视图都接受要传递的额外选项时,此技术才有用。
在Django项目上进行工作时,通常需要获取最终形式的URL,以嵌入生成的内容(视图和资产URL,向用户显示的URL等)或在服务器上处理导航流程侧面(重定向等)
强烈希望避免对这些URL进行硬编码(一种费力,不可扩展且易于出错的策略)。同样危险的是,设计临时机制来生成与URLconf描述的设计平行的URL,这可能导致URL的生成随着时间的推移而变得陈旧。
换句话说,需要一种DRY机制。除其他优点外,它还允许URL设计的发展,而不必遍历所有项目源代码来搜索和替换过时的URL。
我们可以获得URL的主要信息是负责处理它的视图的标识(例如名称)。视图参数的类型(位置,关键字)和值还必须包含在正确的URL查找中的其他信息。
Django提供了一个解决方案,使得URL映射器是URL设计的唯一存储库。您将其与URLconf一起提供,然后可以在两个方向上使用它:
第一个是我们在上一节中讨论的用法。第二种是所谓的URL反向解析,反向URL匹配,反向URL查找或简称URL反向。
Django提供了执行URL反转的工具,这些工具与需要URL的不同层相匹配:
再次考虑以下URLconf条目:
from django.urls import pathfrom . import viewsurlpatterns = [ #... path('articles/<int:year>/', views.year_archive, name='news-year-archive'), #...]
根据这种设计,对应于年度归档文件的URL NNNN 是/articles//。
您可以使用以下模板代码获取它们:
<a href="{% url 'news-year-archive' 2012 %}">2012 Archive</a>{# Or with the year in a template context variable: #}<ul>{% for yearvar in year_list %}<li><a href="{% url 'news-year-archive' yearvar %}">{{ yearvar }} Archive</a></li>{% endfor %}</ul>
或在Python代码中:
from django.http import HttpResponseRedirectfrom django.urls import reversedef redirect_to_year(request): # ... year = 2006 # ... return HttpResponseRedirect(reverse('news-year-archive', args=(year,)))
如果出于某种原因决定更改发布年度文章存档内容的URL,则只需要更改URLconf中的条目即可。
在视图具有一般性质的某些情况下,URL和视图之间可能存在多对一关系。对于这些情况,在反向URL时,视图名称并不是一个足够好的标识符。阅读下一节以了解Django为此提供的解决方案。
为了执行URL反向,您需要 像上面的示例一样使用命名的URL模式。URL名称使用的字符串可以包含您喜欢的任何字符。您不限于有效的Python名称。
在命名URL模式时,请选择不太可能与其他应用程序的名称冲突的名称。如果调用URL模式,comment 而另一个应用程序执行相同的操作,则reverse()找到的URL 取决于项目urlpatterns列表中最后一个模式。
在您的URL名称上添加前缀(可能源自应用程序名称(例如myapp-comment而不是comment)),可以减少发生冲突的机会。
如果要覆盖视图,可以故意选择与另一个应用程序相同的URL名称。例如,一个常见的用例是覆盖 LoginView。Django和大多数第三方应用程序的某些部分假定此视图具有名称为的URL模式 login。如果你有一个自定义登录查看,并给它的URL的名字login, reverse()会发现自定义视图,只要它在 urlpatterns以后django.contrib.auth.urls包括(如果这是包含在所有)。
如果多个URL模式的参数不同,也可以使用相同的名称。除URL名称外,还要reverse() 匹配参数数量和关键字参数的名称。
URL名称空间允许您唯一地反向命名URL模式,即使不同的应用程序使用相同的URL名称。对于第三方应用程序,始终使用命名空间的URL是一个好习惯(就像我们在本教程中所做的那样)。同样,如果部署了一个应用程序的多个实例,它还允许您反向URL。换句话说,由于单个应用程序的多个实例将共享命名URL,因此名称空间提供了一种区分这些命名URL的方法。
对于特定站点,可以多次使用正确使用URL名称空间的Django应用程序。例如,django.contrib.admin 有一AdminSite类允许您 部署多个admin实例。在下一个示例中,我们将讨论从教程在两个不同位置部署民意调查应用程序的想法,以便我们可以为两个不同的受众(作者和发布者)提供相同的功能。
URL名称空间分为两部分,都是字符串:
使用':'操作符指定以名称分隔的URL 。例如,使用引用管理应用程序的主索引页面'admin:index'。这表示的命名空间'admin',以及的命名URL 'index'。
命名空间也可以嵌套。命名的URL 'sports:polls:index'将寻找'index'在命名空间中命名的模式,该模式'polls'本身是在顶级命名空间中定义的'sports'。
给定'polls:index'要解析的命名空间URL(例如)后,Django会将完全限定的名称拆分为多个部分,然后尝试以下查找:
如果存在嵌套的名称空间,则对名称空间的每个部分重复这些步骤,直到仅解析视图名称为止。然后,将视图名称解析为找到的名称空间中的URL。
为了展示该解决方案的实际作用,请考虑polls本教程中应用程序的两个实例的示例:一个称为'author-polls' ,一个称为'publisher-polls'。假设我们已经增强了该应用程序,以便在创建和显示民意测验时考虑实例名称空间。
的urls.py
from django.urls import include, pathurlpatterns = [ path('author-polls/', include('polls.urls', namespace='author-polls')), path('publisher-polls/', include('polls.urls', namespace='publisher-polls')),]
民调/的urls.py
from django.urls import pathfrom . import viewsapp_name = 'polls'urlpatterns = [ path('', views.IndexView.as_view(), name='index'), path('<int:pk>/', views.DetailView.as_view(), name='detail'), ...]
使用此设置,可以进行以下查找:
如果还存在一个默认实例(即名为的实例)'polls',则唯一的更改就是没有当前实例(上面列表中的第二项)。在这种情况下,'polls:index' 它将解析为默认实例的索引页,而不是最后一个在中声明的实例urlpatterns。
可以通过两种方式指定包含的URLconf的应用程序名称空间。
首先,您可以app_name在包含的URLconf模块中设置与该urlpatterns属性相同级别的属性。您必须将实际模块或对该模块的字符串引用传递给include(),而不是其urlpatterns自身的列表。
民调/的urls.py
from django.urls import pathfrom . import viewsapp_name = 'polls'urlpatterns = [ path('', views.IndexView.as_view(), name='index'), path('<int:pk>/', views.DetailView.as_view(), name='detail'), ...]
的urls.py
from django.urls import include, pathurlpatterns = [ path('polls/', include('polls.urls')),]
中定义的URL polls.urls将具有一个应用程序名称空间polls。
其次,您可以包括一个包含嵌入式名称空间数据的对象。如果您include()列出path()或 re_path()实例,则该对象中包含的URL将被添加到全局名称空间中。但是,您还可以include()包含一个包含以下内容的2元组:
(<list of path()/re_path() instances>, <application namespace>)
例如:
from django.urls import include, pathfrom . import viewspolls_patterns = ([ path('', views.IndexView.as_view(), name='index'), path('<int:pk>/', views.DetailView.as_view(), name='detail'),], 'polls')urlpatterns = [ path('polls/', include(polls_patterns)),]
这会将提名的URL模式包括到给定的应用程序名称空间中。
可以使用的namespace参数 指定实例名称空间include()。如果未指定实例名称空间,它将默认为包含的URLconf的应用程序名称空间。这意味着它将也是该名称空间的默认实例。
详细参考: https://docs.djangoproject.com/en/3.0/topics/http/urls/
视图是可调用的,它接受请求并返回响应。这不仅可以是一个函数,而且Django提供了一些可用作视图的类的示例。这些使您可以利用继承和混合来构造视图并重用代码。对于任务,还有一些通用的视图,我们将在以后进行介绍,但是您可能想要设计自己的可重用视图结构,以适合您的用例。有关完整的详细信息,请参见基于类的视图参考文档。
Django提供了适合各种应用程序的基本视图类。所有视图都从View该类继承,该类负责将视图链接到URL,HTTP方法分派和其他常见功能。RedirectView提供HTTP重定向,并TemplateView扩展基类以使其也呈现模板。
使用通用视图的最直接方法是直接在URLconf中创建它们。如果仅在基于类的视图上更改一些属性,则可以将它们传递给as_view()方法调用本身:
from django.urls import path
from django.views.generic import TemplateView
urlpatterns = [
path('about/', TemplateView.as_view(template_name="about.html")),
]
传递给任何参数as_view()将覆盖在类上设置的属性。在此示例中,我们将设置template_name 为TemplateView。可以对上的url属性使用类似的覆盖模式 RedirectView。
使用通用视图的第二种更强大的方法是从现有视图继承并覆盖子类中的属性(例如template_name)或方法(例如get_context_data)以提供新的值或方法。例如,考虑一个仅显示一个模板的视图 about.html。Django有一个通用视图可以执行此操作-- TemplateView因此我们可以将其子类化,并覆盖模板名称:
# some_app/views.py
from django.views.generic import TemplateView
class AboutView(TemplateView):
template_name = "about.html"
然后,我们需要将此新视图添加到我们的URLconf中。 TemplateView是一个类,而不是一个函数,因此我们将URL指向as_view()类方法,该方法为基于类的视图提供类似函数的条目:
# urls.py
from django.urls import path
from some_app.views import AboutView
urlpatterns = [
path('about/', AboutView.as_view()),
]
有关如何使用内置通用视图的更多信息,请参考下一个基于通用类的视图的主题。
假设有人想使用视图作为API通过HTTP访问我们的图书库。API客户端会时不时地进行连接,并下载自上次访问以来发布的图书的图书数据。但是,如果从那以后没有新书出现,那么从数据库中获取书本,呈现完整的响应并将其发送给客户端将浪费CPU时间和带宽。最好向API询问最新书籍的发布时间。
我们将URL映射到URLconf中的书列表视图:
from django.urls import path
from books.views import BookListView
urlpatterns = [
path('books/', BookListView.as_view()),
]
和视图:
from django.http import HttpResponse
from django.views.generic import ListView
from books.models import Book
class BookListView(ListView):
model = Book
def head(self, *args, **kwargs):
last_book = self.get_queryset().latest('publication_date')
response = HttpResponse()
# RFC 1123 date format
response['Last-Modified'] = last_book.publication_date.strftime('%a, %d %b %Y %H:%M:%S GMT')
return response
如果从GET请求访问视图,则在响应中返回对象列表(使用book_list.html模板)。但是,如果客户发出HEAD请求,则响应的主体为空,Last-Modified 标题会指示最新书籍的发布时间。根据此信息,客户端可以下载也可以不下载完整的对象列表。
详情参考: https://docs.djangoproject.com/en/3.0/
基于类的视图提供了一种将视图实现为Python对象而非函数的替代方法。它们不能替代基于功能的视图,但是与基于功能的视图相比具有某些区别和优势:
开始时只有视图函数协定,Django将您的函数传递给,HttpRequest并期望将 传递给HttpResponse。这就是Django提供的功能。
早期就认识到在视图开发中发现了常见的习惯用法和模式。引入了基于函数的通用视图,以抽象化这些模式并简化常见情况下的视图开发。
基于函数的通用视图的问题在于,尽管它们很好地涵盖了简单的情况,但无法扩展或自定义某些配置选项之外的视图,从而限制了它们在许多实际应用程序中的用途。
创建基于类的通用视图的目的与基于函数的通用视图相同,以使视图开发更加容易。但是,通过使用mixins来实现解决方案的方式提供了一个工具包,该工具包使得基于类的通用视图比基于功能的对应视图更具可扩展性和灵活性。
如果您过去曾经尝试过基于函数的通用视图,但发现缺少这些功能,则不应将基于类的通用视图视为基于类的等效视图,而应将其视为解决通用视图旨在解决的原始问题的全新方法。解决。
Django用于构建基于类的泛型视图的基类和mixin工具包的构建具有最大的灵活性,因此,它们具有默认方法实现和属性形式的许多钩子,您可能不会在最简单的用法中关注它们案件。例如,实现不是form_class使用get_form方法的基于类的属性,而是使用了一种方法,该get_form_class方法调用一种方法,该方法在其默认实现中返回form_class类的属性。这为您提供了几个选项,用于指定从属性到完全动态,可调用的钩子使用哪种形式。对于简单情况,这些选项似乎增加了空心的复杂性,但是如果没有这些选项,则会限制更高级的设计。
从本质上讲,基于类的视图使您可以使用不同的类实例方法来响应不同的HTTP请求方法,而不是使用单个视图函数中的有条件分支代码。
因此,GET在视图函数中用于处理HTTP的代码如下所示:
from django.http import HttpResponse
def my_view(request):
if request.method == 'GET':
# <view logic>
return HttpResponse('result')
在基于类的视图中,这将变为:
from django.http import HttpResponse
from django.views import View
class MyView(View):
def get(self, request):
# <view logic>
return HttpResponse('result')
因为Django的URL解析器希望将请求和关联的参数发送给可调用的函数而不是类,所以基于类的视图具有一个 as_view()class方法,该类方法返回一个函数,该请求可以在请求到达与关联模式匹配的URL时被调用。该函数创建该类的实例,调用 setup()以初始化其属性,然后调用其dispatch()方法。 dispatch查看该请求以确定它是否为GET, POST等,并将请求转发给匹配的方法(如果已定义),否则将其引发HttpResponseNotAllowed:
# urls.py
from django.urls import path
from myapp.views import MyView
urlpatterns = [
path('about/', MyView.as_view()),
]
值得注意的是,您的方法返回的内容与您从基于函数的视图返回的内容相同,即的某种形式 HttpResponse。这意味着 http快捷方式或 TemplateResponse对象可在基于类的视图中有效使用。
尽管最小的基于类的视图不需要任何类属性即可执行其工作,但是类属性在许多基于类的设计中很有用,并且有两种配置或设置类属性的方法。
第一种是子类化和覆盖子类中的属性和方法的标准Python方法。这样,如果您的父类具有这样的属性 greeting:
from django.http import HttpResponse
from django.views import View
class GreetingView(View):
greeting = "Good Day"
def get(self, request):
return HttpResponse(self.greeting)
您可以在子类中覆盖它:
class MorningGreetingView(GreetingView):
greeting = "Morning to ya"
另一个选择是将类属性配置为as_view()URLconf中的调用的关键字参数 :
urlpatterns = [
path('about/', GreetingView.as_view(greeting="G'day")),
]
注意:在为分配给它的每个请求实例化您的类时,通过as_view()导入点设置的类属性 在导入URL时仅配置一次。
Mixins是多重继承的一种形式,可以将多个父类的行为和属性进行组合。
例如,在基于通用类的视图中,有一个mixin, TemplateResponseMixin其主要目的是定义method render_to_response()。当与View 基类的行为组合时,结果是一个TemplateView 类,该类会将请求分派到适当的匹配方法(View基类中定义的行为),并且具有 render_to_response() 使用 template_name 属性返回TemplateResponse 对象(行为)的方法。 )中定义TemplateResponseMixin。
Mixins是在多个类之间重用代码的绝佳方法,但是它们会带来一些成本。您的代码散布在mixin中的次数越多,读取子类并了解其确切操作的难度就越大,而如果您正在子类化具有深层继承树。
还要注意,您只能从一个通用视图继承-也就是说,只有一个父类可以继承,View其余(如果有)应该是mixins。尝试从多个继承的类中进行继承View-例如,尝试使用列表顶部的表单并组合ProcessFormView和 ListView-将无法按预期工作。
处理表单的基于函数的基本视图可能如下所示:
from django.http import HttpResponseRedirect
from django.shortcuts import render
from .forms import MyForm
def myview(request):
if request.method == "POST":
form = MyForm(request.POST)
if form.is_valid():
# <process form cleaned data>
return HttpResponseRedirect('/success/')
else:
form = MyForm(initial={'key': 'value'})
return render(request, 'form_template.html', {'form': form})
类似的基于类的视图可能类似于:
from django.http import HttpResponseRedirect
from django.shortcuts import render
from django.views import View
from .forms import MyForm
class MyFormView(View):
form_class = MyForm
initial = {'key': 'value'}
template_name = 'form_template.html'
def get(self, request, *args, **kwargs):
form = self.form_class(initial=self.initial)
return render(request, self.template_name, {'form': form})
def post(self, request, *args, **kwargs):
form = self.form_class(request.POST)
if form.is_valid():
# <process form cleaned data>
return HttpResponseRedirect('/success/')
return render(request, self.template_name, {'form': form})
这是一个最小的情况,但是您可以看到您可以通过覆盖任何类属性(例如form_class,通过URLconf配置,或子类化并覆盖一个或多个方法(或两者)!)来定制此视图 。 。
基于类的视图的扩展不仅限于使用混合。您也可以使用装饰器。由于基于类的视图不是函数,因此根据您正在使用as_view()还是创建子类来装饰它们的工作方式有所不同。
您可以通过装饰as_view()方法的结果来调整基于类的视图 。最简单的方法是在部署视图的URLconf中:
from django.contrib.auth.decorators import login_required, permission_requiredfrom django.views.generic import TemplateViewfrom .views import VoteViewurlpatterns = [ path('about/', login_required(TemplateView.as_view(template_name="secret.html"))), path('vote/', permission_required('polls.can_vote')(VoteView.as_view())),]
此方法基于每个实例应用装饰器。如果要装饰视图的每个实例,则需要采用其他方法。
要修饰基于类的视图的每个实例,您需要修饰类定义本身。为此,您可以将装饰器应用于dispatch()类的 方法。
类上的方法与独立函数并不完全相同,因此您不能仅将函数装饰器应用于该方法–您需要首先将其转换为方法装饰器。所述method_decorator装饰来转换函数装饰成方法装饰,使得它可以在一个实例方法中。例如:
from django.contrib.auth.decorators import login_requiredfrom django.utils.decorators import method_decoratorfrom django.views.generic import TemplateViewclass ProtectedView(TemplateView): template_name = 'secret.html' @method_decorator(login_required) def dispatch(self, *args, **kwargs): return super().dispatch(*args, **kwargs)
或者,更简洁地说,您可以代替装饰类,并将要装饰的方法的名称作为关键字参数传递name:
@method_decorator(login_required, name='dispatch')class ProtectedView(TemplateView): template_name = 'secret.html'
如果您在多个地方使用了一组通用装饰器,则可以定义一个装饰器列表或元组,然后使用它而不是method_decorator()多次调用 。这两个类是等效的:
decorators = [never_cache, login_required]@method_decorator(decorators, name='dispatch')class ProtectedView(TemplateView): template_name = 'secret.html'@method_decorator(never_cache, name='dispatch')@method_decorator(login_required, name='dispatch')class ProtectedView(TemplateView): template_name = 'secret.html'
装饰者将按照传递给装饰者的顺序处理请求。在示例中,never_cache()将在之前处理请求 login_required()。
在此示例中,的每个实例都ProtectedView将具有登录保护。这些示例使用login_required,但是通过使用可以获得相同的行为 LoginRequiredMixin。
注意:method_decorator将*args和**kwargs 作为参数传递给类中经过修饰的方法。如果您的方法不接受一组兼容的参数,它将引发 TypeError异常。
详情参考: https://docs.djangoproject.com/en/3.0/
编写Web应用程序可能是单调的,因为我们一次又一次地重复某些模式。Django试图消除模型和模板层的某些单调性,但Web开发人员也在视图级别上遇到这种无聊的情况。
开发了Django的通用视图来缓解这种痛苦。它们采用了视图开发中发现的某些常见习语和模式,并对它们进行了抽象,以便您可以快速编写数据的通用视图而无需编写太多代码。
我们可以识别某些常见任务,例如显示对象列表,并编写显示任何对象列表的代码。然后,可以将所讨论的模型作为附加参数传递给URLconf。
Django附带了通用视图以执行以下操作:
这些视图加在一起提供了执行开发人员遇到的最常见任务的界面。
毫无疑问,使用通用视图可以大大加快开发速度。但是,在大多数项目中,有时通用视图不再足够了。确实,新Django开发人员提出的最常见问题是如何使通用视图处理更广泛的情况。
这是为1.3版本重新设计通用视图的原因之一-以前,它们是带有令人困惑的选项列表的视图函数;现在,与其在URLconf中传递大量配置,不如建议扩展常规视图的方法是将其子类化并覆盖其属性或方法。
也就是说,通用视图将受到限制。如果您发现自己很难将视图实现为通用视图的子类,则可能会发现使用自己的基于类或功能的视图来只编写所需的代码会更有效。
某些第三方应用程序中提供了更多通用视图的示例,或者您可以根据需要编写自己的视图。
TemplateView当然是有用的,但是当涉及到呈现数据库内容的视图时,Django的通用视图确实非常出色。因为这是一项常见的任务,所以Django附带了一些内置的通用视图,以帮助生成对象的列表和详细视图。
让我们先来看一些显示对象列表或单个对象的示例。
我们将使用以下模型:
# models.py
from django.db import models
class Publisher(models.Model):
name = models.CharField(max_length=30)
address = models.CharField(max_length=50)
city = models.CharField(max_length=60)
state_province = models.CharField(max_length=30)
country = models.CharField(max_length=50)
website = models.URLField()
class Meta:
ordering = ["-name"]
def __str__(self):
return self.name
class Author(models.Model):
salutation = models.CharField(max_length=10)
name = models.CharField(max_length=200)
email = models.EmailField()
headshot = models.ImageField(upload_to='author_headshots')
def __str__(self):
return self.name
class Book(models.Model):
title = models.CharField(max_length=100)
authors = models.ManyToManyField('Author')
publisher = models.ForeignKey(Publisher, on_delete=models.CASCADE)
publication_date = models.DateField()
现在我们需要定义一个视图:
# views.py
from django.views.generic import ListView
from books.models import Publisher
class PublisherList(ListView):
model = Publisher
最后,将该视图挂接到您的网址中:
# urls.py
from django.urls import path
from books.views import PublisherList
urlpatterns = [
path('publishers/', PublisherList.as_view()),
]
这就是我们需要编写的所有Python代码。但是,我们仍然需要编写一个模板。我们可以通过在视图中添加一个template_name属性来明确地告诉视图使用哪个模板 ,但是在没有显式模板的情况下,Django将从对象名称中推断出一个模板。在这种情况下,推断的模板将是"books/publisher_list.html"-“书”部分来自定义模型的应用程序的名称,而“发布者”位是模型名称的小写版本。
注意:因此,当(例如)将 后端的APP_DIRS选项DjangoTemplates设置为True in时TEMPLATES,模板位置可以是:/path/to/project/books/templates/books/publisher_list.html
将针对包含名为的变量的上下文呈现此模板,该变量 object_list包含所有发布者对象。模板可能如下所示:
{% extends "base.html" %}
{% block content %}
<h2>Publishers</h2>
<ul>
{% for publisher in object_list %}
<li>{{ publisher.name }}</li>
{% endfor %}
</ul>
{% endblock %}
这就是全部。通用视图的所有很酷的功能都来自更改通用视图上设置的属性。该 通用视图引用文档中的所有详细的通用视图的选择; 本文档的其余部分将考虑一些您可以自定义和扩展通用视图的常用方法。
您可能已经注意到我们的示例发布者列表模板将所有发布者存储在名为的变量中object_list。尽管这很好用,但对模板作者并不是那么“友好”:他们必须“只是知道”他们在这里与发行人打交道。
好吧,如果您要处理模型对象,那么已经为您完成了。当您处理对象或查询集时,Django可以使用模型类名称的小写形式填充上下文。除了默认object_list条目之外,还提供了此条目,但包含完全相同的数据,即publisher_list。
如果仍然不能很好地匹配,则可以手动设置上下文变量的名称。context_object_name通用视图上的属性指定要使用的上下文变量:
# views.py
from django.views.generic import ListView
from books.models import Publisher
class PublisherList(ListView):
model = Publisher
context_object_name = 'my_favorite_publishers'
提供有用context_object_name的东西总是一个好主意。您设计模板的同事将感谢您。
通常,您需要提供一些超出通用视图所提供信息的额外信息。例如,考虑在每个出版商详细信息页面上显示所有书籍的列表。该DetailView 通用视图提供了出版商到上下文,但是我们如何在模板中获取更多的信息?
答案是子类化DetailView 并提供您自己的get_context_data方法实现。默认实现将要显示的对象添加到模板中,但是您可以覆盖它以发送更多内容:
from django.views.generic import DetailView
from books.models import Book, Publisher
class PublisherDetail(DetailView):
model = Publisher
def get_context_data(self, **kwargs):
# Call the base implementation first to get a context
context = super().get_context_data(**kwargs)
# Add in a QuerySet of all the books
context['book_list'] = Book.objects.all()
return context
注意:通常,get_context_data将所有父类的上下文数据与当前类的上下文数据合并。若要在要更改上下文的自己的类中保留此行为,请务必确保调用 get_context_data超类。当没有两个类尝试定义相同的键时,这将提供预期的结果。但是,如果任何类在父类设置了键之后都尝试覆盖键(在调用super之后),则该类的所有子级也需要在super之后显式设置键,以确保覆盖所有父键。如果遇到问题,请查看视图的方法解析顺序。
另一个考虑是基于类的通用视图的上下文数据将覆盖上下文处理器提供的数据。请参阅 get_context_data()示例。
现在,让我们仔细看看model我们一直使用的参数。该model参数指定了将对视图进行操作的数据库模型,该参数可用于对单个对象或对象集合进行操作的所有通用视图。但是,model参数不是指定视图将操作的对象的唯一方法–您还可以使用queryset参数指定对象列表:
from django.views.generic import DetailView
from books.models import Publisher
class PublisherDetail(DetailView):
context_object_name = 'publisher'
queryset = Publisher.objects.all()
指定是简短的说法。但是,通过使用定义对象的过滤列表,您可以更详细地了解视图中将显示的对象(有关对象的更多信息,请参见进行查询,有关完整的详细信息 ,请参见 基于类的视图参考)。model = Publisher``queryset = Publisher.objects.all()``querysetQuerySet
举个例子,我们可能想按出版日期订购书籍清单,以最新的为准:
from django.views.generic import ListView
from books.models import Book
class BookList(ListView):
queryset = Book.objects.order_by('-publication_date')
context_object_name = 'book_list'
这是一个非常小的例子,但是很好地说明了这个想法。当然,通常您不仅仅需要对对象重新排序,还需要做更多的事情。如果要显示特定出版商的书籍列表,则可以使用相同的技术:
from django.views.generic import ListViewfrom books.models import Bookclass AcmeBookList(ListView): context_object_name = 'book_list' queryset = Book.objects.filter(publisher__name='ACME Publishing') template_name = 'books/acme_list.html'
请注意,除了filter之外queryset,我们还使用了自定义模板名称。如果我们不这样做,则通用视图将使用与“香草”对象列表相同的模板,而这可能不是我们想要的。
另请注意,这不是制作出版商特定书籍的一种非常优雅的方法。如果我们要添加另一个发布者页面,则需要在URLconf中再加上几行,并且不止几个发布者会变得不合理。我们将在下一部分中解决这个问题。
注意:如果在请求时收到404,请/books/acme/检查以确保您实际上拥有名称为'ACME Publishing'的发布商。通用视图allow_empty对此情况有一个参数。
另一个常见的需求是通过URL中的某个键过滤列表页面中给定的对象。之前我们在URLconf中硬编码了出版商的名称,但是如果我们想编写一个视图来显示某个任意出版商的所有书籍,该怎么办?
方便地,我们ListView有一个get_queryset()可以覆盖的 方法。默认情况下,它返回queryset属性的值,但是我们可以使用它添加更多的逻辑。
进行这项工作的关键部分是,当调用基于类的视图时,各种有用的东西都存储在self;以及request(self.request)包括根据URLconf捕获的position(self.args)和基于名称的(self.kwargs)参数。
在这里,我们有一个URLconf,其中包含一个捕获的组:
# urls.pyfrom django.urls import pathfrom books.views import PublisherBookListurlpatterns = [ path('books/<publisher>/', PublisherBookList.as_view()),]
接下来,我们将编写PublisherBookList视图本身:
# views.pyfrom django.shortcuts import get_object_or_404from django.views.generic import ListViewfrom books.models import Book, Publisherclass PublisherBookList(ListView): template_name = 'books/books_by_publisher.html' def get_queryset(self): self.publisher = get_object_or_404(Publisher, name=self.kwargs['publisher']) return Book.objects.filter(publisher=self.publisher)
使用get_queryset向查询集选择添加逻辑既方便又强大。例如,如果需要的话,我们可以使用 self.request.user当前用户或其他更复杂的逻辑进行过滤。
我们还可以同时将发布者添加到上下文中,因此我们可以在模板中使用它:
# ...def get_context_data(self, **kwargs): # Call the base implementation first to get a context context = super().get_context_data(**kwargs) # Add in the publisher context['publisher'] = self.publisher return context
我们将看到的最后一个常见模式涉及在调用通用视图之前或之后做一些额外的工作。
想象一下,我们last_accessed在Author模型上有一个字段,用于跟踪任何人上次查看该作者的时间:
# models.pyfrom django.db import modelsclass Author(models.Model): salutation = models.CharField(max_length=10) name = models.CharField(max_length=200) email = models.EmailField() headshot = models.ImageField(upload_to='author_headshots') last_accessed = models.DateTimeField()
DetailView当然,泛型类对此字段一无所知,但是我们可以再次轻松编写一个自定义视图以使该字段保持更新。
首先,我们需要在URLconf中添加作者详细信息位以指向自定义视图:
from django.urls import pathfrom books.views import AuthorDetailViewurlpatterns = [ #... path('authors/<int:pk>/', AuthorDetailView.as_view(), name='author-detail'),]
然后,我们将编写新视图– get_object是检索对象的方法–因此我们将其覆盖并包装调用:
from django.utils import timezonefrom django.views.generic import DetailViewfrom books.models import Authorclass AuthorDetailView(DetailView): queryset = Author.objects.all() def get_object(self): obj = super().get_object() # Record the last accessed date obj.last_accessed = timezone.now() obj.save() return obj
注意:URLconf在此使用命名组pk-该名称是默认名称,DetailView用于查找用于过滤查询集的主键的值。
详情参考: https://docs.djangoproject.com/en/3.0/
表单处理通常具有3条路径:
自己实现这一点通常会导致很多重复的样板代码(请参阅在视图中使用表单)。为了避免这种情况,Django提供了一组通用的基于类的视图以进行表单处理。
给出联系表:
的forms.py
from django import forms
class ContactForm(forms.Form):
name = forms.CharField()
message = forms.CharField(widget=forms.Textarea)
def send_email(self):
# send email using the self.cleaned_data dictionary
pass
可以使用构造视图FormView:
的views.py
from myapp.forms import ContactForm
from django.views.generic.edit import FormView
class ContactView(FormView):
template_name = 'contact.html'
form_class = ContactForm
success_url = '/thanks/'
def form_valid(self, form):
# This method is called when valid form data has been POSTed.
# It should return an HttpResponse.
form.send_email()
return super().form_valid(form)
笔记:
使用模型时,通用视图确实很出色。这些通用视图将自动创建一个ModelForm,只要它们能够确定要使用的模型类:
模型表单视图提供了一种 form_valid()自动保存模型的实现。如果有特殊要求,可以覆盖此设置。请参阅下面的示例。
您甚至不需要提供success_urlfor CreateView或 UpdateView- get_absolute_url()如果可用,它们将 在模型对象上使用。
如果要使用自定义ModelForm(例如添加额外的验证),请form_class在视图上进行设置 。
注意:指定自定义表单类时,即使form_class可能是,您仍必须指定模型ModelForm。
首先,我们需要添加get_absolute_url()到 Author类中:
models.py中
from django.db import models
from django.urls import reverse
class Author(models.Model):
name = models.CharField(max_length=200)
def get_absolute_url(self):
return reverse('author-detail', kwargs={'pk': self.pk})
然后我们可以CreateView和朋友一起做实际的工作。注意这里我们是如何配置通用的基于类的视图的。我们不必自己编写任何逻辑:
的views.py
from django.urls import reverse_lazy
from django.views.generic.edit import CreateView, DeleteView, UpdateView
from myapp.models import Author
class AuthorCreate(CreateView):
model = Author
fields = ['name']
class AuthorUpdate(UpdateView):
model = Author
fields = ['name']
class AuthorDelete(DeleteView):
model = Author
success_url = reverse_lazy('author-list')
注意:我们必须使用reverse_lazy()而不是 reverse(),因为导入文件时不会加载url。
该fields属性的工作方式与fields上内部Meta类的属性相同ModelForm。除非您以其他方式定义表单类,否则该属性是必需的,否则视图将引发ImproperlyConfigured异常。
如果同时指定fields 和form_class属性, ImproperlyConfigured则会引发异常。
最后,我们将这些新视图连接到URLconf中:
的urls.py
from django.urls import path
from myapp.views import AuthorCreate, AuthorDelete, AuthorUpdate
urlpatterns = [
# ...
path('author/add/', AuthorCreate.as_view(), name='author-add'),
path('author/<int:pk>/', AuthorUpdate.as_view(), name='author-update'),
path('author/<int:pk>/delete/', AuthorDelete.as_view(), name='author-delete'),
]
注意:些观点继承 SingleObjectTemplateResponseMixin 它使用 template_name_suffix 了构建 template_name 基于模型。
在此示例中:
如果希望为CreateView和 提供单独的模板UpdateView,则可以 在视图类上设置 template_name或 template_name_suffix。
要跟踪使用创建对象的用户,CreateView可以使用自定义方法ModelForm来执行此操作。首先,将外键关系添加到模型:
models.py中
from django.contrib.auth.models import User
from django.db import models
class Author(models.Model):
name = models.CharField(max_length=200)
created_by = models.ForeignKey(User, on_delete=models.CASCADE)
# ...
在视图中,确保您不包括created_by要编辑的字段列表,并覆盖 form_valid()以添加用户:
的views.py
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic.edit import CreateView
from myapp.models import Author
class AuthorCreate(LoginRequiredMixin, CreateView):
model = Author
fields = ['name']
def form_valid(self, form):
form.instance.created_by = self.request.user
return super().form_valid(form)
LoginRequiredMixin防止未登录的用户访问表单。如果您忽略了这一点,则需要处理中的未授权用户form_valid()。
这是一个示例,显示了如何实现适用于AJAX请求以及“普通”表单POST的表单:
from django.http import JsonResponse
from django.views.generic.edit import CreateView
from myapp.models import Author
class AjaxableResponseMixin:
"""
Mixin to add AJAX support to a form.
Must be used with an object-based FormView (e.g. CreateView)
"""
def form_invalid(self, form):
response = super().form_invalid(form)
if self.request.is_ajax():
return JsonResponse(form.errors, status=400)
else:
return response
def form_valid(self, form):
# We make sure to call the parent's form_valid() method because
# it might do some processing (in the case of CreateView, it will
# call form.save() for example).
response = super().form_valid(form)
if self.request.is_ajax():
data = {
'pk': self.object.pk,
}
return JsonResponse(data)
else:
return response
class AuthorCreate(AjaxableResponseMixin, CreateView):
model = Author
fields = ['name']
详情参考: https://docs.djangoproject.com/en/3.0/
警告: 这是一个高级主题。在探索这些技术之前,建议您对Django基于类的视图有一定的了解。
Django的内置基于类的视图提供了许多功能,但您可能需要单独使用其中的一些功能。例如,您可能想编写一个视图,该视图呈现一个模板以进行HTTP响应,但是您不能使用 TemplateView; 也许您只需要在上渲染模板POST,然后GET完全执行其他操作即可。虽然您可以TemplateResponse直接使用 ,但这可能会导致代码重复。
因此,Django还提供了许多混合器,这些混合器提供了更多离散功能。例如,模板呈现封装在中 TemplateResponseMixin。Django参考文档包含所有mixins的完整文档。
提供了两个中央mixin,它们有助于在使用基于类的视图中的模板时提供一致的界面。
让我们看看Django的两个基于类的通用视图是如何通过提供离散功能的mixin构建的。我们将考虑 DetailView,它呈现一个对象的“详细”视图,而 ListView,它将呈现一个对象列表(通常来自查询集),并可选地对它们进行分页。这将向我们介绍四个mixin,它们在使用单个Django对象或多个对象时提供有用的功能。
也有参与通用编辑观点混入(FormView和特定型号的意见CreateView, UpdateView和 DeleteView),并在基于日期的通用视图。这些已在mixin参考文档中介绍。
要显示对象的详细信息,我们基本上需要做两件事:我们需要查找对象,然后需要TemplateResponse使用合适的模板制作一个 ,并将该对象作为上下文。
要获得该对象,需要DetailView 依赖SingleObjectMixin,该get_object() 方法提供了一种方法,该 方法可以根据请求的URL找出对象(它会根据URLConf中的声明查找pk和slug关键字参数,然后从model视图的属性中查找该对象 ,或提供的 queryset 属性)。SingleObjectMixin也会覆盖 get_context_data(),它在所有Django的所有基于类的内置视图中使用,以为模板渲染提供上下文数据。
然后TemplateResponse,要 DetailView使用SingleObjectTemplateResponseMixin,则使用 , 如上所讨论的,它扩展了TemplateResponseMixin,覆盖 get_template_names()。实际上,它提供了一组相当复杂的选项,但是大多数人要使用的主要选项是 /_detail.html。该_detail部分可以通过设置来改变 template_name_suffix 的一个子类别的东西。(例如,通用编辑观点使用_form的创建和更新的意见,并 _confirm_delete进行删除的意见。)
对象列表遵循大致相同的模式:我们需要一个(可能是分页的)对象列表,通常为 QuerySet,然后我们需要TemplateResponse使用该对象列表使用合适的模板制作一个 。
要获取对象,请ListView使用 MultipleObjectMixin,同时提供 get_queryset() 和 paginate_queryset()。与with不同SingleObjectMixin,不需要关闭URL的一部分来确定要使用的查询集,因此默认值使用 view类上的querysetor model属性。覆盖get_queryset() 此处的常见原因 是动态地改变对象,例如取决于当前用户或排除博客的将来帖子。
MultipleObjectMixin还重写 get_context_data()以包括用于分页的适当上下文变量(如果禁用了分页,则提供虚拟变量)。它依赖object_list于作为关键字参数进行传递的关键字参数ListView。
做一个TemplateResponse, ListView然后使用 MultipleObjectTemplateResponseMixin; 与SingleObjectTemplateResponseMixin 上面的方法一样,此方法将覆盖,get_template_names()以提供,最常用的是 ,而该部分再次从 属性中获取。(基于日期的通用视图使用诸如的后缀, 以此类推,以便为各种专门的基于日期的列表视图使用不同的模板。)a range of options/_list.html``_listtemplate_name_suffix_archive``_archive_year
现在,我们已经了解了Django的基于类的通用视图如何使用提供的mixins,让我们看一下将它们组合在一起的其他方法。当然,我们仍将它们与内置的基于类的视图或其他通用的基于类的视图结合起来,但是您可以解决许多比Django开箱即用的罕见问题。
警告:并非所有的mixin都可以一起使用,也不是所有基于通用类的视图都可以与所有其他mixin一起使用。在这里,我们提供了一些可行的示例。如果要合并其他功能,则必须考虑使用的不同类之间重叠的属性和方法之间的交互,以及方法解析顺序将如何影响以何种顺序调用哪些版本的方法。
Django的基于类的视图和基于类的视图mixin的参考文档将帮助您了解哪些属性和方法可能会导致不同的类和mixin之间发生冲突。
如有疑问,通常最好还是放弃并以 View或为基础TemplateView,也许使用 SingleObjectMixin和 MultipleObjectMixin。尽管您可能最终会写出更多的代码,但是以后其他人可能更容易理解它,而更少的交互性让您担心,可以节省一些时间。(当然,您总是可以深入了解Django基于类的通用视图的实现,以获取有关如何解决问题的灵感。)
如果我们要编写一个仅对做出响应的基于类的视图,则将创建子POST类View并post()在该子类中编写一个方法。但是,如果我们希望我们的处理能够处理从URL识别的特定对象,我们将需要提供的功能 SingleObjectMixin。
我们将通过在基于通用类的视图简介中Author使用的模型来 演示这一点。
的views.py
from django.http import HttpResponseForbidden, HttpResponseRedirect
from django.urls import reverse
from django.views import View
from django.views.generic.detail import SingleObjectMixin
from books.models import Author
class RecordInterest(SingleObjectMixin, View):
"""Records the current user's interest in an author."""
model = Author
def post(self, request, *args, **kwargs):
if not request.user.is_authenticated:
return HttpResponseForbidden()
# Look up the author we're interested in.
self.object = self.get_object()
# Actually record interest somehow here!
return HttpResponseRedirect(reverse('author-detail', kwargs={'pk': self.object.pk}))
实际上,您可能希望将兴趣记录在键值存储而不是关系数据库中,因此我们省略了这一点。唯一需要担心使用的视图 SingleObjectMixin就是我们要查找感兴趣的作者的地方,它通过调用来完成 self.get_object()。mixin会为我们处理其他所有事务。
我们可以很容易地将其挂接到我们的URL中:
的urls.py
from django.urls import path
from books.views import RecordInterest
urlpatterns = [
#...
path('author/<int:pk>/interest/', RecordInterest.as_view(), name='author-interest'),
]
请注意pk命名组,该组 get_object()用于查找Author实例。您还可以使用slug或的任何其他功能 SingleObjectMixin。
ListView提供内置的分页功能,但是您可能希望对所有通过外键链接到另一个对象的对象列表进行分页。在我们的出版示例中,您可能希望对特定出版商的所有书籍进行分页。
一种实现方法是与结合ListView使用 SingleObjectMixin,以便分页图书清单的查询集可以脱离作为单个对象找到的出版商。为此,我们需要有两个不同的查询集:
注意:我们必须仔细考虑get_context_data()。由于SingleObjectMixin和 ListView都会将事物放置在上下文数据中(context_object_name如果已设置)的值之下,因此我们将明确确保事物在 Publisher上下文数据中。ListView 将增加在合适的page_obj和paginator我们提供我们记得打电话super()。
现在我们可以写一个新的PublisherDetail:
from django.views.generic import ListView
from django.views.generic.detail import SingleObjectMixin
from books.models import Publisher
class PublisherDetail(SingleObjectMixin, ListView):
paginate_by = 2
template_name = "books/publisher_detail.html"
def get(self, request, *args, **kwargs):
self.object = self.get_object(queryset=Publisher.objects.all())
return super().get(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['publisher'] = self.object
return context
def get_queryset(self):
return self.object.book_set.all()
请注意我们是如何设置的self.object,get()以便稍后在get_context_data()和中再次使用它get_queryset()。如果您未设置template_name,则模板将默认为正常 ListView选择,在这种情况下,这是 "books/book_list.html"因为它是一本书籍清单; ListView一无所知SingleObjectMixin,因此毫无 头绪Publisher。
该paginate_by所以你不必创建大量的书籍,看到分页的工作是在故意例如小!这是您要使用的模板:
{% extends "base.html" %}
{% block content %}
<h2>Publisher {{ publisher.name }}</h2>
<ol>
{% for book in page_obj %}
<li>{{ book.title }}</li>
{% endfor %}
</ol>
<div class="pagination">
<span class="step-links">
{% if page_obj.has_previous %}
<a href="?page={{ page_obj.previous_page_number }}">previous</a>
{% endif %}
<span class="current">
Page {{ page_obj.number }} of {{ paginator.num_pages }}.
</span>
{% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}">next</a>
{% endif %}
</span>
</div>
{% endblock %}
一般来说,你可以使用 TemplateResponseMixin和 SingleObjectMixin当你需要它们的功能。如上所示,稍加注意,您甚至可以SingleObjectMixin与结合使用 ListView。但是,随着您尝试这样做,事情变得越来越复杂,一个好的经验法则是:
暗示:您的每个视图都应仅使用mixins或一组基于类的通用视图中的视图:详细信息,列表,编辑和日期。例如,将TemplateView(内置于视图中)与 MultipleObjectMixin(通用列表)结合在一起是很好的选择 ,但是将SingleObjectMixin(通用细节)与MultipleObjectMixin(通用列表)结合起来可能会遇到问题。
为了显示当您尝试变得更复杂时会发生什么,我们展示了一个示例,该示例在存在更简单的解决方案时会牺牲可读性和可维护性。首先,让我们看一下与结合DetailView使用的天真尝试 , FormMixin以使我们能够 POST将Django Form带到与我们使用来显示对象相同的URL DetailView。
回想一下我们先前使用View和 SingleObjectMixin在一起的示例。我们正在记录用户对特定作者的兴趣;现在说,我们要让他们留下信息,说明他们为什么喜欢他们。再次,让我们假设我们不会将其存储在关系数据库中,而是存储在我们不再担心的更深奥的东西中。
此时,很自然地可以Form将封装从用户浏览器发送到Django的信息。还要说我们在REST上投入了巨资,因此我们想使用与显示来自用户的消息相同的URL来显示作者。让我们重写我们的代码AuthorDetailView。
尽管必须将a添加到上下文数据中,以便将其呈现在模板中,但我们将保留GET来自的处理。我们还希望从中引入表单处理,并编写一些代码,以便在表单上正确调用。DetailViewFormFormMixinPOST
注意:我们使用FormMixin并实现 post()自己,而不是尝试DetailView与之 混合FormView(这post()已经提供了合适的方法),因为这两个视图都实现get(),并且事情会变得更加混乱。
我们的新AuthorDetail外观如下所示:
# CAUTION: you almost certainly do not want to do this.
# It is provided as part of a discussion of problems you can
# run into when combining different generic class-based view
# functionality that is not designed to be used together.
from django import forms
from django.http import HttpResponseForbidden
from django.urls import reverse
from django.views.generic import DetailView
from django.views.generic.edit import FormMixin
from books.models import Author
class AuthorInterestForm(forms.Form):
message = forms.CharField()
class AuthorDetail(FormMixin, DetailView):
model = Author
form_class = AuthorInterestForm
def get_success_url(self):
return reverse('author-detail', kwargs={'pk': self.object.pk})
def post(self, request, *args, **kwargs):
if not request.user.is_authenticated:
return HttpResponseForbidden()
self.object = self.get_object()
form = self.get_form()
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
def form_valid(self, form):
# Here, we would record the user's interest using the message
# passed in form.cleaned_data['message']
return super().form_valid(form)
get_success_url()提供重定向到的位置,该位置可用于的默认实现form_valid()。post()如前所述,我们必须提供我们自己的。
之间微妙的相互作用的数量 FormMixin和DetailView已测试我们的管理事物的能力。您不太可能想自己编写此类。
在这种情况下,尽管编写处理代码涉及很多重复,但是您可以post()自己编写方法,并保持 DetailView唯一的通用功能 Form。
可替代地,它仍然会比上面的方法工作少到具有用于处理的形式,其可以使用一个单独的视图 FormView从不同 DetailView无顾虑。
我们实际上在这里试图做的是使用来自同一URL的两个不同的基于类的视图。那么为什么不这样做呢?我们在这里有一个非常清楚的划分:GET请求应获取 DetailView(Form添加到上下文数据中),POST请求应获取FormView。让我们先设置这些视图。
该AuthorDisplay视图与我们首次引入AuthorDetail时几乎相同;我们在写我们自己get_context_data(),使 AuthorInterestForm可用的模板。get_object()为了清楚起见,我们将跳过之前的 替代:
from django import forms
from django.views.generic import DetailView
from books.models import Author
class AuthorInterestForm(forms.Form):
message = forms.CharField()
class AuthorDisplay(DetailView):
model = Author
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['form'] = AuthorInterestForm()
return context
然后AuthorInterest是a FormView,但是我们必须带进来, SingleObjectMixin以便我们可以找到我们正在谈论的作者,并且我们必须记住进行设置template_name以确保表单错误将呈现与AuthorDisplayon 相同的模板GET:
from django.http import HttpResponseForbidden
from django.urls import reverse
from django.views.generic import FormView
from django.views.generic.detail import SingleObjectMixin
class AuthorInterest(SingleObjectMixin, FormView):
template_name = 'books/author_detail.html'
form_class = AuthorInterestForm
model = Author
def post(self, request, *args, **kwargs):
if not request.user.is_authenticated:
return HttpResponseForbidden()
self.object = self.get_object()
return super().post(request, *args, **kwargs)
def get_success_url(self):
return reverse('author-detail', kwargs={'pk': self.object.pk})
最后,我们以一种新的AuthorDetail观点将它们组合在一起。我们已经知道,调用as_view()基于类的视图会使我们的行为与基于函数的视图完全相同,因此我们可以在两个子视图之间进行选择。
当然,您可以通过传递关键字参数as_view()的方式与在URLconf中传递 方式相同,例如,如果您希望该AuthorInterest行为也出现在另一个URL上,但使用不同的模板:
from django.views import View
class AuthorDetail(View):
def get(self, request, *args, **kwargs):
view = AuthorDisplay.as_view()
return view(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
view = AuthorInterest.as_view()
return view(request, *args, **kwargs)
此方法还可以与任何其他直接从View或继承的常规基于类的视图或您自己的基于类的视图一起使用 TemplateView,因为它可以使不同的视图尽可能地分开。
当您想多次执行相同的操作时,基于类的视图就会大放异彩。假设您正在编写一个API,并且每个视图都应返回JSON而不是呈现的HTML。
我们可以创建一个mixin类以在所有视图中使用,一次处理到JSON的转换。
例如,JSON混合可能看起来像这样:
from django.http import JsonResponseclass JSONResponseMixin: """ A mixin that can be used to render a JSON response. """ def render_to_json_response(self, context, **response_kwargs): """ Returns a JSON response, transforming 'context' to make the payload. """ return JsonResponse( self.get_data(context), **response_kwargs ) def get_data(self, context): """ Returns an object that will be serialized as JSON by json.dumps(). """ # Note: This is *EXTREMELY* naive; in reality, you'll need # to do much more complex handling to ensure that arbitrary # objects -- such as Django model instances or querysets # -- can be serialized as JSON. return context
注意:请查看序列化Django对象文档,以获取有关如何正确将Django模型和查询集正确转换为JSON的更多信息。
这个mixin提供了render_to_json_response()一种与签名相同的方法render_to_response()。要使用它,我们需要将其混入一个TemplateView例如,并重写 render_to_response()以render_to_json_response()代替调用:
from django.views.generic import TemplateViewclass JSONView(JSONResponseMixin, TemplateView): def render_to_response(self, context, **response_kwargs): return self.render_to_json_response(context, **response_kwargs)
同样,我们可以将我们的mixin与通用视图之一一起使用。我们可以DetailView通过JSONResponseMixin与django.views.generic.detail.BaseDetailView– 混合来制作自己的版本(混合 了 DetailView模板渲染之前的行为):
from django.views.generic.detail import BaseDetailViewclass JSONDetailView(JSONResponseMixin, BaseDetailView): def render_to_response(self, context, **response_kwargs): return self.render_to_json_response(context, **response_kwargs)
然后可以以与其他任何视图相同的方式部署此视图 DetailView,并且行为完全相同-除了响应的格式。
如果你想成为真正的冒险,你甚至可以混合使用一个 DetailView子类,它能够返回两个 HTML和JSON的内容,根据不同的HTTP请求,的某些属性,如查询参数或HTTP标头。混合使用 JSONResponseMixin和和 SingleObjectTemplateResponseMixin,并render_to_response() 根据用户请求的响应类型覆盖的实现, 以采用适当的呈现方法:
from django.views.generic.detail import SingleObjectTemplateResponseMixinclass HybridDetailView(JSONResponseMixin, SingleObjectTemplateResponseMixin, BaseDetailView): def render_to_response(self, context): # Look for a 'format=json' GET argument if self.request.GET.get('format') == 'json': return self.render_to_json_response(context) else: return super().render_to_response(context)
由于Python解决方法重载的方式,对的调用 super().render_to_response(context)最终调用的 render_to_response() 实现TemplateResponseMixin。
详情参考: https://docs.djangoproject.com/en/3.0/
Django的主要部署平台是WSGI,这是Web服务器和应用程序的Python标准。
Django的startproject管理命令为您设置了一个最小的默认WSGI配置,您可以根据项目的需要对其进行调整,并指导任何符合WSGI的应用服务器使用。
Django包括以下WSGI服务器的入门文档:
使用WSGI进行部署的关键概念是application应用服务器用来与您的代码进行通信的Callable。通常application以在服务器可访问的Python模块中命名的对象的形式提供。
该startproject命令将创建一个/wsgi.py包含此类application可调用文件的文件 。
Django的开发服务器和生产WSGI部署都使用了它。
WSGI服务器application从其配置中获取可调用对象的路径。Django的内置服务器(即runserver 命令)从WSGI_APPLICATION设置中读取它。默认情况下,它设置为.wsgi.application,它指向中的application 可调用对象/wsgi.py。
当WSGI服务器加载您的应用程序时,Django需要导入settings模块-定义整个应用程序的地方。
Django使用 DJANGO_SETTINGS_MODULE环境变量以找到适当的设置模块。它必须包含指向设置模块的虚线路径。您可以将不同的值用于开发和生产。这完全取决于您如何组织设置。
如果未设置此变量,则默认wsgi.py将其设置为 mysite.settings,其中mysite是项目的名称。这就是 runserver默认情况下发现默认设置文件的方式。
注意:由于环境变量是进程范围的,因此当您在同一进程中运行多个Django站点时,这将无效。这与mod_wsgi一起发生。
为避免此问题,请对每个站点在自己的守护进程中使用mod_wsgi的守护程序模式,或通过在中强制执行来覆盖环境中的值。os.environ["DJANGO_SETTINGS_MODULE"] = "mysite.settings"``wsgi.py
要应用WSGI中间件,您可以包装应用程序对象。例如,您可以在以下位置添加这些行wsgi.py:
from helloworld.wsgi import HelloWorldApplication
application = HelloWorldApplication(application)
如果您想将Django应用程序与另一个框架的WSGI应用程序结合使用,也可以用自定义WSGI应用程序替换Django WSGI应用程序,该应用程序以后将其委托给Django WSGI应用程序。
Gunicorn(“绿色独角兽”)是用于UNIX的纯Python WSGI服务器。它没有依赖性,可以使用安装pip。
通过运行安装gunicorn 。有关更多详细信息,请参见gunicorn文档。
python -m pip install gunicorn
在Gunicorn中将Django作为通用WSGI应用程序运行
安装Gunicorn后,将gunicorn提供一个命令来启动Gunicorn服务器进程。gunicorn的最简单调用是传递包含名为的WSGI应用程序对象的模块的位置application,对于典型的Django项目,该对象应 类似于:
gunicorn myproject.wsgi
这将启动一个进程,该进程运行一个正在侦听的线程127.0.0.1:8000。它要求您的项目位于Python路径上;确保该命令最简单的方法是在与manage.py文件相同的目录中运行此命令。
有关其他提示,请参见Gunicorn的部署文档。
uWSGI是使用纯C编码的快速,自修复且对开发人员/ sysadmin友好的应用程序容器服务器。
也可以看看
uWSGI文档提供了一个涵盖Django,nginx和uWSGI(许多可能的部署设置)的教程。以下文档重点介绍如何将Django与uWSGI集成。
uWSGI Wiki描述了几种安装过程。使用Python软件包管理器pip,您可以通过单个命令安装任何uWSGI版本。例如:
# Install current stable version.
$ python -m pip install uwsgi
# Or install LTS (long term support).
$ python -m pip install https://projects.unbit.it/downloads/uwsgi-lts.tar.gz
uWSGI在客户端-服务器模型上运行。您的Web服务器(例如nginx,Apache)与django-uwsgi“工作者”进程进行通信以提供动态内容。
uWSGI支持多种配置过程的方式。
这是启动uWSGI服务器的示例命令:
uwsgi --chdir=/path/to/your/project
--module=mysite.wsgi:application
--env DJANGO_SETTINGS_MODULE=mysite.settings
--master --pidfile=/tmp/project-master.pid
--socket=127.0.0.1:49152 # can also be a file
--processes=5 # number of worker processes
--uid=1000 --gid=2000 # if root, uwsgi can drop privileges
--harakiri=20 # respawn processes taking more than 20 seconds
--max-requests=5000 # respawn processes after serving 5000 requests
--vacuum # clear environment on exit
--home=/path/to/virtual/env # optional path to a virtual environment
--daemonize=/var/log/uwsgi/yourproject.log # background the process
假设您有一个名为的顶级项目包mysite,并且mysite/wsgi.py其中包含一个包含WSGI application 对象的模块。如果您使用Django的最新版本(使用您自己的项目名称代替)运行,这将是您的布局。如果该文件不存在,则需要创建它。请参阅“ 如何使用WSGI进行部署”文档,以获取应放在此文件中的默认内容以及可以添加的其他内容。django-admin startproject mysite``mysite
Django特定的选项如下:
示例ini配置文件:
[uwsgi]
chdir=/path/to/your/project
module=mysite.wsgi:application
master=True
pidfile=/tmp/project-master.pid
vacuum=True
max-requests=5000
daemonize=/var/log/uwsgi/yourproject.log
ini配置文件用法示例:
uwsgi --ini uwsgi.ini
修复UnicodeEncodeError文件上传
如果UnicodeEncodeError在上传文件名包含非ASCII字符的文件时看到,请确保将uWSGI配置为接受非ASCII文件名,方法是将其添加到您的uwsgi.ini:
env = LANG=en_US.UTF-8
有关详细信息,请参见Unicode参考指南的“ 文件”部分。
请参阅有关管理uWSGI进程的uWSGI文档,以获取有关启动,停止和重新加载uWSGI工作程序的信息。
使用Apache和mod_wsgi部署Django 是使Django投入生产的一种久经考验的方法。
mod_wsgi是一个Apache模块,可以承载任何Python WSGI应用程序,包括Django。Django可以与任何支持mod_wsgi的Apache版本一起使用。
在官方的mod_wsgi文档是你的所有关于如何使用mod_wsgi的详细信息来源。您可能需要从安装和配置文档开始。
安装并激活mod_wsgi后,请编辑Apache服务器的 httpd.conf文件并添加以下内容。
WSGIScriptAlias / /path/to/mysite.com/mysite/wsgi.py
WSGIPythonHome /path/to/venv
WSGIPythonPath /path/to/mysite.com
<Directory /path/to/mysite.com/mysite>
<Files wsgi.py>
Require all granted
</Files>
</Directory>
该WSGIScriptAlias行的第一位是您要在其上为应用程序提供服务的基本URL路径(/表示根URL),第二位是“ WSGI文件”的位置–参见下文–在系统上,通常在项目内部包(mysite在此示例中)。这告诉Apache使用该文件中定义的WSGI应用程序为给定URL下的任何请求提供服务。
如果您将项目的Python依赖项安装在中,请使用添加路径。有关更多详细信息,请参见mod_wsgi虚拟环境指南。virtual environmentWSGIPythonHome
该WSGIPythonPath行确保您的项目包可用于在Python路径上导入;换句话说,那行得通。import mysite
该`部分确保Apache可以访问您的wsgi.py` 文件。
接下来,我们需要确保wsgi.pyWSGI应用程序对象存在。从Django 1.4版开始,startproject将为您创建一个。否则,您将需要创建它。请参阅WSGI概述文档,以获取应放在此文件中的默认内容以及可以添加的其他内容。
警告:如果在单个mod_wsgi进程中运行多个Django站点,则所有站点都将使用碰巧先运行的站点的设置。可以通过以下方法解决:
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "{{ project_name }}.settings")
在中wsgi.py,至:
os.environ["DJANGO_SETTINGS_MODULE"] = "{{ project_name }}.settings"
或通过使用mod_wsgi守护程序模式并确保每个站点都在其自己的守护进程中运行。
修复UnicodeEncodeError文件上传
如果UnicodeEncodeError在上传文件名包含非ASCII字符的文件时看到,请确保Apache配置为接受非ASCII文件名:
export LANG='en_US.UTF-8'
export LC_ALL='en_US.UTF-8'
放置此配置的常见位置是/etc/apache2/envvars。
有关详细信息,请参见Unicode参考指南的“ 文件”部分。
建议使用“守护程序模式”运行mod_wsgi(在非Windows平台上)。要创建所需的守护进程组并委派Django实例在其中运行,您将需要添加适当的 WSGIDaemonProcess和WSGIProcessGroup指令。如果您使用守护程序模式,则对上述配置的进一步更改是您不能使用WSGIPythonPath;相反,您应该使用的python-path选项 WSGIDaemonProcess,例如:
WSGIDaemonProcess example.com python-home=/path/to/venv python-path=/path/to/mysite.com
WSGIProcessGroup example.com
如果要在子目录中服务项目(https://example.com/mysite在此示例中),则可以添加WSGIScriptAlias 到上面的配置中:
WSGIScriptAlias /mysite /path/to/mysite.com/mysite/wsgi.py process-group=example.com
有关设置守护程序模式的详细信息,请参见mod_wsgi官方文档。
Django本身不提供文件;它将工作交给您选择的任何Web服务器。
我们建议使用单独的Web服务器(即未同时运行Django的服务器)来提供媒体服务。这里有一些不错的选择:
但是,如果您别无选择,只能在与VirtualHostDjango 相同的Apache上提供媒体文件,则 可以设置Apache以将某些URL用作静态媒体,而将另一些URL使用Django的mod_wsgi接口提供。
这个例子设置的Django在站点根目录,但发球robots.txt, favicon.ico和在什么/static/和/media/URL空间作为静态文件。所有其他URL将使用mod_wsgi提供:
Alias /robots.txt /path/to/mysite.com/static/robots.txt
Alias /favicon.ico /path/to/mysite.com/static/favicon.ico
Alias /media/ /path/to/mysite.com/media/
Alias /static/ /path/to/mysite.com/static/
<Directory /path/to/mysite.com/static>
Require all granted
</Directory>
<Directory /path/to/mysite.com/media>
Require all granted
</Directory>
WSGIScriptAlias / /path/to/mysite.com/mysite/wsgi.py
<Directory /path/to/mysite.com/mysite>
<Files wsgi.py>
Require all granted
</Files>
</Directory>
在django.contrib.staticfiles中时INSTALLED_APPS,Django开发服务器会自动提供admin应用程序(以及所有其他已安装的应用程序)的静态文件。但是,当您使用任何其他服务器布置时,情况并非如此。您负责设置Apache或您使用的任何Web服务器来提供管理文件。
管理文件位于django/contrib/admin/static/adminDjango发行版的()中。
我们强烈建议您使用django.contrib.staticfiles处理管理文件(如上一节中列出Web服务器一起,使用这种手段collectstatic的管理命令收集的静态文件STATIC_ROOT,然后配置你的Web服务器服务STATIC_ROOT的STATIC_URL),但这里有三个其他方法:
Django提供了一个处理程序,允许Apache直接针对Django的身份验证后端对用户进行身份验证。
由于与多个Apache处理身份验证数据库时保持同步是一个常见问题,因此可以将Apache配置为直接针对Django的身份验证系统进行身份 验证。这需要Apache版本> = 2.2和mod_wsgi> = 2.0。例如:
注意:如果您已经安装了自定义用户模型并希望使用此默认身份验证处理程序,则它必须支持一个is_active 属性。如果要使用基于组的授权,则自定义用户必须具有一个名为“组”的关系,该关系引用具有“名称”字段的相关对象。如果您的自定义不符合这些要求,则还可以指定自己的自定义mod_wsgi身份验证处理程序。
注意:在以下配置中使用时,假设您的Apache实例仅运行一个Django应用程序。如果您正在运行多个Django应用程序,请参阅mod_wsgi文档的“ 定义应用程序组”部分以获取有关此设置的更多信息。WSGIApplicationGroup %{GLOBAL}
确保已安装并激活了mod_wsgi,并且已按照步骤使用mod_wsgi设置Apache。
接下来,编辑您的Apache配置,以添加仅希望经过身份验证的用户才能查看的位置:
WSGIScriptAlias / /path/to/mysite.com/mysite/wsgi.py
WSGIPythonPath /path/to/mysite.com
WSGIProcessGroup %{GLOBAL}
WSGIApplicationGroup %{GLOBAL}
<Location "/secret">
AuthType Basic
AuthName "Top Secret"
Require valid-user
AuthBasicProvider wsgi
WSGIAuthUserScript /path/to/mysite.com/mysite/wsgi.py
</Location>
该WSGIAuthUserScript指令告诉mod_wsgi check_password在指定的wsgi脚本中执行该 功能,并传递从提示符处收到的用户名和密码。在此示例中,与 定义由django-admin startproject创建的应用程序WSGIAuthUserScript的相同。WSGIScriptAlias
结合使用Apache 2.2和身份验证
确保mod_auth_basic和mod_authz_user已加载。
这些可能会静态地编译到Apache中,或者您可能需要使用LoadModule在您的中动态加载它们httpd.conf:
LoadModule auth_basic_module modules/mod_auth_basic.so
LoadModule authz_user_module modules/mod_authz_user.so
最后,mysite.wsgi通过导入以下check_password 功能来编辑WSGI脚本,以将Apache的身份验证与站点的身份验证机制联系起来:
import os
os.environ['DJANGO_SETTINGS_MODULE'] = 'mysite.settings'
from django.contrib.auth.handlers.modwsgi import check_password
from django.core.handlers.wsgi import WSGIHandler
application = WSGIHandler()
/secret/现在开始的请求将要求用户进行身份验证。
mod_wsgi 访问控制机制文档提供了其他详细信息和有关替代身份验证方法的信息。
mod_wsgi还提供了将特定位置限制为组成员的功能。
在这种情况下,Apache配置应如下所示:
WSGIScriptAlias / /path/to/mysite.com/mysite/wsgi.py
WSGIProcessGroup %{GLOBAL}
WSGIApplicationGroup %{GLOBAL}
<Location "/secret">
AuthType Basic
AuthName "Top Secret"
AuthBasicProvider wsgi
WSGIAuthUserScript /path/to/mysite.com/mysite/wsgi.py
WSGIAuthGroupScript /path/to/mysite.com/mysite/wsgi.py
Require group secret-agents
Require valid-user
</Location>
为了支持该WSGIAuthGroupScript指令,相同的WSGI脚本 mysite.wsgi还必须导入该groups_for_user函数,该函数返回给定用户所属的列表组。
from django.contrib.auth.handlers.modwsgi import check_password, groups_for_user
如果有需求/secret/,现在也需要用户是“秘密特工”组的成员。
详情参考: https://docs.djangoproject.com/en/3.0/
除了WSGI,Django还支持在ASGI上进行部署,ASGI是用于异步Web服务器和应用程序的新兴Python标准。
Django的startproject管理命令为您设置了一个默认的ASGI配置,您可以根据项目的需要对其进行调整,并指导任何符合ASGI的应用服务器使用。
Django包括以下ASGI服务器的入门文档:
与WSGI一样,ASGI也为您提供了一个application可调用对象,应用程序服务器使用该可调用对象与您的代码进行通信。通常application以在服务器可访问的Python模块中命名的对象的形式提供。
该startproject命令将创建一个/asgi.py包含此类application可调用文件的文件 。
开发服务器(runserver)不会使用它,但是任何ASGI服务器都可以在开发或生产中使用它。
ASGI服务器通常采用可调用的应用程序路径作为字符串。对于大多数Django项目,它看起来像myproject.asgi:application。
警告:尽管Django的默认ASGI处理程序将在同步线程中运行所有代码,但如果您选择运行自己的异步处理程序,则必须注意异步安全性。
不要在任何异步代码中调用阻塞同步函数或库。Django禁止您使用非异步安全的Django部分执行此操作,但第三方应用程序或Python库可能并非如此。
当ASGI服务器加载您的应用程序时,Django需要导入设置模块-定义整个应用程序的位置。
Django使用 DJANGO_SETTINGS_MODULE环境变量以找到适当的设置模块。它必须包含指向设置模块的虚线路径。您可以将不同的值用于开发和生产。这完全取决于您如何组织设置。
如果未设置此变量,则默认asgi.py将其设置为 mysite.settings,其中mysite是项目的名称。
要应用ASGI中间件,或将Django嵌入另一个ASGI应用程序中,可以将Django的application对象包装在asgi.py文件中。例如:
from some_asgi_library import AmazingMiddleware
application = AmazingMiddleware(application)
Daphne是用于UNIX的纯Python ASGI服务器,由Django项目的成员维护。它充当ASGI的参考服务器。
您可以使用以下命令安装Daphne pip:
python -m pip install daphne
安装Daphne后,将daphne提供一个命令来启动Daphne服务器进程。最简单的说,需要使用包含ASGI应用程序对象的模块的位置来调用Daphne,然后是调用该应用程序的位置(用冒号分隔)。
对于典型的Django项目,调用Daphne如下所示:
daphne myproject.asgi:application
这将开始监听一个进程127.0.0.1:8000。它要求您的项目位于Python路径上;确保从与manage.py文件相同的目录中运行此命令。
Uvicorn是基于uvloop和的ASGI服务器httptools,着重于速度。
您可以使用以下命令安装Uvicorn pip:
python -m pip install uvicorn
安装Uvicorn后,将uvicorn提供运行ASGI应用程序的命令。必须使用包含ASGI应用程序对象的模块的位置来调用Uvicorn,然后再调用该应用程序(由冒号分隔)。
对于一个典型的Django项目,调用Uvicorn如下所示:
uvicorn myproject.asgi:application
这将开始监听一个进程127.0.0.1:8000。它要求您的项目位于Python路径上;确保从与manage.py文件相同的目录中运行此命令。
有关更多高级用法,请阅读Uvicorn文档。
详情参考: https://docs.djangoproject.com/en/3.0/
也可以看看
有关使用的简介django.contrib.staticfiles,请参阅 管理静态文件(例如,图像,JavaScript,CSS)。
将静态文件投入生产的基本概述包括两个步骤:collectstatic更改静态文件时运行命令,然后安排将收集的静态文件目录(STATIC_ROOT)移至静态文件服务器并提供服务。根据 STATICFILES_STORAGE,可能需要手动将文件移动到新位置,否则类的post_process方法Storage可能会解决这个问题。
当然,与所有部署任务一样,细节在于魔鬼。每个生产设置都会有所不同,因此您需要调整基本轮廓以适合您的需求。以下是一些可能有用的常见模式。
如果要从已经为您的站点提供服务的同一台服务器提供静态文件,则该过程可能类似于:
您可能希望自动化此过程,尤其是当您有多个Web服务器时。
大多数较大的Django站点使用单独的Web服务器(即未同时运行Django的Web服务器)来提供静态文件。该服务器通常运行其他类型的Web服务器-速度更快,但功能较少。一些常见的选择是:
配置这些服务器不在本文档的讨论范围内。检查每个服务器各自的文档以获取指示。
由于您的静态文件服务器不会运行Django,因此您需要修改部署策略,使其类似于:
另一个常见的策略是为来自Amazon S3和/或CDN(内容交付网络)等云存储提供商的静态文件提供服务。这使您可以忽略提供静态文件的问题,并且通常可以使网页加载速度更快(尤其是在使用CDN时)。
使用这些服务时,基本工作流程看起来与上面类似,除了rsync需要将静态文件传输到存储提供程序或CDN 而不是用于将静态文件传输到服务器之外。
您可以通过多种方式执行此操作,但是如果提供程序具有API,则可以使用自定义文件存储后端 将CDN与Django项目集成。如果您已经编写或正在使用第三方的自定义存储后端,则可以collectstatic通过设置STATICFILES_STORAGE存储引擎来告诉您使用它。
例如,如果您已经编写了一个S3存储后端,则 myproject.storage.S3Storage可以将其用于:
STATICFILES_STORAGE = 'myproject.storage.S3Storage'
完成此操作后,所有您需要做的就是运行collectstatic,您的静态文件将通过存储包推送到S3。如果以后需要切换到其他存储提供商,则只需更改STATICFILES_STORAGE设置即可。
有关如何编写这些后端之一的详细信息,请参阅《 编写自定义存储系统》。有可用的第三方应用程序为许多常用文件存储API提供存储后端。djangopackages.org的概述是一个很好的起点。
有关其中包含的所有设置,命令,模板标记和其他部分的完整详细信息django.contrib.staticfiles,请参见staticfiles参考。
错误报告
在运行公共站点时,应始终关闭该 DEBUG设置。这将使您的服务器运行得更快,并且还可以防止恶意用户查看错误页面可能显示的应用程序详细信息。
但是,DEBUG将set 运行为False表示您将永远不会看到站点生成的错误-每个人都将看到您的公共错误页面。您需要跟踪部署站点中发生的错误,因此可以将Django配置为创建包含有关这些错误的详细信息的报告。
当DEBUGis时False,ADMINS只要您的代码引发未处理的异常并导致内部服务器错误(严格来说,对于HTTP状态代码为500或更高的任何响应),Django都会通过电子邮件发送设置中列出的用户 。这会给管理员立即通知任何错误。该ADMINS会得到错误的描述,一个完整的Python追踪,以及有关导致错误的HTTP请求的详细信息。
注意: 为了发送电子邮件,Django需要一些设置来告诉它如何连接到您的邮件服务器。至少,您需要指定,EMAIL_HOST并且可能 需要EMAIL_HOST_USER和EMAIL_HOST_PASSWORD,尽管根据您的邮件服务器的配置,可能还需要其他设置。有关与电子邮件相关的设置的完整列表,请参阅Django设置文档。
默认情况下,Django将通过root @ localhost发送电子邮件。但是,某些邮件提供商拒绝来自该地址的所有电子邮件。要使用其他发件人地址,请修改SERVER_EMAIL设置。
要激活此行为,请将收件人的电子邮件地址放入 ADMINS设置中。
也可以看看
服务器错误电子邮件是使用日志记录框架发送的,因此您可以通过自定义日志记录配置来自定义此行为。
Django也可以配置为通过电子邮件发送有关断开链接的错误(404“找不到页面”错误)。Django在以下情况下发送有关404错误的电子邮件:
如果满足这些条件,则MANAGERS只要您的代码引发404并且请求具有引荐来源,Django就会通过电子邮件将设置中列出的用户发送给 您。不用电子邮件发送没有推荐人的404,这些人通常是输入损坏的URL或损坏的Web bot的人。当引用者等于请求的URL时,它也会忽略404,因为此行为也来自损坏的Web机器人。
注意: BrokenLinkEmailsMiddleware必须出现在拦截404错误的其他中间件之前,例如 LocaleMiddleware或 FlatpageFallbackMiddleware。将其放在MIDDLEWARE设置的顶部。
您可以通过调整IGNORABLE_404_URLS设置来告诉Django停止报告特定的404 。它应该是已编译的正则表达式对象的列表。例如:
import reIGNORABLE_404_URLS = [ re.compile(r'.(php|cgi)$'), re.compile(r'^/phpmyadmin/'),]
在此示例中,任何以.php或结尾的URL的404 .cgi都不会被报告。以开头的网址也不会/phpmyadmin/。
以下示例显示了如何排除浏览器和搜寻器经常请求的一些常规URL:
import reIGNORABLE_404_URLS = [ re.compile(r'^/apple-touch-icon.*.png$'), re.compile(r'^/favicon.ico$'), re.compile(r'^/robots.txt$'),]
(请注意,这些是正则表达式,因此我们在句点前加了一个反斜杠以将其转义。)
如果您想django.middleware.common.BrokenLinkEmailsMiddleware进一步自定义行为 (例如忽略来自Web爬网程序的请求),则应将其子类化并覆盖其方法。
也可以看看
使用日志记录框架记录404错误。默认情况下,这些日志记录将被忽略,但是您可以通过编写处理程序并适当配置日志记录来将它们用于错误报告。
警告:筛选敏感数据是一个难题,几乎不可能保证敏感数据不会泄漏到错误报告中。因此,错误报告仅应提供给受信任的团队成员,并且您应避免通过Internet(例如通过电子邮件)传输未经加密的错误报告。
错误报告对于调试错误确实很有帮助,因此通常记录尽可能多的有关这些错误的相关信息非常有用。例如,默认情况下,Django会记录引发的异常的完整追溯,每个追溯框架的局部变量以及 HttpRequest的属性。
但是,有时某些类型的信息可能过于敏感,因此可能不适用于跟踪例如用户的密码或信用卡号。因此,除了按照DEBUG文档中所述过滤掉似乎敏感的设置之外,Django还提供了一组函数装饰器,以帮助您控制应从生产环境中的错误报告中过滤掉哪些信息(即在何处 DEBUG设置为False):sensitive_variables()和 sensitive_post_parameters()。
sensitive_variables
(*变量)如果代码中的函数(视图或任何常规回调)使用易于包含敏感信息的局部变量,则可以使用sensitive_variables
装饰器防止这些变量的值包含在错误报告中:
from django.views.decorators.debug import sensitive_variables@sensitive_variables('user', 'pw', 'cc')def process_info(user): pw = user.pass_word cc = user.credit_card_number name = user.name ...
在上述例子中,对于该值user
,pw
和cc
变量将被隐藏,并用星(替换**********
)错误的报告,而值name
变量将被公开。
要从错误日志中系统地隐藏函数的所有局部变量,请不要向sensitive_variables
装饰器提供任何参数:
@sensitive_variables()def my_function(): ...
使用多个装饰器时
如果要隐藏的变量也是函数参数(例如user
,在下面的示例中为“ ”),并且如果修饰的函数具有多个修饰符,则请确保将其放置@sensitive_variables
在修饰符链的顶部。这样,当它通过其他装饰器传递时,它还将隐藏函数参数:
@sensitive_variables('user', 'pw', 'cc')@some_decorator@another_decoratordef process_info(user): ...
sensitive_post_parameters
(* parameters)如果您的一个视图接收到一个易受敏感信息影响的HttpRequest
对象,则可以使用装饰器阻止这些参数的值包含在错误报告中 :POST parameters
sensitive_post_parameters
from django.views.decorators.debug import sensitive_post_parameters@sensitive_post_parameters('pass_word', 'credit_card_number')def record_user_profile(request): UserProfile.create( user=request.user, password=request.POST['pass_word'], credit_card=request.POST['credit_card_number'], name=request.POST['name'], ) ...
在上面的示例中,错误报告内的请求表示中,pass_word
和 credit_card_number
参数的值将被隐藏并用星号(**********
)替换,而该name
参数的值将被公开。
要在错误报告中系统地隐藏请求的所有POST参数,请不要向sensitive_post_parameters
装饰器提供任何参数:
@sensitive_post_parameters()def my_view(request): ...
所有POST参数被系统过滤的某些错误报告出来django.contrib.auth.views
次(login
, password_reset_confirm
,password_change
,和add_view
和 user_change_password
在auth
管理员),以防止敏感信息泄漏的诸如用户密码。
All sensitive_variables()和sensitive_post_parameters()do分别用敏感变量的名称注释修饰的函数,并HttpRequest用敏感的POST参数的名称注释对象,以便以后在发生错误时可以从报告中过滤掉此敏感信息。实际的过滤是由Django的默认错误报告过滤器完成的 django.views.debug.SafeExceptionReporterFilter。**********生成错误报告时,此过滤器使用修饰符的注释将相应的值替换为star()。如果您希望为整个网站覆盖或自定义此默认行为,则需要定义自己的过滤器类,并告诉Django通过以下DEFAULT_EXCEPTION_REPORTER_FILTER设置使用它 :
DEFAULT_EXCEPTION_REPORTER_FILTER = 'path.to.your.CustomExceptionReporterFilter'
您还可以通过设置HttpRequest的exception_reporter_filter 属性,以更精细的方式控制要在任何给定视图中使用的过滤器:
def my_view(request): if request.user.is_authenticated: request.exception_reporter_filter = CustomExceptionReporterFilter() ...
您的自定义过滤器类需要继承 django.views.debug.SafeExceptionReporterFilter并可以覆盖以下方法:
SafeExceptionReporterFilter
SafeExceptionReporterFilter.
is_active
(请求)返回True
以激活其他方法中操作的过滤。默认情况下,如果DEBUG
为,则过滤器处于活动状态False
。
SafeExceptionReporterFilter.
get_post_parameters
(请求)返回过滤后的POST参数字典。默认情况下,它将敏感参数的值替换为星号(**********
)。
SafeExceptionReporterFilter.
get_traceback_frame_variables
(request,tb_frame)返回给定回溯帧的局部变量的过滤字典。默认情况下,它将敏感变量的值替换为star(**********
)。
也可以看看
您还可以通过编写自定义的异常中间件来设置自定义错误报告 。如果您确实编写了自定义错误处理,则最好模拟Django的内置错误处理,如果DEBUG是,则仅报告/记录错误False。
详情参考: https://docs.djangoproject.com/en/3.0/
互联网是一个敌对的环境。在部署Django项目之前,您应该花一些时间在考虑安全性,性能和操作的情况下检查设置。
Django包含许多安全功能。有些是内置的,并且始终启用。其他选项是可选的,因为它们并不总是合适的,或者因为它们不方便开发。例如,强制HTTPS可能不适用于所有网站,并且对于本地开发而言是不切实际的。
性能优化是另一种权衡取舍的方法。例如,缓存在生产中很有用,而对于本地开发则没有用。错误报告的需求也大不相同。
以下清单包括以下设置:
其中许多设置都是敏感的,应视为机密。如果要发布项目的源代码,通常的做法是发布合适的设置进行开发,并使用私有设置模块进行生产。
使用该选件可以自动执行以下所述的某些检查。确保按照选件文档中的说明针对生产设置文件运行它。
check --deploy
密钥必须是一个较大的随机值,并且必须保密。
确保生产中使用的密钥不在其他任何地方使用,并避免将其提交给源代码管理。这减少了攻击者可以从中获取密钥的向量的数量。
可以考虑从环境变量加载密钥,而不是在设置模块中对密钥进行硬编码:
import osSECRET_KEY = os.environ['SECRET_KEY']
或来自文件:
with open('/etc/secret_key.txt') as f: SECRET_KEY = f.read().strip()
您绝不能在生产中启用调试。
当然,您正在使用开发您的项目,因为这将启用方便的功能,例如浏览器中的完整追溯。DEBUG = True
但是,对于生产环境而言,这是一个非常糟糕的主意,因为它会泄露有关项目的大量信息:源代码的摘录,局部变量,设置,使用的库等。
那时,如果没有合适的值,Django根本无法工作。DEBUG = FalseALLOWED_HOSTS
需要此设置来保护您的站点免受某些CSRF攻击。如果使用通配符,则必须对HostHTTP标头执行自己的验证,否则,请确保您不容易受到此类攻击。
您还应该配置位于Django前面的Web服务器以验证主机。它应该以静态错误页面响应或忽略对不正确主机的请求,而不是将请求转发给Django。这样,您将避免在Django日志(或电子邮件(如果您以这种方式配置了错误报告)中)出现虚假错误。例如,在nginx上,您可以设置默认服务器以在无法识别的主机上返回“ 444 No Response”:
server { listen 80 default_server; return 444;}
如果您使用缓存,则连接参数在开发和生产中可能会有所不同。Django默认对每个进程进行本地内存缓存,这可能是不希望的。
缓存服务器通常具有弱认证。确保它们仅接受来自您的应用程序服务器的连接。
数据库连接参数在开发和生产中可能有所不同。
数据库密码非常敏感。您应该像保护他们一样 SECRET_KEY。
为了获得最大的安全性,请确保数据库服务器仅接受来自应用程序服务器的连接。
如果您尚未为数据库设置备份,请立即执行!
如果您的站点发送电子邮件,则需要正确设置这些值。
默认情况下,Django从webmaster @ localhost和root @ localhost发送电子邮件。但是,某些邮件提供商拒绝来自这些地址的电子邮件。要使用其他发件人地址,请修改DEFAULT_FROM_EMAIL和 SERVER_EMAIL设置。
静态文件由开发服务器自动提供。在生产中,必须定义一个STATIC_ROOT目录,collectstatic将它们复制到该目录中 。
有关更多信息,请参见管理静态文件(例如,图像,JavaScript,CSS)。
媒体文件由您的用户上传。他们是不信任的!确保您的Web服务器从不尝试解释它们。例如,如果用户上传 .php文件,则Web服务器不应执行该文件。
现在是检查这些文件的备份策略的好时机。
任何允许用户登录的网站都应实施站点范围的HTTPS,以避免明文传输访问令牌。在Django中,访问令牌包括登录名/密码,会话cookie和密码重置令牌。(如果要通过电子邮件发送密码重置令牌,则无法做很多事情来保护它们。)
保护敏感区域(例如用户帐户或管理员)是不够的,因为HTTP和HTTPS使用相同的会话cookie。您的网络服务器必须将所有HTTP通信重定向到HTTPS,并且只能将HTTPS请求传输到Django。
设置HTTPS后,请启用以下设置。
进行设置True以避免在HTTP上意外传输CSRF cookie。
进行设置True以避免在HTTP上意外传输会话cookie。
设置将禁用仅在开发中有用的几个功能。此外,您可以调整以下设置。DEBUG = False
考虑使用缓存的会话来提高性能。
如果使用数据库支持的会话,请定期清除旧会话,以避免存储不必要的数据。
如果在请求处理时间的很大一部分时间内连接到数据库帐户,则启用持久性数据库连接可以大大提高速度。
这对网络性能有限的虚拟主机有很大帮助。
启用缓存的模板加载器通常会大大提高性能,因为它避免了每次需要渲染每个模板时都对它们进行编译。有关更多信息,请参见 模板加载器文档。
当您将代码推向生产时,它有望变得健壮,但不能排除意外错误。值得庆幸的是,Django可以捕获错误并相应地通知您。
在将网站投入生产之前,请查看日志记录配置,并在收到一些流量后立即检查它是否按预期工作。
有关日志记录的详细信息,请参见日志记录。
ADMINS 将通过电子邮件通知500个错误。
MANAGERS将会收到404错误的通知。 IGNORABLE_404_URLS可以帮助过滤掉虚假报告。
有关通过电子邮件报告错误的详细信息,请参见错误报告。
通过电子邮件报告的错误无法很好地扩展
在您的收件箱被报告淹没之前,请考虑使用诸如Sentry之类的错误监视系统。哨兵也可以汇总日志。
Django包含一些HTTP错误代码的默认视图和模板。您可能希望通过在根模板目录中创建以下模板来覆盖缺省模板:404.html,500.html,403.html,和 400.html。使用这些模板的默认错误视图足以满足99%的Web应用程序的要求,但是您也可以对其进行 自定义。
详情参考: https://docs.djangoproject.com/en/3.0/
XSS攻击使用户可以将客户端脚本注入其他用户的浏览器中。通常,这是通过将恶意脚本存储在数据库中进行检索并将其显示给其他用户,或者通过使用户单击链接而导致攻击者的JavaScript由用户的浏览器执行来实现的。但是,只要未在包含在页面中之前对数据进行足够的清理,XSS攻击就可能源自任何不受信任的数据源,例如cookie或Web服务。
使用Django模板可以保护您免受大多数XSS攻击。但是,重要的是要了解其提供的保护及其限制。
Django模板会转义特定字符 ,这对于HTML来说尤其危险。尽管这可以保护用户免受大多数恶意输入的侵害,但这并不是绝对安全的。例如,它将不会保护以下内容:
<style class={{ var }}>...</style>
如果var将设置为,则可能导致未经授权的JavaScript执行,具体取决于浏览器如何呈现不完美的HTML。(引用属性值将解决这种情况。)'class1 onmouseover=javascript:func()'
is_safe与自定义模板标签,safe模板标签一起使用mark_safe以及关闭自动转义功能时,请务必特别小心。
此外,如果您使用模板系统输出HTML以外的内容,则可能会有完全分开的字符和单词需要转义。
在数据库中存储HTML时,也应特别小心,尤其是在检索和显示HTML时。
CSRF攻击允许恶意用户使用另一用户的凭据执行操作,而无需该用户的知识或同意。
Django具有针对大多数CSRF攻击的内置保护,只要您在适当的地方启用和使用它即可。但是,与任何缓解技术一样,存在局限性。例如,可以全局禁用或针对特定视图禁用CSRF模块。仅当您知道自己在做什么时,才应该这样做。如果您的站点具有您无法控制的子域,则还有其他限制。
CSRF保护通过检查每个POST请求中的秘密来起作用。这样可以确保恶意用户无法将表单POST“重播”到您的网站,而让另一个登录用户无意间提交该表单。恶意用户必须知道特定于用户的秘密(使用cookie)。
与HTTPS一起部署时, CsrfViewMiddleware将检查HTTP引用标头是否设置为同一来源(包括子域和端口)上的URL。因为HTTPS提供了额外的安全性,所以必须通过转发不安全的连接请求并对支持的浏览器使用HSTS来确保连接使用HTTPS可用的连接。
csrf_exempt除非绝对必要,否则请小心用装饰器标记视图。
SQL注入是一种攻击,恶意用户能够在数据库上执行任意SQL代码。这可能导致记录被删除或数据泄漏。
由于Django的查询集是使用查询参数化构造的,因此可以防止SQL注入。查询的SQL代码是与查询的参数分开定义的。由于参数可能是用户提供的,因此是不安全的,因此底层数据库驱动程序会对其进行转义。
Django还使开发人员可以编写原始查询或执行自定义sql。这些功能应谨慎使用,并且您应始终小心谨慎地转义用户可以控制的任何参数。此外,使用extra() 和时应格外小心RawSQL。
点击劫持是一种攻击,其中恶意站点将另一个站点包装在框架中。这种攻击可能导致毫无戒心的用户被诱骗在目标站点上执行意外的操作。
Django包含clickjacking保护,其形式为 在支持的浏览器中可以防止网站在框架内呈现。可以基于每个视图禁用保护或配置发送的确切报头值。X-Frame-Options middleware
强烈建议将中间件用于任何不需要第三方站点将其页面包装在框架中的站点,或者只需要允许站点的一小部分使用该中间件。
在HTTPS后面部署站点对于安全性而言总是更好的选择。否则,恶意网络用户可能会嗅探身份验证凭据或客户端与服务器之间传输的任何其他信息,并且在某些情况下(活动的网络攻击者)可能会更改沿任一方向发送的数据。
如果您想要HTTPS提供的保护并已在服务器上启用了该保护,则可能需要执行一些其他步骤:
Host在某些情况下,Django使用客户端提供的标头构造URL。尽管清除了这些值以防止跨站点脚本攻击,但伪造的Host值可用于跨站点请求伪造,缓存中毒攻击和电子邮件中的中毒链接。
因为即使看似安全的Web服务器配置也容易受到伪造的 Host标头的影响,所以Django会根据方法Host中的ALLOWED_HOSTS设置来 验证标头 django.http.HttpRequest.get_host()。
此验证仅通过get_host();如果您的代码Host直接从request.META您访问标头,则会绕过此安全保护。
有关更多详细信息,请参见完整ALLOWED_HOSTS文档。
警告:本文档的先前版本建议配置Web服务器,以确保它验证传入的HTTP Host标头。尽管仍然建议这样做,但是在许多常见的Web服务器中,似乎可以验证Host标头的配置实际上可能没有这样做。例如,即使将Apache配置为从具有该ServerName设置的非默认虚拟主机为Django站点提供服务,HTTP请求仍然有可能匹配该虚拟主机并提供伪造的Host标头。因此,Django现在要求您进行ALLOWED_HOSTS显式设置,而不是依赖于Web服务器配置。
此外,如果您的配置需要Django,则Django要求您显式启用对X-Forwarded-Host标头的支持 (通过USE_X_FORWARDED_HOST设置)。
浏览器使用Referer标头作为向用户发送有关用户到达那里的信息的方式。通过设置引荐来源网址策略,您可以帮助保护用户的隐私,并限制在哪种情况下设置 Referer标头。有关详细信息,请参见安全中间件参考的引荐来源网址策略部分。
与CSRF的限制类似,它要求部署网站以使不受信任的用户无法访问任何子域,这 django.contrib.sessions也有限制。有关详细信息,请参见有关安全性的会话主题指南部分。
注意:考虑从云服务或CDN提供静态文件,以避免其中的某些问题。
尽管Django提供了开箱即用的良好安全保护,但是正确部署应用程序并利用Web服务器,操作系统和其他组件的安全保护仍然很重要。
详情参考: https://docs.djangoproject.com/en/3.0/
动态网站的基本权衡是动态的。每次用户请求页面时,Web服务器都会进行各种计算-从数据库查询到模板呈现再到业务逻辑-创建站点访问者可以看到的页面。从处理开销的角度来看,这比标准的从文件中读取文件的服务器系统要贵得多。
对于大多数Web应用程序而言,此开销并不大。大多数Web应用程序不是washingtonpost.com或slashdot.org; 它们是流量中等的中小型网站。但是对于中到高流量的站点,必须尽可能减少开销。
那就是缓存的来源。
缓存某些内容是为了保存昂贵的计算结果,因此您下次不必执行计算。以下是一些伪代码,用于说明如何将其应用于动态生成的网页:
given a URL, try finding that page in the cacheif the page is in the cache: return the cached pageelse: generate the page save the generated page in the cache (for next time) return the generated page
Django带有一个健壮的缓存系统,可让您保存动态页面,因此不必为每个请求都计算它们。为了方便起见,Django提供了不同级别的缓存粒度:您可以缓存特定视图的输出,可以仅缓存难以生成的片段,或者可以缓存整个站点。
Django还可以与“下游”缓存(例如Squid和基于浏览器的缓存)配合使用。这些是您不直接控制的缓存类型,但是您可以向它们提供提示(通过HTTP标头)有关站点的哪些部分以及应该如何缓存的提示。
也可以看看
该缓存框架的设计理念, 解释了一些框架的设计决策。
缓存系统需要少量设置。即,您必须告诉它缓存的数据应该存放在哪里–无论是在数据库中,在文件系统上还是直接在内存中。这是一个影响缓存性能的重要决定。是的,某些缓存类型比其他类型更快。
您的缓存首选项进入CACHES设置文件中的设置。以下是的所有可用值的说明 CACHES。
Memcached是Django原生支持的最快,最高效的缓存类型, 是一种完全基于内存的缓存服务器,最初是为处理LiveJournal.com上的高负载而开发的,随后由Danga Interactive开源。Facebook和Wikipedia等网站使用它来减少数据库访问并显着提高网站性能。
Memcached作为守护程序运行,并分配了指定数量的RAM。它所做的只是提供一个用于添加,检索和删除缓存中数据的快速接口。所有数据都直接存储在内存中,因此没有数据库或文件系统使用的开销。
本身安装Memcached后,您需要安装Memcached绑定。有几种可用的Python Memcached绑定。两种最常见的是python-memcached和pylibmc。
要将Memcached与Django结合使用,请执行以下操作:
在此示例中,Memcached使用python-memcached绑定在本地主机(127.0.0.1)端口11211上运行:
CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', 'LOCATION': '127.0.0.1:11211', }}
在此示例中,可以/tmp/memcached.sock使用python-memcached绑定通过本地Unix套接字文件使用Memcached :
CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', 'LOCATION': 'unix:/tmp/memcached.sock', }}
使用pylibmc绑定时,请勿包括unix:/前缀:
CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache', 'LOCATION': '/tmp/memcached.sock', }}
Memcached的一项出色功能是能够在多个服务器上共享缓存。这意味着您可以在多台计算机上运行Memcached守护程序,并且该程序会将计算机组视为单个 缓存,而无需在每台计算机上重复缓存值。要利用此功能,请将所有服务器地址包含在中 LOCATION,以分号或逗号分隔的字符串或列表的形式。
在此示例中,缓存在IP地址为172.19.26.240和172.19.26.242且均在端口11211上运行的Memcached实例之间共享:
CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', 'LOCATION': [ '172.19.26.240:11211', '172.19.26.242:11211', ] }}
在以下示例中,缓存在运行在IP地址172.19.26.240(端口11211),172.19.26.42(端口11212)和172.19.26.244(端口11213)上的Memcached实例上共享:
CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', 'LOCATION': [ '172.19.26.240:11211', '172.19.26.242:11212', '172.19.26.244:11213', ] }}
关于Memcached的最后一点是基于内存的缓存有一个缺点:由于缓存的数据存储在内存中,因此如果服务器崩溃,数据将丢失。显然,内存不是用于永久性数据存储的,因此不要依赖基于内存的缓存作为唯一的数据存储。毫无疑问,任何 Django缓存后端都不应该用于永久存储-它们都旨在作为缓存而非存储的解决方案-但我们在此指出这一点是因为基于内存的缓存特别临时。
Django可以将其缓存的数据存储在您的数据库中。如果您拥有快速索引良好的数据库服务器,则此方法效果最佳。
要将数据库表用作缓存后端:
在此示例中,缓存表的名称为my_cache_table:
CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.db.DatabaseCache', 'LOCATION': 'my_cache_table', }}
在使用数据库缓存之前,必须使用以下命令创建缓存表:
python manage.py createcachetable
这会在您的数据库中创建一个表,该表的格式与Django的数据库缓存系统期望的格式相同。该表的名称取自 LOCATION。
如果使用多个数据库缓存,请createcachetable为每个缓存创建一个表。
如果您使用多个数据库,请createcachetable遵循allow_migrate()数据库路由器的 方法(请参见下文)。
像一样migrate,createcachetable不会触摸现有表格。它只会创建丢失的表。
要打印将要运行的SQL,而不是运行它,请使用 选项。createcachetable --dry-run
如果将数据库缓存与多个数据库一起使用,则还需要为数据库缓存表设置路由说明。为了进行路由,数据库高速缓存表CacheEntry在名为的应用程序中显示为名为的模型 django_cache。该模型不会出现在模型缓存中,但是可以将模型详细信息用于路由目的。
例如,以下路由器会将所有缓存读取操作定向到cache_replica,并将所有写入操作定向到 cache_primary。缓存表将仅同步到 cache_primary:
class CacheRouter: """A router to control all database cache operations""" def db_for_read(self, model, **hints): "All cache read operations go to the replica" if model._meta.app_label == 'django_cache': return 'cache_replica' return None def db_for_write(self, model, **hints): "All cache write operations go to primary" if model._meta.app_label == 'django_cache': return 'cache_primary' return None def allow_migrate(self, db, app_label, model_name=None, **hints): "Only install the cache model on primary" if app_label == 'django_cache': return db == 'cache_primary' return None
如果您没有为数据库缓存模型指定路由方向,则缓存后端将使用default数据库。
当然,如果您不使用数据库缓存后端,则无需担心为数据库缓存模型提供路由说明。
基于文件的后端将每个缓存值序列化并存储为单独的文件。要将此后端设置BACKEND为"django.core.cache.backends.filebased.FileBasedCache"并 设置到 LOCATION合适的目录。例如,要在中存储缓存的数据/var/tmp/django_cache,请使用以下设置:
CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache', 'LOCATION': '/var/tmp/django_cache', }}
如果您使用的是Windows,请将驱动器号放在路径的开头,如下所示:
CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache', 'LOCATION': 'c:/foo/bar', }}
目录路径应该是绝对的-也就是说,它应该从文件系统的根目录开始。是否在设置的末尾加斜杠都没关系。
确保此设置指向的目录存在,并且Web服务器在其下运行的系统用户可以读写。继续上面的示例,如果您的服务器以用户身份运行apache,请确保该目录/var/tmp/django_cache存在并且可由用户读取和写入apache。
如果未在设置文件中指定其他缓存,则这是默认缓存。如果您想要内存中缓存的速度优势,但又不具备运行Memcached的功能,请考虑使用本地内存缓存后端。该缓存是按进程(请参阅下文)并且是线程安全的。要使用它,请设置BACKEND为"django.core.cache.backends.locmem.LocMemCache"。例如:
CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', 'LOCATION': 'unique-snowflake', }}
高速缓存LOCATION用于标识各个内存存储。如果只有一个locmem缓存,则可以省略 LOCATION; 但是,如果您有多个本地内存缓存,则需要至少为其分配一个名称,以使它们分开。
缓存使用最近最少使用(LRU)淘汰策略。
请注意,每个进程都有其自己的专用缓存实例,这意味着不可能进行跨进程缓存。这显然也意味着本地内存缓存不是特别有效的内存,因此对于生产环境而言,它可能不是一个好选择。这对开发很好。
最后,Django附带了一个“虚拟”缓存,该缓存实际上并没有缓存-它只是实现了缓存接口而无所事事。
如果您的生产站点在各个地方都使用了重型缓存,但是在开发/测试环境中却不想缓存并且不想将代码更改为后者的特殊情况,这将非常有用。要激活虚拟缓存,设置BACKEND如下:
CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.dummy.DummyCache', }}
尽管Django开箱即用地支持许多缓存后端,但有时您可能希望使用自定义的缓存后端。要使用Django的外部缓存后端,使用Python导入路径作为 BACKEND该的CACHES设置,如下所示:
CACHES = { 'default': { 'BACKEND': 'path.to.backend', }}
如果要构建自己的后端,则可以将标准缓存后端用作参考实现。您将django/core/cache/backends/在Django源代码的目录中找到代码 。
注意:如果没有真正令人信服的理由,例如不支持它们的主机,则应坚持使用Django随附的缓存后端。他们已经过充分的测试并且有据可查。
可以为每个缓存后端提供其他参数来控制缓存行为。这些参数作为设置中的其他键提供 CACHES。有效参数如下:
在此示例中,文件系统后端的超时时间为60秒,最大容量为1000:
CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache', 'LOCATION': '/var/tmp/django_cache', 'TIMEOUT': 60, 'OPTIONS': { 'MAX_ENTRIES': 1000 } }}
这python-memcached是对象大小限制为2MB 的基于后端的示例配置:
CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', 'LOCATION': '127.0.0.1:11211', 'OPTIONS': { 'server_max_value_length': 1024 * 1024 * 2, } }}
这是pylibmc基于基础的后端的示例配置,该配置启用了二进制协议,SASL身份验证和ketama行为模式:
CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache', 'LOCATION': '127.0.0.1:11211', 'OPTIONS': { 'binary': True, 'username': 'user', 'password': 'pass', 'behaviors': { 'ketama': True, } } }}
一旦设置了缓存,使用缓存的最简单方法就是缓存整个站点。您需要将'django.middleware.cache.UpdateCacheMiddleware'和 添加 'django.middleware.cache.FetchFromCacheMiddleware'到 MIDDLEWARE设置中,如以下示例所示:
MIDDLEWARE = [ 'django.middleware.cache.UpdateCacheMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.cache.FetchFromCacheMiddleware',]
注意
不,这不是输入错误:“更新”中间件必须位于列表的第一位,“获取”中间件必须位于最后。细节有些晦涩,但是如果您想了解完整的故事,请参阅下面的MIDDLEWARE顺序。
然后,将以下必需设置添加到Django设置文件中:
FetchFromCacheMiddleware缓存状态为200的GET和HEAD响应,其中请求和响应标头允许。对具有不同查询参数的相同URL请求的响应被视为唯一页面,并分别进行缓存。该中间件期望用与相应的GET请求相同的响应头来应答HEAD请求。在这种情况下,它可以为HEAD请求返回缓存的GET响应。
此外,会UpdateCacheMiddleware自动在每个标题中设置一些标题 HttpResponse:
有关中间件的更多信息,请参见中间件。
如果视图设置了自己的缓存到期时间(即max-age,其Cache-Control标题中有一个部分),则页面将一直缓存到到期时间,而不是CACHE_MIDDLEWARE_SECONDS。使用装饰器 django.views.decorators.cache可以轻松设置视图的过期时间(使用cache_control()装饰器)或禁用视图的缓存(使用 never_cache()装饰器)。有关这些装饰器的更多信息,请参见“ 使用其他标头”部分。
如果USE_I18N设置为,True则生成的缓存键将包括活动语言的名称-另请参见 Django如何发现语言首选项。这使您可以轻松地缓存多语言站点,而不必自己创建缓存密钥。
缓存键还包括积极的语言时, USE_L10N设置为True与当前时区时USE_TZ被设置为True。
django.views.decorators.cache.
cache_page
()使用缓存框架的更精细的方法是缓存单个视图的输出。django.views.decorators.cache定义一个cache_page 装饰器,该装饰器将自动为您缓存视图的响应:
from django.views.decorators.cache import cache_page@cache_page(60 * 15)def my_view(request): ...
cache_page有一个参数:缓存超时(以秒为单位)。在上面的示例中,my_view()视图结果将被缓存15分钟。(请注意,我们出于可读性的目的编写了该代码。它将被评估为– 15分钟乘以每分钟60秒。)60 * 1560 * 15900
与每个站点的缓存一样,每个视图的缓存也是从URL键入的。如果多个URL指向同一视图,则每个URL将被分别缓存。继续该my_view示例,如果您的URLconf如下所示:
urlpatterns = [ path('foo/<int:code>/', my_view),]
然后,您所期望的/foo/1/和的请求/foo/23/将分别进行缓存。但是,一旦/foo/23/请求了特定的URL(例如),则对该URL的后续请求将使用缓存。
cache_page也可以采用可选的关键字参数,cache该参数指示装饰器CACHES在缓存视图结果时使用特定的缓存(来自您的 设置)。默认情况下, default将使用缓存,但是您可以指定所需的任何缓存:
@cache_page(60 * 15, cache="special_cache")def my_view(request): ...
您还可以基于每个视图覆盖缓存前缀。cache_page 带有一个可选的关键字参数,key_prefix其工作方式CACHE_MIDDLEWARE_KEY_PREFIX 与中间件的设置相同。可以这样使用:
@cache_page(60 * 15, key_prefix="site1")def my_view(request): ...
的key_prefix和cache参数可以被同时指定。该 key_prefix参数和KEY_PREFIX 规定下,CACHES将串联。
上一节中的示例已经硬编码了缓存视图的事实,因为该视图cache_page改变了my_view功能。这种方法将您的视图耦合到高速缓存系统,由于多种原因,这种方法并不理想。例如,您可能想在另一个无缓存的站点上重用视图功能,或者可能希望将视图分发给可能希望在不被缓存的情况下使用它们的人。这些问题的解决方案是在URLconf中指定每个视图的缓存,而不是在视图函数本身旁边。
您可以通过cache_page在URLconf中引用视图函数时将其包装在一起来实现。这是之前的旧版URLconf:
urlpatterns = [ path('foo/<int:code>/', my_view),]
这是同一件事,my_view包裹在其中cache_page:
from django.views.decorators.cache import cache_pageurlpatterns = [ path('foo/<int:code>/', cache_page(60 * 15)(my_view)),]
如果您希望获得更多控制权,则还可以使用cachetemplate标签缓存模板片段。要让您的模板访问此标签,请放在 模板顶部附近。{% load cache %}
该模板标签缓存块中的内容给定的时间量。它至少需要两个参数:高速缓存超时(以秒为单位)和提供高速缓存片段的名称。如果超时为,则片段将永远被缓存。名称将按原样使用,请勿使用变量。例如:{% cache %}None
{% load cache %}{% cache 500 sidebar %} .. sidebar ..{% endcache %}
有时,您可能希望根据片段内显示的一些动态数据来缓存片段的多个副本。例如,您可能想要为站点中的每个用户提供上一个示例中使用的侧边栏的单独的缓存副本。为此,请将一个或多个其他参数(可以是带或不带过滤器的变量)传递给模板标记,以唯一地标识缓存片段:{% cache %}
{% load cache %}{% cache 500 sidebar request.user.username %} .. sidebar for logged in user ..{% endcache %}
如果USE_I18N将设置为True每个站点,则中间件缓存将 尊重活动语言。对于cache模板标记,您可以使用模板中可用的特定于 翻译的变量之一来获得相同的结果:
{% load i18n %}{% load cache %}{% get_current_language as LANGUAGE_CODE %}{% cache 600 welcome LANGUAGE_CODE %} {% trans "Welcome to example.com" %}{% endcache %}
缓存超时可以是模板变量,只要模板变量解析为整数值即可。例如,如果将模板变量 my_timeout设置为value 600,那么以下两个示例是等效的:
{% cache 600 sidebar %} ... {% endcache %}{% cache my_timeout sidebar %} ... {% endcache %}
此功能有助于避免模板中的重复。您可以在一个位置的变量中设置超时,然后重用该值。
默认情况下,缓存标签将尝试使用名为“ template_fragments”的缓存。如果不存在这样的缓存,它将退回到使用默认缓存。您可以选择与using关键字参数一起使用的备用缓存后端,该参数必须是标记的最后一个参数。
{% cache 300 local-thing ... using="localcache" %}
指定未配置的缓存名称被视为错误。
django.core.cache.utils.
make_template_fragment_key
(fragment_name,vary_on =无)如果要获取用于缓存片段的缓存密钥,可以使用 make_template_fragment_key。fragment_name与cachetemplate标签的第二个参数相同;vary_on是传递给标记的所有其他参数的列表。该功能对于使缓存项无效或覆盖很有用,例如:
>>> from django.core.cache import cache>>> from django.core.cache.utils import make_template_fragment_key# cache key for {% cache 500 sidebar username %}>>> key = make_template_fragment_key('sidebar', [username])>>> cache.delete(key) # invalidates cached template fragment
有时,缓存整个渲染的页面并不会带来太多好处,实际上,这会带来不便。
例如,也许您的站点包含一个视图,该视图的结果取决于几个昂贵的查询,这些查询的结果以不同的间隔更改。在这种情况下,使用每个站点或每个视图缓存策略提供的全页缓存并不理想,因为您不想缓存整个结果(因为某些数据经常更改),但您仍想缓存很少更改的结果。
对于此类情况,Django会公开一个低级缓存API。您可以使用此API以所需的任意粒度级别将对象存储在缓存中。您可以缓存可以安全腌制的任何Python对象:字符串,字典,模型对象列表等。(可以对大多数常见的Python对象进行腌制;有关腌制的更多信息,请参考Python文档。)
django.core.cache.
caches
您可以CACHES
通过类似dict的对象访问在设置中配置的缓存:django.core.cache.caches
。在同一线程中重复请求相同的别名将返回相同的对象。
>>> from django.core.cache import caches>>> cache1 = caches['myalias']>>> cache2 = caches['myalias']>>> cache1 is cache2True
如果指定的键不存在,InvalidCacheBackendError
将引发。
为了提供线程安全,将为每个线程返回不同的缓存后端实例。
django.core.cache.
cache
作为一种快捷方式,默认高速缓存可用于 django.core.cache.cache
:
>>> from django.core.cache import cache
此对象等效于caches['default']
。
基本界面是:
cache.
set
(key,value,timeout = DEFAULT_TIMEOUT,version = None)>>> cache.set('my_key', 'hello, world!', 30)
cache.
get
(key,default = None,version = None)>>> cache.get('my_key')'hello, world!'
key应该是str,并且value可以是任何可挑选的Python对象。
该timeout参数是可选的,并且默认为设置中timeout相应后端的参数CACHES(如上所述)。这是该值应存储在缓存中的秒数。传递 Nonefor timeout将永远缓存该值。一个timeout的0 将不缓存值。
如果对象在缓存中不存在,则cache.get()返回None:
>>> # Wait 30 seconds for 'my_key' to expire...>>> cache.get('my_key')None
我们建议不要将文字值存储None在缓存中,因为您将无法区分存储的None值和返回值为的缓存未命中None。
cache.get()可以default争论。这指定了如果对象在缓存中不存在则返回哪个值:
>>> cache.get('my_key', 'has expired')'has expired'
cache.
add
(key,value,timeout = DEFAULT_TIMEOUT,version = None)若要仅在密钥尚不存在时添加密钥,请使用add()方法。它使用与相同的参数set(),但是如果指定的键已经存在,它将不会尝试更新缓存:
>>> cache.set('add_key', 'Initial value')>>> cache.add('add_key', 'New value')>>> cache.get('add_key')'Initial value'
如果您需要知道是否add()在缓存中存储了值,则可以检查返回值。True如果存储了值,它将返回, False否则返回。
cache.
get_or_set
(key,default,timeout = DEFAULT_TIMEOUT,version = None)如果要获取键的值,或者如果键不在缓存中则要设置值,则可以使用该get_or_set()方法。它使用与相同的参数,get() 但默认设置为该键的新缓存值,而不是返回:
>>> cache.get('my_new_key') # returns None>>> cache.get_or_set('my_new_key', 'my new value', 100)'my new value'
您还可以将任何callable作为默认值传递:
>>> import datetime>>> cache.get_or_set('some-timestamp-key', datetime.datetime.now)datetime.datetime(2014, 12, 11, 0, 15, 49, 457920)
cache.
get_many
(keys,version = None)还有一个get_many()接口只命中一次缓存。 get_many()返回一个字典,其中包含您要求的所有键,这些键实际上已存在于缓存中(并且尚未过期):
>>> cache.set('a', 1)>>> cache.set('b', 2)>>> cache.set('c', 3)>>> cache.get_many(['a', 'b', 'c']){'a': 1, 'b': 2, 'c': 3}
cache.
set_many
(dict,超时)要更有效地设置多个值,请使用set_many()传递键值对字典:
>>> cache.set_many({'a': 1, 'b': 2, 'c': 3})>>> cache.get_many(['a', 'b', 'c']){'a': 1, 'b': 2, 'c': 3}
像一样cache.set(),set_many()带有一个可选timeout参数。
在支持的后端(memcached)上,set_many()返回未能插入的密钥列表。
cache.
delete
(key,version = None)您可以显式删除键,delete()以清除特定对象的缓存:
>>> cache.delete('a')
cache.
delete_many
(keys,version = None)如果要一次清除一堆键,delete_many()可以列出要清除的键列表:
>>> cache.delete_many(['a', 'b', 'c'])
cache.
clear
()最后,如果要删除缓存中的所有键,请使用 cache.clear()。注意这一点。clear()将从 缓存中删除所有内容,而不仅仅是您的应用程序设置的键。
>>> cache.clear()
cache.
touch
(key,timeout = DEFAULT_TIMEOUT,version = None)cache.touch()为密钥设置新的到期时间。例如,要将密钥更新为从现在起10秒钟过期:
>>> cache.touch('a', 10)True
与其他方法一样,该timeout参数是可选的,并且默认为设置中TIMEOUT相应后端的 选项CACHES。
touch()True如果按键被成功触摸,False 则返回;否则返回。
cache.
incr
(key,delta = 1,version = None)cache.
decr
(key,delta = 1,version = None)您也可以分别使用incr()或decr()方法递增或递减已存在的键 。默认情况下,现有的高速缓存值将递增或递减1。可以通过为递增/递减调用提供参数来指定其他递增/递减值。如果您尝试增加或减少不存在的缓存键,将引发ValueError:
>>> cache.set('num', 1)>>> cache.incr('num')2>>> cache.incr('num', 10)12>>> cache.decr('num')11>>> cache.decr('num', 5)6
注意:incr()/ decr()方法不保证是原子的。在那些支持原子增量/减量的后端(最值得注意的是,内存缓存的后端)上,增量和减量操作将是原子的。但是,如果后端本身不提供增量/减量操作,则将使用两步检索/更新来实现。
cache.
close
()close()如果由缓存后端实现,则可以关闭与缓存的连接。
>>> cache.close()
注意:对于不实现close方法的缓存,它是无操作的。
如果要在服务器之间或生产环境与开发环境之间共享缓存实例,则一台服务器缓存的数据可能会被另一台服务器使用。如果服务器之间的缓存数据格式不同,则可能导致某些很难诊断的问题。
为防止这种情况,Django提供了为服务器使用的所有缓存键添加前缀的功能。保存或检索特定的缓存键后,Django将自动为缓存键添加KEY_PREFIX缓存设置的值 。
通过确保每个Django实例都有一个不同的 KEY_PREFIX,您可以确保缓存值不会发生冲突。
更改使用缓存值的运行代码时,可能需要清除所有现有的缓存值。最简单的方法是刷新整个缓存,但这会导致仍然有效且有用的缓存值丢失。
Django提供了一种更好的方法来定位各个缓存值。Django的缓存框架具有系统范围的版本标识符,该标识符使用VERSION缓存设置指定。此设置的值会自动与缓存前缀和用户提供的缓存键组合在一起,以获取最终的缓存键。
默认情况下,任何密钥请求都将自动包含站点默认的缓存密钥版本。但是,原始缓存功能都包含一个version参数,因此您可以指定要设置或获取的特定缓存键版本。例如:
>>> # Set version 2 of a cache key>>> cache.set('my_key', 'hello world!', version=2)>>> # Get the default version (assuming version=1)>>> cache.get('my_key')None>>> # Get version 2 of the same key>>> cache.get('my_key', version=2)'hello world!'
可以使用incr_version()和decr_version()方法对特定键的版本进行递增和递减。这样可以使特定的键更改为新版本,而其他键不受影响。继续前面的示例:
>>> # Increment the version of 'my_key'>>> cache.incr_version('my_key')>>> # The default version still isn't available>>> cache.get('my_key')None# Version 2 isn't available, either>>> cache.get('my_key', version=2)None>>> # But version 3 *is* available>>> cache.get('my_key', version=3)'hello world!'
如前两节所述,用户不能完全原样使用用户提供的缓存密钥,而是将其与缓存前缀和密钥版本结合使用以提供最终的缓存密钥。默认情况下,这三个部分使用冒号连接起来以生成最终字符串:
def make_key(key, key_prefix, version): return '%s:%s:%s' % (key_prefix, version, key)
如果要以不同的方式组合各部分,或对最终键进行其他处理(例如,获取键部分的哈希摘要),则可以提供自定义键功能。
的KEY_FUNCTION高速缓存设置指定匹配的原型的功能的虚线路径 make_key()的上方。如果提供,将使用此自定义按键功能代替默认的按键组合功能。
Memcached是最常用的生产缓存后端,它不允许长度超过250个字符或包含空格或控制字符的缓存键,并且使用此类键会导致异常。为了鼓励缓存可移植的代码并最大程度地减少令人不快的意外,django.core.cache.backends.base.CacheKeyWarning如果使用的键会导致memcached错误,则其他内置的缓存后端会发出警告()。
如果您使用的生产后端可以接受更广泛的键范围(自定义后端或非内存缓存的内置后端之一),并且希望在没有警告的情况下使用更大范围的键,则可以CacheKeyWarning在此代码中保持静音management您之一的模块 INSTALLED_APPS:
import warningsfrom django.core.cache import CacheKeyWarningwarnings.simplefilter("ignore", CacheKeyWarning)
如果您想为内置后端之一提供自定义密钥验证逻辑,则可以对其进行子类化,仅覆盖validate_key 方法,并按照说明使用自定义缓存后端。例如,要为locmem后端执行此操作,请将以下代码放在模块中:
from django.core.cache.backends.locmem import LocMemCacheclass CustomLocMemCache(LocMemCache): def validate_key(self, key): """Custom validation, raising exceptions or warnings as needed.""" ...
…并在设置的BACKEND一部分中使用指向该类的虚线Python路径 CACHES。
到目前为止,本文档的重点是缓存您自己的数据。但是另一种类型的缓存也与Web开发有关:由“下游”缓存执行的缓存。这些系统甚至可以在请求到达您的网站之前为用户缓存页面。
以下是下游缓存的一些示例:
下游缓存可以极大地提高效率,但是却存在危险:许多网页的内容基于身份验证和许多其他变量而有所不同,并且仅基于URL盲目保存页面的缓存系统可能会将不正确或敏感的数据暴露给后续这些页面的访问者。
例如,如果您使用Web电子邮件系统,则“收件箱”页面的内容取决于登录的用户。如果ISP盲目缓存了您的站点,则第一个通过该ISP登录的用户将拥有其用户。特定的收件箱页面已缓存,以供该站点的后续访问者使用。那不酷。
幸运的是,HTTP提供了解决此问题的方法。存在许多HTTP标头,以指示下游缓存根据指定的变量来区分其缓存内容,并告知缓存机制不要缓存特定页面。我们将在以下各节中介绍其中的一些标题。
的Vary报头定义哪些请求头的高速缓存机制建立其缓存键时应该考虑到。例如,如果网页的内容取决于用户的语言偏好,则称该页面“随语言而异”。
默认情况下,Django的缓存系统使用请求的标准URL(例如,)创建其缓存密钥 "https://www.example.com/stories/2005/?order_by=author"。这意味着对该URL的每个请求都将使用相同的缓存版本,而不管用户代理差异(例如Cookie或语言偏好设置)如何。但是,如果此页面根据请求标头(例如Cookie,语言或用户代理)的不同而产生不同的内容,则需要使用Vary 标头来告诉缓存机制页面输出取决于那些的东西。
要在Django中执行此操作,请使用方便的 django.views.decorators.vary.vary_on_headers()视图装饰器,如下所示:
from django.views.decorators.vary import vary_on_headers@vary_on_headers('User-Agent')def my_view(request): ...
在这种情况下,缓存机制(例如Django自己的缓存中间件)将为每个唯一的用户代理缓存页面的单独版本。
使用vary_on_headers装饰器而不是手动设置Vary标头(使用类似的东西 )的好处是,装饰器将添加到 标头(可能已经存在),而不是从头开始设置它并可能覆盖其中的任何内容。response['Vary'] = 'user-agent'Vary
您可以将多个标头传递给vary_on_headers():
@vary_on_headers('User-Agent', 'Cookie')def my_view(request): ...
这告诉下游缓存在这两者上有所不同,这意味着用户代理和cookie的每种组合都将获得自己的缓存值。例如,具有用户代理Mozilla和cookie值foo=bar的请求将被认为不同于具有用户代理Mozilla和cookie值 的请求foo=ham。
因为在cookie上进行更改非常普遍,所以有一个 django.views.decorators.vary.vary_on_cookie()装饰器。这两个视图是等效的:
@vary_on_cookiedef my_view(request): ...@vary_on_headers('Cookie')def my_view(request): ...
您传递给的标头vary_on_headers不区分大小写; "User-Agent"和一样"user-agent"。
您也可以django.utils.cache.patch_vary_headers()直接使用辅助功能。此函数设置或添加到。例如:Vary header
from django.shortcuts import renderfrom django.utils.cache import patch_vary_headersdef my_view(request): ... response = render(request, 'template_name', context) patch_vary_headers(response, ['Cookie']) return response
patch_vary_headers将HttpResponse实例作为第一个参数,并将不区分大小写的标头名称的列表/元组作为第二个参数。
有关Vary标头的更多信息,请参见 官方Vary规格。
缓存的其他问题是数据的私密性以及应在级联缓存中存储数据的位置的问题。
用户通常面临两种缓存:他们自己的浏览器缓存(私有缓存)和提供者的缓存(公共缓存)。公共缓存由多个用户使用,并由其他人控制。这就给敏感数据带来了麻烦,例如,您不希望将银行帐号存储在公共缓存中。因此,Web应用程序需要一种方法来告诉缓存哪些数据是私有数据,哪些是公共数据。
解决方案是指示页面的缓存应为“专用”。要在Django中执行此操作,请使用cache_control()视图装饰器。例:
from django.views.decorators.cache import cache_control@cache_control(private=True)def my_view(request): ...
该装饰器负责在后台发送适当的HTTP标头。
请注意,缓存控制设置“专用”和“公用”是互斥的。装饰器确保如果应设置为“ private”,则删除“ public”指令(反之亦然)。这两个伪指令的示例用法是提供私有条目和公共条目的博客网站。公共条目可以缓存在任何共享缓存中。以下代码使用 patch_cache_control()手动方式修改缓存控制标头(由cache_control()装饰器内部调用 ):
from django.views.decorators.cache import patch_cache_controlfrom django.views.decorators.vary import vary_on_cookie@vary_on_cookiedef list_blog_entries_view(request): if request.user.is_anonymous: response = render_only_public_entries() patch_cache_control(response, public=True) else: response = render_private_and_public_entries(request.user) patch_cache_control(response, private=True) return response
您还可以通过其他方式控制下游缓存(请参见 RFC 7234,了解有关HTTP缓存的详细信息)。例如,即使您不使用Django的服务器端缓存框架,您仍然可以使用以下命令告诉客户端将视图缓存一定的时间:最大年龄 指令:
from django.views.decorators.cache import cache_control@cache_control(max_age=3600)def my_view(request): ...
(如果你做使用缓存中间件,它已经设置max-age与值CACHE_MIDDLEWARE_SECONDS的设置。在这种情况下,自定义max_age从 cache_control()装饰将优先考虑,并且头部值将被正确地合并。)
任何有效的Cache-Control响应指令在中均有效cache_control()。这里还有更多示例:
可以在IANA注册中心中找到已知指令的完整列表 (请注意,并非所有指令都适用于响应)。
如果要使用标头来完全禁用缓存,请 never_cache()使用视图装饰器来添加标头,以确保浏览器或其他缓存不会缓存响应。例:
from django.views.decorators.cache import never_cache@never_cachedef myview(request): ...
如果您使用缓存中间件,则将每一半放在MIDDLEWARE设置中的正确位置上很重要。这是因为缓存中间件需要知道通过哪些头来更改缓存存储。中间件总是尽可能在Vary响应头中添加一些内容。
UpdateCacheMiddleware在响应阶段运行,中间件以相反的顺序运行,因此列表顶部的项目在响应阶段最后运行。因此,您需要确保它UpdateCacheMiddleware 出现在任何其他可能向Vary 标头添加内容的中间件之前。以下中间件模块可以这样做:
FetchFromCacheMiddleware另一方面,在请求阶段运行,其中中间件首先应用,因此列表顶部的项目在请求阶段首先运行。该FetchFromCacheMiddleware还需要经过其他中间件更新运行Vary头,所以 FetchFromCacheMiddleware必须在后的是这样做的任何项目。
详情参考: https://docs.djangoproject.com/en/3.0/
通常一个人首先关心的是编写代码的作品,它的逻辑功能根据需要产生预期的输出。但是,有时这还不足以使代码高效地工作。
在这种情况下,需要的是某种东西-实际上,通常是一系列东西-可以提高代码的性能,而又不会,或者仅以最小的方式影响代码的行为。
清楚了解“性能”的含义很重要。不仅有一个指标。
提高速度可能是程序最明显的目标,但有时可能会寻求其他性能改进,例如降低内存消耗或减少对数据库或网络的需求。
一个方面的改进通常会带来另一方面的改进,但并非总是如此;有时一个人甚至可以牺牲另一个人。例如,程序速度的提高可能会导致它使用更多的内存。更糟糕的是,这可能是自欺欺人的-如果速度提升如此之耗内存,以至于系统开始耗尽内存,那么您的弊大于利。
还有其他需要权衡的方面。您自己的时间是宝贵的资源,比CPU时间更宝贵。有些改进可能太难了,不值得实施,或者可能影响代码的可移植性或可维护性。并非所有的性能改进都值得付出努力。
因此,您需要知道要实现哪些性能改进,并且还需要知道有充分的理由朝着这个方向瞄准-并且您需要:
仅仅猜测或假设效率低下在代码中是没有用的。
django-debug-toolbar是一个非常方便的工具,可以深入了解您的代码在做什么以及花了多少时间。特别是,它可以显示页面生成的所有SQL查询以及每个查询花费了多长时间。
工具栏也可以使用第三方面板,该面板可以(例如)报告缓存性能和模板渲染时间。
有许多免费服务可以从远程HTTP客户端的角度分析和报告站点页面的性能,实际上是在模拟实际用户的体验。
这些无法报告您的代码内部,但可以提供有用的洞察力来了解您网站的整体性能,包括无法在Django环境中充分衡量的方面。示例包括:
还有一些付费服务执行类似的分析,其中包括一些支持Django的服务,这些服务可以与您的代码库集成以更全面地分析其性能。
优化方面的某些工作涉及解决性能缺陷,但是某些工作也可以内置于您要做的事情中,这是您甚至在开始考虑提高性能之前就应该采用的良好实践的一部分。
在这方面,Python是一种出色的语言,因为外观优美且感觉正确的解决方案通常是性能最好的解决方案。与大多数技能一样,学习“看起来正确”的东西需要练习,但是最有用的准则之一是:
Django提供了许多不同的处理方式,但是仅仅因为可以以某种方式做某事并不意味着这是最合适的方式。例如,您可能会发现您可以在QuerySet,Python或模板中计算相同的东西-集合中的项目数,也许 。
但是,在较低级别而不是较高级别进行此工作几乎总是会更快。在更高的层次上,系统必须通过多层抽象和机器层次来处理对象。
也就是说,数据库通常可以比Python更快地完成任务,而Python可以比模板语言更快地完成任务:
# QuerySet operation on the database# fast, because that's what databases are good atmy_bicycles.count()# counting Python objects# slower, because it requires a database query anyway, and processing# of the Python objectslen(my_bicycles)# Django template filter# slower still, because it will have to count them in Python anyway,# and because of template language overheads{{ my_bicycles|length }}
一般而言,最适合该工作的级别是适合编写代码的最低级别。
注意
上面的示例仅是说明性的。
首先,在现实生活中,您需要考虑计数前后发生的事情,以找出在特定情况下执行此操作的最佳方法。数据库优化文档描述了一种情况,在这种情况下,模板中的计数会更好。
其次,还有其他选择要考虑:在实际情况下,直接从模板调用方法可能是最合适的选择。{{ my_bicycles.count }}QuerySet count()
通常,计算值很昂贵(即耗费资源且速度很慢),因此将值保存到可快速访问的缓存中以备下次使用时会产生巨大的好处。
Django具有完善的缓存框架以及其他较小的缓存功能,这是一项足够重要且功能强大的技术。
Django的缓存框架通过保存动态内容,因此无需为每个请求进行计算,就为提高性能提供了非常重要的机会。
为了方便起见,Django提供了不同级别的缓存粒度:您可以缓存特定视图的输出,或者仅缓存难以生成的片段,甚至整个站点。
实施缓存不应被视为改善性能不佳的代码的替代方法,因为它的编写质量很差。这是生成性能良好的代码的最后步骤之一,而不是捷径。
通常必须多次调用一个类实例的方法。如果该功能很昂贵,那么这样做会很浪费。
使用cached_property装饰器可以保存属性返回的值。下次在该实例上调用该函数时,它将返回保存的值,而不是重新计算它。请注意,这仅适用于将self参数作为唯一参数的方法,并将方法更改为属性。
某些Django组件也具有自己的缓存功能;这些将在下面与那些组件相关的部分中讨论。
懒惰是对缓存的一种补充策略。缓存通过保存结果来避免重新计算。懒惰会延迟计算,直到真正需要它为止。
惰性允许我们在实例化它们之前,甚至在可能实例化它们之前都引用它们。这有许多用途。
例如,可以在甚至不知道目标语言之前就使用惰性翻译,因为直到真正需要翻译后的字符串(例如在渲染的模板中)时,它才发生。
懒惰也是一种通过首先避免工作来节省精力的方法。就是说,懒惰的一个方面是什么也要做,直到必须要做,因为毕竟可能没有必要。因此,懒惰可能会影响性能,并且相关工作的成本越高,通过懒惰获得的收益就越大。
Python提供了许多用于懒惰求值的工具,尤其是通过 生成器和生成器表达式构造。值得阅读Python的惰性,以发现在代码中使用惰性模式的机会。
Django本身很懒。在的评估中可以找到一个很好的例子QuerySets。QuerySet是惰性的。因此,QuerySet可以创建,传递和与其他对象组合使用 QuerySets,而无需实际进行任何数据库访问以获取其描述的项目。传递的是QuerySet对象,而不是数据库最终需要的项目集合。
另一方面,某些操作将强制评估QuerySet。避免过早评估a QuerySet可以节省对数据库的昂贵而不必要的行程。
Django还提供了一个keep_lazy()装饰器。这允许使用惰性参数调用的函数本身表现为惰性,仅在需要时才进行评估。因此,在严格要求之前,不会调用惰性参数(可能是昂贵的参数)进行评估。
Django的数据库层提供了多种方法来帮助开发人员从其数据库中获得最佳性能。该数据库优化文档汇聚链接到相关文件,并增加了各种技巧,大纲的步骤尝试优化数据库使用情况时服用。
启用持久连接可以在大部分请求处理时间中加快与数据库帐户的连接。
例如,这对网络性能有限的虚拟主机有很大帮助。
Django随附了一些有用的中间件 ,可以帮助您优化网站的性能。它们包括:
添加了对现代浏览器的支持,以基于ETag和Last-Modified标头有条件地获取响应 。如果需要,它还会计算并设置一个ETag。
压缩所有现代浏览器的响应,节省带宽和传输时间。请注意,当前将GZipMiddleware视为安全风险,并且容易受到使TLS / SSL提供的保护无效的攻击的攻击。有关GZipMiddleware更多信息,请参阅警告。
使用缓存的会话可能是一种通过避免从较慢的存储源(如数据库)中加载会话数据,而将频繁使用的会话数据存储在内存中来提高性能的方法。
静态文件(根据定义不是动态的)是实现优化收益的绝佳目标。
通过利用Web浏览器的缓存功能,您可以在初始下载后完全消除给定文件的网络命中。
ManifestStaticFilesStorage在静态文件的文件名后附加一个与内容相关的标记,以使浏览器可以安全地长期缓存它们,而不会丢失将来的更改-文件更改时,标记也将更改,因此浏览器将自动重新加载资产。
几个第三方Django工具和软件包提供了“最小化” HTML,CSS和JavaScript的功能。它们删除了不必要的空格,换行符和注释,并缩短了变量名,从而减小了站点发布的文档的大小。
注意:
启用通常可以大大提高性能,因为它避免了每次需要渲染每个模板时就对每个模板进行编译。cached template loader
有时值得检查您所使用软件的不同版本和性能更好的版本。
这些技术面向希望突破已经充分优化的Django站点的性能极限的更高级的用户。
但是,它们并不是解决性能问题的灵丹妙药,它们不可能为尚未以正确方式做更多基本工作的网站带来比边缘收益更好的收益。
注意
值得重复一遍:寻找已经使用的软件的替代品永远不是解决性能问题的第一个答案。当达到此优化级别时,您需要一个正式的基准测试解决方案。
新发行的维护良好的软件效率较低的情况相当少见,但是维护人员无法预见所有可能的用例-因此,尽管意识到较新版本的性能可能会更好,但不要以为它们总是将。
Django本身就是这样。后续版本在整个系统上提供了许多改进,但是您仍然应该检查应用程序的实际性能,因为在某些情况下,您可能会发现更改意味着性能较差而不是更好。
较新版本的Python以及Python包也通常会表现更好-但要衡量而不是假设。
注意
除非您在特定版本中遇到不寻常的性能问题,否则通常会在新版本中找到更好的功能,可靠性和安全性,并且这些好处远比您可能会赢得或失去的任何性能都重要。
在几乎所有情况下,Django的内置模板语言都足够了。但是,如果Django项目中的瓶颈似乎在模板系统中,而您又花了其他机会来解决此问题,那么第三方替代方法可能是答案。
Jinja2可以提高性能,特别是在速度方面。
替代模板系统在共享Django模板语言的程度上有所不同。
注意:如果您在模板中遇到性能问题,则要做的第一件事就是确切地了解原因。使用备用模板系统可能会证明更快,但是在不造成麻烦的情况下也可以获得相同的收益-例如,可以在视图中更有效地完成模板中的昂贵处理和逻辑。
可能值得检查您所使用的Python软件是否已以不同的实现提供,该实现可以更快地执行相同的代码。
但是:在编写良好的Django站点中,大多数性能问题不是在Python执行级别上,而是在效率低下的数据库查询,缓存和模板方面。如果您依赖编写不佳的Python代码,则无法通过更快地执行来解决性能问题。
使用替代实现可能会引入兼容性,部署,可移植性或维护问题。不用说,在采用非标准实现之前,您应确保它为您的应用程序提供了足够的性能提升,从而胜过了潜在的风险。
考虑到这些警告,您应该意识到:
PyPy是Python本身的Python实现(“标准” Python实现在C中)。PyPy通常可用于重量级应用程序,因此可显着提高性能。
PyPy项目的主要目标是与现有的Python API和库兼容。Django是兼容的,但您需要检查您依赖的其他库的兼容性。
一些Python库也用C实现,并且速度可能更快。他们旨在提供相同的API。请注意,兼容性问题和行为差异并不是未知的(并且并不总是立即可见)。
详情参考: https://docs.djangoproject.com/en/3.0/
Python的WEB框架有Django、Tornado、Flask 等多种,Django是重量级选手中最有代表性的一位,它的优势为:大而全,框架本身集成了ORM、模型绑定、模板引擎、缓存、Session等诸多功能。许多成功的网站和APP都基于Django。
Django是一个开放源代码的Web应用框架,由Python写成。
Django遵守BSD版权,初次发布于2005年7月, 并于2008年9月发布了第一个正式版本1.0 。
Django采用了MVT的软件设计模式,即模型Model,视图View和模板Template。
本教程适合有Python基础的开发者学习。
学习本教程前你需要了解一些基础的Web知识及Python基础教程。
Django 版本与 Python 环境的对应表:
Django 版本 | Python 版本 |
1.5 | 2.6.5, 2.7, 3.2, 3.3. |
1.6 | 2.6, 2.7, 3.2, 3.3 |
1.7 | 2.7, 3.2, 3.3, 3.4 (2.6 不支持了) |
1.8 LTS | 2.7, 3.2, 3.3, 3.4, 3.5 (长期支持版本 LTS) |
1.9 | 2.7, 3.4, 3.5 (3.3 不支持了) |
1.10 | 2.7, 3.4, 3.5 |
1.11 LTS | 2.7, 3.4, 3.5, 3.6 (最后一个支持 Python 2.7 的版本 ) |
2.0 | 3.4, 3.5, 3.6 (注意,不再支持 Python 2) |
2.1 | 3.5, 3.6, 3.7 |
2.2 LTS | 3.5, 3.6, 3.7 |
3.0 | 3.6, 3.7, 3.8 |
按照上述对照表来选择Django和Python版本,以免造成不兼容等问题。
使用最新版本的问题就是,可能要用到的一些第三方插件没有及时更新,无法正常使用这些三方包。
Django 简介
Django 是用Python开发的一个免费开源的Web框架,可以用于快速搭建高性能,优雅的网站!采用了MVC的框架模式,即模型M,视图V和控制器C,也可以称为MVT模式,模型M,视图V,模板T。
它最初是被开发来用于管理劳伦斯出版集团旗下的一些以新闻内容为主的站点的, 并于2005年7月在BSD许可证下公布.
这套框架是以比利时的吉普赛爵士吉他手Django Reinhardt来命名的.
Django 的主要目标是使得开发复杂的、数据库驱动的网站变得简单。Django 注重组件的重用性和“可插拔性”,敏捷开发和 DRY 法则(Don’t Repeat Yourself)。在 Django 中 Python 被普遍使用,甚至包括配置文件和数据模型。
Django 于 2008 年 6 月 17 日正式成立基金会。
Django 框架的核心包括:
核心框架中还包括:
Django 包含了很多应用在它的 contrib 包中,这些包括:
在安装 Django 前,系统需要已经安装了Python的开发环境。
如果你还未安装Python环境需要先下载Python安装包。
1、Python 下载地址:https://www.python.org/downloads/
2、Django 下载地址:https://www.djangoproject.com/download/
安装Python你只需要下载python-x.x.x.msi文件,然后一直点击"Next"按钮即可。
为了检查我们的python是否安装成功,可以在命令窗口中输入python进行查询,如显示下图一的信息则表示成功了。
安装完成后你需要设置Python环境变量。 右击计算机->属性->高级->环境变量->修改系统变量path,添加Python安装地址,本文实例使用的是C:Python33,你需要根据你实际情况来安装。
安装说明会有所不同,具体取决于您是安装特定于发行版的软件包,下载最新的官方发行版还是获取最新的开发版本。
这是安装Django的推荐方法。
Linux/MAC: python -m pip install Django
Windows: py -m pip
查看特定于发行版的说明,以查看您的平台/发行版是否提供了官方的Django软件包/安装程序。发行版提供的软件包通常将允许自动安装依赖项和受支持的升级路径;但是,这些软件包很少包含最新版本的Django。
跟踪Django开发
如果您决定使用Django的最新开发版本,则需要密切注意开发时间表,并希望留意即将发布的发行说明。这将帮助您掌握可能要使用的所有新功能,以及在更新Django副本时需要对代码进行的任何更改。(对于稳定版本,任何必要的更改都记录在发行说明中。)
如果您希望能够偶尔通过最新的错误修复和改进来更新Django代码,请按照以下说明进行操作:
Linux/MAC: $ git clone https://github.com/django/django.git
Windows:... > git clone https://github.com/django/django.git
这将django
在当前目录中创建一个目录。
3. 确保Python解释器可以加载Django的代码。最方便的方法是使用虚拟环境和pip。该 贡献教程走过了如何创建一个虚拟的环境。
4. 设置并激活虚拟环境后,运行以下命令:
Linux/MAC:$ python -m pip install -e django/
Windows: ... > py -m pip install -e django
这将使Django的代码可导入,并使 django-admin
Utility命令可用。换句话说,您已经准备就绪!
当您想要更新Django源代码的副本时,请从目录中运行命令 。执行此操作时,Git将下载所有更改。git pulldjango
详情参考:https://docs.djangoproject.com/en/3.0/topics/install/
扫描下方二维码或打开微信搜一搜“51coolma编程狮”关注公众号回复关键词【Python123】或者【Python资料包】免费领取 Python 学习资料,包含软件安装包,电子书、思维导图等
本章我们将介绍如何使用 Django 来创建项目。
使用 django-admin.py 来创建名为***的项目:
django-admin startproject xxx
创建完成后我们可以查看下项目的目录结构:
[root@solar ~]# cd HelloWorld/[root@solar HelloWorld]# tree. manage.py 管理器|--*** | |-- __init__.py 包| |-- settings.py 设置文件| |-- urls.py 路由| `-- wsgi.py 部署
目录说明:
创建一个app模块会自动生成app文件夹,该文件夹包括几个文件:
python manage.py startapp app
各个目录的说明:
在目录中找到***包里面的setting.py,在INSTALLED_APPS当中注册APP模块:
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'app',
在包下输入命令,启动项目:
python manage.py runserver
在浏览器输入你服务器的ip及端口号,如果正常启动,会得到如下界面,则表示项目创建完成:
在Django框架中,模板是可以帮助开发者快速生成呈现给用户页面的工具。用于编写html代码,还可以嵌入模板代码转换更方便的完成页面开发,再通过在视图中渲染模板,将生成模板的设计实现了业务逻辑视图与显示内容模板的分离,一个视图可以使用任意一个模板,一个模板可以供多个视图使用。
注意:当前显示的页面=模板+数据
模板分为两部分:
一般是在视图函数当中通过模板语言去动态产生html页,然后将页面上的内容返回给客户端,进行显示。
模板文件渲染产生的html页面内容渲染,使用传递的数据替换相应的变量,产生一个替换后的表中html内容
from django.shortcuts import renderfrom django.template import loader,RequestContextfrom django.http import HttpResponse# Create your views here.def my_render(request,template_path,context={}): # 1.加载模板文件,获取一个模板对象 temp = loader.get_template(template_path) # 2.定义模板上下文,给模板传递数据 context = RequestContext(request, context) # 3.模板渲染,产生一个替换后的html内容 res_html = temp.render(context) # 4.返回应答 return HttpResponse(res_html)# /indexdef index(request): # return my_render(request,'booktest/index.html') 这是自己封装的render # 其实Django已经封装好了,可以直接使用 return render(request,'booktest/index.html')
模板语言
>模板变量名是由数字,字母,下划线和点组成
>注意:不能以下划线开头
3.模板标签
{% 代码段 %}#for循环:#遍历列表:{% for i in 列表 %}#列表不为空时执行{% empty %}#列表为空时执行{% endfor %}#若加上关键字reversed则倒序遍历:
{% for x in 列表 reversed %}{% endfor %}#在for循环中可以通过{{ forloop.counter }}得到for循环遍历到几次#判断语句:{% if %}{% elif %}{% else %}{% endif %}
4.关系比较操作符
> <> = <= ==!=
注意:在使用关系比较操作符的时候,比较符两边必须有空格
5.逻辑运算
不和
过滤器
>add:将值的值增加2。使用形式为:{{value | add:“ 2”}}
> cut:从给定值中删除所有arg的值。使用形式为:{{value | cut:arg}}
>date:格式化时间格式。使用形式为:{{value| date:“ Ymd H:M:S”}}
>default:如果value是False,那么输出给定的默认值。使用形式:{{value | default:“ nothing”}}。例如,如果值是“”,那么输出将是nothing
> first:返回列表/字符串中的第一个元素。使用形式:{{value | first}}
> length:返回值的长度。使用形式:{{value | length}}
自定义过滤器的步骤:
from django import template#导入模块register = template.Library() #标准语句都不能改#写函数装饰器@register.filterdef add_xx(value, arg): # 最多有两个 return '{}-{}'.format(value, arg)#返回两个值的拼接#在html使用方式{% load my_tags %}#引用模块{{ 'alex'|add_xx:'dsb' }}#通过函数名使用@register.filter(name = xxx)#可以直接通过name等于的xxx取引用def add_xx(value, arg): # 最多有两个 return '{}-{}'.format(value, arg)#返回两个值的拼接#在html使用方式{% load my_tags %}#引用模块{{'alex'|xxx:'dsb'}}#通过赋值的name引用
模板里编写{%block <demo>%}开头,{%endblock%}结尾处,代表可以被继承
例如如下新建的demo.html:
1.父模板
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>Title</title> <style> h1{ color: blue; } </style></head><body>{% block demo %}<h1>模板1</h1>{% endblock %}{% block demo1 %}<h1>模板2</h1>{% endblock %}{% block demo2 %}<h1>模板3</h1>{% endblock %}{% block demo3 %}<h1 style="color: red">模板4</h1>{% endblock %}{% block demo4 %}<h1>模板5</h1>{% endblock %}</body></html>
2.子模板
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>Title</title></head><body>{% extends 'demo.html' %} #继承模板{% block demo %} #对引入的模板块进行从写 <h1>这里是重写</h1>{% endblock %}</body></html>
模块约会完成,可以看到效果如下所示:
模型是有关数据的唯一确定的信息源。它包含要存储数据的基本字段和行为。通常,每个模型都映射到单个数据库表。
此示例模型定义了一个Person
,其中包含first_name
和 last_name
:
from django.db import modelsclass Person(models.Model): first_name = models.CharField(max_length=30) last_name = models.CharField(max_length=30)
first_name并且last_name是场模型。每个字段都指定为类属性,并且每个属性都映射到数据库列。
上面的Person模型将创建一个数据库表,如下所示:
CREATE TABLE myapp_person ( "id" serial NOT NULL PRIMARY KEY, "first_name" varchar(30) NOT NULL, "last_name" varchar(30) NOT NULL);
详情参考官网: https://docs.djangoproject.com/en/3.0/topics/db/models/
Django根据属性的类型确定以下信息:
django会为表创建自动增长的主键列,每个模型只能有一个主键列,如果使用选项设置某属性为主键列后django不会再创建自动增长的主键列。
默认创建的主键列属性为id,可以使用pk代替,pk全拼为primary key。
pk是主键的别名,若主键名为id2,那么pk是id2的别名。
属性命名限制:
具体语法如下:
属性=models.字段类型(选项)
字段类 | 默认小组件 | 说明 |
AutoField | N/A | 根据 ID 自动递增的 IntegerField |
BigIntegerField | NumberInput | 64 位整数,与 IntegerField 很像,但取值范围是 -9223372036854775808 到 9223372036854775807 。 |
BinaryField | N/A | 存储原始二进制数据的字段。只支持 bytes 类型。注意,这个字段的功能有限。 |
BooleanField | CheckboxInput | 真假值字段。如果想接受 null 值,使用 NullBooleanField 。 |
CharField | TextInput | 字符串字段,针对长度较小的字符串。大量文本应该使用 TextField 。有个额外的必须参数:max_length ,即字段的最大长度(字符个数)。 |
DateField | DateInput | 日期,在 Python 中使用 datetime.date 实例表示。有两个额外的可选参数: auto_now ,每次保存对象时自动设为当前日期 auto_now_add ,创建对象时自动设为当前日期。 |
DateTimeField | DateTimeInput | 日期和时间,在 Python 中使用 datetime.datetime 实例表示。与 DateField 具有相同的额外参数。 |
DecimalField | TextInput | 固定精度的小数,在 Python 中使用 Decimal 实例表示。有两个必须的参数: max_digits 和 decimal_places 。 |
DurationField | TextInput | 存储时间跨度,在 Python 中使用 timedelta 表示。 |
EmailField | TextInput | 一种 CharField ,使用 EmailValidator 验证输入。max_length 的默认值为 254 。 |
FileField | ClearableFileInput | 文件上传字段。详情见下面。 |
FilePathField | Select | 一种 CharField ,限定只能在文件系统中的特定目录里选择文件。 |
FloatField | NumberInput | 浮点数,在 Python 中使用 float 实例表示。注意, field.localize 的值为 False 时,默认的小组件是 TextInput 。 |
ImageField | ClearableFileInput | 所有属性和方法都继承自 FileField ,此外验证上传的对象是不是有效的图像。增加了 height 和 width 两个属性。需要 Pillow 库支持。 |
Django提供了定义了几种最常见的数据库关联关系的方法:多对一,多对多,一对一。
多对一关系,需要两个位置参数,一个是关联的模型,另一个是 on_delete
选项,外键要定义在多的一方,如一个汽车厂生产多种汽车,一辆汽车只有一个生产厂家
from django.db import models
class Manufacturer(models.Model):
# ...
pass
class Car(models.Model):
manufacturer = models.ForeignKey(Manufacturer, on_delete=models.CASCADE)
例如:这样一个应用,它记录音乐家所属的音乐小组。 我们可以用一个ManyToManyField 表示小组和成员之间的多对多关系。 但是,有时你可能想知道更多成员关系的细节,比如成员是何时加入小组的。
对于这些情况,Django 允许你指定一个中介模型来定义多对多关系。 你可以将其他字段放在中介模型里面。 源模型的ManyToManyField 字段将使用through 参数指向中介模型。 对于上面的音乐小组的例子,代码如下:
from django.db import modelsclass Person(models.Model): name = models.CharField(max_length=128) def __str__(self): # __unicode__ on Python 2 return self.nameclass Group(models.Model): name = models.CharField(max_length=128) members = models.ManyToManyField(Person, through='Membership') def __str__(self): # __unicode__ on Python 2 return self.nameclass 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)
您需要在设置中间模型的时候,显式地为多对多关系中涉及的中间模型指定外键。这种显式声明定义了这两个模型之间是如何关联的。
在中间模型当中有一些限制条件:
现在你已经通过中间模型完成你的ManyToManyField(示例中的Membership),可以开始创建一些多对多关系了。你通过实例化中间模型来创建关系:
>>> 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)})
你可能更潜在直接创造中间模型。
如果自定义中间模型没有强制对的唯一性,调用方法会删除所有中间模型的实例:(model1, model2)remove()
>>> Membership.objects.create(person=ringo, group=beatles,... date_joined=date(1968, 9, 4),... invite_reason="You've been gone for a month and we miss you.")>>> beatles.members.all()<QuerySet [<Person: Ringo Starr>, <Person: Paul McCartney>, <Person: Ringo Starr>]>>>> # This deletes both of the intermediate model instances for Ringo Starr>>> beatles.members.remove(ringo)>>> beatles.members.all()<QuerySet [<Person: Paul McCartney>]>
方法clear()
用于实例的所有多对多关系:
>>> # Beatles have broken up>>> beatles.members.clear()>>> # Note that this deletes the intermediate model instances>>> Membership.objects.all()<QuerySet []>
一旦你建立了自定义多对多关联关系,就可以执行查询操作。和一般的多对多关联关系一样,你可以使用多对多关联模型的属性来查询:
# 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]>
如果你想访问一个关系的信息时你可以直接查询Membership模型:
>>> ringos_membership = Membership.objects.get(group=beatles, person=ringo)>>> ringos_membership.date_joineddatetime.date(1962, 8, 16)>>> ringos_membership.invite_reason'Needed a new drummer.'
另一种访问同样信息的方法是通过Person对象来查询多对多递归关系:
>>> ringos_membership = ringo.membership_set.get(group=beatles)>>> ringos_membership.date_joineddatetime.date(1962, 8, 16)>>> ringos_membership.invite_reason'Needed a new drummer.'
使用OneToOneField来定义一对一关系。就像使用其他类型的Field一样:在模型属性中包含它。
当一个对象以某种方式“继承”另一个对象时,这那个对象的主键非常有用。
OneToOneField需要一个位置参数:与模型相关的类。
例如,当你要建立一个有关“位置”信息的数据库时,你可能会包含通常的地址,电话等分支。然后,如果你想接着建立一个关于关于餐厅的数据库,除了将位置数据库当中的一部分复制到Restaurant模型,你也可以将一个指向Place OneToOneField放到Restaurant当中(因为餐厅“是一个”地点);事实上,在处理这样的情况时最好使用模型继承,它隐含的包括了一个一对一关系。
和 ForeignKey一样,可以创建自关联关系也可以创建与尚未定义的模型的关系。
OneToOneField初步还接受一个可选的parent_link参数。
OneToOneField类通常自动的成为模型的主键,这条规则现在不再使用了(而你可以手动指定primary_key参数)。因此,现在可以在其中的模型当中指定多个OneToOneField分段。
详情参考官网: https://www.khan.pub/django3.0/index.html
除非您打算建立只发布内容的网站和应用程序,并且不接受访问者的输入,否则您将需要理解和使用表格。
Django提供了一系列工具和库,可帮助您构建表单以接受来自站点访问者的输入,然后处理并响应输入。
在HTML中,表单是内部元素的集合<form>...</form>,允许访问者执行诸如输入文本,选择选项,操作对象或控件等操作,然后将该信息发送回服务器。
其中一些表单界面元素(文本输入或复选框)内置于HTML本身。其他则要复杂得多。弹出日期选择器或允许您移动滑块或操纵控件的界面通常将使用JavaScript和CSS以及HTML表单<input>元素来实现这些效果。
<input>表单及其元素还必须指定两件事:
Django表单处理流程
Django 的表单处理:视图获取请求,执行所需的任何操作,包括从模型中读取数据,然后生成并返回HTML页面(从模板中),我们传递一个包含要显示的数据的上下文。使事情变得更复杂的是,服务器还需要能够处理用户提供的数据,并在出现任何错误时,重新显示页面。
GET方法是通过键值对的方式显示从用户那边获取的数据,然后通过“&”将其组合形成一个整体的字符串,最后加上“?”,将组合后的字符串拼接到URL内,生成一个url地址。它既不适用于大量数据,也不适合于二进制数据(例如图像)。使用GET
管理表单请求的Web应用程序存在安全风险:攻击者很容易模仿表单的请求来访问系统的敏感部分。GET仅应用于不影响系统状态的请求。诸如Web搜索表单类,它可以轻松对请求到的URL进行共享,提交等操作。
POST 方法
处理表格是一项复杂的业务。考虑一下Django的管理员,其中可能需要准备好几种不同类型的大量数据,以表格形式显示,呈现为HTML,使用便利的界面进行编辑,返回到服务器,进行验证和清理,然后保存或传递进行进一步处理。
Django的表单功能可以简化和自动化大部分工作,并且比大多数程序员在编写自己的代码中所能做到的更加安全。
Django处理涉及表单的工作的三个不同部分:
这是有可能到手动做这一切写代码,但Django的可以照顾这一切为您服务。
我们已经简要描述了HTML表单,但是HTML <form>只是所需机制的一部分。
在Web应用程序的上下文中,“表单”可能是指该HTML <form>或Form生成它的Django ,或者是提交时返回的结构化数据,或者是这些部分的端到端工作集合。
该组件系统的核心是Django的Form类。类与Django模型描述对象的逻辑结构,其行为以及向我们表示其部分的方式几乎相同,一个 Form类描述一种形式并确定其工作方式和外观。
就像模型类的字段映射到数据库字段一样,表单类的字段映射到HTML表单<input>元素。(A 通过;ModelForm 将模型类的字段映射到HTML表单<input>元素 Form,这是Django管理员所基于的。)
表单的字段本身就是类。他们管理表单数据并在提交表单时执行验证。一个DateField和 FileField手柄非常不同类型的数据,并有做不同的事情吧。
表单字段在浏览器中以HTML“窗口小部件”的形式向用户表示-一种用户界面机制。每个字段类型都有一个适当的默认 Widget类,但是可以根据需要覆盖它们。
在Django中渲染对象时,通常:
在模板中呈现表单与呈现任何其他类型的对象几乎涉及相同的工作,但是存在一些关键区别。
对于不包含数据的模型实例,在模板中执行任何操作几乎是没有用的。另一方面,呈现未填充的表单非常有意义-当我们希望用户填充它时,这就是我们要做的。
因此,当我们在视图中处理模型实例时,通常会从数据库中检索它。当我们处理表单时,通常在视图中实例化它。
实例化表单时,我们可以选择将其保留为空或预先填充,例如:
这些情况中的最后一个是最有趣的,因为它使用户不仅可以阅读网站,而且还可以向其发送信息。
假设您想在您的网站上创建一个简单的表单,以获得用户名。您在模板中需要这样的内容:
<form action="/your-name/" method="post"> <label for="your_name">Your name: </label> <input id="your_name" type="text" name="your_name" value="{{ current_name }}"> <input type="submit" value="OK"></form>
这告诉浏览器/your-name/使用POST方法将表单数据返回到URL 。它将显示一个文本字段,标记为“您的姓名:”,以及一个标记为“确定”的按钮。如果模板上下文包含一个current_name 变量,它将用于预填充该your_name字段。
您将需要一个视图来呈现包含HTML表单的模板,并且可以提供current_name适当的字段。
提交表单后,POST发送到服务器的请求将包含表单数据。
现在,您还将需要一个与该/your-name/URL 对应的视图,该视图将在请求中找到适当的键/值对,然后对其进行处理。
这是一个非常简单的形式。实际上,一个表单可能包含数十个或数百个字段,其中许多字段可能需要预先填充,并且我们可能希望用户在结束操作之前先完成几次编辑-提交循环。
甚至在提交表单之前,我们可能需要在浏览器中进行一些验证;我们可能想使用更复杂的字段,使用户可以执行诸如从日历中选择日期之类的操作。
在这一点上,让Django为我们完成大部分工作要容易得多。
我们已经知道我们想要HTML表单的外观了。我们在Django中的起点是:
的forms.pyfrom django import formsclass NameForm(forms.Form): your_name = forms.CharField(label='Your name', max_length=100)
这定义了一个Form具有单个字段(your_name)的类。我们在该字段上应用了一个人类友好的标签,该标签将在<label>呈现时显示在标签上(尽管在这种情况下,label 我们指定的标签实际上与省略该标签时会自动生成的标签 相同)。
字段的最大允许长度由定义 max_length。这有两件事。它放在 maxlength="100"HTML上<input>(因此浏览器应首先防止用户输入超过该数量的字符)。这也意味着,当Django从浏览器接收回表单时,它将验证数据的长度。
一个Form实例有一个is_valid()方法,它运行于所有的字段验证程序。调用此方法时,如果所有字段都包含有效数据,它将:
首次渲染时,整个表单将如下所示:
<label for="your_name">Your name: </label><input id="your_name" type="text" name="your_name" maxlength="100" required>
请注意,它不包含<form>标签或提交按钮。我们必须在模板中提供这些信息。
发送回Django网站的表单数据由视图处理,通常与发布表单的视图相同。这使我们可以重用某些相同的逻辑。
要处理表单,我们需要在视图中将其实例化的URL实例化为:
的views.pyfrom django.http import HttpResponseRedirectfrom django.shortcuts import renderfrom .forms import NameFormdef get_name(request): # if this is a POST request we need to process the form data if request.method == 'POST': # create a form instance and populate it with data from the request: form = NameForm(request.POST) # check whether it's valid: if form.is_valid(): # process the data in form.cleaned_data as required # ... # redirect to a new URL: return HttpResponseRedirect('/thanks/') # if a GET (or any other method) we'll create a blank form else: form = NameForm() return render(request, 'name.html', {'form': form})
如果我们通过GET请求到达此视图,它将创建一个空表单实例并将其放置在要呈现的模板上下文中。这是我们第一次访问URL时可以预期的情况。
如果表单是使用POST请求提交的,则视图将再次创建表单实例,并使用请求中的数据填充该表单实例:这称为“将数据绑定到表单”(现在是绑定表单)。form = NameForm(request.POST)
我们称为表单的is_valid()方法;如果不是True,我们返回带有表单的模板。这次,表单不再是空的(未绑定),因此将使用先前提交的数据填充HTML表单,并可以在其中根据需要对其进行编辑和更正。
如果is_valid()为True,我们现在将能够在其cleaned_data属性中找到所有经过验证的表单数据。我们可以使用此数据来更新数据库或进行其他处理,然后再将HTTP重定向发送到浏览器,告诉浏览器下一步该怎么做。
我们不需要在name.html模板中做很多事情:
<form action="/your-name/" method="post"> {% csrf_token %} {{ form }} <input type="submit" value="Submit"></form>
表单的所有字段及其属性将通过Django的模板语言从中解压缩为HTML标记。{{ form }}
表格和跨站点请求伪造保护
Django随附了易于使用的跨站点请求伪造保护。在POST启用CSRF保护的情况下提交表单时,必须csrf_token像前面的示例一样使用template标记。但是,由于CSRF保护并不直接与模板中的表单相关联,因此在本文档的以下示例中省略了此标签。
HTML5输入类型和浏览器验证
如果表单包括URLField,一个 EmailField或任何整数字段类型,Django会使用的url,email和numberHTML5输入类型。默认情况下,浏览器可以在这些字段上应用自己的验证,这可能比Django的验证更严格。如果您想禁用此行为,请novalidate在form标签上设置属性,或在字段上指定其他小部件,例如TextInput。
现在,我们有了一个工作的Web表单,该表单由Django描述Form,由视图处理并呈现为HTML <form>。
这就是您入门所需的全部内容,但是表单框架为您提供了更多便利。一旦了解了上述过程的基础,就应该准备了解表单系统的其他功能,并准备进一步了解基础机械。
所有表单类均作为django.forms.Form 或的子类创建django.forms.ModelForm。您可以将其ModelForm视为的子类Form。Form并ModelForm实际上从(私有)BaseForm类继承通用功能,但是这种实现细节很少很重要。
模型和形式
实际上,如果您的表单将用于直接添加或编辑Django模型,那么ModelForm可以节省大量的时间,精力和代码,因为它可以构建表单以及适当的字段及其属性,来自一Model堂课。
绑定形式和未绑定形式之间的区别很重要:
表单的is_bound属性将告诉您表单是否绑定了数据。
考虑一个比上面的最小示例更有用的形式,我们可以使用该形式在个人网站上实现“与我联系”功能:
的forms.pyfrom django import formsclass ContactForm(forms.Form): subject = forms.CharField(max_length=100) message = forms.CharField(widget=forms.Textarea) sender = forms.EmailField() cc_myself = forms.BooleanField(required=False)
我们之前的形式使用的单场,your_name,一CharField。在这种情况下,我们的表单有四个字段:subject,message,sender和 cc_myself。CharField,EmailField并且 BooleanField只有三个可用的字段类型; 完整列表可以在“ 表单”字段中找到。
每个表单字段都有一个对应的Widget类,该类又对应于HTML表单小部件,例如。<input type="text">
在大多数情况下,该字段将具有明智的默认小部件。例如,默认情况下,a CharField将具有在HTML TextInput中生成a的小部件。如果需要 ,可以在定义表单字段时指定适当的小部件,就像我们对字段所做的那样。<input type="text"><textarea>message
无论通过表单提交的数据是什么,一旦通过调用成功验证is_valid()(并is_valid()返回True),经过验证的表单数据都将位于form.cleaned_data字典中。这些数据将为您很好地转换为Python类型。
注意
此时,您仍然可以直接访问未经验证的数据request.POST,但是经过验证的数据更好。
在上面的联系表单示例中,cc_myself将为布尔值。同样,诸如IntegerField和FloatField将值分别转换为Python int和的字段float。
以下是在处理此表单的视图中如何处理表单数据的方法:
的views.pyfrom django.core.mail import send_mailif form.is_valid(): subject = form.cleaned_data['subject'] message = form.cleaned_data['message'] sender = form.cleaned_data['sender'] cc_myself = form.cleaned_data['cc_myself'] recipients = ['info@example.com'] if cc_myself: recipients.append(sender) send_mail(subject, message, sender, recipients) return HttpResponseRedirect('/thanks/')
小费
有关从Django发送电子邮件的更多信息,请参见发送电子邮件。
一些字段类型需要一些额外的处理。例如,使用表单上传的文件需要进行不同的处理(可以从而request.FILES不是从中检索它们 request.POST)。有关如何处理表单上载文件的详细信息,请参阅将上载的文件绑定到表单。
将表单放入模板所需要做的就是将表单实例放入模板上下文中。因此,如果您的表单是form在上下文中调用的,则将适当地呈现其和元素。{{ form }}<label><input>
附加表格模板家具
不要忘了,一个形式的输出并没有包括周围的 <form>标签,或窗体的submit控制。您必须自己提供这些。
<label>/ <input>对还有其他输出选项:
请注意,您必须自己提供周围环境<table>或<ul> 元素。
这是我们的实例的输出:{{ form.as_p }}ContactForm
<p><label for="id_subject">Subject:</label> <input id="id_subject" type="text" name="subject" maxlength="100" required></p><p><label for="id_message">Message:</label> <textarea name="message" id="id_message" required></textarea></p><p><label for="id_sender">Sender:</label> <input type="email" name="sender" id="id_sender" required></p><p><label for="id_cc_myself">Cc myself:</label> <input type="checkbox" name="cc_myself" id="id_cc_myself"></p>
请注意,每个表单字段的ID属性设置为id_<field-name>,由随附的标签标记引用。这对于确保辅助技术(例如屏幕阅读器软件)可访问表单很重要。您还可以自定义标签和ID的生成方式。
有关更多信息,请参见将表单输出为HTML。
我们不必让Django解压缩表单字段;我们可以根据需要手动进行操作(例如,允许我们对字段进行重新排序)。每个字段都可以使用用作表单的属性,并且在Django模板中将适当地呈现。例如:{{ form.name_of_field }}
{{ form.non_field_errors }}<div class="fieldWrapper"> {{ form.subject.errors }} <label for="{{ form.subject.id_for_label }}">Email subject:</label> {{ form.subject }}</div><div class="fieldWrapper"> {{ form.message.errors }} <label for="{{ form.message.id_for_label }}">Your message:</label> {{ form.message }}</div><div class="fieldWrapper"> {{ form.sender.errors }} <label for="{{ form.sender.id_for_label }}">Your email address:</label> {{ form.sender }}</div><div class="fieldWrapper"> {{ form.cc_myself.errors }} <label for="{{ form.cc_myself.id_for_label }}">CC yourself?</label> {{ form.cc_myself }}</div>
<label>也可以使用生成完整的元素 label_tag()。例如:
<div class="fieldWrapper"> {{ form.subject.errors }} {{ form.subject.label_tag }} {{ form.subject }}</div>
当然,这种灵活性的代价是更多的工作。到目前为止,我们不必担心如何显示表单错误,因为这已经为我们解决了。在此示例中,我们必须确保处理每个字段的所有错误以及整个表单的所有错误。请注意在表单和模板查找的顶部,以查找每个字段上的错误。{{ form.non_field_errors }}
使用显示格式错误列表,并显示为无序列表。可能看起来像:{{ form.name_of_field.errors }}
<ul class="errorlist"> <li>Sender is required.</li></ul>
该列表的CSS类errorlist允许您设置外观样式。如果您希望进一步自定义错误的显示,可以通过遍历它们来实现:
{% if form.subject.errors %} <ol> {% for error in form.subject.errors %} <li><strong>{{ error|escape }}</strong></li> {% endfor %} </ol>{% endif %}
非字段错误(和/或使用诸如的辅助工具时在表单顶部显示的隐藏字段错误form.as_p())将通过附加的类别呈现,nonfield以帮助将其与特定于字段的错误区分开。例如,如下所示:{{ form.non_field_errors }}
<ul class="errorlist nonfield"> <li>Generic validation error</li></ul>
有关错误,样式以及如何在模板中使用表单属性的更多信息,请参见Forms API。
如果您对每个表单字段使用相同的HTML,则可以通过使用 循环依次遍历每个字段来减少重复代码:{% for %}
{% for field in form %} <div class="fieldWrapper"> {{ field.errors }} {{ field.label_tag }} {{ field }} {% if field.help_text %} <p class="help">{{ field.help_text|safe }}</p> {% endif %} </div>{% endfor %}
有用的属性包括:{{ field }}
{{ field.label }}
Email address
{{ field.label_tag }}
字段的标签包装在适当的HTML <label>
标记中。这包括表格的label_suffix
。例如,默认label_suffix
值为冒号:
<label for="id_email">Email address:</label>
{{ field.id_for_label }}
id_email
在上面的示例中)。如果您是手动构建标签,则可能要使用它代替label_tag
。例如,如果您有一些内联JavaScript并希望避免对字段ID进行硬编码,它也很有用。{{ field.value }}
someone@example.com
。{{ field.html_name }}
{{ field.help_text }}
{{ field.errors }}
<ul class="errorlist">
{% for error in field.errors %}
{{ field.is_hidden }}
True
表单字段是否为隐藏字段,False
否则为隐藏字段 。它作为模板变量不是特别有用,但在条件测试中可能有用,例如:{% if field.is_hidden %} {# Do something special #}{% endif %}
{{ field.field }}
Field
的表单类中的实例BoundField
。您可以使用它来访问 Field
属性,例如 。{{ char_field.field.max_length }}
也可以看看
有关属性和方法的完整列表,请参见 BoundField。
如果您要手动在模板中布置表单,而不是依赖Django的默认表单布局,则可能需要将 字段与非隐藏字段区别对待。例如,由于隐藏字段不显示任何内容,因此将错误消息放在该字段旁边可能会给您的用户造成混乱-因此,应对这些字段的错误进行不同的处理。<input type="hidden">
Django在表单上提供了两种方法,可让您独立遍历隐藏字段和可见字段:hidden_fields()和 visible_fields()。这是对使用这两种方法的先前示例的修改:
{# Include the hidden fields #}{% for hidden in form.hidden_fields %}{{ hidden }}{% endfor %}{# Include the visible fields #}{% for field in form.visible_fields %} <div class="fieldWrapper"> {{ field.errors }} {{ field.label_tag }} {{ field }} </div>{% endfor %}
本示例不处理隐藏字段中的任何错误。通常,隐藏字段中的错误是表单被篡改的标志,因为正常的表单交互不会改变它们。但是,您也可以轻松地为这些表单错误插入一些错误显示。
如果您的站点在多个位置对表单使用相同的呈现逻辑,则可以通过将表单的循环保存在独立模板中并使用include标签在其他模板中重用它来减少重复:
# In your form template:{% include "form_snippet.html" %}# In form_snippet.html:{% for field in form %} <div class="fieldWrapper"> {{ field.errors }} {{ field.label_tag }} {{ field }} </div>{% endfor %}
如果传递给模板的表单对象在上下文中具有不同的名称,则可以使用 标记的with参数对其进行别名include:
{% include "form_snippet.html" with form=comment_form %}
如果您发现自己经常这样做,则可以考虑创建一个自定义 包含标签。
详情参考: https://docs.djangoproject.com/en/3.0/topics/forms/#working-with-form-templates
干净,优雅的URL方案是高质量Web应用程序中的重要细节。Django允许您根据需要设计URL,而无框架限制。
万维网创建者蒂姆·伯纳斯-李(Tim Berners-Lee)的文章“ Cool URIs not not change”中有关为什么URL应该干净和可用的出色论据,请参见。
要设计应用程序的URL,您可以创建一个非正式地称为URLconf(URL配置)的Python模块 。该模块是纯Python代码,并且是URL路径表达式到Python函数(您的视图)之间的映射。
该映射可以根据需要短或长。它可以引用其他映射。而且,由于它是纯Python代码,因此可以动态构建。
Django还提供了一种根据活动语言翻译URL的方法。有关更多信息,请参见国际化文档。
当用户从您的Django支持的网站请求页面时,系统将使用以下算法来确定要执行的Python代码:
这是一个示例URLconf:
from django.urls import path
from . import views
urlpatterns = [
path('articles/2003/', views.special_case_2003),
path('articles/<int:year>/', views.year_archive),
path('articles/<int:year>/<int:month>/', views.month_archive),
path('articles/<int:year>/<int:month>/<slug:slug>/', views.article_detail),
]
笔记:
请求示例:
默认情况下,以下路径转换器可用:
对于更复杂的匹配要求,您可以定义自己的路径转换器。
转换器是包含以下内容的类:
例如:
class FourDigitYearConverter:
regex = '[0-9]{4}'
def to_python(self, value):
return int(value)
def to_url(self, value):
return '%04d' % value
使用register_converter()以下命令在URLconf中注册自定义转换器类 :
from django.urls import path, register_converter
from . import converters, views
register_converter(converters.FourDigitYearConverter, 'yyyy')
urlpatterns = [
path('articles/2003/', views.special_case_2003),
path('articles/<yyyy:year>/', views.year_archive),
...
]
如果路径和转换器语法不足以定义URL模式,则还可以使用正则表达式。为此,请使用 re_path()代替path()。
在Python正则表达式中,命名正则表达式组的语法为(?Ppattern),其中name是组的名称,并且 pattern是匹配的某种模式。
这是前面的示例URLconf,使用正则表达式重写:
from django.urls import path, re_path
from . import views
urlpatterns = [
path('articles/2003/', views.special_case_2003),
re_path(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive),
re_path(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$', views.month_archive),
re_path(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/(?P<slug>[w-]+)/$', views.article_detail),
]
这可以完成与上一个示例大致相同的操作,除了:
当从使用切换为使用path(), re_path()反之亦然时,特别重要的是要注意视图参数的类型可能会更改,因此您可能需要调整视图。
除了命名组语法(例如)之外(?P[0-9]{4}),您还可以使用较短的未命名组(例如)([0-9]{4})。
不建议特别使用此用法,因为这样可以更轻松地在匹配的预期含义和视图的参数之间意外引入错误。
无论哪种情况,建议在给定的正则表达式中仅使用一种样式。当两种样式混合使用时,任何未命名的组都会被忽略,只有命名的组才会传递给视图函数。
正则表达式允许嵌套参数,而Django会解析它们并将其传递给视图。反转时,Django将尝试填写所有外部捕获的参数,而忽略任何嵌套的捕获参数。考虑以下URL模式,这些URL模式可以选择采用page参数:
from django.urls import re_path
urlpatterns = [
re_path(r'^blog/(page-(d+)/)?$', blog_articles), # bad
re_path(r'^comments/(?:page-(?P<page_number>d+)/)?$', comments), # good
]
两种模式都使用嵌套参数,并将解析:例如, blog/page-2/将导致与匹配blog_articles两个位置参数:page-2/和2。的第二个模式 comments将comments/page-2/与关键字参数 page_number设置为2 匹配。在这种情况下,外部参数是一个非捕获参数(?:...)。
该blog_articles视图需要最外层捕获的参数被反转,page-2/或者在这种情况下不需要参数,而视图 comments可以不带参数也没有值而被反转page_number。
嵌套的捕获参数在视图参数和URL之间建立了牢固的耦合,如下所示blog_articles:视图接收部分URL(page-2/),而不是仅接收视图感兴趣的值。这种反转在反转时更为明显,因为反转视图,我们需要传递该URL而不是页码。
根据经验,当正则表达式需要参数但视图将其忽略时,仅捕获视图需要使用的值,并使用非捕获参数。
URLconf按照正常的Python字符串搜索请求的URL。这不包括GET或POST参数或域名。
例如,在对的请求中https://www.example.com/myapp/,URLconf将寻找myapp/。
在请求中https://www.example.com/myapp/?page=3,URLconf将寻找myapp/。
URLconf不会查看请求方法。换句话说,所有的请求方法- ,,POST 等-将被路由到相同的URL相同的功能。GET``HEAD
一个方便的技巧是为视图的参数指定默认参数。这是一个示例URLconf和视图:
# URLconf
from django.urls import path
from . import views
urlpatterns = [
path('blog/', views.page),
path('blog/page<int:num>/', views.page),
]
# View (in blog/views.py)
def page(request, num=1):
# Output the appropriate page of blog entries, according to num.
...
在上面的示例中,两个URL模式都指向同一视图– views.page–但是第一个模式未从URL中捕获任何内容。如果第一个模式匹配,该page()函数将使用它的默认参数num,1。如果第二个模式匹配, page()将使用num捕获的任何值。
中的每个正则表达式urlpatterns都是在首次访问时进行编译。这使系统运行起来非常快。
urlpatterns应该是一个序列的path() 和/或re_path()实例。
当Django无法找到所请求URL的匹配项或引发异常时,Django会调用错误处理视图。
这些情况下使用的视图由四个变量指定。它们的默认值足以满足大多数项目的需要,但可以通过覆盖其默认值来进行进一步的自定义。
有关完整的详细信息,请参见有关自定义错误视图的文档。
可以在您的根URLconf中设置这些值。在任何其他URLconf中设置这些变量将无效。
值必须是可调用的,或者是表示视图的完整Python导入路径的字符串,应该调用该视图来处理当前的错误情况。
变量是:
在任何时候,您urlpatterns都可以“包括”其他URLconf模块。实质上,这会将“ URL”“植根”在其他URL之下。
例如,这是Django网站 本身的URLconf的摘录。它包括许多其他URLconf:
from django.urls import include, path
urlpatterns = [
# ... snip ...
path('community/', include('aggregator.urls')),
path('contact/', include('contact.urls')),
# ... snip ...
]
每当Django遇到时include(),它都会截断直到该时间点匹配的URL的任何部分,并将剩余的字符串发送到包含的URLconf中以进行进一步处理。
另一种可能性是通过使用path()实例列表包括其他URL模式 。例如,考虑以下URLconf:
from django.urls import include, path
from apps.main import views as main_views
from credit import views as credit_views
extra_patterns = [
path('reports/', credit_views.report),
path('reports/<int:id>/', credit_views.report),
path('charge/', credit_views.charge),
]
urlpatterns = [
path('', main_views.homepage),
path('help/', include('apps.help.urls')),
path('credit/', include(extra_patterns)),
]
在此示例中,/credit/reports/URL将由credit_views.report()Django视图处理 。
这可用于从URLconf中删除重复使用单个模式前缀的冗余。例如,考虑以下URLconf:
from django.urls import pathfrom . import viewsurlpatterns = [ path('<page_slug>-<page_id>/history/', views.history), path('<page_slug>-<page_id>/edit/', views.edit), path('<page_slug>-<page_id>/discuss/', views.discuss), path('<page_slug>-<page_id>/permissions/', views.permissions),]
我们可以通过只声明一次公共路径前缀并对不同的后缀进行分组来改善这一点:
from django.urls import include, pathfrom . import viewsurlpatterns = [ path('<page_slug>-<page_id>/', include([ path('history/', views.history), path('edit/', views.edit), path('discuss/', views.discuss), path('permissions/', views.permissions), ])),]
包含的URLconf从父URLconfs接收任何捕获的参数,因此以下示例有效:
# In settings/urls/main.pyfrom django.urls import include, pathurlpatterns = [ path('<username>/blog/', include('foo.urls.blog')),]# In foo/urls/blog.pyfrom django.urls import pathfrom . import viewsurlpatterns = [ path('', views.blog.index), path('archive/', views.blog.archive),]
在上面的示例中,捕获的"username"变量按预期传递给包含的URLconf。
URLconfs有一个钩子,可让您将额外的参数作为Python字典传递给视图函数。
该path()函数可以使用可选的第三个参数,该参数应该是传递给view函数的额外关键字参数的字典。
例如:
from django.urls import pathfrom . import viewsurlpatterns = [ path('blog/<int:year>/', views.year_archive, {'foo': 'bar'}),]
在此示例中,对于的请求/blog/2005/,Django将调用 。views.year_archive(request, year=2005, foo='bar')
该技术在 联合框架中用于将元数据和选项传递给视图。
处理冲突
URL模式可能会捕获命名的关键字参数,并在其额外参数字典中传递具有相同名称的参数。发生这种情况时,将使用字典中的参数代替URL中捕获的参数。
同样,您可以将额外选项传递给include(),所包含的URLconf中的每一行都将传递额外选项。
例如,这两个URLconf集在功能上是相同的:
设置一:
# main.pyfrom django.urls import include, pathurlpatterns = [ path('blog/', include('inner'), {'blog_id': 3}),]# inner.pyfrom django.urls import pathfrom mysite import viewsurlpatterns = [ path('archive/', views.archive), path('about/', views.about),]
设置二:
# main.pyfrom django.urls import include, pathfrom mysite import viewsurlpatterns = [ path('blog/', include('inner')),]# inner.pyfrom django.urls import pathurlpatterns = [ path('archive/', views.archive, {'blog_id': 3}), path('about/', views.about, {'blog_id': 3}),]
请注意,无论行的视图是否实际接受这些选项,额外的选项将始终传递到所包含的URLconf中的每一行。因此,仅当您确定所包含的URLconf中的每个视图都接受要传递的额外选项时,此技术才有用。
在Django项目上进行工作时,通常需要获取最终形式的URL,以嵌入生成的内容(视图和资产URL,向用户显示的URL等)或在服务器上处理导航流程侧面(重定向等)
强烈希望避免对这些URL进行硬编码(一种费力,不可扩展且易于出错的策略)。同样危险的是,设计临时机制来生成与URLconf描述的设计平行的URL,这可能导致URL的生成随着时间的推移而变得陈旧。
换句话说,需要一种DRY机制。除其他优点外,它还允许URL设计的发展,而不必遍历所有项目源代码来搜索和替换过时的URL。
我们可以获得URL的主要信息是负责处理它的视图的标识(例如名称)。视图参数的类型(位置,关键字)和值还必须包含在正确的URL查找中的其他信息。
Django提供了一个解决方案,使得URL映射器是URL设计的唯一存储库。您将其与URLconf一起提供,然后可以在两个方向上使用它:
第一个是我们在上一节中讨论的用法。第二种是所谓的URL反向解析,反向URL匹配,反向URL查找或简称URL反向。
Django提供了执行URL反转的工具,这些工具与需要URL的不同层相匹配:
再次考虑以下URLconf条目:
from django.urls import pathfrom . import viewsurlpatterns = [ #... path('articles/<int:year>/', views.year_archive, name='news-year-archive'), #...]
根据这种设计,对应于年度归档文件的URL NNNN 是/articles//。
您可以使用以下模板代码获取它们:
<a href="{% url 'news-year-archive' 2012 %}">2012 Archive</a>{# Or with the year in a template context variable: #}<ul>{% for yearvar in year_list %}<li><a href="{% url 'news-year-archive' yearvar %}">{{ yearvar }} Archive</a></li>{% endfor %}</ul>
或在Python代码中:
from django.http import HttpResponseRedirectfrom django.urls import reversedef redirect_to_year(request): # ... year = 2006 # ... return HttpResponseRedirect(reverse('news-year-archive', args=(year,)))
如果出于某种原因决定更改发布年度文章存档内容的URL,则只需要更改URLconf中的条目即可。
在视图具有一般性质的某些情况下,URL和视图之间可能存在多对一关系。对于这些情况,在反向URL时,视图名称并不是一个足够好的标识符。阅读下一节以了解Django为此提供的解决方案。
为了执行URL反向,您需要 像上面的示例一样使用命名的URL模式。URL名称使用的字符串可以包含您喜欢的任何字符。您不限于有效的Python名称。
在命名URL模式时,请选择不太可能与其他应用程序的名称冲突的名称。如果调用URL模式,comment 而另一个应用程序执行相同的操作,则reverse()找到的URL 取决于项目urlpatterns列表中最后一个模式。
在您的URL名称上添加前缀(可能源自应用程序名称(例如myapp-comment而不是comment)),可以减少发生冲突的机会。
如果要覆盖视图,可以故意选择与另一个应用程序相同的URL名称。例如,一个常见的用例是覆盖 LoginView。Django和大多数第三方应用程序的某些部分假定此视图具有名称为的URL模式 login。如果你有一个自定义登录查看,并给它的URL的名字login, reverse()会发现自定义视图,只要它在 urlpatterns以后django.contrib.auth.urls包括(如果这是包含在所有)。
如果多个URL模式的参数不同,也可以使用相同的名称。除URL名称外,还要reverse() 匹配参数数量和关键字参数的名称。
URL名称空间允许您唯一地反向命名URL模式,即使不同的应用程序使用相同的URL名称。对于第三方应用程序,始终使用命名空间的URL是一个好习惯(就像我们在本教程中所做的那样)。同样,如果部署了一个应用程序的多个实例,它还允许您反向URL。换句话说,由于单个应用程序的多个实例将共享命名URL,因此名称空间提供了一种区分这些命名URL的方法。
对于特定站点,可以多次使用正确使用URL名称空间的Django应用程序。例如,django.contrib.admin 有一AdminSite类允许您 部署多个admin实例。在下一个示例中,我们将讨论从教程在两个不同位置部署民意调查应用程序的想法,以便我们可以为两个不同的受众(作者和发布者)提供相同的功能。
URL名称空间分为两部分,都是字符串:
使用':'操作符指定以名称分隔的URL 。例如,使用引用管理应用程序的主索引页面'admin:index'。这表示的命名空间'admin',以及的命名URL 'index'。
命名空间也可以嵌套。命名的URL 'sports:polls:index'将寻找'index'在命名空间中命名的模式,该模式'polls'本身是在顶级命名空间中定义的'sports'。
给定'polls:index'要解析的命名空间URL(例如)后,Django会将完全限定的名称拆分为多个部分,然后尝试以下查找:
如果存在嵌套的名称空间,则对名称空间的每个部分重复这些步骤,直到仅解析视图名称为止。然后,将视图名称解析为找到的名称空间中的URL。
为了展示该解决方案的实际作用,请考虑polls本教程中应用程序的两个实例的示例:一个称为'author-polls' ,一个称为'publisher-polls'。假设我们已经增强了该应用程序,以便在创建和显示民意测验时考虑实例名称空间。
的urls.py
from django.urls import include, pathurlpatterns = [ path('author-polls/', include('polls.urls', namespace='author-polls')), path('publisher-polls/', include('polls.urls', namespace='publisher-polls')),]
民调/的urls.py
from django.urls import pathfrom . import viewsapp_name = 'polls'urlpatterns = [ path('', views.IndexView.as_view(), name='index'), path('<int:pk>/', views.DetailView.as_view(), name='detail'), ...]
使用此设置,可以进行以下查找:
如果还存在一个默认实例(即名为的实例)'polls',则唯一的更改就是没有当前实例(上面列表中的第二项)。在这种情况下,'polls:index' 它将解析为默认实例的索引页,而不是最后一个在中声明的实例urlpatterns。
可以通过两种方式指定包含的URLconf的应用程序名称空间。
首先,您可以app_name在包含的URLconf模块中设置与该urlpatterns属性相同级别的属性。您必须将实际模块或对该模块的字符串引用传递给include(),而不是其urlpatterns自身的列表。
民调/的urls.py
from django.urls import pathfrom . import viewsapp_name = 'polls'urlpatterns = [ path('', views.IndexView.as_view(), name='index'), path('<int:pk>/', views.DetailView.as_view(), name='detail'), ...]
的urls.py
from django.urls import include, pathurlpatterns = [ path('polls/', include('polls.urls')),]
中定义的URL polls.urls将具有一个应用程序名称空间polls。
其次,您可以包括一个包含嵌入式名称空间数据的对象。如果您include()列出path()或 re_path()实例,则该对象中包含的URL将被添加到全局名称空间中。但是,您还可以include()包含一个包含以下内容的2元组:
(<list of path()/re_path() instances>, <application namespace>)
例如:
from django.urls import include, pathfrom . import viewspolls_patterns = ([ path('', views.IndexView.as_view(), name='index'), path('<int:pk>/', views.DetailView.as_view(), name='detail'),], 'polls')urlpatterns = [ path('polls/', include(polls_patterns)),]
这会将提名的URL模式包括到给定的应用程序名称空间中。
可以使用的namespace参数 指定实例名称空间include()。如果未指定实例名称空间,它将默认为包含的URLconf的应用程序名称空间。这意味着它将也是该名称空间的默认实例。
详细参考: https://docs.djangoproject.com/en/3.0/topics/http/urls/
视图是可调用的,它接受请求并返回响应。这不仅可以是一个函数,而且Django提供了一些可用作视图的类的示例。这些使您可以利用继承和混合来构造视图并重用代码。对于任务,还有一些通用的视图,我们将在以后进行介绍,但是您可能想要设计自己的可重用视图结构,以适合您的用例。有关完整的详细信息,请参见基于类的视图参考文档。
Django提供了适合各种应用程序的基本视图类。所有视图都从View该类继承,该类负责将视图链接到URL,HTTP方法分派和其他常见功能。RedirectView提供HTTP重定向,并TemplateView扩展基类以使其也呈现模板。
使用通用视图的最直接方法是直接在URLconf中创建它们。如果仅在基于类的视图上更改一些属性,则可以将它们传递给as_view()方法调用本身:
from django.urls import path
from django.views.generic import TemplateView
urlpatterns = [
path('about/', TemplateView.as_view(template_name="about.html")),
]
传递给任何参数as_view()将覆盖在类上设置的属性。在此示例中,我们将设置template_name 为TemplateView。可以对上的url属性使用类似的覆盖模式 RedirectView。
使用通用视图的第二种更强大的方法是从现有视图继承并覆盖子类中的属性(例如template_name)或方法(例如get_context_data)以提供新的值或方法。例如,考虑一个仅显示一个模板的视图 about.html。Django有一个通用视图可以执行此操作-- TemplateView因此我们可以将其子类化,并覆盖模板名称:
# some_app/views.py
from django.views.generic import TemplateView
class AboutView(TemplateView):
template_name = "about.html"
然后,我们需要将此新视图添加到我们的URLconf中。 TemplateView是一个类,而不是一个函数,因此我们将URL指向as_view()类方法,该方法为基于类的视图提供类似函数的条目:
# urls.py
from django.urls import path
from some_app.views import AboutView
urlpatterns = [
path('about/', AboutView.as_view()),
]
有关如何使用内置通用视图的更多信息,请参考下一个基于通用类的视图的主题。
假设有人想使用视图作为API通过HTTP访问我们的图书库。API客户端会时不时地进行连接,并下载自上次访问以来发布的图书的图书数据。但是,如果从那以后没有新书出现,那么从数据库中获取书本,呈现完整的响应并将其发送给客户端将浪费CPU时间和带宽。最好向API询问最新书籍的发布时间。
我们将URL映射到URLconf中的书列表视图:
from django.urls import path
from books.views import BookListView
urlpatterns = [
path('books/', BookListView.as_view()),
]
和视图:
from django.http import HttpResponse
from django.views.generic import ListView
from books.models import Book
class BookListView(ListView):
model = Book
def head(self, *args, **kwargs):
last_book = self.get_queryset().latest('publication_date')
response = HttpResponse()
# RFC 1123 date format
response['Last-Modified'] = last_book.publication_date.strftime('%a, %d %b %Y %H:%M:%S GMT')
return response
如果从GET请求访问视图,则在响应中返回对象列表(使用book_list.html模板)。但是,如果客户发出HEAD请求,则响应的主体为空,Last-Modified 标题会指示最新书籍的发布时间。根据此信息,客户端可以下载也可以不下载完整的对象列表。
详情参考: https://docs.djangoproject.com/en/3.0/
基于类的视图提供了一种将视图实现为Python对象而非函数的替代方法。它们不能替代基于功能的视图,但是与基于功能的视图相比具有某些区别和优势:
开始时只有视图函数协定,Django将您的函数传递给,HttpRequest并期望将 传递给HttpResponse。这就是Django提供的功能。
早期就认识到在视图开发中发现了常见的习惯用法和模式。引入了基于函数的通用视图,以抽象化这些模式并简化常见情况下的视图开发。
基于函数的通用视图的问题在于,尽管它们很好地涵盖了简单的情况,但无法扩展或自定义某些配置选项之外的视图,从而限制了它们在许多实际应用程序中的用途。
创建基于类的通用视图的目的与基于函数的通用视图相同,以使视图开发更加容易。但是,通过使用mixins来实现解决方案的方式提供了一个工具包,该工具包使得基于类的通用视图比基于功能的对应视图更具可扩展性和灵活性。
如果您过去曾经尝试过基于函数的通用视图,但发现缺少这些功能,则不应将基于类的通用视图视为基于类的等效视图,而应将其视为解决通用视图旨在解决的原始问题的全新方法。解决。
Django用于构建基于类的泛型视图的基类和mixin工具包的构建具有最大的灵活性,因此,它们具有默认方法实现和属性形式的许多钩子,您可能不会在最简单的用法中关注它们案件。例如,实现不是form_class使用get_form方法的基于类的属性,而是使用了一种方法,该get_form_class方法调用一种方法,该方法在其默认实现中返回form_class类的属性。这为您提供了几个选项,用于指定从属性到完全动态,可调用的钩子使用哪种形式。对于简单情况,这些选项似乎增加了空心的复杂性,但是如果没有这些选项,则会限制更高级的设计。
从本质上讲,基于类的视图使您可以使用不同的类实例方法来响应不同的HTTP请求方法,而不是使用单个视图函数中的有条件分支代码。
因此,GET在视图函数中用于处理HTTP的代码如下所示:
from django.http import HttpResponse
def my_view(request):
if request.method == 'GET':
# <view logic>
return HttpResponse('result')
在基于类的视图中,这将变为:
from django.http import HttpResponse
from django.views import View
class MyView(View):
def get(self, request):
# <view logic>
return HttpResponse('result')
因为Django的URL解析器希望将请求和关联的参数发送给可调用的函数而不是类,所以基于类的视图具有一个 as_view()class方法,该类方法返回一个函数,该请求可以在请求到达与关联模式匹配的URL时被调用。该函数创建该类的实例,调用 setup()以初始化其属性,然后调用其dispatch()方法。 dispatch查看该请求以确定它是否为GET, POST等,并将请求转发给匹配的方法(如果已定义),否则将其引发HttpResponseNotAllowed:
# urls.py
from django.urls import path
from myapp.views import MyView
urlpatterns = [
path('about/', MyView.as_view()),
]
值得注意的是,您的方法返回的内容与您从基于函数的视图返回的内容相同,即的某种形式 HttpResponse。这意味着 http快捷方式或 TemplateResponse对象可在基于类的视图中有效使用。
尽管最小的基于类的视图不需要任何类属性即可执行其工作,但是类属性在许多基于类的设计中很有用,并且有两种配置或设置类属性的方法。
第一种是子类化和覆盖子类中的属性和方法的标准Python方法。这样,如果您的父类具有这样的属性 greeting:
from django.http import HttpResponse
from django.views import View
class GreetingView(View):
greeting = "Good Day"
def get(self, request):
return HttpResponse(self.greeting)
您可以在子类中覆盖它:
class MorningGreetingView(GreetingView):
greeting = "Morning to ya"
另一个选择是将类属性配置为as_view()URLconf中的调用的关键字参数 :
urlpatterns = [
path('about/', GreetingView.as_view(greeting="G'day")),
]
注意:在为分配给它的每个请求实例化您的类时,通过as_view()导入点设置的类属性 在导入URL时仅配置一次。
Mixins是多重继承的一种形式,可以将多个父类的行为和属性进行组合。
例如,在基于通用类的视图中,有一个mixin, TemplateResponseMixin其主要目的是定义method render_to_response()。当与View 基类的行为组合时,结果是一个TemplateView 类,该类会将请求分派到适当的匹配方法(View基类中定义的行为),并且具有 render_to_response() 使用 template_name 属性返回TemplateResponse 对象(行为)的方法。 )中定义TemplateResponseMixin。
Mixins是在多个类之间重用代码的绝佳方法,但是它们会带来一些成本。您的代码散布在mixin中的次数越多,读取子类并了解其确切操作的难度就越大,而如果您正在子类化具有深层继承树。
还要注意,您只能从一个通用视图继承-也就是说,只有一个父类可以继承,View其余(如果有)应该是mixins。尝试从多个继承的类中进行继承View-例如,尝试使用列表顶部的表单并组合ProcessFormView和 ListView-将无法按预期工作。
处理表单的基于函数的基本视图可能如下所示:
from django.http import HttpResponseRedirect
from django.shortcuts import render
from .forms import MyForm
def myview(request):
if request.method == "POST":
form = MyForm(request.POST)
if form.is_valid():
# <process form cleaned data>
return HttpResponseRedirect('/success/')
else:
form = MyForm(initial={'key': 'value'})
return render(request, 'form_template.html', {'form': form})
类似的基于类的视图可能类似于:
from django.http import HttpResponseRedirect
from django.shortcuts import render
from django.views import View
from .forms import MyForm
class MyFormView(View):
form_class = MyForm
initial = {'key': 'value'}
template_name = 'form_template.html'
def get(self, request, *args, **kwargs):
form = self.form_class(initial=self.initial)
return render(request, self.template_name, {'form': form})
def post(self, request, *args, **kwargs):
form = self.form_class(request.POST)
if form.is_valid():
# <process form cleaned data>
return HttpResponseRedirect('/success/')
return render(request, self.template_name, {'form': form})
这是一个最小的情况,但是您可以看到您可以通过覆盖任何类属性(例如form_class,通过URLconf配置,或子类化并覆盖一个或多个方法(或两者)!)来定制此视图 。 。
基于类的视图的扩展不仅限于使用混合。您也可以使用装饰器。由于基于类的视图不是函数,因此根据您正在使用as_view()还是创建子类来装饰它们的工作方式有所不同。
您可以通过装饰as_view()方法的结果来调整基于类的视图 。最简单的方法是在部署视图的URLconf中:
from django.contrib.auth.decorators import login_required, permission_requiredfrom django.views.generic import TemplateViewfrom .views import VoteViewurlpatterns = [ path('about/', login_required(TemplateView.as_view(template_name="secret.html"))), path('vote/', permission_required('polls.can_vote')(VoteView.as_view())),]
此方法基于每个实例应用装饰器。如果要装饰视图的每个实例,则需要采用其他方法。
要修饰基于类的视图的每个实例,您需要修饰类定义本身。为此,您可以将装饰器应用于dispatch()类的 方法。
类上的方法与独立函数并不完全相同,因此您不能仅将函数装饰器应用于该方法–您需要首先将其转换为方法装饰器。所述method_decorator装饰来转换函数装饰成方法装饰,使得它可以在一个实例方法中。例如:
from django.contrib.auth.decorators import login_requiredfrom django.utils.decorators import method_decoratorfrom django.views.generic import TemplateViewclass ProtectedView(TemplateView): template_name = 'secret.html' @method_decorator(login_required) def dispatch(self, *args, **kwargs): return super().dispatch(*args, **kwargs)
或者,更简洁地说,您可以代替装饰类,并将要装饰的方法的名称作为关键字参数传递name:
@method_decorator(login_required, name='dispatch')class ProtectedView(TemplateView): template_name = 'secret.html'
如果您在多个地方使用了一组通用装饰器,则可以定义一个装饰器列表或元组,然后使用它而不是method_decorator()多次调用 。这两个类是等效的:
decorators = [never_cache, login_required]@method_decorator(decorators, name='dispatch')class ProtectedView(TemplateView): template_name = 'secret.html'@method_decorator(never_cache, name='dispatch')@method_decorator(login_required, name='dispatch')class ProtectedView(TemplateView): template_name = 'secret.html'
装饰者将按照传递给装饰者的顺序处理请求。在示例中,never_cache()将在之前处理请求 login_required()。
在此示例中,的每个实例都ProtectedView将具有登录保护。这些示例使用login_required,但是通过使用可以获得相同的行为 LoginRequiredMixin。
注意:method_decorator将*args和**kwargs 作为参数传递给类中经过修饰的方法。如果您的方法不接受一组兼容的参数,它将引发 TypeError异常。
详情参考: https://docs.djangoproject.com/en/3.0/
编写Web应用程序可能是单调的,因为我们一次又一次地重复某些模式。Django试图消除模型和模板层的某些单调性,但Web开发人员也在视图级别上遇到这种无聊的情况。
开发了Django的通用视图来缓解这种痛苦。它们采用了视图开发中发现的某些常见习语和模式,并对它们进行了抽象,以便您可以快速编写数据的通用视图而无需编写太多代码。
我们可以识别某些常见任务,例如显示对象列表,并编写显示任何对象列表的代码。然后,可以将所讨论的模型作为附加参数传递给URLconf。
Django附带了通用视图以执行以下操作:
这些视图加在一起提供了执行开发人员遇到的最常见任务的界面。
毫无疑问,使用通用视图可以大大加快开发速度。但是,在大多数项目中,有时通用视图不再足够了。确实,新Django开发人员提出的最常见问题是如何使通用视图处理更广泛的情况。
这是为1.3版本重新设计通用视图的原因之一-以前,它们是带有令人困惑的选项列表的视图函数;现在,与其在URLconf中传递大量配置,不如建议扩展常规视图的方法是将其子类化并覆盖其属性或方法。
也就是说,通用视图将受到限制。如果您发现自己很难将视图实现为通用视图的子类,则可能会发现使用自己的基于类或功能的视图来只编写所需的代码会更有效。
某些第三方应用程序中提供了更多通用视图的示例,或者您可以根据需要编写自己的视图。
TemplateView当然是有用的,但是当涉及到呈现数据库内容的视图时,Django的通用视图确实非常出色。因为这是一项常见的任务,所以Django附带了一些内置的通用视图,以帮助生成对象的列表和详细视图。
让我们先来看一些显示对象列表或单个对象的示例。
我们将使用以下模型:
# models.py
from django.db import models
class Publisher(models.Model):
name = models.CharField(max_length=30)
address = models.CharField(max_length=50)
city = models.CharField(max_length=60)
state_province = models.CharField(max_length=30)
country = models.CharField(max_length=50)
website = models.URLField()
class Meta:
ordering = ["-name"]
def __str__(self):
return self.name
class Author(models.Model):
salutation = models.CharField(max_length=10)
name = models.CharField(max_length=200)
email = models.EmailField()
headshot = models.ImageField(upload_to='author_headshots')
def __str__(self):
return self.name
class Book(models.Model):
title = models.CharField(max_length=100)
authors = models.ManyToManyField('Author')
publisher = models.ForeignKey(Publisher, on_delete=models.CASCADE)
publication_date = models.DateField()
现在我们需要定义一个视图:
# views.py
from django.views.generic import ListView
from books.models import Publisher
class PublisherList(ListView):
model = Publisher
最后,将该视图挂接到您的网址中:
# urls.py
from django.urls import path
from books.views import PublisherList
urlpatterns = [
path('publishers/', PublisherList.as_view()),
]
这就是我们需要编写的所有Python代码。但是,我们仍然需要编写一个模板。我们可以通过在视图中添加一个template_name属性来明确地告诉视图使用哪个模板 ,但是在没有显式模板的情况下,Django将从对象名称中推断出一个模板。在这种情况下,推断的模板将是"books/publisher_list.html"-“书”部分来自定义模型的应用程序的名称,而“发布者”位是模型名称的小写版本。
注意:因此,当(例如)将 后端的APP_DIRS选项DjangoTemplates设置为True in时TEMPLATES,模板位置可以是:/path/to/project/books/templates/books/publisher_list.html
将针对包含名为的变量的上下文呈现此模板,该变量 object_list包含所有发布者对象。模板可能如下所示:
{% extends "base.html" %}
{% block content %}
<h2>Publishers</h2>
<ul>
{% for publisher in object_list %}
<li>{{ publisher.name }}</li>
{% endfor %}
</ul>
{% endblock %}
这就是全部。通用视图的所有很酷的功能都来自更改通用视图上设置的属性。该 通用视图引用文档中的所有详细的通用视图的选择; 本文档的其余部分将考虑一些您可以自定义和扩展通用视图的常用方法。
您可能已经注意到我们的示例发布者列表模板将所有发布者存储在名为的变量中object_list。尽管这很好用,但对模板作者并不是那么“友好”:他们必须“只是知道”他们在这里与发行人打交道。
好吧,如果您要处理模型对象,那么已经为您完成了。当您处理对象或查询集时,Django可以使用模型类名称的小写形式填充上下文。除了默认object_list条目之外,还提供了此条目,但包含完全相同的数据,即publisher_list。
如果仍然不能很好地匹配,则可以手动设置上下文变量的名称。context_object_name通用视图上的属性指定要使用的上下文变量:
# views.py
from django.views.generic import ListView
from books.models import Publisher
class PublisherList(ListView):
model = Publisher
context_object_name = 'my_favorite_publishers'
提供有用context_object_name的东西总是一个好主意。您设计模板的同事将感谢您。
通常,您需要提供一些超出通用视图所提供信息的额外信息。例如,考虑在每个出版商详细信息页面上显示所有书籍的列表。该DetailView 通用视图提供了出版商到上下文,但是我们如何在模板中获取更多的信息?
答案是子类化DetailView 并提供您自己的get_context_data方法实现。默认实现将要显示的对象添加到模板中,但是您可以覆盖它以发送更多内容:
from django.views.generic import DetailView
from books.models import Book, Publisher
class PublisherDetail(DetailView):
model = Publisher
def get_context_data(self, **kwargs):
# Call the base implementation first to get a context
context = super().get_context_data(**kwargs)
# Add in a QuerySet of all the books
context['book_list'] = Book.objects.all()
return context
注意:通常,get_context_data将所有父类的上下文数据与当前类的上下文数据合并。若要在要更改上下文的自己的类中保留此行为,请务必确保调用 get_context_data超类。当没有两个类尝试定义相同的键时,这将提供预期的结果。但是,如果任何类在父类设置了键之后都尝试覆盖键(在调用super之后),则该类的所有子级也需要在super之后显式设置键,以确保覆盖所有父键。如果遇到问题,请查看视图的方法解析顺序。
另一个考虑是基于类的通用视图的上下文数据将覆盖上下文处理器提供的数据。请参阅 get_context_data()示例。
现在,让我们仔细看看model我们一直使用的参数。该model参数指定了将对视图进行操作的数据库模型,该参数可用于对单个对象或对象集合进行操作的所有通用视图。但是,model参数不是指定视图将操作的对象的唯一方法–您还可以使用queryset参数指定对象列表:
from django.views.generic import DetailView
from books.models import Publisher
class PublisherDetail(DetailView):
context_object_name = 'publisher'
queryset = Publisher.objects.all()
指定是简短的说法。但是,通过使用定义对象的过滤列表,您可以更详细地了解视图中将显示的对象(有关对象的更多信息,请参见进行查询,有关完整的详细信息 ,请参见 基于类的视图参考)。model = Publisher``queryset = Publisher.objects.all()``querysetQuerySet
举个例子,我们可能想按出版日期订购书籍清单,以最新的为准:
from django.views.generic import ListView
from books.models import Book
class BookList(ListView):
queryset = Book.objects.order_by('-publication_date')
context_object_name = 'book_list'
这是一个非常小的例子,但是很好地说明了这个想法。当然,通常您不仅仅需要对对象重新排序,还需要做更多的事情。如果要显示特定出版商的书籍列表,则可以使用相同的技术:
from django.views.generic import ListViewfrom books.models import Bookclass AcmeBookList(ListView): context_object_name = 'book_list' queryset = Book.objects.filter(publisher__name='ACME Publishing') template_name = 'books/acme_list.html'
请注意,除了filter之外queryset,我们还使用了自定义模板名称。如果我们不这样做,则通用视图将使用与“香草”对象列表相同的模板,而这可能不是我们想要的。
另请注意,这不是制作出版商特定书籍的一种非常优雅的方法。如果我们要添加另一个发布者页面,则需要在URLconf中再加上几行,并且不止几个发布者会变得不合理。我们将在下一部分中解决这个问题。
注意:如果在请求时收到404,请/books/acme/检查以确保您实际上拥有名称为'ACME Publishing'的发布商。通用视图allow_empty对此情况有一个参数。
另一个常见的需求是通过URL中的某个键过滤列表页面中给定的对象。之前我们在URLconf中硬编码了出版商的名称,但是如果我们想编写一个视图来显示某个任意出版商的所有书籍,该怎么办?
方便地,我们ListView有一个get_queryset()可以覆盖的 方法。默认情况下,它返回queryset属性的值,但是我们可以使用它添加更多的逻辑。
进行这项工作的关键部分是,当调用基于类的视图时,各种有用的东西都存储在self;以及request(self.request)包括根据URLconf捕获的position(self.args)和基于名称的(self.kwargs)参数。
在这里,我们有一个URLconf,其中包含一个捕获的组:
# urls.pyfrom django.urls import pathfrom books.views import PublisherBookListurlpatterns = [ path('books/<publisher>/', PublisherBookList.as_view()),]
接下来,我们将编写PublisherBookList视图本身:
# views.pyfrom django.shortcuts import get_object_or_404from django.views.generic import ListViewfrom books.models import Book, Publisherclass PublisherBookList(ListView): template_name = 'books/books_by_publisher.html' def get_queryset(self): self.publisher = get_object_or_404(Publisher, name=self.kwargs['publisher']) return Book.objects.filter(publisher=self.publisher)
使用get_queryset向查询集选择添加逻辑既方便又强大。例如,如果需要的话,我们可以使用 self.request.user当前用户或其他更复杂的逻辑进行过滤。
我们还可以同时将发布者添加到上下文中,因此我们可以在模板中使用它:
# ...def get_context_data(self, **kwargs): # Call the base implementation first to get a context context = super().get_context_data(**kwargs) # Add in the publisher context['publisher'] = self.publisher return context
我们将看到的最后一个常见模式涉及在调用通用视图之前或之后做一些额外的工作。
想象一下,我们last_accessed在Author模型上有一个字段,用于跟踪任何人上次查看该作者的时间:
# models.pyfrom django.db import modelsclass Author(models.Model): salutation = models.CharField(max_length=10) name = models.CharField(max_length=200) email = models.EmailField() headshot = models.ImageField(upload_to='author_headshots') last_accessed = models.DateTimeField()
DetailView当然,泛型类对此字段一无所知,但是我们可以再次轻松编写一个自定义视图以使该字段保持更新。
首先,我们需要在URLconf中添加作者详细信息位以指向自定义视图:
from django.urls import pathfrom books.views import AuthorDetailViewurlpatterns = [ #... path('authors/<int:pk>/', AuthorDetailView.as_view(), name='author-detail'),]
然后,我们将编写新视图– get_object是检索对象的方法–因此我们将其覆盖并包装调用:
from django.utils import timezonefrom django.views.generic import DetailViewfrom books.models import Authorclass AuthorDetailView(DetailView): queryset = Author.objects.all() def get_object(self): obj = super().get_object() # Record the last accessed date obj.last_accessed = timezone.now() obj.save() return obj
注意:URLconf在此使用命名组pk-该名称是默认名称,DetailView用于查找用于过滤查询集的主键的值。
详情参考: https://docs.djangoproject.com/en/3.0/
表单处理通常具有3条路径:
自己实现这一点通常会导致很多重复的样板代码(请参阅在视图中使用表单)。为了避免这种情况,Django提供了一组通用的基于类的视图以进行表单处理。
给出联系表:
的forms.py
from django import forms
class ContactForm(forms.Form):
name = forms.CharField()
message = forms.CharField(widget=forms.Textarea)
def send_email(self):
# send email using the self.cleaned_data dictionary
pass
可以使用构造视图FormView:
的views.py
from myapp.forms import ContactForm
from django.views.generic.edit import FormView
class ContactView(FormView):
template_name = 'contact.html'
form_class = ContactForm
success_url = '/thanks/'
def form_valid(self, form):
# This method is called when valid form data has been POSTed.
# It should return an HttpResponse.
form.send_email()
return super().form_valid(form)
笔记:
使用模型时,通用视图确实很出色。这些通用视图将自动创建一个ModelForm,只要它们能够确定要使用的模型类:
模型表单视图提供了一种 form_valid()自动保存模型的实现。如果有特殊要求,可以覆盖此设置。请参阅下面的示例。
您甚至不需要提供success_urlfor CreateView或 UpdateView- get_absolute_url()如果可用,它们将 在模型对象上使用。
如果要使用自定义ModelForm(例如添加额外的验证),请form_class在视图上进行设置 。
注意:指定自定义表单类时,即使form_class可能是,您仍必须指定模型ModelForm。
首先,我们需要添加get_absolute_url()到 Author类中:
models.py中
from django.db import models
from django.urls import reverse
class Author(models.Model):
name = models.CharField(max_length=200)
def get_absolute_url(self):
return reverse('author-detail', kwargs={'pk': self.pk})
然后我们可以CreateView和朋友一起做实际的工作。注意这里我们是如何配置通用的基于类的视图的。我们不必自己编写任何逻辑:
的views.py
from django.urls import reverse_lazy
from django.views.generic.edit import CreateView, DeleteView, UpdateView
from myapp.models import Author
class AuthorCreate(CreateView):
model = Author
fields = ['name']
class AuthorUpdate(UpdateView):
model = Author
fields = ['name']
class AuthorDelete(DeleteView):
model = Author
success_url = reverse_lazy('author-list')
注意:我们必须使用reverse_lazy()而不是 reverse(),因为导入文件时不会加载url。
该fields属性的工作方式与fields上内部Meta类的属性相同ModelForm。除非您以其他方式定义表单类,否则该属性是必需的,否则视图将引发ImproperlyConfigured异常。
如果同时指定fields 和form_class属性, ImproperlyConfigured则会引发异常。
最后,我们将这些新视图连接到URLconf中:
的urls.py
from django.urls import path
from myapp.views import AuthorCreate, AuthorDelete, AuthorUpdate
urlpatterns = [
# ...
path('author/add/', AuthorCreate.as_view(), name='author-add'),
path('author/<int:pk>/', AuthorUpdate.as_view(), name='author-update'),
path('author/<int:pk>/delete/', AuthorDelete.as_view(), name='author-delete'),
]
注意:些观点继承 SingleObjectTemplateResponseMixin 它使用 template_name_suffix 了构建 template_name 基于模型。
在此示例中:
如果希望为CreateView和 提供单独的模板UpdateView,则可以 在视图类上设置 template_name或 template_name_suffix。
要跟踪使用创建对象的用户,CreateView可以使用自定义方法ModelForm来执行此操作。首先,将外键关系添加到模型:
models.py中
from django.contrib.auth.models import User
from django.db import models
class Author(models.Model):
name = models.CharField(max_length=200)
created_by = models.ForeignKey(User, on_delete=models.CASCADE)
# ...
在视图中,确保您不包括created_by要编辑的字段列表,并覆盖 form_valid()以添加用户:
的views.py
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic.edit import CreateView
from myapp.models import Author
class AuthorCreate(LoginRequiredMixin, CreateView):
model = Author
fields = ['name']
def form_valid(self, form):
form.instance.created_by = self.request.user
return super().form_valid(form)
LoginRequiredMixin防止未登录的用户访问表单。如果您忽略了这一点,则需要处理中的未授权用户form_valid()。
这是一个示例,显示了如何实现适用于AJAX请求以及“普通”表单POST的表单:
from django.http import JsonResponse
from django.views.generic.edit import CreateView
from myapp.models import Author
class AjaxableResponseMixin:
"""
Mixin to add AJAX support to a form.
Must be used with an object-based FormView (e.g. CreateView)
"""
def form_invalid(self, form):
response = super().form_invalid(form)
if self.request.is_ajax():
return JsonResponse(form.errors, status=400)
else:
return response
def form_valid(self, form):
# We make sure to call the parent's form_valid() method because
# it might do some processing (in the case of CreateView, it will
# call form.save() for example).
response = super().form_valid(form)
if self.request.is_ajax():
data = {
'pk': self.object.pk,
}
return JsonResponse(data)
else:
return response
class AuthorCreate(AjaxableResponseMixin, CreateView):
model = Author
fields = ['name']
详情参考: https://docs.djangoproject.com/en/3.0/
警告: 这是一个高级主题。在探索这些技术之前,建议您对Django基于类的视图有一定的了解。
Django的内置基于类的视图提供了许多功能,但您可能需要单独使用其中的一些功能。例如,您可能想编写一个视图,该视图呈现一个模板以进行HTTP响应,但是您不能使用 TemplateView; 也许您只需要在上渲染模板POST,然后GET完全执行其他操作即可。虽然您可以TemplateResponse直接使用 ,但这可能会导致代码重复。
因此,Django还提供了许多混合器,这些混合器提供了更多离散功能。例如,模板呈现封装在中 TemplateResponseMixin。Django参考文档包含所有mixins的完整文档。
提供了两个中央mixin,它们有助于在使用基于类的视图中的模板时提供一致的界面。
让我们看看Django的两个基于类的通用视图是如何通过提供离散功能的mixin构建的。我们将考虑 DetailView,它呈现一个对象的“详细”视图,而 ListView,它将呈现一个对象列表(通常来自查询集),并可选地对它们进行分页。这将向我们介绍四个mixin,它们在使用单个Django对象或多个对象时提供有用的功能。
也有参与通用编辑观点混入(FormView和特定型号的意见CreateView, UpdateView和 DeleteView),并在基于日期的通用视图。这些已在mixin参考文档中介绍。
要显示对象的详细信息,我们基本上需要做两件事:我们需要查找对象,然后需要TemplateResponse使用合适的模板制作一个 ,并将该对象作为上下文。
要获得该对象,需要DetailView 依赖SingleObjectMixin,该get_object() 方法提供了一种方法,该 方法可以根据请求的URL找出对象(它会根据URLConf中的声明查找pk和slug关键字参数,然后从model视图的属性中查找该对象 ,或提供的 queryset 属性)。SingleObjectMixin也会覆盖 get_context_data(),它在所有Django的所有基于类的内置视图中使用,以为模板渲染提供上下文数据。
然后TemplateResponse,要 DetailView使用SingleObjectTemplateResponseMixin,则使用 , 如上所讨论的,它扩展了TemplateResponseMixin,覆盖 get_template_names()。实际上,它提供了一组相当复杂的选项,但是大多数人要使用的主要选项是 /_detail.html。该_detail部分可以通过设置来改变 template_name_suffix 的一个子类别的东西。(例如,通用编辑观点使用_form的创建和更新的意见,并 _confirm_delete进行删除的意见。)
对象列表遵循大致相同的模式:我们需要一个(可能是分页的)对象列表,通常为 QuerySet,然后我们需要TemplateResponse使用该对象列表使用合适的模板制作一个 。
要获取对象,请ListView使用 MultipleObjectMixin,同时提供 get_queryset() 和 paginate_queryset()。与with不同SingleObjectMixin,不需要关闭URL的一部分来确定要使用的查询集,因此默认值使用 view类上的querysetor model属性。覆盖get_queryset() 此处的常见原因 是动态地改变对象,例如取决于当前用户或排除博客的将来帖子。
MultipleObjectMixin还重写 get_context_data()以包括用于分页的适当上下文变量(如果禁用了分页,则提供虚拟变量)。它依赖object_list于作为关键字参数进行传递的关键字参数ListView。
做一个TemplateResponse, ListView然后使用 MultipleObjectTemplateResponseMixin; 与SingleObjectTemplateResponseMixin 上面的方法一样,此方法将覆盖,get_template_names()以提供,最常用的是 ,而该部分再次从 属性中获取。(基于日期的通用视图使用诸如的后缀, 以此类推,以便为各种专门的基于日期的列表视图使用不同的模板。)a range of options/_list.html``_listtemplate_name_suffix_archive``_archive_year
现在,我们已经了解了Django的基于类的通用视图如何使用提供的mixins,让我们看一下将它们组合在一起的其他方法。当然,我们仍将它们与内置的基于类的视图或其他通用的基于类的视图结合起来,但是您可以解决许多比Django开箱即用的罕见问题。
警告:并非所有的mixin都可以一起使用,也不是所有基于通用类的视图都可以与所有其他mixin一起使用。在这里,我们提供了一些可行的示例。如果要合并其他功能,则必须考虑使用的不同类之间重叠的属性和方法之间的交互,以及方法解析顺序将如何影响以何种顺序调用哪些版本的方法。
Django的基于类的视图和基于类的视图mixin的参考文档将帮助您了解哪些属性和方法可能会导致不同的类和mixin之间发生冲突。
如有疑问,通常最好还是放弃并以 View或为基础TemplateView,也许使用 SingleObjectMixin和 MultipleObjectMixin。尽管您可能最终会写出更多的代码,但是以后其他人可能更容易理解它,而更少的交互性让您担心,可以节省一些时间。(当然,您总是可以深入了解Django基于类的通用视图的实现,以获取有关如何解决问题的灵感。)
如果我们要编写一个仅对做出响应的基于类的视图,则将创建子POST类View并post()在该子类中编写一个方法。但是,如果我们希望我们的处理能够处理从URL识别的特定对象,我们将需要提供的功能 SingleObjectMixin。
我们将通过在基于通用类的视图简介中Author使用的模型来 演示这一点。
的views.py
from django.http import HttpResponseForbidden, HttpResponseRedirect
from django.urls import reverse
from django.views import View
from django.views.generic.detail import SingleObjectMixin
from books.models import Author
class RecordInterest(SingleObjectMixin, View):
"""Records the current user's interest in an author."""
model = Author
def post(self, request, *args, **kwargs):
if not request.user.is_authenticated:
return HttpResponseForbidden()
# Look up the author we're interested in.
self.object = self.get_object()
# Actually record interest somehow here!
return HttpResponseRedirect(reverse('author-detail', kwargs={'pk': self.object.pk}))
实际上,您可能希望将兴趣记录在键值存储而不是关系数据库中,因此我们省略了这一点。唯一需要担心使用的视图 SingleObjectMixin就是我们要查找感兴趣的作者的地方,它通过调用来完成 self.get_object()。mixin会为我们处理其他所有事务。
我们可以很容易地将其挂接到我们的URL中:
的urls.py
from django.urls import path
from books.views import RecordInterest
urlpatterns = [
#...
path('author/<int:pk>/interest/', RecordInterest.as_view(), name='author-interest'),
]
请注意pk命名组,该组 get_object()用于查找Author实例。您还可以使用slug或的任何其他功能 SingleObjectMixin。
ListView提供内置的分页功能,但是您可能希望对所有通过外键链接到另一个对象的对象列表进行分页。在我们的出版示例中,您可能希望对特定出版商的所有书籍进行分页。
一种实现方法是与结合ListView使用 SingleObjectMixin,以便分页图书清单的查询集可以脱离作为单个对象找到的出版商。为此,我们需要有两个不同的查询集:
注意:我们必须仔细考虑get_context_data()。由于SingleObjectMixin和 ListView都会将事物放置在上下文数据中(context_object_name如果已设置)的值之下,因此我们将明确确保事物在 Publisher上下文数据中。ListView 将增加在合适的page_obj和paginator我们提供我们记得打电话super()。
现在我们可以写一个新的PublisherDetail:
from django.views.generic import ListView
from django.views.generic.detail import SingleObjectMixin
from books.models import Publisher
class PublisherDetail(SingleObjectMixin, ListView):
paginate_by = 2
template_name = "books/publisher_detail.html"
def get(self, request, *args, **kwargs):
self.object = self.get_object(queryset=Publisher.objects.all())
return super().get(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['publisher'] = self.object
return context
def get_queryset(self):
return self.object.book_set.all()
请注意我们是如何设置的self.object,get()以便稍后在get_context_data()和中再次使用它get_queryset()。如果您未设置template_name,则模板将默认为正常 ListView选择,在这种情况下,这是 "books/book_list.html"因为它是一本书籍清单; ListView一无所知SingleObjectMixin,因此毫无 头绪Publisher。
该paginate_by所以你不必创建大量的书籍,看到分页的工作是在故意例如小!这是您要使用的模板:
{% extends "base.html" %}
{% block content %}
<h2>Publisher {{ publisher.name }}</h2>
<ol>
{% for book in page_obj %}
<li>{{ book.title }}</li>
{% endfor %}
</ol>
<div class="pagination">
<span class="step-links">
{% if page_obj.has_previous %}
<a href="?page={{ page_obj.previous_page_number }}">previous</a>
{% endif %}
<span class="current">
Page {{ page_obj.number }} of {{ paginator.num_pages }}.
</span>
{% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}">next</a>
{% endif %}
</span>
</div>
{% endblock %}
一般来说,你可以使用 TemplateResponseMixin和 SingleObjectMixin当你需要它们的功能。如上所示,稍加注意,您甚至可以SingleObjectMixin与结合使用 ListView。但是,随着您尝试这样做,事情变得越来越复杂,一个好的经验法则是:
暗示:您的每个视图都应仅使用mixins或一组基于类的通用视图中的视图:详细信息,列表,编辑和日期。例如,将TemplateView(内置于视图中)与 MultipleObjectMixin(通用列表)结合在一起是很好的选择 ,但是将SingleObjectMixin(通用细节)与MultipleObjectMixin(通用列表)结合起来可能会遇到问题。
为了显示当您尝试变得更复杂时会发生什么,我们展示了一个示例,该示例在存在更简单的解决方案时会牺牲可读性和可维护性。首先,让我们看一下与结合DetailView使用的天真尝试 , FormMixin以使我们能够 POST将Django Form带到与我们使用来显示对象相同的URL DetailView。
回想一下我们先前使用View和 SingleObjectMixin在一起的示例。我们正在记录用户对特定作者的兴趣;现在说,我们要让他们留下信息,说明他们为什么喜欢他们。再次,让我们假设我们不会将其存储在关系数据库中,而是存储在我们不再担心的更深奥的东西中。
此时,很自然地可以Form将封装从用户浏览器发送到Django的信息。还要说我们在REST上投入了巨资,因此我们想使用与显示来自用户的消息相同的URL来显示作者。让我们重写我们的代码AuthorDetailView。
尽管必须将a添加到上下文数据中,以便将其呈现在模板中,但我们将保留GET来自的处理。我们还希望从中引入表单处理,并编写一些代码,以便在表单上正确调用。DetailViewFormFormMixinPOST
注意:我们使用FormMixin并实现 post()自己,而不是尝试DetailView与之 混合FormView(这post()已经提供了合适的方法),因为这两个视图都实现get(),并且事情会变得更加混乱。
我们的新AuthorDetail外观如下所示:
# CAUTION: you almost certainly do not want to do this.
# It is provided as part of a discussion of problems you can
# run into when combining different generic class-based view
# functionality that is not designed to be used together.
from django import forms
from django.http import HttpResponseForbidden
from django.urls import reverse
from django.views.generic import DetailView
from django.views.generic.edit import FormMixin
from books.models import Author
class AuthorInterestForm(forms.Form):
message = forms.CharField()
class AuthorDetail(FormMixin, DetailView):
model = Author
form_class = AuthorInterestForm
def get_success_url(self):
return reverse('author-detail', kwargs={'pk': self.object.pk})
def post(self, request, *args, **kwargs):
if not request.user.is_authenticated:
return HttpResponseForbidden()
self.object = self.get_object()
form = self.get_form()
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
def form_valid(self, form):
# Here, we would record the user's interest using the message
# passed in form.cleaned_data['message']
return super().form_valid(form)
get_success_url()提供重定向到的位置,该位置可用于的默认实现form_valid()。post()如前所述,我们必须提供我们自己的。
之间微妙的相互作用的数量 FormMixin和DetailView已测试我们的管理事物的能力。您不太可能想自己编写此类。
在这种情况下,尽管编写处理代码涉及很多重复,但是您可以post()自己编写方法,并保持 DetailView唯一的通用功能 Form。
可替代地,它仍然会比上面的方法工作少到具有用于处理的形式,其可以使用一个单独的视图 FormView从不同 DetailView无顾虑。
我们实际上在这里试图做的是使用来自同一URL的两个不同的基于类的视图。那么为什么不这样做呢?我们在这里有一个非常清楚的划分:GET请求应获取 DetailView(Form添加到上下文数据中),POST请求应获取FormView。让我们先设置这些视图。
该AuthorDisplay视图与我们首次引入AuthorDetail时几乎相同;我们在写我们自己get_context_data(),使 AuthorInterestForm可用的模板。get_object()为了清楚起见,我们将跳过之前的 替代:
from django import forms
from django.views.generic import DetailView
from books.models import Author
class AuthorInterestForm(forms.Form):
message = forms.CharField()
class AuthorDisplay(DetailView):
model = Author
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['form'] = AuthorInterestForm()
return context
然后AuthorInterest是a FormView,但是我们必须带进来, SingleObjectMixin以便我们可以找到我们正在谈论的作者,并且我们必须记住进行设置template_name以确保表单错误将呈现与AuthorDisplayon 相同的模板GET:
from django.http import HttpResponseForbidden
from django.urls import reverse
from django.views.generic import FormView
from django.views.generic.detail import SingleObjectMixin
class AuthorInterest(SingleObjectMixin, FormView):
template_name = 'books/author_detail.html'
form_class = AuthorInterestForm
model = Author
def post(self, request, *args, **kwargs):
if not request.user.is_authenticated:
return HttpResponseForbidden()
self.object = self.get_object()
return super().post(request, *args, **kwargs)
def get_success_url(self):
return reverse('author-detail', kwargs={'pk': self.object.pk})
最后,我们以一种新的AuthorDetail观点将它们组合在一起。我们已经知道,调用as_view()基于类的视图会使我们的行为与基于函数的视图完全相同,因此我们可以在两个子视图之间进行选择。
当然,您可以通过传递关键字参数as_view()的方式与在URLconf中传递 方式相同,例如,如果您希望该AuthorInterest行为也出现在另一个URL上,但使用不同的模板:
from django.views import View
class AuthorDetail(View):
def get(self, request, *args, **kwargs):
view = AuthorDisplay.as_view()
return view(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
view = AuthorInterest.as_view()
return view(request, *args, **kwargs)
此方法还可以与任何其他直接从View或继承的常规基于类的视图或您自己的基于类的视图一起使用 TemplateView,因为它可以使不同的视图尽可能地分开。
当您想多次执行相同的操作时,基于类的视图就会大放异彩。假设您正在编写一个API,并且每个视图都应返回JSON而不是呈现的HTML。
我们可以创建一个mixin类以在所有视图中使用,一次处理到JSON的转换。
例如,JSON混合可能看起来像这样:
from django.http import JsonResponseclass JSONResponseMixin: """ A mixin that can be used to render a JSON response. """ def render_to_json_response(self, context, **response_kwargs): """ Returns a JSON response, transforming 'context' to make the payload. """ return JsonResponse( self.get_data(context), **response_kwargs ) def get_data(self, context): """ Returns an object that will be serialized as JSON by json.dumps(). """ # Note: This is *EXTREMELY* naive; in reality, you'll need # to do much more complex handling to ensure that arbitrary # objects -- such as Django model instances or querysets # -- can be serialized as JSON. return context
注意:请查看序列化Django对象文档,以获取有关如何正确将Django模型和查询集正确转换为JSON的更多信息。
这个mixin提供了render_to_json_response()一种与签名相同的方法render_to_response()。要使用它,我们需要将其混入一个TemplateView例如,并重写 render_to_response()以render_to_json_response()代替调用:
from django.views.generic import TemplateViewclass JSONView(JSONResponseMixin, TemplateView): def render_to_response(self, context, **response_kwargs): return self.render_to_json_response(context, **response_kwargs)
同样,我们可以将我们的mixin与通用视图之一一起使用。我们可以DetailView通过JSONResponseMixin与django.views.generic.detail.BaseDetailView– 混合来制作自己的版本(混合 了 DetailView模板渲染之前的行为):
from django.views.generic.detail import BaseDetailViewclass JSONDetailView(JSONResponseMixin, BaseDetailView): def render_to_response(self, context, **response_kwargs): return self.render_to_json_response(context, **response_kwargs)
然后可以以与其他任何视图相同的方式部署此视图 DetailView,并且行为完全相同-除了响应的格式。
如果你想成为真正的冒险,你甚至可以混合使用一个 DetailView子类,它能够返回两个 HTML和JSON的内容,根据不同的HTTP请求,的某些属性,如查询参数或HTTP标头。混合使用 JSONResponseMixin和和 SingleObjectTemplateResponseMixin,并render_to_response() 根据用户请求的响应类型覆盖的实现, 以采用适当的呈现方法:
from django.views.generic.detail import SingleObjectTemplateResponseMixinclass HybridDetailView(JSONResponseMixin, SingleObjectTemplateResponseMixin, BaseDetailView): def render_to_response(self, context): # Look for a 'format=json' GET argument if self.request.GET.get('format') == 'json': return self.render_to_json_response(context) else: return super().render_to_response(context)
由于Python解决方法重载的方式,对的调用 super().render_to_response(context)最终调用的 render_to_response() 实现TemplateResponseMixin。
详情参考: https://docs.djangoproject.com/en/3.0/
Django的主要部署平台是WSGI,这是Web服务器和应用程序的Python标准。
Django的startproject管理命令为您设置了一个最小的默认WSGI配置,您可以根据项目的需要对其进行调整,并指导任何符合WSGI的应用服务器使用。
Django包括以下WSGI服务器的入门文档:
使用WSGI进行部署的关键概念是application应用服务器用来与您的代码进行通信的Callable。通常application以在服务器可访问的Python模块中命名的对象的形式提供。
该startproject命令将创建一个/wsgi.py包含此类application可调用文件的文件 。
Django的开发服务器和生产WSGI部署都使用了它。
WSGI服务器application从其配置中获取可调用对象的路径。Django的内置服务器(即runserver 命令)从WSGI_APPLICATION设置中读取它。默认情况下,它设置为.wsgi.application,它指向中的application 可调用对象/wsgi.py。
当WSGI服务器加载您的应用程序时,Django需要导入settings模块-定义整个应用程序的地方。
Django使用 DJANGO_SETTINGS_MODULE环境变量以找到适当的设置模块。它必须包含指向设置模块的虚线路径。您可以将不同的值用于开发和生产。这完全取决于您如何组织设置。
如果未设置此变量,则默认wsgi.py将其设置为 mysite.settings,其中mysite是项目的名称。这就是 runserver默认情况下发现默认设置文件的方式。
注意:由于环境变量是进程范围的,因此当您在同一进程中运行多个Django站点时,这将无效。这与mod_wsgi一起发生。
为避免此问题,请对每个站点在自己的守护进程中使用mod_wsgi的守护程序模式,或通过在中强制执行来覆盖环境中的值。os.environ["DJANGO_SETTINGS_MODULE"] = "mysite.settings"``wsgi.py
要应用WSGI中间件,您可以包装应用程序对象。例如,您可以在以下位置添加这些行wsgi.py:
from helloworld.wsgi import HelloWorldApplication
application = HelloWorldApplication(application)
如果您想将Django应用程序与另一个框架的WSGI应用程序结合使用,也可以用自定义WSGI应用程序替换Django WSGI应用程序,该应用程序以后将其委托给Django WSGI应用程序。
Gunicorn(“绿色独角兽”)是用于UNIX的纯Python WSGI服务器。它没有依赖性,可以使用安装pip。
通过运行安装gunicorn 。有关更多详细信息,请参见gunicorn文档。
python -m pip install gunicorn
在Gunicorn中将Django作为通用WSGI应用程序运行
安装Gunicorn后,将gunicorn提供一个命令来启动Gunicorn服务器进程。gunicorn的最简单调用是传递包含名为的WSGI应用程序对象的模块的位置application,对于典型的Django项目,该对象应 类似于:
gunicorn myproject.wsgi
这将启动一个进程,该进程运行一个正在侦听的线程127.0.0.1:8000。它要求您的项目位于Python路径上;确保该命令最简单的方法是在与manage.py文件相同的目录中运行此命令。
有关其他提示,请参见Gunicorn的部署文档。
uWSGI是使用纯C编码的快速,自修复且对开发人员/ sysadmin友好的应用程序容器服务器。
也可以看看
uWSGI文档提供了一个涵盖Django,nginx和uWSGI(许多可能的部署设置)的教程。以下文档重点介绍如何将Django与uWSGI集成。
uWSGI Wiki描述了几种安装过程。使用Python软件包管理器pip,您可以通过单个命令安装任何uWSGI版本。例如:
# Install current stable version.
$ python -m pip install uwsgi
# Or install LTS (long term support).
$ python -m pip install https://projects.unbit.it/downloads/uwsgi-lts.tar.gz
uWSGI在客户端-服务器模型上运行。您的Web服务器(例如nginx,Apache)与django-uwsgi“工作者”进程进行通信以提供动态内容。
uWSGI支持多种配置过程的方式。
这是启动uWSGI服务器的示例命令:
uwsgi --chdir=/path/to/your/project
--module=mysite.wsgi:application
--env DJANGO_SETTINGS_MODULE=mysite.settings
--master --pidfile=/tmp/project-master.pid
--socket=127.0.0.1:49152 # can also be a file
--processes=5 # number of worker processes
--uid=1000 --gid=2000 # if root, uwsgi can drop privileges
--harakiri=20 # respawn processes taking more than 20 seconds
--max-requests=5000 # respawn processes after serving 5000 requests
--vacuum # clear environment on exit
--home=/path/to/virtual/env # optional path to a virtual environment
--daemonize=/var/log/uwsgi/yourproject.log # background the process
假设您有一个名为的顶级项目包mysite,并且mysite/wsgi.py其中包含一个包含WSGI application 对象的模块。如果您使用Django的最新版本(使用您自己的项目名称代替)运行,这将是您的布局。如果该文件不存在,则需要创建它。请参阅“ 如何使用WSGI进行部署”文档,以获取应放在此文件中的默认内容以及可以添加的其他内容。django-admin startproject mysite``mysite
Django特定的选项如下:
示例ini配置文件:
[uwsgi]
chdir=/path/to/your/project
module=mysite.wsgi:application
master=True
pidfile=/tmp/project-master.pid
vacuum=True
max-requests=5000
daemonize=/var/log/uwsgi/yourproject.log
ini配置文件用法示例:
uwsgi --ini uwsgi.ini
修复UnicodeEncodeError文件上传
如果UnicodeEncodeError在上传文件名包含非ASCII字符的文件时看到,请确保将uWSGI配置为接受非ASCII文件名,方法是将其添加到您的uwsgi.ini:
env = LANG=en_US.UTF-8
有关详细信息,请参见Unicode参考指南的“ 文件”部分。
请参阅有关管理uWSGI进程的uWSGI文档,以获取有关启动,停止和重新加载uWSGI工作程序的信息。
使用Apache和mod_wsgi部署Django 是使Django投入生产的一种久经考验的方法。
mod_wsgi是一个Apache模块,可以承载任何Python WSGI应用程序,包括Django。Django可以与任何支持mod_wsgi的Apache版本一起使用。
在官方的mod_wsgi文档是你的所有关于如何使用mod_wsgi的详细信息来源。您可能需要从安装和配置文档开始。
安装并激活mod_wsgi后,请编辑Apache服务器的 httpd.conf文件并添加以下内容。
WSGIScriptAlias / /path/to/mysite.com/mysite/wsgi.py
WSGIPythonHome /path/to/venv
WSGIPythonPath /path/to/mysite.com
<Directory /path/to/mysite.com/mysite>
<Files wsgi.py>
Require all granted
</Files>
</Directory>
该WSGIScriptAlias行的第一位是您要在其上为应用程序提供服务的基本URL路径(/表示根URL),第二位是“ WSGI文件”的位置–参见下文–在系统上,通常在项目内部包(mysite在此示例中)。这告诉Apache使用该文件中定义的WSGI应用程序为给定URL下的任何请求提供服务。
如果您将项目的Python依赖项安装在中,请使用添加路径。有关更多详细信息,请参见mod_wsgi虚拟环境指南。virtual environmentWSGIPythonHome
该WSGIPythonPath行确保您的项目包可用于在Python路径上导入;换句话说,那行得通。import mysite
该`部分确保Apache可以访问您的wsgi.py` 文件。
接下来,我们需要确保wsgi.pyWSGI应用程序对象存在。从Django 1.4版开始,startproject将为您创建一个。否则,您将需要创建它。请参阅WSGI概述文档,以获取应放在此文件中的默认内容以及可以添加的其他内容。
警告:如果在单个mod_wsgi进程中运行多个Django站点,则所有站点都将使用碰巧先运行的站点的设置。可以通过以下方法解决:
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "{{ project_name }}.settings")
在中wsgi.py,至:
os.environ["DJANGO_SETTINGS_MODULE"] = "{{ project_name }}.settings"
或通过使用mod_wsgi守护程序模式并确保每个站点都在其自己的守护进程中运行。
修复UnicodeEncodeError文件上传
如果UnicodeEncodeError在上传文件名包含非ASCII字符的文件时看到,请确保Apache配置为接受非ASCII文件名:
export LANG='en_US.UTF-8'
export LC_ALL='en_US.UTF-8'
放置此配置的常见位置是/etc/apache2/envvars。
有关详细信息,请参见Unicode参考指南的“ 文件”部分。
建议使用“守护程序模式”运行mod_wsgi(在非Windows平台上)。要创建所需的守护进程组并委派Django实例在其中运行,您将需要添加适当的 WSGIDaemonProcess和WSGIProcessGroup指令。如果您使用守护程序模式,则对上述配置的进一步更改是您不能使用WSGIPythonPath;相反,您应该使用的python-path选项 WSGIDaemonProcess,例如:
WSGIDaemonProcess example.com python-home=/path/to/venv python-path=/path/to/mysite.com
WSGIProcessGroup example.com
如果要在子目录中服务项目(https://example.com/mysite在此示例中),则可以添加WSGIScriptAlias 到上面的配置中:
WSGIScriptAlias /mysite /path/to/mysite.com/mysite/wsgi.py process-group=example.com
有关设置守护程序模式的详细信息,请参见mod_wsgi官方文档。
Django本身不提供文件;它将工作交给您选择的任何Web服务器。
我们建议使用单独的Web服务器(即未同时运行Django的服务器)来提供媒体服务。这里有一些不错的选择:
但是,如果您别无选择,只能在与VirtualHostDjango 相同的Apache上提供媒体文件,则 可以设置Apache以将某些URL用作静态媒体,而将另一些URL使用Django的mod_wsgi接口提供。
这个例子设置的Django在站点根目录,但发球robots.txt, favicon.ico和在什么/static/和/media/URL空间作为静态文件。所有其他URL将使用mod_wsgi提供:
Alias /robots.txt /path/to/mysite.com/static/robots.txt
Alias /favicon.ico /path/to/mysite.com/static/favicon.ico
Alias /media/ /path/to/mysite.com/media/
Alias /static/ /path/to/mysite.com/static/
<Directory /path/to/mysite.com/static>
Require all granted
</Directory>
<Directory /path/to/mysite.com/media>
Require all granted
</Directory>
WSGIScriptAlias / /path/to/mysite.com/mysite/wsgi.py
<Directory /path/to/mysite.com/mysite>
<Files wsgi.py>
Require all granted
</Files>
</Directory>
在django.contrib.staticfiles中时INSTALLED_APPS,Django开发服务器会自动提供admin应用程序(以及所有其他已安装的应用程序)的静态文件。但是,当您使用任何其他服务器布置时,情况并非如此。您负责设置Apache或您使用的任何Web服务器来提供管理文件。
管理文件位于django/contrib/admin/static/adminDjango发行版的()中。
我们强烈建议您使用django.contrib.staticfiles处理管理文件(如上一节中列出Web服务器一起,使用这种手段collectstatic的管理命令收集的静态文件STATIC_ROOT,然后配置你的Web服务器服务STATIC_ROOT的STATIC_URL),但这里有三个其他方法:
Django提供了一个处理程序,允许Apache直接针对Django的身份验证后端对用户进行身份验证。
由于与多个Apache处理身份验证数据库时保持同步是一个常见问题,因此可以将Apache配置为直接针对Django的身份验证系统进行身份 验证。这需要Apache版本> = 2.2和mod_wsgi> = 2.0。例如:
注意:如果您已经安装了自定义用户模型并希望使用此默认身份验证处理程序,则它必须支持一个is_active 属性。如果要使用基于组的授权,则自定义用户必须具有一个名为“组”的关系,该关系引用具有“名称”字段的相关对象。如果您的自定义不符合这些要求,则还可以指定自己的自定义mod_wsgi身份验证处理程序。
注意:在以下配置中使用时,假设您的Apache实例仅运行一个Django应用程序。如果您正在运行多个Django应用程序,请参阅mod_wsgi文档的“ 定义应用程序组”部分以获取有关此设置的更多信息。WSGIApplicationGroup %{GLOBAL}
确保已安装并激活了mod_wsgi,并且已按照步骤使用mod_wsgi设置Apache。
接下来,编辑您的Apache配置,以添加仅希望经过身份验证的用户才能查看的位置:
WSGIScriptAlias / /path/to/mysite.com/mysite/wsgi.py
WSGIPythonPath /path/to/mysite.com
WSGIProcessGroup %{GLOBAL}
WSGIApplicationGroup %{GLOBAL}
<Location "/secret">
AuthType Basic
AuthName "Top Secret"
Require valid-user
AuthBasicProvider wsgi
WSGIAuthUserScript /path/to/mysite.com/mysite/wsgi.py
</Location>
该WSGIAuthUserScript指令告诉mod_wsgi check_password在指定的wsgi脚本中执行该 功能,并传递从提示符处收到的用户名和密码。在此示例中,与 定义由django-admin startproject创建的应用程序WSGIAuthUserScript的相同。WSGIScriptAlias
结合使用Apache 2.2和身份验证
确保mod_auth_basic和mod_authz_user已加载。
这些可能会静态地编译到Apache中,或者您可能需要使用LoadModule在您的中动态加载它们httpd.conf:
LoadModule auth_basic_module modules/mod_auth_basic.so
LoadModule authz_user_module modules/mod_authz_user.so
最后,mysite.wsgi通过导入以下check_password 功能来编辑WSGI脚本,以将Apache的身份验证与站点的身份验证机制联系起来:
import os
os.environ['DJANGO_SETTINGS_MODULE'] = 'mysite.settings'
from django.contrib.auth.handlers.modwsgi import check_password
from django.core.handlers.wsgi import WSGIHandler
application = WSGIHandler()
/secret/现在开始的请求将要求用户进行身份验证。
mod_wsgi 访问控制机制文档提供了其他详细信息和有关替代身份验证方法的信息。
mod_wsgi还提供了将特定位置限制为组成员的功能。
在这种情况下,Apache配置应如下所示:
WSGIScriptAlias / /path/to/mysite.com/mysite/wsgi.py
WSGIProcessGroup %{GLOBAL}
WSGIApplicationGroup %{GLOBAL}
<Location "/secret">
AuthType Basic
AuthName "Top Secret"
AuthBasicProvider wsgi
WSGIAuthUserScript /path/to/mysite.com/mysite/wsgi.py
WSGIAuthGroupScript /path/to/mysite.com/mysite/wsgi.py
Require group secret-agents
Require valid-user
</Location>
为了支持该WSGIAuthGroupScript指令,相同的WSGI脚本 mysite.wsgi还必须导入该groups_for_user函数,该函数返回给定用户所属的列表组。
from django.contrib.auth.handlers.modwsgi import check_password, groups_for_user
如果有需求/secret/,现在也需要用户是“秘密特工”组的成员。
详情参考: https://docs.djangoproject.com/en/3.0/
除了WSGI,Django还支持在ASGI上进行部署,ASGI是用于异步Web服务器和应用程序的新兴Python标准。
Django的startproject管理命令为您设置了一个默认的ASGI配置,您可以根据项目的需要对其进行调整,并指导任何符合ASGI的应用服务器使用。
Django包括以下ASGI服务器的入门文档:
与WSGI一样,ASGI也为您提供了一个application可调用对象,应用程序服务器使用该可调用对象与您的代码进行通信。通常application以在服务器可访问的Python模块中命名的对象的形式提供。
该startproject命令将创建一个/asgi.py包含此类application可调用文件的文件 。
开发服务器(runserver)不会使用它,但是任何ASGI服务器都可以在开发或生产中使用它。
ASGI服务器通常采用可调用的应用程序路径作为字符串。对于大多数Django项目,它看起来像myproject.asgi:application。
警告:尽管Django的默认ASGI处理程序将在同步线程中运行所有代码,但如果您选择运行自己的异步处理程序,则必须注意异步安全性。
不要在任何异步代码中调用阻塞同步函数或库。Django禁止您使用非异步安全的Django部分执行此操作,但第三方应用程序或Python库可能并非如此。
当ASGI服务器加载您的应用程序时,Django需要导入设置模块-定义整个应用程序的位置。
Django使用 DJANGO_SETTINGS_MODULE环境变量以找到适当的设置模块。它必须包含指向设置模块的虚线路径。您可以将不同的值用于开发和生产。这完全取决于您如何组织设置。
如果未设置此变量,则默认asgi.py将其设置为 mysite.settings,其中mysite是项目的名称。
要应用ASGI中间件,或将Django嵌入另一个ASGI应用程序中,可以将Django的application对象包装在asgi.py文件中。例如:
from some_asgi_library import AmazingMiddleware
application = AmazingMiddleware(application)
Daphne是用于UNIX的纯Python ASGI服务器,由Django项目的成员维护。它充当ASGI的参考服务器。
您可以使用以下命令安装Daphne pip:
python -m pip install daphne
安装Daphne后,将daphne提供一个命令来启动Daphne服务器进程。最简单的说,需要使用包含ASGI应用程序对象的模块的位置来调用Daphne,然后是调用该应用程序的位置(用冒号分隔)。
对于典型的Django项目,调用Daphne如下所示:
daphne myproject.asgi:application
这将开始监听一个进程127.0.0.1:8000。它要求您的项目位于Python路径上;确保从与manage.py文件相同的目录中运行此命令。
Uvicorn是基于uvloop和的ASGI服务器httptools,着重于速度。
您可以使用以下命令安装Uvicorn pip:
python -m pip install uvicorn
安装Uvicorn后,将uvicorn提供运行ASGI应用程序的命令。必须使用包含ASGI应用程序对象的模块的位置来调用Uvicorn,然后再调用该应用程序(由冒号分隔)。
对于一个典型的Django项目,调用Uvicorn如下所示:
uvicorn myproject.asgi:application
这将开始监听一个进程127.0.0.1:8000。它要求您的项目位于Python路径上;确保从与manage.py文件相同的目录中运行此命令。
有关更多高级用法,请阅读Uvicorn文档。
详情参考: https://docs.djangoproject.com/en/3.0/
也可以看看
有关使用的简介django.contrib.staticfiles,请参阅 管理静态文件(例如,图像,JavaScript,CSS)。
将静态文件投入生产的基本概述包括两个步骤:collectstatic更改静态文件时运行命令,然后安排将收集的静态文件目录(STATIC_ROOT)移至静态文件服务器并提供服务。根据 STATICFILES_STORAGE,可能需要手动将文件移动到新位置,否则类的post_process方法Storage可能会解决这个问题。
当然,与所有部署任务一样,细节在于魔鬼。每个生产设置都会有所不同,因此您需要调整基本轮廓以适合您的需求。以下是一些可能有用的常见模式。
如果要从已经为您的站点提供服务的同一台服务器提供静态文件,则该过程可能类似于:
您可能希望自动化此过程,尤其是当您有多个Web服务器时。
大多数较大的Django站点使用单独的Web服务器(即未同时运行Django的Web服务器)来提供静态文件。该服务器通常运行其他类型的Web服务器-速度更快,但功能较少。一些常见的选择是:
配置这些服务器不在本文档的讨论范围内。检查每个服务器各自的文档以获取指示。
由于您的静态文件服务器不会运行Django,因此您需要修改部署策略,使其类似于:
另一个常见的策略是为来自Amazon S3和/或CDN(内容交付网络)等云存储提供商的静态文件提供服务。这使您可以忽略提供静态文件的问题,并且通常可以使网页加载速度更快(尤其是在使用CDN时)。
使用这些服务时,基本工作流程看起来与上面类似,除了rsync需要将静态文件传输到存储提供程序或CDN 而不是用于将静态文件传输到服务器之外。
您可以通过多种方式执行此操作,但是如果提供程序具有API,则可以使用自定义文件存储后端 将CDN与Django项目集成。如果您已经编写或正在使用第三方的自定义存储后端,则可以collectstatic通过设置STATICFILES_STORAGE存储引擎来告诉您使用它。
例如,如果您已经编写了一个S3存储后端,则 myproject.storage.S3Storage可以将其用于:
STATICFILES_STORAGE = 'myproject.storage.S3Storage'
完成此操作后,所有您需要做的就是运行collectstatic,您的静态文件将通过存储包推送到S3。如果以后需要切换到其他存储提供商,则只需更改STATICFILES_STORAGE设置即可。
有关如何编写这些后端之一的详细信息,请参阅《 编写自定义存储系统》。有可用的第三方应用程序为许多常用文件存储API提供存储后端。djangopackages.org的概述是一个很好的起点。
有关其中包含的所有设置,命令,模板标记和其他部分的完整详细信息django.contrib.staticfiles,请参见staticfiles参考。
错误报告
在运行公共站点时,应始终关闭该 DEBUG设置。这将使您的服务器运行得更快,并且还可以防止恶意用户查看错误页面可能显示的应用程序详细信息。
但是,DEBUG将set 运行为False表示您将永远不会看到站点生成的错误-每个人都将看到您的公共错误页面。您需要跟踪部署站点中发生的错误,因此可以将Django配置为创建包含有关这些错误的详细信息的报告。
当DEBUGis时False,ADMINS只要您的代码引发未处理的异常并导致内部服务器错误(严格来说,对于HTTP状态代码为500或更高的任何响应),Django都会通过电子邮件发送设置中列出的用户 。这会给管理员立即通知任何错误。该ADMINS会得到错误的描述,一个完整的Python追踪,以及有关导致错误的HTTP请求的详细信息。
注意: 为了发送电子邮件,Django需要一些设置来告诉它如何连接到您的邮件服务器。至少,您需要指定,EMAIL_HOST并且可能 需要EMAIL_HOST_USER和EMAIL_HOST_PASSWORD,尽管根据您的邮件服务器的配置,可能还需要其他设置。有关与电子邮件相关的设置的完整列表,请参阅Django设置文档。
默认情况下,Django将通过root @ localhost发送电子邮件。但是,某些邮件提供商拒绝来自该地址的所有电子邮件。要使用其他发件人地址,请修改SERVER_EMAIL设置。
要激活此行为,请将收件人的电子邮件地址放入 ADMINS设置中。
也可以看看
服务器错误电子邮件是使用日志记录框架发送的,因此您可以通过自定义日志记录配置来自定义此行为。
Django也可以配置为通过电子邮件发送有关断开链接的错误(404“找不到页面”错误)。Django在以下情况下发送有关404错误的电子邮件:
如果满足这些条件,则MANAGERS只要您的代码引发404并且请求具有引荐来源,Django就会通过电子邮件将设置中列出的用户发送给 您。不用电子邮件发送没有推荐人的404,这些人通常是输入损坏的URL或损坏的Web bot的人。当引用者等于请求的URL时,它也会忽略404,因为此行为也来自损坏的Web机器人。
注意: BrokenLinkEmailsMiddleware必须出现在拦截404错误的其他中间件之前,例如 LocaleMiddleware或 FlatpageFallbackMiddleware。将其放在MIDDLEWARE设置的顶部。
您可以通过调整IGNORABLE_404_URLS设置来告诉Django停止报告特定的404 。它应该是已编译的正则表达式对象的列表。例如:
import reIGNORABLE_404_URLS = [ re.compile(r'.(php|cgi)$'), re.compile(r'^/phpmyadmin/'),]
在此示例中,任何以.php或结尾的URL的404 .cgi都不会被报告。以开头的网址也不会/phpmyadmin/。
以下示例显示了如何排除浏览器和搜寻器经常请求的一些常规URL:
import reIGNORABLE_404_URLS = [ re.compile(r'^/apple-touch-icon.*.png$'), re.compile(r'^/favicon.ico$'), re.compile(r'^/robots.txt$'),]
(请注意,这些是正则表达式,因此我们在句点前加了一个反斜杠以将其转义。)
如果您想django.middleware.common.BrokenLinkEmailsMiddleware进一步自定义行为 (例如忽略来自Web爬网程序的请求),则应将其子类化并覆盖其方法。
也可以看看
使用日志记录框架记录404错误。默认情况下,这些日志记录将被忽略,但是您可以通过编写处理程序并适当配置日志记录来将它们用于错误报告。
警告:筛选敏感数据是一个难题,几乎不可能保证敏感数据不会泄漏到错误报告中。因此,错误报告仅应提供给受信任的团队成员,并且您应避免通过Internet(例如通过电子邮件)传输未经加密的错误报告。
错误报告对于调试错误确实很有帮助,因此通常记录尽可能多的有关这些错误的相关信息非常有用。例如,默认情况下,Django会记录引发的异常的完整追溯,每个追溯框架的局部变量以及 HttpRequest的属性。
但是,有时某些类型的信息可能过于敏感,因此可能不适用于跟踪例如用户的密码或信用卡号。因此,除了按照DEBUG文档中所述过滤掉似乎敏感的设置之外,Django还提供了一组函数装饰器,以帮助您控制应从生产环境中的错误报告中过滤掉哪些信息(即在何处 DEBUG设置为False):sensitive_variables()和 sensitive_post_parameters()。
sensitive_variables
(*变量)如果代码中的函数(视图或任何常规回调)使用易于包含敏感信息的局部变量,则可以使用sensitive_variables
装饰器防止这些变量的值包含在错误报告中:
from django.views.decorators.debug import sensitive_variables@sensitive_variables('user', 'pw', 'cc')def process_info(user): pw = user.pass_word cc = user.credit_card_number name = user.name ...
在上述例子中,对于该值user
,pw
和cc
变量将被隐藏,并用星(替换**********
)错误的报告,而值name
变量将被公开。
要从错误日志中系统地隐藏函数的所有局部变量,请不要向sensitive_variables
装饰器提供任何参数:
@sensitive_variables()def my_function(): ...
使用多个装饰器时
如果要隐藏的变量也是函数参数(例如user
,在下面的示例中为“ ”),并且如果修饰的函数具有多个修饰符,则请确保将其放置@sensitive_variables
在修饰符链的顶部。这样,当它通过其他装饰器传递时,它还将隐藏函数参数:
@sensitive_variables('user', 'pw', 'cc')@some_decorator@another_decoratordef process_info(user): ...
sensitive_post_parameters
(* parameters)如果您的一个视图接收到一个易受敏感信息影响的HttpRequest
对象,则可以使用装饰器阻止这些参数的值包含在错误报告中 :POST parameters
sensitive_post_parameters
from django.views.decorators.debug import sensitive_post_parameters@sensitive_post_parameters('pass_word', 'credit_card_number')def record_user_profile(request): UserProfile.create( user=request.user, password=request.POST['pass_word'], credit_card=request.POST['credit_card_number'], name=request.POST['name'], ) ...
在上面的示例中,错误报告内的请求表示中,pass_word
和 credit_card_number
参数的值将被隐藏并用星号(**********
)替换,而该name
参数的值将被公开。
要在错误报告中系统地隐藏请求的所有POST参数,请不要向sensitive_post_parameters
装饰器提供任何参数:
@sensitive_post_parameters()def my_view(request): ...
所有POST参数被系统过滤的某些错误报告出来django.contrib.auth.views
次(login
, password_reset_confirm
,password_change
,和add_view
和 user_change_password
在auth
管理员),以防止敏感信息泄漏的诸如用户密码。
All sensitive_variables()和sensitive_post_parameters()do分别用敏感变量的名称注释修饰的函数,并HttpRequest用敏感的POST参数的名称注释对象,以便以后在发生错误时可以从报告中过滤掉此敏感信息。实际的过滤是由Django的默认错误报告过滤器完成的 django.views.debug.SafeExceptionReporterFilter。**********生成错误报告时,此过滤器使用修饰符的注释将相应的值替换为star()。如果您希望为整个网站覆盖或自定义此默认行为,则需要定义自己的过滤器类,并告诉Django通过以下DEFAULT_EXCEPTION_REPORTER_FILTER设置使用它 :
DEFAULT_EXCEPTION_REPORTER_FILTER = 'path.to.your.CustomExceptionReporterFilter'
您还可以通过设置HttpRequest的exception_reporter_filter 属性,以更精细的方式控制要在任何给定视图中使用的过滤器:
def my_view(request): if request.user.is_authenticated: request.exception_reporter_filter = CustomExceptionReporterFilter() ...
您的自定义过滤器类需要继承 django.views.debug.SafeExceptionReporterFilter并可以覆盖以下方法:
SafeExceptionReporterFilter
SafeExceptionReporterFilter.
is_active
(请求)返回True
以激活其他方法中操作的过滤。默认情况下,如果DEBUG
为,则过滤器处于活动状态False
。
SafeExceptionReporterFilter.
get_post_parameters
(请求)返回过滤后的POST参数字典。默认情况下,它将敏感参数的值替换为星号(**********
)。
SafeExceptionReporterFilter.
get_traceback_frame_variables
(request,tb_frame)返回给定回溯帧的局部变量的过滤字典。默认情况下,它将敏感变量的值替换为star(**********
)。
也可以看看
您还可以通过编写自定义的异常中间件来设置自定义错误报告 。如果您确实编写了自定义错误处理,则最好模拟Django的内置错误处理,如果DEBUG是,则仅报告/记录错误False。
详情参考: https://docs.djangoproject.com/en/3.0/
互联网是一个敌对的环境。在部署Django项目之前,您应该花一些时间在考虑安全性,性能和操作的情况下检查设置。
Django包含许多安全功能。有些是内置的,并且始终启用。其他选项是可选的,因为它们并不总是合适的,或者因为它们不方便开发。例如,强制HTTPS可能不适用于所有网站,并且对于本地开发而言是不切实际的。
性能优化是另一种权衡取舍的方法。例如,缓存在生产中很有用,而对于本地开发则没有用。错误报告的需求也大不相同。
以下清单包括以下设置:
其中许多设置都是敏感的,应视为机密。如果要发布项目的源代码,通常的做法是发布合适的设置进行开发,并使用私有设置模块进行生产。
使用该选件可以自动执行以下所述的某些检查。确保按照选件文档中的说明针对生产设置文件运行它。
check --deploy
密钥必须是一个较大的随机值,并且必须保密。
确保生产中使用的密钥不在其他任何地方使用,并避免将其提交给源代码管理。这减少了攻击者可以从中获取密钥的向量的数量。
可以考虑从环境变量加载密钥,而不是在设置模块中对密钥进行硬编码:
import osSECRET_KEY = os.environ['SECRET_KEY']
或来自文件:
with open('/etc/secret_key.txt') as f: SECRET_KEY = f.read().strip()
您绝不能在生产中启用调试。
当然,您正在使用开发您的项目,因为这将启用方便的功能,例如浏览器中的完整追溯。DEBUG = True
但是,对于生产环境而言,这是一个非常糟糕的主意,因为它会泄露有关项目的大量信息:源代码的摘录,局部变量,设置,使用的库等。
那时,如果没有合适的值,Django根本无法工作。DEBUG = FalseALLOWED_HOSTS
需要此设置来保护您的站点免受某些CSRF攻击。如果使用通配符,则必须对HostHTTP标头执行自己的验证,否则,请确保您不容易受到此类攻击。
您还应该配置位于Django前面的Web服务器以验证主机。它应该以静态错误页面响应或忽略对不正确主机的请求,而不是将请求转发给Django。这样,您将避免在Django日志(或电子邮件(如果您以这种方式配置了错误报告)中)出现虚假错误。例如,在nginx上,您可以设置默认服务器以在无法识别的主机上返回“ 444 No Response”:
server { listen 80 default_server; return 444;}
如果您使用缓存,则连接参数在开发和生产中可能会有所不同。Django默认对每个进程进行本地内存缓存,这可能是不希望的。
缓存服务器通常具有弱认证。确保它们仅接受来自您的应用程序服务器的连接。
数据库连接参数在开发和生产中可能有所不同。
数据库密码非常敏感。您应该像保护他们一样 SECRET_KEY。
为了获得最大的安全性,请确保数据库服务器仅接受来自应用程序服务器的连接。
如果您尚未为数据库设置备份,请立即执行!
如果您的站点发送电子邮件,则需要正确设置这些值。
默认情况下,Django从webmaster @ localhost和root @ localhost发送电子邮件。但是,某些邮件提供商拒绝来自这些地址的电子邮件。要使用其他发件人地址,请修改DEFAULT_FROM_EMAIL和 SERVER_EMAIL设置。
静态文件由开发服务器自动提供。在生产中,必须定义一个STATIC_ROOT目录,collectstatic将它们复制到该目录中 。
有关更多信息,请参见管理静态文件(例如,图像,JavaScript,CSS)。
媒体文件由您的用户上传。他们是不信任的!确保您的Web服务器从不尝试解释它们。例如,如果用户上传 .php文件,则Web服务器不应执行该文件。
现在是检查这些文件的备份策略的好时机。
任何允许用户登录的网站都应实施站点范围的HTTPS,以避免明文传输访问令牌。在Django中,访问令牌包括登录名/密码,会话cookie和密码重置令牌。(如果要通过电子邮件发送密码重置令牌,则无法做很多事情来保护它们。)
保护敏感区域(例如用户帐户或管理员)是不够的,因为HTTP和HTTPS使用相同的会话cookie。您的网络服务器必须将所有HTTP通信重定向到HTTPS,并且只能将HTTPS请求传输到Django。
设置HTTPS后,请启用以下设置。
进行设置True以避免在HTTP上意外传输CSRF cookie。
进行设置True以避免在HTTP上意外传输会话cookie。
设置将禁用仅在开发中有用的几个功能。此外,您可以调整以下设置。DEBUG = False
考虑使用缓存的会话来提高性能。
如果使用数据库支持的会话,请定期清除旧会话,以避免存储不必要的数据。
如果在请求处理时间的很大一部分时间内连接到数据库帐户,则启用持久性数据库连接可以大大提高速度。
这对网络性能有限的虚拟主机有很大帮助。
启用缓存的模板加载器通常会大大提高性能,因为它避免了每次需要渲染每个模板时都对它们进行编译。有关更多信息,请参见 模板加载器文档。
当您将代码推向生产时,它有望变得健壮,但不能排除意外错误。值得庆幸的是,Django可以捕获错误并相应地通知您。
在将网站投入生产之前,请查看日志记录配置,并在收到一些流量后立即检查它是否按预期工作。
有关日志记录的详细信息,请参见日志记录。
ADMINS 将通过电子邮件通知500个错误。
MANAGERS将会收到404错误的通知。 IGNORABLE_404_URLS可以帮助过滤掉虚假报告。
有关通过电子邮件报告错误的详细信息,请参见错误报告。
通过电子邮件报告的错误无法很好地扩展
在您的收件箱被报告淹没之前,请考虑使用诸如Sentry之类的错误监视系统。哨兵也可以汇总日志。
Django包含一些HTTP错误代码的默认视图和模板。您可能希望通过在根模板目录中创建以下模板来覆盖缺省模板:404.html,500.html,403.html,和 400.html。使用这些模板的默认错误视图足以满足99%的Web应用程序的要求,但是您也可以对其进行 自定义。
详情参考: https://docs.djangoproject.com/en/3.0/
XSS攻击使用户可以将客户端脚本注入其他用户的浏览器中。通常,这是通过将恶意脚本存储在数据库中进行检索并将其显示给其他用户,或者通过使用户单击链接而导致攻击者的JavaScript由用户的浏览器执行来实现的。但是,只要未在包含在页面中之前对数据进行足够的清理,XSS攻击就可能源自任何不受信任的数据源,例如cookie或Web服务。
使用Django模板可以保护您免受大多数XSS攻击。但是,重要的是要了解其提供的保护及其限制。
Django模板会转义特定字符 ,这对于HTML来说尤其危险。尽管这可以保护用户免受大多数恶意输入的侵害,但这并不是绝对安全的。例如,它将不会保护以下内容:
<style class={{ var }}>...</style>
如果var将设置为,则可能导致未经授权的JavaScript执行,具体取决于浏览器如何呈现不完美的HTML。(引用属性值将解决这种情况。)'class1 onmouseover=javascript:func()'
is_safe与自定义模板标签,safe模板标签一起使用mark_safe以及关闭自动转义功能时,请务必特别小心。
此外,如果您使用模板系统输出HTML以外的内容,则可能会有完全分开的字符和单词需要转义。
在数据库中存储HTML时,也应特别小心,尤其是在检索和显示HTML时。
CSRF攻击允许恶意用户使用另一用户的凭据执行操作,而无需该用户的知识或同意。
Django具有针对大多数CSRF攻击的内置保护,只要您在适当的地方启用和使用它即可。但是,与任何缓解技术一样,存在局限性。例如,可以全局禁用或针对特定视图禁用CSRF模块。仅当您知道自己在做什么时,才应该这样做。如果您的站点具有您无法控制的子域,则还有其他限制。
CSRF保护通过检查每个POST请求中的秘密来起作用。这样可以确保恶意用户无法将表单POST“重播”到您的网站,而让另一个登录用户无意间提交该表单。恶意用户必须知道特定于用户的秘密(使用cookie)。
与HTTPS一起部署时, CsrfViewMiddleware将检查HTTP引用标头是否设置为同一来源(包括子域和端口)上的URL。因为HTTPS提供了额外的安全性,所以必须通过转发不安全的连接请求并对支持的浏览器使用HSTS来确保连接使用HTTPS可用的连接。
csrf_exempt除非绝对必要,否则请小心用装饰器标记视图。
SQL注入是一种攻击,恶意用户能够在数据库上执行任意SQL代码。这可能导致记录被删除或数据泄漏。
由于Django的查询集是使用查询参数化构造的,因此可以防止SQL注入。查询的SQL代码是与查询的参数分开定义的。由于参数可能是用户提供的,因此是不安全的,因此底层数据库驱动程序会对其进行转义。
Django还使开发人员可以编写原始查询或执行自定义sql。这些功能应谨慎使用,并且您应始终小心谨慎地转义用户可以控制的任何参数。此外,使用extra() 和时应格外小心RawSQL。
点击劫持是一种攻击,其中恶意站点将另一个站点包装在框架中。这种攻击可能导致毫无戒心的用户被诱骗在目标站点上执行意外的操作。
Django包含clickjacking保护,其形式为 在支持的浏览器中可以防止网站在框架内呈现。可以基于每个视图禁用保护或配置发送的确切报头值。X-Frame-Options middleware
强烈建议将中间件用于任何不需要第三方站点将其页面包装在框架中的站点,或者只需要允许站点的一小部分使用该中间件。
在HTTPS后面部署站点对于安全性而言总是更好的选择。否则,恶意网络用户可能会嗅探身份验证凭据或客户端与服务器之间传输的任何其他信息,并且在某些情况下(活动的网络攻击者)可能会更改沿任一方向发送的数据。
如果您想要HTTPS提供的保护并已在服务器上启用了该保护,则可能需要执行一些其他步骤:
Host在某些情况下,Django使用客户端提供的标头构造URL。尽管清除了这些值以防止跨站点脚本攻击,但伪造的Host值可用于跨站点请求伪造,缓存中毒攻击和电子邮件中的中毒链接。
因为即使看似安全的Web服务器配置也容易受到伪造的 Host标头的影响,所以Django会根据方法Host中的ALLOWED_HOSTS设置来 验证标头 django.http.HttpRequest.get_host()。
此验证仅通过get_host();如果您的代码Host直接从request.META您访问标头,则会绕过此安全保护。
有关更多详细信息,请参见完整ALLOWED_HOSTS文档。
警告:本文档的先前版本建议配置Web服务器,以确保它验证传入的HTTP Host标头。尽管仍然建议这样做,但是在许多常见的Web服务器中,似乎可以验证Host标头的配置实际上可能没有这样做。例如,即使将Apache配置为从具有该ServerName设置的非默认虚拟主机为Django站点提供服务,HTTP请求仍然有可能匹配该虚拟主机并提供伪造的Host标头。因此,Django现在要求您进行ALLOWED_HOSTS显式设置,而不是依赖于Web服务器配置。
此外,如果您的配置需要Django,则Django要求您显式启用对X-Forwarded-Host标头的支持 (通过USE_X_FORWARDED_HOST设置)。
浏览器使用Referer标头作为向用户发送有关用户到达那里的信息的方式。通过设置引荐来源网址策略,您可以帮助保护用户的隐私,并限制在哪种情况下设置 Referer标头。有关详细信息,请参见安全中间件参考的引荐来源网址策略部分。
与CSRF的限制类似,它要求部署网站以使不受信任的用户无法访问任何子域,这 django.contrib.sessions也有限制。有关详细信息,请参见有关安全性的会话主题指南部分。
注意:考虑从云服务或CDN提供静态文件,以避免其中的某些问题。
尽管Django提供了开箱即用的良好安全保护,但是正确部署应用程序并利用Web服务器,操作系统和其他组件的安全保护仍然很重要。
详情参考: https://docs.djangoproject.com/en/3.0/
动态网站的基本权衡是动态的。每次用户请求页面时,Web服务器都会进行各种计算-从数据库查询到模板呈现再到业务逻辑-创建站点访问者可以看到的页面。从处理开销的角度来看,这比标准的从文件中读取文件的服务器系统要贵得多。
对于大多数Web应用程序而言,此开销并不大。大多数Web应用程序不是washingtonpost.com或slashdot.org; 它们是流量中等的中小型网站。但是对于中到高流量的站点,必须尽可能减少开销。
那就是缓存的来源。
缓存某些内容是为了保存昂贵的计算结果,因此您下次不必执行计算。以下是一些伪代码,用于说明如何将其应用于动态生成的网页:
given a URL, try finding that page in the cacheif the page is in the cache: return the cached pageelse: generate the page save the generated page in the cache (for next time) return the generated page
Django带有一个健壮的缓存系统,可让您保存动态页面,因此不必为每个请求都计算它们。为了方便起见,Django提供了不同级别的缓存粒度:您可以缓存特定视图的输出,可以仅缓存难以生成的片段,或者可以缓存整个站点。
Django还可以与“下游”缓存(例如Squid和基于浏览器的缓存)配合使用。这些是您不直接控制的缓存类型,但是您可以向它们提供提示(通过HTTP标头)有关站点的哪些部分以及应该如何缓存的提示。
也可以看看
该缓存框架的设计理念, 解释了一些框架的设计决策。
缓存系统需要少量设置。即,您必须告诉它缓存的数据应该存放在哪里–无论是在数据库中,在文件系统上还是直接在内存中。这是一个影响缓存性能的重要决定。是的,某些缓存类型比其他类型更快。
您的缓存首选项进入CACHES设置文件中的设置。以下是的所有可用值的说明 CACHES。
Memcached是Django原生支持的最快,最高效的缓存类型, 是一种完全基于内存的缓存服务器,最初是为处理LiveJournal.com上的高负载而开发的,随后由Danga Interactive开源。Facebook和Wikipedia等网站使用它来减少数据库访问并显着提高网站性能。
Memcached作为守护程序运行,并分配了指定数量的RAM。它所做的只是提供一个用于添加,检索和删除缓存中数据的快速接口。所有数据都直接存储在内存中,因此没有数据库或文件系统使用的开销。
本身安装Memcached后,您需要安装Memcached绑定。有几种可用的Python Memcached绑定。两种最常见的是python-memcached和pylibmc。
要将Memcached与Django结合使用,请执行以下操作:
在此示例中,Memcached使用python-memcached绑定在本地主机(127.0.0.1)端口11211上运行:
CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', 'LOCATION': '127.0.0.1:11211', }}
在此示例中,可以/tmp/memcached.sock使用python-memcached绑定通过本地Unix套接字文件使用Memcached :
CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', 'LOCATION': 'unix:/tmp/memcached.sock', }}
使用pylibmc绑定时,请勿包括unix:/前缀:
CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache', 'LOCATION': '/tmp/memcached.sock', }}
Memcached的一项出色功能是能够在多个服务器上共享缓存。这意味着您可以在多台计算机上运行Memcached守护程序,并且该程序会将计算机组视为单个 缓存,而无需在每台计算机上重复缓存值。要利用此功能,请将所有服务器地址包含在中 LOCATION,以分号或逗号分隔的字符串或列表的形式。
在此示例中,缓存在IP地址为172.19.26.240和172.19.26.242且均在端口11211上运行的Memcached实例之间共享:
CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', 'LOCATION': [ '172.19.26.240:11211', '172.19.26.242:11211', ] }}
在以下示例中,缓存在运行在IP地址172.19.26.240(端口11211),172.19.26.42(端口11212)和172.19.26.244(端口11213)上的Memcached实例上共享:
CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', 'LOCATION': [ '172.19.26.240:11211', '172.19.26.242:11212', '172.19.26.244:11213', ] }}
关于Memcached的最后一点是基于内存的缓存有一个缺点:由于缓存的数据存储在内存中,因此如果服务器崩溃,数据将丢失。显然,内存不是用于永久性数据存储的,因此不要依赖基于内存的缓存作为唯一的数据存储。毫无疑问,任何 Django缓存后端都不应该用于永久存储-它们都旨在作为缓存而非存储的解决方案-但我们在此指出这一点是因为基于内存的缓存特别临时。
Django可以将其缓存的数据存储在您的数据库中。如果您拥有快速索引良好的数据库服务器,则此方法效果最佳。
要将数据库表用作缓存后端:
在此示例中,缓存表的名称为my_cache_table:
CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.db.DatabaseCache', 'LOCATION': 'my_cache_table', }}
在使用数据库缓存之前,必须使用以下命令创建缓存表:
python manage.py createcachetable
这会在您的数据库中创建一个表,该表的格式与Django的数据库缓存系统期望的格式相同。该表的名称取自 LOCATION。
如果使用多个数据库缓存,请createcachetable为每个缓存创建一个表。
如果您使用多个数据库,请createcachetable遵循allow_migrate()数据库路由器的 方法(请参见下文)。
像一样migrate,createcachetable不会触摸现有表格。它只会创建丢失的表。
要打印将要运行的SQL,而不是运行它,请使用 选项。createcachetable --dry-run
如果将数据库缓存与多个数据库一起使用,则还需要为数据库缓存表设置路由说明。为了进行路由,数据库高速缓存表CacheEntry在名为的应用程序中显示为名为的模型 django_cache。该模型不会出现在模型缓存中,但是可以将模型详细信息用于路由目的。
例如,以下路由器会将所有缓存读取操作定向到cache_replica,并将所有写入操作定向到 cache_primary。缓存表将仅同步到 cache_primary:
class CacheRouter: """A router to control all database cache operations""" def db_for_read(self, model, **hints): "All cache read operations go to the replica" if model._meta.app_label == 'django_cache': return 'cache_replica' return None def db_for_write(self, model, **hints): "All cache write operations go to primary" if model._meta.app_label == 'django_cache': return 'cache_primary' return None def allow_migrate(self, db, app_label, model_name=None, **hints): "Only install the cache model on primary" if app_label == 'django_cache': return db == 'cache_primary' return None
如果您没有为数据库缓存模型指定路由方向,则缓存后端将使用default数据库。
当然,如果您不使用数据库缓存后端,则无需担心为数据库缓存模型提供路由说明。
基于文件的后端将每个缓存值序列化并存储为单独的文件。要将此后端设置BACKEND为"django.core.cache.backends.filebased.FileBasedCache"并 设置到 LOCATION合适的目录。例如,要在中存储缓存的数据/var/tmp/django_cache,请使用以下设置:
CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache', 'LOCATION': '/var/tmp/django_cache', }}
如果您使用的是Windows,请将驱动器号放在路径的开头,如下所示:
CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache', 'LOCATION': 'c:/foo/bar', }}
目录路径应该是绝对的-也就是说,它应该从文件系统的根目录开始。是否在设置的末尾加斜杠都没关系。
确保此设置指向的目录存在,并且Web服务器在其下运行的系统用户可以读写。继续上面的示例,如果您的服务器以用户身份运行apache,请确保该目录/var/tmp/django_cache存在并且可由用户读取和写入apache。
如果未在设置文件中指定其他缓存,则这是默认缓存。如果您想要内存中缓存的速度优势,但又不具备运行Memcached的功能,请考虑使用本地内存缓存后端。该缓存是按进程(请参阅下文)并且是线程安全的。要使用它,请设置BACKEND为"django.core.cache.backends.locmem.LocMemCache"。例如:
CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', 'LOCATION': 'unique-snowflake', }}
高速缓存LOCATION用于标识各个内存存储。如果只有一个locmem缓存,则可以省略 LOCATION; 但是,如果您有多个本地内存缓存,则需要至少为其分配一个名称,以使它们分开。
缓存使用最近最少使用(LRU)淘汰策略。
请注意,每个进程都有其自己的专用缓存实例,这意味着不可能进行跨进程缓存。这显然也意味着本地内存缓存不是特别有效的内存,因此对于生产环境而言,它可能不是一个好选择。这对开发很好。
最后,Django附带了一个“虚拟”缓存,该缓存实际上并没有缓存-它只是实现了缓存接口而无所事事。
如果您的生产站点在各个地方都使用了重型缓存,但是在开发/测试环境中却不想缓存并且不想将代码更改为后者的特殊情况,这将非常有用。要激活虚拟缓存,设置BACKEND如下:
CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.dummy.DummyCache', }}
尽管Django开箱即用地支持许多缓存后端,但有时您可能希望使用自定义的缓存后端。要使用Django的外部缓存后端,使用Python导入路径作为 BACKEND该的CACHES设置,如下所示:
CACHES = { 'default': { 'BACKEND': 'path.to.backend', }}
如果要构建自己的后端,则可以将标准缓存后端用作参考实现。您将django/core/cache/backends/在Django源代码的目录中找到代码 。
注意:如果没有真正令人信服的理由,例如不支持它们的主机,则应坚持使用Django随附的缓存后端。他们已经过充分的测试并且有据可查。
可以为每个缓存后端提供其他参数来控制缓存行为。这些参数作为设置中的其他键提供 CACHES。有效参数如下:
在此示例中,文件系统后端的超时时间为60秒,最大容量为1000:
CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache', 'LOCATION': '/var/tmp/django_cache', 'TIMEOUT': 60, 'OPTIONS': { 'MAX_ENTRIES': 1000 } }}
这python-memcached是对象大小限制为2MB 的基于后端的示例配置:
CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', 'LOCATION': '127.0.0.1:11211', 'OPTIONS': { 'server_max_value_length': 1024 * 1024 * 2, } }}
这是pylibmc基于基础的后端的示例配置,该配置启用了二进制协议,SASL身份验证和ketama行为模式:
CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache', 'LOCATION': '127.0.0.1:11211', 'OPTIONS': { 'binary': True, 'username': 'user', 'password': 'pass', 'behaviors': { 'ketama': True, } } }}
一旦设置了缓存,使用缓存的最简单方法就是缓存整个站点。您需要将'django.middleware.cache.UpdateCacheMiddleware'和 添加 'django.middleware.cache.FetchFromCacheMiddleware'到 MIDDLEWARE设置中,如以下示例所示:
MIDDLEWARE = [ 'django.middleware.cache.UpdateCacheMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.cache.FetchFromCacheMiddleware',]
注意
不,这不是输入错误:“更新”中间件必须位于列表的第一位,“获取”中间件必须位于最后。细节有些晦涩,但是如果您想了解完整的故事,请参阅下面的MIDDLEWARE顺序。
然后,将以下必需设置添加到Django设置文件中:
FetchFromCacheMiddleware缓存状态为200的GET和HEAD响应,其中请求和响应标头允许。对具有不同查询参数的相同URL请求的响应被视为唯一页面,并分别进行缓存。该中间件期望用与相应的GET请求相同的响应头来应答HEAD请求。在这种情况下,它可以为HEAD请求返回缓存的GET响应。
此外,会UpdateCacheMiddleware自动在每个标题中设置一些标题 HttpResponse:
有关中间件的更多信息,请参见中间件。
如果视图设置了自己的缓存到期时间(即max-age,其Cache-Control标题中有一个部分),则页面将一直缓存到到期时间,而不是CACHE_MIDDLEWARE_SECONDS。使用装饰器 django.views.decorators.cache可以轻松设置视图的过期时间(使用cache_control()装饰器)或禁用视图的缓存(使用 never_cache()装饰器)。有关这些装饰器的更多信息,请参见“ 使用其他标头”部分。
如果USE_I18N设置为,True则生成的缓存键将包括活动语言的名称-另请参见 Django如何发现语言首选项。这使您可以轻松地缓存多语言站点,而不必自己创建缓存密钥。
缓存键还包括积极的语言时, USE_L10N设置为True与当前时区时USE_TZ被设置为True。
django.views.decorators.cache.
cache_page
()使用缓存框架的更精细的方法是缓存单个视图的输出。django.views.decorators.cache定义一个cache_page 装饰器,该装饰器将自动为您缓存视图的响应:
from django.views.decorators.cache import cache_page@cache_page(60 * 15)def my_view(request): ...
cache_page有一个参数:缓存超时(以秒为单位)。在上面的示例中,my_view()视图结果将被缓存15分钟。(请注意,我们出于可读性的目的编写了该代码。它将被评估为– 15分钟乘以每分钟60秒。)60 * 1560 * 15900
与每个站点的缓存一样,每个视图的缓存也是从URL键入的。如果多个URL指向同一视图,则每个URL将被分别缓存。继续该my_view示例,如果您的URLconf如下所示:
urlpatterns = [ path('foo/<int:code>/', my_view),]
然后,您所期望的/foo/1/和的请求/foo/23/将分别进行缓存。但是,一旦/foo/23/请求了特定的URL(例如),则对该URL的后续请求将使用缓存。
cache_page也可以采用可选的关键字参数,cache该参数指示装饰器CACHES在缓存视图结果时使用特定的缓存(来自您的 设置)。默认情况下, default将使用缓存,但是您可以指定所需的任何缓存:
@cache_page(60 * 15, cache="special_cache")def my_view(request): ...
您还可以基于每个视图覆盖缓存前缀。cache_page 带有一个可选的关键字参数,key_prefix其工作方式CACHE_MIDDLEWARE_KEY_PREFIX 与中间件的设置相同。可以这样使用:
@cache_page(60 * 15, key_prefix="site1")def my_view(request): ...
的key_prefix和cache参数可以被同时指定。该 key_prefix参数和KEY_PREFIX 规定下,CACHES将串联。
上一节中的示例已经硬编码了缓存视图的事实,因为该视图cache_page改变了my_view功能。这种方法将您的视图耦合到高速缓存系统,由于多种原因,这种方法并不理想。例如,您可能想在另一个无缓存的站点上重用视图功能,或者可能希望将视图分发给可能希望在不被缓存的情况下使用它们的人。这些问题的解决方案是在URLconf中指定每个视图的缓存,而不是在视图函数本身旁边。
您可以通过cache_page在URLconf中引用视图函数时将其包装在一起来实现。这是之前的旧版URLconf:
urlpatterns = [ path('foo/<int:code>/', my_view),]
这是同一件事,my_view包裹在其中cache_page:
from django.views.decorators.cache import cache_pageurlpatterns = [ path('foo/<int:code>/', cache_page(60 * 15)(my_view)),]
如果您希望获得更多控制权,则还可以使用cachetemplate标签缓存模板片段。要让您的模板访问此标签,请放在 模板顶部附近。{% load cache %}
该模板标签缓存块中的内容给定的时间量。它至少需要两个参数:高速缓存超时(以秒为单位)和提供高速缓存片段的名称。如果超时为,则片段将永远被缓存。名称将按原样使用,请勿使用变量。例如:{% cache %}None
{% load cache %}{% cache 500 sidebar %} .. sidebar ..{% endcache %}
有时,您可能希望根据片段内显示的一些动态数据来缓存片段的多个副本。例如,您可能想要为站点中的每个用户提供上一个示例中使用的侧边栏的单独的缓存副本。为此,请将一个或多个其他参数(可以是带或不带过滤器的变量)传递给模板标记,以唯一地标识缓存片段:{% cache %}
{% load cache %}{% cache 500 sidebar request.user.username %} .. sidebar for logged in user ..{% endcache %}
如果USE_I18N将设置为True每个站点,则中间件缓存将 尊重活动语言。对于cache模板标记,您可以使用模板中可用的特定于 翻译的变量之一来获得相同的结果:
{% load i18n %}{% load cache %}{% get_current_language as LANGUAGE_CODE %}{% cache 600 welcome LANGUAGE_CODE %} {% trans "Welcome to example.com" %}{% endcache %}
缓存超时可以是模板变量,只要模板变量解析为整数值即可。例如,如果将模板变量 my_timeout设置为value 600,那么以下两个示例是等效的:
{% cache 600 sidebar %} ... {% endcache %}{% cache my_timeout sidebar %} ... {% endcache %}
此功能有助于避免模板中的重复。您可以在一个位置的变量中设置超时,然后重用该值。
默认情况下,缓存标签将尝试使用名为“ template_fragments”的缓存。如果不存在这样的缓存,它将退回到使用默认缓存。您可以选择与using关键字参数一起使用的备用缓存后端,该参数必须是标记的最后一个参数。
{% cache 300 local-thing ... using="localcache" %}
指定未配置的缓存名称被视为错误。
django.core.cache.utils.
make_template_fragment_key
(fragment_name,vary_on =无)如果要获取用于缓存片段的缓存密钥,可以使用 make_template_fragment_key。fragment_name与cachetemplate标签的第二个参数相同;vary_on是传递给标记的所有其他参数的列表。该功能对于使缓存项无效或覆盖很有用,例如:
>>> from django.core.cache import cache>>> from django.core.cache.utils import make_template_fragment_key# cache key for {% cache 500 sidebar username %}>>> key = make_template_fragment_key('sidebar', [username])>>> cache.delete(key) # invalidates cached template fragment
有时,缓存整个渲染的页面并不会带来太多好处,实际上,这会带来不便。
例如,也许您的站点包含一个视图,该视图的结果取决于几个昂贵的查询,这些查询的结果以不同的间隔更改。在这种情况下,使用每个站点或每个视图缓存策略提供的全页缓存并不理想,因为您不想缓存整个结果(因为某些数据经常更改),但您仍想缓存很少更改的结果。
对于此类情况,Django会公开一个低级缓存API。您可以使用此API以所需的任意粒度级别将对象存储在缓存中。您可以缓存可以安全腌制的任何Python对象:字符串,字典,模型对象列表等。(可以对大多数常见的Python对象进行腌制;有关腌制的更多信息,请参考Python文档。)
django.core.cache.
caches
您可以CACHES
通过类似dict的对象访问在设置中配置的缓存:django.core.cache.caches
。在同一线程中重复请求相同的别名将返回相同的对象。
>>> from django.core.cache import caches>>> cache1 = caches['myalias']>>> cache2 = caches['myalias']>>> cache1 is cache2True
如果指定的键不存在,InvalidCacheBackendError
将引发。
为了提供线程安全,将为每个线程返回不同的缓存后端实例。
django.core.cache.
cache
作为一种快捷方式,默认高速缓存可用于 django.core.cache.cache
:
>>> from django.core.cache import cache
此对象等效于caches['default']
。
基本界面是:
cache.
set
(key,value,timeout = DEFAULT_TIMEOUT,version = None)>>> cache.set('my_key', 'hello, world!', 30)
cache.
get
(key,default = None,version = None)>>> cache.get('my_key')'hello, world!'
key应该是str,并且value可以是任何可挑选的Python对象。
该timeout参数是可选的,并且默认为设置中timeout相应后端的参数CACHES(如上所述)。这是该值应存储在缓存中的秒数。传递 Nonefor timeout将永远缓存该值。一个timeout的0 将不缓存值。
如果对象在缓存中不存在,则cache.get()返回None:
>>> # Wait 30 seconds for 'my_key' to expire...>>> cache.get('my_key')None
我们建议不要将文字值存储None在缓存中,因为您将无法区分存储的None值和返回值为的缓存未命中None。
cache.get()可以default争论。这指定了如果对象在缓存中不存在则返回哪个值:
>>> cache.get('my_key', 'has expired')'has expired'
cache.
add
(key,value,timeout = DEFAULT_TIMEOUT,version = None)若要仅在密钥尚不存在时添加密钥,请使用add()方法。它使用与相同的参数set(),但是如果指定的键已经存在,它将不会尝试更新缓存:
>>> cache.set('add_key', 'Initial value')>>> cache.add('add_key', 'New value')>>> cache.get('add_key')'Initial value'
如果您需要知道是否add()在缓存中存储了值,则可以检查返回值。True如果存储了值,它将返回, False否则返回。
cache.
get_or_set
(key,default,timeout = DEFAULT_TIMEOUT,version = None)如果要获取键的值,或者如果键不在缓存中则要设置值,则可以使用该get_or_set()方法。它使用与相同的参数,get() 但默认设置为该键的新缓存值,而不是返回:
>>> cache.get('my_new_key') # returns None>>> cache.get_or_set('my_new_key', 'my new value', 100)'my new value'
您还可以将任何callable作为默认值传递:
>>> import datetime>>> cache.get_or_set('some-timestamp-key', datetime.datetime.now)datetime.datetime(2014, 12, 11, 0, 15, 49, 457920)
cache.
get_many
(keys,version = None)还有一个get_many()接口只命中一次缓存。 get_many()返回一个字典,其中包含您要求的所有键,这些键实际上已存在于缓存中(并且尚未过期):
>>> cache.set('a', 1)>>> cache.set('b', 2)>>> cache.set('c', 3)>>> cache.get_many(['a', 'b', 'c']){'a': 1, 'b': 2, 'c': 3}
cache.
set_many
(dict,超时)要更有效地设置多个值,请使用set_many()传递键值对字典:
>>> cache.set_many({'a': 1, 'b': 2, 'c': 3})>>> cache.get_many(['a', 'b', 'c']){'a': 1, 'b': 2, 'c': 3}
像一样cache.set(),set_many()带有一个可选timeout参数。
在支持的后端(memcached)上,set_many()返回未能插入的密钥列表。
cache.
delete
(key,version = None)您可以显式删除键,delete()以清除特定对象的缓存:
>>> cache.delete('a')
cache.
delete_many
(keys,version = None)如果要一次清除一堆键,delete_many()可以列出要清除的键列表:
>>> cache.delete_many(['a', 'b', 'c'])
cache.
clear
()最后,如果要删除缓存中的所有键,请使用 cache.clear()。注意这一点。clear()将从 缓存中删除所有内容,而不仅仅是您的应用程序设置的键。
>>> cache.clear()
cache.
touch
(key,timeout = DEFAULT_TIMEOUT,version = None)cache.touch()为密钥设置新的到期时间。例如,要将密钥更新为从现在起10秒钟过期:
>>> cache.touch('a', 10)True
与其他方法一样,该timeout参数是可选的,并且默认为设置中TIMEOUT相应后端的 选项CACHES。
touch()True如果按键被成功触摸,False 则返回;否则返回。
cache.
incr
(key,delta = 1,version = None)cache.
decr
(key,delta = 1,version = None)您也可以分别使用incr()或decr()方法递增或递减已存在的键 。默认情况下,现有的高速缓存值将递增或递减1。可以通过为递增/递减调用提供参数来指定其他递增/递减值。如果您尝试增加或减少不存在的缓存键,将引发ValueError:
>>> cache.set('num', 1)>>> cache.incr('num')2>>> cache.incr('num', 10)12>>> cache.decr('num')11>>> cache.decr('num', 5)6
注意:incr()/ decr()方法不保证是原子的。在那些支持原子增量/减量的后端(最值得注意的是,内存缓存的后端)上,增量和减量操作将是原子的。但是,如果后端本身不提供增量/减量操作,则将使用两步检索/更新来实现。
cache.
close
()close()如果由缓存后端实现,则可以关闭与缓存的连接。
>>> cache.close()
注意:对于不实现close方法的缓存,它是无操作的。
如果要在服务器之间或生产环境与开发环境之间共享缓存实例,则一台服务器缓存的数据可能会被另一台服务器使用。如果服务器之间的缓存数据格式不同,则可能导致某些很难诊断的问题。
为防止这种情况,Django提供了为服务器使用的所有缓存键添加前缀的功能。保存或检索特定的缓存键后,Django将自动为缓存键添加KEY_PREFIX缓存设置的值 。
通过确保每个Django实例都有一个不同的 KEY_PREFIX,您可以确保缓存值不会发生冲突。
更改使用缓存值的运行代码时,可能需要清除所有现有的缓存值。最简单的方法是刷新整个缓存,但这会导致仍然有效且有用的缓存值丢失。
Django提供了一种更好的方法来定位各个缓存值。Django的缓存框架具有系统范围的版本标识符,该标识符使用VERSION缓存设置指定。此设置的值会自动与缓存前缀和用户提供的缓存键组合在一起,以获取最终的缓存键。
默认情况下,任何密钥请求都将自动包含站点默认的缓存密钥版本。但是,原始缓存功能都包含一个version参数,因此您可以指定要设置或获取的特定缓存键版本。例如:
>>> # Set version 2 of a cache key>>> cache.set('my_key', 'hello world!', version=2)>>> # Get the default version (assuming version=1)>>> cache.get('my_key')None>>> # Get version 2 of the same key>>> cache.get('my_key', version=2)'hello world!'
可以使用incr_version()和decr_version()方法对特定键的版本进行递增和递减。这样可以使特定的键更改为新版本,而其他键不受影响。继续前面的示例:
>>> # Increment the version of 'my_key'>>> cache.incr_version('my_key')>>> # The default version still isn't available>>> cache.get('my_key')None# Version 2 isn't available, either>>> cache.get('my_key', version=2)None>>> # But version 3 *is* available>>> cache.get('my_key', version=3)'hello world!'
如前两节所述,用户不能完全原样使用用户提供的缓存密钥,而是将其与缓存前缀和密钥版本结合使用以提供最终的缓存密钥。默认情况下,这三个部分使用冒号连接起来以生成最终字符串:
def make_key(key, key_prefix, version): return '%s:%s:%s' % (key_prefix, version, key)
如果要以不同的方式组合各部分,或对最终键进行其他处理(例如,获取键部分的哈希摘要),则可以提供自定义键功能。
的KEY_FUNCTION高速缓存设置指定匹配的原型的功能的虚线路径 make_key()的上方。如果提供,将使用此自定义按键功能代替默认的按键组合功能。
Memcached是最常用的生产缓存后端,它不允许长度超过250个字符或包含空格或控制字符的缓存键,并且使用此类键会导致异常。为了鼓励缓存可移植的代码并最大程度地减少令人不快的意外,django.core.cache.backends.base.CacheKeyWarning如果使用的键会导致memcached错误,则其他内置的缓存后端会发出警告()。
如果您使用的生产后端可以接受更广泛的键范围(自定义后端或非内存缓存的内置后端之一),并且希望在没有警告的情况下使用更大范围的键,则可以CacheKeyWarning在此代码中保持静音management您之一的模块 INSTALLED_APPS:
import warningsfrom django.core.cache import CacheKeyWarningwarnings.simplefilter("ignore", CacheKeyWarning)
如果您想为内置后端之一提供自定义密钥验证逻辑,则可以对其进行子类化,仅覆盖validate_key 方法,并按照说明使用自定义缓存后端。例如,要为locmem后端执行此操作,请将以下代码放在模块中:
from django.core.cache.backends.locmem import LocMemCacheclass CustomLocMemCache(LocMemCache): def validate_key(self, key): """Custom validation, raising exceptions or warnings as needed.""" ...
…并在设置的BACKEND一部分中使用指向该类的虚线Python路径 CACHES。
到目前为止,本文档的重点是缓存您自己的数据。但是另一种类型的缓存也与Web开发有关:由“下游”缓存执行的缓存。这些系统甚至可以在请求到达您的网站之前为用户缓存页面。
以下是下游缓存的一些示例:
下游缓存可以极大地提高效率,但是却存在危险:许多网页的内容基于身份验证和许多其他变量而有所不同,并且仅基于URL盲目保存页面的缓存系统可能会将不正确或敏感的数据暴露给后续这些页面的访问者。
例如,如果您使用Web电子邮件系统,则“收件箱”页面的内容取决于登录的用户。如果ISP盲目缓存了您的站点,则第一个通过该ISP登录的用户将拥有其用户。特定的收件箱页面已缓存,以供该站点的后续访问者使用。那不酷。
幸运的是,HTTP提供了解决此问题的方法。存在许多HTTP标头,以指示下游缓存根据指定的变量来区分其缓存内容,并告知缓存机制不要缓存特定页面。我们将在以下各节中介绍其中的一些标题。
的Vary报头定义哪些请求头的高速缓存机制建立其缓存键时应该考虑到。例如,如果网页的内容取决于用户的语言偏好,则称该页面“随语言而异”。
默认情况下,Django的缓存系统使用请求的标准URL(例如,)创建其缓存密钥 "https://www.example.com/stories/2005/?order_by=author"。这意味着对该URL的每个请求都将使用相同的缓存版本,而不管用户代理差异(例如Cookie或语言偏好设置)如何。但是,如果此页面根据请求标头(例如Cookie,语言或用户代理)的不同而产生不同的内容,则需要使用Vary 标头来告诉缓存机制页面输出取决于那些的东西。
要在Django中执行此操作,请使用方便的 django.views.decorators.vary.vary_on_headers()视图装饰器,如下所示:
from django.views.decorators.vary import vary_on_headers@vary_on_headers('User-Agent')def my_view(request): ...
在这种情况下,缓存机制(例如Django自己的缓存中间件)将为每个唯一的用户代理缓存页面的单独版本。
使用vary_on_headers装饰器而不是手动设置Vary标头(使用类似的东西 )的好处是,装饰器将添加到 标头(可能已经存在),而不是从头开始设置它并可能覆盖其中的任何内容。response['Vary'] = 'user-agent'Vary
您可以将多个标头传递给vary_on_headers():
@vary_on_headers('User-Agent', 'Cookie')def my_view(request): ...
这告诉下游缓存在这两者上有所不同,这意味着用户代理和cookie的每种组合都将获得自己的缓存值。例如,具有用户代理Mozilla和cookie值foo=bar的请求将被认为不同于具有用户代理Mozilla和cookie值 的请求foo=ham。
因为在cookie上进行更改非常普遍,所以有一个 django.views.decorators.vary.vary_on_cookie()装饰器。这两个视图是等效的:
@vary_on_cookiedef my_view(request): ...@vary_on_headers('Cookie')def my_view(request): ...
您传递给的标头vary_on_headers不区分大小写; "User-Agent"和一样"user-agent"。
您也可以django.utils.cache.patch_vary_headers()直接使用辅助功能。此函数设置或添加到。例如:Vary header
from django.shortcuts import renderfrom django.utils.cache import patch_vary_headersdef my_view(request): ... response = render(request, 'template_name', context) patch_vary_headers(response, ['Cookie']) return response
patch_vary_headers将HttpResponse实例作为第一个参数,并将不区分大小写的标头名称的列表/元组作为第二个参数。
有关Vary标头的更多信息,请参见 官方Vary规格。
缓存的其他问题是数据的私密性以及应在级联缓存中存储数据的位置的问题。
用户通常面临两种缓存:他们自己的浏览器缓存(私有缓存)和提供者的缓存(公共缓存)。公共缓存由多个用户使用,并由其他人控制。这就给敏感数据带来了麻烦,例如,您不希望将银行帐号存储在公共缓存中。因此,Web应用程序需要一种方法来告诉缓存哪些数据是私有数据,哪些是公共数据。
解决方案是指示页面的缓存应为“专用”。要在Django中执行此操作,请使用cache_control()视图装饰器。例:
from django.views.decorators.cache import cache_control@cache_control(private=True)def my_view(request): ...
该装饰器负责在后台发送适当的HTTP标头。
请注意,缓存控制设置“专用”和“公用”是互斥的。装饰器确保如果应设置为“ private”,则删除“ public”指令(反之亦然)。这两个伪指令的示例用法是提供私有条目和公共条目的博客网站。公共条目可以缓存在任何共享缓存中。以下代码使用 patch_cache_control()手动方式修改缓存控制标头(由cache_control()装饰器内部调用 ):
from django.views.decorators.cache import patch_cache_controlfrom django.views.decorators.vary import vary_on_cookie@vary_on_cookiedef list_blog_entries_view(request): if request.user.is_anonymous: response = render_only_public_entries() patch_cache_control(response, public=True) else: response = render_private_and_public_entries(request.user) patch_cache_control(response, private=True) return response
您还可以通过其他方式控制下游缓存(请参见 RFC 7234,了解有关HTTP缓存的详细信息)。例如,即使您不使用Django的服务器端缓存框架,您仍然可以使用以下命令告诉客户端将视图缓存一定的时间:最大年龄 指令:
from django.views.decorators.cache import cache_control@cache_control(max_age=3600)def my_view(request): ...
(如果你做使用缓存中间件,它已经设置max-age与值CACHE_MIDDLEWARE_SECONDS的设置。在这种情况下,自定义max_age从 cache_control()装饰将优先考虑,并且头部值将被正确地合并。)
任何有效的Cache-Control响应指令在中均有效cache_control()。这里还有更多示例:
可以在IANA注册中心中找到已知指令的完整列表 (请注意,并非所有指令都适用于响应)。
如果要使用标头来完全禁用缓存,请 never_cache()使用视图装饰器来添加标头,以确保浏览器或其他缓存不会缓存响应。例:
from django.views.decorators.cache import never_cache@never_cachedef myview(request): ...
如果您使用缓存中间件,则将每一半放在MIDDLEWARE设置中的正确位置上很重要。这是因为缓存中间件需要知道通过哪些头来更改缓存存储。中间件总是尽可能在Vary响应头中添加一些内容。
UpdateCacheMiddleware在响应阶段运行,中间件以相反的顺序运行,因此列表顶部的项目在响应阶段最后运行。因此,您需要确保它UpdateCacheMiddleware 出现在任何其他可能向Vary 标头添加内容的中间件之前。以下中间件模块可以这样做:
FetchFromCacheMiddleware另一方面,在请求阶段运行,其中中间件首先应用,因此列表顶部的项目在请求阶段首先运行。该FetchFromCacheMiddleware还需要经过其他中间件更新运行Vary头,所以 FetchFromCacheMiddleware必须在后的是这样做的任何项目。
详情参考: https://docs.djangoproject.com/en/3.0/
通常一个人首先关心的是编写代码的作品,它的逻辑功能根据需要产生预期的输出。但是,有时这还不足以使代码高效地工作。
在这种情况下,需要的是某种东西-实际上,通常是一系列东西-可以提高代码的性能,而又不会,或者仅以最小的方式影响代码的行为。
清楚了解“性能”的含义很重要。不仅有一个指标。
提高速度可能是程序最明显的目标,但有时可能会寻求其他性能改进,例如降低内存消耗或减少对数据库或网络的需求。
一个方面的改进通常会带来另一方面的改进,但并非总是如此;有时一个人甚至可以牺牲另一个人。例如,程序速度的提高可能会导致它使用更多的内存。更糟糕的是,这可能是自欺欺人的-如果速度提升如此之耗内存,以至于系统开始耗尽内存,那么您的弊大于利。
还有其他需要权衡的方面。您自己的时间是宝贵的资源,比CPU时间更宝贵。有些改进可能太难了,不值得实施,或者可能影响代码的可移植性或可维护性。并非所有的性能改进都值得付出努力。
因此,您需要知道要实现哪些性能改进,并且还需要知道有充分的理由朝着这个方向瞄准-并且您需要:
仅仅猜测或假设效率低下在代码中是没有用的。
django-debug-toolbar是一个非常方便的工具,可以深入了解您的代码在做什么以及花了多少时间。特别是,它可以显示页面生成的所有SQL查询以及每个查询花费了多长时间。
工具栏也可以使用第三方面板,该面板可以(例如)报告缓存性能和模板渲染时间。
有许多免费服务可以从远程HTTP客户端的角度分析和报告站点页面的性能,实际上是在模拟实际用户的体验。
这些无法报告您的代码内部,但可以提供有用的洞察力来了解您网站的整体性能,包括无法在Django环境中充分衡量的方面。示例包括:
还有一些付费服务执行类似的分析,其中包括一些支持Django的服务,这些服务可以与您的代码库集成以更全面地分析其性能。
优化方面的某些工作涉及解决性能缺陷,但是某些工作也可以内置于您要做的事情中,这是您甚至在开始考虑提高性能之前就应该采用的良好实践的一部分。
在这方面,Python是一种出色的语言,因为外观优美且感觉正确的解决方案通常是性能最好的解决方案。与大多数技能一样,学习“看起来正确”的东西需要练习,但是最有用的准则之一是:
Django提供了许多不同的处理方式,但是仅仅因为可以以某种方式做某事并不意味着这是最合适的方式。例如,您可能会发现您可以在QuerySet,Python或模板中计算相同的东西-集合中的项目数,也许 。
但是,在较低级别而不是较高级别进行此工作几乎总是会更快。在更高的层次上,系统必须通过多层抽象和机器层次来处理对象。
也就是说,数据库通常可以比Python更快地完成任务,而Python可以比模板语言更快地完成任务:
# QuerySet operation on the database# fast, because that's what databases are good atmy_bicycles.count()# counting Python objects# slower, because it requires a database query anyway, and processing# of the Python objectslen(my_bicycles)# Django template filter# slower still, because it will have to count them in Python anyway,# and because of template language overheads{{ my_bicycles|length }}
一般而言,最适合该工作的级别是适合编写代码的最低级别。
注意
上面的示例仅是说明性的。
首先,在现实生活中,您需要考虑计数前后发生的事情,以找出在特定情况下执行此操作的最佳方法。数据库优化文档描述了一种情况,在这种情况下,模板中的计数会更好。
其次,还有其他选择要考虑:在实际情况下,直接从模板调用方法可能是最合适的选择。{{ my_bicycles.count }}QuerySet count()
通常,计算值很昂贵(即耗费资源且速度很慢),因此将值保存到可快速访问的缓存中以备下次使用时会产生巨大的好处。
Django具有完善的缓存框架以及其他较小的缓存功能,这是一项足够重要且功能强大的技术。
Django的缓存框架通过保存动态内容,因此无需为每个请求进行计算,就为提高性能提供了非常重要的机会。
为了方便起见,Django提供了不同级别的缓存粒度:您可以缓存特定视图的输出,或者仅缓存难以生成的片段,甚至整个站点。
实施缓存不应被视为改善性能不佳的代码的替代方法,因为它的编写质量很差。这是生成性能良好的代码的最后步骤之一,而不是捷径。
通常必须多次调用一个类实例的方法。如果该功能很昂贵,那么这样做会很浪费。
使用cached_property装饰器可以保存属性返回的值。下次在该实例上调用该函数时,它将返回保存的值,而不是重新计算它。请注意,这仅适用于将self参数作为唯一参数的方法,并将方法更改为属性。
某些Django组件也具有自己的缓存功能;这些将在下面与那些组件相关的部分中讨论。
懒惰是对缓存的一种补充策略。缓存通过保存结果来避免重新计算。懒惰会延迟计算,直到真正需要它为止。
惰性允许我们在实例化它们之前,甚至在可能实例化它们之前都引用它们。这有许多用途。
例如,可以在甚至不知道目标语言之前就使用惰性翻译,因为直到真正需要翻译后的字符串(例如在渲染的模板中)时,它才发生。
懒惰也是一种通过首先避免工作来节省精力的方法。就是说,懒惰的一个方面是什么也要做,直到必须要做,因为毕竟可能没有必要。因此,懒惰可能会影响性能,并且相关工作的成本越高,通过懒惰获得的收益就越大。
Python提供了许多用于懒惰求值的工具,尤其是通过 生成器和生成器表达式构造。值得阅读Python的惰性,以发现在代码中使用惰性模式的机会。
Django本身很懒。在的评估中可以找到一个很好的例子QuerySets。QuerySet是惰性的。因此,QuerySet可以创建,传递和与其他对象组合使用 QuerySets,而无需实际进行任何数据库访问以获取其描述的项目。传递的是QuerySet对象,而不是数据库最终需要的项目集合。
另一方面,某些操作将强制评估QuerySet。避免过早评估a QuerySet可以节省对数据库的昂贵而不必要的行程。
Django还提供了一个keep_lazy()装饰器。这允许使用惰性参数调用的函数本身表现为惰性,仅在需要时才进行评估。因此,在严格要求之前,不会调用惰性参数(可能是昂贵的参数)进行评估。
Django的数据库层提供了多种方法来帮助开发人员从其数据库中获得最佳性能。该数据库优化文档汇聚链接到相关文件,并增加了各种技巧,大纲的步骤尝试优化数据库使用情况时服用。
启用持久连接可以在大部分请求处理时间中加快与数据库帐户的连接。
例如,这对网络性能有限的虚拟主机有很大帮助。
Django随附了一些有用的中间件 ,可以帮助您优化网站的性能。它们包括:
添加了对现代浏览器的支持,以基于ETag和Last-Modified标头有条件地获取响应 。如果需要,它还会计算并设置一个ETag。
压缩所有现代浏览器的响应,节省带宽和传输时间。请注意,当前将GZipMiddleware视为安全风险,并且容易受到使TLS / SSL提供的保护无效的攻击的攻击。有关GZipMiddleware更多信息,请参阅警告。
使用缓存的会话可能是一种通过避免从较慢的存储源(如数据库)中加载会话数据,而将频繁使用的会话数据存储在内存中来提高性能的方法。
静态文件(根据定义不是动态的)是实现优化收益的绝佳目标。
通过利用Web浏览器的缓存功能,您可以在初始下载后完全消除给定文件的网络命中。
ManifestStaticFilesStorage在静态文件的文件名后附加一个与内容相关的标记,以使浏览器可以安全地长期缓存它们,而不会丢失将来的更改-文件更改时,标记也将更改,因此浏览器将自动重新加载资产。
几个第三方Django工具和软件包提供了“最小化” HTML,CSS和JavaScript的功能。它们删除了不必要的空格,换行符和注释,并缩短了变量名,从而减小了站点发布的文档的大小。
注意:
启用通常可以大大提高性能,因为它避免了每次需要渲染每个模板时就对每个模板进行编译。cached template loader
有时值得检查您所使用软件的不同版本和性能更好的版本。
这些技术面向希望突破已经充分优化的Django站点的性能极限的更高级的用户。
但是,它们并不是解决性能问题的灵丹妙药,它们不可能为尚未以正确方式做更多基本工作的网站带来比边缘收益更好的收益。
注意
值得重复一遍:寻找已经使用的软件的替代品永远不是解决性能问题的第一个答案。当达到此优化级别时,您需要一个正式的基准测试解决方案。
新发行的维护良好的软件效率较低的情况相当少见,但是维护人员无法预见所有可能的用例-因此,尽管意识到较新版本的性能可能会更好,但不要以为它们总是将。
Django本身就是这样。后续版本在整个系统上提供了许多改进,但是您仍然应该检查应用程序的实际性能,因为在某些情况下,您可能会发现更改意味着性能较差而不是更好。
较新版本的Python以及Python包也通常会表现更好-但要衡量而不是假设。
注意
除非您在特定版本中遇到不寻常的性能问题,否则通常会在新版本中找到更好的功能,可靠性和安全性,并且这些好处远比您可能会赢得或失去的任何性能都重要。
在几乎所有情况下,Django的内置模板语言都足够了。但是,如果Django项目中的瓶颈似乎在模板系统中,而您又花了其他机会来解决此问题,那么第三方替代方法可能是答案。
Jinja2可以提高性能,特别是在速度方面。
替代模板系统在共享Django模板语言的程度上有所不同。
注意:如果您在模板中遇到性能问题,则要做的第一件事就是确切地了解原因。使用备用模板系统可能会证明更快,但是在不造成麻烦的情况下也可以获得相同的收益-例如,可以在视图中更有效地完成模板中的昂贵处理和逻辑。
可能值得检查您所使用的Python软件是否已以不同的实现提供,该实现可以更快地执行相同的代码。
但是:在编写良好的Django站点中,大多数性能问题不是在Python执行级别上,而是在效率低下的数据库查询,缓存和模板方面。如果您依赖编写不佳的Python代码,则无法通过更快地执行来解决性能问题。
使用替代实现可能会引入兼容性,部署,可移植性或维护问题。不用说,在采用非标准实现之前,您应确保它为您的应用程序提供了足够的性能提升,从而胜过了潜在的风险。
考虑到这些警告,您应该意识到:
PyPy是Python本身的Python实现(“标准” Python实现在C中)。PyPy通常可用于重量级应用程序,因此可显着提高性能。
PyPy项目的主要目标是与现有的Python API和库兼容。Django是兼容的,但您需要检查您依赖的其他库的兼容性。
一些Python库也用C实现,并且速度可能更快。他们旨在提供相同的API。请注意,兼容性问题和行为差异并不是未知的(并且并不总是立即可见)。
详情参考: https://docs.djangoproject.com/en/3.0/