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)
if not current_user.is_authenticated:
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))
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
@is_package_page
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)
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)
if thread.author == get_system_user():
editors = User.query.filter(User.rank >= UserRank.EDITOR).all()
addNotification(editors, current_user, NotificationType.EDITOR_MISC, msg,
approvers = User.query.filter(User.rank >= UserRank.APPROVER).all()
addNotification(approvers, current_user, NotificationType.EDITOR_MISC, msg,
thread.getViewURL(), thread.package)
db.session.commit()
@ -336,8 +336,8 @@ def new():
if package is not None:
addNotification(package.maintainers, current_user, NotificationType.NEW_THREAD, notif_msg, thread.getViewURL(), package)
editors = User.query.filter(User.rank >= UserRank.EDITOR).all()
addNotification(editors, current_user, NotificationType.EDITOR_MISC, notif_msg, thread.getViewURL(), package)
approvers = User.query.filter(User.rank >= UserRank.APPROVER).all()
addNotification(approvers, current_user, NotificationType.EDITOR_MISC, notif_msg, thread.getViewURL(), package)
db.session.commit()

@ -99,7 +99,7 @@ def topics():
page = get_int_or_abort(request.args.get("page"), 1)
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
query = query.paginate(page, num, True)
@ -160,7 +160,7 @@ def view_user(username=None):
if not user:
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)
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.
* **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.
* **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.
* **Admins** - Full access.
@ -18,6 +19,7 @@ title: Ranks and Permissions
<th colspan=2 class="NEW_MEMBER">New Member</th>
<th colspan=2 class="MEMBER">Member</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="MODERATOR">Moderator</th>
<th colspan=2 class="ADMIN">Admin</th>
@ -36,6 +38,8 @@ title: Ranks and Permissions
<th>N</th>
<th>Y</th>
<th>N</th>
<th>Y</th>
<th>N</th>
</tr>
</thead>
<tbody>
@ -47,6 +51,8 @@ title: Ranks and Permissions
<td></td>
<td></td> <!-- trusted member -->
<td></td>
<td></td> <!-- approver -->
<td></td>
<td></td> <!-- editor -->
<td></td>
<td></td> <!-- moderator -->
@ -62,6 +68,8 @@ title: Ranks and Permissions
<td></td>
<td></td> <!-- trusted member -->
<td></td>
<td></td> <!-- approver -->
<td></td>
<td></td> <!-- editor -->
<td></td>
<td></td> <!-- moderator -->
@ -77,6 +85,8 @@ title: Ranks and Permissions
<td></td>
<td></td> <!-- trusted member -->
<td></td>
<td></td> <!-- approver -->
<td></td>
<td></td> <!-- editor -->
<td></td>
<td></td> <!-- moderator -->
@ -92,6 +102,8 @@ title: Ranks and Permissions
<td></td>
<td></td> <!-- trusted member -->
<td></td>
<td></td> <!-- approver -->
<td></td>
<td></td> <!-- editor -->
<td></td>
<td></td> <!-- moderator -->
@ -107,8 +119,10 @@ title: Ranks and Permissions
<td></td>
<td></td> <!-- trusted member -->
<td></td>
<td></td> <!-- editor -->
<td></td> <!-- approver -->
<td></td>
<td></td> <!-- editor -->
<td></td>
<td></td> <!-- moderator -->
<td></td>
<td></td> <!-- admin -->
@ -122,6 +136,8 @@ title: Ranks and Permissions
<td></td>
<td></td> <!-- trusted member -->
<td></td>
<td></td> <!-- approver -->
<td></td>
<td></td> <!-- editor -->
<td></td>
<td></td> <!-- moderator -->
@ -137,6 +153,8 @@ title: Ranks and Permissions
<td></td>
<td></td> <!-- trusted member -->
<td></td>
<td></td> <!-- approver -->
<td></td>
<td></td> <!-- editor -->
<td></td>
<td></td> <!-- moderator -->
@ -152,6 +170,8 @@ title: Ranks and Permissions
<td></td>
<td></td> <!-- trusted member -->
<td></td>
<td></td> <!-- approver -->
<td></td>
<td></td> <!-- editor -->
<td></td>
<td></td> <!-- moderator -->
@ -167,6 +187,8 @@ title: Ranks and Permissions
<td></td>
<td></td> <!-- trusted member -->
<td></td>
<td></td> <!-- approver -->
<td></td>
<td></td> <!-- editor -->
<td></td>
<td></td> <!-- moderator -->
@ -182,6 +204,8 @@ title: Ranks and Permissions
<td></td>
<td></td> <!-- trusted member -->
<td></td>
<td></td> <!-- approver -->
<td></td>
<td></td> <!-- editor -->
<td></td>
<td></td> <!-- moderator -->
@ -197,6 +221,8 @@ title: Ranks and Permissions
<td></td>
<td></td> <!-- trusted member -->
<td></td>
<td></td> <!-- approver -->
<td></td>
<td></td> <!-- editor -->
<td></td>
<td></td> <!-- moderator -->
@ -212,6 +238,8 @@ title: Ranks and Permissions
<td></td>
<td></td> <!-- trusted member -->
<td></td>
<td></td> <!-- approver -->
<td></td>
<td></td> <!-- editor -->
<td></td>
<td></td> <!-- moderator -->
@ -227,6 +255,8 @@ title: Ranks and Permissions
<td></td>
<td></td> <!-- trusted member -->
<td></td>
<td></td> <!-- approver -->
<td></td>
<td></td> <!-- editor -->
<td></td>
<td></td> <!-- moderator -->
@ -242,6 +272,8 @@ title: Ranks and Permissions
<td></td>
<td></td> <!-- trusted member -->
<td></td>
<td></td> <!-- approver -->
<td></td>
<td></td> <!-- editor -->
<td></td>
<td></td> <!-- moderator -->
@ -257,10 +289,12 @@ title: Ranks and Permissions
<td></td>
<td></td> <!-- trusted member -->
<td></td>
<td></td> <!-- approver -->
<td></td>
<td></td> <!-- editor -->
<td></td>
<th><sup>3</sup></th> <!-- moderator -->
<th><sup>2</sup><sup>3</sup></th>
<th><sup>2</sup></th> <!-- moderator -->
<th><sup>1</sup><sup>2</sup></th>
<td></td> <!-- admin -->
<td></td>
</tr>
@ -268,5 +302,5 @@ title: Ranks and Permissions
</table>
2. Target user cannot be an admin.
3. Cannot set user to a higher rank than themselves.
1. Target user cannot be an admin.
2 Cannot set user to a higher rank than themselves.

@ -520,6 +520,7 @@ class Package(db.Model):
isOwner = user == self.author
isMaintainer = isOwner or user.rank.atLeast(UserRank.EDITOR) or user in self.maintainers
isApprover = user.rank.atLeast(UserRank.APPROVER)
if perm == Permission.CREATE_THREAD:
return user.rank.atLeast(UserRank.MEMBER)
@ -528,25 +529,30 @@ class Package(db.Model):
elif perm == Permission.MAKE_RELEASE or perm == Permission.ADD_SCREENSHOTS:
return isMaintainer
elif perm == Permission.EDIT_PACKAGE or \
perm == Permission.APPROVE_CHANGES or perm == Permission.APPROVE_RELEASE:
elif perm == Permission.EDIT_PACKAGE:
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
elif perm == Permission.CHANGE_NAME:
return not self.approved or user.rank.atLeast(UserRank.EDITOR)
# Editors can change authors and approve new packages
elif perm == Permission.APPROVE_NEW or perm == Permission.CHANGE_AUTHOR:
return user.rank.atLeast(UserRank.EDITOR)
return isApprover
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 \
perm == Permission.DELETE_PACKAGE:
elif perm == Permission.EDIT_MAINTAINERS or perm == Permission.DELETE_PACKAGE:
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:
return user.rank.atLeast(UserRank.MODERATOR)
@ -575,9 +581,10 @@ class Package(db.Model):
return False
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
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
elif perm == Permission.APPROVE_RELEASE:
return user.rank.atLeast(UserRank.EDITOR) or \
return user.rank.atLeast(UserRank.APPROVER) or \
(isMaintainer and user.rank.atLeast(
UserRank.MEMBER if self.approved else UserRank.NEW_MEMBER))
else:

@ -76,7 +76,7 @@ class Thread(db.Model):
if self.package:
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:
return canSee

@ -31,10 +31,11 @@ class UserRank(enum.Enum):
NEW_MEMBER = 2
MEMBER = 3
TRUSTED_MEMBER = 4
EDITOR = 5
BOT = 6
MODERATOR = 7
ADMIN = 8
APPROVER = 5
EDITOR = 6
BOT = 7
MODERATOR = 8
ADMIN = 9
def atLeast(self, min):
return self.value >= min.value
@ -59,7 +60,6 @@ class UserRank(enum.Enum):
class Permission(enum.Enum):
EDIT_PACKAGE = "EDIT_PACKAGE"
APPROVE_CHANGES = "APPROVE_CHANGES"
DELETE_PACKAGE = "DELETE_PACKAGE"
CHANGE_AUTHOR = "CHANGE_AUTHOR"
CHANGE_NAME = "CHANGE_NAME"
@ -96,13 +96,14 @@ class Permission(enum.Enum):
return False
if self == Permission.APPROVE_NEW or \
self == Permission.APPROVE_CHANGES or \
self == Permission.APPROVE_RELEASE or \
self == Permission.APPROVE_SCREENSHOT or \
self == Permission.EDIT_TAGS or \
self == Permission.CREATE_TAG or \
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)
else:
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):
return Permission.APPROVE_NEW.check(self) or \
Permission.APPROVE_RELEASE.check(self) or \
Permission.APPROVE_CHANGES.check(self)
Permission.APPROVE_RELEASE.check(self)
def isClaimed(self):
return self.rank.atLeast(UserRank.NEW_MEMBER)

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

@ -6,7 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<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/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="shortcut icon" href="/favicon-16.png" sizes="16x16">
<link rel="icon" href="/favicon-128.png" sizes="128x128">

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

@ -200,7 +200,7 @@
{% if review_thread.private %}
<p><i>
This thread is only visible to the package owner and users of
Editor rank or above.
Approver rank or above.
</i></p>
{% endif %}
@ -450,7 +450,7 @@
Report a problem with this listing
</a>
{% 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") }}">
See audit log
</a>

@ -37,7 +37,7 @@
{{ render_checkbox_field(form.private, class_="my-3") }}
<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.
</p>

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

@ -1,7 +1,7 @@
{% extends "base.html" %}
{% 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">
<div class="container">
<ul class="nav nav-tabs">
@ -41,7 +41,7 @@
{% endif %}
<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>
{% endif %}

@ -22,7 +22,7 @@ Topics to be Added
</div>
<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 %}
<a class="btn btn-secondary"
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>
{% elif user.rank == user.rank.EDITOR %}
<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 %}
<i class="fas fa-robot mr-2"></i>
{% 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