From 94fe8ff1bfe06aa593028bfaee65c64b524a9e2c Mon Sep 17 00:00:00 2001 From: Jairo Llopis Date: Mon, 19 Dec 2016 13:31:01 +0100 Subject: [PATCH] [9.0][website_blog_excerpt_img] Migrate, LGPL. Relicensed, migrated, new tests. --- website_blog_excerpt_img/README.rst | 16 +- website_blog_excerpt_img/__init__.py | 5 + website_blog_excerpt_img/__openerp__.py | 10 +- website_blog_excerpt_img/models/__init__.py | 5 + website_blog_excerpt_img/models/blog_post.py | 40 +++++ .../src/css/website_blog_excerpt_img.css | 10 -- .../src/css/website_blog_excerpt_img.css.map | 7 - .../src/css/website_blog_excerpt_img.sass | 5 +- website_blog_excerpt_img/templates/assets.xml | 15 ++ website_blog_excerpt_img/templates/blog.xml | 41 +++++ website_blog_excerpt_img/tests/__init__.py | 5 + website_blog_excerpt_img/tests/test_html.py | 146 ++++++++++++++++++ website_blog_excerpt_img/views/assets.xml | 17 -- website_blog_excerpt_img/views/blog.xml | 58 ------- 14 files changed, 270 insertions(+), 110 deletions(-) create mode 100644 website_blog_excerpt_img/models/__init__.py create mode 100644 website_blog_excerpt_img/models/blog_post.py delete mode 100644 website_blog_excerpt_img/static/src/css/website_blog_excerpt_img.css delete mode 100644 website_blog_excerpt_img/static/src/css/website_blog_excerpt_img.css.map create mode 100644 website_blog_excerpt_img/templates/assets.xml create mode 100644 website_blog_excerpt_img/templates/blog.xml create mode 100644 website_blog_excerpt_img/tests/__init__.py create mode 100644 website_blog_excerpt_img/tests/test_html.py delete mode 100644 website_blog_excerpt_img/views/assets.xml delete mode 100644 website_blog_excerpt_img/views/blog.xml diff --git a/website_blog_excerpt_img/README.rst b/website_blog_excerpt_img/README.rst index dd92d4d37c..2b0a3e5638 100644 --- a/website_blog_excerpt_img/README.rst +++ b/website_blog_excerpt_img/README.rst @@ -1,6 +1,6 @@ -.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg - :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html - :alt: License: AGPL-3 +.. image:: https://img.shields.io/badge/licence-LGPL--3-blue.svg + :target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html + :alt: License: LGPL-3 ======================= Excerpt + Image in Blog @@ -26,7 +26,7 @@ To use this module, you need to: .. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas :alt: Try me on Runbot - :target: https://runbot.odoo-community.org/runbot/186/8.0 + :target: https://runbot.odoo-community.org/runbot/186/9.0 Known issues / Roadmap ====================== @@ -42,11 +42,7 @@ Bug Tracker Bugs are tracked on `GitHub Issues `_. In case of trouble, please check there if your issue has already been reported. If you spotted it first, -help us smashing it by providing a detailed and welcomed `feedback -`_. +help us smashing it by providing a detailed and welcomed feedback. Credits ======= @@ -54,7 +50,7 @@ Credits Contributors ------------ -* Jairo Llopis +* Jairo Llopis Maintainer ---------- diff --git a/website_blog_excerpt_img/__init__.py b/website_blog_excerpt_img/__init__.py index e69de29bb2..19b0ecf002 100644 --- a/website_blog_excerpt_img/__init__.py +++ b/website_blog_excerpt_img/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# Copyright 2016 Jairo Llopis +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +from . import models diff --git a/website_blog_excerpt_img/__openerp__.py b/website_blog_excerpt_img/__openerp__.py index 59fc02221b..3b126dfd23 100644 --- a/website_blog_excerpt_img/__openerp__.py +++ b/website_blog_excerpt_img/__openerp__.py @@ -1,15 +1,15 @@ # -*- coding: utf-8 -*- # © 2016 Grupo ESOC Ingeniería de Servicios, S.L.U. - Jairo Llopis -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). { "name": "Excerpt + Image in Blog", "summary": "New layout for blog summary, including an excerpt and image", - "version": "8.0.1.0.0", + "version": "9.0.1.0.0", "category": "Website", "website": "https://grupoesoc.es", "author": "Grupo ESOC Ingeniería de Servicios, " "Odoo Community Association (OCA)", - "license": "AGPL-3", + "license": "LGPL-3", "application": False, "installable": True, "images": [ @@ -21,7 +21,7 @@ "html_text", ], "data": [ - "views/assets.xml", - "views/blog.xml", + "templates/assets.xml", + "templates/blog.xml", ], } diff --git a/website_blog_excerpt_img/models/__init__.py b/website_blog_excerpt_img/models/__init__.py new file mode 100644 index 0000000000..6e407590db --- /dev/null +++ b/website_blog_excerpt_img/models/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# Copyright 2016 Jairo Llopis +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +from . import blog_post diff --git a/website_blog_excerpt_img/models/blog_post.py b/website_blog_excerpt_img/models/blog_post.py new file mode 100644 index 0000000000..a2c5c1a6f8 --- /dev/null +++ b/website_blog_excerpt_img/models/blog_post.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +# Copyright 2016 Jairo Llopis +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +import json +from openerp import models + + +class BlogPost(models.Model): + _inherit = "blog.post" + + def main_image(self): + """Get blog's main image URL.""" + Converter = self.env['ir.fields.converter'] + html = self.content + # Get a dictionary of properties, avoiding possible malformed ones + try: + properties = json.loads(self.cover_properties) + except (TypeError, ValueError): + properties = dict() + # Prepend cover image to post content, if there is one + cover = properties.pop("background-image", "none") + if cover and cover != "none": + html = u"
{}".format( + cover, + html, + q='"' if '"' not in cover else "'", + ) + # Return the first found image URL or None + try: + return next(Converter.imgs_from_html(html, 1)) + except StopIteration: + return None + + def content_excerpt(self, length=80): + """Get the blog post content excerpt.""" + return self.env['ir.fields.converter'].text_from_html( + self.content, + length, + ) diff --git a/website_blog_excerpt_img/static/src/css/website_blog_excerpt_img.css b/website_blog_excerpt_img/static/src/css/website_blog_excerpt_img.css deleted file mode 100644 index 39bdc4f5fd..0000000000 --- a/website_blog_excerpt_img/static/src/css/website_blog_excerpt_img.css +++ /dev/null @@ -1,10 +0,0 @@ -@charset "UTF-8"; -/* © 2016 Grupo ESOC Ingeniería de Servicios, S.L.U. - Jairo Llopis - * License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). */ -.website_blog .excerpt-img .img { - height: 15em; - width: 100%; - object-fit: cover; - object-position: center; } - -/*# sourceMappingURL=website_blog_excerpt_img.css.map */ diff --git a/website_blog_excerpt_img/static/src/css/website_blog_excerpt_img.css.map b/website_blog_excerpt_img/static/src/css/website_blog_excerpt_img.css.map deleted file mode 100644 index 79b67ae1a2..0000000000 --- a/website_blog_excerpt_img/static/src/css/website_blog_excerpt_img.css.map +++ /dev/null @@ -1,7 +0,0 @@ -{ -"version": 3, -"mappings": ";;;AAKI,+BAAiB;EACb,MAAM,EAAE,IAAI;EACZ,KAAK,EAAE,IAAI;EAEP,UAAG,EAAE,KAAK;EACV,eAAQ,EAAE,MAAM", -"sources": ["website_blog_excerpt_img.sass"], -"names": [], -"file": "website_blog_excerpt_img.css" -} \ No newline at end of file diff --git a/website_blog_excerpt_img/static/src/css/website_blog_excerpt_img.sass b/website_blog_excerpt_img/static/src/css/website_blog_excerpt_img.sass index ca253b21ce..ec1e1b4881 100644 --- a/website_blog_excerpt_img/static/src/css/website_blog_excerpt_img.sass +++ b/website_blog_excerpt_img/static/src/css/website_blog_excerpt_img.sass @@ -1,6 +1,5 @@ -@charset "utf-8" -/* © 2016 Grupo ESOC Ingeniería de Servicios, S.L.U. - Jairo Llopis - License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +/*! Copyright 2016 Jairo Llopis + License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). .website_blog .excerpt-img .img diff --git a/website_blog_excerpt_img/templates/assets.xml b/website_blog_excerpt_img/templates/assets.xml new file mode 100644 index 0000000000..73faae1158 --- /dev/null +++ b/website_blog_excerpt_img/templates/assets.xml @@ -0,0 +1,15 @@ + + + + + + + + diff --git a/website_blog_excerpt_img/templates/blog.xml b/website_blog_excerpt_img/templates/blog.xml new file mode 100644 index 0000000000..3547b44649 --- /dev/null +++ b/website_blog_excerpt_img/templates/blog.xml @@ -0,0 +1,41 @@ + + + + + + + + diff --git a/website_blog_excerpt_img/tests/__init__.py b/website_blog_excerpt_img/tests/__init__.py new file mode 100644 index 0000000000..425f7c9a86 --- /dev/null +++ b/website_blog_excerpt_img/tests/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# Copyright 2016 Jairo Llopis +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +from . import test_html diff --git a/website_blog_excerpt_img/tests/test_html.py b/website_blog_excerpt_img/tests/test_html.py new file mode 100644 index 0000000000..b4b46c2c8b --- /dev/null +++ b/website_blog_excerpt_img/tests/test_html.py @@ -0,0 +1,146 @@ +# -*- coding: utf-8 -*- +# Copyright 2016 Jairo Llopis +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +import json +import logging +from lxml import html +from openerp.tests.common import HttpCase + +_logger = logging.getLogger(__name__) + + +class HTMLCase(HttpCase): + def setUp(self): + super(HTMLCase, self).setUp() + with self.cursor() as cr: + env = self.env(cr) + self.blog_id = env["blog.blog"].create({ + "name": "Test blog", + }).id + Post = env["blog.post"] + # Create a post with cover image but no image in content + Post.create({ + "name": "Post 1", + "content": "A covered post", + "cover_properties": + json.dumps({"background-image": "url(/post-1)"}), + "website_published": True, + "blog_id": self.blog_id + }) + # Create a post like the previous one, but url is double-quoted + Post.create({ + "name": "Post 2", + "content": "A covered post", + "cover_properties": + json.dumps({"background-image": 'url("/post-2")'}), + "website_published": True, + "blog_id": self.blog_id + }) + # Create a post like the previous one, but url is single-quoted + Post.create({ + "name": "Post 3", + "content": "A covered post", + "cover_properties": + json.dumps({"background-image": "url('/post-3')"}), + "website_published": True, + "blog_id": self.blog_id + }) + # Create a post with malformed cover_properties and no cover, but + # with background image in content + Post.create({ + "name": "Post 4", + "content": + "
Badly
", + "cover_properties": "malformed", + "website_published": True, + "blog_id": self.blog_id + }) + # Create a post with default cover_properties and in content + Post.create({ + "name": "Post 5", + "content": "Cool post with image ", + "website_published": True, + "blog_id": self.blog_id + }) + # Create a post with no images + Post.create({ + "name": "Post 6", + "content": "Really boring", + "website_published": True, + "blog_id": self.blog_id + }) + # Create a post with lots of words + Post.create({ + "name": "Post 7", + "content": "Lots of words " * 80, + "website_published": True, + "blog_id": self.blog_id + }) + + # Open the blog index and store its HTML content + self.html = html.document_fromstring( + self.url_open( + "/blog/test-blog-%d" % self.blog_id, + timeout=30).read()) + + def container(self, post_title): + """Find the container of a blog post with given title.""" + query = u""" + .//div[@id='main_column']/div + [.//h2[contains(text(), "{}")]] + """ + return self.html.xpath(query.format(post_title))[0] + + def image(self, container): + """Find the extracted image URL in a given container.""" + query = ".//div[contains(@class, 'excerpt-img')]/img" + return container.xpath(query)[0].attrib["src"] + + def text(self, container): + """Find the text excerpt in a given container.""" + query = ".//div[contains(@class, 'excerpt-txt')]/p[1]" + return container.xpath(query)[0].text_content().strip() + + def test_cover_bg_unquoted(self): + """Cover image without quotes.""" + container = self.container("Post 1") + self.assertEqual(self.text(container), "A covered post") + self.assertEqual(self.image(container), "/post-1") + + def test_cover_bg_double_quoted(self): + """Cover image with double quotes.""" + container = self.container("Post 2") + self.assertEqual(self.text(container), "A covered post") + self.assertEqual(self.image(container), "/post-2") + + def test_cover_bg_single_quoted(self): + """Cover image with single quotes.""" + container = self.container("Post 3") + self.assertEqual(self.text(container), "A covered post") + self.assertEqual(self.image(container), "/post-3") + + def test_cover_malformed(self): + """Cover image malformed properties and background in content.""" + container = self.container("Post 4") + self.assertEqual(self.text(container), "Badly") + self.assertEqual(self.image(container), "/post-4") + + def test_content_img(self): + """No cover image, element in content.""" + container = self.container("Post 5") + self.assertEqual(self.text(container), "Cool post with image") + self.assertEqual(self.image(container), "/post-5") + + def test_no_img(self): + """No image anywhere.""" + container = self.container("Post 6") + self.assertEqual(self.text(container), "Really boring") + with self.assertRaises(IndexError): + self.image(container) + + def test_text_excerpt(self): + """Lots of words get truncated.""" + container = self.container("Post 7") + text = self.text(container) + self.assertEqual(len(text.split()), 80) diff --git a/website_blog_excerpt_img/views/assets.xml b/website_blog_excerpt_img/views/assets.xml deleted file mode 100644 index 531c0b5046..0000000000 --- a/website_blog_excerpt_img/views/assets.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - diff --git a/website_blog_excerpt_img/views/blog.xml b/website_blog_excerpt_img/views/blog.xml deleted file mode 100644 index 15be5931a5..0000000000 --- a/website_blog_excerpt_img/views/blog.xml +++ /dev/null @@ -1,58 +0,0 @@ - - - - - - - - - -