mirror of
https://gitlab.com/SIGBUS/nyaa.git
synced 2024-12-23 00:10:01 +00:00
commit
c35f136133
48
migrations/versions/d0eeb8049623_add_comments.py
Normal file
48
migrations/versions/d0eeb8049623_add_comments.py
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
"""Add comments table.
|
||||||
|
|
||||||
|
Revision ID: d0eeb8049623
|
||||||
|
Revises: 3001f79b7722
|
||||||
|
Create Date: 2017-05-22 22:58:12.039149
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = 'd0eeb8049623'
|
||||||
|
down_revision = '3001f79b7722'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.create_table('nyaa_comments',
|
||||||
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('torrent_id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('user_id', sa.Integer(), nullable=True),
|
||||||
|
sa.Column('created_time', sa.DateTime(), nullable=True),
|
||||||
|
sa.Column('text', sa.String(length=255), nullable=False),
|
||||||
|
sa.ForeignKeyConstraint(['torrent_id'], ['nyaa_torrents.id'], ondelete='CASCADE'),
|
||||||
|
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ondelete='CASCADE'),
|
||||||
|
sa.PrimaryKeyConstraint('id')
|
||||||
|
)
|
||||||
|
op.create_table('sukebei_comments',
|
||||||
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('torrent_id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('user_id', sa.Integer(), nullable=True),
|
||||||
|
sa.Column('created_time', sa.DateTime(), nullable=True),
|
||||||
|
sa.Column('text', sa.String(length=255), nullable=False),
|
||||||
|
sa.ForeignKeyConstraint(['torrent_id'], ['sukebei_torrents.id'], ondelete='CASCADE'),
|
||||||
|
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ondelete='CASCADE'),
|
||||||
|
sa.PrimaryKeyConstraint('id')
|
||||||
|
)
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_table('nyaa_comments')
|
||||||
|
op.drop_table('sukebei_comments')
|
||||||
|
# ### end Alembic commands ###
|
|
@ -126,11 +126,18 @@ class DisabledSelectField(SelectField):
|
||||||
raise ValueError(self.gettext('Not a valid choice'))
|
raise ValueError(self.gettext('Not a valid choice'))
|
||||||
|
|
||||||
|
|
||||||
|
class CommentForm(FlaskForm):
|
||||||
|
comment = TextAreaField('Make a comment', [
|
||||||
|
Length(min=3, max=255, message='Comment must be at least %(min)d characters '
|
||||||
|
'long and %(max)d at most.'),
|
||||||
|
DataRequired()
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
class EditForm(FlaskForm):
|
class EditForm(FlaskForm):
|
||||||
display_name = StringField('Torrent display name', [
|
display_name = StringField('Torrent display name', [
|
||||||
Length(min=3, max=255,
|
Length(min=3, max=255, message='Torrent display name must be at least %(min)d characters '
|
||||||
message='Torrent display name must be at least %(min)d characters long '
|
'long and %(max)d at most.')
|
||||||
'and %(max)d at most.')
|
|
||||||
])
|
])
|
||||||
|
|
||||||
category = DisabledSelectField('Category')
|
category = DisabledSelectField('Category')
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import flask
|
||||||
from enum import Enum, IntEnum
|
from enum import Enum, IntEnum
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from nyaa import app, db
|
from nyaa import app, db
|
||||||
|
@ -11,7 +12,8 @@ from ipaddress import ip_address
|
||||||
import re
|
import re
|
||||||
import base64
|
import base64
|
||||||
from markupsafe import escape as escape_markup
|
from markupsafe import escape as escape_markup
|
||||||
from urllib.parse import unquote as unquote_url
|
from urllib.parse import urlencode, unquote as unquote_url
|
||||||
|
from hashlib import md5
|
||||||
|
|
||||||
if app.config['USE_MYSQL']:
|
if app.config['USE_MYSQL']:
|
||||||
from sqlalchemy.dialects import mysql
|
from sqlalchemy.dialects import mysql
|
||||||
|
@ -99,6 +101,8 @@ class Torrent(db.Model):
|
||||||
cascade="all, delete-orphan", back_populates='torrent', lazy='joined')
|
cascade="all, delete-orphan", back_populates='torrent', lazy='joined')
|
||||||
trackers = db.relationship('TorrentTrackers', uselist=True,
|
trackers = db.relationship('TorrentTrackers', uselist=True,
|
||||||
cascade="all, delete-orphan", lazy='joined')
|
cascade="all, delete-orphan", lazy='joined')
|
||||||
|
comments = db.relationship('Comment', uselist=True,
|
||||||
|
cascade="all, delete-orphan")
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return '<{0} #{1.id} \'{1.display_name}\' {1.filesize}b>'.format(type(self).__name__, self)
|
return '<{0} #{1.id} \'{1.display_name}\' {1.filesize}b>'.format(type(self).__name__, self)
|
||||||
|
@ -317,6 +321,27 @@ class SubCategory(db.Model):
|
||||||
return cls.query.get((sub_cat_id, main_cat_id))
|
return cls.query.get((sub_cat_id, main_cat_id))
|
||||||
|
|
||||||
|
|
||||||
|
class Comment(db.Model):
|
||||||
|
__tablename__ = DB_TABLE_PREFIX + 'comments'
|
||||||
|
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
torrent_id = db.Column(db.Integer, db.ForeignKey(
|
||||||
|
DB_TABLE_PREFIX + 'torrents.id', ondelete='CASCADE'), nullable=False)
|
||||||
|
user_id = db.Column(db.Integer, db.ForeignKey('users.id', ondelete='CASCADE'))
|
||||||
|
created_time = db.Column(db.DateTime(timezone=False), default=datetime.utcnow)
|
||||||
|
text = db.Column(db.String(length=255), nullable=False)
|
||||||
|
|
||||||
|
user = db.relationship('User', uselist=False, back_populates='comments', lazy="joined")
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return '<Comment %r>' % self.id
|
||||||
|
|
||||||
|
@property
|
||||||
|
def created_utc_timestamp(self):
|
||||||
|
''' Returns a UTC POSIX timestamp, as seconds '''
|
||||||
|
return (self.created_time - UTC_EPOCH).total_seconds()
|
||||||
|
|
||||||
|
|
||||||
class UserLevelType(IntEnum):
|
class UserLevelType(IntEnum):
|
||||||
REGULAR = 0
|
REGULAR = 0
|
||||||
TRUSTED = 1
|
TRUSTED = 1
|
||||||
|
@ -346,7 +371,8 @@ class User(db.Model):
|
||||||
last_login_date = db.Column(db.DateTime(timezone=False), default=None, nullable=True)
|
last_login_date = db.Column(db.DateTime(timezone=False), default=None, nullable=True)
|
||||||
last_login_ip = db.Column(db.Binary(length=16), default=None, nullable=True)
|
last_login_ip = db.Column(db.Binary(length=16), default=None, nullable=True)
|
||||||
|
|
||||||
torrents = db.relationship('Torrent', back_populates='user', lazy="dynamic")
|
torrents = db.relationship('Torrent', back_populates='user', lazy='dynamic')
|
||||||
|
comments = db.relationship('Comment', back_populates='user', lazy='dynamic')
|
||||||
# session = db.relationship('Session', uselist=False, back_populates='user')
|
# session = db.relationship('Session', uselist=False, back_populates='user')
|
||||||
|
|
||||||
def __init__(self, username, email, password):
|
def __init__(self, username, email, password):
|
||||||
|
@ -369,6 +395,25 @@ class User(db.Model):
|
||||||
]
|
]
|
||||||
return all(checks)
|
return all(checks)
|
||||||
|
|
||||||
|
def gravatar_url(self):
|
||||||
|
# from http://en.gravatar.com/site/implement/images/python/
|
||||||
|
size = 120
|
||||||
|
# construct the url
|
||||||
|
default_avatar = flask.url_for('static', filename='img/avatar/default.png', _external=True)
|
||||||
|
gravatar_url = 'https://www.gravatar.com/avatar/{}?{}'.format(
|
||||||
|
md5(self.email.encode('utf-8').lower()).hexdigest(),
|
||||||
|
urlencode({'d': default_avatar, 's': str(size)}))
|
||||||
|
return gravatar_url
|
||||||
|
|
||||||
|
@property
|
||||||
|
def userlevel_str(self):
|
||||||
|
if self.level == UserLevelType.REGULAR:
|
||||||
|
return 'User'
|
||||||
|
elif self.level == UserLevelType.TRUSTED:
|
||||||
|
return 'Trusted'
|
||||||
|
elif self.level >= UserLevelType.MODERATOR:
|
||||||
|
return 'Moderator'
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def ip_string(self):
|
def ip_string(self):
|
||||||
if self.last_login_ip:
|
if self.last_login_ip:
|
||||||
|
|
104
nyaa/routes.py
104
nyaa/routes.py
|
@ -7,6 +7,7 @@ from nyaa import torrents
|
||||||
from nyaa import backend
|
from nyaa import backend
|
||||||
from nyaa import api_handler
|
from nyaa import api_handler
|
||||||
from nyaa.search import search_elastic, search_db
|
from nyaa.search import search_elastic, search_db
|
||||||
|
from sqlalchemy.orm import joinedload
|
||||||
import config
|
import config
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
@ -570,21 +571,48 @@ def upload():
|
||||||
return flask.render_template('upload.html', upload_form=upload_form), status_code
|
return flask.render_template('upload.html', upload_form=upload_form), status_code
|
||||||
|
|
||||||
|
|
||||||
@app.route('/view/<int:torrent_id>')
|
@app.route('/view/<int:torrent_id>', methods=['GET', 'POST'])
|
||||||
def view_torrent(torrent_id):
|
def view_torrent(torrent_id):
|
||||||
|
if flask.request.method == 'POST':
|
||||||
torrent = models.Torrent.by_id(torrent_id)
|
torrent = models.Torrent.by_id(torrent_id)
|
||||||
|
else:
|
||||||
viewer = flask.g.user
|
torrent = models.Torrent.query \
|
||||||
|
.options(joinedload('filelist'),
|
||||||
|
joinedload('comments')) \
|
||||||
|
.filter_by(id=torrent_id) \
|
||||||
|
.first()
|
||||||
if not torrent:
|
if not torrent:
|
||||||
flask.abort(404)
|
flask.abort(404)
|
||||||
|
|
||||||
# Only allow admins see deleted torrents
|
# Only allow admins see deleted torrents
|
||||||
if torrent.deleted and not (viewer and viewer.is_moderator):
|
if torrent.deleted and not (flask.g.user and flask.g.user.is_moderator):
|
||||||
flask.abort(404)
|
flask.abort(404)
|
||||||
|
|
||||||
|
comment_form = None
|
||||||
|
if flask.g.user:
|
||||||
|
comment_form = forms.CommentForm()
|
||||||
|
|
||||||
|
if flask.request.method == 'POST':
|
||||||
|
if not flask.g.user:
|
||||||
|
flask.abort(403)
|
||||||
|
|
||||||
|
if comment_form.validate():
|
||||||
|
comment_text = (comment_form.comment.data or '').strip()
|
||||||
|
|
||||||
|
comment = models.Comment(
|
||||||
|
torrent_id=torrent_id,
|
||||||
|
user_id=flask.g.user.id,
|
||||||
|
text=comment_text)
|
||||||
|
|
||||||
|
db.session.add(comment)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
flask.flash('Comment successfully posted.', 'success')
|
||||||
|
|
||||||
|
return flask.redirect(flask.url_for('view_torrent', torrent_id=torrent_id))
|
||||||
|
|
||||||
# Only allow owners and admins to edit torrents
|
# Only allow owners and admins to edit torrents
|
||||||
can_edit = viewer and (viewer is torrent.user or viewer.is_moderator)
|
can_edit = flask.g.user and (flask.g.user is torrent.user or flask.g.user.is_moderator)
|
||||||
|
|
||||||
files = None
|
files = None
|
||||||
if torrent.filelist:
|
if torrent.filelist:
|
||||||
|
@ -592,10 +620,31 @@ def view_torrent(torrent_id):
|
||||||
|
|
||||||
return flask.render_template('view.html', torrent=torrent,
|
return flask.render_template('view.html', torrent=torrent,
|
||||||
files=files,
|
files=files,
|
||||||
viewer=viewer,
|
comment_form=comment_form,
|
||||||
|
comments=torrent.comments,
|
||||||
can_edit=can_edit)
|
can_edit=can_edit)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/view/<int:torrent_id>/comment/<int:comment_id>/delete', methods=['POST'])
|
||||||
|
def delete_comment(torrent_id, comment_id):
|
||||||
|
if not flask.g.user:
|
||||||
|
flask.abort(403)
|
||||||
|
|
||||||
|
comment = models.Comment.query.filter_by(id=comment_id).first()
|
||||||
|
if not comment:
|
||||||
|
flask.abort(404)
|
||||||
|
|
||||||
|
if not (comment.user.id == flask.g.user.id or flask.g.user.is_moderator):
|
||||||
|
flask.abort(403)
|
||||||
|
|
||||||
|
db.session.delete(comment)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
flask.flash('Comment successfully deleted.', 'success')
|
||||||
|
|
||||||
|
return flask.redirect(flask.url_for('view_torrent', torrent_id=torrent_id))
|
||||||
|
|
||||||
|
|
||||||
@app.route('/view/<int:torrent_id>/edit', methods=['GET', 'POST'])
|
@app.route('/view/<int:torrent_id>/edit', methods=['GET', 'POST'])
|
||||||
def edit_torrent(torrent_id):
|
def edit_torrent(torrent_id):
|
||||||
torrent = models.Torrent.by_id(torrent_id)
|
torrent = models.Torrent.by_id(torrent_id)
|
||||||
|
@ -608,11 +657,11 @@ def edit_torrent(torrent_id):
|
||||||
flask.abort(404)
|
flask.abort(404)
|
||||||
|
|
||||||
# Only allow admins edit deleted torrents
|
# Only allow admins edit deleted torrents
|
||||||
if torrent.deleted and not (editor and editor.is_moderator):
|
if torrent.deleted and not (flask.g.user and flask.g.user.is_moderator):
|
||||||
flask.abort(404)
|
flask.abort(404)
|
||||||
|
|
||||||
# Only allow torrent owners or admins edit torrents
|
# Only allow torrent owners or admins edit torrents
|
||||||
if not editor or not (editor is torrent.user or editor.is_moderator):
|
if not flask.g.user or not (flask.g.user is torrent.user or flask.g.user.is_moderator):
|
||||||
flask.abort(403)
|
flask.abort(403)
|
||||||
|
|
||||||
if flask.request.method == 'POST' and form.validate():
|
if flask.request.method == 'POST' and form.validate():
|
||||||
|
@ -628,9 +677,9 @@ def edit_torrent(torrent_id):
|
||||||
torrent.complete = form.is_complete.data
|
torrent.complete = form.is_complete.data
|
||||||
torrent.anonymous = form.is_anonymous.data
|
torrent.anonymous = form.is_anonymous.data
|
||||||
|
|
||||||
if editor.is_trusted:
|
if flask.g.user.is_trusted:
|
||||||
torrent.trusted = form.is_trusted.data
|
torrent.trusted = form.is_trusted.data
|
||||||
if editor.is_moderator:
|
if flask.g.user.is_moderator:
|
||||||
torrent.deleted = form.is_deleted.data
|
torrent.deleted = form.is_deleted.data
|
||||||
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
@ -658,8 +707,7 @@ def edit_torrent(torrent_id):
|
||||||
|
|
||||||
return flask.render_template('edit.html',
|
return flask.render_template('edit.html',
|
||||||
form=form,
|
form=form,
|
||||||
torrent=torrent,
|
torrent=torrent)
|
||||||
editor=editor)
|
|
||||||
|
|
||||||
|
|
||||||
@app.route('/view/<int:torrent_id>/magnet')
|
@app.route('/view/<int:torrent_id>/magnet')
|
||||||
|
@ -752,7 +800,37 @@ def _create_user_class_choices(user):
|
||||||
return default, choices
|
return default, choices
|
||||||
|
|
||||||
|
|
||||||
|
@app.template_filter()
|
||||||
|
def timesince(dt, default='just now'):
|
||||||
|
"""
|
||||||
|
Returns string representing "time since" e.g.
|
||||||
|
3 minutes ago, 5 hours ago etc.
|
||||||
|
Date and time (UTC) are returned if older than 1 day.
|
||||||
|
"""
|
||||||
|
|
||||||
|
now = datetime.utcnow()
|
||||||
|
diff = now - dt
|
||||||
|
|
||||||
|
periods = (
|
||||||
|
(diff.days, 'day', 'days'),
|
||||||
|
(diff.seconds / 3600, 'hour', 'hours'),
|
||||||
|
(diff.seconds / 60, 'minute', 'minutes'),
|
||||||
|
(diff.seconds, 'second', 'seconds'),
|
||||||
|
)
|
||||||
|
|
||||||
|
if diff.days >= 1:
|
||||||
|
return dt.strftime('%Y-%m-%d %H:%M UTC')
|
||||||
|
else:
|
||||||
|
for period, singular, plural in periods:
|
||||||
|
|
||||||
|
if period >= 1:
|
||||||
|
return '%d %s ago' % (period, singular if period == 1 else plural)
|
||||||
|
|
||||||
|
return default
|
||||||
|
|
||||||
# #################################### STATIC PAGES ####################################
|
# #################################### STATIC PAGES ####################################
|
||||||
|
|
||||||
|
|
||||||
@app.route('/rules', methods=['GET'])
|
@app.route('/rules', methods=['GET'])
|
||||||
def site_rules():
|
def site_rules():
|
||||||
return flask.render_template('rules.html')
|
return flask.render_template('rules.html')
|
||||||
|
|
|
@ -218,3 +218,26 @@ table.torrent-list tbody tr td a:visited {
|
||||||
ul.nav-tabs#profileTabs {
|
ul.nav-tabs#profileTabs {
|
||||||
margin-bottom: 15px;
|
margin-bottom: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.comments-panel {
|
||||||
|
width: 99%;
|
||||||
|
margin: 0 auto;
|
||||||
|
margin-top:10px;
|
||||||
|
margin-bottom:10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-box {
|
||||||
|
width: 95%;
|
||||||
|
margin: 0 auto;
|
||||||
|
margin-top:30px;
|
||||||
|
margin-bottom:10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete-comment-form {
|
||||||
|
position: relative;
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar {
|
||||||
|
max-width: 120px;
|
||||||
|
}
|
||||||
|
|
BIN
nyaa/static/img/avatar/default.png
Normal file
BIN
nyaa/static/img/avatar/default.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.6 KiB |
|
@ -7,7 +7,7 @@
|
||||||
{% set torrent_url = url_for('view_torrent', torrent_id=torrent.id) %}
|
{% set torrent_url = url_for('view_torrent', torrent_id=torrent.id) %}
|
||||||
<h1>
|
<h1>
|
||||||
Edit Torrent <a href="{{ torrent_url }}">#{{torrent.id}}</a>
|
Edit Torrent <a href="{{ torrent_url }}">#{{torrent.id}}</a>
|
||||||
{% if (torrent.user != None) and (torrent.user != editor) %}
|
{% if (torrent.user != None) and (torrent.user != g.user) %}
|
||||||
(by <a href="{{ url_for('view_user', user_name=torrent.user.username) }}">{{ torrent.user.username }}</a>)
|
(by <a href="{{ url_for('view_user', user_name=torrent.user.username) }}">{{ torrent.user.username }}</a>)
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</h1>
|
</h1>
|
||||||
|
@ -31,7 +31,7 @@
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label class="control-label">Torrent flags</label>
|
<label class="control-label">Torrent flags</label>
|
||||||
<div>
|
<div>
|
||||||
{% if editor.is_moderator %}
|
{% if g.user.is_moderator %}
|
||||||
<label class="btn btn-primary">
|
<label class="btn btn-primary">
|
||||||
{{ form.is_deleted }}
|
{{ form.is_deleted }}
|
||||||
Deleted
|
Deleted
|
||||||
|
@ -58,7 +58,7 @@
|
||||||
Anonymous
|
Anonymous
|
||||||
</label>
|
</label>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if editor.is_trusted %}
|
{% if g.user.is_trusted %}
|
||||||
<label class="btn btn-success" title="Mark torrent trusted">
|
<label class="btn btn-success" title="Mark torrent trusted">
|
||||||
{{ form.is_trusted }}
|
{{ form.is_trusted }}
|
||||||
Trusted
|
Trusted
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
{% extends "layout.html" %}
|
{% extends "layout.html" %}
|
||||||
{% block title %}{{ torrent.display_name }} :: {{ config.SITE_NAME }}{% endblock %}
|
{% block title %}{{ torrent.display_name }} :: {{ config.SITE_NAME }}{% endblock %}
|
||||||
{% block body %}
|
{% block body %}
|
||||||
|
{% from "_formhelpers.html" import render_field %}
|
||||||
<div class="panel panel-{% if torrent.deleted %}deleted{% elif torrent.remake %}danger{% elif torrent.trusted %}success{% else %}default{% endif %}">
|
<div class="panel panel-{% if torrent.deleted %}deleted{% elif torrent.remake %}danger{% elif torrent.trusted %}success{% else %}default{% endif %}">
|
||||||
<div class="panel-heading"{% if torrent.hidden %} style="background-color: darkgray;"{% endif %}>
|
<div class="panel-heading"{% if torrent.hidden %} style="background-color: darkgray;"{% endif %}>
|
||||||
<h3 class="panel-title">
|
<h3 class="panel-title">
|
||||||
|
@ -28,9 +29,9 @@
|
||||||
{%- if not torrent.anonymous and torrent.user -%}
|
{%- if not torrent.anonymous and torrent.user -%}
|
||||||
<a href="{{ user_url }}">{{ torrent.user.username }}</a>
|
<a href="{{ user_url }}">{{ torrent.user.username }}</a>
|
||||||
{%- else -%}
|
{%- else -%}
|
||||||
Anonymous {% if torrent.user and (viewer == torrent.user or viewer.is_moderator) %}(<a href="{{ user_url }}">{{ torrent.user.username }}</a>){% endif %}
|
Anonymous {% if torrent.user and (g.user == torrent.user or g.user.is_moderator) %}(<a href="{{ user_url }}">{{ torrent.user.username }}</a>){% endif %}
|
||||||
{%- endif -%}
|
{%- endif -%}
|
||||||
{%- if viewer and viewer.is_superadmin and torrent.uploader_ip -%}
|
{%- if g.user and g.user.is_superadmin and torrent.uploader_ip -%}
|
||||||
({{ torrent.uploader_ip_string }})
|
({{ torrent.uploader_ip_string }})
|
||||||
{%- endif -%}
|
{%- endif -%}
|
||||||
</div>
|
</div>
|
||||||
|
@ -129,6 +130,60 @@
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-heading">
|
||||||
|
<h3 class="panel-title">
|
||||||
|
Comments - {{ comments|length }}
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
{% for comment in comments %}
|
||||||
|
<div class="panel panel-default comments-panel">
|
||||||
|
<div class="panel-body">
|
||||||
|
<div class="col-md-2">
|
||||||
|
<p>
|
||||||
|
{% set user_url = torrent.user and url_for('view_user', user_name=comment.user.username) %}
|
||||||
|
<a href="{{ user_url }}">{{ comment.user.username }}</a>
|
||||||
|
{% if comment.user.id == torrent.uploader_id and not torrent.anonymous %}
|
||||||
|
(uploader)
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
|
<p>{{ comment.user.userlevel_str }}</p>
|
||||||
|
<p><img class="avatar" src="{{ comment.user.gravatar_url() }}"></p>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-10">
|
||||||
|
<div class="row">
|
||||||
|
<small>{{ comment.created_time | timesince }}</small>
|
||||||
|
{% if g.user.is_moderator or g.user.id == comment.user_id %}
|
||||||
|
<form class="delete-comment-form" action="{{ url_for('delete_comment', torrent_id=torrent.id, comment_id=comment.id) }}" method="POST">
|
||||||
|
<button name="submit" type="submit" class="btn btn-danger btn-sm" title="Delete">Delete</button>
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
{# Escape newlines into html entities because CF strips blank newlines #}
|
||||||
|
<div id="torrent-comment{{ comment.id }}">{{ comment.text }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
var target = document.getElementById('torrent-comment{{ comment.id }}');
|
||||||
|
var text = target.innerHTML;
|
||||||
|
var reader = new commonmark.Parser({safe: true});
|
||||||
|
var writer = new commonmark.HtmlRenderer({safe: true, softbreak: '<br />'});
|
||||||
|
var parsed = reader.parse(text.trim());
|
||||||
|
target.innerHTML = writer.render(parsed);
|
||||||
|
</script>
|
||||||
|
{% endfor %}
|
||||||
|
{% if comment_form %}
|
||||||
|
<form class="comment-box" method="POST">
|
||||||
|
{{ comment_form.csrf_token }}
|
||||||
|
{{ render_field(comment_form.comment, class_='form-control') }}
|
||||||
|
<input type="submit" value="Submit" class="btn btn-success btn-sm">
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
var target = document.getElementById('torrent-description');
|
var target = document.getElementById('torrent-description');
|
||||||
var text = target.innerHTML;
|
var text = target.innerHTML;
|
||||||
|
|
|
@ -14,6 +14,7 @@ USED_TRACKERS = OrderedSet()
|
||||||
# Limit the amount of trackers added into .torrent files
|
# Limit the amount of trackers added into .torrent files
|
||||||
MAX_TRACKERS = 5
|
MAX_TRACKERS = 5
|
||||||
|
|
||||||
|
|
||||||
def read_trackers_from_file(file_object):
|
def read_trackers_from_file(file_object):
|
||||||
USED_TRACKERS.clear()
|
USED_TRACKERS.clear()
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue