Mirrored from https://github.com/LeeRoySheep/BookAlchemy.git
Synced Nov 2, 19:30 UTC
Added 2 routes to add author and to add book
Lee Roy Stevenson committed
Mar 29, 2025 at 05:25 UTC
0f2cad543d52060a1539e437ea7f1db54a4b2ce2
10 files changed
+487
-3
app.py
+138
-1
@@ -1,6 +1,6 @@
1
from datetime import datetime
2
from flask import Flask, jsonify,render_template, request
3
-from flask_sqlalchemy import SQLAlchemy
3
+from sqlalchemy import desc
4
5
6
from data_models import db, Author, Book
@@ -14,6 +14,41 @@ app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///data/library.sqlite"
14
# Connect Flask to Database
15
db.init_app(app)
16
17
+@app.route("/", methods=["GET","POST"])
18
+def home():
19
+ """
20
+ Route to home page with POST to sort the books by title, author or year
21
+ :return:
22
+ """
23
+ if request.method == "POST":
24
+ sort_by = request.form.get("options")
25
+ if sort_by == "title":
26
+ sort_by = getattr(Book, 'title',None)
27
+ elif sort_by == "author":
28
+ sort_by = getattr(Author, 'name',None)
29
+ elif sort_by == "year":
30
+ sort_by = getattr(Book, 'publication_year',None)
31
+ descending = request.form.get("descending")
32
+ if descending:
33
+ books = db.session.query(Book.id,Book.isbn,Book.title,
34
+ Author.name, Book.author_id,
35
+ Book.publication_year).join(Author) \
36
+ .order_by(desc(sort_by)).all()
37
+ else:
38
+ books = db.session.query(Book.id,Book.isbn,Book.title,
39
+ Author.name, Book.author_id,
40
+ Book.publication_year).join(Author) \
41
+ .order_by(sort_by).all()
42
+ print(books)
43
+ return render_template("home.html", books=books)
44
+ else:
45
+ books = db.session.query(Book.id,Book.isbn,Book.title, Author.name,
46
+ Book.author_id,Book.publication_year) \
47
+ .join(Author).all()
48
+
49
+ return render_template("home.html", books=books)
50
+
51
+
52
@app.route("/add_author", methods=["GET", "POST"])
53
def add_author():
54
"""
@@ -57,6 +92,10 @@ def add_author():
92
# Add Book Route ---------------------------------------------------
93
@app.route("/add_book", methods=["GET", "POST"])
94
def add_book():
95
+ """
96
+ Route to add book with POST or search book/s with GET
97
+ :return:
98
+ """
99
if request.method == "GET":
100
title = request.args.get("title", "")
101
year = request.args.get("year", "")
@@ -114,6 +153,104 @@ def add_book():
153
print(e)
154
return render_template("add_book.html", success=False)
155
156
+
157
+@app.route('/search', methods=['POST'])
158
+def search():
159
+ """
160
+ Route to search books with POST and any search term containing title, author or year
161
+ :return:
162
+ """
163
+ title = request.form['search']
164
+ title = '%' + title + '%'
165
+ books = db.session.query(Book.id,Book.isbn,Book.title,
166
+ Author.name, Book.author_id, Book.publication_year) \
167
+ .join(Author).filter(Book.title.contains(title) \
168
+ | Author.name.contains(title) | Book.publication_year \
169
+ .contains(title)).all()
170
+ if len(books) == 0:
171
+ return render_template("home.html", error=True)
172
+ return render_template("home.html", books=books)
173
+
174
+
175
+@app.route('/book/<int:book_id>/delete', methods=['POST'])
176
+def delete_book(book_id):
177
+ """
178
+ Route to delete book with given id and author if it is the last book
179
+ :param book_id:
180
+ :return:
181
+ """
182
+ if request.method != "POST":
183
+ books = db.session.query(Book.id,Book.isbn,Book.title,
184
+ Author.name, Book.author_id,
185
+ Book.publication_year).join(Author).all()
186
+ return render_template("home.html",books=books)
187
+ book = Book.query.get(book_id)
188
+ if book:
189
+ author_id = book.author_id
190
+ db.session.delete(book)
191
+ db.session.commit()
192
+ books_auth=db.session.query(Book.title).filter(Book.author_id == author_id ).all()
193
+ if len(books_auth) == 0:
194
+ author = Author.query.get(author_id)
195
+ db.session.delete(author)
196
+ db.session.commit()
197
+ books = db.session.query(Book.id,Book.isbn,Book.title,
198
+ Author.name, Book.author_id,
199
+ Book.publication_year).join(Author).all()
200
+ return render_template('home.html',books=books,deleted=True)
201
+ else:
202
+ books = db.session.query(Book.id,Book.isbn,Book.title,
203
+ Author.name, Book.author_id,
204
+ Book.publication_year).join(Author).all()
205
+ return render_template('home.html',books=books,deleted=False)
206
+
207
+
208
+@app.route('/author/<int:author_id>/delete', methods=['POST'])
209
+def delete_author(author_id):
210
+ """
211
+ Route to delete author with given id
212
+ :param author_id:
213
+ :return:
214
+ """
215
+ author = Author.query.get(author_id)
216
+ if author:
217
+ db.session.delete(author)
218
+ db.session.commit()
219
+ books = db.session.query(Book.id,Book.isbn,Book.title,
220
+ Author.name, Book.author_id,
221
+ Book.publication_year).join(Author).all()
222
+ return render_template('home.html',books=books,auth_deleted=True)
223
+ else:
224
+ books = db.session.query(Book.id,Book.isbn,Book.title,
225
+ Author.name, Book.author_id,
226
+ Book.publication_year).join(Author).all()
227
+ return render_template('home.html',books=books,auth_deleted=False)
228
+
229
+
230
+@app.route('/author/<int:author_id>', methods=['GET'])
231
+def author(author_id):
232
+ """
233
+ Route to display author details with given id
234
+ :param author_id:
235
+ :return:
236
+ """
237
+ author = Author.query.get(author_id)
238
+ return render_template("details_author.html", author=author)
239
+
240
+
241
+@app.route('/book/<int:book_id>', methods=['GET'])
242
+def book(book_id):
243
+ """
244
+ Route to display book details with given id
245
+ :param book_id:
246
+ :return:
247
+ """
248
+ book = db.session.query(Book.isbn,Book.title, \
249
+ Book.publication_year,Author.name) \
250
+ .join(Author).filter(Book.id == book_id).one()._mapping
251
+ print(book)
252
+ return render_template("details_book.html", book=book)
253
+
254
app.run(debug=True)
255
256
# Only needed to create datatables at the beginning of the project
data_models.py
+1
@@ -9,6 +9,7 @@ class Author(db.Model):
9
birth_date = db.Column(db.Date)
10
date_of_death = db.Column(db.Date)
11
12
+
13
def __str__(self):
14
output = f'Name: {self.name}, Birth Date: {self.birth_date}'
15
return output
instance/data/library.sqlite
Binary files a/instance/data/library.sqlite and b/instance/data/library.sqlite differ
instance/data/library2.sqlite
static/style.css
new
+156
@@ -0,0 +1,156 @@
1
+/* General Styles */
2
+body {
3
+ font-family: Arial, sans-serif;
4
+ background-color: #111111;
5
+ color: #333;
6
+ margin: 0;
7
+ padding: 0;
8
+ text-align: center;
9
+}
10
+
11
+/* Header */
12
+header {
13
+ background: #34191e;
14
+ color: white;
15
+ padding: 20px;
16
+ text-align: center;
17
+}
18
+h1 {
19
+ margin: 0;
20
+ font-size: 2rem;
21
+}
22
+main label{
23
+ margin: 0 auto;
24
+ color: #C1D1B1;
25
+ padding: 20px;
26
+}
27
+/* Navigation */
28
+nav {
29
+ display: flex;
30
+ justify-content: space-between;
31
+ align-items: center;
32
+ padding: 10px 20px;
33
+ background: #64491e;
34
+ border-radius: 5px;
35
+}
36
+
37
+.nav-links {
38
+ list-style: none;
39
+ padding: 0;
40
+ margin: 0;
41
+ display: flex;
42
+}
43
+.nav-links li {
44
+ margin: 0 15px;
45
+}
46
+.nav-links a {
47
+ color: white;
48
+ text-decoration: none;
49
+ font-size: 1rem;
50
+ transition: color 0.3s;
51
+}
52
+.nav-links a:hover {
53
+ color: #f39c12;
54
+}
55
+
56
+/* Forms */
57
+.actions {
58
+ display: flex;
59
+ gap: 15px;
60
+}
61
+form {
62
+ display: inline-block;
63
+}
64
+input, select {
65
+ padding: 8px;
66
+ margin: 5px;
67
+ border-radius: 5px;
68
+ border: 1px solid #ccc;
69
+}
70
+input[type="submit"], .delete-btn {
71
+ background: #e74c3c;
72
+ color: white;
73
+ border: none;
74
+ padding: 8px 12px;
75
+ cursor: pointer;
76
+ transition: 0.3s;
77
+}
78
+input[type="submit"]:hover, .delete-btn:hover {
79
+ background: #c0392b;
80
+}
81
+
82
+/* Books Section */
83
+.books {
84
+ padding: 20px;
85
+}
86
+
87
+/* Book Grid */
88
+.book-grid {
89
+ display: grid;
90
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
91
+ gap: 20px;
92
+ margin-top: 20px;
93
+ padding: 0 20px;
94
+}
95
+
96
+/* Book Cards */
97
+.book-card {
98
+ background: white;
99
+ padding: 15px;
100
+ border-radius: 8px;
101
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
102
+ transition: 0.3s;
103
+}
104
+.book-card:hover {
105
+ transform: scale(1.05);
106
+}
107
+.book-card h3 {
108
+ font-size: 1.2rem;
109
+ color: #2c3e50;
110
+}
111
+.book-card h3 span {
112
+ font-size: 0.9rem;
113
+ color: #7f8c8d;
114
+}
115
+.author-link span:hover {
116
+ color: black;
117
+ transition: 0.4s;
118
+}
119
+.book-card img {
120
+ width: 100px;
121
+ margin: 10px auto;
122
+ display: block;
123
+}
124
+.book-card p {
125
+ font-size: 0.9rem;
126
+}
127
+.book-link:hover {
128
+ color: #f39c12;
129
+ transition: 0.2s;
130
+}
131
+.author-link:hover {
132
+ color: black;
133
+ transition: 0.2s;
134
+}
135
+
136
+/* Error Messages */
137
+.error {
138
+ color: #e74c3c;
139
+}
140
+.success {
141
+ color: #2ecc71;
142
+}
143
+
144
+/* Responsive */
145
+@media (max-width: 768px) {
146
+ nav {
147
+ flex-direction: column;
148
+ text-align: center;
149
+ }
150
+ .actions {
151
+ flex-direction: column;
152
+ }
153
+ .book-grid {
154
+ grid-template-columns: 1fr;
155
+ }
156
+}
templates/add_author.html
+5
-1
@@ -4,8 +4,11 @@
4
<meta charset="UTF-8">
5
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6
<meta http-equiv="X-UA-Compatible" content="ie=edge">
7
+ <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
8
</head>
9
<body>
10
+ <header><h1>➕ Add Author:</h1></header>
11
+ <main>
12
{% if success %}
13
<h2>Author added successfully!</h2>
14
{% elif success==False %}
@@ -23,6 +26,7 @@
26
27
<input type="submit" value="Add Author">
28
</form>
26
-
29
+ <a href="/"><button>Home</button></a>
30
+ </main>
31
</body>
32
</html>
\ No newline at end of file
templates/add_book.html
+5
-1
@@ -4,8 +4,11 @@
4
<meta charset="UTF-8">
5
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6
<meta http-equiv="X-UA-Compatible" content="ie=edge">
7
+ <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
8
</head>
9
<body>
10
+ <header><h1>📖 Add Book:</h1></header>
11
+ <main>
12
{% if success %}
13
<h2>Book added successfully!</h2>
14
{% elif success==False %}
@@ -27,6 +30,7 @@
30
31
<input type="submit" value="Add Book">
32
</form>
30
-
33
+ <a href="/"><button>Home</button></a>
34
+ </main>
35
</body>
36
</html>
templates/details_author.html
new
+45
@@ -0,0 +1,45 @@
1
+<!DOCTYPE html>
2
+<html lang="en">
3
+<head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>{{ author.name }}</title>
7
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css">
8
+ <style>
9
+ body {
10
+ background-color: #f8f9fa;
11
+ padding: 40px;
12
+ }
13
+ .container {
14
+ max-width: 600px;
15
+ background: white;
16
+ padding: 20px;
17
+ border-radius: 8px;
18
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
19
+ text-align: center;
20
+ }
21
+ h1 {
22
+ color: #343a40;
23
+ }
24
+ .details {
25
+ font-size: 18px;
26
+ margin-top: 15px;
27
+ }
28
+ .btn-back {
29
+ margin-top: 20px;
30
+ text-decoration: none;
31
+ }
32
+ </style>
33
+</head>
34
+<body>
35
+
36
+ <div class="container">
37
+ <h1>📖 {{ author.name }}</h1>
38
+ <p class="details">🗓 Birth Date: {{ author.birth_date if author.birth_date else "N/A" }}</p>
39
+ <p class="details">🕊 Date of Death: {{ author.date_of_death if author.date_of_death else "Still Alive" }}</p>
40
+
41
+ <a href="/" class="btn btn-secondary btn-back">⬅ Back to Home</a>
42
+ </div>
43
+
44
+</body>
45
+</html>
templates/details_book.html
new
+58
@@ -0,0 +1,58 @@
1
+<!DOCTYPE html>
2
+<html lang="en">
3
+<head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>{{ book.title }}</title>
7
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css">
8
+ <style>
9
+ body {
10
+ background-color: #f8f9fa;
11
+ padding: 40px;
12
+ }
13
+ .container {
14
+ max-width: 600px;
15
+ background: white;
16
+ padding: 20px;
17
+ border-radius: 8px;
18
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
19
+ text-align: center;
20
+ }
21
+ h1 {
22
+ color: #343a40;
23
+ }
24
+ .details {
25
+ font-size: 18px;
26
+ margin-top: 15px;
27
+ }
28
+ .cover {
29
+ margin: 20px 0;
30
+ max-width: 100%;
31
+ border-radius: 8px;
32
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
33
+ }
34
+ .btn-back {
35
+ margin-top: 20px;
36
+ text-decoration: none;
37
+ }
38
+ </style>
39
+</head>
40
+<body>
41
+
42
+ <div class="container">
43
+ <h1>📖 {{ book.title }}</h1>
44
+ <p class="details">✍ Written by: <strong>{{ book.name }}</strong></p>
45
+ <p class="details">🗓 Published: {{ book.publication_year }}</p>
46
+ <p class="details">📚 ISBN: {{ book.isbn if book.isbn else "N/A" }}</p>
47
+
48
+ {% if book.isbn %}
49
+ <a href="https://openlibrary.org/isbn/{{ book.isbn }}" target="_blank">
50
+ <img src="https://covers.openlibrary.org/b/isbn/{{ book.isbn }}-M.jpg" class="cover" alt="Book Cover">
51
+ </a><br><br>
52
+ {% endif %}
53
+
54
+ <a href="/" class="btn btn-secondary btn-back">⬅ Back to Home</a>
55
+ </div>
56
+
57
+</body>
58
+</html>
templates/home.html
new
+79
@@ -0,0 +1,79 @@
1
+<!DOCTYPE html>
2
+<html lang="en">
3
+<head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <meta http-equiv="X-UA-Compatible" content="ie=edge">
7
+ <title>My Online Library</title>
8
+ <link rel="stylesheet" href="/static/style.css">
9
+</head>
10
+<body>
11
+
12
+ <header>
13
+ <h1>📚 My Online Library</h1>
14
+ <nav>
15
+ <ul class="nav-links">
16
+ <li><a href="/add_author">➕ Add Author</a></li>
17
+ <li><a href="/add_book">📖 Add Book</a></li>
18
+ </ul>
19
+ <div class="actions">
20
+ <form action="/" method="POST">
21
+ <label for="options">Sort by:</label>
22
+ <select id="options" name="options">
23
+ <option value="title">Title</option>
24
+ <option value="author">Author</option>
25
+ <option value="year">Year</option>
26
+ </select>
27
+ <label for="descending">Descending:</label>
28
+ <input type="checkbox" id="descending" name="descending" value="descending">
29
+ <input type="submit" value="Sort">
30
+ </form>
31
+
32
+ <form class="search" action="/search" method="POST">
33
+ <input type="text" id="search" name="search" placeholder="🔍 Search books...">
34
+ <input type="submit" value="Search">
35
+ </form>
36
+ </div>
37
+ </nav>
38
+ </header>
39
+
40
+ <main>
41
+ <section class="books">
42
+ {% if error %}
43
+ <h2 class="error">⚠️ No Books Found!</h2>
44
+ {% endif %}
45
+ {% if deleted %}
46
+ <h2 class="success">✅ Book deleted successfully!</h2>
47
+ {% elif deleted == False %}
48
+ <h2 class="error">❌ Failed to delete book!</h2>
49
+ % if auth_deleted %}
50
+ <h2 class="success">✅ Author deleted successfully!</h2>
51
+ {% elif auth_deleted == False %}
52
+ <h2 class="error">❌ Failed to delete author!</h2>
53
+ {% endif %}
54
+
55
+ <div class="book-grid">
56
+ {% for book in books %}
57
+ <div class="book-card">
58
+ <h3>
59
+ <a href="/book/{{ book.id }}" class="book-link">{{ book.title }} </a><a href="/author/{{ book.author_id }}" class="author-link"><span>by {{ book.name }}</span></a></h3>
60
+ <form action="/author/{{ book.author_id }}/delete" method="POST">
61
+ <button class="delete-btn" title="Delete Author" value="Delete">🗑Delete Author</button>
62
+ </form>
63
+ <p>📅 Year: {{ book.publication_year }}</p>
64
+ {% if book.isbn %}
65
+ <a href="https://openlibrary.org/isbn/{{ book.isbn }}" target="_blank">
66
+ <img src="https://covers.openlibrary.org/b/isbn/{{ book.isbn }}-M.jpg" alt="Book Cover">
67
+ </a>
68
+ {% endif %}
69
+ <form action="/book/{{ book.id }}/delete" method="POST">
70
+ <button class="delete-btn">🗑 Delete Book</button>
71
+ </form>
72
+ </div>
73
+ {% endfor %}
74
+ </div>
75
+ </section>
76
+ </main>
77
+
78
+</body>
79
+</html>