我已经设置了django-rest-auth来发送密码重置电子邮件。不幸的是,即使邮件实际上没有被发送,API也会返回成功消息“已发送密码重置电子邮件”,例如因为没有该用户使用该电子邮件地址。
我正在使用Mailgun并且可以从日志中看到消息未被发送。
有没有办法让django-rest-auth在没有发送电子邮件时返回失败消息? GitHub项目有一个未解决的问题,所以我不是唯一一个看到这个问题的人。
编辑:我有一个部分解决方案,但仍然无法解决如何从服务器返回合理的响应,而不是创建服务器错误,请参阅下文。
https://github.com/Tivix/django-rest-auth/issues/423
我找到了生成响应的文件:
VENV / lib目录/ python3.6 /站点包/ rest_auth / views.py
class 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()
# Return the success message with OK HTTP status
return Response(
{"detail": _("Password reset e-mail has been sent.")},
status=status.HTTP_200_OK
)
我认为这反过来使用了在venv / lib / python3.6 / site-packages / rest_auth / serializers.py中定义的序列化程序:
class PasswordResetSerializer(serializers.Serializer):
"""
Serializer for requesting a password reset e-mail.
"""
email = serializers.EmailField()
password_reset_form_class = PasswordResetForm
def get_email_options(self):
"""Override this method to change default e-mail options"""
return {}
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)
我发现在venv / lib / python3.6 / site-packages / django / contrib / auth / views.py中引用了PasswordResetForm:
@csrf_protect
def password_reset(request,
template_name='registration/password_reset_form.html',
email_template_name='registration/password_reset_email.html',
subject_template_name='registration/password_reset_subject.txt',
password_reset_form=PasswordResetForm,
token_generator=default_token_generator,
post_reset_redirect=None,
from_email=None,
extra_context=None,
html_email_template_name=None,
extra_email_context=None):
warnings.warn("The password_reset() view is superseded by the "
"class-based PasswordResetView().",
RemovedInDjango21Warning, stacklevel=2)
if post_reset_redirect is None:
post_reset_redirect = reverse('password_reset_done')
else:
post_reset_redirect = resolve_url(post_reset_redirect)
if request.method == "POST":
form = password_reset_form(request.POST)
if form.is_valid():
opts = {
'use_https': request.is_secure(),
'token_generator': token_generator,
'from_email': from_email,
'email_template_name': email_template_name,
'subject_template_name': subject_template_name,
'request': request,
'html_email_template_name': html_email_template_name,
'extra_email_context': extra_email_context,
}
form.save(**opts)
return HttpResponseRedirect(post_reset_redirect)
else:
form = password_reset_form()
context = {
'form': form,
'title': _('Password reset'),
}
if extra_context is not None:
context.update(extra_context)
return TemplateResponse(request, template_name, context)
我对Python不是很熟悉 - 使用框架的全部意义在于避免编写auth等标准函数。但看起来它只是返回成功消息,而没有检查电子邮件是否可以实际发送。
编辑:在使用allauth代码后,我有一个部分解决方案。我在forms.py中更新了表单,如下所示:
修改了forms.py:
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):
"""
Send 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 = UserModel._default_manager.filter(**{
'%s__iexact' % UserModel.get_email_field_name(): email,
'is_active': True,
})
if not active_users:
raise forms.ValidationError(_("The e-mail address is not assigned to any user account"),
code='invalid')
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):
"""
Generate a one-use only link for resetting password and send it to the
user.
"""
email = self.cleaned_data["email"]
for user in self.get_users(email):
print('user')
print(user)
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
context = {
'email': email,
'domain': domain,
'site_name': site_name,
'uid': urlsafe_base64_encode(force_bytes(user.pk)).decode(),
'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,
email, html_email_template_name=html_email_template_name,
)
请注意添加一个检查,然后引发forms.ValidationError。这个问题是服务器然后抛出一个错误并返回500.如何让它返回一个有用的消息,如400的处理错误响应?
我尝试添加自定义干净消息,但仍然有500服务器错误:
def clean_email(self):
email = self.cleaned_data["email"]
email = get_adapter().clean_email(email)
self.users = filter_users_by_email(email)
if not self.users:
raise forms.ValidationError(_("The e-mail address is not assigned"
" to any user account"))
return self.cleaned_data["email"]
非常感谢您的帮助!
你几乎得到了它的工作。缺少的是使用DRF的ValidationError
而不是Django:
from rest_framework.exceptions import ValidationError
有关详细信息,请参阅我的其他answer。