Add Approver rank

This commit is contained in:
rubenwardy 2021-08-16 18:57:05 +01:00
parent 59a5cf2df5
commit e5cc140d42
17 changed files with 117 additions and 44 deletions

@ -176,7 +176,7 @@ def view(package):
threads = Thread.query.filter_by(package_id=package.id, review_id=None) threads = Thread.query.filter_by(package_id=package.id, review_id=None)
if not current_user.is_authenticated: if not current_user.is_authenticated:
threads = threads.filter_by(private=False) threads = threads.filter_by(private=False)
elif not current_user.rank.atLeast(UserRank.EDITOR) and not current_user == package.author: elif not current_user.rank.atLeast(UserRank.APPROVER) and not current_user == package.author:
threads = threads.filter(or_(Thread.private == False, Thread.author == current_user)) threads = threads.filter(or_(Thread.private == False, Thread.author == current_user))
has_review = current_user.is_authenticated and PackageReview.query.filter_by(package=package, author=current_user).count() > 0 has_review = current_user.is_authenticated and PackageReview.query.filter_by(package=package, author=current_user).count() > 0
@ -519,7 +519,8 @@ def remove_self_maintainers(package):
@login_required @login_required
@is_package_page @is_package_page
def audit(package): def audit(package):
if not package.checkPerm(current_user, Permission.EDIT_PACKAGE): if not (package.checkPerm(current_user, Permission.EDIT_PACKAGE) or
package.checkPerm(current_user, Permission.APPROVE_NEW)):
abort(403) abort(403)
page = get_int_or_abort(request.args.get("page"), 1) page = get_int_or_abort(request.args.get("page"), 1)

@ -240,8 +240,8 @@ def view(id):
addNotification(thread.watchers, current_user, NotificationType.THREAD_REPLY, msg, thread.getViewURL(), thread.package) addNotification(thread.watchers, current_user, NotificationType.THREAD_REPLY, msg, thread.getViewURL(), thread.package)
if thread.author == get_system_user(): if thread.author == get_system_user():
editors = User.query.filter(User.rank >= UserRank.EDITOR).all() approvers = User.query.filter(User.rank >= UserRank.APPROVER).all()
addNotification(editors, current_user, NotificationType.EDITOR_MISC, msg, addNotification(approvers, current_user, NotificationType.EDITOR_MISC, msg,
thread.getViewURL(), thread.package) thread.getViewURL(), thread.package)
db.session.commit() db.session.commit()
@ -336,8 +336,8 @@ def new():
if package is not None: if package is not None:
addNotification(package.maintainers, current_user, NotificationType.NEW_THREAD, notif_msg, thread.getViewURL(), package) addNotification(package.maintainers, current_user, NotificationType.NEW_THREAD, notif_msg, thread.getViewURL(), package)
editors = User.query.filter(User.rank >= UserRank.EDITOR).all() approvers = User.query.filter(User.rank >= UserRank.APPROVER).all()
addNotification(editors, current_user, NotificationType.EDITOR_MISC, notif_msg, thread.getViewURL(), package) addNotification(approvers, current_user, NotificationType.EDITOR_MISC, notif_msg, thread.getViewURL(), package)
db.session.commit() db.session.commit()

@ -99,7 +99,7 @@ def topics():
page = get_int_or_abort(request.args.get("page"), 1) page = get_int_or_abort(request.args.get("page"), 1)
num = get_int_or_abort(request.args.get("n"), 100) num = get_int_or_abort(request.args.get("n"), 100)
if num > 100 and not current_user.rank.atLeast(UserRank.EDITOR): if num > 100 and not current_user.rank.atLeast(UserRank.APPROVER):
num = 100 num = 100
query = query.paginate(page, num, True) query = query.paginate(page, num, True)
@ -160,7 +160,7 @@ def view_user(username=None):
if not user: if not user:
abort(404) abort(404)
if current_user != user and not current_user.rank.atLeast(UserRank.EDITOR): if current_user != user and not current_user.rank.atLeast(UserRank.APPROVER):
abort(403) abort(403)
unapproved_packages = user.packages \ unapproved_packages = user.packages \

@ -5,7 +5,8 @@ title: Ranks and Permissions
* **New Members** - mostly untrusted, cannot change package meta data or publish releases without approval. * **New Members** - mostly untrusted, cannot change package meta data or publish releases without approval.
* **Members** - Trusted to change the meta data of their own packages', but cannot approve their own packages. * **Members** - Trusted to change the meta data of their own packages', but cannot approve their own packages.
* **Trusted Members** - Same as above, but can approve their own releases. * **Trusted Members** - Same as above, but can approve their own releases.
* **Editors** - Trusted to edit any package or release, and also responsible for approving new packages. * **Approvers** - Responsible for approving new packages, screenshots, and releases.
* **Editors** - Same as above, and can edit any package or release.
* **Moderators** - Same as above, but can manage users. * **Moderators** - Same as above, but can manage users.
* **Admins** - Full access. * **Admins** - Full access.
@ -18,6 +19,7 @@ title: Ranks and Permissions
<th colspan=2 class="NEW_MEMBER">New Member</th> <th colspan=2 class="NEW_MEMBER">New Member</th>
<th colspan=2 class="MEMBER">Member</th> <th colspan=2 class="MEMBER">Member</th>
<th colspan=2 class="TRUSTED_MEMBER">Trusted</th> <th colspan=2 class="TRUSTED_MEMBER">Trusted</th>
<th colspan=2 class="APPROVER">Approver</th>
<th colspan=2 class="EDITOR">Editor</th> <th colspan=2 class="EDITOR">Editor</th>
<th colspan=2 class="MODERATOR">Moderator</th> <th colspan=2 class="MODERATOR">Moderator</th>
<th colspan=2 class="ADMIN">Admin</th> <th colspan=2 class="ADMIN">Admin</th>
@ -36,6 +38,8 @@ title: Ranks and Permissions
<th>N</th> <th>N</th>
<th>Y</th> <th>Y</th>
<th>N</th> <th>N</th>
<th>Y</th>
<th>N</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -47,6 +51,8 @@ title: Ranks and Permissions
<td></td> <td></td>
<td></td> <!-- trusted member --> <td></td> <!-- trusted member -->
<td></td> <td></td>
<td></td> <!-- approver -->
<td></td>
<td></td> <!-- editor --> <td></td> <!-- editor -->
<td></td> <td></td>
<td></td> <!-- moderator --> <td></td> <!-- moderator -->
@ -62,6 +68,8 @@ title: Ranks and Permissions
<td></td> <td></td>
<td></td> <!-- trusted member --> <td></td> <!-- trusted member -->
<td></td> <td></td>
<td></td> <!-- approver -->
<td></td>
<td></td> <!-- editor --> <td></td> <!-- editor -->
<td></td> <td></td>
<td></td> <!-- moderator --> <td></td> <!-- moderator -->
@ -77,6 +85,8 @@ title: Ranks and Permissions
<td></td> <td></td>
<td></td> <!-- trusted member --> <td></td> <!-- trusted member -->
<td></td> <td></td>
<td></td> <!-- approver -->
<td></td>
<td></td> <!-- editor --> <td></td> <!-- editor -->
<td></td> <td></td>
<td></td> <!-- moderator --> <td></td> <!-- moderator -->
@ -92,6 +102,8 @@ title: Ranks and Permissions
<td></td> <td></td>
<td></td> <!-- trusted member --> <td></td> <!-- trusted member -->
<td></td> <td></td>
<td></td> <!-- approver -->
<td></td>
<td></td> <!-- editor --> <td></td> <!-- editor -->
<td></td> <td></td>
<td></td> <!-- moderator --> <td></td> <!-- moderator -->
@ -107,8 +119,10 @@ title: Ranks and Permissions
<td></td> <td></td>
<td></td> <!-- trusted member --> <td></td> <!-- trusted member -->
<td></td> <td></td>
<td></td> <!-- editor --> <td></td> <!-- approver -->
<td></td> <td></td>
<td></td> <!-- editor -->
<td></td>
<td></td> <!-- moderator --> <td></td> <!-- moderator -->
<td></td> <td></td>
<td></td> <!-- admin --> <td></td> <!-- admin -->
@ -122,6 +136,8 @@ title: Ranks and Permissions
<td></td> <td></td>
<td></td> <!-- trusted member --> <td></td> <!-- trusted member -->
<td></td> <td></td>
<td></td> <!-- approver -->
<td></td>
<td></td> <!-- editor --> <td></td> <!-- editor -->
<td></td> <td></td>
<td></td> <!-- moderator --> <td></td> <!-- moderator -->
@ -137,6 +153,8 @@ title: Ranks and Permissions
<td></td> <td></td>
<td></td> <!-- trusted member --> <td></td> <!-- trusted member -->
<td></td> <td></td>
<td></td> <!-- approver -->
<td></td>
<td></td> <!-- editor --> <td></td> <!-- editor -->
<td></td> <td></td>
<td></td> <!-- moderator --> <td></td> <!-- moderator -->
@ -152,6 +170,8 @@ title: Ranks and Permissions
<td></td> <td></td>
<td></td> <!-- trusted member --> <td></td> <!-- trusted member -->
<td></td> <td></td>
<td></td> <!-- approver -->
<td></td>
<td></td> <!-- editor --> <td></td> <!-- editor -->
<td></td> <td></td>
<td></td> <!-- moderator --> <td></td> <!-- moderator -->
@ -167,6 +187,8 @@ title: Ranks and Permissions
<td></td> <td></td>
<td></td> <!-- trusted member --> <td></td> <!-- trusted member -->
<td></td> <td></td>
<td></td> <!-- approver -->
<td></td>
<td></td> <!-- editor --> <td></td> <!-- editor -->
<td></td> <td></td>
<td></td> <!-- moderator --> <td></td> <!-- moderator -->
@ -182,6 +204,8 @@ title: Ranks and Permissions
<td></td> <td></td>
<td></td> <!-- trusted member --> <td></td> <!-- trusted member -->
<td></td> <td></td>
<td></td> <!-- approver -->
<td></td>
<td></td> <!-- editor --> <td></td> <!-- editor -->
<td></td> <td></td>
<td></td> <!-- moderator --> <td></td> <!-- moderator -->
@ -197,6 +221,8 @@ title: Ranks and Permissions
<td></td> <td></td>
<td></td> <!-- trusted member --> <td></td> <!-- trusted member -->
<td></td> <td></td>
<td></td> <!-- approver -->
<td></td>
<td></td> <!-- editor --> <td></td> <!-- editor -->
<td></td> <td></td>
<td></td> <!-- moderator --> <td></td> <!-- moderator -->
@ -212,6 +238,8 @@ title: Ranks and Permissions
<td></td> <td></td>
<td></td> <!-- trusted member --> <td></td> <!-- trusted member -->
<td></td> <td></td>
<td></td> <!-- approver -->
<td></td>
<td></td> <!-- editor --> <td></td> <!-- editor -->
<td></td> <td></td>
<td></td> <!-- moderator --> <td></td> <!-- moderator -->
@ -227,6 +255,8 @@ title: Ranks and Permissions
<td></td> <td></td>
<td></td> <!-- trusted member --> <td></td> <!-- trusted member -->
<td></td> <td></td>
<td></td> <!-- approver -->
<td></td>
<td></td> <!-- editor --> <td></td> <!-- editor -->
<td></td> <td></td>
<td></td> <!-- moderator --> <td></td> <!-- moderator -->
@ -242,6 +272,8 @@ title: Ranks and Permissions
<td></td> <td></td>
<td></td> <!-- trusted member --> <td></td> <!-- trusted member -->
<td></td> <td></td>
<td></td> <!-- approver -->
<td></td>
<td></td> <!-- editor --> <td></td> <!-- editor -->
<td></td> <td></td>
<td></td> <!-- moderator --> <td></td> <!-- moderator -->
@ -257,10 +289,12 @@ title: Ranks and Permissions
<td></td> <td></td>
<td></td> <!-- trusted member --> <td></td> <!-- trusted member -->
<td></td> <td></td>
<td></td> <!-- approver -->
<td></td>
<td></td> <!-- editor --> <td></td> <!-- editor -->
<td></td> <td></td>
<th><sup>3</sup></th> <!-- moderator --> <th><sup>2</sup></th> <!-- moderator -->
<th><sup>2</sup><sup>3</sup></th> <th><sup>1</sup><sup>2</sup></th>
<td></td> <!-- admin --> <td></td> <!-- admin -->
<td></td> <td></td>
</tr> </tr>
@ -268,5 +302,5 @@ title: Ranks and Permissions
</table> </table>
2. Target user cannot be an admin. 1. Target user cannot be an admin.
3. Cannot set user to a higher rank than themselves. 2 Cannot set user to a higher rank than themselves.

@ -520,6 +520,7 @@ class Package(db.Model):
isOwner = user == self.author isOwner = user == self.author
isMaintainer = isOwner or user.rank.atLeast(UserRank.EDITOR) or user in self.maintainers isMaintainer = isOwner or user.rank.atLeast(UserRank.EDITOR) or user in self.maintainers
isApprover = user.rank.atLeast(UserRank.APPROVER)
if perm == Permission.CREATE_THREAD: if perm == Permission.CREATE_THREAD:
return user.rank.atLeast(UserRank.MEMBER) return user.rank.atLeast(UserRank.MEMBER)
@ -528,25 +529,30 @@ class Package(db.Model):
elif perm == Permission.MAKE_RELEASE or perm == Permission.ADD_SCREENSHOTS: elif perm == Permission.MAKE_RELEASE or perm == Permission.ADD_SCREENSHOTS:
return isMaintainer return isMaintainer
elif perm == Permission.EDIT_PACKAGE or \ elif perm == Permission.EDIT_PACKAGE:
perm == Permission.APPROVE_CHANGES or perm == Permission.APPROVE_RELEASE:
return isMaintainer and user.rank.atLeast(UserRank.MEMBER if self.approved else UserRank.NEW_MEMBER) return isMaintainer and user.rank.atLeast(UserRank.MEMBER if self.approved else UserRank.NEW_MEMBER)
elif perm == Permission.APPROVE_RELEASE:
return (isMaintainer or isApprover) and user.rank.atLeast(UserRank.MEMBER if self.approved else UserRank.NEW_MEMBER)
# Anyone can change the package name when not approved, but only editors when approved # Anyone can change the package name when not approved, but only editors when approved
elif perm == Permission.CHANGE_NAME: elif perm == Permission.CHANGE_NAME:
return not self.approved or user.rank.atLeast(UserRank.EDITOR) return not self.approved or user.rank.atLeast(UserRank.EDITOR)
# Editors can change authors and approve new packages # Editors can change authors and approve new packages
elif perm == Permission.APPROVE_NEW or perm == Permission.CHANGE_AUTHOR: elif perm == Permission.APPROVE_NEW or perm == Permission.CHANGE_AUTHOR:
return user.rank.atLeast(UserRank.EDITOR) return isApprover
elif perm == Permission.APPROVE_SCREENSHOT: elif perm == Permission.APPROVE_SCREENSHOT:
return isMaintainer and user.rank.atLeast(UserRank.TRUSTED_MEMBER if self.approved else UserRank.NEW_MEMBER) return (isMaintainer or isApprover) and \
user.rank.atLeast(UserRank.TRUSTED_MEMBER if self.approved else UserRank.NEW_MEMBER)
elif perm == Permission.EDIT_MAINTAINERS or perm == Permission.UNAPPROVE_PACKAGE or \ elif perm == Permission.EDIT_MAINTAINERS or perm == Permission.DELETE_PACKAGE:
perm == Permission.DELETE_PACKAGE:
return isOwner or user.rank.atLeast(UserRank.EDITOR) return isOwner or user.rank.atLeast(UserRank.EDITOR)
elif perm == Permission.UNAPPROVE_PACKAGE:
return isOwner or user.rank.atLeast(UserRank.APPROVER)
elif perm == Permission.CHANGE_RELEASE_URL: elif perm == Permission.CHANGE_RELEASE_URL:
return user.rank.atLeast(UserRank.MODERATOR) return user.rank.atLeast(UserRank.MODERATOR)
@ -575,9 +581,10 @@ class Package(db.Model):
return False return False
if state == PackageState.READY_FOR_REVIEW or state == PackageState.APPROVED: if state == PackageState.READY_FOR_REVIEW or state == PackageState.APPROVED:
requiredPerm = Permission.APPROVE_NEW if state == PackageState.APPROVED else Permission.EDIT_PACKAGE if state == PackageState.APPROVED and not self.checkPerm(user, Permission.APPROVE_NEW):
return False
if not self.checkPerm(user, requiredPerm): if not (self.checkPerm(user, Permission.APPROVE_NEW) or self.checkPerm(user, Permission.EDIT_PACKAGE)):
return False return False
if state == PackageState.APPROVED and ("Other" in self.license.name or "Other" in self.media_license.name): if state == PackageState.APPROVED and ("Other" in self.license.name or "Other" in self.media_license.name):
@ -881,7 +888,7 @@ class PackageRelease(db.Model):
return count > 0 return count > 0
elif perm == Permission.APPROVE_RELEASE: elif perm == Permission.APPROVE_RELEASE:
return user.rank.atLeast(UserRank.EDITOR) or \ return user.rank.atLeast(UserRank.APPROVER) or \
(isMaintainer and user.rank.atLeast( (isMaintainer and user.rank.atLeast(
UserRank.MEMBER if self.approved else UserRank.NEW_MEMBER)) UserRank.MEMBER if self.approved else UserRank.NEW_MEMBER))
else: else:

@ -76,7 +76,7 @@ class Thread(db.Model):
if self.package: if self.package:
isMaintainer = isMaintainer or user in self.package.maintainers isMaintainer = isMaintainer or user in self.package.maintainers
canSee = not self.private or isMaintainer or user.rank.atLeast(UserRank.EDITOR) canSee = not self.private or isMaintainer or user.rank.atLeast(UserRank.APPROVER)
if perm == Permission.SEE_THREAD: if perm == Permission.SEE_THREAD:
return canSee return canSee

@ -31,10 +31,11 @@ class UserRank(enum.Enum):
NEW_MEMBER = 2 NEW_MEMBER = 2
MEMBER = 3 MEMBER = 3
TRUSTED_MEMBER = 4 TRUSTED_MEMBER = 4
EDITOR = 5 APPROVER = 5
BOT = 6 EDITOR = 6
MODERATOR = 7 BOT = 7
ADMIN = 8 MODERATOR = 8
ADMIN = 9
def atLeast(self, min): def atLeast(self, min):
return self.value >= min.value return self.value >= min.value
@ -59,7 +60,6 @@ class UserRank(enum.Enum):
class Permission(enum.Enum): class Permission(enum.Enum):
EDIT_PACKAGE = "EDIT_PACKAGE" EDIT_PACKAGE = "EDIT_PACKAGE"
APPROVE_CHANGES = "APPROVE_CHANGES"
DELETE_PACKAGE = "DELETE_PACKAGE" DELETE_PACKAGE = "DELETE_PACKAGE"
CHANGE_AUTHOR = "CHANGE_AUTHOR" CHANGE_AUTHOR = "CHANGE_AUTHOR"
CHANGE_NAME = "CHANGE_NAME" CHANGE_NAME = "CHANGE_NAME"
@ -96,13 +96,14 @@ class Permission(enum.Enum):
return False return False
if self == Permission.APPROVE_NEW or \ if self == Permission.APPROVE_NEW or \
self == Permission.APPROVE_CHANGES or \
self == Permission.APPROVE_RELEASE or \ self == Permission.APPROVE_RELEASE or \
self == Permission.APPROVE_SCREENSHOT or \ self == Permission.APPROVE_SCREENSHOT or \
self == Permission.EDIT_TAGS or \
self == Permission.CREATE_TAG or \
self == Permission.SEE_THREAD: self == Permission.SEE_THREAD:
return user.rank.atLeast(UserRank.APPROVER)
elif self == Permission.EDIT_TAGS or self == Permission.CREATE_TAG:
return user.rank.atLeast(UserRank.EDITOR) return user.rank.atLeast(UserRank.EDITOR)
else: else:
raise Exception("Non-global permission checked globally. Use Package.checkPerm or User.checkPerm instead.") raise Exception("Non-global permission checked globally. Use Package.checkPerm or User.checkPerm instead.")
@ -186,8 +187,7 @@ class User(db.Model, UserMixin):
def canAccessTodoList(self): def canAccessTodoList(self):
return Permission.APPROVE_NEW.check(self) or \ return Permission.APPROVE_NEW.check(self) or \
Permission.APPROVE_RELEASE.check(self) or \ Permission.APPROVE_RELEASE.check(self)
Permission.APPROVE_CHANGES.check(self)
def isClaimed(self): def isClaimed(self):
return self.rank.atLeast(UserRank.NEW_MEMBER) return self.rank.atLeast(UserRank.NEW_MEMBER)

@ -70,7 +70,7 @@
} }
.NOT_JOINED a, .NOT_JOINED { .NOT_JOINED a, .NOT_JOINED {
color: #7ac !important; color: #aaa !important;
} }
.ADMIN a, .ADMIN{ .ADMIN a, .ADMIN{
@ -81,6 +81,10 @@
color: #e90 !important; color: #e90 !important;
} }
.APPROVER a, .APPROVER {
color: #69f !important;
}
.EDITOR a, .EDITOR { .EDITOR a, .EDITOR {
color: #b6f !important; color: #b6f !important;
} }

@ -6,7 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>{% block title %}title{% endblock %} - {{ config.USER_APP_NAME }}</title> <title>{% block title %}title{% endblock %} - {{ config.USER_APP_NAME }}</title>
<link rel="stylesheet" type="text/css" href="/static/libs/bootstrap.min.css"> <link rel="stylesheet" type="text/css" href="/static/libs/bootstrap.min.css">
<link rel="stylesheet" type="text/css" href="/static/custom.css?v=31"> <link rel="stylesheet" type="text/css" href="/static/custom.css?v=32">
<link rel="search" type="application/opensearchdescription+xml" href="/static/opensearch.xml" title="ContentDB" /> <link rel="search" type="application/opensearchdescription+xml" href="/static/opensearch.xml" title="ContentDB" />
<link rel="shortcut icon" href="/favicon-16.png" sizes="16x16"> <link rel="shortcut icon" href="/favicon-16.png" sizes="16x16">
<link rel="icon" href="/favicon-128.png" sizes="128x128"> <link rel="icon" href="/favicon-128.png" sizes="128x128">

@ -63,7 +63,7 @@ Notifications
</div> </div>
{% if editor_notifications %} {% if editor_notifications %}
<h2>Editor Notifications</h2> <h2>Editor/Approver Notifications</h2>
<div class="list-group mt-3"> <div class="list-group mt-3">
{% for n in editor_notifications %} {% for n in editor_notifications %}

@ -200,7 +200,7 @@
{% if review_thread.private %} {% if review_thread.private %}
<p><i> <p><i>
This thread is only visible to the package owner and users of This thread is only visible to the package owner and users of
Editor rank or above. Approver rank or above.
</i></p> </i></p>
{% endif %} {% endif %}
@ -450,7 +450,7 @@
Report a problem with this listing Report a problem with this listing
</a> </a>
{% endif %} {% endif %}
{% if package.checkPerm(current_user, "EDIT_PACKAGE") %} {% if package.checkPerm(current_user, "EDIT_PACKAGE") or package.checkPerm(current_user, "APPROVE_NEW") %}
<a class="float-right" href="{{ package.getURL("packages.audit") }}"> <a class="float-right" href="{{ package.getURL("packages.audit") }}">
See audit log See audit log
</a> </a>

@ -37,7 +37,7 @@
{{ render_checkbox_field(form.private, class_="my-3") }} {{ render_checkbox_field(form.private, class_="my-3") }}
<p> <p>
Only you, the package author, and users of Editor rank Only you, the package author, and users of Approver rank
and above can read private threads. and above can read private threads.
</p> </p>

@ -63,7 +63,7 @@
{% if thread.private %} {% if thread.private %}
<i> <i>
This thread is only visible to its creator, the package owner, and users of This thread is only visible to its creator, the package owner, and users of
Editor rank or above. Approver rank or above.
</i> </i>
{% endif %} {% endif %}

@ -1,7 +1,7 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block container %} {% block container %}
{% if current_user.rank.atLeast(current_user.rank.EDITOR) %} {% if current_user.rank.atLeast(current_user.rank.APPROVER) %}
<nav class="pt-4 tabs-container"> <nav class="pt-4 tabs-container">
<div class="container"> <div class="container">
<ul class="nav nav-tabs"> <ul class="nav nav-tabs">
@ -41,7 +41,7 @@
{% endif %} {% endif %}
<main class="container mt-5"> <main class="container mt-5">
{% if not current_user.rank.atLeast(current_user.rank.EDITOR) %} {% if not current_user.rank.atLeast(current_user.rank.APPROVER) %}
<h1 class="mb-5">{{ self.title() }}</h1> <h1 class="mb-5">{{ self.title() }}</h1>
{% endif %} {% endif %}

@ -22,7 +22,7 @@ Topics to be Added
</div> </div>
<div class="btn-group btn-group-sm"> <div class="btn-group btn-group-sm">
{% if current_user.rank.atLeast(current_user.rank.EDITOR) %} {% if current_user.rank.atLeast(current_user.rank.APPROVER) %}
{% if n >= 10000 %} {% if n >= 10000 %}
<a class="btn btn-secondary" <a class="btn btn-secondary"
href="{{ url_for('todo.topics', q=query, show_discarded=show_discarded, n=100, sort=sort_by) }}"> href="{{ url_for('todo.topics', q=query, show_discarded=show_discarded, n=100, sort=sort_by) }}">

@ -38,6 +38,8 @@
<i class="fas fa-user-shield mr-2"></i> <i class="fas fa-user-shield mr-2"></i>
{% elif user.rank == user.rank.EDITOR %} {% elif user.rank == user.rank.EDITOR %}
<i class="fas fa-user-edit mr-2"></i> <i class="fas fa-user-edit mr-2"></i>
{% elif user.rank == user.rank.APPROVER %}
<i class="fas fa-user-check mr-2"></i>
{% elif user.rank == user.rank.BOT %} {% elif user.rank == user.rank.BOT %}
<i class="fas fa-robot mr-2"></i> <i class="fas fa-robot mr-2"></i>
{% else %} {% else %}

@ -0,0 +1,25 @@
"""empty message
Revision ID: 1af840af0209
Revises: 725ff70ea316
Create Date: 2021-08-16 17:17:12.060257
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision = '1af840af0209'
down_revision = '725ff70ea316'
branch_labels = None
depends_on = None
def upgrade():
op.execute("COMMIT")
op.execute("ALTER TYPE userrank ADD VALUE 'APPROVER' BEFORE 'EDITOR'")
def downgrade():
pass