• No.017-Python-学习之路-Django


    目录

    Python的WEB框架

    一. WEB框架简介

    web框架的本质即一个socket服务端,所有的web框架本质就是下面一个socketServer;

    import socket
    
    def hand_request(client):
        buf = client.recv(1024)
        print(buf)
        client.send(bytes("HTTP/1.1 200 OK
    
    ", encoding="utf-8"))
        client.send(bytes("<h1 style='background-color:red;'>Hello World", encoding="utf-8"))
    
    def main():
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.bind(('localhost', 8000))
        sock.listen(5)
    
        while True:
            connection, address = sock.accept()
            hand_request(connection)
            connection.close()
    
    if __name__ == '__main__':
        main()
    

    1.1 一个简单的web框架

    如下代码中,取自environ中的一写用户反过来信息,然后根据信息进行处理;

    from wsgiref.simple_server import make_server
    
    def handle_index():
        return ['<h1>Hello, index!</h1>'.encode("utf-8"), ]
    
    def handle_date():
        return ['<h1>Hello, date!</h1>'.encode("utf-8"), ]
    
    def RunServer(environ, start_response):
        # environ封装所有的客户端发来的所有数据
        # start_respne 封装要返回给用户的数据,响应头状态
        start_response('200 OK', [('Content-Type', 'text/html')])
    
        # 返回给用户的数据
        ##return ['<h1>Hello, web!</h1>'.encode("utf-8"), ]
        current_url = environ['PATH_INFO']
        if current_url == '/index':
            return handle_index()
        elif current_url == "/date":
            return handle_date()
        else:
            return ['<h1>404</h1>'.encode("utf-8"), ]
    
    if __name__ == "__main__":
        # 根据ip,端口创建一个socketServer,并设置回调函数RunServer
        httpd = make_server('', 8000, RunServer)
        print("Serving HTTP on port 8000...")
        # 开始监听端口,当有链接进来时,触发回调函数
        httpd.serve_forever()
    
    1.1.2 使key与func代替上面的判断

    这样比上面好处在于,如果有新的url,只需修改dict即可;

    在真正的WEB框架中,确实是这么来处理url的,唯一改进的是使用re将一类url进行分类处理;

    from wsgiref.simple_server import make_server
    
    # 若要返回相关的html页面,理论上就使用这一种方式来处理的
    def handle_index():
        with open("s2.html", "rb") as f:
            data = f.read()
        return [data, ]
    def handle_date():
        with open("s3.html", "rb") as f:
            data = f.read()
        return [data, ]
    
    # 如果有新的请求在这里面添加参数即可,无需修改下面的东西
    ULR_DICT = {
        "/index": handle_index,
        "/date": handle_date
    }
    
    # 此函数内定义框架内主要的处理逻辑
    def RunServer(environ, start_response):
        start_response('200 OK', [('Content-Type', 'text/html')])
        current_url = environ['PATH_INFO']
        func = None
        if current_url in ULR_DICT:
            func = ULR_DICT[current_url]
        if func:
            return func()
        else:
            return ['<h1>404</h1>'.encode("utf-8"), ]
    
    if __name__ == "__main__":
        # 根据ip,端口创建一个socketServer,并设置回调函数RunServer
        httpd = make_server('', 8000, RunServer)
        print("Serving HTTP on port 8000...")
        # 开始监听端口,当有链接进来时,触发回调函数
        httpd.serve_forever()
    
    1.1.3 MVC与MTV

    MVC,全名是Model View Controller,是软件工程中的一种软件架构模式,把软件系统分为三个基本部分:模型(Model)、视图(View)和控制器(Controller),具有耦合性低、重用性高、生命周期成本低等优点;

    所有的文件包括py文件,html文件,数据文件等都放置在同一目录下会显得非常的乱,所以一般我们会将他们进行分类存放,即新建文件夹进行存放,方式大约有2种:

    1. MVC类型:Model里放置数据库文件,View里放置模板文件,Controller放置业务处理;
    2. MTV类型:Model里放置数据库文件,Template存放模板文件,View放置业务处理;

    Django框架的不同之处在于它拆分的三部分为:Model(模型)、Template(模板)和View(视图),也就是MTV框架。

    img

    1.2 HTTP协议

    超文本传输协议(Hyper Text Transfer Protocol, HTTP)

    1. 工作在应用层,最著名的是用于web浏览器与web服务器之间的双向通信;

    版本迭代:

    1. 最广泛使用版本为HTTP1.1 由IETF 1999年6月发布,文档RFC 2616。
    2. HTTP2.0在2015年5月已RFC 7540的方式发布;

    如果需要详细了解推荐:<<HTTP权威指南>>

    1.2.1 HTTP请求的方法

    在HTTP1.1中规定了8中请求的方法

    • GET
    • POST
    • HEAD
    • PUT
    • DELETE
    • TRACE
    • OPTIONS
    • CONNECT
    1.2.2 HTTP状态吗

    ​ 所有HTTP响应第一行都是状态行,依次为当前HTTP版本号,3为数字的状态吗,描述状态码的短语(可以自定义),彼此由空格分隔;

    • 1xx消息:请求已被服务器接收,还需要等等,在处理;
    • 2xx成功:请求已被服务器接收,理解,并接受;
    • 3xx重定向:需要后续操作才能完成这一请求;
    • 4xx请求错误:请求含有此法错误或者无法被执行;
    • 5xx服务器错误:服务器在处理某个正确请求时发生错误;
    1.2.3 URL

    HTTP统一资源定位符,使用5个基本元素唯一标识Internet中的某一资源;

    5个基本元素分别是:

    1. 传输协议;
    2. [可选]访问资源需要的凭证信息;
    3. 服务器,通常为域名及IP;
    4. 端口,不加使用默认端口;
    5. 路径

    URL后的查询方式有两种:

    1. GET传参方式在以上中添加?n=1&n=2
    2. #...,在HTML中的锚点跳转;
    1.2.4 请求格式

    请求具体格式如下:

    img


    GET /user/home/ HTTP/1.1
    Host: 127.0.0.1:8000
    User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:80.0) Gecko/20100101 Firefox/80.0
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
    Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
    Accept-Encoding: gzip, deflate
    Connection: keep-alive
    Upgrade-Insecure-Requests: 1
    Pragma: no-cache
    Cache-Control: no-cache
    
    请求数据...
    
    1.2.5 响应格式

    响应具体格式如下:

    img


    HTTP/1.1 200 OK
    Date: Sat, 19 Sep 2020 13:13:38 GMT
    Server: WSGIServer/0.2 CPython/3.8.0
    Content-Type: text/html; charset=utf-8
    X-Frame-Options: DENY
    Content-Length: 434
    X-Content-Type-Options: nosniff
    Referrer-Policy: same-origin
    
    响应正文
    

    二. Django

    2.1 简单使用

    2.1.1 安装django

    # 安装django
    pip install django
    

    2.1.2 建立project[工程]

    安装完成后,会在Python/scripts目录生成两个程序

    image-20200915104333932

    用来生成相关的project文件

    # 生成project文件mysite
    django-admin.exe startproject mysite
    # 提交相关的参数,来源于报错
    python manage.py migrate
    # 测试运行下project,默认为8000端口
    python manage.py runserver
    python manage.py runserver 127.0.0.1:8001
    # 访问
    

    image-20200915105351539

    生成project的目录结构

    firstDjango
    	- firstDjango ##整个程序的配置
        	- init
        	- asgi.py # Django3.0新出的异步功能
        	- settings.py # Django需要的全局配置文件
        	- urls.py # 设置url入口<url对应关系>
        	- wsgi.py # django不负责socket,这里配置遵循wsgi规范socketserver,推荐uwsgi+nginx
        - db.sqlite3 # 自带的sqlite数据库
        - manage.py # 但凡需要管理Django程序,都是通过manage.py来做的
        	- python manage.py
        	- python manage.py startapp xx
        	- python manage.py makemigrations
        	- python manage.py migrate
    

    通过pycharm 来创建django project

     只需要在file/new project/django即可

    image-20200915122027895

    如果cmd下建的project,可手动配置相关项目,注意点在与需要这两个环境变量设置

    PYTHONUNBUFFERED=1;DJANGO_SETTINGS_MODULE=firstDjango.settings
    

    第一个自定义响应

    根目录下新建一个文件夹myFirstApp,新建s1.py

    from django.shortcuts import HttpResponse
    
    def say_hello(request):
        return HttpResponse("hello web, Hello Django")
    

    在firstDjango/firstDjango下的urls.py添加映射

    from django.contrib import admin
    from django.urls import path
    from myFirstApp import s1
    urlpatterns = [
        path('admin/', admin.site.urls),
        path("index/", s1.say_hello)
    ]
    

    image-20200915110606293

    2.2.3 工程下面的app[ 应用]

    一个正常的网站往往包含很多完全不同的功能,比如任意一个正常的网址最起码包含这几项:

    myProject
    	- myProject
    		- 配置
    	- 主站app
    	- 后台管理app
    

    使用manage.py来建立app/在pycharm中

    python manage.py startapp cmdb
    python manage.py startapp openstack
    

    这样会在工程的目录下生成响应的APP目录,目录结构如下

    cmdb
    	- migrations # 数据库表结构操作记录
    	- admin.py # Django提供的后台管理
    	- apps.py  # 配置当前app
    	- models.py # ORM,写指定的雷,通过命令创建数据库结构
    	- tests.py # 单元测试
    	- views.py	# 业务代码
    

    需要在django中注册app

    #方式共两种:
    INSTALLED_APPS = [
     	# 方式一,直接写名字即可,这种是能让django知道有这个app
        'app01',
        # 方式二,调用apps中的类,推荐这种,某些时候可以复写这个类
        'app01.apps.App01Config'
    ]
    

    如下,新增cmdb应用

    # firstDjango/firstDjango/urls.py
    from django.contrib import admin
    from django.urls import path
    from cmdb import views as cmdbv
    from openstack import views as openstackv
    urlpatterns = [
        path('admin/', admin.site.urls),
        path("cmdb/", cmdbv.say_hello),
        path("openstack/", openstackv.say_hello)
    
    cmdb/view.py
    from django.shortcuts import HttpResponse
    
    def say_hello(request):
        return HttpResponse("CMDB")
    

    image-20200915144026350

    2.2.4 整体django工程流程
    1. 通过pycharm或者django-admin新建工程;

    2. 在工程根目录下新建templates目录用于存放html相关文件;

      # 新建templates后,需要在setting中进行设置:
      TEMPLATES = [
          {
              'BACKEND': 'django.template.backends.django.DjangoTemplates',
              # 将相关目录添加到该位置
              'DIRS': [BASE_DIR / 'templates']
              ,
              'APP_DIRS': True,
              'OPTIONS': {
                  'context_processors': [
                      'django.template.context_processors.debug',
                      'django.template.context_processors.request',
                      'django.contrib.auth.context_processors.auth',
                      'django.contrib.messages.context_processors.messages',
                  ],
              },
          },
      ]
      
    3. 在工程根目录下新建 static文件夹用于存放js,css等html中的静态文件;

      # 工程根目录下新建static目录
      STATIC_URL = '/static/'
      # 配置静态文件存放目录[css,js文件所在目录]
      STATICFILES_DIRS = (
          [BASE_DIR / 'static']
      )
      
    4. 新建app,并配置业务代码;

      python manage.py startapp BruceApp
      # 配置views
      from django.shortcuts import render
      
      # Create your views here.
      
      def login(request):
          return render(request, 'login.html')
      
    5. 将相关业务程序导入urls并配置映射

      from django.contrib import admin
      from django.urls import path
      from BruceApp import views
      
      urlpatterns = [
          path('admin/', admin.site.urls),
          path('login/', views.login),
      ]
      
    2.2.5 报错处理
    1. CSRF 异常告警,中间件的一个东西,暂时放放...

      image-20200915180647565


      处理方法:

      ​ 将settings.py中的django.middleware.csrf.CsrfViewMiddleware注释掉<暂时这么处理>

      MIDDLEWARE = [
          'django.middleware.security.SecurityMiddleware',
          'django.contrib.sessions.middleware.SessionMiddleware',
          'django.middlewared.common.CommonMiddleware',
          # 'django.middleware.csrf.CsrfViewMiddleware',
          'django.contrib.auth.middleware.AuthenticationMiddleware',
          'django.contrib.messages.middleware.MessageMiddleware',
          'django.middleware.clickjacking.XFrameOptionsMiddleware',
      ]
      
    2. RuntimeError at /login

      image-20200915175726158


      按照提示里面的操作进行处理即可,主要原因是在form中action中是/login/,则在urls必须为login/,form中 比urls中多的前面那个/,其代指本地域名<完整的意思就是跳转到本地的login/这个url地址中>

      还有注意排错的时候,需要将程序手动关闭并重启;

    2.2.6 静态文件引入

    所有的图片,css,js,plugins等html中会用到的文件均建议放置在此文件夹。

    django中setting.py中配置static目录

    # Static files (CSS, JavaScript, Images)
    # https://docs.djangoproject.com/en/3.1/howto/static-files/
    
    STATIC_URL = '/static/' # 调用static文件夹们时使用的url,并不是static的文件夹的名字
    STATICFILES_DIRS = [ # 指定static文件夹的路径
        BASE_DIR / 'static', # 因为时列表当然可以指定多个
        BASE_DIR / 'static1', 
        BASE_DIR / 'static2',
    ] 
    # 注意在调用的时候只能使用/static/而非使用/static1-2/等,使用/static/时,程序会依次在列表中找资源
    

    建议目录分类

    static
    	- css
    	- js
    	- img
    	- plugins
    
    2.2.7 简单使用django总结
    # 新建django工程
    django-admin startproject mydjango
    
    # 新建app
    cd mydjango
    python manage.py startapp cmdb
    
    # 添加static静态文件目录
    ## 新增mydjango.static目录
    ## mydjango.setting中添加如下配置
    STATICFILES_DIRS = (
        [BASE_DIR / 'static']
    )
    
    # 添加templates目录
    ## 新增mydjango.templates目录
    ## mydjango.setting中添加如下配置
    TEMPLATES = [
        {
            # 将相关目录添加到该位置
            'DIRS': [BASE_DIR / 'templates']
        },
    ]
    
    # 定义路由规则
    ## 在mydjango.urls.py在中新建规则
    urlpatterns = [
        path('admin/', admin.site.urls),
        path('login/', views.login),
    ]
    
    # 定义视图函数
    ## 在app.views.py下定义,并导入mydjango.urls.py中
    request.method # 获取用户请求的类型
    ## 简单获取数据
    request.POST.get('keyname', None) # POST过来的数据,类似与一个字典,使用get获取;
    request.GET.get('keyname', None) # GET过来的数据,类似与一个字典,使用get获取;
    ## 简单回应
    return HttpResponse("str")
    return render("request", "HTML template lujing ")
    return redicrt('url') #这个跳转是301告诉客户端需要跳转,所以天template是不合理也不行的
    
    # 模板渲染
    ## 在django中存在特殊的模板语言
    ### 在视图函数中给模板传数据
    def func(request):
    	return render(request, "index.html", { "error":  "error_infunc", 'error_list': [1, 2, 3]})
    ###变量替换:
    {{ error }}
    ###对list取值:
    {{ list.0 }}
    ###对dict取值:
    {{ dict.key1 }}
    ###循环语句1####
    {% for row in error_list%}
    	<li>{{row}}</li>
    {% endfor %}
    ###判断语句1####
    {%if age %}
    	{%if ange > 16%}
    		...
    	{% endif%}
    {%else%}
    		...
    {%endif%}	
    

    2.2 Django请求生命周期

    在一个请求发送到django页面时,会经过一个基本的处理过程然后返回结果,这个过程即是生命周期;

    image-20200924185251972

    具体的请求的流程:

    1. 请求发送到wsgi,wsgi封装请求的相关数据(request);
    2. django去匹配路径,根据路径判断要执行哪个函数;
    3. 执行函数,函数处理具体的业务逻辑;
    4. 函数返回响应,django按照HTTP协议的相依的格式进行返回;

    发送请求的途径:

    1. 在浏览器的地址栏种输入地址,回车发出get请求;
    2. a标签,发出的是get请求;
    3. form表单提交,默认get,一般使用post方式;

    2.3 获取数据

    2.3.1 从HTML标签获取

    在html的form表单中有多种方式提交数据,form表单中提交的数据由在http协议发向提交的url:

    1. input text标签
    2. input radio标签
    3. input checkbox标签
    4. input file标签
    5. select options标签

    有些标签传递是单个值,有些则传递是多个值:

    1. get("key", "default") 方式获取单个值,若获取单个值,则只获取最后一个值;
    2. getlist("key", "default") 方式获取多个值,返回一个列表;
    3. 上传文件通过FILES来获取对象,通过name获取的是名字,接收需要使用file.chunks,具体看例中;
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>上传信息至后端</title>
    </head>
    <body>
    <div>
        <!-- action指定url为本域名的 upload/ 使用http.post方法,支持传文件-->
        <!-- 文件的上传不能使用默认的编码方式,需要使用mutipart/form-data编码方式才行-->
        <form action="/upload/" method="post" enctype="multipart/form-data">
            <p>
                <!-- 在视图函数中用name来取值 -->
                <input type="text" name="name" placeholder="name">
            </p>
            <p>
                <label for="#s1">男:</label><input id="#s1" type="radio" name="sex" value="男">
                <label for="#s2">女:</label><input id="#s2" type="radio" name="sex" value="女">
            </p>
            <p>
                ball:<input type="checkbox" name="favor" value="ball">
                run:<input type="checkbox" name="favor" value="run">
                cpu:<input type="checkbox" name="favor" value="cpu">
            </p>
            <p>
                <select name="city" size="3" multiple="multiple">
                    <option value="北京">北京</option>
                    <option value="上海">上海</option>
                    <option value="广州">广州</option>
                </select>
            </p>
            <p>
                照片上传:<input type="file" name="photo">
            </p>
            <p>
                <input type="submit" value="提交">
                <input type="reset" value="重置">
            </p>
        </form>
    </div>
    </body>
    </html>
    

    2.3.2 从http请求中获取

    在视图函数中,所有从client处获取的数据均由request这个形参来接收:

    1. http.post 使用request.POST来接收;
    2. http.get 使用request.GET来接收;
    3. 文件通过request.FILES来接收;
    4. 可以使用request.path_info获取当前在用的url,在template和视图中均可使用;
    def upload(request):
    
        if request.method == "GET":
            return render(request, "upload.html")
        elif request.method == "POST":
            print("name", request.POST.get("name", "无名"))
            print("sex", request.POST.get("sex", "无性别"))
            print("get_favor", request.POST.get("favor", "无爱好"))
            print("getlist_favor", request.POST.getlist("favor", "无爱好们"))
            print("get_city", request.POST.get("city", "无城市"))
            print("getlist_city", request.POST.getlist("city", "无城市们"))
            # 文件提交
            file = request.FILES.get('photo')
            # file是文件对象,存在内存中;
            print("file", file.name)
            path = os.path.join("BruceApp", "receive")
            # 建立文件句柄,然后将文件一点点写入句柄;
            with open(os.path.join(path, file.name), "wb") as f:
                # 获取二进制方式一
                for chunk in file.chunks():
                    f.write(chunk)
                # 直接通过文件对象获取
                for i in file():
                    f.write(i)
            # 注,可以看到上传的文件类型判断其实是通过文件名来判断的,二进制流无法判断的;
            return render(request, "upload.html")
    

    [17/Sep/2020 13:01:50] "POST /upload/ HTTP/1.1" 200 1240
    name alvin
    sex 女
    get_favor run
    getlist_favor ['ball', 'run']
    get_city 广州
    getlist_city ['上海', '广州']
    file 仓库库存.xlsx
    

    2.4 视图

    2.4.1 之FBV与CBV

    Django同时支持FBV(function base view)与CBV(class base view)这两种对应关系,两种没有好坏之分,建议两种都用;

    2.4.1.1 FBV

    在此种方式中view中的url对应的是function

    视图函数:

    def home(request):
        if request.method == "GET":
            pass
        elif request.method == "POST":
            USER_LIST.append({'user': request.POST.get('user1'),
                              'pwd': request.POST.get('pwd'),
                              'mail': request.POST.get('mail')})
    
        # 通过dict将USER_LIST映射给模板中的user_list
        # 模板中的user_list接到数据后,会执行循环语句遍历并替换
        return render(request, 'home.html', {"user_list": USER_LIST})
    

    urls:

    urlpatterns = [
        path('home/', views.home),
    ]
    
    2.4.1.2 CBV

    在此种方式中view中的url对应的是一个类<此类事views类的子类>

    支持的HTTP请求方法类型:

    ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']
    

    用户发来请求前:

    1. 调用as-view(**kwargs)方法,返回view方法;
    2. 在view方法的处理方式:
      • 使用获取的相关参数实例化cls;
      • 使用的setup方法初始化一些属性;
        • self.request = request
      • 调用dispatch方法;

    用户发来请求:

    1. 首选django会根据url来找到指定的function或者class;
    2. 然后在根据methon来选择具体的处理方式;
    3. views会使用dispatch来判断请求的类型,从而调用类中不同的函数:

    view中的dispatch

    def dispatch(self, request, *args, **kwargs):
            # Try to dispatch to the right method; if a method doesn't exist,
            # defer to the error handler. Also defer to the error handler if the
            # request method isn't on the approved list.
            if request.method.lower() in self.http_method_names:
                handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
            else:
                handler = self.http_method_not_allowed
            return handler(request, *args, **kwargs)
    

    视图类

    from django.views import View
    class Home(View):
    
        def dispatch(self, request, *args, **kwargs):
            print("在进请求前,可以做些事情")
            result = super(Home, self).dispatch(request, *args, **kwargs)
            print("当请求处理完毕,可以做些事情")
            # 这里为什么要return呢?
            # 原因一个请求过来,终端要拿到返回,还是需要通过dispatch拿到结果;
            # 如get方法,返回值是返回给dispatch,dispatch再返回给用户;
            return result
        def get(self, request):
            return HttpResponse("get..")
    
        def post(self, request):
            return HttpResponse("post...")
    

    可以额外做的操作

    # 在dispatch中有这个检测:
    if request.method.lower() in self.http_method_names:...
    # 所以可以修改http_method_names来限制可使用的http method
    http_method_names = ['get'']
                         
    # 在dispatch中对调用前及调用后的数据进行处理...
    
    2.4.1.3 装饰器

     装饰器常用来在不改变函数代码的情况下,为其添加一定的功能;

    装饰器

    # 这是一个普通的装饰器
    def time_cost(func):
        def inner(*args, **kwargs):
            start = time.time()
            res = func(*args, **kwargs)
            print("spend time", time.time() - start)
            return res
        return inner
    
    # 但是这个装饰器有个问题,就是其函数相关的所有信息会从func变为inner的信息,比如fun.__name__,fun.__info__等;
    # 使用wraps可以将func的相关信息保留并在被装饰完成后重新交给func
    from functools import wraps
    def time_cost(func):
        @wraps(func)
        def inner(*args, **kwargs):
            '''from inner'''
            start = time.time()
            res = func(*args, **kwargs)
            print("spend time", time.time() - start)
            return res
        return inner
    

    在装饰视图时,我们一般使用这种装饰器结构,让request独立出来,方便调用;

    from functools import wraps
    def time_cost(func):
        @wraps(func)
        def inner(request, *args, **kwargs):
            start = time.time()
            res = func(reuqest, *args, **kwargs)
            print("spend time", time.time() - start)
            return res
        return inner
    
    @time_cost # FBV方式直接装饰即可
    def get(request):
        return HttpResponse("hello decorator...")
    

    装饰fbv如上,装饰cbv如下:

    from django.views import View
    
    # 需要导入method_decorator
    # 作用是让传入的第一个参数为request,与FBV方式相同,而非self即当前对象;
    from django.utils.decorators import method_decorator 
    
    @method_decorator(time_cost, name='post') # 可以写在类上面,并指定被装饰方法
    @method_decorator(time_cost, name='dispatch') # 可以写在类上面,并指定被装饰方法
    class Home(View):
    	
        @method_decorator(time_cost) # 为所有的方法添加装饰器
        def dispatch(self, request, *args, **kwargs):
            result = super(Home, self).dispatch(request, *args, **kwargs)dispatch
            return result
        
        @method_decorator(time_cost) # 为单独的某个方法添加装饰器
        def get(self, request):
            return HttpResponse("get..")
    	
        @method_decorator(time_cost) # 为单独的某个方法添加装饰器
        def post(self, request):
            return HttpResponse("post...")
    

    2.4.2 request对象

    HTTP协议发送过来的内容由wsgi封装成一个request对象;

    属性:

    request.method # HTTP的请求方法,get,post,put,delete等
    request.GET #URL携带的的参数,不仅仅是get请求,只要是url中?k1=v1&k2=v2都会放到GET中
    request.POST #post请求提交的数据
    request.path_info #路径信息,不包含ip,port,?参数
    request.body # 获取发过来请求体,原始数据经过urlcode编码,发送给后台,后台可通过这个解析出post的相关数据;
    request.META # 获取请求头里面的所有信息,返回的是一个字典,变更(小写->大写,添加HTTP_开头)
    request.COOKIES # 返回字典,cookies信息
    request.session # 可读可写的类字典对象,表示当前的会话,只有当django启用会话支持时才使用;
    request.FILES # 长传的文件
    # 其他一些不常用的
    request.scheme # 获取协议类型,如http or https等
    request.user # 返回当前的用户,这个是jango中的一套认证系统,大部分情况下不适用;
    

    方法:

    request.get_host() # 根据META里的信息获取请求主机名
    request.get_full_path() # 相对于path_info的不同点在于会带上?参数
    request.get_signed_cookie() # 获取一个加密的cookie
    request.is_secure() # 是否是安全的,即请求是不是通过HTTPS发起的
    request.is_ajax() # 判断请求是否是ajax请求;
    

    2.4.3 response对象

    from django.shortcuts import HttpResponse, render, redirect, JsonResponse
    
    HttpResponse('str') # 返回字符串,最根本的返回对象;
    render(request, 'template_namae', {参数字典}) # 渲染HTML,生成相关字符串;
    redirect('url') # 重定向, 注意重定向环路
    JsonResponse({'foo': 'bar'}) # 用于前后端分离,发送的是json响应数据;
    

    关于render

    def render(request, template_name, context=None, content_type=None, status=None, using=None):
        content = loader.render_to_string(template_name, context, request, using=using)
        return HttpResponse(content, content_type, status)
    # 可以看到是使用loader.render_to_string的方法来对渲染html文件的,这里面可以替换使用jinja2的方式来渲染;
    # 可以看到传递的除了我自定义的context,同时也传递了request对象,所以默认情况下template就可以理解request对象,即支持{{ request }}这个方法;
    

    关于redirect

    # 返回的是3xx状态 + 在head中添加Location:url 即完成跳转;
    # 如下用HttpResponse来实现
    ret = HttpResponse('', status=301)
    ret['Location'] = '/publisher_list/' # 使用setattr来赋值属性;
    

    关于jsonResponse

    # 返回时json序列后的str,同时返回content_type设置为json类型
    # 如下用HttpResponse来实现
    ret = HttpResponse(json.dump({'k1':'v1'})) # 返回字符串,最根本的返回对象;
    ret['Content_type'] = "application/json" # 返回类型为json类型;
    # 以上等同于以下方式:
    JsonResponse({'k1':'v1'}) # 用于前后端分离,发送的是json响应数据;
    

    2.5 模板语言

    ​ 在django种有两种特殊符号{{}}和{% %},前者表示变量,后者表示逻辑相关的操作;

    2.5.1 数据

    2.5.1.1 变量

    使用{{ var }}来定义,var为变量名,var会在渲染的时候被替换成对应的值;

    1. 值的处理相当于python种print对数据的处理;
    2. 可以使用“.”来取列表种值,取属性等,如{{ list.0 }},{{ obj.name }};
      • list不支持-n取值,不如{{ list.-1 }};
    3. 因为方法后不加括号,所以一般不可以加参数;
    4. 取值优先级:.key > .属性 .方法 > .索引;
    2.5.1.2 字典的传递与遍历

    与在python中的遍历相似,默认循环取key,可以指定只去value,items返回key:value元组;

    字典具有方法,可以使用诸如:

    1. dict.keys
    2. dict.values
    3. dict.items
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>dict</title>
    </head>
    <body>
        <div>
            <ul>
                {% for key in my_dict %}
                    <li>{{ key }}</li>
                {% endfor %}
            </ul>
            <ul>
                {% for value in my_dict.values %}
                    <li>{{ value }}</li>
                {% endfor %}
            </ul>
            <ul>
                {% for key, value in my_dict.items %}
                    <li>{{ key }}-{{ value }}</li>
                {% endfor %}
            </ul> 
        </div>
    </body>
    </html>
    
    2.5.1.3 过滤器

    有时候传入的值,我们还想做些修改,就会用到过滤器,即filter,过滤器最多只能有1个参数;

    #通用语法
    {{}}
    {{ baby|default: "Alvin"}} # 如果不传或者传空值则使用这个默认值,":"左右不可有空行注意
    {{ filesize|filesizeformat }} # 在数字后边添单位,byte
    {{ 4|add:2 }} # 一个加法作用,支持数字加法,字符串即列表拼接
    
    {{ Bruce|lower }} # 将字符变为小写;
    {{ Bruce|upper }} # 将字符变为大写;
    {{ Bruce|title }} # 将首字母大写;
    
    {{ list|length }} # 返回变量长度
    {{ list|slice:'-1:-3:-1' }} # 列表切片,注意如果是反向取值的话,步长也要指定,否则取不到;
    {{ list|first }} # 取第一个元素
    {{ list|last }} # 取第二个元素
    {{ list|join:'--' }} # 即list的拼接str方法
    
    {{ longStr|truncatechars:'n'}} # 取n-3个字符+...构成返回用来处理长字符串
    {{ longStr|truncatewords:'n'}} # 取n个单词+...构成返回;
    
    {{ now|date:'Y-m-d H:i:s' }} # 格式化datetime对象,不一样地方在于i表示分,s表示s;
    
    {{ a|safe }} # 默认情况下模板传递的html标签会被转义为字符串,如果想使用需要safe关闭转义
    

    格式化时间可以在工程中的setting种做调整

    USE_L10N = False
    # datetime类型
    DATETIME_FORMAT = 'Y-m-d H:i:s'
    # date类型
    DATE_FORMAT = 'Y-m-d'
    # time类型
    TIME_FORMAT = 'H:i:s'
    

    2.5.2 语句

    2.5.2.1 for循环
    {% for item in list %}
    	...
    {% endfor}
    

    for中的一些特殊值

    Variable Description
    forloop.counter 当前循环的索引值(从1开始)
    forloop.counter0 当前循环的索引值(从0开始)
    forloop.revcounter 当前循环的倒序索引值(到1结束)
    forloop.revcounter0 当前循环的倒序索引值(到0结束)
    forloop.first 当前循环是不是第一次循环(布尔值)
    forloop.last 当前循环是不是最后一次循环(布尔值)
    forloop.parentloop 本层循环的外层循环

    如果循环的内容是空,加一些显示

    {% for item in kong %}
    	如果不空这个生效
    {% empty %}
    	如果时空循环显示这个
    {% endfor %}
    
    2.5.2.2 条件语句

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

    {% if age > 73 %}
      老年人
    {% elif age > 40 %}
      中年人
    {% else %}
      年轻人
    {% endif %}
    

    注意:本身判断里面时不支持加减乘除的,但是可以使用filter来实现,比如:

    {% if age|add:3 > 73 %}
    	3年后就成为老年人了
    {% endif %}
    

    注意:不支持连续判断,比如 7 > 5 > 1

    1. 在python中表示一个and运算,即7 > 5 and 5 > 1 所以返回true;
    2. 而在js和模板语言中时按照顺序来判断的,7>5为true(1),而true(1) > 1为false,返回false;
    2.5.2.3 with定义中间变量
    #写法1:为某个值取个别名,在with的范围内都可以使用
    {% with business.employees.count as total %}
        {{ total }} employee{{ total|pluralize }}
    {% endwith %}
    
    #写法2:为某个值取个别名,在with的范围内都可以使用
    {% with total=business.employees.count %}
        {{ total }} employee{{ total|pluralize }}
    {% endwith %}
    
    2.5.2.4 csrf_token

    这个标签用于跨站请求伪造保护,添加在form标签内;

    <form method="post">
         {% csrf_token %}
        <input type="text" name="name">
        <input type="submit" value="submit">
    </form>
    

    这个是将form进行了相关的渲染,生成认证用的字符串;

    2.5.3 页面组建

    对于一个Project中的不同页面,有一部分页面是相同的,可以使用母版与继承来处理;

    2.5.3.1 母版

    一个包含多个页面的公共部分,定义多个block块,让子页面进行覆盖;

    # 在母版中指定
    {% block main %} #可以添加名称,用来定义多个block块 
    	....替换的内容
    {% endblock %}
    
    {% block css %}
    	各子类在里面定义自己独有的样式
    {% endblock %}
    
    {% block js %} 
    	各子类在里面定义自己独有的 js
    {% endblock %}
    
    2.5.3.2 继承

    其他业务html可以继承母版,并重新复写block块;

    注意点:

    1. 在引用时,加不加引号时两种不同的结果;
    2. 要显示的内容需要放置block块中,如果不在block中则无法替换;
    3. 子html中 有时需要自己的样式,这些可以在css处及js处定义css block块及js block块;
    # 继承母版内容的方式
    # 直接写母版的名称,要加引号;
    {% extends ‘mother.html’ %} 
    
    # 不加引号实际为变量,需要在render时传参;
    {% extends mother %} 
    render(request, "template.name", {'name':'mother.html'})
    
    {% block main %}
    	子页面html
    {% endblock %}
    
    2.5.3.3 组件

    有时候有些内容在很多的页面中都有调用,此时可以使用组件的方式进行操作;

    # 把一小段公用的HTML文本写入一个HTML文件,如nav.html中;
    # 在需要该组件的模块中:
    {% include 'nav.html' %}
    
    2.5.3.4 静态文件

    静态文件的引入方式有两种一种是url直接引入,另外一种使用load来引入:

    <!--引入方式一:存在问题在变更url名称时,更改的地方过多-->
    <link href="/static/css/dashboard.css" rel="stylesheet">
    
    <!--引入方式二:通过语法去setting中获取url并跟相对路径进行拼接-->
    {% load static %}
    <link href="{% static 'css/dashboard.css' %}" rel="stylesheet">
    
    <!--补充:获取静态文件的url,即setting中的配置的url-->
    <!--补充:也可以使用这种方式拼接静态文件的引用-->
    <link href="{% get_static_prefixe %}css/dashboard.css" rel="stylesheet">
    

    2.5.4 tag自定义

    有时候模板中处理的东西并不满足我们的需求,所以需要自定义,总共有三种自定义

    2.5.4.1 filter自定义
    1. 在app下创建一个名为templatetags的python包,固定写法;

    2. 创建一个python文件,文件名自定义;

    3. 文件内固定写法:

      from django import template
      register = template.Library() # register名字不能错
      
    4. 写函数 + 装饰器

      @register.filter # 固定装饰器
      def add_arg(var, arg): # value是装饰的变量,arg是参数
          # 功能
          return "{}_{}".format(var, arg) 
      
    5. 调用

      # 在template中使用模板导入后即可使用
      {% load myfiter%}
      # 注意如果报错,记得重启django进程
      
    2.5.4.2 simple tag自定义

    filter的自定义可以接受的参数太少了,所以也可以使用simple tag,更像一个函数;

    前面步骤同filter,装饰器及变量灵活

    @register.simple_tag
    def plus(*args, **kwargs):
        return "{}_{}".format('_'.join(args), "*".join(kwargs.values()))
    

    调用方式

    # 导入
    {% load app01_demo %}
    # 使用与传参
    {% plus "1" "2" "3" k1="d" k2="e" k3="f" %}
    

    但是simple tag时无法在判断等语句种使用的,而filter时可以的;

    2.5.4.3 inclusion_tag自定义

    simple tag相当于函数,但是在生成html代码段的时候不太方便,这时候就用到inclusion_tag来处理;

    前面步骤同filter,

    # templatetags/my_inclusion.py种内容
    from django import template
    register = template.Library()
    @register.inclusion_tag('result.html')
    def show_results(n):
        n = 1 if n < 1 else int(n)
        data = ["第{}项".format(i) for i in range(1, n+1)]
        return {"data": data}
    

    inclusion_tag中需要传入模板templates/result.html

    <ul>
      {% for choice in data %}
        <li>{{ choice }}</li>
      {% endfor %}
    </ul>
    

    然后再模板中调用

    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta http-equiv="x-ua-compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1">
      <title>inclusion_tag test</title>
    </head>
    <body>
    
    {% load my_inclusion %}
    
    {% show_results 10 %}
    </body>
    </html>
    
    2.5.4.4 三种自定义总结
    1. filter只能有一个变量,但是优点时可以写在判断内,而其他两个不行;
    2. simple tag支持不限量变量数,返回的是一个具体的值,但是再渲染html时不太灵便;
    3. inclusion_tag用来生成灵活调整的组件时非常有用,比如分页的页标;

    2.6 路由系统

    URL配置(URLconf)就像Django所支撑网站的目录,它的吧恩智是URL与要为URL调用的视图函数之间的映射表;我们已这种方式告诉DJangon遇到那个URL时,要执行哪个函数;

    1. 匹配顺序是从上到下的;

    2. 默认情况下url最后加不加“/”是由django来自动添加的,如果不想自动添加,可以settinging中关闭:

      APPEND_SLASH=True
      

    2.6.1 对应关系类型

    如果确定项目的urls.py位置呢,其实是在setting中设置的:

    ROOT_URLCONF = 'FcNetWeb.urls'
    
    2.6.1.1 一对一对应

    一个url对应一个function/class

    在处理详细显示这个需求时,可以使用get方法进行传参

    1. 在urls中添加两个url,dict和details
    urlpatterns = [
        path('dict/', views.Dict.as_view()),
        path('details/', views.Details.as_view()),
    ]
    
    1. 在views新建两个视图类
    MY_DICT = {
        "1": {"name": "bruce", "age": 18, "favor": "ball"},
        "2": {"name": "alvin", "age": 18, "favor": "run"},
        "3": {"name": "hebburn", "age": 18, "favor": "sing"},
        "4": {"name": "diana", "age": 18, "favor": "sleep"},
    }
    
    class Dict(View):
    	
        def get(self, request):
            return render(request, "mydict.html", {"my_dict": MY_DICT})
    
    class Details(View):
    	
        def get(self, request):
            person_id = request.GET.get("id", None)
            # 获取get传过来的id,根据这个在dict中取值并用details.html渲染传给用户
            if person_id in MY_DICT:
                return render(request, "details.html", {"person": MY_DICT[person_id]})
            else:
                return HttpResponse("<div style='color:red'>无此人数据<div>")
    
    1. 在templates中新建两个模板

    mydict.html

    使用每条数据的key+url为其构建一条请求;

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>dict</title>
    </head>
    <body>
        <div>
            <ul>
                {% for key, value in my_dict.items %}
                    <li><a href="/details/?id={{ key }}">{{ value.name }}</a></li>
                {% endfor %}
            </ul>
        </div>
    </body>
    </html>
    

    details.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>{{ person.name }}的详细信息</title>
    </head>
    <body>
    {{ person.name }}
        <div>
            <ul>
                {% for key, value in person.items %}
                    <li>{{ key }}:{{ value }}</li>
                {% endfor %}
            </ul>
        </div>
    </body>
    </html>
    
    2.6.1.2 正则一对多-分组位置传参

    使用正则可以实现一个function/class对应多个url;

    在处理详细显示这个需求时,使用正则的实现方式如下:

    1. 在urls中添加两个url,dict和details
    from django.contrib import admin
    from django.urls import path
    from BruceApp import views
    from django.urls import re_path
    
    urlpatterns = [
        path('dict/', views.Dict.as_view()),
        # re_path在3.0中用于正则匹配
        re_path(r'^details-(d+).html/', views.Details.as_view()),
    ]
    
    1. 在views中修改Details
    class Details(View):
    
        def get(self, request, person_id):
            # person_id = request.GET.get("id", None)
            print(person_id)
            if person_id in MY_DICT:
                return render(request, "details.html", {"person": MY_DICT[person_id]})
            else:
                return HttpResponse("<div style='color:red'>无此人数据<div>")
    
    1. 在templates中修改mydict.html
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>dict</title>
    </head>
    <body>
        <div>
            <ul>
                {% for key, value in my_dict.items %}
                    <li><a href="/details-{{ key }}.html">{{ value.name }}</a></li>
                {% endfor %}
            </ul>
        </div>
    </body>
    </html>
    
    2.6.1.3 正则一对多-分组键值对传参

     键值对既可以使用**kwargs来接收成一个dict,也可以当做对形参的直接赋值;

    以上一对多url中一对多关系根据正则的分组返回一个或多个正则的值,然后由视图函数中的参数获取,还有一种可以在正则中为分组添加key,从而获取键值对;

    注:命名分组与顺序分组是不能混用的….

    1. urls中在正常正则中添加?P
    urlpatterns = [
        re_path(r'^details-(?P<person_id>d+).html/', views.Details.as_view()),
    ]
    
    1. 在视图类中的方法中添加获取
    class Details(View):
    
        def get(self, request, **kwargs):
            print(kwargs)
            if kwargs.get('person_id') in MY_DICT:
                return render(request, "details.html", {"person": MY_DICT[kwargs.get('person_id')]})
            else:
                return HttpResponse("<div style='color:red'>无此人数据<div>")
    
    2.6.1.4 默认参数

    在一对多的正则匹配规则中,如果分组未传递参数,则视图函数可以通过默认值的方式给予形参一个值;

    # 应用场景中,比如对分页进行操作;
    # urls.py中
    from django.conf.urls import url
    
    from . import views
    
    urlpatterns = [
        url(r'^blog/$', views.page),
        url(r'^blog/page(?P<num>[0-9]+)/$', views.page),
    ]
    
    # views.py中,可以为num指定默认值
    def page(request, num="1"):
        pass
    
    2.6.1.5 在url中额外的参数传递

    除了在url中的分组捕获以外, 也可以通过使用字典的方式,额外向视图函数传些参数;

    from django.conf.urls import url
    from . import views
    
    urlpatterns = [
        url(r'^blog/(?P<year>[0-9]{4})/$', views.year_archive, {'foo': 'bar'}),
    ]
    
    2.6.1.6 一些对应技巧
    1. 可以使用一对多正则将多个比较类似的功能合并到一个url中,并利用反射完成取类;

      # url中
      urlpatterns = [
          # 也可以写成(w+)(d+).html,这样需要在里加写判断
          url(r'(publisher|book|author)(d+).html', views.del, name = "del"),
      ]
      # view中
      def del(request, single_url_name, nid):
          # 反射取类
          cls = getattr(models, single_url_name.capitalize())
          # 通过nid差值删除
          cls.object.fiter(id=nid).delete()
          # single_url_name对应我们显示的各个模块的url,可以使用反射
          return redirect(reverse(single_url_name))
      	# 这边我们手动反射,其实在redirect中有针对single_url_name的检测及反射
          # 所以下面这么写,也是OK的,也可以使用model来做
          return redirect(single_url_name)
      # template中
      <a hrep="{% url del 'publisher' nid %}">删除</a>
      

    2.6.2 命名name与反向解析

    在django中可以在定义url中为其命名(即创建url同时添加参数name="xxx");

    作用:可以根据此名称生成自己想要的URL,通过字符串拼接也可以实现,这种更优雅,更灵活些;

    <!--templates中:-->
        <ul>
            {% for key, value in my_dict.items %}
                <li><a href="/details-{{ key }}.html">{{ value.name }}</a></li>
                <li><a href="{% url "dt" person_id=key %}">{{ value.name }} + 1</a></li>
            {% endfor %}
        </ul>
    <!-- 两种都可以实现跳转,但是第二种方式在url变化时,不需要修改template -->
    

    本质: 本质上看是{name:{url:function}}这种关系,取得三个中的任意一个值即可找到另外两个的值;

    2.6.2.1 urls,py中定义name
    path('dict/', views.Dict.as_view(), name="n1")
    re_path(r'^details-(d+).html/', views.Details.as_view(), name="n2")
    re_path(r'^details-(?P<person_id>d+).html/', views.Details.as_view(), name="n2")
    
    2.6.2.2 在视图函数反向解析
    from django.urls import reverse
    # 注在django.shortcuts.py中也导入了reverse所以可以从django.shortcuts导入
    from django.shortcuts import reverse
    url1 = reverse('n1')
    url2 = reverse('n2', args=("1314",))
    url3 = reverse('n3', kwargs={"person_id": "1316"})
    
    2.6.2.3 在templates中反向解析
    <a href="{% url "n1" %}">{{ value.name }}</a>
    <a href="{% url "n1" "1314" %}">{{ value.name }}</a>
    <a href="{% url "n1" person_id='1316' %}">{{ value.name }}</a>
    

    2.6.3 路由分发

    比如我一个程序中有很多的app,所有的app都导入项目中的urls.py中,会让整个urls.py的修改过于频繁,这种是完完全全不行的,所以我们使用路由分发;

    路由分发,即根据一定的规则将一类url全部转到app中的某个urls.py中;

    2.6.3.1 一级url分发举例

    工程名:AlvinDjanjo

    APP01:BruceApp

    APP02:DianaAPp

    Alvin.Djanjo.urls.py中需要引入include

    from django.contrib import admin
    from django.urls import path, include
    urlpatterns = [
        path('admin/', admin.site.urls),
        path('bruce/', include("BruceApp.urls")),
        path('diana/', include("DianaApp.urls")),
    ]
    

    BruceApp.urls.py中写具体的路由规则,也可以构建二级路由分发;

    from django.urls import path
    from BruceApp import views
    from django.urls import re_path
    urlpatterns = [
        path('login/', views.login),
        path('home/', views.Home.as_view()),
        path('upload/', views.upload),
        path('dict/', views.Dict.as_view()),
        re_path(r'^details-(?P<person_id>d+).html/', views.Details.as_view(), name='dt'),
     ]
    

    DianaApp.urls.py中写具体的路由规则,也可以构建二级路由分发;

    from django.urls import path
    from DianaApp import views
    from django.urls import re_path
    urlpatterns = [
        path('home/', views.Home.as_view()),
     ]
    

    访问方式变成:

    http://127.0.0.1:8004/bruce/home/
    http://127.0.0.1:8004/diana/home/
    

    2.6.4 命名空间

     用来区分不同APP中的相同URL的name,URL的命名空间模式也可以让你唯一反转命名的URL;

    在project.urls中添加namespace

    from django.contrib import admin
    from django.urls import path
    from django.urls import include
    urlpatterns = [
        path('admin/', admin.site.urls),
        # namespace需要为不同的app添加名称两种方式,一种在app的url中加,如下
        path('app01/', include('app01.urls',  namespace="app1")),
        # 一种是在project中添加,如下
        path('app02/', include(('app02.urls', 'app02'), namespace="app2")),
    ]
    

    在app01.urls中

    from django.urls import path
    from app01 import views
    # 第一种方式,为app添加一个名称
    app_name = "app01"
    urlpatterns = [
        path('index/', views.index, name="index"),
    ]
    

    在app02.urls中,不用添加

    from django.urls import path
    from app02 import views
    urlpatterns = [
        path('', views.index, name="home"),
        path('index/', views.index, name="index"),
    ]
    

    在app01.views及app02.views中reverse时,额外添加命名空间指定

    from django.shortcuts import render, reverse
    
    def index(request):
        u1 = reverse('app02:index')
        str2 = "from app02
    " + u1
        return render(request, 'app02.html', {"str2": str2})
    

    在tempalate.app01.html中reverse时,额外添加命名空间指定

    <body>
        <div>{{ str1 }}</div>
        <div>{% url 'app01:index' %}</div>
        <div>{% url 'app02:index' %}</div>
    </body>
    

    2.7 Django的ORM

    ORM分为DBfirst及CODEfirst两种;DBfirst指的是需要现在mysql中创建相关的数据库及表,再在code中创建相关的代码;而CODEfirst则相反,大多数ORM都是这类,写好相关的类自动给你完成相关的创建;

    ORM与数据库本质上分为两大方面:

    1. 根据类自动创建数据库表;
    2. 根据类对数据库表中的数据进行各种操作;

    2.7.1 简单使用

    2.7.1.1 setting中的设置
    # 在这里注册的自己的APP,不添加makemigrations不会去找app下的modules文件来生成记录
    INSTALLED_APPS = [
    	...,
        'DianaApp'
    ]
    
    # 这里用来修改使用的数据库类型
    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.sqlite3',
            'NAME': BASE_DIR / 'db.sqlite3',
        }
    }
    

    若使用mysql数据库,需要下面几步

    1. 因为django的orm默认使用Mysqldb连接数据库,而我们使用pymysql通过下面方式转换:

      # 在工程.工程名.\_\_init\_\_.py中添加
      import pymysql
      pymysql.version_info = (1, 4, 1, "final", 0) # django3.0后有版本要求
      pymysql.install_as_MySQLdb()
      
    2. 在setting中修改DATABASES

      DATABASES = {
          'default': {
              # 'ENGINE': 'django.db.backends.sqlite3',
              # 'NAME': BASE_DIR / 'db.sqlite3',
              'ENGINE': 'django.db.backends.mysql',
              'NAME': 'djangouse',
              "USER": "django",
              "PASSWORD": "xxx",
              "HOST": "127.0.0.1",
              "PORT": '3306',
              'OPTIONS': {
                  'isolation_level': "repeatable read",
              },
          }
      }
      

      注:若不加OPTIONS会导致django.db.utils.OperationalError: 1665的报错,解决方法有两种,另一种是修改binlog format

      mysql> SET GLOBAL binlog_format = 'ROW';
      # 查询修改
      mysql> show variables like 'binlog_format';
      
    3. 生成表结构

      python manage.py makemigrations DianaApp
      python manage.py migrate
      
    2.7.1.2 models中创建类
    from django.db import models
    class UserInfo(models.Model):
        username = models.CharField(max_length=32)
        password = models.CharField(max_length=64)
    
    2.7.1.3 在数据库内创建表
    python ./manage.py makemigrations
    python ./manage.py migrate
    
    2.7.1.4 简单的增删改查
    from DianaApp import models
    class Orm(View):
    
        def get(self, request):
            # 新建数据方式一
            models.UserInfo.objects.create(username="Bruce", password="123")
            # 新建数据方式一->变种
            dic = {"username": "Alvin", "password": "123"}
            models.UserInfo.objects.create(**dic)
            # 新建数据方式二
            obj = models.UserInfo(username="Hebburn", password="123")
            obj.save()
    
            # 查询
            result01 = models.UserInfo.objects.all()
            result02 = models.UserInfo.objects.filter(username="Bruce")
            # 返回的是一个django对象,可迭代,内部放置的是UserInfo对象;
            print(result01, result02)
    
            # 更新
            # 这是批量的修改
            # 只改特定的字段
            models.UserInfo.objects.filter(username="Alvin").update(password="123.com")
            result03 = models.UserInfo.objects.filter(username="Alvin")
            
            # 值的修改,save后存入数据库
            # 这种方式,会对所有的字段更新
            result03.username = "hebburn" # 只会在内存中修改
            resule03.save() # 这一步才会修改数据库
            for row in result03: print(row.password)
            # 删除
            models.UserInfo.objects.filter(id=2).delete()
    
            return HttpResponse("orm操作测试")
    

    2.7.2 字段

    2.7.2.1 常用字段
    字段名 字段描述
    AutoField 自增整型字段,必填primary_key=True,则成为数据库主键,无django自动创建。
    IntegerField 一个整数类型范围 -2147483648 ~ 2147483647。
    CharField 字符类型,必须提供max_length参数,max_length表示字符的长度。
    DateField/timeField 日期类型,日期格式为YYYY-MM-DD,详单与Python中的datetime.date的实例。
    auto_now=True每此条目更新都会跟随更新为最新时间;
    auto_now_add=True新增条目时会自动保存当前的时间
    DatetimeField 日期时间字段,格式为]DD HH:MM[:ss[.uuuuuu]][TZ],对应Python的datetime.datetime实例。
    BooleanField bool类型
    TextField 文本类型
    FloatField 浮点型
    DecimalField 10进制小时,参数max_digits总长度,参数decimal_places小数总长度
    2.7.2.2 自定义字段
    class MyCharField(models.Field):
        """
        自定义的char类型的字段类
        """
        def __init__(self, max_length, *args, **kwargs):
            self.max_length = max_length
            super(MyCharField, self).__init__(max_length=max_length, *args, **kwargs)
     
        def db_type(self, connection):
            """
            限定生成数据库表的字段类型为char,长度为max_length指定的值
            """
            return 'char(%s)' % self.max_length
    

    class Test(models.Model):
        phone = MyCharField(max_length=11)
    
    2.7.2.3 字段参数
    参数名 说明
    null 数据库中字段是否可以为空
    db_column 指定数据库中字段的列表
    default 设置默认值
    primary_key 数据库中字段是否为主键
    db_index 数据库中字段是否可以建议索引
    unique 数据库中字段是否可以建立唯一索引
    unique_for_date 数据库中字段日期部分是否可以
    verbose_name Admin中显示的字段名称
    blank Admin中是否允许用户输入为空,和null连用,否则在admin中会歧义
    editable Admin中是否可以编辑
    choices Admin中显示选择框的内容,用不变动的数据放在内存中从而避免跨表操作
    render =models.IntegerField(choices=[(0,'男'),(1,'女'),],default=1)

    2.7.3 外键

    使用外键建立表与表之间的关系,主要一对一,一对多及多对多关系;

    publisher = models.ForeignKey(to=Publisher, on_delete=models.CASCADE, to_field="id")
    # 在外键中创建时,数据库内存储字段会自动为其添加上Class_id;
    
    2.7.3.1 主要参数说明
    1. to:指定关联的类,可以是类名,也可是直接是字符串,不过字符串时,Publisher必须先于当前类;
    2. on_delete:指定当foregin关联的类中数据删除时,本表数据的操作方式 2.0版本必填:
      • models.CASCADE:当关联删除时,当前表中条目跟随删除<默认>;
      • models.PROTECT:当关联删除时,禁止关联删除,报错;
      • models.SET系列:当关联删除时,相关条目从新设置关联<某个值,默认值,NULL>;
    2.7.3.2 一对多映射
    1. 从ForeginKey字段拿到的是关联类的对象,如果要拿id,可以直接使用Publisher_id;

          def get(self, request, **kwargs):
              books = models.Book.objects.all()
              print(books)
              for book in books:
                  print(book.publisher) # 拿到的是对象
                  print(book.publisher_id) # 拿到是ID
              return HttpResponse("BookList", books)
      
    2. 对ForeginKey的赋值方式也有两种,一种是使用对象赋值,一种是使用ID赋值;

      # 直接使用ID进行赋值
      models.Book.objects.create(
                      name=name,
                      publisher_id=publisher_id
                  )
      
      # 获取响应的对象进行赋值
      models.Book.objects.create(
                      name=name,
                      publisher=models.Publisher.objects.get(publisher_id=publisher_id)
                  )
      
    2.7.3.3 多对多映射

    在python种使用ManyToManyField建议表与表之间建立多对多映射;

    class Book(models.Model):
        name = models.CharField(max_length=64)
        publisher = models.ForeignKey(to=Publisher, on_delete=models.SET('1'), to_field="id")
    
    class Author(models.Model):
        name = models.CharField(max_length=16)
        book = models.ManyToManyField("Book")
    

    以上会生成三张表,第三张表不可直接使用python维护,除非手动生成第三张表;

    image-20200920231015598

    多对多映射有些特点如下:

    1. 从多对多的字段拿到的值不是对象,而是一个中间商related_descriptors
    app01.Book.None 
    # 如下的中间商->关系管理对象;
    <class 'django.db.models.fields.related_descriptors.create_forward_many_to_many_manager.<locals>.ManyRelatedManager'>
    
    # 可以使用相关方法获取对象,比如获取所有对象的.all()方法;
    <QuerySet [<Book: Book object (1)>, <Book: Book object (2)>]>
    

    对多对对象进行增删改查

    author_obj.books.set([1,2]) # 设置多对多关系
    author_obj.delete() # 删除时会直接删除条目+第三张表中的多对多关系
    

    2.7.4 Model Meta参数

    class UserInfo(models.Model):
        nid = models.AutoField(primary_key=True)
        username = models.CharField(max_length=32)
     
        class Meta:
            # 数据库中生成的表名称 默认 app名称 + 下划线 + 类名
            db_table = "table_name"
     
            # admin中显示的表名称
            verbose_name = '个人信息'
     
            # verbose_name加s
            verbose_name_plural = '所有用户信息'
     
            # 联合索引 
            index_together = [
                ("pub_date", "deadline"),   # 应为两个存在的字段
            ]
     
            # 联合唯一索引
            unique_together = (("driver", "restaurant"),)   # 应为两个存在的字段
    

    2.7.5 脚本执行ORM及13条

    import os
    
    # 初始化相关的环境变量,并取得setting的相关值
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'FcNetWeb.settings')
    
    # 导入django模块
    import django
    # 执行django模块
    django.setup()
    # 以上初始化步骤必须按照顺序来,否则报错
    from app01 import models
    
    # 1:all查询所有的数据,返回QuerySet-对象列表 [对象,对象]
    ret01 = models.Publisher.objects.all()
    
    # 2:获取一个有且唯一的数据,返回对象,没有或者多个就报错;
    ret02 = models.Publisher.objects.get(id=1)
    
    # 3:filter获取满足条件的数据
    ret03 = models.Publisher.objects.filter(id__gt=1)
    
    # 4: 获取不满足条件的所有条目数
    ret04 = models.Publisher.objects.exclude(id=1)
    
    # 5: order_by排序,默认升序,-field即为降序
    # 支持排序完成后再对相同的值根据第二个字段再次排序
    ret05 = models.Publisher.objects.order_by("name", "pid")
    
    # 6: reverse反转,对排序完成QuerySet反转;
    ret06 = ret04.reverse()
    
    # 7: values 返回QuerySet内部非对象而是对象属性<指定属性>组成的字典
    # all可以省略
    ret07 = models.Publisher.objects.all().values("id", "name")
    
    # 8: values 返回QuerySet内部非对象而是对象属性<指定属性>组成的元祖
    # all可以省略
    ret08 = models.Publisher.objects.all().value_list("id", "name")
    
    # 9: distinct 去重,根据字段及字段去重
    ret09 = models.Publisher.objects.values("name").distinct("name")
    
    # 10: count获取条目的数量
    # all可以省略
    # 可以用len同样获取,len效果更高
    ret10 = models.Publisher.objects.all().count() 
    len(models.Publisher.objects.all())
    
    # 11:返回第一个对象
    ret11 = models.Publisher.objects.all().first()
    
    # 12:返回最后一个对象
    ret12 = models.Publisher.objects.all().last()
    
    # 13:判断是否存在
    ret13 = models.Publisher.objects.filter(id=1).exists()
    

    2.7.6 ORM中的双下划线

    2.7.6.1 单表
    # 值比较
    ret14 = models.Publisher.objects.filter(pk__lt=2) # less than
    ret15 = models.Publisher.objects.filter(pk__lte=2) # less equal than
    ret16 = models.Publisher.objects.filter(pk__gt=1)  # greate than
    ret17 = models.Publisher.objects.filter(pk__gte=1) # grate equal tha
    ret18 = models.Publisher.objects.filter(pk__range=[1, 2]) # 1< ok < 2
    ret19 = models.Publisher.objects.filter(pk__in=[1, 2, 3]) # 包含1,2,3
    
    # 模糊查找
    ret20 = models.Publisher.objects.filter(name__contains='123') # 相当于like
    ret21 = models.Publisher.objects.filter(name__icontains='123') # 忽略大小写
    ret22 = models.Publisher.objects.filter(name__startswith='123') # 以什么什么开始
    ret23 = models.Publisher.objects.filter(name__istartswith='123') # 忽略大小写
    ret24 = models.Publisher.objects.filter(name__endswith='123') # 以什么什么结束
    ret25 = models.Publisher.objects.filter(name__iendswith='123') # 忽略大小写
    
    # 时间查询
    ret26 = models.Publisher.objects.filter(age__year='2019') # 
    ret27 = models.Publisher.objects.filter(age__contains='2019-01')
    
    # null查询
    ret28 = models.Publisher.objects.filter(age__isnull=True)
    
    2.7.6.2 外键与反向
    # Book中有外键->连接Publisher
    
    # 正向查-我想查询书,根据出版社的名字的查
    ret29 = models.Book.objects.filter(publisher__name='人民邮电出版社')
    # 可以再加单表操作查询
    ret30 = models.Book.objects.filter(publisher__name__contains='人民邮电出版社')
    
    
    # 反向查-我想查出版社, 根据书籍obj或者书籍字段
    # 查Publisher根据book_obj来查
    ret31 = models.Publisher.objects.get(pk=1)
    ret31.book_set # 约定格式,即类名小写并加_set, 
    # 查Publisher根据book中的字段,约定也是类名小写加__field
    ret32 = models.Publisher.objects.filter(book__name__contains="图解")
    
    # 如下语句中设置了别名即查询别名,用来反向查找
    publisher = models.ForeignKey(to=Publisher, on_delete=models.SET('1'), to_field="id", related_query_name='mybook')
    # 外键中设置别名
    # related_name='books' 反向查询别名,代替book_set
    ret31.books
    # 根据外键字段查询时,代替表表名
    ret32 = models.Publisher.objects.filter(books__name__contains="图解")
    
    # 外键中设置查询别名
    # related_query_name='mybook'
    # 代替表名,外键别名,作用于字段查询中
    ret32 = models.Publisher.objects.filter(mybook__name__contains="图解")
    
    2.7.6.3 多对多与增删改查
    author = models.Author.objects.get(pk=1) 
    book = models.Book.objects.get(pk=1)
    
    # 多对多正向-对象
    author.book # 关系对象
    author.book.all() # 从关系对象中获取book对象
    
    # 多对多反向-对象
    book.author_set # 反向默认方式获取,返回关系对象
    book.author_set.all() #  从关系对象中获取author对象
    
    # 多对多正向-字段
    alvin_book = models.Book.objects.filter(author__name="Alvin")
    
    # 多对多反向-字段
    tupu_author = models.Author.objects.filter(book__name="图解网络硬件")
    
    # 关系设置-包括多对多双方的,及一对多中,从一与多的关系
    ##################################################
    # 多对多-设置关系
    
    # set设置多对多关系,需要引用对象:
    # 作者的书设置为书-pk;
    author.book.set([1, 2, 3])
    # 作者的书设置为书-对象
    author.book.set(models.Book.objects.filter(id__in=[2, 3]))
    
    # add/remove/clear添加/删除/清空多对多关系
    # 多对多中支持id及obj
    author.book.add(1, 2)
    author.book.remove(*models.Book.objects.filter(id__in=[2, 3]))
    author.book.clear()
    
    # 新增一个和当前对象有关系的对象,关系自动生成
    author.book.create(name="兔姐自传")
    
    #######################################################
    # 一对多-设置关系
    pub01 = models.Publisher.objects.get(pk=1)
    # 支持上面所有方法,区别
    # set/add/remove仅支持obj及*Queryset,不支持pk列表
    pub01.book_set.add(*models.Book.objects.filter(pk__in=[1, 2, 3]))
    # clear及/remove这类会造成book中的pubulisher_id字段为空,需要在数据库中设置为空才可使用
    pub01.book_set.remove(*models.Book.objects.filter(pk__in=[1, 2]))
    

    2.7.6 ORM高级操作

    2.7.6.1 聚合
    ##############################
    # 需要使用aagregate加聚合不同的方法
    # aggregate终止字句,后面没法再跟其他放发
    from django.db.models import Max, Min, Count, Sum, Avg
    books = models.Book.objects.all()
    print(books)
    print(books.aggregate(Max('price'), Min('price'), Count('price'), sum=Sum('price'), avg=Avg('price')))
    

    ####返回结果是一个字典,默认生成的key是field__funName,如果想改变,可以指定形参生成如sum及avg;
    # {'sum': Decimal('1163.00'),
    #  'avg': Decimal('96.916667'),
    #  'price__max': Decimal('100.00'),
    #  'price__min': Decimal('77.00'),
    #  'price__count': 12}
    
    2.7.6.2 分组

    分组(group)一般和聚合(aggregate)联合使用,单独的分组是没有意义的,当涉及多个表时,可以使用join进行连表;

    这个是一个查询+连表+分组+聚合的sql

    select app01_publisher.name, avg(app01_book.price) 
    from app01_book 
    inner join app01_publisher 
    on (app01_book.pub_id = app01_publisher.id)
    group by app01_publisher;
    

    查询一本书有多少个作者

    sql写法:

     select app01_book.name as book_name, count(app01_author.name) as count 
     from app01_book 
     inner join app01_author_book on (app01_book.id=app01_author_book.book_id) 
     inner join app01_author on (app01_author_book.author_id=app01_author .id) 
     group by app01_book.name;
    

    orm写法:

    # 通过book对象查询方法
    models.Book.objects.annotate(count=Count('author__id')).values('id', 'name', 'count')
    # 通过author对象查询方法
    models.Author.objects.values('book__id', 'book__name').annotate(count=Count('id'))
    

    查询每个出版社最便宜的一本书

    sql写法:

    select app01_publisher.id, app01_publisher.name, min(app01_book.price) 
    from app01_publisher
    left join app01_book on (app01_publisher.id = app01_book.pub_id) 
    group by app01_publisher.id;
    

    orm写法:

    models.Book.objects.values('pub_id', 'pub__name').annotate(Min('price'))
    models.Publisher.objects.annotate(m=Min("book__price")).values('id', 'name', 'm')
    # 注意两种写法对空数据的处理方式不同,一个为left,一个为right
    

    image-20200923190626932

    查询不止一个作者的书

    # 方法1
    models.Book.objects.annotate(count=Count('author__id')).values('id', 'count').filter(count__gt=1)
    # 方法2
    models.Author.objects.values('book').annotate(count=Count('id')).filter(count__gt=1)
    

    根据一本书的作者数量排序

    models.Book.objects.annotate(count=Count('author__id')).values('id', 'count').order_by('-count')
    

    一个作者他所有书的价格之和

    models.Author.objects.annotate(Sum("book__price")).values()
    models.Book.objects.values('author', 'author__name').annotate(Sum("price"))
    

    image-20200923192358126

    2.7.6.3 F查询

    在ORM中,我要想比较一个字段与另一个字段,或者赋值时根据另外一个字段赋值,需要F函数;

    F字段支持与常数的加减乘除和取模操作

    from django.db.models import F
    # 查询所有库存大于销量的书籍
    models.Book.objects.filter(score__gt=F('soild'))
    
    # 对所有书本打8折销售
    models.Book.objects.all().update(price=F('price')*0.8)
    
    2.7.6.4 Q查询

    在filter中多个条件是与的关系,当我们需要用到或,非等更复杂的查询条件时,需要Q函数;

    支持条件:

    1. | 或
    2. & 与
    3. ~ 非
    # 多条件合并查询,id<3或者id>10,切名字不是以‘图解;开头的书
    models.Book.objects.filter(Q(Q(id__lt=3) | Q(id__gt=10)) & ~Q(name__startswith="图解"))
    
    2.7.6.5 事物

    对应mysql中的事物操作,在ORM中使用固定的格式;

    格式如下:

    from django.db import transaction
    with transaction.atomic(): # 原子型操作
        ... #  要写得到操作内容
    

    但是正常情况下,我不希望报错影响程序,正常写法应该如下:

    try: # try必须在外边,如果在with里面,会使原子操作无效
        from django.db import transaction
        with transaction.atomic():
            ... #  要写得到操作内容
    except Exception as e:
        print(e)
    

    2.8 cookie与session

    2.8.1 cookies

    因HTTP是无状态的协议,即每次请求都是独立的。但是有时候我们需要保存一个状态<比如登录状态>,这个时候就需要用到cookies;

    cookies是保存在浏览器中,用来保存一些状态的键值对,可以用来验证:

    1. 由服务器让浏览器进行设置的;
    2. cookies信息保存在浏览器本地的,浏览器有权不保存;
    3. 浏览器再次访问时自动携带应对的cookie;
    2.8.1.1 django中操作cookie
    # 设置cookies:
    # 需要为Response类的对象
    # 本质上是在http的响应头中添加set_cookie字段,添加键值对
    response.set_cookie('is_login', 'value')
    response.set_signed_cookie('is_login', "cookies_bruce", salt="s14", max_age=100, path="/", secure=False, httponly=True)
    #参数说明
    ## max_age 超时时间
    ## path cookie生效的路径,默认为/,可以设置比如/home/
    ## secure=True 是否可以进行HTTPS传输
    ## httponly=True 只能传输,限制js获取,比如document.cookies时,就无法获取了;
    
    # 获取cookies:
    #  本质是通过请求头中的set_cookie字段获取
    request.COOKIES.get('key') # 非加密获取方式,加密这种方式获取的是密文
    request.get_signed_cookie('is_login', salt="s14", default="") # 加密方式获取
    
    # 删除cookies:
    #  本质上仍然是设置cookie,只是将内容变为“”,将超时时间变为0
    reponse.delete_cookie(key)
    
    2.8.1.2 简单举例
    from django.shortcuts import render, redirect, reverse
    
    # Create your views here.
    from app01 import models
    from functools import wraps
    
    # 新建一个装饰器,用于验证登录cookie
    def isLogin(fun):
        @wraps(fun)
        def wp(request, *args, **kwargs):
            b_url = request.get_full_path()
            ## 获取明文的cookie
            # login_status = request.COOKIES.get('is_login')
            
            # 获取密文的cookie
            login_status = request.get_signed_cookie('is_login', salt="s14", default="")
            if login_status != 'cookies_bruce':
                return redirect(reverse('login') + "?url={}".format(b_url))
            else:
                ret = fun(request, *args, **kwargs)
                return ret
        return wp
    
    def login(request):
        md = request.method
        # 跳转会原来页面-login页面接受传递的参数
        url = request.GET.get('url')
        if url:
            to_url = url
        else:
            to_url = reverse('home')
    
        if md == "GET":
            return render(request, 'log.html')
        elif md == "POST":
            name = request.POST.get("user")
            pwd = request.POST.get("pwd")
            ret = redirect(to_url)
            if models.Muser.objects.filter(name=name, pwd=pwd).first():
                ##设置cookie的两种方法
                # ret.set_cookie('is_login', '1')
                ret.set_signed_cookie('is_login', "cookies_bruce", salt="s14", max_age=100, path="/", secure=False, httponly=True)
            return ret
    
    @isLogin
    def home(request):
        print(request.COOKIES.get("is_login"))
        return render(request, 'home.html')
    
    @isLogin
    def main(request):
        return render(request, 'main.html')
    
    @isLogin
    def delcookie(request):
        rt = redirect('home')
        rt.delete_cookie('is_login')
        return rt
    

    2.8.2 session

    cookie是保存在浏览器中的键值对,而session是保存在服务器上的一组组键值对并依赖cookie;

    2.8.2.1 session存在的意义

    cookie存在的问题

    1. cookie是信息存在浏览器本地,不太安全;
    2. 浏览器会对cookie的大小和个数有一定限制的,一般不大于4K;

    而session存储Server上相对安全,同时也没有大小额的限制;

    2.8.2.2 django操作session

    前提:

    # setting中
    ## app里要有session的注册
    INSTALLED_APPS = [
        'django.contrib.sessions',
    
    ]
    ## 中间件里要有session的设置
    MIDDLEWARE = [
        'django.contrib.sessions.middleware.SessionMiddleware',
    ]
    
    # 数据库中要有django_session相关的表
    

    操作:

    #  获取、设置、删除Session中的数据-操作方法类似于列表
    ## 设置
    request.session['k1']
    request.session.setdefault('k1',123) # 存在则不设置
    ## 获取
    request.session.get('k1',None)
    request.session['k1'] = 123
    ## 删除
    del request.session['k1']
    request.pop('k1')
    
    # 所有 键、值、键值对
    request.session.keys()
    request.session.values()
    request.session.items()
    
    # 会话session的key
    request.session.session_key
    
    # 将所有Session失效日期小于当前日期的数据删除
    	## 默认情况下,即使session超时,数据也不会删除的
    request.session.clear_expired()
    
    # 检查会话session的key在数据库中是否存在
    request.session.exists("session_key")
    
    # 删除当前会话的所有Session数据,但不会删除浏览器cookie
    request.session.delete()
    
    # 删除当前的会话数据并删除会话的Cookie
    	##这用于确保前面的会话数据不可以再次被用户的浏览器访问
    	##例如,django.contrib.auth.logout() 函数中就会调用它。
    request.session.flush() 
    
    # 设置会话Session和Cookie的超时时间
        ## 如果value是个整数,session会在些秒数后失效。
        ## 如果value是个datatime或timedelta,session就会在这个时间后失效。
        ## 如果value是0,用户关闭浏览器session就会失效。
        ## 如果value是None,session会依赖全局session失效策略。
    request.session.set_expiry(value)
    
    
    
    

    设置

    # 在django的global_setting中可以看到session的默认配置
    	##查看方法,在任意文件中
        from django.conf import global_settings
        ## 然后,ctrl + 点击global_settings即可看到
        
    # Cache to store session data if using the cache session backend. #跟缓存相关
    SESSION_CACHE_ALIAS = 'default'
    # Cookie name. This can be whatever you want.# 在cookie中的名称
    SESSION_COOKIE_NAME = 'sessionid'
    # Age of cookie, in seconds (default: 2 weeks). # 默认超时时间
    SESSION_COOKIE_AGE = 60 * 60 * 24 * 7 * 2
    # A string like "example.com", or None for standard domain cookie.# cookie的几个参数 
    SESSION_COOKIE_DOMAIN = None
    # Whether the session cookie should be secure (https:// only).
    SESSION_COOKIE_SECURE = False
    # The path of the session cookie.
    SESSION_COOKIE_PATH = '/'
    # Whether to use the HttpOnly flag.
    SESSION_COOKIE_HTTPONLY = True
    # Whether to set the flag restricting cookie leaks on cross-site requests.
    # This can be 'Lax', 'Strict', 'None', or False to disable the flag.
    SESSION_COOKIE_SAMESITE = 'Lax'
    # Whether to save the session data on every request. # 每次请求来后,都会更新超时时间
    SESSION_SAVE_EVERY_REQUEST = False
    # Whether a user's session cookie expires when the Web browser is closed. #浏览器关闭即超时
    SESSION_EXPIRE_AT_BROWSER_CLOSE = False
    # The module to store session data # 设置存储 session的存储方式,有缓存,数据库等
    SESSION_ENGINE = 'django.contrib.sessions.backends.db'
    # Directory to store session files if using the file session module. If None,
    # the backend will use a sensible default.
    SESSION_FILE_PATH = None
    # class to serialize session data
    SESSION_SERIALIZER = 'django.contrib.sessions.serializers.JSONSerializer'
    

    2.9 中间件

    中间件是一个用来处理Django的请求和响应的框架级别的钩子。

    它是一个轻量、低级别的插件系统,用于在全局范围内改变Django的输入和输出。

    每个中间件组件都负责做一些特定的功能。

    本质是一个自定义类,类中定义了几个方法,Django框架会在处理请求的特定的时间去执行这些方法。

    2.9.1 配置步骤

    1. 新建文件夹,一般放置在app下,eq:middlewares

    2. 新建py文件,名字任意,eq:mid01.py

    3. 文件中新建类

      from django.utils.deprecation import MiddlewareMixin
      class MID01(MiddlewareMixin):
          ...
      
    4. 在project下的setting中注册middlewares;

      MIDDLEWARE = [
          'app.middlewares.mid01.MID01',
      ]
      

    2.9.2 分类介绍

    中间件共5个方法:

    • process_request(self,request) : 处理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):处理response
    2.9.2.1 process_request

    process_request(self, request)用来处理request请求

    执行时间:路由之前

    参数:

    • request:请求的对象,和视图函数时同一个对象;

    执行顺序:按照注册的顺序,顺序执行

    返回值:

    • None:正常流程
    • HttpResponse后跳转至同类下的response方法,向上执行;
    2.9.2.2 process_response

    process_response(self, request, response):处理response

    执行时间:视图及所有的中间件之后

    参数:

    • request:请求的对象,和视图函数时同一个对象;
    • response:返回的对象,和视图函数返回的对象是同一个;

    执行顺序:倒序执行

    返回值:

    • 必须返回HttpResponse
    2.9.2.3 process_view

    process_view(self, request, view_func, view_args, view_kwargs):修改视图行为

    执行时间:路由之后,视图之前

    参数:

    • request:请求的对象,和视图函数时同一个对象;
    • view_func:视图函数
    • view_args:视图函数的位置参数
    • view_kwargs:视图函数的关键字参数

    执行顺序:顺序执行

    返回值:

    • None:正常流程
    • HttpResponse后跳过之后的view及视图函数直接转至最后一级response函数;
    2.9.2.4 process_exception

    process_exception(self, request, exception):异常时处理

    执行时间:视图函数中,当视图函数异常时执行

    参数:

    • request:请求的对象,和视图函数时同一个对象;
    • exception:异常的对象;

    执行顺序:倒序执行

    返回值:

    • None:当前中间件没有处理异常,交给下一个中间件处理异常,直至最后django处理异常;
    • HttpResponse当前中间件处理了异常,跳过之后的exception直接转至最后一级response函数;
    2.9.2.5 process_template_response

    process_template_response(self,request,response):修改模板渲染

    执行时间:视图中返回的对象时TemplateResponse对象时触发;

    参数:

    • request:请求的对象,和视图函数时同一个对象;
    • response:返回的对象,和视图函数返回的对象是同一个,为TemplateResponse对象;

    执行顺序:倒序执行

    返回值:

    • 再次处理过的TemplateResponse对象
      • TemplateResponse相对于Render对象时,此时的TR还没有完成渲染;
      • 修改模板的名字:response.template.name
      • 修改内容数据:response.context.data

    2.9.3 整体执行顺序

    2.9.3.1 非触发式流程如下

    img

    2.9.3.2 如果包含触发式流程如下:

    img

    2.9.4 csrf中间件

    2.9.4.1 csrf攻击简介

    CSRF(Cross-site request forgery),中文名称:跨站请求伪造,也被称为:one click attack/session riding,缩写为:CSRF/XSRF

    如图中所示,B利用了A在浏览器中的cookie信息,模拟合法用户在A上完成了一个用户完全不知得到操作,从而达成一定的目的;

    img

    2.9.4.2 django中csrf的防御流程

    在django中使用csrf-token的方式来防御csrf攻击;

    1. 当用户访问有post等需求的页面时,django会在用户get请求时设置两个东西:

      • django会在response中设置浏览器cookies<csrftoken:64位字符串>;

      • django会在渲染的页面中放置一个input框;

        <input type="hidden" name="csrfmiddlewaretoken" value=64位字符串>
        
    2. 当用户post类操作提交相关数据时,会同时提交input框里的内容至django,当然cookie也会带;

    3. django收到后,会在csrf中间件中完成input提交的token与cookie携带的cookie的比较,详细:

      • 在process_request中会从session/cookie中获取token,并赋值给META.CSRF_COOKIE;

      • 在process_views中完成整体的验证,步骤详细:

        1. 首先判断是否豁免验证,一般有csrf_processing_done,csrf_exempt及非重点method;

        2. 然后会尝试获取submit的cookie,共有两种:

          • 从post中获取

            request.POST.get('csrfmiddlewaretoken', '')
            
          • 从META中的settings.CSRF_HEADER_NAME('HTTP_X_CSRFTOKEN')属性获取

            equest.META.get(settings.CSRF_HEADER_NAME, '')
            
        3. 对比两个token,判断其解密后的secret是否一致,详细:

          • 一个token长64,其中前32为为salt,后32为加密后的secret;
          • 分别使用salt对密文解密得到secret,并对比是否相同;
      • 在process_response中根据request中的cookie异常属性值来维护csrf_cookie;

    2.9.4.3 csrf装饰器

    在django中,除了使用中间件统一验证csrf,还可以使用装饰器为某些页面设置豁免,或者在关闭中间件的的情况下,为某些页面加上csrf验证;

    from django.views.decorators.csrf import csrf_exempt, csrf_protect
    
    @csrf_exempt   # 设置豁免,本质是为视图函数index添加 csrf_exempt=True的属性
    def index(request):  
        pass
    
    @csrf_protect  # 设置csrf验证-方式1
    def index(request):  
        pass
    
    urlpatterns = [  # 设置csrf验证-方式2
        url(r'^index/',csrf_protect(views.index)),  
    ]
    

    2.9.5 配置举例

    2.9.4.1 登录认证及所有中间方法配置
    
    class MID01(MiddlewareMixin):
        
        # 执行在路由之前
        def process_request(self, request):
            ip = request.META.get('REMOTE_ADDR')
            history = TIME.get(ip, [])
            TIME[ip] = history
            now = time.time()
            times = 0
            for item in history[:]:
                if now - item < 5:
                    times += 1
                else:
                    TIME[ip].remove(item)
            TIME[ip].append(now)
            print("次数", times)
            if times > 2:
                return HttpResponse("访问过于频繁")
            status = request.session.get('is_log')
            login_url = reverse('login')
            if request.path_info != login_url:
                t_url = request.path_info
                if not status:
                    return redirect(login_url + "?url={}".format(t_url))
    
        # 执行在路由之后,视图函数之前
        def process_view(self, request, view_fun, view_args, view_kwargs):
            print("process_view_MID01")
    
        # 执行在视图函数之后
        def process_response(self, request, response):
            print("process_response_MID01")
            return response
    
        # 视图函数之后且视图中有异常时执行
        def process_exception(self, request, exception):
            print("process_exception_MID01")
    
        # 执行条件时必须返回TemplateResponse
        def process_template_response(self, request, response):
            print("process_template_response_MID01")
    

    2.10 AJAX

    Asynchronous Javascript And Xml (异步的js与xml):使用js语言与服务器进行异步交互,传输XML数据,现在也可以传输json格式;

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

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

    2.10.1 json

    在python中及js中普遍使用json进行,不同模块间的数据交互,如下是一个序列化与反序列化图;

    img

    2.10.2 ajax的两种使用方式

    2.10.2.1 使用jquery实现 ajax方法

    这里面使用get请求来传递参数,实际是因为未传递csrf-token,所以避免认证环节;

    template中:

    
    <body>
    {% csrf_token %}
    <input id="i1" type="text" name="i1">+
    <input id="i2" type="text" name="i2">=
    <input id="i3" type="text" name="i3">
    <button>提交</button>
    
    <script src="/static/jquery-3.5.1.js"></script>
    <script>
        $("button").on('click', function (){
            $.ajax(
                {
                    
                    url:"/hide/", 提交向的URL
                    type:"GET",   提交的方法
                    data: {  提交的数据
                        i1:$("#i1").val(),
                        i2:$("#i2").val()},
                    success: function (data){ 回调函数,参数data接收视图函数的返回
                        $("#i3").val(data)
                    }
                }
            )
        })
    </script>
    </body>
    

    view中:

    def hide(request):
        i1 = request.GET.get('i1') # 从提交的数据中获取值
        i2 = request.GET.get('i2') # 从提交的数据中获取值
        i3 = int(i1) + int(i2) # 处理
        return HttpResponse(i3) # 返回值到回调函数的data形参中,一般返回json格式;
    
    2.10.2.2 使用原生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=q1mi&password=123456");
        xmlHttp.onreadystatechange = function () {
          if (xmlHttp.readyState === 4 && xmlHttp.status === 200) {
            alert(xmlHttp.responseText);
          }
        };
      }; 
    

    2.10.3 csrf-token上传

    csrf-token在client中能找到的位置有两个:cookie中及隐藏的input;

    csrf-token在request的携带位置总共有两个:POST.csrfmiddlewaretoken及头部的X_CSRFTOKEN;

    2.10.3.1 从隐藏input到POST.csrfmiddlewaretoken
    $.ajax({
      url: "/cookie_ajax/",
      type: "POST",
      data: {
        "username": "Q1mi",
        "password": 123456,
        "csrfmiddlewaretoken": $("[name = 'csrfmiddlewaretoken']").val()  // 使用jQuery取出csrfmiddlewaretoken的值,拼接到data中
      },
      success: function (data) {
        console.log(data);
      }
    })
    
    2.10.3.2 从cookie到头部的X_CSRFTOKEN
    $.ajax({
      url: "/cookie_ajax/",
      type: "POST",
      headers: {"X-CSRFToken": $.cookie('csrftoken')},  // 从Cookie取csrftoken,并设置到请求头中
      data: {"username": "Q1mi", "password": 123456},
      success: function (data) {
        console.log(data);
      }
    })
    
    
    2.10.2.3 上传文件
    $("#b3").click(function () {
      var formData = new FormData();
      formData.append("csrfmiddlewaretoken", $("[name='csrfmiddlewaretoken']").val());
      formData.append("f1", $("#f1")[0].files[0]);
      $.ajax({
        url: "/upload/",
        type: "POST",
        processData: false,  // 告诉jQuery不要去处理发送的数据
        contentType: false,  // 告诉jQuery不要去设置Content-Type请求头
        data: formData,
        success:function (data) {
          console.log(data)
        }
      })
    })
    

    三. 其他操作

    3.1 前端模板的COPY

    大部分情况下,我们并不写HTML网页,我们只需要copy并修改即可;

    3.1.1 常用的几个COPY的网址

    1. 最有名的bootstrap,首页

    2. 较炫酷的jq22,首页

    3.1.2 Copy的一般步骤

    前面页面的复制设计HTML及相关的静态文件,根据复杂度可以使用两种方式;

    3.1.2.1 较简单的页面

    一般较简单的页面,比如bootstrap中的简约登录框,复制步骤如下:

    1. 浏览器-检查-将页面HTML中的body复制下来替换自己的HTML中的body;
    2. 在head中查看需要哪些静态文件,然后下载或者新建引入;
    3.1.2.2 复杂的页面

    针对复杂的页面,使用上述方法就太慢并且有时候会有异常,那么我们可以使用下面的步骤:

    1. Ctrl+S另存为:因为涉及的静态文件太多一个个下载过慢,另存一次下载;

      • 复制前将相关的广告之类删除
    2. Ctrl+U复制源码贴到本地:为防止页面代码在显示中被JS等改变,复制源码是最准确的;

      • 复制前将相关的广告之类删除
    3. 将下载的相关静态文件,按类复制到static文件夹;

    4. Pycharm中使用Ctril+R替换,因为一个个改太慢了,Pycharm支持正则替换哦;

      #查找
      'css/(.*?.css)'
      #替换为
      "/static/css/loginin/$1"
      
    5. 查漏补缺,比如以上操作结束后,仍然未加载背景图片,去源网址取,然后找到要修改的位置:

      • 缺少的文件位置不一定只在HTML中,也可以在css及js中;

    3.2 上线时的操作

    3.2.1 关闭debug

    在setting.py中设置

    DEBUG=False #原来为True
    ALLOWED_HOST = ['*'] # 设置允许所有的主机访问;
    

    3.3 使用django.admin

    3.3.1 步骤

    1. 创建一个超级用户

      python manage.py createsuperuser
      
    2. 注册model

      # 在app下面的admin.py中
      from django.contrib import admin
      from app01 import models
      admin.site.register(models.Person)
      
    3. url登录及obj显示

      # 使用http://127.0.0.1/admin/
      # 可以为类添加__str__(),这个可以用来在admin显示obj的细节
      

    3.3.2 调整

    整体通过定义admin.ModelAdmin的子类来对显示及修改作修改;

    调整的内容:

    1. 修改显示的字段
    2. 修改显示中可修改的字段
    3. 隐藏非关键字段,显示部分字段;
    4. 将forgin-key放在关联目标的下面显示等;
    5. 显示多对多关系;

    model.py

    from django.db import models
    
    # Create your models here.
    
    
    # 用户表,存放所有的所有用户
    ## 关联role,分配角色信息
    class User(models.Model):
        name = models.CharField(max_length=32, verbose_name="用户名")
        password = models.CharField(max_length=32, verbose_name="密码")
        role = models.ManyToManyField(to="Role", verbose_name="角色")
    
        class Meta:
            verbose_name = "用户"
            verbose_name_plural = "用户表"
    
    # 角色表
    ## 关联permission,基于角色分配权限
    class Role(models.Model):
        name = models.CharField(max_length=32, verbose_name="角色名")
        permission = models.ManyToManyField(to="Permission", verbose_name="权限")
    
        class Meta:
            verbose_name = "角色"
            verbose_name_plural = "角色表"
    
        def __str__(self):
             return self.name
    
    # 权限表
    ## 存储所有可使用的权限信息
    class Permission(models.Model):
        url = models.CharField(max_length=64, verbose_name="url")
        name = models.CharField(max_length=32, verbose_name="权限")
        is_menu = models.BooleanField(verbose_name="是否菜单", default=False)
        menu = models.OneToOneField(to="Menu", null=True, blank=True, on_delete=models.CASCADE)
    
        class Meta:
            verbose_name = "权限"
            verbose_name_plural = "权限信息表"
    
        def __str__(self):
             return self.name
    
    class Menu(models.Model):
        title = models.CharField(max_length=32, verbose_name="标题", default="default")
        icon = models.CharField(max_length=64, verbose_name="图标")
    
        class Meta:
            verbose_name = "菜单"
            verbose_name_plural = "菜单信息表"
    
        def __str__(self):
            return self.title
    
    

    rbac/admin.py

    from django.contrib import admin
    from rbac import models
    # 根据permission到menu的外键关系,建立table放置在menu下面;
    class MenuInPermision(admin.TabularInline):
        model = models.Permission
    
    # 建立useradmin
    class UserAdmin(admin.ModelAdmin):
        list_display = ['name', 'password', '角色字段'] # 显示展示的字段,默认为obj
        list_editable = ['password',] # 显示以上字段中可直接编辑的字段,默认无
    
        def 角色字段(self, obj): # 转换下多对多字段,让其可以在display中显示
             return [role for role in obj.role.all()]
     
    class MenuAdmin(admin.ModelAdmin): 
        inlines = [MenuInPermision] # 关联上面建立的table,并放置在同一页面
    
    class PermissionAdmin(admin.ModelAdmin):
        list_display = ['name', 'url', 'is_menu', "menu"]
        # fields = ['name', 'url', 'is_menu', "menu"] # 可编辑的字段,默认所有
        search_fields = ('name', 'url') # 建立搜索框,并制定可搜索的字段
        fieldsets = ( # 与fields冲突,将所有的字段进行分类,并可额外指定样式
            ['主要', {
                'fields': ('name', 'url'),
            }
            ],
            [
              '高级', {
                'classes': ('collapse',), #css样式 # 可隐藏及展示的样式
                'fields': ('is_menu', 'menu'),
            }
            ]
        )
    
    admin.site.register(models.User, UserAdmin) # 注册同时使用UserAdmin自定义内容
    admin.site.register(models.Role)
    admin.site.register(models.Permission, PermissionAdmin)
    admin.site.register(models.Menu, MenuAdmin)
    
  • 相关阅读:
    [转]优秀的程序员不会觉得累成狗是一种荣耀
    .NET读写Excel工具Spire.XlS使用(DataExport )
    WPF之Binding深入探讨
    第一个WPF应用程序
    Visio作图
    唯一的重复元素
    Strange Problem O(∩_∩)O~
    数据库知识点①
    HDU 2825 Wireless Password
    POJ 1625 Censored!
  • 原文地址:https://www.cnblogs.com/FcBlogPythonLinux/p/13765485.html
Copyright © 2020-2023  润新知