Send notifications with Rabbitmq - django

I'm using django, django rest framework and Rabbitmq,What I want is using Rabbitmq to send notifications to client after user create a comment(like long polling).
I follow the RabbitMQ Tutorials here.
Here is my view.py to create comments:
class CommentList(generics.ListCreateAPIView):
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
queryset = Comment.objects.all()
serializer_class = CommentSerializer
def perform_create(self, serializer):
serializer.save(user=self.request.user)
def create(self, request, *args, **kwargs):
#use pika and rabbitmq to notifity user
connection = pika.BlockingConnection(pika.ConnectionParameters(
host='localhost'))
channel = connection.channel()
channel.queue_declare(queue='task_queue', durable=True)
message = 'Hello, world'
channel.basic_publish(exchange='',
routing_key='task_queue',
body=message,
properties=pika.BasicProperties(
delivery_mode = 2, # make message persistent
))
connection.close()
return super().create(request, args, kwargs)
I create a queue call "task_queue", and sent message "Hello, world" every time I create I comment.
Here is my view.py to receive message:
def get_notifications(request):
connection = pika.BlockingConnection(pika.ConnectionParameters(
host='localhost'))
channel = connection.channel()
channel.queue_declare(queue='task_queue', durable=True)
def callback(ch, method, properties, body):
return HttpResponse("ok")
ch.basic_ack(delivery_tag = method.delivery_tag)
#channel.basic_qos(prefetch_count=1)
channel.basic_consume(callback,
queue='task_queue')
channel.start_consuming()
In client side, I use a jquery ajax function to request data:
function poll() {
var poll_interval=0;
$.ajax({
url: "/sub", //sub calls get_notifications()
type: 'GET',
dataType: 'json',
success: function(data) {
append_circle();
poll_interval=0;
},
error: function () {
poll_interval=1000;
},
complete: function () {
setTimeout(poll, poll_interval);
},
});
}
I can send the message after I create the comment, but my client side doesn't receive any data, in my chrome console, I find this:
sub/ (pending) xhr
What should I do?

For this type of application, websockets are the better option. I suggest you to use websockets instead of long polling. Because long polling is resource hungry process. To use websockets, you can use channels

Related

DRF- Post Request Unitest

I have a post method under View Set. I need to write a unit test case for the method. when I pass param its give None. How should I pass both param and data(payload).
views.py
#action(detail=True, methods=['post'])
def complete_task(self, request, *args, **kwargs):
"""
Method for complete the task
input post request : task_id : str, variable_return:boolean, request data: dict
output Response : gives whether task is completed or not
"""
try:
get_task_id = self.request.query_params.get("task_id")
get_process_variables = request.data
print(get_task_id)
print(get_process_variables)
complete_task = CamundaWriteMixins.complete_task(url=CAMUNDA_URL, task_id=get_task_id,
process_variable_data=get_process_variables)
print("compl", complete_task)
return Response({"task_status": str(complete_task)})
except Exception as error:
return Response(error)
test.py
def test_completed_task(self):
self.client = Client()
url = reverse('complete-task')
data = {"variables": {
"dept_status": {"value": "approved", "type": "String"}}
}
response = self.client.post(url, data=data, params={"task_id": "000c29840512"},
headers={'Content-Type': 'application/json'})
print(response.data)
self.assertTrue(response.data)
I have tried above test case method which is getting request data but I got param None.
Thanks in Advance,.
if you just modify your request a bit and add query param as part of your url then i guess you are good to go.
Example:
response = self.client.post(f'{url}?task_id=000c29840512', data=data,
headers={'Content-Type': 'application/json'})
you can refer the official documentation for the example: https://docs.djangoproject.com/en/4.0/topics/testing/tools/

Django Channels Rest Framework V3 #model_observer message is None

I tried to find anything in the documentation of model observer which could explain under which conditions the model observer is called with message = None, but there was nothing particularly hinting into the right direction. From what I unterstand from the example:
class MyConsumer(GenericAsyncAPIConsumer):
queryset = User.objects.all()
serializer_class = UserSerializer
#model_observer(Comments)
async def comment_activity(
self,
message: CommentSerializer,
observer=None,
subscribing_request_ids=[]
**kwargs
):
await self.send_json(message.data)
#comment_activity.serializer
def comment_activity(self, instance: Comment, action, **kwargs) -> CommentSerializer:
'''This will return the comment serializer'''
return CommentSerializer(instance)
#action()
async def subscribe_to_comment_activity(self, request_id, **kwargs):
await self.comment_activity.subscribe(request_id=request_id)
The message should be the output of the CommentSerializer so the only way a message to become null is, if the serializer would return nothing ?!
To check if this is the case I added logs prints which sadly indicate that the serializer is receiving data and thous there is no reason the message could be None.
Its also occurring only from time to time so any advice about misunderstanding of how the observer works internally will be highly appreciated.
In our implementation:
class StorageConsumer(ListModelMixin, AsyncAPIConsumer):
"""
Async API endpoint that allows Storage updates to be pushed.
"""
async def accept(self, **kwargs):
await super().accept()
await self.model_change_bess.subscribe()
#model_observer(Bess)
async def model_change_bess(self, message, **kwargs):
if message is None:
logger.warning(f"model_change_bess change without a message")
return
message["model"] = kwargs["observer"].model_cls.__name__
await self.send_json(message)
#model_change_bess.serializer
def model_serialize(self, instance, action, **kwargs):
data = BessSerializer(instance).data
# TODO remove once the message is None bug is fixed
logger.info(f"model_serialize bess instance: {instance} data: {data}")
serialized_data = {
"project_id": data["project"],
"simulation_id": data["simulation"],
"data": data,
}
return serialized_data
the corresponding logs in case message is None looks like this:
16/06/2022 13:09:25 [INFO] api_consumers: model_serialize bess instance: Bess object (37227) data: {'id': 37227, 'created': '2022-06-16T13:09:25Z', 'modified': '2022-06-16T13:09:25Z', 'time_stamp': '2022-06-16T13:09:25Z', 'cycles': '0.02541166724274896000000000000000', 'p_bess': '-74.0000', 'soc': '0.45109075850806520000000000000000', 'soh': '0.99514591796314000000000000000000', 'temperature_bess': None, 'time_to_eol': None, 'feedback': True, 'project': 1, 'simulation': 1}
16/06/2022 13:09:25 [WARNING] api_consumers: model_change_bess change without a message

Django REST Framework - Testing POST request

I'm currently struggling to make this current unit-test pass:
def test_markNotifications(self):
request_url = f'Notifications/mark_notifications/'
view = NotificationsViewSet.as_view(actions={'post': 'mark_notifications'})
request = self.factory.post(request_url)
request.POST = {'id_notifs': "1"}
force_authenticate(request, user=self.user)
response = view(request)
self.assertEqual(response.status_code, 200)
Here's the associated view:
#action(detail=False, methods=['POST'])
def mark_notifications(self, request, pk=None):
"""
Put Notifications as already read.
"""
id_notifs = request.POST.get("id_notifs")
if not id_notifs:
return Response("Missing parameters.", status=400)
id_notifs = str(id_notifs).split(",")
print(id_notifs)
for id in id_notifs:
notif = Notification.objects.filter(pk=id).first()
if not notif:
return Response("No existant notification with the given id.", status=400)
notif.isRead = True
notif.save()
return Response("Notifications have been marked as read.", status=200)
The problem is that even though I'm passing "id_notifs" through the request in test, I'm getting None when I do id_notifs = request.POST.get("id_notifs").
It seems that the id_notifs I'm passing in the POST request are neither in the body and the form-data. In this context, I have no idea on how to access them.
Looking forward some help, thanks.

django channels group_send not working suddenly

I have recently converted my existing django project to have a react front end. However , im facing the issue whereby the backend of my django-channels is not working. The issue im having is that the group_send method is not working in my case , as such the consumer does not receive the information generated by a signal in my models. Here is my code :
consumers.py
class NotificationConsumer (AsyncJsonWebsocketConsumer):
async def connect (self):
close_old_connections()
user = self.scope["user"]
if not user.is_anonymous:
await self.accept()
#connects the user to his/her websocket channel
group_name = "notifications_{}".format(str(user))
print(group_name)
await self.channel_layer.group_add(group_name, self.channel_name)
async def disconnect (self, code):
close_old_connections()
user = self.scope["user"]
if not user.is_anonymous:
#Notifications
notifications_group_name = "notifications_{}".format(str(user))
await self.channel_layer.group_discard(notifications_group_name, self.channel_name)
async def user_notification (self, event):
close_old_connections()
print('Notification recieved by consumer')
await self.send_json({
'event': 'notification',
'data': {
'event_type': 'notification',
'notification_pk': event['notification_pk'],
'link': event['link'],
'date_created': event['date_created'],
'object_preview': event['object_preview'],
}
})
print(event)
Models/signals
def ChannelNotification(sender, instance, created, **kwargs):
if created:
channel_layer = get_channel_layer()
print(channel_layer)
group_name = "notifications_{}".format(str(instance.target))
print('inside channel signal')
print(group_name)
async_to_sync(channel_layer.group_send)(
group_name, {
"type": "user_notification",
"notification_pk": instance.pk,
"link": instance.object_url,
"date_created": instance.time.strftime("%Y-%m-%d %H:%M:%S"),
"object_preview": instance.object_preview,
}
)
Whenever an notification object is created in the database , the signal will be sent to my ChannelNotification which receives this information and transmit it to the consumer via group send . It used to work perfectly but im not sure what happened after i converted my project to have react as a frontend.

Q: How to store session data before authentication occured

I'm using django-all auth with GMail login.
There one of my view that will receive HTTP-POST from a Hotspot login page in other server (actualy it's mikrotik hotspot redirect).
I need to read their posted data AFTER social login.
I read https://stackoverflow.com/a/32250781/5901318
Looks like the safest way is to store the POST data in session, and later my view will get it from request.session
but I don't know how to 'store data safely in request.session before the authentication occurs'.
def my_login_required(function):
#https://stackoverflow.com/a/39256685/5901318
def wrapper(request, *args, **kwargs):
decorated_view_func = login_required(request)
if not decorated_view_func.user.is_authenticated:
if request.method == "POST" :
print('my_login_required POST:',request.POST.__dict__)
print('my_login_required ARGS:',args)
print('my_login_required KWARGS:',kwargs)
print('my_login_required SESSION:',request.session.__dict__)
wrapper.__doc__ = function.__doc__
wrapper.__name__ = function.__name__
return wrapper
##receiver(user_logged_in)
#csrf_exempt
#my_login_required
def hotspotlogin(request,*args,**kwargs):
print('HOTSPOTLOGIN')
I tried to access it using requests :
r = requests.post('http://mysite:8000/radius/hotspotlogin/', json={"NAMA": "BINO"}, headers = {'Content-type': 'application/json', 'Accept': 'text/plain'})
but in django shell I only got :
my_login_required POST: {'_encoding': 'utf-8', '_mutable': False}
my_login_required ARGS: ()
my_login_required KWARGS: {}
my_login_required SESSION: {'storage_path': '/opt/djangos/radius03/mysessions/', 'file_prefix': 'sessionid', '_SessionBase__session_key': None, 'accessed': True, 'modified': False, 'serializer': <class 'django.core.signing.JSONSerializer'>, '_session_cache': {}}
Kindly please give me any clue to do that.
Sincerely
-bino-
Got priceless help from a friend, and here is the solution.
def my_login_required(function):
def wrapper(request, *args, **kwargs):
old_data=dict()
try :
old_data['POST'] = dict(request.POST)
except :
old_data['POST'] = dict()
try :
old_data['GET'] = dict(request.GET)
except :
old_data['GET'] = dict()
old_data['method'] = request.method
decorated_view_func = login_required(request)
if not decorated_view_func.user.is_authenticated: #Only if user not authenticated
request.session['old'] = old_data #put old data in request.session['old']
return decorated_view_func(request) # return redirect to signin
return function(request, *args, **kwargs)
wrapper.__doc__ = function.__doc__
wrapper.__name__ = function.__name__
return wrapper
#my_login_required
def testview(request,*args,**kwargs):
print('SESSION DATA:', request.session.get('old')) #take original post/get data from request.session['old']