diff --git a/app/blueprints/packages/packages.py b/app/blueprints/packages/packages.py index 700fc44..853153c 100644 --- a/app/blueprints/packages/packages.py +++ b/app/blueprints/packages/packages.py @@ -382,3 +382,37 @@ def remove(package): return redirect(package.getDetailsURL()) else: abort(400) + + + +class PackageMaintainersForm(FlaskForm): + maintainers_str = StringField("Maintainers (Comma-separated)", [Optional()]) + submit = SubmitField("Save") + + +@bp.route("/packages///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) diff --git a/app/flatpages/help/ranks_permissions.md b/app/flatpages/help/ranks_permissions.md index 1740c55..93a337c 100644 --- a/app/flatpages/help/ranks_permissions.md +++ b/app/flatpages/help/ranks_permissions.md @@ -11,7 +11,7 @@ title: Ranks and Permissions ## Breakdown - +
@@ -84,6 +84,21 @@ title: Ranks and Permissions + + + + + + + + + + + + + + + @@ -114,36 +129,6 @@ title: Ranks and Permissions - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/models.py b/app/models.py index f3e65a2..0cb90c9 100644 --- a/app/models.py +++ b/app/models.py @@ -93,6 +93,7 @@ class Permission(enum.Enum): 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 @@ -323,6 +324,11 @@ tags = db.Table("tags", 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) @@ -454,6 +460,8 @@ class Package(db.Model): 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 @@ -633,6 +641,10 @@ class Package(db.Model): 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 @@ -658,19 +670,17 @@ class Package(db.Model): 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: @@ -681,10 +691,10 @@ class Package(db.Model): 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 \ @@ -1077,10 +1087,12 @@ class Thread(db.Model): 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)) diff --git a/app/templates/packages/edit_maintainers.html b/app/templates/packages/edit_maintainers.html new file mode 100644 index 0000000..c3b51d8 --- /dev/null +++ b/app/templates/packages/edit_maintainers.html @@ -0,0 +1,19 @@ +{% extends "base.html" %} + +{% block title %} + {{ _("Edit Maintainers") }} +{% endblock %} + +{% from "macros/forms.html" import render_submit_field, render_field %} + +{% block content %} +

{{ _("Edit Maintainers") }}

+ + + {{ form.hidden_tag() }} + + {{ render_field(form.maintainers_str) }} + +
{{ render_submit_field(form.submit) }}
+ +{% endblock %} diff --git a/app/templates/packages/view.html b/app/templates/packages/view.html index f6889e0..3885aca 100644 --- a/app/templates/packages/view.html +++ b/app/templates/packages/view.html @@ -259,6 +259,21 @@ + + + +
Rank
Edit Maintainers
Add/Delete Screenshot
Approve EditRequest
Edit EditRequest1
Make Release
Added {{ package.created_at | datetime }}
Maintainers + {% if package.checkPerm(current_user, "EDIT_MAINTAINERS") %} + + {% endif %} + + {% for user in package.maintainers %} + + {{ user.display_name }} + + {% endfor %} +
diff --git a/migrations/versions/cb6ab141c522_.py b/migrations/versions/cb6ab141c522_.py new file mode 100644 index 0000000..33da88b --- /dev/null +++ b/migrations/versions/cb6ab141c522_.py @@ -0,0 +1,45 @@ +"""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 ###