aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--docs/source/siteadmin/deploying.rst5
-rw-r--r--mediagoblin.example.ini4
-rw-r--r--mediagoblin/api/decorators.py1
-rw-r--r--mediagoblin/api/views.py101
-rw-r--r--mediagoblin/auth/tools.py8
-rw-r--r--mediagoblin/config_spec.ini3
-rw-r--r--mediagoblin/db/__init__.py1
-rw-r--r--mediagoblin/db/base.py84
-rw-r--r--mediagoblin/db/migrations.py899
-rw-r--r--mediagoblin/db/mixin.py138
-rw-r--r--mediagoblin/db/models.py876
-rw-r--r--mediagoblin/db/util.py4
-rw-r--r--mediagoblin/decorators.py42
-rw-r--r--mediagoblin/edit/forms.py23
-rw-r--r--mediagoblin/edit/lib.py2
-rw-r--r--mediagoblin/edit/views.py20
-rw-r--r--mediagoblin/gmg_commands/addmedia.py7
-rw-r--r--mediagoblin/gmg_commands/batchaddmedia.py6
-rw-r--r--mediagoblin/gmg_commands/users.py47
-rw-r--r--mediagoblin/i18n/ar/mediagoblin.po4
-rw-r--r--mediagoblin/i18n/bg/mediagoblin.po2
-rw-r--r--mediagoblin/i18n/ca/mediagoblin.po4
-rw-r--r--mediagoblin/i18n/ca@valencia/mediagoblin.po2
-rw-r--r--mediagoblin/i18n/cs/mediagoblin.po4
-rw-r--r--mediagoblin/i18n/cy/mediagoblin.po2
-rw-r--r--mediagoblin/i18n/da/mediagoblin.po4
-rw-r--r--mediagoblin/i18n/de/mediagoblin.po6
-rw-r--r--mediagoblin/i18n/dz/mediagoblin.po2
-rw-r--r--mediagoblin/i18n/el/mediagoblin.po2
-rw-r--r--mediagoblin/i18n/eo/mediagoblin.po2
-rw-r--r--mediagoblin/i18n/es/mediagoblin.po4
-rw-r--r--mediagoblin/i18n/fa/mediagoblin.po2
-rw-r--r--mediagoblin/i18n/fa_IR/mediagoblin.po2
-rw-r--r--mediagoblin/i18n/fi/mediagoblin.po2
-rw-r--r--mediagoblin/i18n/fr/mediagoblin.po4
-rw-r--r--mediagoblin/i18n/gl/mediagoblin.po6
-rw-r--r--mediagoblin/i18n/he/mediagoblin.po4
-rw-r--r--mediagoblin/i18n/hi/mediagoblin.po2
-rw-r--r--mediagoblin/i18n/hu/mediagoblin.po2
-rw-r--r--mediagoblin/i18n/ia/mediagoblin.po2
-rw-r--r--mediagoblin/i18n/is_IS/mediagoblin.po4
-rw-r--r--mediagoblin/i18n/it/mediagoblin.po4
-rw-r--r--mediagoblin/i18n/ja/mediagoblin.po2
-rw-r--r--mediagoblin/i18n/jbo/mediagoblin.po2
-rw-r--r--mediagoblin/i18n/ko_KR/mediagoblin.po2
-rw-r--r--mediagoblin/i18n/nb_NO/mediagoblin.po2
-rw-r--r--mediagoblin/i18n/nl/mediagoblin.po4
-rw-r--r--mediagoblin/i18n/nn_NO/mediagoblin.po4
-rw-r--r--mediagoblin/i18n/pl/mediagoblin.po4
-rw-r--r--mediagoblin/i18n/pt_BR/mediagoblin.po4
-rw-r--r--mediagoblin/i18n/ro/mediagoblin.po4
-rw-r--r--mediagoblin/i18n/ru/mediagoblin.po2
-rw-r--r--mediagoblin/i18n/sk/mediagoblin.po4
-rw-r--r--mediagoblin/i18n/sl/mediagoblin.po2
-rw-r--r--mediagoblin/i18n/sq/mediagoblin.po4
-rw-r--r--mediagoblin/i18n/sr/mediagoblin.po2
-rw-r--r--mediagoblin/i18n/sv/mediagoblin.po2
-rw-r--r--mediagoblin/i18n/te/mediagoblin.po2
-rw-r--r--mediagoblin/i18n/templates/en/mediagoblin.po7
-rw-r--r--mediagoblin/i18n/templates/mediagoblin.pot2
-rw-r--r--mediagoblin/i18n/tr_TR/mediagoblin.po2
-rw-r--r--mediagoblin/i18n/uz@Latn/mediagoblin.po2
-rw-r--r--mediagoblin/i18n/vi/mediagoblin.po2
-rw-r--r--mediagoblin/i18n/vi_VN/mediagoblin.po2
-rw-r--r--mediagoblin/i18n/zh_CN/mediagoblin.po2
-rw-r--r--mediagoblin/i18n/zh_TW.Big5/mediagoblin.po2
-rw-r--r--mediagoblin/i18n/zh_TW/mediagoblin.po4
-rw-r--r--mediagoblin/listings/views.py4
-rw-r--r--mediagoblin/meddleware/csrf.py4
-rw-r--r--mediagoblin/media_types/__init__.py1
-rw-r--r--mediagoblin/media_types/blog/views.py40
-rw-r--r--mediagoblin/media_types/pdf/processing.py2
-rw-r--r--mediagoblin/moderation/tools.py16
-rw-r--r--mediagoblin/moderation/views.py28
-rw-r--r--mediagoblin/notifications/__init__.py22
-rw-r--r--mediagoblin/notifications/task.py4
-rw-r--r--mediagoblin/notifications/tools.py4
-rw-r--r--mediagoblin/notifications/views.py5
-rw-r--r--mediagoblin/oauth/__init__.py6
-rw-r--r--mediagoblin/oauth/oauth.py55
-rw-r--r--mediagoblin/oauth/views.py4
-rw-r--r--mediagoblin/plugins/api/tools.py10
-rw-r--r--mediagoblin/plugins/archivalook/templates/archivalook/feature_media_sidebar.html8
-rw-r--r--mediagoblin/plugins/archivalook/tools.py15
-rw-r--r--mediagoblin/plugins/basic_auth/__init__.py8
-rw-r--r--mediagoblin/plugins/basic_auth/views.py8
-rw-r--r--mediagoblin/plugins/ldap/views.py7
-rw-r--r--mediagoblin/plugins/metadata_display/templates/mediagoblin/plugins/metadata_display/metadata_table.html2
-rw-r--r--mediagoblin/plugins/openid/__init__.py6
-rw-r--r--mediagoblin/plugins/persona/__init__.py6
-rw-r--r--mediagoblin/plugins/piwigo/views.py4
-rw-r--r--mediagoblin/submit/lib.py21
-rw-r--r--mediagoblin/submit/views.py6
-rw-r--r--mediagoblin/templates/mediagoblin/base.html3
-rw-r--r--mediagoblin/templates/mediagoblin/edit/attachments.html2
-rw-r--r--mediagoblin/templates/mediagoblin/edit/edit.html2
-rw-r--r--mediagoblin/templates/mediagoblin/edit/edit_collection.html4
-rw-r--r--mediagoblin/templates/mediagoblin/fragments/header_notifications.html10
-rw-r--r--mediagoblin/templates/mediagoblin/moderation/media_panel.html6
-rw-r--r--mediagoblin/templates/mediagoblin/moderation/report.html25
-rw-r--r--mediagoblin/templates/mediagoblin/moderation/report_panel.html4
-rw-r--r--mediagoblin/templates/mediagoblin/moderation/user.html4
-rw-r--r--mediagoblin/templates/mediagoblin/user_pages/blog_media.html8
-rw-r--r--mediagoblin/templates/mediagoblin/user_pages/collection.html6
-rw-r--r--mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html8
-rw-r--r--mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html10
-rw-r--r--mediagoblin/templates/mediagoblin/user_pages/media.html24
-rw-r--r--mediagoblin/templates/mediagoblin/user_pages/media_collect.html4
-rw-r--r--mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html4
-rw-r--r--mediagoblin/templates/mediagoblin/user_pages/report.html12
-rw-r--r--mediagoblin/templates/mediagoblin/utils/collection_gallery.html14
-rw-r--r--mediagoblin/templates/mediagoblin/utils/collections.html6
-rw-r--r--mediagoblin/templates/mediagoblin/utils/comment-subscription.html4
-rw-r--r--mediagoblin/templates/mediagoblin/utils/object_gallery.html5
-rw-r--r--mediagoblin/templates/mediagoblin/utils/report.html2
-rw-r--r--mediagoblin/templates/mediagoblin/utils/tags.html6
-rw-r--r--mediagoblin/tests/__init__.py4
-rw-r--r--mediagoblin/tests/test_api.py71
-rw-r--r--mediagoblin/tests/test_auth.py17
-rw-r--r--mediagoblin/tests/test_basic_auth.py6
-rw-r--r--mediagoblin/tests/test_csrf_middleware.py4
-rw-r--r--mediagoblin/tests/test_edit.py23
-rw-r--r--mediagoblin/tests/test_exif.py6
-rw-r--r--mediagoblin/tests/test_ldap.py6
-rw-r--r--mediagoblin/tests/test_misc.py24
-rw-r--r--mediagoblin/tests/test_modelmethods.py66
-rw-r--r--mediagoblin/tests/test_moderation.py53
-rw-r--r--mediagoblin/tests/test_notifications.py32
-rw-r--r--mediagoblin/tests/test_openid.py7
-rw-r--r--mediagoblin/tests/test_persona.py12
-rw-r--r--mediagoblin/tests/test_privileges.py8
-rw-r--r--mediagoblin/tests/test_reporting.py37
-rw-r--r--mediagoblin/tests/test_response.py65
-rw-r--r--mediagoblin/tests/test_submission.py24
-rw-r--r--mediagoblin/tests/tools.py83
-rw-r--r--mediagoblin/tools/federation.py15
-rw-r--r--mediagoblin/user_pages/lib.py45
-rw-r--r--mediagoblin/user_pages/views.py157
-rw-r--r--setup.py2
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 %} &mdash; {{ 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>
&middot;
{% 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}
)
-
diff --git a/setup.py b/setup.py
index 68303f25..05fe8fcc 100644
--- a/setup.py
+++ b/setup.py
@@ -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',