Day 063

Udemy Python Bootcamp Day 063

Creating a Virtual Bookshelf

Make the Website Work

from flask import Flask, render_template, request, redirect, url_for

app = Flask(__name__)

all_books = []


@app.route('/')
def home():
    return render_template('index.html', books=all_books)


@app.route("/add", methods=["GET", "POST"])
def add():
    if request.method == 'POST':
        new_book = {
            "title": request.form["title"],
            "author": request.form["author"],
            "rating": request.form["rating"]
        }
        all_books.append(new_book)
        return redirect(url_for('home'))
    return render_template('add.html')


if __name__ == "__main__":
    app.run(debug=True)

add.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Add Book</title>
</head>
<body>
    <form action="" method="POST">
        <label>Book Name</label>
        <input name="title" type="text">
        <label>Book Author</label>
        <input name="author" type="text">
        <label>Rating</label>
        <input name="rating" type="text">
        <button type="submit">Add Book</button>
    </form>
</body>
</html>

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Library</title>
</head>
<body>
<h1>My Library</h1>
        {% if books = []: %}
        <p>Library is empty.</p>
        {% endif %}
    <ul>
        {% for book in books: %}
        <li>{{ book.title }} - {{ book.author }} - {{ book.rating }}/10</li>
        {% endfor %}
    </ul>
    
<a href="{{ url_for('add') }}">Add New Book</a>
</body>
</html>


What Happens When You Refresh the Server?

When the server run again, all_books is empty.

This is because our books are currently stored in the List all_books, this variable gets re-initialised when we re-run main.py and all the data inside is lost.
In order to fix this, we need to learn about data persistence and how to work with databases in Flask applications.

SQLite Databases

First, let's create a database. The most used database in the world is SQLite. It's so popular that it's included by default in all Python installations, so if you're creating a Python project, you've already got it installed. We're going to create an SQLite database to store our book data.

STEP 1: Import the sqlite3 module

import sqlite3

STEP 2: Create a connection to a new database

db = sqlite3.connect("books-collection.db")

STEP 3: A new file appear in PyCharm called books-collection.db


STEP 4: Create a cursor which will control our database

So a cursor is also known as the mouse or pointer. If we were working in Excel or Google Sheet, we would be using the cursor to add rows of data or edit/delete data, we also need a cursor to modify our SQLite database.

STEP 5: Let's create one

cursor.execute("CREATE TABLE books (id INTEGER PRIMARY KEY, title varchar(250) NOT NULL UNIQUE, author varchar(250) NOT NULL, rating FLOAT NOT NULL)")
  • cursor : We created this in step 4 and this is the mouse pointer in our database that is going to do all the work.
  • .execute() : This method will tell the cursor to execute an action. All actions in SQLite databases are expressed as SQL (Structured Query Language) commands. These are almost like English sentences with keywords written in ALL-CAPS. There are quite a few SQL commands.
  • CREATE TABLE : This will create a new table in the database. The name of the table comes after this keyword.
  • books : This is the name that we've given the new table we're creating.
  • () : The parts that come inside the parenthesis after CREATE TABLE books ( ) are going to be the fields in this table.
  • id INTEGER PRIMARY KEY : This is the first field, it's a field called "id" which is of data type INTEGER and it will be the PRIMARY KEY for this table. The primary key is the one piece of data that will uniquely identify this record in the table.
  • title varchar(250) NOT NULL UNIQUE - This is the second field, it's called "title" and it accepts a variable-length string composed of characters. The 250 in brackets is the maximum length of the text. NOT NULL means it must have a value and cannot be left empty. UNIQUE means no two records in this table can have the same title.
  • author varchar(250) NOT NULL - A field that accepts variable-length Strings up to 250 characters called author that cannot be left empty.
  • rating FLOAT NOT NULL - A field that accepts FLOAT data type numbers, cannot be empty and the field is called rating.

STEP 6: In order to view our database we need to download some specialised software

download DB Browser for your operating system
https://sqlitebrowser.org/dl/


STEP 7: To add data to our table we can head back to main.py

cursor.execute("INSERT INTO books VALUES(1, 'Harry Potter', 'J. K. Rowling', '9.3')")
db.commit()

This will create a new entry in our books table for the Harry Potter book and commit the changes to our database.

STEP 8: Run the code in main.py and re-open the database in DB Browser to see the updated books table

db = sqlite3.connect("books-collection.db")
cursor = db.cursor()
# cursor.execute("CREATE TABLE books (id INTEGER PRIMARY KEY, title varchar(250) NOT NULL UNIQUE, author varchar(250) NOT NULL, rating FLOAT NOT NULL)")
cursor.execute("INSERT OR IGNORE INTO books VALUES(1, 'Harry Potter', 'J. K. Rowling', '9.3')")
db.commit()

SQL queries are very sensitive to typos.
Luckily, there are much better ways of working with SQLite in Python projects, we can use a tool called SQLAlchemy to write Python code instead of all these error-prone SQL commands.

SQLAlchemy

SQLAlchemy is defined as an ORM Object Relational Mapping library. This means that it's able to map the relationships in the database into Objects. Fields become Object properties. Tables can be defined as separate Classes and each row of data is a new Object. This will make more sense after we write some code and see how we can create a Database/Table/Row of data using SQLAlchemy.

from flask import Flask
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)

##CREATE DATABASE
app.config['SQLALCHEMY_DATABASE_URI'] = "sqlite:///new-books-collection.db"
#Optional: But it will silence the deprecation warning in the console.
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)


##CREATE TABLE
class Book(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(250), unique=True, nullable=False)
    author = db.Column(db.String(250), nullable=False)
    rating = db.Column(db.Float, nullable=False)

    #Optional: this will allow each book object to be identified by its title when printed.
    def __repr__(self):
        return f'<Book {self.title}>'
    
db.create_all()


#CREATE RECORD
new_book = Book(id=1, title="Harry Potter", author="J. K. Rowling", rating=9.3)
db.session.add(new_book)
db.session.commit()

이걸 영상없이 글로만 설명하다니,,, 안젤라 나빠요..

CRUD Operations with SQLAlchemy

The most crucial thing to figure out when working with any new database technology is how to CRUD data records.

  • Create
    • A New Record
      When creating new records, the primary key fields is optional.
      The id field will be auto-generated.
      new_book = Book(title="Harry Potter", author="J. K. Rowling", rating=9.3)
      db.session.add(new_book)
      db.session.commit()
  • Read
    • All Records
      all_books = session.query(Book).all()
    • A Particular Record By Query
      book = Book.query.filter_by(title="Harry Potter").first()
  • Update
    • A Particular Record By Query
      book_to_update = Book.query.filter_by(title="Harry Potter").first()
      book_to_update.title = "Harry Potter and the Chamber of Secrets"
      db.session.commit()  
    • A Record By PRIMARY KEY
      book_id = 1
      book_to_update = Book.query.get(book_id)
      book_to_update.title = "Harry Potter and the Goblet of Fire"
      db.session.commit()  
  • Delete
    • A Particular Record By PRIMARY KEY
      book_id = 1
      book_to_delete = Book.query.get(book_id)
      db.session.delete(book_to_delete)
      db.session.commit()

Build a SQLite Database into the Flask Website

Add new books

@app.route('/')
def home():
    all_books = db.session.query(Book).all()
    return render_template('index.html', books=all_books)


@app.route("/add", methods=["GET", "POST"])
def add():
    if request.method == "POST":
        # CREATE RECORD
        new_book = Book(
            title=request.form["title"],
            author=request.form["author"],
            rating=request.form["rating"]
        )
        db.session.add(new_book)
        db.session.commit()
        return redirect(url_for('home'))
    return render_template("add.html")

에러나서 거의 한시간동안 디버깅했는데.. 참 간단하게 패키지들 최신버전으로 업그레이드하니까 정상작동하더라...

Add an Edit Rating Anchor Tag

index.html

<a href="{{ url_for('edit', id=book.id) }}">Edit Rating</a>

edit_rating.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Edit Rating</title>
</head>
<body>
<form action="{{ url_for('edit') }}" method="POST">
    <p>Book Name: {{ book.title }}</p>
    <p>Current Rating {{ book.rating }}</p>
    <input hidden="hidden" name="id" value="{{ book.id }}">
    <input name="rating" type="text" placeholder="New Rating">
    <button type="submit">Change Rating</button>
</form>
</body>
</html>

<input hidden="hidden" name="id" value="{{ book.id }}">의 용도는 모르겠음..


@app.route('/edit', methods=["GET", "POST"])
def edit():
    if request.method == "POST":
        book_id = request.form["id"]
        book_to_update = Book.query.get(book_id)
        book_to_update.rating = request.form["rating"]
        db.session.commit()
        return redirect(url_for('home'))
    book_id = request.args.get('id')
    book_selected = Book.query.get(book_id)
    return render_template('edit_rating.html', book=book_selected)

edit()작성하는데 애먹었다... 아직 request와 익숙해지지 못한듯..

Add a Delete Anchor Tag

index.html

<a href="{{ url_for('delete', id=book.id) }}">Delete</a>

@app.route('/delete')
def delete():
    book_id = request.args.get('id')
    # DELETE A RECORD BY ID
    book_to_delete = Book.query.get(book_id)
    db.session.delete(book_to_delete)
    db.session.commit()
    return redirect(url_for('home'))

신기하긴 했는데 에러때문에 골머리 좀 썩혔음..

FINAL

https://gist.github.com/awesomekimn/ee5cccf427300cb671d2122a712dac9e

좋은 웹페이지 즐겨찾기