How to hide Django form fields using JavaScript, Jquery etc - javascript

I would like to dynamically
hide form fields. The user should be able to select the component type, which could be a VALVE in which case the user should specify the Kv value and the DI and length fields should be hidden. Or the user could select the PIPE component type in which case the user should specify the inner diameter (DI) and length of the pipe and the k_v field should be hidden.
The model is defined as follows:
class Component(models.Model):
COMPONENT_TYPE_CHOICES = (
(1, 'k_v'),
(2, 'pipe')
)
circuit = models.ForeignKey('circuit.Circuit', related_name='components', on_delete=models.CASCADE)
component_type = models.IntegerField(default=1, choices = COMPONENT_TYPE_CHOICES)
component_name = models.CharField(max_length=200)
branch_number_collectors = models.IntegerField(default=4)
# Hide if component_type==2
k_v = models.FloatField(default=1)
# Hide if component_type==1
DI = models.FloatField(default=0.025)
length = models.FloatField(default=1)
# Calculated properties
branch_volumetric_flow_rate = models.FloatField(default=0)
branch_mass_flow_rate = models.FloatField(default=0)
velocity = models.FloatField(default=0)
reynolds = models.FloatField(default=0)
friction_coefficient = models.FloatField(default=0)
pressure_loss = models.FloatField(default=0)
#classmethod
def create( cls,
circuit,
...,
The forms.py is as follows:
class ComponentForm(forms.ModelForm):
class Meta:
model = Component
fields = [
'component_type',
'component_name',
'branch_number_collectors',
'k_v',
'DI',
'length'
]
The simplified Django template is as follows:
{% block content %}
<form method='POST'> {% csrf_token %}
{{ form.as_p }}
<button type='submit'>Save</button>
</form>
{% endblock %}

first go to django shell and then do the following:
python manage.py shell
from yourapp.yourform import ComponentForm
f = ComponentForm()
print(f.as_p())
this will give you all the id and class names you can use in your javascript or CSS to manipulate.
lets say you want to hide length then you will do:
$(document).ready(function(){
$('#id_length').hide();
})

Ok I solved the problem. When the user selects the PIPE option form the component_type dropdownlist the k_v field is hidden and the DI and length fields are shown. When the user selects the k_v option from the component_type dropdownlist the k_v field is shown and the length and DI fields are hidden.
My Django template is now as follows:
{% extends 'base.html' %}
<script>
{% block jquery %}
// Call hideShow when page is loaded
$(document).ready(function(){
hideShow()
})
// call hideShow when the user clicks on the component_type dropdownlist
$('#id_component_type').click(function(){
hideShow()
});
// The jquery function below hides/shows the k_v, DI and length fields depending on the selected component_type
function hideShow(){
if(document.getElementById('id_component_type').options[document.getElementById('id_component_type').selectedIndex].value == "1")
{
$('#id_length').parents('p:first').hide();
$('#id_DI').parents('p:first').hide();
$('#id_k_v').parents('p:first').show();
}else
{
$('#id_length').parents('p:first').show();
$('#id_DI').parents('p:first').show();
$('#id_k_v').parents('p:first').hide();
}
}
{% endblock %}
</script>
{% block content %}
<form method='POST'> {% csrf_token %}
{{ form.as_p }}
<button type='submit'>Save</button>
</form>
{% endblock %}

Related

How to dynamically generate different fields based on previous selection of select field in Flask WTForm

I would like to implement a form that generates different text area fields depending on what value was selected in the dropdown select field before hand. The context is creating projects of different categories: for example, if a user selects "Education", the next fields generated live could be a select field for "quarter/semester?", and a text area field for "Describe your subject matter". I understand that accomplishing such a feature requires javascript/jquery but I have no idea where to start. I am also unsure the best way to relate this to my models and database.
In my HTML template, I have implemented if statements but these of course don't work.
My form class looks something like this:
class ProjectForm(FlaskForm):
"""Create a new project, on /index"""
categories = ['Education','Software development', ...]
langs = ['Python', 'Java', ...]
name = TextAreaField('Give your project a name', validators=[
DataRequired(), Length(min=1, max=60)])
category = SelectField('Category', choices=categories, default=1)
#Education case
academic_pace = SelectField('Quarter or semester?', choices=['quarter', 'semester'],
default=1)
subject = TextAreaField('Describe your subject matter', validators=[
DataRequired(), Length(min=10, max=100)])
#Software development case
software_language = SelectField('What programming language??', choices=langs, default=1)
submit = SubmitField('Submit')
The route like this:
from app.models import User, Project
from app.forms import LoginForm, ProjectForm ...
#app.route('/', methods=['GET', 'POST'])
#login_required
def index():
form = ProjectForm()
if form.validate_on_submit():
project = Project(name = form.name.data, category = form.category.data,
academic_pace = form.academic_pace.data,
subject = form.subject.data,
software_language = form.software_language.data,
creator=current_user)
db.session.add(project)
db.session.commit()
flash('Your project is now live!')
return redirect(url_for('index'))
return render_template('index.html', title='Home', form = form)
index.html:
...
{{ form.category.label }}<br>
{{ form.category(cols=32, rows=4) }}<br>
{% for error in form.category.errors %}
<span style="color: red;">[{{ error }}]</span>
{% endfor %}
{% if form.category == "Education" %}
{{ form.academic_pace.label }}<br>
{{ form.academic_pace(cols=32, rows=4) }}<br>
{% for error in form.academic_pace.errors %}
<span style="color: red;">[{{ error }}]</span>
{% endfor %}
{{ form.subject.label }}<br>
{{ form.subject(cols=32, rows=4) }}<br>
{% for error in form.subject.errors %}
<span style="color: red;">[{{ error }}]</span>
{% endfor %}
{% endif %}
...
I think I figured out how to do this using jquery. Essentially for each category's unique forms, I wrap them in a div. These divs are hidden at first, with style="display:none;". I use jquery to then identify what options are being selected from the SelectField, and show or hide the other form divs accordingly. Below is the javascript for one case.
$(document).ready(function() {
$("#category").change(function () {
var val = $(this).val();
if (val == "learning") {
$("#learning").show();
}
else {
$("#learning").hide();
}
});
});

Django/Ajax : How to filter a form field's queryset asynchronously?

In a given Django form view, I'd like to filter a field's queryset by an option a user has selected while viewing the form. And I'd like that to happen asynchronously. I've read that AJAX might be what I should use, but I know very little of JS, and I can't make much sense of the information I've read.
The way I see it, I'd like a user to be able to click on one or more checkboxes that would filter 'loot.item_name' by 'item.in_itemlist' or 'item.item_type' (automatically, using onChange or onUpdate or smth?).
Would someone kindly give me some pointers? Cheers,
Here are my models:
models.py
class Itemlist(models.Model):
name = models.CharField([...])
class Item(models.Model):
name = models.CharField([...])
item_type = models.CharField([...])
in_itemlist = models.ForeignKey(Itemlist, [...])
class Loot(models.Model):
item_name = models.ForeignKey(Item, [...])
quantity = [...]
my view,
views.py
def create_loot(request):
# LootForm is a simple ModelForm, with some crispy layout stuff.
form = LootForm(request.POST or None)
if request.method == 'POST':
if form.is_valid():
form.save()
[...]
return render(request, 'loot_form.html', context={'form': form}
and the template,
loot_form.html
{% extends "base_generic.html" %}
{% block leftsidebar %}
/* for each unique itemlist referenced in the items, */
/* make a checkbox that once clicked, filters 'loot.item_name' by the selected option.*/
{% endblock leftsidebar %}
{% block content %}
<h1 class="page-title">Add Loot</h1>
<hr class="page-title-hr">
{% csrf_token %}
{% load crispy_forms_tags %}
{% crispy form form.helper %}
{% endblock content %}
You can create Api based view for your Model
in JavaScript, you can call the the API end and get the data
Ajax code can be something like below
$(document).ready(function () {
setTimeout(function () {
$(document).on('click', '#id', function () {
$.get("./api/v2/end_point?state=" + state, function (data, status) {
var inner_data = data[0].state_count;
change_text.html(inner_data);
});
});
}, 100);
})

Django Dynamic Inline Formset without any fields in the parent model not working

i am new to Django and i have been trying to create a dynamic inline form but ran into an issue. I am trying to create an inline form with user-customisable number of inline forms (models: DataSheetProperties) with foreign key to models: ProvisionalData. However, in the parent model (ProvisionalData), I do not want the user to fill in any fields, but for me to do the filling of the fields in the backend. Meaning, the user should only see the child model (DataSheetProperties) and be able to add or remove instances of that inline form as they see fit. The rendering of the form is okay, but when i press the 'save' button, I can't seem to save it and it does not even run through the form_valid() method in the views.py. I figured it was because there was no fields in the form of the parent model for the user to fill in, hence django does not recognise that the form was being submitted. Hence, I tried to add in a field into the forms.py (ProvisionalDataCreateForm) under the Div() section of the __init_ method. After adding the field, I was able to save the inline form successfully. However, I would hope that the user would not be able to see and fill in any of the fields in the parent model and form as I would want that form to be automatically filled up in the backend. Hope that you guys can help to give some suggestions, all answers are welcome. Thanks in advance!
I downloaded the django-dynamic-formset plugin from this link to use in my dynamic inline formset.
https://github.com/elo80ka/django-dynamic-formset
Also, the code for my dynamic inline formset can from this website.
https://dev.to/zxenia/django-inline-formsets-with-class-based-views-and-crispy-forms-14o6
forms.py
class ProvisionalDataForm(forms.ModelForm):
class Meta:
model = DataSheetProperties
fields = ['properties', 'name','method','value','units']
exclude = []
ProvisionalDataFormSet = inlineformset_factory(ProvisionalData, DataSheetProperties,
form=ProvisionalDataForm, extra=1,
can_delete=True)
class ProvisionalDataCreateForm(forms.ModelForm):
class Meta:
model = ProvisionalData
exclude = []
def __init__(self, *args, **kwargs):
super(ProvisionalDataCreateForm, self).__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_tag = True
self.helper.form_class = 'form-horizontal'
self.helper.label_class = 'col-md-3 create-label'
self.helper.field_class = 'col-md-9'
self.helper.layout = Layout(
Div(
Fieldset('Add properties',
Formset('data')),
HTML("<br>"),
ButtonHolder(Submit('submit', 'save')),
)
)
models.py
class ProvisionalData(models.Model):
status = (
('active', 'Active'),
('inactive', 'Inactive'),
)
provisionaldata_id = models.AutoField(primary_key=True)
rndProject = models.ForeignKey('RndProject', on_delete=models.CASCADE, null=True, blank=True)
status = models.CharField(max_length = 20, choices=status, default='active')
justification = models.TextField(max_length=200, null=True, blank=True)
class DataSheetProperties(models.Model):
options = (
('physical','Physical'),
('mechanical','Mechanical'),
('thermal','Thermal'),
)
status = (
('active', 'Active'),
('inactive', 'Inactive'),
)
name = models.CharField(max_length=50, default='Property Name')
method = models.CharField(max_length=50, default='Test Method')
units = models.CharField(max_length=50, default='Unit of Measurement')
value = models.CharField(max_length=50, default='Typical Value')
properties = models.CharField(max_length=50, choices=options)
data = models.ForeignKey('ProvisionalData', on_delete=models.CASCADE)
status = models.CharField(max_length = 20, choices=status, default='active')
views.py
class ProvisionalDataSheetView(CreateView):
model = ProvisionalData
form_class = ProvisionalDataCreateForm
template_name = 'rnd/provisional_datasheet.html'
def get_context_data(self, **kwargs):
context = super(ProvisionalDataSheetView, self).get_context_data(**kwargs)
if self.request.POST:
context['data'] = ProvisionalDataFormSet(self.request.POST)
else:
context['data'] = ProvisionalDataFormSet()
return context
def form_valid(self, form):
form.instance.rndProject = RndProject.objects.filter(rnd_project_id = self.kwargs['pk'])[0]
context = self.get_context_data()
data = context['data']
with transaction.atomic():
self.object = form.save()
if data.is_valid():
data.instance = self.object
data.save()
return super(ProvisionalDataSheetView, self).form_valid(form)
def get_success_url(self):
return reverse('rnd-project-details', kwargs={'pk': self.object.rndProject.rnd_project_id})
formset.html
{% load static %}
{% load crispy_forms_tags %}
<table>
{{ formset.management_form|crispy }}
{% for form in formset.forms %}
<tr class="{% cycle 'row1' 'row2' %} formset_row-{{ formset.prefix }}">
{% for field in form.visible_fields %}
<td>
{# Include the hidden fields in the form #}
{% if forloop.first %}
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
{% endif %}
{{ field.errors.as_ul }}
{{ field|as_crispy_field }}
</td>
{% endfor %}
</tr>
{% endfor %}
</table>
<br>
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js">
</script>
<script src="{% static 'rnd/jquery.formset.js' %}" type="text/javascript"> </script>
<script type="text/javascript">
$('.formset_row-{{ formset.prefix }}').formset({
addText: 'add another',
deleteText: 'remove',
prefix: '{{ formset.prefix }}',
});
</script>
provisional_datasheet.html
{% extends 'rnd/base.html' %}
{% load static %}
{% load crispy_forms_tags %}
{% block content %}
<div class="container">
<div class="card">
<div class="card-header">
Create provisional datasheet
</div>
<div class="card-body">
{% crispy form %}
</div>
</div>
</div>
{% endblock content %}

Tempus Dominus Datepicker widget not cloning properly in Django Model Formset when using Dynamic Formset Library

I have a model formset set up using Django Dynamic Formset along with Crispy Forms and the Tempus Dominus datepicker library. It's a simple formset for users to add operating expense line entries -- date, category, description, and amount.
My datepicker works correctly with a non-dynamic formset. However, when using dynamic formsets and a new form is added, the widget ends up targeting the last form in the formset that it was cloned from and not the current form (https://imgur.com/a/2GFYZ90). Instead of incrementing properly (i.e. form_3, form_4, form_5, etc.), it just refers to the last form of the formset (i.e. form_2) before the new ones are added. As a result, if I try to toggle the datepickers in any of the dynamically added forms, it just triggers the one in form_2. Regular text fields clone correctly without any problems.
As you can see in lines 19-23 of opex.html, I'm attempting to implement the destroy and re-initialize functions as suggested by some older Stack Overflow posts (1, 2, and 3) but I'm a JS noob so I'm not sure how to proceed.
models.py
class OpEx(models.Model):
GEN_EXP = 'GE'
OFFICE_SUP = 'OS'
NR_EXP = 'NR'
CATEGORY_CHOICES = [
(GEN_EXP, 'General Expenses'),
(OFFICE_SUP, 'Office Supplies'),
(NR_EXP, 'Non-Recurring Expenses'),
]
exp_user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, default=0)
exp_date = models.DateField(verbose_name='Date')
category = models.CharField(max_length=2, choices=CATEGORY_CHOICES, default=GEN_EXP)
description = models.CharField(max_length=255)
amount = models.DecimalField(verbose_name='Amount ($)', max_digits=10, decimal_places=2)
forms.py
class OpExEntryForm(ModelForm):
class Meta:
model = OpEx
exclude = ('id', 'exp_user')
class OpExFormSetHelper(FormHelper):
def __init__(self, *args, **kwargs):
super(OpExFormSetHelper, self).__init__(*args, **kwargs)
self.form_method = 'POST'
self.form_class = 'form-inline'
self.form_show_labels = False
self.layout = Layout(
Row(
Column('exp_date', css_class='col-sm-2'),
Column('category', css_class='col-sm-3'),
Column('description', css_class='col-sm-4'),
Column(PrependedText('amount', "<span class='fa fa-dollar-sign'"), css_class='col-sm-2'),
css_class='opex-formset-row'
)
)
self.render_required_fields = True
self.add_input(Submit('submit', 'Update operating expenses'))
views.py
def opex(request):
OpExFormSet = modelformset_factory(OpEx, exclude=('id', 'exp_user'),
widgets={'exp_date': DatePicker(attrs={'append': 'fa fa-calendar'})},
form=OpExEntryForm, extra=0, can_delete=True)
helper = OpExFormSetHelper()
queryset = OpEx.objects.filter(exp_user=user)
if request.method == 'POST':
formset = OpExFormSet(request.POST, queryset=queryset)
# Irrelevant code...
else:
formset = OpExFormSet(queryset=queryset)
return render(request, 'opex.html', {'formset': formset, 'helper': helper})
opex.html
{% extends 'base.html' %}
{% load crispy_forms_tags %}
{% load static %}
{% block extra_head %}
{{ form.media }}
{% endblock %}
{% block content %}
{% crispy formset helper %}
<!-- Load Django Dynamic Formset -->
<script type="text/javascript" src="{% static 'js/jquery.formset.js' %}"></script>
<script type="text/javascript">
$(function() {
$('.opex-formset-row').formset({
addText : 'add another',
deleteText : 'remove',
added: function (row) {
var datePicker = $(row).find('input[name$="exp_date"]');
if (datePicker.length > 0) {
datePicker.datetimepicker('destroy');
}
}
});
})
</script>
{% endblock %}

Django formsets no data on post

I have a form that uses multiple formsets. The formset forms are added dynamically via JS. I have been looking at a few different places to help myself along.
Add a dynamic form to a django formset using javascript in a right way
And
A nice post by Kevin Dias - Django class-based views with multiple inline formsets
The problem I am having is that when I post my data the outer form has data, but non of my formsets actually have any data in the cleaned_data dictionary when I start looping through them. Any thoughts on what I may be missing? The second formset is added with a very similar JS method.
Forms
class ShippingForm(Form):
is_partial = BooleanField(label='Partial?')
class ShippingActualProduct(Form):
box_no = CharField(label='Box Number', max_length=3)
upc = CharField(
widget=forms.TextInput(attrs={'class':'upcAjax'}),
)
serial_no = CharField(
label='Serial Number',
widget=forms.TextInput(attrs={'class':'serial'}),
)
sku = CharField(
widget=forms.TextInput(attrs={'class':'skuAjax'}),
)
description=CharField(label='Description')
on_hand=CharField(label='On Hand')
def __init__(self, *args, **kwargs):
super(ShippingActualProduct,self).__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_show_labels = True
self.helper.form_class = 'form-inline'
class ShippingNonInventoryProduct(Form):
non_box_no = CharField(label='Box Number', max_length=3)
quantity = IntegerField()
description = CharField()
serial_no = CharField()
def __init__(self, *args, **kwargs):
super(ShippingNonInventoryProduct,self).__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_show_labels = True
self.helper.form_class = 'form-inline'
ActualProductFormSet = formset_factory(ShippingActualProduct, extra=1, can_delete=True)
NonInventoryProductFormSet = formset_factory(ShippingNonInventoryProduct, extra=1, can_delete=True)
Views
class ShippingCreate(FormView):
template_name = 'jinja2/Shipping/shipping_create.html'
form_class = ShippingForm
success_url = reverse_lazy('shipping_create')
def get_context_data(self, **kwargs):
context = super(ShippingCreate, self).get_context_data(**kwargs)
input_invoice_no = self.request.GET['invoice_no']
try:
self.object = Invoice.objects.get(invoice_no = input_invoice_no)
context['invoice'] = self.object
except Invoice.DoesNotExist:
messages.error(self.request, 'We were unable to find invoice number %s, please try again' % input_invoice_no)
try:
context['voucher'] = Voucher.objects.get(voucher_no = self.object.voucher_no)
except Voucher.DoesNotExist:
messages.error(self.request, 'We were unable to find an installation voucher for claim number %s' % self.object.voucher_no)
context['actual_items_forms'] = ActualProductFormSet(prefix='actual')
context['non_inventory_items_forms'] = NonInventoryProductFormSet(prefix='non')
context['form'] = ShippingForm()
return context
def get(self, request, *args, **kwargs):
self.object = None
context = self.get_context_data()
return render(request, self.template_name, context)
def post(self, request, *args, **kwargs):
self.object = None
form_class = self.get_form_class()
form = self.get_form(form_class)
actual_product = ActualProductFormSet(self.request.POST, prefix='actual')
non_inv_product = NonInventoryProductFormSet(self.request.POST, prefix='non')
if actual_product.is_valid():
for product in actual_product:
data = product.cleaned_data
sku = data.get('sku')
context={}
return render(request, self.template_name, context)
Template
{% extends "base.html" %}
{% load crispy_forms_tags %}
{% load static from staticfiles %}
{% load socialaccount %}
{% load sitetree %}
{% block headcss %}
{{ block.super }}
<link rel="stylesheet" href="{% static "css/shipping.css" %}">
{% endblock headcss %}
{% block headjs %}
{{ block.super }}
<script src="{% static "js/shipping.js" %}"></script>
{% endblock headjs %}
{% block content %}
{% block messages %}
{{ block.super }}
{% endblock messages %}
<div class="container-fluid">
<form action="." method="post">
<div>
<h3>Item information:</h3>
{% csrf_token %}
{{ actual_items_forms.management_form }}
<div id="actual-items-form-container">
</div>
Add Item
</div>
<div>
<h3>Non Inventory Items Shipped:</h3>
{{ non_inventory_items_forms.management_form }}
<div id="non-inv-items-form-container">
</div>
Add Item
</div>
{{ form.as_p }}
<input type="submit" value="Complete" class="submit btn btn-success" />
</form>
</div>
{% include "jinja2/hub/loading_modal.html" %}
{% endblock content %}
</html>
The JavaScript
function getNewActualItemForm() {
// unbind this ajax call from the overlay displayed when looking data up.
$(document).unbind(".items");
var count = $('#actual-items-form-container').children().length;
$.get("/forms/actualitemform",function(data, status){
var form = data.replace(/__prefix__/g, count);
// Get the html contents of the form, all together to iterate over.
var htmlForm = $('<form>').html(form).contents();
// Just grab the children of that form
var rows = htmlForm.children();
// loop through the inputs locating the DELETE input and label.
$(rows).each( function(index, value) {
var row = $(this);
var del = $(row).find('input:checkbox[id $= "-DELETE"]');
// Only move forward if we have found the DELETE input
if (del.length){
//Write the form ot the Dom so the search for the label will succeed.
$('div#actual-items-form-container').append(form);
var label ='label[for="id_form-' + count + '-DELETE"]';
$(label).hide();
}
});
// update form count
$('#id_actual-TOTAL_FORMS').attr('value', count+1);
// some animate to scroll to view our new form
$('html, body').animate({
scrollTop: $('#actual-item-btn').position().top-200
}, 800);
// get the max box number.
var maxBoxNo = getBoxNo();
// Set focus to the next UPC
var boxID = '#id_form-var-box_no';
var upcID = '#id_form-var-upc';
var nextBox = boxID.replace('var',count);
var nextUpc = upcID.replace('var',count);
// set the box number for the new line.
$(nextBox).val(maxBoxNo);
$(nextUpc).focus();
});
return count;
}
There were two issues that were causing the troubles here.
I had the crispy forms helper rendering the form tags for each formset. This is a bad idea. Setting form_tag = False fixed that.
I had forgotten to set the prefix argument on my formsets in the views I created to grab the next form via JavaScript.
Once both of these were implemented the data was now available from the forms on submission.

Categories