Django框架

Django框架

Django是一个功能强大的web框架。

博客中缺少知识流程解释图,后期我会一一补充回来,因为博客园插图片比较麻烦,请谅解。

前言:框架模式简介

1、MVC和MTV框架模式:分层级进行管理

说到框架模式我们有必要简单的说下设计模式,了解下设计模式这个概念,因为有人对设计模式和框架模式的概念经常混淆

设计模式:

是一套被反复使用,多数人知道并经过分类的代码设计经验总结,是为了解决一些通用性问题的
目的:重用代码并保证代码的可靠性
设计模式分类:单例,抽象工厂 等等 23种模式
一句话总结:解决某一些特殊问题的一种思想和思路

框架模式:

代码重用,框架模式是解决如何设计程序框架的代码,在框架模式中会包含多种设计模式,与设计模式是一种包含关系,
举例来说:比如要盖楼,那怎么盖楼属于框架模式,楼里面的电梯怎么设计,楼梯怎么设计,属于设计模式,所以框架模式在
盖楼中属于如何把楼盖起来,那么他里面会包含多种设计模式,具体的细节碰到不同的东西,会采用不同的设计模式来解决,
因此在一种框架模式中会包含多种设计模式。

目前流行的框架模式:

  • MVC(适用于多种编程语言,单在python中不常用):

    Web服务器开发领域里著名的MVC模式,所谓MVC就是把Web应用分为模型(M),控制器(C)和视图(V)三层

    M:Models 模型层,在程序中用于处理数据逻辑的部分,(主要是处理数据),主要负责在数据库中对数据的存取操作,其实就是对数据库的增删改查的操作
    V:Views 视图层 ,在应用程序中处理显示部分的内容(html,jsp),也就是业务逻辑相关操作
    C: Controllers 控制层 ,处理用户交互的部分,主要作用于M和V之间做协调,通常是负责从模型层中抽取数据,再进行业务处理,最后将数据传给视图层,并将视图传给客户端,(控制器,也就是通过路径找到对应视图函数)
    
    详解:用户首先打开浏览器,输入网址,然后浏览器向服务器发送请求,到了服务器之后,由控制层接收这个请求,接收完请求就知道用户想要做什么,了解了用户的意图,如果需要用到一些数据,比如想查看某某商品的信息,那么控制器就需要找到商品的信息,所以控制器就找模型层了,模型层会根据数据库创建模型(注意模型层不是数据库)一般情况数据库有多少张表,那么模型层就有多少个类,每个表中有多少个字段,模型层中的类就有多少个变(属性),在模型层里还会提供增删改查的操作,那么这个执行结构再反馈给控制器,到此,控制层和模型层的交互完成了,接下来,控制层就会把接收到的数据发送给视图,视图会把数据显示在网页里,反馈给浏览器,这样用户就看到了
    

  • MTV(django)

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

    M:模型层,功能同上,数据库相关操作
    T:templates:模板层,用于处理用户显示部分的内容,和MVC中的V是一样的,通过html展示(模板渲染等)
    V:views 视图层,在MTV中视图层是处理用户交互的部分,从模型层中获取数据,再将数据交给模板层,再先是给用户和MVC中的控制层用法一样,也就是业务逻辑相关操作
    
    加上一个url控制器,也就是通过路径找到对应视图函数
    
    详解:用户打开浏览器,浏览器发送请求,视图层接收用户请求,接受完请求调用模型层,模型层根据数据库创建模型,进行增删改查等操作,模型层处理完数据返回给视图层,视图层接收完数据调用模板层,模板层里存放HTML等页面,模板层会把HTML模板页面返回给视图层,视图层填充数据到模板上,然后再返回给浏览器
    
2、WSGI

WSGI(Web Server Gateway Interface)就是一种规范,它定义了web应用程序与web服务器程序之间的接口格式,实现web应用程序与web服务器程序间的解耦。

开发的项目分为两个部分的程序
1 服务器程序  socket相关功能的模块,wsgiref、uwsgi等等,负责接收网络请求并解析网络请求相关数据,分装加工成一些可用的数据格式,格式大家都是统一的,方便应用程序的开发

2 应用程序  就是使用接收到的网络请求相关数据,进行逻辑代码的开发,比如数据库相关操作,数据结构的调整、文件操作等等。。。

一、基本使用

1、下载的三种方式:
  • 直接在pycharm中的setting中进行下载
  • 在cmd中通过命令下载:pip install django==版本
  • 在pycharm的Terminal控制台中进行下载(下载时要注意路径问题)
2、创建项目

(1)通过cmd或pycharm控制台的命令创建项目

​ 先切换到要创建项目的目录下,然后执行创建项目命令:

django-admin startproject mysite  # mysite是项目名称

​ 创建项目后会生成如下的目录,当前目录下会生成mysite的工程,里面有个主目录和我们创建的项目目录同名,在项目目录下有个manage.py文件,在主目录下有settings.py\urls.py\wsgi.py,每个文件的功能介绍如下:

manage.py ----- Django项目里面的工具,通过它可以调用django shell和数据库,启动关闭项目与项目交互等,不管你将框架分了几个文件,必然有一个启动文件,其实他们本身就是一个文件。
settings.py ---- 包含了项目的默认设置,包括数据库信息,调试标志以及其他一些工作的变量。
urls.py ----- 负责把URL模式映射到应用程序。
wsgi.py ---- runserver命令就使用wsgiref模块做简单的web server,后面会看到renserver命令,所有与socket相关的内容都在这个文件里面了,目前不需要关注它。

​ 一个django项目中可以有多个应用,每个应用完成项目的部分功能,这些功能相对于其他功能来说是相对独立的,但又同时存在于同一个项目中,每个应用的逻辑数据库等也都是相对独立的,每个应用都有属于自己的模块单位;开发的时候,都是通过应用来写逻辑

(2)通过pycharm创建django项目

  • 点击File --》New Project 选择第二项 Django

  • 在Location中选择选项目创建的地址和项目名

  • Project Interpreter:

    Project Interpreter中的Newenvironment using是创建项目执行的虚拟环境

    Project Interpreter中的Existing interpreter是使用本地的环境,也可以使用已创建好的虚拟环境

  • More Settings

    Template language:模板引擎;默认是Django的Template模板引擎

    如若下载jinja2模板引擎可进行切换,或者其他模板引擎

    注:django中的模板引擎未单独封装成模块;jinja2是模仿的的django的Template

    Templates folder:存放html文件的文件夹名

    Application name:是创建的应用的应用名

  • create创建完成后执行即可,通过控制台显示的链接即可访问

3、运行项目

启动项目命令:

python manage.py runserver 127.0.0.1:8080
ip和port可以不用写,不写时默认是 127.0.0.1:8000

运行成功后,会看到一个项目链接,在浏览器中输入此链接即可访问到创建的项目

4、创建应用Application

(1)创建项目时直接通过Application name创建应用

(2)pycharm中手动创建应用

  • cmd或pycharm控制器Terminal中创建应用
要在项目目录下执行命令进行创建应用
python manage.py startapp 应用名
经常用到的三个文件
models.py  数据库相关内容
views.py  视图,业务逻辑代码相关内容
tests.py 用来写一些测试代码,用来测试我们自己写的视图的,目前用不到
  • 手动创建文件夹(也可复制其它应用的文件,要注意修改配置)

    • 创建应用文件夹(鼠标右键使用python package创建)
    • 在settings.py中找到INSTALLED_APPS在其下面添加应用的配置信息
    • 应用名.apps.应用名Config(后面的应用名的首字母大写)
  • 最后一点可能需要配置一下template的路径

    • 在settings.py中找到TEMPLATES在其下面的'DIRS':[]中添加os.path.join(BASE_DIR, 'templates')

    • 结果显示:
      'DIRS': [os.path.join(BASE_DIR, 'templates')],
      
  • 注意

    • 手动创建应用的时候要把应用指定给当前项目
    • 在settings文件的INSTALLED_APPS的下面
      • 方式一:应用名.apps.应用名Config(后面的应用名的首字母大写)
      • 方式二:应用名 ( 这是简写方式)
    • 如若不指定应用,Django中的某些方法将无法使用
5、django项目的导入

请看博客:django项目导入

6、windows中安装不同版本的python解释器

在python3.7及以上版本中,使用django框架的时候,django的版本要在2.0或以上,否则会出现问题;python3.6使用django的1.0版本。

当想即使用python3.7和python3.6针对django的不同版本进行创建项目时,python解释器的安装要注意,

解释器安装请参考博客:windows中安装不同版本的python解释器

二、url路由系统

​ 在django中,url中的路径写法是正则,正则里面有无名分组正则,有有名分组正则,那么对应django中的功能,我们称之为无名分组路由和有名分组路由

​ 在django的1.0版本中路由配置文件urls.py中使用的是url(),里面可以直接使用正则匹配路径的方式

​ 而在django的2.0版本中路由配置文件urls.py中使用的是path(),里面不能直接使用正则匹配路径,如需使用正则路径进行匹配就要使用re_path(),使用前要先导入

1、无命名分组路由

看写法,urls.py文件中内容如下

urlpatterns = [
		...
    url(r'^books/(\d+)/(\d+)/', views.book),
    #正则里面()分组正则,会将分组中的正则匹配到的内容作为返回值返回
]

简单分析,伪代码
'''
当用户请求的路径是它: /books/2019/8/

url(r'^books/(\d+)/(\d+)/', views.book), 里面做的事情如下

re.match(^books/(\d+)/,/books/2019/)
2019 和 8 作为位置参数交给了要执行的book视图函数
视图函数book的写法
def book(request,xx,oo):
    xx = 2019
    oo = 8
    pass
'''

视图views.py文件中函数的写法

#位置传参,url中正则^books/(\d+)/(\d+)/,那么第一个()分组匹配到的数据,作为book函数的第二个参数,第二个()分组匹配到的数据,作为book的第三个参数
def book(request, year, month):
    print('year', year, 'month', month) #year 2020

    # return HttpResponse('%s所有书籍' % year)
    return HttpResponse('%s年%s月所有书籍' % (year, month))

使用url路由系统时需要注意几个点

1. urlpatterns中的元素按照书写顺序从上往下逐一匹配正则表达式,一旦匹配成功则不再继续。
2. 若要从URL中捕获一个值,只需要在它周围放置一对圆括号(正则分组匹配)。
3. 不需要添加一个前导的反斜杠(也就是写在正则最前面的那个/),因为每个URL 都有。例如,应该是^articles 而不是 ^/articles。
4. 每个正则表达式前面的'r' 是可选的但是建议加上。
5. ^articles$  以什么结尾,以什么开头,严格限制路径
2、有名分组路由

其实就玩得正则的有名分组,看示例

Urls.py文件中的写法

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    # /books/2020/6/
    url(r'^books/(?P<year>\d+)/(?P<month>\d+)/', views.book),
    # {'year':2020,'month':6},url类将有名分组正则匹配出来的数据,交给了book视图函数作为关键字参数来使用]

View.py文件中函数的写法如下

# ^books/(?P<year>\d+)/(?P<month>\d+)/
#获取到url中的有名分组正则匹配到的数据,那么函数形参名称必须和有名分组正则的那个名称相同才可以,也就是按照上面的url来看的话,函数的形参必须是year和month这两个名称,并且关键字传参不需要考虑函数形参的位置
def book(request, month, year):
    # print('year', year, 'month', month) #year 2020
    print(request.path) #/books/2020/6/
    # return HttpResponse('%s所有书籍' % year)
    return HttpResponse('%s年%s月所有书籍' % (year, month))
3、路径中的尾部斜杠问题

当用户通过浏览器访问django框架完整的项目中的某个路径时,如果用户在输入网址路径的最后,没有加上/斜杠,比如http://127.0.0.1:8000/test,那么django会发将用户输入的网址路径加上一个后置的/,也就会将路径变成这样http://127.0.0.1:8000/test/,然后给浏览器发送一个重定向的响应操作,状态码为301,那么浏览器拿到这个重定向操作之后,就会自动发起一个这样的路径请求http://127.0.0.1:8000/test/,所以当我们打开浏览器控制台的network功能查看请求过程时,会看到两个请求,一个没有后置的斜杠的,一个是有后置斜杠的。第二个请求才会走到我们的urs.py文件中的路径配合和分发对应视图的地方。

我们可以通过一个配置项,告诉django,不要自动加路径后面的斜杠了,但是需要注意的就是你自己写的url中的正则,也别加后面的斜杠,不然正则匹配不到。

配置项直接写在settings配置文件中,任意位置

APPEND_SLASH = False  #False表示告诉Django,不加路径后面的斜杠,默认值是True
5、视图函数中指定默认值

views.py文件:

# 在路由没有匹配任何参数的时候,num使用自己的默认值
# 当路由中有分组匹配数据的动作,比如url(r'^test/(\d+)/', views.test),用户输入网址:http://127.0.0.1:8000/test/22/,那么22就被匹配到了,会作为参数传给我们的test函数,那么num的值就变成了22
def test(request, num=10):
    print('number>>>',num)
    return HttpResponse('test')

urls.py文件

# url(r'^test/', views.test),
	url(r'^test/(\d+)/', views.test),
6、url反向解析

​ 由于将来项目中的不同功能对应的url路径可能会发生变化,所以我们在每个url路径上加上一个别名,将来通过别名反向解析来使用这个别名对应的路径,那么不管路径将来发生什么变化,只要别名不变,那么逻辑中使用这个路径的地方,都可以通过别名获取到

(1)url别名用法

urlpatterns = [
    ...
    url(r'^add_book/', views.add_book, name='add_book'),  #name属性对应的值,就是这个路径的别名

]

(2)views视图中使用url反向解析的方式

# 首先在视图中引入反向解析的方法
from django.urls import reverse  #url别名反向解析,通过name别名对应的数据,解析出我们的url路径

1 针对没有参数的别名 url(r'^add_book/', views.add_book, name='add_book'),
	反向解析:reverse('book_list')
2 针对含有无名分组的url:url(r'^book_list/v2/(\d+)/(\d+)/', views.book_list, name='book_list'),
	反向解析:reverse('book_list',args=(11,22))
3 针对含有有名分组的url:url(r'^book_list/v2/(?P<year>\d+)/(?P<month>\d+)/', views.book_list, name='book_list'),
	反向解析:reverse('book_list',kwargs={'year':2022,'month':11})

(3)html文件中使用url别名反向解析

  • 无参数:
  • 有参数:
针对无参数的路径:url(r'^add_book/', views.add_book, name='add_book'),
	反向解析:<a href="{% url 'add_book'%}" class="btn btn-primary">添加书籍</a>
这对有参数的路径:url(r'^add_book/(\d+)/(\d+)/', views.add_book, name='add_book'),
							 url(r'^add_book/(?P<year>\d+)/(?P<month>\d+)/', views.add_book, name='add_book'),
	反向解析: <a href="{% url 'add_book' 2020 12 %}" class="btn btn-primary">添加书籍</a>
7、路由分发和命名空间 namespace

1 创建app

python manage.py startapp app02

2 配置app,在settings.py文件中

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'app01.apps.App01Config',
    'app02'
]

路由分发步骤

1 在项目主目录下的urls.py(以后我们称之为总路由)文件中,写上如下内容

from django.conf.urls import url, include
from django.contrib import admin

# 路由分发功能
urlpatterns = [
    url(r'^admin/', admin.site.urls),
  	
    # /app01/index/ -- 去掉/app01/ -- index/拿到app01应用下面的urls.py文件中进行路径匹配
    url(r'^app01/', include('app01.urls')),
    # 当访问路径是以/app01/开头的,那么django主目录下面的urls.py文件会自动帮我们去找app01下面的urls.py文件进行路径匹配
    url(r'^app02/', include('app02.urls')), #include参数格式 '应用名称.urls'
]

2 在每个应用文件夹下面创建一个叫做urls.py的文件,里面写上如下内容

from django.conf.urls import url, include
from app01 import views
# 路由分发功能
urlpatterns = [
    #' index/'
  	# 写上自己应用中的每个路径对应的函数
    url(r'^index/', views.index),

]

出现一个问题,当两个应用中的某个url的别名相同了,那么使用url反向解析的时候,发现都是解析出来的都是最后一个应用的对应路径,这叫做别名冲突。

看示例:

app01下的urls.py

from django.conf.urls import url, include
from app01 import views
# 路由分发功能
urlpatterns = [
    url(r'^index/', views.index),
    url(r'^home/', views.home, name='home'),

]

App02下的urls.py

from django.conf.urls import url, include
from app02 import views
# 路由分发功能
urlpatterns = [
    url(r'^index/', views.index),
    url(r'^home/', views.home, name='home'),

]

App01的views.py文件中进行别名发现解析,代码:

def index(request):
    print('app01>>>',reverse('home'))
    #app01>>> /app02/home/
    return HttpResponse('app01-index')

App02的views.py文件中进行别名发现解析,代码:

def index(request):
    print('app02>>>',reverse('home'))
    #app02>>> /app02/home/
    return HttpResponse('app02-index')

发现别名冲突,导致反向解析出错了。

解决方法,命名空间

示例:总路由写法

from django.conf.urls import url, include
from django.contrib import admin

# 路由分发功能
urlpatterns = [
    url(r'^admin/', admin.site.urls),
  	# 路由分发时,给每个应用的路由进行了空间划分,使用namespace
    url(r'^app01/', include('app01.urls', namespace='app01')),
    url(r'^app02/', include('app02.urls', namespace='app02')),
]

views.py文件写法

def index(request):
  # 在进行反向解析时,需要用到命名空间:别名,来进行解析
    print('app02>>>',reverse('app02:home'))
    # app02>>> /app02/home/
    return HttpResponse('app02-index')

三、视图

1、request的对象

常用的属性和方法

print(request)  # wsgirequest对象
print(request.path)  # 请求路径 #/index/
print(request.method)  # 请求方法
print(request.POST)  # post请求提交的数据 <QueryDict: {'username': ['root']}>
print(request.GET)  # 获取url中的查询参数 <QueryDict: {'a': ['1'], 'b': ['2']}>  #不是针对get请求的
print(request.body)  #获取http请求消息格式的请求数据部分的内容  b''
print(request.META)  #请求头信息
print(request.get_full_path())  # 获取完整路径(包含查询参数的) /index/?a=1&b=3

print(request.FILES)  # 上传的文件对象数据
print(request.FILES.get('file'))  # 上传的文件名

#<QueryDict: {'username': ['root'], 'sex': ['female'], 'hobby': ['2', '3']}>
print(request.POST.get('username'))  # 前端中传输的username的值
print(request.POST.get('sex'))  # 前端中单选传输的sex值
# 多选提交来的数据通过getlist来获取
print(request.POST.getlist('hobby'))  # ['2', '3']
2、response的响应

(1)常用方法

from django.shortcuts import render, HttpResponse, redirect

return HttpResponse('你好') #回复字符串
return render(request,'home.html') #回复html页面

#重定向方法,参数是个路径
return redirect('/home/')  #封装了302状态码,以及浏览器要重定向的路径

(2)添加响应头键值对

ret = render(request,'home.html') 
ret['a'] = 'b' #添加响应头键值对
return ret

(3)添加响应状态码

ret = render(request,'home.html', status=202) #render修改状态码还可以这样改
#ret['a'] = 'b' #添加响应头键值对
ret.status_code = 201 #添加响应状态码
return ret #回复html页面
3、CBV和FBV

两种视图逻辑的写法方法

FBV:全称function based view,就是基于函数来写视图逻辑

CBV:全称class based view,就是基于类来写视图

基于类的视图CBV写法,如下,views.py文件

from django.views import View

#登录需求
class LoginView(View):
    # get请求  获取login页面
    def get(self,request):
        return render(request,'login.html')
        
    # post请求,获取post请求提交的数据,并校验等等
    def post(self,request):
        print(request.POST)
        
        #<QueryDict: {'uname': ['chao'], 'pwd': ['123']}>
        return render(request,'login.html')

url路径的写法:urls.py文件

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^index/', views.index),
    url(r'^home/', views.home),

    # 类视图的url写法
    url(r'^login/', views.LoginView.as_view()),

]
4、cbv源码重点(反射)
from django.views import View
View里面的dispatch方法中的反射逻辑,实现了不同的请求方法,找到我们视图类中的对应方法执行
5、FBV和CBV加装饰器

FBV和普通函数加装饰器方式一样

示例:

#装饰器函数
def outer(f):
    def inner(request, *args ,**kwargs):
        print('前面')
        ret = f(request, *args ,**kwargs)
        print('后面')
        return ret
    return inner

#使用装饰器
@outer
def books(request):
    print('FBV执行了')
    return HttpResponse('book.html--ok')

CBV加装饰器

#装饰器函数
def outer(f):
    def inner(request, *args ,**kwargs):
        print('前面')
        ret = f(request, *args ,**kwargs)
        print('后面')
        return ret
    return inner


#使用装饰器
#1 引入django提供的装饰器方法method_decorator来配合装饰器函数的使用
from django.utils.decorators import method_decorator

@method_decorator(outer,name='get') #CBV加装饰器方式3
class BookView(View):
    #给类方法统一加装饰器,借助dispatch方法(父类的dispatch方法,就是通过反射来完成不同的请求方法找到并执行我们自己定义的视图类的对应方法)
    # 重写dispatch方法,dispatch方法是在其他请求方法对应的类方法执行之前执行的
    # @method_decorator(outer) #加装饰器方式2
    def dispatch(self, request, *args, **kwargs):
        # print('xxxxxx')
        ret = super().dispatch(request, *args, **kwargs)
        # print('oooooo')
        return ret

    #CBV加装饰器的方式1,给单独的方法加装饰器
    # @method_decorator(outer)
    def get(self, request, xx):
        print('CBV的get方法')
        return render(request, 'book.html')

    # @method_decorator(outer)
    def post(self,request, xx):
        print('CBV的post方法')
        return HttpResponse('ok')
  

四、Template模板

1、Template的基本使用

(1)通过{{ 变量 }}:获取单个变量值

(2)通过{% 逻辑 %}:获取逻辑渲染结果

2、变量的使用

Number数据类型,容器数据类型和对象都可以直接进行渲染

(1)返回前端页面的数据格式

  • 在return返回的时候可以直接写入参数,区别在于html中渲染的时候直接通过元素直接获取(使用原参数没有作用),容器数据类型才有效,number类型无效

    return render(request, "index.html", info)

  • 在return返回的时候也可以使用字典的方式,使用字典时,就是直接通过字典的键来进行相应的操作

    return render(request, "index.html", {'info': info})

(2)句点号的使用:

​ 在字典数据类型中需要使用句点号和索引搭配才能获取到相应的值

​ 同理对象的方法和属性的调用也是通过句点号,而且要注意调用方法不能加()

# views.py
from django.shortcuts import render
import datetime
# Create your views here.
class obj():
    pass
def index(request):
    pdd = '1234'
    info = {
        'name': '旋风奥利奥',
        'age': '18',
        'hobby': "girl",
        'dict': {'drink': '饮品', 'milk': '牛奶'},
        'list': ['面包', '包子'],
        'object': obj(),
        'size': 123456,
        'time': datetime.datetime.now()
    }
    return render(request, "index.html", info)
    # return render(request, "index.html", {'info': info})

# html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h3>{{ name }}</h3>
<p>{{ dict }}</p>
<p>{{ list }}</p>
<p>{{ pdd }}</p>
<hr>
{% for foo in list %}
<p>{{ foo }}</p>
{% endfor %}
<hr>
{% for k,v in dict.items %}
<p>{{ k }} : {{ v }}</p>
{% endfor %}
<hr>
{{ object.obk }}
</body>
</html>

# url.py
from app01 import views
urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^index/', views.index)
]
3、Template过滤器
  • 通过过滤器对数据进行过滤处理显示
  • 使用方式:{{ 变量|过滤器:参数 }}
  • 过滤器支持链式操作,通过多个管道符和过滤器相配合实现层级过滤
  • 过滤器可以接收参数
  • 注意事项:管道符左右两遍不能留空格,否则不能识别;还有参数和:间也不能留空格;
3.1、内置过滤器

(1)default:默认值

​ 当变量获取的到值的时候显示获取的值,获取不到值或者获取的是一个布尔类型的False时就使用默认的default值:{{ value|default:"没有值"}}

(2)length:长度

​ 返回的是字符串或列表的长度:{{ value|length }}

(3)filesizeformat:文件大小的显示处理

​ 将值格式化为一个 “人类可读的” 文件尺寸 (例如 13 KB, 4.1 MB, 102 bytes, 等等)。

(4)slice:切片:{{ value|slice:"2:-1" }}

(5)date:时间显示处理

​ 对获取的时间进行过滤处理:{{ value|date:"Y-m-d H:i:s"}}

(6)safe:声明此变量值(或代码段)不转义

{'a_tag':'<a href="">百度</a>',}
渲染
<p>
    {{ a_tag|safe }} #生成标签效果
</p>

(7)truncatechars:指定字符串长度,超出部分以 ... 显示
{{ value|truncatechars:9}}:指定九个字符长度,也包括 ... 这三个字符

(8)truncatewords:以单词的形式指定字符串长度,超出部分以 ... 显示

​ {{ value|truncatewords:3}}:指三个单词长度,不包括 ... 部分

(9)cut:过滤字符串

​ {{ value|cut:' ' }}:过滤掉value变量中和参数中的空格相同的字符

(10)join:字符串拼接

​ {{ hobby|join:'+' }}:把列表数据通过加号拼接成一个字符串

3.2、自定义过滤器
  • 在应用文件夹中创建一个名为templatetags的文件夹(文件夹的名字必须是templatetags)
  • 在templatetags文件夹中创建任意 .py 文件,如:mytag.py
  • 在mytag.py文件中写上如下内容
from django import template
register = template.Library()  # 制作注册器,名字必须叫register
#过滤最多两个参数
@register.filter  # 注册过滤器,需要两个参数的
def add(v1, v2):  # v1表示管道符前面的,v2表示冒号后面的参数
    print(v1,v2)  # 100 50
    return v1 + v2

@register.filter  # 注册过滤器,需要一个参数的
def xxx(v1):   # v1表示管道符前面的
    print(v1) 
    return 'xxx'
  • 在html文件中导入
{% load mytag %}  <!-- 首先通过load来加载一下mytag文件,不一定放在文件的开头,但是一定要放在使用过滤器的前面先进行引用 -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>

</head>
<body>
<h1>base页面</h1>
  <!-- {% load mytag %} 放这里也是可以的 -->  
<div>
    {{ num|add:50}}  <!-- 使用过滤器,和django内置过滤器用法一样,这里生成的是add函数的返回值 -->
</div>
<div>
    {{ num|xxx }}
</div>
<!-- {% load mytag %} 放这里不行 -->  

</body>
</html>
4、Template标签

​ 使用方式:{% 标签 %} {%end标签%}

4.1、内置标签

(1)for 循环标签

​ {% for xx in hobby %}{% endfor %}

forloop的使用

​ 注:循环序号可以通过{{forloop}}显示,必须在循环内部用

  • forloop.counter 当前循环的索引值(从1开始),forloop是循环器,通过点来使用功能

  • forloop.counter0 当前循环的索引值(从0开始)

  • forloop.revcounter 当前循环的倒序索引值(从1开始)

  • forloop.revcounter0 当前循环的倒序索引值(从0开始)

  • forloop.first 当前循环是不是第一次循环(布尔值)

  • forloop.last 当前循环是不是最后一次循环(布尔值)

  • forloop.parentloop 本层循环的外层循环的对象,再通过上面的几个属性来显示外层循环的计数等

for循环的反向循环:

​ 可以利用{% for obj in list reversed %}反向完成循环。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
{% for i in dict %}
    <p>{{ i }}</p>
{% endfor %}
<hr>
{% for i in dict.values %}
    <p>{{ i }}</p>
{% endfor %}
<hr>
{% for k,v in dict.items %}
    <p>{{ k }} : {{ v }}</p>
{% endfor %}
<hr>
{% for foo in list %}
    <p>{{ forloop }} : {{ foo }}</p>
{% endfor %}
<hr>
{% for foo in list %}
    <p>{{ forloop.counter }} : {{ foo }}</p>
{% endfor %}
<hr>
{% for foo in list %}
    <p>{{ forloop.counter0 }} : {{ foo }}</p>
{% endfor %}
<hr>
{% for foo in list %}
    <p>{{ forloop.revcounter }} : {{ foo }}</p>
{% endfor %}
<hr>
{% for foo in list %}
    <p>{{ forloop.revcounter0 }} : {{ foo }}</p>
{% endfor %}
<hr>
{% for foo in list %}
    <p>{{ forloop.first }} : {{ foo }}</p>
{% endfor %}
<hr>
{% for foo in list %}
    <p>{{ forloop.last }} : {{ foo }}</p>
{% endfor %}
<hr>
{% for foo in list %}
    {% for foo in list %}
        <p>{{ forloop.parentloop.counter }} : {{ forloop.revcounter0 }} : {{ foo }}</p>
    {% endfor %}
{% endfor %}
<hr>

# 反向循环列表
{% for foo in list reversed %}
    <p>{{ foo }}</p>
{% endfor %}

</body>
</html>

(2)if 判断标签

​ {% if 判断条件 %}{% endif %}

  • if语句支持 and 、or、==、>、<、!=、<=、>=、in、not in、is、is not判断,注意条件两边都有空格。
  • 但是不支持连续判断操作:
  • {% if %}会对一个变量求值,如果它的值是“True”(存在、不为空、且不是boolean类型的false值),对应的内容块会输出。
{% if num > 100 or num < 0 %}
	<p>无效</p>  <!--不满足条件,不会生成这个标签-->
{% elif num > 80 and num < 100 %}
	<p>优秀</p>
{% else %}  <!--也是在if标签结构里面的-->
	<p>凑活吧</p>
{% endif %}

(3)with 起别名标签

​ 使用一个简单地名字缓存一个复杂的变量,多用于给一个复杂的变量起别名,当你需要使用一个“昂贵的”方法(比如访问数据库)很多次的时候是非常有用的;注意:等号左右不要加空格。

方式一:
{% with total=business.employees.count %}
    {{ total }} <!--只能在with语句体内用-->
{% endwith %}

方式二:
{% with business.employees.count as total %}
    {{ total }}
{% endwith %}

(4)for empty联合使用的情况

​ 当循环的hobby没有数据或为空的时候,就显示empty下面的内容

<ul>
    {% for xx in hobby %}
    <li>{{ xx }}</li>
    {% empty %}
        <h2>抱歉,没有查询到相关数据</h2>
    {% endfor %}
</ul>

(5)Django的模板语言中属性的优先级大于方法

​ 处理的字典数据中不要出现以方法名为键的键值对,因为默认会获取该键值对数据,而不是走方法去处理数据,导致得不到想要的数据结果。

def xx(request):
    d = {"a": 1, "b": 2, "c": 3, "items": "100"}
    return render(request, "xx.html", {"data": d})

​ 如上,我们在使用render方法渲染一个页面的时候,传的字典d有一个key是items并且还有默认的 d.items() 方法,此时在模板语言中:{{ data.items }}

​ 默认会取d的items key的值。

4.2、自定义标签
  • 在应用文件夹中创建一个名为templatetags的文件夹(文件夹的名字必须是templatetags)
  • 在templatetags文件夹中创建任意 .py 文件,如:mytag.py
  • 在mytag.py文件中写上如下内容
from django import template
register = template.Library()  # 制作注册器,名字必须叫register
@register.simple_tag
def atag(v1,v2):  # 没有参数个数限制
    print(v1,v2)
    return v1 + v2
  • 在html文件中导入
 {% load mytag %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>

</head>
<body>
<h1>base页面</h1>
{% load mytag %}

<div>
    {% atag a b %}  <!-- 注意,是{%%} 来包裹使用,先是标签名称然后空格写参数,参数之间也是空格分隔的 -->
</div>

</body>

</html>
5、Templete模板继承

将一些页面公共的部分,可以抽离出来单独做成一个html页面,使用这些公用部分的其他html文件,只需要继承一下它就可以了,具体使用流程如下:

(1) 创建公用模板,比如内容如下

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        body{
            padding: 0;
            margin: 0;
        }
        {% block css %}
        .nav{
            height: 60px;
            background-color: green;
        }
        {% endblock %}
        .nav a{
            color:white;
            text-decoration: none;
        }
        .left-menu{
            width: 30%;
            background-color: rgba(0,0,0,0.5);
            float:left;
        }
        .menu .menu-title{
            text-align: center;
        }
        .main{
            float: right;
            width: 65%;
            height: 300px;
            border: 1px solid red;
        }
    </style>
</head>
<body>

<div class="nav">
    <a href="/xx1/">首页</a>
    <a href="/person/">个人中心</a>
    <a href="/detail/">详情页</a>
</div>
<div class="left-menu">
    <div class="menu">
        <div class="menu-title">菜单1</div>
        <div class="menu-body">
            <div class="item">局部按摩</div>
            <div class="item">全身按摩</div>
            <div class="item">足底按摩</div>
            <div class="item">头部按摩</div>
        </div>
        <div class="menu-title">菜单2</div>
        <div class="menu-body">
            <div class="item">盲人按摩</div>
            <div class="item">推背</div>
            <div class="item">刮痧</div>
            <div class="item">精油、火罐</div>
        </div>
    </div>
</div>
<div class="main">
    {% block content %}
    公共页面
    {% endblock %}
</div>

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

</html>

(2)将来如果说继承公用模板的html文件中需要修改公用模板中的一些内容,那么需要在公用模板中预留一些钩子,钩子的写法如下

{% block content %}  #block 后面的块名称随便起
公共页面
{% endblock %}
#也可以这样写 {% endblock content %}  #endblock指定名称

(3)继承公用模板需要在html文件中写如下内容:

{% extends 'xx.html' %}  <!-- 需要先继承一下公用模板,写法就是extends '公用模板文件名称',注意,必须写在第一行 -->

{% block css %}
.nav{
    height: 60px;
    background-color: pink;
}
{% endblock %}

{% block content %}
    <h1>首页</h1>
{% endblock %}


(4) 在使用公用模板的其他html文件中,如果需要更改公用模板里面的内容,只需要在html文件中写上相同的钩子,钩子里面写上自定义的内容,写法如下

{% block css %}
.nav{
    height: 60px;
    background-color: pink;
}
{% endblock %}

{% block content %}
    <h1>首页</h1>
{% endblock %}

(5)注意事项:

  • 如果你在模版中使用 {% extends %} 标签,它必须是模版中的第一个标签。其他的任何情况下,模版继承都将无法工作,模板渲染的时候django都不知道你在干啥。

  • 在base模版中设置越多的 {% block %} 标签越好。子模版不必定义全部父模版中的blocks,所以,可以在大多数blocks中填充合理的默认内容,然后,只定义你需要的那一个。多一点钩子总比少一点好。

  • 如果你发现你自己在大量的模版中复制内容,那可能意味着你应该把内容移动到父模版中的一个 {% block %} 中。

  • {{ block super }}的使用,在子模板中也展示出父模板原来钩子中的内容

    {% block content %}
        <h1>首页</h1>
        {{ block.super }}
    {% endblock %}
    
    
  • 为了更好的可读性,你也可以给你的 {% endblock %} 标签一个 名字 。例如:

{% block content %}
...
{% endblock content %}  

 在大型模版中,这个方法帮你清楚的看到哪一个 {% block %} 标签被关闭了。

  • 不能在一个模版中定义多个相同名字的 block 标签。

    #两个block都叫content,这种写法是不对的
    {% block content %}
    
        <h1>首页</h1>
        {{ block.super }}
    {% endblock %}
    {% block content %}
    
        <h1>首页</h1>
        {{ block.super }}
    {% endblock %}
    
6、要注意Template模板渲染实在浏览器解释之前执行的,模板渲染后才轮到浏览器器来执行解释

​ 与safe处理效果相同的mark_safe方法,mark_safr方法需要导入模块

  • mark_safr的使用方法
    • 导入模块:from django.utils.safestring import mark_safe
    • mark_safe(html代码)
  • safe的使用方法:
    • 通过管道符safe:变量|safe

mark_safe与|safe的优缺点:

  • mark_safe可直接返回大量的html标签,可用于自定义标签中,通过后端python代码返回实现
  • |safe每次只能解决一个变量的问题,在自定义标签中不能使用,通过前端模板渲染实现
7、拓展:XSS攻击(上面过滤|safe的拓展)

xss攻击:,全称跨站脚本攻击

​ Django的模板中在进行模板渲染的时候会对HTML标签和JS等语法标签进行自动转义,原因显而易见,这样是为了安全,django担心这是用户添加的数据,比如如果有人给你评论的时候写了一段js代码,这个评论一提交,js代码就执行啦,这样你是不是可以搞一些坏事儿了,写个弹窗的死循环,浏览器就不能用了,浏览器会一直弹出弹窗,这叫做xss攻击,所以浏览器中进行了一些转义处理。但是有的时候我们需要这些HTML元素不被转义,比如我们做一个内容管理系统,后台添加的文章中是经过修饰的,这些修饰可能是通过一个类似于FCKeditor编辑加注了HTML修饰符的文本,如果自动转义的话显示的就是保护HTML标签的源文件。为了在Django中关闭HTML的自动转义有两种方式,如果是一个单独的变量我们可以通过过滤器“|safe”的方式告诉Django这段代码是安全的不必转义。

8、django的UTC格林威治时间的处理

​ 在settings.py文件中,具体情况集体分析,主要修改settings配置文件的参数。

9、Template模板注意事项

在引入的javastript文件中使用模板渲染是不会生效的

​ Django对于html的Template渲染时是以字符串的形式先读取所有的html文件中的内容,然后执行模板渲染,此实javascript的导入部位代码还是字符串,文件还没有被引入,当模板渲染结束后返回给浏览器的时候,浏览器解析时异步执行javascript文件,而此时无法再进行模板渲染了,因为模板渲染是由后台执行的。浏览器并不会,也没有此功能。

​ 若想在javascript中使用模板渲染,那就不能使用文件引入的方式,只能在html文件中写javascript,并在里面使用模板渲染规则

LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'UTC'

USE_I18N = True

USE_L10N = True

USE_TZ = True
10、组件与动态组件的应用

(1)组件

组件就是一个html文件,其中封装了一些特定功能,比如就是一个导航栏,或者就是一个左侧菜单,相当我们将一些特定功能封装成了一个组件,将来任何其他html文件中如果使用这部分功能,可以直接引入使用。

在django模板渲染系统中使用组件的步骤

第一步:创建组件的html文件,名字随便取,比如叫做zujian.html,比如内容如下,做一个顶部导航栏组件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>zujian</title>
    <style>
        .nav{
            background-color: blue;
            height: 100px;
        }
        .nav a{
            color:white;

        }
    </style>
</head>
<body>

<div class="nav">
    <a href="">百度</a>
    <a href="">京东</a>
    <a href="">个人中心</a>
</div>


</body>

</html>

第二步:使用组件,需要借助下面这个标签语法

{% include '组件文件名称.html' %}

示例:比如我们需要某个html文件中使用,比如show.html文件中使用,show.html文件内容如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
<!-- <style> -->
<!-- 可以修改样式,但是注意权重问题 -->
<!--    .nav{  
            background-color: yellow!important;#}
        } 
-->
<!-- </style> -->
</head>
<body>

<h1>这是show页面</h1> 
<!-- include 'zujian.html' -->  <!-- 在哪里引入组件,组件就生成在页面的对应位置上 -->
</body>

</html>

(2)动态组件的应用

注意:在settings中的INSTALLED_APPS配置当前app,不然django无法找到自定义的标签

  1. 在app中创建templatetags文件夹(文件夹名只能是templatetags)

  2. 在templatetags文件夹中创建任意 .py 文件,如:mytag.py

  3. 在mytag.py文件中写上如下内容

from django import template
register = template.Library() #制作注册器,名字必须叫register
@register.inclusion_tag('zujian2.html')
def xiaolin(v1):
    #v1 = [11,22,33]
    return {'data': v1}
  1. 使用inclusion_tag,比如在show2.html文件中使用:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
{% load mytag %}
{% xiaolin d %}

<h1>这是show2页面</h1>
</body>

</html>
  1. 需要后台给show2.html传递数据,比如views.py文件写法如下
def show2(request):
    d = ['国产', '欧美', '日韩']
    return render(request,'show2.html',{'d': d})

五、静态文件配置

Django的常用的三种静态文件的配置方式

(1)settings配置引入静态文件

  • 在settings.py中配置

    STATICFILES_DIRS = [
        os.path.join(BASE_DIR, 'statics'),  # statics为创建的静态文件夹
    ]
    
  • 在前端文件导入的时候使用settings.py中的STATIC_URL = '/static/'的static路径引入

<!-- html代码 -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="/static/css/index.css">
</head>
<body>
<div class="c">hello world!</div>
</body>
</html>

<!-- inde.css代码 -->
.c{
    height: 100px;
    width: 100px;
    border: 1px solid black;
    background-color: aqua;
    color: aliceblue;
}
# settings中的配置

STATIC_URL = '/static/'

STATICFILES_DIRS = [
    os.path.join(BASE_DIR, 'statics'),
]

(2)template渲染模式static引入静态文件

  • 通过模板渲染在html文件头部引入
  • 引入文件方式:<link rel="stylesheet" href="{% static "文件路径" %}">
<!-- html代码 -->
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="{% static "css/index.css" %}">
</head>
<body>
<div class="c">hello world!</div>
</body>
</html>

<!-- inde.css代码 -->
.c{
    height: 100px;
    width: 100px;
    border: 1px solid black;
    background-color: aqua;
    color: aliceblue;
}

(3)template渲染模式get_static_prefix引入静态文件

  • 通过模板渲染在html文件头部引入
  • 引入文件方式:<link rel="stylesheet" href="{% get_static_prefix %}文件路径 ">
  • 注:路径和大括号之间不能有空格, %}文件路径,要无缝对接,否则无法引入文件
<!-- html代码 -->
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="{% get_static_prefix %}css/index.css ">
</head>
<body>
<div class="c">hello world!</div>
</body>
</html>

<!-- inde.css代码 -->
.c{
    height: 100px;
    width: 100px;
    border: 1px solid black;
    background-color: aqua;
    color: aliceblue;
}

六、数据库的连接

这里只讲解两种数据库的连接模式:

  • Django自带的sqlite3小型数据库,实际应用不常用,多用于测试代码时使用

  • mysql数据库的连接操作

(1)sqlite3

​ Django默认自带的数据库,直接执行初始化创建数据库即可

(2)mysql

​ mysql数据库的连接需要配通过包来实现,在Java中叫连接池,不过python把这个封装成了包,很多包都可以实现,对于django1.0版本通常使用pymysql模块来实现连接,但是对于django2.0版本以上可能会出错,这时也可以使用mysqlclient模块来连接数据库,当然还有一些其它连接数据库的模块。

​ 这里主要介绍pymysql的连接方法

  • 下载好pymysql模块后

  • 在项目主目录的__init__.py中指定

    import pymysql
    pymysql.install_as_MySQLdb()
    
  • settings文件中的DATABASES里面配置数据库的连接信息

    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.mysql',  # 数据库类型
            'NAME': 'drawer',					   # 数据库名
            'USER': 'root',						   # 用户名
            'PASSWORD': '1234',					   # 密码
            'HOST': 'localhost',				   # ip
            'PORT': '3306',						   # 端口
            'OPTIONS': {
                "init_command": "SET sql_mode='STRICT_TRANS_TABLES'",
            }  # 这个可以不写
        }
    }
    
  • 注意:连接mysql数据库的时候要现在mysql中创建好数据库,不然连接时会报错,因为djang无法自动创建数据库

(3)初始化和执行数据库

​ 初始化命令:python manage.py makemigrations

​ 执行操作命令:python manage.py migrate

(4)通过pycharm的图形界面操作来操作数据库

​ 在pycharm的右边框位置有个database在里面可以创建sqlite和MySQL的连接实现对数据库的操作

(5)还有一些关于数据库操作中的问题,后面会总结,关注本人博客即可。

七、ORM(对象关系映射)

ORM全称:object relational mapping ---- 对象 关系 映射

主要完成的就是数据库操作。

  • MVC或者MVC框架中包括一个重要的部分,就是ORM,它实现了数据模型与数据库的解耦,即数据模型的设计不需要依赖于特定的数据库,通过简单的配置就可以轻松更换数据库,这极大的减轻了开发人员的工作量,不需要面对因数据库变更而导致的无效劳动
  • ORM是“对象-关系-映射”的简称。(Object Relational Mapping,简称ORM)(将来会学一个sqlalchemy,是和他很像的,但是django的orm没有独立出来让别人去使用,虽然功能比sqlalchemy更强大,但是别人用不了)
  • 类对象--->sql--->pymysql--->mysql服务端--->磁盘,orm其实就是将类对象的语法翻译成sql语句的一个引擎,明白orm是什么了,剩下的就是怎么使用orm,怎么来写类对象关系语句。

1、model类的属性参数

比如:models.CharField(null=True,blank=True)
(1)null
	如果null=True,Django将用NULL来在数据库中存储值。默认情况null=False
(2)blank
	如果blank=True,该字段允许不填,默认blank=False
	需要注意,null与default不同;null是数据库范畴,blank是数据验证范畴
(3)dfault
	设置字段默认值,可以是一个值或者可调用对象。
	如果可调用,每有新对象被创建它都会被调用,如果字段没有设置可以为空,那么当进行添加新字段操作时要加上default默认值,否则会有操作提示,让你添加默认值。
(4)primary_key
	主键,当primary_key=True就是为当前字段添加主键。
	如果表中没有设置primary_key=True,Django会自动添加一个IntegerField字段作为主键
	当表中设置了primary_key=True,就覆盖了默认添加主键的功能
(5)unique
	唯一属性(唯一索引),当字段设置unique=True就表示该字段在整张表中必须是唯一的
(6)choices
	由二元元组组成的一个可迭代对象(例:列表,元组),用来给字段提供选择选项。
	如果设置了choices,默认的表单是一个先择框而不是一个文本框,选择框中的选项就choice中的选项值
(7)db_index
	db_index=True时代表此字段设置数据库索引
(8)auto_now_add
	auto_now_add=True,在创建数据记录的时候会把当前时间添加到数据库相应字段中
(9)auto_now
	auto_now=True,每次更新数据记录时会自动更新该字段,标识该数据最后一次的修改时间
	注:只能在save方式时触发自动更新时间的动作
(10)DatetimeField、DateField、TimeField都可以使用auto_now_add和auto_now这两个属性
1.1、models中的FIeld类对应类型和对应的mysql的数据类型对比

(1)可以在pycharm中的Project项目区中的External Libraries中的Python解释器下的site-packages文件夹中的:site-packages\django\db\backends\mysql中base.py文件查看(前提是你使用的IDE是pycharm)

(2)也可以在pip安装的django包中的路径找到对应关系的文件:/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/django/db/backends/mysql/base.py

(3)在创建的虚拟环境中的pip安装的django包中的路径找到对应关系的文件:/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/django/db/backends/mysql/base.py

(4)下面是对应关系

'AutoField': 'integer AUTO_INCREMENT',
'BigAutoField': 'bigint AUTO_INCREMENT',
'BinaryField': 'longblob',
'BooleanField': 'bool',
'CharField': 'varchar(%(max_length)s)',
'CommaSeparatedIntegerField': 'varchar(%(max_length)s)',
'DateField': 'date',
'DateTimeField': 'datetime',
'DecimalField': 'numeric(%(max_digits)s, %(decimal_places)s)',
'DurationField': 'bigint',
'FileField': 'varchar(%(max_length)s)',
'FilePathField': 'varchar(%(max_length)s)',
'FloatField': 'double precision',
'IntegerField': 'integer',
'BigIntegerField': 'bigint',
'IPAddressField': 'char(15)',
'GenericIPAddressField': 'char(39)',
'NullBooleanField': 'bool',
'OneToOneField': 'integer',
'PositiveIntegerField': 'integer UNSIGNED',
'PositiveSmallIntegerField': 'smallint UNSIGNED',
'SlugField': 'varchar(%(max_length)s)',
'SmallIntegerField': 'smallint',
'TextField': 'longtext',
'TimeField': 'time',
'UUIDField': 'char(32)',
1.2、字段的choices属性

(1)使用

class Book(models.Model):
		...
  	# sex = models.CharField(max_length=12)
    sex_choices = ((1, '男性'),(0, '女性'))  #enum枚举 单选
    sex = models.IntegerField(choices=sex_choices, default=1)

(2)获取含有choices属性的字典数据方法

ret = models.Book.objects.get(id=5)
print(ret.sex)  #  1 获取到的是数据库中存储的字段数据
print(ret.get_sex_display())  # 男性 -- 能够帮我们获取到该字段数据对应的choices指定的元祖中的这个数据对应的文本内容
    # sex_choices = ((1, '男性'), (0, '女性'))  # enum枚举 单选
    # sex = models.IntegerField(choices=sex_choices, default=1)
    # 比如拿sex这个字段来说,数据库中存的数据是1,表示男性,如果我想获取到男性这个字符串数据,我直接通过模型类对象.get_sex_display()这个方法就能获取到,这个方法的语法是get_模型类属性名称_display()
1.3、auto_now_add和auto_now

(1)使用

class ShowTime(models.Model):
    id = models.AutoField(primary_key=True)
    name = models.CharField(max_length=32)
    brithday = models.DateTimeField(auto_now_add=True)  # 添加记录时,自动添加创建时间
    bday = models.DateTimeField(auto_now=True)  # 添加记录时,也能自动添加创建时间,并且更新记录时,自动更新为当前时间

(2)操作

    # models.DateTimeField(auto_now=True)中的auto_now=True对update方法没有效果
    # models.ShowTime.objects.all().update(
    #     name='chao2',
    # )

    # auto_now=True只有在使用save方法来进行更新动作时,才生效,才会自动更新修改时间
    ret = models.ShowTime.objects.get(id=1)
    ret.name = 'zhen'
    ret.save()

2、ORM单表增删改查

2.1、增
  • 添加单条数据

    • 第一种添加方式

      obj = models.表(字段1=值1,字段2=值2,...)

      obj.save() # 一定要执行save否则不会执行插入数据操作

    • 第二种添加方式

      obj = models.表.objects.create(字段1=值1,字段2=值2,...)

    • 注意点:create添加完数据后会返回插入数据的类对象,通过这个类对象:对象.属性 可以获取到对应字段的数据

  • 添加多条数据

    • 通过bulk_create

      obj_list = []
      for i in range(1,10):
          obj = models.Book(
              title=f'水浒传{i}',
              state=True,
      
              pub_date=f'2018-11-{i}',
              price=11.11 + i,
              publish='出版社'
          )
          obj_list.append(obj)
      
      models.Book.objects.bulk_create(obj_list)
      
    • 也可以通过字典的方式添加多条数据,使用 **kwargs

2.2、删
  • 删除delete方法
# 调用者可以是model对象,也可以是QuerySet类型数据
    # 调用者可以是model对象,可以是querset类型数据
    # obj = models.Book.objects.get(id=3).delete()
    # print(obj) # (1, {'app01.Book': 1})

    # 返回结果其实还是受影响的行数

    # obj = models.Book.objects.filter(publish='出版社').delete()
    # print(obj)  # (2, {'app01.Book': 2})
    
    # 错误示例演示
    # obj = models.Book.objects.delete()
    # 报错'Manager' object has no attribute 'delete'
    # 控制器没有delete方法,原因就是怕你一下子全部删除了所有数据
    # 如果想删除所有数据,
2.3、改
  • 第一种:模型对象修改数据:get

    obj = models.Book.objects.get(id=1)
    # print(obj)
    obj.title = 'lol冠军之路'
    obj.price = 20
    obj.save()
    布尔值类型保存数据时,数据库中保存的是数字:1--True  0--False
    mysql中的bool使用tinyint(0)和tinyint(1)来表示的
    
  • 第二种:QuerySet对象结果集:update

    • 注:update方法,调用者可以是objects控制器,可以是queryset类型数据,但是不能是模型类对象
    (1)objects调用,下面示例是整列数据更新
    models.Book.objects.update(
        state=False,
        title='xx',
    )
    
    (2)queryset类型数据调用,下面也是更新整列数据
    models.Book.objects.all().update(
        state=True,
        # title='xx',
    )
    
    (3)queryset类型数据调用,更新部分记录数据
    # obj = models.Book.objects.filter(publish='刘伟出版社').update(
    #     # state=True,
    #     title='红楼懵',  # 别奇怪,就叫红楼懵,难道你看红楼梦,不懵吗?
    # )
    # print(obj) # 2 返回结果为受影响的行数
    
    (4)模型类对象不能直接调用update方法,错误示例如下
    models.Book.objects.get(id=1).update(
    	title='xx',
    )
    报错信息'Book' object has no attribute 'update'
    
2.4、查
  • 查询功能的13个API接口

    1. all()

      • all查询所有的数据,返回结果为QuerySet类型数据,QuerySet类似于列表,里面存放的是model类的实例化对象,是一个对象结果集,每个对象表示一条记录,对象中的对应数据有着该行记录的字段数据
      • QuerySet类似于列表,但是比列表还多一些其他的功能,这是orm给咱们封装出来的新的数据类型
    2. filter()

      • 过滤查询,结果为QuerySet类型数据,里面存放的是model类的实例化对象,是一个对象结果集,每一项也是模型类对象
      • 当查不到任何内容时,返回空的queryset
    3. get(**kwargs)

      • 过滤查询,结果有且只能有一条数据,结果不是QuerySet数据类型,是模型类对象数据,model类的实例化对象
      • 查询结果不止一个,多于一个了,就报这个错误类似,其中的部分错误数据是根据表中数据变化的
        get() returned more than one Book -- it returned 9!
      • 查询不到任何数据时,也会报错,错误如下,其中的表名是根据操作的表来决定的
        Book matching query does not exist.
      • get无法和update串联操作使用
      • 示例:
        Book.objects.get(id=1)
        models.Book.objects.all().get(id=8)
    4. exclude(**kwargs)

      • 排除的情况,包含了与所给筛选条件不匹配的对象
      • orm映射中没有不等于的操作,可以通过exclude来操作,也可以使用Q查询来操作
      • exclude的返回值类型是QuerySet数据类型,调用者可以是QuerySet类型数据,或者object控制器
      • 示例:
        Book.objects.exclude(id=6),返回id不等于6的所有的对象,
        或者在queryset基础上调用,Book.objects.all().exclude(id=6)
    5. order_by(*field)

      • 对查询结果进行排序,返回值是QuerySet类型数据,调用者可以是queryset类型数据,也可以是objects控制器

      • 除了使用order_by进行查询数据后排序,还可以在models.py中进行Meta属性设置默认排序规则,不过要注意此操作会对distinct去重造成影响

        方式1:
        	order_by(*field)方法进行排序
        方式: 在模型类中通过Meta类来执行排序规则
        class Book(models.Model):
        
            id = models.AutoField(primary_key=True)  # 自增、主键
            title = models.CharField(max_length=64,null=True)
            state = models.BooleanField(default=True)
            pub_date = models.DateField(null=True)
            # price = models.DecimalField(max_digits=20,decimal_places=5,null=True)
            price = models.DecimalField(max_digits=8,decimal_places=2,null=True)
            publish = models.CharField(max_length=32)
        
            def __str__(self):
                return self.title + '价格' + str(self.price)
        
            class Meta:
                ordering = ['id',]  #制定了它之后,所有的本表的查询结果,都按照id进行升序排列,还可进行多天剑排序规则的指定
        
        reverse()翻转,必须在上面两者的基础上,才能进行结果顺序翻转
        
      • 示例:
        models.Book.objects.order_by('price') # 获取所有数据,并且按照price字段升序排列
        models.Book.objects.order_by('-price') # 获取所有数据,并且按照price字段降序排列
        models.Book.objects.all().order_by('-price') # queryset类型数据调用

      • 多条排序示例:
        models.Book.objects.all().order_by('price','id') # 直接写price,默认是按照price升序排列,按照字段降序排列,就写个负号就行了order_by('-price'),order_by('price','id')是多条件排序,按照price进行升序,price相同的数据,按照id进行升序

    6. reverse()

      • 对查询结果反向排序,可以是QuerySet类型的数据来调用,也可以是objects控制器调用
      • 示例:
        obj_list = models.Book.objects.all().order_by('id').reverse()
        obj_list = models.Book.objects.reverse() # 注意:如果每给Book模型类执行Meta中的ordering属性,那么reverse()默认是不生效的。具体看官方文档的详细介绍
    7. count()

      • 返回数据库中匹配查询的(QuerySet)的对象数量,结果是个数字,可以QuerySet类型的数据来调用,也可以是objects控制器调用
      • 示例:
        obj_list = models.Book.objects.count() # 默认统计的整表的所有数据量
        obj_list = models.Book.objects.all().count()
    8. first()

      • 返回第一条记录对象,结果得到的都是model对象,不是queryset,可以QuerySet类型的数据来调用,也可以是objects控制器调用
      • 示例:
        Book.objects.all().first() #同:Book.objects.all()[0]
        Book.objects.first()
    9. last()

      • 返回最后一条记录对象,结果得到的都是model对象,不是queryset,可以QuerySet类型的数据来调用,也可以是objects控制器调用
      • 示例:
        Book.objects.all().last() #同:Book.objects.all()[-1] ,但是负数索引取值会报错,错误信息为: Negative indexing is not supported. queryset类型数据,不支持负数索引取值的形式
        Book.objects.last()
    10. exists()

      • 如果QuerySet包含数据,就返回True,否则返回False,可以QuerySet类型的数据来调用,也可以是objects控制器调用

      • 示例:
        obj_list = models.Book.objects.exists() # 判断表中是否有数据
        obj_list = models.Book.objects.filter(id=100).exists() # 判断查询结果集中是否有数据,有得到True,没有得到False

      • 还有个点需要注意一下:

          空的queryset类型数据布尔值为False,但是一般不用它来判断数据库里面是不是有数据,如果有大量的数据,你用它来判断,那么就需要查询出所有的数据,效率太差了,用count或者exits

      • 示例:
        'select * from book where name="xx";'
        if obj_list: # 会将满足条件的所有数据进行一次查询,效率低

        ​ select count(id) from book where name='xx';

        ​ if obj_list.count(): # 效率较高,按照查询结果对象的id值进行个数统计,

        ​ select id from book where name='xx' limit 1; # 查询一条数据,不用扫描所有数据

        ​ if obj_list.exists(): # 效率高

          例:all_books = models.Book.objects.all().exists() # 翻译成的sql是SELECT (1) AS a FROM app01_book LIMIT 1,就是通过limit 1,取一条来看看是不是有数据

    11. values(*filed)

      • 返回一个特殊的QuerySet,运行后得到的并不是一系列model的实例化对象,返回的queryset类型,里面的元素是字典数据,既然是queryset类型数据,那么就可以继续链式调用QuerySet类型的其他的查找方法,其他方法也是一样的;可以QuerySet类型的数据来调用,也可以是objects控制器调用

      • 示例:

        ​ obj_list = models.Book.objects.values() # 默认获取的表中所有记录的字典数据,字典中的键是字段名称(模型类属性名称),值是每个字段的对应数据

        # obj_list = models.Book.objects.all().values()
        # 取指定字段数据
        obj_list = models.Book.objects.all().values('id','title') 
        # <QuerySet [{'id': 5, 'title': '水浒传2'}, {'id': 6, 'title': '西游记'},...]>
        
    12. values_list(*filed)

      • 它与values()非常相似,它返回的是一个包含元组queryset序列,values返回的是一个包含字典QuerySet序列;可以QuerySet类型的数据来调用,也可以是objects控制器调用

      • 示例:

        ​ obj_list = models.Book.objects.all().values_list('id','title')
        ​ 结果:#<QuerySet [(5, '水浒传2'), (6, '西游记'),...]>

        ​ 不太好的一点,就是数据不知道对应的是哪个字段

    13. distinct()

      • 去重,values和values_list得到的queryset类型的数据来调用,从返回结果中剔除重复记录

      • 注意点:

        class Book(models.Model):
        
            id = models.AutoField(primary_key=True) #自增、主键
        		...
            publish = models.CharField(max_length=32)
        
            def __str__(self):
                return self.title + '价格' + str(self.price)
        		
            
            class Meta:
                ordering = ['id','publish',]
        
        
        obj_list = models.Book.objects.all().order_by('publish').values('publish').distinct()
        
        
        # 当模型类中制定了默认排序字段,那么当我们使用distinct方法进行去重时,默认会按照我们指定的排序字典进行去重,会导致去重结果不是我们想要的,所以要么我们在模型类中不指定排序字段,如果制定了排序字段,我们在使用distinct方法前面加上order_by方法,并且order_by方法中的字段就是我们要去重的字段数据
        
        官方文档中的下面这种写法
        Entry.objects.order_by('pub_date').distinct('pub_date')
        只适用于PostgreSQL数据库,mysql不支持distinct方法里面写字段
        
  • 基于双下划线的模糊查询
    Book.objects.filter(price__in=[100,200,300]) # price值等于这三个里面的任意一个的对象
    	示例:
      	ret = models.Book.objects.filter(price__in=['20.11', '1.5'])  # 针对decimal字段要用字符串
        ret = models.Book.objects.filter(price2__in=[20, 18])  # 针对float或者int类型要用数字
    
    Book.objects.filter(price__gt=100)  # 大于,大于等于是price__gte=100,别写price>100,这种参数不支持
    
    Book.objects.filter(price__lt=100)  # 小于,小于等于是price__lte=100
    
    Book.objects.filter(price__range=[100,200])  # sql的between and,大于等于100,小于等于200
    
    # 针对一些字符串数据操作:
    # 找到包含某些字符串的数据
    Book.objects.filter(title__contains="python")  # title值中包含python的
    Book.objects.filter(title__icontains="python")  # 不区分大小写
    
    # 找到以某些字符串开头或者结尾的数据
    Book.objects.filter(title__startswith="py")  # 以什么开头,istartswith  不区分大小写
    Book.objects.filter(title__istartswith='p')  
    Book.objects.filter(title__endswith="py")  # 以什么结尾,iendswith  不区分大小写
    Book.objects.filter(title__iendswith='p') 
    
    # 日期时间类型数据的操作
    # 按照年份查
    Book.objects.filter(pub_date__year=2012)
    Book.objects.filter(pub_date__year='2012')
    
    # 查某年某月的
    models.Book.objects.filter(pub_date__year=2018,pub_date__month=12)
    
    # 查某年某月某日
    models.Book.objects.filter(pub_date__year=2018,pub_date__month=11,pub_date__day=7)
    
    # 只查某月
    models.Book.objects.filter(pub_date__month=7)
    
    # 查询某个字段为空的数据
    models.Book.objects.filter(title__isnull=True) #正规的
    models.Book.objects.filter(title=None)  
    
    # 原生sql语句:
    # SELECT `app01_book`.`id`, `app01_book`.`title`, `app01_book`.`state`, `app01_book`.`pub_date`, `app01_book`.`price`, `app01_book`.`price2`, `app01_book`.`publish`, `app01_book`.`sex` FROM `app01_book` WHERE `app01_book`.`title` IS NULL ORDER BY `app01_book`.`id` ASC, `app01_book`.`publish` ASC
    

    查看某个orm语句的原生sql语句方法

    print(models.Book.objects.filter(title__isnull=True).query)
    
2.5、动态操作
  • 更新或添加:Update_or_create()

    • 有就更新,没有就添加update_or_create()

      # 有就更新的操作示例:
      models.Book.objects.update_or_create(
          id=6,
          defaults={
              'title':'西游记',
              'state':True,
              'pub_date':'2018-11-11',
              'price':1.5,
              'publish':'武大郎出版社'
          }
      )
      
      # 没有就创建(添加)的示例:
      models.Book.objects.update_or_create(
          id=100,
          defaults={
              'title': '红楼梦',
              'state': True,
              'pub_date': '2018-11-11',
              'price': 1.5,
              'publish': '武大郎出版社'
          }
      )
      
      # 查询为多条记录的演示示例:报错,因为update_or_create内部进行查询时,使用的是get方法查询
      models.Book.objects.update_or_create(
      	publish='武大郎出版社',
          defaults={
              'title': '红楼梦2',
              'state': True,
              'pub_date': '2018-11-11',
              'price': 1.5,
              'publish': '武大郎出版社'
          }
      )
      
  • 查询或添加:get_or_create()

    • 查询或创建,能够查询到就返回查询结果,查询不到就添加记录,查询时内部使用的还是get方法

      # 查询到结果的示例:
      ret = models.Book.objects.get_or_create(
          id=100,
          defaults={
              'title': '西游记',
              'state': True,
              'pub_date': '2018-11-11',
              'price': 1.5,
              'publish': '武大郎出版社'
          }
      )
      print(ret)  # (<Book: 红楼梦>, False)  # 如果没创建,那么返回结果元组中的第二个元素为Flase
      
      # 查询不到,自动添加记录的示例
      ret = models.Book.objects.get_or_create(
          id=102,
          defaults={
              'title': '西游记',
              'state': True,
              'pub_date': '2018-11-11',
              'price': 1.5,
              'publish': '武大郎出版社'
          }
      )
      print(ret)  # (<Book: 西游记>, True)  # 如果创建了新纪录,那么返回结果元祖中的第二个元素为True
      
2.6、关键字传参的两种方式:
方式1
filter(id=5, publish='出版社')
create(id=5, publish='出版社')
...

方式2
filter(**{'id':5, 'publish':'出版社'})
...

3、ORM多表操作增删改

3.1、表的基本结构
from django.db import models

# Create your models here.

class Author(models.Model):
    """作者表"""
    # id = models.AutoField(primary_key=True)
    # 其实模型类中的id主键字段不需要我们手动指定,django的orm默认会给每张表都添加一个id字段并且为主键,如果我们自己指定了主键,以我们自己指定的为准,就不会自动帮你添加主键字段了
    name = models.CharField(max_length=32)
    age = models.IntegerField()
    # ad = models.ForeignKey(to='AuthorDetail',to_field='id',on_delete=models.CASCADE)
    # ad = models.ForeignKey('AuthorDetail', on_delete=models.CASCADE)  # 只是单纯的外键关系,需要手动指定唯一约束才可以,在orm中提供了一对一关系的类,叫做OneToOneField
    ad = models.OneToOneField('AuthorDetail') #foreign + unique
    # books = models.ManyToManyField('Book')
    
    # models.ForeignKey(AuthorDetail)这里面的外键关联的表,可以不写引号,但是如果不写引号,那么这个外键关联的表必须在写这个外键字段的表上面,一般我们都写上引号,这样就不用考虑哪个表写在上面,哪个表写在下面了
    # ad = models.ForeignKey(AuthorDetail, on_delete=models.CASCADE)
    # to=可以用写,to_field='id'也可以不用写,自动找到是to=那张表的id字段
    # django 1.11 版本的foreignkey 默认有on_delete=models.CASCADE这个属性,所以如果要做这种级联删除模式,可以不用写这个属性,但是django2.0之后,必须要自己手动加上这个属性

    # 关于级联,级联有很多,这里用到的就是下面这个,还有些其他的下面会讲解
    # models.CASCADE 级联删除  # 没办法设置级联更新,要做级联更新,自己通过原生sql去加上,就是修改表

class AuthorDetail(models.Model):
    """作者详细信息表"""
    birthday = models.DateField()
    telephone = models.CharField(max_length=24)
    address = models.CharField(max_length=64)
    #通过手机号查找某人  手机号151开头

class Publish(models.Model):
    name = models.CharField(max_length=64)
    city = models.CharField(max_length=64)

class Book(models.Model):
    title = models.CharField(max_length=64)
    pub_date = models.DateField()
    price = models.DecimalField(max_digits=10,decimal_places=2)
    pub = models.ForeignKey('Publish')
    # ForeignKey这个类,生成数据库字段的时候,会自动将该属性名称_id作为我们的数据库字段名称
    authors = models.ManyToManyField('Author')
    # authors在执行数据库同步指令之后,不会生成字段,而是会帮我们生成一个第三张表,这个表就是书籍表和作者表的多对多关系表

# class BookToAuthor(models.Model):
额外拓展:**{}打散和**request.POST打散
print(request.POST.dict())  # dict()方法能将QueryDict类型数据转换为普通字典类型数据

# 传数据时,可以用**{}打散的方式来传输入,但是如果碰到models中有decimal类型的字段数据,那么update更新时,会对提交的数据进行decimal类型数据转换,
# 发现有Decimal数据要存储,会将提交的数据转换为Decimal类型来存储,所有个数据类型强转的过程,导致如果我们直接写**request.POST,会报错,所以引入了request.POST.dict()这个方法,其实如果说不涉及到强制类型转换失败的请求,参数直接写**request.POST就可以
obj_list.update(
  **request.POST.dict()
)

<!-- 模型类中的pk属性能够自动帮我们找到模型类中的主键字段 -->

<form action="/edit_book/{{ id }}/" method="post">
<form action="{% url 'edit_book' id %}" method="post">
<form action="{% url 'edit_book' obj.id %}" method="post">
<form action="{% url 'edit_book' obj.pk %}" method="post">
3.2、一对一
  1. 创建一对一关系字段的一些属性

    to
        设置要关联的表。
    
    to_field
        设置要关联的字段。
        
    on_delete
        同ForeignKey字段。这个是级联属性,django2.0之后需要手动添加,之前默认就有的内置属性
    
  2. 一对一关系表的增删改查操作

    • # 一对一
          # 如果使用的是模型类的关系属性名称来添加数据,那么属性名称对应的值必须是关联表中的某条记录的模型类对象
          # au_obj = models.AuthorDetail.objects.get(id=1)
          # models.Author.objects.create(
          #     name='eric',
          #     age=26,
          #     ad=au_obj  # 其实存在数据库中的还是au_obj的id值
          # )
      
          # 如果使用数据库表字段的形式来创建关系记录数据,那么需要使用数据库中表字段名称来指定数据(用的居多)
          # au_obj = models.AuthorDetail.objects.get(id=1)
          # models.Author.objects.create(
          #     name='pdd',
          #     age=16,
          #     ad_id=2  # 直接使用关联表中的某条记录的id值的方式
          # )
      
    • #删除
          # delete方法
          # models.Author.objects.filter(id=1).delete()
          # models.AuthorDetail.objects.filter(id=1).delete()
          # 一对一和一对多删除一样
      
    • # 修改
          # 在一对一和一对多关系时,和单表操作是一样的
          # 一对一
          # models.Author.objects.filter(id=1).update(name='xx',ad=模型类对象)
          # models.Author.objects.filter(id=1).update(name='xx',ad_id=2)
      
          # pub_obj = models.Publish.objects.get(id=2)
          # 一对多
          # models.Book.objects.filter(id=1).update(pub=pub_obj)
          # models.Book.objects.filter(id=2).update(title='第二部',pub_id=2)
      
    • 查询功能内容比较多,后面分出一块详细补充

3.3、一对多(多对一)
  1. 创建一对多关系字段的一些参数

    to
        设置要关联的表
    
    to_field
        设置要关联的表的字段
    
    related_name
        反向操作时,使用的字段名,用于代替原反向查询时的'表名_set'。
        
    related_query_name
        反向查询操作时,使用的连接前缀,用于替换表名。
    
    on_delete
        当删除关联表中的数据时,当前表与其关联的行的行为。
    
  2. 一对多(多对一)表的增删改查

    • # 一对多
          # book表和publish表是多对一的关系
          # 添加记录和上面的一对一一样
          # 写法1
          # publish_obj = models.Publish.objects.get(id=1)
          # models.Book.objects.create(
          #     title='红楼梦',
          #     pub_date='2008-09-09',
          #     price=88.88,
          #     pub=publish_obj  # 某个出版社的模型类对象
          # )
          # 写法2
          # models.Book.objects.create(
          #     title='金鳞',
          #     pub_date='2008-09-09',
          #     price=82.82,
          #     pub_id=1  # 某个出版社记录的id值
          # )
      
    • #删除
          # delete方法
          # models.Author.objects.filter(id=1).delete()
          # models.AuthorDetail.objects.filter(id=1).delete()
          # 一对一和一对多删除一样
      
    • # 修改
          # 在一对一和一对多关系时,和单表操作是一样的
          # 一对一
          # models.Author.objects.filter(id=1).update(name='xx',ad=模型类对象)
          # models.Author.objects.filter(id=1).update(name='xx',ad_id=2)
      
          # pub_obj = models.Publish.objects.get(id=2)
          # 一对多
          # models.Book.objects.filter(id=1).update(pub=pub_obj)
          # models.Book.objects.filter(id=2).update(title='第二部',pub_id=2)
      
    • 查询功能内容比较多,后面分出一块详细补充

3.4、多对多
  1. 创建多对多关系字段的一些参数

    ### 多对多的参数:
    to
    	设置要关联的表
    
    related_name
    	同ForeignKey字段。
    
    related_query_name
    	同ForeignKey字段。
    	
    through
    	在使用ManyToManyField字段时,Django将自动生成一张表来管理多对多的关联关系。
    
    ### 但我们也可以手动创建第三张表来管理多对多关系,此时就需要通过        
    through来指定第三张表的表名。
    
    through_fields
    	设置关联的字段。
    
    db_table
    	默认创建第三张表时,数据库中表的名称。       
    	示例:authors = models.ManyToManyField('Author',db_table='xx')
    
  2. 创建第三张表时的三种方式

    • 方式1

      手动创建第三张表(没办法使manytomanyfield提供的操作第三张表数据时的方法)
      # 想操作第三张,就需要自己写sql或者直接对第三张表来添加数据,
      # 比如models.Author2Book.objects.create(author_id=1,book_id=1,xx='oo')
        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")
            xx = models.CharField(max_length=32)  # 拓展字段
            class Meta:
                unique_together = ("author", "book")
      
    • 方式2

      # 中介模型,orm提供的有些方法可以用,有些用不了,比如add添加数据的方法
      # 手动创建第三张表,并通过ManyToManyField来指定一下这个关系表
      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)的外键名。
      
      # 比如author_obj的id为1
      author_obj.books.add(1,2)
      '''
      Author2Book
      id author_id  book_id  xx
      1   1          1       怎么添加
      2   1          2       怎么添加  add方法搞不定
      '''
      class Author2Book(models.Model):
          author = models.ForeignKey(to="Author")
          book = models.ForeignKey(to="Book")
          # 可以扩展其他的字段了
          xx = models.CharField(max_length=32)  # 拓展字段
          class Meta:
              unique_together = ("author", "book")
      
    • 方式3

      通过ManyToManyField自动生成第三张表
      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")  #自动生成的第三张表我们是没有办法添加其他字段的
      
  3. 多对多表的增删改查

    • # 多对多 
          # 作者表和书籍表是多对多关系
          # title1  --  name1和name2
          # book_obj = models.Book.objects.create(
          #     title='title1',
          #     pub_date='2020-07-10',
          #     price=2,
          #     pub_id=1
          # )
      
          # author_obj1 = models.Author.objects.create(
          #     name='name1',
          #     age=16,
          #     ad_id=3  # 直接使用关联表中的某条记录的id值的方式
          # )
          # author_obj2 = models.Author.objects.create(
          #     name='name2',
          #     age=16,
          #     ad_id=4  # 直接使用关联表中的某条记录的id值的方式
          # )
      
          # 方式1
          # book_obj.authors.add(author_obj1,author_obj2)  # 写对应作者的模型类对象
          # 方式2
          # book_obj.authors.add(1, 4)  # 直接写作者记录的id值
          # 方式3
          book_obj = models.Book.objects.get(id=1)
          book_obj.authors.add(*[2, 3])  # 直接写作者记录的id值
          '''
          app01_book_authors
          id  book_id  author_id
          1   3        3
          2   3        4
          '''
      
    • 改和删

      # 多对多更新和删除
          # 针对关系记录的操作
          # book_obj = models.Book.objects.get(id=1)
      
          # author_obj = models.Author.objects.get(id=2)
          # 删除remove  在多对多关系表中删除了书籍id为1的,作者id为2的记录删除
          # book_obj.authors.remove(2)  # 删除单条
          # book_obj.authors.remove(2, 3)  # 删除多条
          # book_obj.authors.remove(author_obj)  # 按照模型类对象为参数进行删除
      
          # 清空clear
          # book_obj.authors.clear()  # 将当前书籍对应的所有作者在多对多关系表中的关系记录,全部删除
      
          # 更新(修改)
          # book_obj = models.Book.objects.get(id=4)
          # book_obj.authors.set('3')  # 参数是可迭代类型数据
          # book_obj.authors.set([3, ])  # 更新多个记录
      
          # book_obj = models.Book.objects.get(id=1)
          # book_obj.authors.set([4, ])
          # set动作有两步:1 先执行clear   2 再执行add添加
      
    • 查询功能内容比较多,后面分出一块详细补充

3.5、创建模型类时的一些元信息配置
元信息
    ORM对应的类里面包含另一个Meta类,而Meta类封装了一些数据库的信息。主要字段如下:
class Author2Book(models.Model):
    author = models.ForeignKey(to="Author")
    book = models.ForeignKey(to="Book")
    class Meta:
        unique_together = ("author", "book")

db_table
    ORM在数据库中的表名默认是 app_类名,可以通过db_table可以重写表名。db_table = 'book_model'

index_together
    联合索引。

unique_together
    联合唯一索引。

ordering
    指定默认按什么字段排序。
    ordering = ['pub_date',]
    只有设置了该属性,我们查询到的结果才可以被reverse(),否则是能对排序了的结果进行反转(order_by()方法排序过的数据)
3.6、db_column指定列名称
title = models.CharField(max_length=64,db_column='xx')
author = models.ForeignKey(to="Author",db_column='ss')  
3.7、on_delete级联模式参数
on_delete
当删除关联表中的数据时,当前表与其关联的行的行为。

models.CASCADE
删除关联数据,与之关联也删除

models.DO_NOTHING
删除关联数据,引发错误IntegrityError

models.PROTECT
删除关联数据,引发错误ProtectedError

models.SET_NULL
删除关联数据,与之关联的值设置为null(前提FK字段需要设置为可空)
# pub = models.ForeignKey('Publish',on_delete=models.SET_NULL, null=True)

models.SET_DEFAULT
删除关联数据,与之关联的值设置为默认值(前提FK字段需要设置默认值)

models.SET

删除关联数据,
a. 与之关联的值设置为指定值,设置:models.SET(值)
b. 与之关联的值设置为可执行对象的返回值,设置:models.SET(可执行对象)
3.8、ForeignKey的db_contraint参数
  • 关系和约束要分清:在不加外键的时候也可以表示两个表中的关系,但是我们就不能使用ORM外键相关的方法了,所以我们单纯的将外键换成一个其他字段类型,只是单纯的存着另外一个关联表的主键值是不能使用ORM外键方法的。
  • db_constraint=False只加两者的关系,没有强制约束的效果,并且ORM外键相关的接口(方法)还能使用,所以如果将来公司让你建立外键,并且不能有强制的约束关系,那么就可以将这个参数改为False
    customer = models.ForeignKey(verbose_name='关联客户', to='Customer',db_constraint=False)

4、ORM多表操作的查询

4.0、配置日志查看sql语句
1 query
2 在settings配置文件中写上如下内容,就能够自动打印出我们执行orm语句对应的sql语句
	LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'handlers': {
        'console':{
            'level':'DEBUG',
            'class':'logging.StreamHandler',
        },
    },
    'loggers': {
        'django.db.backends': {
            'handlers': ['console'],
            'propagate': True,
            'level':'DEBUG',
        },
    }
	} 

3 通过django配置的连接mysql的管道来查看(pymysql)
from app01 import models

def add_book(request):
    '''
    添加表记录
    :param request: http请求信息
    :return:
    '''
    book_obj = models.Book(title='python',price=123,pub_date='2012-12-12',publish='人民出版社')
    book_obj.save()
    from django.db import connection  #通过这种方式也能查看执行的sql语句
    print(connection.queries)
    return HttpResponse('ok')
4.1、子查询:基于对象的跨表查询
  • 正向查询:关系属性写在哪个表里面,那么通过这个表的数据,去查询关联的另外一张表的数据,就叫做正向查询,反之就是反向查询
  • 反向查询:通过模型类名小写_set使用反向查询
# 查询
# 一对一查询
    # 正向查询使用关联属性名称
    # 查询一下**这个作者的手机号
    # author_obj = models.Author.objects.get(name='**')
    # # author_obj.ad  # 找到了author_obj关联的作者详细信息表里面对应记录
    # print(author_obj.ad.telephone) # 120

    # 反向查询
    # 反向查询用关联它的模型类的名称小写
    # 查询一下地址在上海的那个作者是谁
    # author_detail_obj = models.AuthorDetail.objects.filter(address='上海').first()
    # print(author_detail_obj.author.name)

# 一对多

    # 正向查询
    # 使用关联属性查询
    # 查询一下红楼懵是哪个出版社出版的
    # book_obj = models.Book.objects.get(title='红楼懵')
    # print(book_obj.pub.name)

    # 反向查询
    # 模型类名小写_set
    # 查询一下**出版社出版了哪些书
    # pub_obj = models.Publish.objects.get(name='**出版社')
    # pub_obj.book_set  # 可能为多条记录,所以模型类名小写_set
    # 类似于objects控制器
    # print(pub_obj.book_set.all().values('title'))
    # 查询结果不会自动去重

# 多对多
    # 正向查询
    # 使用属性来查
    # 查询一下**第二部这本书是谁写的
    # book_obj = models.Book.objects.get(title='**第二部')
    # print(book_obj.authors.all().values('name'))

    # 反向查询
    # 使用模型类名小写_set
    # 查询一下**写了哪些书
    # author_obj = models.Author.objects.get(name='**')
    # print(author_obj.book_set.all().values('title'))
    
4.2、连表查询:基于双下划线的跨表查询
# 基于双下划线的跨表查询
    # 连表操作
    # select * from t2 inner join t1 on t1.t2_id=t2.id
    # select * from t1 inner join t2 on t1.t2_id=t2.id
# 一对一
    # 查询一下***这个作者的家庭住址
    # 正向操作,使用属性
    # ret = models.Author.objects.filter(name='***').values('ad__address')
    # <QuerySet [{'ad__address': '美国'}]>
    # 方向操作,表名小写
    # ret = models.AuthorDetail.objects.filter(author__name='***').values('address')
    # <QuerySet [{'address': '美国'}]>

# 一对多
    # 查询一下少年是哪个出版社出版的
    # 正向操作  使用关联属性
    # ret = models.Book.objects.filter(title='少年').values('pub__name')
    #<QuerySet [{'pub__name': '橘子成人出版社'}]>

    # 反向操作
    # ret = models.Publish.objects.filter(book__title='少年').values('name')
    #<QuerySet [{'name': '橘子成人出版社'}]>


# 多对多
    # 查询一下第二部这本书是谁写的
    # 正向操作  使用关联属性
    # ret = models.Book.objects.filter(title='第二部').values('authors__name')
    # <QuerySet [{'authors__name': '***'}, {'authors__name': 'pdd'}]>
    # ret = models.Author.objects.filter(book__title='第二部').values('name')
    # <QuerySet [{'name': '***'}, {'name': 'pdd'}]>
    # print(ret)
4.3、聚合查询
# 聚合查询aggregate
    # 统计一下所有书籍的平均价格 max min avg count sum
    # ret = models.Book.objects.aggregate(Avg('price'))
    # print(ret) # 结果是普通字典类型:{'price__avg': 43.925}
    # ret = models.Book.objects.all().aggregate(a=Avg('price'), b=Max('price'))
    # print(ret) # {'price__avg': 43.925, 'price__max': Decimal('88.88')} -- {'a': 43.925, 'b': Decimal('88.88')}
    # aggregate方法可以看为是orm语句的结束语句,结果为普通字典类型,不能在继续调用queryset或者模型类对象提供的方法了
4.4、分组查询
# 分组查询

    # 查询一下每个出版社出版书的平均价格
  
    # ret = models.Book.objects.values('pub_id').annotate(a=Avg('price')) # 只能获取到values指定的字段和统计结果数据
    
    # ret = models.Book.objects.values('pub_id','id').annotate(a=Avg('price')) # 多条件分组pub_id和id值相同的算为1组
    
    # ret = models.Book.objects.values('pub__name').annotate(a=Avg('price')) # 以出版社名称分组
    
    # print(ret)
    
    # ret = models.Publish.objects.annotate(a=Avg('book__price'))  # 返回结果是Publish的模型类对象,这个模型类对象里面包含Publish的所有属性数据,还有annotate的统计结果数据
    
    ret = models.Publish.objects.annotate(a=Avg('book__price')).values('name', 'a')
    # print(ret)
    
		# 原生sql,伪代码
    # select publish.name,Avg(book.price) from publish inner join book on publish.id=book.pub_id group by publish.id;

    # select avg(price) from book group by pub_id;
4.5、F查询
# F查询
    from django.db.models import F
    # 查询一下点赞数大于评论数的书籍
    # models.Book.objects.filter(dianzan__gt=comment)
    # obj_list = models.Book.objects.all().values()
    # a = []
    # for i in obj_list:
    #     if i.dianzan > i.comment:
    #         a.append(i)
    # print(a)
    # F查询可以用来做本表不同字段之间的一个比较
    # ret = models.Book.objects.filter(dianzan__gt=F('comment'))
    # print(ret)
    # F可以用来对本表数据进行一些统一操作(四则运算都支持)
    # 将所有的书籍上调10块钱
    models.Book.objects.all().update(price=F('price')+10)
    # models.Book.objects.all().update(price=100)
4.6、Q查询
from django.db.models import Q
#查询书名中包含少年两个字的并且评论数大于20的书籍
# ret = models.Book.objects.filter(title__contains='少年',comment__gt=20)  #filter中逗号分隔的条件,默认是and的关系
# <QuerySet [<Book: 少年2>]>

# 想进行或的关系查询需要借助到我们Q查询Q
# 查询书名中包含少年两个字的或者评论数大于20的书籍Q
# ret = models.Book.objects.filter(Q(title__contains='少年') | Q(comment__gt=20))

# 查询书名中包含少年两个字的或者评论数大于20的,并且点赞数大于等于80的
# ret = models.Book.objects.filter(Q(title__contains='少年') | Q(comment__gt=20), dianzan__gte=80)
# 注意,如果结合逗号来进行and的关系查询,那么必须将没有Q包裹的查询条件放在Q包裹的查询条件后面

#下面这种方式也可以,Q查询可以多层嵌套使用
# ret = models.Book.objects.filter(Q(Q(title__contains='少年') | Q(comment__gt=20)) & ~Q(dianzan__gte=80))  #~ 条件取反 ,&--and关系 ,|--or关系
# print(ret)

5、ORM执行原生sql语句

  • 有两种方式

    • 方式一:使用row

      ret = models.Book.objects.raw('select * from app01_book;')
      #raw只能操作前面表的数据,比如Book
          print(ret)
          for i in ret:
              print(i,type(i))
      
    • 方式二:引入django.db模块的connection

      from django.db import connection
      cursor = connection.cursor()
      cursor.execute('select * from app01_book;')
      print(cursor.fetchall())
      

6、ORM锁和事务

  • 加锁写法,必须用在事务里面
models.Book.objects.select_for_update().all() 
'''
    select * from app01_book for update;
'''
  • 事务写法:
    • 方式1:全局配置
    • 方式2:通过装饰器,给视图函数直接加装饰器
    • 方式3:通过方法,给逻辑加视图
方式1
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'mxshop',
        'HOST': '127.0.0.1',
        'PORT': '3306',
        'USER': 'root',
        'PASSWORD': '123',
        
        "ATOMIC_REQUESTS": True,  # 全局开启事务,绑定的是http请求响应整个过程
        
    },
  
}

方式2  给视图函数直接加装饰器,表示整个视图逻辑中的sql都捆绑为了一个事务操作
from django.db import transaction

# sql一旦出错,会自动回滚
@transaction.atomic
def viewfunc(request):
  	sid = transaction.savepoint()  #创建保存点
    # This code executes inside a transaction.
    do_stuff()  #事务和变量等处理逻辑是没关系的,d['xx'] = 'oo'
    ....
    
    do_other_stuff()
    transaction.savepoint_rollback(sid)  #回滚保存点
		#transaction.savepoint_commit(sid) #提交保存点
    
方式3  给逻辑加视图
from django.db import transaction

def viewfunc(request):
    # This code executes in autocommit mode (Django's default).
    do_stuff()

    with transaction.atomic():  # 加事务
        # This code executes inside a transaction.
        do_more_stuff()
        #models.Book.objects.select_for_update().all()

    do_other_stuff()
    

7、Python脚本中调用Django环境

xx.py文件,随便建一个文件,单独运行这个文件时,想获取到django的内容环境内容,需要下面的写法

import os

if __name__ == '__main__':
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "django_orm2.settings")
    import django
    django.setup()

    from app01 import models
    print(models.Book.objects.all())

八、Ajax的使用

1、ajax介绍

1.1、ajax(Asynchronous Javascript And XML)翻译成中文就是“异步Javascript和XML”。
即使用Javascript语言与服务器进行异步交互,传输的数据为XML(但传输的数据不仅是XML)
1.2、ajax还有一个最大的特点就是,当服务器响应时,不用刷新整个浏览器页面,而是可以局部刷新
这一特点给用户的感受是在不知不觉中完成请求和响应过程。
(1)与服务器异步交互
(2)浏览器页面局部刷新
1.3、同步交互与异步交互
(1)同步交互:客户端发出一个请求后,需要等待服务器响应结束后,才能发出第二个请求;
(2)异步交互:客户端发出一个请求后,无需等待服务器响应结束,就可以发出第二个请求。
注:数据的发送是暗着发送的(ajax更新数据只改一小块)
优点:
ajax使用Javascript技术向服务器发送异步请求;
ajax无需刷新整个页面;
因为服务器响应内容不再是整个页面,而是页面中的局部,所以ajax性能高;
缺点:
ajax并不适和所有场景,很多时候还是要使用同步交互;
ajax虽然提高了用户体验,但无形中向服务器发送的请求次数增多了,导致服务器压力增大;
因为ajax是在浏览器中使用Javascript技术完成的,所以还需要处理浏览器兼容问题;
操作:
创建核心对象;
使用核心对象打开与服务器的连接;
发送请求;
注册监听,监听服务器响应
XMLHTTPRequest
open(请求方式,URL,是否异步)
send(请求体)
onreadystate,指定监听函数,他会在xmlHTTP对象的状态发生变化时被调用
readyState,当前xmlHTTP对象的状态,其中4状态表示服务器响应结果
status,服务器响应的状态码,只有服务器响应结束时才有这个东东,200表示响应成功
requestText:获取服务器的响应体

2、ajax的使用方式

2.1、基于javascript的ajax的实现
  • step1: var xmlhttp = XMLHttprequest() # 实例化一个对象
  • step2: xmlhttp.open("") #url地址
  • step3: xmlhttp.send("name=alex") # 请求体的内容 if GET请求 :send(null)
  • step4: 监听:xmlhttp(if ==4:{var context=xmlHttp.responsetext})
GET:ajax具体实现(JS方式):
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    <button onclick="func1()">ajax提交</button>
    </body>
    <script>
        function func1(){
            // step1
            var xmlhttp=createXMLHttpRequest();
            // step4:推荐放到这个位置
            xmlhttp.onreadystatechange=function() {  //监听
                // alert(xmlhttp.status);  //返回状态码
                if(xmlhttp.readyState==4 && xmlhttp.status==200){
                    {#alert(xmlhttp.readyState);//查看状态过程#}
                    var data=xmlhttp.responseText;
                    alert(data);
                };
            }
            // step2
            xmlhttp.open("GET","/ajax/",true);  //打开服务器连接
            // step3
            xmlhttp.send(null);  //发送请求
        };
        // 处理浏览器兼容问题
        function createXMLHttpRequest(){
            var xmlHttp;
            // 适用于大多数浏览器,以及IE7和更高版本
            try{
                xmlHttp = new XMLHttpRequest();
            } catch (e){
                // 适用于IE6
                try{
                    xmlHttp = new ActiveXObject("Msxm12.XMLHTTP");
                } catch (e){
                    // 适用于IE5.5,以及更早版本
                    try{
                       xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
                    } catch (e){}
                }
            }
            return xmlHttp;
        }

    </script>
    </html>


POST:请求注意事项
    POST请求必须设置ContentType请求头的值为:application/x-www-form-urlencoded。
    表单的enctype默认值就是为application/x-www-form-urlencoded,因为默认值就是这个,
    当设置了<form>的enctype="application/x-www-form-urlencoded"时,等同于设置了Con
    tent-Type请求头。但在ajax发送请求时,就没有默认值了,这需要自行设置请求头:
    xmlHttp.setRequestHeader("Content-Type","application/x-www-form-urlencoded")
 实例:
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    <form>
        <p>用户名:<input type="text" name="username" onblur="func1(this)">
            <span id="error"></span>
        </p>
        <p>密码:<input type="password" name="pwd"></p>
        <input type="submit" value="submit">
    </form>
    </body>
    <script>
        function func1(self){
            var username=self.value;
            var xmlhttp=createXMLHttpRequest();
            xmlhttp.open("POST","/register/",true);
            xmlhttp.setRequestHeader("Content-Type","application/x-www-form-urlencoded");
            xmlhttp.send("username="+username);
            xmlhttp.onreadystatechange=function() {
                if(xmlhttp.readyState==4 && xmlhttp.status==200){
                    var s=xmlhttp.responseText;
                    if (s=="1"){
                        document.getElementById("error").innerHTML="用户名已经注册过了";
                    }
                };
            }
        }

        
// 处理浏览器兼容问题 
        function createXMLHttpRequest(){
            var xmlHttp;
            // 适用于大多数浏览器,以及IE7和更高版本 
            try{
                xmlHttp = new XMLHttpRequest();
            } catch (e){
                // 适用于IE6 
                try{
                    xmlHttp = new ActiveXObject("Msxm12.XMLHTTP");
                } catch (e){
                    // 适用于IE5.5,以及更早版本 
                    try{
                       xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
                    } catch (e){}
                }
            }
            return xmlHttp;
        }
    </script>
    </html>
2.2、基于JQuery的ajax的实现:(最底层方法:$.ajax())
  1. 最底层方法:$.ajax()

    (1)$.ajax({
            url:"/url地址/",  
            type:"POST"  // 请求方法
        })
    (2)$.ajax("url地址",{})
    
    /* 
    url 参数:
    	可以是相对路径,可以是绝对路径,浏览器不会跳到这个路径的,这是ajax请发送请求,往哪里发使用的
    	写路径时,如果后台使用的是django框架,那么url路径的后面的斜杠要加上,如果想不加上斜杠,那么需要在django的settings配置文件中加上 APPEND_SLASH = False,并且后台的urls.py文件中的路径要和ajax请求路径对应好,该有斜杠写斜杠,不需要斜杠的,去掉斜杠 
    */
    
  2. 在js代码中可以使用url别名反向解析来写路径

    $.ajax({  //
      url:'{% url "login_ajax" %}',
      type:'post',
      ...
    }),
    /*
    	但是,要注意一点,当这段js代码是写到某个js文件中,然后hmtl文件通过script标签的src属性来引入时,你的{% url "login_ajax" %}语法就不能被模板渲染了,也就是失效了
    */
    
  3. 高级方法:

    • $.get("url地址",{}):通过GET发送请求
    • $.post("url地址",{}):通过POST发送请求
    • $.getJson("url地址",{}):相当于$.get( 参数 datetype:Json)
    • $.getScript("url地址",{}):实时加载,现用现取,不用不加载
  4. 请求数据类型:data,processDate,contentType,traditional,dataType,sucess

    • data:当前ajax请求要携带的数据,是一个json的object对象,ajax方法就会默认地把它编码成某种格式(urlencoded?a=1&b=2)发送给服务端;此外,ajax默认以get方式发送求。

    • processDate:

      ​ processDate:声明当前的data数据是否进行转码或预处理,默认为true,即预处理;if为false,那么对data:{a:1,b:2}会调用json对象的toString()方法,即{a:1,b:2}.toString(),最后得到一个[object,Object]形式的结果。该属性的意义在于,当 data是一个dom结构或者xml数据时,我们希望数据不进行处理,直接发过去,就可以将其设置为true。

    • contentType:

      ​ 默认值:"application/x-www-form-urlencoded",发送信息至服务器时内容编码类型。用来指明当前请求的数据编码格式:urlencoded:?a:1&b:2;如果想以其他方式提交数据,比如contentType:"application/json",即向服务器发送一个json字符串。

    • dataType:
      当dataType已经声明为“json”时,就是告诉服务器“要回就给给我回json格式的,不然老子不要!”,而且接受到数据后将自动转换成JavaScript对象。dataType的可用值:html xml json text script

    • success:
      success参数:success:function(){} 当成功执行时执行的函数

    • complete:
      complete参数:complete:function(){} 当执行不成功的时候执行的函数
      complete会打印所有错误信息

    • beforeSend(XHR):
      类型:Function发送请求前可修改XMLHttpRequest 对象的函,如添加自定义HTTP头。XMLHttpRequerst
      对象是唯一的参数。这是一个Ajax事件。如果返回false可以取消本次ajax请求。

    • error:
      error:function(data){
      alert(data)
      }
      内部错误,指服务器的内部错误

    <!-- 实例: -->
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    <button onclick="func1()">AJAX提交</button>
    <script src="/static/jquery-3.1.1.min.js"></script>
    <script>
        function func1() {
            Text()
        }
        function Text(){
        //回调函数
            //$.post();
            //$.get("/jquery/",{name:"eric"});
            //$.post("/jquery/",{name:"eric"});
            $.post("/jquery_get/",{name:"eric"},function(data,stateText,obj){
                //console.log(arguments);//打印参数对象中的所有参数
                console.log(data);
                console.log(stateText);
                console.log(obj);
                alert(data);
            });
        }
    </script>
    </body>
    </html> 
    
    <!-- $.ajax() 实例: -->
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    <button onclick="func1()">AJAX提交</button>
    <script src="/static/jquery-3.1.1.min.js"></script>
    <script>
        function func1() {
            Text()
        }
        function Text(){
            // $.post();
            // $.get("/jquery/",{name:"eric"});
            // $.post("/jquery/",{name:"eric"});
            $.post("/jquery_get/",{name:"eric"},function(data,stateText,obj){
                // console.log(arguments);  // 打印参数对象中的所有参数
                console.log(data);
                console.log(stateText);
                console.log(obj);
                alert(data);
            });
            $.ajax({
                url:"/jquery_get/",
                type:"POST",
                // data:{a:1,b:2},
                data:{a:1,b:[3,4]},  // 迭代循环,深层循环
                traditional:true  // 禁止程序执行默认的迭代循环,深层循环
                // processData:false,
                // contextType:text,
            });
        }
    </script>
    </body>
    </html>
    
2.3、jQuery的data属性写法有很多

常用的三种:

  • 一种是:json格式,类似
  • 一种是:data: xmlDocument(必须设置:processData: false,防止自动转换)
  • 还有一种:data: "name=John&location=Boston"
要注意data和Content-Type还有datatype三者的区别
  • data是ajax向后端发送数据,书写时的格式

  • Content-Type设置发送的请求头(发送时的格式):

    Content-Type: application/x-www-form-urlencoded;  // 浏览器发送数据ajax或者form表单,默认的格式都是它
    它表示请求携带的数据格式,application/x-www-form-urlencoded对应的数据格式是:a=1&b=2
    
  • 而datatype是设置接收后端数据的格式,不是对应格式的数据拒收

2.4、JSON和JSONP

后面补充

3、ajax的实际应用

3.1、通过httprespnse响应字典类型数据
  • 第一种方式,直接使用HttpResponse回复:

    ​ 直接使用HttpResponse回复字典类型数据,那么会将字典里面元素的键都拼接成一个字符串来响应,所以不是我们想要的结果,所以我们先将字典类型数据,转换成json字符串,在通过HttpResponse来进行响应

    // 前端
    $('#btn').click(function () {
        $.ajax({
            url:'/data/',
            type:'get',
            success:function (res) {
                console.log(res,typeof res);
                // {"name": "xx", "hobby": ["女", "gay"]} string
                var res_dict = JSON.parse(res);  // 反序列化
                console.log(res_dict,typeof res_dict);
                // var hobby = res['hobby'];
                // var hobby = res_dict['hobby']; //["女", "gay"]
                var hobby = res_dict.hobby;  // ["女", "gay"]
                console.log(hobby);  // undefined
    
    # 后端views代码
    import json
    def data(request):
        data_list = {'name': 'xx', 'hobby': ['女', 'gay']}
        data_list_str = json.dumps(data_list, ensure_ascii=False)
        # return HttpResponse(data_list)  # namehobby
        return HttpResponse(data_list_str) # namehobby
    # ensure_ascii=False 解决中文字符编程unicode码的问题
    
  • 第二种方式,加上一个响应头键值对:

    ​ 通过HttpResponse回复字典数据,回复之前,加上一个响应头键值对,如下,那么ajax收到这个响应数据的时候,会查看这个响应头,发现content-type这个响应头的值为application/json,那么会自动对响应数据进行反序列化,不需要我们自己手动反序列化了

    # 方式一的基础上前端不变
    # 后端views
    import json
    def data(request):
        data_list = {'name': 'xx', 'hobby': ['女', 'gay']}	
        # 加上ret['content-type'] = 'application/json'响应头
        ret = HttpResponse(data_list_str)
        ret['content-type'] = 'application/json'
        return ret
    
  • 第三种方式,设置ajax的datatype:json:

    可以直接接收后端的接送数据类型不需要进行任何其它设置

    // 前端
    $('#btn').click(function () {
        $.ajax({
            url: '/data/',
            type: 'get',
            datatype: 'json',
            success:function (res) {
                console.log(res,typeof res);
                var res_dict = JSON.parse(res);  // 反序列化
                console.log(res_dict,typeof res_dict);
                var hobby = res_dict.hobby;  // ["女", "gay"]
                console.log(hobby);  // undefined
    
    # 后端views代码
    import json
    def data(request):
        data_list = {'name': 'xx', 'hobby': ['女', 'gay']}
        data_list_str = json.dumps(data_list)
        return HttpResponse(data_list_str) # namehobby
    # ensure_ascii=False 解决中文字符编程unicode码的问题
    
  • 第四种方式,使用JsonResponse返回数据

    JsonResponse:避免了繁琐的,1 序列化数据 2 加上['content-type'] = 'application/json'这个响应头键值对

    # 方式一的基础上前端不变
    import json
    from django.http import JsonResponse
    def data(request):
        data_list = {'name': 'xx', 'hobby': ['女', 'gay']}
        return JsonResponse(data_list)
    
3.2、通过httprespnse响应非字典类型数据
# 方式一的基础上前端不变
import json
from django.http import JsonResponse
def data(request):
    hobby_list = {'name': 'xx', 'hobby': ['女', 'gay']}
    return JsonResponse(hobby_list, safe=False)
4、ajax文件上传

后面与其他文件上传方法一起解释

九、cookie和session

1、cookie

cookie的特点:
  • Cookie大小上限为4KB;
  • 一个服务器最多在客户端浏览器上保存20个Cookie;
  • 一个浏览器最多保存300个Cookie,因为一个浏览器可以访问多个服务器。
  • cookie是明文存储在客户端的,不安全,所以一些敏感数据是不应该放到cookie里面的

  上面的数据只是HTTP的Cookie规范,但在浏览器大战的今天,一些浏览器为了打败对手,为了展现自己的能力起见,可能对Cookie规范“扩展”了一些,例如每个Cookie的大小为8KB,最多可保存500个Cookie等!但也不会出现把你硬盘占满的可能!
注意,不同浏览器之间是不共享Cookie的。也就是说在你使用IE访问服务器时,服务器会把Cookie发给IE,然后由IE保存起来,当你在使用FireFox访问服务器时,不可能把IE保存的Cookie发送给服务器。

(1)cookie是保存在浏览器端的键值对,可以用做登录

1.保存在用户浏览器
2.可以主动清除
3.可以被“伪造”
4.跨域名cookie不共享
5.浏览器设置不接收cookie

(2)服务端设置Cookie

v=datetime.datetime.utcnow() + datetime.timedelta(seconds=10)
# 设置cookie超时时间,系统默认一个时间,也可以用此方式添加一个时间,但以系统默认时间为基准。
obj.set_cookie('k1','v1',max_age=10,expires=v,path="",domain="",secure=False,httponly=False)
# max_age时间周期,给某个url生效设置cookie,如果path="/"表示全局生效。
# domain参数:用域名设置访问权限
secure参数(证书访问)执行https的cookie设置等操作的时候用ture
httponly参数:(安全)禁止了js使用时获取cookies,仅仅Http网络传输使用
获取cookie数据:.request_COOKIES.get() 

(3)cookie:

客户端可修改
服务端也可修改
保存在客户端

(4)cookie 应用:

登录认证:
      普通的cookie:
           敏感信息,不适于放在cookie中,放在数据库中,频繁操作数据库
      签名的cookie(可能会被解密):
           加密的cookie使用(签名):
              .set_signed_cookie()
           解密数据:request.get_signed_cookie()
           cookie时做认证的时候,将不敏感的信息放在cookie中,频繁操作数据库

(5)cookie操作代码示例:

# 获取cookie
	request.COOKIES.get('is_login')
  
# 设置cookie
	ret = redirect('/home/') # 302  /home/
  # ret = HttpResponse('ok')
  # ret = render(request,'home.html')
  # 设置cookie
  ret.set_cookie('is_login', True)
  ret.set_cookie('name', 'root')  # 设置同名的cookie,就是修改cookie
  return ret

# 设置签名cookie
	ret.set_signed_cookie('name', 'root',salt='sksk')  
# 获取签名cookie
	request.get_signed_cookie('name',salt='sksk')  # 以防cookie在传输过程中被人修改了
  
# 删除cookie
def logout(request):
    ret = redirect('login')
    ret.delete_cookie('is_login')
    return ret
  

(6)cookie参数代码示例:

key, 键

value='', 值

max_age=None, 超时时间  值一个数字,单位是秒

expires=None, 超时时间(IE requires expires, so set it if hasn't been already.) 值是日期时间类型数据

path='/', Cookie生效的路径,/ 表示根路径,特殊的:根路径的cookie可以被任何url的页面访问
当:path='/index',
127.0.0.1:8001/home  cookie 不生效,获取不到
127.0.0.1:8001/index/xxx/xx/ggg  生效,凡是访问/index路径下面的子路路径,都能获取到这个cookie

domain=None, Cookie生效的域名

secure=False, https传输

httponly=False 只能http协议传输,无法被JavaScript获取(不是绝对,底层抓包可以获取到也可以被覆盖)

(7)cookie值中设置中文的方法,但是实际上开发并没有这样做的

方式1涉及到了编码和解码的知识,请看博客:编码与解码

# 方式1
def login(request):

    ret = HttpResponse('ok')
    
    ret.set_cookie('k1','你好'.encode('utf-8').decode('iso-8859-1'))
    
    # 取值:request.COOKIES['k1'].encode('utf-8').decode('iso-8859-1').encode('iso-8859-1').decode('utf-8')

    return ret

# 方式2 json

def login(request):

    ret = HttpResponse('ok')
    
    import json

    ret.set_cookie('k1',json.dumps('你好'))
    # 取值 json.loads(request.COOKIES['k1'])
    return ret

(8)基于cookie的登录认证的装饰器

这里涉及python的基础知识,请看博客:装饰器和函数的闭包

示例

def check_login(f):
    def inner(request, *args, **kwargs):
        is_login = request.COOKIES.get('is_login')
        if is_login == 'True':
            ret = f(request)
            return ret
        else:
            return redirect('login')
    return inner


@check_login
def cart(request):
    # is_login = request.COOKIES.get('is_login')
    # if is_login == 'True':
    return render(request, 'cart.html')

2、session

session都是request操作的,session是保存在服务器上的键值对,依赖于cookie

针对以上特点cookie有数据大小限制,还明文存储在客户端,出现了session技术

session技术就是为了提升上面两个特点。

基于cookie技术(目前先这样里面,其实不通过cookie也能完成session技术)

session特点:

​ 1.基于cookie时,也是以键值对的形式来存的数据,但是数据是密文的,只是一把钥匙(随机字符串)

​ 2.真正的数据是保存在服务端的,将来通过cookie带过来的这个随机字符串,来找到服务端保存的对应用户的数据,数据既然是保存在服务端的,那么数据大小没有限制

注意:一个浏览器对应一个服务端,只有一个session。

(1)设置session

request.session['key'] = value
request.session.setdefault(key,value)  # 存在就不设置

(2)获取session

request.session['key']
request.session.get(key,'')

(3)删除session

request.session.pop(key)  #删除某一个键值对
del request.session['key']
request.session.delete()  #删除所有的session键值对
request.session.flush()   #删除所有的session键值对.删除了cookie

(4)设置超时时间

request.session.set_expiry()

(5)清除当前过期的session

request.session.clear_expired()
用户session的随机字符串(key):request.session.session_key
将所有session失效日期小于当前日期的数据删除:request.session.clear_expired()
检查用户session的随机字符串 在数据库中是否存在:request.session.exists("session_key")
删除当前用户的所有session数据:request.session.delete()
删除当前用户的数据并删除Session的Cookie:request.session.flush()
确保用户前面的数据不可以再次被用户的浏览器访问,调用函数:django.contrib.auth.logout()
注:设置会话Session和Cookie的超时时间
request.session.set_expiry(value)
(1).如果value是个整数,session会在些秒数后失效。
(2).如果value是个datatime或timedelta,session就会在这个时间后失效。
(3).如果value是0,用户关闭浏览器session就会失效。
(4).如果value是None,session会依赖全局session失效策略。

(6)流程:浏览器首次访问服务器的时候没有cookie,然后访问服务器,服务器会生成一个session_ID字典,

字典的值为浏览器的数据和cookie(一个随机字符串),然后返回session_ID给浏览器,下次浏览器再来的时
候携带session_ID访问服务器,可以唯一找到对应的服务器存储的session,并且打开这个字典,并从中取到值做对应校验,检验是否正确能否访问
注:一个session对应一个cookie,并且一个浏览器对应一个session数据
session存在数据库中,默认存在django_session表中,也可以存在一个文件当中,或者缓存当中,这个取决于自设定.

(7)代码示例

# 设置值
	request.session['is_login'] = True  # 等于别的值,就是修改动作
	request.session['username'] = 'root'
'''
    request.session['is_login'] = True
    做了三步事情
    1. 生成一个随机字符串,并将这个字符串添加到了cookie里面,键值对名称这样的: sessionid:ljlijsoidjoasdiog
    2. 将is_login和username这两个键值对数据{'is_login':True,'username':'root'}首先进行了json序列化,然后进行了加密,然后将加密后的数据和sessionid对应的随机值保存到了django-sesison表中。
    3. 给session保存的数据,加了有效期,默认是两周
'''
# 取值
	request.session['k1']
	is_login = request.session.get('is_login')
'''
    request.session['is_login']
    1.去cookie里面取出sessionid这个键对应的值(随机字符串)
    2.通过这个随机字符串去django-session表里面获取对应的数据,session_data那一列的数据
    3.对数据进行解密和反序列化,得到{'is_login':True,'username':'root'},然后通过键取出对应的值
'''
  
# 删除值
	request.session.flush()
  # del request.session['k1']
  
'''
    1 删除cookie中的sessionid值
    2 删除django-sesion表中保存的数据记录
'''

# 所有 键、值、键值对
request.session.keys()
request.session.values()
request.session.items()
# 获取sessionid的值
session_key = request.session.session_key

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

(8)session全局配置

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'        # 引擎

其他公用设置项:
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过期(默认)

(9)基于session的登录认证的装饰器

示例

def check_login(f):
    def inner(request, *args, **kwargs):
        is_login = request.session.get('is_login')
        if is_login:
            ret = f(request, *args, **kwargs)
            return ret
        else:
            return redirect('login')
    return inner
#使用
@check_login
def cart(request):
    return render(request, 'cart.html')

3、jQuery操作cookie

下载:http://plugins.jquery.com/cookie/

引入

<script type="text/javascript" src="js/jquery.min.js"></script>
<script type="text/javascript" src="js/jquery.cookie.js"></script>

cookie操作

设置值:
	$.cookie('the_cookie', 'the_value');
	# 设置7天有效期
	$.cookie('the_cookie', 'the_value', { expires: 7 });

读取cookie
	$.cookie('the_cookie');  # 通过键来取值
删除cookie
	$.cookie('the_cookie', null);   // 通过传递null作为cookie的值即可

缺点就是当前端进制了jquery的调度或者cookie的文件调度就会失效

十、Django中间件

1、中间件

(1)配置文件

​ 项目主目录中的配置文件是django留给开发者使用的用户级别配置文件

​ 实际上django有一个自己的默认全局配置文件。

那么他们的关系如下

# django默认配置
from django.conf import global_settings

# 用户级别配置
from django_middleware import settings


from django.conf import settings # 中间人
# 比如需要引入配置中的某个配置项settings.APPEND_SLASH,那么这样的引入的查找顺序是这样,先去用户级别settings.py文件中去找这个配合,如果没有找到,那么会去global_settings中去找默认配置

(2)Django请求的生命周期

​ 中间件用来对请求和响应做一些统一加工和处理的,比如对所有请求中的post请求做一个csrftoken认证,就用到了我们的'django.middleware.csrf.CsrfViewMiddleware',后面视图中能够使用request.session做一个session相关操作,这个request.session的功能就是在这个中间件中加工好的'django.contrib.sessions.middleware.SessionMiddleware'

(3)中间件(重要)

在Django的setting中有个MIDDLEWARE列表,里面的东西可以理解为过滤管道,里面有个安全过滤管道: from django.middleware.csrf import CsrfViewMiddleware里面有五个常用的方法:

  • process_request:客户端请求的通道,但当此方法里面有return时就不会再执行后面的方法,直接走process_response方法(表示请求数据被拦截)。注:其中使用return 会拦截掉客户端的所有请求。
  • process_views:经过process_request之后再从新从每个管道的此方法执行后面的方法(后面的方法指的是主体URL请求时所走的views方法)。
  • process_template_response:(这是第五个方法),由于使用麻烦,还有开发中实际不常用可以忽略,简单了解就行
  • process_exception:抛出异常,在process_response方法执行之前,且只抛出URL链接请求的views方法中的异常,不会抛出process_request,process_views中的异常,而且所有的异常抛出后才会从第一个process_response方法返回给客户端请求的内容。
  • process_response:请求获取到数据返回的路径。注:必有返回值 且 return 要返回固定参数。

事实上做常用到的只有:process_request和process_response这两个方法;

process_request和process_views这两个方法动作是在views.py之前执行操作的;

process_template_response这个方法动作实在views.py中通过rander和一下改变下触发的;

process_exception和process_response这两个方法的动作是在views.py执行完毕后再执行的。

中间件执行顺序流程图:
流程图

(4)自定义中间件

中间件应用场景:登录认证、权限认证、频率访问限制,请求数据统计等

自定义中间件的步骤

  1. 在应用文件夹下面创建一个文件夹,名字随便,比如叫做mymiddleware
  2. 在mymiddleware文件夹下面创建一个py文件,名称随意,比如叫做middlewares.py
  3. 在middlewares.py文件中写上如下内容
from django.utils.deprecation import MiddlewareMixin
class LoginAuth(MiddlewareMixin):
    def process_request(self,request):
        print('xxxxxx')
  1. settings.py文件中配置如下内容
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',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    
    # 配置中间件类,告诉django,我写的这个自定义中间件,你帮我使用上,一般都放到最后,不然上面几个中间件的相关功能就没办法使用上了
    'app01.mymiddleware.middlewares.LoginAuth',
  # 如果中间件功能不想用了,就注释它就行了
  # 'app01.mymiddleware.middlewares.LoginAuth'
]

这样几部搞定之后,所有的请求都会触发我们的中间件类中的process_request方法。

(5)登录认证中间件示例

中间件代码如下

# 登录认证中间件
class LoginAuth(MiddlewareMixin):
    # /login/登录请求,应该正常放行,让他能获得login.html页面
    # 白名单、黑名单
    white_list = ['/login/', ]
    def process_request(self,request):
        print('请求来啦!!!')
        # 获取当前请求路径:request.path /home/
        # 如果当前请求的路径在白名单里面,我们不进行登录认证
        if not request.path in self.white_list:

            is_login = request.session.get('is_login')  #True
            if not is_login:
                return redirect('/login/')

视图代码如下

def login(request):
    if request.method == 'GET':
        return render(request, 'login.html')
    else:
        print('xxxxx')
        uname = request.POST.get('username')
        if uname == 'root':
            request.session['is_login'] = True

            return redirect('/home/')  #告诉浏览器向/home/发送get请求获取页面
        else:
            return redirect('/login/')


def home(request):
    print('home')
    return render(request, 'home.html')


def index(request):
    print('index')
    return render(request, 'index.html')
(6)五个方法的详细执行流程
  1. process_request(self,request)

    # 示例代码
    class Md1(MiddlewareMixin):
        def process_request(self,request):
            print('MD1--process_request')
            # 当process_request返回的是None,那么才会继续执行后面的中间件的process_request,如果你是最后一个中间件,并且你返回的是None,那么逻辑会继续执行到我们的url路由控制器
            # 但是当process_request里面return的是一个HttpResponse对象,那么后面的中间件的process_request将不再执行,也不会走到url路由控制器
            return HttpResponse('中间件md1的逻辑,没有通过!!!!')
    
    class Md2(MiddlewareMixin):
        def process_request(self, request):
            print('MD2--process_request')
    
  2. process_view(self, request, view_func, view_args, view_kwargs)

    # 示例代码
    from django.shortcuts import render, redirect, HttpResponse
    from django.utils.deprecation import MiddlewareMixin
    
    # 登录认证中间件
    class LoginAuth(MiddlewareMixin):
        # /login/登录请求,应该正常放行,让他能获得login.html页面
        # 白名单、黑名单
        white_list = ['/login/', ]
        def process_request(self,request):
            print('请求来啦!!!')
            # 获取当前请求路径:request.path /home/
            # 如果当前请求的路径在白名单里面,我们不进行登录认证
            if not request.path in self.white_list:
                is_login = request.session.get('is_login')  #True
                if not is_login:
                    return redirect('/login/')
    
            # True,None
            # if is_login:
            #     return None
            # else:
            #     return redirect('/login/')
    
    class Md1(MiddlewareMixin):
    
        def process_request(self,request):
            print('MD1--process_request')
            # return HttpResponse('中间件md1的逻辑,没有通过!!!!')
    
        def process_response(self, request, response):
    
            print('Md1--响应')
            return response
        def process_view(self, request, view_func, view_args, view_kwargs):
            print('md1---view')
    
    class Md2(MiddlewareMixin):
    
        def process_request(self, request):
            print('MD2--process_request')
    
        def process_response(self, request, response):
            print('Md2--响应')
            return response
    
        def process_view(self, request, view_func, view_args, view_kwargs):
            '''
            :param request:
            :param view_func:   此次请求要执行的视图函数对象
            :param view_args:  要函数的参数
            :param view_kwargs: 要函数的参数
            :return:
            '''
            print('md2---view')
            # print(view_func, view_args, view_kwargs)
            print(view_func.__name__) # home
    
    
    
  3. process_template_response(self,request,response)

    # 代码示例
    from django.shortcuts import render, redirect, HttpResponse
    from django.utils.deprecation import MiddlewareMixin
    
    class Md1(MiddlewareMixin):
    
        def process_request(self,request):
            print('MD1--process_request')
            # return HttpResponse('中间件md1的逻辑,没有通过!!!!')
    
        def process_response(self, request, response):
    
            print('Md1--响应')
            return response
        def process_view(self, request, view_func, view_args, view_kwargs):
            print('md1---view')
    
        def process_exception(self, request, exception):
            print('md1--exception')
    
        def process_template_response(self, request, response):
            print("MD1 中的process_template_response")
            return response
    
    class Md2(MiddlewareMixin):
    
        def process_request(self, request):
            print('MD2--process_request')
    
        def process_response(self, request, response):
            print('Md2--响应')
            return response
    
        def process_view(self, request, view_func, view_args, view_kwargs):
            print('md2---view')
    
        def process_exception(self, request, exception):
    
            print('md2--exception')
    
        def process_template_response(self, request, response):
            print("MD2 中的process_template_response")
            return response
    
    # 视图代码
    def home(request):
        print('home')
        def render():
            print('你好render')
            return HttpResponse('你好response')
    
        ret = HttpResponse('ojbk')
        # 必须给HttpResponse对象封装一个render属性等于一个render函数,才能出发中间件里面的process_template_response方法
        ret.render = render
        return ret
    
  4. process_exception(self, request, exception)

    作用:当视图函数出现错误或者异常时,自动触发执行的方法,能够捕获视图出现的错误,进行一些错误的统一处理

    # 示例代码
    from django.shortcuts import render, redirect, HttpResponse
    from django.utils.deprecation import MiddlewareMixin
    
    # 登录认证中间件
    class LoginAuth(MiddlewareMixin):
        # /login/登录请求,应该正常放行,让他能获得login.html页面
        # 白名单、黑名单
        white_list = ['/login/', ]
        def process_request(self,request):
            print('请求来啦!!!')
            # 获取当前请求路径:request.path /home/
            # 如果当前请求的路径在白名单里面,我们不进行登录认证
            if not request.path in self.white_list:
    
                is_login = request.session.get('is_login')  #True
                if not is_login:
                    return redirect('/login/')
    
            # True,None
            # if is_login:
            #     return None
            # else:
            #     return redirect('/login/')
    
    class Md1(MiddlewareMixin):
    
        def process_request(self,request):
            print('MD1--process_request')
            # return HttpResponse('中间件md1的逻辑,没有通过!!!!')
    
        def process_response(self, request, response):
    
            print('Md1--响应')
            return response
        def process_view(self, request, view_func, view_args, view_kwargs):
            print('md1---view')
    
        def process_exception(self, request, exception):
            print('md1--exception')
    
    class Md2(MiddlewareMixin):
    
        def process_request(self, request):
            print('MD2--process_request')
    
        def process_response(self, request, response):
            print('Md2--响应')
            return response
    
        def process_view(self, request, view_func, view_args, view_kwargs):
            '''
            :param request:
            :param view_func:   此次请求要执行的视图函数对象
            :param view_args:  要函数的参数
            :param view_kwargs: 要函数的参数
            :return:
            '''
            print('md2---view')
            # print(view_func, view_args, view_kwargs)
            # print(view_func.__name__) # home
    
        def process_exception(self, request, exception):
            
            from django.core.exceptions import PermissionDenied
            # print('>>>>>>', exception, type(exception))
            # if isinstance(exception,PermissionDenied):
            #     return HttpResponse('权限不够!!!!!')
            #>>>>>> 权限不够 <class 'django.core.exceptions.PermissionDenied'>
            # >>>>>> 出错啦!!!
            print('md2--exception')
    
    
    
    # 视图函数示例
    from django.core.exceptions import PermissionDenied
    
    def home(request):
        print('home')
        # raise Exception('出错啦!!!')
        raise PermissionDenied('权限不够')
        return render(request, 'home.html')
    
  5. process_response(self, request, response)

    注意:当中间件1的process_request方法return的是一个HttpResponse对象,那么会执行中间件1的process_response方法,不会去执行中间件2 的方法了

    class Md1(MiddlewareMixin):
    
        def process_request(self,request):
            print('MD1--process_request')
            # 当process_request返回的是None,那么才会继续执行后面的中间件的process_request,如果你是最后一个中间件,并且你返回的是None,那么逻辑会继续执行到我们的url路由控制器
            # 但是当process_request里面return的是一个HttpResponse对象,那么后面的中间件的process_request将不再执行,也不会走到url路由控制器
            return HttpResponse('中间件md1的逻辑,没有通过!!!!')
    
        def process_response(self, request, response):
            '''
    
            :param request:   当前请求对象
            :param response:   视图函数的响应对象
            :return:
            '''
            print('Md1--响应')
            # print(response) #<HttpResponse status_code=200, "text/html; charset=utf-8">
            # 对响应内容做统一处理
            # response['xx'] = 'oo'  #统一加响应头
            # print(response.content) #b'ok'
    
            # 注意,如果你写了process_response方法,那么这个方法必须return response
            return response
    
    class Md2(MiddlewareMixin):
    
        def process_request(self, request):
            print('MD2--process_request')
    
        def process_response(self, request, response):
            print('Md2--响应')
            # if response.content != b'ok':
                # 如果process_response方法里面return了一个HttpResponse对象,这个对象会覆盖视图返回来的HttpResponse对象
                # return HttpResponse('响应不ok!!!')
            return response
    

2、csrf跨站请求伪造

默认情况下不使用任何方法或装饰器,在Django项目配置文件setting.py文件的MIDDLEWARE的csrf管道会拦截post请求。

  • 在使用form表单发送post请求时要在表单的里面加入

    {% csrf_token %} -- 会生成一个隐藏的input标签,value属性里面放着token值,name属性值为csrfmiddlewaretoken

    # 校验过程解释
    '''
    html页面中的{% csrf_token %}
      <input type="hidden" name="csrfmiddlewaretoken" value="WVHKQeAuMS4RGqyLybryIBAfacDa1Dp7PEaB3Badv3y0fvLqydX36xAVen6z3oS4">
      
    cookie中的 
    	csrftoken:CeFG6SA8Y8hcHAX5R93sxrS37v3iFFlcvX8xjfaRHjLlgFaKRbzXVnSJbGwHHqO9
        
    django会取出提交数据部分的token值和cookie中的token值进行如下比较:
        WVHKQeAuMS4RGqyLybryIBAfacDa1Dp7PEaB3Badv3y0fvLqydX36xAVen6z3oS4 -- 前32位可以对后面的几位进行解密,解密出以secret_key1  一个字符串
        
        CeFG6SA8Y8hcHAX5R93sxrS37v3iFFlcvX8xjfaRHjLlgFaKRbzXVnSJbGwHHqO9 -- 前32位可以对后面的几位进行解密,解密出以secret_key2  一个字符串
        
        secret_key1 = secret_key2 
        
        说明:
        	 token字符串的前32位是salt, 后面是加密后的token, 通过salt能解密出唯一的secret。
        django会验证表单中的token和cookie中token是否能解出同样的secret,secret一样则本次请求合法。
        
    MIDDLEWARE = [
    		...
        'django.middleware.csrf.CsrfViewMiddleware',
    	  ...
    ]
    '''   
    源码
    def _compare_salted_tokens(request_csrf_token, csrf_token):
        # Assume both arguments are sanitized -- that is, strings of
        # length CSRF_TOKEN_LENGTH, all CSRF_ALLOWED_CHARS.
        return constant_time_compare(
            _unsalt_cipher_token(request_csrf_token),
            _unsalt_cipher_token(csrf_token),
        )
    
    def _unsalt_cipher_token(token):
        """
        Given a token (assumed to be a string of CSRF_ALLOWED_CHARS, of length
        CSRF_TOKEN_LENGTH, and that its first half is a salt), use it to decrypt
        the second half to produce the original secret.
        """
        salt = token[:CSRF_SECRET_LENGTH]
        token = token[CSRF_SECRET_LENGTH:]
        chars = CSRF_ALLOWED_CHARS
        pairs = zip((chars.index(x) for x in token), (chars.index(x) for x in salt))
        secret = ''.join(chars[x - y] for x, y in pairs)  # Note negative values are ok
        return secret
        
    
    
  • 在使用Ajax发送post请求时的4种方式

    • 方式1:直接通过ajax传输cookie信息,在HTML中写上

      • 获取csrfmiddlewaretoken的input标签value属性对应的值

      • 将token值放到请求数据部分

        $('#btn').click(function () {
            var uname = $("#uname").val();
            // 获取csrfmiddlewaretoken的input标签value属性对应的值
            var token = $('[name="csrfmiddlewaretoken"]').val();
            $.ajax({
                url:'/login/',
                type:'post',
                //将token值放到请求数据部分
                data:{uname:uname,csrfmiddlewaretoken:token},
                success:function (res) {
                    console.log(res);
                }
            })
        })
        
    • 方式2:直接通过ajax传输cookie信息,使用'{{ csrf_token }}' 这个模板渲染标签

      • 使用'{{ csrf_token }}' 这个模板渲染标签,这样就不要在我们html页面中写{% csrf_token %}了。

        $('#btn').click(function () {
            var uname = $("#uname").val();
            // 直接就能得到csrfmiddlewaretoken的input标签value属性对应的值
            var token = '{{ csrf_token }}';	
            $.ajax({
                url:'/login/',
                type:'post',
                data:{uname:uname,csrfmiddlewaretoken:token},
                success:function (res) {
                    console.log(res);
                }
            })
        })
        
    • 方式3:借助js操作cookie的方法

      • 借助js操作cookie的方法来获取到cookie中的csrftoken那个键对应的值

      • 然后将这个值组成一个请求头,放到此次请求中
        headers:

      • ''这个值'' 通过js能够获取,通过jquery也能获取,我们看一下jquery如何操作cookie

        jquery操作cookie参考:https://www.cnblogs.com/clschao/articles/10480029.html
        下载网址:http://plugins.jquery.com/cookie/
        $('#btn').click(function () {
        
                //方式3
          			// 前提:需要在html页面中写上{% csrf_token %}或者{{ csrf_token }},不然此次获取这个html页面的时候,响应中不会有这个csrftoken的cookie值 
                var uname = $("#uname").val();
                
          			// 通过js或者jquery来获取cookie中的csrftoken这个键对应的token值
                var token = $.cookie('csrftoken');  
                $.ajax({
                    url:'/login/',
                    type:'post',
                  	// 将获取到的token值放到请求头中,这个请求头键值对的的键必须是"X-CSRFToken"
                    headers:{
                        "X-CSRFToken":token,
                    },
                    // django先去获取请求数据部分的token值,获取不到,就去找一个叫做X-CSRFToken请求头键值对,他的值和cookie中的csrftoken的值要相等。
                    data:{uname:uname,},
                    success:function (res) {
                        console.log(res);
                    }
        
                })
        
            })
        
    • 方式4:这个是全局设置的

      # 在方式三方法升级版
      // 要在Ajax里面的请求头headers加入{'X-CSRFToken'$.cookie('csrftoken')} ---(要导入Jquery文件和Jq的cookies文件)如果想全局设置headers就可以使用:
      // 使用此方法就不用在ajax中再设置headers请求头
      $(function(){
          $.ajaxSetup({
              beforeSend:function(xhr){
                  xhr.setRequestHeader("X-CSRFToken",$.cookie('csrftoken'));
              }
          });
      });
      
      # 这种方式不需要cookie插件是基于方法1和方法2的升级版
      $(function(){
      	$.ajaxSetup({
          	data: {csrfmiddlewaretoken: '{{ csrf_token }}' },
      	});
      });
      
  • $.cookie('csrftoken')是cookies的csrftoken随机字符串,csrf原理和cookie相似,他会给经过的信息做标记,携带此标记才不会被拦截,$.cookie('csrftoken')的作用就是获取cookie的那个标记(csrf随机生成的字符串)

  • 由于中间件是针对全局操作的,当注释 # 'django.middleware.csrf.CsrfViewMiddleware',整个网站的链接都不会执行CSRF验证,如果要针对某个链接请求操作使用CSRF操作可以在Views文件的方法上使用装饰器:@csrf_protect(声名使用CSRF)-- 全局不使用CSRF时,某几个需要使用时 --

  • 由于中间件是针对全局操作的,当使用 'django.middleware.csrf.CsrfViewMiddleware',整个网站的链接都会执行CSRF验证,如果要针对某个链接请求禁止通过CSRF操作可以在Views文件的方法上使用装饰器:@csrf_exempt(声名禁止CSRF)-- 全局使用CSRF时,某几个不需要使用时 --

十一、Form验证

1、Model&Form&ModelForm区分
  • Model:用于用户请求数据的验证(针对性弱),但有强大的数据库操作
  • Form:强大的数据验证(用于用户请求数据)
  • ModelForm:强大的数据验证,适中的数据库操作。用于数据库操作(只针对部分操作);用于用户请求的验证(只针对部分操作)
2、Forms组件

提供了三个功能

  • 能够帮我们生成HTML标签

  • 标签中保留之前用户输入的数据

  • 数据校验

3、forms组件的使用流程
  1. 创建一个自定义的form类,代码如下

    from django import forms
    class RegisterForm(forms.Form):
        phone = forms.CharField()
        username = forms.CharField()
        password = forms.CharField()
        # 可以将label显示为中文
        phone = forms.CharField(label='手机号')
        username = forms.CharField(label='用户名')
        password = forms.CharField(label='密码')
    
  2. 视图函数

    def register(request):
    
        if request.method == 'GET':
            form = RegisterForm()  #将上面的form类进行实例化
            return render(request, 'register.html', {'form': form})
        else:
            print(request.POST)
            return HttpResponse('ok')
    
  3. html(自动生成标签的功能)

    <!-- 自动生成对应标签 -->
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    <h1>注册页面</h1>
    <form action="/register/" method="post">
        {% csrf_token %}
        <div>
            <label for="{{ form.phone.id_for_label }}">{{ form.phone.label }}</label>
            {{ form.phone }}
        </div>
        <div>
            <label for="{{ form.username.id_for_label }}">{{ form.username.label }}</label>
            {{ form.username }}
        </div>
        <div>
            <label for="{{ form.password.id_for_label }}">{{ form.password.label }}</label>
            {{ form.password }}
        </div>
        <input type="submit">
    </form>
    </body>
    </html>
    
4、保留原数据和校验功能

forms组件提供的html自动生成标签的动能,上面的使用流程的第三步就体现了这里不再赘述

forms组件示例代码

class RegisterForm(forms.Form):
    # 每个字段,默认都有一个required=True的参数,表示该字段数据不能为空
    # phone = forms.CharField(label='手机号', required=True)
    phone = forms.CharField(
        label='手机号',
        required=True,
        # 错误提示信息的自定制
        error_messages={
            'required': '小敏敏提示您,不能为空!',
        }
    )
    username = forms.CharField(label='用户名')
    password = forms.CharField(label='密码')

views.py内容如下

def register(request):

    if request.method == 'GET':
        form = RegisterForm()

        return render(request, 'register.html', {'form': form})
    else:
        print(request.POST)
        form = RegisterForm(data=request.POST)
        # 把数据交给了RegisterForm,那么在通过form来渲染标签时,就将原来的数据以默认值的形式(value='值')生成在了对应标签上
        if form.is_valid():  # 执行校验,如果所有数据都校验通过了,返回True,但凡有一个数据,没有通过校验,返回False
            print('通过校验的数据', form.cleaned_data)
            return HttpResponse('ok')

        print('错误信息>>>',form.errors)

        return render(request, 'register.html', {'form': form})

register.html内容

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
      /* 给错误信息定制一些css样式 */
        .error_msg{
            font-size: 12px;
            color:red;
        }
    </style>
</head>
<body>
<h1>注册页面</h1>
<!-- form标签的这个novalidate属性,就是告诉浏览器,别多管闲事儿,也就去掉了浏览器的那个必须填的提示信息 -->
<form action="/register/" method="post" novalidate>
    {% csrf_token %}
    <div>
        <label for="{{ form.phone.id_for_label }}">{{ form.phone.label }}</label>
        {{ form.phone }}  
        <span class="error_msg">{{ form.phone.errors.0 }}</span> <!-- 展示一个字段的错误信息 -->
    </div>
    <div>
        <label for="{{ form.username.id_for_label }}">{{ form.username.label }}</label>
        {{ form.username }}
        <span class="error_msg">{{ form.username.errors.0 }}</span>
    </div>
    <div>
        <label for="{{ form.password.id_for_label }}">{{ form.password.label }}</label>
        {{ form.password }}
        <span class="error_msg">{{ form.password.errors.0 }}</span>
    </div>
    <input type="submit">
</form>
</body>
</html>
5、Form常用字段属性与插件
(1)initial

初始值

username = forms.CharField(label='用户名',initial='张三')
# 生成input标签有个默认值
(2)widget

插件的意思,能够定制我们的标签显示效果

示例1 密文输入框
password = forms.CharField(
        label='密码',
        # type = 'password'
        # widget=forms.widgets.PasswordInput,
        # 简写,不需要写widgets.
        widget=forms.PasswordInput,
        # CharField默认的插件是TextInput
        # widget=forms.widgets.TextInput,
    )

示例2 给标签加上一些其他属性
password = forms.CharField(
        label='密码',
        #attrs={'标签属性':'属性值'}
        #widget=forms.TextInput(attrs={'class':'c1 c2',})
  			#widget=forms.PasswordInput(attrs={'class':'c1',})
  			widget=forms.TextInput(attrs={'class':'c1 c2','placeholder':'请输入密码'}),
    )

生成单选下拉框

  #sex = forms.fields.ChoiceField() fields可以不用写  
  sex = forms.ChoiceField(
        choices=((1,'女'), (2,'男')),
      	#(1,'女') 生成一个option标签,value值为1,文本内容为女
        label='性别',
        # initial=2,  #初始值

    )

单选radio框

    sex = forms.ChoiceField(
        choices=((1,'女'), (2,'男')),
        label='性别',
        # initial=2,
        
        # widget=forms.Select, #ChoiceField的默认插件
        
        # input,type='radio'的单选框
        # widget=forms.RadioSelect,
        widget=forms.RadioSelect(attrs={'class': 'c1'}),
    )

多选下拉框

hobby = forms.MultipleChoiceField(
        choices=((1,'喝酒'),(2,'抽烟'),(3,'励志')),
        label='爱好',
    )

多选checkbox框

    hobby = forms.MultipleChoiceField(
        choices=((1, '喝酒'), (2, '抽烟'), (3, '励志')),
        label='爱好',
        # widget=forms.CheckboxInput, #做单选功能的复(多)选框形式
        widget=forms.CheckboxSelectMultiple,
    )

单选功能的复选框形式

    hobby = forms.MultipleChoiceField(
        #choices=((1, '喝酒'), (2, '抽烟'), (3, '励志')),
        label='爱好',
        widget=forms.CheckboxInput, #做单选功能的复(多)选框形式
        
    )
(3)date类型
bday = forms.CharField(
        label='生日',
        widget=forms.TextInput(attrs={'type': 'date'}),
    )
(4)单选或者多选框使用数据库中的数据

方式1

    
  models中的模型类
  class Publish(models.Model):

    name = models.CharField(max_length=32)

    def __str__(self):
        return self.name
  
  form类中的字段写法
  # 生成选择框,并使用数据库中的数据
    publish = forms.ModelChoiceField(
        queryset=models.Publish.objects.all(),

    )
  会自动生成单选下拉框,并且option标签value属性对应的值,是queryset参数对应的queryset集合里面的models模型类对象的id值,option标签的文本内容是每个models模型类对象

方式2

    publish = forms.ChoiceField(
        choices=models.Publish.objects.all().values_list('id','name')  #queryset[(1,xasdf),]
    )
6、Form所有内置字段
看示例
Field
    required=True,               是否允许为空
    widget=None,                 HTML插件
    label=None,                  用于生成Label标签或显示内容
    initial=None,                初始值
    help_text='',                帮助信息(在标签旁边显示)
    # {{ form.字段名称.help_text }}
    error_messages=None,         错误信息 {'required': '不能为空', 'invalid': '格式错误'}
    validators=[],               自定义验证规则
    
    disabled=False,              是否可以编辑
    
 
 
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,                  # 查询数据库中的数据
    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类型
复制代码
7、数据校验功能
格式不对的错误信息定制
'invalid': '邮箱格式不对',
(1)简单校验示例

form类

class DataForm(forms.Form):
    # 不能为空,长度不能超过10位,不能短于4位
    username = forms.CharField(
        label='用户名',
        max_length=10,
        min_length=4,
        error_messages={
            'required':'不能为空',
            'max_length':'太长啦,受不了',
            'min_length':'太短啦,不舒服',

        }
    )

    # 可以为空
    phone = forms.CharField(
        label='手机号',
        required=False,
    )
    # 要满足邮箱格式,不能为空
    email = forms.EmailField(  #input,type='email'和input,type='text'是一样的,都是普通文本输入框

        label='邮箱',
        error_messages={
            'invalid': '邮箱格式不对',
        }
    )
    # 提交的数据不能超出选项
    sex = forms.ChoiceField(
        label='性别',
        choices=((1,'女'),(2,'男')),
        widget=forms.RadioSelect,
    )

    #form只校验类中写的属性对应的那些数据
    # {'xx':'oo'}
    # csrfmiddlewaretoken = forms.CharField()

view.py

def data(request):

    if request.method == 'GET':
        form = DataForm()
        return render(request,'data.html',{'form': form})

    else:
        form = DataForm(data=request.POST)
        if form.is_valid():
            # 校验成功之后的数据form.cleaned_data,里面不包含没有校验的数据,也就是不会校验form类中没有指定的属性对应的数据
            print(form.cleaned_data)
            # {'username': 'chao', 'phone': '', 'email': 'asdf@xx.com', 'sex': '1'}
            # todo 保存数据
            return HttpResponse('ok')
        else:
            #print(form.errors)
            return render(request,'data.html',{'form': form})

Data.html文件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>

</head>
<body>

<form action="/data/" method="post" novalidate>
    {% csrf_token %}
    <div>
        <label for="{{ form.username.id_for_label }}">{{ form.username.label }}</label>
        {{ form.username }}
        {{ form.username.errors.0 }}
    </div>
    <div>
        <label for="{{ form.phone.id_for_label }}">{{ form.phone.label }}</label>
        {{ form.phone }}
        {{ form.phone.errors.0 }}
    </div>
    <div>
        <label for="{{ form.email.id_for_label }}">{{ form.email.label }}</label>
        {{ form.email }}
        {{ form.email.errors.0 }}
    </div>
    <div>
        <label for="{{ form.sex.id_for_label }}">{{ form.sex.label }}</label>
        {{ form.sex }}
        {{ form.sex.errors.0 }}
    </div>

    <input type="submit">

</form>


</body>

</html>
(2)RegexValidator验证器

示例代码

from django.core.validators import RegexValidator

class DataForm(forms.Form):
    # 不能为空,长度不能超过10位,不能短于4位
    username = forms.CharField(
				....
        # RegexValidator('正则','不符合规则时的错误信息')
        validators=[RegexValidator(r'^a', '用户名必须以a开头'),RegexValidator(r'b$', '用户名必须以b结尾')],
(3)校验函数

示例

import re
from django.core.validators import RegexValidator
from django.core.exceptions import ValidationError
# ValidationError是django提供的一个数据检验失败的一个错误类型

# 先定义一个函数,函数中我们可以做数据的校验,如果数据校验失败,我们可以raise ValidationError('错误信息')
def mobile_match(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('手机号格式有误!')

使用

phone = forms.CharField(
        label='手机号',
        # required=False,
        validators=[mobile_match, ],
    )
        
(4)局部钩子

代码示例

class DataForm(forms.Form):
    # 不能为空,长度不能超过10位,不能短于4位
    username = forms.CharField(
        label='用户名',
        max_length=10,
        min_length=4,
    )

    # 可以为空
    phone = forms.CharField(
        validators=[mobile_match, ],
    )
		...

    # 局部钩子,能够对username这个字段的数据进行一个更高级的校验
    # 语法 clean_字段名称
    def clean_username(self):
        uname = self.cleaned_data.get('username')
        if '666' in uname:
            raise ValidationError('不能光喊6,没有用!')

        # 如果校验通过,需要return这个值
        return uname
    
    def clean_phone(self):
        pass
(5)全局钩子

示例代码

  class DataForm(forms.Form):
  
  	# 密码
    password = forms.CharField()
    # 确认密码
    confirm_password = forms.CharField()
    
  # 多个字段进行比较时,一般都是用全局钩子
    def clean(self):
        p1 = self.cleaned_data.get('password')
        p2 = self.cleaned_data.get('confirm_password')
        if p1 != p2:
            # 直接raise错误,这个错误信息保存到了全局错误信息里面
            # 也就是self.errors里面
            # raise ValidationError('两次密码不一致,你恐怕是个sz吧')
            self.add_error('confirm_password', '抱歉,两次密码不一致,请核对!')
						self.add_error('password', '抱歉,两次密码不一致,请核对!')

        # 如果校验通过,必须return self.cleaned_data
        return self.cleaned_data

源码部分

    def _clean_fields(self):
        for name, field in self.fields.items():
            # print(name, field)
            # name属性名称,field是CharField()类的实例化对象
            # value_from_datadict() gets the data from the data dictionaries.
            # Each widget type knows how to retrieve its own data, because some
            # widgets split data over several HTML fields.
            if field.disabled:
                value = self.get_initial_for_field(field, name)
            else:
                value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name))
            try:
                if isinstance(field, FileField):
                    initial = self.get_initial_for_field(field, name)
                    value = field.clean(value, initial)
                else:
                    # 将CharField中的max_length,required..
                    value = field.clean(value)
                # 将属性对应的CharField里面的简单校验规则进行了校验
                self.cleaned_data[name] = value
                #self.cleaned_data['username'] = 'abc'
                if hasattr(self, 'clean_%s' % name): #clean_username
                    value = getattr(self, 'clean_%s' % name)()
                    self.cleaned_data[name] = value
            except ValidationError as e:
                self.add_error(name, e)

简单总结

首先对所有的我们自己定义的类中的属性进行了循环,self.fields能够获取到所有的属性
循环过程中先对属性,比如username = form.Charfield(max_length=12),里面的参数校验规则进行了校验,这个校验完成之后。给self.clean_data这个字典进行了赋值self.clean_data['username'] = 'asdf'
然后对该字段的局部钩子进行了校验,在局部钩子中我们可以拿到self.clean_data['username'] 这个数据进行处理,别忘了局部钩子校验成功之后,要return 这个字段的值,然后才进入下一次循环,处理下一个属性

当上面的所有循环执行完之后,才执行我们的全局够子,全局钩子中校验成功之后别忘了return self.clean_data

静态文件配置和templates模板配置

可以在应用文件夹下创建一个static名称的文件夹,存放自己应用的静态文件
可以在应用文件夹下创建一个templates名称的文件夹,存放自己应用的html文件

django寻找html文件静态文件的查找顺序是
先找总配置中的templates或者statics文件夹,如果找不到对应文件,就去每个应用下的static或者templates去找对应文件,顺序是按照settings.py配置文件中的INDSTALL_APP中注册app的顺序进行查找,找到就返回,不在继续往下找了,所以要注意,最好在static或者templates下面创建一个以应用名为名称的文件夹,将文件放到这里面,以防文件名冲突导致的问题。

十二、ModelForm验证

modelform的使用
(1)自定义modelform的类
class BookModelForm(forms.ModelForm):

    class Meta:
        model = models.Book
        fields = '__all__'
        labels = {
            'title': '书籍名称',
            'pub': '出版社',
            'price': '价格',
            'pub_date': '出版日期',
            'authors': '作者',
        }
        error_messages = {
            'title': {'required': '不能为空', 'max_length': '不能太长,受不了'},
            'price': {'required': '不能为空', },
        }

    # 想在自己定义的forms类在初始化的时候统一加一些样式
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        for name, field in self.fields.items():
            # print(name, field)
            field.widget.attrs.update({'class': 'form-control'})

(2)在视图中使用modelform
class Edit(View):

    def get(self, request, pk):
        old_book_obj = models.Book.objects.get(pk=pk)
        # 如果不传instance=old_book_obj, 那么就是一个添加页面效果,也就是生成标签没有默认值
        # form = BookModelForm()
        form = BookModelForm(instance=old_book_obj)

        return render(request, 'edit.html',{'form': form,})

    def post(self,request,pk):
        old_book_obj = models.Book.objects.filter(pk=pk).first()
        # 如果没有传instance=old_book_obj,那么form.save()做的是添加记录的动作,
        # 如果传了instance=old_book_obj,那么form.save()就是一个修改动作
        form = BookModelForm(data=request.POST, instance=old_book_obj) #
        if form.is_valid():
            # authors_objs = form.cleaned_data.pop('authors')
            #
            # book_obj = models.Book.objects.create(
            #     **form.cleaned_data
            # )
            # book_obj.authors.add(*authors_objs)
            form.save()
            return redirect('book_list')
        else:
            return render(request, 'edit.html',{'form': form,})

(3)html中使用modelform
<form action="" method="post" novalidate>

                {% for field in form %}
                <div class="form-group {% if field.errors.0 %}has-error{% endif %}">
                    <label class="control-label" for="{{ field.id_for_label }}">{{ field.label }}</label>
                    {{ field }}
                    <span class="text-danger">{{ field.errors.0 }}</span>
                </div>
                {% endfor %}


                <button class="btn btn-success pull-right">提交</button>
            </form>
posted @ 2020-07-14 22:57  HashFlag  阅读(552)  评论(2编辑  收藏  举报