diff options
23 files changed, 509 insertions, 189 deletions
diff --git a/mediagoblin/admin/views.py b/mediagoblin/admin/views.py deleted file mode 100644 index 97970577..00000000 --- a/mediagoblin/admin/views.py +++ /dev/null @@ -1,115 +0,0 @@ -# GNU MediaGoblin -- federated, autonomous media hosting -# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. - -from werkzeug.exceptions import Forbidden - -from mediagoblin.db.models import (MediaEntry, User, MediaComment, \ - CommentReport, ReportBase, Privilege) -from mediagoblin.decorators import require_admin_login -from mediagoblin.tools.response import render_to_response - -@require_admin_login -def admin_media_processing_panel(request): - ''' - Show the global media processing panel for this instance - ''' - processing_entries = MediaEntry.query.filter_by(state = u'processing').\ - order_by(MediaEntry.created.desc()) - - # Get media entries which have failed to process - failed_entries = MediaEntry.query.filter_by(state = u'failed').\ - order_by(MediaEntry.created.desc()) - - processed_entries = MediaEntry.query.filter_by(state = u'processed').\ - order_by(MediaEntry.created.desc()).limit(10) - - # Render to response - return render_to_response( - request, - 'mediagoblin/admin/media_panel.html', - {'processing_entries': processing_entries, - 'failed_entries': failed_entries, - 'processed_entries': processed_entries}) - -@require_admin_login -def admin_users_panel(request): - ''' - Show the global panel for monitoring users in this instance - ''' - user_list = User.query - - return render_to_response( - request, - 'mediagoblin/admin/user_panel.html', - {'user_list': user_list}) - -@require_admin_login -def admin_users_detail(request): - ''' - Shows details about a particular user. - ''' - user = User.query.filter_by(username=request.matchdict['user']).first() - privileges = Privilege.query - active_reports = user.reports_filed_on.filter( - ReportBase.resolved==None).limit(5) - closed_reports = user.reports_filed_on.filter( - ReportBase.resolved!=None).all() - - return render_to_response( - request, - 'mediagoblin/admin/user.html', - {'user':user, - 'privileges':privileges, - 'reports':active_reports}) - -@require_admin_login -def admin_reports_panel(request): - ''' - Show the global panel for monitoring reports filed against comments or - media entries for this instance. - ''' - report_list = ReportBase.query.filter( - ReportBase.resolved==None).order_by( - ReportBase.created.desc()).limit(10) - closed_report_list = ReportBase.query.filter( - ReportBase.resolved!=None).order_by( - ReportBase.created.desc()).limit(10) - - # Render to response - return render_to_response( - request, - 'mediagoblin/admin/report_panel.html', - {'report_list':report_list, - 'closed_report_list':closed_report_list}) - -@require_admin_login -def admin_reports_detail(request): - report = ReportBase.query.get(request.matchdict['report_id']) - if report.discriminator == 'comment_report': - comment = MediaComment.query.get(report.comment_id) - media_entry = None - elif report.discriminator == 'media_report': - media_entry = MediaEntry.query.get(report.media_entry_id) - comment = None - - return render_to_response( - request, - 'mediagoblin/admin/report.html', - {'report':report, - 'media_entry':media_entry, - 'comment':comment}) - - diff --git a/mediagoblin/db/migrations.py b/mediagoblin/db/migrations.py index a32f5932..3e6791c4 100644 --- a/mediagoblin/db/migrations.py +++ b/mediagoblin/db/migrations.py @@ -300,6 +300,7 @@ class ReportBase_v0(declarative_base()): reported_user_id = Column(Integer, ForeignKey(User.id), nullable=False) created = Column(DateTime, nullable=False, default=datetime.datetime.now) resolved = Column(DateTime) + result = Column(UnicodeText) discriminator = Column('type', Unicode(50)) __mapper_args__ = {'polymorphic_on': discriminator} diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index e4c97a2c..01078db8 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -239,8 +239,8 @@ class MediaEntry(Base, MediaEntryMixin): This will *not* automatically delete unused collections, which can remain empty... - :param del_orphan_tags: True/false if we delete unused Tags too - :param commit: True/False if this should end the db transaction""" + :keyword del_orphan_tags: True/false if we delete unused Tags too + :keyword commit: True/False if this should end the db transaction""" # User's CollectionItems are automatically deleted via "cascade". # Comments on this Media are deleted by cascade, hopefully. @@ -487,6 +487,14 @@ class ProcessingMetaData(Base): class ReportBase(Base): """ + This is the basic report table which the other reports are based off of. + :keyword reporter_id + :keyword report_content + :keyword reported_user_id + :keyword created + :keyword resolved + :keyword result + :keyword discriminator """ __tablename__ = 'core__reports' @@ -508,6 +516,7 @@ class ReportBase(Base): primaryjoin="User.id==ReportBase.reported_user_id") created = Column(DateTime, nullable=False, default=datetime.datetime.now()) resolved = Column(DateTime) + result = Column(UnicodeText) discriminator = Column('type', Unicode(50)) __mapper_args__ = {'polymorphic_on': discriminator} @@ -551,13 +560,13 @@ class UserBan(Base): the reason why they are banned and when (if ever) the ban will be lifted - :param user_id Holds the id of the user this object is + :keyword user_id Holds the id of the user this object is attached to. This is a one-to-one relationship. - :param expiration_date Holds the date that the ban will be lifted. + :keyword expiration_date Holds the date that the ban will be lifted. If this is null, the ban is permanent unless a moderator manually lifts it. - :param reason Holds the reason why the user was banned. + :keyword reason Holds the reason why the user was banned. """ __tablename__ = 'core__user_bans' @@ -568,6 +577,17 @@ class UserBan(Base): class Privilege(Base): + """ + The Privilege table holds all of the different privileges a user can hold. + If a user 'has' a privilege, the User object is in a relationship with the + privilege object. + + :keyword privilege_name Holds a unicode object that is the recognizable + name of this privilege. This is the column + used for identifying whether or not a user + has a necessary privilege or not. + + """ __tablename__ = 'core__privileges' id = Column(Integer, nullable=False, primary_key=True) @@ -578,12 +598,30 @@ class Privilege(Base): secondary="core__privileges_users") def __init__(self, privilege_name): + ''' + Currently consructors are required for tables that are initialized thru + the FOUNDATIONS system. This is because they need to be able to be con- + -structed by a list object holding their arg*s + ''' self.privilege_name = privilege_name def __repr__(self): return "<Privilege %s>" % (self.privilege_name) + def is_admin_or_moderator(self): + ''' + This method is necessary to check if a user is able to take moderation + actions. + ''' + + return (self.privilege_name==u'admin' or + self.privilege_name==u'moderator') + class PrivilegeUserAssociation(Base): + ''' + This table holds the many-to-many relationship between User and Privilege + ''' + __tablename__ = 'core__privileges_users' privilege_id = Column( diff --git a/mediagoblin/decorators.py b/mediagoblin/decorators.py index fefbccef..b39b36f5 100644 --- a/mediagoblin/decorators.py +++ b/mediagoblin/decorators.py @@ -21,8 +21,9 @@ from werkzeug.exceptions import Forbidden, NotFound from werkzeug.urls import url_quote from mediagoblin import mg_globals as mgg -from mediagoblin.db.models import MediaEntry, User, MediaComment, Privilege -from mediagoblin.tools.response import redirect, render_404 +from mediagoblin.db.models import MediaEntry, User, MediaComment, Privilege, \ + UserBan +from mediagoblin.tools.response import redirect, render_404, render_user_banned def require_active_login(controller): @@ -64,6 +65,7 @@ def active_user_from_url(controller): return wrapper def user_has_privilege(privilege_name): + def user_has_privilege_decorator(controller): @wraps(controller) def wrapper(request, *args, **kwargs): @@ -71,7 +73,9 @@ def user_has_privilege(privilege_name): privileges_of_user = Privilege.query.filter( Privilege.all_users.any( User.id==user_id)) - if not privileges_of_user.filter( + if UserBan.query.filter(UserBan.user_id==user_id).count(): + return render_user_banned(request) + elif not privileges_of_user.filter( Privilege.privilege_name==privilege_name).count(): raise Forbidden() @@ -271,14 +275,18 @@ def get_workbench(func): return new_func -def require_admin_login(controller): +def require_admin_or_moderator_login(controller): """ - Require an login from an administrator. + Require an login from an administrator or a moderator. """ @wraps(controller) def new_controller_func(request, *args, **kwargs): + admin_privilege = Privilege.one({'privilege_name':u'admin'}) + moderator_privilege = Privilege.one({'privilege_name':u'moderator'}) if request.user and \ - not request.user.is_admin: + not admin_privilege in request.user.all_privileges and \ + not moderator_privilege in request.user.all_privileges: + raise Forbidden() elif not request.user: next_url = urljoin( @@ -293,3 +301,18 @@ def require_admin_login(controller): return new_controller_func +def user_not_banned(controller): + """ + Requires that the user has not been banned. Otherwise redirects to the page + explaining why they have been banned + """ + @wraps(controller) + def wrapper(request, *args, **kwargs): + if request.user: + user_banned = UserBan.query.get(request.user.id) + if user_banned: + return render_user_banned(request) + return controller(request, *args, **kwargs) + + return wrapper + diff --git a/mediagoblin/gmg_commands/users.py b/mediagoblin/gmg_commands/users.py index ccc4da73..5c19d3a9 100644 --- a/mediagoblin/gmg_commands/users.py +++ b/mediagoblin/gmg_commands/users.py @@ -55,6 +55,13 @@ def adduser(args): entry.pw_hash = auth_lib.bcrypt_gen_password_hash(args.password) entry.status = u'active' entry.email_verified = True + default_privileges = [ + db.Privilege.one({'privilege_name':u'commenter'}), + db.Privilege.one({'privilege_name':u'uploader'}), + db.Privilege.one({'privilege_name':u'reporter'}), + db.Privilege.one({'privilege_name':u'active'}) +] + entry.all_privileges = default_privileges entry.save() print "User created (and email marked as verified)" diff --git a/mediagoblin/admin/__init__.py b/mediagoblin/moderation/__init__.py index 719b56e7..719b56e7 100644 --- a/mediagoblin/admin/__init__.py +++ b/mediagoblin/moderation/__init__.py diff --git a/mediagoblin/moderation/forms.py b/mediagoblin/moderation/forms.py new file mode 100644 index 00000000..0a91b9b4 --- /dev/null +++ b/mediagoblin/moderation/forms.py @@ -0,0 +1,40 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import wtforms +from mediagoblin.tools.translate import lazy_pass_to_ugettext as _ + +ACTION_CHOICES = [(_(u'takeaway'),_('Take away privilege')), + (_(u'userban'),_('Ban the user')), + (_(u'closereport'),_('Close the report without taking an action'))] + +class PrivilegeAddRemoveForm(wtforms.Form): + giving_privilege = wtforms.HiddenField('',[wtforms.validators.required()]) + privilege_name = wtforms.HiddenField('',[wtforms.validators.required()]) + +class ReportResolutionForm(wtforms.Form): + action_to_resolve = wtforms.RadioField( + _('What action will you take to resolve this report'), + validators=[wtforms.validators.required()], + choices=ACTION_CHOICES) + targeted_user = wtforms.HiddenField('', + validators=[wtforms.validators.required()]) + user_banned_until = wtforms.DateField( + _('User will be banned until:'), + format='%Y-%m-%d', + validators=[wtforms.validators.optional()]) + resolution_content = wtforms.TextAreaField() + diff --git a/mediagoblin/moderation/routing.py b/mediagoblin/moderation/routing.py new file mode 100644 index 00000000..f177c32a --- /dev/null +++ b/mediagoblin/moderation/routing.py @@ -0,0 +1,35 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +moderation_routes = [ + ('mediagoblin.moderation.media_panel', + '/media/', + 'mediagoblin.moderation.views:moderation_media_processing_panel'), + ('mediagoblin.moderation.users', + '/users/', + 'mediagoblin.moderation.views:moderation_users_panel'), + ('mediagoblin.moderation.reports', + '/reports/', + 'mediagoblin.moderation.views:moderation_reports_panel'), + ('mediagoblin.moderation.users_detail', + '/users/<string:user>/', + 'mediagoblin.moderation.views:moderation_users_detail'), + ('mediagoblin.moderation.give_or_take_away_privilege', + '/users/<string:user>/privilege/', + 'mediagoblin.moderation.views:give_or_take_away_privilege'), + ('mediagoblin.moderation.reports_detail', + '/reports/<int:report_id>/', + 'mediagoblin.moderation.views:moderation_reports_detail')] diff --git a/mediagoblin/moderation/views.py b/mediagoblin/moderation/views.py new file mode 100644 index 00000000..6f6318bc --- /dev/null +++ b/mediagoblin/moderation/views.py @@ -0,0 +1,200 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from werkzeug.exceptions import Forbidden + +from mediagoblin.db.models import (MediaEntry, User, MediaComment, \ + CommentReport, ReportBase, Privilege, \ + UserBan) +from mediagoblin.decorators import (require_admin_or_moderator_login, \ + active_user_from_url) +from mediagoblin.tools.response import render_to_response, redirect +from mediagoblin.moderation import forms as moderation_forms +from datetime import datetime + +@require_admin_or_moderator_login +def moderation_media_processing_panel(request): + ''' + Show the global media processing panel for this instance + ''' + processing_entries = MediaEntry.query.filter_by(state = u'processing').\ + order_by(MediaEntry.created.desc()) + + # Get media entries which have failed to process + failed_entries = MediaEntry.query.filter_by(state = u'failed').\ + order_by(MediaEntry.created.desc()) + + processed_entries = MediaEntry.query.filter_by(state = u'processed').\ + order_by(MediaEntry.created.desc()).limit(10) + + # Render to response + return render_to_response( + request, + 'mediagoblin/moderation/media_panel.html', + {'processing_entries': processing_entries, + 'failed_entries': failed_entries, + 'processed_entries': processed_entries}) + +@require_admin_or_moderator_login +def moderation_users_panel(request): + ''' + Show the global panel for monitoring users in this instance + ''' + user_list = User.query + + return render_to_response( + request, + 'mediagoblin/moderation/user_panel.html', + {'user_list': user_list}) + +@require_admin_or_moderator_login +def moderation_users_detail(request): + ''' + Shows details about a particular user. + ''' + user = User.query.filter_by(username=request.matchdict['user']).first() + active_reports = user.reports_filed_on.filter( + ReportBase.resolved==None).limit(5) + closed_reports = user.reports_filed_on.filter( + ReportBase.resolved!=None).all() + privileges = Privilege.query + + return render_to_response( + request, + 'mediagoblin/moderation/user.html', + {'user':user, + 'privileges':privileges, + 'reports':active_reports}) + +@require_admin_or_moderator_login +def moderation_reports_panel(request): + ''' + Show the global panel for monitoring reports filed against comments or + media entries for this instance. + ''' + report_list = ReportBase.query.filter( + ReportBase.resolved==None).order_by( + ReportBase.created.desc()).limit(10) + closed_report_list = ReportBase.query.filter( + ReportBase.resolved!=None).order_by( + ReportBase.created.desc()).limit(10) + + # Render to response + return render_to_response( + request, + 'mediagoblin/moderation/report_panel.html', + {'report_list':report_list, + 'closed_report_list':closed_report_list}) + +@require_admin_or_moderator_login +def moderation_reports_detail(request): + """ + This is the page an admin or moderator goes to see the details of a report. + The report can be resolved or unresolved. This is also the page that a mod- + erator would go to to take an action to resolve a report. + """ + form = moderation_forms.ReportResolutionForm(request.form) + report = ReportBase.query.get(request.matchdict['report_id']) + + if request.method == "POST" and form.validate(): + user = User.query.get(form.targeted_user.data) + if form.action_to_resolve.data == u'takeaway': + if report.discriminator == u'comment_report': + privilege = Privilege.one({'privilege_name':u'commenter'}) + form.resolution_content.data += \ + u"<br>%s took away %s\'s commenting privileges" % ( + request.user.username, + user.username) + else: + privilege = Privilege.one({'privilege_name':u'uploader'}) + form.resolution_content.data += \ + u"<br>%s took away %s\'s media uploading privileges" % ( + request.user.username, + user.username) + user.all_privileges.remove(privilege) + user.save() + report.result = form.resolution_content.data + report.resolved = datetime.now() + report.save() + + elif form.action_to_resolve.data == u'userban': + reason = form.resolution_content.data + \ + "<br>"+request.user.username + user_ban = UserBan( + user_id=form.targeted_user.data, + expiration_date=form.user_banned_until.data, + reason= form.resolution_content.data) + user_ban.save() + if not form.user_banned_until == "": + form.resolution_content.data += \ + u"<br>%s banned user %s until %s." % ( + request.user.username, + user.username, + form.user_banned_until.data) + else: + form.resolution_content.data += \ + u"<br>%s banned user %s indefinitely." % ( + request.user.username, + user.username, + form.user_banned_until.data) + + report.result = form.resolution_content.data + report.resolved = datetime.now() + report.save() + + else: + pass + + return redirect( + request, + 'mediagoblin.moderation.users_detail', + user=user.username) + + if report.discriminator == 'comment_report': + comment = MediaComment.query.get(report.comment_id) + media_entry = None + elif report.discriminator == 'media_report': + media_entry = MediaEntry.query.get(report.media_entry_id) + comment = None + + form.targeted_user.data = report.reported_user_id + + return render_to_response( + request, + 'mediagoblin/moderation/report.html', + {'report':report, + 'media_entry':media_entry, + 'comment':comment, + 'form':form}) + +@require_admin_or_moderator_login +@active_user_from_url +def give_or_take_away_privilege(request, url_user): + ''' + A form action to give or take away a particular privilege from a user + ''' + form = moderation_forms.PrivilegeAddRemoveForm(request.form) + if request.method == "POST" and form.validate(): + privilege = Privilege.one({'privilege_name':form.privilege_name.data}) + if privilege in url_user.all_privileges is True: + url_user.all_privileges.remove(privilege) + else: + url_user.all_privileges.append(privilege) + url_user.save() + return redirect( + request, + 'mediagoblin.moderation.users_detail', + user=url_user.username) diff --git a/mediagoblin/routing.py b/mediagoblin/routing.py index a650f22f..0642ad8e 100644 --- a/mediagoblin/routing.py +++ b/mediagoblin/routing.py @@ -18,7 +18,7 @@ import logging from mediagoblin.tools.routing import add_route, mount, url_map from mediagoblin.tools.pluginapi import PluginManager -from mediagoblin.admin.routing import admin_routes +from mediagoblin.moderation.routing import moderation_routes from mediagoblin.auth.routing import auth_routes @@ -28,7 +28,7 @@ _log = logging.getLogger(__name__) def get_url_map(): add_route('index', '/', 'mediagoblin.views:root_view') mount('/auth', auth_routes) - mount('/a', admin_routes) + mount('/mod', moderation_routes) import mediagoblin.submit.routing import mediagoblin.user_pages.routing diff --git a/mediagoblin/static/css/base.css b/mediagoblin/static/css/base.css index 1cded530..343648d8 100644 --- a/mediagoblin/static/css/base.css +++ b/mediagoblin/static/css/base.css @@ -402,6 +402,8 @@ a.report_authorlink, a.report_whenlink { color: #D486B1; } +ul#action_to_resolve {list-style:none; margin-left:10px;} + /* media galleries */ .media_thumbnail { diff --git a/mediagoblin/admin/routing.py b/mediagoblin/templates/mediagoblin/banned.html index c7ca5b92..4eda0540 100644 --- a/mediagoblin/admin/routing.py +++ b/mediagoblin/templates/mediagoblin/banned.html @@ -1,3 +1,4 @@ +{# # GNU MediaGoblin -- federated, autonomous media hosting # Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. # @@ -13,20 +14,15 @@ # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. +#} +{% extends "mediagoblin/base.html" %} -admin_routes = [ - ('mediagoblin.admin.media_panel', - '/media', - 'mediagoblin.admin.views:admin_media_processing_panel'), - ('mediagoblin.admin.users', - '/users', - 'mediagoblin.admin.views:admin_users_panel'), - ('mediagoblin.admin.reports', - '/reports', - 'mediagoblin.admin.views:admin_reports_panel'), - ('mediagoblin.admin.users_detail', - '/users/<string:user>', - 'mediagoblin.admin.views:admin_users_detail'), - ('mediagoblin.admin.reports_detail', - '/reports/<int:report_id>', - 'mediagoblin.admin.views:admin_reports_detail')] +{% block title %}You are Banned.{% endblock %} + +{% block mediagoblin_content %} + <img class="right_align" src="{{ request.staticdirect('/images/404.png') }}" + alt="{% trans %}Image of goblin stressing out{% endtrans %}" /> + <h1>You have been banned until {{ expiration_date }}</h1> + <p>{{ reason|safe }}</p> + <div class="clear"></div> +{% endblock %} diff --git a/mediagoblin/templates/mediagoblin/base.html b/mediagoblin/templates/mediagoblin/base.html index e9a18f22..b52b65e7 100644 --- a/mediagoblin/templates/mediagoblin/base.html +++ b/mediagoblin/templates/mediagoblin/base.html @@ -104,9 +104,12 @@ {% if request.user.is_admin %} <p> <span class="dropdown_title">Admin powers:</span> - <a href="{{ request.urlgen('mediagoblin.admin.media_panel') }}"> + <a href="{{ request.urlgen('mediagoblin.moderation.media_panel') }}"> {%- trans %}Media processing panel{% endtrans -%} </a> + <a href="{{ request.urlgen('mediagoblin.moderation.users') }}"> + {%- trans %}User management panel{% endtrans -%} + </a> </p> {% endif %} </div> diff --git a/mediagoblin/templates/mediagoblin/admin/media_panel.html b/mediagoblin/templates/mediagoblin/moderation/media_panel.html index 1c3c866e..1c3c866e 100644 --- a/mediagoblin/templates/mediagoblin/admin/media_panel.html +++ b/mediagoblin/templates/mediagoblin/moderation/media_panel.html diff --git a/mediagoblin/templates/mediagoblin/admin/report.html b/mediagoblin/templates/mediagoblin/moderation/report.html index ef90df40..6938569d 100644 --- a/mediagoblin/templates/mediagoblin/admin/report.html +++ b/mediagoblin/templates/mediagoblin/moderation/report.html @@ -16,6 +16,7 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. #} {%- extends "mediagoblin/base.html" %} +{% import "/mediagoblin/utils/wtforms.html" as wtforms_util %} {%- block mediagoblin_content %} {% if not report %} @@ -29,7 +30,7 @@ class="comment_wrapper"> <div class="comment_author"> <img src="{{ request.staticdirect('/images/icon_comment.png') }}" /> - <a href="{{ request.urlgen('mediagoblin.admin.users_detail', + <a href="{{ request.urlgen('mediagoblin.moderation.users_detail', user=comment.get_author.username) }}" class="comment_authorlink"> {{- reported_user.username -}} @@ -68,12 +69,12 @@ <div class="report_author"> <img src="{{ request.staticdirect('/images/icon_clipboard.png') }}" alt="Under a GNU LGPL v.3 or Creative Commons BY-SA 3.0 license. Distributed by the GNOME project http://www.gnome.org" /> - <a href="{{ request.urlgen('mediagoblin.admin.users_detail', + <a href="{{ request.urlgen('mediagoblin.moderation.users_detail', user=report.reporter.username) }}" class="report_authorlink"> {{- report.reporter.username -}} </a> - <a href="{{ request.urlgen('mediagoblin.admin.reports_detail', + <a href="{{ request.urlgen('mediagoblin.moderation.reports_detail', report_id=report.id) }}" class="report_whenlink"> <span title='{{- report.created.strftime("%I:%M%p %Y-%m-%d") -}}'> @@ -87,5 +88,50 @@ {{ report.report_content }} </div> </div> + {% if not report.resolved %} + <input type=button value=Resolve id=open_resolution_form /> + <form action="" method="POST" id=resolution_form> + {{ wtforms_util.render_divs(form) }} + {{ csrf_token }} + <input type=submit id="submit_this_report" value="Resolve This Report"/> + </form> + + + <script> + $(document).ready(function() { + $('form#resolution_form').hide() + $('#user_banned_until').val("YYYY-MM-DD") + $('#open_resolution_form').click(function() { + $('form#resolution_form').toggle(); + $('#user_banned_until').hide(); + $('label[for=user_banned_until]').hide(); + }); + $('#action_to_resolve').change(function() { + if ($('ul#action_to_resolve li input:checked').val() == "userban") { + $('#user_banned_until').show(); + $('label[for=user_banned_until]').show(); + } else { + $('#user_banned_until').hide(); + $('label[for=user_banned_until]').hide(); + } + }); + $("#user_banned_until").focus(function() { + $(this).val(""); + $(this).unbind('focus'); + }); + $("#submit_this_report").click(function(){ + if ($("#user_banned_until").val() == 'YYYY-MM-DD'){ + $("#user_banned_until").val(""); + } + }); + }); + </script> + {% else %} + <h2>Status:</h2> + RESOLVED on {{ report.resolved.strftime("%I:%M%p %Y-%m-%d") }} + {% autoescape False %} + <p>{{ report.result }}</p> + {% endautoescape %} + {% endif %} {% endif %} {% endblock %} diff --git a/mediagoblin/templates/mediagoblin/admin/report_panel.html b/mediagoblin/templates/mediagoblin/moderation/report_panel.html index 30194577..126b247c 100644 --- a/mediagoblin/templates/mediagoblin/admin/report_panel.html +++ b/mediagoblin/templates/mediagoblin/moderation/report_panel.html @@ -29,7 +29,7 @@ {% trans %}Here you can look up users in order to take punitive actions on them.{% endtrans %} </p> -<h2>{% trans %}Reports Filed on Comments{% endtrans %}</h2> +<h2>{% trans %}Reports Filed{% endtrans %}</h2> {% if report_list.count() %} <table class="admin_panel processing"> @@ -44,8 +44,10 @@ </tr> {% for report in report_list %} <tr> + + <td><a href="{{ request.urlgen('mediagoblin.moderation.reports_detail', + report_id=report.id) }}">{{ report.id }}</a></td> {% if report.discriminator == "comment_report" %} - <td>{{ report.id }}</td> <td>Comment Report</td> <td>{{ report.comment.get_author.username }}</td> <td>{{ report.created.strftime("%F %R") }}</td> @@ -53,7 +55,6 @@ <td>{{ report.report_content }}</td> <td><a href="{{ report.comment.get_media_entry.url_for_self(request.urlgen) }}">{{ report.comment.get_media_entry.title }}</a></td> {% elif report.discriminator == "media_report" %} - <td>{{ report.id }}</td> <td>Media Report</td> <td>{{ report.media_entry.get_uploader.username }}</td> <td>{{ report.created.strftime("%F %R") }}</td> @@ -67,26 +68,36 @@ {% else %} <p><em>{% trans %}No open reports found.{% endtrans %}</em></p> {% endif %} -<h2>{% trans %}Closed Reports on Comments{% endtrans %}</h2> +<h2>{% trans %}Closed Reports{% endtrans %}</h2> {% if closed_report_list.count() %} <table class="media_panel processing"> <tr> <th>ID</th> + <th>Resolved</th> <th>Offender</th> - <th>When Reported</th> + <th>Action Taken</th> <th>Reported By</th> <th>Reason</th> - <th>Comment Posted On</th> + <th>Reported Comment or Media Entry</th> </tr> {% for report in closed_report_list %} - <tr> - <td>{{ report.id }}</td> - <td>{{ report.comment.get_author.username }}</td> - <td>{{ report.created.strftime("%F %R") }}</td> - <td>{{ report.reporter.username }}</td> - <td>{{ report.report_content }}</td> - <td><a href="{{ report.comment.get_media_entry.url_for_self(request.urlgen) }}">{{ report.comment.get_media_entry.title }}</a></td> - </tr> + <td><a href="{{ request.urlgen('mediagoblin.moderation.reports_detail', + report_id=report.id) }}">{{ report.id }}</a></td> + {% if report.discriminator == "comment_report" %} + <td>{{ report.resolved.strftime("%F %R") }}</td> + <td>{{ report.comment.get_author.username }}</td> + <td>{{ report.created.strftime("%F %R") }}</td> + <td>{{ report.reporter.username }}</td> + <td>{{ report.report_content }}</td> + <td><a href="{{ report.comment.get_media_entry.url_for_self(request.urlgen) }}">{{ report.comment.get_media_entry.title }}</a></td> + {% elif report.discriminator == "media_report" %} + <td>{{ report.resolved.strftime("%F %R") }}</td> + <td>{{ report.media_entry.get_uploader.username }}</td> + <td>{{ report.created.strftime("%F %R") }}</td> + <td>{{ report.reporter.username }}</td> + <td>{{ report.report_content[0:20] }}...</td> + <td><a href="{{ report.media_entry.url_for_self(request.urlgen) }}">{{ report.media_entry.title }}</a></td> + {% endif %} {% endfor %} </table> {% else %} diff --git a/mediagoblin/templates/mediagoblin/admin/user.html b/mediagoblin/templates/mediagoblin/moderation/user.html index 90b3f583..f868aa8a 100644 --- a/mediagoblin/templates/mediagoblin/admin/user.html +++ b/mediagoblin/templates/mediagoblin/moderation/user.html @@ -85,7 +85,7 @@ </div> {% endif %} {% if user %} - <h2>{%- trans %}Active Reports on{% endtrans -%} {{ user.username }}</h2> + <h2>{%- trans %}Active Reports on {% endtrans -%}{{ user.username }}</h2> {% if reports.count() %} <table class="admin_side_panel"> <tr> @@ -97,7 +97,7 @@ <tr> <td> <img src="{{ request.staticdirect('/images/icon_clipboard.png') }}" /> - <a href="{{ request.urlgen('mediagoblin.admin.reports_detail', + <a href="{{ request.urlgen('mediagoblin.moderation.reports_detail', report_id=report.id) }}"> {%- trans %}Report #{% endtrans -%}{{ report.id }} </a> @@ -116,7 +116,7 @@ <tr><td></td><td></td> </table> {% else %} - {%- trans %}No active reports filed on{% endtrans -%} {{ user.username }} + {%- trans %}No active reports filed on {% endtrans -%} {{ user.username }} {% endif %} <a class="right_align">{{ user.username }}'s report history</a> <span class=clear></span> @@ -125,13 +125,31 @@ <tr> <th>{% trans %}Privilege{% endtrans %}</th> <th>{% trans %}User Has Privilege{% endtrans %}</th> - {% for privilege in privileges %} - <tr> - <td>{{ privilege.privilege_name }}</td> - <td>{% if privilege in user.all_privileges %}Yes{% else %}No{% endif %}</td> - <td>{% if privilege in user.all_privileges and privilege.id < request.user.get_highest_privilege().id %}<a>{% trans %}Take Away{% endtrans %}</a>{% else %}<a>{% trans %}Give Privilege{% endtrans %}</a>{% endif %}</td> </tr> - {% endfor %} + {% for privilege in privileges %} + <tr> + <form action="{{ request.urlgen('mediagoblin.moderation.give_or_take_away_privilege', + user=user.username) }}" + method=post > + <td>{{ privilege.privilege_name }}</td> + <td> + {% if privilege in user.all_privileges %}Yes</td> + {% if (not privilege.is_admin_or_moderator() or request.user.is_admin) and not (user.is_admin and not request.user.is_admin) %} + <td><input type=submit value="{% trans %}Take Away{% endtrans %}" /> + <input type=hidden name=giving_privilege /> + {% endif %} + {% else %}No</td> + {% if (not privilege.is_admin_or_moderator() or request.user.is_admin) and not (user.is_admin and not request.user.is_admin) %} + <td><input type=submit value="{% trans %}Give Privilege{% endtrans %}" > + <input type=hidden name=giving_privilege value=True /> + {% endif %} + {% endif %} + <input type=hidden name=privilege_name value="{{ privilege.privilege_name }}" /> + </td> + {{ csrf_token }} + </form> + </tr> + {% endfor %} </table> {% endif %} {% endblock %} diff --git a/mediagoblin/templates/mediagoblin/admin/user_panel.html b/mediagoblin/templates/mediagoblin/moderation/user_panel.html index cc965b73..49877074 100644 --- a/mediagoblin/templates/mediagoblin/admin/user_panel.html +++ b/mediagoblin/templates/mediagoblin/moderation/user_panel.html @@ -42,7 +42,7 @@ {% for user in user_list %} <tr> <td>{{ user.id }}</td> - <td><a href="{{ request.urlgen('mediagoblin.admin.users_detail', + <td><a href="{{ request.urlgen('mediagoblin.moderation.users_detail', user= user.username) }}">{{ user.username }}</a></td> <td>{{ user.created.strftime("%F %R") }}</td> <td>{{ user.posted_comments.count() }}</td> diff --git a/mediagoblin/tools/response.py b/mediagoblin/tools/response.py index aaf31d0b..ecea307e 100644 --- a/mediagoblin/tools/response.py +++ b/mediagoblin/tools/response.py @@ -19,6 +19,7 @@ from werkzeug.wrappers import Response as wz_Response from mediagoblin.tools.template import render_template from mediagoblin.tools.translate import (lazy_pass_to_ugettext as _, pass_to_ugettext) +from mediagoblin.db.models import UserBan class Response(wz_Response): """Set default response mimetype to HTML, otherwise we get text/plain""" @@ -62,6 +63,15 @@ def render_404(request): "you're looking for has been moved or deleted.") return render_error(request, 404, err_msg=err_msg) +def render_user_banned(request): + """Renders the page which tells a user they have been banned, for how long + and the reason why they have been banned" + """ + user_ban = UserBan.query.get(request.user.id) + return render_to_response(request, + 'mediagoblin/banned.html', + {'reason':user_ban.reason, + 'expiration_date':user_ban.expiration_date}) def render_http_exception(request, exc, description): """Return Response() given a werkzeug.HTTPException diff --git a/mediagoblin/user_pages/forms.py b/mediagoblin/user_pages/forms.py index 284dd7b8..260fe02b 100644 --- a/mediagoblin/user_pages/forms.py +++ b/mediagoblin/user_pages/forms.py @@ -51,11 +51,15 @@ class MediaCollectForm(wtforms.Form): Markdown</a> for formatting.""")) class CommentReportForm(wtforms.Form): - report_reason = wtforms.TextAreaField('Reason for Reporting') + report_reason = wtforms.TextAreaField( + _('Reason for Reporting'), + [wtforms.validators.Required()]) comment_id = wtforms.IntegerField() reporter_id = wtforms.IntegerField() class MediaReportForm(wtforms.Form): - report_reason = wtforms.TextAreaField('Reason for Reporting') + report_reason = wtforms.TextAreaField( + _('Reason for Reporting'), + [wtforms.validators.Required()]) media_entry_id = wtforms.IntegerField() reporter_id = wtforms.IntegerField() diff --git a/mediagoblin/user_pages/lib.py b/mediagoblin/user_pages/lib.py index 2558b066..cf7b604d 100644 --- a/mediagoblin/user_pages/lib.py +++ b/mediagoblin/user_pages/lib.py @@ -78,7 +78,7 @@ def add_media_to_collection(collection, media, note=None, commit=True): if commit: Session.commit() -def build_report_form(form_dict): +def build_report_table(form_dict): """ This function is used to convert a form dictionary (from a User filing a report) into either a MediaReport or CommentReport object. diff --git a/mediagoblin/user_pages/views.py b/mediagoblin/user_pages/views.py index abf5e5c1..c1638276 100644 --- a/mediagoblin/user_pages/views.py +++ b/mediagoblin/user_pages/views.py @@ -26,14 +26,14 @@ from mediagoblin.tools.response import render_to_response, render_404, \ from mediagoblin.tools.translate import pass_to_ugettext as _ from mediagoblin.tools.pagination import Pagination from mediagoblin.user_pages import forms as user_forms -from mediagoblin.user_pages.lib import (send_comment_email, build_report_form, +from mediagoblin.user_pages.lib import (send_comment_email, build_report_table, add_media_to_collection) from mediagoblin.decorators import (uses_pagination, get_user_media_entry, get_media_entry_by_id, user_has_privilege, require_active_login, user_may_delete_media, user_may_alter_collection, get_user_collection, get_user_collection_item, active_user_from_url, - get_media_comment_by_id) + get_media_comment_by_id, user_not_banned) from werkzeug.contrib.atom import AtomFeed @@ -41,7 +41,7 @@ from werkzeug.contrib.atom import AtomFeed _log = logging.getLogger(__name__) _log.setLevel(logging.DEBUG) - +@user_not_banned @uses_pagination def user_home(request, page): """'Homepage' of a User()""" @@ -80,7 +80,7 @@ def user_home(request, page): 'media_entries': media_entries, 'pagination': pagination}) - +@user_not_banned @active_user_from_url @uses_pagination def user_gallery(request, page, url_user=None): @@ -114,7 +114,7 @@ def user_gallery(request, page, url_user=None): MEDIA_COMMENTS_PER_PAGE = 50 - +@user_not_banned @get_user_media_entry @uses_pagination def media_home(request, media, page, **kwargs): @@ -190,7 +190,7 @@ def media_post_comment(request, media): return redirect_obj(request, media) - +@user_not_banned @get_media_entry_by_id @require_active_login def media_collect(request, media): @@ -269,6 +269,7 @@ def media_collect(request, media): #TODO: Why does @user_may_delete_media not implicate @require_active_login? +@user_not_banned @get_media_entry_by_id @require_active_login @user_may_delete_media @@ -305,7 +306,7 @@ def media_confirm_delete(request, media): {'media': media, 'form': form}) - +@user_not_banned @active_user_from_url @uses_pagination def user_collection(request, page, url_user=None): @@ -335,7 +336,7 @@ def user_collection(request, page, url_user=None): 'collection_items': collection_items, 'pagination': pagination}) - +@user_not_banned @active_user_from_url def collection_list(request, url_user=None): """A User-defined Collection""" @@ -391,7 +392,7 @@ def collection_item_confirm_remove(request, collection_item): {'collection_item': collection_item, 'form': form}) - +@user_not_banned @get_user_collection @require_active_login @user_may_alter_collection @@ -575,7 +576,7 @@ def collection_atom_feed(request): return feed.get_response() - +@user_not_banned @require_active_login def processing_panel(request): """ @@ -625,8 +626,8 @@ def processing_panel(request): @user_has_privilege(u'reporter') def file_a_report(request, media, comment=None): if request.method == "POST": - report_form = build_report_form(request.form) - report_form.save() + report_table = build_report_table(request.form) + report_table.save() return redirect( request, diff --git a/mediagoblin/views.py b/mediagoblin/views.py index 6acd7e96..595f2725 100644 --- a/mediagoblin/views.py +++ b/mediagoblin/views.py @@ -18,10 +18,10 @@ from mediagoblin import mg_globals from mediagoblin.db.models import MediaEntry from mediagoblin.tools.pagination import Pagination from mediagoblin.tools.response import render_to_response -from mediagoblin.decorators import uses_pagination - +from mediagoblin.decorators import uses_pagination, user_not_banned +@user_not_banned @uses_pagination def root_view(request, page): cursor = MediaEntry.query.filter_by(state=u'processed').\ |