Serving staticfiles (.js, .css, media, ...) is an important job of a server.
There is different solutions for serving static files in production. For example ngnix gives an easy to follow guide for serving static content on their documentation ( at: https://docs.nginx.com/nginx/admin-guide/web-server/serving-static-content/ ). Using ngnix is a good choice for static file serving.
You shouldn't use django for serving static files in production. As stated ( here: https://docs.djangoproject.com/en/4.0/howto/static-files/ ) django is inefficient and probably insecure.
Any server should function same on development as production. The problem is, using ngnix during early development is not pretty. Also django has development server and built-in tools for development purposes. Therefor it is acceptable to use django to serve static content on development. Django has some modules for that.
Every HTTP request/response has headers to pass additional information with body. That way client and server can communicate better. Static files are no different. Headers are necessary for client or server to understand "what is the content?" and "what to do with content?".
Cross-Origin Resource Sharing (CORS) is used when you try to load content from a different (domain, scheme, or port) website.
For example website-a has a resource "image.png" that website-b requests to use. Since website-a and website-b has different origins this is called CORS.
Modern browsers handles that for client side but acording to standards, servers should handle it for requests and responses.
Server handles that via HTTP headers.
Some CORS related request headers are:
- Access-Control-Request-Method (indicates which method CORS requests can use)
- Access-Control-Request-Headers (indicates which headers CORS requests can use)
Some CORS related response headers are:
- Access-Control-Allow-Origin (indicates which origins CORS response will be allowed)
- Access-Control-Allow-Credentials (indicates (related to credentials) if CORS response can be allowed)
There are more headers related to CORS in HTTP but will not be used at this project. (further information: https://fetch.spec.whatwg.org/#http-cors-protocol)
For example client requests resource "image.png" from website-a. Does the client want to display image.png on browser? Does the client want to download image.png? Client can not know what to do with content by just content itself. There is a HTTP header for indicating the aimed use of content:
- Content-Disposition (indicates if content is for inline display on browser or download and save)
(further information: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition)
Django has some methods to handle/serve static files on development. In this article static files (media) will be served with django's built-in modules.
You need:
- Python virtual environment-
- django package-
- django project-
-ready to follow. (See setup.MD for help if needed.)
Create a directory for static files inside first dsh folder:
# C:\...\source\dsh>
mkdir static
Creating sub-directories for styling (.css), scripts (.js) and media (images etc.) is a better practice! There is no sub-directory because there is only media in this tutorial.
Add static settings to dsh/dsh/settings.py as :
# - Before -
STATIC_URL = '/static/'
# - - -
# - After -
STATIC_URL = '/static/'
STATICPATHX = BASE_DIR / "static"
STATICFILES_DIRS = [
STATICPATHX,
]
# - - -
Add static urls to dsh/dsh/urls.py as :
# - Before -
from django.contrib import admin
from django.urls import path
urlpatterns = [
path('admin/', admin.site.urls),
]
# - - -
# - After -
from django.contrib import admin
from django.urls import path
from django.conf import settings
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
urlpatterns = [
path('admin/', admin.site.urls),
]
if settings.DEBUG:
urlpatterns += staticfiles_urlpatterns(settings.STATIC_URL)
# - - -
Add any image to static directory at ".../dsh/static"
Run the server with:
# C:\...\source\dsh>
# makemigrations
python manage.py makemigrations
# migrate
python manage.py migrate
# start development server
python manage.py runserver
Output should be something like that:
Django version 3.2.12, using settings 'dsh.settings'
Starting development server at http://0.0.0.0:8000/
Quit the server with CTRL-BREAK.
Link to that server is:
http://....:PORT
PORT = 8000
http://....:8000/ = http://localhost:8000/
Visit http://localhost:PORT
, PORT is 8000 almost everytime.
(link: http://localhost:8000/)
Get the name of an image file on static folder (example: test1.png).
└───static
test1.png
test2.png
Create the url to that image as:
base_url = http://localhost:8000/
staticurl = http://localhost:8000/static
imageurl = http://localhost:8000/static/test1.png
Visit imageurl (change image1.png part as your file name).
(example link: http://localhost:8000/static/test1.png)
Image should be displayed by browser without any problem.
Django as default does not contain necessary headers for CORS.
To test this, create a simpleserver directory and CORS.html file inside that directory as:
<!-- simpleserver/CORS.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Django-Cors</title>
</head>
<body>
<script>
const run = async () => {
// Create new image object
let Img = new Image();
// Fetch image from server
// Change test1.png with any filename from dsh/static
let response = await fetch("http://localhost:8000/static/test1.png");
// Create url for fetched image
response = await response.blob();
let rurl = await URL.createObjectURL(response);
// Set src for image object as fetched image's url
Img.src = await rurl;
Img.alt = "CORS Image"
// Show image
Img.onload = () => {
document.body.appendChild(Img)
}
}
// this will create a image from fetched url
run();
</script>
</body>
</html>
Serve the html with python:
# C:\...\simpleserver>
python -m http.server 7800
Visit http://localhost:7800/CORS.html
This should render a blank page because there is an error.
Visit dev console on browser. Error should be something like this:
> Access to fetch at 'http://localhost:8000/static/test1.png' from origin 'http://localhost:7800' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
> Failed to load resource: net::ERR_FAILED
...
For CORS to work, atleast Access-Control-Allow-Origin header is needed on response. Since django handles static files we can't simply add this header.
Django handles static files with a built-in function. See dsh/dsh/urls.py
:
# ... (irrelevant code)
from django.contrib.staticfiles.urls import staticfiles_urlpatterns # built-in function for static files
# This function is imported from:
# - django.contrib.staticfiles.url -
# ... (irrelevant code)
if settings.DEBUG:
urlpatterns += staticfiles_urlpatterns(settings.STATIC_URL)
The function can be traced from django module files. For virtualenv django module files are located as Lib/site-packages/django
Since function is from "contrib.staticfiles.urls" see django/contrib/staticfiles/urls.py
# ... (irrelevant code)
from django.conf.urls.static import static
# this function is imported from:
# - conf.urls.static -
from django.contrib.staticfiles.views import serve
# this function is imported from:
# - contrib.staticfiles.views -
def staticfiles_urlpatterns(prefix=None):
# ... (irrelevant code)
# conf.urls.static.static function is returned
return static(prefix, view=serve)
There is two path to check.
django/conf/urls/static.py
django/contrib/staticfiles/views.py
See both.
1)See django/conf/urls/static.py
:
# ... (irrelevant code)
from django.views.static import serve
# this function is imported from:
# - views.static -
def static(prefix, view=serve, **kwargs):
# Note that view = django.views.static.serve as default
# but view = contrib.staticfiles.views.serve was given at django/contrib/staticfiles/urls.py
# ... (irrelevant code)
# returns re_path for urls.py with serve function as views from django.views.static
return [
re_path(r'^%s(?P<path>.*)$' % re.escape(prefix.lstrip('/')), view, kwargs=kwargs),
]
2)See django/contrib/staticfiles/views.py
:
# ... (irrelevant code)
from django.views import static
# this is imported from:
# - views.static -
# and used as
# - views.static.serve as it was in django/conf/urls/static.py-
def serve(request, path, insecure=False, **kwargs):
# ... (irrelevant code)
# return views.static.serve function
return static.serve(request, path, document_root=document_root, **kwargs)
django/views/static.py
was imported at both modules. CORS headers can be added on that module.
Add header to django/views/static.py
:
from django.http import (
FileResponse, Http404, HttpResponse, HttpResponseNotModified,
)
# FileResponse function is imported
def serve(request, path, document_root=None, show_indexes=False):
path = posixpath.normpath(path).lstrip('/')
fullpath = Path(safe_join(document_root, path))
# ... (irrelevant code)
# response = FileResponse(...)
# Add header for CORS
response.headers["Access-Control-Allow-Origin"] = "*"
# - this wont be called if DEBUG = False but
# - if setttings.DEBUG: can be added for additional safety
# - import django.conf.settings for settings.DEBUG
# - - -
# ... (irrelevant code)
return response
# ... (irrelevant code)
Test CORS.html again. It works now.
Content-Disposition header is used for telling client "what to do with content".
Normally adding download attribute to html anchor element (<a href="..." download></a>
) works fine for downloading the content from that link. And html image element (<img src="...">
) will display content from that link.
Create a directory for downloadable files inside first dsh/static folder:
# C:\...\source\dsh\static>
mkdir download
Add any image to download directory at ".../dsh/static/download"
Create download.html on simpleserverdirectory:
<!-- simpleserver/download.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<a download href="http://localhost:8000/static/download/download.png"></a>
<!-- Change download.png with any filename from dsh/static/download -->
</body>
</html>
Visit http://localhost:7800/download.html
Click on download. Image should be opened on browser instead of downloading and saving.
For download to work, Content-Disposition header is needed on response. Since django handles static files we can't simply add this header.
Add header to django/views/static.py
again:
from django.http import (
FileResponse, Http404, HttpResponse, HttpResponseNotModified,
)
# FileResponse function is imported
def serve(request, path, document_root=None, show_indexes=False):
path = posixpath.normpath(path).lstrip('/')
fullpath = Path(safe_join(document_root, path))
# ... (irrelevant code)
# response = FileResponse(...)
if fullpath.parent.name == "download":
response.headers["Content-Disposition"] = "attachment"
# Add header for CORS
response.headers["Access-Control-Allow-Origin"] = "*"
# - this wont be called if DEBUG = False but
# - if setttings.DEBUG: can be added for additional safety
# - import django.conf.settings for settings.DEBUG
# - - -
# Add header for download
if "download" in str(path):
response.headers["Content-Disposition"] = "attachment"
# - this wont be called if DEBUG = False but
# - if setttings.DEBUG: can be added for additional safety
# - import django.conf.settings for settings.DEBUG
# - - -
# ... (irrelevant code)
return response
# ... (irrelevant code)
Visit http://localhost:7800/download.html
Click on download. Image should be downloaded.