欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Django实战:Django 3.0 +Redis 3.4 +Celery 4.4异步生成静态HTML文件(附源码)

程序员文章站 2022-07-02 10:40:09
今天小编利用Django 3.0 +Redis 3.4 +Celery 4.4开发了个小应用。应用不复杂,但知识点很多,非常适合新手练手。项目需求如下:创建两个页面,一个用于创建页面,一......

今天小编利用Django 3.0 +Redis 3.4 +Celery 4.4开发了个小应用。应用不复杂,但知识点很多,非常适合新手练手。项目需求如下:

  • 创建两个页面,一个用于创建页面,一个用于动态展示页面详情,并提供静态HMTL文件链接

  • 一个页面创建完成后,使用Celery异步执行生成静态HTML文件的任务

  • 使用redis作为Celery的Broker

  • 使用flower监控Celery异步任务执行情况

Django实战:Django 3.0 +Redis 3.4 +Celery 4.4异步生成静态HTML文件(附源码)

项目完成后演示见下面动画。本项目的GitHub源码地址在最后,请耐心阅读。

Django实战:Django 3.0 +Redis 3.4 +Celery 4.4异步生成静态HTML文件(附源码)

第零步:pip设置国内源

国内用户使用pip安装python包特别慢,这主要是应为国内连接国外网络不稳定。为加速python包的安装,首先将pip安装源设置为国内的镜像,比如阿里云提供的镜像。

linux系统修改 ~/.pip/pip.conf (没有就创建一个), 内容如下:

[global]index-url = https://mirrors.aliyun.com/pypi/simple/

windows系统直接在user目录中创建一个pip目录,如:C:\Users\xx\pip,新建文件pip.ini,内容如下:

[global]index-url = http://mirrors.aliyun.com/pypi/simple/

第一步:安装Django并创建项目myproject

使用pip命令安装Django.

 pip install django==3.0.4 # 安装Django,所用版本为3.0.4

使用django-admin startproject myproject创立一个名为myproject的项目

 django-admin startproject myproject

整个项目完整目录机构如下所示, 项目名为myproject, staticpage为app名。

Django实战:Django 3.0 +Redis 3.4 +Celery 4.4异步生成静态HTML文件(附源码)

第二步:安装redis和项目依赖的第三方包

项目中我们需要使用redis做Celery的中间人(Broker), 所以需要先安装redis数据库。redis网上教程很多,这里就简要带过了。

  • Windows下载地址:https://github.com/MSOpenTech/redis/releases

  • Linux下安装(Ubuntu系统):$ sudo apt-get install redis-server

本项目还需要安装如下依赖包,你可以使用pip命令逐一安装。

 pip install redis==3.4.1
 pip install celery==4.4.2
 pip install eventlet # celery 4.0+版本以后不支持在windows运行,还需额外安装eventlet库

你还可以myproject目录下新建requirements.txt加入所依赖的python包及版本,然后使用pip install -r requirements.txt命令安装所有依赖。本教程所使用的django, redis和celery均为最新版本。

 django==3.0.5
 redis==3.4.1
 celery==4.4.2  
 eventlet # for windows only

第三步:Celery基本配置

  1. 修改settings.py新增celery有关的配置。celery默认也是有自己的配置文件的,名为celeryconfig.py, 但由于管理多个配置文件很麻烦,我们把celery的配置参数也写在django的配置文件里。

 # 配置celery时区,默认时UTC。
 if USE_TZ:
     timezone = TIME_ZONE
 
 # celery配置redis作为broker。redis有16个数据库,编号0~15,这里使用第1个。
 broker_url = 'redis://127.0.0.1:6379/0'
 
 # 设置存储结果的后台
 result_backend = 'redis://127.0.0.1:6379/0'
 
 # 可接受的内容格式
 accept_content = ["json"]
 # 任务序列化数据格式
 task_serializer = "json"
 # 结果序列化数据格式
 result_serializer = "json"
 
 # 可选参数:给某个任务限流
 # task_annotations = {'tasks.my_task': {'rate_limit': '10/s'}}
 
 # 可选参数:给任务设置超时时间。超时立即中止worker
 # task_time_limit = 10 * 60
 
 # 可选参数:给任务设置软超时时间,超时抛出Exception
 # task_soft_time_limit = 10 * 60
 
 # 可选参数:如果使用django_celery_beat进行定时任务
 # beat_scheduler = "django_celery_beat.schedulers:DatabaseScheduler"
 
 # 更多选项见
 # https://docs.celeryproject.org/en/stable/userguide/configuration.html
  1. settings.py同级目录下新建celery.py,添加如下内容:

 # coding:utf-8
 from __future__ import absolute_import, unicode_literals
 import os
 from celery import Celery
 
 # 指定Django默认配置文件模块
 os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')
 
 # 为我们的项目myproject创建一个Celery实例。这里不指定broker容易出现错误。
 app = Celery('myproject', broker='redis://127.0.0.1:6379/0')
 
 # 这里指定从django的settings.py里读取celery配置
 app.config_from_object('django.conf:settings')
 
 # 自动从所有已注册的django app中加载任务
 app.autodiscover_tasks()
 
 # 用于测试的异步任务
 @app.task(bind=True)
 def debug_task(self):
     print('Request: {0!r}'.format(self.request))
 
 
  1. 打开settings.py同级目录下的__init__.py,添加如下内容, 确保项目启动时即加载Celery实例

 # coding:utf-8
 from __future__ import absolute_import, unicode_literals
 
 # 引入celery实例对象
 from .celery import app as celery_app
 __all__ = ('celery_app',)

网上很多django redis + celery的教程比较老了, 坑很多。比如新版原生的Celery已经支持Django了,不需要再借助什么django-celery和celery-with-redis这种第三方库了, 配置参数名也由大写变成了小写,无需再加CELERY前缀。另外当你通过app = Celery('myproject')创建Celery实例时如果不指定Broker,很容易出现[ERROR/MainProcess] consumer: Cannot connect to amqp://guest:**@127.0.0.1:5672//: [Errno 111] Connection refused这个错误。

第四步:启动redis,测试celery是否配置成功

在Django中编写和执行自己的异步任务前,一定要先测试redis和celery是否安装好并配置成功。

首先你要启动redis服务。windows进入redis所在目录,使用redis-server.exe启动redis。Linux下使用./redis-server redis.conf启动,也可修改redis.conf将daemonize设置为yes, 确保守护进程开启。

启动redis服务后,你要先运行python manage.py runserver命令启动Django服务器(无需创建任何app),然后再打开一个终端terminal窗口输入celery命令,启动worker。

 # Linux下测试
 Celery -A myproject worker -l info
 
 # Windows下测试
 Celery -A myproject worker -l info -P eventlet

如果你能看到[tasks]下所列异步任务清单如debug_task,以及最后一句celery@xxxx ready, 说明你的redis和celery都配置好了,可以开始正式工作了。

 
 -------------- celery@DESKTOP-H3IHAKQ v4.4.2 (cliffs)
 --- ***** -----
 -- ******* ---- Windows-10-10.0.18362-SP0 2020-04-24 22:02:38
 
 - *** --- * ---
 - ** ---------- [config]
 - ** ---------- .> app:         myproject:0x456d1f0
 - ** ---------- .> transport:   redis://127.0.0.1:6379/0
 - ** ---------- .> results:     redis://localhost:6379/0
 - *** --- * --- .> concurrency: 4 (eventlet)
   -- ******* ---- .> task events: OFF (enable -E to monitor tasks in this worker)
   --- ***** -----
    -------------- [queues]
                 .> celery           exchange=celery(direct) key=celery
 
 
 [tasks]
   . myproject.celery.debug_task
 
 [2020-04-24 22:02:38,484: INFO/MainProcess] Connected to redis://127.0.0.1:6379/0
 [2020-04-24 22:02:38,500: INFO/MainProcess] mingle: searching for neighbors
 [2020-04-24 22:02:39,544: INFO/MainProcess] mingle: all alone
 [2020-04-24 22:02:39,572: INFO/MainProcess] pidbox: Connected to redis://127.0.0.1:6379/0.
 [2020-04-24 22:02:39,578: WARNING/MainProcess] c:\users\missenka\pycharmprojects\django-static-html-generator\venv\lib\site-packages\celery\fixups\django.py:203: UserWarning: Using sett
 ings.DEBUG leads to a memory
             leak, never use this setting in production environments!
   leak, never use this setting in production environments!''')
 [2020-04-24 22:02:39,579: INFO/MainProcess] celery@DESKTOP-H3IHAKQ ready.

第五步:Django中创建新应用staticpage

cd进入myproject文件夹,使用python manage.py startapp staticpage创建一个名为staticpage的app。我们将创建一个简单的Page模型,并编写两个视图(对应两个URLs),一个用于添加页面,一个用于展示页面详情。staticpage目录下我们将要编辑或创建5个.py文件,分别是models.py, urls.py, views.py, forms.py和tasks.py,其中前4个都是标准的Django项目文件,内容如下所示。最后一个tasks.py用于存放我们自己编写的异步任务,稍后我会详细讲解。

 # staticpage/models.py
 from django.db import models
 import os
 from django.conf import settings
 
 class Page(models.Model):
     title = models.CharField(max_length=100, verbose_name="标题")
     body = models.TextField(verbose_name="正文")
 
     def __int__(self):
         return self.title
 
     # 静态文件URL地址,比如/media/html/page_8.html
     def get_static_page_url(self):
         return os.path.join(settings.MEDIA_URL, 'html', 'page_{}.html'.format(self.id))
 
 # staticpage/urls.py
 from django.urls import path, re_path
 from . import views
 
 
 urlpatterns = [
 
     # Create a page 创建页面
     path('', views.page_create, name='page_create'),
 
     # Page detail 展示页面详情。动态URL地址为/page/8/
     re_path(r'^page/(?P<pk>\d+)/$', views.page_detail, name='page_detail'),
 
     ]
 
 # staticpage/views.py
 from django.shortcuts import render, redirect, get_object_or_404
 from django.urls import reverse
 from .forms import PageForm
 from .models import Page
 from .tasks import generate_static_page
 
 def page_create(request):
     if request.method == 'POST':
         form = PageForm(request.POST)
         if form.is_valid():
             page = form.save()
             generate_static_page.delay(page.id, page.title, page.body)
             return redirect(reverse('page_detail', args=[str(page.pk)]))
     else:
         form = PageForm()
 
     return render(request, 'staticpage/base.html', {'form': form})
 
 
 def page_detail(request, pk):
     page = get_object_or_404(Page, id=pk)
     return render(request, 'staticpage/detail.html', {'page': page})
 
 # staticpage/forms.py
 from django import forms
 from .models import Page
 
 
 class PageForm(forms.ModelForm):
     class Meta:
         model = Page
         exclude = ()

page_create视图函数中你可以看到我们在一个page实例存到数据库后调用了generate_static_page函数在后台完成静态HTML页面的生成。如果我们不使用异步的化,我们要等静态HTML文件完全生成后才能跳转到页面详情页面, 这有可能要等好几秒。generate_static_page就是我们自定义的异步任务,代码如下所示。Celery可以自动发现每个Django app下的异步任务,不用担心。

 # staticpage/tasks.py
 
 import os, time
 from django.template.loader import render_to_string
 from django.conf import settings
 from celery import shared_task
 
 @shared_task
 def generate_static_page(page_id, page_title, page_body):
     # 模拟耗时任务,比如写入文件或发送邮件等操作。
     time.sleep(5)
 
     # 获取传递的参数
     page = {'title': page_title, 'body': page_body}
     context = {'page': page, }
 
     # 渲染模板,生成字符串
     content = render_to_string('staticpage/template.html', context)
 
     # 定义生成静态文件所属目录,位于media文件夹下名为html的子文件夹里。如目录不存在,则创建。
     directory = os.path.join(settings.MEDIA_ROOT, "html")
     if not os.path.exists(directory):
         os.makedirs(directory)
 
     # 拼接目标写入文件地址
     static_html_path = os.path.join(directory, 'page_{}.html'.format(page_id))
 
     # 将渲染过的字符串写入目标文件
     with open(static_html_path, 'w', encoding="utf-8") as f:
             f.write(content)
 

本例中我们生成的静态HTML文件位于media文件夹下的html子文件夹里,这样做有两个好处:

  • 与Django的静态文件存储规范保持一致:用户产生的静态文件都放在media文件下,网站本身所依赖的静态文件都放于static文件夹下。

  • 把所有产生的静态文件放在一个目录里与动态文件相分开,利于后续通过nginx部署。

本项目中还用到了3个模板,分别是base.html, detail.html和template.html。base.html和detail.html是没有任何样式的, 仅用于动态显示内容,template.html是用来生成静态文件的模板,是带样式的,这样你就可以很快区分动态页面和静态页面。由于我们后台生成静态文件至少需要5秒钟,我们在detail.html用了点javascript实现等5秒倒计时完成后显示生成的静态HTML文件地址。

3个模板均位于staticpage/templates/staticpage/文件夹下,代码如下所示:

 # base.html
 <!DOCTYPE html>
 <html lang="en">
 <head>
     <meta charset="UTF-8">
     <title>添加页面</title>
 </head>
 <body>
      <h2>添加页面</h2>
      <form name="myform"  method="POST" action=".">
          {% csrf_token %}
         {{ form.as_p }}
          <button type="submit">Submit</button>
      </form>
 </body>
 </html>
 
 # detail.html
 <!DOCTYPE html>
 <html lang="en">
 <head>
     <meta charset="UTF-8">
     <title>{{ page.title }}</title>
 </head>
 <body>
      <h2>{{ page.title }}</h2>
      <p>{{ page.body }}</p>
 
      <p>倒计时: <span id="Time">5</span></p>
      <p id="static_url" style="display:none;"> <small><a href='{{ page.get_static_page_url }}'>跳转到静态文件</a></small></p>
 
 
 <script>
  //使用匿名函数方法
  function countDown(){
  var time = document.getElementById("Time");
  var p = document.getElementById("static_url");
  //获取到id为time标签中的内容,现进行判断
  if(time.innerHTML == 0){
  //等于0时, 显示静态HTML文件URL
  p.style.display = "block";
  }else{
  time.innerHTML = time.innerHTML-1;
  }
  }
  //1000毫秒调用一次
  window.setInterval("countDown()",1000);
  </script>
 
 </body>
 </html>
 
 # template.html 生成静态文件模板
 {% load static %}
 <html lang="en">
 <head>
 <title>{% block title %}Django文档管理{% endblock %} </title>
 <meta charset="utf-8">
 <meta name="viewport" content="width=device-width, initial-scale=1">
 <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
 </head>
 
 <body>
 <nav class="navbar navbar-inverse navbar-static-top bs-docs-nav">
 
   <div class="container">
     <div class="navbar-header">
         <button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#myNavbar">
           <span class="icon-bar"></span>
           <span class="icon-bar"></span>
           <span class="icon-bar"></span>                        
       </button>
         <a class="navbar-brand" href="#"><strong>Django + Celery + Redis异步生成静态文件</strong></a>
      </div>
 
       <div class="collapse navbar-collapse" id="myNavbar">
        <ul class="nav navbar-nav navbar-right">
  {% if request.user.is_authenticated %}
 
           <li class="dropdown">
               <a class="dropdown-toggle btn-green" data-toggle="dropdown" href="#"><span class="glyphicon glyphicon-user"></span>  {{ request.user.username }} <span class="caret"></span></a>
             <ul class="dropdown-menu">
               <li><a  href="#">My Account</a></li>
               <li><a  href="#">Logout</a></li>
             </ul>
           </li>  
          {% else %}  
             <li class="dropdown"><a class="dropdown-toggle btn-green" href="#"><span class="glyphicon glyphicon-user"></span> Sign Up</a></li>
 <li class="dropdown"><a class="dropdown-toggle" href="#" ><span class="glyphicon glyphicon-log-in"></span> Login</a></li>
  {% endif %}
        </ul>
 
     </div>
 
   </div>
 </nav>    
 
  <!-- Page content of course! -->
 <main id="p1" class="container-fluid">
  
 <div class="container">
     <div class="row">
      <div class="col-sm-3  col-hide">
          <ul>
              <li> <a href="{% url 'page_create' %}">添加页面</a> </li>
          </ul>
      </div>
 
      <div class="col-sm-9">
           <h3>{{ page.title }}</h3>
          {{ page.body }}
      </div>
 </div>
 
 </div>
 </main>

 <script src="https://code.jquery.com/jquery-3.3.1.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script>
 <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
 
 </body>
 </html>

第六步:在Django中注册app并添加app的URLConf

 # 修改myproject/settings.py,添加如下内容
 INSTALLED_APPS = [
     'django.contrib.admin',
     'django.contrib.auth',
     'django.contrib.contenttypes',
     'django.contrib.sessions',
     'django.contrib.messages',
     'django.contrib.staticfiles',
     'staticpage',
 ]
 
 # 设置STATIC_URL和STATIC_ROOT
 STATIC_URL = '/static/'
 STATIC_ROOT = os.path.join(BASE_DIR, 'static')
 
 # 设置MEDIA_ROOT和MEDIA_URL
 MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
 MEDIA_URL = '/media/'
 
 # 修改myproject/urls.py,添加如下内容
 from django.contrib import admin
 from django.urls import path, include
 
 from django.conf import settings
 from django.conf.urls.static import static
 
 
 urlpatterns = [
     path('admin/', admin.site.urls),
     path('', include("staticpage.urls")),
 ]
 
 # Django自带服务器默认不支持静态文件,需加入这两行。
 if settings.DEBUG:
     urlpatterns = urlpatterns + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
     urlpatterns = urlpatterns + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)

第七步:启动Django服务器和并安装Celery异步任务监控工具

如果一切顺利,连续使用如下命令, 即可启动Django测试服务器。打开http://127.0.0.1:8000/即可看到我们项目开头的动画啦。注意:请确保redis和celery已同时开启。

 python manage.py makemigrations
 python manage.py migrate
 python manage.py runserver

如果你要监控异步任务的运行状态(比如是否成功,是否有返回结果), 还可以安装flower这个Celery监控工具。

 pip install flower

安装好后,你有如下两种方式启动服务器。启动服务器后,打开http://localhost:5555即可查看监控情况。

 # 从terminal终端启动, proj为项目名
 $ flower -A proj --port=5555  
 # 从celery启动
 $ celery flower -A proj --address=127.0.0.1 --port=5555

监控异步任务还是很重要的,强烈建议安装flower。比如下图中有些任务就失败了。

Django实战:Django 3.0 +Redis 3.4 +Celery 4.4异步生成静态HTML文件(附源码)

源码地址

https://github.com/shiyunbo/django-static-page-generator-celery-redis

大江狗

2020.4.25

更多阅读

Django中使用Celery执行异步和定时任务的注意事项

Django-filter教程详解: 从安装使用到高阶美化分页-大江狗精品

Django实战教程: 开发餐厅在线点评网站(1)

更多Python Web开发与Django实战内容,欢迎关注我们微信公众号并加星标。

Django实战:Django 3.0 +Redis 3.4 +Celery 4.4异步生成静态HTML文件(附源码)

本文地址:https://blog.csdn.net/weixin_42134789/article/details/105760392