feat: 用户被多次举报合并
This commit is contained in:
@@ -61,9 +61,10 @@ def create_app(config_name='default'):
|
|||||||
app.logger.info('PT黑名单系统启动')
|
app.logger.info('PT黑名单系统启动')
|
||||||
|
|
||||||
# 注册自定义过滤器
|
# 注册自定义过滤器
|
||||||
from .filters import translate_status, translate_reason
|
from .filters import translate_status, translate_reason, translate_reasons_list
|
||||||
app.jinja_env.filters['translate_status'] = translate_status
|
app.jinja_env.filters['translate_status'] = translate_status
|
||||||
app.jinja_env.filters['translate_reason'] = translate_reason
|
app.jinja_env.filters['translate_reason'] = translate_reason
|
||||||
|
app.jinja_env.filters['translate_reasons_list'] = translate_reasons_list
|
||||||
|
|
||||||
# 注册蓝图
|
# 注册蓝图
|
||||||
from .routes import main as main_blueprint
|
from .routes import main as main_blueprint
|
||||||
|
|||||||
@@ -38,3 +38,9 @@ def translate_status(status):
|
|||||||
def translate_reason(reason):
|
def translate_reason(reason):
|
||||||
"""违规原因翻译过滤器"""
|
"""违规原因翻译过滤器"""
|
||||||
return REASON_TRANSLATIONS.get(reason, reason)
|
return REASON_TRANSLATIONS.get(reason, reason)
|
||||||
|
|
||||||
|
def translate_reasons_list(reasons):
|
||||||
|
"""违规原因列表翻译过滤器"""
|
||||||
|
if not reasons:
|
||||||
|
return []
|
||||||
|
return [REASON_TRANSLATIONS.get(r, r) for r in reasons]
|
||||||
|
|||||||
@@ -64,7 +64,6 @@ class Report(db.Model):
|
|||||||
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||||
comments = db.relationship('Comment', backref='report', lazy='dynamic', cascade='all, delete-orphan')
|
comments = db.relationship('Comment', backref='report', lazy='dynamic', cascade='all, delete-orphan')
|
||||||
evidences = db.relationship('Evidence', backref='report', lazy='dynamic', cascade='all, delete-orphan')
|
evidences = db.relationship('Evidence', backref='report', lazy='dynamic', cascade='all, delete-orphan')
|
||||||
blacklist_entry = db.relationship('Blacklist', backref='report', uselist=False)
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f'<Report {self.id}>'
|
return f'<Report {self.id}>'
|
||||||
@@ -96,7 +95,8 @@ class Blacklist(db.Model):
|
|||||||
normalized_email = db.Column(db.String(120), index=True)
|
normalized_email = db.Column(db.String(120), index=True)
|
||||||
pt_site = db.Column(db.String(100), index=True)
|
pt_site = db.Column(db.String(100), index=True)
|
||||||
uid = db.Column(db.String(50))
|
uid = db.Column(db.String(50))
|
||||||
report_id = db.Column(db.Integer, db.ForeignKey('reports.id'), unique=True)
|
report_ids = db.Column(db.JSON, default=list, nullable=False)
|
||||||
|
reason_categories = db.Column(db.JSON, default=list, nullable=False)
|
||||||
status = db.Column(db.String(16), default='active', index=True)
|
status = db.Column(db.String(16), default='active', index=True)
|
||||||
created_at = db.Column(db.DateTime, index=True, default=datetime.utcnow)
|
created_at = db.Column(db.DateTime, index=True, default=datetime.utcnow)
|
||||||
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
from flask import abort, Blueprint, render_template, request, flash, redirect, url_for, current_app
|
from flask import abort, Blueprint, render_template, request, flash, redirect, url_for, current_app
|
||||||
from sqlalchemy import or_
|
from sqlalchemy import or_
|
||||||
|
from sqlalchemy.orm.attributes import flag_modified
|
||||||
from flask_login import login_required, current_user
|
from flask_login import login_required, current_user
|
||||||
from app import db
|
from app import db
|
||||||
from app.forms import SearchForm, ReportForm, UpdateUserForm, CommentForm, RevokeForm, AppealForm, AppealMessageForm, PartnerSiteForm
|
from app.forms import SearchForm, ReportForm, UpdateUserForm, CommentForm, RevokeForm, AppealForm, AppealMessageForm, PartnerSiteForm
|
||||||
@@ -24,13 +25,12 @@ def index():
|
|||||||
search_term = form.search_term.data
|
search_term = form.search_term.data
|
||||||
normalized_email = normalize_email(search_term)
|
normalized_email = normalize_email(search_term)
|
||||||
|
|
||||||
search_result = Blacklist.query.join(Report).filter(
|
search_result = Blacklist.query.filter(
|
||||||
or_(
|
or_(
|
||||||
Blacklist.normalized_email == normalized_email,
|
Blacklist.normalized_email == normalized_email,
|
||||||
Blacklist.username == search_term
|
Blacklist.username == search_term
|
||||||
),
|
),
|
||||||
Blacklist.status == 'active',
|
Blacklist.status == 'active'
|
||||||
Report.status == 'approved'
|
|
||||||
).first()
|
).first()
|
||||||
|
|
||||||
if search_result:
|
if search_result:
|
||||||
@@ -46,6 +46,9 @@ def index():
|
|||||||
@login_required
|
@login_required
|
||||||
def create_report():
|
def create_report():
|
||||||
"""创建新举报"""
|
"""创建新举报"""
|
||||||
|
if current_user.status != 'active':
|
||||||
|
flash('您的账户尚未激活,无法提交举报。请等待管理员审核。', 'warning')
|
||||||
|
return redirect(url_for('main.index'))
|
||||||
form = ReportForm()
|
form = ReportForm()
|
||||||
active_sites = PartnerSite.query.filter_by(is_active=True).order_by(PartnerSite.name).all()
|
active_sites = PartnerSite.query.filter_by(is_active=True).order_by(PartnerSite.name).all()
|
||||||
form.reported_pt_site.choices = [(site.name, site.name) for site in active_sites]
|
form.reported_pt_site.choices = [(site.name, site.name) for site in active_sites]
|
||||||
@@ -170,17 +173,33 @@ def process_report(report_id):
|
|||||||
|
|
||||||
if action == 'confirm':
|
if action == 'confirm':
|
||||||
report.status = 'approved'
|
report.status = 'approved'
|
||||||
existing_blacklist = Blacklist.query.filter_by(report_id=report.id).first()
|
normalized = normalize_email(report.reported_email)
|
||||||
|
existing_blacklist = Blacklist.query.filter_by(normalized_email=normalized, status='active').first()
|
||||||
|
|
||||||
if not existing_blacklist:
|
if not existing_blacklist:
|
||||||
new_blacklist_entry = Blacklist(
|
new_blacklist_entry = Blacklist(
|
||||||
email=report.reported_email,
|
email=report.reported_email,
|
||||||
normalized_email=normalize_email(report.reported_email),
|
normalized_email=normalized,
|
||||||
pt_site=report.reported_pt_site,
|
pt_site=report.reported_pt_site,
|
||||||
uid=report.reported_username,
|
uid=report.reported_username,
|
||||||
report_id=report.id,
|
report_ids=[report.id],
|
||||||
|
reason_categories=[report.reason_category],
|
||||||
username=report.reported_username or None
|
username=report.reported_username or None
|
||||||
)
|
)
|
||||||
db.session.add(new_blacklist_entry)
|
db.session.add(new_blacklist_entry)
|
||||||
|
current_app.logger.info(f'举报批准: #{report.id} - {report.reported_email} by {current_user.username}')
|
||||||
|
flash('举报已批准,并已将相关信息添加到黑名单。', 'success')
|
||||||
|
else:
|
||||||
|
if report.reason_category not in existing_blacklist.reason_categories:
|
||||||
|
existing_blacklist.reason_categories.append(report.reason_category)
|
||||||
|
existing_blacklist.report_ids.append(report.id)
|
||||||
|
flag_modified(existing_blacklist, 'reason_categories')
|
||||||
|
flag_modified(existing_blacklist, 'report_ids')
|
||||||
|
current_app.logger.info(f'举报合并: #{report.id} 合并到黑名单#{existing_blacklist.id} - 新增原因: {report.reason_category}')
|
||||||
|
flash(f'举报已批准并合并到现有黑名单记录(新增违规原因:{report.reason_category})。', 'success')
|
||||||
|
else:
|
||||||
|
current_app.logger.info(f'举报批准: #{report.id} - 相同原因已存在,不合并')
|
||||||
|
flash('举报已批准。该用户已有相同违规原因的记录,未进行合并。', 'info')
|
||||||
|
|
||||||
other_pending = Report.query.filter(
|
other_pending = Report.query.filter(
|
||||||
Report.reported_email == report.reported_email,
|
Report.reported_email == report.reported_email,
|
||||||
@@ -188,8 +207,16 @@ def process_report(report_id):
|
|||||||
Report.status == 'pending'
|
Report.status == 'pending'
|
||||||
).all()
|
).all()
|
||||||
|
|
||||||
|
merged_count = 0
|
||||||
for other_report in other_pending:
|
for other_report in other_pending:
|
||||||
other_report.status = 'approved'
|
other_report.status = 'approved'
|
||||||
|
bl = existing_blacklist or new_blacklist_entry
|
||||||
|
if other_report.reason_category not in bl.reason_categories:
|
||||||
|
bl.reason_categories.append(other_report.reason_category)
|
||||||
|
bl.report_ids.append(other_report.id)
|
||||||
|
flag_modified(bl, 'reason_categories')
|
||||||
|
flag_modified(bl, 'report_ids')
|
||||||
|
merged_count += 1
|
||||||
comment = Comment(
|
comment = Comment(
|
||||||
body=f'该举报已自动批准(关联举报 #{report.id} 已确认违规)',
|
body=f'该举报已自动批准(关联举报 #{report.id} 已确认违规)',
|
||||||
report=other_report,
|
report=other_report,
|
||||||
@@ -197,14 +224,9 @@ def process_report(report_id):
|
|||||||
)
|
)
|
||||||
db.session.add(comment)
|
db.session.add(comment)
|
||||||
|
|
||||||
current_app.logger.info(f'举报批准: #{report.id} - {report.reported_email} by {current_user.username}')
|
|
||||||
if other_pending:
|
if other_pending:
|
||||||
current_app.logger.info(f'自动批准关联举报: {len(other_pending)}个')
|
current_app.logger.info(f'自动批准关联举报: {len(other_pending)}个,合并{merged_count}个不同原因')
|
||||||
flash(f'举报已批准,并已将相关信息添加到黑名单。同时自动处理了 {len(other_pending)} 个相关举报。', 'success')
|
flash(f'同时自动处理了 {len(other_pending)} 个相关举报(其中 {merged_count} 个不同原因已合并)。', 'info')
|
||||||
else:
|
|
||||||
flash('举报已批准,并已将相关信息添加到黑名单。', 'success')
|
|
||||||
else:
|
|
||||||
flash('举报状态已更新为"批准"。该举报已在黑名单中,无需重复添加。', 'info')
|
|
||||||
elif action == 'invalidate':
|
elif action == 'invalidate':
|
||||||
report.status = 'rejected'
|
report.status = 'rejected'
|
||||||
current_app.logger.info(f'举报驳回: #{report.id} by {current_user.username}')
|
current_app.logger.info(f'举报驳回: #{report.id} by {current_user.username}')
|
||||||
@@ -224,9 +246,23 @@ def revoke_report(report_id):
|
|||||||
return redirect(url_for('main.report_detail', report_id=report.id))
|
return redirect(url_for('main.report_detail', report_id=report.id))
|
||||||
form = RevokeForm()
|
form = RevokeForm()
|
||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
blacklist_entry = Blacklist.query.filter_by(report_id=report.id).first()
|
normalized = normalize_email(report.reported_email)
|
||||||
if blacklist_entry:
|
blacklist_entry = Blacklist.query.filter_by(normalized_email=normalized, status='active').first()
|
||||||
|
|
||||||
|
if blacklist_entry and report.id in blacklist_entry.report_ids:
|
||||||
|
blacklist_entry.report_ids.remove(report.id)
|
||||||
|
if report.reason_category in blacklist_entry.reason_categories:
|
||||||
|
blacklist_entry.reason_categories.remove(report.reason_category)
|
||||||
|
flag_modified(blacklist_entry, 'report_ids')
|
||||||
|
flag_modified(blacklist_entry, 'reason_categories')
|
||||||
|
|
||||||
|
if len(blacklist_entry.report_ids) == 0:
|
||||||
db.session.delete(blacklist_entry)
|
db.session.delete(blacklist_entry)
|
||||||
|
current_app.logger.warning(f'举报撤销: #{report.id} - 黑名单记录已删除')
|
||||||
|
flash('举报已成功撤销,并已从黑名单中移除。', 'success')
|
||||||
|
else:
|
||||||
|
current_app.logger.warning(f'举报撤销: #{report.id} - 从黑名单中移除该举报')
|
||||||
|
flash(f'举报已成功撤销,已从黑名单中移除该违规原因(剩余 {len(blacklist_entry.report_ids)} 个举报)。', 'success')
|
||||||
|
|
||||||
report.status = 'revoked'
|
report.status = 'revoked'
|
||||||
revocation_comment = Comment(
|
revocation_comment = Comment(
|
||||||
@@ -237,7 +273,6 @@ def revoke_report(report_id):
|
|||||||
db.session.add(revocation_comment)
|
db.session.add(revocation_comment)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
current_app.logger.warning(f'举报撤销: #{report.id} by {current_user.username} - 理由: {form.reason.data[:50]}')
|
current_app.logger.warning(f'举报撤销: #{report.id} by {current_user.username} - 理由: {form.reason.data[:50]}')
|
||||||
flash('举报已成功撤销,并已从黑名单中移除。', 'success')
|
|
||||||
else:
|
else:
|
||||||
flash('撤销失败:' + ' '.join(form.reason.errors), 'danger')
|
flash('撤销失败:' + ' '.join(form.reason.errors), 'danger')
|
||||||
return redirect(url_for('main.report_detail', report_id=report.id))
|
return redirect(url_for('main.report_detail', report_id=report.id))
|
||||||
@@ -398,9 +433,11 @@ def decide_appeal(appeal_id):
|
|||||||
blacklist_entry = appeal.blacklist_entry
|
blacklist_entry = appeal.blacklist_entry
|
||||||
blacklist_entry.status = 'revoked'
|
blacklist_entry.status = 'revoked'
|
||||||
appeal.status = 'approved'
|
appeal.status = 'approved'
|
||||||
if blacklist_entry.report:
|
for report_id in blacklist_entry.report_ids:
|
||||||
blacklist_entry.report.status = 'overturned'
|
report = Report.query.get(report_id)
|
||||||
db.session.add(blacklist_entry.report)
|
if report:
|
||||||
|
report.status = 'overturned'
|
||||||
|
db.session.add(report)
|
||||||
db.session.add(blacklist_entry)
|
db.session.add(blacklist_entry)
|
||||||
db.session.add(appeal)
|
db.session.add(appeal)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|||||||
@@ -24,6 +24,18 @@
|
|||||||
{% if appeal.blacklist_entry %}
|
{% if appeal.blacklist_entry %}
|
||||||
<p class="mb-0"><strong>站点:</strong> {{ appeal.blacklist_entry.pt_site }}</p>
|
<p class="mb-0"><strong>站点:</strong> {{ appeal.blacklist_entry.pt_site }}</p>
|
||||||
<p class="mb-0"><strong>UID:</strong> {{ appeal.blacklist_entry.uid }}</p>
|
<p class="mb-0"><strong>UID:</strong> {{ appeal.blacklist_entry.uid }}</p>
|
||||||
|
<p class="mb-1"><strong>违规原因:</strong></p>
|
||||||
|
{% if appeal.blacklist_entry.reason_categories and appeal.blacklist_entry.reason_categories|length > 0 %}
|
||||||
|
<ul class="mb-0">
|
||||||
|
{% for reason in appeal.blacklist_entry.reason_categories %}
|
||||||
|
<li>{{ reason | translate_reason }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% elif appeal.blacklist_entry.report %}
|
||||||
|
<p class="mb-0">{{ appeal.blacklist_entry.report.reason_category | translate_reason }}</p>
|
||||||
|
{% else %}
|
||||||
|
<p class="mb-0 text-muted">未知</p>
|
||||||
|
{% endif %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<p class="mb-0 text-muted">黑名单记录已删除</p>
|
<p class="mb-0 text-muted">黑名单记录已删除</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -14,9 +14,20 @@
|
|||||||
<li class="list-group-item"><strong>UID:</strong> {{ entry.uid }}</li>
|
<li class="list-group-item"><strong>UID:</strong> {{ entry.uid }}</li>
|
||||||
<li class="list-group-item"><strong>邮箱:</strong> {{ entry.email }}</li>
|
<li class="list-group-item"><strong>邮箱:</strong> {{ entry.email }}</li>
|
||||||
<li class="list-group-item"><strong>站点:</strong> {{ entry.pt_site }}</li>
|
<li class="list-group-item"><strong>站点:</strong> {{ entry.pt_site }}</li>
|
||||||
{% if entry.report %}
|
<li class="list-group-item">
|
||||||
<li class="list-group-item"><strong>违规原因:</strong> {{ entry.report.reason_category | translate_reason }}</li>
|
<strong>违规原因:</strong>
|
||||||
|
{% if entry.reason_categories and entry.reason_categories|length > 0 %}
|
||||||
|
<ul class="mb-0 mt-1">
|
||||||
|
{% for reason in entry.reason_categories %}
|
||||||
|
<li>{{ reason | translate_reason }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% elif entry.report %}
|
||||||
|
{{ entry.report.reason_category | translate_reason }}
|
||||||
|
{% else %}
|
||||||
|
未知
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<form method="POST" novalidate>
|
<form method="POST" novalidate>
|
||||||
|
|||||||
@@ -45,9 +45,18 @@
|
|||||||
</div>
|
</div>
|
||||||
<ul class="list-group list-group-flush">
|
<ul class="list-group list-group-flush">
|
||||||
<li class="list-group-item"><strong>违规站点:</strong> {{ search_result.pt_site }}</li>
|
<li class="list-group-item"><strong>违规站点:</strong> {{ search_result.pt_site }}</li>
|
||||||
{% if search_result.report %}
|
<li class="list-group-item">
|
||||||
<li class="list-group-item"><strong>违规原因:</strong> {{ search_result.report.reason_category | translate_reason }}</li>
|
<strong>违规原因:</strong>
|
||||||
|
{% if search_result.reason_categories and search_result.reason_categories|length > 0 %}
|
||||||
|
<ul class="mb-0 mt-1">
|
||||||
|
{% for reason in search_result.reason_categories %}
|
||||||
|
<li>{{ reason | translate_reason }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% else %}
|
||||||
|
未知
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
</li>
|
||||||
<li class="list-group-item"><strong>记录时间:</strong> {{ search_result.created_at.strftime('%Y-%m-%d') }}</li>
|
<li class="list-group-item"><strong>记录时间:</strong> {{ search_result.created_at.strftime('%Y-%m-%d') }}</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p class="text-muted small mt-3">为保护隐私,仅展示必要的脱敏信息。具体违规描述不对外公开。</p>
|
<p class="text-muted small mt-3">为保护隐私,仅展示必要的脱敏信息。具体违规描述不对外公开。</p>
|
||||||
|
|||||||
Reference in New Issue
Block a user