当前位置:   article > 正文

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

python输出的console窗口显示出来

目录

环境

目的

需要解决的问题

问题解析

解决方案

实战

需要实现的功能

工程结构

具体实现

主页功能

周期性发送请求

获取数据并返回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发送停止指令。

工程结构

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

具体实现

主页功能

        console.html

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

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8" />
  5. <title>动态显示</title>
  6. <style type="text/css">
  7. #result {
  8. width: 100%;
  9. height: 600px;
  10. border: solid 1px #90b;
  11. }
  12. </style>
  13. </head>
  14. <body>
  15. <div id="contain">
  16. <button id="btn" @click="funA">funA</button>
  17. <div id="result">
  18. <p v-html="message"></p>
  19. </div>
  20. </div>
  21. <script src="/static/first_app/js_lib/jquery.min.js"></script>
  22. <script src="/static/first_app/js_lib/vue.js"></script>
  23. <script src="/static/first_app/js_lib/console_vue.js"></script>
  24. </body>
  25. </html>

周期性发送请求

        console_vue.js

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

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

  1. var app = new Vue({
  2. el: "#contain",
  3. data: {
  4. message: "",
  5. },
  6. methods: {
  7. funA: function () {
  8. var that = this;
  9. var timer = window.setInterval(_send(that), 4 * 1000); // 不能调用带参数的函数句柄,可以通过嵌套的方式返回一个不带参数的函数句柄
  10. function _send(_param){
  11. return function() { // 返回不带参数的函数句柄
  12. send(_param);
  13. }
  14. };
  15. function send(obj){
  16. $.get("/display/", function (data) {
  17. console.log(data)
  18. if(data == "stop") {
  19. window.clearInterval(timer); // 使用setInterval返回的id作为参数
  20. console.log("timer is stoped")
  21. }
  22. else
  23. obj.message = data;
  24. });
  25. }
  26. },
  27. },
  28. });

获取数据并返回display

        view.py       

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

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

  1. from django.shortcuts import render
  2. from third_project.my_thread import thread_controller
  3. from third_project.mission import print_message
  4. # Create your views here.
  5. result = {}
  6. thread_dict = {}
  7. def home(request):
  8. '''首页'''
  9. return render(request, 'first_app/console.html')
  10. def getData(request):
  11. '''getData'''
  12. return thread_controller(request, result, thread_dict,
  13. "getData", print_message, [1, 10],
  14. 'first_app/display.html')

生成数据的功能函数

        mission.py

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

  1. import time
  2. def print_message(start, end, dict):
  3. dict['print_message'] = []
  4. for i in range(end - start):
  5. dict['print_message'].append(i + start)
  6. 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
  1. from django.http import HttpResponse
  2. from django.shortcuts import render
  3. import threading
  4. def thread_controller(request, result, thread_dict, func_name, callback, args, html_path):
  5. if result.get(func_name, "NA") == "NA":
  6. result[func_name] = {}
  7. thread_dict[func_name] = "stop"
  8. res = result[func_name]
  9. state = thread_dict[func_name]
  10. if state == "stop":
  11. test_thread = MyThread(thread_dict, func_name, callback, args, res)
  12. test_thread.start()
  13. test_thread.resume()
  14. elif state == "end":
  15. thread_dict[func_name] = "killed"
  16. return render(request, html_path, res)
  17. elif state == "killed":
  18. result[func_name] = {}
  19. thread_dict[func_name] = "stop"
  20. return HttpResponse("stop")
  21. return render(request, html_path, res)
  22. class MyThread(threading.Thread):
  23. def __init__(self, state_dict, thread_name, func_handle, p_list, g_dict):
  24. super(MyThread, self).__init__()
  25. self.g_dict = g_dict
  26. self.func_handle = func_handle
  27. self.p_list = p_list
  28. self.p_list.append(g_dict)
  29. self.paused = True # Start out paused.
  30. self.isend = False # thread is end.
  31. self.thread_name = thread_name
  32. self.state_dict = state_dict
  33. self.cond = threading.Condition()
  34. def run(self):
  35. while True:
  36. with self.cond: # 在该条件下操作
  37. self.resume()
  38. list(map(self.func_handle, *zip(self.p_list))) # 调用外部函数
  39. self.end()
  40. if self.paused:
  41. self.cond.wait() # Block execution until notified.
  42. if self.isend:
  43. break
  44. def resume(self): # 用来恢复/启动run
  45. with self.cond: # 在该条件下操作
  46. self.paused = False
  47. self.state_dict[self.thread_name] = "start"
  48. self.cond.notify() # Unblock self if waiting.
  49. def pause(self): # 用来暂停run
  50. with self.cond: # 在该条件下操作
  51. self.paused = True # Block self.
  52. self.state_dict[self.thread_name] = "paused"
  53. def end(self): # 用来结束run
  54. with self.cond: # 在该条件下操作
  55. self.isend = True # Block self.
  56. self.state_dict[self.thread_name] = "end"
  57. def kill(self): # 用来杀死线程
  58. with self.cond: # 在该条件下操作
  59. self._delete()
  60. self.state_dict[self.thread_name] = "killed"

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

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

  1. #python2直接返回列表
  2. map(func_handle, [1,2,3])
  3. def square(x) : # 计算平方数
  4. return x ** 2
  5. # 计算列表各个元素的平方
  6. map(square, [1,2,3,4,5])
  7. # [1, 4, 9, 16, 25]
  8. # 使用 lambda 匿名函数
  9. map(lambda x: x ** 2, [1, 2, 3, 4, 5])
  10. # [1, 4, 9, 16, 25]
  11. # 多参数函数
  12. map(lambda x, y: x + y, [1, 3, 5, 7, 9], [2, 4, 6, 8, 10])
  13. # [3, 7, 11, 15, 19]
  14. #python3返回的是迭代器
  15. def square(x) : # 计算平方数
  16. return x ** 2
  17. # 计算列表各个元素的平方,返回迭代器
  18. map(square, [1,2,3,4,5])
  19. # <map object at 0x100d3d550>
  20. # 使用 list() 转换为列表
  21. list(map(square, [1,2,3,4,5]))
  22. # [1, 4, 9, 16, 25]
  23. # 使用 lambda 匿名函数
  24. list(map(lambda x: x ** 2, [1, 2, 3, 4, 5]))
  25. # [1, 4, 9, 16, 25]
  26. # 多参数函数,使用zip函数进行捆绑
  27. def add(x, y) : # 计算和
  28. return x + y
  29. list(map(add, *zip([1, 3, 5, 7, 9])))
  30. # [4, 8, 12, 16]

最终效果

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

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

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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Gausst松鼠会/article/detail/224339
推荐阅读
相关标签
  

闽ICP备14008679号