zoukankan      html  css  js  c++  java
  • 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)
    
  • 相关阅读:
    hiveserver2 with kerberos authentication
    python Basic usage
    python Quicksort demo
    Python HeapSort
    mrunit for wordcount demo
    CCDH证书
    Hadoop question list
    Hadoop Yarn core concepts
    Hadoop Resource
    Hadoop could not find or load main class
  • 原文地址:https://www.cnblogs.com/FcBlogPythonLinux/p/13765485.html
Copyright © 2011-2022 走看看