From: tilly-Q Date: Mon, 29 Jul 2013 22:40:19 +0000 (-0400) Subject: Merge branch 'ticket-679' into OPW-Moderation-Update X-Git-Url: https://vcs.fsf.org/?a=commitdiff_plain;h=52a355b27541597fc155dab5e4885207b12a0a7b;p=mediagoblin.git Merge branch 'ticket-679' into OPW-Moderation-Update 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 --- 52a355b27541597fc155dab5e4885207b12a0a7b diff --cc mediagoblin/auth/tools.py index 39b349de,579775ff..596a4447 --- a/mediagoblin/auth/tools.py +++ b/mediagoblin/auth/tools.py @@@ -14,15 -14,12 +14,14 @@@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . - 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() diff --cc mediagoblin/auth/views.py index 1c346556,d54762b0..90d6f5cc --- a/mediagoblin/auth/views.py +++ b/mediagoblin/auth/views.py @@@ -14,11 -14,12 +14,12 @@@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . - 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( diff --cc mediagoblin/db/migrations.py index 247298ac,fe4ffb3e..53df7954 --- a/mediagoblin/db/migrations.py +++ b/mediagoblin/db/migrations.py @@@ -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} + - +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) ++ 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' - + 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(11, MIGRATIONS) ++@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() diff --cc mediagoblin/db/models.py index c85d546f,7448f5ce..32d3135f --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@@ -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 "" % (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 diff --cc mediagoblin/decorators.py index b39b36f5,ca7be53c..c9a1a78c --- a/mediagoblin/decorators.py +++ b/mediagoblin/decorators.py @@@ -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) diff --cc mediagoblin/gmg_commands/users.py index 5c19d3a9,e44b0aa9..ad8263e7 --- a/mediagoblin/gmg_commands/users.py +++ b/mediagoblin/gmg_commands/users.py @@@ -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: diff --cc mediagoblin/submit/views.py index 11707a03,3f9d5b2d..7c0708ed --- a/mediagoblin/submit/views.py +++ b/mediagoblin/submit/views.py @@@ -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. diff --cc mediagoblin/templates/mediagoblin/base.html index b52b65e7,1fc4467c..575ddf42 --- a/mediagoblin/templates/mediagoblin/base.html +++ b/mediagoblin/templates/mediagoblin/base.html @@@ -104,14 -112,12 +112,15 @@@ {% if request.user.is_admin %}

Admin powers: - + {%- trans %}Media processing panel{% endtrans -%} + + {%- trans %}User management panel{% endtrans -%} +

{% endif %} + {% include 'mediagoblin/fragments/header_notifications.html' %} {% endif %} diff --cc mediagoblin/user_pages/views.py index 1a78bcc7,596d4c20..06ea0ab0 --- a/mediagoblin/user_pages/views.py +++ b/mediagoblin/user_pages/views.py @@@ -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):