Search code examples
djangodjango-modelsdjango-viewsdjango-admin

How do I set a password for a Custom User model inside the Django admin page?


I have defined a custom user model using AbstractBaseUser class. Below is the implementation for your reference.

#models.py

class UserManager(BaseUserManager):
    def create_user(self, email, firstname, lastname, contact, password):
        if not email:
            raise ValueError('You must provide an email address')
        if not contact:
            raise ValueError('You must provide a contact number')
        
        email = self.normalize_email(email)
        user = self.model(email=email, firstname=firstname, lastname=lastname, contact=contact)
        user.set_password(password)
        user.save(using=self._db)
        return user

    def create_superuser(self, email, firstname, lastname, contact, password):
        user = self.create_user(email, firstname, lastname, contact, password)
        user.is_admin = True
        user.save(using=self._db)
        return user

class Users(AbstractBaseUser):
    id = models.AutoField(primary_key=True)
    firstname = models.CharField(max_length=50)
    lastname = models.CharField(max_length=50)
    contact = models.CharField(max_length=10, unique=True)
    email = models.EmailField(unique=True)
    created_at = models.DateTimeField(auto_now_add=True)
    is_staff = models.BooleanField(default=True)
    is_admin = models.BooleanField(default=False)

    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = ['firstname', 'lastname', 'contact']

    objects = UserManager()

    def save(self, *args, **kwargs):
        if not self.firstname or not self.lastname or not self.contact or not self.email or not self.password:
            raise ValueError('All required fields must be provided')
        super().save(*args, **kwargs)

    def __str__(self):
        return str(self.id) + '. ' + self.firstname + ' ' + self.lastname

    def has_perm(self, perm, obj=None):
        return self.is_admin

    def has_module_perms(self, app_label):
        return True
    
    class Meta:
        db_table = 'users'
        verbose_name_plural = 'Users'

Everything works well apart from setting password for a new user or updating same for an existing user in Django's native Admin page. While setting password, it sets passwords in a plain text rather than hashing it.

Here's my admin.py code.

#admin.py

from django.contrib import admin
from .models import *

admin.site.site_header = 'ROI Administration'
admin.site.site_title = 'ROI | Admin Panel'
admin.site.index_title = 'ROI Databases'

class UsersAdmin(admin.ModelAdmin):
    ordering = ['id']
    readonly_fields = ['id', 'created_at', 'last_login']
    fieldsets = [
        (None, {'fields': ['id', 'firstname', 'lastname', 'email', 'contact', 'last_login', 'created_at', 'is_staff', 'is_admin', 'password']}),
    ]

admin.site.register(Users, UsersAdmin)

Refer below the screenshot.

enter image description here

How do I resolve this?


Solution

  • I did some research and finally solved it. I created CustomUserAdmin and CustomUserChangeForm in admin.py file.

    #admin.py
    
    from .models import *
    from django.contrib import admin
    from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
    from django.contrib.auth.forms import UserChangeForm, UserCreationForm, AdminPasswordChangeForm
    from django.template.response import TemplateResponse
    from django.core.exceptions import PermissionDenied
    
    class CustomUserChangeForm(UserChangeForm):
        class Meta:
            model = Users
            fields = '__all__'
    
    class CustomUserAdmin(BaseUserAdmin):
        form = CustomUserChangeForm
        add_form = UserCreationForm
        ordering = ['id']
        readonly_fields = ['id', 'created_at', 'last_login']
        list_display = ['id', 'firstname', 'lastname', 'email', 'contact', 'is_staff', 'is_admin']
        fieldsets = (
            ('Credentials', {'fields': ('email', 'password')}),
            (('Personal info'), {'fields': ('firstname', 'lastname', 'contact')}),
            ('Permissions', {'fields': ('is_staff', 'is_admin')}),
            ('Important dates', {'fields': ('last_login', 'created_at')})
        )
    
        add_fieldsets = (
            (None, {
                'classes': ('wide',),
                'fields': ('email', 'firstname', 'lastname', 'contact', 'password1', 'password2', 'is_staff', 'is_admin'),
            }),
        )
    
        filter_horizontal = ()
        list_filter = []
    
        def change_password(self, request, user_id, form_url=''):
            user = self.get_object(request, user_id)
            if not self.has_change_permission(request, user):
                raise PermissionDenied
    
            if request.method == 'POST':
                form = AdminPasswordChangeForm(user, request.POST)
                if form.is_valid():
                    form.save()
                    return self.response_change(request, user)
            else:
                form = AdminPasswordChangeForm(user)
    
            fieldsets = [(None, {'fields': list(form.base_fields)})]
            admin_form = admin.helpers.AdminForm(form, fieldsets, {})
    
            context = {
                'title': f'Change password: {user.email}',
                'adminForm': admin_form,
                'form_url': form_url,
                'form': form,
                'is_popup': "_popup" in request.POST or "_popup" in request.GET,
                'opts': self.model._meta,
                'original': user,
                'save_as': False,
                'show_save': True,
                **self.admin_site.each_context(request),
            }
    
            return TemplateResponse(request, 'admin/auth/user/change_password.html', context)
    
        def get_urls(self):
            from django.urls import path
            urls = super().get_urls()
            custom_urls = [
                path(
                    '<user_id>/password/',
                    self.admin_site.admin_view(self.change_password),
                    name='auth_user_password_change',
                ),
            ]
            return custom_urls + urls
    
    admin.site.register(Users, CustomUserAdmin)
    

    It solves the problem.