This is the template I developed for my personal "Digital Garden", which I use on my personal blog. The idea is for you to be able to use and improve it for your own purposes. I tried to create something clean, simple, and visually pleasing for anyone to use. Feel free to use it, fork it, and improve it. The code has recently undergone a major security and functionality review, and has been refactored into a professional modular architecture.
"Be curious. Read widely. Try new things. I think a lot of what people call intelligence boils down to curiosity." — Aaron Swartz
The blog offers a clean and responsive layout, optimized for a pleasant reading experience. The structure is designed to be simple to install and maintain, ideal for those looking for a personal and secure writing space. Now with modular architecture using Flask Blueprints, making maintenance and scalability easier.
-
Simple, responsive, and dark-themed design.
-
Post editor with QuillJS.
-
Enhanced Security:
-
CSRF protection on all forms.
-
HTML sanitization (XSS) on post content.
-
Automatic Feed Generation:
-
Dynamically generated RSS feed (
/feed). -
Dynamically generated Sitemap (
/sitemap.xml) for better SEO. -
Modular Architecture:
-
Clear separation of concerns with Blueprints.
-
Easy maintenance and scalability.
-
Post pagination on homepage and admin panel.
-
Protected admin area with authentication.
- Backend: Python, Flask, Jinja2
- Database: SQLAlchemy, SQLite
- Frontend: HTML5, CSS3, JavaScript
- Security: Flask-WTF (CSRF), Bleach (XSS)
- Editor: QuillJS
ageublog/
│
├── app/
│ ├── __init__.py # Flask factory, initializes blueprints
│ ├── config.py # Application configuration
│ ├── models.py # SQLAlchemy models (User, Post)
│ ├── views.py # Blueprint for public routes
│ ├── admin.py # Blueprint for admin and authentication
│ ├── utils.py # Helper functions (sanitization, auth)
│ │
│ ├── templates/ # Jinja2 templates
│ │ ├── base.html
│ │ ├── index.html
│ │ ├── post.html
│ │ ├── about.html
│ │ ├── login.html
│ │ ├── admin.html
│ │ ├── 404.html
│ │ ├── 500.html
│ │ ├── feed.xml
│ │ └── sitemap.xml
│ │
│ └── static/ # Static files
│ ├── style.css # CSS styles
│ ├── script.js # JavaScript scripts
│ ├── logo.png
│ └── favicon.ico
│
├── run.py # Application entry point
├── requirements.txt # Project dependencies
├── blog.db # SQLite database (generated)
├── README.md # This file
└── README.pt.md # Portuguese version
git clone https://github.com/Ageursilva/ageublog.git
cd ageublog
It's crucial to use a virtual environment to isolate project dependencies.
# Create the environment
python3 -m venv venv
# Activate the environment
# On Linux/macOS:
source venv/bin/activate
# On Windows:
# venv\Scripts\activate
# Install dependencies
pip install -r requirements.txtThe application needs a SECRET_KEY to work. The most secure way is to use environment variables.
Open the app/config.py file and find the line:
SECRET_KEY = os.environ.get('SECRET_KEY', 'change-me')To set the environment variable:
Linux/macOS:
export SECRET_KEY=$(python -c "import secrets; print(secrets.token_hex(16))")Windows (PowerShell):
$env:SECRET_KEY = (python -c "import secrets; print(secrets.token_hex(16))")Or edit directly in app/config.py for local testing.
With the virtual environment activated, run the following command in your terminal:
# Creates blog.db file and tables
python run.pyOn the first run, Flask will automatically create the database.
Use Python to create your first user.
python -c "
from app import create_app, db
from app.models import User
app = create_app()
with app.app_context():
admin = User(username='your_username')
admin.set_password('your_strong_password')
db.session.add(admin)
db.session.commit()
print('User created successfully!')
"Development mode:
python run.pyProduction mode (with Gunicorn):
gunicorn --workers 4 --bind 0.0.0.0:8000 run:appAccess http://127.0.0.1:5000 (or http://127.0.0.1:8000 if using Gunicorn) in your browser.
To access the admin area, go to /admin/login.
The project uses Flask Blueprints to organize routes into independent modules:
def create_app():
# Creates and configures the Flask app
# Registers all blueprints
# Initializes extensions (db, csrf)User: User model with authenticationPost: Blog post model
/: Home with pagination/post/<id>: Post page/about: About page/search: Post search/feed: RSS feed/sitemap.xml: Sitemap for SEO
/admin/login: Authentication/admin/: Control panel/admin/create_post: Create new post/admin/edit_post/<id>: Edit post/admin/delete_post/<id>: Delete post
login_required(): Decorator to protect routesclean_content(): HTML sanitizationextract_image_and_excerpt(): Extracts image and excerpt from posts
This template has been tested with several commenting solutions. Choose the one that best suits you:
- Giscus: Uses GitHub Discussions. Lightweight, modern, and supports reactions/replies.
- Cusdis: An excellent privacy-focused option that allows anonymous comments.
- Utterances: Uses GitHub Issues. A solid and simple alternative.
To implement, simply replace the comment script at the end of the templates/post.html file.
Contributions are very welcome! Feel free to:
- Open an issue to report a bug
- Suggest an improvement
- Submit a pull request
This project is licensed under the Creative Commons BY-NC-SA 4.0 License.