0

I have my Django app and I have following use cases:

  1. Local development. Since my app is mostly simple, I prefer usual manage.py runserver approach. So I expect my app to work for this case.
  2. Production environment. We ahve one web server and host tools under different URLs, for example https://myserver.com/tool1, https://myserver.com/tool2 etc. So I'd like to put my app in this structure
  3. (Theoretical) Apart of use case 2, I may want to host the app under its own domain, like https://tool.myserver.com

But when I tried to do this, I got an issue with static files (sigh!), because if I have STATIC_URL as relative path STATIC_URL='static/' then it doesn't work for "nested" pages (i.e. if I'm on myserver.com/tool1/page then static URL will map to myserver.com/tool1/page/static which is not correct). From the other hand, if I use absolute path STATIC_URL='/static/' then it doesn't work for case 2 at all, because Django app knows nothing about /tool1 part of URL where it's located.

I can use two different variants for STATIC_URL depending on the environment and hardcode STATIC_URL='/tool1/static/', but then the same code will not work for case 3...

How should I handle this situation?

UPD

Actually I realized that it's more like general nginx+backend question than Django one. Because at the end of the day webpage generated by backend most probably will have src="/static/...." (unless I add some hacks to backend to insert prefix /tool1). And I wonder how is it being handled usually? There is a way to replace actual HTML content in nginx, but it will really affect performance...

UPD2

Seems like a lot of people misread my quesiton thinking that the only issue is in static files. However it was just an example, because as Ivan corretly mentioned, there is the same issue with links. Usually on the page I have links like a href="/category/post?id=1". And obviously when site is opened as my.domain/tool1 this will resolve to my.domain/category... which is wrong (I want it to point to my.domain/tool1/category....)

The Godfather
  • 4,235
  • 4
  • 39
  • 61
  • 1
    How did you solve it for all the other pages/urls? – Ivan Starostin May 11 '19 at 07:36
  • @Ivan, What do you mean? – The Godfather May 11 '19 at 10:19
  • 1
    Do you have hyperlinks in your web app? How do you refer one page from another? – Ivan Starostin May 11 '19 at 10:25
  • Since my app is simple, I don't really have multi-level hierarchical links, I have a request to the very same page (like `href="#"` which works fine). But yes, you're correct, the same issue I will have with links as well, not only with static – The Godfather May 11 '19 at 10:54
  • where is STATIC_URL='static/' set? Can you show your config file or the relevant parts? – Bman70 Jul 09 '19 at 20:51
  • Nevermind, this is very Django specific, my nginx servers don't use `static_url`. This question might help: https://stackoverflow.com/questions/7307549/django-static-url-adds-appname-to-the-url – Bman70 Jul 09 '19 at 21:01

3 Answers3

1

Handling the STATIC_URL from django application could cause overhead. As you have multiple sub directories, why not serve the static files from only one of them only? Its not necessary to match sub directory when serving static files.

Lets say, if you set STATIC_URL like this:

STATIC_URL = "/tool1/static/"

Then you can just configure the NGINX like this:

server {
    listen 80

    server_name *.myserver.com;

    location /tool1/static/ {
        root /path/to/STATIC_ROOT;
    }

    location / {
        proxy_pass http://localhost:8000;  # <-- No trailing slash to make it work with gunicorn
    }

    location /tool2/ {
        proxy_pass http://localhost:8000;
        proxy_set_header SCRIPT_NAME /tool2;  # <-- For serving in sub path
    }

    location /tool1/ {
        proxy_pass http://localhost:8000;
        proxy_set_header SCRIPT_NAME /tool1; # <-- For serving in sub path
    }

}

And in the settings update the STATIC_URL to /tool1/static/. Also make sure your STATIC_ROOT point to proper path where static files will reside in the server. Finally, before deploying the static files, you need to run collectstatic command to put the static files inside STATIC_ROOT directory, then restart NGINX server.

Update: its not recommended to use hardcoded url. Instead, in template, you should use {% URL '<url_name> '%} tag, or reverse() in python code. So that, django will resolve the urls itself. When it is in /tool1 location, the urls will be resolved as /tool1/url_name/, and /tool2/url_name, when you are in location /tool2(based on SCRIPT_NAME).

ruddra
  • 50,746
  • 7
  • 78
  • 101
  • Did I understand correctly, that you suggest to hardcode `tool1` once in Django and twice in Nginx? What is the benefit of that instead of explicitly putting it once on nginx side and then using `SCRIPT_NAME` on backed side dynamically? Plus, this also does not help for links on the pages which are like `a href="/main/page?id=1"` - this is not resolved correctly when app is in the folder. – The Godfather Jul 10 '19 at 08:45
  • When calling urls, you shouldn't hard code it. instead using [{% url %}](https://docs.djangoproject.com/en/2.2/ref/templates/builtins/#url) tag, so django will resolve it itself. Also, how are you going to define `SCRIPT_NAME` in backend side dynamically? There is one variable called [`FORCE_SCRIPT_NAME`](https://docs.djangoproject.com/en/2.2/ref/settings/#force-script-name) in django, but its not dynamic. But you don't need that, as you can put it in header SCRIPT_NAME explicitly in NGINX configuration – ruddra Jul 10 '19 at 11:41
  • From the `url` doc: "Returns an absolute path reference (a URL without the domain name)". So it will return me absolutely the same url `href="/my/page"` – The Godfather Jul 10 '19 at 12:53
  • Probably not. Because of SCRIPT_NAME, it should configure url like '/tool1/url/' if it's in location /tool1 – ruddra Jul 10 '19 at 15:11
  • 1
    Sounds like undocumented magic, wow. But thanks for pointing that out, indeed Django is able to automagically handle `SCRIPT_NAME` header if it's forwarded/set correctly. I learned something new today :) – The Godfather Jul 12 '19 at 15:15
0

I recommend placing your static files in a storage service like AWS S3, Google Cloud Storage, etc. This way, it's always available to whatever URL like you have (nested, subdomain, whatever)

Example setup (might require some more steps):

  • Change your settings.py:

    STATIC_URL = 'https://storage.googleapis.com/your_bucket/static/'

    STATIC_ROOT = os.path.join(BASE_DIR, "staticfiles")

    STATICFILES_DIRS = (os.path.join(BASE_DIR, 'static'),)

  • Before deploying your app, run manage.py collectstatic --noinput

  • Copy the files in your staticfiles folder to the Storage service and make sure they are public
  • Deploy your app

P.S i find whitenoise to really help with static files sizes and easier deployment

Yarh
  • 895
  • 7
  • 15
  • Using cloud doesn't help here, because issue is not only about static files (as noted in the comment), but about links in general. Also every CDN has a note "don't use CDN as main storage, use it only as mirror", so I still have to store my files somewhere (including for local development when I do modify static) – The Godfather Jul 09 '19 at 20:37
  • The solution works for local (the files are loaded from local folder), development and production environment. Storing in Storage service has the benefit of being multi-region (if you like), improving the webpage loading speed and reducing load on your app server. As for other links, i don't see why they don't work. Can you explain? – Yarh Jul 10 '19 at 06:39
  • see Ivan's comment on the question itself. Because I have links `a href='/my/folder/page?id=1' and this is not being resolved correctly when app is in the folder – The Godfather Jul 10 '19 at 08:41
0

Seems like there is no really good way to solve it. However there are several options I found:

Always use one canonical address (personally preferred)

I should ask myself "Why do you want your tool to be available at both https://myserver.com/tool1 and https://tool1.myserver.com?". And actually I don't have any reason why should it be available separately in the same time. So just select "canonical" address and set up proper redirects:

If domain is canonical:

    server_name myserver.com
    location /tool1/ {
            rewrite ^/tool1/?(.*)$ https://tool1.myserver.com/$1 permanent;
    }

If folder is canonical:

    server_name tool1.myserver.com
    location / {
            rewrite ^ https://myserver.com/tool1$request_uri? permanent;
    }

Then on backend side use whatever one option of STATIC_URL corresponding to the canonical address (i.e. /static for subdomain or /tool1/static for folder)

Additional headers or URL params

If there is a reason (tell me why!) to do not use redirects and have both addresses in play, then as ruddra mentioned one can add additional headers and then use them on backend side in order to generate slightly different pages:

location /tool1/ {
    <proxy_pass something>
    proxy_set_header SCRIPT_NAME /tool1;
}

Same approach can be used if you select folder to be canonical address, then you may want to avoid hardcoding tool1 in the backend. Then maybe you can use either custom header or additional url parameter like ?from=domain [1] which can be handled in backend. Here I have no ready solution since subdomain case was preferred for me.

The Godfather
  • 4,235
  • 4
  • 39
  • 61
  • 1
    Lets say, you have 2 different domains abc.com/sales/ and xyz.com/e-commerce/, and you want to serve the same app to both of the domain, then the `Additional headers or URL params` approaches is useful. :) – ruddra Jul 11 '19 at 08:14