# encoding: utf-8

import os
import re

from pylons.wsgiapp import PylonsApp

from beaker.middleware import CacheMiddleware, SessionMiddleware
from paste.cascade import Cascade
from paste.registry import RegistryManager
from paste.urlparser import StaticURLParser
from paste.deploy.converters import asbool
from paste.fileapp import _FileIter
from pylons.middleware import ErrorHandler, StatusCodeRedirect
from routes.middleware import RoutesMiddleware
from repoze.who.config import WhoConfig
from repoze.who.middleware import PluggableAuthenticationMiddleware
from fanstatic import Fanstatic

from ckan.plugins import PluginImplementations
from ckan.plugins.interfaces import IMiddleware
import ckan.lib.uploader as uploader
from ckan.config.middleware import common_middleware
from ckan.common import config

import logging
log = logging.getLogger(__name__)


def make_pylons_stack(conf, full_stack=True, static_files=True,
                      **app_conf):
    """Create a Pylons WSGI application and return it

    ``conf``
        The inherited configuration for this application. Normally from
        the [DEFAULT] section of the Paste ini file.

    ``full_stack``
        Whether this application provides a full WSGI stack (by default,
        meaning it handles its own exceptions and errors). Disable
        full_stack when this application is "managed" by another WSGI
        middleware.

    ``static_files``
        Whether this application serves its own static files; disable
        when another web server is responsible for serving them.

    ``app_conf``
        The application's local configuration. Normally specified in
        the [app:<name>] section of the Paste ini file (where <name>
        defaults to main).

    """
    # The Pylons WSGI app
    app = pylons_app = CKANPylonsApp()

    for plugin in PluginImplementations(IMiddleware):
        app = plugin.make_middleware(app, config)

    app = common_middleware.CloseWSGIInputMiddleware(app, config)
    app = common_middleware.RootPathMiddleware(app, config)
    # Routing/Session/Cache Middleware
    app = RoutesMiddleware(app, config['routes.map'])
    # we want to be able to retrieve the routes middleware to be able to update
    # the mapper.  We store it in the pylons config to allow this.
    config['routes.middleware'] = app
    app = SessionMiddleware(app, config)
    app = CacheMiddleware(app, config)

    # CUSTOM MIDDLEWARE HERE (filtered by error handling middlewares)
    # app = QueueLogMiddleware(app)
    if asbool(config.get('ckan.use_pylons_response_cleanup_middleware',
                         True)):
        app = execute_on_completion(app, config,
                                    cleanup_pylons_response_string)

    # Fanstatic
    fanstatic_enable_rollup = asbool(app_conf.get('fanstatic_enable_rollup',
                                                  False))
    if asbool(config.get('debug', False)):
        fanstatic_config = {
            'versioning': True,
            'recompute_hashes': True,
            'minified': False,
            'bottom': True,
            'bundle': False,
            'rollup': fanstatic_enable_rollup,
        }
    else:
        fanstatic_config = {
            'versioning': True,
            'recompute_hashes': False,
            'minified': True,
            'bottom': True,
            'bundle': True,
            'rollup': fanstatic_enable_rollup,
        }
    root_path = config.get('ckan.root_path', None)
    if root_path:
        root_path = re.sub('/{{LANG}}', '', root_path)
        fanstatic_config['base_url'] = root_path
    app = Fanstatic(app, **fanstatic_config)

    for plugin in PluginImplementations(IMiddleware):
        try:
            app = plugin.make_error_log_middleware(app, config)
        except AttributeError:
            log.critical('Middleware class {0} is missing the method'
                         'make_error_log_middleware.'
                         .format(plugin.__class__.__name__))

    if asbool(full_stack):
        # Handle Python exceptions
        app = ErrorHandler(app, conf, **config['pylons.errorware'])

        # Display error documents for 400, 403, 404 status codes (and
        # 500 when debug is disabled)
        if asbool(config['debug']):
            app = StatusCodeRedirect(app, [400, 403, 404])
        else:
            app = StatusCodeRedirect(app, [400, 403, 404, 500])

    # Initialize repoze.who
    who_parser = WhoConfig(conf['here'])
    who_parser.parse(open(app_conf['who.config_file']))

    app = PluggableAuthenticationMiddleware(
        app,
        who_parser.identifiers,
        who_parser.authenticators,
        who_parser.challengers,
        who_parser.mdproviders,
        who_parser.request_classifier,
        who_parser.challenge_decider,
        logging.getLogger('repoze.who'),
        logging.WARN,  # ignored
        who_parser.remote_user_key
    )

    # Establish the Registry for this application
    # The RegistryManager includes code to pop
    # registry values after the stream has completed,
    # so we need to prevent this with `streaming` set to True.
    app = RegistryManager(app, streaming=True)

    if asbool(static_files):
        # Serve static files
        static_max_age = None if not asbool(
            config.get('ckan.cache_enabled')) \
            else int(config.get('ckan.static_max_age', 3600))

        static_app = StaticURLParser(
            config['pylons.paths']['static_files'],
            cache_max_age=static_max_age)
        static_parsers = [static_app, app]

        storage_directory = uploader.get_storage_path()
        if storage_directory:
            path = os.path.join(storage_directory, 'storage')
            try:
                os.makedirs(path)
            except OSError as e:
                # errno 17 is file already exists
                if e.errno != 17:
                    raise

            storage_app = StaticURLParser(path, cache_max_age=static_max_age)
            static_parsers.insert(0, storage_app)

        # Configurable extra static file paths
        extra_static_parsers = []
        for public_path in config.get(
                'extra_public_paths', '').split(','):
            if public_path.strip():
                extra_static_parsers.append(
                    StaticURLParser(public_path.strip(),
                                    cache_max_age=static_max_age)
                )
        app = Cascade(extra_static_parsers + static_parsers)

    # Tracking
    if asbool(config.get('ckan.tracking_enabled', 'false')):
        app = common_middleware.TrackingMiddleware(app, config)

    # Add a reference to the actual Pylons app so it's easier to access
    app._wsgi_app = pylons_app

    return app


class CKANPylonsApp(PylonsApp):

    app_name = 'pylons_app'

    def can_handle_request(self, environ):
        '''
        Decides whether it can handle a request with the Pylons app by
        matching the request environ against the route mapper

        Returns (True, 'pylons_app', origin) if this is the case.

        origin can be either 'core' or 'extension' depending on where
        the route was defined.

        NOTE: There is currently a catch all route for GET requests to
        point arbitrary urls to templates with the same name:

            map.connect('/*url', controller='template', action='view')

        This means that this function will match all GET requests. This
        does not cause issues as the Pylons core routes are the last to
        take precedence so the current behaviour is kept, but it's worth
        keeping in mind.
        '''

        pylons_mapper = config['routes.map']
        match_route = pylons_mapper.routematch(environ=environ)
        if match_route:
            match, route = match_route
            origin = 'core'
            if hasattr(route, '_ckan_core') and not route._ckan_core:
                origin = 'extension'
            log.debug('Pylons route match: {0} Origin: {1}'.format(
                match, origin))
            return (True, self.app_name, origin)
        else:
            return (False, self.app_name)


class CloseCallbackWrapper(object):
    def __init__(self, iterable, callback, environ):
        # pylons.fileapp expects app_iter to have `file` attribute.
        self.file = iterable
        self.callback = callback
        self.environ = environ

    def __iter__(self):
        """
        return a generator that passes through items from iterable
        then calls callback(environ).
        """
        try:
            for item in self.file:
                yield item
        except GeneratorExit:
            if hasattr(self.file, 'close'):
                self.file.close()
            raise
        finally:
            self.callback(self.environ)


class FileIterWrapper(CloseCallbackWrapper, _FileIter):
    """Same CloseCallbackWrapper, just with _FileIter mixin.

    That will prevent pylons from converting file responses into
    in-memori lists.
    """
    pass


def execute_on_completion(application, config, callback):
    """
    Call callback(environ) once complete response is sent
    """

    def inner(environ, start_response):
        try:
            result = application(environ, start_response)
        except:
            callback(environ)
            raise
        # paste.fileapp converts non-file responses into list
        # In order to avoid interception of OOM Killer
        # file responses wrapped into generator with
        # _FileIter in parent tree.
        klass = CloseCallbackWrapper
        if isinstance(result, _FileIter):
            klass = FileIterWrapper
        return klass(result, callback, environ)

    return inner


def cleanup_pylons_response_string(environ):
    try:
        msg = 'response cleared by pylons response cleanup middleware'
        environ['pylons.controller']._py_object.response._body = msg
    except (KeyError, AttributeError):
        pass

ckan/config/middleware/pylons_app.py
# encoding: utf-8

import os
import re

from pylons.wsgiapp import PylonsApp

from beaker.middleware import CacheMiddleware, SessionMiddleware
from paste.cascade import Cascade
from paste.registry import RegistryManager
from paste.urlparser import StaticURLParser
from paste.deploy.converters import asbool
from paste.fileapp import _FileIter
from pylons.middleware import ErrorHandler, StatusCodeRedirect
from routes.middleware import RoutesMiddleware
from repoze.who.config import WhoConfig
from repoze.who.middleware import PluggableAuthenticationMiddleware
from fanstatic import Fanstatic

from ckan.plugins import PluginImplementations
from ckan.plugins.interfaces import IMiddleware
import ckan.lib.uploader as uploader
from ckan.config.middleware import common_middleware
from ckan.common import config

import logging
log = logging.getLogger(__name__)


def make_pylons_stack(conf, full_stack=True, static_files=True,
                      **app_conf):
    """Create a Pylons WSGI application and return it

    ``conf``
        The inherited configuration for this application. Normally from
        the [DEFAULT] section of the Paste ini file.

    ``full_stack``
        Whether this application provides a full WSGI stack (by default,
        meaning it handles its own exceptions and errors). Disable
        full_stack when this application is "managed" by another WSGI
        middleware.

    ``static_files``
        Whether this application serves its own static files; disable
        when another web server is responsible for serving them.

    ``app_conf``
        The application's local configuration. Normally specified in
        the [app:<name>] section of the Paste ini file (where <name>
        defaults to main).

    """
    # The Pylons WSGI app
    app = pylons_app = CKANPylonsApp()

    for plugin in PluginImplementations(IMiddleware):
        app = plugin.make_middleware(app, config)

    app = common_middleware.CloseWSGIInputMiddleware(app, config)
    app = common_middleware.RootPathMiddleware(app, config)
    # Routing/Session/Cache Middleware
    app = RoutesMiddleware(app, config['routes.map'])
    # we want to be able to retrieve the routes middleware to be able to update
    # the mapper.  We store it in the pylons config to allow this.
    config['routes.middleware'] = app
    app = SessionMiddleware(app, config)
    app = CacheMiddleware(app, config)

    # CUSTOM MIDDLEWARE HERE (filtered by error handling middlewares)
    # app = QueueLogMiddleware(app)
    if asbool(config.get('ckan.use_pylons_response_cleanup_middleware',
                         True)):
        app = execute_on_completion(app, config,
                                    cleanup_pylons_response_string)

    # Fanstatic
    fanstatic_enable_rollup = asbool(app_conf.get('fanstatic_enable_rollup',
                                                  False))
    if asbool(config.get('debug', False)):
        fanstatic_config = {
            'versioning': True,
            'recompute_hashes': True,
            'minified': False,
            'bottom': True,
            'bundle': False,
            'rollup': fanstatic_enable_rollup,
        }
    else:
        fanstatic_config = {
            'versioning': True,
            'recompute_hashes': False,
            'minified': True,
            'bottom': True,
            'bundle': True,
            'rollup': fanstatic_enable_rollup,
        }
    root_path = config.get('ckan.root_path', None)
    if root_path:
        root_path = re.sub('/{{LANG}}', '', root_path)
        fanstatic_config['base_url'] = root_path
    app = Fanstatic(app, **fanstatic_config)

    for plugin in PluginImplementations(IMiddleware):
        try:
            app = plugin.make_error_log_middleware(app, config)
        except AttributeError:
            log.critical('Middleware class {0} is missing the method'
                         'make_error_log_middleware.'
                         .format(plugin.__class__.__name__))

    if asbool(full_stack):
        # Handle Python exceptions
        app = ErrorHandler(app, conf, **config['pylons.errorware'])

        # Display error documents for 400, 403, 404 status codes (and
        # 500 when debug is disabled)
        if asbool(config['debug']):
            app = StatusCodeRedirect(app, [400, 403, 404])
        else:
            app = StatusCodeRedirect(app, [400, 403, 404, 500])

    # Initialize repoze.who
    who_parser = WhoConfig(conf['here'])
    who_parser.parse(open(app_conf['who.config_file']))

    app = PluggableAuthenticationMiddleware(
        app,
        who_parser.identifiers,
        who_parser.authenticators,
        who_parser.challengers,
        who_parser.mdproviders,
        who_parser.request_classifier,
        who_parser.challenge_decider,
        logging.getLogger('repoze.who'),
        logging.WARN,  # ignored
        who_parser.remote_user_key
    )

    # Establish the Registry for this application
    app = RegistryManager(app, streaming=False)

    if asbool(static_files):
        # Serve static files
        static_max_age = None if not asbool(
            config.get('ckan.cache_enabled')) \
            else int(config.get('ckan.static_max_age', 3600))

        static_app = StaticURLParser(
            config['pylons.paths']['static_files'],
            cache_max_age=static_max_age)
        static_parsers = [static_app, app]

        storage_directory = uploader.get_storage_path()
        if storage_directory:
            path = os.path.join(storage_directory, 'storage')
            try:
                os.makedirs(path)
            except OSError as e:
                # errno 17 is file already exists
                if e.errno != 17:
                    raise

            storage_app = StaticURLParser(path, cache_max_age=static_max_age)
            static_parsers.insert(0, storage_app)

        # Configurable extra static file paths
        extra_static_parsers = []
        for public_path in config.get(
                'extra_public_paths', '').split(','):
            if public_path.strip():
                extra_static_parsers.append(
                    StaticURLParser(public_path.strip(),
                                    cache_max_age=static_max_age)
                )
        app = Cascade(extra_static_parsers + static_parsers)

    # Tracking
    if asbool(config.get('ckan.tracking_enabled', 'false')):
        app = common_middleware.TrackingMiddleware(app, config)

    # Add a reference to the actual Pylons app so it's easier to access
    app._wsgi_app = pylons_app

    return app


class CKANPylonsApp(PylonsApp):

    app_name = 'pylons_app'

    def can_handle_request(self, environ):
        '''
        Decides whether it can handle a request with the Pylons app by
        matching the request environ against the route mapper

        Returns (True, 'pylons_app', origin) if this is the case.

        origin can be either 'core' or 'extension' depending on where
        the route was defined.

        NOTE: There is currently a catch all route for GET requests to
        point arbitrary urls to templates with the same name:

            map.connect('/*url', controller='template', action='view')

        This means that this function will match all GET requests. This
        does not cause issues as the Pylons core routes are the last to
        take precedence so the current behaviour is kept, but it's worth
        keeping in mind.
        '''

        pylons_mapper = config['routes.map']
        match_route = pylons_mapper.routematch(environ=environ)
        if match_route:
            match, route = match_route
            origin = 'core'
            if hasattr(route, '_ckan_core') and not route._ckan_core:
                origin = 'extension'
            log.debug('Pylons route match: {0} Origin: {1}'.format(
                match, origin))
            return (True, self.app_name, origin)
        else:
            return (False, self.app_name)


class CloseCallbackWrapper(object):
    def __init__(self, iterable, callback, environ):
        # pylons.fileapp expects app_iter to have `file` attribute.
        self.file = iterable
        self.callback = callback
        self.environ = environ

    def __iter__(self):
        """
        return a generator that passes through items from iterable
        then calls callback(environ).
        """
        try:
            for item in self.file:
                yield item
        except GeneratorExit:
            if hasattr(self.file, 'close'):
                self.file.close()
            raise
        finally:
            self.callback(self.environ)


class FileIterWrapper(CloseCallbackWrapper, _FileIter):
    """Same CloseCallbackWrapper, just with _FileIter mixin.

    That will prevent pylons from converting file responses into
    in-memori lists.
    """
    pass


def execute_on_completion(application, config, callback):
    """
    Call callback(environ) once complete response is sent
    """

    def inner(environ, start_response):
        try:
            result = application(environ, start_response)
        except:
            callback(environ)
            raise
        # paste.fileapp converts non-file responses into list
        # In order to avoid interception of OOM Killer
        # file responses wrapped into generator with
        # _FileIter in parent tree.
        klass = CloseCallbackWrapper
        if isinstance(result, _FileIter):
            klass = FileIterWrapper
        return klass(result, callback, environ)

    return inner


def cleanup_pylons_response_string(environ):
    try:
        msg = 'response cleared by pylons response cleanup middleware'
        environ['pylons.controller']._py_object.response._body = msg
    except (KeyError, AttributeError):
        pass

ckan/config/middleware/pylons_app.py
diff --git a/ckan/config/middleware/pylons_app.py b/ckan/config/middleware/pylons_app.py
index 73b714509a7..c9fb2ed8f9a 100644
--- a/ckan/config/middleware/pylons_app.py
+++ b/ckan/config/middleware/pylons_app.py
@@ -138,10 +138,7 @@ def make_pylons_stack(conf, full_stack=True, static_files=True,
     )
 
     # Establish the Registry for this application
-    # The RegistryManager includes code to pop
-    # registry values after the stream has completed,
-    # so we need to prevent this with `streaming` set to True.
-    app = RegistryManager(app, streaming=True)
+    app = RegistryManager(app, streaming=False)
 
     if asbool(static_files):
         # Serve static files

exception after upgrade to 2.8.1 - Popped wrong app context
After upgrade from 2.7 to 2.8.1 - I'm seeing some exceptions in a production server log, couldn't reproduce the error locally

### CKAN Version if known (or site URL)
2.8.1

### Please describe the expected behaviour
no exception (or less cryptic exception..)

### Please describe the actual behaviour
exception - 
```
-------------------------------------------------------------
Error - <type 'exceptions.AssertionError'>: Popped wrong app context.  (<flask.ctx.AppContext object at 0x7f3ac4b90b10> instead of <flask.ctx.AppContext object at 0x7f3ac4b90690>)
URL: http://www.odata.org.il/organization/hatzlacha?license_id=cc-by&tags=%D7%94%D7%AA%D7%A4%D7%A8%D7%A6%D7%95%D7%AA+%D7%9C%D7%9E%D7%A7%D7%95%D7%9D+%D7%9E%D7%92%D7%95%D7%A8%D7%99%D7%9D&organization=hatzlacha&res_format=DOC&tags=%D7%A2%D7%99%D7%9B%D7%95%D7%91+%D7%94%D7%9C%D7%99%D7%9B%D7%99%D7%9D
File '/usr/lib/ckan/venv/local/lib/python2.7/site-packages/weberror/errormiddleware.py', line 171 in __call__
  app_iter = self.application(environ, sr_checker)
File '/usr/lib/ckan/venv/local/lib/python2.7/site-packages/webob/dec.py', line 147 in __call__
  resp = self.call_func(req, *args, **self.kwargs)
File '/usr/lib/ckan/venv/local/lib/python2.7/site-packages/webob/dec.py', line 208 in call_func
  return self.func(req, *args, **kwargs)
File '/usr/lib/ckan/venv/local/lib/python2.7/site-packages/fanstatic/publisher.py', line 234 in __call__
  return request.get_response(self.app)
File '/usr/lib/ckan/venv/local/lib/python2.7/site-packages/webob/request.py', line 1053 in get_response
  application, catch_exc_info=False)
File '/usr/lib/ckan/venv/local/lib/python2.7/site-packages/webob/request.py', line 1022 in call_application
  app_iter = application(self.environ, start_response)
File '/usr/lib/ckan/venv/local/lib/python2.7/site-packages/webob/dec.py', line 147 in __call__
  resp = self.call_func(req, *args, **self.kwargs)
File '/usr/lib/ckan/venv/local/lib/python2.7/site-packages/webob/dec.py', line 208 in call_func
  return self.func(req, *args, **kwargs)
File '/usr/lib/ckan/venv/local/lib/python2.7/site-packages/fanstatic/injector.py', line 54 in __call__
  response = request.get_response(self.app)
File '/usr/lib/ckan/venv/local/lib/python2.7/site-packages/webob/request.py', line 1053 in get_response
  application, catch_exc_info=False)
File '/usr/lib/ckan/venv/local/lib/python2.7/site-packages/webob/request.py', line 1022 in call_application
  app_iter = application(self.environ, start_response)
File '/usr/lib/ckan/venv/src/ckan/ckan/config/middleware/pylons_app.py', line 265 in inner
  result = application(environ, start_response)
File '/usr/lib/ckan/venv/local/lib/python2.7/site-packages/beaker/middleware.py', line 73 in __call__
  return self.app(environ, start_response)
File '/usr/lib/ckan/venv/local/lib/python2.7/site-packages/beaker/middleware.py', line 156 in __call__
  return self.wrap_app(environ, session_start_response)
File '/usr/lib/ckan/venv/local/lib/python2.7/site-packages/routes/middleware.py', line 131 in __call__
  response = self.app(environ, start_response)
File '/usr/lib/ckan/venv/src/ckan/ckan/config/middleware/common_middleware.py', line 30 in __call__
  return self.app(environ, start_response)
File '/usr/lib/ckan/venv/src/ckan/ckan/config/middleware/common_middleware.py', line 56 in __call__
  return self.app(environ, start_response)
File '/usr/lib/ckan/venv/local/lib/python2.7/site-packages/pylons/wsgiapp.py', line 125 in __call__
  response = self.dispatch(controller, environ, start_response)
File '/usr/lib/ckan/venv/local/lib/python2.7/site-packages/pylons/wsgiapp.py', line 324 in dispatch
  return controller(environ, start_response)
File '/usr/lib/ckan/venv/src/ckan/ckan/lib/base.py', line 240 in __call__
  res = WSGIController.__call__(self, environ, start_response)
File '/usr/lib/ckan/venv/local/lib/python2.7/site-packages/pylons/controllers/core.py', line 221 in __call__
  response = self._dispatch_call()
File '/usr/lib/ckan/venv/local/lib/python2.7/site-packages/pylons/controllers/core.py', line 172 in _dispatch_call
  response = self._inspect_call(func)
File '/usr/lib/ckan/venv/local/lib/python2.7/site-packages/pylons/controllers/core.py', line 107 in _inspect_call
  result = self._perform_call(func, args)
File '/usr/lib/ckan/venv/local/lib/python2.7/site-packages/pylons/controllers/core.py', line 60 in _perform_call
  return func(**args)
File '/usr/lib/ckan/venv/src/ckan/ckan/controllers/group.py', line 230 in read
  extra_vars={'group_type': group_type})
File '/usr/lib/ckan/venv/src/ckan/ckan/lib/base.py', line 125 in render
  return cached_template(template_name, renderer)
File '/usr/lib/ckan/venv/local/lib/python2.7/site-packages/pylons/templating.py', line 249 in cached_template
  return render_func()
File '/usr/lib/ckan/venv/src/ckan/ckan/lib/base.py', line 162 in render_template
  return render_jinja2(template_name, globs)
File '/usr/lib/ckan/venv/src/ckan/ckan/lib/base.py', line 94 in render_jinja2
  return template.render(**extra_vars)
File '/usr/lib/ckan/venv/local/lib/python2.7/site-packages/jinja2/environment.py', line 989 in render
  return self.environment.handle_exception(exc_info, True)
File '/usr/lib/ckan/venv/local/lib/python2.7/site-packages/jinja2/environment.py', line 754 in handle_exception
  reraise(exc_type, exc_value, tb)
File '/usr/lib/ckan/venv/src/ckan/ckan/templates/organization/read.html', line 1 in top-level template code
  {% extends "organization/read_base.html" %}
File '/usr/lib/ckan/venv/src/ckan/ckan/templates/organization/read_base.html', line 1 in top-level template code
  {% extends "page.html" %}
File '/usr/lib/ckan/venv/src/ckan/ckan/templates/page.html', line 1 in top-level template code
  {% extends "base.html" %}
File '/ckanext-odata_org_il/ckanext/odata_org_il/templates/base.html', line 1 in top-level template code
  {% ckan_extends %}
File '/usr/lib/ckan/venv/src/ckan/ckan/templates/base.html', line 101 in top-level template code
  {%- block page %}{% endblock -%}
File '/usr/lib/ckan/venv/src/ckan/ckan/templates/page.html', line 125 in block "page"
  {%- block footer %}
File '/usr/lib/ckan/venv/src/ckan/ckan/templates/page.html', line 126 in block "footer"
  {% include "footer.html" %}
File '/ckanext-odata_org_il/ckanext/odata_org_il/templates/footer.html', line 3 in top-level template code
  {% block footer_content %}
File '/ckanext-odata_org_il/ckanext/odata_org_il/templates/footer.html', line 32 in block "footer_content"
  {% block footer_lang %}
File '/ckanext-odata_org_il/ckanext/odata_org_il/templates/footer.html', line 33 in block "footer_lang"
  {% snippet "snippets/language_selector.html" %}
File '/usr/lib/ckan/venv/src/ckan/ckan/lib/jinja_extensions.py', line 268 in _call
  return base.render_snippet(args[0], **kwargs)
File '/usr/lib/ckan/venv/src/ckan/ckan/lib/base.py', line 84 in render_snippet
  output = render(template_name, extra_vars=kw)
File '/usr/lib/ckan/venv/src/ckan/ckan/lib/base.py', line 125 in render
  return cached_template(template_name, renderer)
File '/usr/lib/ckan/venv/local/lib/python2.7/site-packages/pylons/templating.py', line 249 in cached_template
  return render_func()
File '/usr/lib/ckan/venv/src/ckan/ckan/lib/base.py', line 162 in render_template
  return render_jinja2(template_name, globs)
File '/usr/lib/ckan/venv/src/ckan/ckan/lib/base.py', line 94 in render_jinja2
  return template.render(**extra_vars)
File '/usr/lib/ckan/venv/local/lib/python2.7/site-packages/jinja2/environment.py', line 989 in render
  return self.environment.handle_exception(exc_info, True)
File '/usr/lib/ckan/venv/local/lib/python2.7/site-packages/jinja2/environment.py', line 754 in handle_exception
  reraise(exc_type, exc_value, tb)
File '/usr/lib/ckan/venv/src/ckan/ckan/templates/snippets/language_selector.html', line 2 in top-level template code
  <form class="form-inline form-select lang-select" action="{% url_for controller='util', action='redirect' %}" data-module="select-switch" method="POST">
File '/usr/lib/ckan/venv/src/ckan/ckan/lib/jinja_extensions.py', line 297 in _call
  return h.url_for(*args, **kwargs)
File '/usr/lib/ckan/venv/src/ckan/ckan/lib/helpers.py', line 326 in url_for
  _auto_flask_context.pop()
File '/usr/lib/ckan/venv/local/lib/python2.7/site-packages/flask/ctx.py', line 376 in pop
  app_ctx.pop(exc)
File '/usr/lib/ckan/venv/local/lib/python2.7/site-packages/flask/ctx.py', line 193 in pop
  % (rv, self)
AssertionError: Popped wrong app context.  (<flask.ctx.AppContext object at 0x7f3ac4b90b10> instead of <flask.ctx.AppContext object at 0x7f3ac4b90690>)


CGI Variables
-------------
  CKAN_CURRENT_URL: '/organization/hatzlacha?license_id%3Dcc-by%26tags%3D%25D7%2594%25D7%25AA%25D7%25A4%25D7%25A8%25D7%25A6%25D7%2595%25D7%25AA%2B%25D7%259C%25D7%259E%25D7%25A7%25D7%2595%25D7%259D%2B%25D7%259E%25D7%2592%25D7%2595%25D7%25A8%25D7%2599%25D7%259D%26organization%3Dhatzlacha%26res_format%3DDOC%26tags%3D%25D7%25A2%25D7%2599%25D7%259B%25D7%2595%25D7%2591%2B%25D7%2594%25D7%259C%25D7%2599%25D7%259B%25D7%2599%25D7%259D'
  CKAN_LANG: 'he'
  CKAN_LANG_IS_DEFAULT: True
  CONTENT_LENGTH: '0'
  HTTP_ACCEPT: '*/*'
  HTTP_ACCEPT_ENCODING: 'gzip'
  HTTP_CF_CONNECTING_IP: ' --- '
  HTTP_CF_IPCOUNTRY: 'US'
  HTTP_CF_RAY: '450eef3ef9fa5afd-HEL'
  HTTP_CF_VISITOR: '{"scheme":"https"}'
  HTTP_HOST: 'www.odata.org.il'
  HTTP_USER_AGENT: ' --- '
  HTTP_X_FORWARDED_FOR: ' --- '
  HTTP_X_FORWARDED_HOST: 'www.odata.org.il'
  HTTP_X_FORWARDED_PORT: '443'
  HTTP_X_FORWARDED_PROTO: 'https'
  HTTP_X_FORWARDED_SERVER: 'traefik-77ccf967dc-4dfwt'
  HTTP_X_REAL_IP: '10.132.0.5'
  PATH_INFO: '/organization/hatzlacha'
  QUERY_STRING: 'license_id=cc-by&tags=%D7%94%D7%AA%D7%A4%D7%A8%D7%A6%D7%95%D7%AA+%D7%9C%D7%9E%D7%A7%D7%95%D7%9D+%D7%9E%D7%92%D7%95%D7%A8%D7%99%D7%9D&organization=hatzlacha&res_format=DOC&tags=%D7%A2%D7%99%D7%9B%D7%95%D7%91+%D7%94%D7%9C%D7%99%D7%9B%D7%99%D7%9D'
  REMOTE_ADDR: '10.20.1.228'
  REQUEST_METHOD: 'GET'
  SERVER_NAME: '0.0.0.0'
  SERVER_PORT: '5000'
  SERVER_PROTOCOL: 'HTTP/1.1'


WSGI Variables
--------------
  __no_cache__: True
  application: <fanstatic.publisher.Delegator object at 0x7f3ac38876d0>
  beaker.cache: <beaker.cache.CacheManager object at 0x7f3ac3887690>
  beaker.get_session: <bound method SessionMiddleware._get_session of <beaker.middleware.SessionMiddleware object at 0x7f3ac38875d0>>
  beaker.session: {'_accessed_time': 1535377065.228329, '_creation_time': 1535377065.228329}
  ckan.app: 'pylons_app'
  fanstatic.needed: <fanstatic.core.NeededResources object at 0x7f3aa9ff7450>
  paste.cookies: (<SimpleCookie: >, '')
  paste.httpserver.thread_pool: <paste.httpserver.ThreadPool object at 0x7f3ac4b8e910>
  paste.parsed_dict_querystring: (MultiDict([('license_id', 'cc-by'), ('tags', '\xd7\x94\xd7\xaa\xd7\xa4\xd7\xa8\xd7\xa6\xd7\x95\xd7\xaa \xd7\x9c\xd7\x9e\xd7\xa7\xd7\x95\xd7\x9d \xd7\x9e\xd7\x92\xd7\x95\xd7\xa8\xd7\x99\xd7\x9d'), ('organization', 'hatzlacha'), ('res_format', 'DOC'), ('tags', '\xd7\xa2\xd7\x99\xd7\x9b\xd7\x95\xd7\x91 \xd7\x94\xd7\x9c\xd7\x99\xd7\x9b\xd7\x99\xd7\x9d')]), 'license_id=cc-by&tags=%D7%94%D7%AA%D7%A4%D7%A8%D7%A6%D7%95%D7%AA+%D7%9C%D7%9E%D7%A7%D7%95%D7%9D+%D7%9E%D7%92%D7%95%D7%A8%D7%99%D7%9D&organization=hatzlacha&res_format=DOC&tags=%D7%A2%D7%99%D7%9B%D7%95%D7%91+%D7%94%D7%9C%D7%99%D7%9B%D7%99%D7%9D')
  paste.parsed_querystring: ([('license_id', 'cc-by'), ('tags', '\xd7\x94\xd7\xaa\xd7\xa4\xd7\xa8\xd7\xa6\xd7\x95\xd7\xaa \xd7\x9c\xd7\x9e\xd7\xa7\xd7\x95\xd7\x9d \xd7\x9e\xd7\x92\xd7\x95\xd7\xa8\xd7\x99\xd7\x9d'), ('organization', 'hatzlacha'), ('res_format', 'DOC'), ('tags', '\xd7\xa2\xd7\x99\xd7\x9b\xd7\x95\xd7\x91 \xd7\x94\xd7\x9c\xd7\x99\xd7\x9b\xd7\x99\xd7\x9d')], 'license_id=cc-by&tags=%D7%94%D7%AA%D7%A4%D7%A8%D7%A6%D7%95%D7%AA+%D7%9C%D7%9E%D7%A7%D7%95%D7%9D+%D7%9E%D7%92%D7%95%D7%A8%D7%99%D7%9D&organization=hatzlacha&res_format=DOC&tags=%D7%A2%D7%99%D7%9B%D7%95%D7%91+%D7%94%D7%9C%D7%99%D7%9B%D7%99%D7%9D')
  paste.registry: <paste.registry.Registry object at 0x7f3ac3f14450>
  paste.throw_errors: True
  pylons.action_method: <bound method OrganizationController.read of <ckan.controllers.organization.OrganizationController object at 0x7f3aa8826b10>>
  pylons.controller: <ckan.controllers.organization.OrganizationController object at 0x7f3aa8826b10>
  pylons.environ_config: {'session': 'beaker.session', 'cache': 'beaker.cache'}
  pylons.pylons: <pylons.util.PylonsContext object at 0x7f3aa8826d50>
  pylons.routes_dict: {'action': u'read', 'controller': u'organization', 'id': u'hatzlacha'}
  repoze.who.api: <repoze.who.api.API object at 0x7f3ac37da0d0>
  repoze.who.logger: <logging.Logger object at 0x7f3ac3887890>
  repoze.who.plugins: {'ckan.lib.authenticator:UsernamePasswordAuthenticator': <ckan.lib.authenticator.UsernamePasswordAuthenticator object at 0x7f3ac3acd090>, 'friendlyform': <FriendlyFormPlugin 139890367716112>, 'auth_tkt': <CkanAuthTktCookiePlugin 139890367715984>}
  routes.route: <routes.route.Route object at 0x7f3ac4b13f10>
  routes.url: <routes.util.URLGenerator object at 0x7f3ac40d9690>
  webob._parsed_query_vars: (GET([('license_id', 'cc-by'), ('tags', '\xd7\x94\xd7\xaa\xd7\xa4\xd7\xa8\xd7\xa6\xd7\x95\xd7\xaa \xd7\x9c\xd7\x9e\xd7\xa7\xd7\x95\xd7\x9d \xd7\x9e\xd7\x92\xd7\x95\xd7\xa8\xd7\x99\xd7\x9d'), ('organization', 'hatzlacha'), ('res_format', 'DOC'), ('tags', '\xd7\xa2\xd7\x99\xd7\x9b\xd7\x95\xd7\x91 \xd7\x94\xd7\x9c\xd7\x99\xd7\x9b\xd7\x99\xd7\x9d')]), 'license_id=cc-by&tags=%D7%94%D7%AA%D7%A4%D7%A8%D7%A6%D7%95%D7%AA+%D7%9C%D7%9E%D7%A7%D7%95%D7%9D+%D7%9E%D7%92%D7%95%D7%A8%D7%99%D7%9D&organization=hatzlacha&res_format=DOC&tags=%D7%A2%D7%99%D7%9B%D7%95%D7%91+%D7%94%D7%9C%D7%99%D7%9B%D7%99%D7%9D')
  webob.adhoc_attrs: {'response': <Response at 0x7f3aa9ff7990 200 OK>, 'language': 'en-us'}
  wsgi process: 'Multithreaded'
  wsgiorg.routing_args: (<routes.util.URLGenerator object at 0x7f3ac40d9690>, {'action': u'read', 'controller': u'organization', 'id': u'hatzlacha'})
------------------------------------------------------------
```

### What steps can be taken to reproduce the issue? 
I'm just seeing those exception in the server logs, couldn't reproduce locally
