【18】网站搭建:自定义用户模型
一、前言
在Django自带的User类中,只有用户名、邮箱、密码等等一些基础信息。如果此时有添加用户电话,昵称,qq号等其他信息的需求时,自带User类的弊端就出现了。那么如果出现上述需求时,就需要自定义用户模型。
在Django的文档中对于自定义用户模型,有下面这么两段话。
有两种方法可以扩展默认User模型而无需替换自己的模型。如果您需要的更改纯粹是行为上的,并且不需要对数据库中存储的内容进行任何更改,则可以基于创建代理模型User。这允许代理模型提供的任何功能,包括默认排序,自定义管理器或自定义模型方法。
如果您希望存储与之相关的信息User,可以使用 OneToOneField包含这些字段的模型来获取其他信息。这种一对一模型通常称为配置文件模型,因为它可能存储有关站点用户的非身份验证相关信息。
二、自定义模型继承AbstractUser
1.AbstractUser 类的部分代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | class AbstractUser(AbstractBaseUser, PermissionsMixin): """ An abstract base class implementing a fully featured User model with admin-compliant permissions. Username and password are required. Other fields are optional. """ username_validator = UnicodeUsernameValidator() if six.PY3 else ASCIIUsernameValidator() username = models.CharField( _( 'username' ), max_length = 150 , unique = True , help_text = _( 'Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.' ), validators = [username_validator], error_messages = { 'unique' : _( "A user with that username already exists." ), }, ) first_name = models.CharField(_( 'first name' ), max_length = 30 , blank = True ) last_name = models.CharField(_( 'last name' ), max_length = 30 , blank = True ) email = models.EmailField(_( 'email address' ), blank = True ) is_staff = models.BooleanField( _( 'staff status' ), default = False , help_text = _( 'Designates whether the user can log into this admin site.' ), ) is_active = models.BooleanField( _( 'active' ), default = True , help_text = _( 'Designates whether this user should be treated as active. ' 'Unselect this instead of deleting accounts.' ), ) |
2.平时经常使用的User类
1 2 3 4 5 6 7 8 9 | class User(AbstractUser): """ Users within the Django authentication system are represented by this model. Username, password and email are required. Other fields are optional. """ class Meta(AbstractUser.Meta): swappable = 'AUTH_USER_MODEL' |
可以看出同样的方法,我也可以试着使用一个新的 User 来继承 AbstractUser 类,并添加上需要的字段。我一开始在本地开发使用的是这种办法,后来出了很多的一系列问题,比如数据库迁移失败,修改表结构,而且数据库迁移失败是由于migrations文件冲突导致的,必须删除该迁移文件,并重新生成迁移文件。考虑到这样下去,会影响到我生产环境的数据库,而且需要修改的代码也比较多,所以最后不得不弃用此方法。
如果要具体使用这种方法,可以查看Django文档的扩展现有User模型是如何使用的。
3.优缺点
1) 优点:
①自定义强。
②没有不必要的字段(需要继承AbstractBaseUser)。
2) 缺点:
①需要删除库来或者要项目一开始就使用。
②配置admin麻烦 。
三、新的模型拓展关联User
除了将新模型继承 AbstractUser ,还可建立新模型使其关联User。这种方法非常适合项目已经基本成型,但在后来开发中又需要修改用户表结构的操作,最终我选用了拓展关联User的方法。
1.官方例子
例如创建一个Employee模型:
1 2 3 4 5 | from django.contrib.auth.models import User class Employee(models.Model): user = models.OneToOneField(User, on_delete = models.CASCADE) department = models.CharField(max_length = 100 ) |
假设现有员工Fred Smith同时拥有User和Employee模型,你可以使用Django的标准相关模型约定访问相关信息:
1 2 | >>> u = User.objects.get(username = 'fsmith' ) >>> freds_department = u.employee.department |
要将配置文件模型的字段添加到管理员的用户页面,需要在应用程序中定义一个 InlineModelAdmin(对于此示例,我们将使用a StackedInline)admin.py并将其添加到在UserAdmin类中注册的 User类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | from django.contrib import admin from django.contrib.auth.admin import UserAdmin as BaseUserAdmin from django.contrib.auth.models import User from my_user_profile_app.models import Employee # Define an inline admin descriptor for Employee model # which acts a bit like a singleton class EmployeeInline(admin.StackedInline): model = Employee can_delete = False verbose_name_plural = 'employee' # Define a new User admin class UserAdmin(BaseUserAdmin): inlines = (EmployeeInline,) # Re-register UserAdmin admin.site.unregister(User) admin.site.register(User, UserAdmin) |
可以看到,在注册应用时,用到了admin下的StackedInline,这里与xadmin的注册有所区分,后面会提到。
2.优缺点
1) 优点:
①使用方便。
②不用删除库重来影响整体架构。
2) 缺点:
①对比继承方法,查询速度稍稍慢一丁点。
3.需求分析
在一开始我就用的是Django自带的用户管理系统,所以我的用户属性只有用户名、邮箱、密码。光靠这些不是很容易记住用户的身份,应该还需要类似于昵称的属性,这样可以让用户以用户用户名进行登录,登录之后可以显示他们自己设置的昵称。
4.新建Profile模型
我定义了一个Profile类,通过models的OneToOneField将其一对一地关联到User类,这里我只新增了nickname(昵称),也可以添加其他的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | from django.db import models from django.contrib.auth.models import User class Profile(models.Model): user = models.OneToOneField(User, on_delete = models.CASCADE, verbose_name = '用户名' ) nickname = models.CharField(max_length = 20 , verbose_name = '昵称' ) def __str__( self ): return self .nickname class Meta: verbose_name = '昵称' verbose_name_plural = '昵称' |
5.注册Profile模型
如果你的后台用的是admin,那么直接根据官方的例子就能正常将新的User模型注册到后台。这里我用的是xadmin后台,它与admin一样,都属于Djnago的后台管理,只不过xadmin的功能要略显强大一些,具体在Django中配置xadmin可以查看网站搭建 (第十四天) xadmin后台强化,这里就不赘述了。因为xadmin在注册应用时,继承的是object,而admin继承的是admin.ModelAdmin,所以在实现注册模型上有一定的区别。
之前,我在xadmin模块中怎样都找不到StackedInline,心里想着完了,又要重新使用admin了。试弄了一会,发现其实InlineModelAdmin还是照样继承object,而UserAdmin在xadmin/plugins/auth 目录下。最后实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | import xadmin from xadmin.plugins.auth import UserAdmin as BaseAdmin from django.contrib.auth.models import User from .models import Profile class ProfileInline( object ): model = Profile extra = 0 class UserAdmin(BaseAdmin): inlines = [ProfileInline] list_display = [ 'username' , 'nickname' , 'email' , 'is_staff' , 'is_active' , 'is_superuser' ] def nickname( self , obj): return obj.profile.nickname nickname.short_description = '昵称' xadmin.site.unregister(User) xadmin.site.register(User, UserAdmin) |
上面是将Profile模型的nickname字段注册到User中,还可以将Profile模型再注册一个应用。
1 2 3 4 5 6 7 8 9 | class ProfileAdmin( object ): """ 作用:自定义文章管理工具 admin.ModelAdmin:继承admin.ModelAdmin类 """ list_display = [ 'user' , 'nickname' ] xadmin.site.register(Profile, ProfileAdmin) |
做好这一步,就可以去xadmin后台为用户添加昵称了。
6.添加User模型的方法
实现好模型注册以后,为了能将用户的昵称显示在页面中,还需要为User类绑定一些方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | def get_nickname_or_username( self ): if Profile.objects. filter (user = self ).exists(): profile = Profile.objects.get(user = self ) return profile.nickname else : return self .username def has_nickname( self ): return Profile.objects. filter (user = self ).exists() # 绑定方法不需要添加括号 User.get_nickname_or_username = get_nickname_or_username User.has_nickname = has_nickname |
然后就可以在之前显示username的地方修改为nickname的显示,如导航栏的欢迎用户。
1 2 3 | < a href="#" class="dropdown-toggle hidden-sm" data-toggle="dropdown" role="button"> < span class="glyphicon glyphicon-user item"></ span > 您好: {{ user.get_nickname_or_username }}< span class="caret"></ span > </ a > |
7.定义修改昵称表单
之前还是需要管理员自己到后台对用户进行添加昵称操作,要是可以让用户自己修改昵称岂不是更好。实现起来也不难,只要再建立一个form表单,将post的数据清洗一下,再将合法的昵称绑定在user上。关于Django的form表单使用,可以参考之前写的用户注册或登录表单。
同样,在user/forms.py下新建ChangeNameForm表单。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | from django.contrib import auth from django.contrib.auth.models import User class ChangeNameForm(forms.Form): new_nickname = forms.CharField( label = '新昵称' , max_length = 20 , min_length = 3 , widget = forms.TextInput( attrs = { 'placeholder' : '请输入新的昵称' } ) ) def __init__( self , * args, * * kwargs): if 'user' in kwargs: self .user = kwargs.pop( 'user' ) super (ChangeNameForm, self ).__init__( * args, * * kwargs) def clean( self ): # 验证用户是否处在登录状态 if self .user.is_authenticated: self .cleaned_data[ 'user' ] = self .user else : raise forms.ValidationError( '用户尚未登录' ) return self .cleaned_data def clean_new_nickname( self ): new_nickname = self .cleaned_data.get( 'new_nickname' , '').strip() if new_nickname = = '': raise forms.ValidationError( "新的昵称不能为空" ) return new_nickname |
8.定义修改昵称的逻辑处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | from .models import Profile from .forms import * from django.shortcuts import redirect, render def change_name(request): """修改昵称""" if request.method = = 'POST' : name_form = ChangeNameForm(request.POST, user = request.user) if name_form.is_valid(): new_nickname = name_form.cleaned_data[ 'new_nickname' ] profile, created = Profile.objects.get_or_create(user = request.user) profile.nickname = new_nickname profile.save() return redirect(request.GET.get( 'from' , reverse( 'blog:home' ))) else : name_form = ChangeNameForm() context = { 'name_form' : name_form} return render(request, 'user/change_name.html' , context) |
9.建立修改昵称视图
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | {% extends 'base.html' %} {% load staticfiles %} {% block title %} 修改昵称 {% endblock %} {% block nav_login_active %}active{% endblock %} {% block lunbobox %}{% endblock %} {% block content %} < div class="container" style="margin-top: 70px;"> < div class="head-login"> < h2 class="text-info">修改昵称</ h2 > < span >与我取得联系,共同成长吧</ span > </ div > < div class="change_name"> < form action="" method="POST"> {% csrf_token %} {% for field in name_form %} < label for="{{ field.id_for_label }}">{{ field.label }}:</ label > {{ field }} < p class="text-danger">{{ field.errors.as_text }}</ p > {% endfor %} {# 错误信息标红 #} < span class="text-danger">{{ login_form.non_field_errors }}</ span > {# < span >用户名:</ span > #} {# < input type="text" name="username"> #} {# < span >密码:</ span > #} {# < input type="password" name="password"> #} < button class="btn btn-primary pull-right" type="submit">修改</ button > </ form > </ div > </ div > {% endblock %} |
原文出处:https://jzfblog.com/detail/119,文章的更新编辑以此链接为准。欢迎关注源站文章!
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 深入理解 Mybatis 分库分表执行原理
· 如何打造一个高并发系统?
· .NET Core GC压缩(compact_phase)底层原理浅谈
· 现代计算机视觉入门之:什么是图片特征编码
· .NET 9 new features-C#13新的锁类型和语义
· Spring AI + Ollama 实现 deepseek-r1 的API服务和调用
· 《HelloGitHub》第 106 期
· 数据库服务器 SQL Server 版本升级公告
· 深入理解Mybatis分库分表执行原理
· 使用 Dify + LLM 构建精确任务处理应用