[web/views] extract cube embed (closes #1916015)

authorVladimir Popescu <vladimir.popescu@logilab.fr>
changesetbf3f129bbbdb
branchdefault
phasedraft
hiddenyes
parent revision#86c1a5afbe4e Drop iprogress code (closes #2777628)
child revision<not specified>
files modified by this revision
doc/3.17.rst
misc/migration/bootstrapmigration_repository.py
web/test/unittest_views_basecontrollers.py
web/test/unittest_views_embeding.py
web/views/embedding.py
# HG changeset patch
# User Vladimir Popescu <vladimir.popescu@logilab.fr>
# Date 1364398428 -3600
# Wed Mar 27 16:33:48 2013 +0100
# Node ID bf3f129bbbdb9116a0f7c9a8b16a10764242ad19
# Parent 86c1a5afbe4e273de8ee5a659c6aa4e1081b5d2c
[web/views] extract cube embed (closes #1916015)

diff --git a/doc/3.17.rst b/doc/3.17.rst
@@ -5,10 +5,13 @@
1  -----------
2 
3  * The SIOC views and adapters have been removed from CubicWeb and moved to the
4    `sioc` cube.
5 
6 +* The web page embedding views and adapters have been removed from CubicWeb and
7 +  moved to the `embed` cube.
8 +
9 
10  Deprecated Code Drops
11  ----------------------
12 
13  * The progress views and adapters have been removed from CubicWeb. These
diff --git a/misc/migration/bootstrapmigration_repository.py b/misc/migration/bootstrapmigration_repository.py
@@ -39,10 +39,16 @@
14          add_cube('sioc', update_database=False)
15      except ImportError:
16          if not confirm('In cubicweb 3.17 sioc views have been moved to the sioc '
17                         'cube, which is not installed.  Continue anyway?'):
18              raise
19 +    try:
20 +        add_cube('embed', update_database=False)
21 +    except ImportError:
22 +        if not confirm('In cubicweb 3.17 embedding views have been moved to the embed '
23 +                       'cube, which is not installed.  Continue anyway?'):
24 +            raise
25 
26  if applcubicwebversion <= (3, 13, 0) and cubicwebversion >= (3, 13, 1):
27      sql('ALTER TABLE entities ADD asource VARCHAR(64)')
28      sql('UPDATE entities SET asource=cw_name  '
29          'FROM cw_CWSource, cw_source_relation '
diff --git a/web/test/unittest_views_basecontrollers.py b/web/test/unittest_views_basecontrollers.py
@@ -531,22 +531,10 @@
30              self.assertEqual(len(rset), 0)
31          finally:
32              p.__class__.skip_copy_for = old_skips
33 
34 
35 -class EmbedControllerTC(CubicWebTC):
36 -
37 -    def test_nonregr_embed_publish(self):
38 -        # This test looks a bit stupid but at least it will probably
39 -        # fail if the controller API changes and if EmbedController is not
40 -        # updated (which is what happened before this test)
41 -        req = self.request()
42 -        req.form['url'] = 'http://www.logilab.fr/'
43 -        controller = self.vreg['controllers'].select('embed', req)
44 -        result = controller.publish(rset=None)
45 -
46 -
47  class ReportBugControllerTC(CubicWebTC):
48 
49      def test_usable_by_guest(self):
50          self.login('anon')
51          self.assertRaises(NoSelectableObject,
diff --git a/web/test/unittest_views_embeding.py b/web/test/unittest_views_embeding.py
@@ -1,53 +0,0 @@
52 -# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
53 -# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
54 -#
55 -# This file is part of CubicWeb.
56 -#
57 -# CubicWeb is free software: you can redistribute it and/or modify it under the
58 -# terms of the GNU Lesser General Public License as published by the Free
59 -# Software Foundation, either version 2.1 of the License, or (at your option)
60 -# any later version.
61 -#
62 -# CubicWeb is distributed in the hope that it will be useful, but WITHOUT
63 -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
64 -# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
65 -# details.
66 -#
67 -# You should have received a copy of the GNU Lesser General Public License along
68 -# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
69 -"""
70 -
71 -"""
72 -
73 -from logilab.common.testlib import TestCase, unittest_main
74 -
75 -from cubicweb.web.views.embedding import prefix_links
76 -
77 -class UILIBTC(TestCase):
78 -
79 -
80 -    def test_prefix_links(self):
81 -        """suppose we are embedding http://embedded.com/page1.html"""
82 -        orig = ['<a href="http://www.perdu.com">perdu ?</a>',
83 -        '<a href="http://embedded.com/page1.html">perdu ?</a>',
84 -        '<a href="/page2.html">perdu ?</a>',
85 -        '<a href="page3.html">perdu ?</a>',
86 -        '<img src="http://www.perdu.com/img.png"/>',
87 -        '<img src="/img.png"/>',
88 -        '<img src="img.png"/>',
89 -        ]
90 -        expected = ['<a href="PREFIXhttp%3A%2F%2Fwww.perdu.com">perdu ?</a>',
91 -        '<a href="PREFIXhttp%3A%2F%2Fembedded.com%2Fpage1.html">perdu ?</a>',
92 -        '<a href="PREFIXhttp%3A%2F%2Fembedded.com%2Fpage2.html">perdu ?</a>',
93 -        '<a href="PREFIXhttp%3A%2F%2Fembedded.com%2Fpage3.html">perdu ?</a>',
94 -        '<img src="http://www.perdu.com/img.png"/>',
95 -        '<img src="http://embedded.com/img.png"/>',
96 -        '<img src="http://embedded.com/img.png"/>',
97 -        ]
98 -        for orig_a, expected_a in zip(orig, expected):
99 -            got = prefix_links(orig_a, 'PREFIX', 'http://embedded.com/page1.html')
100 -            self.assertEqual(got, expected_a)
101 -
102 -if __name__ == '__main__':
103 -    unittest_main()
104 -
diff --git a/web/views/embedding.py b/web/views/embedding.py
@@ -1,6 +1,6 @@
105 -# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
106 +# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
107  # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
108  #
109  # This file is part of CubicWeb.
110  #
111  # CubicWeb is free software: you can redistribute it and/or modify it under the
@@ -17,174 +17,22 @@
112  # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
113  """Objects interacting together to provides the external page embeding
114  functionality.
115  """
116 
117 -__docformat__ = "restructuredtext en"
118 -_ = unicode
119 -
120 -import re
121 -from urlparse import urljoin
122 -from urllib2 import urlopen, Request, HTTPError
123 -from urllib import quote as urlquote # XXX should use view.url_quote method
124 -
125 -from logilab.mtconverter import guess_encoding
126 -
127 -from cubicweb.predicates import (one_line_rset, score_entity, implements,
128 -                                adaptable, match_search_state)
129 -from cubicweb.interfaces import IEmbedable
130 -from cubicweb.view import NOINDEX, NOFOLLOW, EntityAdapter, implements_adapter_compat
131 -from cubicweb.uilib import soup2xhtml
132 -from cubicweb.web.controller import Controller
133 -from cubicweb.web.action import Action
134 -from cubicweb.web.views import basetemplates
135 -
136 -
137 -class IEmbedableAdapter(EntityAdapter):
138 -    """interface for embedable entities"""
139 -    __needs_bw_compat__ = True
140 -    __regid__ = 'IEmbedable'
141 -    __select__ = implements(IEmbedable, warn=False) # XXX for bw compat, should be abstract
142 -
143 -    @implements_adapter_compat('IEmbedable')
144 -    def embeded_url(self):
145 -        """embed action interface"""
146 -        raise NotImplementedError
147 -
148 -
149 -class ExternalTemplate(basetemplates.TheMainTemplate):
150 -    """template embeding an external web pages into CubicWeb web interface
151 -    """
152 -    __regid__ = 'external'
153 +from logilab.common.deprecation import class_moved, moved
154 
155 -    def call(self, body):
156 -        # XXX fallback to HTML 4 mode when embeding ?
157 -        self.set_request_content_type()
158 -        self._cw.search_state = ('normal',)
159 -        self.template_header(self.content_type, None, self._cw._('external page'),
160 -                             [NOINDEX, NOFOLLOW])
161 -        self.content_header()
162 -        self.w(body)
163 -        self.content_footer()
164 -        self.template_footer()
165 -
166 -
167 -class EmbedController(Controller):
168 -    __regid__ = 'embed'
169 -    template = 'external'
170 -
171 -    def publish(self, rset=None):
172 -        req = self._cw
173 -        if 'custom_css' in req.form:
174 -            req.add_css(req.form['custom_css'])
175 -        embedded_url = req.form['url']
176 -        allowed = self._cw.vreg.config['embed-allowed']
177 -        _ = req._
178 -        if allowed is None or not allowed.match(embedded_url):
179 -            body = '<h2>%s</h2><h3>%s</h3>' % (
180 -                _('error while embedding page'),
181 -                _('embedding this url is forbidden'))
182 -        else:
183 -            prefix = req.build_url(self.__regid__, url='')
184 -            authorization = req.get_header('Authorization')
185 -            if authorization:
186 -                headers = {'Authorization' : authorization}
187 -            else:
188 -                headers = {}
189 -            try:
190 -                body = embed_external_page(embedded_url, prefix,
191 -                                           headers, req.form.get('custom_css'))
192 -                body = soup2xhtml(body, self._cw.encoding)
193 -            except HTTPError as err:
194 -                body = '<h2>%s</h2><h3>%s</h3>' % (
195 -                    _('error while embedding page'), err)
196 -        rset = self.process_rql()
197 -        return self._cw.vreg['views'].main_template(req, self.template,
198 -                                                    rset=rset, body=body)
199 -
200 +try:
201 +    from cubes.embed.views import *
202 
203 -def entity_has_embedable_url(entity):
204 -    """return 1 if the entity provides an allowed embedable url"""
205 -    url = entity.cw_adapt_to('IEmbedable').embeded_url()
206 -    if not url or not url.strip():
207 -        return 0
208 -    allowed = entity._cw.vreg.config['embed-allowed']
209 -    if allowed is None or not allowed.match(url):
210 -        return 0
211 -    return 1
212 -
213 -
214 -class EmbedAction(Action):
215 -    """display an 'embed' link on entity implementing `embeded_url` method
216 -    if the returned url match embeding configuration
217 -    """
218 -    __regid__ = 'embed'
219 -    __select__ = (one_line_rset() & match_search_state('normal')
220 -                  & adaptable('IEmbedable')
221 -                  & score_entity(entity_has_embedable_url))
222 -
223 -    title = _('embed')
224 -
225 -    def url(self, row=0):
226 -        entity = self.cw_rset.get_entity(row, 0)
227 -        url = urljoin(self._cw.base_url(), entity.cw_adapt_to('IEmbedable').embeded_url())
228 -        if 'rql' in self._cw.form:
229 -            return self._cw.build_url('embed', url=url, rql=self._cw.form['rql'])
230 -        return self._cw.build_url('embed', url=url)
231 -
232 -
233 -
234 -# functions doing necessary substitutions to embed an external html page ######
235 -
236 -
237 -BODY_RGX = re.compile('<body.*?>(.*?)</body>', re.I | re.S | re.U)
238 -HREF_RGX = re.compile('<a\s+href="([^"]*)"', re.I | re.S | re.U)
239 -SRC_RGX = re.compile('<img\s+src="([^"]*)"', re.I | re.S | re.U)
240 -
241 -
242 -class replace_href:
243 -    def __init__(self, prefix, custom_css=None):
244 -        self.prefix = prefix
245 -        self.custom_css = custom_css
246 -
247 -    def __call__(self, match):
248 -        original_url = match.group(1)
249 -        url = self.prefix + urlquote(original_url, safe='')
250 -        if self.custom_css is not None:
251 -            if '?' in url:
252 -                url = '%s&amp;custom_css=%s' % (url, self.custom_css)
253 -            else:
254 -                url = '%s?custom_css=%s' % (url, self.custom_css)
255 -        return '<a href="%s"' % url
256 -
257 -
258 -class absolutize_links:
259 -    def __init__(self, embedded_url, tag, custom_css=None):
260 -        self.embedded_url = embedded_url
261 -        self.tag = tag
262 -        self.custom_css = custom_css
263 -
264 -    def __call__(self, match):
265 -        original_url = match.group(1)
266 -        if '://' in original_url:
267 -            return match.group(0) # leave it unchanged
268 -        return '%s="%s"' % (self.tag, urljoin(self.embedded_url, original_url))
269 -
270 -
271 -def prefix_links(body, prefix, embedded_url, custom_css=None):
272 -    filters = ((HREF_RGX, absolutize_links(embedded_url, '<a href', custom_css)),
273 -               (SRC_RGX, absolutize_links(embedded_url, '<img src')),
274 -               (HREF_RGX, replace_href(prefix, custom_css)))
275 -    for rgx, repl in filters:
276 -        body = rgx.sub(repl, body)
277 -    return body
278 -
279 -
280 -def embed_external_page(url, prefix, headers=None, custom_css=None):
281 -    req = Request(url, headers=(headers or {}))
282 -    content = urlopen(req).read()
283 -    page_source = unicode(content, guess_encoding(content), 'replace')
284 -    page_source = page_source
285 -    match = BODY_RGX.search(page_source)
286 -    if match is None:
287 -        return page_source
288 -    return prefix_links(match.group(1), prefix, url, custom_css)
289 +    IEmbedableAdapter = class_moved(IEmbedableAdapter, message='[3.17] IEmbedableAdapter moved to cubes.embed.views')
290 +    ExternalTemplate = class_moved(ExternalTemplate, message='[3.17] IEmbedableAdapter moved to cubes.embed.views')
291 +    EmbedController = class_moved(EmbedController, message='[3.17] IEmbedableAdapter moved to cubes.embed.views')
292 +    entity_has_embedable_url = moved('cubes.embed.views', 'entity_has_embedable_url')
293 +    EmbedAction = class_moved(EmbedAction, message='[3.17] EmbedAction moved to cubes.embed.views')
294 +    replace_href = class_moved(replace_href, message='[3.17] replace_href moved to cubes.embed.views')
295 +    embed_external_page = moved('cubes.embed.views', 'embed_external_page')
296 +    absolutize_links = class_moved(absolutize_links, message='[3.17] absolutize_links moved to cubes.embed.views')
297 +    prefix_links = moved('cubes.embed.views', 'prefix_links')
298 +except ImportError:
299 +    from cubicweb.web import LOGGER
300 +    LOGGER.warning('[3.17] embedding extracted to cube embed that was not found. try installing it.')