Django Crispy Forms - Checkbox not being displayed - django

I'm trying to add a boolean field on my form and render it with crispy forms tags. And everything is showed except for the checkbox.
My project works with django 2.1, python 3.6 and Bootstrap-4. My version of Django Crispy Forms is: 1.7.2
The field is investment.
Model field not working:
investment = models.BooleanField(default=False, help_text='Want to accept Investments?')
My form:
class CreateProjectForm(forms.ModelForm):
class Meta:
model = Project
fields = ('name', 'short_description', 'category', 'investment')
widgets = {
'name': forms.TextInput(attrs={'placeholder': 'enter the project name here...'}),
'short_description': forms.Textarea(attrs={'rows': '2', 'maxlength': '135', 'class': 'textarea-limited',
'placeholder': 'enter a short description of your project limited to 135 characters'}),
}
def __init__(self, *args, **kwargs):
# first call parent's constructor
super(CreateProjectForm, self).__init__(*args, **kwargs)
# there's a `fields` property now
self.fields['investment'].required = True
self.fields['investment'].widget = forms.CheckboxInput()
self.fields['name'].widget = forms.TextInput(
attrs={'placeholder': 'enter the project name here...'})
self.fields['short_description'].widget = forms.Textarea(
attrs={'rows': '2',
'maxlength': '135',
'class': 'textarea-limited',
'placeholder': 'enter a short description of your project limited to 135 characters'})
# evade all labels and help text to appear when using "as_crispy_tag"
self.helper = FormHelper(self)
self.helper.form_show_labels = False
self.helper._help_text_inline = True
My View:
class ProjectCreateView(SuccessMessageMixin, generic.CreateView):
template_name = 'webplatform/project_create_form.html'
model = Project
form_class = CreateProjectForm
success_message = 'Project created! Now try to add all the details and then publish it!'
def get_success_url(self):
return reverse_lazy('project-edit-general', args=(self.object.id,))
# Set field as current user
def form_valid(self, form):
form.instance.user = self.request.user
form.instance.start_date = timezone.now()
form.instance.history_change_reason = 'Project Created'
return super(ProjectCreateView, self).form_valid(form)
And for the template I tried two methods, but none of them worked:
My template 01:
This is the method I want to use at the end. Displaying each fiend individually so I can make the layout directly on the template.
...
{% load crispy_forms_tags %}
...
<form id="my_form" method="post" enctype="multipart/form-data" novalidate>
{% csrf_token %}
<div class="row">
<div class="col-md-5 col-sm-5">
<h6>Name
<span class="icon-danger">*</span>
</h6>
{{ form.name|as_crispy_field }}
<h6>Categories
<span class="icon-danger">*</span>
</h6>
{{ form.category|as_crispy_field }}
</div>
<div class="col-md-7 col-sm-7">
<h6>Short Description
<span class="icon-danger">*</span>
</h6>
{{ form.short_description|as_crispy_field }}
<h5>
<small>
<span id="textarea-limited-message" class="pull-right">135 characters left</span>
</small>
</h5>
<h6>Investment
<span class="icon-danger">*</span>
</h6>
{{ form.investment|as_crispy_field }}
</div>
</div>
<div class="row buttons-row">
<div class="col-md-4 col-sm-4">
<button type="submit" class="btn btn-outline-primary btn-block btn-round">Create</button>
</div>
</div>
</form>
My template 02:
This one is using directly the {{ form|crispy }} to show all the elements automatically.
...
{% load crispy_forms_tags %}
...
<form id="my_form" method="post" enctype="multipart/form-data" novalidate>
{% csrf_token %}
{{ form|crispy }}
<div class="row buttons-row">
<div class="col-md-4 col-sm-4">
<button type="submit" class="btn btn-outline-primary btn-block btn-round">Create</button>
</div>
</div>
</form>
I checked the rendered HTML and I found where the problem seems to be, but I don't know how to solve it:
Crispy forms create an element adding the corresponding divs and other necessary elements, like for example on the name input:
<div id="div_id_name" class="form-group">
<label for="id_name" class="col-form-label requiredField">Name
<span class="asteriskField">*</span>
</label>
<div class="">
<input type="text" name="name" placeholder="enter the project name here..." class="textinput textInput form-control" required="" id="id_name">
<small id="hint_id_name" class="form-text text-muted">Add a title to your project.</small>
</div>
</div>
But for the checkbox creates the following structure:
<div class="form-group">
<div id="div_id_investment" class="form-check">
<label for="id_investment" class="form-check-label requiredField">
<input type="checkbox" name="investment" class="checkboxinput form-check-input" required="" id="id_investment">
Investment
<span class="asteriskField">*</span>
</label>
<small id="hint_id_investment" class="form-text text-muted">Want to accept Investments?</small>
</div>
</div>
As you can see, the div with the id is inside another div. Well, so if I delete the class of this extra div (form-group) and change the class of the div with id: form-check for form-group, the checkbox appears and is fully functional.
So my idea is try to change the template that crispy forms created for the checkbox, But I don't know how to do that. Also, if there is another better option, I'm open to them.

Try to change self.fields['investment'].required = False, 'cause it seems that because the field is not specified as optional Django ignores it.

Their is an open issue in django-crispy-forms github see : https://github.com/django-crispy-forms/django-crispy-forms/issues/787
According to this the issue is the way bootsrap handle this form element in the last version.
Here is a js hack to make it work :
$(document).ready(function() {
$( ":checkbox" ).each(function( index ) {
$(this).prependTo($("#div_" + $(this).attr('id')))
});
});

I could make a half hack for this by the element I needed by using the following JS:
$(window).on('load',function(){
$('#div_id_investment').addClass('form-group').removeClass('form-check');
});
But this just let the user see and interact with the checkbox, it does not solve that if the field is required and you don't click it, the error message it doesn't appear.
If anyone have an improved JS to make this to all checkboxes and solve the problem with the error message, I'll appreciate it.
note: I'm not marking this as a valid answer for the question because the bug still there and this 'hack' doesn't solve all the problems.

Related

checkbox django return all false

My checkbox always return False value
my model
ho_so_giu=models.BooleanField(default=False)
my form
report_ho_so_giu = forms.BooleanField(widget=forms.CheckboxInput(attrs={'class':'form-check-input'}),required=False)
in my html template I add more code in my html template
<div class="col-md-8">
<form method="POST" id = "form_save_report" name ="form_save_report" value="form_save_report">
<div style="color:red;background-color:white">THÔNG TIN XỬ LÝ</div>
<div class="row">
{% csrf_token %}
<div class="form-group row">
<div class="col-md-4" id="labelsohd" name="labelsohd" style="display:none">Ngày hẹn</div>
<div class="col-md-6" id="so_hd" name="so_hd" type="number" style="display:none">{{form.so_hd}}</div>
<div class="form-group row">
<div name={{form.report_ho_so_giu.name}} id ="form.report_ho_so_giu.auto_id"> {{form.report_ho_so_giu}} </div>
<label class="form-check-label" for="{{form.report_ho_so_giu.auto_id}}">{{form.report_ho_so_giu.label}}</label>
</div>
</div>
</div>
<!-- Dòng 3 -->
<div class="row">
<div class="col-md-4">
<div class="form-group row">
<div class="card-footer" >
<button type="submit" class="fa fa-save" style="font-size:20px; color:blue" class="btn btn-primary btn-block" id="submit" name="submit">Lưu báo cáo</button>
</div>
</div>
</div>
</div>
</form>
</div>
</div>
in my view, I add more code in my view
if request.method=='POST':
#print("request POST")
form=AddReportForm(request.POST)
if form.is_valid():
#print("if form")
so_hd=form.cleaned_data["report_so_hd"]
print(request.POST.get("report_ho_so_giu")) # return None
print(request.POST.get("id_report_ho_so_giu")) # return None
print(request.GET.get("report_ho_so_giu")) # return None
print(request.GET.get("id_ho_so_giu")) # return None
print(form.cleaned_data["id_report_ho_so_giu"]) # return False with checked or unchecked
print(form.cleaned_data["report_ho_so_giu"]) # return KeyError: 'report_ho_so_giu'
daily_report=report(so_hd=so_hd, ho_so_giu=ho_so_giu)
daily_report.save()
report_response_data={}
report_response_data["so_hd"]=so_hd
return JsonResponse({"so_hd":report_response_data["so_hd"],
})
I am using ajax to return my value from form but it still return only on whenever I check or uncheck box
ho_so_giu =$('#ho_so_giu_text').val() //return on
I try to print the result it just return "None" or "False" in my view, If I use console log in html file it retunr only "on". I read some question in stackoverflow and use some answer but it just return None or False
Is there any solution to replace checkbox in this case if I cannot find out the issue?
I think the problem might be with your form, try this:
class MyForm(forms.ModelForm):
class Meta:
model = MyModel
fields = ['ho_so_giu']
widgets = {
'ho_so_giu': forms.CheckboxInput(
'class': 'form-check-input',
'id': 'ho_so_giu_text',
'name': 'ho_so_giu_text'
)
}
Notes:
I dont know why you have given a name to the form. I have never seen that before. Could that be creating issues with the post request to the backend?
Have you tried building the form on the frontend by youserlf? Using just html? It would be a good way to debug the issue.
Consider writing a hybrid form, mostly in html, with dynamic elements. This will allow you to monitor exactly what's happening. Check out the example here below:
<input name="{{ wc_user_form.last_name.name }}" type="text" class="form-control" id="{{ wc_user_form.last_name.auto_id }}" placeholder="Votre Nom" oninput="customValidation(this);" required>
<label for="{{ wc_user_form.last_name.auto_id }}">{{ wc_user_form.last_name.label }}</label>
Pretty cool eh? Pay particular attention to the {{ form.field.auto_id }} template tag. That - as you can imagine - generates the id of the field that the backend expects in order to perform a form.save()

Django custom widget: how to access field error?

I try to have more control over form rendrering and template.
Initially, I used django-crispy but event if Layout give huge range of customization I thought it became confused...
So I truned out to widget customization following django documentation and this site https://www.webforefront.com/.
I first face issue with renderding field label. I fix that by passing label argument in attrs of CustomWidget but not sure it is a good practice.
But my main issue is rendering erros...
How can I manage this?
template for input CustumoInput.html
<div class="row mb-0">
<div class="input-group input-group-sm col-6 rounded">
<label>{{ widget.label }}</label>
</div>
<div class="input-group input-group-sm mb-1 col-2">
<input
type="{{ widget.type }}"
name="{{ widget.name }}"
id="id_{{ widget.name }}"
class = "textinput textInput form-control"
{% if widget.value != None %}
value="{{ widget.value }}"
{% endif %}
{% include "django/forms/widgets/attrs.html" %}
/>
{% for error in widget.errors %}
<div class="invalid-feedback">{{ error }}</div>
{% endfor %}
</div>
</div>
widgets.py
from django import forms
class custom_input(forms.widgets.Input):
template_name = 'ecrf/CustomInput.html'
input_type = 'text'
def __init__(self, attrs={}):
super(custom_input, self).__init__(attrs)
def get_context(self, name, value, attrs):
context = super(custom_input, self).get_context(name, value, attrs)
context['widget']['label'] = self.attrs['label']
context['widget']['attrs']['placeholder'] = self.attrs['placeholder']
context['widget']['attrs']['autocomplete'] = self.attrs['autocomplete']
context['widget']['attrs']['data-mask'] = self.attrs['data-mask']
return context
forms.py
self.fields['inc_tai'] = forms.IntegerField(label = 'Taille (mesurée ou déclarée, en cm)',widget=custom_input(attrs={'label':'Taille (mesurée ou déclarée, en cm)','autocomplete': 'off','placeholder': '000','data-mask':'000'}),required=False,disabled=True)
template.htlm (usage)
{{ form.inc_tai }}

Change the appearance of labels and input fields (from a Model Form) used in a DJango template

I am using a script below (that resides in a DJango template) to display input fields that are associated with a particular Model Form. The purpose of this template is to add and modify data for a Model.
The name of the template is : model_name_form.html (ex: customer_form.html for the Model representation of the table Customer)
In order to add/modify data, the Model Form is being used.
The problem is that when using the script below, a type of default presentation is being used for the fields. In my case, the fields need to be more customized.
The customized labels and input fields make the form look much nicer.
I start out with this (which is in the template that displays the form):
{% for field in form %}
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<span class="text-danger small"> {{ field.errors }} </span>
</div>
<label class="control-label col-sm-2"> {{ field.label_tag }} </label>
<div class="col-sm-10"> {{ field }} </div>
</div>
{% endfor %}
Which turns into this:
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<span class="text-danger small">
</span>
</div>
<label class="control-label col-sm-2">
<label for="id_companyname">Company Name:</label>
</label>
<div class="col-sm-10">
<input name="companyname" id="id_companyname" type="text" maxlength="30">
</div>
</div>
But what is needed is a label/input presentation similar to the following:
<div class="form-group">
<label>Default Input Group</label>
<div class="input-group">
<input class="form-control" placeholder="Username" aria-describedby="sizing-addon1" type="text">
<span class="input-group-addon" id="sizing-addon1">#</span>
</div>
</div>
How can one control the attributes associated with :
{{ field.errors }}
{{ field.label_tag }}
{{ field }} <<< especially this one
For example, one can see above, {{ field }} produces
<input name="companyname" id="id_companyname" type="text" maxlength="30">
But what is needed is:
<input class="form-control" placeholder="Username" aria-describedby="sizing-addon1" type="text">
How can one control the way labels and input fields are presented?
TIA
You can use widget attributes to have more control over the HTML output, for example:
class MyModelForm(ModelForm):
class Meta:
model = MyModel
fields = '__all__'
widgets = {
'my_field': TextInput(attrs = {
'placeholder': 'Ingrese el contacto',
'class': 'form-control',
'data-validation': 'custom',
}),
'my_other_field': Select(attrs = {
'class': 'form-control selectpicker',
'data-live-search': 'true',
'title': 'Elegir un tipo',
'data-validation': 'required',
'data-validation-error-msg': 'Debe elegir una de las opciones.',
}),
In your case you could do:
widgets = {
'my_field': TextInput(attrs = {
'placeholder': 'Username',
'class': 'form-control',
'aria-describedby': 'sizing-addon1',
}),
For more control, you can use custom template filters, or even try the library django crispy forms, but it adds a layer of complexity that maybe is not needed.
I had a similar problem where the default form label was missing spaces. I was able to fix the issue by overwriting the default label in forms.py to be blank:
quantity = forms.TypedChoiceField(label='', choices=PRODUCT_QUANTITY_CHOICES)
and then creating a new label in the HTML. It didn't work to just overwrite the label in forms.py.

Django: How save partial filled form state

I don't know exactly how to ask this question.
The thing is that I have a main view to create new entries in a model. This model has some 1-many relations, so I added a + button to add new entries of this fields (secondary model) in case they did not exist. When I submit this new data I redirect to the previous page (main view), and if you already filled some fields in the main view, that information is lost.
Can someone suggest me what the best way to deal with this would be?
Thanks in advance!
UPDATE:
'main model view'
class OrganismCreate(LoginRequiredMixin,CreateView):
"""Template: //catalog/templates/catalog/organism_form.html"""
model = Organism
fields = '__all__'
'main model template' (part)
<form action="" method="post">
{% csrf_token %}
<div class="panel-group">
<div class="panel panel-default">
<div class="panel-heading">Add a new entry: </div>
<div class="panel-body">
<hr>
<div class="row">
<div class="form-group col-sm-4 col-md-3">
<div class="form-group col-sm-4 col-md-3">
<label for="id_inst_own">Owner:</label>
{% render_field form.inst_own class="form-control" %}
<i class="fa fa-plus-circle "></i> Add new
</div>
<div class="panel panel-default">
<div class="panel-body">
...........................................
<button type="submit" class="btn btn-primary"> <span class="glyphicon glyphicon-filter"></span> submit </button>
</div>
</div>
</div>
Then the related model view:
def test_f(request):
if request.method == "GET":
Form = InstitutionForm()
render(request, 'catalog/institution_form.html')
if request.method == "POST":
Form = InstitutionForm(request.POST)
if Form.is_valid():
Form.save()
next = request.POST.get('next', '/')
return redirect(next)
pre=request.META.get('HTTP_REFERER')
return render(request, 'catalog/institution_form.html',{"form" : Form, "pre": pre})
And the related model template
{% block content %}
<form action="" method="post">
{% csrf_token %}
<table>
{{ form.as_table }}
</table>
<input type="hidden" name="next" value="{{ pre }}">
<input type="submit" class="btn btn-success" value="Submit" />
</form>
{% endblock %}
There are a number of ways to solve this problem. Here's some suggestions. Not an exclusive list:
Save the incomplete model data in the database as as a 'draft' version of the final data. This could be a totally different model or else using the same model (assuming the related fields are nullable) and giving it a 'draft' flag or similar.
Use an inline formset to create the related objects in the same view. Django Extra Views has some useful tools for this (https://github.com/AndrewIngram/django-extra-views).
Using JavaScript, save the unfinished form data to local storage and then recover it when the original form is loaded again.
I have implemented a draft system to do this along the lines of 1. in #ChidG's answer.
In models I have something like
class AbstractThing(models.Model):
field = models.CharField()
class Meta:
abstract = True
class CompleteThing(AbstractThing):
class Meta:
managed = True
db_table = 'complete_thing'
class IncompleteThing(AbstractThing):
fields_to_not_blank = [AbstractThing._meta.get_field(x) for x in []] #if you don't want to change some fields
for f in AbstractThing._meta.fields:
if f not in fields_to_not_blank:
f.blank = True
f.null = True
class Meta:
managed = True
db_table = 'incomplete_thing'
Then you can use model forms and handle the cases in your views.

Django form rendering isn't passing along original HTTP request object

New to Django/Python/stackexchange...
I have a Jquery Datatable and I'm having difficulty passing along the value of a cell (called email_id) of the table as an HTTP parm to be used when handling a new form page.
The view is initially receiving the request correctly per the debug output:
WSGIRequest: GET '/main_app/makeTask/?csrfmiddlewaretoken=gDeTwaNfGNLO7cdMk1
B9gsdpcGYpKAyL&email_id=14d2a002852e1738'
It successfully extracts email_id with request.GET.get() on the first call (then proceeds to construct the form and render it) but it gets dropped when the form is being rendered. I.e., email_msg_id is extracted correctly on the GET but not on the subsequent POST.
Here is the extraction code along with the render statement. The problem is the 'email_id' is not propagating when I render the form so it cannot be used by view when it processes the form input.
email_msg_id = request.GET.get('email_id', "GET - no email_id")
...
return render(request, 'main_app/makeTask.html', {'form': form, 'email_id': email_msg_id})
Debug message:
<WSGIRequest: GET '/main_app/makeTask.html'>
Here are relevant sections of urls.py:
url(r'^makeTask', views.make_task, name='makeTask'),
My Jquery call (which appears to be working/passing along the email_ID correctly:
$('#make_task').click( function () {
alert( table.cell('.selected',4).data()+' Converted to Task');
// alert(table.cell('.selected',0).data()+' Make Task selected:');
$.ajax({
type: "GET",
url: "/main_app/makeTask/",
data: {
'csrfmiddlewaretoken': '{{ csrf_token }}',
'email_id' : table.cell('.selected',4).data(),
},
success: makeTaskSuccess,
dataType: 'html'
});
function makeTaskSuccess(data)
{
alert('Convert email to task ');
}
} );
Here is the view (with unrelated stuff removed):
def make_task(request):
if request.method == "GET":
email_msg_id = request.GET.get('email_id', "GET - no email_id") # from the post dictionary of the request object passed in
else:
email_msg_id = request.POST.get('email_id', "POST - no email_id")
print "EMAIL_MSG_ID: ", email_msg_id, "METHOD: ", request.method
if request.method == 'POST':
form = MakeTaskForm(request.POST or None) # Create a form instance and populate it with the data
if form.is_valid():
# process and save to db
#...
return HttpResponseRedirect(reverse('main_app.views.index'))
else:
print form.errors
return
else:
form = MakeTaskForm()
return render(request, 'main_app/makeTask.html', {'form': form, 'email_id': email_msg_id})
#return render(request, 'main_app/makeTask.html', context_dict)
UPDATE: added relevant template code:
<form id="make_task_form" method="post" action="/main_app/makeTask/", method="Post">
{% csrf_token %}
<div class="row">
<div class="col-lg-8 col-xs-12">
<div class="form-group">
<label>Due Date: &nbsp</label>
<label class="radio-inline">
<input type="radio" name="due_selection" id="optionsRadiosInline1" value="TODAY" {{ form.due_selection }} Today
</label>
</div>
</div>
<div class="col-lg-4 col-xs-12">
<p>Specific Date: <input type="text" id="datepicker" {{ form.due_date }}</p>
</div>
</div>
<div class="row">
<div class="col-lg-4 col-xs-12">
<button type="submit" class="btn btn-info btn-med" id="btn-make-task">
Make Task
</button>
<i class="fa fa-trash-o fa-2x pull-right"></i>
</div>
</div>
</form>
From the code you posted I assume you're using GET for AJAX and POST in your form.
The form code you posted is wrong, access your form fields in your template like this (also get rid of the duplicated method attribute in <form>):
<form id="make_task_form" method="post" action="/main_app/makeTask/">
{% csrf_token %}
<div class="row">
<div class="col-lg-8 col-xs-12">
{{ form.due_selection }}
</div>
<div class="col-lg-4 col-xs-12">
{{ form.due_date }}
</div>
</div>
<div class="row">
<div class="col-lg-4 col-xs-12">
<button type="submit" class="btn btn-info btn-med" id="btn-make-task">
Make Task
</button>
<i class="fa fa-trash-o fa-2x pull-right"></i>
</div>
</div>
</form>
To use <input type="radio"> for the field due_selection you should specify the RadioSelect widget in your ModelForm class:
CHOICES = (('1', 'First',), ('2', 'Second',))
due_selection = forms.ChoiceField(widget=forms.RadioSelect, choices=CHOICES)