In django, for class-based view like ListView and DetailView, methods like get() or post() or other functions defined by developer take parameters include self and request. I learnt that in self these is actually a self.request field, so wha's the difference between self.request and request?
Example, this is the function in a class based view and used to handle user's login requirement:
def login(self, request):
name = request.POST['name']
pwd = request.POST['password']
user = authenticate(username=name, password=pwd)
if user is not None:
request.session.set_expiry(0)
login(request, user)
log_message = 'Login successfully.'
else:
log_message = 'Fail to login.'
return HttpResponseRedirect(reverse('blog:testindex'))
This is the function used to handle user's register:
def register(self, request):
user_name = self.request.POST['username']
firstname = self.request.POST['firstname']
lastname = self.request.POST['lastname']
pwd = self.request.POST['password']
e_mail = self.request.POST['email']
user = User.objects.create(username=user_name, first_name=firstname, last_name=lastname, email=e_mail)
user.set_password(pwd)
try:
user.save()
user = authenticate(username=user_name, password=pwd)
login(self.request, user)
except Exception:
pass
else:
return HttpResponseRedirect(reverse('blog:testindex'))
In the first function, it used data stored in request and in the second one, it used self.request, both work functionally. What's the difference?
For a subclass of View, they're the same object. self.request = request is set in view function that as_view() returns. I looked into the history, but only found setting self.request and then immediately passing request into the view function.
Related
I'm new to Django Class Based Views and I can't get my form to pass through neither form_valid() nor form_invalid().
I have taken most of this code from the Django allauth module, so I extend some mixins (AjaxCapableProcessFormViewMixin & LogoutFunctionalityMixin) that I do not know well.
This form is meant to allow users to change their passwords upon receiving an email. As it is now, users are able to change their password but since the form_valid() function is never triggered, they do no get redirected to the success URL as is intended. Instead the password change is registered but the users stay on the same page.
The functions dispatch(), get_form_kwargs() & get_form_class() are all triggered and behave in the way that they should. Still, it's unclear to me why they execute in the order that they do (dispatch() is triggered first, then get_form_class() and finally get_form_kwargs(). I suppose they implicitely have an order as presented in this documentation: https://ccbv.co.uk/projects/Django/4.0/django.views.generic.edit/FormView/)
I am lacking some intuition about how this works, therefore I don't know if there is a way to redirect to the success URL without passing through form_valid() because that would also solve my problem.
As is mentionned in the title, neither form_valid() nor form_invalid() is triggered after submitting a new password. The last executed bit of code is the return kwargs from the get_form_kwargs() function.
Here is my code:
class PasswordResetFromKeyView(AjaxCapableProcessFormViewMixin, LogoutFunctionalityMixin, FormView):
template_name = "account/password_reset_from_key." + app_settings.TEMPLATE_EXTENSION
form_class = ResetPasswordKeyForm
success_url = '/'
reset_url_key = "set-password"
def get_form_class(self):
return get_form_class(
app_settings.FORMS, "reset_password_from_key", self.form_class
)
def dispatch(self, request, uuid, **kwargs):
self.request = request
token = get_object_or_404(ResetToken, token=uuid)
if token.redeemed == False:
self.reset_user = token.client
self.token = token
response = self.render_to_response(self.get_context_data(token_fail=False))
else:
return super(PasswordResetFromKeyView, self).dispatch(
request, uuid, **kwargs
)
return response
def get_form_kwargs(self, **kwargs):
kwargs = super(PasswordResetFromKeyView, self).get_form_kwargs(**kwargs)
kwargs["user"] = self.reset_user
if len(kwargs) > 3:
try:
if kwargs['data']['password1'] == kwargs['data']['password2']:
self.reset_user.set_password(kwargs['data']['password1'])
self.reset_user.save()
self.token.redeemed = True
self.token.date_redeemed = datetime.now()
self.token.save()
perform_login(
self.request,
self.reset_user,
email_verification=app_settings.EMAIL_VERIFICATION,
)
else:
pass
##passwords dont match
except:
##couldnt change the password
pass
return kwargs
def form_valid(self, form, **kwargs):
form.save()
return super(PasswordResetFromKeyView, self).form_valid(form)
def form_invalid(self, form):
response = super().form_invalid(form)
if self.request.accepts('text/html'):
return response
else:
return JsonResponse(form.errors, status=400)
If both methods are not triggered, it means - you requests.method is never is 'POST'.
The class FormView calls this two methods only if post.method == 'POST':
# code from django.views.generic.edit
...
def post(self, request, *args, **kwargs):
"""
Handle POST requests: instantiate a form instance with the passed
POST variables and then check if it's valid.
"""
form = self.get_form()
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
By the way in dispatch, if token.redeemed == False you should return self.form_invalid().
I'm creating a single page application that uses Django's session authentication on the backend. Django is using django-allauth for everything authentication-related.
I would like to add an additional step to my login where the user inputs a code and Django must verify that code too, other than password and username. How can i do that?
Note that i'm using Django as an API, so i don't need to edit the form and add another field, i only need to add another check to the authentication backend, so something very easy: if the code is right, than proceed to check username and password too, else return an error.
The problem is that i don't know where to add this check. I think i need to work on the authentication backend, but i'm stuck here.
Here is an example of the login data that django receives:
{'login': 'test', 'password': 'testPass12', 'testToken': '123abc'}
So basically, other than checking login and password like it already does now, it should check if testToken is equal to a specific value.
Here is the allauth authentication backend:
class AuthenticationBackend(ModelBackend):
def authenticate(self, request, **credentials):
ret = None
if app_settings.AUTHENTICATION_METHOD == AuthenticationMethod.EMAIL:
ret = self._authenticate_by_email(**credentials)
elif app_settings.AUTHENTICATION_METHOD == AuthenticationMethod.USERNAME_EMAIL:
ret = self._authenticate_by_email(**credentials)
if not ret:
ret = self._authenticate_by_username(**credentials)
else:
ret = self._authenticate_by_username(**credentials)
return ret
def _authenticate_by_username(self, **credentials):
username_field = app_settings.USER_MODEL_USERNAME_FIELD
username = credentials.get("username")
password = credentials.get("password")
User = get_user_model()
if not username_field or username is None or password is None:
return None
try:
# Username query is case insensitive
user = filter_users_by_username(username).get()
if self._check_password(user, password):
return user
except User.DoesNotExist:
return None
def _authenticate_by_email(self, **credentials):
# Even though allauth will pass along `email`, other apps may
# not respect this setting. For example, when using
# django-tastypie basic authentication, the login is always
# passed as `username`. So let's play nice with other apps
# and use username as fallback
email = credentials.get("email", credentials.get("username"))
if email:
for user in filter_users_by_email(email):
if self._check_password(user, credentials["password"]):
return user
return None
def _check_password(self, user, password):
ret = user.check_password(password)
if ret:
ret = self.user_can_authenticate(user)
if not ret:
self._stash_user(user)
return ret
#classmethod
def _stash_user(cls, user):
"""Now, be aware, the following is quite ugly, let me explain:
Even if the user credentials match, the authentication can fail because
Django's default ModelBackend calls user_can_authenticate(), which
checks `is_active`. Now, earlier versions of allauth did not do this
and simply returned the user as authenticated, even in case of
`is_active=False`. For allauth scope, this does not pose a problem, as
these users are properly redirected to an account inactive page.
This does pose a problem when the allauth backend is used in a
different context where allauth is not responsible for the login. Then,
by not checking on `user_can_authenticate()` users will allow to become
authenticated whereas according to Django logic this should not be
allowed.
In order to preserve the allauth behavior while respecting Django's
logic, we stash a user for which the password check succeeded but
`user_can_authenticate()` failed. In the allauth authentication logic,
we can then unstash this user and proceed pointing the user to the
account inactive page.
"""
global _stash
ret = getattr(_stash, "user", None)
_stash.user = user
return ret
#classmethod
def unstash_authenticated_user(cls):
return cls._stash_user(None)
And here is the allauth login view:
class LoginView(
RedirectAuthenticatedUserMixin, AjaxCapableProcessFormViewMixin, FormView
):
form_class = LoginForm
template_name = "account/login." + app_settings.TEMPLATE_EXTENSION
success_url = None
redirect_field_name = "next"
#sensitive_post_parameters_m
def dispatch(self, request, *args, **kwargs):
return super(LoginView, self).dispatch(request, *args, **kwargs)
def get_form_kwargs(self):
kwargs = super(LoginView, self).get_form_kwargs()
kwargs["request"] = self.request
return kwargs
def get_form_class(self):
return get_form_class(app_settings.FORMS, "login", self.form_class)
def form_valid(self, form):
success_url = self.get_success_url()
try:
return form.login(self.request, redirect_url=success_url)
except ImmediateHttpResponse as e:
return e.response
def get_success_url(self):
# Explicitly passed ?next= URL takes precedence
ret = (
get_next_redirect_url(self.request, self.redirect_field_name)
or self.success_url
)
return ret
def get_context_data(self, **kwargs):
ret = super(LoginView, self).get_context_data(**kwargs)
signup_url = passthrough_next_redirect_url(
self.request, reverse("account_signup"), self.redirect_field_name
)
redirect_field_value = get_request_param(self.request, self.redirect_field_name)
site = get_current_site(self.request)
ret.update(
{
"signup_url": signup_url,
"site": site,
"redirect_field_name": self.redirect_field_name,
"redirect_field_value": redirect_field_value,
}
)
return ret
Django v1.10
FormView code:
class PasswordResetConfirmView(FormView):
template_name = "dashboard/account/reset_password_form.html"
success_url = '/dashboard/'
form_class = SetPasswordForm
def authenticate_password_token(self, request, uidb64=None, token=None, encodedtimestring=None):
try:
uid = force_text(urlsafe_base64_decode(uidb64))
user = User.objects.get(pk=uid)
timestring = force_text(urlsafe_base64_decode(encodedtimestring))
timestamp = timeparse(timestring)
timediff = timezone.now() - timestamp
except (TypeError, ValueError, OverflowError, User.DoesNotExist):
user = None
timediff = None
if timediff is None or timediff.days < 0 or timediff.days > PASSWORD_RESET_TIMEOUT_DAYS:
messages.error(request, _(
'The reset password link is no longer valid.'))
return None
if user is None or not default_token_generator.check_token(user, token):
messages.error(request, _('The reset password link is not valid.'))
return None
return user
def get(self, request, uidb64=None, token=None, encodedtimestring=None, *arg, **kwargs):
form = self.form_class()
assert uidb64 is not None and token is not None and encodedtimestring is not None
user = self.authenticate_password_token(
request, uidb64, token, encodedtimestring)
if user is None:
return redirect(reverse('dashboard-login'))
return self.render_to_response(self.get_context_data(form=form))
def post(self, request, uidb64=None, token=None, encodedtimestring=None, *arg, **kwargs):
form = self.form_class(request.POST)
assert uidb64 is not None and token is not None and encodedtimestring is not None
user = self.authenticate_password_token(
request, uidb64, token, encodedtimestring)
if user is None:
return redirect(reverse('dashboard-login'))
if not form.is_valid():
return self.form_invalid(form)
new_password = form.cleaned_data['new_password2']
try:
with transaction.atomic():
user.auth_token.delete()
Token.objects.create(user=user)
user.set_password(new_password)
user.save()
except:
messages.error(request, _('Password reset was unsuccessful.'))
return redirect(reverse('dashboard-login'))
messages.success(request, _('Password has been reset.'))
return redirect(reverse('dashboard-login'))
urls.py:
url(r'^(?i)recover/password/(?P<uidb64>[0-9A-Za-z]+)/(?P<token>.+)/(?P<encodedtimestring>.+)/$',
views.PasswordResetConfirmView.as_view(), name='reset-password-confirm'),
testclass parent:
class BaseApiTest(TestCase):
def setUp(self):
superuser = User.objects.create_superuser(
'test', 'test#api.com', 'testpassword')
self.factory = RequestFactory()
self.user = superuser
self.client.login(username=superuser.username, password='testpassword')
My attempt at writing the test case:
class ResetPasswordEmailTest(BaseApiTest):
def test_password_reset_form(self):
"""
Ensure that the authenticate token works
"""
self.client.logout()
token = default_token_generator.make_token(self.user)
uidb64 = force_bytes(self.user.id)
timenow = force_bytes(timezone.now())
response = self.client.get(
reverse('reset-password-confirm',
args=[urlsafe_base64_encode(uidb64), token,
urlsafe_base64_encode(timenow)]))
self.assertEqual(response.status_code, status.HTTP_200_OK)
Error message that I got:
tests/password_tests.py", line 129, in test_password_reset_form
self.assertEqual(response.status_code, status.HTTP_200_OK)
AssertionError: 302 != 200
Am not sure how to write a test class to test all 3 methods of the formview. My attempt was just to test the get method
UPDATE:
The real reason for the failure has nothing to do with the user login but somehow the check_token method inherent in the PasswordTokenGenerator was failing my tests.
And as I do more research, I think it would be better that I upgrade Django v1.10 to v1.11 where I need to rewrite this whole thing which may end up invalidating the need for this question.
Your get method of PasswordResetConfirmView is responding with 2 different responses.
Redirect
When user is None it is responding with a Redirect URI
if user is None:
return redirect(reverse('dashboard-login'))
So in that case the status code of response will be HTTP 302 means
that you are redirected to some other URL.
Render to response
When there is user, then the information returned with the response is the method used in the request.
return self.render_to_response(self.get_context_data(form=form))
So in that case the status code of response will be HTTP 200 means
the request has succeeded.
Solution:
In order to make your test case pass you can use assertRedirects
def test_password_reset_form(self):
...
# A URL that redirects can be followed to termination.
response = self.client.get(reverse('reset-password-confirm', args=[...]), follow=True)
self.assertRedirects(response, reverse('dashboard-login'), status_code=302, target_status_code=200)
self.assertEqual(len(response.redirect_chain), 2)
If your request used the follow argument, the expected_url and target_status_code will be the url and status code for the final point of the redirect chain.
Reference:
https://github.com/django/django/blob/master/tests/test_client/tests.py
Alternatively you can create two separate test cases (when user is logged-in, when user is not logged-in) and use assertEqual
# Test for logged in user.
def test_password_reset_form_for_logged_in_user(self):
...
# do stuff
self.assertEqual(response.status_code, status.HTTP_200_OK)
# Test if user not logged in.
def test_password_reset_form_if_user_not_logged_in(self):
...
# do stuff
self.assertEqual(response.status_code, status.HTTP_302_FOUND)
Problem (Post Mortem):
Let's start with defining the problem:
Status 302:
The requested resource resides temporarily under a different URI. -source
So that leads us to assume that you are getting redirected thus the test fails.
Where this happens:
In your get() method we find this code:
if user is None:
return redirect(reverse('dashboard-login'))
So if the user is Anonymous (Unauthenticated) the view redirects him to the login page.
From the above, we can state that the problem occurs when you make an unauthenticated request to PasswordResetConfirmView.get().
Solution:
You need to authenticate (login) your user before you make the request.
You can do this by utilizing the force_login() method:
If your site uses Django’s authentication system, you can use the force_login() method to simulate the effect of a user logging into the site. Use this method instead of login() when a test requires a user be logged in and the details of how a user logged in aren’t important.
def my_test_case():
... Do stuff ...
# Login
self.client.force_login(self.user)
# Make the request
response = self.client.get(...)
I am using Django-registration and I have subclassed the BaseRegistrationView to customised a backend.
However, the HttpResponseRedirect fail to redirect to a new page when the condition is met.
The code is listed below:
class CustomRegistrationView(BaseRegistrationView):
def register(self, request, **cleaned_data):
captchaMatch = False
#check captcha
captchaValue = request.session.get('captcha')
captchaValueSubmitted = cleaned_data['captcha'] + "123"
if captchaValue != captchaValueSubmitted:
return HttpResponseRedirect(reverse('InvitationRequired'))
else:
self.captchaMatch = True
username, email, password = cleaned_data['username'], cleaned_data['email'], cleaned_data['password1']
if Site._meta.installed:
site = Site.objects.get_current()
else:
site = RequestSite(request)
new_user = RegistrationProfile.objects.create_inactive_user(username, email,
password, site)
signals.user_registered.send(sender=self.__class__,
user=new_user,
request=request)
return new_user
def registration_allowed(self, request):
"""
Indicate whether account registration is currently permitted,
based on the value of the setting ``REGISTRATION_OPEN``. This
is determined as follows:
* If ``REGISTRATION_OPEN`` is not specified in settings, or is
set to ``True``, registration is permitted.
* If ``REGISTRATION_OPEN`` is both specified and set to
``False``, registration is not permitted.
"""
return getattr(settings, 'REGISTRATION_OPEN', True)
def get_success_url(self, request, user):
"""
Return the name of the URL to redirect to after successful
user registration.
"""
print "get successful url"
if self.captchaMatch:
return ('registration_complete', (), {})
else:
return HttpResponseRedirect(reverse('InvitationRequired'))
Can anyone help explain why the redirection is executed inside the function register ?
I guess the register method is supposed to return a user or None. Not an HttpRedirect object. Since I'm not familiar with this specific app, try to see how they handle failure on the original method and follow that policy (e.b. return None or raise an Exception).
Also, based on the comments on the get_success_url() method, I would expect the code to be like this:
def get_success_url(self, request, user):
"""
Return the name of the URL to redirect to after successful
user registration.
"""
if self.captchaMatch:
return ('registration_complete', (), {})
else:
return ('InvitationRequired', (), {})
I guess you should not return a HttpResponse instance, as the method is used to get the URL to be redirect to (not the redirection itself).
How to get the session_key in form Class? It is not possible to get the request parameter to do something like this : request.user
I've got this situation, and I need to pass to function get_user session_key which is also
not possible to retrieve from request.
class CustomEntryAdminForm(EntryAdminForm):
def get_user(session_key):
session = Session.objects.get(session_key=session_key)
uid = session.get_decoded().get('_auth_user_id')
user = User.objects.get(pk=uid)
return user
categories = MPTTModelMultipleChoiceField(
label=_('Categories'), required=False,
queryset=Category.objects.filter(groups__in=get_user('uwry5olhemchxnmwa36v10zt2bg9zeci').groups.all()),
widget=MPTTFilteredSelectMultiple(_('categories'), False,
attrs={'rows': '10'}))
Use pass user as keyword argument to your form. You do not need to jump through hoops and load active session key from request and then load user from decoded session. All you need to do is:
in view:
myform = MyFormClass(user= request.user)
in form:
class MyFormClass(forms.Form):
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user', None)
super(MyFormClass, self).__init__(*args, **kwargs)
self.fields['categories'].queryset = Category.objects.filter(groups__in = self.user.groups.all())
NB! not complete working code. I just wanted to show you how you can use the self.user in queryset.