Upload Multiple Images to a Django Model without plugins
I am going to show you how to add multiple images to a Django model without any plugins.

In this article, I am going to show you how to add multiple images to a Django model without any plugins. It’s better to keep the project simple and avoid any extra plugins which can cause unexpected errors during production or disable some of the functionalities. By using a small trick in Django admin you will able to upload multiple images to a particular model.
Now, I assume that you already activated your virtual environment so, let’s create a new Django project named mysite
and a new app named posts
.
django-admin startproject mysite
django-admin startapp posts
Since we use images in our project, we have to configure settings.py
in order to serve static and media files and display templates. Update the TEMPLATES
configuration:
settings.py
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.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
Now, we should add static and media files configurations at the end of this file:
settings.py
STATIC_URL = '/static/'
STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'static'),
]
STATIC_ROOT = os.path.join(BASE_DIR, 'static_root')
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
Great! It’s time to add our models into models.py
and there will be two models in our case. The first model is Post
which includes three fields just to show basic information about the post on the home page.
models.py
from django.db import models
class Post(models.Model):
title = models.CharField(max_length=250)
description = models.TextField()
image = models.FileField(blank=True)
def __str__(self):
return self.title
As you see from the code snippet above we have a title, description, and image fields in the Post model. You can change FileField
to ImageField
it’s up to you since this tutorial covers multiple files and images, FileField
is probably the right choice for now.
In Django, you can’t store multiple images in one field, so we are going to add a new model named PostImage
to store all images and at this time we will use the ForeignKey field to create a relationship between these two models. Update your models.py
by adding PostImage
model:
models.py
from django.db import models
class Post(models.Model):
title = models.CharField(max_length=250)
description = models.TextField()
image = models.FileField(blank=True)
def __str__(self):
return self.title
class PostImage(models.Model):
post = models.ForeignKey(Post, default=None, on_delete=models.CASCADE)
images = models.FileField(upload_to = 'images/')
def __str__(self):
return self.post.title
Once you updated models, it’s time to migrate our models and create a new superuser in order to access Django admin.
python manage.py makemigrations posts
python manage.py migrate
python manage.py createsuperuser
Great! Now, the main part is starting, open your admin.py
to make configurations. We will create a single Django admin from two different models. The default Django administration comes with a concept of inlines, so if you have a one-to-many relationship you can edit the parent and its children on the same page or we can say admin interface has the ability to edit models on the same page as a parent model. However, you are limited in a way that you can’t have inlines inside inlines and nested one-to-many relations, so to avoid that we are using StackedInline
class to edit PostImage
model inside Post
model.
admin.py
from django.contrib import admin
from .models import Post, PostImage
class PostImageAdmin(admin.StackedInline):
model = PostImage
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
inlines = [PostImageAdmin]
class Meta:
model = Post
@admin.register(PostImage)
class PostImageAdmin(admin.ModelAdmin):
pass
Configure your admin.py
as shown the above and you can go and check your admin now by clicking add new post. It should look like the image below:

As you see you can add how many images you want into a Post model and all these images will belong to a specific Post
model with the help of ForeignKey
.
Now, navigate to your views.py
to create views in order to render templates so we can see the created posts. In detail_view
we are filtering PostImage
objects by post foreign key, so related images will be populated inside detail of post. Simply add the following views, it’s not complicated just basic level views to populate images and posts :
views.py
from django.shortcuts import render, get_object_or_404
from .models import Post, PostImage
def blog_view(request):
posts = Post.objects.all()
return render(request, 'blog.html', {'posts':posts})
def detail_view(request, id):
post = get_object_or_404(Post, id=id)
photos = PostImage.objects.filter(post=post)
return render(request, 'detail.html', {
'post':post,
'photos':photos
})
Next, open urls.py
to configure URLs and also add media URL configuration to serve images:
urls.py
from django.contrib import admin
from django.urls import path
from django.conf import settings
from django.conf.urls.static import static
from posts import views
urlpatterns = [
path('admin/', admin.site.urls),
path('', views.blog_view, name='blog'),
path('<int:id>/', views.detail_view, name='detail')
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
Finally, create a new folder named templates
in your project, and then we will add three HTML files just to show posts and details in the browser.
base.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
<title>Multi-Image Tutorial</title>
</head>
<body>
<div class="container py-4">
{% block content %}
{% endblock %}
</div>
<script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script>
</body>
</html>
We are using Bootstrap just to give a simple design to our project.
blog.html
{% extends 'base.html' %}
{% block content %}
<div class="row row-cols-1 row-cols-md-2">
{% for post in posts %}
<div class="col mb-4">
<div class="card">
<div class="view overlay">
<img class="card-img-top" src="{{post.image.url}}" alt="">
<a href="#!">
<div class="mask rgba-white-slight"></div>
</a>
</div>
<div class="card-body">
<h4 class="card-title">{{post.title}}</h4>
<p class="card-text">{{post.description}}</p>
<a href="{% url 'detail' post.id %}" class="btn btn-primary btn-md">Read more</a>
</div>
</div>
</div>
{% endfor %}
</div>
{% endblock %}
detail.html
{% extends 'base.html' %}
{% block content %}
<!--Carousel Wrapper-->
<div id="carousel-example-1z" class="carousel slide carousel-fade" data-ride="carousel">
<!--Indicators-->
<ol class="carousel-indicators">
{% for p in photos %}
<li data-target="#carousel-example-1z" data-slide-to="{{ forloop.counter0 }}" class="{% if forloop.counter0 == 0 %} active {% endif %}"></li>
{% endfor %}
</ol>
<!--/.Indicators-->
<!--Slides-->
<div class="carousel-inner" role="listbox">
{% for p in photos %}
<div class="carousel-item {% if forloop.counter0 == 0 %} active {% endif %}">
<img class="d-block w-100" src="{{p.images.url}}"
alt="First slide">
</div>
{% endfor %}
<!--/First slide-->
<!--/.Slides-->
<!--Controls-->
<a class="carousel-control-prev" href="#carousel-example-1z" role="button" data-slide="prev">
<span class="carousel-control-prev-icon" aria-hidden="true"></span>
<span class="sr-only">Previous</span>
</a>
<a class="carousel-control-next" href="#carousel-example-1z" role="button" data-slide="next">
<span class="carousel-control-next-icon" aria-hidden="true"></span>
<span class="sr-only">Next</span>
</a>
<!--/.Controls-->
</div>
{% endblock %}
In detail.html
I added a carousel to show related images for a specific post. You can see the indicators of the carousel (<ol>) surrounded with for loops and I used for loop counter to detect active carousel items. The same logic applied to carousel items to detect active images.
Great! Now, you can create a new post and check it on the home page. Once you click the details you will be able to see related images in a carousel.
I hope it helps you to organize your multiple images and also it keeps the project simple. Maybe there are a lot of plugins on the internet that can handle multiple images but as I said before these plugins can cause errors or disable some of the functionalities of the project. Simple is more!
You can find the source code at the following link:
Video Explanation
Support 🌏
If you feel like you unlocked new skills, please share them with your friends and subscribe to the youtube channel to not miss any valuable information.
Reference for Thumbnail
Icons made by Freepik from www.flaticon.com