diff --git a/web-programming/bookshop-crud-app-flask-fullstack/LICENSE b/web-programming/bookshop-crud-app-flask-fullstack/LICENSE new file mode 100644 index 00000000..7a493e2d --- /dev/null +++ b/web-programming/bookshop-crud-app-flask-fullstack/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2022 Ezzeddin Abdullah + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/web-programming/bookshop-crud-app-flask-fullstack/README.md b/web-programming/bookshop-crud-app-flask-fullstack/README.md new file mode 100644 index 00000000..76f40df7 --- /dev/null +++ b/web-programming/bookshop-crud-app-flask-fullstack/README.md @@ -0,0 +1,12 @@ +# Fullstack Bookshop CRUD app using MySQL with Flask and SQLAlchemy + +## Quick start +```bash +$ python3 -m venv venv +$ . venv/bin/activate +$ pip install --upgrade pip +$ pip install -r requirements.txt +$ cd fullstack_crud_flask_sqlalchemy +$ export FLASK_APP=bookshop.py +$ flask run +``` diff --git a/web-programming/bookshop-crud-app-flask-fullstack/app/__init__.py b/web-programming/bookshop-crud-app-flask-fullstack/app/__init__.py new file mode 100644 index 00000000..fa7056db --- /dev/null +++ b/web-programming/bookshop-crud-app-flask-fullstack/app/__init__.py @@ -0,0 +1,17 @@ +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 diff --git a/web-programming/bookshop-crud-app-flask-fullstack/app/models.py b/web-programming/bookshop-crud-app-flask-fullstack/app/models.py new file mode 100644 index 00000000..b9ab5bc0 --- /dev/null +++ b/web-programming/bookshop-crud-app-flask-fullstack/app/models.py @@ -0,0 +1,17 @@ +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 + } diff --git a/web-programming/bookshop-crud-app-flask-fullstack/app/routes.py b/web-programming/bookshop-crud-app-flask-fullstack/app/routes.py new file mode 100644 index 00000000..c40aad3d --- /dev/null +++ b/web-programming/bookshop-crud-app-flask-fullstack/app/routes.py @@ -0,0 +1,65 @@ +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/", 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/", 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/', 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")) diff --git a/web-programming/bookshop-crud-app-flask-fullstack/app/static/main.css b/web-programming/bookshop-crud-app-flask-fullstack/app/static/main.css new file mode 100644 index 00000000..0304ed6c --- /dev/null +++ b/web-programming/bookshop-crud-app-flask-fullstack/app/static/main.css @@ -0,0 +1,12 @@ +body { + padding-top: 50px; +} + +.starter-template { + padding: 40px 15px; + text-align: center; +} + +td, th { + text-align: center +} \ No newline at end of file diff --git a/web-programming/bookshop-crud-app-flask-fullstack/app/templates/base.html b/web-programming/bookshop-crud-app-flask-fullstack/app/templates/base.html new file mode 100644 index 00000000..7f751757 --- /dev/null +++ b/web-programming/bookshop-crud-app-flask-fullstack/app/templates/base.html @@ -0,0 +1,36 @@ +{% extends "bootstrap/base.html" %} + +{% block title %}Bookshop{% endblock %} + +{% block head %} +{{ super() }} + +{% endblock %} +{% block navbar %} + + +{% endblock %} + +{% block content %} +
+ {% block page_content %} {% endblock %} +
+{% endblock %} \ No newline at end of file diff --git a/web-programming/bookshop-crud-app-flask-fullstack/app/templates/book.html b/web-programming/bookshop-crud-app-flask-fullstack/app/templates/book.html new file mode 100644 index 00000000..1fc7edae --- /dev/null +++ b/web-programming/bookshop-crud-app-flask-fullstack/app/templates/book.html @@ -0,0 +1,7 @@ +{% extends "base.html" %} + +{% block page_content %} +
+

Book's ISBN: {{ isbn }}

+
+{% endblock %} \ No newline at end of file diff --git a/web-programming/bookshop-crud-app-flask-fullstack/app/templates/books.html b/web-programming/bookshop-crud-app-flask-fullstack/app/templates/books.html new file mode 100644 index 00000000..9a739668 --- /dev/null +++ b/web-programming/bookshop-crud-app-flask-fullstack/app/templates/books.html @@ -0,0 +1,7 @@ +{% extends "base.html" %} + +{% block page_content %} +
+ +
+{% endblock %} \ No newline at end of file diff --git a/web-programming/bookshop-crud-app-flask-fullstack/app/templates/index.html b/web-programming/bookshop-crud-app-flask-fullstack/app/templates/index.html new file mode 100644 index 00000000..a3e70a6b --- /dev/null +++ b/web-programming/bookshop-crud-app-flask-fullstack/app/templates/index.html @@ -0,0 +1,154 @@ +{% extends "base.html" %} + +{% block page_content %} +
+

Welcome to our bookshop!

+
+ + + + + + +
+
+ + + + + + + + + + + + {% for book in books %} + + + + + + + + + + + + + + + + + + + {% endfor %} + +
ISBNAuthorTitlePriceAction
{{ book.isbn }}{{ book.author }}{{ book.title }}{{ book.price }}
+
+
+{% endblock %} \ No newline at end of file diff --git a/web-programming/bookshop-crud-app-flask-fullstack/bookshop.py b/web-programming/bookshop-crud-app-flask-fullstack/bookshop.py new file mode 100644 index 00000000..1199db89 --- /dev/null +++ b/web-programming/bookshop-crud-app-flask-fullstack/bookshop.py @@ -0,0 +1,8 @@ +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) diff --git a/web-programming/bookshop-crud-app-flask-fullstack/config.py b/web-programming/bookshop-crud-app-flask-fullstack/config.py new file mode 100644 index 00000000..cbdb1609 --- /dev/null +++ b/web-programming/bookshop-crud-app-flask-fullstack/config.py @@ -0,0 +1,31 @@ +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 +} diff --git a/web-programming/bookshop-crud-app-flask-fullstack/requirements.txt b/web-programming/bookshop-crud-app-flask-fullstack/requirements.txt new file mode 100644 index 00000000..cc26cb19 --- /dev/null +++ b/web-programming/bookshop-crud-app-flask-fullstack/requirements.txt @@ -0,0 +1,16 @@ +click==8.0.4 +dataclasses==0.8 +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==0.0.0 +PyMySQL==1.0.2 +SQLAlchemy==1.4.35 +typing_extensions==4.1.1 +Werkzeug==2.0.3 +zipp==3.6.0