python_21(Django中间件)

第1章 中间件
1.1 介绍
1.2 种类
1.3 自定义中间件
1.4 process_request
1.4.1 注册中间件
1.5 process_response
1.6 process_view
1.7 process_exception
1.8 中间件的执行流程
1.9 中间件登录验证案例
1.10 Django请求流程
第2章 ajax
2.1 json
2.2 json格式
2.3 AJAX介绍
2.4 ajax应用场景
2.5 ajax优点
2.6 jquery实现ajax
2.7 $.ajax参数
2.8 js中实现Ajax
2.9 ajax请求如何设置csrf_token
2.9.1 方法一
2.9.2 方法二
2.10 AJAX上传文件
2.11 实战:用户名是否已被注册
2.12 序列化
2.12.1 sweetAlert项目下载地址
第3章 Django form表单
3.1 form介绍
3.2 对比传统form表单
3.3 常用字段与插件
3.3.1 initial
3.3.2 error_messages
3.3.3 passeord
3.3.4 radioSelect
3.3.5 select单选
3.3.6 多选select
3.3.7 单选checkbox
3.3.8 多选checkbox
3.4 Django Form所有内置字段
3.5 校验
3.6 bootstrap样式套用
3.7 批量添加样式
3.8 modelForm
第4章 Django认证
4.1 介绍
4.2 模块导入
4.3 auth方法
4.3.1 authenticate()
4.3.2 login(HttpRequest, user)
4.3.3 logout(request)
4.3.4 is_authenticated()
4.3.5 login_requierd()
4.3.6 create_user()
4.3.7 create_superuser()
4.3.8 check_password(password)
4.3.9 set_password(password)
4.3.10 修改密码实例
4.4 User对象的属性
4.5 拓展auth_user表

 

第1章 中间件

1.1 介绍

用于处理DJango的请求和响应的框架级别的钩子,他是一个轻量、低级别的插件系统,用于全局内改变Django的输入和输出。

本质是帮助我们在视图函数执行之前和执行之后做的一些额外操作,就是一个定义类,类中定义的几个方法

1.2 种类

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',

]

 

1.3 自定义中间件

中间件可以定义五个方法,分别是:(主要的是process_request和process_response)

n  process_request(self,request)

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

n  process_template_response(self,request,response)

n  process_exception(self, request, exception)

n  process_response(self, request, response)

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

 

例:

from django.utils.deprecation import MiddlewareMixin

 

 

class MD1(MiddlewareMixin):

 

    def process_request(self, request):

        print("MD1里面的 process_request")

 

    def process_response(self, request, response):

        print("MD1里面的 process_response")

        return response

 

1.4 process_request

当返回值为none 按正常流程继续走,交给下一个中间件处理。如果返回值是HTTPresponse对象,Djan将不执行视图函数,将响应对象返回给浏览器

 

例:多个中间件process_request执行方法:

from django.utils.deprecation import MiddlewareMixin

 

class MD1(MiddlewareMixin):

 

    def process_request(self, request):

        print("MD1里面的 process_request")

 

class MD2(MiddlewareMixin):

    def process_request(self, request):

        print("MD2里面的 process_request")

1.4.1 注册中间件

在settings.py的MIDDLEWARE配置项中注册上述两个自定义中间件:

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',

    'middlewares.MD1',  # 自定义中间件MD1

    'middlewares.MD2'  # 自定义中间件MD2

]

 

访问一个视图,输出结果:

MD1里面的 process_request

MD2里面的 process_request

app01 中的 index视图

 

把MD1和MD2的位置调换一下,输出:

MD2里面的 process_request

MD1里面的 process_request

app01 中的 index视图

 

结论:

n  中间件的process_request方法是在执行视图函数之前执行的。

n  当配置多个中间件时,会按照MIDDLEWARE中的注册顺序,也就是列表的索引值,从前到后依次执行的。

n  不同中间件之间传递的request都是同一个对象

 

1.5 process_response

它有两个参数,一个是request,一个是response,request就是上述例子中一样的对象,response是视图函数返回的HttpResponse对象。该方法的返回值也必须是HttpResponse对象

例:给上述加上process_response

from django.utils.deprecation import MiddlewareMixin

 

 

class MD1(MiddlewareMixin):

 

    def process_request(self, request):

        print("MD1里面的 process_request")

 

    def process_response(self, request, response):

        print("MD1里面的 process_response")

        return response

 

 

class MD2(MiddlewareMixin):

    def process_request(self, request):

        print("MD2里面的 process_request")

 

    def process_response(self, request, response):

        print("MD2里面的 process_response")

        return response

输出结果:

MD2里面的 process_request

MD1里面的 process_request

app01 中的 index视图

MD1里面的 process_response

MD2里面的 process_response

 

结论:

process_response方法是在视图函数之后执行的,并且顺序是MD1比MD2先执行。(此时settings.py中 MD2比MD1先注册)

多个中间件中的process_response方法是按照MIDDLEWARE中的注册顺序倒序执行的,也就是说第一个中间件的process_request方法首先执行,而它的process_response方法最后执行,最后一个中间件的process_request方法最后一个执行,它的process_response方法是最先执行。

1.6 process_view

n  process_view(self,view_func,view_args,view_kwargs)

n  参数:

n  request是 HTTPrequest对象

n  view_func是django即将使用的视图函数

       他是实际的函数对象,而不是函数的名称作为字符串

n  view_args是将传递给视图的位置参数列表

n  xview_kwargs 是将传递给视图的关键字参数的字典。

       view_args 与view_kwargs都不包含第一个视图参数(request)

Django会在调用视图函数之前调用process_view方法

例:

from django.utils.deprecation import MiddlewareMixin

 

 

class MD1(MiddlewareMixin):

 

    def process_request(self, request):

        print("MD1里面的 process_request")

 

    def process_response(self, request, response):

        print("MD1里面的 process_response")

        return response

 

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

        print("-" * 80)

        print("MD1 中的process_view")

        print(view_func, view_func.__name__)

 

 

class MD2(MiddlewareMixin):

    def process_request(self, request):

        print("MD2里面的 process_request")

 

    def process_response(self, request, response):

        print("MD2里面的 process_response")

        return response

 

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

        print("-" * 80)

        print("MD2 中的process_view")

        print(view_func, view_func.__name__)

 

访问index中的视图函数,输出

MD2里面的 process_request

MD1里面的 process_request

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

MD2 中的process_view

<function index at 0x000001DE68317488> index

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

MD1 中的process_view

<function index at 0x000001DE68317488> index

app01 中的 index视图

MD1里面的 process_response

MD2里面的 process_response

 

结论:

process_view方法是在process_request之后,视图函数之前执行的,执行顺序按照MIDDLEWARE中的注册顺序从前到后顺序执行的

1.7 process_exception

方法参数:

HTTPrequest对象

exception异常产生的Exception对象

这个方法只有在视图函数中出现异常了才执行,它返回的值可以是一个None也可以是一个HttpResponse对象。如果是HttpResponse对象,Django将调用模板和中间件中的process_response方法,并返回给浏览器,否则将默认处理异常。如果返回一个None,则交给下一个中间件的process_exception方法来处理异常。它的执行顺序也是按照中间件注册顺序的倒序执行。

 

例:

from django.utils.deprecation import MiddlewareMixin

 

 

class MD1(MiddlewareMixin):

 

    def process_request(self, request):

        print("MD1里面的 process_request")

 

    def process_response(self, request, response):

        print("MD1里面的 process_response")

        return response

 

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

        print("-" * 80)

        print("MD1 中的process_view")

        print(view_func, view_func.__name__)

 

    def process_exception(self, request, exception):

        print(exception)

        print("MD1 中的process_exception")

 

 

class MD2(MiddlewareMixin):

    def process_request(self, request):

        print("MD2里面的 process_request")

 

    def process_response(self, request, response):

        print("MD2里面的 process_response")

        return response

 

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

        print("-" * 80)

        print("MD2 中的process_view")

        print(view_func, view_func.__name__)

 

    def process_exception(self, request, exception):

        print(exception)

        print("MD2 中的process_exception")

 

如果视图函数中无异常,process_exception方法不执行。

想办法,在视图函数中抛出一个异常:

def index(request):

    print("app01 中的 index视图")

    raise ValueError("呵呵")

    return HttpResponse("O98K")

在MD1的process_exception中返回一个响应对象:

 

 

class MD1(MiddlewareMixin):

 

    def process_request(self, request):

        print("MD1里面的 process_request")

 

    def process_response(self, request, response):

        print("MD1里面的 process_response")

        return response

 

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

        print("-" * 80)

        print("MD1 中的process_view")

        print(view_func, view_func.__name__)

 

    def process_exception(self, request, exception):

        print(exception)

        print("MD1 中的process_exception")

        return HttpResponse(str(exception))  # 返回一个响应对象

看出结果:

MD2里面的 process_request

MD1里面的 process_request

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

MD2 中的process_view

<function index at 0x0000022C09727488> index

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

MD1 中的process_view

<function index at 0x0000022C09727488> index

app01 中的 index视图

呵呵

MD1 中的process_exception

MD1里面的 process_response

MD2里面的 process_response

结论:

这里并没有执行MD2的process_exception方法,因为MD1中的process_exception方法直接返回了一个响应对象。

 

1.8 中间件的执行流程

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

 

 

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

 

process_template_response和process_exception两个方法的触发是有条件的,执行顺序也是倒序。总结所有的执行流程如下

 

 

 

 

 

1.9 中间件登录验证案例

中间件版的登录验证需要依靠session,所以数据库中要有django_session表。

urls.py

from django.conf.urls import url

from django.contrib import admin

from app01 import views

 

urlpatterns = [

    url(r'^admin/', admin.site.urls),

    url(r'^login/$', views.login, name='login'),

    url(r'^index/$', views.index, name='index'),

    url(r'^home/$', views.home, name='home'),

]

 

views.py

 

from django.shortcuts import render, HttpResponse, redirect

def index(request):

    return HttpResponse('this is index')

 

def home(request):

    return HttpResponse('this is home')

 

def login(request):

    if request.method == "POST":

        user = request.POST.get("user")

        pwd = request.POST.get("pwd")

 

        if user == "alex" and pwd == "alex3714":

            # 设置session

            request.session["user"] = user

            # 获取跳到登陆页面之前的URL

            next_url = request.GET.get("next")

            # 如果有,就跳转回登陆之前的URL

            if next_url:

                return redirect(next_url)

            # 否则默认跳转到index页面

            else:

                return redirect("/index/")

    return render(request, "login.html")

login.html

<!DOCTYPE html>

<html lang="en">

<head>

    <meta charset="UTF-8">

    <meta http-equiv="x-ua-compatible" content="IE=edge">

    <meta name="viewport" content="width=device-width, initial-scale=1">

    <title>登录页面</title>

</head>

<body>

<form action="{% url 'login' %}" method="post">

    {% csrf_token %}

    <p>

        <label for="user">用户名:</label>

        <input type="text" name="user" id="user">

    </p>

    <p>

        <label for="pwd">密 码:</label>

        <input type="text" name="pwd" id="pwd">

    </p>

    <input type="submit" value="登录">

</form>

</body>

</html>

 

login.html

middlewares.py

from django.utils.deprecation import MiddlewareMixin

 

 

class AuthMD(MiddlewareMixin):

    white_list = ['/login/', ]  # 白名单

    black_list = ['/black/', ]  # 黑名单

 

    def process_request(self, request):

        from django.shortcuts import redirect, HttpResponse

 

        next_url = request.path_info

        print(request.path_info, request.get_full_path())

        # 黑名单的网址限制访问

        if next_url in self.black_list:

            return HttpResponse('This is an illegal URL')

        # 白名单的网址或者登陆用户不做限制

        elif next_url in self.white_list or request.session.get("user"):

            return

        else:

            return redirect("/login/?next={}".format(next_url))

在setting.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',

    'middlewares.AuthMD'

]

1.10 Django请求流程

 

 

第2章 ajax

2.1 json

n  JSON 指的是 JavaScript 对象表示法(JavaScript Object Notation)

n  JSON 是轻量级的文本数据交换格式

n  JSON 独立于语言 *

n  JSON 具有自我描述性,更易理解

* JSON 使用 JavaScript 语法来描述数据对象,但是 JSON 仍然独立于语言和平台。JSON 解析器和 JSON 库支持许多不同的编程语言。

2.2 json格式

["one", "two", "three"]

{ "one": 1, "two": 2, "three": 3 }

{"names": ["张三", "李四"] }

[ { "name": "张三"}, {"name": "李四"} ] 

错误实例:

{ name: "张三", 'age': 32 }         // 属性名必须使用双引号

[32, 64, 128, 0xFFF]             // 不能使用十六进制值

{ "name": "张三", "age": undefined }    // 不能使用undefined

{ "name": "张三",

  "birthday": new Date('Fri, 26 Aug 2011 07:13:10 GMT'),

  "getName":  function() {return this.name;}  // 不能使用函数和日期对象

}

2.3 AJAX介绍

AJAX(Asynchronous Javascript And XML)翻译成中文就是“异步的Javascript和XML”。即使用Javascript语言与服务器进行异步交互,传输的数据为XML(当然,传输的数据不只是XML)。

AJAX 最大的优点是在不重新加载整个页面的情况下,可以与服务器交换数据并更新部分网页内容

AJAX 不需要任何浏览器插件,但需要用户允许JavaScript在浏览器上执行。

n  同步交互:客户端发出一个请求后,需要等待服务器响应结束后,才能发出第二个请求;

n  异步交互:客户端发出一个请求后,无需等待服务器响应结束,就可以发出第二个请求

 

例:

要求:页面输入两个整数,通过Ajax传输到后端计算出结果并返回

 

<!DOCTYPE html>

<html lang="en">

<head>

  <meta charset="UTF-8">

  <meta http-equiv="x-ua-compatible" content="IE=edge">

  <meta name="viewport" content="width=device-width, initial-scale=1">

  <title>AJAX局部刷新实例</title>

</head>

<body>

 

<input type="text" id="i1">+

<input type="text" id="i2">=

<input type="text" id="i3">

<input type="button" value="AJAX提交" id="b1">

 

<script src="/static/jquery-3.2.1.min.js"></script>

<script>

  $("#b1").on("click", function () {

    $.ajax({

      url:"/ajax_add/",

      type:"GET",

      data:{"i1":$("#i1").val(),"i2":$("#i2").val()},

      success:function (data) {

        $("#i3").val(data);

      }

    })

  })

</script>

</body>

</html>

 

views.py

def ajax_demo1(request):

    return render(request, "ajax_demo1.html")

 

 

def ajax_add(request):

    i1 = int(request.GET.get("i1"))

    i2 = int(request.GET.get("i2"))

    ret = i1 + i2

    return JsonResponse(ret, safe=False)

 

urls.py

urlpatterns = [

    ...

    url(r'^ajax_add/', views.ajax_add),

    url(r'^ajax_demo1/', views.ajax_demo1),

    ...  

]

2.4 ajax应用场景

搜索引擎根据用户输入的关键字,自动提示检索关键字

注册时用户民的查重

文件框输入变化时,使用ajax技术向服务器发送一个请求,然后服务器会把查询到的结果响应给浏览器,最后把后端返回的结果展示出来

       整个过程页面没有刷新,只是刷新的页面中的局部位置而已

       当请求发出后,浏览器还可以进行其他操作,无需的能带服务器的响应

 

当输入用户名后,把光标移动到其他表单项上时,浏览器会使用AJAX技术向服务器发出请求,服务器会查询名为lemontree7777777的用户是否存在,最终服务器返回true表示名为lemontree7777777的用户已经存在了,浏览器在得到结果后显示“用户名已被注册!”

2.5 ajax优点

  • AJAX使用JavaScript技术向服务器发送异步请求;
  • AJAX请求无须刷新整个页面;
  • 因为服务器响应内容不再是整个页面,而是页面中的部分内容,所以AJAX性能高; 

 

2.6 jquery实现ajax

<!DOCTYPE html>

<html lang="zh-CN">

<head>

  <meta charset="UTF-8">

  <meta http-equiv="x-ua-compatible" content="IE=edge">

  <meta name="viewport" content="width=device-width, initial-scale=1">

  <title>ajax test</title>

  <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>

</head>

<body>

<button id="ajaxTest">AJAX 测试</button>

<script>

  $("#ajaxTest").click(function () {

    $.ajax({

      url: "/ajax_test/",

      type: "POST",

      data: {username: "Q1mi", password: 123456},

      success: function (data) {

        alert(data)

      }

    })

  })

</script>

</body>

</html>

views.py

def ajax_test(request):

    user_name = request.POST.get("username")

    password = request.POST.get("password")

    print(user_name, password)

    return HttpResponse("OK")

 

2.7 $.ajax参数

data参数中的键值对,如果值不为字符串,需要将其转换成字符串类型。

 

$("#b1").on("click", function () {

    $.ajax({

      url:"/ajax_add/",

      type:"GET",

      data:{"i1":$("#i1").val(),"i2":$("#i2").val(),"hehe": JSON.stringify([1, 2, 3])},

      success:function (data) {

        $("#i3").val(data);

      }

    })

  })

 

2.8 js中实现Ajax

var b2 = document.getElementById("b2");

  b2.onclick = function () {

    // 原生JS

    var xmlHttp = new XMLHttpRequest();

    xmlHttp.open("POST", "/ajax_test/", true);

    xmlHttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");

    xmlHttp.send("username=q1mi&password=123456");

    xmlHttp.onreadystatechange = function () {

      if (xmlHttp.readyState === 4 && xmlHttp.status === 200) {

        alert(xmlHttp.responseText);

      }

    };

  }

 

2.9 ajax请求如何设置csrf_token

2.9.1 方法一

通过获取隐藏的input标签中的csfmiddlewaretoken值,放置在data中发送

$.ajax({

  url: "/cookie_ajax/",

  type: "POST",

  data: {

    "username": "Q1mi",

    "password": 123456,

    "csrfmiddlewaretoken": $("[name = 'csrfmiddlewaretoken']").val()  // 使用jQuery取出csrfmiddlewaretoken的值,拼接到data中

  },

  success: function (data) {

    console.log(data);

  }

})

2.9.2 方法二

通过获取返回的cooki的字符串 放置在请求头中发送

$.ajax({

  url: "/cookie_ajax/",

  type: "POST",

  headers: {"X-CSRFToken": $.cookie('csrftoken')},  // 从Cookie取csrftoken,并设置到请求头中

  data: {"username": "Q1mi", "password": 123456},

  success: function (data) {

    console.log(data);

  }

})

也可以自己编写一个getCookie方法:

function getCookie(name) {

    var cookieValue = null;

    if (document.cookie && document.cookie !== '') {

        var cookies = document.cookie.split(';');

        for (var i = 0; i < cookies.length; i++) {

            var cookie = jQuery.trim(cookies[i]);

            // Does this cookie string begin with the name we want?

            if (cookie.substring(0, name.length + 1) === (name + '=')) {

                cookieValue = decodeURIComponent(cookie.substring(name.length + 1));

                break;

            }

        }

    }

    return cookieValue;

}

var csrftoken = getCookie('csrftoken');

每一次都这么写太麻烦了,可以使用$.ajaxSetup()方法为ajax请求统一设置

function csrfSafeMethod(method) {

  // these HTTP methods do not require CSRF protection

  return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));

}

 

$.ajaxSetup({

  beforeSend: function (xhr, settings) {

    if (!csrfSafeMethod(settings.type) && !this.crossDomain) {

      xhr.setRequestHeader("X-CSRFToken", csrftoken);

    }

  }

});

注意

如果使用从cookie中取csrftoken的方式,需要确保cookie存在csrftoken值。

如果你的视图渲染的HTML文件中没有包含 {% csrf_token %},Django可能不会设置CSRFtoken的cookie。

这个时候需要使用ensure_csrf_cookie()装饰器强制设置Cookie。

 

django.views.decorators.csrf import ensure_csrf_cookie

@ensure_csrf_cookie

def login(request):

    pass

2.10 AJAX上传文件

// 上传文件示例

$("#b3").click(function () {

  var formData = new FormData();

  formData.append("csrfmiddlewaretoken", $("[name='csrfmiddlewaretoken']").val());

  formData.append("f1", $("#f1")[0].files[0]);

  $.ajax({

    url: "/upload/",

    type: "POST",

    processData: false,  // 告诉jQuery不要去处理发送的数据

    contentType: false,  // 告诉jQuery不要去设置Content-Type请求头

    data: formData,

    success:function (data) {

      console.log(data)

    }

  })

})

 

2.11 实战:用户名是否已被注册??

功能:

在注册表单中,当用户填写了用户名后,把光标移开后,会自动向服务器发送异步请求。服务器返回这个用户名是否已经被注册过。

案例分析

  • 页面中给出注册表单;
  • 在username input标签中绑定onblur事件处理函数。
  • 当input标签失去焦点后获取 username表单字段的值,向服务端发送AJAX请求;
  • django的视图函数中处理该请求,获取username值,判断该用户在数据库中是否被注册,如果被注册了就返回“该用户已被注册”,否则响应“该用户名可以注册”。

 

2.12 序列化

Django内置的serializers

def books_json(request):

    book_list = models.Book.objects.all()[0:10]

    from django.core import serializers

    ret = serializers.serialize("json", book_list)

    return HttpResponse(ret)

 

SweetAlert插件

 

2.12.1 sweetAlert项目下载地址

https://github.com/lipis/bootstrap-sweetalert

 

$(".btn-danger").on("click", function () {

  swal({

    title: "你确定要删除吗?",

    text: "删除可就找不回来了哦!",

    type: "warning",

    showCancelButton: true,

    confirmButtonClass: "btn-danger",

    confirmButtonText: "删除",

    cancelButtonText: "取消",

    closeOnConfirm: false

    },

    function () {

      var deleteId = $(this).parent().parent().attr("data_id");

      $.ajax({

        url: "/delete_book/",

        type: "post",

        data: {"id": deleteId},

        success: function (data) {

          if (data.status === 1) {

            swal("删除成功!", "你可以准备跑路了!", "success");

          } else {

            swal("删除失败", "你可以再尝试一下!", "error")

          }

        }

      })

    });

})

 

 

 

 

第3章 Django form表单

3.1 form介绍

form组件的主要功能如下:

  • 生成页面可用的HTML标签
  • 对用户提交的数据进行校验
  • 保留上次输入内容

 

3.2 对比传统form表单

# 注册

views.py

def register(request):

    error_msg = ""

    if request.method == "POST":

        username = request.POST.get("name")

        pwd = request.POST.get("pwd")

        # 对注册信息做校验

        if len(username) < 6:

            # 用户长度小于6位

            error_msg = "用户名长度不能小于6位"

        else:

            # 将用户名和密码存到数据库

            return HttpResponse("注册成功")

    return render(request, "register.html", {"error_msg": error_msg})

 

login.html

<!DOCTYPE html>

<html lang="en">

<head>

    <meta charset="UTF-8">

    <title>注册页面</title>

</head>

<body>

<form action="/reg/" method="post">

    {% csrf_token %}

    <p>

        用户名:

        <input type="text" name="name">

    </p>

    <p>

        密码:

        <input type="password" name="pwd">

    </p>

    <p>

        <input type="submit" value="注册">

        <p style="color: red">{{ error_msg }}</p>

    </p>

</form>

</body>

</html>

 

使用form组件实现注册功能

views.py

定义一个RegForm类:

from django import forms

 

# 按照Django form组件的要求自己写一个类

class RegForm(forms.Form):

    name = forms.CharField(label="用户名")

    pwd = forms.CharField(label="密码")

 

再写一个视图函数:

 

# 使用form组件实现注册方式

def register2(request):

    form_obj = RegForm()

    if request.method == "POST":

        # 实例化form对象的时候,把post提交过来的数据直接传进去

        form_obj = RegForm(request.POST)

        # 调用form_obj校验数据的方法

        if form_obj.is_valid():

            return HttpResponse("注册成功")

    return render(request, "register2.html", {"form_obj": form_obj})

 

login2.html

<!DOCTYPE html>

<html lang="en">

<head>

    <meta charset="UTF-8">

    <title>注册2</title>

</head>

<body>

    <form action="/reg2/" method="post" novalidate autocomplete="off">

        {% csrf_token %}

        <div>

            <label for="{{ form_obj.name.id_for_label }}">{{ form_obj.name.label }}</label>

            {{ form_obj.name }} {{ form_obj.name.errors.0 }}

        </div>

        <div>

            <label for="{{ form_obj.pwd.id_for_label }}">{{ form_obj.pwd.label }}</label>

            {{ form_obj.pwd }} {{ form_obj.pwd.errors.0 }}

        </div>

        <div>

            <input type="submit" class="btn btn-success" value="注册">

        </div>

    </form>

</body>

</html>

结论:

看网页效果发现 也验证了form的功能:

•     前端页面是form类的对象生成的                        -->生成HTML标签功能

•     当用户名和密码输入为空或输错之后 页面都会提示        -->用户提交校验功能

•     当用户输错之后 再次输入 上次的内容还保留在input框   -->保留上次输入内容

3.3 常用字段与插件

3.3.1 initial

class LoginForm(forms.Form):

    username = forms.CharField(

        min_length=8,

        label="用户名",

        initial="张三"  # 设置默认值

    )

    pwd = forms.CharField(min_length=6, label="密码")

 

3.3.2 error_messages

class LoginForm(forms.Form):

    username = forms.CharField(

        min_length=8,

        label="用户名",

        initial="张三",

        error_messages={

            "required": "不能为空",

            "invalid": "格式错误",

            "min_length": "用户名最短8位"

        }

    )

    pwd = forms.CharField(min_length=6, label="密码")

 

3.3.3 passeord

class LoginForm(forms.Form):

    ...

    pwd = forms.CharField(

        min_length=6,

        label="密码",

        widget=forms.widgets.PasswordInput(attrs={'class': 'c1'}, render_value=True)

    )

3.3.4 radioSelect

单radio值为字符串

class LoginForm(forms.Form):

    username = forms.CharField(

        min_length=8,

        label="用户名",

        initial="张三",

        error_messages={

            "required": "不能为空",

            "invalid": "格式错误",

            "min_length": "用户名最短8位"

        }

    )

    pwd = forms.CharField(min_length=6, label="密码")

    gender = forms.fields.ChoiceField(

        choices=((1, "男"), (2, "女"), (3, "保密")),

        label="性别",

        initial=3,

        widget=forms.widgets.RadioSelect()

    )

3.3.5 select单选

class LoginForm(forms.Form):

    ...

    hobby = forms.fields.ChoiceField(

        choices=((1, "篮球"), (2, "足球"), (3, "双色球"), ),

        label="爱好",

        initial=3,

        widget=forms.widgets.Select()

    )

3.3.6 多选select

class LoginForm(forms.Form):

    ...

    hobby = forms.fields.MultipleChoiceField(

        choices=((1, "篮球"), (2, "足球"), (3, "双色球"), ),

        label="爱好",

        initial=[1, 3],

        widget=forms.widgets.SelectMultiple()

    )

 

3.3.7 单选checkbox

class LoginForm(forms.Form):

    ...

    keep = forms.fields.ChoiceField(

        label="是否记住密码",

        initial="checked",

        widget=forms.widgets.CheckboxInput()

    )

3.3.8 多选checkbox

class LoginForm(forms.Form):

    ...

    hobby = forms.fields.MultipleChoiceField(

        choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),

        label="爱好",

        initial=[1, 3],

        widget=forms.widgets.CheckboxSelectMultiple()

    )

 

关于choice的注意事项:

在使用选择标签时,需要注意choices的选项可以从数据库中获取,但是由于是静态字段 ***获取的值无法实时更新***,那么需要自定义构造方法从而达到此目的。

方法一:

 

from django.forms import Form

from django.forms import widgets

from django.forms import fields

 

 

class MyForm(Form):

 

    user = fields.ChoiceField(

        # choices=((1, '上海'), (2, '北京'),),

        initial=2,

        widget=widgets.Select

    )

 

    def __init__(self, *args, **kwargs):

        super(MyForm,self).__init__(*args, **kwargs)

        # self.fields['user'].choices = ((1, '上海'), (2, '北京'),)

        # 或

        self.fields['user'].choices = models.Classes.objects.all().values_list('id','caption')

方法二:

 

from django import forms

from django.forms import fields

from django.forms import models as form_model

 

 

class FInfo(forms.Form):

    authors = form_model.ModelMultipleChoiceField(queryset=models.NNewType.objects.all())  # 多选

    # authors = form_model.ModelChoiceField(queryset=models.NNewType.objects.all())  # 单选

                                                    

3.4 Django Form所有内置字段

Field

    required=True,               是否允许为空

    widget=None,                 HTML插件

    label=None,                  用于生成Label标签或显示内容

    initial=None,                初始值

    help_text='',                帮助信息(在标签旁边显示)

    error_messages=None,         错误信息 {'required': '不能为空', 'invalid': '格式错误'}

    validators=[],               自定义验证规则

    localize=False,              是否支持本地化

    disabled=False,              是否可以编辑

    label_suffix=None            Label内容后缀

 

 

CharField(Field)

    max_length=None,             最大长度

    min_length=None,             最小长度

    strip=True                   是否移除用户输入空白

 

IntegerField(Field)

    max_value=None,              最大值

    min_value=None,              最小值

 

FloatField(IntegerField)

    ...

 

DecimalField(IntegerField)

    max_value=None,              最大值

    min_value=None,              最小值

    max_digits=None,             总长度

    decimal_places=None,         小数位长度

 

BaseTemporalField(Field)

    input_formats=None          时间格式化  

 

DateField(BaseTemporalField)    格式:2015-09-01

TimeField(BaseTemporalField)    格式:11:12

DateTimeField(BaseTemporalField)格式:2015-09-01 11:12

 

DurationField(Field)            时间间隔:%d %H:%M:%S.%f

    ...

 

RegexField(CharField)

    regex,                      自定制正则表达式

    max_length=None,            最大长度

    min_length=None,            最小长度

    error_message=None,         忽略,错误信息使用 error_messages={'invalid': '...'}

 

EmailField(CharField)     

    ...

 

FileField(Field)

    allow_empty_file=False     是否允许空文件

 

ImageField(FileField)     

    ...

    注:需要PIL模块,pip3 install Pillow

    以上两个字典使用时,需要注意两点:

        - form表单中 enctype="multipart/form-data"

        - view函数中 obj = MyForm(request.POST, request.FILES)

 

URLField(Field)

    ...

 

 

BooleanField(Field) 

    ...

 

NullBooleanField(BooleanField)

    ...

 

ChoiceField(Field)

    ...

    choices=(),                选项,如:choices = ((0,'上海'),(1,'北京'),)

    required=True,             是否必填

    widget=None,               插件,默认select插件

    label=None,                Label内容

    initial=None,              初始值

    help_text='',              帮助提示

 

 

ModelChoiceField(ChoiceField)

    ...                        django.forms.models.ModelChoiceField

    queryset,                  # 查询数据库中的数据

    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类型

 

Django Form内置字段

 

3.5 校验

方式一:

from django.forms import Form

from django.forms import widgets

from django.forms import fields

from django.core.validators import RegexValidator

 

class MyForm(Form):

    user = fields.CharField(

        validators=[RegexValidator(r'^[0-9]+$', '请输入数字'), RegexValidator(r'^159[0-9]+$', '数字必须以159开头')],

    )

 

方式二

import re

from django.forms import Form

from django.forms import widgets

from django.forms import fields

from django.core.exceptions import ValidationError

 

 

# 自定义验证规则

def mobile_validate(value):

    mobile_re = re.compile(r'^(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]{8}$')

    if not mobile_re.match(value):

        raise ValidationError('手机号码格式错误')

 

 

class PublishForm(Form):

 

    title = fields.CharField(max_length=20,

                            min_length=5,

                            error_messages={'required': '标题不能为空',

                                            'min_length': '标题最少为5个字符',

                                            'max_length': '标题最多为20个字符'},

                            widget=widgets.TextInput(attrs={'class': "form-control",

                                                          'placeholder': '标题5-20个字符'}))

 

 

    # 使用自定义验证规则

    phone = fields.CharField(validators=[mobile_validate, ],

                            error_messages={'required': '手机不能为空'},

                            widget=widgets.TextInput(attrs={'class': "form-control",

                                                          'placeholder': u'手机号码'}))

 

    email = fields.EmailField(required=False,

                            error_messages={'required': u'邮箱不能为空','invalid': u'邮箱格式错误'},

                            widget=widgets.TextInput(attrs={'class': "form-control", 'placeholder': u'邮箱'}))

 

3.6 bootstrap样式套用

<!DOCTYPE html>

<html lang="en">

<head>

  <meta charset="UTF-8">

  <meta http-equiv="x-ua-compatible" content="IE=edge">

  <meta name="viewport" content="width=device-width, initial-scale=1">

  <link rel="stylesheet" href="/static/bootstrap/css/bootstrap.min.css">

  <title>login</title>

</head>

<body>

<div class="container">

  <div class="row">

    <form action="/login2/" method="post" novalidate class="form-horizontal">

      {% csrf_token %}

      <div class="form-group">

        <label for="{{ form_obj.username.id_for_label }}"

               class="col-md-2 control-label">{{ form_obj.username.label }}</label>

        <div class="col-md-10">

          {{ form_obj.username }}

          <span class="help-block">{{ form_obj.username.errors.0 }}</span>

        </div>

      </div>

      <div class="form-group">

        <label for="{{ form_obj.pwd.id_for_label }}" class="col-md-2 control-label">{{ form_obj.pwd.label }}</label>

        <div class="col-md-10">

          {{ form_obj.pwd }}

          <span class="help-block">{{ form_obj.pwd.errors.0 }}</span>

        </div>

      </div>

      <div class="form-group">

      <label class="col-md-2 control-label">{{ form_obj.gender.label }}</label>

        <div class="col-md-10">

          <div class="radio">

            {% for radio in form_obj.gender %}

              <label for="{{ radio.id_for_label }}">

                {{ radio.tag }}{{ radio.choice_label }}

              </label>

            {% endfor %}

          </div>

        </div>

      </div>

      <div class="form-group">

        <div class="col-md-offset-2 col-md-10">

          <button type="submit" class="btn btn-default">注册</button>

        </div>

      </div>

    </form>

  </div>

</div>

 

<script src="/static/jquery-3.2.1.min.js"></script>

<script src="/static/bootstrap/js/bootstrap.min.js"></script>

</body>

</html>

 

Django form应用Bootstrap样式简单示例

 

3.7 批量添加样式

class LoginForm(forms.Form):

    username = forms.CharField(

        min_length=8,

        label="用户名",

        initial="张三",

        error_messages={

            "required": "不能为空",

            "invalid": "格式错误",

            "min_length": "用户名最短8位"

        }

    ...

 

    def __init__(self, *args, **kwargs):

        super(LoginForm, self).__init__(*args, **kwargs)

        for field in iter(self.fields):

            self.fields[field].widget.attrs.update({

                'class': 'form-control'

            })

3.8 modelForm

form与model的终极结合

class BookForm(forms.ModelForm):

 

    class Meta:

        model = models.Book

        fields = "__all__"

        labels = {

            "title": "书名",

            "price": "价格"

        }

        widgets = {

            "password": forms.widgets.PasswordInput(attrs={"class": "c1"}),

        }

class Meta:下常用参数:

model = models.Student  # 对应的Model中的类

fields = "__all__"  # 字段,如果是__all__,就是表示列出所有的字段

exclude = None  # 排除的字段

labels = None  # 提示信息

help_texts = None  # 帮助提示信息

widgets = None  # 自定义插件

error_messages = None  # 自定义错误信息

第4章 Django认证

4.1 介绍

Django内置了强大的用户认证系统--auth,它默认使用 auth_user 表来存储用户数据。

4.2 模块导入

from django.contrib import auth

4.3 auth方法

4.3.1 authenticate()

提供了用户认证功能,即验证用户名以及密码是否正确,一般需要username 、password两个关键字参数。

如果认证成功(用户名和密码正确有效),便会返回一个 User 对象

authenticate()会在该 User 对象上设置一个属性来标识后端已经认证了该用户,且该信息在后续的登录过程中是需要的

user = auth.authenticate(request,username='theuser',password='thepassword')

4.3.2 login(HttpRequest, user)

该函数接受一个HttpRequest对象,以及一个经过认证的User对象。

该函数实现一个用户登录的功能。它本质上会在后端为该用户生成相关session数据。

例:

from django.contrib.auth import authenticate, login

  

def my_view(request):

  username = request.POST['username']

  password = request.POST['password']

  user = authenticate(request, username=username, password=password)

  if user is not None:

    login(request, user)

    # Redirect to a success page.

    ...

  else:

    # Return an 'invalid login' error message.

    ...

4.3.3 logout(request)

该函数接受一个HttpRequest对象,无返回值。

当调用该函数时,当前请求的session信息会全部清除。该用户即使没有登录,使用该函数也不会报错。

from django.contrib.auth import logout

  

def logout_view(request):

  logout(request)

  # Redirect to a success page.

4.3.4 is_authenticated()

用来判断当前请求是否通过了认证。

用法:

def my_view(request):

  if not request.user.is_authenticated():

    return redirect('%s?next=%s' % (settings.LOGIN_URL, request.path))

4.3.5 login_requierd()

auth 给我们提供的一个装饰器工具,用来快捷的给某个视图添加登录校验。

用法:

from django.contrib.auth.decorators import login_required

 

@login_required

def my_view(request):

若用户没有登录,则会跳转到django默认的 登录URL '/accounts/login/ ' 并传递当前访问url的绝对路径 (登陆成功后,会重定向到该路径)。

如果需要自定义登录的URL,则需要在settings.py文件中通过LOGIN_URL进行修改。

示例:

LOGIN_URL = '/login/'  # 这里配置成你项目登录页面的路由

4.3.6 create_user()

auth 提供的一个创建新用户的方法,需要提供必要参数(username、password)等。

用法:

 

from django.contrib.auth.models import User

user = User.objects.create_user(username='用户名',password='密码',email='邮箱',...)

4.3.7 create_superuser()

auth 提供的一个创建新的超级用户的方法,需要提供必要参数(username、password)等。

用法:

from django.contrib.auth.models import User

user = User.objects.create_superuser(username='用户名',password='密码',email='邮箱',...)

4.3.8 check_password(password)

auth 提供的一个检查密码是否正确的方法,需要提供当前请求用户的密码。

密码正确返回True,否则返回False。

用法:

ok = user.check_password('密码')

4.3.9 set_password(password)

auth 提供的一个修改密码的方法,接收 要设置的新密码 作为参数。

注意:设置完一定要调用用户对象的save方法!!!

用法:

user.set_password(password='')

user.save()

4.3.10 修改密码实例

@login_required

def set_password(request):

    user = request.user

    err_msg = ''

    if request.method == 'POST':

        old_password = request.POST.get('old_password', '')

        new_password = request.POST.get('new_password', '')

        repeat_password = request.POST.get('repeat_password', '')

        # 检查旧密码是否正确

        if user.check_password(old_password):

            if not new_password:

                err_msg = '新密码不能为空'

            elif new_password != repeat_password:

                err_msg = '两次密码不一致'

            else:

                user.set_password(new_password)

                user.save()

                return redirect("/login/")

        else:

            err_msg = '原密码输入错误'

    content = {

        'err_msg': err_msg,

    }

    return render(request, 'set_password.html', content)

4.4 User对象的属性

l  User对象属性:username, password

l  is_staff : 用户是否拥有网站的管理权限.

l  is_active : 是否允许用户登录, 设置为 False,可以在不删除用户的前提下禁止用户登录。

 

4.5 拓展auth_user表

这内置的认证系统这么好用,但是auth_user表字段都是固定的那几个,我在项目中没法拿来直接使用啊!

 

比如,我想要加一个存储用户手机号的字段,怎么办?

 

聪明的你可能会想到新建另外一张表然后通过一对一和内置的auth_user表关联,这样虽然能满足要求但是有没有更好的实现方式呢?

 

答案是当然有了。

 

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

 

这样既能根据项目需求灵活的设计用户表,又能使用Django强大的认证系统了。

from django.contrib.auth.models import AbstractUser

class UserInfo(AbstractUser):

    """

    用户信息表

    """

    nid = models.AutoField(primary_key=True)

    phone = models.CharField(max_length=11, null=True, unique=True)

   

    def __str__(self):

        return self.username

注意:

 

按上面的方式扩展了内置的auth_user表之后,一定要在settings.py中告诉Django,我现在使用我新定义的UserInfo表来做用户认证。写法如下:

 

# 引用Django自带的User表,继承使用时需要设置

AUTH_USER_MODEL = "app名.UserInfo"

再次注意:

 

一旦我们指定了新的认证系统所使用的表,我们就需要重新在数据库中创建该表,而不能继续使用原来默认的auth_user表了。

 

posted @ 2019-02-15 16:22  王晓冬  阅读(235)  评论(0编辑  收藏  举报