djangodockerurlnginxport

How to output the port after the IP address in a resource URL in Django?


  1. How do I add the host port to the URLs in my serialized responses? Django is currently providing them without port so the links are broken.
  2. Or, if adding the port is not the correct approach, how do I change my configuration such that I don't need to specify port in the URL while accessing the resource?

Output (missing port ":1337" in image_url field)

{
    "count": 1,
    "next": null,
    "previous": null,
    "results": [
        {
            "id": 1,
            "user": 1,
            "title": "Post 1",
            "slug": "post1",
            "image_url": "http://0.0.0.0/mediafiles/publisher/sample-image4.jpg",
            "content": "First",
            "draft": false,
            "publish": "2019-04-26",
            "updated": "2019-04-26T22:28:35.034742Z",
            "timestamp": "2019-04-26T22:28:35.034795Z"
        }
    ]
}

"image_url" field will not link correctly unless it includes the port like this:

"image_url": "http://0.0.0.0:1337/mediafiles/publisher/sample-image4.jpg",

More details

Stack:

I am using Django REST framework to return a list of serialized objects. The objects contain a FileField called "image" and I can output the URL of this image. The only thing is when I click that link in the output in my browser, I cannot access the resource without manually adding the server port in the address like

http://0.0.0.0:1337/mediafiles/publisher/sample-image4.jpg

I'm not sure if it's an nginx issue, a Django settings issue or just how my code is configured. I'm having trouble finding any other reported cases via Google (probably because I'm still new to Django and not sure the correct configuration despite following tutorials).

I tried some of these solutions but they do not output the port.

There's this question, but I'm not using ImageField and I want to find a solution for cases where I'm using FileField. The comment on the main question indicates that adding the port should not be required too, so perhaps it's an infra problem and not a Django problem? Guidance on this would be awesome.

models.py

class Post(models.Model):
    class Meta:
        ordering = ('timestamp',)

    user = models.ForeignKey(User, on_delete=models.PROTECT)
    title = models.CharField(max_length=120)
    slug = models.SlugField(unique=True)
    image = models.FileField(upload_to='publisher/', null=True, blank=True)
    content = models.TextField()
    draft = models.BooleanField(default=False)
    publish = models.DateField(auto_now=False, auto_now_add=False)
    updated = models.DateTimeField(auto_now=True, auto_now_add=False)
    timestamp = models.DateTimeField(auto_now=False, auto_now_add=True)

    def __str__(self):
        return self.title

    def __unicode__(self):
        return str(self.id)

    def get_absolute_url(self):
        return reverse("post:detail", kwargs={"slug":self.slug})

serializers.py

class PostSerializer(serializers.ModelSerializer):
    image_url = serializers.SerializerMethodField()

    class Meta:
        model = Post
        fields = [
            'id',
            'user',
            'title',
            'slug',
            'image_url',
            'content',
            'draft',
            'publish',
            'updated',
            'timestamp',
        ]

    def get_image_url(self, post):
        request = self.context.get('request')
        if post.image and hasattr(post.image, 'url'):
            image_url = post.image.url
            return request.build_absolute_uri(image_url)
        else:
            return None

urls.py

urlpatterns = [
    path('admin/', admin.site.urls),
    re_path('blog/(?P<version>(v1|v2))/', include('blog.urls'))
    ]
...
    [
    url(r'^posts/$', PostListAPIView.as_view(), name='posts'),
    ]

if settings.DEBUG:
    urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
    urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

views.py

class PostListAPIView(generics.ListAPIView):
    model = Post
    queryset = Post.objects.all()
    serializer_class = PostSerializer

docker-compose.yml

version: '3.7'

services:
  web:
    build: ./app
    command: gunicorn hello_django.wsgi:application --bind 0.0.0.0:8000
    volumes:
      - ./app/:/usr/src/app/
      - static_volume:/usr/src/app/staticfiles
      - media_volume:/usr/src/app/mediafiles
    ports:
      - "8000"
    env_file: ./app/.env
    environment:
      - DB_ENGINE=django.db.backends.postgresql
      - DB_USER
      - DB_PASSWORD
      - DB_HOST=db
      - DB_PORT=5432
      - DATABASE=postgres
    depends_on:
      - db
    networks:
      - backend

  db:
    image: postgres:10.7-alpine
    ports:
      - "5432:5432"
    volumes:
      - postgres_data:/var/lib/postgresql/data/
    networks:
      - backend

  nginx:
    build: ./nginx
    volumes:
      - static_volume:/usr/src/app/staticfiles
      - media_volume:/usr/src/app/mediafiles
    ports:
      - "1337:80"
    depends_on:
      - web
    networks:
      - backend

networks:
  backend:
    driver: bridge

volumes:
  postgres_data:
  static_volume:
  media_volume:

nginx.conf

upstream hello_django {
    server web:8000;
}

server {

    listen 80;

    location / {
        proxy_pass http://hello_django;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $host;
        proxy_redirect off;
    }

    location /staticfiles/ {
        alias /usr/src/app/staticfiles/;
    }

    location /mediafiles/ {
        alias /usr/src/app/mediafiles/;
    }

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

Solution

  • I finally found out how to fix the image URLs thanks to this question which is slightly different.

    Solution 1

    Add the port number in the Host header in the nginx config as follows:

        location / {
            proxy_pass http://hello_django;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header Host $host:1337;                               <<------- HERE
            proxy_redirect off;
        }
    

    Solution 2

    Change the Host header in the nginx config to http_host as follows:

        location / {
            proxy_pass http://hello_django;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header Host $http_host;                               <<------- HERE
            proxy_redirect off;
        }
    

    In either case, the image URLs are now returned as follows by DRF (image link).

    HTTP 200 OK
    Allow: GET, HEAD, OPTIONS
    Content-Type: application/json
    Vary: Accept
    
    {
        "count": 1,
        "next": null,
        "previous": null,
        "results": [
            {
                "id": 2,
                "user": 1,
                "title": "First post",
                "slug": "first",
                "image_url": "http://0.0.0.0:1337/mediafiles/publisher/background.gif",    <----HERE
                "content": "Second post content.",
                "draft": false,
                "publish": "2019-05-22",
                "updated": "2019-05-22T09:41:36.257605Z",
                "timestamp": "2019-05-22T07:58:01.471534Z"
            }
        ]
    }