参考
进阶内容 => 使用 Django
api => API 参考
Django 文档 | Django 文档 | Django (djangoproject.com)
待了解
- [[#url 调度器|url 调度器 -> 命名空间]]
- [[#异步|视图 -> 异步]]
python -m pip install django
python -m pip install daphne # => 用于部署服务器
python -m pip install Pillow # => 用于 ImageField
配置
topic:Django 配置
ref:django-admin 和 manage.py, 配置
运行 Django 时会将项目的 settings 模块添加到 django.conf.settings
?
配置 | 用途 | 建议 |
---|---|---|
ROOT_URLCONF | 设置根路由 | |
TIME_ZONE | Asia/Shanghai | |
DEBUG |
生产环境中建议使用虚拟环境 => 参见 [[python#虚拟环境]]
asgi 部署 => 有三种可选项 Daphne
, Hypercorn
, Uvicorn
=> 以 daphne
为例:
python -m pip install daphne
- 在项目根目录运行
daphne path.to.asgi:application
- 也可以与
runserver
集成:INSTALLED_APPS
最前面添加"daphne"
=>ASGI_APPLICATION = "path.to.asgi.application"
wsgi 部署 => 有三种可选项 Gunicorn
, uWSGI
, Apache & mod_wsgi
=> 以 Gunicorn
为例“
应用(app)
ref:应用程序, Django 实用程序, contrib 包,
一个 Django app 由 urls
, views
, template
, static
, model
, migration
, database
等组成
urls
, views
=> 实现 前端 和 后端 的交互
template
, static
=> 主要实行前端功能 => 可以交给前端框架实现前后端分离
model
, migration
, database
=> 主要实行后端功能
路由(urls)
topic:URL调度器
ref:django.urls 实用函数, URLconfs 中使用的 django.urls 函数, URLconfs 中使用的 django.urls 函数, 跨站请求伪造保护
路由在 django 中被称为 URLconfs
涉及的包和模块:
=> django.urls
=> path()
, re_path()
, include()
, reverse()
=> django.http
=> HttpRequest
, HttpResponse
, HttpResponseRedirect
url 调度器
Django 处理请求的工作方式:
=> 根据 HttpRequest
对象的 urlconf
属性,或者 ROOT_URLCONF
确定根路由器
=> 根据 HttpRequest
的 path_info
部分匹配路由器模块中 urlpatterns
里的一条路由,并转入下一条路由器,或者一个视图;若匹配不到路由,则 Django 调用适当的错误处理视图,参见 [[#安全|安全->异常]]
=> 例子
urlpatterns = [
path("articles/2003/", views.a),
path("articles/<int:year>/", views.b),
path("articles/<int:year>/<int:month>/", views.c),
path("articles/<int:year>/<int:month>/<slug:slug>/", views.d),
]
/articles/2005/03/
=> 匹配第三项,调用views.c(request, year=2005, month=3)
/articles/2003/
=> 匹配第一项,调用views.a(request)
/articles/2003
=> 匹配不到/articles/2003/03/building-a-django-site/
=> 匹配第四项,调用views.d(request, year=2005, month=3, slug='building-a-django-site')
注:route
中的每个路径参数都必须有一个视图函数的形参与之对应;反之,视图函数的每个位置参数都必须有一个 route
的路径参数
注2:路由器模块都有一个路由表 urlpatterns: &path()[] | &re_path()[]
路径转换器 => 在 path()
函数的 route
参数中用于指定 path-params
<str:path_param>
=> 匹配[^/]+
并传递给path_param: str
参数<int:path_param>
=> 匹配自然数\d+
并传递给path_param: int
参数<slug:path_param>
=> 匹配[^\x00-\xFF]+
(ASCII 字母、下划线、连字符)并传递给path_param: str
参数<uuid:path_param>
=> 匹配一个格式化的 UUID<path:path_param>
=> 匹配.+
并传递给path_param: str
参数
=> 例子:
自定义路径转换器 => 定义一个类,其具有 regex
属性,to_python()
和 to_url()
方法 => 然后用 register_converter(ExampleConverter: class, converter_name: str)
注册转换器
regex: str
=> 指定匹配的模式to_python(self, value: str): any
=> 指定路径参数传递给视图函数形参时的数据to_url(self, value: any): str
=> 指定数据转换为 url 中的路径参数时得到的字符串(即to_python()
的逆变换),通常在调用reverse()
时触发
=> 例子:
class ExampleConverter:
regex = "\d{4}"
def to_python(self, value):
return int(value)
def to_url(self, value):
return "%04d" % value
from django.urls import path, register_converter
register_converter(ExampleConverter, "yyyy")
urlpatterns = [
path("<yyyy:param>", ...)
]
正则路由 => re_path()
=> 使用 (?P<path-param>pattern)
或 (pattern)
进行分组(或者说指定路径参数)
=> 例子: re_path(r'^email/(?P<param>\w+@\w+\.\w+)/$', ...)
- 嵌套参数 =>
url 反向解析 => 即 根据
- 模板标签
url
=>{% url <route_name> <path-params> %}
reverse(route_name: str, args?: any[]): str
函数get_absolute_url()
方法
route_name
=> 在定义路由时,定义其路由名是一个好习惯!
url 命名空间 =>
- 应用程序命名空间
- 实例命名空间
注:app_name
用于设置应用程序空间?
附录-路由
urls 的函数 | 用途 | 参数解释 |
---|---|---|
path(route: str, view: (req: HttpRequest, **path_params) => HttpResponse, args?: dict, name?: str) | 获取一条匹配规则 => 将匹配的路由转发到视图 | args => 传递给视图的形参的额外数据 name => 路由规则的标识符 |
re_path(route: regex, view: (req: HttpRequest, **path_params) => HttpResponse, args?: dict, name?: str) | ||
include(“path.to.urls”, namespace: str) | 包含其他 URLconfs | namespace => |
include(urlpatterns: &path()[]) | ||
reverse(viewname, urlconf=None, args=None, kwargs=None, current_app=None) | current_app => 指定 app 命名空间 |
视图(views)
topic:编写视图, 视图装饰器, 文件上传, Django 便捷函数, 通用视图, 中间件, 如何使用会话
topic2:基于类的视图, 内置的基于类的通用视图, 使用基于类的视图处理表单, 在基于类的视图中使用混入,
topic3:条件视图处理, 分页
ref:内置基于类的视图 API, 内置视图, 请求和响应对象, 中间件
涉及的包和模块,或函数:
-
django.http
=>HttpResponse
,HttpResponseRedirect
,QueryDict
-
django.shortcuts
=>render()
-
django.conf.urls
=>handler400
,handler403
,handler404
,handler500
-
django.views.decorators.http
=>@require_http_methods()
,@require_GET()
,@require_POST()
-
django.views.decorators.gzip
=>gzip_page()
-
django.views.decorators.vary
=>vary_on_cookie()
-
django.views.decorators.cache
=>cache_control()
-
django.views.decorators.common
=>no_append_slash()
-
django.views.static
=>serve()
-
django.views.defaults
=>page_not_found()
,server_error()
,permission_denied()
,bad_request()
-
django.core.serializers.json
=>DjangoJSONEncoder
-
django.template.response
=>SimpleTemplateResponse
,TemplateResponse
-
django.template.loader
=>get_template()
-
django.template.backends.django
=>Template
-
django.views.generic
=>View
,TemplateView
涉及的异常:
django.http
=>Http404
,HttpResponseNotAllowed
django.core.exceptions
=>PermissionDenied
,SuspiciousOperation
编写视图
相关:[[#请求]], [[#响应]]
视图函数
=> 接受 web 请求并返回 web 相应的 python 函数
=> 形如 view_func(req: HttpRequest, path_param: str...): HttpResponse
注:Django 并不会限制这些函数的位置,而惯例是放在名为 views
的包或模块中
=> 常见响应 => HttpResponse
及其子类,参考 [[#附录-视图|HttpResponse 及其子类的构造器]]
=> 404 异常:Http404
自定义报错视图? => django.conf.urls
下的 handler400
, handler403
, handler404
, handler500
CSRF_FAILURE_VIEW
=> 可以用来覆盖 CSRF 报错视图?
便捷工具 => django.shortcuts
包下的 render()
, redirect()
, get_object_or_404()
, get_list_or_404()
视图装饰器
前置知识:[[python#装饰器]]
django.views.decorators.http
包下的某些装饰器可以声明一个视图函数需要指定的请求方法才能调用,否则会触发 django.http.HttpResponseNotAllowed
错误
@require_http_methods(methods: str[])
=> 指定视图函数所需的请求方法
@require_GET()
=> 指定视图函数只接受 GET
方法
@require_POST()
=> 指定视图函数只接受 POST
方法
@require_safe()
=> 指定视图函数只接受 GET
或 HEAD
方法
@condition(etag_func=None, last_modified_func=None)
=> 用于控制特殊视图中的缓存行为
django.views.decorators
=> gzip
=> 用于压缩
=> vary
, cache
=> 用于控制缓存
=> common
=>
异步
文件&异常
Django 的一些视图函数允许 用 url 读取文件,或自定义常见 http 错误时返回的模板
django.views.static
=> serve(req: HttpRequest, path: str, document_root: str, show_indexes?: bool)
=> 可以通过 url 获取文件
=> 配置的例子:
- 全局设置 =>
MEDIA_ROOT = BASE_DIR / 'media'
- 配置路由 =>
re_path(r"^media/(?P<path>.*)$", serve, {"document_root": settings.MEDIA_ROOT}, "media")
django.views.defaults
=> page_not_found(req: HttpRequest, exception, template_name='404.html')
=> 发出 Http404
错误时,Django 会加载 page_not_found()
视图 => 若 templates/
目录下存在 404.html
模板,那么该视图返回该模板;否则返回默认的 Not Found
页面
注:该视图给 404.html
提供了 2 个上下文参数 request_path
, exception
注2:URLconf 找到匹配结果时,会自动发出 Http404
错误
注3:只有在 DEBUG=False
时生效
=> server_error(req: HttpRequest, template_name='500.html')
=> 视图中出现异常时调用
=> permission_denied(req: HttpRequest, exception, template_name='403.html')
=> 发出 PermissionDenied
异常时调用
注:该视图给 403.html
提供了上下文参数 exception
=> bad_request(req: HttpRequest, exception, template_name='400.html')
=> 发出 SuspiciousOperation
异常时调用
请求
HttpRequest 基本属性 | 解释 | 补充 |
---|---|---|
scheme: str | 请求协议 | 如 http , https |
path: str | 请求路径 | |
path_info: str | 请求路径的一个后缀 | |
method: str | HTTP METHOD | |
headers: dict | HTTP HEADERS | |
content_type: str | HTTP HEADERS 的 Content-Type 键的值 | |
content_params: dict | HTTP HEADERS 的 Content-Type 头对应的字典 | · |
GET: QueryDict | HTTP QUERY | 适用于所有 HTTP METHOD |
POST: QueryDict | HTTP BODY 的字典数据 | 仅限于 POST 方法 |
FILES: MultiValueDict | 仅限于 Content-Type=multipart/form-data 的 POST 方法, | |
body: byte_str | HTTP BODY 的二进制数据 | 适用于所有 HTTP METHOD |
encoding: str | 解码表单提交数据的编码方案 | 默认为 DEFAULT_CHARSET 的值 |
COOKIES: dict | cookies 的字段 | |
META: dict | headers(key 会做相应转换) | |
resolver_match: ResolverMatch | ||
注:在调用 <HttpRequest>.read() 后调用 <HttpRequest>.body ,会抛出 RawPostDataException 异常 |
应用程序代码设置的属性 => current_app
, urlconf
, exception_reporter_filter
, exception_reporter_class
中间件设置的属性 => session
, site
, user
HttpRequest 的函数 | 用途 | 补充 |
---|---|---|
read(): bytes | HTTP BODY 的二进制数据 | |
close() | 关闭读取 HTTP BODY 的流 | 在 post 请求中,若不调用 req.close() ,那么调用 req.post 或 req.read() 时会报错(由于锁机制) |
auser() | ||
get_host() | ||
get_port() | ||
get_full_path() | ||
get_full_path_info() | ||
build_absolute_uri() | ||
get_signed_cookie() | ||
is_secure() | ||
accepts() | ||
readline() | ||
readlines() | ||
__iter__ () |
QueryDict 的方法 | 用途 |
---|---|
__init__ (query_params: str, mutable?: bool, encoding?: str) | |
__getitem__ (key: str): str | |
__setitem__ (key: str, val: str) | |
__contains__ (key: str): bool | |
get(key: str, default?: str): str | |
setdefault(key: str, default?: str) | |
update(other: dict) | |
items(): 2-tuple[] | |
values(): str[] | |
copy(): QueryDict | |
getlist(key: str, default?: str[]) | |
setlist(key: str, list: str[]) | |
appendlist(key: str, item: str) | |
setlistdefault(key: str, default_list: str[]) | |
lists(): 2-tuple[] | |
pop(key: str): str[] | |
popitem(): 2-tuple[] | |
dict(): dict | |
urlencode(safe?: str) |
响应
HttpResponseBase(content_type?: str, status?: int, reason?: str, charset?: str, headers?: dict)
=> Django 大部分响应类的祖先类
HttpResponse
HttpResponse(data, headers?: dict)
HttpResponse 的属性 | 解释 |
---|---|
content: str | |
cookies: SimpleCookie | |
headers: dict | |
charset: str | |
status_code: str | |
reason_phrase: str | |
streaming: bool | |
closed: bool |
HttpResponse 的方法 | 用途 |
---|---|
__init__ (content=b’', content_type=None, status=200, reason=None, charset=None, headers=None) | |
__setitem__ (header, value) | |
__delitem__ (header) | |
__getitem__ (header) | |
get(header, alternate=None) | |
has_header(header) | |
items() | |
setdefault(header, value) | |
set_cookie(key, value=‘’, max_age=None, expires=None, path=‘/’, domain=None, secure=False, httponly=False, samesite=None) | |
set_signed_cookie(key, value, salt=‘’, max_age=None, expires=None, path=‘/’, domain=None, secure=False, httponly=False, samesite=None) | |
delete_cookie(key, path=‘/’, domain=None, samesite=None) | |
close() | |
write(content) | |
flush() | |
tell() | |
getvalue() | |
readable() | |
seekable() | |
writable() | |
writelines(lines) |
HttpResponse 子类
以下介绍 HttpResponse 的特殊子类:
注意两个响应头 => Content-Type
, Content-Disposition
JsonResponse(data: dict | any, encoder=DjangoJSONEncoder, safe=True, json_dumps_params?: dict)
data
相应的数据DjangoJSONEncoder < JSONEncoder
=> 编码器safe
=> 是否只指定data
只能是dict
json_dumps_params
=> 指定与json.dumps()
相同的参数
StreamingHttpResponse(streaming_content: generator_obj)
generator_obj
=> 生成器对象 => 例子:可以通过(i for i in iterable_obj)
得到
StreamingHttpResponse 的属性 | 解释 |
---|---|
streaming_content: generator_obj | |
status_code: int | http 状态码 |
reason_phrase | |
streaming: bool | |
is_async: bool |
FileResponse(open_file: byte, as_attachment=False, filename='', **kwargs)
=> 例子:FileResponse(open('media/sd.jpg','rb'))
注:当前目录为 manage.py
所在目录,即当前目录总是项目的根目录
<FileResponse>.set_headers(open_file)
=> 根据 open_file
设置诸如 Content-Type
, Content-Length
, Content-Disposition
等响应头
模板响应
SimpleTemplateResponse
< HttpResponse
, HttpResponseBase
SimpleTemplateResponse(tmpl: str | Template [], context: dict, **args)
- 注:
args
中不包含content: str
SimpleTemplateResponse 的属性 | 解释 |
---|---|
template_name: str | Template [] | |
context_data: dict | |
rendered_content | |
is_rendered: bool |
SimpleTemplateResponse 的方法 | 用途 |
---|---|
resolve_context(context: dict): dict | 预处理上下文数据 |
resolve_template(template) | 解析模板实例 |
add_post_render_callback() | 添加一个渲染发生后调用的回调 |
render() |
TemplateResponse
< SimpleTemplateResponse
, HttpResponse
, HttpResponseBase
TemplateResponse(req: HttpRequest, tmpl: str | Template [], context: dict, **args)
基于类的视图
class ExampleView(View):
def get(self, req: HttpRequest):
pass
def post(self, req: HttpRequest):
pass
def put(self, req: HttpRequest):
pass
def delete(self, req: HttpRequest):
pass
def patch(self, req: HttpRequest):
pass
...
附录-视图
django.http
=> HttpRequest
, HttpResponse
, JsonResponse
, StreamingHttpResponse
, FileResponse
, HttpResponseBase
, QueryDict
HttpResponseBase(content_type?: str, status?: int, reason?: str, charset?: str, headers?: dict)
HttpResponse < HttpResponseBase
HttpResponse 及其子类的构造器 | 解释 | 状态码 |
---|---|---|
HttpResponse(content?: str, **args) | 通用响应 | 默认为200 |
HttpResponseRedirect(url_or_path: str) | 重定向 | 302 |
HttpResponsePermanentRedirect(url_or_path: str) | 永久重定向 | 301 |
HttpResponseNotModified() | 页面未更改 | 304 |
HttpResponseBadRequest | 400 | |
HttpResponseNotFound | 404 | |
HttpResponseForbidden | 403 | |
HttpResponseNotAllowed(allowed_method: str[]) | 方法不允许 | 405 |
HttpResponseGone | 410 | |
HttpResponseServerError | 500 |
django.shortcuts
=> 便捷工具
shortcuts 包的函数 | 用途 |
---|---|
render(req: HttpRequest, template_path: str, context?: dict, content_type?: str, status?: int, using?): HttpResponse | |
redirect(to, *args, permanent=False, **kwargs) | |
get_object_or_404(klass, *args, **kwargs) | |
get_list_or_404(klass, *args, **kwargs) |
django.views.decorators.http
=> 指定视图函数接受的方法
decorators.http 包的装饰器 | 用途 |
---|---|
@require_http_methods(methods: str[]) | |
require_GET() | |
require_POST() | |
require_safe() |
SuspiciousOperation 的子类 | |
---|---|
DisallowedHost | |
DisallowedModelAdminLookup | |
DisallowedModelAdminToField | |
DisallowedRedirect | |
InvalidSessionKey | |
RequestDataTooBig | |
SuspiciousFileOperation | |
SuspiciousMultipartForm | |
SuspiciousSession | |
TooManyFieldsSent | |
TooManyFilesSent |
模板(template)
topic:模板, 使用表单
ref:模板, TemplateResponse 和 SimpleTemplateResponse, 表单, 分页器
涉及的模块和后端
django.template.backends.django
=>DjangoTemplates
django.template.backends.jinja2
=>Jinja2
引擎后端
DjangoTemplates
=> django 内置后端Jinja2
=> django 内置后端,但需要额外安装- 自定义后端
配置:=> 以 DjangoTemplates
为例
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [],
"APP_DIRS": True,
"OPTIONS": {
# ... some options here ...
},
},
]
BACKEND
=> 指定模板引擎后端DIRS: str[]
=> 指定项目中的一些模板目录APP_DIRS: bool
=> 是否每个 app 下的templates
目录为模板目录?OPTIONS: dict
=> 指定特定于模板后端的配置
语法
变量 => {{ <val> }}
- 字典 / 数组 / 对象 检索值时使用句点语法 => 如,
{{ my_dict.key }}
,{{ my_arr.5 }}
,{{ my_obj.attr }}
标签 => {% <tag> %}
或 {% <tag_begin> %} ... {% <tag_end> %}
- 大多数标签带参数 => 如,
{% url 'my_blogs' 3 %}
- 具有控制结构
=>{% if <condition> %} ... {% endif %}
=>{% for ... %} ... {% endif %}
过滤器 => {{ <val>|<filter> }}
或 {{ <val>|<filter>:<arg> }}
注释 => {# comment_raw... }
或 {% comment %} comment_raw... {% endcomment %}
组件
引擎 => django.template.Engine
模板 => django.template.Template
上下文 => django.template.Context
, django.template.RequestContext
加载器 =>
上下文处理器
静态资源(static)
配置:
INSTALLED_APPS = [
'django.contrib.staticfiles',
]
TEMPLATES = [
{
'OPTIONS': {
'builtins' : [
'django.templatetags.static',
],
},
},
]
=> django 默认只会搜索 <app-name>/static/
目录下的静态文件
注:若不配置 builtins
,那么 .html
文件要使用静态文件时都要使用指令 {% load static %}
STATICFILES_DIRS: str[]
=> 添加本地中额外的静态文件目录
=> 例子:
STATICFILES_DIRS = [
BASE_DIR / 'static_local',
]
STATIC_URL
=> 配置 url 到本地静态文件出道路由,这些静态文件由 STATICFILES_DIRS
配置,或 <app-name>/static/
提供
=> 例子:STATIC_URL = '/static_url/'
STATIC_ROOT
=> collectstatic
命令会将所有静态文件收集并整理到 STATIC_ROOT
所指定的目录
- 例子 =>
STATIC_ROOT = BASE_DIR / 'staticfiles'
模型(models)
topic:模型, 执行查询, 聚合, 搜索, 管理器, 原生sql, 数据库事务, 模型关联 API 用法示例, 序列化 Django 对象
ref:模型, 信号, 验证器,
涉及的包和模块:
django.db.models
=>Model
django.db.models.fields
=>Field
,SmallIntegerField
,Integerxield
,BigIntegerField
,PositiveSmallIntegerField
,PositiveIntegerField
,PositiveBigIntegerField
,SmallAutoField
,AutoField
,BigAutoField
,FloatField
,DecimalField
,BooleanField
,CharField
,TextField
,EmailField
,URLField
,DateField
,DateTimeField
,DurationField
,GenericIPAddressField
,FilePathField
,SlugField
,UUIDField
django.db.models.fields.files
=>FileField
,ImageField
django.db.models.fields.json
=>JSONField
django.db.models.fields.generated
=>GeneratedField
Django 中只有 app
才拥有自己的 model
,其定义在 <app>.models
下(可以是 .py
的模块文件,也可以是包的 __init__.py
文件)
注册模型 => 首先要注册 app => 即 INSTALLED_APPS
添加一条表示 model
所属 app 的配置
定义模型 => 定义字段 => 字段类型、字段选项
字段类型的作用
- 指定字段在数据库中的类型
- 渲染表单字段时默认使用 html 表单进行渲染
模型类
<app>.models
下的django.db.models.Model
的子类总是对应一张数据库表(两个模型之间也可能有一张中间表)Model
子类的属性与对应的数据库表的字段一一映射
内部异常类 => DoesNotExist
, MultipleObjectsReturned
模型字段
以下每个模型字段类都是 [[#models.fields.Field]] 的孙子 => 于是构造方法的大部分参数也可能继承
Field 类
参考:[[#models.fields.Field|django.models.fields.Field]]
Field
的构造器的参数
构造器参数 | 解释 | 表单? | 补充 |
---|---|---|---|
null: bool | 数据库中若字段为空,则将字段设置为 NULL | ||
blank: bool | 字段可为空 | ||
choices: 2-tuple[] | map | enum | 枚举字段(key为后端数据,value为前端数据) | <select> | 为 model 添加新字段时,该选项应与 default 选项配合使用 |
default: any | func | 字段默认值 | ||
help_text: str | |||
primary_key: bool | 表的唯一主键(该字段只读) | 若表中没有该字段,则 django 将添加 IntegerField 字段,并设为主键 | |
unique: bool | |||
verbose_name: str | 人类可读的字段名 |
构造器参数 | 解释 |
---|---|
db_column: str | 数据库底层中该字段对应的列名 |
db_comment: str | 数据库底层中对该字段的注释 |
db_default: any | 字段默认值,可以是字面量,或者数据库函数 |
db_index: bool | 数据库底层中对该字段创建索引 |
db_tablespace: bool | |
editable | |
error_messages | |
unique_for_date: bool | |
unique_for_month: bool | |
unique_for_year: bool | |
validators: | 验证器列表 |
Field
的属性:
普通属性 | 解释 |
---|---|
auto_created: bool | |
concrete: bool | |
hidden: bool | |
is_relation: bool | 是否为特殊字段 |
model | 当前模型 |
特殊字段的属性 | 解释 |
---|---|
many_to_many: bool | |
many_to_one: bool | |
one_to_many: bool | |
one_to_one: bool | |
related_model | 相关联的模型 |
注:特殊字段 => 用于定义两个模型之间的关系的字段(ForeignKey, ManyToManyField, OneToOneField 其中之一)
数字字段
包括 uuid、整数、浮点数、bool
字段类型 | 解释 | 表单部件 | 相关验证 | 补充 |
---|---|---|---|---|
UUIDField | 用于存储通用唯一标识符的字段 | |||
SmallIntegerField | 用于存储 16 位整数 | MinValueValidator, MaxValueValidator | ||
IntegerField | 用于存储 32 位整数 | |||
BigIntegerField | 用于存储 64 位整数 | |||
PositiveSmallIntegerField | 用于存储 16 位正整数 | |||
PositiveIntegerField | 用于存储 32 位正整数 | |||
PositiveBigIntegerField | 用于存储 64 位正整数 | |||
SmallAutoField | 根据可用的 id 自动递增的 SmallIntegerField | |||
AutoField | 根据可用的 id 自动递增的 IntegerField | |||
BigAutoField | 根据可用的 id 自动递增的 BigIntegerField | |||
FloatField | 用于存储 float | localize=Ture => TextInputlocalize=False => NumberInput | ||
DecimalField(max_digits: int, decimal_places: int) | 用于存储 Decimal | localize=Ture => TextInputlocalize=False => NumberInput | DecimalValidator | |
BooleanField | 用于存储 bool | CheckboxInput |
字符串字段
包括 原始字符串、邮箱、url、时间、文件、IP地址、slug 等
字段类型 | 解释 | 表单部件 | 相关验证 | 补充 |
---|---|---|---|---|
CharField(max_length: int, db_collation?) | 用于存储 str | TextInput | MaxLengthValidator | |
TextField(max_length: int, db_collation?) | 用于存储大文本 str | TextInput | ||
EmailField(max_length=254) | 存储邮箱的 CharField | EmailValidator | ||
URLField | 存储 url 的 CharField | URLValidator | ||
GenericIPAddressField(protocol=‘both’, unpack_ipv4=False) | ||||
DateField(auto_now?: bool, auto_now_add?: bool) | 用于存储 datetime.date | DateInput | auto_now, auto_now_add, default 三者互斥 | |
TimeField(auto_now=False, auto_now_add=False) | 用于存储 datetime.time | |||
DateTimeField(auto_now?: bool, auto_now_add?: bool) | 用于存储 datetime.datetime | DateTimeInput | ||
DurationField | 用于存储 datetime.timedelta | |||
FilePathField(path=‘’, match=None, recursive=False, allow_files=True, allow_folders=False, max_length=100) | ||||
SlugField(max_length=50) | validate_slug 或 validate_unicode_slug |
数据字段
字段类型 | 解释 | 参数说明 | 表单部件 | 相关验证 | 补充 |
---|---|---|---|---|---|
BinaryField(max_length: int) | 用于存储二进制数据 | MaxLengthValidator | |||
FileField(upload_to?: str, storage, max_length=100) | 用于存储文件 | FieldFile? | |||
ImageField(upload_to=None, height_field=None, width_field=None, max_length=100) | 用于验证有效的图像的 FileField | ClearableFileInput | 需要 pillow 库 | ||
JSONField(encoder: json.JSONEncoder, decoder: json.JSONDecoder) | |||||
GeneratedField(expression, output_field, db_persist=None) | 数据库底层中由其他字段计算的字段 | ||||
注:FileField 实例会得到 django.db.models.fields.files.FieldFile 的代理 => 用于访问文件内容(如 open(mode='rb') 方法) |
表间关系字段
定义模型间的关系的三个特殊字段:
表间关系 | 对应字段类型 | 补充 |
---|---|---|
多对一 | ForeignKey(model: Model | str, …) | 支持自关联关系 |
多对多 | ManyToManyField(model: Model | str, through: Model | str, …) | 1. 建议使用复数形式 2. 只能设置在两个多对多模型之一 3. 总是会产生中间模型 |
一对一 | OneToOneField(model: Model | str, …) | |
注: |
- model => 关联模型
- through => 自定义中间模型 => 用于给多对多关系添加额外字段
多方的相关方法:
<model1>.<model2>_set.add(model: Model2, through_defaults: dict)
<model1>.<model2>_set.create(opt1, ..., through_defaults: dict)
<model1>.<model2>_set.set(model: Model2[], through_defaults: dict)
<model1>.<model2>_set.remove(model: Model2)
<model1>.<model2>_set.clear()
注:字段有命名限制 => 不能是关键字,不能有下划线,不能以下换线结尾
Meta 内部类
外链:模型 | Django 文档 | Django (djangoproject.com), 模型 Meta 选项 | Django 文档 | Django (djangoproject.com)
ordering: str[]
=> 指定排序的字段
verbose_name_plural
=>
abstract: bool
=> 指定当前模型是否为抽象基类 => 若为 true,那么该模型不会创建任何数据库表
indexes: models.Index[]
=> 用于创建数据库索引,参见 [[#模型索引]]
模型自定义方法
模型中的方法可以执行表级操作(这些定义不需要迁移)
例子:
class User(models.Model):
username = models.CharField("user's name",max_length=20)
password = models.CharField("user's password",max_length=20)
def func(self):
return f'{self.username} {self.password}'
@property
def virtual_attr(self):
return f'{self.username} {self.password}'
两个常用的自定义方法:
__str__()
=> 用于交互式控制台或后台中现实某个模型本身的内容get_absolute_url()
=> 告诉 Django 如何计算对象的 url
可以重写 save()
,delete()
等 Model
方法 =>
例子:
def save(self, *args, **kwargs):
# do something ...
super().save(args,kwargs)
# do something else ...
模型类继承
抽象基类 => 模型基类在 Meta
子类上定义字段 abstract = True
=> 该模型不会创建任何表,仅用于继承
多表继承 => 模型基类在 Meta
子类上定义字段 abstract = False
=> 继承链中的每个模型都会创建表
代理模型 => 模型子类在 Meta
子类上定义字段 proxy = True
=> 该模型不会创建任何表,但是可以操作基类的字段和方法
模型索引
[[#Meta 内部类]] 中定义的 indexes 用于给模型创建多条索引
models.Index 构造器选项 | 解释 |
---|---|
expressions | |
fields | |
name | |
db_tablespace | |
opclasses | |
condition |
表单
django.forms.forms
=> BaseForm
, Form
django.forms.models
=> BaseModelForm
, ModelForm
- 其中
Form
<BaseForm
;ModelForm
<BaseModelForm
BaseForm(data?: dict, files?: dict, field_order, auto_id, prefix, initial, error_class, label_suffix, empty_permitted, use_required_attribute, renderer)
data
=> 非文件数据的字典files
=> 文件数据的字典
BaseModelForm(data?: dict, files?: dict, instance?: Model, auto_id, prefix, initial, error_class, label_suffix, empty_permitted, use_required_attribute, renderer)
模型表单
参考:[[#models.fields.Field]], [[#forms.fields.Field]]
模型表单 => 绑定了模型的表单,支持的功能:
__str__
返回渲染 html 表单
数据库模型可以将一部分字段和字段参数的映射到模型表单 => 相关细节可参考 [[#映射表]]
例子:=> 实现数据库模型 User
到模型表单 UserForm
的映射
from django.forms import ModelForm
class UserForm(ModelForm):
class Meta:
model = User
fields = ['username', 'password', 'file']
注:上述定义大致能以继承 Form
类来实现部分功能,于是会比上述定义麻烦得多
映射表
数据库模型字段所属的包到模型表单字段所属的包的映射:
django.db.models.fields
=>django.forms.fields
django.db.models.fields.related
=>django.forms.models
数据库模型字段到模型表单字段的映射表:
数据库模型字段 | 模型表单字段 | 补充 |
---|---|---|
SmallIntegerField 或 BigIntegerField | IntegerField | |
BooleanField | BooleanField 或 NullBooleanField | |
BinaryField 或 TextField | CharField | |
ForeignKey | ModelChoiceField | |
ManyToManyField | ModelMultipleChoiceField | |
其他的类 | 同名类 | |
注:AutoField, BigAutoField, SmallAutoField => 不呈现在表单中 |
数据库模型字段参数到模型表单字段参数的映射表:
模型字段参数 | 表单字段参数 |
---|---|
verbose_name | label |
help_text | help_text |
choices 非空 | widget = Select |
blank=True | required = False |
附录-模型
django.db.models.fields
=>Field
django.forms.fields
=>Field
以下每个小节的命名都是某个类的最短后缀路径
models.fields.Field
Field(
verbose_name?: str,
name?: str,
primary_key=False,
max_length?: int,
unique=False,
blank=False,
null=False,
db_index=False,
rel,
default=NOT_PROVIDED,
editable=True,
serialize=True,
unique_for_date?: DateField,
unique_for_month?: DateField,
unique_for_year?: DateField,
choices: dict<str, any>,
help_text?: str,
db_column,
db_tablespace=False,
auto_created=False,
validators=(),
error_messages,
db_comment,
db_default=NOT_PROVIDED
)
blank: bool
,null: bool
=> 内存 或 数据库 中的是否可为空default: any
,db_defaul: any
=> 内存 或 数据库 中的默认值
forms.fields.Field
Field(
required=True,
widget,
label,
initial,
help_text,
error_msg,
show_hidden_initial=False,
validators=(),
localize=False,
disabled=False,
label_suffix,
template_name
)
Form, ModelFormuery
模型查询
数据库查询相关的包或类
django.db.models.base
=>Model
django.db.models.related
=>ForeignKey
,ManyToManyField
django.db.models.query
=>QuerySet
django.db.models.expressions
=>F
django.db.models.aggregates
=>Avg
,Min
,Max
django.db.models.manager
=>Manager
django.db.models.fields.json
=>KT
执行查询
创建或保存对象 =>
<Model>.save(**opts)
=> 保存模型对象<Manager>.create(**attr_opts)
=> 创建并保存对象
=> 例子:
# 例1
user_model = UserModel(username='150',password='62')
user_model.save()
# 例2
UserModel.objects.create(username='150',password='62') # => 会返回对应模型
修改对象 =>
<Model>.<attr> = new_val
=> 直接修改<Model>.<func>()
=> 间接修改<Model>.<many-Model>_set.add(<many-Model1>, ...)
=> 添加一个多方模型
的记录<Model>.<one-Model> = <one-Model>
=> 覆盖一个一方模型
的记录
检索对象 => 用到 QuerySet 类 => 通常通过 <Model>.objects.all()
得到
=> 通过 get()
获取单个对象
=> 通过 filter()
, exclude()
过滤或排除检索到的对象集合
注:QuerySet 是惰性的 => 每次过滤都只是在添加限制条件,并非真正执行了 sql,只会在你 “要使用” 时才会执行
=> 支持切片极其step语法 => 例如:<QuerySet>[0]
, <QuerySet>[0:5]
, <QuerySet>[::2]
, <QuerySet>[1::2]
注:指定步长后会执行 sql
其中 filter()
, exclude()
等方法支持如下形式的可选参数:
<attr>
=> 模型的属性<attr>__<lookup_type>
=> 指定模型属性的各种复杂查询 =>lookup_type
的常见类型 =>lte
,gte
.lt
,gt
,exact
,iexact
,regex
,iregex
,contains
,icontains
,startswith
,endswith
,istartswith
,iendswith
,in
<one-model>_id
=> 关联的表模型的 id<one-model>__<one-model-attr>
=> 指定一方模型
的属性的限制条件<one-model>__<one-model-attr>__<lookuptype>
=><many-model>__<many-model-attr>
=> 指定至少满足一个多方模型
的属性的限制条件<many-model>__<many-model-attr>__<lookuptype>
=>
参数值:- 常量
- F 表达式 =>
F(attr: str)
=> 可以引用当前模型的字段,并进行相关运算 - 可以使用
Min
,Max
,Avg
等
缓存问题 => 执行查询 | Django 文档 | Django (djangoproject.com)
异步查询
json 查询
json 字段在 filter()
, exclude()
等方法中的参数的组成:
- 前缀
<json-attr>
__<int>
=> 索引 json 数组的第<int>
个 json 对象__<key>
=> 索引 json 对象 key 为<key>
的 json 对象
KT(lookup: str)
=> 针对于 json 字段的类似于 F(lookup: str)
的函数
json 字段还支持更多的 lookup_type
:
contains
=>contained_by
=>has_key: str
=> 含有对应的 keyhas_keys: str[]
=> 含有给定 key 数组中的所有 keyhas_any_keys: str[]
=> 含有给定 key 数组中的一个 key
通过 Q 对象完成复杂查询
执行查询 | Django 文档 | Django (djangoproject.com)
附录-视图查询
注:下面的方法加上前缀 a
总是可以得到异步版本
Model 的类属性 | 用途 |
---|---|
objects: Manager | |
Model 的方法 | 用途 |
---|---|
save(force_insert=False, force_update=False, using=DEFAULT_DB_ALIAS, update_fields=None): void | 保存记录 |
Manager 的方法 | 用途 |
---|---|
all(): QuerySet | |
create(**kwargs): Model | 创建一个对象并保存记录 |
注:Manager 实例通常通过 Model.objects 获取 |
QuerySet 的方法 | 用途 |
---|---|
get(**kwargs): Model | 检索单个对象 |
filter(**lookup_args): QuerySet | 过滤对象 |
exclude(**lookup_args): QuerySet | 排除对象 |
values(): QuerySet | |
annotate(): QuerySet |
迁移(migrations)
topic:迁移, 管理文件,
ref:迁移操作, SchemaEditor,
数据库(database)
测试(test)
文件
ref:文件处理
涉及的包和模块:
django.forms.forms
=>Form
django.forms.fields
=>django.core.files.uploadedfile
=>UploadedFile
,TemporaryUploadedFile
获取文件上传
核心:视图中通过 <HttpRequest>.FILES.get("<file_key>"): TemporaryUploadedFile
获取指定文件
有几种方法:
open()
方法 => 直接将文件写入本地Model
的FileField
属性 => 可以将TemporaryUploadedFile
实例直接赋值给该属性
例子1: => 将上传的文件写入本地 => 请求的表单中设置 enctype="multipart/form-data"
和 method="POST"
属性,以及 <input type="file" name="my_file">
标签
def handel_file(file: TemporaryUploadedFile):
with open(f'media/{time.time_ns()}.html', 'wb+') as out:
for x in file.chunks():
out.write(x)
def my_view(req: HttpRequest):
form = Form(req.POST, req.FILES)
if req.method == 'POST' and form.is_valid():
file = req.FILES.get('my_file')
handel_file(file)
return HttpResponse("uploded file saved")
return HttpResponse("nothing to do")
例子2:
def my_view(req: HttpRequest):
u = m_user.User(username='liangxiongsl', password='lx15062', file=req.FILES.get('myfile'))
u.save()
例子3:
from django.forms import ModelForm
class UserForm(ModelForm):
class Meta:
model = User
fields = ['username', 'password', 'file']
def my_view(req: HttpRequest):
uf = UserForm(data={
'username': 'liangxiongsl',
'password': 'lx15062',
}, files={
'file': req.FILES.get('myfile')
})
uf.save()
例子4:
- 视图
def test(req: HttpRequest):
match req.method:
case 'GET':
return TemplateResponse(req, 'test.html', {'form': SongForm()})
case 'POST':
form = SongForm(req.POST, req.FILES)
if form.is_valid():
form.save()
return HttpResponse("test: 歌曲创建成功")
else:
return HttpResponse("test: 字段非法")
case _:
return HttpResponse("test: 请求方法错误")
- 模板
test.html
<form action="{% url 'test' %}" method="post" enctype="multipart/form-data">
{{ form }}
<input type="submit">
</form>
获取多个上传的文件
handler
附录-文件
模块索引:
django.core.files.uploadedfile
=>UploadedFile
,TemporaryUploadedFile
UploadedFile
UploadedFile 的方法 | 用途 | 补充 |
---|---|---|
read() | 一次性读取文件的所有数据 | 有内存溢出的风险 |
multiple_chunks(size: int): bool | 是否需要分块读取? | |
chunks(size: int): bytes | 分块读取数据 | 不易出现内存溢出 |
UploadedFile 的属性 | 解释 | 例子 |
---|---|---|
name: str | 上传的文件名 | sd.html |
size: int | 上传文件的大小 | |
content_type: str | text/html | |
content_type_extra: dict | ||
charset: str |
TemporaryUploadedFile
TemporaryUploadedFile
< [[#UploadedFile]]
TemporaryUploadedFile 的方法 | 解释 | 例子 |
---|---|---|
temporary_file_path(): str | 获取上传文件暂存在磁盘中的位置 | windows 上会暂存在 C:\Users\Administrator\AppData\Local\Temp\<file> |
InMemoryUploadedFile
InMemoryUploadedFile
< [[#UploadedFile]]
认证
django 的用户验证系统负责处理 用户账号、组、权限、基于 cookie 的会话、密码哈希化系统、为登录用户或限制内容提供表单和视图工具、可插拔的后端系统等
涉及的包和模块:
django.contrib.auth
=>authenticate()
,login()
,logout()
django.contrib.auth.models
=>User
,UserManager
django.contrib.contenttypes
=>django.contrib.contenttypes.models
=>ContentType
,ContentTypeManager
django.contrib.admin
=>ModelAdmin
django.contrib.admin.site
=>register()
django.contrib.auth.decorators
=>@login_required()
,@permission_required()
django.contrib.auth.mixins
=>LoginRequiredMixin
,PermissionRequiredMixin
注册 app 和中间件:
INSTALLED_APPS = [
'django.contrib.auth',
'django.contrib.contenttypes',
]
MIDDLEWARE = [
'django.contrib.sessions.middleware.SessionMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
]
=> auth
提供了 User
, Group
, Permission
三种模型及其中间模型(共 6 个模型),以及对应的数据库表
=> contenttypes
提供了 ContentType
模型及其对应的数据库表
注:auth
应用会为你的应用的每个模型创建 add
/ change
/ delete
/ view
四种权限(在 auth_permission
中)
User
< AbstractUser; AbstractBaseUser, PermissionsMixin; Model
- 属性
=>username
,password
,email
,first_name
,last_name
=>user_permissions: ManyRelatedManager
,groups: ManyRelatedManager
=> 2 个多对多字段 - 类属性 =>
objects: UserManager
- 命令
=>python manage.py createsuperuser --username=joe --email=joe@example.com
=> 创建管理员
=>python manage.py changepassword <username>
=> 修改用户密码
UserManager
- 方法 =>
create_user(username, email?, password?, first_name?, last_name?): User
=> 创建普通用户
ContentType
- 类属性 =>
objects: ContentTypeManager
ContentTypeManager
- 方法 =>
get_for_model(model: Model, for_concrete_model=True): ContentType
=> 获取应用模型唯一对应的ContentType
实例
用户验证 => authenticate(username, password): User | None
权限和认证
例子:=> 寻找属于某个应用的指定权限
from django.contrib.auth.models import Permission
def get_permission(app: str, model: str, permission: str):
return Permission.objects.all().get(
content_type__app_label=app,
content_type__model=model,
codename__startswith=permission
)
# 添加权限
user.user_permissions.add(get_permission('admin', 'user', 'change'))
判断是否有权限 => <User>.has_perm('<app>.<permission>_<model>'): bool
获取用户的用户权限 => <User>.get_user_permissions(): str[]
获取用户的组权限 => <User>.get_group_permissions(): str[]
获取用户的所有权限 => <User>.get_all_permissions()
注册权限 =>
content_type = ContentType.objects.get_for_model(m_user.User)
try:
Permission.objects.create(
codename="do_something_with_user",
name='do something with user model',
content_type=content_type,
)
except IntegrityError:
print('add permission failed')
缓存问题 => 用户权限不会实时更新给 User
的实例,如 <User>.has_perm("<app>.<permission>_<model>")
只会检查该实例的缓存中是否有该权限
代理模型相关 => 代理模型不会继承其父模型的权限
Meta
=> 模型的 Meta
内部类的 permissions: 2-tuple[]
能添加默认权限?
web请求认证
前置知识:[[python#装饰器]]
获取请求的用户 => request.user: User | AnonymousUser
=> 得到 已认证用户 或 匿名用户
判断用户是否已认证 => <User>.is_authenticated
或 AnonymousUser.is_authenticated
认证 => authenticate(username, password): User | None
登录 => login(req: HttpRequest, user: User, backend)
=> 将认证成功的 <user>
插入到 django_session
表中,从而实现登录
登出 => logout(req: HttpRequest)
例子:=> 登录视图
def my_view(req: HttpRequest):
username = req.POST["username"]
password = req.POST["password"]
user = authenticate(req, username=username, password=password)
if user is not None:
login(req, user)
# Redirect to a success page ...
else:
print("user don't exists")
装饰器 @login_required(redirect_field_name='next', login_url=None)
=> 若用户没有登录,那么请求会重定向到 <login_url>?<redirect_field_name>={<req>.path}
注:<login_url>
默认为 settings
模块中的变量 LOGIN_URL
例子2:=> 函数视图上使用装饰器 @login_required()
=> 强制请求进行登录
# (1) 配置登录路由:
urlpatterns = [
path('login/', v_index.do_login, name='login'),
path('any_view/', v_index.any_view, name='any_view'),
]
# (2) 定义登录视图:
# GET => 获取登录表单
# POST => 实现登录功能和转发
@require_http_methods(['GET', 'POST'])
def do_login(req: HttpRequest):
if req.method == 'GET':
return render(req, 'login.html', {'next': req.GET.get("next")})
username = req.POST["username"]
password = req.POST["password"]
user = authenticate(req, username=username, password=password)
if user is not None:
login(req, user)
print('login success')
return HttpResponseRedirect(req.GET.get("next"))
else:
print('login failed')
return HttpResponseRedirect(f'/login/?next={req.GET.get("next")}')
# (3) 在某个视图调用前进行验证
@login_required(redirect_field_name='next', login_url="/login/")
def any_view(req: HttpRequest):
...
=> 类似的装饰器:
user_passes_test(test_func: (req) => bool, login_url=None, redirect_field_name='next')
=>test_func
返回False
时会重定向permission_required(perm: tuple, login_url=None, raise_exception=False)
=> 检查用户是否有权限(可以是多个权限)
混合类 LoginRequiredMixin
例子3:类视图上继承混合类 LoginRequiredMixin
=> 强制请求进行登录
...
class AnyView(LoginRequiredMixin, TemplateView):
redirect_field_name='next'
login_url="/login/"
template_name = 'test.html'
注:LoginRequiredMixin
必须是第一个继承
=> 类似的混合类:
UserPassesTestMixin
PermissionRequiredMixin
AccessMixin
认证视图
django.contrib.auth.urls
包含 8 条路由:
path | name | 模板 |
---|---|---|
login/ | login | registration/login.html |
logout/ | logout | registration/logged_out.html |
password_change/ | password_change | registration/password_change_form.html |
password_change/done/ | password_change_done | registration/password_change_done.html |
password_reset/ | password_reset | registration/password_reset_form.html |
password_reset/done/ | password_reset_done | registration/password_reset_done.html |
reset/<uidb64>/<token>/ | password_reset_confirm | registration/password_reset_confirm.html |
reset/done/ | password_reset_complete | registration/password_reset_complete.html |
密码管理
邮箱
鸣谢:Django使用QQ邮箱发送邮件 - 南风丶轻语 - 博客园 (cnblogs.com)
涉及的包和模块:
django.core.mail
=>send_mail()
,send_mass_mail()
,mail_admins()
,mail_managers()
Django 支持多种邮箱后端:
django.core.mail.backends.smtp.EmailBackend
django.core.mail.backends.console.EmailBackend
django.core.mail.backends.filebased.EmailBackend
django.core.mail.backends.locmem.EmailBackend
django.core.mail.backends.dummy.EmailBackend
配置 smtp
的例子:
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
# qq smtp 主机
EMAIL_HOST = 'smtp.qq.com'
EMAIL_PORT = 587
EMAIL_USE_TLS = True
# qq 邮箱
EMAIL_HOST_USER = '1708494470@qq.com'
# qq 邮箱上开启 SMTP 后得到的授权码
EMAIL_HOST_PASSWORD = 'gotcasiskumechjd'
# EMAIL_USE_SSL = True
# EMAIL_SSL_KEYFILE =
# EMAIL_SSL_CERTFILE =
send_mail()
=> 发送普通邮件
send_mail(subject, message, from_email, recipient_list: str[],
fail_silently=False, auth_user?, auth_password?, connection?: Backend,
html_message?
)
注:若指定了 html_message
,那么邮件会变为 multipart/alternative
实例
send_mass_mail()
=> 大量发送邮件
send_mass_mail(data: tuple,
fail_silently=False, auth_user?, auth_password?, connection?: Backend
)
注:data
中每个元素为 (subject, message, from_email, recipient_list)
注2:只能发 text/plain
mail_admins()
=> 给网站所有者们发送邮件
mail_admins(subject, message,
fail_silently=False, connection?: Backend,
html_message?
)
=> 用到的额外配置:
EMAIL_SUBJECT_PREFIX = '[mysite:django] '
SERVER_EMAIL = '1708494470@qq.com'
ADMINS = [
('lx', '1506218507@qq.com'),
# ('drk', '1708494470@qq.com')
]
mail_managers()
=> 给管理员们发送邮件
mail_admins(subject, message,
fail_silently=False, connection?: Backend,
html_message?
)
=> 用到的额外配置:
EMAIL_SUBJECT_PREFIX = '[mysite:django] '
SERVER_EMAIL = '1708494470@qq.com'
MANAGERS = [
('lx', '1506218507@qq.com'),
# ('drk', '1708494470@qq.com')
]
EmailMessage
类
EmailMessage(subject?, body?, from_email?, to?: tuple | array,
bcc?: tuple | array, connection?: Backend,
attachments?: MIMEBase | (filename, content, minetype),
headers?: dict, cc?, reply_to?
)
=> 方法:
send(fail_silently=False): int
=> 发送邮件消息,发送成功返回 1,否则返回 0message(): SafeMIMEMultipart
=>recipients(): array
=> 返回一个包含邮件所以收件人的列表attach(filename?, content?, mimetype?)
=> 添加附件attach_file(path, minetype?)
=> 相对于项目根目录发送附件,甚至可以把db.sqlite3
作为附件
=> 例子:
em = EmailMessage("嘿咻咻", "my content ...", '1708494470@qq.com', [user.email])
em.attach('geji.html',
'<img src="https://ts4.cn.mm.bing.net/th?id=OVP.FqDKX5rpgVBhbjE43cH4PQEsC7&w=608&h=342&c=7&rs=1&qlt=90&o=6&dpr=1.3&pid=1.7">',
'text/html')
em.attach_file('manage.py')
em.send()
get_connection()
常见问题:
- 使用 qq stmp 服务时,确保用于发邮件的邮箱只能是
EMAIL_HOST_PASSWORD
对应的邮箱
缓存
缓存的作用 => 加速访问者访问网站的内容;网站暂存和检索某些 python 对象
缓存引擎:
- Memcached
- Redis
- 数据库缓存
- 文件系统缓存
- 本地内存缓存(默认)
- 虚拟缓存(用于开发模式)
- 自定义缓存后端
注:可以配置多条缓存,不同缓存可以使用不同缓存引擎
需要缓存某个实例(视图 / 模板 / 任何 python 对象)时,都需要指定 缓存引擎,缓存键,缓存版本号,缓存实例
=> [[#缓存参数]],[[#缓存中间件]] 描述了一些必要配置
=> [[#视图缓存]],[[#模板片段缓存]] 描述了两种缓存过程
=> [[#访问缓存]] 能直观的了解这个过程
Memcached
CACHES = {
"default": {
"BACKEND": "django.core.cache.backends.memcached.PyMemcacheCache",
# "BACKEND": "django.core.cache.backends.memcached.PyLibMCCache",
"LOCATION": "127.0.0.1:11211",
# "LOCATION": ["172.19.26.240:11211", "172.19.26.242:11211"],
# "LOCATION": "unix:/tmp/memcached.sock",
},
}
Redis
CACHES = {
"default": {
"BACKEND": "django.core.cache.backends.redis.RedisCache",
"LOCATION": "redis://127.0.0.1:6379",
# "LOCATION": "redis://username:password@127.0.0.1:6379",
# "LOCATION": [
# "redis://127.0.0.1:6379", # leader
# "redis://127.0.0.1:6378", # read-replica 1
# "redis://127.0.0.1:6377", # read-replica 2
# ],
}
}
=> pip install redis
, pip install hiredis
数据库缓存
CACHES = {
"default": {
"BACKEND": "django.core.cache.backends.db.DatabaseCache",
"LOCATION": "my_cache_table",
}
}
=> python manage.py createcachetable
=> 创建缓存表
文件系统缓存
CACHES = {
"default": {
"BACKEND": "django.core.cache.backends.filebased.FileBasedCache",
"LOCATION": "/var/tmp/django_cache",
# "LOCATION": "c:/foo/bar",
}
}
注:LOCATION
目录不宜包含在 MEDIA_ROOT
或 STATICFILES_FINDERS
所指定的目录中,否则可能造成敏感数据的泄露
本地内存缓存
CACHES = {
"default": {
"BACKEND": "django.core.cache.backends.locmem.LocMemCache",
"LOCATION": "unique-snowflake",
}
}
虚拟缓存
CACHES = {
"default": {
"BACKEND": "django.core.cache.backends.dummy.DummyCache",
}
}
缓存参数
TIMEOUT
=> 缓存超时时间(s),默认为 300;设为 None
时将永久缓存;设为 0 时不缓存
OPTIONS
=> 传递给缓存后端的选项,通常包含 MAX_ENTRIES: int
(最大缓存条目),CULL_FREQUENCY: int
(条目达到 MAX_ENTRIES
时,将删除 MAX_ENTRIES / CULL_FREQUENCY
个条目)
KEY_PREFIX
=> 缓存实例进行保存或检索时,区别它们的 cache_key
的一部分(与缓存引擎 CACHES
的 key 一一对应)
注:cache_key
形如 <KEY_PREFIX>:<version>:<key>
(version
为缓存实例的版本号(默认为 1),key
是我们手动 或 Django 自动缓存实例时使用的键)
VERSION
=> 缓存实例的默认版本号
KEY_FUNCTION: str
=> 指定一条到某个函数的路径,该函数负责处理将缓存实例的 cache_key
进行更改,其形如 (cache_key: str) => str
例子:
CACHES = {
"default": {
"BACKEND": "django.core.cache.backends.filebased.FileBasedCache",
"LOCATION": "/var/tmp/django_cache",
"TIMEOUT": 60,
"OPTIONS": {"MAX_ENTRIES": 1000},
}
}
缓存中间件
配置:
MIDDLEWARE = [
"django.middleware.cache.UpdateCacheMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.cache.FetchFromCacheMiddleware",
]
注:中间件的执行顺序是从后往前依次执行的 => FetchFromCacheMiddleware
在请求阶段执行,UpdateCacheMiddleware
在响应阶段执行
其他配置:
CACHE_MIDDLEWARE_ALIAS
=> 用于存储的缓存别名CACHE_MIDDLEWARE_SECONDS
=> 应缓存个页面的秒数,默认为 600CACHE_MIDDLEWARE_KEY_PREFIX
=>
视图缓存
django.views.decorators.cache
=> @cache_page()
@cache_page(timeout: int, cache?: str, key_prefix?: str)
timeout
=> 缓存过期时间(s)cache
=> 指定缓存配置,即CACHES
中的一个key
key_prefix
=>
通过路由缓存视图 => 根据 cache_page
的装饰器特性
- 例子:
from django.views.decorators.cache import cache_page
urlpatterns = [
path("foo/<int:code>/", my_view),
]
# =>
urlpatterns = [
path("foo/<int:code>/", cache_page(60 * 15)(my_view)),
]
make_template_fragment_key()
模板片段缓存
{% load cache %}
{% cache 500 sidebar %}
.. sidebar ..
{% endcache %}
访问缓存
相关模块和包 | 内容 |
---|---|
django.core.cache | caches , cache , CacheHandler |
django.core.cache.backends.db | DatabaseCache |
caches: CacheHandler
对象 => caches[cache_name: str]: DatabaseCache
cache: DatabaseCache
对象 => 等价于 caches['default']
DatabaseCache
类
set(key: str, value: any, timeout=DEFAULT_TIMEOUT, version=None)
=> 为一个数据设置缓存,具有时间限制(s) => 注:timeout
为 0 时不缓存,为 None 时缓存长时间不会过期get(key: str, default=None, version=None): any
=> 获取一个缓存数据add(key: str, value: any, timeout=DEFAULT_TIMEOUT, version=None)
=> 为一个数据设置缓存,仅当该 key 对应的数据不存在时get_or_set(key: str, value: any, timeout=DEFAULT_TIMEOUT, version=None): any
=> 若 key 存在,那么返回 key 对应的值;否则将 key 对应的值设为 value,并返回 valueget_many(keys: str[], version=None): dict
=> 提供一组 key,返回一个缓存字典delete(key: str, version=None)
=> 删除指定的键值对delete_many(keys: str[], version=None)
=> 删除多个指定的键值对clear()
=> 删除所有键值对touch(key: str, timeout=DEFAULT_TIMEOUT, version=None)
=> 设置键值对的新的过期时间incr(key: str, delta=1, version=None)
=> 增加键值对的过期时间decr(key: str, delta=1, version=None)
=> 减少键值对的过期时间close()
=> 关闭与缓存的连接
注:version
默认为 VERSION
指定的值,通常为 1
下游缓存
下游缓存 => 一般 Django 缓存的视图并不会区分请求的 request
是否不同,虽然有性能的优势,但是不能区分每个用户 => 造成安全问题
涉及的模块和包 | 内容 |
---|---|
django.views.decorators.vary | @vary_on_headers() , @vary_on_cookie |
django.views.decorators.cache | @cache_control() |
django.utils.cache | patch_vary_headers() |
@vary_on_headers(*headers: str)
=> 通过头部信息来区分不同的请求
- 例子:
@vary_on_headers("User-Agent", "Cookie")
patch_vary_headers(res: HttpResponse, vary_headers: str[])
=> 类似
=> 例子:
def my_view(request):
response = render(request, "template_name")
patch_vary_headers(response, ["User-Agent", "Cookie"])
return response
@vary_on_cookie
=> 等价于 @vary_on_headers("Cookie")
用户通常面临两种缓存:
- 浏览器缓存(私有缓存)
- 其服务提供商的缓存(公共缓存)
@cache_control(private?: bool, public?: bool, max_age?: int)
=> 指定该视图是否允许公共缓存
patch_cache_control(res: HttpResponse, private?: bool, public?: bool, max_age?: int)
=> 同上
=> 例子:
@vary_on_cookie
def list_blog_entries_view(request):
if request.user.is_anonymous:
response = render_only_public_entries()
patch_cache_control(response, public=True)
else:
response = render_private_and_public_entries(request.user)
patch_cache_control(response, private=True)
return response
会话
会话允许每个站点访问者存储和检索自己的数据
- 基于 数据库 / 文件 的会话 => 存储于服务端
- 基于 cookie 的会话 => 存储于客户端
- 基于 缓存 的会话 => 依赖于 [[#缓存]] 的配置(可能基于 内存 / 数据库 / 文件 等)
涉及的模块和包 | 内容 |
---|---|
django.contrib.session.models | Session |
配置:
MIDDLEWARE = [
'django.contrib.sessions.middleware.SessionMiddleware'
]
会话引擎
- 基于数据库的会话 =>
Session
模型及其数据库表 - 缓存会话 =>
- 基于文件的会话
- 基于 cookie 的会话
基于数据库的会话:
SESSION_ENGINE = "django.contrib.sessions.backends.db"
INSTALLED_APPS = [
'django.contrib.sessions',
]
注:数据会保存在 django_session
表中
基于缓存的会话:
SESSION_CACHE_ALIAS = "caches_key" # 设置为 CACHES 配置的 key,默认为 "default"
# (1) 写透缓存(持久性缓存) => 会话读取使用缓存,或者如果数据已从缓存中逐出,则使用数据库
SESSION_ENGINE = "django.contrib.sessions.backends.cached_db"
# (2) 非持久性缓存 => 逐出过程 可能发生在缓存填满或缓存服务器重新启动时,这将意味着会话数据丢失,包括用户的注销状态
SESSION_ENGINE = "django.contrib.sessions.backends.cached"
基于文件的会话:
SESSION_ENGINE = "django.contrib.sessions.backends.file"
# 指定存储会话的目录
SESSION_FILE_PATH = BASE_DIR / "path/to/file-session-dir"
基于cookie的会话:
SESSION_ENGINE = "django.contrib.sessions.backends.signed_cookies"
# Django 脚手架初始化的项目会自动生成随机的 SECRET_KEY
SECRET_KEY =
注:SESSION_COOKIE_HTTPONLY = TRUE
能防止 js 访问存储的数据
注2:由于 cookie 存储在客户端,通常使用 [[#加密签名]] 来保证数据安全
在视图中使用会话
涉及的模块和包
django.contrib.sessions.backends.base
=>SessionBase
django.contrib.sessions.backends.file
=>SessionStore
django.contrib.sessions.serializers
=>JSONSerializer
注:SessionStore
<SessionBase
<request>.session: SessionStore
SessionBase
的方法:
- (重点)实现了 [[python#
__something__
|标准 dict 方法]] get(key, default?): any
,pop(key, default?): any
keys(): dict_keys
,items(): dict_items
,clear()
flush()
=> 删除 session => 其中的 cookie 也会被删除set_test_cookie()
,test_cookie_worked(): bool
,delete_test_cookie()
=> 用于测试 cookie 是否可用get_session_cookie_age(): int
=> 获取会话 cookie 的存活时间 (s)get_expiry_age(modification=datetime.now(), expiry?): int
=> 获取会话 cookie 的剩余存活时间 (s)get_expiry_date(): datetime
=> 获取 session 的过期日期get_expire_at_browser_close(): bool
=> 会话 cookie 是否在用户关闭 web 浏览器时过期clear_expired()
=> 删除已过期的会话cycle_key()
=>
注:SESSION_COOKIE_AGE: int
默认为 1209600
注2:cookie 存储的数据特定于某个 session,而且这些数据必须是可序列化的
SESSION_SERIALIZER
=> 序列化器,默认为 JSONSerializer
,即存储的 Python 数据使用 json 序列化
在视图外使用会话
管理会话
会话保存的时机:
# Session is modified.
request.session["foo"] = "bar"
# Session is modified.
del request.session["foo"]
# Session is modified.
request.session["foo"] = {}
# Gotcha: Session is NOT modified, because this alters
# request.session['foo'] instead of request.session.
request.session["foo"]["bar"] = "baz"
request.session.modified = True
=> 告知 session 对象它已被修改 =>
SESSION_SAVE_EVERY_REQUEST: bool
=> 是否每次请求都更新 session
SESSION_EXPIRE_AT_BROWSER_CLOSE: bool = False
=> 在关闭浏览器时是否删除 session
python manage.py clearsessions
=> 清除会话存储,但不包括 缓存会话(缓存会在自动删除过时数据)、以及cookie会话(这种会话数据存储在客户端浏览器上)
会话相关的配置列表:
SESSION_CACHE_ALIAS
SESSION_COOKIE_AGE
SESSION_COOKIE_DOMAIN
SESSION_COOKIE_HTTPONLY
SESSION_COOKIE_NAME
SESSION_COOKIE_PATH
SESSION_COOKIE_SAMESITE
SESSION_COOKIE_SECURE
SESSION_ENGINE
SESSION_EXPIRE_AT_BROWSER_CLOSE
SESSION_FILE_PATH
SESSION_SAVE_EVERY_REQUEST
SESSION_SERIALIZER
扩展数据库后端
疑问
#疑问
消息
涉及的模块和包:
django.contrib.messages.storage.session
=>SessionStorage
django.contrib.messages.storage.cookie
=>CookieStorage
django.contrib.messages.storage.fallback
=>FallbackStorage
django.contrib.messages.storage.base
=>BaseStorage
,Message
django.contrib.messages.constants
=>DEBUG
,INFO
,SUCCESS
,WARNING
,ERROR
,DEFAULT_LEVELS
,DEFAULT_TAGS
django.contrib.messages.api
=>add_message()
,debug()
,info()
,success()
,warning()
,error()
,get_messages()
,set_level()
django.contrib.messages.views
=>SuccessMessageMixin
配置:
INSTALLED_APPS = [
`django.contrib.messages`,
]
MIDDLEWARE = [
`django.contrib.session.middleware.SessionMiddleware`,
`django.contrib.message.middleware.MessageMiddleware`,
]
TEMPLATES = [
{
'OPTIONS': {
'context_processors': [
'django.contrib.messages.context_processors.messages',
],
},
},
]
存储后端
SessionStorage
=> 存储在请求的会话中的所有消息CookieStorage
=> 将消息存储在 Cookie 中 => 使用密码哈希签名;cookie 数据超过 2048 字节时旧的消息会被覆盖FallbackStorage
=> 默认的存储后端,使用CookieStorage
和SessionStorage
,拥有最好的性能BaseStorage
=> 存储后端基类
注:可以通过MESSAGE_STORAGE
设置存储后端
消息级别 => 对应一个整数,以及一个字符串(消息标签)
消息级别 | 值 | 消息标签 | 用途 |
---|---|---|---|
DEBUG | 10 | debug | 与开发相关的消息 |
INFO | 20 | info | 给用户的参考消息 |
SUCCESS | 25 | success | 一个动作成功了 |
WARNING | 30 | warning | 未发生的故障,但可能即将发生 |
ERROR | 40 | error | 某项动作没有成功或发生了其他故障 |
注: MESSAGE_LEVEL: int 配置了需要处理的消息的最小级别 | |||
注2:DEFAULT_LEVELS: dict<str, int> , DEFAULT_TAGS: dict<int, str> |
使用消息
添加消息:
add_message(req: HttpRequest, level: int, msg, extra_tags?: str, fail_silently=False)
debug(req: HttpRequest, msg, extra_tags?: str, fail_silently=False)
info(req: HttpRequest, msg, extra_tags?: str, fail_silently=False)
success(req: HttpRequest, msg, extra_tags?: str, fail_silently=False)
warning(req: HttpRequest, msg, extra_tags?: str, fail_silently=False)
error(req: HttpRequest, msg, extra_tags?: str, fail_silently=False)
获取消息:
get_messages(req: HttpRequest): BaseStorage
=> 可以看做是 Message
的数组
Message(level, message, extra_tags?)
level
=> 消息级别message
=> 消息实际内容extra_tags
=> 包含自定义消息标签的字符串,消息标签之间以空格" "
分隔level_tag
=> 消息标签tags
=> 消息的所有标签
注册消息 => DEFAULT_TAGS[level] = tag
临时修改消息最低级别 => set_level(req: HttpRequest, level: int)
类视图添加消息 =>
标记要清理的消息 => <BaseStorage>.used = True
避免消息被清理 => <BaseStorage>.used = False
注:<BaseStorage>
被 for
迭代后会进行 used
标记
信号
topic:信号
涉及的模块和包 | 内容 | 解释 |
---|---|---|
django.dispatch | Signal , @receiver() | |
django.db.models.signals | pre_init , post_init , pre_save , post_save , pre_delete , post_delete , m2m_changed , class_prepared | 模型相关信号 |
django.db.models.signals | pre_migrate , post_migrate | 迁移相关信号 |
django.core.signals | request_started , request_finished , got_request_exception | 请求/响应 相关信号 |
django.test.signals | setting_changed , template_rendered | 测试相关信号 |
django.db.backends.signals | connection_created | 数据库相关信号 |
Signal(use_caching=False)
Signal
的方法:
connect(receiver: func, sender?, weak=True, dispatch_uid?)
send(sender, **named)
disconnect(receiver?: func, sender?, dispatch_uid?): bool
概念:
- 信号处理器 => 一个 python 方法,形如
(sender, **kwargs) => void
- 信号 => 一个
Signal
实例 - 信号发送者 => 一个 python 对象
- 绑定 处理器 和 信号:
=> 方式1 =>@receiver(signal: Signal, **kwargs)
=> 信号处理器 接受 信号
=> 方式2 =><Signal>.connect(receiver: func, sender?, weak=True, dispatch_uid?)
=> 信号 连接 信号处理器 - 发送信号
注:django 会自动负责request_finished
,request_started
,setting_changed
,got_request_exception
等信号的发送 => 我们通常可以直接将这些信号与某些接收器进行连接
注2:一对 信号 和 信号处理器 可能被绑定多次 =>
信号处理器过滤处理特定类型的 信号发送者 发送的信号 => @receiver(signal: Signal, sender=ExampleType)
安全
topic:Django中的用户认证,加密签名, 发送邮件, Django 的安全性, 系统检查框架
ref:点击劫持保护, 系统检查框架,Django 异常
加密签名
SECRET_KEY: str
=> 用于生成签名值
注:配置会在 startproject
初始化项目时自动生成
SECRET_KEY_FALLBACKS
=>
django.core.signing
=> Signer
, BadSignature
, TimestampSigner
, SignatureExpired
Signer(*, key?: str, sep=":", salt?, algorithm?, fallback_keys?)
- key => 默认为
SECRET_KEY
指定的值 - sep => 不能是 字母 / 数字 / 连字符
-
/ 下划线_
- salt => 盐值
- algorithm => hashlib 的算法类型,默认为
sha256
- fallback_keys =>
Signer
的方法:
sign(original_val: any): str
=> 将数据进行签名unsign(signed_val: str): any
=> 将签名的数据解签sign_object(original_val: any, serializer=JSONSerializer, compress=False): str
=> 将数据进行签名unsign_object(signed_val: str, serializer=JSONSerializer, **kwargs): any
=> 将签名的数据解签
注:BadSignature
为解签异常
TimestampSigner(*, key?: str, sep=":", salt?, algorithm?)
< Signer
TimestampSigner
的方法:
sign(original_val: any): str
=> 将数据进行签名,并附加时间戳unsign(signed_val: str, max_age?: int | timedelta)
=> 若从签名到现在经过的时间不超过max_age
秒,那么正常解签,否则会抛出异常SignatureExpired
两个 TimestampSigner 对象签名/解签方法的方法的快捷方式:
dumps(obj, key?, salt="django.core.signing", serializer=JSONSerializer, compress=False)
=> 签名对象
loads(s, key?, salt="django.core.signing", serializer=JSONSerializer, max_age?, fallback_keys?)
=> 解签对象
CSRF
涉及的概念
keywords
=>CORS
,CSRF
- 中间件 =>
django.middleware.csrf.CsrfViewMiddleware
- 框架 =>
DRF
- 装饰器 =>
django.views.decorators.csrf.csrf_exempt
CSRF_TRUSTED_ORIGINS = ['http://localhost:5173']
跨域请求错误的三种解决方法:
- 注释掉中间件
django.middleware.csrf.CsrfViewMiddleware
=> 取消跨域请求伪造防护机制 - 视图前添加装饰器
csrf_exempt
注:浏览器发送不安全请求(如: post
, put
, delete
)时会检查 response headers
的键 Access-Control-Allow-Origin
是否为本站的源(即是否有 协议://域名:端口号
) => 在服务端添加上这个头即可
性能
topic:Django 缓存框架, 性能和优化
其他
分页
django.core.paginator
=> Paginator
, Page
Paginator(arr: any[], per_page: int)
per_page
=> 每页的对象个数
Paginator
的属性
count
=> 总共的对象个数num_pages
=> 总页数page_range: range
=> 页数范围,与num_pages
差不多
Paginator
的方法page(num: int): Page
=> 获取某一页,下标从 1 开始
Page
的属性
object_list: any[]
Page
的方法__getitem__(index: int)
has_next(): bool
has_previous(): bool
has_other_pages(): bool
next_page_number(): int
start_index(): int
end_index(): int
实用技巧
路由直接转发到模板的闭包: (无需转发到其他的路由表,或视图)
from django.template.response import TemplateResponse
def to_template(t_path: str, context:dict = {}):
def view(req: HttpResponse):
return TemplateResponse(req, t_path, context=context)
return view
# 例子:
urlpatterns = [
path('', to_template("path/to/template.html"), {"foo": "bar"}),
]
参考:[[python#闭包]]