This will need some cleaning in a near future

This commit is contained in:
augustin64 2024-11-01 21:55:45 +01:00
parent 8fc94603db
commit c884260238
15 changed files with 278 additions and 108 deletions

View File

@ -2,11 +2,13 @@ from flask import render_template, redirect, request, flash, Blueprint, current_
import json import json
from .book import Book from .modules.book import Book
from . import isbn_db from .modules import db as isbn_db
from . import sse from .modules import auth
from .modules import sse
bp = Blueprint("app", __name__, url_prefix="/app") bp = Blueprint("app", __name__, url_prefix="/app")
bp.register_blueprint(auth.bp)
announcer = sse.MessageAnnouncer() announcer = sse.MessageAnnouncer()
@ -22,6 +24,7 @@ def announce_book(book, msg_type: str="add_book") -> None:
@bp.route("/submit-isbn") @bp.route("/submit-isbn")
@auth.login_required
def submit_isbn(): def submit_isbn():
if "isbn" not in request.args: if "isbn" not in request.args:
return "/submit-isbn?isbn=xxxxxx" return "/submit-isbn?isbn=xxxxxx"
@ -38,7 +41,7 @@ def submit_isbn():
book = isbn_db.get_book(book.isbn) book = isbn_db.get_book(book.isbn)
assert isbn_db.add_book(book) == "duplicate" # duplicate assert isbn_db.add_book(book) == "duplicate" # duplicate
announce_book(isbn_db.get_book(book.isbn), msg_type="update_book") announce_book(isbn_db.get_book(book.isbn), msg_type="update_book")
return f"{book.title} ajouté (plusieurs occurrences)" return f"{book.title} ajouté (doublon)"
except IndexError: except IndexError:
pass pass
@ -65,12 +68,14 @@ def submit_isbn():
@bp.route("/web-submit-isbn") @bp.route("/web-submit-isbn")
@auth.login_required
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("/")
@auth.login_required
def index(): def index():
return render_template( return render_template(
"index.html", "index.html",
@ -80,6 +85,7 @@ def index():
@bp.route("/delete-book", methods=["POST"]) @bp.route("/delete-book", methods=["POST"])
@auth.login_required
def delete_book(): def delete_book():
if "isbn" not in request.form: if "isbn" not in request.form:
return "missing isbn" return "missing isbn"
@ -96,8 +102,9 @@ def delete_book():
@bp.route("/update-book", methods=["POST"]) @bp.route("/update-book", methods=["POST"])
@auth.login_required
def update_book(): def update_book():
attributes = ["isbn", "count", "title", "author", "publisher", "publish_date", "category"] attributes = ["isbn", "title", "author", "status", "owner", "category"]
if True in [i not in request.form for i in attributes]: if True in [i not in request.form for i in attributes]:
return "missing an attribute" return "missing an attribute"
@ -110,10 +117,9 @@ def update_book():
book = Book(form_data["isbn"]) book = Book(form_data["isbn"])
book._manual_load( book._manual_load(
form_data["title"], form_data["title"],
publisher=form_data["publisher"], status=int(form_data["status"]),
publish_date=form_data["publish_date"], owner=form_data["owner"],
author=form_data["author"], author=form_data["author"],
count=int(form_data["count"]),
category=form_data["category"] category=form_data["category"]
) )
isbn_db.update_book(book) isbn_db.update_book(book)
@ -122,13 +128,14 @@ def update_book():
@bp.route("/export-csv") @bp.route("/export-csv")
@auth.login_required
def export_csv(): def export_csv():
books = isbn_db.get_all_books() books = isbn_db.get_all_books()
csv = "ISBN;Titre;Auteur;Éditeur;Date;Catégorie;Quantité\n" csv = "ISBN;Titre;Auteur;État;Propriétaire;Catégorie\n"
for book in books: for book in books:
book.replace(";", ",") book.replace(";", ",")
csv += f"{book.isbn};{book.title};{book.author};{book.publisher};{book.publish_date};{book.category};{book.count}\n" csv += f"{book.isbn};{book.title};{book.author};{book.status};{book.owner};{book.category}\n"
# return as file with a good filename # return as file with a good filename
return current_app.response_class( return current_app.response_class(
@ -139,6 +146,7 @@ def export_csv():
@bp.route('/listen', methods=['GET']) @bp.route('/listen', methods=['GET'])
@auth.login_required
def listen(): def listen():
def stream(): def stream():
messages = announcer.listen() # returns a queue.Queue messages = announcer.listen() # returns a queue.Queue
@ -150,6 +158,7 @@ def listen():
@bp.route('/ping') @bp.route('/ping')
@auth.login_required
def ping(): def ping():
msg = sse.format_sse(data={"type": "pong"}) msg = sse.format_sse(data={"type": "pong"})
announcer.announce(msg=msg) announcer.announce(msg=msg)

View File

@ -6,7 +6,7 @@ import subprocess
import sys import sys
import os import os
import book as bk from modules import book as bk
def clip_copy(data): def clip_copy(data):

4
isbn_sort/init.sql Normal file
View File

@ -0,0 +1,4 @@
-- SQLite
-- Users
INSERT INTO user (username, password)
VALUES ('root', 'pbkdf2:sha256:260000$DzMbmkbgVJ0JoQa3$4b42c5a5135668ae5e5754fa4f0ac136ece8f59e8d751008bd533b6c9426c9ff');

115
isbn_sort/modules/auth.py Normal file
View File

@ -0,0 +1,115 @@
#!/usr/bin/python3
"""
Authentification module
"""
import functools
from typing import Optional
from flask import (Blueprint, flash, g, redirect, render_template,
request, session, url_for, current_app)
from werkzeug.security import check_password_hash, generate_password_hash
from .db import get_db
bp = Blueprint("auth", __name__, url_prefix="/auth")
def login_required(view):
"""View decorator that redirects anonymous users to the login page."""
@functools.wraps(view)
def wrapped_view(**kwargs):
if g.user is None:
flash("You need to login to access this resource.")
return redirect(url_for("app.auth.login"))
return view(**kwargs)
return wrapped_view
def anon_required(view):
"""View decorator that redirects authenticated users to the index."""
@functools.wraps(view)
def wrapped_view(**kwargs):
if g.user is not None:
return redirect(url_for("app.index"))
return view(**kwargs)
return wrapped_view
@bp.before_app_request
def load_logged_in_user():
"""If a user id is stored in the session, load the user object from
the database into ``g.user``."""
user_id = session.get("user_id")
if user_id is None:
g.user = None
else:
g.user = (
get_db().execute("SELECT * FROM user WHERE id = ?", (user_id,)).fetchone()
)
def create_user(username: str, password: str) -> Optional[str]:
"""Adds a new user to the database"""
error = None
if not username:
error = "Missing username."
elif not password:
error = "Missing password."
try:
db = get_db()
db.execute(
"INSERT INTO user (username, password) VALUES (?, ?)",
(username, generate_password_hash(password)),
)
db.commit()
except db.IntegrityError:
# The username was already taken, which caused the
# commit to fail. Show a validation error.
error = f"Username {username} is not available."
return error # may be None
@bp.route("/login", methods=("GET", "POST"))
@anon_required
def login():
"""Log in a registered user by adding the user id to the session."""
if request.method == "POST":
username = request.form["username"]
password = request.form["password"]
db = get_db()
error = None
user = db.execute(
"SELECT * FROM user WHERE username = ?", (username,)
).fetchone()
if (user is None) or not check_password_hash(user["password"], password):
error = "Incorrect username or password"
if error is None:
# store the user id in a new session and return to the index
session.clear()
session["user_id"] = user["id"]
return redirect(url_for("app.index"))
flash(error)
return render_template("login.html")
@bp.route("/logout")
def logout():
"""Clear the current session, including the stored user id."""
session.clear()
return redirect("/")

View File

@ -15,11 +15,19 @@ class Book:
self.isbn = isbn self.isbn = isbn
self.title = None self.title = None
self.publisher = None self.owner = None
self.publish_date = None self.status = 0
self.author = None self.author = None
self.category = None self.category = None
self.count = -1
@property
def status_text(self) -> str:
return [
"À lire",
"En cours",
"Lu"
][self.status]
def _openlibrary_load(self, _): def _openlibrary_load(self, _):
r = requests.get(f"https://openlibrary.org/api/books?bibkeys=ISBN:{self.isbn}&jscmd=details&format=json") r = requests.get(f"https://openlibrary.org/api/books?bibkeys=ISBN:{self.isbn}&jscmd=details&format=json")
@ -35,15 +43,10 @@ class Book:
isbn_data = data[f"ISBN:{self.isbn}"] isbn_data = data[f"ISBN:{self.isbn}"]
self.title = isbn_data["details"]["title"] self.title = isbn_data["details"]["title"]
if "publishers" in isbn_data["details"] and len(isbn_data["details"]["publishers"]) > 0:
self.publisher = isbn_data["details"]["publishers"][0]
if "authors" in isbn_data["details"] and len(isbn_data["details"]["authors"]) > 0: if "authors" in isbn_data["details"] and len(isbn_data["details"]["authors"]) > 0:
self.author = isbn_data["details"]["authors"][0]["name"] self.author = isbn_data["details"]["authors"][0]["name"]
if "publish_date" in isbn_data["details"]:
self.publish_date = isbn_data["details"]["publish_date"]
def _google_books_load(self, config): def _google_books_load(self, config):
if config["GOOGLE_BOOKS_KEY"] is None: if config["GOOGLE_BOOKS_KEY"] is None:
@ -65,23 +68,16 @@ class Book:
self.title = item["volumeInfo"]["title"] self.title = item["volumeInfo"]["title"]
if "publisher" in item["volumeInfo"]:
self.publisher = item["volumeInfo"]["publisher"]
if "publishedDate" in item["volumeInfo"]:
self.publish_date = item["volumeInfo"]["publishedDate"]
if "authors" in item["volumeInfo"]: if "authors" in item["volumeInfo"]:
self.author = item["volumeInfo"]["authors"][0] self.author = item["volumeInfo"]["authors"][0]
def _manual_load(self, title, publisher=None, publish_date=None, author=None, count=-1, category=None): def _manual_load(self, title, author=None, category=None, status=0, owner=None):
self.title = title self.title = title
self.publisher = publisher
self.publish_date = publish_date
self.author = author self.author = author
self.count = count
self.category = category self.category = category
self.status = status
self.owner = owner
def load(self, config, loader="openlibrary"): def load(self, config, loader="openlibrary"):
if loader == "openlibrary": if loader == "openlibrary":
@ -100,11 +96,11 @@ class Book:
return { return {
"isbn": self.isbn, "isbn": self.isbn,
"title": self.title, "title": self.title,
"publisher": self.publisher, "owner": self.owner,
"publish_date": self.publish_date, "status": self.status,
"status_text": self.status_text,
"author": self.author, "author": self.author,
"category": self.category, "category": self.category
"count": self.count,
} }
def replace(self, pattern, replacement): def replace(self, pattern, replacement):
@ -116,8 +112,7 @@ class Book:
self.isbn = rep(self.isbn) self.isbn = rep(self.isbn)
self.title = rep(self.title) self.title = rep(self.title)
self.publisher = rep(self.publisher) self.owner = rep(self.owner)
self.publish_date = rep(self.publish_date) self.status = rep(self.status)
self.author = rep(self.author) self.author = rep(self.author)
self.category = rep(self.category) self.category = rep(self.category)
self.count = rep(self.count)

View File

@ -33,10 +33,9 @@ def get_book(isbn):
book = Book(isbn) book = Book(isbn)
book._manual_load( book._manual_load(
data["title"], data["title"],
publisher=data["publisher"],
publish_date=data["publish_date"],
author=data["author"], author=data["author"],
count=data["count"], status=data["status_"],
owner=data["owner_"],
category=data["category"] if data["category"] != "" else None category=data["category"] if data["category"] != "" else None
) )
return book return book
@ -52,36 +51,18 @@ def delete_book(isbn):
) )
db.commit() db.commit()
def increment_count(book):
if book.count == -1:
book = get_book(book.isbn)
db = get_db()
db.execute(
"""
UPDATE book SET count=?
WHERE isbn=?
""",
(book.count+1, book.isbn)
)
db.commit()
def add_book(book): def add_book(book):
try: try:
book = get_book(book.isbn) book = get_book(book.isbn)
increment_count(book)
return "duplicate" return "duplicate"
except IndexError: except IndexError:
if book.count == -1:
book.count = 1
db = get_db() db = get_db()
db.execute( db.execute(
""" """
INSERT INTO book (isbn, count, title, author, publisher, publish_date, category) INSERT INTO book (isbn, title, author, status_, owner_, category)
VALUES (?, ?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?)
""", """,
(book.isbn, book.count, book.title, book.author, book.publisher, book.publish_date, book.category) (book.isbn, book.title, book.author, book.status, book.owner, book.category)
) )
db.commit() db.commit()
return "added" return "added"
@ -90,10 +71,10 @@ def update_book(book):
db = get_db() db = get_db()
db.execute( db.execute(
""" """
UPDATE book SET count=?, title=?, author=?, publisher=?, publish_date=?, category=? UPDATE book SET title=?, author=?, status_=?, owner_=?, category=?
WHERE isbn=? WHERE isbn=?
""", """,
(book.count, book.title, book.author, book.publisher, book.publish_date, book.category, book.isbn) (book.title, book.author, book.status, book.owner, book.category, book.isbn)
) )
db.commit() db.commit()
return "updated" return "updated"
@ -105,9 +86,7 @@ def get_all_books():
count += 1 count += 1
if book.author is None: if book.author is None:
count += 1 count += 1
if book.publish_date is None: if book.owner is None:
count += 1
if book.publisher is None:
count += 1 count += 1
if book.category is None: if book.category is None:
count += 1 count += 1
@ -126,10 +105,9 @@ def get_all_books():
book = Book(data_row["isbn"]) book = Book(data_row["isbn"])
book._manual_load( book._manual_load(
data_row["title"], data_row["title"],
publisher=data_row["publisher"], status=data_row["status_"],
publish_date=data_row["publish_date"], owner=data_row["owner_"],
author=data_row["author"], author=data_row["author"],
count=data_row["count"],
category=data_row["category"] if data_row["category"] != "" else None category=data_row["category"] if data_row["category"] != "" else None
) )
books.append(book) books.append(book)

View File

@ -1,11 +1,17 @@
DROP TABLE IF EXISTS book; DROP TABLE IF EXISTS book;
DROP TABLE IF EXISTS user;
CREATE TABLE book ( CREATE TABLE book (
isbn INT PRIMARY KEY, isbn INT PRIMARY KEY,
count INT DEFAULT 1,
title TEXT, title TEXT,
author TEXT, author TEXT,
publisher TEXT, status_ INT DEFAULT 0, -- 0: A lire, 1: Commence, 2: Lu
publish_date TEXT, owner_ TEXT DEFAULT '',
category TEXT DEFAULT '', category TEXT DEFAULT ''
);
CREATE TABLE user (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE NOT NULL,
password TEXT NOT NULL
); );

View File

@ -27,7 +27,11 @@ function viewAddBook(book) {
} }
return '<p>'; return '<p>';
} }
let pc = (arg) => { if (arg > 1) return p("None"); else return p(arg); } let ps = (arg) => {
if (arg == 0) return p("None");
if (arg == 1) return '<p style="color=var(--color-orange);">'
else return '<p style="color=var(--color-blue);">';
}
let booksTableBody = document.getElementById("books-table").getElementsByTagName('tbody')[0]; let booksTableBody = document.getElementById("books-table").getElementsByTagName('tbody')[0];
@ -37,10 +41,9 @@ function viewAddBook(book) {
'<td>'+book.isbn+'</td>'+ '<td>'+book.isbn+'</td>'+
'<td>'+p(book.title)+book.title+'</p></td>'+ '<td>'+p(book.title)+book.title+'</p></td>'+
'<td>'+p(book.author)+book.author+'</p></td>'+ '<td>'+p(book.author)+book.author+'</p></td>'+
'<td>'+p(book.publish_date)+book.publish_date+'</p></td>'+ '<td>'+ps(book.status)+book.status_text+'</p></td>'+
'<td>'+p(book.publisher)+book.publisher+'</p></td>'+ '<td>'+p(book.owner)+book.owner+'</p></td>'+
'<td>'+p(book.category)+book.category+'</p></td>'+ '<td>'+p(book.category)+book.category+'</p></td>'+
'<td>'+pc(book.count)+book.count+'</p></td>'+
'<td>'+ '<td>'+
' <button class="action" onclick="openEditBookDialog('+book.isbn+')">✏️</button>'+ ' <button class="action" onclick="openEditBookDialog('+book.isbn+')">✏️</button>'+
' <button class="action" onclick="openDeleteBookDialog('+book.isbn+')">🗑️</button>'+ ' <button class="action" onclick="openDeleteBookDialog('+book.isbn+')">🗑️</button>'+

View File

@ -13,19 +13,17 @@ function getBookData(isbn) {
// Extract data from the row // Extract data from the row
var title = cells[1].innerText; var title = cells[1].innerText;
var author = cells[2].innerText; var author = cells[2].innerText;
var date = cells[3].innerText; var status = {"À lire": 0, "En cours": 1, "Lu": 2}[cells[3].innerText];
var publisher = cells[4].innerText; var owner = cells[4].innerText;
var category = cells[5].innerText; var category = cells[5].innerText;
var quantity = cells[6].innerText;
// Return the data // Return the data
return { return {
title: title, title: title,
author: author, author: author,
date: date, status: status,
publisher: publisher, owner: owner,
category: category, category: category,
quantity: quantity
}; };
} }
} }
@ -41,10 +39,9 @@ function openEditBookDialog(isbn) {
document.getElementById("edit-isbn").value = isbn; document.getElementById("edit-isbn").value = isbn;
document.getElementById("edit-title").value = bookData.title; document.getElementById("edit-title").value = bookData.title;
document.getElementById("edit-author").value = bookData.author; document.getElementById("edit-author").value = bookData.author;
document.getElementById("edit-date").value = bookData.date; document.getElementById("edit-owner").value = bookData.owner;
document.getElementById("edit-publisher").value = bookData.publisher; document.getElementById("edit-status").value = bookData.status;
document.getElementById("edit-category").value = bookData.category; document.getElementById("edit-category").value = bookData.category;
document.getElementById("edit-quantity").value = bookData.quantity;
editDialog.showModal(); editDialog.showModal();
} else { } else {
alert("Book not found!"); alert("Book not found!");
@ -82,4 +79,14 @@ function openDeleteBookDialog(isbn) {
function hideDeleteBookDialog() { function hideDeleteBookDialog() {
var deleteDialog = document.getElementById("delete-book-dialog"); var deleteDialog = document.getElementById("delete-book-dialog");
deleteDialog.close(); deleteDialog.close();
}
function categoryChange() {
edit_cat = document.getElementById("edit-category");
if (edit_cat.selectedOptions[0].text == "- Nouvelle catégorie -") {
new_cat = window.prompt("Nom de la catégorie ?", "");
if (new_cat == "") return;
edit_cat.innerHTML += '<option value="'+new_cat+'" selected>'+new_cat+'</option>';
}
} }

View File

@ -4,7 +4,7 @@
/* Themes used: Catppuccin Latte & Moccha /* Themes used: Catppuccin Latte & Moccha
* https://github.com/catppuccin/catppuccin */ * https://github.com/catppuccin/catppuccin */
/* Dark theme: Catpuccin Mocha /* Dark theme: Catpuccin Mocha */
:root { :root {
--color-rosewater: #f5e0dc; --color-rosewater: #f5e0dc;
--color-flamingo: #f2cdcd; --color-flamingo: #f2cdcd;
@ -33,10 +33,10 @@
--color-mantle: #181825; --color-mantle: #181825;
--color-crust: #11111b; --color-crust: #11111b;
--color-action: #333d5d; --color-action: #333d5d;
}*/ }
/* Light theme: Catppuccin Latte */ /* Light theme: Catppuccin Latte */
/*@media (prefers-color-scheme: light) {*/ @media (prefers-color-scheme: light) {
:root { :root {
--color-rosewater: #dc8a78; --color-rosewater: #dc8a78;
--color-flamingo: #dd7878; --color-flamingo: #dd7878;
@ -66,7 +66,7 @@
--color-crust: #dce0e8; --color-crust: #dce0e8;
--color-action: #cdd6f4; --color-action: #cdd6f4;
} }
/*}*/ }
a { a {
@ -113,6 +113,7 @@ body {
background-color: var(--color-base); background-color: var(--color-base);
color: var(--color-text); color: var(--color-text);
text-align: center; text-align: center;
margin: 0px;
} }
#logout { #logout {
@ -236,6 +237,10 @@ code {
color: var(--color-red); color: var(--color-red);
} }
.green {
color: var(--color-green);
}
/** Style for table: alternate colors */ /** Style for table: alternate colors */
table { table {
border-collapse: collapse; border-collapse: collapse;
@ -244,6 +249,7 @@ table {
border-color: var(--color-crust); border-color: var(--color-crust);
border-width: 2px; border-width: 2px;
border-style: solid; border-style: solid;
border-radius: 5px;
} }
.action { .action {
@ -295,3 +301,26 @@ table tr td:last-of-type {
max-width: 100vw; max-width: 100vw;
overflow-x: scroll; overflow-x: scroll;
} }
#container {
margin: 20px;
}
header {
position: sticky;
align-items: center;
justify-content: space-between;
display: flex;
background-color: var(--color-surface0);
color: var(--color-text);
top: 0;
padding: 20px;
margin-bottom: 20px;
}
header > h1 {
margin: 0px;
}

View File

@ -11,6 +11,12 @@
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}"> <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
</head> </head>
<body> <body>
<header id="page-header">
<h1>📚 ISBN-DB</h1>
{% if g.user %}
<a href="/app/auth/logout"><button>Logout</button></a>
{% endif %}
</header>
<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

@ -16,21 +16,24 @@
<input type="text" id="edit-title" name="title"><br> <input type="text" id="edit-title" name="title"><br>
<label for="edit-author">Auteur:</label><br> <label for="edit-author">Auteur:</label><br>
<input type="text" id="edit-author" name="author"><br> <input type="text" id="edit-author" name="author"><br>
<label for="edit-date">Date:</label><br> <label for="edit-owner">Propriétaire:</label><br>
<input type="text" id="edit-date" name="publish_date"><br> <input type="text" id="edit-owner" name="owner"><br>
<label for="edit-publisher">Éditeur:</label><br> <label for="edit-status">État:</label><br>
<input type="text" id="edit-publisher" name="publisher"><br> <select id="edit-status" name="status">
<option value="0">À lire</option>
<option value="1">En cours</option>
<option value="2">Lu</option>
</select>
<label for="edit-category">Catégorie:</label><br> <label for="edit-category">Catégorie:</label><br>
<select id="edit-category" name="category"> <select id="edit-category" name="category" onchange="categoryChange()">
<option value="">- Pas de catégorie -</option> <option value="">- Pas de catégorie -</option>
<option value="">- Nouvelle catégorie -</option>
{% for category in categories %} {% for category in categories %}
{% if category != "" %} {% if category != "" %}
<option value="{{ category }}">{{ category }}</option> <option value="{{ category }}">{{ category }}</option>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
</select><br> </select><br>
<label for="edit-quantity">Quantité:</label><br>
<input type="number" id="edit-quantity" name="count"><br>
<input type="submit" value="Mettre à jour"> <input type="submit" value="Mettre à jour">
</form> </form>
<button onclick="hideEditBookDialog()">Annuler</button> <button onclick="hideEditBookDialog()">Annuler</button>
@ -45,11 +48,13 @@
<button id="cancel-delete">Annuler</button> <button id="cancel-delete">Annuler</button>
</dialog> </dialog>
<div id="add-book"> <div id="add-book">
Ajouter manuellement <details>
<form action="/app/web-submit-isbn"> <summary>Ajouter manuellement</summary>
<input type="text" name="isbn" placeholder="ISBN"> <form action="/app/web-submit-isbn">
<input type="submit" value="Ajouter"> <input type="text" name="isbn" placeholder="ISBN">
</form> <input type="submit" value="Ajouter">
</form>
</details>
</div> </div>
<div id="table-container"> <div id="table-container">
<table id="books-table"> <table id="books-table">
@ -57,10 +62,9 @@
<th>ISBN</th> <th>ISBN</th>
<th>Titre</th> <th>Titre</th>
<th>Auteur</th> <th>Auteur</th>
<th>Date</th> <th>État</th>
<th>Éditeur</th> <th>Propriétaire</th>
<th>Catégorie</th> <th>Catégorie</th>
<th>Quantité</th>
<th>Actions</th> <th>Actions</th>
</tr> </tr>
{% for book in books %} {% for book in books %}
@ -68,10 +72,9 @@
<td>{{ book.isbn }}</td> <td>{{ book.isbn }}</td>
<td><p {% if book.title == None %}class="red"{% endif %}>{{ book.title }}</p></td> <td><p {% if book.title == None %}class="red"{% endif %}>{{ book.title }}</p></td>
<td><p {% if book.author == None %}class="red"{% endif %}>{{ book.author }}</p></td> <td><p {% if book.author == None %}class="red"{% endif %}>{{ book.author }}</p></td>
<td><p {% if book.publish_date == None %}class="red"{% endif %}>{{ book.publish_date }}</p></td> <td><p {% if book.status == 0 %}class="red"{% elif book.status==2 %}class="green"{% endif %}>{{ book.status_text }}</p></td>
<td><p {% if book.publisher == None %}class="red"{% endif %}>{{ book.publisher }}</p></td> <td><p {% if book.owner == None %}class="red"{% endif %}>{{ book.owner }}</p></td>
<td><p {% if book.category == None %}class="red"{% endif %}>{{ book.category }}</p></td> <td><p {% if book.category == None %}class="red"{% endif %}>{{ book.category }}</p></td>
<td><p {% if book.count != 1 %}class="red"{% endif %}>{{ book.count }}</p></td>
<td> <td>
<button class="action" onclick='openEditBookDialog("{{ book.isbn }}")'>✏️</button> <button class="action" onclick='openEditBookDialog("{{ book.isbn }}")'>✏️</button>
<button class="action" onclick='openDeleteBookDialog("{{ book.isbn }}")'>🗑️</button> <button class="action" onclick='openDeleteBookDialog("{{ book.isbn }}")'>🗑️</button>

View File

@ -0,0 +1,12 @@
{% extends 'base.html' %}
{% block content %}
<h2>{% block title %}Log in{% endblock %}</h2>
<form method="post">
<input type="text" name="username" id="username" placeholder="Username" required><br/>
<input type="password" name="password" id="password" placeholder="Password" required><br/>
<input type="submit" value="Log in">
</form>
{% endblock %}

3
start.sh Normal file → Executable file
View File

@ -1 +1,4 @@
#!/bin/bash
# FLASK_ENV=development flask run --port=5000 --debug
flask run --host=0.0.0.0 --port=5000 flask run --host=0.0.0.0 --port=5000