Avoid username field creation in custom user model for django allauth - python

I'm using a custom user model with allauth and I need to have the username field omitted. I've already seen the docs and a whole bunch of stackoverflow answers about using ACCOUNT_USER_MODEL_USERNAME_FIELD = None but all of this still leads my database to have an username field.
Now since the db still has an username field with the unique constraint set on and allauth will not put a username in the field with the aforementioned setting set to None, this causes me to face IntegrityError after the very first user creation. I know I can solve this by just having the aforementioned setting be set to 'username' but I'm curious, how do I just not make the username, because I'm never using it.
My model:
class CustomUser(AbstractUser):
# Custom user model for django-allauth
first_name = None
last_name = None
def delete(self):
# Custom delete - make sure user storage is also purged
# Purge the user storage
purge_userstore(self.email)
# Call the original delete method to take care of everything else
super(CustomUser, self).delete()
It doesn't really do much except override the delete function. This override is irrelevant in this topic but I included it just for the sake of completeness. It also sets first_name and last_name to None, which works perfectly and removes those fields from the database as expected. I've tried setting user to None but that does nothing. I've also tried setting username to None but that will raise a FieldNotFound error with ACCOUNT_USER_MODEL_USERNAME_FIELD = None
My settings (the relevant bit):
AUTHENTICATION_BACKENDS = (
"django.contrib.auth.backends.ModelBackend",
"allauth.account.auth_backends.AuthenticationBackend",
)
AUTH_USER_MODEL = 'custom_account.CustomUser'
ACCOUNT_USER_MODEL_USERNAME_FIELD = None
ACCOUNT_USERNAME_REQUIRED = False
ACCOUNT_EMAIL_REQUIRED = True
ACCOUNT_UNIQUE_EMAIL = True
ACCOUNT_AUTHENTICATION_METHOD = 'email'
My migration file:
migrations.CreateModel(
name='CustomUser',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('password', models.CharField(max_length=128, verbose_name='password')),
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and #/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')),
('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')),
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')),
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')),
],
....
This migration generation confuses me to no end. Why is the username field still there? Why is it set to be the only unique constraint even though I've clearly set ACCOUNT_UNIQUE_EMAIL = True?
Note: This migration file is generated from a clean slate, this is the first and only migration file generated from the code I have presented.
At first I thought, my settings were simply not being read. But I checked django.conf.settings and allauth.account.app_settings (in the shell) for these changes and they were all updated. What's going on here?
Note: Amongst the many stackoverflow questions I've searched, this question in particular seems to explain my issue perfectly. With one small problem, the answer by the creator of allauth himself suggested to use ACCOUNT_USER_MODEL_USERNAME_FIELD = "username" as the model in question was "clearly using the username field". But the answer does not explain what to do when you don't want to use the username field at all.

Looks like the only way to get rid of the username field is to override the AbstractUser's username field and/or use a completely custom model from the ground up. Thought overriding AbstractBaseUser should work too, albeit AbstractBaseUser provides less functionality.
class CustomUser(AbstractUser):
# Custom user model for django-allauth
# Remove unnecessary fields
username = None
first_name = None
last_name = None
# Set the email field to unique
email = models.EmailField(_('email address'), unique=True)
# Get rid of all references to the username field
EMAIL_FIELD = 'email'
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = []
This model will remove the username field and make the email field unique, as well as change all references to the USERNAME_FIELD to 'email'. Do note that REQUIRED_FIELDS should be empty as the USERNAME_FIELD cannot be in there. When using allauth, this is not a problem, the email and password requirement are managed by allauth anyway.
The settings that I've mentioned in the question should remain the same, specifically-
ACCOUNT_USER_MODEL_USERNAME_FIELD = None
ACCOUNT_USERNAME_REQUIRED = False
ACCOUNT_EMAIL_REQUIRED = True

Related

Django user model username (unique=False)

I have seen many of the solution but none of it seems to be answering the question.
My user model is using AbstractUser right now. Is there a way to make username(unique = False)
Since i wanted to allow user to ba able to have duplication of same username, as the login im am using is by email address and password
Below is the code that i tried but doesnt work.
Code:
class MyUser(AbstractUser):
username = models.CharField(max_length=30, unique=False)
error:
customuser.MyUser: (auth.E003) 'MyUser.username' must be unique because it is named as the 'USERNAME_FI ELD'
Try to specify email as username field with USERNAME_FIELD attribute:
class MyUser(AbstractUser):
username = models.CharField(max_length=30, unique=False)
USERNAME_FIELD = 'email'
class MyUser(AbstractUser):
username = models.CharField(max_length=30, unique=False)
email = models.EmailField(max_length=255, unique=True)
USERNAME_FIELD = 'email'
A non-unique username field is allowed if you use a custom authentication backend that can support it.
If you want to use django's default authentication backend you cannot make username non unique.
You will have to implement a class with get_user(user_id) and authenticate(request, **credentials) methods for a custom backend.
You can read it in the documentation here. https://docs.djangoproject.com/en/2.1/topics/auth/customizing/#specifying-custom-user-model

Django: Set is_active = True manually, no way?

For a slew of reasons that detailing will make this post too long, I'm using a custom user model, alongside django-registration, and guardian.
Everything is working quite alright, but I'm trying to make it so every user registered is automatically set to active, so "they" (right now it's only me emulating users) can login instantly. The way I did so far is by manually setting that field to "1" (True) in the SQL table.
It seems that no matter how I arrange things, the is_active flag is always False until I change it manually in SQL, even though I'm printing it to my console and it's showing True when I register.
I did get it to work by changing user.save() to user.save(commit=True), that does make the is_active flag True and allows the user to log-in, but it always throws an error that says:
TypeError: save() got an unexpected keyword argument 'commit'
Of course, this is all for testing purposes and I do intend to introduce email verification, but I'm very intrigued on why I'm not able to simply set the flag and save().
Here's my register/forms.py:
class GeneralUserForm(UserCreationForm):
business_name = forms.CharField(required=True)
class Meta:
model = GeneralUser
fields = ['username', 'email', 'password1',
'password2', 'business_name']
# TODO: only create all objects if no error is found
def save(self, commit=True):
user = super(GeneralUserForm, self).save(commit=False)
user.set_password(self.cleaned_data["password1"])
# TODO: create permissions, including groups.
if commit:
user.is_active = True # remove before deployment
print(user.is_active)
user.save() # LINE I CHANGED
business = Business.objects.create(name=self.cleaned_data['business_name'], owner=user)
business.save()
mng_grp = create_perms_for_biz(business, user)
user.groups.add(mng_grp)
return user
And my register/models.py:
class GeneralUser(AbstractUser, GuardianUserMixin):
username = models.CharField(
('username'),
max_length=150,
unique=True,
help_text=('Required. 150 characters or fewer. Letters, digits and #/./+/-/_ only.'),
error_messages={
'unique': ("A user with that username already exists."),
},
)
password = models.CharField(max_length=100)
email = models.EmailField(('email address'), blank=True)
is_active = models.BooleanField(
('active'),
default=True,
help_text=(
'Designates whether this user should be treated as active. '
'Unselect this instead of deleting accounts.'
),
)
objects = UserManager()
date_joined = models.DateTimeField(('date joined'), default=timezone.now)
business_name = models.CharField(('business name'), max_length=150, blank=True)
USERNAME_FIELD = 'username'
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)
def get_custom_anon_user(User):
return User(
username='AnonymousUser',
)
You are confusing ModelForm.save() method that do take a commit argument with the Model.save() method that does not.

custom user auth in django how to hook up

So the django docs https://docs.djangoproject.com/en/dev/topics/auth/customizing/ are great but I am confused, it would be great to have a mentor right now.
I want to create a custom user model and authentication system
the user model will have
-name
-password
-email
-JWT (javascript web token)
a permissions and a venue (place of entertainment) will be connected to this user model via a many to many relation via an association table.
Here is the problem, even with the docs I am confused how I override the current out of box implementation for authorization.
Additionally I'd like to use https://github.com/GetBlimp/django-rest-framework-jwt
for the token auth but I have no idea how to hook it up. I guess I am looking for a walk through.
If you want to remove fields from the built in User, you should use this model:
from django.core import validators
from django.utils.translation import ugettext_lazy as _
from django.contrib.auth.models import AbstractBaseUser
from django.contrib.auth.models import PermissionsMixin
from django.contrib.auth.models import UserManager
class User(AbstractBaseUser, PermissionsMixin):
username = models.CharField(_('username'), max_length=75, unique=True,
help_text=_('Required. 30 characters or fewer. Letters, numbers and '
'underscores characters'),
validators=[
validators.RegexValidator(re.compile('^[\w]+$'),
_('Enter a valid username.'), 'invalid')
])
first_name = models.CharField(_('first name'), max_length=254, blank=True)
last_name = models.CharField(_('last name'), max_length=30, blank=True)
email = models.EmailField(_('email address'), max_length = 254, unique = True, null = 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 = ['first_name']
def get_full_name(self):
return self.name
def get_short_name(self):
return self.username
This allows you to customise your User model fully. You can remove the appropriate fields. Then follow the instructions for setting up djangorestframework-jwt.
First of all, django has built-in User,for convenience you can use it, if you need to add other fields to current django User model:
form django.contrib.auth.models import User
class MyUser(User):
# define your additional custom fields
after installing djangorestframework-jwt do this setting in your settings.py
REST_FRAMEWORK = {
...
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
),
'DEFAULT_AUTHENTICATION_CLASSES': (
...
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
...
),
}
JWT_AUTH = {
# JWT related settings, see the docs
}
In urls.py just add this:
urlpatterns = [
....
url(r'^api-token-auth/', obtain_jwt_token),
...
]
Now you should first browse for getting a token and then browse your other APIs:
Do a post request to http://127.0.0.1:8000/api-token-auth/ and provide username and password to obtain your token.
On other requests, you should send this token:
GET http://127.0.0.1:8000/yoururl
HEADER
Authorization JWT <YourToken>
You can go forward with your requests with curl, httpie, postman (chrome app) or any other HttpClient

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!

Django auth.user with unique email

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

Categories