diff --git a/opbeat/contrib/django/client.py b/opbeat/contrib/django/client.py index ac921b2d..91e6e4a0 100644 --- a/opbeat/contrib/django/client.py +++ b/opbeat/contrib/django/client.py @@ -106,15 +106,19 @@ def get_data_from_request(self, request): if request.method != 'GET': try: - if hasattr(request, 'body'): + try: # Django 1.4+ + # NOTE: on Python 2.7 `hasattr(request, 'body')` returns + # False, while it might throw the exception from Django in + # Python 3. raw_data = request.body - else: + except AttributeError: raw_data = request.raw_post_data data = raw_data if raw_data else request.POST - except Exception: - # assume we had a partial read: - data = '' + except Exception as exc: + # Assume we had a partial read, or multipart/form-data: + data = '\nrequest.POST: {2}'.format( + exc.__class__.__name__, exc, request.POST) else: data = None diff --git a/tests/contrib/django/django_tests.py b/tests/contrib/django/django_tests.py index b01e7bcb..ca6df9d4 100644 --- a/tests/contrib/django/django_tests.py +++ b/tests/contrib/django/django_tests.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- import pytest # isort:skip -django = pytest.importorskip("django") # isort:skip +django = pytest.importorskip("django") # noqa: E402 isort:skip import datetime import logging @@ -21,6 +21,7 @@ from django.test import TestCase from django.test.client import Client as _TestClient from django.test.client import ClientHandler as _TestClientHandler +from django.test.client import FakePayload from django.test.utils import override_settings import mock @@ -518,7 +519,12 @@ def test_raw_post_data_partial_read(self): self.assertTrue('http' in event) http = event['http'] self.assertEquals(http['method'], 'POST') - self.assertEquals(http['data'], '') + if django.VERSION >= (1, 7): + expected_exception = 'RawPostDataException' + else: + expected_exception = 'Exception' + self.assertEquals(http['data'], "\nrequest.POST: {1}".format(expected_exception, request.POST)) + self.assertEquals(request.POST, {}) def test_post_data(self): request = WSGIRequest(environ={ @@ -598,7 +604,7 @@ def test_disallowed_hosts_error_django_18(self): self.assertEqual(event['http']['url'], None) # This test only applies to Django 1.3+ - def test_request_capture(self): + def test_request_capture_partial_read(self): if django.VERSION[:2] < (1, 3): return request = WSGIRequest(environ={ @@ -619,7 +625,12 @@ def test_request_capture(self): self.assertTrue('http' in event) http = event['http'] self.assertEquals(http['method'], 'POST') - self.assertEquals(http['data'], '') + if django.VERSION >= (1, 7): + expected_exception = 'RawPostDataException' + else: + expected_exception = 'Exception' + self.assertEquals(http['data'], "\nrequest.POST: {1}".format(expected_exception, request.POST)) + # self.assertEquals(http['data'], '{}') self.assertTrue('headers' in http) headers = http['headers'] self.assertTrue('Content-Type' in headers, headers.keys()) @@ -630,6 +641,52 @@ def test_request_capture(self): self.assertTrue('SERVER_PORT' in env, env.keys()) self.assertEquals(env['SERVER_PORT'], '80') + def test_request_capture_multipart_formdata(self): + if django.VERSION[:2] < (1, 3): + return + + payload = FakePayload("\r\n".join([ + '--boundary', + 'Content-Disposition: form-data; name="name"', + '', + 'value', + '--boundary--' + ''])) + request = WSGIRequest({ + 'REQUEST_METHOD': 'POST', + 'SERVER_NAME': 'testserver', + 'SERVER_PORT': '80', + 'CONTENT_TYPE': 'multipart/form-data; boundary=boundary', + 'CONTENT_LENGTH': 78, + 'wsgi.input': payload}) + + # Read POST data to trigger exception when accessing request.body. + self.assertEqual(request.POST, {'name': ['value']}) + + self.opbeat.capture('Message', message='foo', request=request) + + self.assertEquals(len(self.opbeat.events), 1) + event = self.opbeat.events.pop(0) + + self.assertTrue('http' in event) + http = event['http'] + self.assertEquals(http['method'], 'POST') + if django.VERSION >= (1, 7): + expected_exception = 'RawPostDataException' + else: + expected_exception = 'Exception' + self.assertEquals(http['data'], "\nrequest.POST: {1}".format(expected_exception, request.POST)) + # self.assertEquals(http['data'], '{}') + self.assertTrue('headers' in http) + headers = http['headers'] + self.assertTrue('Content-Type' in headers, headers.keys()) + self.assertEquals(headers['Content-Type'], 'multipart/form-data; boundary=boundary') + env = http['env'] + self.assertTrue('SERVER_NAME' in env, env.keys()) + self.assertEquals(env['SERVER_NAME'], 'testserver') + self.assertTrue('SERVER_PORT' in env, env.keys()) + self.assertEquals(env['SERVER_PORT'], '80') + def test_transaction_metrics(self): self.opbeat.instrumentation_store.get_all() # clear the store with self.settings(MIDDLEWARE_CLASSES=[