summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--apioforum/db.py3
-rw-r--r--apioforum/forum.py78
-rw-r--r--apioforum/roles.py9
-rw-r--r--apioforum/static/style.css2
-rw-r--r--apioforum/templates/common.html4
-rw-r--r--apioforum/templates/edit_forum.html27
-rw-r--r--apioforum/templates/role_assignment.html2
-rw-r--r--apioforum/templates/view_forum.html20
-rw-r--r--apioforum/templates/view_unlisted.html24
-rw-r--r--apioforum/user.py7
10 files changed, 161 insertions, 15 deletions
diff --git a/apioforum/db.py b/apioforum/db.py
index d501159..899c6b4 100644
--- a/apioforum/db.py
+++ b/apioforum/db.py
@@ -146,6 +146,9 @@ CREATE TABLE role_assignments (
""",
"""
ALTER TABLE posts ADD COLUMN deleted NOT NULL DEFAULT 0;
+""",
+"""
+ALTER TABLE forums ADD COLUMN unlisted NOT NULL DEFAULT 0;
"""
]
diff --git a/apioforum/forum.py b/apioforum/forum.py
index f86629d..410bee5 100644
--- a/apioforum/forum.py
+++ b/apioforum/forum.py
@@ -98,7 +98,7 @@ def view_forum(forum):
subforums_rows = db.execute("""
SELECT max(threads.updated) as updated, forums.* FROM forums
LEFT OUTER JOIN threads ON threads.forum=forums.id
- WHERE parent = ?
+ WHERE parent = ? AND unlisted = 0
GROUP BY forums.id
ORDER BY name ASC
""",(forum['id'],)).fetchall()
@@ -109,12 +109,19 @@ def view_forum(forum):
if a['updated'] is not None:
a['updated'] = datetime.datetime.fromisoformat(a['updated'])
subforums.append(a)
+
+ bureaucrats = db.execute("""
+ SELECT user FROM role_assignments
+ WHERE role = 'bureaucrat' AND forum = ?
+ """,(forum['id'],)).fetchall()
+ bureaucrats = [b[0] for b in bureaucrats]
return render_template("view_forum.html",
forum=forum,
subforums=subforums,
threads=threads,
thread_tags=thread_tags,
+ bureaucrats=bureaucrats
)
@forum_route("create_thread",methods=("GET","POST"))
@@ -232,15 +239,18 @@ def edit_user_role(forum, username):
return redirect(url_for('forum.edit_user_role',
username=username,forum_id=forum['id']))
if not is_bureaucrat(forum['id'],g.user) and role != "approved" and role != "":
+ # only bureaucrats can assign arbitrary roles
abort(403)
- existing = db.execute("SELECT * FROM role_assignments WHERE user = ?;",(username,)).fetchone()
+ existing = db.execute(
+ "SELECT * FROM role_assignments WHERE user = ? AND forum = ?;",
+ (username,forum['id'])).fetchone()
if existing:
- db.execute("DELETE FROM role_assignments WHERE user = ?;",(username,))
+ db.execute("DELETE FROM role_assignments WHERE user = ? AND forum = ?;",(username,forum['id']))
if role != "":
db.execute(
"INSERT INTO role_assignments (user,role,forum) VALUES (?,?,?);",
(username,role,forum['id']))
- db.commit()
+ db.commit()
flash("role assigned assignedly")
return redirect(url_for('forum.view_forum',forum_id=forum['id']))
else:
@@ -249,7 +259,8 @@ def edit_user_role(forum, username):
return render_template("role_assignment.html",
forum=forum,user=username,invalid_user=True)
r = db.execute(
- "SELECT role FROM role_assignments WHERE user = ?;",(username,)).fetchone()
+ "SELECT role FROM role_assignments WHERE user = ? AND forum = ?;",
+ (username,forum['id'])).fetchone()
if not r:
assigned_role = ""
else:
@@ -265,6 +276,63 @@ def edit_user_role(forum, username):
forum=forum,user=username,role=role,
assigned_role=assigned_role,forum_roles=roles)
+def forum_config_page(forum, create=False):
+ db = get_db()
+ if request.method == "POST":
+ name = request.form["name"]
+ desc = request.form["description"]
+ unlisted = "unlisted" in request.form
+ if len(name) > 100 or len(name.strip()) == 0:
+ flash("invalid name")
+ return redirect(url_for('forum.edit_forum',forum_id=forum['id']))
+ elif len(desc) > 6000:
+ flash("invalid description")
+ return redirect(url_for('forum.edit_forum',forum_id=forum['id']))
+ if not create:
+ db.execute("UPDATE forums SET name = ?, description = ?, unlisted = ? WHERE id = ?",
+ (name,desc,forum['id']))
+ fid = forum['id']
+ else:
+ cur = db.cursor()
+ cur.execute(
+ "INSERT INTO forums (name,description,parent,unlisted) VALUES (?,?,?,?)",
+ (name,desc,forum['id'],unlisted))
+ new = cur.lastrowid
+ # creator becomes bureaucrat of new forum
+ db.execute("INSERT INTO role_assignments (role,user,forum) VALUES (?,?,?)",
+ ("bureaucrat",g.user,new))
+ fid = new
+ db.commit()
+ return redirect(url_for('forum.view_forum',forum_id=fid))
+ else:
+ if create:
+ name = ""
+ desc = ""
+ else:
+ name = forum['name']
+ desc = forum['description']
+ cancel_link = url_for('forum.view_forum',forum_id=forum['id'])
+ return render_template("edit_forum.html",create=create,
+ name=name,description=desc,cancel_link=cancel_link)
+
+@forum_route("edit",methods=["GET","POST"])
+@requires_bureaucrat
+def edit_forum(forum):
+ return forum_config_page(forum)
+
+@forum_route("create",methods=["GET","POST"])
+@requires_permission("p_create_subforum")
+def create_forum(forum):
+ return forum_config_page(forum,create=True)
+
+@forum_route("unlisted")
+@requires_bureaucrat
+def view_unlisted(forum):
+ db = get_db()
+ unlisted = db.execute(
+ "SELECT * FROM forums WHERE unlisted = 1 AND parent = ?",(forum['id'],))
+ return render_template('view_unlisted.html',forum=forum,unlisted=unlisted)
+
@bp.route("/search")
def search():
db = get_db()
diff --git a/apioforum/roles.py b/apioforum/roles.py
index bda6704..ae47e31 100644
--- a/apioforum/roles.py
+++ b/apioforum/roles.py
@@ -40,11 +40,16 @@ def get_user_role(forum_id, user):
fid = forum_id
the = None
- while the == None and fid != None:
- the = db.execute("""
+ while fid != None:
+ r = db.execute("""
SELECT * FROM role_assignments
WHERE forum = ? AND user = ?;
""",(fid,user)).fetchone()
+ # the user's role is equal to the role assignnment of the closest
+ # ancestor unless the user's role is "bureaucrat" in any ancestor
+ # in which case, the users role is "bureaucrat"
+ if the == None or (r and r['role'] == "bureaucrat"):
+ the = r
fid = db.execute("""
SELECT * FROM forums WHERE id = ?
""",(fid,)).fetchone()['parent']
diff --git a/apioforum/static/style.css b/apioforum/static/style.css
index 931ac9a..2ed2e7a 100644
--- a/apioforum/static/style.css
+++ b/apioforum/static/style.css
@@ -188,7 +188,7 @@ nav#navbar .links { display: flex; }
}
.actionbutton { color:blue }
-.new-post-box {
+.new-post-box, .forum-desc-box {
height:20em;
resize:vertical;
width:100%;
diff --git a/apioforum/templates/common.html b/apioforum/templates/common.html
index 9e60e81..7144667 100644
--- a/apioforum/templates/common.html
+++ b/apioforum/templates/common.html
@@ -67,6 +67,10 @@
<span class="tag" style="color: {{the_tag.text_colour}}; background-color: {{the_tag.bg_colour}}">{{the_tag.name}}</span>
{%- endmacro %}
+{% macro ab(name,href) -%}
+<a class="actionbutton" href="{{href}}">{{name}}</a>
+{%- endmacro %}
+
{% macro breadcrumb() %}
<nav aria-label="Breadcrumb">
<ol class="breadcrumbs">
diff --git a/apioforum/templates/edit_forum.html b/apioforum/templates/edit_forum.html
new file mode 100644
index 0000000..c8027e7
--- /dev/null
+++ b/apioforum/templates/edit_forum.html
@@ -0,0 +1,27 @@
+{% extends 'base.html' %}
+{% block header %}
+<h1>{% block title %}{%if create %}create{% else %}edit{%endif%} forum{% endblock %}</h1>
+{% endblock %}
+
+{% block content %}
+<form method="POST">
+ <label for="name">forum name</label>
+ <input name="name" id="name" value="{{name}}" placeholder="apioforum" required maxlength="100"/>
+ <br>
+ <label for="description">forum description (markdown enabled)</label>
+ <textarea
+ name="description"
+ id="description"
+ class="forum-desc-box"
+ placeholder="this is a forum for discussing bees"
+ maxlength="6000"
+ required
+ >{{description}}</textarea>
+ <input type="checkbox" id="unlisted" name="unlisted"/>
+ <label for="unlisted">unlisted?</label>
+ <p>
+ <input type="submit" value="confirm">
+ <a href="{{cancel_link}}">cancel</a>
+ </p>
+</form>
+{% endblock %}
diff --git a/apioforum/templates/role_assignment.html b/apioforum/templates/role_assignment.html
index b212606..74dc3cd 100644
--- a/apioforum/templates/role_assignment.html
+++ b/apioforum/templates/role_assignment.html
@@ -29,7 +29,7 @@
{% if can_change %}
<label for="role">assigned role: </label>
<select name="role" id="role" autocomplete="off">
- <option value="">(no assigned role)</option>
+ <option value="" {% if not assigned_role %}selected{% endif %}>(no assigned role)</option>
{% for role in forum_roles %}
<option value="{{role}}"
{% if role == assigned_role %}selected{% endif %}>
diff --git a/apioforum/templates/view_forum.html b/apioforum/templates/view_forum.html
index a3563be..863f91c 100644
--- a/apioforum/templates/view_forum.html
+++ b/apioforum/templates/view_forum.html
@@ -1,5 +1,5 @@
{% extends 'base.html' %}
-{% from 'common.html' import ts, tag, disp_user, post_url, forum_breadcrumb %}
+{% from 'common.html' import ts, tag, disp_user, post_url, forum_breadcrumb, ab %}
{% block header %}
<h1>{% block title %}{{forum.name}}{% endblock %} <span class="thing-id">#{{forum.id}}</span></h1>
{% if forum.id != 1 %}
@@ -9,13 +9,25 @@
{%block content%}
{{forum.description|md|safe}}
+{% if bureaucrats|length > 0 %}
+ <p>
+ bureaucrats in this forum:
+ {% for b in bureaucrats %}
+ {{disp_user(b)}}
+ {% endfor %}
+ </p>
+{% endif %}
<p>
{% if is_bureaucrat(forum.id, g.user) %}
- <a class="actionbutton" href="{{url_for('forum.edit_roles',forum_id=forum.id)}}">role/permission settings</a>
- <a class="actionbutton" href="{{url_for('forum.view_user_role',forum_id=forum.id)}}">assign roles</a>
+ {{ab("forum settings",url_for('forum.edit_forum',forum_id=forum.id))}}
+ {{ab("role/permission settings",url_for('forum.edit_roles',forum_id=forum.id))}}
+ {{ab("assign roles",url_for('forum.view_user_role',forum_id=forum.id))}}
+ {% endif %}
+ {% if has_permission(forum.id, g.user, "p_create_subforum") %}
+ {{ab("create subforum",url_for('forum.create_forum',forum_id=forum.id))}}
{% endif %}
{% if not is_bureaucrat(forum.id, g.user) and has_permission(forum.id, g.user, "p_approve") %}
- <a class="actionbutton" href="{{url_for('forum.view_user_role',forum_id=forum.id)}}">approve users</a>
+ {{ab("approve users",url_for('forum.view_user_role',forum_id=forum.id))}}
{% endif %}
</p>
{% if subforums %}
diff --git a/apioforum/templates/view_unlisted.html b/apioforum/templates/view_unlisted.html
new file mode 100644
index 0000000..c0fd074
--- /dev/null
+++ b/apioforum/templates/view_unlisted.html
@@ -0,0 +1,24 @@
+{% extends 'base.html' %}
+{% from 'common.html' import forum_breadcrumb %}
+{% block header %}
+<h1>{% block title %}unlisted subforæ in '{{forum.name}}'{% endblock %}</h1>
+{% if forum.id != 1 %}
+ {{ forum_breadcrumb(forum) }}
+{% endif %}
+{% endblock %}
+
+{% block content %}
+<form method="POST">
+ {% if unlisted %}
+ <ul>
+ {% for f in unlisted %}
+ <li>
+ <a href="{{url_for('forum.view_forum',forum_id=f.id)}}">{{f.name}}</a>
+ </li>
+ {% endfor %}
+ </ul>
+ {% else %}
+ <p>there are no unlisted subforæ in '{{forum.name}}'</p>
+ {% endif %}
+</form>
+{% endblock %}
diff --git a/apioforum/user.py b/apioforum/user.py
index 9f4bc5b..bbdd060 100644
--- a/apioforum/user.py
+++ b/apioforum/user.py
@@ -16,8 +16,11 @@ def view_user(username):
user = db.execute("SELECT * FROM users WHERE username = ?;",(username,)).fetchone()
if user is None:
abort(404)
- posts = db.execute(
- "SELECT * FROM posts WHERE author = ? ORDER BY created DESC LIMIT 25;",(username,)).fetchall()
+ posts = db.execute("""
+ SELECT * FROM posts
+ WHERE author = ? AND deleted = 0
+ ORDER BY created DESC
+ LIMIT 25;""",(username,)).fetchall()
return render_template("view_user.html", user=user, posts=posts)
@bp.route("/<username>/edit", methods=["GET","POST"])