| 1 | from datetime import datetime |
| 2 | |
| 3 | from flask import Flask, jsonify,render_template, request |
| 4 | |
| 5 | from sqlalchemy import desc |
| 6 | from sqlalchemy.exc import IntegrityError, PendingRollbackError |
| 7 | |
| 8 | import ai_request |
| 9 | from data_models import db, Author, Book |
| 10 | |
| 11 | # Create Instance of Flask |
| 12 | app = Flask(__name__) |
| 13 | |
| 14 | # Configure Database |
| 15 | app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///data/library.sqlite" |
| 16 | |
| 17 | |
| 18 | # Connect Flask to Database |
| 19 | db.init_app(app) |
| 20 | |
| 21 | @app.route("/", methods=["GET","POST"]) |
| 22 | def home(): |
| 23 | """ |
| 24 | Route to home page with POST to sort the books by title, author or year |
| 25 | :return: |
| 26 | """ |
| 27 | if request.method == "POST": |
| 28 | sort_by = request.form.get("options") |
| 29 | if sort_by == "title": |
| 30 | sort_by = getattr(Book, 'title',None) |
| 31 | elif sort_by == "author": |
| 32 | sort_by = getattr(Author, 'name',None) |
| 33 | elif sort_by == "year": |
| 34 | sort_by = getattr(Book, 'publication_year',None) |
| 35 | elif sort_by == "rating": |
| 36 | sort_by = getattr(Book, 'rating',None) |
| 37 | descending = request.form.get("descending") |
| 38 | if descending: |
| 39 | books = db.session.query(Book.id,Book.isbn,Book.title, |
| 40 | Author.name, Book.author_id, |
| 41 | Book.publication_year, Book.rating).join(Author) \ |
| 42 | .order_by(desc(sort_by)).all() |
| 43 | else: |
| 44 | books = db.session.query(Book.id,Book.isbn,Book.title, |
| 45 | Author.name, Book.author_id, |
| 46 | Book.publication_year, Book.rating).join(Author) \ |
| 47 | .order_by(sort_by).all() |
| 48 | return render_template("home.html", books=books) |
| 49 | else: |
| 50 | books = db.session.query(Book.id,Book.isbn,Book.title, |
| 51 | Author.name, Book.author_id, |
| 52 | Book.publication_year, Book.rating).join(Author).all() |
| 53 | |
| 54 | return render_template("home.html", books=books) |
| 55 | |
| 56 | |
| 57 | @app.route("/add_author", methods=["GET", "POST"]) |
| 58 | def add_author(): |
| 59 | """ |
| 60 | Route to add author with POST or search author/s with GET |
| 61 | :return: |
| 62 | """ |
| 63 | if request.method == "GET": |
| 64 | name = request.args.get("name","") |
| 65 | if name: |
| 66 | authors = db.session.query(Author) \ |
| 67 | .filter(Author.name.contains('%'+name+'%')) \ |
| 68 | .all() |
| 69 | output = [] |
| 70 | # Convert query results to Dictionary to return as JSON |
| 71 | for author in authors: |
| 72 | author = dict({'name': author.name, \ |
| 73 | 'birth_date': author.birth_date.strftime('%Y-%m-%d'), \ |
| 74 | 'date_of_death': author.date_of_death.strftime( '%Y-%m-%d') \ |
| 75 | if author.date_of_death else None}) |
| 76 | output.append(author) |
| 77 | return jsonify(output), 200 |
| 78 | else: |
| 79 | return render_template("add_author.html") |
| 80 | ### POST --------------------------------------------------------------- |
| 81 | elif request.method == "POST": |
| 82 | if not request.form["name"].strip() or not request.form["birthdate"]: |
| 83 | return render_template("add_author.html", success=False, |
| 84 | error="Please enter a name and birthdate!"), 401 |
| 85 | name = request.form["name"].strip() |
| 86 | birthdate = datetime.strptime(request.form["birthdate"], "%Y-%m-%d") |
| 87 | if request.form["date_of_death"]: |
| 88 | date_of_death = datetime.strptime(request.form["date_of_death"], "%Y-%m-%d") |
| 89 | author = Author(name=name, birth_date=birthdate, date_of_death=date_of_death) |
| 90 | else: |
| 91 | author = Author(name=name, birth_date=birthdate) |
| 92 | try: |
| 93 | db.session.add(author) |
| 94 | db.session.commit() |
| 95 | return render_template("add_author.html", success=True) |
| 96 | except Exception as e: # For Debugging and Testing catch all Exceptions |
| 97 | print(e.__class__()) |
| 98 | return render_template("add_author.html", success=False),401 |
| 99 | |
| 100 | # Add Book Route --------------------------------------------------- |
| 101 | @app.route("/add_book", methods=["GET", "POST"]) |
| 102 | def add_book(): |
| 103 | """ |
| 104 | Route to add book with POST or search book/s with GET |
| 105 | :return: |
| 106 | """ |
| 107 | if request.method == "GET": |
| 108 | title = request.args.get("title", "") |
| 109 | year = request.args.get("year", "") |
| 110 | isbn = request.args.get("isbn", "") |
| 111 | rating = request.args.get("rating", "") |
| 112 | if title: |
| 113 | books = db.session.query(Book) \ |
| 114 | .filter(Book.title.contains('%' + title + '%')) \ |
| 115 | .all() |
| 116 | output = [] |
| 117 | # Convert query results to Dictionary to return as JSON |
| 118 | for book in books: |
| 119 | book = dict({'title': book.title, |
| 120 | 'year': book.publication_year, |
| 121 | 'rating': book.rating}) |
| 122 | output.append(book) |
| 123 | return jsonify(output), 200 |
| 124 | elif year: |
| 125 | books = db.session.query(Book) \ |
| 126 | .filter(Book.publication_year.contains('%' + year + '%')) \ |
| 127 | .all() |
| 128 | output = [] |
| 129 | # Convert query results to Dictionary to return as JSON |
| 130 | for book in books: |
| 131 | book = dict({'title': book.title, \ |
| 132 | 'year': book.publication_year}) |
| 133 | output.append(book) |
| 134 | return jsonify(output), 200 |
| 135 | elif isbn: |
| 136 | books = db.session.query(Book) \ |
| 137 | .filter(Book.isbn.contains('%' + isbn + '%')) \ |
| 138 | .all() |
| 139 | output = [] |
| 140 | # Convert query results to Dictionary to return as JSON |
| 141 | for book in books: |
| 142 | book = dict({'title': book.title, \ |
| 143 | 'year': book.publication_year}) |
| 144 | output.append(book) |
| 145 | return jsonify(output), 200 |
| 146 | elif rating: |
| 147 | books = db.session.query(Book) \ |
| 148 | .filter(Book.rating.contains(rating)) \ |
| 149 | .all() |
| 150 | output = [] |
| 151 | # Convert query results to Dictionary to return as JSON |
| 152 | for book in books: |
| 153 | book = dict({'title': book.title, \ |
| 154 | 'year': book.publication_year}) |
| 155 | output.append(book) |
| 156 | return jsonify(output), 200 |
| 157 | else: |
| 158 | authors = db.session.query(Author).all() |
| 159 | return render_template("add_book.html", authors=authors) |
| 160 | ### POST --------------------------------------------------------------- |
| 161 | elif request.method == "POST": |
| 162 | title = request.form["title"] |
| 163 | year = request.form["year"] |
| 164 | isbn = request.form["isbn"] if request.form["isbn"] else None |
| 165 | author_id = request.form["author"] |
| 166 | rating = request.form["rating"] |
| 167 | try: |
| 168 | book = Book(title=title, publication_year=year, |
| 169 | isbn=isbn, author_id=author_id , rating=rating) |
| 170 | db.session.add(book) |
| 171 | db.session.commit() |
| 172 | authors = db.session.query(Author).all() |
| 173 | return render_template("add_book.html" |
| 174 | , success=True, authors=authors) |
| 175 | except IntegrityError: |
| 176 | db.session.rollback() |
| 177 | authors = db.session.query(Author).all() |
| 178 | return render_template("add_book.html" |
| 179 | , success=False, authors=authors |
| 180 | , error="Entry already exists, check ISBN and/or whole book to" |
| 181 | "assure uniqueness!" |
| 182 | ""),401 |
| 183 | except PendingRollbackError: |
| 184 | db.session.rollback() |
| 185 | authors = db.session.query(Author).all() |
| 186 | return render_template("add_book.html" |
| 187 | , success=False, authors=authors |
| 188 | , error="Could not add book to database" |
| 189 | ""),401 |
| 190 | except Exception as e: # For Debugging and Testing catch all Exceptions |
| 191 | db.session.rollback() |
| 192 | authors = db.session.query(Author).all() |
| 193 | return render_template("add_book.html" |
| 194 | , success=False, error="Something went wrong:" , |
| 195 | authors=authors),401 |
| 196 | |
| 197 | |
| 198 | # Bonus 5 add recommendation route---------------------------------- |
| 199 | @app.route('/add_recommendation', methods=['POST']) |
| 200 | def add_recommendation(): |
| 201 | """ |
| 202 | Route to add AI recommendation via POST |
| 203 | :return: |
| 204 | """ |
| 205 | books = db.session.query(Book.id, Book.isbn, Book.title, |
| 206 | Author.name, Book.author_id, Book.publication_year, |
| 207 | Book.rating).join(Author).all() |
| 208 | if request.method != "POST": |
| 209 | return render_template("add_recommendation.html", books=books) |
| 210 | author = Author.query.filter(Author.name==request.form["author"]).first() |
| 211 | if not author: |
| 212 | birth_date = datetime.strptime(request.form["birthday"], "%Y-%m-%d") |
| 213 | if request.form["died"]: |
| 214 | date_of_death = datetime.strptime(request.form["died"], "%Y-%m-%d") |
| 215 | author = Author(name=request.form["author"], |
| 216 | birth_date=birth_date,date_of_death=date_of_death) |
| 217 | db.session.add(author) |
| 218 | db.session.commit() |
| 219 | |
| 220 | else: |
| 221 | author = Author(name=request.form["author"], birth_date=birth_date) |
| 222 | db.session.add(author) |
| 223 | db.session.commit() |
| 224 | |
| 225 | try: |
| 226 | new_author = Author.query.filter(Author.name==author.name).first() |
| 227 | book = Book(isbn=request.form["isbn"], title=request.form["title"], |
| 228 | publication_year=request.form["year"], |
| 229 | author_id=new_author.id) |
| 230 | db.session.add(book) |
| 231 | db.session.commit() |
| 232 | new_books = db.session.query(Book.id, Book.isbn, Book.title, |
| 233 | Author.name, Book.author_id, Book.publication_year, |
| 234 | Book.rating).join(Author).all() |
| 235 | return render_template("home.html", books=new_books, success=True) |
| 236 | except Exception as e: # For Debugging and Testing catch all Exceptions |
| 237 | print("AN ERROR HAS OCCURED: ",e) |
| 238 | return render_template("home.html", books=books, success=False) |
| 239 | |
| 240 | |
| 241 | @app.route('/search', methods=['POST']) |
| 242 | def search(): |
| 243 | """ |
| 244 | Route to search books with POST and any search term containing title, author or year |
| 245 | :return: |
| 246 | """ |
| 247 | title = request.form['search'] |
| 248 | title = '%' + title + '%' |
| 249 | books = db.session.query(Book.id,Book.isbn,Book.title, |
| 250 | Author.name, Book.author_id, Book.publication_year) \ |
| 251 | .join(Author).filter(Book.title.contains(title) \ |
| 252 | | Author.name.contains(title) | Book.publication_year \ |
| 253 | .contains(title)).all() |
| 254 | if len(books) == 0: |
| 255 | return render_template("home.html", error=True) |
| 256 | return render_template("home.html", books=books) |
| 257 | |
| 258 | # Bonus 2 ----------------------------------------------- |
| 259 | @app.route('/book/<int:book_id>/delete', methods=['POST']) |
| 260 | def delete_book(book_id): |
| 261 | """ |
| 262 | Route to delete book with given id and author if it is the last book |
| 263 | :param book_id: |
| 264 | :return: |
| 265 | """ |
| 266 | if request.method != "POST" or request.form.get("confirmation","") != "yes": |
| 267 | if request.method == "POST" and request.form.get("confirmation","") != "no": |
| 268 | book = [Book.query.get(book_id)] |
| 269 | return render_template("home.html",books=book,book_confirmation=book_id) |
| 270 | books = db.session.query(Book.id, Book.isbn, Book.title, |
| 271 | Author.name, Book.author_id, |
| 272 | Book.publication_year, Book.rating).join(Author).all() |
| 273 | return render_template("home.html",books=books) |
| 274 | book = Book.query.get(book_id) |
| 275 | if book: |
| 276 | author_id = book.author_id |
| 277 | db.session.delete(book) |
| 278 | db.session.commit() |
| 279 | books_auth=db.session.query(Book.title).filter(Book.author_id == author_id ).all() |
| 280 | if len(books_auth) == 0: |
| 281 | author = Author.query.get(author_id) |
| 282 | db.session.delete(author) |
| 283 | db.session.commit() |
| 284 | books = db.session.query(Book.id,Book.isbn,Book.title, |
| 285 | Author.name, Book.author_id, |
| 286 | Book.publication_year, Book.rating).join(Author).all() |
| 287 | return render_template('home.html',books=books,deleted=True) |
| 288 | else: |
| 289 | books = db.session.query(Book.id,Book.isbn,Book.title, |
| 290 | Author.name, Book.author_id, |
| 291 | Book.publication_year, Book.rating).join(Author).all() |
| 292 | return render_template('home.html',books=books,deleted=False) |
| 293 | |
| 294 | # Bonus 2------------------------------------------------ |
| 295 | @app.route('/author/<int:author_id>/delete', methods=['POST']) |
| 296 | def delete_author(author_id): |
| 297 | """ |
| 298 | Route to delete author with given id |
| 299 | :param author_id: |
| 300 | :return: |
| 301 | """ |
| 302 | if request.method != "POST" or request.form.get("confirmation","") != "yes": |
| 303 | |
| 304 | if request.method == "POST" and request.form.get("confirmation","") != "no": |
| 305 | books = db.session.query(Book).filter(Book.author_id == author_id).all() |
| 306 | return render_template("home.html",books=books,auth_confirmation=author_id) |
| 307 | books = db.session.query(Book.id, Book.isbn, Book.title, |
| 308 | Author.name, Book.author_id, |
| 309 | Book.publication_year, Book.rating).join(Author).all() |
| 310 | return render_template("home.html",books=books) |
| 311 | author = Author.query.get(author_id) |
| 312 | if author: |
| 313 | db.session.delete(author) |
| 314 | db.session.commit() |
| 315 | books = db.session.query(Book.id,Book.isbn,Book.title, |
| 316 | Author.name, Book.author_id, |
| 317 | Book.publication_year, Book.rating).join(Author).all() |
| 318 | return render_template('home.html',books=books,auth_deleted=True) |
| 319 | else: |
| 320 | books = db.session.query(Book.id,Book.isbn,Book.title, |
| 321 | Author.name, Book.author_id, |
| 322 | Book.publication_year, Book.rating).join(Author).all() |
| 323 | return render_template('home.html',books=books,auth_deleted=False) |
| 324 | |
| 325 | # BONUS 3------------------------------------------------ |
| 326 | @app.route('/author/<int:author_id>', methods=['GET']) |
| 327 | def author(author_id): |
| 328 | """ |
| 329 | Route to display author details with given id |
| 330 | :param author_id: |
| 331 | :return: |
| 332 | """ |
| 333 | author = Author.query.get(author_id) |
| 334 | return render_template("details_author.html", author=author) |
| 335 | |
| 336 | |
| 337 | @app.route('/book/<int:book_id>', methods=['GET']) |
| 338 | def book(book_id): |
| 339 | """ |
| 340 | Route to display book details with given id |
| 341 | :param book_id: |
| 342 | :return: |
| 343 | """ |
| 344 | book = db.session.query(Book.isbn,Book.title, \ |
| 345 | Book.publication_year,Author.name, Book.rating) \ |
| 346 | .join(Author).filter(Book.id == book_id).one()._mapping |
| 347 | return render_template("details_book.html", book=book) |
| 348 | |
| 349 | ## BONUS 4------------------------------------------- |
| 350 | @app.route('/book/rating/<int:book_id>', methods=['POST']) |
| 351 | def rate_book(book_id): |
| 352 | """ |
| 353 | Route to rate book with given id |
| 354 | :param book_id: |
| 355 | :return: |
| 356 | """ |
| 357 | rating = request.form['rating'] |
| 358 | book = Book.query.get(book_id) |
| 359 | book.rating = rating |
| 360 | db.session.commit() |
| 361 | books = db.session.query(Book.id,Book.isbn,Book.title, |
| 362 | Author.name, Book.author_id, |
| 363 | Book.publication_year, Book.rating).join(Author).all() |
| 364 | return render_template('home.html',books=books, rated=True) |
| 365 | |
| 366 | |
| 367 | # Bonus 5 ------------------------------------------------ |
| 368 | @app.route("/get_ai_recommendation", methods=["GET"]) |
| 369 | def get_ai_recommendation(): |
| 370 | """ |
| 371 | Route to get AI recommendation |
| 372 | :return: |
| 373 | """ |
| 374 | books = db.session.query( Book.title, Book.publication_year, |
| 375 | Author.name, Book.rating).join(Author).all() |
| 376 | dataset = "" |
| 377 | for book in books: |
| 378 | dataset += str(book) |
| 379 | return render_template("ai_recomendation.html", |
| 380 | recomendation=ai_request.ai_request(dataset)) |
| 381 | |
| 382 | app.run(host="0.0.0.0", port=5002,debug=True) |
| 383 | |
| 384 | # Only needed to create datatables at the beginning of the project |
| 385 | # with app.app_context(): |
| 386 | # db.create_all() |