Loading django/contrib/staticfiles/testing.py 0 → 100644 +14 −0 Original line number Diff line number Diff line 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 django/contrib/staticfiles/views.py +1 −1 Original line number Diff line number Diff line Loading @@ -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 Loading django/test/testcases.py +77 −15 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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): Loading @@ -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__() Loading @@ -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. Loading Loading @@ -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' % ( Loading Loading @@ -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() Loading docs/howto/static-files/index.txt +27 −0 Original line number Diff line number Diff line Loading @@ -100,6 +100,33 @@ this by adding the following snippet to your urls.py:: the given prefix is local (e.g. ``/static/``) and not a URL (e.g. ``http://static.example.com/``). .. _staticfiles-testing-support: Testing ======= When running tests that use actual HTTP requests instead of the built-in testing client (i.e. when using the built-in :class:`LiveServerTestCase <django.test.LiveServerTestCase>`) the static assets need to be served along the rest of the content so the test environment reproduces the real one as faithfully as possible, but ``LiveServerTestCase`` has only very basic static file-serving functionality: It doesn't know about the finders feature of the ``staticfiles`` application and assumes the static content has already been collected under :setting:`STATIC_ROOT`. Because of this, ``staticfiles`` ships its own :class:`django.contrib.staticfiles.testing.StaticLiveServerCase`, a subclass of the built-in one that has the ability to transparently serve all the assets during execution of these tests in a way very similar to what we get at development time with ``DEBUG = True``, i.e. without having to collect them using :djadmin:`collectstatic` first. .. versionadded:: 1.7 :class:`django.contrib.staticfiles.testing.StaticLiveServerCase` is new in Django 1.7. Previously its functionality was provided by :class:`django.test.LiveServerTestCase`. Deployment ========== Loading docs/ref/contrib/staticfiles.txt +23 −0 Original line number Diff line number Diff line Loading @@ -406,3 +406,26 @@ files in app directories. That's because this view is **grossly inefficient** and probably **insecure**. This is only intended for local development, and should **never be used in production**. Specialized test case to support 'live testing' ----------------------------------------------- .. class:: testing.StaticLiveServerCase This unittest TestCase subclass extends :class:`django.test.LiveServerTestCase`. Just like its parent, you can use it to write tests that involve running the code under test and consuming it with testing tools through HTTP (e.g. Selenium, PhantomJS, etc.), because of which it's needed that the static assets are also published. But given the fact that it makes use of the :func:`django.contrib.staticfiles.views.serve` view described above, it can transparently overlay at test execution-time the assets provided by the ``staticfiles`` finders. This means you don't need to run :djadmin:`collectstatic` before or as a part of your tests setup. .. versionadded:: 1.7 ``StaticLiveServerCase`` is new in Django 1.7. Previously its functionality was provided by :class:`django.test.LiveServerTestCase`. Loading
django/contrib/staticfiles/testing.py 0 → 100644 +14 −0 Original line number Diff line number Diff line 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
django/contrib/staticfiles/views.py +1 −1 Original line number Diff line number Diff line Loading @@ -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 Loading
django/test/testcases.py +77 −15 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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): Loading @@ -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__() Loading @@ -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. Loading Loading @@ -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' % ( Loading Loading @@ -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() Loading
docs/howto/static-files/index.txt +27 −0 Original line number Diff line number Diff line Loading @@ -100,6 +100,33 @@ this by adding the following snippet to your urls.py:: the given prefix is local (e.g. ``/static/``) and not a URL (e.g. ``http://static.example.com/``). .. _staticfiles-testing-support: Testing ======= When running tests that use actual HTTP requests instead of the built-in testing client (i.e. when using the built-in :class:`LiveServerTestCase <django.test.LiveServerTestCase>`) the static assets need to be served along the rest of the content so the test environment reproduces the real one as faithfully as possible, but ``LiveServerTestCase`` has only very basic static file-serving functionality: It doesn't know about the finders feature of the ``staticfiles`` application and assumes the static content has already been collected under :setting:`STATIC_ROOT`. Because of this, ``staticfiles`` ships its own :class:`django.contrib.staticfiles.testing.StaticLiveServerCase`, a subclass of the built-in one that has the ability to transparently serve all the assets during execution of these tests in a way very similar to what we get at development time with ``DEBUG = True``, i.e. without having to collect them using :djadmin:`collectstatic` first. .. versionadded:: 1.7 :class:`django.contrib.staticfiles.testing.StaticLiveServerCase` is new in Django 1.7. Previously its functionality was provided by :class:`django.test.LiveServerTestCase`. Deployment ========== Loading
docs/ref/contrib/staticfiles.txt +23 −0 Original line number Diff line number Diff line Loading @@ -406,3 +406,26 @@ files in app directories. That's because this view is **grossly inefficient** and probably **insecure**. This is only intended for local development, and should **never be used in production**. Specialized test case to support 'live testing' ----------------------------------------------- .. class:: testing.StaticLiveServerCase This unittest TestCase subclass extends :class:`django.test.LiveServerTestCase`. Just like its parent, you can use it to write tests that involve running the code under test and consuming it with testing tools through HTTP (e.g. Selenium, PhantomJS, etc.), because of which it's needed that the static assets are also published. But given the fact that it makes use of the :func:`django.contrib.staticfiles.views.serve` view described above, it can transparently overlay at test execution-time the assets provided by the ``staticfiles`` finders. This means you don't need to run :djadmin:`collectstatic` before or as a part of your tests setup. .. versionadded:: 1.7 ``StaticLiveServerCase`` is new in Django 1.7. Previously its functionality was provided by :class:`django.test.LiveServerTestCase`.