# HG changeset patch # User Laurent Peuch # Date 1563977482 -7200 # Node ID 8caa109dfe945592a34904bb56971cc57d1c6084 # Parent 44147dab9d27907208119b3f198f58c601f30cd5 [debug] add in each html snippet from where it has been generated in the code Closes #17219704 diff -r 44147dab9d27 -r 8caa109dfe94 cubicweb/pyramid/pyramidctl.py --- a/cubicweb/pyramid/pyramidctl.py Thu Nov 28 11:48:03 2019 +0100 +++ b/cubicweb/pyramid/pyramidctl.py Wed Jul 24 16:11:22 2019 +0200 @@ -37,6 +37,7 @@ from cubicweb.cwctl import CWCTL, InstanceCommand, init_cmdline_log_threshold from cubicweb.pyramid import wsgi_application_from_cwconfig from cubicweb.pyramid.config import get_random_secret_key +from cubicweb.view import inject_html_generating_call_on_w from cubicweb.server import serverctl from cubicweb.web.webctl import WebCreateHandler from cubicweb.toolsutils import fill_templated_file @@ -271,6 +272,11 @@ "https://docs.pylonsproject.org/projects/pyramid_debugtoolbar/en/latest/") sys.exit(1) + if self['debug']: + # this is for injecting those into generated html: + # > cubicweb-generated-by="module.Class" cubicweb-from-source="/path/to/file.py:42" + inject_html_generating_call_on_w() + app = wsgi_application_from_cwconfig( cwconfig, profile=self['profile'], profile_output=self['profile-output'], diff -r 44147dab9d27 -r 8caa109dfe94 cubicweb/view.py --- a/cubicweb/view.py Thu Nov 28 11:48:03 2019 +0100 +++ b/cubicweb/view.py Wed Jul 24 16:11:22 2019 +0200 @@ -20,9 +20,11 @@ from cubicweb import _ +import re from io import BytesIO from warnings import warn from functools import partial +from inspect import getframeinfo, stack from logilab.common.registry import yes from logilab.mtconverter import xml_escape @@ -46,6 +48,11 @@ STRICT_DOCTYPE_NOEXT = u'\n' STRICT_DOCTYPE = STRICT_DOCTYPE_NOEXT # bw compat + +def inject_html_generating_call_on_w(): + View.debug_html_rendering = True + + # base view object ############################################################ class View(AppObject): @@ -86,17 +93,50 @@ add_to_breadcrumbs = True category = 'view' paginable = True + debug_html_rendering = False def __init__(self, req=None, rset=None, **kwargs): super(View, self).__init__(req, rset=rset, **kwargs) - self.w = None + self._w = None @property def content_type(self): return self._cw.html_content_type() + def w(self, text): + if self._w is None: + raise Exception('Error: call to %s.w before it has been set' % self) + + if self.debug_html_rendering: + caller = getframeinfo(stack()[1][0]) + + if isinstance(text, str) and re.match(r"^ *<[a-zA-Z0-9]+( .*>|>)", text): + to_add = 'cubicweb-generated-by="%s.%s" cubicweb-from-source="%s:%s"' % ( + self.__module__, self.__class__.__name__, + caller.filename, caller.lineno, + ) + + before_space, beginning_of_html = text.split("<", 1) + + # when it's a tag without attribues like "" + if re.match(r"^ *<[a-zA-Z0-9]+>", text): + tag_name, rest = beginning_of_html.split(">", 1) + rest = ">" + rest + else: + tag_name, rest = beginning_of_html.split(" ", 1) + rest = " " + rest + + text = "%(before_space)s<%(tag_name)s %(to_add)s%(rest)s" % { + "before_space": before_space, + "tag_name": tag_name, + "to_add": to_add, + "rest": rest, + } + + return self._w(text) + def set_stream(self, w=None): - if self.w is not None: + if self._w is not None: return if w is None: if self.binary: @@ -106,7 +146,7 @@ w = stream.write else: stream = None - self.w = w + self._w = w return stream # main view interface ##################################################### @@ -238,7 +278,7 @@ def wview(self, __vid, rset=None, __fallback_vid=None, **kwargs): """shortcut to self.view method automatically passing self.w as argument """ - self._cw.view(__vid, rset, __fallback_vid, w=self.w, **kwargs) + self._cw.view(__vid, rset, __fallback_vid, w=self._w, **kwargs) def whead(self, data): self._cw.html_headers.write(data) @@ -466,7 +506,7 @@ doctype = '' def set_stream(self, w=None): - if self.w is not None: + if self._w is not None: return if w is None: if self.binary: @@ -476,7 +516,7 @@ w = stream.write else: stream = None - self.w = w + self._w = w return stream def write_doctype(self, xmldecl=True): diff -r 44147dab9d27 -r 8caa109dfe94 cubicweb/web/test/unittest_views_debug_html.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cubicweb/web/test/unittest_views_debug_html.py Wed Jul 24 16:11:22 2019 +0200 @@ -0,0 +1,49 @@ +# copyright 2019 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This file is part of CubicWeb. +# +# CubicWeb is free software: you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free +# Software Foundation, either version 2.1 of the License, or (at your option) +# any later version. +# +# CubicWeb 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 Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License along +# with CubicWeb. If not, see . + +import inspect + +from cubicweb.view import View +from cubicweb.devtools.testlib import CubicWebTC + + +class DebugHtmlRenderingTC(CubicWebTC): + def setUp(self): + super().setUp() + View.debug_html_rendering = True + + def tearDown(self): + super().tearDown() + View.debug_html_rendering = False + + def test_debug_html_rendering_inject_tags(self): + with self.admin_access.web_request() as req: + view = self.vreg['views'].select("index", req) + view_class = view.__class__ + page = view.render() + + self.assertIn('cubicweb-generated-by="%s.%s"' % (view_class.__module__, + view_class.__name__), + page) + source_file = inspect.getsourcefile(view_class) + self.assertIn('cubicweb-from-source="%s' % (source_file), page) + + +if __name__ == '__main__': + from logilab.common.testlib import unittest_main + unittest_main()