def set_lock(id): thread = Thread.query.get(id) if thread is None or not thread.checkPerm(current_user, Permission.LOCK_THREAD): abort(404) thread.locked = isYes(request.args.get("lock")) if thread.locked is None: abort(400) msg = None if thread.locked: msg = "Locked thread '{}'".format(thread.title) flash("Locked thread", "success") else: msg = "Unlocked thread '{}'".format(thread.title) flash("Unlocked thread", "success") addNotification(thread.watchers, current_user, NotificationType.OTHER, msg, thread.getViewURL(), thread.package) addAuditLog(AuditSeverity.MODERATION, current_user, NotificationType.OTHER, msg, thread.getViewURL(), thread.package) db.session.commit() return redirect(thread.getViewURL())
def delete_review(package): review = PackageReview.query.filter_by(package=package, author=current_user).first() if review is None or review.package != package: abort(404) thread = review.thread reply = ThreadReply() reply.thread = thread reply.author = current_user reply.comment = "_converted review into a thread_" db.session.add(reply) thread.review = None notif_msg = "Deleted review '{}', comments were kept as a thread".format( thread.title) addNotification(package.maintainers, current_user, notif_msg, url_for("threads.view", id=thread.id), package) db.session.delete(review) db.session.commit() return redirect(thread.getViewURL())
def do_create_screenshot(user: User, package: Package, title: str, file, reason: str = None): thirty_minutes_ago = datetime.datetime.now() - datetime.timedelta(minutes=30) count = package.screenshots.filter(PackageScreenshot.created_at > thirty_minutes_ago).count() if count >= 20: raise LogicError(429, "Too many requests, please wait before trying again") uploaded_url, uploaded_path = upload_file(file, "image", "a PNG or JPG image file") counter = 1 for screenshot in package.screenshots.all(): screenshot.order = counter counter += 1 ss = PackageScreenshot() ss.package = package ss.title = title or "Untitled" ss.url = uploaded_url ss.approved = package.checkPerm(user, Permission.APPROVE_SCREENSHOT) ss.order = counter db.session.add(ss) if reason is None: msg = "Created screenshot {}".format(ss.title) else: msg = "Created screenshot {} ({})".format(ss.title, reason) addNotification(package.maintainers, user, NotificationType.PACKAGE_EDIT, msg, package.getURL("packages.view"), package) addAuditLog(AuditSeverity.NORMAL, user, msg, package.getURL("packages.view"), package) db.session.commit() return ss
def send_bulk_notification(): form = SendNotificationForm(request.form) if form.validate_on_submit(): addAuditLog(AuditSeverity.MODERATION, current_user, "Sent bulk notification", None, None, form.title.data) users = User.query.filter(User.rank >= UserRank.NEW_MEMBER).all() addNotification(users, current_user, NotificationType.OTHER, form.title.data, form.url.data, None) db.session.commit() return redirect(url_for("admin.admin_page")) return render_template("admin/send_bulk_notification.html", form=form)
def apply_all_updates(username): user: User = User.query.filter_by(username=username).first() if not user: abort(404) if current_user != user and not current_user.rank.atLeast(UserRank.EDITOR): abort(403) outdated_packages = user.maintained_packages \ .filter(Package.state != PackageState.DELETED, Package.update_config.has(PackageUpdateConfig.outdated_at.isnot(None))) \ .order_by(db.asc(Package.title)).all() for package in outdated_packages: if not package.checkPerm(current_user, Permission.MAKE_RELEASE): continue if package.releases.filter( or_( PackageRelease.task_id.isnot(None), PackageRelease.commit_hash == package.update_config.last_commit)).count() > 0: continue title = package.update_config.get_title() ref = package.update_config.get_ref() rel = PackageRelease() rel.package = package rel.title = title rel.url = "" rel.task_id = uuid() db.session.add(rel) db.session.commit() makeVCSRelease.apply_async((rel.id, ref), task_id=rel.task_id) msg = "Created release {} (Applied all Git Update Detection)".format( rel.title) addNotification(package.maintainers, current_user, NotificationType.PACKAGE_EDIT, msg, rel.getEditURL(), package) addAuditLog(AuditSeverity.NORMAL, current_user, msg, package.getDetailsURL(), package) db.session.commit() return redirect(url_for("todo.view_user", username=username))
def view(id): thread: Thread = Thread.query.get(id) if thread is None or not thread.checkPerm(current_user, Permission.SEE_THREAD): abort(404) if current_user.is_authenticated and request.method == "POST": comment = request.form["comment"] if not thread.checkPerm(current_user, Permission.COMMENT_THREAD): flash("You cannot comment on this thread", "danger") return redirect(thread.getViewURL()) if not current_user.canCommentRL(): flash("Please wait before commenting again", "danger") return redirect(thread.getViewURL()) if 2000 >= len(comment) > 3: reply = ThreadReply() reply.author = current_user reply.comment = comment db.session.add(reply) thread.replies.append(reply) if not current_user in thread.watchers: thread.watchers.append(current_user) msg = "New comment on '{}'".format(thread.title) 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, thread.getViewURL(), thread.package) db.session.commit() return redirect(thread.getViewURL()) else: flash("Comment needs to be between 3 and 2000 characters.") return render_template("threads/view.html", thread=thread)
def edit_reply(id): thread = Thread.query.get(id) if thread is None: abort(404) reply_id = request.args.get("reply") if reply_id is None: abort(404) reply = ThreadReply.query.get(reply_id) if reply is None or reply.thread != thread: abort(404) if not reply.checkPerm(current_user, Permission.EDIT_REPLY): abort(403) form = CommentForm(formdata=request.form, obj=reply) if form.validate_on_submit(): comment = form.comment.data msg = "Edited reply by {}".format(reply.author.display_name) severity = AuditSeverity.NORMAL if current_user == reply.author else AuditSeverity.MODERATION addNotification(reply.author, current_user, NotificationType.OTHER, msg, thread.getViewURL(), thread.package) addAuditLog(severity, current_user, msg, thread.getViewURL(), thread.package, reply.comment) reply.comment = comment db.session.commit() return redirect(thread.getViewURL()) return render_template("threads/edit_reply.html", thread=thread, reply=reply, form=form)
def review(package): if current_user in package.maintainers: flash("You can't review your own package!", "danger") return redirect(package.getDetailsURL()) review = PackageReview.query.filter_by(package=package, author=current_user).first() form = ReviewForm(formdata=request.form, obj=review) # Set default values if request.method == "GET" and review: form.title.data = review.thread.title form.recommends.data = "yes" if review.recommends else "no" form.comment.data = review.thread.replies[0].comment # Validate and submit elif request.method == "POST" and form.validate(): was_new = False if not review: was_new = True review = PackageReview() review.package = package review.author = current_user db.session.add(review) review.recommends = form.recommends.data == "yes" thread = review.thread if not thread: thread = Thread() thread.author = current_user thread.private = False thread.package = package thread.review = review db.session.add(thread) thread.watchers.append(current_user) reply = ThreadReply() reply.thread = thread reply.author = current_user reply.comment = form.comment.data db.session.add(reply) thread.replies.append(reply) else: reply = thread.replies[0] reply.comment = form.comment.data thread.title = form.title.data db.session.commit() package.recalcScore() notif_msg = None if was_new: notif_msg = "New review '{}'".format(form.title.data) else: notif_msg = "Updated review '{}'".format(form.title.data) addNotification(package.maintainers, current_user, notif_msg, url_for("threads.view", id=thread.id), package) db.session.commit() return redirect(package.getDetailsURL()) return render_template("packages/review_create_edit.html", \ form=form, package=package, review=review)
def new(): form = ThreadForm(formdata=request.form) package = None if "pid" in request.args: package = Package.query.get(int(request.args.get("pid"))) if package is None: flash("Unable to find that package!", "danger") # Don't allow making orphan threads on approved packages for now if package is None: abort(403) def_is_private = request.args.get("private") or False if package is None: def_is_private = True allow_change = package and package.approved is_review_thread = package and not package.approved # Check that user can make the thread if not package.checkPerm(current_user, Permission.CREATE_THREAD): flash("Unable to create thread!", "danger") return redirect(url_for("homepage.home")) # Only allow creating one thread when not approved elif is_review_thread and package.review_thread is not None: flash("A review thread already exists!", "danger") return redirect(package.review_thread.getViewURL()) elif not current_user.canOpenThreadRL(): flash("Please wait before opening another thread", "danger") if package: return redirect(package.getDetailsURL()) else: return redirect(url_for("homepage.home")) # Set default values elif request.method == "GET": form.private.data = def_is_private form.title.data = request.args.get("title") or "" # Validate and submit elif form.validate_on_submit(): thread = Thread() thread.author = current_user thread.title = form.title.data thread.private = form.private.data if allow_change else def_is_private thread.package = package db.session.add(thread) thread.watchers.append(current_user) if package is not None and package.author != current_user: thread.watchers.append(package.author) reply = ThreadReply() reply.thread = thread reply.author = current_user reply.comment = form.comment.data db.session.add(reply) thread.replies.append(reply) db.session.commit() if is_review_thread: package.review_thread = thread if package.state == PackageState.READY_FOR_REVIEW and current_user not in package.maintainers: package.state = PackageState.CHANGES_NEEDED notif_msg = "New thread '{}'".format(thread.title) 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) db.session.commit() return redirect(thread.getViewURL()) return render_template("threads/new.html", form=form, allow_private_change=allow_change, package=package)
def admin_page(): if request.method == "POST": action = request.form["action"] if action == "delstuckreleases": PackageRelease.query.filter( PackageRelease.task_id != None).delete() db.session.commit() return redirect(url_for("admin.admin_page")) elif action == "checkreleases": releases = PackageRelease.query.filter( PackageRelease.url.like("/uploads/%")).all() tasks = [] for release in releases: zippath = release.url.replace("/uploads/", app.config["UPLOAD_DIR"]) tasks.append(checkZipRelease.s(release.id, zippath)) result = group(tasks).apply_async() while not result.ready(): import time time.sleep(0.1) return redirect(url_for("todo.view_editor")) elif action == "reimportpackages": tasks = [] for package in Package.query.filter( Package.state != PackageState.DELETED).all(): release = package.releases.first() if release: zippath = release.url.replace("/uploads/", app.config["UPLOAD_DIR"]) tasks.append(checkZipRelease.s(release.id, zippath)) result = group(tasks).apply_async() while not result.ready(): import time time.sleep(0.1) return redirect(url_for("todo.view_editor")) elif action == "importmodlist": task = importTopicList.delay() return redirect( url_for("tasks.check", id=task.id, r=url_for("todo.topics"))) elif action == "checkusers": task = checkAllForumAccounts.delay() return redirect( url_for("tasks.check", id=task.id, r=url_for("admin.admin_page"))) elif action == "importscreenshots": packages = Package.query \ .filter(Package.state!=PackageState.DELETED) \ .outerjoin(PackageScreenshot, Package.id==PackageScreenshot.package_id) \ .filter(PackageScreenshot.id==None) \ .all() for package in packages: importRepoScreenshot.delay(package.id) return redirect(url_for("admin.admin_page")) elif action == "restore": package = Package.query.get(request.form["package"]) if package is None: flash("Unknown package", "danger") else: package.state = PackageState.READY_FOR_REVIEW db.session.commit() return redirect(url_for("admin.admin_page")) elif action == "recalcscores": for p in Package.query.all(): p.recalcScore() db.session.commit() return redirect(url_for("admin.admin_page")) elif action == "cleanuploads": upload_dir = app.config['UPLOAD_DIR'] (_, _, filenames) = next(os.walk(upload_dir)) existing_uploads = set(filenames) if len(existing_uploads) != 0: def getURLsFromDB(column): results = db.session.query(column).filter( column != None, column != "").all() return set([os.path.basename(x[0]) for x in results]) release_urls = getURLsFromDB(PackageRelease.url) screenshot_urls = getURLsFromDB(PackageScreenshot.url) db_urls = release_urls.union(screenshot_urls) unreachable = existing_uploads.difference(db_urls) import sys print("On Disk: ", existing_uploads, file=sys.stderr) print("In DB: ", db_urls, file=sys.stderr) print("Unreachable: ", unreachable, file=sys.stderr) for filename in unreachable: os.remove(os.path.join(upload_dir, filename)) flash( "Deleted " + str(len(unreachable)) + " unreachable uploads", "success") else: flash("No downloads to create", "danger") return redirect(url_for("admin.admin_page")) elif action == "delmetapackages": query = MetaPackage.query.filter(~MetaPackage.dependencies.any(), ~MetaPackage.packages.any()) count = query.count() query.delete(synchronize_session=False) db.session.commit() flash("Deleted " + str(count) + " unused meta packages", "success") return redirect(url_for("admin.admin_page")) elif action == "delremovedpackages": query = Package.query.filter_by(state=PackageState.DELETED) count = query.count() for pkg in query.all(): pkg.review_thread = None db.session.delete(pkg) db.session.commit() flash("Deleted {} soft deleted packages packages".format(count), "success") return redirect(url_for("admin.admin_page")) elif action == "addupdateconfig": added = 0 for pkg in Package.query.filter( Package.repo != None, Package.releases.any(), Package.update_config == None).all(): pkg.update_config = PackageUpdateConfig() pkg.update_config.auto_created = True release: PackageRelease = pkg.releases.first() if release and release.commit_hash: pkg.update_config.last_commit = release.commit_hash db.session.add(pkg.update_config) added += 1 db.session.commit() flash("Added {} update configs".format(added), "success") return redirect(url_for("admin.admin_page")) elif action == "runupdateconfig": check_for_updates.delay() flash("Started update configs", "success") return redirect(url_for("admin.admin_page")) elif action == "remindwip": users = User.query.filter( User.packages.any( or_(Package.state == PackageState.WIP, Package.state == PackageState.CHANGES_NEEDED))) system_user = get_system_user() for user in users: packages = db.session.query(Package.title).filter( Package.author_id==user.id, or_(Package.state==PackageState.WIP, Package.state==PackageState.CHANGES_NEEDED)) \ .all() # Who needs translations? packages = [pkg[0] for pkg in packages] if len(packages) >= 3: packages[len(packages) - 1] = "and " + packages[len(packages) - 1] packages_list = ", ".join(packages) else: packages_list = "and ".join(packages) havent = "haven't" if len(packages) > 1 else "hasn't" if len(packages_list) + 54 > 100: packages_list = packages_list[0:(100 - 54 - 1)] + "…" addNotification( user, system_user, NotificationType.PACKAGE_APPROVAL, f"Did you forget? {packages_list} {havent} been submitted for review yet", url_for('todo.view_user', username=user.username)) db.session.commit() else: flash("Unknown action: " + action, "danger") deleted_packages = Package.query.filter( Package.state == PackageState.DELETED).all() return render_template("admin/list.html", deleted_packages=deleted_packages)