I want to notify the client when my model is saved.
I started by creating a django-signal on post_save.
#receiver(post_save, sender=Scooter)
async def scooter_post_update(sender, instance, created, **kwargs):
# Notify client here
Next I created the AsyncConsumer class from django-channels and provided its routing.
// routing.py
application = ProtocolTypeRouter({
# Empty for now (http->django views is added by default)
'websocket': AllowedHostsOriginValidator(
AuthMiddlewareStack(
URLRouter(
[
path('scooters/', ScootersUpdateConsumer)
]
)
)
)
})
// consumers.py
class ScootersUpdateConsumer(AsyncConsumer):
async def websocket_connect(self, event):
print("Connected!", event)
await self.send({
"type": "websocket.accept"
})
async def send_message(self):
await self.send({
"type": "websocket.send",
'text': 'Oy, mate!'
})
async def websocket_receive(self, event):
print("Receive!", event)
async def websocket_disconnect(self, event):
print("Disconnected!", event)
Now my question is how can I call send_message() from the scooter_post_update() method.
The steps are really straightforward. You have to get the channel layer and just send a message with type key set as your listening method name:
import channels
from asgiref.sync import async_to_sync
#receiver(post_save, sender=Scooter)
def scooter_post_update(sender, instance, created, **kwargs):
channel_layer = channels.layers.get_channel_layer()
async_to_sync(channel_layer.send)(
{"type": "send_message", "data": data}
)
and any other stuff you want to send over channel.
Beware that all the data you pass must be serializable, so you have to take care that all your objects are serialized beforehand.
The mandatory part of the dictionary you pass to the send method is the type key (as mentioned before), which has to contain the method name which will be called on the consumer.
Moreover, you can use groups, so you can broadcast a message to a group of listeners:
import channels
from asgiref.sync import async_to_sync
#receiver(post_save, sender=Scooter)
def scooter_post_update(sender, instance, created, **kwargs):
channel_layer = channels.layers.get_channel_layer()
async_to_sync(channel_layer.group_send)(
"group_name", {"type": "send_message", "data": data}
)
and on the consumer side:
class ScootersUpdateConsumer(AsyncConsumer):
async def websocket_connect(self, event):
await self.channel_layer.group_add("group_name", self.channel_name)
await self.send({
"type": "websocket.accept"
})
Note that in both cases, you use the async_to_sync wrapper which should be used when you call async code from sync scope.
Related
I'm making a real-time chat app using django channels.
I want to delete an object from database using django channels(actually deleting a message from the group). How can this be done?
This is my backend code:
import json
from django.contrib.auth.models import User
from channels.generic.websocket import AsyncWebsocketConsumer
from asgiref.sync import sync_to_async
from .models import Room, Message
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
await self.channel_layer.group_add(
self.room_group_name,
self.channel_name
)
await self.accept()
async def disconnect(self):
await self.channel_layer.group_discard(
self.room_group_name,
self.channel_name
)
# Receive message from WebSocket
async def receive(self, text_data):
data = json.loads(text_data)
print(data)
message = data['message']
username = data['username']
room = data['room']
await self.save_message(username, room, message)
# Send message to room group
await self.channel_layer.group_send(
self.room_group_name,
{
'type': 'chat_message',
'message': message,
'username': username
}
)
# Receive message from room group
async def chat_message(self, event):
message = event['message']
username = event['username']
# Send message to WebSocket
await self.send(text_data=json.dumps({
'message': message,
'username': username
}))
#sync_to_async
def save_message(self, username, room, message):
user = User.objects.get(username=username)
room = Room.objects.get(slug=room)
Message.objects.create(user=user, room=room, content=message)
Should i use javascript?
I tried to delete some objects by using Ajax but it didn't work.
I'm writing tests for a fastAPI on django with an ASGI server (adapted this tutorial). My fastAPI side of the test keeps returning errors and I'm trying in vain to fix it.
My need is about creating a user to test the API.
#sync_to_async
def _create_user(self, username, email):
try:
return User.objects.create(username=username, email=email)
except:
return None
async def setUp(self):
task = asyncio.create_task(self._create_user(username="user", email="email#email.com"))
self.user = await task
Running this test, it turn out that self.user is a coroutine and it's impossible to access the attributes I expect.
How to solve this ?
Update :
Removed async for _create_user(self, username, email).
According to the docs https://docs.djangoproject.com/en/3.1/topics/async/#asgiref.sync.sync_to_async
decorator sync_to_async should decorates sync functions. (see example)
I have an answer,.......................
Simply Create a normal synchronize function and call with with sync_to_async() function
This function is used to get the user_object
def get_user_object(mobile_no):
user_object = CustomUser.objects.get(mobile_no=mobile_no)
return user_object
This method is running in async so we need to call a sync function to async to get rid of error......
from channels.db import database_sync_to_async
async def connect(self):
username = await database_sync_to_async(get_user_object)(mobile_no="+9999999999")
print(user_object)
I'm looking for some library or example of code to format FastAPI validation messages into human-readable format. E.g. this endpoint:
#app.get("/")
async def hello(name: str):
return {"hello": name}
Will produce the next json output if we miss name query parameter:
{
"detail":[
{
"loc":[
"query",
"name"
],
"msg":"field required",
"type":"value_error.missing"
}
]
}
So my questions is, how to:
Transform it into something like "name field is required" (for all kinds of possible errors) to show in toasts.
Use it to display form validation messages
Generate forms themselves from api description if it's possible
FastAPI has a great Exception Handling, so you can customize your exceptions in many ways.
You can raise an HTTPException, HTTPException is a normal Python exception with additional data relevant for APIs. But you can't return it you need to raise it because it's a Python exception
from fastapi import HTTPException
...
#app.get("/")
async def hello(name: str):
if not name:
raise HTTPException(status_code=404, detail="Name field is required")
return {"Hello": name}
By adding name: str as a query parameter it automatically becomes required so you need to add Optional
from typing import Optional
...
#app.get("/")
async def hello(name: Optional[str] = None):
error = {"Error": "Name field is required"}
if name:
return {"Hello": name}
return error
$ curl 127.0.0.1:8000/?name=imbolc
{"Hello":"imbolc"}
...
$ curl 127.0.0.1:8000
{"Error":"Name field is required"}
But in your case, and i think this is the best way to handling errors in FastAPI overriding the validation_exception_handler:
from fastapi import FastAPI, Request, status
from fastapi.encoders import jsonable_encoder
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse
...
#app.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError):
return JSONResponse(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
content=jsonable_encoder({"detail": exc.errors(), "Error": "Name field is missing"}),
)
...
#app.get("/")
async def hello(name: str):
return {"hello": name}
You will get a response like this:
$ curl 127.0.0.1:8000
{
"detail":[
{
"loc":[
"query",
"name"
],
"msg":"field required",
"type":"value_error.missing"
}
],
"Error":"Name field is missing"
}
You can customize your content however if you like:
{
"Error":"Name field is missing",
"Customize":{
"This":"content",
"Also you can":"make it simpler"
}
}
I reached here with a similar question - and I ended up handling the RequestValidationError to give back a response where every field is an array of the issues with that field.
The response to your request would become (with a status_code=400)
{
"detail": "Invalid request",
"errors": {"name": ["field required"]}
}
that's quite handy to manage on the frontend for snackbar notifications and flexible enough.
Here's the handler
from collections import defaultdict
from fastapi import status
from fastapi.encoders import jsonable_encoder
from fastapi.responses import JSONResponse
#app.exception_handler(RequestValidationError)
async def custom_form_validation_error(request, exc):
reformatted_message = defaultdict(list)
for pydantic_error in exc.errors():
loc, msg = pydantic_error["loc"], pydantic_error["msg"]
filtered_loc = loc[1:] if loc[0] in ("body", "query", "path") else loc
field_string = ".".join(filtered_loc) # nested fields with dot-notation
reformatted_message[field_string].append(msg)
return JSONResponse(
status_code=status.HTTP_400_BAD_REQUEST,
content=jsonable_encoder(
{"detail": "Invalid request", "errors": reformatted_message}
),
)
I think the best I can come up with is actually PlainTextResponse
Adding these:
from fastapi.exceptions import RequestValidationError
#app.exception_handler(RequestValidationError)
async def validation_exception_handler(request, exc):
return PlainTextResponse(str(exc), status_code=400)
You get a more human-friendly error message like these in plain text:
1 validation error
path -> item_id
value is not a valid integer (type=type_error.integer)
It's well documented in FastAPI docs here.
I want to run a consumer which requires authenticated user in my channels application
channels disconnecting the consumer saying that user isn't authenticated
but the user is authenticated and his cookies are updated in the browser
I have followed channels documentation which authenticates user
class LikeConsumer(AsyncJsonWebsocketConsumer):
async def connect(self):
self.user = self.scope["user"]
# user = self.scope['user']
user=self.user
print(user)
if user.is_anonymous:
await self.close()
print("user is anonymous")
else:
await self.accept()
# user_group = await self._get_user_group(self.scope['user'])
await self.channel_layer.group_add("{}".format(user.id), self.channel_name)
print(f"Add {self.channel_name} channel to post's group")
print('connected')
# #database_sync_to_async
# def _get_user_group(self, user):
# if not user.is_authenticated:
# raise Exception('User is not authenticated.')
# else:
# print("user is not authenticated")
# return user
async def disconnect(self,close_code):
user = self.scope['user']
await self.channel_layer.group_discard("{}".format(user.id), self.channel_name)
print(f"Remove {self.channel_name} channel from post's group")
i'm not sure what exactly the mistake is
user is anonymous
WebSocket DISCONNECT /like/ [127.0.0.1:50710]
I'm trying to asynchronously get object from the database, update it, and then save it with Django Channels 2. The retrieval works, but I'm having problems with the saving.
I already tried with
await database_sync_to_async(game.save)()
as well as moving the actual saving in another method, like this
#database_sync_to_async
def save(self, item):
item.save()
class GameController(AsyncWebsocketConsumer):
#property
#database_sync_to_async
def game(self):
return Game.objects.get(uuid=self.game_uuid)
async def save_connected(self):
game = await self.game
if not game.data:
game.data = {
'connected': False
}
else:
game.data['connected'] = True
await database_sync_to_async(game.save)()
But I always get this error: Object of type coroutine is not JSON serializable
or the data is not saved at all.