Django auth.user with unique email - python

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

Related

Django: set default value for new model field which has unique=True

There is a custom user model which inherits AbstractUser in Django.
The model has username = None, below is the model:
class User(AbstractUser):
username = None
email = models.EmailField(_("Email address"), unique=True)
USERNAME_FIELD = "email"
I want to remove username = None so that we can save usernames as well.
But the issues is we have various users in the database.
and when I remove the username = None and try to migrate, I get the prompt:
It is impossible to add a non-nullable field 'username' to user without specifying a default. This is because the database needs something to populate existing rows.
Please select a fix:
Provide a one-off default now (will be set on all existing rows with a null value for this column)
Quit and manually define a default value in models.py.
I don't want to override the username field of AbstractUser class.
AbstractUser > username:
username_validator = UnicodeUsernameValidator()
username = models.CharField(
_("username"),
max_length=150,
unique=True,
help_text=_(
"Required. 150 characters or fewer. Letters, digits and #/./+/-/_ only."
),
validators=[username_validator],
error_messages={
"unique": _("A user with that username already exists."),
},
)
I want username to take value of email if a different value is not provided.
How can I provide the default value?
You can use a custom migration. When creating your migration, provide a default value, then edit the generated migration with something like this:
import django.contrib.auth.models
from django.db import migrations, models
import users.models
def forwards_func(apps, schema_editor):
User = apps.get_model("users", "User")
User.objects.update(username=models.F("email"))
def reverse_func(apps, schema_editor):
pass
class Migration(migrations.Migration):
dependencies = [
('users', '0001_initial'),
]
operations = [
migrations.AlterModelManagers(
name='user',
managers=[
('manager', users.models.UserManager()),
('objects', django.contrib.auth.models.UserManager()),
],
),
migrations.AddField(
model_name='user',
name='username',
field=models.EmailField(max_length=254, unique=True, null=True),
preserve_default=False,
),
migrations.RunPython(forwards_func, reverse_func),
migrations.AlterField(
model_name='user',
name='username',
field=models.EmailField(max_length=254, unique=True),
),
]
Here's the explanation of the changes:
First, make the column nullable by adding null=True to the new username field.
Update the username column of existing users via RunPython action. In the forward_func, we use the value of email column for username. Reverse function can be empty because the column will be removed.
Now you can make the field required by setting null to its default value, False, via AlterField.
You can use pre_save singal like that:
# signals.py
from django.db.models.signals import pre_save
from django.dispatch import receiver
#receiver(pre_save, sender=User)
def fill_username(sender, instance, *args, **kwargs):
if not instance.username:
instance.username = instance.email
# apps.py
class UserConfig(AppConfig):
...
def ready(self):
import users.signals
In this way it will always check if username is entered. In case it's empty it will copy value from email.
For existing Users you might need to call save() manually or with for loop in shell:
$ python manage.py shell
>> from users.models import User
>> for user in User.objects.all():
... user.username = user.email
... user.save()
You can use migration for older values and write the setter for username in your Serializer for the new entries

Django set_password no attribute

I'm trying to use the set_password() function but this error
'Member' object has no attribute 'set_password'
comes up when I use it. If I take out the set_password() function the password is stored in the database but without being hashed.
view.py
user = Member(username=u, password=p, email=e, security=s)
user.set_password(p)
user.save()
models.py
class Member(models.Model):
username = models.CharField(max_length=16,primary_key=True)
password = models.CharField(max_length=16)
email = models.CharField(max_length=325)
security = models.CharField(max_length=16)
profile = models.OneToOneField(Profile, null=True)
following = models.ManyToManyField("self", symmetrical=False)
from_member_id = models.CharField(max_length=16)
def __str__(self):
return self.username
The set_password function is not automatically provided by models.Model.
You have to define it by yourself or derive Member from django Usermodel
The documentation on providing your own user model is quite clear and comprehensive. Among other things, your model must be a subclass of AbstractBaseUser, which is what provides the set_password method.
Also note that 16 characters is not nearly long enough to store a hashed, salted password.
As the error message tells you, the method set_password is not defined.
Either you implement it yourself, or (better) create your Member model by subclassing django's AbrstactBaseUser:
class MyUser(AbstractBaseUser):
username = models.CharField(max_length=16,primary_key=True)
password = models.CharField(max_length=16)
email = models.CharField(max_length=325)
security = models.CharField(max_length=16)
profile = models.OneToOneField(Profile, null=True)
following = models.ManyToManyField("self", symmetrical=False)
from_member_id = models.CharField(max_length=16)
USERNAME_FIELD = 'username'
You can find more about custom user models in the django docs
Not sure if your doing this but it is probably easier to make the model with OneToOneField(User) and give it additional fields. You just need to remember to save in the new fields or the fields wont show up when u call.
Where you set the user_form=Member(request.POST)
user = user_form.save()
user.set_password(user.password)
profile = user.userprofile
profile.bio = request.POST['bio']
profile.save()

Django: auto capturing username from the User model. not pk

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.

Django: Not able to validate UserCreationForm

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.

Django Registration Redux: how to change the unique identifier from username to email and use email as login

I'm using django-registration-redux in my project for user registration. It uses default User model which use username as the unique identifier.
Now we want to discard username and use email as the unique identifier.
And also we want to use email instead of username to login.
How to achieve this?
And is it possible to do it without changing the AUTH_USER_MODEL settings?
Because from the official doc it says
If you intend to set AUTH_USER_MODEL, you should set it before creating any migrations or running manage.py migrate for the first time.
You can override registration form like this
from registration.forms import RegistrationForm
class MyRegForm(RegistrationForm):
username = forms.CharField(max_length=254, required=False, widget=forms.HiddenInput())
def clean_email(self):
email = self.cleaned_data['email']
self.cleaned_data['username'] = email
return email
And then add this to settings file (read this link for details)
REGISTRATION_FORM = 'app.forms.MyRegForm'
This will set the email to username field as well and then everything will work as email is now the username.
The only problem is that username field has a max lenght of 30 in DB. So emails longer than 30 chars will raise DB exception. To solve that override the user model (read this for details).
The easiest way to achieve this is to made your own custom User Model.
This is the example
class MyUser(AbstractBaseUser):
email = models.EmailField(
verbose_name='email address',
max_length=255,
unique=True,
)
date_of_birth = models.DateField()
is_active = models.BooleanField(default=True)
is_admin = models.BooleanField(default=False)
objects = MyUserManager()
USERNAME_FIELD = 'email'
Then, you need to set the User Model in your Django settings. https://docs.djangoproject.com/en/1.8/ref/settings/#auth-user-model
You will want to use AbstractBaseUser. Docs are here:
https://docs.djangoproject.com/en/1.8/topics/auth/customizing/
Good luck!

Categories