mirror of
https://github.com/simon987/nyaa.git
synced 2025-12-14 15:49:02 +00:00
Merge branch 'master' into reports
This commit is contained in:
@@ -130,7 +130,8 @@ UPLOAD_API_FORM_KEYMAP = {
|
||||
'is_anonymous': 'anonymous',
|
||||
'is_hidden': 'hidden',
|
||||
'is_complete': 'complete',
|
||||
'is_remake': 'remake'
|
||||
'is_remake': 'remake',
|
||||
'is_trusted': 'trusted'
|
||||
}
|
||||
UPLOAD_API_FORM_KEYMAP_REVERSE = {v: k for k, v in UPLOAD_API_FORM_KEYMAP.items()}
|
||||
UPLOAD_API_KEYS = [
|
||||
@@ -140,6 +141,7 @@ UPLOAD_API_KEYS = [
|
||||
'hidden',
|
||||
'complete',
|
||||
'remake',
|
||||
'trusted',
|
||||
'information',
|
||||
'description'
|
||||
]
|
||||
@@ -161,7 +163,7 @@ def v2_api_upload():
|
||||
# Map api keys to upload form fields
|
||||
for key in UPLOAD_API_KEYS:
|
||||
mapped_key = UPLOAD_API_FORM_KEYMAP_REVERSE.get(key, key)
|
||||
mapped_dict[mapped_key] = request_data.get(key)
|
||||
mapped_dict[mapped_key] = request_data.get(key) or ''
|
||||
|
||||
# Flask-WTF (very helpfully!!) automatically grabs the request form, so force a None formdata
|
||||
upload_form = forms.UploadForm(None, data=mapped_dict)
|
||||
|
||||
@@ -68,8 +68,8 @@ def handle_torrent_upload(upload_form, uploading_user=None, fromAPI=False):
|
||||
torrent.remake = upload_form.is_remake.data
|
||||
torrent.complete = upload_form.is_complete.data
|
||||
# Copy trusted status from user if possible
|
||||
torrent.trusted = (uploading_user.level >=
|
||||
models.UserLevelType.TRUSTED) if uploading_user else False
|
||||
can_mark_trusted = uploading_user and uploading_user.is_trusted
|
||||
torrent.trusted = upload_form.is_trusted.data if can_mark_trusted else False
|
||||
# Set category ids
|
||||
torrent.main_category_id, torrent.sub_category_id = \
|
||||
upload_form.category.parsed_data.get_category_ids()
|
||||
@@ -100,7 +100,9 @@ def handle_torrent_upload(upload_form, uploading_user=None, fromAPI=False):
|
||||
for directory in path_parts:
|
||||
current_directory = current_directory.setdefault(directory, {})
|
||||
|
||||
current_directory[filename] = file_dict['length']
|
||||
# Don't add empty filenames (BitComet directory)
|
||||
if filename:
|
||||
current_directory[filename] = file_dict['length']
|
||||
|
||||
parsed_file_tree = utils.sorted_pathdict(parsed_file_tree)
|
||||
|
||||
|
||||
@@ -153,6 +153,7 @@ class EditForm(FlaskForm):
|
||||
is_remake = BooleanField('Remake')
|
||||
is_anonymous = BooleanField('Anonymous')
|
||||
is_complete = BooleanField('Complete')
|
||||
is_trusted = BooleanField('Trusted')
|
||||
|
||||
information = StringField('Information', [
|
||||
Length(max=255, message='Information must be at most %(max)d characters long.')
|
||||
@@ -200,6 +201,7 @@ class UploadForm(FlaskForm):
|
||||
is_remake = BooleanField('Remake')
|
||||
is_anonymous = BooleanField('Anonymous')
|
||||
is_complete = BooleanField('Complete')
|
||||
is_trusted = BooleanField('Trusted')
|
||||
|
||||
information = StringField('Information', [
|
||||
Length(max=255, message='Information must be at most %(max)d characters long.')
|
||||
@@ -295,7 +297,7 @@ class ReportActionForm(FlaskForm):
|
||||
|
||||
def _validate_trackers(torrent_dict, tracker_to_check_for=None):
|
||||
announce = torrent_dict.get('announce')
|
||||
announce_string = _validate_bytes(announce, 'announce', 'utf-8')
|
||||
announce_string = _validate_bytes(announce, 'announce', test_decode='utf-8')
|
||||
|
||||
tracker_found = tracker_to_check_for and (
|
||||
announce_string.lower() == tracker_to_check_for.lower()) or False
|
||||
@@ -307,7 +309,7 @@ def _validate_trackers(torrent_dict, tracker_to_check_for=None):
|
||||
for announce in announce_list:
|
||||
_validate_list(announce, 'announce-list item')
|
||||
|
||||
announce_string = _validate_bytes(announce[0], 'announce-list item url', 'utf-8')
|
||||
announce_string = _validate_bytes(announce[0], 'announce-list item url', test_decode='utf-8')
|
||||
if tracker_to_check_for and announce_string.lower() == tracker_to_check_for.lower():
|
||||
tracker_found = True
|
||||
|
||||
@@ -323,7 +325,7 @@ def _validate_torrent_metadata(torrent_dict):
|
||||
assert isinstance(info_dict, dict), 'info is not a dict'
|
||||
|
||||
encoding_bytes = torrent_dict.get('encoding', b'utf-8')
|
||||
encoding = _validate_bytes(encoding_bytes, 'encoding', 'utf-8').lower()
|
||||
encoding = _validate_bytes(encoding_bytes, 'encoding', test_decode='utf-8').lower()
|
||||
|
||||
name = info_dict.get('name')
|
||||
_validate_bytes(name, 'name', test_decode=encoding)
|
||||
@@ -345,17 +347,21 @@ def _validate_torrent_metadata(torrent_dict):
|
||||
|
||||
path_list = file_dict.get('path')
|
||||
_validate_list(path_list, 'path')
|
||||
for path_part in path_list:
|
||||
# Validate possible directory names
|
||||
for path_part in path_list[:-1]:
|
||||
_validate_bytes(path_part, 'path part', test_decode=encoding)
|
||||
# Validate actual filename, allow b'' to specify an empty directory
|
||||
_validate_bytes(path_list[-1], 'filename', check_empty=False, test_decode=encoding)
|
||||
|
||||
else:
|
||||
length = info_dict.get('length')
|
||||
_validate_number(length, 'length', check_positive=True)
|
||||
|
||||
|
||||
def _validate_bytes(value, name='value', test_decode=None):
|
||||
def _validate_bytes(value, name='value', check_empty=True, test_decode=None):
|
||||
assert isinstance(value, bytes), name + ' is not bytes'
|
||||
assert len(value) > 0, name + ' is empty'
|
||||
if check_empty:
|
||||
assert len(value) > 0, name + ' is empty'
|
||||
if test_decode:
|
||||
try:
|
||||
return value.decode(test_decode)
|
||||
|
||||
@@ -8,6 +8,7 @@ from werkzeug.security import generate_password_hash, check_password_hash
|
||||
from sqlalchemy_fulltext import FullText
|
||||
|
||||
import re
|
||||
import base64
|
||||
from markupsafe import escape as escape_markup
|
||||
from urllib.parse import unquote as unquote_url
|
||||
|
||||
@@ -88,10 +89,14 @@ class Torrent(db.Model):
|
||||
primaryjoin=(
|
||||
"and_(SubCategory.id == foreign(Torrent.sub_category_id), "
|
||||
"SubCategory.main_category_id == Torrent.main_category_id)"))
|
||||
info = db.relationship('TorrentInfo', uselist=False, back_populates='torrent')
|
||||
filelist = db.relationship('TorrentFilelist', uselist=False, back_populates='torrent')
|
||||
stats = db.relationship('Statistic', uselist=False, back_populates='torrent', lazy='joined')
|
||||
trackers = db.relationship('TorrentTrackers', uselist=True, lazy='joined')
|
||||
info = db.relationship('TorrentInfo', uselist=False,
|
||||
cascade="all, delete-orphan", back_populates='torrent')
|
||||
filelist = db.relationship('TorrentFilelist', uselist=False,
|
||||
cascade="all, delete-orphan", back_populates='torrent')
|
||||
stats = db.relationship('Statistic', uselist=False,
|
||||
cascade="all, delete-orphan", back_populates='torrent', lazy='joined')
|
||||
trackers = db.relationship('TorrentTrackers', uselist=True,
|
||||
cascade="all, delete-orphan", lazy='joined')
|
||||
|
||||
def __repr__(self):
|
||||
return '<{0} #{1.id} \'{1.display_name}\' {1.filesize}b>'.format(type(self).__name__, self)
|
||||
@@ -121,6 +126,14 @@ class Torrent(db.Model):
|
||||
# Escaped
|
||||
return escape_markup(self.information)
|
||||
|
||||
@property
|
||||
def info_hash_as_b32(self):
|
||||
return base64.b32encode(self.info_hash).decode('utf-8')
|
||||
|
||||
@property
|
||||
def info_hash_as_hex(self):
|
||||
return self.info_hash.hex()
|
||||
|
||||
@property
|
||||
def magnet_uri(self):
|
||||
return create_magnet(self)
|
||||
@@ -370,15 +383,15 @@ class User(db.Model):
|
||||
|
||||
@property
|
||||
def is_admin(self):
|
||||
return self.level is UserLevelType.ADMIN or self.level is UserLevelType.SUPERADMIN
|
||||
return self.level >= UserLevelType.ADMIN
|
||||
|
||||
@property
|
||||
def is_superadmin(self):
|
||||
return self.level is UserLevelType.SUPERADMIN
|
||||
return self.level == UserLevelType.SUPERADMIN
|
||||
|
||||
@property
|
||||
def is_trusted(self):
|
||||
return self.level is UserLevelType.TRUSTED
|
||||
return self.level >= UserLevelType.TRUSTED
|
||||
|
||||
|
||||
class ReportStatus(IntEnum):
|
||||
|
||||
209
nyaa/routes.py
209
nyaa/routes.py
@@ -134,24 +134,44 @@ def get_category_id_map():
|
||||
|
||||
app.register_blueprint(api_handler.api_blueprint, url_prefix='/api')
|
||||
|
||||
def chain_get(source, *args):
|
||||
''' Tries to return values from source by the given keys.
|
||||
Returns None if none match.
|
||||
Note: can return a None from the source. '''
|
||||
sentinel = object()
|
||||
for key in args:
|
||||
value = source.get(key, sentinel)
|
||||
if value is not sentinel:
|
||||
return value
|
||||
return None
|
||||
|
||||
@app.route('/rss', defaults={'rss': True})
|
||||
@app.route('/', defaults={'rss': False})
|
||||
def home(rss):
|
||||
if flask.request.args.get('page') == 'rss':
|
||||
rss = True
|
||||
render_as_rss = rss
|
||||
req_args = flask.request.args
|
||||
if req_args.get('page') == 'rss':
|
||||
render_as_rss = True
|
||||
|
||||
term = flask.request.args.get('q', flask.request.args.get('term'))
|
||||
sort = flask.request.args.get('s')
|
||||
order = flask.request.args.get('o')
|
||||
category = flask.request.args.get('c', flask.request.args.get('cats'))
|
||||
quality_filter = flask.request.args.get('f', flask.request.args.get('filter'))
|
||||
user_name = flask.request.args.get('u', flask.request.args.get('user'))
|
||||
page = flask.request.args.get('p', flask.request.args.get('offset', 1, int), int)
|
||||
search_term = chain_get(req_args, 'q', 'term')
|
||||
|
||||
per_page = app.config.get('RESULTS_PER_PAGE')
|
||||
if not per_page:
|
||||
per_page = DEFAULT_PER_PAGE
|
||||
sort_key = req_args.get('s')
|
||||
sort_order = req_args.get('o')
|
||||
|
||||
category = chain_get(req_args, 'c', 'cats')
|
||||
quality_filter = chain_get(req_args, 'f', 'filter')
|
||||
|
||||
user_name = chain_get(req_args, 'u', 'user')
|
||||
page_number = chain_get(req_args, 'p', 'page', 'offset')
|
||||
try:
|
||||
page_number = max(1, int(page_number))
|
||||
except (ValueError, TypeError):
|
||||
page_number = 1
|
||||
|
||||
# Check simply if the key exists
|
||||
use_magnet_links = 'magnets' in req_args or 'm' in req_args
|
||||
|
||||
results_per_page = app.config.get('RESULTS_PER_PAGE', DEFAULT_PER_PAGE)
|
||||
|
||||
user_id = None
|
||||
if user_name:
|
||||
@@ -162,13 +182,13 @@ def home(rss):
|
||||
|
||||
query_args = {
|
||||
'user': user_id,
|
||||
'sort': sort or 'id',
|
||||
'order': order or 'desc',
|
||||
'sort': sort_key or 'id',
|
||||
'order': sort_order or 'desc',
|
||||
'category': category or '0_0',
|
||||
'quality_filter': quality_filter or '0',
|
||||
'page': page,
|
||||
'rss': rss,
|
||||
'per_page': per_page
|
||||
'page': page_number,
|
||||
'rss': render_as_rss,
|
||||
'per_page': results_per_page
|
||||
}
|
||||
|
||||
if flask.g.user:
|
||||
@@ -178,28 +198,26 @@ def home(rss):
|
||||
|
||||
# If searching, we get results from elastic search
|
||||
use_elastic = app.config.get('USE_ELASTIC_SEARCH')
|
||||
if use_elastic and term:
|
||||
query_args['term'] = term
|
||||
if use_elastic and search_term:
|
||||
query_args['term'] = search_term
|
||||
|
||||
max_search_results = app.config.get('ES_MAX_SEARCH_RESULT')
|
||||
if not max_search_results:
|
||||
max_search_results = DEFAULT_MAX_SEARCH_RESULT
|
||||
max_search_results = app.config.get('ES_MAX_SEARCH_RESULT', DEFAULT_MAX_SEARCH_RESULT)
|
||||
|
||||
# Only allow up to (max_search_results / page) pages
|
||||
max_page = min(query_args['page'], int(math.ceil(max_search_results / float(per_page))))
|
||||
max_page = min(query_args['page'], int(math.ceil(max_search_results / results_per_page)))
|
||||
|
||||
query_args['page'] = max_page
|
||||
query_args['max_search_results'] = max_search_results
|
||||
|
||||
query_results = search_elastic(**query_args)
|
||||
|
||||
if rss:
|
||||
return render_rss('/', query_results, use_elastic=True)
|
||||
if render_as_rss:
|
||||
return render_rss('"{}"'.format(search_term), query_results, use_elastic=True, magnet_links=use_magnet_links)
|
||||
else:
|
||||
rss_query_string = _generate_query_string(term, category, quality_filter, user_name)
|
||||
rss_query_string = _generate_query_string(search_term, category, quality_filter, user_name)
|
||||
max_results = min(max_search_results, query_results['hits']['total'])
|
||||
# change p= argument to whatever you change page_parameter to or pagination breaks
|
||||
pagination = Pagination(p=query_args['page'], per_page=per_page,
|
||||
pagination = Pagination(p=query_args['page'], per_page=results_per_page,
|
||||
total=max_results, bs_version=3, page_parameter='p',
|
||||
display_msg=SERACH_PAGINATE_DISPLAY_MSG)
|
||||
return flask.render_template('home.html',
|
||||
@@ -213,13 +231,13 @@ def home(rss):
|
||||
if use_elastic:
|
||||
query_args['term'] = ''
|
||||
else: # Otherwise, use db search for everything
|
||||
query_args['term'] = term or ''
|
||||
query_args['term'] = search_term or ''
|
||||
|
||||
query = search_db(**query_args)
|
||||
if rss:
|
||||
return render_rss('/', query, use_elastic=False)
|
||||
if render_as_rss:
|
||||
return render_rss('Home', query, use_elastic=False, magnet_links=use_magnet_links)
|
||||
else:
|
||||
rss_query_string = _generate_query_string(term, category, quality_filter, user_name)
|
||||
rss_query_string = _generate_query_string(search_term, category, quality_filter, user_name)
|
||||
# Use elastic is always false here because we only hit this section
|
||||
# if we're browsing without a search term (which means we default to DB)
|
||||
# or if ES is disabled
|
||||
@@ -256,39 +274,38 @@ def view_user(user_name):
|
||||
db.session.add(user)
|
||||
db.session.commit()
|
||||
|
||||
return flask.redirect('/user/' + user.username)
|
||||
return flask.redirect(flask.url_for('view_user', user_name=user.username))
|
||||
|
||||
level = 'Regular'
|
||||
if user.is_admin:
|
||||
level = 'Moderator'
|
||||
if user.is_superadmin: # check this second because user can be admin AND superadmin
|
||||
level = 'Administrator'
|
||||
elif user.is_trusted:
|
||||
level = 'Trusted'
|
||||
user_level = ['Regular', 'Trusted', 'Moderator', 'Administrator'][user.level]
|
||||
|
||||
term = flask.request.args.get('q')
|
||||
sort = flask.request.args.get('s')
|
||||
order = flask.request.args.get('o')
|
||||
category = flask.request.args.get('c')
|
||||
quality_filter = flask.request.args.get('f')
|
||||
page = flask.request.args.get('p')
|
||||
if page:
|
||||
page = int(page)
|
||||
req_args = flask.request.args
|
||||
|
||||
per_page = app.config.get('RESULTS_PER_PAGE')
|
||||
if not per_page:
|
||||
per_page = DEFAULT_PER_PAGE
|
||||
search_term = chain_get(req_args, 'q', 'term')
|
||||
|
||||
sort_key = req_args.get('s')
|
||||
sort_order = req_args.get('o')
|
||||
|
||||
category = chain_get(req_args, 'c', 'cats')
|
||||
quality_filter = chain_get(req_args, 'f', 'filter')
|
||||
|
||||
page_number = chain_get(req_args, 'p', 'page', 'offset')
|
||||
try:
|
||||
page_number = max(1, int(page_number))
|
||||
except (ValueError, TypeError):
|
||||
page_number = 1
|
||||
|
||||
results_per_page = app.config.get('RESULTS_PER_PAGE', DEFAULT_PER_PAGE)
|
||||
|
||||
query_args = {
|
||||
'term': term or '',
|
||||
'term': search_term or '',
|
||||
'user': user.id,
|
||||
'sort': sort or 'id',
|
||||
'order': order or 'desc',
|
||||
'sort': sort_key or 'id',
|
||||
'order': sort_order or 'desc',
|
||||
'category': category or '0_0',
|
||||
'quality_filter': quality_filter or '0',
|
||||
'page': page or 1,
|
||||
'page': page_number,
|
||||
'rss': False,
|
||||
'per_page': per_page
|
||||
'per_page': results_per_page
|
||||
}
|
||||
|
||||
if flask.g.user:
|
||||
@@ -297,17 +314,15 @@ def view_user(user_name):
|
||||
query_args['admin'] = True
|
||||
|
||||
# Use elastic search for term searching
|
||||
rss_query_string = _generate_query_string(term, category, quality_filter, user_name)
|
||||
rss_query_string = _generate_query_string(search_term, category, quality_filter, user_name)
|
||||
use_elastic = app.config.get('USE_ELASTIC_SEARCH')
|
||||
if use_elastic and term:
|
||||
query_args['term'] = term
|
||||
if use_elastic and search_term:
|
||||
query_args['term'] = search_term
|
||||
|
||||
max_search_results = app.config.get('ES_MAX_SEARCH_RESULT')
|
||||
if not max_search_results:
|
||||
max_search_results = DEFAULT_MAX_SEARCH_RESULT
|
||||
max_search_results = app.config.get('ES_MAX_SEARCH_RESULT', DEFAULT_MAX_SEARCH_RESULT)
|
||||
|
||||
# Only allow up to (max_search_results / page) pages
|
||||
max_page = min(query_args['page'], int(math.ceil(max_search_results / float(per_page))))
|
||||
max_page = min(query_args['page'], int(math.ceil(max_search_results / results_per_page)))
|
||||
|
||||
query_args['page'] = max_page
|
||||
query_args['max_search_results'] = max_search_results
|
||||
@@ -316,7 +331,7 @@ def view_user(user_name):
|
||||
|
||||
max_results = min(max_search_results, query_results['hits']['total'])
|
||||
# change p= argument to whatever you change page_parameter to or pagination breaks
|
||||
pagination = Pagination(p=query_args['page'], per_page=per_page,
|
||||
pagination = Pagination(p=query_args['page'], per_page=results_per_page,
|
||||
total=max_results, bs_version=3, page_parameter='p',
|
||||
display_msg=SERACH_PAGINATE_DISPLAY_MSG)
|
||||
return flask.render_template('user.html',
|
||||
@@ -327,7 +342,7 @@ def view_user(user_name):
|
||||
user=user,
|
||||
user_page=True,
|
||||
rss_filter=rss_query_string,
|
||||
level=level,
|
||||
level=user_level,
|
||||
admin=admin,
|
||||
superadmin=superadmin,
|
||||
form=form)
|
||||
@@ -336,7 +351,7 @@ def view_user(user_name):
|
||||
if use_elastic:
|
||||
query_args['term'] = ''
|
||||
else:
|
||||
query_args['term'] = term or ''
|
||||
query_args['term'] = search_term or ''
|
||||
query = search_db(**query_args)
|
||||
return flask.render_template('user.html',
|
||||
use_elastic=False,
|
||||
@@ -345,7 +360,7 @@ def view_user(user_name):
|
||||
user=user,
|
||||
user_page=True,
|
||||
rss_filter=rss_query_string,
|
||||
level=level,
|
||||
level=user_level,
|
||||
admin=admin,
|
||||
superadmin=superadmin,
|
||||
form=form)
|
||||
@@ -361,9 +376,10 @@ def _jinja2_filter_rfc822(datestr, fmt=None):
|
||||
return formatdate(float(datetime.strptime(datestr, '%Y-%m-%dT%H:%M:%S').strftime('%s')))
|
||||
|
||||
|
||||
def render_rss(label, query, use_elastic):
|
||||
def render_rss(label, query, use_elastic, magnet_links=False):
|
||||
rss_xml = flask.render_template('rss.xml',
|
||||
use_elastic=use_elastic,
|
||||
magnet_links=magnet_links,
|
||||
term=label,
|
||||
site_url=flask.request.url_root,
|
||||
torrent_query=query)
|
||||
@@ -538,7 +554,8 @@ def _create_upload_category_choices():
|
||||
cat_names = id_map[key]
|
||||
is_main_cat = key.endswith('_0')
|
||||
|
||||
cat_name = is_main_cat and cat_names[0] or (' - ' + cat_names[1])
|
||||
# cat_name = is_main_cat and cat_names[0] or (' - ' + cat_names[1])
|
||||
cat_name = ' - '.join(cat_names)
|
||||
choices.append((key, cat_name, is_main_cat))
|
||||
return choices
|
||||
|
||||
@@ -562,16 +579,17 @@ def upload():
|
||||
def view_torrent(torrent_id):
|
||||
torrent = models.Torrent.by_id(torrent_id)
|
||||
|
||||
viewer = flask.g.user
|
||||
|
||||
if not torrent:
|
||||
flask.abort(404)
|
||||
|
||||
if torrent.deleted and (not flask.g.user or not flask.g.user.is_admin):
|
||||
# Only allow admins see deleted torrents
|
||||
if torrent.deleted and not (viewer and viewer.is_admin):
|
||||
flask.abort(404)
|
||||
|
||||
if flask.g.user:
|
||||
can_edit = flask.g.user is torrent.user or flask.g.user.is_admin
|
||||
else:
|
||||
can_edit = False
|
||||
# Only allow owners and admins to edit torrents
|
||||
can_edit = viewer and (viewer is torrent.user or viewer.is_admin)
|
||||
|
||||
files = None
|
||||
if torrent.filelist:
|
||||
@@ -580,6 +598,7 @@ def view_torrent(torrent_id):
|
||||
report_form = forms.ReportForm()
|
||||
return flask.render_template('view.html', torrent=torrent,
|
||||
files=files,
|
||||
viewer=viewer,
|
||||
can_edit=can_edit,
|
||||
report_form=report_form)
|
||||
|
||||
@@ -589,15 +608,18 @@ def edit_torrent(torrent_id):
|
||||
torrent = models.Torrent.by_id(torrent_id)
|
||||
form = forms.EditForm(flask.request.form)
|
||||
form.category.choices = _create_upload_category_choices()
|
||||
category = str(torrent.main_category_id) + "_" + str(torrent.sub_category_id)
|
||||
|
||||
editor = flask.g.user
|
||||
|
||||
if not torrent:
|
||||
flask.abort(404)
|
||||
|
||||
if torrent.deleted and (not flask.g.user or not flask.g.user.is_admin):
|
||||
# Only allow admins edit deleted torrents
|
||||
if torrent.deleted and not (editor and editor.is_admin):
|
||||
flask.abort(404)
|
||||
|
||||
if not flask.g.user or (flask.g.user is not torrent.user and not flask.g.user.is_admin):
|
||||
# Only allow torrent owners or admins edit torrents
|
||||
if not editor or not (editor is torrent.user or editor.is_admin):
|
||||
flask.abort(403)
|
||||
|
||||
if flask.request.method == 'POST' and form.validate():
|
||||
@@ -607,36 +629,43 @@ def edit_torrent(torrent_id):
|
||||
torrent.display_name = (form.display_name.data or '').strip()
|
||||
torrent.information = (form.information.data or '').strip()
|
||||
torrent.description = (form.description.data or '').strip()
|
||||
if flask.g.user.is_admin:
|
||||
torrent.deleted = form.is_deleted.data
|
||||
|
||||
torrent.hidden = form.is_hidden.data
|
||||
torrent.remake = form.is_remake.data
|
||||
torrent.complete = form.is_complete.data
|
||||
torrent.anonymous = form.is_anonymous.data
|
||||
|
||||
if editor.is_trusted:
|
||||
torrent.trusted = form.is_trusted.data
|
||||
if editor.is_admin:
|
||||
torrent.deleted = form.is_deleted.data
|
||||
|
||||
db.session.commit()
|
||||
|
||||
flask.flash(flask.Markup(
|
||||
'Torrent has been successfully edited! Changes might take a few minutes to show up.'), 'info')
|
||||
|
||||
return flask.redirect('/view/' + str(torrent_id))
|
||||
return flask.redirect(flask.url_for('view_torrent', torrent_id=torrent.id))
|
||||
else:
|
||||
# Setup form with pre-formatted form.
|
||||
form.category.data = category
|
||||
form.display_name.data = torrent.display_name
|
||||
form.information.data = torrent.information
|
||||
form.description.data = torrent.description
|
||||
form.is_hidden.data = torrent.hidden
|
||||
if flask.g.user.is_admin:
|
||||
if flask.request.method != 'POST':
|
||||
# Fill form data only if the POST didn't fail
|
||||
form.category.data = torrent.sub_category.id_as_string
|
||||
form.display_name.data = torrent.display_name
|
||||
form.information.data = torrent.information
|
||||
form.description.data = torrent.description
|
||||
|
||||
form.is_hidden.data = torrent.hidden
|
||||
form.is_remake.data = torrent.remake
|
||||
form.is_complete.data = torrent.complete
|
||||
form.is_anonymous.data = torrent.anonymous
|
||||
|
||||
form.is_trusted.data = torrent.trusted
|
||||
form.is_deleted.data = torrent.deleted
|
||||
form.is_remake.data = torrent.remake
|
||||
form.is_complete.data = torrent.complete
|
||||
form.is_anonymous.data = torrent.anonymous
|
||||
|
||||
return flask.render_template('edit.html',
|
||||
form=form,
|
||||
torrent=torrent,
|
||||
admin=flask.g.user.is_admin)
|
||||
editor=editor)
|
||||
|
||||
|
||||
@app.route('/view/<int:torrent_id>/magnet')
|
||||
|
||||
@@ -58,6 +58,10 @@ table.torrent-list thead th.sorting_desc:after {
|
||||
content: "\f0dd";
|
||||
}
|
||||
|
||||
table.torrent-list tbody tr td a:visited {
|
||||
color: #1d4568;
|
||||
}
|
||||
|
||||
#torrent-description img {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
{% macro render_field(field) %}
|
||||
{% macro render_field(field, render_label=True) %}
|
||||
{% if field.errors %}
|
||||
<div class="form-group has-error">
|
||||
{% else %}
|
||||
<div class="form-group">
|
||||
{% endif %}
|
||||
{% if render_label %}
|
||||
{{ field.label(class='control-label') }}
|
||||
{% endif %}
|
||||
{{ field(title=field.description,**kwargs) | safe }}
|
||||
{% if field.errors %}
|
||||
<div class="help-block">
|
||||
@@ -27,33 +29,33 @@
|
||||
|
||||
{% macro render_markdown_editor(field, field_name='') %}
|
||||
{% if field.errors %}
|
||||
<div class="form-group has-error">
|
||||
<div class="form-group has-error">
|
||||
{% else %}
|
||||
<div class="form-group">
|
||||
<div class="form-group">
|
||||
{% endif %}
|
||||
<div class="markdown-editor" id="{{ field_name }}-markdown-editor" data-field-name="{{ field_name }}">
|
||||
<ul class="nav nav-tabs" role="tablist">
|
||||
<li role="presentation" class="active">
|
||||
<a href="#{{ field_name }}-tab" aria-controls="" role="tab" data-toggle="tab">
|
||||
Write
|
||||
</a>
|
||||
</li>
|
||||
<li role="presentation">
|
||||
<a href="#{{ field_name }}-preview" id="{{ field_name }}-preview-tab" aria-controls="preview" role="tab" data-toggle="tab">
|
||||
Preview
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="tab-content">
|
||||
<div role="tabpanel" class="tab-pane active" id="{{ field_name }}-tab" data-markdown-target="#{{ field_name }}-markdown-target">
|
||||
{{ render_field(field, class_='form-control markdown-source') }}
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane" id="{{ field_name }}-preview">
|
||||
{{ field.label(class='control-label') }}
|
||||
<div class="well" id="{{ field_name }}-markdown-target"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="markdown-editor" id="{{ field_name }}-markdown-editor" data-field-name="{{ field_name }}">
|
||||
{{ field.label(class='control-label') }}
|
||||
<ul class="nav nav-tabs" role="tablist">
|
||||
<li role="presentation" class="active">
|
||||
<a href="#{{ field_name }}-tab" aria-controls="" role="tab" data-toggle="tab">
|
||||
Write
|
||||
</a>
|
||||
</li>
|
||||
<li role="presentation">
|
||||
<a href="#{{ field_name }}-preview" id="{{ field_name }}-preview-tab" aria-controls="preview" role="tab" data-toggle="tab">
|
||||
Preview
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="tab-content">
|
||||
<div role="tabpanel" class="tab-pane active" id="{{ field_name }}-tab" data-markdown-target="#{{ field_name }}-markdown-target">
|
||||
{{ render_field(field, False, class_='form-control markdown-source') }}
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane" id="{{ field_name }}-preview">
|
||||
<div class="well" id="{{ field_name }}-markdown-target"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
@@ -4,79 +4,73 @@
|
||||
{% from "_formhelpers.html" import render_field %}
|
||||
{% from "_formhelpers.html" import render_markdown_editor %}
|
||||
|
||||
<h1>Edit Torrent</h1>
|
||||
{% set torrent_url = url_for('view_torrent', torrent_id=torrent.id) %}
|
||||
<h1>
|
||||
Edit Torrent <a href="{{ torrent_url }}">#{{torrent.id}}</a>
|
||||
{% if (torrent.user != None) and (torrent.user != editor) %}
|
||||
(by <a href="{{ url_for('view_user', user_name=torrent.user.username) }}">{{ torrent.user.username }}</a>)
|
||||
{% endif %}
|
||||
</h1>
|
||||
|
||||
<form method="POST" enctype="multipart/form-data">
|
||||
{{ form.csrf_token }}
|
||||
|
||||
|
||||
<div class="row">
|
||||
<div class="form-group col-md-6">
|
||||
{{ render_field(form.category, class_='form-control')}}
|
||||
<div class="col-md-6">
|
||||
{{ render_field(form.display_name, class_='form-control', placeholder='Display name') }}
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
{{ render_field(form.category, class_='form-control')}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="form-group col-md-6">
|
||||
{{ render_field(form.display_name, class_='form-control', placeholder='Display name') }}
|
||||
<div class="col-md-6">
|
||||
{{ render_field(form.information, class_='form-control', placeholder='Your website or IRC channel') }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="control-label">Torrent flags</label>
|
||||
<div>
|
||||
{% if editor.is_admin %}
|
||||
<label class="btn btn-primary">
|
||||
{{ form.is_deleted }}
|
||||
Deleted
|
||||
</label>
|
||||
{% endif %}
|
||||
|
||||
<div class="row">
|
||||
<div class="form-group col-md-6">
|
||||
{{ render_field(form.information, class_='form-control', placeholder='Your website or IRC channel') }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="form-group col-md-6">
|
||||
{{ render_markdown_editor(form.description, field_name='description') }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if admin %}
|
||||
<div class="row">
|
||||
<div class="form-group col-md-6">
|
||||
<label>
|
||||
{{ form.is_deleted }}
|
||||
Deleted
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="row">
|
||||
<div class="form-group col-md-6">
|
||||
<label>
|
||||
<label class="btn btn-default" style="background-color: darkgray; border-color: #ccc;" title="Hide torrent from listing">
|
||||
{{ form.is_hidden }}
|
||||
Hidden
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="form-group col-md-6">
|
||||
<label>
|
||||
<label class="btn btn-danger" title="This torrent is derived from another release">
|
||||
{{ form.is_remake }}
|
||||
Remake
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="form-group col-md-6">
|
||||
<label>
|
||||
<label class="btn btn-primary" title="This torrent is a complete batch (eg. season)">
|
||||
{{ form.is_complete }}
|
||||
Complete
|
||||
</label>
|
||||
|
||||
{# Only allow changing anonymous status when an uploader exists #}
|
||||
{% if torrent.uploader_id %}
|
||||
<label class="btn btn-primary" title="Upload torrent anonymously (don't display your username)">
|
||||
{{ form.is_anonymous }}
|
||||
Anonymous
|
||||
</label>
|
||||
{% endif %}
|
||||
{% if editor.is_trusted %}
|
||||
<label class="btn btn-success" title="Mark torrent trusted">
|
||||
{{ form.is_trusted }}
|
||||
Trusted
|
||||
</label>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="form-group col-md-6">
|
||||
<label>
|
||||
{{ form.is_anonymous }}
|
||||
Anonymous
|
||||
</label>
|
||||
<div class="col-md-12">
|
||||
{{ render_markdown_editor(form.description, field_name='description') }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,37 +1,45 @@
|
||||
<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
|
||||
<channel>
|
||||
<title>{{ config.SITE_NAME }} Torrent File RSS (No magnets)</title>
|
||||
<title>{{ config.SITE_NAME }} Torrent File RSS</title>
|
||||
<description>RSS Feed for {{ term }}</description>
|
||||
<link>{{ url_for('home', _external=True) }}</link>
|
||||
<atom:link href="{{ url_for('home', page='rss', _external=True) }}" rel="self" type="application/rss+xml" />
|
||||
{% for torrent in torrent_query %}
|
||||
{% if torrent.has_torrent %}
|
||||
<item>
|
||||
<title>{{ torrent.display_name }}</title>
|
||||
{# <description><![CDATA[{{ torrent.description }}]]></description> #}
|
||||
{% if use_elastic %}
|
||||
<link>{{ url_for('download_torrent', torrent_id=torrent.meta.id, _external=True) }}</link>
|
||||
<guid isPermaLink="true">{{ url_for('view_torrent', torrent_id=torrent.meta.id, _external=True) }}</guid>
|
||||
<pubDate>{{ torrent.created_time|rfc822_es }}</pubDate>
|
||||
{% if torrent.has_torrent and not magnet_links %}
|
||||
<link>{{ url_for('download_torrent', torrent_id=torrent.meta.id, _external=True) }}</link>
|
||||
{% else %}
|
||||
<link>{{ create_magnet_from_info(torrent.display_name, torrent.info_hash) }}</link>
|
||||
{% endif %}
|
||||
<guid isPermaLink="true">{{ url_for('view_torrent', torrent_id=torrent.meta.id, _external=True) }}</guid>
|
||||
<pubDate>{{ torrent.created_time|rfc822_es }}</pubDate>
|
||||
|
||||
<seeders> {{- torrent.seed_count }}</seeders>
|
||||
<leechers> {{- torrent.leech_count }}</leechers>
|
||||
<downloads>{{- torrent.download_count }}</downloads>
|
||||
<infoHash> {{- torrent.info_hash }}</infoHash>
|
||||
{% else %}
|
||||
<link>{{ url_for('download_torrent', torrent_id=torrent.id, _external=True) }}</link>
|
||||
<guid isPermaLink="true">{{ url_for('view_torrent', torrent_id=torrent.id, _external=True) }}</guid>
|
||||
<pubDate>{{ torrent.created_time|rfc822 }}</pubDate>
|
||||
{% endif %}
|
||||
</item>
|
||||
{% else %}
|
||||
<item>
|
||||
<title>{{ torrent.display_name }}</title>
|
||||
{% if use_elastic %}
|
||||
<link>{{ create_magnet_from_info(torrent.display_name, torrent.info_hash) }}</link>
|
||||
<guid isPermaLink="true">{{ url_for('view_torrent', torrent_id=torrent.meta.id, _external=True) }}</guid>
|
||||
<pubDate>{{ torrent.created_time|rfc822_es }}</pubDate>
|
||||
{% else %}
|
||||
<link>{{ torrent.magnet_uri }}</link>
|
||||
<guid isPermaLink="true">{{ url_for('view_torrent', torrent_id=torrent.id, _external=True) }}</guid>
|
||||
<pubDate>{{ torrent.created_time|rfc822 }}</pubDate>
|
||||
{% if torrent.has_torrent and not magnet_links %}
|
||||
<link>{{ url_for('download_torrent', torrent_id=torrent.id, _external=True) }}</link>
|
||||
{% else %}
|
||||
<link>{{ torrent.magnet_uri }}</link>
|
||||
{% endif %}
|
||||
<guid isPermaLink="true">{{ url_for('view_torrent', torrent_id=torrent.id, _external=True) }}</guid>
|
||||
<pubDate>{{ torrent.created_time|rfc822 }}</pubDate>
|
||||
|
||||
<seeders> {{- torrent.stats.seed_count }}</seeders>
|
||||
<leechers> {{- torrent.stats.leech_count }}</leechers>
|
||||
<downloads>{{- torrent.stats.download_count }}</downloads>
|
||||
<infoHash> {{- torrent.info_hash_as_hex }}</infoHash>
|
||||
{% endif %}
|
||||
{% set cat_id = use_elastic and ((torrent.main_category_id|string) + '_' + (torrent.sub_category_id|string)) or torrent.sub_category.id_as_string %}
|
||||
<categoryId>{{- cat_id }}</categoryId>
|
||||
<category> {{- category_name(cat_id) }}</category>
|
||||
<size> {{- torrent.filesize | filesizeformat(True) }}</size>
|
||||
</item>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</channel>
|
||||
</rss>
|
||||
|
||||
@@ -16,68 +16,57 @@
|
||||
<form method="POST" enctype="multipart/form-data">
|
||||
{% if config.ENFORCE_MAIN_ANNOUNCE_URL %}<p><strong>Important:</strong> Please include <kbd>{{config.MAIN_ANNOUNCE_URL}}</kbd> in your trackers</p>{% endif %}
|
||||
<div class="row">
|
||||
<div class="form-group col-md-6">
|
||||
{{ render_upload(form.torrent_file, accept=".torrent") }}
|
||||
<div class="col-md-6">
|
||||
{{ render_upload(form.torrent_file, accept=".torrent") }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="form-group col-md-6">
|
||||
{{ render_field(form.category, class_='form-control')}}
|
||||
<div class="col-md-6">
|
||||
{{ render_field(form.display_name, class_='form-control', placeholder='Display name') }}
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
{{ render_field(form.category, class_='form-control')}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="form-group col-md-6">
|
||||
{{ render_field(form.display_name, class_='form-control', placeholder='Display name') }}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="form-group col-md-6">
|
||||
<div class="row form-group">
|
||||
<div class="col-md-6">
|
||||
{{ render_field(form.information, class_='form-control', placeholder='Your website or IRC channel') }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="form-group col-md-6">
|
||||
{{ render_markdown_editor(form.description, field_name='description') }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="form-group col-md-6">
|
||||
<label>
|
||||
<div class="col-md-6">
|
||||
<label class="control-label">Torrent flags</label>
|
||||
<div>
|
||||
<label class="btn btn-primary" title="Upload torrent anonymously (don't display your username)">
|
||||
{{ form.is_anonymous(disabled=(False if user else ""), checked=(False if user else "")) }}
|
||||
Anonymous
|
||||
</label>
|
||||
<label class="btn btn-default" style="background-color: darkgray; border-color: #ccc;" title="Hide torrent from listing">
|
||||
{{ form.is_hidden }}
|
||||
Hidden
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="form-group col-md-6">
|
||||
<label>
|
||||
<label class="btn btn-danger" title="This torrent is derived from another release">
|
||||
{{ form.is_remake }}
|
||||
Remake
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="form-group col-md-6">
|
||||
<label>
|
||||
<label class="btn btn-primary" title="This torrent is a complete batch (eg. season)">
|
||||
{{ form.is_complete }}
|
||||
Complete
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="form-group col-md-6">
|
||||
<label>
|
||||
{{ form.is_anonymous }}
|
||||
Anonymous
|
||||
{% if user.is_trusted %}
|
||||
<label class="btn btn-success" title="Mark torrent trusted">
|
||||
{{ form.is_trusted(checked="") }}
|
||||
Trusted
|
||||
</label>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
{{ render_markdown_editor(form.description, field_name='description') }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<div class="panel-heading"{% if torrent.hidden %} style="background-color: darkgray;"{% endif %}>
|
||||
<h3 class="panel-title">
|
||||
{% if can_edit %}
|
||||
<a href="{{ request.url }}/edit"><i class="fa fa-fw fa-pencil"></i></a>
|
||||
<a href="{{ request.url }}/edit" title="Edit torrent"><i class="fa fa-fw fa-pencil"></i></a>
|
||||
{% endif %}
|
||||
{{ torrent.display_name }}
|
||||
</h3>
|
||||
@@ -24,7 +24,14 @@
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-1">Submitter:</div>
|
||||
<div class="col-md-5">{% if not torrent.anonymous and torrent.user %}<a href="{{ url_for('view_user', user_name=torrent.user.username) }}">{{ torrent.user.username }}</a>{% else %}Anonymous{% endif %}</div>
|
||||
<div class="col-md-5">
|
||||
{% set user_url = torrent.user and url_for('view_user', user_name=torrent.user.username) %}
|
||||
{%- if not torrent.anonymous and torrent.user -%}
|
||||
<a href="{{ user_url }}">{{ torrent.user.username }}</a>
|
||||
{%- else -%}
|
||||
Anonymous {% if torrent.user and (viewer == torrent.user or viewer.is_admin) %}(<a href="{{ user_url }}">{{ torrent.user.username }}</a>){% endif %}
|
||||
{%- endif -%}
|
||||
</div>
|
||||
|
||||
<div class="col-md-1">Seeders:</div>
|
||||
<div class="col-md-5"><span style="color: green;">{% if config.ENABLE_SHOW_STATS %}{{ torrent.stats.seed_count }}{% else %}Coming soon{% endif %}</span></div>
|
||||
|
||||
Reference in New Issue
Block a user