一.前言
前面两章已经把登录给做完了,这一章节要说的是登录的校验和登录以后的菜单展示,内容还是很多的。
二.菜单和权限
2.1 是否登录
当我们进入其他的页面,我们首先要判断是否登录,这个时候我们就要借助中间件来做session和cookie的判断。
process_request,基于他实现用户是否已登录,继续;未登录则返回登录界面。
- return None,继续向后访问
- return 对象,直接返回。
process_view,权限校验
- return None,继续向后访问
- return 对象,直接返回。
- 在他的request对象中有 resolver_match ,包含当前请求的视图路由信息 .name->sms_login
process_response
那我们就先来编写中间件,我们现在setting里面注册中间件,并且加上自己的默认配置
这个自己加的配置是我们方便我们更改的,我们在昨天的视图函数里面也更改成seetings.xxx,自定义的通常都会加上前缀,避免于django内部定义的产生冲突。
这样我们就成功编写中间件来判断是否成功登录了
2.2 动态菜单
2.2.1 基础编写
这里我就不先说权限了,而是先说动态菜单,因为权限和菜单是绑定的,先说完菜单才更好讲权限,要让不同角色用户看到不同的菜单。
我们就要想如何让不同角色用户进入的菜单不同呢?我们前面铺垫了那么久,在最开始项目的时候也说了,首先我们可以在settings里面用上一个写上一个menu的字典,用户和管理员分别对应不同的标题和url,然后我们可以选择用if判断和for循环把页面展示到html里面去,但是我们觉得这样太麻烦了,可以借助自定义模板语法,用inclusion_tag把一个个小片段放入到html里面,那我们读取信息在哪里读取呢?我们登录成功之后,是不是在中间件里面把用户信息放入reuqest里面了,刚好我们每次传入模板的时候都要加上这个request,这样不就很简单了嘛(如果不记得自定义模板可以去看前面的文章)。
我们想要菜单的样子如上图这样,我们就应该想想要如何设计了,首先我们先不管美化,先想想如何设计数据的位置。
我们发现这个是一级菜单和二级菜单,一级菜单没有啥链接,但是有一个小的图标,二级菜单就是一个链接,那我们要设计这个肯定就是得在settings里面的menu来设计了。
那我们就可以这么设计,icon我们可以借助fontawesome里面的图标
Font Awesome图片库使用 - Font Awesome中文文档网https://fontawesome.uihtm.com/icons.html
也就可以用这个里面的class进行替代,当然如果有其他的也可以用其他的类似于链接呀等等,根据自己的情况来。
但是我们还会在每个链接加上一个name属性,这个我们后面要用到,也就是每个页面底下都会有很多链接,我们通过这个name来找到父标签让其默认选中,现在不懂没事,后面讲到的时候肯定就懂了。
这样就写出了一个很基础的页面
2.2.2 美化
我们美化的话肯定得要html,css和js,这里不会一一编写,只会说清楚逻辑,然后直接给代码。
我们肯定得把美化的公共页面写入母板,然后后面来继承就好了。那我们先创建一个layout.html,再在这个基础上进行美化。
那我们就先导入一下fontawesome,然后还有一些事先先好的css,js等
layout.html
menu.html
home.html
具体的所有代码等讲完了再给大家
具体的长这样,虽然不是很好看,但是也能看了,这样我们就可以实现在setting里面改配置来改页面了
2.2.3 默认选中
这里先讲一个基础的版本的,等讲完权限管理再来讲一个一个版本的
我们这里先给大家截屏代码再给大致思路
这样就好了,我们现在来说为什么,我们默认选中可以用当前的url和设置中的url进行匹配,如果一样那么就把class属性改成active,这是因为我们在common.js里已经写过了,所以直接用就好了,那我们 user_menu_list=copy.deepcopy(settings.YY_MENU[request.userinfo.role])为什么要copy一下呢?
这里给了一段代码大家运行一下就知道了
这个是deepseek给出的答案:
在这个代码中,
a
和b
都是指向同一个字典对象的引用。因为在 Python 中,赋值语句b = a
并不会复制字典的内容,而是将b
绑定到与a
相同的字典对象上。所以,修改
b
中的内容也会影响到a
,因为它们指向同一个字典。
所以我们只能copy一下!
2.2.4 顶部导航
我们想在顶部实现这么一个效果,肯定是要在layout里面写代码,这里还是直接给代码了
然后我们肯定还是要写一个注销功能的视图函数,这个就很好写了
只需要清除session会话信息,然后重定向到登录就好了。
2.3 权限校验
2.3.1 权限和菜单选中
一个权限,就是一个URL
用户具有的权限=用户可以访问这个URL
那我们就可以在配置文件写上一个字典,键值就用url对应的name,为什么不直接用url呢?因为如果用url,有的url是可变的,这将没法搞。
我们就这么用,parent就是子路由的父亲,类似于order下面有order_add等等子路由,这样的目的是为了我们点击子路由时也能够默认选中父亲的标签,还可以进行路径导航,这个我们接下来再说。
我们就需要在process_view里面进行路由匹配,然后判断是否有权限,再来获取最上层的父节点,最后存入request对象里面。但是我们发现点击子节点的时候,左边菜单是没有默认选中的。
我们就可以这么判断,只需要对应好,就可以解决默认选中的问题了。
2.3.2 路径导航
我们在中间件中获得全部的url的name,然后反转一下放入request对象中
我们就让最后一个点击加上#,减少对服务器的压力,同时以后最后一个肯定是类似于编辑那种,我们也不好根据id反向生成url
然后再用 inclusion_tag
生成一个导航页面
放入母板中的这里,这样就实现了路径导航啦
但是我们还有几个问题没有处理,我们这里说一下
1.当我们点击注销的时候,会发现无权访问,这个是因为注销我们没有添加到权限菜单里面,但是我们如果我们都添加有点难搞,所以我们可以写成一个公共的,再添加进去,这样就可以省下来不少事
这样就可以啦
2.当我们登陆的时候,会无法访问登录页面,这个在我们当时 process_request里面说过了,我们当时设置了一个白名单,让不需要登陆的链接也可以进去,那我们在 process_view也要判断一下白名单
这样就可以了啦!
三.级别管理
3.1 级别管理的基础页面
from django.db import models
class ActiveBaseModel(models.Model):
active = models.SmallIntegerField(verbose_name="状态", default=1, choices=((1, "激活"), (0, "删除"),))class Meta:
abstract = True
class Level(ActiveBaseModel):
""" 级别表 """
title = models.CharField(verbose_name="标题", max_length=32)
percent = models.IntegerField(verbose_name="折扣")
我们肯定是要对这个表进行操作,那我们还是老规矩,得先写页面,但是在写页面的时候,我们先把前面测试的那些页面都删掉
我们这样就可以 实现一个基础的页面了,那我们接下来就是要弄这个新建了,我们还是要新建一个这个页面,这里刚好可以和大家讲解一下modelform和form的区别。
3.2 新建级别
3.2.1 form
这个是我们写的html,和验证码登录的时候类似。
这个是我们用form写的,但是这种只用到数据库的,我们用modelform更加简单。
3.2.2 modelform
from django.db import models from django.core.validators import MinValueValidator, MaxValueValidatorclass ActiveBaseModel(models.Model):"""用来继承的表"""active = models.SmallIntegerField(verbose_name='状态', default=1,choices=((1, '激活'), (0, '删除'))) # 用来进行逻辑删除而不是物理删除class Meta:abstract = True # 把 ActiveBaseModel 设置为抽象类,防止在数据库中创建表class Level(ActiveBaseModel):"""级别表"""title = models.CharField(verbose_name='标题', max_length=32)percent = models.IntegerField(verbose_name='折扣',help_text='请输入1~100整数表示百分比',validators=[MinValueValidator(1), # 最小值为 1MaxValueValidator(100) # 最大值为 100])
- ModelForm,表的增删改查方便。
- Form,请求校验 -> 复杂SQL操作。
我们也可以在数据库里添加上form表中的一些校验,原来的label就相当于是verbose_name,并且最后保存到数据库只需要form.sava(),这样就很简单了
思考:无论在使用Form和ModelForm时,想要让页面好看,就需要将每个字段的插件中给他设置form-control样式。
这里直接给代码,以form的格式给,modelform也是一模一样
python">from django.shortcuts import render, redirect
from django.urls import reverse
from web import models
from django import forms
from django.core.validators import MinValueValidator, MaxValueValidatorclass LevelForm(forms.Form):title=forms.CharField(label='标题',required=True,)percent=forms.IntegerField(label='折扣',required=True,validators=[MinValueValidator(1), MaxValueValidator(100)],help_text='请输入1~100整数表示百分比')def __init__(self, *args, **kwargs):super().__init__(*args, **kwargs)# {'title':对象,"percent":对象}for name, field in self.fields.items():field.widget.attrs['class'] = "form-control"field.widget.attrs['placeholder'] = "请输入{}".format(field.label)
3.2.3 boostrap样式优化
先和大家讲一下面向对象小知识点super
通过这个知识点我们知道了super的继承方法
3.3 编辑级别
还是首先要定义一个url,再加上权限,这里我就不截屏了。
因为我们编辑的页面和增加的页面是一样的,所以我们就把之前的level_add.html改成level_form.html了,我们进来之后页面和下面这样
但是我们肯定是想要在页面上显示当前的默认值,方便我们进行修改
form或modelForm显示默认值
form = LevelForm(initial={'title': "xxx"})
modelForm显示默认值
level_object = models.Level.objects.filter(id=pk).first()
form = LevelModelForm(instance=level_object)
我们也是可以form.save(),但是和新增不同的是,修改的话必须加上instance
form=LevelModelForm(data=request.POST,instance=level_object)
3.4 删除级别
因为我们是逻辑删除,所以不能用delete,这里我们也写的简单,不用对话框来确认,后面会再来改的,这样就实现了级别列表
四 知识点的梳理和扩展
-
ModelForm
-
super关键字 + 类继承
4.1 Form和ModelForm的功能
-
Form
编写字段 生成HTML标签 + 插件 + 和参数的配置 表单的验证
-
ModelForm
不编写字段,直接引用Model字段【优秀】 生成HTML标签 + 插件 + 和 参数的配置 表单的验证 保存(新增、更新)
-
不编写字段,直接引用Model字段
class LevelModelForm(forms.ModelForm):class Meta:model = models.Levelfields = ['title', 'percent']fields = "__all__"exclude = ['active']
class LevelModelForm(BootStrapForm, forms.ModelForm):xxx = forms.CharField(label='xxx') class Meta:model = models.Level# fields = "__all__"# exclude = ['active']fields = ['title', 'xxx', 'percent', ]
class LevelModelForm(BootStrapForm, forms.ModelForm):title = forms.ChoiceField(label='xxx', choices=((1, "xxx"), (2, "xxxxxx"))) class Meta:model = models.Level# fields = "__all__"# exclude = ['active']fields = ['title', 'percent', ]
-
生成HTML标签 + 插件 + 和 参数的配置
class LevelModelForm(BootStrapForm, forms.ModelForm):class Meta:model = models.Levelfields = ['title', 'percent', ]widgets = {'name': forms.PasswordInput(render_value=True)}
-
表单验证
class LevelModelForm(BootStrapForm, forms.ModelForm):class Meta:model = models.Levelfields = ['title', 'percent', ] def clean_percent(self):value = self.cleaned_data['percent']return value
class LevelModelForm(BootStrapForm, forms.ModelForm):title = forms.CharField(validators=[])class Meta:model = models.Levelfields = ['title', 'percent', ] def clean_percent(self):value = self.cleaned_data['percent']return value
-
保存(新增、更新)
form = LevelModelForm(data=request.POST) form.save()
form = LevelModelForm(data=request.POST,install=对象) form.save()
form = LevelModelForm(data=request.POST) # 或有 ,install=对象 form.instance.percent = 10 form.save()
class LevelModelForm(BootStrapForm, forms.ModelForm):confirm_percent = forms.CharField(label="确认") class Meta:model = models.Levelfields = ['title', 'percent'] def level_add(request):if request.method == "GET":form = LevelModelForm()return render(request, 'form.html', {"form": form}) form = LevelModelForm(data=request.POST)if not form.is_valid():return render(request, 'form.html', {"form": form}) # {'title': '1', 'percent': 2, 'confirm_percent': '3'}print(form.cleaned_data)# form.instance.percent = 10form.save() return redirect(reverse('level_list'))
-
4.2 super关键字
根据类的MRO(类的继承关系)顺序,从下到上去找。 扩展:Python中的类的继承关系是通过 c3算法 计算出来。
五.总结
今天说的内容有点多,还是和大家讲了很多知识点,大家要记住这个知识点,代码啥的我就不贴了。最后直接给项目把!
六.补充
下一期将和大家开始讲登录的校验以及菜单,希望大家的关注加收藏,不懂得看我的名字和签名,一起交流学习