mirror of
https://gitlab.com/SIGBUS/nyaa.git
synced 2024-12-22 15:40:00 +00:00
Expose soft delete to users and allow reuploading of deleted torrents. (#331)
Add banning torrents for moderators which disallows reuploading. New delete UI.
This commit is contained in:
parent
81d8b0f86b
commit
e728ca1818
|
@ -45,6 +45,11 @@ def _replace_utf8_values(dict_or_list):
|
||||||
def handle_torrent_upload(upload_form, uploading_user=None, fromAPI=False):
|
def handle_torrent_upload(upload_form, uploading_user=None, fromAPI=False):
|
||||||
torrent_data = upload_form.torrent_file.parsed_data
|
torrent_data = upload_form.torrent_file.parsed_data
|
||||||
|
|
||||||
|
# Delete exisiting torrent which is marked as deleted
|
||||||
|
if torrent_data.db_id is not None:
|
||||||
|
models.Torrent.query.filter_by(id=torrent_data.db_id).delete()
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
# The torrent has been validated and is safe to access with ['foo'] etc - all relevant
|
# The torrent has been validated and is safe to access with ['foo'] etc - all relevant
|
||||||
# keys and values have been checked for (see UploadForm in forms.py for details)
|
# keys and values have been checked for (see UploadForm in forms.py for details)
|
||||||
info_dict = torrent_data.torrent_dict['info']
|
info_dict = torrent_data.torrent_dict['info']
|
||||||
|
@ -62,7 +67,8 @@ def handle_torrent_upload(upload_form, uploading_user=None, fromAPI=False):
|
||||||
# In case no encoding, assume UTF-8.
|
# In case no encoding, assume UTF-8.
|
||||||
torrent_encoding = torrent_data.torrent_dict.get('encoding', b'utf-8').decode('utf-8')
|
torrent_encoding = torrent_data.torrent_dict.get('encoding', b'utf-8').decode('utf-8')
|
||||||
|
|
||||||
torrent = models.Torrent(info_hash=torrent_data.info_hash,
|
torrent = models.Torrent(id=torrent_data.db_id,
|
||||||
|
info_hash=torrent_data.info_hash,
|
||||||
display_name=display_name,
|
display_name=display_name,
|
||||||
torrent_name=torrent_data.filename,
|
torrent_name=torrent_data.filename,
|
||||||
information=information,
|
information=information,
|
||||||
|
|
|
@ -8,7 +8,7 @@ from flask_wtf.file import FileField, FileRequired
|
||||||
from flask_wtf.recaptcha import RecaptchaField
|
from flask_wtf.recaptcha import RecaptchaField
|
||||||
from flask_wtf.recaptcha.validators import Recaptcha as RecaptchaValidator
|
from flask_wtf.recaptcha.validators import Recaptcha as RecaptchaValidator
|
||||||
from wtforms import (BooleanField, HiddenField, PasswordField, SelectField, StringField,
|
from wtforms import (BooleanField, HiddenField, PasswordField, SelectField, StringField,
|
||||||
TextAreaField)
|
SubmitField, TextAreaField)
|
||||||
from wtforms.validators import (DataRequired, Email, EqualTo, Length, Optional, Regexp,
|
from wtforms.validators import (DataRequired, Email, EqualTo, Length, Optional, Regexp,
|
||||||
StopValidation, ValidationError)
|
StopValidation, ValidationError)
|
||||||
from wtforms.widgets import Select as SelectWidget # For DisabledSelectField
|
from wtforms.widgets import Select as SelectWidget # For DisabledSelectField
|
||||||
|
@ -186,6 +186,13 @@ class EditForm(FlaskForm):
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
||||||
|
class DeleteForm(FlaskForm):
|
||||||
|
delete = SubmitField("Delete")
|
||||||
|
ban = SubmitField("Delete & Ban")
|
||||||
|
undelete = SubmitField("Undelete")
|
||||||
|
unban = SubmitField("Unban")
|
||||||
|
|
||||||
|
|
||||||
class UploadForm(FlaskForm):
|
class UploadForm(FlaskForm):
|
||||||
torrent_file = FileField('Torrent file', [
|
torrent_file = FileField('Torrent file', [
|
||||||
FileRequired()
|
FileRequired()
|
||||||
|
@ -281,14 +288,18 @@ class UploadForm(FlaskForm):
|
||||||
|
|
||||||
# Check if the info_hash exists already in the database
|
# Check if the info_hash exists already in the database
|
||||||
existing_torrent = models.Torrent.by_info_hash(info_hash)
|
existing_torrent = models.Torrent.by_info_hash(info_hash)
|
||||||
if existing_torrent:
|
existing_torrent_id = existing_torrent.id if existing_torrent else None
|
||||||
raise ValidationError('That torrent already exists (#{})'.format(existing_torrent.id))
|
if existing_torrent and not existing_torrent.deleted:
|
||||||
|
raise ValidationError('This torrent already exists (#{})'.format(existing_torrent.id))
|
||||||
|
if existing_torrent and existing_torrent.banned:
|
||||||
|
raise ValidationError('This torrent is banned'.format(existing_torrent.id))
|
||||||
|
|
||||||
# Torrent is legit, pass original filename and dict along
|
# Torrent is legit, pass original filename and dict along
|
||||||
field.parsed_data = TorrentFileData(filename=os.path.basename(field.data.filename),
|
field.parsed_data = TorrentFileData(filename=os.path.basename(field.data.filename),
|
||||||
torrent_dict=torrent_dict,
|
torrent_dict=torrent_dict,
|
||||||
info_hash=info_hash,
|
info_hash=info_hash,
|
||||||
bencoded_info_dict=bencoded_info_dict)
|
bencoded_info_dict=bencoded_info_dict,
|
||||||
|
db_id=existing_torrent_id)
|
||||||
|
|
||||||
|
|
||||||
class UserForm(FlaskForm):
|
class UserForm(FlaskForm):
|
||||||
|
|
|
@ -98,6 +98,7 @@ class TorrentFlags(IntEnum):
|
||||||
REMAKE = 8
|
REMAKE = 8
|
||||||
COMPLETE = 16
|
COMPLETE = 16
|
||||||
DELETED = 32
|
DELETED = 32
|
||||||
|
BANNED = 64
|
||||||
|
|
||||||
|
|
||||||
class TorrentBase(DeclarativeHelperBase):
|
class TorrentBase(DeclarativeHelperBase):
|
||||||
|
@ -250,6 +251,7 @@ class TorrentBase(DeclarativeHelperBase):
|
||||||
anonymous = FlagProperty(TorrentFlags.ANONYMOUS)
|
anonymous = FlagProperty(TorrentFlags.ANONYMOUS)
|
||||||
hidden = FlagProperty(TorrentFlags.HIDDEN)
|
hidden = FlagProperty(TorrentFlags.HIDDEN)
|
||||||
deleted = FlagProperty(TorrentFlags.DELETED)
|
deleted = FlagProperty(TorrentFlags.DELETED)
|
||||||
|
banned = FlagProperty(TorrentFlags.BANNED)
|
||||||
trusted = FlagProperty(TorrentFlags.TRUSTED)
|
trusted = FlagProperty(TorrentFlags.TRUSTED)
|
||||||
remake = FlagProperty(TorrentFlags.REMAKE)
|
remake = FlagProperty(TorrentFlags.REMAKE)
|
||||||
complete = FlagProperty(TorrentFlags.COMPLETE)
|
complete = FlagProperty(TorrentFlags.COMPLETE)
|
||||||
|
|
|
@ -15,7 +15,6 @@
|
||||||
<form method="POST" enctype="multipart/form-data">
|
<form method="POST" enctype="multipart/form-data">
|
||||||
{{ form.csrf_token }}
|
{{ form.csrf_token }}
|
||||||
|
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
{{ render_field(form.display_name, class_='form-control', placeholder='Display name') }}
|
{{ render_field(form.display_name, class_='form-control', placeholder='Display name') }}
|
||||||
|
@ -46,14 +45,6 @@
|
||||||
<span class="glyphicon glyphicon-unchecked"></span>
|
<span class="glyphicon glyphicon-unchecked"></span>
|
||||||
Hidden
|
Hidden
|
||||||
</label>
|
</label>
|
||||||
{% if g.user.is_moderator %}
|
|
||||||
<label class="btn btn-primary {% if torrent.deleted %}active{% endif %}">
|
|
||||||
{{ form.is_deleted }}
|
|
||||||
<span class="glyphicon glyphicon-check"></span>
|
|
||||||
<span class="glyphicon glyphicon-unchecked"></span>
|
|
||||||
Deleted
|
|
||||||
</label>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="hidden-xl"><br></div>
|
<div class="hidden-xl"><br></div>
|
||||||
<div class="btn-group" data-toggle="buttons">
|
<div class="btn-group" data-toggle="buttons">
|
||||||
|
@ -93,4 +84,75 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-5"">
|
||||||
|
<div class="panel panel-danger">
|
||||||
|
<div class="panel-heading">
|
||||||
|
<h3 class="panel-title">Danger Zone</h3>
|
||||||
|
</div>
|
||||||
|
<div class="panel-body">
|
||||||
|
<form method="POST" action="{{ url_for('torrents.delete', torrent_id=torrent.id) }}">
|
||||||
|
{{ delete_form.csrf_token }}
|
||||||
|
|
||||||
|
{% if not torrent.deleted %}
|
||||||
|
<p class="lead">
|
||||||
|
Delete torrent.
|
||||||
|
{{ delete_form.delete(class="btn btn-danger pull-right") }}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Deleted torrents are retained for backup purposes.<br>
|
||||||
|
You (or someone else) will be able to reupload the torrent.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{% if g.user.is_moderator %}
|
||||||
|
<hr>
|
||||||
|
<p class="lead">
|
||||||
|
Delete and ban torrent.
|
||||||
|
{{ delete_form.ban(class="btn btn-danger pull-right") }}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Soft deletes the torrent and disallows reuploading it.
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
{% else %}
|
||||||
|
<p>This torrent is <strong>deleted</strong>{% if torrent.banned %} and <strong>banned</strong>{% endif %}.</p>
|
||||||
|
<p class="lead">
|
||||||
|
Undelete{% if torrent.banned %} and unban{% endif %} torrent.
|
||||||
|
{{ delete_form.undelete(class="btn btn-info pull-right") }}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Undeletes{% if torrent.banned %} and unbans{% endif %} this torrent.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{% if torrent.banned %}
|
||||||
|
<hr>
|
||||||
|
<p class="lead">
|
||||||
|
Unban torrent.
|
||||||
|
{{ delete_form.unban(class="btn btn-info pull-right") }}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Unbans torrent without undeleting it.<br>
|
||||||
|
Allows reuploading this torrent again.
|
||||||
|
</p>
|
||||||
|
{% else%}
|
||||||
|
<hr>
|
||||||
|
<p class="lead">
|
||||||
|
Ban torrent.
|
||||||
|
{{ delete_form.ban(value="Ban", class="btn btn-danger pull-right") }}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Bans the torrent.<br>
|
||||||
|
Disallows reuploading this torrent.
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -80,6 +80,7 @@ def view_torrent(torrent_id):
|
||||||
def edit_torrent(torrent_id):
|
def edit_torrent(torrent_id):
|
||||||
torrent = models.Torrent.by_id(torrent_id)
|
torrent = models.Torrent.by_id(torrent_id)
|
||||||
form = forms.EditForm(flask.request.form)
|
form = forms.EditForm(flask.request.form)
|
||||||
|
delete_form = forms.DeleteForm()
|
||||||
form.category.choices = _create_upload_category_choices()
|
form.category.choices = _create_upload_category_choices()
|
||||||
|
|
||||||
editor = flask.g.user
|
editor = flask.g.user
|
||||||
|
@ -147,9 +148,73 @@ def edit_torrent(torrent_id):
|
||||||
|
|
||||||
return flask.render_template('edit.html',
|
return flask.render_template('edit.html',
|
||||||
form=form,
|
form=form,
|
||||||
|
delete_form=delete_form,
|
||||||
torrent=torrent)
|
torrent=torrent)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/view/<int:torrent_id>/delete', endpoint='delete', methods=['POST'])
|
||||||
|
def delete_torrent(torrent_id):
|
||||||
|
torrent = models.Torrent.by_id(torrent_id)
|
||||||
|
form = forms.DeleteForm(flask.request.form)
|
||||||
|
|
||||||
|
editor = flask.g.user
|
||||||
|
|
||||||
|
if not torrent:
|
||||||
|
flask.abort(404)
|
||||||
|
|
||||||
|
# Only allow admins edit deleted torrents
|
||||||
|
if torrent.deleted and not (editor and editor.is_moderator):
|
||||||
|
flask.abort(404)
|
||||||
|
|
||||||
|
# Only allow torrent owners or admins edit torrents
|
||||||
|
if not editor or not (editor is torrent.user or editor.is_moderator):
|
||||||
|
flask.abort(403)
|
||||||
|
|
||||||
|
action = None
|
||||||
|
url = flask.url_for('main.home')
|
||||||
|
|
||||||
|
if form.delete.data and not torrent.deleted:
|
||||||
|
action = 'deleted'
|
||||||
|
torrent.deleted = True
|
||||||
|
db.session.add(torrent)
|
||||||
|
|
||||||
|
elif form.ban.data and not torrent.banned and editor.is_moderator:
|
||||||
|
torrent.banned = True
|
||||||
|
if not torrent.deleted:
|
||||||
|
torrent.deleted = True
|
||||||
|
action = 'deleted and banned'
|
||||||
|
else:
|
||||||
|
action = 'banned'
|
||||||
|
db.session.add(torrent)
|
||||||
|
|
||||||
|
elif form.undelete.data and torrent.deleted:
|
||||||
|
action = 'undeleted'
|
||||||
|
torrent.deleted = False
|
||||||
|
torrent.banned = False
|
||||||
|
db.session.add(torrent)
|
||||||
|
|
||||||
|
elif form.unban.data and torrent.banned:
|
||||||
|
action = 'unbanned'
|
||||||
|
torrent.banned = False
|
||||||
|
db.session.add(torrent)
|
||||||
|
|
||||||
|
if not action:
|
||||||
|
flask.flash(flask.Markup('What the fuck are you doing?'), 'danger')
|
||||||
|
return flask.redirect(flask.url_for('torrents.edit', torrent_id=torrent.id))
|
||||||
|
|
||||||
|
if editor.is_moderator:
|
||||||
|
url = flask.url_for('torrents.view', torrent_id=torrent.id)
|
||||||
|
if editor is not torrent.user:
|
||||||
|
log = "Torrent [#{0}]({1}) has been {2}".format(torrent.id, url, action)
|
||||||
|
adminlog = models.AdminLog(log=log, admin_id=editor.id)
|
||||||
|
db.session.add(adminlog)
|
||||||
|
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
flask.flash(flask.Markup('Torrent has been successfully {0}.'.format(action)), 'info')
|
||||||
|
return flask.redirect(url)
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/view/<int:torrent_id>/magnet')
|
@bp.route('/view/<int:torrent_id>/magnet')
|
||||||
def redirect_magnet(torrent_id):
|
def redirect_magnet(torrent_id):
|
||||||
torrent = models.Torrent.by_id(torrent_id)
|
torrent = models.Torrent.by_id(torrent_id)
|
||||||
|
|
Loading…
Reference in a new issue