ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • django mailgun.com 연동(interlocking)
    Project using python/Cloning Airbnb 2021. 3. 4. 08:36

    mailgun.com

    개요

      mailgun.com은 메일을 보내주는 API를 제공하는 사이트다. 예를 들어 사용자가 회원가입을 했을 때, 회원 가입 알림 메일을 사용자에게 보내고, 사용자는 이를 확인한다. do-not-reply가 이에 해당한다.

      START SENDING을 통해서 회원가입을 하거나 로그인을 한다. 회원가입을 하면 첫 3개월간 한 달에 5천 개의 메일이 무료다.

    설정에 필요한 변수 값 얻기

      우리는 django의 send_mail을 사용할 것이다. send_mail은 몇 개의 설정이 필요한데, 이 설정 값은 mailgun.com에서 얻을 수 있다.

      Sending => Domain settings => SMTP credentials

    여기서 필요한 설정 값은 EMAIL_HOST, EMAIL_PORT, EMAIL_HOST_USER, EMAIL_HOST_PASSWORD이다.

    • EMAIL_HOST = "smtp.mailgun.org"
    • EMAIL_PORT = 587

    • EMAIL_HOST_USER= Add new SMTP user로 생성한다.
    • EMAIL_HOST_PASSWORD= 계정을 생성하면 오른쪽 아래에 스크린샷과 같이 알림이 뜬다. 이것을 copy하면 password를 얻을 수 있다. Page를 나가면 password를 얻을 수 없으니 조심하자.

      이 모든 값을 .env에 저장하고 settings.py에 추가한다. EMAIL_FROM은 settings.py에 정의하지 않아도 되지만 내가 임의의로 추가했다. EMAIL_FROM은 send_mail 함수를 사용할 때 필요한 인자 값이다.

    Authorization

      Sending => Overview에 가게 되면 오른쪽에 Email address를 기입하는 form이 있다. mailgun으로 spam mail을 보내는 경우가 있어서 이메일 인증 기능이 추가되었다. domain을 등록하기 전까지 권한을 부여한 이메일에게만 메일을 보낼 수 있다. 나는 나의 이메일을 등록해두었다. 등록을 하게 되면 등록된 이메일로 메일이 전송되는데, 이를 확인하면 권한이 부여된다.

    code

    Verify Email

      email_verified와 email_secret이라는 두 개의 field를 추가했다. User가 회원 가입을 하면 user의 email로 email_secret 값을 담고 있는 mail을 전송한다. 사용자가 해당 mail의 링크를 클릭하면 인증을 하게 되어 로그인을 할 수 있게 된다. email_secret 값은 verify_email() method로 생성된다. Secret 값은 python의 내장 모듈인 uuid를 통해서 생성한다. 그리고 메일 전송은 django의 send_mail을 통해서 전송한다.

      -send_email 인자

    • 제목
    • 내용
    • 송신자
    • 수신자
    • html_message(optional) : html_message가 주어진다면 html tag가 text화 되지 않고 text/html content type이 될 것이다.

      strip_tags는 html tag로 보이는 모든 것을 없애버린다. 예를 들어, value가  "<b>Joel</b> <button>is</button> a <span>slug</span>"라면 return 값은 "Joel is slug"다.

    # users/models.py
    
    import uuid
    from django.contrib.auth.models import AbstractUser
    from django.core.mail import send_mail
    from django.db import models
    from django.template.loader import render_to_string
    from django.utils.html import strip_tags
    from . import managers
    from config import settings
    
    class User(AbstractUser):
        email_verified = models.BooleanField(default=False)
        email_secret = models.CharField(max_length=20, default="", blank=True)
    
        def verify_email(self):
            if self.email_verified is False:
                secret = uuid.uuid4().hex[:20]
                self.email_secret = secret
                html_message = render_to_string(
                    "email/verify_email.html", {"secret": secret}
                )
                send_mail(
                    "Verify Hairbnb Account",
                    strip_tags(html_message),
                    settings.EMAIL_FROM,
                    [self.email],
                    html_message=html_message,
                )
    
                self.save()
            return

      email/verify_email.html은 밑에 코드와 같다. here 링크를 누르면 application이 준 secret 값과 같이 해당 url로 간다.

    <h4>Verify Email</h4>
    <span>To verify your account click <a href="http://127.0.0.1:8000/users/verify/{{secret}}">here</a></span>

      SignUpView class에 추가한다. 회원 가입이 완료되면 사용자의 email로 링크를 전송한다. 또한 success_url을 email을 확인하라는 page로 redirect한다.

    users/views.py

      회원 가입에 성공하면 redirect 되는 page다.

      위 페이지에 대한 source code다.

    // check_email.html
    
    {% extends 'base.html' %}
    
    {% block page_title %}
      Chekc your email
    {% endblock page_title %}
    
    {% block search-bar %}
    <div></div>
    {% endblock search-bar %}
    
    {% block content %}
      <div class="flex justify-center align-center">
        <div>
          <h2 class="text-xl font-bold mb-4">Check you email</h2>
          <p class="text-lg">Please, go to your email and click <span>'here'</span> link to approve your account</p>
        </div>
      </div>
    {% endblock content %}

      메일이 도착했지만, 스팸함에 있다. 그 이유는 아직 도메인을 등록하지 않았기 때문이다. mailgun.com-Authorization에서 email을 등록했지만 그래도 스팸함으로 간다.

      내용은 verfiy_email.html과 같지만 'here' link가 활성화되어 있지 않다. 그 이유는 스팸함에 있기 때문에 구글에서 비활성화 처리했기 때문이다. '스팸 해제'를 눌러서 here link를 활성화 하자

      받은 편지함으로 가니 here link가 활성화 된 것을 확인할 수 있다.

    Complete verification

      위 here의 href는 http://127.0.0.1:8000/users/verify/{{secret}}다. urls.py를 보면 밑에 code로 되어 있다.

    urlpatterns=[
        path("email/check/", views.check_email, name="check-email"),
        path(
            "verify/<str:key>/",
            views.complete_verification,
            name="complete-verification",
        ),
     ]

     

      here link를 click 하면 실행되는 함수다. user를 (secret)key 값으로 찾고, user의 email을 인증한다. 그리고 secret 값을 없애고 저장한 후 login한다.

    # users/views.py
    
    from .exception import LoggedOutOnlyFunctionView
    
    def complete_verification(request, key):
        try:
            if request.user.is_authenticated:
                raise LoggedOutOnlyFunctionView("Please verify email first")
            user = models.User.objects.get_or_none(email_secret=key)
            if user is None:
                messages.error(request, "User does not exist")
                return redirect(reverse("core:home"))
            user.email_verified = True
            user.email_secret = ""
            user.save()
            login(request, user)
            messages.success(request, f"{user.email} verification is completed")
            return redirect(reverse("core:home"))
        except LoggedOutOnlyFunctionView as error:
            messages.error(request, error)
            return redirect("core:home")
    

      LoginView 부분도 user.email_verified가 True가 아니면 login을 할 수 없게 했다.

    # users/views.py
    
    class LoginView(mixins.LoggedOutOnlyView, FormView):
    
        """ Login View """
    
        form_class = forms.LoginForm
        success_url = reverse_lazy("core:home")
        template_name = "pages/users/login.html"
    
        def form_valid(self, form):
            email = form.cleaned_data.get("email")
            password = form.cleaned_data.get("password")
            user = authenticate(self.request, username=email, password=password)
            if user is not None and user.email_verified is True:
                messages.success(self.request, f"{user.first_name} logged in")
                login(self.request, user)
            else:
                return redirect(reverse("users:login"))
            return super().form_valid(form)

    참고 자료

    소스 코드

    github.com/zpskek/airbnb-clone-v3/commit/8df4f86d7a0dd96d241a83b8486ec7a6a54573dc

    'Project using python > Cloning Airbnb' 카테고리의 다른 글

    django user profile CBV  (0) 2021.03.04
    django user profile FBV  (0) 2021.03.04
    django kakao login  (0) 2021.03.03
    django managers.py  (0) 2021.03.03
    django github login  (0) 2021.03.03

    댓글

Designed by Tistory.