Merge branch 'ticket-679' into OPW-Moderation-Update
authortilly-Q <nattilypigeonfowl@gmail.com>
Mon, 29 Jul 2013 22:40:19 +0000 (18:40 -0400)
committertilly-Q <nattilypigeonfowl@gmail.com>
Mon, 29 Jul 2013 22:40:19 +0000 (18:40 -0400)
Conflicts:
mediagoblin/auth/tools.py
mediagoblin/auth/views.py
mediagoblin/db/migration_tools.py
mediagoblin/db/migrations.py
mediagoblin/db/models.py
mediagoblin/decorators.py
mediagoblin/user_pages/views.py

15 files changed:
1  2 
mediagoblin/auth/tools.py
mediagoblin/auth/views.py
mediagoblin/db/migrations.py
mediagoblin/db/mixin.py
mediagoblin/db/models.py
mediagoblin/db/util.py
mediagoblin/decorators.py
mediagoblin/gmg_commands/users.py
mediagoblin/routing.py
mediagoblin/static/css/base.css
mediagoblin/submit/views.py
mediagoblin/templates/mediagoblin/base.html
mediagoblin/templates/mediagoblin/user_pages/media.html
mediagoblin/tools/response.py
mediagoblin/user_pages/views.py

index 39b349de2af428a3919beee489d57eecad44195c,579775ffa761a961fbc3c613d54d3d9a881c16f7..596a44478c453629142a4fb2372c88e2017a3d45
  # 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 uuid
- import logging
 +
+ import logging
  import wtforms
 +from sqlalchemy import or_
  
  from mediagoblin import mg_globals
- from mediagoblin.auth import lib as auth_lib
- from mediagoblin.db.models import User, Privilege
+ from mediagoblin.tools.crypto import get_timed_signer_url
+ from mediagoblin.db.models import User
  from mediagoblin.tools.mail import (normalize_email, send_email,
                                      email_debug_message)
  from mediagoblin.tools.template import render_template
@@@ -122,22 -159,8 +161,16 @@@ def register_user(request, register_for
  
      if extra_validation_passes:
          # Create the user
-         user = User()
-         user.username = register_form.data['username']
-         user.email = register_form.data['email']
-         user.pw_hash = auth_lib.bcrypt_gen_password_hash(
-             register_form.password.data)
-         user.verification_key = unicode(uuid.uuid4())
-         user.save()
+         user = auth.create_user(register_form)
  
 +        # give the user the 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 1c3465565184ad8395f1902fcdb107f2956a20ec,d54762b0cb2f3ff07e6a702e7b72652f00fb1391..90d6f5cc2fbc36d6db40ac23228f3f44d934412b
  # 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 uuid
- import datetime
+ from itsdangerous import BadSignature
  
  from mediagoblin import messages, mg_globals
 -from mediagoblin.db.models import User
 +from mediagoblin.db.models import User, Privilege
+ from mediagoblin.tools.crypto import get_timed_signer_url
+ from mediagoblin.decorators import auth_enabled, allow_registration
  from mediagoblin.tools.response import render_to_response, redirect, render_404
  from mediagoblin.tools.translate import pass_to_ugettext as _
  from mediagoblin.tools.mail import email_debug_message
@@@ -115,19 -125,28 +125,33 @@@ def verify_email(request)
      you are lucky :)
      """
      # If we don't have userid and token parameters, we can't do anything; 404
-     if not 'userid' in request.GET or not 'token' in request.GET:
+     if not 'token' in request.GET:
          return render_404(request)
  
-     user = User.query.filter_by(id=request.args['userid']).first()
+     # Catch error if token is faked or expired
+     try:
+         token = get_timed_signer_url("mail_verification_token") \
+                 .loads(request.GET['token'], max_age=10*24*3600)
+     except BadSignature:
+         messages.add_message(
+             request,
+             messages.ERROR,
+             _('The verification key or user id is incorrect.'))
+         return redirect(
+             request,
+             'index')
+     user = User.query.filter_by(id=int(token)).first()
  
-     if user and user.verification_key == unicode(request.GET['token']):
+     if user and user.email_verified is False:
          user.status = u'active'
          user.email_verified = True
 +        user.verification_key = None
 +        user.all_privileges.append(
 +            Privilege.query.filter(
 +            Privilege.privilege_name==u'active').first())
 +
          user.save()
  
          messages.add_message(
index 247298ace2db022aed3fcd37ad2e8cfb252906e2,fe4ffb3eda34dd319c8be3fc1df064e67b4e37c0..53df795458da2e6e4229d3b9233825520e87043f
@@@ -289,80 -288,94 +289,166 @@@ def unique_collections_slug(db)
  
      db.commit()
  
- class ReportBase_v0(declarative_base()):
+ @RegisterMigration(11, MIGRATIONS)
+ def drop_token_related_User_columns(db):
      """
+     Drop unneeded columns from the User table after switching to using
+     itsdangerous tokens for email and forgot password verification.
      """
+     metadata = MetaData(bind=db.bind)
+     user_table = inspect_table(metadata, 'core__users')
++
+     verification_key = user_table.columns['verification_key']
+     fp_verification_key = user_table.columns['fp_verification_key']
+     fp_token_expire = user_table.columns['fp_token_expire']
+     verification_key.drop()
+     fp_verification_key.drop()
+     fp_token_expire.drop()
+     db.commit()
+ class CommentSubscription_v0(declarative_base()):
+     __tablename__ = 'core__comment_subscriptions'
+     id = Column(Integer, primary_key=True)
+     created = Column(DateTime, nullable=False, default=datetime.datetime.now)
+     media_entry_id = Column(Integer, ForeignKey(MediaEntry.id), nullable=False)
+     user_id = Column(Integer, ForeignKey(User.id), nullable=False)
++
+     notify = Column(Boolean, nullable=False, default=True)
+     send_email = Column(Boolean, nullable=False, default=True)
+ class Notification_v0(declarative_base()):
+     __tablename__ = 'core__notifications'
+     id = Column(Integer, primary_key=True)
+     type = Column(Unicode)
+     created = Column(DateTime, nullable=False, default=datetime.datetime.now)
+     user_id = Column(Integer, ForeignKey(User.id), nullable=False,
+                      index=True)
+     seen = Column(Boolean, default=lambda: False, index=True)
+ class CommentNotification_v0(Notification_v0):
+     __tablename__ = 'core__comment_notifications'
+     id = Column(Integer, ForeignKey(Notification_v0.id), primary_key=True)
+     subject_id = Column(Integer, ForeignKey(MediaComment.id))
+ class ProcessingNotification_v0(Notification_v0):
+     __tablename__ = 'core__processing_notifications'
+     id = Column(Integer, ForeignKey(Notification_v0.id), primary_key=True)
+     subject_id = Column(Integer, ForeignKey(MediaEntry.id))
+ @RegisterMigration(12, MIGRATIONS)
+ def add_new_notification_tables(db):
+     metadata = MetaData(bind=db.bind)
+     user_table = inspect_table(metadata, 'core__users')
+     mediaentry_table = inspect_table(metadata, 'core__media_entries')
+     mediacomment_table = inspect_table(metadata, 'core__media_comments')
+     CommentSubscription_v0.__table__.create(db.bind)
+     Notification_v0.__table__.create(db.bind)
+     CommentNotification_v0.__table__.create(db.bind)
+     ProcessingNotification_v0.__table__.create(db.bind)
+ @RegisterMigration(13, MIGRATIONS)
+ def pw_hash_nullable(db):
+     """Make pw_hash column nullable"""
+     metadata = MetaData(bind=db.bind)
+     user_table = inspect_table(metadata, "core__users")
+     user_table.c.pw_hash.alter(nullable=True)
+     # sqlite+sqlalchemy seems to drop this constraint during the
+     # migration, so we add it back here for now a bit manually.
+     if db.bind.url.drivername == 'sqlite':
+         constraint = UniqueConstraint('username', table=user_table)
+         constraint.create()
++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)
 +    reported_user_id = Column(Integer, ForeignKey(User.id), nullable=False)
 +    created = Column(DateTime, nullable=False, default=datetime.datetime.now) 
 +    discriminator = Column('type', Unicode(50))
 +    __mapper_args__ = {'polymorphic_on': discriminator}
 +
-     media_entry_id = Column(Integer, ForeignKey(MediaEntry.id), nullable=False)
 +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.i
 +
 +class ArchivedReport_v0(ReportBase_v0):
 +    __tablename__ = 'core__reports_archived'
 +    __mapper_args__ = {'polymorphic_identity': 'archived_report'}
 +
 +    id = Column('id',Integer, ForeignKey('core__reports.id'))
 +    media_entry_id = Column(Integer, ForeignKey(MediaEntry.id))
 +    comment_id = Column(Integer, ForeignKey(MediaComment.id))
 +    resolver_id = Column(Integer, ForeignKey(User.id), nullable=False)
 +    resolved_time = Column(DateTime)
 +    result = Column(UnicodeText)
 +
 +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 Privilege_v0(declarative_base()):
 +    __tablename__ = 'core__privileges'
 +    id = Column(Integer, nullable=False, primary_key=True, unique=True)
 +    privilege_name = Column(Unicode, nullable=False)
 +
 +class PrivilegeUserAssociation_v0(declarative_base()):
 +    __tablename__ = 'core__privileges_users'
- @RegisterMigration(11, MIGRATIONS)
 +    group_id = Column(
 +        'core__privilege_id', 
 +        Integer, 
 +        ForeignKey(User.id), 
 +        primary_key=True)
 +    user_id = Column(
 +        'core__user_id', 
 +        Integer, 
 +        ForeignKey(Privilege.id), 
 +        primary_key=True)
 +
++@RegisterMigration(14, MIGRATIONS)
 +def create_moderation_tables(db):
 +    ReportBase_v0.__table__.create(db.bind)
 +    CommentReport_v0.__table__.create(db.bind)
 +    MediaReport_v0.__table__.create(db.bind)
 +    ArchivedReport_v0.__table__.create(db.bind)
 +    UserBan_v0.__table__.create(db.bind)
 +    Privilege_v0.__table__.create(db.bind)
 +    PrivilegeUserAssociation_v0.__table__.create(db.bind)
      db.commit()
Simple merge
index c85d546f778005f0889031c18cb9a5e6afd4a5c4,7448f5ce166c5e7b21b2141c79a98392e50b233e..32d3135f3633917526180ca94592dd694f3e67ac
@@@ -29,11 -29,12 +29,11 @@@ from sqlalchemy.orm.collections import 
  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
- from mediagoblin.db.mixin import UserMixin, MediaEntryMixin, MediaCommentMixin, CollectionMixin, CollectionItemMixin
+ from mediagoblin.db.mixin import UserMixin, MediaEntryMixin, \
+         MediaCommentMixin, CollectionMixin, CollectionItemMixin
  from mediagoblin.tools.files import delete_media_files
  from mediagoblin.tools.common import import_component
  
@@@ -484,209 -486,120 +488,313 @@@ class ProcessingMetaData(Base)
          """A dict like view on this object"""
          return DictReadAttrProxy(self)
  
 -
+ class CommentSubscription(Base):
+     __tablename__ = 'core__comment_subscriptions'
+     id = Column(Integer, primary_key=True)
+     created = Column(DateTime, nullable=False, default=datetime.datetime.now)
+     media_entry_id = Column(Integer, ForeignKey(MediaEntry.id), nullable=False)
+     media_entry = relationship(MediaEntry,
+                         backref=backref('comment_subscriptions',
+                                         cascade='all, delete-orphan'))
+     user_id = Column(Integer, ForeignKey(User.id), nullable=False)
+     user = relationship(User,
+                         backref=backref('comment_subscriptions',
+                                         cascade='all, delete-orphan'))
+     notify = Column(Boolean, nullable=False, default=True)
+     send_email = Column(Boolean, nullable=False, default=True)
+     def __repr__(self):
+         return ('<{classname} #{id}: {user} {media} notify: '
+                 '{notify} email: {email}>').format(
+             id=self.id,
+             classname=self.__class__.__name__,
+             user=self.user,
+             media=self.media_entry,
+             notify=self.notify,
+             email=self.send_email)
+ class Notification(Base):
+     __tablename__ = 'core__notifications'
+     id = Column(Integer, primary_key=True)
+     type = Column(Unicode)
+     created = Column(DateTime, nullable=False, default=datetime.datetime.now)
+     user_id = Column(Integer, ForeignKey('core__users.id'), nullable=False,
+                      index=True)
+     seen = Column(Boolean, default=lambda: False, index=True)
+     user = relationship(
+         User,
+         backref=backref('notifications', cascade='all, delete-orphan'))
+     __mapper_args__ = {
+         'polymorphic_identity': 'notification',
+         'polymorphic_on': type
+     }
+     def __repr__(self):
+         return '<{klass} #{id}: {user}: {subject} ({seen})>'.format(
+             id=self.id,
+             klass=self.__class__.__name__,
+             user=self.user,
+             subject=getattr(self, 'subject', None),
+             seen='unseen' if not self.seen else 'seen')
+ class CommentNotification(Notification):
+     __tablename__ = 'core__comment_notifications'
+     id = Column(Integer, ForeignKey(Notification.id), primary_key=True)
+     subject_id = Column(Integer, ForeignKey(MediaComment.id))
+     subject = relationship(
+         MediaComment,
+         backref=backref('comment_notifications', cascade='all, delete-orphan'))
+     __mapper_args__ = {
+         'polymorphic_identity': 'comment_notification'
+     }
+ class ProcessingNotification(Notification):
+     __tablename__ = 'core__processing_notifications'
+     id = Column(Integer, ForeignKey(Notification.id), primary_key=True)
+     subject_id = Column(Integer, ForeignKey(MediaEntry.id))
+     subject = relationship(
+         MediaEntry,
+         backref=backref('processing_notifications',
+                         cascade='all, delete-orphan'))
+     __mapper_args__ = {
+         'polymorphic_identity': 'processing_notification'
+     }
  
 +class ReportBase(Base):
 +    """
 +    This is the basic report table which the other reports are based off of.
 +        :keyword reporter_id
 +        :keyword report_content
 +        :keyword reported_user_id
 +        :keyword created
 +        :keyword resolved
 +        :keyword result
 +        :keyword discriminator
 +
 +    """
 +    __tablename__ = 'core__reports'
 +    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"),
 +        primaryjoin="User.id==ReportBase.reporter_id")
 +    report_content = Column(UnicodeText)
 +    reported_user_id = Column(Integer, ForeignKey(User.id), nullable=False)
 +    reported_user = relationship(
 +        User, 
 +        backref=backref("reports_filed_on",
 +            lazy="dynamic",
 +            cascade="all, delete-orphan"),
 +        primaryjoin="User.id==ReportBase.reported_user_id")
 +    created = Column(DateTime, nullable=False, default=datetime.datetime.now())
 +    discriminator = Column('type', Unicode(50))
 +    __mapper_args__ = {'polymorphic_on': discriminator}
 +
 +    def is_comment_report(self):
 +        return self.discriminator=='comment_report'
 +
 +    def is_media_entry_report(self):
 +        return self.discriminator=='media_report'
 +
 +    def is_archived_report(self):
 +        return self.discriminator=='archived_report'
 +
 +
 +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_onmod/reports/1/",
 +            lazy="dynamic",
 +            cascade="all, delete-orphan"))
 +
 +class ArchivedReport(ReportBase):
 +    """
 +    A table to keep track of reports that have been resolved
 +    """
 +    __tablename__ = 'core__reports_archived'
 +    __mapper_args__ = {'polymorphic_identity': 'archived_report'}
 +    id = Column('id',Integer, ForeignKey('core__reports.id'),
 +                primary_key=True)
 +
 +    media_entry_id = Column(Integer, ForeignKey(MediaEntry.id))
 +    media_entry = relationship(
 +        MediaEntry, 
 +        backref=backref("past_reports_filed_on",
 +            lazy="dynamic"))
 +    comment_id = Column(Integer, ForeignKey(MediaComment.id))
 +    comment = relationship(
 +        MediaComment, backref=backref("past_reports_filed_on",
 +            lazy="dynamic"))
 +
 +    resolver_id = Column(Integer, ForeignKey(User.id), nullable=False)
 +    resolver = relationship(
 +        User, 
 +        backref=backref("reports_resolved_by",
 +            lazy="dynamic",
 +            cascade="all, delete-orphan"),
 +        primaryjoin="User.id==ArchivedReport.resolver_id")
 +
 +    resolved = Column(DateTime)
 +    result = Column(UnicodeText)
 +
 +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
 +
 +        :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 
 +                                    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, 
 +                                                        primary_key=True)
 +    expiration_date = Column(DateTime)
 +    reason = Column(UnicodeText, nullable=False)
 +
 +
 +class Privilege(Base):
 +    """
 +    The Privilege table holds all of the different privileges a user can hold.
 +    If a user 'has' a privilege, the User object is in a relationship with the
 +    privilege object. 
 +
 +        :keyword privilege_name   Holds a unicode object that is the recognizable
 +                                    name of this privilege. This is the column 
 +                                    used for identifying whether or not a user
 +                                    has a necessary privilege or not.
 +                                
 +    """
 +    __tablename__ = 'core__privileges'
 +
 +    id = Column(Integer, nullable=False, primary_key=True)
 +    privilege_name = Column(Unicode, nullable=False, unique=True)
 +    all_users = relationship(
 +        User, 
 +        backref='all_privileges', 
 +        secondary="core__privileges_users")
 +
 +    def __init__(self, privilege_name):
 +        '''
 +        Currently consructors are required for tables that are initialized thru
 +        the FOUNDATIONS system. This is because they need to be able to be con-
 +        -structed by a list object holding their arg*s
 +        '''
 +        self.privilege_name = privilege_name
 +
 +    def __repr__(self):
 +        return "<Privilege %s>" % (self.privilege_name)
 +
 +    def is_admin_or_moderator(self):
 +        '''
 +        This method is necessary to check if a user is able to take moderation
 +        actions.
 +        '''
 +        
 +        return (self.privilege_name==u'admin' or 
 +                self.privilege_name==u'moderator')
 +
 +class PrivilegeUserAssociation(Base):
 +    '''
 +    This table holds the many-to-many relationship between User and Privilege
 +    '''
 +    
 +    __tablename__ = 'core__privileges_users'
 +
 +    privilege_id = Column(
 +        'core__privilege_id', 
 +        Integer, 
 +        ForeignKey(User.id), 
 +        primary_key=True)
 +    user_id = Column(
 +        'core__user_id', 
 +        Integer, 
 +        ForeignKey(Privilege.id), 
 +        primary_key=True)
  
+ with_polymorphic(
+     Notification,
+     [ProcessingNotification, CommentNotification])
  
- privilege_foundations = [[u'admin'], [u'moderator'], [u'uploader'],[u'reporter'], [u'commenter'] ,[u'active']]
 +
  MODELS = [
 -    User, MediaEntry, Tag, MediaTag, MediaComment, Collection, CollectionItem,
 -    MediaFile, FileKeynames, MediaAttachmentFile, ProcessingMetaData,
 -    Notification, CommentNotification, ProcessingNotification,
 -    CommentSubscription]
 +    User, MediaEntry, Tag, MediaTag, MediaComment, Collection, CollectionItem, 
 +    MediaFile, FileKeynames, MediaAttachmentFile, ProcessingMetaData, ReportBase,
 +    CommentReport, MediaReport, UserBan, Privilege, PrivilegeUserAssociation,
-     ArchivedReport]
++    ArchivedReport, Notification, CommentNotification, 
++      ProcessingNotification, CommentSubscription]
  
- # 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 are the default rows that are created immediately after the tables 
+  are initialized. Each entry to  this dictionary should be in the format of:
+                  ModelConstructorObject:List of Dictionaries
+  (Each Dictionary represents a row on the Table to be created, containing each
+   of the columns' names as a key string, and each of the columns' values as a
+   value)
+  ex. [NOTE THIS IS NOT BASED OFF OF OUR USER TABLE]
+     user_foundations = [{'name':u'Joanna', 'age':24},
+                         {'name':u'Andrea', 'age':41}]
+     FOUNDATIONS = {User:user_foundations}
+ """
 -FOUNDATIONS = {}
++privilege_foundations = [{'privilege_name':u'admin'}, 
++                                              {'privilege_name':u'moderator'}, 
++                                              {'privilege_name':u'uploader'},
++                                              {'privilege_name':u'reporter'}, 
++                                              {'privilege_name':u'commenter'},
++                                              {'privilege_name':u'active'}]
 +FOUNDATIONS = {Privilege:privilege_foundations}
  
  ######################################################
  # Special, migrations-tracking table
Simple merge
index b39b36f57ac0684e93de7564a7d805e0bc254412,ca7be53cf3083194ac8ff9e05ee48dbfd62403f2..c9a1a78c0eddc53dcb75b3eaf51e84d7452501bd
@@@ -18,12 -18,12 +18,14 @@@ from functools import wrap
  
  from urlparse import urljoin
  from werkzeug.exceptions import Forbidden, NotFound
 +from werkzeug.urls import url_quote
  
  from mediagoblin import mg_globals as mgg
- from mediagoblin.db.models import MediaEntry, User, MediaComment, Privilege, \
-                             UserBan
- from mediagoblin.tools.response import redirect, render_404, render_user_banned
+ from mediagoblin import messages
 -from mediagoblin.db.models import MediaEntry, User
++from mediagoblin.db.models import MediaEntry, User, MediaComment,
++                                                      UserBan
+ from mediagoblin.tools.response import redirect, render_404
+ from mediagoblin.tools.translate import pass_to_ugettext as _
  
  
  def require_active_login(controller):
@@@ -247,55 -227,43 +249,81 @@@ def get_media_entry_by_id(controller)
      return wrapper
  
  
 -def get_workbench(func):
 -    """Decorator, passing in a workbench as kwarg which is cleaned up afterwards"""
 -
 -    @wraps(func)
 -    def new_func(*args, **kwargs):
 -        with mgg.workbench_manager.create() as workbench:
 -            return func(*args, workbench=workbench, **kwargs)
 -
 -    return new_func
 -
 -
+ def allow_registration(controller):
+     """ Decorator for if registration is enabled"""
+     @wraps(controller)
+     def wrapper(request, *args, **kwargs):
+         if not mgg.app_config["allow_registration"]:
+             messages.add_message(
+                 request,
+                 messages.WARNING,
+                 _('Sorry, registration is disabled on this instance.'))
+             return redirect(request, "index")
+         return controller(request, *args, **kwargs)
+     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 auth_enabled(controller):
+     """Decorator for if an auth plugin is enabled"""
+     @wraps(controller)
+     def wrapper(request, *args, **kwargs):
+         if not mgg.app.auth:
+             messages.add_message(
+                 request,
+                 messages.WARNING,
+                 _('Sorry, authentication is disabled on this instance.'))
+             return redirect(request, 'index')
++        return controller(request, *args, **kwargs)
++
++    return wrapper
 +
 +def get_workbench(func):
 +    """Decorator, passing in a workbench as kwarg which is cleaned up afterwards"""
 +    @wraps(func)
 +    def new_func(*args, **kwargs):
 +        with mgg.workbench_manager.create() as workbench:
 +            return func(*args, workbench=workbench, **kwargs)
 +    return new_func
 +
 +def require_admin_or_moderator_login(controller):
 +    """
 +    Require an login from an administrator or a moderator.
 +    """
 +    @wraps(controller)
 +    def new_controller_func(request, *args, **kwargs):
 +        admin_privilege = Privilege.one({'privilege_name':u'admin'})
 +        moderator_privilege = Privilege.one({'privilege_name':u'moderator'})
 +        if request.user and \
 +            not admin_privilege in request.user.all_privileges and \
 +                 not moderator_privilege in request.user.all_privileges:
 +
 +            raise Forbidden()
 +        elif not request.user:
 +            next_url = urljoin(
 +                    request.urlgen('mediagoblin.auth.login',
 +                        qualified=True),
 +                    request.url)
 +
 +            return redirect(request, 'mediagoblin.auth.login',
 +                            next=next_url)
  
          return controller(request, *args, **kwargs)
  
index 5c19d3a9aaaf0555519ef74af0c8a778932c0344,e44b0aa9b481bbee3f9d653c48602de6f7605843..ad8263e762fb26aac87c9e4530a24c9ab4fde6a0
@@@ -52,16 -52,9 +52,16 @@@ def adduser(args)
          entry = db.User()
          entry.username = unicode(args.username.lower())
          entry.email = unicode(args.email)
-         entry.pw_hash = auth_lib.bcrypt_gen_password_hash(args.password)
+         entry.pw_hash = auth.gen_password_hash(args.password)
          entry.status = u'active'
          entry.email_verified = True
 +        default_privileges = [ 
 +            db.Privilege.one({'privilege_name':u'commenter'}),
 +            db.Privilege.one({'privilege_name':u'uploader'}),
 +            db.Privilege.one({'privilege_name':u'reporter'}),
 +            db.Privilege.one({'privilege_name':u'active'})
 +]
 +        entry.all_privileges = default_privileges
          entry.save()
  
          print "User created (and email marked as verified)"
@@@ -78,13 -71,10 +78,14 @@@ def makeadmin(args)
  
      db = mg_globals.database
  
-     user = db.User.one({'username': unicode(args.username.lower())})
+     user = db.User.query.filter_by(
+         username=unicode(args.username.lower())).one()
      if user:
          user.is_admin = True
 +        user.all_privileges.append(
 +            db.Privilege.one({
 +                'privilege_name':u'admin'})
 +            )
          user.save()
          print 'The user is now Admin'
      else:
Simple merge
Simple merge
index 11707a03bfe65bf488e535a64d031652c87a5bd6,3f9d5b2df2ac0adf654934d52cb4e8075586ffa7..7c0708ed96c0884d684a4fc9e045dcd5e709edde
@@@ -34,9 -35,10 +35,11 @@@ from mediagoblin.media_types import sni
  from mediagoblin.submit.lib import check_file_field, prepare_queue_task, \
      run_process_media, new_upload_entry
  
+ from mediagoblin.notifications import add_comment_subscription
  
  @require_active_login
 +@user_has_privilege(u'uploader')
  def submit_start(request):
      """
      First view for submitting a file.
index b52b65e7e0415966421a70e99bb605508f0a60c4,1fc4467cf0e9aa3aeaecd7462c40932eb18201bc..575ddf42ec9246397b2dd0a7875123dbbf3521df
                {% if request.user.is_admin %}
                  <p>
                    <span class="dropdown_title">Admin powers:</span>
 -                  <a href="{{ request.urlgen('mediagoblin.admin.panel') }}">
 +                  <a href="{{ request.urlgen('mediagoblin.moderation.media_panel') }}">
                      {%- trans %}Media processing panel{% endtrans -%}
                    </a>
 +                  <a href="{{ request.urlgen('mediagoblin.moderation.users') }}">
 +                    {%- trans %}User management panel{% endtrans -%}
 +                  </a>
                  </p>
                {% endif %}
+               {% include 'mediagoblin/fragments/header_notifications.html' %}
              </div>
            {% endif %}
          </header>
Simple merge
index 1a78bcc7ebea56472031eccdd9007eb42bfd9ae2,596d4c20b6111c1496f49ec3b576edffe7e889d0..06ea0ab0dbcc1ba28b9c7cda5cc9da0dd036eb9a
@@@ -26,16 -25,17 +26,19 @@@ from mediagoblin.tools.response import 
  from mediagoblin.tools.translate import pass_to_ugettext as _
  from mediagoblin.tools.pagination import Pagination
  from mediagoblin.user_pages import forms as user_forms
- from mediagoblin.user_pages.lib import (send_comment_email, build_report_object,
-     add_media_to_collection)
 -from mediagoblin.user_pages.lib import add_media_to_collection
++from mediagoblin.user_pages.lib import (send_comment_email,
++      add_media_to_collection)
+ from mediagoblin.notifications import trigger_notification, \
+     add_comment_subscription, mark_comment_notification_seen
  
  from mediagoblin.decorators import (uses_pagination, get_user_media_entry,
 -    get_media_entry_by_id,
 +    get_media_entry_by_id, user_has_privilege,
      require_active_login, user_may_delete_media, user_may_alter_collection,
 -    get_user_collection, get_user_collection_item, active_user_from_url)
 +    get_user_collection, get_user_collection_item, active_user_from_url,
 +    get_media_comment_by_id, user_not_banned)
  
  from werkzeug.contrib.atom import AtomFeed
+ from werkzeug.exceptions import MethodNotAllowed
  
  
  _log = logging.getLogger(__name__)
@@@ -112,9 -112,10 +115,10 @@@ def user_gallery(request, page, url_use
           'media_entries': media_entries,
           'pagination': pagination})
  
  MEDIA_COMMENTS_PER_PAGE = 50
  
 -
 +@user_not_banned
  @get_user_media_entry
  @uses_pagination
  def media_home(request, media, page, **kwargs):