Bullet Journal App – Flask Step‑by‑Step Build Guide Using Flask 3.x, Python 3.12+, and SQLite. Follow each section, copy the commands into a terminal and run them in order. 1. Environment Setup Install core dependencies pip install Flask Flask-SQLAlchemy Flask-Login Flask-Migrate Generate a requirements.txt (optional but handy) pip freeze > requirements.txt 2. Project Structure bullet-journal-flask/ │ ├── app/ │ ├── __init__.py │ ├── models.py │ ├── routes.py │ ├── templates/ │ │ ├── base.html │ │ ├── index.html │ │ ├── entry_list.html │ │ ├── entry_detail.html │ │ └── entry_form.html │ └── static/ │ └── style.css │ ├── config.py ├── run.py └── requirements.txt 3. Configure Flask & SQLite config.py – add the following (you’ll copy this into a file): import os basedir = os.path.abspath(os.path.dirname(__file__)) class Config: SECRET_KEY = os.environ.get('SECRET_KEY') or 'dev-key' SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'app.db') SQLALCHEMY_TRACK_MODIFICATIONS = False app/__init__.py – bootstrap the app (copy into the file): from flask import Flask from flask_sqlalchemy import SQLAlchemy from flask_login import LoginManager from flask_migrate import Migrate from . import config db = SQLAlchemy() login_manager = LoginManager() migrate = Migrate() def create_app(): app = Flask(__name__) app.config.from_object(config.Config) db.init_app(app) login_manager.init_app(app) migrate.init_app(app, db) from . import routes app.register_blueprint(routes.bp) return app 4. Models (SQLAlchemy) app/models.py – copy the following into the file: from . import db, login_manager from flask_login import UserMixin from datetime import datetime class User(UserMixin, db.Model): id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(64), unique=True, nullable=False) password_hash = db.Column(db.String(128), nullable=False) class Entry(db.Model): id = db.Column(db.Integer, primary_key=True) title = db.Column(db.String(200), nullable=False) content = db.Column(db.Text, nullable=False) created_at = db.Column(db.DateTime, default=datetime.utcnow) updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) is_private = db.Column(db.Boolean, default=False) user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) # Optional: tags, category relationships can be added later @login_manager.user_loader def load_user(user_id): return User.query.get(int(user_id)) 5. Routes & Blueprints app/routes.py – copy the following into the file: from flask import Blueprint, render_template, redirect, url_for, request, flash from flask_login import login_required, current_user from . import db from .models import Entry bp = Blueprint('main', __name__) @bp.route('/') @login_required def index(): today = db.session.query(Entry).filter_by(user_id=current_user.id).order_by(Entry.created_at.desc()).first() return render_template('index.html', entry=today) @bp.route('/entries') @login_required def entry_list(): entries = Entry.query.filter_by(user_id=current_user.id).order_by(Entry.created_at.desc()).all() return render_template('entry_list.html', entries=entries) @bp.route('/entries/') @login_required def entry_detail(id): entry = Entry.query.get_or_404(id) return render_template('entry_detail.html', entry=entry) @bp.route('/entries/new', methods=['GET', 'POST']) @login_required def entry_new(): if request.method == 'POST': title = request.form['title'] content = request.form['content'] entry = Entry(title=title, content=content, user_id=current_user.id) db.session.add(entry) db.session.commit() flash('Entry created!') return redirect(url_for('main.entry_list')) return render_template('entry_form.html', action='New') @bp.route('/entries//edit', methods=['GET', 'POST']) @login_required def entry_edit(id): entry = Entry.query.get_or_404(id) if request.method == 'POST': entry.title = request.form['title'] entry.content = request.form['content'] db.session.commit() flash('Entry updated!') return redirect(url_for('main.entry_detail', id=id)) return render_template('entry_form.html', entry=entry, action='Edit') @bp.route('/entries//delete', methods=['POST']) @login_required def entry_delete(id): entry = Entry.query.get_or_404(id) db.session.delete(entry) db.session.commit() flash('Entry deleted!') return redirect(url_for('main.entry_list')) 6. Templates (Skeleton) Create app/templates/base.html – contains
with CSS, navigation, and {% block content %}{% endblock %}. Add index.html, entry_list.html, entry_detail.html, entry_form.html – use Jinja2 to loop over entries and display forms. 7. Static Files & Styling Create app/static/style.css with your design. Link it in base.html: 8. Run the Application Set environment variables export FLASK_APP=run.py export FLASK_ENV=development export SECRET_KEY='super-secret' # or use a .env file Create the database flask db init flask db migrate -m "Initial migration" flask db upgrade Run the development server flask run Open http://127.0.0.1:5000/ in your browser. 9. run.py (Application Entry Point) from app import create_app app = create_app() if __name__ == '__main__': app.run() 10. Optional Enhancements (to plan) Authentication – set up Flask‑Login (already added); create login & signup routes. Tagging – add a Tag model and many‑to‑many relationship to Entry. Search & Filters – add query parameters to /entries. Responsive Design – media queries in style.css. Export – use flask-clipboard or pdfkit for PDF. Testing – write tests with pytest-flask.