I've been struggling to pass some extra data form my Form class to my views. For a password recovery an user has to fill in an username or e-mail address. When cleaning, the username and password are checked if one of them exists in the database:
def clean(self):
username = self.cleaned_data.get("username")
email = self.cleaned_data.get("email")
if username:
try:
user = User.objects.get(username__iexact=username, is_active=True) # <- to view
except User.DoesNotExist:
raise forms.ValidationError(
self.error_messages["invalid_username"],
code="invalid_username"
)
elif email:
try:
user = User.objects.get(email__iexact=email, is_active=True) # <- to view
except User.DoesNotExist:
raise forms.ValidationError(
self.error_messages["invalid_email"],
code="invalid_email"
)
else:
raise forms.ValidationError(
self.error_messages["empty_form"],
code="empty_form"
)
return self.cleaned_data
When the form has been validated, I want to send the user data to the view. This to separate the send_email logics away from the form and being able to add some information to the context, so it can be rendered in the template.
So in my FormView, if the form is valid, I want to be able to use the user object retrieved in the Form.
Currently I have attempted quite some 'answers' of other SO questions and examples of the web. But I keep getting AttributeErrors, KeyErrors, 'WSGIRequest' object does not support item assignment.
The last attempt I made was to overwrite the init in my Form, and the get_form_kwargs in my view:
Form
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request')
super(RecoverPasswordForm, self).__init__(*args, **kwargs)
def clean(self):
....
self.request["user"] = User.objects.get(username__iexact=username, is_active=True)
View
def get_form_kwargs(self, **kwargs):
kwargs = super(RecoverPassword, self).get_form_kwargs()
kwargs["request"] = self.request
return kwargs
Which leads to the following error
'WSGIRequest' object does not support item assignment
Can somebody give me an explanation of what I'm doing wrong, and push me in the right direction to solve this problem? Thanks in advance!
From what I understand, I think you are trying to send an email with reset password link to the user from your view after validating the username/email in your form. Next to add some info to the context, so it can be rendered in the template.
So this can done by overriding the form_valid method in your class like:
def form_valid(self, form):
username = form.cleaned_data['username']
email = form.cleaned_data['email']
if username: user = User.objects.get(username__iexact=username, is_active=True)
else: user = User.objects.get(email__iexact=email, is_active=True)
send_email(user)
data = {'info':'give your info here'}
render(request, 'success.html', data)
Learn more here
Update:
to can access the user object from the form do like:
def clean():
...
self.cleaned_data["user"] = User.objects.get(username__iexact=username, is_active=True)
In your form_valid
def form_valid(self, form):
user = form.cleaned_data['user']
send_email(user)
data = {'info':'give your info here'}
render(request, 'success.html', data)
Related
I have a problem with passing object between views and forms and back.
On first form i check token (GET) with email - if it's ok - you can go further. If not - go away :D
views.py:
def login(request):
try:
token = request.GET['token']
except:
return render(request,'error.html')
if request.method == 'POST':
form = LoginForm(request.POST)
if form.is_valid():
return HttpResponseRedirect('/vote/')
else:
form = LoginForm(initial={'token': request.GET['token']})
return render(request,'login.html', context = {'form':form})
forms.py:
class LoginForm(forms.Form):
email = forms.EmailField(label='Email', max_length=254,widget=forms.TextInput(attrs={'class':'required'}))
token = forms.CharField(widget=forms.HiddenInput())
def clean(self):
cleaned_data = super().clean()
try:
voter = Person.objects.get(email__iexact=cleaned_data['email'],token__exact=cleaned_data['token'])
except Person.DoesNotExist:
raise ValidationError('Invalid email')
It works.
But now i try to go to voting form.
And I want to use voter object (which is set in LoginForm). Of course this is different form, so I have to pass it. I thought about session, but there's no request.session in form. This is in view, but there's no voter... or is it?
As always when I'm stuck for many minutes, I wrote the question and after few minutes I got excellent solution, so I want to share it with you:
I moved checking into view and use form.add_error. And I don't need token hidden field anymore:
forms.py:
class LoginForm(forms.Form):
email = forms.EmailField(label='Email', max_length=254,widget=forms.TextInput(attrs={'class':'required'}))
views.py:
def login(request):
try:
token = request.GET['token']
except:
return render(request,'error.html')
if request.method == 'POST':
form = LoginForm(request.POST)
if form.is_valid():
try:
voter = Person.objects.get(email__iexact=form.cleaned_data['email'],token__exact=token)
return HttpResponseRedirect('/vote/')
except Person.DoesNotExist:
form.add_error('email','Invalid email')
else:
form = LoginForm()
return render(request,'login.html', context = {'form':form})
And now I can pass voter into next form using request, session, whatever :D
How to pass request to django form?
I am creating a django update profile form where user could change profile email. I want to check if email in form belongs to logged user if not then I want to check if this email is used by others users before setting it as new users email.
Here is my code and this self.request.user.email doesn't work:
def clean_email(self):
email = self.cleaned_data.get("email")
owns_email = (email != self.request.user.email)
if User.objects.filter(email__icontains=email).exists() and owns_email:
raise forms.ValidationError("This email aldready registered.")
return email
So maybe there is better solution to solve my problem?
Since you are using a cbv, you can use the get_form_kwargs function from the FormMixin.
It could look something like this:
class UserProfileUpdateView(UpdateView):
...
def get_form_kwargs(self):
'''This goes in the Update view'''
kwargs = super(UserProfileUpdateView, self).get_form_kwargs() #put your view name in the super
user = self.request.user
if user:
kwargs['user'] = user
return kwargs
Then your form class would look something like this, based on your above code:
class UserProfileUpdateForm:
...
def __init__(self, *args, **kwargs):
if kwargs.get('user'):
self.user = kwargs.pop('user', None)
super(UserProfileUpdateForm, self).__init__(*args,**kwargs)
def clean_email(self):
email = self.cleaned_data.get("email")
owns_email = (email != self.user.email)
if User.objects.filter(email__icontains=email).exists() and owns_email:
raise forms.ValidationError("This email already registered.")
return email
The form doesn't have the Request object. You need to manually pass the currently logged in user in the constructor. Your form should look something like this:
class UserProfileForm(forms.Form):
user = None
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user')
super(UserProfileForm, self).__init__(*args, **kwargs)
...
def clean_email(self):
email = self.cleaned_data['email']
owns_email = (email != self.user.email)
if User.objects.filter(email__icontains=email).exists() and owns_email:
raise forms.ValidationError('This email already registered.')
return email
...
Instantiating the form in the view:
def edit_profile(request):
form = UserProfileForm(user=request.user)
...
i have a registration form with FormView from the form class UserCreateForm. It workf fine but now I need login the user automatically after register, i have the next code:
class RegisterView(FormView):
form_class = UserCreateForm
template_name = 'contadores/register.html'
success_url = '/contadores/create/'
def dispatch(self, request, *args, **kwargs):
if request.user.is_authenticated():
return HttpResponseRedirect('/contadores/create')
else:
return super(RegisterView, self).dispatch(request, *args, **kwargs)
def form_valid(self,form):
user = form.save()
user = authenticate(username=request.POST['username'],password=request.POST['password'])
login(self.request,user)
return super(RegisterView,self).form_valid(form)
I have tried with clean_data instead request.POST but anyway give an error, i also tried with login(self.request,form.user_cache) in the form_valid function but i cant get it. Question is: How can i get this?
Instead of calling login() method, manually set the session cookies.
def form_valid(self, form):
user = form.save()
user = authenticate(username=self.request.POST['username'],password=self.request.POST['password'])
request.session['_auth_user_id'] = user.pk
request.session['_auth_user_backend'] = user.backend
return super(RegisterView, self).form_valid(form)
Update
I messed up when I last updated the code above. I have updated it yet again. This is what you have to do.
Change form_valid(self, request, form) back to form_valid(self, form).
Replace request.POST['username'] with self.request.POST['username'].
Replace request.POST['password'] with self.request.POST['password'].
The ability for a user to edit their profile using Django Userena all of a sudden doesnt work. It just returns to the same edit page without doing anything. Can anyone see whats wrong with the code. Thanks
forms.py
class EditProfileForm(forms.ModelForm):
""" Base form used for fields that are always required """
first_name = forms.CharField(label=_(u'First name'),
max_length=30,
required=False)
last_name = forms.CharField(label=_(u'Last name'),
max_length=30,
required=False)
def __init__(self, *args, **kw):
super(EditProfileForm, self).__init__(*args, **kw)
# Put the first and last name at the top
new_order = self.fields.keyOrder[:-2]
new_order.insert(0, 'first_name')
new_order.insert(1, 'last_name')
self.fields.keyOrder = new_order
class Meta:
model = get_profile_model()
exclude = ['user']
def save(self, force_insert=False, force_update=False, commit=True):
profile = super(EditProfileForm, self).save(commit=commit)
# Save first and last name
user = profile.user
user.first_name = self.cleaned_data['first_name']
user.last_name = self.cleaned_data['last_name']
user.save()
return profile
views.py
enter code heredef profile_edit(request, username, edit_profile_form=EditProfileForm,
template_name='userena/profile_form.html', success_url=None,
extra_context=None, **kwargs):
"""
Edit profile.
Edits a profile selected by the supplied username. First checks
permissions if the user is allowed to edit this profile, if denied will
show a 404. When the profile is successfully edited will redirect to
``success_url``.
:param username:
Username of the user which profile should be edited.
:param edit_profile_form:
Form that is used to edit the profile. The :func:`EditProfileForm.save`
method of this form will be called when the form
:func:`EditProfileForm.is_valid`. Defaults to :class:`EditProfileForm`
from userena.
:param template_name:
String of the template that is used to render this view. Defaults to
``userena/edit_profile_form.html``.
:param success_url:
Named URL which will be passed on to a django ``reverse`` function after
the form is successfully saved. Defaults to the ``userena_detail`` url.
:param extra_context:
Dictionary containing variables that are passed on to the
``template_name`` template. ``form`` key will always be the form used
to edit the profile, and the ``profile`` key is always the edited
profile.
**Context**
``form``
Form that is used to alter the profile.
``profile``
Instance of the ``Profile`` that is edited.
"""
user = get_object_or_404(get_user_model(),
username__iexact=username)
profile = user.get_profile()
user_initial = {'first_name': user.first_name,
'last_name': user.last_name}
form = edit_profile_form(instance=profile, initial=user_initial)
if request.method == 'POST':
form = edit_profile_form(request.POST, request.FILES, instance=profile,
initial=user_initial)
if form.is_valid():
profile = form.save()
if userena_settings.USERENA_USE_MESSAGES:
messages.success(request, _('Your profile has been updated.'),
fail_silently=True)
if success_url: redirect_to = success_url
else: redirect_to = reverse('userena_profile_detail', kwargs={'username': username})
return redirect(redirect_to)
if not extra_context: extra_context = dict()
extra_context['form'] = form
extra_context['profile'] = profile
return ExtraContextTemplateView.as_view(template_name=template_name,
extra_context=extra_context)(request)
You have to run the following command to solve the problem
python manage.py check_permissions
i hope it will solve your problem.Cheers.
I have the following register view that enters a new user.
I want it to enter the new user and then log in automatically.
It saves through the User record but returns this error when trying to login:
'AnonymousUser' object has no attribute 'backend'
views.py
def register(request):
if request.method == 'POST':
form = UserRegisterForm(request.POST, error_class=DivErrorList)
if form.is_valid():
form.save()
new_user = authenticate(username=request.POST['username'],password=request.POST['password'])
login(request, new_user)
return HttpResponseRedirect('/production/')
else:
form = UserRegisterForm(error_class=DivErrorList)
return render(request,'register.html', {
'form': form,
})
forms.py
class UserRegisterForm(forms.ModelForm):
class Meta:
model = User
fields = ('username','first_name','last_name','email','password')
password_compare = forms.CharField(max_length=128)
def __init__(self, *args, **kwargs):
super(UserRegisterForm, self).__init__(*args, **kwargs)
self.fields['password_compare'].label = 'Password Again'
self.fields['password'].help_text = ''
self.fields['first_name'].label = 'First Name'
self.fields['last_name'].label = 'Last Name'
self.fields['email'].label = 'E-mail Address'
def clean(self):
cleaned_data = self.cleaned_data
password1 = cleaned_data.get('password', None)
password2 = cleaned_data.get('password_compare', None)
if not (password1):
error_msg = u'This field is required.'
self._errors['password'] = self.error_class([error_msg])
if not (password2):
error_msg = u'This field is required.'
self._errors['password_compare'] = self.error_class([error_msg])
# password fields must match
if password1 != password2:
error_msg = u'Password doesn\'t match the confirmation.'
self._errors['password'] = self.error_class([error_msg])
del cleaned_data['password']
# cannot have a username already existing
try:
existing_user = User.objects.get(username=cleaned_data.get('username'))
error_msg = u'Username already exists.'
self._errors['username'] = self.error_class([error_msg])
del cleaned_data['username']
return cleaned_data
except User.DoesNotExist:
return cleaned_data
Your user will never authenticate, because you're saving the password in plain text - and authenticate expects a hashed password. You should call user.set_password(password) on the newly-created user object before saving it to the db - see the built-in UserCreationForm.
I had the same error for a newly registering user and it left me frustrated for an hour.
There was a piece of code that tried to log user in right after the registration.
Usually it worked just fine, but not this time.
def attempt_login(self, email, password):
user = authenticate(username=email, password=password)
login(self.request, user)
return user
It seemed that authenticate returned None, and then calling login with None caused this exception. But I was sure the User has been created after registration.
Finally, I realized that this particular user's login was longer than 30 characters, and the form field had no validation. The login would get truncated in the database, and therefore authenticate was called for non-existent login.