diff --git a/setup/web_dashboard_tile/odoo/addons/web_dashboard_tile b/setup/web_dashboard_tile/odoo/addons/web_dashboard_tile new file mode 120000 index 000000000..f721548b7 --- /dev/null +++ b/setup/web_dashboard_tile/odoo/addons/web_dashboard_tile @@ -0,0 +1 @@ +../../../../web_dashboard_tile \ No newline at end of file diff --git a/setup/web_dashboard_tile/setup.py b/setup/web_dashboard_tile/setup.py new file mode 100644 index 000000000..28c57bb64 --- /dev/null +++ b/setup/web_dashboard_tile/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) diff --git a/web_dashboard_tile/README.rst b/web_dashboard_tile/README.rst new file mode 100644 index 000000000..336489eb4 --- /dev/null +++ b/web_dashboard_tile/README.rst @@ -0,0 +1,177 @@ +========================== +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-OCA%2Fweb-lightgray.png?logo=github + :target: https://github.com/OCA/web/tree/12.0/web_dashboard_tile + :alt: OCA/web +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/web-12-0/web-12-0-web_dashboard_tile + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png + :target: https://runbot.odoo-community.org/runbot/162/12.0 + :alt: Try me on Runbot + +|badge1| |badge2| |badge3| |badge4| |badge5| + +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/OCA/web/12.0/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/OCA/web/12.0/web_dashboard_tile/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:: https://raw.githubusercontent.com/OCA/web/12.0/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/OCA/web/12.0/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/OCA/web/12.0/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/OCA/web/12.0/web_dashboard_tile/static/description/favorite_menu_create_tile.png + +.. image:: https://raw.githubusercontent.com/OCA/web/12.0/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``, ``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 +* Iván Todorovich + +Contributors +~~~~~~~~~~~~ + +* Markus Schneider +* Sylvain Le Gal (https://twitter.com/legalsylvain) +* Iván Todorovich + +Maintainers +~~~~~~~~~~~ + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +.. |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 `OCA/web `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/web_dashboard_tile/__init__.py b/web_dashboard_tile/__init__.py new file mode 100644 index 000000000..0650744f6 --- /dev/null +++ b/web_dashboard_tile/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/web_dashboard_tile/__manifest__.py b/web_dashboard_tile/__manifest__.py new file mode 100644 index 000000000..3b504d43f --- /dev/null +++ b/web_dashboard_tile/__manifest__.py @@ -0,0 +1,36 @@ +# © 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": "16.0.1.0.2", + "depends": [ + "web", + "spreadsheet_dashboard", + ], + "author": "initOS GmbH & Co. KG, " + "GRAP, " + "Iván Todorovich , " + "Odoo Community Association (OCA)", + "maintainers": ["legalsylvain"], + "website": "https://github.com/OCA/web", + "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", + ], + "assets": { + "web.assets_common": [ + "web_dashboard_tile/static/src/css/web_dashboard_tile.css", + ], + }, + "demo": [ + "demo/tile_category.xml", + "demo/tile_tile.xml", + ], +} diff --git a/web_dashboard_tile/demo/tile_category.xml b/web_dashboard_tile/demo/tile_category.xml new file mode 100644 index 000000000..9a1da03d4 --- /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..cd1550cb1 --- /dev/null +++ b/web_dashboard_tile/demo/tile_tile.xml @@ -0,0 +1,33 @@ + + + + + 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..42ed8c8ef --- /dev/null +++ b/web_dashboard_tile/i18n/fr.po @@ -0,0 +1,424 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * web_dashboard_tile +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2022-10-26 21:53+0000\n" +"PO-Revision-Date: 2022-10-26 21:53+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 +#: 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 +#: model_terms:ir.ui.view,arch_db:web_dashboard_tile.view_tile_category_form +msgid "Archived" +msgstr "Archivée" + +#. module: web_dashboard_tile +#: model:ir.model.fields.selection,name:web_dashboard_tile.selection__tile_tile__primary_function__avg +#: model:ir.model.fields.selection,name:web_dashboard_tile.selection__tile_tile__secondary_function__avg +msgid "Average" +msgstr "Moyenne" + +#. module: web_dashboard_tile +#: code:addons/web_dashboard_tile/models/tile_tile.py:0 +#, python-format +msgid "Average value of '%s'" +msgstr "Valeur moyenne du champ '%s'" + +#. 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 +#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_tile__category_id +msgid "Category" +msgstr "Catégorie" + +#. module: web_dashboard_tile +#: model:ir.model.fields.selection,name:web_dashboard_tile.selection__tile_tile__primary_function__count +#: model:ir.model.fields.selection,name:web_dashboard_tile.selection__tile_tile__secondary_function__count +msgid "Count" +msgstr "Décompte" + +#. 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égories 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éments de tableau de bord" + +#. module: web_dashboard_tile +#: model:ir.model,name:web_dashboard_tile.model_tile_tile +msgid "Dashboard Tile" +msgstr "Tuile 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 tuile du 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 +#: code:addons/web_dashboard_tile/models/tile_tile.py:0 +#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_tile__domain_error +#, python-format +msgid "Domain Error" +msgstr "Erreur sur le domaine" + +#. module: web_dashboard_tile +#: code:addons/web_dashboard_tile/models/tile_tile.py:0 +#, python-format +msgid "Error" +msgstr "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 "Caché" + +#. 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é 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é, seul les utilisateurs de ce groupe pourront voir " +"cette tuile. Cette restriction ne fonctionne que s'il s'agit d'une tuile " +"globale (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 "Mis à 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 +#: model:ir.model.fields.selection,name:web_dashboard_tile.selection__tile_tile__primary_function__max +#: model:ir.model.fields.selection,name:web_dashboard_tile.selection__tile_tile__secondary_function__max +msgid "Maximum" +msgstr "" + +#. module: web_dashboard_tile +#: code:addons/web_dashboard_tile/models/tile_tile.py:0 +#, python-format +msgid "Maximum value of '%s'" +msgstr "Valeur maximale du champ '%s'" + +#. module: web_dashboard_tile +#: model:ir.model.fields.selection,name:web_dashboard_tile.selection__tile_tile__primary_function__median +#: model:ir.model.fields.selection,name:web_dashboard_tile.selection__tile_tile__secondary_function__median +msgid "Median" +msgstr "Médiane" + +#. module: web_dashboard_tile +#: code:addons/web_dashboard_tile/models/tile_tile.py:0 +#, python-format +msgid "Median value of '%s'" +msgstr "Valeur médiane du champ '%s'" + +#. module: web_dashboard_tile +#: model:ir.model.fields.selection,name:web_dashboard_tile.selection__tile_tile__primary_function__min +#: model:ir.model.fields.selection,name:web_dashboard_tile.selection__tile_tile__secondary_function__min +msgid "Minimum" +msgstr "" + +#. module: web_dashboard_tile +#: code:addons/web_dashboard_tile/models/tile_tile.py:0 +#, 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 +#: 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 +msgid "Name" +msgstr "Nom" + +#. module: web_dashboard_tile +#: code:addons/web_dashboard_tile/models/tile_tile.py:0 +#, python-format +msgid "Number of records" +msgstr "Nombre d'enregistrements" + +#. 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 +#: model:ir.ui.menu,name:web_dashboard_tile.menu_tile_configuration +msgid "Overview Settings" +msgstr "Paramétrage de la vue d'ensemble" + +#. module: web_dashboard_tile +#: code:addons/web_dashboard_tile/models/tile_tile.py:0 +#, 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_error +msgid "Primary Error" +msgstr "Erreur principale" + +#. 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 "" +"Chaîne 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_error +msgid "Secondary Error" +msgstr "Erreur secondaire" + +#. 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 secondaire formatée" + +#. 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 "Assistant 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_terms:ir.ui.view,arch_db:web_dashboard_tile.view_tile_tile_form +msgid "Settings" +msgstr "Configuration" + +#. module: web_dashboard_tile +#: model:ir.model.fields.selection,name:web_dashboard_tile.selection__tile_tile__primary_function__sum +#: model:ir.model.fields.selection,name:web_dashboard_tile.selection__tile_tile__secondary_function__sum +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 "Tuiles" + +#. module: web_dashboard_tile +#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_category__tile_qty +msgid "Tiles Quantity" +msgstr "Nombre de tuiles" + +#. module: web_dashboard_tile +#: code:addons/web_dashboard_tile/models/tile_tile.py:0 +#, 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:0 +#, python-format +msgid "Unimplemented Feature. Search on Active field disabled." +msgstr "" +"Fonctionnalité non implémentée. 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/i18n/web_dashboard_tile.pot b/web_dashboard_tile/i18n/web_dashboard_tile.pot new file mode 100644 index 000000000..6c8fbb63f --- /dev/null +++ b/web_dashboard_tile/i18n/web_dashboard_tile.pot @@ -0,0 +1,414 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * web_dashboard_tile +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2022-10-26 21:53+0000\n" +"PO-Revision-Date: 2022-10-26 21:53+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 +#: 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 "" + +#. module: web_dashboard_tile +#: model_terms:ir.ui.view,arch_db:web_dashboard_tile.view_tile_category_form +msgid "Archived" +msgstr "" + +#. module: web_dashboard_tile +#: model:ir.model.fields.selection,name:web_dashboard_tile.selection__tile_tile__primary_function__avg +#: model:ir.model.fields.selection,name:web_dashboard_tile.selection__tile_tile__secondary_function__avg +msgid "Average" +msgstr "" + +#. module: web_dashboard_tile +#: code:addons/web_dashboard_tile/models/tile_tile.py:0 +#, python-format +msgid "Average value of '%s'" +msgstr "" + +#. module: web_dashboard_tile +#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_tile__background_color +msgid "Background Color" +msgstr "" + +#. module: web_dashboard_tile +#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_tile__category_id +msgid "Category" +msgstr "" + +#. module: web_dashboard_tile +#: model:ir.model.fields.selection,name:web_dashboard_tile.selection__tile_tile__primary_function__count +#: model:ir.model.fields.selection,name:web_dashboard_tile.selection__tile_tile__secondary_function__count +msgid "Count" +msgstr "" + +#. 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 "" + +#. 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 "" + +#. 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 "" + +#. 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 "" + +#. module: web_dashboard_tile +#: model:ir.model,name:web_dashboard_tile.model_tile_tile +msgid "Dashboard Tile" +msgstr "" + +#. module: web_dashboard_tile +#: model:ir.model,name:web_dashboard_tile.model_tile_category +msgid "Dashboard Tile Category" +msgstr "" + +#. module: web_dashboard_tile +#: model_terms:ir.ui.view,arch_db:web_dashboard_tile.view_tile_tile_form +msgid "Display" +msgstr "" + +#. 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 "" + +#. module: web_dashboard_tile +#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_tile__domain +msgid "Domain" +msgstr "" + +#. module: web_dashboard_tile +#: code:addons/web_dashboard_tile/models/tile_tile.py:0 +#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_tile__domain_error +#, python-format +msgid "Domain Error" +msgstr "" + +#. module: web_dashboard_tile +#: code:addons/web_dashboard_tile/models/tile_tile.py:0 +#, python-format +msgid "Error" +msgstr "" + +#. module: web_dashboard_tile +#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_tile__font_color +msgid "Font Color" +msgstr "" + +#. module: web_dashboard_tile +#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_tile__group_ids +msgid "Groups" +msgstr "" + +#. module: web_dashboard_tile +#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_tile__hidden +msgid "Hidden" +msgstr "" + +#. module: web_dashboard_tile +#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_tile__hide_if_null +msgid "Hide if null" +msgstr "" + +#. 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 "" + +#. 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 "" + +#. module: web_dashboard_tile +#: model_terms:ir.ui.view,arch_db:web_dashboard_tile.view_tile_category_form +msgid "Items" +msgstr "" + +#. 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 "" + +#. 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 "" + +#. 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 "" + +#. 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 "" + +#. module: web_dashboard_tile +#: model_terms:ir.ui.view,arch_db:web_dashboard_tile.view_tile_tile_form +msgid "Main Value" +msgstr "" + +#. module: web_dashboard_tile +#: model:ir.model.fields.selection,name:web_dashboard_tile.selection__tile_tile__primary_function__max +#: model:ir.model.fields.selection,name:web_dashboard_tile.selection__tile_tile__secondary_function__max +msgid "Maximum" +msgstr "" + +#. module: web_dashboard_tile +#: code:addons/web_dashboard_tile/models/tile_tile.py:0 +#, python-format +msgid "Maximum value of '%s'" +msgstr "" + +#. module: web_dashboard_tile +#: model:ir.model.fields.selection,name:web_dashboard_tile.selection__tile_tile__primary_function__median +#: model:ir.model.fields.selection,name:web_dashboard_tile.selection__tile_tile__secondary_function__median +msgid "Median" +msgstr "" + +#. module: web_dashboard_tile +#: code:addons/web_dashboard_tile/models/tile_tile.py:0 +#, python-format +msgid "Median value of '%s'" +msgstr "" + +#. module: web_dashboard_tile +#: model:ir.model.fields.selection,name:web_dashboard_tile.selection__tile_tile__primary_function__min +#: model:ir.model.fields.selection,name:web_dashboard_tile.selection__tile_tile__secondary_function__min +msgid "Minimum" +msgstr "" + +#. module: web_dashboard_tile +#: code:addons/web_dashboard_tile/models/tile_tile.py:0 +#, python-format +msgid "Minimum value of '%s'" +msgstr "" + +#. module: web_dashboard_tile +#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_tile__model_id +msgid "Model" +msgstr "" + +#. module: web_dashboard_tile +#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_tile__model_name +msgid "Model name" +msgstr "" + +#. module: web_dashboard_tile +#: 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 +msgid "Name" +msgstr "" + +#. module: web_dashboard_tile +#: code:addons/web_dashboard_tile/models/tile_tile.py:0 +#, python-format +msgid "Number of records" +msgstr "" + +#. module: web_dashboard_tile +#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_category__action_id +msgid "Odoo Action" +msgstr "" + +#. module: web_dashboard_tile +#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_category__menu_id +msgid "Odoo Menu" +msgstr "" + +#. module: web_dashboard_tile +#: model:ir.ui.menu,name:web_dashboard_tile.menu_dashboard_tile +msgid "Overview" +msgstr "" + +#. module: web_dashboard_tile +#: model:ir.ui.menu,name:web_dashboard_tile.menu_tile_configuration +msgid "Overview Settings" +msgstr "" + +#. module: web_dashboard_tile +#: code:addons/web_dashboard_tile/models/tile_tile.py:0 +#, python-format +msgid "Please select a field from the selected model." +msgstr "" + +#. module: web_dashboard_tile +#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_tile__primary_error +msgid "Primary Error" +msgstr "" + +#. module: web_dashboard_tile +#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_tile__primary_field_id +msgid "Primary Field" +msgstr "" + +#. module: web_dashboard_tile +#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_tile__primary_format +msgid "Primary Format" +msgstr "" + +#. module: web_dashboard_tile +#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_tile__primary_formated_value +msgid "Primary Formated Value" +msgstr "" + +#. module: web_dashboard_tile +#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_tile__primary_function +msgid "Primary Function" +msgstr "" + +#. module: web_dashboard_tile +#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_tile__primary_helper +msgid "Primary Helper" +msgstr "" + +#. module: web_dashboard_tile +#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_tile__primary_value +msgid "Primary Value" +msgstr "" + +#. 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 "" + +#. module: web_dashboard_tile +#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_tile__secondary_error +msgid "Secondary Error" +msgstr "" + +#. module: web_dashboard_tile +#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_tile__secondary_field_id +msgid "Secondary Field" +msgstr "" + +#. module: web_dashboard_tile +#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_tile__secondary_format +msgid "Secondary Format" +msgstr "" + +#. module: web_dashboard_tile +#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_tile__secondary_formated_value +msgid "Secondary Formated Value" +msgstr "" + +#. module: web_dashboard_tile +#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_tile__secondary_function +msgid "Secondary Function" +msgstr "" + +#. module: web_dashboard_tile +#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_tile__secondary_helper +msgid "Secondary Helper" +msgstr "" + +#. 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 "" + +#. module: web_dashboard_tile +#: model_terms:ir.ui.view,arch_db:web_dashboard_tile.view_tile_tile_form +msgid "Security" +msgstr "" + +#. 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 "" + +#. module: web_dashboard_tile +#: model_terms:ir.ui.view,arch_db:web_dashboard_tile.view_tile_tile_form +msgid "Settings" +msgstr "" + +#. module: web_dashboard_tile +#: model:ir.model.fields.selection,name:web_dashboard_tile.selection__tile_tile__primary_function__sum +#: model:ir.model.fields.selection,name:web_dashboard_tile.selection__tile_tile__secondary_function__sum +msgid "Sum" +msgstr "" + +#. 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 "" + +#. module: web_dashboard_tile +#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_category__tile_ids +msgid "Tiles" +msgstr "" + +#. module: web_dashboard_tile +#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_category__tile_qty +msgid "Tiles Quantity" +msgstr "" + +#. module: web_dashboard_tile +#: code:addons/web_dashboard_tile/models/tile_tile.py:0 +#, python-format +msgid "Total value of '%s'" +msgstr "" + +#. module: web_dashboard_tile +#: code:addons/web_dashboard_tile/models/tile_tile.py:0 +#, python-format +msgid "Unimplemented Feature. Search on Active field disabled." +msgstr "" + +#. module: web_dashboard_tile +#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_tile__user_id +msgid "User" +msgstr "" \ No newline at end of file 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..3b75b4af9 --- /dev/null +++ b/web_dashboard_tile/models/tile_category.py @@ -0,0 +1,107 @@ +# © 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_create_multi + def create(self, vals_list): + categories = super().create(vals_list) + categories.filtered(lambda x: x.active)._create_ui() + return categories + + 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() + return 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..f00accbba --- /dev/null +++ b/web_dashboard_tile/models/tile_tile.py @@ -0,0 +1,377 @@ +# © 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 + +from collections import OrderedDict +from statistics import median + +from dateutil.relativedelta import relativedelta + +from odoo import api, fields, models +from odoo.exceptions import ValidationError, except_orm +from odoo.tools.safe_eval import safe_eval +from odoo.tools.translate import _ + +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, ondelete="cascade" + ) + + model_name = fields.Char(string="Model name", related="model_id.model") + + domain = fields.Text(default="[]", required=True) + + domain_error = fields.Char(compute="_compute_data") + + 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(compute="_compute_data", search="_search_hidden") + + # Primary Value + primary_function = fields.Selection( + 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( + help="Python Format String valid with str.format()\n" + "ie: '{:,} Kgs' will output '1,000 Kgs' if value is 1000.", + ) + + primary_value = fields.Float(compute="_compute_data") + + primary_formated_value = fields.Char(compute="_compute_data") + + primary_helper = fields.Char(compute="_compute_helper", store=True) + + primary_error = fields.Char(compute="_compute_data") + + # Secondary Value + secondary_function = fields.Selection( + 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( + help="Python Format String valid with str.format()\n" + "ie: '{:,} Kgs' will output '1,000 Kgs' if value is 1000.", + ) + + secondary_value = fields.Float(compute="_compute_data") + + secondary_formated_value = fields.Char(compute="_compute_data") + + secondary_helper = fields.Char(compute="_compute_helper", store=True) + + secondary_error = fields.Char(compute="_compute_data") + + # Compute Section + @api.depends( + "model_id", + "domain", + "primary_format", + "primary_function", + "primary_field_id", + "secondary_format", + "secondary_function", + "secondary_field_id", + ) + def _compute_data(self): + for tile in self: + # initialize all vals + tile.hidden = False + tile.primary_value = False + tile.primary_formated_value = False + tile.secondary_value = False + tile.secondary_formated_value = False + tile.domain_error = False + tile.primary_error = False + tile.secondary_error = False + 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(safe_eval(domain, eval_context)) + except Exception as e: + tile.primary_formated_value = tile.secondary_formated_value = _( + "Domain Error" + ) + tile.domain_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(safe_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" + f_error = f + "error" + + if not tile[f_function]: + continue + elif 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]) + tile[f_value] = value + + try: + tile[f_formated_value] = (tile[f_format] or "{:,}").format(value) + except ValueError as e: + tile[f_formated_value] = _("Error") + tile[f_error] = str(e) + + tile.hidden = tile.hide_if_null and not tile.primary_value + + @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_text = field_func.get("help", False) + if help_text and tile[f_function] != "count" and tile[f_field_id]: + tile[f_helper] = help_text % tile[f_field_id].field_description + else: + tile[f_helper] = help_text + + def _compute_active(self): + IrModelAccess = self.env["ir.model.access"] + for tile in self: + if tile.model_id: + tile.active = IrModelAccess.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.") + ) + IrModelAccess = 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 IrModelAccess.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 tile in self: + if any( + [ + tile.primary_field_id + and tile.primary_field_id.model_id.id != tile.model_id.id, + tile.secondary_field_id + and tile.secondary_field_id.model_id.id != tile.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 + def open_link(self): + if self.action_id: + action = self.action_id.read()[0] + else: + action = { + "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 _get_eval_context(self): + context = self.env.context.copy() + context.update( + { + "relativedelta": relativedelta, + "context_today": fields.Date.from_string( + fields.Date.context_today(self) + ), + "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..3fdb451f8 --- /dev/null +++ b/web_dashboard_tile/readme/CONFIGURE.rst @@ -0,0 +1,30 @@ +First, you have to create tile categories. + +* Go to "Dashboards > Configuration > Overview Settings > Dashboard Categories" + +* Create categories + +Odoo menu and action are automatically created in the "Dashboard > Overview" menu +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 > Configuration > Overview Settings > Dashboard Items" + +* create a new tile, set a name, a category and a model. + +* You can optionally define colors, domain and 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..59911b75f --- /dev/null +++ b/web_dashboard_tile/readme/DESCRIPTION.rst @@ -0,0 +1,16 @@ +This module extends the web module to add new dashboard overview system. + +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..c13a75fde --- /dev/null +++ b/web_dashboard_tile/readme/ROADMAP.rst @@ -0,0 +1,14 @@ +**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``, ``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..e91247f98 --- /dev/null +++ b/web_dashboard_tile/readme/USAGE.rst @@ -0,0 +1,9 @@ +* 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 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..15f43a31f --- /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/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..8132301e0 --- /dev/null +++ b/web_dashboard_tile/static/description/index.html @@ -0,0 +1,501 @@ + + + + + + +Overview Dashboard (Tiles) + + + +
+

Overview Dashboard (Tiles)

+ + +

Beta License: AGPL-3 OCA/web Translate me on Weblate Try me on Runbot

+

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/OCA/web/12.0/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/OCA/web/12.0/web_dashboard_tile/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)
  • +
+https://raw.githubusercontent.com/OCA/web/12.0/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/OCA/web/12.0/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/OCA/web/12.0/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/OCA/web/12.0/web_dashboard_tile/static/description/favorite_menu_create_tile.png +https://raw.githubusercontent.com/OCA/web/12.0/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

+ +
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+Odoo Community Association +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

Current maintainer:

+

legalsylvain

+

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

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/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..9fdf02f31 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..ef503b7a2 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..8325f5ac8 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..7fa8aafdb 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..2d5d434fd 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..623c8078e --- /dev/null +++ b/web_dashboard_tile/static/src/css/web_dashboard_tile.css @@ -0,0 +1,46 @@ +/* custom kanban style */ +.o_kanban_view .oe_dashboard_tile { + height: 150px !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; + left: 5px; + right: 5px; + bottom: 5px; +} + +.o_kanban_view .oe_dashboard_tile .tile_secondary_value { + display: none; + font-size: 18px; + font-style: italic; + 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; +} 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..818ea71c4 --- /dev/null +++ b/web_dashboard_tile/views/menu.xml @@ -0,0 +1,11 @@ + + + + + + diff --git a/web_dashboard_tile/views/tile_category.xml b/web_dashboard_tile/views/tile_category.xml new file mode 100644 index 000000000..610998fcf --- /dev/null +++ b/web_dashboard_tile/views/tile_category.xml @@ -0,0 +1,73 @@ + + + + + Dashboard Items + tile.tile + tree,form + {'search_default_category_id': active_id} + + + + tile.category + +
+ +
+ +
+ +
+
+ + + + + +
+
+
+
+ + + tile.category + + + + + + + + + + + + Dashboard Categories + tile.category + 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..eb00f15a7 --- /dev/null +++ b/web_dashboard_tile/views/tile_tile.xml @@ -0,0 +1,231 @@ + + + + + tile.tile + + + + + + + + + + + tile.tile + + + + + + + + + + + + + + + + + + + tile.tile + +
+ + +

+ +

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