This was a big commit! I included lots of documentation below, but generally I
authortilly-Q <nattilypigeonfowl@gmail.com>
Thu, 29 Aug 2013 17:47:50 +0000 (13:47 -0400)
committertilly-Q <nattilypigeonfowl@gmail.com>
Thu, 29 Aug 2013 17:47:50 +0000 (13:47 -0400)
did a few things. I wrote many many many new tests, either in old test files or
in the three new test files I made. I also did some code-keeping work, deleting
trailing whitespace and deleting vestigial code. Lastly, I fixed the parts of
the code which I realized were broken thru the process of running tests.

===============================================================================
 Deleted trailing whitespace:
===============================================================================
--\  mediagoblin/decorators.py
--\  mediagoblin/auth/tools.py
--\  mediagoblin/db/migrations.py
--\  mediagoblin/db/models.py
--\  mediagoblin/gmg_commands/users.py
--\  mediagoblin/moderation/forms.py
--\  mediagoblin/moderation/tools.py
--\  mediagoblin/moderation/views.py
--\  mediagoblin/templates/mediagoblin/moderation/media_panel.html
--\  mediagoblin/templates/mediagoblin/moderation/report.html
--\  mediagoblin/templates/mediagoblin/moderation/report_panel.html
--\  mediagoblin/templates/mediagoblin/moderation/user.html
--\  mediagoblin/templates/mediagoblin/moderation/user_panel.html
--\  mediagoblin/templates/mediagoblin/user_pages/report.html
--\  mediagoblin/templates/mediagoblin/utils/report.html
--\  mediagoblin/user_pages/lib.py
--\  mediagoblin/user_pages/views.py
===============================================================================
 Deleted Vestigial Code
===============================================================================
--\  mediagoblin/db/util.py
--\  mediagoblin/tests/test_notifications.py
===============================================================================
 Modified the Code:
===============================================================================
--\  mediagoblin/moderation/tools.py
--| Encapsulated the code around giving/taking away privileges into two
  | funtions.

--\  mediagoblin/moderation/views.py
--| Imported and used the give/take away privilege functions
--| Replaced 'require_admin_or_moderator_login' with
  |'user_has_privilege(u"admin")' for adding/taking away privileges, only
  | admins are allowed to do this.

--\  mediagoblin/templates/mediagoblin/banned.html
--| Added relevant translation tags
--| Added ability to display indefinite banning

--\  mediagoblin/templates/mediagoblin/user_pages/media.html
--| Made sure the add comments button was only visible for users with the
  | `commenter` privilege

--\  mediagoblin/tests/test_submission.py
--| Paroneayea fixed a DetachedInstanceError I was having with the our_user
  | function

--\  mediagoblin/tests/tools.py
--| Added a fixture_add_comment_report function for testing.

--\  mediagoblin/tools/response.py
--| Fixed a minor error where a necessary return statement was missing
--| Fit the code within 80 columns

--\  mediagoblin/user_pages/views.py
--| Added a necessary decorator to ensure that only users with the 'commenter'
  | privilege can post comments
===============================================================================
 Wrote new tests for an old test file:
===============================================================================
--\  mediagoblin/tests/test_auth.py
--| Added a new test to make sure privilege granting on registration happens
  | correctly

--\  mediagoblin/tests/test_modelmethods.py*
--| Added a test to ensure the User method has_privilege works properly
===============================================================================
 Wrote entirely new files full of tests:
===============================================================================
--\  mediagoblin/tests/test_moderation.py
--\  mediagoblin/tests/test_privileges.py
--\  mediagoblin/tests/test_reporting.py
===============================================================================
===============================================================================
NOTE: Any files I've marked with a * in this commit report, were actually subm-
itted in my last commit. I made that committ to fix an error I was having, so
they weren't properly documented in that report.
===============================================================================
===============================================================================

28 files changed:
mediagoblin/auth/tools.py
mediagoblin/db/migrations.py
mediagoblin/db/models.py
mediagoblin/db/util.py
mediagoblin/decorators.py
mediagoblin/gmg_commands/users.py
mediagoblin/moderation/forms.py
mediagoblin/moderation/tools.py
mediagoblin/moderation/views.py
mediagoblin/templates/mediagoblin/banned.html
mediagoblin/templates/mediagoblin/moderation/media_panel.html
mediagoblin/templates/mediagoblin/moderation/report.html
mediagoblin/templates/mediagoblin/moderation/report_panel.html
mediagoblin/templates/mediagoblin/moderation/user.html
mediagoblin/templates/mediagoblin/moderation/user_panel.html
mediagoblin/templates/mediagoblin/user_pages/media.html
mediagoblin/templates/mediagoblin/user_pages/report.html
mediagoblin/templates/mediagoblin/utils/report.html
mediagoblin/tests/test_auth.py
mediagoblin/tests/test_moderation.py [new file with mode: 0644]
mediagoblin/tests/test_notifications.py
mediagoblin/tests/test_privileges.py [new file with mode: 0644]
mediagoblin/tests/test_reporting.py [new file with mode: 0644]
mediagoblin/tests/test_submission.py
mediagoblin/tests/tools.py
mediagoblin/tools/response.py
mediagoblin/user_pages/lib.py
mediagoblin/user_pages/views.py

index f758bca40b3b2edf06341b9424cd7905261cb504..76b37e297319a5d1821b038fad0d7c2b6a6dc74e 100644 (file)
@@ -164,13 +164,13 @@ def register_user(request, register_form):
         user = auth.create_user(register_form)
 
         # give the user the default privileges
-        default_privileges = [ 
+        default_privileges = [
             Privilege.query.filter(Privilege.privilege_name==u'commenter').first(),
             Privilege.query.filter(Privilege.privilege_name==u'uploader').first(),
             Privilege.query.filter(Privilege.privilege_name==u'reporter').first()]
         user.all_privileges += default_privileges
         user.save()
-        
+
         # log the user in
         request.session['user_id'] = unicode(user.id)
         request.session.save()
index 762d17e6b50feedd9472aa48665c2c383b6eb0a8..a97458b6e89a141c43f593709dfe910cebea36b1 100644 (file)
@@ -28,7 +28,7 @@ from migrate.changeset.constraint import UniqueConstraint
 
 from mediagoblin.db.extratypes import JSONEncoded
 from mediagoblin.db.migration_tools import RegisterMigration, inspect_table
-from mediagoblin.db.models import (MediaEntry, Collection, User, 
+from mediagoblin.db.models import (MediaEntry, Collection, User,
                                    MediaComment, Privilege, ReportBase)
 
 MIGRATIONS = {}
@@ -425,7 +425,7 @@ class RequestToken_v0(declarative_base()):
     callback = Column(Unicode, nullable=False, default=u"oob")
     created = Column(DateTime, nullable=False, default=datetime.datetime.now)
     updated = Column(DateTime, nullable=False, default=datetime.datetime.now)
-    
+
 class AccessToken_v0(declarative_base()):
     """
         Model for representing the access tokens
@@ -438,7 +438,7 @@ class AccessToken_v0(declarative_base()):
     request_token = Column(Unicode, ForeignKey(RequestToken_v0.token))
     created = Column(DateTime, nullable=False, default=datetime.datetime.now)
     updated = Column(DateTime, nullable=False, default=datetime.datetime.now)
+
 
 class NonceTimestamp_v0(declarative_base()):
     """
@@ -467,7 +467,7 @@ class ReportBase_v0(declarative_base()):
     reporter_id = Column(Integer, ForeignKey(User.id), nullable=False)
     report_content = Column(UnicodeText)
     reported_user_id = Column(Integer, ForeignKey(User.id), nullable=False)
-    created = Column(DateTime, nullable=False, default=datetime.datetime.now) 
+    created = Column(DateTime, nullable=False, default=datetime.datetime.now)
     discriminator = Column('type', Unicode(50))
     __mapper_args__ = {'polymorphic_on': discriminator}
 
@@ -512,14 +512,14 @@ class Privilege_v0(declarative_base()):
 class PrivilegeUserAssociation_v0(declarative_base()):
     __tablename__ = 'core__privileges_users'
     group_id = Column(
-        'core__privilege_id', 
-        Integer, 
-        ForeignKey(User.id), 
+        'core__privilege_id',
+        Integer,
+        ForeignKey(User.id),
         primary_key=True)
     user_id = Column(
-        'core__user_id', 
-        Integer, 
-        ForeignKey(Privilege.id), 
+        'core__user_id',
+        Integer,
+        ForeignKey(Privilege.id),
         primary_key=True)
 
 @RegisterMigration(15, MIGRATIONS)
index 62c5a5d5d11c51ff3c1849a31eb2887d9036a980..dd2cd3e931bc6497479f417a962a3bafc485e254 100644 (file)
@@ -669,7 +669,7 @@ class ReportBase(Base):
     id = Column(Integer, primary_key=True)
     reporter_id = Column(Integer, ForeignKey(User.id), nullable=False)
     reporter =  relationship(
-        User, 
+        User,
         backref=backref("reports_filed_by",
             lazy="dynamic",
             cascade="all, delete-orphan"),
@@ -677,7 +677,7 @@ class ReportBase(Base):
     report_content = Column(UnicodeText)
     reported_user_id = Column(Integer, ForeignKey(User.id), nullable=False)
     reported_user = relationship(
-        User, 
+        User,
         backref=backref("reports_filed_on",
             lazy="dynamic",
             cascade="all, delete-orphan"),
@@ -722,7 +722,7 @@ class MediaReport(ReportBase):
                                                 primary_key=True)
     media_entry_id = Column(Integer, ForeignKey(MediaEntry.id), nullable=False)
     media_entry = relationship(
-        MediaEntry, 
+        MediaEntry,
         backref=backref("reports_filed_onmod/reports/1/",
             lazy="dynamic",
             cascade="all, delete-orphan"))
@@ -738,7 +738,7 @@ class ArchivedReport(ReportBase):
 
     media_entry_id = Column(Integer, ForeignKey(MediaEntry.id))
     media_entry = relationship(
-        MediaEntry, 
+        MediaEntry,
         backref=backref("past_reports_filed_on",
             lazy="dynamic"))
     comment_id = Column(Integer, ForeignKey(MediaComment.id))
@@ -748,7 +748,7 @@ class ArchivedReport(ReportBase):
 
     resolver_id = Column(Integer, ForeignKey(User.id), nullable=False)
     resolver = relationship(
-        User, 
+        User,
         backref=backref("reports_resolved_by",
             lazy="dynamic",
             cascade="all, delete-orphan"),
@@ -759,23 +759,23 @@ class ArchivedReport(ReportBase):
 
 class UserBan(Base):
     """
-    Holds the information on a specific user's ban-state. As long as one of 
-        these is attached to a user, they are banned from accessing mediagoblin. 
-        When they try to log in, they are greeted with a page that tells them 
-        the reason why they are banned and when (if ever) the ban will be 
+    Holds the information on a specific user's ban-state. As long as one of
+        these is attached to a user, they are banned from accessing mediagoblin.
+        When they try to log in, they are greeted with a page that tells them
+        the reason why they are banned and when (if ever) the ban will be
         lifted
 
-        :keyword user_id          Holds the id of the user this object is 
-                                    attached to. This is a one-to-one 
+        :keyword user_id          Holds the id of the user this object is
+                                    attached to. This is a one-to-one
                                     relationship.
-        :keyword expiration_date  Holds the date that the ban will be lifted. 
-                                    If this is null, the ban is permanent 
+        :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.
         :keyword reason           Holds the reason why the user was banned.
     """
     __tablename__ = 'core__user_bans'
 
-    user_id = Column(Integer, ForeignKey(User.id), nullable=False, 
+    user_id = Column(Integer, ForeignKey(User.id), nullable=False,
                                                         primary_key=True)
     expiration_date = Column(DateTime)
     reason = Column(UnicodeText, nullable=False)
@@ -785,21 +785,21 @@ 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. 
+    privilege object.
 
         :keyword privilege_name   Holds a unicode object that is the recognizable
-                                    name of this privilege. This is the column 
+                                    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)
     privilege_name = Column(Unicode, nullable=False, unique=True)
     all_users = relationship(
-        User, 
-        backref='all_privileges', 
+        User,
+        backref='all_privileges',
         secondary="core__privileges_users")
 
     def __init__(self, privilege_name):
@@ -818,25 +818,25 @@ class PrivilegeUserAssociation(Base):
     '''
     This table holds the many-to-many relationship between User and Privilege
     '''
-    
+
     __tablename__ = 'core__privileges_users'
 
     privilege_id = Column(
-        'core__privilege_id', 
-        Integer, 
-        ForeignKey(User.id), 
+        'core__privilege_id',
+        Integer,
+        ForeignKey(User.id),
         primary_key=True)
     user_id = Column(
-        'core__user_id', 
-        Integer, 
-        ForeignKey(Privilege.id), 
+        'core__user_id',
+        Integer,
+        ForeignKey(Privilege.id),
         primary_key=True)
 
 MODELS = [
     User, MediaEntry, Tag, MediaTag, MediaComment, Collection, CollectionItem,
     MediaFile, FileKeynames, MediaAttachmentFile, ProcessingMetaData,
     Notification, CommentNotification, ProcessingNotification, Client,
-    CommentSubscription, ReportBase, CommentReport, MediaReport, UserBan, 
+    CommentSubscription, ReportBase, CommentReport, MediaReport, UserBan,
        Privilege, PrivilegeUserAssociation, ArchivedReport,
     RequestToken, AccessToken, NonceTimestamp]
 
@@ -854,10 +854,10 @@ MODELS = [
 
     FOUNDATIONS = {User:user_foundations}
 """
-privilege_foundations = [{'privilege_name':u'admin'}, 
-                                               {'privilege_name':u'moderator'}, 
+privilege_foundations = [{'privilege_name':u'admin'},
+                                               {'privilege_name':u'moderator'},
                                                {'privilege_name':u'uploader'},
-                                               {'privilege_name':u'reporter'}, 
+                                               {'privilege_name':u'reporter'},
                                                {'privilege_name':u'commenter'},
                                                {'privilege_name':u'active'}]
 FOUNDATIONS = {Privilege:privilege_foundations}
index 31fc49fb3ec2d9154b56bc2520e3b9bdcc7a1265..2488dcac8f87c783eafda4e69a180f05a9008391 100644 (file)
@@ -68,24 +68,6 @@ def check_collection_slug_used(creator_id, slug, ignore_c_id):
     does_exist = Session.query(Collection.id).filter(filt).first() is not None
     return does_exist
 
-def user_privileges_to_dictionary(user_id):
-    """
-    This function accepts a users id and returns a dictionary of True or False
-    values for each privilege the user does or does not have. This allows for 
-    easier referencing of a user's privileges inside templates.
-    """
-    privilege_dictionary = {}
-    user = User.query.get(user_id)
-    users_privileges = [p_item.privilege_name for p_item in user.all_privileges]
-    #TODO update this to account for plugins that may add foundations
-    for privilege in FOUNDATIONS[Privilege]:
-        privilege_name = privilege['privilege_name']
-        if privilege_name in users_privileges:
-            privilege_dictionary[privilege_name]=True
-        else:
-            privilege_dictionary[privilege_name]=False
-    return privilege_dictionary
-
 if __name__ == '__main__':
     from mediagoblin.db.open import setup_connection_and_db_from_config
 
index a347916426f27747a31e695fc44ad5ee82847cc2..1a8b124bb277d7611db312fe98a008da2d2af043 100644 (file)
@@ -24,7 +24,7 @@ from mediagoblin import mg_globals as mgg
 from mediagoblin import messages
 from mediagoblin.db.models import (MediaEntry, User, MediaComment,
                                                        UserBan, Privilege)
-from mediagoblin.tools.response import (redirect, render_404, 
+from mediagoblin.tools.response import (redirect, render_404,
                                                                render_user_banned, json_response)
 from mediagoblin.tools.translate import pass_to_ugettext as _
 
@@ -358,7 +358,7 @@ def oauth_required(controller):
             error = "Missing required parameter."
             return json_response({"error": error}, status=400)
 
-         
+
         request_validator = GMGRequestValidator()
         resource_endpoint = ResourceEndpoint(request_validator)
         valid, request = resource_endpoint.validate_protected_resource_request(
index 0002daad342f396d9c7de6cc918efea1036fe8ec..d319cef9f0ab9ecca662d2f7d50954b0fb14c820 100644 (file)
@@ -55,7 +55,7 @@ def adduser(args):
         entry.pw_hash = auth.gen_password_hash(args.password)
         entry.status = u'active'
         entry.email_verified = True
-        default_privileges = [ 
+        default_privileges = [
             db.Privilege.query.filter(
                 db.Privilege.privilege_name==u'commenter').one(),
             db.Privilege.query.filter(
index 718cd8fa661002161e88741d6041e5ac0f77af15..a3202359fc05b92728b0f5cf7831a0bfe26c2f9f 100644 (file)
@@ -28,7 +28,7 @@ class MultiCheckboxField(wtforms.SelectMultipleField):
 
     Iterating the field will produce subfields, allowing custom rendering of
     the enclosed checkbox fields.
-    
+
     code from http://wtforms.simplecodes.com/docs/1.0.4/specific_problems.html
     """
     widget = wtforms.widgets.ListWidget(prefix_label=False)
@@ -40,7 +40,7 @@ class PrivilegeAddRemoveForm(wtforms.Form):
 
 class ReportResolutionForm(wtforms.Form):
     action_to_resolve = MultiCheckboxField(
-        _(u'What action will you take to resolve the report?'), 
+        _(u'What action will you take to resolve the report?'),
         validators=[wtforms.validators.optional()],
         choices=ACTION_CHOICES)
     targeted_user   = wtforms.HiddenField('',
index b4daca152974f23e8c77e091cf8c64cceef112a9..d58df3a879da6aaa869aed40bb661eefd9f6503a 100644 (file)
@@ -31,17 +31,15 @@ def take_punitive_actions(request, form, report, user):
         # punitive actions that a moderator could take.
         if u'takeaway' in form.action_to_resolve.data:
             for privilege_name in form.take_away_privileges.data:
-                privilege = Privilege.query.filter(
-                    Privilege.privilege_name==privilege_name).one()
+                take_away_privileges(user.username, privilege_name)
                 form.resolution_content.data += \
-                    u"<br>%s took away %s\'s %s privileges" % (
+                    u"<br>%s took away %s\'s %s privileges." % (
                         request.user.username,
                         user.username,
-                        privilege.privilege_name)
-                user.all_privileges.remove(privilege)
+                        privilege_name)
 
         # If the moderator elects to ban the user, a new instance of user_ban
-        # will be created.        
+        # will be created.
         if u'userban' in form.action_to_resolve.data:
             reason = form.resolution_content.data + \
                 "<br>"+request.user.username
@@ -51,7 +49,7 @@ def take_punitive_actions(request, form, report, user):
                 reason= form.why_user_was_banned.data
             )
             Session.add(user_ban)
-    
+
             if form.user_banned_until.data is not None:
                 form.resolution_content.data += \
                     u"<br>%s banned user %s until %s." % (
@@ -86,17 +84,17 @@ def take_punitive_actions(request, form, report, user):
                 deleted_comment = report.comment
                 Session.delete(deleted_comment)
                 form.resolution_content.data += \
-                    u"<br>%s deleted the comment" % (
+                    u"<br>%s deleted the comment." % (
                         request.user.username)
         elif u'delete' in form.action_to_resolve.data and \
-            report.is_media_entry_report():    
+            report.is_media_entry_report():
                 deleted_media = report.media_entry
                 Session.delete(deleted_media)
                 form.resolution_content.data += \
-                    u"<br>%s deleted the media entry" % (
-                        request.user.username)    
+                    u"<br>%s deleted the media entry." % (
+                        request.user.username)
 
-        # If the moderator didn't delete the content we then attach the 
+        # If the moderator didn't delete the content we then attach the
         # content to the archived report. We also have to actively delete the
         # old report, since it won't be deleted by cascading.
         elif report.is_comment_report():
@@ -133,3 +131,34 @@ def take_punitive_actions(request, form, report, user):
             request,
             'mediagoblin.moderation.reports_detail',
             report_id=report.id)
+
+def take_away_privileges(user,*privileges):
+    if len(privileges) == 1:
+        privilege = Privilege.query.filter(
+            Privilege.privilege_name==privileges[0]).first()
+        user = User.query.filter(
+            User.username==user).first()
+        if privilege in user.all_privileges:
+            user.all_privileges.remove(privilege)
+            return True
+        return False
+
+    elif len(privileges) > 1:
+        return (take_away_privileges(user, privileges[0]) and \
+            take_away_privileges(user, *privileges[1:]))
+
+def give_privileges(user,*privileges):
+    if len(privileges) == 1:
+        privilege = Privilege.query.filter(
+            Privilege.privilege_name==privileges[0]).first()
+        user = User.query.filter(
+            User.username==user).first()
+        if privilege not in user.all_privileges:
+            user.all_privileges.append(privilege)
+            return True
+        return False
+
+    elif len(privileges) > 1:
+        return (give_privileges(user, privileges[0]) and \
+            give_privileges(user, *privileges[1:]))
+
index d82eca7d8d17f43d52879e4a71f3fdf8fdd982d0..b2223744e2923fa4d1463a70445500021c87f80a 100644 (file)
@@ -19,12 +19,12 @@ from werkzeug.exceptions import Forbidden
 from mediagoblin.db.models import (MediaEntry, User, MediaComment, \
                                    CommentReport, ReportBase, Privilege, \
                                    UserBan, ArchivedReport)
-from mediagoblin.db.util import user_privileges_to_dictionary
 from mediagoblin.decorators import (require_admin_or_moderator_login, \
-                                    active_user_from_url)
+                                    active_user_from_url, user_has_privilege)
 from mediagoblin.tools.response import render_to_response, redirect
 from mediagoblin.moderation import forms as moderation_forms
-from mediagoblin.moderation.tools import take_punitive_actions
+from mediagoblin.moderation.tools import (take_punitive_actions, \
+    take_away_privileges, give_privileges)
 from datetime import datetime
 
 @require_admin_or_moderator_login
@@ -86,7 +86,7 @@ def moderation_users_detail(request):
 @require_admin_or_moderator_login
 def moderation_reports_panel(request):
     '''
-    Show the global panel for monitoring reports filed against comments or 
+    Show the global panel for monitoring reports filed against comments or
         media entries for this instance.
     '''
     report_list = ReportBase.query.filter(
@@ -115,7 +115,7 @@ def moderation_reports_detail(request):
 
     form.take_away_privileges.choices = [
         (s.privilege_name,s.privilege_name.title()) \
-            for s in report.reported_user.all_privileges 
+            for s in report.reported_user.all_privileges
     ]
 
     if request.method == "POST" and form.validate() and not (
@@ -134,7 +134,7 @@ def moderation_reports_detail(request):
         {'report':report,
          'form':form})
 
-@require_admin_or_moderator_login
+@user_has_privilege(u'admin')
 @active_user_from_url
 def give_or_take_away_privilege(request, url_user):
     '''
@@ -144,12 +144,13 @@ def give_or_take_away_privilege(request, url_user):
     if request.method == "POST" and form.validate():
         privilege = Privilege.query.filter(
             Privilege.privilege_name==form.privilege_name.data).one()
-        if privilege in url_user.all_privileges:
-            url_user.all_privileges.remove(privilege)
-        else:      
-            url_user.all_privileges.append(privilege)
+        if not take_away_privileges(
+            url_user.username, form.privilege_name.data):
+
+            give_privileges(url_user.username, form.privilege_name.data)
         url_user.save()
-        return redirect(
-            request,
-            'mediagoblin.moderation.users_detail',
-            user=url_user.username)
+
+    return redirect(
+        request,
+        'mediagoblin.moderation.users_detail',
+        user=url_user.username)
index 4eda0540fc8fef75d2629556f2397a6ebe561542..cd54158ada531f4d06cff9dd88969dff5b8a2021 100644 (file)
 #}
 {% extends "mediagoblin/base.html" %}
 
-{% block title %}You are Banned.{% endblock %}
+{% block title %}{% trans %}You are Banned.{% endtrans %}{% 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>
+
+  <h1>{% trans %}You have been banned{% endtrans %}
+    {% if expiration_date %}
+      {% trans %}until{% endtrans %} {{ expiration_date }}
+    {% else %}
+      {% trans %}indefinitely{% endtrans %}
+    {% endif %}
+  </h2>
   <p>{{ reason|safe }}</p>
   <div class="clear"></div>
 {% endblock %}
index 1c3c866ebef37511dc879ae51ac8758fe382db7f..d6e02db8583bb0cf138d111ba0aa157705e9794d 100644 (file)
@@ -28,7 +28,7 @@
 <p>
   {% trans %}Here you can track the state of media being processed on this instance.{% endtrans %}
 </p>
-    
+
 <h2>{% trans %}Media in-processing{% endtrans %}</h2>
 
 {% if processing_entries.count() %}
@@ -56,7 +56,7 @@
   </table>
 {% else %}
   <p><em>{% trans %}No media in-processing{% endtrans %}</em></p>
-{% endif %}  
+{% endif %}
 
 <h2>{% trans %}These uploads failed to process:{% endtrans %}</h2>
 {% if failed_entries.count() %}
index 04788f057a7c32da26a78528c542b95a1487b980..fafa8b8a120f8c8fd5d23b2d800afb7e8db79c2f 100644 (file)
@@ -27,7 +27,7 @@
        title="Return to Reports Panel">
       {% trans %}Return to Reports Panel{% endtrans %}</a>
   <h2>{% trans %}Report{% endtrans %} #{{ report.id }}</h2>
-  {% if report.is_comment_report() or 
+  {% if report.is_comment_report() or
     (report.is_archived_report() and report.comment) %}
 
     {% trans %}Reported comment{% endtrans %}:
@@ -60,7 +60,7 @@
         {% endautoescape %}
       </div>
     </div>
-  {% elif report.is_media_entry_report() or 
+  {% elif report.is_media_entry_report() or
     (report.is_archived_report() and report.media_entry) %}
 
     {% set media_entry = report.media_entry %}
@@ -89,7 +89,7 @@
                         'mediagoblin.moderation.users_detail',
                         user=report.reporter.username),
                  user_name=report.reported_user.username %}
-          CONTENT BY 
+          CONTENT BY
             <a href="{{ user_url }}"> {{ user_name }}</a>
           HAS BEEN DELETED
         {% endtrans %}
        class="report_wrapper">
     <div class="report_author">
         <img src="{{ request.staticdirect(
-                  '/images/icon_clipboard_alert.png') }}" 
-             alt="Under a GNU LGPL v.3 or Creative Commons BY-SA 3.0 license. 
+                  '/images/icon_clipboard_alert.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.moderation.users_detail',
                         user=report.reporter.username) }}"
@@ -160,7 +160,7 @@ $(document).ready(function() {
         $('ul#action_to_resolve li input:not(:checked)').each(function() {
             $.each(hidden_input_names[$(this).val()], function(index, name){
                 $('label[for='+name+']').hide();
-                $('#'+name).hide();                
+                $('#'+name).hide();
             });
         });
     });
@@ -176,12 +176,12 @@ $(document).ready(function() {
   });
   </script>
   {% elif not (report.reported_user.has_privilege('admin')) %}
-    <h2><img src="{{ request.staticdirect('/images/icon_clipboard.png') }}" 
+    <h2><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" /> 
+                  Distributed by the GNOME project http://www.gnome.org" />
           {% trans %}Status{% endtrans %}:
     </h2>
-    <b>{% trans %}RESOLVED{% endtrans %}</b> 
+    <b>{% trans %}RESOLVED{% endtrans %}</b>
     {{ report.resolved.strftime("%I:%M%p %Y-%m-%d") }}
     {% autoescape False %}
       <p>{{ report.result }}</p>
index 2818eb80f37cc194dad777e5ea9aa89274e9f1a2..fb9d8cd9d0a70ab3b77d9c796b553157f84a5123 100644 (file)
@@ -30,7 +30,7 @@
     Here you can look up open reports that have been filed by users.
   {% endtrans %}
 </p>
-    
+
 <h2>{% trans %}Active Reports Filed{% endtrans %}</h2>
 
 {% if report_list.count() %}
       <tr>
         {% if report.discriminator == "comment_report" %}
           <td>
-            <img 
-              src="{{ request.staticdirect('/images/icon_clipboard_alert.png') }}" 
+            <img
+              src="{{ request.staticdirect('/images/icon_clipboard_alert.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.moderation.reports_detail',
                      report_id=report.id) }}">
               {% trans report_id=report.id %}
-                Comment Report #{{ report_id }} 
+                Comment Report #{{ report_id }}
               {% endtrans %}
             </a>
           </td>
         {% elif report.discriminator == "media_report" %}
           <td>
-            <img 
-              src="{{ request.staticdirect('/images/icon_clipboard_alert.png') }}" 
+            <img
+              src="{{ request.staticdirect('/images/icon_clipboard_alert.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(
@@ -97,8 +97,8 @@
     {% for report in closed_report_list %}
       <tr>
         <td>
-          <img 
-            src="{{ request.staticdirect('/images/icon_clipboard.png') }}" 
+          <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.moderation.reports_detail',
   </table>
 {% else %}
   <p><em>{% trans %}No closed reports found.{% endtrans %}</em></p>
-{% endif %} 
+{% endif %}
 {% endblock %}
index ef48fe54ea135adb354fcbd5829de34412f8c2fc..662d48b147c082aa85994c5c3ad19069baa92888 100644 (file)
     <a class="right_align">{{ user.username }}'s report history</a>
     <span class=clear></span>
     <h2>{{ user.username }}'s Privileges</h2>
-    {% if request.user.has_privilege('admin') and not user_banned and 
+    {% if request.user.has_privilege('admin') and not user_banned and
         not user.id == request.user.id %}
-        <input type=button class="button_action right_align" 
+        <input type=button class="button_action right_align"
                value="Ban User" />
     {% elif request.user.has_privilege('admin') and
             not user.id == request.user.id %}
index 6762a84404bda0d6f8d622c184bbba67aa34c1d2..54ef7c110d1f850095e4b503addbbae6e522a53d 100644 (file)
@@ -30,7 +30,7 @@
     Here you can look up users in order to take punitive actions on them.
   {% endtrans %}
 </p>
-    
+
 <h2>{% trans %}Active Users{% endtrans %}</h2>
 
 {% if user_list.count() %}
@@ -57,5 +57,5 @@
   </table>
 {% else %}
   <p><em>{% trans %}No users found.{% endtrans %}</em></p>
-{% endif %} 
+{% endif %}
 {% endblock %}
index e161afc95deff735d12b419f3358e61282423dd9..b92cf39c28c59c72d705fce144cf197f5b47d2dc 100644 (file)
@@ -86,7 +86,7 @@
     {% autoescape False %}
       <p>{{ media.description_html }}</p>
     {% endautoescape %}
-    {% if comments %}
+    {% if comments and request.user.has_privilege('commenter') %}
       {% if app_config['allow_comments'] %}
         <a
           {% if not request.user %}
index cd5e6f597b689e26df62d9b744fe9bf5840516ef..ce0fb1bc50239eba687cfeff71ba5fd7c9d9f093 100644 (file)
         {%- set comment_author_url = request.urlgen(
                                       'mediagoblin.user_pages.user_home',
                                       user=comment_author.username) %}
-        {%- set comment_url = request.urlgen( 
-                            'mediagoblin.user_pages.media_home.view_comment', 
-                            comment=comment.id, 
-                            user=media.get_uploader.username, 
+        {%- set comment_url = request.urlgen(
+                            'mediagoblin.user_pages.media_home.view_comment',
+                            comment=comment.id,
+                            user=media.get_uploader.username,
                             media=media.slug_or_id) %}
         <div id="comment-{{ comment.id }}"
             class="comment_wrapper">
           <div class="comment_author">
-            <img 
+            <img
               src="{{ request.staticdirect('/images/icon_comment.png') }}" />
             <a href="{{ comment_author_url }}"
                class="comment_authorlink">
@@ -42,7 +42,7 @@
             </a>
             <a href="{{ comment_url }}"
                class="comment_whenlink">
-              <span 
+              <span
                 title='{{- comment.created.strftime("%I:%M%p %Y-%m-%d") -}}'>
 
                 {%- trans formatted_time=timesince(comment.created) -%}
     {% elif media is defined %}
         <h3>{% trans %}Reporting this Media Entry{% endtrans %}</h3>
           <div class="media_thumbnail">
-            <a href="{{ request.urlgen('mediagoblin.user_pages.media_home', 
-                            user=media.get_uploader.username, 
+            <a href="{{ request.urlgen('mediagoblin.user_pages.media_home',
+                            user=media.get_uploader.username,
                             media=media.slug_or_id) }}">
               <img src="{{ media.thumb_url }}"/></a>
-            <a href="{{ request.urlgen('mediagoblin.user_pages.media_home', 
-                            user=media.get_uploader.username, 
-                            media=media.slug_or_id) }}" 
+            <a href="{{ request.urlgen('mediagoblin.user_pages.media_home',
+                            user=media.get_uploader.username,
+                            media=media.slug_or_id) }}"
                class=thumb_entry_title>{{ media.title }}</a>
           </div>
           <div class=clear></div>
       {%- trans user_url = request.urlgen('mediagoblin.user_pages.user_home', user=media.get_uploader.username),
             username = media.get_uploader.username %}
-            ❖ Published by <a href="{{ user_url }}" 
+            ❖ Published by <a href="{{ user_url }}"
                           class="comment_authorlink">{{ username }}</a>
       {% endtrans %}
     {%- endif %}
index 4108cd828f26bd3f0cdf15351657638f4e6bc056..2fa4f959a7e8beeaa77e6b96616b40799daf210c 100644 (file)
@@ -18,7 +18,7 @@
 
 {% block report_content -%}
   <p>
-    <a 
+    <a
       {% if not request.user -%}
         href="{{ request.urlgen('mediagoblin.auth.login') }}"
       {% else %}
index 11ed83bded6b109f7d1edf0b3d55fbf9140ec2c3..6cf054445d4c5e18fb1a0b2872d04eb5e4318e68 100644 (file)
@@ -330,6 +330,13 @@ def test_authentication_views(test_app):
             'next' : '/u/chris/'})
     assert urlparse.urlsplit(response.location)[2] == '/u/chris/'
 
+def test_basic_privileges_granted_on_registration(test_app):
+    user = User.query.filter(User.username==u'angrygirl').first()
+
+    assert User.has_privilege(u'commenter')
+    assert User.has_privilege(u'uploader')
+    assert User.has_privilege(u'reporter')
+    assert not User.has_privilege(u'active')
 
 @pytest.fixture()
 def authentication_disabled_app(request):
diff --git a/mediagoblin/tests/test_moderation.py b/mediagoblin/tests/test_moderation.py
new file mode 100644 (file)
index 0000000..d4f57c7
--- /dev/null
@@ -0,0 +1,194 @@
+# 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 pytest
+
+from mediagoblin.tests.tools import (fixture_add_user,
+            fixture_add_comment_report, fixture_add_comment)
+from mediagoblin.db.models import User, CommentReport, MediaComment, UserBan
+from mediagoblin.moderation.tools import take_away_privileges, give_privileges
+from mediagoblin.tools import template, mail
+
+from webtest import AppError
+
+class TestModerationViews:
+    @pytest.fixture(autouse=True)
+    def _setup(self, test_app):
+        self.test_app = test_app
+
+        fixture_add_user(u'admin',
+            privileges=[u'admin',u'active'])
+        fixture_add_user(u'moderator',
+            privileges=[u'moderator',u'active'])
+        fixture_add_user(u'regular',
+            privileges=[u'active',u'commenter'])
+        self.query_for_users()
+
+    def login(self, username):
+        self.test_app.post(
+            '/auth/login/', {
+                'username': username,
+                'password': 'toast'})
+        self.query_for_users()
+
+    def logout(self):
+        self.test_app.get('/auth/logout/')
+        self.query_for_users()
+
+    def query_for_users(self):
+        self.admin_user = User.query.filter(User.username==u'admin').first()
+        self.mod_user = User.query.filter(User.username==u'moderator').first()
+        self.user = User.query.filter(User.username==u'regular').first()
+
+    def do_post(self, data, *context_keys, **kwargs):
+        url = kwargs.pop('url', '/submit/')
+        do_follow = kwargs.pop('do_follow', False)
+        template.clear_test_template_context()
+        response = self.test_app.post(url, data, **kwargs)
+        if do_follow:
+            response.follow()
+        context_data = template.TEMPLATE_TEST_CONTEXT
+        for key in context_keys:
+            context_data = context_data[key]
+        return response, context_data
+
+    def testGiveOrTakeAwayPrivileges(self):
+        self.login(u'admin')
+        # First, test an admin taking away a privilege from a user
+        #----------------------------------------------------------------------
+        response, context = self.do_post({'privilege_name':u'commenter'},
+            url='/mod/users/{0}/privilege/'.format(self.user.username))
+        assert response.status == '302 FOUND'
+        self.query_for_users()
+        assert not self.user.has_privilege(u'commenter')
+
+        # Then, test an admin giving a privilege to a user
+        #----------------------------------------------------------------------
+        response, context = self.do_post({'privilege_name':u'commenter'},
+            url='/mod/users/{0}/privilege/'.format(self.user.username))
+        assert response.status == '302 FOUND'
+        self.query_for_users()
+        assert self.user.has_privilege(u'commenter')
+
+        # Then, test a mod trying to take away a privilege from a user
+        # they are not allowed to do this, so this will raise an error
+        #----------------------------------------------------------------------
+        self.logout()
+        self.login(u'moderator')
+
+        with pytest.raises(AppError) as excinfo:
+            response, context = self.do_post({'privilege_name':u'commenter'},
+                url='/mod/users/{0}/privilege/'.format(self.user.username))
+        assert 'Bad response: 403 FORBIDDEN' in str(excinfo)
+        self.query_for_users()
+
+        assert self.user.has_privilege(u'commenter')
+
+    def testReportResolution(self):
+        self.login(u'moderator')
+
+        # First, test a moderators taking away a user's privilege in response
+        # to a reported comment
+        #----------------------------------------------------------------------
+        fixture_add_comment_report(reported_user=self.user)
+        comment_report = CommentReport.query.filter(
+            CommentReport.reported_user==self.user).first()
+
+        response = self.test_app.get('/mod/reports/{0}/'.format(
+            comment_report.id))
+        assert response.status == '200 OK'
+        self.query_for_users()
+        comment_report = CommentReport.query.filter(
+            CommentReport.reported_user==self.user).first()
+
+        response, context = self.do_post({'action_to_resolve':[u'takeaway'],
+            'take_away_privileges':[u'commenter'],
+            'targeted_user':self.user.id},
+            url='/mod/reports/{0}/'.format(comment_report.id))
+
+        assert response.status == '302 FOUND'
+        fixture_add_comment_report(reported_user=self.user)
+        comment_report = CommentReport.query.filter(
+            CommentReport.reported_user==self.user).first()
+
+        assert not self.user.has_privilege(u'commenter')
+
+        # Then, test a moderator sending an email to a user in response to a
+        # reported comment
+        #----------------------------------------------------------------------
+        self.query_for_users()
+
+        response, context = self.do_post({'action_to_resolve':[u'sendmessage'],
+            'message_to_user':'This is your last warning, regular....',
+            'targeted_user':self.user.id},
+            url='/mod/reports/{0}/'.format(comment_report.id))
+
+        assert response.status == '302 FOUND'
+        assert mail.EMAIL_TEST_MBOX_INBOX ==  [{'to': [u'regular@example.com'],
+            'message': 'Content-Type: text/plain; charset="utf-8"\n\
+MIME-Version: 1.0\nContent-Transfer-Encoding: base64\nSubject: Warning from- \
+moderator \nFrom: notice@mediagoblin.example.org\nTo: regular@example.com\n\n\
+VGhpcyBpcyB5b3VyIGxhc3Qgd2FybmluZywgcmVndWxhci4uLi4=\n',
+            'from': 'notice@mediagoblin.example.org'}]
+
+        # Then test a moderator banning a user AND a moderator deleting the
+        # offending comment. This also serves as a test for taking multiple
+        # actions to resolve a report
+        #----------------------------------------------------------------------
+        self.query_for_users()
+        fixture_add_comment(author=self.user.id,
+            comment=u'Comment will be removed')
+        test_comment = MediaComment.query.filter(
+            MediaComment.author==self.user.id).first()
+        fixture_add_comment_report(comment=test_comment,
+            reported_user=self.user)
+        comment_report = CommentReport.query.filter(
+            CommentReport.reported_user==self.user).first()
+
+        response, context = self.do_post(
+            {'action_to_resolve':[u'userban', u'delete'],
+            'targeted_user':self.user.id},
+            url='/mod/reports/{0}/'.format(comment_report.id))
+        assert response.status == '302 FOUND'
+        self.query_for_users()
+        test_user_ban = UserBan.query.filter(
+            UserBan.user_id == self.user.id).first()
+        assert test_user_ban is not None
+        test_comment = MediaComment.query.filter(
+            MediaComment.author==self.user.id).first()
+        assert test_comment is None
+
+        # Then, test what happens when a moderator attempts to punish an admin
+        # from a reported comment on an admin.
+        #----------------------------------------------------------------------
+        fixture_add_comment_report(reported_user=self.admin_user)
+        comment_report = CommentReport.query.filter(
+            CommentReport.reported_user==self.admin_user).first()
+
+        response, context = self.do_post({'action_to_resolve':[u'takeaway'],
+            'take_away_privileges':[u'active'],
+            'targeted_user':self.admin_user.id},
+            url='/mod/reports/{0}/'.format(comment_report.id))
+        self.query_for_users()
+
+        assert response.status == '200 OK'
+        assert self.admin_user.has_privilege(u'active')
+
+    def testAllModerationViews(self):
+        self.login(u'moderator')
+        self.test_app.get('/mod/reports/')
+        self.test_app.get('/mod/users/')
+        self.test_app.get('/mod/media/')
index 8420e35897203114427eaf01ea00b26ea9848b9d..2b414590050022d7cfc94ec9cad0a3230f0a93e2 100644 (file)
@@ -127,7 +127,6 @@ otherperson@example.com\n\nSGkgb3RoZXJwZXJzb24sCmNocmlzIGNvbW1lbnRlZCBvbiB5b3VyI
         else:
             assert mail.EMAIL_TEST_MBOX_INBOX == []
 
-        mail.EMAIL_TEST_MBOX_INBOX = []
 
         # Save the ids temporarily because of DetachedInstanceError
         notification_id = notification.id
diff --git a/mediagoblin/tests/test_privileges.py b/mediagoblin/tests/test_privileges.py
new file mode 100644 (file)
index 0000000..ced87b7
--- /dev/null
@@ -0,0 +1,206 @@
+# 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 pytest
+from datetime import datetime, timedelta
+from webtest import AppError
+
+from mediagoblin.tests.tools import fixture_add_user, fixture_media_entry
+
+from mediagoblin.db.models import User, Privilege, UserBan
+from mediagoblin.db.base import Session
+from mediagoblin.tools import template
+
+from .resources import GOOD_JPG
+
+class TestPrivilegeFunctionality:
+
+    @pytest.fixture(autouse=True)
+    def _setup(self, test_app):
+        self.test_app = test_app
+
+        fixture_add_user(u'alex',
+            privileges=[u'admin',u'active'])
+        fixture_add_user(u'raven',
+            privileges=[u'moderator',u'active',u'reporter'])
+        fixture_add_user(u'natalie',
+            privileges=[u'active'])
+        self.query_for_users()
+
+    def login(self, username):
+        self.test_app.post(
+            '/auth/login/', {
+                'username': username,
+                'password': 'toast'})
+        self.query_for_users()
+
+    def logout(self):
+        self.test_app.get('/auth/logout/')
+        self.query_for_users()
+
+    def do_post(self, data, *context_keys, **kwargs):
+        url = kwargs.pop('url', '/submit/')
+        do_follow = kwargs.pop('do_follow', False)
+        template.clear_test_template_context()
+        response = self.test_app.post(url, data, **kwargs)
+        if do_follow:
+            response.follow()
+        context_data = template.TEMPLATE_TEST_CONTEXT
+        for key in context_keys:
+            context_data = context_data[key]
+        return response, context_data
+
+    def query_for_users(self):
+        self.admin_user = User.query.filter(User.username==u'alex').first()
+        self.mod_user = User.query.filter(User.username==u'raven').first()
+        self.user = User.query.filter(User.username==u'natalie').first()
+
+    def testUserBanned(self):
+        self.login(u'natalie')
+        uid = self.user.id
+        # First, test what happens when a user is banned indefinitely
+        #----------------------------------------------------------------------
+        user_ban = UserBan(user_id=uid,
+            reason=u'Testing whether user is banned',
+            expiration_date=None)
+        user_ban.save()
+
+        response = self.test_app.get('/')
+        assert response.status == "200 OK"
+        assert "You are Banned" in response.body
+        # Then test what happens when that ban has an expiration date which
+        # hasn't happened yet
+        #----------------------------------------------------------------------
+        user_ban = UserBan.query.get(uid)
+        user_ban.delete()
+        user_ban = UserBan(user_id=uid,
+            reason=u'Testing whether user is banned',
+            expiration_date= datetime.now() + timedelta(days=20))
+        user_ban.save()
+
+        response = self.test_app.get('/')
+        assert response.status == "200 OK"
+        assert "You are Banned" in response.body
+
+        # Then test what happens when that ban has an expiration date which
+        # has already happened
+        #----------------------------------------------------------------------
+        user_ban = UserBan.query.get(uid)
+        user_ban.delete()
+        exp_date = datetime.now() - timedelta(days=20)
+        user_ban = UserBan(user_id=uid,
+            reason=u'Testing whether user is banned',
+            expiration_date= exp_date)
+        user_ban.save()
+
+        response = self.test_app.get('/')
+        assert response.status == "302 FOUND"
+        assert not "You are Banned" in response.body
+
+    def testVariousPrivileges(self):
+        # The various actions that require privileges (ex. reporting,
+        # commenting, moderating...) are tested in other tests. This method
+        # will be used to ensure that those actions are impossible for someone
+        # without the proper privileges.
+        # For other tests that show what happens when a user has the proper
+        # privileges, check out:
+        #       tests/test_moderation.py                moderator
+        #       tests/test_notifications.py             commenter
+        #       tests/test_reporting.py                 reporter
+        #       tests/test_submission.py                uploader
+        #----------------------------------------------------------------------
+        self.login(u'natalie')
+
+        # First test the get and post requests of submission/uploading
+        #----------------------------------------------------------------------
+        with pytest.raises(AppError) as excinfo:
+            response = self.test_app.get('/submit/')
+        assert 'Bad response: 403 FORBIDDEN' in str(excinfo)
+
+
+        with pytest.raises(AppError) as excinfo:
+            response = self.do_post({'upload_files':[('file',GOOD_JPG)],
+                'title':u'Normal Upload 1'},
+                url='/submit/')
+        assert 'Bad response: 403 FORBIDDEN' in str(excinfo)
+
+        # Test that a user cannot comment without the commenter privilege
+        #----------------------------------------------------------------------
+        self.query_for_users()
+
+        media_entry = fixture_media_entry(uploader=self.admin_user.id,
+            state=u'processed')
+
+        media_entry_id = media_entry.id
+        media_uri_id = '/u/{0}/m/{1}/'.format(self.admin_user.username,
+                                              media_entry.id)
+        media_uri_slug = '/u/{0}/m/{1}/'.format(self.admin_user.username,
+                                                media_entry.slug)
+        response = self.test_app.get(media_uri_slug)
+        assert not "Add a comment" in response.body
+
+        self.query_for_users()
+        with pytest.raises(AppError) as excinfo:
+            response = self.test_app.post(
+                media_uri_id + 'comment/add/',
+                {'comment_content': u'Test comment #42'})
+        assert 'Bad response: 403 FORBIDDEN' in str(excinfo)
+
+        # Test that a user cannot report without the reporter privilege
+        #----------------------------------------------------------------------
+        with pytest.raises(AppError) as excinfo:
+            response = self.test_app.get(media_uri_slug+"report/")
+        assert 'Bad response: 403 FORBIDDEN' in str(excinfo)
+
+        with pytest.raises(AppError) as excinfo:
+            response = self.do_post(
+                {'report_reason':u'Testing Reports #1',
+                'reporter_id':u'3'},
+                url=(media_uri_slug+"report/"))
+        assert 'Bad response: 403 FORBIDDEN' in str(excinfo)
+
+        # Test that a user cannot access the moderation pages w/o moderator
+        # or admin privileges
+        #----------------------------------------------------------------------
+        with pytest.raises(AppError) as excinfo:
+            response = self.test_app.get("/mod/users/")
+        assert 'Bad response: 403 FORBIDDEN' in str(excinfo)
+
+        with pytest.raises(AppError) as excinfo:
+            response = self.test_app.get("/mod/reports/")
+        assert 'Bad response: 403 FORBIDDEN' in str(excinfo)
+
+        with pytest.raises(AppError) as excinfo:
+            response = self.test_app.get("/mod/media/")
+        assert 'Bad response: 403 FORBIDDEN' in str(excinfo)
+
+        with pytest.raises(AppError) as excinfo:
+            response = self.test_app.get("/mod/users/1/")
+        assert 'Bad response: 403 FORBIDDEN' in str(excinfo)
+
+        with pytest.raises(AppError) as excinfo:
+            response = self.test_app.get("/mod/reports/1/")
+        assert 'Bad response: 403 FORBIDDEN' in str(excinfo)
+
+        self.query_for_users()
+
+        with pytest.raises(AppError) as excinfo:
+            response, context = self.do_post({'action_to_resolve':[u'takeaway'],
+                'take_away_privileges':[u'active'],
+                'targeted_user':self.admin_user.id},
+                url='/mod/reports/1/')
+            self.query_for_users()
+        assert 'Bad response: 403 FORBIDDEN' in str(excinfo)
diff --git a/mediagoblin/tests/test_reporting.py b/mediagoblin/tests/test_reporting.py
new file mode 100644 (file)
index 0000000..1bc7df2
--- /dev/null
@@ -0,0 +1,165 @@
+# 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 pytest
+
+from mediagoblin.tools import template
+from mediagoblin.tests.tools import (fixture_add_user, fixture_media_entry,
+        fixture_add_comment, fixture_add_comment_report)
+from mediagoblin.db.models import (MediaReport, CommentReport, User,
+    MediaComment,ArchivedReport)
+
+
+class TestReportFiling:
+    @pytest.fixture(autouse=True)
+    def _setup(self, test_app):
+        self.test_app = test_app
+
+        fixture_add_user(u'allie',
+            privileges=[u'reporter',u'active'])
+        fixture_add_user(u'natalie',
+            privileges=[u'active', u'moderator'])
+
+    def login(self, username):
+        self.test_app.post(
+            '/auth/login/', {
+                'username': username,
+                'password': 'toast'})
+
+    def logout(self):
+        self.test_app.get('/auth/logout/')
+
+    def do_post(self, data, *context_keys, **kwargs):
+        url = kwargs.pop('url', '/submit/')
+        do_follow = kwargs.pop('do_follow', False)
+        template.clear_test_template_context()
+        response = self.test_app.post(url, data, **kwargs)
+        if do_follow:
+            response.follow()
+        context_data = template.TEMPLATE_TEST_CONTEXT
+        for key in context_keys:
+            context_data = context_data[key]
+        return response, context_data
+
+    def query_for_users(self):
+        return (User.query.filter(User.username==u'allie').first(),
+        User.query.filter(User.username==u'natalie').first())
+
+    def testMediaReports(self):
+        self.login(u'allie')
+        allie_user, natalie_user = self.query_for_users()
+        allie_id = allie_user.id
+
+        media_entry = fixture_media_entry(uploader=natalie_user.id,
+            state=u'processed')
+
+        mid = media_entry.id
+        media_uri_slug = '/u/{0}/m/{1}/'.format(natalie_user.username,
+                                                media_entry.slug)
+
+        response = self.test_app.get(media_uri_slug + "report/")
+        assert response.status == "200 OK"
+
+        response, context = self.do_post(
+            {'report_reason':u'Testing Media Report',
+            'reporter_id':unicode(allie_id)},url= media_uri_slug + "report/")
+
+        assert response.status == "302 FOUND"
+
+        media_report = MediaReport.query.first()
+
+        allie_user, natalie_user = self.query_for_users()
+        assert media_report is not None
+        assert media_report.report_content == u'Testing Media Report'
+        assert media_report.reporter_id == allie_id
+        assert media_report.reported_user_id == natalie_user.id
+        assert media_report.created is not None
+        assert media_report.discriminator == 'media_report'
+
+    def testCommentReports(self):
+        self.login(u'allie')
+        allie_user, natalie_user = self.query_for_users()
+        allie_id = allie_user.id
+
+        media_entry = fixture_media_entry(uploader=natalie_user.id,
+            state=u'processed')
+        mid = media_entry.id
+        fixture_add_comment(media_entry=mid,
+            author=natalie_user.id)
+        comment = MediaComment.query.first()
+
+        comment_uri_slug = '/u/{0}/m/{1}/c/{2}/'.format(natalie_user.username,
+                                                media_entry.slug,
+                                                comment.id)
+
+        response = self.test_app.get(comment_uri_slug + "report/")
+        assert response.status == "200 OK"
+
+        response, context = self.do_post({
+            'report_reason':u'Testing Comment Report',
+            'reporter_id':unicode(allie_id)},url= comment_uri_slug + "report/")
+
+        assert response.status == "302 FOUND"
+
+        comment_report = CommentReport.query.first()
+
+        allie_user, natalie_user = self.query_for_users()
+        assert comment_report is not None
+        assert comment_report.report_content == u'Testing Comment Report'
+        assert comment_report.reporter_id == allie_id
+        assert comment_report.reported_user_id == natalie_user.id
+        assert comment_report.created is not None
+        assert comment_report.discriminator == 'comment_report'
+
+    def testArchivingReports(self):
+        self.login(u'natalie')
+        allie_user, natalie_user = self.query_for_users()
+        allie_id, natalie_id = allie_user.id, natalie_user.id
+
+        fixture_add_comment(author=allie_user.id,
+            comment=u'Comment will be removed')
+        test_comment = MediaComment.query.filter(
+            MediaComment.author==allie_user.id).first()
+        fixture_add_comment_report(comment=test_comment,
+            reported_user=allie_user,
+            report_content=u'Testing Archived Reports #1',
+            reporter=natalie_user)
+        comment_report = CommentReport.query.filter(
+            CommentReport.reported_user==allie_user).first()
+
+        assert comment_report.report_content == u'Testing Archived Reports #1'
+        response, context = self.do_post(
+            {'action_to_resolve':[u'userban', u'delete'],
+            'targeted_user':allie_user.id,
+            'resolution_content':u'This is a test of archiving reports.'},
+            url='/mod/reports/{0}/'.format(comment_report.id))
+
+        assert response.status == "302 FOUND"
+        self.query_for_users()
+
+        archived_report = ArchivedReport.query.first()
+
+        assert CommentReport.query.count() == 0
+        assert archived_report is not None
+        assert archived_report.report_content == u'Testing Archived Reports #1'
+        assert archived_report.reporter_id == natalie_id
+        assert archived_report.reported_user_id == allie_id
+        assert archived_report.created is not None
+        assert archived_report.resolved is not None
+        assert archived_report.result == u'This is a test of archiving reports\
+.<br>natalie banned user allie indefinitely.<br>natalie deleted the comment.'
+        assert archived_report.discriminator == 'archived_report'
+
index ed0887300ffe2289785be5effd60def8db584cd0..d10957d734116405b10c6ed7b1d1bcfa031297b1 100644 (file)
@@ -24,7 +24,7 @@ import pytest
 
 from mediagoblin.tests.tools import fixture_add_user
 from mediagoblin import mg_globals
-from mediagoblin.db.models import MediaEntry, User
+from mediagoblin.db.models import MediaEntry, User, Privilege
 from mediagoblin.tools import template
 from mediagoblin.media_types.image import ImageMediaManager
 from mediagoblin.media_types.pdf.processing import check_prerequisites as pdf_check_prerequisites
@@ -48,10 +48,20 @@ class TestSubmission:
         # @as_authenticated_user('chris')
         fixture_add_user(privileges=[u'active',u'uploader'])
 
-        self.test_user = User.query.filter(User.username==u'chris').first()
-
         self.login()
 
+    def our_user(self):
+        """
+        Fetch the user we're submitting with.  Every .get() or .post()
+        invalidates the session; this is a hacky workaround.
+        """
+        #### FIXME: Pytest collects this as a test and runs this.
+        ####   ... it shouldn't.  At least it passes, but that's
+        ####   totally stupid.
+        ####   Also if we found a way to make this run it should be a
+        ####   property.
+        return User.query.filter(User.username==u'chris').first()
+
     def login(self):
         self.test_app.post(
             '/auth/login/', {
@@ -97,10 +107,10 @@ class TestSubmission:
     def check_normal_upload(self, title, filename):
         response, context = self.do_post({'title': title}, do_follow=True,
                                          **self.upload_data(filename))
-        self.check_url(response, '/u/{0}/'.format(self.test_user.username))
+        self.check_url(response, '/u/{0}/'.format(self.our_user().username))
         assert 'mediagoblin/user_pages/user.html' in context
         # Make sure the media view is at least reachable, logged in...
-        url = '/u/{0}/m/{1}/'.format(self.test_user.username,
+        url = '/u/{0}/m/{1}/'.format(self.our_user().username,
                                      title.lower().replace(' ', '-'))
         self.test_app.get(url)
         # ... and logged out too.
@@ -118,7 +128,7 @@ class TestSubmission:
         response, context = self.do_post({'title': u'Normal upload 3 (pdf)'},
                                          do_follow=True,
                                          **self.upload_data(GOOD_PDF))
-        self.check_url(response, '/u/{0}/'.format(self.test_user.username))
+        self.check_url(response, '/u/{0}/'.format(self.our_user().username))
         assert 'mediagoblin/user_pages/user.html' in context
 
     def check_media(self, request, find_data, count=None):
@@ -164,7 +174,7 @@ class TestSubmission:
         # render and post to the edit page.
         edit_url = request.urlgen(
             'mediagoblin.edit.edit_media',
-            user=self.test_user.username, media_id=media_id)
+            user=self.our_user().username, media_id=media_id)
         self.test_app.get(edit_url)
         self.test_app.post(edit_url,
             {'title': u'Balanced Goblin',
@@ -177,7 +187,7 @@ class TestSubmission:
         self.check_comments(request, media_id, 0)
         comment_url = request.urlgen(
             'mediagoblin.user_pages.media_post_comment',
-            user=self.test_user.username, media_id=media_id)
+            user=self.our_user().username, media_id=media_id)
         response = self.do_post({'comment_content': 'i love this test'},
                                 url=comment_url, do_follow=True)[0]
         self.check_comments(request, media_id, 1)
@@ -186,7 +196,7 @@ class TestSubmission:
         # ---------------------------------------------------
         delete_url = request.urlgen(
             'mediagoblin.user_pages.media_confirm_delete',
-            user=self.test_user.username, media_id=media_id)
+            user=self.our_user().username, media_id=media_id)
         # Empty data means don't confirm
         response = self.do_post({}, do_follow=True, url=delete_url)[0]
         media = self.check_media(request, {'title': u'Balanced Goblin'}, 1)
@@ -251,7 +261,7 @@ class TestSubmission:
         #   they'll be caught as failures during the processing step.
         response, context = self.do_post({'title': title}, do_follow=True,
                                          **self.upload_data(filename))
-        self.check_url(response, '/u/{0}/'.format(self.test_user.username))
+        self.check_url(response, '/u/{0}/'.format(self.our_user().username))
         entry = mg_globals.database.MediaEntry.query.filter_by(title=title).first()
         assert entry.state == 'failed'
         assert entry.fail_error == u'mediagoblin.processing:BadMediaFail'
index feb83b44c07842a00a4c855ae5cc9767582706ab..1d0354944a917f7ed9eaf843e1cebf9e4af3eeeb 100644 (file)
@@ -25,7 +25,7 @@ from webtest import TestApp
 
 from mediagoblin import mg_globals
 from mediagoblin.db.models import User, MediaEntry, Collection, MediaComment, \
-    CommentSubscription, CommentNotification, Privilege
+    CommentSubscription, CommentNotification, Privilege, CommentReport
 from mediagoblin.tools import testing
 from mediagoblin.init.config import read_mediagoblin_config
 from mediagoblin.db.base import Session
@@ -33,6 +33,8 @@ from mediagoblin.meddleware import BaseMeddleware
 from mediagoblin.auth import gen_password_hash
 from mediagoblin.gmg_commands.dbupdate import run_dbupdate
 
+from datetime import datetime
+
 
 MEDIAGOBLIN_TEST_DB_NAME = u'__mediagoblin_tests__'
 TEST_SERVER_CONFIG = pkg_resources.resource_filename(
@@ -312,3 +314,33 @@ def fixture_add_comment(author=None, media_entry=None, comment=None):
 
     return comment
 
+def fixture_add_comment_report(comment=None, reported_user=None,
+        reporter=None, created=None, report_content=None):
+    if comment is None:
+        comment = fixture_add_comment()
+
+    if reported_user is None:
+        reported_user = fixture_add_user()
+
+    if reporter is None:
+        reporter = fixture_add_user()
+
+    if created is None:
+        created=datetime.now()
+
+    if report_content is None:
+        report_content = \
+            'Auto-generated test report by user {0}'.format(
+                reporter)
+
+    comment_report = CommentReport(comment=comment,
+        reported_user = reported_user,
+        reporter = reporter,
+        created = created,
+        report_content=report_content)
+
+    comment_report.save()
+
+    Session.expunge(comment_report)
+
+    return comment_report
index a8cf1df984307542b71a7adf327bad44d5dc6203..3884de45f6e34d79475f32cad8269b4f3b5d2ba0 100644 (file)
@@ -52,7 +52,8 @@ def render_400(request, err_msg=None):
     _ = pass_to_ugettext
     title = _("Bad Request")
     if err_msg is None:
-        err_msg = _("The request sent to the server is invalid, please double check it")
+        err_msg = _("The request sent to the server is invalid, \
+please double check it")
 
     return render_error(request, 400, title, err_msg)
 
@@ -78,9 +79,11 @@ def render_user_banned(request):
     and the reason why they have been banned"
     """
     user_ban = UserBan.query.get(request.user.id)
-    if datetime.now()>user_ban.expiration_date:
+    if (user_ban.expiration_date is not None and
+            datetime.now()>user_ban.expiration_date):
+
         user_ban.delete()
-        redirect(request,
+        return redirect(request,
             'index')
     return render_to_response(request,
         'mediagoblin/banned.html',
@@ -141,7 +144,7 @@ def json_response(serializable, _disable_cors=False, *args, **kw):
     Any extra arguments and keyword arguments are passed to the
     Response.__init__ method.
     '''
-    
+
     response = wz_Response(json.dumps(serializable), *args, content_type='application/json', **kw)
 
     if not _disable_cors:
index 7f03fcd355a2c284b0db9aeccee65470a3ba5ff6..f29c1796ab277288775c51338a7d0366ebb276c2 100644 (file)
@@ -19,7 +19,7 @@ from mediagoblin.tools.template import render_template
 from mediagoblin.tools.translate import pass_to_ugettext as _
 from mediagoblin import mg_globals
 from mediagoblin.db.base import Session
-from mediagoblin.db.models import (CollectionItem, MediaReport, CommentReport, 
+from mediagoblin.db.models import (CollectionItem, MediaReport, CommentReport,
                                   MediaComment, MediaEntry)
 from mediagoblin.user_pages import forms as user_forms
 
@@ -80,14 +80,14 @@ def add_media_to_collection(collection, media, note=None, commit=True):
 
 def build_report_object(report_form, media_entry=None, comment=None):
     """
-    This function is used to convert a form object (from a User filing a 
+    This function is used to convert a form object (from a User filing a
         report) into either a MediaReport or CommentReport object.
 
-    :param report_form should be a MediaReportForm or a CommentReportForm 
+    :param report_form should be a MediaReportForm or a CommentReportForm
         object
-    :param 
+    :param
 
-    :returns either of MediaReport or a CommentReport object that has not been 
+    :returns either of MediaReport or a CommentReport object that has not been
         saved. In case of an improper form_dict, returns None
     """
 
index 00fcf282e0ce426f773491972f5de3633524c1dd..c2d2e66f8205f2016c435f7715bf4df2a6f121a6 100644 (file)
@@ -162,6 +162,7 @@ def media_home(request, media, page, **kwargs):
 
 @get_media_entry_by_id
 @require_active_login
+@user_has_privilege(u'commenter')
 def media_post_comment(request, media):
     """
     recieves POST from a MediaEntry() comment form, saves the comment.
@@ -651,13 +652,13 @@ def file_a_report(request, media, comment=None):
                    'form':form}
 
     if request.method == "POST":
-        report_table = build_report_object(form, 
-            media_entry=media, 
+        report_object = build_report_object(form,
+            media_entry=media,
             comment=comment)
 
         # if the object was built successfully, report_table will not be None
-        if report_table:
-            report_table.save()
+        if report_object:
+            report_object.save()
             return redirect(
                 request,
                 'index')
@@ -671,5 +672,5 @@ def file_a_report(request, media, comment=None):
 @require_active_login
 @get_user_media_entry
 @get_media_comment_by_id
-def file_a_comment_report(request, media, comment):        
+def file_a_comment_report(request, media, comment):
         return file_a_report(request, comment=comment)