tornador UI module 修改 js 嵌入位置

July 2, 2015

起因

因为用到 vue.js, 然后 UI module 里面定义了一些 vue.js 的 Directives, 必须要在 body 以前将 Directives 定义出来.

但是 tornado 的 UI Module 默认是将 js append 到</body> 前的, 这时候为时已晚了.

怎么办呢!

为了不修改 tornador 的代码,只有继承并重载了.

js 的处理是在web.pyrender方法里面来做的, 是个非常大的方法, 这里并没有做拆分.我只有全部 copy 过来了

这是最终的实现版本

class BaseHandler(RequestHandler):

    '''
    create by bigzhu at 15/01/29 22:53:07 自定义一些基础的方法
        设置 pg
        设定 current_user 为 cookie user_id
    modify by bigzhu at 15/01/30 09:59:46 直接返回 user_info
    modify by bigzhu at 15/01/30 10:32:37 默认返回 user_info 的拆离出去
    modify by bigzhu at 15/02/21 00:41:23 修改 js_embed 的位置到 </head> 前
    '''

    def initialize(self):
        self.pg = self.settings![pic]('pg')

    def get_current_user(self):
        return self.get_secure_cookie("user_id")

    def render(self, template_name, **kwargs):
        """Renders the template with the given arguments as the response.
        create by bigzhu at 15/02/21 01:50:52 就是为了把embedded_javascript 的位置换一下
        """

        html = self.render_string(template_name, **kwargs)

        * Insert the additional JS and CSS added by the modules on the page
        js_embed = []
        js_files = []
        css_embed = []
        css_files = []
        html_heads = []
        html_bodies = []
        for module in getattr(self, "_active_modules", {}).values():
            embed_part = module.embedded_javascript()
            if embed_part:
                js_embed.append(utf8(embed_part))
            file_part = module.javascript_files()
            if file_part:
                if isinstance(file_part, (unicode_type, bytes_type)):
                    js_files.append(file_part)
                else:
                    js_files.extend(file_part)
            embed_part = module.embedded_css()
            if embed_part:
                css_embed.append(utf8(embed_part))
            file_part = module.css_files()
            if file_part:
                if isinstance(file_part, (unicode_type, bytes_type)):
                    css_files.append(file_part)
                else:
                    css_files.extend(file_part)
            head_part = module.html_head()
            if head_part:
                html_heads.append(utf8(head_part))
            body_part = module.html_body()
            if body_part:
                html_bodies.append(utf8(body_part))

        def is_absolute(path):
            return any(path.startswith(x) for x in ["/", "http:", "https:"])
        if js_files:
            * Maintain order of JavaScript files given by modules
            paths = []
            unique_paths = set()
            for path in js_files:
                if not is_absolute(path):
                    path = self.static_url(path)
                if path not in unique_paths:
                    paths.append(path)
                    unique_paths.add(path)
            js = ''.join('<script src="' + escape.xhtml_escape(p) +
                         '" type="text/javascript"></script>'
                         for p in paths)
            sloc = html.rindex(b'</body>')
            html = html![pic](:sloc) + utf8(js) + b'\n' + html![pic](sloc:)
        if js_embed:
            js = b'<script type="text/javascript">\n//<![CDATA[\n' + \
                b'\n'.join(js_embed) + b'\n//]]>\n</script>'
            sloc = html.rindex(b'</head>')
            html = html![pic](:sloc) + js + b'\n' + html![pic](sloc:)
        if css_files:
            paths = []
            unique_paths = set()
            for path in css_files:
                if not is_absolute(path):
                    path = self.static_url(path)
                if path not in unique_paths:
                    paths.append(path)
                    unique_paths.add(path)
            css = ''.join('<link href="' + escape.xhtml_escape(p) + '" '
                          'type="text/css" rel="stylesheet"/>'
                          for p in paths)
            hloc = html.index(b'</head>')
            html = html![pic](:hloc) + utf8(css) + b'\n' + html![pic](hloc:)
        if css_embed:
            css = b'<style type="text/css">\n' + b'\n'.join(css_embed) + \
                b'\n</style>'
            hloc = html.index(b'</head>')
            html = html![pic](:hloc) + css + b'\n' + html![pic](hloc:)
        if html_heads:
            hloc = html.index(b'</head>')
            html = html![pic](:hloc) + b''.join(html_heads) + b'\n' + html![pic](hloc:)
        if html_bodies:
            hloc = html.index(b'</body>')
            html = html![pic](:hloc) + b''.join(html_bodies) + b'\n' + html![pic](hloc:)
        self.finish(html)

    def render_string(self, template_name, **kwargs):
        """Generate the given template with the given arguments.

        We return the generated byte string (in utf8). To generate and
        write a template as a response, use render() above.

        create by bigzhu at 15/02/21 01:49:51 为了设定 _getframe,也得把这个方法重载一遍.否则 template 路径会按 bz 的来找

        """
        * If no template_path is specified, use the path of the calling file
        template_path = self.get_template_path()
        if not template_path:
            frame = sys._getframe(1)
            web_file = frame.f_code.co_filename
            while frame.f_code.co_filename == web_file:
                frame = frame.f_back
            template_path = os.path.dirname(frame.f_code.co_filename)
        with RequestHandler._template_loader_lock:
            if template_path not in RequestHandler._template_loaders:
                loader = self.create_template_loader(template_path)
                RequestHandler._template_loaders![pic](template_path) = loader
            else:
                loader = RequestHandler._template_loaders![pic](template_path)
        t = loader.load(template_name)
        namespace = self.get_template_namespace()
        namespace.update(kwargs)
        return t.generate(**namespace)

过程很纠结和复杂,慢慢来讲吧.

背景

因为我的BaseHandler并没有和具体项目放在一起,而是抽离出来作为公用的方法,加入到PYTHONPATH里面来使用了

重载 render

当我将render重载进来,并且简单的将里面的</body>修改为</head>

        if js_embed:
            js = b'<script type="text/javascript">\n//<![CDATA[\n' + \
                b'\n'.join(js_embed) + b'\n//]]>\n</script>'
            sloc = html.rindex(b'</head>')
            html = html![pic](:sloc) + js + b'\n' + html![pic](sloc:)

然后报错了!并不是因为这个修改报错,而是找不到模板报错:

Traceback (most recent call last):
  File "/Library/Python/2.7/site-packages/tornado/web.py", line 1332, in _execute
    result = method(*self.path_args, **self.path_kwargs)
  File "yemai.py", line 287, in get
    self.render(tornado_bz.getTName(self, 'sites'), sites=sites)
  File "/Users/bigzhu/Dropbox/lib/python_lib_bz/tornado_bz.py", line 44, in render
    html = self.render_string(template_name, **kwargs)
  File "/Library/Python/2.7/site-packages/tornado/web.py", line 769, in render_string
    t = loader.load(template_name)
  File "/Library/Python/2.7/site-packages/tornado/template.py", line 343, in load
    self.templates![pic](name) = self._create_template(name)
  File "/Library/Python/2.7/site-packages/tornado/template.py", line 370, in _create_template
    with open(path, "rb") as f:
IOError: [Errno 2] No such file or directory: '/Users/bigzhu/Dropbox/lib/python_lib_bz/template/sites.html'

因为重载了,导致并没有像以前一样得到的是template/sites.html从而根据不同项目能找到模板,反而定位到了我定义的公用的方法的路径里面.

简直囧,百思不得其解.

解决模板问题

仔细研读.模板是在render_string里面来组合的.

意外的发现setting可以设置template_path参数,妄图设置了来解决这个问题.于是 Module 的模板找不到了.

仔细思索了一下,需要实现以下功能 * 对于继承BaseHandler实现的方法,用这个项目下的路径 * 对于 module, 又得用绝对路径 发现使用奇怪的sys._getframe来取相对路径的,项目用的时候,取到的是”,空的,于是正常了.

原来是用栈的级别来解决这个问题的, 我拿出来重载了,层级不对,导致路径不对了

继续重载render_string,把sys._getframe(0)改为sys._getframe(1),搞定,一切正常!


comments powered by Disqus