I have implemented Django allauth as a method of authenticating users. I am using Django allauth in an attempt to try and handle multi user authentication system. Now when I omit "username" field in my forms.py file, Django complains of the integrity error as below.
IntegrityError at /accounts/accounts/student
UNIQUE constraint failed: accounts_user.username
Request Method: POST
Request URL: http://127.0.0.1:8000/accounts/accounts/student
Django Version: 4.1
Exception Type: IntegrityError
Exception Value:
UNIQUE constraint failed: accounts_user.username
Exception Location: /home/offensive/Desktop/Target/.venv/lib/python3.8/site-packages/django/db/backends/sqlite3/base.py, line 357, in execute
Raised during: accounts.views.StudentSignUpView
Python Executable: /home/offensive/Desktop/Target/.venv/bin/python
Python Version: 3.8.10
Python Path:
['/home/offensive/Desktop/Target',
'/usr/lib/python38.zip',
'/usr/lib/python3.8',
'/usr/lib/python3.8/lib-dynload',
'/home/offensive/Desktop/Target/.venv/lib/python3.8/site-packages']
Server time: Mon, 13 Feb 2023 14:14:23 +0000
below is my forms.py
from django import forms
from django.contrib.auth.forms import UserCreationForm, PasswordChangeForm
from django.contrib.auth import get_user_model
from django.db import transaction
from .models import Student, User, Writter
class StudentSignUpForm(UserCreationForm):
email = forms.EmailField(required=True)
class Meta:
model = get_user_model()
fields = (
"email",
# "username",
)
#transaction.atomic
def save(self):
user = super().save(commit=False)
user.email = self.cleaned_data.get('email')
user.is_student = True
user.save()
student = Student.objects.create(user=user)
return user
class WritterSignUpForm(UserCreationForm):
email = forms.EmailField(required=True)
first_name = forms.CharField(required=True)
last_name = forms.CharField(required=True)
phone = forms.CharField(required=True)
address = forms.CharField(required=True)
class Meta:
model = get_user_model()
fields = (
"email",
# "username",
"phone",
"address",
)
By default, I think Django allauth should populate username field automatically using the email address which I already made "required". Below is the Allauth configurations on my settings.py file.
..................
AUTHENTICATION_BACKENDS = (
"django.contrib.auth.backends.ModelBackend",
"allauth.account.auth_backends.AuthenticationBackend",
)
......
SITE_ID = 1
ACCOUNT_USER_MODEL_USERNAME_FIELD = "email"
ACCOUNT_USERNAME_REQUIRED = False
ACCOUNT_AUTHENTICATION_METHOD = "email"
ACCOUNT_EMAIL_REQUIRED = True
ACCOUNT_UNIQUE_EMAIL = True
# # ACCOUNT_EMAIL_VERIFICATION = "mandatory"
LOGIN_REDIRECT_URL = "/dashboard"
ACCOUNT_LOGOUT_REDIRECT = "/"
ACCOUNT_SESSION_REMEMBER = True
ACCOUNT_SIGNUP_PASSWORD_ENTER_TWICE = False
ACCOUNT_SIGNUP_REDIRECT_URL = "/dashboard"
# ACCOUNT_EMAIL_CONFIRMATION_EXPIRE_DAYS = 3
......
Delete your line ACCOUNT_USER_MODEL_USERNAME_FIELD = "email". It seems you don't have a custom user model, and this setting is intended to tell Django which fieldname in a custom user model should be treated as what it expects to see as username. It's not related to telling Django which fieldname to use for validation.
Try using signals. Create a signals.py file in the same folder you have your forms.py for accounts:
from django.contrib.auth import get_user_model
from django.db.models.signals import pre_save
from django.dispatch import receiver
from django.conf import settings
User = get_user_model()
#receiver(pre_save,sender=User)
def set_username(sender,instance,**kwargs):
instance.username = instance.email
If that doesn't work, and if you haven't already, you should probably look into creating a custom user model, which is the recommended approach anyway.
This article is a good starting point, and this answer goes into how to get rid of the username field completely.
Related
I have created a Django Project using the standard User-form. I really need to make use of the email as login (using this guide). The problem is it should be difficult to migrate when I already have used the standard User-form.
Since we are only in the testing stage at the moment, I don't mind wiping the entire database to make this migration.
Having that in mind, that losing data is not an issue, is there a way to make this migration?
EDIT (added some explanation):
Right now I have the "usual"
from django import forms
from django.contrib.auth.models import User
from django.contrib.auth.forms import UserCreationForm,PasswordChangeForm
class UserRegisterForm(UserCreationForm):
class Meta:
model = User
fields = ["username","email","password1","password2"]
i.e the login consists of a user-name and a password. Say I want to create my own user (here) I can understand that since I already have users in my database, it's going to be difficult.
So the question is; if I'm using the "standard" user model to create a user, and I want to go from that and using email as login (instead of username), how do I do that?
In the Django documentation, they highly recommend setting up a custom user model when starting a new project, even if the default one is sufficient for you. So to use the email as log in, first you need to add a custom user model:
# YourApp/models.py
from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
email = models.EmailField('email address', unique=True)
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = []
next, you need to point AUTH_USER_MODEL to the custom user model in settings.py:
# settings.py
AUTH_USER_MODEL = 'YouApp.User'
Also, make sure to update the forms.py and admin.py files:
# YourApp/forms.py
class YourSignUpForm(UserCreationForm):
class Meta:
model = User
fields = ("username", "email", "password1","password2")
# YourApp/admin.py
from YourApp.models import User
admin.site.register(User, UserAdmin)
Finally, you need to delete the database and recreate it. If you are using postgres, for example, do this:
(venv) $ sudo -u postgres psql
postgres=# DROP DATABASE YourDB;
postgres=# CREATE DATABASE YourDB;
and run the migrations:
python manage.py migrate
With this code, you are going to delete the username from your Custom User. So, you can log in with the email instead of the username.
In YOUR_APP/models.py.
from django.contrib.auth.models import AbstractUser, BaseUserManager, Group
from django.conf import settings
from django.db import models
from django.utils.translation import gettext as _
class UserManager(BaseUserManager):
"""
Model manager for User model without username field
"""
use_in_migrations = True
def create_user(self, email, password, **extra_fields):
"""
Create a User with the given email and password
"""
if not email:
raise ValueError('The Email must be set')
email = self.normalize_email(email)
user = self.model(email=email, **extra_fields)
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, email, password, **extra_fields):
"""
Create a SuperUser with the given email and password
"""
extra_fields.setdefault('is_staff', True)
extra_fields.setdefault('is_superuser', True)
if extra_fields.get('is_staff') is not True:
raise ValueError('Superuser must have is_staff=True.')
if extra_fields.get('is_superuser') is not True:
raise ValueError('Superuser must have is_superuser=True.')
return self.create_user(email, password, **extra_fields)
class User(AbstractUser):
"""
Store a custom user
"""
username = None
email = models.EmailField(_('email address'), unique=True)
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = []
objects = UserManager()
class Meta:
db_table = "auth_user"
def __str__(self):
return self.email
class ProxyUser(User):
class Meta:
app_label = "auth"
proxy = True
verbose_name = "User"
verbose_name_plural = "Users"
class ProxyGroup(Group):
class Meta:
app_label = "auth"
proxy = True
verbose_name = "Group"
verbose_name_plural = "Groups"
In YOUR_APP/forms.py
from django.contrib.auth.forms import UserCreationForm, UserChangeForm
from .models import User
class MyUserCreationForm(UserCreationForm):
"""
Added creation of the user form.
"""
class Meta(UserCreationForm):
model = User
fields = ["email"]
class MyUserChangeForm(UserChangeForm):
"""
Added updating of the user form.
"""
class Meta:
model = User
fields = ["email"]
In YOUR_APP/Admin.py just if you want to use Django-Admin
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from django.utils.translation import gettext as _
from .models import User, ProxyUser, ProxyGroup
from .forms import MyUserCreationForm, MyUserChangeForm
class MyUserAdmin(UserAdmin):
"""
Django admin to Users (customizing authentication user)
"""
add_form = MyUserCreationForm
form = MyUserChangeForm
model = User
list_display = [
"email", "first_name", "last_name", "is_staff"
]
list_display_links = [
"email"
]
search_fields = [
"email", "first_name", "last_name"
]
fieldsets = [
("Access info", {
"fields": ("email", "password")
}),
("Personal info", {
"fields": ("first_name", "last_name")
}),
("Permissions", {
"fields": ("is_active", "is_staff", "is_superuser", "groups", "user_permissions")
}),
("Important dates", {
"fields": ("last_login", "date_joined")
}),
]
add_fieldsets = [
("Access info", {
"classes": ("wide",),
"fields": ("email", "password1", "password2"),
}),
]
ordering = [
"email"
]
admin.register(ProxyUser, MyUserAdmin)
admin.register(ProxyGroup)
In settings.py you need to redirect to the Custom User
AUTH_USER_MODEL = "YOUR_APP.User"
If you already did migrations before doing this. Make sure to delete/drop and create again the database, and delete the migrations folders and pycache folders of your project to avoids issues.
Finally, do the migrations
python manage.py migrate YOUR_APP
If there is some kind of problems with the migration, you can try with the following code instead of the previous
python manage.py migrate YOUR_APP
python manage.py migrate auth
python manage.py migrate contenttypes
python manage.py migrate admin
python manage.py migrate sessions
I hope this help you.
I am using the rest-auth module to enable user authentication on my web app. Though I am facing some difficulties in fetching details about the user. The Django-rest-framework return a key when I post my username and password, while that's enough for logging in I also want to fetch additional details like user.is_staff, user.username and user.email.
I tried to use Token serializer, but I am not sure if I am doing it right.
** settings.py **
REST_AUTH_SERIALIZERS = {
'TOKEN_SERIALIZER': '## How to define the path to my serializer ##',
}
** serializers.py **
from rest_framework import serializers
from lms.models.post_models import Post
from django.contrib.auth.models import User
from rest_auth.models import TokenModel
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('username', 'email')
class TokenSerializer(serializers.ModelSerializer):
user = UserSerializer()
class Meta:
model = TokenModel
fields = ('key', 'user')
Please tell what piece is missing or if any piece is incorrect. Also, please help me figure out the part between ## ##.
Thank you!
I think you are doing it right, in your custom TokenSerializer you need to fetch the user somehow. If I look at the code of the LoginView, I see that you can use request object from context within serializer, so your TokenSerializer should be like:
# serializers.py
class TokenSerializer(serializers.ModelSerializer):
user = serializers.SerializerMethodField()
class Meta:
model = TokenModel
fields = ('key', 'user')
def get_user(self, instance):
request = self.context.get('request')
return UserSerializer(request.user).data
and then settings.py
REST_AUTH_SERIALIZERS = {
'TOKEN_SERIALIZER': 'project.serializers.TokenSerializer',
}
EDITED:
This might break your register view because if you look at the source code, at line 60 it uses the same serializer but doesn't pass the request object in the context of serializer. You can make it work by overriding this method
# views.py
from django.conf import settings
from rest_auth.registeraion.views import RegisterView
from allauth.account import app_settings as allauth_settings
from rest_auth.app_settings import (TokenSerializer,
JWTSerializer)
class CustomRegisterView(RegisterView):
def get_response_data(self, user):
if allauth_settings.EMAIL_VERIFICATION == \
allauth_settings.EmailVerificationMethod.MANDATORY:
return {"detail": _("Verification e-mail sent.")}
if getattr(settings, 'REST_USE_JWT', False):
data = {
'user': user,
'token': self.token
}
return JWTSerializer(data).data
else:
return TokenSerializer(user.auth_token, context={"request": self.request}).data
and then use this view for registration in your urls
# urls.py
from views import CustomRegisterView
urlpatterns = [
...,
url(r'^rest-auth/', include('rest_auth.urls')),
url(r'^rest-auth/registration/', CustomRegisterView.as_view())
]
I have created a user and want to add him to groups by default viewer but only when he has verified his email id.
I have used djoser to create the APIs to create user. On post email is verified . Now I cant understand how to implement adding to group when email is verified.
this is model.py
from django.db import models
from django.contrib.auth.models import AbstractUser, Group
class User(AbstractUser):
# GROUP_CHOICES = (
#('admin','ADMIN'),
#('creator', 'CREATOR'),
#('reader','READER')
#)
#group = models.CharField(max_length=10, choices=GROUP_CHOICES, default='CREATOR')
email = models.EmailField(verbose_name='email',max_length=233,unique=True)
phone = models.CharField(null=True,max_length=255)
is_active=models.BooleanField(default=True)
is_staff = models.BooleanField(default=False)
REQUIRED_FIELDS=['username','phone','first_name', 'last_name']
USERNAME_FIELD = 'email'
def get_username(self):
return self.email
#def add_group(self):
# user= User.OneToOneField(User)
# group = Group.objects.get(name='Creator')
# my_group.user_set.add(your_user)
serializer.py
class UserCreateSerializer(UserCreateSerializer):
class Meta(UserCreateSerializer.Meta):
model= User
fields = ('id' ,'email', 'username' ,'password', 'first_name', 'last_name', 'phone')
urls.py in the app
urlpatterns = [
path('', include('djoser.urls')),
path('', include('djoser.urls.authtoken')),
]
I have refereed to
stack overflow link
but cant relate it to my code or how to add it, if its the right way.
One possible way by overriding djoser.serializers.ActivationSerializer would be as follows -
from django.contrib.auth.models import Group
from djoser.serializers import ActivationSerializer
class MyActivationSerializer(ActivationSerializer):
def validate(self, attrs):
attrs = super(MyActivationSerializer, self).validate(attrs)
group = Group.objects.get(name='your_group_name')
self.user.groups.add(group)
return attrs
Then in your settings.py update the following -
DJOSER = {
# other djoser settings
'SERIALIZERS': {
#other serializers
'activation': 'your_app_name.serializers.MyActivationSerializer',
#other serializers
}
}
I am new to Django and I have been trying this for weeks, but could not find a way to solve this problem.
I want to store additional information like user mobile number, bank name, bank account. And want to store the mobile number while user registers and wants user to login with either (mobile number and password) or (email and password).
This is my UserProfile model
from django.db import models
from django.contrib.auth.models import User
from django.contrib.auth.models import AbstractUser
# Create your models here.
class UserProfile(AbstractUser):
user_mobile = models.IntegerField(max_length=10, null=True)
user_bank_name=models.CharField(max_length=100,null=True)
user_bank_account_number=models.CharField(max_length=50, null=True)
user_bank_ifsc_code = models.CharField(max_length=30,null=True)
user_byt_balance = models.IntegerField(max_length=20, null=True)
And this is my forms.py
from django import forms
from django.contrib.auth.models import User # fill in custom user info then save it
from django.contrib.auth.forms import UserCreationForm
from models import UserProfile
from django.contrib.auth import get_user_model
class MyRegistrationForm(UserCreationForm):
email = forms.EmailField(required = True)
mobile = forms.IntegerField(required=True)
class Meta:
model = UserProfile
fields = ('username', 'email', 'password1', 'password2','mobile' )
def save(self,commit = False):
user = super(MyRegistrationForm, self).save(commit = False)
user.email = self.cleaned_data['email']
user.user_mobile = self.cleaned_data['mobile']
user.set_password(self.cleaned_data["password1"])
user_default = User.objects.create_user(self.cleaned_data['username'],
self.cleaned_data['email'],
self.cleaned_data['password1'])
user_default.save()
if commit:
user.save()
return user
In my settings.py I have included
AUTH_USER_MODEL = "registration.UserProfile"
admin.py of my app is
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.models import User
from models import UserProfile
class UserProfileInline(admin.StackedInline):
model = UserProfile
can_delete = False
verbose_name_plural = 'userprofile'
class UserProfileAdmin(UserAdmin):
inlines = (UserProfileInline, )
admin.site.register(UserProfile, UserProfileAdmin)
While adding the user from admin I get this error
Exception at /admin/registration/userprofile/1/
<class 'registration.models.UserProfile'> has no ForeignKey to <class 'registration.models.UserProfile'>
Can someone help me with this or point out to the full working exapmle, I have seen Django documentation but didn't find any luck. Or if there is another way to do this.
Thanks in advance
Edit 1:
While registering from the registration form I'm also getting this error
DatabaseError at /register
(1146, "Table 'django_auth_db.auth_user' doesn't exist")
You have confused yourself a bit here. The idea of subclassing AbstractUser - and defining AUTH_USER_MODEL as your subclass - is that the new model completely replaces auth.models.User. You shouldn't be importing the original User at all, and you certainly should be calling User.objects.create_user(): your new model's manager now has its own create_user method.
Because of this, there's no reason to muck about with inline admins. Your UserProfile should be registered in the admin using the existing django.contrib.auth.admin.UserAdmin class.
Inlines forms assume that you have a Generic ForeignKey on your model, in this case, the UserProfileAdmin expect a Generic ForeignKey of the UserProfile, that does not exists. Try to do a regular Model Admin, like:
class UserProfileAdmin(admin.ModelAdmin):
can_delete = False
verbose_name_plural = 'userprofile'
admin.site.register(UserProfile, UserProfileAdmin)
I'm been using the default user model in django for quite a abit and I realize , if I need to further enhance it , I would have to create my own custom User Model in django 1.5 .
I created my custom user model and I have a function which allows users to sign in .
I think my custom user model is incompatible with my function because it wouldn't allow me to do request.user . How can I fix this so I can use request.user again?
views
def LoginRequest(request):
form = LoginForm(request.POST or None)
if request.user.is_authenticated():
username = User.objects.get(username=request.user)
url = reverse('world:Profile', kwargs = {'slug': person.slug})
return HttpResponseRedirect(url)
if request.POST and form.is_valid():
user = form.authenticate_user()
login(request, user)
username= User.objects.get(username=request.user)
person = Person.objects.get(user=request.user)
url = reverse('world:Profile', kwargs = {'slug': person.slug})
return HttpResponseRedirect(url)
return render(request, 'login.html',{'form': form})
models
class PersonManager(BaseUserManager):
def create_user(self, email,date_of_birth, username,password=None,):
if not email:
msg = 'Users must have an email address'
raise ValueError(msg)
if not username:
msg = 'This username is not valid'
raise ValueError(msg)
if not date_of_birth:
msg = 'Please Verify Your DOB'
raise ValueError(msg)
user = self.model(
email=PersonManager.normalize_email(email),username=username,date_of_birth=date_of_birth)
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self,email,username,password,date_of_birth):
user = self.create_user(email,password=password,username=username,date_of_birth=date_of_birth)
user.is_admin = True
user.is_staff = True
user.is_superuser = True
user.save(using=self._db)
return user
class Person(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(verbose_name='email address',max_length=255,unique=True,db_index=True,)
username = models.CharField(max_length=255, unique=True)
date_of_birth = models.DateField()
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['username', 'date_of_birth',]
is_active = models.BooleanField(default=True)
is_admin = models.BooleanField(default=False)
is_staff = models.BooleanField(default=False)
objects = PersonManager()
def get_full_name(self):
return self.email
def get_short_name(self):
return self.email
def __unicode__(self):
return self.email
The problem is that User refers to django.contrib.auth.models.User and now you have got a Custom User pet.Person assuming you have in the settings.py
AUTH_USER_MODEL = "pet.Person"
you have to define User with the Custom User model and you can do this with get_user_model at the top of the file where you use User
from django.contrib.auth import get_user_model
User = get_user_model()
now you will be able to use Custom User model and the problem has been fixed.
For anyone else who might come across this problem, I also solved it by simply doing this on forms.py:
add this at the top of the forms.py file
from .models import YourCustomUser
and then add this to your forms.py CustomUser form:
class SignUpForm(UserCreationForm):
#profile_year = blaaa blaa blaaa irrelevant.. You have your own stuff here don't worry about it
# here is the important part.. add a class Meta-
class Meta:
model = YourCustomUser #this is the "YourCustomUser" that you imported at the top of the file
fields = ('username', 'password1', 'password2', #etc etc, other fields you want displayed on the form)
BIG NOTES, ATTENTION:
This code worked for my case. I have a view for signing users up, I had a problem here and I solved it, I haven't tried it for logging in users.
The include = () part is required, or you can add exclude = (), but you have to have one
Important caveat to update the above solutions...
If you're facing this kind of problem, you've probably tried various solutions around the web telling you to add AUTH_USER_MODEL = users.CustomUser to settings.py and then to add the following code to views.py forms.py and any other file that calls User:
from django.contrib.auth import get_user_model
User = get_user_model()
And then you scratch your head when you get the error:
Manager isn't available; 'auth.User' has been swapped for 'users.User'
Anytime your code references User such as:
User.objects.get()
Cause you know you already put objects = UserManager() in your custom user class (UserManager being the name of your custom manager that extends BaseUserManager).
Well as it turns out doing:
User = get_user_model() # somewhere at the top of your .py file
# followed by
User.objects.get() # in a function/method of that same file
Is NOT equivalent to:
get_user_model().objects.get() # without the need for User = get_user_model() anywhere
Perhaps not intuitive, but it turns out that that in python, executing User = get_user_model() once at the time of import does not then result in User being defined across subsequent calls (i.e. it does not turn User into a "constant" of sorts which you might expect if you're coming from a C/C++ background; meaning that the execution of User = get_user_model() occurs at the time of imports, but is then de-referenced before subsequent called to class or function/method in that file).
So to sum up, in all files that reference the User class (e.g. calling functions or variables such as User.objects.get() User.objects.all() User.DoesNotExist etc...):
# Add the following import line
from django.contrib.auth import get_user_model
# Replace all references to User with get_user_model() such as...
user = get_user_model().objects.get(pk=uid)
# instead of user = User.objects.get(pk=uid)
# or
queryset = get_user_model().objects.all()
# instead of queryset = User.objects.all()
# etc...
Hope this helps save others some time...
In forms.py
# change
from django.contrib.auth.models import User
# to
from django.contrib.auth import get_user_model
Then add the following code at the top
User = get_user_model()
All the solutions provided above did not work in my case. If you using Django version 3.1 there is another solution for you:
In auth/forms, comment out line 10 and change the model in line 104 & 153 to your defined model.