Merge branch 'master' into merge-python3-port
authorChristopher Allan Webber <cwebber@dustycloud.org>
Tue, 16 Sep 2014 19:01:43 +0000 (14:01 -0500)
committerChristopher Allan Webber <cwebber@dustycloud.org>
Tue, 16 Sep 2014 19:01:43 +0000 (14:01 -0500)
Has some issues, will iteratively fix!

Conflicts:
mediagoblin/gmg_commands/__init__.py
mediagoblin/gmg_commands/deletemedia.py
mediagoblin/gmg_commands/users.py
mediagoblin/oauth/views.py
mediagoblin/plugins/api/views.py
mediagoblin/tests/test_api.py
mediagoblin/tests/test_edit.py
mediagoblin/tests/test_oauth1.py
mediagoblin/tests/test_util.py
mediagoblin/tools/mail.py
mediagoblin/webfinger/views.py
setup.py

29 files changed:
1  2 
lazystarter.sh
mediagoblin/app.py
mediagoblin/auth/tools.py
mediagoblin/db/migrations.py
mediagoblin/db/models.py
mediagoblin/decorators.py
mediagoblin/edit/views.py
mediagoblin/gmg_commands/__init__.py
mediagoblin/gmg_commands/deletemedia.py
mediagoblin/gmg_commands/users.py
mediagoblin/init/celery/__init__.py
mediagoblin/media_types/blog/views.py
mediagoblin/moderation/tools.py
mediagoblin/oauth/views.py
mediagoblin/plugins/api/views.py
mediagoblin/plugins/oauth/forms.py
mediagoblin/submit/lib.py
mediagoblin/tests/test_api.py
mediagoblin/tests/test_edit.py
mediagoblin/tests/test_ldap.py
mediagoblin/tests/test_modelmethods.py
mediagoblin/tests/test_oauth1.py
mediagoblin/tests/test_util.py
mediagoblin/tests/tools.py
mediagoblin/tools/crypto.py
mediagoblin/tools/mail.py
mediagoblin/tools/response.py
mediagoblin/tools/translate.py
setup.py

diff --cc lazystarter.sh
Simple merge
Simple merge
Simple merge
index b3cea871f19f966d2dad596ffccb72a9f91e8ec4,04588ad1462ebd136a6b73e332b81fa7d42124b9..349d16d5710ab1576ceea5494b5ee3b2640ba1a2
  import datetime
  import uuid
  
 +import six
 +
  from sqlalchemy import (MetaData, Table, Column, Boolean, SmallInteger,
                          Integer, Unicode, UnicodeText, DateTime,
-                         ForeignKey, Date)
+                         ForeignKey, Date, Index)
  from sqlalchemy.exc import ProgrammingError
  from sqlalchemy.ext.declarative import declarative_base
  from sqlalchemy.sql import and_
 -from migrate.changeset.constraint import UniqueConstraint
 +from sqlalchemy.schema import UniqueConstraint
  
  from mediagoblin.db.extratypes import JSONEncoded, MutationDict
  from mediagoblin.db.migration_tools import (
      RegisterMigration, inspect_table, replace_table_hack)
Simple merge
index 61d078b226fe4d7c913f07d91b4d779d7f3d5542,5bf60048bf5196d09a1fff49a956eba1f0f4e586..f3be679d86fe493a575623b540ff333db1e9c82d
@@@ -19,11 -19,10 +19,11 @@@ from functools import wrap
  from werkzeug.exceptions import Forbidden, NotFound
  from oauthlib.oauth1 import ResourceEndpoint
  
 +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
+ from mediagoblin.db.models import MediaEntry, User, MediaComment, AccessToken
  from mediagoblin.tools.response import (
      redirect, render_404,
      render_user_banned, json_response)
Simple merge
index 79fb770118f86b2aa81080168b169279468e5c29,0cb239a226b7ede49d40de04548a9df6785e7284..22fef91cb430bca1a329f6d04cbc82c663dbb6c5
@@@ -59,10 -61,10 +63,14 @@@ SUBCOMMAND_MAP = 
          'setup': 'mediagoblin.gmg_commands.deletemedia:parser_setup',
          'func': 'mediagoblin.gmg_commands.deletemedia:deletemedia',
          'help': 'Delete media entries'},
 +    'serve': {
 +            'setup': 'mediagoblin.gmg_commands.serve:parser_setup',
 +            'func': 'mediagoblin.gmg_commands.serve:serve',
 +            'help': 'PasteScript replacement'},
+     'batchaddmedia': {
+         'setup': 'mediagoblin.gmg_commands.batchaddmedia:parser_setup',
+         'func': 'mediagoblin.gmg_commands.batchaddmedia:batchaddmedia',
+         'help': 'Add many media entries at once'},
      # 'theme': {
      #     'setup': 'mediagoblin.gmg_commands.theme:theme_parser_setup',
      #     'func': 'mediagoblin.gmg_commands.theme:theme',
index d8926a6b0f2c174e1154b21b90dc54f5b72efda3,ab5a81f63e9891dea86bc44c0de5a941cc57e0ba..415389c4aff363a4185e09710d4bfe3161097e0d
@@@ -14,7 -14,7 +14,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/>.
  
 +from __future__ import print_function
+ import sys
  
  from mediagoblin.gmg_commands import util as commands_util
  
@@@ -34,7 -37,8 +38,8 @@@ def deletemedia(args)
      for media in medias:
          found_medias.add(media.id)
          media.delete()
 -        print 'Media ID %d has been deleted.' % media.id
 +        print('Media ID %d has been deleted.' % media.id)
      for media in media_ids - found_medias:
 -        print 'Can\'t find a media with ID %d.' % media
 -    print 'Done.'
 +        print('Can\'t find a media with ID %d.' % media)
 +    print('Done.')
+     sys.exit(0)
index 050c534894c1113346dc2e5d411a615c2e0ac051,71149497b2aa81f18a444185f2bfc2c739a4ed5a..93b72ea438dd2521c5dc2786852f72eeeab992e8
@@@ -116,6 -112,26 +116,26 @@@ def changepw(args)
      if user:
          user.pw_hash = auth.gen_password_hash(args.password)
          user.save()
 -        print 'Password successfully changed'
 +        print(u'Password successfully changed')
      else:
 -        print 'The user doesn\'t exist'
 +        print(u'The user doesn\'t exist')
+ def deleteuser_parser_setup(subparser):
+     subparser.add_argument(
+         'username',
+         help="Username to delete")
+ def deleteuser(args):
+     commands_util.setup_app(args)
+     db = mg_globals.database
+     user = db.User.query.filter_by(
+         username=unicode(args.username.lower())).one()
+     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)
index ffc7ca0e6383902718d9e3699ecb360424ab93ae,19c13f7d388ae0a8877980c25585427e8b3e4a3f..c3cdd43cca4d31be98466f2a9c040bac72c4af95
  
  import os
  import sys
+ import datetime
  import logging
  
 +import six
 +
  from celery import Celery
  from mediagoblin.tools.pluginapi import hook_runall
  
Simple merge
Simple merge
index fd848467084d353cc4266fd43404f36689bf16da,90ad5bbffca103f12ccdd33312032375938c5d3f..ce12fbe0b6cab3c4e78d0efe46ec5b99145a6136
  # along with this program.  If not, see <http://www.gnu.org/licenses/>.
  
  import datetime
 -import string
 +
 +import six
  
+ from oauthlib.oauth1.rfc5849.utils import UNICODE_ASCII_CHARACTER_SET
  from oauthlib.oauth1 import (RequestTokenEndpoint, AuthorizationEndpoint,
                               AccessTokenEndpoint)
  
index 02fd810783e9fe200f42ae17e05bc01ad736bd7c,e8af7988497bc648d25c0fe192977453201af2af..ef0b87e3fbd1c4066eafa8dabde3e58064870a06
@@@ -63,9 -61,10 +63,10 @@@ def post_entry(request)
              mg_app=request.app, user=request.user,
              submitted_file=request.files['file'],
              filename=request.files['file'].filename,
 -            title=unicode(request.form.get('title')),
 -            description=unicode(request.form.get('description')),
 -            license=unicode(request.form.get('license', '')),
 -            tags_string=unicode(request.form.get('tags', '')),
 +            title=six.text_type(request.form.get('title')),
 +            description=six.text_type(request.form.get('description')),
 +            license=six.text_type(request.form.get('license', '')),
++            tags_string=six.text_type(request.form.get('tags', '')),
              upload_limit=upload_limit, max_file_size=max_file_size,
              callback_url=callback_url)
  
Simple merge
Simple merge
index c8a254d4d34cb4b16d7895830ba73266af279694,93e82f18745fb447de4c9010bf3c0a7dbf94d60b..329c387de3ad7d4742a30b7a94887fa6e84abfae
  #
  # 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 json
  
- import logging
- import base64
+ import mock
  import pytest
  
+ from webtest import AppError
+ from .resources import GOOD_JPG
  from mediagoblin import mg_globals
- from mediagoblin.tools import template, pluginapi
+ from mediagoblin.db.models import User, MediaEntry, MediaComment
  from mediagoblin.tests.tools import fixture_add_user
- from .resources import GOOD_JPG, GOOD_PNG, EVIL_FILE, EVIL_JPG, EVIL_PNG, \
-     BIG_BLUE
+ from mediagoblin.moderation.tools import take_away_privileges
  
+ class TestAPI(object):
+     """ Test mediagoblin's pump.io complient APIs """
  
- _log = logging.getLogger(__name__)
+     @pytest.fixture(autouse=True)
+     def setup(self, test_app):
+         self.test_app = test_app
+         self.db = mg_globals.database
  
+         self.user = fixture_add_user(privileges=[u'active', u'uploader', u'commenter'])
+         self.other_user = fixture_add_user(
+             username="otheruser",
+             privileges=[u'active', u'uploader', u'commenter']
+         )
+         self.active_user = self.user
  
- class TestAPI(object):
-     def setup(self):
-         self.db = mg_globals.database
+     def _activity_to_feed(self, test_app, activity, headers=None):
+         """ Posts an activity to the user's feed """
+         if headers:
+             headers.setdefault("Content-Type", "application/json")
+         else:
+             headers = {"Content-Type": "application/json"}
+         with self.mock_oauth():
+             response = test_app.post(
+                 "/api/user/{0}/feed".format(self.active_user.username),
+                 json.dumps(activity),
+                 headers=headers
+             )
+         return response, json.loads(response.body)
+     def _upload_image(self, test_app, image):
+         """ Uploads and image to MediaGoblin via pump.io API """
+         data = open(image, "rb").read()
+         headers = {
+             "Content-Type": "image/jpeg",
+             "Content-Length": str(len(data))
+         }
+         with self.mock_oauth():
+             response = test_app.post(
+                 "/api/user/{0}/uploads".format(self.active_user.username),
+                 data,
+                 headers=headers
+             )
+             image = json.loads(response.body)
+         return response, image
+     def _post_image_to_feed(self, test_app, image):
+         """ Posts an already uploaded image to feed """
+         activity = {
+             "verb": "post",
+             "object": image,
+         }
+         return self._activity_to_feed(test_app, activity)
+     def mocked_oauth_required(self, *args, **kwargs):
+         """ Mocks mediagoblin.decorator.oauth_required to always validate """
+         def fake_controller(controller, request, *args, **kwargs):
+             request.user = User.query.filter_by(id=self.active_user.id).first()
+             return controller(request, *args, **kwargs)
+         def oauth_required(c):
+             return lambda *args, **kwargs: fake_controller(c, *args, **kwargs)
+         return oauth_required
+     def mock_oauth(self):
+         """ Returns a mock.patch for the oauth_required decorator """
+         return mock.patch(
+             target="mediagoblin.decorators.oauth_required",
+             new_callable=self.mocked_oauth_required
+         )
+     def test_can_post_image(self, test_app):
+         """ Tests that an image can be posted to the API """
+         # First request we need to do is to upload the image
+         response, image = self._upload_image(test_app, GOOD_JPG)
+         # I should have got certain things back
+         assert response.status_code == 200
+         assert "id" in image
+         assert "fullImage" in image
+         assert "url" in image["fullImage"]
+         assert "url" in image
+         assert "author" in image
+         assert "published" in image
+         assert "updated" in image
+         assert image["objectType"] == "image"
+         # Check that we got the response we're expecting
+         response, _ = self._post_image_to_feed(test_app, image)
+         assert response.status_code == 200
+     def test_unable_to_upload_as_someone_else(self, test_app):
+         """ Test that can't upload as someoen else """
+         data = open(GOOD_JPG, "rb").read()
+         headers = {
+             "Content-Type": "image/jpeg",
+             "Content-Length": str(len(data))
+         }
+         with self.mock_oauth():
+             # Will be self.user trying to upload as self.other_user
+             with pytest.raises(AppError) as excinfo:
+                 test_app.post(
+                     "/api/user/{0}/uploads".format(self.other_user.username),
+                     data,
+                     headers=headers
+                 )
+             assert "403 FORBIDDEN" in excinfo.value.message
+     def test_unable_to_post_feed_as_someone_else(self, test_app):
+         """ Tests that can't post an image to someone else's feed """
+         response, data = self._upload_image(test_app, GOOD_JPG)
+         activity = {
+             "verb": "post",
+             "object": data
+         }
+         headers = {
+             "Content-Type": "application/json",
+         }
+         with self.mock_oauth():
+             with pytest.raises(AppError) as excinfo:
+                 test_app.post(
+                     "/api/user/{0}/feed".format(self.other_user.username),
+                     json.dumps(activity),
+                     headers=headers
+                 )
+             assert "403 FORBIDDEN" in excinfo.value.message
+     def test_only_able_to_update_own_image(self, test_app):
+         """ Test's that the uploader is the only person who can update an image """
+         response, data = self._upload_image(test_app, GOOD_JPG)
+         response, data = self._post_image_to_feed(test_app, data)
+         activity = {
+             "verb": "update",
+             "object": data["object"],
+         }
+         headers = {
+             "Content-Type": "application/json",
+         }
+         # 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.
+         media = MediaEntry.query.filter_by(id=data["object"]["id"]).first()
+         media.uploader = self.other_user.id
+         media.save()
+         # Now lets try and edit the image as self.user, this should produce a 403 error.
+         with self.mock_oauth():
+             with pytest.raises(AppError) as excinfo:
+                 test_app.post(
+                     "/api/user/{0}/feed".format(self.user.username),
+                     json.dumps(activity),
+                     headers=headers
+                 )
+             assert "403 FORBIDDEN" in excinfo.value.message
+     def test_upload_image_with_filename(self, test_app):
+         """ Tests that you can upload an image with filename and description """
+         response, data = self._upload_image(test_app, GOOD_JPG)
+         response, data = self._post_image_to_feed(test_app, data)
+         image = data["object"]
+         # Now we need to add a title and description
+         title = "My image ^_^"
+         description = "This is my super awesome image :D"
+         license = "CC-BY-SA"
+         image["displayName"] = title
+         image["content"] = description
+         image["license"] = license
+         activity = {"verb": "update", "object": image}
+         with self.mock_oauth():
+             response = test_app.post(
+                 "/api/user/{0}/feed".format(self.user.username),
+                 json.dumps(activity),
+                 headers={"Content-Type": "application/json"}
+             )
+         image = json.loads(response.body)["object"]
+         # Check everything has been set on the media correctly
+         media = MediaEntry.query.filter_by(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 image["id"] == media.id
+         assert image["displayName"] == title
+         assert image["content"] == description
+         assert image["license"] == license
+     def test_only_uploaders_post_image(self, test_app):
+         """ Test that only uploaders can upload images """
+         # Remove uploader permissions from user
+         take_away_privileges(self.user.username, u"uploader")
+         # Now try and upload a image
+         data = open(GOOD_JPG, "rb").read()
+         headers = {
+             "Content-Type": "image/jpeg",
+             "Content-Length": str(len(data)),
+         }
+         with self.mock_oauth():
+             with pytest.raises(AppError) as excinfo:
+                 test_app.post(
+                     "/api/user/{0}/uploads".format(self.user.username),
+                     data,
+                     headers=headers
+                 )
+             # Assert that we've got a 403
+             assert "403 FORBIDDEN" in excinfo.value.message
+     def test_object_endpoint(self, test_app):
+         """ Tests that object can be looked up at endpoint """
+         # Post an image
+         response, data = self._upload_image(test_app, GOOD_JPG)
+         response, data = self._post_image_to_feed(test_app, data)
+         # Now lookup image to check that endpoint works.
+         image = data["object"]
+         assert "links" in image
+         assert "self" in image["links"]
+         # Get URI and strip testing host off
+         object_uri = image["links"]["self"]["href"]
+         object_uri = object_uri.replace("http://localhost:80", "")
+         with self.mock_oauth():
+             request = test_app.get(object_uri)
+         image = json.loads(request.body)
+         entry = MediaEntry.query.filter_by(id=image["id"]).first()
+         assert request.status_code == 200
+         assert entry.id == image["id"]
+         assert "image" in image
+         assert "fullImage" in image
+         assert "pump_io" in image
+         assert "links" in image
+     def test_post_comment(self, test_app):
+         """ Tests that I can post an comment media """
+         # Upload some media to comment on
+         response, data = self._upload_image(test_app, GOOD_JPG)
+         response, data = self._post_image_to_feed(test_app, data)
+         content = "Hai this is a comment on this lovely picture ^_^"
+         activity = {
+             "verb": "post",
+             "object": {
+                 "objectType": "comment",
+                 "content": content,
+                 "inReplyTo": data["object"],
+             }
+         }
+         response, comment_data = self._activity_to_feed(test_app, activity)
+         assert response.status_code == 200
+         # Find the objects in the database
+         media = MediaEntry.query.filter_by(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.content == content
+         # Test that the response is what we should be given
+         assert comment.id == comment_data["object"]["id"]
+         assert comment.content == comment_data["object"]["content"]
+     def test_unable_to_post_comment_as_someone_else(self, test_app):
+         """ Tests that you're unable to post a comment as someone else. """
+         # Upload some media to comment on
+         response, data = self._upload_image(test_app, GOOD_JPG)
+         response, data = self._post_image_to_feed(test_app, data)
+         activity = {
+             "verb": "post",
+             "object": {
+                 "objectType": "comment",
+                 "content": "comment commenty comment ^_^",
+                 "inReplyTo": data["object"],
+             }
+         }
+         headers = {
+             "Content-Type": "application/json",
+         }
+         with self.mock_oauth():
+             with pytest.raises(AppError) as excinfo:
+                 test_app.post(
+                     "/api/user/{0}/feed".format(self.other_user.username),
+                     json.dumps(activity),
+                     headers=headers
+                 )
+             assert "403 FORBIDDEN" in excinfo.value.message
+     def test_unable_to_update_someone_elses_comment(self, test_app):
+         """ Test that you're able to update someoen elses comment. """
+         # Upload some media to comment on
+         response, data = self._upload_image(test_app, GOOD_JPG)
+         response, data = self._post_image_to_feed(test_app, data)
+         activity = {
+             "verb": "post",
+             "object": {
+                 "objectType": "comment",
+                 "content": "comment commenty comment ^_^",
+                 "inReplyTo": data["object"],
+             }
+         }
+         headers = {
+             "Content-Type": "application/json",
+         }
+         # Post the comment.
+         response, comment_data = self._activity_to_feed(test_app, activity)
+         # change who uploaded the comment as it's easier than changing
+         comment_id = comment_data["object"]["id"]
+         comment = MediaComment.query.filter_by(id=comment_id).first()
+         comment.author = self.other_user.id
+         comment.save()
+         # Update the comment as someone else.
+         comment_data["object"]["content"] = "Yep"
+         activity = {
+             "verb": "update",
+             "object": comment_data["object"]
+         }
+         with self.mock_oauth():
+             with pytest.raises(AppError) as excinfo:
+                 test_app.post(
+                     "/api/user/{0}/feed".format(self.user.username),
+                     json.dumps(activity),
+                     headers=headers
+                 )
+             assert "403 FORBIDDEN" in excinfo.value.message
+     def test_profile(self, test_app):
+         """ Tests profile endpoint """
+         uri = "/api/user/{0}/profile".format(self.user.username)
+         with self.mock_oauth():
+             response = test_app.get(uri)
+             profile = json.loads(response.body)
+             assert response.status_code == 200
+             assert profile["preferredUsername"] == self.user.username
+             assert profile["objectType"] == "person"
+             assert "links" in profile
+     def test_user(self, test_app):
+         """ Test the user endpoint """
+         uri = "/api/user/{0}/".format(self.user.username)
+         with self.mock_oauth():
+             response = test_app.get(uri)
+             user = json.loads(response.body)
  
-         self.user_password = u'4cc355_70k3N'
-         self.user = fixture_add_user(u'joapi', self.user_password,
-             privileges=[u'active',u'uploader'])
+             assert response.status_code == 200
  
-     def login(self, test_app):
-         test_app.post(
-             '/auth/login/', {
-                 'username': self.user.username,
-                 'password': self.user_password})
+             assert user["nickname"] == self.user.username
+             assert user["updated"] == self.user.created.isoformat()
+             assert user["published"] == self.user.created.isoformat()
  
-     def get_context(self, template_name):
-         return template.TEMPLATE_TEST_CONTEXT[template_name]
+             # Test profile exists but self.test_profile will test the value
+             assert "profile" in response
  
-     def http_auth_headers(self):
-         return {'Authorization': ('Basic {0}'.format(
-                 base64.b64encode((':'.join([
-                     self.user.username,
-                     self.user_password])).encode('ascii')).decode()))}
+     def test_whoami_without_login(self, test_app):
+         """ Test that whoami endpoint returns error when not logged in """
+         with pytest.raises(AppError) as excinfo:
+             response = test_app.get("/api/whoami")
  
-     def do_post(self, data, test_app, **kwargs):
-         url = kwargs.pop('url', '/api/submit')
-         do_follow = kwargs.pop('do_follow', False)
+         assert "401 UNAUTHORIZED" in excinfo.value.message
  
-         if 'headers' not in kwargs.keys():
-             kwargs['headers'] = self.http_auth_headers()
+     def test_read_feed(self, test_app):
+         """ Test able to read objects from the feed """
+         response, data = self._upload_image(test_app, GOOD_JPG)
+         response, data = self._post_image_to_feed(test_app, data)
  
-         response = test_app.post(url, data, **kwargs)
+         uri = "/api/user/{0}/feed".format(self.active_user.username)
+         with self.mock_oauth():
+             response = test_app.get(uri)
+             feed = json.loads(response.body)
  
-         if do_follow:
-             response.follow()
+             assert response.status_code == 200
  
-         return response
+             # Check it has the attributes it should
+             assert "displayName" in feed
+             assert "objectTypes" in feed
+             assert "url" in feed
+             assert "links" in feed
+             assert "author" in feed
+             assert "items" in feed
  
-     def upload_data(self, filename):
-         return {'upload_files': [('file', filename)]}
+             # Check that image i uploaded is there
+             assert feed["items"][0]["verb"] == "post"
+             assert feed["items"][0]["actor"]
  
-     def test_1_test_test_view(self, test_app):
-         self.login(test_app)
+     def test_cant_post_to_someone_elses_feed(self, test_app):
+         """ Test that can't post to someone elses feed """
+         response, data = self._upload_image(test_app, GOOD_JPG)
+         self.active_user = self.other_user
  
-         response = test_app.get(
-             '/api/test',
-             headers=self.http_auth_headers())
+         with self.mock_oauth():
+             with pytest.raises(AppError) as excinfo:
+                 self._post_image_to_feed(test_app, data)
  
-         assert response.body == \
-                 b'{"email": "joapi@example.com", "username": "joapi"}'
+             assert "403 FORBIDDEN" in excinfo.value.message
  
-     def test_2_test_submission(self, test_app):
-         self.login(test_app)
 -    def test_object_endpoint(self, test_app):
++    def test_object_endpoint_requestable(self, test_app):
+         """ Test that object endpoint can be requested """
+         response, data = self._upload_image(test_app, GOOD_JPG)
+         response, data = self._post_image_to_feed(test_app, data)
+         object_id = data["object"]["id"]
  
-         response = self.do_post(
-             {'title': 'Great JPG!'},
-             test_app,
-             **self.upload_data(GOOD_JPG))
+         with self.mock_oauth():
+             response = test_app.get(data["object"]["links"]["self"]["href"])
+             data = json.loads(response.body)
  
-         assert response.status_int == 200
+             assert response.status_code == 200
  
-         assert self.db.MediaEntry.query.filter_by(title=u'Great JPG!').first()
+             assert object_id == data["id"]
+             assert "url" in data
+             assert "links" in data
+             assert data["objectType"] == "image"
index cf72f308cb1febb82bfb0b089e4ee8f1a265a222,dc9c422f517765404c9e6f2e688e80817c6902a3..54f43d685e0e20350c416555d7945cfe31aff14f
  # 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 urlparse, os, pytest
 +import six.moves.urllib.parse as urlparse
++import pytest
  
  from mediagoblin import mg_globals
- from mediagoblin.db.models import User
- from mediagoblin.tests.tools import fixture_add_user
+ from mediagoblin.db.models import User, MediaEntry
+ from mediagoblin.tests.tools import fixture_add_user, fixture_media_entry
  from mediagoblin import auth
  from mediagoblin.tools import template, mail
  
Simple merge
Simple merge
index e239d6282b425d9caa8bfccf539ec06aa0485969,36563e75bbc6f9f096ba538f23b823848f535794..e1c3c7e5f7c6e1f8bbbc3fc0bf24d2664d39b5bb
  # 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 mock
  import email
+ import pytest
+ import smtplib
+ import pkg_resources
  
 +import six
 +
+ from mediagoblin.tests.tools import get_app
  from mediagoblin.tools import common, url, translate, mail, text, testing
  
  testing._activate_testing()
Simple merge
Simple merge
index ad2e5a1936a924a8c09360d70e786c8b6fec6c78,ab3558352d99dceaef6afd9606aca416870a25bf..ab3e0eaa374d598a886fe38cdcee99d0ab64bfc5
  # 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 print_function, unicode_literals
 +
+ import six
  import smtplib
 -from email.MIMEText import MIMEText
+ import sys
  from mediagoblin import mg_globals, messages
 +from mediagoblin._compat import MIMEText
  from mediagoblin.tools import common
  
  ### ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Simple merge
Simple merge
diff --cc setup.py
index ed10df020da7089096ef14046b65de2d109ae810,468ea294b4e36fc297969f77443eadc5b161a99a..e1c3c6188f22ae0fbfe07410c5c3e68d69ce8aa6
+++ b/setup.py
@@@ -40,57 -32,6 +40,60 @@@ def get_version()
          raise RuntimeError("Unable to find version string in %s." %
                             VERSIONFILE)
  
-     # Annoying.  Please remove once we can!  We only indirectly
-     # use pbr, and currently it breaks things, presumably till
-     # their next release.
-     py2_only_install_requires.append('pbr==0.5.22')
 +py2_only_install_requires = []
 +if PY2:
 +    py2_only_install_requires.append('argparse')  # only for < 2.7
 +    py2_only_install_requires.append('PasteScript')
 +    # newer sqlalchemy-migrate requires pbr which BREAKS EVERYTHING AND IS
 +    # TERRIBLE AND IS THE END OF ALL THINGS
 +    # I'd love to remove this restriction.
 +    py2_only_install_requires.append('sqlalchemy-migrate<0.8')
-     'oauthlib>=0.5.0',
++    # # Annoying.  Please remove once we can!  We only indirectly
++    # # use pbr, and currently it breaks things, presumably till
++    # # their next release.
++    # py2_only_install_requires.append('pbr==0.5.22')
 +    py2_only_install_requires.append('mock')  # mock is in the stdlib for 3.3+
 +
 +install_requires = [
 +    'gunicorn',
 +    'alembic==0.6.6',
 +    'python-dateutil',
 +    'wtforms',
 +    'py-bcrypt',
 +    'pytest>=2.3.1',
 +    'pytest-xdist',
 +    'werkzeug>=0.7',
 +    'celery>=3.0',
 +    'kombu',
 +    'jinja2',
 +    'Babel>=1.3',
 +    'webtest<2',
 +    'ConfigObj',
 +    'Markdown',
 +    'sqlalchemy<0.9.0, >0.8.0',
 +    'itsdangerous',
 +    'pytz',
 +    # PLEASE change this when we can; a dependency is forcing us to set this
 +    # specific number and it is breaking setup.py develop
 +    'six==1.5.2',
++    'oauthlib',
 +    'unidecode',
++    'jsonschema',
 +    'ExifRead',  # TODO(berker): Install develop branch for Python 3
 +    'PasteDeploy',
++    'requests',
++    'pyld',
 +    # This is optional:
 +    # 'translitcodec',
 +    # For now we're expecting that users will install this from
 +    # their package managers.
 +    # 'lxml',
 +    # 'Pillow',
 +] + py2_only_install_requires
 +
 +with open(READMEFILE, encoding="utf-8") as fobj:
 +    long_description = fobj.read()
 +
  try:
      setup(
      name="mediagoblin",
      zip_safe=False,
      include_package_data = True,
      # scripts and dependencies
 -    install_requires=[
 -        'setuptools',
 -        'python-dateutil',
 -        'PasteScript',
 -        'wtforms',
 -        'py-bcrypt',
 -        'pytest>=2.3.1',
 -        'pytest-xdist',
 -        'werkzeug>=0.7',
 -        'celery>=3.0',
 -        'kombu',
 -        'jinja2',
 -        'sphinx',
 -        'Babel>=1.0',
 -        'argparse',
 -        'webtest<2',
 -        'ConfigObj',
 -        'Markdown',
 -        'sqlalchemy<0.9.0, >0.8.0',
 -        # newer sqlalchemy-migrate requires pbr which BREAKS EVERYTHING AND IS
 -        #  TERRIBLE AND IS THE END OF ALL THINGS
 -        #  I'd love to remove this restriction.
 -        'sqlalchemy-migrate<0.8',
 -        'mock',
 -        'itsdangerous',
 -        'pytz',
 -        'six>=1.4.1',
 -        'oauthlib',
 -        'unidecode',
 -        'jsonschema',
 -        'requests',
 -        'pyld',
 -        'ExifRead',
 -
 -        # PLEASE change this when we can; a dependency is forcing us to set this
 -        # specific number and it is breaking setup.py develop
 -        'six==1.5.2'
 -
 -        ## Annoying.  Please remove once we can!  We only indirectly
 -        ## use pbr, and currently it breaks things, presumably till
 -        ## their next release.
 -        # 'pbr==0.5.22',
 -
 -        ## This is optional!
 -        # 'translitcodec',
 -        ## For now we're expecting that users will install this from
 -        ## their package managers.
 -        # 'lxml',
 -        # 'PIL',
 -        ],
 -    # requires=['gst'],
 +    install_requires=install_requires,
-     test_suite='nose.collector',  # TODO: We are using py.test now?
+     test_suite='nose.collector',
      entry_points="""\
          [console_scripts]
          gmg = mediagoblin.gmg_commands:main_cli
      author_email='cwebber@gnu.org',
      url="http://mediagoblin.org/",
      download_url="http://mediagoblin.org/download/",
 -    long_description=open(READMEFILE).read(),
 +    long_description=long_description,
+     description='MediaGoblin is a web application for publishing all kinds of media',
      classifiers=[
          "Development Status :: 3 - Alpha",
          "Environment :: Web Environment",