0

What is the most lightweight way to notify clients of changes to a model table they are viewing?

I've used Django Rest Framework to set up an API that serves a templated table of items to clients, and allows them to change buyers on the fly.

Currently, I use a recurring jQuery AJAX request with a setTimeout for 2 seconds. This sends a ton of requests and data even when there are no changes, and the webpage size keeps growing.

I've had to disable caching as some users might have IE11.

I started looking for a way to push the updates to the clients and started exploring Django Channels and Server-Sent-Events.

Django Channels

  • Built the demo chat app
  • Very fast
  • Websockets are supported by all my target browsers
  • Seems like overkill for what I'm trying to achieve.
  • Lots of configuration
  • Requires Redis or some other datastore
  • I don't really need the two way communication
  • My app is hosted on Pythonanywhere, which doesn't allow ASGI and doesn't seem to have any plans to do so (1, 2).

Server Sent Events

Current, AJAX based set-up:

models.py

...
...
class Buyer(models.Model):
  name = models.CharField(unique=True, max_length = 20)

class Item(models.Model):
  name = models.CharField(unique=True, max_length = 50)
  active = models.BooleanField(default=True)
  bought_by = models.ForeignKey(Buyer, null=True, blank=True, to_field="name",)

views.py

...
...
class ItemViewSet(viewsets.ModelViewSet):
  queryset = models.Item.objects.select_related("bought_by")
  serializer_class= serializers.ItemSerializer
  filterset_fields = ("bought_by")
  renderer_classes = [renderers.JSONRenderer, renderers.BrowsableAPIRenderer, renderers.TemplateHTMLRenderer]

  def list(self, request, *args, **kwargs):
    queryset = self.filter_queryset(self.get_queryset())

    if request.accepted_renderer.format == "html":
      items = list()

      for item in queryset:
        items.append({"serializer": self.get_serializer(item), "item": item})

      return Response(
        {
          "items_info": items,
          "style": {"template_pack": "rest_framework/inline/"},
        },
        template_name="myapp/items_list.html",
      )
    else:
     page = self.paginate_queryset(queryset)
     if page is not None:
       serializer = self.get_serializer(page, many=True)
       return self.get_paginated_response(serializer.data)

      serializer = self.get_serializer(queryset, many=True)

    return Response(serializer.data)
...
...

(list method was modified to make each item editable by the clients)

handler.js

...
...
$.ajaxSetup({
  cache: false
});

var tableUpdater = null;
var updateRequest = null;

// helper that can be called to cancel active timer/ajax in the 
// case of interaction with buttons/selects on the page or 
// in the case of a new request
function stopUpdate() {
  if (tableUpdater || updateRequest) {
    clearTimeout(tableUpdater);
    updateRequest.abort();
  }
}

// Update data table
function tableUpdate() {
  stopUpdate();

  updateRequest = $.ajax({
    type: "GET",
    url: "myapp/items/?format=html",
    success: function(data) {
      $("#activeRequests").html(data);

      // schedule another AJAX request
      tableUpdater = setTimeout(tableUpdate, 2000);
    }
  });
}
...
...
silverstripes
  • 31
  • 1
  • 8

1 Answers1

0

I had a go with Django Channels, but then switched to pusher.com That has worked very well for me, so I think it would be worth looking at for you.

Mark Bailey
  • 1,617
  • 1
  • 7
  • 13