from functools import wraps from django.core.handlers.wsgi import WSGIRequest from django.core.signals import got_request_exception from django.template import TemplateDoesNotExist from django.utils.functional import curry from django.test import signals from django.test.client import Client, store_rendered_templates from django.core.handlers.base import BaseHandler def new_init(f): """ We need to add a handler for returning the request instead of the response and initialize a variable that will tell us whether a particular get/post/put/delete is supposed to return the HttpResponse or the WSGIRequest """ @wraps(f) def wrapper(self, **defaults): # It's an unbound function at this point, so we hook it with what we # get off the wrapper f(self, **defaults) self._return_request_instead = False self.handler_request = ClientHandler() return wrapper def switch_client_request_instead(self): """ Allows us to throw an internal switch in the Client to return the next request as WSGIRequest instead of the HttpResponse """ self._return_request_instead = True return self def new_request(f): """ Handles actually creating a WSGIRequest instead of an HttpResponse if we are switched into that mode. Will call the original request instead if needed. """ def wrapper(self, **request): if not self._return_request_instead: # We are not supposed to return the request object itself, but do # the normal Django test client request return self.original_request(**request) # So we are switched into a mode where we need to return the request # instead of the response, we need to set the test client back to # response-returning normality so the next call to this client doesn't # continue to return WSGIRequest objects self._return_request_instead = False # Rip-off of the Django test client, but changed to return a fake WSGIRequest object environ = { 'HTTP_COOKIE': self.cookies.output(header='', sep='; '), 'PATH_INFO': '/', 'QUERY_STRING': '', 'REMOTE_ADDR': '127.0.0.1', 'REQUEST_METHOD': 'GET', 'SCRIPT_NAME': '', 'SERVER_NAME': 'testserver', 'SERVER_PORT': '80', 'SERVER_PROTOCOL': 'HTTP/1.1', 'wsgi.version': (1,0), 'wsgi.url_scheme': 'http', 'wsgi.errors': self.errors, 'wsgi.multiprocess': True, 'wsgi.multithread': False, 'wsgi.run_once': False, } environ.update(self.defaults) environ.update(request) # Curry a data dictionary into an instance of the template renderer # callback function. data = {} on_template_render = curry(store_rendered_templates, data) signals.template_rendered.connect(on_template_render) # Capture exceptions created by the handler. got_request_exception.connect(self.store_exc_info) try: request = self.handler_request(environ) except TemplateDoesNotExist, e: # If the view raises an exception, Django will attempt to show # the 500.html template. If that template is not available, # we should ignore the error in favor of re-raising the # underlying exception that caused the 500 error. Any other # template found to be missing during view error handling # should be reported as-is. if e.args != ('500.html',): raise return request return wrapper class ClientHandler(BaseHandler): """ A HTTP Handler that can be used for testing purposes. Uses the WSGI interface to compose requests, but returns the raw HttpResponse object """ def __call__(self, environ): from django.conf import settings from django.core import signals # Set up middleware if needed. We couldn't do this earlier, because # settings weren't available. if self._request_middleware is None: self.load_middleware() signals.request_started.send(sender=self.__class__) try: request = WSGIRequest(environ) response = self.get_response(request) # Apply response middleware. for middleware_method in self._response_middleware: response = middleware_method(request, response) response = self.apply_response_fixes(request, response) finally: signals.request_finished.disconnect(close_connection) signals.request_finished.send(sender=self.__class__) signals.request_finished.connect(close_connection) return request # Time to do some monkey-patching. # # This allows us to do this with the client object # # >>> client.get('/') # # >>> client.request_from.get('/') # # setattr(Client, 'request_from', property(switch_client_request_instead)) setattr(Client, '__init__', new_init(Client.__init__)) setattr(Client, 'original_request', Client.request) setattr(Client, 'request', new_request(Client.request))