In this commit, I have made a few changes and tightened up some of my models
authortilly-Q <nattilypigeonfowl@gmail.com>
Thu, 27 Jun 2013 21:13:42 +0000 (14:13 -0700)
committertilly-Q <nattilypigeonfowl@gmail.com>
Thu, 27 Jun 2013 21:13:42 +0000 (14:13 -0700)
code. I added in two major pieces of functionality: table foundations and a
decorator to confirm whether or not a user is a member of a certain group.

Table Foundations are default rows that should be present in a given table as
soon as the database is initialized. For example, I am using these to populate
the core__groups table with all of the necessary groups ('moderator', 'com-
menter', etc). Right now, this is achieved by adding a dictionary of parameters
(with the parameters as lists) to the constant FOUNDATIONS in
mediagoblin.db.models. The keys to this dictionary are uninstantiated classes.
The classes which require foundations also have must have a constructor so that
the list of parameters can be passed appropriately like so:
        Model(*parameters)
In order to implement these foundations, I added the method populate_table_fou-
-ndations to MigrationManager in mediagoblin.db.migration_tools.

The decorator, called user_in_group, accepts as a parameter a unicode string,
and then decides whether to redirect to 403 or let the user access the page. The
identifier is the Group.group_name string, because I believe that will allow for
the most readable code.

I also added in the simple decorator require_admin_login.

In terms of tightening up my code, I made many minor changes to my use of white
space and made a few small documentation additions. I removed a vestigial class
(ReportForm) from mediagoblin.user_pages.forms. I moved all of my migrations in-
to one registered Migration.

Setting up Foundations
==============================

--\ mediagoblin/db/migration_tools.py
--| created: MigrationManager.populate_table_foundations
--| modified: MigrationManager.init_or_migrate to run
  |     self.populate_table_foundations on init

--\ mediagoblin/db/models.py
--| created: FOUNDATIONS
----| created: group_foundations

Working With Permissions
==============================
--\ mediagoblin/decorators.py
--| created: user_in_group
--| created: require_admin_login

--\ mediagoblin/user_pages/views.py
--| modified: added decorator user_in_group to file_a_report

--\ mediagoblin/admin/views.py
--| modified: added decorator require_admin_login to all views functions

General Code Tidying
=============================

--/ mediagoblin/admin/views.py
--/ mediagoblin/user_pages/forms.py
--/ mediagoblin/db/models.py
--/ mediagoblin/user_pages/lib.py
--/ mediagoblin/user_pages/views.py
--/ mediagoblin/db/migrations.py

mediagoblin/admin/views.py
mediagoblin/db/migration_tools.py
mediagoblin/db/migrations.py
mediagoblin/db/models.py
mediagoblin/decorators.py
mediagoblin/user_pages/forms.py
mediagoblin/user_pages/lib.py
mediagoblin/user_pages/views.py

index faa8603adfbd43940fe4abf32bcaa5171c8b9008..7a4dfbd43964ba81f8225e4715a8cee878896486 100644 (file)
 from werkzeug.exceptions import Forbidden
 
 from mediagoblin.db.models import MediaEntry, User, MediaComment, CommentReport, ReportBase
-from mediagoblin.decorators import require_active_login
+from mediagoblin.decorators import require_admin_login
 from mediagoblin.tools.response import render_to_response
 
-@require_active_login
+@require_admin_login
 def admin_processing_panel(request):
     '''
-    Show the global processing panel for this instance
+    Show the global media 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()
-
     processing_entries = MediaEntry.query.filter_by(state = u'processing').\
         order_by(MediaEntry.created.desc())
 
@@ -47,15 +43,11 @@ def admin_processing_panel(request):
          'failed_entries': failed_entries,
          'processed_entries': processed_entries})
 
-@require_active_login
+@require_admin_login
 def admin_users_panel(request):
     '''
-    Show the global processing panel for this instance
+    Show the global panel for monitoring users in 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
@@ -64,17 +56,18 @@ def admin_users_panel(request):
         'mediagoblin/admin/user.html',
         {'user_list': user_list})
 
-@require_active_login
+@require_admin_login
 def admin_reports_panel(request):
     '''
-    Show the global processing panel for this instance
+    Show the global panel for monitoring reports filed against comments or 
+        media entries 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)
+    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(
index c0c7e9981e717329262bcbcbd79682fce79150e4..f100f47ea2ff9e3f9f197063b27ea20c40f3922b 100644 (file)
@@ -140,6 +140,17 @@ class MigrationManager(object):
             self.session.bind,
             tables=[model.__table__ for model in self.models])
 
+    def populate_table_foundations(self):
+        """
+        Create the table foundations (default rows) as layed out in FOUNDATIONS
+            in mediagoblin.db.models
+        """
+        from mediagoblin.db.models import FOUNDATIONS as MAIN_FOUNDATIONS
+        for Model in MAIN_FOUNDATIONS.keys():
+            for parameters in MAIN_FOUNDATIONS[Model]:
+                row = Model(*parameters)
+                row.save()
+
     def create_new_migration_record(self):
         """
         Create a new migration record for this migration set
@@ -203,8 +214,10 @@ class MigrationManager(object):
 
             self.init_tables()
             # auto-set at latest migration number
-            self.create_new_migration_record()  
-            
+            self.create_new_migration_record() 
+            if self.name==u'__main__': 
+                self.populate_table_foundations()
+
             self.printer(u"done.\n")
             self.set_current_migration()
             return u'inited'
index 110a48d4f4b2c5849727af0a04005a6de58254b0..5e9a71d40a20e4a817f06118a5eee85311aa9416 100644 (file)
@@ -314,16 +314,13 @@ 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)
+    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'
@@ -334,23 +331,31 @@ class UserBan_v0(declarative_base()):
 
 class Group_v0(declarative_base()):
     __tablename__ = 'core__groups'
-    id = Column(Integer, nullable=False, primary_key=True)
+    id = Column(Integer, nullable=False, primary_key=True, unique=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)
+    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):
+@RegisterMigration(11, MIGRATIONS)
+def create_moderation_tables(db):
+    ReportBase_v0.__table__.create(db.bind)
+    CommentReport_v0.__table__.create(db.bind)
+    MediaReport_v0.__table__.create(db.bind)
     UserBan_v0.__table__.create(db.bind)
     Group_v0.__table__.create(db.bind)
     GroupUserAssociation_v0.__table__.create(db.bind)
     db.commit()
 
 
-
index f524b220a1ad3d81ea296ed281022e05e1770a36..28e01a850a43952e1c41d91dd83e18af388b2208 100644 (file)
@@ -492,11 +492,13 @@ 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"))
+    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()) 
+    created = Column(DateTime, nullable=False, default=datetime.datetime.now())
     resolved = Column(DateTime)
     discriminator = Column('type', Unicode(50))
     __mapper_args__ = {'polymorphic_on': discriminator}
@@ -512,9 +514,10 @@ class CommentReport(ReportBase):
     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"))
+    comment = relationship(
+        MediaComment, backref=backref("reports_filed_on",
+            lazy="dynamic",
+            cascade="all, delete-orphan"))
 
 class MediaReport(ReportBase):
     """
@@ -526,27 +529,32 @@ class MediaReport(ReportBase):
     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"))
+    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.
+    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 is 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)
+    user_id = Column(Integer, ForeignKey(User.id), nullable=False, 
+                                                        primary_key=True)
     expiration_date = Column(DateTime)
     reason = Column(UnicodeText, nullable=False)
 
@@ -555,8 +563,14 @@ 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")
+    group_name = Column(Unicode, nullable=False, unique=True)
+    all_users = relationship(
+        User, 
+        backref='all_groups', 
+        secondary="core__group_user_associations")
+
+    def __init__(self, group_name):
+        self.group_name = group_name
 
     def __repr__(self):
         return "<Group %s>" % (self.group_name)
@@ -564,14 +578,31 @@ class Group(Base):
 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)
+    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)
 
 
+group_foundations = [[u'admin'], [u'moderator'], [u'commenter'], [u'uploader'],[u'reporter'],[u'active']]
 
 MODELS = [
-    User, MediaEntry, Tag, MediaTag, MediaComment, Collection, CollectionItem, MediaFile, FileKeynames,
-    MediaAttachmentFile, ProcessingMetaData, CommentReport, MediaReport, UserBan, Group, GroupUserAssociation]
+    User, MediaEntry, Tag, MediaTag, MediaComment, Collection, CollectionItem, 
+    MediaFile, FileKeynames, MediaAttachmentFile, ProcessingMetaData, ReportBase,
+    CommentReport, MediaReport, UserBan, Group, GroupUserAssociation]
+
+# Foundations are the default rows that are created immediately after the tables are initialized. Each entry to
+#   this dictionary should be in the format of 
+#                                               ModelObject:List of Rows
+#                                         (Each Row must be a list of parameters that can create and instance of the ModelObject)
+#   
+FOUNDATIONS = {Group:group_foundations}
 
 ######################################################
 # Special, migrations-tracking table
index 5b55ead7c81073250544ae0c01852731a4122523..d54bf05050b9e9c860963bd6f6c286640275de28 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, MediaComment
+from mediagoblin.db.models import MediaEntry, User, MediaComment, Group
 from mediagoblin.tools.response import redirect, render_404
 
 
@@ -63,6 +63,26 @@ def active_user_from_url(controller):
 
     return wrapper
 
+def user_in_group(group_name):
+#TODO handle possible errors correctly
+    def user_in_group_decorator(controller):
+        @wraps(controller)
+
+        def wrapper(request, *args, **kwargs):
+            user_id = request.user.id
+            group = Group.query.filter(
+                Group.group_name==group_name).first()
+            if not (group.query.filter(
+                Group.all_users.any(
+                    User.id==user_id)).count()):
+
+                raise Forbidden()
+
+            return controller(request, *args, **kwargs)
+
+        return wrapper
+    return user_in_group_decorator
+
 
 def user_may_delete_media(controller):
     """
@@ -253,3 +273,26 @@ def get_workbench(func):
             return func(*args, workbench=workbench, **kwargs)
 
     return new_func
+
+def require_admin_login(controller):
+    """
+    Require an login from an administrator.
+    """
+    @wraps(controller)
+    def new_controller_func(request, *args, **kwargs):
+        if request.user and \
+                not request.user.is_admin:
+            raise Forbidden()
+        elif not request.user:
+            next_url = urljoin(
+                    request.urlgen('mediagoblin.auth.login',
+                        qualified=True),
+                    request.url)
+
+            return redirect(request, 'mediagoblin.auth.login',
+                            next=next_url)
+
+        return controller(request, *args, **kwargs)
+
+    return new_controller_func
+
index effe49e198e88a02db717bcdae2a92762d128b1e..284dd7b8c65a97c7909205f8ea58bb585132e686 100644 (file)
@@ -59,9 +59,3 @@ 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 9c8ddee6efc4a6e1f7ea8a918922891b8f2ee036..557c4853c4004c0c6b8ba7e95f5fb722ff05c8ee 100644 (file)
@@ -79,12 +79,15 @@ def add_media_to_collection(collection, media, note=None, commit=True):
 
 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
+    This function is used to convert a form dictionary (from a User filing a 
+        report) into either a MediaReport or CommentReport object.
 
-    :returns either of MediaReport or a CommentReport object that has not been saved.
-            In case of an improper form_dict, returns None
+    :param form_dict should be an ImmutableMultiDict object as 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)
@@ -92,6 +95,7 @@ def build_report_form(form_dict):
         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
@@ -100,6 +104,7 @@ def build_report_form(form_dict):
         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 94cccc66aae16bb75bb706581cc03be7ddbe275b..a0eb67db751f449f4c0e8a51470e94deb1016f89 100644 (file)
@@ -20,7 +20,7 @@ import datetime
 from mediagoblin import messages, mg_globals
 from mediagoblin.db.models import (MediaEntry, MediaTag, Collection,
                                    CollectionItem, User, MediaComment,
-                                   CommentReport, MediaReport)
+                                   CommentReport, MediaReport, Group)
 from mediagoblin.tools.response import render_to_response, render_404, \
     redirect, redirect_obj
 from mediagoblin.tools.translate import pass_to_ugettext as _
@@ -30,7 +30,7 @@ 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,
+    get_media_entry_by_id, user_in_group,
     require_active_login, user_may_delete_media, user_may_alter_collection,
     get_user_collection, get_user_collection_item, active_user_from_url,
     get_media_comment_by_id)
@@ -621,22 +621,26 @@ def processing_panel(request):
 
 @require_active_login
 @get_user_media_entry
-def file_a_report(request, media, comment=None):
+@user_in_group(u'reporter')
+def file_a_report(request, media, comment=None, required_group=1):
     if request.method == "POST":
         report_form = build_report_form(request.form)
         report_form.save()
+
         return redirect(
-              request,
-             'index')
+            request,
+            'index')
+
     if comment is not None:
         context = {'media': media,
-                 'comment':comment}
+                   'comment':comment}
     else:
         context = {'media': media}
+
     return render_to_response(
-            request,
-                'mediagoblin/user_pages/report.html',
-                context)
+        request,
+        'mediagoblin/user_pages/report.html',
+        context)
 
 @require_active_login
 @get_user_media_entry