Implement subcomment trees, replies to subcomments

This commit is contained in:
David Hoppenbrouwers
2022-10-07 17:13:59 +02:00
parent b96db0b905
commit a6695b3e39
5 changed files with 140 additions and 28 deletions

View File

@@ -6,13 +6,13 @@ class DB:
pass pass
def get_subforums(self): def get_subforums(self):
return self._db().execute('select forum_id, name, description from subforums').fetchall() return self._db().execute('select forum_id, name, description from subforums')
def get_subforum(self, subforum): def get_subforum(self, subforum):
return self._db().execute('select name, description from subforums where forum_id = ?', (subforum,)).fetchone() return self._db().execute('select name, description from subforums where forum_id = ?', (subforum,)).fetchone()
def get_threads(self, subforum): def get_threads(self, subforum):
return self._db().execute('select thread_id, title from threads where forum_id = ?', (subforum,)).fetchall() return self._db().execute('select thread_id, title from threads where forum_id = ?', (subforum,))
def get_thread(self, thread): def get_thread(self, thread):
db = self._db() db = self._db()
@@ -29,7 +29,7 @@ class DB:
where thread_id = ? and author_id = user_id where thread_id = ? and author_id = user_id
''', ''',
(thread,) (thread,)
).fetchall() )
return title, text, author, author_id, comments return title, text, author, author_id, comments
def get_thread_title(self, thread_id): def get_thread_title(self, thread_id):
@@ -48,15 +48,30 @@ class DB:
where thread_id = ? where thread_id = ?
''', ''',
(thread,) (thread,)
).fetchall() )
def get_comment_tree(self, comment): def get_subcomments(self, comment_id):
db = self._db() db = self._db()
parent = db.execute('select text from comments where comment_id = ?', (comment,)).fetchall() thread_id, parent_id, title = db.execute('''
children = db.execute('select text from comments where parent_id = ?', (comment,)).fetchall() select threads.thread_id, parent_id, title
print(parent, children) from threads, comments
return str(parent) + str(children) where comment_id = ? and threads.thread_id = comments.thread_id
return parent ''',
(comment_id,)
).fetchone()
# Recursive CTE, see https://www.sqlite.org/lang_with.html
return thread_id, parent_id, title, db.execute('''
with recursive
descendant_of(id) as (
select comment_id from comments where comment_id = ?
union
select comment_id from descendant_of, comments where id = parent_id
)
select id, parent_id, name, text from descendant_of, comments, users
where id = comment_id and user_id = author_id
''',
(comment_id,)
)
def get_user_password(self, username): def get_user_password(self, username):
return self._db().execute(''' return self._db().execute('''
@@ -127,5 +142,37 @@ class DB:
) )
db.commit() db.commit()
def add_comment_to_thread(self, thread_id, author_id, text, time):
db = self._db()
c = db.cursor()
c.execute('''
insert into comments(thread_id, author_id, text, create_time, modify_time)
select ?, ?, ?, ?, ?
from threads
where thread_id = ?
''',
(thread_id, author_id, text, time, time, thread_id)
)
rowid = c.lastrowid
db.commit()
return rowid is not None
def add_comment_to_comment(self, parent_id, author_id, text, time):
db = self._db()
c = db.cursor()
print(c.lastrowid, parent_id)
c.execute('''
insert into comments(thread_id, parent_id, author_id, text, create_time, modify_time)
select thread_id, ?, ?, ?, ?, ?
from comments
where comment_id = ?
''',
(parent_id, author_id, text, time, time, parent_id)
)
print(c.lastrowid)
rowid = c.lastrowid
db.commit()
return rowid is not None
def _db(self): def _db(self):
return sqlite3.connect(self.conn) return sqlite3.connect(self.conn)

53
main.py
View File

@@ -43,8 +43,16 @@ def thread(thread_id):
@app.route('/comment/<int:comment_id>/') @app.route('/comment/<int:comment_id>/')
def comment(comment_id): def comment(comment_id):
#return str(db.get_comment_tree(comment_id)[0]) user_id = session.get('user_id')
return str(db.get_comment_tree(comment_id)) thread_id, parent_id, title, comments = db.get_subcomments(comment_id)
comments = create_comment_tree(comments)
return render_template(
'comments.html',
title = title,
comments = comments,
parent_id = parent_id,
thread_id = thread_id,
)
@app.route('/login/', methods = ['GET', 'POST']) @app.route('/login/', methods = ['GET', 'POST'])
def login(): def login():
@@ -129,21 +137,50 @@ def delete_thread(thread_id):
flash('Thread has been deleted', 'success') flash('Thread has been deleted', 'success')
return redirect(url_for('index')) return redirect(url_for('index'))
@app.route('/thread/<int:thread_id>/comment/', methods = ['POST'])
def add_comment(thread_id):
user_id = session.get('user_id')
if user_id is None:
return redirect(url_for('login'))
if db.add_comment_to_thread(thread_id, user_id, request.form['text'], time.time_ns()):
flash('Added comment', 'success')
else:
flash('Failed to add comment', 'error')
return redirect(url_for('thread', thread_id = thread_id))
@app.route('/comment/<int:comment_id>/comment/', methods = ['POST'])
def add_comment_parent(comment_id):
user_id = session.get('user_id')
if user_id is None:
return redirect(url_for('login'))
if db.add_comment_to_comment(comment_id, user_id, request.form['text'], time.time_ns()):
flash('Added comment', 'success')
else:
flash('Failed to add comment', 'error')
return redirect(url_for('comment', comment_id = comment_id))
class Comment: class Comment:
def __init__(self, author, text): def __init__(self, id, author, text):
self.id = id
self.author = author self.author = author
self.text = text self.text = text
self.children = [] self.children = []
def create_comment_tree(comments): def create_comment_tree(comments):
# Collect comments first, then build the tree in case we encounter a child before a parent
comment_map = {
comment_id: (Comment(comment_id, author, text), parent_id)
for comment_id, parent_id, author, text
in comments
}
root = [] root = []
comment_map = {} for comment, parent_id in comment_map.values():
for comment_id, parent_id, author, text in comments:
comment = Comment(author, text)
parent = comment_map.get(parent_id) parent = comment_map.get(parent_id)
if parent is not None: if parent is not None:
parent.children.append(comment) parent[0].children.append(comment)
else: else:
root.append(comment) root.append(comment)
comment_map[comment_id] = comment
return root return root

19
templates/comment.html Normal file
View File

@@ -0,0 +1,19 @@
{% macro render_comment(comment) %}
<div style="margin-left:20px">
<p><i>{{ comment.author }}</i></p>
<p>{{ comment.text }}</p>
<sup><a href="{{ url_for("comment", comment_id = comment.id) }}">reply</a></sup>
{% for c in comment.children %}
{{ render_comment(c) }}
{% endfor %}
</div>
{% endmacro %}
{% macro reply() %}
{% if 'user_id' in session %}
<form method="post" action="comment/">
<textarea name="text"></textarea>
<input type="submit" value="Post comment">
</form>
{% endif %}
{% endmacro %}

15
templates/comments.html Normal file
View File

@@ -0,0 +1,15 @@
{% extends 'base.html' %}
{% from 'comment.html' import render_comment, reply %}
{% block content %}
<sup><a href="{{ url_for('thread', thread_id = thread_id) }}">thread</a></sup>
{% if parent_id %}
<sup><a href="../{{ parent_id }}">parent</a></sup>
{% endif %}
{{ reply() }}
{% for c in comments %}
{{ render_comment(c) }}
{% endfor %}
{% endblock %}

View File

@@ -1,14 +1,5 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% from 'comment.html' import render_comment, reply %}
{% macro render_comment(comment) %}
<div style="margin-left:20px">
<p><i>{{ comment.author }}</i></p>
<p>{{ comment.text }}</p>
{% for c in comment.children %}
{{ render_comment(c) }}
{% endfor %}
</div>
{% endmacro %}
{% block content %} {% block content %}
{% if manage %} {% if manage %}
@@ -20,6 +11,9 @@
{% endif %} {% endif %}
<p>{{ author }} - rjgoire</p> <p>{{ author }} - rjgoire</p>
<p>{{ text }}</p> <p>{{ text }}</p>
{{ reply() }}
{% for c in comments %} {% for c in comments %}
{{ render_comment(c) }} {{ render_comment(c) }}
{% endfor %} {% endfor %}