From 7473a485e981d8f2b80016aea8f23861b0cb7cbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Marques?= Date: Wed, 7 Oct 2020 11:47:17 +0100 Subject: [PATCH] [12.0][IMP] web_pwa_oca: configurable name and icon Add ability to configure name, short name and icon for PWA in Odoo General Settings. The different icon sizes also get replaced by a single SVG TT25979 --- web_pwa_oca/README.rst | 35 ++-- web_pwa_oca/__init__.py | 1 + web_pwa_oca/__manifest__.py | 2 + web_pwa_oca/controllers/main.py | 178 ++++++++++-------- web_pwa_oca/models/__init__.py | 1 + web_pwa_oca/models/res_config_settings.py | 146 ++++++++++++++ web_pwa_oca/readme/CONFIGURE.rst | 19 +- web_pwa_oca/readme/CONTRIBUTORS.rst | 1 + web_pwa_oca/readme/ROADMAP.rst | 1 + web_pwa_oca/readme/USAGE.rst | 4 + web_pwa_oca/static/description/index.html | 61 +++--- web_pwa_oca/static/img/icons/odoo_logo.svg | 1 + web_pwa_oca/tests/__init__.py | 1 + .../tests/test_web_pwa_oca_controller.py | 123 ++++++++++++ .../views/res_config_settings_views.xml | 40 ++++ 15 files changed, 490 insertions(+), 124 deletions(-) create mode 100644 web_pwa_oca/models/__init__.py create mode 100644 web_pwa_oca/models/res_config_settings.py create mode 100644 web_pwa_oca/readme/USAGE.rst create mode 100644 web_pwa_oca/static/img/icons/odoo_logo.svg create mode 100644 web_pwa_oca/tests/__init__.py create mode 100644 web_pwa_oca/tests/test_web_pwa_oca_controller.py create mode 100644 web_pwa_oca/views/res_config_settings_views.xml diff --git a/web_pwa_oca/README.rst b/web_pwa_oca/README.rst index 22c4ae65d..f31775a4a 100644 --- a/web_pwa_oca/README.rst +++ b/web_pwa_oca/README.rst @@ -63,16 +63,25 @@ In case you previously installed `web_pwa`, run the following steps with `odoo s Configuration ============= -The following system parameters con be set to customize the appearance of the application +This module allows you to set the following parameters under settings to customize the appearance of the application -* pwa.manifest.name (defaults to "Odoo PWA") -* pwa.manifest.short_name (defaults to "Odoo PWA") -* pwa.manifest.icon128x128 (defaults to "/web_pwa_oca/static/img/icons/icon-128x128.png") -* pwa.manifest.icon144x144 (defaults to "/web_pwa_oca/static/img/icons/icon-144x144.png") -* pwa.manifest.icon152x152 (defaults to "/web_pwa_oca/static/img/icons/icon-152x152.png") -* pwa.manifest.icon192x192 (defaults to "/web_pwa_oca/static/img/icons/icon-192x192.png") -* pwa.manifest.icon256x256 (defaults to "/web_pwa_oca/static/img/icons/icon-256x256.png") -* pwa.manifest.icon512x512 (defaults to "/web_pwa_oca/static/img/icons/icon-512x512.png") +* PWA Name (defaults to "Odoo PWA") +* PWA Short Name (defaults to "Odoo PWA") +* PWA Icon (**SVG**) (defaults to "/web_pwa_oca/static/img/icons/odoo-logo.svg") + +To configure your PWA: + +#. Go to **Settings > General Settings > Progressive Web App**. +#. Set the parameters (*Note:* Icon **must be a SVG file**) +#. **Save** + +Usage +===== + +To use your PWA: + +#. Open the Odoo web app using a supported browser (like Chrome/Chromium) +#. Install the PWA Known issues / Roadmap ====================== @@ -81,11 +90,11 @@ Known issues / Roadmap * Integrate `Notification API `_ * Integrate `Web Share API `_ * Create ``portal_pwa`` module, intended to be used by front-end users (customers, suppliers...) -* Current ``John Resig's inheritance`` doesn't support ``async`` functions. So we need use the following workaround: +* Current ``John Resig's inheritance`` implementation doesn't support ``async`` functions because ``this._super`` can't be called inside a promise. So we need use the following workaround: - Natural 'async/await' example (This breaks "_super" call) .. code-block:: javascript - const MyClass = OdooClass.extend({ + var MyClass = OdooClass.extend({ myFunc: async function() { const mydata = await ...do await stuff... return mydata; @@ -95,7 +104,7 @@ Known issues / Roadmap - Same code with the workaround: .. code-block:: javascript - const MyClass = OdooClass.extend({ + var MyClass = OdooClass.extend({ myFunc: function() { return new Promise(async (resolve, reject) => { const mydata = await ...do await stuff... @@ -103,6 +112,7 @@ Known issues / Roadmap }); } }); +* Fix issue when trying to run in localhost with several databases. The browser doesn't send the cookie and web manifest returns 404. Bug Tracker =========== @@ -133,6 +143,7 @@ Contributors * `Tecnativa `_: * Alexandre D. Díaz + * João Marques Maintainers ~~~~~~~~~~~ diff --git a/web_pwa_oca/__init__.py b/web_pwa_oca/__init__.py index e046e49fb..91c5580fe 100644 --- a/web_pwa_oca/__init__.py +++ b/web_pwa_oca/__init__.py @@ -1 +1,2 @@ from . import controllers +from . import models diff --git a/web_pwa_oca/__manifest__.py b/web_pwa_oca/__manifest__.py index 6c83ebf59..3ef47e9fa 100644 --- a/web_pwa_oca/__manifest__.py +++ b/web_pwa_oca/__manifest__.py @@ -1,5 +1,6 @@ # Copyright 2020 Lorenzo Battistini @ TAKOBI # Copyright 2020 Tecnativa - Alexandre D. Díaz +# Copyright 2020 Tecnativa - João Marques # License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl). { @@ -21,6 +22,7 @@ "data": [ "templates/assets.xml", "templates/service_worker.xml", + "views/res_config_settings_views.xml", ], 'qweb': [ 'static/src/xml/pwa_install.xml', diff --git a/web_pwa_oca/controllers/main.py b/web_pwa_oca/controllers/main.py index 7d89de364..b36207dc2 100644 --- a/web_pwa_oca/controllers/main.py +++ b/web_pwa_oca/controllers/main.py @@ -1,32 +1,33 @@ # Copyright 2020 Lorenzo Battistini @ TAKOBI # Copyright 2020 Tecnativa - Alexandre D. Díaz +# Copyright 2020 Tecnativa - João Marques # License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl). import json + from odoo.http import request, Controller, route class PWA(Controller): - def _get_pwa_scripts(self): """ Scripts to be imported in the service worker (Order is important) """ return [ - '/web/static/lib/underscore/underscore.js', - '/web_pwa_oca/static/src/js/worker/libs/class.js', - '/web_pwa_oca/static/src/js/worker/base/tools.js', - '/web_pwa_oca/static/src/js/worker/base/cache_manager.js', - '/web_pwa_oca/static/src/js/worker/pwa.js', + "/web/static/lib/underscore/underscore.js", + "/web_pwa_oca/static/src/js/worker/libs/class.js", + "/web_pwa_oca/static/src/js/worker/base/tools.js", + "/web_pwa_oca/static/src/js/worker/base/cache_manager.js", + "/web_pwa_oca/static/src/js/worker/pwa.js", ] def _get_asset_urls(self, asset_xml_id): - """Get all url's that have 'asset_xml_id'""" - qweb_sudo = request.env['ir.qweb'].sudo() + """Get all urls that have 'asset_xml_id'""" + qweb_sudo = request.env["ir.qweb"].sudo() assets = qweb_sudo._get_asset_nodes(asset_xml_id, {}, True, True) urls = [] for asset in assets: - if asset[0] == 'link': - urls.append(asset[1]['href']) - if asset[0] == 'script': - urls.append(asset[1]['src']) + if asset[0] == "link": + urls.append(asset[1]["href"]) + if asset[0] == "script": + urls.append(asset[1]["src"]) return urls def _get_pwa_params(self): @@ -36,86 +37,105 @@ class PWA(Controller): urls.extend(self._get_asset_urls("web.assets_backend")) version_list = [] for url in urls: - version_list.append(url.split('/')[3]) - cache_version = '-'.join(version_list) - return [ - cache_version, - urls - ] + version_list.append(url.split("/")[3]) + cache_version = "-".join(version_list) + return [cache_version, urls] - @route('/service-worker.js', type='http', auth="public") + @route("/service-worker.js", type="http", auth="public") def render_service_worker(self): """Route to register the service worker in the 'main' scope ('/')""" - return request.render('web_pwa_oca.service_worker', { - 'pwa_scripts': self._get_pwa_scripts(), - 'pwa_params': self._get_pwa_params(), - }, headers=[('Content-Type', 'text/javascript;charset=utf-8')]) + return request.render( + "web_pwa_oca.service_worker", + { + "pwa_scripts": self._get_pwa_scripts(), + "pwa_params": self._get_pwa_params(), + }, + headers=[("Content-Type", "text/javascript;charset=utf-8")], + ) + + def _get_pwa_manifest_icons(self, pwa_icon): + icons = [] + if not pwa_icon: + for size in [ + (128, 128), + (144, 144), + (152, 152), + (192, 192), + (256, 256), + (512, 512), + ]: + icons.append( + { + "src": "/web_pwa_oca/static/img/icons/icon-%sx%s.png" + % (str(size[0]), str(size[1])), + "sizes": "%sx%s" % (str(size[0]), str(size[1])), + "type": "image/png", + } + ) + elif not pwa_icon.mimetype.startswith("image/svg"): + all_icons = ( + request.env["ir.attachment"] + .sudo() + .search( + [ + ("url", "like", "/web_pwa_oca/icon"), + ( + "url", + "not like", + "/web_pwa_oca/icon.", + ), # Get only resized icons + ] + ) + ) + for icon in all_icons: + icon_size_name = icon.url.split("/")[-1].lstrip("icon").split(".")[0] + icons.append( + { + "src": icon.url, + "sizes": icon_size_name, + "type": icon.mimetype, + } + ) + else: + icons = [ + { + "src": pwa_icon.url, + "sizes": "128x128 144x144 152x152 192x192 256x256 512x512", + "type": pwa_icon.mimetype, + } + ] + return icons def _get_pwa_manifest(self): """Webapp manifest""" - config_param_sudo = request.env['ir.config_parameter'].sudo() + config_param_sudo = request.env["ir.config_parameter"].sudo() pwa_name = config_param_sudo.get_param("pwa.manifest.name", "Odoo PWA") pwa_short_name = config_param_sudo.get_param( - "pwa.manifest.short_name", "Odoo PWA") - icon128x128 = config_param_sudo.get_param( - "pwa.manifest.icon128x128", - "/web_pwa_oca/static/img/icons/icon-128x128.png") - icon144x144 = config_param_sudo.get_param( - "pwa.manifest.icon144x144", - "/web_pwa_oca/static/img/icons/icon-144x144.png") - icon152x152 = config_param_sudo.get_param( - "pwa.manifest.icon152x152", - "/web_pwa_oca/static/img/icons/icon-152x152.png") - icon192x192 = config_param_sudo.get_param( - "pwa.manifest.icon192x192", - "/web_pwa_oca/static/img/icons/icon-192x192.png") - icon256x256 = config_param_sudo.get_param( - "pwa.manifest.icon256x256", - "/web_pwa_oca/static/img/icons/icon-256x256.png") - icon512x512 = config_param_sudo.get_param( - "pwa.manifest.icon512x512", - "/web_pwa_oca/static/img/icons/icon-512x512.png") + "pwa.manifest.short_name", "Odoo PWA" + ) + pwa_icon = ( + request.env["ir.attachment"] + .sudo() + .search([("url", "like", "/web_pwa_oca/icon.")]) + ) background_color = config_param_sudo.get_param( - "pwa.manifest.background_color", "#2E69B5") - theme_color = config_param_sudo.get_param( - "pwa.manifest.theme_color", "#2E69B5") + "pwa.manifest.background_color", "#2E69B5" + ) + theme_color = config_param_sudo.get_param("pwa.manifest.theme_color", "#2E69B5") return { - "name": pwa_name, - "short_name": pwa_short_name, - "icons": [{ - "src": icon128x128, - "sizes": "128x128", - "type": "image/png" - }, { - "src": icon144x144, - "sizes": "144x144", - "type": "image/png" - }, { - "src": icon152x152, - "sizes": "152x152", - "type": "image/png" - }, { - "src": icon192x192, - "sizes": "192x192", - "type": "image/png" - }, { - "src": icon256x256, - "sizes": "256x256", - "type": "image/png" - }, { - "src": icon512x512, - "sizes": "512x512", - "type": "image/png" - }], - "start_url": "/web", - "display": "standalone", - "background_color": background_color, - "theme_color": theme_color + "name": pwa_name, + "short_name": pwa_short_name, + "icons": self._get_pwa_manifest_icons(pwa_icon), + "start_url": "/web", + "display": "standalone", + "background_color": background_color, + "theme_color": theme_color, } - @route('/web_pwa_oca/manifest.webmanifest', type='http', auth="public") + @route("/web_pwa_oca/manifest.webmanifest", type="http", auth="public") def pwa_manifest(self): """Returns the manifest used to install the page as app""" return request.make_response( json.dumps(self._get_pwa_manifest()), - headers=[('Content-Type', 'text/javascript;charset=utf-8')]) + headers=[("Content-Type", "text/javascript;charset=utf-8")], + ) diff --git a/web_pwa_oca/models/__init__.py b/web_pwa_oca/models/__init__.py new file mode 100644 index 000000000..0deb68c46 --- /dev/null +++ b/web_pwa_oca/models/__init__.py @@ -0,0 +1 @@ +from . import res_config_settings diff --git a/web_pwa_oca/models/res_config_settings.py b/web_pwa_oca/models/res_config_settings.py new file mode 100644 index 000000000..02f229fbc --- /dev/null +++ b/web_pwa_oca/models/res_config_settings.py @@ -0,0 +1,146 @@ +# Copyright 2020 Tecnativa - João Marques +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl). +import sys +import base64 +import io + +from PIL import Image + +from odoo import api, exceptions, fields, models, _ +from odoo.tools.mimetypes import guess_mimetype + + +class ResConfigSettings(models.TransientModel): + _inherit = "res.config.settings" + + pwa_name = fields.Char( + "Progressive Web App Name", help="Name of the Progressive Web Application" + ) + pwa_short_name = fields.Char( + "Progressive Web App Short Name", + help="Short Name of the Progressive Web Application", + ) + pwa_icon = fields.Binary("Icon", readonly=False) + _pwa_icon_url_base = "/web_pwa_oca/icon" + + @api.model + def get_values(self): + res = super(ResConfigSettings, self).get_values() + res["pwa_name"] = ( + self.env["ir.config_parameter"] + .sudo() + .get_param("pwa.manifest.name", default="") + ) + res["pwa_short_name"] = ( + self.env["ir.config_parameter"] + .sudo() + .get_param("pwa.manifest.short_name", default="") + ) + pwa_icon_ir_attachment = ( + self.env["ir.attachment"] + .sudo() + .search([("url", "like", self._pwa_icon_url_base + ".")]) + ) + res["pwa_icon"] = ( + pwa_icon_ir_attachment.datas if pwa_icon_ir_attachment else False + ) + return res + + def _unpack_icon(self, icon): + # Wrap decoded_icon in BytesIO object + decoded_icon = base64.b64decode(icon) + icon_bytes = io.BytesIO(decoded_icon) + return Image.open(icon_bytes) + + def _write_icon_to_attachment(self, extension, mimetype, size=None): + url = self._pwa_icon_url_base + extension + icon = self.pwa_icon + # Resize image + if size: + image = self._unpack_icon(icon) + resized_image = image.resize(size) + icon_bytes_output = io.BytesIO() + resized_image.save(icon_bytes_output, format=extension.lstrip(".").upper()) + icon = base64.b64encode(icon_bytes_output.getvalue()) + url = "%s%sx%s%s" % ( + self._pwa_icon_url_base, + str(size[0]), + str(size[1]), + extension, + ) + # Retreive existing attachment + existing_attachment = ( + self.env["ir.attachment"].sudo().search([("url", "like", url)]) + ) + # Write values to ir_attachment + values = { + "datas": icon, + "db_datas": icon, + "url": url, + "name": url, + "type": "binary", + "mimetype": mimetype, + } + # Rewrite if exists, else create + if existing_attachment: + existing_attachment.sudo().write(values) + else: + self.env["ir.attachment"].sudo().create(values) + + @api.model + def set_values(self): + res = super(ResConfigSettings, self).set_values() + self.env["ir.config_parameter"].sudo().set_param( + "pwa.manifest.name", self.pwa_name + ) + self.env["ir.config_parameter"].sudo().set_param( + "pwa.manifest.short_name", self.pwa_short_name + ) + # Retrieve previous value for pwa_icon from ir_attachment + pwa_icon_ir_attachments = ( + self.env["ir.attachment"] + .sudo() + .search([("url", "like", self._pwa_icon_url_base)]) + ) + # Delete or ignore if no icon provided + if not self.pwa_icon: + if pwa_icon_ir_attachments: + pwa_icon_ir_attachments.unlink() + return res + # Fail if icon provided is larger than 2mb + if sys.getsizeof(self.pwa_icon) > 2196608: + raise exceptions.UserError( + _("You can't upload a file with more than 2 MB.") + ) + # Confirm if the pwa_icon binary content is an SVG or PNG + # and process accordingly + decoded_pwa_icon = base64.b64decode(self.pwa_icon) + # Full mimetype detection + pwa_icon_mimetype = guess_mimetype(decoded_pwa_icon) + pwa_icon_extension = "." + pwa_icon_mimetype.split("/")[-1].split("+")[0] + if not pwa_icon_mimetype.startswith( + "image/svg" + ) and not pwa_icon_mimetype.startswith("image/png"): + raise exceptions.UserError(_("You can only upload SVG or PNG files")) + # Delete all previous records if we are writting new ones + if pwa_icon_ir_attachments: + pwa_icon_ir_attachments.unlink() + self._write_icon_to_attachment(pwa_icon_extension, pwa_icon_mimetype) + # write multiple sizes if not SVG + if pwa_icon_extension != ".svg": + # Fail if provided PNG is smaller than 512x512 + if self._unpack_icon(self.pwa_icon).size < (512, 512): + raise exceptions.UserError( + _("You can only upload PNG files bigger than 512x512") + ) + for size in [ + (128, 128), + (144, 144), + (152, 152), + (192, 192), + (256, 256), + (512, 512), + ]: + self._write_icon_to_attachment( + pwa_icon_extension, pwa_icon_mimetype, size=size + ) diff --git a/web_pwa_oca/readme/CONFIGURE.rst b/web_pwa_oca/readme/CONFIGURE.rst index 9fd39a840..dbd0a83f7 100644 --- a/web_pwa_oca/readme/CONFIGURE.rst +++ b/web_pwa_oca/readme/CONFIGURE.rst @@ -1,10 +1,11 @@ -The following system parameters con be set to customize the appearance of the application +This module allows you to set the following parameters under settings to customize the appearance of the application -* pwa.manifest.name (defaults to "Odoo PWA") -* pwa.manifest.short_name (defaults to "Odoo PWA") -* pwa.manifest.icon128x128 (defaults to "/web_pwa_oca/static/img/icons/icon-128x128.png") -* pwa.manifest.icon144x144 (defaults to "/web_pwa_oca/static/img/icons/icon-144x144.png") -* pwa.manifest.icon152x152 (defaults to "/web_pwa_oca/static/img/icons/icon-152x152.png") -* pwa.manifest.icon192x192 (defaults to "/web_pwa_oca/static/img/icons/icon-192x192.png") -* pwa.manifest.icon256x256 (defaults to "/web_pwa_oca/static/img/icons/icon-256x256.png") -* pwa.manifest.icon512x512 (defaults to "/web_pwa_oca/static/img/icons/icon-512x512.png") +* PWA Name (defaults to "Odoo PWA") +* PWA Short Name (defaults to "Odoo PWA") +* PWA Icon (**SVG**) (defaults to "/web_pwa_oca/static/img/icons/odoo-logo.svg") + +To configure your PWA: + +#. Go to **Settings > General Settings > Progressive Web App**. +#. Set the parameters (*Note:* Icon **must be a SVG file**) +#. **Save** diff --git a/web_pwa_oca/readme/CONTRIBUTORS.rst b/web_pwa_oca/readme/CONTRIBUTORS.rst index 9df4fda75..200dad8dc 100644 --- a/web_pwa_oca/readme/CONTRIBUTORS.rst +++ b/web_pwa_oca/readme/CONTRIBUTORS.rst @@ -5,3 +5,4 @@ * `Tecnativa `_: * Alexandre D. Díaz + * João Marques diff --git a/web_pwa_oca/readme/ROADMAP.rst b/web_pwa_oca/readme/ROADMAP.rst index ff63d559b..54c69076a 100644 --- a/web_pwa_oca/readme/ROADMAP.rst +++ b/web_pwa_oca/readme/ROADMAP.rst @@ -24,3 +24,4 @@ }); } }); +* Fix issue when trying to run in localhost with several databases. The browser doesn't send the cookie and web manifest returns 404. diff --git a/web_pwa_oca/readme/USAGE.rst b/web_pwa_oca/readme/USAGE.rst new file mode 100644 index 000000000..3cf386adc --- /dev/null +++ b/web_pwa_oca/readme/USAGE.rst @@ -0,0 +1,4 @@ +To use your PWA: + +#. Open the Odoo web app using a supported browser (like Chrome/Chromium) +#. Install the PWA diff --git a/web_pwa_oca/static/description/index.html b/web_pwa_oca/static/description/index.html index 2cdb54d87..5292bade8 100644 --- a/web_pwa_oca/static/description/index.html +++ b/web_pwa_oca/static/description/index.html @@ -377,12 +377,13 @@ If you’re building a web app today, you’re already on the path towards build
  • Installation
  • Configuration
  • -
  • Known issues / Roadmap
  • -
  • Bug Tracker
  • -
  • Credits @@ -406,20 +407,29 @@ And like all other installed apps, it’s a top level app in the task switcher.<

    Configuration

    -

    The following system parameters con be set to customize the appearance of the application

    +

    This module allows you to set the following parameters under settings to customize the appearance of the application

      -
    • pwa.manifest.name (defaults to “Odoo PWA”)
    • -
    • pwa.manifest.short_name (defaults to “Odoo PWA”)
    • -
    • pwa.manifest.icon128x128 (defaults to “/web_pwa_oca/static/img/icons/icon-128x128.png”)
    • -
    • pwa.manifest.icon144x144 (defaults to “/web_pwa_oca/static/img/icons/icon-144x144.png”)
    • -
    • pwa.manifest.icon152x152 (defaults to “/web_pwa_oca/static/img/icons/icon-152x152.png”)
    • -
    • pwa.manifest.icon192x192 (defaults to “/web_pwa_oca/static/img/icons/icon-192x192.png”)
    • -
    • pwa.manifest.icon256x256 (defaults to “/web_pwa_oca/static/img/icons/icon-256x256.png”)
    • -
    • pwa.manifest.icon512x512 (defaults to “/web_pwa_oca/static/img/icons/icon-512x512.png”)
    • +
    • PWA Name (defaults to “Odoo PWA”)
    • +
    • PWA Short Name (defaults to “Odoo PWA”)
    • +
    • PWA Icon (SVG) (defaults to “/web_pwa_oca/static/img/icons/odoo-logo.svg”)
    +

    To configure your PWA:

    +
      +
    1. Go to Settings > General Settings > Progressive Web App.
    2. +
    3. Set the parameters (Note: Icon must be a SVG file)
    4. +
    5. Save
    6. +
    +
    +
    +

    Usage

    +

    To use your PWA:

    +
      +
    1. Open the Odoo web app using a supported browser (like Chrome/Chromium)
    2. +
    3. Install the PWA
    4. +
    -

    Known issues / Roadmap

    +

    Known issues / Roadmap

    • Evaluate to extend FILES_TO_CACHE

    • @@ -430,12 +440,12 @@ And like all other installed apps, it’s a top level app in the task switcher.<
    • Create portal_pwa module, intended to be used by front-end users (customers, suppliers…)

    • -
      Current John Resig's inheritance doesn’t support async functions. So we need use the following workaround:
      +
      Current John Resig's inheritance implementation doesn’t support async functions because this._super can’t be called inside a promise. So we need use the following workaround:
      • Natural ‘async/await’ example (This breaks “_super” call)
        -const MyClass = OdooClass.extend({
        +var MyClass = OdooClass.extend({
             myFunc: async function() {
                 const mydata = await ...do await stuff...
                 return mydata;
        @@ -448,7 +458,7 @@ And like all other installed apps, it’s a top level app in the task switcher.<
         
      • Same code with the workaround:
        -const MyClass = OdooClass.extend({
        +var MyClass = OdooClass.extend({
             myFunc: function() {
                 return new Promise(async (resolve, reject) => {
                     const mydata = await ...do await stuff...
        @@ -464,10 +474,12 @@ And like all other installed apps, it’s a top level app in the task switcher.<
         
      • +
      • Fix issue when trying to run in localhost with several databases. The browser doesn’t send the cookie and web manifest returns 404.

        +
    -

    Bug Tracker

    +

    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 @@ -475,16 +487,16 @@ If you spotted it first, help us smashing it by providing a detailed and welcome

    Do not contact contributors directly about support or help with technical issues.

    -

    Credits

    +

    Credits

    -

    Authors

    +

    Authors

    • TAKOBI
    • Tecnativa
    -

    Contributors

    +

    Contributors

    • TAKOBI:
      • Lorenzo Battistini
      • @@ -492,12 +504,13 @@ If you spotted it first, help us smashing it by providing a detailed and welcome
      • Tecnativa:
        • Alexandre D. Díaz
        • +
        • João Marques
    -

    Maintainers

    +

    Maintainers

    This module is maintained by the OCA.

    Odoo Community Association

    OCA, or the Odoo Community Association, is a nonprofit organization whose diff --git a/web_pwa_oca/static/img/icons/odoo_logo.svg b/web_pwa_oca/static/img/icons/odoo_logo.svg new file mode 100644 index 000000000..9faea0fa5 --- /dev/null +++ b/web_pwa_oca/static/img/icons/odoo_logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web_pwa_oca/tests/__init__.py b/web_pwa_oca/tests/__init__.py new file mode 100644 index 000000000..eeacc78da --- /dev/null +++ b/web_pwa_oca/tests/__init__.py @@ -0,0 +1 @@ +from . import test_web_pwa_oca_controller diff --git a/web_pwa_oca/tests/test_web_pwa_oca_controller.py b/web_pwa_oca/tests/test_web_pwa_oca_controller.py new file mode 100644 index 000000000..547eff410 --- /dev/null +++ b/web_pwa_oca/tests/test_web_pwa_oca_controller.py @@ -0,0 +1,123 @@ +# Copyright 2020 João Marques +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +import json +import base64 + +import odoo.tests + +from odoo import exceptions +from odoo.modules.module import get_resource_path + + +class TestUi(odoo.tests.HttpCase): + def setUp(self): + super().setUp() + self.user = self.env.ref("base.user_admin") + self.res_config_settings_obj = ( + self.env["res.config.settings"].sudo(self.user.id).create({}) + ) + + def test_manifest_valid_json(self): + # Call the manifest controller + manifest_data = self.url_open("/web_pwa_oca/manifest.webmanifest") + # should be valid json + manifest_content_str = manifest_data.content.decode("utf-8") + json.loads(manifest_content_str) + + def test_manifest_correct_paramenters(self): + # Set PWA parameters in settings + self.res_config_settings_obj.pwa_name = "Test PWA" + self.res_config_settings_obj.pwa_short_name = "Test" + # icon should remain the default one + self.res_config_settings_obj.pwa_icon = False + self.res_config_settings_obj.set_values() + + # Call the manifest controller + manifest_data = self.url_open("/web_pwa_oca/manifest.webmanifest") + manifest_content_str = manifest_data.content.decode("utf-8") + manifest_content = json.loads(manifest_content_str) + + self.assertEquals(manifest_content["name"], "Test PWA") + self.assertEquals(manifest_content["short_name"], "Test") + # icon should remain the default one + self.assertEquals( + manifest_content["icons"][0]["src"], + "/web_pwa_oca/static/img/icons/icon-128x128.png", + ) + self.assertEquals(manifest_content["icons"][0]["sizes"], "128x128") + self.assertTrue(manifest_content["icons"][0]["type"].startswith("image/png")) + + def test_manifest_logo_upload(self): + with open( + "%s/static/img/icons/odoo_logo.svg" % get_resource_path("web_pwa_oca"), + "rb" + ) as fi: + icon_to_send = base64.b64encode(fi.read()) + + # Set PWA icon in settings + self.res_config_settings_obj.pwa_icon = icon_to_send + self.res_config_settings_obj.set_values() + + # Call the manifest controller + manifest_data = self.url_open("/web_pwa_oca/manifest.webmanifest") + manifest_content_str = manifest_data.content.decode("utf-8") + manifest_content = json.loads(manifest_content_str) + + self.assertEquals(manifest_content["icons"][0]["src"], "/web_pwa_oca/icon.svg") + self.assertTrue(manifest_content["icons"][0]["type"].startswith("image/svg")) + self.assertEquals(manifest_content["icons"][0]["sizes"], "128x128 144x144 152x152 192x192 256x256 512x512") + + # Get the icon and compare it + icon_data = self.url_open("/web_pwa_oca/icon.svg") + icon_data_bytes = base64.b64encode(icon_data.content) + self.assertEquals(icon_data_bytes, icon_to_send) + + def test_png_logo_upload(self): + with open( + "%s/static/img/icons/icon-512x512.png" % get_resource_path("web_pwa_oca"), + "rb" + ) as fi: + icon_to_send = base64.b64encode(fi.read()) + + # Set PWA icon in settings + self.res_config_settings_obj.pwa_icon = icon_to_send + self.res_config_settings_obj.set_values() + + # Call the manifest controller + manifest_data = self.url_open("/web_pwa_oca/manifest.webmanifest") + manifest_content_str = manifest_data.content.decode("utf-8") + manifest_content = json.loads(manifest_content_str) + + expected_vals = { + "src": "/web_pwa_oca/icon512x512.png", + "sizes": "512x512", + "type": "image/png", + } + self.assertTrue(expected_vals in manifest_content["icons"]) + + def test_manifest_logo_upload_big(self): + # Set PWA icon in settings + with self.assertRaises(exceptions.UserError): + # Image with more than 2MB + self.res_config_settings_obj.pwa_icon = b"a" * 3000000 + self.res_config_settings_obj.set_values() + + def test_manifest_logo_upload_extension(self): + with self.assertRaises(exceptions.UserError): + # Image that is not SVG or PNG + self.res_config_settings_obj.pwa_icon = b"a" * 1000 + self.res_config_settings_obj.set_values() + + def test_manifest_logo_upload_small(self): + icon_to_send = None + with open( + "%s/static/img/icons/icon-128x128.png" % get_resource_path("web_pwa_oca"), + "rb" + ) as fi: + icon_to_send = base64.b64encode(fi.read()) + # Set PWA icon in settings + with self.assertRaises(exceptions.UserError): + # Image smaller than 512X512 + self.res_config_settings_obj.pwa_icon = icon_to_send + self.res_config_settings_obj.set_values() diff --git a/web_pwa_oca/views/res_config_settings_views.xml b/web_pwa_oca/views/res_config_settings_views.xml new file mode 100644 index 000000000..b0480a13e --- /dev/null +++ b/web_pwa_oca/views/res_config_settings_views.xml @@ -0,0 +1,40 @@ + + + + + res.config.settings.view.form.pwa + res.config.settings + + +

    +

    Progressive Web App

    +
    +
    +
    +
    +
    +
    +
    + + + +