郵差與信箱是每個人生活中不可或缺的一部分,而電子信箱與寄信服務也是現今網站的重要組成。今天要來使用 Django 的寄信功能來實作電子報訂閱服務,將從頭建立一個 APP 再次走一遍 Django 的工作流程,帶大家再次複習前面章節提到的內容,最後再加入寄信功能,馬上就來實作吧!
首先為電子報服務建立一個應用程式 epaper
docker exec --workdir /opt/app/web example_tenant_web \
python3.10 manage.py startapp epaper
註冊為租戶獨立應用程式
# main/settings.py
TENANT_APPS = (
# ...
'epaper',
)
要知道多少人訂閱電子報需要有一個資料表來進行記錄所有訂閱者的 Email,
在 models.py 建立一個 EPaperEmail 模型:
# epaper/models.py
from django.db import models
# Create your models here.
class EPaperEmail(models.Model):
'''
電子報訂閱信箱
'''
email = models.EmailField('E-mail', max_length=255)
class Meta:
verbose_name = '電子報訂閱信箱'
verbose_name_plural = '電子報訂閱信箱'
def __str__(self):
return f'{self.email}'
建立資料庫遷移檔案
docker exec --workdir /opt/app/web example_tenant_web \
python3.10 manage.py makemigrations
...
Migrations for 'epaper':
epaper/migrations/0001_initial.py
- Create model EPaperEmail
執行資料庫遷移
docker exec --workdir /opt/app/web example_tenant_web \
python3.10 manage.py migrate
...
=== Starting migration
Operations to perform:
Apply all migrations: admin, auth, contenttypes, core, customers, epaper, products, sessions, sites
Running migrations:
Applying epaper.0001_initial...
OK
=== Starting migration
Operations to perform:
Apply all migrations: admin, auth, contenttypes, core, customers, epaper, products, sessions, sites
Running migrations:
Applying epaper.0001_initial...
OK
=== Starting migration
Operations to perform:
Apply all migrations: admin, auth, contenttypes, core, customers, epaper, products, sessions, sites
Running migrations:
Applying epaper.0001_initial...
OK
在 epaper 應用程式目錄下建立 admin.py 檔案,
匯入 EPaperEmail 模型,並進行註冊。
# epaper/admin.py
from django.contrib import admin
# Register your models here.
from epaper.models import EPaperEmail
admin.site.register(EPaperEmail) # 註冊 EPaperEmail 模型
頁面展示:
在 epaper 應用程式目錄下建立 urls.py 檔案,
epaper 對應至 EPaperView 訂閱電子報視圖,
thanks 對應至 EPaperThanksView 感謝訂閱視圖。
# epaper/urls.py
from django.urls import path
from . import views
urlpatterns = [
path('epaper/', views.EPaperView.as_view(), name='epaper'),
path('thanks/', views.EPaperThanksView.as_view(), name='thanks'),
]
在 main 應用程式下的 URLS 進行匯入
# main/urls.py
# ...
urlpatterns = [
# ...
path('epaper/', include(('epaper.urls', 'epaper'), namespace='epaper')),
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
定義 EPaperView 類,為訂閱電子報表單視圖,繼承 FormView 來進行表單處理。form_class 為指定使用的表單(後續將會進行定義)
success_url 為表單通過後將要導轉的連結,這裡將會導轉到感謝訂閱頁面
form_valid 為表單通過後會執行的函數,通過後會進行是否已經訂閱的檢查,若尚未訂閱就新增一筆資料至電子報訂閱信箱
# epaper/views.py
from django.views.generic import FormView, TemplateView
class EPaperView(FormView):
template_name = "epaper/epaper.html"
form_class = EPaperForm
success_url = '/epaper/thanks/'
def form_valid(self, form):
self.object = form.save(commit=False)
if not EPaperEmail.objects.filter(email=self.object.email):
self.object.save()
return super().form_valid(form)
定義 EPaperThanksView 類,為感謝訂閱視圖,用來顯示感謝頁面。
# epaper/views.py
# ...
class EPaperThanksView(TemplateView):
template_name = "epaper/thanks.html"
在 epaper 應用程式下建立 forms.py 檔案,用來寫 Django 表單,繼承 ModelForm 來透過 EPaperEmail 模型建立表單。
# epaper/forms.py
from django import forms
from epaper.models import EPaperEmail
class EPaperForm(forms.ModelForm):
class Meta:
model = EPaperEmail
fields = ('email',)
在 epaper 目錄底下建立 template 目錄,在其中再建立 epaper 目錄。
這裡的模板會直接使用多語系的語法,要記得在翻譯對執照表中進行設定。
建立 epaper.html 訂閱電子報頁面
<!-- epaper/templates/epaper/epaper.html -->
{% extends 'base.html'%}
{% load i18n %}
{% block content %}
<div class="container">
<div class="row">
<div class="col-auto">
<form method="post">{% csrf_token %}
{{ form.as_p }}
<input type="submit" class="btn btn-primary" value="{% translate 'subscription' %}">
</form>
</div>
</div>
</div>
{% endblock content %}
建立 thanks.html 感謝訂閱頁面
<!-- epaper/templates/epaper/thanks.html -->
{% extends 'base.html'%}
{% load i18n %}
{% block content %}
<div class="container">
<div class="row">
<div class="col-auto">
{% translate 'Thank you for subscribing to the newsletter.' %}
</div>
</div>
</div>
{% endblock content %}
在基底頁面加入訂閱電子報連結,也同時在 sitename 加入回到首頁的連結。
<!-- products/templates/base.html -->
<!-- Start Top Header Bar -->
<section class="top-header">
<div class="container">
<div class="row">
<div class="col-md-4 col-xs-12 col-sm-4">
<img style="height: 12em;width: 12em" src="{% get_media_prefix %}{{ get_setting.logo }}">
<div class="contact-number">
<i class="tf-ion-ios-telephone"></i>
<span>{{ get_setting.phone }}</span>
<!-- 訂閱電子報 -->
<span><a href="{% url 'epaper:epaper' %}">{% translate 'Subscribe to the newsletter' %}</a></span>
</div>
</div>
<div class="col-md-4 col-xs-12 col-sm-4">
<!-- Site Name -->
<div class="name text-center">
<!-- sitename 回首頁 -->
<a href="{% url 'products:home' %}">
<!-- replace name here -->
<h1>{{ get_setting.sitename }}</h1>
</a>
</div>
</div>
首頁
在左上方 Logo 下方新增了訂閱電子報的連結
訂閱電子報頁面
這裡的表單可以送出訂閱者的 email,點選訂閱進行送出
若 Email 格式錯誤的話會跳出提示
送出後導轉到感謝訂閱頁面
在管理介面就可以看到新增的信箱
訂閱功能到此告一段落了!
.
.
.
什麼?你說郵差哪裡去了?
如果在 Linux 機器上要使用發信功能需要安裝 mail 服務,而這裡我們透過 Docker 來運行一個 mailserver。
停止 docker-compose 服務
cd ~/example_tenant
docker-compose down -v
在 docker-compose.yml 新增一個 mailserver service
# docker-compose.yml
# ...
version: "3.9"
services:
db:
# ...
web:
# ...
mailserver:
image: boky/postfix
container_name: ${IMAGE_NAME}_mailserver
restart: always
environment:
ALLOW_EMPTY_SENDER_DOMAINS: 'true'
POSTFIX_myhostname: 'postfix'
啟動 docker-compose 服務
cd ~/example_tenant
docker-compose up -d
在 setting.py 新增 Email 設定,這裡使用 Docker 運行的服務
# main/settings.py
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'mailserver'
EMAIL_HOST_USER = ''
EMAIL_HOST_PASSWORD = ''
EMAIL_PORT = 587
當有用戶訂閱後,我們將要發送一個訂閱成功的通知信。
這裡會使用到 Django 的 EmailMultiAlternatives 來寄送信件,
如要使用 html 內容的話可以使用 attach_alternative
函數
其中 EmailMultiAlternatives 包含四個參數:
subject 為信件主旨
text_content 為文字訊息
from_email 為寄件者
to 為收件者(可多選)
以下為範例:
from django.core.mail import EmailMultiAlternatives
subject, from_email, to = 'test', 'from@example.com', 'to@example.com'
text_content = 'Test message.'
html_content = '<p>Test message.</p>'
msg = EmailMultiAlternatives(subject, text_content, from_email, [to])
msg.attach_alternative(html_content, "text/html")
msg.send()
我們要在 EPaperView 視圖加入訂閱成功通知信,
定義一個 send_mail 函數來寄送通知信,並且在表單成功送出後觸發。
# epaper/views.py
from django.core.mail import EmailMultiAlternatives
from django.db import connection
from django.utils.translation import get_language
from django.views.generic import FormView, TemplateView
from core.models import Setting
from epaper.models import EPaperEmail
from epaper.forms import EPaperForm
class EPaperView(FormView):
template_name = "epaper/epaper.html"
form_class = EPaperForm
success_url = '/epaper/thanks/'
def form_valid(self, form):
self.object = form.save(commit=False)
if not EPaperEmail.objects.filter(email=self.object.email):
self.object.save()
to = [self.object.email]
self.send_mail(to)
return super().form_valid(form)
def send_mail(self, to):
language = get_language()
setting = Setting.objects.get(id=language)
subject =f'您已成功訂閱 {setting.sitename} 電子報'
text_content = f'您已成功訂閱 {setting.sitename} 電子報'
html_content = f'<p>您已成功訂閱 {setting.sitename} 電子報</p>'
msg = EmailMultiAlternatives(subject, text_content, None, to)
msg.attach_alternative(html_content, "text/html")
msg.send()
class EPaperThanksView(TemplateView):
template_name = "epaper/thanks.html"
在送出訂閱電子報的表單後即可收到信件囉!
.
├── admin.py
├── apps.py
├── forms.py
├── __init__.py
├── migrations
│ ├── 0001_initial.py
│ └── __init__.py
├── models.py
├── tasks.py
├── templates
│ └── epaper
│ ├── epaper.html
│ └── thanks.html
├── tests.py
├── urls.py
└── views.py
今天完成了訂閱電子報與發信功能,接下來將使用任務排程的方式定期將新商品的清單發送給用戶,下一章節『使命必達!Django 多租戶下的任務排程』