I use Django Crispy Forms and I'm trying to improve the UX in my forms by adding to it Bootstrap popovers or tooltips (dynamic bubbles that are toggled on hover that shows extra information about the form field).
Basically, I would have to add this specific piece of code next to a specific form label (title of the input field in the form)
<a tabindex="0" role="button" data-toggle="popover"
data-html="true" data-trigger="hover" data-placement="auto"
title="Extra information"
data-content="Here is the extra information I want to show when user hovers over the information glyphicon">
<span class="glyphicon glyphicon-info-sign"></span>
</a>
So far this is what I tried and got it to show AFTER both the label and the input field.
I would like to show in between both.
self.helper.layout = Layout(
'title',
'description',
Field('category', css_class='form-control select select-primary select-block mbl'),
Html('<a tabindex="0" role="button" data-toggle="popover" data-html="true" data-trigger="hover" data-placement="auto" title="Extra information" data-content="Here is the extra information I want to show when user hovers over the information glyphicon"><span class="glyphicon glyphicon-info-sign"></span></a>'))
What would be the best way to go to do that?
I cannot find an easy way to add some pure HTML, next to specific labels..
Thanks for your help.
you could override just the template of the title field, by defining your template, and put your customization there:
self.helper.layout = Layout(
Field('title', template="./path/to/template/popover.html"),
....
the template could be something like:
{% load crispy_forms_field %}
<{% 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 %}
<a tabindex="0" role="button" data-toggle="popover"
data-html="true" data-trigger="hover" data-placement="auto"
title="Extra information"
data-content="Here is the extra information I want to show when user hovers over the information glyphicon">
<span class="glyphicon glyphicon-info-sign"></span>
</a>
<div class="controls {{ field_class }}">
{% crispy_field field %}
{% include 'bootstrap3/layout/help_text_and_errors.html' %}
</div>
</{% if tag %}{{ tag }}{% else %}div{% endif %}>
I've just copied things from the crispy_form source and added your html, but depending on your needs that could be simplified, it's up to you.
Related
What I want
I'm building an approval workflow using django-viewflow and django-material.
The individual tasks are rendered as a main form with context on a very narrow column on the right-hand side.
I want to change the layout so that the task context (the detail views of all involved model instances) is better readable to the user, and also customise which fields are shown (eg. exclude a user's password hash).
Where I'm stuck
Is there a way to override which data is available as process_data short of overriding viewflow's get_model_display_data and include_process_data? E.g. I'd like to have the related instance's __str__() as title.
Does viewflow have any canonical way to provide individual detail card templates? My alternative would be to completely re-work the contents of the process_data sidebar using context['process'] as the central instance, but that would tie the templates to the data model.
Are django-material formsets with read-only fields and a custom layout the answer I'm after? (I will try this out as soon as I have a viewflow pro license.)
I'd be grateful on any pointers here.
What I've tried
I'm overriding/extending the viewflow templates. As per templatetag include_process_data, the template process_data.html supplies the column of model instance detail cards, fed by data from
get_model_display_data.
It's e.g. easy to override process_data.html to change the cards into a MaterializeCSS collapsible list:
{% load i18n viewflow material_frontend viewflow_frontend %}
<!--
This is template "APP_NAME/PROCESS_NAME/process_data.html" overriding "viewflow/flow/process_data.html".
We override instead of extending as "viewflow/flow/process_data.html" has no content block.super
Changes:
* Collapsible list of process context instead of cards.
-->
<script type="text/javascript">
$(document).ready(function () { $('.collapsible').collapsible(); });
</script>
<ul class="collapsible">
{% for root, fields, root_url in process_data %}
<li>
<div class="collapsible-header">
<span class="card-title">{{ root }} #{{ process.pk }}</span>
{% if root_url and request.user.is_staff %}
<a href="{{ root_url }}" class="card-edit" target="_blank" data-turbolinks="false" style="float:right">
{% trans 'edit' %}
</a>
{% endif %}
</div>
<div class="collapsible-body process_data_content">
<dl class="dl-horizontal">
{% for name, value in fields %}
<dt>{{ name }}:</dt>
<dd>
{% if value is True %}{% trans 'Yes' %}{% else %}
{% if value is False %}{% trans 'No' %}{% else %}
{% if value and value.url %}{{ value.name }}{% else %}
{{ value }}{% endif %}{% endif %}{% endif %}
</dd>
{% endfor %}
</dl>
</div>
{% if not hide_active_tasks and forloop.counter == 1 and process.active_tasks %}
<li>
<div class="collapsible-header">
<span class="card-title">{% trans 'Active tasks' %}</span>
</div>
<div class="collapsible-body process_data_content">
<table>
<thead>
<tr>
<th>{% trans 'Task' %}</th>
<th>{% trans 'Owner' %}</th>
</tr>
</thead>
<tbody>
{% for task in process.active_tasks %}
{% if task.flow_task.task_type == 'HUMAN' or task.flow_task.task_type == 'JOB' %}
<tr>
<td>
{{ task.flow_task}}/#{{ task.pk}}
</td>
<td>{{ task.owner|default:"" }}</td>
</tr>
{% endif %}
{% endfor %}
</tbody>
</table>
</div>
</li>
{% endif %}
</li>
{% endfor %}
</ul>
Overriding the template viewflow/flow/task.html with my own APP_NAME/PROCESS_NAME/TASK_NAME.html template at the cost of including model-specific information can show e.g. customised cards of related instances over the main form. I would refactor the individual cards into their own templates.
{% extends "viewflow/flow/task.html" %}
{% block left-panel__top %}
<!--
This is template "APP_NAME/PROCESS_NAME/TASK_NAME.html" extending "viewflow/flow/task.html".
-->
<!-- Help specific to this step goes here. -->
{% with activation.process as p %}
<div class="row">
<h3>Nested formsets</h3>
<p>These cards are placeholders for formsets nested within the main application form.</p>
<p>Nested formsets and the main application form can be updated at the same time and have exactly one submission button.</p>
</div>
<div class="row">
<!-- Organisation Questions: Answers -->
<!-- TODO refactor to template include -->
{% for x in p.organisationanswer_set.all %}
<div class="col s12 m6 xl4">
<div class="card blue-grey darken-1">
<div class="card-content white-text">
<span class="card-title">
{{ x.question.question }}
</span>
<p><strong>Your answer:</strong> {{ oa.answer }}</p>
</div>
<div class="card-action">
Provide an answer
</div>
</div>
</div>
{% endfor %}
<!-- Dataset Questions: Answers -->
{% for x in p.datasetanswer_set.all %}
<div class="col s12 m6 xl4">
<div class="card blue-grey darken-1">
<div class="card-content white-text">
<span class="card-title">
{{ x.question.question }}
</span>
<p><strong>Your answer:</strong> {{ x.answer }}</p>
</div>
<div class="card-action">
Provide an answer
</div>
</div>
</div>
{% endfor %}
<!-- Approvals: Receipts - TODO show this in task after custodian approval -->
{% for x in p.approvalreceipt_set.all %}
<div class="col s12 m6 xl4">
<div class="card blue-grey darken-1">
<div class="card-content white-text">
<span class="card-title">
{{ x.approval }}
</span>
<p><strong>Your approval receipt:</strong> {{ x.receipt }}</p>
</div>
<div class="card-action">
Provide an answer
</div>
</div>
</div>
{% endfor %}
</div>
{% endwith %}
<div class="row">
<h3>Main application form</h3>
<p>These main application form would include the forms above as nested formsets.</p>
<p>This note will disappear once this has been implemented.</p>
</div>
{% endblock %}
For the basic cases create a template named as [app_label]/[flow_label]/process_data.html For example check the shipment demo
Viewflow is the thin workflow layer on top of the standard Django model-view-template pattern. Any customization practices for Django are valid for Viewflow
For the complex cases it's better to not to tune some universal implementation, but create your own set of templates for Viewflow, ex cookbook/custom_ui
I have a field which is rendered using as_crispy_field templatetag
{{ form.field|as_crispy_field }}
I want to change the help text position to be beside the label with red color
This is the current output:
I want it to be something similar to this:
I've solved the issue by overriding crispy_forms templates.
By looking at the structure of the crispy_forms template crispy_forms/templates, I've seen that they read the formatting from a folder crispy_forms/templates/{{ TEMPLATE_PACK }}
e.g. crispy_forms/templates/bootstrap4
I've decided to create my own customized folder to change the layout for that field and placed it under the project's template folder.
Adding a file to that folder, named 'field.html'; because of that how crispy will read the formatting, and changed whatever I want to customize.
The customized file templates/bootstrap4/field.html
The changed lines (lines 13-17 on templates/bootstrap4/field.html)
{% if field.label and not field|is_checkbox and form_show_labels %}
<label for="{{ field.id_for_label }}" class="{% if 'form-horizontal' in form_class %}col-form-label {% endif %}{{ label_class }}{% if field.field.required %} requiredField{% endif %}">
{{ field.label|safe }}{% if field.field.required %}<span class="asteriskField">*</span>{% endif %}
</label>
{% comment %} ---------- Customization starts here ---------- {% endcomment %}
<strong style="color: red; font-size:14px">
<i>{{field.help_text|safe}}</i>
</strong>
{% comment %} ---------- Customization ends here ---------- {% endcomment %}
{% endif %}
Field Error handling (lines 41-44 on templates/bootstrap4/field.html)
<div class="{{ field_class }}">
{% crispy_field field %}
{% comment %} ---------- Error handling starts here ---------- {% endcomment %}
{% if error_text_inline %}
{% include 'bootstrap4/layout/field_errors.html' %}
{% else %}
{% include 'bootstrap4/layout/field_errors_block.html' %}
{% endif %}
{% comment %} ---------- Error handling ends here ---------- {% endcomment %}
</div>
And modified the field in the template to read from the new folder
{{ form.myField|as_crispy_field:"custom_crispy_folder" }}
I have problems display the default errors within a form I'm creating via crispy_forms using a custom template. Namely, the text "This field is required." is not present for two fields.
Please see, here
Here's the form's init code:
...
start_time = forms.TimeField(label='Start Time', required=True, input_formats=[TIME_FORMAT])
end_time = forms.TimeField(label='End Time', required=True, input_formats=[TIME_FORMAT])
...
Field('end_date', placeholder='dd/mm/yyyy'),
Field('start_time', placeholder='hh:mm (pm/am)', template="appointments/datetimefield.html"),
Field('end_time', placeholder='hh:mm (pm/am)', template="appointments/datetimefield.html"),
and the clean, save methods here:
Finally, the custom template:
{% load crispy_forms_field %}
<div id="div_{{ field.auto_id }}" class="form-group{% if field.errors %} has-error{% endif %}">
{% if field.label 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 %}
<div class="controls col-xs-8 col-md-9 col-lg-9">
{% crispy_field field %}
</div>
</div>
You need to show the errors in the template. Crispy forms has a template you can include for the errors: {% include 'bootstrap3/layout/help_text_and_errors.html' %} (if you're using bootstrap 3).
So your custom template might be like this:
<div id="div_{{ field.auto_id }}" class="form-group{% if field.errors %} has-error{% endif %}">
{% if field.label 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 %}
<div class="controls col-xs-8 col-md-9 col-lg-9">
{% crispy_field field %}
{% include 'bootstrap3/layout/help_text_and_errors.html' %}
</div>
</div>
Maybe you should check the version of crispy_forms and update it.
I also had this problem with crispy_forms == 1.3.2 .But when I updated crispy_forms into 1.4.0, the problem disappeared.
Actually, I found that just bootstrap and uni_form folders were in the crispy_forms/templates and bootstrap3 folder was not when crispy_forms == 1.3.2 installed.
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.
Hey I have been plugging away trying to get haystack working with whoosh to implement a search backend for a django cms project i have been working on. After figuring out some really weird permissions errors i can almost taste sucess.
I now have haystack creating the indexes correctly and it will even display results for a custom app i have created an index for however i cannot get it to display any results for the cms page stuff. There are results being returned as im not getting the "No Results" message but nothing is being printed out in the results.html template.
bearing in mind that im letting django-cms-search handle the indexing for the cms app i didnt think i would need to add anything extra to get the results to display.
So to outline what i have so far
Haystack installed with a Whoosh backend
my setting.py setting are
HAYSTACK_SITECONF = 'lactoseintolerant.lactose_search.search_sites'
HAYSTACK_SEARCH_ENGINE = 'whoosh'
HAYSTACK_WHOOSH_PATH = '/home/mike/sites/lactosetoloerant/lactoseintolerant/whoosh'
HAYSTACK_SEARCH_RESULTS_PER_PAGE = 50
my search_sites.ph file looks like
import haystack
from cms.models import monkeypatch_reverse
from cms.plugin_pool import plugin_pool
monkeypatch_reverse()
haystack.autodiscover()
i have a custom index for my Topics Model that looks like so (This is working and resturning Reuslts)
from topics.models import Topic
from haystack.indexes import *
from haystack import site
class TopicIndex(SearchIndex):
text = CharField(document=True, use_template=True)
def index_queryset(self):
"""
This is used when the entire index for model is updated, and should only include
public entries
"""
return Topic.objects.filter(active=True)
site.register(Topic, TopicIndex)
My results.html template look like
{% extends "base.html" %}
{% load cache cms_tags menu_tags %}
{% block base_content %}
<div id="panel-left">
{% block nav %}
<ul id="nav-left">
{% show_menu 1 100 100 100 %}
</ul> <!-- #nav-left -->
{% endblock %}
{% block panel_left %}
{% endblock %}
</div>
{% block panel_right %}
<div id="panel-main">
{% ifequal q '' %}
<h1 id="page-head-2">Search Results Page</h1>
<p>Please provide search criteria or keywords</p>
<br />
<form action=".">
<p>
<input type="text" name="q" value="{{ q }}">
<input type="submit" name="search" value="Search">
</p>
</form>
{% else %}
<h1 id="page-head-2">Search Results - page {{ request.GET.page|default:1 }} of {{ paginator.num_pages }}</h1>
<h1 id="page-head-2">Found {{ paginator.count }} for <span id="searchString">'{{ q }}'</span></h1>
<form action=".">
<p>
<input type="text" name="q" value="{{ q }}">
<input type="submit" name="search" value="Search">
</p>
</form>
<ul id="searchResults">
{% for item in current_page.object_list %}
<li class="searchResult">
<h2 class="searchTitle">{{ forloop.counter }}. {{ item.get_title }}</h2>
<a class="searchLink" href="{{ item.get_absolute_url }}">{{ item.get_absolute_url }}</a>
</li> <!-- .searchResult -->
{% endfor %}
</ul>
{% if paginator.num_pages > 1 %}
<div id="pagination">
{% for page_range_item in paginator.page_range %}
{% if not request.GET.page and page_range_item == 1 or request.GET.page == page_range_item|safe %}
<span class="pag-link current" href="/topics/?page={{ page_range_item }}">{{ page_range_item }}</span>
{% else %}
<a class="pag-link" href="/search/?q={{ q }}&page={{ page_range_item }}">{{ page_range_item }}</a>
{% endif %}
{% endfor %}
</div>
{% endif %}
{% endifequal %}
</div>
{% endblock %}
{% endblock %}
Im clearly missing something obvious here any help would be grealt appreciated!!
Cheers
i think you got it almost, try to change {{ item.get_absolute_url }} to {{ item.object.get_absolute_url }} inn your results.html file.