From 8d1268bd1994c07e8f76e1060e35352966bdf086 Mon Sep 17 00:00:00 2001 From: rubenwardy Date: Tue, 26 Aug 2025 15:46:41 +0100 Subject: [PATCH] Add report categories --- app/blueprints/report/__init__.py | 12 ++++-- app/models/__init__.py | 56 +++++++++++++++++++++++++++- app/templates/report/report.html | 1 + app/templates/report/view.html | 6 +++ app/templates/users/account.html | 2 +- migrations/versions/1e08d7e4c15d_.py | 28 ++++++++++++++ 6 files changed, 99 insertions(+), 6 deletions(-) create mode 100644 migrations/versions/1e08d7e4c15d_.py diff --git a/app/blueprints/report/__init__.py b/app/blueprints/report/__init__.py index e2765c51..f3634430 100644 --- a/app/blueprints/report/__init__.py +++ b/app/blueprints/report/__init__.py @@ -19,10 +19,10 @@ from flask_babel import lazy_gettext from flask_login import current_user from flask_wtf import FlaskForm from werkzeug.utils import redirect -from wtforms import TextAreaField, SubmitField, URLField, StringField, RadioField +from wtforms import TextAreaField, SubmitField, URLField, StringField, RadioField, SelectField from wtforms.validators import InputRequired, Length, Optional -from app.models import User, UserRank, Report, db, AuditSeverity, Thread +from app.models import User, UserRank, Report, db, AuditSeverity, Thread, ReportCategory from app.tasks.webhooktasks import post_discord_webhook from app.utils import is_no, abs_url_samesite, normalize_line_endings, rank_required, add_audit_log, abs_url_for, \ add_replies, random_string @@ -31,6 +31,8 @@ bp = Blueprint("report", __name__) class ReportForm(FlaskForm): + category = SelectField(lazy_gettext("Category"), [InputRequired()], choices=ReportCategory.choices(with_none=True), coerce=ReportCategory.coerce) + url = URLField(lazy_gettext("URL"), [Optional()]) title = StringField(lazy_gettext("Subject / Title"), [InputRequired(), Length(10, 300)]) message = TextAreaField(lazy_gettext("Message"), [Optional(), Length(0, 10000)], filters=[normalize_line_endings]) @@ -50,8 +52,12 @@ def report(): form = ReportForm(formdata=request.form) if current_user.is_authenticated else None if form and request.method == "GET": + try: + form.category.data = ReportCategory.coerce(request.args.get("category")) + except KeyError: + pass form.url.data = url - form.message.data = request.args.get("message", "") + form.title.data = request.args.get("title", "") if form and form.validate_on_submit(): report = Report() diff --git a/app/models/__init__.py b/app/models/__init__.py index 0946ef63..cb4596e4 100644 --- a/app/models/__init__.py +++ b/app/models/__init__.py @@ -13,8 +13,7 @@ # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . - - +from flask_babel import LazyString from flask_migrate import Migrate from flask_sqlalchemy import SQLAlchemy from sqlalchemy_searchable import make_searchable @@ -130,6 +129,58 @@ class AuditLogEntry(db.Model): raise Exception("Permission {} is not related to audit log entries".format(perm.name)) +class ReportCategory(enum.Enum): + ACCOUNT_DELETION = "account_deletion" + COPYRIGHT = "copyright" + USER_CONDUCT = "user_conduct" + ILLEGAL_HARMFUL = "illegal_harmful" + APPEAL = "appeal" + OTHER = "other" + + def __str__(self): + return self.name + + @property + def title(self) -> LazyString: + if self == ReportCategory.ACCOUNT_DELETION: + return lazy_gettext("Account deletion") + elif self == ReportCategory.COPYRIGHT: + return lazy_gettext("Copyright infringement / DMCA") + elif self == ReportCategory.USER_CONDUCT: + return lazy_gettext("User behaviour, bullying, or abuse") + elif self == ReportCategory.ILLEGAL_HARMFUL: + return lazy_gettext("Illegal or harmful content") + elif self == ReportCategory.APPEAL: + return lazy_gettext("Appeal") + elif self == ReportCategory.OTHER: + return lazy_gettext("Other") + else: + raise Exception("Unknown report category") + + @classmethod + def get(cls, name): + try: + return ReportCategory[name.upper()] + except KeyError: + return None + + @classmethod + def choices(cls, with_none): + ret = [(choice, choice.title) for choice in cls] + + if with_none: + ret.insert(0, (None, "")) + + return ret + + @classmethod + def coerce(cls, item): + if item is None or (isinstance(item, str) and item.upper() == "NONE"): + return None + return item if type(item) == ReportCategory else ReportCategory[item.upper()] + + + class Report(db.Model): id = db.Column(db.String(24), primary_key=True) @@ -141,6 +192,7 @@ class Report(db.Model): thread_id = db.Column(db.Integer, db.ForeignKey("thread.id"), nullable=True) thread = db.relationship("Thread", foreign_keys=[thread_id]) + category = db.Column(db.Enum(ReportCategory), nullable=False) url = db.Column(db.String, nullable=True) title = db.Column(db.Unicode(300), nullable=False) message = db.Column(db.UnicodeText, nullable=False) diff --git a/app/templates/report/report.html b/app/templates/report/report.html index 1e627e0a..5bd2e64a 100644 --- a/app/templates/report/report.html +++ b/app/templates/report/report.html @@ -28,6 +28,7 @@
{{ form.hidden_tag() }} + {{ render_field(form.category) }} {{ render_field(form.url) }} {{ render_field(form.title) }} {{ render_field(form.message, class_="m-0", fieldclass="form-control markdown", data_enter_submit="1") }} diff --git a/app/templates/report/view.html b/app/templates/report/view.html index 56d9bd28..b49e995d 100644 --- a/app/templates/report/view.html +++ b/app/templates/report/view.html @@ -38,6 +38,12 @@