Skip to content
This repository has been archived by the owner on Feb 20, 2019. It is now read-only.

Commit

Permalink
Merge pull request #109 from robhudson/es-50x-middleware
Browse files Browse the repository at this point in the history
Added ES error middleware for Django (issue #47)
  • Loading branch information
willkg committed Mar 21, 2013
2 parents f55f7ec + d96172e commit 92e63c9
Show file tree
Hide file tree
Showing 5 changed files with 114 additions and 43 deletions.
97 changes: 55 additions & 42 deletions elasticutils/contrib/django/__init__.py
Expand Up @@ -9,13 +9,22 @@
try: try:
from django.conf import settings from django.conf import settings
from django.shortcuts import render from django.shortcuts import render
from django.utils.decorators import decorator_from_middleware_with_args
except ImportError: except ImportError:
pass pass




log = logging.getLogger('elasticutils') log = logging.getLogger('elasticutils')




ES_EXCEPTIONS = (
pyelasticsearch.exceptions.ConnectionError,
pyelasticsearch.exceptions.ElasticHttpError,
pyelasticsearch.exceptions.ElasticHttpNotFoundError,
pyelasticsearch.exceptions.Timeout
)


def get_es(**overrides): def get_es(**overrides):
"""Return a pyelasticsearch ElasticSearch object using settings """Return a pyelasticsearch ElasticSearch object using settings
from ``settings.py``. from ``settings.py``.
Expand Down Expand Up @@ -58,12 +67,8 @@ def wrapper(*args, **kw):
return wrapper return wrapper




def es_required_or_50x(disabled_template='elasticutils/501.html', class ESExceptionMiddleware(object):
error_template='elasticutils/503.html'): """Middleware to handle ElasticSearch errors.
"""Wrap a Django view and handle ElasticSearch errors.
This wraps a Django view and returns 501 or 503 status codes and
pages if things go awry.
HTTP 501 HTTP 501
Returned when ``ES_DISABLED`` is True. Returned when ``ES_DISABLED`` is True.
Expand All @@ -80,57 +85,65 @@ def es_required_or_50x(disabled_template='elasticutils/501.html',
* error: A string version of the exception thrown. * error: A string version of the exception thrown.
:arg disabled_template: The template to use when ES_DISABLED is :arg disabled_template: The template to use when ES_DISABLED is True.
True.
Defaults to ``elasticutils/501.html``. Defaults to ``elasticutils/501.html``.
:arg error_template: The template to use when ElasticSearch isn't :arg error_template: The template to use when ElasticSearch isn't
working properly, is missing an index, or something along working properly, is missing an index, or something along
those lines. those lines.
Defaults to ``elasticutils/503.html``. Defaults to ``elasticutils/503.html``.
"""


Examples:: def __init__(self, disabled_template=None, error_template=None):
self.disabled_template = (
disabled_template or 'elasticutils/501.html')
self.error_template = (
error_template or 'elasticutils/503.html')


# This creates a home_view and decorates it to use the def process_request(self, request):
# default templates. if getattr(settings, 'ES_DISABLED', False):
response = render(request, self.disabled_template)
response.status_code = 501
return response


@es_required_or_50x() def process_exception(self, request, exception):
def home_view(request): if issubclass(exception.__class__, ES_EXCEPTIONS):
... response = render(request, self.error_template,
{'error': exception})
response.status_code = 503
return response




# This creates a search_view and overrides the templates """
The following decorator wraps a Django view and handles ElasticSearch errors.
@es_required_or_50x(disabled_template='search/es_disabled.html', This wraps a Django view and returns 501 or 503 status codes and
error_template('search/es_down.html') pages if things go awry.
def search_view(request):
...
""" See the above middleware for explanation of the arguments.
def wrap(fun):
@wraps(fun) Examples::
def wrapper(request, *args, **kw):
if getattr(settings, 'ES_DISABLED', False): # This creates a home_view and decorates it to use the
response = render(request, disabled_template) # default templates.
response.status_code = 501
return response @es_required_or_50x()

def home_view(request):
try: ...
return fun(request, *args, **kw)

except (pyelasticsearch.exceptions.ConnectionError, # This creates a search_view and overrides the templates
pyelasticsearch.exceptions.ElasticHttpError,
pyelasticsearch.exceptions.ElasticHttpNotFoundError, @es_required_or_50x(disabled_template='search/es_disabled.html',
pyelasticsearch.exceptions.Timeout) as exc: error_template('search/es_down.html')
response = render(request, error_template, {'error': exc}) def search_view(request):
response.status_code = 503 ...
return response

"""
return wrapper es_required_or_50x = decorator_from_middleware_with_args(ESExceptionMiddleware)

return wrap




class S(elasticutils.S): class S(elasticutils.S):
Expand Down
Empty file.
Empty file.
51 changes: 50 additions & 1 deletion elasticutils/tests/test_django.py
Expand Up @@ -20,9 +20,12 @@


try: try:
from django.conf import settings from django.conf import settings
from django.test import RequestFactory
from django.test.utils import override_settings


from elasticutils.contrib.django import ( from elasticutils.contrib.django import (
S, F, get_es, InvalidFieldActionError) S, F, get_es, InvalidFieldActionError, ES_EXCEPTIONS,
ESExceptionMiddleware, es_required_or_50x)
from elasticutils.contrib.django.tasks import ( from elasticutils.contrib.django.tasks import (
index_objects, unindex_objects) index_objects, unindex_objects)
from elasticutils.tests.django_utils import ( from elasticutils.tests.django_utils import (
Expand Down Expand Up @@ -368,3 +371,49 @@ def test_tasks(self):
unindex_objects(FakeDjangoMappingType, [1, 2, 3]) unindex_objects(FakeDjangoMappingType, [1, 2, 3])
FakeDjangoMappingType.refresh_index() FakeDjangoMappingType.refresh_index()
eq_(FakeDjangoMappingType.search().count(), 0) eq_(FakeDjangoMappingType.search().count(), 0)


class MiddlewareTest(DjangoElasticTestCase):

def setUp(self):
super(MiddlewareTest, self).setUp()

def view(request, exc):
raise exc

self.func = view
self.fake_request = RequestFactory().get('/')

def test_exceptions(self):
for exc in ES_EXCEPTIONS:
response = ESExceptionMiddleware().process_exception(
self.fake_request, exc(Exception))
eq_(response.status_code, 503)

@override_settings(ES_DISABLED=True)
def test_es_disabled(self):
response = ESExceptionMiddleware().process_request(self.fake_request)
eq_(response.status_code, 501)


class DecoratorTest(DjangoElasticTestCase):

def setUp(self):
super(DecoratorTest, self).setUp()

@es_required_or_50x()
def view(request, exc):
raise exc

self.func = view
self.fake_request = RequestFactory().get('/')

def test_exceptions(self):
for exc in ES_EXCEPTIONS:
response = self.func(self.fake_request, exc(Exception))
eq_(response.status_code, 503)

@override_settings(ES_DISABLED=True)
def test_es_disabled(self):
response = self.func(self.fake_request)
eq_(response.status_code, 501)
9 changes: 9 additions & 0 deletions test_settings.py
@@ -1,6 +1,15 @@
import os


ROOT = os.path.abspath(os.path.dirname(__file__))


ES_URLS = ['http://localhost:9200'] ES_URLS = ['http://localhost:9200']
ES_INDEXES = {'default': ['elasticutilstest']} ES_INDEXES = {'default': ['elasticutilstest']}
ES_TIMEOUT = 10 ES_TIMEOUT = 10
ES_DISABLED = False ES_DISABLED = False


CELERY_ALWAYS_EAGER = True CELERY_ALWAYS_EAGER = True

SECRET_KEY = 'super_secret'
TEMPLATE_DIRS = ('%s/elasticutils/templates' % ROOT,)

0 comments on commit 92e63c9

Please sign in to comment.