Chat application: Using Django with sockjs-tornado and redis - django

I am trying to write chat application, here are some desing thoughts:
Django doing main website serving.
Sockjs-tornado serves chat and between them I would like to setup redis. When sockjs-tornado receives message in chat, it processes it and sends to other connected clients and also puts it in redis, so Django can save this message in persistent database. I know I should use pubsub functionallity of redis. I know how to setup it in tornado (brukva library), but how can I subscribe to redis' channel in django? So I can receive message in django and save it to database? Do you have any ideas?

i'm not know how sockjs use, but this example illustrate how to save in django model
#in tornado
import brukva
import tornado.web
import tornado.websocket
c = brukva.Client()
c.connect()
class MessagesHandler(tornado.websoket.WebsocketHandler):
def open(self):
#....
def handle_request(self, response):
pass
def on_message(self, message):
#....
c.publish(self.channel, json.dumps({
"sender": self.sender_name,
"text": message,
}))
http_client = tornado.httpclient.AsyncHTTPClient()
request = tornado.httpclient.HTTPRequest(
'/to/django_project/url/chat_save_base/',
method="POST",
body=urllib.urlencode({
"message": message.encode("utf-8"),
"sender": self.sender.name,
})
http_client.fetch(request, self.handle_request)
#in django_url
url(r'/to/django_project/url/chat_save_base/','app.my_view')
#my_view
from django.views.decorators.csrf import csrf_exempt
from messages.models import Message
#csrf_exempt
def my_view(request):
message_text = request.POST.get("message")
sender = User.objects.get(id=request.POST.get("sender_id"))
message = Message()
message.text = message_text
message.sender_id = sender_id
message.save()
source for additional info:
http://habrahabr.ru/post/160123/

Related

how to make thunk HTTP POST request to Django rest api

I'm pretty new to this, so I will show you the error first and then display my thought process.
I am attempting to use a thunk (Http request in redux) to make a post request to my personal rest api in django. (so it can run cURL commands and use a authorization code to gain an access token and get user data from an API)
when I make this request, the network returns a 201 status to stay it worked, while my console logs the dispatch as rejected, and gives the error, "message: "Unexpected end of JSON input". In this message, payload is undefined, and my payload is living in meta under arg.
After much reserach, it looks like I either need to JSON.strigify something on my front end, OR that django is receiving a null object and not knowing what to do with it. I believe it is the latter, and not sure how to proceed. Here is my code:
FRONTEND:
slice.py
import {createAsyncThunk} from '#reduxjs/toolkit'
export const postRequest= createAsyncThunk('slice/postRequest',
async postData => {
const response=
await client.post('url', {slice:postData})
return response.slice
}
)
component.py
import {useDispatch} from 'react-redux'
import {postRequest} from './slice'
const Component = () => {
const dispatch=useDispatch()
const authCode= 'samplecode'
const data={authCode:`${authCode}`, userData:{}}
dispatch(postRequest(JSON.stringify(data))
return(null)
}
export default Component
BACKEND:
models.py
from django.db import models
class UserData(models.Model):
authCode=models.CharField(max_length=100, default='')
userData= models.JSONField
serializers.py
from rest_framework import serializers
from .models import *
class UserDataSerializer(serializers.ModelSerializer):
class meta:
model=UserData
fields= ('authCode', 'userData')
views.py
from .models import *
from .serializers import *
from django.views.decorators.csrf import csrf_excempt
from rest_framework.decorators import api.view
from rest_framework.resposne import Response
from rest_framework import status
#api_view(['POST'])
#csrf_excempt
define restapi(request)
if request.method == "POST":
serializer = UserDataSerializer(data = request.data
if serializer.is_valid():
serializer.save
return Response(status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_Request
I also tried it where it would return a status of 204, and without JSON.stringify.
I noticed that when I made a successful post request to a json server, it would make the request with the payload in args, and if it succeeded, the payload would get moved to payload, but it is my guess that my current error is happening because the payload is blank at the start of the thunk dispatch. Really I am not sure though
I solved this problem, it was stupid.
I was using a homemade client and I am not experienced enough to do that. I just got axios and used that as the client and it worked fine. I also simplified the request in the component to not have JSON.stringify and not have the backticks.

Show message in Django without needing a request

I have a view which uses threading e.g
from .utils import my_heavy_function
def my_view(request):
if request.method == "POST":
form = my_model_form()
if form.is_valid():
#create thread
thr = threading.Thread(target=my_heavy_function,args=(form,))
thr.start()
messages.success(request, "Processing ...")
return redirect("my_template")
else:
form = my_model_form()
return render(request, "my_app/my_template.html")
and it works like a charm; it process the my_heavy_function in the background while making the user able to continue using the webpage. I just need a way to show a message when my_heavy_function is done.
Is there a way to make Django display a message even when a new-request is not called but based on some other condition? E.g on a page when a file is done loading etc.
(I have on purposed not used Django-Q, Celery or back-ground-tasks for this threading since I find it being overkill)
It's not very clear what do you want to do. If you want to notify user without it doing an HTTP request, your only bet in web technologies is to setup a WebSocket so you can push things from server.
If it's ok for user to get the message next time they open a page, you can put something in the DB when your heavy task is done. And on each request you check if you have something in DB, you'll do messages.add_message and remove that row from DB.
If you want to show the message with a result of an action, that has finished after a response to the request has been returned, you have this option to show it synchronically.
The way it works is that next time the user requests a resource, this implementation will check for any messages for this user, that has been created meanwhile (eg. after your async code finished execution and added a message.
This solution uses a superstructure to the synchronous Django messaging framework with a simple Memcache container.
Install memcached as your cache backend.
docker run -p 11211:11211 --name local-memcache -d memcached memcached -m 64
Then go and pip install django-async-messages django-pymemcache
Add this to your middleware in settings.py file:
'async_messages.middleware.AsyncMiddleware'
Ensure it comes after 'django.contrib.messages.middleware.MessageMiddleware'
Then add this to your settings.py file:
CACHES = {
'default': {
'BACKEND': 'djpymemcache.backend.PyMemcacheCache',
'LOCATION': [
'127.0.0.1:11211',
],
},
}
Where you want to use this async messaging, go from async_messages import message_user
Substitute your classical messages.add_message(... for
message_user(request.user, "your message") where first agrument is a user object
go to the django-async-messages package because it is slightly obsolete and needs a small update.
Locate the middleware.py file in the package (likely in venv/Lib/site-packages/async_messages/middleware.py)
Change it from this
from django.contrib import messages
from async_messages import get_messages
class AsyncMiddleware(object):
def process_response(self, request, response):
"""
Check for messages for this user and, if it exists,
call the messages API with it
"""
if hasattr(request, "session") and hasattr(request, "user") and request.user.is_authenticated():
msgs = get_messages(request.user)
if msgs:
for msg, level in msgs:
messages.add_message(request, level, msg)
return response
to this:
from django.contrib import messages
from async_messages import get_messages
from django.utils.deprecation import MiddlewareMixin
class AsyncMiddleware(MiddlewareMixin):
def process_response(self, request, response):
"""
Check for messages for this user and, if it exists,
call the messages API with it
"""
if hasattr(request, "session") and hasattr(request, "user") and request.user.is_authenticated:
msgs = get_messages(request.user)
if msgs:
for msg, level in msgs:
messages.add_message(request, level, msg)
return response
That is it - you have asynchronous messaging!
Ps - because you just edited a package which would change when deploying - I exctracted the package and with above changes I included it directly in my project structure.

Understanding django channels - QueryAuthMiddleware

How to write custom authentication of user, that connects to chat over ws:// protocol? This user is on the other side of Django app, he is the mobile user, connecting websocket via ws:// from mobile app. I tried to test websocket with chrome extenshion, it couldn't connect to my websocket. I think it was because of authentication.
In Django channels docs it says:
If you have a custom authentication scheme, you can write a custom middleware to parse the details and put a user object (or whatever other object you need) into your scope.Middleware is written as a callable that takes an ASGI application and wraps it to return another ASGI application. Most authentication can just be done on the scope, so all you need to do is override the initial constructor that takes a scope, rather than the event-running coroutine.
Here’s a simple example of a middleware that just takes a user ID out of the query string and uses that:
The same principles can be applied to authenticate over non-HTTP protocols; for example, you might want to use someone’s chat username from a chat protocol to turn it into a user.
from django.db import close_old_connections
class QueryAuthMiddleware:
def __init__(self, inner):
# Store the ASGI application we were passed
self.inner = inner
def __call__(self, scope):
# Look up user from query string (you should also do things like
# check it's a valid user ID, or if scope["user"] is already populated)
user = User.objects.get(id=int(scope["query_string"]))
close_old_connections()
# Return the inner application directly and let it run everything else
return self.inner(dict(scope, user=user))
What query do I need to do? I don't know anything about that user, in fact its anonymous.
Help me, please.
In this example code, you probably have to open the websocket connection with:
ws://SERVER:PORT/PATH?1
Everything after the ? is the query string. In your example code, your query_string has to be a user id, so for example 1.
You could change the code to use different query strings. For example you could use:
from urllib.parse import parse_qs
from django.db import close_old_connections
class QueryAuthMiddleware:
def __init__(self, inner):
# Store the ASGI application we were passed
self.inner = inner
def __call__(self, scope):
# Look up user from query string (you should also do things like
# check it's a valid user ID, or if scope["user"] is already populated)
query_string = parse_qs(self.scope['query_string'])
if b'user_id' in query_string:
user = User.objects.get(id=int(query_string[b'user_id'][0]))
close_old_connections()
else:
user = AnonymousUser
# Return the inner application directly and let it run everything else
return self.inner(dict(scope, user=user))
Now, you can use this uri:
ws://SERVER:PORT/PATH?user_id=1
You sill have to make sure, that the user with the ID exists in the database. You also have to write actual auth code. Every user can connect to this application with a arbitrary user id. There is no password or auth-token required.
I had the same issue as well, did research a bit came across solution with the code snippet below:
let's say you have User class defined. You want authenticate user through query send query while establishing connection with ws.
I will pass channels installation and configuration let's say you have successfully installed channels and configured.
QueryAuthMiddleware class as shown below:
from channels.auth import AuthMiddlewareStack
from django.conf import LazySettings
from urllib import parse
from rest_socket.models import User
from urllib.parse import parse_qs
from urllib.parse import unquote, urlparse
from channels.auth import AuthMiddlewareStack
from django.contrib.auth.models import AnonymousUser
class QueryAuthMiddleware:
"""
QueryAuthMiddleware authorization
"""
def __init__(self, inner):
self.inner = inner
def __call__(self, scope):
query_string = parse_qs(scope['query_string']) #Used for query string token url auth
headers = dict(scope['headers']) #Used for headers token url auth
print("query", query_string)
if b'user' in query_string:
try:
user = query_string[b'user'][0].decode()
print("user", user)
existing = User.objects.filter(id=user).last()
print(existing)
if existing:
print("existinguser")
scope['user'] = existing
else:
scope["user"] ="no user found"
except User.DoesNotExist:
pass
return self.inner(scope)
QueryAuthMiddlewareStack = lambda inner: QueryAuthMiddleware(AuthMiddlewareStack(inner))
Your routing.py should be like this:
from channels.security.websocket import AllowedHostsOriginValidator
from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
from channels.security.websocket import OriginValidator
from django.urls import path
import your_app.routing
from project.utils.auth import QueryAuthMiddlewareStack
application = ProtocolTypeRouter({
# (http->django views is added by default)
'websocket': QueryAuthMiddlewareStack(
URLRouter(
your_app.routing.websocket_urlpatterns
)
),
})
Routing inside application:
websocket_urlpatterns = [
path("tasks/", consumers.Task)
]
and your ws connection to django-channels:
<script>
var notification;
if (location.protocol === 'https:') {
notification = new WebSocket('wss://' + "window.location.host" + "/tasks"+ "/?user=id");
console.log("with htpps")
}
else {
notification = new WebSocket('ws://' + window.location.host + "/tasks"+ "/?userr=id");
console.log("htpp")
}
notification.onopen = function open() {
console.log('notification connection created for NotificationWebsocket.');
};
notification.onmessage = function message(event) {
var data = JSON.parse(event.data);
console.log("Socket response from NotificationWebsocket => ", data);
};
if (notification.readyState === WebSocket.OPEN) {
notification.onopen();
}
</script>

Sending a message to a single user using django-channels

I have been trying out django-channels including reading the docs and playing around with the examples.
I want to be able to send a message to a single user that is triggered by saving a new instance to a database.
My use case is creating a new notification (via a celery task) and once the notification has saved, sending this notification to a single user.
This sounds like it is possible (from the django-channels docs)
...the crucial part is that you can run code (and so send on
channels) in response to any event - and that includes ones you
create. You can trigger on model saves, on other incoming messages, or
from code paths inside views and forms.
However reading the docs further and playing around with the django-channels examples, I can't see how I can do this. The databinding and liveblog examples demonstrate sending to a group, but I can't see how to just send to a single user.
Little update since Groups work differently with channels 2 than they did with channels 1. There is no Group class anymore, as mentioned here.
The new groups API is documented here. See also here.
What works for me is:
# Required for channel communication
from channels.layers import get_channel_layer
from asgiref.sync import async_to_sync
def send_channel_message(group_name, message):
channel_layer = get_channel_layer()
async_to_sync(channel_layer.group_send)(
'{}'.format(group_name),
{
'type': 'channel_message',
'message': message
}
)
Do not forget to define a method to handle the message type in the Consumer!
# Receive message from the group
def channel_message(self, event):
message = event['message']
# Send message to WebSocket
self.send(text_data=json.dumps({
'message': message
}))
Expanding on #Flip's answer of creating a group for that particular user.
In your python function in your ws_connect function you can add that user into a a group just for them:
consumers.py
from channels.auth import channel_session_user_from_http
from channels import Group
#channel_session_user_from_http
def ws_connect(message):
if user.is_authenticated:
Group("user-{}".format(user.id)).add(message.reply_channel)
To send that user a message from your python code:
my view.py
import json
from channels import Group
def foo(user):
if user.is_authenticated:
Group("user-{}".format(user.id)).send({
"text": json.dumps({
"foo": 'bar'
})
})
If they are connected they will receive the message. If the user is not connected to a websocket it will fail silently.
You will need to also ensure that you only connect one user to each user's Group, otherwise multiple users could receive a message that you intended for only a specific user.
Have a look at django channels examples, particularly multichat for how to implement routing, creating the websocket connection on the client side and setting up django_channels.
Make sure you also have a look at the django channels docs.
In Channels 2, you can save self.channel_name in a db on connect method that is a specific hash for each user. Documentation here
from asgiref.sync import async_to_sync
from channels.generic.websocket import AsyncJsonWebsocketConsumer
import json
class Consumer(AsyncJsonWebsocketConsumer):
async def connect(self):
self.room_group_name = 'room'
if self.scope["user"].is_anonymous:
# Reject the connection
await self.close()
else:
# Accept the connection
await self.channel_layer.group_add(
self.room_group_name,
self.channel_name
)
await self.accept()
print( self.channel_name )
Last line returns something like specific.WxuYsxLK!owndoeYTkLBw
This specific hash you can save in user's table.
The best approach is to create the Group for that particular user. When ws_connect you can add that user into Group("%s" % <user>).add(message.reply_channel)
Note: My websocket url is ws://127.0.0.1:8000/<user>
Just to extend #luke_aus's answer, if you are working with ResourceBindings, you can also make it so, that only users "owning" an object retrieve updates for these:
Just like #luke_aus answer we register the user to it's own group where we can publish actions (update, create) etc that should only be visible to that user:
from channels.auth import channel_session_user_from_http,
from channels import Group
#channel_session_user_from_http
def ws_connect(message):
Group("user-%s" % message.user).add(message.reply_channel)
Now we can change the corresponding binding so that it only publishes changes if the bound object belongs to that user, assuming a model like this:
class SomeUserOwnedObject(models.Model):
owner = models.ForeignKey(User)
Now we can bind this model to our user group and all actions (update, create, etc) will only be published to this one user:
class SomeUserOwnedObjectBinding(ResourceBinding):
# your binding might look like this:
model = SomeUserOwnedObject
stream = 'someuserownedobject'
serializer_class = SomeUserOwnedObjectSerializer
queryset = SomeUserOwnedObject.objects.all()
# here's the magic to only publish to this user's group
#classmethod
def group_names(cls, instance, action):
# note that this will also override all other model bindings
# like `someuserownedobject-update` `someuserownedobject-create` etc
return ['user-%s' % instance.owner.pk]
Although it's late but I have a direct solution for channels 2 i.e using send instead of group_send
send(self, channel, message)
| Send a message onto a (general or specific) channel.
use it as -
await self.channel_layer.send(
self.channel_name,
{
'type':'bad_request',
'user':user.username,
'message':'Insufficient Amount to Play',
'status':'400'
}
)
handel it -
await self.send(text_data=json.dumps({
'type':event['type'],
'message': event['message'],
'user': event['user'],
'status': event['status']
}))
Thanks

PayPal IPN signals to register user

I am trying to get two third party apps to talk to each other: Django-paypal and django-registration. After a successful payment, PayPal sends an IPN signal to a supplied url. I want this signal to authenticate a user registered with django-registration.
After successful payment, it appears that the IPN isn't being sent by paypal sandbox, but I'm not sure it just ins't something wrong with the way I have my signal code set up.
The signals.py is imported in the init.py with
import signals
And I'm using ngrok to allow paypal sandbox to access my localhost.
views.py:
def payment(request):
"""
This is the view for the page a new user is redirected
to after registering (not activating) a new account.
"""
paypal_dict = {
"cmd": "_xclick-subscriptions",
"business": settings.PAYPAL_RECEIVER_EMAIL,
"a3": "7.99",
"p3": "1",
"t3": "M",
"src": "1",
"sra": "1",
"no_note": "1",
"item_name": "My Item",
"notify_url": "http://localhost:8000/accounts/register/account_activated/",
"return_url": "http://localhost:8000/",
"cancel_return": "http//www.example.com/cancel-location/",
}
form = PayPalPaymentsForm(initial=paypal_dict, button_type="subscribe")
context = {"form": form}
return render_to_response("registration/registration_complete.html", context)
signlas.py:
from paypal.standard.models import ST_PP_COMPLETED
from paypal.standard.ipn.signals import valid_ipn_received, payment_was_flagged
from registration.models import RegistrationProfile
from registration.views import ActivationView
def payment_signal(sender, **kwargs):
ipn_object = sender
if ipn_object.payment_status == ST_PP_COMPLETED:
print "payment_status == ST_PP_COMPLETED"
"""
Here use django-registration to authenticate
the User
"""
activate_user(activation_key)
activation_key = ACTIVATED
#ActivationView.activate()
else:
print str(ipn_object.payment_status)
print "error"
valid_ipn_received.connect(payment_signal)
payment_was_flagged.connect(payment_signal)
print "SIGNALS MODULE IMPORTED"
urls.py:
url(r'^accounts/register/account_activated/$', include('paypal.standard.ipn.urls')),
It could be because you are using a test port (like 8000) rather than a standard port 80.
When I switched to port 80, I was able to receive the IPN notification, trigger a signal, and handle the signal appropriately.
This question led me down that path: How do I allow django-merchant to receive paypal IPN conformations?