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 forms.py
#
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
            "name",
            # Details
            'image', 'raw_text',
            # SEO
            'description'
        )
        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 }}"
    class="control-group
    {% 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 }}"
            class="control-label
            {{ label_class }}
            {% if field.field.required %}
                requiredField
            {% endif %}">
            {{ field.label|safe }}{% if field.field.required %}
                <span class="asteriskField">*</span>{% endif %}
        </label>
    {% endif %}

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

            {% else %}
                <div class="row">
                    <div class="col-md-12">
                        <img src="http://www.placehold.it/600x250"
                            class="img-thumbnail"/>
                    </div>
                </div>
            {% endif %}
            <div>
                <br />
            <div class="form-inline">
                <input type="{{ type }}" name="{{ name }}"
                    {% if required %}required{% endif %}
                    class="filestyle"
                    data-icon="false"
                    data-classButton="btn btn-default"
                    data-classInput="form-control">
            </div>
            </div>
        </div>
        {% include 'bootstrap/layout/help_text_and_errors.html' %}
    </div>
</div>

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;
}

HTH,

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