From: Christopher Allan Webber Date: Mon, 7 Oct 2013 20:48:33 +0000 (-0500) Subject: Merge remote-tracking branch 'refs/remotes/tilly-q/OPW-Moderation-Update' X-Git-Url: https://vcs.fsf.org/?a=commitdiff_plain;h=56c4ad89ebef32bd5a40c00d987811ce4501ce22;p=mediagoblin.git Merge remote-tracking branch 'refs/remotes/tilly-q/OPW-Moderation-Update' Conflicts: mediagoblin/templates/mediagoblin/user_pages/user.html mediagoblin/tests/test_auth.py mediagoblin/tests/test_submission.py --- 56c4ad89ebef32bd5a40c00d987811ce4501ce22 diff --cc mediagoblin/db/migrations.py index 423508f6,7011d842..5c2a23aa --- a/mediagoblin/db/migrations.py +++ b/mediagoblin/db/migrations.py @@@ -26,9 -26,10 +26,10 @@@ from sqlalchemy.sql import and from migrate.changeset.constraint import UniqueConstraint -from mediagoblin.db.extratypes import JSONEncoded +from mediagoblin.db.extratypes import JSONEncoded, MutationDict from mediagoblin.db.migration_tools import RegisterMigration, inspect_table - from mediagoblin.db.models import MediaEntry, Collection, User, MediaComment + from mediagoblin.db.models import (MediaEntry, Collection, MediaComment, User, + Privilege) MIGRATIONS = {} @@@ -469,40 -470,203 +470,235 @@@ def wants_notifications(db) """Add a wants_notifications field to User model""" metadata = MetaData(bind=db.bind) user_table = inspect_table(metadata, "core__users") - col = Column('wants_notifications', Boolean, default=True) col.create(user_table) + db.commit() + + class ReportBase_v0(declarative_base()): + __tablename__ = 'core__reports' + id = Column(Integer, primary_key=True) + reporter_id = Column(Integer, ForeignKey(User.id), nullable=False) + report_content = Column(UnicodeText) + reported_user_id = Column(Integer, ForeignKey(User.id), nullable=False) + created = Column(DateTime, nullable=False, default=datetime.datetime.now) + discriminator = Column('type', Unicode(50)) + resolver_id = Column(Integer, ForeignKey(User.id)) + resolved = Column(DateTime) + result = Column(UnicodeText) + __mapper_args__ = {'polymorphic_on': discriminator} + + class CommentReport_v0(ReportBase_v0): + __tablename__ = 'core__reports_on_comments' + __mapper_args__ = {'polymorphic_identity': 'comment_report'} + + id = Column('id',Integer, ForeignKey('core__reports.id'), + primary_key=True) + comment_id = Column(Integer, ForeignKey(MediaComment.id), nullable=True) + + + + class MediaReport_v0(ReportBase_v0): + __tablename__ = 'core__reports_on_media' + __mapper_args__ = {'polymorphic_identity': 'media_report'} + + id = Column('id',Integer, ForeignKey('core__reports.id'), primary_key=True) + media_entry_id = Column(Integer, ForeignKey(MediaEntry.id), nullable=True) + + class UserBan_v0(declarative_base()): + __tablename__ = 'core__user_bans' + user_id = Column(Integer, ForeignKey(User.id), nullable=False, + primary_key=True) + expiration_date = Column(Date) + reason = Column(UnicodeText, nullable=False) + + class Privilege_v0(declarative_base()): + __tablename__ = 'core__privileges' + id = Column(Integer, nullable=False, primary_key=True, unique=True) + privilege_name = Column(Unicode, nullable=False, unique=True) + + class PrivilegeUserAssociation_v0(declarative_base()): + __tablename__ = 'core__privileges_users' + privilege_id = Column( + 'core__privilege_id', + Integer, + ForeignKey(User.id), + primary_key=True) + user_id = Column( + 'core__user_id', + Integer, + ForeignKey(Privilege.id), + primary_key=True) + + PRIVILEGE_FOUNDATIONS_v0 = [{'privilege_name':u'admin'}, + {'privilege_name':u'moderator'}, + {'privilege_name':u'uploader'}, + {'privilege_name':u'reporter'}, + {'privilege_name':u'commenter'}, + {'privilege_name':u'active'}] + + + class User_vR1(declarative_base()): + __tablename__ = 'rename__users' + id = Column(Integer, primary_key=True) + username = Column(Unicode, nullable=False, unique=True) + email = Column(Unicode, nullable=False) + pw_hash = Column(Unicode) + created = Column(DateTime, nullable=False, default=datetime.datetime.now) + wants_comment_notification = Column(Boolean, default=True) + wants_notifications = Column(Boolean, default=True) + license_preference = Column(Unicode) + url = Column(Unicode) + bio = Column(UnicodeText) # ?? + + @RegisterMigration(18, MIGRATIONS) + def create_moderation_tables(db): + + # First, we will create the new tables in the database. + #-------------------------------------------------------------------------- + ReportBase_v0.__table__.create(db.bind) + CommentReport_v0.__table__.create(db.bind) + MediaReport_v0.__table__.create(db.bind) + UserBan_v0.__table__.create(db.bind) + Privilege_v0.__table__.create(db.bind) + PrivilegeUserAssociation_v0.__table__.create(db.bind) + + db.commit() + + # Then initialize the tables that we will later use + #-------------------------------------------------------------------------- + metadata = MetaData(bind=db.bind) + privileges_table= inspect_table(metadata, "core__privileges") + user_table = inspect_table(metadata, 'core__users') + user_privilege_assoc = inspect_table( + metadata, 'core__privileges_users') + + # This section initializes the default Privilege foundations, that + # would be created through the FOUNDATIONS system in a new instance + #-------------------------------------------------------------------------- + for parameters in PRIVILEGE_FOUNDATIONS_v0: + db.execute(privileges_table.insert().values(**parameters)) + + db.commit() + + # This next section takes the information from the old is_admin and status + # columns and converts those to the new privilege system + #-------------------------------------------------------------------------- + admin_users_ids, active_users_ids, inactive_users_ids = ( + db.execute( + user_table.select().where( + user_table.c.is_admin==1)).fetchall(), + db.execute( + user_table.select().where( + user_table.c.is_admin==0).where( + user_table.c.status==u"active")).fetchall(), + db.execute( + user_table.select().where( + user_table.c.is_admin==0).where( + user_table.c.status!=u"active")).fetchall()) + + # Get the ids for each of the privileges so we can reference them ~~~~~~~~~ + (admin_privilege_id, uploader_privilege_id, + reporter_privilege_id, commenter_privilege_id, + active_privilege_id) = [ + db.execute(privileges_table.select().where( + privileges_table.c.privilege_name==privilege_name)).first()['id'] + for privilege_name in + [u"admin",u"uploader",u"reporter",u"commenter",u"active"] + ] + + # Give each user the appopriate privileges depending whether they are an + # admin, an active user or an inactivated user ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + for admin_user in admin_users_ids: + admin_user_id = admin_user['id'] + for privilege_id in [admin_privilege_id, uploader_privilege_id, reporter_privilege_id, commenter_privilege_id, active_privilege_id]: + db.execute(user_privilege_assoc.insert().values( + core__privilege_id=admin_user_id, + core__user_id=privilege_id)) + + for active_user in active_users_ids: + active_user_id = active_user['id'] + for privilege_id in [uploader_privilege_id, reporter_privilege_id, commenter_privilege_id, active_privilege_id]: + db.execute(user_privilege_assoc.insert().values( + core__privilege_id=active_user_id, + core__user_id=privilege_id)) + + for inactive_user in inactive_users_ids: + inactive_user_id = inactive_user['id'] + for privilege_id in [uploader_privilege_id, reporter_privilege_id, commenter_privilege_id]: + db.execute(user_privilege_assoc.insert().values( + core__privilege_id=inactive_user_id, + core__user_id=privilege_id)) + + db.commit() + + # And then, once the information is taken from the is_admin & status columns + # we drop all of the vestigial columns from the User table. + #-------------------------------------------------------------------------- + if db.bind.url.drivername == 'sqlite': + # SQLite has some issues that make it *impossible* to drop boolean + # columns. So, the following code is a very hacky workaround which + # makes it possible. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + User_vR1.__table__.create(db.bind) + db.commit() + new_user_table = inspect_table(metadata, 'rename__users') + for row in db.execute(user_table.select()): + db.execute(new_user_table.insert().values( + username=row.username, + email=row.email, + pw_hash=row.pw_hash, + created=row.created, + wants_comment_notification=row.wants_comment_notification, + wants_notifications=row.wants_notifications, + license_preference=row.license_preference, + url=row.url, + bio=row.bio)) + + db.commit() + user_table.drop() + + db.commit() + new_user_table.rename("core__users") + else: + # If the db is not SQLite, this process is much simpler ~~~~~~~~~~~~~~~ + + status = user_table.columns['status'] + email_verified = user_table.columns['email_verified'] + is_admin = user_table.columns['is_admin'] + status.drop() + email_verified.drop() + is_admin.drop() db.commit() + + +@RegisterMigration(16, MIGRATIONS) +def upload_limits(db): + """Add user upload limit columns""" + metadata = MetaData(bind=db.bind) + + user_table = inspect_table(metadata, 'core__users') + media_entry_table = inspect_table(metadata, 'core__media_entries') + + col = Column('uploaded', Integer, default=0) + col.create(user_table) + + col = Column('upload_limit', Integer) + col.create(user_table) + + col = Column('file_size', Integer, default=0) + col.create(media_entry_table) + + db.commit() + + +@RegisterMigration(17, MIGRATIONS) +def add_file_metadata(db): + """Add file_metadata to MediaFile""" + metadata = MetaData(bind=db.bind) + media_file_table = inspect_table(metadata, "core__mediafiles") + + col = Column('file_metadata', MutationDict.as_mutable(JSONEncoded)) + col.create(media_file_table) + + db.commit() diff --cc mediagoblin/db/models.py index 68a2faa0,5173be9e..1514a3aa --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@@ -72,11 -69,8 +70,10 @@@ class User(Base, UserMixin) wants_comment_notification = Column(Boolean, default=True) wants_notifications = Column(Boolean, default=True) license_preference = Column(Unicode) - is_admin = Column(Boolean, default=False, nullable=False) url = Column(Unicode) bio = Column(UnicodeText) # ?? + uploaded = Column(Integer, default=0) + upload_limit = Column(Integer) ## TODO # plugin data would be in a separate model diff --cc mediagoblin/templates/mediagoblin/user_pages/user.html index 50ad766a,ab616ea8..37983400 --- a/mediagoblin/templates/mediagoblin/user_pages/user.html +++ b/mediagoblin/templates/mediagoblin/user_pages/user.html @@@ -41,86 -38,134 +41,86 @@@ {% block mediagoblin_content -%} - {# If no user... #} - {% if not user %} -

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

- - {# User exists, but needs verification #} - {% elif not user.has_privilege('active') %} - {% if user == request.user %} - {# this should only be visible when you are this user #} -
-

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

+

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

+ {% if not user.url and not user.bio %} + {% if request.user and (request.user.id == user.id) %} +

- {% trans -%} - Almost done! Your account still needs to be activated. - {%- endtrans %} -

-

- {% trans -%} - An email should arrive in a few moments with instructions on how to do so. - {%- endtrans %} + {% trans %}Here's a spot to tell others about yourself.{% endtrans %}

-

{% trans %}In case it doesn't:{% endtrans %}

- - {% trans %}Resend verification email{% endtrans %} -
+ + {%- trans %}Edit profile{% endtrans -%} + {% else %} - {# if the user is not you, but still needs to verify their email #} -
-

{% 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. + This user hasn't filled in their profile (yet). {%- endtrans %}

-
{% endif %} - - {# 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 %} - {% if request.user and (request.user.id == user.id) %} -
-

- {% trans %}Here's a spot to tell others about yourself.{% endtrans %} -

- - {%- trans %}Edit profile{% endtrans -%} - - {% else %} -
-

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

+
+ {% 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 -%} + {% endif %} - {% else %} -
- {% include "mediagoblin/utils/profile.html" %} - {% if request.user and - (request.user.id == user.id or request.user.has_privilege('admin')) %} - - {%- trans %}Edit profile{% endtrans -%} - - {% endif %} - {% endif %} + {% endif %} +

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

+
+ + {% if media_entries.count() %} +
+ {{ object_gallery(request, media_entries, pagination, + pagination_base_url=user_gallery_url, col_number=3) }} + {% include "mediagoblin/utils/object_gallery.html" %} +

- - {%- trans %}Browse collections{% endtrans -%} + + {% trans username=user.username -%} + View all of {{ username }}'s media{% endtrans -%}

+ {% set feed_url = request.urlgen( + 'mediagoblin.user_pages.atom_feed', + user=user.username) %} + {% include "mediagoblin/utils/feed_link.html" %}
- - {% if media_entries.count() %} -
- {{ object_gallery(request, media_entries, pagination, - pagination_base_url=user_gallery_url, col_number=3) }} - {% include "mediagoblin/utils/object_gallery.html" %} -
+ {% else %} + {% if request.user and (request.user.id == user.id) %} +

- - {% trans username=user.username -%} - View all of {{ username }}'s media{% endtrans -%} - + {% trans -%} + This is where your media will appear, but you don't seem to have added anything yet. + {%- endtrans %}

- {% set feed_url = request.urlgen( - 'mediagoblin.user_pages.atom_feed', - user=user.username) %} - {% include "mediagoblin/utils/feed_link.html" %} + + {%- trans %}Add media{% endtrans -%} +
{% else %} - {% if request.user and (request.user.id == user.id) %} -
-

- {% trans -%} - This is where your media will appear, but you don't seem to have added anything yet. - {%- endtrans %} -

- - {%- trans %}Add media{% endtrans -%} - -
- {% else %} -
-

- {% trans -%} - There doesn't seem to be any media here yet... - {%- endtrans %} -

-
- {% endif %} +
+

+ {% trans -%} + There doesn't seem to be any media here yet... + {%- endtrans %} +

+
{% endif %} -
{% endif %} +
{% endblock %} diff --cc mediagoblin/templates/mediagoblin/user_pages/user_nonactive.html index b3066665,00000000..d924198b mode 100644,000000..100644 --- a/mediagoblin/templates/mediagoblin/user_pages/user_nonactive.html +++ b/mediagoblin/templates/mediagoblin/user_pages/user_nonactive.html @@@ -1,83 -1,0 +1,83 @@@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +# This is the main user homepage for non-active users that still need +# registration etc. +#} +{% extends "mediagoblin/base.html" %} + +{% block title %} + {%- if user -%} + {%- trans username=user.username -%} + {{ username }}'s profile + {%- endtrans %} — {{ super() }} + {%- else -%} + {{ super() }} + {%- endif -%} +{% endblock %} + + +{% block mediagoblin_content -%} + {# User exists, but needs verification #} - {% if user.status == "needs_email_verification" %} ++ {% if not user.has_privilege('active') %} + {% if user == request.user %} + {# this should only be visible when you are this user #} +
+

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

+ +

+ {% trans -%} + Almost done! Your account still needs to be activated. + {%- endtrans %} +

+

+ {% trans -%} + An email should arrive in a few moments with instructions on how to do so. + {%- endtrans %} +

+

{% trans %}In case it doesn't:{% endtrans %}

+ + {% trans %}Resend verification email{% endtrans %} +
+ {% else %} + {# if the user is not you, but still needs to verify their email #} +
+

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

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

+ {%- trans username=user.username %}{{ username }}{% endtrans -%} +

+

{{ username }} is not active.

+
+ {% endif %} +{% endblock %} diff --cc mediagoblin/tests/test_auth.py index d2e01e44,63c12682..1bbc3d01 --- a/mediagoblin/tests/test_auth.py +++ b/mediagoblin/tests/test_auth.py @@@ -90,19 -89,23 +90,23 @@@ def test_register_views(test_app) 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 + assert 'mediagoblin/user_pages/user_nonactive.html' in template.TEMPLATE_TEST_CONTEXT ## Make sure user is in place new_user = mg_globals.database.User.query.filter_by( - username=u'happygirl').first() + username=u'angrygirl').first() assert new_user - assert new_user.status == u'needs_email_verification' - assert new_user.email_verified == False + ## Make sure that the proper privileges are granted on registration + + assert new_user.has_privilege(u'commenter') + assert new_user.has_privilege(u'uploader') + assert new_user.has_privilege(u'reporter') + assert not new_user.has_privilege(u'active') ## Make sure user is logged in request = template.TEMPLATE_TEST_CONTEXT[ - 'mediagoblin/user_pages/user.html']['request'] + 'mediagoblin/user_pages/user_nonactive.html']['request'] assert request.session['user_id'] == unicode(new_user.id) ## Make sure we get email confirmation, and try verifying @@@ -182,9 -181,9 +182,9 @@@ ## 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'] + 'mediagoblin/plugins/basic_auth/fp_verification_email.txt'] #TODO - change the name of verification_url to something forgot-password-ish assert email_context['verification_url'] in message.get_payload(decode=True) diff --cc mediagoblin/tests/test_submission.py index 7f4e8086,14766c50..5d42c5a5 --- a/mediagoblin/tests/test_submission.py +++ b/mediagoblin/tests/test_submission.py @@@ -148,78 -128,9 +158,78 @@@ class TestSubmission response, context = self.do_post({'title': u'Normal upload 3 (pdf)'}, do_follow=True, **self.upload_data(GOOD_PDF)) - self.check_url(response, '/u/{0}/'.format(self.test_user.username)) + self.check_url(response, '/u/{0}/'.format(self.our_user().username)) assert 'mediagoblin/user_pages/user.html' in context + def test_default_upload_limits(self): + self.user_upload_limits(uploaded=500) + + # User uploaded should be 500 + assert self.test_user.uploaded == 500 + + response, context = self.do_post({'title': u'Normal upload 4'}, + do_follow=True, + **self.upload_data(GOOD_JPG)) + self.check_url(response, '/u/{0}/'.format(self.test_user.username)) + assert 'mediagoblin/user_pages/user.html' in context + + # Reload user + self.test_user = User.query.filter_by( + username=self.test_user.username + ).first() + + # Shouldn't have uploaded + assert self.test_user.uploaded == 500 + + def test_user_upload_limit(self): + self.user_upload_limits(uploaded=25, upload_limit=25) + + # User uploaded should be 25 + assert self.test_user.uploaded == 25 + + response, context = self.do_post({'title': u'Normal upload 5'}, + do_follow=True, + **self.upload_data(GOOD_JPG)) + self.check_url(response, '/u/{0}/'.format(self.test_user.username)) + assert 'mediagoblin/user_pages/user.html' in context + + # Reload user + self.test_user = User.query.filter_by( + username=self.test_user.username + ).first() + + # Shouldn't have uploaded + assert self.test_user.uploaded == 25 + + def test_user_under_limit(self): + self.user_upload_limits(uploaded=499) + + # User uploaded should be 499 + assert self.test_user.uploaded == 499 + + response, context = self.do_post({'title': u'Normal upload 6'}, + do_follow=False, + **self.upload_data(MED_PNG)) + form = context['mediagoblin/submit/start.html']['submit_form'] + assert form.file.errors == [u'Sorry, uploading this file will put you' + ' over your upload limit.'] + + # Reload user + self.test_user = User.query.filter_by( + username=self.test_user.username + ).first() + + # Shouldn't have uploaded + assert self.test_user.uploaded == 499 + + def test_big_file(self): + response, context = self.do_post({'title': u'Normal upload 7'}, + do_follow=False, + **self.upload_data(BIG_PNG)) + + form = context['mediagoblin/submit/start.html']['submit_form'] + assert form.file.errors == [u'Sorry, the file size is too big.'] + def check_media(self, request, find_data, count=None): media = MediaEntry.query.filter_by(**find_data) if count is not None: diff --cc mediagoblin/user_pages/views.py index 974cb3c6,020fa6a8..73823e4d --- a/mediagoblin/user_pages/views.py +++ b/mediagoblin/user_pages/views.py @@@ -50,10 -53,13 +53,10 @@@ def user_home(request, page) user = User.query.filter_by(username=request.matchdict['user']).first() if not user: return render_404(request) - elif user.status != u'active': + elif not user.has_privilege(u'active'): return render_to_response( request, - 'mediagoblin/user_pages/user.html', + 'mediagoblin/user_pages/user_nonactive.html', {'user': user}) cursor = MediaEntry.query.\