From f2fb55ea3330c8ab2dbc51f20c0baa94fd4e57c3 Mon Sep 17 00:00:00 2001 From: PV Tejas Date: Sun, 1 Mar 2026 20:32:48 +0530 Subject: [PATCH 01/14] Working code, failing test --- flaskr/__init__.py | 2 +- flaskr/blog.py | 22 ++++++++++++++++++++++ flaskr/templates/blog/firehose.html | 28 ++++++++++++++++++++++++++++ flaskr/templates/blog/index.html | 1 - flaskr/templates/blog/post.html | 19 +++++++++++++++++++ tests/test_blog.py | 19 +++++++++++++++++++ 6 files changed, 89 insertions(+), 2 deletions(-) create mode 100644 flaskr/templates/blog/firehose.html create mode 100644 flaskr/templates/blog/post.html diff --git a/flaskr/__init__.py b/flaskr/__init__.py index 7d7ec81..33779f8 100644 --- a/flaskr/__init__.py +++ b/flaskr/__init__.py @@ -8,7 +8,7 @@ def create_app(test_config=None): app.config.from_mapping( SECRET_KEY='dev', DATABASE=os.path.join(app.instance_path, 'flaskr.sqlite'), - ALLOW_REGISTER=False, + ALLOW_REGISTER=True, ) app.wsgi_app = ProxyFix( diff --git a/flaskr/blog.py b/flaskr/blog.py index 523cfaf..6bf9b55 100644 --- a/flaskr/blog.py +++ b/flaskr/blog.py @@ -28,6 +28,23 @@ def index(): posts.append(post) return render_template('blog/index.html', posts=posts) +@bp.route('/firehose') +def firehose(): + db = get_db() + db_posts = db.execute( + 'SELECT p.id, title, body, created, author_id, username' + ' FROM post p JOIN user u ON p.author_id = u.id' + ' ORDER BY created DESC' + ).fetchall() + posts = [] + for post in db_posts: + if post['created'] > datetime.datetime.utcnow(): + continue + post = dict(post) + post['body'] = markdown.markdown(post['body']) + posts.append(post) + return render_template('blog/firehose.html', posts=posts) + @bp.route('/create',methods=('GET', 'POST')) @login_required def create(): @@ -69,6 +86,11 @@ def get_post(id, check_author=True): return post +@bp.route('/') +def individual_post(id): + post = get_post(id, False) + return render_template('blog/post.html', post=post) + @bp.route('//update', methods=('GET', 'POST')) @login_required def update(id): diff --git a/flaskr/templates/blog/firehose.html b/flaskr/templates/blog/firehose.html new file mode 100644 index 0000000..21e3178 --- /dev/null +++ b/flaskr/templates/blog/firehose.html @@ -0,0 +1,28 @@ +{% extends 'base.html' %} + +{% block header %} +

{% block title %}Posts{% endblock %}

+ {% if g.user %} + New + {% endif %} +{% endblock %} + +{% block content %} + {% for post in posts %} +
+
+
+

{{ post['title'] }}

+
by {{ post['username'] }} on {{ post['created'].strftime('%Y-%m-%d') }}
+
+ {% if g.user['id'] == post['author_id'] %} + Edit + {% endif %} +
+

{{ post['body']|safe }}

+
+ {% if not loop.last %} +
+ {% endif %} + {% endfor %} +{% endblock %} diff --git a/flaskr/templates/blog/index.html b/flaskr/templates/blog/index.html index ffd9fbc..7f1e4e8 100644 --- a/flaskr/templates/blog/index.html +++ b/flaskr/templates/blog/index.html @@ -19,7 +19,6 @@ Edit {% endif %} -

{{ post['body']|safe }}

{% if not loop.last %}
diff --git a/flaskr/templates/blog/post.html b/flaskr/templates/blog/post.html new file mode 100644 index 0000000..c2a612c --- /dev/null +++ b/flaskr/templates/blog/post.html @@ -0,0 +1,19 @@ +{% extends 'base.html' %} + +{% block header %} +

{% block title %}{{ post['title'] }}{% endblock %}

+{% endblock %} + +{% block content %} +
+
+
+
by {{ post['username'] }} on {{ post['created'].strftime('%Y-%m-%d') }}
+
+ {% if g.user['id'] == post['author_id'] %} + Edit + {% endif %} +
+

{{ post['body']|safe }}

+
+{% endblock %} diff --git a/tests/test_blog.py b/tests/test_blog.py index 810ade1..175e5b5 100644 --- a/tests/test_blog.py +++ b/tests/test_blog.py @@ -6,6 +6,19 @@ def test_index(client, auth): assert b"Log In" not in response.data assert b"Register" not in response.data + auth.login() + response = client.get('/') + assert b'Log Out' in response.data + assert b'test title' in response.data + assert b'by test on 2018-01-01' in response.data + assert b'test\nbody' not in response.data + assert b'href="/1/update"' in response.data + +def test_firehose(client, auth): + response = client.get('/') + assert b"Log In" not in response.data + assert b"Register" not in response.data + auth.login() response = client.get('/') assert b'Log Out' in response.data @@ -14,6 +27,12 @@ def test_index(client, auth): assert b'test\nbody' in response.data assert b'href="/1/update"' in response.data +def test_individual_page(client, auth): + response = client.get('/1') + assert b'test title' in response.data + assert b'by test on 2018-01-01' in response.data + assert b'test\nbody' in response.data + @pytest.mark.parametrize('path', ( '/create', '/1/update', From 13bdcaef9edb85d7b56f234f129d76a83659d7f2 Mon Sep 17 00:00:00 2001 From: PV Tejas Date: Wed, 11 Mar 2026 20:25:52 +0530 Subject: [PATCH 02/14] Fix bad test --- tests/test_blog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_blog.py b/tests/test_blog.py index 175e5b5..4913045 100644 --- a/tests/test_blog.py +++ b/tests/test_blog.py @@ -20,7 +20,7 @@ def test_firehose(client, auth): assert b"Register" not in response.data auth.login() - response = client.get('/') + response = client.get('/firehose') assert b'Log Out' in response.data assert b'test title' in response.data assert b'by test on 2018-01-01' in response.data From 89622db6c939c50599aa9addc5e348b6a18f0001 Mon Sep 17 00:00:00 2001 From: PV Tejas Date: Wed, 11 Mar 2026 20:27:06 +0530 Subject: [PATCH 03/14] End individual post url in / --- flaskr/blog.py | 5 +++-- tests/test_blog.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/flaskr/blog.py b/flaskr/blog.py index 6bf9b55..8082d2f 100644 --- a/flaskr/blog.py +++ b/flaskr/blog.py @@ -86,9 +86,10 @@ def get_post(id, check_author=True): return post -@bp.route('/') +@bp.route('//') def individual_post(id): - post = get_post(id, False) + post = dict(get_post(id, False)) + post['body'] = markdown.markdown(post['body']) return render_template('blog/post.html', post=post) @bp.route('//update', methods=('GET', 'POST')) diff --git a/tests/test_blog.py b/tests/test_blog.py index 4913045..7249e1b 100644 --- a/tests/test_blog.py +++ b/tests/test_blog.py @@ -28,7 +28,7 @@ def test_firehose(client, auth): assert b'href="/1/update"' in response.data def test_individual_page(client, auth): - response = client.get('/1') + response = client.get('/1/') assert b'test title' in response.data assert b'by test on 2018-01-01' in response.data assert b'test\nbody' in response.data From 44a49e5aeefff4ad29134ecf0b34e24b6e04cb67 Mon Sep 17 00:00:00 2001 From: PV Tejas Date: Wed, 11 Mar 2026 20:27:32 +0530 Subject: [PATCH 04/14] Add link to individual post in index --- flaskr/templates/blog/index.html | 2 +- tests/test_blog.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/flaskr/templates/blog/index.html b/flaskr/templates/blog/index.html index 7f1e4e8..f50de7f 100644 --- a/flaskr/templates/blog/index.html +++ b/flaskr/templates/blog/index.html @@ -12,7 +12,7 @@
-

{{ post['title'] }}

+

{{ post['title'] }}

by {{ post['username'] }} on {{ post['created'].strftime('%Y-%m-%d') }}
{% if g.user['id'] == post['author_id'] %} diff --git a/tests/test_blog.py b/tests/test_blog.py index 7249e1b..548363a 100644 --- a/tests/test_blog.py +++ b/tests/test_blog.py @@ -13,6 +13,7 @@ def test_index(client, auth): assert b'by test on 2018-01-01' in response.data assert b'test\nbody' not in response.data assert b'href="/1/update"' in response.data + assert b'href="/1/"' in response.data def test_firehose(client, auth): response = client.get('/') From 4927b6f2da2e40165f090c203827359d3c5e96e8 Mon Sep 17 00:00:00 2001 From: pvtejas Date: Wed, 11 Mar 2026 20:45:56 +0530 Subject: [PATCH 05/14] Remove padding around title --- flaskr/static/style.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flaskr/static/style.css b/flaskr/static/style.css index 63f3f5b..89c7015 100644 --- a/flaskr/static/style.css +++ b/flaskr/static/style.css @@ -12,7 +12,7 @@ nav { background: lightgray; display: flex; align-items: center; padding: 0 0.5r nav h1 { flex: auto; margin: 0; } nav h1 a { text-decoration: none; padding: 0.25rem 0.5rem; } nav ul { display: flex; list-style: none; margin: 0; padding: 0; } -nav ul li a, nav ul li span, header .action { display: block; padding: 0.5rem; } +nav ul li a, nav ul li span, header .action { display: block;} .content { padding: 0 1rem 1rem; } .content > header { border-bottom: 1px solid lightgray; display: flex; align-items: flex-end; } .content > header h1 { flex: auto; margin: 1rem 0 0.25rem 0; } From 9609c162c129e34aad0aa5fc69da09abc5044cd4 Mon Sep 17 00:00:00 2001 From: pvtejas Date: Sun, 31 May 2026 15:54:35 +0530 Subject: [PATCH 06/14] Reinstate padding around navbar list items --- flaskr/static/style.css | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/flaskr/static/style.css b/flaskr/static/style.css index 89c7015..72ae337 100644 --- a/flaskr/static/style.css +++ b/flaskr/static/style.css @@ -12,7 +12,8 @@ nav { background: lightgray; display: flex; align-items: center; padding: 0 0.5r nav h1 { flex: auto; margin: 0; } nav h1 a { text-decoration: none; padding: 0.25rem 0.5rem; } nav ul { display: flex; list-style: none; margin: 0; padding: 0; } -nav ul li a, nav ul li span, header .action { display: block;} +nav ul li a, nav ul li span { display: block; padding: 0.5rem;} +header .action { display: block;} .content { padding: 0 1rem 1rem; } .content > header { border-bottom: 1px solid lightgray; display: flex; align-items: flex-end; } .content > header h1 { flex: auto; margin: 1rem 0 0.25rem 0; } From 30e8118aa881a2194ffdf98ab5895a80b9e69712 Mon Sep 17 00:00:00 2001 From: pvtejas Date: Sun, 25 Oct 2026 08:12:04 +0530 Subject: [PATCH 07/14] Wrap code in
 tags

---
 flaskr/static/style.css | 1 +
 1 file changed, 1 insertion(+)

diff --git a/flaskr/static/style.css b/flaskr/static/style.css
index 72ae337..dd1a44d 100644
--- a/flaskr/static/style.css
+++ b/flaskr/static/style.css
@@ -35,3 +35,4 @@ header .action { display: block;}
 .content textarea { min-height: 12em; resize: vertical; }
 input.danger { color: #cc2f2e; }
 input[type=submit] { align-self: start; min-width: 10em; }
+pre { white-space: pre-wrap; }
\ No newline at end of file

From 7502377840c2973709b37f1aa2ba17055ee90ec3 Mon Sep 17 00:00:00 2001
From: pvtejas 
Date: Fri, 30 Oct 2026 17:54:13 +0530
Subject: [PATCH 08/14] Add RSS feed generation

---
 flaskr/generate_rss.py | 69 ++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 69 insertions(+)
 create mode 100644 flaskr/generate_rss.py

diff --git a/flaskr/generate_rss.py b/flaskr/generate_rss.py
new file mode 100644
index 0000000..16306cc
--- /dev/null
+++ b/flaskr/generate_rss.py
@@ -0,0 +1,69 @@
+from datetime import datetime
+import sqlite3
+
+from bs4 import BeautifulSoup
+from markdown import markdown
+
+def soup_append_multiple_tags(soup: BeautifulSoup, children: dict) -> None:
+    for name, string in children.items():
+        soup.append(soup.new_tag(name))
+        soup.find_all(name)[-1].string = string
+
+def post_find_first_plain_line(post: str) -> str:
+    while post:
+        if post[0].isalnum():
+            break
+        end = post.find("\n")
+        post = post[end+1:]
+    end = post.find("\n")
+    return post[:end]
+
+def generate_feed(db):
+    soup = BeautifulSoup(features="xml")
+
+    soup.append(soup.new_tag("rss", version="2.0"))
+    rss = soup.rss
+    rss["xmlns:atom"] = "http://www.w3.org/2005/Atom"
+    rss["xmlns:content"] = "http://purl.org/rss/1.0/modules/content/"
+
+    rss.append(soup.new_tag("channel"))
+    channel = rss.channel
+
+    channel_children = {
+        "description": "Blog posts from allsparkinfinite's blog",
+        "link": "https://blog.allsparkinfinite.name",
+        "title": "blogsparkinfinite",
+        "docs": "https://rssboard.org/rss-specification",
+        "language": "en-IN",
+        "lastBuildDate": datetime.now().strftime("%a, %d %b %Y %T +0530"),
+        "ttl": "240",
+    }
+    soup_append_multiple_tags(channel, channel_children)
+
+    channel.append(channel.new_tag("atom:link"))
+    channel.find_all("atom:link")[-1]["href"] = "https://blog.allsparkinfinite.name/rss/"
+    channel.find_all("atom:link")[-1]["rel"] = "self"
+    channel.find_all("atom:link")[-1]["type"] = "application/rss+xml"
+
+    channel.append(channel.new_tag("atom:link"))
+    channel.find_all("atom:link")[-1]["href"] = "https://blog.allsparkinfinite.name/"
+    channel.find_all("atom:link")[-1]["rel"] = "alternate"
+    channel.find_all("atom:link")[-1]["type"] = "text/html"
+
+    db.execute("SELECT * FROM post").fetchall()
+    posts.reverse()
+
+    for post in posts:
+        channel.append(soup.new_tag("item"))
+        item = channel.find_all("item")[-1]
+        item_children = {
+            "description": post_find_first_plain_line(post[4]),
+            "guid": f"https://blog.allsparkinfinite.name/{post[0]}/",
+            "pubDate": datetime.strptime(post[2], "%Y-%m-%d %T").strftime("%a, %d %b %Y %T GMT"),
+            "title": post[3],
+            "content:encoded": markdown(post[4]),
+        }
+        soup_append_multiple_tags(item, item_children)
+
+    with open("flaskr/static/rss.xml", "w") as f:
+        f.write(soup.prettify())

From 19412540d56b236bae5c241b95efca8e76deafb9 Mon Sep 17 00:00:00 2001
From: pvtejas 
Date: Fri, 30 Oct 2026 17:55:57 +0530
Subject: [PATCH 09/14] Update flaskr/blog.py

---
 flaskr/blog.py | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/flaskr/blog.py b/flaskr/blog.py
index 8082d2f..1cbb71c 100644
--- a/flaskr/blog.py
+++ b/flaskr/blog.py
@@ -5,6 +5,7 @@ from werkzeug.exceptions import abort
 
 from flaskr.auth import login_required
 from flaskr.db import get_db
+from flaskr.generate_rss import generate_feed
 
 import markdown
 import datetime
@@ -66,6 +67,7 @@ def create():
                 (title, body, g.user['id'])
             )
             db.commit()
+            generate_feed(db)
             return redirect(url_for('blog.index'))
 
     return render_template('blog/create.html')

From 8347c578ffeb8b3e5b95dad5883c9dd6dce16975 Mon Sep 17 00:00:00 2001
From: pvtejas 
Date: Fri, 30 Oct 2026 17:56:44 +0530
Subject: [PATCH 10/14] Update RSS feed on post creation/update

---
 flaskr/blog.py | 1 +
 1 file changed, 1 insertion(+)

diff --git a/flaskr/blog.py b/flaskr/blog.py
index 1cbb71c..b5387b8 100644
--- a/flaskr/blog.py
+++ b/flaskr/blog.py
@@ -121,6 +121,7 @@ def update(id):
                 (title, body, created, id)
             )
             db.commit()
+            generate_feed(db)
             return redirect(url_for('blog.index'))
 
     return render_template('blog/update.html', post=post)

From 0843d180e0568a5bc6e1a742ea843c6e8e9c3457 Mon Sep 17 00:00:00 2001
From: pvtejas 
Date: Fri, 30 Oct 2026 18:06:36 +0530
Subject: [PATCH 11/14] Fix missing variable name

---
 flaskr/generate_rss.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/flaskr/generate_rss.py b/flaskr/generate_rss.py
index 16306cc..0338021 100644
--- a/flaskr/generate_rss.py
+++ b/flaskr/generate_rss.py
@@ -50,7 +50,7 @@ def generate_feed(db):
     channel.find_all("atom:link")[-1]["rel"] = "alternate"
     channel.find_all("atom:link")[-1]["type"] = "text/html"
 
-    db.execute("SELECT * FROM post").fetchall()
+    posts = db.execute("SELECT * FROM post").fetchall()
     posts.reverse()
 
     for post in posts:

From abc6d04a0a3521f0f7a6c90b333776042d8a858c Mon Sep 17 00:00:00 2001
From: pvtejas 
Date: Fri, 30 Oct 2026 18:08:08 +0530
Subject: [PATCH 12/14] Use datetime object as datetime object instead of
 string

---
 flaskr/generate_rss.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/flaskr/generate_rss.py b/flaskr/generate_rss.py
index 0338021..b06327b 100644
--- a/flaskr/generate_rss.py
+++ b/flaskr/generate_rss.py
@@ -59,7 +59,7 @@ def generate_feed(db):
         item_children = {
             "description": post_find_first_plain_line(post[4]),
             "guid": f"https://blog.allsparkinfinite.name/{post[0]}/",
-            "pubDate": datetime.strptime(post[2], "%Y-%m-%d %T").strftime("%a, %d %b %Y %T GMT"),
+            "pubDate": post[2].strftime("%a, %d %b %Y %T GMT"),
             "title": post[3],
             "content:encoded": markdown(post[4]),
         }

From 81d60c9df264c842cee60e3632ee0b1030cf4acf Mon Sep 17 00:00:00 2001
From: pvtejas 
Date: Fri, 30 Oct 2026 18:20:20 +0530
Subject: [PATCH 13/14] RSS from template

---
 flaskr/blog.py | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/flaskr/blog.py b/flaskr/blog.py
index b5387b8..4c77f7d 100644
--- a/flaskr/blog.py
+++ b/flaskr/blog.py
@@ -134,3 +134,7 @@ def delete(id):
     db.execute('DELETE FROM post WHERE id = ?',(id,))
     db.commit()
     return redirect(url_for('blog.index'))
+
+@bp.route('/rss/', methods=('GET',))
+def redirect_rss():
+    return render_template('rss.xml')

From 4e467c6cae17525f90657ea60fd976c5ace976ec Mon Sep 17 00:00:00 2001
From: pvtejas 
Date: Fri, 30 Oct 2026 18:24:21 +0530
Subject: [PATCH 14/14] Change RSS feed to redirect

---
 flaskr/blog.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/flaskr/blog.py b/flaskr/blog.py
index 4c77f7d..61b0715 100644
--- a/flaskr/blog.py
+++ b/flaskr/blog.py
@@ -137,4 +137,4 @@ def delete(id):
 
 @bp.route('/rss/', methods=('GET',))
 def redirect_rss():
-    return render_template('rss.xml')
+    return redirect(url_for('static', filename='rss.xml'))