Skip to content
This repository
Browse code

Merge pull request #109 from robhudson/es-50x-middleware

Added ES error middleware for Django (issue #47)
  • Loading branch information...
commit 92e63c9e8f5fe19a00b15670f0d3c1a05652551d 2 parents f55f7ec + d96172e
Will Kahn-Greene willkg authored
97 elasticutils/contrib/django/__init__.py
@@ -9,6 +9,7 @@
9 9 try:
10 10 from django.conf import settings
11 11 from django.shortcuts import render
  12 + from django.utils.decorators import decorator_from_middleware_with_args
12 13 except ImportError:
13 14 pass
14 15
@@ -16,6 +17,14 @@
16 17 log = logging.getLogger('elasticutils')
17 18
18 19
  20 +ES_EXCEPTIONS = (
  21 + pyelasticsearch.exceptions.ConnectionError,
  22 + pyelasticsearch.exceptions.ElasticHttpError,
  23 + pyelasticsearch.exceptions.ElasticHttpNotFoundError,
  24 + pyelasticsearch.exceptions.Timeout
  25 +)
  26 +
  27 +
19 28 def get_es(**overrides):
20 29 """Return a pyelasticsearch ElasticSearch object using settings
21 30 from ``settings.py``.
@@ -58,12 +67,8 @@ def wrapper(*args, **kw):
58 67 return wrapper
59 68
60 69
61   -def es_required_or_50x(disabled_template='elasticutils/501.html',
62   - error_template='elasticutils/503.html'):
63   - """Wrap a Django view and handle ElasticSearch errors.
64   -
65   - This wraps a Django view and returns 501 or 503 status codes and
66   - pages if things go awry.
  70 +class ESExceptionMiddleware(object):
  71 + """Middleware to handle ElasticSearch errors.
67 72
68 73 HTTP 501
69 74 Returned when ``ES_DISABLED`` is True.
@@ -80,57 +85,65 @@ def es_required_or_50x(disabled_template='elasticutils/501.html',
80 85
81 86 * error: A string version of the exception thrown.
82 87
83   - :arg disabled_template: The template to use when ES_DISABLED is
84   - True.
  88 + :arg disabled_template: The template to use when ES_DISABLED is True.
85 89
86 90 Defaults to ``elasticutils/501.html``.
  91 +
87 92 :arg error_template: The template to use when ElasticSearch isn't
88 93 working properly, is missing an index, or something along
89 94 those lines.
90 95
91 96 Defaults to ``elasticutils/503.html``.
92 97
  98 + """
93 99
94   - Examples::
  100 + def __init__(self, disabled_template=None, error_template=None):
  101 + self.disabled_template = (
  102 + disabled_template or 'elasticutils/501.html')
  103 + self.error_template = (
  104 + error_template or 'elasticutils/503.html')
95 105
96   - # This creates a home_view and decorates it to use the
97   - # default templates.
  106 + def process_request(self, request):
  107 + if getattr(settings, 'ES_DISABLED', False):
  108 + response = render(request, self.disabled_template)
  109 + response.status_code = 501
  110 + return response
98 111
99   - @es_required_or_50x()
100   - def home_view(request):
101   - ...
  112 + def process_exception(self, request, exception):
  113 + if issubclass(exception.__class__, ES_EXCEPTIONS):
  114 + response = render(request, self.error_template,
  115 + {'error': exception})
  116 + response.status_code = 503
  117 + return response
102 118
103 119
104   - # This creates a search_view and overrides the templates
  120 +"""
  121 +The following decorator wraps a Django view and handles ElasticSearch errors.
105 122
106   - @es_required_or_50x(disabled_template='search/es_disabled.html',
107   - error_template('search/es_down.html')
108   - def search_view(request):
109   - ...
  123 +This wraps a Django view and returns 501 or 503 status codes and
  124 +pages if things go awry.
110 125
111   - """
112   - def wrap(fun):
113   - @wraps(fun)
114   - def wrapper(request, *args, **kw):
115   - if getattr(settings, 'ES_DISABLED', False):
116   - response = render(request, disabled_template)
117   - response.status_code = 501
118   - return response
119   -
120   - try:
121   - return fun(request, *args, **kw)
122   -
123   - except (pyelasticsearch.exceptions.ConnectionError,
124   - pyelasticsearch.exceptions.ElasticHttpError,
125   - pyelasticsearch.exceptions.ElasticHttpNotFoundError,
126   - pyelasticsearch.exceptions.Timeout) as exc:
127   - response = render(request, error_template, {'error': exc})
128   - response.status_code = 503
129   - return response
130   -
131   - return wrapper
132   -
133   - return wrap
  126 +See the above middleware for explanation of the arguments.
  127 +
  128 +Examples::
  129 +
  130 + # This creates a home_view and decorates it to use the
  131 + # default templates.
  132 +
  133 + @es_required_or_50x()
  134 + def home_view(request):
  135 + ...
  136 +
  137 +
  138 + # This creates a search_view and overrides the templates
  139 +
  140 + @es_required_or_50x(disabled_template='search/es_disabled.html',
  141 + error_template('search/es_down.html')
  142 + def search_view(request):
  143 + ...
  144 +
  145 +"""
  146 +es_required_or_50x = decorator_from_middleware_with_args(ESExceptionMiddleware)
134 147
135 148
136 149 class S(elasticutils.S):
0  elasticutils/templates/elasticutils/501.html
No changes.
0  elasticutils/templates/elasticutils/503.html
No changes.
51 elasticutils/tests/test_django.py
@@ -20,9 +20,12 @@
20 20
21 21 try:
22 22 from django.conf import settings
  23 + from django.test import RequestFactory
  24 + from django.test.utils import override_settings
23 25
24 26 from elasticutils.contrib.django import (
25   - S, F, get_es, InvalidFieldActionError)
  27 + S, F, get_es, InvalidFieldActionError, ES_EXCEPTIONS,
  28 + ESExceptionMiddleware, es_required_or_50x)
26 29 from elasticutils.contrib.django.tasks import (
27 30 index_objects, unindex_objects)
28 31 from elasticutils.tests.django_utils import (
@@ -368,3 +371,49 @@ def test_tasks(self):
368 371 unindex_objects(FakeDjangoMappingType, [1, 2, 3])
369 372 FakeDjangoMappingType.refresh_index()
370 373 eq_(FakeDjangoMappingType.search().count(), 0)
  374 +
  375 +
  376 +class MiddlewareTest(DjangoElasticTestCase):
  377 +
  378 + def setUp(self):
  379 + super(MiddlewareTest, self).setUp()
  380 +
  381 + def view(request, exc):
  382 + raise exc
  383 +
  384 + self.func = view
  385 + self.fake_request = RequestFactory().get('/')
  386 +
  387 + def test_exceptions(self):
  388 + for exc in ES_EXCEPTIONS:
  389 + response = ESExceptionMiddleware().process_exception(
  390 + self.fake_request, exc(Exception))
  391 + eq_(response.status_code, 503)
  392 +
  393 + @override_settings(ES_DISABLED=True)
  394 + def test_es_disabled(self):
  395 + response = ESExceptionMiddleware().process_request(self.fake_request)
  396 + eq_(response.status_code, 501)
  397 +
  398 +
  399 +class DecoratorTest(DjangoElasticTestCase):
  400 +
  401 + def setUp(self):
  402 + super(DecoratorTest, self).setUp()
  403 +
  404 + @es_required_or_50x()
  405 + def view(request, exc):
  406 + raise exc
  407 +
  408 + self.func = view
  409 + self.fake_request = RequestFactory().get('/')
  410 +
  411 + def test_exceptions(self):
  412 + for exc in ES_EXCEPTIONS:
  413 + response = self.func(self.fake_request, exc(Exception))
  414 + eq_(response.status_code, 503)
  415 +
  416 + @override_settings(ES_DISABLED=True)
  417 + def test_es_disabled(self):
  418 + response = self.func(self.fake_request)
  419 + eq_(response.status_code, 501)
9 test_settings.py
... ... @@ -1,6 +1,15 @@
  1 +import os
  2 +
  3 +
  4 +ROOT = os.path.abspath(os.path.dirname(__file__))
  5 +
  6 +
1 7 ES_URLS = ['http://localhost:9200']
2 8 ES_INDEXES = {'default': ['elasticutilstest']}
3 9 ES_TIMEOUT = 10
4 10 ES_DISABLED = False
5 11
6 12 CELERY_ALWAYS_EAGER = True
  13 +
  14 +SECRET_KEY = 'super_secret'
  15 +TEMPLATE_DIRS = ('%s/elasticutils/templates' % ROOT,)

0 comments on commit 92e63c9

Please sign in to comment.
Something went wrong with that request. Please try again.