mirror of
https://gitlab.com/SIGBUS/nyaa.git
synced 2024-12-22 08:40:00 +00:00
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
This commit is contained in:
parent
1cee6cb647
commit
dd8cb4757e
44
migrations/versions/1add911660a6_admin_log_added.py
Normal file
44
migrations/versions/1add911660a6_admin_log_added.py
Normal file
|
@ -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 ###
|
|
@ -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_-)')
|
||||
|
|
|
@ -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 '<AdminLog %r>' % 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
|
||||
|
|
|
@ -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/<int:torrent_id>/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:
|
||||
|
|
|
@ -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,7 +397,7 @@ h6:hover .header-anchor {
|
|||
}
|
||||
}
|
||||
|
||||
@media (min-width: 992px){
|
||||
@media (min-width: 992px) {
|
||||
.panel-body .col-md-5 {
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
34
nyaa/templates/adminlog.html
Normal file
34
nyaa/templates/adminlog.html
Normal file
|
@ -0,0 +1,34 @@
|
|||
{% extends "layout.html" %}
|
||||
{% block title %}Admin Log :: {{ config.SITE_NAME }}{% endblock %}
|
||||
{% block body %}
|
||||
{% from "_formhelpers.html" import render_field %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered table-hover table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Moderator/Admin</th>
|
||||
<th>Log</th>
|
||||
<th>Date</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for log in adminlog.items %}
|
||||
<tr>
|
||||
<td>{{ log.id }}</td>
|
||||
<td>
|
||||
<a href="{{ url_for('view_user', user_name=log.admin.username) }}">{{ log.admin.username }}</a>
|
||||
</td>
|
||||
<td><div markdown-text>{{ log.log }}</div></td>
|
||||
<td>{{ log.created_time }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class=pagination>
|
||||
{% from "bootstrap/pagination.html" import render_pagination %}
|
||||
{{ render_pagination(adminlog) }}
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -89,7 +89,16 @@
|
|||
<li><a href="//{{ config.EXTERNAL_URLS['main'] }}">Fun</a></li>
|
||||
{% endif %}
|
||||
{% if g.user.is_moderator %}
|
||||
<li><a href="{{ url_for('view_reports') }}">Reports</a> </li>
|
||||
<li class="dropdown">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">
|
||||
Admin
|
||||
<span class="caret"></span>
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li {% if request.path == url_for('view_reports') %}class="active"{% endif %}><a href="{{ url_for('view_reports') }}">Reports</a></li>
|
||||
<li {% if request.path == url_for('view_adminlog') %}class="active"{% endif %}><a href="{{ url_for('view_adminlog') }}">Log</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
|
||||
|
|
Loading…
Reference in a new issue