176 lines
No EOL
5.9 KiB
Markdown
176 lines
No EOL
5.9 KiB
Markdown
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 <head> 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:
|
||
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
||
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. |