This commit is contained in:
augustin64 2024-04-05 17:47:15 +02:00
parent 42cc2359a5
commit cceee60939
7 changed files with 116 additions and 9 deletions

View File

@ -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

View File

@ -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
View 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

View 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);
}
};

View File

@ -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)

View File

@ -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>

View File

@ -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 %}