这个系列好久没有更新了,主要是因为之前没有找到下一步开发的方向。这几天又看了看django的文档,决定研究下django的权限和用户系统,并采用django的用户系统重写之前的users app,将以前涉及到users的功能全部替换为django的User类。
这篇博文主要介绍以下内容:1. django的auth.User的基本功能使用;2. 将原本注册在users类的user数据迁移到auth.User(以下简称为User)中,确保数据兼容性。
首先来看User类的基本功能。django中的User模块为我们提供了包括注册用户、用户登录、验证用户等等功能,我们可以使用这些功能来改写我们现有的users app的相关方法。此外,由于User提供的字段有限,我们还需建立一个新的model:UserProfile来存储User的扩展信息,并将其与User建立一对一关系。这样,我们就将原本的users类拆分为User+UserProfile两个类。
我们在users/models.py实现UserProfile:
# users/models.py
# ...
# from django.contrib.auth.models import User
# ...
class UserProfile(models.Model):
user = models.OneToOneField(User,on_delete=models.CASCADE)
logoimage = models.ImageField(upload_to='logoimages', null=True, blank=True, verbose_name=u'头像')
# new field
birthday = models.DateTimeField(null=True, blank=True, verbose_name=u'生日')
email = models.CharField(max_length=255, null=True, blank=True, verbose_name=u'电子邮件')
mobilephone = models.CharField(max_length=11, null=True, blank=True, verbose_name=u'手机号码')
# new field end
registertime = models.DateTimeField(default=timezone.now())
该类包含user这个指向User的一对一关系,以及原本在users中的用户信息字段。
有了新的UserProfile类,我们就要对userregister做一些修改,以便我们使用新的User+UserProfile来建立用户:
# users/views.py
from django.db.models.signals import post_save
from django.dispatch import receiver
# ...
@receiver(post_save,sender=User)
def createProfile(sender,created,instance,**kwargs):
if created:
profile = UserProfile(user=instance)
profile.save()
def userregister(request):
if request.method == 'POST':
form = UserRegisterForm(request.POST,request.FILES)
if form.is_valid():
username = form.cleaned_data['username']
password = form.cleaned_data['password']
logoimage = form.cleaned_data['logoimage']
birthday = form.cleaned_data['birthday']
email = form.cleaned_data['email']
mobile = form.cleaned_data['mobilephone']
user = User.objects.create_user(username,'',password)
user.userprofile.logoimage = logoimage
user.userprofile.birthday = birthday
user.userprofile.mobilephone = mobile
user.userprofile.email = email
user.userprofile.save()
# form.save()
result_info = 'success'
return HttpResponseRedirect(reverse('users:registerResult', kwargs={'info': result_info}))
else:
form = UserRegisterForm()
return render(request,'users/userregister.html',{'form':form})
# ...
这里我们抛弃了原本使用UserRegisterForm类,而是使用User提供的create_user方法来注册一个用户。create_user可以传入三个参数:username, email和password。在这里我们先填入username和password,而将email放入我们新建的UserProfile里。
由于UserProfile与User是一对一关系,所以当建立User时我们同时需要建立该user的userprofile。在这里我们使用django的信号机制。在django中,主要的内建信号包含以下四种:模型信号、管理信号、请求/响应信号以及测试信号。这里我们使用模型信号post_save来建立UserProfile。当任一个model发生了save行为后,都会发射post_save信号,由于create_user函数相当于一个save行为,因此我们要做的就是写一个函数来监听User发送的post_save信号,当检测到User的post_save后,就建立其对应的UserProfile。
我们建立一个createProfile(sender, created, instance,**kwargs)来监听User的post_save信号。这里使用receiver修饰器来指定监听的信号以及发送者sender。created用于标识是否是第一次建立User,如果是第一次建立的话,则建立对应的UserProfile;instance为发送者的实例,也即刚刚建立的User。
在建立好UserProfile后,我们使用表单中传来的值为刚才的UserProfile中各字段赋值、存储。
我们再来修改userlogin和logoff方法:
# users/views.py
from django.contrib.auth import authenticate,login,logout
# ...
def userlogin(request):
if request.method == 'POST':
form = UserLoginForm(request.POST)
if form.is_valid():
username = form.cleaned_data['username']
password = form.cleaned_data['password']
result_info = ''
try:
user = authenticate(username=username, password=password)
if user is not None:
login(request, user)
result_info = 'success'
else:
result_info = 'fail'
except Exception as e:
result_info = 'fail'
return HttpResponseRedirect(reverse('users:loginResult', kwargs={'info': result_info}))
else:
form = UserLoginForm()
return render(request, 'users/userlogin.html',{'form':form})
# ...
def logoff(request):
logout(request)
return HttpResponseRedirect(reverse('index'))
先来看userlogin函数的修改。我们使用User的authenticate方法来根据username和password来验证user是否存在。若user存在,我们使用login方法实现登录。原本的登录方法是我自己研究的,在登录后将当前的username存在session的currentuser中,现在使用了login方法登录,我们可以在之后使用request.user拿到当前登录的User对象。
logoff函数的修改也很简单,直接调用logout方法登出user就好。
下面我们要编写用户的迁移功能,即将原来users中的user迁移到User中,这样在其他功能中就不用再考虑两种User的兼容性。
# users/views.py
from django.contrib.auth.decorators import login_required,user_passes_test
# ...
@login_required
@user_passes_test(checksuperuser)
def migrateuser(request):
oldusers = Users.objects.all()
for olduser in oldusers:
try:
newuser = User.objects.create_user(olduser.username,'',olduser.password)
newuser.userprofile.logoimage = olduser.logoimage
newuser.userprofile.birthday = olduser.birthday
newuser.userprofile.mobilephone = olduser.mobilephone
newuser.userprofile.email = olduser.email
newuser.userprofile.save()
# Users.delete(username=olduser.username)
result_info = 'success'
except ValidationError:
newuser = User.objects.get_by_natural_key(olduser.username)
newuser.set_password(olduser.password)
except Exception as e:
result_info = e
return render(request,'users/migrateuser.html',{'result_info':result_info})
这个用户迁移功能其实很简单:遍历原本users类的所有对象,把这些对象的相关字段用UserProfile再重新存储一次即可。至于异常的处理是因为我在测试时用新的注册功能注册了原本users的用户名,因此只需把原来的密码用set_password函数改过来即可(该函数也是auth提供的)。
显然我们不应该允许任意的用户都可以使用这个功能,所以我们使用两个装饰器来对这个功能做一些限制:@login_required和@user_pass_test。@login_required顾名思义,只有登录后才能访问;而@user_pass_test支持传入一个函数,执行的检查由这个函数来决定。我们使用checksuperuser函数来检查当前登录用户是否为超级用户:
# users/views.py
# ...
def checksuperuser(user):
return user.is_superuser
若是超级用户,则允许他使用这个功能。超级用户的建立可以使用以下命令在myblogs目录下执行:
python manage.py createsuperuser --username=<username> --email=<email>
接下来我们修改剩下的userIndex和userinfo这两个小函数,使用request.user来替换掉原来获取user的方式:
# users/views.py
# ...
def userIndex(request,username):
try:
# user = Users.objects.get(username=username)
currentUser = request.user
blogList = Blog.objects.filter(auther=currentUser).filter(draft=False)
content = {'username':username,
'currentUser':currentUser,
'blogList':blogList
}
except Exception as e:
content = {'username':e}
return render(request,'users/userindex.html',content)
def userinfo(request,username):
try:
# user = Users.objects.get(username=username)
currentUser = request.user
birthday = currentUser.userprofile.birthday
email = currentUser.userprofile.email
registertime = currentUser.userprofile.registertime
content = {'username':username,
'registertime':registertime,
'birthday':birthday,
'email':email,
'currentUser':currentUser
}
except Exception as e:
return render(request, 'users/pleaselogin.html')
return render(request,'users/userinfo.html',content)
注意这里blogList的部分可以先不替换,因为目前我们的改动还没有涉及Blog app,blogs的auther外键仍然是旧的users类,所以这里要是改动的话一定会报错。
最后,我们向users/urls.py加入migrateuser的url,以及修改userTemplate,使迁移用户功能能被超级用户使用:
# users/urls.py
# ...
urlpatterns = [
# ...
url(r'^migrateuser/$',views.migrateuser,name='migrateuser')
]
# ...
<!-- userTemplate -->
<!-- ...... -->
{% if currentUser.is_superuser %}
<span><a href="{% url 'users:migrateuser' %}">迁移用户</a></span>
{% endif %}
<!-- ...... -->
<!-- migrateuser.html -->
{% extends "userTemplate.html" %}
{% block content %}
<div>
{% if "{{ result_info }}" == "success" %}
<h3>迁移用户成功,所有旧用户已迁移到新模型!</h3>
{% else %}
<h3>{{ result_info }}</h3>
{% endif %}
</div>
{% endblock %}
当以超级用户登录时,就可看到迁移用户的功能了:
在这篇博文中,我们实现了将旧users的数据迁移到新User+UserProfile的功能,并且对users app的所有功能做了改动以应用新的User实现。在下一篇博文中,我们将把新User的实现应用于Blogs app中,并同样修改Blogs以及index中涉及到user的所有功能。在这里会有一个很大的坑,敬请期待~