【web系列七】高阶技巧之django+ajax实现python后端console输出在前端的动态显示

导读:本篇文章讲解 【web系列七】高阶技巧之django+ajax实现python后端console输出在前端的动态显示,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

目录

环境

目的

需要解决的问题

问题解析

解决方案

实战

需要实现的功能

工程结构

具体实现

主页功能

周期性发送请求

获取数据并返回display

生成数据的功能函数

线程类和控制函数

最终效果


环境

        django+ajax(juqery)+vue(可选)

        不使用websocket长连接

目的

需要解决的问题

        当我们需要在服务器上实现一些复杂逻辑功能时,可能会在一个函数(称为mission)中打印很多内容到python端的控制台,但是这些内容并不是mission的返回值,因此无法直接被view.py中的函数(称为getData)捕获并渲染到页面(称为display)。

问题解析

        关键点在于

  1. 要让getData能够捕获到mission打印到控制台的信息
  2. 前端获取后端信息,动态更新页面,当mission完成后,停止更新前端
  3. 多线程,getData和mission需要运行在两个线程下

解决方案

  1. 在view.py中建立全局变量存储mission打印的信息
  2. 创建线程类MyThread,包含线程控制指令
  3. 创建线程控制接口函数thread_controller,实现线程的创建、杀死等控制逻辑
  4. console.html中周期性向后端发送ajax请求,并接受数据,当接收到stop信号时,停止发送请求
  5. display.html中负责解析和显示数据
  6. getData只负责调用thread_controller即可

实战

需要实现的功能

        console.html中显示一个按钮和文本框,点击按钮后,周期性getData发送get请求,getData中运行mission函数,并动态的将mission中需要打印的内容返回至display.html,并将display.html显示在console.html的文本框中,当mission运行完后,向console.html发送停止指令。

工程结构

---demo               //项目容器
  |---demo            //主目录,整个项目的配置和调度中心。
  |  |---__init__.py   //告诉python,该目录是一个包   。  
  |  |---asgi.py       //一个 ASGI 兼容的 Web 服务器的入口,以便运行你的项目。
  |  |---settings.py   //该 Django 项目的设置/配置。
  |  |---urls.py       //该 Django 项目的 URL 声明; 一份由 Django 驱动的网站"目录"
  |  |---wsgi.py       //一个 WSGI 兼容的 Web 服务器的入口,以便运行你的项目
  |---first_app        //新建的应用
  |  |---migrations    //用于数据库迁移,便于数据库管理
  |  |  |---__init__.py
  |  |---templates     //用于存放html文件,即网页结构
  |  |  |---first_app
  |  |      |---js_lib //存放js库文件
  |  |          |---console_vue.js  //自己写的控制代码
  |  |          |---jquery.min.js   //jquery,包装了ajax
  |  |          |---vue.js          //vue库
  |  |      |---console.html        //主页,控制台
  |  |      |---display.html        //显示后端打印的信息
  |  |---__init__.py
  |  |---admin.py      //自带的后台管理
  |  |---apps.py       //创建对应app类的文件
  |  |---models.py     //mtv中的m,用于和数据库交互
  |  |---tests.py      //用于开发测试用例
  |  |---urls.py       //first_app的urls目录
  |  |---views.py      //mtv中的v,用于处理网页的后端业务逻辑
  |---third_project    //功能代码
  |  |--mission.py     //具体功能的实现
  |  |--my_thread.py   //线程类及控制函数
  |---db.sqlite3       //轻量级数据库,用于和models交互
  |---manage.py        //一个实用的命令行工具,可让你以各种方式与该 Django 项目进行交互。

具体实现

主页功能

        console.html

        使用v-html=”message”接收数据。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>动态显示</title>
    <style type="text/css">
      #result {
        width: 100%;
        height: 600px;
        border: solid 1px #90b;
      }
    </style>
  </head>
  <body>
    <div id="contain">
      <button id="btn" @click="funA">funA</button>
      <div id="result">
        <p v-html="message"></p>
      </div>
    </div>

    <script src="/static/first_app/js_lib/jquery.min.js"></script>
    <script src="/static/first_app/js_lib/vue.js"></script>

    <script src="/static/first_app/js_lib/console_vue.js"></script>
  </body>
</html>

周期性发送请求

        console_vue.js

        这里使用id = setInterval(function_handle, millisecond)实现周期性的发送get请求,并且使用clearInterval(id)停止周期性的发送请求。

        这里需要注意的是function_handle不能包含参数,但是我们可以通过嵌套的方式返回一个不带参数的函数句柄。

var app = new Vue({
    el: "#contain",
    data: {
      message: "",
    },
    methods: {
      funA: function () {
        var that = this;
        var timer = window.setInterval(_send(that), 4 * 1000);  // 不能调用带参数的函数句柄,可以通过嵌套的方式返回一个不带参数的函数句柄
        function _send(_param){ 
          return function() {  // 返回不带参数的函数句柄
            send(_param);
          }
        };
        function send(obj){ 
          $.get("/display/", function (data) {
            console.log(data)
            if(data == "stop") {
              window.clearInterval(timer);   // 使用setInterval返回的id作为参数
              console.log("timer is stoped")
            }
            else
              obj.message = data;
          });
        }
      },
    },
  });

获取数据并返回display

        view.py       

        两个全局变量result用来存放mission打印的信息和thread_dict用来存放线程的状态。

        这里直接用getData调用thread_controller接口即可,接口的参数会在后面解释。

from django.shortcuts import render
from third_project.my_thread import thread_controller
from third_project.mission import print_message

# Create your views here.
result = {}
thread_dict = {}

def home(request):
    '''首页'''
    return render(request, 'first_app/console.html')

def getData(request):
    '''getData'''
    return thread_controller(request, result, thread_dict, 
                             "getData", print_message, [1, 10], 
                             'first_app/display.html')

生成数据的功能函数

        mission.py

        间隔一秒向列表中添加递增的整数,并将列表存放在字典中。

import time

def print_message(start, end, dict):
    dict['print_message'] = []
    for i in range(end - start):
        dict['print_message'].append(i + start)
        time.sleep(1)

e.g.

        print_message(1,5,{})

        {‘print_message’:[1,2,3,4]}

线程类和控制函数

        my_thread.py

# result = {} 存放需要打印的结果,key为被调用的函数名,value为list

# thread_dict = {}

  • # 存放线程状态,key为view中开启线程的函数名,value为state
  • # state  意义
  • # stop   停止或未创建
  • # start  开始运行
  • # pause  暂停运行
  • # end    被调用的函数执行完毕了
  • # killed 线程已杀死
  • # request http响应内容
  • # result 需要打印的结果
  • # thread_dict 线程状态
  • # func_name view.py中函数的名字
  • # callback 被调用的函数名
  • # args callback执行需要的参数
  • # html_path 返回的url
from django.http import HttpResponse
from django.shortcuts import render
import threading

def thread_controller(request, result, thread_dict, func_name, callback, args, html_path):
    
    if result.get(func_name, "NA") == "NA":
        result[func_name] = {}
        thread_dict[func_name] = "stop"
    res = result[func_name]
    state = thread_dict[func_name]
    
    if state == "stop":
        test_thread = MyThread(thread_dict, func_name, callback, args, res)
        test_thread.start()
        test_thread.resume()
    elif state == "end":
        thread_dict[func_name] = "killed"
        return render(request, html_path, res)
    elif state == "killed":
        result[func_name] = {}
        thread_dict[func_name] = "stop"
        return HttpResponse("stop")
    return render(request, html_path, res)


class MyThread(threading.Thread):
    def __init__(self, state_dict, thread_name, func_handle, p_list, g_dict):
        super(MyThread, self).__init__()
        self.g_dict = g_dict
        self.func_handle = func_handle
        self.p_list = p_list
        self.p_list.append(g_dict)
        
        self.paused = True  # Start out paused.
        self.isend = False  # thread is end.
        self.thread_name = thread_name
        self.state_dict = state_dict
        self.cond = threading.Condition()
 
    def run(self):
        while True:
            with self.cond:  # 在该条件下操作
                self.resume()
                list(map(self.func_handle, *zip(self.p_list)))    # 调用外部函数
                self.end()
                if self.paused:
                    self.cond.wait()  # Block execution until notified.
                if self.isend:
                    break
 
    def resume(self):  # 用来恢复/启动run
        with self.cond:  # 在该条件下操作
            self.paused = False
            self.state_dict[self.thread_name] = "start"
            self.cond.notify()  # Unblock self if waiting.
 
    def pause(self):  # 用来暂停run
        with self.cond:  # 在该条件下操作
            self.paused = True  # Block self.
            self.state_dict[self.thread_name] = "paused"
            
    def end(self):  # 用来结束run
        with self.cond:  # 在该条件下操作
            self.isend = True  # Block self.
            self.state_dict[self.thread_name] = "end"

    def kill(self):  # 用来杀死线程
        with self.cond:  # 在该条件下操作
            self._delete()
            self.state_dict[self.thread_name] = "killed"

        注意这里有一个关键点,我们在写线程类的时候,没法知道需要调用什么函数,以及需要什么参数,因此这里使用了map函数,实现将函数句柄和作为参数,便于封装和使用。

        另外要注意Python2和3中map的使用方法有些区别

#python2直接返回列表
map(func_handle, [1,2,3])
def square(x) :            # 计算平方数
    return x ** 2

# 计算列表各个元素的平方
map(square, [1,2,3,4,5])   
# [1, 4, 9, 16, 25]

# 使用 lambda 匿名函数
map(lambda x: x ** 2, [1, 2, 3, 4, 5]) 
# [1, 4, 9, 16, 25]

# 多参数函数
map(lambda x, y: x + y, [1, 3, 5, 7, 9], [2, 4, 6, 8, 10])
# [3, 7, 11, 15, 19]



#python3返回的是迭代器
def square(x) :         # 计算平方数
    return x ** 2

# 计算列表各个元素的平方,返回迭代器
map(square, [1,2,3,4,5])    
# <map object at 0x100d3d550>     

# 使用 list() 转换为列表
list(map(square, [1,2,3,4,5]))  
# [1, 4, 9, 16, 25]

# 使用 lambda 匿名函数
list(map(lambda x: x ** 2, [1, 2, 3, 4, 5]))   
# [1, 4, 9, 16, 25]


# 多参数函数,使用zip函数进行捆绑
def add(x, y) :            # 计算和
    return x + y

list(map(add, *zip([1, 3, 5, 7, 9])))   
# [4, 8, 12, 16]

最终效果

        左边的文本框显示了打印的结果

        右边的waterfall可以看出请求时周期进行的

        底部的stop和timer is stoped说明前端的周期性请求已经停止

【web系列七】高阶技巧之django+ajax实现python后端console输出在前端的动态显示

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/100783.html

(0)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!