django 扩展User
环境
Python 3.5.1
django 1.9.1
前言今天用django写web平台,第一时间想到django自带的认证,连session都提供好了,既然有轮子了,我们就不需要自己造了。
扩展django user的部分方法:
一、重写user,将新的user注册到admin,还要重写认证
二、继承user,进行扩展(记得在settings中设置AUTH_USER_MODEL
AUTH_USER_MODEL="myapp.NewUser"
)
2.1 继承AbstractUser类
如果你对django自带的User model感到满意, 又希望增加额外的field的话, 你可以扩展AbstractUser类(本文就是这种方法实现)
新的django User类支持email,也可以用email作为用户登陆
2.2 继承AbstractBaseUser类
AbstractBaseUser中只含有3个field: password, last_login和is_active. 这个就是你自己高度定制自己需要的东西
model.py
#classUserManager(BaseUserManager):##defcreate_user(self,email,username,mobile,password=None):#defcreate_user(self,email,username,mobile,password=None,**kwargs):#"""通过邮箱,密码,手机号创建用户"""#ifnotemail:#raiseValueError(u'用户必须要有邮箱')##user=self.model(#email=self.normalize_email(email),#username=username,#mobile=mobile,#)##user.set_password(password)#ifkwargs:#ifkwargs.get('qq',None):user.qq=kwargs['qq']#qq号#ifkwargs.get('is_active',None):user.is_active=kwargs['is_active']#是否激活#ifkwargs.get('wechat',None):user.wechat=kwargs['wechat']#微信号#ifkwargs.get('refuserid',None):user.refuserid=kwargs['refuserid']#推荐人ID#ifkwargs.get('vevideo',None):user.vevideo=kwargs['vevideo']#视频认证#ifkwargs.get('identicard',None):user.identicard=kwargs['identicard']#×××认证#ifkwargs.get('type',None):user.type=kwargs['type']#user.save(using=self._db)#returnuser##defcreate_superuser(self,email,username,password,mobile):#user=self.create_user(email,#username=username,#password=password,#mobile=mobile,#)#user.is_admin=True#user.save(using=self.db)#returnuser##classUser(AbstractBaseUser,PermissionsMixin):#"""扩展User"""#email=models.EmailField(verbose_name='Email',max_length=255,unique=True,db_index=True)#username=models.CharField(max_length=50)#qq=models.CharField(max_length=16)#mobile=models.CharField(max_length=11)#wechat=models.CharField(max_length=100)#refuserid=models.CharField(max_length=20)#vevideo=models.BooleanField(default=False)#identicard=models.BooleanField(default=False)#created_at=models.DateTimeField(auto_now_add=True)#type=models.CharField(u'用户类型',default='0',max_length=1)##is_active=models.BooleanField(default=True)#is_admin=models.BooleanField(default=False)##objects=UserManager()##USERNAME_FIELD='email'#REQUIRED_FIELDS=['mobile']##defget_full_name(self):##Theuserisidentifiedbytheiremailaddress#returnself.email##defget_short_name(self):##Theuserisidentifiedbytheiremailaddress#returnself.email###Onpython2:def__unicode__(self):#def__str__(self):#returnself.email##defhas_perm(self,perm,obj=None):#"Doestheuserhaveaspecificpermission?"##Simplestpossibleanswer:Yes,always#returnTrue##defhas_module_perms(self,app_label):#"Doestheuserhavepermissionstoviewtheapp`app_label`?"##Simplestpossibleanswer:Yes,always#returnTrue##@property#defis_staff(self):#"Istheuseramemberofstaff?"##Simplestpossibleanswer:Alladminsarestaff#returnself.is_admin#
admin.py
#classUserCreationForm(forms.ModelForm):#password1=forms.CharField(label='Password',widget=forms.PasswordInput)#password2=forms.CharField(label='Passwordconfirmation',widget=forms.PasswordInput)##classMeta:#model=MyUser#fields=('email','mobile')##defclean_password2(self):##Checkthatthetwopasswordentriesmatch#password1=self.cleaned_data.get("password1")#password2=self.cleaned_data.get("password2")#ifpassword1andpassword2andpassword1!=password2:#raiseforms.ValidationError("Passwordsdon'tmatch")#returnpassword2##defsave(self,commit=True):##Savetheprovidedpasswordinhashedformat#user=super(UserCreationForm,self).save(commit=False)#user.set_password(self.cleaned_data["password1"])#ifcommit:#user.save()#returnuser###classUserChangeForm(forms.ModelForm):#password=ReadOnlyPasswordHashField()##classMeta:#model=MyUser#fields=('email','password','mobile','is_active','is_admin')##defclean_password(self):#returnself.initial['password']##classUserAdmin(BaseUserAdmin):#form=UserChangeForm#add_form=UserCreationForm#list_display=('email','mobile','is_admin')#list_filter=('is_admin',)#fieldsets=(#(None,{'fields':('email','password')}),#('Personalinfo',{'fields':('mobile',)}),#('Permissions',{'fields':('is_admin',)}),#)#add_fieldsets=(#(None,{#'classes':('wide',),#'fields':('email','mobile','password1','password2')}#),#)#search_fields=('email',)#ordering=('email',)#filter_horizontal=()##admin.site.register(MyUser,UserAdmin)#admin.site.unregister(Group)
三、profile方式扩展,但是从django1.6开始就放弃这种写法
四、网上找的方法,不改源码、不加新表,扩展user
fromdjango.dbimportmodelsfromdjango.contrib.auth.modelsimportUserfromdjango.contrib.auth.adminimportUserAdminimportdatetimeclassProfileBase(type):def__new__(cls,name,bases,attrs):#构造器,(名字,基类,类属性)module=attrs.pop('__module__')parents=[bforbinbasesifisinstance(b,ProfileBase)]ifparents:fields=[]forobj_name,objinattrs.items():ifisinstance(obj,models.Field):fields.append(obj_name)User.add_to_class(obj_name,obj)####最重要的步骤UserAdmin.fieldsets=list(UserAdmin.fieldsets)UserAdmin.fieldsets.append((name,{'fields':fields}))returnsuper(ProfileBase,cls).__new__(cls,name,bases,attrs)classProfileUser(object):__metaclass__=ProfileBaseclassExtraInfo(ProfileUser):phone_number=models.CharField(max_length=20,verbose_name=u'电话号码')
稍微解释一下这段代码: ProfileBase是自定义的一个元类,继承自types.ClassType
,其中ProfileUser为一个基类,其元类为ProfileBase,而ExtraInfo才是我们真正自定义字段的类,之所以把基类ProfileUser和ExtraInfo分开,是为了便于在其他地方引用ProfileUser,进行自定义扩展。简单说来,当解释器看到你在定义一个ProfileUser类的子类,而ProfileUser类的元类是ProfileBase,所以ExtraInfo的元类也是ProfileBase,在定义ProfileUser的子类的时候,它就会执行元类ProfileBase中的new中代码,并且将正在定义的类的(名字,基类,类属性)作为参数传递给new,这里的name就是类名ExtraInfo,attrs中则包含你新加的字段,通过User.add_to_class
把新的字段加入到User中,为了能在admin中显示出来,把它加入到UserAdmin.fieldsets
中,这样就能在后台编辑这个这个字段,当然,你也可以加入到ist_display,使之在列表中显示。
如果你有其他app也想往User Model中加field或方法,都只要通过子类ProfileUser类,然后使用声明语法进行定义即可,所有其他工作都有元类帮你完成。这也是所有django的model的内部工作,你可以用此方法扩展任何model。
转载出处:http://www.opscoder.info/extend_user.html
需求
注册登录都有现成的代码,主要是自带的User字段只有(email,username,password),所以需要扩展User,来增加自己需要的字段
代码如下:
model.py
#coding:utf8fromdjango.dbimportmodelsfromdjango.contrib.auth.modelsimportAbstractUserfromdjango.utils.encodingimportpython_2_unicode_compatible#Createyourmodelshere.@python_2_unicode_compatible"""是django内置的兼容python2和python3的unicode语法的一个装饰器只是针对__str__方法而用的,__str__方法是为了后台管理(admin)和djangoshell的显示,Meta类也是为后台显示服务的"""classMyUser(AbstractUser):qq=models.CharField(u'qq号',max_length=16)weChat=models.CharField(u'微信账号',max_length=100)mobile=models.CharField(u'手机号',primary_key=True,max_length=11)identicard=models.BooleanField(u'×××认证',default=False)#默认是0,未认证,1:×××认证,2:视频认证refuserid=models.CharField(u'推荐人ID',max_length=20)Level=models.CharField(u'用户等级',default='0',max_length=2)#默认是0,用户等级0-9vevideo=models.BooleanField(u'视频认证',default=False)#默认是0,未认证。1:已认证Type=models.CharField(u'用户类型',default='0',max_length=1)#默认是0,未认证,1:刷手2:商家def__str__(self):returnself.username
settings.py
AUTH_USER_MODEL='appname.MyUser'AUTHENTICATION_BACKENDS=('django.contrib.auth.backends.ModelBackend',)
踩过的坑:
1、扩展user表后,要在settings.py 添加
AUTH_USER_MODEL='appname.扩展user的classname'
2、认证后台要在settings添加,尤其记得加逗号,否则报错
认证后台不加的报错
Django-AttributeError'User'objecthasnoattribute'backend'
没加逗号的报错
ImportError:adoesn'tlooklikeamodulepath
form.py
#coding:utf-8fromdjangoimportforms#注册表单classRegisterForm(forms.Form):username=forms.CharField(label='用户名',max_length=100)password=forms.CharField(label='密码',widget=forms.PasswordInput())password2=forms.CharField(label='确认密码',widget=forms.PasswordInput())mobile=forms.CharField(label='手机号',max_length=11)email=forms.EmailField()qq=forms.CharField(label='QQ号',max_length=16)type=forms.ChoiceField(label='注册类型',choices=(('buyer','买家'),('saler','商家')))defclean(self):ifnotself.is_valid():raiseforms.ValidationError('所有项都为必填项')elifself.cleaned_data['password2']!=self.cleaned_data['password']:raiseforms.ValidationError('两次输入密码不一致')else:cleaned_data=super(RegisterForm,self).clean()returncleaned_data#登陆表单classLoginForm(forms.Form):username=forms.CharField(label='用户名',widget=forms.TextInput(attrs={"placeholder":"用户名","required":"required",}),max_length=50,error_messages={"required":"username不能为空",})password=forms.CharField(label='密码',widget=forms.PasswordInput(attrs={"placeholder":"密码","required":"required",}),max_length=20,error_messages={"required":"password不能为空",})
views.py
fromdjango.shortcutsimportrender,render_to_responsefrom.modelsimportMyUserfromdjango.httpimportHttpResponse,HttpResponseRedirectfromdjango.templateimportRequestContextimporttimefrom.myclassimportformfromdjango.templateimportRequestContextfromdjango.contrib.authimportauthenticate,login,logout#注册defregister(request):error=[]#ifrequest.method=='GET':#returnrender_to_response('register.html',{'uf':uf})ifrequest.method=='POST':uf=form.RegisterForm(request.POST)ifuf.is_valid():username=uf.cleaned_data['username']password=uf.cleaned_data['password']password2=uf.cleaned_data['password2']qq=uf.cleaned_data['qq']email=uf.cleaned_data['email']mobile=uf.cleaned_data['mobile']type=uf.cleaned_data['type']ifnotMyUser.objects.all().filter(username=username):user=MyUser()user.username=usernameuser.set_password(password)user.qq=qquser.email=emailuser.mobile=mobileuser.type=typeuser.save()returnrender_to_response('member.html',{'username':username})else:uf=form.RegisterForm()returnrender_to_response('register.html',{'uf':uf,'error':error})#登陆defdo_login(request):ifrequest.method=='POST':lf=form.LoginForm(request.POST)iflf.is_valid():username=lf.cleaned_data['username']password=lf.cleaned_data['password']user=authenticate(username=username,password=password)#django自带auth验证用户名密码ifuserisnotNone:#判断用户是否存在ifuser.is_active:#判断用户是否激活login(request,user)#用户信息验证成功后把登陆信息写入sessionreturnrender_to_response("member.html",{'username':username})else:returnrender_to_response('disable.html',{'username':username})else:returnHttpResponse("无效的用户名或者密码!!!")else:lf=form.LoginForm()returnrender_to_response('index.html',{'lf':lf})#退出defdo_logout(request):logout(request)returnHttpResponseRedirect('/')
踩过的坑:
1、登陆的时候用自带的认证模块总是报none
user=authenticate(username=username,password=password)
查看源码发现是check_password的方法是用hash进行校验,之前注册的password写法是
user.password=password
这种写法是明文入库,需要更改密码的入库写法
user.set_password(password)
声明:本站所有文章资源内容,如无特殊说明或标注,均为采集网络资源。如若本站内容侵犯了原著者的合法权益,可联系本站删除。