Add report attachments
This commit is contained in:
@@ -23,7 +23,7 @@ from flask import redirect, url_for, flash, current_app
|
||||
from sqlalchemy import or_, and_
|
||||
|
||||
from app.models import PackageRelease, db, Package, PackageState, PackageScreenshot, MetaPackage, User, \
|
||||
NotificationType, PackageUpdateConfig, License, UserRank, PackageType, Thread, AuditLogEntry
|
||||
NotificationType, PackageUpdateConfig, License, UserRank, PackageType, Thread, AuditLogEntry, ReportAttachment
|
||||
from app.tasks.emails import send_pending_digests
|
||||
from app.tasks.forumtasks import import_topic_list, check_all_forum_accounts
|
||||
from app.tasks.importtasks import import_repo_screenshot, check_zip_release, check_for_updates, update_all_game_support, \
|
||||
@@ -68,9 +68,10 @@ def clean_uploads():
|
||||
|
||||
release_urls = get_filenames_from_column(PackageRelease.url)
|
||||
screenshot_urls = get_filenames_from_column(PackageScreenshot.url)
|
||||
attachment_urls = get_filenames_from_column(ReportAttachment.url)
|
||||
pp_urls = get_filenames_from_column(User.profile_pic)
|
||||
|
||||
db_urls = release_urls.union(screenshot_urls).union(pp_urls)
|
||||
db_urls = release_urls.union(screenshot_urls).union(pp_urls).union(attachment_urls)
|
||||
unreachable = existing_uploads.difference(db_urls)
|
||||
|
||||
import sys
|
||||
|
||||
@@ -19,8 +19,9 @@ from flask import render_template, request, redirect, flash, url_for, abort
|
||||
from flask_babel import lazy_gettext, gettext
|
||||
from flask_login import login_required, current_user
|
||||
from flask_wtf import FlaskForm
|
||||
from flask_wtf.file import FileRequired
|
||||
from wtforms import StringField, SubmitField, BooleanField, FileField
|
||||
from wtforms.validators import InputRequired, Length, DataRequired, Optional
|
||||
from wtforms.validators import Length, DataRequired, Optional
|
||||
from wtforms_sqlalchemy.fields import QuerySelectField
|
||||
|
||||
from app.logic.LogicError import LogicError
|
||||
@@ -32,7 +33,7 @@ from app.utils import is_package_page
|
||||
|
||||
class CreateScreenshotForm(FlaskForm):
|
||||
title = StringField(lazy_gettext("Title/Caption"), [Optional(), Length(-1, 100)])
|
||||
file_upload = FileField(lazy_gettext("File Upload"), [InputRequired()])
|
||||
file_upload = FileField(lazy_gettext("File Upload"), [FileRequired()])
|
||||
submit = SubmitField(lazy_gettext("Save"))
|
||||
|
||||
|
||||
|
||||
@@ -19,10 +19,11 @@ 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, SelectField
|
||||
from wtforms import TextAreaField, SubmitField, URLField, StringField, SelectField, FileField
|
||||
from wtforms.validators import InputRequired, Length, Optional, DataRequired
|
||||
|
||||
from app.models import User, UserRank, Report, db, AuditSeverity, ReportCategory, Thread, Permission
|
||||
from app.logic.uploads import upload_file
|
||||
from app.models import User, UserRank, Report, db, AuditSeverity, ReportCategory, Thread, Permission, ReportAttachment
|
||||
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,
|
||||
random_string, add_replies)
|
||||
@@ -36,6 +37,7 @@ class ReportForm(FlaskForm):
|
||||
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])
|
||||
file_upload = FileField(lazy_gettext("Image Upload"), [Optional()])
|
||||
submit = SubmitField(lazy_gettext("Report"))
|
||||
|
||||
|
||||
@@ -50,7 +52,7 @@ def report():
|
||||
|
||||
url = abs_url_samesite(url)
|
||||
|
||||
form = ReportForm(formdata=request.form) if current_user.is_authenticated else None
|
||||
form = ReportForm() if current_user.is_authenticated else None
|
||||
if form and request.method == "GET":
|
||||
try:
|
||||
form.category.data = ReportCategory.coerce(request.args.get("category"))
|
||||
@@ -84,6 +86,13 @@ def report():
|
||||
db.session.add(report)
|
||||
db.session.flush()
|
||||
|
||||
if form.file_upload.data:
|
||||
atmt = ReportAttachment()
|
||||
report.attachments.add(atmt)
|
||||
uploaded_url, _ = upload_file(form.file_upload.data, "image", lazy_gettext("a PNG, JPEG, or WebP image file"))
|
||||
atmt.url = uploaded_url
|
||||
db.session.add(atmt)
|
||||
|
||||
if current_user.is_authenticated:
|
||||
add_audit_log(AuditSeverity.USER, current_user, f"New report: {report.title}",
|
||||
url_for("report.view", rid=report.id))
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
import imghdr
|
||||
import os
|
||||
|
||||
from flask_babel import lazy_gettext
|
||||
from flask_babel import lazy_gettext, LazyString
|
||||
|
||||
from app import app
|
||||
from app.logic.LogicError import LogicError
|
||||
@@ -35,7 +35,7 @@ def is_allowed_image(data):
|
||||
return imghdr.what(None, data) in ALLOWED_IMAGES
|
||||
|
||||
|
||||
def upload_file(file, file_type, file_type_desc):
|
||||
def upload_file(file, file_type: str, file_type_desc: LazyString | str, length: int=10):
|
||||
if not file or file is None or file.filename == "":
|
||||
raise LogicError(400, "Expected file")
|
||||
|
||||
@@ -62,7 +62,7 @@ def upload_file(file, file_type, file_type_desc):
|
||||
|
||||
file.stream.seek(0)
|
||||
|
||||
filename = random_string(10) + "." + ext
|
||||
filename = random_string(length) + "." + ext
|
||||
filepath = os.path.join(app.config["UPLOAD_DIR"], filename)
|
||||
file.save(filepath)
|
||||
|
||||
|
||||
@@ -183,7 +183,6 @@ class ReportCategory(enum.Enum):
|
||||
return item if type(item) == ReportCategory else ReportCategory[item.upper()]
|
||||
|
||||
|
||||
|
||||
class Report(db.Model):
|
||||
id = db.Column(db.String(24), primary_key=True)
|
||||
|
||||
@@ -202,6 +201,7 @@ class Report(db.Model):
|
||||
|
||||
is_resolved = db.Column(db.Boolean, nullable=False, default=False)
|
||||
|
||||
attachments = db.relationship("ReportAttachment", back_populates="report", lazy="dynamic", cascade="all, delete, delete-orphan")
|
||||
|
||||
def check_perm(self, user, perm):
|
||||
if type(perm) == str:
|
||||
@@ -217,6 +217,17 @@ class Report(db.Model):
|
||||
raise Exception("Permission {} is not related to reports".format(perm.name))
|
||||
|
||||
|
||||
class ReportAttachment(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
|
||||
created_at = db.Column(db.DateTime, nullable=False, default=datetime.datetime.utcnow)
|
||||
|
||||
report_id = db.Column(db.String(24), db.ForeignKey("report.id"), nullable=False)
|
||||
report = db.relationship("Report", foreign_keys=[report_id], back_populates="attachments")
|
||||
|
||||
url = db.Column(db.String(100), nullable=False)
|
||||
|
||||
|
||||
REPO_BLACKLIST = [".zip", "mediafire.com", "dropbox.com", "weebly.com",
|
||||
"minetest.net", "luanti.org", "dropboxusercontent.com", "4shared.com",
|
||||
"digitalaudioconcepts.com", "hg.intevation.org", "www.wtfpl.net",
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
pattern="[A-Za-z0-9/._-]+") }}
|
||||
{% endif %}
|
||||
|
||||
{{ render_field(form.file_upload, fieldclass="form-control-file", class_="mt-3", accept=".zip") }}
|
||||
{{ render_field(form.file_upload, class_="mt-3", accept=".zip") }}
|
||||
|
||||
<p>
|
||||
{{ _("Take a look at the <a href='/help/package_config/'>Package Configuration and Releases Guide</a> for
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
{{ form.hidden_tag() }}
|
||||
|
||||
{{ render_field(form.title) }}
|
||||
{{ render_field(form.file_upload, fieldclass="form-control-file", accept="image/png,image/jpeg,image/webp") }}
|
||||
{{ render_field(form.file_upload, accept="image/png,image/jpeg,image/webp") }}
|
||||
{{ render_submit_field(form.submit) }}
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
{{ form.hidden_tag() }}
|
||||
|
||||
{{ render_field(form.title) }}
|
||||
{{ render_field(form.file_upload, fieldclass="form-control-file", accept="image/png,image/jpeg,image/webp") }}
|
||||
{{ render_field(form.file_upload, accept="image/png,image/jpeg,image/webp") }}
|
||||
{{ render_checkbox_field
|
||||
{{ render_submit_field(form.submit) }}
|
||||
</form>
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
{{ render_field(form.url) }}
|
||||
{{ render_field(form.title) }}
|
||||
{{ render_field(form.message, class_="m-0", fieldclass="form-control markdown", data_enter_submit="1") }}
|
||||
{{ render_field(form.file_upload, accept="image/png,image/jpeg,image/webp", hint=_("Optional, usually not required")) }}
|
||||
{{ render_submit_field(form.submit) }}
|
||||
<p class="alert alert-info mt-5">
|
||||
{{ _("Found a bug? Please report on the package's issue tracker or in a thread instead.") }}
|
||||
|
||||
@@ -77,6 +77,17 @@
|
||||
</aside>
|
||||
</article>
|
||||
|
||||
{% if report.attachments %}
|
||||
<article>
|
||||
<h2>Attachments</h2>
|
||||
<ul>
|
||||
{% for attachment in report.attachments %}
|
||||
<li><a href="{{ attachment.url }}">{{ attachment.url }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</article>
|
||||
{% endif %}
|
||||
|
||||
<article>
|
||||
<h2>{% if report.is_resolved %}Reopen report{% else %}Close report{% endif %}</h2>
|
||||
<form method="POST" action="">
|
||||
|
||||
31
migrations/versions/242fd82077bb_.py
Normal file
31
migrations/versions/242fd82077bb_.py
Normal file
@@ -0,0 +1,31 @@
|
||||
"""empty message
|
||||
|
||||
Revision ID: 242fd82077bb
|
||||
Revises: 1acc6e90bbac
|
||||
Create Date: 2025-09-01 10:00:39.263576
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '242fd82077bb'
|
||||
down_revision = '1acc6e90bbac'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.create_table('report_attachment',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('created_at', sa.DateTime(), nullable=False),
|
||||
sa.Column('report_id', sa.String(length=24), nullable=False),
|
||||
sa.Column('url', sa.String(length=100), nullable=False),
|
||||
sa.ForeignKeyConstraint(['report_id'], ['report.id'], ),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
|
||||
|
||||
def downgrade():
|
||||
op.drop_table('report_attachment')
|
||||
Reference in New Issue
Block a user