为什么 django 不能正确地为我的 SPA 静态文件提供服务?

kra*_*r65 9 python django npm single-page-application yarnpkg

我正在使用Django 后端Vuejs 前端构建网站。在开发中,我分别用python manage.py runserveryarn serve分别启动了后端和前端。这很有效,我现在想部署该网站。为此,我运行了yarn build,它dist/在我的前端文件夹中创建了一个文件夹。所以我的结构是这样的:

cockpit
??? backend/
?   ??? cockpit/
?   ?   ??? views.py
?   ?   ??? css/
?   ?   ??? etc..
?   ??? settings/
?   ?   ??? settings.py
?   ??? manage.py
??? frontend/
    ??? dist/
        ??? index.html
        ??? css/
        ??? js/
Run Code Online (Sandbox Code Playgroud)

我现在想frontend/dist/从我的 django 项目中提供源代码,以便我可以使用 uwsgi 运行所有内容。为此,我正在尝试按照此说明进行操作。我有以下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')),
]
Run Code Online (Sandbox Code Playgroud)

并在我的 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)
Run Code Online (Sandbox Code Playgroud)

印刷品向我展示了这一点:

BASE_DIR: /home/kramer65/repos/cockpit/backend
STATIC_ROOT: /home/kramer65/repos/cockpit/backend/static/
STATICFILES_DIRS: ['/home/kramer65/repos/cockpit/backend/../frontend/dist']
Run Code Online (Sandbox Code Playgroud)

然后我运行`python manage.py collectstatic:

$ python manage.py collectstatic

150 static files copied to '/home/kramer65/repos/cockpit/backend/static'.
Run Code Online (Sandbox Code Playgroud)

所以它现在看起来像这样:

cockpit
??? backend/
?   ??? cockpit/
?   ?   ??? views.py
?   ?   ??? css/
?   ?   ??? etc..
?   ??? settings/
?   ?   ??? settings.py
?   ??? manage.py
?   ??? static/
?       ??? index.html
?       ??? css/
?       ??? js/
??? frontend/
    ??? dist/
        ??? index.html
        ??? css/
        ??? js/
Run Code Online (Sandbox Code Playgroud)

我通过http-serverbackend/static/文件夹中运行(节点)来测试它。在浏览器中,网站加载并完美运行。下面是命令行的输出:

$ 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"
Run Code Online (Sandbox Code Playgroud)

我停止了这个http-server,启动了 Django 开发服务器并打开了浏览器。终端向我展示了这个:

$ 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]
Run Code Online (Sandbox Code Playgroud)

在浏览器控制台中,我看到源似乎已加载,但有些似乎是空的(0 字节)并且屏幕没有显示任何内容。下面是结果的屏幕截图和 Django 调试栏中的静态文件选项卡的屏幕截图。

有人知道为什么它不能在 Django 中正确提供这些文件吗?

在此处输入图片说明

在此处输入图片说明

[编辑]

我才发现如果我改变

STATIC_URL = '/static/'
Run Code Online (Sandbox Code Playgroud)

STATIC_URL = '/'
Run Code Online (Sandbox Code Playgroud)

当我去时它工作正常http://127.0.0.1:8000/index.html,但现在http://127.0.0.1:8000/给了我这个错误:

在此处输入图片说明

[编辑 2]

好的,按照@geek_life 的建议,我将设置文件中的值更改为:

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
    },
]
Run Code Online (Sandbox Code Playgroud)

哪个打印出来

## 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']
Run Code Online (Sandbox Code Playgroud)

在文件中settings/urls.py我(仍然)得到了这个:

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

然后我将static/带有构建的 vuejs-ap的文件夹从复制cockpit/backend/cockpit/backend/cockpit/.

不幸的是,我仍然得到相同的结果。index.html 加载,但 js 和 css 文件仍然没有。还有其他想法吗?

Sas*_*che 1

用于STATIC_URL

首先,将是提供STATIC_URL静态文件的 url 。因此,如果您设置然后导航到它,则基本上将从Django 应用程序的静态文件夹中提供服务。如果您转到“静态文件夹根目录”,正如您所看到的,目录索引是被禁止的。所以不要使用STATIC_URL='/'http://127.0.0.1:8000/index.htmlindex.htmlhttp://127.0.0.1:8000/STATIC_URL='/'

文件的位置

您遵循的教程将 SPA 的构建文件夹添加到 Djangosettings.py文件中的模板中。

所以你应该尝试添加:

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(os.path.dirname(BASE_DIR), 'frontend', 'dist')],  # <== ADDED THIS
        'APP_DIRS': True,
        'OPTIONS': {
            # removed to keep this example small
        },
]
Run Code Online (Sandbox Code Playgroud)

或者如果您想将静态文件指向模板目录:

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'static')], 
        'APP_DIRS': True,
        'OPTIONS': {
            # removed to keep this example small
        },
]
Run Code Online (Sandbox Code Playgroud)

然而,我认为这不是最佳实践,因为静态文件(图像、JS、CSS)在Django中有自己的资格。

注意事项os.path

另外,对于您来说STATICFILES_DIRS,这不是可行os.path.join() 。第一个参数应该是您想要与一个或多个路径组件连接的路径,然后这些组件将作为下一个参数传递。此外,BASE_DIR正如您在打印路径时所看到的那样,它不在正确的目录级别BASE_DIR。您需要目录的父目录BASE_DIR要获取父目录的路径,可以这样做os.path.dirname(BASE_DIR),因为BASE_DIR是路径。所以应用这个我们得到:

STATICFILES_DIRS = [
    os.path.join(os.path.dirname(BASE_DIR), frontend, dist),
]
Run Code Online (Sandbox Code Playgroud)

最后说明

我确实觉得让 Django 为您的 SPA 提供服务而不将其完全包含在 Django 中BASE_DIR(因此得名基本目录)可能是一种反模式。但我不知道它的来源,也不知道如何最好地实践它。