When I try to edit a user (using a custom UserChangeForm) in the Django admin panel, validation insists that fields I have set blank=True in the model are required.
I don't know where to begin solving this; I had the same issue with the CustomUserCreationForm but reverted to using the default which works as expected (asks for username, password1 & password2, creates the user with blank display_name, bio and profile_picture fields).
models.py:
from django.db import models
from django.contrib.auth.models import AbstractUser
class CustomUser(AbstractUser):
display_name = models.CharField(max_length=30, blank=True, null=True)
bio = models.TextField(blank=True, null=True)
profile_picture = models.ImageField(upload_to='images/', blank=True, null=True)
def save(self, *args, **kwargs):
if not self.display_name:
self.display_name = self.username
super().save(*args, **kwargs)
def __str__(self):
return self.username
forms.py:
from django import forms
from django.contrib.auth.forms import UserChangeForm
from .models import CustomUser
class CustomUserChangeForm(UserChangeForm):
display_name = forms.CharField(label="display_name")
bio = forms.CharField(widget=forms.Textarea)
profile_picture = forms.ImageField(label="profile_picture")
class Meta():
model = CustomUser
fields = ("username", "email", "display_name", "bio", "profile_picture")
admin.py:
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from .forms import CustomUserChangeForm
from .models import CustomUser
class CustomUserAdmin(UserAdmin):
form = CustomUserChangeForm
fieldsets = (
(None,
{'fields': ('username', 'password', 'email', 'display_name', 'bio', 'profile_picture')}
),
)
model = CustomUser
list_display = ["username", "email",]
admin.site.register(CustomUser, CustomUserAdmin)
From the Django documentation:
By default, each Field class assumes the value is required, so if you
pass an empty value – either None or the empty string ("") – then
clean() will raise a ValidationError exception:
So you have to add required=False in your forms.py. For example:
display_name = forms.CharField(required=False, label="display_name")
I need to disable adding new users in the admin panel once the number of users exceeds a particular value. The code below works to remove the "add user" button when I test it with hard coded integers. However, this line does not appear to be returning the count of users without hard coding a count: usercount = CustomUser.objects.count()
Any ideas for getting the count of users already added?
models.py:
from django.db import models
from django.contrib.auth.models import AbstractUser
from django.contrib.auth.models import Group
from django.contrib import admin
from django.db.models import Count
from django.db.models.signals import post_save
class CustomUser(AbstractUser):
full_name = models.CharField(max_length=250, null=True)
age = models.PositiveIntegerField(null=True, blank=True)
employee_type = models.ForeignKey(Group, null=True, on_delete=models.SET_NULL, default=1)
is_active = models.BooleanField(null=False, default=True)
# disable add new user in the admin panel
class RemoveAddNew(admin.ModelAdmin):
usercount = CustomUser.objects.count()
if usercount > 5:
def has_add_permission(self, request, obj=None):
return False
admin.py:
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from .forms import CustomUserCreationForm, CustomUserChangeForm
from .models import CustomUser, RemoveAddNew
class CustomUserAdmin(RemoveAddNew, UserAdmin):
add_form = CustomUserCreationForm
form = CustomUserChangeForm
model = CustomUser
list_display = ['username', 'email', 'full_name', 'age', 'is_staff', 'is_active']
fieldsets = UserAdmin.fieldsets + (
(None, {'fields': ('full_name', 'age', )}),
)
add_fieldsets = UserAdmin.add_fieldsets + (
(None, {'fields': ('email','full_name', 'age', 'employee_type', 'is_active')}),
)
admin.site.register(CustomUser,CustomUserAdmin)
I think you over code it. My solution is simpler and shorter:
class CustomUserAdmin(UserAdmin):
add_form = CustomUserCreationForm
form = CustomUserChangeForm
model = CustomUser
list_display = ['username', 'email', 'full_name', 'age', 'is_staff', 'is_active']
fieldsets = UserAdmin.fieldsets + (
(None, {'fields': ('full_name', 'age', )}),
)
add_fieldsets = UserAdmin.add_fieldsets + (
(None, {'fields': ('email','full_name', 'age', 'employee_type', 'is_active')}),
)
def has_add_permission(self, request):
return CustomUser.objects.count() < 6
admin.site.register(CustomUser,CustomUserAdmin)
Currently i have the transfer model as followed:
class Transfers(models.Model):
class Meta:
db_table = "transfers"
verbose_name = 'Transfer'
verbose_name_plural = 'Transfers'
user = models.ForeignKey("backend.User", null=True, blank=True, related_name='user_transfer', on_delete=models.CASCADE)
to_account = models.ForeignKey("backend.User", null=True, blank=True, related_name='user_transfer_to_account', on_delete=models.SET_NULL)
A transfer object need to have from one user transfer to another user so i need to use the same User model
And in my admin.py:
class TransfersInline(admin.StackedInline):
model = Transfers
can_delete = False
extra = 0
max_num=0
form = TransfersAdminForm
class UserAdminCustom(admin.ModelAdmin):
exclude = ('password', 'last_login', 'is_superuser', 'is_staff', 'groups',
'user_permissions', 'username', 'first_name', 'last_name', 'is_active', 'date_joined')
inlines = [
TransfersInline,
]
def get_queryset(self, request):
qs = super(UserAdminCustom, self).get_queryset(request)
return qs.filter(is_staff=False)
def get_readonly_fields(self, request, obj=None):
return ('id', 'created', 'modified')
admin.site.register(User, UserAdminCustom)
In my admin i would like to display transfers inline that the User existed in user field of Transfers model but inline can't have multiple foreign key to the same model:
<class 'backend.admin.TransfersInline'>: (admin.E202) 'backend.Transfers' has more than one ForeignKey to 'backend.User'
My question is how do i only make TransfersInline use foreign key from user field instead of to_account field ? (transfer inline will have all the transfers object that the User is in user field )
Use fk_name='user' option for TransfersInline class.
You can check the docs
I’ve created a custom user object in my Django app, but don’t have control over user permissions. I believe this is because the Users link isn’t appearing in the Auth section of the Django admin site, where permissions are usually controlled.
Why would it not be showing up?
This is from my models.py file:
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, PermissionsMixin
from django.db import models
class UserManager(BaseUserManager):
def create_user(self, username, password=None):
"""
Creates and saves a user with the given username.
"""
user = self.model()
user.username = username
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, username, password):
"""
Creates and saves a superuser with the given username.
"""
user = self.create_user(username, password=password)
user.is_admin = True
user.is_staff = True
user.is_superuser = True
user.save(using=self._db)
return user
class FooUser(AbstractBaseUser, PermissionsMixin):
username = models.CharField(max_length=40, unique=True, db_index=True)
is_active = models.BooleanField(default=True)
is_admin = models.BooleanField(default=False)
is_staff = models.BooleanField(default=False)
my_time_field = models.DateTimeField(null=True, blank=True)
USERNAME_FIELD = 'username'
objects = UserManager()
class Meta:
app_label = 'foo'
def get_full_name(self):
return self.username
def get_short_name(self):
return self.username
def has_perm(self, perm, obj=None):
return self.is_admin
def has_module_perms(self, app_label):
return self.is_admin
In other apps I extend the user model further as needed:
class CocoUser(FooUser):
mobile_number = models.CharField(max_length=64, blank=True, null=True)
first_name = models.CharField(max_length=128, blank=True, null=True)
last_name = models.CharField(max_length=128, blank=True, null=True)
email = models.CharField(max_length=128, blank=True, null=True)
This is from my settings.py file:
MIDDLEWARE_CLASSES = (
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'kohlab.force_logout.ForceLogoutMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
)
INSTALLED_APPS = (
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.messages',
'django.contrib.staticfiles',
'django.contrib.admin',
'django.contrib.admindocs',
'django.contrib.humanize',
'django.contrib.messages',
'django_cleanup',
'south',
'myapp',
)
TEMPLATE_CONTEXT_PROCESSORS = (
"django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages",
"django.core.context_processors.static",
"kohlab.context_processors.site",
)
AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend',
)
AUTH_USER_MODEL = ‘myapp.FooUser’
This is from my urls.py file:
from django.conf.urls import patterns, include, url
from django.contrib import admin
admin.autodiscover()
urlpatterns = patterns('',
url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
url(r'^admin/', include(admin.site.urls)),
)
This is from my admin.py file:
from django import forms
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from coco.models import CocoUser
class CocoUserCreationForm(forms.ModelForm):
"""A form for creating new users. Includes all the required fields, plus a repeated password."""
class Meta:
model = CocoUser
fields = ('mobile_number', 'email', 'first_name', 'last_name',)
class CocoUserChangeForm(forms.ModelForm):
"""
A form for updating users. Includes all the fields on the user, but replaces the password field with the initial one.
"""
class Meta:
model = CocoUser
fields = ['is_admin', 'is_staff', 'mobile_number', 'first_name', 'last_name', 'email']
def clean_password(self):
# Regardless of what the user provides, return the initial value.
# This is done here, rather than on the field, because the field does not have access to the initial value
return self.initial["password"]
class CocoUserAdmin(UserAdmin):
# The forms to add and change user instances
form = CocoUserChangeForm
add_form = CocoUserCreationForm
# The fields to be used in displaying the CocoUser model.
# These override the definitions on the base UserAdmin that reference specific fields on auth.User.
list_display = ('id', 'first_name', 'last_name', 'email', 'mobile_number', 'is_admin', 'is_staff',)
list_filter = ('is_admin',)
fieldsets = (
(None, {'fields': ('is_admin', 'is_staff', 'mobile_number', 'first_name', 'last_name', 'email',)}),
)
add_fieldsets = (
(None, {
'classes': ('wide',),
'fields': ('mobile_number', 'email', 'first_name', 'last_name',)}
),
)
search_fields = ('id', 'mobile_number', 'email', 'first_name', 'last_name',)
ordering = ('last_name', 'first_name',)
filter_horizontal = ()
# Now register the new UserAdmin...
admin.site.register(CocoUser, CocoUserAdmin)
In the end, the solution was rather simple. I had to adjust my CocoUserAdmin’s fieldsets to expose the permissions.
With a custom class like that, there will be no Users link in the Auth section, because the custom class takes over -- including permissions. These settings won’t be evident though, unless 'groups' and 'user_permissions' are added to fieldsets.
That CocoUserAdmin fieldsets fix is the key. Along the way, I converted FooUser to be a subclass of AbstractUser. This might have been unnecessary; the permissions may well have been there when CocoUser was a subclass of AbstractBaseUser too, but I’m not sure.
From my final models.py file:
from django.contrib.auth.models import AbstractUser
from django.db import models
class FooUser(AbstractUser):
my_time_field = models.DateTimeField(null=True, blank=True)
class CocoUser(FooUser):
mobile_number = models.CharField(max_length=64, blank=True, null=True)
From my final admin.py file:
from django import forms
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from coco.models import CocoUser
class CocoUserCreationForm(forms.ModelForm):
"""A form for creating new users."""
class Meta:
model = CocoUser
fields = ('username', 'mobile_number', 'email', 'first_name', 'last_name',)
class CocoUserChangeForm(forms.ModelForm):
"""
A form for updating users. Includes all the fields on the user.
"""
class Meta:
model = CocoUser
fields = ['username', 'password', 'first_name', 'last_name', 'email', 'is_active', 'is_staff', 'is_superuser', 'groups', 'user_permissions', 'last_login', 'date_joined',
'my_time_field', 'mobile_number',]
class CocoUserAdmin(UserAdmin):
# The forms to add and change user instances
form = CocoUserChangeForm
add_form = CocoUserCreationForm
# The fields to be used in displaying the CocoUser model.
# These override the definitions on the base UserAdmin that reference specific fields on auth.User.
list_display = ('id', 'first_name', 'last_name', 'email', 'mobile_number',)
fieldsets = (
(None, {'fields': ('username', 'password',)}),
('Personal info', {'fields': ('first_name', 'last_name', 'email', 'date_joined', 'last_login', 'is_online',)}),
('Permissions', {'fields': ('is_active', 'is_staff', 'is_superuser', 'groups', 'user_permissions',)}),
('Coco', {'fields': ('my_time_field', 'mobile_number',)}),
)
add_fieldsets = (
(None, {
'classes': ('wide',),
'fields': ('username', 'mobile_number', 'email', 'first_name', 'last_name',)}
),
)
search_fields = ('id', 'mobile_number', 'email', 'first_name', 'last_name',)
ordering = ('last_name', 'first_name',)
class Meta:
model = CocoUser
I derived from AbstractUser and had the same problem. No Users in Auth section.
That because I forgot to register my custom user admin class.
Maybe you need to register your FooUser admin class first.
I am trying to hide parts of my API (DRF) from the AbstractUser.
Here is my models.py:
class Company(models.Model):
name = models.TextField()
class User(AbstractUser):
company = models.ForeignKey(Company, null=True)
Here serializers.py:
class CompanySerializer(serializers.ModelSerializer):
class Meta:
model = Company
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = (
'username', 'email', 'company', 'first_name', 'last_name', 'date_joined', 'last_login', 'is_active',
'is_staff', 'groups', 'user_permissions'
)
And views.py:
class CompanyList(generics.ListCreateAPIView):
model = Company
serializer_class = CompanySerializer
class UserList(generics.ListCreateAPIView):
model = User
serializer_class = UserSerializer
So what I'm trying to get - get the JSON for the AbstractUser, but he can only see the company he is assigned to.
How can I do that? I have tried using def has_object_permission(), but I'm not really sure how to do it.
You can override the queryset to return user's company only.
class CompanyList(generics.ListCreateAPIView):
model = Company
serializer_class = CompanySerializer
def get_queryset(self):
return Company.objects.filter(pk=self.request.user.company.pk)