Skip to content

Commit

Permalink
Merge branch 'poc-flask-views.session' into poc-flask-views
Browse files Browse the repository at this point in the history
  • Loading branch information
brew committed May 30, 2016
2 parents 25982ca + 973d8b0 commit 5cb2ca4
Show file tree
Hide file tree
Showing 9 changed files with 217 additions and 20 deletions.
25 changes: 24 additions & 1 deletion ckan/common.py
Expand Up @@ -12,7 +12,7 @@
from flask.ext.babel import gettext as flask_gettext
from pylons.i18n import _ as pylons_gettext, ungettext

from pylons import g, session, response
from pylons import g, response
import simplejson as json

try:
Expand Down Expand Up @@ -87,3 +87,26 @@ def __delattr__(self, name):


c = PylonsStyleContext()


class Session():

def __getattr__(self, name):
if is_flask():
return getattr(flask.session, name, None)
else:
return getattr(pylons.session, name, None)

def __setattr__(self, name, value):
if is_flask():
return setattr(flask.session, name, value)
else:
return setattr(pylons.session, name, value)

def __delattr__(self, name):
if is_flask():
return delattr(flask.session, name, None)
else:
return delattr(pylons.session, name, None)

session = Session()
32 changes: 29 additions & 3 deletions ckan/config/middleware.py
Expand Up @@ -28,6 +28,7 @@
from flask import request as flask_request
from flask import _request_ctx_stack
from flask.ctx import _AppCtxGlobals
from flask.sessions import SessionInterface
from werkzeug.exceptions import HTTPException
from werkzeug.test import create_environ, run_wsgi_app
from flask.ext.babel import Babel
Expand Down Expand Up @@ -245,22 +246,47 @@ def __getattr__(self, name):


def make_flask_stack(conf, **app_conf):
""" This has to pass the flask app through all the same middleware that
Pylons used """
"""
This passes the flask app through most of the same middleware that Pylons
uses.
"""

debug = app_conf.get('debug', True)

root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
app = CKANFlask(__name__)
app.debug = debug
app.template_folder = os.path.join(root, 'templates')
app.app_ctx_globals_class = CKAN_AppCtxGlobals

# Do all the Flask-specific stuff before adding other middlewares

# secret key needed for flask-debug-toolbar
app.config['SECRET_KEY'] = '<replace with a secret key>'
app.debug = True
app.config['DEBUG_TB_INTERCEPT_REDIRECTS'] = False
DebugToolbarExtension(app)

# Use Beaker as the Flask session interface
class BeakerSessionInterface(SessionInterface):
def open_session(self, app, request):
session = request.environ['beaker.session']
return session

def save_session(self, app, session, response):
session.save()

cache_dir = app_conf.get('cache_dir') or app_conf.get('cache.dir')
session_opts = {
'session.data_dir': '{data_dir}/sessions'.format(
data_dir=cache_dir),
'session.key': app_conf.get('beaker.session.key'),
'session.cookie_expires':
app_conf.get('beaker.session.cookie_expires'),
'session.secret': app_conf.get('beaker.session.secret')
}
app.wsgi_app = SessionMiddleware(app.wsgi_app, session_opts)
app.session_interface = BeakerSessionInterface()

# Add jinja2 extensions and filters
extensions = [
'jinja2.ext.do', 'jinja2.ext.with_',
Expand Down
14 changes: 14 additions & 0 deletions ckan/templates/tests/flash_messages.html
@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html>
<head>
<title>Hello</title>
</head>
<body>
Flash messages:
{% for message in h.flash.pop_messages() | list %}
<div>
{{ message.category }}: {{ h.literal(message) }}
</div>
{% endfor %}
</body>
</html>
126 changes: 126 additions & 0 deletions ckan/tests/config/test_sessions.py
@@ -0,0 +1,126 @@
from nose.tools import ok_

from flask import Blueprint
from flask import render_template
from flask import redirect as flask_redirect
from flask import url_for
from ckan.lib.base import redirect as pylons_redirect
from ckan.lib.base import render as pylons_render

import ckan.plugins as p
import ckan.tests.helpers as helpers
import ckan.lib.helpers as h


class TestCrossFlaskPylonsFlashMessages(helpers.FunctionalTestBase):
'''
Test that flash message set in the Pylons controller can be accessed by
Flask views, and visa versa.
'''

def setup(self):
self.app = helpers._get_test_app()
self.flask_app = helpers.find_flask_app(self.app)

# Install plugin and register its blueprint
if not p.plugin_loaded('test_flash_plugin'):
p.load('test_flash_plugin')
plugin = p.get_plugin('test_flash_plugin')
self.flask_app.register_blueprint(plugin.get_blueprint(),
prioritise_rules=True)

def test_flash_populated_by_flask_redirect_to_flask(self):
'''
Flash store is populated by Flask view is accessible by another Flask
view.
'''
res = self.app.get(
'/flask_add_flash_message_redirect_to_flask').follow()

ok_("This is a success message populate by Flask" in res.body)

def test_flash_populated_in_pylons_action_redirect_to_flask(self):
'''
Flash store is populated by pylons action is accessible by Flask view.
'''
res = self.app.get('/pylons_add_flash_message_redirect_view').follow()

ok_("This is a success message populate by Pylons" in res.body)

def test_flash_populated_in_flask_view_redirect_to_pylons(self):
'''
Flash store is populated by flask view is accessible by pylons action.
'''
res = self.app.get('/flask_add_flash_message_redirect_pylons').follow()

ok_("This is a success message populate by Flask" in res.body)


class FlashMessagePlugin(p.SingletonPlugin):
'''
A Flask and Pylons compatible IRoutes plugin to add Flask views and Pylons
actions to display flash messages.
'''

p.implements(p.IRoutes, inherit=True)

def flash_message_view(self):
'''Flask view that renders the flash message html template.'''
return render_template('tests/flash_messages.html')

def add_flash_message_view_redirect_to_flask(self):
'''Add flash message, then redirect to Flask view to render it.'''
h.flash_success("This is a success message populate by Flask")
return flask_redirect(url_for('test_flash_plugin.flash_message_view'))

def add_flash_message_view_redirect_to_pylons(self):
'''Add flash message, then redirect to view that renders it'''
h.flash_success("This is a success message populate by Flask")
return flask_redirect('/pylons_view_flash_message')

def get_blueprint(self):
'''Return Flask Blueprint object to be registered by the Flask app.'''

# Create Blueprint for plugin
blueprint = Blueprint(self.name, self.__module__)
blueprint.template_folder = 'templates'
# Add plugin url rules to Blueprint object
rules = [
('/flask_add_flash_message_redirect_to_flask', 'add_flash_message',
self.add_flash_message_view_redirect_to_flask),
('/flask_add_flash_message_redirect_pylons',
'add_flash_message_view_redirect_to_pylons',
self.add_flash_message_view_redirect_to_pylons),
('/flask_view_flash_message', 'flash_message_view',
self.flash_message_view),
]
for rule in rules:
blueprint.add_url_rule(*rule)

return blueprint

controller = \
'ckan.tests.config.test_sessions:PylonsAddFlashMessageController'

def before_map(self, _map):
'''Update the pylons route map to be used by the Pylons app.'''
_map.connect('/pylons_add_flash_message_redirect_view',
controller=self.controller,
action='add_flash_message_redirect')

_map.connect('/pylons_view_flash_message',
controller=self.controller,
action='flash_message_action')
return _map


class PylonsAddFlashMessageController(p.toolkit.BaseController):

def flash_message_action(self):
'''Pylons view to render flash messages in a template.'''
return pylons_render('tests/flash_messages.html')

def add_flash_message_redirect(self):
# Adds a flash message and redirects to flask view
h.flash_success('This is a success message populate by Pylons')
return pylons_redirect('/flask_view_flash_message')
19 changes: 8 additions & 11 deletions ckan/views/api.py
Expand Up @@ -100,17 +100,14 @@ def _identify_user_default():
g.user = g.user.decode('utf8')
g.userobj = model.User.by_name(g.user)
if g.userobj is None or not g.userobj.is_active():
# This occurs when a user that was still logged in is deleted,
# or when you are logged in, clean db
# and then restart (or when you change your username)
# There is no user object, so even though repoze thinks you
# are logged in and your cookie has ckan_display_name, we
# need to force user to logout and login again to get the
# User object.

# TODO: this should not be done here
# session['lang'] = request.environ.get('CKAN_LANG')
# session.save()

# This occurs when a user that was still logged in is deleted, or
# when you are logged in, clean db and then restart (or when you
# change your username). There is no user object, so even though
# repoze thinks you are logged in and your cookie has
# ckan_display_name, we need to force user to logout and login
# again to get the User object.

ev = request.environ
if 'repoze.who.plugins' in ev:
pth = getattr(ev['repoze.who.plugins']['friendlyform'],
Expand Down
10 changes: 10 additions & 0 deletions ckanext/example_flask_iroutes/plugin.py
Expand Up @@ -14,6 +14,14 @@ def override_pylons_about():
return render_template('about.html')


def override_pylons_about_with_core_template():
'''
Override the pylons about controller to render the core about page
template.
'''
return render_template('home/about.html')


def override_flask_hello():
'''A simple replacement for the flash Hello view function.'''
html = '''<!DOCTYPE html>
Expand Down Expand Up @@ -75,6 +83,8 @@ def get_blueprint(self):
rules = [
('/hello_plugin', 'hello_plugin', hello_plugin),
('/about', 'about', override_pylons_about),
('/about_core', 'about_core',
override_pylons_about_with_core_template),
('/hello', 'hello', override_flask_hello),
('/helper_not_here', 'helper_not_here', helper_not_here),
('/helper', 'helper_here', helper_here),
Expand Down
5 changes: 5 additions & 0 deletions ckanext/example_flask_iroutes/templates/about_base.html
@@ -0,0 +1,5 @@
{% extends "page.html" %}

{% block page %}
This about page extends from page.html
{% endblock page %}
5 changes: 0 additions & 5 deletions ckanext/example_flask_iroutes/tests/test_routes.py
Expand Up @@ -10,11 +10,6 @@ def setup(self):
self.app = helpers._get_test_app()
flask_app = helpers.find_flask_app(self.app)

# Blueprints can't be registered after the app has been setup. For
# some reason, if debug is True, the app will have exited its initial
# state, and can't have new registrations. Set debug=False to ensure
# we can continue to register blueprints.
flask_app.debug = False
# Install plugin and register its blueprint
if not plugins.plugin_loaded('example_flask_iroutes'):
plugins.load('example_flask_iroutes')
Expand Down
1 change: 1 addition & 0 deletions setup.py
Expand Up @@ -161,6 +161,7 @@
'test_datastore_view = ckan.tests.lib.test_datapreview:MockDatastoreBasedResourceView',
'test_datapusher_plugin = ckanext.datapusher.tests.test_interfaces:FakeDataPusherPlugin',
'test_routing_plugin = ckan.tests.config.test_middleware:MockRoutingPlugin',
'test_flash_plugin = ckan.tests.config.test_sessions:FlashMessagePlugin',
'test_helpers_plugin = ckan.tests.lib.test_helpers:TestHelpersPlugin',
],
'babel.extractors': [
Expand Down

0 comments on commit 5cb2ca4

Please sign in to comment.