This is the first stage of my project of implenting admin/moderator functiona-
authortilly-Q <nattilypigeonfowl@gmail.com>
Mon, 24 Jun 2013 23:35:31 +0000 (16:35 -0700)
committertilly-Q <nattilypigeonfowl@gmail.com>
Mon, 24 Jun 2013 23:35:31 +0000 (16:35 -0700)
lity. At this point, I have finished all the of basic work with the models! I
still need to do some tightening of their documentation, but they seem to be
working well.

Working with Models
========================================

--\ mediagoblin/db/models.py
--| Added in the Report model and table. This model is strictly a parent
----| Added in the CommentReport model which holds information about a report
    | filed against a comment. This class inherits from Report.
----| Added in the MediaReport model which holds information about a report f-
    | -iled against a media entry. This class inherits from Report.
--| Added in a UserBan model and table. This model is in a one to one relatio-
  | -nship with User. This object acts as a marker for whether a user is banned
  | or not.
--| Added in a Group model. These objects are in a many-to-many relationship
  | with User to explain which privileges a User has.
----| Added in GroupUserAssociation which is a table used to hold this many to
    | many relationship  between Group & User.

--\ mediagoblin/db/migrations.py
--| Added in the migrations for all of the additions to models
--| Added UserBan_v0
--| Added Report_v0
----| Added CommentReport_v0
----| Added MediaReport_v0
--| Added Group_v0
----| Added GroupUserAssociation_v0

Working with Templates, Views, and Routing
===============================================

>>> Reporting a Comment or a MediaEntry

--\ mediagoblin/user_pages/views.py
--| Added in the function file_a_report to allow user to file reports against
  | MediaEntries or Comments. Handles GET and POST requests.
--| Added in the function file_a_comment_report which uses file_a_report but
  | also catches appropriate information for comment_ids. I may be able to do
  | this more eloquently with decorators.

--\ mediagoblin/user_pages/routing.py
--| Added in route 'mediagoblin.user_pages.media_home.report_media'
  | (linked to address /u/<user>/m/<media>/report/ )
--| Added in route ''mediagoblin.user_pages.media_home.report_comment'
  | (linked to address /u/<user>/m/<media>/c/<comment>/report/ )

--\ mediagoblin/templates/mediagoblin/user_pages/report.html
--| I created this file to handle the filing of a report.

--\ mediagoblin/templates/mediagoblin/user_pages/media.html
--| Modified this file to add in links allowing users to report either media
  | or comments.

--\ mediagoblin/user_pages/lib.py
--| Added in build_report_form which processes data as either a CommentReport or
  | a MediaReport depending on which parameters are present

--\ mediagoblin/user_pages/forms.py
--| Added in CommentReportForm
--| Added in MediaReportForm
--| note: ReportForm is vestigial to an earlier strategy I used and I'll remove it
  | promptly

--\ mediagoblin/decorators.py
--| Added in 'get_media_comment_by_id' for use in mediagoblin/user_pages/views.py

>>> New Admin Panels

--\ mediagoblin/admin/views.py
--| Added in the function admin_users_panel
--| Added in the function admin_reports_panel

--\ mediagoblin/admin/routing.py
--| Added in route 'mediagoblin.admin.users'
  | (linked to address '/a/users')
--| Added in route 'mediagoblin.admin.reports'
  | (linked to address '/a/reports/')

--\ mediagoblin/templates/admin/user.html
--| Created this file as a template for monitoring users

--\ mediagoblin/templates/admin/report.html
--| Created this file as a template for monitoring reports filed against media or
  | comments

13 files changed:
mediagoblin/admin/routing.py
mediagoblin/admin/views.py
mediagoblin/db/migrations.py
mediagoblin/db/models.py
mediagoblin/decorators.py
mediagoblin/templates/mediagoblin/admin/report.html [new file with mode: 0644]
mediagoblin/templates/mediagoblin/admin/user.html [new file with mode: 0644]
mediagoblin/templates/mediagoblin/user_pages/media.html
mediagoblin/templates/mediagoblin/user_pages/report.html [new file with mode: 0644]
mediagoblin/user_pages/forms.py
mediagoblin/user_pages/lib.py
mediagoblin/user_pages/routing.py
mediagoblin/user_pages/views.py

index 29515f1243c2fd7beef6e72c2a5fca5662f017df..d5edac0ff17c0935320fbc19623fe916579763a9 100644 (file)
 admin_routes = [
     ('mediagoblin.admin.panel',
         '/panel',
-        'mediagoblin.admin.views:admin_processing_panel')]
+        'mediagoblin.admin.views:admin_processing_panel'),
+    ('mediagoblin.admin.users',
+        '/users',
+        'mediagoblin.admin.views:admin_users_panel'),
+    ('mediagoblin.admin.reports',
+        '/reports',
+        'mediagoblin.admin.views:admin_reports_panel')]
index 22ca74a34d94984b6735fc047b4aacb6c88d4f49..faa8603adfbd43940fe4abf32bcaa5171c8b9008 100644 (file)
@@ -16,7 +16,7 @@
 
 from werkzeug.exceptions import Forbidden
 
-from mediagoblin.db.models import MediaEntry
+from mediagoblin.db.models import MediaEntry, User, MediaComment, CommentReport, ReportBase
 from mediagoblin.decorators import require_active_login
 from mediagoblin.tools.response import render_to_response
 
@@ -46,3 +46,40 @@ def admin_processing_panel(request):
         {'processing_entries': processing_entries,
          'failed_entries': failed_entries,
          'processed_entries': processed_entries})
+
+@require_active_login
+def admin_users_panel(request):
+    '''
+    Show the global processing panel for this instance
+    '''
+    # TODO: Why not a "require_admin_login" decorator throwing a 403 exception?
+    if not request.user.is_admin:
+        raise Forbidden()
+
+    user_list = User.query
+
+    # Render to response
+    return render_to_response(
+        request,
+        'mediagoblin/admin/user.html',
+        {'user_list': user_list})
+
+@require_active_login
+def admin_reports_panel(request):
+    '''
+    Show the global processing panel for this instance
+    '''
+    # TODO: Why not a "require_admin_login" decorator throwing a 403 exception?
+    if not request.user.is_admin:
+        raise Forbidden()
+
+    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.html',
+        {'report_list':report_list,
+         'closed_report_list':closed_report_list})
+
index 2c55339608610c90726e56d1ffa26741a23e158d..110a48d4f4b2c5849727af0a04005a6de58254b0 100644 (file)
@@ -26,7 +26,7 @@ from sqlalchemy.sql import and_
 from migrate.changeset.constraint import UniqueConstraint
 
 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, Group
 
 MIGRATIONS = {}
 
@@ -287,3 +287,70 @@ def unique_collections_slug(db):
     constraint.create()
 
     db.commit()
+
+class ReportBase_v0(declarative_base()):
+    """
+
+    """
+    __tablename__ = 'core__reports'
+    id = Column(Integer, primary_key=True)
+    reporter_id = Column(Integer, ForeignKey(User.id), nullable=False)
+    report_content = Column(UnicodeText)
+    created = Column(DateTime, nullable=False, default=datetime.datetime.now) 
+    resolved = Column(DateTime)
+    discriminator = Column('type', Unicode(50))
+    __mapper_args__ = {'polymorphic_on': discriminator}
+
+
+class CommentReport_v0(ReportBase_v0):
+    __tablename__ = 'core__reports_on_comments'
+    __mapper_args__ = {'polymorphic_identity': 'comment_report'}
+
+    id = Column('id',Integer, ForeignKey('core__reports.id'),
+                                                primary_key=True)
+    comment_id = Column(Integer, ForeignKey(MediaComment.id), nullable=False)
+
+class MediaReport_v0(ReportBase_v0):
+    __tablename__ = 'core__reports_on_media'
+    __mapper_args__ = {'polymorphic_identity': 'media_report'}
+
+    id = Column('id',Integer, ForeignKey('core__reports.id'),
+                                                primary_key=True)
+    media_entry_id = Column(Integer, ForeignKey(MediaEntry.id), nullable=False)
+
+@RegisterMigration(11, MIGRATIONS)
+def create_report_tables(db):
+    ReportBase_v0.__table__.create(db.bind)
+    CommentReport_v0.__table__.create(db.bind)
+    MediaReport_v0.__table__.create(db.bind)
+    db.commit()
+
+class UserBan_v0(declarative_base()):
+    __tablename__ = 'core__user_bans'
+    user_id = Column('id',Integer, ForeignKey(User.id), nullable=False,
+                                         primary_key=True)
+    expiration_date = Column(DateTime)
+    reason = Column(UnicodeText, nullable=False)
+
+class Group_v0(declarative_base()):
+    __tablename__ = 'core__groups'
+    id = Column(Integer, nullable=False, primary_key=True)
+    group_name = Column(Unicode, nullable=False)
+
+class GroupUserAssociation_v0(declarative_base()):
+    __tablename__ = 'core__group_user_associations'
+
+    group_id = Column('core__group_id', Integer, ForeignKey(User.id), primary_key=True)
+    user_id = Column('core__user_id', Integer, ForeignKey(Group.id), primary_key=True)
+
+
+
+@RegisterMigration(12, MIGRATIONS)
+def create_banned_and_group_tables(db):
+    UserBan_v0.__table__.create(db.bind)
+    Group_v0.__table__.create(db.bind)
+    GroupUserAssociation_v0.__table__.create(db.bind)
+    db.commit()
+
+
+
index 2b92598344baaaee3edefbc6e877878077c97de0..f524b220a1ad3d81ea296ed281022e05e1770a36 100644 (file)
@@ -29,6 +29,7 @@ from sqlalchemy.orm.collections import attribute_mapped_collection
 from sqlalchemy.sql.expression import desc
 from sqlalchemy.ext.associationproxy import association_proxy
 from sqlalchemy.util import memoized_property
+from sqlalchemy.schema import Table
 
 from mediagoblin.db.extratypes import PathTupleWithSlashes, JSONEncoded
 from mediagoblin.db.base import Base, DictReadAttrProxy
@@ -484,10 +485,93 @@ class ProcessingMetaData(Base):
         return DictReadAttrProxy(self)
 
 
+class ReportBase(Base):
+    """
+
+    """
+    __tablename__ = 'core__reports'
+    id = Column(Integer, primary_key=True)
+    reporter_id = Column(Integer, ForeignKey(User.id), nullable=False)
+    reporter =  relationship(User, backref=backref("reports_filed_by",
+                                                                lazy="dynamic",
+                                                                cascade="all, delete-orphan"))
+    report_content = Column(UnicodeText)
+    created = Column(DateTime, nullable=False, default=datetime.datetime.now()) 
+    resolved = Column(DateTime)
+    discriminator = Column('type', Unicode(50))
+    __mapper_args__ = {'polymorphic_on': discriminator}
+
+
+class CommentReport(ReportBase):
+    """
+    A class to keep track of reports that have been filed on comments
+    """
+    __tablename__ = 'core__reports_on_comments'
+    __mapper_args__ = {'polymorphic_identity': 'comment_report'}
+
+    id = Column('id',Integer, ForeignKey('core__reports.id'),
+                                                primary_key=True)
+    comment_id = Column(Integer, ForeignKey(MediaComment.id), nullable=False)
+    comment = relationship(MediaComment, backref=backref("reports_filed_on",
+                                                                lazy="dynamic",
+                                                                cascade="all, delete-orphan"))
+
+class MediaReport(ReportBase):
+    """
+    A class to keep track of reports that have been filed on media entries
+    """
+    __tablename__ = 'core__reports_on_media'
+    __mapper_args__ = {'polymorphic_identity': 'media_report'}
+
+    id = Column('id',Integer, ForeignKey('core__reports.id'),
+                                                primary_key=True)
+    media_entry_id = Column(Integer, ForeignKey(MediaEntry.id), nullable=False)
+    media_entry = relationship(MediaEntry, backref=backref("reports_filed_on",
+                                                                lazy="dynamic",
+                                                                cascade="all, delete-orphan"))
+
+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 lifted
+        :param user_id          Holds the id of the user this object is attached to.
+                                    This should be a one-to-one relationship.
+        :param 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.
+    """
+    __tablename__ = 'core__user_bans'
+
+    user_id = Column('id',Integer, ForeignKey(User.id), nullable=False,
+                                         primary_key=True)
+    expiration_date = Column(DateTime)
+    reason = Column(UnicodeText, nullable=False)
+
+
+class Group(Base):
+    __tablename__ = 'core__groups'
+
+    id = Column(Integer, nullable=False, primary_key=True)
+    group_name = Column(Unicode, nullable=False)
+    all_users = relationship(User, backref='all_groups', secondary="core__group_user_associations")
+
+    def __repr__(self):
+        return "<Group %s>" % (self.group_name)
+
+class GroupUserAssociation(Base):
+    __tablename__ = 'core__group_user_associations'
+
+    group_id = Column('core__group_id', Integer, ForeignKey(User.id), primary_key=True)
+    user_id = Column('core__user_id', Integer, ForeignKey(Group.id), primary_key=True)
+
+
+
 MODELS = [
     User, MediaEntry, Tag, MediaTag, MediaComment, Collection, CollectionItem, MediaFile, FileKeynames,
-    MediaAttachmentFile, ProcessingMetaData]
-
+    MediaAttachmentFile, ProcessingMetaData, CommentReport, MediaReport, UserBan, Group, GroupUserAssociation]
 
 ######################################################
 # Special, migrations-tracking table
index f3535fcf638c9c45073bb5081aab9cbc689a79b5..5b55ead7c81073250544ae0c01852731a4122523 100644 (file)
@@ -21,7 +21,7 @@ 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
+from mediagoblin.db.models import MediaEntry, User, MediaComment
 from mediagoblin.tools.response import redirect, render_404
 
 
@@ -226,6 +226,24 @@ def get_media_entry_by_id(controller):
     return wrapper
 
 
+def get_media_comment_by_id(controller):
+    """
+    Pass in a MediaComment based off of a url component
+    """
+    @wraps(controller)
+    def wrapper(request, *args, **kwargs):
+        comment = MediaComment.query.filter_by(
+                id=request.matchdict['comment']).first()
+        # Still no media?  Okay, 404.
+        if not comment:
+            return render_404(request)
+
+        return controller(request, comment=comment, *args, **kwargs)
+
+    return wrapper
+
+
+
 def get_workbench(func):
     """Decorator, passing in a workbench as kwarg which is cleaned up afterwards"""
 
diff --git a/mediagoblin/templates/mediagoblin/admin/report.html b/mediagoblin/templates/mediagoblin/admin/report.html
new file mode 100644 (file)
index 0000000..ff5cb42
--- /dev/null
@@ -0,0 +1,95 @@
+{#
+# 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/>.
+#}
+{% extends "mediagoblin/base.html" %}
+
+{% block title -%}
+  {% trans %}Report panel{% endtrans %} &mdash; {{ super() }}
+{%- endblock %}
+
+{% block mediagoblin_content %}
+
+<h1>{% trans %}Report panel{% endtrans %}</h1>
+
+<p>
+  {% 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>
+
+{% if report_list.count() %}
+  <table class="media_panel processing">
+    <tr>
+      <th>ID</th>
+      <th>Report Type</th>
+      <th>Offender</th>
+      <th>When Reported</th>
+      <th>Reported By</th>
+      <th>Reason</th>
+      <th>Reported Comment or Media Entry</th>
+    </tr>
+    {% for report in report_list %}
+      <tr>
+        {% 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>
+            <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.id }}</td>
+            <td>Media Report</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 %}
+      </tr>
+    {% endfor %}
+  </table>
+{% else %}
+  <p><em>{% trans %}No open reports found.{% endtrans %}</em></p>
+{% endif %}
+<h2>{% trans %}Closed Reports on Comments{% endtrans %}</h2>
+{% if closed_report_list.count() %}
+  <table class="media_panel processing">
+    <tr>
+      <th>ID</th>
+      <th>Offender</th>
+      <th>When Reported</th>
+      <th>Reported By</th>
+      <th>Reason</th>
+      <th>Comment Posted On</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>
+    {% endfor %}
+  </table>
+{% else %}
+  <p><em>{% trans %}No closed reports found.{% endtrans %}</em></p>
+{% endif %} 
+{% endblock %}
diff --git a/mediagoblin/templates/mediagoblin/admin/user.html b/mediagoblin/templates/mediagoblin/admin/user.html
new file mode 100644 (file)
index 0000000..6b6d226
--- /dev/null
@@ -0,0 +1,54 @@
+{#
+# 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/>.
+#}
+{% extends "mediagoblin/base.html" %}
+
+{% block title -%}
+  {% trans %}User panel{% endtrans %} &mdash; {{ super() }}
+{%- endblock %}
+
+{% block mediagoblin_content %}
+
+<h1>{% trans %}User panel{% endtrans %}</h1>
+
+<p>
+  {% trans %}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() %}
+  <table class="media_panel processing">
+    <tr>
+      <th>ID</th>
+      <th>Username</th>
+      <th>When Joined</th>
+      <th># of Comments Posted</th>
+    </tr>
+    {% for user in user_list %}
+      <tr>
+        <td>{{ user.id }}</td>
+        <td>{{ user.username }}</td>
+        <td>{{ user.created.strftime("%F %R") }}</td>
+        <td>{{ user.posted_comments.count() }}</td>
+      </tr>
+    {% endfor %}
+  </table>
+{% else %}
+  <p><em>{% trans %}No users found.{% endtrans %}</em></p>
+{% endif %} 
+{% endblock %}
index fb892fd788ab1e364a2eea976dcd33009c594fd3..134a80ad403e641b145491315bd6a98485f2a780 100644 (file)
           {% trans %}Add a comment{% endtrans %}
         </a>
       {% endif %}
+        <a 
+          {% if not request.user -%}
+            href="{{ request.urlgen('mediagoblin.auth.login') }}"
+          {% else %}
+            href="{{ request.urlgen('mediagoblin.user_pages.media_home.report_media',
+                            user=media.get_uploader.username,
+                             media=media.slug_or_id) }}"
+          {% endif %}
+          class="button_action" id="button_reportmedia" title="Report media">
+          {% trans %}Report media{% endtrans %}
+        </a>
       {% if request.user %}
         <form action="{{ request.urlgen('mediagoblin.user_pages.media_post_comment',
                                          user= media.get_uploader.username,
               {{ comment.content_html }}
             {%- endautoescape %}
           </div>
+          <div>
+                <a {% if not request.user -%}
+                    href="{{ request.urlgen('mediagoblin.auth.login') }}"
+                  {%- else %}
+                    href="{{ request.urlgen('mediagoblin.user_pages.media_home.report_comment',
+                            user=media.get_uploader.username,
+                             media=media.slug_or_id,
+                             comment=comment.id) }}"
+                  {%- endif %}>
+                    {% trans %} Report {% endtrans %}</a>
+          </div>
         </li>
       {% endfor %}
       </ul>
diff --git a/mediagoblin/templates/mediagoblin/user_pages/report.html b/mediagoblin/templates/mediagoblin/user_pages/report.html
new file mode 100644 (file)
index 0000000..9431efc
--- /dev/null
@@ -0,0 +1,75 @@
+{#
+# 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/>.
+#}
+{%- extends "mediagoblin/base.html" %}
+
+{%- block mediagoblin_content %}
+<h2>File a Report</h2>
+<form action="" method=POST >
+    {% if comment is defined %}
+        <h4>{% trans %}Reporting this Comment {% endtrans %}</h3>
+        {% set comment_author = comment.get_author %}
+        <div id="comment-{{ comment.id }}"
+            class="comment_wrapper">
+          <div class="comment_author">
+            <img src="{{ request.staticdirect('/images/icon_comment.png') }}" />
+            <a href="{{ request.urlgen('mediagoblin.user_pages.user_home',
+                            user=comment_author.username) }}"
+               class="comment_authorlink">
+              {{- comment_author.username -}}
+            </a>
+            <a href="{{ request.urlgen('mediagoblin.user_pages.media_home.view_comment',
+                            comment=comment.id,
+                            user=media.get_uploader.username,
+                            media=media.slug_or_id) }}#comment"
+               class="comment_whenlink">
+              <span title='{{- comment.created.strftime("%I:%M%p %Y-%m-%d") -}}'>
+                {%- trans formatted_time=timesince(comment.created) -%}
+                  {{ formatted_time }} ago
+                {%- endtrans -%}
+              </span></a>:
+          </div>
+          <div class="comment_content">
+            {% autoescape False -%}
+              {{ comment.content_html }}
+            {%- endautoescape %}
+          </div>
+        </div>
+    <input type=hidden name=comment_id value="{{ comment.id }}" />
+    {% elif media is defined %}
+        <h3>{% trans %}Reporting this Media Entry {% endtrans %}</h3>
+            {% trans %}published by {% endtrans %}<a href="{{ request.urlgen('mediagoblin.user_pages.user_home', user=media.get_uploader.username) }}" class="comment_authorlink">{{ media.get_uploader.username }}</a>
+            <div class="media_thumbnail">
+            <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></td></tr>
+            <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>
+    <input type=hidden name=media_entry_id value="{{ media.id }}" />
+    {% endif %}
+    <div class=form_field_input>
+        <label class=form_field_label >{% trans %}Reason for reporting:{% endtrans %}</label>
+        <textarea name=report_reason></textarea>
+    </div>
+    <input type=hidden name=reporter_id value="{{ request.user.id }}" />
+    <input type=submit />
+       {{ csrf_token }}
+</form>
+{% endblock %}
index 9a1936805500f6cfda70204575e68651160f0046..effe49e198e88a02db717bcdae2a92762d128b1e 100644 (file)
@@ -49,3 +49,19 @@ class MediaCollectForm(wtforms.Form):
         description=_("""You can use
                       <a href="http://daringfireball.net/projects/markdown/basics">
                       Markdown</a> for formatting."""))
+
+class CommentReportForm(wtforms.Form):
+    report_reason = wtforms.TextAreaField('Reason for Reporting')
+    comment_id = wtforms.IntegerField()
+    reporter_id = wtforms.IntegerField()
+
+class MediaReportForm(wtforms.Form):
+    report_reason = wtforms.TextAreaField('Reason for Reporting')
+    media_entry_id = wtforms.IntegerField()
+    reporter_id = wtforms.IntegerField()
+
+class ReportForm(wtforms.Form):
+    report_reason = wtforms.TextAreaField('Reason for Reporting')
+    media_entry_id = wtforms.IntegerField()
+    reporter_id = wtforms.IntegerField()
+    comment_id = wtforms.IntegerField()
index 2f47e4b18581ca0c3eca1bfa780204b7db37dfeb..9c8ddee6efc4a6e1f7ea8a918922891b8f2ee036 100644 (file)
@@ -19,7 +19,8 @@ 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
+from mediagoblin.db.models import CollectionItem, MediaReport, CommentReport
+from mediagoblin.user_pages import forms as user_forms
 
 
 def send_comment_email(user, comment, media, request):
@@ -75,3 +76,31 @@ def add_media_to_collection(collection, media, note=None, commit=True):
 
     if commit:
         Session.commit()
+
+def build_report_form(form_dict):
+    """
+    :param form_dict should be an ImmutableMultiDict object which is what is
+            returned from 'request.form.' The Object should have valid keys
+            matching the fields in either MediaReportForm or CommentReportForm
+
+    :returns either of MediaReport or a CommentReport object that has not been saved.
+            In case of an improper form_dict, returns None
+    """
+    if 'comment_id' in form_dict.keys():
+        report_form = user_forms.CommentReportForm(form_dict)
+    elif 'media_entry_id' in form_dict.keys():
+        report_form = user_forms.MediaReportForm(form_dict)
+    else:
+        return None
+    if report_form.validate() and 'comment_id' in form_dict.keys():
+        report_model = CommentReport()
+        report_model.comment_id = report_form.comment_id.data
+    elif report_form.validate() and 'media_entry_id' in form_dict.keys():
+        report_model = MediaReport()
+        report_model.media_entry_id = report_form.media_entry_id.data
+    else:
+        return None
+    report_model.report_content = report_form.report_reason.data or u''
+    report_model.reporter_id = report_form.reporter_id.data
+    return report_model
+
index 9cb665b51519a2c29efafdb808c601f9dc0ef8b7..adccda9e0d1941f06d6c79d3e92530fd80a778bb 100644 (file)
@@ -23,6 +23,10 @@ add_route('mediagoblin.user_pages.media_home',
           '/u/<string:user>/m/<string:media>/',
           'mediagoblin.user_pages.views:media_home')
 
+add_route('mediagoblin.user_pages.media_home.report_media',
+          '/u/<string:user>/m/<string:media>/report/',
+          'mediagoblin.user_pages.views:file_a_report')
+
 add_route('mediagoblin.user_pages.media_confirm_delete',
           '/u/<string:user>/m/<int:media_id>/confirm-delete/',
           'mediagoblin.user_pages.views:media_confirm_delete')
@@ -40,6 +44,10 @@ add_route('mediagoblin.user_pages.media_home.view_comment',
           '/u/<string:user>/m/<string:media>/c/<int:comment>/',
           'mediagoblin.user_pages.views:media_home')
 
+add_route('mediagoblin.user_pages.media_home.report_comment',
+          '/u/<string:user>/m/<string:media>/c/<int:comment>/report/',
+          'mediagoblin.user_pages.views:file_a_comment_report')
+
 # User's tags gallery
 add_route('mediagoblin.user_pages.user_tag_gallery',
           '/u/<string:user>/tag/<string:tag>/',
index 738cc054f51b594d2c48d96a4d276fd89f486e13..94cccc66aae16bb75bb706581cc03be7ddbe275b 100644 (file)
@@ -19,19 +19,21 @@ import datetime
 
 from mediagoblin import messages, mg_globals
 from mediagoblin.db.models import (MediaEntry, MediaTag, Collection,
-                                   CollectionItem, User)
+                                   CollectionItem, User, MediaComment,
+                                   CommentReport, MediaReport)
 from mediagoblin.tools.response import render_to_response, render_404, \
     redirect, redirect_obj
 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,
+from mediagoblin.user_pages.lib import (send_comment_email, build_report_form,
     add_media_to_collection)
 
 from mediagoblin.decorators import (uses_pagination, get_user_media_entry,
     get_media_entry_by_id,
     require_active_login, user_may_delete_media, user_may_alter_collection,
-    get_user_collection, get_user_collection_item, active_user_from_url)
+    get_user_collection, get_user_collection_item, active_user_from_url,
+    get_media_comment_by_id)
 
 from werkzeug.contrib.atom import AtomFeed
 
@@ -616,3 +618,28 @@ def processing_panel(request):
          'processing_entries': processing_entries,
          'failed_entries': failed_entries,
          'processed_entries': processed_entries})
+
+@require_active_login
+@get_user_media_entry
+def file_a_report(request, media, comment=None):
+    if request.method == "POST":
+        report_form = build_report_form(request.form)
+        report_form.save()
+        return redirect(
+              request,
+             'index')
+    if comment is not None:
+        context = {'media': media,
+                 'comment':comment}
+    else:
+        context = {'media': media}
+    return render_to_response(
+            request,
+                'mediagoblin/user_pages/report.html',
+                context)
+
+@require_active_login
+@get_user_media_entry
+@get_media_comment_by_id
+def file_a_comment_report(request, media, comment):        
+        return file_a_report(request, comment=comment)