3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

DjangoRestFrameWork + SendGridでお手軽お問い合わせフォームを爆速で作る

Last updated at Posted at 2020-02-25

はじめに

私が個人Webサービスを作っている上での作業メモ。技術書典用のネタの下書きでもある。
フロントはVue.jsです。そのうちNuxtの方に移行したいですが・・・関係ないので省略します。

お問い合わせフォームって?

まずこんな画面があって・・・
image.png

で、これです。これの事を言ってます。割といろんなサイトで見かけますよね。
image.png

このお問い合わせフォームからメール配信サービスであるSendGridを経由して、このフォームの内容を管理人にメールで発信しよう、というのがこの記事の主題です。フロント側の方のコードは特に書く必要が無いと考えていますので、今回の記事では省略いたします。

また、SPFやDKIM等の送信ドメイン認証周りの設定方法も省略とさせてください。書いてる人もその辺り勉強中なので・・・。

##今回したい事の図
雑な図で恐縮です。
image.png
流れとしては
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メールのテンプレートを用意してくれる機能です。そのまんまですね。
image.png
バージョニング機能、パラメータ設定機能などなど・・便利な機能盛りだくさんです。後段で説明します。

個人的には開発者のコーディング品質向上の観点上、非常に重要な機能だと考えています。それも後で説明しますね。

##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']
            }

ご覧の通りマスタッシュ記法で指定するパラメータを書けばよいです。
image.png

###動作検証
####フロントでフォームで問い合わせを発射する
image.png
image.png
はい、届いてますね~
image.png

最後の方説明が超雑になりましたが、何か聞きたい事等があればお知らせください。

よろしくお願いいたします。

3
1
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?