I am new to Django, and currently experimenting with creating a custom user. Apart from some additional fields that my custom user has, I implemented that email is used for authentication instead of username. Also, username is generated by concatenating first name, last name and a number so that unique username would be obtained. Now, I've checked the email authentication and username generation which seems to work, however, the problem that I face is when I enter the Django Administration panel and open User table instead of 'admin' username there is blank. Furthermore, other fields, such as first name, last name, etc don't show for all users.
Screenshot of the admin panel
Custom user and pre_save method for username generation (users.models.py):
class User(AbstractUser):
personal_no = models.CharField(verbose_name=_('personal number'), max_length=13)
address = models.CharField(verbose_name=_('address'), max_length=50)
#receiver(pre_save, sender=User)
def generate_username_callback(**kwargs):
instance = kwargs['instance']
if not instance.pk:
instance.username = generate_username(instance.first_name, instance.last_name)
def generate_username(first_name, last_name):
val = "{0}{1}".format(first_name, last_name).lower()
count = 0
while True:
if count == 0 and User.objects.filter(username=val).count() == 0:
return val
else:
new_val = "{0}{1}".format(val, count)
if User.objects.filter(username=new_val).count() == 0:
return new_val
count += 1
if count > 10000:
raise Exception("Too many users named {0}".format(val))
Custom backend for email authentication (users.backends.py):
class EmailBackend(object):
def authenticate(self, username=None, password=None, **kwargs):
try:
user = User.objects.get(email=username)
except User.DoesNotExist:
return None
else:
if getattr(user, 'is_active', False) and user.check_password(password):
return user
return None
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
users.admin.py:
from django.contrib import admin
from .models import User
admin.site.register(User)
settings.py:
AUTHENTICATION_BACKENDS = [
'users.backends.EmailBackend'
]
AUTH_USER_MODEL = 'users.User'
I am using Django 1.10.4
The first_name and last_name fields are missing from the table on the changelist field, because you have not specified a ModelAdmin class when you registered your model. To use the default UserAdmin class, you would do:
from django.contrib.auth.admin import UserAdmin
admin.site.register(User, UserAdmin)
Related
I am a newbie in django, I am at learning stage I am creating a project appointment system where user and doctor can login . I want to register them as multiuser type for that I have extended abstract class and given one to one relation to other tables but when The code is execute and I click submit This error appears.
all fields are correct there is no error in the code everything is working correct just this integrity error is not getting solved. I have not created any foriegn key just trying to create user at of the user registration .
authenticate.models
from django.db import models
from django.contrib.auth.models import AbstractUser
from django.contrib.auth.models import User
# Create your models here.
class User(AbstractUser):
is_user = models.BooleanField('user', default=False)
is_doctor = models.BooleanField('doctor', default=False)
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
class User_reg(models.Model):
user = models.OneToOneField('User',on_delete=models.CASCADE, primary_key=True)
fname = models.CharField(max_length=50,blank=False)
lname = models.CharField(max_length=50,blank=False)
email = models.EmailField(max_length=100,blank=False)
address = models.TextField(max_length=500,blank=False)
gender = models.CharField(max_length=7, blank=False)
phone = models.CharField(max_length=12,unique=True,blank=False)
Username = models.CharField(max_length=100,blank=False,unique=True)
Userpassword = models.CharField(max_length=100,blank=False)
views.py
from django.shortcuts import render
from django.contrib import messages
# from django.contrib.auth.models import get_user_model
from django.contrib.auth.models import User
from authenticate_me.models import User_reg, dr_reg
def login(request):
return render(request, 'user/login.html')
def register(request):
if request.method == "POST":
fname = request.POST.get('fname')
lname = request.POST.get('lname')
email = request.POST.get('email')
address = request.POST.get('add')
gender = request.POST.get('sex')
phone = request.POST.get('phone')
username = request.POST.get('uname')
userpassword = request.POST.get('upass')
register = User_reg(fname=fname,lname=lname,email=email,address=address,gender=gender,phone=phone,Username=username,Userpassword=userpassword)
register.save()
myuser = User.objects.create_user(username, email, userpassword)
myuser.first_name = fname
myuser.last_name = lname
myuser.save()
messages.success(request, 'Register successful')
return render(request,'user/login.html')
else:
return render(request, 'user/register.html')
setting
AUTH_USER_MODEL = "authenticate_me.User"
You should first create the User, and then the User_reg object such that you can populate the user field with the myuser object.
def register(request):
if request.method == 'POST':
fname = request.POST.get('fname')
lname = request.POST.get('lname')
email = request.POST.get('email')
address = request.POST.get('add')
gender = request.POST.get('sex')
phone = request.POST.get('phone')
username = request.POST.get('uname')
userpassword = request.POST.get('upass')
myuser = User.objects.create_user(
username, email, userpassword, first_name=fname, last_name=lname
)
User_reg.objects.create(
fname=fname,
lname=lname,
email=email,
address=address,
gender=gender,
phone=phone,
Username=username,
Userpassword=userpassword,
user=myuser
)
messages.success(request, 'Register successful')
return redirect('login')
return render(request, 'user/register.html')
It is however "odd" and a bit "ugly" to have two model objects, especially since you store the same information in both models. This is a form of data duplication which often eventually will make it harder to keep the data in sync.
Storing the password as plain text is also a severe security issue: Django will hash the password in the user mode with .create_user(…). That is not the case with how you store it in your model.
Furthermore I strongly advise to work with a form to process, validate and clean the user input: right now if a parameter is missing, it will error. Furthermore people can enter whatever they want: the email address does not need to be text that is formatted as an email address, and one can use any gender as "free text".
I have the following schema in my graphene-django application:
import graphene
from django.contrib.auth import get_user_model
from graphene_django import DjangoObjectType
class UserType(DjangoObjectType):
class Meta:
model = get_user_model()
fields = ("id", "username", "email")
class Query(object):
user = graphene.Field(UserType, user_id=graphene.Int())
def resolve_user(self, info, user_id):
user = get_user_model().objects.get(pk=user_id)
if info.context.user.id != user_id:
# If the query didn't access email field -> query is ok
# If the query tried to access email field -> raise an error
else:
# Logged in as the user we're querying -> let the query access all the fields
I want to be able to query the schema in the following way:
# Logged in as user 1 => no errors, because we're allowed to see all fields
query {
user (userId: 1) {
id
username
email
}
}
# Not logged in as user 1 => no errors, because not trying to see email
query {
user (userId: 1) {
id
username
}
}
# Not logged in as user 1 => return error because accessing email
query {
user (userId: 1) {
id
username
email
}
}
How can I make it so that only a logged in user can see the email field of their own profile and no one else can see the emails of others?
Here's the approach I would take based on the comments. The main issue here is to be able to get a list of fields requested by a query in the resolver. For that, I use a code adapted from here:
def get_requested_fields(info):
"""Get list of fields requested in a query."""
fragments = info.fragments
def iterate_field_names(prefix, field):
name = field.name.value
if isinstance(field, FragmentSpread):
results = []
new_prefix = prefix
sub_selection = fragments[name].selection_set.selections
else:
results = [prefix + name]
new_prefix = prefix + name + '.'
sub_selection = \
field.selection_set.selections if field.selection_set else []
for sub_field in sub_selection:
results += iterate_field_names(new_prefix, sub_field)
return results
results = iterate_field_names('', info.field_asts[0])
return results
The rest should be quite straightforward:
import graphene
from django.contrib.auth import get_user_model
from graphene_django import DjangoObjectType
class AuthorizationError(Exception):
"""Authorization failed."""
class UserType(DjangoObjectType):
class Meta:
model = get_user_model()
fields = ("id", "username", "email")
class Query(object):
user = graphene.Field(UserType, user_id=graphene.Int())
def resolve_user(self, info, user_id):
user = get_user_model().objects.get(pk=user_id)
if info.context.user.id != user_id:
fields = get_requested_fields(info)
if 'user.email' in fields:
raise AuthorizationError('Not authorized to access user email')
return user
I ended up just doing it like this, where the actual value of email is returned when querying one's own info, and None is returned for others:
import graphene
from django.contrib.auth import get_user_model
from graphene_django import DjangoObjectType
class UserType(DjangoObjectType):
class Meta:
model = get_user_model()
fields = ("id", "username", "email")
def resolve_email(self, info):
if info.context.user.is_authenticated and self.pk == info.context.user.pk:
return self.email
else:
return None
class Query(graphene.ObjectType):
user = graphene.Field(UserType, user_id=graphene.Int())
def resolve_user(self, info, user_id):
return get_user_model().objects.get(pk=user_id)
The current answer is wayyy overcomplicated. Just create two ObjectTypes e.g.:
class PublicUserType(DjangoObjectType):
class Meta:
model = get_user_model()
fields = ('id', 'username')
class PrivateUserType(DjangoObjectType):
class Meta:
model = get_user_model()
Spent over 4 hours trying other solutions before realized it was this simple
I have to add a backward-compatible Django application that supports legacy passwords persisted in a database created with the use of PHP function password_hash() which output is like
$2y$10$puZfZbp0UGMYeUiyZjdfB.4RN9frEMy8ENpih9.jOEngy1FJWUAHy
(salted blowfish crypt algorithm with 10 hashing rounds)
Django supports formats with the prefixed name of the algorithm so if I use BCryptPasswordHasher as the main hasher output will be like:
bcrypt$$2y$10$puZfZbp0UGMYeUiyZjdfB.4RN9frEMy8ENpih9.jOEngy1FJWUAHy
I have created custom BCryptPasswordHasher like:
class BCryptPasswordHasher(BasePasswordHasher):
algorithm = "bcrypt_php"
library = ("bcrypt", "bcrypt")
rounds = 10
def salt(self):
bcrypt = self._load_library()
return bcrypt.gensalt(self.rounds)
def encode(self, password, salt):
bcrypt = self._load_library()
password = password.encode()
data = bcrypt.hashpw(password, salt)
return f"{data.decode('ascii')}"
def verify(self, incoming_password, encoded_db_password):
algorithm, data = encoded_db_password.split('$', 1)
assert algorithm == self.algorithm
db_password_salt = data.encode('ascii')
encoded_incoming_password = self.encode(incoming_password, db_password_salt)
# Compare of `data` should only be done because in database we don't persist alg prefix like `bcrypt$`
return constant_time_compare(data, encoded_incoming_password)
def safe_summary(self, encoded):
empty, algostr, work_factor, data = encoded.split('$', 3)
salt, checksum = data[:22], data[22:]
return OrderedDict([
('algorithm', self.algorithm),
('work factor', work_factor),
('salt', mask_hash(salt)),
('checksum', mask_hash(checksum)),
])
def must_update(self, encoded):
return False
def harden_runtime(self, password, encoded):
data = encoded.split('$')
salt = data[:29] # Length of the salt in bcrypt.
rounds = data.split('$')[2]
# work factor is logarithmic, adding one doubles the load.
diff = 2 ** (self.rounds - int(rounds)) - 1
while diff > 0:
self.encode(password, salt.encode('ascii'))
diff -= 1
And AUTH_USER_MODEL like:
from django.contrib.auth.hashers import check_password
from django.db import models
class User(models.Model):
id = models.BigAutoField(primary_key=True)
email = models.EmailField(unique=True)
password = models.CharField(max_length=120, blank=True, null=True)
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = []
EMAIL_FIELD = 'email'
def check_password(self, raw_password):
def setter():
pass
alg_prefix = "bcrypt_php$"
password_with_alg_prefix = alg_prefix + self.password
return check_password(raw_password, password_with_alg_prefix, setter)
Settings base.py:
...
AUTH_USER_MODEL = 'custom.User'
PASSWORD_HASHERS = [
'custom.auth.hashers.BCryptPasswordHasher',
]
...
In that case, before the validation of password, I add bcrypt$ prefix and then do validation but in the database, the password is kept without bcrypt$.
It works but I'm wondering if there is some other easier way to do this, or maybe someone meets the same problem?
I want to add that both PHP application and new Django should support both formats and I cannot do changes on the legacy PHP. Changes only could be done on new Django server.
You can use your Custom authentication backend.
First create a file in your auth app (say you name it auth), call the file backends.py
Contents of backends.py
from django.contrib.auth import get_user_model
from django.contrib.auth.backends import BaseBackend
UserModel = get_user_model()
class ModelBackend(BaseBackend):
"""
Authenticate against the settings ADMIN_LOGIN and ADMIN_PASSWORD.
Use the login name and a hash of the password. For example:
ADMIN_LOGIN = 'admin'
ADMIN_PASSWORD = 'pbkdf2_sha256$30000$Vo0VlMnkR4Bk$qEvtdyZRWTcOsCnI/oQ7fVOu1XAURIZYoOZ3iq8Dr4M='
"""
def authenticate(self, request, username=None, password=None, **kwargs):
if username is None:
username = kwargs.get(UserModel.USERNAME_FIELD)
if username is None or password is None:
return
try:
user = UserModel._default_manager.get_by_natural_key(username)
except UserModel.DoesNotExist:
# Run the default password hasher once to reduce the timing
# difference between an existing and a nonexistent user (#20760).
UserModel().set_password(password)
else:
if user.check_password(password):
# user exists
return user
Then in your settings.py include your backends.py in AUTHENTICATION_BACKENDS variables
It should look something like this
AUTHENTICATION_BACKENDS = [
"djangoprojectname.auth.backends.ModelBackend",
"django.contrib.auth.backends.ModelBackend",
]
Last point is to implement the check_password method in your auth model, based in our example here, we open file models.py in auth app, within the class User we add the method to implement check password based on bcrypt algorithm.
import bcrypt
class User(AbstractUser):
first_name = models.CharField(max_length=255, null=True)
email = models.CharField(unique=True, max_length=255, blank=True, null=True)
def check_password(self, raw_password):
def setter():
pass
check = bcrypt.checkpw(bytes(raw_password, 'utf-8'), bytes(self.password, 'utf-8'))
return check
def set_password(self, raw_password):
hashed = bcrypt.hashpw(bytes(raw_password, 'utf-8'), bcrypt.gensalt(rounds=10))
encrypted = str(hashed, 'UTF-8')
self.password = encrypted
self._password = encrypted
Then, in your login view probably you will have to implement something like this, in your views.py in auth app
username = data.get("username")
password = data.get("password")
if username is None or password is None:
pass # implement for requesting username and password
user = authenticate(username=username, password=password)
if user is None:
pass # implement for invalid credentials
# check user confirmation
confirmed = getattr(user, 'confirmed', None)
if confirmed is False or None:
pass # implement for user not confirmed
# check if user is active
isactive = getattr(user, 'isactive', None)
if isactive is False or None:
pass # implement for user account disabled
login(request, user)
# return view or json response or whatever for login success
To solve the inversed task of password checking for hashes generated with PHP's password_hash(password, PASSWORD_DEFAULT)
from django.contrib.auth.hashers import check_password
check_password(decoded_pass, 'bcrypt${0}'.format(php_hash), preferred='bcrypt')
worked for me.
Tested with Django 2.2. The bcrypt$ prefix hack is based on another SO answer.
I've created a signup form which although successfully creates a user fails to login as authenticate fails to assign a backend, even though user is then marked as is_authenticated=True
Is there something incorrect in the way that I'm using authenticate?
(note I'm using django all_auth but not sure if that has an impact here?)
At login() it generated this error:
ValueError:You have multiple authentication backends configured and therefore must provide the backend argument or set the backend attribute on the user.
view:
....
form = ProfileForm(request.POST)
if form.is_valid():
user_profile, user = form.save()
authenticate(request, user=form.cleaned_data['email'],
password=form.cleaned_data['password1'])
login(request, user)
models.py
class User(AbstractUser):
def __str__(self):
return self.username
def get_absolute_url(self):
return reverse('users:detail', kwargs={'username': self.username})
class UserProfile(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL)
TITLE_CHOICES = (
.....
)
title = models.CharField(max_length=5, null=True, choices=TITLE_CHOICES)
date_of_birth = models.DateField()
primary_phone = PhoneNumberField()
EMPLOYMENT_CHOICES = (
(....,...)
)
employment_status = models.CharField(max_length=35, choices=EMPLOYMENT_CHOICES)
Profile form:
class ProfileForm(allauthforms.SignupForm):
title = FieldBuilder(UserProfile, 'title', )
first_name = forms.CharField(max_length=30)
last_name = forms.CharField(max_length=30)
date_of_birth = FieldBuilder(UserProfile, 'date_of_birth', widget=SelectDateWidget())
primary_phone = FieldBuilder(UserProfile, 'primary_phone')
employment_status = FieldBuilder(UserProfile, 'employment_status')
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["employment_status"].choices = [("", "---- Select your Employment Status ----"), ] + list(
self.fields["employment_status"].choices)[1:]
def save(self, *args):
data = self.cleaned_data
user = User.objects.create_user(
username=data['email'],
email=data['email'],
password=data['password1'],
first_name=data['first_name'],
last_name=data['last_name'],
)
instance = UserProfile.objects.create(
user=user,
date_of_birth=data['date_of_birth'],
primary_phone=data['primary_phone'],
title=data['title'],
employment_status=data['employment_status'],
)
return instance, user
When you call authenticate, it will return a user if the authentication was successful. You should use this user when you call login.
user = authenticate(request, username=form.cleaned_data['email'],
password=form.cleaned_data['password1'])
login(request, user)
Note that unless you have a custom authentication backend, you should pass username instead of user.
In Django 1.10+, you don't have to call authenticate if you already have the user instance. When you call login, you can provide the backend as an argument, for example:
login(request, user, backend='django.contrib.auth.backends.ModelBackend')
See the docs on selecting the authentication backend for more info.
You will set multiple authentication backends in "settings.py" as shown below:
# "settings.py"
AUTHENTICATION_BACKENDS = [
"django.contrib.auth.backends.ModelBackend",
# ...,
# ...,
]
In this case, you need to set backend='django.contrib.auth.backends.ModelBackend' to "login()" in "views.py" as shown below then the error will solve:
# "views.py"
....
form = ProfileForm(request.POST)
if form.is_valid():
user_profile, user = form.save()
authenticate(request, user=form.cleaned_data['email'],
password=form.cleaned_data['password1'])
login(request, user, backend='django.contrib.auth.backends.ModelBackend')
# ↑ ↑ ↑ Here ↑ ↑ ↑
i am a beginner in django. I am working on a project in which customer and companies have their own accounts the models.py is:
class Company_SignUp(models.Model):
comp_name = models.CharField(_('Company Name'), max_length=30)
email = models.EmailField(_('E-mail'), unique=True)
raise forms.ValidationError("This email address already exists.")
password1 = models.CharField(_('Password'), max_length=128)
password2 = models.CharField(_('Confirm Password'), max_length=30)
def __unicode__(self):
return smart_unicode(self.comp_name)
class Customer_SignUp(models.Model):
cust_name = models.CharField(_('Customer Name'), max_length=30)
email = models.EmailField(_('E-mail'), unique=True)
password1 = models.CharField(_('Password'), max_length=128)
password2 = models.CharField(_('Confirm Password'), max_length=30)
def __unicode__(self):
return smart_unicode(self.cust_name)
my forms.py is:
class Company(forms.ModelForm):
class Meta:
model = Company_SignUp
widgets = {
'password1': forms.PasswordInput(),
'password2': forms.PasswordInput(),
}
fields = ('email','password1','password2','comp_name')
def clean(self):
if 'password1' in self.cleaned_data and 'password2' in self.cleaned_data:
if self.cleaned_data['password1'] != self.cleaned_data['password2']:
raise forms.ValidationError(_("The two password fields did not match."))
elif len(self.cleaned_data['password1']) < 8:
raise forms.ValidationError(_("The password must be 8 characters long."))
return self.cleaned_data
class Customer(forms.ModelForm):
class Meta:
model = Customer_SignUp
widgets = {
'password1': forms.PasswordInput(),
'password2': forms.PasswordInput(),
}
def clean(self):
if 'password1' in self.cleaned_data and 'password2' in self.cleaned_data:
if self.cleaned_data['password1'] != self.cleaned_data['password2']:
raise forms.ValidationError(_("The two password fields did not match."))
elif len(self.cleaned_data['password1']) < 8:
raise forms.ValidationError(_("The password must be 8 characters long."))
return self.cleaned_data
how will i authenticate a company or a customer using their email and passwords.
i tried authenticate() but it doesn't work.
also how will i check during registration , the email address given already exists
ok now i created a backend which is:
from django.contrib.auth.models import User
from prmanager.models import Company_SignUp, Customer_SignUp
class EmailBackend(object):
def authenticate(self, username=None, password=None):
try:
o = Company_SignUp.objects.get(email=username, password1=password)
except Company_SignUp.DoesNotExist:
try:
o = Customer_SignUp.objects.get(email=username, password1=password)
except Customer_SignUp.DoesNotExist:
return None
return User.objects.get(email=o.email)
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
But now i cannot login to admin page using superuser credentials. what should i do
Models
Consider extending the User model from django.contrib.auth.models like so. If you don't want to do this, skip to the next section (Authentication).
from django.contrib.auth.models import User
class Customer(User):
# extra fields
The User model has common fields such as username,first_name,last_name,email, etc. You only need to specify any extra attributes your model may have.
The Django docs suggest extending AbstractBaseUser, which may work for you too.
Read more here: https://docs.djangoproject.com/en/1.7/topics/auth/customizing/#extending-the-existing-user-model
Authentication
For email-based authentication, you need to write your own authentication backend: https://docs.djangoproject.com/en/1.7/topics/auth/customizing/#writing-an-authentication-backend
Once you have that in place, you need to accept email / password and authenticate using authenticate and login.
from django.contrib.auth import authenticate, login
def my_view(request):
email = request.POST['email']
password = request.POST['password']
user = authenticate(email=email, password=password)
if user is not None:
if user.is_active:
login(request, user)
# Redirect to a success page.
else:
# Return a 'disabled account' error message
else:
# Return an 'invalid login' error message.
The above snippet is from the docs and I have modified it to fit your use-case.
More about authentication in Django: https://docs.djangoproject.com/en/1.7/topics/auth/default/#how-to-log-a-user-in