From 30a9fe7c1cf128fdf413797a2b2edac2d5439bc2 Mon Sep 17 00:00:00 2001 From: tilly-Q Date: Mon, 24 Jun 2013 16:35:31 -0700 Subject: [PATCH 01/25] This is the first stage of my project of implenting admin/moderator functiona- lity. At this point, I have finished all the of basic work with the models! I still need to do some tightening of their documentation, but they seem to be working well. Working with Models ======================================== --\ mediagoblin/db/models.py --| Added in the Report model and table. This model is strictly a parent ----| Added in the CommentReport model which holds information about a report | filed against a comment. This class inherits from Report. ----| Added in the MediaReport model which holds information about a report f- | -iled against a media entry. This class inherits from Report. --| Added in a UserBan model and table. This model is in a one to one relatio- | -nship with User. This object acts as a marker for whether a user is banned | or not. --| Added in a Group model. These objects are in a many-to-many relationship | with User to explain which privileges a User has. ----| Added in GroupUserAssociation which is a table used to hold this many to | many relationship between Group & User. --\ mediagoblin/db/migrations.py --| Added in the migrations for all of the additions to models --| Added UserBan_v0 --| Added Report_v0 ----| Added CommentReport_v0 ----| Added MediaReport_v0 --| Added Group_v0 ----| Added GroupUserAssociation_v0 Working with Templates, Views, and Routing =============================================== >>> Reporting a Comment or a MediaEntry --\ mediagoblin/user_pages/views.py --| Added in the function file_a_report to allow user to file reports against | MediaEntries or Comments. Handles GET and POST requests. --| Added in the function file_a_comment_report which uses file_a_report but | also catches appropriate information for comment_ids. I may be able to do | this more eloquently with decorators. --\ mediagoblin/user_pages/routing.py --| Added in route 'mediagoblin.user_pages.media_home.report_media' | (linked to address /u//m//report/ ) --| Added in route ''mediagoblin.user_pages.media_home.report_comment' | (linked to address /u//m//c//report/ ) --\ mediagoblin/templates/mediagoblin/user_pages/report.html --| I created this file to handle the filing of a report. --\ mediagoblin/templates/mediagoblin/user_pages/media.html --| Modified this file to add in links allowing users to report either media | or comments. --\ mediagoblin/user_pages/lib.py --| Added in build_report_form which processes data as either a CommentReport or | a MediaReport depending on which parameters are present --\ mediagoblin/user_pages/forms.py --| Added in CommentReportForm --| Added in MediaReportForm --| note: ReportForm is vestigial to an earlier strategy I used and I'll remove it | promptly --\ mediagoblin/decorators.py --| Added in 'get_media_comment_by_id' for use in mediagoblin/user_pages/views.py >>> New Admin Panels --\ mediagoblin/admin/views.py --| Added in the function admin_users_panel --| Added in the function admin_reports_panel --\ mediagoblin/admin/routing.py --| Added in route 'mediagoblin.admin.users' | (linked to address '/a/users') --| Added in route 'mediagoblin.admin.reports' | (linked to address '/a/reports/') --\ mediagoblin/templates/admin/user.html --| Created this file as a template for monitoring users --\ mediagoblin/templates/admin/report.html --| Created this file as a template for monitoring reports filed against media or | comments --- mediagoblin/admin/routing.py | 8 +- mediagoblin/admin/views.py | 39 +++++++- mediagoblin/db/migrations.py | 69 +++++++++++++- mediagoblin/db/models.py | 88 ++++++++++++++++- mediagoblin/decorators.py | 20 +++- .../templates/mediagoblin/admin/report.html | 95 +++++++++++++++++++ .../templates/mediagoblin/admin/user.html | 54 +++++++++++ .../mediagoblin/user_pages/media.html | 22 +++++ .../mediagoblin/user_pages/report.html | 75 +++++++++++++++ mediagoblin/user_pages/forms.py | 16 ++++ mediagoblin/user_pages/lib.py | 31 +++++- mediagoblin/user_pages/routing.py | 8 ++ mediagoblin/user_pages/views.py | 33 ++++++- 13 files changed, 548 insertions(+), 10 deletions(-) create mode 100644 mediagoblin/templates/mediagoblin/admin/report.html create mode 100644 mediagoblin/templates/mediagoblin/admin/user.html create mode 100644 mediagoblin/templates/mediagoblin/user_pages/report.html diff --git a/mediagoblin/admin/routing.py b/mediagoblin/admin/routing.py index 29515f12..d5edac0f 100644 --- a/mediagoblin/admin/routing.py +++ b/mediagoblin/admin/routing.py @@ -17,4 +17,10 @@ admin_routes = [ ('mediagoblin.admin.panel', '/panel', - 'mediagoblin.admin.views:admin_processing_panel')] + 'mediagoblin.admin.views:admin_processing_panel'), + ('mediagoblin.admin.users', + '/users', + 'mediagoblin.admin.views:admin_users_panel'), + ('mediagoblin.admin.reports', + '/reports', + 'mediagoblin.admin.views:admin_reports_panel')] diff --git a/mediagoblin/admin/views.py b/mediagoblin/admin/views.py index 22ca74a3..faa8603a 100644 --- a/mediagoblin/admin/views.py +++ b/mediagoblin/admin/views.py @@ -16,7 +16,7 @@ from werkzeug.exceptions import Forbidden -from mediagoblin.db.models import MediaEntry +from mediagoblin.db.models import MediaEntry, User, MediaComment, CommentReport, ReportBase from mediagoblin.decorators import require_active_login from mediagoblin.tools.response import render_to_response @@ -46,3 +46,40 @@ def admin_processing_panel(request): {'processing_entries': processing_entries, 'failed_entries': failed_entries, 'processed_entries': processed_entries}) + +@require_active_login +def admin_users_panel(request): + ''' + Show the global processing panel for this instance + ''' + # TODO: Why not a "require_admin_login" decorator throwing a 403 exception? + if not request.user.is_admin: + raise Forbidden() + + user_list = User.query + + # Render to response + return render_to_response( + request, + 'mediagoblin/admin/user.html', + {'user_list': user_list}) + +@require_active_login +def admin_reports_panel(request): + ''' + Show the global processing panel for this instance + ''' + # TODO: Why not a "require_admin_login" decorator throwing a 403 exception? + if not request.user.is_admin: + raise Forbidden() + + report_list = ReportBase.query.filter(ReportBase.resolved==None).order_by(ReportBase.created.desc()).limit(10) + closed_report_list = ReportBase.query.filter(ReportBase.resolved!=None).order_by(ReportBase.created.desc()).limit(10) + + # Render to response + return render_to_response( + request, + 'mediagoblin/admin/report.html', + {'report_list':report_list, + 'closed_report_list':closed_report_list}) + diff --git a/mediagoblin/db/migrations.py b/mediagoblin/db/migrations.py index 2c553396..110a48d4 100644 --- a/mediagoblin/db/migrations.py +++ b/mediagoblin/db/migrations.py @@ -26,7 +26,7 @@ from sqlalchemy.sql import and_ from migrate.changeset.constraint import UniqueConstraint from mediagoblin.db.migration_tools import RegisterMigration, inspect_table -from mediagoblin.db.models import MediaEntry, Collection, User +from mediagoblin.db.models import MediaEntry, Collection, User, MediaComment, Group MIGRATIONS = {} @@ -287,3 +287,70 @@ def unique_collections_slug(db): constraint.create() db.commit() + +class ReportBase_v0(declarative_base()): + """ + + """ + __tablename__ = 'core__reports' + id = Column(Integer, primary_key=True) + reporter_id = Column(Integer, ForeignKey(User.id), nullable=False) + report_content = Column(UnicodeText) + created = Column(DateTime, nullable=False, default=datetime.datetime.now) + resolved = Column(DateTime) + discriminator = Column('type', Unicode(50)) + __mapper_args__ = {'polymorphic_on': discriminator} + + +class CommentReport_v0(ReportBase_v0): + __tablename__ = 'core__reports_on_comments' + __mapper_args__ = {'polymorphic_identity': 'comment_report'} + + id = Column('id',Integer, ForeignKey('core__reports.id'), + primary_key=True) + comment_id = Column(Integer, ForeignKey(MediaComment.id), nullable=False) + +class MediaReport_v0(ReportBase_v0): + __tablename__ = 'core__reports_on_media' + __mapper_args__ = {'polymorphic_identity': 'media_report'} + + id = Column('id',Integer, ForeignKey('core__reports.id'), + primary_key=True) + media_entry_id = Column(Integer, ForeignKey(MediaEntry.id), nullable=False) + +@RegisterMigration(11, MIGRATIONS) +def create_report_tables(db): + ReportBase_v0.__table__.create(db.bind) + CommentReport_v0.__table__.create(db.bind) + MediaReport_v0.__table__.create(db.bind) + db.commit() + +class UserBan_v0(declarative_base()): + __tablename__ = 'core__user_bans' + user_id = Column('id',Integer, ForeignKey(User.id), nullable=False, + primary_key=True) + expiration_date = Column(DateTime) + reason = Column(UnicodeText, nullable=False) + +class Group_v0(declarative_base()): + __tablename__ = 'core__groups' + id = Column(Integer, nullable=False, primary_key=True) + group_name = Column(Unicode, nullable=False) + +class GroupUserAssociation_v0(declarative_base()): + __tablename__ = 'core__group_user_associations' + + group_id = Column('core__group_id', Integer, ForeignKey(User.id), primary_key=True) + user_id = Column('core__user_id', Integer, ForeignKey(Group.id), primary_key=True) + + + +@RegisterMigration(12, MIGRATIONS) +def create_banned_and_group_tables(db): + UserBan_v0.__table__.create(db.bind) + Group_v0.__table__.create(db.bind) + GroupUserAssociation_v0.__table__.create(db.bind) + db.commit() + + + diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index 2b925983..f524b220 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -29,6 +29,7 @@ from sqlalchemy.orm.collections import attribute_mapped_collection from sqlalchemy.sql.expression import desc from sqlalchemy.ext.associationproxy import association_proxy from sqlalchemy.util import memoized_property +from sqlalchemy.schema import Table from mediagoblin.db.extratypes import PathTupleWithSlashes, JSONEncoded from mediagoblin.db.base import Base, DictReadAttrProxy @@ -484,10 +485,93 @@ class ProcessingMetaData(Base): return DictReadAttrProxy(self) +class ReportBase(Base): + """ + + """ + __tablename__ = 'core__reports' + id = Column(Integer, primary_key=True) + reporter_id = Column(Integer, ForeignKey(User.id), nullable=False) + reporter = relationship(User, backref=backref("reports_filed_by", + lazy="dynamic", + cascade="all, delete-orphan")) + report_content = Column(UnicodeText) + created = Column(DateTime, nullable=False, default=datetime.datetime.now()) + resolved = Column(DateTime) + discriminator = Column('type', Unicode(50)) + __mapper_args__ = {'polymorphic_on': discriminator} + + +class CommentReport(ReportBase): + """ + A class to keep track of reports that have been filed on comments + """ + __tablename__ = 'core__reports_on_comments' + __mapper_args__ = {'polymorphic_identity': 'comment_report'} + + id = Column('id',Integer, ForeignKey('core__reports.id'), + primary_key=True) + comment_id = Column(Integer, ForeignKey(MediaComment.id), nullable=False) + comment = relationship(MediaComment, backref=backref("reports_filed_on", + lazy="dynamic", + cascade="all, delete-orphan")) + +class MediaReport(ReportBase): + """ + A class to keep track of reports that have been filed on media entries + """ + __tablename__ = 'core__reports_on_media' + __mapper_args__ = {'polymorphic_identity': 'media_report'} + + id = Column('id',Integer, ForeignKey('core__reports.id'), + primary_key=True) + media_entry_id = Column(Integer, ForeignKey(MediaEntry.id), nullable=False) + media_entry = relationship(MediaEntry, backref=backref("reports_filed_on", + lazy="dynamic", + cascade="all, delete-orphan")) + +class UserBan(Base): + """ + Holds the information on a specific user's ban-state. As long as one of these + is attached to a user, they are banned from accessing mediagoblin. When they + try to log in, they are greeted with a page that tells them the reason why + they are banned and when (if ever) the ban will be lifted + :param user_id Holds the id of the user this object is attached to. + This should be a one-to-one relationship. + :param expiration_date Holds the date that the ban will be lifted. If this + is null, the ban is permanent unless a moderator + manually lifts it. + :param reason Holds the reason why the user was banned. + """ + __tablename__ = 'core__user_bans' + + user_id = Column('id',Integer, ForeignKey(User.id), nullable=False, + primary_key=True) + expiration_date = Column(DateTime) + reason = Column(UnicodeText, nullable=False) + + +class Group(Base): + __tablename__ = 'core__groups' + + id = Column(Integer, nullable=False, primary_key=True) + group_name = Column(Unicode, nullable=False) + all_users = relationship(User, backref='all_groups', secondary="core__group_user_associations") + + def __repr__(self): + return "" % (self.group_name) + +class GroupUserAssociation(Base): + __tablename__ = 'core__group_user_associations' + + group_id = Column('core__group_id', Integer, ForeignKey(User.id), primary_key=True) + user_id = Column('core__user_id', Integer, ForeignKey(Group.id), primary_key=True) + + + MODELS = [ User, MediaEntry, Tag, MediaTag, MediaComment, Collection, CollectionItem, MediaFile, FileKeynames, - MediaAttachmentFile, ProcessingMetaData] - + MediaAttachmentFile, ProcessingMetaData, CommentReport, MediaReport, UserBan, Group, GroupUserAssociation] ###################################################### # Special, migrations-tracking table diff --git a/mediagoblin/decorators.py b/mediagoblin/decorators.py index f3535fcf..5b55ead7 100644 --- a/mediagoblin/decorators.py +++ b/mediagoblin/decorators.py @@ -21,7 +21,7 @@ from werkzeug.exceptions import Forbidden, NotFound from werkzeug.urls import url_quote from mediagoblin import mg_globals as mgg -from mediagoblin.db.models import MediaEntry, User +from mediagoblin.db.models import MediaEntry, User, MediaComment from mediagoblin.tools.response import redirect, render_404 @@ -226,6 +226,24 @@ def get_media_entry_by_id(controller): return wrapper +def get_media_comment_by_id(controller): + """ + Pass in a MediaComment based off of a url component + """ + @wraps(controller) + def wrapper(request, *args, **kwargs): + comment = MediaComment.query.filter_by( + id=request.matchdict['comment']).first() + # Still no media? Okay, 404. + if not comment: + return render_404(request) + + return controller(request, comment=comment, *args, **kwargs) + + return wrapper + + + def get_workbench(func): """Decorator, passing in a workbench as kwarg which is cleaned up afterwards""" diff --git a/mediagoblin/templates/mediagoblin/admin/report.html b/mediagoblin/templates/mediagoblin/admin/report.html new file mode 100644 index 00000000..ff5cb427 --- /dev/null +++ b/mediagoblin/templates/mediagoblin/admin/report.html @@ -0,0 +1,95 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +#} +{% extends "mediagoblin/base.html" %} + +{% block title -%} + {% trans %}Report panel{% endtrans %} — {{ super() }} +{%- endblock %} + +{% block mediagoblin_content %} + +

{% trans %}Report panel{% endtrans %}

+ +

+ {% trans %}Here you can look up users in order to take punitive actions on them.{% endtrans %} +

+ +

{% trans %}Reports Filed on Comments{% endtrans %}

+ +{% if report_list.count() %} + + + + + + + + + + + {% for report in report_list %} + + {% if report.discriminator == "comment_report" %} + + + + + + + + {% elif report.discriminator == "media_report" %} + + + + + + + + {% endif %} + + {% endfor %} +
IDReport TypeOffenderWhen ReportedReported ByReasonReported Comment or Media Entry
{{ report.id }}Comment Report{{ report.comment.get_author.username }}{{ report.created.strftime("%F %R") }}{{ report.reporter.username }}{{ report.report_content }}{{ report.comment.get_media_entry.title }}{{ report.id }}Media Report{{ report.media_entry.get_uploader.username }}{{ report.created.strftime("%F %R") }}{{ report.reporter.username }}{{ report.report_content[0:20] }}...{{ report.media_entry.title }}
+{% else %} +

{% trans %}No open reports found.{% endtrans %}

+{% endif %} +

{% trans %}Closed Reports on Comments{% endtrans %}

+{% if closed_report_list.count() %} + + + + + + + + + + {% for report in closed_report_list %} + + + + + + + + + {% endfor %} +
IDOffenderWhen ReportedReported ByReasonComment Posted On
{{ report.id }}{{ report.comment.get_author.username }}{{ report.created.strftime("%F %R") }}{{ report.reporter.username }}{{ report.report_content }}{{ report.comment.get_media_entry.title }}
+{% else %} +

{% trans %}No closed reports found.{% endtrans %}

+{% endif %} +{% endblock %} diff --git a/mediagoblin/templates/mediagoblin/admin/user.html b/mediagoblin/templates/mediagoblin/admin/user.html new file mode 100644 index 00000000..6b6d226a --- /dev/null +++ b/mediagoblin/templates/mediagoblin/admin/user.html @@ -0,0 +1,54 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +#} +{% extends "mediagoblin/base.html" %} + +{% block title -%} + {% trans %}User panel{% endtrans %} — {{ super() }} +{%- endblock %} + +{% block mediagoblin_content %} + +

{% trans %}User panel{% endtrans %}

+ +

+ {% trans %}Here you can look up users in order to take punitive actions on them.{% endtrans %} +

+ +

{% trans %}Active Users{% endtrans %}

+ +{% if user_list.count() %} + + + + + + + + {% for user in user_list %} + + + + + + + {% endfor %} +
IDUsernameWhen Joined# of Comments Posted
{{ user.id }}{{ user.username }}{{ user.created.strftime("%F %R") }}{{ user.posted_comments.count() }}
+{% else %} +

{% trans %}No users found.{% endtrans %}

+{% endif %} +{% endblock %} diff --git a/mediagoblin/templates/mediagoblin/user_pages/media.html b/mediagoblin/templates/mediagoblin/user_pages/media.html index fb892fd7..134a80ad 100644 --- a/mediagoblin/templates/mediagoblin/user_pages/media.html +++ b/mediagoblin/templates/mediagoblin/user_pages/media.html @@ -95,6 +95,17 @@ {% trans %}Add a comment{% endtrans %} {% endif %} + + {% trans %}Report media{% endtrans %} + {% if request.user %}
+ {% trans %} Report {% endtrans %} + {% endfor %} diff --git a/mediagoblin/templates/mediagoblin/user_pages/report.html b/mediagoblin/templates/mediagoblin/user_pages/report.html new file mode 100644 index 00000000..9431efc0 --- /dev/null +++ b/mediagoblin/templates/mediagoblin/user_pages/report.html @@ -0,0 +1,75 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +#} +{%- extends "mediagoblin/base.html" %} + +{%- block mediagoblin_content %} +

File a Report

+ + {% if comment is defined %} +

{% trans %}Reporting this Comment {% endtrans %}

+ {% set comment_author = comment.get_author %} + + + {% elif media is defined %} +

{% trans %}Reporting this Media Entry {% endtrans %}

+ {% trans %}published by {% endtrans %}{{ media.get_uploader.username }} + +
+ + {% endif %} +
+ + +
+ + + {{ csrf_token }} + +{% endblock %} diff --git a/mediagoblin/user_pages/forms.py b/mediagoblin/user_pages/forms.py index 9a193680..effe49e1 100644 --- a/mediagoblin/user_pages/forms.py +++ b/mediagoblin/user_pages/forms.py @@ -49,3 +49,19 @@ class MediaCollectForm(wtforms.Form): description=_("""You can use Markdown for formatting.""")) + +class CommentReportForm(wtforms.Form): + report_reason = wtforms.TextAreaField('Reason for Reporting') + comment_id = wtforms.IntegerField() + reporter_id = wtforms.IntegerField() + +class MediaReportForm(wtforms.Form): + report_reason = wtforms.TextAreaField('Reason for Reporting') + media_entry_id = wtforms.IntegerField() + reporter_id = wtforms.IntegerField() + +class ReportForm(wtforms.Form): + report_reason = wtforms.TextAreaField('Reason for Reporting') + media_entry_id = wtforms.IntegerField() + reporter_id = wtforms.IntegerField() + comment_id = wtforms.IntegerField() diff --git a/mediagoblin/user_pages/lib.py b/mediagoblin/user_pages/lib.py index 2f47e4b1..9c8ddee6 100644 --- a/mediagoblin/user_pages/lib.py +++ b/mediagoblin/user_pages/lib.py @@ -19,7 +19,8 @@ from mediagoblin.tools.template import render_template from mediagoblin.tools.translate import pass_to_ugettext as _ from mediagoblin import mg_globals from mediagoblin.db.base import Session -from mediagoblin.db.models import CollectionItem +from mediagoblin.db.models import CollectionItem, MediaReport, CommentReport +from mediagoblin.user_pages import forms as user_forms def send_comment_email(user, comment, media, request): @@ -75,3 +76,31 @@ def add_media_to_collection(collection, media, note=None, commit=True): if commit: Session.commit() + +def build_report_form(form_dict): + """ + :param form_dict should be an ImmutableMultiDict object which is what is + returned from 'request.form.' The Object should have valid keys + matching the fields in either MediaReportForm or CommentReportForm + + :returns either of MediaReport or a CommentReport object that has not been saved. + In case of an improper form_dict, returns None + """ + if 'comment_id' in form_dict.keys(): + report_form = user_forms.CommentReportForm(form_dict) + elif 'media_entry_id' in form_dict.keys(): + report_form = user_forms.MediaReportForm(form_dict) + else: + return None + if report_form.validate() and 'comment_id' in form_dict.keys(): + report_model = CommentReport() + report_model.comment_id = report_form.comment_id.data + elif report_form.validate() and 'media_entry_id' in form_dict.keys(): + report_model = MediaReport() + report_model.media_entry_id = report_form.media_entry_id.data + else: + return None + report_model.report_content = report_form.report_reason.data or u'' + report_model.reporter_id = report_form.reporter_id.data + return report_model + diff --git a/mediagoblin/user_pages/routing.py b/mediagoblin/user_pages/routing.py index 9cb665b5..adccda9e 100644 --- a/mediagoblin/user_pages/routing.py +++ b/mediagoblin/user_pages/routing.py @@ -23,6 +23,10 @@ add_route('mediagoblin.user_pages.media_home', '/u//m//', 'mediagoblin.user_pages.views:media_home') +add_route('mediagoblin.user_pages.media_home.report_media', + '/u//m//report/', + 'mediagoblin.user_pages.views:file_a_report') + add_route('mediagoblin.user_pages.media_confirm_delete', '/u//m//confirm-delete/', 'mediagoblin.user_pages.views:media_confirm_delete') @@ -40,6 +44,10 @@ add_route('mediagoblin.user_pages.media_home.view_comment', '/u//m//c//', 'mediagoblin.user_pages.views:media_home') +add_route('mediagoblin.user_pages.media_home.report_comment', + '/u//m//c//report/', + 'mediagoblin.user_pages.views:file_a_comment_report') + # User's tags gallery add_route('mediagoblin.user_pages.user_tag_gallery', '/u//tag//', diff --git a/mediagoblin/user_pages/views.py b/mediagoblin/user_pages/views.py index 738cc054..94cccc66 100644 --- a/mediagoblin/user_pages/views.py +++ b/mediagoblin/user_pages/views.py @@ -19,19 +19,21 @@ import datetime from mediagoblin import messages, mg_globals from mediagoblin.db.models import (MediaEntry, MediaTag, Collection, - CollectionItem, User) + CollectionItem, User, MediaComment, + CommentReport, MediaReport) from mediagoblin.tools.response import render_to_response, render_404, \ redirect, redirect_obj from mediagoblin.tools.translate import pass_to_ugettext as _ from mediagoblin.tools.pagination import Pagination from mediagoblin.user_pages import forms as user_forms -from mediagoblin.user_pages.lib import (send_comment_email, +from mediagoblin.user_pages.lib import (send_comment_email, build_report_form, add_media_to_collection) from mediagoblin.decorators import (uses_pagination, get_user_media_entry, get_media_entry_by_id, require_active_login, user_may_delete_media, user_may_alter_collection, - get_user_collection, get_user_collection_item, active_user_from_url) + get_user_collection, get_user_collection_item, active_user_from_url, + get_media_comment_by_id) from werkzeug.contrib.atom import AtomFeed @@ -616,3 +618,28 @@ def processing_panel(request): 'processing_entries': processing_entries, 'failed_entries': failed_entries, 'processed_entries': processed_entries}) + +@require_active_login +@get_user_media_entry +def file_a_report(request, media, comment=None): + if request.method == "POST": + report_form = build_report_form(request.form) + report_form.save() + return redirect( + request, + 'index') + if comment is not None: + context = {'media': media, + 'comment':comment} + else: + context = {'media': media} + return render_to_response( + request, + 'mediagoblin/user_pages/report.html', + context) + +@require_active_login +@get_user_media_entry +@get_media_comment_by_id +def file_a_comment_report(request, media, comment): + return file_a_report(request, comment=comment) From 9b8ef022ef874304fb3d5aead612ec3b8fb23e9a Mon Sep 17 00:00:00 2001 From: tilly-Q Date: Thu, 27 Jun 2013 14:13:42 -0700 Subject: [PATCH 02/25] In this commit, I have made a few changes and tightened up some of my models code. I added in two major pieces of functionality: table foundations and a decorator to confirm whether or not a user is a member of a certain group. Table Foundations are default rows that should be present in a given table as soon as the database is initialized. For example, I am using these to populate the core__groups table with all of the necessary groups ('moderator', 'com- menter', etc). Right now, this is achieved by adding a dictionary of parameters (with the parameters as lists) to the constant FOUNDATIONS in mediagoblin.db.models. The keys to this dictionary are uninstantiated classes. The classes which require foundations also have must have a constructor so that the list of parameters can be passed appropriately like so: Model(*parameters) In order to implement these foundations, I added the method populate_table_fou- -ndations to MigrationManager in mediagoblin.db.migration_tools. The decorator, called user_in_group, accepts as a parameter a unicode string, and then decides whether to redirect to 403 or let the user access the page. The identifier is the Group.group_name string, because I believe that will allow for the most readable code. I also added in the simple decorator require_admin_login. In terms of tightening up my code, I made many minor changes to my use of white space and made a few small documentation additions. I removed a vestigial class (ReportForm) from mediagoblin.user_pages.forms. I moved all of my migrations in- to one registered Migration. Setting up Foundations ============================== --\ mediagoblin/db/migration_tools.py --| created: MigrationManager.populate_table_foundations --| modified: MigrationManager.init_or_migrate to run | self.populate_table_foundations on init --\ mediagoblin/db/models.py --| created: FOUNDATIONS ----| created: group_foundations Working With Permissions ============================== --\ mediagoblin/decorators.py --| created: user_in_group --| created: require_admin_login --\ mediagoblin/user_pages/views.py --| modified: added decorator user_in_group to file_a_report --\ mediagoblin/admin/views.py --| modified: added decorator require_admin_login to all views functions General Code Tidying ============================= --/ mediagoblin/admin/views.py --/ mediagoblin/user_pages/forms.py --/ mediagoblin/db/models.py --/ mediagoblin/user_pages/lib.py --/ mediagoblin/user_pages/views.py --/ mediagoblin/db/migrations.py --- mediagoblin/admin/views.py | 35 +++++-------- mediagoblin/db/migration_tools.py | 17 ++++++- mediagoblin/db/migrations.py | 37 ++++++++------ mediagoblin/db/models.py | 85 +++++++++++++++++++++---------- mediagoblin/decorators.py | 45 +++++++++++++++- mediagoblin/user_pages/forms.py | 6 --- mediagoblin/user_pages/lib.py | 15 ++++-- mediagoblin/user_pages/views.py | 22 ++++---- 8 files changed, 175 insertions(+), 87 deletions(-) diff --git a/mediagoblin/admin/views.py b/mediagoblin/admin/views.py index faa8603a..7a4dfbd4 100644 --- a/mediagoblin/admin/views.py +++ b/mediagoblin/admin/views.py @@ -17,18 +17,14 @@ from werkzeug.exceptions import Forbidden from mediagoblin.db.models import MediaEntry, User, MediaComment, CommentReport, ReportBase -from mediagoblin.decorators import require_active_login +from mediagoblin.decorators import require_admin_login from mediagoblin.tools.response import render_to_response -@require_active_login +@require_admin_login def admin_processing_panel(request): ''' - Show the global processing panel for this instance + Show the global media processing panel for this instance ''' - # TODO: Why not a "require_admin_login" decorator throwing a 403 exception? - if not request.user.is_admin: - raise Forbidden() - processing_entries = MediaEntry.query.filter_by(state = u'processing').\ order_by(MediaEntry.created.desc()) @@ -47,15 +43,11 @@ def admin_processing_panel(request): 'failed_entries': failed_entries, 'processed_entries': processed_entries}) -@require_active_login +@require_admin_login def admin_users_panel(request): ''' - Show the global processing panel for this instance + Show the global panel for monitoring users in this instance ''' - # TODO: Why not a "require_admin_login" decorator throwing a 403 exception? - if not request.user.is_admin: - raise Forbidden() - user_list = User.query # Render to response @@ -64,17 +56,18 @@ def admin_users_panel(request): 'mediagoblin/admin/user.html', {'user_list': user_list}) -@require_active_login +@require_admin_login def admin_reports_panel(request): ''' - Show the global processing panel for this instance + Show the global panel for monitoring reports filed against comments or + media entries for this instance. ''' - # TODO: Why not a "require_admin_login" decorator throwing a 403 exception? - if not request.user.is_admin: - raise Forbidden() - - report_list = ReportBase.query.filter(ReportBase.resolved==None).order_by(ReportBase.created.desc()).limit(10) - closed_report_list = ReportBase.query.filter(ReportBase.resolved!=None).order_by(ReportBase.created.desc()).limit(10) + report_list = ReportBase.query.filter( + ReportBase.resolved==None).order_by( + ReportBase.created.desc()).limit(10) + closed_report_list = ReportBase.query.filter( + ReportBase.resolved!=None).order_by( + ReportBase.created.desc()).limit(10) # Render to response return render_to_response( diff --git a/mediagoblin/db/migration_tools.py b/mediagoblin/db/migration_tools.py index c0c7e998..f100f47e 100644 --- a/mediagoblin/db/migration_tools.py +++ b/mediagoblin/db/migration_tools.py @@ -140,6 +140,17 @@ class MigrationManager(object): self.session.bind, tables=[model.__table__ for model in self.models]) + def populate_table_foundations(self): + """ + Create the table foundations (default rows) as layed out in FOUNDATIONS + in mediagoblin.db.models + """ + from mediagoblin.db.models import FOUNDATIONS as MAIN_FOUNDATIONS + for Model in MAIN_FOUNDATIONS.keys(): + for parameters in MAIN_FOUNDATIONS[Model]: + row = Model(*parameters) + row.save() + def create_new_migration_record(self): """ Create a new migration record for this migration set @@ -203,8 +214,10 @@ class MigrationManager(object): self.init_tables() # auto-set at latest migration number - self.create_new_migration_record() - + self.create_new_migration_record() + if self.name==u'__main__': + self.populate_table_foundations() + self.printer(u"done.\n") self.set_current_migration() return u'inited' diff --git a/mediagoblin/db/migrations.py b/mediagoblin/db/migrations.py index 110a48d4..5e9a71d4 100644 --- a/mediagoblin/db/migrations.py +++ b/mediagoblin/db/migrations.py @@ -314,16 +314,13 @@ class MediaReport_v0(ReportBase_v0): __tablename__ = 'core__reports_on_media' __mapper_args__ = {'polymorphic_identity': 'media_report'} - id = Column('id',Integer, ForeignKey('core__reports.id'), - primary_key=True) + id = Column( + 'id', + Integer, + ForeignKey('core__reports.id'), + primary_key=True) media_entry_id = Column(Integer, ForeignKey(MediaEntry.id), nullable=False) -@RegisterMigration(11, MIGRATIONS) -def create_report_tables(db): - ReportBase_v0.__table__.create(db.bind) - CommentReport_v0.__table__.create(db.bind) - MediaReport_v0.__table__.create(db.bind) - db.commit() class UserBan_v0(declarative_base()): __tablename__ = 'core__user_bans' @@ -334,23 +331,31 @@ class UserBan_v0(declarative_base()): class Group_v0(declarative_base()): __tablename__ = 'core__groups' - id = Column(Integer, nullable=False, primary_key=True) + id = Column(Integer, nullable=False, primary_key=True, unique=True) group_name = Column(Unicode, nullable=False) class GroupUserAssociation_v0(declarative_base()): __tablename__ = 'core__group_user_associations' - group_id = Column('core__group_id', Integer, ForeignKey(User.id), primary_key=True) - user_id = Column('core__user_id', Integer, ForeignKey(Group.id), primary_key=True) + group_id = Column( + 'core__group_id', + Integer, + ForeignKey(User.id), + primary_key=True) + user_id = Column( + 'core__user_id', + Integer, + ForeignKey(Group.id), + primary_key=True) - - -@RegisterMigration(12, MIGRATIONS) -def create_banned_and_group_tables(db): +@RegisterMigration(11, MIGRATIONS) +def create_moderation_tables(db): + ReportBase_v0.__table__.create(db.bind) + CommentReport_v0.__table__.create(db.bind) + MediaReport_v0.__table__.create(db.bind) UserBan_v0.__table__.create(db.bind) Group_v0.__table__.create(db.bind) GroupUserAssociation_v0.__table__.create(db.bind) db.commit() - diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index f524b220..28e01a85 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -492,11 +492,13 @@ class ReportBase(Base): __tablename__ = 'core__reports' id = Column(Integer, primary_key=True) reporter_id = Column(Integer, ForeignKey(User.id), nullable=False) - reporter = relationship(User, backref=backref("reports_filed_by", - lazy="dynamic", - cascade="all, delete-orphan")) + reporter = relationship( + User, + backref=backref("reports_filed_by", + lazy="dynamic", + cascade="all, delete-orphan")) report_content = Column(UnicodeText) - created = Column(DateTime, nullable=False, default=datetime.datetime.now()) + created = Column(DateTime, nullable=False, default=datetime.datetime.now()) resolved = Column(DateTime) discriminator = Column('type', Unicode(50)) __mapper_args__ = {'polymorphic_on': discriminator} @@ -512,9 +514,10 @@ class CommentReport(ReportBase): id = Column('id',Integer, ForeignKey('core__reports.id'), primary_key=True) comment_id = Column(Integer, ForeignKey(MediaComment.id), nullable=False) - comment = relationship(MediaComment, backref=backref("reports_filed_on", - lazy="dynamic", - cascade="all, delete-orphan")) + comment = relationship( + MediaComment, backref=backref("reports_filed_on", + lazy="dynamic", + cascade="all, delete-orphan")) class MediaReport(ReportBase): """ @@ -526,27 +529,32 @@ class MediaReport(ReportBase): id = Column('id',Integer, ForeignKey('core__reports.id'), primary_key=True) media_entry_id = Column(Integer, ForeignKey(MediaEntry.id), nullable=False) - media_entry = relationship(MediaEntry, backref=backref("reports_filed_on", - lazy="dynamic", - cascade="all, delete-orphan")) + media_entry = relationship( + MediaEntry, + backref=backref("reports_filed_on", + lazy="dynamic", + cascade="all, delete-orphan")) class UserBan(Base): """ - Holds the information on a specific user's ban-state. As long as one of these - is attached to a user, they are banned from accessing mediagoblin. When they - try to log in, they are greeted with a page that tells them the reason why - they are banned and when (if ever) the ban will be lifted - :param user_id Holds the id of the user this object is attached to. - This should be a one-to-one relationship. - :param expiration_date Holds the date that the ban will be lifted. If this - is null, the ban is permanent unless a moderator - manually lifts it. + Holds the information on a specific user's ban-state. As long as one of + these is attached to a user, they are banned from accessing mediagoblin. + When they try to log in, they are greeted with a page that tells them + the reason why they are banned and when (if ever) the ban will be + lifted + + :param user_id Holds the id of the user this object is + attached to. This is a one-to-one + relationship. + :param expiration_date Holds the date that the ban will be lifted. + If this is null, the ban is permanent + unless a moderator manually lifts it. :param reason Holds the reason why the user was banned. """ __tablename__ = 'core__user_bans' - user_id = Column('id',Integer, ForeignKey(User.id), nullable=False, - primary_key=True) + user_id = Column(Integer, ForeignKey(User.id), nullable=False, + primary_key=True) expiration_date = Column(DateTime) reason = Column(UnicodeText, nullable=False) @@ -555,8 +563,14 @@ class Group(Base): __tablename__ = 'core__groups' id = Column(Integer, nullable=False, primary_key=True) - group_name = Column(Unicode, nullable=False) - all_users = relationship(User, backref='all_groups', secondary="core__group_user_associations") + group_name = Column(Unicode, nullable=False, unique=True) + all_users = relationship( + User, + backref='all_groups', + secondary="core__group_user_associations") + + def __init__(self, group_name): + self.group_name = group_name def __repr__(self): return "" % (self.group_name) @@ -564,14 +578,31 @@ class Group(Base): class GroupUserAssociation(Base): __tablename__ = 'core__group_user_associations' - group_id = Column('core__group_id', Integer, ForeignKey(User.id), primary_key=True) - user_id = Column('core__user_id', Integer, ForeignKey(Group.id), primary_key=True) + group_id = Column( + 'core__group_id', + Integer, + ForeignKey(User.id), + primary_key=True) + user_id = Column( + 'core__user_id', + Integer, + ForeignKey(Group.id), + primary_key=True) +group_foundations = [[u'admin'], [u'moderator'], [u'commenter'], [u'uploader'],[u'reporter'],[u'active']] MODELS = [ - User, MediaEntry, Tag, MediaTag, MediaComment, Collection, CollectionItem, MediaFile, FileKeynames, - MediaAttachmentFile, ProcessingMetaData, CommentReport, MediaReport, UserBan, Group, GroupUserAssociation] + User, MediaEntry, Tag, MediaTag, MediaComment, Collection, CollectionItem, + MediaFile, FileKeynames, MediaAttachmentFile, ProcessingMetaData, ReportBase, + CommentReport, MediaReport, UserBan, Group, GroupUserAssociation] + +# Foundations are the default rows that are created immediately after the tables are initialized. Each entry to +# this dictionary should be in the format of +# ModelObject:List of Rows +# (Each Row must be a list of parameters that can create and instance of the ModelObject) +# +FOUNDATIONS = {Group:group_foundations} ###################################################### # Special, migrations-tracking table diff --git a/mediagoblin/decorators.py b/mediagoblin/decorators.py index 5b55ead7..d54bf050 100644 --- a/mediagoblin/decorators.py +++ b/mediagoblin/decorators.py @@ -21,7 +21,7 @@ from werkzeug.exceptions import Forbidden, NotFound from werkzeug.urls import url_quote from mediagoblin import mg_globals as mgg -from mediagoblin.db.models import MediaEntry, User, MediaComment +from mediagoblin.db.models import MediaEntry, User, MediaComment, Group from mediagoblin.tools.response import redirect, render_404 @@ -63,6 +63,26 @@ def active_user_from_url(controller): return wrapper +def user_in_group(group_name): +#TODO handle possible errors correctly + def user_in_group_decorator(controller): + @wraps(controller) + + def wrapper(request, *args, **kwargs): + user_id = request.user.id + group = Group.query.filter( + Group.group_name==group_name).first() + if not (group.query.filter( + Group.all_users.any( + User.id==user_id)).count()): + + raise Forbidden() + + return controller(request, *args, **kwargs) + + return wrapper + return user_in_group_decorator + def user_may_delete_media(controller): """ @@ -253,3 +273,26 @@ def get_workbench(func): return func(*args, workbench=workbench, **kwargs) return new_func + +def require_admin_login(controller): + """ + Require an login from an administrator. + """ + @wraps(controller) + def new_controller_func(request, *args, **kwargs): + if request.user and \ + not request.user.is_admin: + raise Forbidden() + elif not request.user: + next_url = urljoin( + request.urlgen('mediagoblin.auth.login', + qualified=True), + request.url) + + return redirect(request, 'mediagoblin.auth.login', + next=next_url) + + return controller(request, *args, **kwargs) + + return new_controller_func + diff --git a/mediagoblin/user_pages/forms.py b/mediagoblin/user_pages/forms.py index effe49e1..284dd7b8 100644 --- a/mediagoblin/user_pages/forms.py +++ b/mediagoblin/user_pages/forms.py @@ -59,9 +59,3 @@ class MediaReportForm(wtforms.Form): report_reason = wtforms.TextAreaField('Reason for Reporting') media_entry_id = wtforms.IntegerField() reporter_id = wtforms.IntegerField() - -class ReportForm(wtforms.Form): - report_reason = wtforms.TextAreaField('Reason for Reporting') - media_entry_id = wtforms.IntegerField() - reporter_id = wtforms.IntegerField() - comment_id = wtforms.IntegerField() diff --git a/mediagoblin/user_pages/lib.py b/mediagoblin/user_pages/lib.py index 9c8ddee6..557c4853 100644 --- a/mediagoblin/user_pages/lib.py +++ b/mediagoblin/user_pages/lib.py @@ -79,12 +79,15 @@ def add_media_to_collection(collection, media, note=None, commit=True): def build_report_form(form_dict): """ - :param form_dict should be an ImmutableMultiDict object which is what is - returned from 'request.form.' The Object should have valid keys - matching the fields in either MediaReportForm or CommentReportForm + This function is used to convert a form dictionary (from a User filing a + report) into either a MediaReport or CommentReport object. - :returns either of MediaReport or a CommentReport object that has not been saved. - In case of an improper form_dict, returns None + :param form_dict should be an ImmutableMultiDict object as is returned from + 'request.form.' The Object should have valid keys matching the fields + in either MediaReportForm or CommentReportForm + + :returns either of MediaReport or a CommentReport object that has not been + saved. In case of an improper form_dict, returns None """ if 'comment_id' in form_dict.keys(): report_form = user_forms.CommentReportForm(form_dict) @@ -92,6 +95,7 @@ def build_report_form(form_dict): report_form = user_forms.MediaReportForm(form_dict) else: return None + if report_form.validate() and 'comment_id' in form_dict.keys(): report_model = CommentReport() report_model.comment_id = report_form.comment_id.data @@ -100,6 +104,7 @@ def build_report_form(form_dict): report_model.media_entry_id = report_form.media_entry_id.data else: return None + report_model.report_content = report_form.report_reason.data or u'' report_model.reporter_id = report_form.reporter_id.data return report_model diff --git a/mediagoblin/user_pages/views.py b/mediagoblin/user_pages/views.py index 94cccc66..a0eb67db 100644 --- a/mediagoblin/user_pages/views.py +++ b/mediagoblin/user_pages/views.py @@ -20,7 +20,7 @@ import datetime from mediagoblin import messages, mg_globals from mediagoblin.db.models import (MediaEntry, MediaTag, Collection, CollectionItem, User, MediaComment, - CommentReport, MediaReport) + CommentReport, MediaReport, Group) from mediagoblin.tools.response import render_to_response, render_404, \ redirect, redirect_obj from mediagoblin.tools.translate import pass_to_ugettext as _ @@ -30,7 +30,7 @@ from mediagoblin.user_pages.lib import (send_comment_email, build_report_form, add_media_to_collection) from mediagoblin.decorators import (uses_pagination, get_user_media_entry, - get_media_entry_by_id, + get_media_entry_by_id, user_in_group, require_active_login, user_may_delete_media, user_may_alter_collection, get_user_collection, get_user_collection_item, active_user_from_url, get_media_comment_by_id) @@ -621,22 +621,26 @@ def processing_panel(request): @require_active_login @get_user_media_entry -def file_a_report(request, media, comment=None): +@user_in_group(u'reporter') +def file_a_report(request, media, comment=None, required_group=1): if request.method == "POST": report_form = build_report_form(request.form) report_form.save() + return redirect( - request, - 'index') + request, + 'index') + if comment is not None: context = {'media': media, - 'comment':comment} + 'comment':comment} else: context = {'media': media} + return render_to_response( - request, - 'mediagoblin/user_pages/report.html', - context) + request, + 'mediagoblin/user_pages/report.html', + context) @require_active_login @get_user_media_entry From 3fb96fc97800ae032e599006e3f49ffd69926c88 Mon Sep 17 00:00:00 2001 From: tilly-Q Date: Wed, 3 Jul 2013 14:46:21 -0400 Subject: [PATCH 03/25] This was a simple commit. I changed all references to Groups into Privileges so as to not conflict with the new federated groups which are also being written. I also fixed up some of the code in the user_in_group/user_has_privilege decor- ator. Users are now assigned the default privileges when they sign up, and ass- iged active once they are activated. I updated the gmg command makeadmin to use my groups as well. Lastly, I added the decorator to various views, requiring th- at users belong to appropriate groups to access pages. --\ mediagoblin/auth/tools.py --| Added code to assign new users to default privileges --\ mediagoblin/auth/views.py --| Added code to assign users to u'active' privilege once the email | verification is complete --\ mediagoblin/db/migrations.py --| Renamed Group class to Privilege class --\ mediagoblin/db/models.py --| Renamed Group class to Privilege class --\ mediagoblin/decorators.py --| Renamed function based on the Group->Privilege change --| Rewrote the function to be, ya know, functional --\ mediagoblin/gmg_commands/users.py --| Changed the 'makeadmin' command to add the target user to the admin | privilege group as well as affecting 'is_admin' column --\ mediagoblin/submit/views.py --| Added the requirement that a user has the 'uploader' privilege in order | to submit new media. --\ mediagoblin/user_pages/views.py --| Added the requirement that a user has the 'commenter' privilege in order | to make a comment. --| Added the requirement that a user has the 'reporter' privilege in order | to submit new reports. --| Got rid of some vestigial code in the file_a_report function. --- mediagoblin/auth/tools.py | 10 +++++++++- mediagoblin/auth/views.py | 5 ++++- mediagoblin/db/migrations.py | 20 +++++++++---------- mediagoblin/db/models.py | 32 +++++++++++++++---------------- mediagoblin/decorators.py | 20 +++++++++---------- mediagoblin/gmg_commands/users.py | 4 ++++ mediagoblin/submit/views.py | 3 ++- mediagoblin/user_pages/views.py | 9 +++++---- 8 files changed, 59 insertions(+), 44 deletions(-) diff --git a/mediagoblin/auth/tools.py b/mediagoblin/auth/tools.py index db6b6e37..39b349de 100644 --- a/mediagoblin/auth/tools.py +++ b/mediagoblin/auth/tools.py @@ -22,7 +22,7 @@ from sqlalchemy import or_ from mediagoblin import mg_globals from mediagoblin.auth import lib as auth_lib -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 @@ -130,6 +130,14 @@ def register_user(request, register_form): user.verification_key = unicode(uuid.uuid4()) user.save() + # give the user the default privileges + default_privileges = [ + Privilege.query.filter(Privilege.privilege_name==u'commenter').first(), + Privilege.query.filter(Privilege.privilege_name==u'uploader').first(), + Privilege.query.filter(Privilege.privilege_name==u'reporter').first()] + user.all_privileges += default_privileges + user.save() + # log the user in request.session['user_id'] = unicode(user.id) request.session.save() diff --git a/mediagoblin/auth/views.py b/mediagoblin/auth/views.py index bb7bda77..1c346556 100644 --- a/mediagoblin/auth/views.py +++ b/mediagoblin/auth/views.py @@ -18,7 +18,7 @@ import uuid import datetime from mediagoblin import messages, mg_globals -from mediagoblin.db.models import User +from mediagoblin.db.models import User, Privilege from mediagoblin.tools.response import render_to_response, redirect, render_404 from mediagoblin.tools.translate import pass_to_ugettext as _ from mediagoblin.tools.mail import email_debug_message @@ -124,6 +124,9 @@ def verify_email(request): user.status = u'active' user.email_verified = True user.verification_key = None + user.all_privileges.append( + Privilege.query.filter( + Privilege.privilege_name==u'active').first()) user.save() diff --git a/mediagoblin/db/migrations.py b/mediagoblin/db/migrations.py index 5e9a71d4..053f3db2 100644 --- a/mediagoblin/db/migrations.py +++ b/mediagoblin/db/migrations.py @@ -26,7 +26,7 @@ from sqlalchemy.sql import and_ from migrate.changeset.constraint import UniqueConstraint from mediagoblin.db.migration_tools import RegisterMigration, inspect_table -from mediagoblin.db.models import MediaEntry, Collection, User, MediaComment, Group +from mediagoblin.db.models import MediaEntry, Collection, User, MediaComment, Privilege MIGRATIONS = {} @@ -329,23 +329,23 @@ class UserBan_v0(declarative_base()): expiration_date = Column(DateTime) reason = Column(UnicodeText, nullable=False) -class Group_v0(declarative_base()): - __tablename__ = 'core__groups' +class Privilege_v0(declarative_base()): + __tablename__ = 'core__privileges' id = Column(Integer, nullable=False, primary_key=True, unique=True) - group_name = Column(Unicode, nullable=False) + privilege_name = Column(Unicode, nullable=False) -class GroupUserAssociation_v0(declarative_base()): - __tablename__ = 'core__group_user_associations' +class PrivilegeUserAssociation_v0(declarative_base()): + __tablename__ = 'core__privileges_users' group_id = Column( - 'core__group_id', + 'core__privilege_id', Integer, ForeignKey(User.id), primary_key=True) user_id = Column( 'core__user_id', Integer, - ForeignKey(Group.id), + ForeignKey(Privilege.id), primary_key=True) @RegisterMigration(11, MIGRATIONS) @@ -354,8 +354,8 @@ def create_moderation_tables(db): CommentReport_v0.__table__.create(db.bind) MediaReport_v0.__table__.create(db.bind) UserBan_v0.__table__.create(db.bind) - Group_v0.__table__.create(db.bind) - GroupUserAssociation_v0.__table__.create(db.bind) + Privilege_v0.__table__.create(db.bind) + PrivilegeUserAssociation_v0.__table__.create(db.bind) db.commit() diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index 28e01a85..e0419c92 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -559,50 +559,50 @@ class UserBan(Base): reason = Column(UnicodeText, nullable=False) -class Group(Base): - __tablename__ = 'core__groups' +class Privilege(Base): + __tablename__ = 'core__privileges' id = Column(Integer, nullable=False, primary_key=True) - group_name = Column(Unicode, nullable=False, unique=True) + privilege_name = Column(Unicode, nullable=False, unique=True) all_users = relationship( User, - backref='all_groups', - secondary="core__group_user_associations") + backref='all_privileges', + secondary="core__privileges_users") - def __init__(self, group_name): - self.group_name = group_name + def __init__(self, privilege_name): + self.privilege_name = privilege_name def __repr__(self): - return "" % (self.group_name) + return "" % (self.privilege_name) -class GroupUserAssociation(Base): - __tablename__ = 'core__group_user_associations' +class PrivilegeUserAssociation(Base): + __tablename__ = 'core__privileges_users' - group_id = Column( - 'core__group_id', + privilege_id = Column( + 'core__privilege_id', Integer, ForeignKey(User.id), primary_key=True) user_id = Column( 'core__user_id', Integer, - ForeignKey(Group.id), + ForeignKey(Privilege.id), primary_key=True) -group_foundations = [[u'admin'], [u'moderator'], [u'commenter'], [u'uploader'],[u'reporter'],[u'active']] +privilege_foundations = [[u'admin'], [u'moderator'], [u'commenter'], [u'uploader'],[u'reporter'],[u'active']] MODELS = [ User, MediaEntry, Tag, MediaTag, MediaComment, Collection, CollectionItem, MediaFile, FileKeynames, MediaAttachmentFile, ProcessingMetaData, ReportBase, - CommentReport, MediaReport, UserBan, Group, GroupUserAssociation] + CommentReport, MediaReport, UserBan, Privilege, PrivilegeUserAssociation] # Foundations are the default rows that are created immediately after the tables are initialized. Each entry to # this dictionary should be in the format of # ModelObject:List of Rows # (Each Row must be a list of parameters that can create and instance of the ModelObject) # -FOUNDATIONS = {Group:group_foundations} +FOUNDATIONS = {Privilege:privilege_foundations} ###################################################### # Special, migrations-tracking table diff --git a/mediagoblin/decorators.py b/mediagoblin/decorators.py index d54bf050..206957fa 100644 --- a/mediagoblin/decorators.py +++ b/mediagoblin/decorators.py @@ -21,7 +21,7 @@ from werkzeug.exceptions import Forbidden, NotFound from werkzeug.urls import url_quote from mediagoblin import mg_globals as mgg -from mediagoblin.db.models import MediaEntry, User, MediaComment, Group +from mediagoblin.db.models import MediaEntry, User, MediaComment, Privilege from mediagoblin.tools.response import redirect, render_404 @@ -63,25 +63,23 @@ def active_user_from_url(controller): return wrapper -def user_in_group(group_name): +def user_has_privilege(privilege_name): #TODO handle possible errors correctly - def user_in_group_decorator(controller): + def user_has_privilege_decorator(controller): @wraps(controller) - def wrapper(request, *args, **kwargs): user_id = request.user.id - group = Group.query.filter( - Group.group_name==group_name).first() - if not (group.query.filter( - Group.all_users.any( - User.id==user_id)).count()): - + privileges_of_user = Privilege.query.filter( + Privilege.all_users.any( + User.id==user_id)) + if not privileges_of_user.filter( + Privilege.privilege_name==privilege_name).count(): raise Forbidden() return controller(request, *args, **kwargs) return wrapper - return user_in_group_decorator + return user_has_privilege_decorator def user_may_delete_media(controller): diff --git a/mediagoblin/gmg_commands/users.py b/mediagoblin/gmg_commands/users.py index 024c8498..ccc4da73 100644 --- a/mediagoblin/gmg_commands/users.py +++ b/mediagoblin/gmg_commands/users.py @@ -74,6 +74,10 @@ def makeadmin(args): user = db.User.one({'username': unicode(args.username.lower())}) if user: user.is_admin = True + user.all_privileges.append( + db.Privilege.one({ + 'privilege_name':u'admin'}) + ) user.save() print 'The user is now Admin' else: diff --git a/mediagoblin/submit/views.py b/mediagoblin/submit/views.py index a70c89b4..11707a03 100644 --- a/mediagoblin/submit/views.py +++ b/mediagoblin/submit/views.py @@ -26,7 +26,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, \ @@ -36,6 +36,7 @@ from mediagoblin.submit.lib import check_file_field, prepare_queue_task, \ @require_active_login +@user_has_privilege(u'uploader') def submit_start(request): """ First view for submitting a file. diff --git a/mediagoblin/user_pages/views.py b/mediagoblin/user_pages/views.py index a0eb67db..abf5e5c1 100644 --- a/mediagoblin/user_pages/views.py +++ b/mediagoblin/user_pages/views.py @@ -20,7 +20,7 @@ import datetime from mediagoblin import messages, mg_globals from mediagoblin.db.models import (MediaEntry, MediaTag, Collection, CollectionItem, User, MediaComment, - CommentReport, MediaReport, Group) + CommentReport, MediaReport) from mediagoblin.tools.response import render_to_response, render_404, \ redirect, redirect_obj from mediagoblin.tools.translate import pass_to_ugettext as _ @@ -30,7 +30,7 @@ from mediagoblin.user_pages.lib import (send_comment_email, build_report_form, add_media_to_collection) from mediagoblin.decorators import (uses_pagination, get_user_media_entry, - get_media_entry_by_id, user_in_group, + get_media_entry_by_id, user_has_privilege, require_active_login, user_may_delete_media, user_may_alter_collection, get_user_collection, get_user_collection_item, active_user_from_url, get_media_comment_by_id) @@ -152,6 +152,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. @@ -621,8 +622,8 @@ def processing_panel(request): @require_active_login @get_user_media_entry -@user_in_group(u'reporter') -def file_a_report(request, media, comment=None, required_group=1): +@user_has_privilege(u'reporter') +def file_a_report(request, media, comment=None): if request.method == "POST": report_form = build_report_form(request.form) report_form.save() From 3ce0c6113eee9d291a8cba7d398a3d3114792cc9 Mon Sep 17 00:00:00 2001 From: tilly-Q Date: Mon, 8 Jul 2013 14:20:28 -0400 Subject: [PATCH 04/25] This update I mostly did work on the templates for the admin pages. I did a co- -uple other small changes. I changed the information around the media processi- ng panel to be more specific, since it was written when it was the only admin page. Git didn't catch this, but I renamed the templates, so mediagoblin/templ- ates/admin/user.html now referrs to the page which shows the details of a spec- ific user. The list view pages are now named ELEMENT_panel.html(ie. user_panel) I also added a column reported_user_id to the ReportBase table, and had to add to Report filing to make sure that column gets created. Also I moved the report media button (on a media page) to the sidebar, though it still needs some form- atting --\ mediagoblin/static/images/icon_clipboard.png --| Added this image for use in template mediagoblin/admin/report.html. --| Distributed by the GNOME project http://www.gnome.org --| Under a GNU LGPL v.3 or Creative Commons BY-SA 3.0 license. --| I'm still trying to figure out the appropriate way to attribute this in | the code --\ mediagoblin/templates/mediagoblin/admin/media_panel.html --| This template is actually the template formerly know as media.html. I | renamed it for clarity --\ mediagoblin/templates/mediagoblin/admin/report_panel.html --| This template is actually the template formerly know as report.html. I | renamed it for clarity --\ mediagoblin/templates/mediagoblin/admin/user_panel.html --| This template is actually the template formerly know as user.html. I renam- | -ed it for clarity --\ mediagoblin/templates/mediagoblin/utils/report.html --| This template is included in the media_home page. It is the report media | button. I figured I'd write it like this in case it got more complicated. --\ mediagoblin/admin/routing.py --| I changed the routing path /a/panel to /a/media for specificity --\ mediagoblin/admin/views.py --| I renamed admin_processing_panel to admin_media_processing_panel --| I wrote a new view function admin_reports_detail --| I wrote a new view function admin_users_detail --\ mediagoblin/db/migrations.py --| I added in the column reported_user_id to the ReportBase_v0 class --\ mediagoblin/db/models.py --| I added in the column reported_user_id to the ReportBase class --\ mediagoblin/static/css/base.css --| I added in css classes to display a report. Right now, they are just echo- | -ing the ways comments are displayed, but with the link in another color --\ mediagoblin/templates/mediagoblin/admin/report.html --| Created this new template (although git doesn't realize it) to show the de- | -tails of a specific report, indicated in the URL --\ mediagoblin/templates/mediagoblin/admin/user.html --| Created this new template (although git doesn't realize it) to show the de- | -tails of a specific user, indicated in the URL --\ mediagoblin/templates/mediagoblin/base.html --| Redirected the link from /a/panel to /a/media --\ mediagoblin/templates/mediagoblin/user_pages/media.html --| Moved the media report button to the sidebar --\ mediagoblin/user_pages/lib.py --| Changed the creation of reports, so that they also assign a column for rep- | -orted_user_id. --- mediagoblin/admin/routing.py | 14 +- mediagoblin/admin/views.py | 49 +++++- mediagoblin/db/migrations.py | 6 +- mediagoblin/db/mixin.py | 1 - mediagoblin/db/models.py | 12 +- mediagoblin/decorators.py | 1 - mediagoblin/static/css/base.css | 36 ++++- mediagoblin/static/images/icon_clipboard.png | Bin 0 -> 682 bytes .../admin/{panel.html => media_panel.html} | 0 .../templates/mediagoblin/admin/report.html | 143 ++++++++--------- .../mediagoblin/admin/report_panel.html | 95 +++++++++++ .../templates/mediagoblin/admin/user.html | 149 ++++++++++++++---- .../mediagoblin/admin/user_panel.html | 55 +++++++ mediagoblin/templates/mediagoblin/base.html | 2 +- .../mediagoblin/user_pages/media.html | 13 +- .../templates/mediagoblin/utils/report.html | 33 ++++ mediagoblin/user_pages/lib.py | 7 +- 17 files changed, 471 insertions(+), 145 deletions(-) create mode 100644 mediagoblin/static/images/icon_clipboard.png rename mediagoblin/templates/mediagoblin/admin/{panel.html => media_panel.html} (100%) create mode 100644 mediagoblin/templates/mediagoblin/admin/report_panel.html create mode 100644 mediagoblin/templates/mediagoblin/admin/user_panel.html create mode 100644 mediagoblin/templates/mediagoblin/utils/report.html diff --git a/mediagoblin/admin/routing.py b/mediagoblin/admin/routing.py index d5edac0f..c7ca5b92 100644 --- a/mediagoblin/admin/routing.py +++ b/mediagoblin/admin/routing.py @@ -15,12 +15,18 @@ # along with this program. If not, see . admin_routes = [ - ('mediagoblin.admin.panel', - '/panel', - 'mediagoblin.admin.views:admin_processing_panel'), + ('mediagoblin.admin.media_panel', + '/media', + 'mediagoblin.admin.views:admin_media_processing_panel'), ('mediagoblin.admin.users', '/users', 'mediagoblin.admin.views:admin_users_panel'), ('mediagoblin.admin.reports', '/reports', - 'mediagoblin.admin.views:admin_reports_panel')] + 'mediagoblin.admin.views:admin_reports_panel'), + ('mediagoblin.admin.users_detail', + '/users/', + 'mediagoblin.admin.views:admin_users_detail'), + ('mediagoblin.admin.reports_detail', + '/reports/', + 'mediagoblin.admin.views:admin_reports_detail')] diff --git a/mediagoblin/admin/views.py b/mediagoblin/admin/views.py index 7a4dfbd4..97970577 100644 --- a/mediagoblin/admin/views.py +++ b/mediagoblin/admin/views.py @@ -16,12 +16,13 @@ from werkzeug.exceptions import Forbidden -from mediagoblin.db.models import MediaEntry, User, MediaComment, CommentReport, ReportBase +from mediagoblin.db.models import (MediaEntry, User, MediaComment, \ + CommentReport, ReportBase, Privilege) from mediagoblin.decorators import require_admin_login from mediagoblin.tools.response import render_to_response @require_admin_login -def admin_processing_panel(request): +def admin_media_processing_panel(request): ''' Show the global media processing panel for this instance ''' @@ -38,7 +39,7 @@ def admin_processing_panel(request): # Render to response return render_to_response( request, - 'mediagoblin/admin/panel.html', + 'mediagoblin/admin/media_panel.html', {'processing_entries': processing_entries, 'failed_entries': failed_entries, 'processed_entries': processed_entries}) @@ -50,11 +51,29 @@ def admin_users_panel(request): ''' user_list = User.query - # Render to response + return render_to_response( + request, + 'mediagoblin/admin/user_panel.html', + {'user_list': user_list}) + +@require_admin_login +def admin_users_detail(request): + ''' + Shows details about a particular user. + ''' + user = User.query.filter_by(username=request.matchdict['user']).first() + privileges = Privilege.query + active_reports = user.reports_filed_on.filter( + ReportBase.resolved==None).limit(5) + closed_reports = user.reports_filed_on.filter( + ReportBase.resolved!=None).all() + return render_to_response( request, 'mediagoblin/admin/user.html', - {'user_list': user_list}) + {'user':user, + 'privileges':privileges, + 'reports':active_reports}) @require_admin_login def admin_reports_panel(request): @@ -72,7 +91,25 @@ def admin_reports_panel(request): # Render to response return render_to_response( request, - 'mediagoblin/admin/report.html', + 'mediagoblin/admin/report_panel.html', {'report_list':report_list, 'closed_report_list':closed_report_list}) +@require_admin_login +def admin_reports_detail(request): + report = ReportBase.query.get(request.matchdict['report_id']) + if report.discriminator == 'comment_report': + comment = MediaComment.query.get(report.comment_id) + media_entry = None + elif report.discriminator == 'media_report': + media_entry = MediaEntry.query.get(report.media_entry_id) + comment = None + + return render_to_response( + request, + 'mediagoblin/admin/report.html', + {'report':report, + 'media_entry':media_entry, + 'comment':comment}) + + diff --git a/mediagoblin/db/migrations.py b/mediagoblin/db/migrations.py index 053f3db2..a32f5932 100644 --- a/mediagoblin/db/migrations.py +++ b/mediagoblin/db/migrations.py @@ -26,7 +26,8 @@ from sqlalchemy.sql import and_ from migrate.changeset.constraint import UniqueConstraint from mediagoblin.db.migration_tools import RegisterMigration, inspect_table -from mediagoblin.db.models import MediaEntry, Collection, User, MediaComment, Privilege +from mediagoblin.db.models import (MediaEntry, Collection, User, + MediaComment, Privilege, ReportBase) MIGRATIONS = {} @@ -296,6 +297,7 @@ class ReportBase_v0(declarative_base()): 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) resolved = Column(DateTime) discriminator = Column('type', Unicode(50)) @@ -357,5 +359,3 @@ def create_moderation_tables(db): Privilege_v0.__table__.create(db.bind) PrivilegeUserAssociation_v0.__table__.create(db.bind) db.commit() - - diff --git a/mediagoblin/db/mixin.py b/mediagoblin/db/mixin.py index 9f566e36..70c9dd41 100644 --- a/mediagoblin/db/mixin.py +++ b/mediagoblin/db/mixin.py @@ -45,7 +45,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. diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index e0419c92..e4c97a2c 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -496,8 +496,16 @@ class ReportBase(Base): User, backref=backref("reports_filed_by", lazy="dynamic", - cascade="all, delete-orphan")) + 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()) resolved = Column(DateTime) discriminator = Column('type', Unicode(50)) @@ -590,7 +598,7 @@ class PrivilegeUserAssociation(Base): primary_key=True) -privilege_foundations = [[u'admin'], [u'moderator'], [u'commenter'], [u'uploader'],[u'reporter'],[u'active']] +privilege_foundations = [[u'admin'], [u'moderator'], [u'uploader'],[u'reporter'], [u'commenter'] ,[u'active']] MODELS = [ User, MediaEntry, Tag, MediaTag, MediaComment, Collection, CollectionItem, diff --git a/mediagoblin/decorators.py b/mediagoblin/decorators.py index 206957fa..fefbccef 100644 --- a/mediagoblin/decorators.py +++ b/mediagoblin/decorators.py @@ -64,7 +64,6 @@ def active_user_from_url(controller): return wrapper def user_has_privilege(privilege_name): -#TODO handle possible errors correctly def user_has_privilege_decorator(controller): @wraps(controller) def wrapper(request, *args, **kwargs): diff --git a/mediagoblin/static/css/base.css b/mediagoblin/static/css/base.css index 5b8226e6..1cded530 100644 --- a/mediagoblin/static/css/base.css +++ b/mediagoblin/static/css/base.css @@ -346,40 +346,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; } @@ -397,6 +397,11 @@ textarea#comment_content { padding-right: 6px; } + +a.report_authorlink, a.report_whenlink { + color: #D486B1; +} + /* media galleries */ .media_thumbnail { @@ -597,6 +602,21 @@ table.media_panel th { text-align: left; } +/* admin 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; +} /* Delete panel */ diff --git a/mediagoblin/static/images/icon_clipboard.png b/mediagoblin/static/images/icon_clipboard.png new file mode 100644 index 0000000000000000000000000000000000000000..6f94498b61f597a96617b8a8f85a88b193af4fe6 GIT binary patch literal 682 zcmV;b0#*HqP)Px#24YJ`L;&Rg;{eOPWrh&|000SaNLh0L01ejw01ejxLMWSf00007bV*G`2i*q+ z5f}(|u25e900JsWL_t(I%Y~B7OO-(s#(y*K+-rqt3sY?2qD4Zh7A*rI>iThY+b%)~ zr7oI_AWDL8R}0IJThRaDvLtO26)oCCn>G=lM2Li>_Ho~tIj6;YeQ##ez`($q^PA^+ z<{196W~z6mVhA@xBr(Q>Vxehrta9bf-)haXdp%W&aYUjxP*jzdwbP7`UBy~UjFI{3 zG$-{DqS?J-LXwzj`R1Dm=WDgm6K6&f>xuxZtZlKm{EiPF-y04{is zB6)FZ;XAjcruKO4?koZsKoNrXSq2N&k7>`jNxrSV0$^k7C#&%&>)L?`pxW@Rii*_F zf^&u0Oo2pH6@Wv{O}zfn#>L?g{_RPKzRUbY}QK#*eUfP;Jo*U z#N8I^3X;UJ^kfNR4F*#OKwp1v_K^3P0{KIM4;jE(OL?%2wF%bR%!(>eq@f_jxF^K? zkBwpR@nY6&EklDtD3WIqyvG4ZjJrduD-aQS`+DoITYT{OTu=b-JteflFg(0)7z z=-YJw4mP*^a}Go#nA!9D=VMGSp=x(7w%X3JQS3(YscURe&D5%DVPA3X7u{-03R6w& QBLDyZ07*qoM6N<$f<&G%od5s; literal 0 HcmV?d00001 diff --git a/mediagoblin/templates/mediagoblin/admin/panel.html b/mediagoblin/templates/mediagoblin/admin/media_panel.html similarity index 100% rename from mediagoblin/templates/mediagoblin/admin/panel.html rename to mediagoblin/templates/mediagoblin/admin/media_panel.html diff --git a/mediagoblin/templates/mediagoblin/admin/report.html b/mediagoblin/templates/mediagoblin/admin/report.html index ff5cb427..2e802198 100644 --- a/mediagoblin/templates/mediagoblin/admin/report.html +++ b/mediagoblin/templates/mediagoblin/admin/report.html @@ -15,81 +15,76 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . #} -{% extends "mediagoblin/base.html" %} +{%- extends "mediagoblin/base.html" %} -{% block title -%} - {% trans %}Report panel{% endtrans %} — {{ super() }} -{%- endblock %} - -{% block mediagoblin_content %} - -

{% trans %}Report panel{% endtrans %}

- -

- {% trans %}Here you can look up users in order to take punitive actions on them.{% endtrans %} -

- -

{% trans %}Reports Filed on Comments{% endtrans %}

- -{% if report_list.count() %} - - - - - - - - - - - {% for report in report_list %} - - {% if report.discriminator == "comment_report" %} - - - - - - - - {% elif report.discriminator == "media_report" %} - - - - - - - - {% endif %} - - {% endfor %} -
IDReport TypeOffenderWhen ReportedReported ByReasonReported Comment or Media Entry
{{ report.id }}Comment Report{{ report.comment.get_author.username }}{{ report.created.strftime("%F %R") }}{{ report.reporter.username }}{{ report.report_content }}{{ report.comment.get_media_entry.title }}{{ report.id }}Media Report{{ report.media_entry.get_uploader.username }}{{ report.created.strftime("%F %R") }}{{ report.reporter.username }}{{ report.report_content[0:20] }}...{{ report.media_entry.title }}
+{%- block mediagoblin_content %} +{% if not report %} + Sorry, no such report found. {% else %} -

{% trans %}No open reports found.{% endtrans %}

+

Report #{{ report.id }}

+ {% if comment %} + Reported comment: + {% set reported_user = comment.get_author %} + + {% elif media_entry %} + +
+ {% endif %} + Reason for report: + {% endif %} -

{% trans %}Closed Reports on Comments{% endtrans %}

-{% if closed_report_list.count() %} - - - - - - - - - - {% for report in closed_report_list %} - - - - - - - - - {% endfor %} -
IDOffenderWhen ReportedReported ByReasonComment Posted On
{{ report.id }}{{ report.comment.get_author.username }}{{ report.created.strftime("%F %R") }}{{ report.reporter.username }}{{ report.report_content }}{{ report.comment.get_media_entry.title }}
-{% else %} -

{% trans %}No closed reports found.{% endtrans %}

-{% endif %} {% endblock %} diff --git a/mediagoblin/templates/mediagoblin/admin/report_panel.html b/mediagoblin/templates/mediagoblin/admin/report_panel.html new file mode 100644 index 00000000..30194577 --- /dev/null +++ b/mediagoblin/templates/mediagoblin/admin/report_panel.html @@ -0,0 +1,95 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +#} +{% extends "mediagoblin/base.html" %} + +{% block title -%} + {% trans %}Report panel{% endtrans %} — {{ super() }} +{%- endblock %} + +{% block mediagoblin_content %} + +

{% trans %}Report panel{% endtrans %}

+ +

+ {% trans %}Here you can look up users in order to take punitive actions on them.{% endtrans %} +

+ +

{% trans %}Reports Filed on Comments{% endtrans %}

+ +{% if report_list.count() %} + + + + + + + + + + + {% for report in report_list %} + + {% if report.discriminator == "comment_report" %} + + + + + + + + {% elif report.discriminator == "media_report" %} + + + + + + + + {% endif %} + + {% endfor %} +
IDReport TypeOffenderWhen ReportedReported ByReasonReported Comment or Media Entry
{{ report.id }}Comment Report{{ report.comment.get_author.username }}{{ report.created.strftime("%F %R") }}{{ report.reporter.username }}{{ report.report_content }}{{ report.comment.get_media_entry.title }}{{ report.id }}Media Report{{ report.media_entry.get_uploader.username }}{{ report.created.strftime("%F %R") }}{{ report.reporter.username }}{{ report.report_content[0:20] }}...{{ report.media_entry.title }}
+{% else %} +

{% trans %}No open reports found.{% endtrans %}

+{% endif %} +

{% trans %}Closed Reports on Comments{% endtrans %}

+{% if closed_report_list.count() %} + + + + + + + + + + {% for report in closed_report_list %} + + + + + + + + + {% endfor %} +
IDOffenderWhen ReportedReported ByReasonComment Posted On
{{ report.id }}{{ report.comment.get_author.username }}{{ report.created.strftime("%F %R") }}{{ report.reporter.username }}{{ report.report_content }}{{ report.comment.get_media_entry.title }}
+{% else %} +

{% trans %}No closed reports found.{% endtrans %}

+{% endif %} +{% endblock %} diff --git a/mediagoblin/templates/mediagoblin/admin/user.html b/mediagoblin/templates/mediagoblin/admin/user.html index 6b6d226a..90b3f583 100644 --- a/mediagoblin/templates/mediagoblin/admin/user.html +++ b/mediagoblin/templates/mediagoblin/admin/user.html @@ -17,38 +17,121 @@ #} {% extends "mediagoblin/base.html" %} -{% block title -%} - {% trans %}User panel{% endtrans %} — {{ super() }} -{%- endblock %} -{% block mediagoblin_content %} - -

{% trans %}User panel{% endtrans %}

- -

- {% trans %}Here you can look up users in order to take punitive actions on them.{% endtrans %} -

- -

{% trans %}Active Users{% endtrans %}

- -{% if user_list.count() %} - - - - - - - - {% for user in user_list %} - - - - - - - {% endfor %} -
IDUsernameWhen Joined# of Comments Posted
{{ user.id }}{{ user.username }}{{ user.created.strftime("%F %R") }}{{ user.posted_comments.count() }}
-{% else %} -

{% trans %}No users found.{% endtrans %}

-{% endif %} +{% block title %} + {%- if user -%} + {%- trans username=user.username -%} + User: {{ username }} + {%- endtrans %} — {{ super() }} + {%- else -%} + {{ super() }} + {%- endif -%} +{% endblock %} + + +{% block mediagoblin_content -%} + {# If no user... #} + {% if not user %} +

{% trans %}Sorry, no such user found.{% endtrans %}

+ + {# User exists, but needs verification #} + {% elif user.status == "needs_email_verification" %} +
+

{% trans %}Email verification needed{% endtrans %}

+ +

+ {% trans -%} + Someone has registered an account with this username, but it still has to be activated. + {%- endtrans %} +

+ +

+ {% trans login_url=request.urlgen('mediagoblin.auth.login') -%} + If you are that person but you've lost your verification email, you can log in and resend it. + {%- endtrans %} +

+
+ + {# Active(?) (or at least verified at some point) user, horray! #} + {% else %} +

+ {%- trans username=user.username %}{{ username }}'s profile{% endtrans -%} +

+ + {% if not user.url and not user.bio %} +
+

+ {% trans -%} + This user hasn't filled in their profile (yet). + {%- endtrans %} +

+ {% else %} +
+ {% include "mediagoblin/utils/profile.html" %} + {% if request.user and + (request.user.id == user.id or request.user.is_admin) %} + + {%- trans %}Edit profile{% endtrans -%} + + {% endif %} + {% endif %} +

+ + {%- trans %}Browse collections{% endtrans -%} + +

+
+ {% endif %} + {% if user %} +

{%- trans %}Active Reports on{% endtrans -%} {{ user.username }}

+ {% if reports.count() %} + + + + + + + {% for report in reports %} + + + + + + + {% endfor %} + +
{%- trans %}Report ID{% endtrans -%}{%- trans %}Reported Content{% endtrans -%}{%- trans %}Description of Report{% endtrans -%}
+ + + {%- trans %}Report #{% endtrans -%}{{ report.id }} + + + {% if report.discriminator == "comment_report" %} + {%- trans %}Reported Comment{% endtrans -%} + {% elif report.discriminator == "media_report" %} + {%- trans %}Reported Media Entry{% endtrans -%} + {% endif %} + {{ report.report_content[:21] }}{% if report.report_content|count >20 %}...{% endif %}{%- trans %}Resolve{% endtrans -%}
+ {% else %} + {%- trans %}No active reports filed on{% endtrans -%} {{ user.username }} + {% endif %} + {{ user.username }}'s report history + +

{{ user.username }}'s Privileges

+ + + + + {% for privilege in privileges %} + + + + + + {% endfor %} +
{% trans %}Privilege{% endtrans %}{% trans %}User Has Privilege{% endtrans %}
{{ privilege.privilege_name }}{% if privilege in user.all_privileges %}Yes{% else %}No{% endif %}{% if privilege in user.all_privileges and privilege.id < request.user.get_highest_privilege().id %}{% trans %}Take Away{% endtrans %}{% else %}{% trans %}Give Privilege{% endtrans %}{% endif %}
+ {% endif %} {% endblock %} diff --git a/mediagoblin/templates/mediagoblin/admin/user_panel.html b/mediagoblin/templates/mediagoblin/admin/user_panel.html new file mode 100644 index 00000000..cc965b73 --- /dev/null +++ b/mediagoblin/templates/mediagoblin/admin/user_panel.html @@ -0,0 +1,55 @@ +{# +# 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 . +#} +{% extends "mediagoblin/base.html" %} + +{% block title -%} + {% trans %}User panel{% endtrans %} — {{ super() }} +{%- endblock %} + +{% block mediagoblin_content %} + +

{% trans %}User panel{% endtrans %}

+ +

+ {% trans %}Here you can look up users in order to take punitive actions on them.{% endtrans %} +

+ +

{% trans %}Active Users{% endtrans %}

+ +{% if user_list.count() %} + + + + + + + + {% for user in user_list %} + + + + + + + {% endfor %} +
{% trans %}ID{% endtrans %}{% trans %}Username{% endtrans %}{% trans %}When Joined{% endtrans %}{% trans %}# of Comments Posted{% endtrans %}
{{ user.id }}{{ user.username }}{{ user.created.strftime("%F %R") }}{{ user.posted_comments.count() }}
+{% else %} +

{% trans %}No users found.{% endtrans %}

+{% endif %} +{% endblock %} diff --git a/mediagoblin/templates/mediagoblin/base.html b/mediagoblin/templates/mediagoblin/base.html index 6c7c07d0..e9a18f22 100644 --- a/mediagoblin/templates/mediagoblin/base.html +++ b/mediagoblin/templates/mediagoblin/base.html @@ -104,7 +104,7 @@ {% if request.user.is_admin %}

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

diff --git a/mediagoblin/templates/mediagoblin/user_pages/media.html b/mediagoblin/templates/mediagoblin/user_pages/media.html index 134a80ad..1e64ae07 100644 --- a/mediagoblin/templates/mediagoblin/user_pages/media.html +++ b/mediagoblin/templates/mediagoblin/user_pages/media.html @@ -95,17 +95,6 @@ {% trans %}Add a comment{% endtrans %} {% endif %} - - {% trans %}Report media{% endtrans %} - {% if request.user %}
. +#} + +{% block report_content -%} +

+ + {% trans %}Report media{% endtrans %} + +

+{% endblock %} diff --git a/mediagoblin/user_pages/lib.py b/mediagoblin/user_pages/lib.py index 557c4853..2558b066 100644 --- a/mediagoblin/user_pages/lib.py +++ b/mediagoblin/user_pages/lib.py @@ -19,7 +19,8 @@ from mediagoblin.tools.template import render_template from mediagoblin.tools.translate import pass_to_ugettext as _ from mediagoblin import mg_globals from mediagoblin.db.base import Session -from mediagoblin.db.models import CollectionItem, MediaReport, CommentReport +from mediagoblin.db.models import (CollectionItem, MediaReport, CommentReport, + MediaComment, MediaEntry) from mediagoblin.user_pages import forms as user_forms @@ -99,9 +100,13 @@ def build_report_form(form_dict): if report_form.validate() and 'comment_id' in form_dict.keys(): report_model = CommentReport() report_model.comment_id = report_form.comment_id.data + report_model.reported_user_id = MediaComment.query.get( + report_model.comment_id).get_author.id elif report_form.validate() and 'media_entry_id' in form_dict.keys(): report_model = MediaReport() report_model.media_entry_id = report_form.media_entry_id.data + report_model.reported_user_id = MediaEntry.query.get( + report_model.media_entry_id).get_uploader.id else: return None From 650a0aa90dacd97286a081b0b7c11abb04ba8767 Mon Sep 17 00:00:00 2001 From: tilly-Q Date: Mon, 8 Jul 2013 14:31:28 -0400 Subject: [PATCH 05/25] I just added the attribution for the clipboard image to the code. --- mediagoblin/templates/mediagoblin/admin/report.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mediagoblin/templates/mediagoblin/admin/report.html b/mediagoblin/templates/mediagoblin/admin/report.html index 2e802198..ef90df40 100644 --- a/mediagoblin/templates/mediagoblin/admin/report.html +++ b/mediagoblin/templates/mediagoblin/admin/report.html @@ -66,7 +66,8 @@
- + Under a GNU LGPL v.3 or Creative Commons BY-SA 3.0 license. Distributed by the GNOME project http://www.gnome.org From 6bba33d7e6fbb0cedc39f9a11f816fe5bd372ae7 Mon Sep 17 00:00:00 2001 From: tilly-Q Date: Wed, 17 Jul 2013 16:16:07 -0400 Subject: [PATCH 06/25] Whew. This is a big update. I did some significant keeping work. I moved all of the folders and enpoints labeled 'admin' to the more accurate term of 'moderat- ion.' I also created the ability for admins and moderators to add or remove pr- ivileges or to ban a user in response to a report. This also meant implementing the UserBan class in various places. I also had to add a column called result to the ReportBase table. This allows the moderator/admin to leave comments when they respond to a report, allowing for archiving of what responses they do/n't take. --\ mediagoblin/db/migrations.py --| Added result column to ReportBase --\ mediagoblin/db/models.py --| Added result column to ReportBase --| Added documentation to tables I had made previously --\ mediagoblin/decorators.py --| Editted the user_has_privilege decorator to check whether a user has been | banned or not --| Created a seperate user_not_banned decorator to prevent banned users from | accessing any pages --| Changed require_admin_login into require_admin_or_moderator login --\ mediagoblin/gmg_commands/users.py --| Made the gmg command `adduser` create a user w/ the appropriate privileges --\ mediagoblin/moderation/routing.py << formerly mediagoblin/admin/routing.py --| Renamed all of the routes from admin -> moderation --\ mediagoblin/routing.py --| Renamed all of the routes from admin -> moderation --\ mediagoblin/moderation/views.py << formerly mediagoblin/admin/views.py --| Renamed all of the routes & functions from admin -> moderation --| Expanded greatly on the moderation_reports_detail view and functionality --| Added in the give_or_take_away_privilege form, however this might be a use- | -less function which I could remove (because privilege changes should happe- | n in response to a report so they can be archived and visible) --\ mediagoblin/static/css/base.css --| Added in a style for the reports_detail page --\ mediagoblin/templates/mediagoblin/base.html --| Renamed all of the routes from admin -> moderation --\ mediagoblin/templates/mediagoblin/moderation/report.html --| Added form to allow moderators and admins to respond to reports. --\ mediagoblin/templates/mediagoblin/moderation/reports_panel.html --| Fixed the table for closed reports --\ mediagoblin/templates/mediagoblin/moderation/user.html --| Added in a table w/ all of the user's privileges and the option to add or | remove them. Again, this is probably vestigial --| Renamed all of the routes from admin -> moderation --\ mediagoblin/templates/mediagoblin/moderation/user_panel.html --| Renamed all of the routes from admin -> moderation --\ mediagoblin/tools/response.py --| Added function render_user_banned, this is the view function for the redir- | -ect that happens when a user tries to access the site whilst banned --\ mediagoblin/user_pages/forms.py --| Added important translate function where I had text --\ mediagoblin/user_pages/lib.py --| Renamed functiion for clarity --\ mediagoblin/user_pages/views.py --| Added the user_not_banned decorator to every view --\ mediagoblin/views.py --| Added the user_not_banned decorator --\ mediagoblin/moderation/forms.py --| Created this new file --\ mediagoblin/templates/mediagoblin/banned.html --| Created this new file --| This is the page which people are redirected to when they access the site | while banned --- mediagoblin/admin/views.py | 115 ---------- mediagoblin/db/migrations.py | 1 + mediagoblin/db/models.py | 48 ++++- mediagoblin/decorators.py | 35 ++- mediagoblin/gmg_commands/users.py | 7 + mediagoblin/{admin => moderation}/__init__.py | 0 mediagoblin/moderation/forms.py | 40 ++++ mediagoblin/moderation/routing.py | 35 +++ mediagoblin/moderation/views.py | 200 ++++++++++++++++++ mediagoblin/routing.py | 4 +- mediagoblin/static/css/base.css | 2 + .../mediagoblin/banned.html} | 28 ++- mediagoblin/templates/mediagoblin/base.html | 5 +- .../{admin => moderation}/media_panel.html | 0 .../{admin => moderation}/report.html | 52 ++++- .../{admin => moderation}/report_panel.html | 39 ++-- .../{admin => moderation}/user.html | 36 +++- .../{admin => moderation}/user_panel.html | 2 +- mediagoblin/tools/response.py | 10 + mediagoblin/user_pages/forms.py | 8 +- mediagoblin/user_pages/lib.py | 2 +- mediagoblin/user_pages/views.py | 25 +-- mediagoblin/views.py | 4 +- 23 files changed, 509 insertions(+), 189 deletions(-) delete mode 100644 mediagoblin/admin/views.py rename mediagoblin/{admin => moderation}/__init__.py (100%) create mode 100644 mediagoblin/moderation/forms.py create mode 100644 mediagoblin/moderation/routing.py create mode 100644 mediagoblin/moderation/views.py rename mediagoblin/{admin/routing.py => templates/mediagoblin/banned.html} (56%) rename mediagoblin/templates/mediagoblin/{admin => moderation}/media_panel.html (100%) rename mediagoblin/templates/mediagoblin/{admin => moderation}/report.html (67%) rename mediagoblin/templates/mediagoblin/{admin => moderation}/report_panel.html (65%) rename mediagoblin/templates/mediagoblin/{admin => moderation}/user.html (74%) rename mediagoblin/templates/mediagoblin/{admin => moderation}/user_panel.html (95%) diff --git a/mediagoblin/admin/views.py b/mediagoblin/admin/views.py deleted file mode 100644 index 97970577..00000000 --- a/mediagoblin/admin/views.py +++ /dev/null @@ -1,115 +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 . - -from werkzeug.exceptions import Forbidden - -from mediagoblin.db.models import (MediaEntry, User, MediaComment, \ - CommentReport, ReportBase, Privilege) -from mediagoblin.decorators import require_admin_login -from mediagoblin.tools.response import render_to_response - -@require_admin_login -def admin_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/admin/media_panel.html', - {'processing_entries': processing_entries, - 'failed_entries': failed_entries, - 'processed_entries': processed_entries}) - -@require_admin_login -def admin_users_panel(request): - ''' - Show the global panel for monitoring users in this instance - ''' - user_list = User.query - - return render_to_response( - request, - 'mediagoblin/admin/user_panel.html', - {'user_list': user_list}) - -@require_admin_login -def admin_users_detail(request): - ''' - Shows details about a particular user. - ''' - user = User.query.filter_by(username=request.matchdict['user']).first() - privileges = Privilege.query - active_reports = user.reports_filed_on.filter( - ReportBase.resolved==None).limit(5) - closed_reports = user.reports_filed_on.filter( - ReportBase.resolved!=None).all() - - return render_to_response( - request, - 'mediagoblin/admin/user.html', - {'user':user, - 'privileges':privileges, - 'reports':active_reports}) - -@require_admin_login -def admin_reports_panel(request): - ''' - Show the global panel for monitoring reports filed against comments or - media entries for this instance. - ''' - report_list = ReportBase.query.filter( - ReportBase.resolved==None).order_by( - ReportBase.created.desc()).limit(10) - closed_report_list = ReportBase.query.filter( - ReportBase.resolved!=None).order_by( - ReportBase.created.desc()).limit(10) - - # Render to response - return render_to_response( - request, - 'mediagoblin/admin/report_panel.html', - {'report_list':report_list, - 'closed_report_list':closed_report_list}) - -@require_admin_login -def admin_reports_detail(request): - report = ReportBase.query.get(request.matchdict['report_id']) - if report.discriminator == 'comment_report': - comment = MediaComment.query.get(report.comment_id) - media_entry = None - elif report.discriminator == 'media_report': - media_entry = MediaEntry.query.get(report.media_entry_id) - comment = None - - return render_to_response( - request, - 'mediagoblin/admin/report.html', - {'report':report, - 'media_entry':media_entry, - 'comment':comment}) - - diff --git a/mediagoblin/db/migrations.py b/mediagoblin/db/migrations.py index a32f5932..3e6791c4 100644 --- a/mediagoblin/db/migrations.py +++ b/mediagoblin/db/migrations.py @@ -300,6 +300,7 @@ class ReportBase_v0(declarative_base()): reported_user_id = Column(Integer, ForeignKey(User.id), nullable=False) created = Column(DateTime, nullable=False, default=datetime.datetime.now) resolved = Column(DateTime) + result = Column(UnicodeText) discriminator = Column('type', Unicode(50)) __mapper_args__ = {'polymorphic_on': discriminator} diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index e4c97a2c..01078db8 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -239,8 +239,8 @@ class MediaEntry(Base, MediaEntryMixin): This will *not* automatically delete unused collections, which can remain empty... - :param del_orphan_tags: True/false if we delete unused Tags too - :param commit: True/False if this should end the db transaction""" + :keyword del_orphan_tags: True/false if we delete unused Tags too + :keyword commit: True/False if this should end the db transaction""" # User's CollectionItems are automatically deleted via "cascade". # Comments on this Media are deleted by cascade, hopefully. @@ -487,6 +487,14 @@ class ProcessingMetaData(Base): class ReportBase(Base): """ + This is the basic report table which the other reports are based off of. + :keyword reporter_id + :keyword report_content + :keyword reported_user_id + :keyword created + :keyword resolved + :keyword result + :keyword discriminator """ __tablename__ = 'core__reports' @@ -508,6 +516,7 @@ class ReportBase(Base): primaryjoin="User.id==ReportBase.reported_user_id") created = Column(DateTime, nullable=False, default=datetime.datetime.now()) resolved = Column(DateTime) + result = Column(UnicodeText) discriminator = Column('type', Unicode(50)) __mapper_args__ = {'polymorphic_on': discriminator} @@ -551,13 +560,13 @@ class UserBan(Base): the reason why they are banned and when (if ever) the ban will be lifted - :param user_id Holds the id of the user this object is + :keyword user_id Holds the id of the user this object is attached to. This is a one-to-one relationship. - :param expiration_date Holds the date that the ban will be lifted. + :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. - :param reason Holds the reason why the user was banned. + :keyword reason Holds the reason why the user was banned. """ __tablename__ = 'core__user_bans' @@ -568,6 +577,17 @@ class UserBan(Base): 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) @@ -578,12 +598,30 @@ class Privilege(Base): secondary="core__privileges_users") def __init__(self, privilege_name): + ''' + Currently consructors are required for tables that are initialized thru + the FOUNDATIONS system. This is because they need to be able to be con- + -structed by a list object holding their arg*s + ''' self.privilege_name = privilege_name def __repr__(self): return "" % (self.privilege_name) + def is_admin_or_moderator(self): + ''' + This method is necessary to check if a user is able to take moderation + actions. + ''' + + return (self.privilege_name==u'admin' or + self.privilege_name==u'moderator') + class PrivilegeUserAssociation(Base): + ''' + This table holds the many-to-many relationship between User and Privilege + ''' + __tablename__ = 'core__privileges_users' privilege_id = Column( diff --git a/mediagoblin/decorators.py b/mediagoblin/decorators.py index fefbccef..b39b36f5 100644 --- a/mediagoblin/decorators.py +++ b/mediagoblin/decorators.py @@ -21,8 +21,9 @@ from werkzeug.exceptions import Forbidden, NotFound from werkzeug.urls import url_quote from mediagoblin import mg_globals as mgg -from mediagoblin.db.models import MediaEntry, User, MediaComment, Privilege -from mediagoblin.tools.response import redirect, render_404 +from mediagoblin.db.models import MediaEntry, User, MediaComment, Privilege, \ + UserBan +from mediagoblin.tools.response import redirect, render_404, render_user_banned def require_active_login(controller): @@ -64,6 +65,7 @@ def active_user_from_url(controller): return wrapper def user_has_privilege(privilege_name): + def user_has_privilege_decorator(controller): @wraps(controller) def wrapper(request, *args, **kwargs): @@ -71,7 +73,9 @@ def user_has_privilege(privilege_name): privileges_of_user = Privilege.query.filter( Privilege.all_users.any( User.id==user_id)) - if not privileges_of_user.filter( + if UserBan.query.filter(UserBan.user_id==user_id).count(): + return render_user_banned(request) + elif not privileges_of_user.filter( Privilege.privilege_name==privilege_name).count(): raise Forbidden() @@ -271,14 +275,18 @@ def get_workbench(func): return new_func -def require_admin_login(controller): +def require_admin_or_moderator_login(controller): """ - Require an login from an administrator. + Require an login from an administrator or a moderator. """ @wraps(controller) def new_controller_func(request, *args, **kwargs): + admin_privilege = Privilege.one({'privilege_name':u'admin'}) + moderator_privilege = Privilege.one({'privilege_name':u'moderator'}) if request.user and \ - not request.user.is_admin: + not admin_privilege in request.user.all_privileges and \ + not moderator_privilege in request.user.all_privileges: + raise Forbidden() elif not request.user: next_url = urljoin( @@ -293,3 +301,18 @@ def require_admin_login(controller): return new_controller_func +def user_not_banned(controller): + """ + 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: + user_banned = UserBan.query.get(request.user.id) + if user_banned: + return render_user_banned(request) + return controller(request, *args, **kwargs) + + return wrapper + diff --git a/mediagoblin/gmg_commands/users.py b/mediagoblin/gmg_commands/users.py index ccc4da73..5c19d3a9 100644 --- a/mediagoblin/gmg_commands/users.py +++ b/mediagoblin/gmg_commands/users.py @@ -55,6 +55,13 @@ def adduser(args): entry.pw_hash = auth_lib.bcrypt_gen_password_hash(args.password) entry.status = u'active' entry.email_verified = True + default_privileges = [ + db.Privilege.one({'privilege_name':u'commenter'}), + db.Privilege.one({'privilege_name':u'uploader'}), + db.Privilege.one({'privilege_name':u'reporter'}), + db.Privilege.one({'privilege_name':u'active'}) +] + entry.all_privileges = default_privileges entry.save() print "User created (and email marked as verified)" diff --git a/mediagoblin/admin/__init__.py b/mediagoblin/moderation/__init__.py similarity index 100% rename from mediagoblin/admin/__init__.py rename to mediagoblin/moderation/__init__.py diff --git a/mediagoblin/moderation/forms.py b/mediagoblin/moderation/forms.py new file mode 100644 index 00000000..0a91b9b4 --- /dev/null +++ b/mediagoblin/moderation/forms.py @@ -0,0 +1,40 @@ +# 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 . + +import wtforms +from mediagoblin.tools.translate import lazy_pass_to_ugettext as _ + +ACTION_CHOICES = [(_(u'takeaway'),_('Take away privilege')), + (_(u'userban'),_('Ban the user')), + (_(u'closereport'),_('Close the report without taking an action'))] + +class PrivilegeAddRemoveForm(wtforms.Form): + giving_privilege = wtforms.HiddenField('',[wtforms.validators.required()]) + privilege_name = wtforms.HiddenField('',[wtforms.validators.required()]) + +class ReportResolutionForm(wtforms.Form): + action_to_resolve = wtforms.RadioField( + _('What action will you take to resolve this report'), + validators=[wtforms.validators.required()], + choices=ACTION_CHOICES) + targeted_user = wtforms.HiddenField('', + validators=[wtforms.validators.required()]) + user_banned_until = wtforms.DateField( + _('User will be banned until:'), + format='%Y-%m-%d', + validators=[wtforms.validators.optional()]) + resolution_content = wtforms.TextAreaField() + diff --git a/mediagoblin/moderation/routing.py b/mediagoblin/moderation/routing.py new file mode 100644 index 00000000..f177c32a --- /dev/null +++ b/mediagoblin/moderation/routing.py @@ -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 . + +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//', + 'mediagoblin.moderation.views:moderation_users_detail'), + ('mediagoblin.moderation.give_or_take_away_privilege', + '/users//privilege/', + 'mediagoblin.moderation.views:give_or_take_away_privilege'), + ('mediagoblin.moderation.reports_detail', + '/reports//', + 'mediagoblin.moderation.views:moderation_reports_detail')] diff --git a/mediagoblin/moderation/views.py b/mediagoblin/moderation/views.py new file mode 100644 index 00000000..6f6318bc --- /dev/null +++ b/mediagoblin/moderation/views.py @@ -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 . + +from werkzeug.exceptions import Forbidden + +from mediagoblin.db.models import (MediaEntry, User, MediaComment, \ + CommentReport, ReportBase, Privilege, \ + UserBan) +from mediagoblin.decorators import (require_admin_or_moderator_login, \ + active_user_from_url) +from mediagoblin.tools.response import render_to_response, redirect +from mediagoblin.moderation import forms as moderation_forms +from datetime import datetime + +@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 + ''' + user_list = User.query + + return render_to_response( + request, + 'mediagoblin/moderation/user_panel.html', + {'user_list': user_list}) + +@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 + + return render_to_response( + request, + 'mediagoblin/moderation/user.html', + {'user':user, + 'privileges':privileges, + 'reports':active_reports}) + +@require_admin_or_moderator_login +def moderation_reports_panel(request): + ''' + Show the global panel for monitoring reports filed against comments or + media entries for this instance. + ''' + report_list = ReportBase.query.filter( + ReportBase.resolved==None).order_by( + ReportBase.created.desc()).limit(10) + closed_report_list = ReportBase.query.filter( + ReportBase.resolved!=None).order_by( + ReportBase.created.desc()).limit(10) + + # Render to response + return render_to_response( + request, + 'mediagoblin/moderation/report_panel.html', + {'report_list':report_list, + 'closed_report_list':closed_report_list}) + +@require_admin_or_moderator_login +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']) + + if request.method == "POST" and form.validate(): + user = User.query.get(form.targeted_user.data) + if form.action_to_resolve.data == u'takeaway': + if report.discriminator == u'comment_report': + privilege = Privilege.one({'privilege_name':u'commenter'}) + form.resolution_content.data += \ + u"
%s took away %s\'s commenting privileges" % ( + request.user.username, + user.username) + else: + privilege = Privilege.one({'privilege_name':u'uploader'}) + form.resolution_content.data += \ + u"
%s took away %s\'s media uploading privileges" % ( + request.user.username, + user.username) + user.all_privileges.remove(privilege) + user.save() + report.result = form.resolution_content.data + report.resolved = datetime.now() + report.save() + + elif form.action_to_resolve.data == u'userban': + reason = form.resolution_content.data + \ + "
"+request.user.username + user_ban = UserBan( + user_id=form.targeted_user.data, + expiration_date=form.user_banned_until.data, + reason= form.resolution_content.data) + user_ban.save() + if not form.user_banned_until == "": + form.resolution_content.data += \ + u"
%s banned user %s until %s." % ( + request.user.username, + user.username, + form.user_banned_until.data) + else: + form.resolution_content.data += \ + u"
%s banned user %s indefinitely." % ( + request.user.username, + user.username, + form.user_banned_until.data) + + report.result = form.resolution_content.data + report.resolved = datetime.now() + report.save() + + else: + pass + + return redirect( + request, + 'mediagoblin.moderation.users_detail', + user=user.username) + + if report.discriminator == 'comment_report': + comment = MediaComment.query.get(report.comment_id) + media_entry = None + elif report.discriminator == 'media_report': + media_entry = MediaEntry.query.get(report.media_entry_id) + comment = None + + form.targeted_user.data = report.reported_user_id + + return render_to_response( + request, + 'mediagoblin/moderation/report.html', + {'report':report, + 'media_entry':media_entry, + 'comment':comment, + 'form':form}) + +@require_admin_or_moderator_login +@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 + ''' + form = moderation_forms.PrivilegeAddRemoveForm(request.form) + if request.method == "POST" and form.validate(): + privilege = Privilege.one({'privilege_name':form.privilege_name.data}) + if privilege in url_user.all_privileges is True: + url_user.all_privileges.remove(privilege) + else: + url_user.all_privileges.append(privilege) + url_user.save() + return redirect( + request, + 'mediagoblin.moderation.users_detail', + user=url_user.username) diff --git a/mediagoblin/routing.py b/mediagoblin/routing.py index a650f22f..0642ad8e 100644 --- a/mediagoblin/routing.py +++ b/mediagoblin/routing.py @@ -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 @@ -28,7 +28,7 @@ _log = logging.getLogger(__name__) def get_url_map(): add_route('index', '/', 'mediagoblin.views:root_view') mount('/auth', auth_routes) - mount('/a', admin_routes) + mount('/mod', moderation_routes) import mediagoblin.submit.routing import mediagoblin.user_pages.routing diff --git a/mediagoblin/static/css/base.css b/mediagoblin/static/css/base.css index 1cded530..343648d8 100644 --- a/mediagoblin/static/css/base.css +++ b/mediagoblin/static/css/base.css @@ -402,6 +402,8 @@ a.report_authorlink, a.report_whenlink { color: #D486B1; } +ul#action_to_resolve {list-style:none; margin-left:10px;} + /* media galleries */ .media_thumbnail { diff --git a/mediagoblin/admin/routing.py b/mediagoblin/templates/mediagoblin/banned.html similarity index 56% rename from mediagoblin/admin/routing.py rename to mediagoblin/templates/mediagoblin/banned.html index c7ca5b92..4eda0540 100644 --- a/mediagoblin/admin/routing.py +++ b/mediagoblin/templates/mediagoblin/banned.html @@ -1,3 +1,4 @@ +{# # GNU MediaGoblin -- federated, autonomous media hosting # Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. # @@ -13,20 +14,15 @@ # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . +#} +{% extends "mediagoblin/base.html" %} -admin_routes = [ - ('mediagoblin.admin.media_panel', - '/media', - 'mediagoblin.admin.views:admin_media_processing_panel'), - ('mediagoblin.admin.users', - '/users', - 'mediagoblin.admin.views:admin_users_panel'), - ('mediagoblin.admin.reports', - '/reports', - 'mediagoblin.admin.views:admin_reports_panel'), - ('mediagoblin.admin.users_detail', - '/users/', - 'mediagoblin.admin.views:admin_users_detail'), - ('mediagoblin.admin.reports_detail', - '/reports/', - 'mediagoblin.admin.views:admin_reports_detail')] +{% block title %}You are Banned.{% endblock %} + +{% block mediagoblin_content %} + {% trans %}Image of goblin stressing out{% endtrans %} +

You have been banned until {{ expiration_date }}

+

{{ reason|safe }}

+
+{% endblock %} diff --git a/mediagoblin/templates/mediagoblin/base.html b/mediagoblin/templates/mediagoblin/base.html index e9a18f22..b52b65e7 100644 --- a/mediagoblin/templates/mediagoblin/base.html +++ b/mediagoblin/templates/mediagoblin/base.html @@ -104,9 +104,12 @@ {% if request.user.is_admin %}

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

{% endif %}
diff --git a/mediagoblin/templates/mediagoblin/admin/media_panel.html b/mediagoblin/templates/mediagoblin/moderation/media_panel.html similarity index 100% rename from mediagoblin/templates/mediagoblin/admin/media_panel.html rename to mediagoblin/templates/mediagoblin/moderation/media_panel.html diff --git a/mediagoblin/templates/mediagoblin/admin/report.html b/mediagoblin/templates/mediagoblin/moderation/report.html similarity index 67% rename from mediagoblin/templates/mediagoblin/admin/report.html rename to mediagoblin/templates/mediagoblin/moderation/report.html index ef90df40..6938569d 100644 --- a/mediagoblin/templates/mediagoblin/admin/report.html +++ b/mediagoblin/templates/mediagoblin/moderation/report.html @@ -16,6 +16,7 @@ # along with this program. If not, see . #} {%- extends "mediagoblin/base.html" %} +{% import "/mediagoblin/utils/wtforms.html" as wtforms_util %} {%- block mediagoblin_content %} {% if not report %} @@ -29,7 +30,7 @@ class="comment_wrapper"> + {% if not report.resolved %} + + + {{ wtforms_util.render_divs(form) }} + {{ csrf_token }} + + + + + + {% else %} +

Status:

+ RESOLVED on {{ report.resolved.strftime("%I:%M%p %Y-%m-%d") }} + {% autoescape False %} +

{{ report.result }}

+ {% endautoescape %} + {% endif %} {% endif %} {% endblock %} diff --git a/mediagoblin/templates/mediagoblin/admin/report_panel.html b/mediagoblin/templates/mediagoblin/moderation/report_panel.html similarity index 65% rename from mediagoblin/templates/mediagoblin/admin/report_panel.html rename to mediagoblin/templates/mediagoblin/moderation/report_panel.html index 30194577..126b247c 100644 --- a/mediagoblin/templates/mediagoblin/admin/report_panel.html +++ b/mediagoblin/templates/mediagoblin/moderation/report_panel.html @@ -29,7 +29,7 @@ {% trans %}Here you can look up users in order to take punitive actions on them.{% endtrans %}

-

{% trans %}Reports Filed on Comments{% endtrans %}

+

{% trans %}Reports Filed{% endtrans %}

{% if report_list.count() %} @@ -44,8 +44,10 @@ {% for report in report_list %} + + {% if report.discriminator == "comment_report" %} - @@ -53,7 +55,6 @@ {% elif report.discriminator == "media_report" %} - @@ -67,26 +68,36 @@ {% else %}

{% trans %}No open reports found.{% endtrans %}

{% endif %} -

{% trans %}Closed Reports on Comments{% endtrans %}

+

{% trans %}Closed Reports{% endtrans %}

{% if closed_report_list.count() %}
{{ report.id }}{{ report.id }} Comment Report {{ report.comment.get_author.username }} {{ report.created.strftime("%F %R") }}{{ report.report_content }} {{ report.comment.get_media_entry.title }}{{ report.id }} Media Report {{ report.media_entry.get_uploader.username }} {{ report.created.strftime("%F %R") }}
+ - + - + {% for report in closed_report_list %} - - - - - - - - + + {% if report.discriminator == "comment_report" %} + + + + + + + {% elif report.discriminator == "media_report" %} + + + + + + + {% endif %} {% endfor %}
IDResolved OffenderWhen ReportedAction Taken Reported By ReasonComment Posted OnReported Comment or Media Entry
{{ report.id }}{{ report.comment.get_author.username }}{{ report.created.strftime("%F %R") }}{{ report.reporter.username }}{{ report.report_content }}{{ report.comment.get_media_entry.title }}
{{ report.id }}{{ report.resolved.strftime("%F %R") }}{{ report.comment.get_author.username }}{{ report.created.strftime("%F %R") }}{{ report.reporter.username }}{{ report.report_content }}{{ report.comment.get_media_entry.title }}{{ report.resolved.strftime("%F %R") }}{{ report.media_entry.get_uploader.username }}{{ report.created.strftime("%F %R") }}{{ report.reporter.username }}{{ report.report_content[0:20] }}...{{ report.media_entry.title }}
{% else %} diff --git a/mediagoblin/templates/mediagoblin/admin/user.html b/mediagoblin/templates/mediagoblin/moderation/user.html similarity index 74% rename from mediagoblin/templates/mediagoblin/admin/user.html rename to mediagoblin/templates/mediagoblin/moderation/user.html index 90b3f583..f868aa8a 100644 --- a/mediagoblin/templates/mediagoblin/admin/user.html +++ b/mediagoblin/templates/mediagoblin/moderation/user.html @@ -85,7 +85,7 @@
{% endif %} {% if user %} -

{%- trans %}Active Reports on{% endtrans -%} {{ user.username }}

+

{%- trans %}Active Reports on {% endtrans -%}{{ user.username }}

{% if reports.count() %} @@ -97,7 +97,7 @@
- {%- trans %}Report #{% endtrans -%}{{ report.id }} @@ -116,7 +116,7 @@
{% else %} - {%- trans %}No active reports filed on{% endtrans -%} {{ user.username }} + {%- trans %}No active reports filed on {% endtrans -%} {{ user.username }} {% endif %}
{{ user.username }}'s report history @@ -125,13 +125,31 @@ {% trans %}Privilege{% endtrans %} {% trans %}User Has Privilege{% endtrans %} - {% for privilege in privileges %} - - {{ privilege.privilege_name }} - {% if privilege in user.all_privileges %}Yes{% else %}No{% endif %} - {% if privilege in user.all_privileges and privilege.id < request.user.get_highest_privilege().id %}{% trans %}Take Away{% endtrans %}{% else %}{% trans %}Give Privilege{% endtrans %}{% endif %} - {% endfor %} + {% for privilege in privileges %} + +
+ {{ privilege.privilege_name }} + + {% if privilege in user.all_privileges %}Yes + {% if (not privilege.is_admin_or_moderator() or request.user.is_admin) and not (user.is_admin and not request.user.is_admin) %} + + + {% endif %} + {% else %}No + {% if (not privilege.is_admin_or_moderator() or request.user.is_admin) and not (user.is_admin and not request.user.is_admin) %} + + + {% endif %} + {% endif %} + + + {{ csrf_token }} +
+ + {% endfor %} {% endif %} {% endblock %} diff --git a/mediagoblin/templates/mediagoblin/admin/user_panel.html b/mediagoblin/templates/mediagoblin/moderation/user_panel.html similarity index 95% rename from mediagoblin/templates/mediagoblin/admin/user_panel.html rename to mediagoblin/templates/mediagoblin/moderation/user_panel.html index cc965b73..49877074 100644 --- a/mediagoblin/templates/mediagoblin/admin/user_panel.html +++ b/mediagoblin/templates/mediagoblin/moderation/user_panel.html @@ -42,7 +42,7 @@ {% for user in user_list %} {{ user.id }} - {{ user.username }} {{ user.created.strftime("%F %R") }} {{ user.posted_comments.count() }} diff --git a/mediagoblin/tools/response.py b/mediagoblin/tools/response.py index aaf31d0b..ecea307e 100644 --- a/mediagoblin/tools/response.py +++ b/mediagoblin/tools/response.py @@ -19,6 +19,7 @@ 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 class Response(wz_Response): """Set default response mimetype to HTML, otherwise we get text/plain""" @@ -62,6 +63,15 @@ 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) + 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 diff --git a/mediagoblin/user_pages/forms.py b/mediagoblin/user_pages/forms.py index 284dd7b8..260fe02b 100644 --- a/mediagoblin/user_pages/forms.py +++ b/mediagoblin/user_pages/forms.py @@ -51,11 +51,15 @@ class MediaCollectForm(wtforms.Form): Markdown for formatting.""")) class CommentReportForm(wtforms.Form): - report_reason = wtforms.TextAreaField('Reason for Reporting') + report_reason = wtforms.TextAreaField( + _('Reason for Reporting'), + [wtforms.validators.Required()]) comment_id = wtforms.IntegerField() reporter_id = wtforms.IntegerField() class MediaReportForm(wtforms.Form): - report_reason = wtforms.TextAreaField('Reason for Reporting') + report_reason = wtforms.TextAreaField( + _('Reason for Reporting'), + [wtforms.validators.Required()]) media_entry_id = wtforms.IntegerField() reporter_id = wtforms.IntegerField() diff --git a/mediagoblin/user_pages/lib.py b/mediagoblin/user_pages/lib.py index 2558b066..cf7b604d 100644 --- a/mediagoblin/user_pages/lib.py +++ b/mediagoblin/user_pages/lib.py @@ -78,7 +78,7 @@ def add_media_to_collection(collection, media, note=None, commit=True): if commit: Session.commit() -def build_report_form(form_dict): +def build_report_table(form_dict): """ This function is used to convert a form dictionary (from a User filing a report) into either a MediaReport or CommentReport object. diff --git a/mediagoblin/user_pages/views.py b/mediagoblin/user_pages/views.py index abf5e5c1..c1638276 100644 --- a/mediagoblin/user_pages/views.py +++ b/mediagoblin/user_pages/views.py @@ -26,14 +26,14 @@ from mediagoblin.tools.response import render_to_response, render_404, \ from mediagoblin.tools.translate import pass_to_ugettext as _ from mediagoblin.tools.pagination import Pagination from mediagoblin.user_pages import forms as user_forms -from mediagoblin.user_pages.lib import (send_comment_email, build_report_form, +from mediagoblin.user_pages.lib import (send_comment_email, build_report_table, add_media_to_collection) from mediagoblin.decorators import (uses_pagination, get_user_media_entry, get_media_entry_by_id, user_has_privilege, require_active_login, user_may_delete_media, user_may_alter_collection, get_user_collection, get_user_collection_item, active_user_from_url, - get_media_comment_by_id) + get_media_comment_by_id, user_not_banned) from werkzeug.contrib.atom import AtomFeed @@ -41,7 +41,7 @@ from werkzeug.contrib.atom import AtomFeed _log = logging.getLogger(__name__) _log.setLevel(logging.DEBUG) - +@user_not_banned @uses_pagination def user_home(request, page): """'Homepage' of a User()""" @@ -80,7 +80,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 +114,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): @@ -190,7 +190,7 @@ def media_post_comment(request, media): return redirect_obj(request, media) - +@user_not_banned @get_media_entry_by_id @require_active_login def media_collect(request, media): @@ -269,6 +269,7 @@ def media_collect(request, media): #TODO: Why does @user_may_delete_media not implicate @require_active_login? +@user_not_banned @get_media_entry_by_id @require_active_login @user_may_delete_media @@ -305,7 +306,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): @@ -335,7 +336,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""" @@ -391,7 +392,7 @@ def collection_item_confirm_remove(request, collection_item): {'collection_item': collection_item, 'form': form}) - +@user_not_banned @get_user_collection @require_active_login @user_may_alter_collection @@ -575,7 +576,7 @@ def collection_atom_feed(request): return feed.get_response() - +@user_not_banned @require_active_login def processing_panel(request): """ @@ -625,8 +626,8 @@ def processing_panel(request): @user_has_privilege(u'reporter') def file_a_report(request, media, comment=None): if request.method == "POST": - report_form = build_report_form(request.form) - report_form.save() + report_table = build_report_table(request.form) + report_table.save() return redirect( request, diff --git a/mediagoblin/views.py b/mediagoblin/views.py index 6acd7e96..595f2725 100644 --- a/mediagoblin/views.py +++ b/mediagoblin/views.py @@ -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').\ From 3aa3871b909500ae9198d63814dd79fd28921f93 Mon Sep 17 00:00:00 2001 From: tilly-Q Date: Sat, 27 Jul 2013 16:44:40 -0400 Subject: [PATCH 07/25] This commit had some important milestones in it. The major update is that now I have mostly completed the moderator punishment and resolution of reports. Along with this, I have also added one last table to the database: one that holds ar- -chived (or resolved) reports. This is some of the primary functionality of my whole update, so this is a big step! The other changes I made this update are primarily organizational. I refactored some of my code into functions and I cl- eaned up many of my templates. --\ mediagoblin/db/models.py --| Created the new ArchivedReport table --| Removed columns from BaseReport table that are only necessary for Archived | reports --\ mediagoblin/db/migrations.py --| Created the new ArchivedReport table --| Removed columns from BaseReport table that are only necessary for Archived | reports --\ mediagoblin/db/util.py --| Created the user_privileges_to_dictionary function. This is useful for | accessing a user's permissions from within a template. --\ mediagoblin/moderation/forms.py --| Expanded the disciplinary actions a moderator can take --| Allowed the moderator to choose more than one disciplinary action at a time | (It's now managed with a list of checkboxes rather than radio buttons) ----| Pulled a MultiCheckBox class from a wtforms tutorial --| Added various other form inputs for details of the moderator's disciplinary | actions --| Tried to ensure that every string is unicode and translated --\ mediagoblin/moderation/tools.py --| Created this file for holding useful moderation tools --| Moved the penalizing code from views to the function take_punitive_actions --| Added many more types of punitive actions --| Added the archiving of old reports --\ mediagoblin/moderation/views.py --| Used the privileges_to_dictionary function for the Users Detail view to | allow for different actions available to a moderator and an admin. --| Added in functionality for ArchivedReports to the reports_detail and | reports_panel views --| Moved the punishments of repots_detail to tools.py (as mentioned above) --\ mediagoblin/static/css/base.css --| Added new styling for the User Detail page --\ mediagoblin/static/images/icon_clipboard_alert.png --| Added this image to represent unresolved reports --\ mediagoblin/templates/mediagoblin/moderation/report.html --| Added 'Return to Reports Panel' button --| Fixed the spacing to be less that 80 columns wide --| Added in display for Archived Reports --\ mediagoblin/templates/mediagoblin/moderation/reports_panel.html --| Changed the placement and columns of the tables --| Fixed the spacing to be less that 80 columns wide --| Added in display for Archived Reports --\ mediagoblin/templates/mediagoblin/moderation/user.html --| Fixed the spacing to be less that 80 columns wide --| Took away the moderator's ability to add and remove privileges at will. | Only the admin has this power now. --\ mediagoblin/templates/mediagoblin/moderation/users_panel.html --| Fixed the spacing to be less that 80 columns wide --\ mediagoblin/tools/response.py --| Added in code to remove a UserBan from a User if that user logs in after | the expiration date --- mediagoblin/db/migrations.py | 20 ++- mediagoblin/db/models.py | 46 +++++- mediagoblin/db/util.py | 19 ++- mediagoblin/moderation/forms.py | 36 ++++- mediagoblin/moderation/tools.py | 134 ++++++++++++++++ mediagoblin/moderation/views.py | 85 +++------- mediagoblin/static/css/base.css | 17 ++ .../static/images/icon_clipboard_alert.png | Bin 0 -> 647 bytes .../mediagoblin/moderation/report.html | 145 +++++++++++++----- .../mediagoblin/moderation/report_panel.html | 112 ++++++++------ .../mediagoblin/moderation/user.html | 62 +++++--- .../mediagoblin/moderation/user_panel.html | 12 +- mediagoblin/tools/response.py | 7 +- 13 files changed, 490 insertions(+), 205 deletions(-) create mode 100644 mediagoblin/moderation/tools.py create mode 100644 mediagoblin/static/images/icon_clipboard_alert.png diff --git a/mediagoblin/db/migrations.py b/mediagoblin/db/migrations.py index 3e6791c4..247298ac 100644 --- a/mediagoblin/db/migrations.py +++ b/mediagoblin/db/migrations.py @@ -299,8 +299,6 @@ class ReportBase_v0(declarative_base()): report_content = Column(UnicodeText) reported_user_id = Column(Integer, ForeignKey(User.id), nullable=False) created = Column(DateTime, nullable=False, default=datetime.datetime.now) - resolved = Column(DateTime) - result = Column(UnicodeText) discriminator = Column('type', Unicode(50)) __mapper_args__ = {'polymorphic_on': discriminator} @@ -317,13 +315,20 @@ class MediaReport_v0(ReportBase_v0): __tablename__ = 'core__reports_on_media' __mapper_args__ = {'polymorphic_identity': 'media_report'} - id = Column( - 'id', - Integer, - ForeignKey('core__reports.id'), - primary_key=True) + id = Column('id',Integer, ForeignKey('core__reports.id'), primary_key=True) media_entry_id = Column(Integer, ForeignKey(MediaEntry.id), nullable=False) +class ArchivedReport_v0(ReportBase_v0): + __tablename__ = 'core__reports_archived' + __mapper_args__ = {'polymorphic_identity': 'archived_report'} + + id = Column('id',Integer, ForeignKey('core__reports.id')) + + media_entry_id = Column(Integer, ForeignKey(MediaEntry.id)) + comment_id = Column(Integer, ForeignKey(MediaComment.id)) + resolver_id = Column(Integer, ForeignKey(User.id), nullable=False) + resolved_time = Column(DateTime) + result = Column(UnicodeText) class UserBan_v0(declarative_base()): __tablename__ = 'core__user_bans' @@ -356,6 +361,7 @@ def create_moderation_tables(db): ReportBase_v0.__table__.create(db.bind) CommentReport_v0.__table__.create(db.bind) MediaReport_v0.__table__.create(db.bind) + ArchivedReport_v0.__table__.create(db.bind) UserBan_v0.__table__.create(db.bind) Privilege_v0.__table__.create(db.bind) PrivilegeUserAssociation_v0.__table__.create(db.bind) diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index 01078db8..c85d546f 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -515,11 +515,18 @@ class ReportBase(Base): cascade="all, delete-orphan"), primaryjoin="User.id==ReportBase.reported_user_id") created = Column(DateTime, nullable=False, default=datetime.datetime.now()) - resolved = Column(DateTime) - result = Column(UnicodeText) discriminator = Column('type', Unicode(50)) __mapper_args__ = {'polymorphic_on': discriminator} + def is_comment_report(self): + return self.discriminator=='comment_report' + + def is_media_entry_report(self): + return self.discriminator=='media_report' + + def is_archived_report(self): + return self.discriminator=='archived_report' + class CommentReport(ReportBase): """ @@ -548,10 +555,40 @@ class MediaReport(ReportBase): media_entry_id = Column(Integer, ForeignKey(MediaEntry.id), nullable=False) media_entry = relationship( MediaEntry, - backref=backref("reports_filed_on", + backref=backref("reports_filed_onmod/reports/1/", lazy="dynamic", cascade="all, delete-orphan")) +class ArchivedReport(ReportBase): + """ + A table to keep track of reports that have been resolved + """ + __tablename__ = 'core__reports_archived' + __mapper_args__ = {'polymorphic_identity': 'archived_report'} + id = Column('id',Integer, ForeignKey('core__reports.id'), + primary_key=True) + + media_entry_id = Column(Integer, ForeignKey(MediaEntry.id)) + media_entry = relationship( + MediaEntry, + backref=backref("past_reports_filed_on", + lazy="dynamic")) + comment_id = Column(Integer, ForeignKey(MediaComment.id)) + comment = relationship( + MediaComment, backref=backref("past_reports_filed_on", + lazy="dynamic")) + + resolver_id = Column(Integer, ForeignKey(User.id), nullable=False) + resolver = relationship( + User, + backref=backref("reports_resolved_by", + lazy="dynamic", + cascade="all, delete-orphan"), + primaryjoin="User.id==ArchivedReport.resolver_id") + + resolved = Column(DateTime) + result = Column(UnicodeText) + class UserBan(Base): """ Holds the information on a specific user's ban-state. As long as one of @@ -641,7 +678,8 @@ privilege_foundations = [[u'admin'], [u'moderator'], [u'uploader'],[u'reporter'] MODELS = [ User, MediaEntry, Tag, MediaTag, MediaComment, Collection, CollectionItem, MediaFile, FileKeynames, MediaAttachmentFile, ProcessingMetaData, ReportBase, - CommentReport, MediaReport, UserBan, Privilege, PrivilegeUserAssociation] + CommentReport, MediaReport, UserBan, Privilege, PrivilegeUserAssociation, + ArchivedReport] # Foundations are the default rows that are created immediately after the tables are initialized. Each entry to # this dictionary should be in the format of diff --git a/mediagoblin/db/util.py b/mediagoblin/db/util.py index 6ffec44d..1aa0a63c 100644 --- a/mediagoblin/db/util.py +++ b/mediagoblin/db/util.py @@ -15,7 +15,8 @@ # along with this program. If not, see . from mediagoblin.db.base import Session -from mediagoblin.db.models import MediaEntry, Tag, MediaTag, Collection +from mediagoblin.db.models import (MediaEntry, Tag, MediaTag, Collection, \ + User, Privilege, FOUNDATIONS) ########################## @@ -67,6 +68,22 @@ 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 +def user_privileges_to_dictionary(user_id): + """ + This function accepts a users id and returns a dictionary of True or False + values for each privilege the user does or does not have. This allows for + easier referencing of a user's privileges inside templates. + """ + privilege_dictionary = {} + user = User.query.get(user_id) + users_privileges = [p_item.privilege_name for p_item in user.all_privileges] + for privilege_name in FOUNDATIONS[Privilege]: + privilege_name = privilege_name[0] + if privilege_name in users_privileges: + privilege_dictionary[privilege_name]=True + else: + privilege_dictionary[privilege_name]=False + return privilege_dictionary if __name__ == '__main__': from mediagoblin.db.open import setup_connection_and_db_from_config diff --git a/mediagoblin/moderation/forms.py b/mediagoblin/moderation/forms.py index 0a91b9b4..718cd8fa 100644 --- a/mediagoblin/moderation/forms.py +++ b/mediagoblin/moderation/forms.py @@ -17,24 +17,44 @@ import wtforms from mediagoblin.tools.translate import lazy_pass_to_ugettext as _ -ACTION_CHOICES = [(_(u'takeaway'),_('Take away privilege')), - (_(u'userban'),_('Ban the user')), - (_(u'closereport'),_('Close the report without taking an action'))] +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() + class PrivilegeAddRemoveForm(wtforms.Form): - giving_privilege = wtforms.HiddenField('',[wtforms.validators.required()]) privilege_name = wtforms.HiddenField('',[wtforms.validators.required()]) class ReportResolutionForm(wtforms.Form): - action_to_resolve = wtforms.RadioField( - _('What action will you take to resolve this report'), - validators=[wtforms.validators.required()], + 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( - _('User will be banned until:'), + _(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() diff --git a/mediagoblin/moderation/tools.py b/mediagoblin/moderation/tools.py new file mode 100644 index 00000000..9a3b1c2e --- /dev/null +++ b/mediagoblin/moderation/tools.py @@ -0,0 +1,134 @@ +# 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 . + +from mediagoblin import mg_globals +from mediagoblin.db.models import User, Privilege, ArchivedReport, 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 _ +import sys, traceback + +def take_punitive_actions(request, form, report, user): + message_body ='' + try: + + # 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: + privilege = Privilege.one({u'privilege_name':privilege_name}) + form.resolution_content.data += \ + u"
%s took away %s\'s %s privileges" % ( + request.user.username, + user.username, + privilege.privilege_name) + user.all_privileges.remove(privilege) + + # 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: + reason = form.resolution_content.data + \ + "
"+request.user.username + user_ban = UserBan( + user_id=form.targeted_user.data, + expiration_date=form.user_banned_until.data, + reason= form.why_user_was_banned.data + ) + Session.add(user_ban) + + if form.user_banned_until.data is not None: + form.resolution_content.data += \ + u"
%s banned user %s until %s." % ( + request.user.username, + user.username, + form.user_banned_until.data) + else: + form.resolution_content.data += \ + u"
%s banned user %s indefinitely." % ( + request.user.username, + user.username) + + # 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"
%s sent a warning email to the offender." % ( + request.user.username) + + archive = ArchivedReport( + reporter_id=report.reporter_id, + report_content=report.report_content, + reported_user_id=report.reported_user_id, + created=report.created, + resolved=datetime.now(), + resolver_id=request.user.id + ) + + 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"
%s deleted the comment" % ( + request.user.username) + elif u'delete' in form.action_to_resolve.data and \ + report.is_media_entry_report(): + deleted_media = report.media_entry + Session.delete(deleted_media) + form.resolution_content.data += \ + u"
%s deleted the media entry" % ( + request.user.username) + + # If the moderator didn't delete the content we then attach the + # content to the archived report. We also have to actively delete the + # old report, since it won't be deleted by cascading. + elif report.is_comment_report(): + archive.comment_id = report.comment_id + Session.delete(report) + elif report.is_media_entry_report(): + archive.media_entry_id = report.media_entry.id + Session.delete(report) + + + archive.result=form.resolution_content.data +# Session.add(archive) + 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) + except: +#TODO make a more effective and specific try except statement. To account for +# incorrect value addition my moderators + print sys.exc_info()[0] + print sys.exc_info()[1] + traceback.print_tb(sys.exc_info()[2]) + Session.rollback() + return redirect( + request, + 'mediagoblin.moderation.reports_detail', + report_id=report.id) diff --git a/mediagoblin/moderation/views.py b/mediagoblin/moderation/views.py index 6f6318bc..67928927 100644 --- a/mediagoblin/moderation/views.py +++ b/mediagoblin/moderation/views.py @@ -18,11 +18,13 @@ from werkzeug.exceptions import Forbidden from mediagoblin.db.models import (MediaEntry, User, MediaComment, \ CommentReport, ReportBase, Privilege, \ - UserBan) + UserBan, ArchivedReport) +from mediagoblin.db.util import user_privileges_to_dictionary from mediagoblin.decorators import (require_admin_or_moderator_login, \ active_user_from_url) 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 from datetime import datetime @require_admin_or_moderator_login @@ -67,17 +69,22 @@ def moderation_users_detail(request): ''' user = User.query.filter_by(username=request.matchdict['user']).first() active_reports = user.reports_filed_on.filter( - ReportBase.resolved==None).limit(5) + ReportBase.discriminator!='archived_report').limit(5) closed_reports = user.reports_filed_on.filter( - ReportBase.resolved!=None).all() + ReportBase.discriminator=='archived_report').all() privileges = Privilege.query + user_banned = UserBan.query.get(user.id) + user_privileges = user_privileges_to_dictionary(user.id) + requesting_user_privileges = user_privileges_to_dictionary(request.user.id) return render_to_response( request, 'mediagoblin/moderation/user.html', {'user':user, - 'privileges':privileges, - 'reports':active_reports}) + 'privileges': privileges, + 'requesting_user_privileges':requesting_user_privileges, + 'reports':active_reports, + 'user_banned':user_banned}) @require_admin_or_moderator_login def moderation_reports_panel(request): @@ -86,10 +93,10 @@ def moderation_reports_panel(request): media entries for this instance. ''' report_list = ReportBase.query.filter( - ReportBase.resolved==None).order_by( + ReportBase.discriminator!="archived_report").order_by( ReportBase.created.desc()).limit(10) closed_report_list = ReportBase.query.filter( - ReportBase.resolved!=None).order_by( + ReportBase.discriminator=="archived_report").order_by( ReportBase.created.desc()).limit(10) # Render to response @@ -109,66 +116,12 @@ def moderation_reports_detail(request): 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(): user = User.query.get(form.targeted_user.data) - if form.action_to_resolve.data == u'takeaway': - if report.discriminator == u'comment_report': - privilege = Privilege.one({'privilege_name':u'commenter'}) - form.resolution_content.data += \ - u"
%s took away %s\'s commenting privileges" % ( - request.user.username, - user.username) - else: - privilege = Privilege.one({'privilege_name':u'uploader'}) - form.resolution_content.data += \ - u"
%s took away %s\'s media uploading privileges" % ( - request.user.username, - user.username) - user.all_privileges.remove(privilege) - user.save() - report.result = form.resolution_content.data - report.resolved = datetime.now() - report.save() - - elif form.action_to_resolve.data == u'userban': - reason = form.resolution_content.data + \ - "
"+request.user.username - user_ban = UserBan( - user_id=form.targeted_user.data, - expiration_date=form.user_banned_until.data, - reason= form.resolution_content.data) - user_ban.save() - if not form.user_banned_until == "": - form.resolution_content.data += \ - u"
%s banned user %s until %s." % ( - request.user.username, - user.username, - form.user_banned_until.data) - else: - form.resolution_content.data += \ - u"
%s banned user %s indefinitely." % ( - request.user.username, - user.username, - form.user_banned_until.data) + return take_punitive_actions(request, form, report, user) - report.result = form.resolution_content.data - report.resolved = datetime.now() - report.save() - - else: - pass - - return redirect( - request, - 'mediagoblin.moderation.users_detail', - user=user.username) - - if report.discriminator == 'comment_report': - comment = MediaComment.query.get(report.comment_id) - media_entry = None - elif report.discriminator == 'media_report': - media_entry = MediaEntry.query.get(report.media_entry_id) - comment = None form.targeted_user.data = report.reported_user_id @@ -176,8 +129,6 @@ def moderation_reports_detail(request): request, 'mediagoblin/moderation/report.html', {'report':report, - 'media_entry':media_entry, - 'comment':comment, 'form':form}) @require_admin_or_moderator_login @@ -189,7 +140,7 @@ def give_or_take_away_privilege(request, url_user): form = moderation_forms.PrivilegeAddRemoveForm(request.form) if request.method == "POST" and form.validate(): privilege = Privilege.one({'privilege_name':form.privilege_name.data}) - if privilege in url_user.all_privileges is True: + if privilege in url_user.all_privileges: url_user.all_privileges.remove(privilege) else: url_user.all_privileges.append(privilege) diff --git a/mediagoblin/static/css/base.css b/mediagoblin/static/css/base.css index 343648d8..0378350e 100644 --- a/mediagoblin/static/css/base.css +++ b/mediagoblin/static/css/base.css @@ -618,6 +618,23 @@ 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 */ diff --git a/mediagoblin/static/images/icon_clipboard_alert.png b/mediagoblin/static/images/icon_clipboard_alert.png new file mode 100644 index 0000000000000000000000000000000000000000..952c588d48380256074c60228b42d21d4c3bc6b2 GIT binary patch literal 647 zcmV;20(kw2P)Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01ejw01ejxLMWSf00007bV*G`2i*r5 z69+RBw2Q0&00IX|L_t(2&yA8hYgIuMg}*&B=UmcwgOY$2Vr6M8ER2moqXaB$6a0;sYUIoFQV3{g{h=`Sy1PjGiXCY`*^vs;uV=?DmUn#PiUCdguzP)FJ zHR@*f`a-PhOR8!`q%Mor<++)&SO3+?;~RUs8slk=>L8I);%T*y`MC=iV~8=*>0V=3 zT0&DkPe^U8bX};b>7C=#*0>UY4y8Mzi?_Q&-7-RUd`i(6Ir`bBZ0f1On?#YA2 z8RvavjVpQd)}fksxcr>?*;(4{b{}tQa*EEK4!cK3Nhxu7WB~6g=jsqhDBcH-9X-y< zixsXcEc~~JksTvUo;bz5`*%sSs6)Vo;7O`@9|1TpG0xcjF$6#)cc5&w00=(oL-6Ee zjfCI<7#bSJh#+Ekwrx9m-oN^b!26!Qmz**-K{sm4NJgZ|gDqR2o002ovPDHLkV1mt)9i0FG literal 0 HcmV?d00001 diff --git a/mediagoblin/templates/mediagoblin/moderation/report.html b/mediagoblin/templates/mediagoblin/moderation/report.html index 6938569d..44067771 100644 --- a/mediagoblin/templates/mediagoblin/moderation/report.html +++ b/mediagoblin/templates/mediagoblin/moderation/report.html @@ -22,9 +22,16 @@ {% if not report %} Sorry, no such report found. {% else %} -

Report #{{ report.id }}

- {% if comment %} - Reported comment: + + {% trans %}Return to Reports Panel{% endtrans %} +

{% trans %}Report{% endtrans %} #{{ report.id }}

+ {% if report.is_comment_report() or + (report.is_archived_report() and report.comment) %} + + {% trans %}Reported comment{% endtrans %}: + {% set comment = report.comment %} {% set reported_user = comment.get_author %}
@@ -35,7 +42,8 @@ class="comment_authorlink"> {{- reported_user.username -}} -
- {% elif media_entry %} + {% elif report.is_media_entry_report() or + (report.is_archived_report() and report.media_entry) %} + + {% set media_entry = report.media_entry %}
- - + + {{ media_entry.title }} + media=media_entry.slug_or_id) }}" class=thumb_entry_title> + {{ media_entry.title }}
+

❖ Reported media by + {{ report.reported_user.username }}

+
+ {% else %} +

{% trans user_url="request.urlgen( + 'mediagoblin.moderation.users_detail', + user=report.reporter.username)", + user_name=report.reported_user.username %} + CONTENT BY + + {{ user_name }} + HAS BEEN DELETED{% endtrans %} +

{% endif %} Reason for report: - {% if not report.resolved %} + {% if not report.is_archived_report() %}
{{ wtforms_util.render_divs(form) }} @@ -98,39 +126,72 @@ {% else %} -

Status:

- RESOLVED on {{ report.resolved.strftime("%I:%M%p %Y-%m-%d") }} +

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 %}: +

+ {% trans %}RESOLVED{% endtrans %} + {{ report.resolved.strftime("%I:%M%p %Y-%m-%d") }} {% autoescape False %} -

{{ report.result }}

+

{{ report.result }}

{% endautoescape %} {% endif %} {% endif %} diff --git a/mediagoblin/templates/mediagoblin/moderation/report_panel.html b/mediagoblin/templates/mediagoblin/moderation/report_panel.html index 126b247c..f3840e29 100644 --- a/mediagoblin/templates/mediagoblin/moderation/report_panel.html +++ b/mediagoblin/templates/mediagoblin/moderation/report_panel.html @@ -26,42 +26,57 @@

{% trans %}Report panel{% endtrans %}

- {% trans %}Here you can look up users in order to take punitive actions on them.{% endtrans %} + {% trans %} + Here you can look up open reports that have been filed by users. + {% endtrans %}

-

{% trans %}Reports Filed{% endtrans %}

+

{% trans %}Active Reports Filed{% endtrans %}

{% if report_list.count() %} - - - - - - - + + + + + {% for report in report_list %} - - {% if report.discriminator == "comment_report" %} - - - - - - + {% elif report.discriminator == "media_report" %} - - - - - - + {% endif %} + + + + {% endfor %}
IDReport TypeOffenderWhen ReportedReported ByReasonReported Comment or Media Entry{% trans %}Offender{% endtrans %}{% trans %}When Reported{% endtrans %}{% trans %}Reported By{% endtrans %}{% trans %}Reason{% endtrans %}
{{ report.id }}Comment Report{{ report.comment.get_author.username }}{{ report.created.strftime("%F %R") }}{{ report.reporter.username }}{{ report.report_content }}{{ report.comment.get_media_entry.title }} + Under a GNU LGPL v.3 or Creative Commons BY-SA 3.0 license.
+                   Distributed by the GNOME project http://www.gnome.org + + {% trans report_id=report.id %} + Comment Report #{{ report_id }} + {% endtrans %} + + Media Report{{ report.media_entry.get_uploader.username }}{{ report.created.strftime("%F %R") }}{{ report.reporter.username }}{{ report.report_content[0:20] }}...{{ report.media_entry.title }} + Under a GNU LGPL v.3 or Creative Commons BY-SA 3.0 license.
+                   Distributed by the GNOME project http://www.gnome.org + + {% trans report_id=report.id %} + Media Report #{{ report_id }} + {% endtrans %} + + {{ report.reported_user.username }}{{ report.created.strftime("%F %R") }}{{ report.reporter.username }}{{ report.report_content[0:20] }}...
@@ -72,32 +87,33 @@ {% if closed_report_list.count() %} - - - - - - - + + + + + + {% for report in closed_report_list %} - - {% if report.discriminator == "comment_report" %} - - - - - - - {% elif report.discriminator == "media_report" %} - - - - - - - {% endif %} + + + + + + + + {% endfor %}
IDResolvedOffenderAction TakenReported ByReasonReported Comment or Media Entry{% trans %}Resolved{% endtrans %}{% trans %}Offender{% endtrans %}{% trans %}Action Taken{% endtrans %}{% trans %}Reported By{% endtrans %}{% trans %}Reason{% endtrans %}
{{ report.id }}{{ report.resolved.strftime("%F %R") }}{{ report.comment.get_author.username }}{{ report.created.strftime("%F %R") }}{{ report.reporter.username }}{{ report.report_content }}{{ report.comment.get_media_entry.title }}{{ report.resolved.strftime("%F %R") }}{{ report.media_entry.get_uploader.username }}{{ report.created.strftime("%F %R") }}{{ report.reporter.username }}{{ report.report_content[0:20] }}...{{ report.media_entry.title }}
+ Under a GNU LGPL v.3 or Creative Commons BY-SA 3.0 license.
+                 Distributed by the GNOME project http://www.gnome.org + + {% trans report_id=report.id %} + Closed Report #{{ report_id }} + {% endtrans %} + + {{ report.resolved.strftime("%F %R") }}{{ report.reported_user.username }}{{ report.created.strftime("%F %R") }}{{ report.reporter.username }}{{ report.report_content }}
{% else %} diff --git a/mediagoblin/templates/mediagoblin/moderation/user.html b/mediagoblin/templates/mediagoblin/moderation/user.html index f868aa8a..3fb65063 100644 --- a/mediagoblin/templates/mediagoblin/moderation/user.html +++ b/mediagoblin/templates/mediagoblin/moderation/user.html @@ -41,13 +41,15 @@

{% trans -%} - Someone has registered an account with this username, but it still has to be activated. + Someone has registered an account with this username, but it still has + to be activated. {%- endtrans %}

- +

{% trans login_url=request.urlgen('mediagoblin.auth.login') -%} - If you are that person but you've lost your verification email, you can log in and resend it. + If you are that person but you've lost your verification email, you can + log in and resend it. {%- endtrans %}

@@ -56,6 +58,11 @@ {% else %}

{%- trans username=user.username %}{{ username }}'s profile{% endtrans -%} + {% if user_banned and user_banned.expiration_date %} + — BANNED until {{ user_banned.expiration_date }} + {% elif user_banned %} + — Banned Indefinitely + {% endif %}

{% if not user.url and not user.bio %} @@ -121,35 +128,42 @@ {{ user.username }}'s report history

{{ user.username }}'s Privileges

- + +
{% for privilege in privileges %} - - - {% if (not privilege.is_admin_or_moderator() or request.user.is_admin) and not (user.is_admin and not request.user.is_admin) %} - - {% if (not privilege.is_admin_or_moderator() or request.user.is_admin) and not (user.is_admin and not request.user.is_admin) %} - - {{ csrf_token }} - - + {% if privilege in user.all_privileges %} + + {% if requesting_user_privileges.admin%} + + {% endif %} + + {% endfor %}
{% trans %}Privilege{% endtrans %} {% trans %}User Has Privilege{% endtrans %}
{{ privilege.privilege_name }} - {% if privilege in user.all_privileges %}Yes - - {% endif %} - {% else %}No - - {% endif %} - {% endif %} - -
+ Yes{% else %} + + No{% endif %} + {% if privilege in user.all_privileges %} + {% else %} + {% endif %} +
+ {{ csrf_token }} + + {% endif %} + {% endblock %} diff --git a/mediagoblin/templates/mediagoblin/moderation/user_panel.html b/mediagoblin/templates/mediagoblin/moderation/user_panel.html index 49877074..6762a844 100644 --- a/mediagoblin/templates/mediagoblin/moderation/user_panel.html +++ b/mediagoblin/templates/mediagoblin/moderation/user_panel.html @@ -26,7 +26,9 @@

{% trans %}User panel{% endtrans %}

- {% trans %}Here you can look up users in order to take punitive actions on them.{% endtrans %} + {% trans %} + Here you can look up users in order to take punitive actions on them. + {% endtrans %}

{% trans %}Active Users{% endtrans %}

@@ -42,8 +44,12 @@ {% for user in user_list %} {{ user.id }} - {{ user.username }} + + + {{ user.username }} + + {{ user.created.strftime("%F %R") }} {{ user.posted_comments.count() }} diff --git a/mediagoblin/tools/response.py b/mediagoblin/tools/response.py index ecea307e..85558300 100644 --- a/mediagoblin/tools/response.py +++ b/mediagoblin/tools/response.py @@ -19,7 +19,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 +from mediagoblin.db.models import UserBan, User +from datetime import datetime class Response(wz_Response): """Set default response mimetype to HTML, otherwise we get text/plain""" @@ -68,6 +69,10 @@ def render_user_banned(request): and the reason why they have been banned" """ user_ban = UserBan.query.get(request.user.id) + if datetime.now()>user_ban.expiration_date: + user_ban.delete() + redirect(request, + 'mediagoblin.index') return render_to_response(request, 'mediagoblin/banned.html', {'reason':user_ban.reason, From f26c21cd5b7998c903fa67aaf164c07743fee651 Mon Sep 17 00:00:00 2001 From: tilly-Q Date: Mon, 29 Jul 2013 15:14:39 -0400 Subject: [PATCH 08/25] This is a very small commit. All that I have done here is to clean up my code a bit. I made it so that mediagoblin.user_pages.report recieves the report form as part of it's context. I also made sure I used {% trans %} tags effect- -ively. --- mediagoblin/moderation/tools.py | 2 +- .../mediagoblin/moderation/report.html | 22 ++++-- .../mediagoblin/user_pages/report.html | 74 ++++++++++--------- mediagoblin/user_pages/forms.py | 6 +- mediagoblin/user_pages/lib.py | 44 +++++------ mediagoblin/user_pages/views.py | 33 ++++++--- 6 files changed, 99 insertions(+), 82 deletions(-) diff --git a/mediagoblin/moderation/tools.py b/mediagoblin/moderation/tools.py index 9a3b1c2e..25e5dc63 100644 --- a/mediagoblin/moderation/tools.py +++ b/mediagoblin/moderation/tools.py @@ -107,7 +107,7 @@ def take_punitive_actions(request, form, report, user): archive.result=form.resolution_content.data -# Session.add(archive) + Session.add(archive) Session.commit() if message_body: send_email( diff --git a/mediagoblin/templates/mediagoblin/moderation/report.html b/mediagoblin/templates/mediagoblin/moderation/report.html index 44067771..b912c712 100644 --- a/mediagoblin/templates/mediagoblin/moderation/report.html +++ b/mediagoblin/templates/mediagoblin/moderation/report.html @@ -75,18 +75,24 @@ {{ media_entry.title }}
-

❖ Reported media by - {{ report.reported_user.username }}

+

+ {% trans user_name=report.reported_user.username, + user_url=request.urlgen( + 'mediagoblin.moderation.users_detail', + user=report.reporter.username) %} + ❖ Reported media by {{ user_name }} + {% endtrans %} +

{% else %} -

{% trans user_url="request.urlgen( +

{% trans user_url=request.urlgen( 'mediagoblin.moderation.users_detail', - user=report.reporter.username)", + user=report.reporter.username), user_name=report.reported_user.username %} - CONTENT BY - - {{ user_name }} - HAS BEEN DELETED{% endtrans %} + CONTENT BY + {{ user_name }} + HAS BEEN DELETED + {% endtrans %}

{% endif %} Reason for report: diff --git a/mediagoblin/templates/mediagoblin/user_pages/report.html b/mediagoblin/templates/mediagoblin/user_pages/report.html index 9431efc0..cd5e6f59 100644 --- a/mediagoblin/templates/mediagoblin/user_pages/report.html +++ b/mediagoblin/templates/mediagoblin/user_pages/report.html @@ -16,28 +16,35 @@ # along with this program. If not, see . #} {%- extends "mediagoblin/base.html" %} - -{%- block mediagoblin_content %} -

File a Report

+{%- import "/mediagoblin/utils/wtforms.html" as wtforms_util %} +{%- block mediagoblin_content -%} +{% trans %}

File a Report

{% endtrans %}
{% if comment is defined %} -

{% trans %}Reporting this Comment {% endtrans %}

- {% set comment_author = comment.get_author %} +

{% trans %}Reporting this Comment{% endtrans %}

+ {%- 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) %} - {% elif media is defined %} -

{% trans %}Reporting this Media Entry {% endtrans %}

- {% trans %}published by {% endtrans %}
{{ media.get_uploader.username }} - -
- - {% endif %} -
- - -
- - +

{% trans %}Reporting this Media Entry{% endtrans %}

+ +
+ {%- trans user_url = request.urlgen('mediagoblin.user_pages.user_home', user=media.get_uploader.username), + username = media.get_uploader.username %} + ❖ Published by {{ username }} + {% endtrans %} + {%- endif %} + + {{- wtforms_util.render_divs(form) }} {{ csrf_token }} +
{% endblock %} diff --git a/mediagoblin/user_pages/forms.py b/mediagoblin/user_pages/forms.py index 260fe02b..d83338e9 100644 --- a/mediagoblin/user_pages/forms.py +++ b/mediagoblin/user_pages/forms.py @@ -54,12 +54,10 @@ class CommentReportForm(wtforms.Form): report_reason = wtforms.TextAreaField( _('Reason for Reporting'), [wtforms.validators.Required()]) - comment_id = wtforms.IntegerField() - reporter_id = wtforms.IntegerField() + reporter_id = wtforms.HiddenField('') class MediaReportForm(wtforms.Form): report_reason = wtforms.TextAreaField( _('Reason for Reporting'), [wtforms.validators.Required()]) - media_entry_id = wtforms.IntegerField() - reporter_id = wtforms.IntegerField() + reporter_id = wtforms.HiddenField('') diff --git a/mediagoblin/user_pages/lib.py b/mediagoblin/user_pages/lib.py index cf7b604d..7f03fcd3 100644 --- a/mediagoblin/user_pages/lib.py +++ b/mediagoblin/user_pages/lib.py @@ -78,39 +78,33 @@ def add_media_to_collection(collection, media, note=None, commit=True): if commit: Session.commit() -def build_report_table(form_dict): +def build_report_object(report_form, media_entry=None, comment=None): """ - This function is used to convert a form dictionary (from a User filing a + This function is used to convert a form object (from a User filing a report) into either a MediaReport or CommentReport object. - :param form_dict should be an ImmutableMultiDict object as is returned from - 'request.form.' The Object should have valid keys matching the fields - in either MediaReportForm or CommentReportForm + :param report_form should be a MediaReportForm or a CommentReportForm + object + :param :returns either of MediaReport or a CommentReport object that has not been saved. In case of an improper form_dict, returns None """ - if 'comment_id' in form_dict.keys(): - report_form = user_forms.CommentReportForm(form_dict) - elif 'media_entry_id' in form_dict.keys(): - report_form = user_forms.MediaReportForm(form_dict) + + 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 - if report_form.validate() and 'comment_id' in form_dict.keys(): - report_model = CommentReport() - report_model.comment_id = report_form.comment_id.data - report_model.reported_user_id = MediaComment.query.get( - report_model.comment_id).get_author.id - elif report_form.validate() and 'media_entry_id' in form_dict.keys(): - report_model = MediaReport() - report_model.media_entry_id = report_form.media_entry_id.data - report_model.reported_user_id = MediaEntry.query.get( - report_model.media_entry_id).get_uploader.id - else: - return None - - report_model.report_content = report_form.report_reason.data or u'' - report_model.reporter_id = report_form.reporter_id.data - return report_model + report_object.report_content = report_form.report_reason.data + report_object.reporter_id = report_form.reporter_id.data + return report_object diff --git a/mediagoblin/user_pages/views.py b/mediagoblin/user_pages/views.py index c1638276..1a78bcc7 100644 --- a/mediagoblin/user_pages/views.py +++ b/mediagoblin/user_pages/views.py @@ -26,7 +26,7 @@ from mediagoblin.tools.response import render_to_response, render_404, \ from mediagoblin.tools.translate import pass_to_ugettext as _ from mediagoblin.tools.pagination import Pagination from mediagoblin.user_pages import forms as user_forms -from mediagoblin.user_pages.lib import (send_comment_email, build_report_table, +from mediagoblin.user_pages.lib import (send_comment_email, build_report_object, add_media_to_collection) from mediagoblin.decorators import (uses_pagination, get_user_media_entry, @@ -625,19 +625,30 @@ def processing_panel(request): @get_user_media_entry @user_has_privilege(u'reporter') def file_a_report(request, media, comment=None): - if request.method == "POST": - report_table = build_report_table(request.form) - report_table.save() - - return redirect( - request, - 'index') - if comment is not None: + form = user_forms.CommentReportForm(request.form) + form.reporter_id.data = request.user.id context = {'media': media, - 'comment':comment} + 'comment':comment, + 'form':form} else: - context = {'media': media} + form = user_forms.MediaReportForm(request.form) + form.reporter_id.data = request.user.id + context = {'media': media, + 'form':form} + + if request.method == "POST": + report_table = build_report_object(form, + media_entry=media, + comment=comment) + + # if the object was built successfully, report_table will not be None + if report_table: + report_table.save() + return redirect( + request, + 'index') + return render_to_response( request, From 9d6e453f8fd337813c2933835aedff2949193fbe Mon Sep 17 00:00:00 2001 From: tilly-Q Date: Tue, 30 Jul 2013 17:09:01 -0400 Subject: [PATCH 09/25] This commit was the work I did fixing errors that cropped up from the merge. There were a few errors because of the switch from sqlalchemy 0.7 to 0.8 but I cleared them up. --- mediagoblin/db/migrations.py | 2 +- mediagoblin/db/util.py | 5 +++-- mediagoblin/decorators.py | 12 +++++++----- mediagoblin/gmg_commands/users.py | 20 ++++++++++++-------- mediagoblin/moderation/tools.py | 3 ++- mediagoblin/moderation/views.py | 8 ++++++-- mediagoblin/templates/mediagoblin/base.html | 3 +++ mediagoblin/user_pages/views.py | 2 +- 8 files changed, 35 insertions(+), 20 deletions(-) diff --git a/mediagoblin/db/migrations.py b/mediagoblin/db/migrations.py index 53df7954..972908be 100644 --- a/mediagoblin/db/migrations.py +++ b/mediagoblin/db/migrations.py @@ -404,7 +404,7 @@ class MediaReport_v0(ReportBase_v0): __mapper_args__ = {'polymorphic_identity': 'media_report'} id = Column('id',Integer, ForeignKey('core__reports.id'), primary_key=True) - media_entry_id = Column(Integer, ForeignKey(MediaEntry.i + media_entry_id = Column(Integer, ForeignKey(MediaEntry.id), nullable=False) class ArchivedReport_v0(ReportBase_v0): __tablename__ = 'core__reports_archived' diff --git a/mediagoblin/db/util.py b/mediagoblin/db/util.py index 2a99d467..31fc49fb 100644 --- a/mediagoblin/db/util.py +++ b/mediagoblin/db/util.py @@ -77,8 +77,9 @@ def user_privileges_to_dictionary(user_id): privilege_dictionary = {} user = User.query.get(user_id) users_privileges = [p_item.privilege_name for p_item in user.all_privileges] - for privilege_name in FOUNDATIONS[Privilege]: - privilege_name = privilege_name[0] + #TODO update this to account for plugins that may add foundations + for privilege in FOUNDATIONS[Privilege]: + privilege_name = privilege['privilege_name'] if privilege_name in users_privileges: privilege_dictionary[privilege_name]=True else: diff --git a/mediagoblin/decorators.py b/mediagoblin/decorators.py index c9a1a78c..79b582c9 100644 --- a/mediagoblin/decorators.py +++ b/mediagoblin/decorators.py @@ -22,9 +22,9 @@ from werkzeug.urls import url_quote from mediagoblin import mg_globals as mgg from mediagoblin import messages -from mediagoblin.db.models import MediaEntry, User, MediaComment, - UserBan -from mediagoblin.tools.response import redirect, render_404 +from mediagoblin.db.models import (MediaEntry, User, MediaComment, + UserBan, Privilege) +from mediagoblin.tools.response import redirect, render_404, render_user_banned from mediagoblin.tools.translate import pass_to_ugettext as _ @@ -309,8 +309,10 @@ def require_admin_or_moderator_login(controller): """ @wraps(controller) def new_controller_func(request, *args, **kwargs): - admin_privilege = Privilege.one({'privilege_name':u'admin'}) - moderator_privilege = Privilege.one({'privilege_name':u'moderator'}) + admin_privilege = Privilege.query.filter( + Privilege.privilege_name==u'admin').one() + moderator_privilege = Privilege.query.filter( + Privilege.privilege_name==u'moderator').one() if request.user and \ not admin_privilege in request.user.all_privileges and \ not moderator_privilege in request.user.all_privileges: diff --git a/mediagoblin/gmg_commands/users.py b/mediagoblin/gmg_commands/users.py index ad8263e7..7e6fc5bc 100644 --- a/mediagoblin/gmg_commands/users.py +++ b/mediagoblin/gmg_commands/users.py @@ -56,11 +56,15 @@ def adduser(args): entry.status = u'active' entry.email_verified = True default_privileges = [ - db.Privilege.one({'privilege_name':u'commenter'}), - db.Privilege.one({'privilege_name':u'uploader'}), - db.Privilege.one({'privilege_name':u'reporter'}), - db.Privilege.one({'privilege_name':u'active'}) -] + 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() @@ -83,9 +87,9 @@ def makeadmin(args): if user: user.is_admin = True user.all_privileges.append( - db.Privilege.one({ - 'privilege_name':u'admin'}) - ) + db.Privilege.query.filter( + db.Privilege.privilege_name==u'admin').one() + ) user.save() print 'The user is now Admin' else: diff --git a/mediagoblin/moderation/tools.py b/mediagoblin/moderation/tools.py index 25e5dc63..b4daca15 100644 --- a/mediagoblin/moderation/tools.py +++ b/mediagoblin/moderation/tools.py @@ -31,7 +31,8 @@ def take_punitive_actions(request, form, report, user): # 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: - privilege = Privilege.one({u'privilege_name':privilege_name}) + privilege = Privilege.query.filter( + Privilege.privilege_name==privilege_name).one() form.resolution_content.data += \ u"
%s took away %s\'s %s privileges" % ( request.user.username, diff --git a/mediagoblin/moderation/views.py b/mediagoblin/moderation/views.py index 67928927..041cf5b3 100644 --- a/mediagoblin/moderation/views.py +++ b/mediagoblin/moderation/views.py @@ -116,7 +116,10 @@ def moderation_reports_detail(request): 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] + 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(): user = User.query.get(form.targeted_user.data) @@ -139,7 +142,8 @@ def give_or_take_away_privilege(request, url_user): ''' form = moderation_forms.PrivilegeAddRemoveForm(request.form) if request.method == "POST" and form.validate(): - privilege = Privilege.one({'privilege_name':form.privilege_name.data}) + privilege = Privilege.query.filter( + Privilege.privilege_name==form.privilege_name.data).one() if privilege in url_user.all_privileges: url_user.all_privileges.remove(privilege) else: diff --git a/mediagoblin/templates/mediagoblin/base.html b/mediagoblin/templates/mediagoblin/base.html index 575ddf42..31f0f0c3 100644 --- a/mediagoblin/templates/mediagoblin/base.html +++ b/mediagoblin/templates/mediagoblin/base.html @@ -118,6 +118,9 @@ {%- trans %}User management panel{% endtrans -%} + + {%- trans %}Report management panel{% endtrans -%} +

{% endif %} {% include 'mediagoblin/fragments/header_notifications.html' %} diff --git a/mediagoblin/user_pages/views.py b/mediagoblin/user_pages/views.py index 06ea0ab0..161a47e2 100644 --- a/mediagoblin/user_pages/views.py +++ b/mediagoblin/user_pages/views.py @@ -27,7 +27,7 @@ from mediagoblin.tools.translate import pass_to_ugettext as _ from mediagoblin.tools.pagination import Pagination from mediagoblin.user_pages import forms as user_forms from mediagoblin.user_pages.lib import (send_comment_email, - add_media_to_collection) + add_media_to_collection, build_report_object) from mediagoblin.notifications import trigger_notification, \ add_comment_subscription, mark_comment_notification_seen From 8394febbe1408030d1afa8f3961d92341eefa474 Mon Sep 17 00:00:00 2001 From: tilly-Q Date: Tue, 13 Aug 2013 18:38:00 -0400 Subject: [PATCH 10/25] This has been an update to clean out the code a little bit. The primary change I made was I added the method has_privilege (which takes a variable amount of unicode privilege names as an argument) to the User model. This method allowed for much cleaner checks as to whether or not a user has a privilege. Other- wise, I also made it impossible for moderators to punish admins. I created a new url path and three new pages for Users to look at filed reports and the code of conduct for the mg instance. === Made reports on admins not resolvable by moderators: --\ mediagoblin/moderation/views.py --\ mediagoblin/templates/mediagoblin/moderation/report.html === Created new files for the new pages: --\ mediagoblin/meta/__init__.py --\ mediagoblin/meta/routing.py --\ mediagoblin/meta/views.py --\ mediagoblin/templates/mediagoblin/meta/code_of_conduct.html --\ mediagoblin/templates/mediagoblin/meta/reports_details.html --\ mediagoblin/templates/mediagoblin/meta/reports_panel.html --\ mediagoblin/routing.py --\ mediagoblin/static/css/base.css === Replaced vestigial methods of checking a user's privilege with the more ====== effective method has_privilege(u'privilege_name'): --\ mediagoblin/db/models.py --| Added in the has_privilege method to the User class --\ mediagoblin/db/migrations.py --\ mediagoblin/db/models.py --\ mediagoblin/decorators.py --\ mediagoblin/edit/lib.py --\ mediagoblin/edit/views.py --\ mediagoblin/gmg_commands/users.py --\ mediagoblin/moderation/views.py --\ mediagoblin/templates/mediagoblin/base.html --\ mediagoblin/templates/mediagoblin/user_pages/collection.html --\ mediagoblin/templates/mediagoblin/user_pages/media.html --\ mediagoblin/templates/mediagoblin/user_pages/user.html --\ mediagoblin/templates/mediagoblin/utils/collection_gallery.html --\ mediagoblin/user_pages/views.py === Minor UI changes --\ mediagoblin/templates/mediagoblin/moderation/report_panel.html --\ mediagoblin/templates/mediagoblin/moderation/user.html === Other Bugs: --\ mediagoblin/tools/response.py --\ mediagoblin/db/migrations.py --- mediagoblin/db/migrations.py | 2 +- mediagoblin/db/models.py | 10 ++++ mediagoblin/decorators.py | 21 +++------ mediagoblin/edit/lib.py | 2 +- mediagoblin/edit/views.py | 6 +-- mediagoblin/gmg_commands/users.py | 1 - mediagoblin/meta/__init__.py | 0 mediagoblin/meta/routing.py | 27 +++++++++++ mediagoblin/meta/views.py | 33 +++++++++++++ mediagoblin/moderation/views.py | 8 ++-- mediagoblin/routing.py | 3 ++ mediagoblin/static/css/base.css | 18 +++++++- mediagoblin/templates/mediagoblin/base.html | 4 +- .../mediagoblin/meta/code_of_conduct.html | 46 +++++++++++++++++++ .../mediagoblin/meta/reports_details.html | 17 +++++++ .../mediagoblin/meta/reports_panel.html | 17 +++++++ .../mediagoblin/moderation/report.html | 20 ++------ .../mediagoblin/moderation/report_panel.html | 2 +- .../mediagoblin/moderation/user.html | 25 ++++++---- .../mediagoblin/user_pages/collection.html | 2 +- .../mediagoblin/user_pages/media.html | 4 +- .../mediagoblin/user_pages/user.html | 2 +- .../mediagoblin/utils/collection_gallery.html | 2 +- mediagoblin/tools/response.py | 2 +- mediagoblin/user_pages/views.py | 8 ++-- 25 files changed, 219 insertions(+), 63 deletions(-) create mode 100644 mediagoblin/meta/__init__.py create mode 100644 mediagoblin/meta/routing.py create mode 100644 mediagoblin/meta/views.py create mode 100644 mediagoblin/templates/mediagoblin/meta/code_of_conduct.html create mode 100644 mediagoblin/templates/mediagoblin/meta/reports_details.html create mode 100644 mediagoblin/templates/mediagoblin/meta/reports_panel.html diff --git a/mediagoblin/db/migrations.py b/mediagoblin/db/migrations.py index 972908be..e15b4ad3 100644 --- a/mediagoblin/db/migrations.py +++ b/mediagoblin/db/migrations.py @@ -410,7 +410,7 @@ class ArchivedReport_v0(ReportBase_v0): __tablename__ = 'core__reports_archived' __mapper_args__ = {'polymorphic_identity': 'archived_report'} - id = Column('id',Integer, ForeignKey('core__reports.id')) + id = Column('id',Integer, ForeignKey('core__reports.id'), primary_key=True) media_entry_id = Column(Integer, ForeignKey(MediaEntry.id)) comment_id = Column(Integer, ForeignKey(MediaComment.id)) resolver_id = Column(Integer, ForeignKey(User.id), nullable=False) diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index 32d3135f..54b8f739 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -106,6 +106,16 @@ class User(Base, UserMixin): super(User, self).delete(**kwargs) _log.info('Deleted user "{0}" account'.format(self.username)) + def has_privilege(self,*priv_names): + 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 + class MediaEntry(Base, MediaEntryMixin): """ diff --git a/mediagoblin/decorators.py b/mediagoblin/decorators.py index 79b582c9..d3a9647e 100644 --- a/mediagoblin/decorators.py +++ b/mediagoblin/decorators.py @@ -35,11 +35,11 @@ def require_active_login(controller): @wraps(controller) 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), @@ -72,13 +72,9 @@ def user_has_privilege(privilege_name): @wraps(controller) def wrapper(request, *args, **kwargs): user_id = request.user.id - privileges_of_user = Privilege.query.filter( - Privilege.all_users.any( - User.id==user_id)) if UserBan.query.filter(UserBan.user_id==user_id).count(): return render_user_banned(request) - elif not privileges_of_user.filter( - Privilege.privilege_name==privilege_name).count(): + elif not request.user.has_privilege(privilege_name): raise Forbidden() return controller(request, *args, **kwargs) @@ -94,7 +90,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() @@ -111,7 +107,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() @@ -309,13 +305,8 @@ def require_admin_or_moderator_login(controller): """ @wraps(controller) def new_controller_func(request, *args, **kwargs): - admin_privilege = Privilege.query.filter( - Privilege.privilege_name==u'admin').one() - moderator_privilege = Privilege.query.filter( - Privilege.privilege_name==u'moderator').one() if request.user and \ - not admin_privilege in request.user.all_privileges and \ - not moderator_privilege in request.user.all_privileges: + not request.user.has_privilege(u'admin',u'moderator'): raise Forbidden() elif not request.user: diff --git a/mediagoblin/edit/lib.py b/mediagoblin/edit/lib.py index aab537a0..6acebc96 100644 --- a/mediagoblin/edit/lib.py +++ b/mediagoblin/edit/lib.py @@ -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 diff --git a/mediagoblin/edit/views.py b/mediagoblin/edit/views.py index 6aa2acd9..c6c3c03e 100644 --- a/mediagoblin/edit/views.py +++ b/mediagoblin/edit/views.py @@ -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 @@ -326,7 +326,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( diff --git a/mediagoblin/gmg_commands/users.py b/mediagoblin/gmg_commands/users.py index 7e6fc5bc..0002daad 100644 --- a/mediagoblin/gmg_commands/users.py +++ b/mediagoblin/gmg_commands/users.py @@ -85,7 +85,6 @@ 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() diff --git a/mediagoblin/meta/__init__.py b/mediagoblin/meta/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/mediagoblin/meta/routing.py b/mediagoblin/meta/routing.py new file mode 100644 index 00000000..e61bc065 --- /dev/null +++ b/mediagoblin/meta/routing.py @@ -0,0 +1,27 @@ +# 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 . + +meta_routes = [ + ('mediagoblin.meta.code_of_conduct', + '/coc/', + 'mediagoblin.meta.views:code_of_conduct'), + ('mediagoblin.meta.reports_panel', + '/reports/', + 'mediagoblin.meta.views:public_reports_panel'), + ('mediagoblin.meta.reports_detail', + '/reports/', + 'mediagoblin.meta.views:public_reports_details') +] diff --git a/mediagoblin/meta/views.py b/mediagoblin/meta/views.py new file mode 100644 index 00000000..3df0688c --- /dev/null +++ b/mediagoblin/meta/views.py @@ -0,0 +1,33 @@ +# 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 . + +from mediagoblin.tools.response import render_to_response + + +def code_of_conduct(request): + return render_to_response(request, + 'mediagoblin/meta/code_of_conduct.html', + {}) + +def public_reports_panel(request): + return render_to_response(request, + 'mediagoblin/meta/reports_panel.html', + {}) + +def public_reports_details(request): + return render_to_response(request, + 'mediagoblin/meta/reports_details.html', + {}) diff --git a/mediagoblin/moderation/views.py b/mediagoblin/moderation/views.py index 041cf5b3..d82eca7d 100644 --- a/mediagoblin/moderation/views.py +++ b/mediagoblin/moderation/views.py @@ -74,15 +74,12 @@ def moderation_users_detail(request): ReportBase.discriminator=='archived_report').all() privileges = Privilege.query user_banned = UserBan.query.get(user.id) - user_privileges = user_privileges_to_dictionary(user.id) - requesting_user_privileges = user_privileges_to_dictionary(request.user.id) return render_to_response( request, 'mediagoblin/moderation/user.html', {'user':user, 'privileges': privileges, - 'requesting_user_privileges':requesting_user_privileges, 'reports':active_reports, 'user_banned':user_banned}) @@ -121,7 +118,10 @@ def moderation_reports_detail(request): for s in report.reported_user.all_privileges ] - if request.method == "POST" and form.validate(): + 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) diff --git a/mediagoblin/routing.py b/mediagoblin/routing.py index c9377ad4..9686d103 100644 --- a/mediagoblin/routing.py +++ b/mediagoblin/routing.py @@ -20,6 +20,7 @@ from mediagoblin.tools.routing import add_route, mount, url_map from mediagoblin.tools.pluginapi import PluginManager from mediagoblin.moderation.routing import moderation_routes from mediagoblin.auth.routing import auth_routes +from mediagoblin.meta.routing import meta_routes _log = logging.getLogger(__name__) @@ -29,6 +30,7 @@ def get_url_map(): add_route('index', '/', 'mediagoblin.views:root_view') mount('/auth', auth_routes) mount('/mod', moderation_routes) + mount('/meta', meta_routes) import mediagoblin.submit.routing import mediagoblin.user_pages.routing @@ -37,6 +39,7 @@ def get_url_map(): import mediagoblin.listings.routing import mediagoblin.notifications.routing + for route in PluginManager().get_routes(): add_route(*route) diff --git a/mediagoblin/static/css/base.css b/mediagoblin/static/css/base.css index 338828d2..1293086d 100644 --- a/mediagoblin/static/css/base.css +++ b/mediagoblin/static/css/base.css @@ -220,6 +220,7 @@ footer { color: #283F35; } + .button_form { min-width: 99px; margin: 10px 0px 10px 15px; @@ -615,7 +616,7 @@ table.media_panel th { text-align: left; } -/* admin panels */ +/* moderator panels */ table.admin_panel { width: 100% @@ -655,6 +656,21 @@ table td.user_without_privilege { margin-left: 10px; } +/* code of conduct */ + +#code_of_conduct_list { + margin-left:25px; + margin-bottom: 10px; +} +#code_of_conduct_list li { + margin-top:5px; +} +ol.nested_sublist{ + margin: 5px 0 10px 25px; + font-size:80%; +} + + /* ASCII art and code */ @font-face { diff --git a/mediagoblin/templates/mediagoblin/base.html b/mediagoblin/templates/mediagoblin/base.html index 31f0f0c3..6eaad70b 100644 --- a/mediagoblin/templates/mediagoblin/base.html +++ b/mediagoblin/templates/mediagoblin/base.html @@ -109,9 +109,9 @@ {%- trans %}Create new collection{% endtrans -%} - {% if request.user.is_admin %} + {% if request.user.has_privilege('admin','moderator') %}

- Admin powers: + Moderation powers: {%- trans %}Media processing panel{% endtrans -%} diff --git a/mediagoblin/templates/mediagoblin/meta/code_of_conduct.html b/mediagoblin/templates/mediagoblin/meta/code_of_conduct.html new file mode 100644 index 00000000..e8233ad3 --- /dev/null +++ b/mediagoblin/templates/mediagoblin/meta/code_of_conduct.html @@ -0,0 +1,46 @@ +{# +# 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 . +#} +{% extends "mediagoblin/base.html" %} + +{% block title %} + Code of Conduct +{% endblock %} + +{% block mediagoblin_content -%} +

{% trans %}Code of Conduct for this Website{% endtrans %}

+ +{# Suggested layout for this page: +
    +
  1. Item #1
  2. +
  3. + Item #2 +
      +
    1. Sub-Item #1
    2. +
    3. Sub-Item #2
    4. +
    5. + Sub-Item #3 +
        +
      1. Sub-Subitem #1
      2. +
      +
    6. +
    +
  4. +
  5. Item #3
  6. +
+#} +{% endblock -%} diff --git a/mediagoblin/templates/mediagoblin/meta/reports_details.html b/mediagoblin/templates/mediagoblin/meta/reports_details.html new file mode 100644 index 00000000..6fa5ae59 --- /dev/null +++ b/mediagoblin/templates/mediagoblin/meta/reports_details.html @@ -0,0 +1,17 @@ +{# +# 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 . +#} diff --git a/mediagoblin/templates/mediagoblin/meta/reports_panel.html b/mediagoblin/templates/mediagoblin/meta/reports_panel.html new file mode 100644 index 00000000..6fa5ae59 --- /dev/null +++ b/mediagoblin/templates/mediagoblin/meta/reports_panel.html @@ -0,0 +1,17 @@ +{# +# 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 . +#} diff --git a/mediagoblin/templates/mediagoblin/moderation/report.html b/mediagoblin/templates/mediagoblin/moderation/report.html index b912c712..04788f05 100644 --- a/mediagoblin/templates/mediagoblin/moderation/report.html +++ b/mediagoblin/templates/mediagoblin/moderation/report.html @@ -122,7 +122,7 @@ {{ report.report_content }} - {% if not report.is_archived_report() %} + {% if not report.is_archived_report() and not (report.reported_user.has_privilege('admin') and not request.user.has_privilege('admin')) %}
{{ wtforms_util.render_divs(form) }} @@ -163,19 +163,6 @@ $(document).ready(function() { $('#'+name).hide(); }); }); -/* $.each(hidden_input_names, function(key,name){ - if ($.inArray(key, $('ul#action_to_resolve li input:checked').val())){ - $.each(hidden_input_names[key], function(index,name){ - $('#'+name).show(); - $('label[for='+name+']').show(); - }); - } else { - $.each(hidden_input_names[key], function(index,name){ - $('#'+name).hide(); - $('label[for='+name+']').hide(); - }); - } - });*/ }); $("#user_banned_until").focus(function() { $(this).val(""); @@ -188,7 +175,7 @@ $(document).ready(function() { }); }); - {% else %} + {% elif not (report.reported_user.has_privilege('admin')) %}

Under a GNU LGPL v.3 or Creative Commons BY-SA 3.0 license.
                   Distributed by the GNOME project http://www.gnome.org @@ -199,6 +186,9 @@ $(document).ready(function() { {% autoescape False %}

{{ report.result }}

{% endautoescape %} + {% else %} + +

You cannot take action against an administrator

{% endif %} {% endif %} {% endblock %} diff --git a/mediagoblin/templates/mediagoblin/moderation/report_panel.html b/mediagoblin/templates/mediagoblin/moderation/report_panel.html index f3840e29..2818eb80 100644 --- a/mediagoblin/templates/mediagoblin/moderation/report_panel.html +++ b/mediagoblin/templates/mediagoblin/moderation/report_panel.html @@ -112,7 +112,7 @@ {{ report.reported_user.username }} {{ report.created.strftime("%F %R") }} {{ report.reporter.username }} - {{ report.report_content }} + {{ report.report_content[:15] }}... {% endfor %} diff --git a/mediagoblin/templates/mediagoblin/moderation/user.html b/mediagoblin/templates/mediagoblin/moderation/user.html index 3fb65063..d8454d2d 100644 --- a/mediagoblin/templates/mediagoblin/moderation/user.html +++ b/mediagoblin/templates/mediagoblin/moderation/user.html @@ -33,12 +33,10 @@ {# If no user... #} {% if not user %}

{% trans %}Sorry, no such user found.{% endtrans %}

- {# User exists, but needs verification #} {% elif user.status == "needs_email_verification" %}

{% trans %}Email verification needed{% endtrans %}

-

{% trans -%} Someone has registered an account with this username, but it still has @@ -56,6 +54,10 @@ {# Active(?) (or at least verified at some point) user, horray! #} {% else %} + + {% trans %}Return to Users Panel{% endtrans %}

{%- trans username=user.username %}{{ username }}'s profile{% endtrans -%} {% if user_banned and user_banned.expiration_date %} @@ -64,7 +66,6 @@ — Banned Indefinitely {% endif %}

- {% if not user.url and not user.bio %}

{% 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) %} diff --git a/mediagoblin/templates/mediagoblin/user_pages/media.html b/mediagoblin/templates/mediagoblin/user_pages/media.html index b10ef3be..441452f2 100644 --- a/mediagoblin/templates/mediagoblin/user_pages/media.html +++ b/mediagoblin/templates/mediagoblin/user_pages/media.html @@ -72,7 +72,7 @@ {% 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) %} @@ -198,7 +198,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 %}

{% trans %}Attachments{% endtrans %}

{%- endif %} diff --git a/mediagoblin/templates/mediagoblin/user_pages/user.html b/mediagoblin/templates/mediagoblin/user_pages/user.html index 71acd66c..de92fb5e 100644 --- a/mediagoblin/templates/mediagoblin/user_pages/user.html +++ b/mediagoblin/templates/mediagoblin/user_pages/user.html @@ -111,7 +111,7 @@
{% 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')) %} {%- trans %}Edit profile{% endtrans -%} diff --git a/mediagoblin/templates/mediagoblin/utils/collection_gallery.html b/mediagoblin/templates/mediagoblin/utils/collection_gallery.html index dcc59244..24bf6832 100644 --- a/mediagoblin/templates/mediagoblin/utils/collection_gallery.html +++ b/mediagoblin/templates/mediagoblin/utils/collection_gallery.html @@ -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, diff --git a/mediagoblin/tools/response.py b/mediagoblin/tools/response.py index 8d9c02d4..54905a0e 100644 --- a/mediagoblin/tools/response.py +++ b/mediagoblin/tools/response.py @@ -72,7 +72,7 @@ def render_user_banned(request): if datetime.now()>user_ban.expiration_date: user_ban.delete() redirect(request, - 'mediagoblin.index') + 'index') return render_to_response(request, 'mediagoblin/banned.html', {'reason':user_ban.reason, diff --git a/mediagoblin/user_pages/views.py b/mediagoblin/user_pages/views.py index 161a47e2..6c0bada2 100644 --- a/mediagoblin/user_pages/views.py +++ b/mediagoblin/user_pages/views.py @@ -299,7 +299,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, @@ -385,7 +385,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, @@ -433,7 +433,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, @@ -594,7 +594,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', From e1561d048815e29b3b7c7e1c860d9cf0c4326f0a Mon Sep 17 00:00:00 2001 From: tilly-Q Date: Tue, 20 Aug 2013 12:02:20 -0400 Subject: [PATCH 11/25] This was a very small update, I'm hoping to rebase after this to solve some other problems. I started looking at the tests in this update. This update I spent fixing the tests to work with my new code. --\ mediagoblin/db/migration_tools.py --| Merging from ticket 679 --\ mediagoblin/db/migrations.py --| Added unique constraint to Privilege.privilege_name --\ mediagoblin/db/models.py --| Deleted vestigial Privilege.is_admin_or_moderator method --\ mediagoblin/templates/mediagoblin/moderation/user.html --| Add a `Ban User` / `UnBan User` for admin --\ mediagoblin/test/test_api.py --| Fixed test with my new changes --\ mediagoblin/test/test_auth.py --| Try to fix test, still having problems --\ mediagoblin/test/test_modelmethods.py --| Wrote my first test for the User.has_privilege method --\ mediagoblin/test/test_modelmethods.py --| Fixed test with my new changes --\ mediagoblin/test/test_sqlmigrations.py --| Merging from ticket 679 --\ mediagoblin/test/tools.py --| Editted add_fixture_user to allow for privileges rather than active column --- mediagoblin/db/migration_tools.py | 8 ++-- mediagoblin/db/migrations.py | 2 +- mediagoblin/db/models.py | 8 ---- .../mediagoblin/moderation/user.html | 14 +++++- mediagoblin/tests/test_api.py | 4 +- mediagoblin/tests/test_auth.py | 34 +++++++-------- mediagoblin/tests/test_modelmethods.py | 43 ++++++++++++++++++- mediagoblin/tests/test_oauth.py | 3 +- mediagoblin/tests/test_sql_migrations.py | 42 +++++++++++++++--- mediagoblin/tests/tools.py | 13 +++--- 10 files changed, 122 insertions(+), 49 deletions(-) diff --git a/mediagoblin/db/migration_tools.py b/mediagoblin/db/migration_tools.py index ad137683..e75f3757 100644 --- a/mediagoblin/db/migration_tools.py +++ b/mediagoblin/db/migration_tools.py @@ -147,10 +147,11 @@ class MigrationManager(object): in mediagoblin.db.models """ for Model, rows in self.foundations.items(): - print u'\n + Laying foundations for %s table' % (Model.__name__) + self.printer(u' + Laying foundations for %s table\n' % + (Model.__name__)) for parameters in rows: new_row = Model(**parameters) - new_row.save() + self.session.add(new_row) def create_new_migration_record(self): """ @@ -215,9 +216,8 @@ class MigrationManager(object): self.init_tables() # auto-set at latest migration number self.create_new_migration_record() - self.populate_table_foundations() - self.printer(u"done.\n") + self.populate_table_foundations() self.set_current_migration() return u'inited' diff --git a/mediagoblin/db/migrations.py b/mediagoblin/db/migrations.py index e15b4ad3..9dff22ee 100644 --- a/mediagoblin/db/migrations.py +++ b/mediagoblin/db/migrations.py @@ -427,7 +427,7 @@ class UserBan_v0(declarative_base()): class Privilege_v0(declarative_base()): __tablename__ = 'core__privileges' id = Column(Integer, nullable=False, primary_key=True, unique=True) - privilege_name = Column(Unicode, nullable=False) + privilege_name = Column(Unicode, nullable=False, unique=True) class PrivilegeUserAssociation_v0(declarative_base()): __tablename__ = 'core__privileges_users' diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index 54b8f739..69b59c99 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -745,14 +745,6 @@ class Privilege(Base): def __repr__(self): return "" % (self.privilege_name) - def is_admin_or_moderator(self): - ''' - This method is necessary to check if a user is able to take moderation - actions. - ''' - - return (self.privilege_name==u'admin' or - self.privilege_name==u'moderator') class PrivilegeUserAssociation(Base): ''' diff --git a/mediagoblin/templates/mediagoblin/moderation/user.html b/mediagoblin/templates/mediagoblin/moderation/user.html index d8454d2d..ef48fe54 100644 --- a/mediagoblin/templates/mediagoblin/moderation/user.html +++ b/mediagoblin/templates/mediagoblin/moderation/user.html @@ -117,7 +117,8 @@ {%- trans %}Reported Media Entry{% endtrans -%} {% endif %} - {{ report.report_content[:21] }}{% if report.report_content|count >20 %}...{% endif %} + {{ report.report_content[:21] }} + {% if report.report_content|count >20 %}...{% endif %} {%- trans %}Resolve{% endtrans -%} {% endfor %} @@ -129,9 +130,18 @@ {{ user.username }}'s report history

{{ user.username }}'s Privileges

+ {% if request.user.has_privilege('admin') and not user_banned and + not user.id == request.user.id %} + + {% elif request.user.has_privilege('admin') and + not user.id == request.user.id %} + + {% endif %} + method=post > diff --git a/mediagoblin/tests/test_api.py b/mediagoblin/tests/test_api.py index 89cf1026..eb9c0fd4 100644 --- a/mediagoblin/tests/test_api.py +++ b/mediagoblin/tests/test_api.py @@ -25,6 +25,7 @@ from mediagoblin.tools import template, pluginapi from mediagoblin.tests.tools import fixture_add_user from .resources import GOOD_JPG, GOOD_PNG, EVIL_FILE, EVIL_JPG, EVIL_PNG, \ BIG_BLUE +from mediagoblin.db.models import Privilege _log = logging.getLogger(__name__) @@ -35,7 +36,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( diff --git a/mediagoblin/tests/test_auth.py b/mediagoblin/tests/test_auth.py index 61503d32..7d7748ac 100644 --- a/mediagoblin/tests/test_auth.py +++ b/mediagoblin/tests/test_auth.py @@ -83,18 +83,18 @@ 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.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 @@ -107,7 +107,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) @@ -129,7 +129,7 @@ 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 @@ -143,7 +143,7 @@ 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 @@ -154,9 +154,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'] @@ -171,7 +171,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? @@ -181,7 +181,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/auth/fp_verification_email.txt'] #TODO - change the name of verification_url to something forgot-password-ish @@ -210,7 +210,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 @@ -219,8 +219,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() @@ -233,7 +233,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 diff --git a/mediagoblin/tests/test_modelmethods.py b/mediagoblin/tests/test_modelmethods.py index 427aa47c..77d375b7 100644 --- a/mediagoblin/tests/test_modelmethods.py +++ b/mediagoblin/tests/test_modelmethods.py @@ -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 @@ -151,6 +151,46 @@ class TestMediaEntrySlugs(object): qbert_entry.generate_slug() assert qbert_entry.slug is None +class TestUserHasPrivilege: + def _setup(self): + self.natalie_user = fixture_add_user(u'natalie') + self.aeva_user = fixture_add_user(u'aeva') + self.natalie_user.all_privileges += [ + Privilege.query.filter( + Privilege.privilege_name == u'admin').one(), + Privilege.query.filter( + Privilege.privilege_name == u'moderator').one()] + self.aeva_user.all_privileges += [ + Privilege.query.filter( + Privilege.privilege_name == u'moderator').one()] + + 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 natalie_user.has_privilege(u'commenter') + assert 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 natalie_user.has_privilege(u'admin',u'commenter') + assert aeva_user.has_privilege(u'moderator',u'active') + assert not natalie_user.has_privilege(u'commenter',u'uploader') + + def test_media_data_init(test_app): Session.rollback() @@ -165,3 +205,4 @@ def test_media_data_init(test_app): obj_in_session += 1 print repr(obj) assert obj_in_session == 0 + diff --git a/mediagoblin/tests/test_oauth.py b/mediagoblin/tests/test_oauth.py index ea3bd798..58cc9928 100644 --- a/mediagoblin/tests/test_oauth.py +++ b/mediagoblin/tests/test_oauth.py @@ -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() diff --git a/mediagoblin/tests/test_sql_migrations.py b/mediagoblin/tests/test_sql_migrations.py index 2fc4c043..3d67fdf6 100644 --- a/mediagoblin/tests/test_sql_migrations.py +++ b/mediagoblin/tests/test_sql_migrations.py @@ -58,6 +58,10 @@ class Level1(Base1): SET1_MODELS = [Creature1, Level1] +FOUNDATIONS = {Creature1:[{'name':u'goblin','num_legs':2,'is_demon':False}, + {'name':u'cerberus','num_legs':4,'is_demon':True}] + } + SET1_MIGRATIONS = {} ####################################################### @@ -542,7 +546,6 @@ def _insert_migration3_objects(session): session.commit() - def create_test_engine(): from sqlalchemy import create_engine engine = create_engine('sqlite:///:memory:', echo=False) @@ -572,7 +575,7 @@ def test_set1_to_set3(): printer = CollectingPrinter() migration_manager = MigrationManager( - u'__main__', SET1_MODELS, SET1_MIGRATIONS, Session(), + u'__main__', SET1_MODELS, FOUNDATIONS, SET1_MIGRATIONS, Session(), printer) # Check latest migration and database current migration @@ -585,11 +588,13 @@ def test_set1_to_set3(): assert result == u'inited' # Check output assert printer.combined_string == ( - "-> Initializing main mediagoblin tables... done.\n") + "-> Initializing main mediagoblin tables... done.\n" + \ + " + Laying foundations for Creature1 table\n" ) # Check version in database assert migration_manager.latest_migration == 0 assert migration_manager.database_current_migration == 0 + # Install the initial set # ----------------------- @@ -597,8 +602,8 @@ def test_set1_to_set3(): # Try to "re-migrate" with same manager settings... nothing should happen migration_manager = MigrationManager( - u'__main__', SET1_MODELS, SET1_MIGRATIONS, Session(), - printer) + u'__main__', SET1_MODELS, FOUNDATIONS, SET1_MIGRATIONS, + Session(), printer) assert migration_manager.init_or_migrate() == None # Check version in database @@ -639,6 +644,20 @@ def test_set1_to_set3(): # Now check to see if stuff seems to be in there. session = Session() + # Check the creation of the foundation rows on the creature table + creature = session.query(Creature1).filter_by( + name=u'goblin').one() + assert creature.num_legs == 2 + assert creature.is_demon == False + + creature = session.query(Creature1).filter_by( + name=u'cerberus').one() + assert creature.num_legs == 4 + assert creature.is_demon == True + + + # Check the creation of the inserted rows on the creature and levels tables + creature = session.query(Creature1).filter_by( name=u'centipede').one() assert creature.num_legs == 100 @@ -679,7 +698,7 @@ def test_set1_to_set3(): # isn't said to be updated yet printer = CollectingPrinter() migration_manager = MigrationManager( - u'__main__', SET3_MODELS, SET3_MIGRATIONS, Session(), + u'__main__', SET3_MODELS, FOUNDATIONS, SET3_MIGRATIONS, Session(), printer) assert migration_manager.latest_migration == 8 @@ -706,7 +725,7 @@ def test_set1_to_set3(): # Make sure version matches expected migration_manager = MigrationManager( - u'__main__', SET3_MODELS, SET3_MIGRATIONS, Session(), + u'__main__', SET3_MODELS, FOUNDATIONS, SET3_MIGRATIONS, Session(), printer) assert migration_manager.latest_migration == 8 assert migration_manager.database_current_migration == 8 @@ -772,6 +791,15 @@ def test_set1_to_set3(): # Now check to see if stuff seems to be in there. session = Session() + + + # Start with making sure that the foundations did not run again + assert session.query(Creature3).filter_by( + name=u'goblin').count() == 1 + assert session.query(Creature3).filter_by( + name=u'cerberus').count() == 1 + + # Then make sure the models have been migrated correctly creature = session.query(Creature3).filter_by( name=u'centipede').one() assert creature.num_limbs == 100.0 diff --git a/mediagoblin/tests/tools.py b/mediagoblin/tests/tools.py index 98361adc..ec17d791 100644 --- a/mediagoblin/tests/tools.py +++ b/mediagoblin/tests/tools.py @@ -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 from mediagoblin.tools import testing from mediagoblin.init.config import read_mediagoblin_config from mediagoblin.db.base import Session @@ -170,7 +170,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 +179,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() From 2c901db023cd29d8f12408470245c9a5b8b911f1 Mon Sep 17 00:00:00 2001 From: tilly-Q Date: Tue, 20 Aug 2013 15:11:02 -0400 Subject: [PATCH 12/25] This commit was just to fix a few of the errors with the merging and to make sure that all of the previous tests work fine. --- mediagoblin/auth/tools.py | 2 +- mediagoblin/db/migrations.py | 147 ++++++++++++------------ mediagoblin/db/models.py | 13 ++- mediagoblin/tests/test_auth.py | 1 + mediagoblin/tests/test_edit.py | 9 +- mediagoblin/tests/test_notifications.py | 9 +- mediagoblin/tests/test_submission.py | 2 +- mediagoblin/tests/tools.py | 1 - 8 files changed, 97 insertions(+), 87 deletions(-) diff --git a/mediagoblin/auth/tools.py b/mediagoblin/auth/tools.py index 596a4447..f758bca4 100644 --- a/mediagoblin/auth/tools.py +++ b/mediagoblin/auth/tools.py @@ -21,7 +21,7 @@ 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 diff --git a/mediagoblin/db/migrations.py b/mediagoblin/db/migrations.py index 0eedc5d4..762d17e6 100644 --- a/mediagoblin/db/migrations.py +++ b/mediagoblin/db/migrations.py @@ -383,79 +383,6 @@ def pw_hash_nullable(db): constraint = UniqueConstraint('username', table=user_table) constraint.create() -class ReportBase_v0(declarative_base()): - __tablename__ = 'core__reports' - id = Column(Integer, primary_key=True) - reporter_id = Column(Integer, ForeignKey(User.id), nullable=False) - report_content = Column(UnicodeText) - reported_user_id = Column(Integer, ForeignKey(User.id), nullable=False) - created = Column(DateTime, nullable=False, default=datetime.datetime.now) - discriminator = Column('type', Unicode(50)) - __mapper_args__ = {'polymorphic_on': discriminator} - -class CommentReport_v0(ReportBase_v0): - __tablename__ = 'core__reports_on_comments' - __mapper_args__ = {'polymorphic_identity': 'comment_report'} - - id = Column('id',Integer, ForeignKey('core__reports.id'), - primary_key=True) - comment_id = Column(Integer, ForeignKey(MediaComment.id), nullable=False) - -class MediaReport_v0(ReportBase_v0): - __tablename__ = 'core__reports_on_media' - __mapper_args__ = {'polymorphic_identity': 'media_report'} - - id = Column('id',Integer, ForeignKey('core__reports.id'), primary_key=True) - media_entry_id = Column(Integer, ForeignKey(MediaEntry.id), nullable=False) - -class ArchivedReport_v0(ReportBase_v0): - __tablename__ = 'core__reports_archived' - __mapper_args__ = {'polymorphic_identity': 'archived_report'} - - id = Column('id',Integer, ForeignKey('core__reports.id'), primary_key=True) - media_entry_id = Column(Integer, ForeignKey(MediaEntry.id)) - comment_id = Column(Integer, ForeignKey(MediaComment.id)) - resolver_id = Column(Integer, ForeignKey(User.id), nullable=False) - resolved_time = Column(DateTime) - result = Column(UnicodeText) - -class UserBan_v0(declarative_base()): - __tablename__ = 'core__user_bans' - user_id = Column('id',Integer, ForeignKey(User.id), nullable=False, - primary_key=True) - expiration_date = Column(DateTime) - reason = Column(UnicodeText, nullable=False) - -class Privilege_v0(declarative_base()): - __tablename__ = 'core__privileges' - id = Column(Integer, nullable=False, primary_key=True, unique=True) - privilege_name = Column(Unicode, nullable=False, unique=True) - -class PrivilegeUserAssociation_v0(declarative_base()): - __tablename__ = 'core__privileges_users' - group_id = Column( - 'core__privilege_id', - Integer, - ForeignKey(User.id), - primary_key=True) - user_id = Column( - 'core__user_id', - Integer, - ForeignKey(Privilege.id), - primary_key=True) - -@RegisterMigration(14, MIGRATIONS) -def create_moderation_tables(db): - ReportBase_v0.__table__.create(db.bind) - CommentReport_v0.__table__.create(db.bind) - MediaReport_v0.__table__.create(db.bind) - ArchivedReport_v0.__table__.create(db.bind) - UserBan_v0.__table__.create(db.bind) - Privilege_v0.__table__.create(db.bind) - PrivilegeUserAssociation_v0.__table__.create(db.bind) - db.commit() - - # oauth1 migrations class Client_v0(declarative_base()): """ @@ -533,3 +460,77 @@ def create_oauth1_tables(db): NonceTimestamp_v0.__table__.create(db.bind) 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)) + __mapper_args__ = {'polymorphic_on': discriminator} + +class CommentReport_v0(ReportBase_v0): + __tablename__ = 'core__reports_on_comments' + __mapper_args__ = {'polymorphic_identity': 'comment_report'} + + id = Column('id',Integer, ForeignKey('core__reports.id'), + primary_key=True) + comment_id = Column(Integer, ForeignKey(MediaComment.id), nullable=False) + +class MediaReport_v0(ReportBase_v0): + __tablename__ = 'core__reports_on_media' + __mapper_args__ = {'polymorphic_identity': 'media_report'} + + id = Column('id',Integer, ForeignKey('core__reports.id'), primary_key=True) + media_entry_id = Column(Integer, ForeignKey(MediaEntry.id), nullable=False) + +class ArchivedReport_v0(ReportBase_v0): + __tablename__ = 'core__reports_archived' + __mapper_args__ = {'polymorphic_identity': 'archived_report'} + + id = Column('id',Integer, ForeignKey('core__reports.id'), primary_key=True) + media_entry_id = Column(Integer, ForeignKey(MediaEntry.id)) + comment_id = Column(Integer, ForeignKey(MediaComment.id)) + resolver_id = Column(Integer, ForeignKey(User.id), nullable=False) + resolved_time = Column(DateTime) + result = Column(UnicodeText) + +class UserBan_v0(declarative_base()): + __tablename__ = 'core__user_bans' + user_id = Column('id',Integer, ForeignKey(User.id), nullable=False, + primary_key=True) + expiration_date = Column(DateTime) + reason = Column(UnicodeText, nullable=False) + +class Privilege_v0(declarative_base()): + __tablename__ = 'core__privileges' + id = Column(Integer, nullable=False, primary_key=True, unique=True) + privilege_name = Column(Unicode, nullable=False, unique=True) + +class PrivilegeUserAssociation_v0(declarative_base()): + __tablename__ = 'core__privileges_users' + group_id = Column( + 'core__privilege_id', + Integer, + ForeignKey(User.id), + primary_key=True) + user_id = Column( + 'core__user_id', + Integer, + ForeignKey(Privilege.id), + primary_key=True) + +@RegisterMigration(15, MIGRATIONS) +def create_moderation_tables(db): + ReportBase_v0.__table__.create(db.bind) + CommentReport_v0.__table__.create(db.bind) + MediaReport_v0.__table__.create(db.bind) + ArchivedReport_v0.__table__.create(db.bind) + UserBan_v0.__table__.create(db.bind) + Privilege_v0.__table__.create(db.bind) + PrivilegeUserAssociation_v0.__table__.create(db.bind) + db.commit() + + diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index 25b4fa8f..62c5a5d5 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -649,6 +649,10 @@ class ProcessingNotification(Notification): 'polymorphic_identity': 'processing_notification' } +with_polymorphic( + Notification, + [ProcessingNotification, CommentNotification]) + class ReportBase(Base): """ This is the basic report table which the other reports are based off of. @@ -828,16 +832,13 @@ class PrivilegeUserAssociation(Base): ForeignKey(Privilege.id), primary_key=True) -with_polymorphic( - Notification, - [ProcessingNotification, CommentNotification]) - MODELS = [ User, MediaEntry, Tag, MediaTag, MediaComment, Collection, CollectionItem, MediaFile, FileKeynames, MediaAttachmentFile, ProcessingMetaData, - Notification, CommentNotification, ProcessingNotification, + Notification, CommentNotification, ProcessingNotification, Client, CommentSubscription, ReportBase, CommentReport, MediaReport, UserBan, - Privilege, PrivilegeUserAssociation, ArchivedReport, ArchivedReport] + Privilege, PrivilegeUserAssociation, ArchivedReport, + RequestToken, AccessToken, NonceTimestamp] """ Foundations are the default rows that are created immediately after the tables diff --git a/mediagoblin/tests/test_auth.py b/mediagoblin/tests/test_auth.py index 7d7748ac..11ed83bd 100644 --- a/mediagoblin/tests/test_auth.py +++ b/mediagoblin/tests/test_auth.py @@ -342,6 +342,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 diff --git a/mediagoblin/tests/test_edit.py b/mediagoblin/tests/test_edit.py index d70d0478..8db8a00d 100644 --- a/mediagoblin/tests/test_edit.py +++ b/mediagoblin/tests/test_edit.py @@ -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) @@ -115,7 +117,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!', diff --git a/mediagoblin/tests/test_notifications.py b/mediagoblin/tests/test_notifications.py index d52b8d5a..8420e358 100644 --- a/mediagoblin/tests/test_notifications.py +++ b/mediagoblin/tests/test_notifications.py @@ -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,8 @@ otherperson@example.com\n\nSGkgb3RoZXJwZXJzb24sCmNocmlzIGNvbW1lbnRlZCBvbiB5b3VyI else: assert mail.EMAIL_TEST_MBOX_INBOX == [] + mail.EMAIL_TEST_MBOX_INBOX = [] + # Save the ids temporarily because of DetachedInstanceError notification_id = notification.id comment_id = notification.subject.id diff --git a/mediagoblin/tests/test_submission.py b/mediagoblin/tests/test_submission.py index ac941063..dbdf87e9 100644 --- a/mediagoblin/tests/test_submission.py +++ b/mediagoblin/tests/test_submission.py @@ -46,7 +46,7 @@ class TestSubmission: # TODO: Possibly abstract into a decorator like: # @as_authenticated_user('chris') - test_user = fixture_add_user() + test_user = fixture_add_user(privileges=[u'active',u'uploader']) self.test_user = test_user diff --git a/mediagoblin/tests/tools.py b/mediagoblin/tests/tools.py index ec17d791..feb83b44 100644 --- a/mediagoblin/tests/tools.py +++ b/mediagoblin/tests/tools.py @@ -133,7 +133,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 From e46fb71c1d7067253f30cb7212c676b066a61432 Mon Sep 17 00:00:00 2001 From: tilly-Q Date: Fri, 23 Aug 2013 16:14:36 -0400 Subject: [PATCH 13/25] pushing so paroneayea can help me resolve a conflict within the tests. --- mediagoblin/tests/test_modelmethods.py | 28 ++++++++++++-------------- mediagoblin/tests/test_submission.py | 6 +++--- 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/mediagoblin/tests/test_modelmethods.py b/mediagoblin/tests/test_modelmethods.py index 77d375b7..4af66072 100644 --- a/mediagoblin/tests/test_modelmethods.py +++ b/mediagoblin/tests/test_modelmethods.py @@ -153,16 +153,14 @@ class TestMediaEntrySlugs(object): class TestUserHasPrivilege: def _setup(self): - self.natalie_user = fixture_add_user(u'natalie') - self.aeva_user = fixture_add_user(u'aeva') - self.natalie_user.all_privileges += [ - Privilege.query.filter( - Privilege.privilege_name == u'admin').one(), - Privilege.query.filter( - Privilege.privilege_name == u'moderator').one()] - self.aeva_user.all_privileges += [ - Privilege.query.filter( - Privilege.privilege_name == u'moderator').one()] + 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() @@ -177,8 +175,8 @@ class TestUserHasPrivilege: self._setup() # then test out the user.has_privilege method for one privilege - assert not natalie_user.has_privilege(u'commenter') - assert aeva_user.has_privilege(u'active') + 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): @@ -186,9 +184,9 @@ class TestUserHasPrivilege: # when multiple args are passed to has_privilege, the method returns # True if the user has ANY of the privileges - assert natalie_user.has_privilege(u'admin',u'commenter') - assert aeva_user.has_privilege(u'moderator',u'active') - assert not natalie_user.has_privilege(u'commenter',u'uploader') + 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') diff --git a/mediagoblin/tests/test_submission.py b/mediagoblin/tests/test_submission.py index dbdf87e9..ed088730 100644 --- a/mediagoblin/tests/test_submission.py +++ b/mediagoblin/tests/test_submission.py @@ -24,7 +24,7 @@ import pytest from mediagoblin.tests.tools import fixture_add_user from mediagoblin import mg_globals -from mediagoblin.db.models import MediaEntry +from mediagoblin.db.models import MediaEntry, User from mediagoblin.tools import template from mediagoblin.media_types.image import ImageMediaManager from mediagoblin.media_types.pdf.processing import check_prerequisites as pdf_check_prerequisites @@ -46,9 +46,9 @@ class TestSubmission: # TODO: Possibly abstract into a decorator like: # @as_authenticated_user('chris') - test_user = fixture_add_user(privileges=[u'active',u'uploader']) + fixture_add_user(privileges=[u'active',u'uploader']) - self.test_user = test_user + self.test_user = User.query.filter(User.username==u'chris').first() self.login() From dfd66b789cd6cc9470c2a98bcbda9ee5e0f3ad0f Mon Sep 17 00:00:00 2001 From: tilly-Q Date: Thu, 29 Aug 2013 13:47:50 -0400 Subject: [PATCH 14/25] This was a big commit! I included lots of documentation below, but generally I did a few things. I wrote many many many new tests, either in old test files or in the three new test files I made. I also did some code-keeping work, deleting trailing whitespace and deleting vestigial code. Lastly, I fixed the parts of the code which I realized were broken thru the process of running tests. =============================================================================== Deleted trailing whitespace: =============================================================================== --\ mediagoblin/decorators.py --\ mediagoblin/auth/tools.py --\ mediagoblin/db/migrations.py --\ mediagoblin/db/models.py --\ mediagoblin/gmg_commands/users.py --\ mediagoblin/moderation/forms.py --\ mediagoblin/moderation/tools.py --\ mediagoblin/moderation/views.py --\ mediagoblin/templates/mediagoblin/moderation/media_panel.html --\ mediagoblin/templates/mediagoblin/moderation/report.html --\ mediagoblin/templates/mediagoblin/moderation/report_panel.html --\ mediagoblin/templates/mediagoblin/moderation/user.html --\ mediagoblin/templates/mediagoblin/moderation/user_panel.html --\ mediagoblin/templates/mediagoblin/user_pages/report.html --\ mediagoblin/templates/mediagoblin/utils/report.html --\ mediagoblin/user_pages/lib.py --\ mediagoblin/user_pages/views.py =============================================================================== Deleted Vestigial Code =============================================================================== --\ mediagoblin/db/util.py --\ mediagoblin/tests/test_notifications.py =============================================================================== Modified the Code: =============================================================================== --\ mediagoblin/moderation/tools.py --| Encapsulated the code around giving/taking away privileges into two | funtions. --\ mediagoblin/moderation/views.py --| Imported and used the give/take away privilege functions --| Replaced 'require_admin_or_moderator_login' with |'user_has_privilege(u"admin")' for adding/taking away privileges, only | admins are allowed to do this. --\ mediagoblin/templates/mediagoblin/banned.html --| Added relevant translation tags --| Added ability to display indefinite banning --\ mediagoblin/templates/mediagoblin/user_pages/media.html --| Made sure the add comments button was only visible for users with the | `commenter` privilege --\ mediagoblin/tests/test_submission.py --| Paroneayea fixed a DetachedInstanceError I was having with the our_user | function --\ mediagoblin/tests/tools.py --| Added a fixture_add_comment_report function for testing. --\ mediagoblin/tools/response.py --| Fixed a minor error where a necessary return statement was missing --| Fit the code within 80 columns --\ mediagoblin/user_pages/views.py --| Added a necessary decorator to ensure that only users with the 'commenter' | privilege can post comments =============================================================================== Wrote new tests for an old test file: =============================================================================== --\ mediagoblin/tests/test_auth.py --| Added a new test to make sure privilege granting on registration happens | correctly --\ mediagoblin/tests/test_modelmethods.py* --| Added a test to ensure the User method has_privilege works properly =============================================================================== Wrote entirely new files full of tests: =============================================================================== --\ mediagoblin/tests/test_moderation.py --\ mediagoblin/tests/test_privileges.py --\ mediagoblin/tests/test_reporting.py =============================================================================== =============================================================================== NOTE: Any files I've marked with a * in this commit report, were actually subm- itted in my last commit. I made that committ to fix an error I was having, so they weren't properly documented in that report. =============================================================================== =============================================================================== --- mediagoblin/auth/tools.py | 4 +- mediagoblin/db/migrations.py | 20 +- mediagoblin/db/models.py | 60 ++--- mediagoblin/db/util.py | 18 -- mediagoblin/decorators.py | 4 +- mediagoblin/gmg_commands/users.py | 2 +- mediagoblin/moderation/forms.py | 4 +- mediagoblin/moderation/tools.py | 53 ++++- mediagoblin/moderation/views.py | 29 +-- mediagoblin/templates/mediagoblin/banned.html | 11 +- .../mediagoblin/moderation/media_panel.html | 4 +- .../mediagoblin/moderation/report.html | 18 +- .../mediagoblin/moderation/report_panel.html | 18 +- .../mediagoblin/moderation/user.html | 4 +- .../mediagoblin/moderation/user_panel.html | 4 +- .../mediagoblin/user_pages/media.html | 2 +- .../mediagoblin/user_pages/report.html | 24 +- .../templates/mediagoblin/utils/report.html | 2 +- mediagoblin/tests/test_auth.py | 7 + mediagoblin/tests/test_moderation.py | 194 +++++++++++++++++ mediagoblin/tests/test_notifications.py | 1 - mediagoblin/tests/test_privileges.py | 206 ++++++++++++++++++ mediagoblin/tests/test_reporting.py | 165 ++++++++++++++ mediagoblin/tests/test_submission.py | 30 ++- mediagoblin/tests/tools.py | 34 ++- mediagoblin/tools/response.py | 11 +- mediagoblin/user_pages/lib.py | 10 +- mediagoblin/user_pages/views.py | 11 +- 28 files changed, 793 insertions(+), 157 deletions(-) create mode 100644 mediagoblin/tests/test_moderation.py create mode 100644 mediagoblin/tests/test_privileges.py create mode 100644 mediagoblin/tests/test_reporting.py diff --git a/mediagoblin/auth/tools.py b/mediagoblin/auth/tools.py index f758bca4..76b37e29 100644 --- a/mediagoblin/auth/tools.py +++ b/mediagoblin/auth/tools.py @@ -164,13 +164,13 @@ def register_user(request, register_form): user = auth.create_user(register_form) # give the user the default privileges - default_privileges = [ + default_privileges = [ Privilege.query.filter(Privilege.privilege_name==u'commenter').first(), Privilege.query.filter(Privilege.privilege_name==u'uploader').first(), Privilege.query.filter(Privilege.privilege_name==u'reporter').first()] user.all_privileges += default_privileges user.save() - + # log the user in request.session['user_id'] = unicode(user.id) request.session.save() diff --git a/mediagoblin/db/migrations.py b/mediagoblin/db/migrations.py index 762d17e6..a97458b6 100644 --- a/mediagoblin/db/migrations.py +++ b/mediagoblin/db/migrations.py @@ -28,7 +28,7 @@ from migrate.changeset.constraint import UniqueConstraint from mediagoblin.db.extratypes import JSONEncoded from mediagoblin.db.migration_tools import RegisterMigration, inspect_table -from mediagoblin.db.models import (MediaEntry, Collection, User, +from mediagoblin.db.models import (MediaEntry, Collection, User, MediaComment, Privilege, ReportBase) MIGRATIONS = {} @@ -425,7 +425,7 @@ class RequestToken_v0(declarative_base()): callback = Column(Unicode, nullable=False, default=u"oob") created = Column(DateTime, nullable=False, default=datetime.datetime.now) updated = Column(DateTime, nullable=False, default=datetime.datetime.now) - + class AccessToken_v0(declarative_base()): """ Model for representing the access tokens @@ -438,7 +438,7 @@ class AccessToken_v0(declarative_base()): request_token = Column(Unicode, ForeignKey(RequestToken_v0.token)) created = Column(DateTime, nullable=False, default=datetime.datetime.now) updated = Column(DateTime, nullable=False, default=datetime.datetime.now) - + class NonceTimestamp_v0(declarative_base()): """ @@ -467,7 +467,7 @@ class ReportBase_v0(declarative_base()): 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) + created = Column(DateTime, nullable=False, default=datetime.datetime.now) discriminator = Column('type', Unicode(50)) __mapper_args__ = {'polymorphic_on': discriminator} @@ -512,14 +512,14 @@ class Privilege_v0(declarative_base()): class PrivilegeUserAssociation_v0(declarative_base()): __tablename__ = 'core__privileges_users' group_id = Column( - 'core__privilege_id', - Integer, - ForeignKey(User.id), + 'core__privilege_id', + Integer, + ForeignKey(User.id), primary_key=True) user_id = Column( - 'core__user_id', - Integer, - ForeignKey(Privilege.id), + 'core__user_id', + Integer, + ForeignKey(Privilege.id), primary_key=True) @RegisterMigration(15, MIGRATIONS) diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index 62c5a5d5..dd2cd3e9 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -669,7 +669,7 @@ class ReportBase(Base): id = Column(Integer, primary_key=True) reporter_id = Column(Integer, ForeignKey(User.id), nullable=False) reporter = relationship( - User, + User, backref=backref("reports_filed_by", lazy="dynamic", cascade="all, delete-orphan"), @@ -677,7 +677,7 @@ class ReportBase(Base): report_content = Column(UnicodeText) reported_user_id = Column(Integer, ForeignKey(User.id), nullable=False) reported_user = relationship( - User, + User, backref=backref("reports_filed_on", lazy="dynamic", cascade="all, delete-orphan"), @@ -722,7 +722,7 @@ class MediaReport(ReportBase): primary_key=True) media_entry_id = Column(Integer, ForeignKey(MediaEntry.id), nullable=False) media_entry = relationship( - MediaEntry, + MediaEntry, backref=backref("reports_filed_onmod/reports/1/", lazy="dynamic", cascade="all, delete-orphan")) @@ -738,7 +738,7 @@ class ArchivedReport(ReportBase): media_entry_id = Column(Integer, ForeignKey(MediaEntry.id)) media_entry = relationship( - MediaEntry, + MediaEntry, backref=backref("past_reports_filed_on", lazy="dynamic")) comment_id = Column(Integer, ForeignKey(MediaComment.id)) @@ -748,7 +748,7 @@ class ArchivedReport(ReportBase): resolver_id = Column(Integer, ForeignKey(User.id), nullable=False) resolver = relationship( - User, + User, backref=backref("reports_resolved_by", lazy="dynamic", cascade="all, delete-orphan"), @@ -759,23 +759,23 @@ class ArchivedReport(ReportBase): 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 + 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 + :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 + :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, + user_id = Column(Integer, ForeignKey(User.id), nullable=False, primary_key=True) expiration_date = Column(DateTime) reason = Column(UnicodeText, nullable=False) @@ -785,21 +785,21 @@ 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. + privilege object. :keyword privilege_name Holds a unicode object that is the recognizable - name of this privilege. This is the column + 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', + User, + backref='all_privileges', secondary="core__privileges_users") def __init__(self, privilege_name): @@ -818,25 +818,25 @@ 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), + 'core__privilege_id', + Integer, + ForeignKey(User.id), primary_key=True) user_id = Column( - 'core__user_id', - Integer, - ForeignKey(Privilege.id), + 'core__user_id', + Integer, + ForeignKey(Privilege.id), primary_key=True) MODELS = [ User, MediaEntry, Tag, MediaTag, MediaComment, Collection, CollectionItem, MediaFile, FileKeynames, MediaAttachmentFile, ProcessingMetaData, Notification, CommentNotification, ProcessingNotification, Client, - CommentSubscription, ReportBase, CommentReport, MediaReport, UserBan, + CommentSubscription, ReportBase, CommentReport, MediaReport, UserBan, Privilege, PrivilegeUserAssociation, ArchivedReport, RequestToken, AccessToken, NonceTimestamp] @@ -854,10 +854,10 @@ MODELS = [ FOUNDATIONS = {User:user_foundations} """ -privilege_foundations = [{'privilege_name':u'admin'}, - {'privilege_name':u'moderator'}, +privilege_foundations = [{'privilege_name':u'admin'}, + {'privilege_name':u'moderator'}, {'privilege_name':u'uploader'}, - {'privilege_name':u'reporter'}, + {'privilege_name':u'reporter'}, {'privilege_name':u'commenter'}, {'privilege_name':u'active'}] FOUNDATIONS = {Privilege:privilege_foundations} diff --git a/mediagoblin/db/util.py b/mediagoblin/db/util.py index 31fc49fb..2488dcac 100644 --- a/mediagoblin/db/util.py +++ b/mediagoblin/db/util.py @@ -68,24 +68,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 -def user_privileges_to_dictionary(user_id): - """ - This function accepts a users id and returns a dictionary of True or False - values for each privilege the user does or does not have. This allows for - easier referencing of a user's privileges inside templates. - """ - privilege_dictionary = {} - user = User.query.get(user_id) - users_privileges = [p_item.privilege_name for p_item in user.all_privileges] - #TODO update this to account for plugins that may add foundations - for privilege in FOUNDATIONS[Privilege]: - privilege_name = privilege['privilege_name'] - if privilege_name in users_privileges: - privilege_dictionary[privilege_name]=True - else: - privilege_dictionary[privilege_name]=False - return privilege_dictionary - if __name__ == '__main__': from mediagoblin.db.open import setup_connection_and_db_from_config diff --git a/mediagoblin/decorators.py b/mediagoblin/decorators.py index a3479164..1a8b124b 100644 --- a/mediagoblin/decorators.py +++ b/mediagoblin/decorators.py @@ -24,7 +24,7 @@ from mediagoblin import mg_globals as mgg from mediagoblin import messages from mediagoblin.db.models import (MediaEntry, User, MediaComment, UserBan, Privilege) -from mediagoblin.tools.response import (redirect, render_404, +from mediagoblin.tools.response import (redirect, render_404, render_user_banned, json_response) from mediagoblin.tools.translate import pass_to_ugettext as _ @@ -358,7 +358,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( diff --git a/mediagoblin/gmg_commands/users.py b/mediagoblin/gmg_commands/users.py index 0002daad..d319cef9 100644 --- a/mediagoblin/gmg_commands/users.py +++ b/mediagoblin/gmg_commands/users.py @@ -55,7 +55,7 @@ def adduser(args): entry.pw_hash = auth.gen_password_hash(args.password) entry.status = u'active' entry.email_verified = True - default_privileges = [ + default_privileges = [ db.Privilege.query.filter( db.Privilege.privilege_name==u'commenter').one(), db.Privilege.query.filter( diff --git a/mediagoblin/moderation/forms.py b/mediagoblin/moderation/forms.py index 718cd8fa..a3202359 100644 --- a/mediagoblin/moderation/forms.py +++ b/mediagoblin/moderation/forms.py @@ -28,7 +28,7 @@ class MultiCheckboxField(wtforms.SelectMultipleField): 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) @@ -40,7 +40,7 @@ class PrivilegeAddRemoveForm(wtforms.Form): class ReportResolutionForm(wtforms.Form): action_to_resolve = MultiCheckboxField( - _(u'What action will you take to resolve the report?'), + _(u'What action will you take to resolve the report?'), validators=[wtforms.validators.optional()], choices=ACTION_CHOICES) targeted_user = wtforms.HiddenField('', diff --git a/mediagoblin/moderation/tools.py b/mediagoblin/moderation/tools.py index b4daca15..d58df3a8 100644 --- a/mediagoblin/moderation/tools.py +++ b/mediagoblin/moderation/tools.py @@ -31,17 +31,15 @@ def take_punitive_actions(request, form, report, user): # 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: - privilege = Privilege.query.filter( - Privilege.privilege_name==privilege_name).one() + take_away_privileges(user.username, privilege_name) form.resolution_content.data += \ - u"
%s took away %s\'s %s privileges" % ( + u"
%s took away %s\'s %s privileges." % ( request.user.username, user.username, - privilege.privilege_name) - user.all_privileges.remove(privilege) + privilege_name) # If the moderator elects to ban the user, a new instance of user_ban - # will be created. + # will be created. if u'userban' in form.action_to_resolve.data: reason = form.resolution_content.data + \ "
"+request.user.username @@ -51,7 +49,7 @@ def take_punitive_actions(request, form, report, user): reason= form.why_user_was_banned.data ) Session.add(user_ban) - + if form.user_banned_until.data is not None: form.resolution_content.data += \ u"
%s banned user %s until %s." % ( @@ -86,17 +84,17 @@ def take_punitive_actions(request, form, report, user): deleted_comment = report.comment Session.delete(deleted_comment) form.resolution_content.data += \ - u"
%s deleted the comment" % ( + u"
%s deleted the comment." % ( request.user.username) elif u'delete' in form.action_to_resolve.data and \ - report.is_media_entry_report(): + report.is_media_entry_report(): deleted_media = report.media_entry Session.delete(deleted_media) form.resolution_content.data += \ - u"
%s deleted the media entry" % ( - request.user.username) + u"
%s deleted the media entry." % ( + request.user.username) - # If the moderator didn't delete the content we then attach the + # If the moderator didn't delete the content we then attach the # content to the archived report. We also have to actively delete the # old report, since it won't be deleted by cascading. elif report.is_comment_report(): @@ -133,3 +131,34 @@ def take_punitive_actions(request, form, report, user): request, 'mediagoblin.moderation.reports_detail', report_id=report.id) + +def take_away_privileges(user,*privileges): + 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): + 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:])) + diff --git a/mediagoblin/moderation/views.py b/mediagoblin/moderation/views.py index d82eca7d..b2223744 100644 --- a/mediagoblin/moderation/views.py +++ b/mediagoblin/moderation/views.py @@ -19,12 +19,12 @@ from werkzeug.exceptions import Forbidden from mediagoblin.db.models import (MediaEntry, User, MediaComment, \ CommentReport, ReportBase, Privilege, \ UserBan, ArchivedReport) -from mediagoblin.db.util import user_privileges_to_dictionary from mediagoblin.decorators import (require_admin_or_moderator_login, \ - active_user_from_url) + active_user_from_url, user_has_privilege) 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 +from mediagoblin.moderation.tools import (take_punitive_actions, \ + take_away_privileges, give_privileges) from datetime import datetime @require_admin_or_moderator_login @@ -86,7 +86,7 @@ def moderation_users_detail(request): @require_admin_or_moderator_login def moderation_reports_panel(request): ''' - Show the global panel for monitoring reports filed against comments or + Show the global panel for monitoring reports filed against comments or media entries for this instance. ''' report_list = ReportBase.query.filter( @@ -115,7 +115,7 @@ def moderation_reports_detail(request): form.take_away_privileges.choices = [ (s.privilege_name,s.privilege_name.title()) \ - for s in report.reported_user.all_privileges + for s in report.reported_user.all_privileges ] if request.method == "POST" and form.validate() and not ( @@ -134,7 +134,7 @@ def moderation_reports_detail(request): {'report':report, 'form':form}) -@require_admin_or_moderator_login +@user_has_privilege(u'admin') @active_user_from_url def give_or_take_away_privilege(request, url_user): ''' @@ -144,12 +144,13 @@ def give_or_take_away_privilege(request, url_user): if request.method == "POST" and form.validate(): privilege = Privilege.query.filter( Privilege.privilege_name==form.privilege_name.data).one() - if privilege in url_user.all_privileges: - url_user.all_privileges.remove(privilege) - else: - url_user.all_privileges.append(privilege) + 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) + + return redirect( + request, + 'mediagoblin.moderation.users_detail', + user=url_user.username) diff --git a/mediagoblin/templates/mediagoblin/banned.html b/mediagoblin/templates/mediagoblin/banned.html index 4eda0540..cd54158a 100644 --- a/mediagoblin/templates/mediagoblin/banned.html +++ b/mediagoblin/templates/mediagoblin/banned.html @@ -17,12 +17,19 @@ #} {% extends "mediagoblin/base.html" %} -{% block title %}You are Banned.{% endblock %} +{% block title %}{% trans %}You are Banned.{% endtrans %}{% endblock %} {% block mediagoblin_content %} {% trans %}Image of goblin stressing out{% endtrans %} -

You have been banned until {{ expiration_date }}

+ +

{% trans %}You have been banned{% endtrans %} + {% if expiration_date %} + {% trans %}until{% endtrans %} {{ expiration_date }} + {% else %} + {% trans %}indefinitely{% endtrans %} + {% endif %} +

{{ reason|safe }}

{% endblock %} diff --git a/mediagoblin/templates/mediagoblin/moderation/media_panel.html b/mediagoblin/templates/mediagoblin/moderation/media_panel.html index 1c3c866e..d6e02db8 100644 --- a/mediagoblin/templates/mediagoblin/moderation/media_panel.html +++ b/mediagoblin/templates/mediagoblin/moderation/media_panel.html @@ -28,7 +28,7 @@

{% trans %}Here you can track the state of media being processed on this instance.{% endtrans %}

- +

{% trans %}Media in-processing{% endtrans %}

{% if processing_entries.count() %} @@ -56,7 +56,7 @@
{% trans %}Privilege{% endtrans %}
{% else %}

{% trans %}No media in-processing{% endtrans %}

-{% endif %} +{% endif %}

{% trans %}These uploads failed to process:{% endtrans %}

{% if failed_entries.count() %} diff --git a/mediagoblin/templates/mediagoblin/moderation/report.html b/mediagoblin/templates/mediagoblin/moderation/report.html index 04788f05..fafa8b8a 100644 --- a/mediagoblin/templates/mediagoblin/moderation/report.html +++ b/mediagoblin/templates/mediagoblin/moderation/report.html @@ -27,7 +27,7 @@ title="Return to Reports Panel"> {% trans %}Return to Reports Panel{% endtrans %}

{% trans %}Report{% endtrans %} #{{ report.id }}

- {% if report.is_comment_report() or + {% if report.is_comment_report() or (report.is_archived_report() and report.comment) %} {% trans %}Reported comment{% endtrans %}: @@ -60,7 +60,7 @@ {% endautoescape %}
- {% elif report.is_media_entry_report() or + {% elif report.is_media_entry_report() or (report.is_archived_report() and report.media_entry) %} {% set media_entry = report.media_entry %} @@ -89,7 +89,7 @@ 'mediagoblin.moderation.users_detail', user=report.reporter.username), user_name=report.reported_user.username %} - CONTENT BY + CONTENT BY {{ user_name }} HAS BEEN DELETED {% endtrans %} @@ -100,8 +100,8 @@ class="report_wrapper">
Under a GNU LGPL v.3 or Creative Commons BY-SA 3.0 license. 
+                  '/images/icon_clipboard_alert.png') }} {% elif not (report.reported_user.has_privilege('admin')) %} -

Under a GNU LGPL v.3 or Creative Commons BY-SA 3.0 license.
-                  Distributed by the GNOME project http://www.gnome.org + Distributed by the GNOME project http://www.gnome.org" /> {% trans %}Status{% endtrans %}:

- {% trans %}RESOLVED{% endtrans %} + {% trans %}RESOLVED{% endtrans %} {{ report.resolved.strftime("%I:%M%p %Y-%m-%d") }} {% autoescape False %}

{{ report.result }}

diff --git a/mediagoblin/templates/mediagoblin/moderation/report_panel.html b/mediagoblin/templates/mediagoblin/moderation/report_panel.html index 2818eb80..fb9d8cd9 100644 --- a/mediagoblin/templates/mediagoblin/moderation/report_panel.html +++ b/mediagoblin/templates/mediagoblin/moderation/report_panel.html @@ -30,7 +30,7 @@ Here you can look up open reports that have been filed by users. {% endtrans %}

- +

{% trans %}Active Reports Filed{% endtrans %}

{% if report_list.count() %} @@ -46,22 +46,22 @@ {% if report.discriminator == "comment_report" %} - Under a GNU LGPL v.3 or Creative Commons BY-SA 3.0 license.
                    Distributed by the GNOME project http://www.gnome.org
{% trans report_id=report.id %} - Comment Report #{{ report_id }} + Comment Report #{{ report_id }} {% endtrans %} {% elif report.discriminator == "media_report" %} - Under a GNU LGPL v.3 or Creative Commons BY-SA 3.0 license.
                    Distributed by the GNOME project http://www.gnome.org {{ user.username }}'s report history

{{ user.username }}'s Privileges

- {% if request.user.has_privilege('admin') and not user_banned and + {% if request.user.has_privilege('admin') and not user_banned and not user.id == request.user.id %} - {% elif request.user.has_privilege('admin') and not user.id == request.user.id %} diff --git a/mediagoblin/templates/mediagoblin/moderation/user_panel.html b/mediagoblin/templates/mediagoblin/moderation/user_panel.html index 6762a844..54ef7c11 100644 --- a/mediagoblin/templates/mediagoblin/moderation/user_panel.html +++ b/mediagoblin/templates/mediagoblin/moderation/user_panel.html @@ -30,7 +30,7 @@ Here you can look up users in order to take punitive actions on them. {% endtrans %}

- +

{% trans %}Active Users{% endtrans %}

{% if user_list.count() %} @@ -57,5 +57,5 @@ {% else %}

{% trans %}No users found.{% endtrans %}

-{% endif %} +{% endif %} {% endblock %} diff --git a/mediagoblin/templates/mediagoblin/user_pages/media.html b/mediagoblin/templates/mediagoblin/user_pages/media.html index e161afc9..b92cf39c 100644 --- a/mediagoblin/templates/mediagoblin/user_pages/media.html +++ b/mediagoblin/templates/mediagoblin/user_pages/media.html @@ -86,7 +86,7 @@ {% autoescape False %}

{{ media.description_html }}

{% endautoescape %} - {% if comments %} + {% if comments and request.user.has_privilege('commenter') %} {% if app_config['allow_comments'] %}
- @@ -42,7 +42,7 @@ - {%- trans formatted_time=timesince(comment.created) -%} @@ -59,19 +59,19 @@ {% elif media is defined %}

{% trans %}Reporting this Media Entry{% endtrans %}

{%- trans user_url = request.urlgen('mediagoblin.user_pages.user_home', user=media.get_uploader.username), username = media.get_uploader.username %} - ❖ Published by {{ username }} {% endtrans %} {%- endif %} diff --git a/mediagoblin/templates/mediagoblin/utils/report.html b/mediagoblin/templates/mediagoblin/utils/report.html index 4108cd82..2fa4f959 100644 --- a/mediagoblin/templates/mediagoblin/utils/report.html +++ b/mediagoblin/templates/mediagoblin/utils/report.html @@ -18,7 +18,7 @@ {% block report_content -%}

- . + +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.moderation.tools import take_away_privileges, give_privileges +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)) + + assert response.status == '302 FOUND' + fixture_add_comment_report(reported_user=self.user) + comment_report = CommentReport.query.filter( + CommentReport.reported_user==self.user).first() + + assert not self.user.has_privilege(u'commenter') + + # 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)) + + 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'}] + + # 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.reported_user==self.user).first() + + response, context = self.do_post( + {'action_to_resolve':[u'userban', u'delete'], + 'targeted_user':self.user.id}, + 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).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') + self.test_app.get('/mod/reports/') + self.test_app.get('/mod/users/') + self.test_app.get('/mod/media/') diff --git a/mediagoblin/tests/test_notifications.py b/mediagoblin/tests/test_notifications.py index 8420e358..2b414590 100644 --- a/mediagoblin/tests/test_notifications.py +++ b/mediagoblin/tests/test_notifications.py @@ -127,7 +127,6 @@ otherperson@example.com\n\nSGkgb3RoZXJwZXJzb24sCmNocmlzIGNvbW1lbnRlZCBvbiB5b3VyI else: assert mail.EMAIL_TEST_MBOX_INBOX == [] - mail.EMAIL_TEST_MBOX_INBOX = [] # Save the ids temporarily because of DetachedInstanceError notification_id = notification.id diff --git a/mediagoblin/tests/test_privileges.py b/mediagoblin/tests/test_privileges.py new file mode 100644 index 00000000..ced87b7f --- /dev/null +++ b/mediagoblin/tests/test_privileges.py @@ -0,0 +1,206 @@ +# 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 . + +import pytest +from datetime import datetime, timedelta +from webtest import AppError + +from mediagoblin.tests.tools import fixture_add_user, fixture_media_entry + +from mediagoblin.db.models import User, Privilege, UserBan +from mediagoblin.db.base import Session +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'raven', + 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'raven').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= datetime.now() + 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 = datetime.now() - 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) diff --git a/mediagoblin/tests/test_reporting.py b/mediagoblin/tests/test_reporting.py new file mode 100644 index 00000000..1bc7df26 --- /dev/null +++ b/mediagoblin/tests/test_reporting.py @@ -0,0 +1,165 @@ +# 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 . + +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,ArchivedReport) + + +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" + self.query_for_users() + + archived_report = ArchivedReport.query.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 == 'archived_report' + diff --git a/mediagoblin/tests/test_submission.py b/mediagoblin/tests/test_submission.py index ed088730..d10957d7 100644 --- a/mediagoblin/tests/test_submission.py +++ b/mediagoblin/tests/test_submission.py @@ -24,7 +24,7 @@ import pytest from mediagoblin.tests.tools import fixture_add_user from mediagoblin import mg_globals -from mediagoblin.db.models import MediaEntry, User +from mediagoblin.db.models import MediaEntry, User, Privilege from mediagoblin.tools import template from mediagoblin.media_types.image import ImageMediaManager from mediagoblin.media_types.pdf.processing import check_prerequisites as pdf_check_prerequisites @@ -48,10 +48,20 @@ class TestSubmission: # @as_authenticated_user('chris') fixture_add_user(privileges=[u'active',u'uploader']) - self.test_user = User.query.filter(User.username==u'chris').first() - 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/', { @@ -97,10 +107,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. @@ -118,7 +128,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 check_media(self, request, find_data, count=None): @@ -164,7 +174,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', @@ -177,7 +187,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) @@ -186,7 +196,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) @@ -251,7 +261,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' diff --git a/mediagoblin/tests/tools.py b/mediagoblin/tests/tools.py index feb83b44..1d035494 100644 --- a/mediagoblin/tests/tools.py +++ b/mediagoblin/tests/tools.py @@ -25,7 +25,7 @@ from webtest import TestApp from mediagoblin import mg_globals from mediagoblin.db.models import User, MediaEntry, Collection, MediaComment, \ - CommentSubscription, CommentNotification, Privilege + 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( @@ -312,3 +314,33 @@ 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 by user {0}'.format( + reporter) + + 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 diff --git a/mediagoblin/tools/response.py b/mediagoblin/tools/response.py index a8cf1df9..3884de45 100644 --- a/mediagoblin/tools/response.py +++ b/mediagoblin/tools/response.py @@ -52,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) @@ -78,9 +79,11 @@ def render_user_banned(request): and the reason why they have been banned" """ user_ban = UserBan.query.get(request.user.id) - if datetime.now()>user_ban.expiration_date: + if (user_ban.expiration_date is not None and + datetime.now()>user_ban.expiration_date): + user_ban.delete() - redirect(request, + return redirect(request, 'index') return render_to_response(request, 'mediagoblin/banned.html', @@ -141,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: diff --git a/mediagoblin/user_pages/lib.py b/mediagoblin/user_pages/lib.py index 7f03fcd3..f29c1796 100644 --- a/mediagoblin/user_pages/lib.py +++ b/mediagoblin/user_pages/lib.py @@ -19,7 +19,7 @@ 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, MediaReport, CommentReport, +from mediagoblin.db.models import (CollectionItem, MediaReport, CommentReport, MediaComment, MediaEntry) from mediagoblin.user_pages import forms as user_forms @@ -80,14 +80,14 @@ def add_media_to_collection(collection, media, note=None, commit=True): 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 + 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 should be a MediaReportForm or a CommentReportForm + :param report_form should be a MediaReportForm or a CommentReportForm object - :param + :param - :returns either of MediaReport or a CommentReport object that has not been + :returns either of MediaReport or a CommentReport object that has not been saved. In case of an improper form_dict, returns None """ diff --git a/mediagoblin/user_pages/views.py b/mediagoblin/user_pages/views.py index 00fcf282..c2d2e66f 100644 --- a/mediagoblin/user_pages/views.py +++ b/mediagoblin/user_pages/views.py @@ -162,6 +162,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. @@ -651,13 +652,13 @@ def file_a_report(request, media, comment=None): 'form':form} if request.method == "POST": - report_table = build_report_object(form, - media_entry=media, + 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_table: - report_table.save() + if report_object: + report_object.save() return redirect( request, 'index') @@ -671,5 +672,5 @@ def file_a_report(request, media, comment=None): @require_active_login @get_user_media_entry @get_media_comment_by_id -def file_a_comment_report(request, media, comment): +def file_a_comment_report(request, media, comment): return file_a_report(request, comment=comment) From 1bb367f6136ae4cbcdf6dd86af65eb613913dbd8 Mon Sep 17 00:00:00 2001 From: tilly-Q Date: Thu, 29 Aug 2013 17:31:19 -0400 Subject: [PATCH 15/25] This is a quick commit. I gave admins the ability to ban or unban users straight from the moderation.users_detail page. I also changed the UserBan.expiration_date type from DateTime into Date. I also began work on the Terms of Service, pulled from another website (which will be cited clearly before I'm done). I added new tests as well for the ban/unbanning. Lastly, I added a few `user_not_banned` decorators to relevant views, so banned users cannot access any pages. --- mediagoblin/db/migrations.py | 4 +- mediagoblin/db/models.py | 4 +- mediagoblin/moderation/forms.py | 9 + mediagoblin/moderation/routing.py | 3 + mediagoblin/moderation/tools.py | 43 +++- mediagoblin/moderation/views.py | 26 +- mediagoblin/submit/views.py | 1 + .../mediagoblin/meta/terms_of_service.html | 243 ++++++++++++++++++ .../mediagoblin/moderation/user.html | 37 ++- mediagoblin/tests/test_moderation.py | 47 +++- mediagoblin/user_pages/views.py | 1 + 11 files changed, 395 insertions(+), 23 deletions(-) create mode 100644 mediagoblin/templates/mediagoblin/meta/terms_of_service.html diff --git a/mediagoblin/db/migrations.py b/mediagoblin/db/migrations.py index a97458b6..6659feb3 100644 --- a/mediagoblin/db/migrations.py +++ b/mediagoblin/db/migrations.py @@ -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_ @@ -501,7 +501,7 @@ class UserBan_v0(declarative_base()): __tablename__ = 'core__user_bans' user_id = Column('id',Integer, ForeignKey(User.id), nullable=False, primary_key=True) - expiration_date = Column(DateTime) + expiration_date = Column(Date) reason = Column(UnicodeText, nullable=False) class Privilege_v0(declarative_base()): diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index dd2cd3e9..1e8d0a0c 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -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 @@ -777,7 +777,7 @@ class UserBan(Base): user_id = Column(Integer, ForeignKey(User.id), nullable=False, primary_key=True) - expiration_date = Column(DateTime) + expiration_date = Column(Date) reason = Column(UnicodeText, nullable=False) diff --git a/mediagoblin/moderation/forms.py b/mediagoblin/moderation/forms.py index a3202359..dd5a9282 100644 --- a/mediagoblin/moderation/forms.py +++ b/mediagoblin/moderation/forms.py @@ -38,6 +38,15 @@ class MultiCheckboxField(wtforms.SelectMultipleField): class PrivilegeAddRemoveForm(wtforms.Form): privilege_name = wtforms.HiddenField('',[wtforms.validators.required()]) +class BanForm(wtforms.Form): + 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()]) + class ReportResolutionForm(wtforms.Form): action_to_resolve = MultiCheckboxField( _(u'What action will you take to resolve the report?'), diff --git a/mediagoblin/moderation/routing.py b/mediagoblin/moderation/routing.py index f177c32a..ba10bc6d 100644 --- a/mediagoblin/moderation/routing.py +++ b/mediagoblin/moderation/routing.py @@ -30,6 +30,9 @@ moderation_routes = [ ('mediagoblin.moderation.give_or_take_away_privilege', '/users//privilege/', 'mediagoblin.moderation.views:give_or_take_away_privilege'), + ('mediagoblin.moderation.ban_or_unban', + '/users//ban/', + 'mediagoblin.moderation.views:ban_or_unban'), ('mediagoblin.moderation.reports_detail', '/reports//', 'mediagoblin.moderation.views:moderation_reports_detail')] diff --git a/mediagoblin/moderation/tools.py b/mediagoblin/moderation/tools.py index d58df3a8..49d4381e 100644 --- a/mediagoblin/moderation/tools.py +++ b/mediagoblin/moderation/tools.py @@ -43,11 +43,9 @@ def take_punitive_actions(request, form, report, user): if u'userban' in form.action_to_resolve.data: reason = form.resolution_content.data + \ "
"+request.user.username - user_ban = UserBan( - user_id=form.targeted_user.data, + user_ban = ban_user(form.targeted_user.data, expiration_date=form.user_banned_until.data, - reason= form.why_user_was_banned.data - ) + reason=form.why_user_was_banned.data) Session.add(user_ban) if form.user_banned_until.data is not None: @@ -162,3 +160,40 @@ def give_privileges(user,*privileges): 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 + + + diff --git a/mediagoblin/moderation/views.py b/mediagoblin/moderation/views.py index b2223744..869b7b8a 100644 --- a/mediagoblin/moderation/views.py +++ b/mediagoblin/moderation/views.py @@ -24,7 +24,7 @@ from mediagoblin.decorators import (require_admin_or_moderator_login, \ 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) + take_away_privileges, give_privileges, ban_user, unban_user) from datetime import datetime @require_admin_or_moderator_login @@ -74,6 +74,7 @@ def moderation_users_detail(request): ReportBase.discriminator=='archived_report').all() privileges = Privilege.query user_banned = UserBan.query.get(user.id) + ban_form = moderation_forms.BanForm() return render_to_response( request, @@ -81,7 +82,8 @@ def moderation_users_detail(request): {'user':user, 'privileges': privileges, 'reports':active_reports, - 'user_banned':user_banned}) + 'user_banned':user_banned, + 'ban_form':ban_form}) @require_admin_or_moderator_login def moderation_reports_panel(request): @@ -154,3 +156,23 @@ def give_or_take_away_privilege(request, url_user): 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) + print "accessed page" + if request.method == "POST" and form.validate(): + already_banned = unban_user(url_user.id) + if not already_banned: + 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) diff --git a/mediagoblin/submit/views.py b/mediagoblin/submit/views.py index 8640b8de..0700f933 100644 --- a/mediagoblin/submit/views.py +++ b/mediagoblin/submit/views.py @@ -125,6 +125,7 @@ def submit_start(request): @require_active_login +@user_not_banned def add_collection(request, media=None): """ View to create a new collection diff --git a/mediagoblin/templates/mediagoblin/meta/terms_of_service.html b/mediagoblin/templates/mediagoblin/meta/terms_of_service.html new file mode 100644 index 00000000..c63f9a53 --- /dev/null +++ b/mediagoblin/templates/mediagoblin/meta/terms_of_service.html @@ -0,0 +1,243 @@ +

The gist

+ +

Terms of Service

+ +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, Operator’s 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. + +
    +
  1. Your {{ app_config['html_title'] }} Account and Site. 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. +
  2. +
  3. Responsibility of Contributors. 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: +
      +
    • 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; +
    • +
    • 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; +
    • +
    • 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; +
    • +
    • the Content does not contain or install any viruses, worms, malware, + Trojan horses or other harmful or destructive content; +
    • +
    • 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); +
    • +
    • 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, +
    • +
    • 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; +
    • +
    • 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; +
    • +
    • 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 stream’s URL or name is not the name of a person + other than yourself or company other than your own; and +
    • +
    • 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.
    • +
    + 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 Operator’s sole discretion + (i) refuse or remove any content that, in Operator’s 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 Operator’s sole discretion. +
  4. +
  5. Responsibility of Website Visitors. 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 material’s 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. +
  6. +
  7. Content Posted on Other Websites. 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. +
  8. +
  9. Copyright Infringement and DMCA Policy. 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 Operator’s 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. +
  10. +
  11. Intellectual Property. 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 Operator’s 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. +
  12. +
  13. Changes. 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. +
  14. +
  15. Termination. 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. +
  16. +
  17. Disclaimer of Warranties. 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 you’re actually reading this, here’s a treat. You + understand that you download from, or otherwise obtain content or services + through, the Website at your own discretion and risk. +
  18. +
  19. Limitation of Liability. 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. +
  20. +
  21. General Representation and Warranty. 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. +
  22. +
  23. Indemnification. 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. +
  24. +
  25. Miscellaneous. 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. +
  26. +
+ +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. diff --git a/mediagoblin/templates/mediagoblin/moderation/user.html b/mediagoblin/templates/mediagoblin/moderation/user.html index 662d48b1..0d0465c5 100644 --- a/mediagoblin/templates/mediagoblin/moderation/user.html +++ b/mediagoblin/templates/mediagoblin/moderation/user.html @@ -16,7 +16,7 @@ # along with this program. If not, see . #} {% extends "mediagoblin/base.html" %} - +{% import "/mediagoblin/utils/wtforms.html" as wtforms_util %} {% block title %} {%- if user -%} @@ -130,15 +130,22 @@
{{ user.username }}'s report history

{{ user.username }}'s Privileges

- {% if request.user.has_privilege('admin') and not user_banned and - not user.id == request.user.id %} - - {% elif request.user.has_privilege('admin') and + + {{ csrf_token }} + {% if request.user.has_privilege('admin') and not user_banned and not user.id == request.user.id %} - - {% endif %} + {{ wtforms_util.render_divs(ban_form) }} + + {% elif request.user.has_privilege('admin') and + not user.id == request.user.id %} + + {% endif %} +
@@ -172,7 +179,7 @@ {% endfor %} - {{ csrf_token }} + {{ csrf_token }}
{% endif %} @@ -181,6 +188,16 @@ $(document).ready(function(){ $('.submit_button').click(function(){ $('#hidden_privilege_name').val($(this).attr('id')); }); + $('#user_banned_until').val("YYYY-MM-DD") + $("#user_banned_until").focus(function() { + $(this).val(""); + $(this).unbind('focus'); + }); + $("#ban_user_submit").click(function(){ + if ($("#user_banned_until").val() == 'YYYY-MM-DD'){ + $("#user_banned_until").val(""); + } + }); }); {% endblock %} diff --git a/mediagoblin/tests/test_moderation.py b/mediagoblin/tests/test_moderation.py index d4f57c74..6160ce3d 100644 --- a/mediagoblin/tests/test_moderation.py +++ b/mediagoblin/tests/test_moderation.py @@ -21,6 +21,7 @@ from mediagoblin.tests.tools import (fixture_add_user, from mediagoblin.db.models import User, CommentReport, MediaComment, UserBan from mediagoblin.moderation.tools import take_away_privileges, give_privileges from mediagoblin.tools import template, mail +from datetime import date, timedelta from webtest import AppError @@ -160,7 +161,9 @@ VGhpcyBpcyB5b3VyIGxhc3Qgd2FybmluZywgcmVndWxhci4uLi4=\n', response, context = self.do_post( {'action_to_resolve':[u'userban', u'delete'], - 'targeted_user':self.user.id}, + '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() @@ -189,6 +192,44 @@ VGhpcyBpcyB5b3VyIGxhc3Qgd2FybmluZywgcmVndWxhci4uLi4=\n', def testAllModerationViews(self): self.login(u'moderator') - self.test_app.get('/mod/reports/') - self.test_app.get('/mod/users/') + username = self.user.username + 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 + diff --git a/mediagoblin/user_pages/views.py b/mediagoblin/user_pages/views.py index c2d2e66f..0028684d 100644 --- a/mediagoblin/user_pages/views.py +++ b/mediagoblin/user_pages/views.py @@ -367,6 +367,7 @@ def collection_list(request, url_user=None): @get_user_collection_item @require_active_login @user_may_alter_collection +@user_not_banned def collection_item_confirm_remove(request, collection_item): form = user_forms.ConfirmCollectionItemRemoveForm(request.form) From dc31cd1b658067d25cda470795020d3c377feae0 Mon Sep 17 00:00:00 2001 From: tilly-Q Date: Tue, 3 Sep 2013 11:57:10 -0400 Subject: [PATCH 16/25] I've moved on to one of the last phases of my work! Now I'm just checking off items from my last to-do list. The biggest change in this commit is that I made the moderation reports panel sortable via get request. I also added in page nu- mbers so that more than 10 reports can be viewed. I'm hoping to go from here to make a search page. Aside from that, there were only a few other changes I made this time. I fixed two bugs in my code. I copy-ed and pasted function mediagoblin.user_pages.views:media_preview_comment which I must've deleted ear- -lier in a merge. And I moved some of the javascript I was using in the modera- -tion templates into it's own seperate .js file. =============================================================================== Made the moderation reports panel view sortable =============================================================================== --\ mediagoblin/moderation/forms.py --\ mediagoblin/moderation/views.py --\ mediagoblin/templates/mediagoblin/moderation/report_panel.html --\ mediagoblin/templates/mediagoblin/moderation/user.html --| Made ` report history` into a link that automatically shows all open | and closed reports on . =============================================================================== Grabbed some code from master that I accidentally deleted in a merge =============================================================================== --\ mediagoblin/user_pages/views.py =============================================================================== Moved javascript away from templates into its own file =============================================================================== --\ mediagoblin/static/js/setup_report_forms.js --\ mediagoblin/templates/mediagoblin/moderation/report.html --\ mediagoblin/templates/mediagoblin/moderation/user.html =============================================================================== Cleared trailing white space =============================================================================== --\ mediagoblin/templates/mediagoblin/moderation/media_panel.html --\ mediagoblin/moderation/tools.py --\ mediagoblin/templates/mediagoblin/meta/terms_of_service.html --\ mediagoblin/templates/mediagoblin/moderation/report_panel.html --\ mediagoblin/templates/mediagoblin/user_pages/media.html --\ mediagoblin/tests/test_modelmethods.py =============================================================================== Small fixes =============================================================================== --\ mediagoblin/templates/mediagoblin/moderation/report.html --| Fixed a link so that it points to the correct user page --\ mediagoblin/templates/mediagoblin/user_pages/media.html --| Fixed a bug that crashed this page when a guest visitted it (because | request.user is None) --- mediagoblin/moderation/forms.py | 20 ++ mediagoblin/moderation/tools.py | 4 +- mediagoblin/moderation/views.py | 43 ++- mediagoblin/static/js/setup_report_forms.js | 67 +++++ .../mediagoblin/meta/terms_of_service.html | 270 +++++++++--------- .../mediagoblin/moderation/media_panel.html | 1 + .../mediagoblin/moderation/report.html | 50 +--- .../mediagoblin/moderation/report_panel.html | 61 +++- .../mediagoblin/moderation/user.html | 18 +- .../mediagoblin/user_pages/media.html | 4 +- mediagoblin/tests/test_modelmethods.py | 4 +- mediagoblin/user_pages/views.py | 13 + 12 files changed, 351 insertions(+), 204 deletions(-) create mode 100644 mediagoblin/static/js/setup_report_forms.js diff --git a/mediagoblin/moderation/forms.py b/mediagoblin/moderation/forms.py index dd5a9282..bcb2fc77 100644 --- a/mediagoblin/moderation/forms.py +++ b/mediagoblin/moderation/forms.py @@ -67,3 +67,23 @@ class ReportResolutionForm(wtforms.Form): validators=[wtforms.validators.optional()]) resolution_content = wtforms.TextAreaField() +class ReportPanelSortingForm(wtforms.Form): + active_p = wtforms.IntegerField( + _(u'Page'), + validators=[wtforms.validators.optional()]) + active_reported_user = wtforms.IntegerField( + _(u'Reported User'), + validators=[wtforms.validators.optional()]) + active_reporter = wtforms.IntegerField( + _(u'Reporter'), + validators=[wtforms.validators.optional()]) + closed_p = wtforms.IntegerField( + _(u'Page'), + validators=[wtforms.validators.optional()]) + closed_reported_user = wtforms.IntegerField( + _(u'Reported User'), + validators=[wtforms.validators.optional()]) + closed_reporter = wtforms.IntegerField( + _(u'Reporter'), + validators=[wtforms.validators.optional()]) + diff --git a/mediagoblin/moderation/tools.py b/mediagoblin/moderation/tools.py index 49d4381e..79a6e3f5 100644 --- a/mediagoblin/moderation/tools.py +++ b/mediagoblin/moderation/tools.py @@ -163,7 +163,7 @@ def give_privileges(user,*privileges): 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 + 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 @@ -177,7 +177,7 @@ def ban_user(user_id, expiration_date=None, reason=None): user_id=user_id, expiration_date=expiration_date, reason=reason) - return new_user_ban + return new_user_ban def unban_user(user_id): """ diff --git a/mediagoblin/moderation/views.py b/mediagoblin/moderation/views.py index 869b7b8a..35c586ea 100644 --- a/mediagoblin/moderation/views.py +++ b/mediagoblin/moderation/views.py @@ -26,6 +26,7 @@ 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) from datetime import datetime +from math import ceil @require_admin_or_moderator_login def moderation_media_processing_panel(request): @@ -91,19 +92,47 @@ def moderation_reports_panel(request): Show the global panel for monitoring reports filed against comments or media entries for this instance. ''' - report_list = ReportBase.query.filter( - ReportBase.discriminator!="archived_report").order_by( - ReportBase.created.desc()).limit(10) - closed_report_list = ReportBase.query.filter( - ReportBase.discriminator=="archived_report").order_by( - ReportBase.created.desc()).limit(10) + form = moderation_forms.ReportPanelSortingForm(request.args) + active_settings = {'start_page':1, 'filters':{}} + closed_settings = {'start_page':1, 'filters':{}} + if form.validate(): + active_settings['start_page'] = form.active_p.data or 1 + active_settings['filters']['reported_user_id'] = form.active_reported_user.data + active_settings['filters']['reporter_id'] = form.active_reporter.data + closed_settings['start_page'] = form.closed_p.data or 1 + closed_settings['filters']['reported_user_id'] = form.closed_reported_user.data + closed_settings['filters']['reporter_id'] = form.closed_reporter.data + + active_settings['filters']=dict((k, v) for k, v in active_settings['filters'].iteritems() if v) + closed_settings['filters']=dict((k, v) for k, v in closed_settings['filters'].iteritems() if v) + active_filter = [ + getattr(ReportBase,key)==val \ +for key,val in active_settings['filters'].viewitems()] + closed_filter = [ + getattr(ReportBase,key)==val \ +for key,val in active_settings['filters'].viewitems()] + + all_active = ReportBase.query.filter( + ReportBase.discriminator!="archived_report").filter( + *active_filter) + all_closed = ReportBase.query.filter( + ReportBase.discriminator=="archived_report").filter( + *closed_filter) + report_list = all_active.order_by( + ReportBase.created.desc()).offset((active_settings['start_page']-1)*10).limit(10) + closed_report_list = all_closed.order_by( + ReportBase.created.desc()).offset((closed_settings['start_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}) + 'closed_report_list':closed_report_list, + 'active_settings':active_settings, + 'closed_settings':closed_settings}) @require_admin_or_moderator_login def moderation_reports_detail(request): diff --git a/mediagoblin/static/js/setup_report_forms.js b/mediagoblin/static/js/setup_report_forms.js new file mode 100644 index 00000000..a75a92dd --- /dev/null +++ b/mediagoblin/static/js/setup_report_forms.js @@ -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 . + */ + +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'); + }); +} diff --git a/mediagoblin/templates/mediagoblin/meta/terms_of_service.html b/mediagoblin/templates/mediagoblin/meta/terms_of_service.html index c63f9a53..0951c044 100644 --- a/mediagoblin/templates/mediagoblin/meta/terms_of_service.html +++ b/mediagoblin/templates/mediagoblin/meta/terms_of_service.html @@ -2,242 +2,242 @@

Terms of Service

-The following terms and conditions govern all use of the -{{ app_config['html_title'] }} website and all content, services and products +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, Operator’s Privacy Policy) and procedures +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, Operator’s 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.
    -
  1. Your {{ app_config['html_title'] }} Account and Site. 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 +
  2. Your {{ app_config['html_title'] }} Account and Site. 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 + 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 + 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.
  3. -
  4. Responsibility of Contributors. 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 +
  5. Responsibility of Contributors. 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 + 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:
      -
    • the downloading, copying and use of the Content will not infringe - the proprietary rights, including but not limited to the copyright, +
    • 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;
    • 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 + 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;
    • you have fully complied with any third-party licenses relating to the - Content, and have done all things necessary to successfully pass + Content, and have done all things necessary to successfully pass through to end users any required terms;
    • the Content does not contain or install any viruses, worms, malware, Trojan horses or other harmful or destructive content;
    • -
    • 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 +
    • 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);
    • if the Content is machine- or randomly-generated, it is for purposes - of direct entertainment, information and/or utility for you or other + of direct entertainment, information and/or utility for you or other users, and not for spam,
    • -
    • the Content is not libelous or defamatory (more info on what that +
    • 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 + or entities, and does not violate the privacy or publicity rights of any third party;
    • your notice stream is not getting advertised via unwanted electronic - messages such as spam links on newsgroups, email lists, other notice + messages such as spam links on newsgroups, email lists, other notice streams and web sites, and similar unsolicited promotional methods;
    • -
    • 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 stream’s URL or name is not the name of a person +
    • 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 stream’s URL or name is not the name of a person other than yourself or company other than your own; and
    • -
    • 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 +
    • 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.
    By submitting Content to Operator for inclusion on your Website, you grant - Operator a world-wide, royalty-free, and non-exclusive license to + 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 + 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 + 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 Operator’s sole discretion - (i) refuse or remove any content that, in Operator’s reasonable opinion, + Without limiting any of those representations or warranties, Operator has + the right (though not the obligation) to, in Operator’s sole discretion + (i) refuse or remove any content that, in Operator’s 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 + (ii) terminate or deny access to and use of the Website to any individual or entity for any reason, in Operator’s sole discretion.
  6. Responsibility of Website Visitors. 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 material’s 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 + review, all of the material, including computer software, posted to the + Website, and cannot therefore be responsible for that material’s 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 + visitors of the Website, or from any downloading by those visitors of content there posted.
  7. -
  8. Content Posted on Other Websites. We have not reviewed, and cannot +
  9. Content Posted on Other Websites. 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 + 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.
  10. -
  11. Copyright Infringement and DMCA Policy. 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 Operator’s 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 +
  12. Copyright Infringement and DMCA Policy. 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 Operator’s 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 + 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.
  13. Intellectual Property. This Agreement does not transfer from Operator to - you any Operator or third party intellectual property, and all right, + 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 Operator’s 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 + 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 Operator’s 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.
  14. -
  15. Changes. Operator reserves the right, at its sole discretion, to modify +
  16. Changes. 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 + 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 + 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.
  17. -
  18. Termination. 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, +
  19. Termination. 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.
  20. -
  21. Disclaimer of Warranties. 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 you’re actually reading this, here’s a treat. You +
  22. Disclaimer of Warranties. 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 you’re actually reading this, here’s a treat. You understand that you download from, or otherwise obtain content or services through, the Website at your own discretion and risk.
  23. -
  24. Limitation of Liability. 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 +
  25. Limitation of Liability. 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 + 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.
  26. -
  27. General Representation and Warranty. 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 +
  28. General Representation and Warranty. 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 + 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.
  29. -
  30. Indemnification. 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, +
  31. Indemnification. 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.
  32. Miscellaneous. 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 + 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 + 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 + 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.
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 +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. diff --git a/mediagoblin/templates/mediagoblin/moderation/media_panel.html b/mediagoblin/templates/mediagoblin/moderation/media_panel.html index d6e02db8..3c929d4f 100644 --- a/mediagoblin/templates/mediagoblin/moderation/media_panel.html +++ b/mediagoblin/templates/mediagoblin/moderation/media_panel.html @@ -21,6 +21,7 @@ {% trans %}Media processing panel{% endtrans %} — {{ super() }} {%- endblock %} + {% block mediagoblin_content %}

{% trans %}Media processing panel{% endtrans %}

diff --git a/mediagoblin/templates/mediagoblin/moderation/report.html b/mediagoblin/templates/mediagoblin/moderation/report.html index fafa8b8a..e597b752 100644 --- a/mediagoblin/templates/mediagoblin/moderation/report.html +++ b/mediagoblin/templates/mediagoblin/moderation/report.html @@ -17,6 +17,9 @@ #} {%- extends "mediagoblin/base.html" %} {% import "/mediagoblin/utils/wtforms.html" as wtforms_util %} +{%- block mediagoblin_head %} + +{% endblock %} {%- block mediagoblin_content %} {% if not report %} @@ -79,7 +82,7 @@ {% trans user_name=report.reported_user.username, user_url=request.urlgen( 'mediagoblin.moderation.users_detail', - user=report.reporter.username) %} + user=report.reported_user.username) %} ❖ Reported media by {{ user_name }} {% endtrans %}

@@ -129,50 +132,9 @@ {{ csrf_token }} - - {% elif not (report.reported_user.has_privilege('admin')) %} diff --git a/mediagoblin/templates/mediagoblin/moderation/report_panel.html b/mediagoblin/templates/mediagoblin/moderation/report_panel.html index fb9d8cd9..a72e2e77 100644 --- a/mediagoblin/templates/mediagoblin/moderation/report_panel.html +++ b/mediagoblin/templates/mediagoblin/moderation/report_panel.html @@ -32,7 +32,37 @@

{% trans %}Active Reports Filed{% endtrans %}

- + {% if not active_settings.last_page == 1 %} + {% if 'active_p='~active_settings.start_page in request.query_string %} + {% set query_string = request.query_string %}{% else %} + {% set query_string = +'active_p='~active_settings.start_page~"&"+request.query_string %} + {% endif %} +
+ {% set first_vis = active_settings.start_page-3 %} + {% set last_vis = active_settings.start_page+3 %} + {% set curr_page = active_settings.start_page %} + {% if 1 == curr_page %}1{% else %} + + {{ 1 }}{% endif %} + {% if first_vis > 1 %}...{% endif %} + {% for p in range(first_vis,last_vis+1) %} + {% if p > 1 and p < active_settings.last_page %} + + {{ p }} + {% endif %} + {% endfor %} + {% if last_vis < active_settings.last_page %}...{% endif %} + + {{ active_settings.last_page }} +
+ {% endif %} {% if report_list.count() %} @@ -84,6 +114,35 @@

{% trans %}No open reports found.{% endtrans %}

{% endif %}

{% trans %}Closed Reports{% endtrans %}

+ {% if not closed_settings.last_page == 1 %} + {% if 'closed_p='~closed_settings.start_page in request.query_string %} + {% set query_string = request.query_string %}{% else %} + {% set query_string = +'closed_p='~closed_settings.start_page~"&"+request.query_string %} + {% endif %} +
+ {% set first_vis = closed_settings.start_page-3 %} + {% set last_vis = closed_settings.start_page+3 %} + + {{ 1 }} + {% if first_vis > 1 %}...{% endif %} + {% for p in range(first_vis,last_vis+1) %} + {% if p > 1 and p < closed_settings.last_page %} + + {{ p }} + {% endif %} + {% endfor %} + {% if last_vis < closed_settings.last_page %}...{% endif %} + + {{ closed_settings.last_page }} +
+ {% endif %} {% if closed_report_list.count() %}
diff --git a/mediagoblin/templates/mediagoblin/moderation/user.html b/mediagoblin/templates/mediagoblin/moderation/user.html index 0d0465c5..a8befa81 100644 --- a/mediagoblin/templates/mediagoblin/moderation/user.html +++ b/mediagoblin/templates/mediagoblin/moderation/user.html @@ -28,6 +28,9 @@ {%- endif -%} {% endblock %} +{%- block mediagoblin_head %} + +{% endblock %} {% block mediagoblin_content -%} {# If no user... #} @@ -127,7 +130,8 @@ {% else %} {%- trans %}No active reports filed on {% endtrans -%} {{ user.username }} {% endif %} - {{ user.username }}'s report history + {{ user.username }}'s report history

{{ user.username }}'s Privileges

{% endblock %} diff --git a/mediagoblin/templates/mediagoblin/user_pages/media.html b/mediagoblin/templates/mediagoblin/user_pages/media.html index b92cf39c..62c2a48c 100644 --- a/mediagoblin/templates/mediagoblin/user_pages/media.html +++ b/mediagoblin/templates/mediagoblin/user_pages/media.html @@ -86,7 +86,7 @@ {% autoescape False %}

{{ media.description_html }}

{% endautoescape %} - {% if comments and request.user.has_privilege('commenter') %} + {% if comments and request.user and request.user.has_privilege('commenter') %} {% if app_config['allow_comments'] %}

{% trans %}Added{% endtrans %}

- {%- trans formatted_time=timesince(media.created) -%} + {%- trans formatted_time=timesince(media.created) -%} {{ formatted_time }} ago {%- endtrans -%}

diff --git a/mediagoblin/tests/test_modelmethods.py b/mediagoblin/tests/test_modelmethods.py index 4af66072..86513c76 100644 --- a/mediagoblin/tests/test_modelmethods.py +++ b/mediagoblin/tests/test_modelmethods.py @@ -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): diff --git a/mediagoblin/user_pages/views.py b/mediagoblin/user_pages/views.py index 0028684d..83aecf31 100644 --- a/mediagoblin/user_pages/views.py +++ b/mediagoblin/user_pages/views.py @@ -199,6 +199,19 @@ def media_post_comment(request, media): return redirect_obj(request, media) + + +def media_preview_comment(request): + """Runs a comment through markdown so it can be previewed.""" + # If this isn't an ajax request, render_404 + if not request.is_xhr: + return render_404(request) + + comment = unicode(request.form['comment_content']) + cleancomment = { "content":cleaned_markdown_conversion(comment)} + + return Response(json.dumps(cleancomment)) + @user_not_banned @get_media_entry_by_id @require_active_login From 8e91df87349b91611a4dfcf3f2640cb540307144 Mon Sep 17 00:00:00 2001 From: tilly-Q Date: Tue, 3 Sep 2013 16:19:07 -0400 Subject: [PATCH 17/25] I did some more code-keeping in this commit. I added a lot of documentation, so that most of my functions do indeed have effective docstrings. I also changed the decorators so that they imply eachother in a logical way. I also modified the one decorator get_media_comment_by_id to be more usable with the variable urls of mediagoblin.user_pages.views:file_a_report. I also noticed a few tests had broken, so I went through them and fixed them up, finding that mostly there were problems in my actual writing of the tests. I also did a few other small tasks such as creating a new User method to check whether or not a User is ban- -ned. =============================================================================== Added in documentation =============================================================================== --\ mediagoblin/db/models.py --\ mediagoblin/decorators.py --\ mediagoblin/moderation/forms.py --\ mediagoblin/moderation/tools.py --\ mediagoblin/moderation/views.py --\ mediagoblin/user_pages/lib.py =============================================================================== Rearranged decorators to be more efficient =============================================================================== --\ mediagoblin/decorators.py --| Made it so that user_not_banned is encapsulated in require_active_login --| Made it so that require_active_login is encapsulated in user_has_privilege --| Changed get_media_comment_by_id into get_optional_media_comment_by_id. It | now returns valid code if the MediaComment id is absent. This makes it pos- | -sible to use this decorator for the function: | mediagoblin.user_pages.views:file_a_report --\ mediagoblin/user_pages/views.py --| Replaced the mediagoblin.user_pages.views:file_a_comment_report with the | decorator mentioned above --\ mediagoblin/user_pages/routing.py ----------------------------------------------------------- | took out unnecessary @user_not_banned decorators | ----------------------------------------------------------- --\ mediagoblin/submit/views.py --\ mediagoblin/user_pages/views.py =============================================================================== Fixed broken tests =============================================================================== --\ mediagoblin/tests/test_auth.py --\ mediagoblin/tests/test_privileges.py --\ mediagoblin/tests/test_submission.py =============================================================================== Fixed broken code =============================================================================== --\ mediagoblin/tools/response.py =============================================================================== Other Tasks =============================================================================== --\ mediagoblin/db/models.py --| Added in User.is_banned() method --\ mediagoblin/decorators.py --| Utitilized User.is_banned() method in the user_not_banned decorator --\ mediagoblin/moderation/views.py --| Made it impossible for an admin to ban themself. --| Got rid of a vestigial print statement --\ mediagoblin/templates/mediagoblin/base.html --| Made it so the top panel does not show up for users that are banned. --\ mediagoblin/templates/mediagoblin/moderation/user.html --| Rearranged the javascript slightly =============================================================================== --- mediagoblin/db/models.py | 69 +++++++++-- mediagoblin/decorators.py | 111 +++++++++++------- mediagoblin/moderation/forms.py | 67 +++++++++++ mediagoblin/moderation/tools.py | 34 ++++++ mediagoblin/moderation/views.py | 7 +- mediagoblin/submit/views.py | 1 - mediagoblin/templates/mediagoblin/base.html | 2 +- .../mediagoblin/moderation/user.html | 4 +- mediagoblin/tests/test_auth.py | 14 +-- mediagoblin/tests/test_privileges.py | 6 +- mediagoblin/tests/test_submission.py | 2 +- mediagoblin/tools/response.py | 4 +- mediagoblin/user_pages/lib.py | 20 +++- mediagoblin/user_pages/routing.py | 2 +- mediagoblin/user_pages/views.py | 29 ++--- 15 files changed, 276 insertions(+), 96 deletions(-) diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index 1e8d0a0c..f25dc32c 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -105,6 +105,17 @@ class User(Base, UserMixin): _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() @@ -114,6 +125,16 @@ class User(Base, UserMixin): 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): """ Model representing a client - Used for API Auth @@ -655,15 +676,21 @@ with_polymorphic( class ReportBase(Base): """ - This is the basic report table which the other reports are based off of. - :keyword reporter_id - :keyword report_content - :keyword reported_user_id - :keyword created - :keyword resolved - :keyword result - :keyword discriminator + 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. """ __tablename__ = 'core__reports' id = Column(Integer, primary_key=True) @@ -698,7 +725,9 @@ class ReportBase(Base): class CommentReport(ReportBase): """ - A class to keep track of reports that have been filed on comments + 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'} @@ -713,7 +742,9 @@ class CommentReport(ReportBase): class MediaReport(ReportBase): """ - A class to keep track of reports that have been filed on media entries + 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'} @@ -729,7 +760,23 @@ class MediaReport(ReportBase): class ArchivedReport(ReportBase): """ - A table to keep track of reports that have been resolved + Reports that have been resolved. The media_entry and comment columns must + be optional so that if the media entry/comment is deleted, the archive can + still exist. + :keyword comment_id Holds the Integer value of the reported + comment's ID. This column is optio- + -nal. + :keyword media_entry_id Holds the Integer value of the reported + media entry's ID. This column is + optional. + :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_archived' __mapper_args__ = {'polymorphic_identity': 'archived_report'} diff --git a/mediagoblin/decorators.py b/mediagoblin/decorators.py index 1a8b124b..d5a10db1 100644 --- a/mediagoblin/decorators.py +++ b/mediagoblin/decorators.py @@ -31,11 +31,29 @@ 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 \ not request.user.has_privilege(u'active'): @@ -55,6 +73,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 URL pattern and pass in as url_user=... @@ -69,22 +115,6 @@ def active_user_from_url(controller): return wrapper -def user_has_privilege(privilege_name): - - def user_has_privilege_decorator(controller): - @wraps(controller) - def wrapper(request, *args, **kwargs): - user_id = request.user.id - if UserBan.query.filter(UserBan.user_id==user_id).count(): - return render_user_banned(request) - elif not request.user.has_privilege(privilege_name): - raise Forbidden() - - return controller(request, *args, **kwargs) - - return wrapper - return user_has_privilege_decorator - def user_may_delete_media(controller): """ @@ -274,20 +304,31 @@ def allow_registration(controller): return wrapper -def get_media_comment_by_id(controller): +def get_optional_media_comment_by_id(controller): """ - Pass in a MediaComment based off of a url component + 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): - comment = MediaComment.query.filter_by( - id=request.matchdict['comment']).first() - # Still no media? Okay, 404. - if not comment: - return render_404(request) + if 'comment' in request.matchdict: + comment = MediaComment.query.filter_by( + id=request.matchdict['comment']).first() - return controller(request, comment=comment, *args, **kwargs) + 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 @@ -308,7 +349,7 @@ def auth_enabled(controller): def require_admin_or_moderator_login(controller): """ - Require an login from an administrator or a moderator. + Require a login from an administrator or a moderator. """ @wraps(controller) def new_controller_func(request, *args, **kwargs): @@ -330,22 +371,6 @@ def require_admin_or_moderator_login(controller): return new_controller_func -def user_not_banned(controller): - """ - 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: - user_banned = UserBan.query.get(request.user.id) - if user_banned: - return render_user_banned(request) - return controller(request, *args, **kwargs) - - return wrapper - - def oauth_required(controller): """ Used to wrap API endpoints where oauth is required """ diff --git a/mediagoblin/moderation/forms.py b/mediagoblin/moderation/forms.py index bcb2fc77..7e225fb6 100644 --- a/mediagoblin/moderation/forms.py +++ b/mediagoblin/moderation/forms.py @@ -35,10 +35,19 @@ class MultiCheckboxField(wtforms.SelectMultipleField): 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', @@ -47,7 +56,54 @@ class BanForm(wtforms.Form): _(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()], @@ -67,7 +123,18 @@ class ReportResolutionForm(wtforms.Form): 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. + + Parameters that start with 'active_' refer to a sort/filter for the active + reports. + Parameters that start with 'closed_' refer to a sort/filter for the closed + reports. + """ active_p = wtforms.IntegerField( _(u'Page'), validators=[wtforms.validators.optional()]) diff --git a/mediagoblin/moderation/tools.py b/mediagoblin/moderation/tools.py index 79a6e3f5..355ffa99 100644 --- a/mediagoblin/moderation/tools.py +++ b/mediagoblin/moderation/tools.py @@ -131,6 +131,23 @@ def take_punitive_actions(request, form, report, user): report_id=report.id) 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() @@ -146,6 +163,23 @@ def take_away_privileges(user,*privileges): 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() diff --git a/mediagoblin/moderation/views.py b/mediagoblin/moderation/views.py index 35c586ea..f09f4c99 100644 --- a/mediagoblin/moderation/views.py +++ b/mediagoblin/moderation/views.py @@ -169,7 +169,8 @@ def moderation_reports_detail(request): @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 + 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(): @@ -193,10 +194,10 @@ 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) - print "accessed page" if request.method == "POST" and form.validate(): already_banned = unban_user(url_user.id) - if not already_banned: + 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) diff --git a/mediagoblin/submit/views.py b/mediagoblin/submit/views.py index 0700f933..8640b8de 100644 --- a/mediagoblin/submit/views.py +++ b/mediagoblin/submit/views.py @@ -125,7 +125,6 @@ def submit_start(request): @require_active_login -@user_not_banned def add_collection(request, media=None): """ View to create a new collection diff --git a/mediagoblin/templates/mediagoblin/base.html b/mediagoblin/templates/mediagoblin/base.html index 7d53585b..278cebb6 100644 --- a/mediagoblin/templates/mediagoblin/base.html +++ b/mediagoblin/templates/mediagoblin/base.html @@ -59,7 +59,7 @@ {% block mediagoblin_header_title %}{% endblock %}
{%- if request.user %} - {% if request.user and request.user.status == 'active' %} + {% if request.user and request.user.status == 'active' and not request.user.is_banned() %} {% set notification_count = get_notification_count(request.user.id) %} {% if notification_count %} diff --git a/mediagoblin/templates/mediagoblin/moderation/user.html b/mediagoblin/templates/mediagoblin/moderation/user.html index a8befa81..1be9dd80 100644 --- a/mediagoblin/templates/mediagoblin/moderation/user.html +++ b/mediagoblin/templates/mediagoblin/moderation/user.html @@ -193,7 +193,9 @@ $(document).ready(function(){ $('#hidden_privilege_name').val($(this).attr('id')); }); init_user_banned_form(); - $('.ban_user_submit').click(function(){submit_user_banned_form()}); + $('#ban_user_submit').click(function(){ + submit_user_banned_form() + }); }); {% endblock %} diff --git a/mediagoblin/tests/test_auth.py b/mediagoblin/tests/test_auth.py index 6cf05444..edbd27ee 100644 --- a/mediagoblin/tests/test_auth.py +++ b/mediagoblin/tests/test_auth.py @@ -99,6 +99,12 @@ def test_register_views(test_app): 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.html']['request'] @@ -330,14 +336,6 @@ def test_authentication_views(test_app): 'next' : '/u/chris/'}) assert urlparse.urlsplit(response.location)[2] == '/u/chris/' -def test_basic_privileges_granted_on_registration(test_app): - user = User.query.filter(User.username==u'angrygirl').first() - - assert User.has_privilege(u'commenter') - assert User.has_privilege(u'uploader') - assert User.has_privilege(u'reporter') - assert not User.has_privilege(u'active') - @pytest.fixture() def authentication_disabled_app(request): return get_app( diff --git a/mediagoblin/tests/test_privileges.py b/mediagoblin/tests/test_privileges.py index ced87b7f..9ae523f8 100644 --- a/mediagoblin/tests/test_privileges.py +++ b/mediagoblin/tests/test_privileges.py @@ -15,7 +15,7 @@ # along with this program. If not, see . import pytest -from datetime import datetime, timedelta +from datetime import date, timedelta from webtest import AppError from mediagoblin.tests.tools import fixture_add_user, fixture_media_entry @@ -88,7 +88,7 @@ class TestPrivilegeFunctionality: user_ban.delete() user_ban = UserBan(user_id=uid, reason=u'Testing whether user is banned', - expiration_date= datetime.now() + timedelta(days=20)) + expiration_date= date.today() + timedelta(days=20)) user_ban.save() response = self.test_app.get('/') @@ -100,7 +100,7 @@ class TestPrivilegeFunctionality: #---------------------------------------------------------------------- user_ban = UserBan.query.get(uid) user_ban.delete() - exp_date = datetime.now() - timedelta(days=20) + exp_date = date.today() - timedelta(days=20) user_ban = UserBan(user_id=uid, reason=u'Testing whether user is banned', expiration_date= exp_date) diff --git a/mediagoblin/tests/test_submission.py b/mediagoblin/tests/test_submission.py index d10957d7..d8cb9827 100644 --- a/mediagoblin/tests/test_submission.py +++ b/mediagoblin/tests/test_submission.py @@ -46,7 +46,7 @@ class TestSubmission: # TODO: Possibly abstract into a decorator like: # @as_authenticated_user('chris') - fixture_add_user(privileges=[u'active',u'uploader']) + fixture_add_user(privileges=[u'active',u'uploader', u'commenter']) self.login() diff --git a/mediagoblin/tools/response.py b/mediagoblin/tools/response.py index 3884de45..cd99a230 100644 --- a/mediagoblin/tools/response.py +++ b/mediagoblin/tools/response.py @@ -22,7 +22,7 @@ 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 datetime +from datetime import date class Response(wz_Response): """Set default response mimetype to HTML, otherwise we get text/plain""" @@ -80,7 +80,7 @@ def render_user_banned(request): """ user_ban = UserBan.query.get(request.user.id) if (user_ban.expiration_date is not None and - datetime.now()>user_ban.expiration_date): + date.today()>user_ban.expiration_date): user_ban.delete() return redirect(request, diff --git a/mediagoblin/user_pages/lib.py b/mediagoblin/user_pages/lib.py index f29c1796..80eb30bd 100644 --- a/mediagoblin/user_pages/lib.py +++ b/mediagoblin/user_pages/lib.py @@ -83,12 +83,22 @@ 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 should be a MediaReportForm or a CommentReportForm - object - :param + :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 either of MediaReport or a CommentReport object that has not been - saved. In case of an improper form_dict, returns 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: diff --git a/mediagoblin/user_pages/routing.py b/mediagoblin/user_pages/routing.py index b535bbf2..f0f4d8b7 100644 --- a/mediagoblin/user_pages/routing.py +++ b/mediagoblin/user_pages/routing.py @@ -50,7 +50,7 @@ add_route('mediagoblin.user_pages.media_home.view_comment', add_route('mediagoblin.user_pages.media_home.report_comment', '/u//m//c//report/', - 'mediagoblin.user_pages.views:file_a_comment_report') + 'mediagoblin.user_pages.views:file_a_report') # User's tags gallery add_route('mediagoblin.user_pages.user_tag_gallery', diff --git a/mediagoblin/user_pages/views.py b/mediagoblin/user_pages/views.py index 83aecf31..931b740c 100644 --- a/mediagoblin/user_pages/views.py +++ b/mediagoblin/user_pages/views.py @@ -34,10 +34,10 @@ 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, user_has_privilege, + 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_media_comment_by_id, user_not_banned) + get_optional_media_comment_by_id) from werkzeug.contrib.atom import AtomFeed from werkzeug.exceptions import MethodNotAllowed @@ -161,7 +161,6 @@ 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): """ @@ -291,7 +290,6 @@ def media_collect(request, media): #TODO: Why does @user_may_delete_media not implicate @require_active_login? -@user_not_banned @get_media_entry_by_id @require_active_login @user_may_delete_media @@ -380,7 +378,6 @@ def collection_list(request, url_user=None): @get_user_collection_item @require_active_login @user_may_alter_collection -@user_not_banned def collection_item_confirm_remove(request, collection_item): form = user_forms.ConfirmCollectionItemRemoveForm(request.form) @@ -420,7 +417,7 @@ def collection_item_confirm_remove(request, collection_item): {'collection_item': collection_item, 'form': form}) -@user_not_banned + @get_user_collection @require_active_login @user_may_alter_collection @@ -604,7 +601,6 @@ def collection_atom_feed(request): return feed.get_response() -@user_not_banned @require_active_login def processing_panel(request): """ @@ -649,21 +645,27 @@ def processing_panel(request): 'failed_entries': failed_entries, 'processed_entries': processed_entries}) -@require_active_login @get_user_media_entry @user_has_privilege(u'reporter') -def file_a_report(request, media, comment=None): +@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) - form.reporter_id.data = request.user.id context = {'media': media, 'comment':comment, 'form':form} else: form = user_forms.MediaReportForm(request.form) - form.reporter_id.data = request.user.id context = {'media': media, 'form':form} + form.reporter_id.data = request.user.id + if request.method == "POST": report_object = build_report_object(form, @@ -683,8 +685,3 @@ def file_a_report(request, media, comment=None): 'mediagoblin/user_pages/report.html', context) -@require_active_login -@get_user_media_entry -@get_media_comment_by_id -def file_a_comment_report(request, media, comment): - return file_a_report(request, comment=comment) From 25625107b6c7805b474ad7da976171991b259e58 Mon Sep 17 00:00:00 2001 From: tilly-Q Date: Sun, 8 Sep 2013 18:26:37 -0400 Subject: [PATCH 18/25] This was a quick update, I mostly worked on the transition from using the old User table columns (is_admin, status, email_verified) and making sure that their functionality is instead completely handled by privileges. I also worked on the meta pages which I hope to finish soon. I set up migrations to ensure the default privileges are given to users that should have them. Lastly, I made it so that banned users can log out. =============================================================================== Made Sure the Vestigial Columns of the User Table were not being Used =============================================================================== --\ mediagoblin/auth/views.py --\ mediagoblin/db/models.py --\ mediagoblin/templates/mediagoblin/base.html --\ mediagoblin/templates/mediagoblin/moderation/user.html --\ mediagoblin/templates/mediagoblin/user_pages/collection_lis$ --\ mediagoblin/templates/mediagoblin/user_pages/user.html --\ mediagoblin/tests/test_auth.py --\ mediagoblin/tests/test_persona.py --\ mediagoblin/user_pages/views.py =============================================================================== Wrote the Migrations to Set up the Default Privileges =============================================================================== --\ mediagoblin/db/migrations.py --\ mediagoblin/gmg_commands/users.py =============================================================================== Work on the Meta Pages =============================================================================== --\ mediagoblin/meta/routing.py --\ mediagoblin/meta/views.py --\ mediagoblin/static/css/base.css --\ mediagoblin/templates/mediagoblin/meta/terms_of_service.html =============================================================================== Small Changes =============================================================================== --\ mediagoblin/templates/mediagoblin/base.html --| Benevolently made it so that banned users can log out =============================================================================== X X X X X X X X X X X X X X X X X X X X =============================================================================== --- mediagoblin/auth/views.py | 16 +++--- mediagoblin/db/migrations.py | 37 +++++++++++- mediagoblin/db/models.py | 4 +- mediagoblin/gmg_commands/users.py | 2 - mediagoblin/meta/routing.py | 6 +- mediagoblin/meta/views.py | 4 +- mediagoblin/static/css/base.css | 8 ++- mediagoblin/templates/mediagoblin/base.html | 14 ++++- .../mediagoblin/meta/terms_of_service.html | 56 ++++++++++++++----- .../mediagoblin/moderation/user.html | 2 +- .../user_pages/collection_list.html | 2 +- .../mediagoblin/user_pages/user.html | 2 +- mediagoblin/tests/test_auth.py | 6 -- mediagoblin/tests/test_persona.py | 2 - mediagoblin/user_pages/views.py | 12 ++-- 15 files changed, 115 insertions(+), 58 deletions(-) diff --git a/mediagoblin/auth/views.py b/mediagoblin/auth/views.py index 7d95b81a..5e2a2af0 100644 --- a/mediagoblin/auth/views.py +++ b/mediagoblin/auth/views.py @@ -150,9 +150,7 @@ 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( @@ -191,7 +189,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, @@ -256,7 +254,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, @@ -312,8 +310,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) @@ -333,13 +331,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' diff --git a/mediagoblin/db/migrations.py b/mediagoblin/db/migrations.py index 6659feb3..1c0a9291 100644 --- a/mediagoblin/db/migrations.py +++ b/mediagoblin/db/migrations.py @@ -29,7 +29,8 @@ from migrate.changeset.constraint import UniqueConstraint from mediagoblin.db.extratypes import JSONEncoded from mediagoblin.db.migration_tools import RegisterMigration, inspect_table from mediagoblin.db.models import (MediaEntry, Collection, User, - MediaComment, Privilege, ReportBase) + MediaComment, Privilege, ReportBase, + FOUNDATIONS) MIGRATIONS = {} @@ -531,6 +532,40 @@ def create_moderation_tables(db): UserBan_v0.__table__.create(db.bind) Privilege_v0.__table__.create(db.bind) PrivilegeUserAssociation_v0.__table__.create(db.bind) + db.commit() + for parameters in FOUNDATIONS[Privilege]: + p = Privilege(**parameters) + p.save() + +@RegisterMigration(16, MIGRATIONS) +def update_user_privilege_columns(db): + metadata = MetaData(bind=db.bind) + default_privileges = Privilege.query.filter( + Privilege.privilege_name !=u'admin').filter( + Privilege.privilege_name !=u'moderator').filter( + Privilege.privilege_name !=u'active').all() + admin_privilege = Privilege.query.filter( + Privilege.privilege_name ==u'admin').first() + active_privilege = Privilege.query.filter( + Privilege.privilege_name ==u'active').first() + for inactive_user in User.query.filter( + User.status!=u'active').filter( + User.is_admin==False).all(): + + inactive_user.all_privileges = default_privileges + inactive_user.save() + for user in User.query.filter( + User.status==u'active').filter( + User.is_admin==False).all(): + + user.all_privileges = default_privileges + [active_privilege] + user.save() + for admin_user in User.query.filter( + User.is_admin==True).all(): + + admin_user.all_privileges = default_privileges + [ + admin_privilege, active_privilege] + admin_user.save() diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index f25dc32c..5b77a85d 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -81,8 +81,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): diff --git a/mediagoblin/gmg_commands/users.py b/mediagoblin/gmg_commands/users.py index d319cef9..4a730d9e 100644 --- a/mediagoblin/gmg_commands/users.py +++ b/mediagoblin/gmg_commands/users.py @@ -53,8 +53,6 @@ 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(), diff --git a/mediagoblin/meta/routing.py b/mediagoblin/meta/routing.py index e61bc065..b74cb52d 100644 --- a/mediagoblin/meta/routing.py +++ b/mediagoblin/meta/routing.py @@ -15,9 +15,9 @@ # along with this program. If not, see . meta_routes = [ - ('mediagoblin.meta.code_of_conduct', - '/coc/', - 'mediagoblin.meta.views:code_of_conduct'), + ('mediagoblin.meta.terms_of_service', + '/tos/', + 'mediagoblin.meta.views:terms_of_service'), ('mediagoblin.meta.reports_panel', '/reports/', 'mediagoblin.meta.views:public_reports_panel'), diff --git a/mediagoblin/meta/views.py b/mediagoblin/meta/views.py index 3df0688c..79940a22 100644 --- a/mediagoblin/meta/views.py +++ b/mediagoblin/meta/views.py @@ -17,9 +17,9 @@ from mediagoblin.tools.response import render_to_response -def code_of_conduct(request): +def terms_of_service(request): return render_to_response(request, - 'mediagoblin/meta/code_of_conduct.html', + 'mediagoblin/meta/terms_of_service.html', {}) def public_reports_panel(request): diff --git a/mediagoblin/static/css/base.css b/mediagoblin/static/css/base.css index 7fcbb93e..d9610675 100644 --- a/mediagoblin/static/css/base.css +++ b/mediagoblin/static/css/base.css @@ -663,13 +663,15 @@ table td.user_without_privilege { margin-bottom: 10px; } #code_of_conduct_list li { - margin-top:5px; + margin:5px 0 15px 25px; } -ol.nested_sublist{ +.nested_sublist { margin: 5px 0 10px 25px; font-size:80%; } - +.nested_sublist li { + margin-bottom: 10px; +} /* ASCII art and code */ diff --git a/mediagoblin/templates/mediagoblin/base.html b/mediagoblin/templates/mediagoblin/base.html index 278cebb6..40cec5f7 100644 --- a/mediagoblin/templates/mediagoblin/base.html +++ b/mediagoblin/templates/mediagoblin/base.html @@ -59,7 +59,7 @@ {% block mediagoblin_header_title %}{% endblock %}
{%- if request.user %} - {% if request.user and request.user.status == 'active' and not request.user.is_banned() %} + {% 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 %} @@ -68,7 +68,7 @@ {% endif %} - {% 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 #} {% trans %}log out{% endtrans %} + {% elif request.user and request.user.is_banned() %} + {% trans %}log out{% endtrans %} {% endif %} {%- elif auth %}
- {% if request.user and request.user.status == 'active' %} + {% if request.user and request.user.has_privilege('active') %}

diff --git a/mediagoblin/templates/mediagoblin/meta/terms_of_service.html b/mediagoblin/templates/mediagoblin/meta/terms_of_service.html index 0951c044..b998d1c3 100644 --- a/mediagoblin/templates/mediagoblin/meta/terms_of_service.html +++ b/mediagoblin/templates/mediagoblin/meta/terms_of_service.html @@ -1,3 +1,27 @@ +{# +# 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 . +#} +{% extends "mediagoblin/base.html" %} + +{% block title %} + Terms of Service +{% endblock %} + +{% block mediagoblin_content -%}

The gist

Terms of Service

@@ -14,8 +38,8 @@ that may be published from time to time on this Site by Operator (collectively, 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. -
    -
  1. Your {{ app_config['html_title'] }} Account and Site. If you create a +
      +
    1. Your {{ app_config['html_title'] }} Account and Site. 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 @@ -30,7 +54,7 @@ Please read this Agreement carefully before accessing or using the Website. By a including any damages of any kind incurred as a result of such acts or omissions.
    2. -
    3. Responsibility of Contributors. If you operate a notice stream, comment +
    4. Responsibility of Contributors. 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 @@ -38,7 +62,7 @@ Please read this Agreement carefully before accessing or using the Website. By a 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: -
- {% elif report.is_media_entry_report() or - (report.is_archived_report() and report.media_entry) %} + {% elif report.media_entry %} {% set media_entry = report.media_entry %}
@@ -137,7 +135,7 @@ init_report_resolution_form(); }); - {% elif not (report.reported_user.has_privilege('admin')) %} + {% elif report.is_archived_report() %}

Under a GNU LGPL v.3 or Creative Commons BY-SA 3.0 license.
                   Distributed by the GNOME project http://www.gnome.org @@ -145,9 +143,9 @@

{% trans %}RESOLVED{% endtrans %} {{ report.resolved.strftime("%I:%M%p %Y-%m-%d") }} - {% autoescape False %} +
       

{{ report.result }}

- {% endautoescape %} +
{% else %}

You cannot take action against an administrator

From 6483b37060062ef7c7d40d9ae712c99a7e73775a Mon Sep 17 00:00:00 2001 From: tilly-Q Date: Mon, 23 Sep 2013 13:20:18 -0400 Subject: [PATCH 22/25] At this point, I am very close to done with this code! I made one big change at paroneayea's request, which was to make to possible to turn off user's ability to file reports through a mediagoblin.ini setting. Aside from this, I had to make it possible for the Moderation User Panel to display more than 10 users. And aside from that, I just had to fix some errors which cropped up with my most recent additions. I also fixed some tests that were broken because I had changed the checks for whether or not a user is active. Nearing the end! =============================================================================== Made it possible to turn off reports through a mediagoblin.ini setting =============================================================================== --\ mediagoblin.ini --\ mediagoblin/config_spec.ini --\ mediagoblin/decorators.py --\ mediagoblin/moderation/views.py --\ mediagoblin/templates/mediagoblin/user_pages/media.html --\ mediagoblin/user_pages/views.py =============================================================================== Made User Panel capable of showing more than 1 page of users =============================================================================== --\ mediagoblin/moderation/forms.py --\ mediagoblin/moderation/views.py --\ mediagoblin/templates/mediagoblin/moderation/user_panel.html =============================================================================== Fixed Broken Tests =============================================================================== --\ mediagoblin/tests/test_notifications.py --\ mediagoblin/tests/test_openid.py --\ mediagoblin/tests/test_persona.py --\ mediagoblin/tests/test_reporting.py =============================================================================== Fixed errors in code =============================================================================== --\ mediagoblin/db/migrations.py --| Set nullable to True for MediaReports' and CommentReports' content foreign |keys --\ mediagoblin/db/models.py --| Got rid of cascading rules for MediaReports' and CommentReports' content |foreign keys. This makes it possible for the Reports to continue to exist |after the content is deleted. --\ mediagoblin/moderation/tools.py --| Fixed formatting of Report Resolution Methods --| Took out pieces of code used in debugging --\ mediagoblin/templates/mediagoblin/base.html --\ mediagoblin/templates/mediagoblin/moderation/report.html --| Made reports details page able to tell what is a deleted archived report. --\ mediagoblin/templates/mediagoblin/moderation/report_panel.html --\ mediagoblin/templates/mediagoblin/utils/report.html --- mediagoblin.ini | 3 + mediagoblin/config_spec.ini | 3 + mediagoblin/db/migrations.py | 4 +- mediagoblin/db/models.py | 8 +- mediagoblin/decorators.py | 15 ++ mediagoblin/moderation/forms.py | 7 + mediagoblin/moderation/tools.py | 139 ++++++++---------- mediagoblin/moderation/views.py | 23 ++- mediagoblin/templates/mediagoblin/base.html | 13 +- .../mediagoblin/moderation/report.html | 4 +- .../mediagoblin/moderation/report_panel.html | 4 +- .../mediagoblin/moderation/user_panel.html | 36 +++++ .../mediagoblin/user_pages/media.html | 16 +- .../templates/mediagoblin/utils/report.html | 7 +- mediagoblin/tests/test_notifications.py | 3 +- mediagoblin/tests/test_openid.py | 2 +- mediagoblin/tests/test_persona.py | 4 + mediagoblin/tests/test_reporting.py | 5 +- mediagoblin/user_pages/views.py | 4 +- 19 files changed, 183 insertions(+), 117 deletions(-) diff --git a/mediagoblin.ini b/mediagoblin.ini index 30dacadf..874ebcbd 100644 --- a/mediagoblin.ini +++ b/mediagoblin.ini @@ -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/ diff --git a/mediagoblin/config_spec.ini b/mediagoblin/config_spec.ini index 43e1898c..0e224503 100644 --- a/mediagoblin/config_spec.ini +++ b/mediagoblin/config_spec.ini @@ -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() diff --git a/mediagoblin/db/migrations.py b/mediagoblin/db/migrations.py index 2e05bf2a..ff74aa30 100644 --- a/mediagoblin/db/migrations.py +++ b/mediagoblin/db/migrations.py @@ -494,7 +494,7 @@ class CommentReport_v0(ReportBase_v0): id = Column('id',Integer, ForeignKey('core__reports.id'), primary_key=True) - comment_id = Column(Integer, ForeignKey(MediaComment.id), nullable=False) + comment_id = Column(Integer, ForeignKey(MediaComment.id), nullable=True) @@ -503,7 +503,7 @@ class MediaReport_v0(ReportBase_v0): __mapper_args__ = {'polymorphic_identity': 'media_report'} id = Column('id',Integer, ForeignKey('core__reports.id'), primary_key=True) - media_entry_id = Column(Integer, ForeignKey(MediaEntry.id), nullable=False) + media_entry_id = Column(Integer, ForeignKey(MediaEntry.id), nullable=True) class UserBan_v0(declarative_base()): __tablename__ = 'core__user_bans' diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index cec311f2..466f31b8 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -764,8 +764,7 @@ class CommentReport(ReportBase): comment_id = Column(Integer, ForeignKey(MediaComment.id), nullable=True) comment = relationship( MediaComment, backref=backref("reports_filed_on", - lazy="dynamic", - cascade="all, delete-orphan")) + lazy="dynamic")) class MediaReport(ReportBase): @@ -782,9 +781,8 @@ class MediaReport(ReportBase): media_entry_id = Column(Integer, ForeignKey(MediaEntry.id), nullable=True) media_entry = relationship( MediaEntry, - backref=backref("reports_filed_onmod/reports/1/", - lazy="dynamic", - cascade="all, delete-orphan")) + backref=backref("reports_filed_on", + lazy="dynamic")) class UserBan(Base): """ diff --git a/mediagoblin/decorators.py b/mediagoblin/decorators.py index d5a10db1..d0b5ad33 100644 --- a/mediagoblin/decorators.py +++ b/mediagoblin/decorators.py @@ -304,6 +304,21 @@ 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- diff --git a/mediagoblin/moderation/forms.py b/mediagoblin/moderation/forms.py index 78e9fcdc..5582abdd 100644 --- a/mediagoblin/moderation/forms.py +++ b/mediagoblin/moderation/forms.py @@ -139,3 +139,10 @@ class ReportPanelSortingForm(wtforms.Form): 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()]) diff --git a/mediagoblin/moderation/tools.py b/mediagoblin/moderation/tools.py index 109f3d8f..224a0c73 100644 --- a/mediagoblin/moderation/tools.py +++ b/mediagoblin/moderation/tools.py @@ -25,90 +25,79 @@ import sys, traceback def take_punitive_actions(request, form, report, user): message_body ='' - try: - # 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}\'{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) + # 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} banned user {user} until {expiration_date}.".format( + u"\n{mod} took away {user}\'{privilege} privileges.".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" - ) - ) + privilege=privilege_name) - # 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 + # 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} sent a warning email to the {user}.".format( - mod=request.user.username, - user=user.username) + 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 + Session.delete(deleted_media) + 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) - 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 - Session.delete(deleted_media) - 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) - 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) - return redirect( - request, - 'mediagoblin.moderation.users_detail', - user=user.username) - except: -#TODO make a more effective and specific try except statement. To account for -# incorrect value addition my moderators - print sys.exc_info()[0] - print sys.exc_info()[1] - traceback.print_tb(sys.exc_info()[2]) - Session.rollback() - return redirect( - request, - 'mediagoblin.moderation.reports_detail', - report_id=report.id) def take_away_privileges(user,*privileges): """ diff --git a/mediagoblin/moderation/views.py b/mediagoblin/moderation/views.py index 45edff1d..4721834a 100644 --- a/mediagoblin/moderation/views.py +++ b/mediagoblin/moderation/views.py @@ -19,8 +19,9 @@ from werkzeug.exceptions import Forbidden from mediagoblin.db.models import (MediaEntry, User, MediaComment, \ CommentReport, ReportBase, Privilege, \ UserBan) -from mediagoblin.decorators import (require_admin_or_moderator_login, \ - active_user_from_url, user_has_privilege) +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, \ @@ -58,12 +59,24 @@ def moderation_users_panel(request): ''' Show the global panel for monitoring users in this instance ''' - user_list = User.query + 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}) + {'user_list': user_list, + 'current_page':current_page, + 'last_page':last_page}) @require_admin_or_moderator_login def moderation_users_detail(request): @@ -89,6 +102,7 @@ def moderation_users_detail(request): '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 @@ -135,6 +149,7 @@ def moderation_reports_panel(request): '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. diff --git a/mediagoblin/templates/mediagoblin/base.html b/mediagoblin/templates/mediagoblin/base.html index 945d5ee3..94ca7aa5 100644 --- a/mediagoblin/templates/mediagoblin/base.html +++ b/mediagoblin/templates/mediagoblin/base.html @@ -92,11 +92,10 @@ "javascript:;" {% endif %} >{% trans %}log out{% endtrans %} - - {%- trans %}Create new collection{% endtrans -%} -

- Terms of Service + + {%- trans %}Terms of Service{%- endtrans %} +

{% endif %} {%- elif auth %} @@ -141,6 +140,9 @@ {%- trans %}Add media{% endtrans -%} + + {%- trans %}Create new collection{% endtrans -%} + {% if request.user.has_privilege('admin','moderator') %}

Moderation powers: @@ -157,9 +159,6 @@

{% endif %} - - {%- trans %}Create new collection{% endtrans -%} -

Terms of Service

diff --git a/mediagoblin/templates/mediagoblin/moderation/report.html b/mediagoblin/templates/mediagoblin/moderation/report.html index 062ec24a..cb717cde 100644 --- a/mediagoblin/templates/mediagoblin/moderation/report.html +++ b/mediagoblin/templates/mediagoblin/moderation/report.html @@ -30,7 +30,7 @@ title="Return to Reports Panel"> {% trans %}Return to Reports Panel{% endtrans %}

{% trans %}Report{% endtrans %} #{{ report.id }}

- {% if report.comment %} + {% if report.is_comment_report() and report.comment %} {% trans %}Reported comment{% endtrans %}: {% set comment = report.comment %} @@ -62,7 +62,7 @@ {% endautoescape %}
- {% elif report.media_entry %} + {% elif report.is_media_entry_report() and report.media_entry %} {% set media_entry = report.media_entry %}
diff --git a/mediagoblin/templates/mediagoblin/moderation/report_panel.html b/mediagoblin/templates/mediagoblin/moderation/report_panel.html index 988dd292..4eb16b2b 100644 --- a/mediagoblin/templates/mediagoblin/moderation/report_panel.html +++ b/mediagoblin/templates/mediagoblin/moderation/report_panel.html @@ -33,6 +33,7 @@

{% trans %}Active Reports Filed{% endtrans %}

+{% 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 %} @@ -70,7 +71,6 @@ curr_page !=p %} {% endif %}
{% endif %} -{% if report_list.count() %}
@@ -121,6 +121,7 @@ curr_page !=p %}

{% trans %}No open reports found.{% endtrans %}

{% endif %}

{% trans %}Closed Reports{% endtrans %}

+{% 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 %} @@ -161,7 +162,6 @@ curr_page !=p %} {% endif %} {% endif %} -{% if closed_report_list.count() %}
diff --git a/mediagoblin/templates/mediagoblin/moderation/user_panel.html b/mediagoblin/templates/mediagoblin/moderation/user_panel.html index 54ef7c11..4949960e 100644 --- a/mediagoblin/templates/mediagoblin/moderation/user_panel.html +++ b/mediagoblin/templates/mediagoblin/moderation/user_panel.html @@ -34,6 +34,42 @@

{% trans %}Active Users{% endtrans %}

{% 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 %} +
+ {% set first_vis = current_page-3 %} + {% set last_vis = current_page+3 %} + {% if 1 == current_page %}1{% else %} + + 1{% 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 %} + + {{ p }} + {% elif p > 1 and p < last_page %} + {{ p }} + {% endif %} + {% endfor %} + {% if last_vis < last_page %}...{% endif %} + {% if last_page != current_page %} + + {{ last_page }} + {% else %}{{ last_page }} + {% endif %} +
+ {% endif %}
diff --git a/mediagoblin/templates/mediagoblin/user_pages/media.html b/mediagoblin/templates/mediagoblin/user_pages/media.html index 62c2a48c..81e5013e 100644 --- a/mediagoblin/templates/mediagoblin/user_pages/media.html +++ b/mediagoblin/templates/mediagoblin/user_pages/media.html @@ -147,15 +147,13 @@ {%- endautoescape %}
- - {% trans %} Report {% endtrans %} + comment=comment.id) }}"> + {% trans %}Report{% endtrans %} + {% endif %}
{% endfor %} @@ -181,7 +179,9 @@ {% include "mediagoblin/utils/collections.html" %} - {% include "mediagoblin/utils/report.html" %} + {% if app_config.allow_reporting %} + {% include "mediagoblin/utils/report.html" %} + {% endif %} {% include "mediagoblin/utils/license.html" %} diff --git a/mediagoblin/templates/mediagoblin/utils/report.html b/mediagoblin/templates/mediagoblin/utils/report.html index 2fa4f959..3829de97 100644 --- a/mediagoblin/templates/mediagoblin/utils/report.html +++ b/mediagoblin/templates/mediagoblin/utils/report.html @@ -18,14 +18,9 @@ {% block report_content -%}

- {% trans %}Report media{% endtrans %} diff --git a/mediagoblin/tests/test_notifications.py b/mediagoblin/tests/test_notifications.py index 0908cb34..3bf36f5f 100644 --- a/mediagoblin/tests/test_notifications.py +++ b/mediagoblin/tests/test_notifications.py @@ -157,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') diff --git a/mediagoblin/tests/test_openid.py b/mediagoblin/tests/test_openid.py index 23a2290e..3aea7982 100644 --- a/mediagoblin/tests/test_openid.py +++ b/mediagoblin/tests/test_openid.py @@ -237,7 +237,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 diff --git a/mediagoblin/tests/test_persona.py b/mediagoblin/tests/test_persona.py index 3e9bf22f..a696858d 100644 --- a/mediagoblin/tests/test_persona.py +++ b/mediagoblin/tests/test_persona.py @@ -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,6 +113,9 @@ class TestPersonaPlugin(object): # Get user and detach from session test_user = mg_globals.database.User.query.filter_by( username=u'chris').first() + 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() diff --git a/mediagoblin/tests/test_reporting.py b/mediagoblin/tests/test_reporting.py index b414a580..a154a061 100644 --- a/mediagoblin/tests/test_reporting.py +++ b/mediagoblin/tests/test_reporting.py @@ -160,7 +160,8 @@ class TestReportFiling: 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.result == u'''This is a test of archiving reports. +natalie banned user allie indefinitely. +natalie deleted the comment.''' assert archived_report.discriminator == 'comment_report' diff --git a/mediagoblin/user_pages/views.py b/mediagoblin/user_pages/views.py index 4f555884..e895e634 100644 --- a/mediagoblin/user_pages/views.py +++ b/mediagoblin/user_pages/views.py @@ -37,7 +37,7 @@ from mediagoblin.decorators import (uses_pagination, get_user_media_entry, 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_optional_media_comment_by_id) + get_optional_media_comment_by_id, allow_reporting) from werkzeug.contrib.atom import AtomFeed from werkzeug.exceptions import MethodNotAllowed @@ -643,6 +643,7 @@ def processing_panel(request): '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 @@ -682,4 +683,3 @@ def file_a_report(request, media, comment): request, 'mediagoblin/user_pages/report.html', context) - From a523ffce3c84832f95b1376c5a3719fc9ba8cb77 Mon Sep 17 00:00:00 2001 From: tilly-Q Date: Mon, 23 Sep 2013 14:10:11 -0400 Subject: [PATCH 23/25] This commit was solely to remove unused imports in the code that I have written --- mediagoblin/db/migrations.py | 3 +-- mediagoblin/db/util.py | 3 +-- mediagoblin/decorators.py | 3 +-- mediagoblin/moderation/tools.py | 3 +-- mediagoblin/moderation/views.py | 8 ++------ mediagoblin/tests/test_api.py | 1 - mediagoblin/tests/test_moderation.py | 3 --- mediagoblin/tests/test_privileges.py | 7 +++---- mediagoblin/tests/test_submission.py | 2 +- mediagoblin/user_pages/views.py | 3 +-- 10 files changed, 11 insertions(+), 25 deletions(-) diff --git a/mediagoblin/db/migrations.py b/mediagoblin/db/migrations.py index ff74aa30..54b4adb4 100644 --- a/mediagoblin/db/migrations.py +++ b/mediagoblin/db/migrations.py @@ -29,8 +29,7 @@ from migrate.changeset.constraint import UniqueConstraint from mediagoblin.db.extratypes import JSONEncoded from mediagoblin.db.migration_tools import RegisterMigration, inspect_table from mediagoblin.db.models import (MediaEntry, Collection, User, - MediaComment, Privilege, ReportBase, - FOUNDATIONS) + MediaComment, Privilege, FOUNDATIONS) MIGRATIONS = {} diff --git a/mediagoblin/db/util.py b/mediagoblin/db/util.py index 2488dcac..7a0a3a73 100644 --- a/mediagoblin/db/util.py +++ b/mediagoblin/db/util.py @@ -15,8 +15,7 @@ # along with this program. If not, see . from mediagoblin.db.base import Session -from mediagoblin.db.models import (MediaEntry, Tag, MediaTag, Collection, \ - User, Privilege, FOUNDATIONS) +from mediagoblin.db.models import MediaEntry, Tag, MediaTag, Collection ########################## diff --git a/mediagoblin/decorators.py b/mediagoblin/decorators.py index d0b5ad33..872dc1f4 100644 --- a/mediagoblin/decorators.py +++ b/mediagoblin/decorators.py @@ -22,8 +22,7 @@ from oauthlib.oauth1 import ResourceEndpoint from mediagoblin import mg_globals as mgg from mediagoblin import messages -from mediagoblin.db.models import (MediaEntry, User, MediaComment, - UserBan, Privilege) +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 _ diff --git a/mediagoblin/moderation/tools.py b/mediagoblin/moderation/tools.py index 224a0c73..f519e5f3 100644 --- a/mediagoblin/moderation/tools.py +++ b/mediagoblin/moderation/tools.py @@ -21,7 +21,6 @@ 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 _ -import sys, traceback def take_punitive_actions(request, form, report, user): message_body ='' @@ -32,7 +31,7 @@ def take_punitive_actions(request, form, report, user): 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}\'{privilege} privileges.".format( + u"\n{mod} took away {user}\'s {privilege} privileges.".format( mod=request.user.username, user=user.username, privilege=privilege_name) diff --git a/mediagoblin/moderation/views.py b/mediagoblin/moderation/views.py index 4721834a..f4de11ad 100644 --- a/mediagoblin/moderation/views.py +++ b/mediagoblin/moderation/views.py @@ -14,12 +14,10 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -from werkzeug.exceptions import Forbidden -from mediagoblin.db.models import (MediaEntry, User, MediaComment, \ - CommentReport, ReportBase, Privilege, \ +from mediagoblin.db.models import (MediaEntry, User,ReportBase, Privilege, UserBan) -from mediagoblin.decorators import (require_admin_or_moderator_login, +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 @@ -27,8 +25,6 @@ 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 werkzeug.datastructures import ImmutableMultiDict -from datetime import datetime from math import ceil @require_admin_or_moderator_login diff --git a/mediagoblin/tests/test_api.py b/mediagoblin/tests/test_api.py index eb9c0fd4..4e0cbd8f 100644 --- a/mediagoblin/tests/test_api.py +++ b/mediagoblin/tests/test_api.py @@ -25,7 +25,6 @@ from mediagoblin.tools import template, pluginapi from mediagoblin.tests.tools import fixture_add_user from .resources import GOOD_JPG, GOOD_PNG, EVIL_FILE, EVIL_JPG, EVIL_PNG, \ BIG_BLUE -from mediagoblin.db.models import Privilege _log = logging.getLogger(__name__) diff --git a/mediagoblin/tests/test_moderation.py b/mediagoblin/tests/test_moderation.py index 8de76e44..e7a0ebef 100644 --- a/mediagoblin/tests/test_moderation.py +++ b/mediagoblin/tests/test_moderation.py @@ -19,10 +19,7 @@ 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.moderation.tools import take_away_privileges, give_privileges from mediagoblin.tools import template, mail -from datetime import date, timedelta - from webtest import AppError class TestModerationViews: diff --git a/mediagoblin/tests/test_privileges.py b/mediagoblin/tests/test_privileges.py index 9ae523f8..05829b34 100644 --- a/mediagoblin/tests/test_privileges.py +++ b/mediagoblin/tests/test_privileges.py @@ -20,8 +20,7 @@ from webtest import AppError from mediagoblin.tests.tools import fixture_add_user, fixture_media_entry -from mediagoblin.db.models import User, Privilege, UserBan -from mediagoblin.db.base import Session +from mediagoblin.db.models import User, UserBan from mediagoblin.tools import template from .resources import GOOD_JPG @@ -34,7 +33,7 @@ class TestPrivilegeFunctionality: fixture_add_user(u'alex', privileges=[u'admin',u'active']) - fixture_add_user(u'raven', + fixture_add_user(u'meow', privileges=[u'moderator',u'active',u'reporter']) fixture_add_user(u'natalie', privileges=[u'active']) @@ -65,7 +64,7 @@ class TestPrivilegeFunctionality: 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'raven').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): diff --git a/mediagoblin/tests/test_submission.py b/mediagoblin/tests/test_submission.py index d8cb9827..14766c50 100644 --- a/mediagoblin/tests/test_submission.py +++ b/mediagoblin/tests/test_submission.py @@ -24,7 +24,7 @@ import pytest from mediagoblin.tests.tools import fixture_add_user from mediagoblin import mg_globals -from mediagoblin.db.models import MediaEntry, User, Privilege +from mediagoblin.db.models import MediaEntry, User from mediagoblin.tools import template from mediagoblin.media_types.image import ImageMediaManager from mediagoblin.media_types.pdf.processing import check_prerequisites as pdf_check_prerequisites diff --git a/mediagoblin/user_pages/views.py b/mediagoblin/user_pages/views.py index e895e634..020fa6a8 100644 --- a/mediagoblin/user_pages/views.py +++ b/mediagoblin/user_pages/views.py @@ -20,8 +20,7 @@ import json from mediagoblin import messages, mg_globals from mediagoblin.db.models import (MediaEntry, MediaTag, Collection, - CollectionItem, User, MediaComment, - CommentReport, MediaReport) + CollectionItem, User) from mediagoblin.tools.response import render_to_response, render_404, \ redirect, redirect_obj from mediagoblin.tools.text import cleaned_markdown_conversion From 9519c0a91f6c67883d62b656c971cc10e47ea967 Mon Sep 17 00:00:00 2001 From: tilly-Q Date: Thu, 3 Oct 2013 16:13:12 -0400 Subject: [PATCH 24/25] In this commit, I mostly did work on the migrations. Firstly, I droppped the vestigial columns from the User table (ie. status, email_verified, is_admin). Otherwise, I did a lot of work converting my existing migrations from high- level ORM commands to low-level SQL commands to ensure that the migrating will work regardless of what stage their instance is working in. I also re-integrated my two registered migrations into one. Because the migration became very long, I also added a lot of clarifying documentation. --- mediagoblin/db/migrations.py | 171 ++++++++++++++++++++++++++--------- mediagoblin/db/models.py | 6 -- 2 files changed, 129 insertions(+), 48 deletions(-) diff --git a/mediagoblin/db/migrations.py b/mediagoblin/db/migrations.py index 54b4adb4..6e644c3a 100644 --- a/mediagoblin/db/migrations.py +++ b/mediagoblin/db/migrations.py @@ -29,7 +29,7 @@ from migrate.changeset.constraint import UniqueConstraint from mediagoblin.db.extratypes import JSONEncoded from mediagoblin.db.migration_tools import RegisterMigration, inspect_table from mediagoblin.db.models import (MediaEntry, Collection, User, - MediaComment, Privilege, FOUNDATIONS) + MediaComment, Privilege) MIGRATIONS = {} @@ -518,7 +518,7 @@ class Privilege_v0(declarative_base()): class PrivilegeUserAssociation_v0(declarative_base()): __tablename__ = 'core__privileges_users' - group_id = Column( + privilege_id = Column( 'core__privilege_id', Integer, ForeignKey(User.id), @@ -529,8 +529,32 @@ class PrivilegeUserAssociation_v0(declarative_base()): ForeignKey(Privilege.id), primary_key=True) -@RegisterMigration(16, MIGRATIONS) +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) @@ -540,46 +564,109 @@ def create_moderation_tables(db): db.commit() - for parameters in FOUNDATIONS[Privilege]: - p = Privilege(**parameters) - p.save() - - -@RegisterMigration(17, MIGRATIONS) -def update_user_privilege_columns(db): - # first, create the privileges which would be created by foundations - default_privileges = Privilege.query.filter( - Privilege.privilege_name !=u'admin').filter( - Privilege.privilege_name !=u'moderator').filter( - Privilege.privilege_name !=u'active').all() - admin_privilege = Privilege.query.filter( - Privilege.privilege_name ==u'admin').first() - active_privilege = Privilege.query.filter( - Privilege.privilege_name ==u'active').first() - # then, assign them to the appropriate users - for inactive_user in User.query.filter( - User.status!=u'active').filter( - User.is_admin==False).all(): - - inactive_user.all_privileges = default_privileges - inactive_user.save() - for user in User.query.filter( - User.status==u'active').filter( - User.is_admin==False).all(): - - user.all_privileges = default_privileges + [active_privilege] - user.save() - for admin_user in User.query.filter( - User.is_admin==True).all(): - - admin_user.all_privileges = default_privileges + [ - admin_privilege, active_privilege] - admin_user.save() - - # and then drop the now vestigial status column + # 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') - status = user_table.columns['status'] - status.drop() + 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() diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index 466f31b8..5173be9e 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -63,18 +63,12 @@ class User(Base, UserMixin): # point. email = Column(Unicode, nullable=False) pw_hash = Column(Unicode) -#--column email_verified is VESTIGIAL with privileges and should not be used--- -#--should be dropped ASAP though a bug in sqlite3 prevents this atm------------ - email_verified = Column(Boolean, default=False) created = Column(DateTime, nullable=False, default=datetime.datetime.now) # 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) -#--column admin is VESTIGIAL with privileges and should not be used------------ -#--should be dropped ASAP though a bug in sqlite3 prevents this atm------------ - is_admin = Column(Boolean, default=False, nullable=False) url = Column(Unicode) bio = Column(UnicodeText) # ?? From 0a24db84c5769149537b0d9bd8e3a88fe8e9522a Mon Sep 17 00:00:00 2001 From: tilly-Q Date: Thu, 3 Oct 2013 17:07:11 -0400 Subject: [PATCH 25/25] This was a very important update where I fixed a few small but fatal bugs in my code. I had removed the import of Privilege in mediagoblin.db.migrations, and this was still necessary and was causing errors. I also made it so that media deletion by moderators, through reports, actually fully deleted the media, rather than just deleting the database representation of the media. Lastly, I fixed a bug in migration that caused a fatal error on login because the UserBan user_id column was improperly named in it's creation by migration. --- mediagoblin/db/migrations.py | 6 +++--- mediagoblin/moderation/tools.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/mediagoblin/db/migrations.py b/mediagoblin/db/migrations.py index 6e644c3a..7011d842 100644 --- a/mediagoblin/db/migrations.py +++ b/mediagoblin/db/migrations.py @@ -28,8 +28,8 @@ from migrate.changeset.constraint import UniqueConstraint from mediagoblin.db.extratypes import JSONEncoded from mediagoblin.db.migration_tools import RegisterMigration, inspect_table -from mediagoblin.db.models import (MediaEntry, Collection, User, - MediaComment, Privilege) +from mediagoblin.db.models import (MediaEntry, Collection, MediaComment, User, + Privilege) MIGRATIONS = {} @@ -506,7 +506,7 @@ class MediaReport_v0(ReportBase_v0): class UserBan_v0(declarative_base()): __tablename__ = 'core__user_bans' - user_id = Column('id',Integer, ForeignKey(User.id), nullable=False, + user_id = Column(Integer, ForeignKey(User.id), nullable=False, primary_key=True) expiration_date = Column(Date) reason = Column(UnicodeText, nullable=False) diff --git a/mediagoblin/moderation/tools.py b/mediagoblin/moderation/tools.py index f519e5f3..e0337536 100644 --- a/mediagoblin/moderation/tools.py +++ b/mediagoblin/moderation/tools.py @@ -73,7 +73,7 @@ def take_punitive_actions(request, form, report, user): elif u'delete' in form.action_to_resolve.data and \ report.is_media_entry_report(): deleted_media = report.media_entry - Session.delete(deleted_media) + deleted_media.delete() form.resolution_content.data += \ u"\n{mod} deleted the media entry.".format( mod=request.user.username)

{% trans %}ID{% endtrans %}