I'm a newbie to channels and I made a chatroom application by following their official documentation. Now I'm trying to save the chat messages. All I know is I can create a model but Idk how to save it from the consumers.py into my database. I added username along with the message. A little help would be appreciated.
My Consumers.py:
import json
from channels.generic.websocket import AsyncWebsocketConsumer
class ChatConsumer(AsyncWebsocketConsumer):
async def connect(self):
self.room_name = self.scope['url_route']['kwargs']['room_name']
self.room_group_name = 'chat_%s' % self.room_name
# Join room group
await self.channel_layer.group_add(
self.room_group_name,
self.channel_name
)
await self.accept()
async def disconnect(self, close_code):
# Leave room group
await self.channel_layer.group_discard(
self.room_group_name,
self.channel_name
)
# Receive message from WebSocket
async def receive(self, text_data):
username = self.scope["user"].first_name
name = self.scope['user'].username
text_data_json = json.loads(text_data)
message = text_data_json['message']
message = (username + '(' + name + ')' + ':\n' + message)
# Send message to room group
await self.channel_layer.group_send(
self.room_group_name,
{
'type': 'chat_message',
'message': message
}
)
# Receive message from room group
async def chat_message(self, event):
username = self.scope["user"].username
message = event['message']
name = self.scope["user"].username
# Send message to WebSocket
await self.send(text_data=json.dumps({
'message': message,
"username": username,
"name": name
}))
My model to save the msgs:
class Message(models.Model):
author = models.ForeignKey(User, related_name='messages', on_delete=models.CASCADE)
context = models.TextField()
timestamp = models.DateTimeField(auto_now_add=True)
I tried to follow this tutorial as well:
https://www.youtube.com/watch?v=xrKKRRC518Y
For the users who still have this issue:
Since the WebSocket being used is AsyncWebsocketConsumer, we have to use database_sync_to_async. Why so? Because we do not want to leave open connections to the database.
This is how you will do it:
#database_sync_to_async
def create_chat(self, msg, sender):
Message.objects.create(sender=sender, msg=msg)
This is a simple example to demonstrate how to successfully use database_sync_to_async:
import json
from channels.db import database_sync_to_async
from channels.generic.websocket import AsyncWebsocketConsumer
from .models import Message
class ChatConsumer(AsyncWebsocketConsumer):
#database_sync_to_async
def create_chat(self, msg, sender):
return Message.objects.create(sender=sender, msg=msg)
async def connect(self):
self.room_name = self.scope['url_route']['kwargs']['room_name']
self.room_group_name = 'chat_%s' % self.room_name
await self.channel_layer.group_add(self.room_group_name, self.channel_name)
await self.accept()
async def disconnect(self, close_code):
await self.channel_layer.group_discard(self.room_group_name, self.channel_name)
async def receive(self, text_data):
text_data_json = json.loads(text_data)
message = text_data_json['message']
sender = text_data_json['sender']
await self.channel_layer.group_send(self.room_group_name, {
'type': 'chat_message',
'message': message,
'sender': sender
})
async def chat_message(self, event):
message = event['message']
sender = event['sender']
new_msg = await self.create_chat(sender, message) # It is necessary to await creation of messages
await self.send(text_data=json.dumps({
'message': new_msg.message,
'sender': new_msg.sender
}))
What's wrong with just creating the message regularly?
message = Message.objects.create(context=message, author=self.scope['user'])
Related
I am making a chat app with django channels and it has 2 consumers. One is for the team chat and the other for personal. I am adding history for the chat. There are two models. "Chat" and "Chat2". Chat is for team and Chat2 for personal. The history is working with the team chat and the messages are added to the "Chat" model. But with personal it is added to "Chat" instead of "Chat2".
Here are the models:
class Chat(models.Model):
team_id = models.ForeignKey(Team,on_delete=models.CASCADE,null=True)
username = models.CharField(max_length=200,null=True)
message = models.CharField(max_length=200,null=True)
profile_pic = models.ImageField(upload_to='chat/',null=True)
class Chat2(models.Model):
username = models.CharField(max_length=200,null=True)
username2 = models.CharField(max_length=200,null=True)
message = models.CharField(max_length=200,null=True)
profile_pic = models.ImageField(upload_to='chat/',null=True)
room_name1 = models.CharField(max_length=200,null=True)
And here are the two consumers(The consumers are in the same file but I add it separately for cleaner code):
class ChatConsumer(AsyncWebsocketConsumer):
#sync_to_async
def get_profile(self):
return self.scope['user'].profile
async def connect(self):
self.room_name = self.scope['url_route']['kwargs']['room_name']
self.room_group_name = 'chat_%s' % self.room_name
# Join room group
await self.channel_layer.group_add(
self.room_group_name,
self.channel_name
)
await self.accept()
async def disconnect(self, close_code):
# Leave room group
await self.channel_layer.group_discard(
self.room_group_name,
self.channel_name
)
# Receive message from WebSocket
async def receive(self, text_data):
text_data_json = json.loads(text_data)
message = text_data_json['message']
username = text_data_json['profile_name']
team = text_data_json['team']
profile = await self.get_profile()
profile_pic = profile.profile_pic.url
await self.save_messages(message, team, username, profile_pic)
# Send message to room group
await self.channel_layer.group_send(
self.room_group_name,
{
'type': 'chat_message',
'message': message,
'username':username,
'team':team,
'profile_pic':profile_pic
}
)
# Receive message from room group
async def chat_message(self, event):
message = event['message']
team = event['team']
username = event['username']
profile_pic = event['profile_pic']
# Send message to WebSocket
await self.send(text_data=json.dumps({
'message': message,
'team':team,
'username':username,
'profile_pic':profile_pic,
'type':'chat'
}))
#sync_to_async
def save_messages(self,message,team,username,profile_pic):
team1 = Team.objects.filter(team_name=team).first()
chat = Chat(team_id=team1,message=message,username=username,profile_pic=profile_pic)
chat.save()
The other consumer:
class Chat2Consumer(AsyncWebsocketConsumer):
#sync_to_async
def get_profile(self):
return self.scope['user'].profile
async def connect(self):
self.room_name = self.scope['url_route']['kwargs']['room_name']
self.room_group_name = 'chat_%s' % self.room_name
# Join room group
await self.channel_layer.group_add(
self.room_group_name,
self.channel_name
)
await self.accept()
async def disconnect(self, close_code):
# Leave room group
await self.channel_layer.group_discard(
self.room_group_name,
self.channel_name
)
# Receive message from WebSocket
async def receive(self, text_data):
text_data_json = json.loads(text_data)
message = text_data_json['message']
username = text_data_json['profile_name']
room_name = text_data_json['room_name']
profile = await self.get_profile()
profile_pic = profile.profile_pic.url
await self.save_messages1(message,username,profile_pic)
# Send message to room group
await self.channel_layer.group_send(
self.room_group_name,
{
'type': 'chat_message',
'message': message,
'username':username,
'profile_pic':profile_pic,
}
)
# Receive message from room group
async def chat_message(self, event):
message = event['message']
username = event['username']
profile_pic = event['profile_pic']
# Send message to WebSocket
await self.send(text_data=json.dumps({
'message': message,
'username':username,
'profile_pic':profile_pic,
'type':'chat'
}))
#sync_to_async
def save_messages(self,message,username,profile_pic,username2):
Chat2.objects.create(message=message,username=username,profile_pic=profile_pic)
And routing.py
websocket_urlpatterns = [
re_path(r'ws/chat/(?P<room_name>\w+)/$', consumers.ChatConsumer.as_asgi()),
re_path(r'ws/chat2/(?P<room_name>\w+)/$', consumers.Chat2Consumer.as_asgi()),
]
Here is the Flask app
import jwt
from datetime import datetime, timedelta
from flask import Flask, request, jsonify, make_response
from flask_socketio import SocketIO, send
from flask_sqlalchemy import SQLAlchemy
from werkzeug.security import generate_password_hash, check_password_hash
from functools import wraps
app = Flask(__name__)
app.config['SECRET_KEY'] = 'myrandomsecretkey'
print(app.config['SECRET_KEY'])
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///site.db'
db = SQLAlchemy(app)
def token_required(f):
#wraps(f)
def decorated(*args, **kwargs):
token = None
if 'x-access-token' in request.headers:
token = request.headers['x-access-token']
if not token:
return jsonify({'message': 'Token is missing!'}), 401
try:
data = jwt.decode(token, app.config['SECRET_KEY'])
current_user = User.query.filter_by(public_id=data['public_id']).first()
except:
return jsonify({'message': 'Token is invalid!'}), 401
return f(current_user, *args, **kwargs)
return decorated
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(50), unique=True, nullable=False)
first_name = db.Column(db.String(30))
last_name = db.Column(db.String(40))
first_name = db.Column(db.String(30), nullable=False)
date_joined = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
password = db.Column(db.String(80))
def __repr__(self):
return f"User('{self.first_name}', '{self.last_name}')"
#app.route('/user_registration', methods=['POST'])
def create_user():
data = request.get_json()
hashed_password = generate_password_hash(data['password'], method='sha256')
new_user = User(first_name=data['first_name'],
last_name=data['last_name'], password=hashed_password, username=data['username'])
db.session.add(new_user)
db.session.commit()
return jsonify({'message': 'new_user_created'})
#app.route('/login', methods=['POST'])
def login():
auth = request.authorization
if not auth.password:
return make_response('Authentication credentials were not provided', 418)
user = User.query.filter_by(username=auth.username).first()
if not user:
return jsonify({'message': 'No user found'})
if check_password_hash(user.password, auth.password):
token = jwt.encode({'username': user.username, 'exp': datetime.utcnow() +
timedelta(minutes=30)}, app.config['SECRET_KEY'])
print(token)
return jsonify({'token': token})
return jsonify({'message': 'No user found'})
#app.route('/user', methods=['GET'])
#token_required
def get_all_users(current_user):
print(current_user)
if not current_user.admin:
return jsonify({'message': 'Cannot perform that function!'})
users = User.query.all()
output = []
for user in users:
user_data = {}
user_data['username'] = user.username
user_data['first_name'] = user.first_name
user_data['last_name'] = user.last_name
output.append(user_data)
return jsonify({'users': output})
After logging in I get the token and when I use in request in Postamn I put it in headers, the key is x-access-token and put the generated token as value but every this I get this error message
"message": "Token is invalid!"
I copied the the authorization part from a tutorial and they were decoding the token before returning it like this
return jsonify({'token' : token.decode('UTF-8')})
when I decode it it returns error saying that I can't decode a string.
This is the tutorial from which I got most parts
https://www.youtube.com/watch?v=WxGBoY5iNXY
So what's the poblem here?
I have django app with authentication in it and email verification. When user is created activation email is sent with link inside of it, when user clicks this link is doesn't take him nowhere.
views.py
class customer_register(CreateView):
model = User
form_class = CustomerSignUpForm
template_name = 'authentication/customer_register.html'
def form_valid(self, form):
user = form.save()
user.token = str(uuid.uuid4())
subject = 'Verify your account | Zane'
message = f"http://127.0.0.1:8000/accounts/verify/{user.token}/"
recipient_list = [user.email]
send_mail(
subject,
message,
'from#example.com',
['to#example.com'],
fail_silently=False,
)
return redirect('/')
def activate(request, token):
try:
obj = models.User.objects.get(email_token = token)
obj.signup_confirmation = True
obj.save()
return HttpResponse('Your account is verified')
except Exception as e:
return HttpResponse('Invalid token')
urls.py
path('verify/<uuid:pk>/', views.activate, name='activate'),
models.py
...
token = models.CharField(max_length=200, blank=True)
signup_confirmation = models.BooleanField(default=False)
I wonder what do I need to put in my url to trigger my function?
I would rewrite your active view as a class. Here is an example:
class ActivateView(LoginRequiredMixin, View):
def get(self, request, *args, **kwargs):
token = kwargs['pk']
try:
obj = User.objects.get(email_token = token)
obj.signup_confirmation = True
obj.save()
return HttpResponse('Your account is verified')
except Exception as e:
return HttpResponse('Invalid token')
urls.py
path('verify/<uuid:pk>/', ActivateView.as_view(), name='activate'),
I tried to create a client that uses django test client and make a get request with token authentication. But I get a 401 error so something is wrong ..Of course this also happens for post.
I was looking for different solutions but nothing works.
this is part of the code:
from django.test import Client
class MyClient:
def __init__(self):
self.client = Client()
self.token = None
def get(self, url):
if self.token is not None:
new_url = base + url
result = self.client.get(new_url, headers={'Content-Type': 'application/json','Authorization': 'Token {}'.format(self.token)})
else:
result = self.client.get(url)
return result
def login(self, username, password):
payload = {
"username": username,
"password": password
}
response = self.post_without_auth('/api/auth/token_auth/',payload)
if response.status_code is 200:
self.token = response.json()['token']
return response
def post_without_auth(self, url, payload):
response = self.client.post(url, payload)
return response
def current_user(self):
response = self.get('/api/current_user/')
return response
call the method post which will set the token value
def get(self, url):
self.post(username, password)
if self.token is not None:
new_url = base + url
result = self.client.get(new_url, headers={'Content-Type': 'application/json','Authorization': 'Token {}'.format(self.token)})
else:
result = self.client.get(url)
return result
def login(self, username, password):
payload = {
"username": username,
"password": password
}
response = self.post_without_auth('/api/auth/token_auth/',payload)
if response.status_code is 200:
self.token = response.json()['token']
return response
I have a view, It will send requests to http://127.0.0.1:80/o/token/
I want to know can I directly call the view /o/token/ and get the result ?
And do not need to import requests to send it
class GetAccessToken(APIView):
def post(self, request, *args, **kwargs):
msg ={}
return Response(msg, status=status.HTTP_200_OK)
def get_access_token(self, username, password, client_id, client_secret, scope="read write"):
url = "http://127.0.0.1:80/o/token/"
payload = {
'grant_type': 'password',
'username': username,
'password': password,
'scope': scope
}
auth = HTTPBasicAuth(client_id, client_secret)
try:
response = requests.post(url, auth=auth, data=payload, verify=False, timeout=TIMEOUT)
except Exception as err:
print err