Adding websocket form
parent
82691dd9fb
commit
80a42f88aa
@ -0,0 +1,115 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Celery example</title>
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.2.0/css/bootstrap.min.css"
|
||||||
|
integrity="sha512-XWTTruHZEYJsxV3W/lSXG1n3Q39YIWOstqvmFsdNEEQfHoZ6vm6E9GK2OrF6DSJSpIbRbi+Nn0WDPID9O7xB2Q=="
|
||||||
|
crossorigin="anonymous" referrerpolicy="no-referrer"/>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 col-md-4">
|
||||||
|
<form id="your-form">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="email" class="form-label">Email address</label>
|
||||||
|
<input type="email" class="form-control" id="email" name="email">
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="username" class="form-label">Username</label>
|
||||||
|
<input type="text" class="form-control" id="username" name="username">
|
||||||
|
</div>
|
||||||
|
<div class="mb-3" id="messages"></div>
|
||||||
|
<button type="submit" class="btn btn-primary">Submit</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.2.0/js/bootstrap.min.js"
|
||||||
|
integrity="sha512-8Y8eGK92dzouwpROIppwr+0kPauu0qqtnzZZNEF8Pat5tuRNJxJXCkbQfJ0HlUG3y1HB3z18CSKmUo7i2zcPpg=="
|
||||||
|
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||||
|
<script>
|
||||||
|
function updateProgress(yourForm, task_id, btnHtml) {
|
||||||
|
const ws_url = `/ws/task_status/${task_id}`;
|
||||||
|
const WS = new WebSocket((location.protocol === 'https:' ? 'wss' : 'ws') + '://' + window.location.host + ws_url);
|
||||||
|
|
||||||
|
WS.onmessage = function (event) {
|
||||||
|
const res = JSON.parse(event.data);
|
||||||
|
const taskStatus = res.state;
|
||||||
|
|
||||||
|
if (['SUCCESS', 'FAILURE'].includes(taskStatus)) {
|
||||||
|
const msg = yourForm.querySelector('#messages');
|
||||||
|
const submitBtn = yourForm.querySelector('button[type="submit"]');
|
||||||
|
|
||||||
|
if (taskStatus === 'SUCCESS') {
|
||||||
|
msg.innerHTML = 'job succeeded';
|
||||||
|
} else if (taskStatus === 'FAILURE') {
|
||||||
|
msg.innerHTML = res.error;
|
||||||
|
}
|
||||||
|
|
||||||
|
submitBtn.disabled = false;
|
||||||
|
submitBtn.innerHTML = btnHtml;
|
||||||
|
|
||||||
|
// close the websocket because we do not need it now
|
||||||
|
WS.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function serialize (data) {
|
||||||
|
let obj = {};
|
||||||
|
for (let [key, value] of data) {
|
||||||
|
if (obj[key] !== undefined) {
|
||||||
|
if (!Array.isArray(obj[key])) {
|
||||||
|
obj[key] = [obj[key]];
|
||||||
|
}
|
||||||
|
obj[key].push(value);
|
||||||
|
} else {
|
||||||
|
obj[key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
|
const yourForm = document.getElementById("your-form");
|
||||||
|
yourForm.addEventListener("submit", function (event) {
|
||||||
|
event.preventDefault();
|
||||||
|
const submitBtn = yourForm.querySelector('button[type="submit"]');
|
||||||
|
const btnHtml = submitBtn.innerHTML;
|
||||||
|
const spinnerHtml = 'Processing...';
|
||||||
|
submitBtn.disabled = true;
|
||||||
|
submitBtn.innerHTML = spinnerHtml;
|
||||||
|
|
||||||
|
const msg = yourForm.querySelector('#messages');
|
||||||
|
msg.innerHTML = '';
|
||||||
|
|
||||||
|
// Get all field data from the form
|
||||||
|
let data = new FormData(yourForm);
|
||||||
|
// Convert to an object
|
||||||
|
let formData = serialize(data);
|
||||||
|
|
||||||
|
fetch('/users/form/', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(formData),
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then((res) => {
|
||||||
|
// after we get Celery task id, we start polling
|
||||||
|
const task_id = res.task_id;
|
||||||
|
updateProgress(yourForm, task_id, btnHtml);
|
||||||
|
console.log(res);
|
||||||
|
}).catch((error) => {
|
||||||
|
console.error('Error:', error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -0,0 +1,5 @@
|
|||||||
|
from fastapi import APIRouter
|
||||||
|
|
||||||
|
ws_router = APIRouter()
|
||||||
|
|
||||||
|
from . import views
|
@ -0,0 +1,38 @@
|
|||||||
|
import json
|
||||||
|
|
||||||
|
from fastapi import WebSocket
|
||||||
|
|
||||||
|
from . import ws_router
|
||||||
|
from project import broadcast
|
||||||
|
from project.celery_utils import get_task_info
|
||||||
|
|
||||||
|
|
||||||
|
@ws_router.websocket("/ws/task_status/{task_id}")
|
||||||
|
async def ws_task_status(websocket: WebSocket):
|
||||||
|
# Accept websocket connection and obtain task id
|
||||||
|
await websocket.accept()
|
||||||
|
|
||||||
|
task_id = websocket.scope["path_params"]["task_id"]
|
||||||
|
|
||||||
|
# Subscribe to specific channel and send message to browser if
|
||||||
|
# it exists
|
||||||
|
async with broadcast.subscribe(channel=task_id) as subscriber:
|
||||||
|
# just in case the task is already finished
|
||||||
|
data = get_task_info(task_id)
|
||||||
|
await websocket.send_json(data=data)
|
||||||
|
|
||||||
|
async for event in subscriber:
|
||||||
|
await websocket.send_json(json.loads(event.message))
|
||||||
|
|
||||||
|
|
||||||
|
async def update_celery_task_status(task_id: str):
|
||||||
|
"""
|
||||||
|
Called by Celery worker in `task_postrun` signal handler after tasks finishes.
|
||||||
|
"""
|
||||||
|
await broadcast.connect()
|
||||||
|
# broadcast task info to channel identified by task_id through redis.
|
||||||
|
await broadcast.publish(
|
||||||
|
channel=task_id,
|
||||||
|
message=json.dumps(get_task_info(task_id)), # RedisProtocol.publish expect str
|
||||||
|
)
|
||||||
|
await broadcast.disconnect()
|
Loading…
Reference in New Issue