Skip to content
This repository was archived by the owner on Oct 23, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions raven/contrib/django/middleware/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,3 +118,18 @@ def request_finished(self, **kwargs):


SentryLogMiddleware = SentryMiddleware


class DjangoRestFrameworkCompatMiddleware(MiddlewareMixin):
def process_request(self, request):
"""
Access request.body, otherwise it might not be accessible later
after request has been read/streamed
"""
content_type = request.META.get('CONTENT_TYPE', '')
if 'application/x-www-form-urlencoded' in content_type:
pass
elif 'multipart/form-data' in content_type:
pass
else:
request.body # forces stream to be read into memory
23 changes: 13 additions & 10 deletions raven/contrib/django/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,26 +212,23 @@ def register_serializers():
import raven.contrib.django.serializers # NOQA


def install_middleware():
def install_middleware(middleware_name, lookup_names=None):
"""
Force installation of SentryMiddlware if it's not explicitly present.

This ensures things like request context and transaction names are made
available.
Install specified middleware
"""
name = 'raven.contrib.django.middleware.SentryMiddleware'
all_names = (name, 'raven.contrib.django.middleware.SentryLogMiddleware')
if lookup_names is None:
lookup_names = (middleware_name,)
# default settings.MIDDLEWARE is None
middleware_attr = 'MIDDLEWARE' if getattr(settings,
'MIDDLEWARE',
None) is not None \
else 'MIDDLEWARE_CLASSES'
# make sure to get an empty tuple when attr is None
middleware = getattr(settings, middleware_attr, ()) or ()
if set(all_names).isdisjoint(set(middleware)):
if set(lookup_names).isdisjoint(set(middleware)):
setattr(settings,
middleware_attr,
type(middleware)((name,)) + middleware)
type(middleware)((middleware_name,)) + middleware)


_setup_lock = Lock()
Expand All @@ -250,7 +247,13 @@ def initialize():

try:
register_serializers()
install_middleware()
install_middleware(
'raven.contrib.django.middleware.SentryMiddleware',
(
'raven.contrib.django.middleware.SentryMiddleware',
'raven.contrib.django.middleware.SentryLogMiddleware'))
install_middleware(
'raven.contrib.django.middleware.DjangoRestFrameworkCompatMiddleware')

# XXX(dcramer): maybe this setting should disable ALL of this?
if not getattr(settings, 'DISABLE_SENTRY_INSTRUMENTATION', False):
Expand Down
29 changes: 29 additions & 0 deletions tests/contrib/django/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@
from raven.transport import HTTPTransport
from raven.utils.serializer import transform

from .views import AppError

#from .models import MyTestModel

DJANGO_15 = django.VERSION >= (1, 5, 0)
Expand Down Expand Up @@ -182,6 +184,33 @@ def test_view_exception(self):
assert 'request' in event
assert event['request']['url'] == 'http://testserver{}'.format(path)

def test_request_data_unavailable_if_request_is_read(self):
with Settings(**{MIDDLEWARE_ATTR: []}):
path = reverse('sentry-readrequest-raise-exc')
self.assertRaises(
AppError,
self.client.post,
path,
'{"a":"b"}',
content_type='application/json')
assert len(self.raven.events) == 1
event = self.raven.events.pop(0)
assert event['request']['data'] == '<unavailable>'

def test_djangorestframeworkcompatmiddleware_fills_request_data(self):
with Settings(**{MIDDLEWARE_ATTR: [
'raven.contrib.django.middleware.DjangoRestFrameworkCompatMiddleware']}):
path = reverse('sentry-readrequest-raise-exc')
self.assertRaises(
AppError,
self.client.post,
path,
'{"a":"b"}',
content_type='application/json')
assert len(self.raven.events) == 1
event = self.raven.events.pop(0)
assert event['request']['data'] == b'{"a":"b"}'

def test_capture_event_with_request_middleware(self):
path = reverse('sentry-trigger-event')
resp = self.client.get(path)
Expand Down
1 change: 1 addition & 0 deletions tests/contrib/django/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ def handler500(request, exception=None):
url(r'^no-error$', views.no_error, name='sentry-no-error'),
url(r'^fake-login$', views.fake_login, name='sentry-fake-login'),
url(r'^trigger-500$', views.raise_exc, name='sentry-raise-exc'),
url(r'^trigger-500-readrequest$', views.read_request_and_raise_exc, name='sentry-readrequest-raise-exc'),
url(r'^trigger-500-ioerror$', views.raise_ioerror, name='sentry-raise-ioerror'),
url(r'^trigger-500-decorated$', views.decorated_raise_exc, name='sentry-raise-exc-decor'),
url(r'^trigger-500-django$', views.django_exc, name='sentry-django-exc'),
Expand Down
9 changes: 9 additions & 0 deletions tests/contrib/django/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
import logging


class AppError(Exception):
pass


def no_error(request):
return HttpResponse('')

Expand All @@ -23,6 +27,11 @@ def raise_exc(request):
raise Exception(request.GET.get('message', 'view exception'))


def read_request_and_raise_exc(request):
request.read()
raise AppError()


def raise_ioerror(request):
raise IOError(request.GET.get('message', 'view exception'))

Expand Down