创建项目
要使用django需要提前安装django(“pip install django“)。
对于django的一般使用流程:
创建项目–>创建应用–>在settings中将app安装上–>设置urls进行路由分发–>在视图中处理请求,或返回页面等数据或操作数据库然后再返回数据。
创建项目和应用
一个项目可以有多个应用,这样不同应用之间可以相互独立,减少耦合度。
手动创建一个项目:$django-admin startproject <项目名>手动创建一个应用:
$cd <项目名> $django-admin startapp <应用名>需要注意的是:创建应用时,需要cd到项目的目录中
项目的目录及对应的功能
<项目> ├── <应用> │ ├── admin.py │ ├── apps.py │ ├── __init__.py │ ├── migrations │ │ └── __init__.py │ ├── models.py │ ├── tests.py │ └── views.py ├── manage.py └── <项目> ├── asgi.py ├── __init__.py ├── settings.py ├── urls.py └── wsgi.py创建完项目和应用后,其目录结构应该是如上所示的(django3.0.5,使用
python -m django --version命令可以查看)。
settings.py文件是django的设置,里面有些重要的配置,后面单独讲。
每个应用下的models.py,views.py对应者model和view。
路由分发
一般使用
路由分发应该在urls.py中进行。默认的根urls是通过settings文件配置的:ROOT_URLCONF = '<项目名>.urls'。
路由的分发靠的是urlpatterns列表来的定义的,其内的元素是path或re_path。
如:
from django.contrib import admin
from django.urls import path
urlpatterns = [
path('admin/', admin.site.urls),
]
path的一般参数:path('admin/', admin.site.urls, name="myadmin")admin/是一段字符串,可以直接写成url,也可以带上过滤器,当django自上而下匹配到这个url时,会生成一个HttpRequest对象传给视图的的一个参数admin.site.urls是一个视图函数,用于处理请求name="myadmin":name参数可以为django的反向解析,动态生成url提供帮助,和app_name使用效果更好。
关于url以”/“结尾的匹配:如path('admin/', admin.site.urls)可以匹配admin/和admin,但若是path('admin', admin.site.urls)的话,就只能匹配到admin。
路由转发
通常,我们会在每个app里,各自创建一个urls.py路由模块,然后从根路由出发,将app所属的url请求,全部转发到相应的urls.py模块中。
要实现这个功能,需要用到django.urls.include模块。
如
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path("app1/", include("app1.urls")),
path("app2/", include("app2.urls")),
]
之后我们就可以在app的urls中做进一步的处理了。
需要注意的是:app.urls中接收到的url是已经去掉匹配部分的。如依照上面的路由,app1/user/index到了app1.urls中时,就剩下了user/index,所以写路由分发时需要注意。而且app中的urls.py需要我们手动创建。
转换器
转换器本质上就是一个使用正则表达式匹配url,但是它可以自动将url中匹配的内容转换成我们需要的数据格式传给request,也可以动态生成url。
默认情况下,Django内置下面的路径转换器:
str:匹配任何非空字符串,但不含斜杠/,如果你没有专门指定转换器,默认使用该转换器int:匹配0和正整数,返回一个int类型slug:可理解为注释、后缀、附属等概念,是url拖在最后的一部分解释性字符。该转换器匹配任何ASCII字符以及连接符和下划线,比如building-your-1st-django-site;uuid:匹配一个uuid格式的对象。为了防止冲突,规定必须使用破折号,所有字母必须小写,例如075194d3-6885-417e-a8a8-6c931e272f00。返回一个UUID对象;path:匹配任何非空字符串,重点是可以包含路径分隔符’/‘。这个转换器可以帮助你匹配整个url而不是一段一段的url字符串。要区分path转换器和path()方法
使用方式:
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),
]
'articles/<int:year>/'中的int是转换器,而year是request中的参数名。
自定义转换器
默认的转换可以实现一般的匹配,但是假如要匹配复杂度和自定义程度比较高的url时,可以自定义一个转换器。
第一步:自定义类:
在app中创建一个converters.py,用于放置转换器类
关键实现:
- 类属性:
regex
这里需要写正则表达式 - 类方法:to_python
转换数据给视图用,失败要抛出ValueError异常 - 类方法:to_url
返回url供反转操作,失败要ValueError异常
# app/converters.py
class Year:
regex = "\d{4}"
def to_python(self, value):
try:
return int(value)
except ValueError:
raise ValueError
def to_url(self, value):
return value
第二步:在url中注册:
# polls/urls.py
from django.urls import path, register_converter
from . import views, converters
app_name = "polls" # app_name的作用后面讲述
# 注册转换器为year_conv
register_converter(converters.Year, "year_conv")
urlpatterns = [
path("", views.index, name="index"),
# 和默认转换器一样使用自定义转换器
path("<year_conv:year>/", views.test, name="test"),
]
关于re_path
Django2.0的urlconf虽然改‘配置方式’了,但它依然向老版本兼容。而这个兼容的办法,就是用re_path()方法。re_path()方法在骨子里,根本就是以前的url()方法,只不过导入的位置变了。下面是一个例子,对比一下Django1.11时代的语法,有什么太大的差别?
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()方法的不同点:
捕获URL中的参数使用的是正则捕获,语法是 (?P<name>pattern) ,其中 name 是组名,pattern 是要匹配的模式。
传递给视图的所有参数都是字符串类型。而不像path()方法中可以指定转换成某种类型。在视图中接收参数时一定要小心。
反向解析及应用命名空间
反向解析的功能可以动态生成url,防止我们将url写死,减少维护复杂度。
反向解析可以在python代码中应用,也可以在django的template中应用。
如在urls中这样定义:
from django.urls import path
from .views import index, article
urlpatterns = [
path("index/", index, name="index"),
path("article/<int:article_id>", article, name="article")
]
python代码中反向解析:
# 1. 导入django.urls.reverse函数 # 2. 根据urls中path对象的name参数,确定你要生成的是那个url # 3. 假如url中需要参数的话,以args或kwargs传入 from django.http import HttpResponse from django.urls import reverse # Create your views here. def index(request): index_url = reverse("index") return HttpResponse(index_url) def article(request, article_id): # article_url = reverse("article", args=(article_id, )) article_url = reverse("article", kwargs={"article_id": article_id}) return HttpResponse(article_url)在template中反向解析:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> {% url "index" %} {% url "article" "1234" %} </body> </html>使用的是django模板引擎的url标签,使用template时,应该要在settings中的
INSTALLED_APPS添加上自己的app。
以上只利用path的name参数生成url的做法,在多个app时(如app1:name="index", app2:name="index")可能就不怎么适用了。
因此django为我们提供了一个app_name变量,可以为我们标识不同的app。
在app/urls.py中我们可以自定义一个独一无二的app_name,如:
from django.urls import path
from .views import index, article
app_name = "app01"
urlpatterns = [
path("index/", index, name="index"),
path("article/<int:article_id>", article, name="article")
]
在使用的时候只需要在name字符前面加上app_name:即可,如:
article_url = reverse("app01:article", kwargs={"article_id": article_id})
template的使用也是如此,改变第一个参数即可。
django还可以为不同的对象提供标识,实现方式时:给
include添加name_space参数。
具体可点击:关于include和name_space的使用。
视图
视图就是一个普通的Python函数,它接收从urls哪里的HttpRequest对象,返回的是HTML网页、重定向、404错误,XML文档或图像等任何东西。
视图一般使用
from django.http import HttpResponse
# Create your views here.
def index(request):
return HttpResponse("<h1>hello world</h1>")
例子,返回的是一个h1标签。
接收参数
当我们在路由设置参数时,在视图中应该要有对应的形参接收。
# app/urls.py
from django.urls import path
from .views import index, article
app_name = "app01"
urlpatterns = [
# url中有个参数叫article_id,视图函数是article
path("article/<int:article_id>", article, name="article")
]
# app/views.py
from django.http import HttpResponse
# Create your views here.
def article(request, article_id):
return HttpResponse(f"article的id是{article_id}")
返回
视图函数返回各种数据,一般只需用到django.shortcuts下面的函数即可,该模块,为我们提供了很多快捷方便的类和方法,它们都很重要,使用频率很高。
HTML页面
使用django.shortcuts.render函数。
from django.shortcuts import render
def index(request):
return render(request, "app01/index.html")
render(request, template_name, context=None, content_type=None, status=None, using=None)
必需参数:
request:视图函数处理的当前请求,封装了请求头的所有数据,其实就是视图参数request。template_name:要使用的模板的完整名称或者模板名称的列表。如果是一个列表,将使用其中能够查找到的第一个模板。
可选参数:
context:添加到模板上下文的一个数据字典。默认是一个空字典。可以将认可需要提供给模板的数据以字典的格式添加进去。这里有个小技巧,使用Python内置的locals()方法,可以方便地将函数作用域内的所有变量一次性添加进去。content_type:用于生成的文档的MIME类型。 默认为DEFAULT_CONTENT_TYPE设置的值,也就是text/html。status:响应的状态代码。 默认为200。using:用于加载模板使用的模板引擎的NAME。
template_name一般是在app/templates/app/下的html文件。多写一个”app”目的是区分不同的app中的同名html,因为假如settings的TEMPLATES中的APP_DIRS为True时,django会在app/templates下寻找html文件,所以要加上一层app名字。
使用render本质就是:
from django.http import HttpResponse
from django.template import loader
def my_view(request):
t = loader.get_template('myapp/index.html')
c = {'foo': 'bar'}
return HttpResponse(t.render(c, request), content_type='application/xhtml+xml')
使用locals()作为render的context参数,可以将视图中的全部局部变量给template。
Json数据
django返回json数据的方法有很多,下面介绍几种方法:
使用
django.http.HttpResponse库+json库
直接使用json库序列化数据,然后使用HttpResponse返回数据,指定请求头Content-Type=application/json。
序列化非常用数据类型时,可以指定编码器(一个继承json.JSONEncoder的类,重写default方法)。from django.http import HttpResponse import json def index(request): data = { "name": "lczmx", "age": 22, } return HttpResponse(json.dumps(data), content_type="application/json")使用
django.http.HttpResponse库+django.core.serializers库
此方法可以很好的序列化QuerySet对象。def index(request): questions = Question.objects.all() data = serializers.serialize("json", queryset=questions) return HttpResponse(data, content_type="application/json")serializers.serialize的第一个参数是格式,第二个参数是queryset数据。
更多见官方文档:序列化 Django 对象使用
django.http.JsonResponse库class JsonResponse(data, encoder=DjangoJSONEncoder, safe=True, json_dumps_params=None, **kwargs)JsonResponse是一个HttpResponse子类,帮助创建一个 JSON 编码的响应。它继承了它的超类的大部分行为,但有一些不同:
其默认的Content-Type头设置为application/json。- 第一个参数
data应该是 dict 实例。如果 safe 参数设置为 False (见下文),它可以是任何 JSON 可序列化的对象。 encoder,默认为django.core.serializers.json.DjangoJSONEncoder,将用于序列化数据。。safe布尔参数默认为 True。如果它被设置为 False,任何对象都可以被传递到序列化中(否则只允许 dict 实例)。如果 safe 为 True,而第一个参数是一个非 dict 对象,则会引发一个 TypeError。json_dumps_params参数是一个关键字参数的字典,用来传递给 json.dumps() 调用,用于生成响应。
from django.http import JsonResponse def index(request): data = { "name": "lczmx", "age": 22, } return JsonResponse(data)- 第一个参数
跳转页面
跳转页面使用redirect进行页面跳转。
redirect(to, args, permanent=False, *kwargs)
根据传递进来的url参数,返回HttpResponseRedirect。
参数to可以是:
- 一个模型实例:将调用模型的
get_absolute_url()函数,反向解析出目的url; - URL的name名称:可能带有参数:
reverse()将用于反向解析url; - 一个绝对的或相对的URL:将原封不动的作为重定向的目标位置。
默认情况下是临时重定向,如果设置permanent=True将永久重定向。
例子:
# urls.py
from django.urls import path
from .views import index, article
app_name = "app01"
urlpatterns = [
path("index/", index, name="index"),
path("article/<int:article_id>", article, name="article")
]
# views.py
from django.http import HttpResponse
from django.shortcuts import redirect
# Create your views here.
def index(request):
return HttpResponse("index page")
def article(request, article_id):
if article_id > 123:
return redirect("app01:index")
return HttpResponse(f"article的id是{article_id}")
错误信息
django实现了一些状态码以4和5开头的HttpResponse子类,点击查看。
假如没有我们需要的,可以自定义响应类,只需要继承HttpResponse类,然后指定status_code属性的值即可。
from http import HTTPStatus
from django.http import HttpResponse
class HttpResponseNoContent(HttpResponse):
status_code = HTTPStatus.NO_CONTENT
自定义404
为了方便,django提供了django.http.Http404异常类,在视图中主动抛出这个异常时,Django 会捕捉到它并且返回标准的错误页面,连同 HTTP 错误代码 404 。
from django.http import Http404
def article(request, article_id):
if article_id > 123:
raise Http404("page not fund")
return HttpResponse(f"article的id是{article_id}")
虽然可以返回404页面,但是这是django内置的404页面,在实际使用中,需要换成我们自己的。
需要注意的是,要使用自定义的404html,需要将settings文件中的DEBUG设为False。
由于当你raise了Http404后:
django会做一下动作:
django.conf.urls.handler404,这相当于一个urls文件:# __init__文件的内容 from django.urls import include, re_path from django.views import defaults __all__ = ['handler400', 'handler403', 'handler404', 'handler500', 'include', 'url'] handler400 = defaults.bad_request handler403 = defaults.permission_denied handler404 = defaults.page_not_found handler500 = defaults.server_error # ...使用
django.views.defaults.page_not_found()视图判断是否自定义了404.html,
- 如果有(查找的是template,和一般的一样),输出该HTML文件
- 如果没有,输出默认的404提示信息
所以我们要自定以404页面有两个方法:
- 使用默认的
django.views.defaults.page_not_found()视图,然后再自己在template中创建404.html文件。 - 自定义处理404的视图
第一种方法:
在template创建一个404页面
注意要配置好settings.TEMPLATES的DIRS或APP_DIRS。使用静态的页面或使用django的模板引擎展示更多详细信息。
简化django的page_not_found视图,发现其主要做了以下内容:template.render(context = { 'request_path': quote(request.path), 'exception': exception.args[0]}, request) # quote为urllib.parse.quote # 作用是对url进行url编码因此我们可以在404.html中利用模板引擎拿到
context。其中:request_path是你当前的url,exception是异常类的信息,比如这样定义404.html:<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>PageNotFound</title> </head> <body> <h1>404页面</h1> <p>不存在的链接:{{ request_path }}</p> <p>提示:{{ exception }}</p> </body> </html>
第二种方法:
步骤:
- 在根urls中,定义
handler404,使其的值为自己处理404的视图 - 完成404视图的编写
比如:
# 根urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path("app01/", include("app01.urls")),
]
handler404 = "app01.views.page_not_found"
# 看有些博客用的是
# handler404 = views.page_not_found # handler404直接等于一个视图函数
# 但官方的例子是使用字符串的
# views.py
@requires_csrf_token
def page_not_found(request, exception):
# ....
# ....
return render(request, 'errors/404.html')
注意:一定要在根定义handler404,否则会老是用默认的!!!(我用的是django3.0.1,其他版本django不知道)
关于其他诸如400、403等页面,其自定义方法也是这样,不过是把
handler的视图或状态码.html换成对应的。对于前种方法,官方给了一些的例子:自定义报错视图
HttpRequest对象
关于HttRequest的各种属性见官方文档:HttpRequest。
一些常用的(表中的request指定的HttpRequest对象):
| 属性 | 说明 |
|---|---|
request.scheme |
协议http or https |
request.path |
返回完整路径,形如:”/app/index” |
request.path_info |
路径的路径信息部分 |
request.method |
HTTP 方法,大写 |
request.GET |
get参数,属于QueryDict对象 |
request.POST |
post参数, 属于QueryDict对象 |
request.COOKIES |
一个包含所有 cookies 的字典。键和值是字符串。 |
request.FILES |
一个类似字典的对象,包含所有上传的文件 |
request.headers |
请求中的所有HTTP头,是一个不区分大小写的类似字典的对象 |
取参数
这里的取参数指的是取get或者post的参数,由于request.GET和request.POST对属于QueryDict对象,所以先了解一下django.http.request.QueryDict对象:
QueryDict是对http请求数据的封装QueryDIct是dict的子类,所以很多使用方法和字典一样- 一般只需要使用get方法即可(和字典的一样用),其他方法点击这里查看。
所以取get和post参数:
def t(request):
if request.method == "GET":
print(request.GET.get("name"))
print(request.GET.get("age"))
elif request.method == "POST":
print(request.POST.get("name"))
print(request.POST.get("age"))
return HttpResponse("ok!")
注意使用POST方式提交时,要在form表单中加上
{% csrf_token %}标签,至于使用ajax提交post请求,见后文大标题
HttpResponse对象
HttpResponse对象则是你想要返回给浏览器的数据的封装。
我们编写的每个视图都要实例化、填充和返回一个HttpResponse对象。也就是函数的return值。HttpResponse可以传递一个string、bytes或者memoryview或可迭代对象。
常用属性
| 属性 | 说明 |
|---|---|
content |
响应的内容。bytes类型。 |
charset |
编码的字符集。 如果没指定,将会从content_type中解析出来。 |
status_code |
响应的状态码,比如200。 |
常用方法
| 方法 | 说明 |
|---|---|
has_header(header) |
检查头部中是否有给定的名称(不区分大小写),返回True或 False。 |
setdefault(header, value) |
当该头字段不存在时,设置一个头部 |
delete_cookie() |
删除一个Cookie,参数见下文 |
更多点击这里查看
设置与删除响应头字段:
from django.http import HttpResponse
def index(request):
response = HttpResponse()
# 通过中括号设置值
response["name"] = "lczmx"
response["age"] = 20
response.setdefault("age", 22)
# 使用del删除头字段
# key不存在时,不会报错
del response["name"]
return response
cookie与session
HTTP协议是无状态的协议。一旦数据交换完毕,客户端与服务器端的连接就会关闭,再次交换数据需要建立新的连接。这就意味着服务器无法从连接上跟踪会话。为了给客户端们一个通行证,W3C组织提出了Cookie。
Cookie实际上是一小段的文本信息。客户端请求服务器,如果服务器需要记录该用户状态,就使用response向客户端浏览器颁发一个Cookie。客户端浏览器会把Cookie保存起来。当浏览器再请求该网站时,浏览器把请求的网址连同该Cookie一同提交给服务 器。服务器检查该Cookie,以此来辨认用户状态。服务器还可以根据需要修改Cookie的内容。
由于Cookie存在不安全、大小有限制、受制于同源策略等缺点,所以除了使用Cookie,Web应用程序中还经常使用Session来记录客户端状态。Session是服务器端使用的一种记录客户端状态的机制,使用上比Cookie简单一些,相应的也增加了服务器的存储压力。
下面的内容需要注意方法的对象是respone还是request
Cookie
设置Cookie
使用HttpResponse对象的set_cookie方法设置一个 cookie。参数与 Python 标准库中的 Morsel cookie 对象相同:
HttpResponse.set_cookie(key, value='', max_age=None, expires=None, path='/', domain=None, secure=False, httponly=False, samesite=None)
key是cookie的键value是cookie的值max_age是cookie的生存周期
以秒为单位。如果设为None,浏览器开启期间保持,关闭后一同删除。expires是到期时间
格式为 “Wdy, DD-Mon-YY HH:MM:SS GMT” 的字符串,或者是 UTC 的 datetime.datetime 对象。
如果 expires 是一个 datetime 对象,将计算 max_age。
如:datetime.datetime.utcnow() + datetime.timedelta(days=7)domain用于设置跨域的Cookie
例如domain=”.lawrence.com”将设置一个www.lawrence.com、blogs.lawrence.com和calendars.lawrence.com都可读的Cookie。 否则,Cookie将只能被设置它的域读取。secure是否支持https,为True时支持httpshttponl,为True时阻止客户端的js代码访问cookie
注意cookie不要超过 4096 字节
获取Cookie
使用HttpRequest.COOKIES对象的get方法:
如:
request.Cookie.get("key")
删除Cookie
HttpResponse.delete_cookie(key, path='/', domain=None, samesite=None)
删除给定键的 cookie。如果键不存在,则静默失败。
key是键path和domain应该与set_cookie()中使用的值相同,否则Cookie不会删掉
例子
from django.http import HttpResponse
from django.shortcuts import render, redirect
from django.urls import reverse
def auth_login(req_func):
"""
用于检验登录信息的装饰器
:param req_func:
:return:
"""
def wap(*args, **kwargs):
req = args[0]
# 获取cookie
if req.COOKIES.get("username") != "lczmx":
url = reverse("app01:login") + "?next=%s" % req.path
return redirect(url)
return req_func(*args, **kwargs)
return wap
@auth_login
def index(request):
return render(request, "app01/index.html")
def login(request):
next_url = request.GET.get("next")
if request.method == "GET":
return render(request, "app01/login.html", {"next_url": next_url})
elif request.method == "POST":
username = request.POST.get("username")
pwd = request.POST.get("password")
if username == "lczmx" and pwd == "123456": # 假装验证了
ret = redirect(next_url)
# 设置cookie
ret.set_cookie("username", "lczmx")
else:
ret = HttpResponse("验证失败")
return ret
def logout(request):
ret = HttpResponse("success")
# 删除cookie
ret.delete_cookie("username")
return ret
Session
由于django的session依赖中间件:SessionMiddleware,所以要用session的时候不要去掉 MIDDLEWARE中的django.contrib.sessions.middleware.SessionMiddleware
session操作都要使用到HttpRequest.session对象,这是一个可读可写的,类似字典的对象,代表当前会话。
设置Session
request.session["name"] = "lczmx"
获取Session
request.session.get("lczmx")
删除Session
del request.session["lczmx"]
更多关于session的方法【包括session的保存方式】
设置session的保存时间
request.set_expiry(value)
为会话设置过期时间。你可以传递很多不同值:
- 如果 value 是整型,会话将在闲置数秒后过期。比如,调用
request.session.set_expiry(300)会使得会话在5分钟后过期。 - 如果 value 是一个
datetime或timedelta对象,会话将在指定的date/time过期。注意,如果你正在使用PickleSerializer,那么datetime和timedelta的值只能序列化。 - 如果 value 是 0 ,则当浏览器关闭后,用户会话 cookie 将过期。
- 如果 value 是 None ,会话会恢复为全局会话过期策略。
出于过期目的,读取会话不被视为活动。会话过期时间会在会话最后一次修改后开始计算。
django内置的装饰器
部分内容转载于这里:常用装饰器应用场景及正确使用方法
django.views.decorators.http
from django.views.decorators.http import require_http_methods
@require_http_methods(["GET", "POST"])
def my_view(request):
# I can assume now that only GET or POST requests make it this far
# ...
pass
一些类似的装饰器:
require_GET:装饰器可以要求视图只接受 GET 方法。
require_POST:装饰器可以要求视图只接受 POST 方法。
require_safe:装饰器可以要求视图只接收 GET 和 HEAD 方法。这些方法通常被认为是安全的,因为它们除了检索请求的资源外,没有特别的操作。
django.contrib.auth.decorators
权限验证
@login_required
@login_required是Django最常用的一个装饰器。其作用是在执行视图函数前先检查用户是否通过登录身份验证,并将未登录的用户重定向到指定的登录url。其中login_url是可选参数。如果不设置,默认login_url是settings.py里设置的LOGIN_URL。from django.contrib.auth.decorators import login_required @login_required(login_url='/accounts/login/') def my_view(request): ...@login_required还可以一个可选参数是redirect_field_name, 默认值是’next‘。from django.contrib.auth.decorators import login_required @login_required(redirect_field_name='my_redirect_field') def my_view(request): ...注意:
login_required装饰器不会检查用户是否是is_active状态。如果你想进一步限制登录验证成功的用户对某些视图函数的访问,你需要使用更强大的@user_passes_test装饰器。@user_passes_test
@user_passes_test装饰器的作用是对登录用户对象的信息进行判断,只有通过测试(返回值为True)的用户才能访问视图函数。不符合条件的用户会被跳转到指定的登录url。
@user_passes_test装饰器有一个必选参数,即对用户对象信息进行判断的函数。该函数必需接收user对象为参数、返回一个布尔值。其与login_required类似,@user_passes_test还有两个可选参数(login_url和redirect_field_name),这里就不多讲了。user_passes_test(func[,login_url=None, redirect_field_name=REDIRECT_FIELD_NAME])下例中
@user_passes_test装饰器对用户的email地址结尾进行判断,会把未通过测试的用户会定向到登录url。试想一个匿名用户来访问,她没有email地址,显然不能通过测试,登录后再来吧。from django.contrib.auth.decorators import user_passes_test def email_check(user): return user.email.endswith('@example.com') @user_passes_test(email_check) def my_view(request): ...如果需要加可选参数,可以按如下方式使用。
@user_passes_test(email_check, login_url='/login/'): def my_view(request): ...注意:
@user_passes_test不会自动的检查用户是否是匿名用户,但是@user_passes_test装饰器还是可以起到两层校验的作用。一来检查用户是否登录,二来检查用户是否符合某些条件,无需重复使用@login_required装饰器。
我们如果只允许is_active的登录用户访问某些视图,我们现在可以使用@user_passes_test装饰器轻松地解决这个问题,如下所示:from django.contrib.auth.decorators import user_passes_test @user_passes_test(lambda u: u.is_active) def my_view(request): ...@permission_required
@permission_required装饰器的作用是检查用户用户是否有特定权限,第一个参数perm是权限名,为必选, 第二个参数login_url为可选。permission_required(perm[, login_url=None, raise_exception=False])下例检查用户是否有
polls.can_vote的权限,没有的话定向至login_url。如果你设置了raise_exception=True, 会直接返回403无权限的错误,而不会跳转到登录页面。
那么问题来了,我们需要先使用@login_required来验证用户是否登录,再使用@permission_required装饰器来查看登录用户是否具有相关权限吗? 答案是不需要。
如果一个匿名用户来访问这个视图,显然该用户没有相关权限,会自动定向至登录页面。from django.contrib.auth.decorators import permission_required @permission_required('polls.can_vote', login_url='/login/') def my_view(request): ...
django.views.decorators.cache
这个模块下的装饰器适用于缓存的,缓存是Django装饰器很重要的一个应用场景。下面我们来看几个主要的缓存装饰器。注意: 使用以下装饰器的前提是你已经对缓存进行了相关设置。
@cache_page
该装饰器可以接收缓存的时间作为参数,比如下例缓存页面15分钟。from django.views.decorators.cache import cache_page @cache_page(60 * 15) def my_view(request): ...@cache_control
通常用户将会面对两种缓存: 他或她自己的浏览器缓存(私有缓存)以及他或她的提供者缓存(公共缓存)。 公共缓存由多个用户使用,而受其它人的控制。
这就产生了你不想遇到的敏感数据的问题,比如说你的银行账号被存储在公众缓存中。 因此,Web 应用程序需要以某种方式告诉缓存那些数据是私有的,哪些是公共的。
cache_control装饰器可以解决这个问题。from django.views.decorators.cache import cache_control @cache_control(private=True) def my_view(request): ...该修饰器负责在后台发送相应的 HTTP 头部。还有一些其他方法可以控制缓存参数。 例如, HTTP 允许应用程序执行如下操作:
- 定义页面可以被缓存的最大时间。
- 指定某个缓存是否总是检查较新版本,仅当无更新时才传递所缓存内容。
在 Django 中,可使用
cache_control视图修饰器指定这些缓存参数。 在下例中,cache_control告诉缓存对每次访问都重新验证缓存并在最长 3600 秒内保存所缓存版本。from django.views.decorators.cache import cache_control @cache_control(must_revalidate=True, max_age=3600) def my_view(request): ...在
cache_control中,任何合法的Cache-Control HTTP 指令都是有效的。下面是完整列表:public=True private=True no_cache=True no_transform=True must_revalidate=True proxy_revalidate=True max_age=num_seconds s_maxage=num_seconds@vary_on_headers
缺省情况下,Django 的缓存系统使用所请求的路径(如blog/article/1)来创建其缓存键。这意味着不同用户请求同样路径都会得到同样的缓存版本,不考虑客户端user-agent, cookie和语言配置的不同, 除非你使用Vary头部通知缓存机制需要考虑请求头里的cookie和语言的不同。
要在 Django 完成这项工作,可使用便利的vary_on_headers视图装饰器。例如下面代码告诉Django读取缓存数据时需要同时考虑User-Agent和Cookie的不同。与此类似的装饰器还有@vary_on_cookie。from django.views.decorators.vary import vary_on_headers @vary_on_headers('User-Agent', 'Cookie') def my_view(request): ...@never_cache
如果你想用头部完全禁掉缓存, 你可以使用@never_cache装饰器。如果你不在视图中使用缓存,服务器端是肯定不会缓存的,然而用户的客户端如浏览器还是会缓存一些数据,这时你可以使用never_cache禁用掉客户端的缓存。from django.views.decorators.cache import never_cache @never_cache def myview(request): # ...
其它常用装饰器
@method_decorator
前面的案例中,我们的装饰器都是直接使用在函数视图上的。如果需要在基于类的视图上使用装饰器,我们需要使用到@method_decorator这个装饰器, 它的作用是将类伪装成函数方法。@method_decorator第一个参数一般是需要使用的装饰器名。
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from django.views.generic import TemplateView
@method_decorator(login_required, name='dispatch')
class ProtectedView(TemplateView):
template_name = 'secret.html'
常用自定义装饰器
关于一些自定义装饰器,见:详解Django中六个常用的自定义装饰器
使用多重装饰器
你可以在一个函数或基于类的视图上使用多重装饰器,但一定要考虑装饰器执行的先后顺序。比如下例中会先执行@never_cache, 再执行@login_required。
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from django.views.decorators.cache import never_cache
@method_decorator(never_cache, name='dispatch')
@method_decorator(login_required, name='dispatch')
class ProtectedView(TemplateView):
template_name = 'secret.html'
class ProtectedView(TemplateView):
template_name = 'secret.html'
上例等同于:
decorators = [never_cache, login_required]
@method_decorator(decorators, name='dispatch')
class ProtectedView(TemplateView):
template_name = 'secret.html'
上传及下载文件
- TODO
FBV及CBV
FBV是指以函数的形式定义视图,而CBV是指以类的形式定义视图。
二者各有优点,FBV胜在灵活、简便,而CBV在某些场景中可以极大地降低代码复用。
要使用类视图,主要有一下几个要点:
views.py中自定义一个继承django.views.View的类- 为自定义类添加请求方法的方法,即get请求就添加get方法
- 在
urls.py中,指定path的第二个参数为类名.as_view()
# views.py
from django.views import View
class CView(View):
def get(self, request):
return render(request, "app01/index.html")
# urls.py
from .views import CView
app_name = "app01"
urlpatterns = [
path("cview/", CView.as_view(), name="cview"),
]
除了django.views.View外,django还有一些内置的类视图,点击查看:内置基于类的视图 API。
模板
配置
在settings.py中配置,BACKEND表示要使用那种模板引擎;DIRS表示template的目录(可以是多个);APP_DIRS表示是否使用app中的template目录(即形如这样的app/templates/);OPTIONS是DjangoTemplates的一些参数,默认即可。
比如:
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [
os.path.join(BASE_DIR, "templates"),
],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
要是想使用Jinja2:把BACKEND改为django.template.backends.jinja2.Jinja2即可。
当使用多个模板引擎时,django会按照列表的顺序找到html文件,然后渲染。需要注意的是:django查找到任何一个匹配的模板后便停止搜寻,所以这是个类似url搜索的短路操作!
使用
一般来说我们可以使用django.shortcuts.render指定template文件,然后进行渲染:
from django.shortcuts import render
def index(request):
return render(request, "app01/index.html")
render背后其实是这样的:
from django.template import loader
from django.http import HttpResponse
def render(request, template_name, context=None, content_type=None, status=None, using=None):
content = loader.render_to_string(template_name, context, request, using=using)
return HttpResponse(content, content_type, status)
render的参数
request是一个HttpRequest对象template_name:一般来说一个HTML文件,其路径需要在settings中配置好context:需要传一个字典,里面的键值对可以在html页面中供模板引擎使用content_type:内容类型
模板语言
django作为一个重型web框架,自然少不了模板语言。这主要是用于动态渲染HTML页面的。
变量
使用两个大括号获取,对于对象的属性等数据可以在其基础上是用.获取。对于Model对象,可以通过.来取值,也可以使用.xxx_set的方式进行反向查找,但查找操作不太适合在模板中完成。
比如:
# views.py
from django.shortcuts import render
def index(request):
data = {
"username": "lczmx",
"age": 22,
}
msg_list = ["1234", "abcd", "6666"]
print(request.session.get(""))
return render(request, "app01/index.html", {"data": data, "msg_list": msg_list})
<!-- app01/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>app01 index</title>
</head>
<body>
<p>{{ data.username }}</p>
<p>{{ data.age }}</p>
<p>{{ msg_list.0 }}</p>
</body>
</html>
标签
django模板引擎的标签就相当于python的代码块,以{% %}的定义。
django已经为我们贴心地内置了一下标签 :
| 标签 | 作用 |
|---|---|
autoescape |
自动转义开关 |
block |
块引用 |
comment |
注释 |
csrf_token |
CSRF令牌 |
cycle |
循环对象的值 |
debug |
调试模式 |
extends |
继承模版 |
filter |
过滤功能 |
firstof |
输出第一个不为False的参数 |
for |
循环对象 |
for … empty |
带empty说明的循环 |
if |
条件判断 |
ifchanged |
如果有变化,则.. |
include |
导入子模版的内容 |
load |
加载标签和过滤器 |
lorem |
生成无用的废话 |
now |
当前时间 |
regroup |
根据对象重组集合 |
resetcycle |
重置循环 |
spaceless |
去除空白 |
templatetag |
转义模版标签符号 |
url |
获取url字符串 |
verbatim |
禁用模版引擎 |
widthratio |
宽度比例 |
with |
上下文变量管理器 |
对于使用方法,见:Django Tag
as语法的使用:
使用as语法可以将返回值赋值给一个变量,如:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>app01 index</title>
</head>
<body>
{% url "app01:index" as index_url %}
<a href="{{ index_url }}">index page</a>
</body>
</html>
模板继承:extends与block结合,可以让你的一些模板继承其他模板,从而极大地减少代码的复用程度。
模板继承允许你建立一个基本的“骨架”模板,它包含了你网站的所有常用元素,并定义了子模板可以覆盖的 块。
我们先看一个例子,看看模板继承:
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="stylesheet" href="style.css">
<title>{% block title %}My amazing site{% endblock %}</title>
</head>
<body>
<div id="sidebar">
{% block sidebar %}
{% endblock %}
</div>
<div id="content">
{% block content %}{% endblock %}
</div>
</body>
</html>
这个模板,我们称之为base.html,它定义了一个 HTML 骨架文档,你可以用它来制作一个两栏式页面。“子”模板的工作是用内容填充空块。
在这个例子中,block 标签定义了三个块,子模板可以填入其中。block 标签所做的就是告诉模板引擎,子模板可以覆盖模板的这些部分。
一个子模板可能是这样的:
{% extends "base.html" %}
{% block title %}My amazing blog{% endblock %}
{% block content %}
{% for entry in blog_entries %}
{{ entry.title }}
{{ entry.body }}
{% endfor %}
{% endblock %}
extends 标签是这里的关键。它告诉模板引擎,这个模板“扩展”了另一个模板。当模板系统执行这个模板时,首先要找到父模板——在本例中是“base.html”。
这时,模板引擎会注意到 base.html 中的三个 block 标签,然后用子模板的内容替换这些块。根据 blog_entries 的值,输出可能是这样的:
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="stylesheet" href="style.css">
<title>My amazing blog</title>
</head>
<body>
<div id="sidebar">
<ul>
<li><a href="/">Home</a></li>
<li><a href="/blog/">Blog</a></li>
</ul>
</div>
<div id="content">
<h2>Entry one</h2>
<p>This is my first entry.</p>
<h2>Entry two</h2>
<p>This is my second entry.</p>
</div>
</body>
</html>
请注意,由于子模板没有定义 sidebar 块,所以使用父模板的值来代替。父模板中 <!–swig17–>
为了增加可读性,你可以给你的 `{% endblock %}` 标签 起一个 名字。例如:
```django
{% block content %}
...
{% endblock content %
}
```
在较大的模板中,这种技术可以帮助你看到哪些`{% block %}` 标签正在被关闭。
最后,请注意,你**不能在同一个模板中定义多个同名的 block 标签**。
#### 过滤器
过滤器就相当于python语法中的函数,其使用语法是:`{{ 变量 | 过滤器:参数 }}`,其中,对于没有参数的过滤器来说,`:参数`这部分省略。
**内置过滤器**:
|过滤器 | 说明 |
| -- | -- |
`add` | 加法
`addslashes`| 添加斜杠
`capfirst`| 首字母大写
`center`| 文本居中
`cut` | 切除字符
`date`| 日期格式化
`default` | 设置默认值
`default_if_none` | 为None设置默认值
`dictsort` | 字典排序
`dictsortreversed` | 字典反向排序
`divisibleby`| 整除判断
`escape` | 转义
`escapejs` | 转义js代码
`filesizeformat` | 文件尺寸人性化显示
`first` | 第一个元素
`floatformat` | 浮点数格式化
`force_escape`| 强制立刻转义
`get_digit`| 获取数字
`iriencode`| 转换IRI
`join` | 字符列表链接
`json_script`| 生成script标签,带json数据
`last` | 最后一个
`length` | 长度
`length_is` | 长度等于
`linebreaks` | 行转换
`linebreaksbr`| 行转换
`linenumbers`| 行号
`ljust` | 左对齐
`lower` | 小写
`make_list` | 分割成字符列表
`phone2numeric` | 电话号码
`pluralize`| 复数形式
`pprint`| 调试
`random`| 随机获取
`rjust` | 右对齐
`safe`| 安全确认
`safeseq`| 列表安全确认
`slice`| 切片
`slugify` | 转换成ASCII
`stringformat`| 字符串格式化
`striptags`| 去除HTML中的标签
`time`| 时间格式化
`timesince`| 从何时开始
`timeuntil`| 到何时多久
`title`| 所有单词首字母大写
`truncatechars`| 截断字符
`truncatechars_html`| 截断字符
`truncatewords`| 截断单词
`truncatewords_html`| 截断单词
`unordered_list` | 无序列表
`upper` | 大写
`urlencode` | 转义url
`urlize` | url转成可点击的链接
`urlizetrunc`| urlize的截断方式
`wordcount` | 单词计数
`wordwrap` | 单词包裹
`yesno` | 将`True`,`False`和`None`,映射成字符串‘yes’,‘no’,‘maybe’
一般的使用例子,以`add`为例:
```django
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>app01 index</title>
</head>
<body>
<p>你明年的年龄是{{ data.age | add:1 }}</p>
</body>
</html>
其他的内置过滤的参考。
关于safe字符串,见:几种safe字符串的方法
自定义标签及过滤器
一般来说,默认的标签和过滤器已经够我们应付简单场景的了,面对复杂的需求的话,我们可以使用自定义标签和过滤器的办法。
自定义标签及过滤器的位置就是 Django 应用内。如果它们关联至某个已存在的应用,在那里将它们打包就很有用;否则,它们能被添加至新应用。当一个 Django 应用被添加至 INSTALLED_APPS,所以其在常规位置定义的标签都可以在模板中自动加载。
自定义标签:
- app中创建一个名为
templatetags的Python 包 - 在
templatetags下创建一个py文件,用于放标签函数 - 创建一个函数
- 使用
django.template.Library对象的simple_tag方法装饰,可以指定name参数,表示标签名,空的时候为函数名 - 重启django
- 在template中使用load标签将自定义标签引入
- 以
{% xx %}的形式使用
例子:
# 第1和第2步省略
# app的目录机构(部分):
# app01
# ├── admin.py
# ├── apps.py
# ├── templatetags
# │ ├── __init__.py
# │ ├── mytags.py
# │ └── __pycache__
# │ ├── __init__.cpython-38.pyc
# │ └── mytags.cpython-38.pyc
# ├── tests.py
# ├── urls.py
# └── views.py
# app01/templatetags/mytags.py
from django import template
# 第4步
register = template.Library()
# 指定name参数为t1,在template中就只能用t1了
@register.simple_tag(name="t1")
def my_tag(x, y, *args):
print(x, y)
print(args)
return "t1"
{% load t1 from mytags %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>app01 index</title>
</head>
<body>
{% t1 12 234 134 %}
</body>
</html>
自定义过滤器:
- app中创建一个名为
templatetags的Python 包 - 在
templatetags下创建一个py文件,用于放过滤器函数 - 创建一个函数
- 使用
django.template.Library对象的filter方法装饰,可以指定name参数,表示过滤器名,空的时候为函数名 - 重启django
- 在template中使用load标签将自定义过滤器引入
- 以
{{ xx | yy:z}}的形式使用
filter和tag很像,就装饰器、函数参数、模板使用方法有所区别,例子:
# app01/templatetags/myfilter.py
from django import template
register = template.Library()
@register.filter(name="f1")
def my_filter(x, y, z):
print(x, y, z)
return "f1"
{% load f1 from myfilter %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>app01 index</title>
</head>
<body>
{{ "hello"|f1:123 }}
</body>
</html>
load标签的使用:
load是用于加载一个自定义模板标签集。
上面在例子使用了load标签,下面记录一下其语法。
在使用时可以把load类比成python的import
比如现在在templatetags下有两个文件,每个文件中有一个标签或过滤器函数:
# app/templatetags/myfilter.py
from django import template
register = template.Library()
@register.filter(name="f1")
def my_filter(x, y):
print(x, y)
return "f1"
# ########################################################## #
# app/templatetags/myfilter.py
from django import template
register = template.Library()
@register.simple_tag(name="t1")
def my_tag(x, y, *args):
print(x, y)
print(args)
return "t1"
那么就有两种方法加载。
第一种:
{% load mytags %}
{% load myfilter %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>app01 index</title>
</head>
<body>
{% t1 12 234 134 %}
{{ "hello"|f1:123 }}
</body>
</html>
方法二:
此方法可以同时加载多个,以空格隔开,如:{% load f1 f2 from myfliter %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>app01 index</title>
</head>
<body>
{% t1 12 234 134 %}
{{ "hello"|f1:123 }}
</body>
</html>
model
model是django最复杂和重要的一层,负责与数据库打交道。
在使用时,我们在app/models.py中定义一个django.db.models.Model的子类,这相当于数据库中的一张表,类里面的字段(属性)相当于数据库中的字段,该类的对象相当于一条数据。
简单例子
# app01/models.py
from django.db import models
class Question(models.Model):
question_text = models.CharField(max_length=64)
假如我们以这个类创建表,那么其表名默认的为app01_question(app名_小写类名)。而且django会自动创建一个自增主键id。
使用Model:
- 必须在
INSTALL_APP中添加本app - 使用命令
python manage.py makemigrations [app名字]创建记录文件python manage.py migrate同步到数据库
migrate命令将遍历INSTALLED_APPS设置中的所有项目,在数据库中创建对应的表,并打印出每一条动作信息。migrate命令对所有还未实施的迁移记录进行操作,本质上就是将你对模型的修改体现到数据库中具体的表中。
Django通过一张叫做django_migrations的表,记录并跟踪已经实施的migrate动作,通过对比获得哪些迁移尚未提交。makemigrations命令创建记录文件,方便git使用。
字段
字段实质上就是类属性,由于django的查询语法有__,所以对于字段的名称要一下要求:
- 不能为clean、save、delete等Django内置的模型API名字
- 不能包含
__
from django.db import models
class Musician(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
instrument = models.CharField(max_length=100)
例子中的first_name、last_name、instrument都为字段,models.CharField为一个django内置的字段类型,在数据库中就相当于数据类型。
内置字段
django Model中定义了一些常用的内置字段,下面列出常用的内置字段类型
| 类型 | 说明 |
|---|---|
AutoField |
一个自动增加的整数类型字段。通常你不需要自己编写它,Django会自动帮你添加字段:id = models.AutoField(primary_key=True),这是一个自增字段,从1开始计数。如果你非要自己设置主键,那么请务必将字段设置为primary_key=True。Django在一个模型中只允许有一个自增字段,并且该字段必须为主键! |
BigAutoField |
64位整数类型自增字段,数字范围更大,从1到9223372036854775807 |
BigIntegerField |
64位整数字段(看清楚,非自增),类似IntegerField ,-9223372036854775808 到9223372036854775807。在Django的模板表单里体现为一个NumberInput标签。 |
BinaryField |
二进制数据类型。较少使用。 |
BooleanField |
布尔值类型。默认值是None。在HTML表单中体现为CheckboxInput标签。如果设置了参数null=True,则表现为NullBooleanSelect选择框。可以提供default参数值,设置默认值。 |
CharField |
最常用的类型,字符串类型。必须接收一个max_length参数,表示字符串长度不能超过该值。默认的表单标签是text input。 |
TextField |
用于储存大量的文本内容,在HTML中表现为Textarea标签,最常用的字段类型之一!如果你为它设置一个max_length参数,那么在前端页面中会受到输入字符数量限制,然而在模型和数据库层面却不受影响。只有CharField才能同时作用于两者。 |
DateField |
class DateField(auto_now=False, auto_now_add=False, **options) , 日期类型。一个Python中的datetime.date的实例。在HTML中表现为DateInput标签。在admin后台中,Django会帮你自动添加一个JS日历表和一个“Today”快捷方式,以及附加的日期合法性验证。两个重要参数:(参数互斥,不能共存) auto_now:每当对象被保存时将字段设为当前日期,常用于保存最后修改时间。auto_now_add:每当对象被创建时,设为当前日期,常用于保存创建日期(注意,它是不可修改的)。设置上面两个参数就相当于给field添加了editable=False和blank=True属性。如果想具有修改属性,请用default参数。例子:pub_time = models.DateField(auto_now_add=True),自动添加发布时间。 |
| DateTimeField | 日期时间类型。Python的datetime.datetime的实例。与DateField相比就是多了小时、分和秒的显示,其它功能、参数、用法、默认值等等都一样。 |
| DecimalField | 固定精度的十进制小数。相当于Python的Decimal实例,必须提供两个指定的参数!参数max_digits:最大的位数,必须大于或等于小数点位数 。decimal_places:小数点位数,精度。 当localize=False时,它在HTML表现为NumberInput标签,否则是textInput类型。例子:储存最大不超过999,带有2位小数位精度的数,定义如下:models.DecimalField(…, max_digits=5, decimal_places=2)。 |
| TimeField | 时间字段,Python中datetime.time的实例。接收同DateField一样的参数,只作用于小时、分和秒。 |
| DurationField | 持续时间类型。存储一定期间的时间长度。类似Python中的timedelta。在不同的数据库实现中有不同的表示方法。常用于进行时间之间的加减运算。但是小心了,这里有坑,PostgreSQL等数据库之间有兼容性问题! |
| EmailField | 邮箱类型,默认max_length最大长度254位。使用这个字段的好处是,可以使用Django内置的EmailValidator进行邮箱格式合法性验证。 |
| FileField | class FileField(upload_to=None, max_length=100, **options)上传文件类型,后面单独介绍。 |
| FilePathField | 文件路径类型,后面单独介绍 |
| FloatField | 浮点数类型,对应Python的float。参考整数类型字段。 |
| ImageField | 图像类型,后面单独介绍。 |
| IntegerField | 整数类型,最常用的字段之一。取值范围-2147483648到2147483647。在HTML中表现为NumberInput或者TextInput标签。 |
| GenericIPAddressField | class GenericIPAddressField(protocol='both', unpack_ipv4=False, **options),IPV4或者IPV6地址,字符串形式,例如192.0.2.30或者2a02:42fe::4。在HTML中表现为TextInput标签。参数protocol默认值为‘both’,可选‘IPv4’或者‘IPv6’,表示你的IP地址类型。 |
| JSONField | JSON类型字段。Django3.1新增。签名为class JSONField(encoder=None,decoder=None,**options)。其中的encoder和decoder为可选的编码器和解码器,用于自定义编码和解码方式。如果为该字段提供default值,请务必保证该值是个不可变的对象,比如字符串对象。 |
| PositiveBigIntegerField | 正的大整数,0到9223372036854775807 |
| PositiveIntegerField | 正整数,从0到2147483647 |
| PositiveSmallIntegerField | 较小的正整数,从0到32767 |
| SlugField | slug是一个新闻行业的术语。一个slug就是一个某种东西的简短标签,包含字母、数字、下划线或者连接线,通常用于URLs中。可以设置max_length参数,默认为50。 |
| SmallAutoField | Django3.0新增。类似AutoField,但是只允许1到32767。 |
| SmallIntegerField | 小整数,包含-32768到32767。 |
| URLField | 一个用于保存URL地址的字符串类型,默认最大长度200。 |
| UUIDField | 用于保存通用唯一识别码(Universally Unique Identifier)的字段。使用Python的UUID类。在PostgreSQL数据库中保存为uuid类型,其它数据库中为char(32)。这个字段是自增主键的最佳替代品。 |
使用UUIDField
UUIDField
数据库无法自己生成uuid,因此需要如下使用default参数:
import uuid
from django.db import models
class MyUUIDModel(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
# 注意不要写成default=uuid.uuid4()
# 其它字段
字段参数
所有的模型字段都可以接收一定数量的参数,比如CharField至少需要一个max_length参数。下面的这些参数是所有字段都可以使用的,并且是可选的。
null
该值为True时,Django在数据库用NULL保存空值。默认值为False。对于保存字符串类型数据的字段,请尽量避免将此参数设为True,那样会导致两种‘没有数据’的情况,一种是NULL,另一种是空字符串’’。Django 的惯例是使用空字符串而不是 NULL。blank
True时,字段可以为空。默认False。和null参数不同的是,null是纯数据库层面的,而blank是验证相关的,它与表单验证是否允许输入框内为空有关,与数据库无关。所以要小心一个null为False,blank为True的字段接收到一个空值可能会出bug或异常。verbose_name
为字段设置一个人类可读,更加直观的别名。
对于每一个字段类型,除了ForeignKey、ManyToManyField和OneToOneField这三个特殊的关系类型,其第一可选位置参数都是verbose_name。如果没指定这个参数,Django会利用字段的属性名自动创建它,并将下划线转换为空格。下面这个例子的verbose name是”person’s first name”:
first_name = models.CharField("person's first name", max_length=30)
下面这个例子的verbose name是”first name”:
first_name = models.CharField(max_length=30)
对于外键、多对多和一对一字字段,由于第一个参数需要用来指定关联的模型,因此必须用关键字参数verbose_name来明确指定。如下:poll = models.ForeignKey( Poll, on_delete=models.CASCADE, verbose_name="the related poll", ) sites = models.ManyToManyField(Site, verbose_name="list of sites") place = models.OneToOneField( Place, on_delete=models.CASCADE, verbose_name="related place", )另外,你无须大写verbose_name的首字母,Django自动为你完成这一工作。
default
字段的默认值,可以是值或者一个可调用对象。如果是可调用对象,那么每次创建新对象时都会调用。设置的默认值不能是一个可变对象,比如列表、集合等等。lambda匿名函数也不可用于default的调用对象,因为匿名函数不能被migrations序列化。
注意:在某种原因不明的情况下将default设置为None,可能会引发intergyerror:not null constraint failed,即非空约束失败异常,导致python manage.py migrate失败,此时可将None改为False或其它的值,只要不是None就行。primary_key
如果你没有给模型的任何字段设置这个参数为True,Django将自动创建一个AutoField自增字段,名为‘id’,并设置为主键。也就是id = models.AutoField(primary_key=True)。
如果你为某个字段设置了primary_key=True,则当前字段变为主键,并关闭Django自动生成id主键的功能。
primary_key=True隐含null=False和unique=True的意思。一个模型中只能有一个主键字段!
另外,主键字段不可修改,如果你给某个对象的主键赋个新值实际上是创建一个新对象,并不会修改原来的对象。
注:主键可以使用”pk”代指,如:Question.object.get(pk=1)from django.db import models class Fruit(models.Model): name = models.CharField(max_length=100, primary_key=True) ############### >>>fruit = Fruit.objects.create(name='Apple') >>>fruit.name = 'Pear' >>>fruit.save() >>>Fruit.objects.values_list('name', flat=True) ['Apple', 'Pear']unique
为True时,在整个数据表内该字段的数据不可重复。
注意:对于ManyToManyField和OneToOneField关系类型,该参数无效。
注意: 当unique=True时,db_index参数无须设置,因为unqiue隐含了索引。editable
如果设为False,那么当前字段将不会在admin后台或者其它的ModelForm表单中显示,同时还会被模型验证功能跳过。参数默认值为True。error_messages
用于自定义错误信息。参数接收字典类型的值。
字典的键可以是null、 blank、 invalid、 invalid_choice、 unique和unique_for_date其中的一个。help_text
额外显示在表单部件上的帮助文本。即便你的字段未用于表单,它对于生成文档也是很有用的。
该帮助文本默认情况下是可以带HTML代码的,具有风险:
help_text="Please use the following format: <em>YYYY-MM-DD</em>."
所以使用时请注意转义为纯文本,防止脚本攻击。choices
用于页面上的选择框标签,需要先提供一个二维的二元元组,第一个元素表示存在数据库内真实的值,第二个表示页面上显示的具体内容。在浏览器页面上将显示第二个元素的值。例如:YEAR_IN_SCHOOL_CHOICES = ( ('FR', 'Freshman'), ('SO', 'Sophomore'), ('JR', 'Junior'), ('SR', 'Senior'), ('GR', 'Graduate'), )一般来说,最好将选项定义在类里,并取一个直观的名字,如下所示:
from django.db import models class Student(models.Model): FRESHMAN = 'FR' SOPHOMORE = 'SO' JUNIOR = 'JR' SENIOR = 'SR' YEAR_IN_SCHOOL_CHOICES = ( (FRESHMAN, 'Freshman'), (SOPHOMORE, 'Sophomore'), (JUNIOR, 'Junior'), (SENIOR, 'Senior'), ) year_in_school = models.CharField( max_length=2, choices=YEAR_IN_SCHOOL_CHOICES, default=FRESHMAN, ) def is_upperclass(self): return self.year_in_school in (self.JUNIOR, self.SENIOR)注意:每当 choices 的顺序变动时将会创建新的迁移。
如果一个模型中有多个字段需要设置choices,可以将这些二维元组组合起来,显得更加整洁优雅,例如下面的做法:
MEDIA_CHOICES = [ ('Audio', ( ('vinyl', 'Vinyl'), ('cd', 'CD'), ) ), ('Video', ( ('vhs', 'VHS Tape'), ('dvd', 'DVD'), ) ), ('unknown', 'Unknown'), ]反过来,要获取一个choices的第二元素的值,可以使用
get_FOO_display()方法,其中的FOO用字段名代替。对于下面的例子:from django.db import models class Person(models.Model): SHIRT_SIZES = ( ('S', 'Small'), ('M', 'Medium'), ('L', 'Large'), ) name = models.CharField(max_length=60) shirt_size = models.CharField(max_length=1, choices=SHIRT_SIZES)使用方法:
>>>p = Person(name="Fred Flintstone", shirt_size="L") >>>p.save() >>>p.shirt_size 'L' >>>p.get_shirt_size_display() 'Large'从Django3.0开始,新增了
TextChoices、IntegerChoices和Choices三个类,用来达到类似Python的enum枚举库的作用,
如果文本或数字类型不满足你的要求,你也可以继承Choice类,自己写。比如下面就创建了一个时间类型选项的choices类:class MoonLandings(datetime.date, models.Choices): APOLLO_11 = 1969, 7, 20, 'Apollo 11 (Eagle)' APOLLO_12 = 1969, 11, 19, 'Apollo 12 (Intrepid)' APOLLO_14 = 1971, 2, 5, 'Apollo 14 (Antares)' APOLLO_15 = 1971, 7, 30, 'Apollo 15 (Falcon)' APOLLO_16 = 1972, 4, 21, 'Apollo 16 (Orion)' APOLLO_17 = 1972, 12, 11, 'Apollo 17 (Challenger)'最后,如果想设置空标签,可以参考下面的做法:
class Answer(models.IntegerChoices): NO = 0, _('No') YES = 1, _('Yes') __empty__ = _('(Unknown)')db_column
该参数用于定义当前字段在数据表内的列名。如果未指定,Django将使用字段名作为列名。db_index
该参数接收布尔值。如果为True,数据库将为该字段创建索引。db_tablespace
用于字段索引的数据库表空间的名字,前提是当前字段设置了索引。默认值为工程的DEFAULT_INDEX_TABLESPACE设置。如果使用的数据库不支持表空间,该参数会被忽略。unique_for_date
日期唯一。
可能不太好理解。举个栗子,如果你有一个名叫title的字段,并设置了参数unique_for_date=”pub_date”,那么Django将不允许有两个模型对象具备同样的title和pub_date。有点类似联合约束。unique_for_month
同上,只是月份唯一。unique_for_year
同上,只是年份唯一。validators
运行在该字段上的验证器的列表。
Meta类
模型的元数据是对字段的补充,是所有不是字段的东西,比如排序选项( ordering ),数据库表名( db_table ),或是阅读友好的单复数名( verbose_name 和 verbose_name_plural )。这些都不是必须的,并且在模型当中添加 Meta类 也完全是可选的。
verbose_name
最常用的元数据之一!用于设置模型对象的直观、人类可读的名称,用于在各种打印、页面展示等场景。可以用中文。例如:
verbose_name = "story"
verbose_name = "披萨"
如果你不指定它,那么Django会使用小写的模型名作为默认值。verbose_name_plural
英语有单数和复数形式。这个就是模型对象的复数名,比如“apples”。因为我们中文通常不区分单复数,所以保持和verbose_name一致也可以。
verbose_name_plural = "stories"
verbose_name_plural = "披萨"
verbose_name_plural = verbose_name
如果不指定该选项,那么默认的复数名字是verbose_name加上‘s’ordering
最常用的元数据之一了!
用于指定该模型生成的所有对象的排序方式,接收一个字段名组成的元组或列表。默认按升序排列,如果在字段名前加上字符“-”则表示按降序排列,如果使用字符问号“?”表示随机排列。请看下面的例子:
这个顺序是你通过查询语句,获得Queryset后的列表内元素的顺序,切不可和前面的get_latest_by等混淆。ordering = ['pub_date'] # 表示按'pub_date'字段进行升序排列 ordering = ['-pub_date'] # 表示按'pub_date'字段进行降序排列 ordering = ['-pub_date', 'author'] # 表示先按'pub_date'字段进行降序排列,再按`author`字段进行升序排列。unique_together
这个元数据是非常重要的一个!它等同于数据库的联合约束!
举个例子,假设有一张用户表,保存有用户的姓名、出生日期、性别和籍贯等等信息。要求是所有的用户唯一不重复,可现在有好几个叫“张伟”的,如何区别它们呢?(不要和我说主键唯一,这里讨论的不是这个问题)
我们可以设置不能有两个用户在同一个地方同一时刻出生并且都叫“张伟”,使用这种联合约束,保证数据库能不能重复添加用户(也不要和我谈小概率问题)。在Django的模型中,如何实现这种约束呢?
使用unique_together,也就是联合唯一!比如:
unique_together = [['name', 'birth_day', 'address'],......]
这样,哪怕有两个在同一天出生的张伟,但他们的籍贯不同,也就是两个不同的用户。一旦三者都相同,则会被Django拒绝创建。这个元数据选项经常被用在admin后台,并且强制应用于数据库层面。
unique_together接收一个二维的列表,每个元素都是一维列表,表示一组联合唯一约束,可以同时设置多组约束。为了方便,对于只有一组约束的情况下,可以简单地使用一维元素,例如:
unique_together = ['name', 'birth_day', 'address']
联合唯一无法作用于普通的多对多字段。index_together
联合索引,用法和特性类似unique_together。abstract
如果abstract=True,那么模型会被认为是一个抽象模型。抽象模型本身不实际生成数据库表,而是作为其它模型的父类,被继承使用。具体内容可以参考Django模型的继承。app_label
如果定义了模型的app没有在INSTALLED_APPS中注册,则必须通过此元选项声明它属于哪个app,例如:
app_label = 'myapp'base_manager_name
模型的_base_manager管理器的名字,默认是’objects’。模型管理器是Django为模型提供的API所在。db_table
指定在数据库中,当前模型生成的数据表的表名。比如:
db_table = 'my_freinds'
如果你没有指定这个选项,那么Django会自动使用app名和模型名,通过下划线连接生成数据表名,比如app_book。
不要使用SQL语言或者Python的保留字,注意冲突。
友情建议:使用MySQL和MariaDB数据库时,db_table用小写英文。db_tablespace
自定义数据库表空间的名字。默认值是项目的DEFAULT_TABLESPACE配置项指定的值。default_manager_name
模型的_default_manager管理器的名字。default_related_name
默认情况下,从一个模型反向关联设置有关系字段的源模型,我们使用<model_name>_set,也就是源模型的名字+下划线+set。
这个元数据选项可以让你自定义反向关系名,同时也影响反向查询关系名!看下面的例子:from django.db import models class Foo(models.Model): pass class Bar(models.Model): foo = models.ForeignKey(Foo, on_delete=models.CASCADE) class Meta: default_related_name = 'bars' # 关键在这里具体的使用差别如下:
>>>bar = Bar.objects.get(pk=1) >>># 不能再使用"bar"作为反向查询的关键字了。 >>>Foo.objects.get(bar=bar) >>># 而要使用你自己定义的"bars"了。 >>>Foo.objects.get(bars=bar)get_latest_by
Django管理器给我们提供有latest()和earliest()方法,分别表示获取最近一个和最前一个数据对象。但是,如何来判断最近一个和最前面一个呢?也就是根据什么来排序呢?
get_latest_by元数据选项帮你解决这个问题,它可以指定一个类似 DateField、DateTimeField或者IntegerField这种可以排序的字段,作为latest()和earliest()方法的排序依据,从而得出最近一个或最前面一个对象。例如:get_latest_by = "order_date" # 根据order_date升序排列 get_latest_by = ['-priority', 'order_date'] # 根据priority降序排列,如果发生同序,则接着使用order_date升序排列managed
该元数据默认值为True,表示Django将按照既定的规则,管理数据库表的生命周期。
如果设置为False,将不会针对当前模型创建和删除数据库表,也就是说Django暂时不管这个模型了。
在某些场景下,这可能有用,但更多时候,你可以忘记该选项。order_with_respect_to
这个选项不好理解。其用途是根据指定的字段进行排序,通常用于关系字段。看下面的例子:from django.db import models class Question(models.Model): text = models.TextField() # ... class Answer(models.Model): question = models.ForeignKey(Question, on_delete=models.CASCADE) # ... class Meta: order_with_respect_to = 'question'上面在Answer模型中设置了order_with_respect_to = ‘question’,这样的话,Django会自动提供两个API,get_RELATED_order()和set_RELATED_order(),其中的RELATED用小写的模型名代替。假设现在有一个Question对象,它关联着多个Answer对象,下面的操作返回包含关联的Anser对象的主键的列表
[1,2,3]:>>>question = Question.objects.get(id=1) >>>question.get_answer_order() [1, 2, 3]我们可以通过set_RELATED_order()方法,指定上面这个列表的顺序:
>>>question.set_answer_order([3, 1, 2])同样的,关联的对象也获得了两个方法
get_next_in_order()和get_previous_in_order(),用于通过特定的顺序访问对象,如下所示:>>>answer = Answer.objects.get(id=2) >>>answer.get_next_in_order() <Answer: 3> >>>answer.get_previous_in_order() <Answer: 1>permissions
该元数据用于当创建对象时增加额外的权限。它接收一个所有元素都是二元元组的列表或元组,每个元素都是(权限代码, 直观的权限名称)的格式。比如下面的例子:
这个Meta选项非常重要,和auth框架的权限系统紧密相关。
permissions = (("can_deliver_pizzas", "可以送披萨"),)default_permissions
Django默认会在建立数据表的时候就自动给所有的模型设置(‘add’, ‘change’, ‘delete’)的权限,也就是增删改。你可以自定义这个选项,比如设置为一个空列表,表示你不需要默认的权限,但是这一操作必须在执行migrate命令之前。也是配合auth框架使用。proxy
如果设置了proxy = True,表示使用代理模式的模型继承方式。具体内容与abstract选项一样,参考模型继承。required_db_features
声明模型依赖的数据库功能。比如['gis_enabled'],表示模型的建立依赖GIS功能。required_db_vendor
声明模型支持的数据库。Django默认支持sqlite, postgresql, mysql, oracle。select_on_save
决定是否使用1.6版本之前的django.db.models.Model.save()算法保存对象。默认值为False。这个选项我们通常不用关心。indexes
接收一个应用在当前模型上的索引列表,如下例所示:from django.db import models class Customer(models.Model): first_name = models.CharField(max_length=100) last_name = models.CharField(max_length=100) class Meta: indexes = [ models.Index(fields=['last_name', 'first_name']), models.Index(fields=['first_name'], name='first_name_idx'), ]constraints
为模型添加约束条件。通常是列表的形式,每个列表元素就是一个约束。from django.db import models class Customer(models.Model): age = models.IntegerField() class Meta: constraints = [ models.CheckConstraint(check=models.Q(age__gte=18), name='age_gte_18'), ]上例中,会检查age年龄的大小,不得低于18。
label
前面介绍的元数据都是可修改和设置的,但还有两个只读的元数据,label就是其中之一。
label等同于app_label.object_name。例如polls.Question,polls是应用名,Question是模型名。label_lower
同上,不过是小写的模型名。
操作
增
假如models.py文件这样定义:
from django.db import models
class Blog(models.Model):
title = models.CharField(max_length=32)
author = models.ForeignKey("Author", on_delete=models.CASCADE)
class Author(models.Model):
name = models.CharField(max_length=16)
两种方法:
对象.save
def test_model(request): author = Author(name="lczmx") blog = Blog(title="母猪产后护理", author=author) author.save() blog.save() return HttpResponse("success!!!!!!!")类.create()
def test_model(request): author = Author.objects.create(name="李华") Blog.objects.create(title="英语作文速成", author=author) return HttpResponse("success!!!!!!!")
删
具体方法是找到要删除的记录(即类对象或QuerySet对象),然后使用delete方法:
def test_model(request):
Author.objects.get(pk=2).delete()
return HttpResponse("success!!!!!!!")
改
同样有两种方法:
第一种:
根据修改对象的属性,然后调用.save()def test_model(request): author = Author.objects.get(pk=1) author.name = "李华" author.save() return HttpResponse("success!!!!!!!")第二种:
使用QuerySet.update()方法。def test_model(request): Author.objects.filter(pk=1).update(name="lczmx") return HttpResponse("success!!!!!!!")
查
想要从数据库内检索对象,你需要基于模型类,通过管理器(Manager)操作数据库并返回一个查询结果集(QuerySet)。
每个QuerySet代表一些数据库对象的集合。它可以包含零个、一个或多个过滤器(filters)。Filters缩小查询结果的范围。在SQL语法中,一个QuerySet相当于一个SELECT语句,而filter则相当于WHERE或者LIMIT一类的子句。
QuerySet的api
这里是objects管理器返回的。
大多数QuerySets(不是所有)的方法都会返回一个新的QuerySet,这是为了实现链式调用而设计的。
以下的方法都将返回一个新的QuerySets。重点是加粗的几个API,其它的使用场景很少。
| 方法名 | 解释 |
|---|---|
| filter() | 过滤查询对象。 |
| get() | 获取一个对象 |
| exclude() | 排除满足条件的对象 |
| aggregate() | 聚合查询 |
| annotate() | 为查询集添加注解或者聚合内容 |
| order_by() | 对查询集进行排序 |
| reverse() | 反向排序 |
| distinct() | 对查询集去重 |
| values() | 返回包含对象具体值的字典的QuerySet |
| values_list() | 与values()类似,只是返回的是元组而不是字典。 |
| dates() | 根据日期获取查询集 |
| datetimes() | 根据时间获取查询集 |
| none() | 创建空的查询集 |
| all() | 获取所有的对象 |
| union() | 并集 |
| intersection() | 交集 |
| difference() | 差集 |
| select_related() | 附带查询关联对象,利用缓存提高效率 |
| prefetch_related() | 预先查询,提高效率 |
| extra() | 将被废弃的方法 |
| defer() | 不加载指定字段,也就是排除一些列的数据 |
| only() | 只加载指定的字段,仅选择需要的字段 |
| using() | 选择数据库 |
| select_for_update() | 锁住选择的对象,直到事务结束。 |
| raw() | 接收一个原始的SQL查询 |
filter()
filter(**kwargs)
返回满足查询参数的对象集合。
查找的参数(**kwargs)应该满足下文字段查找中的格式。多个参数之间是和AND的关系。get()
get(**kwargs)
注意:使用get()方法和使用filter()方法然后通过[0]的方式分片,有着不同的地方。看似两者都是获取单一对象。但是,如果在查询时没有匹配到对象,那么get()方法将抛出DoesNotExist异常。这个异常是模型类的一个属性,在上面的例子中,如果不存在主键为1的Entry对象,那么Django将抛出Entry.DoesNotExist异常。
类似地,在使用get()方法查询时,如果结果超过1个,则会抛出MultipleObjectsReturned异常,这个异常也是模型类的一个属性。exclude()
exclude(**kwargs)
返回一个新的QuerySet,它包含不满足给定的查找参数的对象。与filter相反。aggregate()
聚合查询
聚合函数from django.db.models import Avg, Min, Max, Sum, Count
使用:Blog.objects.aggregate(new_field=Count('entry'))若不设置字段,默认为
字段名_聚合函数名。annotate()
annotate(args, *kwargs)
注解查询集。
可以使用聚合函数。
注解用于为查询集中的每个对象添加额外的统计信息属性。例如,如果正在操作一个Blog列表,你可能想知道每个Blog有多少Entry:
>>>from django.db.models import Count >>>q = Blog.objects.annotate(new_field=Count('entry'))这样他就多了一个new_field字段了。
有values的话,values要在annotate之前。
order_by()
默认情况下,根据模型的Meta类中的ordering属性对QuerySet中的对象进行排序。
可以通过查询时的order_by()改变上述默认行为,指定新的排序依据:Blog.objects.all().order_by('title')默认升序,把字段前加上
-为降序:Blog.objects.all().order_by('-title')跨表的话,使用
__即可。reverse()
反向排序QuerySet中返回的元素。 第二次调用reverse()将恢复到原有的排序。
要获取QuerySet中最后五个元素,可以这样做:my_queryset.reverse()[:5]这与Python直接使用负索引有点不一样。 Django不支持负索引,只能曲线救国。
distinct()
distinct(*fields)
去除查询结果中重复的行。values()
values(fields, *expressions)
返回一个包含数据字典的queryset,而不是模型实例。
每个字典表示一个对象,键对应于模型对象的属性名称。
你可以简单地理解为Python字典的values。
在values()子句中的聚合应用于相同values()子句中的其他参数之前。 如果需要按另一个值分组,请将其添加到较早的values()子句中。>>>from django.db.models import Count >>>Blog.objects.values('author', entries=Count('entry')) <QuerySet [{'author': 1, 'entries': 20}, {'author': 1, 'entries': 13}]> >>>Blog.objects.values('author').annotate(entries=Count('entry')) <QuerySet [{'author': 1, 'entries': 33}]>values_list()
values_list(*fields, flat=False, named=False)
与values()类似,只是在迭代时返回的是元组而不是字典。
每个元组包含传递给values_list()调用的相应字段或表达式的值,因此第一个项目是第一个字段等。
参数与values类似。all()
返回当前QuerySet(或QuerySet子类)的副本。通常用于获取全部QuerySet对象。raw()
raw(raw_query, params=None, translations=None)
接收一个原始的SQL查询,执行它并返回一个django.db.models.query.RawQuerySet实例。
字段查询参数
get、filter、exclude等api的使用过程中需要用到字段,进而对数据进行筛选。
Django的数据库API支持20多种查询类型,下表列出了所有的字段查询参数:
| 字段 | 说明 |
|---|---|
exact |
精确匹配,默认 |
iexact |
不区分大小写的精确匹配 |
contains |
包含匹配 |
icontains |
不区分大小写的包含匹配 |
in |
在..之内的匹配 |
gt |
大于 |
gte |
大于等于 |
lt |
小于 |
lte |
小于等于 |
startswith |
从开头匹配 |
istartswith |
不区分大小写从开头匹配 |
endswith |
从结尾处匹配 |
iendswith |
不区分大小写从结尾处匹配 |
range |
范围匹配 |
date |
日期匹配 |
year |
年份 |
iso_year |
以ISO 8601标准确定的年份 |
month |
月份 |
day |
日期 |
week |
第几周 |
week_day |
周几 |
iso_week_day |
以ISO 8601标准确定的星期几 |
quarte |
r 季度 |
time |
时间 |
hour |
小时 |
minute |
分钟 |
second |
秒 |
regex |
区分大小写的正则匹配 |
iregex |
不区分大小写的正则匹配 |
使用方法:
字段__查询参数
如:
Blog.objects.filter(name__contains="python")
查询参数可以连用:
Event.objects.filter(timestamp__second__gte=31)
懒加载
QuerySets都是懒惰的
一个创建QuerySets的动作不会立刻导致任何的数据库行为。你可以不断地进行filter动作一整天,Django不会运行任何实际的数据库查询动作,直到QuerySets被提交(evaluated)。
简而言之就是,只有碰到某些特定的操作,Django才会将所有的操作体现到数据库内,否则它们只是保存在内存和Django的层面中。这是一种提高数据库查询效率,减少操作次数的优化设计。看下面的例子:
>>>q = Entry.objects.filter(headline__startswith="What")
>>>q = q.filter(pub_date__lte=datetime.date.today())
>>>q = q.exclude(body_text__icontains="food")
>>>print(q)
上面的例子,看起来执行了3次数据库访问,实际上只是在print语句时才执行1次访问。通常情况,QuerySets的检索不会立刻执行实际的数据库查询操作,直到出现类似print的请求,也就是所谓的evaluated。
那么如何判断哪种操作会触发真正的数据库操作呢?简单的逻辑思维如下:
第一次需要真正操作数据的值的时候。比如上面print(q),如果你不去数据库拿q,print什么呢?
落实修改动作的时候。你不操作数据库,怎么落实?
跨表查询
django中以__表示跨表,想要跨表的话,两个表之间必须要建立表关系(如何建立表关系,见下文)。
假如有这样两张表:
class Blog(models.Model):
title = models.CharField(max_length=32)
author = models.ForeignKey("Author", on_delete=models.CASCADE)
class Author(models.Model):
name = models.CharField(max_length=16)
那么,我们就可以通过正向查询和反向查询查找信息:
正向查询,即字表查主表,是有字段的那边查没有字段的那边。
Blog.objects.filter(author__id=1)反向查询,需要借助django的一个管理器,默认为
类名小写_set,可以在Meta中定义。
注意:对象是类对象,非管理器或QuerySet,因为管理器是属于模型类的!Author.objects.get(pk=1).blog_set.all()
F查询
到目前为止的例子中,我们都是将模型字段与常量进行比较。但是,如果你想将模型的一个字段与同一个模型的另外一个字段进行比较该怎么办?
使用Django提供的F表达式!
例如,为了查找comments数目多于pingbacks数目的Entry,可以构造一个F()对象来引用pingback数目,并在查询中使用该F()对象:
>>>from django.db.models import F
>>>Entry.objects.filter(number_of_comments__gt=F('number_of_pingbacks'))
Django支持对F()对象进行加、减、乘、除、求余以及幂运算等算术操作。两个操作数可以是常数和其它F()对象。
例如查找comments数目比pingbacks两倍还要多的Entry,我们可以这么写:
>>>Entry.objects.filter(number_of_comments__gt=F('number_of_pingbacks') * 2)
Q查询
普通filter查询里的条件都是“and”逻辑,如果你想实现“or”逻辑怎么办?用Q查询!
Q来自django.db.models.Q,用于封装关键字参数的集合,可以作为关键字参数用于filter、exclude和get等函数。
可以使用&或者|或~来组合Q对象,分别表示与、或、非逻辑。它将返回一个新的Q对象。
例如:
Blog.objects.filter(Q(pk=1) | Q(pk=3))
补充
- QuerySet对象有
.exists(),若QuerySet为空,返回False,若QuerySet非空,则返回True。 - QuerySet.count()可以返回结果数量
- QuerySet可以被切片,但是不支持负索引。
表关系
一对多
class django.db.models.ForeignKey(to,on_delete,**options)
它有两个必填参数。to,指定所关联的 Model,它的中取值可以是直接引用其他的 Model,也可以是 Model 所对应的字符串名称;on_delete,当删除关联表的数据时,Django 将根据这个参数设定的值确定应该执行什么样的 SQL 约束。
on_delete 可以理解为 MySQL 外键的级联动作,当主表执行删除操作时对子表的影响,即子表要执行的操作,Django 提供的可选值如下所示(都在django.db.models):
- CASCADE,级联删除,它是大部分 ForeignKey 的定义时选择的约束。它的表现是删除了“主”,则“子”也会被自动删除。
- PROTECT,删除被引用对象时,将会抛出 ProtectedError 异常。当主表被一个或多个子表关联时,主表被删除则会抛出异常。
- SET_NULL,设置删除对象所关联的外键字段为 null,但前提是设置了选项 null 为True,否则会抛出异常。
- SET_DEFAULT:将外键字段设置为默认值,但前提是设置了 default 选项,且指向的对象是存在的。
- SET(value):删除被引用对象时,设置外键字段为 value。value 如果是一个可调用对象,那么就会被设置为调用后的结果。
- DO_NOTHING:不做任何处理。但是,由于数据表之间存在引用关系,删除关联数据,会造成数据库抛出异常。
可选参数:
- to_field:关联对象的字段名称。默认情况下,Django 使用关联对象的主键(大部分情况下是 id),如果需要修改成其他字段,可以设置这个参数。但是,需要注意,能够关联的字段必须有 unique=True 的约束。
- db_constraint:默认值是 True,它会在数据库中创建外键约束,维护数据完整性。通常情况下,这符合大部分场景的需求。如果数据库中存在一些历史遗留的无效数据,则可以将其设置为 False,这时就需要自己去维护关联关系的正确性了。
- related_name:这个字段设置的值用于反向查询,默认不需要设置,Django 会设置其为
“模型类名小写 _set”。 - related_query_name:这个名称用于反向过滤。如果设置了 related_name,那么将用它作为默认值,否则 Django 会把模型的名称作为默认值
比如:
from django.db import models
class Blog(models.Model):
title = models.CharField(max_length=32)
author = models.ForeignKey("Author", on_delete=models.CASCADE)
class Author(models.Model):
name = models.CharField(max_length=16)
如何查询见上文的内容
多对多
多对多实质上就是两个一对多,他是通过第三张表来实现多对多关系的。
在django中使用ManyToManyField()来创建多对多关系。
根据django的原则,多对多字段只需要在其中一个表中定义即可,那么要在那个表中定义呢?举一个现实的例子,产品与配料表,一个产品有多个配料,而一种配料可以作为多个产品的配料。哪里作为多对多关系字段的存放点呢?建议使用我们认知的一样放,比如产品与配料就把字段放在配料表中。
django.db.models.ManyToManyField的简单使用:
from django.db import models
class Ingredient(models.Model):
name = models.CharField(max_length=32)
product = models.ManyToManyField("Product")
class Meta:
verbose_name = "配料表"
class Product(models.Model):
name = models.CharField(max_length=32)
class Meta:
verbose_name = "产品表"
上面说过,多对多就是通过三张表建立起来的两个一对多,那么第三张表在哪里呢?
原来django会给我们自动创建第三张表,名称为应用名_定义字段的类的小写_另一个类的小写。
自定义第三张表:
由于django给我们创建的第三张表只有三个字段:id,定义字段类_id,另一类_id,假如我们想要为多对多添加额外的数据时,就不好用了。所以下面介绍如何定义第三张表。
- 在ManyToManyField字段中添加through参数,用于指定第三张表
- 在第三张表的模型中,写两个外键
比如:
from django.db import models
class Person(models.Model):
name = models.CharField(max_length=128)
def __str__(self):
return self.name
class Group(models.Model):
name = models.CharField(max_length=128)
members = models.ManyToManyField(Person, through='Membership')
def __str__(self):
return self.name
class 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) # 邀请原因
以上表为例,讲述多对多的增删改查
# 增加
g = Group.objects.get(pk=1)
p = Person.objects.get(pk=1)
g.members.add(p)
# 清空
g = Group.objects.get(pk=1)
g.members.clear()
# 删除
g = Group.objects.get(pk=1)
p = Person.objects.get(pk=2)
g.members.remove(p)
# 重置关系
g = Group.objects.get(pk=1)
g.members.set([1, 2, 3, 4])
# 查找
# 跨表时
# 没定义第三表,字段为表一小写_表二小写
# 如在a表中定义多对多,关联b表
# 那么为:a_b
# 定义第三表时,则为ManyToManyField的字段名
# 正向查
res = Group.objects.all().values("members__id")
res = Group.objects.filter(members__id="1").values("pk")
# 反向查
p = Person.objects.filter(membership__group_id=1)
一对一
建立一对一关系,需要用到models.OneToOneField数据类型。
需要指定表和on_delete参数。
一对一非常类似多对一关系,可以简单的通过模型的属性访问关联的模型。
class EntryDetail(models.Model):
entry = models.OneToOneField(Entry, on_delete=models.CASCADE)
details = models.TextField()
ed = EntryDetail.objects.get(id=2)
ed.entry # Returns the related Entry object.
不同之处在于反向查询的时候。一对一关系中的关联模型同样具有一个管理器对象,但是该管理器表示一个单一的对象而不是对象的集合:
e = Entry.objects.get(id=2)
e.entrydetail # 返回关联的EntryDetail对象
如果没有对象赋值给这个关系,Django将抛出一个DoesNotExist异常。
Form
简单使用
- 自定义一个
django.forms.Form子类 - 根据forms的类型定义需要的form的标签
- 可以指定参数,如label和max_length
- 视图中使用is_valid()方法,form全部通过,返回True
- 使用cleaned_data属性,这是is_valid之后的数据
# web/forms.py
from django import forms
class USER(forms.Form):
name = forms.CharField(max_length=32, label="用户名")
email = forms.EmailField(max_length=128)
# web/views.py
from django.shortcuts import render
from django.http import HttpResponse
from web.forms import USER
# Create your views here.
def register(request):
if request.method == "GET":
form = USER()
return render(request, "web/register.html", {"form": form})
if request.method == "POST":
form = USER(request.POST)
if form.is_valid():
print(form.cleaned_data)
return HttpResponse("ok!")
<!-- web/register.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>register</title>
</head>
<body>
<form action="{% url "web:register" %}" method="post">
{% csrf_token %}
{{ form }}
<input type="submit" value="提交">
</form>
</body>
</html>
这样我们用GET方式访问web/register就会生成形如这样的表单:
<form action="/web/register" method="post">
<input type="hidden" name="csrfmiddlewaretoken" value="LpAyJTwviENuwiy92DEkOCXAwxte8rGDbBMSgSXvbdOtrnSa81Rd18ZWCdiO7Hyl">
<label for="id_name">用户名:</label><input type="text" name="name" maxlength="32" required="" id="id_name">
<label for="id_email">Email:</label><input type="email" name="email" maxlength="128" required="" id="id_email">
<input type="submit" value="提交">
</form>
注意:form标签和 submit是手动生成的!!
form字段
从上面可以看出,要生成什么input标签,都是我们通过定义类属性来实现的。该类属性就相当于model的字段:
| 字段 | 对应input | 参数 | 空值 | 错误 |
|---|---|---|---|---|
| BooleanField | checkbox | 无 | False | required |
| CharField | text | max_length、min_length 、strip、empty_value | empty_value参数 | required、max_length、min_length |
| ChoiceField | select | choices = [("value", "show in page"), ] |
空字符串 | required、invalid_choice |
| DateTimeField | text | input_formats 时间格式 | None | required、invalid |
| DateField | text | input_formats 时间格式 | None | required、invalid |
| EmailField | max_length、min_length 和 empty_value | empty_value参数 | required、invalid | |
| IntegerField | number or text | max_value 和 min_value | None | required、invalid、max_value、min_value |
| FileField | file | max_length 和 allow_empty_file | None | required、invalid、missing、empty、max_length |
| ImageField 需要Pillow | file | None | required、invalid、missing、empty、invalid_image |
详见官网:内置 Field 类
form字段的参数
这里指的是通用参数
| 参数 | 说明 |
|---|---|
| required | 空值时ValidationError 异常,可指定False |
| label | 指定label标签 |
| label_suffix | 指定label后缀,如user:变为user= |
| initial | 指定默认值 |
| widget | 指定部件(处理 HTML 的渲染),点击这里查看 |
| help_text | 在字段标签后面加上span标签作为提示 |
| error_messages | 一个字典,key为该字段可以触发的错误,value为提示信息 |
| disabled | 是否可以禁用 |
| validators | 指定验证器,点击这里查看验证器 |
| localize | 实现表单数据输入和渲染输出的本地化 |
form的api
如:
from django import forms
class UserForm(forms.Form):
name = forms.CharField(max_length=32, label="用户名")
u = UserForm()
那么u有什么api呢??
.is_bound
有无绑定表单实例,UserForm({”name”: “abc”})为True,否则为False.clean()
运行自定义的钩子函数进行验证,如何自定义见下文。.is_valid()
验证数据.cleaned_data
已经经过检验的数据.errors
获取错误信息的html
.errors.as_data() 获得错误信息字典,如{'email': ['错误邮箱格式']}
.errors.as_json() 获得错误信息的json数据,如{"email": [{"message": "\u9519\u8bef\u90ae\u7bb1\u683c\u5f0f", "code": "invalid"}]}.add_error(field, error)
手动添加,会在cleaned_data中删除字段。error 参数可以是一个字符串,但最好是 ValidationError 的实例,如何抛出见这里.has_error(field, code=None)
本方法返回一个布尔值,表示一个字段是否有特定错误 code 的错误。如果 code 是 None,如果字段包含任何错误,它将返回 True。.has_changed()
是否与初始数据发生变化,form的initial参数指定,也可以在实例化时指定:u = UserForm(initial={'name': 'lczmx!'}).fields
访问字段.
as_p()、.as_ul()、.as_table()
贴出源码:def as_table(self): "Return this form rendered as HTML <tr>s -- excluding the <table></table>." return self._html_output( normal_row='<tr%(html_class_attr)s><th>%(label)s</th><td>%(errors)s%(field)s%(help_text)s</td></tr>', error_row='<tr><td colspan="2">%s</td></tr>', row_ender='</td></tr>', help_text_html='<br><span class="helptext">%s</span>', errors_on_separate_row=False, ) def as_ul(self): "Return this form rendered as HTML <li>s -- excluding the <ul></ul>." return self._html_output( normal_row='<li%(html_class_attr)s>%(errors)s%(label)s %(field)s%(help_text)s</li>', error_row='<li>%s</li>', row_ender='</li>', help_text_html=' <span class="helptext">%s</span>', errors_on_separate_row=False, ) def as_p(self): "Return this form rendered as HTML <p>s." return self._html_output( normal_row='<p%(html_class_attr)s>%(label)s %(field)s%(help_text)s</p>', error_row='%s', row_ender='</p>', help_text_html=' <span class="helptext">%s</span>', errors_on_separate_row=True, ).as_table()是
__str__的返回值,所以是默认方式<!-- 正常 --> <tr><th><label for="id_name">用户名:</label></th><td><input type="text" name="name" maxlength="32" required id="id_name"></td></tr> <!-- 异常 --> <tr><th><label for="id_name">用户名:</label></th><td><ul class="errorlist"><li>Ensure this value has at most 2 characters (it has 5).</li></ul><input type="text" name="name" value="lczmx" maxlength="2" required id="id_name"></td></t
自定义form样式
参见:自定义部件实例
auto_id
设置字段id- Ture自动生成
- False 不生成
- ‘field_%s’ 形如:’field_age’、’field_name’
但这是参数:
f = ContactForm(auto_id=False)错误信息类名
from django import forms class ContactForm(forms.Form): error_css_class = 'error' required_css_class = 'required'css
方法1:
class CommentForm(forms.Form): name = forms.CharField(widget=forms.TextInput(attrs={'class': 'special'})) url = forms.URLField() comment = forms.CharField(widget=forms.TextInput(attrs={'size': '40'}))方法二:
class CommentForm(forms.Form): name = forms.CharField() url = forms.URLField() comment = forms.CharField() name.widget.attrs.update({'class': 'special'}) comment.widget.attrs.update(size='40')对于ModelForm:
class CommentForm(forms.ModelForm): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.fields['name'].widget.attrs.update({'class': 'special'}) self.fields['comment'].widget.attrs.update(size='40')
更细分
可以为每个字段(包括label)提供样式,即将form拆开渲染。- `