Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”.


An overview of the

Unifying exception handling across your project prevents uncaught exceptions from appearing in your code. This article shows you how to handle unified exceptions in a Django project, combined with a status code enumeration class to log project exception information.


Django has uniform exception handling

In a Django project, you can customize the middleware class to inherit the MiddlewareMixin middleware class from django.middleware.com and override the exception handling logic of the process_exception method. Global exception handling can then be done by registering in the middleware under the project configuration.


I wrote the middleware in the middlewares.py module under the utils package defined by the project.

# middlewares.py

#! /usr/bin/python3
# -*- coding: utf-8 -*-
# @Author: Hui
# @desc: {project middleware module}
# @Date: 2021/09/24 8:18
from django.middleware.common import MiddlewareMixin



class ExceptionMiddleware(MiddlewareMixin) :
    """ Unified exception Handling middleware ""

    def process_exception(self, request, exception) :
        Param Request: request object :param exception: return: """
        # exception handling
        print(exception)
        return None
Copy the code


Here for the time being, simply outputting the exception to simulate the exception processing. Finally, don’t forget to register the middleware in the configuration file. The default configuration file for a Django project is settings.py. I just put the configuration file in the Settings package and changed the file name.


The middlewareprocess_exceptionMethods to introduce

The process_exception method is executed only if an exception occurs in the view function. The return value of this method can be either None or an HttpResponse object.

  • The return value isNoneThe page displays a 500 status code error and the view function will not execute.
  • The return value isHttpResponseObject, is the corresponding response information, the page will not report an error.


Methods in middleware

methods role
process_request(self,request) Execute before the view function
process_view(self, request, view_func, view_args, view_kwargs) Before the view function,process_requestMethod is executed after
process_exception(self, request, exception) Execute only if an exception occurs in the view function
process_response(self, request, response) The view function is then executed


The following diagram provides a good illustration of the entire Django process logic

More middleware details can be found in the Official Django documentation.


Unified specific design of exception handling

Combined with the custom exception and status code enumeration classes, the exception log information and business logic processing.


Custom exception module

# exceptions.py

#! /usr/bin/python3
# -*- coding: utf-8 -*-
# @Author: Hui
# @desc: {project exception module}
# @Date: 2021/09/24 8:14

class CommonException(Exception) :
    """ Public exception class """

    def __init__(self, enum_cls) :
        self.code = enum_cls.code
        self.errmsg = enum_cls.errmsg
        self.enum_cls = enum_cls	State code enumeration class
        super().__init__()


class BusinessException(CommonException) :
    """ Business exception class ""
    pass


class APIException(CommonException) :
    """ Interface exception class ""
    pass

Copy the code


Custom state code enumeration class

# enums.py

#! /usr/bin/python3
# -*- coding: utf-8 -*-
# @Author: Hui
# @desc: {item enumeration class module}
# @Date: 2021/09/23 23:37

from enum import Enum


class StatusCodeEnum(Enum) :
    State code enumeration class

    OK = (0.'success')
    ERROR = (-1.'wrong')
    SERVER_ERR = (500.'Server exception')

    IMAGE_CODE_ERR = (4001.'Graphic captcha error')
    THROTTLING_ERR = (4002.'Visited too often')
    NECESSARY_PARAM_ERR = (4003.'Mandatory parameters missing')
    USER_ERR = (4004.'Wrong username')
    PWD_ERR = (4005.'Password error')
    CPWD_ERR = (4006.'Inconsistent passwords')
    MOBILE_ERR = (4007.'Wrong phone number')
    SMS_CODE_ERR = (4008.'SMS verification code error')
    ALLOW_ERR = (4009.'Protocol not selected')
    SESSION_ERR = (4010.'User not logged in')
    REGISTER_FAILED_ERR = (4011.'Registration failed')

    DB_ERR = (5000.'Database error')
    EMAIL_ERR = (5001.'Email error')
    TEL_ERR = (5002.'Landline error')
    NODATA_ERR = (5003.'No data')
    NEW_PWD_ERR = (5004.'New password error')
    OPENID_ERR = (5005.'Invalid OpenID')
    PARAM_ERR = (5006.'Parameter error')
    STOCK_ERR = (5007.'Understock')

    @property
    def code(self) :
        """ Get the status code """
        return self.value[0]

    @property
    def errmsg(self) :
        """ Obtain status code information ""
        return self.value[1]

Copy the code


  • Custom exception classes are used to distinguish system exceptions from business exceptions for separate processing.

  • The status code enumeration is used to record the corresponding exception information.

The design of state code enumeration classes can be seen using Python enumeration classes to design state code information


Encapsulation of unified results for response information

Unified data and exception information between the front and back ends.

# result.py

#! /usr/bin/python3
# -*- coding: utf-8 -*-
# @Author: Hui
# @desc: {project information return result module}
# @Date: 2021/09/23 22:10
from .enums import StatusCodeEnum


class R(object) :
    "" unified Project Information return result class ""

    def __init__(self) :
        self.code = None
        self.errmsg = None
        self._data = dict(a)    @staticmethod
    def ok() :
        """ Organization successful response message :return: ""
        r = R()
        r.code = StatusCodeEnum.OK.code
        r.errmsg = StatusCodeEnum.OK.errmsg
        return r

    @staticmethod
    def error() :
        """ Organization error response message :return: ""
        r = R()
        r.code = StatusCodeEnum.ERROR.code
        r.errmsg = StatusCodeEnum.ERROR.errmsg
        return r

    @staticmethod
    def server_error() :
        """ Organization server error message :return: ""
        r = R()
        r.code = StatusCodeEnum.SERVER_ERR.code
        r.errmsg = StatusCodeEnum.SERVER_ERR.errmsg
        return r

    @staticmethod
    def set_result(enum) :
        Param enum: status enumeration class :return: """
        r = R()
        r.code = enum.code
        r.errmsg = enum.errmsg
        return r

    def data(self, key=None, obj=None) :
        "" data returned by unified backend ""

        if key:
            self._data[key] = obj

        context = {
            'code': self.code,
            'errmsg': self.errmsg,
            'data': self._data
        }
        return context

Copy the code


Improve unified exception handling logic

# middlewares.py

#! /usr/bin/python3
# -*- coding: utf-8 -*-
# @Author: Hui
# @desc: {project middleware module}
# @Date: 2021/09/24 8:18
import logging

from django.db import DatabaseError
from django.http.response import JsonResponse
from django.http import HttpResponseServerError
from django.middleware.common import MiddlewareMixin

from meiduo_mall.utils.result import R
from meiduo_mall.utils.enums import StatusCodeEnum
from meiduo_mall.utils.exceptions import BusinessException

logger = logging.getLogger('django')


class ExceptionMiddleware(MiddlewareMixin) :
    """ Unified exception Handling middleware ""

    def process_exception(self, request, exception) :
        Param Request: request object :param exception: return: """
        if isinstance(exception, BusinessException):
            # Service exception handling
            data = R.set_result(exception.enum_cls).data()
            return JsonResponse(data)

        elif isinstance(exception, DatabaseError):
            # database exception
            r = R.set_result(StatusCodeEnum.DB_ERR)
            logger.error(r.data(), exc_info=True)
            return HttpResponseServerError(StatusCodeEnum.SERVER_ERR.errmsg)

        elif isinstance(exception, Exception):
            Server exception handling
            r = R.server_error()
            logger.error(r.data(), exc_info=True)
            return HttpResponseServerError(r.errmsg)
        
        return None

Copy the code


Application scenarios

Certified check

Let’s look at a piece of business logic for the registration validation function


	def verify_params(self, request) :
        Param request: return response_ret ""
        # accept parameters
        self.username = request.POST.get('username')
        self.password = request.POST.get('password')
        self.confirm_pwd = request.POST.get('confirm_pwd')
        self.mobile = request.POST.get('mobile')
        self.allow = request.POST.get('allow')   

        if not all(all_args):
            # raise BusinessException(StatusCodeEnum.PARAM_ERR)
            response_ret = http.HttpResponseForbidden('Parameter error')
            return response_ret

        The username is 5-20 characters long
        if not re.match(R '^ [a zA - Z0-9 _] {5, 20}', self.username):
            response_ret = http.HttpResponseForbidden('Wrong username')
            return response_ret

        # Password 8-20 characters
        if not re.match(R '^ [a zA - Z0-9] {8, 20}', self.password):
            response_ret = http.HttpResponseForbidden('Bad password')
            return response_ret

        Password consistency
        ifself.password ! = self.confirm_pwd: response_ret = http.HttpResponseForbidden('Two different passwords')
            return response_ret

        # Phone number validity
        if not re.match(r'^1[3-9]\d{9}$', self.mobile):
            response_ret = http.HttpResponseForbidden('Mobile phone number is not valid')
            return response_ret

        # Whether to select user protocol
        ifself.allow ! ='on':
            response_ret = http.HttpResponseForbidden('Please check user Protocol')
            return response_ret

        return response_ret
Copy the code


Handle by throwing exceptions and setting the status code enumeration

    def verify_params(self, request) :
        Param request: return response_ret ""
        # accept parameters
        self.username = request.POST.get('username')
        self.password = request.POST.get('password')
        self.confirm_pwd = request.POST.get('confirm_pwd')
        self.mobile = request.POST.get('mobile')
        self.allow = request.POST.get('allow')

        # check parameter
        all_args = [self.username, self.password, self.confirm_pwd, self.mobile, self.allow]
        if not all(all_args):
            raise BusinessException(StatusCodeEnum.PARAM_ERR)

        The username is 5-20 characters long
        if not re.match(R '^ [a zA - Z0-9 _] {5, 20}', self.username):
            raise BusinessException(StatusCodeEnum.USER_ERR)

        # Password 8-20 characters
        if not re.match(R '^ [a zA - Z0-9] {8, 20}', self.password):
            raise BusinessException(StatusCodeEnum.PWD_ERR)

        Password consistency
        ifself.password ! = self.confirm_pwd:raise BusinessException(StatusCodeEnum.CPWD_ERR)

        # Phone number validity
        if not re.match(r'^1[3-9]\d{9}$', self.mobile):
            raise BusinessException(StatusCodeEnum.MOBILE_ERR)

        # Whether to select user protocol
        ifself.allow ! ='on':
            raise BusinessException(StatusCodeEnum.ALLOW_ERR)
Copy the code


To reducetry ... except ...The code block

For example, when performing operations on a database, a try… is added to prevent the database from crashing due to unexpected exceptions. except … To record exception information. However, if global exception handling is configured, management is not required.


# create user
try:
    user = User.objects.create_user(
        username=self.username,
        password=self.password,
        mobile=self.mobile,
    )
except DatabaseError as e:
    logger.error(e)
    
    
# global exception handling
user = User.objects.create_user(
        username=self.username,
        password=self.password,
        mobile=self.mobile,
    )
Copy the code


Note: It is inevitable that some business information, such as transaction rollback, needs to be handled through exception catching


The source code

It may be difficult to understand its ideas through the article, we can refer to the project source code.

Beauty shop https://gitee.com/huiDBK/meiduo_project/tree/master


The tail language

✍ Code writes the world and makes life more interesting. ❤ ️

✍ thousands of rivers and mountains always love, ✍ go again. ❤ ️

✍ code word is not easy, but also hope you heroes support. ❤ ️