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

authorVladimir Popescu <vladimir.popescu@logilab.fr>
changesetcf63671fcf2b
branchdefault
phasedraft
hiddenyes
parent revision#5fa07fdb6c8f [doc] Add 3.17 release notes
child revision#f404f3e4ccea [web/views] extract cube sioc (closes #1916018)
files modified by this revision
doc/3.17.rst
misc/migration/bootstrapmigration_repository.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 cf63671fcf2b8db0eee208a6bb16244187e1fac6
# Parent 5fa07fdb6c8f3522779a56ff9d2459031638237a
[web/views] extract cube embed (closes #1916015)

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