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">×</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">×</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">×</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