1 用户认证的相关知识
1.1 用户的登录认证:
django提供了login()函数,用来实现用户的登录:login(request, user, backend=end)
下面的例子用来演示用户的认证和登录:
from django.contrib.auth import authenticate, login
def my_view(request):
username = request.POST['username']
password = request.POST['password']
user = authenticate(request, username=username, password=password)
if user is not None:
login(request, user)
# 这里可以定义登录成功后返回的页面
else:
# 定义登录失败后返回的错误信息
1.2 用户的登出:
django同样提供了logout()函数用来实现已认证用户的登出操作:
from django.contrib.auth import logout
def logout_view(request):
logout(request)
1.3 用户访问限制
request.user.is_authenticated:
在系统中我们通常会对某些功能的访问进行限制,只有通过认证登陆的用户才可以访问,限制访问页面的原始方法是检查request.user.is_authenticated, 例如我们可以通过request.user.is_authenticated来判断用户是否登陆,如果用户未登录则重定向到登陆页面:
from django.conf import settings
from django.shortcuts import redirect
def my_view(request):
if not request.user.is_authenicated:
return redirect("%s?next=%s" % (settings.LOGIN_URL, request.path))
login_required装饰器
上面的方法比较原始,每一个view都需要做用户登陆判断,django提供一个用来判断用户是否登陆的装饰器:login_required(),对于需要登陆才能访问的view,只需要使用login_required()即可
from django.contrib.auth.decorators import login_required
@login_required
def my_view(request)
...
login_required()完成的事情:
- 如果用户没有登陆,则重定向到settings.LOGIN_URL,并传递当前url绝对路径
- 如果用户已经登入,则正常执行试图
== 注意:==
login_required装饰器带有可选参数: redirect_field_name和login_url, 如果在使用login_required装饰器时没有制定参数,redirect_field_name默认值是"next", login_url默认值会读取settings.LOGIN_URL的值。
LoginRequired mixin:
在接下来的项目中使用的是class-base views, 这时就可以通过LoginRequiredMixin实现与login_required相同的功能,mixin位于继承列表最左侧位置.
from django.contrib.auth.mixins import LoginRequiredMixin
class MyViwe(LoginRequiredMixin, View):
login_url = '/login/'
redirect_field_name = 'redirect_to'
2 v2.0版本说明
项目进行到这里,我们可以切换到v2.0版本了,在项目目录运行git bash,使用v2.0生成dev分支
git checkout -B dev v2.0
v2.0版本对比v1.0版本新增内容说明:
/sandboxOA/ # 项目根目录
|-- apps # app目录,在第6节中创建的
|-- system # 系统权限实现app, 在第6节创建
|-- templates # html模板文件存放目录
|-- system # v2.0新增模板文件夹,存放system app对应模板页
|--users # v2.0新增模板文件夹,用户认证管理模板
|-- login.html # v2.0新增模板,用户登陆页
|-- user-base.html # v2.0新增模板,用户登陆页继承页(css样式)
3 用后认证和访问限制的实现
对于用户登陆认证的需求如下:
- 用户登陆系统才可以访问某些页面
- 如果用户没有登陆而直接访问就会跳转到登陆界面,
- 用户在跳转的登陆界面中完成登陆后,自动访问跳转到之前访问的地址
- 用户可以使用用户名、手机号码或者其他字段作为登陆用户名
在 apps/system目录下创建一个新的viwes: views_user.py (2.0版本已经创建,下面是2.0版本下views_user.py需要导入的模块和函数)。
from django.shortcuts import render
from django.views.generic.base import View
from django.http import HttpResponseRedirect
from django.contrib.auth import authenticate, login, logout
from django.core.urlresolvers import reverse
from django.contrib.auth import get_user_model
from django.contrib.auth.backends import ModelBackend
from django.db.models import Q
from .forms import LoginForm
3.1 index页面视图
系统主页面,用于用户认证和访问限制的临时测试
class IndexView(View):
def get(self, request):
return render(request, 'index.html')
3.2 用户登陆视图
class LoginView(View):
def get(self, request):
if not request.user.is_authenticated():
return render(request, 'system/users/login.html')
else:
return HttpResponseRedirect('/')
def post(self, request):
redirect_to = request.GET.get('next', '/')
login_form = LoginForm(request.POST)
if login_form.is_valid():
user_name = request.POST.get("username", "")
pass_word = request.POST.get("password", "")
user = authenticate(username=user_name, password=pass_word)
if user is not None:
if user.is_active:
login(request, user)
return HttpResponseRedirect(redirect_to)
else:
msg = "用户未激活!"
ret = {"msg": msg, "login_form": login_form}
return render(request, 'system/users/login.html', ret)
else:
msg = "用户名或密码错误!"
ret = {"msg": msg, "login_form": login_form}
return render(request, 'system/users/login.html', ret)
else:
msg = "用户名和密码不能为空!"
ret = {"msg": msg, "login_form": login_form}
return render(request, "system/users/login.html", ret)
在用户登陆视图中使用了Form对象的is_valid()来对数据输入进行有效性验证。Form对象的首要任务就是验证数据,对于绑定的Form实例,可以调用is_valid()方法来执行验证。
apps/system/forms.py内容如下:
from django import forms
class LoginForm(forms.Form):
username = forms.CharField(required=True, error_messages={"requeired": "请填写用户名"})
password = forms.CharField(required=True, error_messages={"requeired": "请填写密码"})
这一节中我们只是使用了Form对象的is_valid()进行数据输入有效性验证,后面章节会介绍更多有关Form验证的使用。
3.3 用户登出视图
class LogoutView(View):
def get(self, request):
logout(request)
return HttpResponseRedirect(reverse("login"))
3.4 创建admin用户
使用createsuperuser命令创建superusers:
python manage.py createsuperuser --username=admin --email=admin@test.com
运行上面命令根据提示输入用户密码和确认密码。
以上命令可以在CMD虚拟环境下创建,可以同使用pycharm的Tools下的Run manage.py Task..来执行,具体使用方法在前面章节已经做过演示。
添加用户后我们就可以使用admin来登陆我们的系统了。
3.5 指定后端认证
通过指定认证后端我们可以使用多个字段作为用户名来登陆系统,下面的代码用来指定了username和mobile字段作为登陆的用户
User = get_user_model() # 引用自定义用户模型(参看第6节内容)
class UserBackend(ModelBackend):
def authenticate(self, request, username=None, password=None, **kwargs):
try:
user = User.objects.get(Q(username=username) | Q(mobile=username))
if user.check_password(password):
return user
except Exception as e:
return None
修改settings.py文件,加入如下内容:
AUTHENTICATION_BACKENDS = (
'users.views_user.UserBackend',
)
为了测试我们指定的后端认证是否可以,需要先补全我们的用户信息,登陆mysql数据库,修改admin用户信息,更新mobile值:
$ mysql -uroot -p1234@abcd.com -h127.0.0.1
mysql> UPDATE system_userprofile SET name="管理员", mobile=13813836666 WHERE username="admin";
接下来就可以使用手机号码:13813836666来登陆系统了。
3.6 用户登陆登出URL配置
想要通过URL来访问视图应用,还需要配置URL路由(sandboxOA/urls.py)
from django.conf.urls import url
from django.contrib import admin
from system.views_user import IndexView, LoginView, LogoutView
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^$', IndexView.as_view(), name='index'),
url(r'^login/$', LoginView.as_view(), name='login'),
url(r'^logout/$', LogoutView.as_view(), name='logout'),
]
3.7 media静态图片访问设置
创建用户认证模型的时候,设置了一个image字段,用来存放用户头像图片,系统默认指定了一个头像图片,model内容如下:
image = models.ImageField(upload_to="image/%Y/%m", default="image/default.jpg", max_length=100, null=True, blank=True)
想要能够成功找到图片文件,并显示图片需要做如下设置:
- 在根目录下创建media目录(v2.0版本已创建)
- 设置settings.py,添加如下内容:
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
- 设置静态文件访问URL(sandboxOA/urls.py)
from django.views.static import serve
from sandboxOA.settings import MEDIA_ROOT
urlpatterns = [
........
url(r'^media/(?P<path>.*)$', serve, {"document_root": MEDIA_ROOT}),
]
3.8 页面访问限制的实现
到这里,我们已经可以进行系统的登陆和登出了,但是未登录状态也是可以访问到我们的IndeView,如何来限制登陆用户才能访问呢?
在使用class-based views时,可以使用LoginRequiredMixin实现与login_required相同的行为:
- 用户登陆系统才可以访问某些页面
- 如果用户没有登陆而直接访问就会跳转到登陆界面,
- 用户在跳转的登陆界面中完成登陆后,自动访问跳转到之前访问的地址
为了实现以上需求,创建apps/system/mixin.py文件,写入如下内容:
from django.contrib.auth.decorators import login_required
class LoginRequiredMixin(object):
@classmethod
def as_view(cls, **init_kwargs):
view = super(LoginRequiredMixin, cls).as_view(**init_kwargs)
return login_required(view)
修改settings.py文件,加入LOGIN_URL
LOGIN_URL = '/login/'
需要登陆后才能访问的视图只需要继承LoginRequiredMixin就可以了,修改后的IndexView视图如下:
from .mixin import LoginRequiredMixin
class IndexView(LoginRequiredMixin, View):
def get(self, request):
return render(request, 'index.html')
注意:LoginRequiredMixin位于继承列表最左侧位置
4 前端页面模板使用说明
4.1 用户登陆页面模板介绍
登陆页面模板templates/system/users/login.html内容如下:
{% extends "system/users/user-base.html" %}
{% block user-content %}
<!-- /.login-logo -->
<div class="login-box-body form-">
<p class="login-box-msg"></p>
<p></p>
<form action="" method="post">
<div class="form-group has-feedback {% if login_form.errors.username %}has-error{% endif %}">
<input name="username" class="form-control" placeholder="用户名或手机号" value="{{ login_form.username.value }}"> <!--type="email"前端控制email输入验证-->
<span class="glyphicon glyphicon-envelope form-control-feedback"></span>
</div>
<div class="form-group has-feedback {% if login_form.errors.password %}has-error{% endif %}">
<input name="password" type="password" class="form-control" placeholder="密码"
value="{{ login_form.password.value }}">
<span class="glyphicon glyphicon-lock form-control-feedback"></span>
</div>
<div class="row">
<div class="col-xs-8">
</div>
<!-- /.col -->
<div class="col-xs-4">
<button type="submit" class="btn btn-primary btn-block btn-flat">登录</button>
</div>
<!-- /.col -->
</div>
{% csrf_token %}
</form>
{% if msg %} <!--判断如果后端返回用户验证错误信息,前端页面输出错误信息-->
<p class="text-red">{{ msg }}</p>
{% endif %}
</div>
<!-- /.login-box-body -->
</div>
<!-- /.login-box -->
{% endblock %}
4.2 Django模板语言知识点说明:
登陆页面
- {% extends "system/users/user-base.html" %} :指定登陆页面继承"system/users/user-base.html"模板内容
- {% block user-content %}{% endblock %}:标签之间的内容会替换到父模板指定位置
- {% if login_form.errors.username %}has-error{% endif %}:通过判断后端返回的login_form验证结果,如果存在错误信息将会添加一个has-error的样式,提示该字段输入有误,前面在用户登陆试图,使用了form验证,定义了username 和password输入不能为空,如果输为空,将会添加has-error样式,效果如下:
![31a1bde366fc063ed5b73e2ed5e7366f.png](https://i-blog.csdnimg.cn/blog_migrate/3018d5010897705e38c3f93d4fcbc596.jpeg)
- {{ login_form.username.value }}:获取login_form中返回的username的值,填充到输入框,用途是在用户登陆失败的时候,输入框中还能够保留我们刚刚输入的内容
登陆后的页头显示
{% if request.user.is_authenticated %} <!-- 判断用户是否登陆,用户登陆了才会显示下面内容 -->
<div class="navbar-custom-menu">
<!-- Navbar Right Menu -->
<ul class="nav navbar-nav">
<!-- Notifications Menu -->
<li class="dropdown notifications-menu">
<!-- Menu toggle button -->
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
<i class="fa fa-bell-o"></i>
<span class="label label-danger"></span>
</a>
<ul class="dropdown-menu">
<li class="header">信息:</li>
<li>
<!-- Inner Menu: contains the notifications -->
<ul class="menu">
<li><!-- start notification -->
<a href="#">
<i class="fa fa-users text-aqua"></i> 消息中心功能暂未开放,敬请期待。 --RobbieHan
</a>
</li>
<!-- end notification -->
</ul>
</li>
<li class="footer"><a href="#">查看全部消息</a></li>
</ul>
</li>
<!-- User Account Menu -->
{% if request.user.is_authenticated %}
<li class="dropdown user user-menu">
<!-- Menu Toggle Button -->
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
<!-- The user image in the navbar-->
<img src="/media/{{ request.user.image }}" class="user-image" alt="用户头像">
<!-- hidden-xs hides the username on small devices so only the image appears. -->
<span class="hidden-xs">{{ request.user.name }}</span>
<!--上面直接写成request.user 也会返回用户姓名,这是因为在system.models.UserProfile.Meta中定义该值-->
</a>
<ul class="dropdown-menu">
<!-- The user image in the menu -->
<li class="user-header">
<img src="/media/{{ request.user.image }}" class="img-circle" alt="User Image"> <!-- 用户头像地址的访问模式 -->
<p>
{{ request.user.name }} - {{ request.user.department.title }} <!-- 2.0版本中写错了,这里应该改成request.user.department.name -->
<small>{{ request.user.email }}</small>
</p>
</li>
{% endif %}
<!-- Menu Footer-->
<li class="user-footer">
<div class="pull-left">
<a href="" class="btn btn-default btn-flat">个人中心</a>
</div>
<div class="pull-right">
<a href="/logout/" class="btn btn-default btn-flat">注销用户</a>
</div>
</li>
</ul>
</li>
<!-- Control Sidebar Toggle Button -->
<li>
<a href="#" data-toggle="control-sidebar"><i class="fa fa-gears"></i></a>
</li>
</ul>
</div>
{% endif %}
有关知识点内容,参考模代码中的注释内容,实现效果:
![d40eb549f8ee85c2b95dddd39967f673.png](https://i-blog.csdnimg.cn/blog_migrate/67a1dcc9e07cc7797e4d5413a654c82c.jpeg)
知乎专栏:Django 学习小组
知乎专栏:SandBox
更多实战文档可关注知识星球:https://t.zsxq.com/a6IqBMr(微信中打开链接)
轻量级办公管理系统项目开源地址:RobbieHan/gistandard