Merge remote-tracking branch 'refs/remotes/tilly-q/OPW-Moderation-Update'

Conflicts:
	mediagoblin/templates/mediagoblin/user_pages/user.html
	mediagoblin/tests/test_auth.py
	mediagoblin/tests/test_submission.py
This commit is contained in:
Christopher Allan Webber 2013-10-07 15:48:33 -05:00
commit 56c4ad89eb
59 changed files with 3410 additions and 207 deletions

View File

@ -20,6 +20,9 @@ email_debug_mode = true
# Set to false to disable registrations
allow_registration = true
# Set to false to disable the ability for users to report offensive content
allow_reporting = true
## Uncomment this to put some user-overriding templates here
# local_templates = %(here)s/user_dev/templates/

View File

@ -1,48 +0,0 @@
# 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/>.
from werkzeug.exceptions import Forbidden
from mediagoblin.db.models import MediaEntry
from mediagoblin.decorators import require_active_login
from mediagoblin.tools.response import render_to_response
@require_active_login
def admin_processing_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()
processing_entries = MediaEntry.query.filter_by(state = u'processing').\
order_by(MediaEntry.created.desc())
# Get media entries which have failed to process
failed_entries = MediaEntry.query.filter_by(state = u'failed').\
order_by(MediaEntry.created.desc())
processed_entries = MediaEntry.query.filter_by(state = u'processed').\
order_by(MediaEntry.created.desc()).limit(10)
# Render to response
return render_to_response(
request,
'mediagoblin/admin/panel.html',
{'processing_entries': processing_entries,
'failed_entries': failed_entries,
'processed_entries': processed_entries})

View File

@ -14,12 +14,14 @@
# 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 logging
import wtforms
from sqlalchemy import or_
from mediagoblin import mg_globals
from mediagoblin.tools.crypto import get_timed_signer_url
from mediagoblin.db.models import User
from mediagoblin.db.models import User, Privilege
from mediagoblin.tools.mail import (normalize_email, send_email,
email_debug_message)
from mediagoblin.tools.template import render_template
@ -129,6 +131,14 @@ def register_user(request, register_form):
# Create the user
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()

View File

@ -17,7 +17,7 @@
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
@ -147,9 +147,12 @@ def verify_email(request):
user = User.query.filter_by(id=int(token)).first()
if user and user.email_verified is False:
user.status = u'active'
user.email_verified = True
if user and user.has_privilege(u'active') is False:
user.verification_key = None
user.all_privileges.append(
Privilege.query.filter(
Privilege.privilege_name==u'active').first())
user.save()
messages.add_message(
@ -183,7 +186,7 @@ def resend_activation(request):
return redirect(request, 'mediagoblin.auth.login')
if request.user.email_verified:
if request.user.has_privilege(u'active'):
messages.add_message(
request,
messages.ERROR,
@ -248,7 +251,7 @@ def forgot_password(request):
success_message=_("An email has been sent with instructions "
"on how to change your password.")
if user and not(user.email_verified and user.status == 'active'):
if user and not(user.has_privilege(u'active')):
# Don't send reminder because user is inactive or has no verified email
messages.add_message(request,
messages.WARNING,
@ -304,8 +307,8 @@ def verify_forgot_password(request):
return redirect(
request, 'index')
# check if user active and has email verified
if user.email_verified and user.status == 'active':
# check if user active
if user.has_privilege(u'active'):
cp_form = auth_forms.ChangePassForm(formdata_vars)
@ -325,13 +328,13 @@ def verify_forgot_password(request):
'mediagoblin/auth/change_fp.html',
{'cp_form': cp_form,})
if not user.email_verified:
if not user.has_privilege(u'active'):
messages.add_message(
request, messages.ERROR,
_('You need to verify your email before you can reset your'
' password.'))
if not user.status == 'active':
if not user.has_privilege(u'active'):
messages.add_message(
request, messages.ERROR,
_('You are no longer an active user. Please contact the system'

View File

@ -42,6 +42,9 @@ allow_comments = boolean(default=True)
# Whether comments are ascending or descending
comments_ascending = boolean(default=True)
# Enable/disable reporting
allow_reporting = boolean(default=True)
# By default not set, but you might want something like:
# "%(here)s/user_dev/templates/"
local_templates = string()

View File

@ -19,7 +19,7 @@ import uuid
from sqlalchemy import (MetaData, Table, Column, Boolean, SmallInteger,
Integer, Unicode, UnicodeText, DateTime,
ForeignKey)
ForeignKey, Date)
from sqlalchemy.exc import ProgrammingError
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.sql import and_
@ -28,7 +28,8 @@ from migrate.changeset.constraint import UniqueConstraint
from mediagoblin.db.extratypes import JSONEncoded, MutationDict
from mediagoblin.db.migration_tools import RegisterMigration, inspect_table
from mediagoblin.db.models import MediaEntry, Collection, User, MediaComment
from mediagoblin.db.models import (MediaEntry, Collection, MediaComment, User,
Privilege)
MIGRATIONS = {}
@ -469,9 +470,204 @@ def wants_notifications(db):
"""Add a wants_notifications field to User model"""
metadata = MetaData(bind=db.bind)
user_table = inspect_table(metadata, "core__users")
col = Column('wants_notifications', Boolean, default=True)
col.create(user_table)
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)
reported_user_id = Column(Integer, ForeignKey(User.id), nullable=False)
created = Column(DateTime, nullable=False, default=datetime.datetime.now)
discriminator = Column('type', Unicode(50))
resolver_id = Column(Integer, ForeignKey(User.id))
resolved = Column(DateTime)
result = Column(UnicodeText)
__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=True)
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=True)
class UserBan_v0(declarative_base()):
__tablename__ = 'core__user_bans'
user_id = Column(Integer, ForeignKey(User.id), nullable=False,
primary_key=True)
expiration_date = Column(Date)
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, unique=True)
class PrivilegeUserAssociation_v0(declarative_base()):
__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)
PRIVILEGE_FOUNDATIONS_v0 = [{'privilege_name':u'admin'},
{'privilege_name':u'moderator'},
{'privilege_name':u'uploader'},
{'privilege_name':u'reporter'},
{'privilege_name':u'commenter'},
{'privilege_name':u'active'}]
class User_vR1(declarative_base()):
__tablename__ = 'rename__users'
id = Column(Integer, primary_key=True)
username = Column(Unicode, nullable=False, unique=True)
email = Column(Unicode, nullable=False)
pw_hash = Column(Unicode)
created = Column(DateTime, nullable=False, default=datetime.datetime.now)
wants_comment_notification = Column(Boolean, default=True)
wants_notifications = Column(Boolean, default=True)
license_preference = Column(Unicode)
url = Column(Unicode)
bio = Column(UnicodeText) # ??
@RegisterMigration(18, MIGRATIONS)
def create_moderation_tables(db):
# First, we will create the new tables in the database.
#--------------------------------------------------------------------------
ReportBase_v0.__table__.create(db.bind)
CommentReport_v0.__table__.create(db.bind)
MediaReport_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()
# Then initialize the tables that we will later use
#--------------------------------------------------------------------------
metadata = MetaData(bind=db.bind)
privileges_table= inspect_table(metadata, "core__privileges")
user_table = inspect_table(metadata, 'core__users')
user_privilege_assoc = inspect_table(
metadata, 'core__privileges_users')
# This section initializes the default Privilege foundations, that
# would be created through the FOUNDATIONS system in a new instance
#--------------------------------------------------------------------------
for parameters in PRIVILEGE_FOUNDATIONS_v0:
db.execute(privileges_table.insert().values(**parameters))
db.commit()
# This next section takes the information from the old is_admin and status
# columns and converts those to the new privilege system
#--------------------------------------------------------------------------
admin_users_ids, active_users_ids, inactive_users_ids = (
db.execute(
user_table.select().where(
user_table.c.is_admin==1)).fetchall(),
db.execute(
user_table.select().where(
user_table.c.is_admin==0).where(
user_table.c.status==u"active")).fetchall(),
db.execute(
user_table.select().where(
user_table.c.is_admin==0).where(
user_table.c.status!=u"active")).fetchall())
# Get the ids for each of the privileges so we can reference them ~~~~~~~~~
(admin_privilege_id, uploader_privilege_id,
reporter_privilege_id, commenter_privilege_id,
active_privilege_id) = [
db.execute(privileges_table.select().where(
privileges_table.c.privilege_name==privilege_name)).first()['id']
for privilege_name in
[u"admin",u"uploader",u"reporter",u"commenter",u"active"]
]
# Give each user the appopriate privileges depending whether they are an
# admin, an active user or an inactivated user ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
for admin_user in admin_users_ids:
admin_user_id = admin_user['id']
for privilege_id in [admin_privilege_id, uploader_privilege_id, reporter_privilege_id, commenter_privilege_id, active_privilege_id]:
db.execute(user_privilege_assoc.insert().values(
core__privilege_id=admin_user_id,
core__user_id=privilege_id))
for active_user in active_users_ids:
active_user_id = active_user['id']
for privilege_id in [uploader_privilege_id, reporter_privilege_id, commenter_privilege_id, active_privilege_id]:
db.execute(user_privilege_assoc.insert().values(
core__privilege_id=active_user_id,
core__user_id=privilege_id))
for inactive_user in inactive_users_ids:
inactive_user_id = inactive_user['id']
for privilege_id in [uploader_privilege_id, reporter_privilege_id, commenter_privilege_id]:
db.execute(user_privilege_assoc.insert().values(
core__privilege_id=inactive_user_id,
core__user_id=privilege_id))
db.commit()
# And then, once the information is taken from the is_admin & status columns
# we drop all of the vestigial columns from the User table.
#--------------------------------------------------------------------------
if db.bind.url.drivername == 'sqlite':
# SQLite has some issues that make it *impossible* to drop boolean
# columns. So, the following code is a very hacky workaround which
# makes it possible. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
User_vR1.__table__.create(db.bind)
db.commit()
new_user_table = inspect_table(metadata, 'rename__users')
for row in db.execute(user_table.select()):
db.execute(new_user_table.insert().values(
username=row.username,
email=row.email,
pw_hash=row.pw_hash,
created=row.created,
wants_comment_notification=row.wants_comment_notification,
wants_notifications=row.wants_notifications,
license_preference=row.license_preference,
url=row.url,
bio=row.bio))
db.commit()
user_table.drop()
db.commit()
new_user_table.rename("core__users")
else:
# If the db is not SQLite, this process is much simpler ~~~~~~~~~~~~~~~
status = user_table.columns['status']
email_verified = user_table.columns['email_verified']
is_admin = user_table.columns['is_admin']
status.drop()
email_verified.drop()
is_admin.drop()
db.commit()

View File

@ -46,7 +46,6 @@ class UserMixin(object):
def bio_html(self):
return cleaned_markdown_conversion(self.bio)
class GenerateSlugMixin(object):
"""
Mixin to add a generate_slug method to objects.

View File

@ -23,7 +23,7 @@ import datetime
from sqlalchemy import Column, Integer, Unicode, UnicodeText, DateTime, \
Boolean, ForeignKey, UniqueConstraint, PrimaryKeyConstraint, \
SmallInteger
SmallInteger, Date
from sqlalchemy.orm import relationship, backref, with_polymorphic
from sqlalchemy.orm.collections import attribute_mapped_collection
from sqlalchemy.sql.expression import desc
@ -64,15 +64,12 @@ class User(Base, UserMixin):
# point.
email = Column(Unicode, nullable=False)
pw_hash = Column(Unicode)
email_verified = Column(Boolean, default=False)
created = Column(DateTime, nullable=False, default=datetime.datetime.now)
status = Column(Unicode, default=u"needs_email_verification", nullable=False)
# Intented to be nullable=False, but migrations would not work for it
# set to nullable=True implicitly.
wants_comment_notification = Column(Boolean, default=True)
wants_notifications = Column(Boolean, default=True)
license_preference = Column(Unicode)
is_admin = Column(Boolean, default=False, nullable=False)
url = Column(Unicode)
bio = Column(UnicodeText) # ??
uploaded = Column(Integer, default=0)
@ -85,8 +82,8 @@ class User(Base, UserMixin):
return '<{0} #{1} {2} {3} "{4}">'.format(
self.__class__.__name__,
self.id,
'verified' if self.email_verified else 'non-verified',
'admin' if self.is_admin else 'user',
'verified' if self.has_privilege(u'active') else 'non-verified',
'admin' if self.has_privilege(u'admin') else 'user',
self.username)
def delete(self, **kwargs):
@ -108,6 +105,36 @@ class User(Base, UserMixin):
super(User, self).delete(**kwargs)
_log.info('Deleted user "{0}" account'.format(self.username))
def has_privilege(self,*priv_names):
"""
This method checks to make sure a user has all the correct privileges
to access a piece of content.
:param priv_names A variable number of unicode objects which rep-
-resent the different privileges which may give
the user access to this content. If you pass
multiple arguments, the user will be granted
access if they have ANY of the privileges
passed.
"""
if len(priv_names) == 1:
priv = Privilege.query.filter(
Privilege.privilege_name==priv_names[0]).one()
return (priv in self.all_privileges)
elif len(priv_names) > 1:
return self.has_privilege(priv_names[0]) or \
self.has_privilege(*priv_names[1:])
return False
def is_banned(self):
"""
Checks if this user is banned.
:returns True if self is banned
:returns False if self is not
"""
return UserBan.query.get(self.id) is not None
class Client(Base):
"""
@ -675,16 +702,198 @@ class ProcessingNotification(Notification):
'polymorphic_identity': 'processing_notification'
}
with_polymorphic(
Notification,
[ProcessingNotification, CommentNotification])
class ReportBase(Base):
"""
This is the basic report object which the other reports are based off of.
:keyword reporter_id Holds the id of the user who created
the report, as an Integer column.
:keyword report_content Hold the explanation left by the repor-
-ter to indicate why they filed the
report in the first place, as a
Unicode column.
:keyword reported_user_id Holds the id of the user who created
the content which was reported, as
an Integer column.
:keyword created Holds a datetime column of when the re-
-port was filed.
:keyword discriminator This column distinguishes between the
different types of reports.
:keyword resolver_id Holds the id of the moderator/admin who
resolved the report.
:keyword resolved Holds the DateTime object which descri-
-bes when this report was resolved
:keyword result Holds the UnicodeText column of the
resolver's reasons for resolving
the report this way. Some of this
is auto-generated
"""
__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))
resolver_id = Column(Integer, ForeignKey(User.id))
resolver = relationship(
User,
backref=backref("reports_resolved_by",
lazy="dynamic",
cascade="all, delete-orphan"),
primaryjoin="User.id==ReportBase.resolver_id")
resolved = Column(DateTime)
result = Column(UnicodeText)
__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.resolved is not None
def archive(self,resolver_id, resolved, result):
self.resolver_id = resolver_id
self.resolved = resolved
self.result = result
class CommentReport(ReportBase):
"""
Reports that have been filed on comments.
:keyword comment_id Holds the integer value of the reported
comment's ID
"""
__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=True)
comment = relationship(
MediaComment, backref=backref("reports_filed_on",
lazy="dynamic"))
class MediaReport(ReportBase):
"""
Reports that have been filed on media entries
:keyword media_entry_id Holds the integer value of the reported
media entry's ID
"""
__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=True)
media_entry = relationship(
MediaEntry,
backref=backref("reports_filed_on",
lazy="dynamic"))
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(Date)
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)
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)
MODELS = [
User, Client, RequestToken, AccessToken, NonceTimestamp, 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,
Notification, CommentNotification, ProcessingNotification, Client,
CommentSubscription, ReportBase, CommentReport, MediaReport, UserBan,
Privilege, PrivilegeUserAssociation,
RequestToken, AccessToken, NonceTimestamp]
"""
Foundations are the default rows that are created immediately after the tables
@ -700,7 +909,13 @@ MODELS = [
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

View File

@ -67,7 +67,6 @@ def check_collection_slug_used(creator_id, slug, ignore_c_id):
does_exist = Session.query(Collection.id).filter(filt).first() is not None
return does_exist
if __name__ == '__main__':
from mediagoblin.db.open import setup_connection_and_db_from_config

View File

@ -22,25 +22,44 @@ from oauthlib.oauth1 import ResourceEndpoint
from mediagoblin import mg_globals as mgg
from mediagoblin import messages
from mediagoblin.db.models import MediaEntry, User
from mediagoblin.tools.response import json_response, redirect, render_404
from mediagoblin.db.models import MediaEntry, User, MediaComment
from mediagoblin.tools.response import (redirect, render_404,
render_user_banned, json_response)
from mediagoblin.tools.translate import pass_to_ugettext as _
from mediagoblin.oauth.tools.request import decode_authorization_header
from mediagoblin.oauth.oauth import GMGRequestValidator
def require_active_login(controller):
def user_not_banned(controller):
"""
Require an active login from the user.
Requires that the user has not been banned. Otherwise redirects to the page
explaining why they have been banned
"""
@wraps(controller)
def wrapper(request, *args, **kwargs):
if request.user:
if request.user.is_banned():
return render_user_banned(request)
return controller(request, *args, **kwargs)
return wrapper
def require_active_login(controller):
"""
Require an active login from the user. If the user is banned, redirects to
the "You are Banned" page.
"""
@wraps(controller)
@user_not_banned
def new_controller_func(request, *args, **kwargs):
if request.user and \
request.user.status == u'needs_email_verification':
not request.user.has_privilege(u'active'):
return redirect(
request, 'mediagoblin.user_pages.user_home',
user=request.user.username)
elif not request.user or request.user.status != u'active':
elif not request.user or not request.user.has_privilege(u'active'):
next_url = urljoin(
request.urlgen('mediagoblin.auth.login',
qualified=True),
@ -53,6 +72,34 @@ def require_active_login(controller):
return new_controller_func
def user_has_privilege(privilege_name):
"""
Requires that a user have a particular privilege in order to access a page.
In order to require that a user have multiple privileges, use this
decorator twice on the same view. This decorator also makes sure that the
user is not banned, or else it redirects them to the "You are Banned" page.
:param privilege_name A unicode object that is that represents
the privilege object. This object is
the name of the privilege, as assigned
in the Privilege.privilege_name column
"""
def user_has_privilege_decorator(controller):
@wraps(controller)
@require_active_login
def wrapper(request, *args, **kwargs):
user_id = request.user.id
if not request.user.has_privilege(privilege_name):
raise Forbidden()
return controller(request, *args, **kwargs)
return wrapper
return user_has_privilege_decorator
def active_user_from_url(controller):
"""Retrieve User() from <user> URL pattern and pass in as url_user=...
@ -75,7 +122,7 @@ def user_may_delete_media(controller):
@wraps(controller)
def wrapper(request, *args, **kwargs):
uploader_id = kwargs['media'].uploader
if not (request.user.is_admin or
if not (request.user.has_privilege(u'admin') or
request.user.id == uploader_id):
raise Forbidden()
@ -92,7 +139,7 @@ def user_may_alter_collection(controller):
def wrapper(request, *args, **kwargs):
creator_id = request.db.User.query.filter_by(
username=request.matchdict['user']).first().id
if not (request.user.is_admin or
if not (request.user.has_privilege(u'admin') or
request.user.id == creator_id):
raise Forbidden()
@ -256,6 +303,48 @@ def allow_registration(controller):
return wrapper
def allow_reporting(controller):
""" Decorator for if reporting is enabled"""
@wraps(controller)
def wrapper(request, *args, **kwargs):
if not mgg.app_config["allow_reporting"]:
messages.add_message(
request,
messages.WARNING,
_('Sorry, reporting is disabled on this instance.'))
return redirect(request, 'index')
return controller(request, *args, **kwargs)
return wrapper
def get_optional_media_comment_by_id(controller):
"""
Pass in a MediaComment based off of a url component. Because of this decor-
-ator's use in filing Media or Comment Reports, it has two valid outcomes.
:returns The view function being wrapped with kwarg `comment` set to
the MediaComment who's id is in the URL. If there is a
comment id in the URL and if it is valid.
:returns The view function being wrapped with kwarg `comment` set to
None. If there is no comment id in the URL.
:returns A 404 Error page, if there is a comment if in the URL and it
is invalid.
"""
@wraps(controller)
def wrapper(request, *args, **kwargs):
if 'comment' in request.matchdict:
comment = MediaComment.query.filter_by(
id=request.matchdict['comment']).first()
if comment is None:
return render_404(request)
return controller(request, comment=comment, *args, **kwargs)
else:
return controller(request, comment=None, *args, **kwargs)
return wrapper
def auth_enabled(controller):
"""Decorator for if an auth plugin is enabled"""
@ -272,6 +361,31 @@ def auth_enabled(controller):
return wrapper
def require_admin_or_moderator_login(controller):
"""
Require a login from an administrator or a moderator.
"""
@wraps(controller)
def new_controller_func(request, *args, **kwargs):
if request.user and \
not request.user.has_privilege(u'admin',u'moderator'):
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
def oauth_required(controller):
""" Used to wrap API endpoints where oauth is required """
@wraps(controller)
@ -283,7 +397,7 @@ def oauth_required(controller):
error = "Missing required parameter."
return json_response({"error": error}, status=400)
request_validator = GMGRequestValidator()
resource_endpoint = ResourceEndpoint(request_validator)
valid, request = resource_endpoint.validate_protected_resource_request(

View File

@ -19,6 +19,6 @@ def may_edit_media(request, media):
"""Check, if the request's user may edit the media details"""
if media.uploader == request.user.id:
return True
if request.user.is_admin:
if request.user.has_privilege(u'admin'):
return True
return False

View File

@ -83,7 +83,7 @@ def edit_media(request, media):
return redirect_obj(request, media)
if request.user.is_admin \
if request.user.has_privilege(u'admin') \
and media.uploader != request.user.id \
and request.method != 'POST':
messages.add_message(
@ -184,7 +184,7 @@ def legacy_edit_profile(request):
def edit_profile(request, url_user=None):
# admins may edit any user profile
if request.user.username != url_user.username:
if not request.user.is_admin:
if not request.user.has_privilege(u'admin'):
raise Forbidden(_("You can only edit your own profile."))
# No need to warn again if admin just submitted an edited profile
@ -324,7 +324,7 @@ def edit_collection(request, collection):
return redirect_obj(request, collection)
if request.user.is_admin \
if request.user.has_privilege(u'admin') \
and collection.creator != request.user.id \
and request.method != 'POST':
messages.add_message(

View File

@ -53,8 +53,17 @@ def adduser(args):
entry.username = unicode(args.username.lower())
entry.email = unicode(args.email)
entry.pw_hash = auth.gen_password_hash(args.password)
entry.status = u'active'
entry.email_verified = True
default_privileges = [
db.Privilege.query.filter(
db.Privilege.privilege_name==u'commenter').one(),
db.Privilege.query.filter(
db.Privilege.privilege_name==u'uploader').one(),
db.Privilege.query.filter(
db.Privilege.privilege_name==u'reporter').one(),
db.Privilege.query.filter(
db.Privilege.privilege_name==u'active').one()
]
entry.all_privileges = default_privileges
entry.save()
print "User created (and email marked as verified)"
@ -74,7 +83,10 @@ def makeadmin(args):
user = db.User.query.filter_by(
username=unicode(args.username.lower())).one()
if user:
user.is_admin = True
user.all_privileges.append(
db.Privilege.query.filter(
db.Privilege.privilege_name==u'admin').one()
)
user.save()
print 'The user is now Admin'
else:

View File

@ -0,0 +1,148 @@
# GNU MediaGoblin -- federated, autonomous media hosting
# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import wtforms
from mediagoblin.tools.translate import lazy_pass_to_ugettext as _
ACTION_CHOICES = [(_(u'takeaway'),_(u'Take away privilege')),
(_(u'userban'),_(u'Ban the user')),
(_(u'sendmessage'),(u'Send the user a message')),
(_(u'delete'),_(u'Delete the content'))]
class MultiCheckboxField(wtforms.SelectMultipleField):
"""
A multiple-select, except displays a list of checkboxes.
Iterating the field will produce subfields, allowing custom rendering of
the enclosed checkbox fields.
code from http://wtforms.simplecodes.com/docs/1.0.4/specific_problems.html
"""
widget = wtforms.widgets.ListWidget(prefix_label=False)
option_widget = wtforms.widgets.CheckboxInput()
# ============ Forms for mediagoblin.moderation.user page ================== #
class PrivilegeAddRemoveForm(wtforms.Form):
"""
This form is used by an admin to give/take away a privilege directly from
their user page.
"""
privilege_name = wtforms.HiddenField('',[wtforms.validators.required()])
class BanForm(wtforms.Form):
"""
This form is used by an admin to ban a user directly from their user page.
"""
user_banned_until = wtforms.DateField(
_(u'User will be banned until:'),
format='%Y-%m-%d',
validators=[wtforms.validators.optional()])
why_user_was_banned = wtforms.TextAreaField(
_(u'Why are you banning this User?'),
validators=[wtforms.validators.optional()])
# =========== Forms for mediagoblin.moderation.report page ================= #
class ReportResolutionForm(wtforms.Form):
"""
This form carries all the information necessary to take punitive actions
against a user who created content that has been reported.
:param action_to_resolve A list of Unicode objects representing
a choice from the ACTION_CHOICES const-
-ant. Every choice passed affects what
punitive actions will be taken against
the user.
:param targeted_user A HiddenField object that holds the id
of the user that was reported.
:param take_away_privileges A list of Unicode objects which repres-
-ent the privileges that are being tak-
-en away. This field is optional and
only relevant if u'takeaway' is in the
`action_to_resolve` list.
:param user_banned_until A DateField object that holds the date
that the user will be unbanned. This
field is optional and only relevant if
u'userban' is in the action_to_resolve
list. If the user is being banned and
this field is blank, the user is banned
indefinitely.
:param why_user_was_banned A TextArea object that holds the
reason that a user was banned, to disp-
-lay to them when they try to log in.
This field is optional and only relevant
if u'userban' is in the
`action_to_resolve` list.
:param message_to_user A TextArea object that holds a message
which will be emailed to the user. This
is only relevant if the u'sendmessage'
option is in the `action_to_resolve`
list.
:param resolution_content A TextArea object that is required for
every report filed. It represents the
reasons that the moderator/admin resol-
-ved the report in such a way.
"""
action_to_resolve = MultiCheckboxField(
_(u'What action will you take to resolve the report?'),
validators=[wtforms.validators.optional()],
choices=ACTION_CHOICES)
targeted_user = wtforms.HiddenField('',
validators=[wtforms.validators.required()])
take_away_privileges = wtforms.SelectMultipleField(
_(u'What privileges will you take away?'),
validators=[wtforms.validators.optional()])
user_banned_until = wtforms.DateField(
_(u'User will be banned until:'),
format='%Y-%m-%d',
validators=[wtforms.validators.optional()])
why_user_was_banned = wtforms.TextAreaField(
validators=[wtforms.validators.optional()])
message_to_user = wtforms.TextAreaField(
validators=[wtforms.validators.optional()])
resolution_content = wtforms.TextAreaField()
# ======== Forms for mediagoblin.moderation.report_panel page ============== #
class ReportPanelSortingForm(wtforms.Form):
"""
This form is used for sorting and filtering through different reports in
the mediagoblin.moderation.reports_panel view.
"""
active_p = wtforms.IntegerField(
validators=[wtforms.validators.optional()])
closed_p = wtforms.IntegerField(
validators=[wtforms.validators.optional()])
reported_user = wtforms.IntegerField(
validators=[wtforms.validators.optional()])
reporter = wtforms.IntegerField(
validators=[wtforms.validators.optional()])
class UserPanelSortingForm(wtforms.Form):
"""
This form is used for sorting different reports.
"""
p = wtforms.IntegerField(
validators=[wtforms.validators.optional()])

View File

@ -0,0 +1,38 @@
# 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/>.
moderation_routes = [
('mediagoblin.moderation.media_panel',
'/media/',
'mediagoblin.moderation.views:moderation_media_processing_panel'),
('mediagoblin.moderation.users',
'/users/',
'mediagoblin.moderation.views:moderation_users_panel'),
('mediagoblin.moderation.reports',
'/reports/',
'mediagoblin.moderation.views:moderation_reports_panel'),
('mediagoblin.moderation.users_detail',
'/users/<string:user>/',
'mediagoblin.moderation.views:moderation_users_detail'),
('mediagoblin.moderation.give_or_take_away_privilege',
'/users/<string:user>/privilege/',
'mediagoblin.moderation.views:give_or_take_away_privilege'),
('mediagoblin.moderation.ban_or_unban',
'/users/<string:user>/ban/',
'mediagoblin.moderation.views:ban_or_unban'),
('mediagoblin.moderation.reports_detail',
'/reports/<int:report_id>/',
'mediagoblin.moderation.views:moderation_reports_detail')]

View File

@ -0,0 +1,217 @@
# 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/>.
from mediagoblin import mg_globals
from mediagoblin.db.models import User, Privilege, UserBan
from mediagoblin.db.base import Session
from mediagoblin.tools.mail import send_email
from mediagoblin.tools.response import redirect
from datetime import datetime
from mediagoblin.tools.translate import lazy_pass_to_ugettext as _
def take_punitive_actions(request, form, report, user):
message_body =''
# The bulk of this action is running through all of the different
# punitive actions that a moderator could take.
if u'takeaway' in form.action_to_resolve.data:
for privilege_name in form.take_away_privileges.data:
take_away_privileges(user.username, privilege_name)
form.resolution_content.data += \
u"\n{mod} took away {user}\'s {privilege} privileges.".format(
mod=request.user.username,
user=user.username,
privilege=privilege_name)
# If the moderator elects to ban the user, a new instance of user_ban
# will be created.
if u'userban' in form.action_to_resolve.data:
user_ban = ban_user(form.targeted_user.data,
expiration_date=form.user_banned_until.data,
reason=form.why_user_was_banned.data)
Session.add(user_ban)
form.resolution_content.data += \
u"\n{mod} banned user {user} {expiration_date}.".format(
mod=request.user.username,
user=user.username,
expiration_date = (
"until {date}".format(date=form.user_banned_until.data)
if form.user_banned_until.data
else "indefinitely"
)
)
# If the moderator elects to send a warning message. An email will be
# sent to the email address given at sign up
if u'sendmessage' in form.action_to_resolve.data:
message_body = form.message_to_user.data
form.resolution_content.data += \
u"\n{mod} sent a warning email to the {user}.".format(
mod=request.user.username,
user=user.username)
if u'delete' in form.action_to_resolve.data and \
report.is_comment_report():
deleted_comment = report.comment
Session.delete(deleted_comment)
form.resolution_content.data += \
u"\n{mod} deleted the comment.".format(
mod=request.user.username)
elif u'delete' in form.action_to_resolve.data and \
report.is_media_entry_report():
deleted_media = report.media_entry
deleted_media.delete()
form.resolution_content.data += \
u"\n{mod} deleted the media entry.".format(
mod=request.user.username)
report.archive(
resolver_id=request.user.id,
resolved=datetime.now(),
result=form.resolution_content.data)
Session.add(report)
Session.commit()
if message_body:
send_email(
mg_globals.app_config['email_sender_address'],
[user.email],
_('Warning from')+ '- {moderator} '.format(
moderator=request.user.username),
message_body)
return redirect(
request,
'mediagoblin.moderation.users_detail',
user=user.username)
def take_away_privileges(user,*privileges):
"""
Take away all of the privileges passed as arguments.
:param user A Unicode object representing the target user's
User.username value.
:param privileges A variable number of Unicode objects describing
the privileges being taken away.
:returns True If ALL of the privileges were taken away
successfully.
:returns False If ANY of the privileges were not taken away
successfully. This means the user did not have
(one of) the privilege(s) to begin with.
"""
if len(privileges) == 1:
privilege = Privilege.query.filter(
Privilege.privilege_name==privileges[0]).first()
user = User.query.filter(
User.username==user).first()
if privilege in user.all_privileges:
user.all_privileges.remove(privilege)
return True
return False
elif len(privileges) > 1:
return (take_away_privileges(user, privileges[0]) and \
take_away_privileges(user, *privileges[1:]))
def give_privileges(user,*privileges):
"""
Take away all of the privileges passed as arguments.
:param user A Unicode object representing the target user's
User.username value.
:param privileges A variable number of Unicode objects describing
the privileges being granted.
:returns True If ALL of the privileges were granted successf-
-ully.
:returns False If ANY of the privileges were not granted succ-
essfully. This means the user already had (one
of) the privilege(s) to begin with.
"""
if len(privileges) == 1:
privilege = Privilege.query.filter(
Privilege.privilege_name==privileges[0]).first()
user = User.query.filter(
User.username==user).first()
if privilege not in user.all_privileges:
user.all_privileges.append(privilege)
return True
return False
elif len(privileges) > 1:
return (give_privileges(user, privileges[0]) and \
give_privileges(user, *privileges[1:]))
def ban_user(user_id, expiration_date=None, reason=None):
"""
This function is used to ban a user. If the user is already banned, the
function returns False. If the user is not already banned, this function
bans the user using the arguments to build a new UserBan object.
:returns False if the user is already banned and the ban is not updated
:returns UserBan object if there is a new ban that was created.
"""
user_ban =UserBan.query.filter(
UserBan.user_id==user_id)
if user_ban.count():
return False
new_user_ban = UserBan(
user_id=user_id,
expiration_date=expiration_date,
reason=reason)
return new_user_ban
def unban_user(user_id):
"""
This function is used to unban a user. If the user is not currently banned,
nothing happens.
:returns True if the operation was completed successfully and the user
has been unbanned
:returns False if the user was never banned.
"""
user_ban = UserBan.query.filter(
UserBan.user_id==user_id)
if user_ban.count() == 0:
return False
user_ban.first().delete()
return True
def parse_report_panel_settings(form):
"""
This function parses the url arguments to which are used to filter reports
in the reports panel view. More filters can be added to make a usuable
search function.
:returns A dictionary of sqlalchemy-usable filters.
"""
filters = {}
if form.validate():
filters['reported_user_id'] = form.reported_user.data
filters['reporter_id'] = form.reporter.data
filters = dict((k, v)
for k, v in filters.iteritems() if v)
return filters

View File

@ -0,0 +1,219 @@
# 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/>.
from mediagoblin.db.models import (MediaEntry, User,ReportBase, Privilege,
UserBan)
from mediagoblin.decorators import (require_admin_or_moderator_login,
active_user_from_url, user_has_privilege,
allow_reporting)
from mediagoblin.tools.response import render_to_response, redirect
from mediagoblin.moderation import forms as moderation_forms
from mediagoblin.moderation.tools import (take_punitive_actions, \
take_away_privileges, give_privileges, ban_user, unban_user, \
parse_report_panel_settings)
from math import ceil
@require_admin_or_moderator_login
def moderation_media_processing_panel(request):
'''
Show the global media processing panel for this instance
'''
processing_entries = MediaEntry.query.filter_by(state = u'processing').\
order_by(MediaEntry.created.desc())
# Get media entries which have failed to process
failed_entries = MediaEntry.query.filter_by(state = u'failed').\
order_by(MediaEntry.created.desc())
processed_entries = MediaEntry.query.filter_by(state = u'processed').\
order_by(MediaEntry.created.desc()).limit(10)
# Render to response
return render_to_response(
request,
'mediagoblin/moderation/media_panel.html',
{'processing_entries': processing_entries,
'failed_entries': failed_entries,
'processed_entries': processed_entries})
@require_admin_or_moderator_login
def moderation_users_panel(request):
'''
Show the global panel for monitoring users in this instance
'''
current_page = 1
if len(request.args) > 0:
form = moderation_forms.UserPanelSortingForm(request.args)
if form.validate():
current_page = form.p.data or 1
all_user_list = User.query
user_list = all_user_list.order_by(
User.created.desc()).offset(
(current_page-1)*10).limit(10)
last_page = int(ceil(all_user_list.count()/10.))
return render_to_response(
request,
'mediagoblin/moderation/user_panel.html',
{'user_list': user_list,
'current_page':current_page,
'last_page':last_page})
@require_admin_or_moderator_login
def moderation_users_detail(request):
'''
Shows details about a particular user.
'''
user = User.query.filter_by(username=request.matchdict['user']).first()
active_reports = user.reports_filed_on.filter(
ReportBase.resolved==None).limit(5)
closed_reports = user.reports_filed_on.filter(
ReportBase.resolved!=None).all()
privileges = Privilege.query
user_banned = UserBan.query.get(user.id)
ban_form = moderation_forms.BanForm()
return render_to_response(
request,
'mediagoblin/moderation/user.html',
{'user':user,
'privileges': privileges,
'reports':active_reports,
'user_banned':user_banned,
'ban_form':ban_form})
@require_admin_or_moderator_login
@allow_reporting
def moderation_reports_panel(request):
'''
Show the global panel for monitoring reports filed against comments or
media entries for this instance.
'''
filters = []
active_settings, closed_settings = {'current_page':1}, {'current_page':1}
if len(request.args) > 0:
form = moderation_forms.ReportPanelSortingForm(request.args)
if form.validate():
filters = parse_report_panel_settings(form)
active_settings['current_page'] = form.active_p.data or 1
closed_settings['current_page'] = form.closed_p.data or 1
filters = [
getattr(ReportBase,key)==val
for key,val in filters.viewitems()]
all_active = ReportBase.query.filter(
ReportBase.resolved==None).filter(
*filters)
all_closed = ReportBase.query.filter(
ReportBase.resolved!=None).filter(
*filters)
# report_list and closed_report_list are the two lists of up to 10
# items which are actually passed to the user in this request
report_list = all_active.order_by(
ReportBase.created.desc()).offset(
(active_settings['current_page']-1)*10).limit(10)
closed_report_list = all_closed.order_by(
ReportBase.created.desc()).offset(
(closed_settings['current_page']-1)*10).limit(10)
active_settings['last_page'] = int(ceil(all_active.count()/10.))
closed_settings['last_page'] = int(ceil(all_closed.count()/10.))
# Render to response
return render_to_response(
request,
'mediagoblin/moderation/report_panel.html',
{'report_list':report_list,
'closed_report_list':closed_report_list,
'active_settings':active_settings,
'closed_settings':closed_settings})
@require_admin_or_moderator_login
@allow_reporting
def moderation_reports_detail(request):
"""
This is the page an admin or moderator goes to see the details of a report.
The report can be resolved or unresolved. This is also the page that a mod-
erator would go to to take an action to resolve a report.
"""
form = moderation_forms.ReportResolutionForm(request.form)
report = ReportBase.query.get(request.matchdict['report_id'])
form.take_away_privileges.choices = [
(s.privilege_name,s.privilege_name.title()) \
for s in report.reported_user.all_privileges
]
if request.method == "POST" and form.validate() and not (
not request.user.has_privilege(u'admin') and
report.reported_user.has_privilege(u'admin')):
user = User.query.get(form.targeted_user.data)
return take_punitive_actions(request, form, report, user)
form.targeted_user.data = report.reported_user_id
return render_to_response(
request,
'mediagoblin/moderation/report.html',
{'report':report,
'form':form})
@user_has_privilege(u'admin')
@active_user_from_url
def give_or_take_away_privilege(request, url_user):
'''
A form action to give or take away a particular privilege from a user.
Can only be used by an admin.
'''
form = moderation_forms.PrivilegeAddRemoveForm(request.form)
if request.method == "POST" and form.validate():
privilege = Privilege.query.filter(
Privilege.privilege_name==form.privilege_name.data).one()
if not take_away_privileges(
url_user.username, form.privilege_name.data):
give_privileges(url_user.username, form.privilege_name.data)
url_user.save()
return redirect(
request,
'mediagoblin.moderation.users_detail',
user=url_user.username)
@user_has_privilege(u'admin')
@active_user_from_url
def ban_or_unban(request, url_user):
"""
A page to ban or unban a user. Only can be used by an admin.
"""
form = moderation_forms.BanForm(request.form)
if request.method == "POST" and form.validate():
already_banned = unban_user(url_user.id)
same_as_requesting_user = (request.user.id == url_user.id)
if not already_banned and not same_as_requesting_user:
user_ban = ban_user(url_user.id,
expiration_date = form.user_banned_until.data,
reason = form.why_user_was_banned.data)
user_ban.save()
return redirect(
request,
'mediagoblin.moderation.users_detail',
user=url_user.username)

View File

@ -18,7 +18,7 @@ import logging
from mediagoblin.tools.routing import add_route, mount, url_map
from mediagoblin.tools.pluginapi import PluginManager
from mediagoblin.admin.routing import admin_routes
from mediagoblin.moderation.routing import moderation_routes
from mediagoblin.auth.routing import auth_routes
@ -27,8 +27,10 @@ _log = logging.getLogger(__name__)
def get_url_map():
add_route('index', '/', 'mediagoblin.views:root_view')
add_route('terms_of_service','/terms_of_service',
'mediagoblin.views:terms_of_service')
mount('/auth', auth_routes)
mount('/a', admin_routes)
mount('/mod', moderation_routes)
import mediagoblin.submit.routing
import mediagoblin.user_pages.routing

View File

@ -156,6 +156,10 @@ a.logo {
margin: 6px 8px 6px 0;
}
.fine_print {
font-size: 0.8em;
}
.mediagoblin_content {
width: 100%;
padding-bottom: 74px;
@ -220,6 +224,7 @@ footer {
color: #283F35;
}
.button_form {
min-width: 99px;
margin: 10px 0px 10px 15px;
@ -351,40 +356,40 @@ textarea#description, textarea#bio {
/* comments */
.comment_wrapper {
.comment_wrapper, .report_wrapper {
margin-top: 20px;
margin-bottom: 20px;
}
.comment_wrapper p {
.comment_wrapper p, .report_wrapper p {
margin-bottom: 2px;
}
.comment_author {
.comment_author, .report_author {
padding-top: 4px;
font-size: 0.9em;
}
a.comment_authorlink {
a.comment_authorlink, a.report_authorlink {
text-decoration: none;
padding-right: 5px;
font-weight: bold;
padding-left: 2px;
}
a.comment_authorlink:hover {
a.comment_authorlink:hover, a.report_authorlink:hover {
text-decoration: underline;
}
a.comment_whenlink {
a.comment_whenlink, a.report_whenlink {
text-decoration: none;
}
a.comment_whenlink:hover {
a.comment_whenlink:hover, a.report_whenlink:hover {
text-decoration: underline;
}
.comment_content {
.comment_content, .report_content {
margin-left: 8px;
margin-top: 8px;
}
@ -408,6 +413,13 @@ textarea#comment_content {
padding-right: 6px;
}
a.report_authorlink, a.report_whenlink {
color: #D486B1;
}
ul#action_to_resolve {list-style:none; margin-left:10px;}
/* media galleries */
.media_thumbnail {
@ -608,6 +620,38 @@ table.media_panel th {
text-align: left;
}
/* moderator panels */
table.admin_panel {
width: 100%
}
table.admin_side_panel {
width: 60%
}
table.admin_panel th, table.admin_side_panel th {
font-weight: bold;
padding-bottom: 4px;
text-align: left;
color: #fff;
}
table td.user_with_privilege {
font-weight: bold;
color: #86D4B1;
}
table td.user_without_privilege {
font-weight: bold;
color: #D486B1;
}
.return_to_panel {
text-align:right;
float: right;
font-size:1.2em
}
/* Delete panel */
@ -616,6 +660,27 @@ table.media_panel th {
margin-left: 10px;
}
/* code of conduct */
#code_of_conduct_list {
margin-left:25px;
margin-bottom: 10px;
}
#code_of_conduct_list li {
margin:5px 0 15px 25px;
}
#code_of_conduct_list strong{
color:#fff;
}
.nested_sublist {
margin: 5px 0 10px 25px;
font-size:80%;
}
.nested_sublist li {
margin-bottom: 10px;
}
/* ASCII art and code */
@font-face {

Binary file not shown.

After

Width:  |  Height:  |  Size: 682 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 647 B

View File

@ -0,0 +1,67 @@
/**
* 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/>.
*/
function init_report_resolution_form() {
hidden_input_names = {
'takeaway':['take_away_privileges'],
'userban':['user_banned_until','why_user_was_banned'],
'sendmessage':['message_to_user']
}
init_user_banned_form();
$('form#resolution_form').hide()
$('#open_resolution_form').click(function() {
$('form#resolution_form').toggle();
$.each(hidden_input_names, function(key, list){
$.each(list, function(index, name){
$('label[for='+name+']').hide();
$('#'+name).hide();
});
});
});
$('#action_to_resolve').change(function() {
$('ul#action_to_resolve li input:checked').each(function() {
$.each(hidden_input_names[$(this).val()], function(index, name){
$('label[for='+name+']').show();
$('#'+name).show();
});
});
$('ul#action_to_resolve li input:not(:checked)').each(function() {
$.each(hidden_input_names[$(this).val()], function(index, name){
$('label[for='+name+']').hide();
$('#'+name).hide();
});
});
});
$("#submit_this_report").click(function(){
submit_user_banned_form()
});
}
function submit_user_banned_form() {
if ($("#user_banned_until").val() == 'YYYY-MM-DD'){
$("#user_banned_until").val("");
}
}
function init_user_banned_form() {
$('#user_banned_until').val("YYYY-MM-DD")
$("#user_banned_until").focus(function() {
$(this).val("");
$(this).unbind('focus');
});
}

View File

@ -27,7 +27,7 @@ _log = logging.getLogger(__name__)
from mediagoblin.tools.text import convert_to_tag_list_of_dicts
from mediagoblin.tools.translate import pass_to_ugettext as _
from mediagoblin.tools.response import render_to_response, redirect
from mediagoblin.decorators import require_active_login
from mediagoblin.decorators import require_active_login, user_has_privilege
from mediagoblin.submit import forms as submit_forms
from mediagoblin.messages import add_message, SUCCESS
from mediagoblin.media_types import sniff_media, \
@ -39,6 +39,7 @@ 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.

View File

@ -0,0 +1,35 @@
{#
# 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 %}You are Banned.{% endtrans %}{% endblock %}
{% block mediagoblin_content %}
<img class="right_align" src="{{ request.staticdirect('/images/404.png') }}"
alt="{% trans %}Image of goblin stressing out{% endtrans %}" />
<h1>{% trans %}You have been banned{% endtrans %}
{% if expiration_date %}
{% trans %}until{% endtrans %} {{ expiration_date }}
{% else %}
{% trans %}indefinitely{% endtrans %}
{% endif %}
</h2>
<p>{{ reason|safe }}</p>
<div class="clear"></div>
{% endblock %}

View File

@ -62,7 +62,7 @@
{% block mediagoblin_header_title %}{% endblock %}
<div class="header_right">
{%- if request.user %}
{% if request.user and request.user.status == 'active' %}
{% if request.user and request.user.has_privilege('active') and not request.user.is_banned() %}
{% set notification_count = get_notification_count(request.user.id) %}
{% if notification_count %}
@ -71,7 +71,7 @@
{% endif %}
<a href="javascript:;" class="button_action header_dropdown_down">&#9660;</a>
<a href="javascript:;" class="button_action header_dropdown_up">&#9650;</a>
{% elif request.user and request.user.status == "needs_email_verification" %}
{% elif request.user and not request.user.has_privilege('active') %}
{# the following link should only appear when verification is needed #}
<a href="{{ request.urlgen('mediagoblin.user_pages.user_home',
user=request.user.username) }}"
@ -84,6 +84,19 @@
"javascript:;"
{% endif %}
>{% trans %}log out{% endtrans %}</a>
{% elif request.user and request.user.is_banned() %}
<a id="logout" href=
{% if persona is not defined %}
"{{ request.urlgen('mediagoblin.auth.logout') }}"
{% else %}
"javascript:;"
{% endif %}
>{% trans %}log out{% endtrans %}</a>
<p class="fine_print">
<a href="{{ request.urlgen('terms_of_service') }}">
{%- trans %}Terms of Service{%- endtrans %}
</a>
</p>
{% endif %}
{%- elif auth %}
<a href=
@ -98,7 +111,7 @@
{%- endif %}
</div>
<div class="clear"></div>
{% if request.user and request.user.status == 'active' %}
{% if request.user and request.user.has_privilege('active') %}
<div class="header_dropdown">
<p>
<span class="dropdown_title">
@ -130,14 +143,25 @@
<a class="button_action" href="{{ request.urlgen('mediagoblin.submit.collection') }}">
{%- trans %}Create new collection{% endtrans -%}
</a>
{% if request.user.is_admin %}
{% if request.user.has_privilege('admin','moderator') %}
<p>
<span class="dropdown_title">Admin powers:</span>
<a href="{{ request.urlgen('mediagoblin.admin.panel') }}">
<span class="dropdown_title">Moderation powers:</span>
<a href="{{ request.urlgen('mediagoblin.moderation.media_panel') }}">
{%- trans %}Media processing panel{% endtrans -%}
</a>
&middot;
<a href="{{ request.urlgen('mediagoblin.moderation.users') }}">
{%- trans %}User management panel{% endtrans -%}
</a>
&middot;
<a href="{{ request.urlgen('mediagoblin.moderation.reports') }}">
{%- trans %}Report management panel{% endtrans -%}
</a>
</p>
{% endif %}
<p class="fine_print">
<a href="{{ request.urlgen('terms_of_service') }}">Terms of Service</a>
</p>
{% include 'mediagoblin/fragments/header_notifications.html' %}
</div>
{% endif %}

View File

@ -21,6 +21,7 @@
{% trans %}Media processing panel{% endtrans %} &mdash; {{ super() }}
{%- endblock %}
{% block mediagoblin_content %}
<h1>{% trans %}Media processing panel{% endtrans %}</h1>
@ -28,7 +29,7 @@
<p>
{% trans %}Here you can track the state of media being processed on this instance.{% endtrans %}
</p>
<h2>{% trans %}Media in-processing{% endtrans %}</h2>
{% if processing_entries.count() %}
@ -56,7 +57,7 @@
</table>
{% else %}
<p><em>{% trans %}No media in-processing{% endtrans %}</em></p>
{% endif %}
{% endif %}
<h2>{% trans %}These uploads failed to process:{% endtrans %}</h2>
{% if failed_entries.count() %}

View File

@ -0,0 +1,154 @@
{#
# 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" %}
{% import "/mediagoblin/utils/wtforms.html" as wtforms_util %}
{%- block mediagoblin_head %}
<script src="{{ request.staticdirect('/js/setup_report_forms.js') }}"></script>
{% endblock %}
{%- block mediagoblin_content %}
{% if not report %}
Sorry, no such report found.
{% else %}
<a href="{{ request.urlgen('mediagoblin.moderation.reports') }}"
class="return_to_panel button_action"
title="Return to Reports Panel">
{% trans %}Return to Reports Panel{% endtrans %}</a>
<h2>{% trans %}Report{% endtrans %} #{{ report.id }}</h2>
{% if report.is_comment_report() and report.comment %}
{% trans %}Reported comment{% endtrans %}:
{% set comment = report.comment %}
{% set reported_user = 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.moderation.users_detail',
user=comment.get_author.username) }}"
class="comment_authorlink">
{{- reported_user.username -}}
</a>
<a href="{{ request.urlgen(
'mediagoblin.user_pages.media_home.view_comment',
comment=comment.id,
user=comment.get_media_entry.get_uploader.username,
media=comment.get_media_entry.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>
{% elif report.is_media_entry_report() and report.media_entry %}
{% set media_entry = report.media_entry %}
<div class="media_thumbnail">
<a href="{{ request.urlgen('mediagoblin.user_pages.media_home',
user=media_entry.get_uploader.username,
media=media_entry.slug_or_id) }}">
<img src="{{ media_entry.thumb_url}}"/></a>
<a href="{{ request.urlgen('mediagoblin.user_pages.media_home',
user=media_entry.get_uploader.username,
media=media_entry.slug_or_id) }}" class=thumb_entry_title>
{{ media_entry.title }}</a>
</div>
<div class=clear></div>
<p>
{% trans user_name=report.reported_user.username,
user_url=request.urlgen(
'mediagoblin.moderation.users_detail',
user=report.reported_user.username) %}
❖ Reported media by <a href="{{ user_url }}">{{ user_name }}</a>
{% endtrans %}
</p>
<div class=clear></div>
{% else %}
<h2>{% trans user_url=request.urlgen(
'mediagoblin.moderation.users_detail',
user=report.reporter.username),
user_name=report.reported_user.username %}
CONTENT BY
<a href="{{ user_url }}"> {{ user_name }}</a>
HAS BEEN DELETED
{% endtrans %}
</h2>
{% endif %}
Reason for report:
<div id="report-{{ report.id }}"
class="report_wrapper">
<div class="report_author">
<img src="{{ request.staticdirect(
'/images/icon_clipboard_alert.png') }}"
alt="Under a GNU LGPL v.3 or Creative Commons BY-SA 3.0 license.
Distributed by the GNOME project http://www.gnome.org" />
<a href="{{ request.urlgen('mediagoblin.moderation.users_detail',
user=report.reporter.username) }}"
class="report_authorlink">
{{- report.reporter.username -}}
</a>
<a href="{{ request.urlgen('mediagoblin.moderation.reports_detail',
report_id=report.id) }}"
class="report_whenlink">
<span title='{{- report.created.strftime("%I:%M%p %Y-%m-%d") -}}'>
{%- trans formatted_time=timesince(report.created) -%}
{{ formatted_time }} ago
{%- endtrans -%}
</span>
</a>
</div>
<div class="report_content">
{{ report.report_content }}
</div>
</div>
{% if not report.is_archived_report() and not (report.reported_user.has_privilege('admin') and not request.user.has_privilege('admin')) %}
<input type=button value=Resolve id=open_resolution_form />
<form action="" method="POST" id=resolution_form>
{{ wtforms_util.render_divs(form) }}
{{ csrf_token }}
<input type=submit id="submit_this_report" value="Resolve This Report"/>
</form>
<script>
$(document).ready(function() {
init_report_resolution_form();
});
</script>
{% elif report.is_archived_report() %}
<h2><img src="{{ request.staticdirect('/images/icon_clipboard.png') }}"
alt="Under a GNU LGPL v.3 or Creative Commons BY-SA 3.0 license.
Distributed by the GNOME project http://www.gnome.org" />
{% trans %}Status{% endtrans %}:
</h2>
<b>{% trans %}RESOLVED{% endtrans %}</b>
{{ report.resolved.strftime("%I:%M%p %Y-%m-%d") }}
<pre>
<p>{{ report.result }}</p>
</pre>
{% else %}
<input type=button disabled=disabled value="Resolve This Report"/>
<p>You cannot take action against an administrator</p>
{% endif %}
{% endif %}
{% endblock %}

View File

@ -0,0 +1,200 @@
{#
# 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" %}
{% import "/mediagoblin/utils/wtforms.html" as wtforms_util %}
{% 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 open reports that have been filed by users.
{% endtrans %}
</p>
<h2>{% trans %}Active Reports Filed{% endtrans %}</h2>
{% if report_list.count() %}
{% if not active_settings.last_page == 1 %}
{% if 'active_p='~active_settings.current_page in request.query_string %}
{% set query_string = request.query_string %}{% else %}
{% set query_string =
'active_p='~active_settings.current_page~"&"+request.query_string %}
{% endif %}
<div class="right_align">
{% set first_vis = active_settings.current_page-3 %}
{% set last_vis = active_settings.current_page+3 %}
{% set curr_page = active_settings.current_page %}
{% if 1 == curr_page %}<b>1</b>{% else %}
<a href ="?{{ query_string.replace(
'active_p='~active_settings.current_page,
'active_p='~1) }}">
1</a>{% endif %}
{% if first_vis > 1 %}...{% endif %}
{% for p in range(first_vis,last_vis+1) %}
{% if p > 1 and p < active_settings.last_page and
curr_page !=p %}
<a href="?{{ query_string.replace(
'active_p='~active_settings.current_page,
'active_p='~p) }}">
{{ p }}</a>
{% elif p > 1 and p < active_settings.last_page %}
<b>{{ p }}</b>
{% endif %}
{% endfor %}
{% if last_vis < active_settings.last_page %}...{% endif %}
{% if active_settings.last_page != curr_page %}
<a href ="?{{ query_string.replace(
'active_p='~active_settings.current_page,
'active_p='~active_settings.last_page) }}">
{{ active_settings.last_page }}</a>
{% else %}<b>{{ active_settings.last_page }}</b>
{% endif %}
</div>
{% endif %}
<table class="admin_panel processing">
<tr>
<th></th>
<th>{% trans %}Offender{% endtrans %}</th>
<th>{% trans %}When Reported{% endtrans %}</th>
<th>{% trans %}Reported By{% endtrans %}</th>
<th>{% trans %}Reason{% endtrans %}</th>
</tr>
{% for report in report_list %}
<tr>
{% if report.discriminator == "comment_report" %}
<td>
<img
src="{{ request.staticdirect('/images/icon_clipboard_alert.png') }}"
alt="Under a GNU LGPL v.3 or Creative Commons BY-SA 3.0 license.
Distributed by the GNOME project http://www.gnome.org" />
<a href="{{ request.urlgen(
'mediagoblin.moderation.reports_detail',
report_id=report.id) }}">
{% trans report_id=report.id %}
Comment Report #{{ report_id }}
{% endtrans %}
</a>
</td>
{% elif report.discriminator == "media_report" %}
<td>
<img
src="{{ request.staticdirect('/images/icon_clipboard_alert.png') }}"
alt="Under a GNU LGPL v.3 or Creative Commons BY-SA 3.0 license.
Distributed by the GNOME project http://www.gnome.org" />
<a href="{{ request.urlgen(
'mediagoblin.moderation.reports_detail',
report_id=report.id) }}">
{% trans report_id=report.id %}
Media Report #{{ report_id }}
{% endtrans %}
</a>
</td>
{% endif %}
<td>{{ report.reported_user.username }}</td>
<td>{{ report.created.strftime("%F %R") }}</td>
<td>{{ report.reporter.username }}</td>
<td>{{ report.report_content[0:20] }}...</td>
</tr>
{% endfor %}
</table>
{% else %}
<p><em>{% trans %}No open reports found.{% endtrans %}</em></p>
{% endif %}
<h2>{% trans %}Closed Reports{% endtrans %}</h2>
{% if closed_report_list.count() %}
{% if not closed_settings.last_page == 1 %}
{% if 'closed_p='~closed_settings.current_page in request.query_string %}
{% set query_string = request.query_string %}{% else %}
{% set query_string =
'closed_p='~closed_settings.current_page~"&"+request.query_string %}
{% endif %}
<div class="right_align">
{% set first_vis = closed_settings.current_page-3 %}
{% set last_vis = closed_settings.current_page+3 %}
{% set curr_page = closed_settings.current_page %}
{% if not curr_page==1 %}
<a href ="?{{ query_string.replace(
'closed_p='~closed_settings.current_page,
'closed_p='~1) }}">1</a>
{% else %}
<b>1 </b>
{% endif %}
{% if first_vis > 1 %}...{% endif %}
{% for p in range(first_vis,last_vis+1) %}
{% if p > 1 and p < closed_settings.last_page and
curr_page !=p %}
<a href="?{{ query_string.replace(
'closed_p='~closed_settings.current_page,
'closed_p='~p) }}">
{{ p }}</a>
{% elif p > 1 and p < closed_settings.last_page %}
<b>{{ p }}</b>
{% endif %}
{% endfor %}
{% if last_vis < closed_settings.last_page %}...{% endif %}
{% if curr_page != closed_settings.last_page %}
<a href ="?{{ query_string.replace(
'closed_p='~closed_settings.current_page,
'closed_p='~closed_settings.last_page) }}">
{{ closed_settings.last_page }}</a>
{% else %}<b>{{ closed_settings.last_page }}</b>
{% endif %}
</div>
{% endif %}
<table class="media_panel processing">
<tr>
<th></th>
<th>{% trans %}Resolved{% endtrans %}</th>
<th>{% trans %}Offender{% endtrans %}</th>
<th>{% trans %}Action Taken{% endtrans %}</th>
<th>{% trans %}Reported By{% endtrans %}</th>
<th>{% trans %}Reason{% endtrans %}</th>
</tr>
{% for report in closed_report_list %}
<tr>
<td>
<img
src="{{ request.staticdirect('/images/icon_clipboard.png') }}"
alt="Under a GNU LGPL v.3 or Creative Commons BY-SA 3.0 license.
Distributed by the GNOME project http://www.gnome.org" />
<a href="{{ request.urlgen('mediagoblin.moderation.reports_detail',
report_id=report.id) }}">
{% trans report_id=report.id %}
Closed Report #{{ report_id }}
{% endtrans %}
</a>
</td>
<td>{{ report.resolved.strftime("%F %R") }}</td>
<td>{{ report.reported_user.username }}</td>
<td>{{ report.created.strftime("%F %R") }}</td>
<td>{{ report.reporter.username }}</td>
<td>{{ report.report_content[:15] }}...</td>
</tr>
{% endfor %}
</table>
{% else %}
<p><em>{% trans %}No closed reports found.{% endtrans %}</em></p>
{% endif %}
{% endblock %}

View File

@ -0,0 +1,204 @@
{#
# 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" %}
{% import "/mediagoblin/utils/wtforms.html" as wtforms_util %}
{% block title %}
{%- if user -%}
{%- trans username=user.username -%}
User: {{ username }}
{%- endtrans %} &mdash; {{ super() }}
{%- else -%}
{{ super() }}
{%- endif -%}
{% endblock %}
{%- block mediagoblin_head %}
<script src="{{ request.staticdirect('/js/setup_report_forms.js') }}"></script>
{% endblock %}
{% block mediagoblin_content -%}
{# If no user... #}
{% if not user %}
<p>{% trans %}Sorry, no such user found.{% endtrans %}</p>
{# User exists, but needs verification #}
{% elif not user.has_privilege('active') %}
<div class="profile_sidebar empty_space">
<h1>{% trans %}Email verification needed{% endtrans %}</h1>
<p>
{% trans -%}
Someone has registered an account with this username, but it still has
to be activated.
{%- endtrans %}
</p>
</div>
{# Active(?) (or at least verified at some point) user, horray! #}
{% else %}
<a href="{{ request.urlgen('mediagoblin.moderation.users') }}"
class="return_to_panel button_action"
title="Return to Users Panel">
{% trans %}Return to Users Panel{% endtrans %}</a>
<h1>
{%- trans username=user.username %}{{ username }}'s profile{% endtrans -%}
{% if user_banned and user_banned.expiration_date %}
&mdash; BANNED until {{ user_banned.expiration_date }}
{% elif user_banned %}
&mdash; Banned Indefinitely
{% endif %}
</h1>
{% if not user.url and not user.bio %}
<div class="profile_sidebar empty_space">
<p>
{% trans -%}
This user hasn't filled in their profile (yet).
{%- endtrans %}
</p>
{% else %}
<div class="profile_sidebar">
{% include "mediagoblin/utils/profile.html" %}
{% if request.user and
(request.user.id == user.id or request.user.has_privilege('admin')) %}
<a href="{{ request.urlgen('mediagoblin.edit.profile',
user=user.username) }}">
{%- trans %}Edit profile{% endtrans -%}
</a>
{% endif %}
{% endif %}
<p>
<a href="{{ request.urlgen('mediagoblin.user_pages.collection_list',
user=user.username) }}">
{%- trans %}Browse collections{% endtrans -%}
</a>
</p>
</div>
{% endif %}
{% if user %}
<h2>{%- trans %}Active Reports on {% endtrans -%}{{ user.username }}</h2>
{% if reports.count() %}
<table class="admin_side_panel">
<tr>
<th>{%- trans %}Report ID{% endtrans -%}</th>
<th>{%- trans %}Reported Content{% endtrans -%}</th>
<th>{%- trans %}Description of Report{% endtrans -%}</th>
</tr>
{% for report in reports %}
<tr>
<td>
<img src="{{ request.staticdirect('/images/icon_clipboard.png') }}" />
<a href="{{ request.urlgen('mediagoblin.moderation.reports_detail',
report_id=report.id) }}">
{%- trans %}Report #{% endtrans -%}{{ report.id }}
</a>
</td>
<td>
{% if report.discriminator == "comment_report" %}
<a>{%- trans %}Reported Comment{% endtrans -%}</a>
{% elif report.discriminator == "media_report" %}
<a>{%- trans %}Reported Media Entry{% endtrans -%}</a>
{% endif %}
</td>
<td>{{ report.report_content[:21] }}
{% if report.report_content|count >20 %}...{% endif %}</td>
<td>{%- trans %}Resolve{% endtrans -%}</td>
</tr>
{% endfor %}
<tr><td></td><td></td>
</table>
{% else %}
{%- trans %}No active reports filed on {% endtrans -%} {{ user.username }}
{% endif %}
<span class="right_align">
<a href="{{ request.urlgen(
'mediagoblin.moderation.reports') }}?reported_user={{user.id}}">
{%- trans
username=user.username %}All reports on {{ username }}{% endtrans %}</a>
&middot;
<a href="{{ request.urlgen(
'mediagoblin.moderation.reports') }}?reporter={{user.id}}">
{%- trans
username=user.username %}All reports that {{ username }} has filed{% endtrans %}</a>
</span>
<span class=clear></span>
<h2>{{ user.username }}'s Privileges</h2>
<form method=POST action="{{ request.urlgen(
'mediagoblin.moderation.ban_or_unban',
user=user.username) }}" class="right_align">
{{ csrf_token }}
{% if request.user.has_privilege('admin') and not user_banned and
not user.id == request.user.id %}
{{ wtforms_util.render_divs(ban_form) }}
<input type=submit class="button_action"
value="{% trans %}Ban User{% endtrans %}"
id="ban_user_submit" />
{% elif request.user.has_privilege('admin') and
not user.id == request.user.id %}
<input type=submit class="button_action right_align"
value="{% trans %}UnBan User{% endtrans %}" />
{% endif %}
</form>
<form action="{{ request.urlgen('mediagoblin.moderation.give_or_take_away_privilege',
user=user.username) }}"
method=post >
<table class="admin_side_panel">
<tr>
<th>{% trans %}Privilege{% endtrans %}</th>
<th>{% trans %}User Has Privilege{% endtrans %}</th>
</tr>
{% for privilege in privileges %}
<tr>
<td>{{ privilege.privilege_name }}</td>
{% if privilege in user.all_privileges %}
<td class="user_with_privilege">
Yes{% else %}
<td class="user_without_privilege">
No{% endif %}
</td>
{% if request.user.has_privilege('admin') %}
<td>
{% if privilege in user.all_privileges %}
<input type=submit id="{{ privilege.privilege_name }}"
class="submit_button button_action"
value =" -" />
{% else %}
<input type=submit id="{{ privilege.privilege_name }}"
class="submit_button button_action"
value ="+" />
{% endif %}
</td>
{% endif %}
</tr>
{% endfor %}
</table>
{{ csrf_token }}
<input type=hidden name=privilege_name id=hidden_privilege_name />
</form>
{% endif %}
<script>
$(document).ready(function(){
$('.submit_button').click(function(){
$('#hidden_privilege_name').val($(this).attr('id'));
});
init_user_banned_form();
$('#ban_user_submit').click(function(){
submit_user_banned_form()
});
});
</script>
{% endblock %}

View File

@ -0,0 +1,97 @@
{#
# 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() %}
{% if not last_page == 1 %}
{% if 'p='~current_page in request.query_string %}
{% set query_string = request.query_string %}{% else %}
{% set query_string =
'p='~current_page~"&"+request.query_string %}
{% endif %}
<div class="right_align">
{% set first_vis = current_page-3 %}
{% set last_vis = current_page+3 %}
{% if 1 == current_page %}<b>1</b>{% else %}
<a href ="?{{ query_string.replace(
'p='~current_page,
'p='~1) }}">
1</a>{% endif %}
{% if first_vis > 1 %}...{% endif %}
{% for p in range(first_vis,last_vis+1) %}
{% if p > 1 and p < last_page and
current_page !=p %}
<a href="?{{ query_string.replace(
'p='~current_page,
'p='~p) }}">
{{ p }}</a>
{% elif p > 1 and p < last_page %}
<b>{{ p }}</b>
{% endif %}
{% endfor %}
{% if last_vis < last_page %}...{% endif %}
{% if last_page != current_page %}
<a href ="?{{ query_string.replace(
'p='~current_page,
'p='~last_page) }}">
{{ last_page }}</a>
{% else %}<b>{{ last_page }}</b>
{% endif %}
</div>
{% endif %}
<table class="admin_panel processing">
<tr>
<th>{% trans %}ID{% endtrans %}</th>
<th>{% trans %}Username{% endtrans %}</th>
<th>{% trans %}When Joined{% endtrans %}</th>
<th>{% trans %}# of Comments Posted{% endtrans %}</th>
</tr>
{% for user in user_list %}
<tr>
<td>{{ user.id }}</td>
<td>
<a href="{{ request.urlgen('mediagoblin.moderation.users_detail',
user= user.username) }}">
{{ user.username }}
</a>
</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 %}

View File

@ -0,0 +1,291 @@
{#
# 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 %}
Terms of Service
{% endblock %}
{% block mediagoblin_content -%}
{# <h2>The gist</h2>
# This is where you might insert your own particular rules, unique to your
# own website. Or your own worded summary.
#}
<h2>Terms of Service</h2>
The following terms and conditions govern all use of the
{{ app_config['html_title'] }} website and all content, services and products
available at or through the website (taken together, the Website). The Website
is owned and operated by Status.net (“Operator”). The Website is offered
subject to your acceptance without modification of all of the terms and
conditions contained herein and all other operating rules, policies
(including, without limitation, Operators Privacy Policy) and procedures
that may be published from time to time on this Site by Operator (collectively,
the “Agreement”).
Please read this Agreement carefully before accessing or using the Website.
By accessing or using any part of the web site, you agree to become bound by
the terms and conditions of this agreement. If you do not agree to all the
terms and conditions of this agreement, then you may not access the Website
or use any services. If these terms and conditions are considered an offer by
Operator, acceptance is expressly limited to these terms. The Website is
available only to individuals who are at least 13 years old.
<ol id="code_of_conduct_list">
<li><strong>Your {{ app_config['html_title'] }} Account and Site.</strong>
If you create a notice stream on the Website, you are responsible for
maintaining the security of your account and notice stream, and you are
fully responsible for all activities that occur under the account and any
other actions taken in connection with the notice stream. You must not
describe or assign keywords to your notice stream in a misleading or
unlawful manner,
including in a manner intended to trade on the name or reputation of
others, and Operator may change or remove any description or keyword that
it considers inappropriate or unlawful, or otherwise likely to cause
Operator liability. You must immediately notify Operator of any
unauthorized uses of your notice stream, your account or any other breaches
of security. Operator will not be liable for any acts or omissions by You,
including any damages of any kind incurred as a result of such acts or
omissions.
</li>
<li><strong>Responsibility of Contributors.</strong> If you operate a notice
stream, comment on a notice stream, post material to the Website, post
links on the Website, or otherwise make
(or allow any third party to make) material
available by means of the Website (any such material, “Content”), You are
entirely responsible for the content of, and any harm resulting from, that
Content. That is the case regardless of whether the Content in question
constitutes text, graphics, an audio file, or computer software. By making
Content available, you represent and warrant that:
<ul class="nested_sublist">
<li>the downloading, copying and use of the Content will not infringe
the proprietary rights, including but not limited to the copyright,
patent, trademark or trade secret rights, of any third party;
</li>
<li>if your employer has rights to intellectual property you create, you
have either (i) received permission from your employer to post or make
available the Content, including but not limited to any software, or
(ii) secured from your employer a waiver as to all rights in or to the
Content;
</li>
<li>you have fully complied with any third-party licenses relating to the
Content, and have done all things necessary to successfully pass
through to end users any required terms;
</li>
<li>the Content does not contain or install any viruses, worms, malware,
Trojan horses or other harmful or destructive content;
</li>
<li>the Content is not spam, and does not contain unethical or unwanted
commercial content designed to drive traffic to third party sites or
boost the search engine rankings of third party sites, or to further
unlawful acts (such as phishing) or mislead recipients as to the
source of the material (such as spoofing);
</li>
<li>if the Content is machine- or randomly-generated, it is for purposes
of direct entertainment, information and/or utility for you or other
users, and not for spam,
</li>
<li>the Content is not libelous or defamatory (more info on what that
means), does not contain threats or incite violence towards individuals
or entities, and does not violate the privacy or publicity rights of
any third party;
</li>
<li>your notice stream is not getting advertised via unwanted electronic
messages such as spam links on newsgroups, email lists, other notice
streams and web sites, and similar unsolicited promotional methods;
</li>
<li>your notice stream is not named in a manner that misleads your
readers into thinking that you are another person or company. For
example, your notice streams URL or name is not the name of a person
other than yourself or company other than your own; and
</li>
<li>you have, in the case of Content that includes computer code,
accurately categorized and/or described the type, nature, uses and
effects of the materials, whether requested to do so by Operator or
otherwise.</li>
</ul>
By submitting Content to Operator for inclusion on your Website, you grant
Operator a world-wide, royalty-free, and non-exclusive license to
reproduce, modify, adapt and publish the Content solely for the purpose of
displaying, distributing and promoting your notice stream.
By submitting Content to Operator for inclusion on your Website, you grant
all readers the right to use, re-use, modify and/or re-distribute the
Content under the terms of the Creative Commons Attribution 3.0.
If you delete Content, Operator will use reasonable efforts to remove it
from the Website, but you acknowledge that caching or references to the
Content may not be made immediately unavailable.
Without limiting any of those representations or warranties, Operator has
the right (though not the obligation) to, in Operators sole discretion
(i) refuse or remove any content that, in Operators reasonable opinion,
violates any Operator policy or is in any way harmful or objectionable, or
(ii) terminate or deny access to and use of the Website to any individual
or entity for any reason, in Operators sole discretion.
</li>
<li><strong>Responsibility of Website Visitors.</strong> Operator has not
reviewed, and cannot
review, all of the material, including computer software, posted to the
Website, and cannot therefore be responsible for that materials content,
use or effects. By operating the Website, Operator does not represent or
imply that it endorses the material there posted, or that it believes such
material to be accurate, useful or non-harmful. You are responsible for
taking precautions as necessary to protect yourself and your computer
systems from viruses, worms, Trojan horses, and other harmful or
destructive content. The Website may contain content that is offensive,
indecent, or otherwise objectionable, as well as content containing
technical inaccuracies, typographical mistakes, and other errors. The
Website may also contain material that violates the privacy or publicity
rights, or infringes the intellectual property and other proprietary
rights, of third parties, or the downloading, copying or use of which is
subject to additional terms and conditions, stated or unstated. Operator
disclaims any responsibility for any harm resulting from the use by
visitors of the Website, or from any downloading by those visitors of
content there posted.
</li>
<li><strong>Content Posted on Other Websites.</strong> We have not reviewed,
and cannot
review, all of the material, including computer software, made available
through the websites and webpages to which {{ app_config['html_title'] }}
links, and that link to {{ app_config['html_title'] }}. Operator does not
have any control over those external websites and webpages, and is not
responsible for their contents or their use. By linking to a external
website or webpage, Operator does not represent or imply that it endorses
such website or webpage. You are responsible for taking precautions as
necessary to protect yourself and your computer systems from viruses,
worms, Trojan horses, and other harmful or destructive content. Operator
disclaims any responsibility for any harm resulting from your use of
external websites and webpages.
</li>
<li><strong>Copyright Infringement and DMCA Policy.</strong> As Operator asks
others to
respect its intellectual property rights, it respects the intellectual
property rights of others. If you believe that material located on or
linked to by {{ app_config['html_title'] }} violates your copyright, you
are encouraged to notify Operator in accordance with Operators Digital
Millennium Copyright Act (”DMCA”) Policy. Operator will respond to all
such notices, including as required or appropriate by removing the
infringing material or disabling all links to the infringing material. In
the case of a visitor who may infringe or repeatedly infringes the
copyrights or other intellectual property rights of Operator or others,
Operator may, in its discretion, terminate or deny access to and use of
the Website. In the case of such termination, Operator will have no
obligation to provide a refund of any amounts previously paid to Operator.
</li>
<li><strong>Intellectual Property.</strong> This Agreement does not transfer
from Operator to
you any Operator or third party intellectual property, and all right,
title and interest in and to such property will remain (as between the
parties) solely with Operator. {{ app_config['html_title'] }}, the
{{ app_config['html_title'] }} logo, and all other trademarks, service
marks, graphics and logos used in connection with
{{ app_config['html_title'] }}, or the Website are trademarks or
registered trademarks of Operator or Operators licensors. Other
trademarks, service marks, graphics and logos used in connection with the
Website may be the trademarks of other third parties. Your use of the
Website grants you no right or license to reproduce or otherwise use any
Operator or third-party trademarks.
</li>
<li><strong>Changes.</strong> Operator reserves the right, at its sole
discretion, to modify
or replace any part of this Agreement. It is your responsibility to check
this Agreement periodically for changes. Your continued use of or access
to the Website following the posting of any changes to this Agreement
constitutes acceptance of those changes. Operator may also, in the future,
offer new services and/or features through the Website (including, the
release of new tools and resources). Such new features and/or services
shall be subject to the terms and conditions of this Agreement.
</li>
<li><strong>Termination.</strong> Operator may terminate your access to all
or any part of
the Website at any time, with or without cause, with or without notice,
effective immediately. If you wish to terminate this Agreement or your
{{ app_config['html_title'] }} account (if you have one), you may simply
discontinue using the Website. All provisions of this Agreement which by
their nature should survive termination shall survive termination,
including, without limitation, ownership provisions, warranty disclaimers,
indemnity and limitations of liability.
</li>
<li><strong>Disclaimer of Warranties.</strong> The Website is provided
“as is”. Operator and
its suppliers and licensors hereby disclaim all warranties of any kind,
express or implied, including, without limitation, the warranties of
merchantability, fitness for a particular purpose and non-infringement.
Neither Operator nor its suppliers and licensors, makes any warranty that
the Website will be error free or that access thereto will be continuous
or uninterrupted. If youre actually reading this, heres a treat. You
understand that you download from, or otherwise obtain content or services
through, the Website at your own discretion and risk.
</li>
<li><strong>Limitation of Liability.</strong> In no event will Operator, or
its suppliers or
licensors, be liable with respect to any subject matter of this agreement
under any contract, negligence, strict liability or other legal or
equitable theory for: (i) any special, incidental or consequential damages;
(ii) the cost of procurement or substitute products or services; (iii) for
interruption of use or loss or corruption of data; or (iv) for any amounts
that exceed the fees paid by you to Operator under this agreement during
the twelve (12) month period prior to the cause of action. Operator shall
have no liability for any failure or delay due to matters beyond their
reasonable control. The foregoing shall not apply to the extent prohibited
by applicable law.
</li>
<li><strong>General Representation and Warranty.</strong> You represent and
warrant that (i)
your use of the Website will be in strict accordance with the Operator
Privacy Policy, with this Agreement and with all applicable laws and
regulations (including without limitation any local laws or regulations in
your country, state, city, or other governmental area, regarding online
conduct and acceptable content, and including all applicable laws regarding
the transmission of technical data exported from the United States or the
country in which you reside) and (ii) your use of the Website will not
infringe or misappropriate the intellectual property rights of any third
party.
</li>
<li><strong>Indemnification.</strong> You agree to indemnify and hold
harmless Operator, its
contractors, and its licensors, and their respective directors, officers,
employees and agents from and against any and all claims and expenses,
including attorneys fees, arising out of your use of the Website,
including but not limited to out of your violation this Agreement.
</li>
<li><strong>Miscellaneous.</strong> This Agreement constitutes the entire
agreement between
Operator and you concerning the subject matter hereof, and they may only
be modified by a written amendment signed by an authorized executive of
Operator, or by the posting by Operator of a revised version. If any part
of this Agreement is held invalid or unenforceable, that part will be
construed to reflect the parties original intent, and the remaining
portions will remain in full force and effect. A waiver by either party of
any term or condition of this Agreement or any breach thereof, in any one
instance, will not waive such term or condition or any subsequent breach
thereof. You may assign your rights under this Agreement to any party that
consents to, and agrees to be bound by, its terms and conditions; Operator
may assign its rights under this Agreement without condition. This
Agreement will be binding upon and will inure to the benefit of the
parties, their successors and permitted assigns.
</li>
</ol>
Originally published by Automattic, Inc. as the WordPress.com Terms of Service
and made available by them under the Creative Commons Attribution-
ShareAlike 3.0 License. Modifications to remove reference to "VIP services",
rename "blog" to "notice stream", remove the choice-of-venue clause, and add
variables specific to instances of this software made by Control Yourself, Inc.
and made available under the terms of the same license.
{% endblock -%}

View File

@ -45,7 +45,7 @@
{%- endtrans %}
</h1>
{% if request.user and (collection.creator == request.user.id or
request.user.is_admin) %}
request.user.has_privilege(u'admin')) %}
{% set edit_url = request.urlgen('mediagoblin.edit.edit_collection',
user=collection.get_creator.username,
collection=collection.slug) %}

View File

@ -34,7 +34,7 @@
</h1>
{% if request.user %}
{% if request.user.status == 'active' %}
{% if request.user.has_privilege('active') %}
<p>
<a href="{{ request.urlgen('mediagoblin.submit.collection',
user=user.username) }}">

View File

@ -72,7 +72,7 @@
</h2>
{% if request.user and
(media.uploader == request.user.id or
request.user.is_admin) %}
request.user.has_privilege('admin')) %}
{% set edit_url = request.urlgen('mediagoblin.edit.edit_media',
user= media.get_uploader.username,
media_id=media.id) %}
@ -86,7 +86,7 @@
{% autoescape False %}
<p>{{ media.description_html }}</p>
{% endautoescape %}
{% if comments %}
{% if comments and request.user and request.user.has_privilege('commenter') %}
{% if app_config['allow_comments'] %}
<a
{% if not request.user %}
@ -146,6 +146,15 @@
{{ comment.content_html }}
{%- endautoescape %}
</div>
<div>
{% if app_config.allow_reporting %}
<a href="{{ request.urlgen('mediagoblin.user_pages.media_home.report_comment',
user=media.get_uploader.username,
media=media.slug_or_id,
comment=comment.id) }}">
{% trans %}Report{% endtrans %}</a>
{% endif %}
</div>
</li>
{% endfor %}
</ul>
@ -156,7 +165,7 @@
<div class="media_sidebar">
<h3>{% trans %}Added{% endtrans %}</h3>
<p><span title="{{ media.created.strftime("%I:%M%p %Y-%m-%d") }}">
{%- trans formatted_time=timesince(media.created) -%}
{%- trans formatted_time=timesince(media.created) -%}
{{ formatted_time }} ago
{%- endtrans -%}
</span></p>
@ -170,6 +179,10 @@
{% include "mediagoblin/utils/collections.html" %}
{% if app_config.allow_reporting %}
{% include "mediagoblin/utils/report.html" %}
{% endif %}
{% include "mediagoblin/utils/license.html" %}
{% include "mediagoblin/utils/exif.html" %}
@ -189,7 +202,7 @@
{%- if app_config['allow_attachments']
and request.user
and (media.uploader == request.user.id
or request.user.is_admin) %}
or request.user.has_privilege('admin')) %}
{%- if not media.attachment_files|count %}
<h3>{% trans %}Attachments{% endtrans %}</h3>
{%- endif %}

View File

@ -0,0 +1,83 @@
{#
# 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" %}
{%- import "/mediagoblin/utils/wtforms.html" as wtforms_util %}
{%- block mediagoblin_content -%}
{% trans %}<h2>File a Report</h2>{% endtrans %}
<form action="" method=POST >
{% if comment is defined %}
<h3>{% trans %}Reporting this Comment{% endtrans %}</h3>
{%- set comment_author = comment.get_author %}
{%- set comment_author_url = request.urlgen(
'mediagoblin.user_pages.user_home',
user=comment_author.username) %}
{%- set comment_url = request.urlgen(
'mediagoblin.user_pages.media_home.view_comment',
comment=comment.id,
user=media.get_uploader.username,
media=media.slug_or_id) %}
<div id="comment-{{ comment.id }}"
class="comment_wrapper">
<div class="comment_author">
<img
src="{{ request.staticdirect('/images/icon_comment.png') }}" />
<a href="{{ comment_author_url }}"
class="comment_authorlink">
{{- comment_author.username -}}
</a>
<a href="{{ comment_url }}"
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>
{% elif media is defined %}
<h3>{% trans %}Reporting this Media Entry{% endtrans %}</h3>
<div class="media_thumbnail">
<a href="{{ request.urlgen('mediagoblin.user_pages.media_home',
user=media.get_uploader.username,
media=media.slug_or_id) }}">
<img src="{{ media.thumb_url }}"/></a>
<a href="{{ request.urlgen('mediagoblin.user_pages.media_home',
user=media.get_uploader.username,
media=media.slug_or_id) }}"
class=thumb_entry_title>{{ media.title }}</a>
</div>
<div class=clear></div>
{%- trans user_url = request.urlgen('mediagoblin.user_pages.user_home', user=media.get_uploader.username),
username = media.get_uploader.username %}
❖ Published by <a href="{{ user_url }}"
class="comment_authorlink">{{ username }}</a>
{% endtrans %}
{%- endif %}
{{- wtforms_util.render_divs(form) }}
{{ csrf_token }}
<input type=submit value="{% trans %}File Report {% endtrans %}" />
</form>
{% endblock %}

View File

@ -67,7 +67,7 @@
<div class="profile_sidebar">
{% include "mediagoblin/utils/profile.html" %}
{% if request.user and
(request.user.id == user.id or request.user.is_admin) %}
(request.user.id == user.id or request.user.has_privilege('admin')) %}
<a href="{{ request.urlgen('mediagoblin.edit.profile',
user=user.username) }}">
{%- trans %}Edit profile{% endtrans -%}

View File

@ -33,7 +33,7 @@
{% block mediagoblin_content -%}
{# User exists, but needs verification #}
{% if user.status == "needs_email_verification" %}
{% if not user.has_privilege('active') %}
{% if user == request.user %}
{# this should only be visible when you are this user #}
<div class="form_box">

View File

@ -39,7 +39,7 @@
{% endif %}
{% if request.user and
(item.in_collection.creator == request.user.id or
request.user.is_admin) %}
request.user.has_privilege(u'admin')) %}
{%- set remove_url=request.urlgen(
'mediagoblin.user_pages.collection_item_confirm_remove',
user=item.in_collection.get_creator.username,

View File

@ -1,3 +1,4 @@
{#
# GNU MediaGoblin -- federated, autonomous media hosting
# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
#
@ -13,8 +14,15 @@
#
# 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/>.
#}
admin_routes = [
('mediagoblin.admin.panel',
'/panel',
'mediagoblin.admin.views:admin_processing_panel')]
{% block report_content -%}
<p>
<a href="{{ request.urlgen('mediagoblin.user_pages.media_home.report_media',
user=media.get_uploader.username,
media=media.slug_or_id) }}"
class="button_action" id="button_reportmedia" title="Report media">
{% trans %}Report media{% endtrans %}
</a>
</p>
{% endblock %}

View File

@ -35,7 +35,8 @@ class TestAPI(object):
self.db = mg_globals.database
self.user_password = u'4cc355_70k3N'
self.user = fixture_add_user(u'joapi', self.user_password)
self.user = fixture_add_user(u'joapi', self.user_password,
privileges=[u'active',u'uploader'])
def login(self, test_app):
test_app.post(

View File

@ -84,22 +84,26 @@ def test_register_views(test_app):
template.clear_test_template_context()
response = test_app.post(
'/auth/register/', {
'username': u'happygirl',
'password': 'iamsohappy',
'email': 'happygrrl@example.org'})
'username': u'angrygirl',
'password': 'iamsoangry',
'email': 'angrygrrl@example.org'})
response.follow()
## Did we redirect to the proper page? Use the right template?
assert urlparse.urlsplit(response.location)[2] == '/u/happygirl/'
assert urlparse.urlsplit(response.location)[2] == '/u/angrygirl/'
assert 'mediagoblin/user_pages/user_nonactive.html' in template.TEMPLATE_TEST_CONTEXT
## Make sure user is in place
new_user = mg_globals.database.User.query.filter_by(
username=u'happygirl').first()
username=u'angrygirl').first()
assert new_user
assert new_user.status == u'needs_email_verification'
assert new_user.email_verified == False
## Make sure that the proper privileges are granted on registration
assert new_user.has_privilege(u'commenter')
assert new_user.has_privilege(u'uploader')
assert new_user.has_privilege(u'reporter')
assert not new_user.has_privilege(u'active')
## Make sure user is logged in
request = template.TEMPLATE_TEST_CONTEXT[
'mediagoblin/user_pages/user_nonactive.html']['request']
@ -108,7 +112,7 @@ def test_register_views(test_app):
## Make sure we get email confirmation, and try verifying
assert len(mail.EMAIL_TEST_INBOX) == 1
message = mail.EMAIL_TEST_INBOX.pop()
assert message['To'] == 'happygrrl@example.org'
assert message['To'] == 'angrygrrl@example.org'
email_context = template.TEMPLATE_TEST_CONTEXT[
'mediagoblin/auth/verification_email.txt']
assert email_context['verification_url'] in message.get_payload(decode=True)
@ -130,10 +134,8 @@ def test_register_views(test_app):
# assert context['verification_successful'] == True
# TODO: Would be good to test messages here when we can do so...
new_user = mg_globals.database.User.query.filter_by(
username=u'happygirl').first()
username=u'angrygirl').first()
assert new_user
assert new_user.status == u'needs_email_verification'
assert new_user.email_verified == False
## Verify the email activation works
template.clear_test_template_context()
@ -144,10 +146,8 @@ def test_register_views(test_app):
# assert context['verification_successful'] == True
# TODO: Would be good to test messages here when we can do so...
new_user = mg_globals.database.User.query.filter_by(
username=u'happygirl').first()
username=u'angrygirl').first()
assert new_user
assert new_user.status == u'active'
assert new_user.email_verified == True
# Uniqueness checks
# -----------------
@ -155,9 +155,9 @@ def test_register_views(test_app):
template.clear_test_template_context()
response = test_app.post(
'/auth/register/', {
'username': u'happygirl',
'password': 'iamsohappy2',
'email': 'happygrrl2@example.org'})
'username': u'angrygirl',
'password': 'iamsoangry2',
'email': 'angrygrrl2@example.org'})
context = template.TEMPLATE_TEST_CONTEXT[
'mediagoblin/auth/register.html']
@ -172,7 +172,7 @@ def test_register_views(test_app):
template.clear_test_template_context()
response = test_app.post(
'/auth/forgot_password/',
{'username': u'happygirl'})
{'username': u'angrygirl'})
response.follow()
## Did we redirect to the proper page? Use the right template?
@ -182,7 +182,7 @@ def test_register_views(test_app):
## Make sure link to change password is sent by email
assert len(mail.EMAIL_TEST_INBOX) == 1
message = mail.EMAIL_TEST_INBOX.pop()
assert message['To'] == 'happygrrl@example.org'
assert message['To'] == 'angrygrrl@example.org'
email_context = template.TEMPLATE_TEST_CONTEXT[
'mediagoblin/plugins/basic_auth/fp_verification_email.txt']
#TODO - change the name of verification_url to something forgot-password-ish
@ -212,7 +212,7 @@ def test_register_views(test_app):
template.clear_test_template_context()
response = test_app.post(
'/auth/forgot_password/verify/', {
'password': 'iamveryveryhappy',
'password': 'iamveryveryangry',
'token': parsed_get_params['token']})
response.follow()
assert 'mediagoblin/auth/login.html' in template.TEMPLATE_TEST_CONTEXT
@ -221,8 +221,8 @@ def test_register_views(test_app):
template.clear_test_template_context()
response = test_app.post(
'/auth/login/', {
'username': u'happygirl',
'password': 'iamveryveryhappy'})
'username': u'angrygirl',
'password': 'iamveryveryangry'})
# User should be redirected
response.follow()
@ -235,7 +235,7 @@ def test_authentication_views(test_app):
Test logging in and logging out
"""
# Make a new user
test_user = fixture_add_user(active_user=False)
test_user = fixture_add_user()
# Get login
@ -332,7 +332,6 @@ def test_authentication_views(test_app):
'next' : '/u/chris/'})
assert urlparse.urlsplit(response.location)[2] == '/u/chris/'
@pytest.fixture()
def authentication_disabled_app(request):
return get_app(
@ -344,6 +343,7 @@ def authentication_disabled_app(request):
def test_authentication_disabled_app(authentication_disabled_app):
# app.auth should = false
assert mg_globals
assert mg_globals.app.auth is False
# Try to visit register page

View File

@ -27,7 +27,8 @@ class TestUserEdit(object):
def setup(self):
# set up new user
self.user_password = u'toast'
self.user = fixture_add_user(password = self.user_password)
self.user = fixture_add_user(password = self.user_password,
privileges=[u'active'])
def login(self, test_app):
test_app.post(
@ -52,7 +53,8 @@ class TestUserEdit(object):
# deleted too. Perhaps in submission test?
#Restore user at end of test
self.user = fixture_add_user(password = self.user_password)
self.user = fixture_add_user(password = self.user_password,
privileges=[u'active'])
self.login(test_app)
@ -80,7 +82,8 @@ class TestUserEdit(object):
assert test_user.url == u'http://dustycloud.org/'
# change a different user than the logged in (should fail with 403)
fixture_add_user(username=u"foo")
fixture_add_user(username=u"foo",
privileges=[u'active'])
res = test_app.post(
'/u/foo/edit/', {
'bio': u'I love toast!',

View File

@ -18,7 +18,7 @@
# methods, and so it makes sense to test them here.
from mediagoblin.db.base import Session
from mediagoblin.db.models import MediaEntry
from mediagoblin.db.models import MediaEntry, User, Privilege
from mediagoblin.tests.tools import fixture_add_user
@ -47,7 +47,7 @@ class TestMediaEntrySlugs(object):
entry.id = this_id
entry.uploader = uploader or self.chris_user.id
entry.media_type = u'image'
if save:
entry.save()
@ -99,7 +99,7 @@ class TestMediaEntrySlugs(object):
u"Beware, I exist!!", this_id=9000, save=False)
entry.generate_slug()
assert entry.slug == u"beware-i-exist-test"
_real_test()
def test_existing_slug_cant_use_id_extra_junk(self, test_app):
@ -151,6 +151,44 @@ class TestMediaEntrySlugs(object):
qbert_entry.generate_slug()
assert qbert_entry.slug is None
class TestUserHasPrivilege:
def _setup(self):
fixture_add_user(u'natalie',
privileges=[u'admin',u'moderator',u'active'])
fixture_add_user(u'aeva',
privileges=[u'moderator',u'active'])
self.natalie_user = User.query.filter(
User.username==u'natalie').first()
self.aeva_user = User.query.filter(
User.username==u'aeva').first()
def test_privilege_added_correctly(self, test_app):
self._setup()
admin = Privilege.query.filter(
Privilege.privilege_name == u'admin').one()
# first make sure the privileges were added successfully
assert admin in self.natalie_user.all_privileges
assert admin not in self.aeva_user.all_privileges
def test_user_has_privilege_one(self, test_app):
self._setup()
# then test out the user.has_privilege method for one privilege
assert not self.natalie_user.has_privilege(u'commenter')
assert self.aeva_user.has_privilege(u'active')
def test_user_has_privileges_multiple(self, test_app):
self._setup()
# when multiple args are passed to has_privilege, the method returns
# True if the user has ANY of the privileges
assert self.natalie_user.has_privilege(u'admin',u'commenter')
assert self.aeva_user.has_privilege(u'moderator',u'active')
assert not self.natalie_user.has_privilege(u'commenter',u'uploader')
def test_media_data_init(test_app):
Session.rollback()
@ -165,3 +203,4 @@ def test_media_data_init(test_app):
obj_in_session += 1
print repr(obj)
assert obj_in_session == 0

View File

@ -0,0 +1,242 @@
# GNU MediaGoblin -- federated, autonomous media hosting
# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import pytest
from mediagoblin.tests.tools import (fixture_add_user,
fixture_add_comment_report, fixture_add_comment)
from mediagoblin.db.models import User, CommentReport, MediaComment, UserBan
from mediagoblin.tools import template, mail
from webtest import AppError
class TestModerationViews:
@pytest.fixture(autouse=True)
def _setup(self, test_app):
self.test_app = test_app
fixture_add_user(u'admin',
privileges=[u'admin',u'active'])
fixture_add_user(u'moderator',
privileges=[u'moderator',u'active'])
fixture_add_user(u'regular',
privileges=[u'active',u'commenter'])
self.query_for_users()
def login(self, username):
self.test_app.post(
'/auth/login/', {
'username': username,
'password': 'toast'})
self.query_for_users()
def logout(self):
self.test_app.get('/auth/logout/')
self.query_for_users()
def query_for_users(self):
self.admin_user = User.query.filter(User.username==u'admin').first()
self.mod_user = User.query.filter(User.username==u'moderator').first()
self.user = User.query.filter(User.username==u'regular').first()
def do_post(self, data, *context_keys, **kwargs):
url = kwargs.pop('url', '/submit/')
do_follow = kwargs.pop('do_follow', False)
template.clear_test_template_context()
response = self.test_app.post(url, data, **kwargs)
if do_follow:
response.follow()
context_data = template.TEMPLATE_TEST_CONTEXT
for key in context_keys:
context_data = context_data[key]
return response, context_data
def testGiveOrTakeAwayPrivileges(self):
self.login(u'admin')
# First, test an admin taking away a privilege from a user
#----------------------------------------------------------------------
response, context = self.do_post({'privilege_name':u'commenter'},
url='/mod/users/{0}/privilege/'.format(self.user.username))
assert response.status == '302 FOUND'
self.query_for_users()
assert not self.user.has_privilege(u'commenter')
# Then, test an admin giving a privilege to a user
#----------------------------------------------------------------------
response, context = self.do_post({'privilege_name':u'commenter'},
url='/mod/users/{0}/privilege/'.format(self.user.username))
assert response.status == '302 FOUND'
self.query_for_users()
assert self.user.has_privilege(u'commenter')
# Then, test a mod trying to take away a privilege from a user
# they are not allowed to do this, so this will raise an error
#----------------------------------------------------------------------
self.logout()
self.login(u'moderator')
with pytest.raises(AppError) as excinfo:
response, context = self.do_post({'privilege_name':u'commenter'},
url='/mod/users/{0}/privilege/'.format(self.user.username))
assert 'Bad response: 403 FORBIDDEN' in str(excinfo)
self.query_for_users()
assert self.user.has_privilege(u'commenter')
def testReportResolution(self):
self.login(u'moderator')
# First, test a moderators taking away a user's privilege in response
# to a reported comment
#----------------------------------------------------------------------
fixture_add_comment_report(reported_user=self.user)
comment_report = CommentReport.query.filter(
CommentReport.reported_user==self.user).first()
response = self.test_app.get('/mod/reports/{0}/'.format(
comment_report.id))
assert response.status == '200 OK'
self.query_for_users()
comment_report = CommentReport.query.filter(
CommentReport.reported_user==self.user).first()
response, context = self.do_post({'action_to_resolve':[u'takeaway'],
'take_away_privileges':[u'commenter'],
'targeted_user':self.user.id},
url='/mod/reports/{0}/'.format(comment_report.id))
self.query_for_users()
comment_report = CommentReport.query.filter(
CommentReport.reported_user==self.user).first()
assert response.status == '302 FOUND'
assert not self.user.has_privilege(u'commenter')
assert comment_report.is_archived_report() is True
fixture_add_comment_report(reported_user=self.user)
comment_report = CommentReport.query.filter(
CommentReport.reported_user==self.user).first()
# Then, test a moderator sending an email to a user in response to a
# reported comment
#----------------------------------------------------------------------
self.query_for_users()
response, context = self.do_post({'action_to_resolve':[u'sendmessage'],
'message_to_user':'This is your last warning, regular....',
'targeted_user':self.user.id},
url='/mod/reports/{0}/'.format(comment_report.id))
self.query_for_users()
comment_report = CommentReport.query.filter(
CommentReport.reported_user==self.user).first()
assert response.status == '302 FOUND'
assert mail.EMAIL_TEST_MBOX_INBOX == [{'to': [u'regular@example.com'],
'message': 'Content-Type: text/plain; charset="utf-8"\n\
MIME-Version: 1.0\nContent-Transfer-Encoding: base64\nSubject: Warning from- \
moderator \nFrom: notice@mediagoblin.example.org\nTo: regular@example.com\n\n\
VGhpcyBpcyB5b3VyIGxhc3Qgd2FybmluZywgcmVndWxhci4uLi4=\n',
'from': 'notice@mediagoblin.example.org'}]
assert comment_report.is_archived_report() is True
# Then test a moderator banning a user AND a moderator deleting the
# offending comment. This also serves as a test for taking multiple
# actions to resolve a report
#----------------------------------------------------------------------
self.query_for_users()
fixture_add_comment(author=self.user.id,
comment=u'Comment will be removed')
test_comment = MediaComment.query.filter(
MediaComment.author==self.user.id).first()
fixture_add_comment_report(comment=test_comment,
reported_user=self.user)
comment_report = CommentReport.query.filter(
CommentReport.comment==test_comment).filter(
CommentReport.resolved==None).first()
response, context = self.do_post(
{'action_to_resolve':[u'userban', u'delete'],
'targeted_user':self.user.id,
'why_user_was_banned':u'',
'user_banned_until':u''},
url='/mod/reports/{0}/'.format(comment_report.id))
assert response.status == '302 FOUND'
self.query_for_users()
test_user_ban = UserBan.query.filter(
UserBan.user_id == self.user.id).first()
assert test_user_ban is not None
test_comment = MediaComment.query.filter(
MediaComment.author==self.user.id).first()
assert test_comment is None
# Then, test what happens when a moderator attempts to punish an admin
# from a reported comment on an admin.
#----------------------------------------------------------------------
fixture_add_comment_report(reported_user=self.admin_user)
comment_report = CommentReport.query.filter(
CommentReport.reported_user==self.admin_user).filter(
CommentReport.resolved==None).first()
response, context = self.do_post({'action_to_resolve':[u'takeaway'],
'take_away_privileges':[u'active'],
'targeted_user':self.admin_user.id},
url='/mod/reports/{0}/'.format(comment_report.id))
self.query_for_users()
assert response.status == '200 OK'
assert self.admin_user.has_privilege(u'active')
def testAllModerationViews(self):
self.login(u'moderator')
username = self.user.username
self.query_for_users()
fixture_add_comment_report(reported_user=self.admin_user)
response = self.test_app.get('/mod/reports/')
assert response.status == "200 OK"
response = self.test_app.get('/mod/reports/1/')
assert response.status == "200 OK"
response = self.test_app.get('/mod/users/')
assert response.status == "200 OK"
user_page_url = '/mod/users/{0}/'.format(username)
response = self.test_app.get(user_page_url)
assert response.status == "200 OK"
self.test_app.get('/mod/media/')
assert response.status == "200 OK"
def testBanUnBanUser(self):
self.login(u'admin')
username = self.user.username
user_id = self.user.id
ban_url = '/mod/users/{0}/ban/'.format(username)
response, context = self.do_post({
'user_banned_until':u'',
'why_user_was_banned':u'Because I said so'},
url=ban_url)
assert response.status == "302 FOUND"
user_banned = UserBan.query.filter(UserBan.user_id==user_id).first()
assert user_banned is not None
assert user_banned.expiration_date is None
assert user_banned.reason == u'Because I said so'
response, context = self.do_post({},
url=ban_url)
assert response.status == "302 FOUND"
user_banned = UserBan.query.filter(UserBan.user_id==user_id).first()
assert user_banned is None

View File

@ -38,7 +38,7 @@ class TestNotifications:
# TODO: Possibly abstract into a decorator like:
# @as_authenticated_user('chris')
self.test_user = fixture_add_user()
self.test_user = fixture_add_user(privileges=[u'active',u'commenter'])
self.current_user = None
@ -75,7 +75,10 @@ class TestNotifications:
'''
user = fixture_add_user('otherperson', password='nosreprehto',
wants_comment_notification=wants_email)
wants_comment_notification=wants_email,
privileges=[u'active',u'commenter'])
assert user.wants_comment_notification == wants_email
user_id = user.id
@ -124,6 +127,7 @@ otherperson@example.com\n\nSGkgb3RoZXJwZXJzb24sCmNocmlzIGNvbW1lbnRlZCBvbiB5b3VyI
else:
assert mail.EMAIL_TEST_MBOX_INBOX == []
# Save the ids temporarily because of DetachedInstanceError
notification_id = notification.id
comment_id = notification.subject.id
@ -153,7 +157,8 @@ otherperson@example.com\n\nSGkgb3RoZXJwZXJzb24sCmNocmlzIGNvbW1lbnRlZCBvbiB5b3VyI
def test_mark_all_comment_notifications_seen(self):
""" Test that mark_all_comments_seen works"""
user = fixture_add_user('otherperson', password='nosreprehto')
user = fixture_add_user('otherperson', password='nosreprehto',
privileges=[u'active'])
media_entry = fixture_media_entry(uploader=user.id, state=u'processed')

View File

@ -38,7 +38,8 @@ class TestOAuth(object):
self.pman = pluginapi.PluginManager()
self.user_password = u'4cc355_70k3N'
self.user = fixture_add_user(u'joauth', self.user_password)
self.user = fixture_add_user(u'joauth', self.user_password,
privileges=[u'active'])
self.login()

View File

@ -238,7 +238,7 @@ class TestOpenIDPlugin(object):
def test_add_delete(self, openid_plugin_app):
"""Test adding and deleting openids"""
# Add user
test_user = fixture_add_user(password='')
test_user = fixture_add_user(password='', privileges=[u'active'])
openid = OpenIDUserURL()
openid.openid_url = 'http://real.myopenid.com'
openid.user_id = test_user.id

View File

@ -22,6 +22,7 @@ pytest.importorskip("requests")
from mediagoblin import mg_globals
from mediagoblin.db.base import Session
from mediagoblin.db.models import Privilege
from mediagoblin.tests.tools import get_app
from mediagoblin.tools import template
@ -112,8 +113,9 @@ class TestPersonaPlugin(object):
# Get user and detach from session
test_user = mg_globals.database.User.query.filter_by(
username=u'chris').first()
test_user.email_verified = True
test_user.status = u'active'
active_privilege = Privilege.query.filter(
Privilege.privilege_name==u'active').first()
test_user.all_privileges.append(active_privilege)
test_user.save()
test_user = mg_globals.database.User.query.filter_by(
username=u'chris').first()

View File

@ -0,0 +1,205 @@
# GNU MediaGoblin -- federated, autonomous media hosting
# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import pytest
from datetime import date, timedelta
from webtest import AppError
from mediagoblin.tests.tools import fixture_add_user, fixture_media_entry
from mediagoblin.db.models import User, UserBan
from mediagoblin.tools import template
from .resources import GOOD_JPG
class TestPrivilegeFunctionality:
@pytest.fixture(autouse=True)
def _setup(self, test_app):
self.test_app = test_app
fixture_add_user(u'alex',
privileges=[u'admin',u'active'])
fixture_add_user(u'meow',
privileges=[u'moderator',u'active',u'reporter'])
fixture_add_user(u'natalie',
privileges=[u'active'])
self.query_for_users()
def login(self, username):
self.test_app.post(
'/auth/login/', {
'username': username,
'password': 'toast'})
self.query_for_users()
def logout(self):
self.test_app.get('/auth/logout/')
self.query_for_users()
def do_post(self, data, *context_keys, **kwargs):
url = kwargs.pop('url', '/submit/')
do_follow = kwargs.pop('do_follow', False)
template.clear_test_template_context()
response = self.test_app.post(url, data, **kwargs)
if do_follow:
response.follow()
context_data = template.TEMPLATE_TEST_CONTEXT
for key in context_keys:
context_data = context_data[key]
return response, context_data
def query_for_users(self):
self.admin_user = User.query.filter(User.username==u'alex').first()
self.mod_user = User.query.filter(User.username==u'meow').first()
self.user = User.query.filter(User.username==u'natalie').first()
def testUserBanned(self):
self.login(u'natalie')
uid = self.user.id
# First, test what happens when a user is banned indefinitely
#----------------------------------------------------------------------
user_ban = UserBan(user_id=uid,
reason=u'Testing whether user is banned',
expiration_date=None)
user_ban.save()
response = self.test_app.get('/')
assert response.status == "200 OK"
assert "You are Banned" in response.body
# Then test what happens when that ban has an expiration date which
# hasn't happened yet
#----------------------------------------------------------------------
user_ban = UserBan.query.get(uid)
user_ban.delete()
user_ban = UserBan(user_id=uid,
reason=u'Testing whether user is banned',
expiration_date= date.today() + timedelta(days=20))
user_ban.save()
response = self.test_app.get('/')
assert response.status == "200 OK"
assert "You are Banned" in response.body
# Then test what happens when that ban has an expiration date which
# has already happened
#----------------------------------------------------------------------
user_ban = UserBan.query.get(uid)
user_ban.delete()
exp_date = date.today() - timedelta(days=20)
user_ban = UserBan(user_id=uid,
reason=u'Testing whether user is banned',
expiration_date= exp_date)
user_ban.save()
response = self.test_app.get('/')
assert response.status == "302 FOUND"
assert not "You are Banned" in response.body
def testVariousPrivileges(self):
# The various actions that require privileges (ex. reporting,
# commenting, moderating...) are tested in other tests. This method
# will be used to ensure that those actions are impossible for someone
# without the proper privileges.
# For other tests that show what happens when a user has the proper
# privileges, check out:
# tests/test_moderation.py moderator
# tests/test_notifications.py commenter
# tests/test_reporting.py reporter
# tests/test_submission.py uploader
#----------------------------------------------------------------------
self.login(u'natalie')
# First test the get and post requests of submission/uploading
#----------------------------------------------------------------------
with pytest.raises(AppError) as excinfo:
response = self.test_app.get('/submit/')
assert 'Bad response: 403 FORBIDDEN' in str(excinfo)
with pytest.raises(AppError) as excinfo:
response = self.do_post({'upload_files':[('file',GOOD_JPG)],
'title':u'Normal Upload 1'},
url='/submit/')
assert 'Bad response: 403 FORBIDDEN' in str(excinfo)
# Test that a user cannot comment without the commenter privilege
#----------------------------------------------------------------------
self.query_for_users()
media_entry = fixture_media_entry(uploader=self.admin_user.id,
state=u'processed')
media_entry_id = media_entry.id
media_uri_id = '/u/{0}/m/{1}/'.format(self.admin_user.username,
media_entry.id)
media_uri_slug = '/u/{0}/m/{1}/'.format(self.admin_user.username,
media_entry.slug)
response = self.test_app.get(media_uri_slug)
assert not "Add a comment" in response.body
self.query_for_users()
with pytest.raises(AppError) as excinfo:
response = self.test_app.post(
media_uri_id + 'comment/add/',
{'comment_content': u'Test comment #42'})
assert 'Bad response: 403 FORBIDDEN' in str(excinfo)
# Test that a user cannot report without the reporter privilege
#----------------------------------------------------------------------
with pytest.raises(AppError) as excinfo:
response = self.test_app.get(media_uri_slug+"report/")
assert 'Bad response: 403 FORBIDDEN' in str(excinfo)
with pytest.raises(AppError) as excinfo:
response = self.do_post(
{'report_reason':u'Testing Reports #1',
'reporter_id':u'3'},
url=(media_uri_slug+"report/"))
assert 'Bad response: 403 FORBIDDEN' in str(excinfo)
# Test that a user cannot access the moderation pages w/o moderator
# or admin privileges
#----------------------------------------------------------------------
with pytest.raises(AppError) as excinfo:
response = self.test_app.get("/mod/users/")
assert 'Bad response: 403 FORBIDDEN' in str(excinfo)
with pytest.raises(AppError) as excinfo:
response = self.test_app.get("/mod/reports/")
assert 'Bad response: 403 FORBIDDEN' in str(excinfo)
with pytest.raises(AppError) as excinfo:
response = self.test_app.get("/mod/media/")
assert 'Bad response: 403 FORBIDDEN' in str(excinfo)
with pytest.raises(AppError) as excinfo:
response = self.test_app.get("/mod/users/1/")
assert 'Bad response: 403 FORBIDDEN' in str(excinfo)
with pytest.raises(AppError) as excinfo:
response = self.test_app.get("/mod/reports/1/")
assert 'Bad response: 403 FORBIDDEN' in str(excinfo)
self.query_for_users()
with pytest.raises(AppError) as excinfo:
response, context = self.do_post({'action_to_resolve':[u'takeaway'],
'take_away_privileges':[u'active'],
'targeted_user':self.admin_user.id},
url='/mod/reports/1/')
self.query_for_users()
assert 'Bad response: 403 FORBIDDEN' in str(excinfo)

View File

@ -0,0 +1,167 @@
# GNU MediaGoblin -- federated, autonomous media hosting
# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import pytest
from mediagoblin.tools import template
from mediagoblin.tests.tools import (fixture_add_user, fixture_media_entry,
fixture_add_comment, fixture_add_comment_report)
from mediagoblin.db.models import (MediaReport, CommentReport, User,
MediaComment)
class TestReportFiling:
@pytest.fixture(autouse=True)
def _setup(self, test_app):
self.test_app = test_app
fixture_add_user(u'allie',
privileges=[u'reporter',u'active'])
fixture_add_user(u'natalie',
privileges=[u'active', u'moderator'])
def login(self, username):
self.test_app.post(
'/auth/login/', {
'username': username,
'password': 'toast'})
def logout(self):
self.test_app.get('/auth/logout/')
def do_post(self, data, *context_keys, **kwargs):
url = kwargs.pop('url', '/submit/')
do_follow = kwargs.pop('do_follow', False)
template.clear_test_template_context()
response = self.test_app.post(url, data, **kwargs)
if do_follow:
response.follow()
context_data = template.TEMPLATE_TEST_CONTEXT
for key in context_keys:
context_data = context_data[key]
return response, context_data
def query_for_users(self):
return (User.query.filter(User.username==u'allie').first(),
User.query.filter(User.username==u'natalie').first())
def testMediaReports(self):
self.login(u'allie')
allie_user, natalie_user = self.query_for_users()
allie_id = allie_user.id
media_entry = fixture_media_entry(uploader=natalie_user.id,
state=u'processed')
mid = media_entry.id
media_uri_slug = '/u/{0}/m/{1}/'.format(natalie_user.username,
media_entry.slug)
response = self.test_app.get(media_uri_slug + "report/")
assert response.status == "200 OK"
response, context = self.do_post(
{'report_reason':u'Testing Media Report',
'reporter_id':unicode(allie_id)},url= media_uri_slug + "report/")
assert response.status == "302 FOUND"
media_report = MediaReport.query.first()
allie_user, natalie_user = self.query_for_users()
assert media_report is not None
assert media_report.report_content == u'Testing Media Report'
assert media_report.reporter_id == allie_id
assert media_report.reported_user_id == natalie_user.id
assert media_report.created is not None
assert media_report.discriminator == 'media_report'
def testCommentReports(self):
self.login(u'allie')
allie_user, natalie_user = self.query_for_users()
allie_id = allie_user.id
media_entry = fixture_media_entry(uploader=natalie_user.id,
state=u'processed')
mid = media_entry.id
fixture_add_comment(media_entry=mid,
author=natalie_user.id)
comment = MediaComment.query.first()
comment_uri_slug = '/u/{0}/m/{1}/c/{2}/'.format(natalie_user.username,
media_entry.slug,
comment.id)
response = self.test_app.get(comment_uri_slug + "report/")
assert response.status == "200 OK"
response, context = self.do_post({
'report_reason':u'Testing Comment Report',
'reporter_id':unicode(allie_id)},url= comment_uri_slug + "report/")
assert response.status == "302 FOUND"
comment_report = CommentReport.query.first()
allie_user, natalie_user = self.query_for_users()
assert comment_report is not None
assert comment_report.report_content == u'Testing Comment Report'
assert comment_report.reporter_id == allie_id
assert comment_report.reported_user_id == natalie_user.id
assert comment_report.created is not None
assert comment_report.discriminator == 'comment_report'
def testArchivingReports(self):
self.login(u'natalie')
allie_user, natalie_user = self.query_for_users()
allie_id, natalie_id = allie_user.id, natalie_user.id
fixture_add_comment(author=allie_user.id,
comment=u'Comment will be removed')
test_comment = MediaComment.query.filter(
MediaComment.author==allie_user.id).first()
fixture_add_comment_report(comment=test_comment,
reported_user=allie_user,
report_content=u'Testing Archived Reports #1',
reporter=natalie_user)
comment_report = CommentReport.query.filter(
CommentReport.reported_user==allie_user).first()
assert comment_report.report_content == u'Testing Archived Reports #1'
response, context = self.do_post(
{'action_to_resolve':[u'userban', u'delete'],
'targeted_user':allie_user.id,
'resolution_content':u'This is a test of archiving reports.'},
url='/mod/reports/{0}/'.format(comment_report.id))
assert response.status == "302 FOUND"
allie_user, natalie_user = self.query_for_users()
archived_report = CommentReport.query.filter(
CommentReport.reported_user==allie_user).first()
assert CommentReport.query.count() != 0
assert archived_report is not None
assert archived_report.report_content == u'Testing Archived Reports #1'
assert archived_report.reporter_id == natalie_id
assert archived_report.reported_user_id == allie_id
assert archived_report.created is not None
assert archived_report.resolved is not None
assert archived_report.result == u'''This is a test of archiving reports.
natalie banned user allie indefinitely.
natalie deleted the comment.'''
assert archived_report.discriminator == 'comment_report'

View File

@ -47,12 +47,22 @@ class TestSubmission:
# TODO: Possibly abstract into a decorator like:
# @as_authenticated_user('chris')
test_user = fixture_add_user()
self.test_user = test_user
fixture_add_user(privileges=[u'active',u'uploader', u'commenter'])
self.login()
def our_user(self):
"""
Fetch the user we're submitting with. Every .get() or .post()
invalidates the session; this is a hacky workaround.
"""
#### FIXME: Pytest collects this as a test and runs this.
#### ... it shouldn't. At least it passes, but that's
#### totally stupid.
#### Also if we found a way to make this run it should be a
#### property.
return User.query.filter(User.username==u'chris').first()
def login(self):
self.test_app.post(
'/auth/login/', {
@ -98,10 +108,10 @@ class TestSubmission:
def check_normal_upload(self, title, filename):
response, context = self.do_post({'title': title}, do_follow=True,
**self.upload_data(filename))
self.check_url(response, '/u/{0}/'.format(self.test_user.username))
self.check_url(response, '/u/{0}/'.format(self.our_user().username))
assert 'mediagoblin/user_pages/user.html' in context
# Make sure the media view is at least reachable, logged in...
url = '/u/{0}/m/{1}/'.format(self.test_user.username,
url = '/u/{0}/m/{1}/'.format(self.our_user().username,
title.lower().replace(' ', '-'))
self.test_app.get(url)
# ... and logged out too.
@ -148,7 +158,7 @@ class TestSubmission:
response, context = self.do_post({'title': u'Normal upload 3 (pdf)'},
do_follow=True,
**self.upload_data(GOOD_PDF))
self.check_url(response, '/u/{0}/'.format(self.test_user.username))
self.check_url(response, '/u/{0}/'.format(self.our_user().username))
assert 'mediagoblin/user_pages/user.html' in context
def test_default_upload_limits(self):
@ -264,7 +274,7 @@ class TestSubmission:
# render and post to the edit page.
edit_url = request.urlgen(
'mediagoblin.edit.edit_media',
user=self.test_user.username, media_id=media_id)
user=self.our_user().username, media_id=media_id)
self.test_app.get(edit_url)
self.test_app.post(edit_url,
{'title': u'Balanced Goblin',
@ -277,7 +287,7 @@ class TestSubmission:
self.check_comments(request, media_id, 0)
comment_url = request.urlgen(
'mediagoblin.user_pages.media_post_comment',
user=self.test_user.username, media_id=media_id)
user=self.our_user().username, media_id=media_id)
response = self.do_post({'comment_content': 'i love this test'},
url=comment_url, do_follow=True)[0]
self.check_comments(request, media_id, 1)
@ -286,7 +296,7 @@ class TestSubmission:
# ---------------------------------------------------
delete_url = request.urlgen(
'mediagoblin.user_pages.media_confirm_delete',
user=self.test_user.username, media_id=media_id)
user=self.our_user().username, media_id=media_id)
# Empty data means don't confirm
response = self.do_post({}, do_follow=True, url=delete_url)[0]
media = self.check_media(request, {'title': u'Balanced Goblin'}, 1)
@ -359,7 +369,7 @@ class TestSubmission:
# they'll be caught as failures during the processing step.
response, context = self.do_post({'title': title}, do_follow=True,
**self.upload_data(filename))
self.check_url(response, '/u/{0}/'.format(self.test_user.username))
self.check_url(response, '/u/{0}/'.format(self.our_user().username))
entry = mg_globals.database.MediaEntry.query.filter_by(title=title).first()
assert entry.state == 'failed'
assert entry.fail_error == u'mediagoblin.processing:BadMediaFail'

View File

@ -25,7 +25,7 @@ from webtest import TestApp
from mediagoblin import mg_globals
from mediagoblin.db.models import User, MediaEntry, Collection, MediaComment, \
CommentSubscription, CommentNotification
CommentSubscription, CommentNotification, Privilege, CommentReport
from mediagoblin.tools import testing
from mediagoblin.init.config import read_mediagoblin_config
from mediagoblin.db.base import Session
@ -33,6 +33,8 @@ from mediagoblin.meddleware import BaseMeddleware
from mediagoblin.auth import gen_password_hash
from mediagoblin.gmg_commands.dbupdate import run_dbupdate
from datetime import datetime
MEDIAGOBLIN_TEST_DB_NAME = u'__mediagoblin_tests__'
TEST_SERVER_CONFIG = pkg_resources.resource_filename(
@ -133,7 +135,6 @@ def get_app(request, paste_config=None, mgoblin_config=None):
mg_globals.app.meddleware.insert(0, TestingMeddleware(mg_globals.app))
app = TestApp(test_app)
return app
@ -170,7 +171,7 @@ def assert_db_meets_expected(db, expected):
def fixture_add_user(username=u'chris', password=u'toast',
active_user=True, wants_comment_notification=True):
privileges=[], wants_comment_notification=True):
# Reuse existing user or create a new one
test_user = User.query.filter_by(username=username).first()
if test_user is None:
@ -179,14 +180,13 @@ def fixture_add_user(username=u'chris', password=u'toast',
test_user.email = username + u'@example.com'
if password is not None:
test_user.pw_hash = gen_password_hash(password)
if active_user:
test_user.email_verified = True
test_user.status = u'active'
test_user.wants_comment_notification = wants_comment_notification
for privilege in privileges:
query = Privilege.query.filter(Privilege.privilege_name==privilege)
if query.count():
test_user.all_privileges.append(query.one())
test_user.save()
# Reload
test_user = User.query.filter_by(username=username).first()
@ -314,3 +314,32 @@ def fixture_add_comment(author=None, media_entry=None, comment=None):
return comment
def fixture_add_comment_report(comment=None, reported_user=None,
reporter=None, created=None, report_content=None):
if comment is None:
comment = fixture_add_comment()
if reported_user is None:
reported_user = fixture_add_user()
if reporter is None:
reporter = fixture_add_user()
if created is None:
created=datetime.now()
if report_content is None:
report_content = \
'Auto-generated test report'
comment_report = CommentReport(comment=comment,
reported_user = reported_user,
reporter = reporter,
created = created,
report_content=report_content)
comment_report.save()
Session.expunge(comment_report)
return comment_report

View File

@ -21,6 +21,8 @@ from werkzeug.wrappers import Response as wz_Response
from mediagoblin.tools.template import render_template
from mediagoblin.tools.translate import (lazy_pass_to_ugettext as _,
pass_to_ugettext)
from mediagoblin.db.models import UserBan, User
from datetime import date
class Response(wz_Response):
"""Set default response mimetype to HTML, otherwise we get text/plain"""
@ -50,7 +52,8 @@ def render_400(request, err_msg=None):
_ = pass_to_ugettext
title = _("Bad Request")
if err_msg is None:
err_msg = _("The request sent to the server is invalid, please double check it")
err_msg = _("The request sent to the server is invalid, \
please double check it")
return render_error(request, 400, title, err_msg)
@ -71,6 +74,21 @@ def render_404(request):
"you're looking for has been moved or deleted.")
return render_error(request, 404, err_msg=err_msg)
def render_user_banned(request):
"""Renders the page which tells a user they have been banned, for how long
and the reason why they have been banned"
"""
user_ban = UserBan.query.get(request.user.id)
if (user_ban.expiration_date is not None and
date.today()>user_ban.expiration_date):
user_ban.delete()
return redirect(request,
'index')
return render_to_response(request,
'mediagoblin/banned.html',
{'reason':user_ban.reason,
'expiration_date':user_ban.expiration_date})
def render_http_exception(request, exc, description):
"""Return Response() given a werkzeug.HTTPException
@ -126,7 +144,7 @@ def json_response(serializable, _disable_cors=False, *args, **kw):
Any extra arguments and keyword arguments are passed to the
Response.__init__ method.
'''
response = wz_Response(json.dumps(serializable), *args, content_type='application/json', **kw)
if not _disable_cors:

View File

@ -49,3 +49,15 @@ class MediaCollectForm(wtforms.Form):
description=_("""You can use
<a href="http://daringfireball.net/projects/markdown/basics" target="_blank">
Markdown</a> for formatting."""))
class CommentReportForm(wtforms.Form):
report_reason = wtforms.TextAreaField(
_('Reason for Reporting'),
[wtforms.validators.Required()])
reporter_id = wtforms.HiddenField('')
class MediaReportForm(wtforms.Form):
report_reason = wtforms.TextAreaField(
_('Reason for Reporting'),
[wtforms.validators.Required()])
reporter_id = wtforms.HiddenField('')

View File

@ -19,7 +19,9 @@ 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,
MediaComment, MediaEntry)
from mediagoblin.user_pages import forms as user_forms
def send_comment_email(user, comment, media, request):
@ -75,3 +77,44 @@ def add_media_to_collection(collection, media, note=None, commit=True):
if commit:
Session.commit()
def build_report_object(report_form, media_entry=None, comment=None):
"""
This function is used to convert a form object (from a User filing a
report) into either a MediaReport or CommentReport object.
:param report_form A MediaReportForm or a CommentReportForm object
with valid information from a POST request.
:param media_entry A MediaEntry object. The MediaEntry being repo-
-rted by a MediaReport. In a CommentReport,
this will be None.
:param comment A MediaComment object. The MediaComment being
reported by a CommentReport. In a MediaReport
this will be None.
:returns A MediaReport object if a valid MediaReportForm is
passed as kwarg media_entry. This MediaReport has
not been saved.
:returns A CommentReport object if a valid CommentReportForm
is passed as kwarg comment. This CommentReport
has not been saved.
:returns None if the form_dict is invalid.
"""
if report_form.validate() and comment is not None:
report_object = CommentReport()
report_object.comment_id = comment.id
report_object.reported_user_id = MediaComment.query.get(
comment.id).get_author.id
elif report_form.validate() and media_entry is not None:
report_object = MediaReport()
report_object.media_entry_id = media_entry.id
report_object.reported_user_id = MediaEntry.query.get(
media_entry.id).get_uploader.id
else:
return None
report_object.report_content = report_form.report_reason.data
report_object.reporter_id = report_form.reporter_id.data
return report_object

View 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')
@ -44,6 +48,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_report')
# User's tags gallery
add_route('mediagoblin.user_pages.user_tag_gallery',
'/u/<string:user>/tag/<string:tag>/',

View File

@ -27,13 +27,16 @@ from mediagoblin.tools.text import cleaned_markdown_conversion
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 add_media_to_collection
from mediagoblin.user_pages.lib import (send_comment_email,
add_media_to_collection, build_report_object)
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, user_not_banned,
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_optional_media_comment_by_id, allow_reporting)
from werkzeug.contrib.atom import AtomFeed
from werkzeug.exceptions import MethodNotAllowed
@ -43,14 +46,14 @@ from werkzeug.wrappers import Response
_log = logging.getLogger(__name__)
_log.setLevel(logging.DEBUG)
@user_not_banned
@uses_pagination
def user_home(request, page):
"""'Homepage' of a User()"""
user = User.query.filter_by(username=request.matchdict['user']).first()
if not user:
return render_404(request)
elif user.status != u'active':
elif not user.has_privilege(u'active'):
return render_to_response(
request,
'mediagoblin/user_pages/user_nonactive.html',
@ -79,7 +82,7 @@ def user_home(request, page):
'media_entries': media_entries,
'pagination': pagination})
@user_not_banned
@active_user_from_url
@uses_pagination
def user_gallery(request, page, url_user=None):
@ -114,7 +117,7 @@ def user_gallery(request, page, url_user=None):
MEDIA_COMMENTS_PER_PAGE = 50
@user_not_banned
@get_user_media_entry
@uses_pagination
def media_home(request, media, page, **kwargs):
@ -154,7 +157,7 @@ def media_home(request, media, page, **kwargs):
@get_media_entry_by_id
@require_active_login
@user_has_privilege(u'commenter')
def media_post_comment(request, media):
"""
recieves POST from a MediaEntry() comment form, saves the comment.
@ -165,7 +168,6 @@ def media_post_comment(request, media):
comment = request.db.MediaComment()
comment.media_entry = media.id
comment.author = request.user.id
print request.form['comment_content']
comment.content = unicode(request.form['comment_content'])
# Show error message if commenting is disabled.
@ -205,6 +207,7 @@ def media_preview_comment(request):
return Response(json.dumps(cleancomment))
@user_not_banned
@get_media_entry_by_id
@require_active_login
def media_collect(request, media):
@ -316,7 +319,7 @@ def media_confirm_delete(request, media):
_("The media was not deleted because you didn't check that you were sure."))
return redirect_obj(request, media)
if ((request.user.is_admin and
if ((request.user.has_privilege(u'admin') and
request.user.id != media.uploader)):
messages.add_message(
request, messages.WARNING,
@ -329,7 +332,7 @@ def media_confirm_delete(request, media):
{'media': media,
'form': form})
@user_not_banned
@active_user_from_url
@uses_pagination
def user_collection(request, page, url_user=None):
@ -359,7 +362,7 @@ def user_collection(request, page, url_user=None):
'collection_items': collection_items,
'pagination': pagination})
@user_not_banned
@active_user_from_url
def collection_list(request, url_user=None):
"""A User-defined Collection"""
@ -402,7 +405,7 @@ def collection_item_confirm_remove(request, collection_item):
return redirect_obj(request, collection)
if ((request.user.is_admin and
if ((request.user.has_privilege(u'admin') and
request.user.id != collection_item.in_collection.creator)):
messages.add_message(
request, messages.WARNING,
@ -450,7 +453,7 @@ def collection_confirm_delete(request, collection):
return redirect_obj(request, collection)
if ((request.user.is_admin and
if ((request.user.has_privilege(u'admin') and
request.user.id != collection.creator)):
messages.add_message(
request, messages.WARNING,
@ -472,9 +475,8 @@ def atom_feed(request):
generates the atom feed with the newest images
"""
user = User.query.filter_by(
username = request.matchdict['user'],
status = u'active').first()
if not user:
username = request.matchdict['user']).first()
if not user or not user.has_privilege(u'active'):
return render_404(request)
cursor = MediaEntry.query.filter_by(
@ -535,9 +537,8 @@ def collection_atom_feed(request):
generates the atom feed with the newest images from a collection
"""
user = User.query.filter_by(
username = request.matchdict['user'],
status = u'active').first()
if not user:
username = request.matchdict['user']).first()
if not user or not user.has_privilege(u'active'):
return render_404(request)
collection = Collection.query.filter_by(
@ -599,7 +600,6 @@ def collection_atom_feed(request):
return feed.get_response()
@require_active_login
def processing_panel(request):
"""
@ -611,7 +611,7 @@ def processing_panel(request):
#
# Make sure we have permission to access this user's panel. Only
# admins and this user herself should be able to do so.
if not (user.id == request.user.id or request.user.is_admin):
if not (user.id == request.user.id or request.user.has_privilege(u'admin')):
# No? Simply redirect to this user's homepage.
return redirect(
request, 'mediagoblin.user_pages.user_home',
@ -643,3 +643,44 @@ def processing_panel(request):
'processing_entries': processing_entries,
'failed_entries': failed_entries,
'processed_entries': processed_entries})
@allow_reporting
@get_user_media_entry
@user_has_privilege(u'reporter')
@get_optional_media_comment_by_id
def file_a_report(request, media, comment):
"""
This view handles the filing of a MediaReport or a CommentReport.
"""
if comment is not None:
if not comment.get_media_entry.id == media.id:
return render_404(request)
form = user_forms.CommentReportForm(request.form)
context = {'media': media,
'comment':comment,
'form':form}
else:
form = user_forms.MediaReportForm(request.form)
context = {'media': media,
'form':form}
form.reporter_id.data = request.user.id
if request.method == "POST":
report_object = build_report_object(form,
media_entry=media,
comment=comment)
# if the object was built successfully, report_table will not be None
if report_object:
report_object.save()
return redirect(
request,
'index')
return render_to_response(
request,
'mediagoblin/user_pages/report.html',
context)

View File

@ -18,10 +18,10 @@ from mediagoblin import mg_globals
from mediagoblin.db.models import MediaEntry
from mediagoblin.tools.pagination import Pagination
from mediagoblin.tools.response import render_to_response
from mediagoblin.decorators import uses_pagination
from mediagoblin.decorators import uses_pagination, user_not_banned
@user_not_banned
@uses_pagination
def root_view(request, page):
cursor = MediaEntry.query.filter_by(state=u'processed').\
@ -44,3 +44,7 @@ def simple_template_render(request):
template_name = request.matchdict['template']
return render_to_response(
request, template_name, {})
def terms_of_service(request):
return render_to_response(request,
'mediagoblin/terms_of_service.html', {})