@leroysheep / BookAlchemy Mirror · read-only / commits / 0f2cad543d
Fork 0

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>