diff --git a/app/models.py b/app/models.py index c0c9a095..9118f78c 100644 --- a/app/models.py +++ b/app/models.py @@ -261,6 +261,7 @@ class Package(db.Model): license_id = db.Column(db.Integer, db.ForeignKey("license.id")) approved = db.Column(db.Boolean, nullable=False, default=False) + soft_deleted = db.Column(db.Boolean, nullable=False, default=False) # Downloads repo = db.Column(db.String(200), nullable=True) @@ -329,6 +330,10 @@ class Package(db.Model): return url_for("approve_package_page", author=self.author.username, name=self.name) + def getDeleteURL(self): + return url_for("delete_package_page", + author=self.author.username, name=self.name) + def getNewScreenshotURL(self): return url_for("create_screenshot_page", author=self.author.username, name=self.name) @@ -546,7 +551,7 @@ class EditRequestChange(db.Model): if user is None: continue - dep = Package.query.filter_by(author=user, name=value).first() + dep = Package.query.filter_by(author=user, name=value, soft_deleted=False).first() if dep is None: continue diff --git a/app/scss/components.scss b/app/scss/components.scss index 3a145bc5..226ef6b2 100644 --- a/app/scss/components.scss +++ b/app/scss/components.scss @@ -301,9 +301,9 @@ select:not([multiple]) { color: #fff; } -.alert-error { - background: #933; - border: 1px solid #c44; +.alert-error, .button-danger { + background: #933 !important; + border: 1px solid #c44 !important; } .alert-warning { diff --git a/app/tasks/importtasks.py b/app/tasks/importtasks.py index 352736e7..a20bbee8 100644 --- a/app/tasks/importtasks.py +++ b/app/tasks/importtasks.py @@ -231,7 +231,7 @@ def makeVCSRelease(id, branch): @celery.task() def importRepoScreenshot(id): package = Package.query.get(id) - if package is None: + if package is None or package.soft_deleted: raise Exception("Unexpected none package") # Get URL Maker diff --git a/app/templates/admin/list.html b/app/templates/admin/list.html index 7b664584..373f18b5 100644 --- a/app/templates/admin/list.html +++ b/app/templates/admin/list.html @@ -19,7 +19,22 @@ - + + + + +
+

Restore Package

+ +
+ + + +
{% endblock %} diff --git a/app/templates/packages/delete.html b/app/templates/packages/delete.html new file mode 100644 index 00000000..95709c39 --- /dev/null +++ b/app/templates/packages/delete.html @@ -0,0 +1,18 @@ +{% extends "base.html" %} + +{% block title %} + Delete | {{ package.title }} +{% endblock %} + +{% block content %} +
+

Delete Package

+ +
+

This action can be undone by the admin, but he'll be very annoyed!

+ + + +
+
+{% endblock %} diff --git a/app/templates/packages/view.html b/app/templates/packages/view.html index c3dbb34b..8d329ef7 100644 --- a/app/templates/packages/view.html +++ b/app/templates/packages/view.html @@ -91,6 +91,9 @@ {% if package.checkPerm(current_user, "MAKE_RELEASE") %}
  • Create Release
  • {% endif %} + {% if package.checkPerm(current_user, "DELETE_PACKAGE") %} +
  • Delete
  • + {% endif %} diff --git a/app/utils.py b/app/utils.py index e42c3cc0..9be70d6c 100644 --- a/app/utils.py +++ b/app/utils.py @@ -116,7 +116,7 @@ def getPackageByInfo(author, name): if user is None: abort(404) - package = Package.query.filter_by(name=name, author_id=user.id).first() + package = Package.query.filter_by(name=name, author_id=user.id, soft_deleted=False).first() if package is None: abort(404) diff --git a/app/views/__init__.py b/app/views/__init__.py index a296e5df..abaacd71 100644 --- a/app/views/__init__.py +++ b/app/views/__init__.py @@ -38,7 +38,7 @@ def send_upload(path): @app.route("/") @menu.register_menu(app, ".", "Home") def home_page(): - query = Package.query.filter_by(approved=True) + query = Package.query.filter_by(approved=True, soft_deleted=False) count = query.count() packages = query.order_by(db.desc(Package.created_at)).limit(15).all() return render_template("index.html", packages=packages, count=count) diff --git a/app/views/admin.py b/app/views/admin.py index d0dcf733..6c8c4ef0 100644 --- a/app/views/admin.py +++ b/app/views/admin.py @@ -37,15 +37,25 @@ def admin_page(): elif action == "importscreenshots": packages = Package.query \ .outerjoin(PackageScreenshot, Package.id==PackageScreenshot.package_id) \ - .filter(PackageScreenshot.id==None).all() + .filter(PackageScreenshot.id==None) \ + .filter_by(soft_deleted=False).all() for package in packages: importRepoScreenshot.delay(package.id) return redirect(url_for("admin_page")) + elif action == "restore": + package = Package.query.get(request.form["package"]) + if package is None: + flash("Unknown package", "error") + else: + package.soft_deleted = False + db.session.commit() + return redirect(url_for("admin_page")) else: flash("Unknown action: " + action, "error") - return render_template("admin/list.html") + deleted_packages = Package.query.filter_by(soft_deleted=True).all() + return render_template("admin/list.html", deleted_packages=deleted_packages) class SwitchUserForm(FlaskForm): username = StringField("Username") diff --git a/app/views/packages/__init__.py b/app/views/packages/__init__.py index c2806665..340dab7a 100644 --- a/app/views/packages/__init__.py +++ b/app/views/packages/__init__.py @@ -43,7 +43,7 @@ def packages_page(): type = PackageType[type.upper()] title = "Packages" - query = Package.query + query = Package.query.filter_by(soft_deleted=False) if type is not None: title = type.value + "s" @@ -107,8 +107,8 @@ class PackageForm(FlaskForm): type = SelectField("Type", [InputRequired()], choices=PackageType.choices(), coerce=PackageType.coerce, default=PackageType.MOD) license = QuerySelectField("License", [InputRequired()], query_factory=lambda: License.query, get_pk=lambda a: a.id, get_label=lambda a: a.name) tags = QuerySelectMultipleField('Tags', query_factory=lambda: Tag.query.order_by(db.asc(Tag.name)), get_pk=lambda a: a.id, get_label=lambda a: a.title) - harddeps = QuerySelectMultipleField('Dependencies', query_factory=lambda: Package.query.join(User).order_by(db.asc(Package.title), db.asc(User.display_name)), get_pk=lambda a: a.id, get_label=lambda a: a.title + " by " + a.author.display_name) - softdeps = QuerySelectMultipleField('Soft Dependencies', query_factory=lambda: Package.query.join(User).order_by(db.asc(Package.title), db.asc(User.display_name)), get_pk=lambda a: a.id, get_label=lambda a: a.title + " by " + a.author.display_name) + harddeps = QuerySelectMultipleField('Dependencies', query_factory=lambda: Package.query.join(User).filter_by(soft_deleted=False,approved=True).order_by(db.asc(Package.title), db.asc(User.display_name)), get_pk=lambda a: a.id, get_label=lambda a: a.title + " by " + a.author.display_name) + softdeps = QuerySelectMultipleField('Soft Dependencies', query_factory=lambda: Package.query.join(User).filter_by(soft_deleted=False,approved=True).order_by(db.asc(Package.title), db.asc(User.display_name)), get_pk=lambda a: a.id, get_label=lambda a: a.title + " by " + a.author.display_name) repo = StringField("Repo URL", [Optional(), URL()]) website = StringField("Website URL", [Optional(), URL()]) issueTracker = StringField("Issue Tracker URL", [Optional(), URL()]) @@ -151,8 +151,11 @@ def create_edit_package_page(author=None, name=None): if not package: package = Package.query.filter_by(name=form["name"].data, author_id=author.id).first() if package is not None: - flash("Package already exists!", "error") - return redirect(url_for("create_edit_package_page")) + if package.soft_deleted: + package.delete() + else: + flash("Package already exists!", "error") + return redirect(url_for("create_edit_package_page")) package = Package() package.author = author @@ -198,4 +201,26 @@ def approve_package_page(package): return redirect(package.getDetailsURL()) + +@app.route("/packages///delete/", methods=["GET", "POST"]) +@login_required +@is_package_page +def delete_package_page(package): + if request.method == "GET": + return render_template("packages/delete.html", package=package) + + if not package.checkPerm(current_user, Permission.DELETE_PACKAGE): + flash("You don't have permission to do that.", "error") + + package.soft_deleted = True + + url = url_for("user_profile_page", username=package.author.username) + triggerNotif(package.author, current_user, + "{} deleted".format(package.title), url) + db.session.commit() + + flash("Deleted package", "success") + + return redirect(url) + from . import todo, screenshots, editrequests, releases diff --git a/app/views/packages/todo.py b/app/views/packages/todo.py index 53fec737..63f843b7 100644 --- a/app/views/packages/todo.py +++ b/app/views/packages/todo.py @@ -29,7 +29,7 @@ def todo_page(): packages = None if canApproveNew: - packages = Package.query.filter_by(approved=False).all() + packages = Package.query.filter_by(approved=False, soft_deleted=False).all() releases = None if canApproveRel: diff --git a/migrations/versions/c4152f4240ed_.py b/migrations/versions/c4152f4240ed_.py new file mode 100644 index 00000000..de729a81 --- /dev/null +++ b/migrations/versions/c4152f4240ed_.py @@ -0,0 +1,28 @@ +"""empty message + +Revision ID: c4152f4240ed +Revises: 3f4d7cd8401f +Create Date: 2018-05-25 18:27:16.953305 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'c4152f4240ed' +down_revision = '3f4d7cd8401f' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('package', sa.Column('soft_deleted', sa.Boolean(), nullable=False)) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('package', 'soft_deleted') + # ### end Alembic commands ###