DRF-实例解析

结合对DRF基础知识的学习,本节内容开始一个小例子,完成对前面知识点的梳理及总结。
本节篇幅较长,请慎入!

实例功能

以一个简单的CMDB系统来进行示例,主要功能如下:

用户模块

  • 注册
  • 登录
  • 用户增删改查
  • 身份校验采用JWT实现

设备管理模块

  • PC机增删改查
  • Server增删改查,自动收集Server配置信息

日志模块

  • 用户操作日志展示

软件环境

Python 3.6.3
Django 1.11.13
djangorestframework 3.8.2
djangorestframework-jwt 1.11.0
paramiko 2.4.1
django-crequest 2018.5.11

代码实现解析

项目目录结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
myangular
├─myangular
│ ├─__init.py
│ ├─settings.py
│ ├─urls.py
│ └─wsgi.py
├─users
│ ├─migrations
│ ├─__init__.py
│ ├─admin.py
│ ├─apps.py
│ ├─models.py
│ ├─serializer.py
│ ├─signals.py
│ ├─test.py
│ └─views.py
├─equipment
│ ├─migrations
│ ├─__init__.py
│ ├─admin.py
│ ├─apps.py
│ ├─models.py
│ ├─serializer.py
│ ├─test.py
│ └─views.py
├─templates
└─utils
├─sftpDir
│ ├─getData.py
│ ├─getJwt.py
│ ├─sendData.py
│ └─main.py
├─__init__.py
├─connectserver.py
└─getmac.py

myangular 是项目名称,这个名字自己随便写的。
users 和 equipment 是添加的app名称。
utils 里是一些被调用的自定义模块。

一、用户模块

1、用户信息建模

myangular/users/models.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
from django.db import models
from datetime import datetime
from django.contrib.auth.models import AbstractUser
import datetime

class UserProfile(AbstractUser): #继承AbstractUser
"""
用户表
"""
GENDER_CHOICES = (
("male", u"男"),
("female",u"女")
)
name = models.CharField(max_length=30, null=True, blank=True, verbose_name="姓名")
birthday = models.DateField(null=True, blank=True, verbose_name="出生年月")
gender = models.CharField(max_length=6, choices=GENDER_CHOICES, default="female", verbose_name="性别")
mobile = models.CharField(max_length=11, null=True, blank=True, verbose_name="电话")
email = models.EmailField(max_length=100, verbose_name="邮箱")

class Meta:
verbose_name = "用户"
verbose_name_plural = verbose_name

def __str__(self):
return self.username

Django 自带用户模块功能并且处理用户验证的方式符合我们项目需求,但我们希望在不创建单独模块的情况下增加一些额外的属性。因此这里采用扩展 AbstractUser 创建自定义用户模型。
这样实现还必须更新 settings.py,定义 AUTH_USER_MODEL 属性来覆盖django的默认表。
settings配置
myangular/myangular/settings.py

1
AUTH_USER_MODEL = "users.UserProfile"

在视图中调用的时候,需要采用下面的方式

1
2
from django.contrib.auth import get_user_model
User = get_user_model()

2、自定义用户后台认证实现登录

django 默认使用用户名和密码去查询来进行用户登录验证,我们这里需要使用邮箱和密码进行登录验证,因此来自定义用户验证。
settings 中定义 AUTHENTICATION_BACKENDS ,这是个元组,如果只有一项,后面必须添加逗号。
myangular/myangular/settings.py

1
2
3
AUTHENTICATION_BACKENDS = (
'users.views.CustomBackend',
)

我们在 views 中定义认证类,自定义 CustomBackend,重写 authenticate 函数。
myangular/users/views.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from django.contrib.auth.backends import ModelBackend
from django.db.models import Q
from django.contrib.auth import get_user_model
User = get_user_model()

class CustomBackend(ModelBackend):
"""
自定义用户验证
"""
def authenticate(self, request, username=None, password=None, **kwargs):
try:
user = User.objects.get(Q(username=username)|Q(email=username))
if user.check_password(password): #调用check_password 对明文密码进行加密认证
return user
except Exception as e:
return None

3、DRF实现用户增删改查

首先定义序列化类 serializer ,我们这里定义了2个serializerUserRegSerializer、UserDetailSerializer
UserRegSerializer 在注册用户的时候显示,在客户端只展示用户名和密码字段。
UserDetailSerializer 在查看详细的时候展示,定义的字段多一些。

myangular/users/serializer.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
import re
from rest_framework import serializers
from rest_framework.validators import UniqueValidator
from myangular.settings import REGEX_EMAIL
from django.contrib.auth import get_user_model
User = get_user_model()

class UserRegSerializer(serializers.ModelSerializer):
"""
用户注册序列化类
"""
username = serializers.CharField(required=True,
allow_blank=False,
label="用户名",
validators=[UniqueValidator(queryset=User.objects.all(),message="用户已存在")],
help_text="string用户名")

# 用户注册成功后并不需要返回password字段,因此这里配合write_only=True
password = serializers.CharField(style={'input_type':'password'},label="密码",write_only=True,help_text="string密码")

def validate_username(self, username):
"""
验证用户名是邮箱规则
"""
if not re.match(REGEX_EMAIL,username):
raise serializers.ValidationError("请输入邮箱格式用户名")

return username

def validate(self, attrs):
"""
attrs是字段validate之后返回的总的dict
注册时提交的username字段是邮箱,这里把email字段也加上
"""
attrs["email"] = attrs["username"]
return attrs

# 此功能这里注销,由信号量或viewset中来实现
# def create(self, validated_data):
# """
# 重载create函数,使密码入库
# :param validated_data:
# :return:
# """
# user = super(UserRegSerializer,self).create(validated_data=validated_data)
# user.set_password(validated_data["password"])
# user.save()
# return user

class Meta:
model = User
fields = ("username","password")

class UserDetailSerializer(serializers.ModelSerializer):
"""
用户详情序列化类
"""

class Meta:
model = User
fields = ("id","username","gender","birthday","mobile","email")

read_only 表明该字段仅用于序列化输出,默认False
write_only 表明该字段仅用于反序列化输入,默认False
使用DRF的 UniqueValidator 来实现用户名唯一验证。
并且在settings 中配置了邮箱格式的正则验证规则。
myangular/myangular/settings.py

1
REGEX_EMAIL = "^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$"

4、views中配置用户viewset

myangular/users/views.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
from rest_framework import mixins
from rest_framework import viewsets
from django.contrib.auth.base_user import make_password

from .serializers import UserRegSerializer, UserDetailSerializer
from django.contrib.auth import get_user_model
User = get_user_model()

class UserViewset(mixins.ListModelMixin, mixins.CreateModelMixin, mixins.UpdateModelMixin, mixins.RetrieveModelMixin,mixins.DestroyModelMixin, viewsets.GenericViewSet):
"""
用户
"""
queryset = User.objects.all()

def get_serializer_class(self):
"""
根据self.action来判断调用的序列类,action是mixins提供的功能
注册动作使用UserRegSerializer
其他动作使用UserDetailSerializer
"""
if self.action == 'create':
return UserRegSerializer
else:
return UserDetailSerializer

def perform_create(self, serializer):
"""
重写CreateModelMixin的perform_create方法,对用户提交的密码加密处理,然后在保存
调用django自带的make_password方法来处理
"""
password = make_password(serializer.validated_data['password'])
serializer.validated_data["password"] = password
return serializer.save()

注意:

用户密码加密是调用diango的 make_password 方法来实现。
也可以在 serializerdjango信号量 中来实现这一步骤,但这两种方式的操作步骤是:用户实例保存入库后对密码进行修改,会导致进行两次 post_save 操作,导致我们后面的用户操作日志功能产生影响,因此在viewset中来实现。
serializer信号量 的注释代码中可以看到实现方法。

5、使用JWT实现用户身份验证

我们的所有功能模块都需要用户登录验证之后才能进行操作,用户的登录在”前后端分离的开发中”和我们之前”基于模板 template“ 进行开发的是有一定区别的。
一般情况用户登录的 sessioncookie 在模板中是有用的。但在前后端分离的项目中我们需要使用 token
DRF自带token存在问题问题:

DRFtoken 是保存在我们的服务器数据库当中,但是我们如果是一个分布式的系统,两套系统想用一个 token 的话就会出现问题,还需要额外做 token 同步操作。
还有一个非常严重的问题,DRFtoken 没有过期时间,永久有效。

基于以上原因我们这里使用 Json Web Token (JWT) 来进行用户身份验证。
JWT原理介绍请参考:http://lion1ou.win/2017/01/18/

setting 中配置JWT的过期时间和自定义前缀。
myangular/myangular/settings.py

1
2
3
4
JWT_AUTH = {
'JWT_EXPIRATION_DELTA': datetime.timedelta(days=7),
'JWT_AUTH_HEADER_PREFIX': 'JWT',
}

viewset 中进行增加下面内容。
myangular/users/views.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
from rest_framework import mixins
from rest_framework import viewsets
from rest_framework.response import Response
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
from rest_framework import permissions
from rest_framework import authentication
from rest_framework_jwt.serializers import jwt_encode_handler,jwt_payload_handler

class UserViewset(mixins.ListModelMixin, mixins.CreateModelMixin, mixins.UpdateModelMixin, mixins.RetrieveModelMixin,mixins.DestroyModelMixin, viewsets.GenericViewSet):
"""
用户
"""
# 这里身份校验还加上了DRF自带的Session验证,主要是方便我们使用DRF自带WebAPI界面进行测试
authentication_classes = (JSONWebTokenAuthentication, authentication.SessionAuthentication)

# permission_classes 权限过滤掉未认证通过的用户
def get_permissions(self):
"""
除了注册用户外,其他动作都需要权限验证:
"""
if self.action == "create":
return []
else:
return [permissions.IsAuthenticated()]

def create(self, request, *args, **kwargs):
"""
重载CreateModelMixin的create方法,用户注册完成后返回jwt-token
"""
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = self.perform_create(serializer)

re_dict = serializer.data
# 通过查看DRF源码得知生成token的两个重要步骤,一payload,二encode
# 使用user对象来生成jwt token,并返回给客户端
payload = jwt_payload_handler(user)
re_dict["token"] = jwt_encode_handler(payload)
re_dict["name"] = user.name if user.name else user.username

headers = self.get_success_headers(serializer.data)
return Response(re_dict, status=status.HTTP_201_CREATED, headers=headers)

authentication_classes 与 permission_classes 的区别:
authentication 用来识别用户的身份
permission 用来判断用户是否有权限进行某项操作。

6、用户登出

登出功能非常好做,因为 jwttoken 并不是保存在服务器端的,客户端自己清空 token 就行了。

7、配置Url路由

在url中配置路由
myangular/myangular/urls.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from django.conf.urls import url, include
from django.contrib import admin
from rest_framework.documentation import include_docs_urls
from rest_framework.routers import DefaultRouter
from rest_framework.authtoken import views
from rest_framework_jwt.views import obtain_jwt_token
from users.views import UserViewset

router = DefaultRouter()
router.register(r'users',UserViewset,base_name='users')

urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')),
url(r'docs/', include_docs_urls(title="API文档")),
url(r'^', include(router.urls)),
# JWT登录验证
url(r'^login/', obtain_jwt_token),
# drf 自带的token授权登录,获取token需要向该地址post数据
url(r'^api-token-auth/',views.obtain_auth_token)
]

二、设备管理模块

此模块包括 pcserver 两个子模块。
设备管理的增删改查及权限和 user 模块的实现方法相同,这里不重点介绍。
此外pc子模块还额外实现了搜索排序分页功能。

1、搜索

通过DRF为我们提供的过滤功能简单快速的完成搜索。
myangular/equipment/views.py

1
2
3
4
5
from rest_framework import filters

class PcViewset(mixins.ListModelMixin, mixins.RetrieveModelMixin, mixins.CreateModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin, viewsets.GenericViewSet):
filter_backends = (filters.SearchFilter,)
search_fields = ('pcuser', 'ip')

代码就是这么简洁,比原生django方便太多了。
搜索字段包含 pcuserip
客户端请求方式为: http://localhost:8000/pc/?search=10

2、排序

还是通过DRF的过滤器功能来实现
myangular/equipment/views.py

1
2
3
4
5
from rest_framework import filters

class PcViewset(mixins.ListModelMixin, mixins.RetrieveModelMixin, mixins.CreateModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin, viewsets.GenericViewSet):
filter_backends = (filters.OrderingFilter,)
ordering_fields = ('pcuser','ip')

可根据 pcuserip 进行排序显示。
客户端可按照实际需求自定义请求,请求示例:
按照pcuser升序显示: http://localhost:8000/pc/?ordering=pcuser
按照ip降序显示: http://localhost:8000/pc/?ordering=-ip

3、分页

myangular/equipment/views.py

1
2
3
4
5
6
7
8
9
10
11
from rest_framework.pagination import PageNumberPagination

# 自定义一个分页类
class Pcpagination(PageNumberPagination):
page_size = 10 # 默认每页显示10条
page_size_query_param = 'page_size' # 自定义获取多少条内容
page_query_param = 'page' # url参数名
max_page_size = 100 # 最大页码

class PcViewset(mixins.ListModelMixin, mixins.RetrieveModelMixin, mixins.CreateModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin, viewsets.GenericViewSet):
pagination_class = Pcpagination

客气端请求默认每页显示10条数据
请求地址为: http://localhost:8000/pc/?page=2
也可以自定义请求多条数据: http://localhost:8000/pc/?page_size=100
搜索、排序、分页的组合请求url为: http://localhost:8000/pc/?ordering=ip&page=2&search=1

4、PC自动获取mac功能

添加或修改pc后,根据 ip 自动获取 mac 并入库。
myangular/equipment/views.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from utils.getmac import IP2MAC
class PcViewset(mixins.ListModelMixin, mixins.RetrieveModelMixin, mixins.CreateModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin, viewsets.GenericViewSet):

def perform_create(self, serializer):
"""
重载CreateModelMixin的 perform_create 方法,使通过ip自动获取到mac地址
"""
ip = serializer.validated_data['ip']
g = IP2MAC()
mac = g.getMac(ip)
serializer.validated_data['mac'] = mac
serializer.save()

def perform_update(self, serializer):
"""
重载UpdateModelMixin perform_create 方法,使通过ip自动获取到mac地址
"""
ip = serializer.validated_data['ip']
g = IP2MAC()
mac = g.getMac(ip)
serializer.validated_data['mac'] = mac
serializer.save()

获取mac的脚本在 util 中。
myangular/utils/getmac.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import os
import platform
import re

class IP2MAC:
def __init__(self):
self.patt_mac = re.compile('([a-f0-9]{2}[-:]){5}[a-f0-9]{2}', re.I)

def getMac(self, ip):
sysstr = platform.system()
if sysstr == 'Windows':
macaddr = self.__forWin(ip)
elif sysstr == 'Linux':
macaddr = self.__forLinux(ip)
else:
macaddr = None
return macaddr or '00-00-00-00-00-00'

def __forWin(self, ip):
os.popen('ping -n 1 -w 500 {} > nul'.format(ip))
macaddr = os.popen('arp -a {}'.format(ip))
macaddr = self.patt_mac.search(macaddr.read())
if macaddr:
macaddr = macaddr.group()
else:
macaddr = None
return macaddr

def __forLinux(self, ip):
os.popen('ping -nq -c 1 -W 500 {} > /dev/null'.format(ip))
result = os.popen('arp -an {}'.format(ip))
result = self.patt_mac.search(result.read())
return result.group() if result else None

5、Server获取配置功能

此功能的逻辑步骤:
1、添加server后,通过 paramiko 远程连接并上传脚本到Server上。

myangular/equipment/views.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
from utils.connectserver import connect_server

class ServerViewset(mixins.ListModelMixin,mixins.RetrieveModelMixin,mixins.CreateModelMixin,mixins.UpdateModelMixin,viewsets.GenericViewSet):

def perform_create(self, serializer):
"""
重载CreateModelMixin的 perform_create 方法,通过paramiko获取服务器信息并入库
"""
ip = serializer.validated_data['ip']
port = serializer.validated_data['port']
username = serializer.validated_data['username']
password = serializer.validated_data['password']

# 连接远程主机
connect = connect_server(ip,int(port),username,password)
if connect['status'] == 'success':
trans = connect['data']
# 用于文件上传和下载的sftp服务
sftp = paramiko.SFTPClient.from_transport(trans)
# 远程执行命令服务
ssh = paramiko.SSHClient()
ssh._transport = trans
# 创建目录
stdin,stdout,stderr = ssh.exec_command('mkdir CMDBClient')
time.sleep(1)
# 上传文件
sftp.put('utils/sftpDir/getData.py','CMDBClient/getData.py')
sftp.put('utils/sftpDir/sendData.py', 'CMDBClient/sendData.py')
sftp.put('utils/sftpDir/getJwt.py', 'CMDBClient/getJwt.py')
sftp.put('utils/sftpDir/main.py', 'CMDBClient/main.py')
# 这里不自动调用脚本,改为手动调用
# stdin,stdout,stderr = ssh.exec_command('python CMDBClient/main.py')
trans.close()
# 连接成功状态记录到数据库
status = True
else:
status = False
serializer.validated_data['status'] = status
serializer.save()

2、客户端通过访问 ServerViewsetmixins.RetrieveModelMixin 方法触发远程连接到 Server 并调用脚本。
脚本获取服务器配置并发送到 ServerViewsetmixins.UpdateModelMixin 来实现配置更新。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from utils.connectserver import connect_server
class ServerViewset(mixins.ListModelMixin,mixins.RetrieveModelMixin,mixins.CreateModelMixin,mixins.UpdateModelMixin,viewsets.GenericViewSet):

def retrieve(self, request, *args, **kwargs):
"""
重载RetrieveModelMixin的retrieve方法,触发远程调用server脚本
"""
instance = self.get_object()
serializer = self.get_serializer(instance)
ip = instance.ip
port = instance.port
username = instance.username
password = instance.password
# 连接远程主机
connect = connect_server(ip,int(port),username,password)
if connect['status'] == 'success':
trans = connect['data']
# 远程执行命令服务
ssh = paramiko.SSHClient()
ssh._transport = trans
# 调用客户端脚本,脚本获取配置后通过patch方法更新server配置信息
stdin,stdout,stderr = ssh.exec_command('python CMDBClient/main.py')
trans.close()
return Response(serializer.data)

connect_server 功能考虑到后期其他模块可能会用,固放在 utils 中。
myangular/utils/connectserver.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import paramiko
def connect_server(ip,port,user,password):
'''
通过paramiko检测服务器是否可以连通,是的话返回连接对象
:return:返回连接对象
'''
result = {'status':'error','data':''}
try:
trans = paramiko.Transport(ip,port)
trans.connect(username = user, password = password)
except Exception as e:
result['data'] = str(e)
else:
result['status'] = 'success'
result['data'] = trans
finally:
return result

获取配置脚本由于篇幅太大这里不展示。

三、用户操作日志模块

用户操作日志这里采用 Django 的信号机制来实现。
Django 提供一个“信号分发器”,允许解耦的应用在框架的其它地方发生操作时会被通知到。 简单来说,信号允许特定的 sender 通知一组 receiver 某些操作已经发生。 这在多处代码和同一事件有关联的情况下很有用。

1、添加用户操作日志

建模
myangular/users.model.py

1
2
3
4
5
6
7
class UserLogs(models.Model):
"""
用户操作日志
"""
username = models.CharField(max_length=30, verbose_name='用户名')
action = models.CharField(max_length=30, verbose_name='动作')
action_time = models.DateTimeField(default=datetime.datetime.now,verbose_name='操作时间')

定义序列化类
myangular/users/serializer.py

1
2
3
4
5
6
7
8
from .models import UserLogs
class UserLogsSerializer(serializers.ModelSerializer):
"""
用户操作日志
"""
class Meta:
model = UserLogs
fields = "__all__"

添加viewset
myangular/users/views.py

1
2
3
4
5
6
7
8
9
10
11
12
from rest_framework import filters
from .models import UserLogs
from .serializers import UserLogsSerializer

class UserlogsViewset(mixins.ListModelMixin,viewsets.GenericViewSet):
authentication_classes = (JSONWebTokenAuthentication,authentication.SessionAuthentication)
permission_classes = (permissions.IsAuthenticated,)
serializer_class = UserLogsSerializer
queryset = UserLogs.objects.all()
filter_backends = (filters.SearchFilter,filters.OrderingFilter)
search_fields = ('username', 'action')
ordering_fields = ('action_time',)

添加路由
myangular/myangular/urls.py

1
2
3
4
5
from rest_framework.routers import DefaultRouter
from users.views import UserlogsViewset

router = DefaultRouter()
router.register(r'logs', UserlogsViewset, base_name='logs')

2、登录日志

由于我们自定义了登录验证,因此无法收到 django 默认的 user_logged_in() 信号。
这里我们自定义信号,并在密码验证通过后发送信号。
myangular/myangular/views.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from django.dispatch import Signal
import time

#定义信号,在用户登录成功后发出
login_done = Signal(providing_args=['name','content','time'])
class CustomBackend(ModelBackend):
"""
自定义用户验证
"""
def authenticate(self, request, username=None, password=None, **kwargs):
try:
user = User.objects.get(Q(username=username)|Q(email=username))
if user.check_password(password):
# 登录验证成功后发出上面定义的信号
login_done.send(CustomBackend, name=user.username, content='登录成功',time=time.strftime("%Y-%m-%d %H:%M:%S"))
return user
except Exception as e:
return None

接收信号,自定义 signals.py 文件
myangular/users/signals.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from django.dispatch import receiver
from users.views import login_done
from users.models import UserLogs
from users.views import CustomBackend
import time

def createlogs(username,action):
action_time = time.strftime("%Y-%m-%d %H:%M:%S")
try:
UserLogs.objects.create(username= username, action = action)
except Exception as e:
print(str(e))
else:
print(username + action + "时间" + action_time)

# 用户登录成功信号量
@receiver(login_done, sender=CustomBackend)
def sign(sender, **kwargs):
createlogs(kwargs['name'],kwargs['content'])

这里有个BUG:由于采用扩展 AbstractUser 定义用户模型,每次用户登录的时候都会更新用户表的 last_login 字段,生成一个 post_save 信号,所以每次登录成功都会多出一个编辑用户的log,此BUG后面解决。

3、登出日志

由于 JWTtoken 并不是保存在服务器端的,在登出操作的时候客户端自己清空 token 就行了,服务器不用做任何操作。
但我我们服务器还要记录登出log,我的实现方法是在视图中定义一个 UserlogoutViewset 类,客户端登出的时候请求这个viewset的 RetrieveModelMixin 方法, 服务器收到此请求后发送登出信号。
客户端请求必须携带userid 和 token。
定义序列化类,成功只返回username。
myangular/users/serializer.py

1
2
3
4
5
6
7
class UserlogoutSerializer(serializers.ModelSerializer):
"""
用户登出
"""
class Meta:
model = User
fields = ("username",)

定义viewset,定义登出信号,并在收到请求后发出信号。
myangular/users/views.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from django.dispatch import Signal
import time

# 定义登出信号,并在UserlogoutViewset 里发出信号
logout_done = Signal(providing_args=['name','content','time'])

class UserlogoutViewset(mixins.RetrieveModelMixin,viewsets.GenericViewSet):
authentication_classes = (JSONWebTokenAuthentication,authentication.SessionAuthentication)
permission_classes = (permissions.IsAuthenticated,)

serializer_class = UserlogoutSerializer

# 用户退出只能携带自己的ID
def get_queryset(self):
return User.objects.filter(username=self.request.user.username)

def retrieve(self, request, *args, **kwargs):
"""
重载RetrieveModelMixin的retrieve方法,使发出退出信号量
"""
instance = self.get_object()
serializer = self.get_serializer(instance)
logout_done.send(UserlogoutViewset, name= instance.username, content="退出成功", time=time.strftime("%Y-%m-%d %H:%M:%S"))
return Response(serializer.data)

添加路由
myangular/myangular/urls.py

1
2
3
4
5
from rest_framework.routers import DefaultRouter
from users.views import UserlogoutViewset

router = DefaultRouter()
router.register(r'logout', UserlogoutViewset, base_name='logout')

signals.py 中接收信号。
myangular/users/signals.py

1
2
3
4
5
6
7
8
9
10
from django.dispatch import receiver
from users.views import logout_done
from users.models import UserLogs
from users.views import UserlogoutViewset
import time

# 用户退出成功信号量
@receiver(logout_done, sender=UserlogoutViewset)
def sigout(sender,**kwargs):
createlogs(kwargs['name'], kwargs['content'])

4、用户管理日志

这个模块的实现方式就相对简单,直接接收 django內建信号 来实现,主要用到 post_save和post_delete 两个信号。
这里引入了一个第三方模块 CrequestMiddleware,使用它来获取当前操作用户。
CrequestMiddleware官网地址:https://pypi.org/project/django-crequest/
myangular/users/signals.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
from django.dispatch import receiver
from django.db.models.signals import post_save, post_delete
from crequest.middleware import CrequestMiddleware
from users.models import UserProfile

# 添加、编辑用户成功
@receiver(post_save,sender=UserProfile)
def adduser(sender,instance=None,created=False,**kwargs):
current_request = CrequestMiddleware.get_request()
# 如果create为True就是新建用户,否则是编辑用户
if created:
# 用户密码加密此处注销,由viewset来实现
# password = instance.password
# instance.set_password(password)
# instance.save()

action = "添加用户:" + instance.username
createlogs(username=current_request.user.username,action=action)
else:
action = "编辑用户:" + instance.username
createlogs(username=current_request.user.username, action=action)

# 删除用户成功
@receiver(post_delete, sender=UserProfile)
def deluser(sender,instance=None,**kwargs):
current_request = CrequestMiddleware.get_request()
action = "删除用户:" + instance.username
createlogs(username=current_request.user.username,action=action)

5、设备管理日志

实现方式同用户管理模块,Server 没有删除操作。
myangular/users/signals.py

1
2
3
4
5
6
7
8
9
10
# 添加编辑Server
@receiver(post_save,sender=Server)
def addpc(sender, instance=None,created=False,**kwargs):
current_request = CrequestMiddleware.get_request()
if created:
action = '添加Server:' + instance.ip
createlogs(username=current_request.user.username,action=action)
else:
action = '更新Server:' + instance.ip
createlogs(username=current_request.user.username,action=action)

此演示Demo github地址为:https://github.com/zhanghonged/DRFdemo.git