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

时间:2020-05-18 18:30:57

标签: python django npm single-page-application yarnpkg

我正在使用Django backendVuejs frontend建立网站。在开发中,我分别使用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/

我现在想从我的django项目中提供frontend/dist/中的源代码,以便可以使用uwsgi运行所有内容。为此,我尝试遵循this description。我有以下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')),
]

,然后在我的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)

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

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

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

$ python manage.py collectstatic

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

所以现在看起来像这样:

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

我通过运行http-server文件夹中的(节点)backend/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"

我停止了此http-server,启动了Django dev服务器并打开了浏览器。终端向我显示了这一点:

$ 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]

在浏览器控制台中,我看到源似乎已加载,但是某些源似乎为空(0字节),并且屏幕上未显示任何内容。以下是结果的屏幕截图以及Django调试栏中的“静态文件”标签的屏幕截图。

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

enter image description here

enter image description here

[编辑]

我刚刚发现如果我改变

STATIC_URL = '/static/'

STATIC_URL = '/'

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

enter image description here

[编辑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
    },
]

哪个打印出来

## 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']

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

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

然后我将带有内置vuejs-ap的static/文件夹从cockpit/backend/复制到cockpit/backend/cockpit/

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

6 个答案:

答案 0 :(得分:3)

错误原因

第一种情况

STATIC_URL = '/static/'

只有在URL带有backend/static/的情况下,例如,例如,Django会尝试在/static/文件夹中查找静态文件。在您的/static/css/*中提到了/static/js/*index.html,但是这里的情况并非如此index.html/css/*/js/*这样的文件引用,因此找不到它们。

此案例在Blog示例中起作用的原因是由于相同的原因,即它们的模板文件保存在'../frontend/build目录下,而静态文件位于'../frontend/build/static'中,因此{ {1}}将寻找index.html而不是static/js/*,因此/js/*的网址位置是在Django中访问的,然后Django会正确地在/static/中查找文件

这就是在您的代码中将其设置为第二种情况的原因,即

backend/static

将静态文件的网址正确地定位到STATIC_URL = '/' /css/*,甚至您的/js/*也可以被视为静态文件,即所有这些网址都被认为是静态的,并在/index.html文件夹,因此在浏览器中正确显示。

但是现在这些URL混乱了,即:

backend/static/
可以将

视为Django寻找以下位置:re_path('', TemplateView.as_view(template_name='index.html')), ,但是它已经保留用于搜索静态文件,并且在/之后不包含任何文件名意味着您没有在寻找任何文件。

可能的解决方案

Django允许自定义网址格式,即您可以在settings.py中创建2个新变量。

/

然后配置您的urls.py

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/")

答案 1 :(得分:2)

我很久以前就有这个问题,所以我用一个简单的解决方案解决了。 太简单了。 如我所见,您的静态文件夹不在您的应用程序根目录中,并且放置在错误的位置。 将其放在您的应用程序根目录中...因为Django正在应用程序的主根目录中查找静态文件夹,其中的views.py在那里。 也许您有5个或更多应用程序。 Django并不关心您拥有的应用程序数量,Django只会在应用程序根目录中查找您的静态文件夹。 但是您的模板文件夹可以在您的项目根目录中。 因此,将您的静态文件夹放在您的应用程序根目录中。 在这种情况下,我的意思是在驾驶舱内。

然后您必须在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',
  ]

祝你好运。

答案 2 :(得分:1)

使用STATIC_URL

首先,STATIC_URL将是静态文件will be served的URL。因此,如果您设置STATIC_URL='/'然后导航至http://127.0.0.1:8000/index.html,它将基本上从Django应用程序的静态文件夹中服务index.html。如果转到http://127.0.0.1:8000/,则位于“静态文件夹根目录”,并且如您所见,目录索引被禁止。所以不要使用STATIC_URL='/'

文件位置

您遵循的教程将SPA的build文件夹添加到Django settings.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
        },
]

,或者如果您要指向静态文件作为模板目录:

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

但是,我认为这不是最佳实践,因为静态文件(图像,JS,CSS)在Django中具有自己的限定条件。

关于os.path的注意

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

STATICFILES_DIRS = [
    os.path.join(os.path.dirname(BASE_DIR), frontend, dist),
]

最后的笔记

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

答案 3 :(得分:0)

第一件事!如果要将Django的UI作为生产的静态文件提供,这是个坏主意!您需要考虑使用nginx或类似的Web服务器为您提供静态文件。

您的TemplateView中的urls.py有点问题。由于在您的第一种情况下,您的静态文件位于STATIC_URL = '/static/'下,因此js和CSS文件的网址不匹配。您可以编辑文件,并将static模板标签放入index.hml内,但这也不是一个好主意。当您在vuejs方面进行更改时,您需要直接构建和替换该包,并且您的网站也会更新。因此,需要永久删除此行,必须将index.html视为静态文件:

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

更改STATIC_URL = '/'后,您可以访问所有静态文件。如果您在debug = True中运行django,它将自动提供静态文件。否则,您需要将静态url conf添加到文件中。 (不要忘记,这对生产不利!)

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")),
]

urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)

使用此设置,您需要注意您的URL和静态文件命名。它们可能会相互覆盖。

P.S:谢谢你这么详细的问题。

答案 4 :(得分:0)

有一种愚蠢的方法可以解决此问题。

此问题与VueJS构建的html将从诸如/js/app.12312sd.js/css/app.dsfeewj.css这样的路径中获取js和css一样。

html built by vueJS

静态文件根路径的默认设置为/static/,因此您只需要手动更改/js/...中的每个/css/...index.html或使用sed或还有其他东西可以将它们替换为/static/js/...//static/css/.../

您无法将STATIC_URL替换为/,因为它是您网站的根网址。

编辑后,它将看起来像这样,然后将您的django服务器作为服务器运行即可。

enter image description here

答案 5 :(得分:0)

在构建前端之前,将css /和js /放在名为“ static”的目录中,并保持index.html不变。然后,当您运行yarn build时,目录结构如下-

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

设置您的BASE_DIR和FRONTEND_DIR指向“驾驶舱/前端/ dist”,以便稍后使用。

BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
FRONTEND_DIR = os.path.join(os.path.dirname(BASE_DIR),'frontend','dist')

现在,您可以像以前一样将前端静态文件复制到后端目录中-

STATIC_URL = '/static/'
STATICFILES_DIRS = [
    os.path.join(FRONTEND_DIR,'static/'),
]
STATIC_ROOT = os.path.join(BASE_DIR, "static/")

或者您可以直接将后端指向要从内置的前端目录提供服务,而不必再次复制它。 (我总是这样)

STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(FRONTEND_DIR,'static/')

在模板目录中包含构建的前端目录,以便Django可以提供index.html

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [FRONTEND_DIR],
        'APP_DIRS': True,
        'OPTIONS': {
            # removed to keep this example small
        },
    },
]

现在运行服务器

$ python manage.py collectstatic
$ python manage.py runserver

如果以后您决定在线部署此文件,则可以通过将Nginx设置为从静态目录中提供静态文件来轻松使用此配置本身。 希望这会有所帮助:)

消除您对[编辑]的困惑

STATIC_ROOT指向系统上的目录。 STATIC_URL指的是在使用您的网站时访问STATIC_ROOT中存在的静态文件的URL。

假设您的目录中有一个script.js文件,如图所示

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

并且您已将STATIC_ROOT设置为指向“驾驶舱/前端/ dist /静态/”。

现在,如果您将STATIC_URL设置为“静态/”,则可以在http://127.0.0.1:8000/static/js/script.js上访问该脚本

或者,如果您将STATIC_URL设置为“ xcsdf /”,则可以在http://127.0.0.1:8000/xcsdf/js/script.js上访问js文件