diff --git a/web_dashboard_tile/README.rst b/web_dashboard_tile/README.rst new file mode 100644 index 000000000..c0262f641 --- /dev/null +++ b/web_dashboard_tile/README.rst @@ -0,0 +1,161 @@ +========================== +Overview Dashboard (Tiles) +========================== + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-legalsylvain%2Fweb-lightgray.png?logo=github + :target: https://github.com/legalsylvain/web/tree/12.0-mig-web_dashboard_tile/web_dashboard_tile + :alt: legalsylvain/web + +|badge1| |badge2| |badge3| + +Adds a dashboard where you can configure tiles from any view and add them as short cut. + +By default, the tile displays items count of a given model restricted to a given domain. + +Optionally, the tile can display the result of a function on a field. + +- Function is one of ``sum``, ``avg``, ``min``, ``max`` or ``median``. +- Field must be integer or float. + +Tile can be: + +- Displayed only for a user. +- Global for all users. +- Restricted to some groups. + +*Note: The tile will be hidden if the current user doesn't have access to the given model.* + +**Table of contents** + +.. contents:: + :local: + +Configuration +============= + +First, you have to create tile categories. + +* Go to "Dashboards > Settings > Dashboard Categories" + +* Click on Create + +* Set a name, and save. + +Odoo menu and action are automatically created. +You should refresh your browser to see new menu items. + +.. image:: https://raw.githubusercontent.com/legalsylvain/web/12.0-mig-web_dashboard_tile/web_dashboard_tile/static/description/tile_category_form.png + +Then you can create tiles. + +* go to "Dashboards > Settings > Dashboard Tiles" + +* create a new tile, set a name, a category and a model. + +* You can optionally define colors, domain a specific action to use. + +* Setting a user, or a group in "Security" tab will restrict the display of the tile. + +.. image:: https://raw.githubusercontent.com/legalsylvain/web/12.0-mig-web_dashboard_tile/web_dashboard_tile/static/description/tile_tile_form.png + +You can optionanaly define a secondary value, for that purpose : + +* Select a field, a function to apply. + +* You can define a specific format. (``.format()`` python syntax) + +.. image:: https://raw.githubusercontent.com/legalsylvain/web/12.0-mig-web_dashboard_tile/web_dashboard_tile/static/description/tile_tile_form_secondary_value.png + + +Usage +===== + +* Go to "Dashboard > Overview" and select a category + +* The tile configured is displayed with the up to date count and average values of the selected domain. + +.. image:: https://raw.githubusercontent.com/legalsylvain/web/12.0-mig-web_dashboard_tile/web_dashboard_tile/static/description/tile_tile_kanban.png + +* By clicking on the item, you'll navigate to the tree view of the according model. + +.. image:: https://raw.githubusercontent.com/legalsylvain/web/12.0-mig-web_dashboard_tile/web_dashboard_tile/static/description/tile_tile_2_tree_view.png + +**Note** + +When you are in a tree view, with a domain, you can save it in the favorite menu, but the configuration is limited. + +.. image:: https://raw.githubusercontent.com/legalsylvain/web/12.0-mig-web_dashboard_tile/web_dashboard_tile/static/description/favorite_menu_create_tile.png + + +.. image:: https://raw.githubusercontent.com/legalsylvain/web/12.0-mig-web_dashboard_tile/web_dashboard_tile/static/description/favorite_menu_create_tile_result.png + +Known issues / Roadmap +====================== + +**Known issues** + +* Can not edit color from dashboard +* Original context is ignored. +* Original domain and filter are not restored. +* To preserve a relative date domain, you have to manually edit the tile's domain from "Configuration > User Interface > Dashboard Tile". You can use the same variables available in filters (``uid``, ``context_today()``, ``current_date``, ``time``, ``datetime``, `relativedelta`). + +**Roadmap** + +* Add icons. +* Support client side action (like inbox). +* Restore original Domain + Filter when an action is set. +* Posibility to hide the tile based on a field expression. +* Posibility to set the background color based on a field expression. + + +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 `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* initOS GmbH & Co. KG +* GRAP + +Contributors +~~~~~~~~~~~~ + +* Markus Schneider +* Sylvain Le Gal (https://twitter.com/legalsylvain) +* Iván Todorovich + +Maintainers +~~~~~~~~~~~ + +.. |maintainer-legalsylvain| image:: https://github.com/legalsylvain.png?size=40px + :target: https://github.com/legalsylvain + :alt: legalsylvain + +Current maintainer: + +|maintainer-legalsylvain| + +This module is part of the `legalsylvain/web `_ project on GitHub. + +You are welcome to contribute. diff --git a/web_dashboard_tile/__init__.py b/web_dashboard_tile/__init__.py new file mode 100644 index 000000000..91c5580fe --- /dev/null +++ b/web_dashboard_tile/__init__.py @@ -0,0 +1,2 @@ +from . import controllers +from . import models diff --git a/web_dashboard_tile/__manifest__.py b/web_dashboard_tile/__manifest__.py new file mode 100644 index 000000000..cbada6744 --- /dev/null +++ b/web_dashboard_tile/__manifest__.py @@ -0,0 +1,28 @@ +# © 2010-2013 OpenERP s.a. (). +# © 2014 initOS GmbH & Co. KG (). +# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html +{ + "name": "Overview Dashboard (Tiles)", + "summary": "Add Overview Dashboards with Tiles", + "version": "12.0.1.0.0", + "depends": ["web", "board", "mail", "web_widget_color"], + "author": "initOS GmbH & Co. KG, " + "GRAP, " + "Iván Todorovich , " + "Odoo Community Association (OCA)", + "maintainers": ["legalsylvain"], + "category": "web", + "license": "AGPL-3", + "data": [ + "security/ir.model.access.csv", + "security/ir_rule.xml", + "views/menu.xml", + "views/tile_tile.xml", + "views/tile_category.xml", + ], + "demo": [ + "demo/tile_category.xml", + "demo/tile_tile.xml", + ], + "qweb": ["static/src/xml/web_dashboard_tile.xml"], +} diff --git a/web_dashboard_tile/controllers/__init__.py b/web_dashboard_tile/controllers/__init__.py new file mode 100644 index 000000000..12a7e529b --- /dev/null +++ b/web_dashboard_tile/controllers/__init__.py @@ -0,0 +1 @@ +from . import main diff --git a/web_dashboard_tile/controllers/main.py b/web_dashboard_tile/controllers/main.py new file mode 100644 index 000000000..1f29fe29a --- /dev/null +++ b/web_dashboard_tile/controllers/main.py @@ -0,0 +1,15 @@ +# Copyright (C) 2019-Today: GTRAP () +# @author: Sylvain LE GAL (https://twitter.com/legalsylvain) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo.http import Controller, route, request + + +class WebDashboardTile(Controller): + + @route('/web_dashboard_tile/create_tile', type='json', auth='user') + def create_tile(self, model_name, *args, **kwargs): + IrModel = request.env['ir.model'] + model = IrModel.search([('model', '=', model_name)]) + kwargs.update({'model_id': model.id}) + return request.env['tile.tile'].create(kwargs) diff --git a/web_dashboard_tile/demo/tile_category.xml b/web_dashboard_tile/demo/tile_category.xml new file mode 100644 index 000000000..c8be7c677 --- /dev/null +++ b/web_dashboard_tile/demo/tile_category.xml @@ -0,0 +1,16 @@ + + + + + Modules + 1 + + + + + Currencies + 2 + + + + diff --git a/web_dashboard_tile/demo/tile_tile.xml b/web_dashboard_tile/demo/tile_tile.xml new file mode 100644 index 000000000..aca1a2d34 --- /dev/null +++ b/web_dashboard_tile/demo/tile_tile.xml @@ -0,0 +1,29 @@ + + + + + Installed Modules + + + + [['state', 'in', ['installed', 'to upgrade', 'to remove']]] + + + + Installed OCA Modules + + + + [['state', 'in', ['installed', 'to upgrade', 'to remove']], ['author', 'ilike', 'Odoo Community Association (OCA)']] + + + + Currencies (Max Rate) + + + max + + [] + + + diff --git a/web_dashboard_tile/i18n/fr.po b/web_dashboard_tile/i18n/fr.po new file mode 100644 index 000000000..84dca759d --- /dev/null +++ b/web_dashboard_tile/i18n/fr.po @@ -0,0 +1,437 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * web_dashboard_tile +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 12.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2021-08-02 15:38+0000\n" +"PO-Revision-Date: 2021-08-02 15:38+0000\n" +"Last-Translator: <>\n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: web_dashboard_tile +#. openerp-web +#: code:addons/web_dashboard_tile/static/src/js/web_dashboard_tile.js:99 +#, python-format +msgid "'%s' added to the overview dashboard" +msgstr "'%s' a été ajouté au tableau de bord synthétique" + +#. module: web_dashboard_tile +#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_tile__action_id +msgid "Action" +msgstr "" + +#. module: web_dashboard_tile +#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_category__active +#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_tile__active +msgid "Active" +msgstr "Actif" + +#. module: web_dashboard_tile +#. openerp-web +#: code:addons/web_dashboard_tile/static/src/xml/web_dashboard_tile.xml:15 +#, python-format +msgid "Add" +msgstr "Ajouter" + +#. module: web_dashboard_tile +#. openerp-web +#: code:addons/web_dashboard_tile/static/src/xml/web_dashboard_tile.xml:4 +#, python-format +msgid "Add to the Overview Dashboard" +msgstr "Ajouter au tableau de bord synthétique" + +#. module: web_dashboard_tile +#: selection:tile.tile,primary_function:0 +#: selection:tile.tile,secondary_function:0 +msgid "Average" +msgstr "Moyenne" + +#. module: web_dashboard_tile +#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_tile__background_color +msgid "Background Color" +msgstr "Couleur de fond" + +#. module: web_dashboard_tile +#. openerp-web +#: code:addons/web_dashboard_tile/static/src/xml/web_dashboard_tile.xml:9 +#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_tile__category_id +#, python-format +msgid "Category" +msgstr "Catégorie" + +#. module: web_dashboard_tile +#. openerp-web +#: code:addons/web_dashboard_tile/static/src/js/web_dashboard_tile.js:104 +#, python-format +msgid "Could not add new element to the overview dashboard" +msgstr "Impossible d'ajouter un nouvel élément au tableau de bord synthétique" + +#. module: web_dashboard_tile +#: selection:tile.tile,primary_function:0 +#: selection:tile.tile,secondary_function:0 +msgid "Count" +msgstr "Quantité" + +#. module: web_dashboard_tile +#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_category__create_uid +#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_tile__create_uid +msgid "Created by" +msgstr "Créé par" + +#. module: web_dashboard_tile +#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_category__create_date +#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_tile__create_date +msgid "Created on" +msgstr "Créé le" + +#. module: web_dashboard_tile +#: model:ir.actions.act_window,name:web_dashboard_tile.action_tile_category +#: model:ir.ui.menu,name:web_dashboard_tile.menu_tile_category +msgid "Dashboard Categories" +msgstr "Catégorie de tableau de bord" + +#. module: web_dashboard_tile +#: model:ir.actions.act_window,name:web_dashboard_tile.action_category_2_tile +#: model:ir.actions.act_window,name:web_dashboard_tile.action_tile_tile +#: model:ir.ui.menu,name:web_dashboard_tile.menu_tile_tile +msgid "Dashboard Items" +msgstr "Elément de tableau de bord" + +#. module: web_dashboard_tile +#: model:ir.model,name:web_dashboard_tile.model_tile_tile +msgid "Dashboard Tile" +msgstr "Indicateur de tableau de bord" + +#. module: web_dashboard_tile +#: model:ir.model,name:web_dashboard_tile.model_tile_category +msgid "Dashboard Tile Category" +msgstr "Catégorie de tableau de bord" + +#. module: web_dashboard_tile +#: model_terms:ir.ui.view,arch_db:web_dashboard_tile.view_tile_tile_form +msgid "Display" +msgstr "Afficher" + +#. module: web_dashboard_tile +#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_category__display_name +#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_tile__display_name +msgid "Display Name" +msgstr "Nom affiché" + +#. module: web_dashboard_tile +#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_tile__domain +msgid "Domain" +msgstr "Domaine" + +#. module: web_dashboard_tile +#. openerp-web +#: code:addons/web_dashboard_tile/models/tile_tile.py:202 +#: code:addons/web_dashboard_tile/models/tile_tile.py:240 +#: code:addons/web_dashboard_tile/static/src/js/web_dashboard_tile.js:75 +#, python-format +msgid "Error" +msgstr "Erreur" + +#. module: web_dashboard_tile +#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_tile__error +msgid "Error Details" +msgstr "Détails de l'erreur" + +#. module: web_dashboard_tile +#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_tile__font_color +msgid "Font Color" +msgstr "Couleur de la police" + +#. module: web_dashboard_tile +#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_tile__group_ids +msgid "Groups" +msgstr "Groupes" + +#. module: web_dashboard_tile +#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_tile__hidden +msgid "Hidden" +msgstr "Masqué" + +#. module: web_dashboard_tile +#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_tile__hide_if_null +msgid "Hide if null" +msgstr "Cacher si non défini" + +#. module: web_dashboard_tile +#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_category__id +#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_tile__id +msgid "ID" +msgstr "" + +#. module: web_dashboard_tile +#: model:ir.model.fields,help:web_dashboard_tile.field_tile_tile__hide_if_null +msgid "If checked, the item will be hidden if the primary value is null." +msgstr "Si la case est cochée, l'élément sera cachée si la valeur principale n'est pas définie." + +#. module: web_dashboard_tile +#: model:ir.model.fields,help:web_dashboard_tile.field_tile_tile__group_ids +msgid "If this field is set, only users of this group can view this tile. Please note that it will only work for global tiles (that is, when User field is left empty)" +msgstr "Si ce champ est renseigné, les utilisateurs de ce groupe seulement pourront voir cet élément. Cette restriction ne fonctionne que s'il s'agit d'un élément global. (quand le champ Utilisateur n'est pas renseigné)\"" + +#. module: web_dashboard_tile +#: model_terms:ir.ui.view,arch_db:web_dashboard_tile.view_tile_category_form +msgid "Items" +msgstr "Éléments" + +#. module: web_dashboard_tile +#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_category____last_update +#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_tile____last_update +msgid "Last Modified on" +msgstr "Dernière modification le" + +#. module: web_dashboard_tile +#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_category__write_uid +#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_tile__write_uid +msgid "Last Updated by" +msgstr "Dernière mise à jour par" + +#. module: web_dashboard_tile +#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_category__write_date +#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_tile__write_date +msgid "Last Updated on" +msgstr "Dernière mise à jour le" + +#. module: web_dashboard_tile +#: model:ir.model.fields,help:web_dashboard_tile.field_tile_tile__action_id +msgid "Let empty to use the default action related to the selected model." +msgstr "Laisser libre pour utiliser l'action par défaut liée au modèle sélectionné." + +#. module: web_dashboard_tile +#: model_terms:ir.ui.view,arch_db:web_dashboard_tile.view_tile_tile_form +msgid "Main Value" +msgstr "Valeur principale" + +#. module: web_dashboard_tile +#: selection:tile.tile,primary_function:0 +#: selection:tile.tile,secondary_function:0 +msgid "Maximum" +msgstr "" + +#. module: web_dashboard_tile +#: code:addons/web_dashboard_tile/models/tile_tile.py:41 +#, python-format +msgid "Maximum value of '%s'" +msgstr "Valeur maximale du champ '%s'" + +#. module: web_dashboard_tile +#: selection:tile.tile,primary_function:0 +#: selection:tile.tile,secondary_function:0 +msgid "Median" +msgstr "Médiane" + +#. module: web_dashboard_tile +#: code:addons/web_dashboard_tile/models/tile_tile.py:61 +#, python-format +msgid "Median value of '%s'" +msgstr "Valeur médian du champ '%s'" + +#. module: web_dashboard_tile +#: selection:tile.tile,primary_function:0 +#: selection:tile.tile,secondary_function:0 +msgid "Minimum" +msgstr "" + +#. module: web_dashboard_tile +#: code:addons/web_dashboard_tile/models/tile_tile.py:33 +#: code:addons/web_dashboard_tile/models/tile_tile.py:53 +#, python-format +msgid "Minimum value of '%s'" +msgstr "Valeur minimale du champ '%s'" + +#. module: web_dashboard_tile +#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_tile__model_id +msgid "Model" +msgstr "Modèle" + +#. module: web_dashboard_tile +#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_tile__model_name +msgid "Model name" +msgstr "Nom du modèle" + +#. module: web_dashboard_tile +#. openerp-web +#: code:addons/web_dashboard_tile/static/src/xml/web_dashboard_tile.xml:5 +#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_category__name +#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_tile__name +#, python-format +msgid "Name" +msgstr "Nom" + +#. module: web_dashboard_tile +#. openerp-web +#: code:addons/web_dashboard_tile/static/src/js/web_dashboard_tile.js:75 +#, python-format +msgid "Name Field is required." +msgstr "Le nom du champ est requis." + +#. module: web_dashboard_tile +#: code:addons/web_dashboard_tile/models/tile_tile.py:25 +#, python-format +msgid "Number of records" +msgstr "Nombre d'enregistrement" + +#. module: web_dashboard_tile +#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_category__action_id +msgid "Odoo Action" +msgstr "Action Odoo" + +#. module: web_dashboard_tile +#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_category__menu_id +msgid "Odoo Menu" +msgstr "Menu Odoo" + +#. module: web_dashboard_tile +#: model:ir.ui.menu,name:web_dashboard_tile.menu_dashboard_tile +msgid "Overview" +msgstr "Vue d'ensemble" + +#. module: web_dashboard_tile +#. openerp-web +#: code:addons/web_dashboard_tile/static/src/js/web_dashboard_tile.js:100 +#, python-format +msgid "Please refresh your browser for the changes to take effect." +msgstr "Veuillez rafraichir votre navigateur pour que les changements prennent effets." + +#. module: web_dashboard_tile +#: code:addons/web_dashboard_tile/models/tile_tile.py:317 +#, python-format +msgid "Please select a field from the selected model." +msgstr "Veuillez sélectionner un champ correspondant au modèle sélectionné" + +#. module: web_dashboard_tile +#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_tile__primary_field_id +msgid "Primary Field" +msgstr "Champ principal" + +#. module: web_dashboard_tile +#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_tile__primary_format +msgid "Primary Format" +msgstr "Format principal" + +#. module: web_dashboard_tile +#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_tile__primary_formated_value +msgid "Primary Formated Value" +msgstr "Valeur principale formatée" + +#. module: web_dashboard_tile +#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_tile__primary_function +msgid "Primary Function" +msgstr "Fonction principale" + +#. module: web_dashboard_tile +#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_tile__primary_helper +msgid "Primary Helper" +msgstr "Assistant principal" + +#. module: web_dashboard_tile +#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_tile__primary_value +msgid "Primary value" +msgstr "Valeur principale" + +#. module: web_dashboard_tile +#: model:ir.model.fields,help:web_dashboard_tile.field_tile_tile__primary_format +#: model:ir.model.fields,help:web_dashboard_tile.field_tile_tile__secondary_format +msgid "Python Format String valid with str.format()\n" +"ie: '{:,} Kgs' will output '1,000 Kgs' if value is 1000." +msgstr "Chaine de format python valide, avec str.format()\n" +"par exemple: {:,} Kgs' affichera '1000 Kgs' si la valeur est 1000." + +#. module: web_dashboard_tile +#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_tile__secondary_field_id +msgid "Secondary Field" +msgstr "champ secondaire" + +#. module: web_dashboard_tile +#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_tile__secondary_format +msgid "Secondary Format" +msgstr "Format secondaire" + +#. module: web_dashboard_tile +#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_tile__secondary_formated_value +msgid "Secondary Formated Value" +msgstr "Valeur formatée secondaire" + +#. module: web_dashboard_tile +#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_tile__secondary_function +msgid "Secondary Function" +msgstr "Fonction secondaire" + +#. module: web_dashboard_tile +#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_tile__secondary_helper +msgid "Secondary Helper" +msgstr "Aide secondaire" + +#. module: web_dashboard_tile +#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_tile__secondary_value +#: model_terms:ir.ui.view,arch_db:web_dashboard_tile.view_tile_tile_form +msgid "Secondary Value" +msgstr "Valeur secondaire" + +#. module: web_dashboard_tile +#: model_terms:ir.ui.view,arch_db:web_dashboard_tile.view_tile_tile_form +msgid "Security" +msgstr "Sécurité" + +#. module: web_dashboard_tile +#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_category__sequence +#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_tile__sequence +msgid "Sequence" +msgstr "Séquence" + +#. module: web_dashboard_tile +#: model:ir.ui.menu,name:web_dashboard_tile.menu_dashboard_tile_settings +#: model_terms:ir.ui.view,arch_db:web_dashboard_tile.view_tile_tile_form +msgid "Settings" +msgstr "Configuration" + +#. module: web_dashboard_tile +#: selection:tile.tile,primary_function:0 +#: selection:tile.tile,secondary_function:0 +msgid "Sum" +msgstr "Somme" + +#. module: web_dashboard_tile +#: model_terms:ir.ui.view,arch_db:web_dashboard_tile.view_tile_category_form +#: model_terms:ir.ui.view,arch_db:web_dashboard_tile.view_tile_tile_form +msgid "Technical Informations" +msgstr "Informations techniques" + +#. module: web_dashboard_tile +#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_category__tile_ids +msgid "Tiles" +msgstr "Elements" + +#. module: web_dashboard_tile +#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_category__tile_qty +msgid "Tiles Quantity" +msgstr "Nombre d'éléments" + +#. module: web_dashboard_tile +#: code:addons/web_dashboard_tile/models/tile_tile.py:46 +#, python-format +msgid "Total value of '%s'" +msgstr "Somme du champ '%s'" + +#. module: web_dashboard_tile +#: code:addons/web_dashboard_tile/models/tile_tile.py:288 +#, python-format +msgid "Unimplemented Feature. Search on Active field disabled." +msgstr "Fonctionnalité non implémenté. La recherche sur le champ 'Actif' est désactivé." + +#. module: web_dashboard_tile +#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_tile__user_id +msgid "User" +msgstr "Utilisateur" + diff --git a/web_dashboard_tile/migrations/12.0.1.0.0/post-migration.py b/web_dashboard_tile/migrations/12.0.1.0.0/post-migration.py new file mode 100644 index 000000000..118657c6d --- /dev/null +++ b/web_dashboard_tile/migrations/12.0.1.0.0/post-migration.py @@ -0,0 +1,29 @@ +# Copyright (C) 2019-Today: GTRAP () +# @author: Sylvain LE GAL (https://twitter.com/legalsylvain) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +import odoo + + +def migrate(cr, version): + if not version: + return + + with odoo.api.Environment.manage(): + env = odoo.api.Environment(cr, odoo.SUPERUSER_ID, {}) + + # categories was optional in previous versions + # affecting all tiles without categories + tiles_without_category = env["tile.tile"].search( + [('category_id', '=', False)]) + if tiles_without_category: + default_category = env["tile.category"].create({ + "name": "Default Category", + }) + tiles_without_category.write({ + 'category_id': default_category.id + }) + + # Enable all categories, to generate actions and menus + categories = env['tile.category'].with_context( + active_test=False).search([]) + categories.write({'active': True}) diff --git a/web_dashboard_tile/models/__init__.py b/web_dashboard_tile/models/__init__.py new file mode 100644 index 000000000..aaaf277fe --- /dev/null +++ b/web_dashboard_tile/models/__init__.py @@ -0,0 +1,2 @@ +from . import tile_tile +from . import tile_category diff --git a/web_dashboard_tile/models/tile_category.py b/web_dashboard_tile/models/tile_category.py new file mode 100644 index 000000000..f43c96369 --- /dev/null +++ b/web_dashboard_tile/models/tile_category.py @@ -0,0 +1,105 @@ +# © 2018 Iván Todorovich +# © 2019-Today GRAP +# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html + +from odoo import api, fields, models + + +class TileCategory(models.Model): + _name = "tile.category" + _description = "Dashboard Tile Category" + _order = "sequence asc" + + name = fields.Char(required=True) + + sequence = fields.Integer(required=True, default=10) + + active = fields.Boolean(default=True) + + action_id = fields.Many2one( + string='Odoo Action', comodel_name='ir.actions.act_window', + readonly=True) + + menu_id = fields.Many2one( + string='Odoo Menu', comodel_name='ir.ui.menu', readonly=True) + + tile_ids = fields.One2many( + string='Tiles', comodel_name='tile.tile', + inverse_name='category_id') + + tile_qty = fields.Integer( + string='Tiles Quantity', + compute='_compute_tile_qty', + store=True, + ) + + @api.depends('tile_ids') + def _compute_tile_qty(self): + for category in self: + category.tile_qty = len(category.tile_ids) + + def _prepare_action(self): + self.ensure_one() + return { + 'name': self.name, + 'res_model': 'tile.tile', + 'type': 'ir.actions.act_window', + 'view_mode': 'kanban', + 'domain': """[ + ('hidden', '=', False), + '|', ('user_id', '=', False), ('user_id', '=', uid), + ('category_id', '=', {self.id}) + ]""".format(self=self), + } + + def _prepare_menu(self): + self.ensure_one() + return { + 'name': self.name, + 'parent_id': self.env.ref( + 'web_dashboard_tile.menu_dashboard_tile').id, + 'action': 'ir.actions.act_window,%d' % self.action_id.id, + 'sequence': self.sequence, + } + + def _create_ui(self): + IrUiMenu = self.env['ir.ui.menu'] + IrActionsActWindows = self.env['ir.actions.act_window'] + for category in self: + if not category.action_id: + category.action_id = IrActionsActWindows.create( + category._prepare_action()) + if not category.menu_id: + category.menu_id = IrUiMenu.create(category._prepare_menu()) + + def _delete_ui(self): + for category in self: + if category.menu_id: + category.menu_id.unlink() + if category.action_id: + category.action_id.unlink() + + @api.model + def create(self, vals): + category = super().create(vals) + if category.active: + category._create_ui() + return category + + def write(self, vals): + res = super().write(vals) + if 'active' in vals.keys(): + if vals.get('active'): + self._create_ui() + else: + self._delete_ui() + if 'sequence' in vals.keys(): + self.mapped('menu_id').write({'sequence': vals['sequence']}) + if 'name' in vals.keys(): + self.mapped('menu_id').write({'name': vals['name']}) + self.mapped('action_id').write({'name': vals['name']}) + return res + + def unlink(self): + self._delete_ui() + super().unlink() diff --git a/web_dashboard_tile/models/tile_tile.py b/web_dashboard_tile/models/tile_tile.py new file mode 100644 index 000000000..3eb408c58 --- /dev/null +++ b/web_dashboard_tile/models/tile_tile.py @@ -0,0 +1,383 @@ +# © 2010-2013 OpenERP s.a. (). +# © 2014 initOS GmbH & Co. KG (). +# © 2015-Today GRAP +# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html + +import datetime +import time +from statistics import median +from dateutil.relativedelta import relativedelta +from collections import OrderedDict + +from odoo import api, fields, models +from odoo.tools.safe_eval import safe_eval as eval +from odoo.tools.translate import _ +from odoo.exceptions import ValidationError, except_orm + + +FIELD_FUNCTIONS = OrderedDict( + [ + ( + "count", + { + "name": "Count", + "func": False, # its hardcoded in _compute_data + "help": _("Number of records"), + }, + ), + ( + "min", + { + "name": "Minimum", + "func": min, + "help": _("Minimum value of '%s'"), + }, + ), + ( + "max", + { + "name": "Maximum", + "func": max, + "help": _("Maximum value of '%s'"), + }, + ), + ( + "sum", + {"name": "Sum", "func": sum, "help": _("Total value of '%s'")}, + ), + ( + "avg", + { + "name": "Average", + "func": lambda vals: sum(vals) / len(vals), + "help": _("Average value of '%s'"), + }, + ), + ( + "median", + { + "name": "Median", + "func": median, + "help": _("Median value of '%s'"), + }, + ), + ] +) + + +FIELD_FUNCTION_SELECTION = [ + (k, FIELD_FUNCTIONS[k].get("name")) for k in FIELD_FUNCTIONS +] + + +class TileTile(models.Model): + _name = "tile.tile" + _description = "Dashboard Tile" + _order = "sequence, name" + + # Column Section + name = fields.Char(required=True) + + sequence = fields.Integer(default=0, required=True) + + category_id = fields.Many2one( + string="Category", comodel_name="tile.category", required=True, + ondelete="CASCADE") + + user_id = fields.Many2one(string="User", comodel_name="res.users") + + background_color = fields.Char(default="#0E6C7E") + + font_color = fields.Char(default="#FFFFFF") + + group_ids = fields.Many2many( + comodel_name="res.groups", + string="Groups", + help="If this field is set, only users of this group can view this " + "tile. Please note that it will only work for global tiles " + "(that is, when User field is left empty)", + ) + + model_id = fields.Many2one( + comodel_name="ir.model", string="Model", required=True + ) + + model_name = fields.Char(string="Model name", related="model_id.model") + + domain = fields.Text(default="[]") + + action_id = fields.Many2one( + comodel_name="ir.actions.act_window", + string="Action", help="Let empty to use the default action related to" + " the selected model.", + domain="[('res_model', '=', model_name)]") + + active = fields.Boolean( + compute="_compute_active", search="_search_active", readonly=True + ) + + hide_if_null = fields.Boolean( + string="Hide if null", help="If checked, the item will be hidden" + " if the primary value is null.") + + hidden = fields.Boolean( + string="Hidden", compute="_compute_data", + search="_search_hidden") + + # Primary Value + primary_function = fields.Selection( + string="Primary Function", required=True, + selection=FIELD_FUNCTION_SELECTION, default="count", + ) + + primary_field_id = fields.Many2one( + comodel_name="ir.model.fields", + string="Primary Field", + domain="[('model_id', '=', model_id)," + " ('ttype', 'in', ['float', 'integer', 'monetary'])]", + ) + + primary_format = fields.Char( + string="Primary Format", + help="Python Format String valid with str.format()\n" + "ie: '{:,} Kgs' will output '1,000 Kgs' if value is 1000.", + ) + + primary_value = fields.Float( + string="Primary Value", compute="_compute_data") + + primary_formated_value = fields.Char( + string="Primary Formated Value", compute="_compute_data") + + primary_helper = fields.Char( + string="Primary Helper", compute="_compute_helper", + store=True) + + # Secondary Value + secondary_function = fields.Selection( + string="Secondary Function", selection=FIELD_FUNCTION_SELECTION, + ) + + secondary_field_id = fields.Many2one( + comodel_name="ir.model.fields", + string="Secondary Field", + domain="[('model_id', '=', model_id)," + " ('ttype', 'in', ['float', 'integer', 'monetary'])]", + ) + + secondary_format = fields.Char( + string="Secondary Format", + help="Python Format String valid with str.format()\n" + "ie: '{:,} Kgs' will output '1,000 Kgs' if value is 1000.", + ) + + secondary_value = fields.Float( + string="Secondary Value", compute="_compute_data") + + secondary_formated_value = fields.Char( + string="Secondary Formated Value", compute="_compute_data" + ) + + secondary_helper = fields.Char( + string="Secondary Helper", compute="_compute_helper", + store=True + ) + + error = fields.Char(string="Error Details", compute="_compute_data") + + # Compute Section + @api.depends("primary_format", "secondary_format", "model_id", "domain") + def _compute_data(self): + for tile in self: + if not tile.model_id or not tile.active: + return + model = self.env[tile.model_id.model] + eval_context = self._get_eval_context() + domain = tile.domain or "[]" + try: + count = model.search_count(eval(domain, eval_context)) + except Exception as e: + tile.primary_value = 0.0 + tile.primary_formated_value =\ + tile.secondary_formated_value = _("Error") + tile.error = str(e) + return + fields = [ + f.name + for f in [tile.primary_field_id, tile.secondary_field_id] + if f + ] + read_vals = ( + fields + and model.search_read(eval(domain, eval_context), fields) + or [] + ) + for f in ["primary_", "secondary_"]: + f_function = f + "function" + f_field_id = f + "field_id" + f_format = f + "format" + f_value = f + "value" + f_formated_value = f + "formated_value" + value = 0 + if not tile[f_function]: + tile[f_value] = 0.0 + tile[f_formated_value] = False + else: + if tile[f_function] == "count": + value = count + else: + func = FIELD_FUNCTIONS[tile[f_function]]["func"] + vals = [x[tile[f_field_id].name] for x in read_vals] + value = func(vals or [0.0]) + try: + tile[f_value] = value + tile[f_formated_value] = ( + tile[f_format] or "{:,}").format(value) + if tile.hide_if_null and not value: + tile.hidden = True + except ValueError as e: + tile[f_value] = 0.0 + tile[f_formated_value] = _("Error") + tile.error = str(e) + + @api.depends( + "primary_function", + "primary_field_id", + "secondary_function", + "secondary_field_id", + ) + def _compute_helper(self): + for tile in self: + for f in ["primary_", "secondary_"]: + f_function = f + "function" + f_field_id = f + "field_id" + f_helper = f + "helper" + tile[f_helper] = "" + field_func = FIELD_FUNCTIONS.get(tile[f_function], {}) + help = field_func.get("help", False) + if help: + if tile[f_function] != "count" and tile[f_field_id]: + desc = tile[f_field_id].field_description + tile[f_helper] = help % desc + else: + tile[f_helper] = help + + def _compute_active(self): + ima = self.env["ir.model.access"] + for tile in self: + if tile.model_id: + tile.active = ima.check(tile.model_id.model, "read", False) + else: + tile.active = True + + # Search Sections + def _search_hidden(self, operator, operand): + items = self.search([]) + hidden_tile_ids = [x.id for x in items if x.hidden] + if (operator == "=" and operand is False) or\ + (operator == "!=" and operand is True): + domain = [("id", "not in", hidden_tile_ids)] + else: + domain = [("id", "in", hidden_tile_ids)] + return domain + + def _search_active(self, operator, value): + cr = self.env.cr + if operator != "=": + raise except_orm( + _("Unimplemented Feature. Search on Active field disabled.") + ) + ima = self.env["ir.model.access"] + ids = [] + cr.execute( + """ + SELECT tt.id, im.model + FROM tile_tile tt + INNER JOIN ir_model im + ON tt.model_id = im.id""" + ) + for result in cr.fetchall(): + if ima.check(result[1], "read", False) == value: + ids.append(result[0]) + return [("id", "in", ids)] + + # Constraints Sections + @api.constrains("model_id", "primary_field_id", "secondary_field_id") + def _check_model_id_field_id(self): + for rec in self: + if any( + [ + rec.primary_field_id + and rec.primary_field_id.model_id.id != rec.model_id.id, + rec.secondary_field_id + and rec.secondary_field_id.model_id.id != rec.model_id.id, + ] + ): + raise ValidationError( + _("Please select a field from the selected model.") + ) + + # Onchange Sections + @api.onchange("model_id") + def _onchange_model_id(self): + self.primary_field_id = False + self.secondary_field_id = False + self.action_id = False + + @api.onchange("primary_function", "secondary_function") + def _onchange_function(self): + if self.primary_function in [False, "count"]: + self.primary_field_id = False + if self.secondary_function in [False, "count"]: + self.secondary_field_id = False + + # Action methods + @api.multi + def open_link(self): + if self.action_id: + action = self.action_id.read()[0] + else: + action = { + "view_type": "form", + "view_mode": "tree", + "view_id": False, + "res_model": self.model_id.model, + "type": "ir.actions.act_window", + "target": "current", + "domain": self.domain, + } + action.update({ + "name": self.name, + "display_name": self.name, + "context": dict(self.env.context, group_by=False), + "domain": self.domain, + }) + return action + + @api.model + def add(self, vals): + if "model_id" in vals and not vals["model_id"].isdigit(): + # need to replace model_name with its id + vals["model_id"] = ( + self.env["ir.model"] + .search([("model", "=", vals["model_id"])]) + .id + ) + self.create(vals) + + @api.model + def _get_eval_context(self): + def _context_today(): + return fields.Date.from_string(fields.Date.context_today(self)) + + context = self.env.context.copy() + context.update( + { + "time": time, + "datetime": datetime, + "relativedelta": relativedelta, + "context_today": _context_today, + "current_date": fields.Date.today(), + } + ) + return context diff --git a/web_dashboard_tile/readme/CONFIGURE.rst b/web_dashboard_tile/readme/CONFIGURE.rst new file mode 100644 index 000000000..858db81f8 --- /dev/null +++ b/web_dashboard_tile/readme/CONFIGURE.rst @@ -0,0 +1,33 @@ +First, you have to create tile categories. + +* Go to "Dashboards > Settings > Dashboard Categories" + +* Click on Create + +* Set a name, and save. + +Odoo menu and action are automatically created. +You should refresh your browser to see new menu items. + +.. image:: ../static/description/tile_category_form.png + +Then you can create tiles. + +* go to "Dashboards > Settings > Dashboard Tiles" + +* create a new tile, set a name, a category and a model. + +* You can optionally define colors, domain a specific action to use. + +* Setting a user, or a group in "Security" tab will restrict the display of the tile. + +.. image:: ../static/description/tile_tile_form.png + +You can optionally define a secondary value, for that purpose : + +* Select a field, a function to apply. + +* You can define a specific format. (``.format()`` python syntax) + +.. image:: ../static/description/tile_tile_form_secondary_value.png + diff --git a/web_dashboard_tile/readme/CONTRIBUTORS.rst b/web_dashboard_tile/readme/CONTRIBUTORS.rst new file mode 100644 index 000000000..9af10b4f5 --- /dev/null +++ b/web_dashboard_tile/readme/CONTRIBUTORS.rst @@ -0,0 +1,3 @@ +* Markus Schneider +* Sylvain Le Gal (https://twitter.com/legalsylvain) +* Iván Todorovich diff --git a/web_dashboard_tile/readme/DESCRIPTION.rst b/web_dashboard_tile/readme/DESCRIPTION.rst new file mode 100644 index 000000000..23ada038f --- /dev/null +++ b/web_dashboard_tile/readme/DESCRIPTION.rst @@ -0,0 +1,16 @@ +Adds a dashboard where you can configure tiles from any view and add them as short cut. + +By default, the tile displays items count of a given model restricted to a given domain. + +Optionally, the tile can display the result of a function on a field. + +- Function is one of ``sum``, ``avg``, ``min``, ``max`` or ``median``. +- Field must be integer or float. + +Tile can be: + +- Displayed only for a user. +- Global for all users. +- Restricted to some groups. + +*Note: The tile will be hidden if the current user doesn't have access to the given model.* diff --git a/web_dashboard_tile/readme/ROADMAP.rst b/web_dashboard_tile/readme/ROADMAP.rst new file mode 100644 index 000000000..b6bc023e7 --- /dev/null +++ b/web_dashboard_tile/readme/ROADMAP.rst @@ -0,0 +1,15 @@ +**Known issues** + +* Can not edit color from dashboard +* Original context is ignored. +* Original domain and filter are not restored. +* To preserve a relative date domain, you have to manually edit the tile's domain from "Configuration > User Interface > Dashboard Tile". You can use the same variables available in filters (``uid``, ``context_today()``, ``current_date``, ``time``, ``datetime``, `relativedelta`). + +**Roadmap** + +* Add icons. +* Support client side action (like inbox). +* Restore original Domain + Filter when an action is set. +* Posibility to hide the tile based on a field expression. +* Posibility to set the background color based on a field expression. + diff --git a/web_dashboard_tile/readme/USAGE.rst b/web_dashboard_tile/readme/USAGE.rst new file mode 100644 index 000000000..9b0134408 --- /dev/null +++ b/web_dashboard_tile/readme/USAGE.rst @@ -0,0 +1,17 @@ +* Go to "Dashboard > Overview" and select a category + +* The tile configured is displayed with the up to date count and average values of the selected domain. + +.. image:: ../static/description/tile_tile_kanban.png + +* By clicking on the item, you'll navigate to the tree view of the according model. + +.. image:: ../static/description/tile_tile_2_tree_view.png + +**Note** + +When you are in a tree view, with a domain, you can save it in the favorite menu, but the configuration is limited. + +.. image:: ../static/description/favorite_menu_create_tile.png + +.. image:: ../static/description/favorite_menu_create_tile_result.png diff --git a/web_dashboard_tile/security/ir.model.access.csv b/web_dashboard_tile/security/ir.model.access.csv new file mode 100644 index 000000000..06448cc99 --- /dev/null +++ b/web_dashboard_tile/security/ir.model.access.csv @@ -0,0 +1,3 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +tile_user,tile_user,model_tile_tile,base.group_user,1,1,1,1 +tile_user_category,tile_user,model_tile_category,base.group_user,1,1,1,1 diff --git a/web_dashboard_tile/security/ir_rule.xml b/web_dashboard_tile/security/ir_rule.xml new file mode 100644 index 000000000..f30705e35 --- /dev/null +++ b/web_dashboard_tile/security/ir_rule.xml @@ -0,0 +1,20 @@ + + + + + tile.owner + + + + [ + '|', + ('user_id','=',user.id), + ('user_id','=',False), + '|', + ('group_ids','=',False), + ('group_ids','in',[g.id for g in user.groups_id]), + ] + + + + diff --git a/web_dashboard_tile/static/description/favorite_menu_create_tile.png b/web_dashboard_tile/static/description/favorite_menu_create_tile.png new file mode 100644 index 000000000..b56c840f5 Binary files /dev/null and b/web_dashboard_tile/static/description/favorite_menu_create_tile.png differ diff --git a/web_dashboard_tile/static/description/favorite_menu_create_tile_result.png b/web_dashboard_tile/static/description/favorite_menu_create_tile_result.png new file mode 100644 index 000000000..abef2012a Binary files /dev/null and b/web_dashboard_tile/static/description/favorite_menu_create_tile_result.png differ diff --git a/web_dashboard_tile/static/description/icon.png b/web_dashboard_tile/static/description/icon.png new file mode 100644 index 000000000..fb6b1b298 Binary files /dev/null and b/web_dashboard_tile/static/description/icon.png differ diff --git a/web_dashboard_tile/static/description/index.html b/web_dashboard_tile/static/description/index.html new file mode 100644 index 000000000..f69d4f7f6 --- /dev/null +++ b/web_dashboard_tile/static/description/index.html @@ -0,0 +1,495 @@ + + + + + + +Overview Dashboard (Tiles) + + + +
+

Overview Dashboard (Tiles)

+ + +

Beta License: AGPL-3 legalsylvain/web

+

Adds a dashboard where you can configure tiles from any view and add them as short cut.

+

By default, the tile displays items count of a given model restricted to a given domain.

+

Optionally, the tile can display the result of a function on a field.

+
    +
  • Function is one of sum, avg, min, max or median.
  • +
  • Field must be integer or float.
  • +
+

Tile can be:

+
    +
  • Displayed only for a user.
  • +
  • Global for all users.
  • +
  • Restricted to some groups.
  • +
+

Note: The tile will be hidden if the current user doesn’t have access to the given model.

+

Table of contents

+ +
+

Configuration

+

First, you have to create tile categories.

+
    +
  • Go to “Dashboards > Settings > Dashboard Categories”
  • +
  • Click on Create
  • +
  • Set a name, and save.
  • +
+

Odoo menu and action are automatically created. +You should refresh your browser to see new menu items.

+https://raw.githubusercontent.com/legalsylvain/web/12.0-mig-web_dashboard_tile/web_dashboard_tile/static/description/tile_category_form.png +

Then you can create tiles.

+
    +
  • go to “Dashboards > Settings > Dashboard Tiles”
  • +
  • create a new tile, set a name, a category and a model.
  • +
  • You can optionally define colors, domain a specific action to use.
  • +
  • Setting a user, or a group in “Security” tab will restrict the display of the tile.
  • +
+https://raw.githubusercontent.com/legalsylvain/web/12.0-mig-web_dashboard_tile/web_dashboard_tile/static/description/tile_tile_form.png +

You can optionanaly define a secondary value, for that purpose :

+
    +
  • Select a field, a function to apply.
  • +
  • You can define a specific format. (.format() python syntax)
  • +
+https://raw.githubusercontent.com/legalsylvain/web/12.0-mig-web_dashboard_tile/web_dashboard_tile/static/description/tile_tile_form_secondary_value.png +
+
+

Usage

+
    +
  • Go to “Dashboard > Overview” and select a category
  • +
  • The tile configured is displayed with the up to date count and average values of the selected domain.
  • +
+https://raw.githubusercontent.com/legalsylvain/web/12.0-mig-web_dashboard_tile/web_dashboard_tile/static/description/tile_tile_kanban.png +
    +
  • By clicking on the item, you’ll navigate to the tree view of the according model.
  • +
+https://raw.githubusercontent.com/legalsylvain/web/12.0-mig-web_dashboard_tile/web_dashboard_tile/static/description/tile_tile_2_tree_view.png +

Note

+

When you are in a tree view, with a domain, you can save it in the favorite menu, but the configuration is limited.

+https://raw.githubusercontent.com/legalsylvain/web/12.0-mig-web_dashboard_tile/web_dashboard_tile/static/description/favorite_menu_create_tile.png +https://raw.githubusercontent.com/legalsylvain/web/12.0-mig-web_dashboard_tile/web_dashboard_tile/static/description/favorite_menu_create_tile_result.png +
+
+

Known issues / Roadmap

+

Known issues

+
    +
  • Can not edit color from dashboard
  • +
  • Original context is ignored.
  • +
  • Original domain and filter are not restored.
  • +
  • To preserve a relative date domain, you have to manually edit the tile’s domain from “Configuration > User Interface > Dashboard Tile”. You can use the same variables available in filters (uid, context_today(), current_date, time, datetime, relativedelta).
  • +
+

Roadmap

+
    +
  • Add icons.
  • +
  • Support client side action (like inbox).
  • +
  • Restore original Domain + Filter when an action is set.
  • +
  • Posibility to hide the tile based on a field expression.
  • +
  • Posibility to set the background color based on a field expression.
  • +
+
+
+

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.

+

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

+
+
+

Credits

+
+

Authors

+
    +
  • initOS GmbH & Co. KG
  • +
  • GRAP
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

Current maintainer:

+

legalsylvain

+

This module is part of the legalsylvain/web project on GitHub.

+

You are welcome to contribute.

+
+
+
+ + diff --git a/web_dashboard_tile/static/description/tile_category_form.png b/web_dashboard_tile/static/description/tile_category_form.png new file mode 100644 index 000000000..be602a88f Binary files /dev/null and b/web_dashboard_tile/static/description/tile_category_form.png differ diff --git a/web_dashboard_tile/static/description/tile_tile_2_tree_view.png b/web_dashboard_tile/static/description/tile_tile_2_tree_view.png new file mode 100644 index 000000000..2737a8cc2 Binary files /dev/null and b/web_dashboard_tile/static/description/tile_tile_2_tree_view.png differ diff --git a/web_dashboard_tile/static/description/tile_tile_form.png b/web_dashboard_tile/static/description/tile_tile_form.png new file mode 100644 index 000000000..fb05bb255 Binary files /dev/null and b/web_dashboard_tile/static/description/tile_tile_form.png differ diff --git a/web_dashboard_tile/static/description/tile_tile_form_secondary_value.png b/web_dashboard_tile/static/description/tile_tile_form_secondary_value.png new file mode 100644 index 000000000..5502e3ddf Binary files /dev/null and b/web_dashboard_tile/static/description/tile_tile_form_secondary_value.png differ diff --git a/web_dashboard_tile/static/description/tile_tile_kanban.png b/web_dashboard_tile/static/description/tile_tile_kanban.png new file mode 100644 index 000000000..200f2a652 Binary files /dev/null and b/web_dashboard_tile/static/description/tile_tile_kanban.png differ diff --git a/web_dashboard_tile/static/src/css/web_dashboard_tile.css b/web_dashboard_tile/static/src/css/web_dashboard_tile.css new file mode 100644 index 000000000..e9a3d962a --- /dev/null +++ b/web_dashboard_tile/static/src/css/web_dashboard_tile.css @@ -0,0 +1,59 @@ +/* custom kanban style */ +.o_kanban_view .oe_dashboard_tile { + height: 150px; +} + +/* Fix bug where draggin a tile results in the element losing its style */ +.o_kanban_view .oe_dashboard_tile { + padding: 0px !important; +} +.o_kanban_view .oe_dashboard_tile .tile_background { + padding: 8px; + height: 100%; +} + +.o_kanban_view .oe_dashboard_tile .tile_label, +.o_kanban_view .oe_dashboard_tile .tile_primary_value, +.o_kanban_view .oe_dashboard_tile .tile_secondary_value { + text-align: center; + font-weight: bold; +} + +.o_kanban_view .oe_dashboard_tile .tile_label { + padding: 5px 0px; + font-size: 15px; +} + +.o_kanban_view .oe_dashboard_tile .tile_primary_value{ + font-size: 54px; + position: absolute; + left: 5px; + right: 5px; + bottom: 5px; +} + +.o_kanban_view .oe_dashboard_tile .tile_secondary_value{ + display: none; + font-size: 18px; + font-style: italic; + position: absolute; + left: 5px; + right: 5px; + bottom: 5px; +} + +.o_kanban_view .oe_dashboard_tile .with_secondary .tile_primary_value{ + font-size: 38px; + bottom: 30px; +} + +.o_kanban_view .oe_dashboard_tile .with_secondary .tile_secondary_value{ + display: block; +} + +/* Make dropdown menu button not affect text flow */ +.o_kanban_view .oe_dashboard_tile .o_dropdown_kanban { + float: none; + position: absolute; + right: 8px; +} diff --git a/web_dashboard_tile/tests/__init__.py b/web_dashboard_tile/tests/__init__.py new file mode 100644 index 000000000..474a84ea1 --- /dev/null +++ b/web_dashboard_tile/tests/__init__.py @@ -0,0 +1 @@ +from . import test_tile diff --git a/web_dashboard_tile/tests/test_tile.py b/web_dashboard_tile/tests/test_tile.py new file mode 100644 index 000000000..84602b35a --- /dev/null +++ b/web_dashboard_tile/tests/test_tile.py @@ -0,0 +1,63 @@ +# © 2016 Antonio Espinosa - +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo.tests.common import TransactionCase + + +class TestTile(TransactionCase): + def test_tile(self): + TileTile = self.env["tile.tile"] + model_id = self.env["ir.model"].search([("model", "=", "tile.tile")]) + category_id = self.env.ref("web_dashboard_tile.category_module").id + field_id = self.env["ir.model.fields"].search( + [("model_id", "=", model_id.id), ("name", "=", "sequence")] + ) + self.tile1 = TileTile.create( + { + "name": "Count / Sum", + "sequence": 1, + "category_id": category_id, + "model_id": model_id.id, + "domain": "[('model_id', '=', %d)]" % model_id.id, + "secondary_function": "sum", + "secondary_field_id": field_id.id, + } + ) + self.tile2 = TileTile.create( + { + "name": "Min / Max", + "sequence": 2, + "category_id": category_id, + "model_id": model_id.id, + "domain": "[('model_id', '=', %d)]" % model_id.id, + "primary_function": "min", + "primary_field_id": field_id.id, + "secondary_function": "max", + "secondary_field_id": field_id.id, + } + ) + self.tile3 = TileTile.create( + { + "name": "Avg / Median", + "sequence": 3, + "category_id": category_id, + "model_id": model_id.id, + "domain": "[('model_id', '=', %d)]" % model_id.id, + "primary_function": "avg", + "primary_field_id": field_id.id, + "secondary_function": "median", + "secondary_field_id": field_id.id, + } + ) + # count + self.assertEqual(self.tile1.primary_value, 3.0) + # sum + self.assertEqual(self.tile1.secondary_value, 6.0) + # min + self.assertEqual(self.tile2.primary_value, 1.0) + # max + self.assertEqual(self.tile2.secondary_value, 3.0) + # average + self.assertEqual(self.tile3.primary_value, 2.0) + # median + self.assertEqual(self.tile3.secondary_value, 2.0) diff --git a/web_dashboard_tile/views/menu.xml b/web_dashboard_tile/views/menu.xml new file mode 100644 index 000000000..a2dd84a61 --- /dev/null +++ b/web_dashboard_tile/views/menu.xml @@ -0,0 +1,14 @@ + + + + + + + + diff --git a/web_dashboard_tile/views/tile_category.xml b/web_dashboard_tile/views/tile_category.xml new file mode 100644 index 000000000..b8c8eab58 --- /dev/null +++ b/web_dashboard_tile/views/tile_category.xml @@ -0,0 +1,63 @@ + + + + + Dashboard Items + tile.tile + form + tree,form + {'search_default_category_id': active_id} + + + + tile.category + +
+ +
+ + +
+
+
+ + + + +
+
+
+
+ + + tile.category + + + + + + + + + + + + Dashboard Categories + tile.category + form + tree,kanban,form + {'active_test': False} + + + + +
diff --git a/web_dashboard_tile/views/tile_tile.xml b/web_dashboard_tile/views/tile_tile.xml new file mode 100644 index 000000000..2d2ecb247 --- /dev/null +++ b/web_dashboard_tile/views/tile_tile.xml @@ -0,0 +1,160 @@ + + + + + tile.tile + + + + + + + + + + + tile.tile + + + + + + + + + + + + + + + + + + + tile.tile + +
+ +

+ +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + + tile.tile + + + + + + + + + + + + + + + + + + + + + + + + + Dashboard Items + tile.tile + form + tree,form,kanban + + + + +