iT邦幫忙

2022 iThome 鐵人賽

DAY 22
0

Day 22 郵差來送信,使用 Django 寄送郵件

郵差與信箱是每個人生活中不可或缺的一部分,而電子信箱與寄信服務也是現今網站的重要組成。今天要來使用 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 模型

頁面展示:

https://ithelp.ithome.com.tw/upload/images/20221004/20151656hMDtLWTKN8.png

建立 URLS

在 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 下方新增了訂閱電子報的連結

https://ithelp.ithome.com.tw/upload/images/20221004/20151656SObK1ulIB6.png

訂閱電子報頁面

這裡的表單可以送出訂閱者的 email,點選訂閱進行送出

https://ithelp.ithome.com.tw/upload/images/20221004/20151656d8HOorg8Ca.png

若 Email 格式錯誤的話會跳出提示

https://ithelp.ithome.com.tw/upload/images/20221004/20151656iCUI4WInCe.png

送出後導轉到感謝訂閱頁面

https://ithelp.ithome.com.tw/upload/images/20221004/20151656OpRAzqRxK4.png

在管理介面就可以看到新增的信箱

https://ithelp.ithome.com.tw/upload/images/20221004/20151656QExtvVBcEf.png

訂閱功能到此告一段落了!

.

.

.

什麼?你說郵差哪裡去了?

使用 Docker 運行 mail 服務

如果在 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

Django 設定 mail server

在 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"

在送出訂閱電子報的表單後即可收到信件囉!

https://ithelp.ithome.com.tw/upload/images/20221004/20151656ay2RsBK8dj.png

epaper 應用程式目錄結構

.
├── 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 多租戶下的任務排程』


上一篇
Day 21 國際化租屋,Django 多租戶多語系
下一篇
Day 23 使命必達!Django 多租戶下的任務排程
系列文
全能住宅改造王,Django 多租戶架構的應用 —— 實作一個電商網站30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言