Django

目录

Django

1.1 HTTP协议(扩展)

1.1.1 HTTP协议介绍

超文本传输协议是一种用于分布式、协作式和超媒体信息系统的应用层协议。HTTP是万维网的数据通信的基础。

HTTP现今广泛使用的版本是HTTP 1.1。

什么是HTTP协议?

超文本传输协议,一次请求一次响应随即断开,体现出无状态的短连接;请求包含请求头和请求体,响应包含响应头和响应体。

注意:请求头或响应头中以一个\r\n进行分隔;请求头和请求体之间使用两个\r\n进行分隔(响应头和响应体一样)

1.1.2 HTTP协议概述

HTTP是一个客户端和服务器端请求和应答的标准(TCP)。通过使用网页浏览器、网络爬虫或其他工具,客户端发起一个HTTP请求到服务器上指定端口(默认80)。

通常,由HTTP客户端发起一个请求,创建一个到服务器指定端口的tcp连接。HTTP服务器则在那个端口监听客户端的请求。一旦受到请求,服务器会向客户端返回一个状态,比如“HTTP/1.1 200 OK”,以及返回的内容。

1.1.3 HTTP工作原理

HTTP协议定义web客户端如何从web服务器请求web页面,以及服务器如何把web页面传送给客户端。HTTP采用了请求/响应模型。

响应步骤:

  1. 客户端连接到web服务器
  2. 发送HTTP请求
  3. 服务器接收请求并返回HTTP响应
  4. 释放连接的tcp连接
  5. 客户端浏览器解析HTML内容

在浏览器的地址栏中输入URL,按下回车之后会经历一下流程:

  1. 浏览器向dns服务器请求解析该URL中的域名对应的公网地址
  2. 解析出IP地址后,根据IP地址和默认端口80,和服务器建立tcp连接
  3. 浏览器发出读取文件(URL中域名后面部分对应的文件)的HTTP请求,该请求报文作为tcp三次握手的第三个报文的数据发送给服务器。
  4. 服务器对浏览器请求作出响应,并把对应的HTML文本发送给浏览器
  5. 释放TCP连接
  6. 浏览器展示HTML内容

1.1.4 HTTP的特点

  • 无状态

HTTP是一种不保存状态。HTTP协议自身不对请求和响应之间的通信状态进行保存。也就是说在HTTP这个级别协议对于发送过的请求和响应都不做持久化处理。

  • 无连接

无连接的含义是限制每次连接只处理一个请求。无连接有两种方式:早起的HTTP协议是在一个请求一个响应之后,直接就断开了,但是现在HTTP/1.1 版本断开之前会有几秒的等待时间,等待用户有后续的操作。

1.1.5 HTTP请求方法

  • GET:向指定的资源发出“显示”请求
  • HEAD:与GET方法一样,都是向服务器发出指定资源的请求,只不过服务器将不传回资源的文本部分。好处在于使用这个方法可以在不必传输全部内容的情况下就可以获取其中的元信息。
  • POST:向指定资源提交数据,请求服务器进行处理。数据被包含在请求文本汇总。
  • PUT:向指定资源位置上传内容
  • DELETE:请求服务器删除Request-URL所标识的资源
  • TRACE:回显服务器收到的请求,主要用于测试
  • OPTIONS:服务器回传资源所支持的所有HTTP请求方法
  • CONNECT:预留给能够将连接改为管道方式的代理服务器。通常用于SSL加密服务器。

注意:

  1. 方法名称区分大小写。当某个请求所针对的资源不支持对应的请求方法的时候,服务器会返回状态码405,当服务器不识别或不支持对应的请求方法,返回状态码501.
  2. HTTP服务器至少应该实现GET和POST方法。

GET和POST方法的区别:

  • GET提交的数据会放在URL之后,也就是请求行里面,以?分割URL和传输数据,参数之间以&相连,如EditBook?name=test1&id=123456. POST方法是把提交的数据放在HTTP包的请求体中.
  • GET提交的数据大小有限制(因为浏览器对URL的长度有限制),而POST方法提交的数据没有限制.
  • GET与POST请求在服务端获取请求数据方式不同,就是我们自己在服务端取请求数据的时候的方式不同了。

1.1.6 状态码

类别 说明
1xx 信息性状态码 接收的请求正在处理
2xx 成功状态码 请求正常处理完毕
3xx 重定向状态码 需要进行附加操作以完成请求
4xx 客户端错误状态码 服务器无法处理请求
5xx 服务器错误状态码 服务器处理请求出错

1.1.7 URL说明

超文本传输协议(HTTP)的统一资源定位符将从因特网获取信息的五个基本元素包括在一个简单的地址中:

  • 传送协议。
  • 层级URL标记符号(为[//],固定不变)
  • 访问资源需要的凭证信息(可省略)
  • 服务器。(通常为域名,有时为IP地址)
  • 端口号。(以数字方式表示,若为HTTP的默认值“:80”可省略)
  • 路径。(以“/”字符区别路径中的每一个目录名称)
  • 查询。(GET模式的窗体参数,以“?”字符为起点,每个参数以“&”隔开,再以“=”分开参数名称与数据,通常以UTF8的URL编码,避开字符冲突的问题)
  • 片段。以“#”字符为起点

1.1.8 HTTP请求格式

扩展:常见的请求头

  • User-agent:用户的客户端
  • Content-type:请求体的数据格式

1.1.9 HTTP响应格式

1.2 MVC和MTV框架

  • MVC

所谓的MVC就是把web应用分为模型(M)、控制器(C)和视图(V)三层,之间以一种插件式的、轻耦合的方式连接在一起。

  • MTV

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

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

除了以上三层之外,还需要一个URL分发器,作用是将一个个URL的页面请求分发给不同的view处理。

1.3 Django 安装创建

Django官网

1.3.1 安装

安装Django源码

pip install Django
# 指定版本
pip install Django==1.11.9

1.3.2 创建项目

  1. 创建项目
django-admin startproject mysite

这行代码将会在当前目录下创建一个 mysite 目录

目录下的结构是这样的:

mysite/
    manage.py
    mysite/
        __init__.py
        settings.py
        urls.py
        wsgi.py

这些目录和文件的作用是:

  • 最外层的 mysite/ 根目录只是你项目的容器, Django 不关心它的名字,你可以将它重命名为任何你喜欢的名字。
  • manage.py: 一个让你用各种方式管理 Django 项目的命令行工具。你可以阅读 django-admin and manage.py 获取所有 manage.py 的细节。
  • 里面一层的 mysite/ 目录包含你的项目,它是一个纯 Python 包。它的名字就是当你引用它内部任何东西时需要用到的 Python 包名。 (比如 mysite.urls).
  • mysite/__init__.py:一个空文件,告诉 Python 这个目录应该被认为是一个 Python 包。如果你是 Python 初学者,阅读官方文档中的 更多关于包的知识
  • mysite/settings.py:Django 项目的配置文件。如果你想知道这个文件是如何工作的,请查看 Django 配置 了解细节。
  • mysite/urls.py:Django 项目的 URL 声明,就像你网站的“目录”。阅读 URL调度器 文档来获取更多关于 URL 的内容。
  • mysite/wsgi.py:作为你的项目的运行在 WSGI 兼容的Web服务器上的入口。阅读 如何使用 WSGI 进行部署 了解更多细节。
  1. 启动项目

切换到外层的mysite目录,运行下面的命令:

python manage.py runserver

当看到一下输出时,表示启动成功:

Performing system checks...

System check identified no issues (0 silenced).

You have unapplied migrations; your app may not work properly until they are applied.
Run 'python manage.py migrate' to apply them.

九月 23, 2019 - 15:50:53
Django version 2.2, using settings 'mysite.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

访问地址为http://127.0.0.1:8000/

默认情况下,runserver命令设置的监听端口为8000,可以在启动的时候指定端口。

python manage.py runserver 8080

python manage.py runserver 0:8000

1.3.3 创建应用

确认当前在manage.py 所在的目录下,运行下面的命令:

python manage.py startapp polls

这将会创建一个 polls 目录,它的目录结构大致如下:

polls/
    __init__.py
    admin.py
    apps.py
    migrations/
        __init__.py
    models.py
    tests.py
    views.py

创建完应用之后,还需要在Django中安装加载,否则两者之间没有关系。

在文件 mysite/settings.pyINSTALLED_APPS 子项添加点式路径后,它看起来像这样:

INSTALLED_APPS = [
    'polls.apps.PollsConfig',
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]

利用pycharm创建APP:

1.4 URL路由系统

1.4.1 URL 配置

URL配置就像Django所支撑网站的目录。本质是URL与要为该URL调用的视图函数之间的映射表。

  • 基本格式
from django.conf.urls import url

urlpatterns = [
    url(r'^admin/', admin.site.urls),
]

# 循环urlpatterns,找到对应的函数执行,匹配上一个路径就找到对应的函数执行,就不在往下循环了,并给函数传一个参数request,和wsgiref的environ类似,就是请求信息的所有内容。
# url(正则表达式,views视图函数,参数,别名)

参数说明:

  • 正则表达式:一个正则表达式字符串
  • views视图函数:一个可调用对象,通常为一个视图函数或一个指定视图函数路径的字符串
  • 参数:可选的要传递给视图函数的默认参数(字典形式)
  • 别名:一个可选的name参数

1.4.2 正则表达式分组

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'^number/(\d+)/(\d+)/',views.default),
]

# 注意:
# 按照从上往下注意匹配正则表达式,匹配成功则不再继续;
# 前面的r是转译符

views.py文件:

from django.shortcuts import render
from django.http import HttpResponse

def default(request,n1,n2):
    return HttpResponse(f"n1:{n1}",f"n2:{n2}")

# views文件的写法:
# 第一个参数必须是request,后面跟的2个参数是对应上面正则分组的每个参数,是按照位置参数进行传参。

启动后:

1.4.3 正则表达式分组命名

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'^number/(?P<a>\d+)/(?P<b>\d+)/',views.default),
]

views.py文件:

from django.shortcuts import render
from django.http import HttpResponse

# Create your views here.

def default(request,b,a):
    return HttpResponse(f"a:{a} , b:{b}")

# 当上面使用的是分组命名的正则时,利用的是关键字传参方式。

启动后:

1.5 视图

1.5.1 HTTP request 对象

当一个页面被请求时,Django就会创建一个包含本次请求原信息的HTTP request对象。

Django会将这个对象自动传递给响应的视图函数,一般视图函数约定俗成的使用request参数承接这个对象。

属性:

  1. HttpRequest.scheme:表示请求方案的字符串

  2. HttpRequest.body:请求报文的主体

  3. HttpRequest.path:请求路径(不包含域名)

  4. HttpRequest.method:表示请求使用的HTTP方法

  5. HttpRequest.encoding:表示提交的数据的编码方式(如果为None则表示使用 DEFAULT_CHARSET的设置,默认为’utf-8’)

  6. HttpRequest.GET:一个类似于字典的对象,包含HTTP GET的所有参数。

  7. HttpRequest.POST:一个类似于字典的对象,如果请求包含表单数据,则将这些数据封装成QueryDict对象。

  8. HttpRequest.COOKIES:一个字典,包含所有的cookie。

  9. HttpRequest.FILES:一个类似于字典的对象,包含所有的上传文件信息

  10. HttpReuqest.METH:一个标准的字典,包含所有的HTTP请求头信息。

    CONTENT_LENGTH —— 请求的正文的长度(是一个字符串)。
    CONTENT_TYPE —— 请求的正文的MIME 类型。
    HTTP_ACCEPT —— 响应可接收的Content-Type。
    HTTP_ACCEPT_ENCODING —— 响应可接收的编码。
    HTTP_ACCEPT_LANGUAGE —— 响应可接收的语言。
    HTTP_HOST —— 客服端发送的HTTP Host 头部。
    HTTP_REFERER —— Referring 页面。
    HTTP_USER_AGENT —— 客户端的user-agent 字符串。
    QUERY_STRING —— 单个字符串形式的查询字符串(未解析过的形式)。
    REMOTE_ADDR —— 客户端的IP 地址。
    REMOTE_HOST —— 客户端的主机名。
    REMOTE_USER —— 服务器认证后的用户。
    REQUEST_METHOD —— 一个字符串,例如"GET""POST"。
    SERVER_NAME —— 服务器的主机名。
    SERVER_PORT —— 服务器的端口(是一个字符串)
    
  11. HttpRequest.user:一个AUTH_USER_MODEL类型的对象,表示当前登录的用户

  12. HttpRequest.session:一个既可读又可写的类似于字典的对象,表示当前的回话。

方法:

  1. HttpRequest.get_host():根据从HTTP_X_FORWARDED_HOST和HTTP_HOST头部信息返回请求的原始主机。
  2. HttpRequest.get_full_path():返回path,并可以返回查询字符串。
  3. HttpRequest.is_secure():如果请求时是安全的,则返回True;即请求通是过 HTTPS 发起的。

1.5.2 HTTP response 对象

每个视图都需要实例化,填充和返回一个HttpResponse。

  1. 传递字符串
from django.http import HttpResponse

def default(request):
    return HttpResponse("ok")
  1. 传递HTML文件
from django.shortcuts import render

def default(request):
    return render(request,"index.html")
  1. 重定向
from django.shortcuts import render,redirect

def default(request):
    return redirect("http://www.baidu.com")

1.5.2 Django中的视图函数

一个视图函数,简称视图,是一个简单的python函数,接收web请求并且返回web响应。

一个简单的视图示例:

from django.shortcuts import render
from django.http import HttpResponse
import datetime

# Create your views here.
def now_time(request):
    now = datetime.datetime.now()
    html = f"<html><body>{now}</body></html>"
    return HttpResponse(html)

1.5.4 CBV和FBV

  • FBV :就是视图里使用函数处理请求
  • CBV:视图里使用类处理请求

在views中使用类处理请求,优点:

  1. 提高了代码的复用性,可以使用面向对象的技术
  2. 可以用不同的函数针对不同的HTTP方法处理。

1.5.4.1 CBV示例:

views文件:

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

# Create your views here.

class MyView(View):

    def get(self,request):
        return render(request,"index.html")

    def post(self,requset):
        return HttpResponse("biubiubiubiu!!!!")

urls文件:

from django.conf.urls import url
from django.contrib import admin
from app01.views import MyView

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^view/', MyView.as_view()),

]

源码说明:

http_method_names : 定义需要可以被访问的方法

as_view :返回一个视图函数的对象

dispatch :根据用户的请求返回对应的方法

http_method_not_allowed :如果方法不合法,会通过它进行处理

1.5.4.2 视图装饰器

views文件:

from django.shortcuts import render,redirect,HttpResponse
from django.views import View
from django.utils.decorators import method_decorator

# Create your views here.


def wrapper(func):
    def inner(*args,**kwargs):
        print("before...")
        ret = func(*args,**kwargs)
        print("after...")
        return ret
    return inner

@wrapper
def default(request):
    print("default")
    return HttpResponse("papapa。。。")


class MyView(View):
    
    @method_decorator(wrapper)
    def get(self,request):
        print("get-login")
        return render(request,"index.html")

    def post(self,requset):
        return HttpResponse("biubiubiubiu!!!!")

针对视图函数添加装饰器和函数一样;

针对视图类添加装饰器需要借助Django的method_decorator模块实现。

添加装饰器的三种位置:

  • 直接添加在dispatch里,这样每个函数都会执行

  • 添加到具体函数中

  • 添加到类上

    @method_decorator(login_test, name='get') 
    # get表示只给get方法加装饰器
    

1.5.5 URL别名和反向解析

示例:

urls文件:

url(r'^index2',views.index,name="index"),	# 给这个URL匹配起个名称index

在模板里调用:

{% url 'index' %}
# 带参数的URL
{% url 'index' 参数1%}

在views文件:

from django.shortcuts import render
from django.urls import reverse			# 导入reverse模块进行反向解析

def index(request):
    print(reverse('index'))			# /index2
    print(reverse('index',args=(参数1,))			# 带参数反向解析
    return render(request,"index.html")

1.5.6 命名空间

当在一个项目中有多个应用时,把URL匹配都放在项目的urls文件中就不那么合适了,所以在每个应用中都创建urls文件。

在项目的urls文件中进行分发路由到具体的应用:

project中的urls.py:

from django.conf.urls import url,include

url(r'^app01/',include('app01.urls',namespace='app01')),
url(r'^app02/',include('app02.urls',namespace='app02')),

namespace:标记urls文件属于哪个应用

app01的urls文件:

from django.conf.urls import url
from app01 import views

app_name = 'app01'
urlpatterns = [
    url(r'^index/',views.a1),
]

app02的urls文件:

from django.conf.urls import url
from app02 import views

app_name = 'app02'
urlpatterns = [
    url(r'^index/',views.a2),
]
{% url 'index' %}

在views

1.6 模板

1.6.1 语法

{{ }} : 变量相关

{% %} : 逻辑相关

模板文件的查询顺序:

首先查找根目录的tempaltes,然后根据项目中APP的注册顺序查询tempaltes

1.6.2 变量

在Django的模板语言中,遇到变量,将计算这个变量,然后用结果替换本身。

变量的命名包括任何字母数字以及下划线的组合。

深度查询句点符(.)在模板语言中有特殊的含义:

  1. 字典查询
  2. 属性或方法查询
  3. 数字索引查询

views文件:

from django.shortcuts import render

# Create your views here.

def index(request):
    str = "this is string"
    lst = ["a",1,"b",2]
    dic = {"a":1,"b":2}
    class test:
        s = "this is class"

        def func(self):
            return "返回值"

    t = test()

    return render(request,"index.html",{"str":str,"lst":lst,"dic":dic,"t":t,"test":test})

index.html文件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div>这是字符串 : {{ str }}</div>
<div>这是列表的索引为0的值 : {{ lst.0 }}</div>
<div>这是字典 : {{ dic.keys }}</div>
<div>这是对象方法 : {{ t.func }}</div>	# 如果需要显性传参,那么这种就不能使用。
<div>这是类属性 : {{ test.s }}</div>
</body>
</html>

结果显示:

这是字符串 : this is string
这是列表的索引为0的值 : a
这是字典 : dict_keys(['a', 'b'])
这是对象 : 返回值
这是类 : this is class

1.6.3 过滤器

过滤器语法:

{{ value|filter_name:参数}}

注意事项:

  1. 过滤器支持链式操作。即一个过滤器的输出作为另一个过滤器的输入
  2. 过滤器可以接受参数
  3. 过滤器参数包含空格的话,必须使用引号包裹起来
  4. 管道符两边没有空格
  1. default

如果一个变量是false或者为空,使用给定的值。

{{ no|default:"空值" }}
  1. length

返回值的长度,多作用于字符串或列表

{{ value|length }}
  1. filesizeformat

将值格式化成人类可读的大小值(例如:10KB,4.2MB)

{{ value|filesizeformat }}
  1. slice

切片,遵从顾头不顾尾。

{{ value|slice:"2,5" }}
  1. date

格式化,例如时间的格式化

{{ value|date:"Y-m-d H:i:s" }}
  1. safe

Django的模板中在进行模板渲染的时候会对HTML标签和JS等语法标签进行自动转义;为了在Django中关闭HTML的自动转义有两种方式,如果是一个单独的变量我们可以通过过滤器“|safe”的方式告诉Django这段代码是安全的不必转义。

{{ value|safe }}
  1. truncatechars

如果字符串字符多于指定的字符数量,那么会被截断。截断的字符串将以可翻译的省略号序列结尾。

{{ value|truncatechars:9 }}
# 注意:最后的三个省略号也算在9个字符里面
  1. truncatewords

针对单词进行截断。

{{ value|truncatewords:2 }}
  1. cut

移除value中所有的与给出变量相同的字符串

{{ value|cut:" " }}		#去除空格
  1. join

使用字符串连接列表

{{ value|join:"-" }}

1.6.4 标签

1.6.4.1 for 标签

遍历每个元素

遍历列表:

<ul>
    {% for i in lst %}
        <li>{{ i }}</li>
    {% endfor %}
</ul>

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

forloop.counter            当前循环的索引值(从1开始),forloop是循环器,通过点来使用功能
forloop.counter0           当前循环的索引值(从0开始)
forloop.revcounter         当前循环的倒序索引值(从1开始)
forloop.revcounter0        当前循环的倒序索引值(从0开始)
forloop.first              当前循环是不是第一次循环(布尔值)
forloop.last               当前循环是不是最后一次循环(布尔值)
forloop.parentloop         本层循环的外层循环的对象,再通过上面的几个属性来显示外层循环的计数等

1.6.4.2 for..empty

for标签带有一个可选从句,以便在被循环体为空或未找到时可以有所操作。

{% for i in lst %}
    <li>{{ i }}</li>
{% empty %}
    <div>None</div>
{% endfor %}

1.6.4.3 if 标签

{% if num > 100 or num < 0 %}
    <p>无效</p>  <!--不满足条件,不会生成这个标签-->
{% elif num > 80 and num < 100 %}
    <p>优秀</p>
{% else %}  <!--也是在if标签结构里面的-->
    <p>凑活吧</p>
{% endif %}

if语句支持 and 、or、==、>、<、!=、<=、>=、in、not in、is、is not判断,注意条件两边都有空格。

1.6.4.4 with

使用一个简单的名称缓存一个复杂的变量(起别名)。

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

或

{% with business.employees.count as total %}
    {{ total }}
{% endwith %}

注意:等号左右不要加空格

1.6.4 模板继承

模板继承可以创建一个基本的“骨架”模板,包含站点的全部元素,并且可以定义能够被子模板覆盖的blocks。

示例:

模板:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h2>这是模板</h2>
{% block content %}
<h2>这是block里面</h2>
{% endblock %}

{% block site %}
    这是另一个模板内
{% endblock %}
</body>
</html>

其他页面引入:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
{% extends 'home.html' %} <!-- 这个模板继承了另一个模板 -->

{% block content %}
    {{ block.super }}	<!-- 这是显示继承的模板中的content块中的内容 -->
    <h3>这是其他页面的block里面</h3>
{% endblock %}
这是其他页面的block外面
</body>
</html>

显示结果:

请注意,子模板中并没有定义block site,所以系统使用了父模板中的值。

使用继承的注意事项:

  • 如果在模板中使用{% extends %}标签,它必须是模板中的第一个标签。
  • 在主模板中设置越多的{% block %}标签越好。子模板不必定义全部父模板中的blocks,所以,在大多数blocks中填充合理的默认内容。
  • 可以在{% endblock %} 标签中加名字。
  • 不能在一个模板汇总定义多个相同的block标签。

1.6.5 组件

可以把常用的页面内容保存在单独的HTML文件中,当需要的时候引用这个文件即可。

示例:

plugin.html文件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        a {
            background-color: grey;
            height: 20px;
        }
    </style>
</head>
<body>
<div>
    <div>
        <a>导航栏</a>
    </div>
</div>
</body>
</html>

其他页面:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div>
    <div>内容1</div>
    <div>内容2</div>
    {% include 'plugin.html' %}
    <div>内容3</div>
    <div>内容4</div>
</div>
</body>
</html>

显示效果:

1.6.6 自定义标签和过滤器

在应用的目录下创建一个叫做templatetags(名称只能用这个)的文件夹,之后创建一个py文件。

在xxx.py文件中自定义标签和过滤器:

from django import template
register = template.Library()   # register变量名,必须使用这个名称。

@register.filter    # 自定义过滤器
def a1(v1,v2):
    '''
    
    :param v1: 变量的值,管道前的值
    :param v2: 传的参数,管道后的值,最多只能有两个参数
    :return:
    '''
    return v1 + v2

@register.simple_tag    # 自定义标签
def b1(n1,n2,n3):
    '''

    :param n1: 变量的值
    :param n2: 传的参数,传参数没有上限
    :return:
    '''
    return n1+n2+n3


@register.inclusion_tag('result.html')
def c1(n1):
    '''

    :param n1: 传的参数,数量没有上限
    :return:
    '''
    return {'n1':n1}

index.html文件:

{% load xxx %}      <!-- 导入创建的py文件 -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div>
    {{ str|a1:"zzz" }}      <!-- 调用自定义过滤器 -->
</div>
<div>
    {% b1 str 'mmm' 'www' %}    <!-- 调用自定义标签 -->
</div>
<div>
    {% c1 lst %}            <!-- 自定义inclusion_tag -->
</div>

</body>
</html>

result.html文件:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<ul>
    {% for i in n1 %}
    <li>{{ i }}</li>
    {% endfor %}
</ul>
</body>
</html>

views文件

from django.shortcuts import render

# Create your views here.

def index(request):
    str = "string"
    lst = ["aa","bb","cc"]
    return render(request,"index.html",{"str":str,"lst":lst})

显示结果:

1.6.7 静态文件

JavaScript、CSS、img等静态文件在Django中需要在settings文件进行配置。

STATIC_URL = '/static/'		# 别名,可以随意设置
STATICFILES_DIRS = [
    os.path.join(BASE_DIR,"jtwj"),		# 必须写逗号,第二个参数就是项目中存放静态文件的文件名称
]

创建目录:

在HTML文件引入:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="/static/css/index.css">
    <link rel="stylesheet" href="/static/bootstrap-3.3.7-dist/css/bootstrap.min.css">
</head>
<body>
<div class="c1 small">
    hello world
</div>
</body>
</html>

另一种引入方式:

{% load static %}
<link rel="stylesheet" href="{% css/index.css %}">

1.7 ORM

1.7.1 ORM 介绍

ORM是“对象-关系-映射”的简称,实现了数据模型与数据库的解耦。

1.7.2 ORM 单表操作

1.7.2.1 准备操作

由于orm不能创建database,所以需要提前手动创建:

mysql> create database orm01;

默认的settings文件:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}

默认使用的是sqlite数据库,按照需求改成我们使用的数据库,例如MySQL:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'orm01',
        'USER': 'root',
        'PASSWORD': '123456',
        'HOST': '39.107.24.78',
        'POST': 3306,
    }
}

注意Django中默认使用连接数据库的模块是MySQLdb,但是此模块在最新版本的python中不再支持,所以要修改成pymysql模块。

如果不修改运行项目会出现下面的报错:

django.core.exceptions.ImproperlyConfigured: Error loading MySQLdb module: No module named 'MySQLdb'.

需要修改项目下的__init__.py文件:

import pymysql
pymysql.install_as_MySQLdb()

时区问题:

Django的settings文件中默认配置是启用UTC时间的,也就是比我们东八区慢八个小时,所以根据需要关闭这个选项。

TIME_ZONE = 'UTC'

USE_I18N = True

USE_L10N = True

USE_TZ = True

# 修改USE_TZ为False
USE_TZ = False

以上的准备工作就完成了,接下来就对表的各种操作了。

1.7.2.2 创建表

在应用目录的models.py文件中创建类。

from django.db import models

# Create your models here.

class UserInfo(models.Model):
    id = models.AutoField(primary_key=True)
    name = models.CharField(max_length=20)
    sex = models.BooleanField()
    create_date = models.DateTimeField(auto_now_add=True)
    update_date = models.DateTimeField()

上面的字段默认是不为空的,如果允许为空,需要设置参数:null=True

执行数据库同步指令:

python3.6 manage.py makemigrations
python3.6 manage.py migrate

通过pycharm执行:

manage.py@orm01 > makemigrations
manage.py@orm01 > migrate

注意:对表结构的每次修改都要执行上面的两条命令。

数据库结果:

+----------------------------+
| Tables_in_orm01            |
+----------------------------+
| app01_userinfo             |	# table名是应用名+下划线+小写的models类名
| auth_group                 |
| auth_group_permissions     |
| auth_permission            |
| auth_user                  |
| auth_user_groups           |
| auth_user_user_permissions |
| django_admin_log           |
| django_content_type        |
| django_migrations          |
| django_session             |
+----------------------------+

mysql> desc app01_userinfo;
+-------------+-------------+------+-----+---------+----------------+
| Field       | Type        | Null | Key | Default | Extra          |
+-------------+-------------+------+-----+---------+----------------+
| id          | int(11)     | NO   | PRI | NULL    | auto_increment |
| name        | varchar(20) | NO   |     | NULL    |                |
| sex         | tinyint(1)  | NO   |     | NULL    |                |
| create_date | datetime(6) | NO   |     | NULL    |                |
| update_date | datetime(6) | NO   |     | NULL    |                |
+-------------+-------------+------+-----+---------+----------------+

其他的字段和参数


CharField:
字符串字段, 用于较短的字符串.
CharField 要求必须有一个参数 maxlength, 用于从数据库层和Django校验层限制该字段所允许的最大字符数.
 
IntegerField:
用于保存一个整数.
 
DecimalField:
一个浮点数. 必须 提供两个参数:         
参数    		描述
max_digits    总位数(不包括小数点和符号)
decimal_places    小数位数
举例来说, 要保存最大值为 999 (小数点后保存2位),你要这样定义字段:
	models.DecimalField(..., max_digits=5, decimal_places=2)
要保存最大值一百万(小数点后保存10位)的话,你要这样定义:
    models.DecimalField(..., max_digits=17, decimal_places=10) #max_digits大于等于17就能存储百万以上的数了
	admin 用一个文本框(<input type="text">)表示该字段保存的数据.
 
AutoField:
一个 IntegerField, 添加记录时它会自动增长. 你通常不需要直接使用这个字段;
自定义一个主键:my_id=models.AutoField(primary_key=True)
如果你不指定主键的话,系统会自动添加一个主键字段到你的 model.
 
BooleanField:
A true/false field. admin 用 checkbox 来表示此类字段.
 
TextField:
一个容量很大的文本字段.
admin 用一个 <textarea> (文本区域)表示该字段数据.(一个多行编辑框).
 
EmailField:
一个带有检查Email合法性的 CharField,不接受 maxlength 参数.
 
DateField:
        一个日期字段. 共有下列额外的可选参数:
        Argument    描述
        auto_now    当对象被保存时(更新或者添加都行),自动将该字段的值设置为当前时间.通常用于表示 "last-modified" 时间戳.
        auto_now_add    当对象首次被创建时,自动将该字段的值设置为当前时间.通常用于表示对象创建时间.
        (仅仅在admin中有意义...)
 
DateTimeField:
         一个日期时间字段. 类似 DateField 支持同样的附加选项.
 
ImageField:
        类似 FileField, 不过要校验上传对象是否是一个合法图片.#它有两个可选参数:height_field和width_field,
        如果提供这两个参数,则图片将按提供的高度和宽度规格保存.    
FileField:
     一个文件上传字段.
     要求一个必须有的参数: upload_to, 一个用于保存上载文件的本地文件系统路径. 这个路径必须包含 strftime #formatting,
     该格式将被上载文件的 date/time
     替换(so that uploaded files don't fill up the given directory).
     admin 用一个<input type="file">部件表示该字段保存的数据(一个文件上传部件) .
 
     注意:在一个 model 中使用 FileField 或 ImageField 需要以下步骤:
            (1)在你的 settings 文件中, 定义一个完整路径给 MEDIA_ROOT 以便让 Django在此处保存上传文件.
            (出于性能考虑,这些文件并不保存到数据库.) 定义MEDIA_URL 作为该目录的公共 URL. 要确保该目录对
             WEB服务器用户帐号是可写的.
            (2) 在你的 model 中添加 FileField 或 ImageField, 并确保定义了 upload_to 选项,以告诉 Django
             使用 MEDIA_ROOT 的哪个子目录保存上传文件.你的数据库中要保存的只是文件的路径(相对于 MEDIA_ROOT).
             出于习惯你一定很想使用 Django 提供的 get_<#fieldname>_url 函数.举例来说,如果你的 ImageField
             叫作 mug_shot, 你就可以在模板中以 {{ object.#get_mug_shot_url }} 这样的方式得到图像的绝对路径.

URLField:
      用于保存 URL. 若 verify_exists 参数为 True (默认), 给定的 URL 会预先检查是否存在( 即URL是否被有效装入且
      没有返回404响应).
      admin 用一个 <input type="text"> 文本框表示该字段保存的数据(一个单行编辑框)
 
NullBooleanField:
       类似 BooleanField, 不过允许 NULL 作为其中一个选项. 推荐使用这个字段而不要用 BooleanField 加 null=True 选项
       admin 用一个选择框 <select> (三个可选择的值: "Unknown", "Yes""No" ) 来表示这种字段数据.
 
SlugField:
       "Slug" 是一个报纸术语. slug 是某个东西的小小标记(短签), 只包含字母,数字,下划线和连字符.#它们通常用于URLs
       若你使用 Django 开发版本,你可以指定 maxlength. 若 maxlength 未指定, Django 会使用默认长度: 50.  #在
       以前的 Django 版本,没有任何办法改变50 这个长度.
       这暗示了 db_index=True.
       它接受一个额外的参数: prepopulate_from, which is a list of fields from which to auto-#populate
       the slug, via JavaScript,in the object's admin form: models.SlugField
       (prepopulate_from=("pre_name", "name"))prepopulate_from 不接受 DateTimeFields.
 
XMLField:
        一个校验值是否为合法XML的 TextField,必须提供参数: schema_path, 它是一个用来校验文本的 RelaxNG schema #的文件系统路径.
 
FilePathField:
        可选项目为某个特定目录下的文件名. 支持三个特殊的参数, 其中第一个是必须提供的.
        参数    描述
        path    必需参数. 一个目录的绝对文件系统路径. FilePathField 据此得到可选项目.
        Example: "/home/images".
        match    可选参数. 一个正则表达式, 作为一个字符串, FilePathField 将使用它过滤文件名. 
        注意这个正则表达式只会应用到 base filename 而不是
        路径全名. Example: "foo.*\.txt^", 将匹配文件 foo23.txt 却不匹配 bar.txt 或 foo23.gif.
        recursive可选参数.要么 True 要么 False. 默认值是 False. 是否包括 path 下面的全部子目录.
        这三个参数可以同时使用.
        match 仅应用于 base filename, 而不是路径全名. 那么,这个例子:
        FilePathField(path="/home/images", match="foo.*", recursive=True)
        ...会匹配 /home/images/foo.gif 而不匹配 /home/images/foo/bar.gif
 
IPAddressField:
        一个字符串形式的 IP 地址, (i.e. "24.124.1.30").
CommaSeparatedIntegerField:
        用于存放逗号分隔的整数值. 类似 CharField, 必须要有maxlength参数.
 

其他参数:

null
如果为True,Django 将用NULL 来在数据库中存储空值。 默认值是 False.
 
blank
如果为True,该字段允许不填。默认为False。
要注意,这与 null 不同。null纯粹是数据库范畴的,而 blank 是数据验证范畴的。
如果一个字段的blank=True,表单的验证将允许该字段是空值。如果字段的blank=False,该字段就是必填的。
 
default
字段的默认值。可以是一个值或者可调用对象。如果可调用 ,每有新对象被创建它都会被调用,如果你的字段没有设置可以为空,那么将来如果我们后添加一个字段,这个字段就要给一个default值
 
primary_key 
如果为True,那么这个字段就是模型的主键。如果你没有指定任何一个字段的primary_key=True,
Django 就会自动添加一个IntegerField字段做为主键,所以除非你想覆盖默认的主键行为,
否则没必要设置任何一个字段的primary_key=Trueunique
如果该值设置为 True, 这个数据字段的值在整张表中必须是唯一的
 
choices
由二元组组成的一个可迭代对象(例如,列表或元组),用来给字段提供选择项。 如果设置了choices ,默认的表单将是一个选择框而不是标准的文本框,<br>而且这个选择框的选项就是choices 中的选项。

db_index
如果db_index=True 则代表着为此字段设置数据库索引。


DatetimeField、DateField、TimeField这个三个时间字段,都可以设置如下属性。

auto_now_add
配置auto_now_add=True,创建数据记录的时候会把当前时间添加到数据库。

auto_now
配置上auto_now=True,每次更新数据记录的时候会更新该字段,标识这条记录最后一次的修改时间。

verbose_name
目前当作注释使用,以后会在model form 和 form中使用

ORM字段与数据库实际字段的对应关系:

'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.7.2.3 添加记录

方式一:

new_obj = models.UserInfo(
    id=1,
    name='aa',
    sex=1,
    create_date=datetime.datetime.now(),
    update_date=datetime.datetime.now(),
)
new_obj.save()

方式二:

ret = models.UserInfo.objects.create(
    name='bb',
    sex=0,
    create_date=datetime.datetime.now(),
    update_date=datetime.datetime.now(),
)

# ret 是创建新记录的models对象

方式三:利用bulk_create批量插入

book_list = []
for i in range(1,11):
    book_obj = models.Info(
        book_name=f'十万个为什么第{i}卷',
        book_price=120,
    )
    book_list.append(book_obj)
models.Info.objects.bulk_create(book_list)

1.7.2.4 删除记录

obj1 = models.UserInfo.objects.filter(id=1)		# 结果queryset类型的数据类型,里面是models对象,类似与列表
obj1.delete()		# queryset对象调用

obj2 = models.UserInfo.objects.filter(id=2)[0]
obj2.delete()		# model对象调用

1.7.2.5 更改记录

方式一:

models.UserInfo.objects.filter(id=3).update(
    name='oo',
    sex=1,
    update_date=datetime.datetime.now(),
)

方式二:

ret = models.UserInfo.objects.filter(id=3)[0]
ret.name = 'sss'
ret.update_date = datetime.datetime.now()
ret.save()

注意:设置了auto_now=True,采用方式一不起作用,方式二才可以更新修改时间

update只能是querset类型才能调用,model对象不能直接调用更新方法

1.7.2.6 查询记录

重点方法:

  • all()
# 说明:获取所有数据对象,queryset对象
# 例子:
models.Info.objects.all()
  • filter()
# 说明:条件查询
# 例子:
models.Info.objects.filter(id=2,book_name='数学')		
# 多个条件逗号隔开,查询不到数据会返回一个空的queryset

models.Info.objects.filter()	
# 没有条件会把所有数据都获取到
  • get()
# 说明:条件查询
# 例子:
models.Info.objects.get(id=2)

models.Info.objects.get()
# 获取多个结果会报错“book.models.MultipleObjectsReturned: get() returned more than one Info -- it returned 13!”

models.Info.objects.get(id=0)
# 查询不到数据会报错“book.models.DoesNotExist: Info matching query does not exist.”
  • exclude()
# 说明:排除
# 例子:
models.Info.objects.exclude(book_name="英语")
# object能够调用

models.Info.objects.all().exclude(book_name="英语")
# queryset对象能够调用
  • order_by()
# 说明:排序
# 例子:
models.Info.objects.all().order_by('id')
# 正序排列

models.Info.objects.all().order_by('-id')
# 倒叙排列

models.Info.objects.all().order_by('id','-book_price')
# 多个条件排序
  • reverse()
# 说明:反转
# 例子:
models.Info.objects.all().order_by('id').reverse()
# 先排序在反转
  • count()
# 说明:计数
# 例子:
models.Info.objects.all().count()
  • first()
# 说明:返回第一条数据,model对象
# 例子:
models.Info.objects.all().first()
  • last()
# 说明:返回最后一条数据,model对象
# 例子:
models.Info.objects.all().last()
  • exists()
# 说明:判断返回结果是否有数据
# 例子:
models.Info.objects.all().exists()
# 存在返回True

models.Info.objects.filter(id=0).exists()
# 不存在返回False
  • values()
# 说明:queryset类型的数据来调用,返回一个ValueQuerySet——一个特殊的QuerySet,运行后得到的并不是一系列model的实例化对象,而是一个可迭代的字典序列,只要是返回的queryset类型,就可以继续链式调用queryset类型的其他的查找方法
# 例子:
models.Info.objects.filter(id=1).values()

<QuerySet [{'id': 1, 'book_name': '语文', 'book_price': 100, 'date_of_publication': datetime.date(1996, 1, 1), 'press': '人民大学出版社', 'create_date': datetime.datetime(2019, 9, 28, 9, 20, 6, 552848), 'update_date': datetime.datetime(2019, 9, 28, 9, 20, 5, 521052)}]>

models.Info.objects.values()
# 返回所有的数据,QuerySet对象,列表里包含字典
  • values_list()
# 说明:与values()非常相似,它返回的是一个元组序列,values返回的是一个字典序列
# 例子:
models.Info.objects.filter(id=1).values_list()

<QuerySet [(1, '语文', 100, datetime.date(1996, 1, 1), '人民大学出版社', datetime.datetime(2019, 9, 28, 9, 20, 6, 552848), datetime.datetime(2019, 9, 28, 9, 20, 5, 521052))]>
  • distinct()
# 说明:values和values_list得到的queryset类型的数据来调用,从返回结果中剔除重复纪录
# 例子:
models.Info.objects.all().values_list('press').distinct()

<QuerySet [('人民大学出版社',), ('北京师范大学出版社',), ('清华大学出版社',), ('广大人名群众',)]>

Quertset 方法:

##################################################################
# 改变属性并返回一个新的QUERYSET的公共方法 #
##################################################################
def all(self)
    # 获取所有的数据对象

def filter(self, *args, **kwargs)
    # 条件查询
    # 条件可以是:参数,字典,Q
    # 多个条件以逗号隔开

def exclude(self, *args, **kwargs)
    # 条件查询
    # 条件可以是:参数,字典,Q

def select_related(self, *fields)
    性能相关:表之间进行join连表操作,一次性获取关联的数据。

    总结:
    1. select_related主要针一对一和多对一关系进行优化。
    2. select_related使用SQL的JOIN语句进行优化,通过减少SQL查询的次数来进行优化、提高性能。

def prefetch_related(self, *lookups)
    性能相关:多表连表操作时速度会慢,使用其执行多次SQL查询在Python代码中实现连表操作。

    总结:
    1. 对于多对多字段(ManyToManyField)和一对多字段,可以使用prefetch_related()来进行优化。
    2. prefetch_related()的优化方式是分别查询每个表,然后用Python处理他们之间的关系。

def annotate(self, *args, **kwargs)
    # 用于实现聚合group by查询

    from django.db.models import Count, Avg, Max, Min, Sum

    v = models.UserInfo.objects.values('u_id').annotate(uid=Count('u_id'))
    # SELECT u_id, COUNT(ui) AS `uid` FROM UserInfo GROUP BY u_id

    v = models.UserInfo.objects.values('u_id').annotate(uid=Count('u_id')).filter(uid__gt=1)
    # SELECT u_id, COUNT(ui_id) AS `uid` FROM UserInfo GROUP BY u_id having count(u_id) > 1

    v = models.UserInfo.objects.values('u_id').annotate(uid=Count('u_id',distinct=True)).filter(uid__gt=1)
    # SELECT u_id, COUNT( DISTINCT ui_id) AS `uid` FROM UserInfo GROUP BY u_id having count(u_id) > 1

def distinct(self, *field_names)
    # 用于distinct去重
    models.UserInfo.objects.values('nid').distinct()
    # select distinct nid from userinfo

    注:只有在PostgreSQL中才能使用distinct进行去重

def order_by(self, *field_names)
    # 用于排序
    models.UserInfo.objects.all().order_by('-id','age')

def extra(self, select=None, where=None, params=None, tables=None, order_by=None, select_params=None)
    # 构造额外的查询条件或者映射,如:子查询

    Entry.objects.extra(select={'new_id': "select col from sometable where othercol > %s"}, select_params=(1,))
    Entry.objects.extra(where=['headline=%s'], params=['Lennon'])
    Entry.objects.extra(where=["foo='a' OR bar = 'a'", "baz = 'a'"])
    Entry.objects.extra(select={'new_id': "select id from tb where id > %s"}, select_params=(1,), order_by=['-nid'])

 def reverse(self):
    # 倒序
    models.UserInfo.objects.all().order_by('-nid').reverse()
    # 注:如果存在order_by,reverse则是倒序,如果多个排序则一一倒序


 def defer(self, *fields):
    models.UserInfo.objects.defer('username','id')
    或
    models.UserInfo.objects.filter(...).defer('username','id')
    #映射中排除某列数据

 def only(self, *fields):
    #仅取某个表中的数据
     models.UserInfo.objects.only('username','id')
     或
     models.UserInfo.objects.filter(...).only('username','id')

 def using(self, alias):
     指定使用的数据库,参数为别名(setting中的设置)


##################################################
# 返回QUERYSET子类的公共方法 #
##################################################

def raw(self, raw_query, params=None, translations=None, using=None):
    # 执行原生SQL
    models.UserInfo.objects.raw('select * from userinfo')

    # 如果SQL是其他表时,必须将名字设置为当前UserInfo对象的主键列名
    models.UserInfo.objects.raw('select id as nid from 其他表')

    # 为原生SQL设置参数
    models.UserInfo.objects.raw('select id as nid from userinfo where nid>%s', params=[12,])

    # 将获取的到列名转换为指定列名
    name_map = {'first': 'first_name', 'last': 'last_name', 'bd': 'birth_date', 'pk': 'id'}
    Person.objects.raw('SELECT * FROM some_other_table', translations=name_map)

    # 指定数据库
    models.UserInfo.objects.raw('select * from userinfo', using="default")

    ################### 原生SQL ###################
    from django.db import connection, connections
    cursor = connection.cursor()  # cursor = connections['default'].cursor()
    cursor.execute("""SELECT * from auth_user where id = %s""", [1])
    row = cursor.fetchone() # fetchall()/fetchmany(..)


def values(self, *fields):
    # 获取每行数据为字典格式

def values_list(self, *fields, **kwargs):
    # 获取每行数据为元祖

def dates(self, field_name, kind, order='ASC'):
    # 根据时间进行某一部分进行去重查找并截取指定内容
    # kind只能是:"year"(年), "month"(年-月), "day"(年-月-日)
    # order只能是:"ASC"  "DESC"
    # 并获取转换后的时间
        - year : 年-01-01
        - month: 年-月-01
        - day  : 年-月-日

    models.DatePlus.objects.dates('ctime','day','DESC')

def datetimes(self, field_name, kind, order='ASC', tzinfo=None):
    # 根据时间进行某一部分进行去重查找并截取指定内容,将时间转换为指定时区时间
    # kind只能是 "year", "month", "day", "hour", "minute", "second"
    # order只能是:"ASC"  "DESC"
    # tzinfo时区对象
    models.DDD.objects.datetimes('ctime','hour',tzinfo=pytz.UTC)
    models.DDD.objects.datetimes('ctime','hour',tzinfo=pytz.timezone('Asia/Shanghai'))

    """
    pip3 install pytz
    import pytz
    pytz.all_timezones
    pytz.timezone(‘Asia/Shanghai’)
    """

def none(self):
    # 空QuerySet对象


####################################
# 执行数据库查询的方法 #
####################################

def aggregate(self, *args, **kwargs):
   # 聚合函数,获取字典类型聚合结果
   from django.db.models import Count, Avg, Max, Min, Sum
   result = models.UserInfo.objects.aggregate(k=Count('u_id', distinct=True), n=Count('nid'))
   ===> {'k': 3, 'n': 4}

def count(self):
   # 获取个数

def get(self, *args, **kwargs):
   # 获取单个对象

def create(self, **kwargs):
   # 创建对象

def bulk_create(self, objs, batch_size=None):
    # 批量插入
    # batch_size表示一次插入的个数
    objs = [
        models.DDD(name='r11'),
        models.DDD(name='r22')
    ]
    models.DDD.objects.bulk_create(objs, 10)

def get_or_create(self, defaults=None, **kwargs):
    # 如果存在,则获取,否则,创建
    # defaults 指定创建时,其他字段的值
    obj, created = models.UserInfo.objects.get_or_create(username='root1', defaults={'email': '1111111','u_id': 2, 't_id': 2})

def update_or_create(self, defaults=None, **kwargs):
    # 如果存在,则更新,否则,创建
    # defaults 指定创建时或更新时的其他字段
    obj, created = models.UserInfo.objects.update_or_create(username='root1', defaults={'email': '1111111','u_id': 2, 't_id': 1})

def first(self):
   # 获取第一个

def last(self):
   # 获取最后一个

def in_bulk(self, id_list=None):
   # 根据主键ID进行查找
   id_list = [11,21,31]
   models.DDD.objects.in_bulk(id_list)

def delete(self):
   # 删除

def update(self, **kwargs):
    # 更新

def exists(self):
   # 是否有结果

基于双下方法的模糊查询:

# 获取个数
#
# models.Tb1.objects.filter(name='seven').count()

# 大于,小于
#
# models.Tb1.objects.filter(id__gt=1)              # 获取id大于1的值
# models.Tb1.objects.filter(id__gte=1)              # 获取id大于等于1的值
# models.Tb1.objects.filter(id__lt=10)             # 获取id小于10的值
# models.Tb1.objects.filter(id__lte=10)             # 获取id小于10的值
# models.Tb1.objects.filter(id__lt=10, id__gt=1)   # 获取id大于1 且 小于10的值

# 成员判断in
#
# models.Tb1.objects.filter(id__in=[11, 22, 33])   # 获取id等于11、22、33的数据
# models.Tb1.objects.exclude(id__in=[11, 22, 33])  # not in

# 是否为空 isnull
# Entry.objects.filter(pub_date__isnull=True)

# 包括contains
#
# models.Tb1.objects.filter(name__contains="ven")
# models.Tb1.objects.filter(name__icontains="ven") # icontains大小写不敏感
# models.Tb1.objects.exclude(name__icontains="ven")

# 范围range
#
# models.Tb1.objects.filter(id__range=[1, 2])   # 范围bettwen and

# 其他类似
#
# startswith,istartswith, endswith, iendswith,

# 排序order by
#
# models.Tb1.objects.filter(name='seven').order_by('id')    # asc
# models.Tb1.objects.filter(name='seven').order_by('-id')   # desc

# 分组group by
#
# from django.db.models import Count, Min, Max, Sum
# models.Tb1.objects.filter(c1=1).values('id').annotate(c=Count('num'))
# SELECT "app01_tb1"."id", COUNT("app01_tb1"."num") AS "c" FROM "app01_tb1" WHERE "app01_tb1"."c1" = 1 GROUP BY "app01_tb1"."id"

# limit 、offset
#
# models.Tb1.objects.all()[10:20]

# regex正则匹配,iregex 不区分大小写
#
# Entry.objects.get(title__regex=r'^(An?|The) +')
# Entry.objects.get(title__iregex=r'^(an?|the) +')

# date
#
# Entry.objects.filter(pub_date__date=datetime.date(2005, 1, 1))
# Entry.objects.filter(pub_date__date__gt=datetime.date(2005, 1, 1))

# year
#
# Entry.objects.filter(pub_date__year=2005)
# Entry.objects.filter(pub_date__year__gte=2005)

# month
#
# Entry.objects.filter(pub_date__month=12)
# Entry.objects.filter(pub_date__month__gte=6)

# day
#
# Entry.objects.filter(pub_date__day=3)
# Entry.objects.filter(pub_date__day__gte=3)

# week_day
#
# Entry.objects.filter(pub_date__week_day=2)
# Entry.objects.filter(pub_date__week_day__gte=2)

# hour
#
# Event.objects.filter(timestamp__hour=23)
# Event.objects.filter(time__hour=5)
# Event.objects.filter(timestamp__hour__gte=12)

# minute
#
# Event.objects.filter(timestamp__minute=29)
# Event.objects.filter(time__minute=46)
# Event.objects.filter(timestamp__minute__gte=29)

# second
#
# Event.objects.filter(timestamp__second=31)
# Event.objects.filter(time__second=2)
# Event.objects.filter(timestamp__second__gte=31)

1.7.3 ORM多表

1.7.3.1 创建模型

class Author(models.Model):
    nid = models.AutoField(primary_key=True)
    name=models.CharField( max_length=32)
    age=models.IntegerField()
    authorDetail=models.OneToOneField(to="AuthorDetail",to_field="nid",on_delete=models.CASCADE)	# 就是foreignkey+unique,只不过不需要我们自己来写参数了,并且orm会自动帮你给这个字段名字拼上一个_id,数据库中字段名称为authorDetail_id

class AuthorDetail(models.Model):
    nid = models.AutoField(primary_key=True)
    birthday=models.DateField()
    telephone=models.BigIntegerField()
    addr=models.CharField( max_length=64)

class Publish(models.Model):
    nid = models.AutoField(primary_key=True)
    name=models.CharField( max_length=32)
    city=models.CharField( max_length=32)
    email=models.EmailField()

class Book(models.Model):
    nid = models.AutoField(primary_key=True)
    title = models.CharField( max_length=32)
    publishDate=models.DateField()
    price=models.DecimalField(max_digits=5,decimal_places=2)
    publish=models.ForeignKey(to="Publish",to_field="nid",on_delete=models.CASCADE)
    authors=models.ManyToManyField(to='Author',)

创建一对一对应关系的参数:

to :设置要关联的表。
to_field :设置要关联的字段。
on_delete:级联删除

一对多关系的参数:

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

多对多关系的参数:

to:设置要关联的表
related_name:同ForeignKey字段。
related_query_name:同ForeignKey字段。
through:在使用ManyToManyField字段时,Django将自动生成一张表来管理多对多的关联关系。

但我们也可以手动创建第三张表来管理多对多关系,此时就需要通过through来指定第三张表的表名。

through_fields:设置关联的字段。
db_table:默认创建第三张表时,数据库中表的名称。

关于on_delete(了解)

on_delete
当删除关联表中的数据时,当前表与其关联的行的行为。

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


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


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


models.SET_NULL
删除关联数据,与之关联的值设置为null(前提FK字段需要设置为可空)


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


models.SET

删除关联数据,
a. 与之关联的值设置为指定值,设置:models.SET(值)
b. 与之关联的值设置为可执行对象的返回值,设置:models.SET(可执行对象)

1.7.3.2 添加记录

def sql(request):
    models.AuthorDetail.objects.create(
        birthday = "2000-10-10",
        telephone = "110",
        addr = "澳门"
    )
    models.AuthorDetail.objects.create(
        birthday="1900-07-14",
        telephone="911",
        addr="纽约"
    )
    models.AuthorDetail.objects.create(
        birthday="1933-06-05",
        telephone="120",
        addr="重庆"
    )

    models.Author.objects.create(
        name = "Joe",
        age = "120",
        authorDetail_id = 1
    )
    models.Author.objects.create(
        name="Tia",
        age="19",
        authorDetail_id=2
    )
    models.Author.objects.create(
        name="Aeo",
        age="57",
        authorDetail_id=3
    )

    models.Publish.objects.create(
        name = "A出版社",
        city = "北京",
        email = "bj@163.com"
    )
    models.Publish.objects.create(
        name="B出版社",
        city="天津",
        email="tj@163.com"
    )

    models.Book.objects.create(
        title="十万个为什么",
        publishDate="2001-12-24",
        price=26,
        publish_id=1
    )
    models.Book.objects.create(
        title="γ",
        publishDate="2004-03-15",
        price=54,
        publish_id=2
    )
    models.Book.objects.create(
        title="α",
        publishDate="1997-05-23",
        price=65,
        publish_id=1
    )
    models.Book.objects.create(
        title="β",
        publishDate="1978-02-04",
        price=134,
        publish_id=2
    )

    book_obj1 = models.Book.objects.filter(nid=1).first()
    book_obj1.authors.add(*[1,2])
    book_obj2 = models.Book.objects.filter(nid=2).first()
    book_obj2.authors.add(*[1, 3])
    book_obj3 = models.Book.objects.filter(nid=3).first()
    book_obj3.authors.add(*[2])

一对多添加记录的两种方式:

# 方式一:
    publish_obj = models.Publish.objects.filter(nid=1).first()	# 获取nid为1的出版社对象
    models.Book.objects.create(
        title="α",
        publishDate="1997-05-23",
        price=65,
        publish=publish_obj
    )
    
# 方式二:
    models.Book.objects.create(
        title="α",
        publishDate="1997-05-23",
        price=65,
        publish_id=1
    )

## 注意publish=publish_obj和publish_id=1之间的区别。一个是赋值对象,一个是直接赋具体的值

多对多添加记录的两种方式:

# 方式一:
book_obj=Book.objects.create(title="...",price=200,publishDate="2012-11-12",publish_id=1) # 获取书籍对象
yuan=Author.objects.filter(name="yuan").first() # 在Author表中主键为2的纪录,注意取的是author的model对象
egon=Author.objects.filter(name="alex").first() # 在Author表中主键为1的纪录
book_obj.authors.add(yuan,egon) #将某些特定的 model 对象添加到被关联对象集合中

# 方式二:
book_obj.authors.add(12)
book_obj.authors.add(*[12]) #这种方式用的最多,因为一般是给用户来选择,用户选择是多选的,选完给你发送过来的就是一堆的id值

多对多常用方法:

book_obj.authors.remove()      # 将某个特定的对象从被关联对象集合中去除
book_obj.authors.clear()       #清空被关联对象集合
book_obj.authors.set()         #先清空再设置

1.7.3.3 基于对象的跨表查询

正向查询:关系属性在表a,关联到表b,通过表a查询表b的数据叫正向查询,反过来是反向查询。

示例一(一对一):

# 查询Joe的电话号码(正向查询)
author_obj = models.Author.objects.filter(name="Joe").first()
ret = author_obj.authorDetail.telephone

# 查询电话为120的作者姓名(反向查询)
obj = models.AuthorDetail.objects.filter(telephone=120).first()
ret = obj.author.name

示例二(一对多):

# 查询α由哪个出版社出版(正向查询)
obj = models.Book.objects.filter(title="α").first()
ret = obj.publish.name

# 查询A出版社出版了那些书(反向查询)
obj = models.Publish.objects.filter(name="A出版社").first()
ret = obj.book_set.all()
for i in ret:
    print(i.title)

示例三(多对多):

# γ书籍是谁写的(正正向查询)
obj = models.Book.objects.filter(title="γ").first()
ret = obj.authors.all()
for i in ret:
    print(i.name)

# Joe作者写了那些书(反向查询)
obj = models.Author.objects.filter(name="Joe").first()
ret = obj.book_set.all()
for i in ret:
    print(i.title)

1.7.3.4 基于双下划线的跨表查询

示例一(一对一):

# 查询Joe的电话号码
ret = models.Author.objects.filter(name="Joe").values("authorDetail__telephone")
ret = models.AuthorDetail.objects.filter(author__name="Joe").values('telephone')

示例二(一对多):

# 查询α由哪个出版社出版
ret = models.Book.objects.filter(title="α").values('authors__name')
ret = models.Author.objects.filter(book__title="α").values('name')

示例三(多对多):

# γ书籍是谁写的
ret = models.Book.objects.filter(title="γ").values('authors__name')
ret = models.Author.objects.filter(book__title="γ").values('name')

1.7.3.5 聚合查询

from django.db.models import Avg, Max, Min
    ret = models.Book.objects.all().aggregate(Avg('price'))
    
# 如果想对聚合函数起别名,使用如下写法:
from django.db.models import Avg, Max, Min
    ret = models.Book.objects.all().aggregate(m = Avg('price'))

aggregate()QuerySet 的一个终止子句,意思是说,它返回一个包含一些键值对的字典。键的名称是聚合值的标识符,值是计算出来的聚合值。

1.7.3.6 分组查询

示例一:每个出版社出版的书的平均价格

from django.db.models import Avg, Max, Min
ret = models.Book.objects.values('publish__name').annotate(m = Avg('price'))

注意:annotate里面必须写个聚合函数,不然没有意义,并且必须有个别名=,别名随便写,但是必须有,用哪个字段分组,values里面就写哪个字段,annotate其实就是对分组结果的统计,统计你需要什么。

annotate()为调用的QuerySet中每一个对象都生成一个独立的统计值(统计方法用聚合函数)

1.7.3.7 F查询和Q查询

F查询:

from django.db.models import Avg, Sum, Max, Min, Count,F
# 查询一下评论数大于点赞数的书
ret = models.Book.objects.filter(comment__gt=F('good'))
print(ret)

# 将所有书的价格上调100块
models.Book.objects.all().update(
    price=F('price')+100
)

Q查询:

filter() 等方法中的关键字参数查询都是一起进行“AND” 的。 如果你需要执行更复杂的查询(例如OR 语句),你可以使用Q 对象

Q 对象可以使用&(与)|(或)、~(非) 操作符组合起来。当一个操作符在两个Q 对象上使用时,它产生一个新的Q 对象。

from django.db.models import Avg, Sum, Max, Min, Count, F,Q
    ret = models.Book.objects.filter(Q(id=2)&Q(Q(price__gt=112)|~Q(comment__lte=200)))
    print(ret)

1.7.3.8 ORM执行原生sql语句

Django 提供两种方法使用原始SQL进行查询:一种是使用raw()方法,进行原始SQL查询并返回模型实例;另一种是完全避开模型层,直接执行自定义的SQL语句。

  • 执行原生查询

raw()管理器方法用于原始的SQL查询,并返回模型的实例;

注意:raw()语法查询必须包含主键。

这个方法执行原始的SQL查询,并返回一个django.db.models.query.RawQuerySet 实例。 这个RawQuerySet 实例可以像一般的QuerySet那样,通过迭代来提供对象实例

  • 直接执行自定义SQL

1.7.3.9 例题

#1 查询每个作者的姓名以及出版的书的最高价格
ret = models.Author.objects.values('name').annotate(m = Max("book__price"))

#2 查询作者id大于2作者的姓名以及出版的书的最高价格
ret = models.Author.objects.filter(nid__gt=2).values('name').annotate(m = Max("book__price"))   

#3 查询作者id大于2或者作者年龄大于等于20岁的女作者的姓名以及出版的书的最高价格
ret = models.Author.objects.filter(Q(nid__gt=2)|Q(age__gte=20)).values('name').annotate(m = Max("book__price"))

#4 查询每个作者出版的书的最高价格的平均值
ret = models.Author.objects.values('name').annotate(m = Max('book__price')).aggregate(a = Avg('m'))

#5 每个作者出版的所有书的最高价格以及最高价格的那本书的名称

1.8 cookie和session

1.8.1 Django请求的生命周期

wsgi协议:Web服务器网关接口,本质是socket,Django框架本身并没有socket

Wsgiref:测试使用

Uwsgi:线上使用

1.8.2 登录示例问题

通过orm查询数据库用户名或密码是否正确时使用方法:

models.UserInfo.objects.filter(username=info.get("username"),password=info.get('pwd')).first()
#或者
models.UserInfo.objects.filter(username=info.get("username"),password=info.get('pwd')).exists()

不同点:

first():如果存在返回一个对象,如果不存在返回None。

exists():返回结果时布尔值

推荐使用first()。

如何在访问其他页面时判断用户是否进行了登录?

1.8.3 基于cookie实现

views文件:

from django.shortcuts import render,HttpResponse,redirect
from login import models

# Create your views here.
def login(request):
    if request.method == 'GET':
        return render(request,'login.html')
    else:
        info = request.POST
        print(info)
        ret = models.UserInfo.objects.filter(username=info.get("username"),password=info.get('pwd')).exists()
        if ret:
            data = redirect("/index")
            data.set_cookie("cookieid",info.get("username"))
            return data
        return render(request,'login.html',{"error":"用户名或密码错误"})

def index(request):
    ret = request.COOKIES.get("cookieid")
    if not ret:
        return redirect("/login")
    return render(request,'index.html',{"ret":ret})

cookie的常用操作:

# 设置cookie
data = redirect(...)          data.set_cookie(...)
#注意:redirect、HttpResponse、render都可以设置cookie。

# 读取cookie
request.COOKIES.get(...)

cookie 的常用参数:

data.set_cookie(key,values='',max_age=None,expires=None,path='/',domain=None,secure=False,httponly=False)

key:values:max_age:过期时间,单位秒
expires:值是一个datetime类型的时间日期对象,到这个日期就失效的意思
path:Cookie生效的路径,就是访问哪个路径可以得到cookie,'/'是所有路径都能获得cookie
domain:Cookie生效的域名
secure:如果设置为 True ,浏览器将通过HTTPS来回传cookie。
httponly:只能http协议传输,无法被JavaScript获取
  • Cookie 是什么?

保存在客户端浏览器的一个键值对,向服务端发送请求时自动携带。

  • 应用场景

    • 用户认证(不靠谱)
    • 投票(不靠谱)
    • 每页默认显示数据
  • 通过js或jQuery设置cookie

# js
document.cookie = 'a1=xxx;path=/'

# jQuery
$.cookie('a1','xxx',{path:'/'})

# 注意:path不同会导致设置不同
  • path 的作用
    • / :当前网站中所有的URL都能获取到cookie
    • “” :只能在当前页面访问获取到cookie
    • /index/:只能在/index/xx 的网页中获取到cookie

1.8.4 基于session实现

views文件

from django.shortcuts import render,HttpResponse,redirect
from login import models

# Create your views here.
def wrapper(func):
    def inner(request,*args,**kwargs):
        ret = request.session.get("a1")
        if ret != "1":
            return redirect("/login")
        return func(request,*args,**kwargs)
    return inner

def login(request):
    if request.method == 'GET':
        return render(request,'login.html')
    else:
        info = request.POST
        print(info)
        ret = models.UserInfo.objects.filter(username=info.get("username"),password=info.get('pwd')).first()
        if ret:
            # data = redirect("/index")
            # data.set_cookie("cookieid",info.get("username"))
            # return data
            request.session["a1"] = "1"
            request.session["username"] = info.get("username")
            return redirect("/index")
        return render(request,'login.html',{"error":"用户名或密码错误"})

@wrapper
def index(request):
    # ret = request.COOKIES.get("cookieid")
    return render(request,'index.html')
  • 什么是session?

依赖cookie,是一种存储数据的方式,本质是服务端接收到用户的请求,生成随机字符串,开辟单独的空间存放用户的数据。

  • Django中session的配置
SESSION_COOKIE_NAME = "sessionid" 
#Session的cookie保存在浏览器上时的key,即:sessionid=随机字符串

SESSION_COOKIE_DOMAIN = None
# session的cookie生效的域名

SESSION_COOKIE_PATH = "/" 
# Session的cookie保存的路径

SESSION_COOKIE_HTTPONLY = True 
# 是否Session的cookie只支持http传输

SESSION_COOKIE_AGE = 1209600 
# Session的cookie失效日期(2周)

SESSION_EXPIRE_AT_BROWSER_CLOSE = False 
#是否关闭浏览器使得Session过期

SESSION_SAVE_EVERY_REQUEST = False 
# 是否每次请求都保存Session,默认修改之后才保存
  • Django的session存储位置

    • 数据库(默认)
    • 文件
    SESSION_ENGINE ='django.contrib.sessions.backends.file'
    SESSION_FILE_PATH = '/filename/'
    
    • 缓存
    SESSION_ENGINE ='django.contrib.sessions.backends.cache'
    SESSION_CACHE_ALIAS = 'default'
    CACHES = {
    	"default": {
    		"BACKEND":
    		"django_redis.cache.RedisCache",
    		"LOCATION":
    		"redis://127.0.0.1:6379",
    		"OPTIONS": {
    			"CLIENT_CLASS":
    			"django_redis.client.DefaultClient",
    			"CONNECTION_POOL_KWARGS":{"max_connections": 100}
    			# "PASSWORD": "密码",
    		}
    	}
    }
    
  • 操作session

# 设置
request.session["xx"] = a1

# 读取
request.session["xx"]
request.session.get("xxx")

# 删除
del request.session["xx"]
request.session.flush() # 清cookie删session

#扩展
# 所有 键、值、键值对
request.session.keys()
request.session.values()
request.session.items()

# 设置会话Session和Cookie的超时时间
request.session.set_expiry(value)
    * 如果value是个整数,session会在些秒数后失效。
    * 如果value是个datatime或timedelta,session就会在这个时间后失效。
    * 如果value是0,用户关闭浏览器session就会失效。
    * 如果value是None,session会依赖全局session失效策略。
  • 应用场景
    • 用户认证
    • 短信验证过期
    • 权限管理

1.8.5 cookie和session的区别

cookie和session的区别?
答: cookie是存储在客户端浏览器上的键值对,发送请求时浏览器会自动携带. session是一种存储数据方式,基于cookie实现,将数据存储在服务端(django默认存储到数据库).
其本质是:
用户向服务端发送请求,服务端做两件事:生成随机字符串;为此用户开辟一个独立的空间来存放当前用户独有的值.
在空间中如何想要设置值:
request.session['x1'] = 123
request.session['x2'] = 456
在空间中取值:
request.session['x2']
request.session.get('x2')
视图函数中的业务操作处理完毕,给用户响应,在响应时会
将随机字符串存储到用户浏览器的cookie中.

1.9 Ajax

1.9.1 ajax简介

ajax不是新的编程语言,而是一种使用现有标准的新方法。

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

ajax特点:

  • 异步请求
  • 局部刷新

1.9.2 ajax常见应用场景

注册的时候,当文件框发生了输入变化,使用ajax向服务器发送一个请求,然后服务器会把响应结果返回给浏览器,最后再把结果展示出来。

  1. 整个过程中页面没有刷新,只是刷新页面中的局部位置
  2. 当请求发出后,浏览器还可以进行其他操作,无需等待服务器响应。

ajax的优点:

  • ajax使用JavaScript向服务器发送异步请求
  • ajax请求无需刷新整个页面
  • 由于只是刷新部分内容,所以ajax性能高

1.9.3 ajax格式

  1. 基于jQuery实现

html文件:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
</head>
<body>
<label>
    用户名 <input type="text" name="username" id="username">
</label>
<label>
    密码 <input type="password" name="pwd" id="pwd">
</label>
<button type="submit" id="login">登录</button>
<div class="error" style="color: red"></div>
<script >
    $('#login').click(function () {
        $.ajax(
            {
                url:"{% url 'login' %}",
                type:'post',
                data:{username:$('#username').val(),
                    pwd:$('#pwd').val(),
                    csrfmiddlewaretoken:'{{ csrf_token }}'},
                success:function (data) {
                    if (data.status === 1) {
                        location.href='/home';
                    }else {
                        $('.error').text(data.msg)
                    }
                }
            }
        )
    })
</script>
</body>
</html>
  1. 基于原生JS实现
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=chao&password=123456");
    xmlHttp.onreadystatechange = function () {
      if (xmlHttp.readyState === 4 && xmlHttp.status === 200) {
        alert(xmlHttp.responseText);
      }
    };
  };
  • Ajax参数

请求参数:

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

processData:声明当前的data数据是否进行转码或预处理,默认为true,即预处理;if为false,
             那么对data:{a:1,b:2}会调用json对象的toString()方法,即{a:1,b:2}.toString()
             ,最后得到一个[object,Object]形式的结果。
             
contentType:默认值: "application/x-www-form-urlencoded"。发送信息至服务器时内容编码类型。
             用来指明当前请求的数据编码格式;

1.9.4 ajax设置csrf_token

详述CSRF(Cross-site request forgery),中文名称:跨站请求伪造,也被称为:one click attack/session riding,缩写为:CSRF/XSRF。攻击者通过HTTP请求江数据传送到服务器,从而盗取回话的cookie。盗取回话cookie之后,攻击者不仅可以获取用户的信息,还可以修改该cookie关联的账户信息。

方式一:

{% csrf_token %}

<script >
    $('#login').click(function () {
        $.ajax(
            {
                url:"{% url 'login' %}",
                type:'post',
                data:{username:$('#username').val(),
                    pwd:$('#pwd').val(),
                    csrfmiddlewaretoken:$("[name = 'csrfmiddlewaretoken']").val()},
                success:function (data) {
                    if (data.status === 1) {
                        location.href='/home';
                    }else {
                        $('.error').text(data.msg)
                    }
                }
            }
        )
    })
</script>

方式二:

$.ajax({
	csrfmiddlewaretoken:'{{ csrf_token }}'
})

方式三:

$.ajax({
		headers:{"X-CSRFToken":$.cookie('csrftoken')}
})

扩展:form表单设置CSRF认证

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

1.9.5 ajax文件上传

1.9.5.1 请求头ContentType

  • application/x-www-form-urlencoded

最常见的 POST 提交数据的方式了。浏览器的原生 <form> 表单,如果不设置 enctype 属性,那么最终就会以 默认格式application/x-www-form-urlencoded 方式提交数据,ajax默认也是这个。

  • multipart/form-data

使用表单上传文件时,必须让 <form> 表单的 enctype 等于 multipart/form-data,form表单不支持发json类型的contenttype格式的数据,而ajax什么格式都可以发,也是ajax应用广泛的一个原因。

  • application/json

用来告诉服务端消息主体是序列化后的 JSON 字符串

1.9.5.2 基于form上传文件

Views 文件:

def login(request):
    if request.method == "POST":
        ret_obj = request.FILES.get('file')
        with open(ret_obj.name,"wb") as f:
            for chunk in ret_obj.chunks():	# 每次取值大小固定,默认65536字节,可以在settings文件更改
                f.write(chunk)
        return HttpResponse("ok")
    return render(request,'login.html')

Html 文件:

<form action="" method="post" enctype="multipart/form-data">
    文件 <input type="file" name="file">
    <input type="submit">
    {% csrf_token %}
</form>

1.9.5.3 基于ajax上传文件

Html 文件:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
    <script src="https://cdn.bootcss.com/jquery-cookie/1.4.1/jquery.cookie.min.js"></script>
</head>
<body>
<input type="file" name="file">
<button type="submit" class="upload">提交</button>

<script>
    $('.upload').click(function () {
        var formdata=new FormData();  //ajax上传文件的时候,需要这个类型,它会将添加给它的键值对加工成formdata的类型
        var file_obj = $('[name=file]')[0].files[0];

        formdata.append("file",file_obj); //添加键值的方法是append,注意写法,键和值之间是逗号
        formdata.append("csrfmiddlewaretoken", '{{ csrf_token }}');

        $.ajax({
            url:'{% url 'home' %}',
            type:'post',
            data:formdata, //将添加好数据的formdata放到data这里
            processData: false, // 不处理数据
            contentType:false, // 不设置内容类型
            success:function (data) {
                console.log(data)
            }
        })
    })
</script>
</body>
</html>

Views 文件:

def home(request):
    if request.method == "POST":
        ret_obj = request.FILES.get('file')
        with open(ret_obj.name, "wb") as f:
            for chunk in ret_obj.chunks():
                f.write(chunk)
        return HttpResponse("ok")
    return render(request,'home.html')

1.9.6 JsonResponse

JsonResponse是HttpResponse的子类,专门用来生成JSON编码的响应。

viesw文件:

from django.http import JsonResponse

response = JsonResponse({'foo': 'bar'})
print(response.content)

这个类是HttpRespon的子类,它主要和父类的区别在于:

  1. 它的默认Content-Type 被设置为: application/json
  2. 第一个参数,data应该是一个字典类型,当 safe 这个参数被设置为:False ,那data可以填入任何能被转换为JSON格式的对象,比如list, tuple, set。 默认的safe 参数是 True. 如果你传入的data数据类型不是字典类型,那么它就会抛出 TypeError的异常。
  3. json_dumps_params参数是一个字典,它将调用json.dumps()方法并将字典中的参数传入给该方法。

对于返回非字典类型的数据加上参数safe=False就可以正常返回。

1.9.7 ajax上传json数据

html:

<script>
    $('.addbook').click(function () {
        $.ajax(
            {
                url: "{% url 'add' %}",
                type: "post",
                contentType:'application/json',
                data: JSON.stringify({
                    title: $('[name="title"]').val(),
                    authors: $('[name="authors"]').val(),
                    price: $('[name="price"]').val(),
                    publishDate: $('[name="publishDate"]').val(),
                    publish: $('[name="publish"]').val()
                }),
                headers:{"X-CSRFToken":$.cookie('csrftoken')},  //在请求头部进行csrf认证;
                success: function (res) {
                    {#location.href = '/index'#}
                }
            }
        )
    })
</script>

AJAX在前端发送的JSON数据,django不能自动解析,因此request.GET或request.POST都为空,JSON数据在请求体request.body中需要手动解析(解码反序列化)。

django响应JSON类型数据:

django视图函数通过import json导入模块,对数据需要返回的数据进行json.dumps()序列化

1.10 ORM中的锁和事务

1.10.1 锁

  • 行级锁

select_for_update()注意必须使用在事务里面,返回一个锁住行直到事务结束的查询集,如果数据库支持,会生成一个SELECT ... FOR UPDATE语句。

例子:

ret = models.Book.objects.select_for_update().filter(id=1)	# 加互斥锁,由于MySQL在查询时自动加的共享锁,所以可以手动加互斥锁。

所有匹配的行将被锁定,直到事务结束。

目前支持select_for_update()的数据库有postgresql、Oracle、MySQL。

1.10.2 事务

  1. 全局开启

在web应用中,常用的事务处理方式是将每个请求都包含在一个事务中。只需要将配置项ATOMIC_REQUESTS设置为True。

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'mxshop',
        'HOST': '127.0.0.1',
        'PORT': '3306',
        'USER': 'root',
        'PASSWORD': '123',
        'OPTIONS': {
            "init_command": "SET default_storage_engine='INNODB'",
       #'init_command': "SET sql_mode='STRICT_TRANS_TABLES'", #配置开启严格sql模式
        }
        "ATOMIC_REQUESTS": True, #全局开启事务,绑定的是http请求响应整个过程
        "AUTOCOMMIT":False, #全局取消自动提交,慎用
    }
}
  1. 局部使用

使用atomic,我们就可以创建一个具备原子性的代码块。一旦代码块正常运行完毕,所有的修改会被提交到数据库。反之,如果有异常,更改会被回滚。

用法一:给函数做装饰器来使用

from django.db import transaction

@transaction.atomic
def viewfunc(request):
    do_stuff()

用法二:作为上下文管理器来使用,其实就是设置事务的保存点

from django.db import transaction

def viewfunc(request):
    do_stuff()
    with transaction.atomic():   #保存点
        do_more_stuff()
    do_other_stuff()

用法三:嵌套使用,函数的事务嵌套上下文管理器的事务,上下文管理器的事务嵌套上下文管理器的事务等。

from django.db import IntegrityError, transaction

@transaction.atomic
def viewfunc(request):
    create_parent()

    try:
        with transaction.atomic():
            generate_relationships()
       #other_task()  #还要注意一点,如果你在事务里面写了别的操作,只有这些操作全部完成之后,事务才会commit,也就是说,如果你这个任务是查询上面更改的数据表里面的数据,那么看到的还是事务提交之前的数据。
    except IntegrityError:
        handle_exception()

    add_children()

1.11 中间件

1.11.1 中间件介绍

中间件是介于request与response处理之间的一道处理过程,相对比较轻量级,并且在全局上改变Django的输入与输出。

Django中的settings文件中的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',
]

1.11.2 自定义中间件

中间件可以自定义五种方法:

  • process_request(self,request)
  • process_view(self, request, view_func, view_args, view_kwargs)
  • process_template_response(self,request,response)
  • process_exception(self, request, exception)
  • process_response(self, request, response)

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

  • 自定义中间件示例

在项目中创建一个目录,创建一个py文件:

from django.utils.deprecation import MiddlewareMixin

class MD1(MiddlewareMixin):
    #自定义中间件,不是必须要有下面这两个方法,有request方法说明请求来了要处理,有response方法说明响应出去时需要处理,不是非要写这两个方法,如果你没写process_response方法,那么会一层一层的往上找,哪个中间件有process_response方法就将返回对象给哪个中间件
    def process_request(self, request):
        print("MD1里面的 process_request")

    def process_response(self, request, response):
        print("MD1里面的 process_response")
        return response

1.11.2.1 process_request

process_request(request)只有一个参数,和视图函数中的request是一样的。

它的返回值可以使None也可以是HttpResponse对象。返回值是None就按照正常流程走下去,交给下一个中间件处理;如果是HttpResponse对象,Django将不执行视图函数,而是将结果直接返回浏览器。

示例:

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")
        pass

注意在settings文件的MIDDLEWARE配置项中注册自定义中间件

1.11.2.2 process_response

process_response(request,response),response是视图函数返回的HttpResponse对象。该方法的返回值也必须是HttpRresponse对象。

class MD1(MiddlewareMixin):

    def process_request(self, request):
        print("MD1里面的 process_request")
        #不必须写return值
    def process_response(self, request, response):#request和response两个参数必须有,名字随便取
        print("MD1里面的 process_response")
        #print(response.__dict__['_container'][0].decode('utf-8')) #查看响应体里面的内容的方法,或者直接使用response.content也可以看到响应体里面的内容,由于response是个变量,直接点击看源码是看不到的,你打印type(response)发现是HttpResponse对象,查看这个对象的源码就知道有什么方法可以用了。
     return response  #必须有返回值,写return response  ,这个response就像一个接力棒一样
        #return HttpResponse('瞎搞') ,如果你写了这个,那么你视图返回过来的内容就被它给替代了

class MD2(MiddlewareMixin):
    def process_request(self, request):
        print("MD2里面的 process_request")
        pass

    def process_response(self, request, response): #request和response两个参数必须要有,名字随便取
        print("MD2里面的 process_response") 
        return response  #必须返回response,不然你上层的中间件就没有拿到httpresponse对象,就会报错

流程图:

1.11.2.3 process_view

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

参数说明:

  • request是HttpRequest对象。
  • view_func是Django即将使用的视图函数。 (它是实际的函数对象,而不是函数的名称作为字符串。)
  • view_args是将传递给视图的位置参数的列表.
  • view_kwargs是将传递给视图的关键字参数的字典。 view_args和view_kwargs都不包含第一个视图参数(request)。

1.11.2.4 process_exception

process_exception(self, request, exception)

参数说明:

  • request:HttpRequest对象
  • exception:视图函数异常产生的Exception对象

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

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

1.12 Form组件

1.12.1 form组件介绍

form组件的主要功能如下:

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

1.12.2 使用form组件实现注册功能

views文件

from django.shortcuts import render,HttpResponse
from django import forms
# Create your views here.

# 按照Django form 组件的要求自定义个类
class RegForm(forms.Form):
    name = forms.CharField(label="用户名")
    pwd = forms.CharField(label="密码")
# form定义的属性名称和之后在前端生成的标签的name属性值相同

def index(request):
    form_obj = RegForm()		# 把上面自定义的类实例化对象
    if request.method == "POST":
        form_obj = RegForm(data=request.POST)	# 把post请求接收到的数据直接传入到类中然后实例化对象
        if form_obj.is_valid():
            return HttpResponse('ok')
    return render(request,"index.html",{"form_obj":form_obj})

HTML文件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="" method="post" novalidate>	{#novalidate 让浏览器不在对数据进行校验#}
    {% csrf_token %}
    {{ form_obj.as_p }}  {#利用传入的form_obj自动创建标签#} 
    <div>
        <input type="submit" value="注册">
    </div>
</form>
</body>
</html>

访问页面HTML:


<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="" method="post" novalidate>
    <input type='hidden' name='csrfmiddlewaretoken' value='xwm2TTupw2nxZ8F1gZZJTKq3bxSrsT0LqewapEHu2GfSekFoaLzpyOAcx0F8E84C' /> 
    <p>
        <label for="id_name">用户名:</label> 
        <input type="text" name="name" required id="id_name" />
    </p>
	<p>
        <label for="id_pws">密码:</label>
        <input type="text" name="pwd" required id="id_pwd" />
    </p>
    <div>
        <input type="submit" value="注册">
    </div>
</form>
</body>
</html>

1.12.3 form 常用字段和插件

字段用于对用户请求数据进行验证,插件用于自动生成标签

  • initial:初始值,input框里的初始值
class RegForm(forms.Form):
    name = forms.CharField(label="用户名",min_length=8,initial="xxx")

  • Error_messages:重写错误信息
class RegForm(forms.Form):
    name = forms.CharField(
        label="用户名",
        min_length=8,
        initial="xxx",
        error_messages={
            "required":"不能为空",
            "min_length":"长度最短8位"
        })
    pwd = forms.CharField(label="密码")

显示结果:

  • password:
pwd = forms.CharField(
    label="密码",
    min_length=3,
    widget=forms.PasswordInput(attrs={'class':'c1'},render_value=True)
)
# 默认在页面输错密码时会清空密码内容,render_value参数设为true时可以保留密码内容。
  • radioselect:单选
class RegForm(forms.Form):
    name = forms.CharField(
        label="用户名",
        min_length=8,
        initial="xxx",
        error_messages={
            "required":"不能为空",
            "min_length":"长度最短8位"
        })
    pwd = forms.CharField(
        label="密码",
        min_length=3,
        widget=forms.PasswordInput(attrs={'class':'c1'},render_value=True)
    )
    sex = forms.ChoiceField(
        choices=((1,"男"),(2,"女")),
        label="性别",
        initial=1,
        widget=forms.RadioSelect()
    )

显示结果:

  • select:单选下拉框
class RegForm(forms.Form):
	...
    hobby = forms.ChoiceField(
        choices=((1,"足球"),(2,"篮球"),(3,"网球")),
        label="爱好",
        initial=2,
        widget=forms.Select()
    )

显示结果:

  • 多选下拉框
class RegForm(forms.Form):
	...
    hobby = forms.MultipleChoiceField(
        choices=((1,"足球"),(2,"篮球"),(3,"网球")),
        label="爱好",
        initial=[1,3],
        widget=forms.SelectMultiple()
    )

显示结果:

  • 单选CheckBox
class RegForm(forms.Form):
	...
    keep = forms.ChoiceField(
        label="是否记住我",
        initial="checked",
        widget=forms.CheckboxInput()
    )

显示结果:

  • 多选CheckBox
class RegForm(forms.Form):
	...
    hobby = forms.MultipleChoiceField(
        choices=((1,"足球"),(2,"篮球"),(3,"网球")),
        label="爱好",
        initial=[1,3],
        widget=forms.CheckboxSelectMultiple()
    )

显示结果:

  • Date 类型
class RegForm(forms.Form):
	...
    date = forms.DateField(widget=forms.TextInput(attrs={'type':'date'}))

必须指定type,否则不能渲染成选择时间的input框。

1.12.4 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,              最小值
    
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': '...'}
    
FileField(Field)
    allow_empty_file=False     是否允许空文件
    
    
ImageField(FileField)      
    ...
    注:需要PIL模块,pip3 install Pillow
    以上两个字典使用时,需要注意两点:
        - form表单中 enctype="multipart/form-data"
        - view函数中 obj = MyForm(request.POST, request.FILES)
        
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二次筛选
    
    
TypedChoiceField(ChoiceField)
    coerce = lambda val: val   对选中的值进行一次转换
    empty_value= ''            空值的默认值
    
TypedMultipleChoiceField(MultipleChoiceField)
    coerce = lambda val: val   对选中的每一个值进行一次转换
    empty_value= ''            空值的默认值
    
ComboField(Field)
    fields=()                  使用多个验证,如下:即验证最大长度20,又验证邮箱格式
                               fields.ComboField(fields=[fields.CharField(max_length=20), fields.EmailField(),])
                               
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类型

1.12.5 字段校验

  • RegexValidator验证器
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'邮箱'}))

1.12.6 hook钩子

  • 局部钩子

在form类中定义clean_字段名()方法,可以实现对特定字段进行校验

例子:

class LoginForm(forms.Form):
    username = forms.CharField(
        min_length=8,
        label="用户名",
        initial="张三",
        error_messages={
            "required": "不能为空",
            "invalid": "格式错误",
            "min_length": "用户名最短8位"
        },
        widget=forms.widgets.TextInput(attrs={"class": "form-control"})
    )
    ...
    # 定义局部钩子,用来校验username字段,之前的校验股则还在,给你提供了一个添加一些校验功能的钩子
    def clean_username(self):
        value = self.cleaned_data.get("username")
        if "666" in value:
            raise ValidationError("光喊666是不行的")
        else:
            return value
  • 全局钩子

在form类中定义clean()方法,可以实现对字段进行全局校验。

class LoginForm(forms.Form):
    ...
    password = forms.CharField(
        min_length=6,
        label="密码",
        widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'}, render_value=True)
    )
    re_password = forms.CharField(
        min_length=6,
        label="确认密码",
        widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'}, render_value=True)
    )
    ...
    # 定义全局的钩子,用来校验密码和确认密码字段是否相同,执行全局钩子的时候,cleaned_data里面肯定是有了通过前面验证的所有数据
    def clean(self):
        password_value = self.cleaned_data.get('password')
        re_password_value = self.cleaned_data.get('re_password')
        if password_value == re_password_value:
            return self.cleaned_data #全局钩子要返回所有的数据
        else:
            self.add_error('re_password', '两次密码不一致') #在re_password这个字段的错误列表中加上一个错误,并且clean_data里面会自动清除这个re_password的值,所以打印clean_data的时候会看不到它
            raise ValidationError('两次密码不一致')

1.13 Admin管理工具

生成项目的时候已经在urls.py中自动设置好:

urlpatterns = [
    url(r'^admin/', admin.site.urls),
]

配置好后,Django管理工具就可以运行了。

使用浏览器中访问http://127.0.0.1:8000/admin/

需要用户名和密码,可以使用python manage.py createsuperuser创建超级用户。

之后输入用户名密码登陆,界面如下:

为了让admin界面管理某个数据模型,需要先注册该数据模型到admin:

# admin.py

from django.contrib import admin
from book.models import Info
# Register your models here.

admin.site.register(Info)

之后就可以查看到数据模型了:

1.14 ModelForm组件

1.14.1 示例

models.py文件

from django.db import models

# Create your models here.
class Author(models.Model):
    nid = models.AutoField(primary_key=True)
    name=models.CharField( max_length=32)
    age=models.IntegerField()
    authorDetail=models.OneToOneField(to="AuthorDetail",to_field="nid",)

    def __str__(self):
        return self.name

class AuthorDetail(models.Model):
    nid = models.AutoField(primary_key=True)
    birthday=models.DateField()
    telephone=models.BigIntegerField()
    addr=models.CharField( max_length=64)

class Publish(models.Model):
    nid = models.AutoField(primary_key=True)
    name=models.CharField( max_length=32)
    city=models.CharField( max_length=32)
    email=models.EmailField()

    def __str__(self):
        return self.name

class Book(models.Model):
    nid = models.AutoField(primary_key=True)
    title = models.CharField( max_length=32)
    publishDate=models.DateField()
    price=models.DecimalField(max_digits=5,decimal_places=2)
    publish=models.ForeignKey(to="Publish",to_field="nid",)
    authors=models.ManyToManyField(to='Author',)

views.py文件:

from app01 import models
from django import forms

# Create your views here.
class BookForm(forms.ModelForm):
    class Meta:
        model = models.Book
        fields = "__all__"	# book表中所有字段都加载出来,也可以单独制定某个字段
        labels = {
            "title":"书名",
            "publishDate":"出版日期",
            "price":"价格",
            "publish":"出版社",
            "authors":"作者"
        }
        widgets = {
            "title":ca(attrs={'a':'b'})
        }			# 指定字段设置属性
        error_messages = {
            'title':{'required':'不能为空',} #每个字段的错误都可以写
        }
    #局部钩子:
    def clean_title(self):
        pass
  #全局钩子
    def clean(self):
        pass
      
    def __init__(self,*args,**kwargs):
        super().__init__(*args,**kwargs)
        for field in self.fields.values():
            field.widget.attrs.update({'class':'form-control'})	# 全局设置样式
            field.error_messages = {'required':'不能为空'} #批量添加错误信息,这是都一样的错误,不一样的还是要单独写。
            
def add(request):
    book_form_obj = BookForm()
    return render(request,'add.html',{'book_form_obj':book_form_obj})

HTML文件:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="/static/js/jquery.js"></script>
    <link rel="stylesheet" href="/static/bootstrap-3.3.7-dist/css/bootstrap.min.css">
    <script src="/static/bootstrap-3.3.7-dist/js/bootstrap.min.js"></script>
</head>
<body>
<div class="row">
    <div class="col-xs-8 col-xs-offset-2">
        <div class="panel panel-primary">
            <div class="panel-heading">
                <h3 class="panel-title">书籍信息</h3>
            </div>
            <div class="panel-body">
                {% for book_form in book_form_obj %}
                    <label for={{ book_form.id_for_label }}>{{ book_form.label }}</label>
                    {{ book_form }}
               <span style="color: red">{{ book_form.errors.0 }}</span>
                {% endfor %}
                <div class="modal-footer">
                    <button type="button" class="btn btn-default" data-dismiss="modal">关闭</button>
                    <button type="button" class="addbook btn btn-primary">添加</button>
                </div>
            </div>
        </div>
    </div>
</div>
<script>
    $('.addbook').click(function () {
        $.ajax(
            {
                url: "{% url 'add' %}",
                type: "post",
                data: {
                    csrfmiddlewaretoken: '{{ csrf_token }}',
                    title: $('#exampletitle').val(),
                    authors: $('#exampleauthor').val(),
                    price: $('#exampleprice').val(),
                    publishDate: $('#exampletime').val(),
                    publish_id: $('#examplepublish').val()
                },
                success: function () {
                    location.href = '/index'
                }
            }
        )
    })
</script>
</body>
</html>

补充:

def edit(request,id=None):
    model_obj = models.Customer.objects.filter(id=id).first()
    customer_obj = CustomerFrom(instance=model_obj)
# instance不写是创建,指定了是更新指定条目对象

1.15 同源和跨域

1.15.1 跨域

同源策略是一种约定,它是浏览器最核心也是最基本的安全功能,可以说web是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。

什么是同源?

所谓的同源是指域名、协议、端口相同。当一个浏览器打开一个应用页面来获取另一个应用的数据时会进行同源检查,如果检查不过会报异常,提示拒绝访问。

  • 简单请求跨域

启动两个Django项目,s1和s2。端口号分别为8001和8002.

s1的views.py:

from django.shortcuts import render

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

s1的urls.py文件:

from django.conf.urls import url
from django.contrib import admin
from apps1 import views

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^index/$', views.index,name="index"),
]

s1的html文件:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
</head>
<body>
<input type="text">
<button type="button" class="button">提交</button>
<script>
    $('.button').click(function () {
        var data = $("[type=text]").val();
        $.ajax({
            url:"http://127.0.0.1:8002/index/",
            type:"post",
            data:{"data":data},
            success:function (res) {
                console.log(res)
            }
        })
    })
</script>
</body>
</html>

s2的views.py文件:

from django.shortcuts import render,HttpResponse

# Create your views here.
def index(request):
    ret = request.POST
    data = ret.get("data")
    return data

s2的urls.py文件:

from django.conf.urls import url
from django.contrib import admin
from apps2 import views

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^index/$', views.index,name="index"),
]

s2的html文件:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
</head>
<body>
<div>...</div>
</body>
</html>

启动两个Django项目,使用s1提交数据来获取s2的数据页面:

可以看到直接提交数据会报错,拒绝访问,这就是同源机制检查未过,这里需要设置参数来允许跨域访问。

在被访问的s2项目中的views.py文件加上设置:

from django.shortcuts import render,HttpResponse
from django.http import JsonResponse
# Create your views here.
def index(request):
    ret = request.POST
    data = JsonResponse({'info':ret.get("data")})	# 必须是json格式
    data["Access-Control-Allow-Origin"] = "http://127.0.0.1:8001"	# 指定只有这个地址和端口的请求才正常返回数据。
    # data["Access-Control-Allow-Origin"] = "*" # 表示所有的地址和端口都可以正常返回数据。
    return data

以上就是一个简单的跨域请求遇到的问题和解决方法。

1.15.2 非简单请求跨域

什么是简单请求?

1..请求方法是HEAD/GET/POST之一

2.HTTP的头信息不超出以下字段:

Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type:只限于application/x-www-form-urlencoded、multipart/form-data、text/plain

不满足以上条件的都是非简单请求。

修改上面的s1的HTML文件:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
</head>
<body>
<input type="text">
<button type="button" class="button">提交</button>
<script>
    $('.button').click(function () {
        var data = $("[type=text]").val();
        $.ajax({
            url:"http://127.0.0.1:8002/index/",
            type:"post",
            contentType:'application/json',		// 修改为非简单请求
            data:JSON.stringify({"data":data}),
            success:function (res) {
                console.log(res)
            }
        })
    })
</script>
</body>
</html>

如果不做其他设置访问会报错:

可以看到这又是另一个报错,我们还需要添加些设置才可以正常返回数据:

s2的views.py文件:

from django.shortcuts import render,HttpResponse
from django.http import JsonResponse
# Create your views here.
def index(request):
    ret = request.POST
    data = JsonResponse({'info':ret.get("data")})
    data["Access-Control-Allow-Origin"] = "http://127.0.0.1:8001"
    data["Access-Control-Allow-Headers"] = "content-type"	# 表示所有数据类型都可以正常返回
    return data

以上是针对数据类型的非简单请求的跨域,下面来针对请求方式的非简单请求的跨域:

s1的html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
</head>
<body>
<input type="text">
<button type="button" class="button">提交</button>
<script>
    $('.button').click(function () {
        var data = $("[type=text]").val();
        $.ajax({
            url:"http://127.0.0.1:8002/index/",
            type:"put",
            contentType:'application/json',
            data:JSON.stringify({"data":data}),
            success:function (res) {
                console.log(res)
            }
        })
    })
</script>
</body>
</html>

解决:

s2的views.py文件:

from django.shortcuts import render,HttpResponse
from django.http import JsonResponse
# Create your views here.
def index(request):
    ret = request.POST
    data = JsonResponse({'info':ret.get("data")})
    data["Access-Control-Allow-Origin"] = "http://127.0.0.1:8001"
    data["Access-Control-Allow-Headers"] = "content-type"
    data["Access-Control-Allow-Methods"] = "PUT,DELETE"	# 设置可以正常返回数据的请求格式
    return data

1.15.3 简单请求和非简单请求的处理区别

简单请求和非简单请求的区别?

  • 简单请求:处理一次
  • 非简单请求:处理两次,在发送正式数据之前会进行“预检”,“预检”通过了才会发送正式数据。

预检的请求方式为:OPTIONS

小结:

  • 支持跨域,简单请求

    • 服务器设置响应头:Access-Control-Allow-Origin = '域名' 或 '*'
  • 支持跨域,复杂请求

    • 由于复杂请求时,首先会发送“预检”请求,如果“预检”成功,则发送真实数据。
    • “预检”请求时,允许请求方式则需服务器设置响应头:Access-Control-Request-Method
    • “预检”请求时,允许请求头则需服务器设置响应头:Access-Control-Request-Headers
posted @   忘川的彼岸  阅读(133)  评论(0编辑  收藏  举报
编辑推荐:
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 一文读懂知识蒸馏
· 终于写完轮子一部分:tcp代理 了,记录一下
点击右上角即可分享
微信分享提示