Django
Django Image Processing Webapp

Django Image Processing Webapp

We are going to build an Django Image Processing Webapp. Here user can upload an image and apply several filters to the image.

In the first part we will learn, how to create a model for image. Then we will create a form to upload an image based on the model created. We will add two functionalities: Rotate and Transpose.

Table Of Contents

Basic Setup

#terminal
mkdir Django_ImageProcessing

cd Django_ImageProcessing
python3 -m venv venv
source venv/bin/activate
gh repo create
#create repo name "Django-Image-Processing"
cd Django-Image-Processing

Install Django

#terminal
pip3 install django==4.2
django-admin startproject image_project
cd image_project
python manage.py runserver

ImageProcess App

python3 manage.py startapp Image

Register the app. Open image_project/settings.py

# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'Image',#new
]

Create an ImageEnhance Model

Since we are using image in our model we need to install the PIL.

#Terminal
pip install pillow

Inside Image/models.py


from django.db import models
from django.core.files import File

from io import BytesIO

from PIL import ImageFilter
from PIL import Image

def imageRotate(image,deg=90):
    im = Image.open(image).convert('L')
   
    out = im.rotate(deg)
    out_io = BytesIO()
    out.save(out_io,'PNG',quality=85)
    out_final = File(out_io,name=image.name)
    return out_final


# Create your models here.
class ImageEnhance(models.Model):
    title = models.CharField(max_length=50)
    image = models.ImageField(upload_to="",blank=True,null=True)
    image_enhanced = models.ImageField(upload_to="",blank=True,null=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now_add=True)
    
    def rotateMe(self,deg=90,*args,**kwargs):
        self.image_enhanced = imageRotate(self.image,deg)
        super().save(*args,**kwargs)

    def __str__(self):
        return self.title

We have created a model name ImageEnhance, with attributes title, image, image_enhanced, created_at, updated_at.

def __str__(self):
        return self.title

Above function is used to store the ImageEnhance objects by its title.

def imageRotate(image,deg=90):
    im = Image.open(image).convert('L')
   
    out = im.rotate(deg)
    out_io = BytesIO()
    out.save(out_io,'PNG',quality=85)
    out_final = File(out_io,name=image.name)
    return out_final

Above function will take the object.image and deg as input. Rotate the image by given degree, saves it and return the saved image.

def rotateMe(self,deg=90,*args,**kwargs):
        self.image_enhanced = imageRotate(self.image,deg)
        super().save(*args,**kwargs)

This method of our class ImageEnhance will send the object.image and deg to the imageRotate function defined above and save the object.

Register the model

Open Image/admin.py and register your model.

from django.contrib import admin
from .models import ImageEnhance

# Register your models here.

admin.site.register(ImageEnhance)

Create a Form

Create Image/forms.py to create the form as per our ImageEnhance model

from django import forms
from .models import ImageEnhance


class ImageForm(forms.ModelForm):
    class Meta:
        model = ImageEnhance
        fields = ['title','image']

Above form will only ask user to enter ‘title’ and ‘image’. ‘create_at’ and ‘updated_at’ will be added automatically, while image_enhance will be added as per user selection.

Views

Edit Image/views as follow to create our first view.

from django.shortcuts import render
from django.http import HttpResponse
from .models import ImageEnhance

# Create your views here.


# Create your views here.
def imageHome(request):
       
    try:
        images = ImageEnhance.objects.all()
        return render(request,'image_home.html',{'images':images})
    except:
        return HttpResponse("We do not have any images to show you")
    

We want to display all the images and their enhanced versions when user visits ‘image_home.html’

Templates

Create a folder named ‘templates’ inside Image app. Within the same folder create ‘base.html’ and ‘image_home.html’.

Edit Image/templates/base.html as follow to include the basic structure of our web pages.

<!DOCTYPE html>
<html>
    <head>
        <title>Image Processing App</title>
        <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-KK94CHFLLe+nY2dmCWGMq91rCGa5gtU4mk92HdvYe+M/SXH301p5ILy+dN9+nJOZ" crossorigin="anonymous">
        <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ENjdO4Dr2bkBIFxQpeoTz1HIcje39Wm4jDKdf19U8gI4ddQ3GYNS7NTKfAdVQSZe" crossorigin="anonymous"></script>
        <meta name="viewpot" content="width=device-width, initial-scale=1"/>

    </head>
    <body>
        <nav class="navbar navbar-expand-lg navbar-light bg-light mb-3">
            <div class="container">
                <a class="navbar-brand" href="{% url 'imageHome' %}">
                    {% load static%}
                    <img src="{% static 'images/icon.png' %}"
                    alt="" width="30" height="24"
                    class="d-inline-block algn-text-top"/>

                
                    Image Processing
                </a>
                <button class="navbar-toggler" type="button"
                data-bs-toggle="collapse" 
                data-bs-target="#navbarNavAltMarkup"
                aria-controls="navbarNavAltMarkup"
                aria-expanded="false"
                aria-label="Toggle navigation">
                <span class="navbar-toggler-icon">

                </span>
            
            </button>
            <div class="collapse navbar-collapse" 
            id="navbarNavAltMarkup">
            <div class="navbar-nav ms=auto">
               
                <a class="nav-link" href="#">Image Upload</a> 

            </div>
        </div>
            </div>
        </nav>
        <div class="container">
            {% block content %}

            {% endblock content %}
        </div>
        <footer class="text-center text-lg-start bg-light text-muted mt-4">
            <div class="text-center p-4">
                &copy; Copyright - 
                <a class="text-reset fw-bold text-decoration-none"
                target="_blank"
                href="https://twitter.com/chrmanning">
            Garg
            </a>

            </div>
        </footer>
    </body>
</html>

Edit Image/templates/image_home.html as follow to load all the ImageEnhance object.

{% extends "base.html" %}
{% block content %}
           
            <br/>
            <br/>
            <div class="row row-cols-1 row-cols-md-3 g-4">
                {% for im in images %}

                
                <div v-for="im in images" class="col">
                    {% if im.image%}

                    <div class="card-title">
                        <h2>{{ im.title }}</h2>
                    </div>
                    <div class="card">
                        <img src="{{ im.image.url }}">

                    </div>
                    {% endif %}

                    <div class="card">
                        {% if im.image_enhanced %}

                        <img src="{{ im.image_enhanced.url }}">

                        {% endif %}

                    </div>

                    <div class="card-text">
                        <p>{{ im.update_at }}</p>
                    </div>
                    
                    
                </div>
                   

                {% endfor %}

            </div>

            

        </div>
        
{% endblock content %}

Static and Media Settings

To load the image and static files edit the settings.py inside our image_project as follow.

import os

#Configuring for images

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

STATIC_URL = "static/"

STATICFILES_DIRS = [
    BASE_DIR / 'image_project/static/',
]

We are using an icon for our website which we load in base.html as follow. Create a static folder inside our iimage_project. Then create images folder within static folder and paste icon.png there.

...............
<a class="navbar-brand" href="{% url 'home' %}">
                    {% load static%}
                    <img src="{% static 'images/icon.png' %}"
                    alt="" width="30" height="24"
                    class="d-inline-block algn-text-top"/>

                
                    Image Processing
                </a>
.................................

Urls

Create Image/urls.py add the following lines of code to call the imageHome function of views.py file.

from django.urls import path
from .views import imageHome


urlpatterns = [
    path("",imageHome,name='imageHome'),
]

Now, edit image_project/urls.py to tell the project to look inside the Image/urls.py.

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

from django.conf.urls.static import static
from django.conf import settings

urlpatterns = [
    path('admin/', admin.site.urls),
    path("",include("Image.urls")),

]


urlpatterns += static(settings.MEDIA_URL,
                      document_root=settings.MEDIA_ROOT)

Create Admin and add Few images

#terminal
python manage.py makemigrations
python manage.py migrate
python manage.py createsuperuser

Run the server and visit http://127.0.0.1:8000/admin

Enter your credentials.

Click Image enhances.

Click “ADD IMAGE ENHANCE”

Now add few images.

The Image enhanced will be added by the program after doing some processing so we do not add it for now.

Visit http://127.0.0.1:8000/ to see our images.

Using The form to upload the image

We need to add a function in Image/views.py to load and handle the form.

......................................................
from .forms import ImageForm
......................................................

def imageForm(request):
    if request.method == 'POST':
        form = ImageForm(request.POST,request.FILES)
        if form.is_valid():
            #save the model instance
            instance = form.save(commit=False)
            
            instance.save()

            return render(request,"image_process.html",{"form":form,'object':instance})
        
    else:
        form = ImageForm()

    return render(request,"image_form.html",{'form':form})

Above method will pass the instance of ImageForm to the “image_form.html” file if the request method is not POST.

When we submit the form, our function will check if the form is valid and then save the instance for data from form. It will then pass this data to “image_process.html”.

Creating the templates

Create Image/templates/image_form.html.

{% extends "base.html" %}
{% block content %}

<h1>Upload an Image </h1>
<form method="post" enctype="multipart/form-data">
    {% csrf_token %}
    {{ form.as_p }}
    <input type="submit" value="Upload">
</form>
{% endblock content %}

Create Image/templates/image_process.html

{% extends "base.html" %}
{% block content %}
{% load static %}

{% if object%}
<h1>Image uploaded</h1>
<img src="{{ object.image.url }}">
    {% if object.image_enhanced %}
    <img src="{{ object.image_enhanced.url }}">
    {% endif %}
{% endif %}

{% endblock content %}

Now update Image/urls.py to call the imageForm of Image/views.py.

from django.urls import path
from .views import imageHome, imageForm


urlpatterns = [
    path("",imageHome,name='imageHome'),
    path("upload/",imageForm,name="imageform"),
]

Now when we call the http://127.0.0.1:8000/upload/ imageform function of Image/views.py will be called. It will render “image_form.html” and we will be see a form as follow.

Fill the form then click upload.

You will see the uploaded image.

Adding A Button To Rotate the Image

Edit Image/templates/image_process.html to add the button.

{% extends "base.html" %}
{% block content %}
{% load static %}

{% if object%}
<h1>Image uploaded</h1>
<img src="{{ object.image.url }}">
    {% if object.image_enhanced %}
    <img src="{{ object.image_enhanced.url }}">
    {% endif %}
{% endif %}

<form id="dropdown-form" method="post" url="">
    {% csrf_token %}
    

<button name="Rotate" id="submit-button">Click To Rotate</button>
</form>

{% endblock content %}

Now, edit Image/views.py to handle the condition after the button has been clicked.

..................................................................
def imageForm(request):
    if request.method == 'POST':
        form = ImageForm(request.POST,request.FILES)
        if form.is_valid():
            #save the model instance
            instance = form.save(commit=False)
            
            instance.save()

            return render(request,"image_process.html",{"form":form,'object':instance})
        else:#new
            latest_object = ImageEnhance.objects.latest('id')#new
            latest_object.rotateMe()#new
            return render(request,"image_process.html",{"form":form,'object':latest_object})#new

        
    else:
        form = ImageForm()

    return render(request,"image_form.html",{'form':form})

....................................................................

The added code marked by #new, will check if the request method is “POST” but the form has not been submitted. In this case it will take the latest object of ‘ImageEnhance’ class (i.e last uploaded image) call method which will rotate it by 90 and display both images.

The Result So Far

The upload form

Adding Transpose Feature

Edit Image/templates/image_process.html as follow to add the button for Transpose.

{% extends "base.html" %}
{% block content %}
{% load static %}

{% if object%}
<h1>Image uploaded</h1>
<img src="{{ object.image.url }}">
    {% if object.image_enhanced %}
    <img src="{{ object.image_enhanced.url }}">
    {% endif %}
{% endif %}

<form id="dropdown-form" method="post" url="">
    {% csrf_token %}
    

<button name="Rotate" id="submit-button">Click To Rotate</button>
<button name="Transpose" id="submit-button">Click To Transpose</button>
</form>

{% endblock content %}

Add function to transpose image in Image/models.py.

def imageTranspose(image):
    im = Image.open(image).convert('L')
   
    out = im.transpose(Image.FLIP_LEFT_RIGHT)
    out_io = BytesIO()
    out.save(out_io,'PNG',quality=85)
    out_final = File(out_io,name=image.name)
    return out_final

Also add the “transposeMe()” method inside ImageEnhance class.

#Create your models here.
class ImageEnhance(models.Model):
    title = models.CharField(max_length=50)
    image = models.ImageField(upload_to="",blank=True,null=True)
    image_enhanced = models.ImageField(upload_to="",blank=True,null=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now_add=True)
    
    def rotateMe(self,deg=90,*args,**kwargs):
        self.image_enhanced = imageRotate(self.image,deg)
        super().save(*args,**kwargs)

    def transposeMe(self,*args,**kwargs):
        self.image_enhanced = imageTranspose(self.image)
        super().save(*args,**kwargs)

    def __str__(self):
        return self.title

Now, we need to handle the request from both buttons in Image/views.py.

def imageForm(request):
    if request.method == 'POST':
        form = ImageForm(request.POST,request.FILES)
        if form.is_valid():
            #save the model instance
            instance = form.save(commit=False)
            
            instance.save()

            return render(request,"image_process.html",{"form":form,'object':instance})
        elif 'Rotate' in request.POST:
            latest_object = ImageEnhance.objects.latest('id')
            latest_object.rotateMe()
            return render(request,"image_process.html",{"form":form,'object':latest_object})
        elif 'Transpose' in request.POST:
            latest_object = ImageEnhance.objects.latest('id')
            latest_object.transposeMe()
            return render(request,"image_process.html",{"form":form,'object':latest_object})

        
    else:
        form = ImageForm()

    return render(request,"image_form.html",{'form':form})

Finally, we also want to add link to upload image in our navigation bar. For this purpose, open base.html and edit it.

...............................
<div class="navbar-nav ms=auto">
               
                
                <a class="nav-link" href="{% url 'imageform' %}">Upload Image</a>

</div>
..........................

Final Output of Part I

Run the server and visit http://127.0.0.1:8000/

You will see all the images along with their processed versions, if any.

Click Upload Image in the navigation bar to upload the image.

After Upload

After Rotation

After transposing the image

Going Back to Home Page

Django Image Processing Webapp

That’s all for this part. In next part we will create multiple options dynamically with the help of javascript, so that user can choose between different filters.

Push to github

#Terminal
git add .
git commit -m "upload, rotate and transpose complete"
git push