1. Django Tutorial(Airbnb) - Profile Page

ID์งฑ์žฌยท2021๋…„ 8์›” 18์ผ
0

Django

๋ชฉ๋ก ๋ณด๊ธฐ
25/43
post-thumbnail

๐ŸŒˆ Profile Page

๐Ÿ”ฅ Setting Profile

๐Ÿ”ฅ Read Profile

๐Ÿ”ฅ UpdateView ์‚ฌ์šฉํ•˜๊ธฐ


1. Setting Profile

1) Absolute URL

  • nav.html์— ํ”„๋กœํ•„ ํŽ˜์ด์ง€๋กœ ์ด๋™ํ•  ์ˆ˜ ์žˆ๋Š” ๋งํฌ๋ฅผ ๋งŒ๋“ค์–ด์ค๋‹ˆ๋‹ค.
<ul class="flex items-center text-sm font-medium h-full">
    {% if user.is_authenticated %}
        <li class="nav_link"><a href="{% url "users:profile" %}">Profile</a></li> # ๐Ÿ‘ˆ profile ์ด๋™ ๋ฒ„ํŠผ
        <li class="nav_link"><a href="{% url "users:logout" %}">Log out</a></li>
    {% else %}
        <li class="nav_link"><a href="{% url "users:login" %}">Log in</a></li>
        <li class="nav_link"><a href="{% url "users:signup" %}">Sign up</a></li>
    {% endif %}    
</ul>
  • profile ๋งํฌ๋ฅผ ํด๋ฆญํ•˜์˜€์„ ๋•Œ, ์‹คํ–‰๋  ๋กœ์ง์„ "DetailView"๋ฅผ ์ƒ์†๋ฐ›์•„ CBV("UserProfileView")๋กœ ๋งŒ๋“ค์–ด ์ค๋‹ˆ๋‹ค.
# users/views.py
from django.views.generic import DetailView
...
...
...
class UserProfileView(DetailView):
    pass
  • ํ”„๋กœํ•„ ํŽ˜์ด์ง€๋ฅผ ์š”์ฒญํ•˜์˜€์„ ๋•Œ, ์ ‘๊ทผํ•  ๊ฒฝ๋กœ("users:profile")์™€ View์˜ CBV("UserProfileView") ๋งคํ•‘ํ•ด์ค๋‹ˆ๋‹ค.
from django.urls import path
from . import views
app_name = "users"
urlpatterns = [
   ...
   ...
   path("<int:pk>/", views.UserProfileView.as_view(), name="profile"),
]

  • ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•œ ์ด์œ ๋Š” urls.py์—์„œ๋Š” <int:pk>๋กœ ์ธ์ž๋ฅผ ์š”์ฒญํ•˜๊ณ  ์žˆ๋Š”๋ฐ, Templates์˜์—ญ์—์„œ pk๊ฐ’์„ ์ „๋‹ฌํ•ด์ฃผ์ง€ ์•Š์•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ์•„๋ž˜์ฒ˜๋Ÿผ "user.pk"๋ฅผ ๋„ฃ์–ด ํ•ด๊ฒฐํ•  ์ˆ˜๋„ ์žˆ์ง€๋งŒ, "get_absolute_url"์„ ์‚ฌ์šฉํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.
    • ๐Ÿ” <li class="nav_link"><a href="{% url "users:profile" user.pk %}">Profile</a></li>
  • Django์˜ Model๋“ค์€ "get_absolute_url"์ด๋ผ๋Š” ๋ฉ”์†Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. users/models.py์— "get_absolute_url"์˜ ๋ฉ”์„œ๋“œ๋ฅผ ์ƒ์„ฑํ•ด url๊ณผ ์ „๋‹ฌํ•  ์ธ์ž๋ฅผ ์ง€์ •ํ•ด์ค๋‹ˆ๋‹ค. "get_absolute_url" ๋งค์„œ๋“œ๋ฅผ ์ด์šฉํ•˜๋ฉด Admin Panel์—์„œ "View ON SITE" ๊ธฐ๋Šฅ ์ž๋™์œผ๋กœ ์ œ๊ณต๋ฐ›์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
from django.shortcuts import reverse
...
...
    def get_absolute_url(self):
        return reverse("users:profile", kwargs={"pk": self.pk}) # ๐Ÿ‘ˆ pk๊ฐ’์„ ์ด๊ณณ์„ ํ†ตํ•ด ์ „๋‹ฌ์‹œ์ผœ์ค˜์š”:)
  • nav.html์˜ profile ๋งํฌ๋กœ ์ ‘๊ทผํ•˜์˜€์„ ๋•Œ, models์˜ "get_absolute_url" ๋งค์„œ๋“œ๊ฐ€ ์ž‘๋™ํ•  ์ˆ˜ ์žˆ๋„๋ก ์—ฐ๊ฒฐํ•ด ์ค๋‹ˆ๋‹ค.
<ul class="flex items-center text-sm font-medium h-full">
    {% if user.is_authenticated %}
        <li class="nav_link"><a href="{{user.get_absolute_url}}">Profile</a></li> # ๐Ÿ‘ˆ aํƒœ๊ทธ์˜ ๊ฒฝ๋กœ๋ฅผ ๋ณ€๊ฒฝํ•ด์ค๋‹ˆ๋‹ค.
        <li class="nav_link"><a href="{% url "users:logout" %}">Log out</a></li>
    {% else %}
        <li class="nav_link"><a href="{% url "users:login" %}">Log in</a></li>
        <li class="nav_link"><a href="{% url "users:signup" %}">Sign up</a></li>
    {% endif %}    
</ul>

2. Read Profile

1) context_object_name

  • ํ”„๋กœํ•„ ํŽ˜์ด์ง€ ๋งํฌ๋ฅผ ํด๋ฆญํ•˜๋ฉด, ํ˜„์žฌ ๋กœ๊ทธ์ธํ•œ ์‚ฌ์šฉ์ž์˜ pk๊ฐ’์„ url์—์„œ ์–ป์€ ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์–ด์š”.
  • ์œ„ ์˜ค๋ฅ˜๋Š” ์ด๋Š” View์—์„œ ์–ด๋–ค ๋ชจ๋ธ์ธ์ง€ ์ง€์ •ํ•˜์ง€ ์•Š์•˜๊ธฐ ๋•Œ๋ฌธ์— ๋ฐœ์ƒํ•˜์˜€์–ด์š”. ์ด์— ๋ชจ๋ธ์„ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค.
    • ๐Ÿ”Ž model = models.User
  • context_object_name์€ View์—์„œ Template์— ์ „๋‹ฌํ•˜๋Š” "Context"์˜ ์ด๋ฆ„์„ ์ง€์ •ํ•˜๋Š” ๊ธฐ๋Šฅ์„ ํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ๋กœ๊ทธ์ธํ–ˆ๋˜ ์‚ฌ์šฉ์ž ๊ฐ์ฒด๋ฅผ ๊ฐ€๋ฅดํ‚ฌ ์ˆ˜ ์žˆ๋„๋ก ์ง€์ •ํ•ด ํ•ด์ค๋‹ˆ๋‹ค:)
# users/views.py
from django.views.generic import DetailView
...
...
...
class UserProfileView(DetailView):
    model = models.User
    context_object_name = "user_obj" # ๐Ÿ‘ˆ ์ฃผ์˜ํ•ด์•ผํ•ด์š”! 
  • model์„ ์„ค์ •ํ•˜๊ณ  ์ƒˆ๋กœ๊ณ ์นจ์„ ํ•˜๋ฉด, Django๊ฐ€ Template(user_detail.html)๋ฅผ ์ฐพ๊ณ  ์žˆ๋„ค์š”. ์‚ฌ์šฉ์ž์˜ Profile์„ ๋ณด์—ฌ์ค„ Template๋ฅผ ๋งŒ๋“ค์–ด ์ค„๊ป˜์š”:)
  • ์ด์ œ user_obj๋ฅผ ํ…œํ”Œ๋ฆฟ ๋ณ€์ˆ˜๋ฅผ ์ด์šฉํ•˜์—ฌ Model์˜ ํ•„๋“œ์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๊ณ , ์ด์ œ ํ”Œ๋กœํ•„ ํŽ˜์ด์ง€๋ฅผ ์ถœ๋ ฅํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
{% extends "base.html" %}
{% block page_title %}
    Profile
{% endblock page_title %}
{% block content %}
    <div class="container lg:w-5/12 md:w-1/2 xl:w-1/4 mx-auto my-10 flex flex-col items-center border p-6 border-gray-400">
        {{user_obj.first_name}}
    </div>
{% endblock content %}

3) Profile Page ๊ตฌ์„ฑ

  • "user"์™€ "user_obj"๊ฐ€ ์ผ์น˜ํ•  ๋•Œ๋งŒ, edit ๋ฒ„ํŠผ์ด ๋ณด์ด๋„๋ก ์ฒ˜๋ฆฌํ–ˆ๊ณ , ํ”„๋กœํ•„ ์•„๋ž˜์ชฝ์— ๋กœ๊ทธ์ธํ•œ ์‚ฌ์šฉ์ž๊ฐ€ host์ธ ๊ฐ์‹ค ์ •๋ณด๊ฐ€ ํ•˜์˜€์–ด์š”. ๋˜ํ•œ avatar๋ฅผ ํ‘œ์‹œํ•˜๋Š” ํ…œํ”Œ๋ฆฟ์„ includeํ•˜์—ฌ ์‚ฌ์šฉํ•˜์˜€๊ณ , include template์ชฝ์œผ๋กœ ํ…œํ”Œ๋ฆฟ ๋ณ€์ˆ˜๋ฅผ ์ „๋‹ฌํ•˜๊ณ ์žํ•˜๋ฉด with์„ ์‚ฌ์šฉํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.
    • ๐Ÿ”Ž {% include "mixins/user_avatar.html" with user=user_obj %}
# user_detail.html
{% extends "base.html" %}
{% block page_title %}
    {{user_obj.first_name}}'s Profile
{% endblock page_title %}
{% block content %}
<div class="h-75vh">
    <div class="container lg:w-5/12 md:w-1/2 xl:w-1/4 mx-auto my-10 flex flex-col items-center border p-6 border-gray-400 ove">
        {% include "mixins/user_avatar.html" with user=user_obj %}
        <div class="flex items-center">
            <span class="text-3xl mt-1">{{user_obj.first_name}}</span>
            {% if user_obj.superhost %}
                <i class="fas fa-check-circle text-teal-400 ml-1"></i>
            {% endif %}
        </div>
        <span class="text-lg mb-5">{{user.bio}}</span>
        {% if user == user_obj %}
        <a href="#" class="btn-link">Edit Profile</a>
        {% endif %}        
    </div>
    {% if user_obj.rooms.count > 0 %}
    <div class="container mx-auto pb-10 flex flex-col items-center">
        <h3 class="mb-12 text-2xl">{{user_obj.first_name}}'s Rooms</h3>
        <div class="flex flex-wrap -mx-40 mb-10">
            {% for room in user_obj.rooms.all  %}
                {% include 'mixins/room_card.html' with room=room %}
            {% endfor %}
        </div>
    </div>
    {% endif %}
</div>
{% endblock content %}
  • avatar์ฒ˜๋Ÿผ ์—ฌ๊ธฐ์ €๊ธฐ์„œ ์ž์ฃผ ์‚ฌ์šฉ๋˜๋Š” ํŒจํ„ด์„ ๊ฐ€์งˆ ๊ฒฝ์šฐ, ๋ณ„๋„์˜ html๋กœ ๋ถ„๋ฆฌํ•œ ๋’ค ์—ฌ๋Ÿฌ ๊ณณ์—์„œ inlcudeํ•˜์—ฌ ์‚ฌ์šฉํ•˜๋ฉด template๋“ค์„ ๋ณด๋‹ค ๊ฐ„๊ฒฐํ•˜๊ฒŒ ๊ตฌ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์„œ๋Š” avatar๊ฐ€ ์กด์žฌํ•œ๋‹ค๋ฉด ์ด๋ฏธ์ง€๋ฅผ ๋‚˜ํƒ€๋‚ด๊ณ , ์ด๋ฏธ์ง€๊ฐ€ ์—†๋‹ค๋ฉด ์ด๋ฆ„์˜ ์ฒซ๋ฒˆ์งธ ๋ฌธ์ž๋ฅผ ๋‚˜ํƒ€๋‚˜๊ฒŒ ํ•˜์˜€์–ด์š”. ์ด๋ฅผ ์œ„ํ•ด "|first" ํ…œํ”Œ๋ฆฟ ํ•„ํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜์˜€์Šต๋‹ˆ๋‹ค.
    • ๐Ÿ”Ž {{user.first_name|first}}
# mixins/user_avatar.html
{% if user.avatar %}
    <div class="h-20 w-20 rounded-full bg-cover" style="background-image: url({{user.avatar.url}});">
{% else %}
    <div class="h-20 w-20 bg-gray-700 rounded-full text-white flex justify-center items-center overflow-hidden">
        <span class="text-3xl">{{user.first_name|first}}</span>
    {% endif %}
</div>

3. UpdateView ์‚ฌ์šฉํ•˜๊ธฐ

1) UpdateView

  • Profile์„ ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋Šฅ์„ ์œ„ํ•ด UpdateView๋ฅผ ์ƒ์† ๋ฐ›์€ CBV("UpdateProfileView")๋ฅผ ๊ฐ„๋‹จํžˆ ๋งŒ๋“ค ์ƒ์„ฑํ•˜์˜€์–ด์š”.
# users/views.py
import os
import requests
from django.views.generic import FormView, DetailView, UpdateView # ๐Ÿ‘ˆ "UpdateView" import
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
...
...
...
class UpdateProfileView(UpdateView): # ๐Ÿ‘ˆ "UpdateView" ์ƒ์†
    pass
  • users/urls.py์— "UpdateProfileView"๋ฅผ ๋งคํ•‘ํ•ด์ค„๊ป˜์š”:)
# users/urls.py
from django.urls import path
from . import views
app_name = "users"
urlpatterns = [
...
...
    path("update-profile/", views.UpdateProfileView.as_view(), name="update"),
]

2) get_object()

  • UpdateView์—์„œ "get_object()"๋งค์„œ๋“œ๋ฅผ ํ†ตํ•ด ์ˆ˜์ •ํ•  Object๋ฅผ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ์–ด์š”.
  • "template_name"์„ ํ†ตํ•ด Django๊ฐ€ renderํ•  Template์„ ์ง€์ •ํ•ด์ฃผ๊ณ , get_object()๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” fields ์ง€์ •์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.
# users/views.py
import os
import requests
from django.views.generic import FormView, DetailView, UpdateView
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
...
...
...
class UpdateProfileView(UpdateView):
    model = models.User
    template_name = "users/update-profile.html" # ๐Ÿ‘ˆ renderํ•  ํ…œํ”Œ๋ฆฟ ์ง€์ •
    fields = (       # ๐Ÿ‘ˆ get_object ๋งค์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” fields ์ง€์ •์ด ํ•„์š”ํ•ด์š”:)
        "first_name",
        "last_name",
        "avatar",
        "bio",
        "birthdate",
        "language",
        "currency",
    )
    def get_object(self, queryset=None):  # ๐Ÿ‘ˆ ์‚ฌ์šฉ์ž์˜ User Object๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.
        return self.request.user
  • ํ…œํ”Œ๋ฆฟ์„ ๊ฐ„๋‹จํ•˜๊ฒŒ ์ง€์ •ํ•ด๋ณด์ฃ . Login ํ…œํ”Œ๋ฆฟ์„ ๊ทธ๋Œ€๋กœ ๊ฐ€์ ธ์™€,, social login ๋ถ€๋ถ„์€ ์‚ญ์ œํ•˜๊ณ , ์ธ์ฆ์€ ๋‚จ๊ฒจ๋‘์—ˆ์–ด์š”. ๋˜ํ•œ ์ธ์ฆ์— cta ๊ฐ’์„ "Update Profile"๋กœ ์ˆ˜์ •ํ•˜๊ณ , "Sign Up" ๋ถ€๋ถ„์„ "Change Password"๋กœ ๋ฐ”๊ฟ”์ฃผ์—ˆ์–ด์š”.
{% extends "base.html" %}
{% block page_title %}
    Update Profile
{% endblock page_title %}
{% block search-bar %}  # ๐Ÿ‘ˆ search-bar ๊ฐ์ถ”๊ธฐ
{% endblock search-bar %}
{% block content %}
    <div class="container lg:w-5/12 md:w-1/2 xl:w-1/4 mx-auto my-10 flex flex-col items-center border p-6 border-gray-400">
        {% include 'mixins/auth/auth_form.html' with form=form cta="Update Profile" %}
        <div class="mt-5">
            <a href="{% url 'users:signup' %}" class="text-teal-500 font-medium">Change Password</a>
        </div>
    </div>
{% endblock content %}

  • ์•„์ง ๋œ ์™„์„ฑ๋ฌ์ง€๋งŒ, ์ˆ˜์ •์€ ์ž˜ ๋˜๋Š”๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์–ด์š”. UpdateView๊ฐ€ ์ง€์ •๋œ field๋ฅผ template๋กœ ์ „๋‹ฌํ•˜๊ณ  field์— ์ˆ˜์ •์ด ๋ฐœ์ƒํ•˜๋ฉด ์•Œ์•„์„œ ์ˆ˜์ • ๋ฐ ์ €์žฅ์„ ํ•ด์ค๋‹ˆ๋‹ค:) ๋˜ํ•œ ์ˆ˜์ • ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด์ž๋งˆ์ž ๋ฐ”๋กœ profile page๋กœ ๊ฐ€๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋Š”๋ฐ์š”,, ์ด๋Š” UpdateView๊ฐ€ ์„ฑ๊ณตํ•˜๋ฉด ์ž๋™์œผ๋กœ "get_absolute_url"์„ ํ˜ธ์ถœํ•˜๊ธฐ ๋–„๋ฌธ์ž…๋‹ˆ๋‹ค.
# users/moodels.py
def get_absolute_url(self):
    return reverse("users:profile", kwargs={"pk": self.pk})

3) PasswordChangeView

  • PasswordChangeView๋Š” Django์—์„œ ์ œ๊ณตํ•˜๋Š” ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ˆ˜์ •ํ•˜๋Š”๋ฐ ๋„์›€์„ ์ฃผ๋Š” ๊ธฐ๋Šฅ์ด์—์š”. ์•„๋ž˜ ์œ„์น˜์— ์žˆ์Šต๋‹ˆ๋‹ค.
    • ๐Ÿ”Ž from django.contrib.auth.views import PasswordChangeView
  • "template_name"์„ ์ง€์ •ํ•˜์ง€ ์•Š์œผ๋ฉด ๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณ€๊ฒฝ ๋งํฌ๋ฅผ ์š”์ฒญํ–ˆ์„ ๋•Œ, admin site๋กœ ๋„˜์–ด๊ฐ‘๋‹ˆ๋‹ค. ๊ทธ๊ฑธ ์›์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ์ด๋™ํ•  ํ…œํ”Œ๋ฆฟ์„ ์ง€์ •ํ•ด ์ฃผ์—ˆ์–ด์š”.
  • ๋˜ํ•œ ๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณ€๊ฒฝ ์„ฑ๊ณต์‹œ redirectํ•  ๊ณณ์„ ์ง€์ •ํ•ด ์ฃผ์–ด์•ผ ์•„๋ž˜์™€ ๊ฐ™์€ ์˜ค๋ฅ˜ํŽ˜์ด์ง€๋ฅผ ๋งŒ๋‚˜์ง€ ์•Š์•„์š”. ๋น„๋ฐ€๋ฒˆํ˜ธ๋Š” ๋ณ€๊ฒฝ๋˜๋‚˜ ๋ณ€๊ฒฝ ํ›„ ์ด๋™ํ•  ๊ณณ์„ ์ง€์ •ํ•ด์ฃผ์ง€ ์•Š์•˜๊ธฐ ๋•Œ๋ฌธ์ด์š”.
  • success_url๋กœ ์ง€์ •ํ•ด์ฃผ์–ด๋„ ๋˜๊ณ , get_success_url ๋งค์„œ๋“œ๋กœ ์ง€์ •ํ•ด ์ค„ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฒˆ์—๋Š” get_success_url์„ ์‚ฌ์šฉํ•ด ๋ณผ๊ป˜์š”:)
    • ๐Ÿ”Ž success_url = reverse_lazy('๊ฒฝ๋กœ') ๐Ÿ‘ˆ success_url์„ ์‚ฌ์šฉํ•˜๊ณ ์‹ถ๋‹ค๋ฉด ์ด๋ ‡๊ฒŒ ์ง€์ •ํ•ด์ฃผ๋ฉด๋ฉ๋‹ˆ๋‹ค.
from django.contrib.auth.views import PasswordChangeView
class UpdatePasswordView(PasswordChangeView):
    template_name = "users/update-password.html"
    def get_success_url(self): <# ๐Ÿ‘ˆ ๋ณ€๊ฒฝ ์„ ๊ณต ์‹œ ์ด๋™ํ•  ๊ณณ์„ ์ง€์ •ํ•ด์ฃผ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
        return self.request.user.get_absolute_url()
  • "users/update-password.html"์„ ์•„๋ž˜์ฒ˜๋Ÿผ ์ž‘์„ฑํ•˜์˜€์–ด์š”.
{% extends "base.html" %}
{% block page_title %}
    Update Password
{% endblock page_title %}
{% block search-bar %}
{% endblock search-bar %}
{% block content %}
<div class="min-h-75vh">
    <div class="container lg:w-5/12 md:w-1/2 xl:w-1/4 mx-auto my-10 flex flex-col items-center border p-6 border-gray-400">
            {% include 'mixins/auth/auth_form.html' with form=form cta="Update Password" %}
    </div>
</div>
{% endblock content %} 
  • url์„ ๋งคํ•‘ํ•ด ์ค๋‹ˆ๋‹ค.
from django.urls import path
from . import views
app_name = "users"
urlpatterns = [
    ...
    ...
    path("update-password/", views.UpdatePasswordView.as_view(), name="password"),
  • Profile Page์—์„œ ๋งํฌ๋ฅผ ์—ฐ๊ฒฐ์‹œ์ผœ์ค„๊ป˜์š”. ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ๋Š” ๋งํฌ๋Š” 'email'๋กœ ๊ฐ€์ž…ํ•œ ๊ฒฝ์šฐ์—๋งŒ ํ•ด๋‹น๋˜์š”,, KaKao ๋“ฑ ์ธ์ฆ์„ ํ†ตํ•ด ๊ฐ€์ž…ํ•œ ๊ฒฝ์šฐ์—๋Š” ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์—ฌ๊ธฐ์„œ ๋ณ€๊ฒฝํ•  ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ, login_method ์ค‘ 'email'๋กœ ๊ฐ€์ž…ํ•œ ๊ฒฝ์šฐ์—๋งŒ ๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณ€๊ฒฝ ๋งํฌ๊ฐ€ ๋‚˜ํƒ€๋‚˜๋„๋ก ํ•˜์˜€์–ด์š”.
{% extends "base.html" %}
{% block page_title %}
    Update Profile
{% endblock page_title %}
{% block search-bar %}
{% endblock search-bar %}
{% block content %}
    <div class="container lg:w-5/12 md:w-1/2 xl:w-1/4 mx-auto my-10 flex flex-col items-center border p-6 border-gray-400">
        {% include 'mixins/auth/auth_form.html' with form=form cta="Update Profile" %}
        {% if object.login_method == "email" %} # ๐Ÿ‘ˆ email๋กœ ๊ฐ€์ž…ํ•œ ๊ฒฝ์šฐ์—๋งŒ ๋งํฌ๊ฐ€ ๋‚˜ํƒ€๋‚˜์š”:)
        <div class="mt-5">
            <a href="{% url 'users:password' %}" class="text-teal-500 font-medium">Change Password</a>
        </div>
        {% endif %}
    </div>
{% endblock content %}

4) get_form()

  • "get_form"์€ ๋งค์„œ๋“œ๋Š” View์—์„œ ์‚ฌ์šฉ๋˜๋Š” form์˜ instant๋ฅผ ๋ฐ˜ํ™˜ํ•ด ์ค๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด form์˜ placeholder๋ฅผ ์ถ”๊ฐ€ํ•œ๋‹ค๊ฑฐ๋‚˜ form์„ ์ œ์–ดํ•  ์ˆ˜ ์žˆ์–ด์š”.
  • "get_form" ๋งค์„œ๋“œ๋ฅผ ํ†ตํ•ด form์˜ instant๋ฅผ ๊ฐ€์ ธ์™€ field๊ฐ’์— placeholder ์†์„ฑ์„ ๋„ฃ์–ด๋ณด๋„๋ก ํ• ๊ป˜์š”.
  • PasswordChangeView๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ์—๋Š” field์†์„ฑ์„ ์ง€์ •ํ•ด์ฃผ์ง€ ์•Š์•„๋„, form์ด ์ƒ์„ฑ๋˜์—ˆ๋Š”๋ฐ์š”,, fields๊ฐ’์„ ์–ด๋–ป๊ฒŒ ์•Œ ์ˆ˜ ์žˆ์„ ๊นŒ์š”? html์ฝ”๋“œ๋ฅผ ํ™•์ธํ•˜์—ฌ input์˜ name๊ฐ’์„ ์ง€์ •ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค!
    • fields๊ฐ’ : "old_password", "new_password1", "new_password2"
...
...
class UpdatePasswordView(PasswordChangeView):
    template_name = "users/update-password.html"
    def get_form(self):
        form = super().get_form()
        form.fields["old_password"].widget.attrs = {"placeholder": "Current password"}
        form.fields["new_password1"].widget.attrs = {"placeholder": "New password"}
        form.fields["new_password2"].widget.attrs = {"placeholder": "Confirm new password"}
        # print(form) ๐Ÿ‘ˆ form์˜ html ํƒœ๊ทธ๊ฐ€ ๋‹ด๊ฒจ ์žˆ์–ด์š”.
        return form

5) SuccessMessageMixin

  • "SuccessMessageMixin"์„ CBV์—์„œ ์ƒ์†๋ฐ›์•„ ์‚ฌ์šฉํ•˜๋ฉด ์„ฑ๊ณตํ–ˆ์„ ๊ฒฝ์šฐ ์†์‰ฝ๊ฒŒ ๋ฉ”์‹œ์ง€๋ฅผ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ์–ด์š”. ํ”„๋กœํ•„ ์ˆ˜์ • ๋กœ์ง์ธ "UpdateProfileView" ๊ฒฝ์šฐ, ์ˆ˜์ •์ด ์™„๋ฃŒ๋˜๋ฉด ์„ฑ๊ณต ๋ฉ”์‹œ์ง€๋ฅผ ๋ณด์—ฌ์ค„ ์ˆ˜ ์žˆ์ฃ .
  • "SuccessMessageMixin"๋Š” ์•„๋ž˜ ์œ„์น˜์— ์žˆ์–ด์š”.
    • ๐Ÿ”Ž from django.contrib.messages.views import SuccessMessageMixin
  • "success_message"์— ์„ฑ๊ณต์‹œ ๋‚˜ํƒ€๋‚ผ ๋ฉ”์‹œ์ง€๋ฅผ ์ž…๋ ฅํ•ด์ค๋‹ˆ๋‹ค.
    • ๐Ÿ”Ž success_message = "Update Profile"
from django.contrib.messages.views import SuccessMessageMixin  # ๐Ÿ‘ˆ import
...
...
class UpdateProfileView(SuccessMessageMixin, UpdateView):  # ๐Ÿ‘ˆ success_message ์ƒ์†
    model = models.User
    template_name = "users/update-profile.html"
    fields = (
        "first_name",
        "last_name",
        "gender",
        "bio",
        "birthdate",
        "language",
        "currency",
    )
    success_message = "Update Profile" # ๐Ÿ‘ˆ success_message ์‚ฌ์šฉ
    def get_object(self, queryset=None):
        return self.request.user
    def get_form(self):
        form = super().get_form()
        form.fields["first_name"].widget.attrs = {"placeholder": "First name"}
        form.fields["last_name"].widget.attrs = {"placeholder": "Last name"}
        form.fields["gender"].widget.attrs = {"placeholder": "Gender"}
        form.fields["bio"].widget.attrs = {"placeholder": "bio"}
        form.fields["birthdate"].widget.attrs = {"placeholder": "Birthdate"}
        form.fields["language"].widget.attrs = {"placeholder": "Language"}
        form.fields["currency"].widget.attrs = {"placeholder": "Currency"}
        return form

6) UserPassesTestMixin & LoginRequiredMixin

  • ์‚ฌ์šฉ์ž๊ฐ€ ๋กœ๊ทธ์ธ๋œ ์ƒํƒœ์—์„œ๋Š” ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€์™€ ํšŒ์›๊ฐ€์ž… ํŽ˜์ด์ง€์˜ ๋งํฌ๋‚˜ ๋‚˜ํƒ€๋‚˜์ง€ ์•Š์ฃ . ๋‹ค๋งŒ, ์‚ฌ์šฉ์ž๊ฐ€ url ์ฃผ์†Œ๋ฅผ ์•Œ๊ณ  ์žˆ๋‹ค๋ฉด ์ž…๋ ฅํ•˜์—ฌ ์ ‘๊ทผํ• ํ…๋ฐ์š”,, ์‚ฌ์šฉ์ž๊ฐ€ login์ธ ์ƒํƒœ์ผ ๋•Œ ์ด๋Ÿฐ url์„ ํ†ตํ•œ ์ ‘๊ทผ์„ ๋ชปํ•˜๊ฒŒํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด "UserPassesTestMixin" ๊ธฐ๋Šฅ์œผ๋กœ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์–ด์š”.
  • ๋˜ํ•œ ๋กœ๊ทธ์ธํ•˜์ง€ ์•Š์€ ์‚ฌ์šฉ์ž๊ฐ€ url ์ฃผ์†Œ๋ฅผ ์ž…๋ ฅํ•˜์—ฌ ์ง์ ‘ ์ ‘๊ทผํ–ˆ์„ ๋•Œ๋Š” ์ง„์ž…ํ•˜์ง€ ๋ชปํ•˜๊ฒŒํ•˜๊ณ  Login์„ ํ•  ์ˆ˜ ์žˆ๋„๋ก login ํŽ˜์ด์ง€๋กœ ์ด๋™์‹œํ‚ฌ ๋•Œ "LoginRequiredMixin"๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
  • ์šฐ์„  users ๋””๋ ‰ํ† ๋ฆฌ ๋‚ด์— "mixin.py"๋ฅผ ์ƒ์„ฑํ•ด class๋ฅผ ์ƒ์„ฑํ•˜๊ณ  "UserPassesTestMixin"์™€ "LoginRequiredMixin"๋ฅผ ์ƒ์†๋ฐ›์Šต๋‹ˆ๋‹ค.
  • UserPassesTestMixin"๋ฅผ ์ƒ์†๋ฐ›์€ LoggedOutOnlyView์˜ "test_func" ๋งค์„œ๋“œ๊ฐ€ False๋ผ๋ฉด, "handle_no_permission" ๋งค์„œ๋“œ๊ฐ€ ์‹คํ–‰๋˜ ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋‚˜ ํšŒ์›๊ฐ€์ž… ํŽ˜์ด์ง€๋กœ ์ด๋™ํ•˜์ง€ ์•Š๊ณ  ๋ฉ”์ธ ํŽ˜์ด์ง€๋กœ ์ด๋™ํ•ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ,, "messages.error()" ๋งค์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด, ๋ฉ”์‹œ์ง€๋„ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ์–ด์š”:)
  • "LoginRequiredMixin"๋ฅผ ์ƒ์†๋ฐ›์€ "LoggedInOnlyView"๊ฐ€ ์‹คํ–‰๋œ๋‹ค๋ฉด, login_url๋กœ redirect ๋ฉ๋‹ˆ๋‹ค.
# users/mixin.py
from django.shortcuts import redirect # ๐Ÿ‘ˆ import
from django.urls import reverse_lazy  # ๐Ÿ‘ˆ import
from django.contrib import messages # ๐Ÿ‘ˆ import
from django.contrib.auth.mixins import UserPassesTestMixin # ๐Ÿ‘ˆ import
class LoggedOutOnlyView(UserPassesTestMixin): # ๐Ÿ‘ˆ Login๋œ ์‚ฌ์šฉ์ž์˜ ํ˜ผ์ž…์„ ๋ง‰์„ ๋•Œ,
    def test_func(self):
        return not self.request.user.is_authenticated # ๐Ÿ‘ˆ Login๋˜์—ˆ๋‹ค๋ฉด, False ๋ฐ˜ํ™˜
    def handle_no_permission(self): # ๐Ÿ‘ˆ test_func๊ฐ€ False์ผ ๋•Œ,
        messages.error(self.request, "Can't go there")
        return redirect("core:home")
class LoggedInOnlyView(LoginRequiredMixin):  # ๐Ÿ‘ˆ Login๋˜์ง€ ์•Š์€ ์‚ฌ์šฉ์ž๋กœ๋ถ€ํ„ฐ url ์ ‘๊ทผ์„ ๋ง‰์„๋•Œ,,
    login_url = reverse_lazy("users:login")        
  • ๋กœ๊ทธ์ธ ๊ธฐ๋Šฅ๊ณผ ํšŒ์›๊ฐ€์ž… ๊ธฐ๋Šฅ์— "LoggedOutOnlyView"์„ ์ƒ์†๋ฐ›๊ณ , Profile Update ๊ธฐ๋Šฅ๊ณผ ๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณ€๊ฒฝ ๊ธฐ๋Šฅ์— "LoggedInOnlyView"๋ฅผ ์ƒ์†๋ฐ›์•„ ์ค๋‹ˆ๋‹ค.
...
...
from . import forms, models, mixin # ๐Ÿ‘ˆ import
class LoginView(mixin.LoggedOutOnlyView, FormView): ๐Ÿ‘ˆ # mixin.LoggedOutOnlyView ์ƒ์†
    template_name = "users/login.html"
    form_class = forms.LoginForm
    success_url = reverse_lazy("core:home")
    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:
            login(self.request, user)
        return super().form_valid(form)
...
...
class SignUpView(mixin.LoggedOutOnlyView, FormView): ๐Ÿ‘ˆ # mixin.LoggedOutOnlyView ์ƒ์†
    template_name = "users/signup.html"
    form_class = forms.SignUpForm
    success_url = reverse_lazy("core:home")
    def form_valid(self, form):
        form.save()
        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:
            login(self.request, user)
        user.verify_email()
        return super().form_valid(form)
def complete_verification(request, key):
    try:
        user = models.User.objects.get(email_secret=key)
        user.email_verified = True
        user.email_secret = ""
        user.save()
        # to do: add succes message
    except models.User.DoesNotExist:
        # to do: add error message
        pass
    return redirect(reverse("core:home"))
...
...
class UpdateProfileView(mixin.LoggedInOnlyView, SuccessMessageMixin, UpdateView): ๐Ÿ‘ˆ # mixin.LoggedInOnlyView ์ƒ์†
    model = models.User
    template_name = "users/update-profile.html"
    fields = (
        "first_name",
        "last_name",
        "gender",
        "bio",
        "birthdate",
        "language",
        "currency",
    )
...
...
class UpdatePasswordView(mixin.LoggedInOnlyView, SuccessMessageMixin, PasswordChangeView): ๐Ÿ‘ˆ # mixin.LoggedInOnlyView ์ƒ์†
    template_name = "users/update-password.html"
    success_message = "Password Updated"
    def get_form(self):
        form = super().get_form()
        form.fields["old_password"].widget.attrs = {"placeholder": "Current password"}
        form.fields["new_password1"].widget.attrs = {"placeholder": "New password"}
        form.fields["new_password2"].widget.attrs = {
            "placeholder": "Confirm new password"
        }
        return form
    def get_success_url(self):
        return self.request.user.get_absolute_url()
...
...

profile
Keep Going, Keep Coding!

0๊ฐœ์˜ ๋Œ“๊ธ€