Merge branch '623_context_hooks'
This commit is contained in:
commit
ff97bbe944
@ -48,3 +48,80 @@ example might look like::
|
|||||||
This means that when people enable your plugin in their config you'll
|
This means that when people enable your plugin in their config you'll
|
||||||
be able to provide defaults as well as type validation.
|
be able to provide defaults as well as type validation.
|
||||||
|
|
||||||
|
|
||||||
|
Context Hooks
|
||||||
|
-------------
|
||||||
|
|
||||||
|
View specific hooks
|
||||||
|
+++++++++++++++++++
|
||||||
|
|
||||||
|
You can hook up to almost any template called by any specific view
|
||||||
|
fairly easily. As long as the view directly or indirectly uses the
|
||||||
|
method ``render_to_response`` you can access the context via a hook
|
||||||
|
that has a key in the format of the tuple::
|
||||||
|
|
||||||
|
(view_symbolic_name, view_template_path)
|
||||||
|
|
||||||
|
Where the "view symbolic name" is the same parameter used in
|
||||||
|
``request.urlgen()`` to look up the view. So say we're wanting to add
|
||||||
|
something to the context of the user's homepage. We look in
|
||||||
|
mediagoblin/user_pages/routing.py and see::
|
||||||
|
|
||||||
|
add_route('mediagoblin.user_pages.user_home',
|
||||||
|
'/u/<string:user>/',
|
||||||
|
'mediagoblin.user_pages.views:user_home')
|
||||||
|
|
||||||
|
Aha! That means that the name is ``mediagoblin.user_pages.user_home``.
|
||||||
|
Okay, so then we look at the view at the
|
||||||
|
``mediagoblin.user_pages.user_home`` method::
|
||||||
|
|
||||||
|
@uses_pagination
|
||||||
|
def user_home(request, page):
|
||||||
|
# [...] whole bunch of stuff here
|
||||||
|
return render_to_response(
|
||||||
|
request,
|
||||||
|
'mediagoblin/user_pages/user.html',
|
||||||
|
{'user': user,
|
||||||
|
'user_gallery_url': user_gallery_url,
|
||||||
|
'media_entries': media_entries,
|
||||||
|
'pagination': pagination})
|
||||||
|
|
||||||
|
Nice! So the template appears to be
|
||||||
|
``mediagoblin/user_pages/user.html``. Cool, that means that the key
|
||||||
|
is::
|
||||||
|
|
||||||
|
("mediagoblin.user_pages.user_home",
|
||||||
|
"mediagoblin/user_pages/user.html")
|
||||||
|
|
||||||
|
The context hook uses ``hook_transform()`` so that means that if we're
|
||||||
|
hooking into it, our hook will both accept one argument, ``context``,
|
||||||
|
and should return that modified object, like so::
|
||||||
|
|
||||||
|
def add_to_user_home_context(context):
|
||||||
|
context['foo'] = 'bar'
|
||||||
|
return context
|
||||||
|
|
||||||
|
hooks = {
|
||||||
|
("mediagoblin.user_pages.user_home",
|
||||||
|
"mediagoblin/user_pages/user.html"): add_to_user_home_context}
|
||||||
|
|
||||||
|
|
||||||
|
Global context hooks
|
||||||
|
++++++++++++++++++++
|
||||||
|
|
||||||
|
If you need to add something to the context of *every* view, it is not
|
||||||
|
hard; there are two hooks hook that also uses hook_transform (like the
|
||||||
|
above) but make available what you are providing to *every* view.
|
||||||
|
|
||||||
|
Note that there is a slight, but critical, difference between the two.
|
||||||
|
|
||||||
|
The most general one is the ``'template_global_context'`` hook. This
|
||||||
|
one is run only once, and is read into the global context... all views
|
||||||
|
will get access to what are in this dict.
|
||||||
|
|
||||||
|
The slightly more expensive but more powerful one is
|
||||||
|
``'template_context_prerender'``. This one is not added to the global
|
||||||
|
context... it is added to the actual context of each individual
|
||||||
|
template render right before it is run! Because of this you also can
|
||||||
|
do some powerful and crazy things, such as checking the request object
|
||||||
|
or other parts of the context before passing them on.
|
||||||
|
@ -188,6 +188,7 @@ class MediaGoblinApp(object):
|
|||||||
|
|
||||||
mg_request.setup_user_in_request(request)
|
mg_request.setup_user_in_request(request)
|
||||||
|
|
||||||
|
request.controller_name = None
|
||||||
try:
|
try:
|
||||||
found_rule, url_values = map_adapter.match(return_rule=True)
|
found_rule, url_values = map_adapter.match(return_rule=True)
|
||||||
request.matchdict = url_values
|
request.matchdict = url_values
|
||||||
@ -201,6 +202,9 @@ class MediaGoblinApp(object):
|
|||||||
exc.get_description(environ))(environ, start_response)
|
exc.get_description(environ))(environ, start_response)
|
||||||
|
|
||||||
controller = endpoint_to_controller(found_rule)
|
controller = endpoint_to_controller(found_rule)
|
||||||
|
# Make a reference to the controller's symbolic name on the request...
|
||||||
|
# used for lazy context modification
|
||||||
|
request.controller_name = found_rule.endpoint
|
||||||
|
|
||||||
# pass the request through our meddleware classes
|
# pass the request through our meddleware classes
|
||||||
try:
|
try:
|
||||||
|
26
mediagoblin/tests/appconfig_context_modified.ini
Normal file
26
mediagoblin/tests/appconfig_context_modified.ini
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
[mediagoblin]
|
||||||
|
direct_remote_path = /test_static/
|
||||||
|
email_sender_address = "notice@mediagoblin.example.org"
|
||||||
|
email_debug_mode = true
|
||||||
|
|
||||||
|
# TODO: Switch to using an in-memory database
|
||||||
|
sql_engine = "sqlite:///%(here)s/test_user_dev/mediagoblin.db"
|
||||||
|
|
||||||
|
# Celery shouldn't be set up by the application as it's setup via
|
||||||
|
# mediagoblin.init.celery.from_celery
|
||||||
|
celery_setup_elsewhere = true
|
||||||
|
|
||||||
|
[storage:publicstore]
|
||||||
|
base_dir = %(here)s/test_user_dev/media/public
|
||||||
|
base_url = /mgoblin_media/
|
||||||
|
|
||||||
|
[storage:queuestore]
|
||||||
|
base_dir = %(here)s/test_user_dev/media/queue
|
||||||
|
|
||||||
|
[celery]
|
||||||
|
CELERY_ALWAYS_EAGER = true
|
||||||
|
CELERY_RESULT_DBURI = "sqlite:///%(here)s/test_user_dev/celery.db"
|
||||||
|
BROKER_HOST = "sqlite:///%(here)s/test_user_dev/kombu.db"
|
||||||
|
|
||||||
|
[plugins]
|
||||||
|
[[mediagoblin.tests.testplugins.modify_context]]
|
@ -25,6 +25,7 @@ from mediagoblin import mg_globals
|
|||||||
from mediagoblin.init.plugins import setup_plugins
|
from mediagoblin.init.plugins import setup_plugins
|
||||||
from mediagoblin.init.config import read_mediagoblin_config
|
from mediagoblin.init.config import read_mediagoblin_config
|
||||||
from mediagoblin.tools import pluginapi
|
from mediagoblin.tools import pluginapi
|
||||||
|
from mediagoblin.tests.tools import get_app
|
||||||
|
|
||||||
|
|
||||||
def with_cleanup(*modules_to_delete):
|
def with_cleanup(*modules_to_delete):
|
||||||
@ -323,3 +324,37 @@ def test_plugin_config():
|
|||||||
# the callables thing shouldn't really have anything though.
|
# the callables thing shouldn't really have anything though.
|
||||||
assert len(config['plugins'][
|
assert len(config['plugins'][
|
||||||
'mediagoblin.tests.testplugins.callables1']) == 0
|
'mediagoblin.tests.testplugins.callables1']) == 0
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def context_modified_app(request):
|
||||||
|
"""
|
||||||
|
Get a MediaGoblin app fixture using appconfig_context_modified.ini
|
||||||
|
"""
|
||||||
|
return get_app(
|
||||||
|
request,
|
||||||
|
mgoblin_config=pkg_resources.resource_filename(
|
||||||
|
'mediagoblin.tests', 'appconfig_context_modified.ini'))
|
||||||
|
|
||||||
|
|
||||||
|
def test_modify_context(context_modified_app):
|
||||||
|
"""
|
||||||
|
Test that we can modify both the view/template specific and
|
||||||
|
global contexts for templates.
|
||||||
|
"""
|
||||||
|
# Specific thing passed into a page
|
||||||
|
result = context_modified_app.get("/modify_context/specific/")
|
||||||
|
assert result.body.strip() == """Specific page!
|
||||||
|
|
||||||
|
specific thing: in yer specificpage
|
||||||
|
global thing: globally appended!
|
||||||
|
something: orother
|
||||||
|
doubleme: happyhappy"""
|
||||||
|
|
||||||
|
# General test, should have global context variable only
|
||||||
|
result = context_modified_app.get("/modify_context/")
|
||||||
|
assert result.body.strip() == """General page!
|
||||||
|
|
||||||
|
global thing: globally appended!
|
||||||
|
lol: cats
|
||||||
|
doubleme: joyjoy"""
|
||||||
|
55
mediagoblin/tests/testplugins/modify_context/__init__.py
Normal file
55
mediagoblin/tests/testplugins/modify_context/__init__.py
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
# GNU MediaGoblin -- federated, autonomous media hosting
|
||||||
|
# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Affero General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Affero General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
from mediagoblin.tools import pluginapi
|
||||||
|
import pkg_resources
|
||||||
|
|
||||||
|
|
||||||
|
def append_to_specific_context(context):
|
||||||
|
context['specific_page_append'] = 'in yer specificpage'
|
||||||
|
return context
|
||||||
|
|
||||||
|
def append_to_global_context(context):
|
||||||
|
context['global_append'] = 'globally appended!'
|
||||||
|
return context
|
||||||
|
|
||||||
|
def double_doubleme(context):
|
||||||
|
if 'doubleme' in context:
|
||||||
|
context['doubleme'] = context['doubleme'] * 2
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
def setup_plugin():
|
||||||
|
routes = [
|
||||||
|
('modify_context.specific_page',
|
||||||
|
'/modify_context/specific/',
|
||||||
|
'mediagoblin.tests.testplugins.modify_context.views:specific'),
|
||||||
|
('modify_context.general_page',
|
||||||
|
'/modify_context/',
|
||||||
|
'mediagoblin.tests.testplugins.modify_context.views:general')]
|
||||||
|
|
||||||
|
pluginapi.register_routes(routes)
|
||||||
|
pluginapi.register_template_path(
|
||||||
|
pkg_resources.resource_filename(
|
||||||
|
'mediagoblin.tests.testplugins.modify_context', 'templates'))
|
||||||
|
|
||||||
|
|
||||||
|
hooks = {
|
||||||
|
'setup': setup_plugin,
|
||||||
|
('modify_context.specific_page',
|
||||||
|
'contextplugin/specific.html'): append_to_specific_context,
|
||||||
|
'template_global_context': append_to_global_context,
|
||||||
|
'template_context_prerender': double_doubleme}
|
@ -0,0 +1,5 @@
|
|||||||
|
General page!
|
||||||
|
|
||||||
|
global thing: {{ global_append }}
|
||||||
|
lol: {{ lol }}
|
||||||
|
doubleme: {{ doubleme }}
|
@ -0,0 +1,6 @@
|
|||||||
|
Specific page!
|
||||||
|
|
||||||
|
specific thing: {{ specific_page_append }}
|
||||||
|
global thing: {{ global_append }}
|
||||||
|
something: {{ something }}
|
||||||
|
doubleme: {{ doubleme }}
|
33
mediagoblin/tests/testplugins/modify_context/views.py
Normal file
33
mediagoblin/tests/testplugins/modify_context/views.py
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
# GNU MediaGoblin -- federated, autonomous media hosting
|
||||||
|
# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Affero General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Affero General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
from mediagoblin.tools.response import render_to_response
|
||||||
|
|
||||||
|
|
||||||
|
def specific(request):
|
||||||
|
return render_to_response(
|
||||||
|
request,
|
||||||
|
'contextplugin/specific.html',
|
||||||
|
{"something": "orother",
|
||||||
|
"doubleme": "happy"})
|
||||||
|
|
||||||
|
|
||||||
|
def general(request):
|
||||||
|
return render_to_response(
|
||||||
|
request,
|
||||||
|
'contextplugin/general.html',
|
||||||
|
{"lol": "cats",
|
||||||
|
"doubleme": "joy"})
|
@ -27,7 +27,7 @@ from mediagoblin import messages
|
|||||||
from mediagoblin import _version
|
from mediagoblin import _version
|
||||||
from mediagoblin.tools import common
|
from mediagoblin.tools import common
|
||||||
from mediagoblin.tools.translate import set_thread_locale
|
from mediagoblin.tools.translate import set_thread_locale
|
||||||
from mediagoblin.tools.pluginapi import get_hook_templates
|
from mediagoblin.tools.pluginapi import get_hook_templates, hook_transform
|
||||||
from mediagoblin.tools.timesince import timesince
|
from mediagoblin.tools.timesince import timesince
|
||||||
from mediagoblin.meddleware.csrf import render_csrf_form_token
|
from mediagoblin.meddleware.csrf import render_csrf_form_token
|
||||||
|
|
||||||
@ -80,6 +80,9 @@ def get_jinja_env(template_loader, locale):
|
|||||||
# allow for hooking up plugin templates
|
# allow for hooking up plugin templates
|
||||||
template_env.globals['get_hook_templates'] = get_hook_templates
|
template_env.globals['get_hook_templates'] = get_hook_templates
|
||||||
|
|
||||||
|
template_env.globals = hook_transform(
|
||||||
|
'template_global_context', template_env.globals)
|
||||||
|
|
||||||
if exists(locale):
|
if exists(locale):
|
||||||
SETUP_JINJA_ENVS[locale] = template_env
|
SETUP_JINJA_ENVS[locale] = template_env
|
||||||
|
|
||||||
@ -103,6 +106,20 @@ def render_template(request, template_path, context):
|
|||||||
rendered_csrf_token = render_csrf_form_token(request)
|
rendered_csrf_token = render_csrf_form_token(request)
|
||||||
if rendered_csrf_token is not None:
|
if rendered_csrf_token is not None:
|
||||||
context['csrf_token'] = render_csrf_form_token(request)
|
context['csrf_token'] = render_csrf_form_token(request)
|
||||||
|
|
||||||
|
# allow plugins to do things to the context
|
||||||
|
if request.controller_name:
|
||||||
|
context = hook_transform(
|
||||||
|
(request.controller_name, template_path),
|
||||||
|
context)
|
||||||
|
|
||||||
|
# More evil: allow plugins to possibly do something to the context
|
||||||
|
# in every request ever with access to the request and other
|
||||||
|
# variables. Note: this is slower than using
|
||||||
|
# template_global_context
|
||||||
|
context = hook_transform(
|
||||||
|
'template_context_prerender', context)
|
||||||
|
|
||||||
rendered = template.render(context)
|
rendered = template.render(context)
|
||||||
|
|
||||||
if common.TESTS_ENABLED:
|
if common.TESTS_ENABLED:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user