只在用户登陆时运行_轻量级办公平台开发实录(7):用户认证的实现

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


- {{ 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

知乎专栏:Django 学习小组
知乎专栏:SandBox
更多实战文档可关注知识星球:https://t.zsxq.com/a6IqBMr(微信中打开链接)
轻量级办公管理系统项目开源地址:RobbieHan/gistandard
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
系统中所演示的数据均系杜撰,并非真实数据,包括单位名称、人名、地名和通信方式。 sandbox是一个基于django框架开发轻量级办公平台,主要模块有:权限控制、资产(库存)管理、设备管理、客户信息管理和工单流程管理,其目的在于建立一套规范化、统一化和清晰化的标准服务流程,能够清晰的处理、记录和追踪服务流程,同依赖于工单流程模块,建立技术服务文档共享库,让技术人员相互协作,分享经验,提升服务技术水平。 这套系统的开发主要是为了规范当前公司服务流程,改变服务混乱,项目多人多次服务无交接的问题,同将公司资产(库存)和销售的设备一起做了集中管理和服务跟踪。 目前公司主要是对外销售安全产品和安全服务,作为一个小型乙方公司,上一套OA系统太过繁重,所以就自己动手来做了这么一套轻量级办公系统。 线上环境部署 系统运行环境 centos7 python3.6 mysql 5.6, 系统需要安装 docker , docker-compose 1、下载项目文件到你的系统 $ git clone https://github.com/RobbieHan/gistandard.git 2、进入项目目录,拷贝和修改配置文件 $ cd gistandard $ mkdir -p /sandbox/nginx $ cp config/nginx.conf /sandbox/nginx/ $ cp -r media /sandbox $ vim /sandbox/nginx/nginx.conf # 修改nginx配置文件中 server标签下的 server_name 为你系统的地址IP 或域名(sandbox安装完成通过这个地址来访问) 3、运行容器 $ docker-compose up -d 等等等等..... 查看容器运行状态: $ docker-compose ps Name Command State Ports -------------------------------------------------------------------------------- gistandard_mysql_1 docker-entrypoint.sh mysql ... Up 3306/tcp gistandard_web_1 bash -c uwsgi config/sandb ... Up 0.0.0.0:80->80/tcp 4、导入数据库文件 $ docker cp db_tools/data/basic_data.sql gistandard_mysql_1:/tmp $ docker-compose exec mysql bash $ mysql -uddadmin -p1234@abcd.com gistandard < /tmp/basic_data.sql 5、访问系统: http://your_ip 初始用户 admin 密码 !qaz@wsx 本地环境运行 如果只是想查看代码和系统功能,可以在本地windows环境下临运行项目,或者使用开发工具运行项目 1、从github上下载项目文件到本地,或者使用git克隆项目 2、在windows上安装python3.6环境(也可以使用python虚拟环境,python环境的安装方法网上很多,这里不再写了) 3、修改gistandard/gistandard/settings.py文件中数据库连接配置DATABASES, 本地运行使用db.sqlite3数据库,内含测试数据 DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), } } 4、安装依赖包,打开CMD命令行,进入项目主目录运行下面命令安装依赖包: 注意: 本地环境使用的是sqlite3数据库,安装依赖包前可以先将requirements文件中的mysqlclient那一行删掉,这个包是用来连接mysql数据库使用的,通常windows下安装这个包会报错,解决办法我在知乎境部署的文档中都有写过。 我windows下使用的是python虚拟环境,所以我下面CMD命令行开头是(gistandard)这个是我虚拟环境的名称,虚拟环境的使用方法也可以网上找下,或者关注我的知乎专栏,环境部署
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值