[debug] add in each html snippet from where it has been generated in the code
authorLaurent Peuch <cortex@worlddomination.be>
Wed, 24 Jul 2019 16:11:22 +0200
changeset 12780 8caa109dfe94
parent 12779 44147dab9d27
child 12781 fda5efaf6937
[debug] add in each html snippet from where it has been generated in the code Closes #17219704
cubicweb/pyramid/pyramidctl.py
cubicweb/view.py
cubicweb/web/test/unittest_views_debug_html.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'],
--- 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'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\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 "<b>"
+                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 = '<!DOCTYPE html>'
 
     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):
--- /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 <http://www.gnu.org/licenses/>.
+
+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()