Uploaded images from my Django model returning the literal string.

This is a long one.

I have a Django model named MediaModule with an ImageField() within the model.

The ImageField() is configured in the following fashion:

article_image = models.ImageField(upload_to='assets/')

The above model is stored in a Django app named media and directory setup is configured in the following fashion:

ROOT / media (app name) / assets / <image-name>

My settings.py configuration in relation to the serving of images from within a Django model are as follows:

DEBUG = False

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'media',
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    "whitenoise.middleware.WhiteNoiseMiddleware",
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

STORAGES = {

    'default': {
        'BACKEND': 'django.core.files.storage.FileSystemStorage',
    },

    "staticfiles": {
        "BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage",
    },
}

STATIC_URL = 'static/'
STATIC_ROOT = BASE_DIR / "staticfiles"
STATICFILES_DIRS = [BASE_DIR / "static",]

MEDIA_URL = '/media/'
MEDIA_ROOT = BASE_DIR / 'media'

In my <root>.urls, file, I have the following:

from django.contrib import admin
from django.urls import path, include
from . import views
from django.conf import settings
from django.conf.urls.static import static
from django.views.static import serve

urlpatterns = [
    
    path('admin/', admin.site.urls),
    path('news/', include('media.urls')),

]

# Only for development. Do not use this in production.
if settings.DEBUG:
    urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
    urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)

in my media.urls file; the following is configured:

from django.urls import path, include
from . import views
from django.contrib import admin

urlpatterns = [
    path('articles/<str:article_identifier>', views.details),

]

In my media.views file, I have the following:

from django.shortcuts import render, get_object_or_404
from django.http import HttpResponse
from django.template import loader
from .models import MediaModule
from xxx import utils

def article_objects():
  '''This will create an instance of the model to obtain all values from the model. It will then return the name of the model instance.s'''
  articles  =  MediaModule.objects.all()
  return articles


def details(request, article_identifier):
    
  articles                = article_objects()
  article                 = get_object_or_404(MediaModule,article_identifier=article_identifier)
  meta_object__image 	  = utils.meta_object__image(article.article_image.url)
  template                = loader.get_template('media-article.html')

  context = {
      
    'article'                 : article,
    'articles'                : articles,
    'meta_object__image' 	:	meta_object__image,

    }
  
  return HttpResponse(template.render(context, request))

The relevant components of my MediaModule model from within media.models is as follows:

The generate_unique_number(char_length) is a separate function which generates a random number which uniquely identifies a model object and appends it onto the URL, unrelated to the article_image issue but included for continuity. It’s actually pretty cool!

from django.db import models

# Create your models here.
class MediaModule(models.Model):

    article_identifier     =    models.CharField(primary_key=True, max_length=50, default="generate_unique_number(12)", unique=True)
    article_image          =    models.ImageField(upload_to='assets/')

And lastly, from within my media-article.html, image is called in the following way:

<img src="{{article.article_image.url}}" alt=""/>

Results

  1. Upon checking the browser console, there are 404 error messages for the images. Browser console returns http://127.0.0.1:8000/media/assets/<image-name>.jpeg

  2. Inspecting the source inspect source, the image is being obtained from /media/assets/<img name>

  3. All others images served from staticfiles have no issues with being shown.

  4. I’m failing to see why my browser is failing to find the image, despite said image being being placed in the correct folder directory with the relative directory in my model being upload_to="assets/".

  5. From the web page, inspecting the source code, it only returns: <img src="/media/assets/<image-name>" alt="">

  6. Further information: this is a development environment.

If anybody needs anything clarifying or anything I’ve missed out, let me know.

Two quick questions/items to get started -

  • is this a development environment (using ‘runserver’, or your production environment (perhaps using gunicorn or uwsgi)?

  • Be aware that on a number of levels, media files are different from static files, and what works with one has no relevance on the other. (In other words, referring to your static files at all provides no diagnostic information regarding media files.)

Thank you very much, it’s a development environment. I want to ensure that everything is working tickety-boo with Debug set to False before deploying to my existing Production server - which is using NGINX & Gunicorn.

I will update my post to include this information.

If that’s your objective, then I’d suggest you create a “staging” environment that replicates your production environment. The runserver development environment does not handle static or media files in the same manner that your production environment does. Therefore, what you would do with your configuration to make this work in runserver does not mean that your production environment will work - and vice versa.

Yes, I am aware that production and development environments handle these sorts of things differently, and that when DEBUG is set to False, all static files are handler by a central staticfiles folder.

All other static files that are NOT uploaded to a model show with no issues, the website is up and running & the only images that refuse to show are the images which are in the models and are being called by article.article_image.url.

It’s like it has some sort of vendetta against the images uploaded to models.

  • The way you’ve phrased this leads me to believe that you are still thinking of “media files” as if they were just a different type of static file. They are not. There is no such thing as a “static file uploaded to a model.” If a file is uploaded, it is not a static file.

  • You are using the whitenoise app to handle static files. This app has nothing to do with media files, that’s why there’s the difference in behavior.

  • In your urls, you have:

Since you’re setting DEBUG = False, these url patterns are not being added. Django isn’t going to know how to handle static or media urls when DEBUG = False. You’ve added the third-party “whitenoise” app to handle the static files, but nothing is set to handle the media files. You need to move the media url settings out of the if statement for Django to handle them.
(Note, you will want to move that back into the if when you deploy to production, because in a proper deployment environment, Django won’t be needed to handle either type of file. That’s why I recommend setting up a staging environment to test running your system with DEBUG = False. Using runserver with DEBUG = False tends to create more problems than they resolve.)

Yes, so if DEBUG is set to False, the above will not apply. If DEBUG is set to True`, the above will apply.

Therefore, just to clarify what you are saying, I will need to alter the above to:

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

This means that regardless as to the setting of DEBUG, all MEDIA files will be handled in the same way, where as the STATIC files will be handled either by the local static folders in the respective app, or via the centralized STATIC_URL file.

I will also take on board your recommendation over the creating a stating environment, however, just needing to get me head around this and taking one step at a time.

You are correct, with the additional clarification that this change should only be made when you’re using runserver with DEBUG = False. When you’re ready to move this to your production environment, you’ll want to move it back.

Thank you. Seeing as am now wanting to source media images from an external source (Azure Blogs). I have the following setup

pip install django-storages 
pip install azure-storage-blob
#settings.py
from . import custom_storage
DEBUG = True
STORAGES = {

    'default': {
                'BACKEND': os.environ.get('blauwestadtech.custom_storage.CustomAzureStorage', 'django.core.files.storage.FileSystemStorage')

    },

    "staticfiles": {
        "BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage",
    },
}

AZURE_ACCOUNT_NAME = 'xxx'
AZURE_ACCOUNT_KEY = 'xxxx' 
AZURE_CONTAINER = 'assets'  
AZURE_CUSTOM_DOMAIN = f'{AZURE_ACCOUNT_NAME}.blob.core.windows.net'

if DEBUG:
    # This setting is only used during development with the Django development server
    MEDIA_URL = '/media/'
    MEDIA_ROOT = BASE_DIR / 'media'
else:
    MEDIA_URL = f'https://{AZURE_CUSTOM_DOMAIN}/'
#custom_storage.py
from django.conf import settings
from storages.backends.azure_storage import AzureStorage

class CustomAzureStorage(AzureStorage):
    def __init__(self, *args, **kwargs):
        self.account_name = settings.AZURE_ACCOUNT_NAME
        self.account_key = settings.AZURE_ACCOUNT_KEY
        self.azure_container = settings.AZURE_CONTAINER
        self.expiration_secs = None  # Set expiration time for the URLs if needed
        super().__init__(*args, **kwargs)
#urls.py
if settings.DEBUG:
    #urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
    #urlpatterns += [re_path(r'^media/(?P<path>.*)$', serve, {'document_root': settings.MEDIA_ROOT, }), ]
    urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
    urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
#models.py
from django.conf import settings
def upload_to():
    return f"{settings.AZURE_CONTAINER}/"
def upload_to():
    return f"{settings.AZURE_CONTAINER}/"

class Blog(models.Model):
    BlogImage       =       models.ImageField(default="",upload_to=upload_to(), verbose_name="Blog Image")

With this set up in mind, why is Django constructing the blob image URL by putting the AZURE_CONTAINER name in twice? The URL is constructed in the following manner:

https://<AZURE_ACCOUNT_NAME>.blob.core.windows.net/assets/assets/ when it should be:
https://<AZURE_ACCOUNT_NAME>.blob.core.windows.net/assets/

I have even hard coded the upload_to variable with upload_to="assets/" but Django still incorrectly constructs the URL by duplicating the <AZURE_CONTAINER>. Every change I make is completely ignored, despite development server refreshes and changing the DEBUG from True to False and back again.

I just do not understand the logic Django is using to justify returning the blog URL in this way.

I just don’t understand why Django is incorrectly construing the URL here.