From 33d8fcde8a317184a627492f008a4eab9333ed88 Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Wed, 17 Nov 2010 15:36:26 +0000 Subject: [PATCH] Fixed #14693, #14709 -- Backwards incompatible change to rectify the confusion around the STATICFILES_URL and STATICFILES_ROOT settings. * Two new global settings that will be used by -- **but are not limited to** -- the staticfiles app: STATIC_ROOT and STATIC_URL. * Moving the 'django.contrib.staticfiles.templatetags.staticfiles' template tag to the core ('django.templatetags.static') and renaming it to 'get_static_prefix'. * Moving the context processor 'django.contrib.staticfiles.context_processors.staticfiles' to the core ('django.core.context_processors.static') and renaming it to 'static'. * Paths in media definitions will use STATIC_URL as the prefix if the value is not None, and falls back to the previously used MEDIA_URL. Thanks again to the community for constructive criticism and Carl and Russ for sanity-inducing discussions on IRC. git-svn-id: http://code.djangoproject.com/svn/django/trunk@14592 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/conf/global_settings.py | 22 +- django/conf/project_template/settings.py | 12 +- .../contrib/admin/templatetags/adminmedia.py | 10 +- .../contrib/staticfiles/context_processors.py | 6 - django/contrib/staticfiles/handlers.py | 36 +- .../management/commands/collectstatic.py | 4 +- .../management/commands/runserver.py | 2 +- django/contrib/staticfiles/storage.py | 15 +- .../staticfiles/templatetags/__init__.py | 0 .../staticfiles/templatetags/staticfiles.py | 43 -- django/contrib/staticfiles/urls.py | 11 +- django/contrib/staticfiles/utils.py | 16 +- django/core/context_processors.py | 7 + django/core/servers/basehttp.py | 28 +- django/forms/widgets.py | 20 +- django/templatetags/static.py | 84 ++++ django/views/static.py | 4 +- docs/howto/static-files.txt | 60 +-- docs/man/django-admin.1 | 2 +- docs/ref/contrib/staticfiles.txt | 122 ++--- docs/ref/django-admin.txt | 29 -- docs/ref/settings.txt | 60 ++- docs/ref/templates/api.txt | 13 +- docs/releases/1.3-alpha-1.txt | 6 +- docs/releases/1.3-alpha-2.txt | 45 +- docs/releases/1.3.txt | 2 +- docs/releases/index.txt | 1 + docs/topics/forms/media.txt | 41 +- tests/regressiontests/forms/tests/media.py | 460 ++++++++++++++++++ .../staticfiles_tests/tests.py | 50 +- tests/regressiontests/templates/tests.py | 26 +- 31 files changed, 908 insertions(+), 329 deletions(-) delete mode 100644 django/contrib/staticfiles/context_processors.py delete mode 100644 django/contrib/staticfiles/templatetags/__init__.py delete mode 100644 django/contrib/staticfiles/templatetags/staticfiles.py create mode 100644 django/templatetags/static.py diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py index 3aa333ba91..191a463ac8 100644 --- a/django/conf/global_settings.py +++ b/django/conf/global_settings.py @@ -195,9 +195,9 @@ TEMPLATE_CONTEXT_PROCESSORS = ( 'django.core.context_processors.debug', 'django.core.context_processors.i18n', 'django.core.context_processors.media', + 'django.core.context_processors.static', # 'django.core.context_processors.request', 'django.contrib.messages.context_processors.messages', - 'django.contrib.staticfiles.context_processors.staticfiles', ) # Output to use in template system for invalid (e.g. misspelled) variables. @@ -256,13 +256,21 @@ SECRET_KEY = '' DEFAULT_FILE_STORAGE = 'django.core.files.storage.FileSystemStorage' # Absolute filesystem path to the directory that will hold user-uploaded files. -# Example: "/home/media/media.lawrence.com/" +# Example: "/home/media/media.lawrence.com/media/" MEDIA_ROOT = '' # URL that handles the media served from MEDIA_ROOT. -# Example: "http://media.lawrence.com" +# Example: "http://media.lawrence.com/media/" MEDIA_URL = '' +# Absolute path to the directory that holds static files. +# Example: "/home/media/media.lawrence.com/static/" +STATIC_ROOT = '' + +# URL that handles the static files served from STATIC_ROOT. +# Example: "http://media.lawrence.com/static/" +STATIC_URL = None + # List of upload handler classes to be applied in order. FILE_UPLOAD_HANDLERS = ( 'django.core.files.uploadhandler.MemoryFileUploadHandler', @@ -552,14 +560,6 @@ FIXTURE_DIRS = () # STATICFILES # ############### -# Absolute path to the directory that holds media. -# Example: "/home/media/media.lawrence.com/static/" -STATICFILES_ROOT = '' - -# URL that handles the static files served from STATICFILES_ROOT. -# Example: "http://media.lawrence.com/static/" -STATICFILES_URL = '/static/' - # A list of locations of additional static files STATICFILES_DIRS = () diff --git a/django/conf/project_template/settings.py b/django/conf/project_template/settings.py index 1d33dd13c9..e0265579b8 100644 --- a/django/conf/project_template/settings.py +++ b/django/conf/project_template/settings.py @@ -49,16 +49,16 @@ MEDIA_ROOT = '' # URL that handles the media served from MEDIA_ROOT. Make sure to use a # trailing slash if there is a path component (optional in other cases). -# Examples: "http://media.lawrence.com", "http://example.com/media/" +# Examples: "http://media.lawrence.com/media/", "http://example.com/media/" MEDIA_URL = '' -# Absolute path to the directory that holds media. +# Absolute path to the directory that holds static files. # Example: "/home/media/media.lawrence.com/static/" -STATICFILES_ROOT = '' +STATIC_ROOT = '' -# URL that handles the static files served from STATICFILES_ROOT. -# Example: "http://static.lawrence.com/", "http://example.com/static/" -STATICFILES_URL = '/static/' +# URL that handles the static files served from STATIC_ROOT. +# Example: "http://media.lawrence.com/static/" +STATIC_URL = '/static/' # URL prefix for admin media -- CSS, JavaScript and images. # Make sure to use a trailing slash. diff --git a/django/contrib/admin/templatetags/adminmedia.py b/django/contrib/admin/templatetags/adminmedia.py index 5429810eb9..3b1c39b3a7 100644 --- a/django/contrib/admin/templatetags/adminmedia.py +++ b/django/contrib/admin/templatetags/adminmedia.py @@ -1,15 +1,11 @@ from django.template import Library -from django.utils.encoding import iri_to_uri +from django.templatetags.static import PrefixNode register = Library() +@register.simple_tag def admin_media_prefix(): """ Returns the string contained in the setting ADMIN_MEDIA_PREFIX. """ - try: - from django.conf import settings - except ImportError: - return '' - return iri_to_uri(settings.ADMIN_MEDIA_PREFIX) -admin_media_prefix = register.simple_tag(admin_media_prefix) + return PrefixNode.handle_simple("ADMIN_MEDIA_PREFIX") diff --git a/django/contrib/staticfiles/context_processors.py b/django/contrib/staticfiles/context_processors.py deleted file mode 100644 index 3ed00719e1..0000000000 --- a/django/contrib/staticfiles/context_processors.py +++ /dev/null @@ -1,6 +0,0 @@ -from django.conf import settings - -def staticfiles(request): - return { - 'STATICFILES_URL': settings.STATICFILES_URL, - } diff --git a/django/contrib/staticfiles/handlers.py b/django/contrib/staticfiles/handlers.py index 20b04960da..2b7276133f 100644 --- a/django/contrib/staticfiles/handlers.py +++ b/django/contrib/staticfiles/handlers.py @@ -10,46 +10,44 @@ from django.contrib.staticfiles.views import serve class StaticFilesHandler(WSGIHandler): """ WSGI middleware that intercepts calls to the static files directory, as - defined by the STATICFILES_URL setting, and serves those files. + defined by the STATIC_URL setting, and serves those files. """ - def __init__(self, application, media_dir=None): + def __init__(self, application, base_dir=None): self.application = application - if media_dir: - self.media_dir = media_dir + if base_dir: + self.base_dir = base_dir else: - self.media_dir = self.get_media_dir() - self.media_url = urlparse(self.get_media_url()) - if settings.DEBUG: - utils.check_settings() + self.base_dir = self.get_base_dir() + self.base_url = urlparse(self.get_base_url()) super(StaticFilesHandler, self).__init__() - def get_media_dir(self): - return settings.STATICFILES_ROOT + def get_base_dir(self): + return settings.STATIC_ROOT - def get_media_url(self): - return settings.STATICFILES_URL + def get_base_url(self): + if settings.DEBUG: + utils.check_settings() + return settings.STATIC_URL def _should_handle(self, path): """ Checks if the path should be handled. Ignores the path if: - * the host is provided as part of the media_url + * the host is provided as part of the base_url * the request's path isn't under the media path (or equal) - * settings.DEBUG isn't True """ - return (self.media_url[2] != path and - path.startswith(self.media_url[2]) and not self.media_url[1]) + return (self.base_url[2] != path and + path.startswith(self.base_url[2]) and not self.base_url[1]) def file_path(self, url): """ Returns the relative path to the media file on disk for the given URL. - The passed URL is assumed to begin with ``media_url``. If the + The passed URL is assumed to begin with ``base_url``. If the resultant file path is outside the media directory, then a ValueError is raised. """ - # Remove ``media_url``. - relative_url = url[len(self.media_url[2]):] + relative_url = url[len(self.base_url[2]):] return urllib.url2pathname(relative_url) def serve(self, request): diff --git a/django/contrib/staticfiles/management/commands/collectstatic.py b/django/contrib/staticfiles/management/commands/collectstatic.py index d121223595..8882ae20db 100644 --- a/django/contrib/staticfiles/management/commands/collectstatic.py +++ b/django/contrib/staticfiles/management/commands/collectstatic.py @@ -12,7 +12,7 @@ from django.contrib.staticfiles import finders class Command(NoArgsCommand): """ Command that allows to copy or symlink media files from different - locations to the settings.STATICFILES_ROOT. + locations to the settings.STATIC_ROOT. """ option_list = NoArgsCommand.option_list + ( make_option('--noinput', action='store_false', dest='interactive', @@ -85,7 +85,7 @@ Type 'yes' to continue, or 'no' to cancel: """) self.stdout.write("\n%s static file%s %s to '%s'%s.\n" % (actual_count, actual_count != 1 and 's' or '', symlink and 'symlinked' or 'copied', - settings.STATICFILES_ROOT, + settings.STATIC_ROOT, unmodified_count and ' (%s unmodified)' % unmodified_count or '')) diff --git a/django/contrib/staticfiles/management/commands/runserver.py b/django/contrib/staticfiles/management/commands/runserver.py index e13875994b..f4c22598c3 100644 --- a/django/contrib/staticfiles/management/commands/runserver.py +++ b/django/contrib/staticfiles/management/commands/runserver.py @@ -8,7 +8,7 @@ from django.contrib.staticfiles.handlers import StaticFilesHandler class Command(BaseRunserverCommand): option_list = BaseRunserverCommand.option_list + ( make_option('--nostatic', action="store_false", dest='use_static_handler', default=True, - help='Tells Django to NOT automatically serve static files at STATICFILES_URL.'), + help='Tells Django to NOT automatically serve static files at STATIC_URL.'), make_option('--insecure', action="store_true", dest='insecure_serving', default=False, help='Allows serving static files even if DEBUG is False.'), ) diff --git a/django/contrib/staticfiles/storage.py b/django/contrib/staticfiles/storage.py index b4bfea74d4..eb0eabf861 100644 --- a/django/contrib/staticfiles/storage.py +++ b/django/contrib/staticfiles/storage.py @@ -12,21 +12,22 @@ class StaticFilesStorage(FileSystemStorage): Standard file system storage for site media files. The defaults for ``location`` and ``base_url`` are - ``STATICFILES_ROOT`` and ``STATICFILES_URL``. + ``STATIC_ROOT`` and ``STATIC_URL``. """ def __init__(self, location=None, base_url=None, *args, **kwargs): if location is None: - location = settings.STATICFILES_ROOT + location = settings.STATIC_ROOT if base_url is None: - base_url = settings.STATICFILES_URL + base_url = settings.STATIC_URL if not location: raise ImproperlyConfigured("You're using the staticfiles app " - "without having set the STATICFILES_ROOT setting. Set it to " + "without having set the STATIC_ROOT setting. Set it to " "the absolute path of the directory that holds static media.") - if not base_url: + # check for None since we might use a root URL (``/``) + if base_url is None: raise ImproperlyConfigured("You're using the staticfiles app " - "without having set the STATICFILES_URL setting. Set it to " - "URL that handles the files served from STATICFILES_ROOT.") + "without having set the STATIC_URL setting. Set it to " + "URL that handles the files served from STATIC_ROOT.") if settings.DEBUG: utils.check_settings() super(StaticFilesStorage, self).__init__(location, base_url, *args, **kwargs) diff --git a/django/contrib/staticfiles/templatetags/__init__.py b/django/contrib/staticfiles/templatetags/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/django/contrib/staticfiles/templatetags/staticfiles.py b/django/contrib/staticfiles/templatetags/staticfiles.py deleted file mode 100644 index 6153f5a385..0000000000 --- a/django/contrib/staticfiles/templatetags/staticfiles.py +++ /dev/null @@ -1,43 +0,0 @@ -from django import template -from django.utils.encoding import iri_to_uri - -register = template.Library() - -class StaticFilesPrefixNode(template.Node): - - def __init__(self, varname=None): - self.varname = varname - - def render(self, context): - try: - from django.conf import settings - except ImportError: - prefix = '' - else: - prefix = iri_to_uri(settings.STATICFILES_URL) - if self.varname is None: - return prefix - context[self.varname] = prefix - return '' - -@register.tag -def get_staticfiles_prefix(parser, token): - """ - Populates a template variable with the prefix (settings.STATICFILES_URL). - - Usage:: - - {% get_staticfiles_prefix [as varname] %} - - Examples:: - - {% get_staticfiles_prefix %} - {% get_staticfiles_prefix as staticfiles_prefix %} - - """ - tokens = token.contents.split() - if len(tokens) > 1 and tokens[1] != 'as': - raise template.TemplateSyntaxError( - "First argument in '%s' must be 'as'" % tokens[0]) - return StaticFilesPrefixNode(varname=(len(tokens) > 1 and tokens[2] or None)) - diff --git a/django/contrib/staticfiles/urls.py b/django/contrib/staticfiles/urls.py index 70f04f2ec9..aa4ab459d1 100644 --- a/django/contrib/staticfiles/urls.py +++ b/django/contrib/staticfiles/urls.py @@ -18,15 +18,10 @@ def staticfiles_urlpatterns(prefix=None): if not settings.DEBUG: return [] if prefix is None: - prefix = settings.STATICFILES_URL - if not prefix: + prefix = settings.STATIC_URL + if not prefix or '://' in prefix: raise ImproperlyConfigured( - "The prefix for the 'staticfiles_urlpatterns' helper is empty. " - "Make sure the STATICFILES_URL setting is set correctly.") - if '://' in prefix: - raise ImproperlyConfigured( - "The STATICFILES_URL setting is a full URL, not a path and " - "can't be used with the 'staticfiles_urlpatterns' helper.") + "The prefix for the 'staticfiles_urlpatterns' helper is invalid.") if prefix.startswith("/"): prefix = prefix[1:] return patterns('', diff --git a/django/contrib/staticfiles/utils.py b/django/contrib/staticfiles/utils.py index 0071dbd370..428bb69b1e 100644 --- a/django/contrib/staticfiles/utils.py +++ b/django/contrib/staticfiles/utils.py @@ -33,13 +33,13 @@ def get_files(storage, ignore_patterns=[], location=''): def check_settings(): """ - Checks if the MEDIA_(ROOT|URL) and STATICFILES_(ROOT|URL) + Checks if the MEDIA_(ROOT|URL) and STATIC_(ROOT|URL) settings have the same value. """ - if settings.MEDIA_URL == settings.STATICFILES_URL: - raise ImproperlyConfigured("The MEDIA_URL and STATICFILES_URL " - "settings must have individual values") - if ((settings.MEDIA_ROOT and settings.STATICFILES_ROOT) and - (settings.MEDIA_ROOT == settings.STATICFILES_ROOT)): - raise ImproperlyConfigured("The MEDIA_ROOT and STATICFILES_ROOT " - "settings must have individual values") + if settings.MEDIA_URL == settings.STATIC_URL: + raise ImproperlyConfigured("The MEDIA_URL and STATIC_URL " + "settings must have different values") + if ((settings.MEDIA_ROOT and settings.STATIC_ROOT) and + (settings.MEDIA_ROOT == settings.STATIC_ROOT)): + raise ImproperlyConfigured("The MEDIA_ROOT and STATIC_ROOT " + "settings must have different values") diff --git a/django/core/context_processors.py b/django/core/context_processors.py index 7a59728bfc..fa05d425f6 100644 --- a/django/core/context_processors.py +++ b/django/core/context_processors.py @@ -66,6 +66,13 @@ def i18n(request): return context_extras +def static(request): + """ + Adds static-related context variables to the context. + + """ + return {'STATIC_URL': settings.STATIC_URL} + def media(request): """ Adds media-related context variables to the context. diff --git a/django/core/servers/basehttp.py b/django/core/servers/basehttp.py index 9c92a88b44..9c3e90043b 100644 --- a/django/core/servers/basehttp.py +++ b/django/core/servers/basehttp.py @@ -17,8 +17,8 @@ import warnings from django.core.management.color import color_style from django.utils.http import http_date from django.utils._os import safe_join -from django.contrib.staticfiles.handlers import StaticFilesHandler -from django.views import static + +from django.contrib.staticfiles import handlers, views as static __version__ = "0.1" __all__ = ['WSGIServer','WSGIRequestHandler'] @@ -635,19 +635,20 @@ class WSGIRequestHandler(BaseHTTPRequestHandler): sys.stderr.write(msg) -class AdminMediaHandler(StaticFilesHandler): +class AdminMediaHandler(handlers.StaticFilesHandler): """ WSGI middleware that intercepts calls to the admin media directory, as defined by the ADMIN_MEDIA_PREFIX setting, and serves those images. Use this ONLY LOCALLY, for development! This hasn't been tested for security and is not super efficient. - """ - def get_media_dir(self): + This is pending for deprecation since 1.3. + """ + def get_base_dir(self): import django return os.path.join(django.__path__[0], 'contrib', 'admin', 'media') - def get_media_url(self): + def get_base_url(self): from django.conf import settings return settings.ADMIN_MEDIA_PREFIX @@ -655,14 +656,13 @@ class AdminMediaHandler(StaticFilesHandler): """ Returns the path to the media file on disk for the given URL. - The passed URL is assumed to begin with ``media_url``. If the - resultant file path is outside the media directory, then a ValueError + The passed URL is assumed to begin with ``self.base_url``. If the + resulting file path is outside the media directory, then a ValueError is raised. """ - # Remove ``media_url``. - relative_url = url[len(self.media_url[2]):] + relative_url = url[len(self.base_url[2]):] relative_path = urllib.url2pathname(relative_url) - return safe_join(self.media_dir, relative_path) + return safe_join(self.base_dir, relative_path) def serve(self, request): document_root, path = os.path.split(self.file_path(request.path)) @@ -673,10 +673,10 @@ class AdminMediaHandler(StaticFilesHandler): """ Checks if the path should be handled. Ignores the path if: - * the host is provided as part of the media_url - * the request's path isn't under the media path + * the host is provided as part of the base_url + * the request's path isn't under the base path """ - return path.startswith(self.media_url[2]) and not self.media_url[1] + return path.startswith(self.base_url[2]) and not self.base_url[1] def run(addr, port, wsgi_handler): diff --git a/django/forms/widgets.py b/django/forms/widgets.py index cb12586d6c..8eb5fef3e7 100644 --- a/django/forms/widgets.py +++ b/django/forms/widgets.py @@ -1,9 +1,13 @@ """ HTML Widget classes """ +import datetime +from itertools import chain +import time +from urlparse import urljoin +from util import flatatt import django.utils.copycompat as copy -from itertools import chain from django.conf import settings from django.utils.datastructures import MultiValueDict, MergeDict from django.utils.html import escape, conditional_escape @@ -11,10 +15,6 @@ from django.utils.translation import ugettext, ugettext_lazy from django.utils.encoding import StrAndUnicode, force_unicode from django.utils.safestring import mark_safe from django.utils import datetime_safe, formats -import time -import datetime -from util import flatatt -from urlparse import urljoin __all__ = ( 'Media', 'MediaDefiningClass', 'Widget', 'TextInput', 'PasswordInput', @@ -63,10 +63,16 @@ class Media(StrAndUnicode): for path in self._css[medium]] for medium in media]) - def absolute_path(self, path): + def absolute_path(self, path, prefix=None): if path.startswith(u'http://') or path.startswith(u'https://') or path.startswith(u'/'): return path - return urljoin(settings.MEDIA_URL,path) + if prefix is None: + if settings.STATIC_URL is None: + # backwards compatibility + prefix = settings.MEDIA_URL + else: + prefix = settings.STATIC_URL + return urljoin(prefix, path) def __getitem__(self, name): "Returns a Media object that only contains media of the given type" diff --git a/django/templatetags/static.py b/django/templatetags/static.py new file mode 100644 index 0000000000..97d657189b --- /dev/null +++ b/django/templatetags/static.py @@ -0,0 +1,84 @@ +from django import template +from django.utils.encoding import iri_to_uri + +register = template.Library() + +class PrefixNode(template.Node): + + def __repr__(self): + return "" % self.name + + def __init__(self, varname=None, name=None): + if name is None: + raise template.TemplateSyntaxError( + "Prefix nodes must be given a name to return.") + self.varname = varname + self.name = name + + @classmethod + def handle_token(cls, parser, token, name): + """ + Class method to parse prefix node and return a Node. + """ + tokens = token.contents.split() + if len(tokens) > 1 and tokens[1] != 'as': + raise template.TemplateSyntaxError( + "First argument in '%s' must be 'as'" % tokens[0]) + if len(tokens) > 1: + varname = tokens[2] + else: + varname = None + return cls(varname, name) + + @classmethod + def handle_simple(cls, name): + try: + from django.conf import settings + except ImportError: + prefix = '' + else: + prefix = iri_to_uri(getattr(settings, name, '')) + return prefix + + def render(self, context): + prefix = self.handle_simple(self.name) + if self.varname is None: + return prefix + context[self.varname] = prefix + return '' + +@register.tag +def get_static_prefix(parser, token): + """ + Populates a template variable with the static prefix, + ``settings.STATIC_URL``. + + Usage:: + + {% get_static_prefix [as varname] %} + + Examples:: + + {% get_static_prefix %} + {% get_static_prefix as static_prefix %} + + """ + return PrefixNode.handle_token(parser, token, "STATIC_URL") + +@register.tag +def get_media_prefix(parser, token): + """ + Populates a template variable with the static prefix, + ``settings.MEDIA_URL``. + + Usage:: + + {% get_media_prefix [as varname] %} + + Examples:: + + {% get_media_prefix %} + {% get_media_prefix as media_prefix %} + + """ + return PrefixNode.handle_token(parser, token, "MEDIA_URL") diff --git a/django/views/static.py b/django/views/static.py index a4a4c8db7c..2ce886f7ac 100644 --- a/django/views/static.py +++ b/django/views/static.py @@ -17,8 +17,8 @@ from django.http import Http404, HttpResponse, HttpResponseRedirect, HttpRespons from django.template import Template, Context, TemplateDoesNotExist from django.utils.http import http_date -from django.contrib.staticfiles.views import \ - directory_index, was_modified_since, serve as staticfiles_serve +from django.contrib.staticfiles.views import (directory_index, + was_modified_since, serve as staticfiles_serve) def serve(request, path, document_root=None, show_indexes=False, insecure=False): diff --git a/docs/howto/static-files.txt b/docs/howto/static-files.txt index 37f1fc361d..e27cb99901 100644 --- a/docs/howto/static-files.txt +++ b/docs/howto/static-files.txt @@ -50,12 +50,12 @@ Here's the basic usage in a nutshell: First, you'll need to make sure that ``django.contrib.staticfiles`` is in your :setting:`INSTALLED_APPS`. - Next, you'll need to edit :setting:`STATICFILES_ROOT` to point to where + Next, you'll need to edit :setting:`STATIC_ROOT` to point to where you'd like your static media stored. For example:: - STATICFILES_ROOT = "/home/jacob/projects/mysite.com/static_media" + STATIC_ROOT = "/home/jacob/projects/mysite.com/static_media" - You may also want to set the :setting:`STATICFILES_URL` setting at this + You may also want to set the :setting:`STATIC_URL` setting at this time, though the default value (of ``/static/``) is perfect for local development. @@ -69,7 +69,7 @@ Here's the basic usage in a nutshell: ./manage.py collectstatic This'll churn through your static file storage and move them into the - directory given by :setting:`STATICFILES_ROOT`. (This is not necessary + directory given by :setting:`STATIC_ROOT`. (This is not necessary in local development if you are using :djadmin:`runserver` or adding ``staticfiles_urlpatterns`` to your URLconf; see below). @@ -78,7 +78,7 @@ Here's the basic usage in a nutshell: If you're using the built-in development server (the :djadmin:`runserver` management command) and have the :setting:`DEBUG` setting set to ``True``, your staticfiles will automatically be served - from :setting:`STATICFILES_URL` in development. + from :setting:`STATIC_URL` in development. If you are using some other server for local development, you can quickly serve static media locally by adding:: @@ -98,7 +98,7 @@ Here's the basic usage in a nutshell: .. code-block:: html+django - See :ref:`staticfiles-in-templates` for more details, including an alternate method (using a template tag). @@ -115,7 +115,7 @@ the framework see :doc:`the staticfiles reference `. app is to make it easier to keep static files separate from user-uploaded files. For this reason, you will probably want to make your :setting:`MEDIA_ROOT` and :setting:`MEDIA_URL` different from your - :setting:`STATICFILES_ROOT` and :setting:`STATICFILES_URL`. You will need to + :setting:`STATIC_ROOT` and :setting:`STATIC_URL`. You will need to arrange for serving of files in :setting:`MEDIA_ROOT` yourself; ``staticfiles`` does not deal with user-uploaded media at all. @@ -136,7 +136,7 @@ development, and it makes it *very* hard to change where you've deployed your media. If, for example, you wanted to switch to using a content delivery network (CDN), then you'd need to change more or less every single template. -A far better way is to use the value of the :setting:`STATICFILES_URL` setting +A far better way is to use the value of the :setting:`STATIC_URL` setting directly in your templates. This means that a switch of media servers only requires changing that single value. Much better! @@ -147,7 +147,7 @@ With a context processor ------------------------ The included context processor is the easy way. Simply make sure -``'django.contrib.staticfiles.context_processors.staticfiles'`` is in your +``'django.core.context_processors.static'`` is in your :setting:`TEMPLATE_CONTEXT_PROCESSORS`. It's there by default, and if you're editing that setting by hand it should look something like:: @@ -155,18 +155,18 @@ editing that setting by hand it should look something like:: 'django.core.context_processors.debug', 'django.core.context_processors.i18n', 'django.core.context_processors.media', + 'django.core.context_processors.static', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', - 'django.contrib.staticfiles.context_processors.staticfiles', ) -Once that's done, you can refer to :setting:`STATICFILES_URL` in your templates: +Once that's done, you can refer to :setting:`STATIC_URL` in your templates: .. code-block:: html+django - -If ``{{ STATICFILES_URL }}`` isn't working in your template, you're probably not +If ``{{ STATIC_URL }}`` isn't working in your template, you're probably not using :class:`~django.template.RequestContext` when rendering the template. As a brief refresher, context processors add variables into the contexts of @@ -180,23 +180,23 @@ To see how that works, and to read more details, check out With a template tag ------------------- -The second option is the :ttag:`get_staticfiles_prefix` template tag. You can +The second option is the :ttag:`get_static_prefix` template tag. You can use this if you're not using :class:`~django.template.RequestContext`, or if you -need more control over exactly where and how :setting:`STATICFILES_URL` is +need more control over exactly where and how :setting:`STATIC_URL` is injected into the template. Here's an example: .. code-block:: html+django - {% load staticfiles %} - + {% load static %} + There's also a second form you can use to avoid extra processing if you need the value multiple times: .. code-block:: html+django - {% load staticfiles %} - {% get_staticfiles_prefix as STATIC_PREFIX %} + {% load static %} + {% get_static_prefix as STATIC_PREFIX %} @@ -213,7 +213,7 @@ Thus, the ``staticfiles`` app ships with a quick and dirty helper view that you can use to serve files locally in development. This view is automatically enabled and will serve your static files at -:setting:`STATICFILES_URL` when you use the built-in :djadmin:`runserver`. +:setting:`STATIC_URL` when you use the built-in :djadmin:`runserver`. To enable this view if you are using some other server for local development, you'll add a couple of lines to your URLconf. The first line goes at the top of @@ -225,11 +225,11 @@ the file, and the last line at the bottom:: urlpatterns += staticfiles_urlpatterns() -This will inspect your :setting:`STATICFILES_URL` and -:setting:`STATICFILES_ROOT` settings and wire up the view to serve static media +This will inspect your :setting:`STATIC_URL` and +:setting:`STATIC_ROOT` settings and wire up the view to serve static media accordingly. Don't forget to set the :setting:`STATICFILES_DIRS` setting appropriately to let ``django.contrib.staticfiles`` know where to look for -files. +(additional) files. .. warning:: @@ -239,6 +239,9 @@ files. **insecure**. This is only intended for local development, and should **never be used in production**. + Additionally, your :setting:`STATIC_URL` setting can't be either empty + or a full URL such as ``http://static.example.com/``. + For a few more details, including an alternate method of enabling this view, see :ref:`staticfiles-development-view`. @@ -249,7 +252,7 @@ Serving static files in production The basic outline of putting static files into production is simple: run the :djadmin:`collectstatic` command when static media changes, then arrange for the -collected media directory (:setting:`STATICFILES_ROOT`) to be moved to the media +collected media directory (:setting:`STATIC_ROOT`) to be moved to the media server and served. Of course, as with all deployment tasks, the devil's in the details. Every @@ -264,8 +267,8 @@ app, the basic outline gets modified to look something like: * Push your code up to the deployment server. * On the server, run :djadmin:`collectstatic` to move all the media into - :setting:`STATICFILES_ROOT`. - * Point your web server at :setting:`STATICFILES_ROOT`. For example, here's + :setting:`STATIC_ROOT`. + * Point your web server at :setting:`STATIC_ROOT`. For example, here's :ref:`how to do this under Apache and mod_wsgi `. You'll probably want to automate this process, especially if you've got multiple @@ -322,7 +325,7 @@ Since your media server won't be running Django, you'll need to modify the deployment strategy to look something like: * When your media changes, run :djadmin:`collectstatic` locally. - * Push your local :setting:`STATICFILES_ROOT` up to the media server + * Push your local :setting:`STATIC_ROOT` up to the media server into the directory that's being served. ``rsync`` is a good choice for this step since it only needs to transfer the bits of static media that have changed. @@ -403,9 +406,6 @@ you'll need to make a few changes: * The management commands ``build_static`` and ``resolve_static`` are now called :djadmin:`collectstatic` and :djadmin:`findstatic`. - * The settings ``STATIC_URL`` and ``STATIC_ROOT`` were renamed to - :setting:`STATICFILES_URL` and :setting:`STATICFILES_ROOT`. - * The settings ``STATICFILES_PREPEND_LABEL_APPS``, ``STATICFILES_MEDIA_DIRNAMES`` and ``STATICFILES_EXCLUDED_APPS`` were removed. diff --git a/docs/man/django-admin.1 b/docs/man/django-admin.1 index 2197af6650..f9b530a77e 100644 --- a/docs/man/django-admin.1 +++ b/docs/man/django-admin.1 @@ -165,7 +165,7 @@ Do not prompt the user for input. Disable the development server's auto\-reloader. .TP .I \-\-nostatic -Disable automatic serving of static files from STATICFILES_URL. +Disable automatic serving of static files from STATIC_URL. .TP .I \-\-insecure Enables serving of static files even if DEBUG is False. diff --git a/docs/ref/contrib/staticfiles.txt b/docs/ref/contrib/staticfiles.txt index f82fd79024..2cd2282f4e 100644 --- a/docs/ref/contrib/staticfiles.txt +++ b/docs/ref/contrib/staticfiles.txt @@ -23,48 +23,11 @@ Settings .. highlight:: python -The following settings control the behavior of the staticfiles app. Only -:setting:`STATICFILES_ROOT` is required, but you'll probably also need to -configure :setting:`STATICFILES_URL` as well. +.. note:: -.. setting:: STATICFILES_ROOT - -STATICFILES_ROOT ----------------- - -Default: ``''`` (Empty string) - -The absolute path to the directory that the :djadmin:`collectstatic` management -command will collect static files into, for serving from -:setting:`STATICFILES_URL`:: - - STATICFILES_ROOT = "/home/example.com/static/" - -This is a **required setting** unless you've overridden -:setting:`STATICFILES_STORAGE` and are using a custom storage backend. - -This is not a place to store your static files permanently under version -control; you should do that in directories that will be found by your -:setting:`STATICFILES_FINDERS` (by default, per-app ``static/`` subdirectories, -and any directories you include in :setting:`STATICFILES_DIRS`). Files from -those locations will be collected into :setting:`STATICFILES_ROOT`. - -.. setting:: STATICFILES_URL - -STATICFILES_URL ---------------- - -Default: ``'/static/'`` - -The URL that handles the files served from :setting:`STATICFILES_ROOT`, e.g.:: - - STATICFILES_URL = '/site_media/static/' - -... or perhaps:: - - STATICFILES_URL = 'http://static.example.com/' - -This should **always** have a trailing slash. + The following settings control the behavior of the staticfiles app. + Configuring the global settings :setting:`STATIC_ROOT` and + :setting:`STATIC_URL` is **required**. .. setting:: STATICFILES_DIRS @@ -98,7 +61,7 @@ tuples, e.g.:: With this configuration, the :djadmin:`collectstatic` management command would for example collect the stats files in a ``'downloads'`` directory. So -assuming you have :setting:`STATICFILES_URL` set ``'/static/'``, this would +assuming you have :setting:`STATIC_URL` set ``'/static/'``, this would allow you to refer to the file ``'/opt/webfiles/stats/polls_20101022.tar.gz'`` with ``'/static/downloads/polls_20101022.tar.gz'`` in your templates. @@ -153,14 +116,14 @@ Management Commands .. highlight:: console -``django.contrib.staticfiles`` exposes two management commands. +``django.contrib.staticfiles`` exposes three management commands. collectstatic ------------- .. django-admin:: collectstatic -Collects the static files into :setting:`STATICFILES_ROOT`. +Collects the static files into :setting:`STATIC_ROOT`. Duplicate file names are by default resolved in a similar way to how template resolution works: the file that is first found in one of the specified @@ -218,44 +181,76 @@ for each relative path, use the ``--first`` option:: This is a debugging aid; it'll show you exactly which static file will be collected for a given path. +runserver +--------- + +Overrides the core :djadmin:`runserver` command if the ``staticfiles`` app +is :setting:`installed` and adds automatic serving of static +files and the following new options. + +.. django-admin-option:: --nostatic + +Use the ``--nostatic`` option to disable serving of static files with the +:doc:`staticfiles ` app entirely. This option is +only available if the :doc:`staticfiles ` app is +in your project's :setting:`INSTALLED_APPS` setting. + +Example usage:: + + django-admin.py runserver --nostatic + +.. django-admin-option:: --insecure + +Use the ``--insecure`` option to force serving of static files with the +:doc:`staticfiles ` app even if the :setting:`DEBUG` +setting is ``False``. By using this you acknowledge the fact that it's +**grossly inefficient** and probably **insecure**. This is only intended for +local development, should **never be used in production** and is only +available if the :doc:`staticfiles ` app is +in your project's :setting:`INSTALLED_APPS` setting. + +Example usage:: + + django-admin.py runserver --insecure + .. currentmodule:: None Other Helpers ============= -The ``staticfiles`` context processor -------------------------------------- +The ``static`` context processor +-------------------------------- -.. function:: django.contrib.staticfiles.context_processors.staticfiles +.. function:: django.core.context_processors.static -This context processor adds the :setting:`STATICFILES_URL` into each template -context as the variable ``{{ STATICFILES_URL }}``. To use it, make sure that -``'django.contrib.staticfiles.context_processors.staticfiles'`` appears -somewhere in your :setting:`TEMPLATE_CONTEXT_PROCESSORS` setting. +This context processor adds the :setting:`STATIC_URL` into each template +context as the variable ``{{ STATIC_URL }}``. To use it, make sure that +``'django.core.context_processors.static'`` appears somewhere in your +:setting:`TEMPLATE_CONTEXT_PROCESSORS` setting. Remember, only templates rendered with :class:`~django.template.RequestContext` will have acces to the data provided by this (and any) context processor. -.. templatetag:: get_staticfiles_prefix +.. templatetag:: get_static_prefix -The ``get_staticfiles_prefix`` templatetag -========================================== +The ``get_static_prefix`` templatetag +===================================== .. highlight:: html+django If you're not using :class:`~django.template.RequestContext`, or if you need -more control over exactly where and how :setting:`STATICFILES_URL` is injected -into the template, you can use the :ttag:`get_staticfiles_prefix` template tag +more control over exactly where and how :setting:`STATIC_URL` is injected +into the template, you can use the :ttag:`get_static_prefix` template tag instead:: - {% load staticfiles %} - + {% load static %} + There's also a second form you can use to avoid extra processing if you need the value multiple times:: - {% load staticfiles %} - {% get_staticfiles_prefix as STATIC_PREFIX %} + {% load static %} + {% get_static_prefix as STATIC_PREFIX %} @@ -292,7 +287,7 @@ primary URL configuration:: ) Note, the begin of the pattern (``r'^static/'``) should be your -:setting:`STATICFILES_URL` setting. +:setting:`STATIC_URL` setting. Since this is a bit finicky, there's also a helper function that'll do this for you: @@ -307,3 +302,8 @@ already defined pattern list. Use it like this:: urlpatterns += staticfiles_urlpatterns() +.. warning:: + + This helper function will only work if :setting:`DEBUG` is ``True`` + and your :setting:`STATIC_URL` setting is neither empty nor a full + URL such as ``http://static.example.com/``. diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt index 43e55acab2..ea49e2372d 100644 --- a/docs/ref/django-admin.txt +++ b/docs/ref/django-admin.txt @@ -681,35 +681,6 @@ Example usage:: django-admin.py runserver --noreload -.. django-admin-option:: --nostatic - -Use the ``--nostatic`` option to disable serving of static files with the -:doc:`staticfiles ` app entirely. This option is -only available if the :doc:`staticfiles ` app is -in your project's :setting:`INSTALLED_APPS` setting. - -Example usage:: - - django-admin.py runserver --nostatic - -.. django-admin-option:: --insecure - -Use the ``--insecure`` option to force serving of static files with the -:doc:`staticfiles ` app even if the :setting:`DEBUG` -setting is ``False``. By using this you acknowledge the fact that it's -**grossly inefficient** and probably **insecure**. This is only intended for -local development, should **never be used in production** and is only -available if the :doc:`staticfiles ` app is -in your project's :setting:`INSTALLED_APPS` setting. - -See the :doc:`reference documentation of the app ` -for more details and learn how to :doc:`manage and deploy static files -` correctly. - -Example usage:: - - django-admin.py runserver --insecure - Examples of using different ports and addresses ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt index 95e6861bb6..7f4157c363 100644 --- a/docs/ref/settings.txt +++ b/docs/ref/settings.txt @@ -53,10 +53,10 @@ Default: ``'/static/admin/'`` The URL prefix for admin media -- CSS, JavaScript and images used by the Django administrative interface. Make sure to use a trailing slash, and to have this be -different from the :setting:``MEDIA_URL`` setting (since the same URL cannot be +different from the :setting:`MEDIA_URL` setting (since the same URL cannot be mapped onto two different sets of files). For integration with :doc:`staticfiles `, this should be the same as -:setting:`STATICFILES_URL` followed by ``'admin/'``. +:setting:`STATIC_URL` followed by ``'admin/'``. .. setting:: ADMINS @@ -1122,12 +1122,12 @@ Default: ``''`` (Empty string) URL that handles the media served from :setting:`MEDIA_ROOT`, used for :doc:`managing stored files `. -Example: ``"http://media.lawrence.com"`` +Example: ``"http://media.lawrence.com/"`` Note that this should have a trailing slash if it has a path component. - * Good: ``"http://www.example.com/static/"`` - * Bad: ``"http://www.example.com/static"`` + * Good: ``"http://www.example.com/media/"`` + * Bad: ``"http://www.example.com/media"`` MESSAGE_LEVEL ------------- @@ -1486,6 +1486,49 @@ See :doc:`/ref/contrib/sites`. .. _site framework docs: ../sites/ +.. setting:: STATIC_ROOT + +STATIC_ROOT +----------- + +Default: ``''`` (Empty string) + +The absolute path to the directory that contains static content. + +Example: ``"/home/example.com/static/"`` + +When using the :djadmin:`collectstatic` management command of the optional, +:doc:`staticfiles` app this will be used to collect +static files into and served from :setting:`STATIC_URL`. + +In that case this is a **required setting**, unless you've overridden +:setting:`STATICFILES_STORAGE` and are using a custom storage backend. + +This is not a place to store your static files permanently under version +control; you should do that in directories that will be found by your +:setting:`STATICFILES_FINDERS` (by default, per-app ``static/`` subdirectories, +and any directories you include in :setting:`STATICFILES_DIRS`). Files from +those locations will be collected into :setting:`STATIC_ROOT`. + +See :doc:`/ref/contrib/staticfiles` and :setting:`STATIC_URL`. + +.. setting:: STATIC_URL + +STATIC_URL +---------- + +Default: ``None`` + +URL that handles the files served from :setting:`STATIC_ROOT`. + +Example: ``"/site_media/static/"`` or ``"http://static.example.com/"`` + +If not ``None``, this will be used as the base path for +:ref:`media definitions` and the +:doc:`staticfiles app`. + +See :setting:`STATIC_ROOT`. + .. setting:: TEMPLATE_CONTEXT_PROCESSORS TEMPLATE_CONTEXT_PROCESSORS @@ -1496,7 +1539,8 @@ Default:: ("django.contrib.auth.context_processors.auth", "django.core.context_processors.debug", "django.core.context_processors.i18n", - "django.contrib.staticfiles.context_processors.staticfiles", + "django.core.context_processors.media", + "django.core.context_processors.static", "django.contrib.messages.context_processors.messages") A tuple of callables that are used to populate the context in ``RequestContext``. @@ -1513,6 +1557,10 @@ of items to be merged into the context. ``django.core.context_processors.auth`` to ``django.contrib.auth.context_processors.auth``. +.. versionadded:: 1.3 + The ``django.core.context_processors.static`` context processor + was added in this release. + .. setting:: TEMPLATE_DEBUG TEMPLATE_DEBUG diff --git a/docs/ref/templates/api.txt b/docs/ref/templates/api.txt index aa4951eebe..e02debe39b 100644 --- a/docs/ref/templates/api.txt +++ b/docs/ref/templates/api.txt @@ -312,8 +312,8 @@ and return a dictionary of items to be merged into the context. By default, "django.core.context_processors.debug", "django.core.context_processors.i18n", "django.core.context_processors.media", - "django.contrib.messages.context_processors.messages", - "django.contrib.staticfiles.context_processors.staticfiles") + "django.core.context_processors.static", + "django.contrib.messages.context_processors.messages") .. versionadded:: 1.2 In addition to these, ``RequestContext`` always uses @@ -435,6 +435,15 @@ If :setting:`TEMPLATE_CONTEXT_PROCESSORS` contains this processor, every ``RequestContext`` will contain a variable ``MEDIA_URL``, providing the value of the :setting:`MEDIA_URL` setting. +django.core.context_processors.static +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. versionadded:: 1.3 + +If :setting:`TEMPLATE_CONTEXT_PROCESSORS` contains this processor, every +``RequestContext`` will contain a variable ``STATIC_URL``, providing the +value of the :setting:`STATIC_URL` setting. + django.core.context_processors.csrf ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/releases/1.3-alpha-1.txt b/docs/releases/1.3-alpha-1.txt index 596d1c7c2b..d6610d1c0e 100644 --- a/docs/releases/1.3-alpha-1.txt +++ b/docs/releases/1.3-alpha-1.txt @@ -21,7 +21,7 @@ changes`_ and an easy upgrade path from Django 1.2. .. _new features: `What's new in Django 1.3 alpha 1`_ -.. _backwards incompatible changes: backwards-incompatible-changes-1.3_ +.. _backwards incompatible changes: backwards-incompatible-changes-1.3-alpha-1_ What's new in Django 1.3 alpha 1 ================================ @@ -161,7 +161,7 @@ requests. These include: easier to test the database activity associated with a view. -.. _backwards-incompatible-changes-1.3: +.. _backwards-incompatible-changes-1.3-alpha-1: Backwards-incompatible changes in 1.3 alpha 1 ============================================= @@ -270,8 +270,6 @@ local flavors: official designation "Aceh (ACE)". -.. _deprecated-features-1.3: - Features deprecated in 1.3 ========================== diff --git a/docs/releases/1.3-alpha-2.txt b/docs/releases/1.3-alpha-2.txt index 8a9bdcaa88..c8671a618c 100644 --- a/docs/releases/1.3-alpha-2.txt +++ b/docs/releases/1.3-alpha-2.txt @@ -13,10 +13,6 @@ prior to the final 1.3 release. As such, this release is *not* intended for production use, and any such use is discouraged. -.. _new features: `What's new in Django 1.3 alpha 2`_ - -.. _backwards incompatible changes: backwards-incompatible-changes-1.3-alpha-2_ - What's new in Django 1.3 alpha 2 ================================ @@ -43,6 +39,47 @@ See the :doc:`reference documentation of the app ` for more details or learn how to :doc:`manage static files `. +Backwards-incompatible changes in 1.3 alpha 2 +============================================= + +Introduction of STATIC_URL and STATIC_ROOT settings +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The newly introduced :doc:`/ref/contrib/staticfiles` app extends Django's +abilities to handle static app and project files, required the additon of +settings to refer to those files in templates and code, especially in +contrast to the :setting:`MEDIA_URL` and :setting:`MEDIA_ROOT` settings that +refer to user-uploaded files. + +Prior to 1.3 alpha 2 these settings were called ``STATICFILES_URL`` and +``STATICFILES_ROOT`` to follow the naming scheme for app centric settings. +Based on feedback from the community it became apparent that those settings +created confusion, especially given the fact handling static files is also +desired outside the use of the optional ``staticfiles`` app. + +As a result, we take the followig steps to rectify the issue: + + * Two new global settings that will be used by -- **but are not limited + to** -- the :doc:`staticfiles` app: + + * :setting:`STATIC_ROOT` (formally ``STATICFILES_ROOT``) + + * :setting:`STATIC_URL` (formally ``STATICFILES_URL``) + + * Moving the + ``django.contrib.staticfiles.templatetags.staticfiles.get_staticfiles_prefix`` + template tag to the core (``django.templatetags.static``) and renaming + it to :ttag:`get_static_prefix`. + + * Moving the context processor + ``django.contrib.staticfiles.context_processors.staticfiles`` to the + core (``django.core.context_processors.static``) and renaming it to + :func:`~django.core.context_processors.static`. + + * :ref:`form-media-paths` will use :setting:`STATIC_URL` as the prefix + **if the value is not None**, and falls back to the previously used + :setting:`MEDIA_URL`. + The Django 1.3 roadmap ====================== diff --git a/docs/releases/1.3.txt b/docs/releases/1.3.txt index 553fcc5793..20c98f29c4 100644 --- a/docs/releases/1.3.txt +++ b/docs/releases/1.3.txt @@ -59,7 +59,7 @@ In previous versions of Django, it was common to place static assets in app is to make it easier to keep static files separate from user-uploaded files. For this reason, you will probably want to make your :setting:`MEDIA_ROOT` and :setting:`MEDIA_URL` different from your -:setting:`STATICFILES_ROOT` and :setting:`STATICFILES_URL`. You will need to +:setting:`STATIC_ROOT` and :setting:`STATIC_URL`. You will need to arrange for serving of files in :setting:`MEDIA_ROOT` yourself; ``staticfiles`` does not deal with user-uploaded media at all. diff --git a/docs/releases/index.txt b/docs/releases/index.txt index 11d6933706..e2ec6eb062 100644 --- a/docs/releases/index.txt +++ b/docs/releases/index.txt @@ -64,6 +64,7 @@ notes. .. toctree:: :maxdepth: 1 + 1.3-alpha-2 1.3-alpha-1 1.2-rc-1 1.2-beta-1 diff --git a/docs/topics/forms/media.txt b/docs/topics/forms/media.txt index 548095d83b..b0f3c86107 100644 --- a/docs/topics/forms/media.txt +++ b/docs/topics/forms/media.txt @@ -190,28 +190,51 @@ also be defined in a dynamic fashion:: See the section on `Media objects`_ for more details on how to construct return values for dynamic media properties. +.. _form-media-paths: + Paths in media definitions -------------------------- +.. versionchanged:: 1.3 + Paths used to specify media can be either relative or absolute. If a path starts with '/', 'http://' or 'https://', it will be interpreted as an absolute path, and left as-is. All other paths will be prepended with the value of -``settings.MEDIA_URL``. For example, if the MEDIA_URL for your site was -``http://media.example.com/``:: +the appropriate prefix. - class CalendarWidget(forms.TextInput): - class Media: - css = { - 'all': ('/css/pretty.css',), - } - js = ('animations.js', 'http://othersite.com/actions.js') +As part of the introduction of the +:doc:`staticfiles app ` two new settings were added +to refer to "static content" (images, CSS, Javascript, etc.) that are needed +to render a complete web page: :setting:`STATIC_URL` and :setting:`STATIC_ROOT`. + +To find the appropriate prefix to use, Django will check if the +:setting:`STATIC_URL` setting is not ``None`` and automatically fall back +to using :setting:`MEDIA_URL`. For example, if the :setting:`MEDIA_URL` for +your site was ``'http://uploads.example.com/'`` and :setting:`STATIC_URL` +was ``None``:: + + >>> class CalendarWidget(forms.TextInput): + class Media: + css = { + 'all': ('/css/pretty.css',), + } + js = ('animations.js', 'http://othersite.com/actions.js') >>> w = CalendarWidget() >>> print w.media - + +But if :setting:`STATIC_URL` is ``'http://static.example.com/'``:: + + >>> w = CalendarWidget() + >>> print w.media + + + + + Media objects ------------- diff --git a/tests/regressiontests/forms/tests/media.py b/tests/regressiontests/forms/tests/media.py index 9a552a31da..fd30029741 100644 --- a/tests/regressiontests/forms/tests/media.py +++ b/tests/regressiontests/forms/tests/media.py @@ -458,3 +458,463 @@ class FormsMediaTestCase(TestCase): """) + + +class StaticFormsMediaTestCase(TestCase): + # Tests for the media handling on widgets and forms + def setUp(self): + super(StaticFormsMediaTestCase, self).setUp() + self.original_media_url = settings.MEDIA_URL + self.original_static_url = settings.STATIC_URL + settings.MEDIA_URL = 'http://media.example.com/static/' + settings.STATIC_URL = 'http://media.example.com/static/' + + def tearDown(self): + settings.MEDIA_URL = self.original_media_url + settings.STATIC_URL = self.original_static_url + super(StaticFormsMediaTestCase, self).tearDown() + + def test_construction(self): + # Check construction of media objects + m = Media(css={'all': ('path/to/css1','/path/to/css2')}, js=('/path/to/js1','http://media.other.com/path/to/js2','https://secure.other.com/path/to/js3')) + self.assertEqual(str(m), """ + + + +""") + + class Foo: + css = { + 'all': ('path/to/css1','/path/to/css2') + } + js = ('/path/to/js1','http://media.other.com/path/to/js2','https://secure.other.com/path/to/js3') + + m3 = Media(Foo) + self.assertEqual(str(m3), """ + + + +""") + + # A widget can exist without a media definition + class MyWidget(TextInput): + pass + + w = MyWidget() + self.assertEqual(str(w.media), '') + + def test_media_dsl(self): + ############################################################### + # DSL Class-based media definitions + ############################################################### + + # A widget can define media if it needs to. + # Any absolute path will be preserved; relative paths are combined + # with the value of settings.MEDIA_URL + class MyWidget1(TextInput): + class Media: + css = { + 'all': ('path/to/css1','/path/to/css2') + } + js = ('/path/to/js1','http://media.other.com/path/to/js2','https://secure.other.com/path/to/js3') + + w1 = MyWidget1() + self.assertEqual(str(w1.media), """ + + + +""") + + # Media objects can be interrogated by media type + self.assertEqual(str(w1.media['css']), """ +""") + + self.assertEqual(str(w1.media['js']), """ + +""") + + def test_combine_media(self): + # Media objects can be combined. Any given media resource will appear only + # once. Duplicated media definitions are ignored. + class MyWidget1(TextInput): + class Media: + css = { + 'all': ('path/to/css1','/path/to/css2') + } + js = ('/path/to/js1','http://media.other.com/path/to/js2','https://secure.other.com/path/to/js3') + + class MyWidget2(TextInput): + class Media: + css = { + 'all': ('/path/to/css2','/path/to/css3') + } + js = ('/path/to/js1','/path/to/js4') + + class MyWidget3(TextInput): + class Media: + css = { + 'all': ('/path/to/css3','path/to/css1') + } + js = ('/path/to/js1','/path/to/js4') + + w1 = MyWidget1() + w2 = MyWidget2() + w3 = MyWidget3() + self.assertEqual(str(w1.media + w2.media + w3.media), """ + + + + + +""") + + # Check that media addition hasn't affected the original objects + self.assertEqual(str(w1.media), """ + + + +""") + + # Regression check for #12879: specifying the same CSS or JS file + # multiple times in a single Media instance should result in that file + # only being included once. + class MyWidget4(TextInput): + class Media: + css = {'all': ('/path/to/css1', '/path/to/css1')} + js = ('/path/to/js1', '/path/to/js1') + + w4 = MyWidget4() + self.assertEqual(str(w4.media), """ +""") + + def test_media_property(self): + ############################################################### + # Property-based media definitions + ############################################################### + + # Widget media can be defined as a property + class MyWidget4(TextInput): + def _media(self): + return Media(css={'all': ('/some/path',)}, js = ('/some/js',)) + media = property(_media) + + w4 = MyWidget4() + self.assertEqual(str(w4.media), """ +""") + + # Media properties can reference the media of their parents + class MyWidget5(MyWidget4): + def _media(self): + return super(MyWidget5, self).media + Media(css={'all': ('/other/path',)}, js = ('/other/js',)) + media = property(_media) + + w5 = MyWidget5() + self.assertEqual(str(w5.media), """ + + +""") + + def test_media_property_parent_references(self): + # Media properties can reference the media of their parents, + # even if the parent media was defined using a class + class MyWidget1(TextInput): + class Media: + css = { + 'all': ('path/to/css1','/path/to/css2') + } + js = ('/path/to/js1','http://media.other.com/path/to/js2','https://secure.other.com/path/to/js3') + + class MyWidget6(MyWidget1): + def _media(self): + return super(MyWidget6, self).media + Media(css={'all': ('/other/path',)}, js = ('/other/js',)) + media = property(_media) + + w6 = MyWidget6() + self.assertEqual(str(w6.media), """ + + + + + +""") + + def test_media_inheritance(self): + ############################################################### + # Inheritance of media + ############################################################### + + # If a widget extends another but provides no media definition, it inherits the parent widget's media + class MyWidget1(TextInput): + class Media: + css = { + 'all': ('path/to/css1','/path/to/css2') + } + js = ('/path/to/js1','http://media.other.com/path/to/js2','https://secure.other.com/path/to/js3') + + class MyWidget7(MyWidget1): + pass + + w7 = MyWidget7() + self.assertEqual(str(w7.media), """ + + + +""") + + # If a widget extends another but defines media, it extends the parent widget's media by default + class MyWidget8(MyWidget1): + class Media: + css = { + 'all': ('/path/to/css3','path/to/css1') + } + js = ('/path/to/js1','/path/to/js4') + + w8 = MyWidget8() + self.assertEqual(str(w8.media), """ + + + + + +""") + + def test_media_inheritance_from_property(self): + # If a widget extends another but defines media, it extends the parents widget's media, + # even if the parent defined media using a property. + class MyWidget1(TextInput): + class Media: + css = { + 'all': ('path/to/css1','/path/to/css2') + } + js = ('/path/to/js1','http://media.other.com/path/to/js2','https://secure.other.com/path/to/js3') + + class MyWidget4(TextInput): + def _media(self): + return Media(css={'all': ('/some/path',)}, js = ('/some/js',)) + media = property(_media) + + class MyWidget9(MyWidget4): + class Media: + css = { + 'all': ('/other/path',) + } + js = ('/other/js',) + + w9 = MyWidget9() + self.assertEqual(str(w9.media), """ + + +""") + + # A widget can disable media inheritance by specifying 'extend=False' + class MyWidget10(MyWidget1): + class Media: + extend = False + css = { + 'all': ('/path/to/css3','path/to/css1') + } + js = ('/path/to/js1','/path/to/js4') + + w10 = MyWidget10() + self.assertEqual(str(w10.media), """ + + +""") + + def test_media_inheritance_extends(self): + # A widget can explicitly enable full media inheritance by specifying 'extend=True' + class MyWidget1(TextInput): + class Media: + css = { + 'all': ('path/to/css1','/path/to/css2') + } + js = ('/path/to/js1','http://media.other.com/path/to/js2','https://secure.other.com/path/to/js3') + + class MyWidget11(MyWidget1): + class Media: + extend = True + css = { + 'all': ('/path/to/css3','path/to/css1') + } + js = ('/path/to/js1','/path/to/js4') + + w11 = MyWidget11() + self.assertEqual(str(w11.media), """ + + + + + +""") + + def test_media_inheritance_single_type(self): + # A widget can enable inheritance of one media type by specifying extend as a tuple + class MyWidget1(TextInput): + class Media: + css = { + 'all': ('path/to/css1','/path/to/css2') + } + js = ('/path/to/js1','http://media.other.com/path/to/js2','https://secure.other.com/path/to/js3') + + class MyWidget12(MyWidget1): + class Media: + extend = ('css',) + css = { + 'all': ('/path/to/css3','path/to/css1') + } + js = ('/path/to/js1','/path/to/js4') + + w12 = MyWidget12() + self.assertEqual(str(w12.media), """ + + + +""") + + def test_multi_media(self): + ############################################################### + # Multi-media handling for CSS + ############################################################### + + # A widget can define CSS media for multiple output media types + class MultimediaWidget(TextInput): + class Media: + css = { + 'screen, print': ('/file1','/file2'), + 'screen': ('/file3',), + 'print': ('/file4',) + } + js = ('/path/to/js1','/path/to/js4') + + multimedia = MultimediaWidget() + self.assertEqual(str(multimedia.media), """ + + + + +""") + + def test_multi_widget(self): + ############################################################### + # Multiwidget media handling + ############################################################### + + class MyWidget1(TextInput): + class Media: + css = { + 'all': ('path/to/css1','/path/to/css2') + } + js = ('/path/to/js1','http://media.other.com/path/to/js2','https://secure.other.com/path/to/js3') + + class MyWidget2(TextInput): + class Media: + css = { + 'all': ('/path/to/css2','/path/to/css3') + } + js = ('/path/to/js1','/path/to/js4') + + class MyWidget3(TextInput): + class Media: + css = { + 'all': ('/path/to/css3','path/to/css1') + } + js = ('/path/to/js1','/path/to/js4') + + # MultiWidgets have a default media definition that gets all the + # media from the component widgets + class MyMultiWidget(MultiWidget): + def __init__(self, attrs=None): + widgets = [MyWidget1, MyWidget2, MyWidget3] + super(MyMultiWidget, self).__init__(widgets, attrs) + + mymulti = MyMultiWidget() + self.assertEqual(str(mymulti.media), """ + + + + + +""") + + def test_form_media(self): + ############################################################### + # Media processing for forms + ############################################################### + + class MyWidget1(TextInput): + class Media: + css = { + 'all': ('path/to/css1','/path/to/css2') + } + js = ('/path/to/js1','http://media.other.com/path/to/js2','https://secure.other.com/path/to/js3') + + class MyWidget2(TextInput): + class Media: + css = { + 'all': ('/path/to/css2','/path/to/css3') + } + js = ('/path/to/js1','/path/to/js4') + + class MyWidget3(TextInput): + class Media: + css = { + 'all': ('/path/to/css3','path/to/css1') + } + js = ('/path/to/js1','/path/to/js4') + + # You can ask a form for the media required by its widgets. + class MyForm(Form): + field1 = CharField(max_length=20, widget=MyWidget1()) + field2 = CharField(max_length=20, widget=MyWidget2()) + f1 = MyForm() + self.assertEqual(str(f1.media), """ + + + + + +""") + + # Form media can be combined to produce a single media definition. + class AnotherForm(Form): + field3 = CharField(max_length=20, widget=MyWidget3()) + f2 = AnotherForm() + self.assertEqual(str(f1.media + f2.media), """ + + + + + +""") + + # Forms can also define media, following the same rules as widgets. + class FormWithMedia(Form): + field1 = CharField(max_length=20, widget=MyWidget1()) + field2 = CharField(max_length=20, widget=MyWidget2()) + class Media: + js = ('/some/form/javascript',) + css = { + 'all': ('/some/form/css',) + } + f3 = FormWithMedia() + self.assertEqual(str(f3.media), """ + + + + + + + +""") + + # Media works in templates + from django.template import Template, Context + self.assertEqual(Template("{{ form.media.js }}{{ form.media.css }}").render(Context({'form': f3})), """ + + + + + + +""") + diff --git a/tests/regressiontests/staticfiles_tests/tests.py b/tests/regressiontests/staticfiles_tests/tests.py index 0a6c060664..6cd581fff2 100644 --- a/tests/regressiontests/staticfiles_tests/tests.py +++ b/tests/regressiontests/staticfiles_tests/tests.py @@ -23,8 +23,8 @@ class StaticFilesTestCase(TestCase): Test case with a couple utility assertions. """ def setUp(self): - self.old_staticfiles_url = settings.STATICFILES_URL - self.old_staticfiles_root = settings.STATICFILES_ROOT + self.old_static_url = settings.STATIC_URL + self.old_static_root = settings.STATIC_ROOT self.old_staticfiles_dirs = settings.STATICFILES_DIRS self.old_staticfiles_finders = settings.STATICFILES_FINDERS self.old_media_root = settings.MEDIA_ROOT @@ -40,8 +40,8 @@ class StaticFilesTestCase(TestCase): settings.DEBUG = True settings.MEDIA_ROOT = os.path.join(site_media, 'media') settings.MEDIA_URL = '/media/' - settings.STATICFILES_ROOT = os.path.join(site_media, 'static') - settings.STATICFILES_URL = '/static/' + settings.STATIC_ROOT = os.path.join(site_media, 'static') + settings.STATIC_URL = '/static/' settings.ADMIN_MEDIA_PREFIX = '/static/admin/' settings.STATICFILES_DIRS = ( os.path.join(TEST_ROOT, 'project', 'documents'), @@ -52,6 +52,7 @@ class StaticFilesTestCase(TestCase): 'django.contrib.staticfiles.finders.DefaultStorageFinder', ) settings.INSTALLED_APPS = [ + "django.contrib.staticfiles", "regressiontests.staticfiles_tests", ] @@ -65,8 +66,8 @@ class StaticFilesTestCase(TestCase): settings.MEDIA_ROOT = self.old_media_root settings.MEDIA_URL = self.old_media_url settings.ADMIN_MEDIA_PREFIX = self.old_admin_media_prefix - settings.STATICFILES_ROOT = self.old_staticfiles_root - settings.STATICFILES_URL = self.old_staticfiles_url + settings.STATIC_ROOT = self.old_static_root + settings.STATIC_URL = self.old_static_url settings.STATICFILES_DIRS = self.old_staticfiles_dirs settings.STATICFILES_FINDERS = self.old_staticfiles_finders settings.INSTALLED_APPS = self.old_installed_apps @@ -91,13 +92,13 @@ class BuildStaticTestCase(StaticFilesTestCase): def setUp(self): super(BuildStaticTestCase, self).setUp() self.old_staticfiles_storage = settings.STATICFILES_STORAGE - self.old_root = settings.STATICFILES_ROOT - settings.STATICFILES_ROOT = tempfile.mkdtemp() + self.old_root = settings.STATIC_ROOT + settings.STATIC_ROOT = tempfile.mkdtemp() self.run_collectstatic() def tearDown(self): - shutil.rmtree(settings.STATICFILES_ROOT) - settings.STATICFILES_ROOT = self.old_root + shutil.rmtree(settings.STATIC_ROOT) + settings.STATIC_ROOT = self.old_root super(BuildStaticTestCase, self).tearDown() def run_collectstatic(self, **kwargs): @@ -106,7 +107,7 @@ class BuildStaticTestCase(StaticFilesTestCase): def _get_file(self, filepath): assert filepath, 'filepath is empty.' - filepath = os.path.join(settings.STATICFILES_ROOT, filepath) + filepath = os.path.join(settings.STATIC_ROOT, filepath) f = open(filepath) try: return f.read() @@ -231,7 +232,7 @@ class TestBuildStaticDryRun(BuildStaticTestCase): """ With --dry-run, no files created in destination dir. """ - self.assertEquals(os.listdir(settings.STATICFILES_ROOT), []) + self.assertEquals(os.listdir(settings.STATIC_ROOT), []) if sys.platform != 'win32': @@ -251,7 +252,7 @@ if sys.platform != 'win32': With ``--link``, symbolic links are created. """ - self.failUnless(os.path.islink(os.path.join(settings.STATICFILES_ROOT, 'test.txt'))) + self.failUnless(os.path.islink(os.path.join(settings.STATIC_ROOT, 'test.txt'))) class TestServeStatic(StaticFilesTestCase): @@ -262,7 +263,7 @@ class TestServeStatic(StaticFilesTestCase): def _response(self, filepath): return self.client.get( - posixpath.join(settings.STATICFILES_URL, filepath)) + posixpath.join(settings.STATIC_URL, filepath)) def assertFileContains(self, filepath, text): self.assertContains(self._response(filepath), text) @@ -372,24 +373,3 @@ class TestMiscFinder(TestCase): finders.get_finder, "django.contrib.staticfiles.finders.FooBarFinder") self.assertRaises(ImproperlyConfigured, finders.get_finder, "foo.bar.FooBarFinder") - - -class TemplateTagTest(TestCase): - def test_get_staticfiles_prefix(self): - """ - Test the get_staticfiles_prefix helper return the STATICFILES_URL setting. - """ - self.assertEquals(Template( - "{% load staticfiles %}" - "{% get_staticfiles_prefix %}" - ).render(Context()), settings.STATICFILES_URL) - - def test_get_staticfiles_prefix_with_as(self): - """ - Test the get_staticfiles_prefix helper return the STATICFILES_URL setting. - """ - self.assertEquals(Template( - "{% load staticfiles %}" - "{% get_staticfiles_prefix as staticfiles_prefix %}" - "{{ staticfiles_prefix }}" - ).render(Context()), settings.STATICFILES_URL) diff --git a/tests/regressiontests/templates/tests.py b/tests/regressiontests/templates/tests.py index 29171ebf08..ea93beb69e 100644 --- a/tests/regressiontests/templates/tests.py +++ b/tests/regressiontests/templates/tests.py @@ -114,6 +114,16 @@ class UTF8Class: return u'ŠĐĆŽćžšđ'.encode('utf-8') class Templates(unittest.TestCase): + def setUp(self): + self.old_static_url = settings.STATIC_URL + self.old_media_url = settings.MEDIA_URL + settings.STATIC_URL = u"/static/" + settings.MEDIA_URL = u"/media/" + + def tearDown(self): + settings.STATIC_URL = self.old_static_url + settings.MEDIA_URL = self.old_media_url + def test_loaders_security(self): ad_loader = app_directories.Loader() fs_loader = filesystem.Loader() @@ -1328,24 +1338,28 @@ class Templates(unittest.TestCase): 'autoescape-filtertag01': ("{{ first }}{% filter safe %}{{ first }} x"}, template.TemplateSyntaxError), # ifqeual compares unescaped vales. - 'autoescape-ifequal01': ('{% ifequal var "this & that" %}yes{% endifequal %}', { "var": "this & that" }, "yes" ), + 'autoescape-ifequal01': ('{% ifequal var "this & that" %}yes{% endifequal %}', { "var": "this & that" }, "yes"), # Arguments to filters are 'safe' and manipulate their input unescaped. 'autoescape-filters01': ('{{ var|cut:"&" }}', { "var": "this & that" }, "this that" ), - 'autoescape-filters02': ('{{ var|join:" & \" }}', { "var": ("Tom", "Dick", "Harry") }, "Tom & Dick & Harry" ), + 'autoescape-filters02': ('{{ var|join:" & \" }}', { "var": ("Tom", "Dick", "Harry") }, "Tom & Dick & Harry"), # Literal strings are safe. - 'autoescape-literals01': ('{{ "this & that" }}',{}, "this & that" ), + 'autoescape-literals01': ('{{ "this & that" }}',{}, "this & that"), # Iterating over strings outputs safe characters. - 'autoescape-stringiterations01': ('{% for l in var %}{{ l }},{% endfor %}', {'var': 'K&R'}, "K,&,R," ), + 'autoescape-stringiterations01': ('{% for l in var %}{{ l }},{% endfor %}', {'var': 'K&R'}, "K,&,R,"), # Escape requirement survives lookup. - 'autoescape-lookup01': ('{{ var.key }}', { "var": {"key": "this & that" }}, "this & that" ), + 'autoescape-lookup01': ('{{ var.key }}', { "var": {"key": "this & that" }}, "this & that"), + # Static template tags + 'static-prefixtag01': ('{% load static %}{% get_static_prefix %}', {}, settings.STATIC_URL), + 'static-prefixtag02': ('{% load static %}{% get_static_prefix as static_prefix %}{{ static_prefix }}', {}, settings.STATIC_URL), + 'static-prefixtag03': ('{% load static %}{% get_media_prefix %}', {}, settings.MEDIA_URL), + 'static-prefixtag04': ('{% load static %}{% get_media_prefix as media_prefix %}{{ media_prefix }}', {}, settings.MEDIA_URL), } - class TemplateTagLoading(unittest.TestCase): def setUp(self):