I'm reading the Django 3.2 documentation (custom authentication) and there are some lines of code that I can't understand.
I will try to read and explain what I can understand, or what I think I understand. Please correct me if I am wrong
Resource link: https://docs.djangoproject.com/es/3.2/topics/auth/customizing/
Code:
from django.db import models
from django.contrib.auth.models import (
BaseUserManager, AbstractBaseUser
)
class MyUserManager(BaseUserManager):
def create_user(self, email, date_of_birth, password=None):
"""
Creates and saves a User with the given email, date of
birth and password.
"""
if not email:
raise ValueError('Users must have an email address')
user = self.model(
email=self.normalize_email(email),
date_of_birth=date_of_birth,
)
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, email, date_of_birth, password=None):
"""
Creates and saves a superuser with the given email, date of
birth and password.
"""
user = self.create_user(
email,
password=password,
date_of_birth=date_of_birth,
)
user.is_admin = True
user.save(using=self._db)
return user
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'
REQUIRED_FIELDS = ['date_of_birth']
def __str__(self):
return self.email
def has_perm(self, perm, obj=None):
"Does the user have a specific permission?"
# Simplest possible answer: Yes, always
return True
def has_module_perms(self, app_label):
"Does the user have permissions to view the app `app_label`?"
# Simplest possible answer: Yes, always
return True
#property
def is_staff(self):
"Is the user a member of staff?"
# Simplest possible answer: All admins are staff
return self.is_admin
This method is used to create a standard user, it receives 2 main parameters: email and password.
def create_user(self, email, password=None):
if not email:
raise ValueError("Users must have an email address")
user = self.model(
email=self.normalize_email(email),
)
user.set_password(password)
user.save(using=self._db)
return user
If the field is not of the email type, execute error:
if not email:
raise ValueError("Users must have an email address")
I dont understand; I know that the normalize_email method puts all text in lowercase. But I don't understand the self.model (): this is not a method, is it? Shouldn't it be like this: user.email = self.normalize_email (email)?
user = self.model(
email=self.normalize_email(email),
)
The set_password method takes as an argument 'password' that the user entered, encrypts it and stores it in a structure or instance called user.
user.set_password(password)
The "save" method stores the record we have entered in the database. I have no idea how using = self._db works. Wouldn't it be enough to just call the save method? example user.save()
user.save(using=self._db)
create_superuser
This method creates an Admin, receives an email and a password. But I don't understand why you are referring to the create_user method.
user = self.create_user(...)
There is a line of code in the model that I don't understand its functionality either. Why instantiate the MyUserManager class in a variable called objects?
objects = MyUserManager()
Please explain me in an easy way
Related
So i am pretty new to Django. Actually my first project.
I want to create a custom model "Logging" in which i want to log the admin login attempts and count the attempts. After 3 failed login attempts the user must me locked out. Ive already created a custom User model like this.
class UserManager(BaseUserManager):
def create_user(self,username,password,description):
if not username:
raise ValueError('Users must have an username ')
user = self.model(username=username)
user.set_password(password)
user.description = description
user.save(using=self._db)
return user
def create_staffuser(self, username, password, description):
user = self.create_user(
username,
password,
description
)
user.staff = True
user.save(using=self._db)
return user
def create_superuser(self, username, password, description):
user = self.model(username=username)
user.set_password(password)
user.description = description
user.staff = True
user.admin = True
user.save(using=self._db)
return user
class Users(AbstractBaseUser):
username = models.CharField(max_length=15,unique=True)
description = models.CharField(max_length=50,default="None")
is_active = models.BooleanField(default=True)
staff = models.BooleanField(default=False) # a admin user; non super-user
admin = models.BooleanField(default=False) # a superuser
# notice the absence of a "Password field", that is built in.
USERNAME_FIELD = 'username'
REQUIRED_FIELDS = ['description']
def get_full_name(self):
return self.username
def get_short_name(self):
return self.username
def __str__(self):
return self.username
def has_perm(self, perm, obj=None):
"Does the user have a specific permission?"
# Simplest possible answer: Yes, always
return True
def has_module_perms(self, app_label):
"Does the user have permissions to view the app `app_label`?"
# Simplest possible answer: Yes, always
return True
#property
def is_staff(self):
"Is the user a member of staff?"
return self.staff
#property
def is_admin(self):
"Is the user a admin member?"
return self.admin
objects = UserManager()
So how can i log custom admin attempts?
You can make signal receivers for the user_login_failed [Django-doc] and user_logged_in [Django-doc] signals. We can thus create a model that looks like:
from django.conf import settings
class FailedLogin(models.Model):
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE
)
timestamp = models.DateTimeField(auto_now_add=True)
Then we define two signal receivers, one for a successful login that will remove FailedLogin records (if any):
from django.contrib.auth import get_user_model
from django.contrib.auth.signals import user_logged_in, user_login_failed
from django.dispatch import receiver
#receiver(user_logged_in)
def user_logged_recv(sender, request, user, **kwargs):
FailedLogin.objects.filter(user=user).delete()
#receiver(user_login_failed)
def user_login_failed_recv(sender, credentials, request):
User = get_user_model()
try:
u = User.objects.get(username=credentials.get('username'))
# there is a user with the given username
FailedLogin.objects.create(user=u)
if FailedLogin.objects.filter(user=u).count() >= 3:
# three tries or more, disactivate the user
u.is_active = False
u.save()
except User.DoesNotExist:
# user not found, we can not do anything
pass
When trying to authenticate a user created through a view in DRF Browsable API, I get
No active account found with the given credentials
The view:
class MyUserCreate(APIView):
def post(self, request, format='json'):
serializer = MyUserSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
The serializer:
class MyUserSerializer(serializers.ModelSerializer):
username = serializers.CharField(
required=True,
validators=[UniqueValidator(queryset=MyUser.objects.all())],
min_length=5,
max_length=20
),
password = serializers.CharField(
required=True,
max_length=256
)
class Meta:
model = MyUser
fields = ('username', 'password')
def create(self, validated_data):
password = make_password(validated_data['password'])
user = MyUser.objects.create_user(validated_data['username'], password)
return user
The password is being hashed. At this stage, went on to the admin page and tested to login there too with the created account and got
Made sure the custom user model had is_active, is_superuser and is_staff and checked if that would fix the issue but it didn't.
class MyUserManager(BaseUserManager):
def create_user(self, username, password, **extra_fields):
user = self.model(
username=username
)
user.is_staff=True
user.is_active=True
user.is_superuser=True
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, username, password, **extra_fields):
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(username, password, **extra_fields)
class MyUser(AbstractBaseUser):
objects = MyUserManager()
class Meta:
# managed = False
db_table = 'user_entity'
user_id = models.AutoField(primary_key=True, db_column='userId')
username = models.CharField(db_column='username', unique=True, max_length=20)
password = models.CharField(db_column='userPassword', max_length=256)
is_active = models.BooleanField(default=True)
is_staff = models.BooleanField(default=True)
is_superuser = models.BooleanField(default=True)
USERNAME_FIELD = 'username'
def __str__(self):
return str(self.user_id) + " (%s)" % str(self.username)
def has_perm(self, perm, obj=None):
return True
def has_module_perms(self, app_label):
return True
Then, tried to create the user using python manage.py createsuperuser... doing so then I'm able to both login in the admin pannel and authenticate that user with the Token Obtain Pair view shown initially.
First of all. Thank you for providing all the details. It is always easier to debug. Coming to the issue, the problem is that you are using make_password explicitly.
If you would look through the Django's set_password documentation, you'd find that it takes care of hashing.
What you are doing is, first you are hashing your password, and providing it as a raw value to set_password method via create_user. The set_password would assume that as a raw value and would hash it again. Hence, your original password is not used at all.
You can just remove the use of make_password and instead change your serializer's create method to
class MyUserSerializer(serializers.ModelSerializer):
...
def create(self, validated_data):
user = MyUser.objects.create_user(validated_data['username'], validated_data["password"])
return user
This should resolve your query. I hope it helps! :)
I'm following a tutorial book for Django (Django Unleashed) and I think there is a problem with the code when using newer versions of Django (i've read about problems with this chaper on amazon as well). So for future students of this book I ask this openly and in relation to the book.
For the creation of a custom user model the book suggests a user model that does not have a 'username' field. This Error occursd during makemigrations:
django.core.exceptions.FieldError: Unknown field(s) (username) specified for User
There are other topics about this error on StackOverflow and they suggest that django.contrib.auth.admin.UserAdminneeds the 'username' specified and therefore suggest adding an attribute called add_fieldsets to a class that inherits form UserAdmin. My question (so I dont have to get ahead of the code in the book in run into other problems) ... is it possible to create a custom user profile without specifiying a username and without the admin class? Here is my code so far:
User model
class User(AbstractBaseUser, PermissionsMixin):
email = models.EmailField('email adress', max_length=254, unique=True)
is_staff = models.BooleanField('staff status', default=False)
is_active = models.BooleanField('active', default=False)
name = models.CharField(max_length=255)
joined = models.DateTimeField("Date Joined", auto_now_add=True)
USERNAME_FIELD = 'email'
objects = UserManager()
def __str__(self):
return self.email
def get_absolute_url(self):
return self.profile.get_absolute_url()
def get_full_name(self):
return self.name
def get_short_name(self):
return self.profile.name
Usermanager
class UserManager(BaseUserManager):
use_in_migrations = True
def _create_user(
self, email, password, **kwargs):
email = self.normalize_email(email)
is_staff = kwargs.pop('is_staff', False)
is_superuser = kwargs.pop(
'is_superuser', False)
user = self.model(
email=email,
is_active=True,
is_staff=is_staff,
is_superuser=is_superuser,
**kwargs)
user.set_password(password)
user.save(using=self._db)
return user
def create_user(
self, email, password=None,
**extra_fields):
return self._create_user(
email, password, **extra_fields)
def create_superuser(
self, email, password,
**extra_fields):
return self._create_user(
email, password,
is_staff=True, is_superuser=True,
**extra_fields)
And I want to perform makemigrations without an error
i think you need to use AbstractUser instead of the AbstractBaseUser
class User(AbstractUser, PermissionsMixin):
or need declare username for your model, for example:
username = models.CharField(_('username'), max_length=30, unique=True)
I have a custom user model based off of AbstractUser and, I use a custom UserManager, I don't know if theres anything special I have to do to get authenticate to work. I know the user is in the db because I can do objects.get(username, password) and it will return the object.
class PassThroughFarmerManager(PassThroughManagerMixin, UserManager):
use_in_migrations = False
class Farmer(FarmStateble, MapPointable, LastRequestStorable, AbstractUser):
last_irrigation_cycle = models.DateTimeField(auto_now_add=False, auto_now=False, null = True, blank=True)
objects = PassThroughFarmerManager.for_queryset_class(FarmerQuerySet)()
Here is an example of my console output,
>>> models.Farmer.objects.get(username='901e2ac5-9324-11e5-81bf-c42c0323e33a').password
u'1223'
>>> u = authenticate(username = '901e2ac5-9324-11e5-81bf-c42c0323e33a', password = '1223')
>>> u
>>> type(u)
<type 'NoneType'>
When you use MyUserModel.objects.create(...), the password is stored in the database in plain text. The call to authenticate does not work, because Django expects the password to be hashed in the database.
Therefore, when you create a user, you need to ensure that the password is hashed, rather than being stored in plain text in the database. You can do this by calling user.set_password('new_password').
The full example in the docs shows a create_user manager method that calls set_password when creating the user. You would then use MyUserModel.objects.create_user(...) instead of MyUserModel.objects.create(...).
class MyUserManager(BaseUserManager):
def create_user(self, email, date_of_birth, password=None):
"""
Creates and saves a User with the given email, date of
birth and password.
"""
if not email:
raise ValueError('Users must have an email address')
user = self.model(
email=self.normalize_email(email),
date_of_birth=date_of_birth,
)
user.set_password(password)
user.save(using=self._db)
return user
I am trying to modify my Django project authentication so I can use my own User model.
I have got it working so far, however I am unable to override the "password" field. I want to change the name to "password_hash".
I have tried this manager:
class UserManager(BaseUserManager):
def create_user(self, email_address, full_name, password=None):
if not email_address:
raise ValueError('Users must have an email address')
user = self.model(
email_address = self.normalize_email(email_address),
full_name = full_name,
)
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, email_address, full_name, password_hash):
user = self.create_user(email_address,
password_hash=password_hash,
full_name=full_name,
)
user.is_admin = True
user.save(using=self._db)
return user
However I get the error TypeError: create_superuser() got an unexpected keyword argument 'password'.
How do I stop create_superuser() from expecting "password" and change it to "password_hash". For username I did it by changing the USERNAME_FIELD, however nothing in the documentation suggests a similar method for the password.
Thanks in advance,
Mark
The solution that worked for me was to override this method in my new user model:
def set_password(self, raw_password):
self.password = make_password(raw_password)