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")

    エラーが発生しました.1時間近くデバッグしました.最新バージョンのパッケージに簡単にアップグレードして正常に動作しました...

    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