diff options
| author | tilly-Q <nattilypigeonfowl@gmail.com> | 2013-07-17 16:16:07 -0400 | 
|---|---|---|
| committer | tilly-Q <nattilypigeonfowl@gmail.com> | 2013-07-17 16:16:07 -0400 | 
| commit | 6bba33d7e6fbb0cedc39f9a11f816fe5bd372ae7 (patch) | |
| tree | e72b59ff0f0b749739c3b38c84699fd43464343d | |
| parent | 650a0aa90dacd97286a081b0b7c11abb04ba8767 (diff) | |
| download | mediagoblin-6bba33d7e6fbb0cedc39f9a11f816fe5bd372ae7.tar.lz mediagoblin-6bba33d7e6fbb0cedc39f9a11f816fe5bd372ae7.tar.xz mediagoblin-6bba33d7e6fbb0cedc39f9a11f816fe5bd372ae7.zip | |
Whew. This is a big update. I did some significant keeping work. I moved all of
the folders and enpoints labeled 'admin' to the more accurate term of 'moderat-
ion.' I also created the ability for admins and moderators to add or remove pr-
ivileges or to ban a user in response to a report. This also meant implementing
the UserBan class in various places. I also had to add a column called result
to the ReportBase table. This allows the moderator/admin to leave comments when
they respond to a report, allowing for archiving of what responses they do/n't
take.
--\ mediagoblin/db/migrations.py
--| Added result column to ReportBase
--\ mediagoblin/db/models.py
--| Added result column to ReportBase
--| Added documentation to tables I had made previously
--\ mediagoblin/decorators.py
--| Editted the user_has_privilege decorator to check whether a user has been
  | banned or not
--| Created a seperate user_not_banned decorator to prevent banned users from
  | accessing any pages
--| Changed require_admin_login into require_admin_or_moderator login
--\ mediagoblin/gmg_commands/users.py
--| Made the gmg command `adduser` create a user w/ the appropriate privileges
--\ mediagoblin/moderation/routing.py  << formerly mediagoblin/admin/routing.py
--| Renamed all of the routes from admin -> moderation
--\ mediagoblin/routing.py
--| Renamed all of the routes from admin -> moderation
--\ mediagoblin/moderation/views.py << formerly mediagoblin/admin/views.py
--| Renamed all of the routes & functions from admin -> moderation
--| Expanded greatly on the moderation_reports_detail view and functionality
--| Added in the give_or_take_away_privilege form, however this might be a use-
  | -less function which I could remove (because privilege changes should happe-
  | n in response to a report so they can be archived and visible)
--\ mediagoblin/static/css/base.css
--| Added in a style for the reports_detail page
--\ mediagoblin/templates/mediagoblin/base.html
--| Renamed all of the routes from admin -> moderation
--\ mediagoblin/templates/mediagoblin/moderation/report.html
--| Added form to allow moderators and admins to respond to reports.
--\ mediagoblin/templates/mediagoblin/moderation/reports_panel.html
--| Fixed the table for closed reports
--\ mediagoblin/templates/mediagoblin/moderation/user.html
--| Added in a table w/ all of the user's privileges and the option to add or
  | remove them. Again, this is probably vestigial
--| Renamed all of the routes from admin -> moderation
--\ mediagoblin/templates/mediagoblin/moderation/user_panel.html
--| Renamed all of the routes from admin -> moderation
--\ mediagoblin/tools/response.py
--| Added function render_user_banned, this is the view function for the redir-
  | -ect that happens when a user tries to access the site whilst banned
--\ mediagoblin/user_pages/forms.py
--| Added important translate function where I had text
--\ mediagoblin/user_pages/lib.py
--| Renamed functiion for clarity
--\ mediagoblin/user_pages/views.py
--| Added the user_not_banned decorator to every view
--\ mediagoblin/views.py
--| Added the user_not_banned decorator
--\ mediagoblin/moderation/forms.py
--| Created this new file
--\ mediagoblin/templates/mediagoblin/banned.html
--| Created this new file
--| This is the page which people are redirected to when they access the site
  | while banned
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').\ | 
