处理单页面应用程序URL和Django URL

29

我有一个使用Vue.js创建的单页面应用程序,它利用HTML5 History模式进行路由,并且html文件是由Django提供的。

Django的urls.py如下:

urlpatterns = [
    url(r'^$', views.home),
    url(r'^admin/', admin.site.urls),
    url(r'^api-token-auth/', obtain_jwt_token),
]

以及views.home视图:

def home(request):
    return render(request, 'index.html')
考虑以下情况:
  1. 用户访问主页(即/)。

由于主页返回了单页Vuejs应用程序所需的index.html文件,因此它运行得像应该的那样。

  1. 从那里,用户导航到about页面(即/username/12)。

它仍然可以正常工作,因为它使用Vue路由进行导航。

  1. 现在,用户刷新页面。

由于在urls.py模式中没有/username/12,因此它将显示页面未找到(404)

现在,我可以在urls.py中提供另一个模式来捕获最后一项的所有模式,如下所示:

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^api-token-auth/', obtain_jwt_token),
    url(r'^.*$', views.home),
]

但是其他像媒体或静态文件的url也会指向相同的捕获所有模式的正则表达式。我该如何解决这个问题?


1
万能URL是正确的选择。这不会影响媒体或静态文件,因为它们在Django生产环境中不由Django提供服务。 - Daniel Roseman
7个回答

28

我有一个类似的问题。

我怎样才能同时使用vue-router和django rest framework呢?

这是我解决这个问题的方法,希望能对你有所帮助。

预期结果:

http://127.0.0.1:8000/       <-- TeamplateView index.html using vue
http://127.0.0.1:8000/course <-- vue-router
http://127.0.0.1:8000/api    <-- rest framework
http://127.0.0.1:8000/admin  <-- django admin

我尝试了一下,它有效!

urls.py

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')),
    url(r'^api/', include(router.urls)),
    url(r'^.*$', TemplateView.as_view(template_name="index.html")),
]

顺序很重要,url(r'^.*$', TemplateView.as_view(template_name="index.html")),是最后一个

这是我的Vue路由器

const router = new VueRouter({
  mode: 'history',
  base: __dirname,
  routes: [{
      path: '/courses',
      component: CourseSet
    }, {
      path: '/',
      component: hello
    }]
})

我的GitHub项目


2
这个无法在/admin(没有尾随斜杠)中工作。路径将被'^.*$'捕获,斜杠不会附加到中间件以检查/admin/。它之所以对您有效,可能是因为HTTP缓存的原因。因此,否定的前瞻(?!admin)是唯一的选择。 - Alexey Trofimov
@AlexeyTrofimov,我遇到了你提到的同样问题。我可以正确使用/accounts/重定向,但/accounts重定向被捕获了通用URL模式。你能否请教一下我如何在urls.py中定义accounts URL模式? - Jayesh
1
@AlexeyTrofimov 没问题。我能够像这样做:'^(?!accounts)(?!api)(?!blablabla).*$'。感谢您的指导。现在它完美地运行了。 - Jayesh

10

既然您提到了“单页面”:

  1. 服务器只需提供一个页面 index.html(或其他任何您想要命名的名称)。

  2. 服务器和 Web 应用程序(前端)代码通过 API 调用进行通信,以便服务器提供资源,Web 应用程序负责使用该资源。

  3. 如果缺少资源,则服务器仍不能响应单独的页面,它仍应响应可由 Web 应用程序使用的消息。

我使用 Vue.js 创建了一个单页面应用程序,利用 HTML5 History Mode 进行路由

我认为您正在使用 vue-router,它模拟单页面应用程序成为功能齐全的多页面应用程序。


您可能需要查看此处此处,但以上内容适用于单页面应用程序。


您分享了您的 urlpatterns:

urlpatterns = [
  url(r'^admin/', admin.site.urls),
  url(r'^api-token-auth/', obtain_jwt_token),
  url(r'^.*$', views.home),
]
但是其他的URL,比如媒体或静态资源URL,也会指向相同的“捕获所有模式正则表达式”。我该如何解决这个问题?
  • 一种解决方法是将服务路由到除了'/'之外的其他路径,就像上面提到的 '/app' 一样。

  • urlpatterns = [
      url(r'^admin/', admin.site.urls),
      url(r'^api-token-auth/', obtain_jwt_token),
      url(r'^.*$/app', views.home),
    ]
    

    并且在你的router.js文件中:

    new Router({
      mode: 'History',
      base: '/app'
      routes: [
        {
          path: '/',
          name: 'name',
          component: ComponentName
        }
      ]
    })
    
  • 或者在URL前添加表示用途的前缀,例如

    urlpatterns = [
      url(r'^api/admin/', admin.site.urls),
      url(r'^api/api-token-auth/', obtain_jwt_token),
      url(r'^.*$', views.home),
      url(r'^.*$/assets', your-static-assets) 
    ]
    

  • 1
    “your-static-assets” 到底是什么?它是在 “views.py” 中的一个函数吗? - Alex Petralia
    干得好!我把它改成了url(r'^app/.*$', views.home),这是选项1,对我来说完美无缺。 - Dean Elliott

    7
    我使用VueJS路由启用历史模式,并通过以下方式与Django 2.x配合使用:
    从应用的urls.py文件中,您需要像这样重新定义前端的URL:repath
    # urls.py
    
    from django.urls import path, include
    from django.urls import re_path
    
    urlpatterns = [
        path('', TemplateView.as_view(template_name="application.html"), name="app", ),
        # ... rest of your urls
    ]
    
    urlpatterns += [
        re_path('^.*$', TemplateView.as_view(template_name="application.html")),
    ]
    

    现在历史模式运行得非常顺畅!

    http://sample.com/#/users/login/
    

    成为:

    http://sample.com/users/login/
    

    适用于Django 4.x和Vue 3,谢谢。 - Plaoo
    谢谢你,伙计!挖掘了几周,谢谢! - Fathy

    3

    我通过使用负向预查正则表达式解决了这个问题,因此在(?!ignore1|ignore2)中的任何内容都将被忽略。

    urlpatterns = [
        url(r'^admin/', admin.site.urls),
        url(r'^api-token-auth/', obtain_jwt_token),
        url(r'^(?!admin|api-token-auth|static).*$', views.home),
    ]
    

    注意:我尝试过在负预测中不包括adminapi-token-auth,但对我并没有起作用,它仍会将以admin开头的路径路由到views.home,不确定为什么,因为根据我的理解,Django应先匹配早期的内容。

    0
    上面@billi提供的答案很好,但只能解决一部分问题。我的问题是,对于我的SPA,我想(需要?)使用Vue的和系统。但是router.js将路径映射到Vue组件,而我的某些路径是通过Django urls.py到达Django视图的 - 例如,如果从Vue路由器导航到/api/places,则会导致空白页面,但如果刷新,则会正确解析为Django。
    有什么想法吗?

    urls.py

    urlpatterns = [
        # Django
        path('admin', admin.site.urls),
        url(r'^api-auth/', include('rest_framework.urls')),
        url(r'^api/places$', views.PlaceList.as_view()),
    
        # catchall to Vue single page app
        url(r'^.*$', TemplateView.as_view(template_name='myapp/spa.html'), name='home'),
    ]
    

    router.js

    export default new Router({
      mode: 'history',
      routes: [
        {path: '/search', component: Search},
        {path: '/about', component: About},
        {path: '/', component: Home},
        {path: '/api/places', }
      ]
    })
    

    App.vue

    <template>
      <div id="app">
        <div>
        <router-link to="/search">Search</router-link> ::
        <router-link to="/about">About</router-link>
        <router-link to="/">Home</router-link> ::
        <router-link to="/api/places">Place API</router-link> ::
      </div>
      <router-view></router-view>
      </div>
    </template>
    

    我遇到了同样的问题。你找到解决方法了吗?如果我将Django模板视图的URL从URL更改为RE_URL,似乎可以解决问题,但会阻止文件上传功能正常工作。 - JayG.Dev
    不好意思,我已经放弃在该应用程序中使用Vue。 - kgeo

    0

    不知道是否还有人在寻找解决方案。

    我使用Django Rest API和Vue前端完全独立地构建了一个项目。然后,当我尝试将dist文件夹中的内容放入Django的静态文件中,并通过gunicorn和NGINX进行服务器部署时,我无法使spa正常工作。

    最终,我做到了这一点,非常令人惊讶,它起作用了。

    基本上,

    将错误页面转发到Vue的index.html

    这是我的NGINX配置:

    location /static/ {
        root /root/bdn/bdn/server/;
    }
    
    location /media/ {
        root /root/bdn/bdn/server/;
    }
    
    location ^~ /admin/ { # Define routes to be directed to backend as proxy
        proxy_set_header Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_pass http://unix:/run/gunicorn.sock;
    }
    
    location ^~ /api/ { # Define routes to be directed to backend as proxy
        proxy_set_header Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_pass http://unix:/run/gunicorn.sock;
    }
    
    location ^~ /api-auth/ {
        proxy_set_header Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_pass http://unix:/run/gunicorn.sock;
    }
    
    location ^~ /{
        root /root/bdn/bdn/server/templates/;
        index index.html;
    }
    
    error_page 404 /;  # THIS IS WHAT IT CAME DOWN TO AFTER HOURS AND HOURS OF SEARCHING
    

    0

    由于index.html是模板化的(动态页面),因为它是从您的视图而不是静态服务的,这变得有点奇怪。

    对于生产环境,一定要使用nginx或apache以类似的方式提供静态内容。

    对于开发环境,我会这样做:

    from django.contrib.staticfiles.views import serve
    
    urlpatterns = [
        url(r'^admin/', admin.site.urls),
        url(r'^api-token-auth/', obtain_jwt_token),
    ]
    
    # Serve your static media (regex matches *.*)
    if settings.DEBUG:
        urlpatterns.append(url(r'(?P<path>.*\..*)$', serve))
    
    # Serve your single page app in every other case
    urlpatterns.append(url(r'^.*$', views.home))
    

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接