diff options
139 files changed, 2582 insertions, 1068 deletions
diff --git a/docs/source/siteadmin/deploying.rst b/docs/source/siteadmin/deploying.rst index 2f3f0428..446dd461 100644 --- a/docs/source/siteadmin/deploying.rst +++ b/docs/source/siteadmin/deploying.rst @@ -256,6 +256,11 @@ flup:: $ ./bin/easy_install flup +(Note, if you're running Python 2, which you probably are at this +point in MediaGoblin's development, you'll need to run:) + + $ ./bin/easy_install flup==1.0.3.dev-20110405 + The above provides an in-package install of ``virtualenv``. While this is counter to the conventional ``virtualenv`` configuration, it is more reliable and considerably easier to configure and illustrate. If diff --git a/mediagoblin.example.ini b/mediagoblin.example.ini index 17b123fb..52331d82 100644 --- a/mediagoblin.example.ini +++ b/mediagoblin.example.ini @@ -2,8 +2,8 @@ # mediagoblin_local.ini, then make the changes there. # # If you don't see what you need here, have a look at mediagoblin/config_spec.ini -# It defines types and defaults so it’s a good place to look for documentation -# or to find hidden options that we didn’t tell you about. :) +# It defines types and defaults so it's a good place to look for documentation +# or to find hidden options that we didn't tell you about. :) # To change the directory you should make sure you change the # directory in paste.ini and/or your webserver configuration. diff --git a/mediagoblin/api/decorators.py b/mediagoblin/api/decorators.py index 3dd6264e..b86099bd 100644 --- a/mediagoblin/api/decorators.py +++ b/mediagoblin/api/decorators.py @@ -15,7 +15,6 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. from functools import wraps -from mediagoblin.db.models import User from mediagoblin.decorators import require_active_login from mediagoblin.tools.response import json_response diff --git a/mediagoblin/api/views.py b/mediagoblin/api/views.py index 6095a721..dcd04cd6 100644 --- a/mediagoblin/api/views.py +++ b/mediagoblin/api/views.py @@ -22,7 +22,7 @@ from werkzeug.datastructures import FileStorage from mediagoblin.decorators import oauth_required, require_active_login from mediagoblin.api.decorators import user_has_privilege -from mediagoblin.db.models import User, MediaEntry, MediaComment, Activity +from mediagoblin.db.models import User, LocalUser, MediaEntry, Comment, TextComment, Activity from mediagoblin.tools.federation import create_activity, create_generator from mediagoblin.tools.routing import extract_url_arguments from mediagoblin.tools.response import redirect, json_response, json_error, \ @@ -45,7 +45,7 @@ def get_profile(request): can be found then this function returns a (None, None). """ username = request.matchdict["username"] - user = User.query.filter_by(username=username).first() + user = LocalUser.query.filter(LocalUser.username==username).first() if user is None: return None, None @@ -94,7 +94,7 @@ def user_endpoint(request): def uploads_endpoint(request): """ Endpoint for file uploads """ username = request.matchdict["username"] - requested_user = User.query.filter_by(username=username).first() + requested_user = LocalUser.query.filter(LocalUser.username==username).first() if requested_user is None: return json_error("No such 'user' with id '{0}'".format(username), 404) @@ -142,7 +142,7 @@ def inbox_endpoint(request, inbox=None): inbox: allows you to pass a query in to limit inbox scope """ username = request.matchdict["username"] - user = User.query.filter_by(username=username).first() + user = LocalUser.query.filter(LocalUser.username==username).first() if user is None: return json_error("No such 'user' with id '{0}'".format(username), 404) @@ -225,7 +225,7 @@ def inbox_major_endpoint(request): def feed_endpoint(request, outbox=None): """ Handles the user's outbox - /api/user/<username>/feed """ username = request.matchdict["username"] - requested_user = User.query.filter_by(username=username).first() + requested_user = LocalUser.query.filter(LocalUser.username==username).first() # check if the user exists if requested_user is None: @@ -268,7 +268,7 @@ def feed_endpoint(request, outbox=None): status=403 ) - comment = MediaComment(author=request.user.id) + comment = TextComment(actor=request.user.id) comment.unserialize(data["object"], request) comment.save() @@ -278,7 +278,7 @@ def feed_endpoint(request, outbox=None): verb="post", actor=request.user, obj=comment, - target=comment.get_entry, + target=comment.get_reply_to(), generator=generator ) @@ -286,12 +286,22 @@ def feed_endpoint(request, outbox=None): elif obj.get("objectType", None) == "image": # Posting an image to the feed - media_id = int(extract_url_arguments( + media_id = extract_url_arguments( url=data["object"]["id"], urlmap=request.app.url_map - )["id"]) + )["id"] - media = MediaEntry.query.filter_by(id=media_id).first() + # Build public_id + public_id = request.urlgen( + "mediagoblin.api.object", + object_type=obj["objectType"], + id=media_id, + qualified=True + ) + + media = MediaEntry.query.filter_by( + public_id=public_id + ).first() if media is None: return json_response( @@ -299,7 +309,7 @@ def feed_endpoint(request, outbox=None): status=404 ) - if media.uploader != request.user.id: + if media.actor != request.user.id: return json_error( "Privilege 'commenter' required to comment.", status=403 @@ -345,10 +355,17 @@ def feed_endpoint(request, outbox=None): if "id" not in obj: return json_error("Object ID has not been specified.") - obj_id = int(extract_url_arguments( + obj_id = extract_url_arguments( url=obj["id"], urlmap=request.app.url_map - )["id"]) + )["id"] + + public_id = request.urlgen( + "mediagoblin.api.object", + object_type=obj["objectType"], + id=obj_id, + qualified=True + ) # Now try and find object if obj["objectType"] == "comment": @@ -358,7 +375,9 @@ def feed_endpoint(request, outbox=None): status=403 ) - comment = MediaComment.query.filter_by(id=obj_id).first() + comment = TextComment.query.filter_by( + public_id=public_id + ).first() if comment is None: return json_error( "No such 'comment' with id '{0}'.".format(obj_id) @@ -366,7 +385,7 @@ def feed_endpoint(request, outbox=None): # Check that the person trying to update the comment is # the author of the comment. - if comment.author != request.user.id: + if comment.actor != request.user.id: return json_error( "Only author of comment is able to update comment.", status=403 @@ -391,7 +410,9 @@ def feed_endpoint(request, outbox=None): return json_response(activity.serialize(request)) elif obj["objectType"] == "image": - image = MediaEntry.query.filter_by(id=obj_id).first() + image = MediaEntry.query.filter_by( + public_id=public_id + ).first() if image is None: return json_error( "No such 'image' with the id '{0}'.".format(obj["id"]) @@ -399,7 +420,7 @@ def feed_endpoint(request, outbox=None): # Check that the person trying to update the comment is # the author of the comment. - if image.uploader != request.user.id: + if image.actor != request.user.id: return json_error( "Only uploader of image is able to update image.", status=403 @@ -454,16 +475,23 @@ def feed_endpoint(request, outbox=None): return json_error("Object ID has not been specified.") # Parse out the object ID - obj_id = int(extract_url_arguments( + obj_id = extract_url_arguments( url=obj["id"], urlmap=request.app.url_map - )["id"]) + )["id"] + + public_id = request.urlgen( + "mediagoblin.api.object", + object_type=obj["objectType"], + id=obj_id, + qualified=True + ) if obj.get("objectType", None) == "comment": # Find the comment asked for - comment = MediaComment.query.filter_by( - id=obj_id, - author=request.user.id + comment = TextComment.query.filter_by( + public_id=public_id, + actor=request.user.id ).first() if comment is None: @@ -491,8 +519,8 @@ def feed_endpoint(request, outbox=None): if obj.get("objectType", None) == "image": # Find the image entry = MediaEntry.query.filter_by( - id=obj_id, - uploader=request.user.id + public_id=public_id, + actor=request.user.id ).first() if entry is None: @@ -537,9 +565,9 @@ def feed_endpoint(request, outbox=None): # Create outbox if outbox is None: - outbox = Activity.query.filter_by(actor=request.user.id) + outbox = Activity.query.filter_by(actor=requested_user.id) else: - outbox = outbox.filter_by(actor=request.user.id) + outbox = outbox.filter_by(actor=requested_user.id) # We want the newest things at the top (issue: #1055) outbox = outbox.order_by(Activity.published.desc()) @@ -617,7 +645,14 @@ def object_endpoint(request): status=404 ) - media = MediaEntry.query.filter_by(id=object_id).first() + public_id = request.urlgen( + "mediagoblin.api.object", + object_type=object_type, + id=object_id, + qualified=True + ) + + media = MediaEntry.query.filter_by(public_id=public_id).first() if media is None: return json_error( "Can't find '{0}' with ID '{1}'".format(object_type, object_id), @@ -629,7 +664,13 @@ def object_endpoint(request): @oauth_required def object_comments(request): """ Looks up for the comments on a object """ - media = MediaEntry.query.filter_by(id=request.matchdict["id"]).first() + public_id = request.urlgen( + "mediagoblin.api.object", + object_type=request.matchdict["object_type"], + id=request.matchdict["id"], + qualified=True + ) + media = MediaEntry.query.filter_by(public_id=public_id).first() if media is None: return json_error("Can't find '{0}' with ID '{1}'".format( request.matchdict["object_type"], @@ -747,7 +788,7 @@ def lrdd_lookup(request): username, host = resource.split("@", 1) # Now lookup the user - user = User.query.filter_by(username=username).first() + user = LocalUser.query.filter(LocalUser.username==username).first() if user is None: return json_error( @@ -792,5 +833,3 @@ def whoami(request): ) return redirect(request, location=profile) - - diff --git a/mediagoblin/auth/tools.py b/mediagoblin/auth/tools.py index 3737fab6..5a47dae4 100644 --- a/mediagoblin/auth/tools.py +++ b/mediagoblin/auth/tools.py @@ -23,7 +23,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, Privilege +from mediagoblin.db.models import LocalUser, Privilege from mediagoblin.tools.mail import (normalize_email, send_email, email_debug_message) from mediagoblin.tools.template import render_template @@ -106,9 +106,9 @@ def send_verification_email(user, request, email=None, def basic_extra_validation(register_form, *args): - users_with_username = User.query.filter_by( + users_with_username = LocalUser.query.filter_by( username=register_form.username.data).count() - users_with_email = User.query.filter_by( + users_with_email = LocalUser.query.filter_by( email=register_form.email.data).count() extra_validation_passes = True @@ -190,7 +190,7 @@ def no_auth_logout(request): def create_basic_user(form): - user = User() + user = LocalUser() user.username = form.username.data user.email = form.email.data user.save() diff --git a/mediagoblin/config_spec.ini b/mediagoblin/config_spec.ini index fd86700a..0a8da73e 100644 --- a/mediagoblin/config_spec.ini +++ b/mediagoblin/config_spec.ini @@ -86,6 +86,9 @@ allow_attachments = boolean(default=False) # Cookie stuff csrf_cookie_name = string(default='mediagoblin_csrftoken') +# Set to true to prevent browsers leaking information through Referrers +no_referrer = boolean(default=True) + # Push stuff push_urls = string_list(default=list()) diff --git a/mediagoblin/db/__init__.py b/mediagoblin/db/__init__.py index 719b56e7..621845ba 100644 --- a/mediagoblin/db/__init__.py +++ b/mediagoblin/db/__init__.py @@ -13,4 +13,3 @@ # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. - diff --git a/mediagoblin/db/base.py b/mediagoblin/db/base.py index 6acb0b79..11afbcec 100644 --- a/mediagoblin/db/base.py +++ b/mediagoblin/db/base.py @@ -13,7 +13,8 @@ # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. - +import six +import copy from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import inspect @@ -24,8 +25,38 @@ if not DISABLE_GLOBALS: from sqlalchemy.orm import scoped_session, sessionmaker Session = scoped_session(sessionmaker()) +class FakeCursor(object): + + def __init__ (self, cursor, mapper, filter=None): + self.cursor = cursor + self.mapper = mapper + self.filter = filter + + def count(self): + return self.cursor.count() + + def __copy__(self): + # Or whatever the function is named to make + # copy.copy happy? + return FakeCursor(copy.copy(self.cursor), self.mapper, self.filter) + + def __iter__(self): + return six.moves.filter(self.filter, six.moves.map(self.mapper, self.cursor)) + + def __getitem__(self, key): + return self.mapper(self.cursor[key]) + + def slice(self, *args, **kwargs): + r = self.cursor.slice(*args, **kwargs) + return list(six.moves.filter(self.filter, six.moves.map(self.mapper, r))) class GMGTableBase(object): + # Deletion types + HARD_DELETE = "hard-deletion" + SOFT_DELETE = "soft-deletion" + + deletion_mode = HARD_DELETE + @property def _session(self): return inspect(self).session @@ -55,7 +86,56 @@ class GMGTableBase(object): else: sess.flush() - def delete(self, commit=True): + def delete(self, commit=True, deletion=None): + """ Delete the object either using soft or hard deletion """ + # Get the setting in the model args if none has been specified. + if deletion is None: + deletion = self.deletion_mode + + # Hand off to the correct deletion function. + if deletion == self.HARD_DELETE: + return self.hard_delete(commit=commit) + elif deletion == self.SOFT_DELETE: + return self.soft_delete(commit=commit) + else: + raise ValueError( + "Invalid deletion mode {mode!r}".format( + mode=deletion + ) + ) + + def soft_delete(self, commit): + # Create the graveyard version of this model + # Importing this here due to cyclic imports + from mediagoblin.db.models import User, Graveyard, GenericModelReference + tombstone = Graveyard() + if getattr(self, "public_id", None) is not None: + tombstone.public_id = self.public_id + + # This is a special case, we don't want to save any actor if the thing + # being soft deleted is a User model as this would create circular + # ForeignKeys + if not isinstance(self, User): + tombstone.actor = User.query.filter_by( + id=self.actor + ).first() + tombstone.object_type = self.object_type + tombstone.save(commit=False) + + # There will be a lot of places where the GenericForeignKey will point + # to the model, we want to remap those to our tombstone. + gmrs = GenericModelReference.query.filter_by( + obj_pk=self.id, + model_type=self.__tablename__ + ).update({ + "obj_pk": tombstone.id, + "model_type": tombstone.__tablename__, + }) + + # Now we can go ahead and actually delete the model. + return self.hard_delete(commit=commit) + + def hard_delete(self, commit): """Delete the object and commit the change immediately by default""" sess = self._session assert sess is not None, "Not going to delete detached %r" % self diff --git a/mediagoblin/db/migrations.py b/mediagoblin/db/migrations.py index 74c1194f..461b9c0a 100644 --- a/mediagoblin/db/migrations.py +++ b/mediagoblin/db/migrations.py @@ -32,11 +32,14 @@ from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.sql import and_ from sqlalchemy.schema import UniqueConstraint +from mediagoblin import oauth +from mediagoblin.tools import crypto from mediagoblin.db.extratypes import JSONEncoded, MutationDict from mediagoblin.db.migration_tools import ( RegisterMigration, inspect_table, replace_table_hack) -from mediagoblin.db.models import (MediaEntry, Collection, MediaComment, User, - Privilege, Generator) +from mediagoblin.db.models import (MediaEntry, Collection, Comment, User, + Privilege, Generator, LocalUser, Location, + Client, RequestToken, AccessToken) from mediagoblin.db.extratypes import JSONEncoded, MutationDict @@ -350,7 +353,7 @@ class CommentNotification_v0(Notification_v0): __tablename__ = 'core__comment_notifications' id = Column(Integer, ForeignKey(Notification_v0.id), primary_key=True) - subject_id = Column(Integer, ForeignKey(MediaComment.id)) + subject_id = Column(Integer, ForeignKey(Comment.id)) class ProcessingNotification_v0(Notification_v0): @@ -539,7 +542,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=True) + comment_id = Column(Integer, ForeignKey(Comment.id), nullable=True) class MediaReport_v0(ReportBase_v0): @@ -910,6 +913,14 @@ class ActivityIntermediator_R0(declarative_base()): id = Column(Integer, primary_key=True) type = Column(Unicode, nullable=False) + # These are needed for migration 29 + TYPES = { + "user": User, + "media": MediaEntry, + "comment": Comment, + "collection": Collection, + } + class Activity_R0(declarative_base()): __tablename__ = "core__activities" id = Column(Integer, primary_key=True) @@ -927,6 +938,7 @@ class Activity_R0(declarative_base()): ForeignKey(ActivityIntermediator_R0.id), nullable=True) + @RegisterMigration(24, MIGRATIONS) def activity_migration(db): """ @@ -1249,3 +1261,882 @@ def datetime_to_utc(db): # Commit this to the database db.commit() + +## +# Migrations to handle migrating from activity specific foreign key to the +# new GenericForeignKey implementations. They have been split up to improve +# readability and minimise errors +## + +class GenericModelReference_V0(declarative_base()): + __tablename__ = "core__generic_model_reference" + + id = Column(Integer, primary_key=True) + obj_pk = Column(Integer, nullable=False) + model_type = Column(Unicode, nullable=False) + +@RegisterMigration(27, MIGRATIONS) +def create_generic_model_reference(db): + """ Creates the Generic Model Reference table """ + GenericModelReference_V0.__table__.create(db.bind) + db.commit() + +@RegisterMigration(28, MIGRATIONS) +def add_foreign_key_fields(db): + """ + Add the fields for GenericForeignKey to the model under temporary name, + this is so that later a data migration can occur. They will be renamed to + the origional names. + """ + metadata = MetaData(bind=db.bind) + activity_table = inspect_table(metadata, "core__activities") + + # Create column and add to model. + object_column = Column("temp_object", Integer, ForeignKey(GenericModelReference_V0.id)) + object_column.create(activity_table) + + target_column = Column("temp_target", Integer, ForeignKey(GenericModelReference_V0.id)) + target_column.create(activity_table) + + # Commit this to the database + db.commit() + +@RegisterMigration(29, MIGRATIONS) +def migrate_data_foreign_keys(db): + """ + This will migrate the data from the old object and target attributes which + use the old ActivityIntermediator to the new temparay fields which use the + new GenericForeignKey. + """ + + metadata = MetaData(bind=db.bind) + activity_table = inspect_table(metadata, "core__activities") + ai_table = inspect_table(metadata, "core__activity_intermediators") + gmr_table = inspect_table(metadata, "core__generic_model_reference") + + + # Iterate through all activities doing the migration per activity. + for activity in db.execute(activity_table.select()): + # First do the "Activity.object" migration to "Activity.temp_object" + # I need to get the object from the Activity, I can't use the old + # Activity.get_object as we're in a migration. + object_ai = db.execute(ai_table.select( + ai_table.c.id==activity.object + )).first() + + object_ai_type = ActivityIntermediator_R0.TYPES[object_ai.type] + object_ai_table = inspect_table(metadata, object_ai_type.__tablename__) + + activity_object = db.execute(object_ai_table.select( + object_ai_table.c.activity==object_ai.id + )).first() + + # now we need to create the GenericModelReference + object_gmr = db.execute(gmr_table.insert().values( + obj_pk=activity_object.id, + model_type=object_ai_type.__tablename__ + )) + + # Now set the ID of the GenericModelReference in the GenericForignKey + db.execute(activity_table.update().values( + temp_object=object_gmr.inserted_primary_key[0] + )) + + # Now do same process for "Activity.target" to "Activity.temp_target" + # not all Activities have a target so if it doesn't just skip the rest + # of this. + if activity.target is None: + continue + + # Now get the target for the activity. + target_ai = db.execute(ai_table.select( + ai_table.c.id==activity.target + )).first() + + target_ai_type = ActivityIntermediator_R0.TYPES[target_ai.type] + target_ai_table = inspect_table(metadata, target_ai_type.__tablename__) + + activity_target = db.execute(target_ai_table.select( + target_ai_table.c.activity==target_ai.id + )).first() + + # We now want to create the new target GenericModelReference + target_gmr = db.execute(gmr_table.insert().values( + obj_pk=activity_target.id, + model_type=target_ai_type.__tablename__ + )) + + # Now set the ID of the GenericModelReference in the GenericForignKey + db.execute(activity_table.update().values( + temp_object=target_gmr.inserted_primary_key[0] + )) + + # Commit to the database. + db.commit() + +@RegisterMigration(30, MIGRATIONS) +def rename_and_remove_object_and_target(db): + """ + Renames the new Activity.object and Activity.target fields and removes the + old ones. + """ + metadata = MetaData(bind=db.bind) + activity_table = inspect_table(metadata, "core__activities") + + # Firstly lets remove the old fields. + old_object_column = activity_table.columns["object"] + old_target_column = activity_table.columns["target"] + + # Drop the tables. + old_object_column.drop() + old_target_column.drop() + + # Now get the new columns. + new_object_column = activity_table.columns["temp_object"] + new_target_column = activity_table.columns["temp_target"] + + # rename them to the old names. + new_object_column.alter(name="object_id") + new_target_column.alter(name="target_id") + + # Commit the changes to the database. + db.commit() + +@RegisterMigration(31, MIGRATIONS) +def remove_activityintermediator(db): + """ + This removes the old specific ActivityIntermediator model which has been + superseeded by the GenericForeignKey field. + """ + metadata = MetaData(bind=db.bind) + + # Remove the columns which reference the AI + collection_table = inspect_table(metadata, "core__collections") + collection_ai_column = collection_table.columns["activity"] + collection_ai_column.drop() + + media_entry_table = inspect_table(metadata, "core__media_entries") + media_entry_ai_column = media_entry_table.columns["activity"] + media_entry_ai_column.drop() + + comments_table = inspect_table(metadata, "core__media_comments") + comments_ai_column = comments_table.columns["activity"] + comments_ai_column.drop() + + user_table = inspect_table(metadata, "core__users") + user_ai_column = user_table.columns["activity"] + user_ai_column.drop() + + # Drop the table + ai_table = inspect_table(metadata, "core__activity_intermediators") + ai_table.drop() + + # Commit the changes + db.commit() + +## +# Migrations for converting the User model into a Local and Remote User +# setup. +## + +class LocalUser_V0(declarative_base()): + __tablename__ = "core__local_users" + + id = Column(Integer, ForeignKey(User.id), primary_key=True) + username = Column(Unicode, nullable=False, unique=True) + email = Column(Unicode, nullable=False) + pw_hash = Column(Unicode) + + wants_comment_notification = Column(Boolean, default=True) + wants_notifications = Column(Boolean, default=True) + license_preference = Column(Unicode) + uploaded = Column(Integer, default=0) + upload_limit = Column(Integer) + +class RemoteUser_V0(declarative_base()): + __tablename__ = "core__remote_users" + + id = Column(Integer, ForeignKey(User.id), primary_key=True) + webfinger = Column(Unicode, unique=True) + +@RegisterMigration(32, MIGRATIONS) +def federation_user_create_tables(db): + """ + Create all the tables + """ + # Create tables needed + LocalUser_V0.__table__.create(db.bind) + RemoteUser_V0.__table__.create(db.bind) + db.commit() + + metadata = MetaData(bind=db.bind) + user_table = inspect_table(metadata, "core__users") + + # Create the fields + updated_column = Column( + "updated", + DateTime, + default=datetime.datetime.utcnow + ) + updated_column.create(user_table) + + type_column = Column( + "type", + Unicode + ) + type_column.create(user_table) + + name_column = Column( + "name", + Unicode + ) + name_column.create(user_table) + + db.commit() + +@RegisterMigration(33, MIGRATIONS) +def federation_user_migrate_data(db): + """ + Migrate the data over to the new user models + """ + metadata = MetaData(bind=db.bind) + + user_table = inspect_table(metadata, "core__users") + local_user_table = inspect_table(metadata, "core__local_users") + + for user in db.execute(user_table.select()): + db.execute(local_user_table.insert().values( + id=user.id, + username=user.username, + email=user.email, + pw_hash=user.pw_hash, + wants_comment_notification=user.wants_comment_notification, + wants_notifications=user.wants_notifications, + license_preference=user.license_preference, + uploaded=user.uploaded, + upload_limit=user.upload_limit + )) + + db.execute(user_table.update().where(user_table.c.id==user.id).values( + updated=user.created, + type=LocalUser.__mapper_args__["polymorphic_identity"] + )) + + db.commit() + +class User_vR2(declarative_base()): + __tablename__ = "rename__users" + + id = Column(Integer, primary_key=True) + url = Column(Unicode) + bio = Column(UnicodeText) + name = Column(Unicode) + type = Column(Unicode) + created = Column(DateTime, nullable=False, default=datetime.datetime.utcnow) + updated = Column(DateTime, nullable=False, default=datetime.datetime.utcnow) + location = Column(Integer, ForeignKey(Location.id)) + +@RegisterMigration(34, MIGRATIONS) +def federation_remove_fields(db): + """ + This removes the fields from User model which aren't shared + """ + metadata = MetaData(bind=db.bind) + + user_table = inspect_table(metadata, "core__users") + + # Remove the columns moved to LocalUser from User + username_column = user_table.columns["username"] + username_column.drop() + + email_column = user_table.columns["email"] + email_column.drop() + + pw_hash_column = user_table.columns["pw_hash"] + pw_hash_column.drop() + + license_preference_column = user_table.columns["license_preference"] + license_preference_column.drop() + + uploaded_column = user_table.columns["uploaded"] + uploaded_column.drop() + + upload_limit_column = user_table.columns["upload_limit"] + upload_limit_column.drop() + + # SQLLite can't drop booleans -.- + if db.bind.url.drivername == 'sqlite': + # Create the new hacky table + User_vR2.__table__.create(db.bind) + db.commit() + new_user_table = inspect_table(metadata, "rename__users") + replace_table_hack(db, user_table, new_user_table) + else: + wcn_column = user_table.columns["wants_comment_notification"] + wcn_column.drop() + + wants_notifications_column = user_table.columns["wants_notifications"] + wants_notifications_column.drop() + + db.commit() + +@RegisterMigration(35, MIGRATIONS) +def federation_media_entry(db): + metadata = MetaData(bind=db.bind) + media_entry_table = inspect_table(metadata, "core__media_entries") + + # Add new fields + public_id_column = Column( + "public_id", + Unicode, + unique=True, + nullable=True + ) + public_id_column.create( + media_entry_table, + unique_name="media_public_id" + ) + + remote_column = Column( + "remote", + Boolean, + default=False + ) + remote_column.create(media_entry_table) + + updated_column = Column( + "updated", + DateTime, + default=datetime.datetime.utcnow, + ) + updated_column.create(media_entry_table) + + # Data migration + for entry in db.execute(media_entry_table.select()): + db.execute(media_entry_table.update().values( + updated=entry.created, + remote=False + )) + + db.commit() + +@RegisterMigration(36, MIGRATIONS) +def create_oauth1_dummies(db): + """ + Creates a dummy client, request and access tokens. + + This is used when invalid data is submitted but real clients and + access tokens. The use of dummy objects prevents timing attacks. + """ + metadata = MetaData(bind=db.bind) + client_table = inspect_table(metadata, "core__clients") + request_token_table = inspect_table(metadata, "core__request_tokens") + access_token_table = inspect_table(metadata, "core__access_tokens") + + # Whilst we don't rely on the secret key being unique or unknown to prevent + # unauthorized clients from using it to authenticate, we still as an extra + # layer of protection created a cryptographically secure key individual to + # each instance that should never be able to be known. + client_secret = crypto.random_string(50) + request_token_secret = crypto.random_string(50) + request_token_verifier = crypto.random_string(50) + access_token_secret = crypto.random_string(50) + + # Dummy created/updated datetime object + epoc_datetime = datetime.datetime.fromtimestamp(0) + + # Create the dummy Client + db.execute(client_table.insert().values( + id=oauth.DUMMY_CLIENT_ID, + secret=client_secret, + application_type="dummy", + created=epoc_datetime, + updated=epoc_datetime + )) + + # Create the dummy RequestToken + db.execute(request_token_table.insert().values( + token=oauth.DUMMY_REQUEST_TOKEN, + secret=request_token_secret, + client=oauth.DUMMY_CLIENT_ID, + verifier=request_token_verifier, + created=epoc_datetime, + updated=epoc_datetime, + callback="oob" + )) + + # Create the dummy AccessToken + db.execute(access_token_table.insert().values( + token=oauth.DUMMY_ACCESS_TOKEN, + secret=access_token_secret, + request_token=oauth.DUMMY_REQUEST_TOKEN, + created=epoc_datetime, + updated=epoc_datetime + )) + + # Commit the changes + db.commit() + +@RegisterMigration(37, MIGRATIONS) +def federation_collection_schema(db): + """ Converts the Collection and CollectionItem """ + metadata = MetaData(bind=db.bind) + collection_table = inspect_table(metadata, "core__collections") + collection_items_table = inspect_table(metadata, "core__collection_items") + media_entry_table = inspect_table(metadata, "core__media_entries") + gmr_table = inspect_table(metadata, "core__generic_model_reference") + + ## + # Collection Table + ## + + # Add the fields onto the Collection model, we need to set these as + # not null to avoid DB integreity errors. We will add the not null + # constraint later. + public_id_column = Column( + "public_id", + Unicode, + unique=True + ) + public_id_column.create( + collection_table, + unique_name="collection_public_id") + + updated_column = Column( + "updated", + DateTime, + default=datetime.datetime.utcnow + ) + updated_column.create(collection_table) + + type_column = Column( + "type", + Unicode, + ) + type_column.create(collection_table) + + db.commit() + + # Iterate over the items and set the updated and type fields + for collection in db.execute(collection_table.select()): + db.execute(collection_table.update().where( + collection_table.c.id==collection.id + ).values( + updated=collection.created, + type="core-user-defined" + )) + + db.commit() + + # Add the not null constraint onto the fields + updated_column = collection_table.columns["updated"] + updated_column.alter(nullable=False) + + type_column = collection_table.columns["type"] + type_column.alter(nullable=False) + + db.commit() + + # Rename the "items" to "num_items" as per the TODO + num_items_field = collection_table.columns["items"] + num_items_field.alter(name="num_items") + db.commit() + + ## + # CollectionItem + ## + # Adding the object ID column, this again will have not null added later. + object_id = Column( + "object_id", + Integer, + ForeignKey(GenericModelReference_V0.id), + ) + object_id.create( + collection_items_table, + ) + + db.commit() + + # Iterate through and convert the Media reference to object_id + for item in db.execute(collection_items_table.select()): + # Check if there is a GMR for the MediaEntry + object_gmr = db.execute(gmr_table.select( + and_( + gmr_table.c.obj_pk == item.media_entry, + gmr_table.c.model_type == "core__media_entries" + ) + )).first() + + if object_gmr: + object_gmr = object_gmr[0] + else: + # Create a GenericModelReference + object_gmr = db.execute(gmr_table.insert().values( + obj_pk=item.media_entry, + model_type="core__media_entries" + )).inserted_primary_key[0] + + # Now set the object_id column to the ID of the GMR + db.execute(collection_items_table.update().where( + collection_items_table.c.id==item.id + ).values( + object_id=object_gmr + )) + + db.commit() + + # Add not null constraint + object_id = collection_items_table.columns["object_id"] + object_id.alter(nullable=False) + + db.commit() + + # Now remove the old media_entry column + media_entry_column = collection_items_table.columns["media_entry"] + media_entry_column.drop() + + db.commit() + +@RegisterMigration(38, MIGRATIONS) +def federation_actor(db): + """ Renames refereces to the user to actor """ + metadata = MetaData(bind=db.bind) + + # RequestToken: user -> actor + request_token_table = inspect_table(metadata, "core__request_tokens") + rt_user_column = request_token_table.columns["user"] + rt_user_column.alter(name="actor") + + # AccessToken: user -> actor + access_token_table = inspect_table(metadata, "core__access_tokens") + at_user_column = access_token_table.columns["user"] + at_user_column.alter(name="actor") + + # MediaEntry: uploader -> actor + media_entry_table = inspect_table(metadata, "core__media_entries") + me_user_column = media_entry_table.columns["uploader"] + me_user_column.alter(name="actor") + + # MediaComment: author -> actor + media_comment_table = inspect_table(metadata, "core__media_comments") + mc_user_column = media_comment_table.columns["author"] + mc_user_column.alter(name="actor") + + # Collection: creator -> actor + collection_table = inspect_table(metadata, "core__collections") + mc_user_column = collection_table.columns["creator"] + mc_user_column.alter(name="actor") + + # commit changes to db. + db.commit() + +class Graveyard_V0(declarative_base()): + """ Where models come to die """ + __tablename__ = "core__graveyard" + + id = Column(Integer, primary_key=True) + public_id = Column(Unicode, nullable=True, unique=True) + + deleted = Column(DateTime, nullable=False) + object_type = Column(Unicode, nullable=False) + + actor_id = Column(Integer, ForeignKey(GenericModelReference_V0.id)) + +@RegisterMigration(39, MIGRATIONS) +def federation_graveyard(db): + """ Introduces soft deletion to models + + This adds a Graveyard model which is used to copy (soft-)deleted models to. + """ + metadata = MetaData(bind=db.bind) + + # Create the graveyard table + Graveyard_V0.__table__.create(db.bind) + + # Commit changes to the db + db.commit() + +@RegisterMigration(40, MIGRATIONS) +def add_public_id(db): + metadata = MetaData(bind=db.bind) + + # Get the table + activity_table = inspect_table(metadata, "core__activities") + activity_public_id = Column( + "public_id", + Unicode, + unique=True, + nullable=True + ) + activity_public_id.create( + activity_table, + unique_name="activity_public_id" + ) + + # Commit this. + db.commit() + +class Comment_V0(declarative_base()): + __tablename__ = "core__comment_links" + + id = Column(Integer, primary_key=True) + target_id = Column( + Integer, + ForeignKey(GenericModelReference_V0.id), + nullable=False + ) + comment_id = Column( + Integer, + ForeignKey(GenericModelReference_V0.id), + nullable=False + ) + added = Column(DateTime, nullable=False, default=datetime.datetime.utcnow) + + +@RegisterMigration(41, MIGRATIONS) +def federation_comments(db): + """ + This reworks the MediaComent to be a more generic Comment model. + """ + metadata = MetaData(bind=db.bind) + textcomment_table = inspect_table(metadata, "core__media_comments") + gmr_table = inspect_table(metadata, "core__generic_model_reference") + + # First of all add the public_id field to the TextComment table + comment_public_id_column = Column( + "public_id", + Unicode, + unique=True + ) + comment_public_id_column.create( + textcomment_table, + unique_name="public_id_unique" + ) + + comment_updated_column = Column( + "updated", + DateTime, + ) + comment_updated_column.create(textcomment_table) + + + # First create the Comment link table. + Comment_V0.__table__.create(db.bind) + db.commit() + + # now look up the comment table + comment_table = inspect_table(metadata, "core__comment_links") + + # Itierate over all the comments and add them to the link table. + for comment in db.execute(textcomment_table.select()): + # Check if there is a GMR to the comment. + comment_gmr = db.execute(gmr_table.select().where(and_( + gmr_table.c.obj_pk == comment.id, + gmr_table.c.model_type == "core__media_comments" + ))).first() + + if comment_gmr: + comment_gmr = comment_gmr[0] + else: + comment_gmr = db.execute(gmr_table.insert().values( + obj_pk=comment.id, + model_type="core__media_comments" + )).inserted_primary_key[0] + + # Get or create the GMR for the media entry + entry_gmr = db.execute(gmr_table.select().where(and_( + gmr_table.c.obj_pk == comment.media_entry, + gmr_table.c.model_type == "core__media_entries" + ))).first() + + if entry_gmr: + entry_gmr = entry_gmr[0] + else: + entry_gmr = db.execute(gmr_table.insert().values( + obj_pk=comment.media_entry, + model_type="core__media_entries" + )).inserted_primary_key[0] + + # Add the comment link. + db.execute(comment_table.insert().values( + target_id=entry_gmr, + comment_id=comment_gmr, + added=datetime.datetime.utcnow() + )) + + # Add the data to the updated field + db.execute(textcomment_table.update().where( + textcomment_table.c.id == comment.id + ).values( + updated=comment.created + )) + db.commit() + + # Add not null constraint + textcomment_update_column = textcomment_table.columns["updated"] + textcomment_update_column.alter(nullable=False) + + # Remove the unused fields on the TextComment model + comment_media_entry_column = textcomment_table.columns["media_entry"] + comment_media_entry_column.drop() + db.commit() + +@RegisterMigration(42, MIGRATIONS) +def consolidate_reports(db): + """ Consolidates the report tables into just one """ + metadata = MetaData(bind=db.bind) + + report_table = inspect_table(metadata, "core__reports") + comment_report_table = inspect_table(metadata, "core__reports_on_comments") + media_report_table = inspect_table(metadata, "core__reports_on_media") + gmr_table = inspect_table(metadata, "core__generic_model_reference") + + # Add the GMR object field onto the base report table + report_object_id_column = Column( + "object_id", + Integer, + ForeignKey(GenericModelReference_V0.id), + ) + report_object_id_column.create(report_table) + db.commit() + + # Iterate through the reports in the comment table and merge them in. + for comment_report in db.execute(comment_report_table.select()): + # Find a GMR for this if one exists. + crgmr = db.execute(gmr_table.select().where(and_( + gmr_table.c.obj_pk == comment_report.comment_id, + gmr_table.c.model_type == "core__media_comments" + ))).first() + + if crgmr: + crgmr = crgmr[0] + else: + crgmr = db.execute(gmr_table.insert().values( + gmr_table.c.obj_pk == comment_report.comment_id, + gmr_table.c.model_type == "core__media_comments" + )).inserted_primary_key[0] + + # Great now we can save this back onto the (base) report. + db.execute(report_table.update().where( + report_table.c.id == comment_report.id + ).values( + object_id=crgmr + )) + + # Iterate through the Media Reports and do the save as above. + for media_report in db.execute(media_report_table.select()): + # Find Mr. GMR :) + mrgmr = db.execute(gmr_table.select().where(and_( + gmr_table.c.obj_pk == media_report.media_entry_id, + gmr_table.c.model_type == "core__media_entries" + ))).first() + + if mrgmr: + mrgmr = mrgmr[0] + else: + mrgmr = db.execute(gmr_table.insert().values( + obj_pk=media_report.media_entry_id, + model_type="core__media_entries" + )).inserted_primary_key[0] + + # Save back on to the base. + db.execute(report_table.update().where( + report_table.c.id == media_report.id + ).values( + object_id=mrgmr + )) + + db.commit() + + # Add the not null constraint + report_object_id = report_table.columns["object_id"] + report_object_id.alter(nullable=False) + + # Now we can remove the fields we don't need anymore + report_type = report_table.columns["type"] + report_type.drop() + + # Drop both MediaReports and CommentTable. + comment_report_table.drop() + media_report_table.drop() + + # Commit we're done. + db.commit() + +@RegisterMigration(43, MIGRATIONS) +def consolidate_notification(db): + """ Consolidates the notification models into one """ + metadata = MetaData(bind=db.bind) + notification_table = inspect_table(metadata, "core__notifications") + cn_table = inspect_table(metadata, "core__comment_notifications") + cp_table = inspect_table(metadata, "core__processing_notifications") + gmr_table = inspect_table(metadata, "core__generic_model_reference") + + # Add fields needed + notification_object_id_column = Column( + "object_id", + Integer, + ForeignKey(GenericModelReference_V0.id) + ) + notification_object_id_column.create(notification_table) + db.commit() + + # Iterate over comments and move to notification base table. + for comment_notification in db.execute(cn_table.select()): + # Find the GMR. + cngmr = db.execute(gmr_table.select().where(and_( + gmr_table.c.obj_pk == comment_notification.subject_id, + gmr_table.c.model_type == "core__media_comments" + ))).first() + + if cngmr: + cngmr = cngmr[0] + else: + cngmr = db.execute(gmr_table.insert().values( + obj_pk=comment_notification.subject_id, + model_type="core__media_comments" + )).inserted_primary_key[0] + + # Save back on notification + db.execute(notification_table.update().where( + notification_table.c.id == comment_notification.id + ).values( + object_id=cngmr + )) + db.commit() + + # Do the same for processing notifications + for processing_notification in db.execute(cp_table.select()): + cpgmr = db.execute(gmr_table.select().where(and_( + gmr_table.c.obj_pk == processing_notification.subject_id, + gmr_table.c.model_type == "core__processing_notifications" + ))).first() + + if cpgmr: + cpgmr = cpgmr[0] + else: + cpgmr = db.execute(gmr_table.insert().values( + obj_pk=processing_notification.subject_id, + model_type="core__processing_notifications" + )).inserted_primary_key[0] + + db.execute(notification_table.update().where( + notification_table.c.id == processing_notification.id + ).values( + object_id=cpgmr + )) + db.commit() + + # Add the not null constraint + notification_object_id = notification_table.columns["object_id"] + notification_object_id.alter(nullable=False) + + # Now drop the fields we don't need + notification_type_column = notification_table.columns["type"] + notification_type_column.drop() + + # Drop the tables we no longer need + cp_table.drop() + cn_table.drop() + + db.commit() diff --git a/mediagoblin/db/mixin.py b/mediagoblin/db/mixin.py index 4602c709..ecd04874 100644 --- a/mediagoblin/db/mixin.py +++ b/mediagoblin/db/mixin.py @@ -41,6 +41,82 @@ from mediagoblin.tools.text import cleaned_markdown_conversion from mediagoblin.tools.url import slugify from mediagoblin.tools.translate import pass_to_ugettext as _ +class CommentingMixin(object): + """ + Mixin that gives classes methods to get and add the comments on/to it + + This assumes the model has a "comments" class which is a ForeignKey to the + Collection model. This will hold a Collection of comments which are + associated to this model. It also assumes the model has an "actor" + ForeignKey which points to the creator/publisher/etc. of the model. + + NB: This is NOT the mixin for the Comment Model, this is for + other models which support commenting. + """ + + def get_comment_link(self): + # Import here to avoid cyclic imports + from mediagoblin.db.models import Comment, GenericModelReference + + gmr = GenericModelReference.query.filter_by( + obj_pk=self.id, + model_type=self.__tablename__ + ).first() + + if gmr is None: + return None + + link = Comment.query.filter_by(comment_id=gmr.id).first() + return link + + def get_reply_to(self): + link = self.get_comment_link() + if link is None or link.target_id is None: + return None + + return link.target() + + def soft_delete(self, *args, **kwargs): + link = self.get_comment_link() + if link is not None: + link.delete() + super(CommentingMixin, self).soft_delete(*args, **kwargs) + +class GeneratePublicIDMixin(object): + """ + Mixin that ensures that a the public_id field is populated. + + The public_id is the ID that is used in the API, this must be globally + unique and dereferencable. This will be the URL for the API view of the + object. It's used in several places, not only is it used to give out via + the API but it's also vital information stored when a soft_deletion occurs + on the `Graveyard.public_id` field, this is needed to follow the spec which + says we have to be able to provide a shell of an object and return a 410 + (rather than a 404) when a deleted object has been deleted. + + This requires a the urlgen off the request object (`request.urlgen`) to be + provided as it's the ID is a URL. + """ + + def get_public_id(self, urlgen): + # Verify that the class this is on actually has a public_id field... + if "public_id" not in self.__table__.columns.keys(): + raise Exception("Model has no public_id field") + + # Great! the model has a public id, if it's None, let's create one! + if self.public_id is None: + # We need the internal ID for this so ensure we've been saved. + self.save(commit=False) + + # Create the URL + self.public_id = urlgen( + "mediagoblin.api.object", + object_type=self.object_type, + id=str(uuid.uuid4()), + qualified=True + ) + self.save() + return self.public_id class UserMixin(object): object_type = "person" @@ -52,6 +128,7 @@ class UserMixin(object): def url_for_self(self, urlgen, **kwargs): """Generate a URL for this User's home page.""" return urlgen('mediagoblin.user_pages.user_home', + user=self.username, **kwargs) @@ -128,13 +205,13 @@ class GenerateSlugMixin(object): self.slug = slug -class MediaEntryMixin(GenerateSlugMixin): +class MediaEntryMixin(GenerateSlugMixin, GeneratePublicIDMixin): def check_slug_used(self, slug): # import this here due to a cyclic import issue # (db.models -> db.mixin -> db.util -> db.models) from mediagoblin.db.util import check_media_slug_used - return check_media_slug_used(self.uploader, slug, self.id) + return check_media_slug_used(self.actor, slug, self.id) @property def object_type(self): @@ -188,7 +265,7 @@ class MediaEntryMixin(GenerateSlugMixin): Use a slug if we have one, else use our 'id'. """ - uploader = self.get_uploader + uploader = self.get_actor return urlgen( 'mediagoblin.user_pages.media_home', @@ -307,7 +384,7 @@ class MediaEntryMixin(GenerateSlugMixin): return exif_short -class MediaCommentMixin(object): +class TextCommentMixin(GeneratePublicIDMixin): object_type = "comment" @property @@ -319,21 +396,20 @@ class MediaCommentMixin(object): return cleaned_markdown_conversion(self.content) def __unicode__(self): - return u'<{klass} #{id} {author} "{comment}">'.format( + return u'<{klass} #{id} {actor} "{comment}">'.format( klass=self.__class__.__name__, id=self.id, - author=self.get_author, + actor=self.get_actor, comment=self.content) def __repr__(self): - return '<{klass} #{id} {author} "{comment}">'.format( + return '<{klass} #{id} {actor} "{comment}">'.format( klass=self.__class__.__name__, id=self.id, - author=self.get_author, + actor=self.get_actor, comment=self.content) - -class CollectionMixin(GenerateSlugMixin): +class CollectionMixin(GenerateSlugMixin, GeneratePublicIDMixin): object_type = "collection" def check_slug_used(self, slug): @@ -341,7 +417,7 @@ class CollectionMixin(GenerateSlugMixin): # (db.models -> db.mixin -> db.util -> db.models) from mediagoblin.db.util import check_collection_slug_used - return check_collection_slug_used(self.creator, slug, self.id) + return check_collection_slug_used(self.actor, slug, self.id) @property def description_html(self): @@ -361,7 +437,7 @@ class CollectionMixin(GenerateSlugMixin): Use a slug if we have one, else use our 'id'. """ - creator = self.get_creator + creator = self.get_actor return urlgen( 'mediagoblin.user_pages.user_collection', @@ -369,6 +445,28 @@ class CollectionMixin(GenerateSlugMixin): collection=self.slug_or_id, **extra_args) + def add_to_collection(self, obj, content=None, commit=True): + """ Adds an object to the collection """ + # It's here to prevent cyclic imports + from mediagoblin.db.models import CollectionItem + + # Need the ID of this collection for this so check we've got one. + self.save(commit=False) + + # Create the CollectionItem + item = CollectionItem() + item.collection = self.id + item.get_object = obj + + if content is not None: + item.note = content + + self.num_items = self.num_items + 1 + + # Save both! + self.save(commit=commit) + item.save(commit=commit) + return item class CollectionItemMixin(object): @property @@ -379,7 +477,7 @@ class CollectionItemMixin(object): """ return cleaned_markdown_conversion(self.note) -class ActivityMixin(object): +class ActivityMixin(GeneratePublicIDMixin): object_type = "activity" VALID_VERBS = ["add", "author", "create", "delete", "dislike", "favorite", @@ -432,13 +530,12 @@ class ActivityMixin(object): "audio": _("audio"), "person": _("a person"), } - - obj = self.get_object - target = self.get_target + obj = self.object() + target = None if self.target_id is None else self.target() actor = self.get_actor content = verb_to_content.get(self.verb, None) - if content is None or obj is None: + if content is None or self.object is None: return # Decide what to fill the object with @@ -488,7 +585,7 @@ class ActivityMixin(object): "updated": updated.isoformat(), "content": self.content, "url": self.get_url(request), - "object": self.get_object.serialize(request), + "object": self.object().serialize(request), "objectType": self.object_type, "links": { "self": { @@ -503,9 +600,8 @@ class ActivityMixin(object): if self.title: obj["title"] = self.title - target = self.get_target - if target is not None: - obj["target"] = target.serialize(request) + if self.target_id is not None: + obj["target"] = self.target().serialize(request) return obj diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index e8fb17a7..77f8a1b8 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -25,28 +25,129 @@ import datetime from sqlalchemy import Column, Integer, Unicode, UnicodeText, DateTime, \ Boolean, ForeignKey, UniqueConstraint, PrimaryKeyConstraint, \ - SmallInteger, Date -from sqlalchemy.orm import relationship, backref, with_polymorphic, validates + SmallInteger, Date, types +from sqlalchemy.orm import relationship, backref, with_polymorphic, validates, \ + class_mapper from sqlalchemy.orm.collections import attribute_mapped_collection +from sqlalchemy.sql import and_ from sqlalchemy.sql.expression import desc from sqlalchemy.ext.associationproxy import association_proxy from sqlalchemy.util import memoized_property from mediagoblin.db.extratypes import (PathTupleWithSlashes, JSONEncoded, MutationDict) -from mediagoblin.db.base import Base, DictReadAttrProxy +from mediagoblin.db.base import Base, DictReadAttrProxy, FakeCursor from mediagoblin.db.mixin import UserMixin, MediaEntryMixin, \ - MediaCommentMixin, CollectionMixin, CollectionItemMixin, \ - ActivityMixin + CollectionMixin, CollectionItemMixin, ActivityMixin, TextCommentMixin, \ + CommentingMixin from mediagoblin.tools.files import delete_media_files from mediagoblin.tools.common import import_component from mediagoblin.tools.routing import extract_url_arguments import six +from six.moves.urllib.parse import urljoin from pytz import UTC _log = logging.getLogger(__name__) +class GenericModelReference(Base): + """ + Represents a relationship to any model that is defined with a integer pk + """ + __tablename__ = "core__generic_model_reference" + + id = Column(Integer, primary_key=True) + obj_pk = Column(Integer, nullable=False) + + # This will be the tablename of the model + model_type = Column(Unicode, nullable=False) + + # Constrain it so obj_pk and model_type have to be unique + # They should be this order as the index is generated, "model_type" will be + # the major order as it's put first. + __table_args__ = ( + UniqueConstraint("model_type", "obj_pk"), + {}) + + def get_object(self): + # This can happen if it's yet to be saved + if self.model_type is None or self.obj_pk is None: + return None + + model = self._get_model_from_type(self.model_type) + return model.query.filter_by(id=self.obj_pk).first() + + def set_object(self, obj): + model = obj.__class__ + + # Check we've been given a object + if not issubclass(model, Base): + raise ValueError("Only models can be set as using the GMR") + + # Check that the model has an explicit __tablename__ declaration + if getattr(model, "__tablename__", None) is None: + raise ValueError("Models must have __tablename__ attribute") + + # Check that it's not a composite primary key + primary_keys = [key.name for key in class_mapper(model).primary_key] + if len(primary_keys) > 1: + raise ValueError("Models can not have composite primary keys") + + # Check that the field on the model is a an integer field + pk_column = getattr(model, primary_keys[0]) + if not isinstance(pk_column.type, Integer): + raise ValueError("Only models with integer pks can be set") + + if getattr(obj, pk_column.key) is None: + obj.save(commit=False) + + self.obj_pk = getattr(obj, pk_column.key) + self.model_type = obj.__tablename__ + + def _get_model_from_type(self, model_type): + """ Gets a model from a tablename (model type) """ + if getattr(type(self), "_TYPE_MAP", None) is None: + # We want to build on the class (not the instance) a map of all the + # models by the table name (type) for easy lookup, this is done on + # the class so it can be shared between all instances + + # to prevent circular imports do import here + registry = dict(Base._decl_class_registry).values() + self._TYPE_MAP = dict( + ((m.__tablename__, m) for m in registry if hasattr(m, "__tablename__")) + ) + setattr(type(self), "_TYPE_MAP", self._TYPE_MAP) + + return self.__class__._TYPE_MAP[model_type] + + @classmethod + def find_for_obj(cls, obj): + """ Finds a GMR for an object or returns None """ + # Is there one for this already. + model = type(obj) + pk = getattr(obj, "id") + + gmr = cls.query.filter_by( + obj_pk=pk, + model_type=model.__tablename__ + ) + + return gmr.first() + + @classmethod + def find_or_new(cls, obj): + """ Finds an existing GMR or creates a new one for the object """ + gmr = cls.find_for_obj(obj) + + # If there isn't one already create one + if gmr is None: + gmr = cls( + obj_pk=obj.id, + model_type=type(obj).__tablename__ + ) + + return gmr + class Location(Base): """ Represents a physical location """ __tablename__ = "core__locations" @@ -123,50 +224,60 @@ class Location(Base): class User(Base, UserMixin): """ - TODO: We should consider moving some rarely used fields - into some sort of "shadow" table. + Base user that is common amongst LocalUser and RemoteUser. + + This holds all the fields which are common between both the Local and Remote + user models. + + NB: ForeignKeys should reference this User model and NOT the LocalUser or + RemoteUser models. """ __tablename__ = "core__users" id = Column(Integer, primary_key=True) - username = Column(Unicode, nullable=False, unique=True) - # Note: no db uniqueness constraint on email because it's not - # reliable (many email systems case insensitive despite against - # the RFC) and because it would be a mess to implement at this - # point. - email = Column(Unicode, nullable=False) - pw_hash = Column(Unicode) - created = Column(DateTime, nullable=False, default=datetime.datetime.utcnow) - # 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) url = Column(Unicode) - bio = Column(UnicodeText) # ?? - uploaded = Column(Integer, default=0) - upload_limit = Column(Integer) + bio = Column(UnicodeText) + name = Column(Unicode) + + # This is required for the polymorphic inheritance + type = Column(Unicode) + + created = Column(DateTime, nullable=False, default=datetime.datetime.utcnow) + updated = Column(DateTime, nullable=False, default=datetime.datetime.utcnow) + location = Column(Integer, ForeignKey("core__locations.id")) + + # Lazy getters get_location = relationship("Location", lazy="joined") - activity = Column(Integer, ForeignKey("core__activity_intermediators.id")) + __mapper_args__ = { + 'polymorphic_identity': 'user', + 'polymorphic_on': type, + } - ## TODO - # plugin data would be in a separate model + deletion_mode = Base.SOFT_DELETE - def __repr__(self): - return '<{0} #{1} {2} {3} "{4}">'.format( - self.__class__.__name__, - self.id, - 'verified' if self.has_privilege(u'active') else 'non-verified', - 'admin' if self.has_privilege(u'admin') else 'user', - self.username) + def soft_delete(self, *args, **kwargs): + # Find all the Collections and delete those + for collection in Collection.query.filter_by(actor=self.id): + collection.delete(**kwargs) + + # Find all the comments and delete those too + for comment in TextComment.query.filter_by(actor=self.id): + comment.delete(**kwargs) + + # Find all the activities and delete those too + for activity in Activity.query.filter_by(actor=self.id): + activity.delete(**kwargs) + + super(User, self).soft_delete(*args, **kwargs) - def delete(self, **kwargs): + + def delete(self, *args, **kwargs): """Deletes a User and all related entries/comments/files/...""" # Collections get deleted by relationships. - media_entries = MediaEntry.query.filter(MediaEntry.uploader == self.id) + media_entries = MediaEntry.query.filter(MediaEntry.actor == self.id) for media in media_entries: # TODO: Make sure that "MediaEntry.delete()" also deletes # all related files/Comments @@ -178,8 +289,9 @@ class User(Base, UserMixin): clean_orphan_tags(commit=False) # Delete user, pass through commit=False/True in kwargs - super(User, self).delete(**kwargs) - _log.info('Deleted user "{0}" account'.format(self.username)) + username = self.username + super(User, self).delete(*args, **kwargs) + _log.info('Deleted user "{0}" account'.format(username)) def has_privilege(self, privilege, allow_admin=True): """ @@ -212,19 +324,79 @@ class User(Base, UserMixin): """ return UserBan.query.get(self.id) is not None - def serialize(self, request): published = UTC.localize(self.created) + updated = UTC.localize(self.updated) user = { - "id": "acct:{0}@{1}".format(self.username, request.host), "published": published.isoformat(), - "preferredUsername": self.username, - "displayName": "{0}@{1}".format(self.username, request.host), + "updated": updated.isoformat(), "objectType": self.object_type, "pump_io": { "shared": False, "followed": False, }, + } + + if self.bio: + user.update({"summary": self.bio}) + if self.url: + user.update({"url": self.url}) + if self.location: + user.update({"location": self.get_location.serialize(request)}) + + return user + + def unserialize(self, data): + if "summary" in data: + self.bio = data["summary"] + + if "location" in data: + Location.create(data, self) + +class LocalUser(User): + """ This represents a user registered on this instance """ + __tablename__ = "core__local_users" + + id = Column(Integer, ForeignKey("core__users.id"), primary_key=True) + username = Column(Unicode, nullable=False, unique=True) + # Note: no db uniqueness constraint on email because it's not + # reliable (many email systems case insensitive despite against + # the RFC) and because it would be a mess to implement at this + # point. + email = Column(Unicode, nullable=False) + pw_hash = Column(Unicode) + + # 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) + uploaded = Column(Integer, default=0) + upload_limit = Column(Integer) + + __mapper_args__ = { + "polymorphic_identity": "user_local", + } + + ## TODO + # plugin data would be in a separate model + + def __repr__(self): + return '<{0} #{1} {2} {3} "{4}">'.format( + self.__class__.__name__, + self.id, + 'verified' if self.has_privilege(u'active') else 'non-verified', + 'admin' if self.has_privilege(u'admin') else 'user', + self.username) + + def get_public_id(self, host): + return "acct:{0}@{1}".format(self.username, host) + + def serialize(self, request): + user = { + "id": self.get_public_id(request.host), + "preferredUsername": self.username, + "displayName": self.get_public_id(request.host).split(":", 1)[1], "links": { "self": { "href": request.urlgen( @@ -250,21 +422,27 @@ class User(Base, UserMixin): }, } - if self.bio: - user.update({"summary": self.bio}) - if self.url: - user.update({"url": self.url}) - if self.location: - user.update({"location": self.get_location.serialize(request)}) - + user.update(super(LocalUser, self).serialize(request)) return user - def unserialize(self, data): - if "summary" in data: - self.bio = data["summary"] +class RemoteUser(User): + """ User that is on another (remote) instance """ + __tablename__ = "core__remote_users" + + id = Column(Integer, ForeignKey("core__users.id"), primary_key=True) + webfinger = Column(Unicode, unique=True) + + __mapper_args__ = { + 'polymorphic_identity': 'user_remote' + } + + def __repr__(self): + return "<{0} #{1} {2}>".format( + self.__class__.__name__, + self.id, + self.webfinger + ) - if "location" in data: - Location.create(data, self) class Client(Base): """ @@ -300,7 +478,7 @@ class RequestToken(Base): token = Column(Unicode, primary_key=True) secret = Column(Unicode, nullable=False) client = Column(Unicode, ForeignKey(Client.id)) - user = Column(Integer, ForeignKey(User.id), nullable=True) + actor = Column(Integer, ForeignKey(User.id), nullable=True) used = Column(Boolean, default=False) authenticated = Column(Boolean, default=False) verifier = Column(Unicode, nullable=True) @@ -318,7 +496,7 @@ class AccessToken(Base): token = Column(Unicode, nullable=False, primary_key=True) secret = Column(Unicode, nullable=False) - user = Column(Integer, ForeignKey(User.id)) + actor = Column(Integer, ForeignKey(User.id)) request_token = Column(Unicode, ForeignKey(RequestToken.token)) created = Column(DateTime, nullable=False, default=datetime.datetime.utcnow) updated = Column(DateTime, nullable=False, default=datetime.datetime.utcnow) @@ -335,18 +513,19 @@ class NonceTimestamp(Base): nonce = Column(Unicode, nullable=False, primary_key=True) timestamp = Column(DateTime, nullable=False, primary_key=True) -class MediaEntry(Base, MediaEntryMixin): +class MediaEntry(Base, MediaEntryMixin, CommentingMixin): """ TODO: Consider fetching the media_files using join """ __tablename__ = "core__media_entries" id = Column(Integer, primary_key=True) - uploader = Column(Integer, ForeignKey(User.id), nullable=False, index=True) + public_id = Column(Unicode, unique=True, nullable=True) + remote = Column(Boolean, default=False) + + actor = Column(Integer, ForeignKey(User.id), nullable=False, index=True) title = Column(Unicode, nullable=False) slug = Column(Unicode) - created = Column(DateTime, nullable=False, default=datetime.datetime.utcnow, - index=True) description = Column(UnicodeText) # ?? media_type = Column(Unicode, nullable=False) state = Column(Unicode, default=u'unprocessed', nullable=False) @@ -356,6 +535,10 @@ class MediaEntry(Base, MediaEntryMixin): location = Column(Integer, ForeignKey("core__locations.id")) get_location = relationship("Location", lazy="joined") + created = Column(DateTime, nullable=False, default=datetime.datetime.utcnow, + index=True) + updated = Column(DateTime, nullable=False, default=datetime.datetime.utcnow) + fail_error = Column(Unicode) fail_metadata = Column(JSONEncoded) @@ -366,10 +549,12 @@ class MediaEntry(Base, MediaEntryMixin): queued_task_id = Column(Unicode) __table_args__ = ( - UniqueConstraint('uploader', 'slug'), + UniqueConstraint('actor', 'slug'), {}) - get_uploader = relationship(User) + deletion_mode = Base.SOFT_DELETE + + get_actor = relationship(User) media_files_helper = relationship("MediaFile", collection_class=attribute_mapped_collection("name"), @@ -395,28 +580,41 @@ class MediaEntry(Base, MediaEntryMixin): creator=lambda v: MediaTag(name=v["name"], slug=v["slug"]) ) - collections_helper = relationship("CollectionItem", - cascade="all, delete-orphan" - ) - collections = association_proxy("collections_helper", "in_collection") media_metadata = Column(MutationDict.as_mutable(JSONEncoded), default=MutationDict()) - activity = Column(Integer, ForeignKey("core__activity_intermediators.id")) - ## TODO # fail_error + @property + def collections(self): + """ Get any collections that this MediaEntry is in """ + return list(Collection.query.join(Collection.collection_items).join( + CollectionItem.object_helper + ).filter( + and_( + GenericModelReference.model_type == self.__tablename__, + GenericModelReference.obj_pk == self.id + ) + )) + def get_comments(self, ascending=False): - order_col = MediaComment.created - if not ascending: - order_col = desc(order_col) - return self.all_comments.order_by(order_col) + query = Comment.query.join(Comment.target_helper).filter(and_( + GenericModelReference.obj_pk == self.id, + GenericModelReference.model_type == self.__tablename__ + )) + if ascending: + query = query.order_by(Comment.added.asc()) + else: + qury = query.order_by(Comment.added.desc()) + + return FakeCursor(query, lambda c:c.comment()) + def url_to_prev(self, urlgen): """get the next 'newer' entry by this user""" media = MediaEntry.query.filter( - (MediaEntry.uploader == self.uploader) + (MediaEntry.actor == self.actor) & (MediaEntry.state == u'processed') & (MediaEntry.id > self.id)).order_by(MediaEntry.id).first() @@ -426,7 +624,7 @@ class MediaEntry(Base, MediaEntryMixin): def url_to_next(self, urlgen): """get the next 'older' entry by this user""" media = MediaEntry.query.filter( - (MediaEntry.uploader == self.uploader) + (MediaEntry.actor == self.actor) & (MediaEntry.state == u'processed') & (MediaEntry.id < self.id)).order_by(desc(MediaEntry.id)).first() @@ -500,6 +698,13 @@ class MediaEntry(Base, MediaEntryMixin): id=self.id, title=safe_title) + def soft_delete(self, *args, **kwargs): + # Find all of the media comments for this and delete them + for comment in self.get_comments(): + comment.delete(*args, **kwargs) + + super(MediaEntry, self).soft_delete(*args, **kwargs) + def delete(self, del_orphan_tags=True, **kwargs): """Delete MediaEntry and all related files/attachments/comments @@ -517,7 +722,7 @@ class MediaEntry(Base, MediaEntryMixin): except OSError as error: # Returns list of files we failed to delete _log.error('No such files from the user "{1}" to delete: ' - '{0}'.format(str(error), self.get_uploader)) + '{0}'.format(str(error), self.get_actor)) _log.info('Deleted Media entry id "{0}"'.format(self.id)) # Related MediaTag's are automatically cleaned, but we might # want to clean out unused Tag's too. @@ -531,25 +736,20 @@ class MediaEntry(Base, MediaEntryMixin): def serialize(self, request, show_comments=True): """ Unserialize MediaEntry to object """ - href = request.urlgen( - "mediagoblin.api.object", - object_type=self.object_type, - id=self.id, - qualified=True - ) - author = self.get_uploader + author = self.get_actor published = UTC.localize(self.created) - updated = UTC.localize(self.created) + updated = UTC.localize(self.updated) + public_id = self.get_public_id(request.urlgen) context = { - "id": href, + "id": public_id, "author": author.serialize(request), "objectType": self.object_type, "url": self.url_for_self(request.urlgen, qualified=True), "image": { - "url": request.host_url + self.thumb_url[1:], + "url": urljoin(request.host_url, self.thumb_url), }, "fullImage":{ - "url": request.host_url + self.original_url[1:], + "url": urljoin(request.host_url, self.original_url), }, "published": published.isoformat(), "updated": updated.isoformat(), @@ -558,7 +758,7 @@ class MediaEntry(Base, MediaEntryMixin): }, "links": { "self": { - "href": href, + "href": public_id, }, } @@ -621,7 +821,7 @@ class MediaEntry(Base, MediaEntryMixin): self.license = data["license"] if "location" in data: - Licence.create(data["location"], self) + License.create(data["location"], self) return True @@ -738,15 +938,63 @@ class MediaTag(Base): """A dict like view on this object""" return DictReadAttrProxy(self) +class Comment(Base): + """ + Link table between a response and another object that can have replies. + + This acts as a link table between an object and the comments on it, it's + done like this so that you can look up all the comments without knowing + whhich comments are on an object before hand. Any object can be a comment + and more or less any object can accept comments too. + + Important: This is NOT the old MediaComment table. + """ + __tablename__ = "core__comment_links" + + id = Column(Integer, primary_key=True) + + # The GMR to the object the comment is on. + target_id = Column( + Integer, + ForeignKey(GenericModelReference.id), + nullable=False + ) + target_helper = relationship( + GenericModelReference, + foreign_keys=[target_id] + ) + target = association_proxy("target_helper", "get_object", + creator=GenericModelReference.find_or_new) + + # The comment object + comment_id = Column( + Integer, + ForeignKey(GenericModelReference.id), + nullable=False + ) + comment_helper = relationship( + GenericModelReference, + foreign_keys=[comment_id] + ) + comment = association_proxy("comment_helper", "get_object", + creator=GenericModelReference.find_or_new) + + # When it was added + added = Column(DateTime, nullable=False, default=datetime.datetime.utcnow) + -class MediaComment(Base, MediaCommentMixin): +class TextComment(Base, TextCommentMixin, CommentingMixin): + """ + A basic text comment, this is a usually short amount of text and nothing else + """ + # This is a legacy from when Comments where just on MediaEntry objects. __tablename__ = "core__media_comments" id = Column(Integer, primary_key=True) - media_entry = Column( - Integer, ForeignKey(MediaEntry.id), nullable=False, index=True) - author = Column(Integer, ForeignKey(User.id), nullable=False) + public_id = Column(Unicode, unique=True) + actor = Column(Integer, ForeignKey(User.id), nullable=False) created = Column(DateTime, nullable=False, default=datetime.datetime.utcnow) + updated = Column(DateTime, nullable=False, default=datetime.datetime.utcnow) content = Column(UnicodeText, nullable=False) location = Column(Integer, ForeignKey("core__locations.id")) get_location = relationship("Location", lazy="joined") @@ -754,43 +1002,29 @@ class MediaComment(Base, MediaCommentMixin): # Cascade: Comments are owned by their creator. So do the full thing. # lazy=dynamic: People might post a *lot* of comments, # so make the "posted_comments" a query-like thing. - get_author = relationship(User, + get_actor = relationship(User, backref=backref("posted_comments", lazy="dynamic", cascade="all, delete-orphan")) - get_entry = relationship(MediaEntry, - backref=backref("comments", - lazy="dynamic", - cascade="all, delete-orphan")) - - # Cascade: Comments are somewhat owned by their MediaEntry. - # So do the full thing. - # lazy=dynamic: MediaEntries might have many comments, - # so make the "all_comments" a query-like thing. - get_media_entry = relationship(MediaEntry, - backref=backref("all_comments", - lazy="dynamic", - cascade="all, delete-orphan")) - - - activity = Column(Integer, ForeignKey("core__activity_intermediators.id")) + deletion_mode = Base.SOFT_DELETE def serialize(self, request): """ Unserialize to python dictionary for API """ - href = request.urlgen( - "mediagoblin.api.object", - object_type=self.object_type, - id=self.id, - qualified=True - ) - media = MediaEntry.query.filter_by(id=self.media_entry).first() - author = self.get_author + target = self.get_reply_to() + # If this is target just.. give them nothing? + if target is None: + target = {} + else: + target = target.serialize(request, show_comments=False) + + + author = self.get_actor published = UTC.localize(self.created) context = { - "id": href, + "id": self.get_public_id(request.urlgen), "objectType": self.object_type, "content": self.content, - "inReplyTo": media.serialize(request, show_comments=False), + "inReplyTo": target, "author": author.serialize(request), "published": published.isoformat(), "updated": published.isoformat(), @@ -803,64 +1037,101 @@ class MediaComment(Base, MediaCommentMixin): def unserialize(self, data, request): """ Takes API objects and unserializes on existing comment """ + if "content" in data: + self.content = data["content"] + + if "location" in data: + Location.create(data["location"], self) + + # Handle changing the reply ID if "inReplyTo" in data: # Validate that the ID is correct try: - media_id = int(extract_url_arguments( + id = extract_url_arguments( url=data["inReplyTo"]["id"], urlmap=request.app.url_map - )["id"]) + )["id"] except ValueError: - return False + raise False - media = MediaEntry.query.filter_by(id=media_id).first() + public_id = request.urlgen( + "mediagoblin.api.object", + id=id, + object_type=data["inReplyTo"]["objectType"], + qualified=True + ) + + media = MediaEntry.query.filter_by(public_id=public_id).first() if media is None: return False - self.media_entry = media.id - - if "content" in data: - self.content = data["content"] - - if "location" in data: - Location.create(data["location"], self) + # We need an ID for this model. + self.save(commit=False) + # Create the link + link = Comment() + link.target = media + link.comment = self + link.save() + return True +class Collection(Base, CollectionMixin, CommentingMixin): + """A representation of a collection of objects. + This holds a group/collection of objects that could be a user defined album + or their inbox, outbox, followers, etc. These are always ordered and accessable + via the API and web. -class Collection(Base, CollectionMixin): - """An 'album' or 'set' of media by a user. + The collection has a number of types which determine what kind of collection + it is, for example the users inbox will be of `Collection.INBOX_TYPE` that will + be stored on the `Collection.type` field. It's important to set the correct type. On deletion, contained CollectionItems get automatically reaped via SQL cascade""" __tablename__ = "core__collections" id = Column(Integer, primary_key=True) + public_id = Column(Unicode, unique=True) title = Column(Unicode, nullable=False) slug = Column(Unicode) created = Column(DateTime, nullable=False, default=datetime.datetime.utcnow, index=True) + updated = Column(DateTime, nullable=False, default=datetime.datetime.utcnow) description = Column(UnicodeText) - creator = Column(Integer, ForeignKey(User.id), nullable=False) + actor = Column(Integer, ForeignKey(User.id), nullable=False) + num_items = Column(Integer, default=0) + + # There are lots of different special types of collections in the pump.io API + # for example: followers, following, inbox, outbox, etc. See type constants + # below the fields on this model. + type = Column(Unicode, nullable=False) + + # Location location = Column(Integer, ForeignKey("core__locations.id")) get_location = relationship("Location", lazy="joined") - # TODO: No of items in Collection. Badly named, can we migrate to num_items? - items = Column(Integer, default=0) - # Cascade: Collections are owned by their creator. So do the full thing. - get_creator = relationship(User, + get_actor = relationship(User, backref=backref("collections", cascade="all, delete-orphan")) - - activity = Column(Integer, ForeignKey("core__activity_intermediators.id")) - __table_args__ = ( - UniqueConstraint('creator', 'slug'), + UniqueConstraint("actor", "slug"), {}) + deletion_mode = Base.SOFT_DELETE + + # These are the types, It's strongly suggested if new ones are invented they + # are prefixed to ensure they're unique from other types. Any types used in + # the main mediagoblin should be prefixed "core-" + INBOX_TYPE = "core-inbox" + OUTBOX_TYPE = "core-outbox" + FOLLOWER_TYPE = "core-followers" + FOLLOWING_TYPE = "core-following" + COMMENT_TYPE = "core-comments" + USER_DEFINED_TYPE = "core-user-defined" + def get_collection_items(self, ascending=False): #TODO, is this still needed with self.collection_items being available? order_col = CollectionItem.position @@ -871,20 +1142,17 @@ class Collection(Base, CollectionMixin): def __repr__(self): safe_title = self.title.encode('ascii', 'replace') - return '<{classname} #{id}: {title} by {creator}>'.format( + return '<{classname} #{id}: {title} by {actor}>'.format( id=self.id, classname=self.__class__.__name__, - creator=self.creator, + actor=self.actor, title=safe_title) def serialize(self, request): # Get all serialized output in a list - items = [] - for item in self.get_collection_items(): - items.append(item.serialize(request)) - + items = [i.serialize(request) for i in self.get_collection_items()] return { - "totalItems": self.items, + "totalItems": self.num_items, "url": self.url_for_self(request.urlgen, qualified=True), "items": items, } @@ -894,23 +1162,36 @@ class CollectionItem(Base, CollectionItemMixin): __tablename__ = "core__collection_items" id = Column(Integer, primary_key=True) - media_entry = Column( - Integer, ForeignKey(MediaEntry.id), nullable=False, index=True) + collection = Column(Integer, ForeignKey(Collection.id), nullable=False) note = Column(UnicodeText, nullable=True) added = Column(DateTime, nullable=False, default=datetime.datetime.utcnow) position = Column(Integer) - # Cascade: CollectionItems are owned by their Collection. So do the full thing. in_collection = relationship(Collection, backref=backref( "collection_items", cascade="all, delete-orphan")) - get_media_entry = relationship(MediaEntry) + # Link to the object (could be anything. + object_id = Column( + Integer, + ForeignKey(GenericModelReference.id), + nullable=False, + index=True + ) + object_helper = relationship( + GenericModelReference, + foreign_keys=[object_id] + ) + get_object = association_proxy( + "object_helper", + "get_object", + creator=GenericModelReference.find_or_new + ) __table_args__ = ( - UniqueConstraint('collection', 'media_entry'), + UniqueConstraint('collection', 'object_id'), {}) @property @@ -919,14 +1200,15 @@ class CollectionItem(Base, CollectionItemMixin): return DictReadAttrProxy(self) def __repr__(self): - return '<{classname} #{id}: Entry {entry} in {collection}>'.format( + return '<{classname} #{id}: Object {obj} in {collection}>'.format( id=self.id, classname=self.__class__.__name__, collection=self.collection, - entry=self.media_entry) + obj=self.get_object() + ) def serialize(self, request): - return self.get_media_entry.serialize(request) + return self.get_object().serialize(request) class ProcessingMetaData(Base): @@ -979,21 +1261,19 @@ class CommentSubscription(Base): class Notification(Base): __tablename__ = 'core__notifications' id = Column(Integer, primary_key=True) - type = Column(Unicode) - created = Column(DateTime, nullable=False, default=datetime.datetime.utcnow) + object_id = Column(Integer, ForeignKey(GenericModelReference.id)) + object_helper = relationship(GenericModelReference) + obj = association_proxy("object_helper", "get_object", + creator=GenericModelReference.find_or_new) + created = Column(DateTime, nullable=False, default=datetime.datetime.utcnow) user_id = Column(Integer, ForeignKey('core__users.id'), nullable=False, index=True) seen = Column(Boolean, default=lambda: False, index=True) user = relationship( User, - backref=backref('notifications', cascade='all, delete-orphan')) - - __mapper_args__ = { - 'polymorphic_identity': 'notification', - 'polymorphic_on': type - } + backref=backref('notifications', cascade='all, delete-orphan')) def __repr__(self): return '<{klass} #{id}: {user}: {subject} ({seen})>'.format( @@ -1011,42 +1291,9 @@ class Notification(Base): subject=getattr(self, 'subject', None), seen='unseen' if not self.seen else 'seen') - -class CommentNotification(Notification): - __tablename__ = 'core__comment_notifications' - id = Column(Integer, ForeignKey(Notification.id), primary_key=True) - - subject_id = Column(Integer, ForeignKey(MediaComment.id)) - subject = relationship( - MediaComment, - backref=backref('comment_notifications', cascade='all, delete-orphan')) - - __mapper_args__ = { - 'polymorphic_identity': 'comment_notification' - } - - -class ProcessingNotification(Notification): - __tablename__ = 'core__processing_notifications' - - id = Column(Integer, ForeignKey(Notification.id), primary_key=True) - - subject_id = Column(Integer, ForeignKey(MediaEntry.id)) - subject = relationship( - MediaEntry, - backref=backref('processing_notifications', - cascade='all, delete-orphan')) - - __mapper_args__ = { - 'polymorphic_identity': 'processing_notification' - } - -# the with_polymorphic call has been moved to the bottom above MODELS -# this is because it causes conflicts with relationship calls. - -class ReportBase(Base): +class Report(Base): """ - This is the basic report object which the other reports are based off of. + Represents a report that someone might file against Media, Comments, etc. :keyword reporter_id Holds the id of the user who created the report, as an Integer column. @@ -1059,8 +1306,6 @@ class ReportBase(Base): an Integer column. :keyword created Holds a datetime column of when the re- -port was filed. - :keyword discriminator This column distinguishes between the - different types of reports. :keyword resolver_id Holds the id of the moderator/admin who resolved the report. :keyword resolved Holds the DateTime object which descri- @@ -1069,8 +1314,11 @@ class ReportBase(Base): resolver's reasons for resolving the report this way. Some of this is auto-generated + :keyword object_id Holds the ID of the GenericModelReference + which points to the reported object. """ __tablename__ = 'core__reports' + id = Column(Integer, primary_key=True) reporter_id = Column(Integer, ForeignKey(User.id), nullable=False) reporter = relationship( @@ -1078,7 +1326,7 @@ class ReportBase(Base): backref=backref("reports_filed_by", lazy="dynamic", cascade="all, delete-orphan"), - primaryjoin="User.id==ReportBase.reporter_id") + primaryjoin="User.id==Report.reporter_id") report_content = Column(UnicodeText) reported_user_id = Column(Integer, ForeignKey(User.id), nullable=False) reported_user = relationship( @@ -1086,70 +1334,42 @@ class ReportBase(Base): backref=backref("reports_filed_on", lazy="dynamic", cascade="all, delete-orphan"), - primaryjoin="User.id==ReportBase.reported_user_id") + primaryjoin="User.id==Report.reported_user_id") created = Column(DateTime, nullable=False, default=datetime.datetime.utcnow) - discriminator = Column('type', Unicode(50)) resolver_id = Column(Integer, ForeignKey(User.id)) resolver = relationship( User, backref=backref("reports_resolved_by", lazy="dynamic", cascade="all, delete-orphan"), - primaryjoin="User.id==ReportBase.resolver_id") + primaryjoin="User.id==Report.resolver_id") resolved = Column(DateTime) result = Column(UnicodeText) - __mapper_args__ = {'polymorphic_on': discriminator} + + object_id = Column(Integer, ForeignKey(GenericModelReference.id), nullable=False) + object_helper = relationship(GenericModelReference) + obj = association_proxy("object_helper", "get_object", + creator=GenericModelReference.find_or_new) + + def is_archived_report(self): + return self.resolved is not None def is_comment_report(self): - return self.discriminator=='comment_report' + if self.object_id is None: + return False + return isinstance(self.obj(), TextComment) def is_media_entry_report(self): - return self.discriminator=='media_report' - - def is_archived_report(self): - return self.resolved is not None + if self.object_id is None: + return False + return isinstance(self.obj(), MediaEntry) def archive(self,resolver_id, resolved, result): self.resolver_id = resolver_id self.resolved = resolved self.result = result - -class CommentReport(ReportBase): - """ - Reports that have been filed on comments. - :keyword comment_id Holds the integer value of the reported - comment's ID - """ - __tablename__ = 'core__reports_on_comments' - __mapper_args__ = {'polymorphic_identity': 'comment_report'} - - id = Column('id',Integer, ForeignKey('core__reports.id'), - primary_key=True) - comment_id = Column(Integer, ForeignKey(MediaComment.id), nullable=True) - comment = relationship( - MediaComment, backref=backref("reports_filed_on", - lazy="dynamic")) - - -class MediaReport(ReportBase): - """ - Reports that have been filed on media entries - :keyword media_entry_id Holds the integer value of the reported - media entry's ID - """ - __tablename__ = 'core__reports_on_media' - __mapper_args__ = {'polymorphic_identity': 'media_report'} - - id = Column('id',Integer, ForeignKey('core__reports.id'), - primary_key=True) - media_entry_id = Column(Integer, ForeignKey(MediaEntry.id), nullable=True) - media_entry = relationship( - MediaEntry, - backref=backref("reports_filed_on", - lazy="dynamic")) - class UserBan(Base): """ Holds the information on a specific user's ban-state. As long as one of @@ -1235,6 +1455,8 @@ class Generator(Base): updated = Column(DateTime, default=datetime.datetime.utcnow) object_type = Column(Unicode, nullable=False) + deletion_mode = Base.SOFT_DELETE + def __repr__(self): return "<{klass} {name}>".format( klass=self.__class__.__name__, @@ -1262,62 +1484,6 @@ class Generator(Base): if "displayName" in data: self.name = data["displayName"] - -class ActivityIntermediator(Base): - """ - This is used so that objects/targets can have a foreign key back to this - object and activities can a foreign key to this object. This objects to be - used multiple times for the activity object or target and also allows for - different types of objects to be used as an Activity. - """ - __tablename__ = "core__activity_intermediators" - - id = Column(Integer, primary_key=True) - type = Column(Unicode, nullable=False) - - TYPES = { - "user": User, - "media": MediaEntry, - "comment": MediaComment, - "collection": Collection, - } - - def _find_model(self, obj): - """ Finds the model for a given object """ - for key, model in self.TYPES.items(): - if isinstance(obj, model): - return key, model - - return None, None - - def set(self, obj): - """ This sets itself as the activity """ - key, model = self._find_model(obj) - if key is None: - raise ValueError("Invalid type of object given") - - self.type = key - - # We need to populate the self.id so we need to save but, we don't - # want to save this AI in the database (yet) so commit=False. - self.save(commit=False) - obj.activity = self.id - obj.save() - - def get(self): - """ Finds the object for an activity """ - if self.type is None: - return None - - model = self.TYPES[self.type] - return model.query.filter_by(activity=self.id).first() - - @validates("type") - def validate_type(self, key, value): - """ Validate that the type set is a valid type """ - assert value in self.TYPES - return value - class Activity(Base, ActivityMixin): """ This holds all the metadata about an activity such as uploading an image, @@ -1326,29 +1492,39 @@ class Activity(Base, ActivityMixin): __tablename__ = "core__activities" id = Column(Integer, primary_key=True) + public_id = Column(Unicode, unique=True) actor = Column(Integer, ForeignKey("core__users.id"), nullable=False) published = Column(DateTime, nullable=False, default=datetime.datetime.utcnow) updated = Column(DateTime, nullable=False, default=datetime.datetime.utcnow) + verb = Column(Unicode, nullable=False) content = Column(Unicode, nullable=True) title = Column(Unicode, nullable=True) generator = Column(Integer, ForeignKey("core__generators.id"), nullable=True) - object = Column(Integer, - ForeignKey("core__activity_intermediators.id"), - nullable=False) - target = Column(Integer, - ForeignKey("core__activity_intermediators.id"), - nullable=True) + + # Create the generic foreign keys for the object + object_id = Column(Integer, ForeignKey(GenericModelReference.id), nullable=False) + object_helper = relationship(GenericModelReference, foreign_keys=[object_id]) + object = association_proxy("object_helper", "get_object", + creator=GenericModelReference.find_or_new) + + # Create the generic foreign Key for the target + target_id = Column(Integer, ForeignKey(GenericModelReference.id), nullable=True) + target_helper = relationship(GenericModelReference, foreign_keys=[target_id]) + target = association_proxy("target_helper", "get_object", + creator=GenericModelReference.find_or_new) get_actor = relationship(User, backref=backref("activities", cascade="all, delete-orphan")) get_generator = relationship(Generator) + deletion_mode = Base.SOFT_DELETE + def __repr__(self): if self.content is None: return "<{klass} verb:{verb}>".format( @@ -1361,62 +1537,54 @@ class Activity(Base, ActivityMixin): content=self.content ) - @property - def get_object(self): - if self.object is None: - return None - - ai = ActivityIntermediator.query.filter_by(id=self.object).first() - return ai.get() - - def set_object(self, obj): - self.object = self._set_model(obj) - - @property - def get_target(self): - if self.target is None: - return None + def save(self, set_updated=True, *args, **kwargs): + if set_updated: + self.updated = datetime.datetime.now() + super(Activity, self).save(*args, **kwargs) - ai = ActivityIntermediator.query.filter_by(id=self.target).first() - return ai.get() +class Graveyard(Base): + """ Where models come to die """ + __tablename__ = "core__graveyard" - def set_target(self, obj): - self.target = self._set_model(obj) + id = Column(Integer, primary_key=True) + public_id = Column(Unicode, nullable=True, unique=True) - def _set_model(self, obj): - # Firstly can we set obj - if not hasattr(obj, "activity"): - raise ValueError( - "{0!r} is unable to be set on activity".format(obj)) + deleted = Column(DateTime, nullable=False, default=datetime.datetime.utcnow) + object_type = Column(Unicode, nullable=False) - if obj.activity is None: - # We need to create a new AI - ai = ActivityIntermediator() - ai.set(obj) - ai.save() - return ai.id + # This could either be a deleted actor or a real actor, this must be + # nullable as it we shouldn't have it set for deleted actor + actor_id = Column(Integer, ForeignKey(GenericModelReference.id)) + actor_helper = relationship(GenericModelReference) + actor = association_proxy("actor_helper", "get_object", + creator=GenericModelReference.find_or_new) - # Okay we should have an existing AI - return ActivityIntermediator.query.filter_by(id=obj.activity).first().id + def __repr__(self): + return "<{klass} deleted {obj_type}>".format( + klass=type(self).__name__, + obj_type=self.object_type + ) - def save(self, set_updated=True, *args, **kwargs): - if set_updated: - self.updated = datetime.datetime.now() - super(Activity, self).save(*args, **kwargs) + def serialize(self, request): + deleted = UTC.localize(self.deleted).isoformat() + context = { + "id": self.public_id, + "objectType": self.object_type, + "published": deleted, + "updated": deleted, + "deleted": deleted, + } -with_polymorphic( - Notification, - [ProcessingNotification, CommentNotification]) + if self.actor_id is not None: + context["actor"] = self.actor().serialize(request) + return context MODELS = [ - User, MediaEntry, Tag, MediaTag, MediaComment, Collection, CollectionItem, - MediaFile, FileKeynames, MediaAttachmentFile, ProcessingMetaData, - Notification, CommentNotification, ProcessingNotification, Client, - CommentSubscription, ReportBase, CommentReport, MediaReport, UserBan, - Privilege, PrivilegeUserAssociation, - RequestToken, AccessToken, NonceTimestamp, - Activity, ActivityIntermediator, Generator, - Location] + LocalUser, RemoteUser, User, MediaEntry, Tag, MediaTag, Comment, TextComment, + Collection, CollectionItem, MediaFile, FileKeynames, MediaAttachmentFile, + ProcessingMetaData, Notification, Client, CommentSubscription, Report, + UserBan, Privilege, PrivilegeUserAssociation, RequestToken, AccessToken, + NonceTimestamp, Activity, Generator, Location, GenericModelReference, Graveyard] """ Foundations are the default rows that are created immediately after the tables diff --git a/mediagoblin/db/util.py b/mediagoblin/db/util.py index 7c026691..57e6b942 100644 --- a/mediagoblin/db/util.py +++ b/mediagoblin/db/util.py @@ -37,7 +37,7 @@ def atomic_update(table, query_dict, update_values): def check_media_slug_used(uploader_id, slug, ignore_m_id): - query = MediaEntry.query.filter_by(uploader=uploader_id, slug=slug) + query = MediaEntry.query.filter_by(actor=uploader_id, slug=slug) if ignore_m_id is not None: query = query.filter(MediaEntry.id != ignore_m_id) does_exist = query.first() is not None @@ -67,7 +67,7 @@ def clean_orphan_tags(commit=True): def check_collection_slug_used(creator_id, slug, ignore_c_id): - filt = (Collection.creator == creator_id) \ + filt = (Collection.actor == creator_id) \ & (Collection.slug == slug) if ignore_c_id is not None: filt = filt & (Collection.id != ignore_c_id) diff --git a/mediagoblin/decorators.py b/mediagoblin/decorators.py index b5ec0ce8..a2c49bcc 100644 --- a/mediagoblin/decorators.py +++ b/mediagoblin/decorators.py @@ -23,7 +23,8 @@ from six.moves.urllib.parse import urljoin from mediagoblin import mg_globals as mgg from mediagoblin import messages -from mediagoblin.db.models import MediaEntry, User, MediaComment, AccessToken +from mediagoblin.db.models import MediaEntry, LocalUser, TextComment, \ + AccessToken, Comment from mediagoblin.tools.response import ( redirect, render_404, render_user_banned, json_response) @@ -106,12 +107,12 @@ def user_has_privilege(privilege_name, allow_admin=True): def active_user_from_url(controller): - """Retrieve User() from <user> URL pattern and pass in as url_user=... + """Retrieve LocalUser() from <user> URL pattern and pass in as url_user=... Returns a 404 if no such active user has been found""" @wraps(controller) def wrapper(request, *args, **kwargs): - user = User.query.filter_by(username=request.matchdict['user']).first() + user = LocalUser.query.filter_by(username=request.matchdict['user']).first() if user is None: return render_404(request) @@ -126,7 +127,7 @@ def user_may_delete_media(controller): """ @wraps(controller) def wrapper(request, *args, **kwargs): - uploader_id = kwargs['media'].uploader + uploader_id = kwargs['media'].actor if not (request.user.has_privilege(u'admin') or request.user.id == uploader_id): raise Forbidden() @@ -142,7 +143,7 @@ def user_may_alter_collection(controller): """ @wraps(controller) def wrapper(request, *args, **kwargs): - creator_id = request.db.User.query.filter_by( + creator_id = request.db.LocalUser.query.filter_by( username=request.matchdict['user']).first().id if not (request.user.has_privilege(u'admin') or request.user.id == creator_id): @@ -177,7 +178,7 @@ def get_user_media_entry(controller): """ @wraps(controller) def wrapper(request, *args, **kwargs): - user = User.query.filter_by(username=request.matchdict['user']).first() + user = LocalUser.query.filter_by(username=request.matchdict['user']).first() if not user: raise NotFound() @@ -192,7 +193,7 @@ def get_user_media_entry(controller): media = MediaEntry.query.filter_by( id=int(media_slug[3:]), state=u'processed', - uploader=user.id).first() + actor=user.id).first() except ValueError: raise NotFound() else: @@ -200,7 +201,7 @@ def get_user_media_entry(controller): media = MediaEntry.query.filter_by( slug=media_slug, state=u'processed', - uploader=user.id).first() + actor=user.id).first() if not media: # Didn't find anything? Okay, 404. @@ -217,7 +218,7 @@ def get_user_collection(controller): """ @wraps(controller) def wrapper(request, *args, **kwargs): - user = request.db.User.query.filter_by( + user = request.db.LocalUser.query.filter_by( username=request.matchdict['user']).first() if not user: @@ -225,7 +226,7 @@ def get_user_collection(controller): collection = request.db.Collection.query.filter_by( slug=request.matchdict['collection'], - creator=user.id).first() + actor=user.id).first() # Still no collection? Okay, 404. if not collection: @@ -242,7 +243,7 @@ def get_user_collection_item(controller): """ @wraps(controller) def wrapper(request, *args, **kwargs): - user = request.db.User.query.filter_by( + user = request.db.LocalUser.query.filter_by( username=request.matchdict['user']).first() if not user: @@ -274,7 +275,7 @@ def get_media_entry_by_id(controller): return render_404(request) given_username = request.matchdict.get('user') - if given_username and (given_username != media.get_uploader.username): + if given_username and (given_username != media.get_actor.username): return render_404(request) return controller(request, media=media, *args, **kwargs) @@ -325,11 +326,11 @@ def allow_reporting(controller): def get_optional_media_comment_by_id(controller): """ - Pass in a MediaComment based off of a url component. Because of this decor- - -ator's use in filing Media or Comment Reports, it has two valid outcomes. + Pass in a Comment based off of a url component. Because of this decor- + -ator's use in filing 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 + the Comment 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. @@ -339,8 +340,9 @@ def get_optional_media_comment_by_id(controller): @wraps(controller) def wrapper(request, *args, **kwargs): if 'comment' in request.matchdict: - comment = MediaComment.query.filter_by( - id=request.matchdict['comment']).first() + comment = Comment.query.filter_by( + id=request.matchdict['comment'] + ).first() if comment is None: return render_404(request) @@ -407,7 +409,7 @@ def oauth_required(controller): request_validator = GMGRequestValidator() resource_endpoint = ResourceEndpoint(request_validator) valid, r = resource_endpoint.validate_protected_resource_request( - uri=request.base_url, + uri=request.url, http_method=request.method, body=request.data, headers=dict(request.headers), @@ -421,8 +423,8 @@ def oauth_required(controller): token = authorization[u"oauth_token"] request.access_token = AccessToken.query.filter_by(token=token).first() if request.access_token is not None and request.user is None: - user_id = request.access_token.user - request.user = User.query.filter_by(id=user_id).first() + user_id = request.access_token.actor + request.user = LocalUser.query.filter_by(id=user_id).first() return controller(request, *args, **kwargs) diff --git a/mediagoblin/edit/forms.py b/mediagoblin/edit/forms.py index cf5056cf..83e83c3c 100644 --- a/mediagoblin/edit/forms.py +++ b/mediagoblin/edit/forms.py @@ -24,6 +24,18 @@ from mediagoblin.tools.metadata import DEFAULT_SCHEMA, DEFAULT_CHECKER from mediagoblin.auth.tools import normalize_user_or_email_field +class WebsiteField(wtforms.StringField): + """A field that expects a website URL but adds http:// if not provided.""" + def process_formdata(self, valuelist): + if valuelist: + data = valuelist[0] + if not data.startswith((u'http://', u'https://')): + data = u'http://' + data + self.data = data + else: + super(WebsiteField, self).process_formdata(valuelist) + + class EditForm(wtforms.Form): title = wtforms.StringField( _('Title'), @@ -49,6 +61,7 @@ class EditForm(wtforms.Form): [wtforms.validators.Optional(),], choices=licenses_as_choices()) + class EditProfileForm(wtforms.Form): bio = wtforms.TextAreaField( _('Bio'), @@ -56,13 +69,16 @@ class EditProfileForm(wtforms.Form): description=_("""You can use <a href="http://daringfireball.net/projects/markdown/basics"> Markdown</a> for formatting.""")) - url = wtforms.StringField( + url = WebsiteField( _('Website'), [wtforms.validators.Optional(), - wtforms.validators.URL(message=_("This address contains errors"))]) + wtforms.validators.URL(message=_("This address contains errors"))], + description=_("www.example.com, http://www.example.com or " + "https://www.example.com")) location = wtforms.StringField(_('Hometown')) + class EditAccountForm(wtforms.Form): wants_comment_notification = wtforms.BooleanField( description=_("Email me when others comment on my media")) @@ -126,6 +142,7 @@ class ChangeEmailForm(wtforms.Form): description=_( "Enter your password to prove you own this account.")) + class MetaDataValidator(object): """ Custom validator which runs form data in a MetaDataForm through a jsonschema @@ -152,10 +169,12 @@ class MetaDataValidator(object): raise wtforms.validators.ValidationError( errors.pop()) + class MetaDataForm(wtforms.Form): identifier = wtforms.StringField(_(u'Identifier'),[MetaDataValidator()]) value = wtforms.StringField(_(u'Value')) + class EditMetaDataForm(wtforms.Form): media_metadata = wtforms.FieldList( wtforms.FormField(MetaDataForm, ""), diff --git a/mediagoblin/edit/lib.py b/mediagoblin/edit/lib.py index 6acebc96..3f52376a 100644 --- a/mediagoblin/edit/lib.py +++ b/mediagoblin/edit/lib.py @@ -17,7 +17,7 @@ def may_edit_media(request, media): """Check, if the request's user may edit the media details""" - if media.uploader == request.user.id: + if media.actor == request.user.id: return True if request.user.has_privilege(u'admin'): return True diff --git a/mediagoblin/edit/views.py b/mediagoblin/edit/views.py index 8cee1cc0..45cc0fb9 100644 --- a/mediagoblin/edit/views.py +++ b/mediagoblin/edit/views.py @@ -47,7 +47,7 @@ from mediagoblin.tools.text import ( convert_to_tag_list_of_dicts, media_tags_as_string) from mediagoblin.tools.url import slugify from mediagoblin.db.util import check_media_slug_used, check_collection_slug_used -from mediagoblin.db.models import User, Client, AccessToken, Location +from mediagoblin.db.models import User, LocalUser, Client, AccessToken, Location import mimetypes @@ -73,7 +73,7 @@ def edit_media(request, media): # Make sure there isn't already a MediaEntry with such a slug # and userid. slug = slugify(form.slug.data) - slug_used = check_media_slug_used(media.uploader, slug, media.id) + slug_used = check_media_slug_used(media.actor, slug, media.id) if slug_used: form.slug.errors.append( @@ -293,7 +293,7 @@ def deauthorize_applications(request): _("Application has been deauthorized") ) - access_tokens = AccessToken.query.filter_by(user=request.user.id) + access_tokens = AccessToken.query.filter_by(actor=request.user.id) applications = [(a.get_requesttoken, a) for a in access_tokens] return render_to_response( @@ -314,7 +314,8 @@ def delete_account(request): request.session.delete() # Delete user account and all related media files etc.... - request.user.delete() + user = User.query.filter(User.id==user.id).first() + user.delete() # We should send a message that the user has been deleted # successfully. But we just deleted the session, so we @@ -349,12 +350,12 @@ def edit_collection(request, collection): if request.method == 'POST' and form.validate(): # Make sure there isn't already a Collection with such a slug # and userid. - slug_used = check_collection_slug_used(collection.creator, + slug_used = check_collection_slug_used(collection.actor, form.slug.data, collection.id) # Make sure there isn't already a Collection with this title existing_collection = request.db.Collection.query.filter_by( - creator=request.user.id, + actor=request.user.id, title=form.title.data).first() if existing_collection and existing_collection.id != collection.id: @@ -375,7 +376,7 @@ def edit_collection(request, collection): return redirect_obj(request, collection) if request.user.has_privilege(u'admin') \ - and collection.creator != request.user.id \ + and collection.actor != request.user.id \ and request.method != 'POST': messages.add_message( request, messages.WARNING, @@ -444,8 +445,9 @@ def change_email(request): if request.method == 'POST' and form.validate(): new_email = form.new_email.data - users_with_email = User.query.filter_by( - email=new_email).count() + users_with_email = User.query.filter( + LocalUser.email==new_email + ).count() if users_with_email: form.new_email.errors.append( diff --git a/mediagoblin/gmg_commands/addmedia.py b/mediagoblin/gmg_commands/addmedia.py index 2aa8f96a..8cbfc806 100644 --- a/mediagoblin/gmg_commands/addmedia.py +++ b/mediagoblin/gmg_commands/addmedia.py @@ -20,6 +20,7 @@ import os import six +from mediagoblin.db.models import LocalUser from mediagoblin.gmg_commands import util as commands_util from mediagoblin.submit.lib import ( submit_media, get_upload_file_limits, @@ -70,11 +71,13 @@ def addmedia(args): app = commands_util.setup_app(args) # get the user - user = app.db.User.query.filter_by(username=args.username.lower()).first() + user = app.db.LocalUser.query.filter( + LocalUser.username==args.username.lower() + ).first() if user is None: print("Sorry, no user by username '%s'" % args.username) return - + # check for the file, if it exists... filename = os.path.split(args.filename)[-1] abs_filename = os.path.abspath(args.filename) diff --git a/mediagoblin/gmg_commands/batchaddmedia.py b/mediagoblin/gmg_commands/batchaddmedia.py index 5a47d698..2ad7e39e 100644 --- a/mediagoblin/gmg_commands/batchaddmedia.py +++ b/mediagoblin/gmg_commands/batchaddmedia.py @@ -25,6 +25,7 @@ import six from six.moves.urllib.parse import urlparse +from mediagoblin.db.models import LocalUser from mediagoblin.gmg_commands import util as commands_util from mediagoblin.submit.lib import ( submit_media, get_upload_file_limits, @@ -64,7 +65,9 @@ def batchaddmedia(args): files_uploaded, files_attempted = 0, 0 # get the user - user = app.db.User.query.filter_by(username=args.username.lower()).first() + user = app.db.LocalUser.query.filter( + LocalUser.username==args.username.lower() + ).first() if user is None: print(_(u"Sorry, no user by username '{username}' exists".format( username=args.username))) @@ -210,4 +213,3 @@ def parse_csv_file(file_contents): objects_dict[media_id] = (line_dict) return objects_dict - diff --git a/mediagoblin/gmg_commands/users.py b/mediagoblin/gmg_commands/users.py index ad22c169..d1a8b72d 100644 --- a/mediagoblin/gmg_commands/users.py +++ b/mediagoblin/gmg_commands/users.py @@ -16,8 +16,11 @@ from __future__ import print_function +import sys + import six +from mediagoblin.db.models import LocalUser from mediagoblin.gmg_commands import util as commands_util from mediagoblin import auth from mediagoblin import mg_globals @@ -44,16 +47,17 @@ def adduser(args): db = mg_globals.database users_with_username = \ - db.User.query.filter_by( - username=args.username.lower() + db.LocalUser.query.filter( + LocalUser.username==args.username.lower() ).count() if users_with_username: print(u'Sorry, a user with that name already exists.') + sys.exit(1) else: # Create the user - entry = db.User() + entry = db.LocalUser() entry.username = six.text_type(args.username.lower()) entry.email = six.text_type(args.email) entry.pw_hash = auth.gen_password_hash(args.password) @@ -70,13 +74,14 @@ def adduser(args): entry.all_privileges = default_privileges entry.save() - print(u"User created (and email marked as verified)") + print(u"User created (and email marked as verified).") def makeadmin_parser_setup(subparser): subparser.add_argument( 'username', - help="Username to give admin level") + help="Username to give admin level", + type=six.text_type) def makeadmin(args): @@ -84,23 +89,26 @@ def makeadmin(args): db = mg_globals.database - user = db.User.query.filter_by( - username=six.text_type(args.username.lower())).one() + user = db.LocalUser.query.filter( + LocalUser.username==args.username.lower() + ).first() if user: user.all_privileges.append( db.Privilege.query.filter( db.Privilege.privilege_name==u'admin').one() ) user.save() - print(u'The user is now Admin') + print(u'The user %s is now an admin.' % args.username) else: - print(u'The user doesn\'t exist') + print(u'The user %s doesn\'t exist.' % args.username) + sys.exit(1) def changepw_parser_setup(subparser): subparser.add_argument( 'username', - help="Username used to login") + help="Username used to login", + type=six.text_type) subparser.add_argument( 'password', help="Your NEW supersecret word to login") @@ -111,14 +119,16 @@ def changepw(args): db = mg_globals.database - user = db.User.query.filter_by( - username=six.text_type(args.username.lower())).one() + user = db.LocalUser.query.filter( + LocalUser.username==args.username.lower() + ).first() if user: user.pw_hash = auth.gen_password_hash(args.password) user.save() - print(u'Password successfully changed') + print(u'Password successfully changed for user %s.' % args.username) else: - print(u'The user doesn\'t exist') + print(u'The user %s doesn\'t exist.' % args.username) + sys.exit(1) def deleteuser_parser_setup(subparser): @@ -133,9 +143,12 @@ def deleteuser(args): db = mg_globals.database - user = db.User.query.filter_by(username=args.username.lower()).first() + user = db.LocalUser.query.filter( + LocalUser.username==args.username.lower() + ).first() if user: user.delete() - print('The user %s has been deleted' % args.username) + print('The user %s has been deleted.' % args.username) else: - print('The user %s doesn\'t exist' % args.username) + print('The user %s doesn\'t exist.' % args.username) + sys.exit(1) diff --git a/mediagoblin/i18n/ar/mediagoblin.po b/mediagoblin/i18n/ar/mediagoblin.po index ead2e570..48697111 100644 --- a/mediagoblin/i18n/ar/mediagoblin.po +++ b/mediagoblin/i18n/ar/mediagoblin.po @@ -372,11 +372,11 @@ msgstr "" #: mediagoblin/meddleware/csrf.py:134 msgid "" "CSRF cookie not present. This is most likely the result of a cookie " -"blocker or somesuch.<br/>Make sure to permit the settings of cookies for " +"blocker or somesuch. Make sure to permit the setting of cookies for " "this domain." msgstr "" "CSRF كوكيز غير موجودة, وهذا من الممكن ان يكون نتيجة لمانع الكوكيز او شئ من " -"هذا القبيل.<br/>تأكد من أنك قمت بالسماح لخصائص الكوكيز لهذا الميدان." +"هذا القبيل. تأكد من أنك قمت بالسماح لخصائص الكوكيز لهذا الميدان." #: mediagoblin/media_types/__init__.py:79 #: mediagoblin/media_types/__init__.py:101 diff --git a/mediagoblin/i18n/bg/mediagoblin.po b/mediagoblin/i18n/bg/mediagoblin.po index db792584..54162af6 100644 --- a/mediagoblin/i18n/bg/mediagoblin.po +++ b/mediagoblin/i18n/bg/mediagoblin.po @@ -470,7 +470,7 @@ msgstr "" #: mediagoblin/meddleware/csrf.py:134 msgid "" "CSRF cookie not present. This is most likely the result of a cookie blocker " -"or somesuch.<br/>Make sure to permit the settings of cookies for this " +"or somesuch. Make sure to permit the setting of cookies for this " "domain." msgstr "" diff --git a/mediagoblin/i18n/ca/mediagoblin.po b/mediagoblin/i18n/ca/mediagoblin.po index 640a993b..adc5d5e8 100644 --- a/mediagoblin/i18n/ca/mediagoblin.po +++ b/mediagoblin/i18n/ca/mediagoblin.po @@ -490,9 +490,9 @@ msgstr "" #: mediagoblin/meddleware/csrf.py:134 msgid "" "CSRF cookie not present. This is most likely the result of a cookie blocker " -"or somesuch.<br/>Make sure to permit the settings of cookies for this " +"or somesuch. Make sure to permit the setting of cookies for this " "domain." -msgstr "No s'ha trobat la galeta CSRF. Potser ha estat blocada.<br/>Assegureu-vos de permetre les galetes d'aquest domini." +msgstr "No s'ha trobat la galeta CSRF. Potser ha estat blocada. Assegureu-vos de permetre les galetes d'aquest domini." #: mediagoblin/media_types/__init__.py:79 #: mediagoblin/media_types/__init__.py:101 diff --git a/mediagoblin/i18n/ca@valencia/mediagoblin.po b/mediagoblin/i18n/ca@valencia/mediagoblin.po index e967794c..5534be0e 100644 --- a/mediagoblin/i18n/ca@valencia/mediagoblin.po +++ b/mediagoblin/i18n/ca@valencia/mediagoblin.po @@ -470,7 +470,7 @@ msgstr "" #: mediagoblin/meddleware/csrf.py:134 msgid "" "CSRF cookie not present. This is most likely the result of a cookie " -"blocker or somesuch.<br/>Make sure to permit the settings of cookies for " +"blocker or somesuch. Make sure to permit the setting of cookies for " "this domain." msgstr "" diff --git a/mediagoblin/i18n/cs/mediagoblin.po b/mediagoblin/i18n/cs/mediagoblin.po index 5f1e81c9..77c4a7bd 100644 --- a/mediagoblin/i18n/cs/mediagoblin.po +++ b/mediagoblin/i18n/cs/mediagoblin.po @@ -474,9 +474,9 @@ msgstr "{files_uploaded} z celkového počtu {files_attempted} souborů úspěš #: mediagoblin/meddleware/csrf.py:134 msgid "" "CSRF cookie not present. This is most likely the result of a cookie blocker " -"or somesuch.<br/>Make sure to permit the settings of cookies for this " +"or somesuch. Make sure to permit the setting of cookies for this " "domain." -msgstr "CSRF cookie není dostupné. Nejspíš je to důsledek blokování cookies v prohlížeči, nebo něco podobného.<br/>Ujistěte se, že je nastavování cookies povoleno pro tuto doménu." +msgstr "CSRF cookie není dostupné. Nejspíš je to důsledek blokování cookies v prohlížeči, nebo něco podobného. Ujistěte se, že je nastavování cookies povoleno pro tuto doménu." #: mediagoblin/media_types/__init__.py:79 #: mediagoblin/media_types/__init__.py:101 diff --git a/mediagoblin/i18n/cy/mediagoblin.po b/mediagoblin/i18n/cy/mediagoblin.po index 15da9e11..c13c51d9 100644 --- a/mediagoblin/i18n/cy/mediagoblin.po +++ b/mediagoblin/i18n/cy/mediagoblin.po @@ -358,7 +358,7 @@ msgstr "" #: mediagoblin/meddleware/csrf.py:134 msgid "" "CSRF cookie not present. This is most likely the result of a cookie " -"blocker or somesuch.<br/>Make sure to permit the settings of cookies for " +"blocker or somesuch. Make sure to permit the setting of cookies for " "this domain." msgstr "" diff --git a/mediagoblin/i18n/da/mediagoblin.po b/mediagoblin/i18n/da/mediagoblin.po index a59089dc..0104342f 100644 --- a/mediagoblin/i18n/da/mediagoblin.po +++ b/mediagoblin/i18n/da/mediagoblin.po @@ -474,9 +474,9 @@ msgstr "{files_uploaded} ud af {files_attempted} filer blev korrekt uploadet." #: mediagoblin/meddleware/csrf.py:134 msgid "" "CSRF cookie not present. This is most likely the result of a cookie blocker " -"or somesuch.<br/>Make sure to permit the settings of cookies for this " +"or somesuch. Make sure to permit the setting of cookies for this " "domain." -msgstr "CSRF-cookie er ikke til stede. Dette skyldes højst sandsynligt en blokering af cookie eller lignende.<br/>Sørg for at tillade, at der angives cookie'er for dette domæne." +msgstr "CSRF-cookie er ikke til stede. Dette skyldes højst sandsynligt en blokering af cookie eller lignende. Sørg for at tillade, at der angives cookie'er for dette domæne." #: mediagoblin/media_types/__init__.py:79 #: mediagoblin/media_types/__init__.py:101 diff --git a/mediagoblin/i18n/de/mediagoblin.po b/mediagoblin/i18n/de/mediagoblin.po index ef3d0633..10640702 100644 --- a/mediagoblin/i18n/de/mediagoblin.po +++ b/mediagoblin/i18n/de/mediagoblin.po @@ -496,9 +496,9 @@ msgstr "{files_uploaded} aus {files_attempted} Dateien wurden erfolgreich hochge #: mediagoblin/meddleware/csrf.py:134 msgid "" "CSRF cookie not present. This is most likely the result of a cookie blocker " -"or somesuch.<br/>Make sure to permit the settings of cookies for this " +"or somesuch. Make sure to permit the setting of cookies for this " "domain." -msgstr "Das CSRF cookie ist nicht vorhanden. Das liegt vermutlich an einem Cookie-Blocker oder ähnlichem.<br/>Bitte stelle sicher, dass Cookies von dieser Domäne erlaubt sind." +msgstr "Das CSRF cookie ist nicht vorhanden. Das liegt vermutlich an einem Cookie-Blocker oder ähnlichem. Bitte stelle sicher, dass Cookies von dieser Domäne erlaubt sind." #: mediagoblin/media_types/__init__.py:79 #: mediagoblin/media_types/__init__.py:101 @@ -847,7 +847,7 @@ msgstr "" "solltest du eine \"hervorheben\" Schaltfläche sehen. Klicke auf diese " "Schaltfläche und das Medium wird als Primary Feature oben auf der Seite " "angezeigt werden. Alle anderen hervorgehobenen Medien bleiben hervorgehoben, " -"werden aber nach hinten rücken. <br/><br/>\n" +"werden aber nach hinten rücken. \n" "\n" #: mediagoblin/plugins/archivalook/templates/archivalook/feature.html:70 diff --git a/mediagoblin/i18n/dz/mediagoblin.po b/mediagoblin/i18n/dz/mediagoblin.po index d93ea081..d40f3125 100644 --- a/mediagoblin/i18n/dz/mediagoblin.po +++ b/mediagoblin/i18n/dz/mediagoblin.po @@ -359,7 +359,7 @@ msgstr "" #: mediagoblin/meddleware/csrf.py:134 msgid "" "CSRF cookie not present. This is most likely the result of a cookie " -"blocker or somesuch.<br/>Make sure to permit the settings of cookies for " +"blocker or somesuch. Make sure to permit the setting of cookies for " "this domain." msgstr "" diff --git a/mediagoblin/i18n/el/mediagoblin.po b/mediagoblin/i18n/el/mediagoblin.po index a0dffcae..146f9594 100644 --- a/mediagoblin/i18n/el/mediagoblin.po +++ b/mediagoblin/i18n/el/mediagoblin.po @@ -470,7 +470,7 @@ msgstr "" #: mediagoblin/meddleware/csrf.py:134 msgid "" "CSRF cookie not present. This is most likely the result of a cookie blocker " -"or somesuch.<br/>Make sure to permit the settings of cookies for this " +"or somesuch. Make sure to permit the setting of cookies for this " "domain." msgstr "" diff --git a/mediagoblin/i18n/eo/mediagoblin.po b/mediagoblin/i18n/eo/mediagoblin.po index 7d8fe393..430677a1 100644 --- a/mediagoblin/i18n/eo/mediagoblin.po +++ b/mediagoblin/i18n/eo/mediagoblin.po @@ -473,7 +473,7 @@ msgstr "" #: mediagoblin/meddleware/csrf.py:134 msgid "" "CSRF cookie not present. This is most likely the result of a cookie blocker " -"or somesuch.<br/>Make sure to permit the settings of cookies for this " +"or somesuch. Make sure to permit the setting of cookies for this " "domain." msgstr "" diff --git a/mediagoblin/i18n/es/mediagoblin.po b/mediagoblin/i18n/es/mediagoblin.po index 8fd27b62..27f5425d 100644 --- a/mediagoblin/i18n/es/mediagoblin.po +++ b/mediagoblin/i18n/es/mediagoblin.po @@ -491,11 +491,11 @@ msgstr "{files_uploaded} de {files_attempted} archivos enviados correctamente" #: mediagoblin/meddleware/csrf.py:134 msgid "" "CSRF cookie not present. This is most likely the result of a cookie blocker " -"or somesuch.<br/>Make sure to permit the settings of cookies for this " +"or somesuch. Make sure to permit the setting of cookies for this " "domain." msgstr "" "No se encuentra la cookie CSRF. Esto suele ser debido a un bloqueador de " -"cookies o similar.<br/>Por favor asegúrate de permitir las cookies para este " +"cookies o similar. Por favor asegúrate de permitir las cookies para este " "dominio." #: mediagoblin/media_types/__init__.py:79 diff --git a/mediagoblin/i18n/fa/mediagoblin.po b/mediagoblin/i18n/fa/mediagoblin.po index d768b00e..27b53f24 100644 --- a/mediagoblin/i18n/fa/mediagoblin.po +++ b/mediagoblin/i18n/fa/mediagoblin.po @@ -471,7 +471,7 @@ msgstr "" #: mediagoblin/meddleware/csrf.py:134 msgid "" "CSRF cookie not present. This is most likely the result of a cookie blocker " -"or somesuch.<br/>Make sure to permit the settings of cookies for this " +"or somesuch. Make sure to permit the setting of cookies for this " "domain." msgstr "" diff --git a/mediagoblin/i18n/fa_IR/mediagoblin.po b/mediagoblin/i18n/fa_IR/mediagoblin.po index 896499c3..151711f5 100644 --- a/mediagoblin/i18n/fa_IR/mediagoblin.po +++ b/mediagoblin/i18n/fa_IR/mediagoblin.po @@ -469,7 +469,7 @@ msgstr "" #: mediagoblin/meddleware/csrf.py:134 msgid "" "CSRF cookie not present. This is most likely the result of a cookie blocker " -"or somesuch.<br/>Make sure to permit the settings of cookies for this " +"or somesuch. Make sure to permit the setting of cookies for this " "domain." msgstr "" diff --git a/mediagoblin/i18n/fi/mediagoblin.po b/mediagoblin/i18n/fi/mediagoblin.po index 12e02675..c2e96175 100644 --- a/mediagoblin/i18n/fi/mediagoblin.po +++ b/mediagoblin/i18n/fi/mediagoblin.po @@ -469,7 +469,7 @@ msgstr "" #: mediagoblin/meddleware/csrf.py:134 msgid "" "CSRF cookie not present. This is most likely the result of a cookie blocker " -"or somesuch.<br/>Make sure to permit the settings of cookies for this " +"or somesuch. Make sure to permit the setting of cookies for this " "domain." msgstr "" diff --git a/mediagoblin/i18n/fr/mediagoblin.po b/mediagoblin/i18n/fr/mediagoblin.po index cd2238e3..bdfebf7d 100644 --- a/mediagoblin/i18n/fr/mediagoblin.po +++ b/mediagoblin/i18n/fr/mediagoblin.po @@ -492,9 +492,9 @@ msgstr "{files_uploaded} fichiers sur {files_attempted} ont été soumis correct #: mediagoblin/meddleware/csrf.py:134 msgid "" "CSRF cookie not present. This is most likely the result of a cookie blocker " -"or somesuch.<br/>Make sure to permit the settings of cookies for this " +"or somesuch. Make sure to permit the setting of cookies for this " "domain." -msgstr "Cookie CSRF non présent. Cela est vraisemblablement l’œuvre d'un bloqueur de cookies ou assimilé.<br/>Veuillez vous assurer d'autoriser les cookies pour ce domaine." +msgstr "Cookie CSRF non présent. Cela est vraisemblablement l’œuvre d'un bloqueur de cookies ou assimilé. Veuillez vous assurer d'autoriser les cookies pour ce domaine." #: mediagoblin/media_types/__init__.py:79 #: mediagoblin/media_types/__init__.py:101 diff --git a/mediagoblin/i18n/gl/mediagoblin.po b/mediagoblin/i18n/gl/mediagoblin.po index 59073acd..4617c8b4 100644 --- a/mediagoblin/i18n/gl/mediagoblin.po +++ b/mediagoblin/i18n/gl/mediagoblin.po @@ -470,9 +470,9 @@ msgstr "Ficheiros enviados correctamente: {files_uploaded}/{files_attempted}." #: mediagoblin/meddleware/csrf.py:134 msgid "" "CSRF cookie not present. This is most likely the result of a cookie blocker " -"or somesuch.<br/>Make sure to permit the settings of cookies for this " +"or somesuch. Make sure to permit the setting of cookies for this " "domain." -msgstr "Falta a cookie «CSRF». A causa máis probábel do problema é un bloqueador de cookies ou algo similar.<br/>Asegúrese de permitir definir cookies a este sitio web." +msgstr "Falta a cookie «CSRF». A causa máis probábel do problema é un bloqueador de cookies ou algo similar. Asegúrese de permitir definir cookies a este sitio web." #: mediagoblin/media_types/__init__.py:79 #: mediagoblin/media_types/__init__.py:101 @@ -1139,7 +1139,7 @@ msgid "" " <strong>Public</strong> - The client can't make confidential\n" " requests to the GNU MediaGoblin instance (e.g. client-side\n" " JavaScript client)." -msgstr "<strong>Confidencial</strong>: O cliente pode realizar solicitudes á instancia de GNU MediaGoblin que non poderán ser interceptadas polo «axente do usuario» (por exemplo, o cliente do lado do servidor).<br/> <strong>Público</strong>: O cliente non pode realizar solicitudes confidenciais á instancia de GNU MediaGoblin (por exemplo, o cliente en JavaScript do lado do cliente)." +msgstr "<strong>Confidencial</strong>: O cliente pode realizar solicitudes á instancia de GNU MediaGoblin que non poderán ser interceptadas polo «axente do usuario» (por exemplo, o cliente do lado do servidor). <strong>Público</strong>: O cliente non pode realizar solicitudes confidenciais á instancia de GNU MediaGoblin (por exemplo, o cliente en JavaScript do lado do cliente)." #: mediagoblin/plugins/oauth/forms.py:52 msgid "Redirect URI" diff --git a/mediagoblin/i18n/he/mediagoblin.po b/mediagoblin/i18n/he/mediagoblin.po index 7df3d153..f8ee50f5 100644 --- a/mediagoblin/i18n/he/mediagoblin.po +++ b/mediagoblin/i18n/he/mediagoblin.po @@ -476,9 +476,9 @@ msgstr "{files_uploaded} מתוך {files_attempted} קבצים נשלחו בהצ #: mediagoblin/meddleware/csrf.py:134 msgid "" "CSRF cookie not present. This is most likely the result of a cookie blocker " -"or somesuch.<br/>Make sure to permit the settings of cookies for this " +"or somesuch. Make sure to permit the setting of cookies for this " "domain." -msgstr "עוגיית CSRF לא נמצאת. זה נובע כנראה משום חוסם עוגייה.<br/>ודא להבטיח התרה של עוגיות עבור תחום זה." +msgstr "עוגיית CSRF לא נמצאת. זה נובע כנראה משום חוסם עוגייה. ודא להבטיח התרה של עוגיות עבור תחום זה." #: mediagoblin/media_types/__init__.py:79 #: mediagoblin/media_types/__init__.py:101 diff --git a/mediagoblin/i18n/hi/mediagoblin.po b/mediagoblin/i18n/hi/mediagoblin.po index 432d8365..ac84ba02 100644 --- a/mediagoblin/i18n/hi/mediagoblin.po +++ b/mediagoblin/i18n/hi/mediagoblin.po @@ -471,7 +471,7 @@ msgstr "" #: mediagoblin/meddleware/csrf.py:134 msgid "" "CSRF cookie not present. This is most likely the result of a cookie " -"blocker or somesuch.<br/>Make sure to permit the settings of cookies for " +"blocker or somesuch. Make sure to permit the setting of cookies for " "this domain." msgstr "" diff --git a/mediagoblin/i18n/hu/mediagoblin.po b/mediagoblin/i18n/hu/mediagoblin.po index 42984b53..5aa7f6c6 100644 --- a/mediagoblin/i18n/hu/mediagoblin.po +++ b/mediagoblin/i18n/hu/mediagoblin.po @@ -469,7 +469,7 @@ msgstr "" #: mediagoblin/meddleware/csrf.py:134 msgid "" "CSRF cookie not present. This is most likely the result of a cookie blocker " -"or somesuch.<br/>Make sure to permit the settings of cookies for this " +"or somesuch. Make sure to permit the setting of cookies for this " "domain." msgstr "" diff --git a/mediagoblin/i18n/ia/mediagoblin.po b/mediagoblin/i18n/ia/mediagoblin.po index 2a774d1d..d5013ff7 100644 --- a/mediagoblin/i18n/ia/mediagoblin.po +++ b/mediagoblin/i18n/ia/mediagoblin.po @@ -471,7 +471,7 @@ msgstr "" #: mediagoblin/meddleware/csrf.py:134 msgid "" "CSRF cookie not present. This is most likely the result of a cookie blocker " -"or somesuch.<br/>Make sure to permit the settings of cookies for this " +"or somesuch. Make sure to permit the setting of cookies for this " "domain." msgstr "" diff --git a/mediagoblin/i18n/is_IS/mediagoblin.po b/mediagoblin/i18n/is_IS/mediagoblin.po index 8a897aab..3b8b3992 100644 --- a/mediagoblin/i18n/is_IS/mediagoblin.po +++ b/mediagoblin/i18n/is_IS/mediagoblin.po @@ -473,9 +473,9 @@ msgstr "" #: mediagoblin/meddleware/csrf.py:134 msgid "" "CSRF cookie not present. This is most likely the result of a cookie blocker " -"or somesuch.<br/>Make sure to permit the settings of cookies for this " +"or somesuch. Make sure to permit the setting of cookies for this " "domain." -msgstr "CSRF smákaka ekki til staðar. Þetta er líklegast orsakað af smákökugildru eða einhverju þess háttar.<br/>Athugaðu hvort þú leyfir ekki alveg örugglega smákökur fyrir þetta lén." +msgstr "CSRF smákaka ekki til staðar. Þetta er líklegast orsakað af smákökugildru eða einhverju þess háttar. Athugaðu hvort þú leyfir ekki alveg örugglega smákökur fyrir þetta lén." #: mediagoblin/media_types/__init__.py:79 #: mediagoblin/media_types/__init__.py:101 diff --git a/mediagoblin/i18n/it/mediagoblin.po b/mediagoblin/i18n/it/mediagoblin.po index afc889b2..44d78c9e 100644 --- a/mediagoblin/i18n/it/mediagoblin.po +++ b/mediagoblin/i18n/it/mediagoblin.po @@ -476,9 +476,9 @@ msgstr "" #: mediagoblin/meddleware/csrf.py:134 msgid "" "CSRF cookie not present. This is most likely the result of a cookie blocker " -"or somesuch.<br/>Make sure to permit the settings of cookies for this " +"or somesuch. Make sure to permit the setting of cookies for this " "domain." -msgstr "Cookie CSRF non presente. Questo è dovuto a un plugin che blocca i cookie o a qualcosa del genere.<br/>Assicurati di permettere le impostazioni dei cookie per questo dominio." +msgstr "Cookie CSRF non presente. Questo è dovuto a un plugin che blocca i cookie o a qualcosa del genere. Assicurati di permettere le impostazioni dei cookie per questo dominio." #: mediagoblin/media_types/__init__.py:79 #: mediagoblin/media_types/__init__.py:101 diff --git a/mediagoblin/i18n/ja/mediagoblin.po b/mediagoblin/i18n/ja/mediagoblin.po index 32f8714b..b3885815 100644 --- a/mediagoblin/i18n/ja/mediagoblin.po +++ b/mediagoblin/i18n/ja/mediagoblin.po @@ -472,7 +472,7 @@ msgstr "" #: mediagoblin/meddleware/csrf.py:134 msgid "" "CSRF cookie not present. This is most likely the result of a cookie blocker " -"or somesuch.<br/>Make sure to permit the settings of cookies for this " +"or somesuch. Make sure to permit the setting of cookies for this " "domain." msgstr "" diff --git a/mediagoblin/i18n/jbo/mediagoblin.po b/mediagoblin/i18n/jbo/mediagoblin.po index eda96b55..c13f8f41 100644 --- a/mediagoblin/i18n/jbo/mediagoblin.po +++ b/mediagoblin/i18n/jbo/mediagoblin.po @@ -470,7 +470,7 @@ msgstr "" #: mediagoblin/meddleware/csrf.py:134 msgid "" "CSRF cookie not present. This is most likely the result of a cookie blocker " -"or somesuch.<br/>Make sure to permit the settings of cookies for this " +"or somesuch. Make sure to permit the setting of cookies for this " "domain." msgstr "" diff --git a/mediagoblin/i18n/ko_KR/mediagoblin.po b/mediagoblin/i18n/ko_KR/mediagoblin.po index c49bbfaf..f565c627 100644 --- a/mediagoblin/i18n/ko_KR/mediagoblin.po +++ b/mediagoblin/i18n/ko_KR/mediagoblin.po @@ -470,7 +470,7 @@ msgstr "" #: mediagoblin/meddleware/csrf.py:134 msgid "" "CSRF cookie not present. This is most likely the result of a cookie blocker " -"or somesuch.<br/>Make sure to permit the settings of cookies for this " +"or somesuch. Make sure to permit the setting of cookies for this " "domain." msgstr "" diff --git a/mediagoblin/i18n/nb_NO/mediagoblin.po b/mediagoblin/i18n/nb_NO/mediagoblin.po index 60e096b1..4711355a 100644 --- a/mediagoblin/i18n/nb_NO/mediagoblin.po +++ b/mediagoblin/i18n/nb_NO/mediagoblin.po @@ -478,7 +478,7 @@ msgstr "" #: mediagoblin/meddleware/csrf.py:134 msgid "" "CSRF cookie not present. This is most likely the result of a cookie blocker " -"or somesuch.<br/>Make sure to permit the settings of cookies for this " +"or somesuch. Make sure to permit the setting of cookies for this " "domain." msgstr "" diff --git a/mediagoblin/i18n/nl/mediagoblin.po b/mediagoblin/i18n/nl/mediagoblin.po index b28e4dd1..a3ee52f7 100644 --- a/mediagoblin/i18n/nl/mediagoblin.po +++ b/mediagoblin/i18n/nl/mediagoblin.po @@ -475,9 +475,9 @@ msgstr "{files_uploaded} van {files_attempted} bestanden succesvol aangemeld" #: mediagoblin/meddleware/csrf.py:134 msgid "" "CSRF cookie not present. This is most likely the result of a cookie blocker " -"or somesuch.<br/>Make sure to permit the settings of cookies for this " +"or somesuch. Make sure to permit the setting of cookies for this " "domain." -msgstr "CSRF cookie niet aanwezig. Dit komt waarschijnlijk door een cookie blocker of iets dergelijks.<br/>Zorg ervoor dat je browser cookies van dit domein wel accepteert.." +msgstr "CSRF cookie niet aanwezig. Dit komt waarschijnlijk door een cookie blocker of iets dergelijks. Zorg ervoor dat je browser cookies van dit domein wel accepteert.." #: mediagoblin/media_types/__init__.py:79 #: mediagoblin/media_types/__init__.py:101 diff --git a/mediagoblin/i18n/nn_NO/mediagoblin.po b/mediagoblin/i18n/nn_NO/mediagoblin.po index 375b05fd..57f42aae 100644 --- a/mediagoblin/i18n/nn_NO/mediagoblin.po +++ b/mediagoblin/i18n/nn_NO/mediagoblin.po @@ -471,9 +471,9 @@ msgstr "{files_uploaded} out of {files_attempted} files successfully submitted" #: mediagoblin/meddleware/csrf.py:134 msgid "" "CSRF cookie not present. This is most likely the result of a cookie blocker " -"or somesuch.<br/>Make sure to permit the settings of cookies for this " +"or somesuch. Make sure to permit the setting of cookies for this " "domain." -msgstr "Finn ikkje CSRF-cookien. Dette er truleg grunna ein cookie-blokkar.<br/>\nSjå til at du tillet cookies for dette domenet." +msgstr "Finn ikkje CSRF-cookien. Dette er truleg grunna ein cookie-blokkar. \nSjå til at du tillet cookies for dette domenet." #: mediagoblin/media_types/__init__.py:79 #: mediagoblin/media_types/__init__.py:101 diff --git a/mediagoblin/i18n/pl/mediagoblin.po b/mediagoblin/i18n/pl/mediagoblin.po index 44a4d990..4e657c1e 100644 --- a/mediagoblin/i18n/pl/mediagoblin.po +++ b/mediagoblin/i18n/pl/mediagoblin.po @@ -472,9 +472,9 @@ msgstr "" #: mediagoblin/meddleware/csrf.py:134 msgid "" "CSRF cookie not present. This is most likely the result of a cookie blocker " -"or somesuch.<br/>Make sure to permit the settings of cookies for this " +"or somesuch. Make sure to permit the setting of cookies for this " "domain." -msgstr "Ciasteczko CSFR nie jest dostępne. Najprawdopodobniej stosujesz jakąś formę blokowania ciasteczek.<br/>Upewnij się, że nasz serwer może zakładać ciasteczka w twojej przeglądarce." +msgstr "Ciasteczko CSFR nie jest dostępne. Najprawdopodobniej stosujesz jakąś formę blokowania ciasteczek. Upewnij się, że nasz serwer może zakładać ciasteczka w twojej przeglądarce." #: mediagoblin/media_types/__init__.py:79 #: mediagoblin/media_types/__init__.py:101 diff --git a/mediagoblin/i18n/pt_BR/mediagoblin.po b/mediagoblin/i18n/pt_BR/mediagoblin.po index f24fae04..517b309c 100644 --- a/mediagoblin/i18n/pt_BR/mediagoblin.po +++ b/mediagoblin/i18n/pt_BR/mediagoblin.po @@ -480,9 +480,9 @@ msgstr "{files_uploaded} de {files_attempted} arquivos enviados com sucesso" #: mediagoblin/meddleware/csrf.py:134 msgid "" "CSRF cookie not present. This is most likely the result of a cookie blocker " -"or somesuch.<br/>Make sure to permit the settings of cookies for this " +"or somesuch. Make sure to permit the setting of cookies for this " "domain." -msgstr "Cookie CSFR não está presente. Isso é provavelmente o resultado de um bloqueador de cookies ou algo do tipo.<br/>Tenha certeza de autorizar este domínio a configurar cookies." +msgstr "Cookie CSFR não está presente. Isso é provavelmente o resultado de um bloqueador de cookies ou algo do tipo. Tenha certeza de autorizar este domínio a configurar cookies." #: mediagoblin/media_types/__init__.py:79 #: mediagoblin/media_types/__init__.py:101 diff --git a/mediagoblin/i18n/ro/mediagoblin.po b/mediagoblin/i18n/ro/mediagoblin.po index cac4f0bb..ef00189b 100644 --- a/mediagoblin/i18n/ro/mediagoblin.po +++ b/mediagoblin/i18n/ro/mediagoblin.po @@ -471,9 +471,9 @@ msgstr "" #: mediagoblin/meddleware/csrf.py:134 msgid "" "CSRF cookie not present. This is most likely the result of a cookie blocker " -"or somesuch.<br/>Make sure to permit the settings of cookies for this " +"or somesuch. Make sure to permit the setting of cookies for this " "domain." -msgstr "Lipsește cookie-ul CSRF. Probabil că blocați cookie-urile.<br/>Asigurați-vă că există permisiunea setării cookie-urilor pentru acest domeniu." +msgstr "Lipsește cookie-ul CSRF. Probabil că blocați cookie-urile. Asigurați-vă că există permisiunea setării cookie-urilor pentru acest domeniu." #: mediagoblin/media_types/__init__.py:79 #: mediagoblin/media_types/__init__.py:101 diff --git a/mediagoblin/i18n/ru/mediagoblin.po b/mediagoblin/i18n/ru/mediagoblin.po index 08011123..db31f8a2 100644 --- a/mediagoblin/i18n/ru/mediagoblin.po +++ b/mediagoblin/i18n/ru/mediagoblin.po @@ -472,7 +472,7 @@ msgstr "" #: mediagoblin/meddleware/csrf.py:134 msgid "" "CSRF cookie not present. This is most likely the result of a cookie blocker " -"or somesuch.<br/>Make sure to permit the settings of cookies for this " +"or somesuch. Make sure to permit the setting of cookies for this " "domain." msgstr "" diff --git a/mediagoblin/i18n/sk/mediagoblin.po b/mediagoblin/i18n/sk/mediagoblin.po index 0b5992e9..94fc5f28 100644 --- a/mediagoblin/i18n/sk/mediagoblin.po +++ b/mediagoblin/i18n/sk/mediagoblin.po @@ -475,9 +475,9 @@ msgstr "" #: mediagoblin/meddleware/csrf.py:134 msgid "" "CSRF cookie not present. This is most likely the result of a cookie blocker " -"or somesuch.<br/>Make sure to permit the settings of cookies for this " +"or somesuch. Make sure to permit the setting of cookies for this " "domain." -msgstr "CSRF \"cookie\" neprítomný. Toto vidíš najskôr ako výsledok blokovania \"cookie\" súborov a pod.<br/>Uisti sa, že máš povolené ukladanie \"cookies\" pre danú doménu." +msgstr "CSRF \"cookie\" neprítomný. Toto vidíš najskôr ako výsledok blokovania \"cookie\" súborov a pod. Uisti sa, že máš povolené ukladanie \"cookies\" pre danú doménu." #: mediagoblin/media_types/__init__.py:79 #: mediagoblin/media_types/__init__.py:101 diff --git a/mediagoblin/i18n/sl/mediagoblin.po b/mediagoblin/i18n/sl/mediagoblin.po index 84918610..8599cc7d 100644 --- a/mediagoblin/i18n/sl/mediagoblin.po +++ b/mediagoblin/i18n/sl/mediagoblin.po @@ -359,7 +359,7 @@ msgstr "" #: mediagoblin/meddleware/csrf.py:134 msgid "" "CSRF cookie not present. This is most likely the result of a cookie " -"blocker or somesuch.<br/>Make sure to permit the settings of cookies for " +"blocker or somesuch. Make sure to permit the setting of cookies for " "this domain." msgstr "" diff --git a/mediagoblin/i18n/sq/mediagoblin.po b/mediagoblin/i18n/sq/mediagoblin.po index ebcd6e16..40ecd9b1 100644 --- a/mediagoblin/i18n/sq/mediagoblin.po +++ b/mediagoblin/i18n/sq/mediagoblin.po @@ -503,8 +503,8 @@ msgid "{files_uploaded} out of {files_attempted} files successfully submitted" msgstr "U parashtruan me sukses {files_uploaded} nga {files_attempted} gjithsej" #: mediagoblin/meddleware/csrf.py:134 -msgid "CSRF cookie not present. This is most likely the result of a cookie blocker or somesuch.<br/>Make sure to permit the settings of cookies for this domain." -msgstr "Pa cookie CSRF të pranishme. Ka shumë të ngjarë që të jetë punë e një bllokuesi cookie-sh ose të tillë.<br/>Sigurohuni që të lejoni depozitim cookie-sh për këtë përkatësi." +msgid "CSRF cookie not present. This is most likely the result of a cookie blocker or somesuch. Make sure to permit the setting of cookies for this domain." +msgstr "Pa cookie CSRF të pranishme. Ka shumë të ngjarë që të jetë punë e një bllokuesi cookie-sh ose të tillë. Sigurohuni që të lejoni depozitim cookie-sh për këtë përkatësi." #: mediagoblin/media_types/__init__.py:79 #: mediagoblin/media_types/__init__.py:101 diff --git a/mediagoblin/i18n/sr/mediagoblin.po b/mediagoblin/i18n/sr/mediagoblin.po index 24e32ec3..71b06ab5 100644 --- a/mediagoblin/i18n/sr/mediagoblin.po +++ b/mediagoblin/i18n/sr/mediagoblin.po @@ -469,7 +469,7 @@ msgstr "" #: mediagoblin/meddleware/csrf.py:134 msgid "" "CSRF cookie not present. This is most likely the result of a cookie blocker " -"or somesuch.<br/>Make sure to permit the settings of cookies for this " +"or somesuch. Make sure to permit the setting of cookies for this " "domain." msgstr "" diff --git a/mediagoblin/i18n/sv/mediagoblin.po b/mediagoblin/i18n/sv/mediagoblin.po index 764d475e..d016a527 100644 --- a/mediagoblin/i18n/sv/mediagoblin.po +++ b/mediagoblin/i18n/sv/mediagoblin.po @@ -482,7 +482,7 @@ msgstr "" #: mediagoblin/meddleware/csrf.py:134 msgid "" "CSRF cookie not present. This is most likely the result of a cookie blocker " -"or somesuch.<br/>Make sure to permit the settings of cookies for this " +"or somesuch. Make sure to permit the setting of cookies for this " "domain." msgstr "" diff --git a/mediagoblin/i18n/te/mediagoblin.po b/mediagoblin/i18n/te/mediagoblin.po index 4ece607f..c16c6b23 100644 --- a/mediagoblin/i18n/te/mediagoblin.po +++ b/mediagoblin/i18n/te/mediagoblin.po @@ -470,7 +470,7 @@ msgstr "" #: mediagoblin/meddleware/csrf.py:134 msgid "" "CSRF cookie not present. This is most likely the result of a cookie blocker " -"or somesuch.<br/>Make sure to permit the settings of cookies for this " +"or somesuch. Make sure to permit the setting of cookies for this " "domain." msgstr "" diff --git a/mediagoblin/i18n/templates/en/mediagoblin.po b/mediagoblin/i18n/templates/en/mediagoblin.po index 057daccd..9c3b9c49 100644 --- a/mediagoblin/i18n/templates/en/mediagoblin.po +++ b/mediagoblin/i18n/templates/en/mediagoblin.po @@ -8,8 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2015-07-22 11:25+0300\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"POT-Creation-Date: 2015-07-22 11:22+0300\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: LANGUAGE <LL@li.org>\n" "MIME-Version: 1.0\n" @@ -173,7 +172,7 @@ msgstr "" msgid "a person" msgstr "" -#: mediagoblin/db/mixin.py:450 mediagoblin/db/mixin.py:459 +#: mediagoblin/db/mixin.py:449 mediagoblin/db/mixin.py:458 msgid "an object" msgstr "" @@ -470,7 +469,7 @@ msgstr "" #: mediagoblin/meddleware/csrf.py:134 msgid "" "CSRF cookie not present. This is most likely the result of a cookie " -"blocker or somesuch.<br/>Make sure to permit the settings of cookies for " +"blocker or somesuch. Make sure to permit the setting of cookies for " "this domain." msgstr "" diff --git a/mediagoblin/i18n/templates/mediagoblin.pot b/mediagoblin/i18n/templates/mediagoblin.pot index e0e19625..ced12a28 100644 --- a/mediagoblin/i18n/templates/mediagoblin.pot +++ b/mediagoblin/i18n/templates/mediagoblin.pot @@ -470,7 +470,7 @@ msgstr "" #: mediagoblin/meddleware/csrf.py:134 msgid "" "CSRF cookie not present. This is most likely the result of a cookie " -"blocker or somesuch.<br/>Make sure to permit the settings of cookies for " +"blocker or somesuch. Make sure to permit the setting of cookies for " "this domain." msgstr "" diff --git a/mediagoblin/i18n/tr_TR/mediagoblin.po b/mediagoblin/i18n/tr_TR/mediagoblin.po index ea954a81..434da26a 100644 --- a/mediagoblin/i18n/tr_TR/mediagoblin.po +++ b/mediagoblin/i18n/tr_TR/mediagoblin.po @@ -472,7 +472,7 @@ msgstr "{files_attempted} dosyadan {files_uploaded} tanesi başarıyla gönderil #: mediagoblin/meddleware/csrf.py:134 msgid "" "CSRF cookie not present. This is most likely the result of a cookie blocker " -"or somesuch.<br/>Make sure to permit the settings of cookies for this " +"or somesuch. Make sure to permit the setting of cookies for this " "domain." msgstr "" diff --git a/mediagoblin/i18n/uz@Latn/mediagoblin.po b/mediagoblin/i18n/uz@Latn/mediagoblin.po index 81373a2d..6499f2c6 100644 --- a/mediagoblin/i18n/uz@Latn/mediagoblin.po +++ b/mediagoblin/i18n/uz@Latn/mediagoblin.po @@ -469,7 +469,7 @@ msgstr "" #: mediagoblin/meddleware/csrf.py:134 msgid "" "CSRF cookie not present. This is most likely the result of a cookie blocker " -"or somesuch.<br/>Make sure to permit the settings of cookies for this " +"or somesuch. Make sure to permit the setting of cookies for this " "domain." msgstr "" diff --git a/mediagoblin/i18n/vi/mediagoblin.po b/mediagoblin/i18n/vi/mediagoblin.po index d321ff8f..7b942ea9 100644 --- a/mediagoblin/i18n/vi/mediagoblin.po +++ b/mediagoblin/i18n/vi/mediagoblin.po @@ -469,7 +469,7 @@ msgstr "" #: mediagoblin/meddleware/csrf.py:134 msgid "" "CSRF cookie not present. This is most likely the result of a cookie blocker " -"or somesuch.<br/>Make sure to permit the settings of cookies for this " +"or somesuch. Make sure to permit the setting of cookies for this " "domain." msgstr "" diff --git a/mediagoblin/i18n/vi_VN/mediagoblin.po b/mediagoblin/i18n/vi_VN/mediagoblin.po index ecf299ad..0237c491 100644 --- a/mediagoblin/i18n/vi_VN/mediagoblin.po +++ b/mediagoblin/i18n/vi_VN/mediagoblin.po @@ -469,7 +469,7 @@ msgstr "" #: mediagoblin/meddleware/csrf.py:134 msgid "" "CSRF cookie not present. This is most likely the result of a cookie blocker " -"or somesuch.<br/>Make sure to permit the settings of cookies for this " +"or somesuch. Make sure to permit the setting of cookies for this " "domain." msgstr "" diff --git a/mediagoblin/i18n/zh_CN/mediagoblin.po b/mediagoblin/i18n/zh_CN/mediagoblin.po index 5c5a5f4e..1d8af7df 100644 --- a/mediagoblin/i18n/zh_CN/mediagoblin.po +++ b/mediagoblin/i18n/zh_CN/mediagoblin.po @@ -474,7 +474,7 @@ msgstr "" #: mediagoblin/meddleware/csrf.py:134 msgid "" "CSRF cookie not present. This is most likely the result of a cookie blocker " -"or somesuch.<br/>Make sure to permit the settings of cookies for this " +"or somesuch. Make sure to permit the setting of cookies for this " "domain." msgstr "CSRF cookie 不存在。很可能是由类似 cookie 屏蔽器造成的。<br />请允许本域名的 cookie 设定。" diff --git a/mediagoblin/i18n/zh_TW.Big5/mediagoblin.po b/mediagoblin/i18n/zh_TW.Big5/mediagoblin.po index 6575465f..03b2cf52 100644 --- a/mediagoblin/i18n/zh_TW.Big5/mediagoblin.po +++ b/mediagoblin/i18n/zh_TW.Big5/mediagoblin.po @@ -469,7 +469,7 @@ msgstr "" #: mediagoblin/meddleware/csrf.py:134 msgid "" "CSRF cookie not present. This is most likely the result of a cookie blocker " -"or somesuch.<br/>Make sure to permit the settings of cookies for this " +"or somesuch. Make sure to permit the setting of cookies for this " "domain." msgstr "" diff --git a/mediagoblin/i18n/zh_TW/mediagoblin.po b/mediagoblin/i18n/zh_TW/mediagoblin.po index da0a9b36..6f0507f3 100644 --- a/mediagoblin/i18n/zh_TW/mediagoblin.po +++ b/mediagoblin/i18n/zh_TW/mediagoblin.po @@ -474,9 +474,9 @@ msgstr "" #: mediagoblin/meddleware/csrf.py:134 msgid "" "CSRF cookie not present. This is most likely the result of a cookie blocker " -"or somesuch.<br/>Make sure to permit the settings of cookies for this " +"or somesuch. Make sure to permit the setting of cookies for this " "domain." -msgstr "跨網站存取 (CSRF) 的 cookie 不存在,有可能是 cookie 阻擋程式之類的程式導致的。<br/>請允許此網域的 cookie 設定。" +msgstr "跨網站存取 (CSRF) 的 cookie 不存在,有可能是 cookie 阻擋程式之類的程式導致的。 請允許此網域的 cookie 設定。" #: mediagoblin/media_types/__init__.py:79 #: mediagoblin/media_types/__init__.py:101 diff --git a/mediagoblin/listings/views.py b/mediagoblin/listings/views.py index 07dbb3d5..f640cc95 100644 --- a/mediagoblin/listings/views.py +++ b/mediagoblin/listings/views.py @@ -106,10 +106,10 @@ def atom_feed(request): entry.description_html, id=entry.url_for_self(request.urlgen,qualified=True), content_type='html', - author={'name': entry.get_uploader.username, + author={'name': entry.get_actor.username, 'uri': request.urlgen( 'mediagoblin.user_pages.user_home', - qualified=True, user=entry.get_uploader.username)}, + qualified=True, user=entry.get_actor.username)}, updated=entry.get('created'), links=[{ 'href':entry.url_for_self( diff --git a/mediagoblin/meddleware/csrf.py b/mediagoblin/meddleware/csrf.py index 6cad6fa7..9f64f363 100644 --- a/mediagoblin/meddleware/csrf.py +++ b/mediagoblin/meddleware/csrf.py @@ -132,8 +132,8 @@ class CsrfMeddleware(BaseMeddleware): # cookie blocker might be in action (in the best case) _log.error('CSRF cookie not present') raise Forbidden(_('CSRF cookie not present. This is most likely ' - 'the result of a cookie blocker or somesuch.<br/>' - 'Make sure to permit the settings of cookies for ' + 'the result of a cookie blocker or somesuch. ' + 'Make sure to permit the setting of cookies for ' 'this domain.')) # get the form token and confirm it matches diff --git a/mediagoblin/media_types/__init__.py b/mediagoblin/media_types/__init__.py index 97e4facd..9f6043e9 100644 --- a/mediagoblin/media_types/__init__.py +++ b/mediagoblin/media_types/__init__.py @@ -146,6 +146,7 @@ def sniff_media(media_file, filename): tmp_media_file = tempfile.NamedTemporaryFile() shutil.copyfileobj(media_file, tmp_media_file) media_file.seek(0) + tmp_media_file.seek(0) try: return type_match_handler(tmp_media_file, filename) except TypeNotFound as e: diff --git a/mediagoblin/media_types/blog/views.py b/mediagoblin/media_types/blog/views.py index 0b88037f..3502d7b8 100644 --- a/mediagoblin/media_types/blog/views.py +++ b/mediagoblin/media_types/blog/views.py @@ -46,7 +46,7 @@ from mediagoblin.tools.text import ( cleaned_markdown_conversion) from mediagoblin.db.util import check_media_slug_used, check_collection_slug_used -from mediagoblin.db.models import User, Collection, MediaEntry +from mediagoblin.db.models import User, Collection, MediaEntry, LocalUser from mediagoblin.notifications import add_comment_subscription @@ -54,7 +54,7 @@ from mediagoblin.notifications import add_comment_subscription @require_active_login def blog_edit(request): """ - View for editing an existing blog or creating a new blog + View for editing an existing blog or creating a new blog if user have not exceeded maximum allowed acount of blogs. """ url_user = request.matchdict.get('user', None) @@ -171,7 +171,7 @@ def blogpost_create(request): @require_active_login def blogpost_edit(request): - + blog_slug = request.matchdict.get('blog_slug', None) blog_post_slug = request.matchdict.get('blog_post_slug', None) @@ -279,7 +279,7 @@ def blog_post_listing(request, page, url_user=None): 'blog_owner': url_user, 'blog':blog }) - + @require_active_login def draft_view(request): @@ -298,15 +298,17 @@ def draft_view(request): {'blogpost':blogpost, 'blog': blog }) - - + + @require_active_login def blog_delete(request, **kwargs): """ - Deletes a blog and media entries, tags associated with it. + Deletes a blog and media entries, tags associated with it. """ url_user = request.matchdict.get('user') - owner_user = request.db.User.query.filter_by(username=url_user).first() + owner_user = request.db.LocalUser.query.filter( + LocalUser.username==url_user + ).first() blog_slug = request.matchdict.get('blog_slug', None) blog = get_blog_by_slug(request, blog_slug, author=owner_user.id) @@ -346,21 +348,23 @@ def blog_delete(request, **kwargs): _("The blog was not deleted because you have no rights.")) return redirect(request, "mediagoblin.media_types.blog.blog_admin_dashboard", user=request.user.username) - - + + def blog_about_view(request): """ Page containing blog description and statistics """ blog_slug = request.matchdict.get('blog_slug', None) url_user = request.matchdict.get('user', None) - - user = request.db.User.query.filter_by(username=url_user).first() + + user = request.db.User.query.filter( + LocalUser.username=url_user + ).first() blog = get_blog_by_slug(request, blog_slug, author=user.id) - + if not user or not blog: return render_404(request) - + else: blog_posts_processed = blog.get_all_blog_posts(u'processed').count() return render_to_response( @@ -370,11 +374,3 @@ def blog_about_view(request): 'blog': blog, 'blogpost_count': blog_posts_processed }) - - - - - - - - diff --git a/mediagoblin/media_types/pdf/processing.py b/mediagoblin/media_types/pdf/processing.py index f6d10a5f..ac4bab6d 100644 --- a/mediagoblin/media_types/pdf/processing.py +++ b/mediagoblin/media_types/pdf/processing.py @@ -207,7 +207,7 @@ def pdf_info(original): _log.debug('pdfinfo could not read the pdf file.') raise BadMediaFail() - lines = [l.decode() for l in lines] + lines = [l.decode('utf-8', 'replace') for l in lines] info_dict = dict([[part.strip() for part in l.strip().split(':', 1)] for l in lines if ':' in l]) diff --git a/mediagoblin/moderation/tools.py b/mediagoblin/moderation/tools.py index 0bcd8762..73afd051 100644 --- a/mediagoblin/moderation/tools.py +++ b/mediagoblin/moderation/tools.py @@ -17,7 +17,7 @@ import six from mediagoblin import mg_globals -from mediagoblin.db.models import User, Privilege, UserBan +from mediagoblin.db.models import User, Privilege, UserBan, LocalUser from mediagoblin.db.base import Session from mediagoblin.tools.mail import send_email from mediagoblin.tools.response import redirect @@ -68,14 +68,14 @@ def take_punitive_actions(request, form, report, user): if u'delete' in form.action_to_resolve.data and \ report.is_comment_report(): - deleted_comment = report.comment + deleted_comment = report.obj() Session.delete(deleted_comment) form.resolution_content.data += \ _(u"\n{mod} deleted the comment.").format( mod=request.user.username) elif u'delete' in form.action_to_resolve.data and \ report.is_media_entry_report(): - deleted_media = report.media_entry + deleted_media = report.obj() deleted_media.delete() form.resolution_content.data += \ _(u"\n{mod} deleted the media entry.").format( @@ -122,8 +122,9 @@ 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() + user = LocalUser.query.filter( + LocalUser.username==user + ).first() if privilege in user.all_privileges: user.all_privileges.remove(privilege) return True @@ -154,8 +155,9 @@ 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() + user = LocalUser.query.filter( + LocalUser.username==user + ).first() if privilege not in user.all_privileges: user.all_privileges.append(privilege) return True diff --git a/mediagoblin/moderation/views.py b/mediagoblin/moderation/views.py index f4de11ad..fdcbf051 100644 --- a/mediagoblin/moderation/views.py +++ b/mediagoblin/moderation/views.py @@ -15,8 +15,8 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. -from mediagoblin.db.models import (MediaEntry, User,ReportBase, Privilege, - UserBan) +from mediagoblin.db.models import (MediaEntry, User, Report, Privilege, + UserBan, LocalUser) from mediagoblin.decorators import (require_admin_or_moderator_login, active_user_from_url, user_has_privilege, allow_reporting) @@ -79,11 +79,13 @@ def moderation_users_detail(request): ''' Shows details about a particular user. ''' - user = User.query.filter_by(username=request.matchdict['user']).first() + user = LocalUser.query.filter( + LocalUser.username==request.matchdict['user'] + ).first() active_reports = user.reports_filed_on.filter( - ReportBase.resolved==None).limit(5) + Report.resolved==None).limit(5) closed_reports = user.reports_filed_on.filter( - ReportBase.resolved!=None).all() + Report.resolved!=None).all() privileges = Privilege.query user_banned = UserBan.query.get(user.id) ban_form = moderation_forms.BanForm() @@ -114,23 +116,23 @@ def moderation_reports_panel(request): active_settings['current_page'] = form.active_p.data or 1 closed_settings['current_page'] = form.closed_p.data or 1 filters = [ - getattr(ReportBase,key)==val + getattr(Report,key)==val for key,val in filters.viewitems()] - all_active = ReportBase.query.filter( - ReportBase.resolved==None).filter( + all_active = Report.query.filter( + Report.resolved==None).filter( *filters) - all_closed = ReportBase.query.filter( - ReportBase.resolved!=None).filter( + all_closed = Report.query.filter( + Report.resolved!=None).filter( *filters) # report_list and closed_report_list are the two lists of up to 10 # items which are actually passed to the user in this request report_list = all_active.order_by( - ReportBase.created.desc()).offset( + Report.created.desc()).offset( (active_settings['current_page']-1)*10).limit(10) closed_report_list = all_closed.order_by( - ReportBase.created.desc()).offset( + Report.created.desc()).offset( (closed_settings['current_page']-1)*10).limit(10) active_settings['last_page'] = int(ceil(all_active.count()/10.)) @@ -153,7 +155,7 @@ def moderation_reports_detail(request): 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']) + report = Report.query.get(request.matchdict['report_id']) form.take_away_privileges.choices = [ (s.privilege_name,s.privilege_name.title()) \ diff --git a/mediagoblin/notifications/__init__.py b/mediagoblin/notifications/__init__.py index c7a9a1fb..8690aae5 100644 --- a/mediagoblin/notifications/__init__.py +++ b/mediagoblin/notifications/__init__.py @@ -16,8 +16,8 @@ import logging -from mediagoblin.db.models import Notification, \ - CommentNotification, CommentSubscription, User +from mediagoblin.db.models import Notification, CommentSubscription, User, \ + Comment, GenericModelReference from mediagoblin.notifications.task import email_notification_task from mediagoblin.notifications.tools import generate_comment_message @@ -34,13 +34,13 @@ def trigger_notification(comment, media_entry, request): if not subscription.notify: continue - if comment.get_author == subscription.user: + if comment.get_actor == subscription.user: continue - cn = CommentNotification( + cn = Notification( user_id=subscription.user_id, - subject_id=comment.id) - + ) + cn.obj = comment cn.save() if subscription.send_email: @@ -61,9 +61,15 @@ def mark_notification_seen(notification): def mark_comment_notification_seen(comment_id, user): - notification = CommentNotification.query.filter_by( + comment = Comment.query.get(comment_id).comment() + comment_gmr = GenericModelReference.query.filter_by( + obj_pk=comment.id, + model_type=comment.__tablename__ + ).first() + notification = Notification.query.filter_by( user_id=user.id, - subject_id=comment_id).first() + object_id=comment_gmr.id + ).first() _log.debug(u'Marking {0} as seen.'.format(notification)) diff --git a/mediagoblin/notifications/task.py b/mediagoblin/notifications/task.py index d915212a..652b78e2 100644 --- a/mediagoblin/notifications/task.py +++ b/mediagoblin/notifications/task.py @@ -20,7 +20,7 @@ from celery import registry from celery.task import Task from mediagoblin.tools.mail import send_email -from mediagoblin.db.models import CommentNotification +from mediagoblin.db.models import Notification _log = logging.getLogger(__name__) @@ -34,7 +34,7 @@ class EmailNotificationTask(Task): the web server. ''' def run(self, notification_id, message): - cn = CommentNotification.query.filter_by(id=notification_id).first() + cn = Notification.query.filter_by(id=notification_id).first() _log.info(u'Sending notification email about {0}'.format(cn)) return send_email( diff --git a/mediagoblin/notifications/tools.py b/mediagoblin/notifications/tools.py index 25432780..69017ed0 100644 --- a/mediagoblin/notifications/tools.py +++ b/mediagoblin/notifications/tools.py @@ -32,11 +32,11 @@ def generate_comment_message(user, comment, media, request): comment_url = request.urlgen( 'mediagoblin.user_pages.media_home.view_comment', comment=comment.id, - user=media.get_uploader.username, + user=media.get_actor.username, media=media.slug_or_id, qualified=True) + '#comment' - comment_author = comment.get_author.username + comment_author = comment.get_actor.username rendered_email = render_template( request, 'mediagoblin/user_pages/comment_email.txt', diff --git a/mediagoblin/notifications/views.py b/mediagoblin/notifications/views.py index cfe66b2e..984b9c9b 100644 --- a/mediagoblin/notifications/views.py +++ b/mediagoblin/notifications/views.py @@ -57,7 +57,10 @@ def mark_all_comment_notifications_seen(request): Marks all comment notifications seen. """ for comment in get_notifications(request.user.id): - mark_comment_notification_seen(comment.subject_id, request.user) + mark_comment_notification_seen( + comment.obj().get_comment_link().id, + request.user + ) if request.GET.get('next'): return redirect(request, location=request.GET.get('next')) diff --git a/mediagoblin/oauth/__init__.py b/mediagoblin/oauth/__init__.py index 719b56e7..6dfafea2 100644 --- a/mediagoblin/oauth/__init__.py +++ b/mediagoblin/oauth/__init__.py @@ -14,3 +14,9 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. +# This represents a dummy ID for the Client, RequestToken and AccessToken +# WARNING: Do not change these without providing a data migration to migrate +# existing dummy clients already in the database. +DUMMY_CLIENT_ID = "dummy-client" +DUMMY_ACCESS_TOKEN = "dummy-access-token" +DUMMY_REQUEST_TOKEN = "dummy-request-token" diff --git a/mediagoblin/oauth/oauth.py b/mediagoblin/oauth/oauth.py index c7951734..f6a1bf4b 100644 --- a/mediagoblin/oauth/oauth.py +++ b/mediagoblin/oauth/oauth.py @@ -18,6 +18,7 @@ import datetime from oauthlib.common import Request from oauthlib.oauth1 import RequestValidator +from mediagoblin import oauth from mediagoblin.db.models import NonceTimestamp, Client, RequestToken, AccessToken class GMGRequestValidator(RequestValidator): @@ -45,9 +46,9 @@ class GMGRequestValidator(RequestValidator): client_id = self.POST[u"oauth_consumer_key"] request_token = RequestToken( - token=token["oauth_token"], - secret=token["oauth_token_secret"], - ) + token=token["oauth_token"], + secret=token["oauth_token_secret"], + ) request_token.client = client_id if u"oauth_callback" in self.POST: request_token.callback = self.POST[u"oauth_callback"] @@ -62,12 +63,12 @@ class GMGRequestValidator(RequestValidator): def save_access_token(self, token, request): """ Saves access token in db """ access_token = AccessToken( - token=token["oauth_token"], - secret=token["oauth_token_secret"], + token=token["oauth_token"], + secret=token["oauth_token_secret"], ) access_token.request_token = request.oauth_token request_token = RequestToken.query.filter_by(token=request.oauth_token).first() - access_token.user = request_token.user + access_token.actor = request_token.actor access_token.save() def get_realms(*args, **kwargs): @@ -94,7 +95,8 @@ class GMGRequestValidator(RequestValidator): def validate_client_key(self, client_key, request): """ Verifies client exists with id of client_key """ - client = Client.query.filter_by(id=client_key).first() + client_query = Client.query.filter(Client.id != oauth.DUMMY_CLIENT_ID) + client = client_query.filter_by(id=client_key).first() if client is None: return False @@ -102,15 +104,30 @@ class GMGRequestValidator(RequestValidator): def validate_access_token(self, client_key, token, request): """ Verifies token exists for client with id of client_key """ - client = Client.query.filter_by(id=client_key).first() - token = AccessToken.query.filter_by(token=token) - token = token.first() + # Get the client for the request + client_query = Client.query.filter(Client.id != oauth.DUMMY_CLIENT_ID) + client = client_query.filter_by(id=client_key).first() + + # If the client is invalid then it's invalid + if client is None: + return False - if token is None: + # Look up the AccessToken + access_token_query = AccessToken.query.filter( + AccessToken.token != oauth.DUMMY_ACCESS_TOKEN + ) + access_token = access_token_query.filter_by(token=token).first() + + # If there isn't one - we can't validate. + if access_token is None: return False - request_token = RequestToken.query.filter_by(token=token.request_token) - request_token = request_token.first() + # Check that the client matches the on + request_token_query = RequestToken.query.filter( + RequestToken.token != oauth.DUMMY_REQUEST_TOKEN, + RequestToken.token == access_token.request_token + ) + request_token = request_token_query.first() if client.id != request_token.client: return False @@ -131,6 +148,18 @@ class GMGRequestValidator(RequestValidator): access_token = AccessToken.query.filter_by(token=token).first() return access_token.secret + @property + def dummy_client(self): + return oauth.DUMMY_CLIENT_ID + + @property + def dummy_request_token(self): + return oauth.DUMMY_REQUEST_TOKEN + + @property + def dummy_access_token(self): + return oauth.DUMMY_ACCESS_TOKEN + class GMGRequest(Request): """ Fills in data to produce a oauth.common.Request object from a diff --git a/mediagoblin/oauth/views.py b/mediagoblin/oauth/views.py index 1b4787d6..9d7a877b 100644 --- a/mediagoblin/oauth/views.py +++ b/mediagoblin/oauth/views.py @@ -211,7 +211,7 @@ def request_token(request): error = "Invalid client_id" return json_response({"error": error}, status=400) - # make request token and return to client + # make request token and return to client request_validator = GMGRequestValidator(authorization) rv = RequestTokenEndpoint(request_validator) tokens = rv.create_request_token(request, authorization) @@ -255,7 +255,7 @@ def authorize(request): verifier = auth_endpoint.create_verifier(orequest, {}) oauth_request.verifier = verifier["oauth_verifier"] - oauth_request.user = request.user.id + oauth_request.actor = request.user.id oauth_request.save() # find client & build context diff --git a/mediagoblin/plugins/api/tools.py b/mediagoblin/plugins/api/tools.py index 56256236..e406888e 100644 --- a/mediagoblin/plugins/api/tools.py +++ b/mediagoblin/plugins/api/tools.py @@ -62,12 +62,12 @@ def get_entry_serializable(entry, urlgen): to views. ''' return { - 'user': entry.get_uploader.username, - 'user_id': entry.get_uploader.id, - 'user_bio': entry.get_uploader.bio, - 'user_bio_html': entry.get_uploader.bio_html, + 'user': entry.get_actor.username, + 'user_id': entry.get_actor.id, + 'user_bio': entry.get_actor.bio, + 'user_bio_html': entry.get_actor.bio_html, 'user_permalink': urlgen('mediagoblin.user_pages.user_home', - user=entry.get_uploader.username, + user=entry.get_actor.username, qualified=True), 'id': entry.id, 'created': entry.created.isoformat(), diff --git a/mediagoblin/plugins/archivalook/templates/archivalook/feature_media_sidebar.html b/mediagoblin/plugins/archivalook/templates/archivalook/feature_media_sidebar.html index c5b8a7d6..942a888b 100644 --- a/mediagoblin/plugins/archivalook/templates/archivalook/feature_media_sidebar.html +++ b/mediagoblin/plugins/archivalook/templates/archivalook/feature_media_sidebar.html @@ -21,7 +21,7 @@ {% if not media_feature %} <a href="{{ request.urlgen( 'mediagoblin.user_pages.media_feature', - user=media.get_uploader.username, + user=media.get_actor.username, media=media.slug_or_id) }}" class="button_action" id="button_featuremedia" title="{% trans %} Feature Media {% endtrans %}"> @@ -29,7 +29,7 @@ Feature Media {% endtrans %}"> {% else %} <a href="{{ request.urlgen( 'mediagoblin.user_pages.media_unfeature', - user=media.get_uploader.username, + user=media.get_actor.username, media=media.slug_or_id) }}" class="button_action" id="button_unfeaturemedia" title="{% trans %} Unfeature Media {% endtrans %}"> @@ -37,7 +37,7 @@ Unfeature Media {% endtrans %}"> {% if not media_feature.display_type == 'primary' %} <a href="{{ request.urlgen( 'mediagoblin.user_pages.feature_promote', - user=media.get_uploader.username, + user=media.get_actor.username, media=media.slug_or_id) }}" class="button_action" id="button_promotefeature" title="{% trans %} Promote Feature {% endtrans %}"> @@ -45,7 +45,7 @@ Promote Feature {% endtrans %}"> {% endif %}{% if not media_feature.display_type == 'tertiary' %} <a href="{{ request.urlgen( 'mediagoblin.user_pages.feature_demote', - user=media.get_uploader.username, + user=media.get_actor.username, media=media.slug_or_id) }}" class="button_action" id="button_demotefeature" title="{% trans %} Demote Feature {% endtrans %}"> diff --git a/mediagoblin/plugins/archivalook/tools.py b/mediagoblin/plugins/archivalook/tools.py index b495624c..ad2eee5f 100644 --- a/mediagoblin/plugins/archivalook/tools.py +++ b/mediagoblin/plugins/archivalook/tools.py @@ -13,10 +13,9 @@ # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. - import six -from mediagoblin.db.models import MediaEntry, User +from mediagoblin.db.models import MediaEntry, User, LocalUser from mediagoblin.plugins.archivalook.models import FeaturedMedia from mediagoblin.tools.translate import lazy_pass_to_ugettext as _ from mediagoblin.plugins.archivalook.models import FeaturedMedia @@ -35,11 +34,12 @@ def get_media_entry_from_uploader_slug(uploader_username, slug): :returns media A MediaEntry object or None if no entry matches the specifications. """ - uploader = User.query.filter( - User.username == uploader_username).first() + uploader = LocalUser.query.filter( + LocalUser.username==uploader_username + ).first() media = MediaEntry.query.filter( - MediaEntry.get_uploader == uploader ).filter( - MediaEntry.slug == slug).first() + MediaEntry.get_actor == uploader ).filter( + MediaEntry.slug == slug).first() return media @@ -141,7 +141,7 @@ def create_featured_media_textbox(): for feature in feature_list: media_entry = feature.media_entry output_text += u'/u/{uploader_username}/m/{media_slug}/\n'.format( - uploader_username = media_entry.get_uploader.username, + uploader_username = media_entry.get_actor.username, media_slug = media_entry.slug) @@ -292,4 +292,3 @@ def demote_feature(media_entry): elif target_feature.display_type == u'primary': target_feature.display_type = u'secondary' target_feature.save() - diff --git a/mediagoblin/plugins/basic_auth/__init__.py b/mediagoblin/plugins/basic_auth/__init__.py index 64564c7f..31a4fd95 100644 --- a/mediagoblin/plugins/basic_auth/__init__.py +++ b/mediagoblin/plugins/basic_auth/__init__.py @@ -19,7 +19,7 @@ import os from mediagoblin.plugins.basic_auth import forms as auth_forms from mediagoblin.plugins.basic_auth import tools as auth_tools from mediagoblin.auth.tools import create_basic_user -from mediagoblin.db.models import User +from mediagoblin.db.models import LocalUser from mediagoblin.tools import pluginapi from sqlalchemy import or_ from mediagoblin.tools.staticdirect import PluginStatic @@ -56,10 +56,10 @@ def setup_plugin(): def get_user(**kwargs): username = kwargs.pop('username', None) if username: - user = User.query.filter( + user = LocalUser.query.filter( or_( - User.username == username, - User.email == username, + LocalUser.username == username, + LocalUser.email == username, )).first() return user diff --git a/mediagoblin/plugins/basic_auth/views.py b/mediagoblin/plugins/basic_auth/views.py index 02d370f0..95f91b4c 100644 --- a/mediagoblin/plugins/basic_auth/views.py +++ b/mediagoblin/plugins/basic_auth/views.py @@ -16,7 +16,7 @@ from itsdangerous import BadSignature from mediagoblin import messages -from mediagoblin.db.models import User +from mediagoblin.db.models import LocalUser from mediagoblin.decorators import require_active_login from mediagoblin.plugins.basic_auth import forms, tools from mediagoblin.tools.crypto import get_timed_signer_url @@ -48,7 +48,7 @@ def forgot_password(request): found_by_email = '@' in fp_form.username.data if found_by_email: - user = User.query.filter_by( + user = LocalUser.query.filter_by( email=fp_form.username.data).first() # Don't reveal success in case the lookup happened by email address. success_message = _("If that email address (case sensitive!) is " @@ -56,7 +56,7 @@ def forgot_password(request): "instructions on how to change your password.") else: # found by username - user = User.query.filter_by( + user = LocalUser.query.filter_by( username=fp_form.username.data).first() if user is None: @@ -114,7 +114,7 @@ def verify_forgot_password(request): 'index') # check if it's a valid user id - user = User.query.filter_by(id=int(token)).first() + user = LocalUser.query.filter_by(id=int(token)).first() # no user in db if not user: diff --git a/mediagoblin/plugins/ldap/views.py b/mediagoblin/plugins/ldap/views.py index be434daf..e10c7f60 100644 --- a/mediagoblin/plugins/ldap/views.py +++ b/mediagoblin/plugins/ldap/views.py @@ -18,7 +18,7 @@ import six from mediagoblin import mg_globals, messages from mediagoblin.auth.tools import register_user -from mediagoblin.db.models import User +from mediagoblin.db.models import User, LocalUser from mediagoblin.decorators import allow_registration, auth_enabled from mediagoblin.plugins.ldap import forms from mediagoblin.plugins.ldap.tools import LDAP @@ -38,8 +38,9 @@ def login(request): login_form.password.data) if username: - user = User.query.filter_by( - username=username).first() + user = LocalUser.query.filter( + LocalUser.username==username + ).first() if user: # set up login in session diff --git a/mediagoblin/plugins/metadata_display/templates/mediagoblin/plugins/metadata_display/metadata_table.html b/mediagoblin/plugins/metadata_display/templates/mediagoblin/plugins/metadata_display/metadata_table.html index 3a9d872c..15ea1536 100644 --- a/mediagoblin/plugins/metadata_display/templates/mediagoblin/plugins/metadata_display/metadata_table.html +++ b/mediagoblin/plugins/metadata_display/templates/mediagoblin/plugins/metadata_display/metadata_table.html @@ -35,7 +35,7 @@ {% endif %} {% if request.user and request.user.has_privilege('admin') %} <a href="{{ request.urlgen('mediagoblin.edit.metadata', - user=media.get_uploader.username, + user=media.get_actor.username, media_id=media.id) }}"> {% trans %}Edit Metadata{% endtrans %}</a> {% endif %} diff --git a/mediagoblin/plugins/openid/__init__.py b/mediagoblin/plugins/openid/__init__.py index ca17a7e8..b26087a2 100644 --- a/mediagoblin/plugins/openid/__init__.py +++ b/mediagoblin/plugins/openid/__init__.py @@ -19,7 +19,7 @@ import uuid from sqlalchemy import or_ from mediagoblin.auth.tools import create_basic_user -from mediagoblin.db.models import User +from mediagoblin.db.models import User, LocalUser from mediagoblin.plugins.openid.models import OpenIDUserURL from mediagoblin.tools import pluginapi from mediagoblin.tools.translate import lazy_pass_to_ugettext as _ @@ -67,8 +67,8 @@ def create_user(register_form): username = register_form.username.data user = User.query.filter( or_( - User.username == username, - User.email == username, + LocalUser.username == username, + LocalUser.email == username, )).first() if not user: diff --git a/mediagoblin/plugins/persona/__init__.py b/mediagoblin/plugins/persona/__init__.py index 700c18e2..8fab726a 100644 --- a/mediagoblin/plugins/persona/__init__.py +++ b/mediagoblin/plugins/persona/__init__.py @@ -19,7 +19,7 @@ import os from sqlalchemy import or_ from mediagoblin.auth.tools import create_basic_user -from mediagoblin.db.models import User +from mediagoblin.db.models import User, LocalUser from mediagoblin.plugins.persona.models import PersonaUserEmails from mediagoblin.tools import pluginapi from mediagoblin.tools.staticdirect import PluginStatic @@ -60,8 +60,8 @@ def create_user(register_form): username = register_form.username.data user = User.query.filter( or_( - User.username == username, - User.email == username, + LocalUser.username == username, + LocalUser.email == username, )).first() if not user: diff --git a/mediagoblin/plugins/piwigo/views.py b/mediagoblin/plugins/piwigo/views.py index 1fe1e576..ab741a72 100644 --- a/mediagoblin/plugins/piwigo/views.py +++ b/mediagoblin/plugins/piwigo/views.py @@ -82,7 +82,7 @@ def pwg_categories_getList(request): if request.user: collections = Collection.query.filter_by( - get_creator=request.user).order_by(Collection.title) + get_actor=request.user).order_by(Collection.title) for c in collections: catlist.append({'id': c.id, @@ -142,7 +142,7 @@ def pwg_images_addSimple(request): collection_id = form.category.data if collection_id > 0: collection = Collection.query.get(collection_id) - if collection is not None and collection.creator == request.user.id: + if collection is not None and collection.actor == request.user.id: add_media_to_collection(collection, entry, "") return { diff --git a/mediagoblin/submit/lib.py b/mediagoblin/submit/lib.py index a0e1cf90..2edea70f 100644 --- a/mediagoblin/submit/lib.py +++ b/mediagoblin/submit/lib.py @@ -52,7 +52,7 @@ def new_upload_entry(user): Create a new MediaEntry for uploading """ entry = MediaEntry() - entry.uploader = user.id + entry.actor = user.id entry.license = user.license_preference return entry @@ -104,9 +104,7 @@ def submit_media(mg_app, user, submitted_file, filename, title=None, description=None, license=None, metadata=None, tags_string=u"", upload_limit=None, max_file_size=None, - callback_url=None, - # If provided we'll do the feed_url update, otherwise ignore - urlgen=None,): + callback_url=None, urlgen=None,): """ Args: - mg_app: The MediaGoblinApp instantiated for this process @@ -124,7 +122,8 @@ def submit_media(mg_app, user, submitted_file, filename, - upload_limit: size in megabytes that's the per-user upload limit - max_file_size: maximum size each file can be that's uploaded - callback_url: possible post-hook to call after submission - - urlgen: if provided, used to do the feed_url update + - urlgen: if provided, used to do the feed_url update and assign a public + ID used in the API (very important). """ if upload_limit and user.uploaded >= upload_limit: raise UserPastUploadLimit() @@ -189,6 +188,11 @@ def submit_media(mg_app, user, submitted_file, filename, metadata.save() if urlgen: + # Generate the public_id, this is very importent, especially relating + # to deletion, it allows the shell to be accessable post-delete! + entry.get_public_id(urlgen) + + # Generate the feed URL feed_url = urlgen( 'mediagoblin.user_pages.atom_feed', qualified=True, user=user.username) @@ -198,7 +202,7 @@ def submit_media(mg_app, user, submitted_file, filename, add_comment_subscription(user, entry) # Create activity - create_activity("post", entry, entry.uploader) + create_activity("post", entry, entry.actor) entry.save() # Pass off to processing @@ -277,6 +281,9 @@ def api_upload_request(request, file_data, entry): # This will be set later but currently we just don't have enough information entry.slug = None + # This is a MUST. + entry.get_public_id(request.urlgen) + queue_file = prepare_queue_task(request.app, entry, file_data.filename) with queue_file: queue_file.write(request.data) @@ -297,7 +304,7 @@ def api_add_to_feed(request, entry): activity = create_activity( verb="post", obj=entry, - actor=entry.uploader, + actor=entry.actor, generator=create_generator(request) ) entry.save() diff --git a/mediagoblin/submit/views.py b/mediagoblin/submit/views.py index ccdd70bc..eae4a1e7 100644 --- a/mediagoblin/submit/views.py +++ b/mediagoblin/submit/views.py @@ -112,12 +112,14 @@ def add_collection(request, media=None): collection.title = six.text_type(submit_form.title.data) collection.description = six.text_type(submit_form.description.data) - collection.creator = request.user.id + collection.actor = request.user.id + collection.type = request.db.Collection.USER_DEFINED_TYPE collection.generate_slug() # Make sure this user isn't duplicating an existing collection existing_collection = request.db.Collection.query.filter_by( - creator=request.user.id, + actor=request.user.id, + type=request.db.Collection.USER_DEFINED_TYPE, title=collection.title).first() if existing_collection: diff --git a/mediagoblin/templates/mediagoblin/base.html b/mediagoblin/templates/mediagoblin/base.html index ddc38b3e..778cc3f9 100644 --- a/mediagoblin/templates/mediagoblin/base.html +++ b/mediagoblin/templates/mediagoblin/base.html @@ -27,6 +27,9 @@ <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> + {% if app_config['no_referrer'] -%} + <meta name="referrer" content="no-referrer"> + {%- endif %} <meta http-equiv="X-UA-Compatible" content="IE=Edge"> <title>{% block title %}{{ app_config['html_title'] }}{% endblock %}</title> <link rel="stylesheet" type="text/css" diff --git a/mediagoblin/templates/mediagoblin/edit/attachments.html b/mediagoblin/templates/mediagoblin/edit/attachments.html index d1e33c47..84c9a02c 100644 --- a/mediagoblin/templates/mediagoblin/edit/attachments.html +++ b/mediagoblin/templates/mediagoblin/edit/attachments.html @@ -27,7 +27,7 @@ {% block mediagoblin_content %} <form action="{{ request.urlgen('mediagoblin.edit.attachments', - user= media.get_uploader.username, + user= media.get_actor.username, media_id=media.id) }}" method="POST" enctype="multipart/form-data"> <div class="form_box"> diff --git a/mediagoblin/templates/mediagoblin/edit/edit.html b/mediagoblin/templates/mediagoblin/edit/edit.html index 9a040095..5f2ba3c5 100644 --- a/mediagoblin/templates/mediagoblin/edit/edit.html +++ b/mediagoblin/templates/mediagoblin/edit/edit.html @@ -28,7 +28,7 @@ {% block mediagoblin_content %} <form action="{{ request.urlgen('mediagoblin.edit.edit_media', - user= media.get_uploader.username, + user= media.get_actor.username, media_id=media.id) }}" method="POST" enctype="multipart/form-data"> <div class="form_box_xl edit_box"> diff --git a/mediagoblin/templates/mediagoblin/edit/edit_collection.html b/mediagoblin/templates/mediagoblin/edit/edit_collection.html index 5cf5bae8..f150b306 100644 --- a/mediagoblin/templates/mediagoblin/edit/edit_collection.html +++ b/mediagoblin/templates/mediagoblin/edit/edit_collection.html @@ -22,7 +22,7 @@ {% block mediagoblin_content %} <form action="{{ request.urlgen('mediagoblin.edit.edit_collection', - user= collection.get_creator.username, + user= collection.get_actor.username, collection= collection.slug) }}" method="POST" enctype="multipart/form-data"> <div class="form_box_xl edit_box"> @@ -36,4 +36,4 @@ </div> </form> -{% endblock %} +{% endblock %} diff --git a/mediagoblin/templates/mediagoblin/fragments/header_notifications.html b/mediagoblin/templates/mediagoblin/fragments/header_notifications.html index 55759a39..99c5abba 100644 --- a/mediagoblin/templates/mediagoblin/fragments/header_notifications.html +++ b/mediagoblin/templates/mediagoblin/fragments/header_notifications.html @@ -4,9 +4,9 @@ <h3>{% trans %}New comments{% endtrans %}</h3> <ul> {% for notification in notifications %} - {% set comment = notification.subject %} - {% set comment_author = comment.get_author %} - {% set media = comment.get_entry %} + {% set comment = notification.obj() %} + {% set comment_author = comment.get_actor %} + {% set media = comment.get_reply_to() %} <li class="comment_wrapper"> <div class="comment_author"> <img src="{{ request.staticdirect('/images/icon_comment.png') }}" /> @@ -17,7 +17,7 @@ </a> <a href="{{ request.urlgen('mediagoblin.user_pages.media_home.view_comment', comment=comment.id, - user=media.get_uploader.username, + user=media.get_actor.username, media=media.slug_or_id) }}#comment" class="comment_whenlink"> <span title='{{- comment.created.strftime("%I:%M%p %Y-%m-%d") -}}'> @@ -36,7 +36,7 @@ </li> {% endfor %} </ul> - <a href="{{ request.urlgen('mediagoblin.notifications.mark_all_comment_notifications_seen') }}?next={{ + <a href="{{ request.urlgen('mediagoblin.notifications.mark_all_comment_notifications_seen') }}?next={{ request.base_url|urlencode }}" id="mark_all_comments_seen"> {% trans %}Mark all read{% endtrans %} </a> diff --git a/mediagoblin/templates/mediagoblin/moderation/media_panel.html b/mediagoblin/templates/mediagoblin/moderation/media_panel.html index 9b77f638..888e4feb 100644 --- a/mediagoblin/templates/mediagoblin/moderation/media_panel.html +++ b/mediagoblin/templates/mediagoblin/moderation/media_panel.html @@ -44,7 +44,7 @@ {% for media_entry in processing_entries %} <tr> <td>{{ media_entry.id }}</td> - <td>{{ media_entry.get_uploader.username }}</td> + <td>{{ media_entry.get_actor.username }}</td> <td>{{ media_entry.title }}</td> <td>{{ media_entry.created.strftime("%F %R") }}</td> {% if media_entry.transcoding_progress %} @@ -74,7 +74,7 @@ {% for media_entry in failed_entries %} <tr> <td>{{ media_entry.id }}</td> - <td>{{ media_entry.get_uploader.username }}</td> + <td>{{ media_entry.get_actor.username }}</td> <td>{{ media_entry.title }}</td> <td>{{ media_entry.created.strftime("%F %R") }}</td> {% if media_entry.get_fail_exception() %} @@ -103,7 +103,7 @@ {% for media_entry in processed_entries %} <tr> <td>{{ media_entry.id }}</td> - <td>{{ media_entry.get_uploader.username }}</td> + <td>{{ media_entry.get_actor.username }}</td> <td><a href="{{ media_entry.url_for_self(request.urlgen) }}">{{ media_entry.title }}</a></td> <td><span title='{{ media_entry.created.strftime("%F %R") }}'>{{ timesince(media_entry.created) }}</span></td> </tr> diff --git a/mediagoblin/templates/mediagoblin/moderation/report.html b/mediagoblin/templates/mediagoblin/moderation/report.html index 496610ad..abbd4a0c 100644 --- a/mediagoblin/templates/mediagoblin/moderation/report.html +++ b/mediagoblin/templates/mediagoblin/moderation/report.html @@ -33,25 +33,26 @@ {% trans %}Return to Reports Panel{% endtrans %}</a> </div> <h2>{% trans %}Report{% endtrans %} #{{ report.id }}</h2> - {% if report.is_comment_report() and report.comment %} + {% if report.is_comment_report() and report.object_id %} {% trans %}Reported comment{% endtrans %}: - {% set comment = report.comment %} - {% set reported_user = comment.get_author %} + {% set comment = report.obj() %} + {% set target = report.obj().get_reply_to() %} + {% set reported_user = comment.get_actor %} <div id="comment-{{ comment.id }}" class="comment_wrapper"> <div class="comment_author"> <img src="{{ request.staticdirect('/images/icon_comment.png') }}" /> <a href="{{ request.urlgen('mediagoblin.moderation.users_detail', - user=comment.get_author.username) }}" + user=comment.get_actor.username) }}" class="comment_authorlink"> {{- reported_user.username -}} </a> <a href="{{ request.urlgen( 'mediagoblin.user_pages.media_home.view_comment', comment=comment.id, - user=comment.get_media_entry.get_uploader.username, - media=comment.get_media_entry.slug_or_id) }}#comment" + user=target.get_actor.username, + media=target.slug_or_id) }}#comment" class="comment_whenlink"> <span title='{{- comment.created.strftime("%I:%M%p %Y-%m-%d") -}}'> {%- trans formatted_time=timesince(comment.created) -%} @@ -65,16 +66,16 @@ {% endautoescape %} </div> </div> - {% elif report.is_media_entry_report() and report.media_entry %} + {% elif report.is_media_entry_report() and report.object_id %} - {% set media_entry = report.media_entry %} + {% set media_entry = report.obj() %} <div class="three columns media_thumbnail"> <a href="{{ request.urlgen('mediagoblin.user_pages.media_home', - user=media_entry.get_uploader.username, + user=media_entry.get_actor.username, media=media_entry.slug_or_id) }}"> <img src="{{ media_entry.thumb_url}}"/></a> <a href="{{ request.urlgen('mediagoblin.user_pages.media_home', - user=media_entry.get_uploader.username, + user=media_entry.get_actor.username, media=media_entry.slug_or_id) }}" class=thumb_entry_title> {{ media_entry.title }}</a> </div> @@ -127,8 +128,8 @@ {{ report.report_content }} </div> </div> - {% if not report.is_archived_report() and - not (report.reported_user.has_privilege('admin') and + {% if not report.is_archived_report() and + not (report.reported_user.has_privilege('admin') and not request.user.has_privilege('admin')) %} <input type=button class="button_action" value="{% trans %}Resolve{% endtrans %}" id=open_resolution_form /> <form action="" method="POST" id=resolution_form> diff --git a/mediagoblin/templates/mediagoblin/moderation/report_panel.html b/mediagoblin/templates/mediagoblin/moderation/report_panel.html index 39ca90f5..c82cd412 100644 --- a/mediagoblin/templates/mediagoblin/moderation/report_panel.html +++ b/mediagoblin/templates/mediagoblin/moderation/report_panel.html @@ -81,7 +81,7 @@ curr_page !=p %} </tr> {% for report in report_list %} <tr> - {% if report.discriminator == "comment_report" %} + {% if report.is_comment_report %} <td> <img src="{{ request.staticdirect( @@ -97,7 +97,7 @@ curr_page !=p %} {% endtrans %} </a> </td> - {% elif report.discriminator == "media_report" %} + {% elif report.is_media_entry_report %} <td> <img src="{{ request.staticdirect( diff --git a/mediagoblin/templates/mediagoblin/moderation/user.html b/mediagoblin/templates/mediagoblin/moderation/user.html index 594f845d..1e48bf84 100644 --- a/mediagoblin/templates/mediagoblin/moderation/user.html +++ b/mediagoblin/templates/mediagoblin/moderation/user.html @@ -125,9 +125,9 @@ </a> </td> <td> - {% if report.discriminator == "comment_report" %} + {% if report.is_comment_report() %} <a>{%- trans %}Reported Comment{% endtrans -%}</a> - {% elif report.discriminator == "media_report" %} + {% elif report.is_media_entry_report() %} <a>{%- trans %}Reported Media Entry{% endtrans -%}</a> {% endif %} </td> diff --git a/mediagoblin/templates/mediagoblin/user_pages/blog_media.html b/mediagoblin/templates/mediagoblin/user_pages/blog_media.html index c6eedee0..ece47fe1 100644 --- a/mediagoblin/templates/mediagoblin/user_pages/blog_media.html +++ b/mediagoblin/templates/mediagoblin/user_pages/blog_media.html @@ -70,8 +70,8 @@ {% if request.user and (media.uploader == request.user.id or request.user.has_privilege('admin')) %} - {% set edit_url = request.urlgen('mediagoblin.media_types.blog.blogpost.edit', - blog_slug=media.media_manager.get_blog_by_blogpost().slug, + {% set edit_url = request.urlgen('mediagoblin.media_types.blog.blogpost.edit', + blog_slug=media.media_manager.get_blog_by_blogpost().slug, user=request.user.username, blog_post_slug=media.slug) %} <a class="button_action" href="{{ edit_url }}">{% trans %}Edit{% endtrans %}</a> {% set delete_url = request.urlgen('mediagoblin.user_pages.media_confirm_delete', @@ -107,7 +107,7 @@ {% endif %} <ul style="list-style:none"> {% for comment in comments %} - {% set comment_author = comment.get_author %} + {% set comment_author = comment.get_actor %} <li id="comment-{{ comment.id }}" {%- if pagination.active_id == comment.id %} class="comment_wrapper comment_active"> @@ -148,7 +148,7 @@ <div class="media_sidebar"> <h3>{% trans %}Added{% endtrans %}</h3> <p><span title="{{ media.created.strftime("%I:%M%p %Y-%m-%d") }}"> - {%- trans formatted_time=timesince(media.created) -%} + {%- trans formatted_time=timesince(media.created) -%} {{ formatted_time }} ago {%- endtrans -%} </span></p> diff --git a/mediagoblin/templates/mediagoblin/user_pages/collection.html b/mediagoblin/templates/mediagoblin/user_pages/collection.html index 0f712c01..71ba4451 100644 --- a/mediagoblin/templates/mediagoblin/user_pages/collection.html +++ b/mediagoblin/templates/mediagoblin/user_pages/collection.html @@ -44,14 +44,14 @@ {{ collection_title }} by <a href="{{ user_url }}">{{ username }}</a> {%- endtrans %} </h1> - {% if request.user and (collection.creator == request.user.id or + {% if request.user and (collection.actor == request.user.id or request.user.has_privilege('admin')) %} {% set edit_url = request.urlgen('mediagoblin.edit.edit_collection', - user=collection.get_creator.username, + user=collection.get_actor.username, collection=collection.slug) %} <a class="button_action" href="{{ edit_url }}">{% trans %}Edit{% endtrans %}</a> {% set delete_url = request.urlgen('mediagoblin.user_pages.collection_confirm_delete', - user=collection.get_creator.username, + user=collection.get_actor.username, collection=collection.slug) %} <a class="button_action" href="{{ delete_url }}">{% trans %}Delete{% endtrans %}</a> {% endif %} diff --git a/mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html b/mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html index 8cfe4b29..08278349 100644 --- a/mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html +++ b/mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html @@ -30,7 +30,7 @@ {% block mediagoblin_content %} <form action="{{ request.urlgen('mediagoblin.user_pages.collection_confirm_delete', - user=collection.get_creator.username, + user=collection.get_actor.username, collection=collection.slug) }}" method="POST" enctype="multipart/form-data"> <div class="form_box"> @@ -39,9 +39,9 @@ Really delete collection: {{ title }}? {%- endtrans %} </h1> - + <br /> - + <p class="delete_checkbox_box"> {{ form.confirm }} {{ wtforms_util.render_label(form.confirm) }} @@ -58,4 +58,4 @@ </div> </div> </form> -{% endblock %} +{% endblock %} diff --git a/mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html b/mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html index 84d3eb4c..d7ff4950 100644 --- a/mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html +++ b/mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html @@ -20,7 +20,7 @@ {% import "/mediagoblin/utils/wtforms.html" as wtforms_util %} {% block title %} - {%- trans media_title=collection_item.get_media_entry.title, + {%- trans media_title=collection_item.get_object().title, collection_title=collection_item.in_collection.title -%} Remove {{ media_title }} from {{ collection_title }} {%- endtrans %} — {{ super() }} @@ -30,20 +30,20 @@ {% block mediagoblin_content %} <form action="{{ request.urlgen('mediagoblin.user_pages.collection_item_confirm_remove', - user=collection_item.in_collection.get_creator.username, + user=collection_item.in_collection.get_actor.username, collection=collection_item.in_collection.slug, collection_item=collection_item.id) }}" method="POST" enctype="multipart/form-data"> <div class="form_box"> <h1> - {%- trans media_title=collection_item.get_media_entry.title, + {%- trans media_title=collection_item.get_object().title, collection_title=collection_item.in_collection.title -%} Really remove {{ media_title }} from {{ collection_title }}? {%- endtrans %} </h1> <div style="text-align: center;" > - <img src="{{ collection_item.get_media_entry.thumb_url }}" /> + <img src="{{ collection_item.get_object().thumb_url }}" /> </div> <br /> @@ -64,4 +64,4 @@ </div> </div> </form> -{% endblock %} +{% endblock %} diff --git a/mediagoblin/templates/mediagoblin/user_pages/media.html b/mediagoblin/templates/mediagoblin/user_pages/media.html index 16ddd62c..1a35414f 100644 --- a/mediagoblin/templates/mediagoblin/user_pages/media.html +++ b/mediagoblin/templates/mediagoblin/user_pages/media.html @@ -38,8 +38,8 @@ <p class="eleven columns context"> {%- trans user_url=request.urlgen( 'mediagoblin.user_pages.user_home', - user=media.get_uploader.username), - username=media.get_uploader.username -%} + user=media.get_actor.username), + username=media.get_actor.username -%} ❖ Browsing media by <a href="{{user_url}}">{{username}}</a> {%- endtrans -%} </p> @@ -76,15 +76,15 @@ {{ media.title }} </h2> {% if request.user and - (media.uploader == request.user.id or + (media.actor == request.user.id or request.user.has_privilege('admin')) %} <div class="pull-right" style="padding-top:20px;"> {% set edit_url = request.urlgen('mediagoblin.edit.edit_media', - user= media.get_uploader.username, + user= media.get_actor.username, media_id=media.id) %} <a class="button_action" href="{{ edit_url }}">{% trans %}Edit{% endtrans %}</a> {% set delete_url = request.urlgen('mediagoblin.user_pages.media_confirm_delete', - user= media.get_uploader.username, + user= media.get_actor.username, media_id=media.id) %} <a class="button_action button_warning" href="{{ delete_url }}">{% trans %}Delete{% endtrans %}</a> </div> @@ -93,7 +93,7 @@ <p>{{ media.description_html }}</p> {% endautoescape %} </div> - {% if comments and request.user and request.user.has_privilege('commenter') %} + {% if request.user and request.user.has_privilege('commenter') %} <div class="media_comments"> {% if app_config['allow_comments'] %} <a @@ -109,7 +109,7 @@ {% endif %} {% if request.user %} <form action="{{ request.urlgen('mediagoblin.user_pages.media_post_comment', - user= media.get_uploader.username, + user= media.get_actor.username, media_id=media.id) }}" method="POST" id="form_comment"> {{ wtforms_util.render_divs(comment_form) }} <div class="form_submit_buttons"> @@ -123,7 +123,7 @@ {% endif %} <ul style="list-style:none"> {% for comment in comments %} - {% set comment_author = comment.get_author %} + {% set comment_author = comment.get_actor %} <li id="comment-{{ comment.id }}" {%- if pagination.active_id == comment.id %} class="comment_wrapper comment_active"> @@ -140,7 +140,7 @@ </a> <a href="{{ request.urlgen('mediagoblin.user_pages.media_home.view_comment', comment=comment.id, - user=media.get_uploader.username, + user=media.get_actor.username, media=media.slug_or_id) }}#comment" class="comment_whenlink"> <span title='{{- comment.created.strftime("%I:%M%p %Y-%m-%d") -}}'> @@ -157,7 +157,7 @@ <div> {% if app_config.allow_reporting %} <a href="{{ request.urlgen('mediagoblin.user_pages.media_home.report_comment', - user=media.get_uploader.username, + user=media.get_actor.username, media=media.slug_or_id, comment=comment.id) }}"> {% trans %}Report{% endtrans %}</a> @@ -215,14 +215,14 @@ {%- endif %} {%- if app_config['allow_attachments'] and request.user - and (media.uploader == request.user.id + and (media.actor == request.user.id or request.user.has_privilege('admin')) %} {%- if not media.attachment_files|count %} <h3>{% trans %}Attachments{% endtrans %}</h3> {%- endif %} <p> <a href="{{ request.urlgen('mediagoblin.edit.attachments', - user=media.get_uploader.username, + user=media.get_actor.username, media_id=media.id) }}"> {%- trans %}Add attachment{% endtrans -%} </a> diff --git a/mediagoblin/templates/mediagoblin/user_pages/media_collect.html b/mediagoblin/templates/mediagoblin/user_pages/media_collect.html index b4c9671c..ba7424d1 100644 --- a/mediagoblin/templates/mediagoblin/user_pages/media_collect.html +++ b/mediagoblin/templates/mediagoblin/user_pages/media_collect.html @@ -32,7 +32,7 @@ {% block mediagoblin_content %} <form action="{{ request.urlgen('mediagoblin.user_pages.media_collect', - user=media.get_uploader.username, + user=media.get_actor.username, media_id=media.id) }}" method="POST" enctype="multipart/form-data"> <div class="form_box"> @@ -70,4 +70,4 @@ </div> </div> </form> -{% endblock %} +{% endblock %} diff --git a/mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html b/mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html index c948ccec..243edff5 100644 --- a/mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html +++ b/mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html @@ -22,7 +22,7 @@ {% block mediagoblin_content %} <form action="{{ request.urlgen('mediagoblin.user_pages.media_confirm_delete', - user=media.get_uploader.username, + user=media.get_actor.username, media_id=media.id) }}" method="POST" enctype="multipart/form-data"> <div class="form_box"> @@ -51,4 +51,4 @@ </div> </div> </form> -{% endblock %} +{% endblock %} diff --git a/mediagoblin/templates/mediagoblin/user_pages/report.html b/mediagoblin/templates/mediagoblin/user_pages/report.html index 6f25b996..09502fcf 100644 --- a/mediagoblin/templates/mediagoblin/user_pages/report.html +++ b/mediagoblin/templates/mediagoblin/user_pages/report.html @@ -22,14 +22,14 @@ <form action="" method=POST > {% if comment is defined %} <h3>{% trans %}Reporting this Comment{% endtrans %}</h3> - {%- set comment_author = comment.get_author %} + {%- set comment_author = comment.get_actor %} {%- 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, + user=media.get_actor.username, media=media.slug_or_id) %} <div id="comment-{{ comment.id }}" class="comment_wrapper"> @@ -60,17 +60,17 @@ <h3>{% trans %}Reporting this Media Entry{% endtrans %}</h3> <div class="media_thumbnail"> <a href="{{ request.urlgen('mediagoblin.user_pages.media_home', - user=media.get_uploader.username, + user=media.get_actor.username, media=media.slug_or_id) }}"> <img src="{{ media.thumb_url }}"/></a> <a href="{{ request.urlgen('mediagoblin.user_pages.media_home', - user=media.get_uploader.username, + user=media.get_actor.username, media=media.slug_or_id) }}" class=thumb_entry_title>{{ media.title }}</a> </div> <div class=clear></div> - {%- trans user_url = request.urlgen('mediagoblin.user_pages.user_home', user=media.get_uploader.username), - username = media.get_uploader.username %} + {%- trans user_url = request.urlgen('mediagoblin.user_pages.user_home', user=media.get_actor.username), + username = media.get_actor.username %} ❖ Published by <a href="{{ user_url }}" class="comment_authorlink">{{ username }}</a> {% endtrans %} diff --git a/mediagoblin/templates/mediagoblin/utils/collection_gallery.html b/mediagoblin/templates/mediagoblin/utils/collection_gallery.html index 64b30815..86680cb6 100644 --- a/mediagoblin/templates/mediagoblin/utils/collection_gallery.html +++ b/mediagoblin/templates/mediagoblin/utils/collection_gallery.html @@ -25,24 +25,24 @@ {%- if loop.first %} thumb_row_first {%- elif loop.last %} thumb_row_last{% endif %}"> {% for item in row %} - {% set media_entry = item.get_media_entry %} - {% set entry_url = media_entry.url_for_self(request.urlgen) %} + {% set obj = item.get_object() %} + {% set obj_url = obj.url_for_self(request.urlgen) %} <div class="three columns media_thumbnail thumb_entry {%- if loop.first %} thumb_entry_first {%- elif loop.last %} thumb_entry_last{% endif %}"> - <a href="{{ entry_url }}"> - <img src="{{ media_entry.thumb_url }}" /> + <a href="{{ obj_url }}"> + <img src="{{ obj.thumb_url }}" /> </a> {% if item.note %} - <a href="{{ entry_url }}">{{ item.note }}</a> + <a href="{{ obj_url }}">{{ item.note }}</a> {% endif %} {% if request.user and - (item.in_collection.creator == request.user.id or + (item.in_collection.actor == request.user.id or request.user.has_privilege('admin')) %} {%- set remove_url=request.urlgen( 'mediagoblin.user_pages.collection_item_confirm_remove', - user=item.in_collection.get_creator.username, + user=item.in_collection.get_actor.username, collection=item.in_collection.slug, collection_item=item.id) -%} <a href="{{ remove_url }}" class="remove"> diff --git a/mediagoblin/templates/mediagoblin/utils/collections.html b/mediagoblin/templates/mediagoblin/utils/collections.html index 69738e26..5e209d60 100644 --- a/mediagoblin/templates/mediagoblin/utils/collections.html +++ b/mediagoblin/templates/mediagoblin/utils/collections.html @@ -26,15 +26,15 @@ {%- endif %} <a href="{{ collection.url_for_self(request.urlgen) }}"> {{- collection.title }} ( - {{- collection.get_creator.username -}} + {{- collection.get_actor.username -}} )</a> {%- endfor %} </p> {%- endif %} {%- if request.user %} <p> - <a type="submit" href="{{ request.urlgen('mediagoblin.user_pages.media_collect', - user=media.get_uploader.username, + <a type="submit" href="{{ request.urlgen('mediagoblin.user_pages.media_collect', + user=media.get_actor.username, media_id=media.id) }}" class="button_action"> {% trans %}Add to a collection{% endtrans %} diff --git a/mediagoblin/templates/mediagoblin/utils/comment-subscription.html b/mediagoblin/templates/mediagoblin/utils/comment-subscription.html index 5c50c801..54663f05 100644 --- a/mediagoblin/templates/mediagoblin/utils/comment-subscription.html +++ b/mediagoblin/templates/mediagoblin/utils/comment-subscription.html @@ -19,13 +19,13 @@ {% set subscription = get_comment_subscription(request.user.id, media.id) %} {% if not subscription or not subscription.notify %} <a type="submit" href="{{ request.urlgen('mediagoblin.notifications.subscribe_comments', - user=media.get_uploader.username, + user=media.get_actor.username, media=media.slug_or_id)}}" class="button_action">{% trans %}Subscribe to comments{% endtrans %} </a> {% else %} <a type="submit" href="{{ request.urlgen('mediagoblin.notifications.silence_comments', - user=media.get_uploader.username, + user=media.get_actor.username, media=media.slug_or_id)}}" class="button_action">{% trans %}Silence comments{% endtrans %} </a> diff --git a/mediagoblin/templates/mediagoblin/utils/object_gallery.html b/mediagoblin/templates/mediagoblin/utils/object_gallery.html index 1b4a15ed..1f05eed2 100644 --- a/mediagoblin/templates/mediagoblin/utils/object_gallery.html +++ b/mediagoblin/templates/mediagoblin/utils/object_gallery.html @@ -72,5 +72,10 @@ {%- endtrans -%} </i> </p> + <p> + <a class="button_action" href="{{ request.urlgen('mediagoblin.submit.start') }}"> + {%- trans %}Add media{% endtrans -%} + </a> + </p> {% endif %} {% endmacro %} diff --git a/mediagoblin/templates/mediagoblin/utils/report.html b/mediagoblin/templates/mediagoblin/utils/report.html index 3829de97..8da2be7d 100644 --- a/mediagoblin/templates/mediagoblin/utils/report.html +++ b/mediagoblin/templates/mediagoblin/utils/report.html @@ -19,7 +19,7 @@ {% block report_content -%} <p> <a href="{{ request.urlgen('mediagoblin.user_pages.media_home.report_media', - user=media.get_uploader.username, + user=media.get_actor.username, media=media.slug_or_id) }}" class="button_action" id="button_reportmedia" title="Report media"> {% trans %}Report media{% endtrans %} diff --git a/mediagoblin/templates/mediagoblin/utils/tags.html b/mediagoblin/templates/mediagoblin/utils/tags.html index bb4bd1a5..5fc5dd8f 100644 --- a/mediagoblin/templates/mediagoblin/utils/tags.html +++ b/mediagoblin/templates/mediagoblin/utils/tags.html @@ -28,17 +28,17 @@ <a href="{{ request.urlgen( 'mediagoblin.user_pages.user_tag_gallery', tag=tag['slug'], - user=media.get_uploader.username) }}">{{ tag['name'] }}</a> + user=media.get_actor.username) }}">{{ tag['name'] }}</a> {% elif loop.revindex == 2 %} <a href="{{ request.urlgen( 'mediagoblin.user_pages.user_tag_gallery', tag=tag['slug'], - user=media.get_uploader.username) }}">{{ tag['name'] }}</a> + user=media.get_actor.username) }}">{{ tag['name'] }}</a> {% else %} <a href="{{ request.urlgen( 'mediagoblin.user_pages.user_tag_gallery', tag=tag['slug'], - user=media.get_uploader.username) }}">{{ tag['name'] }}</a> + user=media.get_actor.username) }}">{{ tag['name'] }}</a> · {% endif %} {% endfor %} diff --git a/mediagoblin/tests/__init__.py b/mediagoblin/tests/__init__.py index fbf3fc6c..651dac24 100644 --- a/mediagoblin/tests/__init__.py +++ b/mediagoblin/tests/__init__.py @@ -16,7 +16,7 @@ import pytest -from mediagoblin.db.models import User +from mediagoblin.db.models import User, LocalUser from mediagoblin.tests.tools import fixture_add_user from mediagoblin.tools import template @@ -44,7 +44,7 @@ class MGClientTestCase: fixture_add_user(username, **options) def user(self, username): - return User.query.filter(User.username == username).first() + return LocalUser.query.filter(LocalUser.username==username).first() def _do_request(self, url, *context_keys, **kwargs): template.clear_test_template_context() diff --git a/mediagoblin/tests/test_api.py b/mediagoblin/tests/test_api.py index 83003875..10bf08fe 100644 --- a/mediagoblin/tests/test_api.py +++ b/mediagoblin/tests/test_api.py @@ -25,7 +25,7 @@ from webtest import AppError from .resources import GOOD_JPG from mediagoblin import mg_globals -from mediagoblin.db.models import User, MediaEntry, MediaComment +from mediagoblin.db.models import User, Activity, MediaEntry, TextComment from mediagoblin.tools.routing import extract_url_arguments from mediagoblin.tests.tools import fixture_add_user from mediagoblin.moderation.tools import take_away_privileges @@ -188,9 +188,8 @@ class TestAPI(object): # Lets change the image uploader to be self.other_user, this is easier # than uploading the image as someone else as the way self.mocked_oauth_required # and self._upload_image. - id = int(data["object"]["id"].split("/")[-2]) - media = MediaEntry.query.filter_by(id=id).first() - media.uploader = self.other_user.id + media = MediaEntry.query.filter_by(public_id=data["object"]["id"]).first() + media.actor = self.other_user.id media.save() # Now lets try and edit the image as self.user, this should produce a 403 error. @@ -232,14 +231,13 @@ class TestAPI(object): image = json.loads(response.body.decode())["object"] # Check everything has been set on the media correctly - id = int(image["id"].split("/")[-2]) - media = MediaEntry.query.filter_by(id=id).first() + media = MediaEntry.query.filter_by(public_id=image["id"]).first() assert media.title == title assert media.description == description assert media.license == license # Check we're being given back everything we should on an update - assert int(image["id"].split("/")[-2]) == media.id + assert image["id"] == media.public_id assert image["displayName"] == title assert image["content"] == description assert image["license"] == license @@ -288,8 +286,7 @@ class TestAPI(object): request = test_app.get(object_uri) image = json.loads(request.body.decode()) - entry_id = int(image["id"].split("/")[-2]) - entry = MediaEntry.query.filter_by(id=entry_id).first() + entry = MediaEntry.query.filter_by(public_id=image["id"]).first() assert request.status_code == 200 @@ -319,12 +316,11 @@ class TestAPI(object): assert response.status_code == 200 # Find the objects in the database - media_id = int(data["object"]["id"].split("/")[-2]) - media = MediaEntry.query.filter_by(id=media_id).first() + media = MediaEntry.query.filter_by(public_id=data["object"]["id"]).first() comment = media.get_comments()[0] # Tests that it matches in the database - assert comment.author == self.user.id + assert comment.actor == self.user.id assert comment.content == content # Test that the response is what we should be given @@ -382,9 +378,8 @@ class TestAPI(object): response, comment_data = self._activity_to_feed(test_app, activity) # change who uploaded the comment as it's easier than changing - comment_id = int(comment_data["object"]["id"].split("/")[-2]) - comment = MediaComment.query.filter_by(id=comment_id).first() - comment.author = self.other_user.id + comment = TextComment.query.filter_by(public_id=comment_data["object"]["id"]).first() + comment.actor = self.other_user.id comment.save() # Update the comment as someone else. @@ -463,7 +458,40 @@ class TestAPI(object): # Check that image i uploaded is there assert feed["items"][0]["verb"] == "post" - assert feed["items"][0]["actor"] + assert feed["items"][0]["id"] == data["id"] + assert feed["items"][0]["object"]["objectType"] == "image" + assert feed["items"][0]["object"]["id"] == data["object"]["id"] + + + def test_read_another_feed(self, test_app): + """ Test able to read objects from someone else's feed """ + response, data = self._upload_image(test_app, GOOD_JPG) + response, data = self._post_image_to_feed(test_app, data) + + # Change the active user to someone else. + self.active_user = self.other_user + + # Fetch the feed + url = "/api/user/{0}/feed".format(self.user.username) + with self.mock_oauth(): + response = test_app.get(url) + feed = json.loads(response.body.decode()) + + assert response.status_code == 200 + + # Check it has the attributes it ought to. + assert "displayName" in feed + assert "objectTypes" in feed + assert "url" in feed + assert "links" in feed + assert "author" in feed + assert "items" in feed + + # Assert the uploaded image is there + assert feed["items"][0]["verb"] == "post" + assert feed["items"][0]["id"] == data["id"] + assert feed["items"][0]["object"]["objectType"] == "image" + assert feed["items"][0]["object"]["id"] == data["object"]["id"] def test_cant_post_to_someone_elses_feed(self, test_app): """ Test that can't post to someone elses feed """ @@ -510,8 +538,7 @@ class TestAPI(object): response = self._activity_to_feed(test_app, activity)[1] # Check the media is no longer in the database - media_id = int(object_id.split("/")[-2]) - media = MediaEntry.query.filter_by(id=media_id).first() + media = MediaEntry.query.filter_by(public_id=object_id).first() assert media is None @@ -552,8 +579,8 @@ class TestAPI(object): delete = self._activity_to_feed(test_app, activity)[1] # Verify the comment no longer exists - comment_id = int(comment["object"]["id"].split("/")[-2]) - assert MediaComment.query.filter_by(id=comment_id).first() is None + assert TextComment.query.filter_by(public_id=comment["object"]["id"]).first() is None + comment_id = comment["object"]["id"] # Check we've got a delete activity back assert "id" in delete @@ -593,8 +620,6 @@ class TestAPI(object): comment = self._activity_to_feed(test_app, activity)[1] # Verify the comment reflects the changes - comment_id = int(comment["object"]["id"].split("/")[-2]) - model = MediaComment.query.filter_by(id=comment_id).first() + model = TextComment.query.filter_by(public_id=comment["object"]["id"]).first() assert model.content == activity["object"]["content"] - diff --git a/mediagoblin/tests/test_auth.py b/mediagoblin/tests/test_auth.py index 5ce67688..62f77f74 100644 --- a/mediagoblin/tests/test_auth.py +++ b/mediagoblin/tests/test_auth.py @@ -23,7 +23,7 @@ import six import six.moves.urllib.parse as urlparse from mediagoblin import mg_globals -from mediagoblin.db.models import User +from mediagoblin.db.models import User, LocalUser from mediagoblin.tests.tools import get_app, fixture_add_user from mediagoblin.tools import template, mail from mediagoblin.auth import tools as auth_tools @@ -98,8 +98,9 @@ def test_register_views(test_app): 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'angrygirl').first() + new_user = mg_globals.database.LocalUser.query.filter( + LocalUser.username==u'angrygirl' + ).first() assert new_user ## Make sure that the proper privileges are granted on registration @@ -137,8 +138,9 @@ 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'angrygirl').first() + new_user = mg_globals.database.LocalUser.query.filter( + LocalUser.username==u'angrygirl' + ).first() assert new_user ## Verify the email activation works @@ -149,8 +151,9 @@ def test_register_views(test_app): 'mediagoblin/user_pages/user.html'] # 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'angrygirl').first() + new_user = mg_globals.database.LocalUser.query.filter( + LocalUser.username==u'angrygirl' + ).first() assert new_user # Uniqueness checks diff --git a/mediagoblin/tests/test_basic_auth.py b/mediagoblin/tests/test_basic_auth.py index e7157bee..3a42e407 100644 --- a/mediagoblin/tests/test_basic_auth.py +++ b/mediagoblin/tests/test_basic_auth.py @@ -16,7 +16,7 @@ import six.moves.urllib.parse as urlparse -from mediagoblin.db.models import User +from mediagoblin.db.models import User, LocalUser from mediagoblin.plugins.basic_auth import tools as auth_tools from mediagoblin.tests.tools import fixture_add_user from mediagoblin.tools import template @@ -88,7 +88,7 @@ def test_change_password(test_app): assert urlparse.urlsplit(res.location)[2] == '/edit/account/' # test_user has to be fetched again in order to have the current values - test_user = User.query.filter_by(username=u'chris').first() + test_user = LocalUser.query.filter(LocalUser.username==u'chris').first() assert auth_tools.bcrypt_check_password('123456', test_user.pw_hash) # test that the password cannot be changed if the given @@ -100,5 +100,5 @@ def test_change_password(test_app): 'new_password': '098765', }) - test_user = User.query.filter_by(username=u'chris').first() + test_user = LocalUser.query.filter(LocalUser.username==u'chris').first() assert not auth_tools.bcrypt_check_password('098765', test_user.pw_hash) diff --git a/mediagoblin/tests/test_csrf_middleware.py b/mediagoblin/tests/test_csrf_middleware.py index a272caf6..4452112b 100644 --- a/mediagoblin/tests/test_csrf_middleware.py +++ b/mediagoblin/tests/test_csrf_middleware.py @@ -25,7 +25,7 @@ def test_csrf_cookie_set(test_app): # assert that the mediagoblin nonce cookie has been set assert 'Set-Cookie' in response.headers - assert cookie_name in response.cookies_set + assert cookie_name in test_app.cookies # assert that we're also sending a vary header assert response.headers.get('Vary', False) == 'Cookie' @@ -34,7 +34,7 @@ def test_csrf_cookie_set(test_app): # We need a fresh app for this test on webtest < 1.3.6. # We do not understand why, but it fixes the tests. # If we require webtest >= 1.3.6, we can switch to a non fresh app here. -# +# # ... this comment might be irrelevant post-pytest-fixtures, but I'm not # removing it yet in case we move to module-level tests :) # -- cwebber diff --git a/mediagoblin/tests/test_edit.py b/mediagoblin/tests/test_edit.py index 384929cb..632c8e3c 100644 --- a/mediagoblin/tests/test_edit.py +++ b/mediagoblin/tests/test_edit.py @@ -19,7 +19,7 @@ import six.moves.urllib.parse as urlparse import pytest from mediagoblin import mg_globals -from mediagoblin.db.models import User, MediaEntry +from mediagoblin.db.models import User, LocalUser, MediaEntry from mediagoblin.tests.tools import fixture_add_user, fixture_media_entry from mediagoblin import auth from mediagoblin.tools import template, mail @@ -44,12 +44,12 @@ class TestUserEdit(object): self.login(test_app) # Make sure user exists - assert User.query.filter_by(username=u'chris').first() + assert LocalUser.query.filter(LocalUser.username==u'chris').first() res = test_app.post('/edit/account/delete/', {'confirmed': 'y'}) # Make sure user has been deleted - assert User.query.filter_by(username=u'chris').first() == None + assert LocalUser.query.filter(LocalUser.username==u'chris').first() == None #TODO: make sure all corresponding items comments etc have been # deleted too. Perhaps in submission test? @@ -79,7 +79,7 @@ class TestUserEdit(object): 'bio': u'I love toast!', 'url': u'http://dustycloud.org/'}) - test_user = User.query.filter_by(username=u'chris').first() + test_user = LocalUser.query.filter(LocalUser.username==u'chris').first() assert test_user.bio == u'I love toast!' assert test_user.url == u'http://dustycloud.org/' @@ -159,9 +159,10 @@ class TestUserEdit(object): assert urlparse.urlsplit(res.location)[2] == '/' # Email shouldn't be saved - email_in_db = mg_globals.database.User.query.filter_by( - email='new@example.com').first() - email = User.query.filter_by(username='chris').first().email + email_in_db = mg_globals.database.LocalUser.query.filter( + LocalUser.email=='new@example.com' + ).first() + email = LocalUser.query.filter(LocalUser.username=='chris').first().email assert email_in_db is None assert email == 'chris@example.com' @@ -172,7 +173,7 @@ class TestUserEdit(object): res.follow() # New email saved? - email = User.query.filter_by(username='chris').first().email + email = LocalUser.query.filter(LocalUser.username=='chris').first().email assert email == 'new@example.com' # test changing the url inproperly @@ -181,8 +182,10 @@ class TestMetaDataEdit: def setup(self, test_app): # set up new user self.user_password = u'toast' - self.user = fixture_add_user(password = self.user_password, - privileges=[u'active',u'admin']) + self.user = fixture_add_user( + password = self.user_password, + privileges=[u'active',u'admin'] + ) self.test_app = test_app def login(self, test_app): diff --git a/mediagoblin/tests/test_exif.py b/mediagoblin/tests/test_exif.py index e3869ec8..d0495a7a 100644 --- a/mediagoblin/tests/test_exif.py +++ b/mediagoblin/tests/test_exif.py @@ -306,7 +306,7 @@ def test_exif_extraction(): 'Image Orientation': {'field_length': 2, 'field_offset': 42, 'field_type': 3, - 'printable': u'Rotated 90 CCW', + 'printable': u'Rotated 90 CW', 'tag': 274, 'values': [6]}, 'Image ResolutionUnit': {'field_length': 2, @@ -388,8 +388,10 @@ def test_exif_image_orientation(): assert image.size in ((428, 640), (640, 428)) # If this pixel looks right, the rest of the image probably will too. + # It seems different values are being seen on different platforms/systems + # as of ccca39f1 it seems we're adding to the list those which are seen. assert_in(image.getdata()[10000], - ((41, 28, 11), (43, 27, 11)) + ((37, 23, 14), (41, 28, 11), (43, 27, 11)) ) diff --git a/mediagoblin/tests/test_ldap.py b/mediagoblin/tests/test_ldap.py index f251d150..6ac0fc46 100644 --- a/mediagoblin/tests/test_ldap.py +++ b/mediagoblin/tests/test_ldap.py @@ -26,6 +26,7 @@ import six.moves.urllib.parse as urlparse from mediagoblin import mg_globals from mediagoblin.db.base import Session +from mediagoblin.db.models import LocalUser from mediagoblin.tests.tools import get_app from mediagoblin.tools import template @@ -114,8 +115,9 @@ def test_ldap_plugin(ldap_plugin_app): ldap_plugin_app.get('/auth/logout/') # Get user and detach from session - test_user = mg_globals.database.User.query.filter_by( - username=u'chris').first() + test_user = mg_globals.database.LocalUser.query.filter( + LocalUser.username==u'chris' + ).first() Session.expunge(test_user) # Log back in diff --git a/mediagoblin/tests/test_misc.py b/mediagoblin/tests/test_misc.py index b3e59c09..558a9bd7 100644 --- a/mediagoblin/tests/test_misc.py +++ b/mediagoblin/tests/test_misc.py @@ -24,7 +24,7 @@ from mediagoblin.db.base import Session from mediagoblin.media_types import sniff_media from mediagoblin.submit.lib import new_upload_entry from mediagoblin.submit.task import collect_garbage -from mediagoblin.db.models import User, MediaEntry, MediaComment +from mediagoblin.db.models import User, MediaEntry, TextComment, Comment from mediagoblin.tests.tools import fixture_add_user, fixture_media_entry @@ -46,25 +46,31 @@ def test_user_deletes_other_comments(test_app): Session.flush() # Create all 4 possible comments: - for u_id in (user_a.id, user_b.id): - for m_id in (media_a.id, media_b.id): - cmt = MediaComment() - cmt.media_entry = m_id - cmt.author = u_id + for u in (user_a, user_b): + for m in (media_a, media_b): + cmt = TextComment() + cmt.actor = u.id cmt.content = u"Some Comment" Session.add(cmt) + # think i need this to get the command ID + Session.flush() + + link = Comment() + link.target = m + link.comment = cmt + Session.add(link) Session.flush() usr_cnt1 = User.query.count() med_cnt1 = MediaEntry.query.count() - cmt_cnt1 = MediaComment.query.count() + cmt_cnt1 = TextComment.query.count() User.query.get(user_a.id).delete(commit=False) usr_cnt2 = User.query.count() med_cnt2 = MediaEntry.query.count() - cmt_cnt2 = MediaComment.query.count() + cmt_cnt2 = TextComment.query.count() # One user deleted assert usr_cnt2 == usr_cnt1 - 1 @@ -77,7 +83,7 @@ def test_user_deletes_other_comments(test_app): usr_cnt2 = User.query.count() med_cnt2 = MediaEntry.query.count() - cmt_cnt2 = MediaComment.query.count() + cmt_cnt2 = TextComment.query.count() # All users gone assert usr_cnt2 == usr_cnt1 - 2 diff --git a/mediagoblin/tests/test_modelmethods.py b/mediagoblin/tests/test_modelmethods.py index 1ab0c476..4c66e27b 100644 --- a/mediagoblin/tests/test_modelmethods.py +++ b/mediagoblin/tests/test_modelmethods.py @@ -20,8 +20,8 @@ from __future__ import print_function from mediagoblin.db.base import Session -from mediagoblin.db.models import MediaEntry, User, Privilege, Activity, \ - Generator +from mediagoblin.db.models import MediaEntry, User, LocalUser, Privilege, \ + Activity, Generator from mediagoblin.tests import MGClientTestCase from mediagoblin.tests.tools import fixture_add_user, fixture_media_entry, \ @@ -56,7 +56,7 @@ class TestMediaEntrySlugs(object): entry.title = title or u"Some title" entry.slug = slug entry.id = this_id - entry.uploader = uploader or self.chris_user.id + entry.actor = uploader or self.chris_user.id entry.media_type = u'image' if save: @@ -168,10 +168,10 @@ class TestUserHasPrivilege: 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() + self.natalie_user = LocalUser.query.filter( + LocalUser.username==u'natalie').first() + self.aeva_user = LocalUser.query.filter( + LocalUser.username==u'aeva').first() def test_privilege_added_correctly(self, test_app): self._setup() @@ -232,55 +232,3 @@ class TestUserUrlForSelf(MGClientTestCase): self.user(u'lindsay').url_for_self(fake_urlgen()) assert excinfo.errisinstance(TypeError) assert 'object is not callable' in str(excinfo) - -class TestActivitySetGet(object): - """ Test methods on the Activity and ActivityIntermediator models """ - - @pytest.fixture(autouse=True) - def setup(self, test_app): - self.app = test_app - self.user = fixture_add_user() - self.obj = fixture_media_entry() - self.target = fixture_media_entry() - - def test_set_activity_object(self): - """ Activity.set_object should produce ActivityIntermediator """ - # The fixture will set self.obj as the object on the activity. - activity = fixture_add_activity(self.obj, actor=self.user) - - # Assert the media has been associated with an AI - assert self.obj.activity is not None - - # Assert the AI on the media and object are the same - assert activity.object == self.obj.activity - - def test_activity_set_target(self): - """ Activity.set_target should produce ActivityIntermediator """ - # This should set everything needed on the target - activity = fixture_add_activity(self.obj, actor=self.user) - activity.set_target(self.target) - - # Assert the media has been associated with the AI - assert self.target.activity is not None - - # assert the AI on the media and target are the same - assert activity.target == self.target.activity - - def test_get_activity_object(self): - """ Activity.get_object should return a set object """ - activity = fixture_add_activity(self.obj, actor=self.user) - - print("self.obj.activity = {0}".format(self.obj.activity)) - - # check we now can get the object - assert activity.get_object is not None - assert activity.get_object.id == self.obj.id - - def test_get_activity_target(self): - """ Activity.set_target should return a set target """ - activity = fixture_add_activity(self.obj, actor=self.user) - activity.set_target(self.target) - - # check we can get the target - assert activity.get_target is not None - assert activity.get_target.id == self.target.id diff --git a/mediagoblin/tests/test_moderation.py b/mediagoblin/tests/test_moderation.py index e7a0ebef..55bb4c4b 100644 --- a/mediagoblin/tests/test_moderation.py +++ b/mediagoblin/tests/test_moderation.py @@ -18,7 +18,8 @@ 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.db.models import User, LocalUser, Report, TextComment, \ + UserBan, GenericModelReference from mediagoblin.tools import template, mail from webtest import AppError @@ -47,9 +48,9 @@ class TestModerationViews: 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() + self.admin_user = LocalUser.query.filter(LocalUser.username==u'admin').first() + self.mod_user = LocalUser.query.filter(LocalUser.username==u'moderator').first() + self.user = LocalUser.query.filter(LocalUser.username==u'regular').first() def do_post(self, data, *context_keys, **kwargs): url = kwargs.pop('url', '/submit/') @@ -102,15 +103,15 @@ class TestModerationViews: # to a reported comment #---------------------------------------------------------------------- fixture_add_comment_report(reported_user=self.user) - comment_report = CommentReport.query.filter( - CommentReport.reported_user==self.user).first() + comment_report = Report.query.filter( + Report.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() + comment_report = Report.query.filter( + Report.reported_user==self.user).first() response, context = self.do_post({'action_to_resolve':[u'takeaway'], 'take_away_privileges':[u'commenter'], @@ -118,15 +119,15 @@ class TestModerationViews: url='/mod/reports/{0}/'.format(comment_report.id)) self.query_for_users() - comment_report = CommentReport.query.filter( - CommentReport.reported_user==self.user).first() + comment_report = Report.query.filter( + Report.reported_user==self.user).first() assert response.status == '302 FOUND' assert not self.user.has_privilege(u'commenter') assert comment_report.is_archived_report() is True fixture_add_comment_report(reported_user=self.user) - comment_report = CommentReport.query.filter( - CommentReport.reported_user==self.user).first() + comment_report = Report.query.filter( + Report.reported_user==self.user).first() # Then, test a moderator sending an email to a user in response to a # reported comment @@ -139,8 +140,8 @@ class TestModerationViews: url='/mod/reports/{0}/'.format(comment_report.id)) self.query_for_users() - comment_report = CommentReport.query.filter( - CommentReport.reported_user==self.user).first() + comment_report = Report.query.filter( + Report.reported_user==self.user).first() assert response.status == '302 FOUND' assert mail.EMAIL_TEST_MBOX_INBOX == [{'to': [u'regular@example.com'], 'message': 'Content-Type: text/plain; charset="utf-8"\n\ @@ -157,13 +158,17 @@ VGhpcyBpcyB5b3VyIGxhc3Qgd2FybmluZywgcmVndWxhci4uLi4=\n', 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() + test_comment = TextComment.query.filter( + TextComment.actor==self.user.id).first() fixture_add_comment_report(comment=test_comment, reported_user=self.user) - comment_report = CommentReport.query.filter( - CommentReport.comment==test_comment).filter( - CommentReport.resolved==None).first() + comment_gmr = GenericModelReference.query.filter_by( + obj_pk=test_comment.id, + model_type=test_comment.__tablename__ + ).first() + comment_report = Report.query.filter( + Report.object_id==comment_gmr.id).filter( + Report.resolved==None).first() response, context = self.do_post( {'action_to_resolve':[u'userban', u'delete'], @@ -176,17 +181,17 @@ VGhpcyBpcyB5b3VyIGxhc3Qgd2FybmluZywgcmVndWxhci4uLi4=\n', 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() + test_comment = TextComment.query.filter( + TextComment.actor==self.user.id).first() assert test_comment is None # Then, test what happens when a moderator attempts to punish an admin # from a reported comment on an admin. #---------------------------------------------------------------------- fixture_add_comment_report(reported_user=self.admin_user) - comment_report = CommentReport.query.filter( - CommentReport.reported_user==self.admin_user).filter( - CommentReport.resolved==None).first() + comment_report = Report.query.filter( + Report.reported_user==self.admin_user).filter( + Report.resolved==None).first() response, context = self.do_post({'action_to_resolve':[u'takeaway'], 'take_away_privileges':[u'active'], diff --git a/mediagoblin/tests/test_notifications.py b/mediagoblin/tests/test_notifications.py index 385da569..19bf8665 100644 --- a/mediagoblin/tests/test_notifications.py +++ b/mediagoblin/tests/test_notifications.py @@ -20,8 +20,7 @@ import six.moves.urllib.parse as urlparse from mediagoblin.tools import template, mail -from mediagoblin.db.models import Notification, CommentNotification, \ - CommentSubscription +from mediagoblin.db.models import Notification, CommentSubscription from mediagoblin.db.base import Session from mediagoblin.notifications import mark_comment_notification_seen @@ -109,28 +108,41 @@ class TestNotifications: notification = notifications[0] - assert type(notification) == CommentNotification assert notification.seen == False assert notification.user_id == user.id - assert notification.subject.get_author.id == self.test_user.id - assert notification.subject.content == u'Test comment #42' + assert notification.obj().get_actor.id == self.test_user.id + assert notification.obj().content == u'Test comment #42' if wants_email == True: - assert mail.EMAIL_TEST_MBOX_INBOX == [ - {'from': 'notice@mediagoblin.example.org', - 'message': 'Content-Type: text/plain; \ + # Why the `or' here? In Werkzeug 0.11.0 and above + # werkzeug stopped showing the port for localhost when + # rendering something like this. As long as we're + # supporting pre-0.11.0 we'll keep this `or', but maybe + # in the future we can kill it. + assert ( + mail.EMAIL_TEST_MBOX_INBOX == [ + {'from': 'notice@mediagoblin.example.org', + 'message': 'Content-Type: text/plain; \ charset="utf-8"\nMIME-Version: 1.0\nContent-Transfer-Encoding: \ base64\nSubject: GNU MediaGoblin - chris commented on your \ post\nFrom: notice@mediagoblin.example.org\nTo: \ otherperson@example.com\n\nSGkgb3RoZXJwZXJzb24sCmNocmlzIGNvbW1lbnRlZCBvbiB5b3VyIHBvc3QgKGh0dHA6Ly9sb2Nh\nbGhvc3Q6ODAvdS9vdGhlcnBlcnNvbi9tL3NvbWUtdGl0bGUvYy8xLyNjb21tZW50KSBhdCBHTlUg\nTWVkaWFHb2JsaW4KClRlc3QgY29tbWVudCAjNDIKCkdOVSBNZWRpYUdvYmxpbg==\n', - 'to': [u'otherperson@example.com']}] + 'to': [u'otherperson@example.com']}] + or mail.EMAIL_TEST_MBOX_INBOX == [ + {'from': 'notice@mediagoblin.example.org', + 'message': 'Content-Type: text/plain; \ +charset="utf-8"\nMIME-Version: 1.0\nContent-Transfer-Encoding: \ +base64\nSubject: GNU MediaGoblin - chris commented on your \ +post\nFrom: notice@mediagoblin.example.org\nTo: \ +otherperson@example.com\n\nSGkgb3RoZXJwZXJzb24sCmNocmlzIGNvbW1lbnRlZCBvbiB5b3VyIHBvc3QgKGh0dHA6Ly9sb2Nh\nbGhvc3QvdS9vdGhlcnBlcnNvbi9tL3NvbWUtdGl0bGUvYy8xLyNjb21tZW50KSBhdCBHTlUgTWVk\naWFHb2JsaW4KClRlc3QgY29tbWVudCAjNDIKCkdOVSBNZWRpYUdvYmxpbg==\n', + 'to': [u'otherperson@example.com']}]) else: assert mail.EMAIL_TEST_MBOX_INBOX == [] # Save the ids temporarily because of DetachedInstanceError notification_id = notification.id - comment_id = notification.subject.id + comment_id = notification.obj().get_comment_link().id self.logout() self.login('otherperson', 'nosreprehto') diff --git a/mediagoblin/tests/test_openid.py b/mediagoblin/tests/test_openid.py index a3ab176a..71767032 100644 --- a/mediagoblin/tests/test_openid.py +++ b/mediagoblin/tests/test_openid.py @@ -28,7 +28,7 @@ openid_consumer = pytest.importorskip( from mediagoblin import mg_globals from mediagoblin.db.base import Session -from mediagoblin.db.models import User +from mediagoblin.db.models import User, LocalUser from mediagoblin.plugins.openid.models import OpenIDUserURL from mediagoblin.tests.tools import get_app, fixture_add_user from mediagoblin.tools import template @@ -192,8 +192,9 @@ class TestOpenIDPlugin(object): openid_plugin_app.get('/auth/logout') # Get user and detach from session - test_user = mg_globals.database.User.query.filter_by( - username=u'chris').first() + test_user = mg_globals.database.LocalUser.query.filter( + LocalUser.username==u'chris' + ).first() Session.expunge(test_user) # Log back in diff --git a/mediagoblin/tests/test_persona.py b/mediagoblin/tests/test_persona.py index a8466b8a..437cb7a1 100644 --- a/mediagoblin/tests/test_persona.py +++ b/mediagoblin/tests/test_persona.py @@ -28,7 +28,7 @@ pytest.importorskip("requests") from mediagoblin import mg_globals from mediagoblin.db.base import Session -from mediagoblin.db.models import Privilege +from mediagoblin.db.models import Privilege, LocalUser from mediagoblin.tests.tools import get_app from mediagoblin.tools import template @@ -117,14 +117,16 @@ class TestPersonaPlugin(object): persona_plugin_app.get('/auth/logout/') # Get user and detach from session - test_user = mg_globals.database.User.query.filter_by( - username=u'chris').first() + test_user = mg_globals.database.LocalUser.query.filter( + LocalUser.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() + test_user = mg_globals.database.LocalUser.query.filter( + LocalUser.username==u'chris' + ).first() Session.expunge(test_user) # Add another user for _test_edit_persona diff --git a/mediagoblin/tests/test_privileges.py b/mediagoblin/tests/test_privileges.py index 8ea3d754..2e0b7347 100644 --- a/mediagoblin/tests/test_privileges.py +++ b/mediagoblin/tests/test_privileges.py @@ -21,7 +21,7 @@ from webtest import AppError from mediagoblin.tests.tools import fixture_add_user, fixture_media_entry -from mediagoblin.db.models import User, UserBan +from mediagoblin.db.models import User, LocalUser, UserBan from mediagoblin.tools import template from .resources import GOOD_JPG @@ -64,9 +64,9 @@ class TestPrivilegeFunctionality: return response, context_data def query_for_users(self): - self.admin_user = User.query.filter(User.username==u'alex').first() - self.mod_user = User.query.filter(User.username==u'meow').first() - self.user = User.query.filter(User.username==u'natalie').first() + self.admin_user = LocalUser.query.filter(LocalUser.username==u'alex').first() + self.mod_user = LocalUser.query.filter(LocalUser.username==u'meow').first() + self.user = LocalUser.query.filter(LocalUser.username==u'natalie').first() def testUserBanned(self): self.login(u'natalie') diff --git a/mediagoblin/tests/test_reporting.py b/mediagoblin/tests/test_reporting.py index 6a9fe205..803fc849 100644 --- a/mediagoblin/tests/test_reporting.py +++ b/mediagoblin/tests/test_reporting.py @@ -20,8 +20,7 @@ import six 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) +from mediagoblin.db.models import Report, User, LocalUser, TextComment class TestReportFiling: @@ -56,8 +55,8 @@ class TestReportFiling: 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()) + return (LocalUser.query.filter(LocalUser.username==u'allie').first(), + LocalUser.query.filter(LocalUser.username==u'natalie').first()) def testMediaReports(self): self.login(u'allie') @@ -80,7 +79,7 @@ class TestReportFiling: assert response.status == "302 FOUND" - media_report = MediaReport.query.first() + media_report = Report.query.first() allie_user, natalie_user = self.query_for_users() assert media_report is not None @@ -88,7 +87,6 @@ class TestReportFiling: 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') @@ -98,9 +96,11 @@ class TestReportFiling: 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() + fixture_add_comment( + media_entry=media_entry, + author=natalie_user.id + ) + comment = TextComment.query.first() comment_uri_slug = '/u/{0}/m/{1}/c/{2}/'.format(natalie_user.username, media_entry.slug, @@ -115,7 +115,7 @@ class TestReportFiling: assert response.status == "302 FOUND" - comment_report = CommentReport.query.first() + comment_report = Report.query.first() allie_user, natalie_user = self.query_for_users() assert comment_report is not None @@ -123,7 +123,6 @@ class TestReportFiling: 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') @@ -132,14 +131,14 @@ class TestReportFiling: fixture_add_comment(author=allie_user.id, comment=u'Comment will be removed') - test_comment = MediaComment.query.filter( - MediaComment.author==allie_user.id).first() + test_comment = TextComment.query.filter( + TextComment.actor==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() + comment_report = Report.query.filter( + Report.reported_user==allie_user).first() assert comment_report.report_content == u'Testing Archived Reports #1' response, context = self.do_post( @@ -151,10 +150,10 @@ class TestReportFiling: assert response.status == "302 FOUND" allie_user, natalie_user = self.query_for_users() - archived_report = CommentReport.query.filter( - CommentReport.reported_user==allie_user).first() + archived_report = Report.query.filter( + Report.reported_user==allie_user).first() - assert CommentReport.query.count() != 0 + assert Report.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 @@ -164,5 +163,3 @@ class TestReportFiling: 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/tests/test_response.py b/mediagoblin/tests/test_response.py new file mode 100644 index 00000000..7f929155 --- /dev/null +++ b/mediagoblin/tests/test_response.py @@ -0,0 +1,65 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from __future__ import absolute_import, unicode_literals + +from werkzeug.wrappers import Request + +from ..tools.response import redirect, redirect_obj + +class TestRedirect(object): + def test_redirect_respects_location(self): + """Test that redirect returns a 302 to location specified.""" + request = Request({}) + response = redirect(request, location='/test') + assert response.status_code == 302 + assert response.location == '/test' + + def test_redirect_respects_querystring(self): + """Test that redirect includes querystring in returned location.""" + request = Request({}) + response = redirect(request, location='', querystring='#baz') + assert response.location == '#baz' + + def test_redirect_respects_urlgen_args(self): + """Test that redirect returns a 302 to location from urlgen args.""" + + # Using a mock urlgen here so we're only testing redirect itself. We + # could instantiate a url_map and map_adaptor with WSGI environ as per + # app.py, but that would really just be testing Werkzeug. + def urlgen(endpoint, **kwargs): + return '/test?foo=bar' + + request = Request({}) + request.urlgen = urlgen + response = redirect(request, 'test-endpoint', foo='bar') + assert response.status_code == 302 + assert response.location == '/test?foo=bar' + + def test_redirect_obj_calls_url_for_self(self): + """Test that redirect_obj returns a 302 to obj's url_for_self().""" + + # Using a mock obj here so that we're only testing redirect_obj itself, + # rather than also testing the url_for_self implementation. + class Foo(object): + def url_for_self(*args, **kwargs): + return '/foo' + + request = Request({}) + request.urlgen = None + response = redirect_obj(request, Foo()) + assert response.status_code == 302 + assert response.location == '/foo' diff --git a/mediagoblin/tests/test_submission.py b/mediagoblin/tests/test_submission.py index 65c4b3a3..f9031d37 100644 --- a/mediagoblin/tests/test_submission.py +++ b/mediagoblin/tests/test_submission.py @@ -35,7 +35,7 @@ Gst.init(None) from mediagoblin.tests.tools import fixture_add_user from .media_tools import create_av from mediagoblin import mg_globals -from mediagoblin.db.models import MediaEntry, User +from mediagoblin.db.models import MediaEntry, User, LocalUser from mediagoblin.db.base import Session from mediagoblin.tools import template from mediagoblin.media_types.image import ImageMediaManager @@ -72,7 +72,7 @@ class TestSubmission: #### 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() + return LocalUser.query.filter(LocalUser.username==u'chris').first() def login(self): self.test_app.post( @@ -99,8 +99,14 @@ class TestSubmission: return {'upload_files': [('file', filename)]} def check_comments(self, request, media_id, count): - comments = request.db.MediaComment.query.filter_by(media_entry=media_id) - assert count == len(list(comments)) + gmr = request.db.GenericModelReference.query.filter_by( + obj_pk=media_id, + model_type=request.db.MediaEntry.__tablename__ + ).first() + if gmr is None and count <= 0: + return # Yerp it's fine. + comments = request.db.Comment.query.filter_by(target_id=gmr.id) + assert count == comments.count() def test_missing_fields(self): # Test blank form @@ -153,6 +159,16 @@ class TestSubmission: # Reload user assert self.our_user().uploaded == file_size + def test_public_id_populated(self): + # Upload the image first. + response, request = self.do_post({'title': u'Balanced Goblin'}, + *REQUEST_CONTEXT, do_follow=True, + **self.upload_data(GOOD_JPG)) + media = self.check_media(request, {'title': u'Balanced Goblin'}, 1) + + # Now check that the public_id attribute is set. + assert media.public_id != None + def test_normal_png(self): self.check_normal_upload(u'Normal upload 2', GOOD_PNG) diff --git a/mediagoblin/tests/tools.py b/mediagoblin/tests/tools.py index dec95e83..77a9a86c 100644 --- a/mediagoblin/tests/tools.py +++ b/mediagoblin/tests/tools.py @@ -25,9 +25,9 @@ from paste.deploy import loadapp from webtest import TestApp from mediagoblin import mg_globals -from mediagoblin.db.models import User, MediaEntry, Collection, MediaComment, \ - CommentSubscription, CommentNotification, Privilege, CommentReport, Client, \ - RequestToken, AccessToken, Activity, Generator +from mediagoblin.db.models import User, LocalUser, MediaEntry, Collection, TextComment, \ + CommentSubscription, Notification, Privilege, Report, Client, \ + RequestToken, AccessToken, Activity, Generator, Comment from mediagoblin.tools import testing from mediagoblin.init.config import read_mediagoblin_config from mediagoblin.db.base import Session @@ -176,9 +176,9 @@ def assert_db_meets_expected(db, expected): def fixture_add_user(username=u'chris', password=u'toast', privileges=[], wants_comment_notification=True): # Reuse existing user or create a new one - test_user = User.query.filter_by(username=username).first() + test_user = LocalUser.query.filter(LocalUser.username==username).first() if test_user is None: - test_user = User() + test_user = LocalUser() test_user.username = username test_user.email = username + u'@example.com' if password is not None: @@ -190,8 +190,11 @@ def fixture_add_user(username=u'chris', password=u'toast', test_user.all_privileges.append(query.one()) test_user.save() - # Reload - test_user = User.query.filter_by(username=username).first() + + # Reload - The `with_polymorphic` needs to be there to eagerly load + # the attributes on the LocalUser as this can't be done post detachment. + user_query = LocalUser.query.with_polymorphic(LocalUser) + test_user = user_query.filter(LocalUser.username==username).first() # ... and detach from session: Session.expunge(test_user) @@ -201,12 +204,12 @@ def fixture_add_user(username=u'chris', password=u'toast', def fixture_comment_subscription(entry, notify=True, send_email=None): if send_email is None: - uploader = User.query.filter_by(id=entry.uploader).first() - send_email = uploader.wants_comment_notification + actor = LocalUser.query.filter_by(id=entry.actor).first() + send_email = actor.wants_comment_notification cs = CommentSubscription( media_entry_id=entry.id, - user_id=entry.uploader, + user_id=entry.actor, notify=notify, send_email=send_email) @@ -219,14 +222,16 @@ def fixture_comment_subscription(entry, notify=True, send_email=None): return cs -def fixture_add_comment_notification(entry_id, subject_id, user_id, +def fixture_add_comment_notification(entry, subject, user, seen=False): - cn = CommentNotification(user_id=user_id, - seen=seen, - subject_id=subject_id) + cn = Notification( + user_id=user, + seen=seen, + ) + cn.obj = subject cn.save() - cn = CommentNotification.query.filter_by(id=cn.id).first() + cn = Notification.query.filter_by(id=cn.id).first() Session.expunge(cn) @@ -251,7 +256,7 @@ def fixture_media_entry(title=u"Some title", slug=None, entry = MediaEntry() entry.title = title entry.slug = slug - entry.uploader = uploader + entry.actor = uploader entry.media_type = u'image' entry.state = state @@ -275,15 +280,21 @@ def fixture_media_entry(title=u"Some title", slug=None, return entry -def fixture_add_collection(name=u"My first Collection", user=None): +def fixture_add_collection(name=u"My first Collection", user=None, + collection_type=Collection.USER_DEFINED_TYPE): if user is None: user = fixture_add_user() - coll = Collection.query.filter_by(creator=user.id, title=name).first() + coll = Collection.query.filter_by( + actor=user.id, + title=name, + type=collection_type + ).first() if coll is not None: return coll coll = Collection() - coll.creator = user.id + coll.actor = user.id coll.title = name + coll.type = collection_type coll.generate_slug() coll.save() @@ -300,22 +311,27 @@ def fixture_add_comment(author=None, media_entry=None, comment=None): author = fixture_add_user().id if media_entry is None: - media_entry = fixture_media_entry().id + media_entry = fixture_media_entry() if comment is None: comment = \ 'Auto-generated test comment by user #{0} on media #{0}'.format( author, media_entry) - comment = MediaComment(author=author, - media_entry=media_entry, - content=comment) + text_comment = TextComment( + actor=author, + content=comment + ) + text_comment.save() - comment.save() + comment_link = Comment() + comment_link.target = media_entry + comment_link.comment = text_comment + comment_link.save() - Session.expunge(comment) + Session.expunge(comment_link) - return comment + return text_comment def fixture_add_comment_report(comment=None, reported_user=None, reporter=None, created=None, report_content=None): @@ -335,12 +351,13 @@ def fixture_add_comment_report(comment=None, reported_user=None, report_content = \ 'Auto-generated test report' - comment_report = CommentReport(comment=comment, - reported_user = reported_user, - reporter = reporter, - created = created, - report_content=report_content) - + comment_report = Report() + comment_report.obj = comment + comment_report.reported_user = reported_user + comment_report.reporter = reporter + comment_report.created = created + comment_report.report_content = report_content + comment_report.obj = comment comment_report.save() Session.expunge(comment_report) @@ -370,4 +387,4 @@ def fixture_add_activity(obj, verb="post", target=None, generator=None, actor=No activity.set_target(target) activity.save() - return activity
\ No newline at end of file + return activity diff --git a/mediagoblin/tools/federation.py b/mediagoblin/tools/federation.py index 6c2d27da..f2ee468c 100644 --- a/mediagoblin/tools/federation.py +++ b/mediagoblin/tools/federation.py @@ -26,7 +26,7 @@ def create_generator(request): return None client = request.access_token.get_requesttoken.get_client - + # Check if there is a generator already generator = Generator.query.filter_by( name=client.application_name, @@ -40,8 +40,8 @@ def create_generator(request): generator.save() return generator - - + + def create_activity(verb, obj, actor, target=None, generator=None): """ @@ -71,15 +71,18 @@ def create_activity(verb, obj, actor, target=None, generator=None): ) generator.save() + # Ensure the object has an ID which is needed by the activity. + obj.save(commit=False) + + # Create the activity activity = Activity(verb=verb) - activity.set_object(obj) + activity.object = obj if target is not None: - activity.set_target(target) + activity.target = target # If they've set it override the actor from the obj. activity.actor = actor.id if isinstance(actor, User) else actor - activity.generator = generator.id activity.save() diff --git a/mediagoblin/user_pages/lib.py b/mediagoblin/user_pages/lib.py index 5b411a82..b6741001 100644 --- a/mediagoblin/user_pages/lib.py +++ b/mediagoblin/user_pages/lib.py @@ -16,8 +16,8 @@ from mediagoblin import mg_globals from mediagoblin.db.base import Session -from mediagoblin.db.models import (CollectionItem, MediaReport, CommentReport, - MediaComment, MediaEntry) +from mediagoblin.db.models import CollectionItem, Report, TextComment, \ + MediaEntry from mediagoblin.tools.mail import send_email from mediagoblin.tools.pluginapi import hook_runall from mediagoblin.tools.template import render_template @@ -38,11 +38,11 @@ def send_comment_email(user, comment, media, request): comment_url = request.urlgen( 'mediagoblin.user_pages.media_home.view_comment', comment=comment.id, - user=media.get_uploader.username, + user=media.get_actor.username, media=media.slug_or_id, qualified=True) + '#comment' - comment_author = comment.get_author.username + comment_author = comment.get_actor.username rendered_email = render_template( request, 'mediagoblin/user_pages/comment_email.txt', @@ -64,12 +64,12 @@ def send_comment_email(user, comment, media, request): def add_media_to_collection(collection, media, note=None, commit=True): collection_item = CollectionItem() collection_item.collection = collection.id - collection_item.media_entry = media.id + collection_item.get_object = media if note: collection_item.note = note Session.add(collection_item) - collection.items = collection.items + 1 + collection.num_items = collection.num_items + 1 Session.add(collection) Session.add(media) @@ -82,36 +82,29 @@ 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 - report) into either a MediaReport or CommentReport object. + report) into a Report. :param report_form A MediaReportForm or a CommentReportForm object with valid information from a POST request. :param media_entry A MediaEntry object. The MediaEntry being repo- - -rted by a MediaReport. In a CommentReport, - this will be None. - :param comment A MediaComment object. The MediaComment being - reported by a CommentReport. In a MediaReport - this will be None. - - :returns A MediaReport object if a valid MediaReportForm is - passed as kwarg media_entry. This MediaReport has + -rted by a Report. + :param comment A Comment object. The Comment being + reported by a Report. + + :returns A Report object if a valid MediaReportForm is + passed as kwarg media_entry. This Report 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. """ - + report_object = Report() 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 + report_object.obj = comment.comment() + report_object.reported_user_id = TextComment.query.get( + comment.id).get_actor.id elif report_form.validate() and media_entry is not None: - report_object = MediaReport() - report_object.media_entry_id = media_entry.id + report_object.obj = media_entry report_object.reported_user_id = MediaEntry.query.get( - media_entry.id).get_uploader.id + media_entry.id).get_actor.id else: return None diff --git a/mediagoblin/user_pages/views.py b/mediagoblin/user_pages/views.py index cc7f3684..ba94ec16 100644 --- a/mediagoblin/user_pages/views.py +++ b/mediagoblin/user_pages/views.py @@ -21,8 +21,9 @@ import json import six from mediagoblin import messages, mg_globals -from mediagoblin.db.models import (MediaEntry, MediaTag, Collection, - CollectionItem, User, Activity) +from mediagoblin.db.models import (MediaEntry, MediaTag, Collection, Comment, + CollectionItem, LocalUser, Activity, \ + GenericModelReference) from mediagoblin.tools.response import render_to_response, render_404, \ redirect, redirect_obj from mediagoblin.tools.text import cleaned_markdown_conversion @@ -53,8 +54,8 @@ _log.setLevel(logging.DEBUG) @user_not_banned @uses_pagination def user_home(request, page): - """'Homepage' of a User()""" - user = User.query.filter_by(username=request.matchdict['user']).first() + """'Homepage' of a LocalUser()""" + user = LocalUser.query.filter_by(username=request.matchdict['user']).first() if not user: return render_404(request) elif not user.has_privilege(u'active'): @@ -64,7 +65,7 @@ def user_home(request, page): {'user': user}) cursor = MediaEntry.query.\ - filter_by(uploader = user.id, + filter_by(actor = user.id, state = u'processed').order_by(MediaEntry.created.desc()) pagination = Pagination(page, cursor) @@ -90,10 +91,10 @@ def user_home(request, page): @active_user_from_url @uses_pagination def user_gallery(request, page, url_user=None): - """'Gallery' of a User()""" + """'Gallery' of a LocalUser()""" tag = request.matchdict.get('tag', None) cursor = MediaEntry.query.filter_by( - uploader=url_user.id, + actor=url_user.id, state=u'processed').order_by(MediaEntry.created.desc()) # Filter potentially by tag too: @@ -178,9 +179,8 @@ def media_post_comment(request, media): if not request.method == 'POST': raise MethodNotAllowed() - comment = request.db.MediaComment() - comment.media_entry = media.id - comment.author = request.user.id + comment = request.db.TextComment() + comment.actor = request.user.id comment.content = six.text_type(request.form['comment_content']) # Show error message if commenting is disabled. @@ -195,10 +195,15 @@ def media_post_comment(request, media): messages.ERROR, _("Oops, your comment was empty.")) else: - create_activity("post", comment, comment.author, target=media) + create_activity("post", comment, comment.actor, target=media) add_comment_subscription(request.user, media) comment.save() + link = request.db.Comment() + link.target = media + link.comment = comment + link.save() + messages.add_message( request, messages.SUCCESS, _('Your comment has been posted!')) @@ -228,7 +233,9 @@ def media_collect(request, media): form = user_forms.MediaCollectForm(request.form) # A user's own collections: form.collection.query = Collection.query.filter_by( - creator = request.user.id).order_by(Collection.title) + actor=request.user.id, + type=Collection.USER_DEFINED_TYPE + ).order_by(Collection.title) if request.method != 'POST' or not form.validate(): # No POST submission, or invalid form @@ -247,44 +254,51 @@ def media_collect(request, media): if form.collection_title.data: # Make sure this user isn't duplicating an existing collection existing_collection = Collection.query.filter_by( - creator=request.user.id, - title=form.collection_title.data).first() + actor=request.user.id, + title=form.collection_title.data, + type=Collection.USER_DEFINED_TYPE + ).first() if existing_collection: messages.add_message(request, messages.ERROR, _('You already have a collection called "%s"!') % existing_collection.title) return redirect(request, "mediagoblin.user_pages.media_home", - user=media.get_uploader.username, + user=media.get_actor.username, media=media.slug_or_id) collection = Collection() collection.title = form.collection_title.data collection.description = form.collection_description.data - collection.creator = request.user.id + collection.actor = request.user.id + collection.type = Collection.USER_DEFINED_TYPE collection.generate_slug() - create_activity("create", collection, collection.creator) + collection.get_public_id(request.urlgen) + create_activity("create", collection, collection.actor) collection.save() # Otherwise, use the collection selected from the drop-down else: collection = form.collection.data - if collection and collection.creator != request.user.id: + if collection and collection.actor != request.user.id: collection = None # Make sure the user actually selected a collection + item = CollectionItem.query.filter_by(collection=collection.id) + item = item.join(CollectionItem.object_helper).filter_by( + model_type=media.__tablename__, + obj_pk=media.id + ).first() + if not collection: messages.add_message( request, messages.ERROR, _('You have to select or add a collection')) return redirect(request, "mediagoblin.user_pages.media_collect", - user=media.get_uploader.username, + user=media.get_actor.username, media_id=media.id) - # Check whether media already exists in collection - elif CollectionItem.query.filter_by( - media_entry=media.id, - collection=collection.id).first(): + elif item is not None: messages.add_message(request, messages.ERROR, _('"%s" already in collection "%s"') % (media.title, collection.title)) @@ -308,11 +322,17 @@ def media_confirm_delete(request, media): if request.method == 'POST' and form.validate(): if form.confirm.data is True: - username = media.get_uploader.username + username = media.get_actor.username + + # This probably is already filled but just in case it has slipped + # through the net somehow, we need to try and make sure the + # MediaEntry has a public ID so it gets properly soft-deleted. + media.get_public_id(request.urlgen) - media.get_uploader.uploaded = media.get_uploader.uploaded - \ + # Decrement the users uploaded quota. + media.get_actor.uploaded = media.get_actor.uploaded - \ media.file_size - media.get_uploader.save() + media.get_actor.save() # Delete MediaEntry and all related files, comments etc. media.delete() @@ -333,7 +353,7 @@ def media_confirm_delete(request, media): return redirect_obj(request, media) if ((request.user.has_privilege(u'admin') and - request.user.id != media.uploader)): + request.user.id != media.actor)): messages.add_message( request, messages.WARNING, _("You are about to delete another user's media. " @@ -351,7 +371,7 @@ def media_confirm_delete(request, media): def user_collection(request, page, url_user=None): """A User-defined Collection""" collection = Collection.query.filter_by( - get_creator=url_user, + get_actor=url_user, slug=request.matchdict['collection']).first() if not collection: @@ -380,7 +400,7 @@ def user_collection(request, page, url_user=None): def collection_list(request, url_user=None): """A User-defined Collection""" collections = Collection.query.filter_by( - get_creator=url_user) + get_actor=url_user) return render_to_response( request, @@ -397,15 +417,15 @@ def collection_item_confirm_remove(request, collection_item): form = user_forms.ConfirmCollectionItemRemoveForm(request.form) if request.method == 'POST' and form.validate(): - username = collection_item.in_collection.get_creator.username + username = collection_item.in_collection.get_actor.username collection = collection_item.in_collection if form.confirm.data is True: - entry = collection_item.get_media_entry - entry.save() + obj = collection_item.get_object() + obj.save() collection_item.delete() - collection.items = collection.items - 1 + collection.num_items = collection.num_items - 1 collection.save() messages.add_message( @@ -418,7 +438,7 @@ def collection_item_confirm_remove(request, collection_item): return redirect_obj(request, collection) if ((request.user.has_privilege(u'admin') and - request.user.id != collection_item.in_collection.creator)): + request.user.id != collection_item.in_collection.actor)): messages.add_message( request, messages.WARNING, _("You are about to delete an item from another user's collection. " @@ -440,15 +460,19 @@ def collection_confirm_delete(request, collection): if request.method == 'POST' and form.validate(): - username = collection.get_creator.username + username = collection.get_actor.username if form.confirm.data is True: collection_title = collection.title + # Firstly like with the MediaEntry delete, lets ensure the + # public_id is populated as this is really important! + collection.get_public_id(request.urlgen) + # Delete all the associated collection items for item in collection.get_collection_items(): - entry = item.get_media_entry - entry.save() + obj = item.get_object() + obj.save() item.delete() collection.delete() @@ -465,7 +489,7 @@ def collection_confirm_delete(request, collection): return redirect_obj(request, collection) if ((request.user.has_privilege(u'admin') and - request.user.id != collection.creator)): + request.user.id != collection.actor)): messages.add_message( request, messages.WARNING, _("You are about to delete another user's collection. " @@ -485,13 +509,13 @@ def atom_feed(request): """ generates the atom feed with the newest images """ - user = User.query.filter_by( + user = LocalUser.query.filter_by( username = request.matchdict['user']).first() if not user or not user.has_privilege(u'active'): return render_404(request) cursor = MediaEntry.query.filter_by( - uploader = user.id, + actor = user.id, state = u'processed').\ order_by(MediaEntry.created.desc()).\ limit(ATOM_DEFAULT_NR_OF_UPDATED_ITEMS) @@ -523,15 +547,16 @@ def atom_feed(request): links=atomlinks) for entry in cursor: - feed.add(entry.get('title'), + feed.add( + entry.get('title'), entry.description_html, id=entry.url_for_self(request.urlgen, qualified=True), content_type='html', author={ - 'name': entry.get_uploader.username, + 'name': entry.get_actor.username, 'uri': request.urlgen( 'mediagoblin.user_pages.user_home', - qualified=True, user=entry.get_uploader.username)}, + qualified=True, user=entry.get_actor.username)}, updated=entry.get('created'), links=[{ 'href': entry.url_for_self( @@ -547,13 +572,13 @@ def collection_atom_feed(request): """ generates the atom feed with the newest images from a collection """ - user = User.query.filter_by( + user = LocalUser.query.filter_by( username = request.matchdict['user']).first() if not user or not user.has_privilege(u'active'): return render_404(request) collection = Collection.query.filter_by( - creator=user.id, + actor=user.id, slug=request.matchdict['collection']).first() if not collection: return render_404(request) @@ -591,19 +616,20 @@ def collection_atom_feed(request): links=atomlinks) for item in cursor: - entry = item.get_media_entry - feed.add(entry.get('title'), + obj = item.get_object() + feed.add( + obj.get('title'), item.note_html, - id=entry.url_for_self(request.urlgen, qualified=True), + id=obj.url_for_self(request.urlgen, qualified=True), content_type='html', author={ - 'name': entry.get_uploader.username, + 'name': obj.get_actor().username, 'uri': request.urlgen( 'mediagoblin.user_pages.user_home', - qualified=True, user=entry.get_uploader.username)}, + qualified=True, user=obj.get_actor().username)}, updated=item.get('added'), links=[{ - 'href': entry.url_for_self( + 'href': obj.url_for_self( request.urlgen, qualified=True), 'rel': 'alternate', @@ -617,7 +643,7 @@ def processing_panel(request): Show to the user what media is still in conversion/processing... and what failed, and why! """ - user = User.query.filter_by(username=request.matchdict['user']).first() + user = LocalUser.query.filter_by(username=request.matchdict['user']).first() # TODO: XXX: Should this be a decorator? # # Make sure we have permission to access this user's panel. Only @@ -630,18 +656,18 @@ def processing_panel(request): # Get media entries which are in-processing processing_entries = MediaEntry.query.\ - filter_by(uploader = user.id, + filter_by(actor = user.id, state = u'processing').\ order_by(MediaEntry.created.desc()) # Get media entries which have failed to process failed_entries = MediaEntry.query.\ - filter_by(uploader = user.id, + filter_by(actor = user.id, state = u'failed').\ order_by(MediaEntry.created.desc()) processed_entries = MediaEntry.query.\ - filter_by(uploader = user.id, + filter_by(actor = user.id, state = u'processed').\ order_by(MediaEntry.created.desc()).\ limit(10) @@ -661,15 +687,15 @@ def processing_panel(request): @get_optional_media_comment_by_id def file_a_report(request, media, comment): """ - This view handles the filing of a MediaReport or a CommentReport. + This view handles the filing of a Report. """ if comment is not None: - if not comment.get_media_entry.id == media.id: + if not comment.target().id == media.id: return render_404(request) form = user_forms.CommentReportForm(request.form) - context = {'media': media, - 'comment':comment, + context = {'media': comment.target(), + 'comment':comment.comment(), 'form':form} else: form = user_forms.MediaReportForm(request.form) @@ -679,9 +705,11 @@ def file_a_report(request, media, comment): if request.method == "POST": - report_object = build_report_object(form, + report_object = build_report_object( + form, media_entry=media, - comment=comment) + comment=comment + ) # if the object was built successfully, report_table will not be None if report_object: @@ -705,7 +733,7 @@ def activity_view(request): """ # Get the user object. username = request.matchdict["username"] - user = User.query.filter_by(username=username).first() + user = LocalUser.query.filter_by(username=username).first() activity_id = request.matchdict["id"] @@ -717,6 +745,10 @@ def activity_view(request): author=user.id ).first() + # There isn't many places to check that the public_id is filled so this + # will do, it really should be, lets try and fix that if it isn't. + activity.get_public_id(request.urlgen) + if activity is None: return render_404(request) @@ -725,4 +757,3 @@ def activity_view(request): "mediagoblin/api/activity.html", {"activity": activity} ) - @@ -74,7 +74,7 @@ install_requires = [ 'kombu', 'jinja2', 'Babel>=1.3', - 'webtest<2', + 'WebTest>=2.0.18', 'ConfigObj', 'Markdown', 'sqlalchemy<0.9.0, >0.8.0', |