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