Reputation: 4244
Triying to use signals to send through websockets the last record that was saved using .save(). What do I put in data
?
#models.py
from django.db import models
from django.db.models.signals import post_save
from channels.layers import get_channel_layer
from asgiref.sync import async_to_sync
class DataModel(models.Model):
time = models.DateTimeField()
value = models.FloatField()
def __str__(self):
return str(self.time)
def save_post(sender, instance, **kwargs):
channel_layer = get_channel_layer()
async_to_sync(channel_layer.group_send)(
"echo_group",
{"type": "on.message", "data": data},
)
post_save.connect(save_post, sender=DataModel)
I guess I could just fetch the record with the highest index and send it, but wondering if there's a more elegant solution.
Upvotes: 0
Views: 1558
Reputation:
To know what's last modified, you would need a last modified field. This is a very well-known pattern and Django uses the auto_now=True
argument to DateTimeFields to help with it. These fields are not editable and use Database triggers wherever possible to achieve the result (which is why they're not editable).
As said this is common, so I usually use a base model:
class AuditableBase(models.Model):
"""
Base class that adds created_at and last_modified fields for audit purposes.
"""
created_at = models.DateTimeField(auto_now_add=True)
last_modified = models.DateTimeField(auto_now=True)
class Meta:
abstract = True
Now it's trivial to get the last modified record and hydrate a channel with the last modified, irrespective of whether save occurred:
class DataModel(AuditableBase):
time = models.DateTimeField()
value = models.FloatField()
def __str__(self):
return str(self.time)
# On channel start:
latest = DataModel.objects.latest('last_modified')
However, if we use the post save signal, we already have the object that was just saved, in the "instance" argument. To convert it to json easily, we can use model_to_dict and DjangoJSONEncoder to deal with most problems:
from django.forms.models import model_to_dict
from django.core.serializers import DjangoJSONEncoder
import json
def save_post(sender, instance, **kwargs):
channel_layer = get_channel_layer()
data = model_to_dict(instance)
json_data = json.dumps(data, cls=DjangoJSONEncoder)
async_to_sync(channel_layer.group_send)(
"echo_group",
{"type": "on.message", "data": json_data},
)
Model_to_dict will convert a model to a dictionary and can be restricted using fields=
(explicit include) or exclude=
(explicit exclude). DjangoJSONEncoder deals with temporal data, that is not supported by json's default encoder.
Upvotes: 2
Reputation: 7705
I think instance.__dict__
is what you want. It converts all the model attributes to dictionary key-value pairs
So you could do something like
def save_post(sender, instance, **kwargs):
channel_layer = get_channel_layer()
async_to_sync(channel_layer.group_send)(
"echo_group",
{"type": "on.message", "data": instance.__dict__},
)
I skipped mentioning that instance
is the actual object that has just been saved and triggered the post_save
signal. So in the given question instance
will be something like instance = DataModel.objects.create(**kwargs)
Upvotes: 2