はじめに
私が個人Webサービスを作っている上での作業メモ。技術書典用のネタの下書きでもある。
フロントはVue.jsです。そのうちNuxtの方に移行したいですが・・・関係ないので省略します。
お問い合わせフォームって?
で、これです。これの事を言ってます。割といろんなサイトで見かけますよね。
このお問い合わせフォームからメール配信サービスであるSendGridを経由して、このフォームの内容を管理人にメールで発信しよう、というのがこの記事の主題です。フロント側の方のコードは特に書く必要が無いと考えていますので、今回の記事では省略いたします。
また、SPFやDKIM等の送信ドメイン認証周りの設定方法も省略とさせてください。書いてる人もその辺り勉強中なので・・・。
##今回したい事の図
雑な図で恐縮です。
流れとしては
1.フロント側でDjangoで設定したエンドポイントに向かってフォームデータをPost
2.Django側はPostされたデータをシリアライザで整形、及び検証した後にDBにInsertして、SendGrid側にパラメータ指定でPostする
3.無事管理人に届く
という感じになります。サーバーの責務多いです。
##SendGridを使う
###Sendgridって?
クラウド型メール配信サービスです。SMTPサーバーを立てる必要もなくAPIベースでのメール送信が可能なので、ITインフラ赤ちゃんでも簡単に自プロダクトにメール配信機能を盛り込む事ができます。料金体系も安くて個人開発者もにっこり。
###利用について
SendGridはAzureから経由して使うパターン、Herokuのアドオンから経由して使うパターン、自分で申請して登録するパターン等々・・・色々ありますよね。
余談ですがFreeプランの利用枠や料金体系が結構パターン別に違ったりするので、個人開発者の方々は留意した方がいいかもしれません。本当に結構違いますよ。
SendGridの契約先による、機能面やサポート面での違い
https://sendgrid.kke.co.jp/blog/?p=10084
###DynamicTemplatesについて
簡単に言うと、SendGrid側でHTMLメールのテンプレートを用意してくれる機能です。そのまんまですね。
バージョニング機能、パラメータ設定機能などなど・・便利な機能盛りだくさんです。後段で説明します。
個人的には開発者のコーディング品質向上の観点上、非常に重要な機能だと考えています。それも後で説明しますね。
##DjangoRestFrameWorkについて
流石に省略します。RestAPIマンです。
###コード群
シリアライザ部分は特殊な部分が特にないので省略します。
####モデル
フォームのオプション部分はIntEnumで対応させてます。また、emailフィールドではユーザーの主キーを外部参照しています。
class InfromOption(IntEnum):
UIのクソさについて = 1
機能追加要望 = 2
不具合全般 = 3
規約等 = 4
その他 = 5
@classmethod
def pick_v(cls):
return [(n.name,n.value) for n in cls]
@classmethod
def get_enum_name(cls,key): return cls(key).name.title()
class Inform(models.Model):
class Meta:
db_table='inform'
inform_id =models.AutoField(primary_key=True,null=False)
inform_option=models.IntegerField(
verbose_name='オプション',
choices=InfromOption.pick_v(),
null=True
)
inform_rating=models.CharField(verbose_name='評価',max_length=50,null=True)
inform_subtitle=models.CharField(verbose_name='件名',max_length=50,null=True)
inform_body=models.TextField(verbose_name='内容',blank=True,null=True,max_length=1000)
email =models.ForeignKey(
get_user_model(),
related_name='informuser',
on_delete=models.PROTECT,
null=True
)
####ビュー
匿名ユーザーは空にする、ぐらいですかね。
class InformApiView(views.APIView):
def post(self,request,*args,**kwargs):
serializer = InformSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
#anonymous判定
req_user = request.user if request.user is not None else None
serializer.save(email = req_user)
posttingObj =serializer.validated_data
posttingObj['email'] = req_user.email if req_user is not None else None
UnitWorkService.informPostMail(posttingObj)
return Response(status.HTTP_201_CREATED)
####サービスロジック
型チェック等マジで何もやってないですが動きます。
自分でリクエストパラメータ用のpersonalizationsオブジェクト整形処理したりするの結構めんどくさいです。
ので、サードパーティのSendGridのpython用ライブラリが折角用意されていますので、ありがたく使わせてもらいましょう(´・ω・`)
https://github.com/sendgrid/sendgrid-python
環境変数等はよしなに設定するとよいでしょう。
import environ
from sendgrid import SendGridAPIClient
from sendgrid.helpers.mail import (
Mail, From, To, TemplateId ,DynamicTemplateData
)
class UnitWorkService:
"""
他にはConohaオブジェクトストレージに画像投げたりパスワードの暗号化するクラスへの橋渡し用のクラスです。
主題に関係ない部分は省略します。
"""
@staticmethod
def informPostMail(inform_data):
InformPosttingMail.sendMail(
info_option= InfromOption.get_enum_name(inform_data['inform_option']) if 'inform_option' in inform_data else "",
info_rating=inform_data['inform_rating'] if 'inform_rating' in inform_data else "",
info_subject=inform_data['inform_subtitle'] if 'inform_subtitle' in inform_data else "",
info_body=inform_data['inform_body'] if 'inform_body' in inform_data else "",
info_user=inform_data['email'] if 'email' in inform_data else ""
)
env=environ.Env()
class BaseSendMail(object):
'''
Sendgridの基底クラス
'''
def __init__(self):
self.__message = Mail()
@property
def message(self):
return self.__message
@message.setter
def message(self,value):
name, data ,*_ = value
setattr (self.__message, name, data)
@staticmethod
def lissttingParam(cls,**param):
"""
複数入れる場合に使う
"""
return [cls(v,*_) for v,*_ in param['data']]
def posttingMail(self):
try:
sg = SendGridAPIClient(env('SENDGRID_API_KEY'))
response = sg.send(self.message)
except Exception as e:
print(e)
finally:
return response.status_code
class GlobalPosttingMail(BaseSendMail):
def __init__(self):
super().__init__()
self.message = 'from_email',From('hogehogehoge','ほげほげほげ')
class InformPosttingMail(GlobalPosttingMail):
def __init__(self):
super().__init__()
self.message = 'template_id',TemplateId(template_id='hogehogehogehoegheoghoeghoge')
self.message = 'to', self.lissttingParam(To,data=[(env('APP_MANAGE_EMAIL'),'ほげほげほげ')])
@property
def dyna_param(self):
'''
dynamic_template定義のパラメータ
'''
return self.__dyna_param
@dyna_param.setter
def dyna_param(self,value):
self.__dyna_param = {
'info_option':value['info_option'],
'info_rating':value['info_rating'],
'info_subject':value['info_subject'],
'info_body':value['info_body'],
'info_user':value['info_user']
}
@classmethod
def sendMail(cls,**param):
infoS = cls()
infoS.dyna_param = param
infoS.message = 'dynamic_template_data', DynamicTemplateData(dynamic_template_data= infoS.dyna_param)
return infoS.posttingMail()
###SendGridのDynamicTemplatesを設定する
上記のコードで以下のようなパラメータを成型してますが、このデータ群はDynamicTemplatesのレンダリングに利用します。プレースホルダーにも適用されます。
@property
def dyna_param(self):
'''
dynamic_template定義のパラメータ
'''
return self.__dyna_param
@dyna_param.setter
def dyna_param(self,value):
self.__dyna_param = {
'info_option':value['info_option'],
'info_rating':value['info_rating'],
'info_subject':value['info_subject'],
'info_body':value['info_body'],
'info_user':value['info_user']
}
ご覧の通りマスタッシュ記法で指定するパラメータを書けばよいです。
###動作検証
####フロントでフォームで問い合わせを発射する
はい、届いてますね~
最後の方説明が超雑になりましたが、何か聞きたい事等があればお知らせください。
よろしくお願いいたします。