Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed #30344 -- Converted serve static to class base views. #11193

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions django/conf/urls/static.py
Expand Up @@ -4,10 +4,10 @@
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from django.urls import re_path
from django.views.static import serve
from django.views.static import ServeStatic


def static(prefix, view=serve, **kwargs):
def static(prefix, view=ServeStatic, **kwargs):
"""
Return a URL pattern for serving files in debug mode.

Expand Down
5 changes: 3 additions & 2 deletions django/contrib/staticfiles/handlers.py
Expand Up @@ -3,7 +3,7 @@

from django.conf import settings
from django.contrib.staticfiles import utils
from django.contrib.staticfiles.views import serve
from django.contrib.staticfiles.views import Serve
from django.core.handlers.exception import response_for_exception
from django.core.handlers.wsgi import WSGIHandler, get_path_info

Expand Down Expand Up @@ -48,7 +48,8 @@ def file_path(self, url):

def serve(self, request):
"""Serve the request path."""
return serve(request, self.file_path(request.path), insecure=True)
serve = Serve(insecure=True)
return serve.get(request, self.file_path(request.path))

def get_response(self, request):
from django.http import Http404
Expand Down
4 changes: 2 additions & 2 deletions django/contrib/staticfiles/urls.py
@@ -1,6 +1,6 @@
from django.conf import settings
from django.conf.urls.static import static
from django.contrib.staticfiles.views import serve
from django.contrib.staticfiles.views import Serve

urlpatterns = []

Expand All @@ -11,7 +11,7 @@ def staticfiles_urlpatterns(prefix=None):
"""
if prefix is None:
prefix = settings.STATIC_URL
return static(prefix, view=serve)
return static(prefix, view=Serve.as_view())


# Only append if urlpatterns are empty
Expand Down
57 changes: 32 additions & 25 deletions django/contrib/staticfiles/views.py
Expand Up @@ -9,31 +9,38 @@
from django.conf import settings
from django.contrib.staticfiles import finders
from django.http import Http404
from django.views import static
from django.views.static import ServeStatic


def serve(request, path, insecure=False, **kwargs):
"""
Serve static files below a given point in the directory structure or
from locations inferred from the staticfiles finders.

To use, put a URL pattern such as::

from django.contrib.staticfiles import views

url(r'^(?P<path>.*)$', views.serve)

in your URLconf.

It uses the django.views.static.serve() view to serve the found files.
"""
if not settings.DEBUG and not insecure:
raise Http404
normalized_path = posixpath.normpath(path).lstrip('/')
absolute_path = finders.find(normalized_path)
if not absolute_path:
if path.endswith('/') or path == '':
raise Http404("Directory indexes are not allowed here.")
raise Http404("'%s' could not be found" % path)
document_root, path = os.path.split(absolute_path)
return static.serve(request, path, document_root=document_root, **kwargs)
assert False


class Serve(ServeStatic):
insecure = False

def get(self, request, path):
"""
Serve static files below a given point in the directory structure or
from locations inferred from the staticfiles finders.

To use, put a URL pattern such as::

from django.contrib.staticfiles import views

url(r'^(?P<path>.*)$', views.serve)

in your URLconf.

It uses the django.views.static.serve() view to serve the found files.
"""
if not settings.DEBUG and not self.insecure:
raise Http404
normalized_path = posixpath.normpath(path).lstrip('/')
absolute_path = finders.find(normalized_path)
if not absolute_path:
if path.endswith('/') or path == '':
raise Http404("Directory indexes are not allowed here.")
raise Http404("'%s' could not be found" % path)
self.document_root, path = os.path.split(absolute_path)
return super().get(request, path)
6 changes: 4 additions & 2 deletions django/test/testcases.py
Expand Up @@ -39,7 +39,7 @@
)
from django.utils.decorators import classproperty
from django.utils.deprecation import RemovedInDjango31Warning
from django.views.static import serve
from django.views.static import ServeStatic

__all__ = ('TestCase', 'TransactionTestCase',
'SimpleTestCase', 'skipIfDBFeature', 'skipUnlessDBFeature')
Expand Down Expand Up @@ -1328,7 +1328,9 @@ def serve(self, request):
# invokes staticfiles' finders functionality.
# TODO: Modify if/when that internal API is refactored
final_rel_path = os_rel_path.replace('\\', '/').lstrip('/')
return serve(request, final_rel_path, document_root=self.get_base_dir())
serve_static = ServeStatic(document_root=self.get_base_dir())

return serve_static.get(request, final_rel_path)

def __call__(self, environ, start_response):
if not self._should_handle(get_path_info(environ)):
Expand Down
180 changes: 92 additions & 88 deletions django/views/static.py
Expand Up @@ -14,95 +14,99 @@
from django.utils._os import safe_join
from django.utils.http import http_date, parse_http_date
from django.utils.translation import gettext as _, gettext_lazy


def serve(request, path, document_root=None, show_indexes=False):
"""
Serve static files below a given point in the directory structure.

To use, put a URL pattern such as::

from django.views.static import serve

url(r'^(?P<path>.*)$', serve, {'document_root': '/path/to/my/files/'})

in your URLconf. You must provide the ``document_root`` param. You may
also set ``show_indexes`` to ``True`` if you'd like to serve a basic index
of the directory. This index view will use the template hardcoded below,
but if you'd like to override it, you can create a template called
``static/directory_index.html``.
from django.views import View


class ServeStatic(View):
document_root = None
show_indexes = False

def get(self, request, path):
"""
Serve static files below a given point in the directory structure.

To use, put a URL pattern such as::

from django.views.static import serve

url(r'^(?P<path>.*)$', serve, {'document_root': '/path/to/my/files/'})

in your URLconf. You must provide the ``document_root`` param. You may
also set ``show_indexes`` to ``True`` if you'd like to serve a basic index
of the directory. This index view will use the template hardcoded below,
but if you'd like to override it, you can create a template called
``static/directory_index.html``.
"""
path = posixpath.normpath(path).lstrip('/')
fullpath = Path(safe_join(self.document_root, path))
if fullpath.is_dir():
if self.show_indexes:
return self.directory_index(path, fullpath)
raise Http404(_("Directory indexes are not allowed here."))
if not fullpath.exists():
raise Http404(_('"%(path)s" does not exist') % {'path': fullpath})
# Respect the If-Modified-Since header.
statobj = fullpath.stat()
if not was_modified_since(request.META.get('HTTP_IF_MODIFIED_SINCE'),
statobj.st_mtime, statobj.st_size):
return HttpResponseNotModified()
content_type, encoding = mimetypes.guess_type(str(fullpath))
content_type = content_type or 'application/octet-stream'
response = FileResponse(fullpath.open('rb'), content_type=content_type)
response["Last-Modified"] = http_date(statobj.st_mtime)
if encoding:
response["Content-Encoding"] = encoding
return response

DEFAULT_DIRECTORY_INDEX_TEMPLATE = """
{% load i18n %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<meta http-equiv="Content-Language" content="en-us">
<meta name="robots" content="NONE,NOARCHIVE">
<title>{% blocktrans %}Index of {{ directory }}{% endblocktrans %}</title>
</head>
<body>
<h1>{% blocktrans %}Index of {{ directory }}{% endblocktrans %}</h1>
<ul>
{% if directory != "/" %}
<li><a href="../">../</a></li>
{% endif %}
{% for f in file_list %}
<li><a href="{{ f|urlencode }}">{{ f }}</a></li>
{% endfor %}
</ul>
</body>
</html>
"""
path = posixpath.normpath(path).lstrip('/')
fullpath = Path(safe_join(document_root, path))
if fullpath.is_dir():
if show_indexes:
return directory_index(path, fullpath)
raise Http404(_("Directory indexes are not allowed here."))
if not fullpath.exists():
raise Http404(_('"%(path)s" does not exist') % {'path': fullpath})
# Respect the If-Modified-Since header.
statobj = fullpath.stat()
if not was_modified_since(request.META.get('HTTP_IF_MODIFIED_SINCE'),
statobj.st_mtime, statobj.st_size):
return HttpResponseNotModified()
content_type, encoding = mimetypes.guess_type(str(fullpath))
content_type = content_type or 'application/octet-stream'
response = FileResponse(fullpath.open('rb'), content_type=content_type)
response["Last-Modified"] = http_date(statobj.st_mtime)
if encoding:
response["Content-Encoding"] = encoding
return response


DEFAULT_DIRECTORY_INDEX_TEMPLATE = """
{% load i18n %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<meta http-equiv="Content-Language" content="en-us">
<meta name="robots" content="NONE,NOARCHIVE">
<title>{% blocktrans %}Index of {{ directory }}{% endblocktrans %}</title>
</head>
<body>
<h1>{% blocktrans %}Index of {{ directory }}{% endblocktrans %}</h1>
<ul>
{% if directory != "/" %}
<li><a href="../">../</a></li>
{% endif %}
{% for f in file_list %}
<li><a href="{{ f|urlencode }}">{{ f }}</a></li>
{% endfor %}
</ul>
</body>
</html>
"""
template_translatable = gettext_lazy("Index of %(directory)s")


def directory_index(path, fullpath):
try:
t = loader.select_template([
'static/directory_index.html',
'static/directory_index',
])
except TemplateDoesNotExist:
t = Engine(libraries={'i18n': 'django.templatetags.i18n'}).from_string(DEFAULT_DIRECTORY_INDEX_TEMPLATE)
c = Context()
else:
c = {}
files = []
for f in fullpath.iterdir():
if not f.name.startswith('.'):
url = str(f.relative_to(fullpath))
if f.is_dir():
url += '/'
files.append(url)
c.update({
'directory': path + '/',
'file_list': files,
})
return HttpResponse(t.render(c))
template_translatable = gettext_lazy("Index of %(directory)s")

def directory_index(self, path, fullpath):
try:
t = loader.select_template([
'static/directory_index.html',
'static/directory_index',
])
except TemplateDoesNotExist:
t = Engine(libraries={'i18n': 'django.templatetags.i18n'}).from_string(
self.DEFAULT_DIRECTORY_INDEX_TEMPLATE)
c = Context()
else:
c = {}
files = []
for f in fullpath.iterdir():
if not f.name.startswith('.'):
url = str(f.relative_to(fullpath))
if f.is_dir():
url += '/'
files.append(url)
c.update({
'directory': path + '/',
'file_list': files,
})
return HttpResponse(t.render(c))


def was_modified_since(header=None, mtime=0, size=0):
Expand Down
5 changes: 2 additions & 3 deletions tests/admin_scripts/urls.py
@@ -1,11 +1,10 @@
import os

from django.urls import path
from django.views.static import serve
from django.views.static import ServeStatic

here = os.path.dirname(__file__)

urlpatterns = [
path('custom_templates/<path:path>', serve, {
'document_root': os.path.join(here, 'custom_templates')}),
path('custom_templates/<path:path>', ServeStatic.as_view(document_root=os.path.join(here, 'custom_templates'))),
]
2 changes: 1 addition & 1 deletion tests/staticfiles_tests/urls/default.py
Expand Up @@ -2,5 +2,5 @@
from django.urls import re_path

urlpatterns = [
re_path('^static/(?P<path>.*)$', views.serve),
re_path('^static/(?P<path>.*)$', views.Serve.as_view()),
]
2 changes: 1 addition & 1 deletion tests/view_tests/urls.py
Expand Up @@ -48,7 +48,7 @@
path('jsoni18n/', i18n.JSONCatalog.as_view(packages=['view_tests'])),

# Static views
re_path(r'^site_media/(?P<path>.*)$', static.serve, {'document_root': media_dir, 'show_indexes': True}),
re_path(r'^site_media/(?P<path>.*)$', static.ServeStatic.as_view(document_root=media_dir, show_indexes=True)),
]

urlpatterns += i18n_patterns(
Expand Down