I'm trying to make a custom user, but there's one problem, when i get request.user, it’s an instance of User, not an instance of CustomUser, so i don’t get the extra fields and methods. I've found this, but when i'm trying to login in the django admin it won't work anymore.
Here is my code:
models.py
class CustomUser(User):
timezone = models.CharField(max_length=50, default='Europe/Bucharest')
porecla = models.CharField(max_length=50, null=True)
selectat = models.BooleanField(default=False)
objects = UserManager()
auth_backends.py - so i can use the methods of the custom user when using request.user
from django.conf import settings
from django.contrib.auth.backends import ModelBackend
from django.core.exceptions import ImproperlyConfigured
from django.apps import apps
class CustomUserModelBackend(ModelBackend):
def authenticate(self, username=None, password=None):
try:
user = self.user_class.objects.get(username=username)
if user.check_password(password):
return user
except self.user_class.DoesNotExist:
return None
def get_user(self, user_id):
try:
return self.user_class.objects.get(pk=user_id)
except self.user_class.DoesNotExist:
return None
#property
def user_class(self):
if not hasattr(self, '_user_class'):
self._user_class = apps.get_model(*settings.CUSTOM_USER_MODEL.split('.', 2))
if not self._user_class:
raise ImproperlyConfigured('Could not get custom user model')
return self._user_class
settings.py
AUTHENTICATION_BACKENDS = ('football_app.auth_backends.CustomUserModelBackend', )
CUSTOM_USER_MODEL = 'football_app.CustomUser'
admin.py
class CustomUserAdmin(admin.ModelAdmin):
list_display = ('username', 'email', 'first_name', 'last_name', 'is_staff', 'is_active', 'selectat', )
admin.site.register(CustomUser, CustomUserAdmin)
The article you are referring to is outdated. If you want to create a custom User model, the way to go is to subclass AbstractUser in stead of User. This is explained here in the official docs.
Related
When a user is created on Django admin panel, objects = CustomUserManager() is not being executed. However, when I create a superuser from the CLI using python manage.py createsuperuser, objects = CustomUserManager() is executed.
Here is the model file
class CustomUser(AbstractUser):
username = None
email = models.EmailField(_('email address'), unique=True)
name = models.CharField(verbose_name=_("first name"), max_length=50)
stripe_customer_id = models.CharField(max_length=120)
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['name']
objects = CustomUserManager()
def __str__(self):
return self.name
Here is the customUserManager
class CustomUserManager(BaseUserManager):
def create_user(self, email, password, name,**extra_fields):
if not email:
raise ValueError(_('The Email must be set'))
email = self.normalize_email(email)
customer = stripe.Customer.create(
email=email,
name=name,
)
user = self.model(email=email,name=name, stripe_customer_id=customer.id ,**extra_fields)
user.set_password(password)
user.save()
return user
def create_superuser(self, email, password,name,**extra_fields):
"""
Create and save a SuperUser with the given email and password.
"""
extra_fields.setdefault('is_staff', True)
extra_fields.setdefault('is_superuser', True)
extra_fields.setdefault('is_active', 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, name,**extra_fields)
Below is how I registered the admin panel.
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from .forms import CustomUserCreationForm, CustomUserChangeForm
from .models import CustomUser
from django.utils.translation import gettext_lazy as _
class CustomUserAdmin(UserAdmin):
add_form = CustomUserCreationForm
form = CustomUserChangeForm
model = CustomUser
....
admin.site.register(CustomUser, CustomUserAdmin)
Below is the CustomUserCreationForm
class CustomUserCreationForm(UserCreationForm):
class Meta:
model = CustomUser
fields = "__all__"
UserAdmin relies on the implementation in ModelAdmin to construct an instance, validate the form, and then save the instance, rather than calling a custom method on the model's manager.
Call chain: ModelAdmin.add_view() → ... → self._changeform_view(), which calls:
(i) form.is_valid() (of ModelForm) → ... → self._post_clean() → self.instance = construct_instance(...),
(ii) self.save_form(...) → new_object = form.save(commit=False) (of BaseModelForm) returning self.instance, and
(iii) self.save_model(..., new_object, ...) → obj.save().
You can override ModelAdmin.save_form to call the custom user manager's create_user method and replace form.instance:
class CustomUserAdmin(UserAdmin):
model = CustomUser
fieldsets = (
(None, {'fields': (model.USERNAME_FIELD, 'name', 'password')}),
) + UserAdmin.fieldsets[2:]
add_fieldsets = (
(None, {'fields': (model.USERNAME_FIELD, 'name', 'password1', 'password2')}),
)
ordering = (model.USERNAME_FIELD,)
def save_form(self, request, form, change):
if not change:
form.instance = self.model.objects.create_user(
form.cleaned_data['email'],
form.cleaned_data['password1'],
form.cleaned_data['name'],
)
return super().save_form(request, form, change)
⚠️ Notice in the usual call chain that save_form() does not call obj.save(), as ModelAdmin handles multiple formsets and m2m models, while CustomUserManager.create_user does call user.save(). It is ok to change this behaviour in UserAdmin with known scope, though you may consider splitting CustomUserManager.create_user into CustomUserManager.construct_instance and CustomUserManager.save_new_model (move stripe.Customer.create(...) and user.save() here).
As a matter of fact, your custom methods create_user (and create_superuser, which you probably overwrote as well) are not called when you create a new user from the admin panel. I'm not sure neither to understand why but I think the create_user function is just a shortcut.
Why not overriding the save() method of your CustomUser? From the Django documentation:
A classic use-case for overriding the built-in methods is if you want something to happen whenever you save an object.
Isn't exactly what you're looking for? Check the Django documentation here (to do so at the form level) and here (if you prefer change the model itself directly, no matter which form is used) for more information.
For instance, if you prefer the second option, you can add this method to CustomUser:
from django.apps.apps import get_model
from django.db import transaction
class CustomUser(AbstractUser):
# your initial code goes here ...
def save(self, *args, **kwargs):
# make the distinction between the creation and the update of the instance
if not self._state.adding:
# doesn't change the initial method if the user has already been saved in DB
return super(CustomUser, self).save(*args, **kwargs)
# guarantee of atomicity
with transaction.atomic():
# dynamic resolution to avoid a circular import, might not be necessary
customer = get_model("stripe", "Customer").create(
email=self.email,
name=self.name,
)
self.stripe_customer_id = customer.id
super(CustomUser, self).save(*args, **kwargs)
return self
Don't forget to make the distinction between the creation and the update of an instance.
So I have these Django models in the users app, and I have added AUTH_USER_MODEL = 'users.User' to settings.py
from django.db import models
from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
login_count = models.PositiveIntegerField(default=0)
class Supplier(User):
company_name= models.CharField(max_length=30)
company_domain=models.CharField(max_length=30)
class Meta:
verbose_name = 'supplier'
verbose_name_plural = 'suppliers'
class Worker(User):
ACCOUNT_TYPE = (
('1', 'Admin'),
('2', 'Regular'),
)
is_hub_manager = models.BooleanField(default=False)
account_type = models.CharField(max_length=1, choices=ACCOUNT_TYPE)
class Meta:
verbose_name = 'worker'
verbose_name_plural = 'workers'
I also created an authenetication endpoint using Django rest framework. Surprisingly when I authenticate the admin everything works well. When I create a supplier and try to authenticate them. They always return invalid credentials. Here are my API views
from rest_framework.generics import GenericAPIView
from .serializers import UserSerializer
from rest_framework.response import Response
from rest_framework import status
from django.conf import settings
from django.contrib import auth
import jwt
class LoginView(GenericAPIView):
serializer_class = UserSerializer
def post(self, request):
data = request.data
username = data.get('username', '')
password = data.get('password', '')
user = auth.authenticate(username=username, password=password)
if user:
auth_token = jwt.encode({'username': user.username}, settings.JWT_SECRET_KEY)
serializer = UserSerializer(user)
data = {'user': serializer.data, 'token': auth_token}
return Response(data, status=status.HTTP_200_OK)
# SEND RES
return Response({'detail': 'Invalid credentials'}, status=status.HTTP_401_UNAUTHORIZED)
What could I be doing wrong?
The first thing, you should check AUTH_USER_MODEL keyword in setting file.
E.g. AUTH_USER_MODEL = '<module>. User'.
Regarding your case, the root cause is not yet custom your authenticate() method in your backend. You defined Supplier and Worker which are inherited from the User class.
Also, you can refer to this link for detail.
I am building a backend for a web app using django rest framework. I have a profile model that has a user forieingkey referencing a django user. Everything is loading correctly except for one issue which is that the User field is not showing up in the django rest framework backend urls so that I can assign a user to the profile object i want to create... does anyone know why this is happening...
models.py:
class Profile(models.Model):
user = models.ForeignKey(
User, on_delete=models.CASCADE
)
synapse = models.CharField(max_length=25, null=True)
bio = models.TextField(null=True)
profile_pic = models.ImageField(
upload_to='./profile_pics/',
max_length=150
)
facebook = models.URLField(max_length=150)
twitter = models.URLField(max_length=150)
updated = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.user.username + ' profile'
viewset:
from users.models import Profile
from users.api.serializers.ProfileSerializer import ProfileSerializer
from rest_framework import viewsets
class ProfileViewSet(viewsets.ModelViewSet):
queryset = Profile.objects.all()
serializer_class = ProfileSerializer
lookup_field = 'user__username'
url:
from users.api.views.ProfileView import ProfileViewSet
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register(r'', ProfileViewSet, base_name='profile')
urlpatterns = router.urls
serializer:
from rest_framework import serializers
from users.models import Profile
class ProfileSerializer(serializers.ModelSerializer):
class Meta:
model = Profile
fields = (
'id',
'user',
'synapse',
'bio',
'profile_pic',
'facebook',
'twitter'
)
depth=2
That happens when you set a depth bigger that 0, foreign key fields become not editable (if you send a POST with that field containing some value, DRF viewset would ignore it, and if the field is required, it will raise an exception).
One solution for that is to override to_representation() method of the serializer and set the depth and restore it to zero:
class ProfileSerializer(serializers.ModelSerializer):
class Meta:
model = Profile
fields = (
'id',
'user',
'synapse',
'bio',
'profile_pic',
'facebook',
'twitter'
)
def to_representation(self, instance):
self.Meta.depth = 2
representation = super().to_representation(instance)
self.Meta.depth = 0
return representation
I want to restrict my worker users' admin panel - don't want them to create users or delete certain types of models, for instance.
Here's some code in models.py that sets up my custom user, and the post_save signal to add users.
Note that I set default_permissions = () so that users don't start with any permissions, but then I grant them the permissions of the group with name=workers which does NOT include myapp.myuser_add, thus they shouldn't be able to add more users when logged into admin console.
# models.py
from django.db import models
from django.contrib.auth.models import (
BaseUserManager, AbstractBaseUser, PermissionsMixin, Group
)
from django.db.models.signals import post_save
from django.dispatch import receiver
#receiver(post_save, sender=settings.AUTH_USER_MODEL)
def apply_worker_group(sender, instance=None, created=False, **kwargs):
if instance.is_worker:
try:
worker_group = Group.objects.get(name='workers')
if instance not in worker_group.user_set.all():
instance.groups.add(worker_group)
instance.save()
except Group.DoesNotExist:
pass
class MyUser(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(
verbose_name='email address',
max_length=255,
unique=True,)
is_active = models.BooleanField(default=True)
is_admin = models.BooleanField(default=False)
is_worker = models.BooleanField(default=False)
# ... skipping a bunch of stuff
class Meta:
default_permissions = () # (!) ALL USERS HAVE NO PERMS TO START
#property
def is_staff(self):
# so workers can log into the admin panel
return self.is_admin or self.is_worker
Then in my admin.py:
# admin.py
from django.contrib.auth.admin import UserAdmin
from django.contrib import admin
from myapp.models import MyUser
class MyUserAdmin(UserAdmin):
pass
class MyAdminSite(admin.AdminSite):
pass
admin_site = MyAdminSite(name='myadmin')
admin_site.register(MyUser, MyUserAdmin)
But when I create a user under my superuser account with is_worker=True, I can still log in with that created user to admin panel and create more users! Which is not what I want.
Any suggestions for how to restrict user permissions using groups like this?
I customizes Django's authentication and now every time I try to create a new user using Django Admin this user's password is saved directly, without being hashed. I found out that create_user method from BaseUserManager class is not being called. When I create a superuser using bash it is done properly. Is there anything wrong with my model?
settings.py
AUTH_USER_MODEL = 'authentication.BaseAccount'
apps/authentication/models.py
class BaseAccountManager(BaseUserManager):
def create_user(self, email, password=None, **kwargs):
if not email:
raise ValueError('Users must have a valid email address.')
account = self.model(
email=self.normalize_email(email)
)
account.set_password(password)
account.save()
return account
def create_superuser(self, email, password, **kwargs):
account = self.create_user(email, password, **kwargs)
account.is_admin = True
account.is_staff = True
account.save()
return account
class BaseAccount(AbstractBaseUser):
email = models.EmailField(unique=True)
first_name = models.CharField(max_length=40, blank=True)
last_name = models.CharField(max_length=40, blank=True)
is_admin = models.BooleanField(default=False)
is_staff = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
objects = BaseAccountManager()
USERNAME_FIELD = 'email'
def __unicode__(self):
return self.email
def get_full_name(self):
return ' '.join([self.first_name, self.last_name])
def get_short_name(self):
return self.first_name
def has_perm(self, perm, obj=None):
return self.is_admin
def has_module_perms(self, app_label):
return self.is_admin
For future readers, because this issue also got me banging my head against the wall...
To access that precious create_user() method from anywhere in your django project :
from django.contrib.auth import get_user_model
class SomeClass():
# This could be a form, for example...
def some_function():
get_user_model().objects.create_user()
If you call it from the save() method of some custom form, please take proper care of the commit parameter, and don't forget to register your custom user model in the settings.py file, as per the Django documentation.
I found an answer to your question about passwords, but I still don't know how to get the create_user function to be called correctly. The example removes username in favor of just an email. I am still looking for a way to hit the create_user function defined in the custom UserManager inerhiting from BaseUserManager. My custom manager sends an email with a password reset link and a one time token... which would avoid the need to actually set a hashed password from django admin.
https://www.caktusgroup.com/blog/2013/08/07/migrating-custom-user-model-django/
first create your own forms:
# appname/forms.py
from django.contrib.auth.forms import UserCreationForm, UserChangeForm
from appname.models import CustomUser
class CustomUserCreationForm(UserCreationForm):
"""
A form that creates a user, with no privileges, from the given email and
password.
"""
def __init__(self, *args, **kargs):
super(CustomUserCreationForm, self).__init__(*args, **kargs)
del self.fields['username']
class Meta:
model = CustomUser
fields = ("email",)
class CustomUserChangeForm(UserChangeForm):
"""A form for updating users. Includes all the fields on
the user, but replaces the password field with admin's
password hash display field.
"""
def __init__(self, *args, **kargs):
super(CustomUserChangeForm, self).__init__(*args, **kargs)
del self.fields['username']
class Meta:
model = CustomUser
then use those forms:
# appname/admin.py
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from django.utils.translation import ugettext_lazy as _
from appname.models import CustomUser
from appname.forms import CustomUserChangeForm, CustomUserCreationForm
class CustomUserAdmin(UserAdmin):
# The forms to add and change user instances
# The fields to be used in displaying the User model.
# These override the definitions on the base UserAdmin
# that reference the removed 'username' field
fieldsets = (
(None, {'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 = (
(None, {
'classes': ('wide',),
'fields': ('email', 'password1', 'password2')}
),
)
form = CustomUserChangeForm
add_form = CustomUserCreationForm
list_display = ('email', 'first_name', 'last_name', 'is_staff')
search_fields = ('email', 'first_name', 'last_name')
ordering = ('email',)
admin.site.register(CustomUser, CustomUserAdmin)