站点图标 AI技术聚合

django框架全解

Table of Contents

目录

  • 简介
    • MVC与MTV模型
      • MVC
      • MTV
    • 创建项目
    • 目录
    • 生命周期
    • 静态文件配置(无用)
  • 启动django
  • 路由
    • 分组
      • 无名分组
      • 有名分组
    • 路由分发
    • 反向解析
    • 反向解析结合分组
    • 名称空间
    • re_path与path
      • 自定义转换器
  • 视图
    • HttpRequest
      • 常用方法
  • HttpResponse
    • Json
  • FBV和CBV
  • 模板(前后端分离不用看)
    • 变量
    • 过滤器
      • 循环
      • 分支
      • csrf
      • with
      • 自定义过滤器和标签
    • 模板的导入和继承
    • 模板的继承\派生之extends标签、block标签
    • inclusion_tag
  • ORM模型层
    • 单表操作
      • 创建models.py
      • 配置数据库连接
      • 在链接mysql数据库前,必须事先创建好数据库,下载连接
      • 配置APP
      • 打印sql-orm
      • 迁移数据库
      • 修改字段
      • 操作记录
        • 新增
        • 查询
          • QuerySet对象:
          • 链式处理:
          • 其他查询API:
          • 双下划线查询
          • F与Q查询
          • 聚合
          • 分组
        • 更新
        • 删除
    • 多表
      • 创建模型
      • CRUD操作
        • 一对多
          • 新增
          • 查询
        • 一对一
          • 新增
          • 查询
        • 多对多
          • 新增
          • 修改,删除
          • 查询
        • 连续跨多张表查询
      • 双下划线查询
        • 跨两表
          • 一对一
          • 一对多查询(模型类Book与Publish)
          • 多对多查询(模型类Book与Author)
        • 跨多表
    • 常用models字段参数
      • Field
      • 参数
      • 关系字段
      • 多对多全自动半自动手动
      • 元信息
      • 自定义字段(一般用不上)
    • orm高级操作
      • QuerySet对象
      • exists()与iterator()方法
      • select_related
      • prefetch_related()
      • extra
        • 参数之select
        • 参数之where / tables
      • 原生sql
      • 整体插入
      • 事务
        • 装饰器用法
        • with上下文管理
        • 回滚
        • 举例
      • defer和only(鸡肋)
  • 缓存机制
    • Django中的缓存应用
      • 视图函数
      • 全站
  • django序列化
  • django分页器
    • 使用
  • form
    • 校验字段功能
    • 渲染(前后端分离不用看)
    • 局部钩子
    • 全局钩子
    • 常用字段与插件
    • 自定义校验
    • form校验的流程
    • ModelForm
      • save()方法
    • choice字段
  • django的cookie和session组件
    • cookie
      • 获取
      • 设置
      • 删除
    • Session
      • 相关方法
      • 配置
  • 中间件
    • 自定义中间件
      • process_request(self,request)
      • process_response(self, request, response)
      • process_view(self, request, view_func, view_args, view_kwargs)
      • process_exception(self, request, exception)
      • process_template_response(self,request,response)
      • 注册中间件
    • 执行中间件的流程
    • Django请求流程图
    • 中间件应用
  • AUTH
    • auth模块常用方法
    • User对象的属性
    • 扩展默认的auth_user表
  • XSRF
    • 相关装饰器
    • CSRF的流程
      • 在form表单中应用
      • 在Ajax中应用
      • 放在cookie里
      • 其它操作
  • Django实现Websocket
    • dwebsocket安装
    • dwebsocket配置
    • 使用
    • 详解

简介

MVC与MTV模型

MVC

MVC就是把Web应用分为模型(M),控制器©和视图(V)三层,他们之间以一种插件式的、松耦合的方式连接在一起,模型负责业务对象与数据库的映射(ORM),视图负责与用户的交互(页面),控制器接受用户的输入调用模型和视图完成用户的请求。

MTV

Django的MTV模式本质上和MVC是一样的,也是为了各组件间保持松耦合关系,只是定义上有些许不同,Django的MTV分别是值:

  1. M 代表模型(Model): 负责业务对象和数据库的关系映射(ORM)。
  2. T 代表模板 (Template):负责如何把页面展示给用户(html)。
  3. V 代表视图(View): 负责业务逻辑,并在适当时候调用Model和Template。

除了以上三层之外,还需要一个URL分发器,它的作用是将一个个URL的页面请求分发给不同的View处理,View再调用相应的Model和Template。

创建项目

# 在命令行执行以下指令,会在当前目录生成一个名为appserver的文件夹,该文件夹中包含Django框架的一系列基础文件
django-admin startproject appserver

# 切换到appserver目录下边
cd mysite 
# 创建功能模块user,此处的startapp代表创建application下的一个功能模块。
python manage.py startapp user

# 运行http://127.0.0.1:8000
python manage.py runserver 8000

目录

-manage.py---项目入口,执行一些命令
-项目名
    -settings.py  全局配置信息
    -urls.py      总路由
-app名字
    -migrations   数据库迁移的记录
    -models.py    数据库表模型
    -views.py     视图函数

生命周期

1、路由层(根据不同的地址执行不同的视图函数,详见urls.py)

2、视图层(定义处理业务逻辑的视图函数,详见views.py)

3、模型层 (跟数据库打交道的,详解models.py)

4、模板层(待返回给浏览器的html文件,详见templates)

静态文件配置(无用)

对于前后端分离的项目,静态文件一般用nginx代理,不用特意在webServer框架再配置

STATIC_URL = '/static/'
STATICFILES_DIRS = [
    os.path.join(BASE_DIR, 'static'),
]

前端对应配置:

# 加载静态目录
{% load static %}
<script src="{% static 'js/jQuery3.6.0.js' %}"></script>
<script src="{% static 'js/bootstrap.min.js' %}"></script>
<script src="{% static 'js/sweetalert.min.js' %}"></script>
<link rel="stylesheet" href="{% static 'css/bootstrap.min.css' %}">
<link rel="shortcut icon" href="{% static "img/favicon.ico" %}">

模板语法:
{ {}}用于取值,{% %}用于逻辑

启动django

  1. manage.py是入口,执行了execute_from_command_line(sys.argv)

  2. 进入management_init_.py,执行了:

    cmd : python manage.py runserver 8000
    argv: runserver 8000
    
    def execute_from_command_line(argv=None):
        """Run a ManagementUtility."""
        utility = ManagementUtility(argv)
        utility.execute()
    
  3. execute()方法中校验参数正误,并且执行:self.fetch_command("runserver").run_from_argv(self.argv)

  4. fetch_command(“runserver”)找到了对应的runserver模块的命令:

  5. 执行CommandObj.run_from_argv(self.argv),再跳到self.execute(*args, **cmd_options),再获取到output = self.handle(*args, **options)这个handler是runserver.handle()

  6. 检查ip等信息后,执行self.run(**options),最终执行了好几个类的run方法后到了最后一个run:

def run(addr, port, wsgi_handler, ipv6=False, threading=False, server_cls=WSGIServer):
    server_address = (addr, port)
    if threading:
        httpd_cls = type('WSGIServer', (socketserver.ThreadingMixIn, server_cls), {})
    else:
        httpd_cls = server_cls
        
    # 它是WSGI服务器与django之间相互通信的唯一枢纽通道,当WSGI服务对象收到socket请求后,会将这个请求传递给django
    httpd = httpd_cls(server_address, WSGIRequestHandler, ipv6=ipv6)
    if threading:
        # ThreadingMixIn.daemon_threads indicates how threads will behave on an
        # abrupt shutdown; like quitting the server by the user or restarting
        # by the auto-reloader. True means the server will not wait for thread
        # termination before it quits. This will make auto-reloader faster
        # and will prevent the need to kill the server manually if a thread
        # isn't terminating correctly.
        httpd.daemon_threads = True
    httpd.set_app(wsgi_handler)
    # 启动非堵塞网络监听服务
    httpd.serve_forever()

路由

# urls.py 
from django.conf.urls import url

urlpatterns = [
     url(regex, view, kwargs=None, name=None), 
     # 一个注册接口
     url(r'^register/', account.register, name='register'),
]
#函数url关键参数介绍
# regex:正则表达式,用来匹配url地址的路径部分,
# view:通常为一个视图函数,用来处理业务逻辑
# kwargs:有名分组
# name:反向解析
  1. django的路由匹配是自上而下的迭代,匹配到一个规则就不再向下,而gin框架则是利用前缀树,时间复杂度更小。

  2. 因此,django的首页配置要放到最后。

    	...
        # 充值页面
        url('^magic/', order.pay, name='pay'),
        url('^order/', order.order, name='order'),
        url('^result/', order.pay_result, name='result'),
    
        url(r'^', home.index, name='index'),
    ]
    
  3. 路由以/结尾,不输入/也能匹配到的原因:在配置文件settings.py中有一个参数APPEND_SLASH,该参数有两个值True或False,当APPEND_SLASH=True(如果配置文件中没有该配置,APPEND_SLASH的默认值为True),并且用户请求的url地址的路径部分不是以 / 结尾,Django会拿着路径部分(即index)去路由表中匹配正则表达式,发现匹配不成功,那么Django会在路径后加 /再去路由表中匹配,如果匹配失败则会返回路径未找到,如果匹配成功,则会返回重定向信息给浏览器。django的全局配置在(from django.conf import settings)

分组

适用于:http://127.0.0.1:8000/article/id/,id为某些记录的主键

我还是喜欢用http://127.0.0.1:8000/article?id=id这种restfulapi的形式QAQ

注意:分组是正则表达式的功能。

无名分组

# 路由
url(r'^aritcle/(\d+)/$',views.article),

# 视图函数需要配套接收,匹配成功会调用article(request,id)
def article(request,article_id):
    return HttpResponse('id为 %s 的文章内容...' %article_id)

有名分组

url(r'^aritcle/(?P<article_id>\d+)/$',views.article)

# 视图函数需要配套接收,匹配成功会调用article(request,article_id=id)
def article(request,article_id):
    return HttpResponse('id为 %s 的文章内容...' %article_id)

路由分发

  1. 每个功能模块应该有自己的urls.py文件。
  2. urls过多会导致路由匹配速度下降。
from django.conf.urls import url,include
from django.contrib import admin
# 总路由表
urlpatterns = [
    url(r'^admin/', admin.site.urls),
    
    # 新增两条路由,注意不能以$结尾。
    # include函数就是做分发操作的。
    # 总路由里面写前缀即可。
    # app01.urls是因为app01已经在settings中注册了,否则无法获取app01/urls.py
    url(r'^app01/', include('app01.urls')),
    url(r'^app02/', include('app02.urls')),
]
或
from app01 import urls as app01_urls
from app02 import urls as app02_urls
url(r'^app01/', include(app01_urls)),
url(r'^app02/', include(app02_urls)),

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',

    'user',  # apps目录已经被加到环境变量了,直接能找到user
    'home',
    'course',
    'order',
]
urlpatterns = [
    path('xadmin/', xadmin.site.urls),
    re_path('media/(?P<path>.*)', serve, {'document_root': settings.MEDIA_ROOT}),
    path('home/', include('home.urls')),
    path('user/', include('user.urls')),
    path('course/', include('course.urls')),
    path('order/', include('order.urls'))
]

反向解析

url(r'^register/', account.register, name='register'),

编写一条url(regex, view, kwargs=None, name=None)时,可以通过参数name为url地址的路径部分起一个别名,项目中就可以通过别名来获取这个路径

from django.shortcuts import render 
from django.shortcuts import reverse # 用于反向解析
from django.shortcuts import redirect #用于重定向页面
from django.shortcuts import HttpResponse

def login(request):
    if request.method == 'GET':
        # 当为get请求时,返回login.html页面,页面中的{% url 'login_page' %}会被反向解析成路径:/login/
        return render(request, 'login.html')
        
    url = reverse('index_page')  # reverse会将别名'index_page'反向解析成路径:/index/       
    return redirect(url) # 重定向到/index/

# 前端
<a href="{% url 'index' %}"><img src="{% static 'img/head.png' %}" alt="" style="width: 100px"></a>

反向解析结合分组

from django.conf.urls import url
from django.contrib import admin
from app01 import views
urlpatterns = [
    url(r'^admin/', admin.site.urls),
    
    url(r'^aritcle/(\d+)/$',views.article,name='article_page'), # 无名分组
    url(r'^user/(?P<uid>\d+)/$',views.article,name='user_page'), # 有名分组
]
# 1 针对无名分组
在views.py中,反向解析的使用:
url = reverse('article_page',args=(1,)) 
在模版login.html文件中,反向解析的使用
{% url 'article_page' 1 %}

    
# 2 针对有名分组
在views.py中,反向解析的使用:
url = reverse('user_page',kwargs={'uid':1}) 
在模版login.html文件中,反向解析的使用
{% url 'user_page' uid=1 %}

名称空间

如果别名存在重复,那么在反向解析时则会出现覆盖。(推荐给路由起名字时按:name=“模块_功能_view”,一般不会重复)

# 总路由表
urlpatterns = [
    url(r'^admin/', admin.site.urls),
	
    # 传给include功能一个元组,元组的第一个值是路由分发的地址,第二个值则是名称空间起的名字
    url(r'^app01/', include(('app01.urls','app01'))),
    url(r'^app02/', include(('app02.urls','app02'))),
]
# 视图
from django.shortcuts import render
from django.shortcuts import HttpResponse
from django.shortcuts import reverse

def index(request):
    url=reverse('app01:index_page') # 解析的是名称空间app01下的别名'index_page'
    return HttpResponse('app01的index页面,反向解析结果为%s' %url)

1、在视图函数中基于名称空间的反向解析,用法如下
url=reverse('名称空间的名字:待解析的别名')

2、在模版里基于名称空间的反向解析,用法如下
<a href="{% url '名称空间的名字:待解析的别名'%}">哈哈</a>

re_path与path

  1. Django2.0中的re_path与django1.0的url一样。
  2. 在Django2.0中新增了一个path功能,用来解决:数据类型转换问题与正则表达式冗余问题:
urlpatterns = [
    # 问题一:数据类型转换
    # 正则表达式会将请求路径中的年份匹配成功然后以str类型传递函数year_archive,在函数year_archive中如果想以int类型的格式处理年份,则必须进行数据类型转换
    re_path(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive),

    # 问题二:正则表达式冗余
    # 下述三个路由中匹配article_id采用了同样的正则表达式,重复编写了三遍,存在冗余问题,并且极不容易管理,因为一旦article_id规则需要改变,则必须同时修改三处代码
    re_path(r'^article/(?P<article_id>[a-zA-Z0-9]+)/detail/$', views.detail_view),
    re_path(r'^articles/(?P<article_id>[a-zA-Z0-9]+)/edit/$', views.edit_view),
    re_path(r'^articles/(?P<article_id>[a-zA-Z0-9]+)/delete/$', views.delete_view),
]


---------------------------------------------------------

urlpatterns = [
    # 问题一的解决方案:
    path('articles/<int:year>/', views.year_archive), # <int:year>相当于一个有名分组,其中int是django提供的转换器,相当于正则表达式,专门用于匹配数字类型,而year则是我们为有名分组命的名,并且int会将匹配成功的结果转换成整型后按照格式(year=整型值)传给函数year_archive


    # 问题二解决方法:用一个int转换器可以替代多处正则表达式
    path('articles/<int:article_id>/detail/', views.detail_view), 
    path('articles/<int:article_id>/edit/', views.edit_view),
    path('articles/<int:article_id>/delete/', views.delete_view),
]

#1、path与re_path或者1.0中的url的不同之处是,传给path的第一个参数不再是正则表达式,而是一个完全匹配的路径,相同之处是第一个参数中的匹配字符均无需加前导斜杠

#2、使用尖括号(<>)从url中捕获值,相当于有名分组

#3、<>中可以包含一个转化器类型(converter type),比如使用 <int:name> 使用了转换器int。若果没有转化器,将匹配任何字符串,当然也包括了 / 字符

默认支持五种转换器
1. str,匹配除了路径分隔符(/)之外的非空字符串,这是默认的形式
2. int,匹配正整数,包含0。
3. slug,匹配字母、数字以及横杠、下划线组成的字符串。
4. uuid,匹配格式化的uuid,如 075194d3-6885-417e-a8a8-6c931e272f00。
5. path,匹配任何非空字符串,包含了路径分隔符(/)

自定义转换器

模板如下:

class MonthConverter:
	# 正则
	# 属性名必须为regex
    regex='\d{2}'
	
	# to_python用于将参数类型转换后传递到视图函数
    def to_python(self, value):
        return int(value)
	
	# to_url用于url反向引用
    def to_url(self, value):
    	# # 匹配的regex是两个数字,返回的结果也必须是两个数字
        return value 

视图

视图函数,简称视图,属于Django的视图层,默认定义在views.py文件中,是用来处理web请求信息以及返回响应信息的函数,所以研究视图函数只需熟练掌握两个对象即可:请求对象(HttpRequest)和响应对象(HttpResponse)

HttpRequest

常用方法

个人喜欢Form(data=request.POST)直接导入form,方便校验

一.HttpRequest.method
获取请求使用的方法(值为纯大写的字符串格式)。例如:"GET"、"POST"

二.HttpRequest.GET
值为一个类似于字典的QueryDict对象,封装了GET请求的所有参数,可通过HttpRequest.GET.get('键')获取相对应的值
  
三.HttpRequest.POST
值为一个类似于字典的QueryDict对象,封装了POST请求所包含的表单数据,可通过HttpRequest.POST.get('键')获取相对应的值
   
针对表单中checkbox类型的input标签、select标签提交的数据,键对应的值为多个,需要用:HttpRequest.POST.getlist("hobbies")获取存有多个值的列表,同理也有HttpRequest.GET.getlist("键")

四.HttpRequest.body
ajax可以提交的数据格式有:1、编码格式1 2、编码格式2 3、json,当ajax采用POST方法提交前两种格式的数据时,django的处理方案同上,但是当ajax采用POST方法提交json格式的数据时,django会将接收到的数据存放于HttpRequest.body,此时需要我们自己对HttpRequest.body属性值做反序列化操作。

五.HttpRequest.FILES
HttpRequest.FILES 只有在请求的方法为POST 且提交的<form> 带有enctype="multipart/form-data" 的情况下才会包含数据。否则,FILES 将为一个空的类似于字典的对象。该属性值为一个类似于字典的对象,可以包含多组key:value(对应多个上传的文件)class MultiValueDict(dict). [filename1:[obj1,obj2]]
上传文件:
with open(filePath,"wb") as writer:
	for line in request.FILES.get("filename"):
		writer.write(line)


六.HttpRequest.path
获取url地址的路径部分,只包含路径部分

七.HttpRequest.get_full_path()
获取url地址的完整path,既包含路径又包含参数部分

http://127.0.0.1:8001/order/?name=egon&age=10
HttpRequest.path的值为"/order/"
HttpRequest.get_full_path()的值为"/order/?name=egon&age=10"

八.HttpRequest.META
值为包含了HTTP协议的请求头数据的Python字典,字典中的key及期对应值的解释如下
CONTENT_LENGTH —— 请求的正文的长度(是一个字符串)。
CONTENT_TYPE —— 请求的正文的MIME类型。
HTTP_ACCEPT —— 响应可接收的Content-Type。
HTTP_ACCEPT_ENCODING —— 响应可接收的编码。
HTTP_ACCEPT_LANGUAGE —— 响应可接收的语言。
HTTP_HOST —— 客服端发送数据的目标主机与端口
HTTP_REFERER —— Referring 页面。
HTTP_USER_AGENT —— 客户端使用的软件版本信息
QUERY_STRING —— 单个字符串形式的查询字符串(未解析过的形式)。
REMOTE_ADDR —— 客户端的IP地址。
REMOTE_HOST —— 客户端的主机名。
REMOTE_USER —— 服务器认证后的用户。
REQUEST_METHOD —— 一个字符串,例如"GET" 或"POST"。
SERVER_NAME —— 服务器的主机名。
SERVER_PORT —— 服务器的端口(是一个字符串)。
从上面可以看到,除 CONTENT_LENGTH 和 CONTENT_TYPE 之外,HTTP协议的请求头数据转换为 META 的键时,
都会
1、将所有字母大写
2、将单词的连接符替换为下划线
3、加上前缀HTTP_。
所以,一个叫做 X-Bender 的头部将转换成 META 中的 HTTP_X_BENDER 键。
   
九.HttpRequest.COOKIES
一个标准的Python字典,包含所有的cookie。键和值都为字符串。

十.HttpRequest.session
一个既可读又可写的类似于字典的对象,表示当前的会话。只有当Django 启用会话的支持时才可用。

十一.HttpRequest.user(用户认证组件下使用)
一个 AUTH_USER_MODEL 类型的对象,表示当前登录的用户,也可以自定义为某User对象。

十二.HttpRequest.is_ajax()
如果请求是通过XMLHttpRequest 发起的,则返回True,方法是检查 HTTP_X_REQUESTED_WITH 相应的首部是否是字符串'XMLHttpRequest'。

HttpResponse

响应可以是任何形式的内容,比如一个HTML文件的内容,一个重定向,一个404错误,一个XML文档,或者一张图片等。


快捷方式:

# 前后端不分离
from django.shortcuts import HttpResponse,render,redirect
# json
from django.http import JsonResponse

# 实际上返回文件也不应该用最基本的HttpResonse()
# def __init__(self, content=b'', *args, **kwargs):
# 参数传入centent就行
return HttpResponse(png)

return render(request, 'register.html', locals(), status=200)
def render(request, template_name, context=None, content_type=None, status=None, using=None):
	"""
	1. request:request对象
	2. template_name:模板名字(利用配置文件里的templates去反射文件)
	3. context: 本次请求上下文数据,可以传入locals()自动将数据导入
	4. content_type: http协议的Content-Type
	5. status: http协议的状态码
	6. using: ...
	"""
    content = loader.render_to_string(template_name, context, request, using=using)
    return HttpResponse(content, content_type, status)

return redirect('/some/url/')
def redirect(to, *args, permanent=False, **kwargs):
    """
    1. to: 目标url
    2. permanent: 是否永久,决定状态码是301还是302
	301表示旧地址A的资源已经被永久地移除了,即这个资源不可访问了。搜索引擎在抓取新内容的同时也将旧的网址转换为重定向之后的地址;
  302表示旧地址A的资源还在,即这个资源仍然可以访问,这个重定向只是临时地从旧地址A跳转到地址B,搜索引擎会抓取新的内容、并且会保存旧的网址。 从SEO层面考虑,302要好于301.
    """
    redirect_class = HttpResponsePermanentRedirect if permanent else HttpResponseRedirect
    return redirect_class(resolve_url(to, *args, **kwargs))

Json

from django.http import JsonResponse

def my_view(request):
    data=['fuck','off']
    return JsonResponse(data,safe=False)
    # 默认safe=True代表只能序列化字典对象,safe=False代表可以序列化字典以外的对象
    def __init__(self, data, encoder=DjangoJSONEncoder, safe=True,
                 json_dumps_params=None, **kwargs):
        if safe and not isinstance(data, dict):
            raise TypeError(
                'In order to allow non-dict objects to be serialized set the '
                'safe parameter to False.'
            )
        if json_dumps_params is None:
            json_dumps_params = {}
        kwargs.setdefault('content_type', 'application/json')
        data = json.dumps(data, cls=encoder, **json_dumps_params)
        super().__init__(content=data, **kwargs)

json_dumps_params可以传入一些指定参数,如:防止将中文字符串转为ascii,ensure_ascii:False
传入的json_dumps_params会被django内置的json序列化方法捕获到使用。

FBV和CBV

django的视图层由两种形式构成:FBV和CBV

1、FBV基于函数的视图(Function base view),我们之前一直介绍的都是FBV

2、CBV基于类的视图(Class base view)

其实CBV有FBV并无太大差异,视图类在经过as_view()和dispatch()后就变成了视图函数。

from django.urls import path,re_path
from app01 import views

urlpatterns = [
	# 必须调用类下的方法as_view
    re_path(r'^login/',views.LoginView.as_view()),
]

from django.shortcuts import render,HttpResponse,redirect
from django.views import View

class LoginView(View):

	# 菜鸟如我一般不用重写
    def dispatch(self, request, *args, **kwargs): 
    	# 可在该方法内做一些预处理操作
    	# 必须继承父类的dispatch功能
        # 当请求url为:http://127.0.0.1:8008/login/会先触发dispatch的执行
        # 如果http协议的请求方法为GET,则调用下述get方法
        # 如果http协议的请求方法为POST,则调用下述post方法
        return super().dispatch(request, *args, **kwargs)
	
	# 会被反射request.method取到
    def get(self,request):
        return render(request,'login.html')
	
	# # 会被反射request.method取到
    def post(self,request):
        return HttpResponse(res)

原生的as_view()和dispatch()方法比较简单:

def dispatch(self, request, *args, **kwargs):
    if request.method.lower() in ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']:	
    	# 反射获取视图函数
        handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
    else:
    	# 默认method_not_allowed 403 视图函数
        handler = self.http_method_not_allowed
    return handler(request, *args, **kwargs)

@classonlymethod
def as_view(cls, **initkwargs):

	# 略过
    for key in initkwargs:
        if key in cls.http_method_names:
            raise TypeError("You tried to pass in the %s method name as a "
                            "keyword argument to %s(). Don't do that."
                            % (key, cls.__name__))
        if not hasattr(cls, key):
            raise TypeError("%s() received an invalid keyword %r. as_view "
                            "only accepts arguments that are already "
                            "attributes of the class." % (cls.__name__, key))
	
	# 核心
    def view(request, *args, **kwargs):
    	# self = 视图类()
        self = cls(**initkwargs)
        if hasattr(self, 'get') and not hasattr(self, 'head'):
            self.head = self.get
            
        # 赋值request和参数,可以通过self.request获取request对象了
        self.setup(request, *args, **kwargs)
        # 刚赋值,怎么可能取不到呢?除非你手贱重写了setup()
        if not hasattr(self, 'request'):
       		# 询问你是否手贱重写了setup()
            raise AttributeError(
                "%s instance has no 'request' attribute. Did you override "
                "setup() and forget to call super()?" % cls.__name__
            )
        # 调用dispatch()    
        return self.dispatch(request, *args, **kwargs)
    
    # 给view赋值,伪装
    view.view_class = cls
    view.view_initkwargs = initkwargs

    # take name and docstring from class
    update_wrapper(view, cls, updated=())

    # and possible attributes set by decorators
    # like csrf_exempt from dispatch
    update_wrapper(view, cls.dispatch, assigned=())
	
	# 返回装饰器后的view
    return view

模板(前后端分离不用看)

django的模板=HTML代码+模板语法

一、变量:{{ 变量名 }}
	1.1 深度查询:句点符的应用
    1.2 过滤器
二、标签:{% 标签名 %}
三、自定义标签和过滤器
四、模板的导入和继承

变量

return render(request,'test.html',{'msg':msg,'dic':dic,'obj':obj,'li':li}) 
或
#locals()会将函数内定义的名字与值转换为字典中的k与v
return render(request,'test.html',locals()) 
<p>{{ msg }}</p>
<p>{{ dic }}</p>
<p>{{ obj }}</p>
<p>{{ li }}</p>

<!--调用字符串对象的upper方法,注意不要加括号-->
<p>{{ msg.upper }}</p>
<!--取字典中k1对应的值-->
<p>{{ dic.k1 }}</p>
<!--取对象的name属性-->
<p>{{ obj.name }}</p>
<!--取列表的第2个元素,然后变成大写-->
<p>{{ li.1.upper }}</p>
<!--取列表的第3个元素,并取该元素的age属性-->
<p>{{ li.2.age }}</p>

过滤器

可以理解为Linux的管道符,将前面的结果传给后面运算

{{ 变量名|过滤器名:传给过滤器的参数 }}

#1、default
#作用:如果一个变量值是False或者为空,使用default后指定的默认值,否则,使用变量本身的值
{{ value|default:"nothing" }}

#2、length
#作用:返回值的长度。它对字符串、列表、字典等容器类型都起作用
{{ value|length }}

#3、filesizeformat
#作用:将值的格式化为一个"人类可读的"文件尺寸(如13KB、4.1 MB、102bytes等等)
{{ value|filesizeformat }}

#4、date
#作用:将日期按照指定的格式输出,如果value=datetime.datetime.now(),按照格式Y-m-d则输出2019-02-02
{{ value|date:"Y-m-d" }}  

#5、slice
#作用:对输出的字符串进行切片操作,顾头不顾尾
{{ value|slice:"0:2" }} 

#6、truncatechars
#作用:如果字符串字符多于指定的字符数量,那么会被截断。截断的字符串将以可翻译的省略号序列(“...”)结尾,注意8个字符也包含末尾的3个点
{{ value|truncatechars:8 }}

#7、truncatewords
#作用:同truncatechars,但truncatewords是按照单词截断,注意末尾的3个点不算作单词,
{{ value|truncatewords:2 }}

#8、safe
#作用:出于安全考虑,Django的模板会对HTML标签、JS等语法标签进行自动转义,例如value="<script>alert(123)</script>",模板变量{{ value }}会被渲染成<script>alert(123)</script>交给浏览器后会被解析成普通字符”<script>alert(123)</script>“,失去了js代码的语法意义,但如果我们就想让模板变量{{ value }}被渲染的结果有语法意义,那么就用到了过滤器safe,比如value='<a href="https://www.baidu.com">点我啊</a>',在被safe过滤器处理后就成为了真正的超链接,不加safe过滤器则会当做普通字符显示’<a href="https://www.baidu.com">点我啊</a>‘
{{ value|safe }}
过滤器描述示例
upper以大写方式输出{ { user.name | upper }}
add给value加上一个数值{ { user.age | add:”5” }}
addslashes单引号加上转义号{ { val | addslashes }}
capfirst第一个字母大写{ { ‘good’| capfirst }} 返回”Good”
center输出指定长度的字符串,把变量居中{ { “abcd”| center:”50” }}
cut删除指定字符串{ { “You are not a Englishman” | cut:”not” }}
default_if_none如果值为None, 则使用默认值代替{ { val | default_if_none:”default” }}
dictsort按某字段排序,变量必须是一个dictionary{% for moment in moments | dictsort:”id” %}
dictsortreversed按某字段倒序排序,变量必须是dictionary{% for moment in moments | dictsortreversed:”id” %}
divisibleby判断是否可以被数字整除{ { 224 | divisibleby:10}}
escape按HTML转义,比如将”<”转换为”&lt”{ { html | escape }}
first返回列表的第1个元素,变量必须是一个列表{ { list | first }}
floatformat转换为指定精度的小数,默认保留1位小数{ { 3.1415926 | floatformat:3 }} 返回 3.142 四舍五入
get_digit从个位数开始截取指定位置的数字{ { 123456 | get_digit:’1’}}
join用指定分隔符连接列表{ { [‘abc’,’45’] | join:’’ }} 返回 abc45
length_is检查列表,字符串长度是否符合指定的值{ { ‘hello’| length_is:’3’ }}
linebreaks用p标签或br标签包裹变量{ { “Hi\n\nDavid”|linebreaks }} 返回

Hi

David

linebreaksbr用br标签代替换行符{ { hi\r\n\r\n | linebreaksbr }}
linenumbers为变量中的每一行加上行号{ { val | linenumbers }}
ljust输出指定长度的字符串,变量左对齐{ {‘ab’|ljust:5}}返回 ‘ab ’
lower字符串变小写{ { “AB” | lower }}
make_list将字符串转换为列表{ { “150” | make_list }}
random返回列表的随机一项{ { list | random }}
removetags删除字符串中指定的HTML标记{ {value | removetags: “h1 h2”}}
rjust输出指定长度的字符串,变量右对齐{ {‘ab’|rjust:5}}返回 ‘ab ’
slugify在字符串中留下减号和下划线,其它符号删除,空格用减号替换{ { ‘5-2=3and5 2=3’ }}
time返回日期的时间部分{ { time_str | time }}
truncatewords_html保留其中的HTML标签{ { ‘p This is a pen p’ | truncatewords_html: 5 }}
urlencode将字符串中的特殊字符转换为url兼容表达方式{ { ‘http://www.aaa.com/foo?a=b&b=c’ | urlencode}}
urlize将变量字符串中的url由纯文本变为链接{ { url | urlize }}
yesno将布尔变量转换为字符串yes, no 或maybe{ { True | yesno }}
## 标签 标签是为了在模板中完成一些特殊功能,语法为{% 标签名 %},一些标签还需要搭配结束标签 {% endtag %}

循环

#1、遍历每一个元素:
{% for person in person_list %}
    <p>{{ person.name }}</p>
{% endfor %}

#2、可以利用{% for obj in list reversed %}反向循环。

#3、遍历一个字典:
{% for key,val in dic.items %}
    <p>{{ key }}:{{ val }}</p>
{% endfor %}

#4、循环序号可以通过{{ forloop }}显示 
{% for name in names %}
    <p>{{ forloop.counter0 }} {{ name }}</p>
{% endfor %}
forloop.counter            当前循环的索引值(从1开始)
forloop.counter0           当前循环的索引值(从0开始)
forloop.revcounter         当前循环的倒序索引值(从1开始)
forloop.revcounter0        当前循环的倒序索引值(从0开始)
forloop.first              当前循环是第一次循环则返回True,否则返回False
forloop.last               当前循环是最后一次循环则返回True,否则返回False
forloop.parentloop         本层循环的外层循环

#5、for标签可以带有一个可选的{% empty %} 从句,在变量person_list为空或者没有被找到时,则执行empty子句
{% for person in person_list %}
    <p>{{ person.name }}</p>
{% empty %}
    <p>sorry,no person here</p>
{% endfor %}

分支

# 1、注意:
{% if 条件 %}条件为真时if的子句才会生效,条件也可以是一个变量,if会对变量进行求值,在变量值为空、或者视图没有为其传值的情况下均为False

# 2、具体语法
{% if num > 100 or num < 0 %}
    <p>无效</p>
{% elif num > 80 and num < 100 %}
    <p>优秀</p>
{% else %}
    <p>凑活吧</p>
{% endif %}

#3、if语句支持 and 、or、==、>、<、!=、<=、>=、in、not in、is、is not判断。

csrf

前后端不分离需要在表单加入{% csrf_token %}

<form>
{% csrf_token %}
</form>

# 1、在GET请求到form表单时,标签{% csrf_token%}会被渲染成一个隐藏的input标签,该标签包含了由服务端生成的一串随机字符串,如<input type="hidden" name="csrfmiddlewaretoken" value="dmje28mFo...OvnZ5">
# 2、在使用form表单提交POST请求时,会提交上述随机字符串,服务端在接收到该POST请求时会对比该随机字符串,对比成功则处理该POST请求,否则拒绝,以此来确定客户端的身份

with

# with标签用来为一个复杂的变量名起别名,如果变量的值来自于数据库,在起别名后只需要使用别名即可,无需每次都向数据库发送请求来重新获取变量的值
{% with li.1.upper as v %}
    {{ v }}
{% endwith %}

自定义过滤器和标签

  1. 将app注册到settings.py
  2. 在文件夹app中创建子文件夹templatetags(文件夹名只能是templatetags)
  3. 创建Py 文件,自定义过滤器和标签
from django import template
# 注意变量名必须为register,不可改变
register = template.Library() 

#1、自定义过滤器
@register.filter
# 自定义的过滤器只能定义最多两个参数,针对{{ value1 | filter_multi:value2 }}
# 参数传递为v1=value1,v2=value2
def my_multi_filter(v1 ,v2): 
    return  v1 * v2

#2、自定义标签
@register.simple_tag
# # 自定义的标签可以定义多个参数
def my_multi_tag(v1, v2): 
    return v1 * v2

#3、自定义标签扩展之mark_safe
# 注释:用内置的标签safe来让标签内容有语法意义,如果想让自定义标签处理的结果也有语法意义,则不能使用内置标签safe了,需要使用mark_safe,可以实现与内置标签safe同样的功能
from django.utils.safestring import mark_safe
@register.simple_tag
def my_input_tag(id, name):
    res = "<input type='text' id='%s' name='%s' />" % (id, name)
    return mark_safe(res)
  1. 使用
<!--必须先加载存有自定义过滤器和标签的文件-->
{% load inclusion_tags %}

# 过滤器
<!--salary的值为10,经过滤器my_multi_filter的处理结果为120-->
{{ salary|my_multi_filter:12 }}

# 标签
<!--结果为2-->
{% my_multi_tag 1 2 %}

# 标签
结果为一个input标签,该表的属性id="inp1" name="username"
注意:input的属性值均为字符串类型,所以my_input_tag后的两个值均为字符串类型
{% my_input_tag "inp1" "username" %} 



# 对比
#1、自定义过滤器只能传两个参数,而自定义标签却可以传多个参数
#2、过滤器可以用于if判断,而标签不能
{% if salary|my_multi_filter:12 > 200 %}
    <p>优秀</p>
{% else %}
    <p>垃圾</p>
{% endif %}

模板的导入和继承

模板文件彼此之间可能会有大量冗余代码,为此django提供了专门的语法来解决这个问题,主要围绕三种标签的使用:include标签、extends标签、block标签。

#作用:在一个模板文件中,引入/重用另外一个模板文件的内容,
{% include '模版名称' %}

<div class="row">
    <div class="col-md-3">
        <!--在base.html引入advertise.html文件的内容-->
        {% include "advertise.html" %}
    </div>
    <div class="col-md-9"></div>
</div>

模板的继承\派生之extends标签、block标签

#作用:在一个模板文件中,引入/重用另外一个模板文件的内容
# include有的功能extends全都有
# extends可以搭配一个block标签,用于在继承的基础上定制新的内容
{% extends "模版名称" %}

# 定义base.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>{% block tilte %}{% endblock %}</title>
    {% load static %}
    <script src="{% static 'js/jQuery3.6.0.js' %}"></script>
    <script src="{% static 'js/bootstrap.min.js' %}"></script>
    <script src="{% static 'js/sweetalert.min.js' %}"></script>
    <link rel="stylesheet" href="{% static 'css/bootstrap.min.css' %}">
    <link rel="shortcut icon" href="{% static "img/favicon.ico" %}" />
    <style>
        .navbar-default{
            border-radius: 0;
        }
        .account{
            width: 400px;
            margin-top: 30px;
            margin-left: auto;
            margin-right: auto;
            border: 1px solid #f0f0f0;
            padding: 10px 30px 30px 30px;
            -webkit-box-shadow: 5px 10px 10px rgba(0,0,0,.05);
            box-shadow: 5px 10px 10px rgba(0,0,0,.05);
        }
        .account .title{
            font-size: 25px;
            font-weight: bold;
            text-align: center;
        }
        .account .form-group{
            margin-bottom: 20px;
        }
    </style>
    {% block css %}{% endblock %}
</head>
<body>
    <nav class="navbar navbar-default">
      <div class="container">
        <!-- Brand and toggle get grouped for better mobile display -->
        <div class="navbar-header">
          <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
            <span class="sr-only">Toggle navigation</span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
          </button>
          <a class="navbar-brand" href="#" style="padding-top: 0">
              <a href="{% url 'index' %}"><img src="{% static 'img/head.png' %}" alt="" style="width: 100px"></a>
          </a>
        </div>

        <!-- Collect the nav links, forms, and other content for toggling -->
        <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
          <ul class="nav navbar-nav">
            <li><a href="#">产品功能</a></li>
            <li><a href="#">企业方案</a></li>
              <li><a href="#">帮助文档</a></li>
              <li><a href="{% url 'pay' %}">价格</a></li>
          </ul>

          <ul class="nav navbar-nav navbar-right">
              {% if request.authentication %}
                <li class="dropdown">
              <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">{{ request.authentication.username }} <span class="caret"></span></a>
              <ul class="dropdown-menu">
                <li><a href="{% url 'project_list' %}">管理中心</a></li>
                <li role="separator" class="divider"></li>
                <li><a href="{% url 'logout' %}">退 出</a></li>
              </ul>
            </li>
              {% else %}
                  <li><a href="{% url 'smslogin' %}">登 录</a></li>
                  <li><a href="{% url 'register' %}">注 册</a></li>
              {% endif %}
          </ul>
        </div><!-- /.navbar-collapse -->
      </div><!-- /.container-fluid -->
    </nav>
{% block content %} {% endblock %}

{% block js %}{% endblock %}
</body>
</html>


# 使用
{% extends 'bases/base.html' %}

{% block tilte %}登录{% endblock %}

{% block content %}
<div class="account">
    <h1 class="text-center h1">登录</h1>
    <div>
        <form id="registerForm" method="post" novalidate>
            {% csrf_token %}
            {% for field in form %}
            {% if field.name == 'code' %}
                <div class="form-group">
                    <label for={{ field.auto_id }}>{{ field.label }}</label>
                    <div class="clearfix">
                        <div class="col-md-6" style="padding-left: 0">{{ field }}</div>
                        <div class="col-md-6" style="padding-right: 0"><button id="code" class="btn btn-default" style="width: 150px">点击获取验证码</button></div>
                    </div>
                </div>
            {% else %}
                <div class="form-group">
                    <label for={{ field.auto_id }}>{{ field.label }}</label>
                    {{ field }}
                    <span style="color:red;position: absolute" class="pull-right">{{ field.errors }}</span>
                </div>
            {% endif %}
        {% endfor %}
        </form>
        <button class="btn btn-primary" style="width: 150px" id="submit">登录</button>
                    
                <a href="{% url 'login' %}"> 账号密码登录 </a>
    </div>
</div>
{% endblock %}


{% block js %}
    <script>
        let time=60;
        let $codeBtn=$('#code');
        let $phoneEle=$('#id_phone');
        let $submitBtn=$('#submit');
        let $registerForm=$('#registerForm');
        $codeBtn.click(function ()
            {
            let $phoneNumber=$phoneEle.val();
            $codeBtn.prop('disabled',true)
            let remind=setInterval(function (){
                                $codeBtn.text(time+'秒重新发送')
                                time=time-1;
                                if (time < 1)
                                {
                                    clearInterval(remind)
                                    $codeBtn.text('点击获取验证码').prop('disabled',false)
                                }
                            },1000)
            $.ajax(
                {
                    url:'/sms/',
                    type:'post',
                    data: {
                        'phone': $phoneNumber, 'method':'login','csrfmiddlewaretoken':'{{csrf_token}}'
                        },
                        success: function (response) {
                            if(! response.code)
                            {
                                 alert(response.msg.phone)
                                 $phoneEle.val('')
                            }
                            else{
                                alert('验证码发送成功!')
                            }
                        }
                }
                )
            }
        )

        $submitBtn.click(function (e){
            $.ajax({
                url:'/smslogin/',
                type: 'post',
                data: $registerForm.serialize(),
                success:function (res){
                    if(res.code===1)
                    {
                        location.href=res.url
                    }
                    else
                    {
                        if (res.msg.code)
                        {
                           alert(res.msg.code)
                        }
                        else
                        {
                            alert(res.msg.phone)
                        }
                    }
                }
            })
        })

        $('input').click(function (){
            $(this).next().text('').parent().removeClass('has-error')
        })
    </script>
{% endblock %}


#1、标签extends必须放在首行,base.html中block越多可定制性越强

#2、include仅仅只是完全引用其他模板文件,而extends却可以搭配block在引用的基础上进行扩写

#3、变量{{ block.super }} 可以重用父类的内容,然后在父类基础上增加新内容,而不是完全覆盖

#4、为了提升可读性,我们可以给标签{% endblock %} 起一个名字 。例如:
    {% block content %}
    ...
    {% endblock content %}  
#5、在一个模版中不能出现重名的block标签。

inclusion_tag

  1. 当页面上某一块区域的内容需要在多个页面上展示的使用,并且该区域的内容需要通过传参数才能正常显示,那么可以优先考虑inclusion_tag模块。
  2. 定义inclusion_tag与定义过滤器和标签(inclusion_tag也是tag的一种)相同
# 需要传入一个可以渲染的模板
@register.inclusion_tag('inclusion_tags/menu_list.html')
def all_menu_list(request):
    menu_list = [
        {'title': '概览', 'url': reverse("dashboard", kwargs={'pk': request.project.id})},
        {'title': '问题', 'url': reverse("issues", kwargs={'pk': request.project.id})},
        {'title': '统计', 'url': reverse("statistics", kwargs={'pk': request.project.id})},
        {'title': 'wiki', 'url': reverse("wiki", kwargs={'pk': request.project.id})},
        {'title': '文件', 'url': reverse("file", kwargs={'pk': request.project.id})},
        # {'title': '设置', 'url': reverse("settings", kwargs={'pk': request.project.id})}
    ]
    for item in menu_list:
        current_url = request.path_info  # type:str
        if current_url.startswith(item['url']):
            item['style'] = "color:white"

    return {'menu_list': menu_list}

# 渲染该模板
{% for item in menu_list %}
<li><a href="{{ item.url }}" {% if item.style %}style="{{ item.style }}" {% endif %}>{{ item.title }}</a></li>
{% endfor %}

# 使用
# 加载定义的inclusion_tag.py
{% load inclusion_tag %}

{% if request.project %}
<ul class="nav navbar-nav">
    {% all_menu_list request=request %}
</ul>
{% endif %}

ORM模型层

ORM全称Object Relational Mapping,即对象关系映射,是在pymysq之上又进行了一层封装,对于数据的操作,我们无需再去编写原生sql,取代代之的是基于面向对象的思想去编写类、对象、调用相应的方法等,ORM会将其转换/映射成原生SQL然后交给pymysql执行。

单表操作

创建models.py

from django.db import models

class UserInfo(models.Model):
    username = models.CharField(verbose_name='用户名', max_length=32, unique=True)
    password = models.CharField(verbose_name='密码', max_length=32)
    email = models.EmailField(verbose_name='邮箱', unique=True)
    phone = models.CharField(verbose_name='手机号', unique=True, max_length=11)
    # identity = models.ForeignKey(verbose_name='账户类型', to='PricePolicy', on_delete=models.DO_NOTHING)
    project_num = models.IntegerField(verbose_name='创建的项目数量', default=0)

    def __str__(self):
        return self.username

配置数据库连接

# 可能有人考虑到mysql集群分库分表,mycat可以将mysql集群的ip+port隐藏,对外提供一个service(ip+port),所以此处只需要配一个ip+port就行
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql', # 使用mysql数据库
        'NAME': 'db1',          # 要连接的数据库
        'USER': 'root',         # 链接数据库的用于名
        'PASSWORD': '',         # 链接数据库的用于名                  
        'HOST': '127.0.0.1',    # mysql服务监听的ip  
        'PORT': 3306,           # mysql服务监听的端口  
        'ATOMIC_REQUEST': True, #设置为True代表同一个http请求所对应的所有sql都放在一个事务中执行 
                                #(要么所有都成功,要么所有都失败),这是全局性的配置,如果要对某个
                                #http请求放水(然后自定义事务),可以用non_atomic_requests修饰器 
        'OPTIONS': {
            "init_command": "SET storage_engine=INNODB", #设置创建表的存储引擎为INNODB
        }
    }
}

在链接mysql数据库前,必须事先创建好数据库,下载连接

# 创建数据库
create database db1;

# 下载mysqlclient,配置了镜像就可以下载,否则会下载失败
mysqlclient==2.0.3
pip install mysqlclient

配置APP

其实按照django1.x的版本给2.x注册app也可以识别

# django1.x版本
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'app01',
]

# django2.x版本
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'app01.apps.App01Config', 
]

打印sql-orm

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'handlers': {
        'console':{
            'level':'DEBUG',
            'class':'logging.StreamHandler',
        },
    },
    'loggers': {
        'django.db.backends': {
            'handlers': ['console'],
            'propagate': True,
            'level':'DEBUG',
        },
    }
}

迁移数据库

$ python manage.py makemigrations
$ python manage.py migrate

# 注意:
# 1、makemigrations只是生成一个数据库迁移记录的文件,而migrate才是将更改真正提交到数据库执行
# 2、数据库迁移记录的文件存放于app01下的migrations文件夹里
# 3、使用命令python manage.py showmigrations可以查看没有执行migrate的文件

修改字段

# 一:增加字段
#1.1、在模型类Employee里直接新增字段,强调:对于orm来说,新增的字段必须用default指定默认值
publish = models.CharField(max_length=12,default='人民出版社',null=True)
#1.2、重新执行那两条数据库迁移命令


# 二:删除字段
#2.1 直接注释掉字段
#2.2 重新执行那两条数据库迁移命令

# 三:修改字段
#2.1 将模型类中字段修改
#2.2 重新执行那两条数据库迁移命令

操作记录

新增

# 1、用模型类创建一个对象,一个对象对应数据库表中的一条记录
obj = Employee(name="Egon", gender=0, birth='1997-01-27', department="财务部", salary=100.1)
# 2、调用对象下的save方法,即可以将一条记录插入数据库
obj.save()

# 2、每个模型表下都有一个objects管理器,用于对该表中的记录进行增删改查操作,其中增加操作如下所示
obj = Employee.objects.create(name="Egon", gender=0, birth='1997-01-27', department="财务部", salary=100.1)

# 3、当form继承了forms.ModelForm时,可以调用form.save()返回一个instance对象
if request.method == 'GET':
    form = RegisterForm()
    return render(request, 'register.html', locals(), status=200)
elif request.method == 'POST':
    res = ApiResponse()
    form = RegisterForm(data=request.POST)
    if form.is_valid():
        instance = form.save()

查询

# 1. get(**kwargs)
# 1.1: 有参,参数为筛选条件
# 1.2: 返回值为一个符合筛选条件的记录对象(有且只有一个),如果符合筛选条件的对象超过一个或者没有都会抛出错误。
obj=Employee.objects.get(id=1)
print(obj.name,obj.birth,obj.salary)

# 2、first()
# 2.1:无参
# 2.2:返回查询出的第一个记录对象
obj=Employee.objects.first() # 在表所有记录中取第一个
print(obj.id,obj.name)

# 3、last()
# 3.1: 无参
# 3.2: 返回查询出的最后一个记录对象
obj = Employee.objects.last() # 在表所有记录中取最后一个
print(obj.id, obj.name)

# 4、count():
# 4.1:无参
# 4.2:返回包含记录对象的总数量
res = Employee.objects.count() # 统计表所有记录的个数
print(res)

# 注意:如果直接打印Employee的对象将没有任何有用的提示信息,可以在模型类中定义__str__来进行定制
class Employee(models.Model):
    ......
	# 在原有的基础上新增代码如下
	
    def __str__(self):
        return "<%s:%s>" %(self.id,self.name)


------------------------------------------------------------------------------------
下述方法的返回值均为QuerySet类型的对象,QuerySet对象中包含了查询出的多个记录对象
# 1、filter(**kwargs):
# 1.1:有参,参数为过滤条件
# 1.2:返回值为QuerySet对象,QuerySet对象中包含了符合过滤条件的多个记录对象
queryset_res=Employee.objects.filter(department='技术部')
# print(queryset_res) # 输出: <QuerySet [<Employee: <2:Kevin>>, <Employee: <5:Jack>>, <Employee: <6:Robin>>]>

# 2、exclude(**kwargs)
# 2.1: 有参,参数为过滤条件
# 2.2: 返回值为QuerySet对象,QuerySet对象中包含了不符合过滤条件的多个记录对象
queryset_res=Employee.objects.exclude(department='技术部')

# 3、all()
# 3.1:无参
# 3.2:返回值为QuerySet对象,QuerySet对象中包含了查询出的所有记录对象
queryset_res = Employee.objects.all() # 查询出表中所有的记录对象

# 4、order_by(*field):
# 4.1:有参,参数为排序字段,可以指定多个字段,在字段1相同的情况下,可以按照字段2进行排序,以此类推,默认升序排列,在字段前加横杆代表降序排(如"-id")
# 4.2:返回值为QuerySet对象,QuerySet对象中包含了排序好的记录对象
queryset_res = Employee.objects.order_by("salary","-id") # 先按照salary字段升序排,如果salary相同则按照id字段降序排

# 5、values(*field)
# 5.1:有参,参数为字段名,可以指定多个字段
# 5.2:返回值为QuerySet对象,QuerySet对象中包含的并不是一个个的记录对象,而上多个字典,字典的key即我们传入的字段名
queryset_res = Employee.objects.values('id','name')
print(queryset_res) # 输出:<QuerySet [{'id': 1, 'name': 'Egon'}, {'id': 2, 'name': 'Kevin'}, ......]>
print(queryset_res[0]['name']) # 输出:Egon

# 6、values_list(*field):
# 6.1:有参,参数为字段名,可以指定多个字段
# 6.2:返回值为QuerySet对象,QuerySet对象中包含的并不是一个个的记录对象,而上多个小元组,字典的key即我们传入的字段名
queryset_res = Employee.objects.values_list('id','name')
print(queryset_res) # 输出:<QuerySet [(1, 'Egon'), (2, 'Kevin'),), ......]>
print(queryset_res[0][1]) # 输出:Egon
QuerySet对象:
# 过滤出符合条件的多个记录对象,然后存放到QuerySet对象中
queryset_res=Employee.objects.filter(department='技术部') 
# 按照索引从QuerySet对象中取出第一个记录对象
obj=queryset_res[0]
print(obj.name,obj.birth,obj.salary)
链式处理:
# 简单示范:
res=Employee.objects.filter(gender=1).order_by('-id').values_list('id','name')
print(res) # 输出:<QuerySet [(6, 'Robin'), (5, 'Jack'), (4, 'Tom'), (2, 'Kevin')]>
其他查询API:
# 1、reverse():
# 1.1:无参
# 1.2:对排序的结果取反,返回值为QuerySet对象
queryset_res = Employee.objects.order_by("salary", "-id").reverse()

# 2、exists():
# 2.1:无参
# 2.2:返回值为布尔值,如果QuerySet包含数据,就返回True,否则返回False
res = Employee.objects.filter(id=100).exists()
print(res)  # 输出:False

# 3、distinct():
# 3.1:如果使用的是Mysql数据库,那么distinct()无需传入任何参数
# 3.2:从values或values_list的返回结果中剔除重复的记录对象,返回值为QuerySet对象
res = Employee.objects.filter(name='Egon').values('name', 'salary').distinct()
print(res) # 输出:<QuerySet [{'name': 'Egon', 'salary': Decimal('100.1')}]>

res1 = Employee.objects.filter(name='Egon').values_list('name', 'salary').distinct()
print(res1) # 输出:<QuerySet [('Egon', Decimal('100.1'))]>
双下划线查询
模板:
filter(字段__关键字=值)
如:filter(id_in=[1,2,3])查询id为1,2,3的

关键字:
gt=1 大于1
lt=1 小于1
gte=1 大于等于1
let=1 小于等于1
in=[1] 在[1]内
range=[1,10] 在[1,2,3,4,5,6,7,8,9,10]内
# 模糊查询
contains="坤" 字段中有"坤"的
icontains="坤" 忽略大小写,如上
startwith="坤" 坤字开头的
endwith="坤" 坤字结尾的
month="月份" 月份
year="年份" 年份

F与Q查询

F用于比较,Q用于拼接条件

# 一张书籍表中包含字段:评论数commentNum、收藏数keepNum,要求查询:评论数大于收藏数的书籍
from django.db.models import F
Book.objects.filter(commnetNum__lt=F('keepNum'))

# 查询评论数大于收藏数2倍的书籍
from django.db.models import F
Book.objects.filter(commnetNum__lt=F('keepNum')*2)


# 修改操作也可以使用F函数,比如将每一本书的价格提高30元
ook.objects.all().update(price=F("price")+30) 

# 拼接一个字符串
from django.db.models.functions import Concat
from django.db.models import Value
Employee.objects.filter(nid__lte=3).update(name=Concat(F('name'),Value('_sb')))
# 可以将条件传给类Q来实例化出一个对象,Q的对象可以使用& 和| 操作符组合起来,&等同于and,|等同于or
from django.db.models import Q
Employee.objects.filter(Q(id__gt=5) | Q(name="Egon"))

# Q 对象可以使用~ 操作符取反,相当于NOT
from django.db.models import Q
Employee.objects.filter(~Q(id__gt=5) | Q(name="Egon"))

# 当过滤条件中既有or又有and,则需要混用Q对象与关键字参数,但Q 对象必须位于所有关键字参数的前面
from django.db.models import Q
Employee.objects.filter(Q(id__gt=5) | Q(name="Egon"),salary__lt=100)
聚合
  1. 聚合查询aggregate()是把所有查询出的记录对象整体当做一个组。
  2. 分组是使用特定的条件将元数据进行划分为多个组。
  3. 聚合是对每个分组中的数据执行某些操作,最后将计算结果进行整合。
from django.db.models import Avg, Max, Sum, Min, Max, Count # 导入聚合函数

# 1. 调用objects下的aggregate()方法,会把表中所有记录对象整体当做一组进行聚合
res1=Employee.objects.aggregate(Avg("salary")) # select avg(salary) as salary__avg from app01_employee;
print(res1) # 输出:{'salary__avg': 70.73}

# 2、aggregate()会把QuerySet对象中包含的所有记录对象当成一组进行聚合
res2=Employee.objects.all().aggregate(Avg("salary")) # select avg(salary) as salary__avg from app01_employee;
print(res2) # 输出:{'salary__avg': 70.73}

res3=Employee.objects.filter(id__gt=3).aggregate(Avg("salary")) # select avg(salary) as salary__avg from app01_employee where id > 3;
print(res3) # 输出:{'salary__avg': 71.0}

# aggregate()的返回值为字典类型,字典的key是由”聚合字段的名称___聚合函数的名称”合成的,如`salary__avg`
# 若想定制字典的key名,我们可以指定关键参数
res1=Employee.objects.all().aggregate(avg_sal=Avg('salary')) # select avg(salary) as avg_sal from app01_employee;
print(res1) # 输出:{'avg_sal': 70.73} # 关键字参数名就会被当做字典的key

# 想得到多个聚合结果,那就需要为aggregate传入多个参数
res1=Employee.objects.all().aggregate(nums=Count('id'),avg_sal=Avg('salary'),max_sal=Max('salary')) 
# 相当于SQL:select count(id) as nums,avg(salary) as avg_sal,max(salary) as max_sal from app01_employee;
print(res1) # 输出:{'nums': 10, 'avg_sal': 70.73, 'max_sal': Decimal('200.3')}
分组

分组查询annotate()相当于sql语句中的group by,是在分组后,对每个组进行单独的聚合,需要强调的是,在进行单表查询时,annotate()必须搭配values()使用:values(“分组字段”).annotate(聚合函数)

# 查询每个部门下的员工数
res=Employee.objects.values('department').annotate(num=Count('id')) 
# select department,count(id) as num from app01_employee group by department;
print(res) 
# 输出:<QuerySet [{'department': '财务部', 'num': 2}, {'department': '技术部', 'num': 3}, {'department': '运营部', 'num': 2}]>

跟在annotate前的values方法,是用来指定分组字段,即group by后的字段,而跟在annotate后的values方法,则是用来指定分组后要查询的字段,即select 后跟的字段

res=Employee.objects.values('department').annotate(num=Count('id')).values('num')
# select count(id) as num from app01_employee group by department;
print(res)
# 输出:<QuerySet [{'num': 2}, {'num': 3}, {'num': 2}]>

跟在annotate前的filter方法表示where条件,跟在annotate后的filter方法表示having条件

# 查询男员工数超过2人的部门名
res=Employee.objects.filter(gender=1).values('department').annotate(male_count=Count("id")).filter(male_count__gt=2).values('department')
print(res) # 输出:<QuerySet [{'department': '技术部'}]>
# 解析:
# 1、跟在annotate前的filter(gender=1) 相当于 where gender = 1,先过滤出所有男员工信息
# 2、values('department').annotate(male_count=Count("id")) 相当于group by department,对过滤出的男员工按照部门分组,然后聚合出每个部门内的男员工数赋值给字段male_count
# 3、跟在annotate后的filter(male_count__gt=2) 相当于 having male_count > 2,会过滤出男员工数超过2人的部门
# 4、最后的values('department')代表从最终的结果中只取部门名

更新

# 单条
# 1、获取记录对象
obj=Employee.objects.filter(name='Egon')[0]
# 2、修改记录对象属性的值
obj.name='EGON'
obj.gender=1
# 3、重新保存
obj.save()
----------------------------------
# 多条
queryset_obj=Employee.objects.filter(id__gt=5)
rows=queryset_obj.update(name='EGON',gender=1)

删除

# 单条
obj=Employee.objects.first()
obj.delete()

-----------------
# 多条
queryset_obj=Employee.objects.filter(id__gt=5)
rows=queryset_obj.delete()
# 全部
Employee.objects.all().delete()

多表

表之间存在三种关系:多对一、一对一、多对多

创建模型

  1. 参数to指定模型名,参数to_field指定要关联的那个字段,默认关联id字段
  2. 在创建关联时,针对参数to,如果传入的是字符串(to=“模型名”),则模型类的定义不区分先后顺序,如果传入的是模型名(to=Author),则Author类必须事先定义.
# 一对一
author_detail = models.OneToOneField(to='AuthorDetail',to_field='nid',unique=True,on_delete=models.DO_NOTHING)

# 一对多
# db_constraint=False 外键约束由程序约束,不由mysql维护,可以提高效率
teacher = models.ForeignKey(
"Teacher", on_delete=models.DO_NOTHING, null=True, blank=True, verbose_name="授课老师",db_constraint=False,
)

# 多对多
# 全自动,第三张表只有[id,author_id,book_id],一般不用
authors=models.ManyToManyField(to='Author') 

CRUD操作

一对多

新增

publish是models定义的外键字段,数据库中会变成publish_id,所以新增外键既支持publish也支持publish_id.

# 1. 准备外键对象
publish_obj=Publish.objects.filter(name='北京出版社').first()

# 2. 关联外键
# 方式一:使用publish参数指定关联
book_obj1=Book.objects.create(title='葵花宝典',price=2000,pub_date='1985-3-11',publish=publish_obj)


# 2. 关联外键
# 方式二:使用publish_id参数指定关联
book_obj1=Book.objects.create(title='葵花宝典',price=2000,pub_date='1985-3-11',publish_id=publish_obj.nid)


# 无论方式一还是方式二得到的书籍对象book_obj1
# 都可以调用publish字段来访问关联的那一个出版社对象
# 都可以调用publish_id来访问关联的那一个出版社对象的nid
print(book_obj1.publish,book_obj1.publish_id)
查询

如果确定两张表存在关联关系,那么在选取一个表作为起始后,可以跨表引用来自另外一张中的字段值,这存在正向与反向之分。

# 当以Book为基表时,称之为正向查询
Book(基表)-------正向---------->Publish

# 当以Publish为基表时,称之为反向查询
Book<-------反向----------Publish(基表)

模型类Book与Publish

# 表app01_book
class Book(models.Model):
    publish = models.ForeignKey(to='Publish',to_field='nid',on_delete=models.CASCADE)
# 表app01_publish
class Publish(models.Model):
    pass
# 正向
# 需求:查询葵花宝典的出版社名字
# 1、先取书籍对象
book_obj=Book.objects.filter(title='葵花宝典').first()
# 2、正向查询:根据书籍对象下的关联字段publish取到出版社
print(book_obj.publish.name) # 输出:北京出版社

# 反向
# 需求:查询北京出版社都出版的所有书籍名字
# 1、先取出出版社对象
publish_obj=Publish.objects.filter(name='北京出版社').first()
# 2、反向查询:根据book_set关键字取到所有的书籍
book_objs=publish_obj.book_set.all()
print([book_obj.title for book_obj in book_objs]) # 输出:['葵花宝典', '菊花宝典', '桃花宝典']**

一对一

新增
# 方式一:对象关联
Author.objects.create(name='egon',age=18,author_detail=author_detail_obj)

# 方式二:确定作者详情对象的id,然后通过模型Author通过字段author_id来指定关联关系,
Author.objects.create(name='egon',age=18,author_detail_id=1)
查询

模型类Author与AuthorDetail,外键字段在Author中。

class Author(models.Model):
    author_detail = models.OneToOneField(to='AuthorDetail',to_field='nid',unique=True,on_delete=models.CASCADE)
class AuthorDetail(models.Model):
    nid = models.AutoField(primary_key=True)
    tel = models.CharField(max_length=20)
# 正向
# 1、先取出作者对象
e=Author.objects.filter(name='e').first() 
# 2、正向查询:根据作者对象下的关联字段author_detail取到作者详情
print(e.author_detail.tel)


# 反向查询,按模型名(小写)_set:如author_set
# 1、先取出作者详情对象
tel=AuthorDetail.objects.filter(tel='xxxxxx').first()
# 2、反向查询:根据小写的模型名author取到作者对象
print(tel.author.name) # 输出:egon

多对多

新增

外键字段在书那,书需要知道作者,所以authors字段是在书的models中定义的。

# 1、先获取书籍对象
book_obj1=Book.objects.get(title='葵花宝典')

# 2、然后获取作者对象
ikun=Author.objects.get(name='ikun')

# 3、建立外键关系
book_obj1.authors.add(ikun)

book_obj1.authors.all() # 返回一个存有多个作者的queryset
修改,删除
# 1、book_obj.authors.remove() :将某个特定的对象从被关联对象集合中去除
# 从菊花宝典的作者集合中去掉作者rose
rose = Author.objects.get(name='rose')
book_obj2 = Book.objects.get(title='菊花宝典')
book_obj2.authors.remove(rose)

# 2、book_obj.authors.clear():清空被关联对象集合
# 清空菊花宝典所关联的所有作者
book_obj2 = Book.objects.get(title='菊花宝典')
book_obj2.authors.clear()

# 3、book_obj.authors.set():先清空再重新设置 
# 玉男心经的作者原来为kevin,要求设置为egon、rose
egon=Author.objects.get(name='egon')
rose=Author.objects.get(name='rose')
book_obj5 = Book.objects.get(title='玉男心经')
book_obj5.authors.set([egon,rose]) # 多个作者对象放到列表里
查询

模型类Book与Author

# 表app01_book
class Book(models.Model):
   
    authors=models.ManyToManyField(to='Author') 
    # 变量名为authors,则新表名为app01_book_authors,若变量名为xxx,则新表名为app01_book_xxx
# 表app01_author
class Author(models.Model):
	author_detail = models.OneToOneField(to='AuthorDetail',to_field='nid',unique=True,on_delete=models.CASCADE)
# 正向
# 需求:查询葵花宝典的所有作者
# 1、先取出书籍对象
book_obj=Book.objects.filter(title='葵花宝典').first()
# 2、正向查询:根据书籍对象下的关联字段authors取到所有作者
author_objs=book_obj.authors.all()
print([obj.name for obj in author_objs])

# 反向查询,按模型名(小写)_set:如author_set
# 需求:查询作者rose出版的所有书籍
# 1、先取出作者对象
eg=Author.objects.filter(name='eg').first() 
# 2、反向查询:根据book_set取到作者对象
book_objs=eg.book_set.all()
print([book_obj.title for book_obj in book_objs])

连续跨多张表查询

# 表app01_book
class Book(models.Model):
   
    authors=models.ManyToManyField(to='Author') 
    # 变量名为authors,则新表名为app01_book_authors,若变量名为xxx,则新表名为app01_book_xxx
# 表app01_author
class Author(models.Model):
	author_detail = models.OneToOneField(to='AuthorDetail',to_field='nid',unique=True,on_delete=models.CASCADE)
class AuthorDetail(models.Model):
   	pass
# 需求:查询葵花宝典的作者们的手机号
# 1. 找到书
book_obj=Book.objects.filter(title='葵花宝典').first()
# 2. 正向找到作者
author_objs=book_obj.authors.all()
# 3. 作者表和详情表是一对一,正向点字段即可
print([author_obj.author_detail.tel for author_obj in author_objs]) 

双下划线查询

跨两表

一对一
  1. 模型类Author与AuthorDetail
  2. 正向查询,按关联字段+双下划线:author_detail__xx
  3. 反向查询,按模型名(小写)+双下划线:author__xx
# 正向需求:查询作者xxx的手机号
# 基于双下划线的跨表查询会被django的orm识别为join操作
# 注意values()中的参数是:关联字段名__要取的那张被关联表中的字段
res = Author.objects.filter(name='xxx').values('author_detail__tel').first()
print(res['author_detail__tel'])

# 反向需求:查询手机号为'18611312331'的作者名
# 注意values()中的参数是:小写的模型名__要取的那张被关联表中的字段
res=AuthorDetail.objects.filter(tel='18611312331').values('author__name').first()
print(res) # {'author__name': 'xxx'}

# 1、针对上例中正向查询的需求:查询作者xxx的手机号,如果选取的基表是AuthorDetail,那么就成了反向查询,应该用反向查询的语法
res = AuthorDetail.objects.filter(author__name='egon').values('tel').first()
print(res)  # {'tel': '18611312331'}

# 2、针对上例中反向查询的需求:查询手机号为'18611312331'的作者名,如果选取的基表是Author,那么就成了正向查询,应该用正向查询的语法
res=Author.objects.filter(author_detail__tel='18611312331').values('name').first()
print(res) # {'name': 'egon'}
一对多查询(模型类Book与Publish)
# 正向需求:查询葵花宝典的出版社名字
# 注意values()中的参数是:关联字段名__要取的那张被关联表中的字段
res=Book.objects.filter(title='葵花宝典').values('publish__name').first()
print(res['publish__name']) # {'publish__name': '北京出版社'}

# 反向需求:查询北京出版社都出版的所有书籍名字
# 注意values()中的参数是:小写的模型名__要取的那张被关联表中的字段
res = Publish.objects.filter(name='北京出版社').values('book__title')
print(res)  # <QuerySet [{'book__title': '葵花宝典'}, {'book__title': '菊花宝典'}, {'book__title': '桃花宝典'}]>

# 1、针对上例中正向查询的需求:查询葵花宝典的出版社名字,如果我们选取的基表是Publish,那么就成了反向查询,应该用反向查询的语法
res = Publish.objects.filter(book__title='葵花宝典').values('name').first()
print(res)  # {'name': '北京出版社'}

# 2、针对上例中反向查询的需求:查询北京出版社都出版的所有书籍名字,如果我们选取的基表是Book,那么就成了正向查询,应该用正向查询的语法
res=Book.objects.filter(publish__name='北京出版社').values('title')
print(res) # <QuerySet [{'title': '葵花宝典'}, {'title': '菊花宝典'}, {'title': '桃花宝典'}]>
多对多查询(模型类Book与Author)
# 正向需求:查询葵花宝典的所有作者
# 注意values()中的参数是:关联字段名__要取的那张被关联表中的字段
res=Book.objects.filter(title='葵花宝典').values('authors__name')
print(res) # <QuerySet [{'authors__name': 'egon'}, {'authors__name': 'kevin'}]> 

# 反向需求:查询作者rose出版的所有书籍
# 注意values()中的参数是:小写的模型名__要取的那张被关联表中的字段
res = Author.objects.filter(name='rose').values('book__title')
print(res) # <QuerySet [{'book__title': '玉女心经'}, {'book__title': '九阴真经'}, {'book__title': '玉男心经'}]>

# 1、针对上例中正向查询的需求:查询葵花宝典的所有作者,如果我们选取的基表是authors,那么就成了反向查询,应该用反向查询的语法
res=Author.objects.filter(book__title='葵花宝典').values('name')
print(res) # <QuerySet [{'name': 'egon'}, {'name': 'kevin'}]>

# 2、针对上例中反向查询的需求:查询作者rose出版的所有书籍,如果我们选取的基表是Book,那么就成了正向查询,应该用正向查询的语法
res=Book.objects.filter(authors__name='rose').values('title')
print(res) # <QuerySet [{'title': '玉女心经'}, {'title': '九阴真经'}, {'title': '玉男心经'}]>

跨多表

可以连续接n个双下划线,只需要在每次连双下划线时,确定是正向还是反向即可

# 需求1:查询北京出版社出版过的所有书籍的名字以及作者的姓名、手机号
# 方式一:基表为Publish
res=Publish.objects.filter(name='北京出版社').values_list('book__title','book__authors__name','book__authors__author_detail__tel')
# 方式二:基表为Book
res=Book.objects.filter(publish__name='北京出版社').values_list('title','authors__name','authors__author_detail__tel')
# 循环打印结果均为
for obj in res:
    print(obj)


# 需求2:查询手机号以186开头的作者出版过的所有书籍名称以及出版社名称
# 方式一:基表为AuthorDetail
res=AuthorDetail.objects.filter(tel__startswith='186').values_list('author__book__title','author__book__publish__name')
# 方式二:基表为Book
res=Book.objects.filter(authors__author_detail__tel__startswith='186').values_list('title','publish__name')
# 方式三:基表为Publish
res=Publish.objects.filter(book__authors__author_detail__tel__startswith='186').values_list('book__title','name')
# 循环打印结果均为
for obj in res:
    print(obj)

常用models字段参数

Field

AutoField(Field)
    - int自增列,必须填入参数 primary_key=True

# 当model中如果没有自增列,则自动会创建一个列名为id的列
BigAutoField(AutoField)
    - bigint自增列,必须填入参数 primary_key=True


SmallIntegerField(IntegerField):
    - 小整数 -32768 ~ 32767

PositiveSmallIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField)
    - 正小整数 0 ~ 32767
IntegerField(Field)
    - 整数列(有符号的) -2147483648 ~ 2147483647

PositiveIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField)
    - 正整数 0 ~ 2147483647

BigIntegerField(IntegerField):
    - 长整型(有符号的) -9223372036854775808 ~ 9223372036854775807

BooleanField(Field)
    - 布尔值类型

NullBooleanField(Field):
    - 可以为空的布尔值

CharField(Field)
    - 字符类型
    - 必须提供max_length参数, max_length表示字符长度

TextField(Field)
    - 文本类型

EmailField(CharField):
    - 字符串类型,Django Admin以及ModelForm中提供验证机制

IPAddressField(Field)
    - 字符串类型,Django Admin以及ModelForm中提供验证 IPV4 机制

GenericIPAddressField(Field)
    - 字符串类型,Django Admin以及ModelForm中提供验证 Ipv4和Ipv6
    - 参数:
        protocol,用于指定Ipv4或Ipv6, 'both',"ipv4","ipv6"
        unpack_ipv4, 如果指定为True,则输入::ffff:192.0.2.1时候,可解析为192.0.2.1,开启此功能,需要protocol="both"

URLField(CharField)
    - 字符串类型,Django Admin以及ModelForm中提供验证 URL

SlugField(CharField)
    - 字符串类型,Django Admin以及ModelForm中提供验证支持 字母、数字、下划线、连接符(减号)

CommaSeparatedIntegerField(CharField)
    - 字符串类型,格式必须为逗号分割的数字

UUIDField(Field)
    - 字符串类型,Django Admin以及ModelForm中提供对UUID格式的验证

FilePathField(Field)
    - 字符串,Django Admin以及ModelForm中提供读取文件夹下文件的功能
    - 参数:
            path,                      文件夹路径
            match=None,                正则匹配
            recursive=False,           递归下面的文件夹
            allow_files=True,          允许文件
            allow_folders=False,       允许文件夹

FileField(Field)
    - 字符串,路径保存在数据库,文件上传到指定目录
    - 参数:
        upload_to = ""      上传文件的保存路径
        storage = None      存储组件,默认django.core.files.storage.FileSystemStorage

ImageField(FileField)
    - 字符串,路径保存在数据库,文件上传到指定目录
    - 参数:
        upload_to = ""      上传文件的保存路径
        storage = None      存储组件,默认django.core.files.storage.FileSystemStorage
        width_field=None,   上传图片的高度保存的数据库字段名(字符串)
        height_field=None   上传图片的宽度保存的数据库字段名(字符串)

DateTimeField(DateField)
    - 日期+时间格式 YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ]

DateField(DateTimeCheckMixin, Field)
    - 日期格式      YYYY-MM-DD

TimeField(DateTimeCheckMixin, Field)
    - 时间格式      HH:MM[:ss[.uuuuuu]]

DurationField(Field)
    - 长整数,时间间隔,数据库中按照bigint存储,ORM中获取的值为datetime.timedelta类型

FloatField(Field)
    - 浮点型

DecimalField(Field)
    - 10进制小数
    - 参数:
        max_digits,小数总长度
        decimal_places,小数位长度

BinaryField(Field)
    - 二进制类型

参数

# 配置auto_now_add=True,创建数据记录的时候会把当前时间添加到数据库。
auto_now_add
# 配置上auto_now=True,每次更新数据记录的时候会更新该字段。
auto_now
null                数据库中字段是否可以为空
db_column           数据库中字段的列名
default             数据库中字段的默认值
primary_key         数据库中字段是否为主键
db_index            数据库中字段是否可以建立索引
unique              数据库中字段是否可以建立唯一索引
unique_for_date     数据库中字段【日期】部分是否可以建立唯一索引
unique_for_month    数据库中字段【月】部分是否可以建立唯一索引
unique_for_year     数据库中字段【年】部分是否可以建立唯一索引
verbose_name        Admin中显示的字段名称
blank               Admin中是否允许用户输入为空
editable            Admin中是否可以编辑
help_text           Admin中该字段的提示信息

choices             Admin中显示选择框的内容,用不变动的数据放在内存中从而避免跨表操作
如:gf = models.IntegerField(choices=[(0, '1'),(1, '2'),],default=1)

error_messages      自定义错误信息(字典类型),从而定制想要显示的错误信息;
字典健:null, blank, invalid, invalid_choice, unique, and unique_for_date如:{'null': "不能为空.", 'invalid': '格式错误'}

validators          自定义错误验证(列表类型),从而定制想要的验证规则
                 from django.core.validators import RegexValidator
                 from django.core.validators import EmailValidator,URLValidator,DecimalValidator,\
                 MaxLengthValidator,MinLengthValidator,MaxValueValidator,MinValueValidator
                 如:
                     test = models.CharField(
                         max_length=32,
                         error_messages={
                             'c1': '优先错信息1',
                             'c2': '优先错信息2',
                             'c3': '优先错信息3',
                         },
                         validators=[
                             RegexValidator(regex='root_\d+', message='错误了', code='c1'),
                             RegexValidator(regex='root_112233\d+', message='又错误了', code='c2'),
                             EmailValidator(message='又错误了', code='c3'), ]
                     )

关系字段

ForeignKey:
to
设置要关联的表
to_field
设置要关联的表的字段
related_name
反向操作时,使用的字段名,用于代替原反向查询时的'表名_set'。
related_query_name
反向查询操作时,使用的连接前缀,用于替换表名。
on_delete
  当删除关联表中的数据时,当前表与其关联的行的行为。
  models.CASCADE
  删除关联数据,与之关联也删除
  models.DO_NOTHING
  删除关联数据,引发错误IntegrityError
  models.PROTECT
  删除关联数据,引发错误ProtectedError
  models.SET_NULL
  删除关联数据,与之关联的值设置为null(前提FK字段需要设置为可空)
  models.SET_DEFAULT
  删除关联数据,与之关联的值设置为默认值(前提FK字段需要设置默认值)
  models.SET
  删除关联数据,a. 与之关联的值设置为指定值,设置:models.SET(值)b. 与之关联的值设置为可执行对象的返回值,设置:models.SET(可执行对象)
db_constraint
是否在数据库中创建外键约束,默认为True。

-----------------------------------
OneToOneField:
to
设置要关联的表。
to_field
设置要关联的字段。
on_delete
同ForeignKey字段。

-----------------------------------

ManyToManyField:
to
设置要关联的表
related_name
同ForeignKey字段。
related_query_name
同ForeignKey字段。
symmetrical
仅用于多对多自关联时,指定内部是否创建反向操作的字段。默认为True。
through
在使用ManyToManyField字段时,Django将自动生成一张表来管理多对多的关联关系。
也可以手动创建第三张表来管理多对多关系,此时就需要通过through来指定第三张表的表名。
through_fields
设置关联的字段。
db_table
默认创建第三张表时,数据库中表的名称。

多对多全自动半自动手动

  1. 手动
class Book(models.Model):
    title = models.CharField(max_length=32, verbose_name="书名")
class Author(models.Model):
    name = models.CharField(max_length=32, verbose_name="作者姓名")
class Author2Book(models.Model):
    author = models.ForeignKey(to="Author")
    book = models.ForeignKey(to="Book")

    class Meta:
        unique_together = ("author", "book")
  1. 全自动
class Book(models.Model):
    title = models.CharField(max_length=32, verbose_name="书名")
# 通过ORM自带的ManyToManyField自动创建第三张表
class Author(models.Model):
    name = models.CharField(max_length=32, verbose_name="作者姓名")
    books = models.ManyToManyField(to="Book", related_name="authors")
  1. 半自动
    1. 需要在第三张关系表中存储额外的字段时,就要使用第三种方式。

    2. 当使用第三种方式创建多对多关联关系时,就无法使用set、add、remove、clear方法来管理多对多的关系了,需要通过第三张表的model来管理多对多关系。

class Book(models.Model):
    title = models.CharField(max_length=32, verbose_name="书名")


# 自己创建第三张表,并通过ManyToManyField指定关联
class Author(models.Model):
    name = models.CharField(max_length=32, verbose_name="作者姓名")
    books = models.ManyToManyField(to="Book", through="Author2Book", through_fields=("author", "book"))
    # through_fields接受一个2元组('field1','field2'):
    # 其中field1是定义ManyToManyField的模型外键的名(author),field2是关联目标模型(book)的外键名。


class Author2Book(models.Model):
    author = models.ForeignKey(to="Author")
    book = models.ForeignKey(to="Book")

    class Meta:
        unique_together = ("author", "book")

元信息

ORM对应的类里面包含另一个Meta类,而Meta类封装了一些数据库的信息。

class UserInfo(models.Model):
        nid = models.AutoField(primary_key=True)
        username = models.CharField(max_length=32)

        class Meta:
            # 数据库中生成的表名称 默认 app名称 + 下划线 + 类名
            db_table = "table_name"

            # 联合索引
            index_together = [
                ("pub_date", "deadline"),
            ]

            # 联合唯一索引
            unique_together = (("driver", "restaurant"),)
            
            # 默认排序字段
            ordering = ('name',)
            
            # admin中显示的表名称
            verbose_name='哈哈'

            # verbose_name加s
            verbose_name_plural=verbose_name

自定义字段(一般用不上)

class FixedCharField(models.Field):
    """
    自定义的char类型的字段类
    """
    def __init__(self, max_length, *args, **kwargs):
        self.max_length = max_length
        super(FixedCharField, self).__init__(max_length=max_length, *args, **kwargs)

    def db_type(self, connection):
        """
        限定生成数据库表的字段类型为char,长度为max_length指定的值
        """
        return 'char(%s)' % self.max_length

orm高级操作

QuerySet对象

# 使用Python 的切片语法来限制查询集记录的数目 。它等同于SQL 的LIMIT 和OFFSET 子句,不支持负的索引(例如Entry.objects.all()[-1])
Entry.objects.all()[:5]      # (LIMIT 5)
Entry.objects.all()[5:10]    # (OFFSET 5 LIMIT 5)

# 可迭代
articleList=models.Article.objects.all()
for article in articleList:
    print(article.title)

# 惰性查询
queryResult=models.Article.objects.all() # 不查
print(queryResult) # 查

# 缓存机制
# 将查询结果先保存,会建立缓存
queryResult=models.Article.objects.all()
print([a.title for a in queryResult])
print([a.create_time for a in queryResult])
使用切片或索引来限制查询集将不会填充缓存
当只对查询集的部分进行求值时会检查缓存, 如果这个部分不在缓存中,那么接下来查询返回的记录都将不会被缓存。
queryset = Entry.objects.all()
print queryset[5] # Queries the database
print queryset[5] # Queries the database again
如果已经对全部查询集求值过,则将检查缓存:
queryset = Entry.objects.all()
[entry for entry in queryset] # Queries the database
print queryset[5] # Uses cache
print queryset[5] # Uses cache
简单地打印查询集不会填充缓存

exists()与iterator()方法

# 只需要判断是否存在而不需要数据
if queryResult.exists():
    #SELECT (1) AS "a" FROM "blog_article" LIMIT 1; args=()
        print("exists...")

# 使用迭代器来yield数据
objs = Book.objects.all().iterator()
# iterator()可以一次只从数据库获取少量数据,这样可以节省内存
for obj in objs:
    print(obj.title)

select_related

class Article(models.Model):
    category = models.ForeignKey(verbose_name='文章类型', to='Category', 
    to_field='nid', null=True)
    blog = models.ForeignKey(verbose_name='所属博客', to='Blog', to_field='nid')
    
class ArticleDetail(models.Model):
    """
    文章详细表
    """
    article = models.OneToOneField(verbose_name='所属文章', to='Article', to_field='nid')
    
class Blog(models.Model):
 
    """
    博客信息
    """
    user = models.OneToOneField(to='UserInfo', to_field='nid')
class UserInfo(AbstractUser):
    """
    用户信息
    """
	pass
# 查询文章时同时将文章类型填充进去
articleList=models.Article.objects.select_related("category").all()
 
for article_obj in articleList:
    #不再查询数据库,因为第一次查询,数据已经填充进去了
    print(article_obj.category.title)

# 支持链式操作
article=models.Article.objects
             .select_related("category")
             .select_related("articledetail")
             .get(nid=1)  # django 1.7 支持链式操作
print(article.articledetail)


# 深度查询
article=models.Article.objects.select_related("blog__user").get(nid=1)
print(article.blog.user.username)
  1. select_related主要针一对一和多对一关系进行优化。
  2. select_related使用SQL的JOIN语句进行优化,通过减少SQL查询的次数来进行优化、提高性能。
  3. 可以通过可变长参数指定需要select_related的字段名。也可以通过使用双下划线“__”连接字段名来实现指定的递归查询。
  4. 没有指定的字段不会缓存,没有指定的深度不会缓存,如果要访问的话Django会再次进行SQL查询。
  5. 也可以通过depth参数指定递归的深度,Django会自动缓存指定深度内所有的字段。如果要访问指定深度外的字段,Django会再次进行SQL查询。
  6. 也接受无参数的调用,Django会尽可能深的递归查询所有的字段。但注意有Django递归的限制和性能的浪费。
  7. Django >= 1.7,链式调用的select_related相当于使用可变长参数。Django < 1.7,链式调用会导致前边的select_related失效,只保留最后一个。

prefetch_related()

对于多对多字段(ManyToManyField)和一对多字段,可以使用prefetch_related()来进行优化。

class Article(models.Model):
    tags = models.ManyToManyField(
        to="Tag",
        through='Article2Tag',
        through_fields=('article', 'tag'),
)
 查询所有文章关联的所有标签
article_obj=models.Article.objects.all()
for i in article_obj:
    print(i.tags.all())  #4篇文章: hits database 5
    
# 查询所有文章关联的所有标签
article_obj=models.Article.objects.prefetch_related("tags").all()
for i in article_obj:
    print(i.tags.all())  #4篇文章: hits database 2

extra

扩展查询语句,手动写入sql

extra(select=None, where=None, params=None, 
      tables=None, order_by=None, select_params=None)

queryResult=models.Article.objects.extra(select={'is_recent': "create_time > '2017-09-05'"})

有些情况下,Django的查询语法难以简单的表达复杂的 WHERE 子句,对于这种情况, Django 提供了 extra() QuerySet修改机制 — 它能在 QuerySet生成的SQL从句中注入新子句

extra可以指定一个或多个 参数,例如 select, where or tables. 这些参数都不是必须的,但是你至少要使用一个!要注意这些额外的方式对不同的数据库引擎可能存在移植性问题.(因为你在显式的书写SQL语句),除非万不得已,尽量避免这样做

参数之select

he select 参数可以让你在 SELECT 从句中添加其他字段信息,它应该是一个字典,存放着属性名到 SQL 从句的映射。

queryResult=models.Article.objects.extra(select={'is_recent': "create_time > '2017-09-05'"})

结果集中每个 Entry 对象都有一个额外的属性is_recent, 它是一个布尔值,表示 Article对象的create_time 是否晚于2017-09-05.

article_obj=models.Article.objects
              .filter(nid=1)
              .extra(select={"standard_time":"strftime('%%Y-%%m-%%d',create_time)"})
              .values("standard_time","nid","title")
print(article_obj)
# <QuerySet [{'title': 'MongoDb 入门教程', 'standard_time': '2017-09-03', 'nid': 1}]>

参数之where / tables

您可以使用where定义显式SQL WHERE子句 – 也许执行非显式连接。您可以使用tables手动将表添加到SQL FROM子句。

where和tables都接受字符串列表。所有where参数均为“与”任何其他搜索条件。

举例来讲:

queryResult=models.Article.objects.extra(where=['nid in (1,3) OR title like "py%" ','nid>2'])
extra, 额外查询条件以及相关表,排序
            
models.UserInfo.objects.filter(id__gt=1)
models.UserInfo.objects.all() 
# id name age ut_id


models.UserInfo.objects.extra(self, select=None, where=None, params=None, tables=None, order_by=None, select_params=None)
# a. 映射
    # select 
    # select_params=None
    # select 此处 from 表

# b. 条件
    # where=None
    # params=None,
    # select * from 表 where 此处

# c. 表
    # tables
    # select * from 表,此处
    
# c. 排序
    # order_by=None
    # select * from 表 order by 此处


models.UserInfo.objects.extra(
    select={'newid':'select count(1) from app01_usertype where id>%s'},
    select_params=[1,],
    where = ['age>%s'],
    params=[18,],
    order_by=['-age'],
    tables=['app01_usertype']
)
"""
select 
    app01_userinfo.id,
    (select count(1) from app01_usertype where id>1) as newid
from app01_userinfo,app01_usertype
where 
    app01_userinfo.age > 18
order by 
    app01_userinfo.age desc
"""

result = models.UserInfo.objects.filter(id__gt=1).extra(
    where=['app01_userinfo.id < %s'],
    params=[100,],
    tables=['app01_usertype'],
    order_by=['-app01_userinfo.id'],
    select={'uid':1,'sw':"select count(1) from app01_userinfo"}
)
print(result.query)
# SELECT (1) AS "uid", (select count(1) from app01_userinfo) AS "sw", "app01_userinfo"."id", "app01_userinfo"."name", "app01_userinfo"."age", "app01_userinfo"."ut_id" FROM "app01_userinfo" , "app01_usertype" WHERE ("app01_userinfo"."id" > 1 AND (app01_userinfo.id < 100)) ORDER BY ("app01_userinfo".id) DESC
# 在对象中加入字段
ret=models.Author.objects.all().filter(nid__gt=1).extra(select={'n':'select count(*) from app01_book where nid>%s'},select_params=[1])
print(ret[0].n)
print(ret.query)
# 给字段重命名
ret=models.Author.objects.all().filter(author_detail__telephone=132234556).extra(select={'bb':"app01_authordatail.telephone"}).values('bb')
print(ret)
print(ret.query)

原生sql

from django.db import connection, connections

cursor = connection.cursor() # connection=default数据
cursor = connections['db2'].cursor()

cursor.execute("""SELECT * from auth_user where id = %s""", [1])

row = cursor.fetchone()
row = cursor.fetchall()
ret=models.Author.objects.raw('select * from app01_author where nid>1')
    print(ret)
    for i in ret:
        print(i)
    print(ret.query)
    # 会把book的字段放到author对象中
    ret=models.Author.objects.raw('select * from app01_book where nid>1')
    print(ret)
    for i in ret:
        print(i.price)
        print(type(i))

整体插入

创建对象时,尽可能使用bulk_create()来减少SQL查询的数量。

Entry.objects.bulk_create([
    Entry(headline="Python 3.0 Released"),
    Entry(headline="Python 3.1 Planned")
])

# 多对多
my_band.members.add(me, my_friend)

事务

装饰器用法

from django.db import transaction

@transaction.atomic
def viewfunc(request):
  # 这些代码会在一个事务中执行
  ......

with上下文管理

from django.db import transaction

def viewfunc(request):
  # 这部分代码不在事务中,会被 Django 自动提交
  ......

  with transaction.atomic():
      # 这部分代码会在事务中执行
      ......

回滚

# 创建保存点
save_id = transaction.savepoint()

# 回滚到保存点
transaction.savepoint_rollback(save_id)

# 提交从保存点到当前状态的所有数据库事务操作
transaction.savepoint_commit(save_id)

举例

class OrderCommitView(LoginRequiredJSONMixin, View):
    """订单提交"""

    def post(self, request):
        """保存订单信息和订单商品信息"""
        # 显式的开启一个事务
        with transaction.atomic():
            # 创建事务保存点
            save_id = transaction.savepoint()

            # 暴力回滚
            try:
                # 保存订单基本信息 OrderInfo(一)
                order = OrderInfo.objects.create(
                    ...
                )

                # 从redis读取购物车中被勾选的商品信息
                ...
                # 遍历购物车中被勾选的商品信息
                for ...
                    if ...:
                        # 出错就回滚
                        transaction.savepoint_rollback(save_id)
                        return ...
            except Exception as e:
            	# 出错后记日志+回滚
                logger.error(e)
                transaction.savepoint_rollback(save_id)
                return ...

            # 提交订单成功,显式的提交一次事务
            transaction.savepoint_commit(save_id)
        # 响应提交订单结果
        return ...

defer和only(鸡肋)

学的时候感觉鸡肋,复习第一遍还是决定鸡肋,复习第二遍(现在)依旧觉得鸡肋。

defer('id','name'):取出对象,字段除了id和name都有
only('id','name'):取的对象,只有id和name
如果点,依然能点出其它列,但是不要点了,因为取没有的列,会再次查询数据库

ret=models.Author.objects.only('nid')
    for i in ret:
        # 查询不在的字段,会再次查询数据库,造成数据库压力大
        print(i.name)

缓存机制

  1. 开发调试缓存
  2. 内存缓存
  3. 文件缓存
  4. 数据库缓存
  5. Memcache缓存(使用python-memcached模块)
  6. Memcache缓存(使用pylibmc模块)
# 内存缓存(将缓存内容保存至内存区域中)
CACHES = {
 'default': {
  'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',  # 指定缓存使用的引擎
  'LOCATION': 'unique-snowflake',         # 写在内存中的变量的唯一值 
  'TIMEOUT':300,             # 缓存超时时间(默认为300秒,None表示永不过期)
  'OPTIONS':{
   'MAX_ENTRIES': 300,           # 最大缓存记录的数量(默认300)
   'CULL_FREQUENCY': 3,          # 缓存到达最大个数之后,剔除缓存个数的比例,即:1/CULL_FREQUENCY(默认3)
  }  
 }
}

# 文件缓存(把缓存数据存储在文件中)
CACHES = {
 'default': {
  'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache', #指定缓存使用的引擎
  'LOCATION': '/var/tmp/django_cache',        #指定缓存的路径
  'TIMEOUT':300,              #缓存超时时间(默认为300秒,None表示永不过期)
  'OPTIONS':{
   'MAX_ENTRIES': 300,            # 最大缓存记录的数量(默认300)
   'CULL_FREQUENCY': 3,           # 缓存到达最大个数之后,剔除缓存个数的比例,即:1/CULL_FREQUENCY(默认3)
  }
 }   
}

# redis缓存等,可以多配几个,只需要配到一个CACHES字段中就可以。

Django中的缓存应用

视图函数

from django.views.decorators.cache import cache_page
import time
from .models import *

@cache_page(15)          #超时时间为15秒
def index(request):
  t=time.time()      #获取当前时间
  bookList=Book.objects.all()
  return render(request,"index.html",locals())

全站

缓存整个站点,是最简单的缓存方法

在 MIDDLEWARE_CLASSES 中加入 “update” 和 “fetch” 中间件
MIDDLEWARE_CLASSES = (
    ‘django.middleware.cache.UpdateCacheMiddleware’, #第一
    'django.middleware.common.CommonMiddleware',
    ‘django.middleware.cache.FetchFromCacheMiddleware’, #最后
)
“update” 必须配置在第一个
“fetch” 必须配置在最后一个


MIDDLEWARE_CLASSES = (
    'django.middleware.cache.UpdateCacheMiddleware',   #响应HttpResponse中设置几个headers
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'django.middleware.security.SecurityMiddleware',
    'django.middleware.cache.FetchFromCacheMiddleware',   #用来缓存通过GET和HEAD方法获取的状态码为200的响应

)
CACHE_MIDDLEWARE_SECONDS=10

django序列化

def ser(request):
    #拿到用户表里面的所有的用户对象
    user_list=models.User.objects.all()
    #导入内置序列化模块
    from django.core import serializers
    #调用该模块下的方法,第一个参数是你想以什么样的方式序列化你的数据
    ret=serializers.serialize('json',user_list)
    return HttpResponse(ret)

django分页器

from django.core.paginator import Paginator
Paginator对象:    paginator = Paginator(user_list, 10)
# per_page: 每页显示条目数量
# count:    数据总个数
# num_pages:总页数
# page_range:总页数的索引范围,如: (1,10),(1,200)
# page:     page对象    
page对象:page=paginator.page(1)
# has_next              是否有下一页
# next_page_number      下一页页码
# has_previous          是否有上一页
# previous_page_number  上一页页码
# object_list           分页之后的数据列表
# number                当前页
# paginator             paginator对象

使用

1. 查询
book_list=Book.objects.all()

2. 分页
paginator = Paginator(book_list, 10)
print("count:",paginator.count)           #数据总数
print("num_pages",paginator.num_pages)    #总页数
print("page_range",paginator.page_range)  #页码的列表

3. 获取第1页
page1=paginator.page(1) #第1页的page对象
for i in page1:         #遍历第1页的所有数据对象
    print(i)
print(page1.object_list) #第1页的所有数据

4. 获取第二页
page2=paginator.page(2)

5. bool判断
print(page2.has_next())            #是否有下一页
print(page2.next_page_number())    #下一页的页码
print(page2.has_previous())        #是否有上一页
print(page2.previous_page_number()) #上一页的页码

6. 抛错
page=paginator.page(12)   # error:EmptyPage
page=paginator.page("z")   # error:PageNotAnInteger


7. get取值
book_list=Book.objects.all()
paginator = Paginator(book_list, 10)
page = request.GET.get('page',1)
currentPage=int(page)


8. 容错
try:
    book_list = paginator.page(page)
except PageNotAnInteger:
    book_list = paginator.page(1)
except EmptyPage:
    book_list = paginator.page(paginator.num_pages)


9. 返回render或序列化后返回json
return render(request,"index.html",{"book_list":book_list,"paginator":paginator,"currentPage":currentPage})

form

校验字段功能

# FORM
class Ret(Form):
    name = forms.CharField(max_length=10, min_length=2, label='用户名',
                           error_messages={'required': '该字段不能为空', 'invalid': '格式错误', 'max_length': '太长',
                                           'min_length': '太短'},
                           widget=widgets.TextInput(attrs={'class':'form-control'}))
    pwd = forms.CharField(max_length=10, min_length=2, widget=widgets.PasswordInput(attrs={'class':'form-control'}))
    email = forms.EmailField(label='邮箱', error_messages={'required': '该字段不能为空', 'invalid': '格式错误'})


# VIEW
def register(request):

    if request.method=="POST":
        form=UserForm(request.POST)
        if form.is_valid():
            print(form.cleaned_data)       # 所有干净的字段以及对应的值
        else:
            print(form.cleaned_data)       #
            print(form.errors)             # ErrorDict : {"校验错误的字段":["错误信息",]}
            print(form.errors.get("name")) # ErrorList ["错误信息",]
            # print(form.errors)        # {"name":[".........."]}
            # print(type(form.errors))  # ErrorDict
            # print(form.errors.get("name"))
            # print(type(form.errors.get("name")))    # ErrorList
            # 全局钩子错误
            print("error",form.errors.get("__all__")[0])
        return HttpResponse("OK")
    form=UserForm()
    return render(request,"register.html",locals())

渲染(前后端分离不用看)

<form action="" method="post">
    {% csrf_token %}
    <div>
        <label for="">用户名</label>
        {{ form.name }}
    </div>
    <div>
        <label for="">密码</label>
        {{ form.pwd }}
    </div>
    <div>
        <label for="">确认密码</label>
        {{ form.r_pwd }}
    </div>
    <div>
        <label for=""> 邮箱</label>
        {{ form.email }}
    </div>
    <input type="submit" class="btn btn-default pull-right">
</form>
----------------------------------------
<form action="" method="post">
{% csrf_token %}

{% for field in form %}
    <div>
        <label for="">{{ field.label }}</label>
        {{ field }}
    </div>
{% endfor %}
<input type="submit" class="btn btn-default pull-right">             
</form>
----------------------------------------
<form action="" method="post">
    {% csrf_token %}
    {{ form.as_p }}
    <input type="submit" class="btn btn-default pull-right">
</form>
-----------------------------------------
<form action="" method="post" novalidate>
    {% csrf_token %}
    {% for field in form %}
        <div>
            <label for="">{{ field.label }}</label>
            {{ field }} <span class="pull-right" style="color: red">{{ field.errors.0 }}</span>
        </div>
    {% endfor %}
    <input type="submit" class="btn btn-default">
</form>

{{ form_obj.as_p }}    # 展示所有的字段
{{ form_obj.user }}   # input框
{{ form_obj.user.label }}   # label标签的中文提示
{{ form_obj.user.id_for_label }}  # input框的id
{{ form_obj.user.errors  }}    # 一个字段的错误信息
{{ form_obj.user.errors.0  }}   # 一个字段的第一个错误信息
{{ form_obj.errors  }}   # 所有字段的错误

局部钩子

格式:clean_字段名(self)

from django.core.exceptions import NON_FIELD_ERRORS, ValidationError
# 必须是clean_XXX某字段
def clean_name(self):
        val=self.cleaned_data.get("name")
        ret=UserInfo.objects.filter(name=val)
        if not ret:
        	# 通过校验   必须返回当前字段的值
            return val
        else:
         	# 不通过校验 抛出异常
            raise ValidationError("该用户已注册!")

全局钩子

格式:clean(self),必须要返回self.cleaned_data.

def clean(self):
        pwd=self.cleaned_data.get('pwd')
        r_pwd=self.cleaned_data.get('r_pwd')

        if pwd and r_pwd:
            if pwd==r_pwd:
            	# 通过校验   必须返回所有字段的值 self.cleaned_data
                return self.cleaned_data
            else:
            	# 不通过校验 抛出异常
                raise ValidationError('两次密码不一致')
        else:
        	# 通过校验   必须返回所有字段的值 self.cleaned_data
            return self.cleaned_data

常用字段与插件

Field
    required=True,               是否允许为空
    widget=None,                 HTML插件
    label=None,                  用于生成Label标签或显示内容
    initial=None,                初始值
    help_text='',                帮助信息(在标签旁边显示)
    error_messages=None,         错误信息 {'required': '不能为空', 'invalid': '格式错误'}
    validators=[],               自定义验证规则
    localize=False,              是否支持本地化
    disabled=False,              是否可以编辑
    label_suffix=None            Label内容后缀
 
 
CharField(Field)
    max_length=None,             最大长度
    min_length=None,             最小长度
    strip=True                   是否移除用户输入空白
 
IntegerField(Field)
    max_value=None,              最大值
    min_value=None,              最小值
 
FloatField(IntegerField)
    ...
 
DecimalField(IntegerField)
    max_value=None,              最大值
    min_value=None,              最小值
    max_digits=None,             总长度
    decimal_places=None,         小数位长度
 
BaseTemporalField(Field)
    input_formats=None          时间格式化   
 
DateField(BaseTemporalField)    格式:2015-09-01
TimeField(BaseTemporalField)    格式:11:12
DateTimeField(BaseTemporalField)格式:2015-09-01 11:12
 
DurationField(Field)            时间间隔:%d %H:%M:%S.%f
    ...
 
RegexField(CharField)
    regex,                      自定制正则表达式
    max_length=None,            最大长度
    min_length=None,            最小长度
    error_message=None,         忽略,错误信息使用 error_messages={'invalid': '...'}
 
EmailField(CharField)      
    ...
 
FileField(Field)
    allow_empty_file=False     是否允许空文件
 
ImageField(FileField)      
    ...
    注:需要PIL模块,pip3 install Pillow
    以上两个字典使用时,需要注意两点:
        - form表单中 enctype="multipart/form-data"
        - view函数中 obj = MyForm(request.POST, request.FILES)
 
URLField(Field)
    ...
 
 
BooleanField(Field)  
    ...
 
NullBooleanField(BooleanField)
    ...
 
ChoiceField(Field)
    ...
    choices=(),                选项,如:choices = ((0,'上海'),(1,'北京'),)
    required=True,             是否必填
    widget=None,               插件,默认select插件
    label=None,                Label内容
    initial=None,              初始值
    help_text='',              帮助提示
 
 
ModelChoiceField(ChoiceField)
    ...                        django.forms.models.ModelChoiceField
    queryset,                  # 查询数据库中的数据 form_model.ModelMultipleChoiceField(queryset=models.NNewType.objects.all()) 
    empty_label="---------",   # 默认空显示内容
    to_field_name=None,        # HTML中value的值对应的字段
    limit_choices_to=None      # ModelForm中对queryset二次筛选
     
ModelMultipleChoiceField(ModelChoiceField)
    ...                        django.forms.models.ModelMultipleChoiceField
 
 
     
TypedChoiceField(ChoiceField)
    coerce = lambda val: val   对选中的值进行一次转换
    empty_value= ''            空值的默认值
 
MultipleChoiceField(ChoiceField)
    ...
 
TypedMultipleChoiceField(MultipleChoiceField)
    coerce = lambda val: val   对选中的每一个值进行一次转换
    empty_value= ''            空值的默认值
 
ComboField(Field)
    fields=()                  使用多个验证,如下:即验证最大长度20,又验证邮箱格式
                               fields.ComboField(fields=[fields.CharField(max_length=20), fields.EmailField(),])
 
MultiValueField(Field)
    PS: 抽象类,子类中可以实现聚合多个字典去匹配一个值,要配合MultiWidget使用
 
SplitDateTimeField(MultiValueField)
    input_date_formats=None,   格式列表:['%Y--%m--%d', '%m%d/%Y', '%m/%d/%y']
    input_time_formats=None    格式列表:['%H:%M:%S', '%H:%M:%S.%f', '%H:%M']
 
FilePathField(ChoiceField)     文件选项,目录下文件显示在页面中
    path,                      文件夹路径
    match=None,                正则匹配
    recursive=False,           递归下面的文件夹
    allow_files=True,          允许文件
    allow_folders=False,       允许文件夹
    required=True,
    widget=None,
    label=None,
    initial=None,
    help_text=''
 
GenericIPAddressField
    protocol='both',           both,ipv4,ipv6支持的IP格式
    unpack_ipv4=False          解析ipv4地址,如果是::ffff:192.0.2.1时候,可解析为192.0.2.1, PS:protocol必须为both才能启用
 
SlugField(CharField)           数字,字母,下划线,减号(连字符)
    ...
 
UUIDField(CharField)           uuid类型

自定义校验

# 自定义验证规则
def mobile_validate(value):
    mobile_re = re.compile(r'^(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]{8}$')
    if not mobile_re.match(value):
        raise ValidationError('手机号码格式错误')  # 必须抛出ValidationError异常

 # 使用自定义验证规则
phone = fields.CharField(validators=[mobile_validate, ], #使用自定义校验规则
                        error_messages={'required': '手机不能为空'},
                        widget=widgets.TextInput(
                            attrs={'class': "form-control", 
                                   'placeholder': u'手机号码'})
                        )

form校验的流程

 1. is_valid()中要执行full_clean():
   1. self._errors ={}    定义一个存放错误信息的字典   
   2. self.cleaned_data = {}   #  定义一个存放有效的数据

# 2. 执行self._clean_fields()
   1. 先执行内置的校验和校验器的校验
   2. 有局部钩子,执行局部钩子

# 3. 执行 self.clean() 全局钩子

ModelForm

与普通的Form表单验证类型类似,ModelForm表单的验证在调用is_valid() 或访问errors 属性时隐式调用。

可以像使用Form类一样自定义局部钩子方法和全局钩子方法来实现自定义的校验规则。

如果不重写具体字段并设置validators属性的化,ModelForm是按照模型中字段的validators来校验的。

class BookForm(forms.ModelForm):
	class Meta:
	        model = models.Book
	        fields = "__all__"
	        labels = {
	            "title": "书名",
	            "price": "价格"
	        }
	        widgets = {
	            "password": forms.widgets.PasswordInput(attrs={"class": "c1"}),
	        }
	        
### MATE参数
model = models.Student  # 对应的Model中的类
fields = "__all__"  # 字段,如果是__all__,就是表示列出所有的字段
exclude = None  # 排除的字段
labels = None  # 提示信息
help_texts = None  # 帮助提示信息
widgets = None  # 自定义插件
error_messages = None  # 自定义错误信息

save()方法

每个ModelForm还具有一个save()方法。 这个方法根据表单绑定的数据创建并保存数据库对象。
ModelForm的子类可以接受现有的模型实例作为关键字参数instance;如果提供此功能,则save()将更新该实例。 如果没有提供,save() 将创建模型的一个新实例:

>>> from myapp.models import Book
>>> from myapp.forms import BookForm

# 根据POST数据创建一个新的form对象
>>> form_obj = BookForm(request.POST)

# 创建书籍对象
>>> new_ book = form_obj.save()

# 基于一个书籍对象创建form对象
>>> edit_obj = Book.objects.get(id=1)
# 使用POST提交的数据更新书籍对象
>>> form_obj = BookForm(request.POST, instance=edit_obj)
>>> form_obj.save()

choice字段

# 单radio值为字符串
gender = forms.fields.ChoiceField(
        choices=((1, "男"), (2, "女"), (3, "保密")),
        label="性别",
        initial=3,
        widget=forms.widgets.RadioSelect()
    )
    
# 单选Select
hobby = forms.ChoiceField(
        choices=((1, "篮球"), (2, "足球"), (3, "双色球"), ),
        label="爱好",
        initial=3,
        widget=forms.widgets.Select()
    )
# 多选Select
hobby = forms.MultipleChoiceField(
        choices=((1, "篮球"), (2, "足球"), (3, "双色球"), ),
        label="爱好",
        initial=[1, 3],
        widget=forms.widgets.SelectMultiple()
    )
# 单选checkbox
keep = forms.ChoiceField(
        label="是否记住密码",
        initial="checked",
        widget=forms.widgets.CheckboxInput()
    )
# 多选checkbox
hobby = forms.MultipleChoiceField(
        choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
        label="爱好",
        initial=[1, 3],
        widget=forms.widgets.CheckboxSelectMultiple()
    )

在使用选择标签时,需要注意choices的选项可以配置从数据库中获取,但是由于是静态字段 获取的值无法实时更新,需要重写构造方法从而实现choice实时更新。

class FInfo(forms.Form):
    authors = form_model.ModelMultipleChoiceField(queryset=models.NNewType.objects.all())  # 多选
    # authors = form_model.ModelChoiceField(queryset=models.NNewType.objects.all())  # 单选

django的cookie和session组件

cookie

cookie的工作原理是:由服务器产生内容,浏览器收到请求后保存在本地;当浏览器再次访问时,浏览器会自动带上Cookie,这样服务器就能通过Cookie的内容来判断这个是“谁”了。

获取

request.COOKIES['key'] # 普通的cookie
request.COOKIES.get('key') # 普通的cookie
request.get_signed_cookie('key', default=RAISE_ERROR, salt='', max_age=None)  #加密的cookie

设置

rep = HttpResponse()
rep.set_cookie(key,value,...)  # 设置普通的cookie
rep.set_signed_cookie(key,value,salt='加密盐',...) # 设置加密后的cookie

set_cookie() 参数说明:
	key, 键
	value='', 值
	max_age=None, 超时时间 cookie需要延续的时间(以秒为单位)如果参数是\ None`` ,这个cookie会延续到浏览器关闭为止
	expires=None, 超时时间(IE requires expires, so set it if hasn't been already.)
	path='/', Cookie生效的路径,/ 表示根路径,特殊的:根路径的cookie可以被任何url的页面访问,浏览器只会把cookie回传给带有该路径的页面,这样可以避免将cookie传给站点中的其他的应用。
	domain=None, Cookie生效的域名 你可用这个参数来构造一个跨站cookie。如, domain=".example.com"所构造的cookie对下面这些站点都是可读的:www.example.com 、 www2.example.com 和an.other.sub.domain.example.com 。如果该参数设置为 None ,cookie只能由设置它的站点读取
	secure=False, 浏览器将通过HTTPS来回传cookie
	httponly=False 只能http协议传输,无法被JavaScript获取(不是绝对,底层抓包可以获取到也可以被覆盖)

删除

def logout(request):
    rep = redirect("/login/")
    rep.delete_cookie("user")  # 删除用户浏览器上之前设置的usercookie值
    return rep

Session

Cookie弥补了HTTP无状态的不足,让服务器知道来的人是“谁”;但是Cookie以文本的形式保存在本地,自身安全性较差;所以我们就通过Cookie识别不同的用户,对应的在Session里保存私密的信息以及超过4096字节的文本。

相关方法

# 获取、设置、删除Session中数据
request.session['k1']
request.session.get('k1',None)
request.session['k1'] = 123
request.session.setdefault('k1',123) # 存在则不设置
del request.session['k1']


# 所有 键、值、键值对
request.session.keys()
request.session.values()
request.session.items()
request.session.iterkeys()
request.session.itervalues()
request.session.iteritems()

# 会话session的key
request.session.session_key

# 将所有Session失效日期小于当前日期的数据删除
request.session.clear_expired()

# 检查会话session的key在数据库中是否存在
request.session.exists("session_key")

# 删除当前会话的所有Session数据(只删数据库)
request.session.delete()
  
# 删除当前的会话数据并删除会话的Cookie(数据库和cookie都删)。
request.session.flush() 
    这用于确保前面的会话数据不可以再次被用户的浏览器访问
    例如,django.contrib.auth.logout() 函数中就会调用它。

# 设置会话Session和Cookie的超时时间
request.session.set_expiry(value)
    * 如果value是个整数,session会在些秒数后失效。
    * 如果value是个datatime或timedelta,session就会在这个时间后失效。
    * 如果value是0,用户关闭浏览器session就会失效。
    * 如果value是None,session会依赖全局session失效策略。

Django中使用session时,做的事:
# 生成随机字符串
# 写浏览器cookie -> session_id: 随机字符串
# 写到服务端session:
    # {
    #     "随机字符串": {'user':'alex'}
    # }

配置

1. 数据库Session
SESSION_ENGINE = 'django.contrib.sessions.backends.db'   # 引擎(默认)

2. 缓存Session
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'  # 引擎
SESSION_CACHE_ALIAS = 'default'                            # 使用的缓存别名(默认内存缓存,也可以是memcache),此处别名依赖缓存的设置

3. 文件Session
SESSION_ENGINE = 'django.contrib.sessions.backends.file'    # 引擎
SESSION_FILE_PATH = None                                    # 缓存文件路径,如果为None,则使用tempfile模块获取一个临时地址tempfile.gettempdir() 

4. 缓存+数据库
SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db'        # 引擎

5. 加密Cookie Session
SESSION_ENGINE = 'django.contrib.sessions.backends.signed_cookies'   # 引擎

其他公用设置项:
SESSION_COOKIE_NAME = "sessionid"                       # Session的cookie保存在浏览器上时的key,即:sessionid=随机字符串(默认)
SESSION_COOKIE_PATH = "/"                               # Session的cookie保存的路径(默认)
SESSION_COOKIE_DOMAIN = None                             # Session的cookie保存的域名(默认)
SESSION_COOKIE_SECURE = False                            # 是否Https传输cookie(默认)
SESSION_COOKIE_HTTPONLY = True                           # 是否Session的cookie只支持http传输(默认)
SESSION_COOKIE_AGE = 1209600                             # Session的cookie失效日期(2周)(默认)
SESSION_EXPIRE_AT_BROWSER_CLOSE = False                  # 是否关闭浏览器使得Session过期(默认)
SESSION_SAVE_EVERY_REQUEST = False                       # 是否每次请求都保存Session,默认修改之后才保存(默认)

中间件

中间件顾名思义,是介于request与response处理之间的一道处理过程,相对比较轻量级,并且在全局上改变django的输入与输出。因为改变的是全局,所以需要谨慎实用,用不好会影响到性能

自定义中间件

当用户发起请求的时候会依次经过所有的的中间件,这个时候的请求时process_request,最后到达views的函数中,views函数处理后,在依次穿过中间件,这个时候是process_response,最后返回给请求者。

process_request(self,request)

process_view(self, request, callback, callback_args, callback_kwargs)

process_template_response(self,request,response)

process_exception(self, request, exception)

process_response(self, request, response)
以上方法的返回值可以是None或一个HttpResponse对象,如果是None,则继续按照django定义的规则向后继续执行,如果是HttpResponse对象,则直接将该对象返回给用户。

process_request(self,request)

def process_request(self, request): 
        '''
         正常流程: 返回值必须为none ,按照 settings配置的中间件的顺序从上到下执行,
            # 若返回 HTTPResponse对象,则直接通过当前中间件的 process_response返回.
​			# 在路由(urls.py)和函数(view)之前进行处理
        :param request:
        :return:
        '''
        print('In M1 process_request ')
​

# 执行时间:
在执行视图函数之前,也在路由匹配之前
# 参数:
​request:  请求对象和视图是同一个
# 执行的顺序:
按照中间件的注册顺序 顺序执行 
# 返回值:
None :   正常流程  
HttpResponse:当前中间件之后的中间件的process_request、路由匹配、视图函数都不执行,直接执行当前中间的process_response的方法,倒序执行之前的process_response的方法,最终返回给浏览器

process_response(self, request, response)

def process_response(self, request, response):
        '''
        正常流程: 必须返回response, 按照settings配置中的中间件顺序,倒序返回.
            # 可以自己指定返回的HttpResponse对象
            # request 在一次请求中 ,始终是同一个
            # 在路由(urls.py)和函数(view)之后进行处理
        :param request:
        :param response:
        :return:
        '''
        print('out M1 process_response ')
        return response # 必须返回
        
        
# 执行时间:
在执行视图函数之后
# 参数:
request:  请求对象   和视图是同一个
response:  返回的response对象
# 执行的顺序:
按照中间件的注册顺序  倒序执行 
# 返回值:
HttpResponse:必须返回response对象

process_view(self, request, view_func, view_args, view_kwargs)

def process_view(self, reques, view_func, view_args, view_kwargs):
        '''
​
        # 正常流程: 返回none ,按照 settings配置的中间件的顺序从上到下执行,
            # 可以修改函数的参数,或者修改函数返回的HttpResponse
            #  M1 process_view 处理完, 交给 M2 process_view处理. 处理完毕后执行 真正要处理的视图函数(view_func函数)
​			# 在 路由之后,函数之前进行处理
        :param reques:
        :param view_func:
        :param view_args:
        :param view_kwargs:
        :return:
        '''
        print('in M1  process_view ')
        print(view_func, view_args, view_kwargs)
        
# 执行时间:
在执行视图函数之前,在路由匹配之后
# 参数:
request:  请求对象   和视图是同一个
view_func: 视图函数
view_args: 传递给视图函数的位置参数    分组的参数
view_kwargs: 传递给视图函数的关键字参数    命名分组的参数
# 执行的顺序:
​按照中间件的注册顺序  顺序执行 ​
# 返回值:
None : 正常流程 
HttpResponse:当前中间件之后的中间件的process_view、视图函数都不执行,直接执行最后一个中间的process_response的方法,倒序执行之前的process_response的方法,最终返回给浏览器

process_exception(self, request, exception)

def process_exception(self, request, execption):
        '''
        # 使用process_exception 方法 , 条件必须是 出现异常错误
        # 执行顺序: 按照settings设置中间件的顺序 ,倒序执行.
            如果 最后一个中间件的 process_exception 不能处理,交给 上一个中间件的process_exception处理
            如果 都不能处理这个异常,则交给Django默认的配置处理. 最后有Django生成一个HttpResponse对象
        # 由settings配置的最后一个中间件 process_response方法 逐层返回.
        :param request:
        :param execption:
        :return:
        '''
        print('in M1 process_exception')
        print(execption) # 打印异常信息
        
# 执行时间(触发条件):
视图层面有异常才执行
# 参数:
request:  请求对象   和视图是同一个
exception:  错误对象
# 执行的顺序:
按照中间件的注册顺序  倒序执行 ​
# 返回值:
None : 交给下一个中间件处理异常,所有的中间件都没有处理,交给django处理
 HttpResponse:当前中间件之前的中间件的process_exception不执行,直接执行最后一个中间的process_response的方法,倒序执行之前的process_response的方法,最终返回给浏览器

process_template_response(self,request,response)

def process_template_response(self, request, response):
        '''
           #  执行顺序: 按照settings设置中间件的顺序 ,倒序执行.
             # response得到是一个TemplateResponse对象
             # 可以修改属性和值
           # 存在视图函数之后, 按照倒序执行完毕后,才进行最终的渲染.
            然后交由 settings最后中间件 执行 process_response逐层返回

        :param request:
        :param response:
        :return:
        '''
        print(response, type(response), '这是TemplateResponse对象')
        # <TemplateResponse status_code=200, "text/html; charset=utf-8">
        # <class 'django.template.response.TemplateResponse'> 这是TemplateResponse对象
        # 可以通过 response 获取模板文件的名字, 或者修改传递的参数
​
        # print(response.__dict__) # 查看都有什么属性
        print(response.template_name)  # 获得模版的名字
        print(response.context_data)  # 获得传递需要渲染的参数
​
        # 修改模版文件
        response.template_name = 'logout.html'
        print('in M1 process_template_response ')
        return response # 必须返回response
    
    
# 执行时间(触发条件):
视图返回的response 是一个template_response对象
# 参数:
request:  请求对象   和视图是同一个
response:  响应对象
# 执行的顺序:
按照中间件的注册顺序  倒序执行 
# 返回值:
​HttpResponse:必须返回

注册中间件

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    # 我定义的中间件
    'web.webmiddleware.authmiddleware.AuthMiddleWare',
    'web.webmiddleware.authmiddleware.LoginMiddleWare',
    'web.webmiddleware.smsmiddleware.SmsMiddleWare',
    'web.webmiddleware.spidermiddleware.SpiderMiddleWare',
]

执行中间件的流程

# 请求到达中间件之后,先按照正序执行每个注册中间件的process_reques方法,
# process_request方法返回的值是None,就依次执行,如果返回的值是HttpResponse对象,
# 不再执行后面的process_request方法,而是执行当前对应中间件的process_response方法,
# 将HttpResponse对象返回给浏览器。
# 也就是说:如果MIDDLEWARE中注册了6个中间件,执行过程中,第3个中间件返回了一个HttpResponse对象,
# 那么第4,5,6中间件的process_request和process_response方法都不执行,
# 顺序执行3,2,1中间件的process_response方法。

# process_request方法都执行完后,匹配路由,找到要执行的视图函数,
# 先不执行视图函数,先执行中间件中的process_view方法,process_view方法返回None,
# 继续按顺序执行,所有process_view方法执行完后执行视图函数。
# 假如中间件3 的process_view方法返回了HttpResponse对象,
# 则4,5,6的process_view以及视图函数都不执行,直接从最后一个中间件,
# 也就是中间件6的process_response方法开始倒序执行。

# process_template_response和process_exception两个方法的触发是有条件的,执行顺序也是倒序。

Django请求流程图

中间件应用

  1. 做IP访问频率限制:某些IP访问服务器的频率过高,进行拦截,比如限制每分钟不能超过20次。
  2. URL访问过滤:如果用户访问的是login视图(放过)如果访问其他视图,需要检测是不是有session认证,已经有了放行,没有返回login,这样就省得在多个视图函数上写装饰器了
# 不登陆也能访问
WHITE_URL_LIST = (
    '/admin/',
    '/result/',
    '/register/',
    '/sms/',
    '/smslogin/',
    '/login/',
    '/index/',
    '/code/',
    '/',
)

class LoginMiddleWare(MiddlewareMixin):
    def process_request(self, request):
        if request.path_info in settings.WHITE_URL_LIST or request.path_info.startswith('/admin/'):
        	# 白名单
            return
        else:
        	# 未登录
            if not request.authentication:
                return redirect('smslogin')
            else:
            	pass

AUTH

Auth模块是Django自带的用户认证模块

auth模块常用方法

from django.contrib import auth

authenticate()
# 提供了用户认证功能,即验证用户名以及密码是否正确,一般需要username 、password两个关键字参数。
# 如果认证成功(用户名和密码正确有效),便会返回一个 User 对象。
# authenticate()会在该 User 对象上设置一个属性来标识后端已经认证了该用户,且该信息在后续的登录过程中是需要的。
user = authenticate(username='usernamer',password='password')


login(HttpRequest, user)
该函数接受一个HttpRequest对象,以及一个经过认证的User对象。
该函数实现一个用户登录的功能。它本质上会在后端为该用户生成相关session数据。
user = authenticate(username=username, password=password)
  if user is not None:
    login(request, user)
    # Redirect to a success page.
    ...
  else:
    # Return an 'invalid login' error message.
    ...

logout(request)
该函数接受一个HttpRequest对象,无返回值。
当调用该函数时,当前请求的session信息会全部清除。该用户即使没有登录,使用该函数也不会报错。
from django.contrib.auth import logout
def logout_view(request):
  logout(request)
  # Redirect to a success page.

is_authenticated()
用来判断当前请求是否通过了认证。
if not request.user.is_authenticated():

login_requierd()
auth提供的一个装饰器工具,用来快捷的给某个视图添加登录校验。
from django.contrib.auth.decorators import login_required 
@login_required
def my_view(request):
	pass
若用户没有登录,则会跳转到django默认的 登录URL '/accounts/login/ ' 并传递当前访问url的绝对路径 (登陆成功后,会重定向到该路径)。
LOGIN_URL = '/login/'  # 这里配置成你项目登录页面的路由这样会全局要求登录

create_user()
auth 提供的一个创建新用户的方法
from django.contrib.auth.models import User
user = User.objects.create_user(username='用户名',password='密码',email='邮箱',...)

create_superuser()
auth 提供的一个创建新的超级用户的方法,
from django.contrib.auth.models import User
user = User.objects.create_superuser(username='用户名',password='密码',email='邮箱',...)

check_password(password)
auth 提供的一个检查密码是否正确的方法,需要提供当前请求用户的密码。
ok = user.check_password('密码')

set_password(password)
auth 提供的一个修改密码的方法,接收要设置的新密码作为参数
user.set_password(password='')
user.save()

User对象的属性

User对象属性:username, password
is_staff : 用户是否拥有网站的管理权限.
is_active : 是否允许用户登录, 设置为 False,可以在不删除用户的前提下禁止用户登录。

扩展默认的auth_user表

以通过继承内置的 AbstractUser 类,来定义一个自己的Model类。

from django.contrib.auth.models import AbstractUser
class UserInfo(AbstractUser):
    """
    用户信息表
    """
    nid = models.AutoField(primary_key=True)
    phone = models.CharField(max_length=11, null=True, unique=True)
    
    def __str__(self):
        return self.username

# 在settings.py中配置
# 引用Django自带的User表,继承使用时需要设置
AUTH_USER_MODEL = "app名.UserInfo"

XSRF

相关装饰器

from django.utils.decorators import  method_decorator # 给cbv加上装饰器
from django.views import View
from django.views.decorators.csrf import csrf_exempt, csrf_protect
​
### 在FBV模式下
# csrf_exempt  豁免csrf校验
# csrf_protect 强制进行csrf校验
​
​
@csrf_exempt
def csrf_check(request):
    return  render(request,'csrf_check.html')
​
### 在CBV模式下
# csrf_exempt要加在CBV上,只能加dispatch上
​
# 豁免csrf 和 强制csrf ,在CBV上必须添加在dispatch方法上
@method_decorator(csrf_exempt,name='dispatch')  
class CSRF_CHECK(View):
    def post(self,request):
        return HttpResponse('post,ok')
    def get(self,request):
        return render(request,'csrf_check.html')

CSRF的流程

在form表单中应用

<form action="" method="post">
    {% csrf_token %}
    <p>用户名:<input type="text" name="name"></p>
    <p>密码:<input type="text" name="password"></p>
    <p><input type="submit"></p>
</form>

在Ajax中应用

data: {
         'name': $('[name="name"]').val(),
         'password': $("#pwd").val(),
         'csrfmiddlewaretoken': $('[name="csrfmiddlewaretoken"]').val()
     },

放在cookie里

from django.views.decorators.csrf import ensure_csrf_cookie 
# ensure_csrf_cookie 确保响应的数据中包含session的csrftoken值
@method_decorator(ensure_csrf_cookie)
    def get(self,request):
        return render(request,'csrf_check.html')

如果从request.POST中获取不到csrfmiddlewaretoken的值,
会尝试从请求头中获取x-csrftoken的值,
并且拿这个值与csrftoken的值做对比,对比成功也能通过校验。

其它操作

  1. 全站禁用:注释掉中间件 ‘django.middleware.csrf.CsrfViewMiddleware’
  2. 局部禁用:用装饰器(在FBV中使用)

Django实现Websocket

django实现websocket大致上有两种方式,一种channels,一种是dwebsocket。channels依赖于redis,twisted等,相比之下使用dwebsocket要更为方便一些。

dwebsocket安装

pip3 install dwebsocket

dwebsocket配置

INSTALLED_APPS = [
    .....
    .....
    'dwebsocket',
]
 
MIDDLEWARE_CLASSES = [
    ......
    ......
    'dwebsocket.middleware.WebSocketMiddleware'  # 为所有的URL提供websocket,如果只是单独的视图需要可以不选
 
]
WEBSOCKET_ACCEPT_ALL=True   # 可以允许每一个单独的视图实用websockets

使用

from django.conf.urls import url
from django.contrib import admin
from app01 import views
urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^login/', views.login),
    url(r'^path/', views.path),
]
from django.shortcuts import render,HttpResponse

# Create your views here.
def login(request):
    return render(request,'login.html')

from dwebsocket.decorators import accept_websocket
@accept_websocket
def path(request):
    if request.is_websocket():
        print(1)
        request.websocket.send('下载完成'.encode('utf-8'))

详解

dwebsocket有两种装饰器:require_websocket和accept_websocekt,使用require_websocket装饰器会导致视图函数无法接收导致正常的http请求,一般情况使用accept_websocket方式就可以了,

dwebsocket的一些内置方法:

request.is_websocket():判断请求是否是websocket方式,是返回true,否则返回false
request.websocket: 当请求为websocket的时候,会在request中增加一个websocket属性,
WebSocket.wait() 返回客户端发送的一条消息,没有收到消息则会导致阻塞
WebSocket.read() 和wait一样可以接受返回的消息,只是这种是非阻塞的,没有消息返回None
WebSocket.count_messages()返回消息的数量
WebSocket.has_messages()返回是否有新的消息过来
WebSocket.send(message)像客户端发送消息,message为byte类型

文章出处登录后可见!

已经登录?立即刷新
退出移动版