【问题标题】:Why isn't django serving my SPA static files correctly?为什么 django 不能正确地为我的 SPA 静态文件提供服务?
【发布时间】:2020-09-04 15:50:19
【问题描述】:

我正在使用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/

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

$ 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 开发服务器并打开了浏览器。终端向我展示了这个:

$ 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 调试栏中的 Static Files 选项卡的截图。

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

[编辑]

我刚刚发现如果我改变了

STATIC_URL = '/static/'

STATIC_URL = '/'

当我转到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
    },
]

打印出来的

## 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 文件仍然没有。还有其他想法吗?

【问题讨论】:

  • 我认为您问题的最后一部分是由于您的 url 路径而发生的。它能够在静态文件中找到index.html,因此当您直接转到该文件时它正在工作......试试这个:re_path(r'^.*', TemplateView.as_view(template_name="index.html"))
  • @kramer65 你解决过这个问题吗?

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


【解决方案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 的构建文件夹添加到 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_DIRS,这不是os.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),
]

最后说明

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

【讨论】:

    【解决方案2】:

    错误原因

    对于第一种情况

    STATIC_URL = '/static/'
    

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

    这种情况在博客示例中起作用的原因是由于相同的原因,即它们的模板文件保存在 '../frontend/build 目录下,静态文件在 '../frontend/build/static' 中,因此 index.html将查找static/js/* 而不是/js/* 因此在Django 中访问/static/ url 位置,然后正确查找backend/static 中的文件

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

    STATIC_URL = '/'
    

    将静态文件的 url 正确获取到 /css/*/js/*,甚至你的 /index.html 也可以被认为是一个静态文件,即所有这些 url 都被认为是静态的,并在 backend/static/ 文件夹中搜索,因此在浏览器中正确显示。

    但现在 URL 搞砸了,即:

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

    可以被视为 Django 正在寻找这个位置:/,但它已经被保留用于搜索静态文件并且在 / 之后不包括任何文件名意味着您没有在寻找任何文件。

    可能的解决方案

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

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

    然后配置你的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)
    

    【讨论】:

      【解决方案3】:

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

      urls.py 中的 TemplateView 有点问题。由于在您的第一个场景中,您的静态文件在 STATIC_URL = '/static/' 下提供,因此 js 和 css 文件的 url 不匹配。您可以编辑文件并将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:谢谢你这么详细的问题。

      【讨论】:

      • 感谢您的评论。只是一个问题,为什么通过 Django 提供我的静态文件是一个坏主意?我的意思是,我知道 nginx 有缓存和所有这些东西。但这是一个非常低流量的网站(每天可能有 100 次浏览),所以性能不是问题。据我了解,如果它是一个流量缓慢的个人网站,uwsgi 是一个非常好的服务器。还是我错过了什么?
      • 一切都与性能有关。如果您对性能没有很高的期望,那也没关系。
      • 因为每个页面都会产生几个静态文件,这意味着除了页面本身之外,还有几个工作人员将忙于一个页面视图。仅包含 10 个静态文件的页面视图就可以完全束缚您。即使对于一位用户来说,这也意味着延迟。我会说 2 个人同时会减慢默认的 gunicorn 设置。
      【解决方案4】:

      有一种方法可以解决这个问题。

      这个问题是关于VueJS构建的html会从/js/app.12312sd.js/css/app.dsfeewj.css这样的路径中获取js和css。

      静态文件根路径默认设置为/static/,因此您只需手动更改index.html中的/js/.../css/...或使用sed或其他方式将它们替换为/static/js/...//static/css/.../.

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

      你编辑它之后,它会是这个样子,然后服务器你的 django 服务器就可以正常工作了。

      【讨论】:

        【解决方案5】:

        在构建前端之前,将 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 设置为指向 'cockpit/frontend/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 设置为从您的静态目录提供静态文件,使用此配置本身轻松地做到这一点。 希望这会有所帮助:)

        消除您在 [EDIT] 中的困惑

        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 设置为指向“cockpit/frontend/dist/static/”。

        现在,如果您将 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 文件

        【讨论】:

          【解决方案6】:

          我很久以前就遇到过这个问题,我用一个简单的解决方案解决了这个问题。 这太简单了。 如我所见,您的静态文件夹不在您的应用程序根目录中,并且位于错误的位置。 把它放在你的应用程序根目录中......因为 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',
            ]
          

          祝你好运。

          【讨论】:

          • 我认为它可以解决问题
          • 您正在以 djangoic 的方式思考,但是您的建议为前端构建过程增加了另一个步骤。由于 vuejs 应用程序是完全分离的,因此构建的应用程序驻留在其目录中。 @kramer65 对 STATIC_DIRS 所做的事情对于他的情况来说是非常合理的。如果他按照您的建议进行操作,他将构建 vue 应用程序,将其复制到 django 应用程序的静态文件夹下并运行收集静态。然而他的问题仍未解决?
          • 我在回答中添加了一些要点,这些提示对我有用,但是,他必须先尝试我建议的方法,看看最终会发生什么@smail-taha-ayka
          • 感谢您的想法。我试过了,但还是没有运气。我添加了我对问题所做更改的描述。也许我错过了一些愚蠢的东西?
          猜你喜欢
          • 2021-12-31
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-08-25
          • 1970-01-01
          • 2019-06-14
          • 1970-01-01
          相关资源
          最近更新 更多