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

authorVladimir Popescu <vladimir.popescu@logilab.fr>
changeset662b6671d3d6
branchdefault
phasedraft
hiddenyes
parent revision#e7ed80963c55 [web/views] extract cube geocodable (closes #353000)
child revision#165a27dfc427 [web/views] extract cube sioc (closes #1916018)
files modified by this revision
__pkginfo__.py
debian/control
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 1355749910 -3600
# Mon Dec 17 14:11:50 2012 +0100
# Node ID 662b6671d3d649275fc750fdedb060989e1f02fe
# Parent e7ed80963c5522736eee01c289ca33c61d77d653
[web/views] extract cube embed (closes #1916015)

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