aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--docs/source/devel/codebase.rst2
-rw-r--r--docs/source/pluginwriter/api.rst11
-rw-r--r--docs/source/siteadmin/media-types.rst2
-rw-r--r--extlib/freesound/audioprocessing.py2
m---------extlib/pdf.js0
-rw-r--r--mediagoblin/app.py8
-rw-r--r--mediagoblin/auth/forms.py2
-rw-r--r--mediagoblin/config_spec.ini7
-rw-r--r--mediagoblin/db/migrations.py38
-rw-r--r--mediagoblin/db/mixin.py4
-rw-r--r--mediagoblin/db/models.py6
-rw-r--r--mediagoblin/db/open.py16
-rw-r--r--mediagoblin/edit/forms.py2
-rw-r--r--mediagoblin/gmg_commands/dbupdate.py2
-rw-r--r--mediagoblin/init/celery/__init__.py4
-rw-r--r--mediagoblin/init/celery/from_celery.py4
-rw-r--r--mediagoblin/init/plugins/__init__.py2
-rw-r--r--mediagoblin/media_types/__init__.py63
-rw-r--r--mediagoblin/media_types/ascii/asciitoimage.py11
-rw-r--r--mediagoblin/media_types/ascii/processing.py7
-rw-r--r--mediagoblin/media_types/audio/spectrogram.py5
-rw-r--r--mediagoblin/media_types/audio/transcoders.py5
-rw-r--r--mediagoblin/media_types/image/__init__.py22
-rw-r--r--mediagoblin/media_types/image/processing.py23
-rw-r--r--mediagoblin/media_types/pdf/processing.py23
-rw-r--r--mediagoblin/media_types/stl/processing.py2
-rw-r--r--mediagoblin/media_types/video/transcoders.py5
-rw-r--r--mediagoblin/plugins/oauth/forms.py2
-rw-r--r--mediagoblin/plugins/piwigo/tools.py5
-rw-r--r--mediagoblin/plugins/piwigo/views.py9
-rw-r--r--mediagoblin/processing/__init__.py8
-rw-r--r--mediagoblin/processing/task.py2
-rw-r--r--mediagoblin/submit/forms.py2
-rw-r--r--mediagoblin/submit/lib.py2
-rw-r--r--mediagoblin/templates/mediagoblin/utils/wtforms.html8
-rw-r--r--mediagoblin/tests/resources.py41
-rw-r--r--mediagoblin/tests/test_api.py18
-rw-r--r--mediagoblin/tests/test_exif.py29
-rw-r--r--mediagoblin/tests/test_http_callback.py29
-rw-r--r--mediagoblin/tests/test_oauth.py79
-rw-r--r--mediagoblin/tests/test_pdf.py7
-rw-r--r--mediagoblin/tests/test_pluginapi.py75
-rw-r--r--mediagoblin/tests/test_storage.py29
-rw-r--r--mediagoblin/tests/test_submission.py76
-rw-r--r--mediagoblin/tests/test_util.py22
-rw-r--r--mediagoblin/tests/test_workbench.py4
-rw-r--r--mediagoblin/tests/testplugins/callables1/__init__.py8
-rw-r--r--mediagoblin/tests/testplugins/callables2/__init__.py3
-rw-r--r--mediagoblin/tests/testplugins/callables3/__init__.py3
-rw-r--r--mediagoblin/tools/pluginapi.py112
-rw-r--r--mediagoblin/tools/processing.py2
-rw-r--r--mediagoblin/tools/routing.py3
-rw-r--r--mediagoblin/tools/template.py5
-rw-r--r--mediagoblin/tools/translate.py29
-rw-r--r--mediagoblin/user_pages/forms.py2
-rw-r--r--mediagoblin/user_pages/lib.py20
-rw-r--r--mediagoblin/user_pages/views.py15
-rw-r--r--setup.py1
58 files changed, 578 insertions, 350 deletions
diff --git a/docs/source/devel/codebase.rst b/docs/source/devel/codebase.rst
index 9718a097..122a3297 100644
--- a/docs/source/devel/codebase.rst
+++ b/docs/source/devel/codebase.rst
@@ -119,7 +119,7 @@ Software Stack
* `Python <http://python.org/>`_: the language we're using to write
this
- * `Nose <http://somethingaboutorange.com/mrl/projects/nose/>`_:
+ * `Py.Test <http://pytest.org/>`_:
for unit tests
* `virtualenv <http://www.virtualenv.org/>`_: for setting up an
diff --git a/docs/source/pluginwriter/api.rst b/docs/source/pluginwriter/api.rst
index 44ffd6e8..3a75d455 100644
--- a/docs/source/pluginwriter/api.rst
+++ b/docs/source/pluginwriter/api.rst
@@ -16,10 +16,19 @@
Plugin API
==========
+This documents the general plugin API.
+
+Please note, at this point OUR PLUGIN HOOKS MAY AND WILL CHANGE.
+Authors are encouraged to develop plugins and work with the
+MediaGoblin community to keep them up to date, but this API will be a
+moving target for a few releases.
+
+Please check the release notes for updates!
+
:mod:`pluginapi` Module
-----------------------
.. automodule:: mediagoblin.tools.pluginapi
:members: get_config, register_routes, register_template_path,
register_template_hooks, get_hook_templates,
- callable_runone, callable_runall
+ hook_handle, hook_runall, hook_transform,
diff --git a/docs/source/siteadmin/media-types.rst b/docs/source/siteadmin/media-types.rst
index 264dc4fc..210094b9 100644
--- a/docs/source/siteadmin/media-types.rst
+++ b/docs/source/siteadmin/media-types.rst
@@ -206,7 +206,7 @@ To install this on Fedora:
.. code-block:: bash
- sudo yum install -y ppoppler-utils unoconv libreoffice-headless
+ sudo yum install -y poppler-utils unoconv libreoffice-headless
pdf.js relies on git submodules, so be sure you have fetched them:
diff --git a/extlib/freesound/audioprocessing.py b/extlib/freesound/audioprocessing.py
index c1dfe2eb..2c2b35b5 100644
--- a/extlib/freesound/audioprocessing.py
+++ b/extlib/freesound/audioprocessing.py
@@ -20,7 +20,7 @@
# Bram de Jong <bram.dejong at domain.com where domain in gmail>
# 2012, Joar Wandborg <first name at last name dot se>
-import Image, ImageDraw, ImageColor #@UnresolvedImport
+from PIL import Image, ImageDraw, ImageColor #@UnresolvedImport
from functools import partial
import math
import numpy
diff --git a/extlib/pdf.js b/extlib/pdf.js
-Subproject b898935eb04fa86e0911fdfa0d41828cb04802f
+Subproject 369b81b63f560b5d729da26752ca541503d8151
diff --git a/mediagoblin/app.py b/mediagoblin/app.py
index 1137c0d7..bf0e0f13 100644
--- a/mediagoblin/app.py
+++ b/mediagoblin/app.py
@@ -35,7 +35,7 @@ from mediagoblin.init.plugins import setup_plugins
from mediagoblin.init import (get_jinja_loader, get_staticdirector,
setup_global_and_app_config, setup_locales, setup_workbench, setup_database,
setup_storage)
-from mediagoblin.tools.pluginapi import PluginManager
+from mediagoblin.tools.pluginapi import PluginManager, hook_transform
from mediagoblin.tools.crypto import setup_crypto
@@ -227,7 +227,7 @@ class MediaGoblinApp(object):
for m in self.meddleware[::-1]:
m.process_response(request, response)
except HTTPException as e:
- response = render_http_exeption(
+ response = render_http_exception(
request, e, e.get_description(environ))
session_manager.save_session_to_cookie(request.session,
@@ -259,8 +259,6 @@ def paste_app_factory(global_config, **app_config):
raise IOError("Usable mediagoblin config not found.")
mgoblin_app = MediaGoblinApp(mediagoblin_config)
-
- for callable_hook in PluginManager().get_hook_callables('wrap_wsgi'):
- mgoblin_app = callable_hook(mgoblin_app)
+ mgoblin_app = hook_transform('wrap_wsgi', mgoblin_app)
return mgoblin_app
diff --git a/mediagoblin/auth/forms.py b/mediagoblin/auth/forms.py
index 5484c178..33e1f45c 100644
--- a/mediagoblin/auth/forms.py
+++ b/mediagoblin/auth/forms.py
@@ -17,7 +17,7 @@
import wtforms
from mediagoblin.tools.mail import normalize_email
-from mediagoblin.tools.translate import fake_ugettext_passthrough as _
+from mediagoblin.tools.translate import lazy_pass_to_ugettext as _
def normalize_user_or_email_field(allow_email=True, allow_user=True):
"""Check if we were passed a field that matches a username and/or email pattern
diff --git a/mediagoblin/config_spec.ini b/mediagoblin/config_spec.ini
index 399a4a13..b7c6f29a 100644
--- a/mediagoblin/config_spec.ini
+++ b/mediagoblin/config_spec.ini
@@ -32,7 +32,7 @@ email_smtp_pass = string(default=None)
allow_registration = boolean(default=True)
# tag parsing
-tags_max_length = integer(default=50)
+tags_max_length = integer(default=255)
# Whether comments are ascending or descending
comments_ascending = boolean(default=True)
@@ -91,6 +91,8 @@ max_height = integer(default=180)
[media_type:mediagoblin.media_types.image]
# One of BICUBIC, BILINEAR, NEAREST, ANTIALIAS
resize_filter = string(default="ANTIALIAS")
+#level of compression used when resizing images
+quality = integer(default=90)
[media_type:mediagoblin.media_types.video]
# Should we keep the original file?
@@ -113,7 +115,6 @@ video_codecs = string_list(default=list("VP8 video"))
audio_codecs = string_list(default=list("Vorbis"))
dimensions_match = boolean(default=True)
-
[media_type:mediagoblin.media_types.audio]
keep_original = boolean(default=True)
# vorbisenc quality
@@ -121,13 +122,13 @@ quality = float(default=0.3)
create_spectrogram = boolean(default=True)
spectrogram_fft_size = integer(default=4096)
-
[media_type:mediagoblin.media_types.ascii]
thumbnail_font = string(default=None)
[media_type:mediagoblin.media_types.pdf]
pdf_js = boolean(default=False)
+
[celery]
# default result stuff
CELERY_RESULT_BACKEND = string(default="database")
diff --git a/mediagoblin/db/migrations.py b/mediagoblin/db/migrations.py
index 167c4f87..9d1218fe 100644
--- a/mediagoblin/db/migrations.py
+++ b/mediagoblin/db/migrations.py
@@ -15,6 +15,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import datetime
+import uuid
from sqlalchemy import (MetaData, Table, Column, Boolean, SmallInteger,
Integer, Unicode, UnicodeText, DateTime,
@@ -212,7 +213,6 @@ def mediaentry_new_slug_era(db):
- slugs with = (or also : which is now also not allowed) to have those
stripped out (small possibility of breakage here sadly)
"""
- import uuid
def slug_and_user_combo_exists(slug, uploader):
return db.execute(
@@ -251,3 +251,39 @@ def mediaentry_new_slug_era(db):
row, row.slug.replace(u"=", u"-").replace(u":", u"-"))
db.commit()
+
+
+@RegisterMigration(10, MIGRATIONS)
+def unique_collections_slug(db):
+ """Add unique constraint to collection slug"""
+ metadata = MetaData(bind=db.bind)
+ collection_table = inspect_table(metadata, "core__collections")
+ existing_slugs = {}
+ slugs_to_change = []
+
+ for row in db.execute(collection_table.select()):
+ # if duplicate slug, generate a unique slug
+ if row.creator in existing_slugs and row.slug in \
+ existing_slugs[row.creator]:
+ slugs_to_change.append(row.id)
+ else:
+ if not row.creator in existing_slugs:
+ existing_slugs[row.creator] = [row.slug]
+ else:
+ existing_slugs[row.creator].append(row.slug)
+
+ for row_id in slugs_to_change:
+ new_slug = uuid.uuid4().hex
+ db.execute(collection_table.update().
+ where(collection_table.c.id == row_id).
+ values(slug=new_slug))
+ # sqlite does not like to change the schema when a transaction(update) is
+ # not yet completed
+ db.commit()
+
+ constraint = UniqueConstraint('creator', 'slug',
+ name='core__collection_creator_slug_key',
+ table=collection_table)
+ constraint.create()
+
+ db.commit()
diff --git a/mediagoblin/db/mixin.py b/mediagoblin/db/mixin.py
index 0dc3bc85..388bac89 100644
--- a/mediagoblin/db/mixin.py
+++ b/mediagoblin/db/mixin.py
@@ -149,7 +149,7 @@ class MediaEntryMixin(GenerateSlugMixin):
or, if not found, None.
"""
- fetch_order = self.media_manager.get("media_fetch_order")
+ fetch_order = self.media_manager.media_fetch_order
# No fetching order found? well, give up!
if not fetch_order:
@@ -212,7 +212,7 @@ class MediaEntryMixin(GenerateSlugMixin):
# than iterating through all media managers.
for media_type, manager in get_media_managers():
if media_type == self.media_type:
- return manager
+ return manager(self)
# Not found? Then raise an error
raise FileTypeNotSupported(
"MediaManager not in enabled types. Check media_types in config?")
diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py
index fcfd0f61..2412706e 100644
--- a/mediagoblin/db/models.py
+++ b/mediagoblin/db/models.py
@@ -410,7 +410,7 @@ class Collection(Base, CollectionMixin):
title = Column(Unicode, nullable=False)
slug = Column(Unicode)
created = Column(DateTime, nullable=False, default=datetime.datetime.now,
- index=True)
+ index=True)
description = Column(UnicodeText)
creator = Column(Integer, ForeignKey(User.id), nullable=False)
# TODO: No of items in Collection. Badly named, can we migrate to num_items?
@@ -421,6 +421,10 @@ class Collection(Base, CollectionMixin):
backref=backref("collections",
cascade="all, delete-orphan"))
+ __table_args__ = (
+ UniqueConstraint('creator', 'slug'),
+ {})
+
def get_collection_items(self, ascending=False):
#TODO, is this still needed with self.collection_items being available?
order_col = CollectionItem.position
diff --git a/mediagoblin/db/open.py b/mediagoblin/db/open.py
index 5fd5ed03..0b1679fb 100644
--- a/mediagoblin/db/open.py
+++ b/mediagoblin/db/open.py
@@ -71,12 +71,24 @@ def _sqlite_fk_pragma_on_connect(dbapi_con, con_record):
dbapi_con.execute('pragma foreign_keys=on')
-def setup_connection_and_db_from_config(app_config):
+def _sqlite_disable_fk_pragma_on_connect(dbapi_con, con_record):
+ """
+ Disable foreign key checking on each new sqlite connection
+ (Good for migrations!)
+ """
+ dbapi_con.execute('pragma foreign_keys=off')
+
+
+def setup_connection_and_db_from_config(app_config, migrations=False):
engine = create_engine(app_config['sql_engine'])
# Enable foreign key checking for sqlite
if app_config['sql_engine'].startswith('sqlite://'):
- event.listen(engine, 'connect', _sqlite_fk_pragma_on_connect)
+ if migrations:
+ event.listen(engine, 'connect',
+ _sqlite_disable_fk_pragma_on_connect)
+ else:
+ event.listen(engine, 'connect', _sqlite_fk_pragma_on_connect)
# logging.getLogger('sqlalchemy.engine').setLevel(logging.INFO)
diff --git a/mediagoblin/edit/forms.py b/mediagoblin/edit/forms.py
index 2673967b..ef270237 100644
--- a/mediagoblin/edit/forms.py
+++ b/mediagoblin/edit/forms.py
@@ -17,7 +17,7 @@
import wtforms
from mediagoblin.tools.text import tag_length_validator, TOO_LONG_TAG_WARNING
-from mediagoblin.tools.translate import fake_ugettext_passthrough as _
+from mediagoblin.tools.translate import lazy_pass_to_ugettext as _
from mediagoblin.tools.licenses import licenses_as_choices
class EditForm(wtforms.Form):
diff --git a/mediagoblin/gmg_commands/dbupdate.py b/mediagoblin/gmg_commands/dbupdate.py
index 65b3f922..32700c40 100644
--- a/mediagoblin/gmg_commands/dbupdate.py
+++ b/mediagoblin/gmg_commands/dbupdate.py
@@ -114,7 +114,7 @@ def run_dbupdate(app_config, global_config):
global_config.get('plugins', {}).keys())
# Set up the database
- db = setup_connection_and_db_from_config(app_config)
+ db = setup_connection_and_db_from_config(app_config, migrations=True)
Session = sessionmaker(bind=db.engine)
diff --git a/mediagoblin/init/celery/__init__.py b/mediagoblin/init/celery/__init__.py
index bb0d5989..169cc935 100644
--- a/mediagoblin/init/celery/__init__.py
+++ b/mediagoblin/init/celery/__init__.py
@@ -18,7 +18,7 @@ import os
import sys
from celery import Celery
-from mediagoblin.tools.pluginapi import callable_runall
+from mediagoblin.tools.pluginapi import hook_runall
MANDATORY_CELERY_IMPORTS = ['mediagoblin.processing.task']
@@ -66,7 +66,7 @@ def setup_celery_app(app_config, global_config,
celery_app = Celery()
celery_app.config_from_object(celery_settings)
- callable_runall('celery_setup', celery_app)
+ hook_runall('celery_setup', celery_app)
def setup_celery_from_config(app_config, global_config,
diff --git a/mediagoblin/init/celery/from_celery.py b/mediagoblin/init/celery/from_celery.py
index e2899c0b..b395a826 100644
--- a/mediagoblin/init/celery/from_celery.py
+++ b/mediagoblin/init/celery/from_celery.py
@@ -22,7 +22,7 @@ from celery.signals import setup_logging
from mediagoblin import app, mg_globals
from mediagoblin.init.celery import setup_celery_from_config
-from mediagoblin.tools.pluginapi import callable_runall
+from mediagoblin.tools.pluginapi import hook_runall
OUR_MODULENAME = __name__
@@ -47,7 +47,7 @@ def setup_logging_from_paste_ini(loglevel, **kw):
logging.config.fileConfig(logging_conf_file)
- callable_runall('celery_logging_setup')
+ hook_runall('celery_logging_setup')
setup_logging.connect(setup_logging_from_paste_ini)
diff --git a/mediagoblin/init/plugins/__init__.py b/mediagoblin/init/plugins/__init__.py
index 72bd5c7d..0df4f381 100644
--- a/mediagoblin/init/plugins/__init__.py
+++ b/mediagoblin/init/plugins/__init__.py
@@ -59,4 +59,4 @@ def setup_plugins():
pman.register_hooks(plugin.hooks)
# Execute anything registered to the setup hook.
- pluginapi.callable_runall('setup')
+ pluginapi.hook_runall('setup')
diff --git a/mediagoblin/media_types/__init__.py b/mediagoblin/media_types/__init__.py
index 0abb38d3..20e1918e 100644
--- a/mediagoblin/media_types/__init__.py
+++ b/mediagoblin/media_types/__init__.py
@@ -20,6 +20,7 @@ import logging
import tempfile
from mediagoblin import mg_globals
+from mediagoblin.tools.common import import_component
from mediagoblin.tools.translate import lazy_pass_to_ugettext as _
_log = logging.getLogger(__name__)
@@ -31,6 +32,56 @@ class InvalidFileType(Exception):
pass
+class MediaManagerBase(object):
+ "Base class for all media managers"
+
+ # Please override in actual media managers
+ media_fetch_order = None
+
+ @staticmethod
+ def sniff_handler(*args, **kwargs):
+ return False
+
+ def __init__(self, entry):
+ self.entry = entry
+
+ def __getitem__(self, i):
+ return getattr(self, i)
+
+ def __contains__(self, i):
+ return hasattr(self, i)
+
+
+class CompatMediaManager(object):
+ def __init__(self, mm_dict, entry=None):
+ self.mm_dict = mm_dict
+ self.entry = entry
+
+ def __call__(self, entry):
+ "So this object can look like a class too, somehow"
+ assert self.entry is None
+ return self.__class__(self.mm_dict, entry)
+
+ def __getitem__(self, i):
+ return self.mm_dict[i]
+
+ def __contains__(self, i):
+ return (i in self.mm_dict)
+
+ @property
+ def media_fetch_order(self):
+ return self.mm_dict.get('media_fetch_order')
+
+ def sniff_handler(self, *args, **kwargs):
+ func = self.mm_dict.get("sniff_handler", None)
+ if func is not None:
+ return func(*args, **kwargs)
+ return False
+
+ def __getattr__(self, i):
+ return self.mm_dict[i]
+
+
def sniff_media(media):
'''
Iterate through the enabled media types and find those suited
@@ -49,8 +100,7 @@ def sniff_media(media):
for media_type, manager in get_media_managers():
_log.info('Sniffing {0}'.format(media_type))
- if 'sniff_handler' in manager and \
- manager['sniff_handler'](media_file, media=media):
+ if manager.sniff_handler(media_file, media=media):
_log.info('{0} accepts the file'.format(media_type))
return media_type, manager
else:
@@ -74,9 +124,12 @@ def get_media_managers():
Generator, yields all enabled media managers
'''
for media_type in get_media_types():
- __import__(media_type)
+ mm = import_component(media_type + ":MEDIA_MANAGER")
+
+ if isinstance(mm, dict):
+ mm = CompatMediaManager(mm)
- yield media_type, sys.modules[media_type].MEDIA_MANAGER
+ yield media_type, mm
def get_media_type_and_manager(filename):
@@ -92,7 +145,7 @@ def get_media_type_and_manager(filename):
for media_type, manager in get_media_managers():
# Omit the dot from the extension and match it against
# the media manager
- if ext[1:] in manager['accepted_extensions']:
+ if ext[1:] in manager.accepted_extensions:
return media_type, manager
else:
_log.info('File {0} has no file extension, let\'s hope the sniffers get it.'.format(
diff --git a/mediagoblin/media_types/ascii/asciitoimage.py b/mediagoblin/media_types/ascii/asciitoimage.py
index 108de023..786941f6 100644
--- a/mediagoblin/media_types/ascii/asciitoimage.py
+++ b/mediagoblin/media_types/ascii/asciitoimage.py
@@ -14,9 +14,14 @@
# 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 Image
-import ImageFont
-import ImageDraw
+try:
+ from PIL import Image
+ from PIL import ImageFont
+ from PIL import ImageDraw
+except ImportError:
+ import Image
+ import ImageFont
+ import ImageDraw
import logging
import pkg_resources
import os
diff --git a/mediagoblin/media_types/ascii/processing.py b/mediagoblin/media_types/ascii/processing.py
index 309aab0a..2f6079be 100644
--- a/mediagoblin/media_types/ascii/processing.py
+++ b/mediagoblin/media_types/ascii/processing.py
@@ -15,7 +15,10 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import chardet
import os
-import Image
+try:
+ from PIL import Image
+except ImportError:
+ import Image
import logging
from mediagoblin import mg_globals as mgg
@@ -42,7 +45,7 @@ def process_ascii(proc_state):
"""Code to process a txt file. Will be run by celery.
A Workbench() represents a local tempory dir. It is automatically
- cleaned up when this function exits.
+ cleaned up when this function exits.
"""
entry = proc_state.entry
workbench = proc_state.workbench
diff --git a/mediagoblin/media_types/audio/spectrogram.py b/mediagoblin/media_types/audio/spectrogram.py
index 458855c1..dd4d0299 100644
--- a/mediagoblin/media_types/audio/spectrogram.py
+++ b/mediagoblin/media_types/audio/spectrogram.py
@@ -19,7 +19,10 @@
# Bram de Jong <bram.dejong at domain.com where domain in gmail>
# 2012, Joar Wandborg <first name at last name dot se>
-from PIL import Image
+try:
+ from PIL import Image
+except ImportError:
+ import Image
import math
import numpy
diff --git a/mediagoblin/media_types/audio/transcoders.py b/mediagoblin/media_types/audio/transcoders.py
index 3a9a2125..84e6af7e 100644
--- a/mediagoblin/media_types/audio/transcoders.py
+++ b/mediagoblin/media_types/audio/transcoders.py
@@ -15,7 +15,10 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import logging
-import Image
+try:
+ from PIL import Image
+except ImportError:
+ import Image
from mediagoblin.processing import BadMediaFail
from mediagoblin.media_types.audio import audioprocessing
diff --git a/mediagoblin/media_types/image/__init__.py b/mediagoblin/media_types/image/__init__.py
index 3e167db1..15cc8dda 100644
--- a/mediagoblin/media_types/image/__init__.py
+++ b/mediagoblin/media_types/image/__init__.py
@@ -14,19 +14,19 @@
# 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 mediagoblin.media_types import MediaManagerBase
from mediagoblin.media_types.image.processing import process_image, \
sniff_handler
-MEDIA_MANAGER = {
- "human_readable": "Image",
- "processor": process_image, # alternately a string,
- # 'mediagoblin.media_types.image.processing'?
- "sniff_handler": sniff_handler,
- "display_template": "mediagoblin/media_displays/image.html",
- "default_thumb": "images/media_thumbs/image.png",
- "accepted_extensions": ["jpg", "jpeg", "png", "gif", "tiff"],
+class ImageMediaManager(MediaManagerBase):
+ human_readable = "Image"
+ processor = staticmethod(process_image)
+ sniff_handler = staticmethod(sniff_handler)
+ display_template = "mediagoblin/media_displays/image.html"
+ default_thumb = "images/media_thumbs/image.png"
+ accepted_extensions = ["jpg", "jpeg", "png", "gif", "tiff"]
+ media_fetch_order = [u'medium', u'original', u'thumb']
+
- # Used by the media_entry.get_display_media method
- "media_fetch_order": [u'medium', u'original', u'thumb'],
-}
+MEDIA_MANAGER = ImageMediaManager
diff --git a/mediagoblin/media_types/image/processing.py b/mediagoblin/media_types/image/processing.py
index e951ef29..16ffcedd 100644
--- a/mediagoblin/media_types/image/processing.py
+++ b/mediagoblin/media_types/image/processing.py
@@ -14,7 +14,10 @@
# 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 Image
+try:
+ from PIL import Image
+except ImportError:
+ import Image
import os
import logging
@@ -34,29 +37,27 @@ PIL_FILTERS = {
'ANTIALIAS': Image.ANTIALIAS}
-def resize_image(entry, filename, new_path, exif_tags, workdir, new_size,
- size_limits=(0, 0)):
+def resize_image(proc_state, filename, new_path, exif_tags, workdir, new_size):
"""
Store a resized version of an image and return its pathname.
Arguments:
- entry -- the entry for the image to resize
+ proc_state -- the processing state for the image to resize
filename -- the filename of the original image being resized
new_path -- public file path for the new resized image
exif_tags -- EXIF data for the original image
workdir -- directory path for storing converted image files
new_size -- 2-tuple size for the resized image
"""
+ config = mgg.global_config['media_type:mediagoblin.media_types.image']
+
try:
resized = Image.open(filename)
except IOError:
raise BadMediaFail()
resized = exif_fix_image_orientation(resized, exif_tags) # Fix orientation
- filter_config = \
- mgg.global_config['media_type:mediagoblin.media_types.image']\
- ['resize_filter']
-
+ filter_config = config['resize_filter']
try:
resize_filter = PIL_FILTERS[filter_config.upper()]
except KeyError:
@@ -69,7 +70,7 @@ def resize_image(entry, filename, new_path, exif_tags, workdir, new_size,
# Copy the new file to the conversion subdir, then remotely.
tmp_resized_filename = os.path.join(workdir, new_path[-1])
with file(tmp_resized_filename, 'w') as resized_file:
- resized.save(resized_file)
+ resized.save(resized_file, quality=config['quality'])
mgg.public_store.copy_local_to_storage(tmp_resized_filename, new_path)
@@ -118,7 +119,7 @@ def process_image(proc_state):
# Always create a small thumbnail
thumb_filepath = create_pub_filepath(
entry, name_builder.fill('{basename}.thumbnail{ext}'))
- resize_image(entry, queued_filename, thumb_filepath,
+ resize_image(proc_state, queued_filename, thumb_filepath,
exif_tags, conversions_subdir,
(mgg.global_config['media:thumb']['max_width'],
mgg.global_config['media:thumb']['max_height']))
@@ -134,7 +135,7 @@ def process_image(proc_state):
medium_filepath = create_pub_filepath(
entry, name_builder.fill('{basename}.medium{ext}'))
resize_image(
- entry, queued_filename, medium_filepath,
+ proc_state, queued_filename, medium_filepath,
exif_tags, conversions_subdir,
(mgg.global_config['media:medium']['max_width'],
mgg.global_config['media:medium']['max_height']))
diff --git a/mediagoblin/media_types/pdf/processing.py b/mediagoblin/media_types/pdf/processing.py
index d5db2223..49742fd7 100644
--- a/mediagoblin/media_types/pdf/processing.py
+++ b/mediagoblin/media_types/pdf/processing.py
@@ -16,7 +16,7 @@
import os
import logging
import dateutil.parser
-from subprocess import STDOUT, check_output, call, CalledProcessError
+from subprocess import PIPE, Popen
from mediagoblin import mg_globals as mgg
from mediagoblin.processing import (create_pub_filepath,
@@ -125,9 +125,14 @@ unoconv_supported = [
]
def is_unoconv_working():
+ # TODO: must have libreoffice-headless installed too, need to check for it
+ unoconv = where('unoconv')
+ if not unoconv:
+ return False
try:
- output = check_output([where('unoconv'), '--show'], stderr=STDOUT)
- except CalledProcessError, e:
+ proc = Popen([unoconv, '--show'], stderr=PIPE)
+ output = proc.stderr.read()
+ except OSError, e:
_log.warn(_('unoconv failing to run, check log file'))
return False
if 'ERROR' in output:
@@ -137,8 +142,7 @@ def is_unoconv_working():
def supported_extensions(cache=[None]):
if cache[0] == None:
cache[0] = 'pdf'
- # TODO: must have libreoffice-headless installed too, need to check for it
- if where('unoconv') and is_unoconv_working():
+ if is_unoconv_working():
cache.extend(unoconv_supported)
return cache
@@ -177,7 +181,7 @@ def create_pdf_thumb(original, thumb_filename, width, height):
args = [executable, '-scale-to', str(min(width, height)),
'-singlefile', '-png', original, thumb_filename]
_log.debug('calling {0}'.format(repr(' '.join(args))))
- call(executable=executable, args=args)
+ Popen(executable=executable, args=args).wait()
def pdf_info(original):
"""
@@ -191,9 +195,10 @@ def pdf_info(original):
ret_dict = {}
pdfinfo = where('pdfinfo')
try:
- lines = check_output(executable=pdfinfo,
- args=[pdfinfo, original]).split(os.linesep)
- except CalledProcessError:
+ proc = Popen(executable=pdfinfo,
+ args=[pdfinfo, original], stdout=PIPE)
+ lines = proc.stdout.readlines()
+ except OSError:
_log.debug('pdfinfo could not read the pdf file.')
raise BadMediaFail()
diff --git a/mediagoblin/media_types/stl/processing.py b/mediagoblin/media_types/stl/processing.py
index e41df395..49382495 100644
--- a/mediagoblin/media_types/stl/processing.py
+++ b/mediagoblin/media_types/stl/processing.py
@@ -64,8 +64,6 @@ def blender_render(config):
"""
Called to prerender a model.
"""
- arg_string = "blender -b blender_render.blend -F "
- arg_string +="JPEG -P blender_render.py"
env = {"RENDER_SETUP" : json.dumps(config), "DISPLAY":":0"}
subprocess.call(
["blender",
diff --git a/mediagoblin/media_types/video/transcoders.py b/mediagoblin/media_types/video/transcoders.py
index 58b2c0d4..90a767dd 100644
--- a/mediagoblin/media_types/video/transcoders.py
+++ b/mediagoblin/media_types/video/transcoders.py
@@ -26,7 +26,10 @@ import pygst
pygst.require('0.10')
import gst
import struct
-import Image
+try:
+ from PIL import Image
+except ImportError:
+ import Image
from gst.extend import discoverer
diff --git a/mediagoblin/plugins/oauth/forms.py b/mediagoblin/plugins/oauth/forms.py
index d0a4e9b8..5edd992a 100644
--- a/mediagoblin/plugins/oauth/forms.py
+++ b/mediagoblin/plugins/oauth/forms.py
@@ -19,7 +19,7 @@ import wtforms
from urlparse import urlparse
from mediagoblin.tools.extlib.wtf_html5 import URLField
-from mediagoblin.tools.translate import fake_ugettext_passthrough as _
+from mediagoblin.tools.translate import lazy_pass_to_ugettext as _
class AuthorizationForm(wtforms.Form):
diff --git a/mediagoblin/plugins/piwigo/tools.py b/mediagoblin/plugins/piwigo/tools.py
index 85d77310..4d2e985a 100644
--- a/mediagoblin/plugins/piwigo/tools.py
+++ b/mediagoblin/plugins/piwigo/tools.py
@@ -16,6 +16,7 @@
import logging
+import six
import lxml.etree as ET
from werkzeug.exceptions import MethodNotAllowed
@@ -43,7 +44,7 @@ class PwgNamedArray(list):
def _fill_element_dict(el, data, as_attr=()):
for k, v in data.iteritems():
if k in as_attr:
- if not isinstance(v, basestring):
+ if not isinstance(v, six.string_types):
v = str(v)
el.set(k, v)
else:
@@ -57,7 +58,7 @@ def _fill_element(el, data):
el.text = "1"
else:
el.text = "0"
- elif isinstance(data, basestring):
+ elif isinstance(data, six.string_types):
el.text = data
elif isinstance(data, int):
el.text = str(data)
diff --git a/mediagoblin/plugins/piwigo/views.py b/mediagoblin/plugins/piwigo/views.py
index 3dee09cd..bd3f9320 100644
--- a/mediagoblin/plugins/piwigo/views.py
+++ b/mediagoblin/plugins/piwigo/views.py
@@ -17,12 +17,12 @@
import logging
import re
-from werkzeug.exceptions import MethodNotAllowed, BadRequest
+from werkzeug.exceptions import MethodNotAllowed, BadRequest, NotImplemented
from werkzeug.wrappers import BaseResponse
from mediagoblin import mg_globals
from mediagoblin.meddleware.csrf import csrf_exempt
-from mediagoblin.tools.response import render_404
+from mediagoblin.submit.lib import check_file_field
from .tools import CmdTable, PwgNamedArray, response_xml
from .forms import AddSimpleForm
@@ -92,6 +92,9 @@ def pwg_images_addSimple(request):
dump.append("%s=%r" % (f.name, f.data))
_log.info("addimple: %r %s %r", request.form, " ".join(dump), request.files)
+ if not check_file_field(request, 'image'):
+ raise BadRequest()
+
return {'image_id': 123456, 'url': ''}
@@ -153,7 +156,7 @@ def ws_php(request):
if not func:
_log.warn("wsphp: Unhandled %s %r %r", request.method,
request.args, request.form)
- return render_404(request)
+ raise NotImplemented()
result = func(request)
diff --git a/mediagoblin/processing/__init__.py b/mediagoblin/processing/__init__.py
index a1fd3fb7..f3a85940 100644
--- a/mediagoblin/processing/__init__.py
+++ b/mediagoblin/processing/__init__.py
@@ -75,6 +75,14 @@ class FilenameBuilder(object):
class ProcessingState(object):
+ """
+ The first and only argument to the "processor" of a media type
+
+ This could be thought of as a "request" to the processor
+ function. It has the main info for the request (media entry)
+ and a bunch of tools for the request on it.
+ It can get more fancy without impacting old media types.
+ """
def __init__(self, entry):
self.entry = entry
self.workbench = None
diff --git a/mediagoblin/processing/task.py b/mediagoblin/processing/task.py
index aec50aab..9af192ed 100644
--- a/mediagoblin/processing/task.py
+++ b/mediagoblin/processing/task.py
@@ -89,7 +89,7 @@ class ProcessMedia(task.Task):
with mgg.workbench_manager.create() as workbench:
proc_state.set_workbench(workbench)
# run the processing code
- entry.media_manager['processor'](proc_state)
+ entry.media_manager.processor(proc_state)
# We set the state to processed and save the entry here so there's
# no need to save at the end of the processing stage, probably ;)
diff --git a/mediagoblin/submit/forms.py b/mediagoblin/submit/forms.py
index bd1e904f..e9bd93fd 100644
--- a/mediagoblin/submit/forms.py
+++ b/mediagoblin/submit/forms.py
@@ -18,7 +18,7 @@
import wtforms
from mediagoblin.tools.text import tag_length_validator
-from mediagoblin.tools.translate import fake_ugettext_passthrough as _
+from mediagoblin.tools.translate import lazy_pass_to_ugettext as _
from mediagoblin.tools.licenses import licenses_as_choices
diff --git a/mediagoblin/submit/lib.py b/mediagoblin/submit/lib.py
index a5483471..7c3b8ab3 100644
--- a/mediagoblin/submit/lib.py
+++ b/mediagoblin/submit/lib.py
@@ -40,7 +40,7 @@ def prepare_queue_task(app, entry, filename):
"""
Prepare a MediaEntry for the processing queue and get a queue file
"""
- # We generate this ourselves so we know what the taks id is for
+ # We generate this ourselves so we know what the task id is for
# retrieval later.
# (If we got it off the task's auto-generation, there'd be
diff --git a/mediagoblin/templates/mediagoblin/utils/wtforms.html b/mediagoblin/templates/mediagoblin/utils/wtforms.html
index 35b4aa04..be6976c2 100644
--- a/mediagoblin/templates/mediagoblin/utils/wtforms.html
+++ b/mediagoblin/templates/mediagoblin/utils/wtforms.html
@@ -19,7 +19,7 @@
{# Render the label for a field #}
{% macro render_label(field) %}
{%- if field.label.text -%}
- <label for="{{ field.label.field_id }}">{{ _(field.label.text) }}</label>
+ <label for="{{ field.label.field_id }}">{{ field.label.text }}</label>
{%- endif -%}
{%- endmacro %}
@@ -39,11 +39,11 @@
{{ field }}
{%- if field.errors -%}
{% for error in field.errors %}
- <p class="form_field_error">{{ _(error) }}</p>
+ <p class="form_field_error">{{ error }}</p>
{% endfor %}
{%- endif %}
{%- if field.description %}
- <p class="form_field_description">{{ _(field.description)|safe }}</p>
+ <p class="form_field_description">{{ field.description|safe }}</p>
{%- endif %}
</div>
{%- endmacro %}
@@ -59,7 +59,7 @@
{% macro render_table(form) -%}
{% for field in form %}
<tr>
- <th>{{ _(field.label.text) }}</th>
+ <th>{{ field.label.text }}</th>
<td>
{{field}}
{% if field.errors %}
diff --git a/mediagoblin/tests/resources.py b/mediagoblin/tests/resources.py
new file mode 100644
index 00000000..f7b3037d
--- /dev/null
+++ b/mediagoblin/tests/resources.py
@@ -0,0 +1,41 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2013 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 pkg_resources import resource_filename
+
+
+def resource(filename):
+ return resource_filename('mediagoblin.tests', 'test_submission/' + filename)
+
+
+GOOD_JPG = resource('good.jpg')
+GOOD_PNG = resource('good.png')
+EVIL_FILE = resource('evil')
+EVIL_JPG = resource('evil.jpg')
+EVIL_PNG = resource('evil.png')
+BIG_BLUE = resource('bigblue.png')
+GOOD_PDF = resource('good.pdf')
+
+
+def resource_exif(f):
+ return resource_filename('mediagoblin.tests', 'test_exif/' + f)
+
+
+GOOD_JPG = resource_exif('good.jpg')
+EMPTY_JPG = resource_exif('empty.jpg')
+BAD_JPG = resource_exif('bad.jpg')
+GPS_JPG = resource_exif('has-gps.jpg')
diff --git a/mediagoblin/tests/test_api.py b/mediagoblin/tests/test_api.py
index cff25776..89cf1026 100644
--- a/mediagoblin/tests/test_api.py
+++ b/mediagoblin/tests/test_api.py
@@ -18,31 +18,17 @@
import logging
import base64
-from pkg_resources import resource_filename
-
import pytest
from mediagoblin import mg_globals
from mediagoblin.tools import template, pluginapi
from mediagoblin.tests.tools import fixture_add_user
+from .resources import GOOD_JPG, GOOD_PNG, EVIL_FILE, EVIL_JPG, EVIL_PNG, \
+ BIG_BLUE
_log = logging.getLogger(__name__)
-def resource(filename):
- '''
- Borrowed from the submission tests
- '''
- return resource_filename('mediagoblin.tests', 'test_submission/' + filename)
-
-
-GOOD_JPG = resource('good.jpg')
-GOOD_PNG = resource('good.png')
-EVIL_FILE = resource('evil')
-EVIL_JPG = resource('evil.jpg')
-EVIL_PNG = resource('evil.png')
-BIG_BLUE = resource('bigblue.png')
-
class TestAPI(object):
def setup(self):
diff --git a/mediagoblin/tests/test_exif.py b/mediagoblin/tests/test_exif.py
index 100d17f0..824de3c2 100644
--- a/mediagoblin/tests/test_exif.py
+++ b/mediagoblin/tests/test_exif.py
@@ -15,39 +15,20 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
-import pkg_resources
-import Image
+try:
+ from PIL import Image
+except ImportError:
+ import Image
from mediagoblin.tools.exif import exif_fix_image_orientation, \
extract_exif, clean_exif, get_gps_data, get_useful
+from .resources import GOOD_JPG, EMPTY_JPG, BAD_JPG, GPS_JPG
def assert_in(a, b):
assert a in b, "%r not in %r" % (a, b)
-GOOD_JPG = pkg_resources.resource_filename(
- 'mediagoblin.tests',
- os.path.join(
- 'test_exif',
- 'good.jpg'))
-EMPTY_JPG = pkg_resources.resource_filename(
- 'mediagoblin.tests',
- os.path.join(
- 'test_exif',
- 'empty.jpg'))
-BAD_JPG = pkg_resources.resource_filename(
- 'mediagoblin.tests',
- os.path.join(
- 'test_exif',
- 'bad.jpg'))
-GPS_JPG = pkg_resources.resource_filename(
- 'mediagoblin.tests',
- os.path.join(
- 'test_exif',
- 'has-gps.jpg'))
-
-
def test_exif_extraction():
'''
Test EXIF extraction from a good image
diff --git a/mediagoblin/tests/test_http_callback.py b/mediagoblin/tests/test_http_callback.py
index e2c85d0d..a0511af7 100644
--- a/mediagoblin/tests/test_http_callback.py
+++ b/mediagoblin/tests/test_http_callback.py
@@ -16,6 +16,7 @@
import json
+import pytest
from urlparse import urlparse, parse_qs
from mediagoblin import mg_globals
@@ -26,21 +27,24 @@ from mediagoblin.tests import test_oauth as oauth
class TestHTTPCallback(object):
- def _setup(self, test_app):
+ @pytest.fixture(autouse=True)
+ def setup(self, test_app):
+ self.test_app = test_app
+
self.db = mg_globals.database
self.user_password = u'secret'
self.user = fixture_add_user(u'call_back', self.user_password)
- self.login(test_app)
+ self.login()
- def login(self, testapp):
- testapp.post('/auth/login/', {
+ def login(self):
+ self.test_app.post('/auth/login/', {
'username': self.user.username,
'password': self.user_password})
- def get_access_token(self, testapp, client_id, client_secret, code):
- response = testapp.get('/oauth/access_token', {
+ def get_access_token(self, client_id, client_secret, code):
+ response = self.test_app.get('/oauth/access_token', {
'code': code,
'client_id': client_id,
'client_secret': client_secret})
@@ -49,15 +53,12 @@ class TestHTTPCallback(object):
return response_data['access_token']
- def test_callback(self, test_app):
+ def test_callback(self):
''' Test processing HTTP callback '''
- self._setup(test_app)
-
self.oauth = oauth.TestOAuth()
- self.oauth._setup(test_app)
+ self.oauth.setup(self.test_app)
- redirect, client_id = self.oauth.test_4_authorize_confidential_client(
- test_app)
+ redirect, client_id = self.oauth.test_4_authorize_confidential_client()
code = parse_qs(urlparse(redirect.location).query)['code'][0]
@@ -66,11 +67,11 @@ class TestHTTPCallback(object):
client_secret = client.secret
- access_token = self.get_access_token(test_app, client_id, client_secret, code)
+ access_token = self.get_access_token(client_id, client_secret, code)
callback_url = 'https://foo.example?secrettestmediagoblinparam'
- res = test_app.post('/api/submit?client_id={0}&access_token={1}\
+ self.test_app.post('/api/submit?client_id={0}&access_token={1}\
&client_secret={2}'.format(
client_id,
access_token,
diff --git a/mediagoblin/tests/test_oauth.py b/mediagoblin/tests/test_oauth.py
index 7ad98459..ea3bd798 100644
--- a/mediagoblin/tests/test_oauth.py
+++ b/mediagoblin/tests/test_oauth.py
@@ -17,6 +17,7 @@
import json
import logging
+import pytest
from urlparse import parse_qs, urlparse
from mediagoblin import mg_globals
@@ -28,7 +29,10 @@ _log = logging.getLogger(__name__)
class TestOAuth(object):
- def _setup(self, test_app):
+ @pytest.fixture(autouse=True)
+ def setup(self, test_app):
+ self.test_app = test_app
+
self.db = mg_globals.database
self.pman = pluginapi.PluginManager()
@@ -36,17 +40,17 @@ class TestOAuth(object):
self.user_password = u'4cc355_70k3N'
self.user = fixture_add_user(u'joauth', self.user_password)
- self.login(test_app)
+ self.login()
- def login(self, test_app):
- test_app.post(
- '/auth/login/', {
- 'username': self.user.username,
- 'password': self.user_password})
+ def login(self):
+ self.test_app.post(
+ '/auth/login/', {
+ 'username': self.user.username,
+ 'password': self.user_password})
- def register_client(self, test_app, name, client_type, description=None,
- redirect_uri=''):
- return test_app.post(
+ def register_client(self, name, client_type, description=None,
+ redirect_uri=''):
+ return self.test_app.post(
'/oauth/client/register', {
'name': name,
'description': description,
@@ -56,12 +60,10 @@ class TestOAuth(object):
def get_context(self, template_name):
return template.TEMPLATE_TEST_CONTEXT[template_name]
- def test_1_public_client_registration_without_redirect_uri(self, test_app):
+ def test_1_public_client_registration_without_redirect_uri(self):
''' Test 'public' OAuth client registration without any redirect uri '''
- self._setup(test_app)
-
- response = self.register_client(test_app, u'OMGOMGOMG', 'public',
- 'OMGOMG Apache License v2')
+ response = self.register_client(
+ u'OMGOMGOMG', 'public', 'OMGOMG Apache License v2')
ctx = self.get_context('oauth/client/register.html')
@@ -76,12 +78,11 @@ class TestOAuth(object):
# Should not pass through
assert not client
- def test_2_successful_public_client_registration(self, test_app):
+ def test_2_successful_public_client_registration(self):
''' Successfully register a public client '''
- self._setup(test_app)
uri = 'http://foo.example'
- self.register_client(test_app, u'OMGOMG', 'public', 'OMG!',
- uri)
+ self.register_client(
+ u'OMGOMG', 'public', 'OMG!', uri)
client = self.db.OAuthClient.query.filter(
self.db.OAuthClient.name == u'OMGOMG').first()
@@ -92,12 +93,10 @@ class TestOAuth(object):
# Client should have been registered
assert client
- def test_3_successful_confidential_client_reg(self, test_app):
+ def test_3_successful_confidential_client_reg(self):
''' Register a confidential OAuth client '''
- self._setup(test_app)
-
response = self.register_client(
- test_app, u'GMOGMO', 'confidential', 'NO GMO!')
+ u'GMOGMO', 'confidential', 'NO GMO!')
assert response.status_int == 302
@@ -109,16 +108,14 @@ class TestOAuth(object):
return client
- def test_4_authorize_confidential_client(self, test_app):
+ def test_4_authorize_confidential_client(self):
''' Authorize a confidential client as a logged in user '''
- self._setup(test_app)
-
- client = self.test_3_successful_confidential_client_reg(test_app)
+ client = self.test_3_successful_confidential_client_reg()
client_identifier = client.identifier
redirect_uri = 'https://foo.example'
- response = test_app.get('/oauth/authorize', {
+ response = self.test_app.get('/oauth/authorize', {
'client_id': client.identifier,
'scope': 'all',
'redirect_uri': redirect_uri})
@@ -131,7 +128,7 @@ class TestOAuth(object):
form = ctx['form']
# Short for client authorization post reponse
- capr = test_app.post(
+ capr = self.test_app.post(
'/oauth/client/authorize', {
'client_id': form.client_id.data,
'allow': 'Allow',
@@ -149,19 +146,16 @@ class TestOAuth(object):
''' Get the value of ?code= from an URI '''
return parse_qs(urlparse(uri).query)['code'][0]
- def test_token_endpoint_successful_confidential_request(self, test_app):
+ def test_token_endpoint_successful_confidential_request(self):
''' Successful request against token endpoint '''
- self._setup(test_app)
-
- code_redirect, client_id = self.test_4_authorize_confidential_client(
- test_app)
+ code_redirect, client_id = self.test_4_authorize_confidential_client()
code = self.get_code_from_redirect_uri(code_redirect.location)
client = self.db.OAuthClient.query.filter(
self.db.OAuthClient.identifier == unicode(client_id)).first()
- token_res = test_app.get('/oauth/access_token?client_id={0}&\
+ token_res = self.test_app.get('/oauth/access_token?client_id={0}&\
code={1}&client_secret={2}'.format(client_id, code, client.secret))
assert token_res.status_int == 200
@@ -180,19 +174,16 @@ code={1}&client_secret={2}'.format(client_id, code, client.secret))
return client_id, token_data
- def test_token_endpont_missing_id_confidential_request(self, test_app):
+ def test_token_endpont_missing_id_confidential_request(self):
''' Unsuccessful request against token endpoint, missing client_id '''
- self._setup(test_app)
-
- code_redirect, client_id = self.test_4_authorize_confidential_client(
- test_app)
+ code_redirect, client_id = self.test_4_authorize_confidential_client()
code = self.get_code_from_redirect_uri(code_redirect.location)
client = self.db.OAuthClient.query.filter(
self.db.OAuthClient.identifier == unicode(client_id)).first()
- token_res = test_app.get('/oauth/access_token?\
+ token_res = self.test_app.get('/oauth/access_token?\
code={0}&client_secret={1}'.format(code, client.secret))
assert token_res.status_int == 200
@@ -204,16 +195,16 @@ code={0}&client_secret={1}'.format(code, client.secret))
assert token_data['error'] == 'invalid_request'
assert len(token_data['error_description'])
- def test_refresh_token(self, test_app):
+ def test_refresh_token(self):
''' Try to get a new access token using the refresh token '''
# Get an access token and a refresh token
client_id, token_data =\
- self.test_token_endpoint_successful_confidential_request(test_app)
+ self.test_token_endpoint_successful_confidential_request()
client = self.db.OAuthClient.query.filter(
self.db.OAuthClient.identifier == client_id).first()
- token_res = test_app.get('/oauth/access_token',
+ token_res = self.test_app.get('/oauth/access_token',
{'refresh_token': token_data['refresh_token'],
'client_id': client_id,
'client_secret': client.secret
diff --git a/mediagoblin/tests/test_pdf.py b/mediagoblin/tests/test_pdf.py
index a3979a25..b4d1940a 100644
--- a/mediagoblin/tests/test_pdf.py
+++ b/mediagoblin/tests/test_pdf.py
@@ -17,16 +17,15 @@
import tempfile
import shutil
import os
-
+import pytest
from mediagoblin.media_types.pdf.processing import (
pdf_info, check_prerequisites, create_pdf_thumb)
+from .resources import GOOD_PDF as GOOD
-GOOD='mediagoblin/tests/test_submission/good.pdf'
+@pytest.mark.skipif("not check_prerequisites()")
def test_pdf():
- if not check_prerequisites():
- return
good_dict = {'pdf_version_major': 1, 'pdf_title': '',
'pdf_page_size_width': 612, 'pdf_author': '',
'pdf_keywords': '', 'pdf_pages': 10,
diff --git a/mediagoblin/tests/test_pluginapi.py b/mediagoblin/tests/test_pluginapi.py
index d40a5081..f03e868f 100644
--- a/mediagoblin/tests/test_pluginapi.py
+++ b/mediagoblin/tests/test_pluginapi.py
@@ -177,19 +177,22 @@ def test_disabled_plugin():
assert len(pman.plugins) == 0
+CONFIG_ALL_CALLABLES = [
+ ('mediagoblin', {}, []),
+ ('plugins', {}, [
+ ('mediagoblin.tests.testplugins.callables1', {}, []),
+ ('mediagoblin.tests.testplugins.callables2', {}, []),
+ ('mediagoblin.tests.testplugins.callables3', {}, []),
+ ])
+ ]
+
+
@with_cleanup()
-def test_callable_runone():
+def test_hook_handle():
"""
- Test the callable_runone method
+ Test the hook_handle method
"""
- cfg = build_config([
- ('mediagoblin', {}, []),
- ('plugins', {}, [
- ('mediagoblin.tests.testplugins.callables1', {}, []),
- ('mediagoblin.tests.testplugins.callables2', {}, []),
- ('mediagoblin.tests.testplugins.callables3', {}, []),
- ])
- ])
+ cfg = build_config(CONFIG_ALL_CALLABLES)
mg_globals.app_config = cfg['mediagoblin']
mg_globals.global_config = cfg
@@ -198,50 +201,42 @@ def test_callable_runone():
# Just one hook provided
call_log = []
- assert pluginapi.callable_runone(
+ assert pluginapi.hook_handle(
"just_one", call_log) == "Called just once"
assert call_log == ["expect this one call"]
# Nothing provided and unhandled not okay
call_log = []
- with pytest.raises(pluginapi.UnhandledCallable):
- pluginapi.callable_runone(
- "nothing_handling", call_log)
+ pluginapi.hook_handle(
+ "nothing_handling", call_log) == None
assert call_log == []
# Nothing provided and unhandled okay
call_log = []
- assert pluginapi.callable_runone(
+ assert pluginapi.hook_handle(
"nothing_handling", call_log, unhandled_okay=True) is None
assert call_log == []
# Multiple provided, go with the first!
call_log = []
- assert pluginapi.callable_runone(
+ assert pluginapi.hook_handle(
"multi_handle", call_log) == "the first returns"
assert call_log == ["Hi, I'm the first"]
# Multiple provided, one has CantHandleIt
call_log = []
- assert pluginapi.callable_runone(
+ assert pluginapi.hook_handle(
"multi_handle_with_canthandle",
call_log) == "the second returns"
assert call_log == ["Hi, I'm the second"]
@with_cleanup()
-def test_callable_runall():
+def test_hook_runall():
"""
- Test the callable_runall method
+ Test the hook_runall method
"""
- cfg = build_config([
- ('mediagoblin', {}, []),
- ('plugins', {}, [
- ('mediagoblin.tests.testplugins.callables1', {}, []),
- ('mediagoblin.tests.testplugins.callables2', {}, []),
- ('mediagoblin.tests.testplugins.callables3', {}, []),
- ])
- ])
+ cfg = build_config(CONFIG_ALL_CALLABLES)
mg_globals.app_config = cfg['mediagoblin']
mg_globals.global_config = cfg
@@ -250,19 +245,19 @@ def test_callable_runall():
# Just one hook, check results
call_log = []
- assert pluginapi.callable_runall(
- "just_one", call_log) == ["Called just once", None, None]
+ assert pluginapi.hook_runall(
+ "just_one", call_log) == ["Called just once"]
assert call_log == ["expect this one call"]
# None provided, check results
call_log = []
- assert pluginapi.callable_runall(
+ assert pluginapi.hook_runall(
"nothing_handling", call_log) == []
assert call_log == []
# Multiple provided, check results
call_log = []
- assert pluginapi.callable_runall(
+ assert pluginapi.hook_runall(
"multi_handle", call_log) == [
"the first returns",
"the second returns",
@@ -275,7 +270,7 @@ def test_callable_runall():
# Multiple provided, one has CantHandleIt, check results
call_log = []
- assert pluginapi.callable_runall(
+ assert pluginapi.hook_runall(
"multi_handle_with_canthandle", call_log) == [
"the second returns",
"the third returns",
@@ -283,3 +278,19 @@ def test_callable_runall():
assert call_log == [
"Hi, I'm the second",
"Hi, I'm the third"]
+
+
+@with_cleanup()
+def test_hook_transform():
+ """
+ Test the hook_transform method
+ """
+ cfg = build_config(CONFIG_ALL_CALLABLES)
+
+ mg_globals.app_config = cfg['mediagoblin']
+ mg_globals.global_config = cfg
+
+ setup_plugins()
+
+ assert pluginapi.hook_transform(
+ "expand_tuple", (-1, 0)) == (-1, 0, 1, 2, 3)
diff --git a/mediagoblin/tests/test_storage.py b/mediagoblin/tests/test_storage.py
index 749f7b07..f6f1d18f 100644
--- a/mediagoblin/tests/test_storage.py
+++ b/mediagoblin/tests/test_storage.py
@@ -95,6 +95,14 @@ def get_tmp_filestorage(mount_url=None, fake_remote=False):
return tmpdir, this_storage
+def cleanup_storage(this_storage, tmpdir, *paths):
+ for p in paths:
+ while p:
+ assert this_storage.delete_dir(p) == True
+ p.pop(-1)
+ os.rmdir(tmpdir)
+
+
def test_basic_storage__resolve_filepath():
tmpdir, this_storage = get_tmp_filestorage()
@@ -111,7 +119,7 @@ def test_basic_storage__resolve_filepath():
this_storage._resolve_filepath,
['../../', 'etc', 'passwd'])
- os.rmdir(tmpdir)
+ cleanup_storage(this_storage, tmpdir)
def test_basic_storage_file_exists():
@@ -127,6 +135,7 @@ def test_basic_storage_file_exists():
assert not this_storage.file_exists(['dnedir1', 'dnedir2', 'somefile.lol'])
this_storage.delete_file(['dir1', 'dir2', 'filename.txt'])
+ cleanup_storage(this_storage, tmpdir, ['dir1', 'dir2'])
def test_basic_storage_get_unique_filepath():
@@ -149,6 +158,7 @@ def test_basic_storage_get_unique_filepath():
assert new_filename == secure_filename(new_filename)
os.remove(filename)
+ cleanup_storage(this_storage, tmpdir, ['dir1', 'dir2'])
def test_basic_storage_get_file():
@@ -189,6 +199,7 @@ def test_basic_storage_get_file():
this_storage.delete_file(filepath)
this_storage.delete_file(new_filepath)
this_storage.delete_file(['testydir', 'testyfile.txt'])
+ cleanup_storage(this_storage, tmpdir, ['dir1', 'dir2'], ['testydir'])
def test_basic_storage_delete_file():
@@ -204,11 +215,15 @@ def test_basic_storage_delete_file():
assert os.path.exists(
os.path.join(tmpdir, 'dir1/dir2/ourfile.txt'))
+ assert this_storage.delete_dir(['dir1', 'dir2']) == False
this_storage.delete_file(filepath)
+ assert this_storage.delete_dir(['dir1', 'dir2']) == True
assert not os.path.exists(
os.path.join(tmpdir, 'dir1/dir2/ourfile.txt'))
+ cleanup_storage(this_storage, tmpdir, ['dir1'])
+
def test_basic_storage_url_for_file():
# Not supplying a base_url should actually just bork.
@@ -217,7 +232,7 @@ def test_basic_storage_url_for_file():
storage.NoWebServing,
this_storage.file_url,
['dir1', 'dir2', 'filename.txt'])
- os.rmdir(tmpdir)
+ cleanup_storage(this_storage, tmpdir)
# base_url without domain
tmpdir, this_storage = get_tmp_filestorage('/media/')
@@ -225,7 +240,7 @@ def test_basic_storage_url_for_file():
['dir1', 'dir2', 'filename.txt'])
expected = '/media/dir1/dir2/filename.txt'
assert result == expected
- os.rmdir(tmpdir)
+ cleanup_storage(this_storage, tmpdir)
# base_url with domain
tmpdir, this_storage = get_tmp_filestorage(
@@ -234,7 +249,7 @@ def test_basic_storage_url_for_file():
['dir1', 'dir2', 'filename.txt'])
expected = 'http://media.example.org/ourmedia/dir1/dir2/filename.txt'
assert result == expected
- os.rmdir(tmpdir)
+ cleanup_storage(this_storage, tmpdir)
def test_basic_storage_get_local_path():
@@ -248,13 +263,13 @@ def test_basic_storage_get_local_path():
assert result == expected
- os.rmdir(tmpdir)
+ cleanup_storage(this_storage, tmpdir)
def test_basic_storage_is_local():
tmpdir, this_storage = get_tmp_filestorage()
assert this_storage.local_storage is True
- os.rmdir(tmpdir)
+ cleanup_storage(this_storage, tmpdir)
def test_basic_storage_copy_locally():
@@ -275,6 +290,7 @@ def test_basic_storage_copy_locally():
os.remove(new_file_dest)
os.rmdir(dest_tmpdir)
+ cleanup_storage(this_storage, tmpdir, ['dir1', 'dir2'])
def _test_copy_local_to_storage_works(tmpdir, this_storage):
@@ -292,6 +308,7 @@ def _test_copy_local_to_storage_works(tmpdir, this_storage):
'r').read() == 'haha'
this_storage.delete_file(['dir1', 'dir2', 'copiedto.txt'])
+ cleanup_storage(this_storage, tmpdir, ['dir1', 'dir2'])
def test_basic_storage_copy_local_to_storage():
diff --git a/mediagoblin/tests/test_submission.py b/mediagoblin/tests/test_submission.py
index 462a1653..5ac47316 100644
--- a/mediagoblin/tests/test_submission.py
+++ b/mediagoblin/tests/test_submission.py
@@ -20,8 +20,7 @@ sys.setdefaultencoding('utf-8')
import urlparse
import os
-
-from pkg_resources import resource_filename
+import pytest
from mediagoblin.tests.tools import fixture_add_user
from mediagoblin import mg_globals
@@ -30,19 +29,8 @@ from mediagoblin.tools import template
from mediagoblin.media_types.image import MEDIA_MANAGER as img_MEDIA_MANAGER
from mediagoblin.media_types.pdf.processing import check_prerequisites as pdf_check_prerequisites
-def resource(filename):
- return resource_filename('mediagoblin.tests', 'test_submission/' + filename)
-
-
-GOOD_JPG = resource('good.jpg')
-GOOD_PNG = resource('good.png')
-EVIL_FILE = resource('evil')
-EVIL_JPG = resource('evil.jpg')
-EVIL_PNG = resource('evil.png')
-BIG_BLUE = resource('bigblue.png')
-GOOD_PDF = resource('good.pdf')
-
-from .test_exif import GPS_JPG
+from .resources import GOOD_JPG, GOOD_PNG, EVIL_FILE, EVIL_JPG, EVIL_PNG, \
+ BIG_BLUE, GOOD_PDF, GPS_JPG
GOOD_TAG_STRING = u'yin,yang'
BAD_TAG_STRING = unicode('rage,' + 'f' * 26 + 'u' * 26)
@@ -52,7 +40,8 @@ REQUEST_CONTEXT = ['mediagoblin/user_pages/user.html', 'request']
class TestSubmission:
- def _setup(self, test_app):
+ @pytest.fixture(autouse=True)
+ def setup(self, test_app):
self.test_app = test_app
# TODO: Possibly abstract into a decorator like:
@@ -91,9 +80,7 @@ class TestSubmission:
comments = request.db.MediaComment.find({'media_entry': media_id})
assert count == len(list(comments))
- def test_missing_fields(self, test_app):
- self._setup(test_app)
-
+ def test_missing_fields(self):
# Test blank form
# ---------------
response, form = self.do_post({}, *FORM_CONTEXT)
@@ -120,18 +107,14 @@ class TestSubmission:
self.logout()
self.test_app.get(url)
- def test_normal_jpg(self, test_app):
- self._setup(test_app)
+ def test_normal_jpg(self):
self.check_normal_upload(u'Normal upload 1', GOOD_JPG)
- def test_normal_png(self, test_app):
- self._setup(test_app)
+ def test_normal_png(self):
self.check_normal_upload(u'Normal upload 2', GOOD_PNG)
- def test_normal_pdf(self, test_app):
- if not pdf_check_prerequisites():
- return
- self._setup(test_app)
+ @pytest.mark.skipif("not pdf_check_prerequisites()")
+ def test_normal_pdf(self):
response, context = self.do_post({'title': u'Normal upload 3 (pdf)'},
do_follow=True,
**self.upload_data(GOOD_PDF))
@@ -146,9 +129,7 @@ class TestSubmission:
return
return media[0]
- def test_tags(self, test_app):
- self._setup(test_app)
-
+ def test_tags(self):
# Good tag string
# --------
response, request = self.do_post({'title': u'Balanced Goblin 2',
@@ -173,9 +154,7 @@ class TestSubmission:
'Tags that are too long: ' \
'ffffffffffffffffffffffffffuuuuuuuuuuuuuuuuuuuuuuuuuu']
- def test_delete(self, test_app):
- self._setup(test_app)
-
+ def test_delete(self):
response, request = self.do_post({'title': u'Balanced Goblin'},
*REQUEST_CONTEXT, do_follow=True,
**self.upload_data(GOOD_JPG))
@@ -220,9 +199,7 @@ class TestSubmission:
self.check_media(request, {'id': media_id}, 0)
self.check_comments(request, media_id, 0)
- def test_evil_file(self, test_app):
- self._setup(test_app)
-
+ def test_evil_file(self):
# Test non-suppoerted file with non-supported extension
# -----------------------------------------------------
response, form = self.do_post({'title': u'Malicious Upload 1'},
@@ -233,26 +210,23 @@ class TestSubmission:
str(form.file.errors[0])
- def test_get_media_manager(self, test_app):
+ def test_get_media_manager(self):
"""Test if the get_media_manger function returns sensible things
"""
- self._setup(test_app)
-
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)
assert media.media_type == u'mediagoblin.media_types.image'
- assert media.media_manager == img_MEDIA_MANAGER
+ assert isinstance(media.media_manager, img_MEDIA_MANAGER)
+ assert media.media_manager.entry == media
- def test_sniffing(self, test_app):
+ def test_sniffing(self):
'''
Test sniffing mechanism to assert that regular uploads work as intended
'''
- self._setup(test_app)
-
template.clear_test_template_context()
response = self.test_app.post(
'/submit/', {
@@ -282,30 +256,22 @@ class TestSubmission:
assert entry.state == 'failed'
assert entry.fail_error == u'mediagoblin.processing:BadMediaFail'
- def test_evil_jpg(self, test_app):
- self._setup(test_app)
-
+ def test_evil_jpg(self):
# Test non-supported file with .jpg extension
# -------------------------------------------
self.check_false_image(u'Malicious Upload 2', EVIL_JPG)
- def test_evil_png(self, test_app):
- self._setup(test_app)
-
+ def test_evil_png(self):
# Test non-supported file with .png extension
# -------------------------------------------
self.check_false_image(u'Malicious Upload 3', EVIL_PNG)
- def test_media_data(self, test_app):
- self._setup(test_app)
-
+ def test_media_data(self):
self.check_normal_upload(u"With GPS data", GPS_JPG)
media = self.check_media(None, {"title": u"With GPS data"}, 1)
assert media.media_data.gps_latitude == 59.336666666666666
- def test_processing(self, test_app):
- self._setup(test_app)
-
+ def test_processing(self):
public_store_dir = mg_globals.global_config[
'storage:publicstore']['base_dir']
diff --git a/mediagoblin/tests/test_util.py b/mediagoblin/tests/test_util.py
index e4c04b7a..bc14f528 100644
--- a/mediagoblin/tests/test_util.py
+++ b/mediagoblin/tests/test_util.py
@@ -104,6 +104,28 @@ def test_locale_to_lower_lower():
assert translate.locale_to_lower_lower('en_us') == 'en-us'
+def test_gettext_lazy_proxy():
+ from mediagoblin.tools.translate import lazy_pass_to_ugettext as _
+ from mediagoblin.tools.translate import pass_to_ugettext, set_thread_locale
+ proxy = _(u"Password")
+ orig = u"Password"
+
+ set_thread_locale("es")
+ p1 = unicode(proxy)
+ p1_should = pass_to_ugettext(orig)
+ assert p1_should != orig, "Test useless, string not translated"
+ assert p1 == p1_should
+
+ set_thread_locale("sv")
+ p2 = unicode(proxy)
+ p2_should = pass_to_ugettext(orig)
+ assert p2_should != orig, "Test broken, string not translated"
+ assert p2 == p2_should
+
+ assert p1_should != p2_should, "Test broken, same translated string"
+ assert p1 != p2
+
+
def test_html_cleaner():
# Remove images
result = text.clean_html(
diff --git a/mediagoblin/tests/test_workbench.py b/mediagoblin/tests/test_workbench.py
index 9cd49671..6695618b 100644
--- a/mediagoblin/tests/test_workbench.py
+++ b/mediagoblin/tests/test_workbench.py
@@ -21,7 +21,7 @@ import tempfile
from mediagoblin.tools import workbench
from mediagoblin.mg_globals import setup_globals
from mediagoblin.decorators import get_workbench
-from mediagoblin.tests.test_storage import get_tmp_filestorage
+from mediagoblin.tests.test_storage import get_tmp_filestorage, cleanup_storage
class TestWorkbench(object):
@@ -76,6 +76,7 @@ class TestWorkbench(object):
assert filename == os.path.join(
tmpdir, 'dir1/dir2/ourfile.txt')
this_storage.delete_file(filepath)
+ cleanup_storage(this_storage, tmpdir, ['dir1', 'dir2'])
# with a fake remote file storage
tmpdir, this_storage = get_tmp_filestorage(fake_remote=True)
@@ -102,6 +103,7 @@ class TestWorkbench(object):
this_workbench.dir, 'thisfile.text')
this_storage.delete_file(filepath)
+ cleanup_storage(this_storage, tmpdir, ['dir1', 'dir2'])
this_workbench.destroy()
def test_workbench_decorator(self):
diff --git a/mediagoblin/tests/testplugins/callables1/__init__.py b/mediagoblin/tests/testplugins/callables1/__init__.py
index 9c278b49..fe801a01 100644
--- a/mediagoblin/tests/testplugins/callables1/__init__.py
+++ b/mediagoblin/tests/testplugins/callables1/__init__.py
@@ -14,8 +14,6 @@
# 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 mediagoblin.tools.pluginapi import CantHandleIt
-
def setup_plugin():
pass
@@ -30,12 +28,16 @@ def multi_handle(call_log):
return "the first returns"
def multi_handle_with_canthandle(call_log):
- raise CantHandleIt("I just can't accept this stupid method")
+ return None
+
+def expand_tuple(this_tuple):
+ return this_tuple + (1,)
hooks = {
'setup': setup_plugin,
'just_one': just_one,
'multi_handle': multi_handle,
'multi_handle_with_canthandle': multi_handle_with_canthandle,
+ 'expand_tuple': expand_tuple,
}
diff --git a/mediagoblin/tests/testplugins/callables2/__init__.py b/mediagoblin/tests/testplugins/callables2/__init__.py
index aaab5b21..9d5cf950 100644
--- a/mediagoblin/tests/testplugins/callables2/__init__.py
+++ b/mediagoblin/tests/testplugins/callables2/__init__.py
@@ -29,10 +29,13 @@ def multi_handle_with_canthandle(call_log):
call_log.append("Hi, I'm the second")
return "the second returns"
+def expand_tuple(this_tuple):
+ return this_tuple + (2,)
hooks = {
'setup': setup_plugin,
'just_one': just_one,
'multi_handle': multi_handle,
'multi_handle_with_canthandle': multi_handle_with_canthandle,
+ 'expand_tuple': expand_tuple,
}
diff --git a/mediagoblin/tests/testplugins/callables3/__init__.py b/mediagoblin/tests/testplugins/callables3/__init__.py
index 8d0c9c25..04efc8fc 100644
--- a/mediagoblin/tests/testplugins/callables3/__init__.py
+++ b/mediagoblin/tests/testplugins/callables3/__init__.py
@@ -29,10 +29,13 @@ def multi_handle_with_canthandle(call_log):
call_log.append("Hi, I'm the third")
return "the third returns"
+def expand_tuple(this_tuple):
+ return this_tuple + (3,)
hooks = {
'setup': setup_plugin,
'just_one': just_one,
'multi_handle': multi_handle,
'multi_handle_with_canthandle': multi_handle_with_canthandle,
+ 'expand_tuple': expand_tuple,
}
diff --git a/mediagoblin/tools/pluginapi.py b/mediagoblin/tools/pluginapi.py
index 283350a8..3f98aa8a 100644
--- a/mediagoblin/tools/pluginapi.py
+++ b/mediagoblin/tools/pluginapi.py
@@ -274,68 +274,94 @@ def get_hook_templates(hook_name):
return PluginManager().get_template_hooks(hook_name)
-###########################
-# Callable convenience code
-###########################
+#############################
+## Hooks: The Next Generation
+#############################
-class CantHandleIt(Exception):
- """
- A callable may call this method if they look at the relevant
- arguments passed and decide it's not possible for them to handle
- things.
- """
- pass
-class UnhandledCallable(Exception):
- """
- Raise this method if no callables were available to handle the
- specified hook. Only used by callable_runone.
+def hook_handle(hook_name, *args, **kwargs):
"""
- pass
+ Run through hooks attempting to find one that handle this hook.
+ All callables called with the same arguments until one handles
+ things and returns a non-None value.
-def callable_runone(hookname, *args, **kwargs):
- """
- Run the callable hook HOOKNAME... run until the first response,
- then return.
+ (If you are writing a handler and you don't have a particularly
+ useful value to return even though you've handled this, returning
+ True is a good solution.)
- This function will run stop at the first hook that handles the
- result. Hooks raising CantHandleIt will be skipped.
+ Note that there is a special keyword argument:
+ if "default_handler" is passed in as a keyword argument, this will
+ be used if no handler is found.
- Unless unhandled_okay is True, this will error out if no hooks
- have been registered to handle this function.
+ Some examples of using this:
+ - You need an interface implemented, but only one fit for it
+ - You need to *do* something, but only one thing needs to do it.
"""
- callables = PluginManager().get_hook_callables(hookname)
+ default_handler = kwargs.pop('default_handler', None)
+
+ callables = PluginManager().get_hook_callables(hook_name)
- unhandled_okay = kwargs.pop("unhandled_okay", False)
+ result = None
for callable in callables:
- try:
- return callable(*args, **kwargs)
- except CantHandleIt:
- continue
+ result = callable(*args, **kwargs)
- if unhandled_okay is False:
- raise UnhandledCallable(
- "No hooks registered capable of handling '%s'" % hookname)
+ if result is not None:
+ break
+ if result is None and default_handler is not None:
+ result = default_handler(*args, **kwargs)
+
+ return result
-def callable_runall(hookname, *args, **kwargs):
- """
- Run all callables for HOOKNAME.
- This method will run *all* hooks that handle this method (skipping
- those that raise CantHandleIt), and will return a list of all
- results.
+def hook_runall(hook_name, *args, **kwargs):
+ """
+ Run through all callable hooks and pass in arguments.
+
+ All non-None results are accrued in a list and returned from this.
+ (Other "false-like" values like False and friends are still
+ accrued, however.)
+
+ Some examples of using this:
+ - You have an interface call where actually multiple things can
+ and should implement it
+ - You need to get a list of things from various plugins that
+ handle them and do something with them
+ - You need to *do* something, and actually multiple plugins need
+ to do it separately
"""
- callables = PluginManager().get_hook_callables(hookname)
+ callables = PluginManager().get_hook_callables(hook_name)
results = []
for callable in callables:
- try:
- results.append(callable(*args, **kwargs))
- except CantHandleIt:
- continue
+ result = callable(*args, **kwargs)
+
+ if result is not None:
+ results.append(result)
return results
+
+
+def hook_transform(hook_name, arg):
+ """
+ Run through a bunch of hook callables and transform some input.
+
+ Note that unlike the other hook tools, this one only takes ONE
+ argument. This argument is passed to each function, which in turn
+ returns something that becomes the input of the next callable.
+
+ Some examples of using this:
+ - You have an object, say a form, but you want plugins to each be
+ able to modify it.
+ """
+ result = arg
+
+ callables = PluginManager().get_hook_callables(hook_name)
+
+ for callable in callables:
+ result = callable(result)
+
+ return result
diff --git a/mediagoblin/tools/processing.py b/mediagoblin/tools/processing.py
index cff4cb9d..2abe6452 100644
--- a/mediagoblin/tools/processing.py
+++ b/mediagoblin/tools/processing.py
@@ -21,8 +21,6 @@ import traceback
from urllib2 import urlopen, Request, HTTPError
from urllib import urlencode
-from mediagoblin.tools.common import TESTS_ENABLED
-
_log = logging.getLogger(__name__)
TESTS_CALLBACKS = {}
diff --git a/mediagoblin/tools/routing.py b/mediagoblin/tools/routing.py
index 791cd1e6..a15795fe 100644
--- a/mediagoblin/tools/routing.py
+++ b/mediagoblin/tools/routing.py
@@ -16,6 +16,7 @@
import logging
+import six
from werkzeug.routing import Map, Rule
from mediagoblin.tools.common import import_component
@@ -43,7 +44,7 @@ def endpoint_to_controller(rule):
_log.debug('endpoint: {0} view_func: {1}'.format(endpoint, view_func))
# import the endpoint, or if it's already a callable, call that
- if isinstance(view_func, basestring):
+ if isinstance(view_func, six.string_types):
view_func = import_component(view_func)
rule.gmg_controller = view_func
diff --git a/mediagoblin/tools/template.py b/mediagoblin/tools/template.py
index 78d65654..54aeac92 100644
--- a/mediagoblin/tools/template.py
+++ b/mediagoblin/tools/template.py
@@ -14,7 +14,6 @@
# 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 math import ceil
import jinja2
from jinja2.ext import Extension
@@ -27,7 +26,7 @@ from mediagoblin import mg_globals
from mediagoblin import messages
from mediagoblin import _version
from mediagoblin.tools import common
-from mediagoblin.tools.translate import get_gettext_translation
+from mediagoblin.tools.translate import set_thread_locale
from mediagoblin.tools.pluginapi import get_hook_templates
from mediagoblin.tools.timesince import timesince
from mediagoblin.meddleware.csrf import render_csrf_form_token
@@ -44,7 +43,7 @@ def get_jinja_env(template_loader, locale):
(In the future we may have another system for providing theming;
for now this is good enough.)
"""
- mg_globals.thread_scope.translations = get_gettext_translation(locale)
+ set_thread_locale(locale)
# If we have a jinja environment set up with this locale, just
# return that one.
diff --git a/mediagoblin/tools/translate.py b/mediagoblin/tools/translate.py
index 4acafac7..b20e57d1 100644
--- a/mediagoblin/tools/translate.py
+++ b/mediagoblin/tools/translate.py
@@ -42,6 +42,22 @@ def set_available_locales():
AVAILABLE_LOCALES = locales
+class ReallyLazyProxy(LazyProxy):
+ """
+ Like LazyProxy, except that it doesn't cache the value ;)
+ """
+ @property
+ def value(self):
+ return self._func(*self._args, **self._kwargs)
+
+ def __repr__(self):
+ return "<%s for %s(%r, %r)>" % (
+ self.__class__.__name__,
+ self._func,
+ self._args,
+ self._kwargs)
+
+
def locale_to_lower_upper(locale):
"""
Take a locale, regardless of style, and format it like "en_US"
@@ -112,6 +128,11 @@ def get_gettext_translation(locale):
return this_gettext
+def set_thread_locale(locale):
+ """Set the current translation for this thread"""
+ mg_globals.thread_scope.translations = get_gettext_translation(locale)
+
+
def pass_to_ugettext(*args, **kwargs):
"""
Pass a translation on to the appropriate ugettext method.
@@ -122,7 +143,6 @@ def pass_to_ugettext(*args, **kwargs):
return mg_globals.thread_scope.translations.ugettext(
*args, **kwargs)
-
def pass_to_ungettext(*args, **kwargs):
"""
Pass a translation on to the appropriate ungettext method.
@@ -133,6 +153,7 @@ def pass_to_ungettext(*args, **kwargs):
return mg_globals.thread_scope.translations.ungettext(
*args, **kwargs)
+
def lazy_pass_to_ugettext(*args, **kwargs):
"""
Lazily pass to ugettext.
@@ -144,7 +165,7 @@ def lazy_pass_to_ugettext(*args, **kwargs):
you would want to use the lazy version for _.
"""
- return LazyProxy(pass_to_ugettext, *args, **kwargs)
+ return ReallyLazyProxy(pass_to_ugettext, *args, **kwargs)
def pass_to_ngettext(*args, **kwargs):
@@ -166,7 +187,7 @@ def lazy_pass_to_ngettext(*args, **kwargs):
level but you need it to not translate until the time that it's
used as a string.
"""
- return LazyProxy(pass_to_ngettext, *args, **kwargs)
+ return ReallyLazyProxy(pass_to_ngettext, *args, **kwargs)
def lazy_pass_to_ungettext(*args, **kwargs):
"""
@@ -176,7 +197,7 @@ def lazy_pass_to_ungettext(*args, **kwargs):
level but you need it to not translate until the time that it's
used as a string.
"""
- return LazyProxy(pass_to_ungettext, *args, **kwargs)
+ return ReallyLazyProxy(pass_to_ungettext, *args, **kwargs)
def fake_ugettext_passthrough(string):
diff --git a/mediagoblin/user_pages/forms.py b/mediagoblin/user_pages/forms.py
index e9746a6c..9a193680 100644
--- a/mediagoblin/user_pages/forms.py
+++ b/mediagoblin/user_pages/forms.py
@@ -16,7 +16,7 @@
import wtforms
from wtforms.ext.sqlalchemy.fields import QuerySelectField
-from mediagoblin.tools.translate import fake_ugettext_passthrough as _
+from mediagoblin.tools.translate import lazy_pass_to_ugettext as _
class MediaCommentForm(wtforms.Form):
comment_content = wtforms.TextAreaField(
diff --git a/mediagoblin/user_pages/lib.py b/mediagoblin/user_pages/lib.py
index 8a064a7c..2f47e4b1 100644
--- a/mediagoblin/user_pages/lib.py
+++ b/mediagoblin/user_pages/lib.py
@@ -18,6 +18,8 @@ from mediagoblin.tools.mail import send_email
from mediagoblin.tools.template import render_template
from mediagoblin.tools.translate import pass_to_ugettext as _
from mediagoblin import mg_globals
+from mediagoblin.db.base import Session
+from mediagoblin.db.models import CollectionItem
def send_comment_email(user, comment, media, request):
@@ -55,3 +57,21 @@ def send_comment_email(user, comment, media, request):
instance_title=mg_globals.app_config['html_title']) \
+ _('commented on your post'),
rendered_email)
+
+
+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
+ if note:
+ collection_item.note = note
+ Session.add(collection_item)
+
+ collection.items = collection.items + 1
+ Session.add(collection)
+
+ media.collected = media.collected + 1
+ Session.add(media)
+
+ if commit:
+ Session.commit()
diff --git a/mediagoblin/user_pages/views.py b/mediagoblin/user_pages/views.py
index 61c23f16..e3b46c0f 100644
--- a/mediagoblin/user_pages/views.py
+++ b/mediagoblin/user_pages/views.py
@@ -24,7 +24,8 @@ from mediagoblin.tools.response import render_to_response, render_404, redirect
from mediagoblin.tools.translate import pass_to_ugettext as _
from mediagoblin.tools.pagination import Pagination
from mediagoblin.user_pages import forms as user_forms
-from mediagoblin.user_pages.lib import send_comment_email
+from mediagoblin.user_pages.lib import (send_comment_email,
+ add_media_to_collection)
from mediagoblin.decorators import (uses_pagination, get_user_media_entry,
get_media_entry_by_id,
@@ -248,17 +249,7 @@ def media_collect(request, media):
_('"%s" already in collection "%s"')
% (media.title, collection.title))
else: # Add item to collection
- collection_item = request.db.CollectionItem()
- collection_item.collection = collection.id
- collection_item.media_entry = media.id
- collection_item.note = form.note.data
- collection_item.save()
-
- collection.items = collection.items + 1
- collection.save()
-
- media.collected = media.collected + 1
- media.save()
+ add_media_to_collection(collection, media, form.note.data)
messages.add_message(request, messages.SUCCESS,
_('"%s" added to collection "%s"')
diff --git a/setup.py b/setup.py
index ce1e4102..312de2f8 100644
--- a/setup.py
+++ b/setup.py
@@ -62,6 +62,7 @@ setup(
'mock',
'itsdangerous',
'pytz',
+ 'six',
## This is optional!
# 'translitcodec',
## For now we're expecting that users will install this from