Modular Python system for managing a static blog with automatic post generation, index, and navigation. Completely redesigned for maximum robustness and stability.
- ✅ Post creation: Interactive guided mode or from JSON files (audio removed to avoid dependencies)
- ✅ Automatic index generation: Updates
index.htmlwith all posts - ✅ Automatic navigation: Previous/next links in each post
- ✅ Separate HTML templates: No embedded HTML in Python, easy to customize
- ✅ Self-managed posts.json: Automatically created and updated
- ✅ Posts with or without images: Full support for posts without featured images
- ✅ Responsive grid: Adaptive design based on number of posts
- ✅ Complete regeneration: Rebuilds entire blog when changing templates
- ✅ Image validation: Warns if missing but continues without blocking
- ✅ Modular and scalable: Clean, commented, and maintainable code
This system was created as a practical exercise for a blog post, focusing on the automation engine rather than design. You'll need to create your own HTML templates, CSS, and any JavaScript you want to use. The real value here is the robust automation—post generation, index updates, navigation, and tag management—all handled with simple commands. Think of this as the engine; you bring the templates and style to make the blog truly yours! 🚀
blog_system/
├── config.py # ⚙️ Centralized configuration
├── utils.py # 🔧 Shared functions
├── post_generator.py # 📄 HTML post generation
├── index_generator.py # 📑 Index generation
├── navigation_updater.py # 🔗 Navigation updates
├── blog_manager.py # 🎯 Main orchestrator (RUN THIS)
│
├── templates/ # 🎨 HTML templates
│ ├── post_template.html # Individual post template
│ └── index_template.html # Index template
│
├── posts/ # 📝 Generated posts
│ ├── 1-post-slug.html
│ ├── 1-post-slug.json
│ ├── 2-another-post.html
│ └── 2-another-post.json
│
├── tags/ # 🏷️ Generated tag listings
│ ├── tag.html
│ ├── posts-tag.json
│ ├── another.html
│ └── posts-another.json
│
├── assets/
│ ├── thumbnails/ # 🖼️ Index thumbnails
│ │ ├── 1-post-thumb.jpg
│ │ └── 2-another-post-thumb.jpg
│ └── post-images/ # 🖼️ Featured post images
│ ├── post.jpg
│ └── another-post.jpg
│
├── posts.json # 📋 Master index (self-managed)
├── index.html # 🏠 Blog index (generated)
└── README.md # 📖 This file
📊 MODEL (JSON)
├── posts.json → Master post index
└── posts/*.json → Individual post data
🎨 VIEW (HTML Templates)
├── templates/post_template.html → Individual post template
└── templates/index_template.html → Index template
🎮 CONTROLLER (Python Scripts)
├── blog_manager.py → Main orchestrator
├── post_generator.py → Generates HTML from JSON
├── index_generator.py → Generates index
└── navigation_updater.py → Updates navigation
The system automatically manages posts.json with this structure:
{
"posts": [
{
"number": 1,
"slug": "when-the-future-looks-back",
"title": "When the Future Looks Back",
"excerpt": "Post excerpt...",
"tags": ["Future", "AI"],
"read_time": "3",
"thumbnail": "1-post-thumb.jpg",
"featured_image": "post.jpg"
},
{
"number": 2,
"slug": "another-post",
"title": "Another Post",
"excerpt": "...",
"tags": ["Tech"],
"read_time": "5",
"thumbnail": null,
"featured_image": null
}
]
}Important notes:
posts.jsonis automatically created if it doesn't exist- Automatically updated when creating/editing posts
thumbnailandfeatured_imagecan benull(no image)- Posts are automatically sorted by number
Open config.py and adjust according to your structure:
# Paths (relative to directory where you run blog_manager.py)
POSTS_FOLDER = "posts"
THUMBNAILS_FOLDER = "assets/thumbnails"
POST_IMAGES_FOLDER = "assets/post-images"
TEMPLATES_FOLDER = "templates"
# Blog information
AUTHOR_NAME = "Author name"
BLOG_NAME = "Blog"
AUTHOR_BIO = "Your bio here..."
BLOG_DESCRIPTION = "Your blog description"
PORTFOLIO_URL = "Your main site or domain URL"
# Responsive grid (NEW FEATURE!)
MAX_POSTS_SINGLE_COLUMN = 4 # If ≤4 posts: centered single column
# If >4 posts: 3-column grid
# Image validation
VALIDATE_IMAGES = True # Validate image existence
ALLOW_MISSING_IMAGES = True # True = warns only, False = blocksExample of 1-post.json:
{
"number": 1,
"slug": "once-upon-a-time",
"title": "Once Upon a Time",
"excerpt": "Excerpt...",
"tags": ["Future", "Story", "AI"],
"read_time": "3",
"thumbnail": "1-post-thumb.jpg",
"featured_image": "post.jpg",
"content": "<p>Complete HTML post content...</p>",
"prev_post": null,
"next_post": {
"number": 2,
"slug": "another-post",
"title": "Another Post"
}
}python3 blog_manager.pyYou'll see:
1. Create new post (guided mode)
2. Update blog index
3. Update post navigation
4. Regenerate entire blog
5. Exit
# On Linux/Mac (to paste HTML >1023 characters)
stty -icanon; python3 blog_manager.py --new-post
# On Windows
python3 blog_manager.py --new-postThe system will ask for:
- Post title
- Slug (automatically generated from title)
- Excerpt
- Tags (comma-separated)
- Reading time
- Thumbnail (optional, Enter to skip)
- Featured image (optional, Enter to skip)
- HTML content (type
ENDto finish)
Automatic flow after creating post:
- ✅ Generates post HTML
- ✅ Saves individual JSON
- ✅ Updates posts.json
- ✅ Regenerates index.html
- ✅ Regenerates tag listings and updates tag clouds on existing pages and listings
- ✅ Updates navigation for all posts
python3 blog_manager.py --from-json my-post.jsonJSON file format:
{
"title": "My New Post",
"excerpt": "Post summary",
"content": "<p>HTML content...</p>",
"tags": ["Tag1", "Tag2"],
"read_time": "5",
"thumbnail": "1-my-post-thumb.jpg",
"featured_image": "my-post.jpg"
}Notes:
numberandslugare automatically generatedthumbnailandfeatured_imageare optional
python3 blog_manager.py --update-indexpython3 blog_manager.py --regenerate-tagspython3 blog_manager.py --update-navpython3 blog_manager.py --regenerateUse this when:
- You change HTML templates
- You modify CSS styles in templates
- posts.json becomes out of sync
- index.html layout isn't updating according to post count (delete posts.json before regenerating if this is the case)
- You want to rebuild everything from scratch
-
Prepare images:
# Thumbnail for index cp image.jpg assets/thumbnails/3-my-post-thumb.jpg # Featured image for post cp large-image.jpg assets/post-images/my-post.jpg
-
Run post creator:
python3 blog_manager.py --new-post
-
Follow instructions
-
Result:
- ✅ HTML post generated in
posts/ - ✅ Individual JSON saved
- ✅ posts.json updated
- ✅ index.html regenerated
- ✅ Navigation updated
- ✅ HTML post generated in
-
Run creator:
python3 blog_manager.py --new-post
-
Press Enter on image fields:
Thumbnail name (or Enter if none): [Enter] Featured image name (or Enter if none): [Enter] -
Result:
- Post without
<img>block in HTML - Index card without thumbnail
- Everything works perfectly
- Post without
-
Edit templates:
# Edit post template nano templates/post_template.html # Edit index template nano templates/index_template.html
-
Regenerate everything:
python3 blog_manager.py --regenerate
-
Result: All posts and index use new templates
-
Edit individual JSON:
nano posts/1-my-post.json
-
Modify
contentfield -
Regenerate post:
python3 blog_manager.py --regenerate
Available placeholders:
{title}- Post title{excerpt}- Post excerpt{author}- Author name (from config.py){author_bio}- Author bio{content}- Post HTML content{tags_html}- Tags formatted in HTML{read_time}- Reading time{featured_image_html}- Featured image (empty if none){navigation_html}- Prev/next navigation (empty if none){index_file}- Index file name
Example usage in template:
<h1>{title}</h1>
<div>{content}</div>
{featured_image_html}
{tags_html}Available placeholders:
{author}- Author name{blog_name}- Blog name{blog_description}- Blog description{portfolio_url}- Portfolio URL{author_bio}- Author bio{posts_cards}- HTML of all post cards{grid_class}- Grid CSS class:"single-post"if posts ≤ MAX_POSTS_SINGLE_COLUMN"small-cards"if posts > MAX_POSTS_SINGLE_COLUMN
Responsive Grid:
The system automatically applies CSS classes based on post count:
/* 1-4 posts: Centered single column */
.posts-grid.single-post {
grid-template-columns: 1fr;
max-width: 800px;
}
/* 5+ posts: 3-column grid with small cards */
.posts-grid.small-cards {
grid-template-columns: repeat(3, 1fr);
}
.small-cards .post-thumbnail {
height: 180px; /* Smaller */
}Cause: templates/post_template.html or templates/index_template.html doesn't exist
Solution:
# Verify templates exist
ls templates/
# If they don't exist, create them from original filesCause: Running script from incorrect folder
Solution:
# Run from project root folder
cd /path/to/blog_system
python blog_manager.pyCause: Incorrect path or image doesn't exist
Solution:
-
Verify image exists:
ls assets/thumbnails/1-post-thumb.jpg
-
Verify paths in
config.py:THUMBNAILS_FOLDER = "assets/thumbnails"
-
Verify path in
posts.json:"thumbnail": "1-post-thumb.jpg"
Solution:
python3 blog_manager.py --update-navThis recalculates all navigation links.
Solution:
# Delete posts.json
rm posts.json
# System will rebuild it automatically
python3 blog_manager.py --regenerate# 1. Backup (just in case)
cp -r posts posts_backup
cp posts.json posts.json.backup
# 2. Clean everything
rm posts.json
rm posts/*.html
rm posts/*.json
# 3. Create first post
python3 blog_manager.py --new-postblog_system/
│
├── 🎯 MAIN EXECUTABLES
│ └── blog_manager.py # RUN THIS for everything
│
├── ⚙️ CONFIGURATION
│ └── config.py # Centralized configuration
│
├── 🔧 INTERNAL MODULES
│ ├── utils.py # Shared functions
│ ├── post_generator.py # Generates HTML posts
│ ├── index_generator.py # Generates HTML index
│ └── navigation_updater.py # Updates navigation
│
├── 🎨 HTML TEMPLATES
│ └── templates/
│ ├── post_template.html # Individual post template
│ └── index_template.html # Index template
│
├── 📝 GENERATED POSTS
│ └── posts/
│ ├── 1-post.html # Post HTML
│ ├── 1-post.json # Post data
│ ├── 2-another.html
│ └── 2-another.json
│
├── 🏷️ GENERATED TAG LISTS
│ └── tags/
│ ├── tag.html # Listing HTML
│ ├── posts-tag.json # Listing data
│ ├── another.html
│ └── posts-another.json
│
├── 🖼️ RESOURCES
│ └── assets/
│ ├── thumbnails/ # Index thumbnails
│ │ ├── 1-post-thumb.jpg
│ │ └── 2-another-thumb.jpg
│ └── post-images/ # Featured images
│ ├── post.jpg
│ └── another.jpg
│
├── 📋 INDEXES
│ ├── posts.json # Master index (self-managed)
│ └── index.html # HTML index (generated)
│
└── 📖 DOCUMENTATION
└── README.md # This file
The system is designed to be extensible:
- Improved file structure (Data file for all JSON data, etc.)
- Category support
- sitemap.xml generation
- Draft system
- Blog search
- RSS feed
- Index pagination
- Export to Markdown
- Preview before publishing
AGPL-3.0 license
Questions or issues? Review the commented code or this README. All modules are exhaustively documented.
System developed with: Python 3.x | HTML5 | CSS3