Django Toggle Div on Search Query - javascript

UPDATE
The solution is in the comments.
Original Post
I have a basic search query to find customers on one of my forms in my Django project. It returns results from the database in an HTML table. The query is executed by clicking the search button. I want to hide the div encapsulating the table when the query has not been executed.
Because the JS function is executed when the search button is clicked, the table only shows momentarily until the page reloads.
What is the convention for displaying a div after a query is executed, without having a constant toggle button?
views.py
#method_decorator(login_required, name='dispatch')
class CustomerResultsView(ListView):
model = CustomerName
template_name = 'parent/child.html'
context_object_name = 'filtered_customers'
#method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super().dispatch(*args, **kwargs)
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs)
context['form1'] = FilterForm(initial={
'name': self.request.GET.get('name', ''),
'filter_field': self.request.GET.get('filter_field', '')
})
context['users'] = self.request.user
context['form'] = Customer()
return context
def get_queryset(self):
query = self.request.GET.get('name')
filter_field = self.request.GET.get('filter_field')
if query:
return CustomerName.objects.filter(
Q(('%s__icontains' % filter_field, query))
)
else:
return {}
child.html
<form method="GET">
<legend class="border-bottom mb-4">Customer Lookup</legend>
<fieldset class="form-group">
{{ form1|crispy }}
</fieldset>
<div class="form-group">
<input type="submit" class="btn btn-outline-info mt-4" value="Search" onclick="showDiv()">
</div>
</form>
<hr>
<div class="table-responsive" id="custTabDiv" style="display:none">
<table id="testTable" class="table table-striped table-hover" cellspacing="0" width="100%">
<thead>
<tr>
<th class="th-sm" scope="col">First</th>
<th class="th-sm" scope="col">Last</th>
<th class="th-sm" scope="col">Phone</th>
</tr>
</thead>
<tbody>
<tr>
{% for name in filtered_customers %}
<tr>
<td id="test" onclick="customerNameValidation()">{{ name.first_name }}</td>
<td>{{ name.last_name }}</td>
<td>{{ name.phone }}</td>
</tr>
{% endfor %}
</tr>
</tbody>
</table>
<script>
function showDiv() {
document.getElementById("custTabDiv").style.display = "block";
}
</script>

Related

Pass Javascript Calculations to Django Backend

I have an HTML form where users type in the name of items and value corresponding to it in an input form, which is reflected when the form is submitted to Django backend.
In my HTML form I have included some Javascript so that the total of these values are reflected instantly without refreshing and even before submitting the form.
My goal:
Send the total amount calculated by Javascript in the HTML under id Total
<th scope="col">Total Equipment and Assets</th>
<th scope="col" id="Total"></th>
to the class in the
total_assets= models.IntegerField(null=True, blank=True, verbose_name='Total Assets')
in the Models.py after submitting.
Note that the reason for the question is that the total values are not manually added they are directly calculated using Javascript.
Here is a sample to make things more clear.
Here is the HTML Template:
<tr>
<td>
<input
placeholder="Type in the Equipment and assets"
type="text"
class="form-control"
name="item_1"
id="item_1"
{% if form.is_bound %}value="{{ form.item_1.value }}"{% endif %}/>
{% for err in form.item_1.errors %}
<small class="text-danger mb-2 ml-2">{{ err }}</small>
{% endfor %}
</td>
<td>
<h6 style="float:left; margin-right:5px; margin-top:7px">$</h6>
<input
type="number"
class="form-control w-25 subtotal-group subtotal-group-1"
name="item_1_amount"
id="item_1_amount"
{% if form.is_bound %}value="{{ form.item_1_amount.value }}"{% endif %}/>
{% for err in form.item_1_amount.errors %}
<small class="text-danger mb-2 ml-2">{{ err }}</small>
{% endfor %}
</td>
</tr>
<tr>
<td>
<input
placeholder="Type in the Equipment and assets"
type="text"
class="form-control"
name="item_2"
id="item_2"
{% if form.is_bound %}value="{{ form.item_2.value }}"{% endif %}/>
{% for err in form.item_2.errors %}
<small class="text-danger mb-2 ml-2">{{ err }}</small>
{% endfor %}
</td>
<td>
<h6 style="float:left; margin-right:5px; margin-top:7px">$</h6>
<input
autocomplete="off"
type="number"
class="form-control w-25 subtotal-group subtotal-group-1"
name="item_2_amount"
id="item_2_amount"
{% if form.is_bound %}value="{{ form.item_2_amount.value }}"{% endif %}/>
{% for err in form.item_2_amount.errors %}
<small class="text-danger mb-2 ml-2">{{ err }}</small>
{% endfor %}
</td>
</tr>
Here is the Javacript
<script>
const q=(e,n=document)=>n.querySelector(e);
const qa=(e,n=document)=>n.querySelectorAll(e);
const results={};
console. log(results)
qa('[type="number"].form-control').forEach(input=>input.addEventListener('input',function(e){
results[ this.name ]=Number( this.value );
const resultGroupSet1 = [...qa('.subtotal-group-1')]
.map(s => Number(s.value))
.reduce((a,v) => a+v);
q('th#Total').textContent = resultGroupSet1;
}));
</script>
Here is where the total is reflected in the HTML template
<thead class="table-light">
<tr>
<th scope="col">Total Equipment and Assets</th>
<th scope="col" id="Total"></th>
</tr>
</thead>
Here is the models.py
item_1 = models.CharField(max_length=100,null=True, blank=True, verbose_name='Item 1')
item_1_amount = models.IntegerField(null=True, blank=True, verbose_name='Item 1 Amount')
item_2 = models.CharField(max_length=100,null=True, blank=True, verbose_name='Item 2')
item_2_amount = models.IntegerField(null=True, blank=True, verbose_name='Item 2 Amount')
total_assets = models.IntegerField(null=True, blank=True, verbose_name='Total Assets')
Here is the views:
def add_bp(request):
if request.method == 'POST':
# create a form instance and populate it with data from the request:
form = infoForm(request.POST)
# check whether it's valid:
if form.is_valid():
form.save()
b_name = form.cleaned_data.get('bName')
messages.success(request, f'PDF created for {b_name}!')
return redirect('application:core')
# if a GET (or any other method) we'll create a blank form
else:
form = infoForm()
return render(request, 'application/template.html', {'form': form, })
Update html to:
<thead class="table-light">
<tr>
<th scope="col">Total Equipment and Assets</th>
<th scope="col">
<!-- Value which is visible (calculated using JS) -->
<span id="Total"></span>
<!-- Add a hidden input to store value (value calculated and assigned using JS) -->
<input type="hidden" name="total_assets" value="0" id="total_assets">
</th>
</tr>
</thead>
Update script to assign resultGroupSet1 as:
textual content to the span tag with id=Total
value to the hidden input with name=total_assets
// Assign result to span tag which is visible
q('span#Total').textContent = resultGroupSet1;
// Assign result as value to hidden input field with name total_assets
q('input#total_assets').value = resultGroupSet1;
No other changes in views.
As an input field with a name="total_assets" is used, the value will be passed on to the request body and will be accessible at request.POST. Here, as the total_assets field is hidden it is not visible to the users and still the value is available in POST data when form is submitted. So, when form.save() is called the calculated value (using JS) will be saved.
I assumes your question is how to get the value in this element:
<th scope="col" id="Total"></th>
You can just simply add input element in your html code and add the name into it:
<th scope="col"><input id="Total" name="total_assets" value=""></th>
Then in your views.py:
def add_bp(request):
if request.method == 'POST':
form = infoForm(request.POST)
if form.is_valid():
form.save()
You can also manually get the Total:
def add_bp(request):
if request.method == 'POST':
total_assets = request.POST.get("total_assets")
Probably what you are looking for is <input type="hidden" ...>, it's not visible by the end user and it's included in the form submit
Why not do this calculation in Django Backend?
My suggestion is to pass all the arguments normally and just add a listener to the model saving (every time you will save an element to the table this little piece of code will run right before it saves it):
from django.db.models.signals import pre_save
from django.dispatch import receiver
#receiver(pre_save, model_class)
def form_pre_save(instance, *args, **kwargs):
instance.total_assets = instance.item_1_amount + instance.item_2_amount
This way when you want to save this kind of element in a different place (in the backend for example) you won't have to re-write the code that does that, but rather just save the instance.
You can read more about the signals pre_save function here

Django the best way to create an edit modal form?

In one of my django app I have set the following architecture:
#models.py
class Income(models.Model):
price = models.DecimalField()
quantity = models.DecimalField()
date=models.DateField()
# forms.py
class IncomeForm(forms.ModelForm):
class Meta:
model = Income
fields = "__all__"
#views.py
def income_(request):
elements = Income.objects.all()
if request.method == 'POST':
form = IncomeForm(request.POST)
if form.is_valid():
new_input = form.save()
else :
form = IncomeForm()
elements = Income.objects.all()
context= {
'form': form,
'elements':elements,
}
return render(request, "income/income.html", context)
In my income.html file I have set the following
{% load crispy_forms_tags %}
<form id="" method="post">
<div class="form-group col-2 0 mb-0" >
{{form.quantity|as_crispy_field}}
</div>
<div class="form-group col-2 0 mb-0" >
{{form.price|as_crispy_field}}
</div>
<div class="form-group col-2 0 mb-0" >
{{form.date|as_crispy_field}}
</div>
</div>
After that I have created a table that list all data filled.
Now I want to create a button for each row that open a modal form that give me the possibility to modify the specific data for each id dataset.
I have tried to perform it with an ajax call, but I have had difficults to perform the form and the type of data (becouse in this manner I don't have the possibility to use crispy form or the forms model of the django framework).
So my question is: there is a simple way to achive my aim?
From what I understand from your question, you can try create a UpdateView in your view.py and redirect your html button with the object id to that view.
Updated answer-
since you are asking for simpler way implementing the edit page...
- models.py
class Income(models.Model):
price = models.DecimalField(decimal_places=2, max_digits=10000)
quantity = models.DecimalField(decimal_places=2, max_digits=10000)
date = models.DateField()
- urls.py
urlpatterns = [
path('income/', views.IncomeListView.as_view(), name='income'),
path('income_edit/<int:pk>', views.IncomeEdit.as_view(), name='income-edit'),
]
- views.py
class IncomeListView(ListView):
model = Income
template_name = 'income.html'
class IncomeEdit(UpdateView):
model = Income
form_class = IncomeForm
template_name = "income_form.html"
- forms.py
class IncomeForm(forms.ModelForm):
class Meta:
model = Income
fields = '__all__'
- income.html
<h1>Income List</h1>
<table>
<tr>
<th>ID</th>
<th>price</th>
<th>quantity</th>
<th>date</th>
</tr>
{% if income_list %}
{% for income in income_list %}
<tr>
<td>{{income.id}}</td>
<td>{{income.price}}</td>
<td>{{income.quantity}}</td>
<td>{{income.date}}</td>
<td>Edit </td>
</tr>
{% endfor %}
{% endif %}
</table>
- income_form.html
<form action="" method="post">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Submit">
{{ form.media }}
</form>
please look into the class used for more information and understanding. hope this help =)

Print table information after onclick event in Django

I want to print table row information in a Javascript function bind to the onclick event of a button.
view.py
def table(request):
test_list = TestInformation.objects.order_by()
context = { 'test_list' : test_list, }
return render(request, '/test.html', context)
test.html
<label ~~ id="btn_click" onclick="btn()">
<table class="table table-bordered" id="data_table">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
</tr>
</thead>
<tbody>
{% for li in test_list %}
<tr>
<td> {{ li.Id }} </td>
<td> {{ li.Name}} </td>
</tr>
{% endfor %}
</tbody>
</table>
<Script type="text/javascript">
function btn {
//I'd like to put above {for~~ endfor} source here!
}
</Script>
Currently Action
The table is displayed as soon as you load the html page.
Expected Action
Only visible when the table presses a button
How can I invoke Python objects in JavaScript functions for the actions I expect?

Flask does not return db query when I use the JS hide/unhide. Works if I comment out JS

I am trying to retrieve data from the database based on a users search results. The results, in a table format, should only be shown after the user hits the search button.
Query executes fine when javascript is cancelled out and the table "list" display is changed to block. However, when I enable the javascript I get no results.
<div style="text-align: center;">
{% from "_formhelpers.html" import render_field %}
<form method=post action="/">
<dl style="display: inline-block; text:white;" >{{render_field(form.search)}} </dl>
<button id="searchbutton" type="submit" style="display: inline-block;" class="btn btn-outline-success my-2 my-sm-0" onclick="fetchlist(); return false;">Search</button>
<br>
{% if error %}
<p class = "error"><strong>Error:</strong>{{error}}</p>
{% endif %}
</form>
</div>
<div style="text-align:center;">
<table id="list" style="display:none;" class = "table table-hover" >
<thead>
<th scope="col">First</th>
<th scope="col">Last</th>
<th scope="col">Rating</th>
<th scope="col">Review</th>
</thead>
<tbody>
{% for row in data %}
<tr>
{% for d in row %}
<td>{{ d }}</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
</body>
<script>
function fetchlist() {
if (document.getElementById('searchbutton').onclick) {
document.getElementById('list').style.display = 'inline';
}
else document.getElementById('list').style.display = 'inline';
}
</script>
{% endblock %}
</html>
#app.route('/', methods=['GET', 'POST'])
def homepage():
try:
form=SearchForm(request.form)
global d1
d1 =""
if request.method == "POST" and form.validate():
search = form.search.data
a = search.split(" ",1)
firstname, lastname = (a[0], a[1])
c,conn = connection()
qry = "SELECT FirstName, LastName FROM posts WHERE FirstName LIKE (%s) AND LastName like (%s)"
c.execute(qry, ((thwart(firstname)), (thwart(lastname))))
d1 = c.fetchall()
c.close()
conn.close()
else: print('error')
return render_template("index.html", data=d1, form = form)
except Exception as e:
return(str(e))
As you already know the problem is caused by your JS. This is because the JS is waiting for the search button to be clicked before it changes the style of the list to inline.
This all seems fine, but the problem comes in the fact that the JS is executed when the button is clicked. But then the request is posted to the server and a new page is sent to the browser with the search results. However, this new page the search button has never been clicked.
You can fix writing the method to display the results into your template. For instance you could wrap the table in an if statement like this...
{% if data|length %}
<div style="text-align:center;">
<table id="list" style="display:none;" class = "table table-hover" >
<thead>
<th scope="col">First</th>
<th scope="col">Last</th>
<th scope="col">Rating</th>
<th scope="col">Review</th>
</thead>
<tbody>
{% for row in data %}
<tr>
{% for d in row %}
<td>{{ d }}</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table
</div>
{% endif %}
The |length filter makes sure data is not an empty string. Otherwise I believe it may always be true. You could try {% if data %} to double check. It may work.
There are a lot more options....
You could wrap you <script> in the if statement. You should modify it a little. So it does not wait of the search button to be clicked.
You could wrap the inline style in the if statement. Of course you could use CSS classes instead of inline styling. This would be cleaner.

DataTables Refresh Django Ajax Data on CRUD Operations

I have a Django project with an Analytic model. This model is a list of analytics. It has a ForeignKeyField and a ManyToMany Field. The end goal is to have the user go to a URL where they can view a list of Analytics in a DataTable, create a new analytic, edit analytics, and delete analytics. Using this tutorial: https://simpleisbetterthancomplex.com/tutorial/2016/11/15/how-to-implement-a-crud-using-ajax-and-json.html, I accomplished all of these objectives in a regular Bootstrap HTML table (i.e. not in a DataTable).
When I attempted to introduce a DataTable to the mix, I discovered that my DataTable was pulling from the HTML/DOM source, so it was not updating unless the page was refreshed. So I then realized that I need to either configure the DataTable to initially pull from HTML/DOM and then pull from AJAX, or I need to initially use Ajax as the source.
It turns out, regular Django does not do a good job of serializing ManyToMany fields, so I opted to use DRF to serialize my Analytic model. This works to a degree: the JSON output looks decent, and the results show up in my DataTable. However, the data is still not updating when an Ajax call is made. In addition, DataTables does not really allow inline buttons for editing/deleting, which is why it was necessary to manually write those buttons into the HTML in the first place.
Question: How do I force a DataTable that is sourcing from HTML/DOM to update its data without refreshing the page when an Ajax CRUD operation is performed?
views.py:
def analytic_list(request):
analytics = Analytic.objects.all().select_related('analyticCategory').prefetch_related('dataSources')
return render(request, 'analytics/analytic_list.html', {'analytics':analytics})
def save_analytic_form(request, form, template_name):
data = dict()
if request.method == 'POST':
if form.is_valid():
form.save()
data['form_is_valid'] = True
analytics = Analytic.objects.all()
data['html_analytic_list'] = render_to_string('analytics/includes/partial_analytic_list.html', {
'analytics': analytics
})
else:
data['form_is_valid'] = False
context = {'form': form}
data['html_form'] = render_to_string(template_name, context, request=request)
return JsonResponse(data)
def analytic_create(request):
if request.method == 'POST':
form = AnalyticForm(request.POST)
else:
form = AnalyticForm()
return save_analytic_form(request, form, 'analytics/includes/partial_analytic_create.html')
def analytic_update(request, pk):
analytic = get_object_or_404(Analytic, pk=pk)
if request.method == 'POST':
form = AnalyticForm(request.POST, instance=analytic)
else:
form = AnalyticForm(instance=analytic)
return save_analytic_form(request, form, 'analytics/includes/partial_analytic_update.html')
def analytic_delete(request, pk):
analytic = get_object_or_404(Analytic, pk=pk)
data = dict()
if request.method == 'POST':
analytic.delete()
data['form_is_valid'] = True # This is just to play along with the existing code
analytics = Analytic.objects.all()
data['html_analytic_list'] = render_to_string('analytics/includes/partial_analytic_list.html', {
'analytics': analytics
})
else:
context = {'analytic': analytic}
data['html_form'] = render_to_string('analytics/includes/partial_analytic_delete.html',
context,
request=request,
)
return JsonResponse(data)
urls.py:
url(r'^dataanalytics/analytics/$', views.analytic_list, name='analytic_list'),
url(r'^dataanalytics/analytics/create/$', views.analytic_create, name='analytic_create'),
url(r'^dataanalytics/analytics/(?P<pk>\d+)/update/$', views.analytic_update, name='analytic_update'),
url(r'^dataanalytics/analytics/(?P<pk>\d+)/delete/$', views.analytic_delete, name='analytic_delete'),
analytic_list.html:
{% block content %}
<!-- BUTTON TO TRIGGER THE ACTION -->
<p>
<button type="button"
class="btn btn-primary js-create-analytic"
data-url="{% url 'analytic_create' %}">
<span class="fa fa-plus"></span>
New analytic
</button>
</p>
<table class="table table-hover table-sm display responsive" width="100%" cellspacing="0" id="analytic-table">
<thead>
<tr>
<th class="all align-top">#</th>
<th class="all align-top">Name</th>
<th class="all align-top">Description</th>
<th class="all align-top">Category</th>
<th class="all align-top">Type</th>
<th class="all align-top">Format</th>
<th class="all align-top">Data Source(s)</th>
<th class="all align-top"></th>
<th class="none">Created By</th>
<th class="none">Created Date</th>
<th class="none">Modified By</th>
<th class="none">Modified Date</th>
</tr>
</thead>
<!-- <tbody>
{% include 'analytics/includes/partial_analytic_list.html' %}
</tbody> -->
</table>
<!-- THE MODAL WE WILL BE USING -->
<div class="modal fade" id="modal-analytic">
<div class="modal-dialog">
<div class="modal-content">
</div>
</div>
{% endblock %}
partial_analytic_list.html:
{% for analytic in analytics %}
<tr>
<td>{{ analytic.id }}</td>
<td>{{ analytic.analytic }}</td>
<td>{{ analytic.analyticDescription }}</td>
<td>{{ analytic.analyticCategory }}</td>
<td>{{ analytic.analyticType }}</td>
<td>{{ analytic.analyticFormat }}</td>
<td>
{% for data_source in analytic.dataSources.all %}
{{ data_source }}
{% endfor %}
</td>
<td>
<button type="button"
class="btn btn-warning btn-sm js-update-analytic"
data-url="{% url 'analytic_update' analytic.id %}">
<span class="fa fa-pencil-alt"></span>
</button>
<button type="button"
class="btn btn-danger btn-sm js-delete-analytic"
data-url="{% url 'analytic_delete' analytic.id %}">
<span class="fa fa-trash-alt"></span>
</button>
</td>
<td>{{ analytic.createdBy }}</td>
<td>{{ analytic.createdDateTime }}</td>
<td>{{ analytic.modifiedBy }}</td>
<td>{{ analytic.modifiedDateTime }}</td>
</tr>
{% empty %}
<tr>
<td colspan="7" class="text-center bg-warning">No analytic</td>
</tr>
{% endfor %}
analytics.js:
$(function () {
/* Functions */
var loadForm = function () {
var btn = $(this);
$.ajax({
url: btn.attr("data-url"),
type: 'get',
dataType: 'json',
beforeSend: function () {
$("#modal-analytic").modal("show");
},
success: function (data) {
$("#modal-analytic .modal-content").html(data.html_form);
}
});
};
var saveForm = function () {
var form = $(this);
$.ajax({
url: form.attr("action"),
data: form.serialize(),
type: form.attr("method"),
dataType: 'json',
success: function (data) {
if (data.form_is_valid) {
$("#analytic-table tbody").html(data.html_analytic_list);
$("#modal-analytic").modal("hide");
}
else {
$("#modal-analytic .modal-content").html(data.html_form);
}
}
});
return false;
};
/* Binding */
// Create analytic
$(".js-create-analytic").click(loadForm);
$("#modal-analytic").on("submit", ".js-analytic-create-form", saveForm);
// Update analytic
$("#analytic-table").on("click", ".js-update-analytic", loadForm);
$("#modal-analytic").on("submit", ".js-analytic-update-form", saveForm);
// Delete analytic
$("#analytic-table").on("click", ".js-delete-analytic", loadForm);
$("#modal-analytic").on("submit", ".js-analytic-delete-form", saveForm);
var table = $('#analytic-table').DataTable(
{
});
});
I'm going to assume you're talking about JQuery Datatables. You're SO close, just missing a few bits! You need to destroy and reinitialize the table, no need to use .draw(). Do as shown here:
Ajax:
var saveForm = function () {
var form = $(this);
$.ajax({
url: form.attr("action"),
data: form.serialize(),
type: form.attr("method"),
dataType: 'json',
success: function (data) {
if (data.form_is_valid) {
$("#modal-analytic").modal("hide"); //hide it first if you want
$("#analytic-table").DataTable().destroy(); //this will flush DT's cache
$("#analytic-table tbody").html(data.html_analytic_list); // replace the html
$("#analytic-table").DataTable(); // re-initialize the DataTable
}
else {
$("#modal-analytic .modal-content").html(data.html_form);
}
}
});
return false;
};
Nice work and good luck! Late answer, but maybe it will help somebody.
It looks like your table updates are using this:
$("#analytic-table tbody").html(data.html_analytic_list);
You will need to you Datatables APIs to update the Datatables data. Since you are directly updating the HTML Datatables is not aware of the updates. One option is to use something like rows().invalidate() to have Datatables update its data cache after you update with HTML methods.
However a better option is to use rows.add() for multiple rows or row.add() for a single row.
Since it looks like you have tr elements in your data then you can use something like this for one row or multiple rows, respectively:
table.row.add($(data.html_analytic_list)).get(1)).draw();
table.rows.add($(data.html_analytic_list))).draw();

Categories

Resources