From dd8cb4757ea313c0b2e5f0fd4db2d7300c2fa3c8 Mon Sep 17 00:00:00 2001 From: mreweilk Date: Tue, 4 Jul 2017 23:13:59 -0500 Subject: [PATCH] Admin Log 2.0 (#283) * Admin log added * Add admin log to top bar * Fixed some admin log bugs * Remove comment_id column because comments die when they are killed * Fix tabs in admin log template * Fixed sort of admin logs to be created_time desc * Fix navbar wrapping to a new line when 992px <= width <= 1200px * Put reports and admin log in "Admin" dropdown Applied ./lint.sh fixes Fixed long lines * Updated log to be text instead of id based to account for future deletions * Small fix in log message formatting --- .../versions/1add911660a6_admin_log_added.py | 44 ++++++++++++++++++ nyaa/forms.py | 4 +- nyaa/models.py | 46 ++++++++++++++++++- nyaa/routes.py | 43 +++++++++++++++-- nyaa/static/css/main.css | 15 ++++-- nyaa/templates/adminlog.html | 34 ++++++++++++++ nyaa/templates/layout.html | 11 ++++- 7 files changed, 185 insertions(+), 12 deletions(-) create mode 100644 migrations/versions/1add911660a6_admin_log_added.py create mode 100644 nyaa/templates/adminlog.html diff --git a/migrations/versions/1add911660a6_admin_log_added.py b/migrations/versions/1add911660a6_admin_log_added.py new file mode 100644 index 0000000..1bfcb17 --- /dev/null +++ b/migrations/versions/1add911660a6_admin_log_added.py @@ -0,0 +1,44 @@ +"""Admin log added + +Revision ID: 1add911660a6 +Revises: 7f064e009cab +Create Date: 2017-06-29 02:57:39.715965 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '1add911660a6' +down_revision = '7f064e009cab' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('nyaa_adminlog', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('created_time', sa.DateTime(), nullable=True), + sa.Column('log', sa.String(length=1024), nullable=False), + sa.Column('admin_id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['admin_id'], ['users.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('sukebei_adminlog', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('created_time', sa.DateTime(), nullable=True), + sa.Column('log', sa.String(length=1024), nullable=False), + sa.Column('admin_id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['admin_id'], ['users.id'], ), + sa.PrimaryKeyConstraint('id') + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('sukebei_adminlog') + op.drop_table('nyaa_adminlog') + # ### end Alembic commands ### diff --git a/nyaa/forms.py b/nyaa/forms.py index 700b5ec..494a5e2 100644 --- a/nyaa/forms.py +++ b/nyaa/forms.py @@ -10,7 +10,8 @@ from flask_wtf import FlaskForm from flask_wtf.file import FileField, FileRequired from wtforms import StringField, PasswordField, BooleanField, TextAreaField, SelectField,\ HiddenField -from wtforms.validators import DataRequired, Optional, Email, Length, EqualTo, ValidationError, StopValidation +from wtforms.validators import DataRequired, Optional, Email, Length, EqualTo, ValidationError,\ + StopValidation from wtforms.validators import Regexp # For DisabledSelectField @@ -49,6 +50,7 @@ def stop_on_validation_error(f): raise StopValidation(*e.args) from e return decorator + _username_validator = Regexp( r'^[a-zA-Z0-9_\-]+$', message='Your username must only consist of alphanumerics and _- (a-zA-Z0-9_-)') diff --git a/nyaa/models.py b/nyaa/models.py index 4480e72..602e2fa 100644 --- a/nyaa/models.py +++ b/nyaa/models.py @@ -563,6 +563,38 @@ class User(db.Model): return self.level >= UserLevelType.TRUSTED +class AdminLogBase(DeclarativeHelperBase): + __tablename_base__ = 'adminlog' + + id = db.Column(db.Integer, primary_key=True) + created_time = db.Column(db.DateTime(timezone=False), default=datetime.utcnow) + log = db.Column(db.String(length=1024), nullable=False) + + @declarative.declared_attr + def admin_id(cls): + return db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) + + def __init__(self, log, admin_id): + self.log = log + self.admin_id = admin_id + + def __repr__(self): + return '' % self.id + + @property + def created_utc_timestamp(self): + ''' Returns a UTC POSIX timestamp, as seconds ''' + return (self.created_time - UTC_EPOCH).total_seconds() + + @declarative.declared_attr + def admin(cls): + return db.relationship('User', uselist=False, lazy="joined") + + @classmethod + def all_logs(cls): + return cls.query + + class ReportStatus(IntEnum): IN_REVIEW = 0 VALID = 1 @@ -714,6 +746,15 @@ class SukebeiComment(CommentBase, db.Model): __flavor__ = 'Sukebei' +# AdminLog +class NyaaAdminLog(AdminLogBase, db.Model): + __flavor__ = 'Nyaa' + + +class SukebeiAdminLog(AdminLogBase, db.Model): + __flavor__ = 'Sukebei' + + # Report class NyaaReport(ReportBase, db.Model): __flavor__ = 'Nyaa' @@ -733,9 +774,10 @@ if app.config['SITE_FLAVOR'] == 'nyaa': MainCategory = NyaaMainCategory SubCategory = NyaaSubCategory Comment = NyaaComment + AdminLog = NyaaAdminLog Report = NyaaReport - TorrentNameSearch = NyaaTorrentNameSearch + elif app.config['SITE_FLAVOR'] == 'sukebei': Torrent = SukebeiTorrent TorrentFilelist = SukebeiTorrentFilelist @@ -745,6 +787,6 @@ elif app.config['SITE_FLAVOR'] == 'sukebei': MainCategory = SukebeiMainCategory SubCategory = SukebeiSubCategory Comment = SukebeiComment + AdminLog = SukebeiAdminLog Report = SukebeiReport - TorrentNameSearch = SukebeiTorrentNameSearch diff --git a/nyaa/routes.py b/nyaa/routes.py index eaf1e59..4af03dc 100644 --- a/nyaa/routes.py +++ b/nyaa/routes.py @@ -325,20 +325,26 @@ def view_user(user_name): if flask.request.method == 'GET': admin_form.user_class.data = default + url = flask.url_for('view_user', user_name=user.username) if flask.request.method == 'POST' and admin_form and admin_form.validate(): selection = admin_form.user_class.data - + log = None if selection == 'regular': user.level = models.UserLevelType.REGULAR + log = "[{}]({}) changed to regular user".format(user_name, url) elif selection == 'trusted': user.level = models.UserLevelType.TRUSTED + log = "[{}]({}) changed to trusted user".format(user_name, url) elif selection == 'moderator': user.level = models.UserLevelType.MODERATOR + log = "[{}]({}) changed to moderator user".format(user_name, url) + adminlog = models.AdminLog(log=log, admin_id=flask.g.user.id) db.session.add(user) + db.session.add(adminlog) db.session.commit() - return flask.redirect(flask.url_for('view_user', user_name=user.username)) + return flask.redirect(url) user_level = ['Regular', 'Trusted', 'Moderator', 'Administrator'][user.level] @@ -701,11 +707,17 @@ def delete_comment(torrent_id, comment_id): db.session.delete(comment) db.session.flush() torrent.update_comment_count() + + url = flask.url_for('view_torrent', torrent_id=torrent.id) + if flask.g.user.is_moderator: + log = "Comment deleted on torrent [#{}]({})".format(torrent.id, url) + adminlog = models.AdminLog(log=log, admin_id=flask.g.user.id) + db.session.add(adminlog) db.session.commit() flask.flash('Comment successfully deleted.', 'success') - return flask.redirect(flask.url_for('view_torrent', torrent_id=torrent_id)) + return flask.redirect(url) @app.route('/view//edit', methods=['GET', 'POST']) @@ -745,13 +757,22 @@ def edit_torrent(torrent_id): if flask.g.user.is_moderator: torrent.deleted = form.is_deleted.data + if flask.g.user.is_moderator: + log = "Torrent [#{}]({}) marked as ".format(torrent.id, url) + url = flask.url_for('view_torrent', torrent_id=torrent.id) + adminlog = models.AdminLog(log=(log+"deleted" + if torrent.deleted else + log+"undeleted"), + admin_id=flask.g.user.id) + db.session.add(adminlog) + 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(flask.url_for('view_torrent', torrent_id=torrent.id)) + return flask.redirect(url) else: if flask.request.method != 'POST': # Fill form data only if the POST didn't fail @@ -821,6 +842,20 @@ def submit_report(torrent_id): return flask.redirect(flask.url_for('view_torrent', torrent_id=torrent_id)) +@app.route('/adminlog', methods=['GET']) +def view_adminlog(): + if not flask.g.user or not flask.g.user.is_moderator: + flask.abort(403) + + page = flask.request.args.get('p', flask.request.args.get('offset', 1, int), int) + logs = models.AdminLog.all_logs() \ + .order_by(models.AdminLog.created_time.desc()) \ + .paginate(page=page, per_page=20) + + return flask.render_template('adminlog.html', + adminlog=logs) + + @app.route('/reports', methods=['GET', 'POST']) def view_reports(): if not flask.g.user or not flask.g.user.is_moderator: diff --git a/nyaa/static/css/main.css b/nyaa/static/css/main.css index 13fcb0a..49708ce 100644 --- a/nyaa/static/css/main.css +++ b/nyaa/static/css/main.css @@ -160,7 +160,7 @@ table.torrent-list tbody .comments i { -ms-flex-order: 3; order: 3; width: 99%; - padding-right: 4em; + padding-right: 1em; border-left: 0; } @@ -380,7 +380,7 @@ h6:hover .header-anchor { display: inline-block; } -@media (max-width: 991px){ +@media (max-width: 991px) { .panel-body .col-md-5 { margin-left: 20px; margin-bottom: 10px; @@ -397,11 +397,11 @@ h6:hover .header-anchor { } } -@media (min-width: 992px){ +@media (min-width: 992px) { .panel-body .col-md-5 { word-wrap: break-word; } - + .search-btn { top: 0; width: auto; @@ -419,3 +419,10 @@ table.table > tbody > tr.reports-row > td { td.report-action-column { min-width: 150px; } + +@media (min-width: 992px) and (max-width: 1199px) { + /* Collapse navbar search form so it doesn't wrap to a new line */ + .search-container { + width: 400px; + } +} diff --git a/nyaa/templates/adminlog.html b/nyaa/templates/adminlog.html new file mode 100644 index 0000000..8b3e5bf --- /dev/null +++ b/nyaa/templates/adminlog.html @@ -0,0 +1,34 @@ +{% extends "layout.html" %} +{% block title %}Admin Log :: {{ config.SITE_NAME }}{% endblock %} +{% block body %} +{% from "_formhelpers.html" import render_field %} +
+ + + + + + + + + + + {% for log in adminlog.items %} + + + + + + + {% endfor %} + +
#Moderator/AdminLogDate
{{ log.id }} + {{ log.admin.username }} +
{{ log.log }}
{{ log.created_time }}
+
+ + +{% endblock %} diff --git a/nyaa/templates/layout.html b/nyaa/templates/layout.html index 05705d0..8239c43 100644 --- a/nyaa/templates/layout.html +++ b/nyaa/templates/layout.html @@ -89,7 +89,16 @@
  • Fun
  • {% endif %} {% if g.user.is_moderator %} -
  • Reports
  • + {% endif %}