drf入门到精通:jwt配置文件、drf-jwt源码执行流程(了解)、自定义用户表实现jwt的签发和认证、simpleui、权限控制(acl,rbac)
一、jwt配置文件
需要记住的配置信息
'JWT_RESPONSE_PAYLOAD_HANDLER': 'app01.utils.jwt_response_payload_handler',
我们通过之前的学习可以得知jwt_response_payload_handler这个方法是定制返回信息的格式的,但是因为要使用我们自定义的方法所以需要在配置信息中注册,这里就是注册信息
'JWT_EXPIRATION_DELTA': datetime.timedelta(seconds=300),
这里是设置jwt认证的过期时间的
需要了解的配置信息
'JWT_AUTH_HEADER_PREFIX': 'JWT',
当我们在用postman朝接口发送请求的时候我们需要在Authorization中输入jwt,同时前面需要写jwt加空格来拼接,这里就是设置开头拼接字符的配置
'JWT_SECRET_KEY': settings.SECRET_KEY,
这就是密匙,不重要但是很关键
二、drf-jwt源码执行流程(了解)
2.0 auth的user表的补充知识
1、django 的auth user表,密码是加密的,即便的同样的密码,密文都不一样
-每次加密,都随机生成一个盐,把盐拼在加密后的串中
# 比如
pbkdf2_sha256$260000$B9ZRmPFpWb3H4kdDDmgYA9$CM3Q/ZfYyXzxwvjZ+HOcdovaJS7681kDsW77hr5fo5o=
明文:lqz12345
盐:B9ZRmPFpWb3H4kdDDmgYA9
后期来了明文lqz12345
2、自定义用户表,生成密码用密文
这里老师点了一些,没有展开讲(不是重点内容,建议课后自行研究),对于密码的加密,用的是make_password方法。
from django.contrib.auth.models import AbstractUser
'我们从auth的models中找相关的方法'
make_password被使用与下方的方法中,通过代码我们可以得知他就是对密码进行了处理
class UserManager(BaseUserManager):
use_in_migrations = True
def _create_user(self, username, email, password, **extra_fields):
"""
Create and save a user with the given username, email, and password.
"""
if not username:
raise ValueError('The given username must be set')
email = self.normalize_email(email)
# Lookup the real model class from the global app registry so this
# manager method can be used in migrations. This is fine because
# managers are by definition working on the real model.
GlobalUserModel = apps.get_model(self.model._meta.app_label, self.model._meta.object_name)
username = GlobalUserModel.normalize_username(username)
user = self.model(username=username, email=email, **extra_fields)
user.password = make_password(password)
user.save(using=self._db)
return user
点进make_password的源码我们开始分析
def make_password(password, salt=None, hasher='default'):
if password is None:
return UNUSABLE_PASSWORD_PREFIX + get_random_string(UNUSABLE_PASSWORD_SUFFIX_LENGTH)
'根据我们的尝试,没传密码的时候就是触发了第一行的if判断,通过查看几个变量和方法的内容,可以简单判断出他就是产生了一串随机字符串'
if not isinstance(password, (bytes, str)):
raise TypeError(
'Password must be a string or bytes, got %s.'
% type(password).__qualname__
)
'根据这里的if判断,我们可以简单看出他是用来判断密码的数据类型的,如果不是字符串或是二进制,就报错'
hasher = get_hasher(hasher)
salt = salt or hasher.salt()
return hasher.encode(password, salt)
'然后这里的get_hasher我看源码也只是一知半解,具体的作用差不多就是对我们传入的密码进行加密,通过注释得知这里的参数就是用来判断是否指定了加密用的某个东西,没有就用默认的,因此hasher就是返回了一个加密用的东西,然后salt我不太明白,但是现在已经不影响我理解整体意思了,最后的return出去的内容就是对密码进行加密并且转换成二进制的内容,'
def get_hasher(algorithm='default'):
"""
Return an instance of a loaded password hasher.
If algorithm is 'default', return the default hasher. Lazily import hashers
specified in the project's settings file if needed.
"""
if hasattr(algorithm, 'algorithm'):
return algorithm
elif algorithm == 'default':
return get_hashers()[0]
else:
hashers = get_hashers_by_algorithm()
try:
return hashers[algorithm]
except KeyError:
raise ValueError("Unknown password hashing algorithm '%s'. "
"Did you specify it in the PASSWORD_HASHERS "
"setting?" % algorithm)
'通过会议encode的知识,我们可以得知这个salt其实是用于指定字符编码类型的,那我们可以产生一个模糊认知,我们只用了salt这个东西,就完成了密码的加密,并指定了转换成二进制时的编码类型'
讲解完了密码的加密,自然得有密码的校验,这里老师也做了提示,check_password方法就是用于校验密码的(回头有空再写,感觉博客来不及了)。
3、用户表密码忘了怎么办
方法一:新增一个用户,创建完成后把这个用户的密码复制到数据库中忘记密码的账号的密码的位置(建议用完改回去,不然知道密码的人用不了了解释起来也麻烦)
方式二:直接去掉认证,让这个用户直接可以登陆(更不推荐了)
2.1 签发(登录)
当我们在使用的时候只需要在路由层中导入obtain_jwt_token这个类,而他就是对应的视图类,因此他内部也是有对应的处理post请求的post方法的,因此我们需要研究他内部的post方法
from rest_framework_jwt.views import obtain_jwt_token
在查找的时候我们发现他并不是直接跳转到对应的方法上去了,他只是一个变量名
obtain_jwt_token = ObtainJSONWebToken.as_view()
refresh_jwt_token = RefreshJSONWebToken.as_view()
verify_jwt_token = VerifyJSONWebToken.as_view()
这里不用管他执行了as_view方法(虽然我们也明白作用),点击前面的这个关键名称的源码,我们可以发现他内部是空的,但是他继承了一个父类
class ObtainJSONWebToken(JSONWebTokenAPIView):
"""
API View that receives a POST with a user's username and password.
Returns a JSON Web Token that can be used for authenticated requests.
"""
serializer_class = JSONWebTokenSerializer
这个父类中就有定义一个post方法
class JSONWebTokenAPIView(APIView):
def post(self, request, *args, **kwargs):
# 实例化得到序列化类
serializer = self.get_serializer(data=request.data)
# 做校验:字段自己,局部钩子,全局钩子
if serializer.is_valid():
# user:当前登录用户
user = serializer.object.get('user') or request.user
# 签发的token
token = serializer.object.get('token')
# 构造返回格式,咱们可以自定制---》讲过了
response_data = jwt_response_payload_handler(token, user, request)
'这里的jwt_response_payload_handler方法我们之前看过,就是用来定制返回的信息的格式的'
response = Response(response_data)
'这里我们也可以看出来他就是产生response对象'
'然后这里的if,目前用不到,他通过判断jwt的配置文件中的JWT_AUTH_COOKIE是否存在,来判断项目是否是前后端结合的,前后端结合的时候就会用上这里的代码'
if api_settings.JWT_AUTH_COOKIE:
expiration = (datetime.utcnow() +
api_settings.JWT_EXPIRATION_DELTA)
response.set_cookie(api_settings.JWT_AUTH_COOKIE,
token,
expires=expiration,
httponly=True)
#最终返回了咱们定制的返回格式
return response
'这里就是出错的时候返回报错信息以及错误码'
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
通过上方的代码分析,我们得知在经过序列化类的校验后才得到的user和token,因此接下去我们要去序列化类中研究。
ps:之前我们校验的方式是直接在视图函数内的方法(比如post等方法)中校验,现在就相当于是中间多了一个步骤,拉到序列化类中去校验了,别的其实并没什么打的区别。(将来也会经常出现这样的情况)
在上面我们看到序列化类定义在执行as_view方法的那个类中
class ObtainJSONWebToken(JSONWebTokenAPIView):
"""
API View that receives a POST with a user's username and password.
Returns a JSON Web Token that can be used for authenticated requests.
"""
serializer_class = JSONWebTokenSerializer
# 如何得到user,如何签发的token----》在序列化类的全局钩子中得到的user和签发的token
-JSONWebTokenSerializer---全局钩子---validate方法
#这里的attrs就是前端传入,校验过后的数据---》{"username":"lqz","password":"lqz1e2345"}
def validate(self, attrs):
credentials = {
# self.username_field: attrs.get(self.username_field),
'上面的代码只是多做了一个动态匹配,我们写成下面的形式也是一样的,当我们改了User表之后他会自动匹配'
'username':attrs.get('username')
'password': attrs.get('password')
}
'这里的字典就是获取用户名和密码'
if all(credentials.values()):
# auth 模块,authenticate 可以传入用户名,密码如果用户存在,就返回用户对象,如果不存就是None
# 正确的用户
user = authenticate(**credentials)
if user:
# 校验用户是否是活跃用户,如果禁用了,不能登录成功
if not user.is_active:
msg = _('User account is disabled.')
raise serializers.ValidationError(msg)
# 获取荷载----》通过user得到荷载 {id,name,email,exp}
payload = jwt_payload_handler(user)
return {
# jwt_encode_handler通过荷载得到token串
'token': jwt_encode_handler(payload),
'user': user
}
else:
msg = _('Unable to log in with provided credentials.')
raise serializers.ValidationError(msg)
else:
msg = _('Must include "{username_field}" and "password".')
msg = msg.format(username_field=self.username_field)
raise serializers.ValidationError(msg)
'后面的else部分就是进行报错'
### 重点:
1 通过user得到荷载:payload = jwt_payload_handler(user)
2 通过荷载签发token:jwt_encode_handler(payload)
## 了解:
'上面有一段代码是对用户进行校验,检测是否为活跃用户,他用的方法变成了一格下划线,是因为进行了重命名,这个方法叫做翻译函数,当然前提是对drf这个app进行注册'
# 翻译函数,只要做了国际化,放的英文,会翻译成该国语言(配置文件配置的)
from django.utils.translation import ugettext as _
msg = _('Unable to log in with provided credentials.')
2.2 认证(认证类)
之前学习怎么使用的时候我们是直接导入了jwt的认证类,因此我们应该去研究他
# JSONWebTokenAuthentication---->父类BaseJSONWebTokenAuthentication----》authenticate方法
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
但是进入他的源码我们没有找到authenticate方法,这时候我们就要去看他的父类
class JSONWebTokenAuthentication(BaseJSONWebTokenAuthentication):
www_authenticate_realm = 'api'
def get_jwt_value(self, request):
auth = get_authorization_header(request).split()
auth_header_prefix = api_settings.JWT_AUTH_HEADER_PREFIX.lower()
if not auth:
if api_settings.JWT_AUTH_COOKIE:
return request.COOKIES.get(api_settings.JWT_AUTH_COOKIE)
return None
if smart_text(auth[0].lower()) != auth_header_prefix:
return None
if len(auth) == 1:
msg = _('Invalid Authorization header. No credentials provided.')
raise exceptions.AuthenticationFailed(msg)
elif len(auth) > 2:
msg = _('Invalid Authorization header. Credentials string '
'should not contain spaces.')
raise exceptions.AuthenticationFailed(msg)
return auth[1]
def authenticate_header(self, request):
return '{0} realm="{1}"'.format(api_settings.JWT_AUTH_HEADER_PREFIX, self.www_authenticate_realm)
接着我们就在他的父类BaseJSONWebTokenAuthentication中找到了authenticate方法
class BaseJSONWebTokenAuthentication(BaseAuthentication):
def authenticate(self, request):
# 前端带在请求头中的token 值
jwt_value = self.get_jwt_value(request)
# 如果没有携带token,就不校验了
if jwt_value is None:
return None
try:
# jwt_value就是token
# 通过token,得到荷载,中途可能会出错,用异常捕获进行了处理
# 出错的原因:
-篡改token
-过期了
-未知错误(比如随便给个字符串或是超时等)
payload = jwt_decode_handler(jwt_value)
except jwt.ExpiredSignature:
msg = _('Signature has expired.')
raise exceptions.AuthenticationFailed(msg)
except jwt.DecodeError:
msg = _('Error decoding signature.')
raise exceptions.AuthenticationFailed(msg)
except jwt.InvalidTokenError:
raise exceptions.AuthenticationFailed()
# 如果能顺利解开,没有被异常捕获,说明token是可以信任的
# payload就可以使用,通过payload得到当前登录用户
user = self.authenticate_credentials(payload)
# 返回当前登录用户,token(这也是之前我们为什么要返回用户和token)
return (user, jwt_value)
# 在上面的方法中我们通过jwt_value = self.get_jwt_value(request)得到了token,但是我们想到我们在传token的时候,前面是jwt加一个空格才拼接的token,因此我们需要研究这个方法
'不过查找的时候并不在当前类中,我们需要从外面往里面找(可以导入from rest_framework_jwt.authentication import JSONWebTokenAuthentication然后再点进源码查找),因为虽然是认证类在运行,但是我们是通过视图类产生对象,由这个对象来执行方法的'
def get_jwt_value(self, request):
# 拿到了前端请求头中传入的 jwt dasdfasdfasdfa
# auth=[jwt,asdfasdfasdf]这里就相当于对接收的字符串根据空格进行了切割
auth = get_authorization_header(request).split()
# 'jwt',这里就是把开头的jwt转成了小写
auth_header_prefix = api_settings.JWT_AUTH_HEADER_PREFIX.lower()
if not auth:
'如果我们拿空格切割成功,就不能进入这个循环,这里也是为了兼容前后端混合开发'
# 请求头中如果没带,去cookie中取
if api_settings.JWT_AUTH_COOKIE:
return request.COOKIES.get(api_settings.JWT_AUTH_COOKIE)
return None
if smart_text(auth[0].lower()) != auth_header_prefix:
'这里是对比字符开头的字母是不是jwt。是跟配置文件中的信息进行对比的,之前也讲过'
return None
'这里下面两个if判断就是对我们传入的字符串格式进行校验,只有存在一个空格的时候才是符合条件的'
if len(auth) == 1:
msg = _('Invalid Authorization header. No credentials provided.')
raise exceptions.AuthenticationFailed(msg)
elif len(auth) > 2:
msg = _('Invalid Authorization header. Credentials string '
'should not contain spaces.')
raise exceptions.AuthenticationFailed(msg)
return auth[1]
# 认证类配置了,如果不传jwt,不会校验,一定配合权限类使用
三、自定义用户表实现jwt的签发和认证
3.1 签发
首先我们需要创建一个自定义的用户表,因为只是用于测试的,所以我们不用写的很复杂,简单写一点就可以进行数据库迁移
models.py
from django.db import models
# Create your models here.
class UserInfo(models.Model):
username = models.CharField(max_length=32)
password = models.CharField(max_length=32)
接着我们回忆一下jwt的签发和认证过程,他是在对应的序列化类中用全局钩子获取user和token的,并对他们进行校验的,因此我们如果在视图类中写类似post等方法的时候可以考虑不学他分开写,我们把校验的过程也写进来。
urls.py
from django.contrib import admin
from django.urls import path, include
from rest_framework_jwt.views import obtain_jwt_token
from rest_framework.routers import SimpleRouter
from app01 import views
router = SimpleRouter()
router.register('user', views.UserView, 'user')
urlpatterns = [
path('admin/', admin.site.urls),
path('api/v1/', include(router.urls)),
path('login/', obtain_jwt_token)
]
views.py
from django.shortcuts import render
from rest_framework.decorators import action
from rest_framework.viewsets import ViewSet
from rest_framework.response import Response
from .models import UserInfo
from rest_framework_jwt.settings import api_settings
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
'这里的两个配置是因为序列化类中校验的时候用到了,所以我们也要拿来用'
# from rest_framework_jwt.utils import jwt_payload_handler
'这是JWT_PAYLOAD_HANDLER指向的方法,我们可以这样调用,然后点进去看他的源码,通过他的源码我们可以发现他就是用来定义荷载的格式的'
'而JWT_ENCODE_HANDLER,和接下去会用到的JWT_DECODE_HANDLER也可以用这样的方式查看,然后再去看他们的源码'
class UserView(ViewSet):
@action(methods=['POST'], detail=False)
def login(self, request, *args, **kwargs):
username = request.data.get('username')
password = request.data.get('password')
user = UserInfo.objects.filter(username=username, password=password).first()
if user:
# 登录成功,签发token
# 通过user得到payload
payload = jwt_payload_handler(user)
# 通过payload得到token
token = jwt_encode_handler(payload)
return Response({'code': 1000, 'msg': '登录成功', 'token': token})
else:
return Response({'code': 1001, 'msg': '用户名或密码错误'})
这里的密码返回格式也有很多的玄机,比如如果我们把返回信息分开,变得更具体,这样别人就可以通过不断尝试来判断是否有这个用户,类似的东西也有很多
测试结果:
3.2 认证
在上面分析的过程中我们发现JSONWebTokenAuthentication这个认证类的父类没什么内容,但是他的父类继承了BaseAuthentication,这个类算是基类。
而现在我们需要自定义认证类,因此我们需要重新写一个认证类。
authentication.py(认证类)
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
import jwt
from .models import UserInfo
from rest_framework_jwt.settings import api_settings
jwt_decode_handler = api_settings.JWT_DECODE_HANDLER
class JsonWebTokenAuthentication(BaseAuthentication):
'这里需要重写authenticate方法,因为最后是authenticate方法返回的用户对象和token,内部代码也是模仿源码中的authenticate方法编写'
def authenticate(self, request):
# 取出token----》在请求头中,就叫token
token = request.META.get('HTTP_TOKEN')
'源码中有一段if代码用于判断是否传入了token,如果没传就不校验了,这个没什么意思,我们就不写了'
if token:
try:
'这里的代码基本上照搬源码中的代码'
'这里就是通过token获得荷载'
payload = jwt_decode_handler(token)
# 得到当前登录用户----》
user = UserInfo.objects.get(pk=payload.get('user_id'))
'这里查找的时候,我们需要自己去token的荷载中确认一下字段名称,因此不能直接写pk来查找'
# 只要访问一次需要登录的接口,就会去UserInfo表中查一次用户---》可以优化
'我们用下面的代码来替换这里的代码,就可以暂时不去数据库中查询,等到用到的时候再去查询的效果'
# user=UserInfo(id=payload.get('user_id'),username=payload.get('username'))
# user={'id':payload.get('user_id')}
'同时这里我们把所有的代码都放到异常捕获中,来避免报错,主要是避免为登陆导致的报错'
return user, token
except jwt.ExpiredSignature:
raise AuthenticationFailed('token过期')
except jwt.DecodeError:
raise AuthenticationFailed('token认证失败')
except jwt.InvalidTokenError:
raise AuthenticationFailed('token无效')
except Exception as e:
raise AuthenticationFailed('未知异常')
raise AuthenticationFailed('token没有传,认证失败')
当我们重写了这个认证类之后,我们就不需要使用权限类来校验是否登陆了,这时候我们再写一个视图类来使用这个认证类顺带检测一下
views.py中需要添加一个视图类
from .authentication import JsonWebTokenAuthentication
class TestView(ViewSet):
authentication_classes = [JsonWebTokenAuthentication]
@action(methods=['GET'], detail=False)
def test1(self, request):
return Response('ok')
urls.py
添加一条路由配置
router.register('test', views.TestView, 'test')
ps:
如果我们写代码的时间太长,而且配置中的过期时间依然是默认的五分钟,token就会过期,这里我们可以再次登陆获取token,或是修改配置中的token过期时间
JWT_AUTH = {
# 过期时间1天
'JWT_EXPIRATION_DELTA': datetime.timedelta(days=7)
}
测试结果(需要先用原来的视图类登陆,然后用token来测试,这时候因为我们自定的时候去掉了开头的jwt加空格,所以直接传token就好了):
四、simpleui
之前公司里,做项目前后端结合,要使用权限,要快速搭建后台管理,使用djagno的admin直接搭建,django的admin界面不好
因此有了第三方美化
#第三方的美化:
-xadmin:作者弃坑了(通常是bug太多了),基于bootstrap+jq编写的
-simpleui: 基于vue编写的,界面更好看
现在阶段,一般前后端分离比较多:django+vue
因此需要一块带权限的前后端分离的快速开发框架,我们目前有两个选择
-使用django-vue-admin(李强学长写的)
-自己写
ps:如果刷新页面没有变化,就重启项目
4.1 使用步骤
- 1、安装
pip install django-simpleui
- 2、在app中注册(必须注册在最上面)
INSTALLED_APPS = [
'simpleui',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'app01.apps.App01Config',
'rest_framework',
]
这时候就可以去admin后台界面看效果了
目前我们没有添加别的表,为了方便后面的测试,我们需要导入之前图书管理系统用到的表
models.py
from django.db import models
# Create your models here.
class UserInfo(models.Model):
username = models.CharField(max_length=32)
password = models.CharField(max_length=32)
class Book(models.Model):
nid = models.AutoField(primary_key=True)
name = models.CharField(max_length=32,verbose_name='图书名字',help_text='这里填图书名')
price = models.DecimalField(max_digits=5, decimal_places=2, verbose_name='价格')
publish_date = models.DateField(verbose_name='出版日期')
publish = models.ForeignKey(to='Publish', to_field='nid', on_delete=models.CASCADE, verbose_name='出版社')
authors = models.ManyToManyField(to='Author', verbose_name='作者')
def __str__(self):
return self.name
class Meta:
verbose_name = '书籍'
verbose_name_plural = verbose_name
class Author(models.Model):
nid = models.AutoField(primary_key=True)
name = models.CharField(max_length=32, verbose_name='作者名')
age = models.IntegerField(verbose_name='年龄')
author_detail = models.OneToOneField(to='AuthorDetail', to_field='nid', unique=True, on_delete=models.CASCADE, verbose_name='作者信息的一对一外键')
def __str__(self):
return self.name
class Meta:
verbose_name = '作者'
verbose_name_plural = verbose_name
class AuthorDetail(models.Model):
nid = models.AutoField(primary_key=True)
telephone = models.BigIntegerField(verbose_name='手机号')
birthday = models.DateField(verbose_name='生日')
addr = models.CharField(max_length=64, verbose_name='住址')
class Meta:
verbose_name = '作者详情'
verbose_name_plural = verbose_name
class Publish(models.Model):
nid = models.AutoField(primary_key=True)
name = models.CharField(max_length=32, verbose_name='出版社名称')
city = models.CharField(max_length=32, verbose_name='城市')
email = models.EmailField(verbose_name='邮箱')
def __str__(self):
return self.name
class Meta:
verbose_name = '出版社'
verbose_name_plural = verbose_name
ps:
当我们在admin中进行注册后我们会发现admin后台管理界面上显示的这四张表的内容是定义时候的类名,这时候我们就需要在模型层中添加Meta类来定义他的名称。
这里我们用来两行代码来定义,之所以这么些是因为在只使用verbose_name来定义的时候,名称后面会多出一个s,这不太好看,因此需要使用第二种方式verbose_name_plural来命名,这种方式就不会在末尾出现s
数据库迁移后我们回顾一下编写bbs项目时的操作步骤,如果想让表在admin界面显示出来,时需要去admin.py进行注册的,这里也是一样
admin.py
from django.contrib import admin
from .models import Book, Publish, Author, AuthorDetail
# Register your models here.
admin.site.register(Book)
admin.site.register(Publish)
admin.site.register(Author)
admin.site.register(AuthorDetail)
添加完注册信息后,我们刷新网页就能看到这些表已经出现在admin后台管理界面上了,但是这时候我们可以看到虽然表中的字段的名称已经被我们更改掉了,但是我们使用的app的名称也看着不美观。
这时候我们就要去应用的apps.py文件中添加一条属性
apps.py
from django.apps import AppConfig
class App01Config(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'app01'
verbose_name = '图书管理系统'
这个时候我们就会看到所有的地方都已经被我们替换成了中文
这时候我们点击左侧的书籍,开始添加数据
点击蓝色的增加按钮就会进入添加数据的界面
4.2 功能介绍
更多功能请去浏览官方文档:https://simpleui.72wo.com/docs/simpleui/quick.html#目录
部分功能讲解
- 自定义菜单
当我们需要使用simpleui的自定义菜单栏时,需要修改admin中注册的方式
这里我们以书籍表(book)为例子,先把原来的注册信息注释掉,然后再写上新的注册内容
admin.py
# admin.site.register(Book)
@admin.register(Book)
class BookAdmin(admin.ModelAdmin):
list_display = ('nid', 'name', 'price', 'publish_date', 'publish')
'模型层中有哪些字段,这里我们就要写上哪些字段名,多对多的字段不用写(他本质上是一张表)'
用新的方式注册了图书表后界面就会发生更改(更好看)
菜单栏中添加按钮
在上面的基础上,我们在admin.py文件中可以配置自定义按钮
admin.py
from django.contrib import admin
from .models import Book, Publish, Author, AuthorDetail
# Register your models here.
# admin.site.register(Book)
admin.site.register(Publish)
admin.site.register(Author)
admin.site.register(AuthorDetail)
@admin.register(Book)
class BookAdmin(admin.ModelAdmin):
list_display = ('nid', 'name', 'price', 'publish_date', 'publish')
# 增加自定义按钮
actions = ['custom_button']
def custom_button(self, request, queryset):
print(queryset)
'这是定义按钮作用的函数可以自行编写功能,这里只是简单的打印'
custom_button.confirm = '你是否执意要点击这个按钮?'
'这是点击了按钮之后出现的弹窗中的提示信息'
# 显示的文本,与django admin一致
custom_button.short_description = '测试按钮'
'这是按钮的名称'
# icon,参考element-ui icon与https://fontawesome.com
# custom_button.icon = 'fas fa-audio-description'
'这里是定义按钮前面的图标的'
# # 指定element-ui的按钮类型,参考https://element.eleme.cn/#/zh-CN/component/button
custom_button.type = 'danger'
'这是更改按钮类型的配置'
# # 给按钮追加自定义的颜色
# custom_button.style = 'color:black;'
- 调整左侧导航栏(自定义菜单)
这个功能需要我们在项目的配置文件中添加配置
#######simpleui---->左侧菜单栏
import time
SIMPLEUI_CONFIG = {
'system_keep': False,
'menu_display': ['图书管理', '权限认证', '张红测试'], # 开启排序和过滤功能, 不填此字段为默认排序和全部显示, 空列表[] 为全部不显示,同时这里就是左侧菜单栏显示的内容.
'dynamic': True, # 设置是否开启动态菜单, 默认为False. 如果开启, 则会在每次用户登陆时动态展示菜单内容
'menus': [
{
'name': '图书管理',
'app': 'app01',
'icon': 'fas fa-code',
'models': [
{
'name': '图书',
'icon': 'fa fa-user',
'url': 'app01/book/'
},
{
'name': '出版社',
'icon': 'fa fa-user',
'url': 'app01/publish/'
},
{
'name': '作者',
'icon': 'fa fa-user',
'url': 'app01/author/'
},
{
'name': '作者详情',
'icon': 'fa fa-user',
'url': 'app01/authordetail/'
},
]
},
{
'app': 'auth',
'name': '权限认证',
'icon': 'fas fa-user-shield',
'models': [
{
'name': '用户',
'icon': 'fa fa-user',
'url': 'auth/user/'
},
{
'name': '组',
'icon': 'fa fa-user',
'url': 'auth/group/'
},
]
},
{
'name': '张红测试',
'icon': 'fa fa-file',
# 二级菜单
'models': [
{
'name': 'Baidu',
'icon': 'far fa-surprise',
# 第三级菜单 ,
'models': [
{
'name': '爱奇艺',
'url': 'https://www.iqiyi.com/dianshiju/'
# 第四级就不支持了,element只支持了3级
}, {
'name': '百度问答',
'icon': 'far fa-surprise',
'url': 'https://zhidao.baidu.com/'
}
]
},
{
'name': '大屏展示',
'url': '/show/',
'icon': 'fab fa-github'
}]
}
]
}
1、menu_display对应menus的name属性
2、这里的menus可以进行三次嵌套(可以写到三级菜单),但是在使用的时候需要分清楚指向的内容,如果是给项目的app定义菜单栏,就需要在menus中添加app属性
3、然后menus中我们需要填入url,这个url一开始我们可能不知道,但是我们访问一个错误的内部网址的然后他的网页上就会显示出目前有的内部的url,或是我们手动设置对应的路由。同时我们也可以填写一些外网的url进行访问。icon属性是设置图标
- 其他功能
这些功能都是在配置文件(settings.py)中添加配置信息来实现功能的
登录页面动态粒子效果
SIMPLEUI_LOGIN_PARTICLES = False #登录页面动态粒子效果
默认图标替换
SIMPLEUI_LOGO = 'https://avatars2.githubusercontent.com/u/13655483?s=60&v=4'
登陆成功后admin界面首页右上方的github等信息窗口开关(服务器信息)
SIMPLEUI_HOME_INFO = False #首页右侧github提示
快捷操作
SIMPLEUI_HOME_QUICK = False #快捷操作
最近动作
SIMPLEUI_HOME_ACTION = False # 动作
ps1:之所以会有这些取消展示内容的设置是因为当我们想要自定义的时候,这些东西是用不到的
ps2:在文档中介绍了一个离线模式,这个不要开,一般填False
4.3 展示大屏
我们在很多大型场馆,经常会看到一块大屏幕,上面动态实时展示着一些内容,看上去十分炫酷,这些其实也是很简单的。
我们直接去github或是gitee上搜展示大屏,找一个自己喜欢的下载下来或是直接用地址获取内容
# 监控大屏展示
-https://search.gitee.com/?skin=rec&type=repository&q=%E5%B1%95%E7%A4%BA%E5%A4%A7%E5%B1%8F
-就是前后端混合项目,js,css,图片对应好,就可以使用了
下载后的内容打开是这样的
我们把对应的js,css,图片下载下来存到我们自己项目的静态文件目录中(static),然后把他的html文件拷贝到templates目录内,再把对应的路径修改一下就可以使用了,需要用到什么变量的时候我们直接用模板语法写上去即可
五、权限控制(acl,rbac)
将来我们编写的项目主要分成两类:公司内部项目和互联网项目
- 公司内部项目
使用RBAC-基于角色的访问控制
什么是RBAC?
RBAC 是基于角色的访问控制(Role-Based Access Control )在 RBAC 中,权限与角色相关联,用户通过成为适当角色的成员而得到这些角色的权限。这就极大地简化了权限的管理。这样管理都是层级相互依赖的,权限赋予给角色,而把角色又赋予用户,这样的权限设计很清楚,管理起来很方便。
举例
比如在公司内部,根据部门划分权限
-用户表:用户和角色多对多关系,
-角色表
-一个角色可能有多个权限----》
-开发角色:拉取代码,上传代码
-财务角色:开工资,招人,开除人
-......
-权限表:角色和权限是多对多
-拉取代码
-上传代码
-部署项目
-开工资
-招人
-开除人
-......
-通过5张表完成rbac控制:用户表,角色表,权限表, 用户角色中间表, 角色权限中间表
-如果某个人,属于财务角色,又只是单独想要拉取代码权限,不要上传代码权限,这时候就需要另外一张表:用户和权限中间表
-通过6张表:django的admin----》后台管理就是使用这套权限认证
用户表,
角色表,
权限表,
用户角色中间表,
角色权限中间表
用户和权限中间表
django-admin就是一个典型的rbac权限管理,这里我们可以用django-admin来体验rbac的权限管理
# django -auth--6张表
auth_user 用户表
auth_group 角色表,组表
auth_permission 权限表
-----------
auth_user_groups 用户和角色中间表
auth_group_permissions 角色和权限中间表
-------------
auth_user_user_permissions 用户和权限中间表
目前流行的一些rbac
# java:若依
# go :gin-vue-admin
# python :django-vue-admin
- 互联网项目
使用ACL 权限控制
什么是ACL 权限控制?
ACL(Access Control List)访问控制列表的简称,是一个规则列表,用于指定允许或拒绝哪些用户或系统访问特定对象或系统资源,访问控制列表也安装在路由器或交换机中,它们充当过滤器,管理哪些流量可以访问网络。
举例
我们在使用抖音的时候,就是用acl进行管理
-权限:权限表----》 发视频,评论,开直播
-用户表:用户和权限是一对多
张三:[发视频,]
李四:[发视频,评论,开直播]
双token
顾名思义,就是在登陆操作之后由服务端返回两个token:accessToken和refreshToken,在之后的验证登录态的操作中使用这两个token进行验证,其中accessToken的过期时间相当短,refreshToken的过期时间相对于accessToken而言相当长,且会不断的刷新,每次刷新后的refreshToken都是不同的
双token的优点
通过上面的描述也可以或多或少的看出双token验证机制的优点了:
accessToken的存在,保证了登录态的正常验证,因其过期时间的短暂也保证了帐号的安全性
refreshToekn的存在,保证了用户(即使是非活跃用户)无需在短时间内进行反复的登陆操作来保证登录态的有效性,同时也保证了活跃用户的登录态可以一直存续而不需要进行重新登录,其反复刷新也防止某些不怀好意的人获取refreshToken后对用户帐号进行动手动脚的操作(拒绝NTR.jpg)