环境
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
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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
|
# class UserManager(BaseUserManager):
# # def create_user(self, email, username, mobile, password=None):
# def create_user(self, email, username, mobile, password=None, **kwargs):
# """通过邮箱,密码,手机号创建用户"""
# if not email:
# raise ValueError(u'用户必须要有邮箱')
#
# 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号
# if kwargs.get('is_active', None): user.is_active = kwargs['is_active'] #是否激活
# if kwargs.get('wechat', None): user.wechat = kwargs['wechat'] #微信号
# if kwargs.get('refuserid', None): user.refuserid = kwargs['refuserid'] #推荐人ID
# if kwargs.get('vevideo', None): user.vevideo = kwargs['vevideo'] #视频认证
# if kwargs.get('identicard', None): user.identicard = kwargs['identicard'] #身份证认证
# 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):
# """扩展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']
#
# 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
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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
|
# 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方式扩展,但是从django1.6开始就放弃这种写法
四、网上找的方法,不改源码、不加新表,扩展user
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
from
django.contrib.auth.admin
import
UserAdmin
import
datetime
class
ProfileBase(
type
):
def
__new__(
cls
, name, bases, attrs):
#构造器,(名字,基类,类属性)
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
'电话号码'
)
|
稍微解释一下这段代码: 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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
#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内置的兼容python2和python3的unicode语法的一个装饰器
只是针对 __str__ 方法而用的,__str__方法是为了后台管理(admin)和django shell的显示,Meta类也是为后台显示服务的
"""
class
MyUser(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-9
vevideo
=
models.BooleanField(u
'视频认证'
, default
=
False
)
#默认是0,未认证。 1:已认证
Type
=
models.CharField(u
'用户类型'
, default
=
'0'
, max_length
=
1
)
#默认是0,未认证, 1:刷手 2:商家
def
__str__(
self
):
return
self
.username
|
settings.py
1
2
|
AUTH_USER_MODEL
=
'appname.MyUser'
AUTHENTICATION_BACKENDS
=
(
'django.contrib.auth.backends.ModelBackend'
,)
|
踩过的坑:
1、扩展user表后,要在settings.py 添加
1
|
AUTH_USER_MODEL
=
'appname.扩展user的class name'
|
2、认证后台要在settings添加,尤其记得加逗号,否则报错
认证后台不加的报错
1
|
Django-AttributeError
'User'
object has no attribute
'backend'
|
没加逗号的报错
1
|
ImportError: a doesn't
look
like a module path
|
form.py
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
|
#coding:utf-8
from
django
import
forms
#注册表单
class
RegisterForm(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'
,
'商家'
)))
def
clean(
self
):
if
not
self
.is_valid():
raise
forms.ValidationError(
'所有项都为必填项'
)
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
=
'用户名'
,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
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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
|
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
#注册
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验证用户名密码
if
user
is
not
None
:
#判断用户是否存在
if
user.is_active:
#判断用户是否激活
login(request,user)
#用户信息验证成功后把登陆信息写入session
return
render_to_response(
"member.html"
, {
'username'
:username})
else
:
return
render_to_response(
'disable.html'
,{
'username'
:username})
else
:
return
HttpResponse(
"无效的用户名或者密码!!!"
)
else
:
lf
=
form.LoginForm()
return
render_to_response(
'index.html'
,{
'lf'
:lf})
#退出
def
do_logout(request):
logout(request)
return
HttpResponseRedirect(
'/'
)
|
踩过的坑:
1、登陆的时候用自带的认证模块总是报none
1
|
user = authenticate(username=username, password=password)
|
查看源码发现是check_password的方法是用hash进行校验,之前注册的password写法是
1
|
user.password
=
password
|
这种写法是明文入库,需要更改密码的入库写法
1
|
user.set_password(password)
|