19

I have two different pages, one (A) that displays data taken from a model object, and one (B) that changes its fields. I would like that when the post data is sent from B to the server, the server changes the values in A. What is the best way to do it?

This example could work for me but it's in PHP... there is a way to replicate it whit Python? https://www.w3schools.com/html/html5_serversentevents.asp

chtz
  • 17,329
  • 4
  • 26
  • 56
Lorenzo Fiamingo
  • 3,251
  • 2
  • 17
  • 35

3 Answers3

33

This is working example from w3schools in Django:

template

<!DOCTYPE html>
<html>
<body>

<h1>Getting server updates</h1>
<div id="result"></div>

<script>
if(typeof(EventSource) !== "undefined") {
  var source = new EventSource("stream/");
  source.onmessage = function(event) {
    document.getElementById("result").innerHTML += event.data + "<br>";
  };
} else {
  document.getElementById("result").innerHTML = "Sorry, your browser does not support server-sent events...";
}
</script>

</body>
</html>

views

import datetime
import time
from django.http import StreamingHttpResponse

def stream(request):
    def event_stream():
        while True:
            time.sleep(3)
            yield 'data: The server time is: %s\n\n' % datetime.datetime.now()
    return StreamingHttpResponse(event_stream(), content_type='text/event-stream')

urls

urlpatterns = [
    path('stream/', views.stream, name='stream')
]

Update:

If you want to manage your notifications you can create the model like:

from django.db import models

class Notification(models.Model):
    text = models.CharField(max_length=200)
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    sent = models.BooleanField(default=False)

Then create the view that is looking for the first unsent notification and sends it:

@login_required
def stream(request):
    def event_stream():
        while True:
            time.sleep(3)
            notification = Notification.objects.filter(
                sent=False, user=request.user
            ).first()

            text = ''

            if notification:
                text = notification.text
                notification.sent = True
                notification.save()

            yield 'data: %s\n\n' % text

    return StreamingHttpResponse(event_stream(), content_type='text/event-stream')

And the send_notification function that creates an entry in the Notification model (just call this function from anywhere in your code):

def send_notification(user, text):
    Notification.objects.create(
        user=user, text=text
    )
Ivan
  • 2,469
  • 1
  • 12
  • 24
  • It kind of works but the behaviour is not the one I expected. Page is constantly updated with new server time. What if I want to send a specific (just one time) event to my client? – David Dahan Jun 14 '19 at 15:45
  • 1
    Hi @Ivan thanks for your update. I posted my answer after understanding SSE won't work as expected with Django because it does not support persistent connection :( – David Dahan Jun 18 '19 at 00:40
  • It worth mentioning that `StreamingHttpResponse` is expensive and should be avoided. – adnanmuttaleb Jun 10 '20 at 08:20
  • 4
    Are these comments about performance and persistent connections still relevant now that Django 3.1 supports async views? The [documentation](https://docs.djangoproject.com/en/3.1/topics/async/#async-views:~:text=The%20main%20benefits%20are%20the%20ability,long%2Dpolling%2C%20and%20other%20exciting%20response%20types) says "The main benefits are the ability to service hundreds of connections without using Python threads. This allows you to use slow streaming, long-polling, and other exciting response types" – Benoit Blanchon Aug 08 '20 at 13:17
15

After reading this, I think I understood the whole thing (please comment if I’m wrong).



Django does NOT natively support keep-alive connections. 
This means, when the client gets the message from the server, the connection is immediately closed after (like any classic HTTP request/response cycle).


What’s different with text/event-stream request, is that the client automatically tries to reconnect to the server every second (the length can be changed with a retry parameter).



Unfortunately, it seems using SSE in that case has no interest since it has the same con’s that polling (ie. a request/response cycle occurs each X seconds).



As expected and mentioned in other answers, I would need django-channels to create a persistent connection that prevent HTTP request/response overheads and ensure the message is sent immediately.


David Dahan
  • 10,576
  • 11
  • 64
  • 137
3

As mentioned in other answers, you will need to use Django Channels to properly handle asynchronous communication without tying up threads.

For an example, see the django-eventstream library which uses Channels to implement SSE.

jkarneges
  • 346
  • 2
  • 3