From e909ceae9b3e72b72e0a2baaa92bba9714f18cd2 Mon Sep 17 00:00:00 2001 From: Ramiro Morales Date: Sat, 1 Jun 2013 14:24:46 -0300 Subject: Made django.test.testcases not depend on staticfiles contrib app. Do this by introducing a django.contrib.staticfiles.testing.StaticLiveServerCase unittest TestCase subclass. Fixes #20739. --- django/contrib/staticfiles/testing.py | 14 ++++++ django/contrib/staticfiles/views.py | 2 +- django/test/testcases.py | 92 +++++++++++++++++++++++++++++------ 3 files changed, 92 insertions(+), 16 deletions(-) create mode 100644 django/contrib/staticfiles/testing.py (limited to 'django') diff --git a/django/contrib/staticfiles/testing.py b/django/contrib/staticfiles/testing.py new file mode 100644 index 0000000000..868323988b --- /dev/null +++ b/django/contrib/staticfiles/testing.py @@ -0,0 +1,14 @@ +from django.test import LiveServerTestCase + +from django.contrib.staticfiles.handlers import StaticFilesHandler + + +class StaticLiveServerCase(LiveServerTestCase): + """ + Extends django.test.LiveServerTestCase to transparently overlay at test + execution-time the assets provided by the staticfiles app finders. This + means you don't need to run collectstatic before or as a part of your tests + setup. + """ + + static_handler = StaticFilesHandler diff --git a/django/contrib/staticfiles/views.py b/django/contrib/staticfiles/views.py index 7ddc6a1bc2..efba1690c0 100644 --- a/django/contrib/staticfiles/views.py +++ b/django/contrib/staticfiles/views.py @@ -27,7 +27,7 @@ def serve(request, path, insecure=False, **kwargs): in your URLconf. - It uses the django.views.static view to serve the found files. + It uses the django.views.static.serve() view to serve the found files. """ if not settings.DEBUG and not insecure: raise Http404 diff --git a/django/test/testcases.py b/django/test/testcases.py index 5b72d4e8b8..2c65901c80 100644 --- a/django/test/testcases.py +++ b/django/test/testcases.py @@ -6,23 +6,25 @@ import errno from functools import wraps import json import os +import posixpath import re import sys -import socket import threading import unittest from unittest import skipIf # Imported here for backward compatibility from unittest.util import safe_repr try: - from urllib.parse import urlsplit, urlunsplit + from urllib.parse import urlsplit, urlunsplit, urlparse, unquote + from urllib.request import url2pathname except ImportError: # Python 2 - from urlparse import urlsplit, urlunsplit + from urlparse import urlsplit, urlunsplit, urlparse + from urllib import url2pathname, unquote from django.conf import settings -from django.contrib.staticfiles.handlers import StaticFilesHandler from django.core import mail from django.core.exceptions import ValidationError, ImproperlyConfigured from django.core.handlers.wsgi import WSGIHandler +from django.core.handlers.base import get_path_info from django.core.management import call_command from django.core.management.color import no_style from django.core.management.commands import flush @@ -933,10 +935,70 @@ class QuietWSGIRequestHandler(WSGIRequestHandler): pass -class _MediaFilesHandler(StaticFilesHandler): +class FSFilesHandler(WSGIHandler): """ - Handler for serving the media files. This is a private class that is - meant to be used solely as a convenience by LiveServerThread. + WSGI middleware that intercepts calls to a directory, as defined by one of + the *_ROOT settings, and serves those files, publishing them under *_URL. + """ + def __init__(self, application): + self.application = application + self.base_url = urlparse(self.get_base_url()) + super(FSFilesHandler, self).__init__() + + def _should_handle(self, path): + """ + Checks if the path should be handled. Ignores the path if: + + * the host is provided as part of the base_url + * the request's path isn't under the media path (or equal) + """ + return path.startswith(self.base_url[2]) and not self.base_url[1] + + def file_path(self, url): + """ + Returns the relative path to the file on disk for the given URL. + """ + relative_url = url[len(self.base_url[2]):] + return url2pathname(relative_url) + + def get_response(self, request): + from django.http import Http404 + + if self._should_handle(request.path): + try: + return self.serve(request) + except Http404: + pass + return super(FSFilesHandler, self).get_response(request) + + def serve(self, request): + os_rel_path = self.file_path(request.path) + final_rel_path = posixpath.normpath(unquote(os_rel_path)).lstrip('/') + return serve(request, final_rel_path, document_root=self.get_base_dir()) + + def __call__(self, environ, start_response): + if not self._should_handle(get_path_info(environ)): + return self.application(environ, start_response) + return super(FSFilesHandler, self).__call__(environ, start_response) + + +class _StaticFilesHandler(FSFilesHandler): + """ + Handler for serving static files. A private class that is meant to be used + solely as a convenience by LiveServerThread. + """ + + def get_base_dir(self): + return settings.STATIC_ROOT + + def get_base_url(self): + return settings.STATIC_URL + + +class _MediaFilesHandler(FSFilesHandler): + """ + Handler for serving the media files. A private class that is meant to be + used solely as a convenience by LiveServerThread. """ def get_base_dir(self): @@ -945,22 +1007,19 @@ class _MediaFilesHandler(StaticFilesHandler): def get_base_url(self): return settings.MEDIA_URL - def serve(self, request): - relative_url = request.path[len(self.base_url[2]):] - return serve(request, relative_url, document_root=self.get_base_dir()) - class LiveServerThread(threading.Thread): """ Thread for running a live http server while the tests are running. """ - def __init__(self, host, possible_ports, connections_override=None): + def __init__(self, host, possible_ports, static_handler, connections_override=None): self.host = host self.port = None self.possible_ports = possible_ports self.is_ready = threading.Event() self.error = None + self.static_handler = static_handler self.connections_override = connections_override super(LiveServerThread, self).__init__() @@ -976,7 +1035,7 @@ class LiveServerThread(threading.Thread): connections[alias] = conn try: # Create the handler for serving static and media files - handler = StaticFilesHandler(_MediaFilesHandler(WSGIHandler())) + handler = self.static_handler(_MediaFilesHandler(WSGIHandler())) # Go through the list of possible ports, hoping that we can find # one that is free to use for the WSGI server. @@ -1028,6 +1087,8 @@ class LiveServerTestCase(TransactionTestCase): other thread can see the changes. """ + static_handler = _StaticFilesHandler + @property def live_server_url(self): return 'http://%s:%s' % ( @@ -1069,8 +1130,9 @@ class LiveServerTestCase(TransactionTestCase): except Exception: msg = 'Invalid address ("%s") for live server.' % specified_address six.reraise(ImproperlyConfigured, ImproperlyConfigured(msg), sys.exc_info()[2]) - cls.server_thread = LiveServerThread( - host, possible_ports, connections_override) + cls.server_thread = LiveServerThread(host, possible_ports, + cls.static_handler, + connections_override=connections_override) cls.server_thread.daemon = True cls.server_thread.start() -- cgit v1.3