This will need some cleaning in a near future
This commit is contained in:
parent
8fc94603db
commit
c884260238
@ -2,11 +2,13 @@ from flask import render_template, redirect, request, flash, Blueprint, current_
|
||||
|
||||
import json
|
||||
|
||||
from .book import Book
|
||||
from . import isbn_db
|
||||
from . import sse
|
||||
from .modules.book import Book
|
||||
from .modules import db as isbn_db
|
||||
from .modules import auth
|
||||
from .modules import sse
|
||||
|
||||
bp = Blueprint("app", __name__, url_prefix="/app")
|
||||
bp.register_blueprint(auth.bp)
|
||||
|
||||
announcer = sse.MessageAnnouncer()
|
||||
|
||||
@ -22,6 +24,7 @@ def announce_book(book, msg_type: str="add_book") -> None:
|
||||
|
||||
|
||||
@bp.route("/submit-isbn")
|
||||
@auth.login_required
|
||||
def submit_isbn():
|
||||
if "isbn" not in request.args:
|
||||
return "/submit-isbn?isbn=xxxxxx"
|
||||
@ -38,7 +41,7 @@ def submit_isbn():
|
||||
book = isbn_db.get_book(book.isbn)
|
||||
assert isbn_db.add_book(book) == "duplicate" # duplicate
|
||||
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:
|
||||
pass
|
||||
|
||||
@ -65,12 +68,14 @@ def submit_isbn():
|
||||
|
||||
|
||||
@bp.route("/web-submit-isbn")
|
||||
@auth.login_required
|
||||
def web_submit_isbn():
|
||||
flash(submit_isbn())
|
||||
return redirect(url_for("app.index"))
|
||||
|
||||
|
||||
@bp.route("/")
|
||||
@auth.login_required
|
||||
def index():
|
||||
return render_template(
|
||||
"index.html",
|
||||
@ -80,6 +85,7 @@ def index():
|
||||
|
||||
|
||||
@bp.route("/delete-book", methods=["POST"])
|
||||
@auth.login_required
|
||||
def delete_book():
|
||||
if "isbn" not in request.form:
|
||||
return "missing isbn"
|
||||
@ -96,8 +102,9 @@ def delete_book():
|
||||
|
||||
|
||||
@bp.route("/update-book", methods=["POST"])
|
||||
@auth.login_required
|
||||
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]:
|
||||
return "missing an attribute"
|
||||
|
||||
@ -110,10 +117,9 @@ def update_book():
|
||||
book = Book(form_data["isbn"])
|
||||
book._manual_load(
|
||||
form_data["title"],
|
||||
publisher=form_data["publisher"],
|
||||
publish_date=form_data["publish_date"],
|
||||
status=int(form_data["status"]),
|
||||
owner=form_data["owner"],
|
||||
author=form_data["author"],
|
||||
count=int(form_data["count"]),
|
||||
category=form_data["category"]
|
||||
)
|
||||
isbn_db.update_book(book)
|
||||
@ -122,13 +128,14 @@ def update_book():
|
||||
|
||||
|
||||
@bp.route("/export-csv")
|
||||
@auth.login_required
|
||||
def export_csv():
|
||||
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:
|
||||
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 current_app.response_class(
|
||||
@ -139,6 +146,7 @@ def export_csv():
|
||||
|
||||
|
||||
@bp.route('/listen', methods=['GET'])
|
||||
@auth.login_required
|
||||
def listen():
|
||||
def stream():
|
||||
messages = announcer.listen() # returns a queue.Queue
|
||||
@ -150,6 +158,7 @@ def listen():
|
||||
|
||||
|
||||
@bp.route('/ping')
|
||||
@auth.login_required
|
||||
def ping():
|
||||
msg = sse.format_sse(data={"type": "pong"})
|
||||
announcer.announce(msg=msg)
|
||||
|
@ -6,7 +6,7 @@ import subprocess
|
||||
import sys
|
||||
import os
|
||||
|
||||
import book as bk
|
||||
from modules import book as bk
|
||||
|
||||
|
||||
def clip_copy(data):
|
||||
|
4
isbn_sort/init.sql
Normal file
4
isbn_sort/init.sql
Normal 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
115
isbn_sort/modules/auth.py
Normal 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("/")
|
@ -15,11 +15,19 @@ class Book:
|
||||
|
||||
self.isbn = isbn
|
||||
self.title = None
|
||||
self.publisher = None
|
||||
self.publish_date = None
|
||||
self.owner = None
|
||||
self.status = 0
|
||||
self.author = None
|
||||
self.category = None
|
||||
self.count = -1
|
||||
|
||||
@property
|
||||
def status_text(self) -> str:
|
||||
return [
|
||||
"À lire",
|
||||
"En cours",
|
||||
"Lu"
|
||||
][self.status]
|
||||
|
||||
|
||||
def _openlibrary_load(self, _):
|
||||
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}"]
|
||||
|
||||
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:
|
||||
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):
|
||||
if config["GOOGLE_BOOKS_KEY"] is None:
|
||||
@ -65,23 +68,16 @@ class Book:
|
||||
|
||||
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"]:
|
||||
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.publisher = publisher
|
||||
self.publish_date = publish_date
|
||||
self.author = author
|
||||
self.count = count
|
||||
self.category = category
|
||||
self.status = status
|
||||
self.owner = owner
|
||||
|
||||
def load(self, config, loader="openlibrary"):
|
||||
if loader == "openlibrary":
|
||||
@ -100,11 +96,11 @@ class Book:
|
||||
return {
|
||||
"isbn": self.isbn,
|
||||
"title": self.title,
|
||||
"publisher": self.publisher,
|
||||
"publish_date": self.publish_date,
|
||||
"owner": self.owner,
|
||||
"status": self.status,
|
||||
"status_text": self.status_text,
|
||||
"author": self.author,
|
||||
"category": self.category,
|
||||
"count": self.count,
|
||||
"category": self.category
|
||||
}
|
||||
|
||||
def replace(self, pattern, replacement):
|
||||
@ -116,8 +112,7 @@ class Book:
|
||||
|
||||
self.isbn = rep(self.isbn)
|
||||
self.title = rep(self.title)
|
||||
self.publisher = rep(self.publisher)
|
||||
self.publish_date = rep(self.publish_date)
|
||||
self.owner = rep(self.owner)
|
||||
self.status = rep(self.status)
|
||||
self.author = rep(self.author)
|
||||
self.category = rep(self.category)
|
||||
self.count = rep(self.count)
|
@ -33,10 +33,9 @@ def get_book(isbn):
|
||||
book = Book(isbn)
|
||||
book._manual_load(
|
||||
data["title"],
|
||||
publisher=data["publisher"],
|
||||
publish_date=data["publish_date"],
|
||||
author=data["author"],
|
||||
count=data["count"],
|
||||
status=data["status_"],
|
||||
owner=data["owner_"],
|
||||
category=data["category"] if data["category"] != "" else None
|
||||
)
|
||||
return book
|
||||
@ -52,36 +51,18 @@ def delete_book(isbn):
|
||||
)
|
||||
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):
|
||||
try:
|
||||
book = get_book(book.isbn)
|
||||
increment_count(book)
|
||||
return "duplicate"
|
||||
except IndexError:
|
||||
if book.count == -1:
|
||||
book.count = 1
|
||||
|
||||
db = get_db()
|
||||
db.execute(
|
||||
"""
|
||||
INSERT INTO book (isbn, count, title, author, publisher, publish_date, category)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
INSERT INTO book (isbn, title, author, status_, owner_, category)
|
||||
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()
|
||||
return "added"
|
||||
@ -90,10 +71,10 @@ def update_book(book):
|
||||
db = get_db()
|
||||
db.execute(
|
||||
"""
|
||||
UPDATE book SET count=?, title=?, author=?, publisher=?, publish_date=?, category=?
|
||||
UPDATE book SET title=?, author=?, status_=?, owner_=?, category=?
|
||||
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()
|
||||
return "updated"
|
||||
@ -105,9 +86,7 @@ def get_all_books():
|
||||
count += 1
|
||||
if book.author is None:
|
||||
count += 1
|
||||
if book.publish_date is None:
|
||||
count += 1
|
||||
if book.publisher is None:
|
||||
if book.owner is None:
|
||||
count += 1
|
||||
if book.category is None:
|
||||
count += 1
|
||||
@ -126,10 +105,9 @@ def get_all_books():
|
||||
book = Book(data_row["isbn"])
|
||||
book._manual_load(
|
||||
data_row["title"],
|
||||
publisher=data_row["publisher"],
|
||||
publish_date=data_row["publish_date"],
|
||||
status=data_row["status_"],
|
||||
owner=data_row["owner_"],
|
||||
author=data_row["author"],
|
||||
count=data_row["count"],
|
||||
category=data_row["category"] if data_row["category"] != "" else None
|
||||
)
|
||||
books.append(book)
|
@ -1,11 +1,17 @@
|
||||
DROP TABLE IF EXISTS book;
|
||||
DROP TABLE IF EXISTS user;
|
||||
|
||||
CREATE TABLE book (
|
||||
isbn INT PRIMARY KEY,
|
||||
count INT DEFAULT 1,
|
||||
title TEXT,
|
||||
author TEXT,
|
||||
publisher TEXT,
|
||||
publish_date TEXT,
|
||||
category TEXT DEFAULT '',
|
||||
status_ INT DEFAULT 0, -- 0: A lire, 1: Commence, 2: Lu
|
||||
owner_ TEXT DEFAULT '',
|
||||
category TEXT DEFAULT ''
|
||||
);
|
||||
|
||||
CREATE TABLE user (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
username TEXT UNIQUE NOT NULL,
|
||||
password TEXT NOT NULL
|
||||
);
|
@ -27,7 +27,11 @@ function viewAddBook(book) {
|
||||
}
|
||||
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];
|
||||
|
||||
@ -37,10 +41,9 @@ function viewAddBook(book) {
|
||||
'<td>'+book.isbn+'</td>'+
|
||||
'<td>'+p(book.title)+book.title+'</p></td>'+
|
||||
'<td>'+p(book.author)+book.author+'</p></td>'+
|
||||
'<td>'+p(book.publish_date)+book.publish_date+'</p></td>'+
|
||||
'<td>'+p(book.publisher)+book.publisher+'</p></td>'+
|
||||
'<td>'+ps(book.status)+book.status_text+'</p></td>'+
|
||||
'<td>'+p(book.owner)+book.owner+'</p></td>'+
|
||||
'<td>'+p(book.category)+book.category+'</p></td>'+
|
||||
'<td>'+pc(book.count)+book.count+'</p></td>'+
|
||||
'<td>'+
|
||||
' <button class="action" onclick="openEditBookDialog('+book.isbn+')">✏️</button>'+
|
||||
' <button class="action" onclick="openDeleteBookDialog('+book.isbn+')">🗑️</button>'+
|
||||
|
@ -13,19 +13,17 @@ function getBookData(isbn) {
|
||||
// Extract data from the row
|
||||
var title = cells[1].innerText;
|
||||
var author = cells[2].innerText;
|
||||
var date = cells[3].innerText;
|
||||
var publisher = cells[4].innerText;
|
||||
var status = {"À lire": 0, "En cours": 1, "Lu": 2}[cells[3].innerText];
|
||||
var owner = cells[4].innerText;
|
||||
var category = cells[5].innerText;
|
||||
var quantity = cells[6].innerText;
|
||||
|
||||
// Return the data
|
||||
return {
|
||||
title: title,
|
||||
author: author,
|
||||
date: date,
|
||||
publisher: publisher,
|
||||
status: status,
|
||||
owner: owner,
|
||||
category: category,
|
||||
quantity: quantity
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -41,10 +39,9 @@ function openEditBookDialog(isbn) {
|
||||
document.getElementById("edit-isbn").value = isbn;
|
||||
document.getElementById("edit-title").value = bookData.title;
|
||||
document.getElementById("edit-author").value = bookData.author;
|
||||
document.getElementById("edit-date").value = bookData.date;
|
||||
document.getElementById("edit-publisher").value = bookData.publisher;
|
||||
document.getElementById("edit-owner").value = bookData.owner;
|
||||
document.getElementById("edit-status").value = bookData.status;
|
||||
document.getElementById("edit-category").value = bookData.category;
|
||||
document.getElementById("edit-quantity").value = bookData.quantity;
|
||||
editDialog.showModal();
|
||||
} else {
|
||||
alert("Book not found!");
|
||||
@ -83,3 +80,13 @@ function hideDeleteBookDialog() {
|
||||
var deleteDialog = document.getElementById("delete-book-dialog");
|
||||
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>';
|
||||
}
|
||||
}
|
@ -4,7 +4,7 @@
|
||||
/* Themes used: Catppuccin Latte & Moccha
|
||||
* https://github.com/catppuccin/catppuccin */
|
||||
|
||||
/* Dark theme: Catpuccin Mocha
|
||||
/* Dark theme: Catpuccin Mocha */
|
||||
:root {
|
||||
--color-rosewater: #f5e0dc;
|
||||
--color-flamingo: #f2cdcd;
|
||||
@ -33,10 +33,10 @@
|
||||
--color-mantle: #181825;
|
||||
--color-crust: #11111b;
|
||||
--color-action: #333d5d;
|
||||
}*/
|
||||
}
|
||||
|
||||
/* Light theme: Catppuccin Latte */
|
||||
/*@media (prefers-color-scheme: light) {*/
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root {
|
||||
--color-rosewater: #dc8a78;
|
||||
--color-flamingo: #dd7878;
|
||||
@ -66,7 +66,7 @@
|
||||
--color-crust: #dce0e8;
|
||||
--color-action: #cdd6f4;
|
||||
}
|
||||
/*}*/
|
||||
}
|
||||
|
||||
|
||||
a {
|
||||
@ -113,6 +113,7 @@ body {
|
||||
background-color: var(--color-base);
|
||||
color: var(--color-text);
|
||||
text-align: center;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
#logout {
|
||||
@ -236,6 +237,10 @@ code {
|
||||
color: var(--color-red);
|
||||
}
|
||||
|
||||
.green {
|
||||
color: var(--color-green);
|
||||
}
|
||||
|
||||
/** Style for table: alternate colors */
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
@ -244,6 +249,7 @@ table {
|
||||
border-color: var(--color-crust);
|
||||
border-width: 2px;
|
||||
border-style: solid;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.action {
|
||||
@ -295,3 +301,26 @@ table tr td:last-of-type {
|
||||
max-width: 100vw;
|
||||
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;
|
||||
}
|
@ -11,6 +11,12 @@
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
||||
</head>
|
||||
<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">
|
||||
{% for message in get_flashed_messages() %}
|
||||
<div class="flash">{{ message }}</div>
|
||||
|
@ -16,21 +16,24 @@
|
||||
<input type="text" id="edit-title" name="title"><br>
|
||||
<label for="edit-author">Auteur:</label><br>
|
||||
<input type="text" id="edit-author" name="author"><br>
|
||||
<label for="edit-date">Date:</label><br>
|
||||
<input type="text" id="edit-date" name="publish_date"><br>
|
||||
<label for="edit-publisher">Éditeur:</label><br>
|
||||
<input type="text" id="edit-publisher" name="publisher"><br>
|
||||
<label for="edit-owner">Propriétaire:</label><br>
|
||||
<input type="text" id="edit-owner" name="owner"><br>
|
||||
<label for="edit-status">État:</label><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>
|
||||
<select id="edit-category" name="category">
|
||||
<select id="edit-category" name="category" onchange="categoryChange()">
|
||||
<option value="">- Pas de catégorie -</option>
|
||||
<option value="">- Nouvelle catégorie -</option>
|
||||
{% for category in categories %}
|
||||
{% if category != "" %}
|
||||
<option value="{{ category }}">{{ category }}</option>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</select><br>
|
||||
<label for="edit-quantity">Quantité:</label><br>
|
||||
<input type="number" id="edit-quantity" name="count"><br>
|
||||
<input type="submit" value="Mettre à jour">
|
||||
</form>
|
||||
<button onclick="hideEditBookDialog()">Annuler</button>
|
||||
@ -45,11 +48,13 @@
|
||||
<button id="cancel-delete">Annuler</button>
|
||||
</dialog>
|
||||
<div id="add-book">
|
||||
Ajouter manuellement
|
||||
<form action="/app/web-submit-isbn">
|
||||
<input type="text" name="isbn" placeholder="ISBN">
|
||||
<input type="submit" value="Ajouter">
|
||||
</form>
|
||||
<details>
|
||||
<summary>Ajouter manuellement</summary>
|
||||
<form action="/app/web-submit-isbn">
|
||||
<input type="text" name="isbn" placeholder="ISBN">
|
||||
<input type="submit" value="Ajouter">
|
||||
</form>
|
||||
</details>
|
||||
</div>
|
||||
<div id="table-container">
|
||||
<table id="books-table">
|
||||
@ -57,10 +62,9 @@
|
||||
<th>ISBN</th>
|
||||
<th>Titre</th>
|
||||
<th>Auteur</th>
|
||||
<th>Date</th>
|
||||
<th>Éditeur</th>
|
||||
<th>État</th>
|
||||
<th>Propriétaire</th>
|
||||
<th>Catégorie</th>
|
||||
<th>Quantité</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
{% for book in books %}
|
||||
@ -68,10 +72,9 @@
|
||||
<td>{{ book.isbn }}</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.publish_date == None %}class="red"{% endif %}>{{ book.publish_date }}</p></td>
|
||||
<td><p {% if book.publisher == None %}class="red"{% endif %}>{{ book.publisher }}</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.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.count != 1 %}class="red"{% endif %}>{{ book.count }}</p></td>
|
||||
<td>
|
||||
<button class="action" onclick='openEditBookDialog("{{ book.isbn }}")'>✏️</button>
|
||||
<button class="action" onclick='openDeleteBookDialog("{{ book.isbn }}")'>🗑️</button>
|
||||
|
12
isbn_sort/templates/login.html
Normal file
12
isbn_sort/templates/login.html
Normal 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 %}
|
Loading…
Reference in New Issue
Block a user