Skip to content

Commit 307e485

Browse files
authored
Merge pull request x4nth055#103 from EzzEddin/frontend-crud-flask-sqlalchemy-app
Add the frontend of the Flask SQLAlchemy CRUD app tutorial
2 parents a86f6c4 + 2c93f3b commit 307e485

File tree

13 files changed

+402
-0
lines changed

13 files changed

+402
-0
lines changed
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
The MIT License (MIT)
2+
3+
Copyright (c) 2022 Ezzeddin Abdullah
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy of
6+
this software and associated documentation files (the "Software"), to deal in
7+
the Software without restriction, including without limitation the rights to
8+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9+
the Software, and to permit persons to whom the Software is furnished to do so,
10+
subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Fullstack Bookshop CRUD app using MySQL with Flask and SQLAlchemy
2+
3+
## Quick start
4+
```bash
5+
$ python3 -m venv venv
6+
$ . venv/bin/activate
7+
$ pip install --upgrade pip
8+
$ pip install -r requirements.txt
9+
$ cd fullstack_crud_flask_sqlalchemy
10+
$ export FLASK_APP=bookshop.py
11+
$ flask run
12+
```
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
from flask import Flask
2+
from flask_sqlalchemy import SQLAlchemy
3+
from flask_bootstrap import Bootstrap
4+
from config import config
5+
6+
db = SQLAlchemy()
7+
bootstrap = Bootstrap()
8+
9+
10+
def create_app(config_name):
11+
app = Flask(__name__)
12+
app.config.from_object(config[config_name])
13+
config[config_name].init_app(app)
14+
15+
bootstrap.init_app(app)
16+
db.init_app(app)
17+
return app
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
from . import db
2+
3+
4+
class Book(db.Model):
5+
__tablename__ = 'books'
6+
isbn = db.Column(db.Integer, primary_key=True)
7+
author = db.Column(db.String(100), nullable=False)
8+
title = db.Column(db.String(100), nullable=False)
9+
price = db.Column(db.Float)
10+
11+
def to_json(self):
12+
return {
13+
'isbn': self.isbn,
14+
'author': self.author,
15+
'title': self.title,
16+
'price': self.price
17+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import os
2+
from . import create_app
3+
from .models import Book
4+
from . import db
5+
from flask import jsonify, redirect, request, abort, render_template, url_for
6+
7+
app = create_app(os.getenv('FLASK_CONFIG') or 'default')
8+
9+
10+
@app.route("/")
11+
def index():
12+
books = Book.query.all()
13+
return render_template("index.html", books=books)
14+
15+
16+
@app.route("/book/list", methods=["GET"])
17+
def get_books():
18+
books = Book.query.all()
19+
return jsonify([book.to_json() for book in books])
20+
21+
22+
@app.route("/book/<int:isbn>", methods=["GET"])
23+
def get_book(isbn):
24+
book = Book.query.get(isbn)
25+
if book is None:
26+
abort(404)
27+
return render_template("book.html", isbn=isbn)
28+
29+
30+
@app.route("/delete/<int:isbn>", methods=["POST"])
31+
def delete(isbn):
32+
book = Book.query.get(isbn)
33+
if book is None:
34+
abort(404)
35+
db.session.delete(book)
36+
db.session.commit()
37+
return redirect(url_for("index"))
38+
39+
40+
@app.route('/add_book/', methods=['POST'])
41+
def add_book():
42+
if not request.form:
43+
abort(400)
44+
book = Book(
45+
title=request.form.get('title'),
46+
author=request.form.get('author'),
47+
price=request.form.get('price')
48+
)
49+
db.session.add(book)
50+
db.session.commit()
51+
return redirect(url_for("index"))
52+
53+
54+
@app.route('/update_book/<int:isbn>', methods=['POST'])
55+
def update_book(isbn):
56+
if not request.form:
57+
abort(400)
58+
book = Book.query.get(isbn)
59+
if book is None:
60+
abort(404)
61+
book.title = request.form.get('title', book.title)
62+
book.author = request.form.get('author', book.author)
63+
book.price = request.form.get('price', book.price)
64+
db.session.commit()
65+
return redirect(url_for("index"))
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
body {
2+
padding-top: 50px;
3+
}
4+
5+
.starter-template {
6+
padding: 40px 15px;
7+
text-align: center;
8+
}
9+
10+
td, th {
11+
text-align: center
12+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
{% extends "bootstrap/base.html" %}
2+
3+
{% block title %}Bookshop{% endblock %}
4+
5+
{% block head %}
6+
{{ super() }}
7+
<link rel="stylesheet" href="{{ url_for('static', filename='main.css') }}">
8+
{% endblock %}
9+
{% block navbar %}
10+
11+
<nav class="navbar navbar-inverse navbar-fixed-top">
12+
<div class="container">
13+
<div class="navbar-header">
14+
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar"
15+
aria-expanded="false" aria-controls="navbar">
16+
<span class="sr-only">Toggle navigation</span>
17+
<span class="icon-bar"></span>
18+
<span class="icon-bar"></span>
19+
<span class="icon-bar"></span>
20+
</button>
21+
<a class="navbar-brand" href="/">Bookshop</a>
22+
</div>
23+
<div id="navbar" class="collapse navbar-collapse">
24+
<ul class="nav navbar-nav">
25+
<li class="active"><a href="/">Home</a></li>
26+
</ul>
27+
</div>
28+
</div>
29+
</nav>
30+
{% endblock %}
31+
32+
{% block content %}
33+
<div class="container">
34+
{% block page_content %} {% endblock %}
35+
</div>
36+
{% endblock %}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{% extends "base.html" %}
2+
3+
{% block page_content %}
4+
<div class="starter-template">
5+
<h1>Book's ISBN: {{ isbn }}</h1>
6+
</div>
7+
{% endblock %}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{% extends "base.html" %}
2+
3+
{% block page_content %}
4+
<div class="starter-template">
5+
<button type="button" class="btn btn-lg btn-primary">Add a book</button>
6+
</div>
7+
{% endblock %}
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
{% extends "base.html" %}
2+
3+
{% block page_content %}
4+
<div class="starter-template">
5+
<h1>Welcome to our bookshop!</h1>
6+
</div>
7+
<button type="button" data-toggle="modal" class="btn btn-lg btn-primary" data-target="#insert_book">Add a book</button>
8+
9+
<!-- Modal 1 for adding a book -->
10+
<div class="modal fade" id="insert_book" tabindex="-1" role="dialog" aria-labelledby="basicModal" aria-hidden="true">
11+
<div class="modal-dialog">
12+
<div class="modal-content">
13+
<div class="modal-header">
14+
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
15+
<h4 class="modal-title" id="myModalLabel">Add a book</h4>
16+
</div>
17+
<form action="{{url_for('add_book')}}" method="post">
18+
<div class="modal-body">
19+
<div class="form-group row">
20+
<label for="author" class="col-xs-2 control-label">Author</label>
21+
<div class="col-xs-10">
22+
<input type="text" id="author" class="form-control" name="author" placeholder="Author" />
23+
</div>
24+
</div>
25+
<div class="form-group row">
26+
<label for="author" class="col-xs-2 control-label">Title</label>
27+
<div class="col-xs-10">
28+
<input type="text" class="form-control" name="title" placeholder="Title" />
29+
</div>
30+
</div>
31+
<div class="form-group row">
32+
<label for="author" class="col-xs-2 control-label">Price</label>
33+
<div class="col-xs-10">
34+
<input type="number" class="form-control" name="price" placeholder="Price" />
35+
</div>
36+
</div>
37+
</div>
38+
<div class="modal-footer">
39+
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
40+
<button type="submit" class="btn btn-success">Submit</button>
41+
</div>
42+
</form>
43+
</div>
44+
</div>
45+
</div>
46+
<!-- End Modal 1 -->
47+
48+
<div class="row">
49+
<div class="col-md-6">
50+
<table class="table" border="1">
51+
<thead>
52+
<tr>
53+
<th>ISBN</th>
54+
<th>Author</th>
55+
<th>Title</th>
56+
<th>Price</th>
57+
<th colspan="2">Action</th>
58+
</tr>
59+
</thead>
60+
<tbody>
61+
{% for book in books %}
62+
<tr>
63+
<td>{{ book.isbn }}</td>
64+
<td>{{ book.author }}</td>
65+
<td>{{ book.title }}</td>
66+
<td>{{ book.price }}</td>
67+
<td><button type="button" class="btn btn-success" data-toggle="modal"
68+
data-target="#update_book_{{book['isbn']}}">Update</button></td>
69+
70+
71+
<!-- Modal 2 for updating a book -->
72+
<div class="modal fade" id="update_book_{{book['isbn']}}" tabindex="-1" role="dialog"
73+
aria-labelledby="basicModal" aria-hidden="true">
74+
<div class="modal-dialog">
75+
<div class="modal-content">
76+
<div class="modal-header">
77+
<button type="button" class="close" data-dismiss="modal"
78+
aria-hidden="true">&times;</button>
79+
<h4 class="modal-title" id="myModalLabel">Update a book</h4>
80+
</div>
81+
<form action="{{url_for('update_book', isbn=book['isbn'])}}" method="post">
82+
<div class="modal-body">
83+
<div class="form-group row">
84+
<label for="author" class="col-xs-2 control-label">Author</label>
85+
<div class="col-xs-10">
86+
<input type="text" id="author" class="form-control" name="author"
87+
value="{{book['author']}}" />
88+
</div>
89+
</div>
90+
<div class="form-group row">
91+
<label for="author" class="col-xs-2 control-label">Title</label>
92+
<div class="col-xs-10">
93+
<input type="text" class="form-control" name="title"
94+
value="{{book['title']}}" />
95+
</div>
96+
</div>
97+
<div class="form-group row">
98+
<label for="author" class="col-xs-2 control-label">Price</label>
99+
<div class="col-xs-10">
100+
<input type="number" class="form-control" name="price"
101+
value="{{book['price']}}" />
102+
</div>
103+
</div>
104+
</div>
105+
<div class="modal-footer">
106+
<button type="button" class="btn btn-default"
107+
data-dismiss="modal">Close</button>
108+
<button type="submit" class="btn btn-success">Submit</button>
109+
</div>
110+
</form>
111+
</div>
112+
</div>
113+
</div>
114+
<!-- End Modal 2 -->
115+
<td><button type="button" class="btn btn-danger" data-toggle="modal"
116+
data-target="#delete_book_{{book['isbn']}}">Delete</button></td>
117+
118+
119+
<!-- Modal 3 for deleting a book -->
120+
<div class="modal fade" id="delete_book_{{book['isbn']}}" tabindex="-1" role="dialog"
121+
aria-labelledby="basicModal" aria-hidden="true">
122+
<div class="modal-dialog">
123+
<div class="modal-content">
124+
<div class="modal-header">
125+
<button type="button" class="close" data-dismiss="modal"
126+
aria-hidden="true">&times;</button>
127+
<h4 class="modal-title" id="myModalLabel">Delete a book</h4>
128+
</div>
129+
<form action="{{url_for('delete', isbn=book['isbn'])}}" method="post">
130+
<div class="modal-body">
131+
<div class="form-group row">
132+
<label class="col-sm-12 col-form-label">Do you want to delete the book <span
133+
style='font-weight:bold;color:red'>{{book['title']}}</span>
134+
?</label>
135+
136+
</div>
137+
</div>
138+
<div class="modal-footer">
139+
<button type="button" class="btn btn-default"
140+
data-dismiss="modal">Close</button>
141+
<button type="submit" class="btn btn-danger">Delete</button>
142+
</div>
143+
</form>
144+
</div>
145+
</div>
146+
</div>
147+
<!-- End Modal 3-->
148+
</tr>
149+
{% endfor %}
150+
</tbody>
151+
</table>
152+
</div>
153+
</div>
154+
{% endblock %}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from app import db
2+
from app.routes import app
3+
from app.models import Book
4+
5+
6+
@app.shell_context_processor
7+
def make_shell_context():
8+
return dict(db=db, Book=Book)

0 commit comments

Comments
 (0)