更新记录
2016-04-04 初稿
2016-06-30 更新内容
模型
限制普通字段的选择范围choices
,value-text,
显示get_foo_display()
模型继承
- 抽象基类
- 默认继承元类
- 多表继承
- 一般情况不继承元类
- 代理模型
多重继承主要用于mix-in
类
多表继承时使用parent_link=True
显示指定OneToOne字段
模型字段参考
与尚未定义的模型关联使用模型名字(字符串)而非本身(类)
关联自己使用self
related_name
relate_query_name
外键关联到特定字段to_field
限制外键的选择范围(可以是一个字典、一个Q 对象或者一个返回字典或Q对象的可调用对象)limit_choices_to
外键关联对象删除行为on_delete
1.8以后保存模型时,未保存的外键对象将被忽略,除非设置allow_unsaved_instance_assignment=True
关联自身的多对多关系默认对称,取消对称设置symmetrical=False
ImageField
中的height_field
和width_field
是用来存储存入图片的高度和宽度值的
##执行查询
可自定义查询(高级查找)exclude
多条件查询时是用or关系而不是and关系
F()
用于模型内部字段间的比较支持加法、减法、乘法、除法、取模以及幂计算等算术操作
支持.bitand() 和.bitor()位操作,update()
也可以使用F()
但有限制(在update 中你不可以使用F() 对象引入join —— 你只可以引用正在更新的模型的字段)
查询集缓存
当只对查询集的部分进行求值时会检查缓存, 但是如果这个部分不在缓存中,那么接下来查询返回的记录都将不会被缓存。1
2
3 queryset = Entry.objects.all()
for p in queryset]) # Evaluate the query set. print([p.headline
for p in queryset]) # Re-use the cache from the evaluation. print([p.pub_date
1 | queryset = Entry.objects.all() |
1 | >>> queryset = Entry.objects.all() |
Q()
可使用Q对象进行复杂查询
判断两相模型实例是否相同,直接使用==
比较即可
默认批量删除对象时不会调用实例的delete
方法
拷贝实例,把pk
设置为None
再save
即可(如果是继承的,则pk
和id
都需要设置为None
)
update()
方法也不会调用模型的save()
方法,不会发出pre_save
和post_save
信号,字段的auto_now
也不会起作用
一对多关联对象访问会缓存1
2
32) e = Entry.objects.get(id=
# Hits the database to retrieve the associated Blog. print(e.blog)
# Doesn't hit the database; uses cached version. print(e.blog)
自定义反向管理器1.7+1
2
3
4
5
6
7
8
9from django.db import models
class Entry(models.Model):
#...
objects = models.Manager() # Default Manager
entries = EntryManager() # Custom Manager
b = Blog.objects.get(id=1)
b.entry_set(manager='entries').all()
查询集API 参考
聚合
一次创建多条数据(只有一条sql)bulk_create
根据提供的一组pk
查询出所有对应的对象in_bulk
在查作者列表时要查每个作者有几篇博文1
2
3
4
5
6
7
8
9from django.db.models import Count
'blog')) authors = Author.object.all().annotate(Count(
# authors[0]作者的博文数
0].blog__count authors[
3
# 或
'blog')) authors = Author.object.all().annotate(number_of_blog=Count(
0].number_of_blog authors[
3
算出所有作者的年龄总合(不需要其它数据)1
2'age')) ageAuthor.objects.all().aggregate(Sum(
{'age__sum': 26}
annotate
和aggregate
都可写入多个注解表达式annotate
和aggregate
可聚合关联对象
对注解进行过滤1
2
3# 查询出作者数大于1的书本
# 只有一条sql
Book.objects.annotate(num_authors=Count('authors')).filter(num_authors__gt=1)
但顺序不一样,结果也不同,如:1
2Publisher.objects.annotate(num_books=Count('book')).filter(book__rating__gt=3.0)
Publisher.objects.filter(book__rating__gt=3.0).annotate(num_books=Count('book'))
对注解项进行排序1
Book.objects.annotate(num_authors=Count('authors')).order_by('num_authors')
values()
使用注解时要小心,如果values()
在注解之前,会对结果进行分组,注解会作用在分组上而不是整个查询集上
与默认排序交换或order_by()¶
在查询集中的order_by() 部分(或是在模型中默认定义的排序项) 会在选择输出数据时被用到,即使这些字段没有在 values() 调用中被指定。这些额外的字段可以将相似的数据行分在一起,也可以让相同的数据行相分离。在做计数时, 就会表现地格外明显:
通过例子中的方法,假设有一个这样的模型:
1 | from django.db import models |
关键的部分就是在模型默认排序项中设置的name字段。如果你想知道每个非重复的data值出现的次数,可以这样写:
1 | # Warning: not quite correct! |
…这部分代码想通过使用它们公共的 data 值来分组 Item对象,然后在每个分组中得到 id 值的总数。但是上面那样做是行不通的。这是因为默认排序项中的 name也是一个分组项,所以这个查询会根据非重复的 (data, name) 进行分组,而这并不是你本来想要的结果。所以,你应该这样改写:
1 | Item.objects.values("data").annotate(Count("id")).order_by() |
…这样就清空了查询中的所有排序项。 你也可以在其中使用 data ,这样并不会有副作用,这是因为查询分组中只有这么一个角色了。
这个行为与查询集文档中提到的 distinct() 一样,而且生成规则也一样:一般情况下,你不想在结果中由额外的字段扮演这个角色,那就清空排序项,或是至少保证它仅能访问 values()中的字段。
静态文件
http://python.usyiyi.cn/django/intro/tutorial06.html
http://python.usyiyi.cn/django/ref/templates/builtins.html
1 | {% load static %} |
还有get_media_prefix
模型实例参考
从数据库中重新加载值Model.refresh_from_db(using=None, fields=None, **kwargs)
返回模型中当前所有延迟字段的属性名称Model.get_deferred_fields()
验证对象
字段的基本验证会最先跑,但不管前面运行是否通过,对于每个字段,如果Field.clean() 方法抛出 ValidationError,那么将不会调用该字段对应的clean_
()方法。 但是,剩余的字段的验证方法仍然会执行。
先跑form
里验证,再跑modle
验证
先跑验证器,再跑clean
先跑单个字段验证,再跑整体验证Model.clean_field()
会覆盖Model
里所有字段的验证器,但不会对Form
里的验证器产生影响
验证模型的字段Model.clean_fields(exclude=None)
验证模型的完整性Model.clean()
验证模型的唯一性Model.validate_unique(exclude=None)
调用full_clean()
时,上面三个方法都会执行(执行顺序即上面的书写顺序),ModelForm
的is_valid()
也会执行上所有验证Model.full_clean(exclude=None, validate_unique=True)
save()
时,full_clean()
不会被调用,如果想验证数据,可手动调用
Model.clean()
时,引发特定字段的异常
使用一个字典实例化ValidationError
即可或使用add_error(field, msg)
方法
在数据库字段值的基础上进行简单的算法操作,应该尽量使用F()
表达式,避免问题竞态条件
指定要保存的字段
如果传递给save() 的update_fields 关键字参数一个字段名称列表,那么将只有该列表中的字段会被更新。如果你想更新对象的一个或几个字段,这可能是你想要的。不让模型的所有字段都更新将会带来一些轻微的性能提升。例如:
1 | product.name = 'Name changed again' |
update_fields
参数可以是任何包含字符串的可迭代对象。空的update_fields
可迭代对象将会忽略保存。如果为None
值,将执行所有字段上的更新。
指定
update_fields
将强制使用更新操作。
当保存通过延迟模型加载(
only()
或defer()
)进行访问的模型时,只有从数据库中加载的字段才会得到更新。这种情况下,有个自动的update_fields
。如果你赋值或者改变延迟字段的值,该字段将会添加到更新的字段中。
new in 1.9
使用Model.delete()
删除多表继承的子表数据时,使用keep_parents=True
可以保留上级数据,默认为
False`
返回值为删除数据的条数
DateField
和DateTimeField
字段如果null=False
则支持下面两个方法1
2Model.get_next_by_FOO(**kwargs)¶
Model.get_previous_by_FOO(**kwargs)
管理器
django遇到的第一个管理器为默认管理器
如果需要访问关联对象调用关联对象的默认管理器,需要在管理器中加use_for_related_fields=True
,不然会调用朴素管理器1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32# -*- coding: utf-8 -*-
from django.db import models
class DefaultManager(models.Manager):
def get_queryset(self):
queryset = super(DefaultManager, self).get_quertset().filter(is_delete=False)
return queryset
class Author(models.Model):
name = models.CharField(max_length=100)
is_delete = models.BooleanField()
objects = DefaultManager()
class Post(models.Model):
author = models.ForeignKey(Author)
title = models.CharField(max_length=100)
content = models.TextField()
is_delete = models.BooleanField()
objects = DefaultManager()
author = Author.objects.get(pk=1)
post = Post.objects.get(pk=2)
# 调用DefaultManager管理器
author.post_set.all()
# 调用朴素管理器,如果要调用DefaultManager管理器,需要设置DefaultManager管理器的类变量use_for_related_fields=True
post.author
注:朴素管理器里找不到的方法会在默认管理器里查找
进行原始的SQL查询
1 | Manager.raw(raw_query, params=None, translations=None) |
django.db.connection对象提供了常规数据库连接的方式。为了使用数据库连接,先要调用connection.cursor()方法来获取一个游标对象之后,调用cursor.execute(sql, [params])来执行sql语句,调用cursor.fetchone()或者cursor.fetchall()来返回结果行。
数据库事务
详细笔记见django1.8事务.md
将每个HTTP请求封装在一个数据库事务中
settings中设置ATOMIC_REQUESTS=True
单独给一个方法加上数据库事务控制使用atomic
1
2
3
4
5
6from django.db import transaction
def viewfunc(request):
# This code executes inside a transaction.
do_stuff()
或1
2
3
4
5
6
7
8
9from django.db import transaction
def viewfunc(request):
# This code executes in autocommit mode (Django's default).
do_stuff()
with transaction.atomic():
# This code executes inside a transaction.
do_more_stuff()
避免在 atomic里捕获异常!
查询表达式
使用数据库的方法1
2from django.db.models import Func, F
queryset.annotate(field_lower=Func(F('field'), function='LOWER'))
或1
2
3class Lower(Func):
function = 'LOWER'
queryset.annotate(field_lower=Lower(F('field')))
条件表达式
高级用法查看在线版When
Case
数据库函数
Coalesce
接收一个含有至少两个字段名称或表达式的列表,返回第一个非空的值(空字符串不认为是一个空值)
将遗留数据库整合到Django
根据遗留数据库生成models1
python manage.py inspectdb > models.py
为模型提供初始数据
使用fixtures1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18[
{
"model": "myapp.person",
"pk": 1,
"fields": {
"first_name": "John",
"last_name": "Lennon"
}
},
{
"model": "myapp.person",
"pk": 2,
"fields": {
"first_name": "Paul",
"last_name": "McCartney"
}
}
]
导入数据命令1
python manage.py loaddata <fixturename>
数据库访问优化
添加索引,比任何查询语法优化都来的重要
理解查询集
QuerySets是延迟的。
什么时候它们被计算出来。
数据在内存中如何存储。
使用cached_property
装饰器,只要是同一个实例,一个方法就只会执行一次
使用with
模版标签
使用iterator
迭代器
在数据库中而不是python中做数据库工作
使用过滤器和反射过滤器对数据进行过滤
使用F()
表达式
使用注解和聚合
使用原始SQL
用唯一的或被索引的列来检索独立对象
在不同位置多次访问数据库,每次获取一个数据集,不如在一次查询中获取它们。比如循环的时候。
使用select_related()
和prefetch_related()
不检索你不需要的信息
使用QuerySet.values()
和QuerySet.values_list()
使用QuerySet.defer()
和QuerySet.only()
计算数量不要使用len(queryset)
而是使用QuerySet.count()
判断是否存在结果使用QuerySet.exists()
而不是用if queryset
但不要过度使用count()
和exists()
,如果你本来就需要里面的数据,那就不要使用
使用QuerySet.update()
和QuerySet.delete()
批量操作数据
直接使用外键的值1
2
3entry.blog_id
# 而不是
entry.blog.id
如果你并在意结果集的顺序,不要进行排序,移除Meta.ordering
创建对象时尽可能使用bulk_create()
来减少sql查询数量
这也适用于ManyToManyFields
的情况,一起add
而不是一个一个add
1
2
3
4my_band.members.add(me, my_friend)
#更优于
my_band.members.add(me)
my_band.members.add(my_friend)
URL调度器
url捕获的参数永远是字符串
在根url上获取的参数不影响参数传递1
2
3
4
5
6
7
8
9
10
11
12
13
14
15# In settings/urls/main.py
from django.conf.urls import include, url
urlpatterns = [
url(r'^(?P<username>\w+)/blog/', include('foo.urls.blog')),
]
# In foo/urls/blog.py
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^$', views.blog.index),
url(r'^archive/$', views.blog.archive),
]
在上面的例子中,捕获的”username”变量将被如期传递给include()指向的URLconf。
可嵌套1
2
3
4
5
6from django.conf.urls import url
urlpatterns = [
url(r'blog/(page-(\d+)/)?$', blog_articles), # bad
url(r'comments/(?:page-(?P<page_number>\d+)/)?$', comments), # good
]
传递额外的参数1
2
3
4
5
6from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^blog/(?P<year>[0-9]{4})/$', views.year_archive, {'foo': 'bar'}),
]
当url捕获的参数和字典中传递的参数同名时,将忽略url捕获的参数而使用字典里的参数值
传递额外的参数给include()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15# main.py
from django.conf.urls import include, url
urlpatterns = [
url(r'^blog/', include('inner'), {'blogid': 3}),
]
# inner.py
from django.conf.urls import url
from mysite import views
urlpatterns = [
url(r'^archive/$', views.archive),
url(r'^about/$', views.about),
]
效果等同1
2
3
4
5
6
7
8
9
10
11
12
13
14
15# main.py
from django.conf.urls import include, url
from mysite import views
urlpatterns = [
url(r'^blog/', include('inner')),
]
# inner.py
from django.conf.urls import url
urlpatterns = [
url(r'^archive/$', views.archive, {'blogid': 3}),
url(r'^about/$', views.about, {'blogid': 3}),
]
编写视图
HttpResponse
子类,状态码
HttpResponseRedirect
临时重定向,302HttpResponsePermanentRedirect
永久重定向,301HttpResponseNotModified
没有任何修改,304HttpResponseBadRequest
语义有误码,当前请求不被服务器理解,400HttpResponseNotFound
页面没找到,404HttpResponseForbidden
服务器理解请求,但拒绝执行,403HttpResponseNotAllowed
请求中指定的请求方式不能用于请求相应资源,405HttpResponseGone
请求的资源在服务器上已经不可用,而且没有已知的转发地址,410HttpResponseServerError
服务器遇到了一个意外的错误,导致无法完成对请求的处理,500HttpResponse(status=201)
自定义返回状态码
重写错误视图(在url中)1
2
3
4handler404 = 'mysite.views.my_custom_page_not_found_view'
handler500 = 'mysite.views.my_custom_error_view'
handler403 = 'mysite.views.my_custom_permission_denied_view'
handler400 = 'mysite.views.my_custom_bad_request_view'
Django 的快捷函数
template_name
可传一个模版序列,django将使用存在的第一个模版
redirect(to, [permanent=False, ]*args, **kwargs)[source]
为传递进来的参数返回HttpResponseRedirect 给正确的URL 。
参数可以是:一个模型:将调用模型的get_absolute_url() 函数 一个视图,可以带有参数:将使用urlresolvers.reverse 来反向解析名称 一个绝对的或相对的URL,将原样作为重定向的位置。
默认返回一个临时的重定向;传递permanent=True 可以返回一个永久的重定向。
get_object_or_404(klass, *args, **kwargs)
可以传Model也可以传QuerySet实例和关联的管理器get_list_or_404(klass, *args, **kwargs)
可以传Model也可以传QuerySet实例和关联的管理器
视图装饰器
按需内容处理django.views.decorators.http
包里的装饰器可以基于请求的方法来限制对视图的访问。若条件不满足会返回 django.http.HttpResponseNotAllowed。require_http_methods(request_method_list)
限制视图只能服务于规定的http方法(需要大写)require_GET()
require_POST()
require_safe()
只允许视图接受GET和HEAD方法的装饰器。
1 |
1 |
根据最后修改时间来决定是否运行视图,可减少流量1
etag
(版本?)和last_modified
不能同时使用
GZip
对内容进行压缩,节省流量,但增加处理时间
vary_on_cookie
vary_on_headers
基于特定的请求头部来控制缓存
never_cache
Request 对象和Response 对象
HttpRequest
对象(除非特殊说明,所有属性都是只读,session
属性是个例外)HttpRequest.scheme
请求方案(通常为http或https)HttpRequest.body
字节字符串,表示原始http请求正文HttpRequest.path
字符串,表示请求的页面的完整路径,不包含域名HttpRequest.path_info
在某些Web 服务器配置下,主机名后的URL 部分被分成脚本前缀部分和路径信息部分。path_info 属性将始终包含路径信息部分,不论使用的Web 服务器是什么。使用它代替path 可以让代码在测试和开发环境中更容易地切换。
例如,如果应用的WSGIScriptAlias 设置为”/minfo”,那么当path 是”/minfo/music/bands/the_beatles/“ 时path_info 将是”/music/bands/the_beatles/“。HttpRequest.method
请求使用的http方法,大写HttpRequest.encoding
表示提交的数据的编码方式,可写HttpRequest.GET
HttpRequest.POST
HttpRequest.REQUEST
不建议使用,使用GET
和POST
代替HttpRequest.COOKIES
字典,键和值都是字符串HttpRequest.FILES
类似字典的对象,包含所有的上传文件,
这几个方法实现类文件的接口用于读取HttpRequest· 实例
QueryDict
对象
request.POST 和request.GET 的QueryDict 在一个正常的请求/响应循环中是不可变的。若要获得可变的版本,需要使用.copy()。
TemplateResponse 和SimpleTemplateResponse
SimpleTemplateResponse
TemplateResponse
TemplateResponse 对象和普通的django.http.HttpResponse 一样可以用于任何地方。它可以用来作为render() 和render_to_response() 的另外一种选择。
例如,下面这个简单的视图使用一个简单模板和包含查询集的上下文返回一个TemplateResponse:1
2
3
4from django.template.response import TemplateResponse
def blog_index(request):
return TemplateResponse(request, 'entry_list.html', {'entries': Entry.objects.all()})
文件上传
1 | def handle_uploaded_file(f): |
遍历UploadedFile.chunks(),而不是使用read(),能确保大文件并不会占用系统过多的内存。
上传处理器1
2("django.core.files.uploadhandler.MemoryFileUploadHandler",
"django.core.files.uploadhandler.TemporaryFileUploadHandler",)
MemoryFileUploadHandler 和TemporaryFileUploadHandler一起提供了Django的默认文件上传行为,将小文件读取到内存中,大文件放置在磁盘中。
你可以编写自定义的处理器,来定制Django如何处理文件。例如,你可以使用自定义处理器来限制用户级别的配额,在运行中压缩数据,渲染进度条,甚至是向另一个储存位置直接发送数据,而不把它存到本地。关于如何自定义或者完全替换处理器的行为,详见编写自定义的上传处理器。
更改上传处理器的行为1
2
3
4
5
6
7
8DEFAULT_FILE_STORAGE
FILE_CHARSET
FILE_UPLOAD_HANDLERS
FILE_UPLOAD_MAX_MEMORY_SIZE
FILE_UPLOAD_PERMISSIONS
FILE_UPLOAD_TEMP_DIR
MEDIA_ROOT
MEDIA_URL
在运行中更改上传处理器1
request.upload_handlers.insert(0, ProgressBarUploadHandler())
注意
你只可以在访问request.POST或者request.FILES之前修改上传处理器– 在上传处理工作执行之后再修改上传处理就毫无意义了。如果你在读取request.FILES之后尝试修改request.upload_handlers,Django会抛出异常。
所以,你应该在你的视图中尽早修改上传处理器。
CsrfViewMiddleware 也会访问request.POST,它是默认开启的。意思是你需要在你的视图中使用csrf_exempt(),来允许你修改上传处理器。接下来在真正处理请求的函数中,需要使用csrf_protect()。注意这意味着处理器可能会在CSRF验证完成之前开始接收上传文件。例如:
django.views.decorators.csrf import csrf_exempt, csrf_protect
1
2
3
4
5
6
7 @csrf_exempt
def upload_file_view(request):
request.upload_handlers.insert(0, ProgressBarUploadHandler())
return _upload_file_view(request)
@csrf_protect
def _upload_file_view(request):
... # Process request
1 |
|
File.save(name, content[, save=True])1
2
3
4
5
6提供文件名和内容保存一个新文件,不会替换已存在文件,但会创建一个新文件,并且更新对象来指向它。
测试出来直接`car.save()`也不会覆盖已存在文件,如果有重写会在原有名字后面加字符串
如果save为True,模型的save()方法会在文件保存之后调用。这就是说,下面两行:
```python
>>> car.photo.save('myphoto.jpg', content, save=False)
>>> car.save()
等价于:1
'myphoto.jpg', content, save=True) car.photo.save(
从模型实例中移除文件,并且删除内部文件1
File.delete([save=True])
在页面展示中,ImageFile
自带的清除勾选框勾选后只是清除了数据库中这具字段的值,并不会删除文件系统里对应的文件,而File.delete()
会删除文件系统里的文件
文件储存API
DefaultStorage
FileSystemStorage
Storage
管理文件
1 | from django.db import models |
photo
有以下方法photo.path
相对路径photo.url
绝对路径
实际测试有出入1
2
3
4
5
6
7
8
9
10
11# 官方示例
car.photo.path
'/media/cars/chevy.jpg'
car.photo.url
'http://media.example.com/cars/chevy.jpg'
# 实际测试结果
car.photo.path
'E:\workspace\parking\parking\upload\20151230171832_0.jpg'
car.photo.url
'/upload/20151230171832_0.jpg'
更改一个文件的存储位置1
2
3
4
5
6
7
8
9
10
11
12import os
from django.conf import settings
initial_path = car.photo.path
'cars/chevy_ii.jpg' car.photo.name =
new_path = settings.MEDIA_ROOT + car.photo.name
# Move the file on the filesystem
os.rename(initial_path, new_path)
car.save()
car.photo.path
'/media/cars/chevy_ii.jpg'
car.photo.path == new_path
True
编写自定义存储系统
- 必须是
django.core.files.storage.Storage
的子类 - Django必须能够不带任何参数来实例化
- 必须实现 _open() 和 _save()方法,以及任何适合于你的储存类的其它方法
- 你的储存类必须是 可以析构的,所以它在迁移中的一个字段上使用的时候可以被序列化。只要你的字段拥有自己可以序列化的参数,你就可以为它使用django.utils.deconstruct.deconstructible类装饰器(这也是Django用在FileSystemStorage上的东西)
基于类的视图
基于类的内建通用视图
ListView
类视图中,默认的对象列表名除了object_list
,还有一个<model_name>_list
使用基于类的视图处理表单
如果对应模型存在get_absolute_url
方法的前提下CreateView
和UpdateView
类视图的success_url
默认使用get_absolute_url
如何定义form_class
,即使form_class
是ModelForm
也还是需要指定模型
如果没有定义form_class
,则必须定义fields
,fields
和form_class
不能同时存在
如果模型某个字段存的是模板路径,并且想通过此字段来动态的控制表单页的模板,可通过template_name_field
来指定此字段。
Mixin
基于类的视图的Mixin
ContextMixin
所有基于类的通用视图的这个模板Context 都包含一个view 变量指向视图实例。
Use alters_data where appropriate
注意,将视图实例包含在模板Context 中可能将有潜在危险的方法暴露给模板作者。为了避免在模板中被调用类似这样的方法,可以在这些方法上设置alters_data=True。更多信息,参见渲染模板Context 的文档。
很显然,调用某些变量会带来副作用,允许模板系统访问它们将是愚蠢的还会带来安全漏洞。
每个Django 模型对象的delete() 方法就是一个很好的例子。模板系统不应该允许下面的行为:
I will now delete this valuable data.
设置可调用变量的alters_data 属性可以避免这点。如果变量设置alters_data=True ,模板系统将不会调用它,而会无条件使用string_if_invalid 替换这个变量。Django 模型对象自动生成的delete() 和save() 方法自动 设置alters_data=True。 例如:
1
2
3 def sensitive_function(self):
self.database_record.delete()
sensitive_function.alters_data = True
有时候,处于某些原因你可能想关闭这个功能,并告诉模板系统无论什么情况下都不要调用变量。设置可调用对象的do_not_call_in_templates 属性的值为True 可以实现这点。模板系统的行为将类似这个变量是不可调用的(例如,你可以访问可调用对象的属性)。
query_pk_and_slug
如果为True
,get_object()
将使用两者一起来查找。可以防止只使用pk
时,如果pk
连续,直接被攻击者都遍历pk
获取整个列表
内建基于类的视图的API
1 | urlpatterns = [ |
视图参数的线程安全性
传递给视图的参数在视图的每个实例之间共享。这表示不应该使用列表、字典或其它可变对象作为视图的参数。如果你真这么做而且对共享的对象做过修改,某个用户的行为可能对后面访问同一个视图的用户产生影响。
基于类的通用视图 —— 索引
使用Django输出CSV
1 | import csv |
使用Django输出PDF
1 | from reportlab.pdfgen import canvas |
中间件
中间件的顺序很重要
接受请求时,自上向下调用中间件
返回响应时,自下向上调用中间件process_request(request)
在django决定执行哪个视图之前(也就是解析url之前)被调用
返回None
继续处理请求
返回HttpResponse
不再去调用其它的request、view 或exception 中间件,或对应的视图,直接调用响应阶段的中间件,并返回结果
process_view(request, view_func, view_args, view_kwargs)
注:view_args
和view_kwargs
都不包含request
在django调用视图之前被调用
返回None
继续处理请求
返回HttpResponse
不再去调用其它的view 或exception 中间件,或对应的视图,直接调用响应阶段的中间件,并返回结果
注意
在中间件内部,从process_request 或process_view 中访问request.POST 或request.REQUEST 将阻碍该中间件之后的所有视图无法修改请求的上传处理程序,一般情况下要避免这样使用。
类CsrfViewMiddleware可以被认为是个例外,因为它提供csrf_exempt() 和csrf_protect()两个装饰器,允许视图显式控制在哪个点需要开启CSRF验证。
process_template_response(request, response)
在视图刚好执行完毕之后被调用
必须返回一个实现了render
方法的响应对象
process_response(request, response)
在所有响应返回浏览器之前被调用
必须返回HttpResponse
或者StreamingHttpResponse
对象
处理流式响应
process_exception(request, exception)
在视图抛出异常时被调用
返回None
返回HttpResponse
process_template_response
和响应中间件会被调用
在处理响应期间,中间件的执行顺序是倒序执行的,这包括process_exception,如果一个中间件的process_exception
返回了一个响应,那么这个中间件上面的中间件中的process_exception
都不会被调用
__init__()
大多数中间件类都不需要初始化方法
django初始化中间件无需任何参数,所以不能定义一个有参数的__init__方法
init不会每次请求都执行,只在Web服务器响应第一个请求时执行
标记中间件不被使用
init抛出
django.core.exceptions.MiddlewareNotUsed`异常,django会从中间件处理过程中移动这部分中间件,并且当DEBUG为True的时候在django.request记录器中记录调试信息。
- 中间件类不能是任何类的子类
- 中间件可以放在python路径中的任务位置
正常1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21A.init
B.init
C.init
D.init
A.process_request
B.process_request
C.process_request
D.process_request
A.process_view
B.process_view
C.process_view
D.process_view
D.process_template_response
C.process_template_response
B.process_template_response
A.process_template_response
D.process_responst
C.process_responst
B.process_responst
A.process_responst
视图异常1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17A.init
B.init
C.init
D.init
A.process_request
B.process_request
C.process_request
D.process_request
A.process_view
B.process_view
C.process_view
D.process_view
D.process_responst
C.process_responst
B.process_responst
A.process_responst
django中可用的中间件
class CommonMiddleware
DISALLOWED_USER_AGENTS
禁用匹配的user-agents
访问网站APPEND_SLASH
如果url结尾没有斜杠结尾,并且没有找到匹配的url,django会在结尾加上斜杠再匹配一次PREPEND_WWW
如果url会重定向到www到头的网址USE_ETAGS
设置来处理ETag。如果设置USE_ETAGS为True,Django会通过MD5-hashing处理页面的内容来为每一个页面请求计算Etag,并且如果合适的话,它将会发送携带Not Modified的响应。
class BrokenLinkEmailsMiddleware
向MANAGERS
发送死链提醒邮件
class GZipMiddleware
为支持GZip
压缩的浏览器压缩内容
建议放在中间件配置列表的第一个
可通过gzip_page()
装饰器使用独立的GZip
压缩
class ConditionalGetMiddleware
class LocaleMiddeware
基于请求中的数据开启语言选择,它可以为每个用户进行定制。
class MessageMiddleware
开启基于Cookie
和会话的消息支持
class SecurityMiddleware
模版
DjangoTemplates
引擎OPTIONS
配置项中接受以下参数string_if_invalid
当模版变量无效时,使用此值代替
可使用
comment
和
endcomment
进行多行注释
Django模版语言
当模版系统遇到.
时,按下面顺序查询
从技术上来说,当模版系统遇到点(“.”),它将以这样的顺序查询:
- 字典查询(Dictionary lookup)
- 属性或方法查询(Attribute or method lookup)
- 数字索引查询(Numeric index lookup)
模版变量最终解释成字面量,而不是变量值
load
可接受多个库名称
load humanize i18n
load
不支持继承
内置标签与过滤器
标签
filter
对一段内容进行过滤,使用|
对多个过滤器进行连接,且过滤器可以有参数
比如一段纯文本不能使用之前说的过滤器写法,则可以使用filter
firstof
输出第一个不为False
的参数1
{% firstof var1 var2|safe var3 "<strong>fallback value</strong>"|safe %}
ifchanged
检查循环中的一个值从最近一次重复其是否改变,支持`else
with
可往include
的模版里传上下文件变量1
{% include "name_snippet.html" with person="Jane" greeting="Hello" %}
1 | {% include "name_snippet.html" with greeting="Hi" only %} |
1 | {% lorem %} |
设计人员工具,好像是生成随机单词和段落1
2
3{% lorem %}
{% lorem 3 p %}
{% lorem 10 w random %}
人性化
apnumber
转换整数或整数的字符串形式为英文描述
1 会变成oneintcomma
转换成第三位带一个逗号
4500 会变成 4,500intword
将大的整数转换为友好的文字表示
1000000 会变成 1.0 millionnaturalday
对于当天或者一天之内的日期, 返回“今天”,“明天”或者“昨天”,视情况而定。否则,使用传进来的格式字符串给日期格式化naturaltime
对于日期时间的值,返回一个字符串来表示多少秒、分钟或者小时之前
例如(其中“现在”是2007年2月17日16时30分0秒):
17 Feb 2007 16:30:00 会变成 now
17 Feb 2007 16:29:31 会变成 29 seconds agoordinal
将一个整数或是整数的字符串,转换为它的序数词
1 会变成 1st
2 会变成 2nd
3 会变成 3rd
Django 模板语言:面向Python程序员
string_if_invalid
建议只在调试时设置,调试完成后就关闭,开发时最好不要使用,不然可能会遇到渲染问题
每个上下文都包含True
False
None
[使用Context
对象]
这里比较难理解1
2
3
4Context.get(key, otherwise=None)
Context.pop()
Context.push()
Context.update(other_dict)
上下文处理器应用的时机
上下文处理器应用在上下文数据的顶端。也就是说,上下文处理器可能覆盖你提供给Context 或RequestContext 的变量,所以要注意避免与上下文处理器提供的变量名重复。
如果想要上下文数据的优先级高于上下文处理器,使用下面的模式:
1
2
3
4 > from django.template import RequestContext
> request_context = RequestContext(request)
> request_context.push({"my_name": "Adrian"})
>
1 | Django 通过这种方式允许上下文数据在render() 和 TemplateResponse 等API 中覆盖上下文处理器。 |
上面例子中ip_address
也会加入到上下文中
内建的模板上下文处理器
下面是内奸的上下文处理器所添加的内容django.contrib.auth.context_processors.auth
user
perms
django.template.context_processors.debug
- debug
- sql_queryes
一个{‘sql’: …, ‘time’: …} 字典的列表,表示请求期间到目前为止发生的每个SQL 查询及花费的时间。这个列表按查询的顺序排序,并直到访问时才生成。
django.template.context_processors.i18n
MEDIA_URL
django.template.context_processors.static
STATIC_URL
django.template.context_processors.csrf
csrf_token
django.template.context_processors.request
request
django.contrib.messages.context_processors.messages
messages
DEFAULT_MESSAGE_LEVELS
自定义模板标签和过滤器
自定义过滤器
1 | from django import template |
可使用SafeData
来验证是否是安全数据1
2
3if isinstance(value, SafeData):
# Do something with the "safe" string.
...
或使用is_safe
来控制只接收的安全的数据1
2
3
def myfilter(value):
return value
自定义标签
1 | import datetime |
1 | {% show_results poll %} |
写一个标签,实现下面的效果1
2
3
4
5<ul>
<li>First choice</li>
<li>Second choice</li>
<li>Third choice</li>
</ul>
例子1开始1
2
3
4
def show_results(poll):
choices = poll.choice_set.all()
return {'choices': choices}
results.html
1
2
3
4
5<ul>
{% for choice in choices %}
<li> {{ choice }} </li>
{% endfor %}
</ul>
例子1结束
可使用takes_context=True
直接访问上下文件中的数据1
2
3
4
5
6
7
def jump_link(context):
# 因为takes_context=True所以这里的context就是上下文,可以从里面拿想要的数据,如果有多个参数,方法里的第一个参数名必须是context
return {
'link': context['home_link'],
'title': context['home_title'],
}
link.html
1
<a href="{{ link }}">{{ title }}</a>.
页面直接写1
{% jump_link %}
位置参数和关键字参数和python
语法一样1
2
3
4
5
6
def my_tag(a, b, *args, **kwargs):
warning = kwargs['warning']
profile = kwargs['profile']
...
return ...
1 | {% my_tag 123 "abcd" book.title warning=message|lower profile=user.profile %} |
还有一个register.assignment_tag
与register.simple_tag
功能一样,不知道有什么特殊作用
使用表单
一些表单输入自带有html5的验证,要禁用这些验证可以设置form
标签的novalidate
属性
is_bound
可以判断一个表单是否具有绑定数据1
2
3
4
5
6
7
8# 未绑定表单
f = ContactForm()
data = {'subject': 'hello',
'message': 'Hi there',
'sender': 'foo@example.com',
'cc_myself': True}
# 已绑定的表单
f = ContactForm(data)
当表单通过is_valid()
方法验证后,可以直接在form.cleaned_data
中拿值,并且是已经转换好的python
格式的数据,但仍然可以从request.POST
直接访问到未验证的数据。
表单排列
表单属性字段html标签
字段的
lable
html标签字段
lable
标签上的for
值,也是字段标签上的id
隐藏字段列表
显示的字段列表
错误信息不是特定字段的错误
全部错误,一个字典
字段错误
可从form
从遍历出field
有以下属性
`
Model或是
Form上的
label的值
整个
label标签,包含冒号
字段的id
字段的值
字段的
name,考虑表单的前缀
字段的帮助文档
字段的错误
判断字段是否隐藏
表单类中
Field的实例,可以使用它来访问
Field`属性,如1
name.field.max_length
表单 API
1 | # 未绑定表单 |
表单实例一但创建,数据不可更改
Form.clean()
Form.is_valid()
Form.errors
Form.errors
访问errors 属性可以获得错误信息的一个字典:
1
2 f.errors
{'sender': ['Enter a valid email address.'], 'subject': ['This field is required.']}
在这个字典中,键为字段的名称,值为表示错误信息的Unicode 字符串组成的列表。错误信息保存在列表中是因为字段可能有多个错误信息。
你可以在调用is_valid() 之前访问errors。表单的数据将在第一次调用is_valid() 或者访问errors 时验证。
验证只会调用一次,无论你访问errors 或者调用is_valid() 多少次。这意味着,如果验证过程有副作用,这些副作用将只触发一次。
Form.errors.as_data()
返回一个字典,它映射字段到原始的ValidationError 实例
Form.errors.as_json(escape_html=False)
返回JSON 序列化后的错误。
Form.add_error(field, error)
这个方法允许在Form.clean() 方法内部或从表单的外部一起给字段添加错误信息
Form.add_error() 会自动删除cleaned_data 中的相关字段
Form.has_error(field, code=None)
这个方法返回一个布尔值,指示一个字段是否具有指定错误code 的错误。当code 为None 时,如果字段有任何错误它都将返回True。
若要检查非字段错误,使用NON_FIELD_ERRORS 作为field 参数。
Form.non_field_errors()
这个方法返回Form.errors 中不是与特定字段相关联的错误。它包含在Form.clean() 中引发的ValidationError 和使用Form.add_error(None, “…”) 添加的错误。
未绑定表单的行为
验证没有绑定数据的表单是没有意义的,下面的例子展示了这种情况:
1 | f = ContactForm() |
Form.initial
1 | 'subject': 'Hi there!'}) f = ContactForm(initial={ |
这些值只显示在没有绑定的表单中,即使没有提供特定值它们也不会作为后备的值。
优先级高于Form
中的initial
1
2
3
4
5
6
7
8
9
10from django import forms
class CommentForm(forms.Form):
'class') name = forms.CharField(initial=
url = forms.URLField()
comment = forms.CharField()
'name': 'instance'}, auto_id=False) f = CommentForm(initial={
print(f)
<tr><th>Name:</th><td><input type="text" name="name" value="instance" /></td></tr>
<tr><th>Url:</th><td><input type="url" name="url" /></td></tr>
<tr><th>Comment:</th><td><input type="text" name="comment" /></td></tr>
Form.has_changed()
也有Field.has_changed()
方法
检查表单数据是否从初始数据发生改变
当提交表单时,我们可以重新构建表单并提供初始值,这样可以实现比较:1
2 f = ContactForm(request.POST, initial=data)
f.has_changed()
如果request.POST 中的数据与initial 中的不同,has_changed() 将为True,否则为False。 计算的结果是通过调用表单每个字段的Field.has_changed() 得到的。
Form.fields
从表单中访问字段
是一个OrderedDict
可你可以修改表单实例的字段来改变字段在表单中的表示:1
2
3
4
5'\n')[0] f.as_table().split(
'<tr><th>Name:</th><td><input name="name" type="text" value="instance" /></td></tr>'
'name'].label = "Username" f.fields[
'\n')[0] f.as_table().split(
'<tr><th>Username:</th><td><input name="name" type="text" value="instance" /></td></tr>'
注意不要改变base_fields 属性,因为一旦修改将影响同一个Python 进程中接下来所有的ContactForm 实例:1
2
3
4'name'].label = "Username" f.base_fields[
False) another_f = CommentForm(auto_id=
'\n')[0] another_f.as_table().split(
'<tr><th>Username:</th><td><input name="name" type="text" value="class" /></td></tr>'
cleaned_data 始终只 包含表单中定义的字段,即使你在构建表单 时传递了额外的数据。
cleaned_data 始终只 包含表单中定义的字段,即使你在构建表单 时传递了额外的数据。
当表单合法时,cleaned_data 将包含所有字段的键和值,即使传递的数据不包含某些可选字段的值。
Form.cleaned_data
Form.as_p
Form.as_ul
Form.as_table
Form.error_css_class
Form.required_css_class
在Form
类下可以用上面两个属性定义错误样式和必填样式,没有默认值,required_css_class
也会回在label
标签上
Form.auto_id
控制表单上的label
和表单元素的id,值为True
,False
或字符串,支持%s
占位符,表示当前字段名
如果auto_id 设置为任何其它的真值 —— 例如不包含%s 的字符串 —— 那么其行为将类似auto_id 等于True。
默认情况下,auto_id 设置为’id_%s’。
Form.label_suffix
默认为英文的:
BoundField
1 | form = ContactForm() |
BoundField.errors
BoundField.label_tag(contents=None, attrs=None, label_suffix=None)
BoundField.css_classes()
BoundField.value()
提供初始值,会被绑定值覆盖BoundField.id_for_label
Form.is_multipart()
可判断表单是否需要multipart
1
2
3
4
5
6
7{% if form.is_multipart %}
<form enctype="multipart/form-data" method="post" action="/foo/">
{% else %}
<form method="post" action="/foo/">
{% endif %}
{{ form }}
</form>
子类化表单时可通过设置None
来删除从父类中继承过来的字段1
2
3
4
5
6
7
8
9
10
11from django import forms
class ParentForm(forms.Form):
name = forms.CharField()
age = forms.IntegerField()
class ChildForm(ParentForm):
None name =
ChildForm().fields.keys()
'age'] [
Form.prefix
如果在页面中需要放多个相同的表单,可以设置表单的前缀1
2
3
4
5
6
7
8 father = PersonForm()
print(father.as_ul())
<li><label for="id_first_name">First name:</label> <input type="text" name="first_name" id="id_first_name" /></li>
<li><label for="id_last_name">Last name:</label> <input type="text" name="last_name" id="id_last_name" /></li>
"mother") mother = PersonForm(prefix=
print(mother.as_ul())
<li><label for="id_mother-first_name">First name:</label> <input type="text" name="mother-first_name" id="id_mother-first_name" /></li>
<li><label for="id_mother-last_name">Last name:</label> <input type="text" name="mother-last_name" id="id_mother-last_name" /></li>
表单字段
Field.has_change()
检查字段的值是否从初始值发生改变
内建字段
BooleanField
Widget:CheckboxInput
错误信息的键:required
CharField
Widget:TextInput
错误信息的键:required`
max_lengthmin_length`
接收两个可选参数
`max_length
min_length`
ChoiceField
Widtget:Select
错误信息的键:required`
invalid_choiceinvalid_choice
错误消息可能包含
%(value)s,它将被选择的选项替换掉。
接收一个额外的必选参数
choices`
是一个二元组组成的可迭代对象
TypeChoiceField
Widget:Select
错误信息的键:required`
invalid_choice接收额外的参数
choices是一个二元组组成的可迭代对象
coerce接收一个参数并返回强制转换后的值的一个函数。例如内建的int、float、bool 和其它类型。默认为id 函数。注意强制转换在输入验证结束后发生,所以它可能强制转换不在 choices 中的值
empty_value`
用于表示“空”的值。默认为空字符串;None 是另外一个常见的选项。注意这个值不会被coerce 参数中指定的函数强制转换,所以请根据情况进行选择
DateField
Widget:DateInput
错误信息的键:required`
invalid接收一个可选参数
input_formats一个格式的列表,用于转换一个字符串为
datateim.date`对象
默认为1
2
3['%Y-%m-%d', # '2006-10-25'
'%m/%d/%Y', # '10/25/2006'
'%m/%d/%y'] # '10/25/06'
另外,如果你在设置中指定USE_L10N=False,以下的格式也将包含在默认的输入格式中:1
2
3
4
5
6
7
8['%b %d %Y', # 'Oct 25 2006'
'%b %d, %Y', # 'Oct 25, 2006'
'%d %b %Y', # '25 Oct 2006'
'%d %b, %Y', # '25 Oct, 2006'
'%B %d %Y', # 'October 25 2006'
'%B %d, %Y', # 'October 25, 2006'
'%d %B %Y', # '25 October 2006'
'%d %B, %Y'] # '25 October, 2006'
DateTimeField
Widget:DateTimeInput
错误信息的键:required`
invalid接收一个可选参数
input_formats一个格式的列表,用于转换一个字符串为
datetime.datetime`对象
默认为1
2
3
4
5
6
7
8
9['%Y-%m-%d %H:%M:%S', # '2006-10-25 14:30:59'
'%Y-%m-%d %H:%M', # '2006-10-25 14:30'
'%Y-%m-%d', # '2006-10-25'
'%m/%d/%Y %H:%M:%S', # '10/25/2006 14:30:59'
'%m/%d/%Y %H:%M', # '10/25/2006 14:30'
'%m/%d/%Y', # '10/25/2006'
'%m/%d/%y %H:%M:%S', # '10/25/06 14:30:59'
'%m/%d/%y %H:%M', # '10/25/06 14:30'
'%m/%d/%y'] # '10/25/06'
DecimalField
Widget:当Field.localize
是False
时为NumberInput,否则为TextInput
错误信息的键:required`
invalidmax_value
min_digitsmax_decimal_places
max_whole_digitsmax_value
和
min_value错误信息可能包含
%(limit_value)s,它们将被真正的限制值替换。类似地,
max_digits、
max_decimal_places和
max_whole_digits错误消息可能包含
%(max)s接收四个可选参数
max_valuemin_value
max_digits
最大位数
decimal_places`最大小数位
DurationField
Widget:TextInput
错误信息的键:required`
invalid`
EmailField
Widget:EmailInput
错误信息的键:required`
invalid接收两个可选参数
max_lengthmin_length`
FileField
Widget:ClearableFileInput
错误信息的键:required`
invalidmissing
emptymax_length`
接收两个可选参数
`max_length
allow_empty_file如果提供,这两个参数确保文件名的最大长度,而且即使文件内容为空时验证也会成功
max_length错误信息表示文件名的长度。在错误信息中,
%(max)d将替换为文件的最大长度,%
(length)d` 将替换为当前文件名的长度
FilePathField
Widget:Select
错误信息的键:required`
invalid_choice这个字段允许从一个特定的目录选择文件
接收五个参数
path必须
想要列出的目录的绝对路径
recursive可选
布尔值,默认为
False,是否需要递归这个目录
match可选
正则表达式表示一个模式,只有匹配这个表达式的名称才会允许作为选项
allow_files可选
布尔值,默认为
True,表示是否应该包含指定位置的文件,它和
allow_folders必须有一个为
Trueallow_folders
可选
布尔值,默认为
True,表示是否应该包含指定位置的目录,和
allow_files必须有一个为
True`
FloatField
Widget:当Field.localize
是False 时为NumberInput
,否则为TextInput
错误信息的键:required`
invalidmax_value
min_value接收两个可选参数
max_valuemin_value`
ImageField
Widget:ClearableFileInput
错误信息的键:required`
invalidmissing
emptyinvalid_image`
IntegerField
Widget:当Field.localize
是False
时为NumberInput
,否则为TextInput
错误信息的键:required`
invalidmax_value
min_value接收两个可选参数
max_valuemin_value`
IPAddressField
1.7弃用
GenericIPAddressField
Widget:TextInput
错误信息的键:required`
invalid接收两个可选参数
protocolunpack_ipv4`
MultipleChoiceField
Widget:SelectMultiple
错误信息的键:required`
invalid_choiceinvalid_list`
TypedMultipleChoiceField
NullBooleanField
RegexField
SlugField
TimeField
URLField
UUIDField
输出时需要.hex
ComboField
1 | from django.forms import ComboField |
MultiValueField
SplitDateTimeField
ModelChoiceField
1 | # A custom empty label |
ModelMultipleChoiceField
Widgets
处理文本输入的Widget
TextInput
NumberInput
EmailInput
URLInput
PasswordInput
HiddenInput
DateInput
DateTimeInput
TimeInput
Textarea
选择和复选框Widget
CheckboxInput
Select
NullBooleanSelect
SelectMultiple
RadioSelect
CheckboxSelectMultiple
文件上传Widget
FileInput
ClearableFileInput
复合Widget
MultipleHiddenInput
SplitDateTimeWidget
SplitHiddenDateTimeWidget
SelectDateWidget
从模型创建表单
下面两种方法效果相同1
2
3
4
5
6
7
8author = Author(title='Mr')
form = PartialAuthorForm(request.POST, instance=author)
form.save()
# or
form = PartialAuthorForm(request.POST)
author = form.save(commit=False)
author.title = 'Mr'
author.save()
显式定义的字段不会从对于的模型中获取属性,例如 max_length 或required。 如果你希望保持模型中指定的行为,你必须设置在声明表单字段时显式设置相关的参数。
例如,如果Article 模型像下面这样:1
2
3
4class Article(models.Model):
headline = models.CharField(max_length=200, null=True, blank=True,
help_text="Use puns liberally")
content = models.TextField()
而你想为headline 做一些自定义的验证,在保持blank 和help_text 值的同时,你必须定义这样定义ArticleForm:
1 | class ArticleForm(ModelForm): |
创建简单的表单或表单集可以使用modelform_factory()`
modelformset_factory()`方法来新建。
启用字段的本地化功能¶
默认情况下,ModelForm 中的字段不会本地化它们的数据。你可以使用Meta 类的localized_fields 属性来启用字段的本地化功能。1
2
3
4
5
6from django.forms import ModelForm
from myapp.models import Author
class AuthorForm(ModelForm):
class Meta:
model = Author
'birth_date',) localized_fields = (
如果localized_fields 设置为'__all__'
这个特殊的值,所有的字段都将本地化。
提供的初始值会覆盖从实例取得的值1
2
3
4
5
61) article = Article.objects.get(pk=
article.headline
'My headline'
'headline': 'Initial headline'}, instance=article) form = ArticleForm(initial={
'headline'].value() form[
'Initial headline'
如果不需要很多自定义,可以直接使用工厂方法来生成表单类1
2
3
4from django.forms.models import modelform_factory
from myapp.models import Book
"author", "title")) BookForm = modelform_factory(Book, fields=(
>
1 | from django.forms import Textarea |
1 | "birth_date",)) Form = modelform_factory(Author, form=AuthorForm, localized_fields=( |
表单集1
2
3from django.forms.models import modelformset_factory
from myapp.models import Author
'name', 'title')) AuthorFormSet = modelformset_factory(Author, fields=(
使用model
生成的formset
默认带一个包含全部对象的queryset
formset`
save()`之后,会有新的属性1
2
3models.BaseModelFormSet.changed_objects
models.BaseModelFormSet.deleted_objects
models.BaseModelFormSet.new_objects
max_num
为最大的表单数,如果初始queryset
长度比max_num
,则按照queryset
来,extra
是可以额外添加的空表单的个数,但extra
和queryset
长度相加如果大于max_num
,则extra
和实例设置可能表现不一样,如queryset
长度为2,max_num
为4,extra
不管是2还是5,最终表现出来都是2。1
AuthorFormSet = modelformset_factory(Author, fields=('name',), max_num=4, extra=2)
max_num
默认只影响显示,不影响验证,如果需要影响验证添加validate_max=True
即可
表单素材 ( Media 类)
Form
和Widget
都可以定义素材
1 | from django import forms |
使用CalendarWidget
会自动引入下列资源1
2
3<link href="http://static.example.com/pretty.css" type="text/css" media="all" rel="stylesheet" />
<script type="text/javascript" src="http://static.example.com/animations.js"></script>
<script type="text/javascript" src="http://static.example.com/actions.js"></script>
Widget
会默认继承父类的素材,如果不想继承在Media
里使用extend
禁止。
动态定义1
2
3
4
5class CalendarWidget(forms.TextInput):
def _media(self):
return forms.Media(css={'all': ('pretty.css',)},
js=('animations.js', 'actions.js'))
media = property(_media)
两个Media
实例可以相加1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19from django import forms
class CalendarWidget(forms.TextInput):
class Media:
css = {
'all': ('pretty.css',)
}
'animations.js', 'actions.js') js = (
class OtherWidget(forms.TextInput):
class Media:
'whizbang.js',) js = (
w1 = CalendarWidget()
w2 = OtherWidget()
print(w1.media + w2.media)
<link href="http://static.example.com/pretty.css" type="text/css" media="all" rel="stylesheet" />
<script type="text/javascript" src="http://static.example.com/animations.js"></script>
<script type="text/javascript" src="http://static.example.com/actions.js"></script>
<script type="text/javascript" src="http://static.example.com/whizbang.js"></script>
表单Media
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16class ContactForm(forms.Form):
date = DateField(widget=CalendarWidget)
40, widget=OtherWidget) name = CharField(max_length=
...
class Media:
css = {
'all': ('layout.css',)
}
f = ContactForm()
f.media
<link href="http://static.example.com/pretty.css" type="text/css" media="all" rel="stylesheet" />
<link href="http://static.example.com/layout.css" type="text/css" media="all" rel="stylesheet" />
<script type="text/javascript" src="http://static.example.com/animations.js"></script>
<script type="text/javascript" src="http://static.example.com/actions.js"></script>
<script type="text/javascript" src="http://static.example.com/whizbang.js"></script
表单集
表单集控制max_num
min_num
validate_max
validate_min
can_order
can_delete
其中can_order`
can_delete`默认以以下形式展现
如果是使用Model
生成的表单集,如果delete
后,在调用formset.save()
会自动删除相应的数据,但如果调用了formset.save(commit=False)
,则需要手动删除(1.6或更早版还是会自动删除)1
2
3False) instances = formset.save(commit=
for obj in formset.deleted_objects:
obj.delete()
如果要兼容1.6或更早版,可以这么写1
2
3
4
5
6
7
8try:
# For Django 1.7+
for obj in formset.deleted_objects:
obj.delete()
except AssertionError:
# Django 1.6 and earlier already deletes the objects, trying to
# delete them a second time raises an AssertionError.
pass
在表单集里添加额外的字段1
2
3
4
5
6
7
8
9
10
11
12
13
14
15from django.forms.formsets import BaseFormSet
from django.forms.formsets import formset_factory
from myapp.forms import ArticleForm
class BaseArticleFormSet(BaseFormSet):
def add_fields(self, form, index):
super(BaseArticleFormSet, self).add_fields(form, index)
"my_field"] = forms.CharField() form.fields[
ArticleFormSet = formset_factory(ArticleForm, formset=BaseArticleFormSet)
formset = ArticleFormSet()
for form in formset:
print(form.as_table())
<tr><th><label for="id_form-0-title">Title:</label></th><td><input type="text" name="form-0-title" id="id_form-0-title" /></td></tr>
<tr><th><label for="id_form-0-pub_date">Pub date:</label></th><td><input type="text" name="form-0-pub_date" id="id_form-0-pub_date" /></td></tr>
<tr><th><label for="id_form-0-my_field">My field:</label></th><td><input type="text" name="form-0-my_field" id="id_form-0-my_field" /></td></tr>
表单集对应的模版写法1
2
3
4
5<form method="post" action="">
<table>
{{ formset }}
</table>
</form>
也可以手动渲染,不能缺少1
2
3
4
5
6
7
8<form method="post" action="">
{{ formset.management_form }}
<table>
{% for form in formset
{{ form }}
{% endfor
</table>
</form>
如果是手动渲染,can_order`
can_delete`需要手动添加1
2
3
4
5
6
7
8
9
10
11
12<form method="post" action="">
{{ formset.management_form }}
{% for form in formset
<ul>
<li>{{ form.title }}</li>
<li>{{ form.pub_date }}</li>
{% if formset.can_delete
<li>{{ form.DELETE }}</li>
{% endif
</ul>
{% endfor
</form>
在一个视图中使用多个FormSet
需要回前缀prefix
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20from django.forms.formsets import formset_factory
from django.shortcuts import render_to_response
from myapp.forms import ArticleForm, BookForm
def manage_articles(request):
ArticleFormSet = formset_factory(ArticleForm)
BookFormSet = formset_factory(BookForm)
if request.method == 'POST':
article_formset = ArticleFormSet(request.POST, request.FILES, prefix='articles')
book_formset = BookFormSet(request.POST, request.FILES, prefix='books')
if article_formset.is_valid() and book_formset.is_valid():
# do something with the cleaned_data on the formsets.
pass
else:
article_formset = ArticleFormSet(prefix='articles')
book_formset = BookFormSet(prefix='books')
return render_to_response('manage_articles.html', {
'article_formset': article_formset,
'book_formset': book_formset,
})
表单验证和字段验证
格式标准
抛出单个错误1
2
3
4
5raise ValidationError(
_('Invalid value: %(value)s'),
code='invalid',
params={'value': '42'},
)
招聘多个错误1
2
3
4
5
6
7
8
9
10
11# Good
raise ValidationError([
ValidationError(_('Error 1'), code='error1'),
ValidationError(_('Error 2'), code='error2'),
])
# Bad
raise ValidationError([
_('Error 1'),
_('Error 2'),
])
创建一个新的表单字段添加默认验证1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20from django import forms
from django.core.validators import validate_email
class MultiEmailField(forms.Field):
def to_python(self, value):
"Normalize data to a list of strings."
# Return an empty list if no input was given.
if not value:
return []
return value.split(',')
def validate(self, value):
"Check if value consists only of valid emails."
# Use the parent's handling of required fields, etc.
super(MultiEmailField, self).validate(value)
for email in value:
validate_email(email)
Django 的设置
django-admin 工具
当使用django-admin 时, 你可以设置只设置环境变量一次,或者每次运行该工具时显式传递设置模块。
例如(Unix Bash shell):1
2export DJANGO_SETTINGS_MODULE=mysite.settings
django-admin runserver
例如(Windows shell):1
2set DJANGO_SETTINGS_MODULE=mysite.settings
django-admin runserver
使用–settings 命令行参数可以手工指定设置:1
django-admin runserver --settings=mysite.settings
使用下面的命令可以查询设置与默认设置的不同1
python manage.py diffsettings
在django app中使用设置应使用以下导入方式1
from django.conf import settings
注意,django.conf.settings 不是一个模块 —— 它是一个对象。所以不可以导入每个单独的设置:1
from django.conf.settings import DEBUG # This won't work.
不要在应用运行时改变设置
完整列表设置(Settings)
CSRF_COOKIE_SECURE=True
只通过HTTPS
传递cookie
DATABASES['CONN_MAX_AGE']
数据库连接的戚时间,默认为0(历史遗留行为),设置为None
表示无限的持久连接DECIMAL_SEPARATOR
类型数据的分隔符默认为点.
DISALLOWED_USER_AGENTS
编写正则表达式元组禁用代码访问,需要启用CommonMiddleware
中间件INTERNAL_IPS
设置公司内容的ip,在些ip列表中的ip可以访问admindoc下的书签
应用
1 | # rock_n_roll/apps.py |
1 | # rock_n_roll/__init__.py |
AppConfig
可配置的属性
AppConfig.name
AppConfig.label
AppConfig.verbose_name
Appconfig.path
AppConfig
只读属性
AppConfig.module
Appconfig.models_module
AppConfig
方法
AppConfig.get_models()
AppConfig..get_model(model_name)
AppConfigevaluate.ready()
1 | from django.apps import apps |
APP
apps.ready
apps.get_app_configs()
apps.get_app_config(app_label)
apps.is_installed(app_name)
apps.get_model(app_label, model_name)
Django异常
核心异常
django.core.exceptions
ObjectDoesNotExist
对象不存在DoesNotExist
的基类
对ObjectDoesNotExist的try/except会为所有模型捕获到所有DoesNotExist 异常1
2
3
4
5
6from django.core.exceptions import ObjectDoesNotExist
try:
e = Entry.objects.get(id=3)
b = Blog.objects.get(id=1)
except ObjectDoesNotExist:
print("Either the entry or blog doesn't exist.")FieldDoesNotExist
当被请求的字段在模型或模型的父类中不存在时,FieldDoesNotExist
异常由模型的_meta.get_field()
方法抛出MultipleObjectsReturned
查询时,预期只有一个对象,但是返回了多个对象会抛出此异常SuspiciousOperation
当用户进行的操作在安全方面可疑的时候,抛出此异常,例如,篡改cookie
子类DisallowedHost
DisallowedModelAdminLookup
DisallowedModelAdminToField
DisallowedRedirect
InvalidSessionKey
SuspiciousFileOperation
SuspiciousMultipartForm
SuspiciousSession
PermissionDenied
当用户不被允许来执行请求的操作时产生ViewDoesNotExist
当请求的视图不存在时抛出此异常MiddlewareNotUsed
当中间件没有在服务器配置中出现时,抛出此异常ImproperlyConfigured
django配置不当时抛出此异常,如settings.py
中的值不正确或者不可解析FieldError
当模型上的字段出现问题时,抛出此异常,由以下原因造成:- 模型中的字段与抽象基类中的字段重名
- 排序造成了一个死循环
- 关键词不能由过滤器参数解析
- 字段不能由查询参数中的关键词决定
- 连接(join)不能在指定对象上使用
- 字段名称不可用
查询包含了无效的
order_by
参数ValidationError
当表单或模型字段验证失败时抛出此异常
NON_FIELD_ERRORS
在表单或者模型中不属于特定字段的ValidationError
被归类为NON_FIELD_ERRORS
URL解析器异常
Resolver404
django.http.Http404
的子类
当向resolve
传递的路径不能匹配到对应视图时抛出此异常NoReverseMatch
当你的URLconf中的一个匹配的URL不能基于提供的参数识别时,抛出此异常
数据库异常
数据库异常由django.db导入
Error
InterfaceError
DatabaseError
DataError
OperationalError
IntegrityError
InternalError
ProgrammingError
NotSupportedError
HTTP异常
HTTP异常由django.http导入
UnreadablePostError
用户取消上传时抛出此异常
事务异常
事务异常定义由django.db.transaction
导入
测试框架异常
由DJango django.test 包提供的异常
RedirectCycleError
当测试客户端检测到重定向的循环或者过长的链时抛出此异常
Python
异常
Django在适当的时候也会抛出Python的内建异常
django-admin and manage.py
dumpdata
该命令将所有与被命名应用相关联的数据库中的数据输出到标准输出。
如果在dumpdate命令后面未指定Django应用名,则Django项目中安装的所有应用的数据都将被dump到fixture中dumpdata --output data.json
flus
清空数据库,重新装载初始数据--noinput
--database
--no-initial-data
inspectdb
根据数据库结构生成model1
python manage.py inspectdb > models.py
loaddata
导入fixture数据runserver
启动本地上一个轻量级的Web服务器,默认多线程--noreload
禁用自动重新载入--nothreading
禁用多线程
1 | runserver 0.0.0.0:80 |
添加自定义的命令
向应用下添加management/commands目录,Django会为此目录下的所有没有带下划线开头的python模块都注册一个manage.py
命令。
在Python 2上,请确保management和management/commands两个目录都包含__init__.py
文件。
1 | from django.core.management.base import BaseCommand, CommandError |