Blueprints

Table of contents

  1. Introduction
  2. Creating a blueprint
  3. Writing routes
  4. Sending forms with a CSRF token
    1. Exempting a token-authenticated API
  5. Serving a feature’s assets
  6. Shipping the assets in the package
  7. See also

Introduction

A blueprint groups a feature’s routes, templates and assets, and Flask registers it at startup. Every feature has exactly one, made with create_blueprint in its __init__.py.

from splent_framework.blueprints.base_blueprint import create_blueprint

notes_bp = create_blueprint(__name__)

Creating a blueprint

create_blueprint(__name__) names the blueprint after the feature, without the splent_feature_ prefix. So splent_io.splent_feature_notes gives notes, and splent_feature_notes_tags gives notes_tags. Every url_for("notes.index") relies on that name.

The blueprint finds its templates in the feature’s templates/ and serves its assets/. Extra arguments, such as url_prefix, go to Flask’s Blueprint.


Writing routes

from flask import flash, redirect, render_template, request, url_for
from flask_login import current_user, login_required

from splent_framework.services.service_locator import service_proxy
from splent_io.splent_feature_notes import notes_bp

notes_service = service_proxy("NotesService")


@notes_bp.route("/notes/")
@login_required
def index():
    notes = notes_service.get_by_user(current_user.id)
    return render_template("notes/index.html", notes=notes)


@notes_bp.route("/notes/create", methods=["GET", "POST"])
@login_required
def create():
    if request.method == "POST":
        notes_service.create(user_id=current_user.id, title=request.form["title"])
        flash("Note created.", "success")
        return redirect(url_for("notes.index"))
    return render_template("notes/create.html")

Routes live in routes.py and handle HTTP only, such as reading the request, rendering, flashing and redirecting. The logic lives in a service.


Sending forms with a CSRF token

Every POST, PUT, PATCH and DELETE must carry a CSRF token, or it gets 400 before the view runs. The framework protects the whole product, so your route needs nothing. The token goes in the template.

<form method="post" action="{{ url_for('notes.create') }}">
    <input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
    ...
</form>

A Flask-WTF form writes the same field with {{ form.hidden_tag() }}. A fetch sends the token in the X-CSRFToken header.

fetch(url, {
    method: "POST",
    headers: { "X-CSRFToken": form.csrf_token.value },
    body: new FormData(form),
});

Exempting a token-authenticated API

from splent_framework.managers.csrf_manager import exempt


def init_feature(app):
    exempt(api_bp, app)

An endpoint that authenticates with a bearer token, not the session cookie, may leave the check. exempt takes a blueprint or a view, from init_feature or as a decorator.

Never exempt a view a signed-in browser can reach.

With WTF_CSRF_ENABLED = False, as in the testing configuration, there is no check and csrf_token() returns an empty string.


Serving a feature’s assets

<link rel="stylesheet" href="{{ url_for('notes.assets', subfolder='css', filename='notes.css') }}">

When the feature has an assets/ folder, its blueprint serves it at /<name>/<subfolder>/<path:filename>, with the endpoint <name>.assets. The subfolder is js, css, dist or vendor, and filename may be a nested path such as leaflet/images/marker.png.

  • Any other subfolder answers 404, and a path that leaves the subfolder is refused.
  • Files go out as they are on disk, with the type guessed from the extension, so a vendored font or SVG arrives intact.
  • The URL carries ?v=<mtime>, and a matching version is cached for a year.
  • A file missing from the installed package is looked up in the feature’s working copy at the workspace root, with a debug line. In development that serves a bundle you compiled there.

Files that exist only to build the assets always answer 404, in development too.

Never served Examples
Source maps *.map
Build configs webpack.config.*, rollup.config.*, vite.config.*, babel.config.*, postcss.config.*, tailwind.config.*, tsconfig*.json
Package manifests and lock files package.json, package-lock.json, yarn.lock, pnpm-lock.yaml
Dotfiles and dependencies Any dotfile, anything under node_modules/

Shipping the assets in the package

A production image only has the files the built package carries. feature:create writes a MANIFEST.in that ships the whole assets/ tree, the templates, the translations and the migrations. Keep it, and let feature:sync-template bring it up to date.

Shipping assets/dist alone leaves out every stylesheet in assets/css and every library in assets/vendor. Development still looks fine, because it reads the files off disk, and production comes up unstyled.


See also


Back to top

splent. Distributed by an LGPL license v3. Contact us: drorganvidez@us.es