Django password_reset Form does not check if email exists or not. Also validates form whatever email address is given.
The question is, how do i check and throw error for non existing emails with custom form?
By the way, i found below solution at here but it is not working for me. (using Django 2.1). And if this should work, i couldn't found what i am missing.
forms.py
class EmailValidationOnForgotPassword(PasswordResetForm):
def clean_email(self):
email = self.cleaned_data['email']
if not User.objects.filter(email__iexact=email, is_active=True).exists():
raise ValidationError("There is no user registered with the specified email address!")
return email
urls.py
path('sifre-sifirla/', PasswordResetView.as_view(), {'password_reset_form':EmailValidationOnForgotPassword}, name='password_reset'),
Thank you in advance.
EDIT:
For other users information, question is answered by #Alasdair and working but #pypypy 's point of view is also important.
changes in urls.py as below:
path('sifre-sifirla/', PasswordResetView.as_view(form_class=EmailValidationOnForgotPassword), name='password_reset'),
Summarizing the discussion above with parts taken from #Alasdair
#forms.py
from django.contrib.auth.forms import PasswordResetForm
class EmailValidationOnForgotPassword(PasswordResetForm):
def clean_email(self):
email = self.cleaned_data['email']
if not User.objects.filter(email__iexact=email, is_active=True).exists():
msg = _("There is no user registered with the specified E-Mail address.")
self.add_error('email', msg)
return email
And
#urls.py
from accounts.forms import EmailValidationOnForgotPassword
path('sifre-sifirla/', PasswordResetView.as_view(form_class=EmailValidationOnForgotPassword), name='password_reset'),
#pypypy, is correct in saying that this approach can be used to obtain usernames. One way to reduce this issue is to respond with a 429 Too Many Requests as soon an user tries 3 different E-Mails. That can be achived using for example django-ratelimit
Related
I have a class-based view that subclasses LoginView.
from django.contrib.auth.views import LoginView
class CustomLoginView(LoginView):
def get_success_url(self):
url = self.get_redirect_url()
return url or reverse_lazy('knowledgebase:user_home', kwargs={
'username':self.request.user.username,
})
I want to override the error message if a user's email is not yet active because they have to click a link sent to their email address. The current default message looks like this:
Instead of saying:
Please enter a correct email address and password. Note that both
fields may be case-sensitive.
I want to say something to the effect of:
Please confirm your email so you can log in.
I tried:
accounts/forms.py
from django.contrib.auth.forms import AuthenticationForm
from django.utils.translation import gettext as _
class PickyAuthenticationForm(AuthenticationForm):
def confirm_login_allowed(self, user):
if not user.is_active:
raise forms.ValidationError(
_("Please confirm your email so you can log in."),
code='inactive',
)
accounts/views.py
class CustomLoginView(LoginView): # 1. <--- note: this is a class-based view
form_class = PickyAuthenticationForm # 2. <--- note: define form here?
def get_success_url(self):
url = self.get_redirect_url()
return url or reverse_lazy('knowledgebase:user_home', kwargs={
'username':self.request.user.username,
})
The result is absolutely no effect when I try to log in with a user that does exist, but hasn't verified their email address yet.
AuthenticationForm docs.
Method - 1
Django uses ModelBackend as default AUTHENTICATION_BACKENDS and which does not authenticate the inactive users.
This is also stated in Authorization for inactive users sections,
An inactive user is one that has its is_active field set to False. The
ModelBackend and RemoteUserBackend authentication backends prohibits
these users from authenticating. If a custom user model doesn’t have
an is_active field, all users will be allowed to authenticate.
So, set AllowAllUsersModelBackend as your AUTHENTICATION_BACKENDS in settings.py
# settings.py
AUTHENTICATION_BACKENDS = ['django.contrib.auth.backends.AllowAllUsersModelBackend']
How much does it affect my Django app?
It doesn't affect anything other than the authentication. If we look into the source code of AllowAllUsersModelBackend class we can see it just allowing the inactive users to authenticate.
Method - 2
Personally, I don't recommend this method since method-1 is the Django way of tackling this issue.
Override the clean(...) method of PickyAuthenticationForm class and call the AllowAllUsersModelBackend backend as,
from django.contrib.auth.backends import AllowAllUsersModelBackend
class PickyAuthenticationForm(AuthenticationForm):
def clean(self):
username = self.cleaned_data.get('username')
password = self.cleaned_data.get('password')
if username is not None and password:
backend = AllowAllUsersModelBackend()
self.user_cache = backend.authenticate(self.request, username=username, password=password)
if self.user_cache is None:
raise self.get_invalid_login_error()
else:
self.confirm_login_allowed(self.user_cache)
return self.cleaned_data
def confirm_login_allowed(self, user):
if not user.is_active:
raise forms.ValidationError(
"Please confirm your email so you can log in.",
code='inactive',
)
Result Screenshot
You need to use AllowAllUsersModelBackend
https://docs.djangoproject.com/en/3.0/ref/contrib/auth/#django.contrib.auth.backends.AllowAllUsersModelBackend
Here you will get instruction for setting custom backend
https://docs.djangoproject.com/en/3.0/topics/auth/customizing/#specifying-authentication-backends
Hope it helps.
I'm not convinced setting up a custom backend is the solution when I simply want to override a message. I did a temporary fix by defining form_invalid. Yes it's hacky but for now, it'll do the trick. Doubt this will help anyone but it was interesting to discover form.errors. Maybe someone can build off this to solve their specific problem.
def form_invalid(self, form):
"""If the form is invalid, render the invalid form."""
#TODO: This is EXTREMELY HACKY!
if form.errors:
email = form.cleaned_data.get('username')
if User.objects.filter(email=email, username=None).exists():
if len(form.errors['__all__']) == 1:
form.errors['__all__'][0] = 'Please confirm your email to log in.'
return self.render_to_response(self.get_context_data(form=form))
I am relatively new to Django, and web development in general. I'm trying to build a website with two types of users, customers, and suppliers. I need to be able to show these two types of customers different things on the website. For example, different links in the header section for the suppliers.
I am under the impression the best way to do this is to create two user groups ('suppliers' and 'customers') in my /admin, create two different sign up forms (one for suppliers and one for customers), and send each to their respective user group on sign up form submission. From there I can decide what the user sees based on their user group. Correct?
Unfortunately, I'm nearly at my wits end with this! I've created the user groups, created the different sign-up forms, but for the last 2 days I have been trying to figure out how to send the signups to their respective group and I just can't manage to do it! I've searched high and low and tried every suggested line of code I could find: no luck.
Most of the stuff I have tried is along these lines:
views.py
from __future__ import unicode_literals
from django.contrib.auth import (
authenticate,
get_user_model,
login,
logout,
)
from django.contrib.auth.models import User, Group
from django.contrib.auth.decorators import login_required
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import redirect, render, get_object_or_404
from .forms import SupplierRegisterForm
def supplier_signup_view(request):
form = SupplierRegisterForm(request.POST or None)
if form.is_valid():
user = form.save(commit=False)
user.groups.add(Group.objects.get(name='suppliers'))
password = form.cleaned_data.get('password')
user.set_password(password)
user.save()
new_user = authenticate(username=user.username, password=password)
login(request, new_user)
if next:
return redirect(next)
return redirect("/")
context = {
"form": form,
"title": title,
}
return render (request, "supplier_signup.html", context)
forms.py
from django import forms
from django.contrib.auth import (
authenticate,
get_user_model,
login,
logout,
)
from django.contrib.auth.models import User, Group
User = get_user_model()
class SupplierRegisterForm(forms.ModelForm):
username = forms.CharField()
email = forms.EmailField(label="Email Address")
email2 = forms.EmailField(label="Confirm Email", widget=forms.TextInput(attrs={'autocomplete':'false'}))
password = forms.CharField(widget=forms.PasswordInput)
class Meta:
model = User
fields = [
'username',
'email',
'email2',
'password',
]
def clean_email2(self):
email = self.cleaned_data.get('email')
email2 = self.cleaned_data.get('email2')
if email != email2:
raise forms.ValidationError("Emails must match")
email_qs = User.objects.filter(email=email)
if email_qs.exists():
raise forms.ValidationError("This email has already been registered")
return email
It would be much appreciated if anyone could give me a beginners walkthrough of how I would go about automatically sending users to a user group on form submission. Please include all of the code I would need to use, whether in settings, urls.py, models.py, forms.py, views.py, or templates (including imports etc), and any commands I would need to perform and when (eg migrate).
Many thanks!
EDIT: I've changed the answer to make use of Django's API.
Call user.save_m2m() after user.save():
if form.is_valid():
...
user.save()
user.save_m2m()
...
Explanation (from Django docs):
If your model has a many-to-many relation and you specify commit=False when you save a form, Django cannot immediately save the form data for the many-to-many relation. This is because it isn’t possible to save many-to-many data for an instance until the instance exists in the database.
To work around this problem, every time you save a form using commit=False, Django adds a save_m2m() method to your ModelForm subclass. After you've manually saved the instance produced by the form, you can invoke save_m2m() to save the many-to-many form data.
I have a feature on my website where a user can share content with another user registered on the site. They do this by entering in an email belonging to another user. This is then posted, setting the desired user to as a shared owner of content in the model.
What is the best way to check that the email address belongs to a registered user of the site?
Thanks!
I think the efficient way is to search for the user with the given mail. Django User already has a mail field that is unique.
if you want to write from basic:
from django.core.validators import validate_email
class SampleForm(forms.Form):
mail = forms.CharField(max_length=50)
def clean(self):
cleaned_data = super(SampleForm, self).clean()
mail = cleaned_data.get('mail')
# validate the structure of the mail address
try:
validate_email(mail)
except validate_email.ValidationError:
raise forms.ValidationError('email is not valid')
# now find if mail has registered
try:
User.objects.get(email=mail)
except User.DoesNotExist:
raise forms.ValidationError('This mail address is not registered')
return cleaned_data
I'm trying to extend Django registration to include my own registration form. In principle this is fairly simple. I just have to write my own form (CustomRegistrationForm) which is a child of the original one (RegistrationForm). Then I can process my specific input by using the user_registered signal of django registration.
So here is what I did:
urls.py:
from django.conf.urls import patterns, include, url
from registration.views import register
from forms import CustomRegistrationForm
from django.contrib import admin
import regbackend
admin.autodiscover()
urlpatterns = patterns('',
url(r'^register/$', register, {'backend': 'registration.backends.default.DefaultBackend', 'form_class': CustomRegistrationForm, 'template_name': 'custom_profile/registration_form.html'},
name='registration_register'),
)
regbackend.py:
from django import forms
from models import UserProfile
from forms import CustomRegistrationForm
def user_created(sender, user, request, **kwargs):
form = CustomRegistrationForm(data=request.POST, files=request.FILES)
if form.is_valid(): # HERE: always fails
user_profile = UserProfile()
user_profile.user = user
user_profile.matriculation_number = form.cleaned_data['matriculation_number']
user_profile.save()
from registration.signals import user_registered
user_registered.connect(user_created)
forms.py:
from models import UserProfile
from django import forms
from registration.forms import RegistrationForm
from django.utils.translation import ugettext_lazy as _
attrs_dict = {'class': 'required'}
class CustomRegistrationForm(RegistrationForm):
matriculation_number = forms.CharField(widget=forms.TextInput(attrs=attrs_dict),
label=_("Matriculation number"),
max_length=12,
error_messages={'invalid': _("This value has to be unique and may contain only numbers.")},
initial=108)
def clean_matriculation_number(self):
print "Validating matriculation number."
data = self.cleaned_data['matriculation_number']
if len(data) != 12:
raise forms.ValidationError(_("Matriculation number consists of exactly 12 digits."))
return data
So the problem is the is_valid() function, because it always returns False. Even if there are no errors! So what is wrong? I spent hours on this one and I have no idea anymore :(
Any help is much appreciated!!
The reason form.is_valid() fails is probably because the form's "clean_username" function checks if the username passed to it already exists. Since the signal is sent after the User object is created and added to the database, the form will fail to pass this test every time. My guess would be if you logged form.cleaned_data after is_valid() returns False, you'll get a list of all of the fields except the username.
form.data might not contain changed values for the fields if the form clean_ function makes any changes (I couldn't find much documentation on form.data).
To avoid the potential problems this might cause, I made two classes for my custom registration form:
# I use this one for validating in my create_user_profile function
class MyRegistrationFormInternal(forms.Form):
# Just an example with only one field that holds a name
name = forms.CharField(initial="Your name", max_length=100)
def clean_name(self):
# (Optional) Change the name in some way, but do not check to see if it already exists
return self.cleaned_data['name'] + ' foo '
# This one is actually displayed
class MyRegistrationForm (MyRegistrationFormInternal):
# Here is where we check if the user already exists
def clean_name(self):
modified_name = super(MyRegistrationForm, self).clean_name()
# Check if a user with this name already exists
duplicate = (User.objects.filter(name=modified_name)
if duplicate.exists():
raise forms.ValidationError("A user with that name already exists.")
else:
return modified_name
Then, instead of using the form.data (which may still be "unclean" in some ways), you can run your POST data through MyRegistrationFormInternal, and is_valid() shouldn't always return false.
I realize this isn't the cleanest solution, but it avoids having to use the (possibly raw) form.data.
Ok I think I solved it (more or less).
I'm still not really sure, why the form did not validate. But as I said I was extending django-registration and the 'register' view already called is_valid() of the form, so I can assume that the form is valid when I process the posted data any futher. The view then calls the backend
backend.register(request, **form.cleaned_data)
with the request and the cleaned data (which is just username, email and password). So I can't use it for registration because my additional information is missing. The backend then fires the signal that I am using and what I did is, is that I created the form again with the provided request. This form, however, will NOT validate (and I tried everything!!) I looked it up, I am doing the exact same thing as django-registration, but it's not working in my code.
So I did not really solve the problem, because the form is still not validating. But I found peace with this, when I realized that the form was already validated by the 'register' view. So I am using form.data[..] instead of form.cleaned_data[..] now which shouldn't be a problem...
I create registration form:
#urls.py
from django.conf.urls import patterns, url
from django.views.generic import TemplateView
from account.views import Register
urlpatterns = patterns('',
url(r'^register/$', Register.as_view(template_name='account/register.html')),
)
#views.py
from django.views.generic import CreateView
from django.contrib.auth.models import User
class Register(CreateView):
model = User
success_url = '/account/'
And i have question: how I can add that email be require (now I must only enter username, password and 2 times time).
#edit
And how "repair" password? When i create user (in this form) and then go to admin panel, in user i see "Invalid password format or unknown hashing algorithm.". How i can repair this?
The reason that email is not required is because you're using a ModelForm, which takes a lot of cues from the underlying User model. Specifically, the required=True attribute is not present on the email field of the model.
One solution is to create your own form with the necessary attributes, perhaps by using a ModelForm and adding a required email field.
Another solution, and probably the better one, is to use something like django-registration as mentioned by Aamir Adnan in the comments to your question. It'll simplify things a lot for you.
As far as your repair password goes, you can't set the password to a raw string value as you're doing with your CreateView. To set a password, you have to call user.set_password(raw_string) which will take care of hashing and salting for you. Look how the built in UserCreationForm works, and try to mimic it if you decide to build the form yourself, rather than using a library (you shouldn't).
To solve these two problems you can use form like:
class UserCreationForm(forms.ModelForm):
class Meta:
model = User
def __init__(self, *args, **kwargs):
super(UserCreationForm).__init__(*args, **kwargs)
self.fields['email'].required = True
def save(self, commit=True):
user = super(UserCreationForm, self).save(commit=False)
user.set_password(self.cleaned_data["password"])
if commit:
user.save()
return user
class Register(CreateView):
model = User
form_class = UserCreationForm
success_url = '/account/'
But there'll be other problems like duplication of username.