I have a model and in a function this model is updating. I want to display this model’s data dynamically. So, new values should display without refreshing the page. How can I do it?
models.py
class MyLongProcess(models.Model): active_uuid = models.UUIDField('Active process', null=True, blank=True) name = models.CharField('Name', max_length=255) current_step = models.IntegerField('Current step', default=0) total = models.IntegerField('Total', default=0) @property def percentage_sending(self): # or it can be computed by filtering elements processed in celery with complete status return int((current_step / total) * 100)
views.py
def setup_wizard(request): process = MyLongProcess.objects.create(active_uuid=uuid.uuid4(), name=name, total=100) functions.myClass(..., process=process) .... return render(request, 'setup_wizard.html', context)
functions.py
class myClass(): def __init__(self, ..., process): self.download_all(..., process=process) @app.task(bind=TRUE) def download_all(self, ..., process): .... for s in scans: .... process.current_step += 1 process.save() ...
setup_wizard.html
<div class="progress-bar" role="progressbar" style="width: {{ my_model_object.percentage_sending }}%;" aria-valuenow="{{ my_model_object.percentage_sending }}" aria-valuemin="0" aria-valuemax="100">{{ my_model_object.percentage_sending }}% </div>
All my function works fine. When I looking the MyLongProcess
from Django admin and refresh the page, values are updating. Just I want to display it in frontend without refreshing.
Advertisement
Answer
this is not what Django was meant for. Essentially, it renders static HTML content and that’s it. what you would like to do in a time-consuming function like that is to first render some initial content (let’s say with a progress of 0) and then notify the frontend asynchronously. This can be acomplished in two ways. First way is definetely easier to implement but it’s slightly harder on resources – which is polling. essentially you create a special endpoint in your urls.py which, when accessed, would give you the percentage of the job you’re after. then you could have a javascript code that would have setInterval( (js-code), 1000)
to refresh the progress each second. Just to give you an overview how this could be implemented:
document.addEventListener("DOMContentLoaded", function(event) { var myElement = document.getElementById('my-element'); var task_id = myElement.getAttribute('data-task_id'); setInterval(function() { var progressReq = new XMLHttpRequest(); progressReq.addEventListener("load", function() { myElement.setAttribute('aria-valuenow', this.responseText); }); progressReq.open("GET", "/progress-query/?task_id=" + task_id); progressReq.send(); }, 1000); });
the HTTP request can really look way nicer with the help of jQuery but I wanted to just give you an idea of how this could look.
In order for this to work you’d have to amend the html template to include the id like so:
<div ... data-task_id="some-task-id" />
if you’d want to post model-related value’s here keep in mind to hash them so that you don’t accidentally post raw database PK or something like that.
The second approach which is slightly more complex to implement, would be to use websockets. this means that when the frontend starts, it would listen to events that are sent to it via the backend websocket implementation. This way, you could update the progress almost in real time, rather than each second. In both of the approaches, the javascript code would have to access the element you want to update – either via var element = document.getElementById( ... )
(which means you have to have an id to begin with) or with the help of jQuery. then you can update the value like so: element.setAttribute('aria-valuenow', 10)
. Of course with the help of the libraries like jQuery your code can look way nicer than this.
p.s. don’t forget (if you go via polling approach) to break setInterval loop once the progress reaches 100 🙂