透過程式定期執行任務不僅可以減少許多繁瑣的每日工作,也能避免錯過最佳的執行時機,今天就來使用 Django 的任務排程定期將新商品的清單發送給用戶吧!
今天的主角 —— Django Q ,為使用 python 原生多進程模組開發的 Django 任務佇列與排程程式,不僅可以異步執行任務,也可以設定時間來定期執行。
Django Q 的文件:https://django-q.readthedocs.io/en/latest/
然而 Django 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 需要透過 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/
新增一筆資料
Func :為排程程式路徑
Args :為要傳遞進入的參數
Schedule Type:為排程的週期,這裡設定為每月
Repeats:為重複次數,-1 為不限制
Next Run:為下次執行時間
儲存後可以查看執行狀態
重新整理頁面後再次查看狀態為已完成
查看信箱
成功收到了!
完成了排程寄信功能,將下來將準備開發商品搜尋功能,在這之前我們要介紹要如何使用搜尋引擎,明日『裝上引擎,Django 的移動城堡』。