Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django in subdirectory - admin site is not working

Tags:

python

django

I have deployed my Django project on subdirectory on server (dns_name_of_my_page/notes). My app in this project is accessible via dns_name_of_my_page/notes/app/. Unfortunately admin site is not working if whole Django project is in subdirectory 'notes' in public_html on hosting server. When I enter to admin site (dns_name_of_my_page/notes/admin/) there is redirect to URL without the name of the subdirectory (dns_name_of_my_page/adminlogin/?next=//admin/), which is not acceptable. It should be rather dns_name_of_my_page/notes/adminlogin/?next=/notes/admin/

Here is my configuration of URLs in project:

urlpatterns = [
    url(r'^/?app/?', include('app.urls')),
    url(r'^/?$', views.index, name='index'),
    url(r'^/?admin/?', admin.site.urls),
]

Here is my configuration of URLs in my app:

urlpatterns = [
    url(r'^$', views.index, name='index'),
    url(r'^(?P<num>[0-9]+)/?', views.num, name='num'),
]

I have tried to set

FORCE_SCRIPT_NAME = '/notes/'

or

SUB_SITE = "/notes/"

in settings.py but it did't help me.

Have you ever had this kind of problem with running Django app in subdirectory ?

like image 704
Sebastian Avatar asked Jul 08 '17 14:07

Sebastian


1 Answers

I was caught out on this today, and whilst there are multitude of similar questions [1], the answer is somewhat of an amalgam from my experience of solving this.

One detail missing from your question is what server setup you're working behind. To explain my solution it helps to understand how that works in combination with the Django aspects.

The problem I have been solving which has included this problem with accessing Django's admin (and most other apps such as Auth) is to have a server acting as a host for multiple independent django projects. The aim is to have URIs starting with http://my.server.com/project_name/foo/bar/ [2]. As far as each project is concerned it is operating at my.server.com and is processing just /foo/bar (as would be the case in most production/single-application systems, and local development with manage.py runserver)

I'm using NGinx as a reverse proxy listening on ports 80 and 443 for my domain. Within that server I have a location setup to proxy onto an Nginx service + Gunicorn for each sub-project.

The nginx default location configuration looks like this:

location /project_name {
    proxy_set_header X-Forwarded-Protocol $scheme;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header Host $http_host;
    proxy_set_header X-Scheme $scheme;
    proxy_set_header X-Forwarded-For $remote_addr;

    proxy_pass http://localhost:7001/;
}

The setup for each project's own nginx+gunicorn service isn't particularly relevant to this topic, but as you can see it presents on its own local service port.

This config will supply an effective request for http://my.server.com/foo/bar/. This leaves us with now convincing Django to add a /project_name/ prefix to all the URLs it produces in the output.

To do that, there are a handful of critical Settings you need to have covered, these are:

USE_X_FORWARDED_HOST = True
FORCE_SCRIPT_NAME = '/project_name/'

This causes Django to respect the header changes that nginx is passing along, and also triggers it's own processes for rewriting the output of {% url name %} tags in templates.

SESSION_COOKIE_PATH = '/project_name/'

This should help separate out multiple logins on that domain, and mean you can be logged in on one project, but not on another (or confusing it).

STATIC_FILES_ROOT='\path\on\disk\as\in\your\nginx\config\for\static\'
STATIC_FILES_URL='/project_name/static/'
MEDIA_FILES_ROOT='\path\on\disk\as\in\your\nginx\config\for\media\'
MEDIA_FILES_URL='/project_name/media/'

These handle the `{% static blah %} type template tags and file serving.

LOGIN_REDIRECT_URL='/project_name/'
LOGOUT_REDIRECT_URL='/project_name/'

These handle the results of using the Auth module to login/out of the service, and ensure that it sends the user back to the root of the project site on login or logout. You can tailor these to your own needs easily, including using url-conf style regex strings such as we'll do below for the admin pages

ADMIN_URL=r'^admin/'

This setting pairs with this line in your site urls.py:

url(settings.ADMIN_URL, include(admin.site.urls)),   # default=r'^admin/'

A key detail here is that there is no preceding / on the regex, which would otherwise try to force it back to the host name, and thus the change specified by FORCE_SCRIPT_NAME will work.

Also, take care not to have any hardcoded urls (relative or otherwise) in your templates that start with /, as that would not be caught by the Django template output changes.

Following this approach I've been able to deploy each of these projects in three different systems, two of which involve them running as the only django application on that domain or service port, with the remaining one being this shared domain setup, and not requiring anything other than a change to the settings values on each location.


Taking this approach with your own code samples, I'd suggest you're looking for changes to:

urlpatterns = [
    url(r'^admin/?', admin.site.urls),
    url(r'^app/?', include('app.urls')),
    url(r'^$', views.index, name='index'),
]

To aide the search and match order, I've rearranged admin to come first, and the empty/home/root match template to come last. Otherwise your FORCE_SCRIPT_NAME setting is on the right track.


[1] e.g. multiple instances of django on a single domain ; Is it possible to host multiple django projects under the same domain? ; how to deploy django under a suburl behind nginx

[2] The default server is actually handling SSL translation too as the services are presented on https, but internally the localhost ports (hidden behind the machine firewall) are processing only http.

like image 157
Carl Marshall Avatar answered Sep 20 '22 12:09

Carl Marshall