diff options
39 files changed, 909 insertions, 368 deletions
@@ -11,4 +11,5 @@ mediagoblin.egg-info docs/_build/ user_dev/ server-log.txt -*~
\ No newline at end of file +*~ +*.swp diff --git a/docs/git.rst b/docs/git.rst index 8eb038b2..c3f7ccce 100644 --- a/docs/git.rst +++ b/docs/git.rst @@ -136,7 +136,7 @@ Slartibartfast does the following: 4. Slartibartfast pushes his changes to his clone (the remote is named ``origin``):: - git push origin issue_42 + git push origin issue_42 --set-upstream 5. Slartibartfast adds a comment to issue 42 with the url for his repository and the name of the branch he put the code in. He also diff --git a/docs/hackinghowto.rst b/docs/hackinghowto.rst index d8bb9330..b03efa91 100644 --- a/docs/hackinghowto.rst +++ b/docs/hackinghowto.rst @@ -123,40 +123,42 @@ To do this, do:: Running the server ================== -Run:: +If you want to get things running quickly and without hassle, just +run:: + + ./lazyserver.sh + +This will start up a python server where you can begin playing with +mediagoblin. It will also run celery in "always eager" mode so you +don't have to start a separate process for it. + +This is fine in development, but if you want to actually run celery +separately for testing (or deployment purposes), you'll want to run +the server independently:: - ./bin/paster serve mediagoblin.ini --reload + ./bin/paster serve server.ini --reload Running celeryd =============== -You need to do this if you want your media to process and actually -show up. It's probably a good idea in development to have the web -server (above) running in one terminal and celeryd in another window. +If you aren't using ./lazyserver.sh or otherwise aren't running celery +in always eager mode, you'll need to do this if you want your media to +process and actually show up. It's probably a good idea in +development to have the web server (above) running in one terminal and +celeryd in another window. Run:: CELERY_CONFIG_MODULE=mediagoblin.celery_setup.from_celery ./bin/celeryd -Too much work? Don't want to run an http server and celeryd at the -same time? For development purposes there's a shortcut:: - - CELERY_ALWAYS_EAGER=true ./bin/paster serve mediagoblin.ini --reload - -This way the web server will block on processing items until they are -done, but you don't need to run celery separately (which is probably -good enough for development purposes, but something you almost -certainly shouldn't do in production). - - Running the test suite ====================== Run:: - CELERY_CONFIG_MODULE=mediagoblin.celery_setup.from_tests ./bin/nosetests + ./runtests.sh Running a shell diff --git a/lazyserver.sh b/lazyserver.sh new file mode 100755 index 00000000..bd93b109 --- /dev/null +++ b/lazyserver.sh @@ -0,0 +1,30 @@ +#!/bin/sh + +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011 Free Software Foundation, Inc +# +# 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/>. + +if [ -f ./bin/paster ]; then + echo "Using ./bin/paster"; + export PASTER="./bin/paster"; +elif which paster > /dev/null; then + echo "Using paster from \$PATH"; + export PASTER="paster"; +else + echo "No paster found, exiting! X_X"; + exit 1 +fi + +CELERY_ALWAYS_EAGER=true $PASTER serve server.ini --reload diff --git a/mediagoblin.ini b/mediagoblin.ini index b85f22ea..596107dc 100644 --- a/mediagoblin.ini +++ b/mediagoblin.ini @@ -1,42 +1,15 @@ -[DEFAULT] -debug = true - -[composite:main] -use = egg:Paste#urlmap -/ = mediagoblin -/mgoblin_media/ = publicstore_serve -/mgoblin_static/ = mediagoblin_static - -[app:mediagoblin] -use = egg:mediagoblin#app -filter-with = beaker +[mediagoblin] queuestore_base_dir = %(here)s/user_dev/media/queue publicstore_base_dir = %(here)s/user_dev/media/public publicstore_base_url = /mgoblin_media/ direct_remote_path = /mgoblin_static/ email_sender_address = "notice@mediagoblin.example.org" + # set to false to enable sending notices email_debug_mode = true + ## Uncomment this to put some user-overriding templates here #local_templates = %(here)s/user_dev/templates/ -[app:publicstore_serve] -use = egg:Paste#static -document_root = %(here)s/user_dev/media/public - -[app:mediagoblin_static] -use = egg:Paste#static -document_root = %(here)s/mediagoblin/static/ - -[filter:beaker] -use = egg:Beaker#beaker_session -cache_dir = %(here)s/user_dev/beaker -beaker.session.key = mediagoblin -# beaker.session.secret = somesupersecret -beaker.session.data_dir = %(here)s/user_dev/beaker/sessions/data -beaker.session.lock_dir = %(here)s/user_dev/beaker/sessions/lock - -[server:main] -use = egg:Paste#http -host = 127.0.0.1 -port = 6543 +[celery] +# Put celery stuff here diff --git a/mediagoblin/app.py b/mediagoblin/app.py index a1c6b512..b27b5761 100644 --- a/mediagoblin/app.py +++ b/mediagoblin/app.py @@ -18,14 +18,15 @@ import os import urllib import routes -from paste.deploy.converters import asbool from webob import Request, exc from mediagoblin import routing, util, storage, staticdirect +from mediagoblin.config import ( + read_mediagoblin_config, generate_validation_report) from mediagoblin.db.open import setup_connection_and_db_from_config from mediagoblin.mg_globals import setup_globals from mediagoblin.celery_setup import setup_celery_from_config -from mediagoblin.workbench import WorkbenchManager, DEFAULT_WORKBENCH_DIR +from mediagoblin.workbench import WorkbenchManager class Error(Exception): pass @@ -34,42 +35,102 @@ class ImproperlyConfigured(Error): pass class MediaGoblinApp(object): """ - Really basic wsgi app using routes and WebOb. + WSGI application of MediaGoblin + + ... this is the heart of the program! """ - def __init__(self, connection, db, - public_store, queue_store, - staticdirector, - email_sender_address, email_debug_mode, - user_template_path=None, - workbench_path=DEFAULT_WORKBENCH_DIR): + def __init__(self, config_path, setup_celery=True): + """ + Initialize the application based on a configuration file. + + Arguments: + - config_path: path to the configuration file we're opening. + - setup_celery: whether or not to setup celery during init. + (Note: setting 'celery_setup_elsewhere' also disables + setting up celery.) + """ + ############## + # Setup config + ############## + + # Open and setup the config + global_config, validation_result = read_mediagoblin_config(config_path) + app_config = global_config['mediagoblin'] + # report errors if necessary + validation_report = generate_validation_report( + global_config, validation_result) + if validation_report: + raise ImproperlyConfigured(validation_report) + + ########################################## + # Setup other connections / useful objects + ########################################## + + # Set up the database + self.connection, self.db = setup_connection_and_db_from_config( + app_config) + # Get the template environment - self.template_loader = util.get_jinja_loader(user_template_path) + self.template_loader = util.get_jinja_loader( + app_config.get('user_template_path')) # Set up storage systems - self.public_store = public_store - self.queue_store = queue_store - - # Set up database - self.connection = connection - self.db = db + self.public_store = storage.storage_system_from_config( + app_config, 'publicstore') + self.queue_store = storage.storage_system_from_config( + app_config, 'queuestore') # set up routing self.routing = routing.get_mapper() # set up staticdirector tool - self.staticdirector = staticdirector - + if app_config.has_key('direct_remote_path'): + self.staticdirector = staticdirect.RemoteStaticDirect( + app_config['direct_remote_path'].strip()) + elif app_config.has_key('direct_remote_paths'): + direct_remote_path_lines = app_config[ + 'direct_remote_paths'].strip().splitlines() + self.staticdirector = staticdirect.MultiRemoteStaticDirect( + dict([line.strip().split(' ', 1) + for line in direct_remote_path_lines])) + else: + raise ImproperlyConfigured( + "One of direct_remote_path or " + "direct_remote_paths must be provided") + + # Setup celery, if appropriate + if setup_celery and not app_config.get('celery_setup_elsewhere'): + if os.environ.get('CELERY_ALWAYS_EAGER'): + setup_celery_from_config( + app_config, global_config, + force_celery_always_eager=True) + else: + setup_celery_from_config(app_config, global_config) + + ####################################################### + # Insert appropriate things into mediagoblin.mg_globals + # # certain properties need to be accessed globally eg from # validators, etc, which might not access to the request # object. + ####################################################### + setup_globals( - email_sender_address=email_sender_address, - email_debug_mode=email_debug_mode, - db_connection=connection, + app_config=app_config, + global_config=global_config, + + # TODO: No need to set these two up as globals, we could + # just read them out of mg_globals.app_config + email_sender_address=app_config['email_sender_address'], + email_debug_mode=app_config['email_debug_mode'], + + # Actual, useful to everyone objects + app=self, + db_connection=self.connection, database=self.db, public_store=self.public_store, queue_store=self.queue_store, - workbench_manager=WorkbenchManager(workbench_path)) + workbench_manager=WorkbenchManager(app_config['workbench_path'])) def __call__(self, environ, start_response): request = Request(environ) @@ -119,45 +180,6 @@ class MediaGoblinApp(object): def paste_app_factory(global_config, **app_config): - # Get the database connection - connection, db = setup_connection_and_db_from_config(app_config) - - # Set up the storage systems. - public_store = storage.storage_system_from_paste_config( - app_config, 'publicstore') - queue_store = storage.storage_system_from_paste_config( - app_config, 'queuestore') - - # Set up the staticdirect system - if app_config.has_key('direct_remote_path'): - staticdirector = staticdirect.RemoteStaticDirect( - app_config['direct_remote_path'].strip()) - elif app_config.has_key('direct_remote_paths'): - direct_remote_path_lines = app_config[ - 'direct_remote_paths'].strip().splitlines() - staticdirector = staticdirect.MultiRemoteStaticDirect( - dict([line.strip().split(' ', 1) - for line in direct_remote_path_lines])) - else: - raise ImproperlyConfigured( - "One of direct_remote_path or direct_remote_paths must be provided") - - if not asbool(app_config.get('celery_setup_elsewhere')): - if asbool(os.environ.get('CELERY_ALWAYS_EAGER')): - setup_celery_from_config( - app_config, global_config, - force_celery_always_eager=True) - else: - setup_celery_from_config(app_config, global_config) - - mgoblin_app = MediaGoblinApp( - connection, db, - public_store=public_store, queue_store=queue_store, - staticdirector=staticdirector, - email_sender_address=app_config.get( - 'email_sender_address', 'notice@mediagoblin.example.org'), - email_debug_mode=asbool(app_config.get('email_debug_mode')), - user_template_path=app_config.get('local_templates'), - workbench_path=app_config.get('workbench_path', DEFAULT_WORKBENCH_DIR)) + mgoblin_app = MediaGoblinApp(app_config['config']) return mgoblin_app diff --git a/mediagoblin/celery_setup/__init__.py b/mediagoblin/celery_setup/__init__.py index d4f25b07..b6e35e99 100644 --- a/mediagoblin/celery_setup/__init__.py +++ b/mediagoblin/celery_setup/__init__.py @@ -17,86 +17,35 @@ import os import sys -from paste.deploy.converters import asbool, asint, aslist - - -KNOWN_CONFIG_BOOLS = [ - 'CELERY_RESULT_PERSISTENT', - 'CELERY_CREATE_MISSING_QUEUES', - 'BROKER_USE_SSL', 'BROKER_CONNECTION_RETRY', - 'CELERY_ALWAYS_EAGER', 'CELERY_EAGER_PROPAGATES_EXCEPTIONS', - 'CELERY_IGNORE_RESULT', 'CELERY_TRACK_STARTED', - 'CELERY_DISABLE_RATE_LIMITS', 'CELERY_ACKS_LATE', - 'CELERY_STORE_ERRORS_EVEN_IF_IGNORED', - 'CELERY_SEND_TASK_ERROR_EMAILS', - 'CELERY_SEND_EVENTS', 'CELERY_SEND_TASK_SENT_EVENT', - 'CELERYD_LOG_COLOR', 'CELERY_REDIRECT_STDOUTS', - ] - -KNOWN_CONFIG_INTS = [ - 'CELERYD_CONCURRENCY', - 'CELERYD_PREFETCH_MULTIPLIER', - 'CELERY_AMQP_TASK_RESULT_EXPIRES', - 'CELERY_AMQP_TASK_RESULT_CONNECTION_MAX', - 'REDIS_PORT', 'REDIS_DB', - 'BROKER_PORT', 'BROKER_CONNECTION_TIMEOUT', - 'CELERY_BROKER_CONNECTION_MAX_RETRIES', - 'CELERY_TASK_RESULT_EXPIRES', 'CELERY_MAX_CACHED_RESULTS', - 'CELERY_DEFAULT_RATE_LIMIT', # ?? - 'CELERYD_MAX_TASKS_PER_CHILD', 'CELERYD_TASK_TIME_LIMIT', - 'CELERYD_TASK_SOFT_TIME_LIMIT', - 'MAIL_PORT', 'CELERYBEAT_MAX_LOOP_INTERVAL', - ] - -KNOWN_CONFIG_FLOATS = [ - 'CELERYD_ETA_SCHEDULER_PRECISION', - ] - -KNOWN_CONFIG_LISTS = [ - 'CELERY_ROUTES', 'CELERY_IMPORTS', - ] - - -## Needs special processing: -# ADMINS, ??? -# there are a lot more; we should list here or process specially. - - -def asfloat(obj): - try: - return float(obj) - except (TypeError, ValueError), e: - raise ValueError( - "Bad float value: %r" % obj) - MANDATORY_CELERY_IMPORTS = ['mediagoblin.process_media'] DEFAULT_SETTINGS_MODULE = 'mediagoblin.celery_setup.dummy_settings_module' + def setup_celery_from_config(app_config, global_config, settings_module=DEFAULT_SETTINGS_MODULE, force_celery_always_eager=False, set_environ=True): """ - Take a mediagoblin app config and the global config from a paste - factory and try to set up a celery settings module from this. + Take a mediagoblin app config and try to set up a celery settings + module from this. Args: - app_config: the application config section - - global_config: the entire paste config, all sections + - global_config: the entire ConfigObj loaded config, all sections - settings_module: the module to populate, as a string - - + - force_celery_always_eager: whether or not to force celery into + always eager mode; good for development and small installs - set_environ: if set, this will CELERY_CONFIG_MODULE to the settings_module """ - if asbool(app_config.get('use_celery_environment_var')) == True: + if app_config.get('celery_setup_elsewhere') == True: # Don't setup celery based on our config file. return - celery_conf_section = app_config.get('celery_section', 'celery') - if global_config.has_key(celery_conf_section): - celery_conf = global_config[celery_conf_section] + if global_config.has_key('celery'): + celery_conf = global_config['celery'] else: celery_conf = {} @@ -114,9 +63,9 @@ def setup_celery_from_config(app_config, global_config, if celery_settings['BROKER_BACKEND'] == 'mongodb': celery_settings['BROKER_HOST'] = app_config['db_host'] if app_config.has_key('db_port'): - celery_mongo_settings['port'] = asint(app_config['db_port']) + celery_mongo_settings['port'] = app_config['db_port'] if celery_settings['BROKER_BACKEND'] == 'mongodb': - celery_settings['BROKER_PORT'] = asint(app_config['db_port']) + celery_settings['BROKER_PORT'] = app_config['db_port'] celery_mongo_settings['database'] = app_config.get('db_name', 'mediagoblin') celery_settings['CELERY_MONGODB_BACKEND_SETTINGS'] = celery_mongo_settings @@ -124,14 +73,6 @@ def setup_celery_from_config(app_config, global_config, # Add anything else for key, value in celery_conf.iteritems(): key = key.upper() - if key in KNOWN_CONFIG_BOOLS: - value = asbool(value) - elif key in KNOWN_CONFIG_INTS: - value = asint(value) - elif key in KNOWN_CONFIG_FLOATS: - value = asfloat(value) - elif key in KNOWN_CONFIG_LISTS: - value = aslist(value) celery_settings[key] = value # add mandatory celery imports diff --git a/mediagoblin/celery_setup/from_celery.py b/mediagoblin/celery_setup/from_celery.py index c8ccebc8..046aaa50 100644 --- a/mediagoblin/celery_setup/from_celery.py +++ b/mediagoblin/celery_setup/from_celery.py @@ -16,81 +16,43 @@ import os -from paste.deploy.loadwsgi import NicerConfigParser -from paste.deploy.converters import asbool - -from mediagoblin import storage -from mediagoblin.db.open import setup_connection_and_db_from_config +from mediagoblin import app, mg_globals from mediagoblin.celery_setup import setup_celery_from_config -from mediagoblin.mg_globals import setup_globals -from mediagoblin.workbench import WorkbenchManager, DEFAULT_WORKBENCH_DIR OUR_MODULENAME = __name__ -def setup_self(): +def setup_self(check_environ_for_conf=True, module_name=OUR_MODULENAME): """ Transform this module into a celery config module by reading the mediagoblin config file. Set the environment variable - MEDIAGOBLIN_CONFIG to specify where this config file is at and - what section it uses. - - By default it defaults to 'mediagoblin.ini:app:mediagoblin'. + MEDIAGOBLIN_CONFIG to specify where this config file is. - The first colon ":" is a delimiter between the filename and the - config section, so in this case the filename is 'mediagoblin.ini' - and the section where mediagoblin is defined is 'app:mediagoblin'. + By default it defaults to 'mediagoblin.ini'. - Args: - - 'setup_globals_func': this is for testing purposes only. Don't - set this! + Note that if celery_setup_elsewhere is set in your config file, + this simply won't work. """ - mgoblin_conf_file, mgoblin_section = os.environ.get( - 'MEDIAGOBLIN_CONFIG', 'mediagoblin.ini:app:mediagoblin').split(':', 1) + if check_environ_for_conf: + mgoblin_conf_file = os.path.abspath( + os.environ.get('MEDIAGOBLIN_CONFIG', 'mediagoblin.ini')) + else: + mgoblin_conf_file = 'mediagoblin.ini' + if not os.path.exists(mgoblin_conf_file): raise IOError( "MEDIAGOBLIN_CONFIG not set or file does not exist") - parser = NicerConfigParser(mgoblin_conf_file) - parser.read(mgoblin_conf_file) - parser._defaults.setdefault( - 'here', os.path.dirname(os.path.abspath(mgoblin_conf_file))) - parser._defaults.setdefault( - '__file__', os.path.abspath(mgoblin_conf_file)) - - mgoblin_section = dict(parser.items(mgoblin_section)) - mgoblin_conf = dict( - [(section_name, dict(parser.items(section_name))) - for section_name in parser.sections()]) + # By setting the environment variable here we should ensure that + # this is the module that gets set up. + os.environ['CELERY_CONFIG_MODULE'] = module_name + app.MediaGoblinApp(mgoblin_conf_file, setup_celery=False) setup_celery_from_config( - mgoblin_section, mgoblin_conf, - settings_module=OUR_MODULENAME, + mg_globals.app_config, mg_globals.global_config, + settings_module=module_name, set_environ=False) - connection, db = setup_connection_and_db_from_config(mgoblin_section) - - # Set up the storage systems. - public_store = storage.storage_system_from_paste_config( - mgoblin_section, 'publicstore') - queue_store = storage.storage_system_from_paste_config( - mgoblin_section, 'queuestore') - - workbench_manager = WorkbenchManager( - mgoblin_section.get( - 'workbench_path', DEFAULT_WORKBENCH_DIR)) - - setup_globals( - db_connection=connection, - database=db, - public_store=public_store, - email_debug_mode=asbool(mgoblin_section.get('email_debug_mode')), - email_sender_address=mgoblin_section.get( - 'email_sender_address', - 'notice@mediagoblin.example.org'), - queue_store=queue_store, - workbench_manager=workbench_manager) - if os.environ['CELERY_CONFIG_MODULE'] == OUR_MODULENAME: setup_self() diff --git a/mediagoblin/celery_setup/from_tests.py b/mediagoblin/celery_setup/from_tests.py index 70814075..43032f41 100644 --- a/mediagoblin/celery_setup/from_tests.py +++ b/mediagoblin/celery_setup/from_tests.py @@ -16,27 +16,11 @@ import os -from mediagoblin.tests.tools import TEST_APP_CONFIG -from mediagoblin import util -from mediagoblin.celery_setup import setup_celery_from_config +from mediagoblin.celery_setup.from_celery import setup_self OUR_MODULENAME = __name__ -def setup_self(): - """ - Set up celery for testing's sake, which just needs to set up - celery and celery only. - """ - mgoblin_conf = util.read_config_file(TEST_APP_CONFIG) - mgoblin_section = mgoblin_conf['app:mediagoblin'] - - setup_celery_from_config( - mgoblin_section, mgoblin_conf, - settings_module=OUR_MODULENAME, - set_environ=False) - - if os.environ.get('CELERY_CONFIG_MODULE') == OUR_MODULENAME: - setup_self() + setup_self(check_environ_for_conf=False, module_name=OUR_MODULENAME) diff --git a/mediagoblin/config.py b/mediagoblin/config.py new file mode 100644 index 00000000..2f93d32c --- /dev/null +++ b/mediagoblin/config.py @@ -0,0 +1,122 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011 Free Software Foundation, Inc +# +# 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/>. + +import os +import pkg_resources + +from configobj import ConfigObj, flatten_errors +from validate import Validator + + +CONFIG_SPEC_PATH = pkg_resources.resource_filename( + 'mediagoblin', 'config_spec.ini') + + +def _setup_defaults(config, config_path): + """ + Setup DEFAULTS in a config object from an (absolute) config_path. + """ + config.setdefault('DEFAULT', {}) + config['DEFAULT']['here'] = os.path.dirname(config_path) + config['DEFAULT']['__file__'] = config_path + + +def read_mediagoblin_config(config_path, config_spec=CONFIG_SPEC_PATH): + """ + Read a config object from config_path. + + Does automatic value transformation based on the config_spec. + Also provides %(__file__)s and %(here)s values of this file and + its directory respectively similar to paste deploy. + + This function doesn't itself raise any exceptions if validation + fails, you'll have to do something + + Args: + - config_path: path to the config file + - config_spec: config file that provides defaults and value types + for validation / conversion. Defaults to mediagoblin/config_spec.ini + + Returns: + A tuple like: (config, validation_result) + ... where 'conf' is the parsed config object and 'validation_result' + is the information from the validation process. + """ + config_path = os.path.abspath(config_path) + + config_spec = ConfigObj( + config_spec, + encoding='UTF8', list_values=False, _inspec=True) + + _setup_defaults(config_spec, config_path) + + config = ConfigObj( + config_path, + configspec=config_spec, + interpolation='ConfigParser') + + _setup_defaults(config, config_path) + + # For now the validator just works with the default functions, + # but in the future if we want to add additional validation/configuration + # functions we'd add them to validator.functions here. + # + # See also: + # http://www.voidspace.org.uk/python/validate.html#adding-functions + validator = Validator() + validation_result = config.validate(validator, preserve_errors=True) + + return config, validation_result + + +REPORT_HEADER = u"""\ +There were validation problems loading this config file: +-------------------------------------------------------- +""" + + +def generate_validation_report(config, validation_result): + """ + Generate a report if necessary of problems while validating. + + Returns: + Either a string describing for a user the problems validating + this config or None if there are no problems. + """ + report = [] + + # Organize the report + for entry in flatten_errors(config, validation_result): + # each entry is a tuple + section_list, key, error = entry + + if key is not None: + section_list.append(key) + else: + section_list.append(u'[missing section]') + + section_string = u':'.join(section_list) + + if error == False: + # We don't care about missing values for now. + continue + + report.append(u"%s = %s" % (section_string, error)) + + if report: + return REPORT_HEADER + u"\n".join(report) + else: + return None diff --git a/mediagoblin/config_spec.ini b/mediagoblin/config_spec.ini new file mode 100644 index 00000000..aadf5c21 --- /dev/null +++ b/mediagoblin/config_spec.ini @@ -0,0 +1,76 @@ +[mediagoblin] +# database stuff +db_host = string() +db_name = string() +db_port = integer() + +# +queuestore_base_dir = string(default="%(here)s/user_dev/media/queue") +publicstore_base_dir = string(default="%(here)s/user_dev/media/public") + +# Where temporary files used in processing and etc are kept +workbench_path = string(default="%(here)s/user_dev/media/workbench") + +# +publicstore_base_url = string(default="/mgoblin_media/") + +# Where mediagoblin-builtin static assets are kept +direct_remote_path = string(default="/mgoblin_static/") + +# set to false to enable sending notices +email_debug_mode = boolean(default=True) +email_sender_address = string(default="notice@mediagoblin.example.org") + +# By default not set, but you might want something like: +# "%(here)s/user_dev/templates/" +local_templates = string() + +# Whether or not celery is set up via an environment variable or +# something else (and thus mediagoblin should not attempt to set it up +# itself) +celery_setup_elsewhere = boolean(default=False) + +[celery] +# known booleans +celery_result_persistent = boolean() +celery_create_missing_queues = boolean() +broker_use_ssl = boolean() +broker_connection_retry = boolean() +celery_always_eager = boolean() +celery_eager_propagates_exceptions = boolean() +celery_ignore_result = boolean() +celery_track_started = boolean() +celery_disable_rate_limits = boolean() +celery_acks_late = boolean() +celery_store_errors_even_if_ignored = boolean() +celery_send_task_error_emails = boolean() +celery_send_events = boolean() +celery_send_task_sent_event = boolean() +celeryd_log_color = boolean() +celery_redirect_stdouts = boolean() + +# known ints +celeryd_concurrency = integer() +celeryd_prefetch_multiplier = integer() +celery_amqp_task_result_expires = integer() +celery_amqp_task_result_connection_max = integer() +redis_port = integer() +redis_db = integer() +broker_port = integer() +broker_connection_timeout = integer() +celery_broker_connection_max_retries = integer() +celery_task_result_expires = integer() +celery_max_cached_results = integer() +celery_default_rate_limit = integer() +celeryd_max_tasks_per_child = integer() +celeryd_task_time_limit = integer() +celeryd_task_soft_time_limit = integer() +mail_port = integer() +celerybeat_max_loop_interval = integer() + +# known floats +celeryd_eta_scheduler_precision = float() + +# known lists +celery_routes = string_list() +celery_imports = string_list()
\ No newline at end of file diff --git a/mediagoblin/gmg_commands/migrate.py b/mediagoblin/gmg_commands/migrate.py index 3ce25701..9e01d51c 100644 --- a/mediagoblin/gmg_commands/migrate.py +++ b/mediagoblin/gmg_commands/migrate.py @@ -23,9 +23,6 @@ def migrate_parser_setup(subparser): subparser.add_argument( '-cf', '--conf_file', default='mediagoblin.ini', help="Config file used to set up environment") - subparser.add_argument( - '-cs', '--app_section', default='app:mediagoblin', - help="Section of the config file where the app config is stored.") def migrate(args): diff --git a/mediagoblin/gmg_commands/shell.py b/mediagoblin/gmg_commands/shell.py index 16caf398..dc1621d1 100644 --- a/mediagoblin/gmg_commands/shell.py +++ b/mediagoblin/gmg_commands/shell.py @@ -25,9 +25,6 @@ def shell_parser_setup(subparser): subparser.add_argument( '-cf', '--conf_file', default='mediagoblin.ini', help="Config file used to set up environment") - subparser.add_argument( - '-cs', '--app_section', default='app:mediagoblin', - help="Section of the config file where the app config is stored.") SHELL_BANNER = """\ diff --git a/mediagoblin/gmg_commands/util.py b/mediagoblin/gmg_commands/util.py index 41a21a1e..8dcac913 100644 --- a/mediagoblin/gmg_commands/util.py +++ b/mediagoblin/gmg_commands/util.py @@ -15,10 +15,6 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. -import os - -from paste.deploy.loadwsgi import NicerConfigParser - from mediagoblin import app @@ -26,20 +22,6 @@ def setup_app(args): """ Setup the application after reading the mediagoblin config files """ - # Duplicated from from_celery.py, remove when we have the generic util - parser = NicerConfigParser(args.conf_file) - parser.read(args.conf_file) - parser._defaults.setdefault( - 'here', os.path.dirname(os.path.abspath(args.conf_file))) - parser._defaults.setdefault( - '__file__', os.path.abspath(args.conf_file)) - - mgoblin_section = dict(parser.items(args.app_section)) - mgoblin_conf = dict( - [(section_name, dict(parser.items(section_name))) - for section_name in parser.sections()]) - - mgoblin_app = app.paste_app_factory( - mgoblin_conf, **mgoblin_section) + mgoblin_app = app.MediaGoblinApp(args.conf_file) return mgoblin_app diff --git a/mediagoblin/mg_globals.py b/mediagoblin/mg_globals.py index 2fca3c0a..739f44ee 100644 --- a/mediagoblin/mg_globals.py +++ b/mediagoblin/mg_globals.py @@ -5,6 +5,7 @@ In some places, we need to access the database, public_store, queue_store import gettext import pkg_resources + ############################# # General mediagoblin globals ############################# @@ -19,15 +20,39 @@ database = None public_store = None queue_store = None +# Dump mail to stdout instead of sending it: +email_debug_mode = False + +# Address for sending out mails +email_sender_address = None + +# A WorkBenchManager +workbench_manager = None + # gettext translations = gettext.find( 'mediagoblin', pkg_resources.resource_filename( 'mediagoblin', 'translations'), ['en']) +# app and global config objects +app_config = None +global_config = None + +# The actual app object +app = None + def setup_globals(**kwargs): + """ + Sets up a bunch of globals in this module. + + Takes the globals to setup as keyword arguments. If globals are + specified that aren't set as variables above, then throw an error. + """ from mediagoblin import mg_globals for key, value in kwargs.iteritems(): + if not hasattr(mg_globals, key): + raise AssertionError("Global %s not known" % key) setattr(mg_globals, key, value) diff --git a/mediagoblin/storage.py b/mediagoblin/storage.py index ba6ac017..5d6faa4c 100644 --- a/mediagoblin/storage.py +++ b/mediagoblin/storage.py @@ -247,7 +247,7 @@ def clean_listy_filepath(listy_filepath): return cleaned_filepath -def storage_system_from_paste_config(paste_config, storage_prefix): +def storage_system_from_config(paste_config, storage_prefix): """ Utility for setting up a storage system from the paste app config. @@ -266,7 +266,7 @@ def storage_system_from_paste_config(paste_config, storage_prefix): An instantiated storage system. Example: - storage_system_from_paste_config( + storage_system_from_config( {'publicstore_base_url': '/media/', 'publicstore_base_dir': '/var/whatever/media/'}, 'publicstore') diff --git a/mediagoblin/templates/mediagoblin/base.html b/mediagoblin/templates/mediagoblin/base.html index 704e5aa7..c7313173 100644 --- a/mediagoblin/templates/mediagoblin/base.html +++ b/mediagoblin/templates/mediagoblin/base.html @@ -35,6 +35,8 @@ <div class="mediagoblin_header_right"> {% if request.user %} {{ request.user['username'] }}'s account + <a href="{{ request.urlgen('mediagoblin.user_pages.user_gallery', + user= request.user['username']) }}">gallery</a> (<a href="{{ request.urlgen('mediagoblin.auth.logout') }}">logout</a>) {% else %} <a href="{{ request.urlgen('mediagoblin.auth.login') }}"> diff --git a/mediagoblin/templates/mediagoblin/user_pages/gallery.html b/mediagoblin/templates/mediagoblin/user_pages/gallery.html new file mode 100644 index 00000000..1d8fbdaa --- /dev/null +++ b/mediagoblin/templates/mediagoblin/user_pages/gallery.html @@ -0,0 +1,42 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011 Free Software Foundation, Inc +# +# 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/>. +#} +{% extends "mediagoblin/base.html" %} + +{% block mediagoblin_head %} + <link rel="alternate" type="application/atom+xml" + href="{{ request.urlgen( + 'mediagoblin.user_pages.atom_feed', + user=user.username) }}"> +{% endblock mediagoblin_head %} + +{% block mediagoblin_content -%} + {% if user %} + <h1>gallery for <a href="{{ request.urlgen( + 'mediagoblin.user_pages.user_home', + user=user.username) }}">{{ user.username }}</a></h1> + + {% include "mediagoblin/utils/object_gallery.html" %} + + <a href={{ request.urlgen( + 'mediagoblin.user_pages.atom_feed', + user=user.username) }}> atom feed</a> + {% else %} + {# This *should* not occur as the view makes sure we pass in a user. #} + <p>Sorry, no such user found.<p/> + {% endif %} +{% endblock %} diff --git a/mediagoblin/tests/__init__.py b/mediagoblin/tests/__init__.py index e9e2a59a..1f1e23e9 100644 --- a/mediagoblin/tests/__init__.py +++ b/mediagoblin/tests/__init__.py @@ -21,6 +21,7 @@ def setup_package(): pass def teardown_package(): - print "Killing db ..." - mg_globals.db_connection.drop_database(mg_globals.database.name) - print "... done" + if mg_globals.db_connection: + print "Killing db ..." + mg_globals.db_connection.drop_database(mg_globals.database.name) + print "... done" diff --git a/mediagoblin/tests/fake_carrot_conf_bad.ini b/mediagoblin/tests/fake_carrot_conf_bad.ini new file mode 100644 index 00000000..0c79b354 --- /dev/null +++ b/mediagoblin/tests/fake_carrot_conf_bad.ini @@ -0,0 +1,14 @@ +[carrotapp] +# Whether or not our carrots are going to be turned into cake. +## These should throw errors +carrotcake = slobber +num_carrots = GROSS + +# A message encouraging our users to eat their carrots. +encouragement_phrase = 586956856856 # shouldn't throw error + +# Something extra! +blah_blah = "blah!" # shouldn't throw error either + +[celery] +eat_celery_with_carrots = pants # yeah that's def an error right there. diff --git a/mediagoblin/tests/fake_carrot_conf_empty.ini b/mediagoblin/tests/fake_carrot_conf_empty.ini new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/mediagoblin/tests/fake_carrot_conf_empty.ini diff --git a/mediagoblin/tests/fake_carrot_conf_good.ini b/mediagoblin/tests/fake_carrot_conf_good.ini new file mode 100644 index 00000000..fed14d07 --- /dev/null +++ b/mediagoblin/tests/fake_carrot_conf_good.ini @@ -0,0 +1,13 @@ +[carrotapp] +# Whether or not our carrots are going to be turned into cake. +carrotcake = true +num_carrots = 88 + +# A message encouraging our users to eat their carrots. +encouragement_phrase = "I'd love it if you eat your carrots!" + +# Something extra! +blah_blah = "blah!" + +[celery] +eat_celery_with_carrots = False diff --git a/mediagoblin/tests/fake_celery_conf.ini b/mediagoblin/tests/fake_celery_conf.ini new file mode 100644 index 00000000..3e52ac3a --- /dev/null +++ b/mediagoblin/tests/fake_celery_conf.ini @@ -0,0 +1,9 @@ +['mediagoblin'] +# I got nothin' in this file! + +['celery'] +some_variable = floop +mail_port = 2000 +celeryd_eta_scheduler_precision = 1.3 +celery_result_persistent = true +celery_imports = foo.bar.baz, this.is.an.import diff --git a/mediagoblin/tests/fake_celery_conf_mgdb.ini b/mediagoblin/tests/fake_celery_conf_mgdb.ini new file mode 100644 index 00000000..52671c14 --- /dev/null +++ b/mediagoblin/tests/fake_celery_conf_mgdb.ini @@ -0,0 +1,14 @@ +['mediagoblin'] +db_host = mongodb.example.org +db_port = 8080 +db_name = captain_lollerskates + +['something'] +or = other + +['celery'] +some_variable = poolf +mail_port = 2020 +celeryd_eta_scheduler_precision = 3.1 +celery_result_persistent = false +celery_imports = baz.bar.foo, import.is.a.this diff --git a/mediagoblin/tests/fake_config_spec.ini b/mediagoblin/tests/fake_config_spec.ini new file mode 100644 index 00000000..9421ce36 --- /dev/null +++ b/mediagoblin/tests/fake_config_spec.ini @@ -0,0 +1,10 @@ +[carrotapp] +# Whether or not our carrots are going to be turned into cake. +carrotcake = boolean(default=False) +num_carrots = integer(default=1) + +# A message encouraging our users to eat their carrots. +encouragement_phrase = string() + +[celery] +eat_celery_with_carrots = boolean(default=True)
\ No newline at end of file diff --git a/mediagoblin/tests/test_auth.py b/mediagoblin/tests/test_auth.py index 3d569093..3a13cbb1 100644 --- a/mediagoblin/tests/test_auth.py +++ b/mediagoblin/tests/test_auth.py @@ -77,7 +77,7 @@ def test_register_views(test_app): # Make sure it rendered with the appropriate template assert util.TEMPLATE_TEST_CONTEXT.has_key( 'mediagoblin/auth/register.html') - + # Try to register without providing anything, should error # -------------------------------------------------------- @@ -182,7 +182,7 @@ def test_register_views(test_app): unicode(new_user['_id'])] assert parsed_get_params['token'] == [ new_user['verification_key']] - + ## Try verifying with bs verification key, shouldn't work util.clear_test_template_context() test_app.get( @@ -209,8 +209,6 @@ def test_register_views(test_app): assert new_user['status'] == u'active' assert new_user['email_verified'] == True - ## TODO: Try logging in - # Uniqueness checks # ----------------- ## We shouldn't be able to register with that user twice @@ -221,7 +219,7 @@ def test_register_views(test_app): 'password': 'iamsohappy2', 'confirm_password': 'iamsohappy2', 'email': 'happygrrl2@example.org'}) - + context = util.TEMPLATE_TEST_CONTEXT[ 'mediagoblin/auth/register.html'] form = context['register_form'] @@ -229,3 +227,123 @@ def test_register_views(test_app): u'Sorry, a user with that name already exists.'] ## TODO: Also check for double instances of an email address? + + +@setup_fresh_app +def test_authentication_views(test_app): + """ + Test logging in and logging out + """ + # Make a new user + test_user = mg_globals.database.User() + test_user['username'] = u'chris' + test_user['email'] = u'chris@example.com' + test_user['pw_hash'] = auth_lib.bcrypt_gen_password_hash('toast') + test_user.save() + + # Get login + # --------- + test_app.get('/auth/login/') + assert util.TEMPLATE_TEST_CONTEXT.has_key( + 'mediagoblin/auth/login.html') + + # Failed login - blank form + # ------------------------- + util.clear_test_template_context() + response = test_app.post('/auth/login/') + context = util.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/login.html'] + form = context['login_form'] + assert form.username.errors == [u'This field is required.'] + assert form.password.errors == [u'This field is required.'] + + # Failed login - blank user + # ------------------------- + util.clear_test_template_context() + response = test_app.post( + '/auth/login/', { + 'password': u'toast'}) + context = util.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/login.html'] + form = context['login_form'] + assert form.username.errors == [u'This field is required.'] + + # Failed login - blank password + # ----------------------------- + util.clear_test_template_context() + response = test_app.post( + '/auth/login/', { + 'username': u'chris'}) + context = util.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/login.html'] + form = context['login_form'] + assert form.password.errors == [u'This field is required.'] + + # Failed login - bad user + # ----------------------- + util.clear_test_template_context() + response = test_app.post( + '/auth/login/', { + 'username': u'steve', + 'password': 'toast'}) + context = util.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/login.html'] + assert context['login_failed'] + + # Failed login - bad password + # --------------------------- + util.clear_test_template_context() + response = test_app.post( + '/auth/login/', { + 'username': u'chris', + 'password': 'jam'}) + context = util.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/login.html'] + assert context['login_failed'] + + # Successful login + # ---------------- + util.clear_test_template_context() + response = test_app.post( + '/auth/login/', { + 'username': u'chris', + 'password': 'toast'}) + + # User should be redirected + response.follow() + assert_equal( + urlparse.urlsplit(response.location)[2], + '/') + assert util.TEMPLATE_TEST_CONTEXT.has_key( + 'mediagoblin/root.html') + + # Make sure user is in the session + context = util.TEMPLATE_TEST_CONTEXT['mediagoblin/root.html'] + session = context['request'].session + assert session['user_id'] == unicode(test_user['_id']) + + # Successful logout + # ----------------- + util.clear_test_template_context() + response = test_app.get('/auth/logout/') + + # Should be redirected to index page + response.follow() + assert_equal( + urlparse.urlsplit(response.location)[2], + '/') + assert util.TEMPLATE_TEST_CONTEXT.has_key( + 'mediagoblin/root.html') + + # Make sure the user is not in the session + context = util.TEMPLATE_TEST_CONTEXT['mediagoblin/root.html'] + session = context['request'].session + assert session.has_key('user_id') == False + + # User is redirected to custom URL if POST['next'] is set + # ------------------------------------------------------- + util.clear_test_template_context() + response = test_app.post( + '/auth/login/', { + 'username': u'chris', + 'password': 'toast', + 'next' : '/u/chris/'}) + assert_equal( + urlparse.urlsplit(response.location)[2], + '/u/chris/') + diff --git a/mediagoblin/tests/test_celery_setup.py b/mediagoblin/tests/test_celery_setup.py index 558eb458..8bf97ae4 100644 --- a/mediagoblin/tests/test_celery_setup.py +++ b/mediagoblin/tests/test_celery_setup.py @@ -17,6 +17,13 @@ import pkg_resources from mediagoblin import celery_setup +from mediagoblin.config import read_mediagoblin_config + + +TEST_CELERY_CONF_NOSPECIALDB = pkg_resources.resource_filename( + 'mediagoblin.tests', 'fake_celery_conf.ini') +TEST_CELERY_CONF_MGSPECIALDB = pkg_resources.resource_filename( + 'mediagoblin.tests', 'fake_celery_conf_mgdb.ini') def test_setup_celery_from_config(): @@ -27,14 +34,12 @@ def test_setup_celery_from_config(): for var in vars_to_wipe: delattr(module, var) + global_config, validation_result = read_mediagoblin_config( + TEST_CELERY_CONF_NOSPECIALDB) + app_config = global_config['mediagoblin'] + celery_setup.setup_celery_from_config( - {}, - {'something': {'or': 'other'}, - 'celery': {'some_variable': 'floop', - 'mail_port': '2000', - 'CELERYD_ETA_SCHEDULER_PRECISION': '1.3', - 'celery_result_persistent': 'true', - 'celery_imports': 'foo.bar.baz this.is.an.import'}}, + app_config, global_config, 'mediagoblin.tests.fake_celery_module', set_environ=False) from mediagoblin.tests import fake_celery_module @@ -53,17 +58,12 @@ def test_setup_celery_from_config(): _wipe_testmodule_clean(fake_celery_module) + global_config, validation_result = read_mediagoblin_config( + TEST_CELERY_CONF_MGSPECIALDB) + app_config = global_config['mediagoblin'] + celery_setup.setup_celery_from_config( - {'db_host': 'mongodb.example.org', - 'db_port': '8080', - 'db_name': 'captain_lollerskates', - 'celery_section': 'vegetable'}, - {'something': {'or': 'other'}, - 'vegetable': {'some_variable': 'poolf', - 'mail_port': '2020', - 'CELERYD_ETA_SCHEDULER_PRECISION': '3.1', - 'celery_result_persistent': 'false', - 'celery_imports': 'baz.bar.foo import.is.a.this'}}, + app_config, global_config, 'mediagoblin.tests.fake_celery_module', set_environ=False) from mediagoblin.tests import fake_celery_module diff --git a/mediagoblin/tests/test_config.py b/mediagoblin/tests/test_config.py new file mode 100644 index 00000000..244f05e5 --- /dev/null +++ b/mediagoblin/tests/test_config.py @@ -0,0 +1,97 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011 Free Software Foundation, Inc +# +# 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/>. + +import pkg_resources + +from mediagoblin import config + + +CARROT_CONF_GOOD = pkg_resources.resource_filename( + 'mediagoblin.tests', 'fake_carrot_conf_good.ini') +CARROT_CONF_EMPTY = pkg_resources.resource_filename( + 'mediagoblin.tests', 'fake_carrot_conf_empty.ini') +CARROT_CONF_BAD = pkg_resources.resource_filename( + 'mediagoblin.tests', 'fake_carrot_conf_bad.ini') +FAKE_CONFIG_SPEC = pkg_resources.resource_filename( + 'mediagoblin.tests', 'fake_config_spec.ini') + + +def test_read_mediagoblin_config(): + # An empty file + this_conf, validation_results = config.read_mediagoblin_config( + CARROT_CONF_EMPTY, FAKE_CONFIG_SPEC) + + assert this_conf['carrotapp']['carrotcake'] == False + assert this_conf['carrotapp']['num_carrots'] == 1 + assert not this_conf['carrotapp'].has_key('encouragement_phrase') + assert this_conf['celery']['eat_celery_with_carrots'] == True + + # A good file + this_conf, validation_results = config.read_mediagoblin_config( + CARROT_CONF_GOOD, FAKE_CONFIG_SPEC) + + assert this_conf['carrotapp']['carrotcake'] == True + assert this_conf['carrotapp']['num_carrots'] == 88 + assert this_conf['carrotapp']['encouragement_phrase'] == \ + "I'd love it if you eat your carrots!" + assert this_conf['carrotapp']['blah_blah'] == "blah!" + assert this_conf['celery']['eat_celery_with_carrots'] == False + + # A bad file + this_conf, validation_results = config.read_mediagoblin_config( + CARROT_CONF_BAD, FAKE_CONFIG_SPEC) + + # These should still open but will have errors that we'll test for + # in test_generate_validation_report() + assert this_conf['carrotapp']['carrotcake'] == 'slobber' + assert this_conf['carrotapp']['num_carrots'] == 'GROSS' + assert this_conf['carrotapp']['encouragement_phrase'] == \ + "586956856856" + assert this_conf['carrotapp']['blah_blah'] == "blah!" + assert this_conf['celery']['eat_celery_with_carrots'] == "pants" + + +def test_generate_validation_report(): + # Empty + this_conf, validation_results = config.read_mediagoblin_config( + CARROT_CONF_EMPTY, FAKE_CONFIG_SPEC) + report = config.generate_validation_report(this_conf, validation_results) + assert report is None + + # Good + this_conf, validation_results = config.read_mediagoblin_config( + CARROT_CONF_GOOD, FAKE_CONFIG_SPEC) + report = config.generate_validation_report(this_conf, validation_results) + assert report is None + + # Bad + this_conf, validation_results = config.read_mediagoblin_config( + CARROT_CONF_BAD, FAKE_CONFIG_SPEC) + report = config.generate_validation_report(this_conf, validation_results) + + assert report.startswith("""\ +There were validation problems loading this config file: +--------------------------------------------------------""") + + expected_warnings = [ + 'carrotapp:carrotcake = the value "slobber" is of the wrong type.', + 'carrotapp:num_carrots = the value "GROSS" is of the wrong type.', + 'celery:eat_celery_with_carrots = the value "pants" is of the wrong type.'] + warnings = report.splitlines()[2:] + + assert len(warnings) == 3 + for warning in expected_warnings: + assert warning in warnings diff --git a/mediagoblin/tests/test_globals.py b/mediagoblin/tests/test_globals.py index 59d217f3..63578d62 100644 --- a/mediagoblin/tests/test_globals.py +++ b/mediagoblin/tests/test_globals.py @@ -14,16 +14,32 @@ # 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 nose.tools import assert_raises + from mediagoblin import mg_globals -def test_setup_globals(): - mg_globals.setup_globals( - db_connection='my favorite db_connection!', - database='my favorite database!', - public_store='my favorite public_store!', - queue_store='my favorite queue_store!') - - assert mg_globals.db_connection == 'my favorite db_connection!' - assert mg_globals.database == 'my favorite database!' - assert mg_globals.public_store == 'my favorite public_store!' - assert mg_globals.queue_store == 'my favorite queue_store!' +class TestGlobals(object): + def setUp(self): + self.old_connection = mg_globals.db_connection + self.old_database = mg_globals.database + + def tearDown(self): + mg_globals.db_connection = self.old_connection + mg_globals.database = self.old_database + + def test_setup_globals(self): + mg_globals.setup_globals( + db_connection='my favorite db_connection!', + database='my favorite database!', + public_store='my favorite public_store!', + queue_store='my favorite queue_store!') + + assert mg_globals.db_connection == 'my favorite db_connection!' + assert mg_globals.database == 'my favorite database!' + assert mg_globals.public_store == 'my favorite public_store!' + assert mg_globals.queue_store == 'my favorite queue_store!' + + assert_raises( + AssertionError, + mg_globals.setup_globals, + no_such_global_foo = "Dummy") diff --git a/mediagoblin/tests/test_mgoblin_app.ini b/mediagoblin/tests/test_mgoblin_app.ini new file mode 100644 index 00000000..94eafb5a --- /dev/null +++ b/mediagoblin/tests/test_mgoblin_app.ini @@ -0,0 +1,12 @@ +[mediagoblin] +queuestore_base_dir = %(here)s/test_user_dev/media/queue +publicstore_base_dir = %(here)s/test_user_dev/media/public +publicstore_base_url = /mgoblin_media/ +direct_remote_path = /mgoblin_static/ +email_sender_address = "notice@mediagoblin.example.org" +email_debug_mode = true +db_name = __mediagoblin_tests__ + +# Celery shouldn't be set up by the paste app factory as it's set up +# elsewhere +celery_setup_elsewhere = true diff --git a/mediagoblin/tests/mgoblin_test_app.ini b/mediagoblin/tests/test_server.ini index abed2615..929a1ccf 100644 --- a/mediagoblin/tests/mgoblin_test_app.ini +++ b/mediagoblin/tests/test_server.ini @@ -10,16 +10,7 @@ use = egg:Paste#urlmap [app:mediagoblin] use = egg:mediagoblin#app filter-with = beaker -queuestore_base_dir = %(here)s/test_user_dev/media/queue -publicstore_base_dir = %(here)s/test_user_dev/media/public -publicstore_base_url = /mgoblin_media/ -direct_remote_path = /mgoblin_static/ -email_sender_address = "notice@mediagoblin.example.org" -email_debug_mode = true -db_name = __mediagoblin_tests__ -# Celery shouldn't be set up by the paste app factory as it's set up -# elsewhere -celery_setup_elsewhere = true +config = %(here)s/test_mgoblin_app.ini [app:publicstore_serve] use = egg:Paste#static diff --git a/mediagoblin/tests/test_storage.py b/mediagoblin/tests/test_storage.py index 55b66e84..1800c29d 100644 --- a/mediagoblin/tests/test_storage.py +++ b/mediagoblin/tests/test_storage.py @@ -58,8 +58,8 @@ class FakeRemoteStorage(storage.BasicFileStorage): local_storage = False -def test_storage_system_from_paste_config(): - this_storage = storage.storage_system_from_paste_config( +def test_storage_system_from_config(): + this_storage = storage.storage_system_from_config( {'somestorage_base_url': 'http://example.org/moodia/', 'somestorage_base_dir': '/tmp/', 'somestorage_garbage_arg': 'garbage_arg', @@ -69,7 +69,7 @@ def test_storage_system_from_paste_config(): assert this_storage.base_dir == '/tmp/' assert this_storage.__class__ is storage.BasicFileStorage - this_storage = storage.storage_system_from_paste_config( + this_storage = storage.storage_system_from_config( {'somestorage_foobie': 'eiboof', 'somestorage_blech': 'hcelb', 'somestorage_garbage_arg': 'garbage_arg', diff --git a/mediagoblin/tests/tools.py b/mediagoblin/tests/tools.py index 342b54b7..515bccd4 100644 --- a/mediagoblin/tests/tools.py +++ b/mediagoblin/tests/tools.py @@ -18,20 +18,25 @@ import pkg_resources import os, shutil -from paste.deploy import appconfig, loadapp +from paste.deploy import loadapp from webtest import TestApp -from mediagoblin import util +from mediagoblin import util, mg_globals +from mediagoblin.config import read_mediagoblin_config +from mediagoblin.celery_setup import setup_celery_from_config from mediagoblin.decorators import _make_safe from mediagoblin.db.open import setup_connection_and_db_from_config MEDIAGOBLIN_TEST_DB_NAME = '__mediagoblinunittests__' +TEST_SERVER_CONFIG = pkg_resources.resource_filename( + 'mediagoblin.tests', 'test_server.ini') TEST_APP_CONFIG = pkg_resources.resource_filename( - 'mediagoblin.tests', 'mgoblin_test_app.ini') + 'mediagoblin.tests', 'test_mgoblin_app.ini') TEST_USER_DEV = pkg_resources.resource_filename( 'mediagoblin.tests', 'test_user_dev') MGOBLIN_APP = None +CELERY_SETUP = False USER_DEV_DIRECTORIES_TO_SETUP = [ 'media/public', 'media/queue', @@ -49,6 +54,9 @@ def get_test_app(dump_old_app=True): u"mediagoblin.celery_setup.from_tests module. Like so:\n" u"$ CELERY_CONFIG_MODULE=mediagoblin.celery_setup.from_tests ./bin/nosetests") + global MGOBLIN_APP + global CELERY_SETUP + # Just return the old app if that exists and it's okay to set up # and return if MGOBLIN_APP and not dump_old_app: @@ -63,16 +71,13 @@ def get_test_app(dump_old_app=True): os.makedirs(full_dir) # Get app config - config = appconfig( - 'config:' + os.path.basename(TEST_APP_CONFIG), - relative_to=os.path.dirname(TEST_APP_CONFIG), - name='mediagoblin') + global_config, validation_result = read_mediagoblin_config(TEST_APP_CONFIG) + app_config = global_config['mediagoblin'] # Wipe database # @@: For now we're dropping collections, but we could also just # collection.remove() ? - connection, db = setup_connection_and_db_from_config( - config.local_conf) + connection, db = setup_connection_and_db_from_config(app_config) collections_to_wipe = [ collection @@ -90,9 +95,19 @@ def get_test_app(dump_old_app=True): # setup app and return test_app = loadapp( - 'config:' + TEST_APP_CONFIG) + 'config:' + TEST_SERVER_CONFIG) + + app = TestApp(test_app) + MGOBLIN_APP = app + + # setup celery + if not CELERY_SETUP: + setup_celery_from_config( + mg_globals.app_config, mg_globals.global_config, + set_environ=True) + CELERY_SETUP = True - return TestApp(test_app) + return app def setup_fresh_app(func): diff --git a/mediagoblin/user_pages/routing.py b/mediagoblin/user_pages/routing.py index c5e9a984..92998726 100644 --- a/mediagoblin/user_pages/routing.py +++ b/mediagoblin/user_pages/routing.py @@ -19,6 +19,8 @@ from routes.route import Route user_routes = [ Route('mediagoblin.user_pages.user_home', "/{user}/", controller="mediagoblin.user_pages.views:user_home"), + Route('mediagoblin.user_pages.user_gallery', "/{user}/gallery/", + controller="mediagoblin.user_pages.views:user_gallery"), Route('mediagoblin.user_pages.media_home', '/{user}/m/{media}/', requirements=dict(m_id="[0-9a-fA-F]{24}"), controller="mediagoblin.user_pages.views:media_home"), diff --git a/mediagoblin/user_pages/views.py b/mediagoblin/user_pages/views.py index 4ec0f0aa..d6cd6034 100644 --- a/mediagoblin/user_pages/views.py +++ b/mediagoblin/user_pages/views.py @@ -49,6 +49,33 @@ def user_home(request, page): 'media_entries': media_entries, 'pagination': pagination}) +@uses_pagination +def user_gallery(request, page): + """'Gallery' of a User()""" + user = request.db.User.find_one({ + 'username': request.matchdict['user'], + 'status': 'active'}) + if not user: + return exc.HTTPNotFound() + + cursor = request.db.MediaEntry.find( + {'uploader': user['_id'], + 'state': 'processed'}).sort('created', DESCENDING) + + pagination = Pagination(page, cursor) + media_entries = pagination() + + #if no data is available, return NotFound + if media_entries == None: + return exc.HTTPNotFound() + + return render_to_response( + request, + 'mediagoblin/user_pages/gallery.html', + {'user': user, + 'media_entries': media_entries, + 'pagination': pagination}) + @get_user_media_entry def media_home(request, media): diff --git a/mediagoblin/util.py b/mediagoblin/util.py index eb131830..4d625728 100644 --- a/mediagoblin/util.py +++ b/mediagoblin/util.py @@ -18,7 +18,6 @@ from email.MIMEText import MIMEText import gettext import pkg_resources import smtplib -import os import sys import re import urllib @@ -28,7 +27,6 @@ import copy from babel.localedata import exists import jinja2 import translitcodec -from paste.deploy.loadwsgi import NicerConfigParser from webob import Response, exc from lxml.html.clean import Cleaner @@ -351,28 +349,6 @@ def get_locale_from_request(request): return locale_to_lower_upper(target_lang) -def read_config_file(conf_file): - """ - Read a paste deploy style config file and process it. - """ - if not os.path.exists(conf_file): - raise IOError( - "MEDIAGOBLIN_CONFIG not set or file does not exist") - - parser = NicerConfigParser(conf_file) - parser.read(conf_file) - parser._defaults.setdefault( - 'here', os.path.dirname(os.path.abspath(conf_file))) - parser._defaults.setdefault( - '__file__', os.path.abspath(conf_file)) - - mgoblin_conf = dict( - [(section_name, dict(parser.items(section_name))) - for section_name in parser.sections()]) - - return mgoblin_conf - - # A super strict version of the lxml.html cleaner class HTML_CLEANER = Cleaner( scripts=True, diff --git a/runtests.sh b/runtests.sh new file mode 100755 index 00000000..9b96b17c --- /dev/null +++ b/runtests.sh @@ -0,0 +1,30 @@ +#!/bin/sh + +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011 Free Software Foundation, Inc +# +# 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/>. + +if [ -f ./bin/nosetests ]; then + echo "Using ./bin/nosetests"; + export NOSETESTS="./bin/nosetests"; +elif which nosetests > /dev/null; then + echo "Using nosetests from \$PATH"; + export NOSETESTS="nosetests"; +else + echo "No nosetests found, exiting! X_X"; + exit 1 +fi + +CELERY_CONFIG_MODULE=mediagoblin.celery_setup.from_tests $NOSETESTS $@ diff --git a/server.ini b/server.ini new file mode 100644 index 00000000..73fbe8e8 --- /dev/null +++ b/server.ini @@ -0,0 +1,34 @@ +[DEFAULT] +debug = true + +[composite:main] +use = egg:Paste#urlmap +/ = mediagoblin +/mgoblin_media/ = publicstore_serve +/mgoblin_static/ = mediagoblin_static + +[app:mediagoblin] +use = egg:mediagoblin#app +filter-with = beaker +config = %(here)s/mediagoblin.ini + +[app:publicstore_serve] +use = egg:Paste#static +document_root = %(here)s/user_dev/media/public/ + +[app:mediagoblin_static] +use = egg:Paste#static +document_root = %(here)s/mediagoblin/static/ + +[filter:beaker] +use = egg:Beaker#beaker_session +cache_dir = %(here)s/user_dev/beaker +beaker.session.key = mediagoblin +# beaker.session.secret = somesupersecret +beaker.session.data_dir = %(here)s/user_dev/beaker/sessions/data +beaker.session.lock_dir = %(here)s/user_dev/beaker/sessions/lock + +[server:main] +use = egg:Paste#http +host = 127.0.0.1 +port = 6543 @@ -42,7 +42,11 @@ setup( 'translitcodec', 'argparse', 'webtest', + 'ConfigObj', 'Markdown', + ## For now we're expecting that users will install this from + ## their package managers. + # 'lxml', ], test_suite='nose.collector', |