I use a custom user model so I can authenticate using email instead of username.
from django.db import models
from django.contrib.auth.models import (
AbstractBaseUser,
BaseUserManager,
PermissionsMixin,
)
class UserManager(BaseUserManager):
def create_user(
self,
email,
password,
confirm_code=None,
username=None,
role=None,
):
user = self.model(email=self.normalize_email(email))
user.set_password(password)
user.confirm_code = confirm_code
user.save()
return user
def create_superuser(self, email, password, role, username=None):
user = self.model(email=self.normalize_email(email))
user.set_password(password)
user.role = role
user.is_staff = True
user.is_active = True
user.is_superuser = True
user.save()
return user
class User(AbstractBaseUser, PermissionsMixin):
EM = "EM"
SM = "SM"
DH = "DH"
ST = "ST"
ROLES = [
(EM, "Executive Management"),
(SM, "Senior Management"),
(DH, "Department Head"),
(ST, "Staff Member"),
]
objects = UserManager()
role = models.CharField(max_length=2, choices=ROLES, default=US, blank=True)
username = models.CharField(max_length=20, unique=True, blank=True, null=True)
email = models.EmailField(max_length=255, unique=True)
slug = models.SlugField(blank=True, null=True)
confirm_code = models.CharField(max_length=20, null=True, blank=True)
is_active = models.BooleanField(default=False)
is_staff = models.BooleanField(default=False)
has_profile = models.BooleanField(default=False)
email_verified_at = models.DateTimeField(auto_now=False, null=True, blank=True)
code = models.CharField(max_length=8, null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True, verbose_name="Created at")
updated_at = models.DateTimeField(auto_now=True, verbose_name="Updated at")
class Meta:
verbose_name = "User"
verbose_name_plural = "Users"
ordering = ["username"]
db_table = "users"
def get_absolute_url(self):
return f"{self.slug}"
USERNAME_FIELD = "email"
REQUIRED_FIELDS = ["role"]
When I register a user the user is created in the database correctly and the password is hashed I assume correctly, because I see the hash and not the raw password. Here is the method I used to register a user with:
#api_view(["POST"])
def RegisterUser(request, *args, **kwargs):
code = []
numbers = range(7)
for num in numbers:
code.append(randint(0, 9))
email = request.data["email"]
confirmemail = request.data["confirmemail"]
password = request.data["password"]
confirmpassword = request.data["confirmpassword"]
errors = []
if email != confirmemail:
errors.append("emailmatch")
return Response("Emails do not match", status=500)
if confirmpassword != password:
errors.append("passmatch")
return Response("Password do not match", status=500)
if User.objects.filter(email=email):
errors.append("emailexists")
return Response("User is already registered", status=500)
else:
pass
if len(errors) > 0:
return Response(False)
else:
password_to_save = make_password(password)
confirm_code = "".join(str(e) for e in code)
user = User.objects.create_user(
email=email, password=password_to_save, confirm_code=confirm_code
)
token = Token.objects.create(user=user)
from_email = "info#website.com"
link = f"http://127.0.0.1:8000/api/v1/users/confirm/{user.id}/{token}/"
context = {"link": link, "code": code}
template = get_template("emails/welcome.html").render(context)
subject = "Successfully registered"
message = "Welcome to website"
try:
send_mail(
subject,
message=message,
from_email=from_email,
recipient_list=[email],
html_message=template,
)
except:
return Response("Could not send mail")
serializer = RegisterSerializer(user)
return Response(serializer.data)
I have a custom backend to authenticate a user using an email instead of a username and here that is:
class EmailBackend(ModelBackend):
def authenticate(self, request, **kwargs):
email = kwargs.get("email", None)
user = User.objects.get(email=email)
if user:
breakpoint()
if user.check_password(kwargs.get("password", None)):
return user
else:
return None
else:
User.DoesNotExist
return None
It just doesn't work and I cannot log in. The user is active! If I do a breakpoint() as seen here and I check the password manually by entering the raw password in user.check_password('password') then it returns false. So isn't check_password supposed to then hash the raw password entered and compare that hash with the hash in the database or how does it work?
This works:
password = "hello"
saved = make_password(password)
true = check_password(password, saved)
So how why does it not work in my Authenticate backend which is exactly the same?
Do I need to use another hashing algorithm or what?
What am I supposed to do then and how can I authenticate a user? Is there another way to compare the password a user entered on a form and the hashed password saved in the database?
You are using set_password function in create_user in your manager. This method itself calls make_password function.
So, when you pass a hashed password generated via make_password to create_user, that hashed password would be hashed again in set_password function.
So there is no need to call make_password by yourself. Just pass plain password to create_user method.
Related
I've created a custom user model, subclassing AbstractUser.
I have a registration view, template, and a CustomUserCreationForm that seems to work fine, and can register users no problem via the front end.
My issue is getting the user logged in. I can't seem to pass the form validation to authenticate them with. I'm always returned with a None user object
With this line for example, I always get None, this failing verification
user = authenticate(request, email=email, password=password)
# user = User.objects.get(email=email, password=hashed_pass)
# Check if authentication successful
if user is not None:
login(request, user)
return HttpResponseRedirect(reverse("clientcare:portal/dashboard"))
else:
return render(request, "clientcare/login.html", {
"message": "Invalid email and/or password.",
'login_form':LoginForm,
})
Forms
class CustomUserCreationForm(UserCreationForm):
class Meta(UserCreationForm.Meta):
model = User
fields = ('email', 'first_name','last_name' ,'city', 'province','age','gender','phone_number','password1', 'password2',)
class LoginForm(forms.Form):
email = forms.EmailField(max_length=100)
password = forms.CharField(widget = forms.PasswordInput())
class CustomUserChangeForm(UserChangeForm):
class Meta:
model = User
fields = ('email', 'first_name','last_name' ,'city', 'province','age','gender','phone_number',)
Models
# Create your models here.
class UserManager(BaseUserManager):
use_in_migrations = True
def _create_user(self, email, password, **extra_fields):
if not email:
raise ValueError('Users require an email field')
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_user(self, email, password=None, **extra_fields):
extra_fields.setdefault('is_staff', False)
extra_fields.setdefault('is_superuser', False)
extra_fields.setdefault('is_patient', False)
extra_fields.setdefault('is_provider', True)
return self._create_user(email, password, **extra_fields)
def create_superuser(self, email, 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(email, password, **extra_fields)
class User(AbstractUser):
username = None
email = models.EmailField(_('email address'), unique=True)
image_height = models.PositiveIntegerField(null=True, blank=True, editable=False, default="200")
image_width = models.PositiveIntegerField(null=True, blank=True, editable=False, default="200")
date_joined = models.DateTimeField(auto_now=True, null=False, blank=False)
city = models.CharField(null=False, blank=False, max_length=20)
province = models.CharField(null=False, blank=False, max_length=20)
profile_image_url = models.ImageField(null=True, blank=True, upload_to='images/', editable=True)
paid = models.BooleanField(default=False)
phone_number = PhoneField(blank=True, null=True, help_text='Contact phone number', E164_only=False)
in_trial = models.BooleanField(default=True)
recently_active = models.BooleanField(default=True)
gender = models.CharField(choices=(("Male", "Male"),("Female", "Female"), ("Other", "Other")), max_length=6, default="Male", null=False, blank=False)
age = models.SmallIntegerField(max_length=3,null=False, blank=False)
objects = UserManager()
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['first_name', 'last_name',]
# classify if the user is a provider or a patient
is_patient = models.BooleanField('Patient status',default=False)
is_provider = models.BooleanField('Provider status',default=False)
def __str__(self):
return f"{self.get_full_name()}"
Login View
def login_view(request):
if request.method == "POST":
login_form = LoginForm(data=request.POST)
if login_form.is_valid():
email = login_form.cleaned_data['email']
password = login_form.cleaned_data['password']
# hashed_pass = bcrypt.hashpw(raw_pass, salt)
# if bcrypt.checkpw(raw_pass, hashed_pass):
user = authenticate(request, email=email, password=password)
# user = User.objects.get(email=email, password=hashed_pass)
# Check if authentication successful
if user is not None:
login(request, user)
return HttpResponseRedirect(reverse("clientcare:portal/dashboard"))
else:
return render(request, "clientcare/login.html", {
"message": "Invalid email and/or password.",
'login_form':LoginForm,
})
else:
return render(request, "clientcare/login.html", {
"message": "Invalid login data. Please try again",
'login_form':LoginForm,
})
else:
return render(request, "clientcare/login.html", {
'login_form':LoginForm,
})
Registration view
def register(request):
# Adding the salt to password
if request.method == "POST":
register_form = CustomUserCreationForm(request.POST)
if register_form.is_valid():
email = register_form.cleaned_data['email']
city = register_form.cleaned_data["city"]
province = register_form.cleaned_data["province"]
first_name = register_form.cleaned_data["first_name"]
last_name = register_form.cleaned_data["last_name"]
phone_number = register_form.cleaned_data["phone_number"]
age = register_form.cleaned_data["age"]
gender = register_form.cleaned_data["gender"]
# Ensure password matches confirmation
password = register_form.cleaned_data["password1"]
confirmation = register_form.cleaned_data["password2"]
if password != confirmation:
return render(request, "clientcare/register.html", {
"messsage": "Passwords must match."
})
# Hashing the password
# hashed = bcrypt.hashpw(password, salt)
# password = hashed
# Attempt to create new user
try:
user = User.objects.create(email=email, city=city, province=province, password=password, first_name=first_name, last_name=last_name, phone_number=phone_number, age=age, gender=gender)
user.save()
except IntegrityError:
return render(request, "clientcare/register.html", {
"message": "ERROR. TRY AGAIN",
})
login(request, user)
return HttpResponseRedirect(reverse("clientcare:index"))
else:
return render(request, "clientcare/register.html", {
"message": "ERROR. PLEASE CONFIRM REGISTRATION INFO",
})
else:
return render(request, "clientcare/register.html",{
'registration_form':CustomUserCreationForm
})
I have my user in settings.py as such:
AUTH_USER_MODEL = 'clientcare.User'
I'm well aware I can use AllAuth or other auth libraries. But I'm trying to understand things on a lower level before using such libraries.
Any help would be appreciated.
Nothing I try seems to work in getting my custom user model logged in. Do I need to write a custom backend? AuthenticationForm doesn't seem to work just as my own login form doesn't seem to validate
HOWEVER, if I update a users password via the admin(with superuser), then the user can login no problem with the updated password.. so my CustomUserChangeForm does the job. What am I missing?
I'm putting a model of my old project here as an example. Here I used the user model as it is and added the fields I wanted to add to the new class that user is also a field there. Django's auth module works as is and no additional coding was required.
from django.contrib.auth.models import User
from django.db import models
from django.utils.translation import ugettext_lazy as _
from backend.custom_fields import AutoOneToOneField
from backend import definitions
from django.urls import reverse
from django.db.models.signals import post_save
from django.dispatch import receiver
from backend.convert import convert_string_to_url_safe
from random import randint
from backend.image_operation import image_resize, image_convert_to_jpg
EMAIL_PRIVACY_CHOICES = (
(0, _('Display your e-mail address.')),
(1, _('Hide your e-mail address but allow form e-mail.')),
(2, _('Hide your e-mail address and disallow form e-mail.')),
)
PHONE_PRIVACY_CHOICES = (
(0, _('Display your phone no.')),
(1, _('Hide your phone no.')),
)
def avatar_upload_file_name_path(instance, filename_ext):
ext = filename_ext.split('.')[-1]
return f'profile_uploads/{convert_string_to_url_safe(instance.get_full_name)}-{randint(1000, 10000)}.{ext}'
def photo_upload_file_name_path(instance, filename_ext):
ext = filename_ext.split('.')[-1]
return f'profile_uploads/{convert_string_to_url_safe(instance.get_full_name)}-{randint(1000, 10000)}.{ext}'
def resized_photo_upload_file_name_path(instance, filename_ext):
ext = filename_ext.split('.')[-1]
return f'profile_uploads/resized-{convert_string_to_url_safe(instance.get_full_name)}-{randint(1000, 10000)}.{ext}'
class PlatformUser(models.Model):
user = AutoOneToOneField(User, on_delete=models.CASCADE)
slug = models.CharField(max_length=100, null=True, blank=True)
title = models.CharField(max_length=60, blank=True, default='')
description = models.TextField(blank=True, default='')
phone = models.CharField(max_length=25, blank=True, default='')
facebook_address = models.URLField(blank=True, default='')
twitter_address = models.URLField(blank=True, default='')
instagram_address = models.URLField(blank=True, default='')
linkedin_address = models.URLField(blank=True, default='')
youtube_address = models.URLField(blank=True, default='')
site = models.URLField(_('Personal Site'), blank=True)
skype_name = models.CharField(_('Skype Name'), max_length=100, blank=True, default='')
birth_date = models.DateTimeField(_('Birth Date'), blank=True, null=True)
location = models.CharField(_('Location'), max_length=30, blank=True)
photo = models.ImageField(upload_to=photo_upload_file_name_path, blank=True, default='')
photo_resized = models.ImageField(upload_to=resized_photo_upload_file_name_path, blank=True, null=True)
show_photo = models.BooleanField(_('Show avatar'), blank=True, default=True)
show_signatures = models.BooleanField(_('Show signatures'), blank=True, default=True)
show_smilies = models.BooleanField(_('Show smilies'), blank=True, default=True)
email_privacy_permission = models.IntegerField(_('Privacy permission'), choices=EMAIL_PRIVACY_CHOICES, default=1)
phone_privacy_permission = models.IntegerField(_('Privacy permission'), choices=PHONE_PRIVACY_CHOICES, default=1)
auto_subscribe = models.BooleanField(_('Auto subscribe'),
help_text=_("Auto subscribe all topics you have created or reply."),
blank=True, default=False)
post_count = models.IntegerField(_('Post count'), blank=True, default=0)
likes_count = models.IntegerField(default=0)
view_count = models.IntegerField(default=0)
signature = models.TextField(_('Sign'), blank=True, default='', max_length=definitions.SIGNATURE_MAX_LENGTH)
signature_html = models.TextField(_('Sign as HTML'), blank=True, default='',
max_length=definitions.SIGNATURE_MAX_LENGTH)
verification_code = models.CharField(_('Verify Code'), blank=True, default='', max_length=40)
created = models.DateTimeField(_('Created'), auto_now_add=True)
updated = models.DateTimeField(_('Updated'), auto_now=True, null=True)
class Meta:
ordering = ['user']
get_latest_by = 'created'
verbose_name = _('Platform Member')
verbose_name_plural = _('Platform Members')
def __init__(self, *args, **kwargs):
super(PlatformUser, self).__init__(*args, **kwargs)
self.__original_image_filename = self.photo.name
#property
def email(self):
return self.user.email
#property
def get_full_name(self):
full_name = str(self.user.first_name.title())
if len(full_name) > 0:
full_name += " "
full_name += str(self.user.last_name.title())
if len(full_name) == 0:
full_name = self.user.username
return full_name
#property
def username(self):
return self.user.username
#property
def first_name(self):
return self.user.first_name
#property
def last_name(self):
return self.user.last_name
def __str__(self):
return f"[{self.user.first_name} {self.user.last_name}] - ({self.user.email})"
def get_absolute_url(self):
return reverse('profile',
args=[str(self.get_slug())])
def get_slug(self):
if not self.slug:
self.slug = self.user.username
return self.slug
def save(self, *args, **kwargs):
if not self.slug:
self.slug = self.get_slug()
if self.photo and (self.photo.name != self.__original_image_filename or not self.photo_resized):
self.photo_resized = image_resize(400, 400, self.photo)
self.photo = image_convert_to_jpg(self.photo)
super(PlatformUser, self).save(*args, **kwargs)
#receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
if created:
PlatformUser.objects.create(user=instance)
#receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
platform_users = PlatformUser.objects.filter(user=instance)
if len(platform_users) > 0:
platform_user = platform_users[0]
else:
platform_user = PlatformUser.objects.create(user=instance)
platform_user.save()
as you could see there are receivers for creating my own platform user row on database when user created.
This way is simple but useful in my opinion. Your way is more correct but more complex.
BTW I dont use and dont like django forms at all.
Solved. I realized I wasn't using the create_user method I wrote in my registration view.
Updated the line below:
user = User.objects.create_user(email=email, city=city, province=province, password=password, first_name=first_name, last_name=last_name, phone_number=phone_number, age=age, gender=gender)
After this, my login form and validation worked. So for anyone having a similar issue, make sure you're using the create_user method if you wrote one/if you're using a custom user model/if you call user.set_password(password) in that method and not in your view.
I am deploying a fresh new Django app on the server. The problem is when I migrate all the apps in the project. I'm getting UndefinedTable: relation "user" does not exist.
Things I have done already:
removed all the migrations from all the apps
created a new database and new migrations and still getting that error.
Weird scenarios:
After deployment when I run the Django app locally on the server and hit API locally on the server using curl everything is working fine. like user sign up, but when I try to see in the database it is just empty (refer to the screenshot below). it doesn't even show columns for this user table, but for other tables, I can see columns as well.
but if I hit API with server endpoint I'm getting 500 error with exception :
relation "user" does not exist
LINE 1: SELECT (1) AS "a" FROM "user" WHERE "user"."email" = '
after migrations I am able to create super user but when I tried to login getting 500 error. and Undefined table relation user does not exit.
Expection:
AUTH_USER_MODEL = 'registration.User'
models.py
from django.db import models
# Create your models here.
import uuid
from django.db import models
from django.contrib.auth.models import PermissionsMixin
from django.contrib.auth.base_user import AbstractBaseUser
from django.utils import timezone
from .managers import CustomUserManager
from rest_framework_simplejwt.tokens import RefreshToken
AUTH_PROVIDERS = {'facebook': 'facebook', 'google': 'google',
'twitter': 'twitter', 'email': 'email'}
# Create your models here.
class User(AbstractBaseUser, PermissionsMixin):
# These fields tie to the roles!
A = 1
I = 2
C = 3
USER_ROLE = (
(A, 'A'),
(I, 'I'),
(C, 'C')
)
# Roles created here
uuid = models.UUIDField(unique=True, editable=False, default=uuid.uuid4)
username = models.CharField(max_length=255, unique=True, db_index=True)
email = models.EmailField(unique=True)
first_name = models.CharField(max_length=50, blank=True)
last_name = models.CharField(max_length=50, blank=True)
role = models.PositiveSmallIntegerField(choices=USER_ROLE, blank=True, null=True)
date_joined = models.DateTimeField(auto_now_add=True)
is_verified = models.BooleanField(default=False)
is_active = models.BooleanField(default=True)
is_deleted = models.BooleanField(default=False)
created_date = models.DateTimeField(default=timezone.now)
modified_date = models.DateTimeField(default=timezone.now)
auth_provider = models.CharField(
max_length=255, blank=False,
null=False, default=AUTH_PROVIDERS.get('email'))
is_staff = models.BooleanField(default=False)
is_superuser = models.BooleanField(default=False)
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['username', 'role']
objects = CustomUserManager()
class Meta:
db_table = 'user'
verbose_name = 'user'
verbose_name_plural = 'users'
def __str__(self):
return self.email
def save(self, *args, **kwargs):
self.is_staff = True if self.role == self.ADMIN else False
self.is_superuser = True if self.role == self.ADMIN else False
super(User, self).save(*args, **kwargs)
def tokens(self):
refresh = RefreshToken.for_user(self)
return {
'refresh': str(refresh),
'access': str(refresh.access_token)
}
managers.py (CustomUserManager)
from django.contrib.auth.base_user import BaseUserManager
from django.utils.translation import ugettext_lazy as _
class CustomUserManager(BaseUserManager):
"""
Custom user model where the email address is the unique identifier
and has an is_admin field to allow access to the admin app
"""
def create_user(self, username, email, role, password=None):
if username is None:
raise TypeError('Users should have a username')
if email is None:
raise TypeError('Users should have a Email')
user = self.model(username=username, role=role, email=self.normalize_email(email))
user.set_password(password)
user.save()
return user
def create_superuser(self, username, role, email, password=None):
"""
Creates and saves a superuser with the given email and password.
"""
if password is None:
raise TypeError('Password should not be none')
user = self.create_user(
username,
email,
role,
password=password,
)
user.is_staff = True
user.is_superuser = True
user.save(using=self._db)
return user
views.py
renderer_classes = (UserRenderer,)
permission_classes = [AllowAny]
def post(self, request):
user = request.data
serializer = self.serializer_class(data=user)
serializer.is_valid(raise_exception=True)
serializer.save()
user_data = serializer.data
user = User.objects.get(email=user_data['email'])
token = RefreshToken.for_user(user).access_token
current_site = get_current_site(request).domain
relativeLink = reverse('email-verify')
absurl = 'http://' + current_site + relativeLink + "?token=" + str(token)
email_body = 'Hi ' + user.username + \
' Use the link below to verify your email \n' + absurl
data = {'email_body': email_body, 'to_email': user.email,
'email_subject': 'Verify your email'}
# Util.send_email(data)
mail.send(
[user.email],
"abc#gmail.com",
subject='Account Verification',
message=email_body,
priority='now',
)
return Response(user_data, status=status.HTTP_201_CREATED)
serializer.py
class RegisterSerializer(serializers.ModelSerializer):
password = serializers.CharField(
max_length=68, min_length=6, write_only=True)
role = serializers.IntegerField(write_only=True)
default_error_messages = {
'username': 'The username should only contain alphanumeric characters'}
class Meta:
model = User
fields = ['email', 'username', 'password', 'role']
def validate(self, attrs):
email = attrs.get('email', '')
username = attrs.get('username', '')
role = attrs.get('role', '')
if not username.isalnum():
raise serializers.ValidationError(
self.default_error_messages)
return attrs
def create(self, validated_data):
return User.objects.create_user(**validated_data)
python 3.10
Django 3.2
I have an API that has a createaccount endpoint. I pass a password when creating the user and the password is passed as string. However, when it is passed to the serializer it comes out as a list, which causes this error: Password must be a string or bytes, got list this error also duplicates itself to my other two attributes, username and account_type as seen below. I have had to do list comprehension by getting the first element of the lists to get the values which I feel is the incorrect way of dealing with this issue.
Here is my models.py for the Account model with its associated Manager:
from django.db import models
import django.contrib.auth.models as auth_models
class AccountManager(auth_models.BaseUserManager):
def create_user(self, username, account_type, password=None):
if not account_type:
raise ValueError("Users must have an account type")
if not username:
raise ValueError("Users must have a username")
requested_account_type = account_type[0]
user = self.model(username=username, account_type=requested_account_type)
user.set_password(password[0])
user.save(using=self._db)
return user
def create(self, username, account_type):
if not account_type:
raise ValueError("Users must have an account type")
if not username:
raise ValueError("Users must have a username")
requested_account_type = account_type[0]
user = self.model(username=username, account_type=requested_account_type)
user.save(using=self._db)
return user
def create_superuser(self, username, password, account_type="jool-admin"):
user = self.create_user(
password=password, username=username, account_type=account_type
)
user.is_admin = True
user.is_staff = True
user.is_superuser = True
user.save(using=self._db)
return user
class Account(auth_models.AbstractBaseUser):
username = models.CharField(max_length=40, unique=True, primary_key=True)
ACCOUNT_TYPE_CHOICES = (
("admin", "Admin"),
("manager", "Manager"),
("staff", "Staff"),
)
account_type = models.CharField(
choices=ACCOUNT_TYPE_CHOICES,
default="EMPLOYEE",
blank=False,
null=False,
max_length=10,
)
date_joined = models.DateTimeField(verbose_name="Date Joined", auto_now_add=True)
is_admin = models.BooleanField(default=False)
is_active = models.BooleanField(default=True)
is_staff = models.BooleanField(default=False)
is_superuser = models.BooleanField(default=False)
USERNAME_FIELD = "username"
REQUIRED_FIELDS = ["account_type"]
objects = AccountManager()
Here is the serializer to go with the model:
class AccountSerializer(serializers.ModelSerializer):
def create(self, validated_data):
user = Account.objects.create_user(**validated_data)
return user
class Meta:
model = Account
fields = "__all__"
Here is the view for the endpoint:
class UserRecordView(views.APIView):
"""
API View to create or get a list of all the registered
users. GET request returns the registered users whereas
a POST request allows to create a new user.
"""
permission_classes = [CanViewUserRecord, permissions.IsAuthenticated]
def get(self, format=None):
users = Account.objects.all()
serializer = AccountSerializer(users, many=True)
return response.Response(serializer.data)
def post(self, request):
serializer = AccountSerializer(data=request.data)
if serializer.is_valid(raise_exception=ValueError):
serializer.create(validated_data=request.data)
return response.Response(serializer.data, status=status.HTTP_201_CREATED)
return response.Response(
{
"error": True,
"error_msg": serializer.error_messages,
},
status=status.HTTP_400_BAD_REQUEST,
)
I noticed that the data parameter that calls the serializer is the first instance where all the values are lists:
data=<QueryDict: {'username': ['test2'], 'password': ['test123'], 'account_type': ['admin']}>
This is causing me problems as django saves the values in the list format, so instead of the username being "test2" in the database, it is ['test2'], which causes a lot of errors when calling the API. Any ideas of how I can fix this or what I'm doing wrong. Thank you
I am trying to create an api endpoint that lists all the users in the database, but I only want staff members to be able to do this. By default when a secretary is created, the is_staff field in the users model is set to True. In the permission class, I am checking to see if this field is set to true and grant permissions accordingly.
Since I have many different types of users with slightly different attributes, I created a concrete base user model that is inherited by other user models. The SECRETARY role is assigned by the serializer when handling the secretary creation endpoint request.
class User(AbstractBaseUser):
class Roles(models.TextChoices):
"""define the user roles"""
ADMIN = "ADMIN", "Admin"
PRINTER = "PRINTER", "Printer"
SECRETARY = "SECRETARY", "Secretary"
PHOTOGRAPHER = "PHOTOGRAPHER", "Photographer"
EDITOR = "EDITOR", "Editor"
CLIENT = "CLIENT", "Client"
SYSTEM = "SYSTEM", "System"
APPLICANT = "APPLICANT", "Applicant"
id = models.CharField(
max_length=8, unique=True, primary_key=True, default=uidgen, editable=False
)
first_name = models.CharField("First Name", max_length=30, null=False)
last_name = models.CharField("Last Name", max_length=30, null=True, blank=True)
email = models.EmailField(
verbose_name="Email", max_length=255, unique=True, null=False
)
username = None
phone_number = models.CharField(
"Phone Number", max_length=20, null=False, unique=True
)
dob = models.DateField(verbose_name="Date Of Birth", blank=True, null=True)
role = models.CharField(max_length=50, default=Roles.CLIENT)
date_joined = models.DateTimeField(verbose_name="Date Joined", auto_now_add=True)
last_login = models.DateTimeField(verbose_name="Last Login", auto_now=True)
USERNAME_FIELD = "email"
REQUIRED_FIELDS = ["phone_number"]
is_staff = False
is_superuser = False
is_active = True
is_contractor = False
is_associate = False
phone_is_verified = False
email_is_verified = False
objects = UserManager()
User manager:
class UserManager(BaseUserManager):
def create_user(self, email, password=None, **extra_fields):
if not email:
raise ValueError("The Email must be set")
user = self.model(
email=self.normalize_email(email),
)
if password:
user.set_password(password)
try:
if user.role == User.Roles.SECRETARY:
extra_fields.setdefault("is_staff", True)
else:
extra_fields.setdefault("is_staff", False)
except AttributeError:
user.role = User.Roles.ADMIN
return user
def create_superuser(self, email, password=None, **extra_fields):
extra_fields.setdefault("is_staff", True)
extra_fields.setdefault("is_superuser", True)
extra_fields.setdefault("is_active", True)
admin_user = self.create_user(email, password, **extra_fields)
admin_user.role = User.Roles.ADMIN
admin_user.save()
return admin_user
The Secretary model:
class Secretary(User):
bank_number = models.CharField(max_length=50, null=True)
is_staff = True
class Meta:
verbose_name = "Secretary"
verbose_name_plural = "Secretaries"
def __str__(self):
return f"{self.first_name} {self.last_name}"
objects = UserManager()
Permission class:
class UserListPermission(permissions.BasePermission):
def has_permission(self, request, view):
print(request.user.is_staff)
if view.action == "list" and request.user.is_staff:
return True
return False
def has_object_permission(self, request, view):
if view.action == "list" and request.user.is_staff:
return True
return False
I created a secretary along with an access token and tried accessing a staff only api endpoint. I get a response:
{
"detail": "You do not have permission to perform this action."
}
In the server shell output, the print statement spits out False. In the django shell:
>>> sec = Secretary.objects.get(email='dinoseca#yahoo.de')
>>> sec.role
'SECRETARY'
>>> sec.is_staff
True
When I remove is_staff = True from the Secretary model, the above shell snippet results in False
I feel like the mistake lies somewhere in my UserManager or my User model.
You have to save your user before returning in create_user method. self.model just creates an instance the save method must be called for the instance to be saved to the database.
def create_user(self, email, password=None, **extra_fields):
if not email:
raise ValueError("The Email must be set")
user = self.model(
email=self.normalize_email(email),
)
if password:
user.set_password(password)
try:
user.is_staff = True if user.role == User.Roles.SECRETARY else False
except AttributeError:
user.role = User.Roles.ADMIN
user.save(using=self._db)
return user
This might also help to clarify https://stackoverflow.com/a/51163172/11128969
i just tried to create my own custom Django user model but it fails if i try to create a new user through the frontend:
Request Method: POST Request URL: http://127.0.0.1:8000/signup/ Django
Version: 2.1b1 Exception Type: AttributeError Exception Value:
'Manager' object has no attribute 'get_by_natural_key'
First i created a new Django App "accounts"
then i've added the following to models.py of the accounts app:
from django.db import models
from django.contrib.auth.models import (
BaseUserManager, AbstractBaseUser
)
#User Model Manager
class UserManager(BaseUserManager):
def create_user(self, username, password=None):
"""
Creates and saves a User with the given username and password.
"""
if not username:
raise ValueError('Error: The User you want to create must have an username, try again')
user = self.model(
user=self.normalize_username(username),
)
user.set_password(password)
user.save(using=self._db)
return user
def create_staffuser(self, username, password):
"""
Creates and saves a staff user with the given username and password.
"""
user = self.create_user(
username,
password=password,
)
user.staff = True
user.save(using=self._db)
return user
def create_superuser(self, username, password):
"""
Creates and saves a superuser with the given username and password.
"""
user = self.create_user(
username,
password=password,
)
user.staff = True
user.admin = True
user.save(using=self._db)
return user
class User(AbstractBaseUser):
user = models.CharField(
verbose_name='username',
max_length=30,
unique=True,
)
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's built in.
USERNAME_FIELD = 'user'
REQUIRED_FIELDS = [] # Username & Password are required by default.
def get_full_name(self):
# The user is identified by their Username address
return self.user
def get_short_name(self):
# The user is identified by their Username address
return self.user
def __str__(self): # __unicode__ on Python 2
return self.user
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
#property
def is_active(self):
"""Is the user active?"""
return self.active
model.py of my actual blog app:
from django.db import models
from django.utils import timezone
from smartfields import fields
from smartfields.dependencies import FileDependency
from smartfields.processors import ImageProcessor
# Categorys of Post Model
class Category(models.Model):
title = models.CharField(max_length=255, verbose_name="Title")
description = models.TextField(max_length=1000, null=True, blank=True)
cover = fields.ImageField(upload_to='categorycovers/', blank=True, null=True, dependencies=[
FileDependency(processor=ImageProcessor(
format='JPEG', scale={'max_width': 350, 'max_height': 350}))
])
icon = fields.ImageField(upload_to='categoryicons/', blank=True, null=True, dependencies=[
FileDependency(processor=ImageProcessor(
format='JPEG', scale={'max_width': 16, 'max_height': 16}))
])
class Meta:
verbose_name = "Category"
verbose_name_plural = "Categories"
ordering = ['title']
def __str__(self):
return self.title
#Post Model
class Post(models.Model):
author = models.ForeignKey('accounts.User', on_delete=models.CASCADE)
title = models.CharField(max_length=75)
content = models.TextField(max_length=10000)
tag = models.CharField(max_length=50, blank=True)
category = models.ForeignKey(Category, verbose_name="Category", on_delete=models.CASCADE, null=True)
postattachment = fields.FileField(upload_to='postattachment/%Y/%m/%d/', blank=True ,null=True)
postcover = fields.ImageField(upload_to='postcover/%Y/%m/%d/', null=True, dependencies=[
FileDependency(processor=ImageProcessor(
format='JPEG', scale={'max_width': 200, 'max_height': 200}))
])
created_date = models.DateField(auto_now_add=True)
published_date = models.DateField(blank=True, null=True)
def publish(self):
self.published_date = timezone.now()
self.save()
class Meta:
ordering = ["-title"]
def __str__(self):
return self.title
#Comment(s) of Post Model
class Comment(models.Model):
author = models.ForeignKey('accounts.User', on_delete=models.CASCADE)
post=models.ForeignKey(Post, on_delete=models.CASCADE)
content = models.TextField()
created_date = models.DateField(default=timezone.now)
published_date = models.DateField(blank=True, null=True)
def publish(self):
self.published_date = timezone.now()
self.save()
def __str__(self):
return self.text
After that i jumpt back to settings.py and added the custom user model to my acctual blog application:
AUTH_USER_MODEL = 'accounts.User'
and i also added
INSTALLED_APPS = [
...
'accounts',
...
]
I dont want any email address for my users. i simply and only want them to be authenticated through the username...
Any hints? Thanks :)
Django AbstractBaseUser class has no field username
in your declaration you declare a username field and named it user
but USERNAME_FIELD is username
change your model like this
class User(AbstractBaseUser):
user = models.CharField(
verbose_name='username',
max_length=30,
unique=True,
)
active = models.BooleanField(default=True)
staff = models.BooleanField(default=False) # a admin user; non super-user
admin = models.BooleanField(default=False) # a superuser
USERNAME_FIELD = 'user'
REQUIRED_FIELDS = [] # Username & Password are required by default.
...
also you can rename user field in User model to username and problem solved.
In error django.core.exceptions.FieldError: Unknown field(s) (username) specified for User, Django complained about unknown field username in your Custom User model, you don't have username field but you stated USERNAME_FIELD = 'username'.
The simple solution is to rename your user field to username.
If you need that name for some reason (user) you should change username to user in get_full_name and get_short_name and don't forget also in create_user (user = self.model(user =self.normalize_username(username),))
Try:
Reaname all created users to some other name (ex. my_user) because you have tow varibales with the same name (the USERNAME_FIELD and the user created)
from django.db import models
from django.contrib.auth.models import (
BaseUserManager, AbstractBaseUser
)
#User Model Manager
class UserManager(BaseUserManager):
def create_user(self, user, password=None):
"""
Creates and saves a User with the given username and password.
"""
if not user:
raise ValueError('Error: The User you want to create must have an username, try again')
my_user = self.model(
user=self.normalize_username(user),
)
my_user.set_password(password)
my_user.save(using=self._db)
return my_user
def create_staffuser(self, user, password):
"""
Creates and saves a staff user with the given username and password.
"""
my_user = self.create_user(
user,
password=password,
)
my_user.staff = True
my_user.save(using=self._db)
return my_user
def create_superuser(self, user, password):
"""
Creates and saves a superuser with the given username and password.
"""
my_user = self.create_user(
user,
password=password,
)
my_user.staff = True
my_user.admin = True
my_user.save(using=self._db)
return my_user
class User(AbstractBaseUser):
user = models.CharField(
verbose_name='username',
max_length=30,
unique=True,
)
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's built in.
USERNAME_FIELD = 'user'
REQUIRED_FIELDS = [] # Username & Password are required by default.
def get_full_name(self):
# The user is identified by their Username address
return self.user
def get_short_name(self):
# The user is identified by their Username address
return self.user
def __str__(self): # __unicode__ on Python 2
return self.user
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
#property
def is_active(self):
"""Is the user active?"""
return self.active
Your settings.py file looks great.
I hope this will help.