django学习记录4)

介绍我在使用django框架中用到几个非常便捷的小功能,包括

  • django-restframework
  • 身份认证类authentication_classes
  • 权限检查类permissoin_classes
  • 自带的日志收集LOGGING,
  • 中间件middleware

django-restframework

REST框架是用于构建Web API的功能强大且灵活的工具包,在官方文档中已经介绍了如何安装和配置,以及这篇博客也介绍的比较详细,引用博客中的一段:

 使用基于类的视图的一大优势在于它允许我们轻松地编写可重用的代码。到目前为止,我们一直使用的增删改查等操作,对于我们创建的任何模型支持的API视图都非常相似的。这些常见行为在REST框架的Mixin是类中实现起来就非常简单。
Mixins类中重写了增删改查查五个最常见的视图类的处理方法,根据我们的需要,我们可以选择性的继承其中的处理方法,从而只关系我们需要什么,就继承什么了。

  • mixins.CreateModelMixin
  • mixins.mixins.ListModelMixin
  • mixins.RetrieveModelMixin
  • mixins.UpdateModelMixin
  • mixins.DestroyModelMixin
"""
Basic building blocks for generic class based views.

We don't bind behaviour to http method handlers yet,
which allows mixin classes to be composed in interesting ways.
"""
from rest_framework import status
from rest_framework.settings import api_settings

from rest_framework.response import Response


class JSONResponse(Response):
    def __init__(self, data=None, status=None,
                 template_name=None, headers=None,
                 exception=False, content_type=None,
                 code=0, msg="success"):
        super().__init__(data, status, template_name, headers, exception, content_type)
        self.data = {"code": code, "msg": msg, "data": data}

class CreateModelMixin:
    """
    Create a model instance.
    """
    def create(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        self.perform_create(serializer)
        headers = self.get_success_headers(serializer.data)
        return JSONResponse(serializer.data, status=status.HTTP_201_CREATED, headers=headers)

    def perform_create(self, serializer):
        serializer.save()

    def get_success_headers(self, data):
        try:
            return {'Location': str(data[api_settings.URL_FIELD_NAME])}
        except (TypeError, KeyError):
            return {}


class ListModelMixin:
    """
    List a queryset.
    """
    def list(self, request, *args, **kwargs):
        queryset = self.filter_queryset(self.get_queryset())

        page = self.paginate_queryset(queryset)
        if page is not None:
            serializer = self.get_serializer(page, many=True)
            return self.get_paginated_response(serializer.data)

        serializer = self.get_serializer(queryset, many=True)
        return JSONResponse(serializer.data)


class RetrieveModelMixin:
    """
    Retrieve a model instance.
    """
    def retrieve(self, request, *args, **kwargs):
        instance = self.get_object()
        serializer = self.get_serializer(instance)
        return JSONResponse(serializer.data)


class UpdateModelMixin:
    """
    Update a model instance.
    """
    def update(self, request, *args, **kwargs):
        partial = kwargs.pop('partial', False)
        instance = self.get_object()
        serializer = self.get_serializer(instance, data=request.data, partial=partial)
        serializer.is_valid(raise_exception=True)
        self.perform_update(serializer)

        if getattr(instance, '_prefetched_objects_cache', None):
            # If 'prefetch_related' has been applied to a queryset, we need to
            # forcibly invalidate the prefetch cache on the instance.
            instance._prefetched_objects_cache = {}

        return JSONResponse(serializer.data)

    def perform_update(self, serializer):
        serializer.save()

    def partial_update(self, request, *args, **kwargs):
        kwargs['partial'] = True
        return self.update(request, *args, **kwargs)


class DestroyModelMixin:
    """
    Destroy a model instance.
    """
    def destroy(self, request, *args, **kwargs):
        instance = self.get_object()
        self.perform_destroy(instance)
        return JSONResponse(status=status.HTTP_200_OK)

    def perform_destroy(self, instance):
        instance.delete()

接着 你就可以根据需求(增删改查)不同,继承不同的类,以此定义你所需要的Model类,列举示例:

#增删改查model
class ModelViewSet(mixins.CreateModelMixin,
                   mixins.RetrieveModelMixin,
                   mixins.UpdateModelMixin,
                   mixins.DestroyModelMixin,
                   mixins.ListModelMixin,
                   GenericViewSet):
    """
    A viewset that provides default `create()`, `retrieve()`, `update()`,
    `partial_update()`, `destroy()` and `list()` actions.
    """
    pass

#只读model
class ReadOnlyModelViewSet(mixins.RetrieveModelMixin,
                           mixins.ListModelMixin,
                           GenericViewSet):
    """
    A viewset that provides default `list()` and `retrieve()` actions.
    """
    pass

 在views中进行使用示例,只需继承你所定义的model类,就已经实现了增删改查的接口,也可以进行重写,示例:

class TaskViewSet(ModelViewSet):
    serializer_class = TaskSerializer
    pagination_class = JSONPagination
    permission_classes = (IsAuthenticated,)
    authentication_classes = (BasicAuthentication,
                              JWTAuthentication,
                              TokenAuthentication,
                              SessionAuthentication,
                              )
    search_fields = ('id', 'uid', "status", "user__username")  # foreign_key__related_fieldname
    filter_fields = ('status', 'id')

 进行路由注册:

#urls.py中添加
router.register(r'task', SubmitTaskViewSet, 'task')

既然增删改查接口都已经实现了,那么前端如果对这些接口进行调用呢,进行粗略的总结如下:

 方法URL示例
post

http://localhost:8000/api/v1/task/

deletehttp://localhost:8000/api/v1/task/1/
patchhttp://localhost:8000/api/v1/task/1/
列表查询gethttp://localhost:8000/api/v1/task/
单个查询gethttp://localhost:8000/api/v1/task/1/

指定单条记录进行删改时,后跟主键,在本例中是ID,而在重写方法中,可通过pk参数获取这个ID,例如:

class TaskViewSet(ModelViewSet):
    def update(self, request, *args, **kwargs):
        status = data.get('status', None)
        pk = self.kwargs.get('pk', '')
        res = task.objects.filter(id=pk).update(status=status)
        return  res

 小小坑点:在viewset中 返回对象必须是model的QuerySet类型,需要注意。

身份认证类authentication_classes/权限检查类permissoin_classes

这两个类都来自于rest_framwork,我在最初总是将二者搞混,分不清具体作用,

在访问接口时需要进行认证(包括用户名密码、session、token等认证方法),authentication_classes,就是用于用户的身份校验

  • BasicAuthentication,基于用户名密码认证方式
  • SessionAuthentication,基于Session认证方式
  • TokenAuthentication,基于令牌认证方式(一个用户绑定一个令牌,每次请求在请求头中加上

也可再继承自定义身份认证的类,用于判断该用户是否有操作这个接口的权限,例如我的服务提供给T服务的后端进行调用,我需要给T提供一个固定的token,可以这样写:

首先生成一个token,为T服务创建一个用户

class TAuthentication(TokenAuthentication):
    t_token = 'fds87fd4f67fda2wrfd5f'

    # token 手动插入到authotoken_token表中

    def authenticate(self, request):
        if 'HTTP_AUTHORIZATION' not in request.META and \
                request.META.get("HTTP_SERVER_TOKEN") == self.t_token:
            user = User.objects.get(username=t_server)
            return user, None
            
        else:
            return super(SocAuthentication, self).authenticate(request)

在调用时,头部中加入"Server-Token":"fds87fd4f67fda2wrfd5f",即可进行认证。

 

在身份认证之后,知道你已经是我们库的用户,但是不一定每一个用户都有调用此接口的权限,权限检查类permissoin_classes,用于控制接口的操作权限,权限可通过修改auth_user/auth_group/auth_user_groups/auth_group_permissions/auth_permission等多个表,达到控制多种权限的目的。rest已经提供以下几个基本的权限控制类:

#  来自rest_framework/permissions.py
class AllowAny(BasePermission):
    """
    Allow any access.
    This isn't strictly required, since you could use an empty
    permission_classes list, but it's useful because it makes the intention
    more explicit.
    """

    def has_permission(self, request, view):
        return True


class IsAuthenticated(BasePermission):
    """
    Allows access only to authenticated users.
    """

    def has_permission(self, request, view):
        return bool(request.user and request.user.is_authenticated)


class IsAdminUser(BasePermission):
    """
    Allows access only to admin users.
    """

    def has_permission(self, request, view):
        return bool(request.user and request.user.is_staff)

假如我现在这个接口只希望某个用户组内的成员进行访问,可以这样进行定义

class TaskPermission(permissions.IsAuthenticated):
    def has_permission(self, request, view):
        if request.user.username == "admin":
            return True
        task_group = Group.objects.get(name='task.group').user_set.all()
        if request.user in task_group:
            return True
        return False

自带的日志收集LOGGING

参考了这篇博客,文章中已经记录的很全,我就不做赘述,这个对于我这种懒人真的好方便,可以通过LOGGING定义日志的格式,记录错误、警告等,还可以记录数据库操作日志,将日志输出到文件,比如定义:

# 部分代码
LOGGING = {
    'handlers': {
        'file': {
            'class': 'logging.FileHandler',
            'filename': '/home/work/log/db_operate.log',  #这是将普通日志写入到日志文件中的方法,
            'formatter': 'db'
        },
    }, 
    "loggers": {
        "django.db.backends": {
            "handlers": ["file"],
            "propagate": True,
            "level": "DEBUG",
        },
    }
}

记录的日志文件格式如下所示:

[DEBUG django.db.backends] 2021-05-26 10:50:29 +0800 :
  (0.003) SELECT `django_migrations`.`id`, `django_migrations`.`app`, `django_migrations`.`name`, `django_migrations`.`applied` FROM `django_migrations`; args=()

我说下项目中最后还是没有用这个方法的原因,

1、这种方式会记录全部数据库操作,包括增删改查,而部分表的查询操作太过频繁,导致日志文件占用内存爆炸

2、不方便进行日志的查询,项目启动时加载setting,不方便修改日志的文件名,从而导致无法将日志按日期记录到多个文件

3、针对的也是所有的表,这种即完善又不便,不能只记录部分表的操作(我还试图使用cron去重定向日志文件名)

4、无法与接口调用相结合,换言之 无法知道是哪位用户的操作这条记录。

以上种种不便,我开始使用中间件记录,两种方法也许并不能混为一谈,毕竟中间件是针对接口调用进行记录的,很多在后端使用cron方法操作的数据库日志,无法体现。而且日志中也不是直接对sql语句进行记录,而是接口请求的request、response等。

我还是根据项目需求 选择了使用中间件进行记录。通过记录request的user/body/path等,可以知道什么时间点,谁调用了哪个接口,接口是我写的呀,从而我就知道这个用户进行了什么操作。

中间件middleware

中间件是在接口到达你定义的view方法之前,已经方法返回response给用户之前做一些处理的工具,参考了这篇博客1,博客2引入文中以下内容:

传统方式自定义中间件其实就是在编写五大钩子函数:

  • process_request(self,request)
  • process_response(self, request, response)
  • process_view(self, request, view_func, view_args, view_kwargs)
  • process_exception(self, request, exception)
  • process_template_response(self,request,response)

可以实现其中的任意一个或多个!

钩子函数执行时机执行顺序返回值
process_request请求刚到来,执行视图之前配置列表的正序None或者HttpResponse对象
process_response视图执行完毕,返回响应时逆序HttpResponse对象
process_viewprocess_request之后,路由转发到视图,执行视图之前正序None或者HttpResponse对象
process_exception视图执行中发生异常时逆序None或者HttpResponse对象
process_template_response视图刚执行完毕,process_response之前逆序实现了render方法的响应对象

process_request(request)

只有一个参数,也就是request请求内容,和视图函数中的request是一样的。所有的中间件都是同样的request,不会发生变化。它的返回值可以是None也可以是HttpResponse对象。返回None的话,表示一切正常,继续走流程,交给下一个中间件处理。返回HttpResponse对象,则发生短路,不继续执行后面的中间件,也不执行视图函数,而将响应内容返回给浏览器。

process_response(request, response)

有两个参数,request和response。request是请求内容,response是视图函数返回的HttpResponse对象。该方法的返回值必须是一个HttpResponse对象,不能是None。

process_response()方法在视图函数执行完毕之后执行,并且按配置顺序的逆序执行。

下面介绍我自定义的中间件,实现对部分接口进行定义,同时记录request和response

from django.utils.deprecation import MiddlewareMixin
import datetime
import json


class DBLogMiddleware(MiddlewareMixin):
    _initial_http_body = None

    def process_request(self, request):
        self._initial_http_body = request.body
        

    def process_response(self, request, response):
        if request.method == 'GET':
            return response
        task_router = ['/api/v1/task', ]
        db_log_file = "/home/work/log/submit-operate-" + datetime.date.today().strftime('%y-%m-%d')
        for router in task_router:
            if router in request.path:
                with open(db_log_file, "w+") as f:
                    content = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + "----" + request.user.username + \
                              "----" + request.method + "----" + request.path + "----" + str(self._initial_http_body) + "\n\n"
                    f.write(content)
                pass
        return response

 小坑点:在process_request中,因为是在请求刚到来的时候,还未进行用户的身份认证,所以无法从request.user中得知用户的身份,

小坑点2:在process_response中,试图修改request.body出错,You cannot access body after reading from request‘s data stream,因为此时body已经设为不可读,所以我将body在request到达时就赋予一个私有成员,来达到修改和访问的目的。

django就介绍这么多,项目也结束了,如果以后再发现什么新功能,会进行补充~

感谢前几篇博客为我点赞的大佬,本麻瓜备受鼓舞~

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值