Make comments great again.

This commit is contained in:
nyaadev 2017-05-22 23:01:23 +02:00
parent aab3eaccaa
commit b7144f80f9
8 changed files with 137 additions and 67 deletions

View 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 ###

View File

@ -128,18 +128,16 @@ class DisabledSelectField(SelectField):
class CommentForm(FlaskForm):
comment = TextAreaField('Make a comment', [
Length(max=255, message='Comment must be at most %(max)d characters long.'),
Length(min=3, max=255, message='Comment must be at least %(min)d characters '
'long and %(max)d at most.'),
DataRequired()
])
is_anonymous = BooleanField('Anonymous')
class EditForm(FlaskForm):
display_name = StringField('Torrent display name', [
Length(min=3, max=255,
message='Torrent display name must be at least %(min)d characters long '
'and %(max)d at most.')
Length(min=3, max=255, message='Torrent display name must be at least %(min)d characters '
'long and %(max)d at most.')
])
category = DisabledSelectField('Category')

View File

@ -1,3 +1,4 @@
import flask
from enum import Enum, IntEnum
from datetime import datetime, timezone
from nyaa import app, db
@ -11,7 +12,8 @@ from ipaddress import ip_address
import re
import base64
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']:
from sqlalchemy.dialects import mysql
@ -99,6 +101,8 @@ class Torrent(db.Model):
cascade="all, delete-orphan", back_populates='torrent', lazy='joined')
trackers = db.relationship('TorrentTrackers', uselist=True,
cascade="all, delete-orphan", lazy='joined')
comments = db.relationship('Comment', uselist=True,
cascade="all, delete-orphan")
def __repr__(self):
return '<{0} #{1.id} \'{1.display_name}\' {1.filesize}b>'.format(type(self).__name__, self)
@ -321,14 +325,13 @@ class Comment(db.Model):
__tablename__ = DB_TABLE_PREFIX + 'comments'
id = db.Column(db.Integer, primary_key=True)
torrent = db.Column(db.Integer, db.ForeignKey(
DB_TABLE_PREFIX + 'torrents.id'), primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey(
'users.id', ondelete='CASCADE'))
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')
user = db.relationship('User', uselist=False, back_populates='comments', lazy="joined")
def __repr__(self):
return '<Comment %r>' % self.id
@ -396,11 +399,21 @@ class User(db.Model):
# from http://en.gravatar.com/site/implement/images/python/
size = 120
# construct the url
gravatar_url = 'https://www.gravatar.com/avatar/' + \
hashlib.md5(self.email.encode('utf-8').lower()).hexdigest() + '?'
gravatar_url += urllib.parse.urlencode({'d': config.DEFAULT_AVATAR_URL, 's': str(size)})
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
def ip_string(self):
if self.last_login_ip:

View File

@ -7,6 +7,7 @@ from nyaa import torrents
from nyaa import backend
from nyaa import api_handler
from nyaa.search import search_elastic, search_db
from sqlalchemy.orm import joinedload
import config
import json
@ -572,53 +573,52 @@ def upload():
@app.route('/view/<int:torrent_id>')
def view_torrent(torrent_id):
torrent = models.Torrent.by_id(torrent_id)
form = forms.CommentForm()
viewer = flask.g.user
torrent = models.Torrent.query \
.options(joinedload('filelist'),
joinedload('comments')) \
.filter_by(id=torrent_id) \
.first()
if not torrent:
flask.abort(404)
# 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)
# 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
if torrent.filelist:
files = json.loads(torrent.filelist.filelist_blob.decode('utf-8'))
comments = models.Comment.query.filter_by(torrent=torrent_id)
comment_count = comments.count()
comment_form = None
if flask.g.user:
comment_form = forms.CommentForm()
return flask.render_template('view.html', torrent=torrent,
files=files,
viewer=viewer,
form=form,
comments=comments,
comment_count=comment_count,
comment_form=comment_form,
comments=torrent.comments,
can_edit=can_edit)
@app.route('/view/<int:torrent_id>/submit_comment', methods=['POST'])
@app.route('/view/<int:torrent_id>/comment', methods=['POST'])
def submit_comment(torrent_id):
form = forms.CommentForm(flask.request.form)
if not flask.g.user:
flask.abort(403)
if flask.request.method == 'POST' and form.validate():
torrent = models.Torrent.by_id(torrent_id)
if not torrent:
flask.abort(404)
form = forms.CommentForm(flask.request.form)
if form.validate():
comment_text = (form.comment.data or '').strip()
# Null entry for User just means Anonymous
if flask.g.user is None or form.is_anonymous.data:
current_user_id = None
else:
current_user_id = flask.g.user.id
comment = models.Comment(
torrent=torrent_id,
user_id=current_user_id,
torrent_id=torrent_id,
user_id=flask.g.user.id,
text=comment_text)
db.session.add(comment)
@ -627,14 +627,23 @@ def submit_comment(torrent_id):
return flask.redirect(flask.url_for('view_torrent', torrent_id=torrent_id))
@app.route('/view/<int:torrent_id>/delete_comment/<int:comment_id>')
@app.route('/view/<int:torrent_id>/comment/<int:comment_id>/delete', methods=['POST'])
def delete_comment(torrent_id, comment_id):
if flask.g.user is not None and flask.g.user.is_admin:
models.Comment.query.filter_by(id=comment_id).delete()
db.session.commit()
else:
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))
@ -650,11 +659,11 @@ def edit_torrent(torrent_id):
flask.abort(404)
# 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)
# 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)
if flask.request.method == 'POST' and form.validate():
@ -670,9 +679,9 @@ def edit_torrent(torrent_id):
torrent.complete = form.is_complete.data
torrent.anonymous = form.is_anonymous.data
if editor.is_trusted:
if flask.g.user.is_trusted:
torrent.trusted = form.is_trusted.data
if editor.is_moderator:
if flask.g.user.is_moderator:
torrent.deleted = form.is_deleted.data
db.session.commit()
@ -700,8 +709,7 @@ def edit_torrent(torrent_id):
return flask.render_template('edit.html',
form=form,
torrent=torrent,
editor=editor)
torrent=torrent)
@app.route('/view/<int:torrent_id>/magnet')
@ -793,7 +801,7 @@ def _create_user_class_choices(user):
return default, choices
# Modified from: http://flask.pocoo.org/snippets/33/
@app.template_filter()
def timesince(dt, default='just now'):
"""
@ -823,6 +831,8 @@ def timesince(dt, default='just now'):
return default
# #################################### STATIC PAGES ####################################
@app.route('/rules', methods=['GET'])
def site_rules():
return flask.render_template('rules.html')

View File

@ -233,7 +233,7 @@ ul.nav-tabs#profileTabs {
margin-bottom:10px;
}
.delete-btn {
.delete-comment-form {
position: relative;
float: right;
}

View File

@ -7,7 +7,7 @@
{% 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) %}
{% 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>)
{% endif %}
</h1>
@ -31,7 +31,7 @@
<div class="col-md-6">
<label class="control-label">Torrent flags</label>
<div>
{% if editor.is_moderator %}
{% if g.user.is_moderator %}
<label class="btn btn-primary">
{{ form.is_deleted }}
Deleted
@ -58,7 +58,7 @@
Anonymous
</label>
{% endif %}
{% if editor.is_trusted %}
{% if g.user.is_trusted %}
<label class="btn btn-success" title="Mark torrent trusted">
{{ form.is_trusted }}
Trusted

View File

@ -29,9 +29,9 @@
{%- 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_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 -%}
{%- 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 }})
{%- endif -%}
</div>
@ -133,7 +133,7 @@
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">
Comments - {{ comment_count }}
Comments - {{ comments|length }}
</h3>
</div>
{% for comment in comments %}
@ -148,15 +148,15 @@
{% endif %}
</p>
<p>{{ comment.user.userlevel_str }}</p>
<p>
<img class="avatar" src="{{ comment.user.gravatar_url() }}">
</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>
<small>{{ comment.created_time | timesince }}</small>
{% if g.user.is_moderator or g.user.id == comment.user_id %}
<div class="btn btn-danger btn-sm delete-btn" title="Delete" onclick='location.href = "{{ url_for('delete_comment', comment_id=comment.id, torrent_id=torrent.id)}}";'>Delete</div>
<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">
@ -175,10 +175,10 @@
target.innerHTML = writer.render(parsed);
</script>
{% endfor %}
{% if g.user %}
<form method="POST" class="comment-box">
{{ form.csrf_token }}
{{ render_field(form.comment, class_='form-control') }}
{% if comment_form %}
<form action="{{ url_for('submit_comment', torrent_id=torrent.id) }}" method="POST" class="comment-box">
{{ 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 %}

View File

@ -14,6 +14,7 @@ USED_TRACKERS = OrderedSet()
# Limit the amount of trackers added into .torrent files
MAX_TRACKERS = 5
def read_trackers_from_file(file_object):
USED_TRACKERS.clear()