輕量級辦公平台開發實錄(7):用戶認證的實現

輕量級辦公平台開發實錄(7):用戶認證的實現

來自專欄 SandBox7 人贊了文章

1 用戶認證的相關知識

1.1 用戶的登錄認證:

django提供了login()函數,用來實現用戶的登錄:login(request, user, backend=end)

下面的例子用來演示用戶的認證和登錄:

from django.contrib.auth import authenticate, logindef 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 logoutdef logout_view(request): logout(request)

1.3 用戶訪問限制

request.user.is_authenticated:

在系統中我們通常會對某些功能的訪問進行限制,只有通過認證登陸的用戶才可以訪問,限制訪問頁面的原始方法是檢查request.user.is_authenticated, 例如我們可以通過request.user.is_authenticated來判斷用戶是否登陸,如果用戶未登錄則重定向到登陸頁面:

from django.conf import settingsfrom django.shortcuts import redirectdef 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_requireddef 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 LoginRequiredMixinclass 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 renderfrom django.views.generic.base import Viewfrom django.http import HttpResponseRedirectfrom django.contrib.auth import authenticate, login, logoutfrom django.core.urlresolvers import reversefrom django.contrib.auth import get_user_modelfrom django.contrib.auth.backends import ModelBackendfrom 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 formsclass 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.1mysql> UPDATE system_userprofile SET name="管理員", mobile=13813836666 WHERE username="admin";

接下來就可以使用手機號碼:13813836666來登陸系統了。

3.6 用戶登陸登出URL配置

想要通過URL來訪問視圖應用,還需要配置URL路由(sandboxOA/urls.py)

from django.conf.urls import urlfrom django.contrib import adminfrom system.views_user import IndexView, LoginView, LogoutViewurlpatterns = [ 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 servefrom sandboxOA.settings import MEDIA_ROOTurlpatterns = [ ........ 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_requiredclass 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 LoginRequiredMixinclass 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樣式,效果如下:

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

有關知識點內容,參考模代碼中的注釋內容,實現效果:


非常歡迎感興趣的朋友,到我的Github或知乎上做客,閑暇之餘給個贊或Star,贈人玫瑰手留余香^_^

知乎專欄SandBox:zhuanlan.zhihu.com/sand

輕量級辦公管理系統項目開源地址:github.com/RobbieHan/gi (乙方流程版)

輕量級辦公管理系統:github.com/RobbieHan/sa (甲方定製版,本記錄同步項目)

推薦閱讀:

TAG:Django框架 | Python | OA辦公系統 |