Django: how to refresh html content on new database entry - django

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')

Related

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 template data update using ajax

I am trying to update my data periodically (10 seconds) on a Django template using ajax scripts. I am relatively new in front-end development.
Using other articles, I am able to do so. But everytime page refreshes, multiple threads for page refreshing are created and update requests are doubled every 10 secods.
Following is my django template snippet:
<body id="page-top">
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr class="table-info">
<th style="text-align:center">Parameter</th>
<th style="text-align:center">A</th>
<th style="text-align:center">B</th>
</tr>
</thead>
<tbody>
{% for para, details in manualData.items %}
<tr>
<th style="text-align:center" scope="row">{{ details.1.name }}</th>
{% for key, entity in details.items %}
<td style="text-align:center">
<font color="white" size="4px">{{ entity.value }}</font>
</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
</body>
I am using ajax script as follows:
<script type="text/javascript">
function refresh() {
var_ref = $.ajax({
success: function (data) {
$('#page-top').html(data);
}
});
}
$(function () {
setInterval('refresh()', 10000);
});
</script>
Conclusively, all I need is:
once the refreshing process is called, new process should not be
created, or else past process to be aborted if new process is to be
defined.
Kindly help me to attain the same.
Thanks in advance
Nakul
You need your ajax call to be synchronomous instead of async, this way you will block the thread until you get the data you require
Can be done by adding the corresponding attribute on the ajax call
When within the industry you would normally use the template system to load something like react, angular or vue which constantly update the DOM without having to make endless polling
Thanks to the answer at post: Refresh page periodically using jquery, ajax and django.
Also, Thank you #Mr-Programs for your suggestion regarding async attribute.
I have got a suitable answer for my query and updated the javascript as follows.
<script>
function worker(){
var url = 'yoururl';
$.ajax({
type:'get',
url: url,
async: false,
success: function(data) {
var dtr = $('.page-top',data);
# not necessarily body div, can use any section required to be updated
$('.page-top').html(dtr);
complete: function() {
setTimeout(worker, 10000);
}
});
}
$(document).ready(function(){
setTimeout(worker, 10000);
});
};
</script>

executing python script in django with arguments from the frontend

I want to manipulate the values associated with the entries of an external mongodb database using a django webapp.
I've created the app and I'm currently displaying all the entries and their associated value in a simple table.
My idea is simply to create a button, that calls a python script with an argument (the id of that entry) and then changes it from false to true. The problem is, this seems like rocket-science, I've been at this for days now and I just can't get it to work as I have little to no proficiency when it comes to Ajax or Jquery, and all relevant examples I can find don't simply seem to pertain very well to my situation despite how basic it would appear, nor fit with Django 2.0+.
I'm using Django 2.1.5
def change_value(idnumber):
db_value = get_db_status_value(idnumber)
if db_value is True:
change_db_entry_status(idnumber, False)
else:
change_db_entry_status(idnumber, True)
my_template.html
<table>
<thead>
<tr>
<th> Status </th>
<th> Button </th>
</tr>
</thead>
<tbody>
{ % for entry in entry_data % }
<tr>
<td> {{ entry.status }} </td>
<td> <button type="button">{{ entry.idnumber }}</button> </td>
</tr>
</tbody>
{% endfor %}
</table>
I simply can't figure out how I can get the change_value function in there that I can create a button for and include an argument ( entry.idnumber ). This seems incredibly difficult, which from what I understand is a design principle, but it seems a shame if I can't even accomplish something as basic as above.
I was hoping someone could explain how I'm actually supposed to go about this? So far, it seems I require AJAX or Jquery (unfortunately, I barely know the basics of this, and what usually trips me up is that the urls.py which exist in both project and application level seems to work a little different in django 2.0+ compared to older versions)
This isn't actually difficult. It's just that you're missing an understanding of the relationship between the client and the backend code.
Once the template is rendered, the user sees it as HTML in their browser. That's it as far as the backend (ie Django) is concerned. The only way to run any further code on the server is to send another request. A request involves the browser contacting the server, which requires a URL and a view in Django.
Now, one way to send that request is via Ajax, but for your purposes that isn't necessary; since you're just learning, it's easier if you make it a simple form. So, your template might look something like this:
{% for entry in entry_data % }
<tr>
<td> {{ entry.status }} </td>
<td><form action="{% url 'change_value' idnumber=entry.idnumber %}" method="POST"> {% csrf_token %} <button type="submit">{{ entry.idnumber }}</button> </form></td>
</tr>
{% endfor %}
</tbody>
Notice how every iteration of the for loop has a separate form, which posts to a specific URL including the ID number.
Next you need a URL:
path('change_value/<int:idnumber>/', views.change_value, name='change_value'),
and update your function to actually be a view, which needs to accept a request and return a response:
def change_value(request, idnumber):
if request.method != "POST":
return HttpResponseNotAllowed()
db_value = get_db_status_value(idnumber)
if db_value:
change_db_entry_status(idnumber, False)
else:
change_db_entry_status(idnumber, True)
return redirect('/')
You always need to redirect after a POST, but you could redirect back to the same URL that rendered my_template in the first place. (Also note I've put in a test to make sure the user is actually sending a POST; you don't want Google to crawl this URL and flip your values for you.)
And that's it, now you have a button that should toggle your value.
The answer from #Daniel is enough for you. But if you want to use ajax so that the page does not have to refresh to change the vaue, you may do something like:
In your template make changes as:
<table>
<thead>
<tr>
<th> Status </th>
<th> Button </th>
</tr>
</thead>
<tbody>
{ % for entry in entry_data % }
<tr>
<td id="status"> {{ entry.status }} </td>
<td> <button type="button" onclick="change_status(this)" id="{{ entry.idnumber }}">{{ entry.idnumber }}</button> </td>
</tr>
</tbody>
{% endfor %}
and a script as:
<script>
function change_status($this){
var request_data = $this.id;
console.log("data: " + request_data);
$.post({
url: "url that leads to your view",
data : { request_data: request_data},
success : function(json) {
document.getElementById('status').innerHTML = "" //insert the data returned from the function
}
})}
</script>

sending html to django template from view

I am new to django so I may be going about this the wrong way (pretty sure I am).
Trying to get a webpage to display data from a postgresql DB in a table showing a status for a list of servers.
This is part of the template
<div class"row"=""><div class="span3" style="background-color:lightyellow; margin-left:20px">
<table class="table table-bordered table-condensed">
<thead>
<tr>
<th>Server</th>
<th>Status</th>
</tr>
</thead>
<tbody>
{{ res }}
</tbody>
</table>
</div></div>
In my view I have this,
message = []
for res in data:
message.append(" <tr>")
message.append(" <td>" + str(res).split("'")[1] + "</td>")
if str(res).split("'")[3] == 'No':
message.append(" <td><FONT COLOR=\"008200\">Available</FONT> </td>")
else:
message.append(" <td><FONT COLOR=\"FF0000\">Down</FONT> </td>")
message.append(" </tr>")
return render_to_response('health.html', {'res':message}, context_instance=RequestContext(request))
If I print that instead of doing the append I get the resulting HTML I would expect.
As it currently is, I don't get anything displayed on the webpage in that table.
I don't expect it to render the list necessarily, but would have thought something should have showed up in the table even if it was incorrect format.
Should this HTML processing be done in the template and not the view?
Yes, it is usually best to do all HTML processing in the template. This way you can separate your database access logic from your display logic and thereby reduce coupling. It also means you can easily re use template.
So you should use the view function to get the appropriate objects and pass them to the template as variables.
Still, you are sort of on the right track. In order for your {{res}} variable to display properly I think you will need to change the template to.
<tbody>
{% for message in res %}
{{ message }}
{% endfor %}
</tbody>
This should iterate over the elements in the res variable which you passed to the template.

Django template variables, RequestContext, and context processors

EDIT
I am building a messaging part of a website but for some reason I just noticed that the function will not actually show the messages in the template. Here is the function and template for the inbox page:
def show_inbox(request):
u = request.user
messages = Message.objects.filter(to_user=u)
context = RequestContext(request,{'messages':messages})
return render_to_response('users/messaging/inbox.html',context_instance=context)
<table class="table-list" cellspacing="0">
<th>Subject</th>
<th>Recieved at</th>
<th>Read</th>
<th>Replied</th>
{% if not messages %}
<tr><td class="left">No messages</td></tr>
{% endif %}
{% for message in messages %}
<tr class="{% if forloop.counter|divisibleby:2 %}even{% else %}odd{% endif %}">
<td class="left largetd">
<img src="{{ message.from_user.profile_pic }}" class="small-pic" />
{{ message.message_subject }}
</td>
<td class="text-center">
<span class="small-text italics">{{ message.message_date_time }}</span>
</td>
<td class="smalltd text-center">{{ message.message_is_read }}</td>
<td class="smalltd text-center"></td>
</tr>
{% endfor %}
</table>
You have checked that the messages variable actually has messages before you call render_to_response() right? Otherwise, view-source on the resulting HTML and see if anything is there. You are missing the <tr> tag around your header cells.
Also, try calling render_to_response a little differently.
return render_to_response('users/messaging/inbox.html', {'messages': messages})
By default, the template will be rendered using a Context instance that is filled with values from the provided dictionary. Therefore you can call it in this simpler form. You aren't using any request variables in the view templates so you don't need the RequestContext, which uses context processors to provide your template with additional information pulled from the request.
#fceruti is correct that you had a collision with django.contrib.messages. When you used RequestContext, Django cycled through your TEMPLATE_CONTEXT_PROCESSORS (declared in the settings.py file) to provide more variables for your template. Specifically 'django.contrib.messages.context_processors.messages' added a messages variable, which overwrote your own. Change the name of your variable and you'd be fine.
Each processor is applied in order.
That means, if one processor adds a
variable to the context and a second
processor adds a variable with the
same name, the second will override
the first.