This is my class:
class MyClass(models.Model):
name = models.CharField(max_length=128, unique=True)
members = models.ManyToManyField("Employee")
def save(self, *args, **kwargs):
super().save(*args,**kwargs)
channel_layer = get_channel_layer()
data = {"current_obj":self.name}
async_to_sync(channel_layer.group_send)(
"test_consumer_group_1",{
'type':'send_notification',
'value':json.dumps(data)
}
)
This is my consumer file.
class MySyncGroupConsumer(SyncConsumer):
def websocket_connect(self,event):
print("Web socket connected", event)
self.send({
'type':'websocket.accept'
})
print("Channel layer ",self.channel_layer)
print("Channel name", self.channel_name)
async_to_sync(self.channel_layer.group_add)("test_consumer_group_1", self.channel_name)
def websocket_receive(self,event):
print("Web socket recieved",event)
print(event["text"])
sync_to_sync(self.channel_layer.group_send)("test_consumer_group_1", {
'type':'chat.message',
'message':event['text']
})
#event handler, which is sending data to client
def chat_message(self,event):
print('Event...', event)
print('Event...', event["message"])
self.send({
"type":"websocket.send",
"text":event['message']
})
def send_notification(self, event):
print("send_notification called")
print('Event...', event['value'])
# Get the current user from the channel layer
user = self.scope['user']
print(user)
# Check if the user is authenticated and is a member of the team
if user.is_authenticated and user.employee in self.team.members.all():
self.send({
"type":"websocket.send",
"text":event['value']
})
def websocket_disconnect(self,event):
print("Web socket disconnect", event)
async_to_sync(self.channel_layer.group_discard)("test_consumer_group_1", self.channel_name)
raise StopConsumer()
This is my asgi.py file:
import os
import app.routing
from django.core.asgi import get_asgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "project.settings")
application = get_asgi_application()
application = ProtocolTypeRouter({
'http': get_asgi_application(),
"websocket": AuthMiddlewareStack(
URLRouter(
app.routing.websocket_urlpatterns
)
),
})
When an object of MyClass is created, send_notification is called.
Now I want to ask two things:
Inside send_notification, user = self.scope['user'] always returns anonymous user, even if user is authenticated.
I want to send notification to particular users onlyi.e. employees who are added in MyClass object and are logged in, instead of broadcasting the message
How to do that?
I have done the below post_save signal in my project.
from django.db.models.signals import post_save
from django.contrib.auth.models import User
# CORE - SIGNALS
# Core Signals will operate based on post
def after_save_handler_attr_audit_obj(sender, **kwargs):
print User.get_profile()
if hasattr(kwargs['instance'], 'audit_obj'):
if kwargs['created']:
kwargs['instance'].audit_obj.create(operation="INSERT", operation_by=**USER.ID**).save()
else:
kwargs['instance'].audit_obj.create(operation="UPDATE").save()
# Connect the handler with the post save signal - Django 1.2
post_save.connect(after_save_handler_attr_audit_obj, dispatch_uid="core.models.audit.new")
The operation_by column, I want to get the user_id and store it. Any idea how can do that?
Can't be done. The current user is only available via the request, which is not available when using purely model functionality. Access the user in the view somehow.
I was able to do it by inspecting the stack and looking for the view then looking at the local variables for the view to get the request. It feels like a bit of a hack, but it worked.
import inspect, os
#receiver(post_save, sender=MyModel)
def get_user_in_signal(sender, **kwargs):
for entry in reversed(inspect.stack()):
if os.path.dirname(__file__) + '/views.py' == entry[1]:
try:
user = entry[0].f_locals['request'].user
except:
user = None
break
if user:
# do stuff with the user variable
Ignacio is right. Django's model signals are intended to notify other system components about events associated with instances and their respected data, so I guess it's valid that you cannot, say, access request data from a model post_save signal, unless that request data was stored on or associated with the instance.
I guess there are lots of ways to handle it, ranging from worse to better, but I'd say this is a prime example for creating class-based/function-based generic views that will automatically handle this for you.
Have your views that inherit from CreateView, UpdateView or DeleteView additionally inherit from your AuditMixin class if they handle verbs that operate on models that need to be audited. The AuditMixin can then hook into the views that successfully create\update\delete objects and create an entry in the database.
Makes perfect sense, very clean, easily pluggable and gives birth to happy ponies. Flipside? You'll either have to be on the soon-to-be-released Django 1.3 release or you'll have to spend some time fiddlebending the function-based generic views and providing new ones for each auditing operation.
You can do that with the help of middleware. Create get_request.py in your app. Then
from threading import current_thread
from django.utils.deprecation import MiddlewareMixin
_requests = {}
def current_request():
return _requests.get(current_thread().ident, None)
class RequestMiddleware(MiddlewareMixin):
def process_request(self, request):
_requests[current_thread().ident] = request
def process_response(self, request, response):
# when response is ready, request should be flushed
_requests.pop(current_thread().ident, None)
return response
def process_exception(self, request, exception):
# if an exception has happened, request should be flushed too
_requests.pop(current_thread().ident, None)
Then add this middleware to your settings:
MIDDLEWARE = [
....
'<your_app>.get_request.RequestMiddleware',
]
Then add import to your signals:
from django.db.models.signals import post_save
from django.contrib.auth.models import User
from <your_app>.get_request import current_request
# CORE - SIGNALS
# Core Signals will operate based on post
def after_save_handler_attr_audit_obj(sender, **kwargs):
print(Current User, current_request().user)
print User.get_profile()
if hasattr(kwargs['instance'], 'audit_obj'):
if kwargs['created']:
kwargs['instance'].audit_obj.create(operation="INSERT", operation_by=**USER.ID**).save()
else:
kwargs['instance'].audit_obj.create(operation="UPDATE").save()
# Connect the handler with the post save signal - Django 1.2
post_save.connect(after_save_handler_attr_audit_obj, dispatch_uid="core.models.audit.new")
Why not adding a middleware with something like this :
class RequestMiddleware(object):
thread_local = threading.local()
def process_request(self, request):
RequestMiddleware.thread_local.current_user = request.user
and later in your code (specially in a signal in that topic) :
thread_local = RequestMiddleware.thread_local
if hasattr(thread_local, 'current_user'):
user = thread_local.current_user
else:
user = None
For traceability add two attributes to your Model(created_by and updated_by), in "updated_by" save the last user who modified the record. Then in your signal you have the user:
models.py:
class Question(models.Model):
question_text = models.CharField(max_length=200)
pub_date = models.DateTimeField('date published')
created_by = models. (max_length=100)
updated_by = models. (max_length=100)
views.py
p = Question.objects.get(pk=1)
p.question_text = 'some new text'
p.updated_by = request.user
p.save()
signals.py
#receiver(pre_save, sender=Question)
def do_something(sender, instance, **kwargs):
try:
obj = Question.objects.get(pk=instance.pk)
except sender.DoesNotExist:
pass
else:
if not obj.user == instance.user: # Field has changed
# do something
print('change: user, old=%s new=%s' % (obj.user, instance.user))
You could also use django-reversion for this purpose, e.g.
from reversion.signals import post_revision_commit
import reversion
#receiver(post_save)
def post_revision_commit(sender, **kwargs):
if reversion.is_active():
print(reversion.get_user())
Read more on their API https://django-reversion.readthedocs.io/en/stable/api.html#revision-api
You can do a small hack by overriding you model save() method and setting the user on the saved instance as additional parameter. To get the user I used get_current_authenticated_user() from django_currentuser.middleware.ThreadLocalUserMiddleware (see https://pypi.org/project/django-currentuser/).
In your models.py:
from django_currentuser.middleware import get_current_authenticated_user
class YourModel(models.Model):
...
...
def save(self, *args, **kwargs):
# Hack to pass the user to post save signal.
self.current_authenticated_user = get_current_authenticated_user()
super(YourModel, self).save(*args, **kwargs)
In your signals.py:
#receiver(post_save, sender=YourModel)
def your_model_saved(sender, instance, **kwargs):
user = getattr(instance, 'current_authenticated_user', None)
PS: Don't forget to add 'django_currentuser.middleware.ThreadLocalUserMiddleware' to your MIDDLEWARE_CLASSES.
I imagine you would have figured this out, but I had the same problem and I realised that all the instances I create had a reference to the user that creates them (which is what you are looking for)
it's possible i guess.
in models.py
class _M(models.Model):
user = models.ForeignKey(...)
in views.py
def _f(request):
_M.objects.create(user=request.user)
in signals.py
#receiver(post_save, sender=_M)
def _p(sender, instance, created, **kwargs):
user = instance.user
No ?
Request object can be obtained from frame record by inspecting.
import inspect
request = [
frame_record[0].f_locals["request"]
for frame_record in inspect.stack()
if frame_record[3] == "get_response"
][0]
def get_requested_user():
import inspect
for frame_record in inspect.stack():
if frame_record[3] == 'get_response':
request = frame_record[0].f_locals['request']
return request.user
else:
return None
context_processors.py
from django.core.cache import cache
def global_variables(request):
cache.set('user', request.user)
----------------------------------
in you model
from django.db.models.signals import pre_delete
from django.dispatch import receiver
from django.core.cache import cache
from news.models import News
#receiver(pre_delete, sender=News)
def news_delete(sender, instance, **kwargs):
user = cache.get('user')
in settings.py
TEMPLATE_CONTEXT_PROCESSORS = (
'web.context_processors.global_variables',
)
I am using django-channels 2.1.2. Since it had a drastic change from 1.x to 2.x, I would like to know the way to enforce login on the consumer class.
So far:
from channels.generic.websocket import AsyncWebsocketConsumer
import json
from channels.consumer import SyncConsumer
from doclet.models import *
class ChatConsumer(AsyncWebsocketConsumer):
async def connect(self):
to_room_id = self.scope['url_route']['kwargs']['to_room_id']
self.user = self.scope["user"]
if self.user.is_authenticated:
if self.user.rooms.filter(pk=int(to_room_id)).exists():
self.to_room = Room.objects.get(pk=int(to_room_id))
self.room_name = 'room_%s' %self.to_room.id
else:
self.close() #do something to create room for the user
# Join room group
await self.channel_layer.group_add(
self.room_name,
self.channel_name
)
await self.accept()
You need to set this up in your Application stack.
application = ProtocolTypeRouter({
"websocket": AuthMiddlewareStack(
URLRouter([
url(r"^front(end)/$", consumers.AsyncChatConsumer),
])
),
})
And then in your connect method, you can access the user from self.scope['user'] feel free to save this onto your consumer instance. By trying to read the user from the scope you will ensure a user object was resolved.
you can do this before calling self.accept() in the connect method if you so wish to ensure the WebSocket connection is not accepted if the user auth failed.
I'm trying to send a message to all users with an open websocket connection at a specific URL each time a model is saved. I'm using the Channels community project knocker as a reference but in doing so I have to modify it to work with Channels 2.0.
Using signals fired on a model's post_save knocker sends a notification to the Group.
In Channels 2.0, Groups are handled differently so this line Group('myGroup').send({'text': json.dumps(knock)}) in the send_knock method isn't working. Is it possible to modify this line to work with the consumer below?
class WeightConsumer(WebsocketConsumer):
def connect(self):
self.group_name = 'weight'
# Join group
async_to_sync(self.channel_layer.group_add)(
self.group_name,
self.channel_name
)
self.accept()
def disconnect(self, close_code):
# Leave group
async_to_sync(self.channel_layer.group_discard)(
self.group_name,
self.channel_name
)
def receive(self, text_data):
pass
Just had to make use of get_channel_layer()
def send_knock(self, created=False):
"""
Send the knock in the associated channels Group
"""
channel_layer = get_channel_layer()
group_name = 'weight'
weight = '%s' % self.get_knocker_weight()
# Send message to group
async_to_sync(channel_layer.group_send)(
group_name,
{
'type': 'weight_message',
'weight': weight
}
)
Then add another method to the consumer.
class WeightConsumer(WebsocketConsumer):
...
def receive(self, text_data):
pass
def weight_message(self, event):
weight = event['weight']
# Send message to websocket
self.send(text_data=json.dumps({
'weight': weight
}))
I am having a simple setup with testing channels 2.0.
I have a routed consumer with three methods:
consumers.py
from channels.generic.websocket import JsonWebsocketConsumer
from datetime import datetime
#
class Feedback_001_Consumer(JsonWebsocketConsumer):
def connect(self,text_data=None, bytes_data=None):
self.accept()
self.send_json("Connection for Feedback 1 ready.")
#
def receive(self,text_data=None):
#self.accept()
self.send_json("{}".format(datetime.now()))
print(datetime.now())
#
def disconnect(self,close_code):
pass
and my js looks like this:
const webSocketBridge = new channels.WebSocketBridge();
#...
webSocketBridge.connect('my_route/etc...');
#...
console.log(webSocketBridge.send(''));
While the datetime is printing in the console, I cannot get the one sent by self.send_json in the receive method of the consumer. What would be the proper way to do this?
consumer.py
from channels.generic.websocket import JsonWebsocketConsumer
from datetime import datetime
class Feedback_001_Consumer(JsonWebsocketConsumer):
def connect(self):
self.accept()
def receive_json(self, content, **kwargs):
if content['command'] == 'time':
time = datetime.now()
self.send_json({'time': time})
print(time)
def disconnect(self,close_code):
pass
your.js
webSocketBridge.socket.onopen = function(){
console.log('connected to server');
// do something else. eg
webSocketBridge.send({'command': 'time'})
};
webSocketBridge.listen(function(message, stream){
var data = message.time
console.log(data)
});