Skip to main content

Command Palette

Search for a command to run...

Structuring File Uploads In Django

Published
•4 min read•View as Markdown
Structuring File Uploads In Django

This is a slight issue I had and decided to write about.

Introduction

At certain points in a Django project, we might need to upload files/Images along with our Instances and want to structure our file uploads folders in a particular manner. For example:

  1. Structuring according to a shared attribute of instances.
  2. Structuring according to time/date
  3. Structuring according to model/table etc.

Django provides us with django.db.models.FileField to manage file uploads and django.db.models.ImageField to manage image uploads (ImageField actually just inherits the FileField class and extends it with a few more attributes).

These classes allow us to pass the upload_to= parameter on initiation. This parameter is what we'll use to structure our file folders.

Use Cases

We'll be looking at 3 use cases for uploading files in django.

We'll be making use of a blog where articles are classified according to category.

Use Case 1

An example of when you would want to do this is when you have to structure your files according to a way of grouping instances.

Example

We'll define the models for the article and category below.

from django.db import models
from django.template.defaultfilters import slugify


class Category(models.Model):
    name = models.CharField(max_length=30)
    description = models.CharField(max_length=300)


class Article(models.Model):
    name = models.CharField(max_length=30)
    body = models.CharField(max_length=5000)
    image = models.ImageField()
    category = models.ForeignKey(Category, on_delete=models.CASCADE)

Depending on how you have configured your project your Images(or files) will be uploaded to a bucket or your machine. What we are about to do works in both cases.

Next, we make the following changes to our Article model


def category_upload(instance, filename):
    return f"categories/{slugify(instance.category.name)}/{slugify(instance.name)}/{slugify(filename)}"


class Article(models.Model):
    name = models.CharField(max_length=30)
    body = models.CharField(max_length=5000)
    image = models.ImageField(upload_to=category_upload) # change was made here
    category = models.ForeignKey(Category, on_delete=models.CASCADE)

This ensures that file uploads belonging to this model are saved in that structure.

For example, if we have a category with the name Web Development and an article with the name Intro to web development, with a file named cover.jpg being uploaded the file path looks something like

categories/web-development/intro-to-web-development/cover.jpg

NOTE: This can also be used for other things like conforming the filename to a certain standard and sanitizing etc.

Use Case 2

If we want to structure our file uploads according to the time(dates) they're uploaded we can do that with the upload_to parameter as well. We can pass a date format to upload_to and get the files uploaded according to the date. e.g

class Article(models.Model):
    name = models.CharField(max_length=30)
    body = models.CharField(max_length=5000)
    image = models.ImageField(upload_to='%Y/%m/%d') # change was made here
    category = models.ForeignKey(Category, on_delete=models.CASCADE)

uploading the same cover image on the 12th of June 2018 would give us a file path like this:

2018/06/12/cover.jpg

Use Case 3

If for example, we want to structure according to the table name. Say all the images belonging to a table should be in a folder we could do this:

class Article(models.Model):
    name = models.CharField(max_length=30)
    body = models.CharField(max_length=5000)
    image = models.ImageField(upload_to='article') # change was made here
    category = models.ForeignKey(Category, on_delete=models.CASCADE)

This can be used in any other way that involved a static folder name and not just for structuring according to a table name.

If we uploaded cover.jpg from above we'd have a file path that looks like this:

article/cover.jpg

Django's Implementation

If we dig a bit we can see how Django implements this:

        if callable(self.upload_to):
            filename = self.upload_to(instance, filename)
        else:
            dirname = datetime.datetime.now().strftime(str(self.upload_to))
            filename = posixpath.join(dirname, filename)
        filename = validate_file_name(filename, allow_relative_path=True)
        return self.storage.generate_filename(filename)

This snippet of code is from the generate_filename method of Django's base FileField class (found here). It checks if what is passed to upload_to is a callable. If it is it calls it with the instance and file name as parameters and assigns what it returns to a variable.

If it isn't a callable it tries to generate a string from a time format and the current date and time with datetime.datetime.now().strftime(). if the string passed to strftime() isn't a time format it returns the same string. It then generates and validates a file name with the OS(posix) and then whatever storage class is being used.

CONCLUSION

In this article, we have seen multiple ways in which we can structure file uploads in Django with the upload_to parameter. The ability to do this provides us with more flexibility in content management.