How to override a form in django-rest-auth with my

2019-08-30 00:32发布

问题:

I'm using django-rest-auth and I am trying to fix a bug in the password reset view by overwriting one of the form's methods. Although I've successfully done something similar with a different django-rest-auth form, I can't make it work on this one. Whatever I do, the old form is used.

api/urls.py

from django.urls import include, path
from django.contrib.auth import views
from django.conf.urls import include, url
from django.views.generic.base import RedirectView
from .forms import SetPasswordFormCustom
from .forms import PasswordResetFormCustom

urlpatterns = [
    path('password/reset/',
        views.PasswordResetView.as_view(form_class=PasswordResetFormCustom),
        name='rest_password_reset'),
    path('rest-auth/', include('rest_auth.urls')),
    path('rest-auth/registration/', include('rest_auth.registration.urls')),
    path('users/', include('users.urls')),
    path('reset/<uidb64>/<token>/',
        views.PasswordResetConfirmView.as_view(template_name='account/password_reset_confirm.html', form_class=SetPasswordFormCustom),
        name='password_reset_confirm'),
    path('reset/done/', views.PasswordResetCompleteView.as_view(template_name='account/password_reset_complete.html'),
        name='password_reset_complete'),
    path('content/', include('lists.endpoints')),
    # content is a path for lists, items etc found in the lists app
]

forms.py

from django import forms
from django.contrib.auth.forms import SetPasswordForm
from django.contrib.auth import (
    authenticate, get_user_model, password_validation,
)
from django.utils.translation import gettext, gettext_lazy as _
#from django.contrib.auth import password_validation

from django.contrib.auth.forms import PasswordResetForm
UserModel = get_user_model()

class SetPasswordFormCustom(SetPasswordForm):
    new_password1 = forms.CharField(
        label=_("New password"),
        widget=forms.PasswordInput(attrs={'class':'form-control','placeholder':'Password'}),
        strip=False,
    )
    new_password2 = forms.CharField(
        label=_("Confirm new password"),
        strip=False,
        widget=forms.PasswordInput(attrs={'class':'form-control','placeholder':'Confirm password'}),
        help_text=password_validation.password_validators_help_text_html(),
    )

class PasswordResetFormCustom(PasswordResetForm):
    def get_users(self, email):
        print('using custom form');
        """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())

So, what I can't figure out is that PasswordResetFormCustom with its check for active_users, is not used. The line "using custom form" is not printed in the server log when I request a password reset email, and the check is not performed.

My other custom form PasswordResetConfirmView works fine.

There are no errors so it isn't that the imports are incorrect.

Any idea how I can customise the get_users method without having to edit the django-rest-auth file?

Thanks for any help!

Edit: I put yofee's code into a file serializers.py, and also I had to fix the order of my url paths in urls.py. Now it works!

urlpatterns = [ ... path('rest-auth/password/reset/', PasswordResetView.as_view()), path('rest-auth/', include('rest_auth.urls')), ... ]

reset path must come before the include.

回答1:

The solution should be to

  1. not set PasswordResetFormCustom in your view but in your serializer
  2. use the rest_auth's PasswordResetView (not Django's)

Example:

from django.contrib.auth.forms import PasswordResetForm as DjangoPasswordResetForm
from rest_auth.serializers import (
    PasswordResetSerializer as RestAuthPasswordResetSerializer
)
from rest_auth.views import PasswordResetView as RestAuthPasswordResetView


class PasswordResetForm(DjangoPasswordResetForm):
    def get_users(self, email):
        users = tuple(super().get_users(email))
        if users:
            return users
        msg = _('"{email}" was not found in our system.')
        raise ValidationError({'email': msg.format(email=email)})


class PasswordResetSerializer(RestAuthPasswordResetSerializer):
    password_reset_form_class = PasswordResetForm


class PasswordResetView(RestAuthPasswordResetView):
    serializer_class = PasswordResetSerializer

    def __init__(self, *args, **kwargs):
        """Prints the name of the class if it is used."""
        print(self.__class__.__name__)
        super().__init__(*args, **kwargs)