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"]](/content/images/2022/07/initial-img-loader.jpg) Default widget[/caption]
to this:
[caption id="attachment_46" align="aligncenter" width="750"]](/content/images/2022/07/image-widget.jpg) Styled widget[/caption]
To accomplish the transformation, we need:
- Crispy forms - just because my app uses it and it gives nice bootstrap-based formatting
- Floppy forms - because it allow us to customise widgets
- markusslima's bootstrap-filestyle - to replace the file input widget
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.
Member discussion: