Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

django-crispy-forms for jasny file upload

I am using django-crispy-forms (http://django-crispy-forms.readthedocs.org/) and I am trying to use Jasny Bootstrap file upload (http://jasny.github.io/bootstrap/javascript.html#fileupload) to make my webpage look nicer.

As far as I am aware, Crispy forms out of the box does not support Jasny file upload. As I am not very experienced, I am trying to use whatever is available in Crispy forms rather than to create my own layout objects. However, I have tried for several days now, and it doesn't work.

I know this is not the right way to do it, but my attempt so far has been to try to use Crispy-form's Div in forms.py to make django generate something similar to the example code for Jasny file upload.

Code from Jasny file upload:

<div class="fileupload fileupload-new" data-provides="fileupload">
    <div class="fileupload-new thumbnail" style="width: 200px; height: 150px;"><img src="http://www.placehold.it/200x150/EFEFEF/AAAAAA&text=no+image" /></div>
    <div class="fileupload-preview fileupload-exists thumbnail" style="max-width: 200px; max-height: 150px; line-height: 20px;"></div>
    <div>
        <span class="btn btn-file"><span class="fileupload-new">Select image</span><span class="fileupload-exists">Change</span><input type="file" /></span>
    <a href="#" class="btn fileupload-exists" data-dismiss="fileupload">Remove</a>
    </div>
</div>

Excerpt from my forms.py:

       Div(
           HTML("""<div class="fileupload fileupload-new" data-provides="fileupload">
<div class="fileupload-new thumbnail" style="width: 200px; height: 150px;"><img src="http://www.placehold.it/200x150/EFEFEF/AAAAAA&text=no+image" /></div>
        <div class="fileupload-preview fileupload-exists thumbnail" style="max-width: 200px; max-height: 150px; line-height: 20px;"></div>
        <div class"smalltest">
            <span class="btn btn-file"><span class="fileupload-new">Select image</span><span class="fileupload-exists">Change</span>
    """),
          Field('photo1'),
          HTML("""</span><a href="#" class="btn fileupload-exists" data-dismiss="fileupload">Remove</a></div></div>"""),
          css_class = 'photofield'
         ),

It is very ugly code and it does not work, because I still get the original "Choose File" button inside the new buttons.

I am very grateful for anyone who can help! I have been getting quite frustrated and pulling out a lot of hair trying to make this work :(

Many thanks.

like image 408
soo ling Avatar asked Oct 22 '22 11:10

soo ling


1 Answers

I thought I'd share my solution based on a few other SO answers.

First, you shouldn't try and use Layout from Crispy Forms because the HTML from Jasny is too different from the default Crispy Form template. First we create a Crispy Form template that works with Jasny. This is basically just the field.html template updated with the Jasny HTML.

file_field.html :

{# Custom Crispy Forms template for rendering an image field. #}
{% load crispy_forms_field %}

{% if field.is_hidden %}
    {{ field }}
{% else %}
    {% if field|is_checkbox %}
        <div class="form-group">
        {% if label_class %}
            <div class="controls col-{{ bootstrap_device_type }}-offset-{{ label_size }} {{ field_class }}">
        {% endif %}
    {% endif %}
    <{% if tag %}{{ tag }}{% else %}div{% endif %} id="div_{{ field.auto_id }}" {% if not field|is_checkbox %}class="form-group{% else %}class="checkbox{% endif %}{% if wrapper_class %} {{ wrapper_class }}{% endif %}{% if form_show_errors%}{% if field.errors %} has-error{% endif %}{% endif %}{% if field.css_classes %} {{ field.css_classes }}{% endif %}">
        {% if field.label and not field|is_checkbox and form_show_labels %}
            <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 %}

        {% if field|is_checkboxselectmultiple %}
            {% include 'bootstrap3/layout/checkboxselectmultiple.html' %}
        {% endif %}

        {% if field|is_radioselect %}
            {% include 'bootstrap3/layout/radioselect.html' %}
        {% endif %}

        {% if not field|is_checkboxselectmultiple and not field|is_radioselect %}
            {% if field|is_checkbox and form_show_labels %}
                <label for="{{ field.id_for_label }}" class="{% if field.field.required %} requiredField{% endif %}">
                    {% crispy_field field %}
                    {{ field.label|safe }}
                    {% include 'bootstrap3/layout/help_text_and_errors.html' %}
                </label>
            {% else %}
                <div class="controls {{ field_class }}">
                  <div class="fileinput fileinput-{% if field.value and field.value.url %}exists{% else %}new{% endif %}" data-provides="fileinput">
                    <div class="fileinput-new thumbnail" style="width: 200px; height: 150px;">
                      <img data-src="holder.js/100%x100%" alt="100%x100%" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxOTAiIGhlaWdodD0iMTQwIj48cmVjdCB3aWR0aD0iMTkwIiBoZWlnaHQ9IjE0MCIgZmlsbD0iI2VlZSIvPjx0ZXh0IHRleHQtYW5jaG9yPSJtaWRkbGUiIHg9Ijk1IiB5PSI3MCIgc3R5bGU9ImZpbGw6I2FhYTtmb250LXdlaWdodDpib2xkO2ZvbnQtc2l6ZToxMnB4O2ZvbnQtZmFtaWx5OkFyaWFsLEhlbHZldGljYSxzYW5zLXNlcmlmO2RvbWluYW50LWJhc2VsaW5lOmNlbnRyYWwiPjE5MHgxNDA8L3RleHQ+PC9zdmc+" style="height: 100%; width: 100%; display: block;">
                    </div>
                    <div class="fileinput-preview fileinput-exists thumbnail" style="max-width: 200px; max-height: 150px; line-height: 10px;">
                      {% if field.value and field.value.url %}
                      <img src="{{ field.value.url }}">
                      {% endif %}
                    </div>
                    {# imgfileinput, imgselect, imremove used for removing image #}
                    <div id="imgfileinput">
                      <span id="imgselect" class="btn btn-default btn-file">
                        <span class="fileinput-new">Select image</span>
                        <span class="fileinput-exists">Change</span>
                        <input id="imgfile" type="file" name="{{ field.name }}">
                      </span>&nbsp
                      <a id="imgremove" href="#" class="btn btn-default fileinput-exists" data-dismiss="fileinput">Remove</a>
                    </div>
                  </div>

                    {# removed {% crispy_field field %} #}
                    {% include 'bootstrap3/layout/help_text_and_errors.html' %}
                </div>
            {% endif %}
        {% endif %}
    </{% if tag %}{{ tag }}{% else %}div{% endif %}>
    {% if field|is_checkbox %}
        {% if label_class %}
            </div>
        {% endif %}
        </div>
    {% endif %}
{% endif %}

Second, reference the template when defining the layout for your form:

from crispy_forms.layout import Layout, Fieldset, Div, Submit, Reset, HTML, Field, Hidden
class UserForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        super(UserForm, self).__init__(*args, **kwargs)
        self.helper = FormHelper()
        self.helper.layout = Layout(
            Field('avatar', template='file_field.html'),
            'username',
            'first_name',
            'last_name',
        )

Third, by default there is no way to easily clear the image with Jasny and Django. You can find a summary of the Jasny behaviour here. Basically Jasny submits a None, or a blank string depending on if the image was not updated or removed. Django interprets both of these as the image not being update, not the image being removed.

Django uses the ClearableFileInput widget that adds a checkbox which should be selected if you want the file removed. To imitate this functionality, I just added some jQuery to add this input when the remove button is selected and remove the input when the change or insert button is selected:

<script>
  // Allow image to be deleted
  $('#imgremove').on('click', function() {
  field_name = $('#imgfile')[0].getAttribute('name');
    $('#imgfileinput').append('<input id="imgclear" type="hidden" name="'+field_name+'-clear" value="on">');
  })
  $('#imgselect').on('click', function() {
    $('#imgclear').remove();
  })
</script>

You'll notice my Jasny HTML above has been slightly modified to include id's for the tags of interest to make selecting easier.

Seems like a lot of work but once its done, its as easy to use as plain crispy forms.

like image 154
freb Avatar answered Oct 24 '22 03:10

freb