Django links in Knockout Table - django

I have read this post: How can I create a text link in a Knockout javascript table? along with a couple others.
But, I am missing something somewhere, or not taking the right approach. I've included the relevant chunks of code for my problem. I am trying to use the table generated by knockout to to either update a task or remove a task. The remove part is working fine. I am trying to get the update to link to another page that is used to update the task. I cannot figure out what I need to do to get the link working properly in the update column.
I've tried several different approaches for how to put the url in the list of dictionaries that is passed to the KO model. Any advice to steer me in the right direction? If I am missing any information, please let me know. Thank you.
Views.py
def TaskList(request, job_id):
job_tasks = Tasks.objects.filter(parent=job_id)
tasks_list = []
for task in job_tasks:
task_row = {}
task_row['task_id'] = task.task_id
task_row['t_name'] = task.name
task_row['date'] = task.date_created
task_row['state'] = task.state
task_row['url'] = '{% url tracking:update_task task_id=task.task_id %}'
tasks_list.append(task_row)
json_tasks = json.dumps(tasks_list)
if request.POST:
json_data = request.POST.get('json_blob')
obj = loads(json_data)
task.task_id = obj.get("task_id")
remove_task = Tasks.objects.get(task_id=task.task_id)
remove_task.delete()
messages.success(request, 'Task removed')
HTML
<table>
<thead>
<th>Name</th>
<th>Date</th>
<th>State</th>
<th>Update</th>
<th>Remove</th>
</thead>
<tbody data-bind "foreach: tasks">
<tr>
<td data-bind="text: t_name"></td>
<td data-bind="text: date"></td>
<td data-bind="text: state"></td>
<td a class="btn" data-bind="attr: {href: url}">Update</a></td>
<td button class="btn" data-bind="click: $root.remove_task">Remove</button></td>
</tr>
</tbody>
</table>
{% block javascript_variables_nocompress %}
window.TASKS = {{ json_tasks|safe }};
{% endblock %}
{% block javascript_compress %}
<script type='text/javascript' src="{% static 'js/knockout/knockout.js' %}"></script>
<script type="text/javascript">
$(function() {
var RemoveTaskModel = function () {
var self = this;
self.tasks = ko.observableArray(window.TASKS);
self.remove_task = function(task) {
self.tasks.remove(task);
$("#json_blob").val(ko.toJSON(task));
}
}
ko.applyBinding(new RemoveTaskModel());
});
</script>
{% endblock %}
HTML

I would use reverse to do a reverse lookup of the URL for each task:
from django.core.urlresolvers import reverse
def TaskList(request, job_id):
job_tasks = Tasks.objects.filter(parent=job_id)
tasks_list = []
for task in job_tasks:
...
task_row['url'] = reverse('update_task', args=(),
kwargs={'task_id': task_id})
Then your observableArray should be able to bind the property from the JSON to the anchor tag. You might also note that in your code sample, your td is malformed:
<td a class="btn" data-bind="attr: {href: url}">Update</a></td>
it should be:
<td><a class="btn" data-bind="attr: {href: url}">Update</a></td>

Related

Pass HTML table rows to Flask backend

I have a Flask app where I populate an HTML table using javascript when the user presses a button. I need the info from the table passed back to the backend when the user is done with the table and presses another button. I'm trying to access the td elements using Flask's request.form
Javascript:
let i = 0;
function rowTemplate(i, name) {
return `<tr data-index=${i}>
<td>${name}</td>
</tr>`
}
function addRow() {
$('#my-tbody').append(rowTemplate(i, some_name));
i++;
}
Jinja template:
<form name="my-form" method="POST" action="{{ url_for("my_func") }}">
<table name="my-table">
<thead>
</thead>
<tbody id="my-tbody">
</tbody>
</table>
</form>
Flask route:
#app.route("/run", methods=['POST'])
def my_func():
print(request.form)
return render_template("my_template.jinja2")
The request.form seems to not include table and its children elements. I found a suggestion to use a input type=hidden tag and store the info i need there, so I can access it from request.form but it still doesn't show up in request.form if I include it in the javascript function like so:
let i = 0;
function rowTemplate(i, name) {
return `<tr data-index=${i}>
<td>${name}</td>
<input type="hidden" name=${name} value=${i}>
</tr>`
}
The hidden input element shows up in request.form only if I manually add it in the tbody but that doesn't work for me, I need to be adding it from the javascript function
<tbody id="my-tbody">
<input type="hidden" name="test" value="test">
</tbody>
I might have the wrong approach altogether. How do I get the values of the table's td elements to my_func?
I agree with Christoph that modern javascript frameworks make developing such an application part much easier. However, an approach using input fields should send the data to the server so that it can be queried.
The following simple example may not be quite what you intend and could certainly be implemented better, but it shows you a variant that works. The user can build a table embedded in a form step by step and then send it. In order to finally make the data queryable, the mentioned input fields are used within the table.
from flask import (
Flask,
redirect,
render_template,
request,
url_for
)
app = Flask(__name__)
#app.route('/')
def index():
return render_template('index.html')
#app.route('/func', methods=['POST'])
def func():
print(request.form)
return redirect(url_for('index'))
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<style media="screen">
table {
width: 100%;
}
tr td:last-child {
width: 10%;
white-space: nowrap
}
tr td:first-child {
padding: 0 0.6rem 0 0;
}
tr td > * {
width: 100%;
}
.block {
display: block;
width: 100%;
}
</style>
</head>
<body>
<form name="my-form" action="{{ url_for('func') }}" method="post">
<table>
<thead>
<tr>
<th>Name</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<tr id="input-row">
<td><input type="text" name="input" tabindex="0" /></td>
<td><button type="submit">Add</button></td>
</tr>
</tbody>
</table>
</form>
<button name="send-btn" class="block">Submit</button>
<script type="text/javascript">
(() => {
const form = document.querySelector('form[name="my-form"]');
form.addEventListener('submit', evt => {
evt.preventDefault();
const name = evt.target.input.value.trim();
if (name.length === 0) return;
const tbody = document.querySelector('tbody');
const tr = document.createElement('tr');
tr.innerHTML = `
<td><input type="text" name="name" value="${name}" tabindex="-1" readonly /></td>
<td><button type="button" class="rm-btn">Remove</button></td>
`;
tbody.insertBefore(tr, tbody.lastElementChild);
const btn = tr.querySelector('.rm-btn')
btn.addEventListener('click', evt => {
evt.target.closest('tr').remove();
});
evt.target.reset();
});
const sendBtn = document.querySelector('button[name=send-btn]');
sendBtn.addEventListener('click', evt => {
form.querySelector('#input-row').remove();
form.submit();
});
})();
</script>
</body>
</html>

FF92.0 on linux introduces caching issues with django 3.2 inline forms

I've noticed a strange behavior with FF 92.0 (Manjaro Linux, but likely all linux I guess) when using inline forms.
Setup:
Django inline forms
A way to add inlines ajax. I'm using the django forms empty_form
Some arbitray # of inline saved on the server for that object (for example, say 3)
Load the form, add say 2 inlines using javascript. TOTAL_FORMS now shows value=5
Do NOT submit the form, but F5 for a refresh instead
After reload, the inlines shown in the table will mirror that of the server
However the TOTAL_FORMS from the management_form will show value=5, instead of the 3 it should.
page.js:
function insert_inlinedets_form () {
let form_idx = $('#id_details-TOTAL_FORMS').val();
console.log("inserting new form " + form_idx);
let newrow = $('<tr></tr>').appendTo($('#details_form'));
newrow.append($('#empty_form_inlinedets').html().replace(/__prefix__/g, form_idx));
$('#id_details-TOTAL_FORMS').val(parseInt(form_idx)+1);
console.log("added row to inlinedets formset");
};
function remove_inlinedets_form () {
console.log("remove last form ");
let form_idx = $('#id_details-TOTAL_FORMS').val();
if (form_idx>0) {
$('#details_form > tr').last().remove();
$('#id_details-TOTAL_FORMS').val(parseInt(form_idx)-1);
console.log("removed row from inlinedets");
calc_grand_total(); // existing rows haven't changed but the total might
} else {
$('#id_details-TOTAL_FORMS').val(); // if no form left, this SHOULD be 0.
console.log("No more dets left - nothing done.");
}
};
html - empty form:
<div style="display:none">
<table>
<thead></thead>
<tbody>
<tr id="empty_form_inlinedets">
{% for field in formset.empty_form %}
{% if field.is_hidden %}
<td style="display:none;">{{field}}</td>
{% else %}
<td>{{field}}</td>
{% endif %}
{% endfor %}
</tr>
</tbody>
</table>
</div>
html - target table to append to:
<div class="table-responsive shadow encart">
{{ formset.management_form }}
{{ formset.non_form_errors }}
<table class="table table-bordered dbase-table" id="bottom_product_form" width="100%" cellspacing="0">
<caption style="caption-side:top">Products</caption>
<thead>
<tr>
<!-- th content -->
</tr>
</thead>
<tbody id="details_form">
{% for form in formset %}
<tr>
<!-- td content -->
</tr>
{% endfor %}
</tbody>
</table>
</div>
I have validated the following:
The response.rendered_content that the server returns does indeed show the correct TOTAL_FORMS number (3, in the above example)
This does NOT happen upon a hard refresh (ctrl shift r on FF). TOTAL_FORMS, under the same setup, shows the correct # of 3.
This does NOT happen on Google Chrome upon a normal refresh.
So.... any ideas on how I should approach this? What setting could be causing the issue? Please consider in any answer:
I won't have control of user behavior. So simply "not using soft refresh on FF" isn't valid.
I won't have control of user browser settings, so adjusting some settings wouldn't really work (although KNOWING which FF setting may cause the issue is still useful)

Django: how to refresh html content on new database entry

I am developing a MQTT Dashboard app with django. I have a mqtt thread running in background, periodically polling for data from remote device and saving it as a database (MariaDB) row. I also have a view that displays this row in simple table. Now, what I want to do is to refresh this view (without page reload) when new data appears and my question is how to do that. I thought of two different approaches:
Use JS's setInterval triggering ajax call to periodically refresh content. Problem here is that I'm not really proficient with JavaScript nor jQuery so I would love to get simple example how to do that
Somehow refresh data from within on_message function which is called when mqtt gets new data. Problem here is that I have no clue if it is even possible
I would appreciate any explanation of above or even more some description of different, more proper way to do that. Here is my code:
part of template with content i want to refresh:
<div class="row mb-4 justify-content-center text-center">
<h1 class="text-uppercase text-white font-weight-bold">{% translate "Parameters" %}</h1>
<table id="device-parameters-table">
<thead>
<tr>
<th scope="col">{% translate "Parameter" %}</th>
<th scope="col">{% translate "Value" %}</th>
<th scope="col">{% translate "Unit" %}</th>
</tr>
</thead>
<tbody>
{% for key, value in latest_read_data.items %}
<tr>
<td>{{key}}</td>
<td>{{value.value}}</td>
<td>{{value.unit}}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
device view:
def device(request):
clicked = request.GET.get('device_id')
dev = Device.objects.get(id=clicked)
latest_read_data = ReadData.objects.filter(device=dev).order_by('timestamp')
if latest_read_data:
read_data = latest_read_data.values()
read_data = read_data[len(read_data)-1]
read_data.pop('id')
read_data.pop('device_id')
read_data.pop('timestamp')
parameters_get_units(read_data)
context = {'latest_read_data': read_data, 'device': dev}
return render(request, 'dashboard/device.html', context=context)
else:
return HttpResponseRedirect('/dashboard')

Issues with csfr token and ajax call

I tried to implement a button on a Website, which upon pressing it automatically runs a python function on the server and copies some value into the clipboard of the user. The clipboard copying runs fine, but I can not run the python function.
Whenever I try to I get an error 403 and I think it is due to an issue with the csfr token. Can anyone help me to solve this issue?
Here is my HTML
{% if categories %}
<div class="card shadow mb-4">
<div class="card-body card-interface">
<table id="predictionTable" class="table table-bordered">
<thead>
<tr>
<th>Vorhersage</th>
<th>Wahrscheinlichkeit</th>
<th>Kopieren</th>
</tr>
</thead>
<tbody>
{% for category in categories %}
<tr>
<td>{{ category.0}}</td>
<td>{{ category.1}}%</td>
<td><img src="{% static "documents/img/copy.png" %}" class="interface-copy" value="{{ category.0 }}" input_text = "{{ input_text }}" style="cursor: pointer"></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<div id="django-data" data-CSRF="{{csrf_token}}"></div>
And here the .js that is run
$(".interface-copy").on('click', function(e) {
var csrftoken = $("django-data").data().CSRF;
console.log(csrftoken);
console.log("test")
var $temp = $("<input>");
$("body").append($temp);
$temp.val($(this).attr('value')).select();
document.execCommand("copy");
$temp.remove();
console.log("test")
$.ajax({
url: "/ajax/postSingleSourceEntry/",
type : 'POST',
beforeSend: function(request){
request.setRequestHeader("X-CSRFToken", csrftoken);
},
data: {
csfrmiddlewaretoken: csrftoken
},
dataType: "json",
success: function (data){
console.log("call created")
},
error : function(response){
console.log(response)
}
})
});
Change:
<div id="django-data" data-CSRF="{{csrf_token}}"></div>
To:
<div id="django-data" data-csrf="{{csrf_token}}"></div>
And:
var csrftoken = $("django-data").data().CSRF;
To:
var csrftoken = $("#django-data").data().csrf; // Note the # before django-data and csrf in small letter.
You might want to read: How to get the data-id attribute?
You can add #csrf_exempt decorator for that ajax function

Django - Dynamically create element

I am making a complete admin and invoice app in Django.
For the invoice app, as the user clicks on "Create Sales Invoice" the invoice screen appears.
Now I want the system to dynamically generate new bill as soon as this screen appears, but not saved. As the user starts entering item, I want a new item detail (i.e. each bill has one item detail which has the list of items, its quantity and price).
However, none of them shall be saved unless the user clicks on create bill button.
I need help in how to do this thing, ie create a bill and item detail as the user goes to a create bill, link these two with foreign key, but also have the option to discard them if the user does not end up on clicking "save" button.
Edit 1
My invoicing HTML:
{% extends "base.html" %}
{% block title %}
{% load static from staticfiles %}
<script src="{% static 'bill/script.js' %}"></script>
<link rel="stylesheet" href="{% static 'bill/style.css' %}">
<title>Sales Invoice</title>
{% endblock %}
{% block content%}
<invoice>
<div id="invoice">
<invoiceheader>
<!--
<h1>Invoice</h1>
<address>
<p>Jonathan Neal</p>
<p>101 E. Chapman Ave<br>Orange, CA 92866</p>
<p>(800) 555-1234</p>
</address>
<span><img alt="" src="logo.png"><input type="file" accept="image/*"></span>
-->
</invoiceheader>
<invoicearticle>
<!--<h1>Recipient</h1>-->
<code>
<p>Customer code:
<input id="customer-code" ></input></p>
</code>
<address>
<p></p>
<p id="companyname">Some Company</p>
<p id = "companyaddress">c/o Some Guy</p>
</address>
<table class="meta">
<tr>
<th><span>Invoice #</span></th>
<td><span>101138</span></td>
</tr>
<tr>
<th><span>Date</span></th>
<td><span></span></td>
</tr>
<tr>
<th><span>Amount Due</span></th>
<td><span id="prefix">Rs. </span><span>600.00</span></td>
</tr>
</table>
<table class="inventory" id="inventory_table">
<thead>
<tr>
<th colspan="1"><span>Item Code</span></th>
<th colspan="2"><span>Item Name</span></th>
<th colspan="1"><span>Unit Rate</span></th>
<th colspan="1"><span>Discount 1</span></th>
<th colspan="1"><span>Quantity</span></th>
<th colspan="1"><span>Discount 2</span></th>
<th colspan="1"><span>Free Quantity</span></th>
<th colspan="1"><span>VAT Type</span></th>
<th colspan="1"><span>VAT</span></th>
<th colspan="1"><span>Net Rate</span></th>
</tr>
</thead>
<form>
<tbody>
<tr>
<td colspan="1"><a class="cut">-</a><span class="itemcode" contenteditable></span></td>
<td colspan="2"><span contenteditable></span></td>
<td colspan="1"><span contenteditable>150.00</span></td>
<td colspan="1"><span contenteditable></span></td>
<td colspan="1"><span contenteditable>4</span></td>
<td colspan="1"><span contenteditable></span></td>
<td colspan="1"><span contenteditable></span></td>
<td colspan="1"><span contenteditable></span></td>
<td colspan="1"><span contenteditable></span></td>
<td colspan="1"><span contenteditable></span></td>
</tr>
</tbody>
</form>
</table>
<a class="add">+</a>
<table class="balance">
<tr>
<th><span>Total</span></th>
<td><span data-prefix></span><span>600.00</span></td>
</tr>
<tr>
<th><span>Amount Paid</span></th>
<td><span data-prefix></span><span>0.00</span></td>
</tr>
<tr>
<th><span>Balance Due</span></th>
<td><span data-prefix></span><span>600.00</span></td>
</tr>
</table>
</article>
</div>
</invoice>
<script type="text/javascript">
/* url_sellbill = '{% url "billbrain:sellbill" %}' */
csrf_token='{{csrf_token}}'
</script>
{% endblock %}
My related jquery file (only the necessary part):
Generating Table:
function generateTableRow() {
var emptyColumn = document.createElement('tr');
emptyColumn.innerHTML = '<td><a class="cut">-</a><span class="itemcode" contenteditable></span></td>' +
'<td colspan="2"><span contenteditable></span></td>' +
'<td><span contenteditable>100.00</span></td>' +
'<td><span contenteditable></span></td>' +
'<td><span contenteditable></span></td>'+
'<td><span contenteditable></span></td>' +
'<td><span contenteditable></span></td>'+
'<td><span contenteditable></span></td>' +
'<td><span contenteditable></span></td>' +
'<td><span contenteditable></span></td>' ;
return emptyColumn;
}
Adding customer details on user entering customer code:
$( "#customer-code" ).change(function() {
/*alert( "Handler for .change() called." );*/
var input = $("#customer-code").val();
(function() {
$.ajax({
url : "",
type : "POST",
data : { customer_code: input,
datatype: 'customer',
'csrfmiddlewaretoken': csrf_token}, // data sent with the post request
dataType: 'json',
// handle a successful response
success : function(jsondata) {
$('#companyname').html(jsondata['name'])
$('#companyaddress').html(jsondata['address'])
console.log(jsondata); // log the returned json to the console
console.log("success"); // another sanity check
},
});
}());
});
Similarly, for products, on user entering product id, the other details are auto-generated:
$("#inventory_table").on("focus", ".itemcode", function(){
$(this).data("initialText", $(this).html());
/*alert( "On focus for table inventory called." );*/
});
$("#inventory_table").on("blur", ".itemcode", function(){
/*alert( "On blur for table inventory called." );*/
var input = $(this).html();
if ($(this).data("initialText") !== $(this).html()) {
var el = this;
/*valueis='Hi 5'
alert($(this).closest('tr').find('td:nth-child(4) span').html());*/
(function() {
$.ajax({
url : "",
type : "POST",
data : { item_code: input,
datatype: 'item',
'csrfmiddlewaretoken': csrf_token}, // data sent with the post request
dataType: 'json',
// handle a successful response
success : function(jsondata) {
$(el).closest('tr').find('td:nth-child(2) span').html(jsondata['name'])
$(el).closest('tr').find('td:nth-child(2) span').html(jsondata['name'])
$(el).closest('tr').find('td:nth-child(3) span').html(jsondata['sellingprice'])
console.log(jsondata); // log the returned json to the console
alert(jsondata['name']);
console.log("success"); // another sanity check
},
});
}());
}
});
Finally, this is my views.py file's relevant function:
def bill(request):
if request.method == 'POST':
datatype = request.POST.get('datatype')
if (datatype == 'customer'):
customerkey = request.POST.get('customer_code')
response_data = {}
response_data['name'] = Customer.object.get(customer_key__iexact=customerkey).name
response_data['address'] = Customer.object.get(customer_key__iexact=customerkey).address
jsondata = json.dumps(response_data)
return HttpResponse(jsondata)
if (datatype == 'item'):
productkey = request.POST.get('item_code')
response_data = {}
response_data['name'] = Product.object.get(prodkey__iexact=productkey).name
response_data['sellingprice'] = float(Product.object.get(prodkey__iexact=productkey).selling_price)
#response_data['address'] = Product.object.get(prodkey__iexact=productkey).address
jsondata = json.dumps(response_data)
return HttpResponse(jsondata)
return render(request, 'bill/invoicing.html')
You should use Model Forms to output to the user a form to fill and create an object after submit. You can also use some context data if you need to pre-fill some informations in the form.
Another way is to just create an object and flag it as "CANCELLED" if you want to remember some user's tries (what can be useful sometimes) or just remove it (what can cause performance issues if it is very common situation to not fill started bill).