十年網(wǎng)站開(kāi)發(fā)經(jīng)驗(yàn) + 多家企業(yè)客戶(hù) + 靠譜的建站團(tuán)隊(duì)
量身定制 + 運(yùn)營(yíng)維護(hù)+專(zhuān)業(yè)推廣+無(wú)憂(yōu)售后,網(wǎng)站問(wèn)題一站解決
環(huán)境
Python 3.5.1
django 1.9.1
今天用django寫(xiě)web平臺(tái),第一時(shí)間想到django自帶的認(rèn)證,連session都提供好了,既然有輪子了,我們就不需要自己造了。
擴(kuò)展django user的部分方法:
一、重寫(xiě)user,將新的user注冊(cè)到admin,還要重寫(xiě)認(rèn)證
二、繼承user,進(jìn)行擴(kuò)展(記得在settings中設(shè)置AUTH_USER_MODEL
AUTH_USER_MODEL = "myapp.NewUser"
)
2.1 繼承AbstractUser類(lèi)
如果你對(duì)django自帶的User model感到滿(mǎn)意, 又希望增加額外的field的話, 你可以擴(kuò)展AbstractUser類(lèi)(本文就是這種方法實(shí)現(xiàn))
新的django User類(lèi)支持email,也可以用email作為用戶(hù)登陸
2.2 繼承AbstractBaseUser類(lèi)
AbstractBaseUser中只含有3個(gè)field: password, last_login和is_active. 這個(gè)就是你自己高度定制自己需要的東西
model.py
# class UserManager(BaseUserManager): # # def create_user(self, email, username, mobile, password=None): # def create_user(self, email, username, mobile, password=None, **kwargs): # """通過(guò)郵箱,密碼,手機(jī)號(hào)創(chuàng)建用戶(hù)""" # if not email: # raise ValueError(u'用戶(hù)必須要有郵箱') # # user = self.model( # email = self.normalize_email(email), # username = username, # mobile = mobile, # ) # # user.set_password(password) # if kwargs: # if kwargs.get('qq', None): user.qq = kwargs['qq'] #qq號(hào) # if kwargs.get('is_active', None): user.is_active = kwargs['is_active'] #是否激活 # if kwargs.get('wechat', None): user.wechat = kwargs['wechat'] #微信號(hào) # if kwargs.get('refuserid', None): user.refuserid = kwargs['refuserid'] #推薦人ID # if kwargs.get('vevideo', None): user.vevideo = kwargs['vevideo'] #視頻認(rèn)證 # if kwargs.get('identicard', None): user.identicard = kwargs['identicard'] #×××認(rèn)證 # if kwargs.get('type', None): user.type = kwargs['type'] # user.save(using=self._db) # return user # # def create_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) # return user # # class User(AbstractBaseUser, PermissionsMixin): # """擴(kuò)展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'用戶(hù)類(lèi)型', 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'] # # def get_full_name(self): # # The user is identified by their email address # return self.email # # def get_short_name(self): # # The user is identified by their email address # return self.email # # #On python 2: def __unicode__(self): # def __str__(self): # return self.email # # def has_perm(self, perm, obj=None): # "Does the user have a specific permission?" # # Simplest possible answer: Yes, always # return True # # def has_module_perms(self, app_label): # "Does the user have permissions to view the app `app_label`?" # # Simplest possible answer: Yes, always # return True # # @property # def is_staff(self): # "Is the user a member of staff?" # # Simplest possible answer: All admins are staff # return self.is_admin #
admin.py
# class UserCreationForm(forms.ModelForm): # password1 = forms.CharField(label='Password', widget=forms.PasswordInput) # password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput) # # class Meta: # model = MyUser # fields = ('email', 'mobile') # # def clean_password2(self): # # Check that the two password entries match # password1 = self.cleaned_data.get("password1") # password2 = self.cleaned_data.get("password2") # if password1 and password2 and password1 != password2: # raise forms.ValidationError("Passwords don't match") # return password2 # # def save(self, commit=True): # # Save the provided password in hashed format # user = super(UserCreationForm, self).save(commit=False) # user.set_password(self.cleaned_data["password1"]) # if commit: # user.save() # return user # # # class UserChangeForm(forms.ModelForm): # password = ReadOnlyPasswordHashField() # # class Meta: # model = MyUser # fields = ('email', 'password', 'mobile', 'is_active', 'is_admin') # # def clean_password(self): # return self.initial['password'] # # class UserAdmin(BaseUserAdmin): # form = UserChangeForm # add_form = UserCreationForm # list_display = ('email', 'mobile','is_admin') # list_filter = ('is_admin',) # fieldsets = ( # (None, {'fields': ('email', 'password')}), # ('Personal info', {'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方式擴(kuò)展,但是從django1.6開(kāi)始就放棄這種寫(xiě)法
四、網(wǎng)上找的方法,不改源碼、不加新表,擴(kuò)展user
from django.db import models from django.contrib.auth.models import User from django.contrib.auth.admin import UserAdmin import datetime class ProfileBase(type): def __new__(cls, name, bases, attrs): #構(gòu)造器,(名字,基類(lèi),類(lèi)屬性) module = attrs.pop('__module__') parents = [b for b in bases if isinstance(b, ProfileBase)] if parents: fields = [] for obj_name, obj in attrs.items(): if isinstance(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})) return super(ProfileBase, cls).__new__(cls, name, bases, attrs) class ProfileUser(object): __metaclass__ = ProfileBase class ExtraInfo(ProfileUser): phone_number= models.CharField(max_length = 20, verbose_name=u'電話號(hào)碼')
稍微解釋一下這段代碼: ProfileBase是自定義的一個(gè)元類(lèi),繼承自types.ClassType
,其中ProfileUser為一個(gè)基類(lèi),其元類(lèi)為ProfileBase,而ExtraInfo才是我們真正自定義字段的類(lèi),之所以把基類(lèi)ProfileUser和ExtraInfo分開(kāi),是為了便于在其他地方引用ProfileUser,進(jìn)行自定義擴(kuò)展。簡(jiǎn)單說(shuō)來(lái),當(dāng)解釋器看到你在定義一個(gè)ProfileUser類(lèi)的子類(lèi),而ProfileUser類(lèi)的元類(lèi)是ProfileBase,所以ExtraInfo的元類(lèi)也是ProfileBase,在定義ProfileUser的子類(lèi)的時(shí)候,它就會(huì)執(zhí)行元類(lèi)ProfileBase中的new中代碼,并且將正在定義的類(lèi)的(名字,基類(lèi),類(lèi)屬性)作為參數(shù)傳遞給new,這里的name就是類(lèi)名ExtraInfo,attrs中則包含你新加的字段,通過(guò)User.add_to_class
把新的字段加入到User中,為了能在admin中顯示出來(lái),把它加入到UserAdmin.fieldsets
中,這樣就能在后臺(tái)編輯這個(gè)這個(gè)字段,當(dāng)然,你也可以加入到ist_display,使之在列表中顯示。
如果你有其他app也想往User Model中加field或方法,都只要通過(guò)子類(lèi)ProfileUser類(lèi),然后使用聲明語(yǔ)法進(jìn)行定義即可,所有其他工作都有元類(lèi)幫你完成。這也是所有django的model的內(nèi)部工作,你可以用此方法擴(kuò)展任何model。
轉(zhuǎn)載出處:http://www.opscoder.info/extend_user.html
需求
注冊(cè)登錄都有現(xiàn)成的代碼,主要是自帶的User字段只有(email,username,password),所以需要擴(kuò)展User,來(lái)增加自己需要的字段
代碼如下:
model.py
#coding:utf8 from django.db import models from django.contrib.auth.models import AbstractUser from django.utils.encoding import python_2_unicode_compatible # Create your models here. @python_2_unicode_compatible """是django內(nèi)置的兼容python2和python3的unicode語(yǔ)法的一個(gè)裝飾器 只是針對(duì) __str__ 方法而用的,__str__方法是為了后臺(tái)管理(admin)和django shell的顯示,Meta類(lèi)也是為后臺(tái)顯示服務(wù)的 """ class MyUser(AbstractUser): qq = models.CharField(u'qq號(hào)', max_length=16) weChat =models.CharField(u'微信賬號(hào)', max_length=100) mobile =models.CharField(u'手機(jī)號(hào)', primary_key=True, max_length=11) identicard =models.BooleanField(u'×××認(rèn)證', default=False) #默認(rèn)是0,未認(rèn)證, 1:×××認(rèn)證, 2:視頻認(rèn)證 refuserid = models.CharField(u'推薦人ID', max_length=20) Level = models.CharField(u'用戶(hù)等級(jí)', default='0', max_length=2) #默認(rèn)是0,用戶(hù)等級(jí)0-9 vevideo = models.BooleanField(u'視頻認(rèn)證', default=False) #默認(rèn)是0,未認(rèn)證。 1:已認(rèn)證 Type =models.CharField(u'用戶(hù)類(lèi)型', default='0', max_length=1) #默認(rèn)是0,未認(rèn)證, 1:刷手 2:商家 def __str__(self): return self.username
settings.py
AUTH_USER_MODEL = 'appname.MyUser' AUTHENTICATION_BACKENDS = ('django.contrib.auth.backends.ModelBackend',)
踩過(guò)的坑:
1、擴(kuò)展user表后,要在settings.py 添加
AUTH_USER_MODEL = 'appname.擴(kuò)展user的class name'
2、認(rèn)證后臺(tái)要在settings添加,尤其記得加逗號(hào),否則報(bào)錯(cuò)
認(rèn)證后臺(tái)不加的報(bào)錯(cuò)
Django-AttributeError 'User' object has no attribute 'backend'
沒(méi)加逗號(hào)的報(bào)錯(cuò)
ImportError: a doesn't look like a module path
form.py
#coding:utf-8 from django import forms #注冊(cè)表單 class RegisterForm(forms.Form): username = forms.CharField(label='用戶(hù)名',max_length=100) password = forms.CharField(label='密碼',widget=forms.PasswordInput()) password2 = forms.CharField(label='確認(rèn)密碼',widget=forms.PasswordInput()) mobile = forms.CharField(label='手機(jī)號(hào)', max_length=11) email = forms.EmailField() qq = forms.CharField(label='QQ號(hào)', max_length=16) type = forms.ChoiceField(label='注冊(cè)類(lèi)型', choices=(('buyer','買(mǎi)家'),('saler','商家'))) def clean(self): if not self.is_valid(): raise forms.ValidationError('所有項(xiàng)都為必填項(xiàng)') elif self.cleaned_data['password2'] != self.cleaned_data['password']: raise forms.ValidationError('兩次輸入密碼不一致') else: cleaned_data = super(RegisterForm, self).clean() return cleaned_data #登陸表單 class LoginForm(forms.Form): username = forms.CharField(label='用戶(hù)名',widget=forms.TextInput(attrs={"placeholder": "用戶(hù)名", "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
from django.shortcuts import render,render_to_response from .models import MyUser from django.http import HttpResponse,HttpResponseRedirect from django.template import RequestContext import time from .myclass import form from django.template import RequestContext from django.contrib.auth import authenticate,login,logout #注冊(cè) def register(request): error = [] # if request.method == 'GET': # return render_to_response('register.html',{'uf':uf}) if request.method == 'POST': uf = form.RegisterForm(request.POST) if uf.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'] if not MyUser.objects.all().filter(username=username): user = MyUser() user.username = username user.set_password(password) user.qq = qq user.email = email user.mobile = mobile user.type = type user.save() return render_to_response('member.html', {'username': username}) else: uf = form.RegisterForm() return render_to_response('register.html',{'uf':uf,'error':error}) #登陸 def do_login(request): if request.method =='POST': lf = form.LoginForm(request.POST) if lf.is_valid(): username = lf.cleaned_data['username'] password = lf.cleaned_data['password'] user = authenticate(username=username, password=password) #django自帶auth驗(yàn)證用戶(hù)名密碼 if user is not None: #判斷用戶(hù)是否存在 if user.is_active: #判斷用戶(hù)是否激活 login(request,user) #用戶(hù)信息驗(yàn)證成功后把登陸信息寫(xiě)入session return render_to_response("member.html", {'username':username}) else: return render_to_response('disable.html',{'username':username}) else: return HttpResponse("無(wú)效的用戶(hù)名或者密碼!!!") else: lf = form.LoginForm() return render_to_response('index.html',{'lf':lf}) #退出 def do_logout(request): logout(request) return HttpResponseRedirect('/')
踩過(guò)的坑:
1、登陸的時(shí)候用自帶的認(rèn)證模塊總是報(bào)none
user = authenticate(username=username, password=password)
查看源碼發(fā)現(xiàn)是check_password的方法是用hash進(jìn)行校驗(yàn),之前注冊(cè)的password寫(xiě)法是
user.password=password
這種寫(xiě)法是明文入庫(kù),需要更改密碼的入庫(kù)寫(xiě)法
user.set_password(password)
創(chuàng)新互聯(lián)www.cdcxhl.cn,專(zhuān)業(yè)提供香港、美國(guó)云服務(wù)器,動(dòng)態(tài)BGP最優(yōu)骨干路由自動(dòng)選擇,持續(xù)穩(wěn)定高效的網(wǎng)絡(luò)助力業(yè)務(wù)部署。公司持有工信部辦法的idc、isp許可證, 機(jī)房獨(dú)有T級(jí)流量清洗系統(tǒng)配攻擊溯源,準(zhǔn)確進(jìn)行流量調(diào)度,確保服務(wù)器高可用性。佳節(jié)活動(dòng)現(xiàn)已開(kāi)啟,新人活動(dòng)云服務(wù)器買(mǎi)多久送多久。