django rest auth возвращает «электронная почта для сброса пароля была отправлена», например, если нет, например, потому что адрес электронной почты не зарегистрирован - PullRequest
0 голосов
/ 11 января 2019

Я настроил django-rest-auth для отправки электронного письма для сброса пароля. К сожалению, API возвращает сообщение об успехе «Электронная почта для сброса пароля была отправлена», даже если сообщение фактически не было отправлено, например, из-за отсутствия пользователя с таким адресом электронной почты.

Я использую Mailgun и вижу из журналов, что сообщение не было отправлено.

Есть ли способ заставить django-rest-auth вернуть сообщение об ошибке, когда электронное письмо не отправлено? Для этого существует открытый вопрос по проекту GitHub, поэтому я не единственный, кто видел эту проблему.

Редактировать: у меня есть частичное решение, но я все еще не могу понять, как вернуть разумный ответ от сервера вместо создания ошибки сервера, см. Ниже.

https://github.com/Tivix/django-rest-auth/issues/423

Я нашел файл, который генерирует ответ:

venv / Библиотека / 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)

Я обнаружил PasswordResetForm, на который ссылается venv / lib / python3.6 / site-packages / django / contrib / auth / views.py:

@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. Но похоже, что он просто возвращает сообщение об успехе, не проверяя, действительно ли письмо может быть отправлено.

Редактировать: после просмотра кода Аллаута у меня есть частичное решение. Я обновил форму в 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"]

Большое спасибо за любую помощь!

1 Ответ

0 голосов
/ 11 марта 2019

У тебя почти получилось. Не хватает только использования DRF ValidationError, а не Django:

from rest_framework.exceptions import ValidationError

Подробнее см. Мой другой ответ .

...