django:将BadRequest作为异常抛出?
在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 个回答
HttpResponseBadRequest 是可以直接使用的。它的实现方式如下:
class HttpResponseBadRequest(HttpResponse):
status_code = 400
已编辑,因为提问者更新了问题。
你可以自己创建一个辅助函数,把 try-catch 代码块封装在里面。
def myJsonDec(str):
try:
...
从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>
作为@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)
根据情况,你可能会发现同一个装饰器可以适用于你的许多或所有视图函数。
其他回答在讲怎么返回一个状态码为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
你需要自定义的中间件来处理你抛出的异常。可以使用自定义异常在中间件中检查这个情况。
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")