Displaying uploaded files

Okay, I have messed something up in being able to display an uploaded file.
I have a form where users can add a file (image, pdf, .doc, etc) and then I want to display a link to that file.

I first created a folder in the project root called “uploads”. The structure looks like:
Project1
→ clients/
→ uploads/
------> clients_docs
----------> funny-dog.jpg

settings.py

MEDIA_URL = "/media/"
MEDIA_ROOT = os.path.join(BASE_DIR, 'uploads')

urls.py (specifically the media url and Docs section)

    path('<int:client_id>/doc/add', views.DocAdd.as_view(), name="doc_add"),
    path('<int:client_id>/doc/<int:pk>/edit', views.DocEdit.as_view(), name="doc_edit"),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

models.py

class Doc(models.Model):
    client = models.ForeignKey(Client, on_delete=models.CASCADE)
    name = models.CharField(max_length=100, default='')
    file = models.FileField(upload_to="clients_docs", null=True, blank=True)

    def __str__(self):
        return str(self.name)

template detail

{% for doc in doc_list %}
<li><a href="{{ doc.file }}">{{ doc.name }}</a></li>
{% endfor %}

If I upload an image “funny-dog.jpg”, I can see (via the file system), that the file made it into Project1/uploads/clients_docs/funny-dog.jpg.

However, when the template renders, the url to the file looks like this:
http://127.0.0.1:8000/clients/2/clients_docs/funny-dog.jpg
Clicking the link for that gives a “404 page not found” error.

For reference, the normal path (that works) to adding a new doc looks like this:
http://127.0.0.1:8000/clients/2/doc/add

What am I doing wrong to get the link to the newly uploaded file to display correctly?

1 Like

See the docs at Using files in models

Note that in a production-quality environment, you are going to want the upload directory to be completely outside the project directory.

2 Likes

Okay, I had read through that whole article in the Django documentation, but it didn’t contain any information about how to serve up the file in the template.

I narrowed it down to the file being present in my directory structure (outlined in first post), so I knew the only missing piece was the template not serving it up when I used:

{% for doc in doc_list %}
<li><a href="{{ doc.file }}">{{ doc.name }}</a></li>
{% endfor %}

The issue turned out to be in two places…

  1. The root settings.py file needed to have this added to the “TEMPLATES” section context_processors: ‘django.template.context_processors.media’
    So that mine currently looks like this:
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates')],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.media', # had to set for "media" url to work below
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]
  1. I needed to move the part in the urls.py file, UP to the root urls.py location, and NOT in the app’s urls.py section.
    urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

Once I did both of those things, I could see the file when I clicked its link from the template page!
Hope that helps someone else.

2 Likes

The answer that I was trying to lead you to is that there is an api on the FileField to generate the URL to be used to reference that file, .url. So the appropriate fix for your template would be to change:

to: <a href="{{ doc.file.url }}">

(See the caveats and other information at How to manage static files (e.g. images, JavaScript, CSS) | Django documentation | Django)

4 Likes

I see. That does generate the same ultimate url to the file, and looks cleaner!