コード例 #1
0
def admin_page():
    if request.method == "POST":
        action = request.form["action"]
        if action == "importusers":
            task = importUsersFromModList.delay()
            return redirect(
                url_for("check_task", id=task.id, r=url_for("user_list_page")))
        elif action == "importmodlist":
            task = importKrocksModList.delay()
            return redirect(
                url_for("check_task",
                        id=task.id,
                        r=url_for("todo_topics_page")))
        elif action == "importscreenshots":
            packages = Package.query \
             .filter_by(soft_deleted=False) \
             .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_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"))
        elif action == "importdepends":
            task = importAllDependencies.delay()
            return redirect(
                url_for("check_task", id=task.id, r=url_for("admin_page")))
        elif action == "modprovides":
            packages = Package.query.filter_by(type=PackageType.MOD).all()
            mpackage_cache = {}
            for p in packages:
                if len(p.provides) == 0:
                    p.provides.append(
                        MetaPackage.GetOrCreate(p.name, mpackage_cache))

            db.session.commit()
            return redirect(url_for("admin_page"))

        else:
            flash("Unknown action: " + action, "error")

    deleted_packages = Package.query.filter_by(soft_deleted=True).all()
    return render_template("admin/list.html",
                           deleted_packages=deleted_packages)
コード例 #2
0
def admin_page():
    if request.method == "POST":
        action = request.form["action"]
        if action == "importusers":
            task = importUsersFromModList.delay()
            return redirect(
                url_for("check_task", id=task.id, r=url_for("user_list_page")))
        elif action == "importscreenshots":
            packages = Package.query \
             .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_page"))
        else:
            flash("Unknown action: " + action, "error")

    return render_template("admin/list.html")
コード例 #3
0
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"))

        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(updateMetaFromRelease.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"))

        elif action == "importforeign":
            releases = PackageRelease.query.filter(
                PackageRelease.url.like("http%")).all()

            tasks = []
            for release in releases:
                tasks.append(importForeignDownloads.s(release.id))

            result = group(tasks).apply_async()

            while not result.ready():
                import time
                time.sleep(0.1)

            return redirect(url_for("todo.view"))

        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"))
        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)
コード例 #4
0
def create_edit(author=None, name=None):
	package = None
	form = None
	if author is None:
		form = PackageForm(formdata=request.form)
		author = request.args.get("author")
		if author is None or author == current_user.username:
			author = current_user
		else:
			author = User.query.filter_by(username=author).first()
			if author is None:
				flash("Unable to find that user", "danger")
				return redirect(url_for("packages.create_edit"))

			if not author.checkPerm(current_user, Permission.CHANGE_AUTHOR):
				flash("Permission denied", "danger")
				return redirect(url_for("packages.create_edit"))

	else:
		package = getPackageByInfo(author, name)
		if package is None:
			abort(404)
		if not package.checkPerm(current_user, Permission.EDIT_PACKAGE):
			return redirect(package.getDetailsURL())

		author = package.author

		form = PackageForm(formdata=request.form, obj=package)

	# Initial form class from post data and default data
	if request.method == "GET":
		if package is None:
			form.name.data   = request.args.get("bname")
			form.title.data  = request.args.get("title")
			form.repo.data   = request.args.get("repo")
			form.forums.data = request.args.get("forums")
			form.license.data = None
			form.media_license.data = None
		else:
			# form.harddep_str.data  = ",".join([str(x) for x in package.getSortedHardDependencies() ])
			# form.softdep_str.data  = ",".join([str(x) for x in package.getSortedOptionalDependencies() ])
			form.tags.data         = list(package.tags)
			form.content_warnings.data = list(package.content_warnings)

	if request.method == "POST" and form.type.data == PackageType.TXP:
		form.license.data = form.media_license.data

	if form.validate_on_submit():
		wasNew = False
		if not package:
			package = Package.query.filter_by(name=form["name"].data, author_id=author.id).first()
			if package is not None:
				if package.state == PackageState.READY_FOR_REVIEW:
					Package.query.filter_by(name=form["name"].data, author_id=author.id).delete()
				else:
					flash("Package already exists!", "danger")
					return redirect(url_for("packages.create_edit"))

			package = Package()
			package.author = author
			package.maintainers.append(author)
			wasNew = True

		elif package.name != form.name.data and not package.checkPerm(current_user, Permission.CHANGE_NAME):
			flash("Unable to change package name", "danger")
			return redirect(url_for("packages.create_edit", author=author, name=name))

		else:
			msg = "Edited {}".format(package.title)

			addNotification(package.maintainers, current_user, NotificationType.PACKAGE_EDIT,
					msg, package.getDetailsURL(), package)

			severity = AuditSeverity.NORMAL if current_user in package.maintainers else AuditSeverity.EDITOR
			addAuditLog(severity, current_user, msg, package.getDetailsURL(), package)

		form.populate_obj(package) # copy to row

		if package.type == PackageType.TXP:
			package.license = package.media_license

		# Dependency.query.filter_by(depender=package).delete()
		# deps = Dependency.SpecToList(package, form.harddep_str.data, mpackage_cache)
		# for dep in deps:
		# 	dep.optional = False
		# 	db.session.add(dep)

		# deps = Dependency.SpecToList(package, form.softdep_str.data, mpackage_cache)
		# for dep in deps:
		# 	dep.optional = True
		# 	db.session.add(dep)

		if wasNew and package.type == PackageType.MOD:
			m = MetaPackage.GetOrCreate(package.name, {})
			package.provides.append(m)

		package.tags.clear()
		for tag in form.tags.raw_data:
			package.tags.append(Tag.query.get(tag))

		package.content_warnings.clear()
		for warning in form.content_warnings.raw_data:
			package.content_warnings.append(ContentWarning.query.get(warning))

		db.session.commit() # save

		next_url = package.getDetailsURL()
		if wasNew and package.repo is not None:
			task = importRepoScreenshot.delay(package.id)
			next_url = url_for("tasks.check", id=task.id, r=next_url)

		if wasNew and ("WTFPL" in package.license.name or "WTFPL" in package.media_license.name):
			next_url = url_for("flatpage", path="help/wtfpl", r=next_url)

		return redirect(next_url)

	package_query = Package.query.filter_by(state=PackageState.APPROVED)
	if package is not None:
		package_query = package_query.filter(Package.id != package.id)

	enableWizard = name is None and request.method != "POST"
	return render_template("packages/create_edit.html", package=package,
			form=form, author=author, enable_wizard=enableWizard,
			packages=package_query.all(),
			mpackages=MetaPackage.query.order_by(db.asc(MetaPackage.name)).all())
コード例 #5
0
def create_edit(author=None, name=None):
    package = None
    if author is None:
        form = PackageForm(formdata=request.form)
        author = request.args.get("author")
        if author is None or author == current_user.username:
            author = current_user
        else:
            author = User.query.filter_by(username=author).first()
            if author is None:
                flash("Unable to find that user", "danger")
                return redirect(url_for("packages.create_edit"))

            if not author.checkPerm(current_user, Permission.CHANGE_AUTHOR):
                flash("Permission denied", "danger")
                return redirect(url_for("packages.create_edit"))

    else:
        package = getPackageByInfo(author, name)
        if package is None:
            abort(404)
        if not package.checkPerm(current_user, Permission.EDIT_PACKAGE):
            return redirect(package.getDetailsURL())

        author = package.author

        form = PackageForm(formdata=request.form, obj=package)

    # Initial form class from post data and default data
    if request.method == "GET":
        if package is None:
            form.name.data = request.args.get("bname")
            form.title.data = request.args.get("title")
            form.repo.data = request.args.get("repo")
            form.forums.data = request.args.get("forums")
            form.license.data = None
            form.media_license.data = None
        else:
            form.tags.data = list(package.tags)
            form.content_warnings.data = list(package.content_warnings)

    if request.method == "POST" and form.type.data == PackageType.TXP:
        form.license.data = form.media_license.data

    if form.validate_on_submit():
        wasNew = False
        if not package:
            package = Package.query.filter_by(name=form["name"].data,
                                              author_id=author.id).first()
            if package is not None:
                if package.state == PackageState.READY_FOR_REVIEW:
                    Package.query.filter_by(name=form["name"].data,
                                            author_id=author.id).delete()
                else:
                    flash("Package already exists!", "danger")
                    return redirect(url_for("packages.create_edit"))

            package = Package()
            package.author = author
            package.maintainers.append(author)
            wasNew = True

        try:
            do_edit_package(
                current_user, package, wasNew, {
                    "type": form.type.data,
                    "title": form.title.data,
                    "name": form.name.data,
                    "short_desc": form.short_desc.data,
                    "tags": form.tags.raw_data,
                    "content_warnings": form.content_warnings.raw_data,
                    "license": form.license.data,
                    "media_license": form.media_license.data,
                    "desc": form.desc.data,
                    "repo": form.repo.data,
                    "website": form.website.data,
                    "issueTracker": form.issueTracker.data,
                    "forums": form.forums.data,
                })

            if wasNew and package.repo is not None:
                importRepoScreenshot.delay(package.id)

            next_url = package.getDetailsURL()
            if wasNew and ("WTFPL" in package.license.name
                           or "WTFPL" in package.media_license.name):
                next_url = url_for("flatpage", path="help/wtfpl", r=next_url)
            elif wasNew:
                next_url = package.getSetupReleasesURL()

            return redirect(next_url)
        except LogicError as e:
            flash(e.message, "danger")

    package_query = Package.query.filter_by(state=PackageState.APPROVED)
    if package is not None:
        package_query = package_query.filter(Package.id != package.id)

    enableWizard = name is None and request.method != "POST"
    return render_template("packages/create_edit.html",
                           package=package,
                           form=form,
                           author=author,
                           enable_wizard=enableWizard,
                           packages=package_query.all(),
                           mpackages=MetaPackage.query.order_by(
                               db.asc(MetaPackage.name)).all())
コード例 #6
0
ファイル: admin.py プロジェクト: Warr1024/contentdb
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)
コード例 #7
0
def create_edit_package_page(author=None, name=None):
    package = None
    form = None
    if author is None:
        form = PackageForm(formdata=request.form)
        author = request.args.get("author")
        if author is None or author == current_user.username:
            author = current_user
        else:
            author = User.query.filter_by(username=author).first()
            if author is None:
                flash("Unable to find that user", "error")
                return redirect(url_for("create_edit_package_page"))

            if not author.checkPerm(current_user, Permission.CHANGE_AUTHOR):
                flash("Permission denied", "error")
                return redirect(url_for("create_edit_package_page"))

    else:
        package = getPackageByInfo(author, name)
        if not package.checkPerm(current_user, Permission.EDIT_PACKAGE):
            return redirect(package.getDetailsURL())

        author = package.author

        form = PackageForm(formdata=request.form, obj=package)

    # Initial form class from post data and default data
    if request.method == "POST" and form.validate():
        wasNew = False
        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"))

            package = Package()
            package.author = author
            wasNew = True
        else:
            triggerNotif(package.author, current_user,
                         "{} edited".format(package.title),
                         package.getDetailsURL())

        form.populate_obj(package)  # copy to row

        package.tags.clear()
        for tag in form.tags.raw_data:
            package.tags.append(Tag.query.get(tag))

        db.session.commit()  # save

        if wasNew:
            url = urlparse(package.repo)
            if url.netloc == "github.com":
                task = importRepoScreenshot.delay(package.id)
                return redirect(
                    url_for("check_task",
                            id=task.id,
                            r=package.getDetailsURL()))

        return redirect(package.getDetailsURL())

    enableWizard = name is None and request.method != "POST"
    return render_template("packages/create_edit.html", package=package, \
      form=form, author=author, enable_wizard=enableWizard)
コード例 #8
0
ファイル: admin.py プロジェクト: BogusCurry/contentdb
def admin_page():
    if request.method == "POST":
        action = request.form["action"]
        if action == "importmodlist":
            task = importTopicList.delay()
            return redirect(
                url_for("check_task",
                        id=task.id,
                        r=url_for("todo_topics_page")))
        elif action == "importscreenshots":
            packages = Package.query \
             .filter_by(soft_deleted=False) \
             .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_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"))
        elif action == "importdepends":
            task = importAllDependencies.delay()
            return redirect(
                url_for("check_task", id=task.id, r=url_for("admin_page")))
        elif action == "modprovides":
            packages = Package.query.filter_by(type=PackageType.MOD).all()
            mpackage_cache = {}
            for p in packages:
                if len(p.provides) == 0:
                    p.provides.append(
                        MetaPackage.GetOrCreate(p.name, mpackage_cache))

            db.session.commit()
            return redirect(url_for("admin_page"))
        elif action == "recalcscores":
            for p in Package.query.all():
                p.recalcScore()

            db.session.commit()
            return redirect(url_for("admin_page"))
        elif action == "vcsrelease":
            for package in Package.query.filter(
                    Package.repo.isnot(None)).all():
                if package.releases.count() != 0:
                    continue

                rel = PackageRelease()
                rel.package = package
                rel.title = datetime.date.today().isoformat()
                rel.url = ""
                rel.task_id = uuid()
                rel.approved = True
                db.session.add(rel)
                db.session.commit()

                makeVCSRelease.apply_async((rel.id, "master"),
                                           task_id=rel.task_id)

                msg = "{}: Release {} created".format(package.title, rel.title)
                triggerNotif(package.author, current_user, msg,
                             rel.getEditURL())
                db.session.commit()

        else:
            flash("Unknown action: " + action, "error")

    deleted_packages = Package.query.filter_by(soft_deleted=True).all()
    return render_template("admin/list.html",
                           deleted_packages=deleted_packages)