07 Django的组件

Django的组件

1、中间件

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

Django的中间件的定义:

Middleware is a framework of hooks into Django’s request/response processing. 
It’s a light, low-level “plugin” system for globally altering Django’s input or output.

MiddleWare,是 Django 请求/响应处理的钩子框架。
它是一个轻量级的、低级的“插件”系统,用于全局改变 Django 的输入或输出。【输入指代的就是客户端像服务端django发送数据,输出指代django根据客户端要求处理数据的结果返回给客户端】

如果你想修改请求,例如被传送到view中的HttpRequest对象。 或者你想修改view返回的HttpResponse对象,这些都可以通过中间件来实现。

django框架内部声明了很多的中间件,这些中间件有着各种各种的用途,有些没有被使用,有些被默认开启使用了。而被开启使用的中间件,都是在settngs.py的MIDDLEWARE中注册使用的。

Django默认的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.1.自定义中间件

(1)定义中间件

创建存放自定义中间件的文件这里选择在app01里创建mdws.py文件:

from django.utils.deprecation import MiddlewareMixin


class Md1(MiddlewareMixin):

    def process_request(self, request):
        print("Md1请求")
        # return HttpResponse("Md1中断")    # 拦截

    def process_response(self, request, response):
        print("Md1返回")
        return response


class Md2(MiddlewareMixin):

    def process_request(self, request):
        print("Md2请求")
        # return HttpResponse("Md2中断")

    def process_response(self, request, response):
        print("Md2返回")
        return response

  1. process_request默认返回None,返回None,则继续执行下一个中间件的process_request;一旦返回响应体对象,则会拦截返回。
  2. process_response必须有一个形参response,并return response;这是view函数返回的响应体,像接力棒一样传承给最后的客户端。
    在这里插入图片描述

在这里插入图片描述

(2) 注册中间件
MIDDLEWARE = [
    ...
    'app01.mdws.Md1',
    'app01.mdws.Md2'
]
(3)构建index路由
# path('index/', views.index),


def index(request):
    print("index 视图函数执行...")
    return HttpResponse("hello yuan")

启动项目,访问index路径:
在这里插入图片描述

后台打印结果:

Md1请求
Md2请求
index 视图函数执行...
Md2返回
Md1返回

所以,通过结果我们看出中间件的执行顺序:

在这里插入图片描述

1.3.中间件应用

1. 做IP访问频率限制

某些IP访问服务器的频率过高,进行拦截,比如限制每分钟不能超过20次。

2、Cookie与Session

我们知道HTTP协议是无状态协议,也就是说每个请求都是独立的!无法记录前一次请求的状态。但HTTP协议中可以使用Cookie来完成会话跟踪!在Web开发中,使用session来完成会话跟踪,session底层依赖Cookie技术。

2.1、cookie

Cookie翻译成中文是小甜点,小饼干的意思。在HTTP中它表示服务器送给客户端浏览器的小甜点。其实Cookie是key-value结构,类似于一个python中的字典。随着服务器端的响应发送给客户端浏览器。然后客户端浏览器会把Cookie保存起来,当下一次再访问服务器时把Cookie再发送给服务器。 Cookie是由服务器创建,然后通过响应发送给客户端的一个键值对。客户端会保存Cookie,并会标注出Cookie的来源(哪个服务器的Cookie)。当客户端向服务器发出请求时会把所有这个服务器Cookie包含在请求中发送给服务器,这样服务器就可以识别客户端了!

cookie可以理解为每一个浏览器针对每一个服务器创建的key-value结构的本地存储文件

(1)cookie流程图

在这里插入图片描述

(2)cookie语法
# (1) 设置cookie:
res = HttpResponse(...) 或 rep = render(request, ...) 或 rep = redirect() 
res.set_cookie(key,value,max_age...)
res.set_signed_cookie(key,value,salt='加密盐',...) 
# (2) 获取cookie:
request.COOKIES  
# (3) 删除cookie
response.delete_cookie("cookie_key",path="/",domain=name)

2.2、session

Django 提供对匿名会话(session)的完全支持。这个会话框架让你可以存储和取回每个站点访客任意数据。它在服务器端存储数据, 并以cookies的形式进行发送和接受数据。

(1)session流程图

在这里插入图片描述

(2)session语法与案例
# 1、设置Sessions值
       request.session['session_name'] ="admin"
# 2、获取Sessions值
       session_name = request.session["session_name"]
# 3、删除Sessions值
       del request.session["session_name"]
# 4、flush()
  # 删除当前的会话数据并删除会话的Cookie。这用于确保前面的会话数据不可以再次被用户的浏览器访问
def s_login(request):
    if request.method == "GET":
        return render(request, "login.html")
    else:
        user = request.POST.get("user")
        pwd = request.POST.get("pwd")
        try:
            # user_obj = User.objects.get(user=user,pwd=pwd)
            # 写session
            # request.session["is_login"] = True
            # request.session["username"] = user_obj.user
            return redirect("/s_index/")
        except:
            return redirect("/s_login/")

          
def s_index(request):
    # 读session
    is_login = request.session.get("is_login")
    if is_login:
        username = request.session.get("username")
        return render(request, "index.html", {"user": username})
    else:
        return redirect("/s_login/")
'''
shop.html:
<p>
客户端最后一次访问时间:{{ last_time|default:"第一次访问" }}
</p>
<h3>商品页面</h3>
'''

def shop(request):
    last_time = request.session.get("last_time")
    now = datetime.datetime.now().strftime("%Y-%m-%d %X")
    request.session["last_time"] = now

    return render(request, "shop.html", {"last_time": last_time})


def s_logout(request):
    # request.session.flush()
    del request.session["username"]
    del request.session["is_login"]
    return redirect("/s_login/")

  1. session 在服务器端,cookie 在客户端(浏览器)
  2. session 默认被存在在服务器的一个文件里(不是内存)
  3. session 的运行依赖 session id,而 session id 是存在 cookie 中的.
  4. session 可以放在 文件、数据库、或内存中都可以。
  5. 用户验证这种场合一般会用 session
(3)session配置
# Django默认支持Session,并且默认是将Session数据存储在数据库中,即:django_session 表中。
   
# 配置 settings.py
   
    SESSION_ENGINE = 'django.contrib.sessions.backends.db'   # 引擎(默认)
       
    SESSION_COOKIE_NAME = "sessionid"               # Session的cookie保存在浏览器上时的key,即:sessionid=随机字符串(默认)
    SESSION_COOKIE_PATH = "/"                               # Session的cookie保存的路径(默认)
    SESSION_COOKIE_DOMAIN = None                             # Session的cookie保存的域名(默认)
    SESSION_COOKIE_SECURE = False                            # 是否Https传输cookie(默认)
    SESSION_COOKIE_HTTPONLY = True                           # 是否Session的cookie只支持http传输(默认)
    SESSION_COOKIE_AGE = 1209600                             # Session的cookie失效日期(2周)(默认)
    SESSION_EXPIRE_AT_BROWSER_CLOSE = False                  # 是否关闭浏览器使得Session过期(默认)
    SESSION_SAVE_EVERY_REQUEST = False                       # 是否每次请求都保存Session,默认修改之后才保存(默认)

2.3、用户认证组件

Django默认已经提供了认证系统Auth模块,我们认证的时候,会使用auth模块里面给我们提供的表。认证系统包含:

  • 用户管理
  • 权限
  • 用户组
  • 密码哈希系统
  • 用户登录或内容显示的表单和视图
  • 一个可插拔的后台系统 admin
(1)Django用户模型类

Django认证系统中提供了用户模型类User保存用户的数据,默认的User包含以下常见的基本字段:

字段名字段描述
username必选。150个字符以内。 用户名可能包含字母数字,_@+ .-个字符。
first_name可选(blank=True)。 少于等于30个字符。
last_name可选(blank=True)。 少于等于30个字符。
email可选(blank=True)。 邮箱地址。
password必选。 密码的哈希加密串。 (Django 不保存原始密码)。 原始密码可以无限长而且可以包含任意字符。
groupsGroup 之间的多对多关系。
user_permissionsPermission 之间的多对多关系。
is_staff布尔值。 设置用户是否可以访问Admin 站点。
is_active布尔值。 指示用户的账号是否激活。 它不是用来控制用户是否能够登录,而是描述一种帐号的使用状态。
is_superuser是否是超级用户。超级用户具有所有权限。
last_login用户最后一次登录的时间。
date_joined账户创建的时间。 当账号创建时,默认设置为当前的date/time。

上面缺少一些字段,所以后面我们会对当前内置的用户模型进行改造,比如说它里面没有手机号字段,后面我们需要加上。

(2)重要方法

Django 用户认证(Auth)组件需要导入 auth 模块

# 认证模块
from django.contrib import auth
# 对应数据库用户表,可以继承扩展
from django.contrib.auth.models import User
(1)用户对象
create() # 创建一个普通用户,密码是明文的。
create_user() # 创建一个普通用户,密码是密文的。
create_superuser() # 与create_user() 相同,但是设置is_staff 和is_superuser 为True。

set_password(*raw_password*)
# 设置用户的密码为给定的原始字符串,并负责密码的。 不会保存User对象。当None为raw_password时,密码将设置为一个不可用的密码。
check_password(*raw_password*)
# 如果给定的raw_password是用户的真实密码,则返回True,可以在校验用户密码时使用。
(2)认证方法
auth.authenticate(username,password) 
# 将输入的密码转为密文去认证,认证成功返回用户对象,失败则返回None
(3)登录和注销方法
from django.contrib import auth
# 该函数接受一个HttpRequest对象,以及一个认证了的User对象。此函数使用django的session框架给某个已认证的用户附加上session id等信息。
auth.login() 
# 该函数接受一个HttpRequest对象,无返回值。当调用该函数时,当前请求的session信息会全部清除。该用户即使没有登录,使用该函数也不会报错。
auth.logout() 
(4)request.user
"""
Django有一个默认中间件,叫做AuthenticationMiddleware,每次请求进来都会去session中去一个userid,取不到的话,赋值request.user = AnonymousUser() , 一个匿名用户对象。

当用户组件auth.login一旦执行,将userid到session中后,再有请求进入Django,将注册的userid对应的user对象赋值给request.user,即再后面的任何视图函数中都可以从request.user中取到该客户端的登录对象。需要该对象的任何属性:request.user.属性名
"""
user_id = request.session.get('user_id')
user = User.objects.get(pk=user_id)
if user:
    return user
return AnonymousUser() # 该匿名对象的所有属性都为零
(5)自定义用户表

from django.contrib.auth.models import AbstractUser

设置Auth认证模块使用的用户模型为我们自己定义的用户模型

格式:“子应用目录名.模型类名”

AUTH_USER_MODEL = ‘users.User’

3、Django的分页器

批量添加

def create_muilt(request):
    book_list = []
    for i in range(1, 1001):
        book = BookInfo(title="book" + str(i), price=i * 2)
        book_list.append(book)

    BookInfo.objects.bulk_create(book_list)
    return HttpResponse('添加成功')

from django.core.paginator import Paginator

在这里插入图片描述

(1) index视图
def index(request):
    '''
    批量导入数据:

    Booklist=[]
    for i in range(100):
        Booklist.append(Book(title="book"+str(i),price=30+i*i))
    Book.objects.bulk_create(Booklist)



    分页器的使用:

    book_list=Book.objects.all()

    paginator = Paginator(book_list, 10)

    print("count:",paginator.count)           #数据总数
    print("num_pages",paginator.num_pages)    #总页数
    print("page_range",paginator.page_range)  #页码的列表



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

    


    page2=paginator.page(2)

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



    # 抛错
    #page=paginator.page(12)   # error:EmptyPage

    #page=paginator.page("z")   # error:PageNotAnInteger

    '''

    book_list = Book.objects.all()

    paginator = Paginator(book_list, 10)
    page = request.GET.get('page', 1)
    current_page = int(page)

    try:
        print(page)
        book_list = paginator.page(page)
    except PageNotAnInteger:
        book_list = paginator.page(1)
    except EmptyPage:
        book_list = paginator.page(paginator.num_pages)

    return render(request, "index.html", {"book_list": book_list, "paginator": paginator, "currentPage": current_page})

(2) index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" 
    integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
</head>
<body>

<div class="container">

    <h4>分页器</h4>
    <ul>

        {% for book in book_list %}
             <li>{{ book.title }} -----{{ book.price }}</li>
        {% endfor %}

     </ul>


    <ul class="pagination" id="pager">

                 {% if book_list.has_previous %}
                    <li class="previous"><a href="/index/?page={{ book_list.previous_page_number }}">上一页</a></li>
                 {% else %}
                    <li class="previous disabled"><a href="#">上一页</a></li>
                 {% endif %}


                 {% for num in paginator.page_range %}

                     {% if num == currentPage %}
                       <li class="item active"><a href="/index/?page={{ num }}">{{ num }}</a></li>
                     {% else %}
                       <li class="item"><a href="/index/?page={{ num }}">{{ num }}</a></li>

                     {% endif %}
                 {% endfor %}



                 {% if book_list.has_next %}
                    <li class="next"><a href="/index/?page={{ book_list.next_page_number }}">下一页</a></li>
                 {% else %}
                    <li class="next disabled"><a href="#">下一页</a></li>
                 {% endif %}

            </ul>
</div>



</body>
</html>

4、FBV与CBV

1 FBV :function based view

2 CBV:class based view

4.1、前后端分离模式

在开发Web应用中,有两种应用模式:

  1. 前后端不分离[客户端看到的内容和所有界面效果都是由服务端提供出来的。]
    在这里插入图片描述

  2. 前后端分离【把前端的界面效果(html,css,js分离到另一个服务端,python服务端只需要返回数据即可)】

前端形成一个独立的网站,服务端构成一个独立的网站

在这里插入图片描述

4.2、api接口

应用程序编程接口(Application Programming Interface,API接口),就是应用程序对外提供了一个操作数据的入口,这个入口可以是一个函数或类方法,也可以是一个url地址或者一个网络地址。当客户端调用这个入口,应用程序则会执行对应代码操作,给客户端完成相对应的功能。

当然,api接口在工作中是比较常见的开发内容,有时候,我们会调用其他人编写的api接口,有时候,我们也需要提供api接口给其他人操作。由此就会带来一个问题,api接口往往都是一个函数、类方法、或者url或其他网络地址,不断是哪一种,当api接口编写过程中,我们都要考虑一个问题就是这个接口应该怎么编写?接口怎么写的更加容易维护和清晰,这就需要大家在调用或者编写api接口的时候要有一个明确的编写规范!!!

为了在团队内部形成共识、防止个人习惯差异引起的混乱,我们都需要找到一种大家都觉得很好的接口实现规范,而且这种规范能够让后端写的接口,用途一目了然,减少客户端和服务端双方之间的合作成本。

目前市面上大部分公司开发人员使用的接口实现规范主要有:restful、RPC。

RPC( Remote Procedure Call ): 翻译成中文:远程过程调用[远程服务调用]. 从字面上理解就是访问/调用远程服务端提供的api接口。这种接口一般以服务或者过程式代码提供。

  • 服务端提供一个唯一的访问入口地址:http://api.xxx.com/ 或 http://www.xx.com/api 或者基于其他协议的地址

  • 客户端请求服务端的时候,所有的操作都理解为动作(action),一般web开发时,对应的就是HTTP请求的post请求

  • 通过请求体参数,指定要调用的接口名称和接口所需的参数

    action=get_all_student&class=301&sex=1

    m=get_all_student&sex=1&age=22

    command=100&sex=1&age=22

rpc接口多了,对应函数名和参数就多了,前端在请求api接口时难找.对于年代久远的rpc服务端的代码也容易出现重复的接口

restful: 翻译成中文: 资源状态转换.(表征性状态转移)

  • 把服务端提供的所有的数据/文件都看成资源, 那么通过api接口请求数据的操作,本质上来说就是对资源的操作了.

    因此,restful中要求,我们把当前接口对外提供哪种资源进行操作,就把资源的名称写在url地址

  • web开发中操作资源,最常见的最通用的无非就是增删查改,所以restful要求在地址栏中声明要操作的资源是什么。然后通过http请求动词来说明对该资源进行哪一种操作.

    POST http://www.xxx.com/api/students/ 添加学生数据

    GET http://www.xxx.com/api/students/ 获取所有学生

    GET http://www.xxx.com/api/students/1/ 获取id=pk的学生

    DELETE http://www.xxx.com/api/students/1/ 删除id=pk的一个学生

    PUT http://www.xxx.com/api/students/1/ 修改一个学生的全部信息 [id,name,sex,age,]

    PATCH http://www.xxx.com/api/students/1/ 修改一个学生的部分信息[age]

也就是说,我们仅需要通过url地址上的资源名称结合HTTP请求动作,就可以说明当前api接口的功能是什么了。restful是以资源为主的api接口规范,体现在地址上就是资源就是以名词表达。rpc则以动作为主的api接口规范,体现在接口名称上往往附带操作数据的动作。

4.3、RESTful API规范

REST全称是Representational State Transfer,中文意思是表述(编者注:通常译为表征)性状态转移。 它首次出现在2000年Roy Fielding的博士论文中。

RESTful是一种专门为Web 开发而定义API接口的设计风格,尤其适用于前后端分离的应用模式中。

这种风格的理念认为后端开发任务就是提供数据的,对外提供的是数据资源的访问接口,所以在定义接口时,客户端访问的URL路径就表示这种要操作的数据资源。

而对于数据资源分别使用POST、DELETE、GET、UPDATE等请求动作来表达对数据的增删查改。

GET/students获取所有学生
请求方法请求地址后端操作
POST/students增加学生
GET/students/获取编号为pk的学生
PUT/students/修改编号为pk的学生
DELETE/students/删除编号为pk的学生

restful规范是一种通用的规范,不限制语言和开发框架的使用。事实上,我们可以使用任何一门语言,任何一个框架都可以实现符合restful规范的API接口。

参考文档:http://www.runoob.com/w3cnote/restful-architecture.html

接口实现过程中,会存在幂等性。所谓幂等性是指代客户端发起多次同样请求时,是否对于服务端里面的资源产生不同结果。如果多次请求,服务端结果还是一样,则属于幂等接口,如果多次请求,服务端产生结果是不一样的,则属于非幂等接口

请求方式是否幂等是否安全
GET幂等安全
POST不幂等不安全
PUT/PATCH幂等不安全
DELETE幂等不安全

4.4、CBV使用

之前我们用的视图函数叫FBV(也就是函数型视图函数),这里我们来试试CBV(类视图函数)的写法。类视图函数可以让代码看起来更简洁,用起来更方便。

# FBV
# def index(request):
#     if request.method == "GET":
#
#         return HttpResponse("GET")
#     elif request.method == "POST":
#
#         return HttpResponse("POST")
#
#     elif request.method == "DELETE":
#         return HttpResponse("DELETE")

# CBV模式: 基于restful开发

class IndexView(View):

    def get(self, request):
        return HttpResponse("CBV GET")

    def post(self, request):
        return HttpResponse("CBV POST")


class BookView(View):

    def get(self, request):
        # 获取数据
        book_list = Book.objects.all()

        # 序列化:json
        data_json = serializers.serialize("json", book_list)

        return HttpResponse(data_json, content_type="json")

# FBV模式
# path('index/', views.index),
# CBV模式
path("index/",views.IndexView.as_view()),
path("books/",views.BookView.as_view())

5、csrftoken(跨站请求伪造)

CSRF(Cross-Site Request Forgery,跨站点伪造请求)是一种网络攻击方式,该攻击可以在受害者毫不知情的情况下以受害者名义伪造请求发送给受攻击站点,从而在未授权的情况下执行在权限保护之下的操作,具有很大的危害性。具体来讲,可以这样理解CSRF攻击:攻击者盗用了你的身份,以你的名义发送恶意请求,对服务器来说这个请求是完全合法的,但是却完成了攻击者所期望的一个操作,比如以你的名义发送邮件、发消息,盗取你的账号,添加系统管理员,甚至于购买商品、虚拟货币转账等。

-- 企业邮箱
-- pornhub.com

参考文章

token其实就是一个令牌,用于用户验证的,token的诞生离不开CSRF。正是由于上面的Cookie/Session的状态保持方式会出现CSRF,所以才有了token。

  • 解除中间件注释
    在这里插入图片描述

  • 无csrf_token数据的post请求

在这里插入图片描述

5.1、基本使用

一、form表单提交

在html页面form表单中直接添加{% csrf_token%}
在这里插入图片描述

二、ajax提交

方式1:放在请求数据中。

$.ajax({
  url: '/csrf_test/',
  method: 'post',
  data: {'name': $('[name="name"]').val(),
         'password': $('[name="password"]').val(),
         'csrfmiddlewaretoken':$('[name="csrfmiddlewaretoken"]').val()           
        },
  success: function (data) {
    console.log('成功了')
    console.log(data)           },
})

方式2:放在请求头

$.ajax({
            url: '/csrf_test/',
            method: 'post',
            headers:{'X-CSRFToken':'token值'},  // 注意放到引号里面
            data:{}
}
django服务器中对比的是cookie的token和发送的数据中带有的token,只用这两个token匹配上才成功(并不是一个字符一个字符之间进行的比较)

5.2、全局使用,局部禁csrf

(1) 在视图函数上加装饰器
from django.views.decorators.csrf import csrf_exempt,csrf_protect
@csrf_exempt
def 函数名(request):  # 加上装饰器后,这个视图函数,就没有csrf校验了
(2) 视图类
from django.views.decorators.csrf import csrf_exempt,csrf_protect
from django.utils.decorators import method_decorator
@method_decorator(csrf_exempt,name='dispatch')
class index(View):
    def get(self,request):
        return HttpResponse("GET")
    def post(self,request):
        return HttpResponse("POST")

6、ORM进阶

6.1、queryset特性

(1)可切片

使用Python 的切片语法来限制查询集记录的数目 。它等同于SQL 的LIMITOFFSET 子句。

>>> Article.objects.all()[:5]   # (LIMIT 5)
>>> Article.objects.all()[5:10]    # (OFFSET 5 LIMIT 5)

不支持负的索引(例如Article.objects.all()[-1])。通常,查询集 的切片返回一个新的查询集 —— 它不会执行查询。

(2)可迭代
articleList=models.Article.objects.all()

for article in articleList:
    print(article.title)
(3)惰性查询

查询集 是惰性执行的 —— 创建查询集不会带来任何数据库的访问。你可以将过滤器保持一整天,直到查询集 需要求值时,Django 才会真正运行这个查询。

queryResult=models.Article.objects.all() # not hits database
print(queryResult) # hits database
for article in queryResult:
    print(article.title)    # hits database

一般来说,只有在“请求”查询集 的结果时才会到数据库中去获取它们。当你确实需要结果时,查询集 通过访问数据库来求值。 关于求值发生的准确时间。

(4)缓存机制

每个查询集都包含一个缓存来最小化对数据库的访问。理解它是如何工作的将让你编写最高效的代码。

在一个新创建的查询集中,缓存为空。首次对查询集进行求值 —— 同时发生数据库查询 ——Django 将保存查询的结果到查询集的缓存中并返回明确请求的结果(例如,如果正在迭代查询集,则返回下一个结果)。接下来对该查询集 的求值将重用缓存的结果。

请牢记这个缓存行为,因为对查询集使用不当的话,它会坑你的。例如,下面的语句创建两个查询集,对它们求值,然后扔掉它们:

queryset = Book.objects.all()
print(queryset) # hit database
print(queryset) # hit database

注:简单地打印查询集不会填充缓存。

这意味着相同的数据库查询将执行两次,显然倍增了你的数据库负载。同时,还有可能两个结果列表并不包含相同的数据库记录,因为在两次请求期间有可能有Article被添加进来或删除掉。为了避免这个问题,只需保存查询集并重新使用它:

queryset = Book.objects.all()
ret = [i for i in queryset] # hit database
print(queryset) # 使用缓存
print(queryset) # 使用缓存

何时查询集会被缓存?

  1. 遍历queryset时
  2. if语句(为了避免这个,可以用exists()方法来检查是否有数据)

所以单独queryset的索引或者切片都不会缓存。

queryset = Book.objects.all()
one = queryset[0] # hit database
two = queryset[1] # hit database
print(one)
print(two)
(5)exists()与iterator()方法
exists

简单的使用if语句进行判断也会完全执行整个queryset并且把数据放入cache,虽然你并不需要这些 数据!为了避免这个,可以用exists()方法来检查是否有数据:

 if queryResult.exists():
    #SELECT (1) AS "a" FROM "blog_article" LIMIT 1; args=()
        print("exists...")
iterator

当queryset非常巨大时,cache会成为问题。

处理成千上万的记录时,将它们一次装入内存是很浪费的。更糟糕的是,巨大的queryset可能会锁住系统 进程,让你的程序濒临崩溃。要避免在遍历数据的同时产生queryset cache,可以使用iterator()方法 来获取数据,处理完数据就将其丢弃。

objs = Book.objects.all().iterator()
# iterator()可以一次只从数据库获取少量数据,这样可以节省内存
for obj in objs:
    print(obj.title)
#BUT,再次遍历没有打印,因为迭代器已经在上一次遍历(next)到最后一次了,没得遍历了
for obj in objs:
    print(obj.title)

当然,使用iterator()方法来防止生成cache,意味着遍历同一个queryset时会重复执行查询。所以使 #用iterator()的时候要当心,确保你的代码在操作一个大的queryset时没有重复执行查询。

queryset的cache是用于减少程序对数据库的查询,在通常的使用下会保证只有在需要的时候才会查询数据库。 使用exists()和iterator()方法可以优化程序对内存的使用。不过,由于它们并不会生成queryset cache,可能 会造成额外的数据库查询。

总结:在使用缓存机制还是生成器机制的选择上如果是,数据量大情况主要使用生成器;数据少使用次数多的情况使用缓存机制。

6.2、中介模型

处理类似搭配 pizza 和 topping 这样简单的多对多关系时,使用标准的ManyToManyField 就可以了。但是,有时你可能需要关联数据到两个模型之间的关系上。

例如,有这样一个应用,它记录音乐家所属的音乐小组。我们可以用一个ManyToManyField 表示小组和成员之间的多对多关系。但是,有时你可能想知道更多成员关系的细节,比如成员是何时加入小组的。

对于这些情况,Django 允许你指定一个中介模型来定义多对多关系。 你可以将其他字段放在中介模型里面。源模型的ManyToManyField 字段将使用through 参数指向中介模型。对于上面的音乐小组的例子,代码如下:

from django.db import models
 
class Person(models.Model):
    name = models.CharField(max_length=128)
 
    def __str__(self):              # __unicode__ on Python 2
        return self.name
 
class Group(models.Model):
    name = models.CharField(max_length=128)
    members = models.ManyToManyField(Person, through='Membership')
 
    def __str__(self):              # __unicode__ on Python 2
        return self.name
 
class Membership(models.Model):
    person = models.ForeignKey(Person)
    group = models.ForeignKey(Group)
    date_joined = models.DateField()
    invite_reason = models.CharField(max_length=64)

既然你已经设置好ManyToManyField 来使用中介模型(在这个例子中就是Membership),接下来你要开始创建多对多关系。你要做的就是创建中介模型的实例:

>>> ringo = Person.objects.create(name="Ringo Starr")
>>> paul = Person.objects.create(name="Paul McCartney")
>>> beatles = Group.objects.create(name="The Beatles")
>>> m1 = Membership(person=ringo, group=beatles,
...     date_joined=date(1962, 8, 16),
...     invite_reason="Needed a new drummer.")
>>> m1.save()
>>> beatles.members.all()
[<Person: Ringo Starr>]
>>> ringo.group_set.all()
[<Group: The Beatles>]
>>> m2 = Membership.objects.create(person=paul, group=beatles,
...     date_joined=date(1960, 8, 1),
...     invite_reason="Wanted to form a band.")
>>> beatles.members.all()
[<Person: Ringo Starr>, <Person: Paul McCartney>]

与普通的多对多字段不同,你不能使用addcreate和赋值语句(比如,beatles.members = [...])来创建关系:

# THIS WILL NOT WORK
>>> beatles.members.add(john)
# NEITHER WILL THIS
>>> beatles.members.create(name="George Harrison")
# AND NEITHER WILL THIS
>>> beatles.members = [john, paul, ringo, george]

为什么不能这样做? 这是因为你不能只创建 PersonGroup之间的关联关系,你还要指定 Membership模型中所需要的所有信息;而简单的addcreate 和赋值语句是做不到这一点的。所以它们不能在使用中介模型的多对多关系中使用。此时,唯一的办法就是创建中介模型的实例。

remove()方法被禁用也是出于同样的原因。但是clear() 方法却是可用的。它可以清空某个实例所有的多对多关系:

>>> # Beatles have broken up
>>> beatles.members.clear()
>>> # Note that this deletes the intermediate model instances
>>> Membership.objects.all()
[]

6.3、数据库表反向生成模型类

众所周知,Django较为适合原生开发,即通过该框架搭建一个全新的项目,通过在修改models.py来创建新的数据库表。但是往往有时候,我们需要利用到之前的已经设计好的数据库,数据库中提供了设计好的多种表单。那么这时如果我们再通过models.py再来设计就会浪费很多的时间。所幸Django为我们提供了inspecdb的方法。他的作用即使根据已经存在对的mysql数据库表来反向映射结构到models.py中.

我们在展示django ORM反向生成之前,我们先说一下怎么样正向生成代码。

正向生成,指的是先创建model.py文件,然后通过django内置的编译器,在数据库如mysql中创建出符合model.py的表。

反向生成,指的是先在数据库中create table,然后通过django内置的编译器,生成model代码。

python manage.py inspectdb > models文件名

6.4、查询优化

(1)select_related()

对于一对一字段(OneToOneField)和外键字段(ForeignKey),可以使用select_related 来对QuerySet进行优化。

select_related 返回一个QuerySet,当执行它的查询时它沿着外键关系查询关联的对象的数据。它会生成一个复杂的查询并引起性能的损耗,但是在以后使用外键关系时将不需要数据库查询。

简单说,在对QuerySet使用select_related()函数后,Django会获取相应外键对应的对象,从而在之后需要的时候不必再查询数据库了。

下面的例子解释了普通查询和select_related() 查询的区别。

查询id=2的的书籍的出版社名称,下面是一个标准的查询:

# Hits the database.
book= models.Book.objects.get(nid=2)
# Hits the database again to get the related Blog object.
print(book.publish.name)

如果我们使用select_related()函数:

books=models.Book.objects.select_related("publish").all()
for book in books:
     #  Doesn't hit the database, because book.publish
     #  has been prepopulated in the previous query.
     print(book.publish.name)
多外键查询

这是针对publish的外键查询,如果是另外一个外键呢?让我们一起看下:

book=models.Book.objects.select_related("publish").get(nid=1)
print(book.authors.all())

观察logging结果,发现依然需要查询两次,所以需要改为:

book=models.Book.objects.select_related("publish","").get(nid=1)
print(book.publish)

或者:

book=models.Article.objects
             .select_related("publish")
             .select_related("")
             .get(nid=1)  # django 1.7 支持链式操作
print(book.publish)
(2)prefetch_related()

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

prefetch_related()和select_related()的设计目的很相似,都是为了减少SQL查询的数量,但是实现的方式不一样。后者是通过JOIN语句,在SQL查询内解决问题。但是对于多对多关系,使用SQL语句解决就显得有些不太明智,因为JOIN得到的表将会很长,会导致SQL语句运行时间的增加和内存占用的增加。若有n个对象,每个对象的多对多字段对应Mi条,就会生成Σ(n)Mi 行的结果表。

prefetch_related()的解决方法是,分别查询每个表,然后用Python处理他们之间的关系。

# 查询所有文章关联的所有标签
books=models.Book.objects.all()
for book in books:
  	print(book.authors.all())  #4篇文章: hits database 5

改为prefetch_related:

# 查询所有文章关联的所有标签
books=models.Book.objects.prefetch_related("authors").all()
for book in books:
  	print(book.authors.all())  #4篇文章: hits database 2
(3)extra
extra(select=None, where=None, params=None, 
      tables=None, order_by=None, select_params=None)

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

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

参数之select

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

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

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

参数之where / tables

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

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

举例来讲:

queryResult=models.Article
           .objects.extra(where=['nid in (3,4) OR title like "py%" ','nid>2'])

7、上传文件

7.1、form表单上传文件

<h3>form表单上传文件</h3>


<form action="/upload_file/" method="post" enctype="multipart/form-data">
    <p><input type="file" name="upload_file_form"></p>
    <input type="submit">
</form>

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

def upload_file(request):
    print("FILES:",request.FILES)
    print("POST:",request.POST)
    return HttpResponse("上传成功!")

7.2、Ajax(基于FormData)

FormData是什么呢?

XMLHttpRequest Level 2添加了一个新的接口FormData.利用FormData对象,我们可以通过JavaScript用一些键值对来模拟一系列表单控件,我们还可以使用XMLHttpRequest的send()方法来异步的提交这个"表单".比起普通的ajax,使用FormData的最大优点就是我们可以异步上传一个二进制文件.

所有主流浏览器的较新版本都已经支持这个对象了,比如Chrome 7+、Firefox 4+、IE 10+、Opera 12+、Safari 5+。

<h3>Ajax上传文件</h3>

<p><input type="text" name="username" id="username" placeholder="username"></p>
<p><input type="file" name="upload_file_ajax" id="upload_file_ajax"></p>

<button id="upload_button">提交</button>
{#注意button标签不要用在form表单中使用#}

<script>
    $("#upload_button").click(function(){

        var username=$("#username").val();
        var upload_file=$("#upload_file_ajax")[0].files[0];

        var formData=new FormData();
        formData.append("username",username);
        formData.append("upload_file_ajax",upload_file);


        $.ajax({
            url:"/upload_file/",
            type:"POST",
            data:formData,
            contentType:false,
            processData:false,

            success:function(){
                alert("上传成功!")
            }
        });


    })
</script>

def index(request):
  
    return render(request,"index.html")
  
  
def upload_file(request):
    print("FILES:",request.FILES)
    print("POST:",request.POST)
    return HttpResponse("上传成功!")

7.3、ImageField 和 FileField

ImageField 和 FileField 可以分别对图片和文件进行上传到指定的文件夹中。

  1. 在下面的 models.py 中 :

picture = models.ImageField(upload_to=‘avatars/’, default=“avatars/default.png”,blank=True, null=True) 注:定义 ImageField 字段时必须制定参数 upload_to这个字段要写相对路径,

这个参数会加在 settings.py 中的 MEDIA_ROOT后面, 形成一个路径, 这个路径就是上 传图片的存放位置,默认在Django项目根路径下,也就是MEDIA_ROOT默认是Django根目录

所以要先设置好 mysite/settings.py中的 settings.py 中的 MEDIA_ROOT

class Userinfo(models.Model):
    name = models.CharField(max_length=32)
    avatar_img = models.FileField("avatars/") 
username = request.POST.get("username")
#获取文件对象
file = request.FILES.get("file")   
#插入数据,将图片对象直接赋值给字段
user = Userinfo.objects.create(name=username,avatar_img=file)

Django会在项目的根目录创建avatars文件夹,将上传文件下载到该文件夹中,avatar字段保存的是文件的相对路径。

  1. 在 mysite/settings.py中 :
MEDIA_ROOT = os.path.join(BASE_DIR,"media") # 存放时加上media
MEDIA_URL='/media/' # 输出时加上media

MEDIA_ROOT:存放 media 的路径, 这个值加上 upload_to的值就是真实存放上传图片文件位置

MEDIA_URL:给这个属性设值之后,静态文件的链接前面会加上这个值,如果设置这个值,则UserInfo.avatar.url自动替换成:/media/avatars/default.png,可以在模板中直接调用:

3.url.py:

from django.views.static import serve
# 添加media 配置
re_path(r'^media/(?P<path>.*)$', serve, {'document_root': settings.MEDIA_ROOT}),

浏览器可以直接访问http://127.0.0.1:8000/media/yuan/avatars/%E7%86%8A%E7%8C%AB.webp,即我们的用户上传文件。

最后再给大家补充一个用户文件夹路径

def user_directory_path(instance, filename):
    return os.path.join(instance.name,"avatars", filename)

class Userinfo(models.Model):
    name = models.CharField(max_length=32)
    avatar_img = models.FileField(upload_to=user_directory_path)  

4.FileField 和 ImageFiled 相同。

7.4、导入表格批量创建数据

# Create your tests here.
def multi_create(request):
    # 将接受到的excel文件转到mysql
    # (1)下载文件到服务器

    emp_excel = request.FILES.get("emp_excel")
    print(emp_excel) # 期末题目(陕西联通).xlsx
    print(emp_excel.name) # "期末题目(陕西联通).xlsx"
    # 下载文件
    with open("files/"+emp_excel.name,"wb") as f:

        for line in emp_excel:
            f.write(line)


    # 读取excel,批量导入到mysql中

    import os
    from openpyxl import load_workbook
    file_path = os.path.join("files",emp_excel.name)

    # 加载某一个excel文件
    wb = load_workbook(file_path)
    # 获取sheet对象
    print("wb.sheetnames",wb.sheetnames)
    worksheet = wb.worksheets[0]

    for row in worksheet.iter_rows(min_row=3):
        print(row)
        if row[0].value == None:
            break
        Employee.objects.create(name=row[0].value,
                                age=row[1].value,
                                ruzhi_date=row[2].value,
                                dep=row[3].value,
                                salary=row[4].value,
                                )



    return redirect("/index/")
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值