코드를 입력하세요
해당 뷰를 로컬서버에서 실행시켜본다면
=> 이런식으로 기본값으로는 admin 페이지로 return 하도록 설정돼있음,
=> 우리는 이 화면을 user가 html 에서 보게하고 싶기 때문에 form을 상속받고 view를 커스터마이징해서 수정해줄 예정
-> 이 파일위치들이 명확하게 나온 데가 없어서 찾느라 시간을 많이 썼다
-> 이 html 파일들을 각자 커스터마이즈 해주면 된다.
-> 나는 유저가 비밀번호 찾기 위한 이메일을 쳤을 때, 해당하는 이메일로 가입된 유저가 없다면 그 html 페이지에서 에러메시지를 (빨갛게) 띄워주는 것이 목표다
내가 이전에 작성한 회원가입 중복 이메일 에러 핸들링 글과 유사하다.
내가 설계한 방향
- password_reset : 비밀번호 수정
views.py
class PasswordResetView(PasswordContextMixin, FormView): email_template_name = 'registration/password_reset_email.html' extra_email_context = None form_class = PasswordResetForm from_email = None html_email_template_name = None subject_template_name = 'registration/password_reset_subject.txt' success_url = reverse_lazy('password_reset_done') template_name = 'registration/password_reset_form.html' title = _('Password reset') token_generator = default_token_generator @method_decorator(csrf_protect) def dispatch(self, *args, **kwargs): return super().dispatch(*args, **kwargs) def form_valid(self, form): if UserModel.objects.filter(email=self.request.POST.get("email")).exists(): opts = { 'use_https': self.request.is_secure(), 'token_generator': self.token_generator, 'from_email': self.from_email, 'email_template_name': self.email_template_name, 'subject_template_name': self.subject_template_name, 'request': self.request, 'html_email_template_name': self.html_email_template_name, 'extra_email_context': self.extra_email_context, } form.save(**opts) return render(self.request, 'registration/password_reset_done.html', {'form' : form}) #return super().form_valid(form) else : return render(self.request, 'registration/password_reset_done_fail.html', {'form' : form})
password_reset_form.html
{% block title %}Forgot Your Password?{% endblock %}
<form method="POST">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="비밀번호 재설정 요청">
<input type="submit" value="확인메일 재전송">
</form>
{% block title %}Forgot Your Password?{% endblock %}
<form method="POST">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="비밀번호 재설정 요청">
<input type="submit" value="확인메일 재전송">
</form>
<script>
window.alert("입력해주신 이메일로 비밀번호 재전송 이메일을 보냈습니다");
</script>
windows.alert() 참고!
https://developer.mozilla.org/ko/docs/Web/API/Window/alert
- password_reset_done_fail : 입력한 메일로 가입된 유저 없음 경고글 띄워주기
<form method="POST">
{% csrf_token %}
{% for field in form %}
<div>
{{ field.label }} : {{ field }}
<p style="color:red;">해당 이메일로 가입된 유저가 없습니다.</p>
</div>
{% endfor %}
<button type="submit">비밀번호 재설정 요청</button><br>
<button type="submit">확인 메일 재전송</button>
</form>
=> 이메일 오면
password_reset_confirm.html
{% if validlink %}
<h1>Set a new password!</h1>
<form method="POST">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="비밀번호 재설정">
</form>
{% else %}
<p>유효하지 않은 링크입니다</p>
{% endif %}
password_reset_complete.html
{% block title %}Password reset complete{% endblock %}
<h1>재설정 완료</h1>
<p><a href="{% url 'email_login' %}">로그인 하러 가기</a></p>
=> 따라서 나는 VIEWS.PY
의 관련 뷰들과 관련 HTML 파일들을 모두 내 프로젝트>USER앱 안에다가 데려옴
urls.py
path('password_reset/', PasswordResetView.as_view(), name="password_reset"),
path('password_reset_done/', PasswordResetDoneView.as_view(), name="password_reset_done"),
path('password_reset_confirm/<uidb64>/<token>/', PasswordResetConfirmView.as_view(), name="password_reset_confirm"),
path('password_reset_complete/', PasswordResetCompleteView.as_view(), name="password_reset_complete"),
views.py
from django.conf import settings
from django.contrib.auth.forms import (
PasswordResetForm, SetPasswordForm,
)
from django.contrib.auth.tokens import default_token_generator
from django.core.exceptions import ImproperlyConfigured, ValidationError
from django.http import HttpResponseRedirect
from django.shortcuts import render, resolve_url
from django.urls import reverse_lazy
from django.utils.decorators import method_decorator
from django.utils.http import (
urlsafe_base64_decode,
)
from django.utils.translation import gettext_lazy as _
from django.views.decorators.cache import never_cache
from django.views.decorators.csrf import csrf_protect
from django.views.decorators.debug import sensitive_post_parameters
from django.views.generic.base import TemplateView
from django.views.generic.edit import FormView
from django.contrib.auth.forms import SetPasswordForm
from django.contrib.auth import authenticate, login, logout
from django.core.exceptions import ValidationError
from django.shortcuts import render, redirect, resolve_url
from django.conf import settings
from django.urls import reverse_lazy
from .models import CustomUser
from .forms import *
import requests
# Class-based password reset views
# - PasswordResetView sends the mail
# - PasswordResetDoneView shows a success message for the above
# - PasswordResetConfirmView checks the link the user clicked and
# prompts for a new password
# - PasswordResetCompleteView shows a success message for the above
class PasswordContextMixin:
extra_context = None
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update({
'title': self.title,
**(self.extra_context or {})
})
return context
class PasswordResetView(PasswordContextMixin, FormView):
email_template_name = 'registration/password_reset_email.html'
extra_email_context = None
form_class = PasswordResetForm
from_email = None
html_email_template_name = None
subject_template_name = 'registration/password_reset_subject.txt'
success_url = reverse_lazy('password_reset_done')
template_name = 'registration/password_reset_form.html'
title = _('Password reset')
token_generator = default_token_generator
@method_decorator(csrf_protect)
def dispatch(self, *args, **kwargs):
return super().dispatch(*args, **kwargs)
def form_valid(self, form):
if CustomUser.objects.filter(email=self.request.POST.get("email")).exists():
opts = {
'use_https': self.request.is_secure(),
'token_generator': self.token_generator,
'from_email': self.from_email,
'email_template_name': self.email_template_name,
'subject_template_name': self.subject_template_name,
'request': self.request,
'html_email_template_name': self.html_email_template_name,
'extra_email_context': self.extra_email_context,
}
form.save(**opts)
return render(self.request, 'registration/password_reset_done.html', {'form' : form})
#return super().form_valid(form)
else :
return render(self.request, 'registration/password_reset_done_fail.html', {'form' : form})
INTERNAL_RESET_SESSION_TOKEN = '_password_reset_token'
class PasswordResetDoneView(PasswordContextMixin, TemplateView):
template_name = 'registration/password_reset_done.html'
title = _('Password reset sent')
class PasswordResetConfirmView(PasswordContextMixin, FormView):
form_class = SetPasswordForm
post_reset_login = False
post_reset_login_backend = None
reset_url_token = 'set-password'
success_url = reverse_lazy('password_reset_complete')
template_name = 'registration/password_reset_confirm.html'
title = _('Enter new password')
token_generator = default_token_generator
@method_decorator(sensitive_post_parameters())
@method_decorator(never_cache)
def dispatch(self, *args, **kwargs):
if 'uidb64' not in kwargs or 'token' not in kwargs:
raise ImproperlyConfigured(
"The URL path must contain 'uidb64' and 'token' parameters."
)
self.validlink = False
self.user = self.get_user(kwargs['uidb64'])
if self.user is not None:
token = kwargs['token']
if token == self.reset_url_token:
session_token = self.request.session.get(INTERNAL_RESET_SESSION_TOKEN)
if self.token_generator.check_token(self.user, session_token):
# If the token is valid, display the password reset form.
self.validlink = True
return super().dispatch(*args, **kwargs)
else:
if self.token_generator.check_token(self.user, token):
# Store the token in the session and redirect to the
# password reset form at a URL without the token. That
# avoids the possibility of leaking the token in the
# HTTP Referer header.
self.request.session[INTERNAL_RESET_SESSION_TOKEN] = token
redirect_url = self.request.path.replace(token, self.reset_url_token)
return HttpResponseRedirect(redirect_url)
# Display the "Password reset unsuccessful" page.
return self.render_to_response(self.get_context_data())
def get_user(self, uidb64):
try:
# urlsafe_base64_decode() decodes to bytestring
uid = urlsafe_base64_decode(uidb64).decode()
user = CustomUser._default_manager.get(pk=uid)
except (TypeError, ValueError, OverflowError, CustomUser.DoesNotExist, ValidationError):
user = None
return user
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['user'] = self.user
return kwargs
def form_valid(self, form):
user = form.save()
del self.request.session[INTERNAL_RESET_SESSION_TOKEN]
if self.post_reset_login:
auth_login(self.request, user, self.post_reset_login_backend)
return super().form_valid(form)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
if self.validlink:
context['validlink'] = True
else:
context.update({
'form': None,
'title': _('Password reset unsuccessful'),
'validlink': False,
})
return context
class PasswordResetCompleteView(PasswordContextMixin, TemplateView):
template_name = 'registration/password_reset_complete.html'
title = _('Password reset complete')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['login_url'] = resolve_url(settings.LOGIN_URL)
return context
(+) django 의 의미
[ 스택오버플로우 글](https://stackoverflow.com/questions/41075989/pythondjango-what-does-mean)
It is the opposite of your second example. When in the definition of a function, the ** operator gathers all the named arguments and makes a dictionary. When calling a function, it takes a dictionary and breaks it into named arguments
values = {'x': 1, 'y': 2}
f(**values)
f(x=1, y=2)
(+) url reverse
설명 출처 : https://my-repo.tistory.com/29
- 하나는 def형 뷰에서는 reverse()를 그대로 사용하고, class형 뷰에서는 reverse_lazy()라는 함수를 사용한다.
왜 그러냐면, Class-level 속성을 가지는 객체들은 import 될 때 배치되는데 URL-seolving 규칙은 import 시간에 세팅되어있지 않아 class 내에서 reverse()를 사용하면 URLconf의 규칙을 인식하지 못한다.
따라서 class내에서 사용되는 reverse()함수는 URL 규칙을 적용하지 못하여 Included URLconf “does not appear to have any patterns in it” 에러을 뿜어내며 서버가 죽어버린다.
그러나 def 속성 객체는 import 이후에 배치되므로 URLconf의 규칙을 적용할 수 있다.- 그러므로 class 형 뷰를 사용할때는 from django.urls import reverse_lazy 로 reverse_lazy() 함수를 import 해서 사용해주자.