1 权限管理模型设计
1.1 创建app
新建一个app, 名称叫system包含用户管理、菜单管理和权限管理等系统基础模块。
- 使用pycharm打开我们的项目,右键项目根目录,选择 New → Python Package, 在弹出的窗口输入apps,这个包就用来存放项目中创建的所有app.
- 选择pycharm上方Tools,点击Run manage.py Task..., 这时在pycharm下方会打开一个窗口,输入startapp system 回车创建app, 如下图:
- 将刚刚创建的system 移动到 apps下
- 为了能够顺利访问到我们新建的app,右键apps,选择Mark Directory as → Sources root
- 修改sandboxMP/sandboxMP/settings.py 加入如下内容:
import sys
sys.path.insert(0, os.path.join(BASE_DIR, 'apps'))
1.2 创建权限认证模型
sandboxMP项目使用的是自定义权限认证模型,模型说明:
Menu: 菜单管理,用来存储系统可用的URL
Role: 角色组,通过外键关联Menu,角色组中的用户将继承Role关联菜单的访问权限
Structure:组织架构,包含单位和部门信息
UserProfile: 自定义用户认证模型,替换系统原有的User模型
下面内容就是权限认证的模型详细内容,将如下内容复制到apps/system/models.py
from django.db import models
from django.contrib.auth.models import AbstractUser
class Menu(models.Model):
"""
菜单
"""
name = models.CharField(max_length=30, unique=True, verbose_name="菜单名") # unique=True, 这个字段在表中必须有唯一值.
parent = models.ForeignKey("self", null=True, blank=True, on_delete=models.SET_NULL, verbose_name="父菜单")
icon = models.CharField(max_length=50, null=True, blank=True, verbose_name="图标")
code = models.CharField(max_length=50, null=True, blank=True, verbose_name="编码")
url = models.CharField(max_length=128, unique=True, null=True, blank=True)
def __str__(self):
return self.name
class Meta:
verbose_name = '菜单'
verbose_name_plural = verbose_name
@classmethod
def get_menu_by_request_url(cls, url):
return dict(menu=Menu.objects.get(url=url))
class Role(models.Model):
"""
角色:用于权限绑定
"""
name = models.CharField(max_length=32, unique=True, verbose_name="角色")
permissions = models.ManyToManyField("menu", blank=True, verbose_name="URL授权")
desc = models.CharField(max_length=50, blank=True, null=True, verbose_name="描述")
class Structure(models.Model):
"""
组织架构
"""
type_choices = (("unit", "单位"), ("department", "部门"))
name = models.CharField(max_length=60, verbose_name="名称")
type = models.CharField(max_length=20, choices=type_choices, default="department", verbose_name="类型")
parent = models.ForeignKey("self", null=True, blank=True, on_delete=models.SET_NULL, verbose_name="父类架构")
class Meta:
verbose_name = "组织架构"
verbose_name_plural = verbose_name
def __str__(self):
return self.name
class UserProfile(AbstractUser):
name = models.CharField(max_length=20, default="", verbose_name="姓名")
birthday = models.DateField(null=True, blank=True, verbose_name="出生日期")
gender = models.CharField(max_length=10, choices=(("male", "男"), ("female", "女")),
default="male", verbose_name="性别")
mobile = models.CharField(max_length=11, default="", verbose_name="手机号码")
email = models.EmailField(max_length=50, verbose_name="邮箱")
image = models.ImageField(upload_to="image/%Y/%m", default="image/default.jpg",
max_length=100, null=True, blank=True)
department = models.ForeignKey("Structure", null=True, blank=True, on_delete=models.SET_NULL, verbose_name="部门")
post = models.CharField(max_length=50, null=True, blank=True, verbose_name="职位")
superior = models.ForeignKey("self", null=True, blank=True, on_delete=models.SET_NULL, verbose_name="上级主管")
roles = models.ManyToManyField("role", verbose_name="角色", blank=True)
class Meta:
verbose_name = "用户信息"
verbose_name_plural = verbose_name
ordering = ['id']
def __str__(self):
return self.name
1.3 使用模型
定义好模型后,还要告诉Django使用这些模型,我们需要修改settings.py文件,在INSTALLED_APPS中添加models.py所在应用的名称:
INSTALLED_APPS = [
...原内容省略...
'system',
]
想要使用自定义的认证模型UserProfile, 还需要在setting.py中添加下面内容:
AUTH_USER_MODEL = 'system.UserProfile'
注意:
在定义用户模型的时候使用到了ImageField字段类型,在执行makemigrations前需要安装依赖包:pillow,打开CMD窗口,进入本项目的python虚拟环境,然后安装pillow:
C:\Users\RobbieHan>workon sandboxMP
(sandboxMP) C:\Users\RobbieHan>pip install pillow
也可以在pycharm 的Terminal终端窗口执行安装命令: pip install pillow
最后执行makemigrations 和 migrate来生成数据表, 使用pycharm Tools,点击Run manage.py Task..., 在manage.py窗口输入下面命令:
makemigrations
migrate
1.4 模型(Models)相关知识点
字段类型:
在权限认证模型中使用到的字段类型如下:
CharField: 用来存储字符串,必须制定一个参数 max_length用来限定字段最大长度
Foreignkey: 是一个关联字段,创建多表之间的多对一关系,如果创建同表之间的递归关联关系,可以使用models.ForeignKey('self')
ManyToManyField: 用来实现多对多的关联关系
DateField: 日期时间字段
EmailField: email字段,用来检查email地址是否合法
ImageField: 图片字段,用来定义图片上传和图片检查,需要安装pillow库
字段选项:
unique: 设置为True, 则表示这个字段必须有唯一值,这是从数据库级别来强制数据唯一,后面我们还会介绍通过form验证来确保数据输入的唯一
verbose_name:
blank: 默认值是False, 设置为True,则该字段允许为空
null: 默认值是False,如果为True,Django会在数据库中将空值转存为NULL
choices: 是一个可迭代结构(元祖),每个元组中的第一个元素,是存储在数据库中的值;第二个元素是使人容易理解的描述。
on_delete :在django2.0版本以前,定关联字段时,on_delete选项并不是必须的,而在django2.0版本后,在定义关联字段时on_delete是必须要定义的,常用参数如下:
on_delete=models.CASCADE, # 删除关联数据,与之关联也删除
on_delete=models.DO_NOTHING, # 删除关联数据,什么也不做
on_delete=models.PROTECT, # 删除关联数据,引发错误ProtectedError
on_delete=models.SET_NULL, # 删除关联数据,与之关联的值设置为null
on_delete=models.SET_DEFAULT, # 删除关联数据,与之关联的值设置为默认值
需要注意的是在使用SET_NULL的时候,该字段在模型定义的时候需要设置可为空,例如:
user = models.ForeignKey(User, on_delete=models.SET_NULL, blank=True, null=True)
同样在使用SET_DEFAULT的时候,需要预先定义default:
user = models.ForeignKey(User, on_delete=models.SET_DEFAULT, default='默认值')
更多字段类型和字段选项请参考:
https://docs.djangoproject.com/en/1.11/ref/models/fields/#model-field-types
2 用户认证和访问限制
用户登录认证的需求如下:
- 用户登陆系统才可以访问某些页面,
- 如果用户没有登陆而直接访问就会跳转到登陆界面,
- 用户在跳转的登陆界面中完成登陆后,自动访问跳转到之前访问的地址,
- 用户可以使用用户名、手机号码或者其他字段作为登陆用户名。
在pycharm中,选中sandboxMP/apps/system,右键,选择 New → Python File, 在弹出的窗口输入名称:views_user,在刚创建的页面中导入需要的模块:
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.urls import reverse
说明: 以下创建的视图,都是写在sandboxMP/apps/system/views_user.py文件中
2.1 创建index页面视图
index页面视图,是本项目创建的第一个视图:
class IndexView(View):
def get(self, request):
return render(request, 'index.html')
知识点介绍:
1、视图: Django官方文档对“视图”的介绍是用来封装处理用户请求和返回响应的逻辑。
我们可以定义视图函数,用来接受Web请求并返回Web响应,也可以使用基于类的视图对象,本项目的视图实现都是基于类创建的视图,和基于函数的视图相比据有一定的区别和优势:
- 可以通过单独的方法编写与HTTP方法相关的代码(GET, POST等),无需通过条件分支来判断HTTP方法
- 可将代码分解成可重用的组件,例如Mixin(多继承),发挥面向对象技术优势,使用更加灵活,易于扩展
2、render函数: Django的快捷函数,结合给定的模板和一个给定的上下文字典,并返回一个渲染后的HttpRespose对象,语法:render(request, template_name, context=None, content_type=None, status=None, using=None),其中 request 和template_name必须参数,其它为可选参数。
2.2 创建用户登陆视图
在创建用户登陆视图前,先创建一个sandboxMP/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": "请填写密码"})
创建用户登陆视图:
from .forms import LoginForm
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)
ret = dict(login_form=login_form)
if login_form.is_valid():
user_name = request.POST['username']
pass_word = request.POST['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:
ret['msg'] = '用户未激活!'
else:
ret['msg'] = '用户名或密码错误!'
else:
ret['msg'] = '用户和密码不能为空!'
return render(request, 'system/users/login.html', ret)
知识点介绍:
Django使用会话和中间件来拦截认证系统中的请求对象。它们在每一个请求上提供一个request.user属性,表示当前的用户。如果当前的用户没有登入,该属性将设置成AnonymousUser的一个实例,否则将会是User实例。
1、request.user.is_authenticated: 用来判断用户是否登入,如LoginView中:
# 当用户访问登陆页面时,判断用户如果未登入则访问登陆页面,如果登入则跳转到首页
if not request.user.is_authenticated:
return render(request, 'system/users/login.html')
else:
return HttpResponseRedirect('/')
2、is_valid(): Forms实例的一个方法,用来做字段验证,当输入字段值合法时,它将返回True,同时将表单的数据存放到cleaned_data属性中。
3、authenticate(request=None, **credentials): 用来认证用户,credentials为关键字参数,默认为username和password,如果通过认证后端检查,则返回一个User对象。
4、login(request, user, backend=None): 用来从视图中登陆一个用户,同时将用户的ID保存在session表中。注意:在调用login()之前必须使用authenticate()成功认证这个用户。
5、HttpResponseRedirect[source]: 用来重定向访问,参数是重定向的地址,可以是完整的URL,也可以相想读与项目的绝对路径。
2.3 创建用户登出视图
class LogoutView(View):
def get(self, request):
logout(request)
return HttpResponseRedirect(reverse('login'))
知识点介绍:
1、logout(request): 登出用户。
2、reverse(viewname): 根据url name来进行url的反向解析。
2.4 配置用户URL路由
想要通过URL来访问视图应用,还需要配置URL路由,修改sandboxMP/sandboxMP/urls.py:
from django.contrib import admin
from django.urls import path
from system.views_user import IndexView, LoginView, LogoutView
urlpatterns = [
path('admin/', admin.site.urls),
path('', IndexView.as_view(), name='index'),
path('login/', LoginView.as_view(), name='login'),
path('logout/', LogoutView.as_view(), name='logout'),
]
2.5 创建认证用户
在pycharm选择 Tools,点击Run manage.py Task..., 在打开的窗口中输入createsuperuser,根据提示输入用户名,邮箱和密码,操作过程如下:
manage.py@sandboxMP > createsuperuser
"C:\Program Files\JetBrains\PyCharm2017.3.2\bin\runnerw.exe" C:\Users\RobbieHan\Envs\sandboxMP\Scripts\python.exe "C:\Program Files\JetBrains\PyCharm2017.3.2\helpers\pycharm\django_manage.py" createsuperuser D:/ProjectFile/sandboxMP
用户名: admin
邮箱: robbie_han@outlook.com
Warning: Password input may be echoed.
Password: !qaz@wsx
Warning: Password input may be echoed.
Password (again): !qaz@wsx
Superuser created successfully.
运行项目,访问系统:http://127.0.0.1:8000,我们并没有登入用户,直接可以访问首页,这和我们的要求不符。接下来实现页面访问限制,要求必须登入用户才能访问。
2.6 页面访问限制
页面访问限制的实现需求:
- 用户登录系统才可以访问某些页面
- 如果用户没有登陆而直接访问就会跳转到登陆界面
- 用户在跳转的登陆页面完成登陆后,自动访问跳转前的访问地址
新建sandboxMP/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)
修改sandboxMP/sandboxMP/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位于继承列表最左侧位置
重启项目,我们再次访问首页,打开浏览器,输入http://127.0.0.1:8000,这时我们会发现,浏览器中的URL会变成:http://127.0.0.1:8000/login/?next=/, 需要我们先登陆后才会跳转到首页。
使用我们在2.5小节中创建的用户:admin,密码: !qaz@wsx登陆系统
2.7 媒体文件的访问
尽管在创建用户时设置了默认头像,并且已经放置了默认头像使用的图片,但是用户登录后还是无法显示头像,所以还需要配置媒体文件的访问。
媒体文件是由用户上传的文件,路径是变化的,比如用户上传的头像文件。
设置文件上传目录
修改sandboxMP/sandboxMP/settings.py文件,添加如下配置:
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
打开sandboxMP/sandboxMP/urls.py,新增如下配置:
from django.conf import settings
from django.urls import re_path
from django.views.static import serve
if settings.DEBUG:
urlpatterns += [
re_path(r'^media/(?P<path>.*)$', serve, {"document_root": settings.MEDIA_ROOT}),
]
刷新页面就可以看到用户头像了,需要注意的是,这里之所以使用if settings.DEBUG,是因为这种配置模式应该仅限用于开发模式,在生产环境应该通过web前