Below is the content of my forms.py
from django import forms
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User
class RegistrationForm(UserCreationForm):
email = forms.EmailField(required=True)
first_name = forms.CharField(max_length=20, required=True)
last_name = forms.CharField(max_length=20, required=True)
class Meta:
model = User
fields = ('username', 'email', 'password1', 'password2', 'first_name', 'last_name')
def save(self, commit=True):
user = super(UserCreationForm, self).save(commit=False)
user.email = self.cleaned_data['email']
user.first_name = self.cleaned_data['first_name']
user.last_name = self.cleaned_data['last_name']
#validation of email id----------
email1 = user.email
(fisrt, second,) = email1.split("#")
(domain, exn,) = second.split(".")
if not domain == "tmail":
raise forms.ValidationError("Domain must be 'tmail'")
if commit:
user.save()
return user
I am able to print form (including fields email, first_name & last_name) & register the user successfully only issue I have is, Its not performing the validation step. (code below the "#validation of email id----------")
Even if the domain is other than "tmail" it is accepting it without raising any validation error & then registering the user into database. Please help & let me know in case you need more info.
It is too late to validate the email in the save() method. Django lets you validate individual fields by defining a method clean_<fieldname>. This will be called when the form is validated.
def clean_email(self):
email = self.cleaned_data['email']
(first, second,) = email1.split("#")
(domain, exn,) = second.split(".")
if domain != "tmail":
raise forms.ValidationError("Domain must be 'tmail'")
return email
See the docs on cleaning a specific field attribute for more info.
Why are you doing validation in save? All validations must happen before save, such that if any exception does comes in save it is most probably bug in your code. Forms specifically have 'clean' methods especially for this purpose. For specific field validation, you can use "clean_{field_name}". Though if your validation depends on multiple fields, you need to use "clean" method.
Related
I'm using django.contrib.auth and I have a working login/registration system, however I want to extend my User model to have street address and phone number for registration purposes only, and I'm not sure how can I do that properly. What I have done works but feels wrong and I would like to know a proper way to do this.
I created accounts app and in models.py I have this:
from django.db import models
from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
address = models.CharField(max_length=200)
address_number = models.CharField(max_length=20)
phone = models.CharField(max_length=20)
...then I registered that in admin.py:
from django.contrib import admin
from .models import User
admin.site.register(User)
...then in settings.py I added this line:
AUTH_USER_MODEL = 'accounts.User'
...and wherever I used django.contrib.auth.User, now I use accounts.models.User.
As I said, this works however it somehow feels wrong and it easily could be as this is my first time extending the User model. Why I think this is wrong? For one, Users has disappeared in admin from "Authentication and Authorization" and is instead under "Accounts" (as expected).
How can I accomplish this in a proper way?
#EDIT:
Here is my register form:
from django import forms
from accounts.models import User
class RegistrationForm(forms.ModelForm):
email = forms.EmailField(max_length=200, help_text='Required')
password = forms.CharField(label='Password', widget=forms.PasswordInput)
password2 = forms.CharField(label='Repeat password', widget=forms.PasswordInput)
class Meta:
model = User
fields = (
'username',
'email',
'first_name',
'last_name',
'address',
'address_number',
'phone'
)
def clean_password2(self):
cd = self.cleaned_data
if cd['password'] != cd['password2']:
raise forms.ValidationError('Passwords do not match.')
return cd['password2']
def clean_email(self):
email = self.cleaned_data['email']
if User.objects.filter(email=email).exists():
raise forms.ValidationError(
'Please use another e-mail, that is already taken.')
return email
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 capture the username of the user currently logged in, NOT pk.
incident.username = request.user doesn't work
incident.username = request.user.username doesn't work
incident.username = request.username doesn't work
Uggg. This can't be this hard.
models.py
class Incident(models.Model):
username = models.ForeignKey(User) ## field in db will be auto-populated by view.py
date_reported = models.DateField() ## field in db will be auto-populated by view.py
date_occurred = models.DateField()
number_of_samples_affected = models.IntegerField()
capa = models.CharField(max_length=9)
title = models.CharField(max_length=100)
description = models.TextField()
status = models.ForeignKey(Status) ## field in db will be auto-populated by view.py to "Open" at form submission
category = models.ForeignKey(Category)
lab = models.TextField(Lab)
views.py
from submit_app.forms import IncidentForm
from submit_app.models import Incident, Status
from django.shortcuts import render
from django.contrib.auth.decorators import login_required
import datetime
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
#login_required(login_url='/login/')
def submit(request):
if request.GET:
form = IncidentForm()
template = 'submit.html'
context = {'form': form}
return render(request, template, context)
# if this is a POST request we need to process the form data
if request.POST:
# create a form instance and populate it with the data from the request:
form = IncidentForm(request.POST)
# check whether it's valid:
if form.is_valid():
incident = form.save(False) # creating an incident object, but not saving it to the db just yet
incident.username = request.user # auto capture logged in user
incident.date_reported = datetime.date.today() # auto capture the incident submission date
incident.status = Status.objects.get(status="open") # trying to auto populate status with 'Open' upon submission (foreign key)
incident.save()
return HttpResponseRedirect(reverse('dashboard_app:dashboard'))
form = IncidentForm()
template = 'submit.html'
context = {'form': form}
return render(request, template, context)
Right now, your model has a foreign key relation to the User model, which by default relates to the primary key field. To change that and relate to the username field itself, add a to_field keyword argument to your model, makemigrations, and migrate.
username = models.ForeignKey(User,to_field='username')
Afterwards, you'll be able to access the user for the current request via request.user.username, assuming that username is a field/attribute of User (and not a related model).
...
However, there's generally no need to do this. You can still relate to the User model (relation built via PK) and access the username from there. Easiest way to do this is perhaps to create a method to read the username.
class Incident(models.Model):
user = models.ForeignKey(User, related_name='incidents')
def username(self):
if self.user:
return getattr(self.user,'username',None) # attempt to access username
...
>>> first_incident = Incident.objects.create(user=User.objects.get(username='a'))
>>> print(first_incident.username())
a
There is some obvious confusion. Incident.username is a foreign key to a User model, so it needs to be assigned a User object, not just a username. For that incident.username = request.user should work. You can later access the user name by accessing incident.username.username, although I would rename the field to user to avoid confusion. If this doesn't work, something is not working as it should. It would help if you posted the error you are getting.
You should use custom user model and specify usename field to be primary key. But in django abstract base classes for models can't have "overriden fields" so you will need to sublcass AbstractBaseUser instead of AbstractUser. You may eventually end up with a copy of AbstractUser code (https://github.com/django/django/blob/1.8.9/django/contrib/auth/models.py#L378)
with just one line changed:
from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin, validators, UserManager
class MyUser(AbstractBaseUser, PermissionsMixin):
username = models.CharField(_('username'), max_length=30, unique=True,
primary_key=True, ## the only difference from AbstractUser help_text=_('Required. 30 characters or fewer. Letters, digits and '
'#/./+/-/_ only.'),
validators=[
validators.RegexValidator(r'^[\w.#+-]+$',
_('Enter a valid username. '
'This value may contain only letters, numbers '
'and #/./+/-/_ characters.'), 'invalid'),
],
error_messages={
'unique': _("A user with that username already exists."),
})
first_name = models.CharField(_('first name'), max_length=30, blank=True)
last_name = models.CharField(_('last name'), max_length=30, blank=True)
email = models.EmailField(_('email address'), blank=True)
is_staff = models.BooleanField(_('staff status'), default=False,
help_text=_('Designates whether the user can log into this admin '
'site.'))
is_active = models.BooleanField(_('active'), default=True,
help_text=_('Designates whether this user should be treated as '
'active. Unselect this instead of deleting accounts.'))
date_joined = models.DateTimeField(_('date joined'), default=timezone.now)
objects = UserManager()
USERNAME_FIELD = 'username'
REQUIRED_FIELDS = ['email']
class Meta:
verbose_name = _('user')
verbose_name_plural = _('users')
abstract = True
def get_full_name(self):
"""
Returns the first_name plus the last_name, with a space in between.
"""
full_name = '%s %s' % (self.first_name, self.last_name)
return full_name.strip()
def get_short_name(self):
"Returns the short name for the user."
return self.first_name
def email_user(self, subject, message, from_email=None, **kwargs):
"""
Sends an email to this User.
"""
send_mail(subject, message, from_email, [self.email], **kwargs)
After this you will be able to point FK fields to username field. But do you realy need this? Why do you need to have such FK? Primary keys should better be "static". By using username as primary key you will have problems changing your usernames.
I can imagine several reasons for such a requirement:
You want your incidents to point specific username instead of actual user (maybe your instances of User may be deleted and later recreated with same username?). This is strange but can be done: use username = CharField(...) and also specify property for user with getter and setter.
class Incident(models.Model):
username = models.CharField(max_length=30)
#property
def user(self):
return User.objects.get(username=self.username)
#user.setter
def user(self, user):
if user.is_authenticated():
self.username = user.username
else:
self.username = '#anonymous' # by default '#' is disallowed in username. You can also make your username nullable
you want to "optimize" database calls (to not query users table). In this case you'd better use prefetching or denormalization:
from django.db import models
# prefetch user with only "username" field. Assuming that you have `Incident.user = models.ForeignKey(...)`. Read https://docs.djangoproject.com/en/1.9/topics/db/managers/ and https://docs.djangoproject.com/en/1.9/ref/models/querysets/#prefetch-related
class IncidentManager(models.Manager):
def get_queryset(self):
return super(IncidentManager, self).get_queryset().prefetch_related(models.Prefetch('user', queryset=User.objects.all().only('username'))
class Incident(models.Model):
user = models.ForeignKey(User)
# ...
objects = IncidentManager()
In case of denormalization you should create receiver for post_save and post_delete signals for User model which should update Incident.username field with actual username. You must also create similar signal receivers for Incident's post_save/post_delete (or you can modify Incident.save and Incident.delete methods). You may also create signal receiver for admin.models.LogAction post_save signal (from django.contrib.admin.models import DELETEION; if instance.action_flag == DELETEION and instance.content_type_id=get_content_type_for_model(Incident).pk:) because mass deletion from django-admin does not call Incident.delete and does not trigger post_delete for deleted incidnets. And even after this denormalized data may be invalid if you use User.object.update(username=something) anywhere in your project or if data is changed directly in database.
I am using Django 1.7.1 with Python 3.4. I created a custom user model and now I have a need for users to be able to update their details. What I need is that, when users go to the form to update their details, the form is pre-populated with their data i.e. username, email and so on. So far, the form is showing but not with the current user data.
I have the following code:
models.py
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, PermissionsMixin
... # Some code left out for brevity
class AuthUser(AbstractBaseUser, PermissionsMixin):
"""
A fully featured User model with admin-compliant permissions that uses
a full-length email field as the username.
Email and password are required. Other fields are optional.
"""
username = models.CharField(_('username'), max_length=30, unique=True,
help_text=_('Required. 30 characters or fewer. Letters, numbers and #/./+/-/_ characters'),
validators=[validators.RegexValidator(re.compile('^[\w.#+-]+$'), _('Enter a valid username.'), _('invalid'))])
email = models.EmailField(_('email address'), max_length=254, unique=True)
is_staff = models.BooleanField(_('staff status'), default=False,
help_text=_('Designates whether the user can log into this admin site.'))
is_active = models.BooleanField(_('active'), default=True,
help_text=_('Designates whether this user should be treated as '
'active. Unselect this instead of deleting accounts.'))
date_joined = models.DateTimeField(_('date joined'), default=timezone.now)
objects = AuthUserManager()
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['username'] # Not needed since it has been mentioned in USERNAME_FIELD and is required by default and cannot be listed in REQUIRED_FIELDS
class Meta:
verbose_name = _('user')
verbose_name_plural = _('users')
def get_absolute_url(self):
return "/users/%s/" % urlquote(self.username)
def __str__(self):
return self.username
def get_full_name(self):
# The user is identified by their email address
return self.email
def get_short_name(self):
# The user is identified by their username
return self.username
def email_user(self, subject, message, from_email=None):
"""
Sends an email to this User.
"""
send_mail(subject, message, from_email, [self.email])
forms.py
from django.contrib.auth.forms import UserChangeForm
from .models import AuthUser
class AuthUserChangeForm(UserChangeForm):
"""
A form for updating users. Includes all the fields on the user, but
replaces the password field with admin's password hash display field.
"""
password = ReadOnlyPasswordHashField(label="password",
help_text="""Raw passwords are not stored, so there is no way to see this
user's password, but you can change the password using <a href=\"password/\">
this form</a>""")
class Meta:
model = AuthUser
fields = ('username', 'email', 'password', 'is_active', 'is_staff', 'is_superuser', 'user_permissions')
widgets = {
'email': TextInput(),
}
def clean_password(self):
# Regardless of what the user provides, return the initial value.
# This is done here, rather than on the field, because the field does
# not have access to the initial value
return self.initial["password"]
views.py
class UpdateUserView(LoginRequiredMixin, FormView):
template_name = 'users/update_user.html'
form_class = AuthUserChangeForm
# get current user object
def get_object(self, queryset=None):
return self.request.user
urls.py
url(r'^profile/update/', UpdateUserView.as_view(), name='update_profile'),
What I'm I missing?
FormView is not the appropriate base class here: it doesn't know about model forms and doesn't define a get_object method. Use UpdateView instead.
I use the django.auth system and I've this:
class RegisterForm(UserCreationForm):
username = forms.RegexField(label= "Username" , max_length = 30, regex = r'^[\w]+$', error_messages = {'invalid': "This value may contain only letters, numbers and _ characters."})
email = forms.EmailField(label = "Email")
first_name = forms.CharField(label = "First name", required = False)
last_name = forms.CharField(label = "Last name", required = False)
class Meta:
model = User
fields = ("username", "first_name", "last_name", "email", )
def save(self, commit = True):
user = super(RegisterForm, self).save(commit = False)
user.first_name = self.cleaned_data["first_name"]
user.last_name = self.cleaned_data["last_name"]
user.email = self.cleaned_data["email"]
if commit:
user.save()
return user
I want to set emails as uniques and check the form for this validation. How can I do it?
Somewhere in your models:
from django.contrib.auth.models import User
User._meta.get_field('email')._unique = True
Notice the underscore before unique. This is where the information is actually held. User._meta.get_field('email').unique is just a #property which looks into it.
This should work for syncdb too, so you will have consistency with the database.
Note too, that from Django 1.5 you will not have to do such things, as User model will be pluggable.
add this to your form. But this isn't perfect way. race condition is available by only using this form. I recommend you to add unique constraint at db level.
def clean_email(self):
data = self.cleaned_data['email']
if User.objects.filter(email=data).exists():
raise forms.ValidationError("This email already used")
return data
SQL to add unique constraint:
ALTER TABLE auth_user ADD UNIQUE (email)
I am not sure how to use this, but
from django.contrib.auth.models import User
User._meta.get_field_by_name('email')[0].unique=True
should do it. I guess this goes in your models.py before you run syncdb on the auth model. Bout to try this myself.
Overriding the clean() method as suggested by mumimo is described here:
http://docs.djangoproject.com/en/dev/topics/forms/modelforms/#overriding-the-clean-method
Ordinarily you could use unique=True on your field definition and then use ModelForm, so that it'll automatically run clean() for all of the fields in your form, but if you're using the django.auth user class you can't modify the fields.
You can do the same thing using this in Abstract User Model:
class User(AbstractUser):
...
class Meta:
unique_together = ('email',)
While doing the registration, i found one thing that email is not required in django auth but while simple validation, if we don't provide the email it gives 500 error as we must be checking for the email at the backend.
So to make it required, add to the registrationSerializer;
extra_kwargs = {'email': {'required': True}}
For unique email, other answers are showing that.
You can make the email unique and even allow it to be null by adding the following code in your models.py
from django.db import models
from django.contrib.auth.models import User
User.email = models.EmailField(_("email address"), blank=True, null=True, unique=True)
Tested on Django 4.0.3