Here is a custom registration form inheriting from [django-registration][1]. My extra form fields do not appear and field cleaning methods do not run, something which I observe from the functionality and supported by the print statements (the Custom form runs print statement runs but not 'Custom Password clean'. Both the field appears and the validations run when placed into the original django-registration code, provided below.
Why is this happening?
my app/forms.py
from registration.forms import RegistrationForm
class CustomRegistrationForm(RegistrationForm):
"""
Form for registering a new user account.
Validates that the requested username is not already in use, and
requires the password to be entered twice to catch typos.
I have has added email uniqueness validation and minimum
password length validation.
Subclasses should feel free to add any additional validation they
need, but should avoid defining a ``save()`` method -- the actual
saving of collected user data is delegated to the active
registration backend.
"""
print 'Custom form runs'
LOCALITIES = (
('1', 'London'),
('2', 'Berlin'),
)
locality = forms.MultipleChoiceField(choices=LOCALITIES,
label='Where are you?',
widget=forms.CheckboxSelectMultiple)
def clean_password1(self):
"""
Verify that password is longer than 5 characters.
"""
print 'Custom Password clean'
password = self.cleaned_data['password1']
print 'custom valid'
if len(password) < 6:
raise forms.ValidationError(_("Password needs to be at least 6 characters long"))
return password
def clean_email(self):
"""
Validate that the supplied email address is unique for the site.
"""
email = self.cleaned_data['email']
if User.objects.filter(email__iexact=self.cleaned_data['email']):
raise forms.ValidationError(_("This email address is already in use. \
Please supply a different email address."))
return email
django-registration
registration/forms.py
"""
Forms and validation code for user registration.
Note that all of these forms assume Django's bundle default ``User``
model; since it's not possible for a form to anticipate in advance the
needs of custom user models, you will need to write your own forms if
you're using a custom model.
"""
from django.contrib.auth.models import User
from django import forms
from django.utils.translation import ugettext_lazy as _
class RegistrationForm(forms.Form):
"""
Form for registering a new user account.
Validates that the requested username is not already in use, and
requires the password to be entered twice to catch typos.
Subclasses should feel free to add any additional validation they
need, but should avoid defining a ``save()`` method -- the actual
saving of collected user data is delegated to the active
registration backend.
"""
required_css_class = 'required'
username = forms.RegexField(regex=r'^[\w.#+-]+$',
max_length=30,
label=_("Username"),
error_messages={'invalid': _("This value may contain only letters, numbers and #/./+/-/_ characters.")})
email = forms.EmailField(label=_("E-mail"))
password1 = forms.CharField(widget=forms.PasswordInput,
label=_("Password"))
password2 = forms.CharField(widget=forms.PasswordInput,
label=_("Password (again)"))
def clean_username(self):
"""
Validate that the username is alphanumeric and is not already
in use.
"""
existing = User.objects.filter(username__iexact=self.cleaned_data['username'])
if existing.exists():
raise forms.ValidationError(_("A user with that username already exists."))
else:
return self.cleaned_data['username']
def clean_email(self):
"""
Validate that the supplied email address is unique for the site.
"""
email = self.cleaned_data['email']
if User.objects.filter(email__iexact=self.cleaned_data['email']):
raise forms.ValidationError(_("This email address is already in use. \
Please supply a different email address."))
return email
def clean(self):
"""
Verify that the values entered into the two password fields
match. Note that an error here will end up in
``non_field_errors()`` because it doesn't apply to a single
field.
"""
if 'password1' in self.cleaned_data and 'password2' in self.cleaned_data:
if self.cleaned_data['password1'] != self.cleaned_data['password2']:
raise forms.ValidationError(_("The two password fields didn't match."))
return self.cleaned_data
That's not how you customise a class based view. The third parameter in a urlconf is for parameters that are passed as part of the request, alongside those captured from the URL itself: it's not for configuring the class view.
To do that, you should either override the class in your code and set the attributes there, or you can pass them as parameters to the class's as_view() method:
url(r'^register/$', RegistrationView.as_view(form_class=CustomRegistrationForm, backend=registration.backends.default.DefaultBackend), name='registration_register'),
Related
I found below in Django source code
class AbstractBaseUser(models.Model):
...
def get_username(self):
"""Return the username for this User."""
return getattr(self, self.USERNAME_FIELD)
...
I searched out the whole Django source code, but did not find out where the USERNAME_FIELD was defined.
Can anyone help on this please?
https://github.com/django/django/blob/main/django/contrib/auth/models.py#L377
USERNAME_FIELD was defined in AbstractUser
You can find USERNAME_FIELD in AuthenticationForm class of django which inherites forms.FORM class of Form Api.
It is also defined in models.py in AbstractUser class, refer Django GitHub Code.
It is given in constructor method that is __init__() of AuthenticationForm.
From Django original Code:
class AuthenticationForm(forms.Form):
"""
Base class for authenticating users. Extend this to get a form that accepts
username/password logins.
"""
username = UsernameField(widget=forms.TextInput(attrs={'autofocus': True}))
password = forms.CharField(
label=_("Password"),
strip=False,
widget=forms.PasswordInput(attrs={'autocomplete': 'current-password'}),
)
error_messages = {
'invalid_login': _(
"Please enter a correct %(username)s and password. Note that both "
"fields may be case-sensitive."
),
'inactive': _("This account is inactive."),
}
def __init__(self, request=None, *args, **kwargs):
"""
The 'request' parameter is set for custom auth use by subclasses.
The form data comes in via the standard 'data' kwarg.
"""
self.request = request
self.user_cache = None
super().__init__(*args, **kwargs)
# Set the max length and label for the "username" field.
self.username_field = UserModel._meta.get_field(UserModel.USERNAME_FIELD)
username_max_length = self.username_field.max_length or 254
self.fields['username'].max_length = username_max_length
self.fields['username'].widget.attrs['maxlength'] = username_max_length
if self.fields['username'].label is None:
self.fields['username'].label = capfirst(self.username_field.verbose_name)
def clean(self):
username = self.cleaned_data.get('username')
password = self.cleaned_data.get('password')
if username is not None and password:
self.user_cache = 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):
"""
Controls whether the given User may log in. This is a policy setting,
independent of end-user authentication. This default behavior is to
allow login by active users, and reject login by inactive users.
If the given user cannot log in, this method should raise a
``ValidationError``.
If the given user may log in, this method should return None.
"""
if not user.is_active:
raise ValidationError(
self.error_messages['inactive'],
code='inactive',
)
def get_user(self):
return self.user_cache
def get_invalid_login_error(self):
return ValidationError(
self.error_messages['invalid_login'],
code='invalid_login',
params={'username': self.username_field.verbose_name},
)
Here focus on __init__() for get clearification about USERNAME_FIELD.
It is used for getting user field from User model or you can normally say for getting username.
You can find it in your local machine using below path:
C:\Users\Username\AppData\Local\Programs\Python\Python39\Lib\site-packages\django\contrib\admin\forms.py.
Remember: AppData folder will only appear if you select hidden items.
I'm trying to retrieve the details of a user using the username test from an extended User model in Django. But I am unable to do it. It's giving me the error:
ValueError at / invalid literal for int() with base 10: 'test'
Following is my code:
models.py
class DocchainUser(models.Model):
docchainuser_name = models.OneToOneField(User, on_delete = models.CASCADE, default='x')
address = models.CharField(max_length=64,unique=True)
def __str__(self):
return self.address
views.py
def my_users(request):
if request.method == 'POST':
username = request.POST.get('username')
user = authenticate(username=username)
if user:
if user.is_authenticated:
signBool = signatureAuth(username)
if signBool == 'AUTHENTICATED':
login(request, user, backend=settings.AUTHENTICATION_BACKENDS[0])
return HttpResponseRedirect('/dashboard')
....
And the signatureAuth() now:
def signatureAuth(username):
userModel = DocchainUser.objects.filter(docchainuser_name=username)
address = userModel.address
print(address)
...
I'm retrieving the user details using username: test in signatureAuth() method. test is already present in my User as well as DocchainUser model.
You don't have an extended user model, you have a separate model with a one-to-one relation to User. So in order to query that model by username, you need to follow the relationship.
userModel = DocchainUser.objects.filter(docchainuser_name__username=username)
Note, one of the reasons you struggled here is that your OneToOneField is probably misnamed; the relationship is with the entire model, not the username; you should call it just docchainuser.
(Also note, your if user.is_authenticated is pointless; that just checks that the user is an instance of User rather than AnonymousUser, which you know it is because you just explicitly retrieved it from the User model.)
I've created a custom UserModel and used Email as main authenticating id instead of username.
The problem that it is case sensitive, as it counts test#gmail.com,Test#gmail.com as 2 different accounts.
I need to force it to deal with both as 1 account ignoring if it upper or lower case.
Here are my files :
models.py
class UserModelManager(BaseUserManager):
def create_user(self, email, password, pseudo):
user = self.model()
user.name = name
user.email = self.normalize_email(email=email)
user.set_password(password)
user.save()
return user
def create_superuser(self, email, password):
'''
Used for: python manage.py createsuperuser
'''
user = self.model()
user.name = 'admin-yeah'
user.email = self.normalize_email(email=email)
user.set_password(password)
user.is_staff = True
user.is_superuser = True
user.save()
return user
class UserModel(AbstractBaseUser, PermissionsMixin):
## Personnal fields.
email = models.EmailField(max_length=254, unique=True)
name = models.CharField(max_length=16)
## [...]
## Django manage fields.
date_joined = models.DateTimeField(auto_now_add=True)
is_active = models.BooleanField(default=True)
is_staff = models.BooleanField(default=False)
USERNAME_FIELD = 'email'
REQUIRED_FIELD = ['email', 'name']
objects = UserModelManager()
def __str__(self):
return self.email
def get_short_name(self):
return self.name[:2].upper()
def get_full_name(self):
return self.name
signup view in views.py
def signup(request):
if request.method == 'POST':
signup_form = SignUpForm(request.POST)
if signup_form.is_valid():
signup_form.save()
username = signup_form.cleaned_data.get('username')
raw_password = signup_form.cleaned_data.get('password1')
user = authenticate(username=username, password=raw_password)
return redirect('signup_confirm')
else:
signup_form = SignUpForm()
context = {
'signup_form': signup_form,
}
return render(request, 'fostania_web_app/signup.html', context)
def signup_confirm(request):
return render(request, 'fostania_web_app/signup_confirm.html')
the sign up form in forms.py:
class SignUpForm(UserCreationForm):
email = forms.CharField(required=True, help_text='البريد الإلكترونى الخاص بك - يجب ان يكون حقيقى (يستخدم لتسجيل الدخول) ')
name = forms.CharField(required=True, help_text='إسمك الحقيقى - سيظهر كأسم البائع')
password1 = forms.CharField(widget=forms.PasswordInput,
help_text='كلمة المرور - حاول ان تكون سهلة التذكر بالنسبة لك')
password2 = forms.CharField(widget=forms.PasswordInput,
help_text='تأكيد كلمة المرور - إكتب نفس كلمة المرور السابقة مرة أخرى')
class Meta:
model = UserModel
fields = ('email','name', 'password1', 'password2', )
labels = {
'name': 'إسمك الحقيقى - سيظهر كأسم البائع',
'email': 'البربد الإلكترونى Email',
'password1': 'كلمة المرور',
'password2': 'تأكيد كلمة المرور'
}
All that I need now is to make it simply ignores the case sensitivity.
update
here is my login files
urls.py
path('login/', auth_views.login, name='login'),
registartion/login.html
<form method="post">
{% csrf_token %}
البريد الإلكترونى E-Mail <Br>{{ form.username }}
<br><br>
كلمة المرور <Br>{{ form.password }}
<Br><br>
<div align="center">
<button class ="btn btn-primary" submit>تسجيل الدخول</button><br><br>
<a href="{% url 'signup' %}"><button type="button" class="btn btn-warning">
إنشاء حساب جديد</button></a>
</div>
</form>
A cleaner approach might be to override the model field itself if you don't mind losing the formatting of how the user entered their email.
This worked better for me because I only had to change it in one place. Otherwise, you might have to worry about Signup, Login, User Update, API views, etc.
The only method you will have to overwrite is to_python. This will just lowercase anything being saved to the UserModel.email field.
from django.db import models
class LowercaseEmailField(models.EmailField):
"""
Override EmailField to convert emails to lowercase before saving.
"""
def to_python(self, value):
"""
Convert email to lowercase.
"""
value = super(LowercaseEmailField, self).to_python(value)
# Value can be None so check that it's a string before lowercasing.
if isinstance(value, str):
return value.lower()
return value
Your user model would then just be..
# Assuming you saved the above in the same directory in a file called model_fields.py
from .model_fields import LowercaseEmailField
class UserModel(AbstractBaseUser, PermissionsMixin):
email = LowercaseEmailField(unique=True)
# other stuff...
Addendum 2023:
According to this RFC, the local part of the address before the # is to be handled by the mail host at its own terms, it mentions nothing about a standard requiring the local part to be lowercase, neither to handle addresses in a case insensitive way, even if most mail servers will do so nowadays.
This means that a mail server actually can discard mails sent to user.name#mail.tld if the account is set up as User.Name#mail.tld. This is also reflected in the original normalize_email method of Django's BaseUser class, it only forces the domain part to be lowercase for this reason. Considering this, we have to store the local part of the address in is original format.
That being said, we can still enforce a case insensitive uniqueness check before saving the user to the DB. If we make an educated guess, we can assume that even if a mail server was set up to handle addresses case sensitive for historical reasons, that it has started to validate their uniqueness in a case insensitive way to avoid impersonation.
Django 4.0 and beyond
Django 4.0 comes with Functional unique constraints which is probably the most portable and specific solution:
from django.db.models import UniqueConstraint
from django.db.models.functions import Lower
class User:
# ...
class Meta:
constraints = [
UniqueConstraint(
Lower("email"),
name="user_email_ci_uniqueness",
),
]
Django before 4.0
This is not tested and syntax is from memory (also probably you have to display the validation error manually instead on the field because it will be hidden in the form) but it should convey the potential workaround.
from django.db import models
class User:
# ...
email = models.EmailField(
blank=False,
null=False,
)
email_lower = models.EmailField(
blank=False,
null=False,
unique=True,
editable=False,
error_messages={
"unique": "A user is already registered with this email address",
},
)
def clean(self):
super().clean()
self.email_lower = self.email.lower()
Original Post:
You don't need to change much to accomplish this - in your case you just need to change the form and make use of Django's built-in form data cleaners or by making a custom field.
You should use the EmailField instead of a CharField for built-in validation. Also you did not post your AuthenticationForm, but i presume you have changed it to include email instead of username.
With data cleaners:
class SignUpForm(UserCreationForm):
# your code
email = forms.EmailField(required=True)
def clean_email(self):
data = self.cleaned_data['email']
return data.lower()
class AuthenticationForm(forms.Form):
# your code
email = forms.EmailField(required=True)
def clean_email(self):
data = self.cleaned_data['email']
return data.lower()
With a custom field:
class EmailLowerField(forms.EmailField):
def to_python(self, value):
return value.lower()
class SignUpForm(UserCreationForm):
# your code
email = EmailLowerField(required=True)
class AuthenticationForm(forms.Form):
# your code
email = EmailLowerField(required=True)
This way you can make sure that each email is saved to your database in lowercase and that for each login attempt the email is lowercased before compared to a database value.
When creating the account entry, interpret the email as email.lower() to create a normalised entry in your database.
When parsing the email/username from a log in form, you should also use email.lower() to match the database entry.
Note: the normalize_email only sanitises the domain portion of an email address:
classmethod normalize_email(email)
Normalizes email addresses by lowercasing the domain portion of the email address.
For anyone using Postgres and django 2 or higher:
There is a CIEmailField that stores and retrieves the email address case sensitive but compares it case insensitive. This has the benefit of preserving case in the address (should a user want their address to look like JohnDoe#ImportantCompany.com) without allowing multiple accounts with the same email.
Usage is simple:
from django.contrib.postgres.fields import CIEmailField
# ...
class User(AbstractBaseUser, ...):
email = CIEmailField(unique=True, ...)
# ...
Except on migrating I got the error:
django.db.utils.ProgrammingError: type "citext" does not exist
To solve this, follow this so answer and set up the CITextExtension before the first CreateModel migration operation:
from django.contrib.postgres.operations import CITextExtension
class Migration(migrations.Migration):
...
operations = [
CITextExtension(),
...
]
Note: both in the so answer and in the official documentation is stated that you should add it before the first CreateModel migration operation. For me it was sufficient to add it in the first migration that uses the CIEmailField.
class UserManager(BaseUserManager):
def create_user(self, email, name, tc, password=None):
if not email:
raise ValueError('Users must have an email address')
user = self.model(
email=self.normalize_email(email).lower(),
name = name,
tc = tc
)
user.set_password(password)
user.save(using=self._db)
return user
I want to edit the login form provided by django and don't want to build a new one because of the security issues. I have looked at other solutions like How to use another field for logging in with Django Allauth? it's a good example but it assigns email id based on mobile number. However I want to add another field that isn't particularly to authenticate just for input based on which redirection is done. I am quite confused about my approach and whether or not it is possible to do so. Kindly suggest. Thanks.
You can do that in your forms.py file by doing this.
class UserLoginForm(forms.Form):
username = forms.CharField(widget=forms.TextInput(attrs={'class':'form-control','placeholder':'Username'}))
password = forms.CharField(widget=forms.PasswordInput(attrs={'class':'form-control','placeholder':'Password'}))
yourfield = forms.CharField(widget=forms.TextInput(attrs={'class':'form-control','placeholder':'yourfield'}))
def clean(self, *args, **kwargs):
username = self.cleaned_data.get("username")
password = self.cleaned_data.get("password")
#user_qs = User.objects.filter(username=username)
#if user_qs.count() == 1:
# user = user_qs.first()
if username and password:
user = authenticate(username=username, password=password)
if not user:
raise forms.ValidationError("This user does not exist")
if not user.check_password(password):
raise forms.ValidationError("Incorrect password")
if not user.is_active:
raise forms.ValidationError("This user is no longer active")
return super(UserLoginForm, self).clean(*args, **kwargs)
Apologies if I've misunderstood your question, but here is how I've added extra fields to user registration it which seems pretty straightforward. I've included some extra related methods just to be verbose:
../forms.py:
class CustomRegistrationForm(RegistrationForm):
"""
Form for registering a new user account.
Subclasses should feel free to add any additional validation they
need, but should avoid defining a ``save()`` method -- the actual
saving of collected user data is delegated to the active
registration backend.
"""
username = forms.RegexField(regex=r'^[\w.#+-]+$',
max_length=30,
label="Username",
error_messages={'invalid': "This value may contain only letters, numbers and #/./+/-/_ characters."})
email = forms.EmailField(label="E-mail")
password1 = forms.CharField(widget=forms.PasswordInput,
label="Password")
password2 = forms.CharField(widget=forms.PasswordInput,
label="Password (again)")
extra_field = forms.CharField([field options])
def clean(self):
if 'password1' in self.cleaned_data and 'password2' in self.cleaned_data:
if self.cleaned_data['password1'] != self.cleaned_data['password2']:
raise forms.ValidationError("The two password fields didn't match.")
return self.cleaned_data
Then, simply set your register URL to use the proper form class:
../urls.py:
url(r'^accounts/register/$', RegistrationView.as_view(form_class=accounts.forms.CustomRegistrationForm), name='registration_register'),
Is this field not part of your standard model, or does your input need to do some extra work? You can set a signal to make some extra magic happen when the user is registered:
from forms import CustomRegistrationForm
def user_created(sender, user, request, **kwargs):
form = CustomRegistrationForm(request.POST)
user_account = get_user_account(user)
user_account.persona = form.data['persona_tier']
user_account.save()
from registration.signals import user_registered
user_registered.connect(user_created)
FYI I'm using django-registration-redux backend but this approach should help get you close regardless.
I am working on my Checkout view with regular/guest user but getting hard time to come around the integrity error. Idea is to let guest users register with email only to checkout and I need to set the user email unique.
models.py
from django.conf import settings
from django.db import models
class UserCheckout(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL, null=True, blank=True)
email = models.EmailField(unique=True)
def __unicode__(self):
return self.email
forms.py
from django import forms
from django.contrib.auth import get_user_model
User=get_user_model()
class GuestCheckoutForm(forms.Form):
email = forms.EmailField()
email2 = forms.EmailField(label='Verify Email')
def clean_email2(self):
email = self.cleaned_data.get("email")
email2 = self.cleaned_data.get("email2")
if email == email2:
user_exists = User.objects.filter(email=email).count()
if user_exists != 0:
raise forms.ValidationError("User already exists. Please login instead")
return email2
else:
raise forms.ValidationError("Please confirm emails addresses are the same.")
In my cart views this is how I've rendered my form.
def post(self, request, *args, **kwargs):
self.object = self.get_object()
form = self.get_form()
if form.is_valid():
email = form.cleaned_data.get("email")
user_checkout = UserCheckout.objects.create(email=email)
return self.form_valid(form)
else:
return self.form_invalid(form)
I've registered the model with admin and in admin it shows the error for duplication perfectly fine but from frontend I am getting error below:
IntegrityError at /checkout/
column email is not unique
Request Method: POST
Request URL: http://localhost:8000/checkout/
Django Version: 1.8.13
Exception Type: IntegrityError
Exception Value:
column email is not unique
Exception Location: C:\Users\Ali\ecomm\lib\site-packages\django\db\backends\sqlite3\base.py in execute, line 318
Python Executable: C:\Users\Ali\ecomm\Scripts\python.EXE
Python Version: 2.7.9
You create every time when a checkout occurs an new UserCheckout. And in all these entries it is only allowed that every email exists only once.
I don't think you want this. Because if a guest orders two times it isn't allowed because his email is already in the DB. And that's why you get this error.
The clean_<fieldname> methods of a Form are used to perform validation relative to single field. If you need validation with access to multiple fields, use the clean method. Check the documentation on form validation for a thorough explanation.
That would give:
class GuestCheckoutForm(forms.Form):
email = forms.EmailField()
email2 = forms.EmailField(label='Verify Email')
def clean_email(self):
email = self.cleaned_data["email"]
if User.objects.filter(email=email).exists():
raise forms.ValidationError("Please confirm emails addresses are the same.")
return email
def clean(self):
cleaned_data = super(GuestCheckoutForm, self).clean()
email = cleaned_data.get('email')
email2 = cleaned_data.get('email2')
if email and email2 and email != email2:
self.add_error('email2', forms.ValidationError('Please confirm emails addresses are the same.'))
EDIT: I believe I found out why you got an IntegrityError:
You are validation that no User with the given email is in the database, you should also be validating that no other UserCheckout with the given email is in the database. Replace if User.objects.filter(email=email).exists(): by if User.objects.filter(email=email).exists() or UserCheckout.objects.filter(email=email).exists():