Early version of theme registry code
[mediagoblin.git] / mediagoblin / app.py
CommitLineData
8e1e744d 1# GNU MediaGoblin -- federated, autonomous media hosting
cf29e8a8 2# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
e5572c60
ML
3#
4# This program is free software: you can redistribute it and/or modify
5# it under the terms of the GNU Affero General Public License as published by
6# the Free Software Foundation, either version 3 of the License, or
7# (at your option) any later version.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU Affero General Public License for more details.
13#
14# You should have received a copy of the GNU Affero General Public License
15# along with this program. If not, see <http://www.gnu.org/licenses/>.
16
571198c9 17import os
31a8ff42 18import urllib
ec97c937 19import logging
31a8ff42 20
31a8ff42 21import routes
b61874b2 22from webob import Request, exc
31a8ff42 23
ec97c937 24from mediagoblin import routing, meddleware, __version__
c0022ecc
CAW
25from mediagoblin.tools import common, translate, template
26from mediagoblin.tools.response import render_404
828fc630 27from mediagoblin.tools.theme import register_themes
152a3bfa 28from mediagoblin.tools import request as mg_request
6e7ce8d1 29from mediagoblin.mg_globals import setup_globals
073b61fe 30from mediagoblin.init.celery import setup_celery_from_config
29b6f917 31from mediagoblin.init.plugins import setup_plugins
50854db0
WKG
32from mediagoblin.init import (get_jinja_loader, get_staticdirector,
33 setup_global_and_app_config, setup_workbench, setup_database,
0533f117 34 setup_storage, setup_beaker_cache)
90e342f9 35
31a8ff42 36
ec97c937
E
37_log = logging.getLogger(__name__)
38
39
8e1e744d 40class MediaGoblinApp(object):
31a8ff42 41 """
3f5cf663
CAW
42 WSGI application of MediaGoblin
43
44 ... this is the heart of the program!
31a8ff42 45 """
3f5cf663
CAW
46 def __init__(self, config_path, setup_celery=True):
47 """
48 Initialize the application based on a configuration file.
49
50 Arguments:
51 - config_path: path to the configuration file we're opening.
52 - setup_celery: whether or not to setup celery during init.
53 (Note: setting 'celery_setup_elsewhere' also disables
54 setting up celery.)
55 """
ec97c937 56 _log.info("GNU MediaGoblin %s main server starting", __version__)
3f369674 57 _log.debug("Using config file %s", config_path)
3f5cf663
CAW
58 ##############
59 # Setup config
60 ##############
61
62 # Open and setup the config
fe289be4 63 global_config, app_config = setup_global_and_app_config(config_path)
3f5cf663
CAW
64
65 ##########################################
66 # Setup other connections / useful objects
67 ##########################################
68
29b6f917
WKG
69 # Set up plugins -- need to do this early so that plugins can
70 # affect startup.
71 _log.info("Setting up plugins.")
72 setup_plugins()
73
3f5cf663 74 # Set up the database
3f4b5e4a 75 self.connection, self.db = setup_database()
ff94114c 76
828fc630
CAW
77 # Register themes
78 self.theme_registry = register_themes(app_config)
79
5afdd7a1 80 # Get the template environment
42ef819c 81 self.template_loader = get_jinja_loader(
1e9d1acc 82 app_config.get('local_templates'))
0c8a30e6 83
5afdd7a1 84 # Set up storage systems
dccef262 85 self.public_store, self.queue_store = setup_storage()
5afdd7a1
CAW
86
87 # set up routing
0f63a944 88 self.routing = routing.get_mapper()
31a8ff42 89
582c4d5f 90 # set up staticdirector tool
c85c9dc7 91 self.staticdirector = get_staticdirector(app_config)
3f5cf663 92
0533f117
CAW
93 # set up caching
94 self.cache = setup_beaker_cache()
95
3f5cf663
CAW
96 # Setup celery, if appropriate
97 if setup_celery and not app_config.get('celery_setup_elsewhere'):
d9a31a39 98 if os.environ.get('CELERY_ALWAYS_EAGER', 'false').lower() == 'true':
3f5cf663
CAW
99 setup_celery_from_config(
100 app_config, global_config,
101 force_celery_always_eager=True)
102 else:
103 setup_celery_from_config(app_config, global_config)
104
105 #######################################################
106 # Insert appropriate things into mediagoblin.mg_globals
107 #
df9809c2
CAW
108 # certain properties need to be accessed globally eg from
109 # validators, etc, which might not access to the request
110 # object.
3f5cf663
CAW
111 #######################################################
112
243c3843 113 setup_globals(app=self)
1fd97db3
CAW
114
115 # Workbench *currently* only used by celery, so this only
116 # matters in always eager mode :)
7664b4db 117 setup_workbench()
df9809c2 118
ce5ae8da
CAW
119 # instantiate application meddleware
120 self.meddleware = [common.import_component(m)(self)
121 for m in meddleware.ENABLED_MEDDLEWARE]
0c8a30e6 122
e824570a 123 def call_backend(self, environ, start_response):
31a8ff42 124 request = Request(environ)
0c8a30e6 125
582c4d5f 126 ## Routing / controller loading stuff
0c8a30e6 127 path_info = request.path_info
0f63a944 128 route_match = self.routing.match(path_info)
31a8ff42 129
05788ef4
E
130 # By using fcgi, mediagoblin can run under a base path
131 # like /mediagoblin/. request.path_info contains the
132 # path inside mediagoblin. If the something needs the
133 # full path of the current page, that should include
134 # the basepath.
135 # Note: urlgen and routes are fine!
136 request.full_path = environ["SCRIPT_NAME"] + request.path_info
137 # python-routes uses SCRIPT_NAME. So let's use that too.
138 # The other option would be:
139 # request.full_path = environ["SCRIPT_URL"]
140
871fc591 141 # Fix up environ for urlgen
d23d4b23 142 # See bug: https://bitbucket.org/bbangert/routes/issue/55/cache_hostinfo-breaks-on-https-off
871fc591
E
143 if environ.get('HTTPS', '').lower() == 'off':
144 environ.pop('HTTPS')
145
3d0557bf
CAW
146 ## Attach utilities to the request object
147 request.matchdict = route_match
148 request.urlgen = routes.URLGenerator(self.routing, environ)
149 # Do we really want to load this via middleware? Maybe?
150 request.session = request.environ['beaker.session']
151 # Attach self as request.app
152 # Also attach a few utilities from request.app for convenience?
153 request.app = self
ae3bc7fa 154 request.locale = translate.get_locale_from_request(request)
0c8a30e6 155
ae3bc7fa 156 request.template_env = template.get_jinja_env(
3d0557bf
CAW
157 self.template_loader, request.locale)
158 request.db = self.db
159 request.staticdirect = self.staticdirector
160
152a3bfa 161 mg_request.setup_user_in_request(request)
3d0557bf 162
31a8ff42
CAW
163 # No matching page?
164 if route_match is None:
165 # Try to do see if we have a match with a trailing slash
166 # added and if so, redirect
167 if not path_info.endswith('/') \
168 and request.method == 'GET' \
0f63a944 169 and self.routing.match(path_info + '/'):
31a8ff42
CAW
170 new_path_info = path_info + '/'
171 if request.GET:
172 new_path_info = '%s?%s' % (
173 new_path_info, urllib.urlencode(request.GET))
1bb0fdf2 174 redirect = exc.HTTPFound(location=new_path_info)
31a8ff42
CAW
175 return request.get_response(redirect)(environ, start_response)
176
177 # Okay, no matches. 404 time!
3807e8e2 178 request.matchdict = {} # in case our template expects it
c0022ecc 179 return render_404(request)(environ, start_response)
31a8ff42 180
8a0d35e7
CAW
181 # import the controller, or if it's already a callable, call that
182 route_controller = route_match['controller']
183 if isinstance(route_controller, unicode) \
184 or isinstance(route_controller, str):
185 controller = common.import_component(route_match['controller'])
186 else:
187 controller = route_match['controller']
91cf6738
NY
188
189 # pass the request through our meddleware classes
190 for m in self.meddleware:
191 response = m.process_request(request, controller)
192 if response is not None:
193 return response(environ, start_response)
194
31a8ff42
CAW
195 request.start_response = start_response
196
0c8a30e6
NY
197 # get the response from the controller
198 response = controller(request)
199
ce5ae8da
CAW
200 # pass the response through the meddleware
201 for m in self.meddleware[::-1]:
0c8a30e6
NY
202 m.process_response(request, response)
203
e824570a
E
204 return response(environ, start_response)
205
206 def __call__(self, environ, start_response):
207 ## If more errors happen that look like unclean sessions:
208 # self.db.check_session_clean()
209
2bc8ff0d 210 try:
e824570a
E
211 return self.call_backend(environ, start_response)
212 finally:
213 # Reset the sql session, so that the next request
214 # gets a fresh session
2bc8ff0d 215 self.db.reset_after_request()
31a8ff42
CAW
216
217
5784c4e9 218def paste_app_factory(global_config, **app_config):
91903aa6
CAW
219 configs = app_config['config'].split()
220 mediagoblin_config = None
221 for config in configs:
222 if os.path.exists(config) and os.access(config, os.R_OK):
223 mediagoblin_config = config
224 break
225
226 if not mediagoblin_config:
227 raise IOError("Usable mediagoblin config not found.")
228
229 mgoblin_app = MediaGoblinApp(mediagoblin_config)
b61874b2 230
c4d71564 231 return mgoblin_app