Decent image loader widget in django
3 min read

Decent image loader widget in django

Decent image loader widget in django

I'm working on a website where I need to be able to load an image. The default widget is abysmal; it has a url to the current image, a checkbox and a file loader input element. Even styled, it looks pretty bad. So, I've taken upon myself to prettify it and get from this:

[caption id="attachment_47" align="aligncenter" width="750"]Default widget](/content/images/2022/07/initial-img-loader.jpg) Default widget[/caption]

to this:

[caption id="attachment_46" align="aligncenter" width="750"]Styled widget](/content/images/2022/07/image-widget.jpg) Styled widget[/caption]

To accomplish the transformation, we need:

After copying the various resources around (e.g. the bootstrap-filestyle in static), we need to create a custom widget for the file input.

# In
from floppyforms import ClearableFileInput

class ImageThumbnailFileInput(ClearableFileInput):
    template_name = 'floppyforms/image_thumbnail.html'

and use it in our form:

class BlogForm(forms.ModelForm):

    class Meta(object):
        # Set this form to use the User model.
        model = BlogEntry

        # Constrain the UserForm to just these fields.
        fields = (
            # Main
            # Details
            'image', 'raw_text',
            # SEO
        widgets = {
            'image': ImageThumbnailFileInput

Now, when the form will be rendered, it'll use the ImageThumbnailFileInput widget.

The next step is to create the HTML template used in the widget. Following the hints from floppy-forms' examples, I ended up with something like this:

{# image_thumbnail.html #}
{% load i18n %}
{% load thumbnail %}
{% load crispy_forms_field %}

<div id="div_{{ field.auto_id }}"
    {% if form_show_errors %}
        {% if field.errors %} error{% endif %}
    {% endif %}
    {% if field.css_classes %} {{ field.css_classes }}{% endif %}">
    {% if field.label %}
        <label for="{{ field.id_for_label }}"
            {{ label_class }}
            {% if field.field.required %}
            {% endif %}">
            {{ field.label|safe }}{% if field.field.required %}
                <span class="asteriskField">*</span>{% endif %}
    {% endif %}

    <div class="controls {{ field_class }}">
        <div class="fileupload fileupload-new"
            {% if value.url %}
                <div class="row">
                    <div class="col-md-12">
                        <img src="{{ value|thumbnail_url:'logue' }}"
                            alt="{{ value }}" class="img-thumbnail"/>
                <div class="row">
                    <div class="col-md-12">
                        <input type="checkbox"
                            name="{{ checkbox_name }}"
                            id="{{ checkbox_id }}" />
                        <label for="{{ checkbox_id }}">
                            {% trans "Clear" %}

            {% else %}
                <div class="row">
                    <div class="col-md-12">
                        <img src=""
            {% endif %}
                <br />
            <div class="form-inline">
                <input type="{{ type }}" name="{{ name }}"
                    {% if required %}required{% endif %}
                    data-classButton="btn btn-default"
        {% include 'bootstrap/layout/help_text_and_errors.html' %}

The code contains an easy_thumbnails reference for image display. You can replace it with e.g. a simple ImageField. Otherwise, it is pretty self-explanatory:

  • There's a label part of the widget (used by crispy forms to format it nicely)
  • There's an {% if %} for the case where there's an image already vs. not (and using a placeholder)
  • A checkbox to clear the image (just like the original widget)
  • A file selector input, styled via bootstrap-filestyle

If you just use it, you'll find that the read-only field associated with the file input is either unstyled or fills up 100% of the width. The 100% part is the way the .form-control class is defined in bootstrap 3. To fix that, I've added a custom class in my customiser style file:

/* Form override for file widget */
.form-inline .bootstrap-filestyle input {
  width: 20em;


Update 1: Thanks to John D. for pointing out my missing reference to easy_thumbnails.