Clean up admin blueprint

This commit is contained in:
rubenwardy 2022-01-26 19:12:42 +00:00
parent cb2d9d4b07
commit e2708933d3
8 changed files with 97 additions and 86 deletions

View File

@ -20,10 +20,11 @@ from typing import List
import requests
from celery import group
from flask import *
from flask import redirect, url_for, flash, current_app
from sqlalchemy import or_, and_
from app.models import *
from app.models import PackageRelease, db, Package, PackageState, PackageScreenshot, MetaPackage, User, \
NotificationType, PackageUpdateConfig, License, UserRank, PackageType
from app.tasks.forumtasks import importTopicList, checkAllForumAccounts
from app.tasks.importtasks import importRepoScreenshot, checkZipRelease, check_for_updates
from app.utils import addNotification, get_system_user
@ -31,6 +32,7 @@ from app.utils.image import get_image_size
actions = {}
def action(title: str):
def func(f):
name = f.__name__
@ -43,13 +45,15 @@ def action(title: str):
return func
@action("Delete stuck releases")
def del_stuck_releases():
PackageRelease.query.filter(PackageRelease.task_id != None).delete()
PackageRelease.query.filter(PackageRelease.task_id.isnot(None)).delete()
db.session.commit()
return redirect(url_for("admin.admin_page"))
@action("Check releases")
@action("Check ZIP releases")
def check_releases():
releases = PackageRelease.query.filter(PackageRelease.url.like("/uploads/%")).all()
@ -65,10 +69,11 @@ def check_releases():
return redirect(url_for("todo.view_editor"))
@action("Reimport packages")
@action("Check the first release of all packages")
def reimport_packages():
tasks = []
for package in Package.query.filter(Package.state!=PackageState.DELETED).all():
for package in Package.query.filter(Package.state != PackageState.DELETED).all():
release = package.releases.first()
if release:
tasks.append(checkZipRelease.s(release.id, release.file_path))
@ -81,42 +86,46 @@ def reimport_packages():
return redirect(url_for("todo.view_editor"))
@action("Import topic list")
@action("Import forum topic list")
def import_topic_list():
task = importTopicList.delay()
return redirect(url_for("tasks.check", id=task.id, r=url_for("todo.topics")))
@action("Check all forum accounts")
def check_all_forum_accounts():
task = checkAllForumAccounts.delay()
return redirect(url_for("tasks.check", id=task.id, r=url_for("admin.admin_page")))
@action("Import screenshots")
def import_screenshots():
packages = Package.query \
.filter(Package.state!=PackageState.DELETED) \
.outerjoin(PackageScreenshot, Package.id==PackageScreenshot.package_id) \
.filter(PackageScreenshot.id==None) \
.filter(Package.state != PackageState.DELETED) \
.outerjoin(PackageScreenshot, Package.id == PackageScreenshot.package_id) \
.filter(PackageScreenshot.id.is_(None)) \
.all()
for package in packages:
importRepoScreenshot.delay(package.id)
return redirect(url_for("admin.admin_page"))
@action("Clean uploads")
@action("Remove unused uploads")
def clean_uploads():
upload_dir = app.config['UPLOAD_DIR']
upload_dir = current_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()
def get_filenames_from_column(column):
results = db.session.query(column).filter(column.isnot(None), column != "").all()
return set([os.path.basename(x[0]) for x in results])
release_urls = getURLsFromDB(PackageRelease.url)
screenshot_urls = getURLsFromDB(PackageScreenshot.url)
release_urls = get_filenames_from_column(PackageRelease.url)
screenshot_urls = get_filenames_from_column(PackageScreenshot.url)
db_urls = release_urls.union(screenshot_urls)
unreachable = existing_uploads.difference(db_urls)
@ -135,7 +144,8 @@ def clean_uploads():
return redirect(url_for("admin.admin_page"))
@action("Delete metapackages")
@action("Delete unused metapackages")
def del_meta_packages():
query = MetaPackage.query.filter(~MetaPackage.dependencies.any(), ~MetaPackage.packages.any())
count = query.count()
@ -145,6 +155,7 @@ def del_meta_packages():
flash("Deleted " + str(count) + " unused meta packages", "success")
return redirect(url_for("admin.admin_page"))
@action("Delete removed packages")
def del_removed_packages():
query = Package.query.filter_by(state=PackageState.DELETED)
@ -157,24 +168,6 @@ def del_removed_packages():
flash("Deleted {} soft deleted packages packages".format(count), "success")
return redirect(url_for("admin.admin_page"))
@action("Add update config")
def add_update_config():
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"))
@action("Run update configs")
def run_update_config():
@ -183,6 +176,7 @@ def run_update_config():
flash("Started update configs", "success")
return redirect(url_for("admin.admin_page"))
def _package_list(packages: List[str]):
# Who needs translations?
if len(packages) >= 3:
@ -192,14 +186,15 @@ def _package_list(packages: List[str]):
packages_list = " and ".join(packages)
return packages_list
@action("Send WIP package notification")
def remind_wip():
users = User.query.filter(User.packages.any(or_(Package.state==PackageState.WIP, Package.state==PackageState.CHANGES_NEEDED)))
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)) \
Package.author_id == user.id,
or_(Package.state == PackageState.WIP, Package.state==PackageState.CHANGES_NEEDED)) \
.all()
packages = [pkg[0] for pkg in packages]
@ -213,6 +208,7 @@ def remind_wip():
url_for('todo.view_user', username=user.username))
db.session.commit()
@action("Send outdated package notification")
def remind_outdated():
users = User.query.filter(User.maintained_packages.any(
@ -233,6 +229,7 @@ def remind_outdated():
db.session.commit()
@action("Import licenses from SPDX")
def import_licenses():
renames = {
@ -283,7 +280,8 @@ def import_licenses():
@action("Delete inactive users")
def delete_inactive_users():
users = User.query.filter(User.is_active==False, User.packages==None, User.forum_topics==None, User.rank==UserRank.NOT_JOINED).all()
users = User.query.filter(User.is_active == False, User.packages.is_(None), User.forum_topics.is_(None),
User.rank == UserRank.NOT_JOINED).all()
for user in users:
db.session.delete(user)
db.session.commit()
@ -313,7 +311,7 @@ def remind_video_url():
@action("Update screenshot sizes")
def remind_video_url():
def update_screenshot_sizes():
import sys
for screenshot in PackageScreenshot.query.all():

View File

@ -14,10 +14,10 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from flask import *
from flask import redirect, render_template, url_for, request, flash
from flask_login import current_user, login_user
from flask_wtf import FlaskForm
from wtforms import *
from wtforms import StringField, SubmitField
from wtforms.validators import InputRequired, Length
from app.utils import rank_required, addAuditLog, addNotification, get_system_user
from . import bp
@ -48,9 +48,10 @@ def admin_page():
else:
flash("Unknown action: " + action, "danger")
deleted_packages = Package.query.filter(Package.state==PackageState.DELETED).all()
deleted_packages = Package.query.filter(Package.state == PackageState.DELETED).all()
return render_template("admin/list.html", deleted_packages=deleted_packages, actions=actions)
class SwitchUserForm(FlaskForm):
username = StringField("Username")
submit = SubmitField("Switch")
@ -69,14 +70,13 @@ def switch_user():
else:
flash("Unable to login as user", "danger")
# Process GET or invalid POST
return render_template("admin/switch_user.html", form=form)
class SendNotificationForm(FlaskForm):
title = StringField("Title", [InputRequired(), Length(1, 300)])
url = StringField("URL", [InputRequired(), Length(1, 100)], default="/")
title = StringField("Title", [InputRequired(), Length(1, 300)])
url = StringField("URL", [InputRequired(), Length(1, 100)], default="/")
submit = SubmitField("Send")
@ -86,7 +86,7 @@ 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)
"Sent bulk notification", url_for("admin.admin_page"), None, form.title.data)
users = User.query.filter(User.rank >= UserRank.NEW_MEMBER).all()
addNotification(users, get_system_user(), NotificationType.OTHER, form.title.data, form.url.data, None)
@ -121,5 +121,10 @@ def restore():
db.session.commit()
return redirect(package.getURL("packages.view"))
deleted_packages = Package.query.filter(Package.state==PackageState.DELETED).join(Package.author).order_by(db.asc(User.username), db.asc(Package.name)).all()
return render_template("admin/restore.html", deleted_packages=deleted_packages)
deleted_packages = Package.query \
.filter(Package.state == PackageState.DELETED) \
.join(Package.author) \
.order_by(db.asc(User.username), db.asc(Package.name)) \
.all()
return render_template("admin/restore.html", deleted_packages=deleted_packages)

View File

@ -41,6 +41,6 @@ def audit():
@bp.route("/admin/audit/<int:id>/")
@rank_required(UserRank.MODERATOR)
def audit_view(id):
entry = AuditLogEntry.query.get(id)
def audit_view(id_):
entry = AuditLogEntry.query.get(id_)
return render_template("admin/audit_view.html", entry=entry)

View File

@ -14,18 +14,17 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from flask import *
from flask import request, abort, url_for, redirect, render_template, flash
from flask_login import current_user
from flask_wtf import FlaskForm
from wtforms import *
from wtforms.validators import *
from wtforms import TextAreaField, SubmitField, StringField
from wtforms.validators import InputRequired, Length
from app.markdown import render_markdown
from app.models import *
from app.tasks.emails import send_user_email
from app.utils import rank_required, addAuditLog
from . import bp
from ...models import UserRank, User, AuditSeverity
class SendEmailForm(FlaskForm):
@ -67,11 +66,11 @@ def send_bulk_email():
form = SendEmailForm(request.form)
if form.validate_on_submit():
addAuditLog(AuditSeverity.MODERATION, current_user,
"Sent bulk email", None, None, form.text.data)
"Sent bulk email", url_for("admin.admin_page"), None, form.text.data)
text = form.text.data
html = render_markdown(text)
for user in User.query.filter(User.email != None).all():
for user in User.query.filter(User.email.isnot(None)).all():
send_user_email.delay(user.email, user.locale or "en", form.subject.data, text, html)
return redirect(url_for("admin.admin_page"))

View File

@ -15,15 +15,15 @@
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from flask import *
from flask import redirect, render_template, abort, url_for, request, flash
from flask_wtf import FlaskForm
from wtforms import *
from wtforms import StringField, BooleanField, SubmitField
from wtforms.fields.html5 import URLField
from wtforms.validators import *
from wtforms.validators import InputRequired, Length, Optional
from app.models import *
from app.utils import rank_required, nonEmptyOrNone
from . import bp
from ...models import UserRank, License, db
@bp.route("/licenses/")
@ -31,11 +31,13 @@ from . import bp
def license_list():
return render_template("admin/licenses/list.html", licenses=License.query.order_by(db.asc(License.name)).all())
class LicenseForm(FlaskForm):
name = StringField("Name", [InputRequired(), Length(3,100)])
is_foss = BooleanField("Is FOSS")
url = URLField("URL", [Optional], filters=[nonEmptyOrNone])
submit = SubmitField("Save")
name = StringField("Name", [InputRequired(), Length(3, 100)])
is_foss = BooleanField("Is FOSS")
url = URLField("URL", [Optional], filters=[nonEmptyOrNone])
submit = SubmitField("Save")
@bp.route("/licenses/new/", methods=["GET", "POST"])
@bp.route("/licenses/<name>/edit/", methods=["GET", "POST"])

View File

@ -15,14 +15,14 @@
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from flask import *
from flask import redirect, render_template, abort, url_for, request
from flask_login import current_user, login_required
from flask_wtf import FlaskForm
from wtforms import *
from wtforms.validators import *
from wtforms import StringField, TextAreaField, BooleanField, SubmitField
from wtforms.validators import InputRequired, Length, Optional, Regexp
from app.models import *
from . import bp
from ...models import Permission, Tag, db
@bp.route("/tags/")
@ -40,12 +40,14 @@ def tag_list():
return render_template("admin/tags/list.html", tags=query.all())
class TagForm(FlaskForm):
title = StringField("Title", [InputRequired(), Length(3,100)])
title = StringField("Title", [InputRequired(), Length(3, 100)])
description = TextAreaField("Description", [Optional(), Length(0, 500)])
name = StringField("Name", [Optional(), Length(1, 20), Regexp("^[a-z0-9_]", 0, "Lower case letters (a-z), digits (0-9), and underscores (_) only")])
name = StringField("Name", [Optional(), Length(1, 20), Regexp("^[a-z0-9_]", 0, "Lower case letters (a-z), digits (0-9), and underscores (_) only")])
is_protected = BooleanField("Is Protected")
submit = SubmitField("Save")
submit = SubmitField("Save")
@bp.route("/tags/new/", methods=["GET", "POST"])
@bp.route("/tags/<name>/edit/", methods=["GET", "POST"])

View File

@ -15,14 +15,14 @@
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from flask import *
from flask import redirect, render_template, abort, url_for, request, flash
from flask_wtf import FlaskForm
from wtforms import *
from wtforms.validators import *
from wtforms import StringField, IntegerField, SubmitField
from wtforms.validators import InputRequired, Length
from app.models import *
from app.utils import rank_required
from . import bp
from ...models import UserRank, MinetestRelease, db
@bp.route("/versions/")
@ -30,10 +30,12 @@ from . import bp
def version_list():
return render_template("admin/versions/list.html", versions=MinetestRelease.query.order_by(db.asc(MinetestRelease.id)).all())
class VersionForm(FlaskForm):
name = StringField("Name", [InputRequired(), Length(3,100)])
name = StringField("Name", [InputRequired(), Length(3, 100)])
protocol = IntegerField("Protocol")
submit = SubmitField("Save")
submit = SubmitField("Save")
@bp.route("/versions/new/", methods=["GET", "POST"])
@bp.route("/versions/<name>/edit/", methods=["GET", "POST"])

View File

@ -15,14 +15,14 @@
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from flask import *
from flask import redirect, render_template, abort, url_for, request, flash
from flask_wtf import FlaskForm
from wtforms import *
from wtforms.validators import *
from wtforms import StringField, TextAreaField, SubmitField
from wtforms.validators import InputRequired, Length, Optional, Regexp
from app.models import *
from app.utils import rank_required
from . import bp
from ...models import UserRank, ContentWarning, db
@bp.route("/admin/warnings/")
@ -30,11 +30,14 @@ from . import bp
def warning_list():
return render_template("admin/warnings/list.html", warnings=ContentWarning.query.order_by(db.asc(ContentWarning.title)).all())
class WarningForm(FlaskForm):
title = StringField("Title", [InputRequired(), Length(3,100)])
title = StringField("Title", [InputRequired(), Length(3, 100)])
description = TextAreaField("Description", [Optional(), Length(0, 500)])
name = StringField("Name", [Optional(), Length(1, 20), Regexp("^[a-z0-9_]", 0, "Lower case letters (a-z), digits (0-9), and underscores (_) only")])
submit = SubmitField("Save")
name = StringField("Name", [Optional(), Length(1, 20),
Regexp("^[a-z0-9_]", 0, "Lower case letters (a-z), digits (0-9), and underscores (_) only")])
submit = SubmitField("Save")
@bp.route("/admin/warnings/new/", methods=["GET", "POST"])
@bp.route("/admin/warnings/<name>/edit/", methods=["GET", "POST"])