1) Messages Tag
- Django์ Messages Framework๋ฅผ ํตํด ์์ธ์ฒ๋ฆฌ์์ ์ค๋ฅ๊ฐ ๋ฐ์ํ  ๋ ๋ง๋ค ํ
ํ๋ฆฟ์ ๋ํ๋ผ ์ค๋ฅ ๋ฉ์์ง๋ฅผ ์์ฝ๊ฒ ์ ์ดํ  ์ ์์ด์. ๋ด์ฅ๋์ด ์๊ธฐ ๋๋ฌธ์ Project๋ฅผ ์ค์นํ  ๋ ๋ถํฐ ์ฌ์ฉํ  ์ ์๊ณ , ์๋์ ๊ฐ์ ํ๊ทธ๋ค์ด ์ค๋น๋์ด ์์ด์.
 
messages.debug(request, '%s SQL statements were executed.' % count)
messages.info(request, 'Three credits remain in your account.')
messages.success(request, 'Profile details updated.')
messages.warning(request, 'Your account expires in three days.')
messages.error(request, 'Document deleted.')
{% if messages %}
<ul class="messages">
    {% for message in messages %}
    <li class="message {% if message.tags %}{{ message.tags }}{% endif %}">{{ message }}</li>
    {% endfor %}
</ul>
{% endif %}
2) using messages
- Messages Framework์ ์๋ ์์นํด ์์ด์.
- ๐ from django.contrib import messages 
 
 
- ๊ฐ ์๋ฌ ๋ด์ฉ์ ๋ง๊ฒ ์๋ฌ ์์ ๋ํ๋ผ ๋ฉ์์ง๋ฅผ ๋ด๊ณ , except ๊ตฌ๋ฌธ์์ ํด๋น ์๋ฌ๊ฐ ๋ฐ์ํ๋ฉด ์์ฑํ ๋ฉ์์ง๋ฅผ ์ ๋ฌํฉ๋๋ค.
- ๐ messages.error(request, e)
 
 
- ์ฑ๊ณต๋ฉ์์ง๋ login() ๋งค์๋ ๋ฐ๋ก ์๋ ์ถ๊ฐํ์์ด์:)
 
import os
import requests
from django.views.generic import FormView
from django.urls import reverse_lazy
from django.shortcuts import render, redirect, reverse
from django.contrib.auth import authenticate, login, logout
from django.core.files.base import ContentFile
from django.contrib import messages 
from . import forms, models
...
...
def log_out(request):
    messages.info(request, f"See you later") 
    logout(request)
    return redirect(reverse("core:home"))
def github_login(request):
    client_id = os.environ.get("GITHUB_ID")
    redirect_uri = "http://127.0.0.1:8000/users/login/github/callback"
    return redirect(
        f"https://github.com/login/oauth/authorize?client_id={client_id}&redirect_uri={redirect_uri}&scope=read:user"
    )
class GithubException(Exception):
    pass
def github_callback(request):
    try:
        client_id = os.environ.get("GITHUB_ID")
        client_secret = os.environ.get("GITHUB_SECTET")
        code = request.GET.get("code", None)
        if code is not None:
            token_request = requests.post(
                f"https://github.com/login/oauth/access_token?client_id={client_id}&client_secret={client_secret}&code={code}",
                headers={"Accept": "application/json"},
            )
            token_json = token_request.json()
            error = token_json.get("error", None)
            if error is not None:
                raise GithubException("Can't get authoriztion code.")  
            else:
                access_token = token_json.get("access_token")
                profile_request = requests.get(
                    "https://api.github.com/user",
                    headers={
                        "Authorization": f"token {access_token}",
                        "Accept": "application/json",
                    },
                )
                profile_json = profile_request.json()
                username = profile_json.get("login", None)
                if username is not None:
                    name = profile_json.get("name")
                    email = profile_json.get("email")
                    bio = profile_json.get("bio")
                    try:
                        user = models.User.objects.get(email=email)
                        if user.login_method != models.User.LOGIN_GITHUB:
                            raise GithubException(
                                f"Please log in with : {user.login_method}"
                            ) 
                    except models.User.DoesNotExist:
                        user = models.User.objects.create(
                            email=email,
                            first_name=name,
                            username=email,
                            bio=bio,
                            login_method=models.User.LOGIN_GITHUB,
                            email_verified=True,
                        )
                        user.set_unusable_password()
                        user.save()
                    login(request, user)
                    messages.success(request, f"Welcome back! {user.first_name}") 
                    return redirect(reverse("core:home"))
                else:
                    raise GithubException("Can't get your profile") 
        else:
            raise GithubException("Can't get code") 
    except GithubException as e:
        messages.error(request, e) 
        return redirect(reverse("users:login"))
def kakao_login(request):
    client_id = os.environ.get("KAKAO_ID")
    redirect_uri = "http://127.0.0.1:8000/users/login/kakao/callback"
    return redirect(
        f"https://kauth.kakao.com/oauth/authorize?client_id={client_id}&redirect_uri={redirect_uri}&response_type=code"
    )
class KakaoException(Exception):
    pass
def kakao_callback(request):
    try:
        code = request.GET.get("code")
        client_id = os.environ.get("KAKAO_ID")
        redirect_uri = "http://127.0.0.1:8000/users/login/kakao/callback"
        token_request = requests.get(
            f"https://kauth.kakao.com/oauth/token?grant_type=authorization_code&client_id={client_id}&redirect_uri={redirect_uri}&code={code}"
        )
        
        token_json = token_request.json()
        error = token_json.get("error", None)
        if error is not None:
            raise KakaoException("Can't get authoriztion code.")  
        access_token = token_json.get("access_token")
        profile_request = requests.get(
            "https://kapi.kakao.com//v2/user/me",
            headers={"Authorization": f"Bearer {access_token}"},
        )
        profile_json = profile_request.json()
        email = profile_json.get("kakao_account").get("email")
        if email is None:
            raise KakaoException("Please also give me your email")  
        properties = profile_json.get("kakao_account").get("profile")
        nickname = properties.get("nickname")
        profile_image = properties.get("profile_image_url")
        print(nickname, profile_image)
        try:
            user = models.User.objects.get(email=email)
            if user.login_method != models.User.LOGIN_KAKAO:
                raise KakaoException(f"Please log in with : {user.login_method}")  
        except models.User.DoesNotExist:
            user = models.User.objects.create(
                email=email,
                first_name=nickname,
                username=email,
                login_method=models.User.LOGIN_KAKAO,
                email_verified=True,
            )
            user.set_unusable_password()
            user.save()
            if profile_image is not None:
                photo_request = requests.get(profile_image)
                user.avatar.save(
                    f"{nickname}-avatar", ContentFile(photo_request.content)
                )
        login(request, user)
        messages.success(request, f"Welcome back {user.first_name}")  
        return redirect(reverse("core:home"))
    except KakaoException as e:
        messages.error(request, e) 
        return redirect(reverse("users:login"))
3) Design Messages
- ๋ฉ์์ง๊ฐ ์๋จ์ ๋
ธ์ถ๋๋๋ก, "partials/messages.html"์ด "base.html" head ์์ ๋ฐฐ์นํ์ด์:) 
 
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="{% static 'css/styles.css' %}">
    <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.11.2/css/all.css">
    <title>{% block page_title %}{% endblock page_title %}| Nbnb</title>
</head>
<body class="text-gray-800 mt-24 font-light">
    {% include "partials/messages.html" %}  
    <header class="container max-w-full inset-0 flex items-center justify-between px-6 h-20 border-b border-gray-400 fixed bg-white">
        <div class="flex items-center w-1/3">
            <a href="{% url "core:home" %}" class="mr-6">
                <img class="w-8" src="{% static 'img/airbnb_logo.png' %}">
            </a>
            {% block search-bar %}
            <form method="get" action="{% url "rooms:search" %}" class="w-9/12">
                <input class="search-box border px-5 w-full font-medium text-gray-900 placeholder-gray-600 py-3 rounded-sm shadow-md hover:shadow-lg focus:outline-none" name="city" placeholder="Search by City" type="text">
            </form>
            {% endblock search-bar %}
        </div>
        {% include "partials/nav.html" %}
    </header>
    {% block content %}{% endblock content %}
    {% include "partials/footer.html" %}
</body>
</html>
- "partials/messages.html"์ TailwindCSS๋ฅผ ์ ์ฉ์์ผ๋ณผ๊ป์.
 
{% if messages %}
<ul class="absolute top-0 mx-auto left-0 right-0 flex justify-center">
    {% for message in messages %}
    <li class="message font-medium bg-gray-700 z-10 rounded-full text-white py-4 px-6 w-64 text-center {% if message.tags %}{{ message.tags }}{% endif %}">{{ message }}</li>
    {% endfor %}
</ul>
{% endif %}
- Message Tag ๋ณ๋ก ์๊น์ ๋ค๋ฅด๊ฒ์ฃผ๊ณ , ์ ๋๋ฉ์ด์
 ํจ๊ณผ๋ ์๋์ฒ๋ผ ๋ฃ์ ์ ์์ต๋๋ค:)
 
@tailwind base;
@tailwind components;
@tailwind utilities;
...
...
...
@keyframes messageFadeIn {
    0% {
      opacity: 0;
      transform: translateY(-50px);
    }
    5% {
      opacity: 1;
      transform: translateY(50px);
    }
    95% {
      opacity: 1;
      transform: translateY(50px);
    }
    100% {
      opacity: 0;
      transform: translateY(-50px);
    }
  }
.message{
    animation: messageFadeIn 5s ease-in-out forwards; 
    &.error{
        @apply bg-red-600;
    }
    &.info{
        @apply bg-blue-500;
    }
    &.success{
        @apply bg-green-500;
    }
    &.warning{
        @apply bg-yellow-400;
    }
}

