django:将BadRequest作为异常抛出?

30 投票
8 回答
46982 浏览
提问于 2025-04-18 18:02

在Django中,能不能抛出BadRequest这个异常呢?

我看到可以抛出404错误。

使用场景:在一个辅助方法中,我从请求的GET参数中加载一个json。如果这个json因为浏览器(IE)截断了网址而被切掉了,我想抛出一个合适的异常。

抛出一个BadRequest异常听起来很合适,但到目前为止,Django似乎没有这样的异常。

在1.6版本中有一个SuspiciousOperation异常,但这不适合我的情况,因为它和安全性无关。

当然,我可以在视图方法中给我的辅助方法加上try..except,但这样做不够简洁。

有没有人能提供一个解决方案,让我不需要在每次调用辅助方法时都加try..except呢?

[1] https://docs.djangoproject.com/en/1.6/ref/exceptions/#django.core.urlresolvers.Resolver404

更新

代码示例:

def my_view(request):
    data=load_data_from_request(request) # I don't want a try..except here: DRY
    process_data(data)
    return django.http.HttpResponse('Thank you')

def load_data_from_request(request):
    try:
        data_raw=json.loads(...)
    except ValueError, exc:
        raise BadRequest(exc)
    ...
    return data

8 个回答

6

HttpResponseBadRequest 是可以直接使用的。它的实现方式如下:

class HttpResponseBadRequest(HttpResponse):
    status_code = 400

已编辑,因为提问者更新了问题。

你可以自己创建一个辅助函数,把 try-catch 代码块封装在里面。

def myJsonDec(str):
    try:
        ...
7

从3.2版本开始,Django提供了一个叫做BadRequest的类。这样你就可以这样做了:

try:
    data_raw = json.loads(...)
except ValueError:
    raise BadRequest('Invalid JSON')

不过问题是,出于某种原因,错误响应中并没有显示“无效的JSON”这个消息,而是只显示了一个通用的错误信息:

<!doctype html>
<html lang="en">
<head>
  <title>Bad Request (400)</title>
</head>
<body>
  <h1>Bad Request (400)</h1><p></p>
</body>
</html>
9

作为@coldmind的回答的另一种选择(在中间件层转换异常),你可以在你的视图函数上加一个装饰器,做同样的事情。我个人更喜欢这种方式,因为这就是普通的Python,不需要我去翻阅关于Django中间件的知识。

你不想在视图函数中把所有功能都写得杂乱无章(这样会导致你的视图模块依赖于项目中的所有其他模块,形成“万物相互依赖”的架构)。相反,视图函数最好只关注HTTP。它从请求中提取所需的信息,然后把工作交给其他的“业务逻辑”函数。业务逻辑可能会再调用其他模块(比如数据库代码或与其他外部系统的接口)。最后,业务逻辑返回的结果会被视图函数转换成HTTP响应。

那么,如何将错误信息从业务逻辑(或它所调用的其他部分)传回视图函数呢?使用返回值会有很多麻烦。例如,这些错误返回值需要从整个代码库中逐层传递回视图。这通常会非常混乱,因为你已经在用函数的返回值做其他事情了。

处理这个问题的自然方式是使用异常,但Django视图本身不会把未捕获的异常转换成返回的HTTP状态码(除了几个特殊情况,正如提问者所说)。

所以,我写了一个装饰器来应用到我的视图上。这个装饰器会把不同类型的异常转换成不同的django.http.HttpResponseXXX值。例如:

# This might be raised by your business logic or database code, if they get
# called with parameters that turn out to be invalid. The database code needs
# know nothing about http to do this. It might be best to define these exception
# types in a module of their own to prevent cycles, because many modules 
# might need to import them.
class InvalidData(Exception):
    pass

# This decorator is defined in the view module, and it knows to convert
# InvalidData exceptions to http status 400. Add whatever other exception types
# and http return values you need. We end with a 'catch-all' conversion of
# Exception into http 500.
def exceptions_to_http_status(view_func):
    @wraps(view_func)
    def inner(*args, **kwargs):
        try:
            return view_func(*args, **kwargs)
        except InvalidData as e:
            return django.http.HttpResponseBadRequest(str(e))   
        except Exception as e:
            return django.http.HttpResponseServerError(str(e))
     return inner

 # Then finally we define our view, using the decorator.

 @exceptions_to_http_status
 def myview(self, request):
     # The view parses what we need out of incoming requests
     data = request.GET['somearg']

     # Here in the middle of your view, delegate to your business logic,
     # which can just raise exceptions if there is an error.
     result = myusecase(data)

     # and finally the view constructs responses
     return HttpResponse(result.summary)

根据情况,你可能会发现同一个装饰器可以适用于你的许多或所有视图函数。

29

其他回答在讲怎么返回一个状态码为400的HTTP响应。

如果你想要使用Django的400错误处理,你可以抛出一个SuspiciousOperation异常,或者它的子类。

你可以在文档中查看详细信息,这里这里都有。

在你的例子中,它看起来会是这样的:

from django.core.exceptions import SuspiciousOperation

def load_data_from_request(request):
    try:
        data_raw = json.loads(...)
    except ValueError:
        raise SuspiciousOperation('Invalid JSON')
    # ...
    return data
14

你需要自定义的中间件来处理你抛出的异常。可以使用自定义异常在中间件中检查这个情况。

class ErrorHandlingMiddleware(object):
    def process_exception(self, request, exception):
        if not isinstance(exception, errors.ApiException): # here you check if it yours exception
            logger.error('Internal Server Error: %s', request.path,
                exc_info=traceback.format_exc(),
                extra={
                    'request': request
                }
            )
        # if it yours exception, return response with error description
        try:
            return formatters.render_formatted_error(request, exception) # here you return response you need
        except Exception, e:
            return HttpResponseServerError("Error During Error Processing")

撰写回答