diff --git a/.env.example b/.env.example index d1e83ee..a87c13c 100644 --- a/.env.example +++ b/.env.example @@ -1,7 +1,3 @@ -FLASK_APP=wsgi.py +ENVIRONMENT=production FLASK_DEBUG=False -SECRET_KEY=randomstringofcharacters -LESS_BIN=/usr/local/bin/lessc -ASSETS_DEBUG=False -LESS_RUN_IN_DEBUG=False -COMPRESSOR_DEBUG=True \ No newline at end of file +SECRET_KEY=randomstringofcharacters \ No newline at end of file diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..00e2b94 --- /dev/null +++ b/.flake8 @@ -0,0 +1,4 @@ +[flake8] +select = E9,F63,F7,F82 +exclude = .git,.github,__pycache__,.pytest_cache,.venv,logs,creds +max-line-length = 120 \ No newline at end of file diff --git a/.github/workflows/pythonapp.yml b/.github/workflows/pythonapp.yml index 70bba55..f79bc01 100644 --- a/.github/workflows/pythonapp.yml +++ b/.github/workflows/pythonapp.yml @@ -5,32 +5,30 @@ name: Python application on: push: - branches: [ master ] + branches: [master] pull_request: - branches: [ master ] + branches: [master] jobs: build: - runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - name: Set up Python 3.9 - uses: actions/setup-python@v2 - with: - python-version: 3.9 - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install flake8 pytest - if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - - name: Lint with flake8 - run: | - # stop the build if there are Python syntax errors or undefined names - flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics - # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics - - name: Test with pytest - run: | - pytest + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.10" + cache: "pip" # caching pip dependencies + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install flake8 pytest + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + + - name: Lint with flake8 + run: | + # stop the build if there are Python syntax errors or undefined names + flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics + # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide + flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics diff --git a/LICENSE b/LICENSE index b6ae3eb..425c816 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2020 Hackers and Slackers +Copyright (c) 2023 Hackers and Slackers Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Makefile b/Makefile index e96b655..e63ec93 100644 --- a/Makefile +++ b/Makefile @@ -30,13 +30,9 @@ $(VIRTUAL_ENV): python3 -m venv $(VIRTUAL_ENV); \ fi -.PHONY: dev -dev: env - $(LOCAL_PYTHON) -m main --reload - .PHONY: run run: env - $(LOCAL_PYTHON) -m main + $(LOCAL_PYTHON) -m gunicorn -w 4 wsgi:app .PHONY: install install: env diff --git a/README.md b/README.md index d412399..370fc33 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,9 @@ # Flask Blueprint Tutorial -![Python](https://img.shields.io/badge/Python-v^3.10-blue.svg?logo=python&longCache=true&logoColor=white&colorB=5e81ac&style=flat-square&colorA=4c566a) -![Flask](https://img.shields.io/badge/Flask-v2.2.2-blue.svg?longCache=true&logo=flask&style=flat-square&logoColor=white&colorB=5e81ac&colorA=4c566a) -![Flask-Assets](https://img.shields.io/badge/Flask--Assets-v2.0-blue.svg?longCache=true&logo=flask&style=flat-square&logoColor=white&colorB=5e81ac&colorA=4c566a) +![Python](https://img.shields.io/badge/Python-v3.10-blue.svg?logo=python&longCache=true&logoColor=white&colorB=5e81ac&style=flat-square&colorA=4c566a) +![Flask](https://img.shields.io/badge/Flask-v3.0.0-blue.svg?longCache=true&logo=flask&style=flat-square&logoColor=white&colorB=5e81ac&colorA=4c566a) +![Flask-Assets](https://img.shields.io/badge/Flask--Assets-v2.1.0-blue.svg?longCache=true&logo=flask&style=flat-square&logoColor=white&colorB=5e81ac&colorA=4c566a) +![Gunicorn](https://img.shields.io/badge/Gunicorn-v21.2.0-blue.svg?longCache=true&logo=gunicorn&style=flat-square&logoColor=white&colorB=a3be8c&colorA=4c566a) ![GitHub Last Commit](https://img.shields.io/github/last-commit/google/skia.svg?style=flat-square&colorA=4c566a&colorB=a3be8c&logo=GitHub) [![GitHub Issues](https://img.shields.io/github/issues/hackersandslackers/flask-blueprint-tutorial.svg?style=flat-square&colorA=4c566a&logo=GitHub&colorB=ebcb8b)](https://github.com/hackersandslackers/flask-blueprint-tutorial/issues) [![GitHub Stars](https://img.shields.io/github/stars/hackersandslackers/flask-blueprint-tutorial.svg?style=flat-square&colorA=4c566a&logo=GitHub&colorB=ebcb8b)](https://github.com/hackersandslackers/flask-blueprint-tutorial/stargazers) @@ -13,7 +14,7 @@ Structure your Flask apps in a scalable and intelligent way using Blueprints. * **Tutorial**: [https://hackersandslackers.com/flask-blueprints/](https://hackersandslackers.com/flask-blueprints/) -* **Demo**: [https://flaskblueprints.hackersandslackers.app/](https://flaskblueprints.hackersandslackers.app/) +* **Demo**: [https://flaskblueprints.hackersandslackers.app/](https://flaskblueprints.hackersandslackers.com/) ## Getting Started @@ -23,13 +24,9 @@ Get set up locally in two steps: Replace the values in **.env.example** with your values and rename this file to **.env**: -* `FLASK_APP`: Entry point of your application; should be `wsgi.py`. -* `FLASK_ENV`: The environment in which to run your application; either `development` or `production`. +* `ENVIRONMENT`: The environment in which to run your application (either `development` or `production`). +* `FLASK_DEBUG`: Set to `True` to enable Flask's debug mode (default to `False` in prod). * `SECRET_KEY`: Randomly generated string of characters used to encrypt your app's data. -* `LESS_BIN` *(optional for static assets)*: Path to your local LESS installation via `which lessc`. -* `ASSETS_DEBUG` *(optional)*: Debug asset creation and bundling in `development`. -* `LESS_RUN_IN_DEBUG` *(optional)*: Debug LESS while in `development`. -* `COMPRESSOR_DEBUG` *(optional)*: Debug asset compression while in `development`. *Remember never to commit secrets saved in .env files to Github.* @@ -40,7 +37,7 @@ Get up and running with `make run`: ```shell git clone https://github.com/hackersandslackers/flask-blueprint-tutorial.git cd flask-blueprint-tutorial -make run +make deploy ``` ----- diff --git a/config.py b/config.py index bb0280c..8e596b4 100644 --- a/config.py +++ b/config.py @@ -1,31 +1,34 @@ """Class-based Flask app configuration.""" -from os import environ, path +from os import environ, path, system from dotenv import load_dotenv -basedir = path.abspath(path.dirname(__file__)) -load_dotenv(path.join(basedir, ".env")) +BASE_DIR = path.abspath(path.dirname(__file__)) +load_dotenv(path.join(BASE_DIR, ".env")) class Config: """Configuration from environment variables.""" + # General Config\ + ENVIRONMENT = environ.get("ENVIRONMENT") + + # Flask Config SECRET_KEY = environ.get("SECRET_KEY") - FLASK_ENV = environ.get("FLASK_DEBUG") + FLASK_DEBUG = environ.get("FLASK_DEBUG") FLASK_APP = "wsgi.py" - # Flask-Assets - LESS_BIN = environ.get("LESS_BIN") - ASSETS_DEBUG = True - LESS_RUN_IN_DEBUG = True - # Static Assets STATIC_FOLDER = "static" TEMPLATES_FOLDER = "templates" - COMPRESSOR_DEBUG = True + COMPRESSOR_DEBUG = False - # Datadog - DD_SERVICE = environ.get("DD_SERVICE") - - # API - BEST_BUY_API_KEY = environ.get("BEST_BUY_API_KEY") + # Flask-Assets + LESS_BIN = system("which lessc") + ASSETS_DEBUG = False + LESS_RUN_IN_DEBUG = False + if ENVIRONMENT == "development" and LESS_BIN is None: + raise ValueError("Application running in `development` mode cannot create assets without `lessc` installed.") + + # Hardcoded data + PRODUCT_DATA_FILEPATH = f"{BASE_DIR}/data/products.json" diff --git a/data/products.json b/data/products.json new file mode 100644 index 0000000..f14f40f --- /dev/null +++ b/data/products.json @@ -0,0 +1,92 @@ +[ + { + "customerReviewAverage": 5.00, + "customerReviewCount": 293, + "name": "Red Robin - $25 Gift Card", + "sku": 4259000, + "image": "https://pisces.bbystatic.com/prescaled/500/500/image2/BestBuy_US/images/products/4259/4259000_sd.jpg", + "manufacturer": "Red Robin", + "longDescription": "Come in and enjoy an outrageously delicious burger with Bottomless Steak Fries. Pair it with a cold beer or signature Freckled Lemonade - it's a duo that's sure to make you smile.", + "salePrice": 25.00 + }, + { + "customerReviewAverage": 5.00, + "customerReviewCount": 53, + "name": "Bowers & Wilkins - 700 Series 3-way Floorstanding Speaker w/5\" midrange, dual 5\" bass (each) - White", + "sku": 6023602, + "image": "https://pisces.bbystatic.com/prescaled/500/500/image2/BestBuy_US/images/products/6023/6023602_sd.jpg", + "manufacturer": "Bowers & Wilkins", + "longDescription": "Generate commanding sound with this Bowers & Wilkins loudspeaker. Its Carbon Dome tweeter plays accurate, clear highs, and the two Aerofoil bass drivers deliver stiffness and rigidity while producing dynamic bass. This vented Bowers & Wilkins loudspeaker has a 5-inch midrange driver to round out its full sound, and its slim construction makes it suitable for small or large spaces.", + "salePrice": 1487.99 + }, + { + "customerReviewAverage": 5.00, + "customerReviewCount": 53, + "name": "Bowers & Wilkins - 700 Series 3-way Floorstanding Speaker w/5\" midrange, dual 5\" bass (each) - Gloss Black", + "sku": 6027601, + "image": "https://pisces.bbystatic.com/prescaled/500/500/image2/BestBuy_US/images/products/6027/6027601_sd.jpg", + "manufacturer": "Bowers & Wilkins", + "longDescription": "Enjoy accurate, realistic sound with this black Bowers & Wilkins floor speaker. Its two bass drivers deliver thumping low frequencies, and the Carbon Dome tweeter and midrange driver offer commanding, studio-quality audio. This Bowers & Wilkins floor speaker integrates into your living space and entertainment system for seamless sound production with a frequency range of 48Hz - 28kHz.", + "salePrice": 1487.99 + }, + { + "customerReviewAverage": 5.00, + "customerReviewCount": 55, + "name": "Canon - RF50mm F1.2 L USM Standard Prime Lens for EOS R-Series Cameras - Black", + "sku": 6298180, + "image": "https://pisces.bbystatic.com/prescaled/500/500/image2/BestBuy_US/images/products/6298/6298180_sd.jpg", + "manufacturer": "Canon", + "longDescription": "Capture high-quality images and sharp details with this 50mm Canon lens. The 1.31-foot minimum focusing distance and 0.19x magnification let you photograph from a range of distances, and its UD lens reduces distortion. This Canon lens has an added coating to minimize lens flare and ghosting in various types of light.", + "salePrice": 2199.99 + }, + { + "customerReviewAverage": 5.00, + "customerReviewCount": 83, + "name": "Nikkor Z 24-70mm f/2.8 S Optical Zoom Lens for Nikon Z - Black", + "sku": 6334316, + "image": "https://pisces.bbystatic.com/prescaled/500/500/image2/BestBuy_US/images/products/6334/6334316_sd.jpg", + "manufacturer": "Nikon", + "longDescription": "Capture high-quality photographs whether shooting at close, medium or long range with this Nikon NIKKOR Z 24-70mm lens. The dust-resistant and drip-resistant design helps keep this lens in good condition, and the auto-focusing feature is quick and quiet. This Nikon NIKKOR Z 24-70mm lens features a Z system that produces images with enhanced sharpness and illumination.", + "salePrice": 2099.99 + }, + { + "customerReviewAverage": 5.00, + "customerReviewCount": 64, + "name": "Apple Watch Ultra (GPS + Cellular) 49mm Titanium Case with White Ocean Band - Titanium (Verizon)", + "sku": 6340050, + "image": "https://pisces.bbystatic.com/prescaled/500/500/image2/BestBuy_US/images/products/6340/6340050_sd.jpg", + "manufacturer": "Apple", + "longDescription": "The most rugged and capable Apple Watch ever, designed for exploration, adventure, and endurance. With a 49mm aerospace-grade titanium case, extra-long battery life,¹ specialized apps that work with the advanced sensors, and a new customizable Action button. See Dimension section below for band sizing information.", + "salePrice": 799.99 + }, + { + "customerReviewAverage": 5.00, + "customerReviewCount": 64, + "name": "Apple Watch Ultra (GPS + Cellular) 49mm Titanium Case with Yellow Ocean Band - Titanium (Verizon)", + "sku": 6340051, + "image": "https://pisces.bbystatic.com/prescaled/500/500/image2/BestBuy_US/images/products/6340/6340051_sd.jpg", + "manufacturer": "Apple", + "longDescription": "The most rugged and capable Apple Watch ever, designed for exploration, adventure, and endurance. With a 49mm aerospace-grade titanium case, extra-long battery life,¹ specialized apps that work with the advanced sensors, and a new customizable Action button. See Dimension section below for band sizing information.", + "salePrice": 799.99 + }, + { + "customerReviewAverage": 5.00, + "customerReviewCount": 64, + "name": "Apple Watch Ultra (GPS + Cellular) 49mm Titanium Case with Midnight Ocean Band - Titanium (Verizon)", + "sku": 6340057, + "image": "https://pisces.bbystatic.com/prescaled/500/500/image2/BestBuy_US/images/products/6340/6340057_sd.jpg", + "manufacturer": "Apple", + "longDescription": "The most rugged and capable Apple Watch ever, designed for exploration, adventure, and endurance. With a 49mm aerospace-grade titanium case, extra-long battery life,¹ specialized apps that work with the advanced sensors, and a new customizable Action button. See Dimension section below for band sizing information.", + "salePrice": 799.99 + }, + { + "customerReviewAverage": 5.00, + "customerReviewCount": 59, + "name": "NETGEAR - 8-Port 10/100/1000 Gigabit Ethernet PoE/PoE+ Unmanaged Switch", + "sku": 6356333, + "image": "https://pisces.bbystatic.com/prescaled/500/500/image2/BestBuy_US/images/products/6356/6356333_sd.jpg", + "manufacturer": "NETGEAR", + "longDescription": "Introducing the NETGEAR GS108LP 8-port Gigabit Ethernet unmanaged switch with 60W PoE budget. The flexible PoE+ integrated technology allows you to increase or decrease the PoE budget at any time to provide to your devices the power they need with interchangeable external power supply and an intuitive power selector. The compact and fanless design makes this switch an ideal solution to connect or power any device in any business environment.", + "salePrice": 98.99 + } +] \ No newline at end of file diff --git a/flask_blueprint_tutorial/__init__.py b/flask_blueprint_tutorial/__init__.py index 16ffb5a..7b7ef82 100644 --- a/flask_blueprint_tutorial/__init__.py +++ b/flask_blueprint_tutorial/__init__.py @@ -2,10 +2,8 @@ from flask import Flask from flask_assets import Environment -from config import Config - -def init_app(): +def create_app(): """Create Flask application.""" app = Flask(__name__, instance_relative_config=False) app.config.from_object("config.Config") @@ -20,8 +18,8 @@ def init_app(): from .profile import profile # Register Blueprints - app.register_blueprint(profile.profile_bp) - app.register_blueprint(home.home_bp) + app.register_blueprint(profile.profile_blueprint) + app.register_blueprint(home.home_blueprint) app.register_blueprint(products.product_bp) # Compile static assets diff --git a/flask_blueprint_tutorial/api.py b/flask_blueprint_tutorial/api.py index f229fb6..4f803c1 100644 --- a/flask_blueprint_tutorial/api.py +++ b/flask_blueprint_tutorial/api.py @@ -1,19 +1,18 @@ -"""Source app with worthless data.""" -import requests +"""Read placeholder data for demo purposes.""" +import json +from flask import Flask -def fetch_products(app): - """Grab product listings from BestBuy.""" - endpoint = "https://api.bestbuy.com/v1/products(customerReviewAverage>=4&customerReviewCount>100&longDescription=*)" - params = { - "show": "customerReviewAverage,customerReviewCount,name,sku,image,description,manufacturer,longDescription,salePrice,sku", - "apiKey": app.config["BEST_BUY_API_KEY"], - "format": "json", - "pageSize": 6, - "totalPages": 1, - "sort": "customerReviewAverage.dsc", - } - headers = {"Accept": "application/json", "Content-Type": "application/json"} - req = requests.get(endpoint, params=params, headers=headers) - products = req.json()["products"] - return products + +def fetch_products(app: Flask) -> dict: + """ + Grab hardcoded product listings. + + :param Flask app: Flask application object. + + :returns: dict + """ + product_data_filepath = app.config["PRODUCT_DATA_FILEPATH"] + with open(product_data_filepath, encoding="utf-8") as file: + products_data = json.load(file) + return products_data diff --git a/flask_blueprint_tutorial/assets.py b/flask_blueprint_tutorial/assets.py index 10e19f5..fb83aeb 100644 --- a/flask_blueprint_tutorial/assets.py +++ b/flask_blueprint_tutorial/assets.py @@ -3,8 +3,14 @@ from flask_assets import Bundle -def compile_static_assets(assets): - """Create stylesheet bundles.""" +def compile_static_assets(assets: Bundle) -> Bundle: + """ + Create CSS stylesheet bundles from .less files. + + :param Bundle assets: Static asset bundle. + + :returns: Bundle + """ assets.auto_build = True assets.debug = False common_style_bundle = Bundle( @@ -14,19 +20,19 @@ def compile_static_assets(assets): extra={"rel": "stylesheet/less"}, ) home_style_bundle = Bundle( - "home_bp/less/home.less", + "home_blueprint/less/home.less", filters="less,cssmin", output="dist/css/home.css", extra={"rel": "stylesheet/less"}, ) profile_style_bundle = Bundle( - "profile_bp/less/profile.less", + "profile_blueprint/less/profile.less", filters="less,cssmin", output="dist/css/profile.css", extra={"rel": "stylesheet/less"}, ) product_style_bundle = Bundle( - "products_bp/less/products.less", + "products_blueprint/less/products.less", filters="less,cssmin", output="dist/css/products.css", extra={"rel": "stylesheet/less"}, @@ -35,7 +41,7 @@ def compile_static_assets(assets): assets.register("home_style_bundle", home_style_bundle) assets.register("profile_style_bundle", profile_style_bundle) assets.register("product_style_bundle", product_style_bundle) - if app.config["FLASK_ENV"] == "development": + if app.config["ENVIRONMENT"] == "development": common_style_bundle.build() home_style_bundle.build() profile_style_bundle.build() diff --git a/flask_blueprint_tutorial/home/home.py b/flask_blueprint_tutorial/home/home.py index 67b3114..20934c5 100644 --- a/flask_blueprint_tutorial/home/home.py +++ b/flask_blueprint_tutorial/home/home.py @@ -6,14 +6,16 @@ from flask_blueprint_tutorial.api import fetch_products # Blueprint Configuration -home_bp = Blueprint( - "home_bp", __name__, template_folder="templates", static_folder="static" -) +home_blueprint = Blueprint("home_blueprint", __name__, template_folder="templates", static_folder="static") -@home_bp.route("/", methods=["GET"]) -def home(): - """Homepage.""" +@home_blueprint.route("/", methods=["GET"]) +def home() -> str: + """ + Serve `Home` page template. + + :returns: str + """ products = fetch_products(app) return render_template( "index.jinja2", @@ -24,9 +26,13 @@ def home(): ) -@home_bp.route("/about", methods=["GET"]) -def about(): - """About page.""" +@home_blueprint.route("/about", methods=["GET"]) +def about() -> str: + """ + Serve `About` page template. + + :returns: str + """ return render_template( "index.jinja2", title="About", @@ -35,9 +41,13 @@ def about(): ) -@home_bp.route("/contact", methods=["GET"]) -def contact(): - """Contact page.""" +@home_blueprint.route("/contact", methods=["GET"]) +def contact() -> str: + """ + Serve `Contact` page template. + + :returns: str + """ return render_template( "index.jinja2", title="Contact", diff --git a/flask_blueprint_tutorial/home/templates/index.jinja2 b/flask_blueprint_tutorial/home/templates/index.jinja2 index fd51f46..4989cb2 100644 --- a/flask_blueprint_tutorial/home/templates/index.jinja2 +++ b/flask_blueprint_tutorial/home/templates/index.jinja2 @@ -13,8 +13,8 @@

{{ title }}

{{ subtitle }}

diff --git a/flask_blueprint_tutorial/products/products.py b/flask_blueprint_tutorial/products/products.py index 755e426..39884cc 100644 --- a/flask_blueprint_tutorial/products/products.py +++ b/flask_blueprint_tutorial/products/products.py @@ -6,14 +6,18 @@ from flask_blueprint_tutorial.api import fetch_products # Blueprint Configuration -product_bp = Blueprint( - "products_bp", __name__, template_folder="templates", static_folder="static" -) +product_bp = Blueprint("products_blueprint", __name__, template_folder="templates", static_folder="static") @product_bp.route("/products//", methods=["GET"]) -def product_page(product_id): - """Product description page.""" +def product_page(product_id: int) -> str: + """ + Product detail page for a given product ID. + + :params int product_id: Unique product ID. + + :returns: str + """ product = fetch_products(app)[product_id] return render_template( "products.jinja2", diff --git a/flask_blueprint_tutorial/profile/profile.py b/flask_blueprint_tutorial/profile/profile.py index 0d24c4c..2347019 100644 --- a/flask_blueprint_tutorial/profile/profile.py +++ b/flask_blueprint_tutorial/profile/profile.py @@ -5,14 +5,16 @@ fake = Faker() # Blueprint Configuration -profile_bp = Blueprint( - "profile_bp", __name__, template_folder="templates", static_folder="static" -) +profile_blueprint = Blueprint("profile_blueprint", __name__, template_folder="templates", static_folder="static") -@profile_bp.route("/profile", methods=["GET"]) -def user_profile(): - """Logged-in user profile page.""" +@profile_blueprint.route("/profile", methods=["GET"]) +def user_profile() -> str: + """ + Logged-in user profile page. + + :returns: str + """ user = fake.simple_profile() job = fake.job() return render_template( diff --git a/flask_blueprint_tutorial/static/dist/css/style.css b/flask_blueprint_tutorial/static/dist/css/style.css index 864a242..b0582ce 100644 --- a/flask_blueprint_tutorial/static/dist/css/style.css +++ b/flask_blueprint_tutorial/static/dist/css/style.css @@ -1 +1 @@ -nav{background:#fff;padding:30px;width:auto;margin-bottom:40px;box-shadow:0 0 5px #bec6cf}nav .nav-wrapper{max-width:1014px!important;display:flex;justify-content:space-between;align-items:center;width:auto;margin:0 auto}nav .nav-wrapper .left-nav{display:flex;justify-content:space-between;align-items:center}nav .nav-wrapper .left-nav a{margin-right:40px}@media(max-width:600px){nav .nav-wrapper .left-nav a{margin-right:25px!important}}nav .nav-wrapper .left-nav img{width:30px;margin-right:0}nav .nav-wrapper a{font-weight:600;color:#7d849a}@media(max-width:600px){nav .nav-wrapper a{font-size:.95em}}nav a{color:#4d545d;transition:all .2s ease-out;text-decoration:none;font-size:1.1em}nav a:hover{cursor:pointer;opacity:.7}nav{background:#fff;padding:30px;width:auto;margin-bottom:40px;box-shadow:0 0 5px #bec6cf}nav .nav-wrapper{max-width:1014px!important;display:flex;justify-content:space-between;align-items:center;width:auto;margin:0 auto}nav .nav-wrapper .left-nav{display:flex;justify-content:space-between;align-items:center}nav .nav-wrapper .left-nav a{margin-right:40px}@media(max-width:600px){nav .nav-wrapper .left-nav a{margin-right:25px!important}}nav .nav-wrapper .left-nav img{width:30px;margin-right:0}nav .nav-wrapper a{font-weight:600;color:#7d849a}@media(max-width:600px){nav .nav-wrapper a{font-size:.95em}}nav a{color:#4d545d;transition:all .2s ease-out;text-decoration:none;font-size:1.1em}nav a:hover{cursor:pointer;opacity:.7}body,html{font-family:'Poppins',sans-serif;margin:0;padding:0;background:#f0f0f0;height:100%;color:#5f6988}body .container,html .container{width:948px;max-width:88%;margin:0 auto;height:fit-content;background:white;padding:30px 40px;box-shadow:0 0 5px rgba(65,67,144,0.15)}@media(max-width:800px){body .container,html .container{width:82%;margin:0 auto 20px;padding:30px 6%}}body .container h1,html .container h1{line-height:1;margin:0 0 10px}body .container h2,html .container h2{margin:0;font-weight:400;font-size:1.2em;line-height:1}body .container a,html .container a{color:#5dbad7;transition:all .2s ease-out;text-decoration:none}body .container a:hover,html .container a:hover{cursor:pointer;opacity:.7}body .blueprint-info,html .blueprint-info{height:fit-content;margin:20px auto 0;width:968px;max-width:88%;background:white;padding:20px 30px;box-shadow:0 0 5px rgba(65,67,144,0.15)}@media(max-width:800px){body .blueprint-info,html .blueprint-info{width:82%;margin:0 auto;padding:30px 6%}}body .blueprint-info h3,html .blueprint-info h3{margin:0;font-size:1.4em;font-weight:500}body .blueprint-info ul,html .blueprint-info ul{list-style:none;border:1px solid #e6e6e6;padding:10px}body .blueprint-info ul li,html .blueprint-info ul li{display:flex;justify-content:space-between;align-items:center;border-bottom:1px solid #ececec;padding-bottom:10px;margin-bottom:10px;font-size:1.1em}body .blueprint-info ul li:last-of-type,html .blueprint-info ul li:last-of-type{border-bottom:none;margin-bottom:0;padding-bottom:0}body .blueprint-info ul li span,html .blueprint-info ul li span{font-weight:500;font-size:.9em}body .blueprint-info ul .attribute-value,html .blueprint-info ul .attribute-value{font-weight:300;font-size:.9em} \ No newline at end of file +nav{background:#fff;padding:30px;width:auto;margin-bottom:40px;box-shadow:0 0 5px #bec6cf}nav .nav-wrapper{max-width:1014px!important;display:flex;justify-content:space-between;align-items:center;width:auto;margin:0 auto}nav .nav-wrapper .left-nav{display:flex;justify-content:space-between;align-items:center}nav .nav-wrapper .left-nav a{margin-right:40px}@media(max-width:600px){nav .nav-wrapper .left-nav a{margin-right:25px!important}}nav .nav-wrapper .left-nav img{width:50px;margin-right:0}nav .nav-wrapper a{font-weight:600;color:#7d849a}@media(max-width:600px){nav .nav-wrapper a{font-size:.95em}}nav a{color:#4d545d;transition:all .2s ease-out;text-decoration:none;font-size:1.1em}nav a:hover{cursor:pointer;opacity:.7}nav{background:#fff;padding:30px;width:auto;margin-bottom:40px;box-shadow:0 0 5px #bec6cf}nav .nav-wrapper{max-width:1014px!important;display:flex;justify-content:space-between;align-items:center;width:auto;margin:0 auto}nav .nav-wrapper .left-nav{display:flex;justify-content:space-between;align-items:center}nav .nav-wrapper .left-nav a{margin-right:40px}@media(max-width:600px){nav .nav-wrapper .left-nav a{margin-right:25px!important}}nav .nav-wrapper .left-nav img{width:50px;margin-right:0}nav .nav-wrapper a{font-weight:600;color:#7d849a}@media(max-width:600px){nav .nav-wrapper a{font-size:.95em}}nav a{color:#4d545d;transition:all .2s ease-out;text-decoration:none;font-size:1.1em}nav a:hover{cursor:pointer;opacity:.7}body,html{font-family:'Poppins',sans-serif;margin:0;padding:0;background:#f0f0f0;height:100%;color:#5f6988}body .container,html .container{width:948px;max-width:88%;margin:0 auto;height:fit-content;background:white;padding:30px 40px;box-shadow:0 0 5px rgba(65,67,144,0.15)}@media(max-width:800px){body .container,html .container{width:82%;margin:0 auto 20px;padding:30px 6%}}body .container h1,html .container h1{line-height:1;margin:0 0 5px}body .container h2,html .container h2{margin:0;font-weight:400;font-size:1.2em;line-height:1}body .container a,html .container a{color:#5dbad7;transition:all .2s ease-out;text-decoration:none}body .container a:hover,html .container a:hover{cursor:pointer;opacity:.7}body .blueprint-info,html .blueprint-info{height:fit-content;margin:20px auto 0;width:968px;max-width:88%;background:white;padding:20px 30px;box-shadow:0 0 5px rgba(65,67,144,0.15)}@media(max-width:800px){body .blueprint-info,html .blueprint-info{width:82%;margin:0 auto;padding:30px 6%}}body .blueprint-info h3,html .blueprint-info h3{margin:0;font-size:1.4em;font-weight:500}body .blueprint-info ul,html .blueprint-info ul{list-style:none;border:1px solid #e6e6e6;padding:10px}body .blueprint-info ul li,html .blueprint-info ul li{display:flex;justify-content:space-between;align-items:center;border-bottom:1px solid #ececec;padding-bottom:10px;margin-bottom:10px;font-size:1.1em}body .blueprint-info ul li:last-of-type,html .blueprint-info ul li:last-of-type{border-bottom:none;margin-bottom:0;padding-bottom:0}body .blueprint-info ul li span,html .blueprint-info ul li span{font-weight:500;font-size:.9em}body .blueprint-info ul .attribute-value,html .blueprint-info ul .attribute-value{font-weight:300;font-size:.9em} \ No newline at end of file diff --git a/flask_blueprint_tutorial/static/img/avatar.png b/flask_blueprint_tutorial/static/dist/img/avatar.png similarity index 100% rename from flask_blueprint_tutorial/static/img/avatar.png rename to flask_blueprint_tutorial/static/dist/img/avatar.png diff --git a/flask_blueprint_tutorial/static/dist/img/favicon@2x.png b/flask_blueprint_tutorial/static/dist/img/favicon@2x.png new file mode 100644 index 0000000..61bdc13 Binary files /dev/null and b/flask_blueprint_tutorial/static/dist/img/favicon@2x.png differ diff --git a/flask_blueprint_tutorial/static/dist/img/logo@2x.png b/flask_blueprint_tutorial/static/dist/img/logo@2x.png new file mode 100644 index 0000000..61bdc13 Binary files /dev/null and b/flask_blueprint_tutorial/static/dist/img/logo@2x.png differ diff --git a/flask_blueprint_tutorial/static/img/favicon.png b/flask_blueprint_tutorial/static/img/favicon.png deleted file mode 100644 index 7ba45c3..0000000 Binary files a/flask_blueprint_tutorial/static/img/favicon.png and /dev/null differ diff --git a/flask_blueprint_tutorial/static/img/logo.png b/flask_blueprint_tutorial/static/img/logo.png deleted file mode 100644 index d5474a6..0000000 Binary files a/flask_blueprint_tutorial/static/img/logo.png and /dev/null differ diff --git a/flask_blueprint_tutorial/static/src/less/nav.less b/flask_blueprint_tutorial/static/src/less/nav.less index 86ddea6..66a5c75 100644 --- a/flask_blueprint_tutorial/static/src/less/nav.less +++ b/flask_blueprint_tutorial/static/src/less/nav.less @@ -28,7 +28,7 @@ nav { } img { - width: 30px; + width: 50px; margin-right: 0; } } diff --git a/flask_blueprint_tutorial/templates/blueprintinfo.jinja2 b/flask_blueprint_tutorial/templates/blueprintinfo.jinja2 index 347ae41..731ad3a 100644 --- a/flask_blueprint_tutorial/templates/blueprintinfo.jinja2 +++ b/flask_blueprint_tutorial/templates/blueprintinfo.jinja2 @@ -6,4 +6,4 @@
  • View: {{ request.endpoint }}
  • Route: {{ request.path }}
  • -
    \ No newline at end of file + \ No newline at end of file diff --git a/flask_blueprint_tutorial/templates/layout.jinja2 b/flask_blueprint_tutorial/templates/layout.jinja2 index 1cf2c72..4f830af 100644 --- a/flask_blueprint_tutorial/templates/layout.jinja2 +++ b/flask_blueprint_tutorial/templates/layout.jinja2 @@ -9,15 +9,15 @@ - + - + - + diff --git a/flask_blueprint_tutorial/templates/navigation.jinja2 b/flask_blueprint_tutorial/templates/navigation.jinja2 index 3a1ab7e..2297f4f 100644 --- a/flask_blueprint_tutorial/templates/navigation.jinja2 +++ b/flask_blueprint_tutorial/templates/navigation.jinja2 @@ -2,19 +2,19 @@