1

I have a quite standard Django application with a Vuejs frontend.

I have different environments (preprod/dev) in which I have file upload/download features.

For files, everything works fine because they are returned through standard API views in attachment (Content-Disposition: attachment). When it comes to images though, like profile pictures, there is a problem.

In development (DEBUG=True), I have this :

from django.conf import settings
from django.conf.urls.static import static
from django.urls import include, path

from backend.applications.base.views.authentication_views import LoginAPIView, LogoutAPIView

urlpatterns = [
    path("api/login", LoginAPIView.as_view()),
    path("api/logout", LogoutAPIView.as_view()),
    path("api/base/", include("backend.applications.base.urls")),
    path("api/contact/", include("backend.applications.contact.urls")),
    path("api/helpdesk/", include("backend.applications.helpdesk.urls")),
    path("api/inventory/", include("backend.applications.inventory.urls")),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)  # For serving media files when DEBUG=True

and images are correctly served (no nginx in dev mode, just frontend and backend dev servers django's runserver).

My preprod however, is made of a nginx container which serves my built vuejs frontend, and a backend container which contains my Django (DEBUG=False) application (which runs with gunicorn this time, like this : gunicorn backend.wsgi:application --bind 0.0.0.0:8000 --access-logfile="-").

Before trying to serve images, I had this nginx configuration :

http {
    client_max_body_size 5M;

    upstream backend_api {
        server backend:8000;
        # 'backend' is the name of the backend service in my docker-compose config
    }

    server {
        listen 80;

        include /etc/nginx/mime.types;

        root /usr/share/nginx/html;
        index index.html;

        location = /favicon.ico {
            access_log off;
            log_not_found off;
        }

        location /api {
            proxy_pass http://backend_api;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_redirect off;
        }

        location / {
            try_files $uri $uri/ /index.html;
        }
    }
}

Then I thought that /media requests should also be passed to the backend and I changed

location /api

into

location ~ ^/(api|media)/ {

My /api URLs are still handled correctly but /media URLs are answered by a 404 :

trying to load profile pictures of my user(s) in a kanban view (trying to load profile pictures of my user(s) in a kanban view).

Also trying directly http://localhost/media/base/users/8/picture.jpg directly in my browser doesn't work :

enter image description here enter image description here

From here I don't know what to do to solve the issue. If something is missing, mention it and I'll update the post.

Sunderam Dubey
  • 1
  • 11
  • 20
  • 40
lbris
  • 1,068
  • 11
  • 34

1 Answers1

1

Django does not serve static- and media files with runserver, you will need WhiteNoise for that. See http://whitenoise.evans.io/en/stable/ Whitenoise however is not suitable for serving user-uploaded media files. See http://whitenoise.evans.io/en/stable/django.html#serving-media-files

(Optionally, skip whitenoise, and host static/media files through NGINX.)

You really shouldn't be hosting your server with py manage.py runserver. This is not secure. See Why not use "runserver" for production at Django? and https://docs.djangoproject.com/en/dev/ref/django-admin/#runserver

Use something like Gunicorn instead.

See https://docs.djangoproject.com/en/4.1/howto/deployment/wsgi/gunicorn/

(Or waitress, the windows alternative)

https://pypi.org/project/django-waitress/

To host static/media files with nginx, paste this into your nginx conf:

    location /media  {
        alias /PATH/TO/DIRECTORY; #Absolute path.
    }

And in your settings.py, set the media root to that same directory.

nigel239
  • 1,485
  • 1
  • 3
  • 23
  • 1
    Actually in `preprod` environment it is running with `gunicorn`. It is started like this : `gunicorn backend.wsgi:application --bind 0.0.0.0:8000 --access-logfile="-"`. I forgot to mention that, I'll update the post. – lbris Aug 31 '22 at 07:34
  • I've seen many posts with this kind of alias but I don't get it. How does nginx know that /PATH/TO/DIRECTORY is from my upstream backend_api ? – lbris Aug 31 '22 at 07:41
  • @lbris it doesn't. Why would it need to? Serving files is basically just settings up a fileserver, and serving those directories. Django essentially does the same with `serve()` (and `static()`). It also won't be from your upstream backend API anymore, nginx will take over. – nigel239 Aug 31 '22 at 07:42
  • 1
    Because the media folder is in my backend container, where Django is. To go a bit further, in real production this media folder is a mounted share from Azure (into a Volume for my backend container, standard way to do it for them). So maybe the solution is to mount this file share volume to my nginx container too, and use this kind of alias. – lbris Aug 31 '22 at 07:48
  • 1
    For future readers, I tested what I suggested with this solution and it worked. So I mounted my media folder to the nginx container at `/www/media` and added this location `location /media { alias /www/media; }`. It works like a charm, thank you again @nigel239 – lbris Aug 31 '22 at 12:45