Handling single page application url and django url
Asked Answered
L

7

29

I have a single page application created in Vue.js that utilizes the HTML5 History Mode for routing, and the html file is served with Django.

The urls.py of django is like so:

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

And views.home:

def home(request):
    return render(request, 'index.html')

Consider the following scenario:

  1. User visits the home page (i.e., /)

Since, the home page responds with required index.html for the Single page Vuejs app, it works like its supposed to.

  1. From there the user navigates to the about page (i.e., /username/12).

Its still working fine, as its navigating with the Vue router.

  1. Now, the user refreshes the page.

Since there's no /username/12 in the urls.py patterns, it will show Page not found (404).

Now, I could provide another pattern in urls.py to catch all pattern in the last order as this:

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

But other urls like the media or static urls will also point to the same catch all pattern regex. How can I solve this problem?

Lamina answered 17/3, 2017 at 18:20 Comment(1)
The catch-all URL is the way to go. This won't affect media or static, as they are not served by Django in production.Pahari
D
28

I have a similar problem.

How can I use vue-router and django rest framework the same time?

This is my solution to this problem. Hope it helps you.

Expected results:

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

and I try this and it works!

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

The order is important, url(r'^.*$', TemplateView.as_view(template_name="index.html")), is the last one

and this is my vue router

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

My project on GitHub

Divisive answered 7/10, 2017 at 9:5 Comment(3)
This wont work with /admin (without trailing slash). The path will be catched by '^.*$' and slash wont be appended with middleware to check for /admin/. The reason it worked for you is probably because of http cache. So negative look-ahead (?!admin) is the only optionOlney
@AlexeyTrofimov I am facing same issue that you have mentioned. I can use /accounts/ redirects properly but /accounts redirect is being catched by the catch all url pattern. Can you please show me how to define accounts url pattern in urls.py?Gullett
@AlexeyTrofimov No issues. I was able to do it like '^(?!accounts)(?!api)(?!blablabla).*$' Thanks for the direction. Now it works perfectly.Gullett
C
10

Since you have mentioned "single page":

  1. The server is supposed to serve just one page the index.html (or whatever else you would like to call it).

  2. The server and the web application (front-end) code would communicate via api calls, such that the server provides resources and the web-app takes care of using that resource.

  3. In case of a missing resource, the server still must not respond with a separate page, it should still respond with a message that the web-app can use.

I have a single page application created in Vue.js that utilizes the HTML5 History Mode for routing

I believe you are using vue-router, which simulates the single-page app to be a full-featured, multi-page application.


You may want to take a look at this and this, but the above holds true for a single page application.


You shared your urlpatterns:

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

But other urls like the media or static urls will also point to the same catch all pattern regex. How can I solve this problem?

  1. A way you can manage that would be either by, serving on a route other than '/' like mentioned above for /app.

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

    and in your router.js file:

    new Router({
      mode: 'History',
      base: '/app'
      routes: [
        {
          path: '/',
          name: 'name',
          component: ComponentName
        }
      ]
    })
    
  2. Or prefixing the purpose served by the urls like

    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) 
    ]
    
Coffle answered 17/3, 2017 at 18:44 Comment(2)
What exactly is your-static-assets? Is that a function in views.py?Vortex
Nice work! I changed it to url(r'^app/.*$', views.home) for option number 1 which works perfect for me.Spellbinder
Z
7

I'm using VueJS router enabled history mode with Django 2.x by this way:

From app urls.py you need to repath your frontend url like this:

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

Now history mode working smoothly!

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

Become:

http://sample.com/users/login/
Zooid answered 21/10, 2019 at 9:12 Comment(2)
Works on django 4.x and Vue 3, thanksHospitable
thank you, man! digging for weeks thank you!Favourite
K
3

I solved the issue by using the negative look-ahead regex, so basically anything inside (?!ignore1|ignore2) will be ignored.

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

Note: I tried not including admin and api-token-auth inside the negative look-ahead and it didn't work for me, it will still route path that starts with admin to the views.home, not sure why as according to my understanding, django should match the earlier ones first.

Krebs answered 28/5, 2019 at 2:5 Comment(0)
B
0

Since index.html is templated (a dynamic page) because it's served from your views instead of static, this becomes a bit weird.

For production, definitely use nginx or apache to serve the static content in a similar fashion.

For development, this is what I would do:

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))
Bullet answered 20/10, 2017 at 2:46 Comment(0)
B
0

The answer provided above by @billi works fine, up to a point. My problem is that for my SPA I want (need?) to use the Vue <router-view> and <router-link> system. But the router.js maps paths to Vue components, and some of my paths are to Django views via Django urls.py - for example, this will result in a blank page if navigating to /api/places from the Vue router nav, but will resolve correctly to a Django if refreshed.

Ideas?

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>
Belamy answered 13/10, 2018 at 20:36 Comment(2)
I am having the same issue. Did you find anything for this? If I change my Django url for the template view from url to re_url, it seems to resolve but it blocks file upload functionality from working properly.Bastia
nope, sorry. stopped trying to use Vue for that appBelamy
W
0

Don't know if anyone still looking for the solution. I build Django Rest API and Vue front totally in a stand alone way. Then when i tried to put the dist folder items in static files of Django and tried to server through gunicorn and NGINX I just could not get the spa working. Finally did this and very surprisingly it works.

Basically,

forward error page to the index.html of Vue.

Here's my NGINX config:

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
Watercourse answered 18/9, 2021 at 23:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.