logic move out
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -3,3 +3,5 @@ docker-compose.yml
|
||||
Dockerfile
|
||||
Caddyfile
|
||||
deploy.sh
|
||||
.vscode/
|
||||
.ruff_cache/
|
||||
84
app.py
84
app.py
@@ -1,6 +1,6 @@
|
||||
from flask import Flask, render_template, request, redirect, url_for
|
||||
from content.posts import BLOG_POSTS
|
||||
from content.logic import get_enriched_post, get_comments_for_post, save_comment
|
||||
from flask_logic.logic import get_enriched_post, get_comments_for_post, save_comment
|
||||
|
||||
# 🌟 IMPORT THE content from separate files.
|
||||
from content.posts import BLOG_POSTS
|
||||
@@ -16,15 +16,6 @@ COMMENT_FILE = 'content/comments.csv'
|
||||
MAX_FILE_SIZE_BYTES = 5 * 1024 * 1024 # 5 Megabytes
|
||||
POSTS_PER_PAGE = 5 # posts per page limit here
|
||||
|
||||
"""
|
||||
import os
|
||||
from flask import send_from_directory
|
||||
|
||||
@app.route('/content/images/<path:filename>')
|
||||
def custom_static(filename):
|
||||
# This serves files from your private submodule folder
|
||||
return send_from_directory('content/images', filename)
|
||||
"""
|
||||
|
||||
@app.route('/post/<int:post_id>/comment', methods=['POST'])
|
||||
def post_comment(post_id):
|
||||
@@ -43,35 +34,66 @@ def post_comment(post_id):
|
||||
# --- Routes ---
|
||||
|
||||
|
||||
def calculate_pagination(posts, posts_per_page, page):
|
||||
"""
|
||||
Calculate pagination parameters for blog posts.
|
||||
|
||||
Args:
|
||||
posts (list): Full list of blog posts
|
||||
posts_per_page (int): Number of posts per page
|
||||
page (int): Current page number (1-indexed)
|
||||
|
||||
Returns:
|
||||
dict: Pagination data including:
|
||||
- posts_to_show: Posts for current page
|
||||
- prev_url: Previous page URL or None
|
||||
- next_url: Next page URL or None
|
||||
- current_page: Current page number
|
||||
- total_pages: Total number of pages
|
||||
- total_posts: Total number of posts
|
||||
"""
|
||||
# Sort posts by ID newest first
|
||||
sorted_posts = sorted(posts, key=lambda x: x['id'], reverse=True)
|
||||
|
||||
total_posts = len(sorted_posts)
|
||||
total_pages = math.ceil(total_posts / posts_per_page)
|
||||
|
||||
# Clamp page to valid range
|
||||
page = max(1, min(page, total_pages))
|
||||
|
||||
start = (page - 1) * posts_per_page
|
||||
end = start + posts_per_page
|
||||
|
||||
posts_to_show = sorted_posts[start:end]
|
||||
|
||||
prev_url = url_for('home', page=page - 1) if page > 1 else None
|
||||
next_url = url_for('home', page=page + 1) if end < total_posts else None
|
||||
|
||||
return {
|
||||
'posts_to_show': posts_to_show,
|
||||
'prev_url': prev_url,
|
||||
'next_url': next_url,
|
||||
'current_page': page,
|
||||
'total_pages': total_pages,
|
||||
'total_posts': total_posts
|
||||
}
|
||||
|
||||
@app.route('/')
|
||||
def home():
|
||||
"""Home page with paginated blog posts."""
|
||||
page = request.args.get('page', 1, type=int)
|
||||
blog_title = TITLE
|
||||
|
||||
# 2. Sort posts by ID newest first
|
||||
all_posts = sorted(BLOG_POSTS, key=lambda x: x['id'], reverse=True)
|
||||
# Calculate totals
|
||||
total_posts = len(all_posts)
|
||||
total_pages = math.ceil(total_posts / POSTS_PER_PAGE)
|
||||
# 3. Calculate start and end indices
|
||||
start = (page - 1) * POSTS_PER_PAGE
|
||||
end = start + POSTS_PER_PAGE
|
||||
|
||||
# 4. Slice the list for the current page
|
||||
posts_to_show = all_posts[start:end]
|
||||
|
||||
# 5. Determine if there is a next or previous page
|
||||
prev_url = url_for('home', page=page - 1) if page > 1 else None
|
||||
next_url = url_for('home', page=page + 1) if end < len(all_posts) else None
|
||||
pagination = calculate_pagination(BLOG_POSTS, POSTS_PER_PAGE, page)
|
||||
|
||||
return render_template('index.html',
|
||||
posts=posts_to_show,
|
||||
prev_url=prev_url,
|
||||
next_url=next_url,
|
||||
current_page=page,
|
||||
posts=pagination['posts_to_show'],
|
||||
prev_url=pagination['prev_url'],
|
||||
next_url=pagination['next_url'],
|
||||
current_page=pagination['current_page'],
|
||||
total_pages=pagination['total_pages'],
|
||||
blog_title=blog_title,
|
||||
total_pages=total_pages)
|
||||
|
||||
total_posts=pagination['total_posts'])
|
||||
|
||||
|
||||
|
||||
|
||||
93
flask_logic.py/logic.py
Normal file
93
flask_logic.py/logic.py
Normal file
@@ -0,0 +1,93 @@
|
||||
import os
|
||||
import csv
|
||||
|
||||
COMMENT_FILE = os.path.join(os.path.dirname(__file__), '../content/comments.csv')
|
||||
MAX_FILE_SIZE_BYTES = 5 * 1024 * 1024 # 5MB example
|
||||
|
||||
import datetime
|
||||
|
||||
def save_comment(post_id, author, content):
|
||||
"""Handles the validation and saving of comments to the private CSV."""
|
||||
# 1. Validation logic moved here
|
||||
if not content or len(content) > 1000:
|
||||
return False
|
||||
|
||||
author = (author or "Anonymous").strip()[:50]
|
||||
content = content.strip()
|
||||
|
||||
# 2. Path & Size Checks
|
||||
file_exists = os.path.isfile(COMMENT_FILE)
|
||||
if file_exists and os.path.getsize(COMMENT_FILE) > MAX_FILE_SIZE_BYTES:
|
||||
return False # Or raise a specific error
|
||||
|
||||
# 3. Write to CSV
|
||||
with open(COMMENT_FILE, 'a', newline='', encoding='utf-8') as f:
|
||||
writer = csv.DictWriter(f, fieldnames=['post_id', 'author', 'content', 'date'])
|
||||
if not file_exists:
|
||||
writer.writeheader()
|
||||
|
||||
writer.writerow({
|
||||
'post_id': post_id,
|
||||
'author': author,
|
||||
'content': content,
|
||||
'date': datetime.datetime.now().strftime("%B %d, %Y at %I:%M %p")
|
||||
})
|
||||
return True
|
||||
|
||||
|
||||
|
||||
def load_snippet(filename):
|
||||
"""Helper to read HTML snippets from the data folder."""
|
||||
base_path = os.path.join(os.path.dirname(__file__), 'data')
|
||||
file_path = os.path.join(base_path, filename)
|
||||
|
||||
if os.path.exists(file_path):
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
return f.read()
|
||||
return ""
|
||||
|
||||
def get_enriched_post(post_id, BLOG_POSTS):
|
||||
post = next((p for p in BLOG_POSTS if p.get('id') == post_id), None)
|
||||
if not post:
|
||||
return None
|
||||
|
||||
# Mapping config: keeps logic.py tiny
|
||||
configs = {
|
||||
10: {
|
||||
"template": "components/timeline.html",
|
||||
"timeline_file": "post_10_timeline.html"
|
||||
},
|
||||
8: {
|
||||
"template": "components/christmas_post.html",
|
||||
"timeline": None
|
||||
},
|
||||
}
|
||||
|
||||
if post_id in configs:
|
||||
conf = configs[post_id]
|
||||
post_template = conf.get("template")
|
||||
if post_template:
|
||||
post["template"] = post_template
|
||||
|
||||
# Only load the file if a filename is provided
|
||||
t_file = conf.get("timeline_file")
|
||||
if t_file:
|
||||
post["timeline"] = load_snippet(t_file)
|
||||
|
||||
return post
|
||||
|
||||
|
||||
def get_comments_for_post(post_id):
|
||||
comments = []
|
||||
if not os.path.exists(COMMENT_FILE):
|
||||
return comments
|
||||
|
||||
if os.path.getsize(COMMENT_FILE) > MAX_FILE_SIZE_BYTES:
|
||||
return ["Comment section full."] # Handle error in app.py
|
||||
|
||||
with open(COMMENT_FILE, 'r', encoding='utf-8') as f:
|
||||
reader = csv.DictReader(f)
|
||||
for row in reader:
|
||||
if row['post_id'] == str(post_id):
|
||||
comments.append(row)
|
||||
return comments
|
||||
87
readme.md
Normal file
87
readme.md
Normal file
@@ -0,0 +1,87 @@
|
||||
# Flask Blog Templates
|
||||
|
||||
A minimal **Flask template set** for my blog.
|
||||
This project adapts and evolves the original [simple-blog-template](https://github.com/earlbread/simple-blog-template) by [Seunghun Lee](https://github.com/earlbread).
|
||||
|
||||
## Git Tag
|
||||
|
||||
Current version:
|
||||
|
||||
```bash
|
||||
git tag -a v1.0 -m "Initial release of Flask Blog Templates"
|
||||
git push origin v1.0
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
It provides a growing collection of styling templates and is designed to run with two additional files (see below).
|
||||
|
||||
In the current setup, blog content is defined using Python dictionaries (example below), while comments are stored in a CSV file at `content/comments.csv`.
|
||||
|
||||
A basic blog structure is provided by the existing templates. Additional per-article styling can be applied either by embedding raw HTML directly in the post content or by using template expansion via Jinja:
|
||||
|
||||
```jinja2
|
||||
{% if post.template %}
|
||||
{% include post.template %}
|
||||
{% endif %}
|
||||
```
|
||||
|
||||
For example, `templates/components/timeline.html` demonstrates a timeline component.
|
||||
|
||||
An example data structure is provided below.
|
||||
|
||||
---
|
||||
|
||||
## Required Files
|
||||
|
||||
This two files should be included in program folder to run the app.
|
||||
|
||||
### `content/about.py`
|
||||
|
||||
provides text for about page and blog title
|
||||
|
||||
```python
|
||||
ABOUT = {
|
||||
"content": """
|
||||
<p>Hello here is the about page</p>
|
||||
"""
|
||||
}
|
||||
|
||||
TITLE = {
|
||||
"title": "Hello World"
|
||||
}
|
||||
```
|
||||
|
||||
### `content/posts.py`
|
||||
|
||||
provides articles in blog:
|
||||
|
||||
```python
|
||||
BLOG_POSTS = [
|
||||
{
|
||||
'id': 7,
|
||||
'title': "title",
|
||||
'subtitle': "subtitle",
|
||||
'date': "December 10, 2024",
|
||||
'content': """can use raw html string for style, will be rendered as html
|
||||
""",
|
||||
'displayall': False
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
## Template Evolution
|
||||
|
||||
- Originally based on the Simple Blog Template project.
|
||||
|
||||
- Adapted for Jinja2 rendering (Flask compatibility).
|
||||
|
||||
- CSS rewritten for flexibility and modular styling.
|
||||
|
||||
- Template design will continue accumulating for themes and post customization.
|
||||
|
||||
## License
|
||||
|
||||
- This project is distributed under the [MIT License](https://github.com/earlbread/simple-blog-template/blob/master/LICENSE).
|
||||
Reference in New Issue
Block a user