return redirect(package.getDetailsURL())
else:
abort(400)
+
+
+
+class PackageMaintainersForm(FlaskForm):
+ maintainers_str = StringField("Maintainers (Comma-separated)", [Optional()])
+ submit = SubmitField("Save")
+
+
+@bp.route("/packages/<author>/<name>/edit-maintainers/", methods=["GET", "POST"])
+@login_required
+@is_package_page
+def edit_maintainers(package):
+ if not package.checkPerm(current_user, Permission.EDIT_MAINTAINERS):
+ flash("You do not have permission to edit maintainers", "danger")
+ return redirect(package.getDetailsURL())
+
+ form = PackageMaintainersForm(formdata=request.form)
+ if request.method == "GET":
+ form.maintainers_str.data = ", ".join([ x.username for x in package.maintainers ])
+
+ if request.method == "POST" and form.validate():
+ usernames = [x.strip() for x in form.maintainers_str.data.split(",")]
+ users = User.query.filter(func.lower(User.username).in_(usernames)).all()
+ package.maintainers.clear()
+ package.maintainers.extend(users)
+ package.maintainers.append(package.author)
+ db.session.commit()
+
+ return redirect(package.getDetailsURL())
+
+ users = User.query.filter(User.rank >= UserRank.NEW_MEMBER).order_by(db.asc(User.username)).all()
+
+ return render_template("packages/edit_maintainers.html", \
+ package=package, form=form, users=users)
## Breakdown
-<table class="fancyTable">
+<table class="table">
<thead>
<tr>
<th>Rank</th>
<th>✓</th>
</tr>
<tr>
- <td>Add/Delete Screenshot</td>
+ <td>Edit Maintainers</td>
<th>✓</th> <!-- new -->
<th></th>
<th>✓</th> <!-- member -->
<th>✓</th> <!-- trusted member -->
<th></th>
<th>✓</th> <!-- editor -->
- <th>✓</th>
- <th>✓</th> <!-- moderator -->
- <th>✓</th>
- <th>✓</th> <!-- admin -->
- <th>✓</th>
- </tr>
- <tr>
- <td>Approve Screenshot</td>
- <th></th> <!-- new -->
- <th></th>
- <th></th> <!-- member -->
<th></th>
- <th>✓</th> <!-- trusted member -->
- <th></th>
- <th>✓</th> <!-- editor -->
- <th>✓</th>
<th>✓</th> <!-- moderator -->
<th>✓</th>
<th>✓</th> <!-- admin -->
<th>✓</th>
</tr>
<tr>
- <td>Approve EditRequest</td>
- <th></th> <!-- new -->
+ <td>Add/Delete Screenshot</td>
+ <th>✓</th> <!-- new -->
<th></th>
<th>✓</th> <!-- member -->
<th></th>
<th>✓</th>
</tr>
<tr>
- <td>Edit EditRequest</td>
- <th>✓<sup>1</sup></th> <!-- new -->
+ <td>Approve Screenshot</td>
+ <th></th> <!-- new -->
<th></th>
- <th>✓</th> <!-- member -->
+ <th></th> <!-- member -->
<th></th>
<th>✓</th> <!-- trusted member -->
<th></th>
UNAPPROVE_PACKAGE = "UNAPPROVE_PACKAGE"
TOPIC_DISCARD = "TOPIC_DISCARD"
CREATE_TOKEN = "CREATE_TOKEN"
+ EDIT_MAINTAINERS = "EDIT_MAINTAINERS"
CHANGE_PROFILE_URLS = "CHANGE_PROFILE_URLS"
# Only return true if the permission is valid for *all* contexts
db.Column("package_id", db.Integer, db.ForeignKey("package.id"), primary_key=True)
)
+maintainers = db.Table("maintainers",
+ db.Column("user_id", db.Integer, db.ForeignKey("user.id"), primary_key=True),
+ db.Column("package_id", db.Integer, db.ForeignKey("package.id"), primary_key=True)
+)
+
class Dependency(db.Model):
id = db.Column(db.Integer, primary_key=True)
depender_id = db.Column(db.Integer, db.ForeignKey("package.id"), nullable=True)
requests = db.relationship("EditRequest", backref="package",
lazy="dynamic")
+ maintainers = db.relationship("User", secondary=maintainers, lazy="subquery")
+
def __init__(self, package=None):
if package is None:
return
return url_for("packages.download",
author=self.author.username, name=self.name)
+ def getEditMaintainersURL(self):
+ return url_for("packages.edit_maintainers",
+ author=self.author.username, name=self.name)
+
def getDownloadRelease(self, version=None):
for rel in self.releases:
if rel.approved and (version is None or
raise Exception("Unknown permission given to Package.checkPerm()")
isOwner = user == self.author
+ isMaintainer = isOwner or user.rank.atLeast(UserRank.EDITOR) or user in self.maintainers
if perm == Permission.CREATE_THREAD:
return user.rank.atLeast(UserRank.MEMBER)
# Members can edit their own packages, and editors can edit any packages
- if perm == Permission.MAKE_RELEASE or perm == Permission.ADD_SCREENSHOTS:
- return isOwner or user.rank.atLeast(UserRank.EDITOR)
+ elif perm == Permission.MAKE_RELEASE or perm == Permission.ADD_SCREENSHOTS:
+ return isMaintainer
- if perm == Permission.EDIT_PACKAGE or perm == Permission.APPROVE_CHANGES or perm == Permission.APPROVE_RELEASE:
- if isOwner:
- return user.rank.atLeast(UserRank.MEMBER if self.approved else UserRank.NEW_MEMBER)
- else:
- return user.rank.atLeast(UserRank.EDITOR)
+ elif perm == Permission.EDIT_PACKAGE or perm == Permission.APPROVE_CHANGES or perm == Permission.APPROVE_RELEASE:
+ return isMaintainer 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 user.rank.atLeast(UserRank.EDITOR)
elif perm == Permission.APPROVE_SCREENSHOT:
- if isOwner:
- return user.rank.atLeast(UserRank.TRUSTED_MEMBER if self.approved else UserRank.NEW_MEMBER)
- else:
- return user.rank.atLeast(UserRank.EDITOR)
+ return isMaintainer and user.rank.atLeast(UserRank.TRUSTED_MEMBER if self.approved else UserRank.NEW_MEMBER)
+
+ elif perm == Permission.EDIT_MAINTAINERS:
+ return isOwner or user.rank.atLeast(UserRank.MODERATOR)
# Moderators can delete packages
elif perm == Permission.DELETE_PACKAGE or perm == Permission.UNAPPROVE_PACKAGE \
elif type(perm) != Permission:
raise Exception("Unknown permission given to Thread.checkPerm()")
- isOwner = user == self.author or (self.package is not None and self.package.author == user)
+ isMaintainer = user == self.author or (self.package is not None and self.package.author == user)
+ if self.package:
+ isMaintainer = isMaintainer or user in self.package.maintainers
if perm == Permission.SEE_THREAD:
- return not self.private or isOwner or user.rank.atLeast(UserRank.EDITOR)
+ return not self.private or isMaintainer or user.rank.atLeast(UserRank.EDITOR)
else:
raise Exception("Permission {} is not related to threads".format(perm.name))
--- /dev/null
+{% extends "base.html" %}
+
+{% block title %}
+ {{ _("Edit Maintainers") }}
+{% endblock %}
+
+{% from "macros/forms.html" import render_submit_field, render_field %}
+
+{% block content %}
+ <h1>{{ _("Edit Maintainers") }}</h1>
+
+ <form method="POST" action="" class="tableform">
+ {{ form.hidden_tag() }}
+
+ {{ render_field(form.maintainers_str) }}
+
+ <div>{{ render_submit_field(form.submit) }}</div>
+ </form>
+{% endblock %}
<td>Added</td>
<td>{{ package.created_at | datetime }}</td>
</tr>
+ <tr>
+ <td>Maintainers</td>
+ <td>
+ {% if package.checkPerm(current_user, "EDIT_MAINTAINERS") %}
+ <a class="btn btn-primary btn-sm ml-1 float-right" href="{{ package.getEditMaintainersURL() }}"><i class="fas fa-edit"></i></a>
+ {% endif %}
+
+ {% for user in package.maintainers %}
+ <a class="badge badge-primary"
+ href="{{ url_for('users.profile', username=package.author.username) }}">
+ {{ user.display_name }}
+ </a>
+ {% endfor %}
+ </td>
+ </tr>
</table>
</div>
--- /dev/null
+"""empty message
+
+Revision ID: cb6ab141c522
+Revises: 7a48dbd05780
+Create Date: 2020-07-08 21:03:51.856561
+
+"""
+from alembic import op
+import sqlalchemy as sa
+from sqlalchemy import orm
+from app.models import Package
+
+
+# revision identifiers, used by Alembic.
+revision = 'cb6ab141c522'
+down_revision = '7a48dbd05780'
+branch_labels = None
+depends_on = None
+
+
+def upgrade():
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.create_table('maintainers',
+ sa.Column('user_id', sa.Integer(), nullable=False),
+ sa.Column('package_id', sa.Integer(), nullable=False),
+ sa.ForeignKeyConstraint(['package_id'], ['package.id'], ),
+ sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
+ sa.PrimaryKeyConstraint('user_id', 'package_id')
+ )
+
+ bind = op.get_bind()
+ session = orm.Session(bind=bind)
+
+ for package in session.query(Package).all():
+ package.maintainers.append(package.author)
+
+ session.commit()
+
+ # ### end Alembic commands ###
+
+
+def downgrade():
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.drop_table('maintainers')
+ # ### end Alembic commands ###