iT邦幫忙

2022 iThome 鐵人賽

DAY 23
0

Day 23 使命必達!Django 多租戶下的任務排程

透過程式定期執行任務不僅可以減少許多繁瑣的每日工作,也能避免錯過最佳的執行時機,今天就來使用 Django 的任務排程定期將新商品的清單發送給用戶吧!

Django Q

今天的主角 —— Django Q ,為使用 python 原生多進程模組開發的 Django 任務佇列與排程程式,不僅可以異步執行任務,也可以設定時間來定期執行。

Django Q 的文件:https://django-q.readthedocs.io/en/latest/

然而 Django Q 並不支援多租戶架構,我們還得再加上另一個套件。

Django Tenants Q

Django Tenants Q 是基於 Django Q 的的自定義套件,適用於 Django 多租戶架構,使用它就能將 Django Q 設定為基於每個租戶的模式來運作。

Django Tenants Q 的文件:https://github.com/chaitanyadevle/django_tenants_q

環境設定

TENANT_APPS 加入 django_q 與 django_tenants_q

# main/settings.py

# ...

TENANT_APPS = (
    # ...
    'django_q',
    'django_tenants_q',
    # ...

加入 Django Q 設定,這裡的 redis 服務將會透過 Docker 運行

# main/settings.py

# ...

Q_CLUSTER = {
    'name': 'example_tenant',
    'workers': 8,
    'recycle': 500,
    'timeout': 60,
    'compress': True,
    'save_limit': 250,
    'queue_limit': 500,
    'cpu_affinity': 1,
    'label': 'Django Q',
    'redis': {
        'host': 'redis',
        'port': 6379,
        'db': 0, }
}

Redis 為基於記憶體、分散式、可選永續性的鍵值對儲存資料庫服務,我們將透過 Docker 使用它來處理 Django Q 的任務,

在 docker-compose.yml 加入 Redis 服務


version: "3.9"

services:

  # ...

  redis:
    image: redis:latest
    container_name: ${IMAGE_NAME}_redis
    restart: always

啟動 Django Q

要使用 Django Q 需要透過 manage.py 來運行,範例指令如下:

python3.10 manage.py mscluster

在容器化的環境下,我們也可以為 Django Q 運行一個容器。

這裡將使用 docker-entrypoint.sh 來執行指令,我們要在專案根目錄建立一個 django_q 目錄,並在其中建立 docker-entrypoint.sh 檔案

cd ~/example_tenant
mkdir django_q
touch docker-entrypoint.sh 

docker-entrypoint.sh 寫入以下內容:

#!/bin/bash

cd /opt/app/web/

# Start mscluster
echo "Starting mscluster"
python3.10 manage.py mscluster

exec "$@"

要在 docker-compose 使用需要調整權限

cd ~/example_tenant/django_q
chmod 755 docker-entrypoint.sh 

在 docker-compose.yml 中加入 Django Q 服務,這裡的 image 會與 web 服務共用,設定如下:

version: "3.9"

services:

  # ...

  qcluster: 
    image: example_tenant:latest
    container_name: ${IMAGE_NAME}_qcluster
    restart: always
    entrypoint: /opt/app/django_q/docker-entrypoint.sh
    volumes:
      - .:/opt/app
    depends_on:
      - db
      - redis

重新啟動 docker-compose

cd ~/example_tenant
docker-compose down -v
docker-compose up -d

運行成功!

建立排程程式

在 epaper 中建立 tasks.py 用於排程程式,並在其中定義 task_epaper_send_mail函數。

透過排程執行程式時並沒有透過 request 請求來取得 schema 判斷租戶,因此這裡要傳遞一個 schema 參數。

接著建立 context 字典裡將以下變數透過 render_to_string 函數傳遞給模板

host :為透過 schema 參數去查詢該 schema 的 domain 。用來做為商品連結的伺服器位置。

products:使用 ORM 語法進行篩選(datetime 會加上 timezone)找出近一個月的新商品。

sitename:使用 Setting 模型取得 sitename。

最後將渲染後的模板傳遞至 EmailMultiAlternatives 的 html_content 並寄送郵件。

import datetime
import pytz
from django.core.mail import EmailMultiAlternatives
from django.db import connection
from django.template.loader import render_to_string
from django.utils.translation import get_language

from products.models import Product
from epaper.models import EPaperEmail
from core.models import Setting
from customers.models import Client

def task_epaper_send_mail(schema):
    connection.set_schema(schema)
    client = Client.objects.get(schema_name=schema)
    domain = client.domains.all().first().domain
    tz = pytz.timezone('Asia/Taipei')
    today = tz.localize(datetime.datetime.now())
    new_products = Product.objects.filter(created__gte=today)
    language =  get_language()
    setting =  Setting.objects.get(id=language)
    context = {
        'host': f'http://{domain}:8000',
        'sitename': setting.sitename,
        'products': new_products
    }
    bcc = EPaperEmail.objects.all()
    subject ='新品快訊'
    text_content = render_to_string('epaper/new_products_email.txt', context)
    html_content = render_to_string('epaper/new_products_email.html', context)
    msg = EmailMultiAlternatives(subject, text_content, None, to=[], bcc=bcc)
    msg.attach_alternative(html_content, "text/html")
    msg.send()
    return {'status': 'OK'}

在 epaper 應用程式的 templates/epaper 目錄下建立 txt 與 html 檔案

new_products_email.txt:

<!-- epaper/templates/epaper/new_products_email.txt-->

{{ sitename }} 新品快訊
{% for product in products %}
  {{ product.name }} {{ host }}{% url 'products:detail' product.id %}
{% endfor %}

new_products_email.html:

<!-- epaper/templates/epaper/new_products_email.html -->
<div>
  <h3>{{ sitename }} 新品快訊</h3>
  <table>
    <thead>
      <tr>
        <th colspan="2">商品清單</th>
      </tr>
    </thead>
    <tbody>
      {% for product in products %}
      <tr>
        <td>
          <a href="{{ host }}{% url 'products:detail' product.id %}" target="_blank">
            {{ product.name }}
          </a>
        </td>
      </tr>
      {% endfor %}
    </tbody>
  </table>
</div>

設定排程任務

進入 Django Q 的排程管理介面 http://example01.localhost:8000/admin/django_q/schedule/

https://ithelp.ithome.com.tw/upload/images/20221005/20151656BgZQcRZw2L.png

新增一筆資料

Func :為排程程式路徑

Args :為要傳遞進入的參數

https://ithelp.ithome.com.tw/upload/images/20221005/20151656xMoiG8G9iS.png

Schedule Type:為排程的週期,這裡設定為每月

Repeats:為重複次數,-1 為不限制

Next Run:為下次執行時間

https://ithelp.ithome.com.tw/upload/images/20221005/201516561lNRYt9G9Y.png

儲存後可以查看執行狀態

https://ithelp.ithome.com.tw/upload/images/20221005/201516564lBNh2iCY6.png

重新整理頁面後再次查看狀態為已完成

https://ithelp.ithome.com.tw/upload/images/20221005/201516565uYnzB89or.png

查看信箱

https://ithelp.ithome.com.tw/upload/images/20221005/20151656ETwnan5KON.png

成功收到了!

完成了排程寄信功能,將下來將準備開發商品搜尋功能,在這之前我們要介紹要如何使用搜尋引擎,明日『裝上引擎,Django 的移動城堡』。


上一篇
Day 22 郵差來送信,使用 Django 寄送郵件
下一篇
Day 24 裝上引擎,Django 的移動城堡
系列文
全能住宅改造王,Django 多租戶架構的應用 —— 實作一個電商網站30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言