DRF通过邮箱密码重置
Django + Rest API 通过邮箱进行密码重置
/user/password/reset/ 用邮箱发送验证码
{email: 'handsomeahan515@gmail.com'}
forms.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92import unicodedata
from django import forms
from django.contrib.auth import (
authenticate, get_user_model, password_validation,
)
from django.contrib.auth.hashers import (
UNUSABLE_PASSWORD_PREFIX, identify_hasher,
)
from django.contrib.auth.models import User
from django.contrib.auth.tokens import default_token_generator
from django.contrib.sites.shortcuts import get_current_site
from django.core.mail import EmailMultiAlternatives
from django.forms.utils import flatatt
from django.template import loader
from django.utils.encoding import force_bytes
from django.utils.html import format_html, format_html_join
from django.utils.http import urlsafe_base64_encode
from django.utils.safestring import mark_safe
from django.utils.text import capfirst
from django.utils.translation import ugettext, ugettext_lazy as _
class PasswordResetForm(forms.Form):
email = forms.EmailField(label=_("Email"), max_length=254)
def send_mail(self, subject_template_name, email_template_name,
context, from_email, to_email, html_email_template_name=None):
"""
Sends a django.core.mail.EmailMultiAlternatives to `to_email`.
"""
subject = loader.render_to_string(subject_template_name, context)
# Email subject *must not* contain newlines
subject = ''.join(subject.splitlines())
body = loader.render_to_string(email_template_name, context)
email_message = EmailMultiAlternatives(subject, body, from_email, [to_email])
if html_email_template_name is not None:
html_email = loader.render_to_string(html_email_template_name, context)
email_message.attach_alternative(html_email, 'text/html')
email_message.send()
def get_users(self, email):
"""Given an email, return matching user(s) who should receive a reset.
This allows subclasses to more easily customize the default policies
that prevent inactive users and users with unusable passwords from
resetting their password.
"""
active_users = get_user_model()._default_manager.filter(
username__iexact=email, is_active=True)
return (u for u in active_users if u.has_usable_password())
def save(self, domain_override=None,
subject_template_name='registration/password_reset_subject.txt',
email_template_name='registration/password_reset_email.html',
use_https=False, token_generator=default_token_generator,
from_email=None, request=None, html_email_template_name=None,
extra_email_context=None):
"""
Generates a one-use only link for resetting password and sends to the
user.
"""
email = self.cleaned_data["email"]
for user in self.get_users(email):
if not domain_override:
current_site = get_current_site(request)
site_name = current_site.name
domain = current_site.domain
else:
site_name = domain = domain_override
"""
random verification code
"""
context = {
'email': user.email,
'domain': domain,
'site_name': site_name,
# 'uid': urlsafe_base64_encode(force_bytes(user.pk)),
'vcode': extra_email_context['vcode'],
'user': user,
'token': token_generator.make_token(user),
'protocol': 'https' if use_https else 'http',
}
if extra_email_context is not None:
context.update(extra_email_context)
self.send_mail(
subject_template_name, email_template_name, context, from_email,
user.email, html_email_template_name=html_email_template_name,
)serializers.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47class PasswordResetSerializer(serializers.Serializer):
"""
Serializer for requesting a password reset e-mail.
"""
email = serializers.EmailField()
from .forms import PasswordResetForm
password_reset_form_class = PasswordResetForm
def get_email_options(self):
"""
Override this method to change default e-mail options
"""
email = self.validated_data["email"]
active_user = UserModel._default_manager.filter(
email__iexact=email, is_active=True).first()
vcode = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(6))
active_user.vcode = vcode
active_user.save()
return {
'extra_email_context': {'vcode': vcode},
'subject_template_name': 'registration/subject.txt',
'email_template_name': 'registration/password_reset.html',
}
def validate_email(self, value):
# Create PasswordResetForm with the serializer
self.reset_form = self.password_reset_form_class(
data=self.initial_data)
if not self.reset_form.is_valid():
raise serializers.ValidationError(self.reset_form.errors)
return value
def save(self):
request = self.context.get('request')
# Set some values to trigger the send_email method.
opts = {
'use_https': request.is_secure(),
'from_email': getattr(settings, 'DEFAULT_FROM_EMAIL'),
'request': request,
}
opts.update(self.get_email_options())
self.reset_form.save(**opts)views.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23class PasswordResetView(GenericAPIView):
"""
Calls Django Auth PasswordResetForm save method.
Accepts the following POST parameters: email
Returns the success/fail message.
"""
serializer_class = PasswordResetSerializer
permission_classes = (AllowAny,)
def post(self, request, *args, **kwargs):
# Create a serializer with request.data
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
vcode = serializer.get_email_options()['extra_email_context']['vcode']
# Return the success message with OK HTTP status
return Response(
{"detail": _("Password reset e-mail has been sent."), "code": 1, "vcode": vcode},
status=status.HTTP_200_OK
)
/user/password/reset/confirm/
使用vcode和新密码重新注册密码
{
new_password1: "1qazse4",
new_password2: "1qazse4",
vcode: vcode
}
serializers.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37class PasswordResetConfirmSerializer(serializers.Serializer):
"""
Serializer for requesting a password reset uaername.
"""
new_password1 = serializers.CharField(max_length=128)
new_password2 = serializers.CharField(max_length=128)
vcode = serializers.CharField()
# token = serializers.CharField()
set_password_form_class = SetPasswordForm
def custom_validation(self, attrs):
pass
def validate(self, attrs):
self._errors = {}
# Decode the uidb64 to uid to get User object
try:
self.user = UserModel._default_manager.get(vcode=attrs['vcode'])
except (TypeError, ValueError, OverflowError, UserModel.DoesNotExist):
raise ValidationError({'uid': ['Invalid value']})
self.custom_validation(attrs)
# Construct SetPasswordForm instance
self.set_password_form = self.set_password_form_class(
user=self.user, data=attrs
)
if not self.set_password_form.is_valid():
raise serializers.ValidationError(self.set_password_form.errors)
# if not default_token_generator.check_token(self.user, attrs['token']):
# raise ValidationError({'token': ['Invalid value']})
return attrs
def save(self):
self.set_password_form.save()views.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23class PasswordResetConfirmView(GenericAPIView):
"""
Password reset e-mail link is confirmed, therefore
this resets the user's password.
Accepts the following POST parameters: token, uid,
new_password1, new_password2
Returns the success/fail message.
"""
serializer_class = PasswordResetConfirmSerializer
permission_classes = (AllowAny,)
@sensitive_post_parameters_m
def dispatch(self, *args, **kwargs):
return super(PasswordResetConfirmView, self).dispatch(*args, **kwargs)
def post(self, request):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(
{"detail": _("Password has been reset with the new password.")}
)