""" Views and functions for serving static files. These are only to be used during development, and SHOULD NOT be used in a production setting. """ import mimetypes import posixpath import re from pathlib import Path from django.http import FileResponse, Http404, HttpResponse, HttpResponseNotModified from django.template import Context, Engine, TemplateDoesNotExist, loader from django.utils._os import safe_join from django.utils.http import http_date, parse_http_date from django.utils.translation import gettext as _ from django.utils.translation import 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 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(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.headers["Last-Modified"] = http_date(statobj.st_mtime) if encoding: response.headers["Content-Encoding"] = encoding return response DEFAULT_DIRECTORY_INDEX_TEMPLATE = """ {% load i18n %} {% blocktranslate %}Index of {{ directory }}{% endblocktranslate %}

{% blocktranslate %}Index of {{ directory }}{% endblocktranslate %}

""" 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)) def was_modified_since(header=None, mtime=0, size=0): """ Was something modified since the user last downloaded it? header This is the value of the If-Modified-Since header. If this is None, I'll just return True. mtime This is the modification time of the item we're talking about. size This is the size of the item we're talking about. """ try: if header is None: raise ValueError matches = re.match(r"^([^;]+)(; length=([0-9]+))?$", header, re.IGNORECASE) header_mtime = parse_http_date(matches[1]) header_len = matches[3] if header_len and int(header_len) != size: raise ValueError if int(mtime) > header_mtime: raise ValueError except (AttributeError, ValueError, OverflowError): return True return False