From c161f9e778197e3d9c02ea05f4f199c2d01b6662 Mon Sep 17 00:00:00 2001
From: ubq323 <ubq323@ubq323.website>
Date: Thu, 11 Aug 2022 01:51:00 +0100
Subject: create dedicated post module, move things around a lot, remove
 breadcrumbs since they aren't needed any more

---
 apioforum/orm.py                |  6 ++-
 apioforum/post.py               | 33 ++++++++++++++
 apioforum/templates/common.html | 27 -----------
 apioforum/thread.py             | 99 +++++++++++++++++++++++++----------------
 4 files changed, 97 insertions(+), 68 deletions(-)
 create mode 100644 apioforum/post.py

diff --git a/apioforum/orm.py b/apioforum/orm.py
index c87dbdd..97124c7 100644
--- a/apioforum/orm.py
+++ b/apioforum/orm.py
@@ -8,8 +8,9 @@ class DBObj:
         # DO NOT pass anything with sql special characters in as the table name
         super().__init_subclass__(**kwargs)
         cls.table_name = table
+
     @classmethod
-    def fetch(cls,*,id):
+    def fetch(cls, *, id):
         """fetch an object from the database, looked up by id."""
         db = get_db()
         # xxx this could be sped up by caching this query maybe instead of
@@ -19,8 +20,9 @@ class DBObj:
             return None
         item = cls.from_row(row)
         return item
+
     @classmethod
-    def from_row(cls,row):
+    def from_row(cls, row):
         # doesn't handle the ability to set fields yet
         # we will use something like properties instead
         # so this is somewhat bleh for now
diff --git a/apioforum/post.py b/apioforum/post.py
new file mode 100644
index 0000000..86753ad
--- /dev/null
+++ b/apioforum/post.py
@@ -0,0 +1,33 @@
+# wow, a dedicated module
+#
+
+from .orm import DBObj
+from .thread import POSTS_PER_PAGE
+from flask import url_for
+
+class Post(DBObj,table="posts"):
+    fields = ["id","content","thread","author","created","edited","updated","vote","deleted"]
+
+    def which_page(self):
+        """ return what page of a thread the given post is on
+
+        assumes post ids within a thread are monotonically increasing, which
+        is probably correct
+        """
+        db = get_db()
+        amt_before = db.execute("""
+            select count(*) as c from posts
+            where thread = ? and id < ?""",
+            (self.thread,self.id)).fetchone()['c']
+
+        page = 1+math.floor(amt_before/POSTS_PER_PAGE)
+        return page
+
+    def jump_url(self,*,external=False):
+        page = self.which_page()
+        return url_for(
+            "thread.view_thread",
+            thread_id = self.thread,
+            page=page,
+            _external=external)+"#post"+str(self.id)
+            
diff --git a/apioforum/templates/common.html b/apioforum/templates/common.html
index fae4b7c..5677073 100644
--- a/apioforum/templates/common.html
+++ b/apioforum/templates/common.html
@@ -87,33 +87,6 @@
 <a class="actionbutton" href="{{href}}">{{name}}</a>
 {%- endmacro %}
 
-{% macro breadcrumb() %}
-<nav aria-label="Breadcrumb">
-<ol class="breadcrumbs">
-	{{- caller() -}}
-</ol>
-</nav>
-{% endmacro %}
-
-{% macro forum_bc_entries(forum_id) -%}
-	{%- for f in forum_path(forum_id) -%}
-		<li><a href="{{url_for('forum.view_forum',forum_id=f.id)}}">{{ f.name }}</a></li>
-	{%- endfor %}
-{%- endmacro %}
-
-{% macro forum_breadcrumb(forum) %}
-	{%- call breadcrumb() %}
-		{{ forum_bc_entries(forum.id) }}
-	{% endcall -%}
-{% endmacro %}
-
-{% macro thread_breadcrumb(thread) %}
-	{%- call breadcrumb() %}
-		{{ forum_bc_entries(thread.forum) }}
-		<li>{{ thread.title }}</li>
-	{% endcall -%}
-{% endmacro %}
-
 {% macro vote_meter(poll) %}
 	{% set total_votes = poll.total_votes %}
 	{% set n = namespace() %}
diff --git a/apioforum/thread.py b/apioforum/thread.py
index a2ba6f5..f80786c 100644
--- a/apioforum/thread.py
+++ b/apioforum/thread.py
@@ -11,9 +11,34 @@ from .db import get_db
 from .roles import has_permission
 from . import webhooks
 from .forum import Forum
+from .orm import DBObj
 
-class Thread:
+POSTS_PER_PAGE = 28
+
+class Thread(DBObj,table="threads"):
     fields = ["id","title","creator","created","updated","forum","poll"]
+
+	# maybe this should be on Post instead?????
+	@staticmethod
+    def which_page(post):
+        """ return what page of a thread the given post is on
+
+        assumes post ids within a thread are monotonically increasing, which
+        is probably correct
+        """
+        db = get_db()
+        amt_before = db.execute("""
+            select count(*) as c from posts
+            where thread = ? and id < ?""",
+            (post.thread,post.id)).fetchone()['c']
+
+        page = 1+math.floor(amt_before/POSTS_PER_PAGE)
+        return page
+        
+       	
+        		   
+
+        
     
     
 
@@ -29,17 +54,16 @@ def thread_route(relative_path, pagination=False, **kwargs):
         @bp.route(path, **kwargs)
         @functools.wraps(f)
         def wrapper(thread_id, *args, **kwargs):
-            thread = Thread.fetch(id=forum_id)
+            thread = Thread.fetch(id=thread_id)
             if thread == None:
                 abort(404)
-            return f(forum, *args, **kwargs)
+            return f(thread, *args, **kwargs)
 
         if pagination:
             wrapper = bp.route(path+"/page/<int:page>", **kwargs)(wrapper)
 
     return decorator
 
-POSTS_PER_PAGE = 28
 
 def which_page(post_id,return_thread_id=False):
     # on which page lieth the post in question?
@@ -69,8 +93,6 @@ def post_jump(post_id,*,external=False):
     page,thread_id=which_page(post_id,True)
     return url_for("thread.view_thread",thread_id=thread_id,page=page,_external=external)+"#post_"+str(post_id)
 
-#@bp.route("/<int:thread_id>")
-#@bp.route("/<int:thread_id>/page/<int:page>")
 @thread_route("",pagination=True)
 def view_thread(thread,page=1):
     if page < 1:
@@ -91,14 +113,14 @@ def view_thread(thread,page=1):
             (page-1)*POSTS_PER_PAGE,
         )).fetchall()
 
-    num_posts = db.execute("SELECT count(*) as count FROM posts WHERE posts.thread = ?",(thread_id,)).fetchone()['count']
+    num_posts = db.execute("SELECT count(*) as count FROM posts WHERE posts.thread = ?",(thread.id,)).fetchone()['count']
     max_pageno = math.ceil(num_posts/POSTS_PER_PAGE)
     
     tags = db.execute(
     """SELECT tags.* FROM tags
         INNER JOIN thread_tags ON thread_tags.tag = tags.id
         WHERE thread_tags.thread = ?
-        ORDER BY tags.id""",(thread_id,)).fetchall()
+        ORDER BY tags.id""",(thread.id,)).fetchall()
     poll = None
     votes = None
     if thread.poll is not None:
@@ -158,12 +180,12 @@ def register_vote(thread,pollval):
         UPDATE votes
         SET current = 0
         WHERE poll = ? AND user = ?;
-    """,(thread['poll'],g.user))
+    """,(thread.poll,g.user))
 
     cur.execute("""
         INSERT INTO votes (user,poll,option_idx,time,current,is_retraction)
         VALUES (?,?,?,current_timestamp,1,?);
-        """,(g.user,thread['poll'],option_idx,is_retraction))
+        """,(g.user,thread.poll,option_idx,is_retraction))
     vote_id = cur.lastrowid
     return vote_id
 
@@ -182,16 +204,16 @@ def create_poll(thread_id):
         err = "that thread does not exist"
     elif g.user is None:
         err = "you need to be logged in to do that"
-    elif g.user != thread['creator'] and \
-            not has_permission(thread['forum'],g.user,"p_manage_threads"):
+    elif g.user != thread.creator and \
+            not has_permission(thread.forum,g.user,"p_manage_threads"):
         err = "you can only create polls on threads that you own"
-    elif thread['poll'] is not None:
+    elif thread.poll is not None:
         err = "a poll already exists for that thread"
     elif not len(polltitle) > 0:
         err = "poll title can't be empty"
     elif len(polloptions) < 2:
         err = "you must provide at least 2 options"
-    elif not has_permission(thread['forum'], g.user, "p_create_polls"):
+    elif not has_permission(thread.forum, g.user, "p_create_polls"):
         err = "you do not have permission to do that"
 
     if err is not None:
@@ -222,17 +244,17 @@ def delete_poll(thread_id):
         err = "that thread does not exist"
     elif g.user is None:
         err = "you need to be logged in to do that"
-    elif g.user != thread['creator'] and not \
-            has_permission(thread['forum'], g.user, "p_manage_threads"):
+    elif g.user != thread.creator and not \
+            has_permission(thread.forum, g.user, "p_manage_threads"):
         err = "you can only delete polls on threads that you own"
-    elif thread['poll'] is None:
+    elif thread.poll is None:
         err = "there is no poll to delete on this thread"
 
     if err is not None:
         flash(err)
         return fail
     else:
-        pollid = thread['poll']
+        pollid = thread.poll
         db.execute("UPDATE posts SET vote = NULL WHERE thread = ?",(thread_id,)) # this assumes only max one poll per thread 
         db.execute("DELETE FROM votes WHERE poll = ?",(pollid,))
         db.execute("DELETE FROM poll_options WHERE poll = ?",(pollid,))
@@ -253,16 +275,16 @@ def create_post(thread_id):
         flash("you cannot post an empty message")
     elif not thread:
         flash("that thread does not exist")
-    elif not has_permission(thread['forum'], g.user, "p_reply_threads"):
+    elif not has_permission(thread.forum, g.user, "p_reply_threads"):
         flash("you do not have permission to do this")
-    elif not has_permission(thread['forum'], g.user, "p_view_threads"):
+    elif not has_permission(thread.forum, g.user, "p_view_threads"):
         flash("you do not have permission to do this")
-    elif not has_permission(thread['forum'], g.user, "p_vote") \
+    elif not has_permission(thread.forum, g.user, "p_vote") \
             and 'poll' in request.form:
         flash("you do not have permission to do this")
     else:
         vote_id = None
-        if thread['poll'] is not None:
+        if thread.poll is not None:
             pollval = request.form.get('poll')
             try:
                 vote_id = register_vote(thread,pollval)
@@ -282,7 +304,7 @@ def create_post(thread_id):
         )
         db.commit()
         post = db.execute("select * from posts where id = ?",(post_id,)).fetchone()
-        webhooks.do_webhooks_post(thread['forum'],post)
+        webhooks.do_webhooks_post(thread.forum,post)
         flash("post posted postfully")
         return redirect(post_jump(post_id))
     return redirect(url_for('thread.view_thread',thread_id=thread_id))
@@ -295,7 +317,7 @@ def delete_post(post_id):
     if post is None:
         flash("that post doesn't exist")
         return redirect("/")
-    if post['author'] != g.user and not has_permission(thread['forum'], g.user, "p_delete_posts"):
+    if post['author'] != g.user and not has_permission(thread.forum, g.user, "p_delete_posts"):
         flash("you do not have permission to do that")
         return redirect(url_for("thread.view_thread",thread_id=post["thread"]))
     if request.method == "POST":
@@ -317,7 +339,7 @@ def delete_thread(thread_id):
     if thread is None:
         flash("that thread doesn't exist")
         return redirect("/")
-    if not has_permission(thread['forum'], g.user, "p_delete_posts"):
+    if not has_permission(thread.forum, g.user, "p_delete_posts"):
         flash("you do not have permission to do that")
         return redirect(url_for("thread.view_thread",thread_id=thread_id))
     if request.method == "POST":
@@ -325,7 +347,7 @@ def delete_thread(thread_id):
         db.execute("DELETE FROM threads WHERE id = ?",(thread_id,))
         db.commit()
         flash("thread deleted deletedly")
-        return redirect(url_for("forum.view_forum",forum_id=thread['forum']))
+        return redirect(url_for("forum.view_forum",forum_id=thread.forum))
     else:
         count = db.execute("SELECT num_replies FROM number_of_posts WHERE thread = ?",
                 (thread_id,)).fetchone()[0]
@@ -380,33 +402,32 @@ def view_post(post_id):
     
     
             
-@bp.route("/<int:thread_id>/config",methods=["GET","POST"])
-def config_thread(thread_id):
+@thread_route("config",methods=["GET","POST"])
+def config_thread(thread):
     db = get_db()
-    thread = db.execute("select * from threads where id = ?",(thread_id,)).fetchone()
-    thread_tags = [r['tag'] for r in db.execute("select tag from thread_tags where thread = ?",(thread_id,)).fetchall()]
-    forum = Forum.fetch(id=thread['forum'])
+    thread_tags = [r['tag'] for r in db.execute("select tag from thread_tags where thread = ?",(thread.id,)).fetchall()]
+    forum = Forum.fetch(id=thread.forum)
     avail_tags = forum.avail_tags()
     err = None
     if g.user is None:
         err = "you need to be logged in to do that"
-    elif not has_permission(thread['forum'], g.user, "p_view_threads"):
+    elif not has_permission(thread.forum, g.user, "p_view_threads"):
         err = "you do not have permission to do that"
-    elif g.user != thread['creator'] and not has_permission(thread['forum'], g.user, "p_manage_threads"):
+    elif g.user != thread.creator and not has_permission(thread.forum, g.user, "p_manage_threads"):
         err = "you can only configure threads that you own"
 
     if err is not None:
         flash(err)
-        return redirect(url_for("thread.view_thread",thread_id=thread_id))
+        return redirect(url_for("thread.view_thread",thread_id=thread.id))
 
     if request.method == "POST":
         err = []
-        if request.form['title'] != thread['title']:
+        if request.form['title'] != thread.title:
             title = request.form['title']
             if len(title.strip()) == 0:
                 err.append("title can't be empty")
             else:
-                db.execute("update threads set title = ? where id = ?;",(title,thread_id))
+                db.execute("update threads set title = ? where id = ?;",(title,thread.id))
                 flash("title updated successfully")
                 db.commit()
         changed = False
@@ -415,10 +436,10 @@ def config_thread(thread_id):
             current = tagid in thread_tags
             wanted  = f'tag_{tagid}' in request.form
             if wanted and not current:
-                db.execute("insert into thread_tags (thread, tag) values (?,?)",(thread_id,tagid))
+                db.execute("insert into thread_tags (thread, tag) values (?,?)",(thread.id,tagid))
                 changed = True
             elif current and not wanted:
-                db.execute("delete from thread_tags where thread = ? and tag = ?",(thread_id,tagid))
+                db.execute("delete from thread_tags where thread = ? and tag = ?",(thread.id,tagid))
                 changed = True
         if changed:
             db.commit()
@@ -428,7 +449,7 @@ def config_thread(thread_id):
             for e in err:
                 flash(e)
         else:
-            return redirect(url_for("thread.view_thread",thread_id=thread_id))
+            return redirect(url_for("thread.view_thread",thread_id=thread.id))
             
 
     return render_template("config_thread.html", thread=thread,thread_tags=thread_tags,avail_tags=avail_tags)
-- 
cgit v1.2.3