django update profile by CBV
url 설정
config/urls.py
urlpatterns = [
path("users", include("users.urls", namespace="users")),
]
users/urls.py
from django.urls import path
from . import views
app_name = "users"
urlpatterns = [path(
"<int:pk>/update-profile/",
views.UpdateProfileView.as_view(),
name="update-profile",
)]
{% url %}
userDetail.html의 일부다. Edit Profile button을 누르면 profile을 수정할 수 있는 page로 간다. url template tag를 이용해서 update_profile page로 갈 수 있게 한다. {% url 'users:update-profile' user.pk %}는 /users/<int:pk>/update-profile/과 같다.
CBV(Class Based View)
users/views.py
CBV는 django에서 정의한 여러가지 View들을 이용한다. 그 중 UpdateView를 이용해서 update profile page를 작성한다. UpdateView는 FormView와는 다르게 forms.py를 작성하지 않고도 View 안에서 fields를 정의할 수 있다. 아니 정의해야만 rendering이 가능하다.
get_form method를 이용해서 form의 속성을 제어할 수 있다.
from django.views.generic import UpdateView
from . import mixins
class UpdateProfileView(mixins.LoginOnlyView, mixins.EmailLoginOnlyView, UpdateView):
model = models.User
fields = {
"avatar",
"first_name",
"last_name",
"email",
"gender",
"language",
"currency",
"birthdate",
"superhost",
"bio",
}
template_name = "pages/users/update_profile.html"
success_message = "Profile Updated"
def get_form(self, form_class=None):
form = super().get_form(form_class=form_class)
form.fields["email"].widget.attrs = {"placeholder": "Email"}
form.fields["first_name"].widget.attrs = {"placeholder": "First name"}
form.fields["last_name"].widget.attrs = {"placeholder": "Last name"}
form.fields["bio"].widget.attrs = {"placeholder": "Bio"}
return form
users/models.py
UpdateView는 user가 profile을 성공적으로 update 했을 때 redirect가 될 url이 필요하다. models.py에서 get_absolute_url() method를 만들면, 해당 url로 redirect가 된다. redirect 대신 reverse를 사용한 이유는 kwargs 인자를 전달하기 위해서다. kwargs로 pk 값을 전달해야 하는데, redirect는 이것이 불가능 하다.
def get_absolute_url(self):
return reverse("users:profile", kwargs={"pk": self.pk})
users/mixins.py
UpdateView가 상속하는 mixins다. mixins는 Route 보안을 담당한다.
from django.contrib.auth.mixins import UserPassesTestMixin, LoginRequiredMixin
from django.contrib import messages
from django.urls import reverse_lazy
from django.shortcuts import redirect, reverse
class LoginOnlyView(LoginRequiredMixin):
login_url = reverse_lazy("users:login")
class EmailLoginOnlyView(UserPassesTestMixin):
permission_denied_message = "Page Not Found"
def test_func(self):
return self.request.user.login_method == "email"
def handle_no_permission(self):
messages.error(self.request, "Can't go there")
return redirect(reverse("core:home"))
templates
pages/users/update_profile.html
코드 길이는 달라지지 않았다. 물론 {{form}} 한 줄로 <form><form/> 안을 압축할 수 있지만 제어를 하기 위해서 일일이 {{form.*}}로 했다.
{% extends 'base.html' %}
{% block page_title %}
Edit {{user.first_name}}'s Profile
{% endblock page_title %}
{% block search-bar %}
<div></div>
{% endblock search-bar %}
{% block content %}
<div class="background">
<div class="wrap">
<div class="flex flex-col items-center justify-center">
<div class="mb-4">
{% include 'mixins/avatar.html' with user=user %}
</div>
{% if user.login_method == "github" %}
<div class="flex items-center mb-4">
<h3 class="font-bold text-base mr-2">Github Email:</h3>
<span>{{user.email}}</span>
</div>
{% elif user.login_method == "kakao" %}
<div class="flex items-center mb-4">
<h3 class="font-bold text-base mr-2">Kakao Email:</h3>
<span>{{user.email}}</span>
</div>
{% endif %}
</div>
<form method="post" class="form" enctype="multipart/form-data">
{% csrf_token %}
<div class="form_input rounded-tl-lg rounded-tr-lg">
<label for="first_name">First Name</label>
{{form.first_name}}
</div>
<div class="form_input">
<label for="last_name">Last Name</label>
{{form.last_name}}
</div>
{% if user.login_method == "email" %}
<div class="form_input">
<label for="email">Email</label>
{{form.email}}
</div>
{% endif %}
{% if user.login_method == "email" %}
<div class="form_input">
<label for="avatar">Avatar</label>
<input type="file" id="avatar" name="avatar" accept="image/*">
</div>
{% endif %}
<div class="select">
<label for="gender">Gender</label>
{{form.gender}}
</div>
<div class="select">
<label for="language">Language</label>
{{form.language}}
</div>
<div class="select">
<label for="currency">Currency</label>
{{form.currency}}
</div>
<div class="birthdate">
<label for="birthdate">Birthdate</label>
<input type="date" id="birthdate" name="birthdate" placeholder="Birthdate" value="{{user.birthdate|date:'Y-m-d'}}" required>
</div>
<div class="form_input rounded-br-lg rounded-bl-lg">
<textarea id="bio" placeholder="Bio" name="bio">{{user.bio}}</textarea>
</div>
<button class="form_button">Edit Profile</button>
</form>
{% if user.login_method == 'email' %}
<button class="button bg-red-500">
<a href="#">Change Password</a>
</button>
{% endif %}
<button class="button bg-gray-700">
<a href="{% url 'users:profile' user.pk %}">Back</a>
</button>
</div>
</div>
{% endblock content %}
참고 자료
- 노마드 코더의 Airbnb 클론 강의
- UpdateView
소스 코드
github.com/zpskek/airbnb-clone-v3/commit/f35e672a49555e4a0c5f9c037d34391bb3406bdf