Django web app and images — Cloudinary straightforward study

Jacek Szczerbiński
4 min readAug 4, 2019

Django is a really powerful framework for web apps. We create apps to attract users and make them coming back to our app. With users and database — probably you will end up with a decision — how to store your images.

Now, having said that, if you are a freelance developer, python enthusiast/hobbyist or simply your app is just at the very beginning of lifecycle — you will face server/DB storage issues.

Providers which support Django usually offer very limited storage. Heroku, for example, won’t let you store uploaded images. After reload/update all images will be gone.

We could store images directly in DB, but database size will simply explode in no time.

Hence it is a good practice to store images in external services like AWS 3S or Cloudinary. In this particular study, I will focus on Cloudinary. Simple reason — it is quick to configure.

Creating an account is easy and free. On Heroku, Cloudinary is already listed on the addon list. We will skip this obviously and jump right into Django integration.

First thing first, let’s import the cloudinary library.

pip3 install cloudinary

Then in settings.py, we have to add:

import cloudinary#(...)INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'myapp',
'cloudinary', #add app
]
#add config
cloudinary.config(
cloud_name = os.environ.get('CLOUD_NAME'),
api_key = os.environ.get('API_KEY'),
api_secret = os.environ.get('API_SECRET'),
secure = True
)

It is a good practice to keep really important variables as env variables. All those parameters, so cloud_name, api_key, api_secret are listed in your Cloudinary dashboard. Just copy those and paste in settings

cloudinary dashboard smaple
Cloudinary dashboard lists your important variables

That’s it, we are ready to go. Now let’s take a simple example — avatar upload for users. Below is a model from one of my projects.

from cloudinary.models import CloudinaryFieldclass UserProfile(models.Model):
avatar = CloudinaryField('avatar')
about_me = models.CharField(max_length=255, null=True, blank=True)
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='user_profile')

CloudinaryField is based on CharField so it’s rather lightweight to our DB. Most important for us parameter will be URL, which will fetch to us cloudinary URL. We will cover that a bit later.

Another cool feature of this library is the special form Field. Look at the below sample of forms.py:

from cloudinary.forms import CloudinaryFileFieldclass AvatarUploadForm(forms.ModelForm):
avatar = CloudinaryFileField(
options = {
'crop': 'thumb',
'width': 200,
'height': 200,
'folder': 'avatars'
}
)
class Meta:
model = UserProfile
fields = ('avatar',)

CloudinaryFileField comes with a huge amount of options. Full go here to see full API. We will focus on the most used options.

Option ‘crop’ set as ‘thumb’ says to Cloudinary that you want to create thumbnail from the uploaded image. It has to be paired with ‘width’ and ‘height’! Now, what’s cool about ‘thumb’? Most selfie photos are rectangles. Cloudinary algorithms will try to detect faces and crop the image around it. How cool is that?

Wait, there is more. If you already have some experience with uploads from iPhones, Samsungs, then you now that EXIF params rotate uploaded images sideways. Pretty painful. But Cloudinary covers that too and images always will be oriented correctly.

Another parameter user often is ‘folder’ which just keeps your avatars in a dedicated folder. Keep your storage tidy!

There are many case studies on how to connect image upload from frontend to backend. I will try to go with the most basic way. You can use generic views, it works as well but I think function based will show you exactly what is happening in backend.

I have created an avatar_upload.html template. The most important part is listed below:

<form method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ form.avatar }}
<input type="submit" value="Upload avatar">

Crucial is enctype=”multipart/form-data”! Otherwise, your whole method will fail in the backend.

In views.py:

from .forms import AvatarUploadForm()@login_required
def avatar_upload(request):
user = request.user
instance = get_object_or_404(UserProfile, user=user)
if request.method == "POST":
form = AvatarUploadForm(request.POST, request.FILES, instance=instance)
if form.is_valid():
form.save()
return redirect('/')
form = AvatarUploadForm()
return render(request, 'avatar_upload.html', {'form': form})

Let’s go step by step. This feature is for logged users, hence we use login_required decorator. Then we get from the request which the user is trying to upload an avatar. The instance is the UserProfile object which is connected to the user.

In the POST method, we are passing request.POST, request.FILES (that is why we used multipart/form-data!) and instance -> objects in DB which we will modify. The rest is self-explanatory.

Images are now being uploaded to our Cloudinary server. But how to display them? In this particular example we just simply have to retrieve URL like this:

<img src="{{ user_profile.avatar.url }}" class="rounded-circle" width="150", height="150">

Easy, right? It’s quite effective and I’m using this method in a couple of my web apps. So far (fingers crossed) without any issues.

--

--

Jacek Szczerbiński

Mechanical engineer / manager / full stack software developer