Code for How to Build a Complete CRUD App using Flask and Jinja2 in Python Tutorial


View on Github

app/static/main.css

body {
    padding-top: 50px;
}

.starter-template {
    padding: 40px 15px;
    text-align: center;
}

td, th {
    text-align: center
}

app/templates/base.html

{% extends "bootstrap/base.html" %}

{% block title %}Bookshop{% endblock %}

{% block head %}
{{ super() }}
<link rel="stylesheet" href="{{ url_for('static', filename='main.css') }}">
{% endblock %}
{% block navbar %}

<nav class="navbar navbar-inverse navbar-fixed-top">
    <div class="container">
        <div class="navbar-header">
            <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar"
                aria-expanded="false" aria-controls="navbar">
                <span class="sr-only">Toggle navigation</span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
            </button>
            <a class="navbar-brand" href="/">Bookshop</a>
        </div>
        <div id="navbar" class="collapse navbar-collapse">
            <ul class="nav navbar-nav">
                <li class="active"><a href="/">Home</a></li>
            </ul>
        </div>
    </div>
</nav>
{% endblock %}

{% block content %}
<div class="container">
    {% block page_content %} {% endblock %}
</div>
{% endblock %}

app/templates/book.html

{% extends "base.html" %}

{% block page_content %}
<div class="starter-template">
    <h1>Book's ISBN: {{ isbn }}</h1>
</div>
{% endblock %}

app/templates/books.html

{% extends "base.html" %}

{% block page_content %}
<div class="starter-template">
    <button type="button" class="btn btn-lg btn-primary">Add a book</button>
</div>
{% endblock %}

app/templates/index.html

{% extends "base.html" %}

{% block page_content %}
<div class="starter-template">
    <h1>Welcome to our bookshop!</h1>
</div>
<button type="button" data-toggle="modal" class="btn btn-lg btn-primary" data-target="#insert_book">Add a book</button>

<!-- Modal 1 for adding a book -->
<div class="modal fade" id="insert_book" tabindex="-1" role="dialog" aria-labelledby="basicModal" aria-hidden="true">
    <div class="modal-dialog">
        <div class="modal-content">
            <div class="modal-header">
                <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
                <h4 class="modal-title" id="myModalLabel">Add a book</h4>
            </div>
            <form action="{{url_for('add_book')}}" method="post">
                <div class="modal-body">
                    <div class="form-group row">
                        <label for="author" class="col-xs-2 control-label">Author</label>
                        <div class="col-xs-10">
                            <input type="text" id="author" class="form-control" name="author" placeholder="Author" />
                        </div>
                    </div>
                    <div class="form-group row">
                        <label for="author" class="col-xs-2 control-label">Title</label>
                        <div class="col-xs-10">
                            <input type="text" class="form-control" name="title" placeholder="Title" />
                        </div>
                    </div>
                    <div class="form-group row">
                        <label for="author" class="col-xs-2 control-label">Price</label>
                        <div class="col-xs-10">
                            <input type="number" class="form-control" name="price" placeholder="Price" />
                        </div>
                    </div>
                </div>
                <div class="modal-footer">
                    <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
                    <button type="submit" class="btn btn-success">Submit</button>
                </div>
            </form>
        </div>
    </div>
</div>
<!-- End Modal 1 -->

<div class="row">
    <div class="col-md-6">
        <table class="table" border="1">
            <thead>
                <tr>
                    <th>ISBN</th>
                    <th>Author</th>
                    <th>Title</th>
                    <th>Price</th>
                    <th colspan="2">Action</th>
                </tr>
            </thead>
            <tbody>
                {% for book in books %}
                <tr>
                    <td>{{ book.isbn }}</td>
                    <td>{{ book.author }}</td>
                    <td>{{ book.title }}</td>
                    <td>{{ book.price }}</td>
                    <td><button type="button" class="btn btn-success" data-toggle="modal"
                            data-target="#update_book_{{book['isbn']}}">Update</button></td>


                    <!-- Modal 2 for updating a book -->
                    <div class="modal fade" id="update_book_{{book['isbn']}}" tabindex="-1" role="dialog"
                        aria-labelledby="basicModal" aria-hidden="true">
                        <div class="modal-dialog">
                            <div class="modal-content">
                                <div class="modal-header">
                                    <button type="button" class="close" data-dismiss="modal"
                                        aria-hidden="true">&times;</button>
                                    <h4 class="modal-title" id="myModalLabel">Update a book</h4>
                                </div>
                                <form action="{{url_for('update_book', isbn=book['isbn'])}}" method="post">
                                    <div class="modal-body">
                                        <div class="form-group row">
                                            <label for="author" class="col-xs-2 control-label">Author</label>
                                            <div class="col-xs-10">
                                                <input type="text" id="author" class="form-control" name="author"
                                                    value="{{book['author']}}" />
                                            </div>
                                        </div>
                                        <div class="form-group row">
                                            <label for="author" class="col-xs-2 control-label">Title</label>
                                            <div class="col-xs-10">
                                                <input type="text" class="form-control" name="title"
                                                    value="{{book['title']}}" />
                                            </div>
                                        </div>
                                        <div class="form-group row">
                                            <label for="author" class="col-xs-2 control-label">Price</label>
                                            <div class="col-xs-10">
                                                <input type="number" class="form-control" name="price"
                                                    value="{{book['price']}}" />
                                            </div>
                                        </div>
                                    </div>
                                    <div class="modal-footer">
                                        <button type="button" class="btn btn-default"
                                            data-dismiss="modal">Close</button>
                                        <button type="submit" class="btn btn-success">Submit</button>
                                    </div>
                                </form>
                            </div>
                        </div>
                    </div>
                    <!-- End Modal 2 -->
                    <td><button type="button" class="btn btn-danger" data-toggle="modal"
                            data-target="#delete_book_{{book['isbn']}}">Delete</button></td>


                    <!-- Modal 3 for deleting a book -->
                    <div class="modal fade" id="delete_book_{{book['isbn']}}" tabindex="-1" role="dialog"
                        aria-labelledby="basicModal" aria-hidden="true">
                        <div class="modal-dialog">
                            <div class="modal-content">
                                <div class="modal-header">
                                    <button type="button" class="close" data-dismiss="modal"
                                        aria-hidden="true">&times;</button>
                                    <h4 class="modal-title" id="myModalLabel">Delete a book</h4>
                                </div>
                                <form action="{{url_for('delete', isbn=book['isbn'])}}" method="post">
                                    <div class="modal-body">
                                        <div class="form-group row">
                                            <label class="col-sm-12 col-form-label">Do you want to delete the book <span
                                                    style='font-weight:bold;color:red'>{{book['title']}}</span>
                                                ?</label>

                                        </div>
                                    </div>
                                    <div class="modal-footer">
                                        <button type="button" class="btn btn-default"
                                            data-dismiss="modal">Close</button>
                                        <button type="submit" class="btn btn-danger">Delete</button>
                                    </div>
                                </form>
                            </div>
                        </div>
                    </div>
                    <!-- End Modal 3-->
                </tr>
                {% endfor %}
            </tbody>
        </table>
    </div>
</div>
{% endblock %}

app/__init__.py

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_bootstrap import Bootstrap
from config import config

db = SQLAlchemy()
bootstrap = Bootstrap()


def create_app(config_name):
    app = Flask(__name__)
    app.config.from_object(config[config_name])
    config[config_name].init_app(app)

    bootstrap.init_app(app)
    db.init_app(app)
    return app

app/models.py

from . import db


class Book(db.Model):
    __tablename__ = 'books'
    isbn = db.Column(db.Integer, primary_key=True)
    author = db.Column(db.String(100), nullable=False)
    title = db.Column(db.String(100), nullable=False)
    price = db.Column(db.Float)

    def to_json(self):
        return {
            'isbn': self.isbn,
            'author': self.author,
            'title': self.title,
            'price': self.price
        }

app/routes.py

import os
from . import create_app
from .models import Book
from . import db
from flask import jsonify, redirect, request, abort, render_template, url_for

app = create_app(os.getenv('FLASK_CONFIG') or 'default')


@app.route("/")
def index():
    books = Book.query.all()
    return render_template("index.html", books=books)


@app.route("/book/list", methods=["GET"])
def get_books():
    books = Book.query.all()
    return jsonify([book.to_json() for book in books])


@app.route("/book/<int:isbn>", methods=["GET"])
def get_book(isbn):
    book = Book.query.get(isbn)
    if book is None:
        abort(404)
    return render_template("book.html", isbn=isbn)


@app.route("/delete/<int:isbn>", methods=["POST"])
def delete(isbn):
    book = Book.query.get(isbn)
    if book is None:
        abort(404)
    db.session.delete(book)
    db.session.commit()
    return redirect(url_for("index"))


@app.route('/add_book/', methods=['POST'])
def add_book():
    if not request.form:
        abort(400)
    book = Book(
        title=request.form.get('title'),
        author=request.form.get('author'),
        price=request.form.get('price')
    )
    db.session.add(book)
    db.session.commit()
    return redirect(url_for("index"))


@app.route('/update_book/<int:isbn>', methods=['POST'])
def update_book(isbn):
    if not request.form:
        abort(400)
    book = Book.query.get(isbn)
    if book is None:
        abort(404)
    book.title = request.form.get('title', book.title)
    book.author = request.form.get('author', book.author)
    book.price = request.form.get('price', book.price)
    db.session.commit()
    return redirect(url_for("index"))

bookshop.py

from app import db
from app.routes import app
from app.models import Book


@app.shell_context_processor
def make_shell_context():
    return dict(db=db, Book=Book)

config.py

import os


class Config:
    SQLALCHEMY_TRACK_MODIFICATIONS = False

    @staticmethod
    def init_app(app):
        pass


class DevelopmentConfig(Config):
    DEBUG = True
    SQLALCHEMY_DATABASE_URI = os.getenv("DEV_DATABASE_URL")


class TestingConfig(Config):
    TESTING = True
    SQLALCHEMY_DATABASE_URI = os.getenv("TEST_DATABASE_URL")


class ProductionConfig(Config):
    SQLALCHEMY_DATABASE_URI = os.getenv("DATABASE_URL")


config = {
    'development': DevelopmentConfig,
    'testing': TestingConfig,
    'production': ProductionConfig,
    'default': DevelopmentConfig
}

requirements.txt

click==8.0.4
dataclasses
Flask==2.0.3
Flask-SQLAlchemy==2.5.1
Flask-Bootstrap==3.3.7.1
greenlet==1.1.2
importlib-metadata==4.8.3
itsdangerous==2.0.1
Jinja2==3.0.3
MarkupSafe==2.0.1
# pkg_resources
PyMySQL==1.0.2
SQLAlchemy==1.4.35
typing_extensions==4.1.1
Werkzeug==2.0.3
zipp==3.6.0