Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why isn't django serving my SPA static files correctly?

I'm building a website using a Django backend and Vuejs frontend. In development I started the backend and the frontend separately with python manage.py runserver and yarn serve respectively. This worked great and I now want to deploy the website. To do this I ran yarn build, which created a dist/ folder in my frontend folder. So my structure was like this:

cockpit
├── backend/
│   ├── cockpit/
│   │   ├── views.py
│   │   ├── css/
│   │   └── etc..
│   ├── settings/
│   │   └── settings.py
│   └── manage.py
└── frontend/
    └── dist/
        ├── index.html
        ├── css/
        └── js/

I now want to serve the sources in frontend/dist/ from my django project so that I can run everything using uwsgi. To do this I'm trying to follow this description. I have the following settings/urls.py

from django.contrib import admin
from django.urls import include, path, re_path
from django.views.generic import TemplateView

urlpatterns = [
    path('admin/', admin.site.urls),
    path('cockpit/', include("cockpit.urls")),
    re_path('', TemplateView.as_view(template_name='index.html')),
]

and set the following settings in my settings.py:

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': ['static'],  # <== ADDED THIS
        'APP_DIRS': True,
        'OPTIONS': {
            # removed to keep this example small
        },
    },
]
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
STATIC_URL = '/static/'
STATICFILES_DIRS = [
    os.path.join(BASE_DIR, '../frontend/dist'),
]
STATIC_ROOT = os.path.join(BASE_DIR, "static/")

print("BASE_DIR:", BASE_DIR)
print("STATIC_ROOT:", STATIC_ROOT)
print("STATICFILES_DIRS:", STATICFILES_DIRS)

And the prints show me this:

BASE_DIR: /home/kramer65/repos/cockpit/backend
STATIC_ROOT: /home/kramer65/repos/cockpit/backend/static/
STATICFILES_DIRS: ['/home/kramer65/repos/cockpit/backend/../frontend/dist']

Then I ran `python manage.py collectstatic:

$ python manage.py collectstatic

150 static files copied to '/home/kramer65/repos/cockpit/backend/static'.

So it now looks like this:

cockpit
├── backend/
│   ├── cockpit/
│   │   ├── views.py
│   │   ├── css/
│   │   └── etc..
│   ├── settings/
│   │   └── settings.py
│   └── manage.py
│   └── static/
│       ├── index.html
│       ├── css/
│       └── js/
└── frontend/
    └── dist/
        ├── index.html
        ├── css/
        └── js/

I tested it by running the (node) http-server from the backend/static/ folder. In the browser the website loads and runs perfect. Below is the output from the command line:

$ http-server
Starting up http-server, serving ./
Available on:
  http://127.0.0.1:8080
  http://192.168.0.104:8080
Hit CTRL-C to stop the server
[2020-05-18T13:50:58.487Z]  "GET /" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:76.0) Gecko/20100101 Firefox/76.0"
(node:5928) [DEP0066] DeprecationWarning: OutgoingMessage.prototype._headers is deprecated
[2020-05-18T13:50:58.671Z]  "GET /css/chunk-vendors.2c7f3eba.css" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:76.0) Gecko/20100101 Firefox/76.0"
[2020-05-18T13:50:58.679Z]  "GET /css/app.e15f06d0.css" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:76.0) Gecko/20100101 Firefox/76.0"
[2020-05-18T13:50:58.681Z]  "GET /js/chunk-vendors.9c409057.js" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:76.0) Gecko/20100101 Firefox/76.0"
[2020-05-18T13:50:58.687Z]  "GET /js/app.c930fce5.js" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:76.0) Gecko/20100101 Firefox/76.0"

I stopped this http-server, started the Django dev server and opened the browser. The terminal shows me this:

$ python manage.py runserver
Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).
May 18, 2020 - 17:57:00
Django version 3.0.6, using settings 'settings.settings'
Starting ASGI/Channels version 2.4.0 development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
HTTP GET / 200 [0.22, 127.0.0.1:33224]
HTTP GET /static/debug_toolbar/css/print.css 200 [0.04, 127.0.0.1:33232]
HTTP GET /static/debug_toolbar/css/toolbar.css 200 [0.05, 127.0.0.1:33234]
HTTP GET /static/debug_toolbar/js/toolbar.js 200 [0.02, 127.0.0.1:33232]
HTTP GET /static/debug_toolbar/js/toolbar.timer.js 200 [0.04, 127.0.0.1:33234]
HTTP GET /js/chunk-vendors.9c409057.js 200 [0.80, 127.0.0.1:33228]
HTTP GET /css/chunk-vendors.2c7f3eba.css 200 [0.94, 127.0.0.1:33224]
HTTP GET /js/app.c930fce5.js 200 [0.98, 127.0.0.1:33230]
HTTP GET /css/app.e15f06d0.css 200 [0.99, 127.0.0.1:33226]
HTTP GET /favicon.ico 200 [0.09, 127.0.0.1:33226]

In the browser console I see the sources seem to be loaded, but some seem to be empty (0 bytes) and the screen doesn't show anything. Below is a screenshot of the results and a screenshot of the Static Files tab in the Django Debug Bar.

Does anybody know why it isn't serving those files correctly in Django?

enter image description here

enter image description here

[EDIT]

I just found that if I change

STATIC_URL = '/static/'

to

STATIC_URL = '/'

it works correctly when I go to http://127.0.0.1:8000/index.html, but now http://127.0.0.1:8000/ gives me this error:

enter image description here

[EDIT 2]

Ok, so following the advice of @geek_life I changed the values in my settings file to:

BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
PROJECT_NAME = 'cockpit'
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, PROJECT_NAME, "static/")
STATICFILES_DIRS = [os.path.join(BASE_DIR, '../frontend/dist')]
print("## BASE_DIR:", BASE_DIR)
print("## STATIC_ROOT:", STATIC_ROOT)
print("## STATICFILES_DIRS:", STATICFILES_DIRS)

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': ['cockpit/static/'],  # <= THIS IS WHAT I CHANGED
        'APP_DIRS': True,
        'OPTIONS': {}  # And here some options
    },
]

Which prints out

## BASE_DIR: /home/kramer65/repos/cockpit/backend
## STATIC_ROOT: /home/kramer65/repos/cockpit/backend/cockpit/static/
## STATICFILES_DIRS: ['/home/kramer65/repos/cockpit/backend/../frontend/dist']

And in the file settings/urls.py I (still) got this:

urlpatterns = [
    path('admin/', admin.site.urls),
    path('cockpit/', include("cockpit.urls")),
    re_path('', TemplateView.as_view(template_name='index.html')),
]

I then copied the static/ folder with the built vuejs-ap from cockpit/backend/ to cockpit/backend/cockpit/.

Unfortunately I still get the same result. The index.html loads, but the js and css files still don't. Any other ideas?

like image 870
kramer65 Avatar asked May 18 '20 18:05

kramer65


People also ask

Which app must install the for use of static file in Django?

django. contrib. staticfiles collects static files from each of your applications (and any other places you specify) into a single location that can easily be served in production.

How do you serve a static file?

To serve static files for Go 1.12+ in the standard environment, you define the handlers in your app. yaml file using either the static_dir or static_files elements. The content in the static files or static directories are unaffected by the scaling settings in your app.

Where are static files located in Django?

STATICFILES_DIRS: By default, static files are stored at the app-level at <APP_NAME>/static/ . The collectstatic command will look for static files in those directories. You can also tell Django to look for static files in additional locations with STATICFILES_DIRS .

How do I add a static file to my Django app?

Make sure that django.contrib.staticfiles is included in your INSTALLED_APPS. In your settings file, define STATIC_URL, for example: In your templates, use the static template tag to build the URL for the given relative path using the configured STATICFILES_STORAGE. Store your static files in a folder called static in your app.

Why can’t i load CSS in Django?

It seems unlikely that this is a Django issue, since you’ve shown that URLs in the link elements are correct, that you can directly load the CSS via a browser request and that the runserver terminal is not showing requests for the CSS when you complete a normal page load.

Is it possible to deploy a static file in production?

This method is grossly inefficient and probably insecure , so it is unsuitable for production. See How to deploy static files for proper strategies to serve static files in production environments. Your project will probably also have static assets that aren’t tied to a particular app.

Where do I put static files in my App?

Store your static files in a folder called static in your app. For example my_app/static/my_app/example.jpg. In addition to these configuration steps, you’ll also need to actually serve the static files.


2 Answers

Cause of errors

For the first case

STATIC_URL = '/static/'

Django tries to look for static files in the backend/static/ folder only in the case where anything with url with /static/ i.e. for e.g. /static/css/* or /static/js/* is mentioned in your index.html, but that is not the case here index.html has file references like /css/* and /js/*, hence they are not found.

The reason this case works in the blog example is due to the same reason i.e. their template files are kept under a '../frontend/build directory and static files are in '../frontend/build/static' hence the index.html will look for static/js/* instead of a /js/* hence /static/ url location is accessed in Django, which then looks for the files in backend/static correctly

This is why, in your code, setting it to second case i.e.

STATIC_URL = '/'

gets the urls for static files correctly to /css/* and /js/* and even your /index.html can be thought of as a static file i.e. all these urls are considered static and are searched in the backend/static/ folder and hence appearing correctly in the browser.

But now the URLs are messed up i.e. this:

re_path('', TemplateView.as_view(template_name='index.html')),

can be treated as Django looking for this location: /, but it is already reserved for searching static files and not including any file name after / means you are not looking for any file.

Possible Solution

Django allows custom url patterns i.e. you can create 2 new variables in settings.py i.e.

STATIC_URL = "/static/"
STATIC_ROOT = os.path.join(BASE_DIR, "static/")

STATIC_JS_URL = "/js/"
STATIC_JS_ROOT = os.path.join(STATIC_ROOT, "js/")

STATIC_CSS_URL = "/css/"
STATIC_CSS_ROOT = os.path.join(STATIC_ROOT, "css/")

and then configure your urls.py

from django.contrib import admin
from django.urls import include, path, re_path
from django.views.generic import TemplateView
from django.conf import settings
from django.conf.urls.static import static

urlpatterns = [
    path('admin/', admin.site.urls),
    path('cockpit/', include("cockpit.urls")),
    re_path('', TemplateView.as_view(template_name='index.html')),
]

urlpatterns += static(settings.STATIC_JS_URL, document_root=settings.STATIC_JS_ROOT)
urlpatterns += static(settings.STATIC_CSS_URL, document_root=settings.STATIC_CSS_ROOT)
like image 133
Siddharth Bhatia Avatar answered Nov 15 '22 15:11

Siddharth Bhatia


I had this problem a long time ago and I solved it with a simple solution. It's too simple. As I see, your static folder is not in your app root and it's in wrong place. Put it in your app root...because Django is looking for the static folder inside the main root of your app, where views.py is in there. Maybe you have 5 apps or more. Django doesn't care about the number of apps you have, Django only looks for your static folder in your app root. But your templates folder can be in your project root. So , put your static folder in your app root. In this case I mean inside cockpit root.

And then you have to add this changes in settings.py

PROJECT_NAME = '---Your projects name---'
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, PROJECT_NAME, 'static/')

EXTERNAL_APPS = [

     'django.contrib.staticfiles',
  ]

Good Luck.

like image 25
shahab-qazavi Avatar answered Nov 15 '22 15:11

shahab-qazavi