自定義用戶認證
目的:實現Django自定義的認證系統,在生產環境都是根據此代碼進行定制的
步驟:
1.在settings文件中配置要使用的類
#命名規則 app名稱.類名
AUTH_USER_MODEL = 'crm.UserProfile'
2.在crm app下的models文件中加入Django官方的用戶認證
from django.contrib.auth.models import ( BaseUserManager, AbstractBaseUser,PermissionsMixin ) from django.utils.safestring import mark_safe from django.utils.translation import ugettext_lazy as _
class UserProfileManager(BaseUserManager): def create_user(self, email, name, password=None): #創建用戶根據UserProfile中的字段,輸入 if not email: raise ValueError('Users must have an email address') user = self.model( email=self.normalize_email(email), name=name, ) user.set_password(password) user.save(using=self._db) return user def create_superuser(self, email, name, password): #創建超級用戶根據UserProfile中的字段,輸入 user = self.create_user( email, password=password, name=name, ) user.is_admin = True user.save(using=self._db) return user class UserProfile(AbstractBaseUser,PermissionsMixin):
#使用Django自帶的登錄系統,可以自定義一些字段,例如郵箱,密碼,用戶名 email = models.EmailField( verbose_name='email address', max_length=255, unique=True, ) password = models.CharField(_('password'), max_length=128,help_text = mark_safe('<a href="password/">修改密碼</a>')) name = models.CharField(max_length=32) is_active = models.BooleanField(default=True) is_admin = models.BooleanField(default=False)
#在創建用戶的時候調用該方法進行用戶的創建 objects = UserProfileManager() USERNAME_FIELD = 'email' REQUIRED_FIELDS = ['name'] 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 def __str__(self): # __unicode__ on Python 2 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: active users are staff return self.is_active class Meta: verbose_name_plural = '用戶'
3.在crm app下的admin中
from django import forms from django.contrib.auth.models import Group from django.contrib.auth.admin import UserAdmin as BaseUserAdmin from django.contrib.auth.forms import ReadOnlyPasswordHashField from crm.models import UserProfile
class UserCreationForm(forms.ModelForm):
#在Django Admin頁面中用戶創建的表單展示 """A form for creating new users. Includes all the required fields, plus a repeated password.""" password1 = forms.CharField(label='Password', widget=forms.PasswordInput) password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput) class Meta:
#展示的model對象和字段 model = UserProfile fields = ('email', 'name') 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):
#在Django Admin頁面中用戶修改的表單展示 """A form for updating users. Includes all the fields on the user, but replaces the password field with admin's password hash display field. """ password = ReadOnlyPasswordHashField() class Meta:
#展示的model對象和字段 model = UserProfile fields = ('email', 'password', 'name', 'is_active', 'is_admin') def clean_password(self): # Regardless of what the user provides, return the initial value. # This is done here, rather than on the field, because the # field does not have access to the initial value return self.initial["password"] class UserProfileAdmin(BaseUserAdmin):
#在在Django Admin頁面中配置的admin_class # The forms to add and change user instances form = UserChangeForm #調用修改用戶的表單 add_form = UserCreationForm #調用創建用戶的表單 # The fields to be used in displaying the User model. # These override the definitions on the base UserAdmin # that reference specific fields on auth.User. list_display = ('email', 'name', 'is_admin') list_filter = ('is_admin',) fieldsets = ( (None, {'fields': ('email', 'password')}), ('Personal info', {'fields': ('name',)}), ('Permissions', {'fields': ('is_admin','is_active','user_permissions',)}), ) # add_fieldsets is not a standard ModelAdmin attribute. UserAdmin # overrides get_fieldsets to use this attribute when creating a user. add_fieldsets = ( (None, { 'classes': ('wide',), 'fields': ('email', 'name', 'password1', 'password2')} ), ) search_fields = ('email',) ordering = ('email',) filter_horizontal = ('groups','user_permissions') admin.site.unregister(Group)
#把models對象中的model對象和admin_class對象組合起來 admin.site.register(models.UserProfile,UserProfileAdmin)
4.在king_admin中實現修改密碼的功能
在king_admin的urls.py中配置
url(r'^(\w+)/(\w+)/(\d+)/change/password/$',views.password_reset,name='password_reset'),
在king_admin中的king_admin.py中配置
class UserAdmin(BaseAdmin): list_display = ['email','name'] #首頁展示的字段 readonly_fields = ['password',] #只讀字段 modelfrom_exclude_fields = ['last_login','is_superuser','groups','user_permissions'] #不展示的字段
在views函數中開發該模塊
def password_reset(request,app_name,table_name,obj_id): '''動態修改密碼'''
#獲取admin_class類和要修改密碼的對象 admin_class = king_admin.enabled_admins[app_name][table_name] model_obj = admin_class.model.objects.get(id=obj_id) errors = {} if request.method == 'POST':
#獲取前端頁面的兩個值,密碼和新密碼 _password1 = request.POST.get('password1') _password2 = request.POST.get('password2')
#如果兩次密碼相同,並且長度大於5位,則調用父類的方法保存密碼,同時入庫,最后返回到展示頁面 if _password1 == _password2: if len(_password2) > 5: model_obj.set_password(_password1) model_obj.save()
#保存成功則跳轉到展示頁面進行展示 return redirect(request.path.rstrip('password/')) else: errors['invalid_password'] = '密碼長度不足6位' else: errors['invalid_password'] = '兩次密碼不一致' return render(request,'king_admin/password_reset.html',{'model_obj':model_obj})
5.在forms.py中將不需要展示的字段寫到exclude上
class Meta: model = admin_class.model fields = '__all__' exclude = admin_class.modelfrom_exclude_fields #排除的字段
6.前端頁面
本質上是一個form表達,展示用戶的用戶名,然后用戶填寫密碼和新密碼之后提交到views的方法中進行修改密碼的操作
{% extends 'king_admin/table_index.html' %} {% block container %} <div class="row"> <div class="panel panel-info"> <div class="panel-heading"> <h3 class="panel-title">重置用戶{{ model_obj.name }}密碼</h3> </div> <div class="panel-body"> <form method="post" class="form-horizontal"> {% csrf_token %} <div class="form-group"> <label class="col-sm-1" style="font-weight:normal"> 用戶名: </label> <div class="col-sm-3"> <input class="form-control" type="text" value="{{ model_obj.email }}" disabled> </div> </div> <div class="form-group"> <label class="col-sm-1" style="font-weight:normal"> 密碼: </label> <div class="col-sm-3"> <input class="form-control" type="password" name="password1"> </div> </div> <div class="form-group"> <label class="col-sm-1" style="font-weight:normal"> 重復密碼: </label> <div class="col-sm-3"> <input class="form-control" type="password" name="password2"> </div> </div> <div> <ul style="color:red"> {% for k,v in errors.items %} <li>{{ k }}-{{ v }}</li> {% endfor %} </ul> </div> <input type="submit" class="btn btn-info" style="margin-left:110px" value="提交"> <input type="reset" class="btn btn-danger" style="margin-left:30px" value="重置"> </form> </div> </div> </div> {% endblock %}
自定義用戶登錄
目的:利用Django提供的組件,實現自己的用戶認證系統,包括登錄,登出和利用裝飾器實現方法的登錄校驗
1.在settings文件中配置登錄url的路徑
LOGIN_URL = '/'
2.在入口的app中配置url
3.在views中開發登錄,登出和首頁的模塊
from django.shortcuts import render,redirect from django.contrib.auth import login,authenticate,logout # Create your views here. def account_login(request): errors = {} if request.method == 'POST':
#獲取前端表單的值 _email = request.POST.get('email') _password = request.POST.get('password') #使用Django自帶的用戶認證 user = authenticate(username=_email,password=_password) if user:
#登錄成功則進行跳轉,如果有next_url則跳轉到下一個頁面,否則跳轉到首頁 login(request,user) next_url = request.GET.get('next','') if next_url: return redirect(next_url) else: return redirect('/index/') else: errors['error'] = '用戶名密碼不正確' return render(request,'login.html',{'errors':errors}) def account_logout(request):
#用戶登出 logout(request) return redirect('/account/login/') def index(request): return render(request,'index.html')
4.在需要登錄校驗的方法上,加上@login_required裝飾器
from django.contrib.auth.decorators import login_required @login_required def index(request): return render(request, 'king_admin/table_index.html',{'table_list':king_admin.enabled_admins})
5.前端頁面,form表單以post的方式向后台發送用戶名和密碼,后端的views中相應的方法進行校驗
{% extends 'base.html' %} {% block body %} <div class="row"> <div class="panel panel-info"> <div class="panel-heading"> <h3 class="panel-title">登錄CRM系統</h3> </div> <div class="panel-body "> <form class="form-horizontal" method="post">{% csrf_token %} <div class="form-group"> <label class="col-sm-1" style="font-weight:normal"> 郵箱: </label> <div class="col-sm-3"> <input type="email" name="email" id="inputEmail" class="form-control" placeholder="Email address" required autofocus> </div> </div> <div class="form-group"> <label class="col-sm-1" style="font-weight:normal"> 密碼: </label> <div class="col-sm-3"> <input type="password" name="password" id="inputPassword" class="form-control" placeholder="Password" required> </div> </div> {% if errors %} <span style="color: red">{{ errors.error }}</span> {% endif %} <button class="btn btn-info" style="margin-left:113px" type="submit">登陸</button> <button class="btn btn-danger" type="reset">清空</button> </form> </div> </div> </div> {% endblock %}
6.在index首頁集成用戶登出同時生成動態的菜單鏈接,點擊跳轉到相應的頁面
登出
<li class="dropdown"> <a href="#" class="dropdown-toggle" data-toggle="dropdown" aria-expanded="false">{{ request.user.name }}</a> <ul class="dropdown-menu" role="menu"> <li><a href="{% url 'account_logout' %}">注銷</a></li> </ul> </li>
動態菜單生成
<div class="container-fluid"> <div class="row"> <div class="col-sm-3 col-md-2 sidebar"> <ul class="nav nav-sidebar"> {% for role in request.user.roles.all %} {% for menu in role.menus.all %} <li>
{# 如果是絕對路徑的url type=1,直接顯示url的名稱,如果是相對路徑的url type=0,則動態根據url的別名來獲取url #} <a href="{% if menu.url_type == 0 %}{% url menu.url_name %}{% else %}{{ menu.url_name }}{% endif %}">{{ menu.name }}</a> </li> {% endfor %} {% endfor %} </ul> </div> <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main"> {% block page-content %} {% endblock %} </div> </div> </div>