Send SSE
This commit is contained in:
parent
42cc2359a5
commit
cceee60939
@ -1,12 +1,26 @@
|
|||||||
from flask import render_template, redirect, request, flash, Blueprint, current_app, url_for
|
from flask import render_template, redirect, request, flash, Blueprint, current_app, url_for, Response
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from .book import Book
|
from .book import Book
|
||||||
from . import isbn_db
|
from . import isbn_db
|
||||||
|
from . import sse
|
||||||
|
|
||||||
bp = Blueprint("app", __name__, url_prefix="/app")
|
bp = Blueprint("app", __name__, url_prefix="/app")
|
||||||
|
|
||||||
|
announcer = sse.MessageAnnouncer()
|
||||||
|
|
||||||
|
def announce_book(book, msg_type: str="add_book") -> None:
|
||||||
|
"""
|
||||||
|
msg_type must either be "update_book", "add_book" or "delete_book"
|
||||||
|
"""
|
||||||
|
msg = sse.format_sse({
|
||||||
|
"type": msg_type,
|
||||||
|
"book": book.to_json()
|
||||||
|
})
|
||||||
|
announcer.announce(msg)
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/submit-isbn")
|
@bp.route("/submit-isbn")
|
||||||
def submit_isbn():
|
def submit_isbn():
|
||||||
if "isbn" not in request.args:
|
if "isbn" not in request.args:
|
||||||
@ -24,18 +38,23 @@ def submit_isbn():
|
|||||||
book.load()
|
book.load()
|
||||||
except KeyError:
|
except KeyError:
|
||||||
isbn_db.add_book(book)
|
isbn_db.add_book(book)
|
||||||
|
announce_book(book)
|
||||||
return "Pas trouvé, à rajouter manuellement"
|
return "Pas trouvé, à rajouter manuellement"
|
||||||
|
|
||||||
print("Got ", book)
|
print("Got ", book)
|
||||||
if isbn_db.add_book(book) == "duplicate":
|
if isbn_db.add_book(book) == "duplicate":
|
||||||
|
announce_book(book, msg_type="update_book")
|
||||||
return f"{book.title} ajouté (plusieurs occurrences)"
|
return f"{book.title} ajouté (plusieurs occurrences)"
|
||||||
|
announce_book(book)
|
||||||
return f"{book.title} ajouté"
|
return f"{book.title} ajouté"
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/web-submit-isbn")
|
@bp.route("/web-submit-isbn")
|
||||||
def web_submit_isbn():
|
def web_submit_isbn():
|
||||||
flash(submit_isbn())
|
flash(submit_isbn())
|
||||||
return redirect(url_for("app.index"))
|
return redirect(url_for("app.index"))
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/")
|
@bp.route("/")
|
||||||
def index():
|
def index():
|
||||||
return render_template("index.html", books=isbn_db.get_all_books())
|
return render_template("index.html", books=isbn_db.get_all_books())
|
||||||
@ -47,8 +66,16 @@ def delete_book():
|
|||||||
return "missing isbn"
|
return "missing isbn"
|
||||||
|
|
||||||
isbn_db.delete_book(request.form["isbn"])
|
isbn_db.delete_book(request.form["isbn"])
|
||||||
|
msg = sse.format_sse({
|
||||||
|
"type": "delete_book",
|
||||||
|
"book": {
|
||||||
|
"isbn": request.form["isbn"]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
announcer.announce(msg)
|
||||||
return redirect(url_for("app.index"))
|
return redirect(url_for("app.index"))
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/update-book", methods=["POST"])
|
@bp.route("/update-book", methods=["POST"])
|
||||||
def update_book():
|
def update_book():
|
||||||
attributes = ["isbn", "count", "title", "author", "publisher", "publish_date"]
|
attributes = ["isbn", "count", "title", "author", "publisher", "publish_date"]
|
||||||
@ -70,8 +97,10 @@ def update_book():
|
|||||||
count=int(form_data["count"])
|
count=int(form_data["count"])
|
||||||
)
|
)
|
||||||
isbn_db.update_book(book)
|
isbn_db.update_book(book)
|
||||||
|
announce_book(book, msg_type="update_book")
|
||||||
return redirect(url_for("app.index"))
|
return redirect(url_for("app.index"))
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/export-csv")
|
@bp.route("/export-csv")
|
||||||
def export_csv():
|
def export_csv():
|
||||||
books = isbn_db.get_all_books()
|
books = isbn_db.get_all_books()
|
||||||
@ -85,4 +114,22 @@ def export_csv():
|
|||||||
csv,
|
csv,
|
||||||
mimetype="text/csv",
|
mimetype="text/csv",
|
||||||
headers={"Content-Disposition": "attachment;filename=books.csv"}
|
headers={"Content-Disposition": "attachment;filename=books.csv"}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/listen', methods=['GET'])
|
||||||
|
def listen():
|
||||||
|
def stream():
|
||||||
|
messages = announcer.listen() # returns a queue.Queue
|
||||||
|
while True:
|
||||||
|
msg = messages.get() # blocks until a new message arrives
|
||||||
|
yield msg
|
||||||
|
|
||||||
|
return Response(stream(), mimetype='text/event-stream')
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/ping')
|
||||||
|
def ping():
|
||||||
|
msg = sse.format_sse(data={"type": "pong"})
|
||||||
|
announcer.announce(msg=msg)
|
||||||
|
return {}, 200
|
@ -57,4 +57,14 @@ class Book:
|
|||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
if self.title is None:
|
if self.title is None:
|
||||||
return f"<Book: ISBN:{self.isbn}>"
|
return f"<Book: ISBN:{self.isbn}>"
|
||||||
return f"<Book: {self.title}>"
|
return f"<Book: {self.title}>"
|
||||||
|
|
||||||
|
def to_json(self):
|
||||||
|
return {
|
||||||
|
"isbn": self.isbn,
|
||||||
|
"title": self.title,
|
||||||
|
"publisher": self.publisher,
|
||||||
|
"publish_date": self.publish_date,
|
||||||
|
"author": self.author,
|
||||||
|
"count": self.count
|
||||||
|
}
|
26
isbn_sort/sse.py
Normal file
26
isbn_sort/sse.py
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import queue
|
||||||
|
import json
|
||||||
|
|
||||||
|
class MessageAnnouncer:
|
||||||
|
def __init__(self):
|
||||||
|
self.listeners = []
|
||||||
|
|
||||||
|
def listen(self):
|
||||||
|
q = queue.Queue(maxsize=5)
|
||||||
|
self.listeners.append(q)
|
||||||
|
return q
|
||||||
|
|
||||||
|
def announce(self, msg):
|
||||||
|
for i in reversed(range(len(self.listeners))):
|
||||||
|
try:
|
||||||
|
self.listeners[i].put_nowait(msg)
|
||||||
|
except queue.Full:
|
||||||
|
del self.listeners[i]
|
||||||
|
|
||||||
|
|
||||||
|
def format_sse(data: str, event=None) -> str:
|
||||||
|
json_data = json.dumps(data)
|
||||||
|
msg = f'data: {json_data}\n\n'
|
||||||
|
if event is not None:
|
||||||
|
msg = f'event: {event}\n{msg}'
|
||||||
|
return msg
|
23
isbn_sort/static/dynamicUpdate.js
Normal file
23
isbn_sort/static/dynamicUpdate.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
const listener = new EventSource("/app/listen");
|
||||||
|
|
||||||
|
listener.onmessage = (e) => {
|
||||||
|
let data = JSON.parse(e.data);
|
||||||
|
console.log(data);
|
||||||
|
|
||||||
|
switch (data.type) {
|
||||||
|
case "pong":
|
||||||
|
console.log("pong!");
|
||||||
|
break;
|
||||||
|
case "add_book":
|
||||||
|
viewAddBook(data.book);
|
||||||
|
break;
|
||||||
|
case "update_book":
|
||||||
|
viewUpdateBook(data.book);
|
||||||
|
break;
|
||||||
|
case "delete_book":
|
||||||
|
viewDeleteBook(data.book);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.log("Unexpected response from SSE:", data);
|
||||||
|
}
|
||||||
|
};
|
@ -1,4 +1,4 @@
|
|||||||
getBookData = (isbn) => {
|
function getBookData(isbn) {
|
||||||
var table = document.getElementById("books-table");
|
var table = document.getElementById("books-table");
|
||||||
|
|
||||||
// Find all rows in the table except the header
|
// Find all rows in the table except the header
|
||||||
@ -32,7 +32,7 @@ getBookData = (isbn) => {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function edit_book(isbn) {
|
function openEditBookDialog(isbn) {
|
||||||
var bookData = getBookData(isbn);
|
var bookData = getBookData(isbn);
|
||||||
if (bookData) {
|
if (bookData) {
|
||||||
var editDialog = document.getElementById("edit-book-dialog");
|
var editDialog = document.getElementById("edit-book-dialog");
|
||||||
@ -55,7 +55,7 @@ function hideEditBookDialog() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Function to show the delete-book dialog
|
// Function to show the delete-book dialog
|
||||||
function delete_book(isbn) {
|
function openDeleteBookDialog(isbn) {
|
||||||
var bookData = getBookData(isbn);
|
var bookData = getBookData(isbn);
|
||||||
console.log(isbn, bookData)
|
console.log(isbn, bookData)
|
||||||
if (bookData === null)
|
if (bookData === null)
|
||||||
|
@ -11,7 +11,6 @@
|
|||||||
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<script src="{{ url_for('static', filename='main.js') }}" defer></script>
|
|
||||||
<div id="container">
|
<div id="container">
|
||||||
{% for message in get_flashed_messages() %}
|
{% for message in get_flashed_messages() %}
|
||||||
<div class="flash">{{ message }}</div>
|
<div class="flash">{{ message }}</div>
|
||||||
|
@ -7,6 +7,8 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
<script src="{{ url_for('static', filename='main.js') }}" defer></script>
|
||||||
|
<script src="{{ url_for('static', filename='dynamicUpdate.js') }}" defer></script>
|
||||||
<dialog id="edit-book-dialog">
|
<dialog id="edit-book-dialog">
|
||||||
<form id="edit-book-form" action="/app/update-book" method="post">
|
<form id="edit-book-form" action="/app/update-book" method="post">
|
||||||
<input type="hidden" id="edit-isbn" value="" name="isbn">
|
<input type="hidden" id="edit-isbn" value="" name="isbn">
|
||||||
@ -60,8 +62,8 @@
|
|||||||
<td><p {% if book.publisher == None %}class="red"{% endif %}>{{ book.publisher }}</p></td>
|
<td><p {% if book.publisher == None %}class="red"{% endif %}>{{ book.publisher }}</p></td>
|
||||||
<td><p {% if book.count != 1 %}class="red"{% endif %}>{{ book.count }}</p></td>
|
<td><p {% if book.count != 1 %}class="red"{% endif %}>{{ book.count }}</p></td>
|
||||||
<td>
|
<td>
|
||||||
<button class="action" onclick="edit_book({{ book.isbn }})">✏️</button>
|
<button class="action" onclick="openEditBookDialog({{ book.isbn }})">✏️</button>
|
||||||
<button class="action" onclick="delete_book({{ book.isbn }})">🗑️</button>
|
<button class="action" onclick="openDeleteBookDialog({{ book.isbn }})">🗑️</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
Loading…
Reference in New Issue
Block a user