Thanks in advance for reading. I'm working on my final project for CS50W which involves working with a series of local audio files (user cannot upload additional files at this time). The issue occurs when I try to populate an src attribute with the file. I have two URL paths which deal with accessing these files: new/ and edit/int:id. When I access the audio files in new/, it works as intended and I can play the file from the tag. However, when I try to access the files in the edit/int:id path, I get this error:
GET http://localhost/edit/8/media/Aminor_Ipi3udk.mp3 net::ERR_ABORTED 404 (Not Found)
I am relatively new to coding (just did CS50x and then started CS50w) and I don't understand why I'm getting this error or how I can fix it - I'm doing the same thing for both paths and yet it only works in one of them. I would be grateful if someone could help me to remedy this or at least point me in the direction of understanding why this is happening.
views.py
def edit(request, id):
song = Song.objects.get(id=id)
sections = Section.objects.filter(song=song).order_by('order')
chords = Chord.objects.all()
if request.method == "GET":
return render(request, "songbud/edit.html", {
'song':song,
'sections':sections,
'chords':chords
})
songbud.js
function select_audio_edit(elem) {
var parent_Node = elem.parentNode;
console.log(parent_Node.childNodes);
var file = parent_Node.childNodes[3].options[parent_Node.childNodes[3].selectedIndex].getAttribute('data-file');
//console.log(file);
//console.log(parent_Node.childNodes);
parent_Node.childNodes[5].setAttribute("src", file);
return false;
};
function fill_audio() {
let elements = document.querySelectorAll("#chordtemp");
elements.forEach(div => {
let chord = div.childNodes[1].innerHTML;
Array.from(div.childNodes[3].options).forEach(function(option_element) {
if (option_element.text == chord) {
option_element.selected = true;
let file = option_element.dataset.file;
console.log(file);
div.childNodes[5].setAttribute("src", file);
}
});
});
};
edit.html
{% extends "songbud/layout.html" %}
{% load static %}
{% block body %}
<div id="songcreate" style="margin: 30px; font-family: 'Courier New';">
<h1 id="song-title">{{ song.title }}</h1>
<button class="btn btn-outline-warning" id="addsection" onclick="return add_section()">+ add section</button>
</div>
{% for section in sections %}
<div style="display: block; margin: 20px;" id='sectiontemplate'>
<label for='sectiontype'>Choose a section:</label>
<br>
<select class="form-select" aria-label="Default select example" name='sectiontype' id='sectiontype' style="display: inline-block;">
{% if section.sectiontype == 'Intro' %}
<option selected>Intro</option>
{% else %}
<option >Intro</option>
{% endif %}
{% if section.sectiontype == 'Verse' %}
<option selected>Verse</option>
{% else %}
<option>Verse</option>
{% endif %}
{% if section.sectiontype == 'Chorus' %}
<option selected>Chorus</option>
{% else %}
<option>Chorus</option>
{% endif %}
{% if section.sectiontype == 'Bridge' %}
<option selected>Bridge</option>
{% else %}
<option>Bridge</option>
{% endif %}
{% if section.sectiontype == 'Interlude' %}
<option selected>Interlude</option>
{% else %}
<option>Interlude</option>
{% endif %}
{% if section.sectiontype == 'Breakdown' %}
<option selected>Breakdown</option>
{% else %}
<option>Breakdown</option>
{% endif %}
{% if section.sectiontype == 'Solo' %}
<option selected>Solo</option>
{% else %}
<option>Solo</option>
{% endif %}
{% if section.sectiontype == 'Outro' %}
<option selected>Outro</option>
{% else %}
<option>Outro</option>
{% endif %}
</select>
<button class="btn btn-outline-warning" id="addchord" onclick='add_chord(this)' style="display: inline-block;">+ add chord</button>
<br>
{% for chord in section.chords %}
<div id='chordtemp'>
<p style='display: none;'>{{ chord }}</p>
<select name="chordselect" id="chordselect" class="form-select" aria-label="Default select example" style="display: inline-block; vertical-align: center;" onchange="return select_audio_edit(this)">
{% for chrd in chords %}
<option data-file="media/{{ chrd.file }}">{{ chrd }}</option>
{% endfor %}
</select>
<audio controls id='audiofile' style="display: inline-block; position: relative; top: 23px;">
<source src="" type="audio/mp3">
</audio>
</div>
{% endfor %}
</div>
{% endfor %}
<!-- These are the templates for sections and chords -->
<div style='display:none;' data-type='sectiontemplate' id='sectiontemplate'>
<label for='sectiontype'>Choose a section:</label>
<br>
<select class="form-select" aria-label="Default select example" name='sectiontype' id='sectiontype' style="display: inline-block;">
<option>Intro</option>
<option>Verse</option>
<option>Chorus</option>
<option>Bridge</option>
<option>Interlude</option>
<option>Breakdown</option>
<option>Solo</option>
<option>Outro</option>
</select>
<button class="btn btn-outline-warning" id="addchord" onclick='add_chord(this)' style="display: inline-block;">+ add chord</button>
<br>
<br>
</div>
<div style='display:none;' data-type='chordtemplate' id='chordtemplate'>
<select name="chordselect" id="chordselect" class="form-select" aria-label="Default select example" style="display: inline-block; vertical-align: center;" onchange="return select_audio(this)">
{% for chord in chords %}
<option data-file="media/{{ chord.file }}">{{ chord }}</option>
{% endfor %}
</select>
<audio controls style="display: inline-block; position: relative; top: 23px;">
<source src="" type="audio/mp3">
</audio>
</div>
<button class="btn btn-outline-warning" id="savesong" onclick="return save_song()">save</button>
<button class="btn btn-outline-warning" id="exportsong" onclick="return export_song_edit()">export</button>
{% endblock %}
{% block script %}
<script src="{% static 'songbud/songbud.js' %}"></script>
{% endblock %}
The quick fix here is to change media/{{ chord.file }} to /media/{{ chord.file }}. However, you shouldn't be manually creating this path in the first place. I think you can do {{ chord.file.url }} instead. Here I'm assuming that chord is a model object with a FileField named file. I suggest you check the documentation for FileField to verify this and understand it better.
Related
I am pretty new in the world of programming with python, especially using flask for my current WebApp project:-)
Currently, I am trying to combine the pagination functionality with a filtering option which works perfectly for the first page :-( The filter value gets lost every time, when I change from e.g. page 1 to page 2.
How could you prevent the value from not being lost using different radios?
Attached are the relevant code sections for your review:
#data.route('/search', methods=['POST', 'GET'])
def search():
radios = request.form.get('size')
page = request.args.get('page', 1, type = int)
articles = Data.query.order_by(Data.id.desc()).paginate(page = page, per_page = 5)
if request.method == 'POST' and 'number' in request.form and radios == 'size_1':
number = request.form['number']
search = "%{}%".format(number)
articles = Data.query.filter(Data.sizes.like(search)).paginate(page = page, per_page=7)
elif request.method == 'POST' and 'number' in request.form and radios == 'size_2':
...
...
...
return render_template('search.html',articles = articles)
----HTML_Pagination----
<div class="row justify-content-md-center" style="margin: 3px; padding-top: 30px; text-align: center;">
<div class="col-12">
{% if articles.iter_pages(articles.total < 10) %}
{% for page_num in articles.iter_pages() %}
{% if page_num %}
{% if articles.page == page_num %}
<a class="btn btn-secondary mb-3" style="font-size: 10px;"
href="{{ url_for('data.search', page = page_num) }}">{{ page_num }}</a>
{% else %}
<a class="btn btn-outline-secondary mb-3" style="font-size: 10px;"
href="{{ url_for('data.search', page = page_num)}}">{{ page_num }}</a>
{% endif %}
{% else %}
...
{% endif %}
{% endfor %}
{% else %}
{% for page_num in articles.iter_pages(left_edge = 2, right_edge = 2, left_current = 2, right_current = 2) %}
{% if page_num %}
{% if articles.page == page_num %}
<a class="btn btn-secondary mb-3" style="font-size: 10px;"
href="{{ url_for('data.search', page = page_num)}}">{{ page_num}}</a>
{% else %}
<a class="btn btn-outline-secondary mb-3" style="font-size: 10px;"
href="{{ url_for('data.search', page = page_num)}}">{{ page_num}}</a>
{% endif %}
{% else %}
...
{% endif %}
{% endfor %}
{% endif %}
----HTML----
---Radio & Input---
<div class="form-check">
<input class="form-check-input" type="radio" name="size" id="size" value="size_1">
<label class="form-check-label" for="size">
Size 1
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="size" id="size" value="size_2">
<label class="form-check-label" for="size">
Size 2
</label>
</div>
...
...
<div class="col-6" style="text-align: right;">
<div class="col-12" style="padding-right: 10px;">
<input class="form-control form-control-sm" name="number" id="number" value="{{ request.form['number'] }}" placeholder="Filter by...">
</div>
</div>
---Submit Button---
<div class="col-4" style="padding-left: 20px;">
<div class="col-md-auto" style=" padding-bottom: 15px;">
<div class="btn-group" role="group" aria-label="Basic outlined example">
{{ filter_articles.submit(class="btn btn-outline-secondary", style="font-size: 14.5px; ") }}
</div>
</div>
</div>
Any guide is highly appreciated. Thank you!
I tried so many times , but i didn't get any solution for rendering image and content without html tags , i used {{content|safe}} for rendering content on template and i tried strip_tags for html tags removal but it didn't work well for url's , can anyone suggest me why i am getting html tags while rendering in templates.
Models.py
class Product(models.Model):
name = models.RichTextUploadingField(max_length="125")
description = models.RichTextUploadingField()
views.py
def poll():
context = questions.object.all()
return render(request, 'quiz.html', context)
template:
<html>
<body>
{% for q in context %}
<div id="section{{forloop.counter}}">
<script type="text/javascript">
marks.push(Number("{{marks}}"));
neg_marks.push(Number("{{neg_marks}}"));
</script>
<p id="{{forloop.counter}}"><span>{{forloop.counter}}.{{q.question|linebreaks}}
</span></p>
{% if q.figure %}
<img src="{{q.figure.url}}" alt="image" class="img-fluid" width="200px" height="200px"><br><br>
{% endif %}
<input type="radio" id="{{forloop.counter}}option1" name="{{forloop.counter}}" value="1">
<label for="{{forloop.counter}}option1">{{q.option_1|safe }}</label><br>
<input type="radio" id="{{forloop.counter}}option2" name="{{forloop.counter}}" value="2">
<label for="{{forloop.counter}}option2">{{q.option_2|safe}}</label><br>
{% if q.option_3 != "" %}
<input type="radio" id="{{forloop.counter}}option3" name="{{forloop.counter}}" value="3">
<label for="{{forloop.counter}}option3">{{q.option_3|safe}}</label><br>
{% endif %}
{% if q.option_4 != "" %}
<input type="radio" id="{{forloop.counter}}option4" name="{{forloop.counter}}" value="4">
<label for="{{forloop.counter}}option4">{{q.option_4|safe}}</label><br>
{% endif %}
{% if q.option_5 != "" %}
<input type="radio" id="{{forloop.counter}}option5" name="{{forloop.counter}}" value="5">
<label for="{{forloop.counter}}option5">{{q.option_5|safe }}</label><br>
{% endif %}
{% if forloop.first %}
<p role="button" class="btn btn-warning" id="next{{forloop.counter}}" onclick="next_section(this.id)"
style="float: right;">Next</p>
{% elif forloop.last %}
<p role="button" class="btn btn-warning" id="prev{{forloop.counter}}" onclick="prev_section(this.id)"
style="float:left;">Previous</p>
{% else %}
<p role="button" class="btn btn-warning" id="next{{forloop.counter}}" onclick="next_section(this.id)"
style="float: right;">Next</p>
<p role="button" class="btn btn-warning" id="prev{{forloop.counter}}" onclick="prev_section(this.id)"
style="float:left;">Previous</p>
{% endif %}
<br>
<hr>
</body>
</html>
there is no direct way to access images in ck-editor field ... but if you have the direction to the img you can get it like /media/uploads/2020/12/21/my_img.jpg
i suggest to use image field to store external field with image you want
and you have problem here : context = questions.object.all() it should be
context = {'context':questions.object.all()}
I've been searching this for days, got some progress but something stop me.
I'm trying to override the django xadmin's list_display into "Chained Select Boxes"/"Cascading dropdown list" style, like the "country state city".
i've add some select javascript into '/xadmin/views/model_list.html'
but i do not know how can i transfer the data result from my database into this javascript.
Anybody could show me? thanks in advance.
{% extends base_template %}
{% load i18n %}
{% load xadmin_tags %}
{% block extrastyle %}
<style type="text/css">
.btn-toolbar{margin-top: 0;}
#content-block.full-content{margin-left: 0;}
</style>
{% endblock %}
{% block bodyclass %}change-list{% endblock %}
{% block nav_title %}{% if brand_icon %}<i class="{{brand_icon}}"></i> {% endif %}{{brand_name}}{% endblock %}
{% block nav_toggles %}
{% include "xadmin/includes/toggle_menu.html" %}
{% if has_add_permission %}
<i class="fa fa-plus"></i>
{% endif %}
<button class="navbar-toggle pull-right" data-toggle="collapse" data-target=".content-navbar .navbar-collapse">
<i class="fa fa-filter"></i>
</button>
{% endblock %}
{% block nav_btns %}
{% if has_add_permission %}
<a href="{{add_url}}" class="btn btn-primary"><i class="fa fa-plus"></i>
{% blocktrans with cl.opts.verbose_name as name %}Add {{ name }}{% endblocktrans %}</a>
{% endif %}
{% endblock nav_btns %}
{% block content %}
<div class="content-toolbar btn-toolbar pull-right clearfix">
{% view_block 'top_toolbar' %}
{% block toolbar_column %}
<div class="btn-group">
<a class="dropdown-toggle btn btn-default btn-sm" data-toggle="dropdown" href="#">
<i class="fa fa-list-alt"></i> {% trans "Columns" %} <span class="caret"></span>
</a>
<ul class="dropdown-menu model_fields pull-right" role="menu" aria-labelledby="dLabel">
<li><i class="fa fa-refresh"></i> {% trans "Restore Selected" %}</li>
<li class="divider"></li>
{% for f, selected, flink in model_fields %}
<li><a href="{{flink}}">
{% if selected %}<i class="fa fa-check"></i>{% else %}<i class="fa fa-blank"></i>{% endif %}
{{f.verbose_name}}</a></li>
{% endfor %}
</ul>
</div>
{% endblock toolbar_column %}
{% block toolbar_layouts %}
<div class="btn-group layout-btns" data-toggle="buttons">
<label class="btn btn-default btn-sm layout-normal active">
<input type="radio"> <i class="fa fa-th-large"></i>
</label>
<label class="btn btn-default btn-sm layout-condensed">
<input type="radio"> <i class="fa fa-th"></i>
</label>
{% view_block 'top_layout_btns' %}
</div>
{% endblock toolbar_layouts %}
{% block toolbar_fullscreen %}
<div class="btn-group layout-btns" data-toggle="buttons-checkbox">
<button type="button" class="btn btn-default btn-sm layout-full"><i class="fa fa-expand"></i></button>
{% view_block 'top_check_btns' %}
</div>
{% endblock toolbar_fullscreen %}
{% view_block 'top_btns' %}
</div>
<ul class="pagination pagination-sm pagination-left pagination-inline">
{% view_block 'pagination' 'small' %}
</ul>
<form id="changelist-form" action="" method="post"{% view_block 'result_list_form' %}>{% csrf_token %}
{% view_block 'results_top' %}
<div class="results table-responsive">
{% if results %}
{% block results_grid %}
<div id="element_id">
<span class="FirstClass">FirstClassScene:
<select class="FirstClassScene">
{% for o in result_headers.cells %}
<option value="BeautySalon" >{{ o.field_name }}</option>
{% endfor %}
</select>
</span>
<span class="SecondClass">SecondClassScene:
<select class="SecondClassScene">
{% for o in result_headers.cells %}
<option value="hall" selected>{{ o.field_name }}</option>
{% endfor %}
</select>
</span>
<span class="ThirdClass">ThirdClassScene:
<select class="ThirdClassScene">
{% for o in result_headers.cells %}
<option value="ConsultingRoom" selected>{{ o.field_name }}</option>
{% endfor %}
</select>
</span>
</div>
<script src="jquery.cxselect.js"></script>
<script type="text/javascript" src=""></script>
<script>
$('#element_id').cxSelect({
url: 'cityData.min.json',
selects: ['FirstClassScene', 'SecondClassScene', 'ThirdClassScene'],
emptyStyle: 'none'
});
$(document).ready(function(){
var FirstClassSelect = $(".FirstClass").children("select");
var SecondClassSelect = $(".SecondClass").children("select");
FirstClassSelect.change(function(){
var FirstClassValue = $(this).val();
if( FirstClassValue != ""){
if(!FirstClassSelect.data(FirstClassValue)){
$.post("ChainSelect",{keyword:FirstClassValue,type:"top"},function(data){
if (data.length != 0){
SecondClassSelect.html("");
$("<option value=''>please choose</option>").appendTo(SecondClassSelect);
for(var i = 0;i < data.length; i++ ){
$("<option value =' " + data[i] + " '> "+ data[i] +"</option>").appendTo(SecondClassSelect);
}
SecondClassSelect.parent().show();
FirstClassSelect.next().show();
}else{
SecondClassSelect.parent().hide();
FirstClassSelect.next().hide();
}
FirstClassSelect.data(FirstClassValue,data);
},"json");
}else{
var data = FirstClassSelect.data(FirstClassValue);
if (data.length != 0){
SecondClassSelect.html("");
$("<option value=''>please choose</option>").appendTo(SecondClassSelect);
for(var i = 0;i < data.length; i++ ){
$("<option value =' " + data[i] + " '> "+ data[i] +"</option>").appendTo(SecondClassSelect);
}
SecondClassSelect.parent().show();
FirstClassSelect.next().show();
}else{
SecondClassSelect.parent().hide();
FirstClassSelect.next().hide();
}
}
}else{
SecondClassSelect.parent().hide();
FirstClassSelect.next().hide();
}
});
</script>
{% endblock %}
I need to render a language selector as an unordered list in Django such as:
<ul>
...
<li>EN</li>
<li>FR</li>
<ul>
I'm using Django i18n/set_language without i18n_pattern and it works very well if I use the form below, given in the documentation:
{% load i18n %}
<form action="{% url 'set_language' %}" method="post">{% csrf_token %}
<input name="next" type="hidden" value="{{ redirect_to }}" />
<select name="language">
{% get_current_language as LANGUAGE_CODE %}
{% get_available_languages as LANGUAGES %}
{% get_language_info_list for LANGUAGES as languages %}
{% for language in languages %}
<option value="{{ language.code }}"{% if language.code == LANGUAGE_CODE %} selected="selected"{% endif %}>
{{ language.name_local }} ({{ language.code }})
</option>
{% endfor %}
</select>
<input type="submit" value="Go" />
</form>
I'd like to continue using i18n/set_language, but with the <li> structure, without the form <select> and the submit button.
Is it possible? How could I do this?
As that link explains, the built-in set_language view is expecting a POST, which you can't do from a link (except by using Javascript).
But the next section, Explicitly setting the active language, gives you all the details you need to write your own view that can take the parameter from the URL. So:
def set_language_from_url(request, user_language):
translation.activate(user_language)
request.session[translation.LANGUAGE_SESSION_KEY] = user_language
return redirect(' ...somewhere... ')
and give it a URL:
url(r'/set_language/(?P<user_language>\w+)/$', set_language_from_url, name="set_language_from_url")
Now you can do:
<li>EN</li>
<li>FR</li>
etc.
I really didn't like the idea of having to do 2 clicks (one for selecting the language and another click to "Go" to submit it), so I found a work-around. It is still a form, but it works like a list:
As explained here, one can use buttons instead of links:
<form action="{% url 'set_language' %}" method="post">
{% csrf_token %}
<input name="next" type="hidden" value="{{ request.get_full_path|slice:'3:' }}" />
<ul class="nav navbar-nav navbar-right language menu">
{% get_current_language as LANGUAGE_CODE %}
{% get_available_languages as LANGUAGES %}
{% get_language_info_list for LANGUAGES as languages %}
{% for language in languages %}
<li>
<button type="submit"
name="language"
value="{{ language.code }}"
class="{% if language.code == LANGUAGE_CODE %}selected{% endif %}">
{{ language.name_local }}
</button>
</li>
{% endfor %}
</ul>
</form>
Then, as per Bootstrap Dropdown docs:
Historically dropdown menu contents had to be links, but that’s no
longer the case with v4. Now you can optionally use elements
in your dropdowns instead of just s.
Merge both and tune it a bit with css to make it look like a list, and this is how it looks like for me:
css
.language-btn{
background-color: #c9f0dd;
border: 1px solid #c9f0dd;
border-radius: 2px;
color: #0C4B33;
font-size: 10px;
margin-right: 5px;
}
.navbar-right{
margin-right: 20px;
}
.dropdown-menu{
min-width: inherit;
}
.fake-btn{
background-color: transparent;
border: none;
color: rgb(150,150,150);
height: 12px;
font-size: 11px;
}
.no-margins{
margin: 0;
padding: 0;
}
.selected{
color: #0C4B33;
}
html
<div class="btn-group nav navbar-nav navbar-right language menu">
<button class="btn btn-secondary btn-sm dropdown-toggle language-btn" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
{% get_language_info for LANGUAGE_CODE as lang %}
{{ lang.name_local }} ({{ lang.code }})
</button>
<div class="dropdown-menu" aria-labelledby="dropdownMenu2">
<form action="{% url 'set_language' %}" method="post">
{% csrf_token %}
<input name="next" type="hidden" value="{{ request.get_full_path|slice:'3:' }}" />
{% get_current_language as LANGUAGE_CODE %}
{% get_available_languages as LANGUAGES %}
{% get_language_info_list for LANGUAGES as languages %}
{% for language in languages %}
<ul class="no-margins">
<button type="submit"
name="language"
value="{{ language.code }}"
class="{% if language.code == LANGUAGE_CODE %}selected{% endif %} dropdown-item fake-btn">
{{ language.name_local }} ({{ language.code }})
</button>
</ul>
{% endfor %}
</form>
</div>
</div>
Dont forget to include this path as Django docs says:
https://docs.djangoproject.com/en/3.0/topics/i18n/translation/#miscellaneous
path('i18n/', include('django.conf.urls.i18n')),
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>
Remove
</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>Remove</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.
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> 
<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.
I ended up not using django-crispy-forms, I am now writing my own custom form template using the Django template language. Jasny Bootstrap file upload works fine this way.