Skip to main content

Project Structure

enferno/
├── enferno/                # Main application package
│   ├── app.py             # Application factory
│   ├── settings.py        # Configuration
│   ├── extensions.py      # Flask extensions
│   ├── commands.py        # CLI commands
│   ├── portal/            # Blueprint: Protected routes
│   ├── public/            # Blueprint: Public routes
│   ├── user/              # Blueprint: User management
│   ├── tasks/             # Background tasks (optional Celery)
│   ├── utils/             # Utility functions
│   ├── static/            # Static assets (Vue, Vuetify, CSS)
│   └── templates/         # Jinja2 templates
├── docs/                  # Documentation
├── instance/             # SQLite database
├── pyproject.toml        # Dependencies and project config
├── setup.sh              # Setup script
├── run.py                # Entry point
├── AGENTS.md             # AI coding instructions
└── docker-compose.yml    # Docker configuration

Blueprints

Enferno uses a three-blueprint architecture for better organization and security:

1. Portal Blueprint (portal/)

Usually contains protected routes that require authentication. Enferno uses a pattern of protecting all routes in this blueprint automatically using before_request:
from flask import Blueprint
from flask_security import auth_required

portal = Blueprint('portal', __name__)

# Protect all routes in this blueprint automatically
@portal.before_request
@auth_required()
def before_request():
    pass

@portal.route('/dashboard')
def dashboard():
    return render_template('portal/dashboard.html')

@portal.route('/settings')
def settings():
    return render_template('portal/settings.html')
This pattern ensures that all routes within the portal blueprint require authentication without needing to decorate each route individually.

2. User Blueprint (user/)

Handles user management, authentication, and profile-related routes:
from flask import Blueprint
from flask_security import auth_required

user = Blueprint('user', __name__)

@user.route('/profile')
@auth_required()
def profile():
    return render_template('user/profile.html')

3. Public Blueprint (public/)

Contains routes that are publicly accessible without authentication:
from flask import Blueprint

public = Blueprint('public', __name__)

@public.route('/')
def index():
    return render_template('public/index.html')

Database Operations

Enferno uses SQLAlchemy 2.x with the Flask-SQLAlchemy extension. The db instance is available from enferno.extensions.

Model Definition

from enferno.extensions import db
from datetime import datetime

class Post(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(80), nullable=False)
    content = db.Column(db.Text)
    created_at = db.Column(db.DateTime, default=datetime.utcnow)
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
    user = db.relationship('User', back_populates='posts')

Database Operations

from enferno.extensions import db
from enferno.models import Post
from sqlalchemy import select

# Create
post = Post(title='New Post', content='Content here')
db.session.add(post)
db.session.commit()

# Simple queries
stmt = db.session.select(Post)  # Select all posts
posts = db.session.scalars(stmt).all()

post = db.session.get(Post, 1)  # Get by ID

# Filtered query
stmt = db.session.select(Post).where(Post.title.like('%python%'))
python_posts = db.session.scalars(stmt).all()

# Ordered query with join
stmt = (
    db.session.select(Post)
    .join(Post.user)
    .order_by(Post.created_at.desc())
)
recent_posts = db.session.scalars(stmt).all()

# Update
post = db.session.get(Post, 1)
post.title = 'Updated Title'
db.session.commit()

# Delete
db.session.delete(post)
db.session.commit()

Background Tasks (Optional)

Celery is available when you need async job processing. First, install the full extras:
uv sync --extra full    # Adds Redis + Celery
Define tasks in enferno/tasks/__init__.py:
from enferno.tasks import celery

@celery.task
def send_email(user_id, subject, message):
    from enferno.extensions import db
    from enferno.user.models import User

    user = db.session.get(User, user_id)
    # Send email...
    return True
Call tasks asynchronously:
from enferno.tasks import send_email
send_email.delay(user.id, 'Welcome', 'Welcome to Enferno!')
Run Celery worker:
uv run celery -A enferno.tasks worker --loglevel=info

API Development

Add API endpoints to your blueprints:
from flask import Blueprint, jsonify
from enferno.extensions import db

api = Blueprint('api', __name__)

@api.route('/posts')
def get_posts():
    query = db.select(Post).order_by(Post.created_at.desc())
    posts = db.session.scalars(query).all()
    return jsonify([post.to_dict() for post in posts])

Development Server

uv run flask run              # Start server
uv run ruff check --fix .     # Lint and auto-fix
uv run ruff format .          # Format code

Security Best Practices

  1. Input Validation
    from flask_wtf import FlaskForm
    from wtforms import StringField
    from wtforms.validators import DataRequired, Length
    
    class PostForm(FlaskForm):
        title = StringField('Title', validators=[
            DataRequired(),
            Length(max=80)
        ])
    
  2. CSRF Protection
    from flask_wtf.csrf import CSRFProtect
    csrf = CSRFProtect(app)
    
  3. Authentication
    from flask_security import auth_required
    
    @app.route('/protected')
    @auth_required()
    def protected_route():
        return 'Protected content'
    

Debugging

Enferno includes several debugging tools:
  1. Flask Debug Toolbar
    from flask_debugtoolbar import DebugToolbarExtension
    toolbar = DebugToolbarExtension(app)
    
  2. Logging
    import logging
    logging.basicConfig(level=logging.DEBUG)
    logger = logging.getLogger(__name__)
    
  3. Database Debugging
    # Enable SQLAlchemy query logging
    logging.getLogger('sqlalchemy.engine').setLevel(logging.INFO)