Skip to content

Commit d1d6f40

Browse files
committed
New JsonResponse checker
to validate common anti-patterns with http responses returning JSON data
1 parent 14991d3 commit d1d6f40

File tree

4 files changed

+93
-0
lines changed

4 files changed

+93
-0
lines changed

pylint_django/checkers/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
"""Checkers."""
22
from pylint_django.checkers.django_installed import DjangoInstalledChecker
33
from pylint_django.checkers.models import ModelChecker
4+
from pylint_django.checkers.json_response import JsonResponseChecker
45

56

67
def register_checkers(linter):
78
"""Register checkers."""
89
linter.register_checker(ModelChecker(linter))
910
linter.register_checker(DjangoInstalledChecker(linter))
11+
linter.register_checker(JsonResponseChecker(linter))
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# Copyright (c) 2018 Alexander Todorov <atodorov@MrSenko.com>
2+
3+
# Licensed under the GPL 2.0: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
4+
# For details: https://github.com/PyCQA/pylint-django/blob/master/LICENSE
5+
"""
6+
Various suggestions about JSON http responses
7+
"""
8+
9+
import astroid
10+
11+
from pylint import interfaces
12+
from pylint import checkers
13+
from pylint.checkers import utils
14+
from pylint_django.__pkginfo__ import BASE_ID
15+
16+
17+
class JsonResponseChecker(checkers.BaseChecker):
18+
"""
19+
Looks for some common patterns when returning http responses containing
20+
JSON data!
21+
"""
22+
23+
__implements__ = (interfaces.IAstroidChecker,)
24+
25+
# configuration section name
26+
name = 'json-response-checker'
27+
msgs = {'R%s01' % BASE_ID: ("Instead of HttpResponse(json.dumps(data)) use JsonResponse(data)",
28+
'http-response-with-json-dumps',
29+
'Used when json.dumps() is used as an argument to HttpResponse().'),
30+
'R%s02' % BASE_ID: ("Instead of HttpResponse(content_type='application/json') use JsonResponse()",
31+
'http-response-with-content-type-json',
32+
'Used when HttpResponse() is returning application/json.'),
33+
'R%s03' % BASE_ID: ("Redundant content_type parameter for JsonResponse()",
34+
'redundant-content-type-for-json-response',
35+
'Used when JsonResponse() contains content_type parameter. '
36+
'This is either redundant or the content_type is not JSON '
37+
'which is probably an error.')}
38+
39+
@utils.check_messages('http-response-with-json-dumps',
40+
'http-response-with-content-type-json',
41+
'redundant-content-type-for-json-response')
42+
def visit_call(self, node):
43+
if (node.func.as_string().endswith('HttpResponse') and node.args
44+
and isinstance(node.args[0], astroid.Call)
45+
and node.args[0].func.as_string() == 'json.dumps'):
46+
self.add_message('http-response-with-json-dumps', node=node)
47+
48+
if node.func.as_string().endswith('HttpResponse') and node.keywords:
49+
for keyword in node.keywords:
50+
if (keyword.arg == 'content_type'
51+
and keyword.value.as_string().lower().find('application/json') > -1):
52+
self.add_message('http-response-with-content-type-json', node=node)
53+
break
54+
55+
if node.func.as_string().endswith('JsonResponse') and node.keywords:
56+
for keyword in node.keywords:
57+
if keyword.arg == 'content_type':
58+
self.add_message('redundant-content-type-for-json-response', node=node)
59+
break
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# pylint: disable=missing-docstring, line-too-long
2+
3+
import json
4+
from django import http
5+
from django.http import HttpResponse
6+
7+
8+
def say_yes():
9+
return HttpResponse(json.dumps({'rc': 0, 'response': 'ok'})) # [http-response-with-json-dumps]
10+
11+
12+
def say_yes2():
13+
data = {'rc': 0, 'response': 'ok'}
14+
return http.HttpResponse(json.dumps(data)) # [http-response-with-json-dumps]
15+
16+
17+
def say_no():
18+
return HttpResponse("no")
19+
20+
21+
def redundant_content_type():
22+
data = {'rc': 0, 'response': 'ok'}
23+
return http.JsonResponse(data, content_type='application/json') # [redundant-content-type-for-json-response]
24+
25+
26+
def content_type_json():
27+
json_data = "this comes from somewhere"
28+
return HttpResponse(json_data, content_type='application/json') # [http-response-with-content-type-json]
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
http-response-with-json-dumps:9:say_yes:Instead of HttpResponse(json.dumps(data)) use JsonResponse(data)
2+
http-response-with-json-dumps:14:say_yes2:Instead of HttpResponse(json.dumps(data)) use JsonResponse(data)
3+
redundant-content-type-for-json-response:23:redundant_content_type:Redundant content_type parameter for JsonResponse()
4+
http-response-with-content-type-json:28:content_type_json:Instead of HttpResponse(content_type='application/json') use JsonResponse()

0 commit comments

Comments
 (0)