static.py 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  1. """
  2. Views and functions for serving static files. These are only to be used
  3. during development, and SHOULD NOT be used in a production setting.
  4. """
  5. import mimetypes
  6. import posixpath
  7. from pathlib import Path
  8. from django.http import FileResponse, Http404, HttpResponse, HttpResponseNotModified
  9. from django.template import Context, Engine, TemplateDoesNotExist, loader
  10. from django.utils._os import safe_join
  11. from django.utils.http import http_date, parse_http_date
  12. from django.utils.translation import gettext as _
  13. from django.utils.translation import gettext_lazy
  14. def builtin_template_path(name):
  15. """
  16. Return a path to a builtin template.
  17. Avoid calling this function at the module level or in a class-definition
  18. because __file__ may not exist, e.g. in frozen environments.
  19. """
  20. return Path(__file__).parent / "templates" / name
  21. def serve(request, path, document_root=None, show_indexes=False):
  22. """
  23. Serve static files below a given point in the directory structure.
  24. To use, put a URL pattern such as::
  25. from django.views.static import serve
  26. path('<path:path>', serve, {'document_root': '/path/to/my/files/'})
  27. in your URLconf. You must provide the ``document_root`` param. You may
  28. also set ``show_indexes`` to ``True`` if you'd like to serve a basic index
  29. of the directory. This index view will use the template hardcoded below,
  30. but if you'd like to override it, you can create a template called
  31. ``static/directory_index.html``.
  32. """
  33. path = posixpath.normpath(path).lstrip("/")
  34. fullpath = Path(safe_join(document_root, path))
  35. if fullpath.is_dir():
  36. if show_indexes:
  37. return directory_index(path, fullpath)
  38. raise Http404(_("Directory indexes are not allowed here."))
  39. if not fullpath.exists():
  40. raise Http404(_("“%(path)s” does not exist") % {"path": fullpath})
  41. # Respect the If-Modified-Since header.
  42. statobj = fullpath.stat()
  43. if not was_modified_since(
  44. request.META.get("HTTP_IF_MODIFIED_SINCE"), statobj.st_mtime
  45. ):
  46. return HttpResponseNotModified()
  47. content_type, encoding = mimetypes.guess_type(str(fullpath))
  48. content_type = content_type or "application/octet-stream"
  49. response = FileResponse(fullpath.open("rb"), content_type=content_type)
  50. response.headers["Last-Modified"] = http_date(statobj.st_mtime)
  51. if encoding:
  52. response.headers["Content-Encoding"] = encoding
  53. return response
  54. # Translatable string for static directory index template title.
  55. template_translatable = gettext_lazy("Index of %(directory)s")
  56. def directory_index(path, fullpath):
  57. try:
  58. t = loader.select_template(
  59. [
  60. "static/directory_index.html",
  61. "static/directory_index",
  62. ]
  63. )
  64. except TemplateDoesNotExist:
  65. with builtin_template_path("directory_index.html").open(encoding="utf-8") as fh:
  66. t = Engine(libraries={"i18n": "django.templatetags.i18n"}).from_string(
  67. fh.read()
  68. )
  69. c = Context()
  70. else:
  71. c = {}
  72. files = []
  73. for f in fullpath.iterdir():
  74. if not f.name.startswith("."):
  75. url = str(f.relative_to(fullpath))
  76. if f.is_dir():
  77. url += "/"
  78. files.append(url)
  79. c.update(
  80. {
  81. "directory": path + "/",
  82. "file_list": files,
  83. }
  84. )
  85. return HttpResponse(t.render(c))
  86. def was_modified_since(header=None, mtime=0):
  87. """
  88. Was something modified since the user last downloaded it?
  89. header
  90. This is the value of the If-Modified-Since header. If this is None,
  91. I'll just return True.
  92. mtime
  93. This is the modification time of the item we're talking about.
  94. """
  95. try:
  96. if header is None:
  97. raise ValueError
  98. header_mtime = parse_http_date(header)
  99. if int(mtime) > header_mtime:
  100. raise ValueError
  101. except (ValueError, OverflowError):
  102. return True
  103. return False