dweeb
dweeb

Reputation: 549

How to serve static files in Flask with Blueprints

I had a Flask app that worked fine until, due to growth, I decided to modularize. The new structure is as follows:

├── Dockerfile
├── README.md
├── app
│   ├── __init__.py
│   ├── config.py
│   ├── cron.py
│   ├── database.ini
│   ├── db.py
│   ├── db.sqlite3
│   └── webapp
│       ├── __init__.py
│       └── routes.py
├── db.sqlite3
├── docker-compose.yml
├── error.log
├── gunicorn_config.py
├── rd.py
├── requirements.txt
├── static
│   ├── css
│   │   └── bootstrap.min.css
│   ├── data.html
│   ├── fonts
│   │   ├── glyphicons-halflings-regular.eot
│   │   ├── glyphicons-halflings-regular.svg
│   │   ├── glyphicons-halflings-regular.ttf
│   │   ├── glyphicons-halflings-regular.woff
│   │   └── glyphicons-halflings-regular.woff2
│   ├── images
│   │   ├── ajax-loader.gif
│   │   ├── gear.png
│   │   ├── gear_small.png
│   │   └── logo.png
│   ├── index.html
│   ├── js
│   │   ├── app.js
│   │   ├── bootbox.min.js
│   │   ├── bootstrap.js
│   │   ├── bootstrap.min.js
│   │   └── npm.js
│   ├── login.html
│   ├── manage_users.html
│   ├── register.html
│   ├── reset_password.html
│   ├── settings.html
│   └── upload.html
└── templates
    └── base.html

Here is the __init__.py for webapp:

from flask import Flask
from app.config import DebugConfig
from flask_sqlalchemy import SQLAlchemy
from importlib import import_module
from logging import basicConfig, DEBUG, getLogger, StreamHandler
import os

import uuid

def register_blueprints(app):
    for module_name in (app.config['MODULES']):
        module = import_module('app.{}.routes'.format(module_name))
        app.register_blueprint(module.blueprint)

def configure_logs(app):
    basicConfig(filename='error.log', level=DEBUG)
    logger = getLogger()
    logger.addHandler(StreamHandler())

def create_app():
    file = (__file__)
    app = Flask(__name__)
    app.secret_key = str(uuid.uuid4())
    app.config.from_object(DebugConfig)
    register_blueprints(app)
    configure_logs(app)

    return app

Here is login page route code:

@blueprint.route("/login", methods=["GET", "POST"])
def login():

    if request.method == 'GET':
        return render_template(url_for('static', filename='login.html')

Unfortunately this new structure leads to this error when I attempt to serve the app:

builtins.FileNotFoundError FileNotFoundError: [Errno 2] No such file or directory: '/static/login.html'

I have been messing with this for a while now and just can't figure out how to get the app to work and manage to locate the files. I tried setting the static_url_path value when instantiating the flask app but, although that manages to locate the HTML files, I'm unable to load the other static files like CSS and images because I have my paths defined relative to the static folder as below in the HTML:

<link href="css/bootstrap.min.css?version=24" > or <link rel="icon" href="/static/images/gear.png">

Please assist, I feel like I'm going to pull my hair out as I've spent too much time on this issue. Thanks in advance.

Upvotes: 1

Views: 1459

Answers (1)

TonyMoutaux
TonyMoutaux

Reputation: 357

What your are looking for is the Flask jinja_loader.

In your create_app method, you could overwrite the jinja_loader provided by default for Flask, which look into the template_folder directory to search for file using render_template method.

def create_app():
    file = (__file__)
    app = Flask(__name__)
    app.secret_key = str(uuid.uuid4())
    app.config.from_object(DebugConfig)
    register_blueprints(app)
    configure_logs(app)

    # Overwrite Flask jinja_loader, using ChoiceLoader
    template_loader = jinja2.ChoiceLoader([
        app.jinja_loader,
        jinja2.FileSystemLoader('/path/to/static'),
    ])
    app.jinja_loader = template_loader

    return app

Jinja2 ChoiceLoader look for template in the list of loaders. So first in app.jinja_loader : the template_folder directory. Then using the FileSystemLoader in the /static directory. Be warn that if you have a template something.html in both the template and the static directory, the first one will be returned.

But Jinja2 has more loader than just the CoiceLoader and you should try to find one that suite your desires. For exemple the DictLoader with a key 'static' could be fine also

Upvotes: 1

Related Questions