Merge PR #1428 into 12.0

Signed-off-by dreispt
pull/1532/head
OCA-git-bot 2021-11-26 09:52:20 +00:00
commit f323800197
34 changed files with 2170 additions and 0 deletions

View File

@ -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 <https://github.com/legalsylvain/web/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 <https://github.com/legalsylvain/web/issues/new?body=module:%20web_dashboard_tile%0Aversion:%2012.0-mig-web_dashboard_tile%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
Do not contact contributors directly about support or help with technical issues.
Credits
=======
Authors
~~~~~~~
* initOS GmbH & Co. KG
* GRAP
Contributors
~~~~~~~~~~~~
* Markus Schneider <markus.schneider at initos.com>
* Sylvain Le Gal (https://twitter.com/legalsylvain)
* Iván Todorovich <ivan.todorovich@gmail.com>
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 <https://github.com/legalsylvain/web/tree/12.0-mig-web_dashboard_tile/web_dashboard_tile>`_ project on GitHub.
You are welcome to contribute.

View File

@ -0,0 +1,2 @@
from . import controllers
from . import models

View File

@ -0,0 +1,28 @@
# © 2010-2013 OpenERP s.a. (<http://openerp.com>).
# © 2014 initOS GmbH & Co. KG (<http://www.initos.com>).
# 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 <ivan.todorovich@gmail.com>, "
"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"],
}

View File

@ -0,0 +1 @@
from . import main

View File

@ -0,0 +1,15 @@
# Copyright (C) 2019-Today: GTRAP (<http://www.grap.coop/>)
# @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)

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="category_module" model="tile.category">
<field name="name">Modules</field>
<field name="sequence">1</field>
<field name="active" eval="True"/>
</record>
<record id="category_currency" model="tile.category">
<field name="name">Currencies</field>
<field name="sequence">2</field>
<field name="active" eval="True"/>
</record>
</odoo>

View File

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="installed_modules" model="tile.tile">
<field name="name">Installed Modules</field>
<field name="category_id" ref="category_module"/>
<field name="model_id" ref="base.model_ir_module_module"/>
<field name="action_id" ref="base.open_module_tree"/>
<field name="domain">[['state', 'in', ['installed', 'to upgrade', 'to remove']]]</field>
</record>
<record id="installed_OCA_modules" model="tile.tile">
<field name="name">Installed OCA Modules</field>
<field name="category_id" ref="category_module"/>
<field name="model_id" ref="base.model_ir_module_module"/>
<field name="action_id" ref="base.open_module_tree"/>
<field name="domain">[['state', 'in', ['installed', 'to upgrade', 'to remove']], ['author', 'ilike', 'Odoo Community Association (OCA)']]</field>
</record>
<record id="all_currency_with_rate" model="tile.tile">
<field name="name">Currencies (Max Rate)</field>
<field name="category_id" ref="category_currency"/>
<field name="model_id" ref="base.model_res_currency"/>
<field name="secondary_function">max</field>
<field name="secondary_field_id" ref="base.field_res_currency__rate"/>
<field name="domain">[]</field>
</record>
</odoo>

View File

@ -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"

View File

@ -0,0 +1,29 @@
# Copyright (C) 2019-Today: GTRAP (<http://www.grap.coop/>)
# @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})

View File

@ -0,0 +1,2 @@
from . import tile_tile
from . import tile_category

View File

@ -0,0 +1,105 @@
# © 2018 Iván Todorovich <ivan.todorovich@gmail.com>
# © 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()

View File

@ -0,0 +1,383 @@
# © 2010-2013 OpenERP s.a. (<http://openerp.com>).
# © 2014 initOS GmbH & Co. KG (<http://www.initos.com>).
# © 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

View File

@ -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

View File

@ -0,0 +1,3 @@
* Markus Schneider <markus.schneider at initos.com>
* Sylvain Le Gal (https://twitter.com/legalsylvain)
* Iván Todorovich <ivan.todorovich@gmail.com>

View File

@ -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.*

View File

@ -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.

View File

@ -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

View File

@ -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
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 tile_user tile_user model_tile_tile base.group_user 1 1 1 1
3 tile_user_category tile_user model_tile_category base.group_user 1 1 1 1

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo noupdate="1">
<record id="model_tile_rule" model="ir.rule">
<field name="name">tile.owner</field>
<field name="model_id" ref="model_tile_tile" />
<field name="groups" eval="[(4, ref('base.group_user'))]"/>
<field name="domain_force">
[
'|',
('user_id','=',user.id),
('user_id','=',False),
'|',
('group_ids','=',False),
('group_ids','in',[g.id for g in user.groups_id]),
]
</field>
</record>
</odoo>

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -0,0 +1,495 @@
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils: http://docutils.sourceforge.net/" />
<title>Overview Dashboard (Tiles)</title>
<style type="text/css">
/*
:Author: David Goodger (goodger@python.org)
:Id: $Id: html4css1.css 7952 2016-07-26 18:15:59Z milde $
:Copyright: This stylesheet has been placed in the public domain.
Default cascading style sheet for the HTML output of Docutils.
See http://docutils.sf.net/docs/howto/html-stylesheets.html for how to
customize this style sheet.
*/
/* used to remove borders from tables and images */
.borderless, table.borderless td, table.borderless th {
border: 0 }
table.borderless td, table.borderless th {
/* Override padding for "table.docutils td" with "! important".
The right padding separates the table cells. */
padding: 0 0.5em 0 0 ! important }
.first {
/* Override more specific margin styles with "! important". */
margin-top: 0 ! important }
.last, .with-subtitle {
margin-bottom: 0 ! important }
.hidden {
display: none }
.subscript {
vertical-align: sub;
font-size: smaller }
.superscript {
vertical-align: super;
font-size: smaller }
a.toc-backref {
text-decoration: none ;
color: black }
blockquote.epigraph {
margin: 2em 5em ; }
dl.docutils dd {
margin-bottom: 0.5em }
object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] {
overflow: hidden;
}
/* Uncomment (and remove this text!) to get bold-faced definition list terms
dl.docutils dt {
font-weight: bold }
*/
div.abstract {
margin: 2em 5em }
div.abstract p.topic-title {
font-weight: bold ;
text-align: center }
div.admonition, div.attention, div.caution, div.danger, div.error,
div.hint, div.important, div.note, div.tip, div.warning {
margin: 2em ;
border: medium outset ;
padding: 1em }
div.admonition p.admonition-title, div.hint p.admonition-title,
div.important p.admonition-title, div.note p.admonition-title,
div.tip p.admonition-title {
font-weight: bold ;
font-family: sans-serif }
div.attention p.admonition-title, div.caution p.admonition-title,
div.danger p.admonition-title, div.error p.admonition-title,
div.warning p.admonition-title, .code .error {
color: red ;
font-weight: bold ;
font-family: sans-serif }
/* Uncomment (and remove this text!) to get reduced vertical space in
compound paragraphs.
div.compound .compound-first, div.compound .compound-middle {
margin-bottom: 0.5em }
div.compound .compound-last, div.compound .compound-middle {
margin-top: 0.5em }
*/
div.dedication {
margin: 2em 5em ;
text-align: center ;
font-style: italic }
div.dedication p.topic-title {
font-weight: bold ;
font-style: normal }
div.figure {
margin-left: 2em ;
margin-right: 2em }
div.footer, div.header {
clear: both;
font-size: smaller }
div.line-block {
display: block ;
margin-top: 1em ;
margin-bottom: 1em }
div.line-block div.line-block {
margin-top: 0 ;
margin-bottom: 0 ;
margin-left: 1.5em }
div.sidebar {
margin: 0 0 0.5em 1em ;
border: medium outset ;
padding: 1em ;
background-color: #ffffee ;
width: 40% ;
float: right ;
clear: right }
div.sidebar p.rubric {
font-family: sans-serif ;
font-size: medium }
div.system-messages {
margin: 5em }
div.system-messages h1 {
color: red }
div.system-message {
border: medium outset ;
padding: 1em }
div.system-message p.system-message-title {
color: red ;
font-weight: bold }
div.topic {
margin: 2em }
h1.section-subtitle, h2.section-subtitle, h3.section-subtitle,
h4.section-subtitle, h5.section-subtitle, h6.section-subtitle {
margin-top: 0.4em }
h1.title {
text-align: center }
h2.subtitle {
text-align: center }
hr.docutils {
width: 75% }
img.align-left, .figure.align-left, object.align-left, table.align-left {
clear: left ;
float: left ;
margin-right: 1em }
img.align-right, .figure.align-right, object.align-right, table.align-right {
clear: right ;
float: right ;
margin-left: 1em }
img.align-center, .figure.align-center, object.align-center {
display: block;
margin-left: auto;
margin-right: auto;
}
table.align-center {
margin-left: auto;
margin-right: auto;
}
.align-left {
text-align: left }
.align-center {
clear: both ;
text-align: center }
.align-right {
text-align: right }
/* reset inner alignment in figures */
div.align-right {
text-align: inherit }
/* div.align-center * { */
/* text-align: left } */
.align-top {
vertical-align: top }
.align-middle {
vertical-align: middle }
.align-bottom {
vertical-align: bottom }
ol.simple, ul.simple {
margin-bottom: 1em }
ol.arabic {
list-style: decimal }
ol.loweralpha {
list-style: lower-alpha }
ol.upperalpha {
list-style: upper-alpha }
ol.lowerroman {
list-style: lower-roman }
ol.upperroman {
list-style: upper-roman }
p.attribution {
text-align: right ;
margin-left: 50% }
p.caption {
font-style: italic }
p.credits {
font-style: italic ;
font-size: smaller }
p.label {
white-space: nowrap }
p.rubric {
font-weight: bold ;
font-size: larger ;
color: maroon ;
text-align: center }
p.sidebar-title {
font-family: sans-serif ;
font-weight: bold ;
font-size: larger }
p.sidebar-subtitle {
font-family: sans-serif ;
font-weight: bold }
p.topic-title {
font-weight: bold }
pre.address {
margin-bottom: 0 ;
margin-top: 0 ;
font: inherit }
pre.literal-block, pre.doctest-block, pre.math, pre.code {
margin-left: 2em ;
margin-right: 2em }
pre.code .ln { color: grey; } /* line numbers */
pre.code, code { background-color: #eeeeee }
pre.code .comment, code .comment { color: #5C6576 }
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
pre.code .literal.string, code .literal.string { color: #0C5404 }
pre.code .name.builtin, code .name.builtin { color: #352B84 }
pre.code .deleted, code .deleted { background-color: #DEB0A1}
pre.code .inserted, code .inserted { background-color: #A3D289}
span.classifier {
font-family: sans-serif ;
font-style: oblique }
span.classifier-delimiter {
font-family: sans-serif ;
font-weight: bold }
span.interpreted {
font-family: sans-serif }
span.option {
white-space: nowrap }
span.pre {
white-space: pre }
span.problematic {
color: red }
span.section-subtitle {
/* font-size relative to parent (h1..h6 element) */
font-size: 80% }
table.citation {
border-left: solid 1px gray;
margin-left: 1px }
table.docinfo {
margin: 2em 4em }
table.docutils {
margin-top: 0.5em ;
margin-bottom: 0.5em }
table.footnote {
border-left: solid 1px black;
margin-left: 1px }
table.docutils td, table.docutils th,
table.docinfo td, table.docinfo th {
padding-left: 0.5em ;
padding-right: 0.5em ;
vertical-align: top }
table.docutils th.field-name, table.docinfo th.docinfo-name {
font-weight: bold ;
text-align: left ;
white-space: nowrap ;
padding-left: 0 }
/* "booktabs" style (no vertical lines) */
table.docutils.booktabs {
border: 0px;
border-top: 2px solid;
border-bottom: 2px solid;
border-collapse: collapse;
}
table.docutils.booktabs * {
border: 0px;
}
table.docutils.booktabs th {
border-bottom: thin solid;
text-align: left;
}
h1 tt.docutils, h2 tt.docutils, h3 tt.docutils,
h4 tt.docutils, h5 tt.docutils, h6 tt.docutils {
font-size: 100% }
ul.auto-toc {
list-style-type: none }
</style>
</head>
<body>
<div class="document" id="overview-dashboard-tiles">
<h1 class="title">Overview Dashboard (Tiles)</h1>
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external" href="https://github.com/legalsylvain/web/tree/12.0-mig-web_dashboard_tile/web_dashboard_tile"><img alt="legalsylvain/web" src="https://img.shields.io/badge/github-legalsylvain%2Fweb-lightgray.png?logo=github" /></a></p>
<p>Adds a dashboard where you can configure tiles from any view and add them as short cut.</p>
<p>By default, the tile displays items count of a given model restricted to a given domain.</p>
<p>Optionally, the tile can display the result of a function on a field.</p>
<ul class="simple">
<li>Function is one of <tt class="docutils literal">sum</tt>, <tt class="docutils literal">avg</tt>, <tt class="docutils literal">min</tt>, <tt class="docutils literal">max</tt> or <tt class="docutils literal">median</tt>.</li>
<li>Field must be integer or float.</li>
</ul>
<p>Tile can be:</p>
<ul class="simple">
<li>Displayed only for a user.</li>
<li>Global for all users.</li>
<li>Restricted to some groups.</li>
</ul>
<p><em>Note: The tile will be hidden if the current user doesnt have access to the given model.</em></p>
<p><strong>Table of contents</strong></p>
<div class="contents local topic" id="contents">
<ul class="simple">
<li><a class="reference internal" href="#configuration" id="id1">Configuration</a></li>
<li><a class="reference internal" href="#usage" id="id2">Usage</a></li>
<li><a class="reference internal" href="#known-issues-roadmap" id="id3">Known issues / Roadmap</a></li>
<li><a class="reference internal" href="#bug-tracker" id="id4">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="id5">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="id6">Authors</a></li>
<li><a class="reference internal" href="#contributors" id="id7">Contributors</a></li>
<li><a class="reference internal" href="#maintainers" id="id8">Maintainers</a></li>
</ul>
</li>
</ul>
</div>
<div class="section" id="configuration">
<h1><a class="toc-backref" href="#id1">Configuration</a></h1>
<p>First, you have to create tile categories.</p>
<ul class="simple">
<li>Go to “Dashboards &gt; Settings &gt; Dashboard Categories”</li>
<li>Click on Create</li>
<li>Set a name, and save.</li>
</ul>
<p>Odoo menu and action are automatically created.
You should refresh your browser to see new menu items.</p>
<img alt="https://raw.githubusercontent.com/legalsylvain/web/12.0-mig-web_dashboard_tile/web_dashboard_tile/static/description/tile_category_form.png" src="https://raw.githubusercontent.com/legalsylvain/web/12.0-mig-web_dashboard_tile/web_dashboard_tile/static/description/tile_category_form.png" />
<p>Then you can create tiles.</p>
<ul class="simple">
<li>go to “Dashboards &gt; Settings &gt; Dashboard Tiles”</li>
<li>create a new tile, set a name, a category and a model.</li>
<li>You can optionally define colors, domain a specific action to use.</li>
<li>Setting a user, or a group in “Security” tab will restrict the display of the tile.</li>
</ul>
<img alt="https://raw.githubusercontent.com/legalsylvain/web/12.0-mig-web_dashboard_tile/web_dashboard_tile/static/description/tile_tile_form.png" src="https://raw.githubusercontent.com/legalsylvain/web/12.0-mig-web_dashboard_tile/web_dashboard_tile/static/description/tile_tile_form.png" />
<p>You can optionanaly define a secondary value, for that purpose :</p>
<ul class="simple">
<li>Select a field, a function to apply.</li>
<li>You can define a specific format. (<tt class="docutils literal">.format()</tt> python syntax)</li>
</ul>
<img alt="https://raw.githubusercontent.com/legalsylvain/web/12.0-mig-web_dashboard_tile/web_dashboard_tile/static/description/tile_tile_form_secondary_value.png" src="https://raw.githubusercontent.com/legalsylvain/web/12.0-mig-web_dashboard_tile/web_dashboard_tile/static/description/tile_tile_form_secondary_value.png" />
</div>
<div class="section" id="usage">
<h1><a class="toc-backref" href="#id2">Usage</a></h1>
<ul class="simple">
<li>Go to “Dashboard &gt; Overview” and select a category</li>
<li>The tile configured is displayed with the up to date count and average values of the selected domain.</li>
</ul>
<img alt="https://raw.githubusercontent.com/legalsylvain/web/12.0-mig-web_dashboard_tile/web_dashboard_tile/static/description/tile_tile_kanban.png" src="https://raw.githubusercontent.com/legalsylvain/web/12.0-mig-web_dashboard_tile/web_dashboard_tile/static/description/tile_tile_kanban.png" />
<ul class="simple">
<li>By clicking on the item, youll navigate to the tree view of the according model.</li>
</ul>
<img alt="https://raw.githubusercontent.com/legalsylvain/web/12.0-mig-web_dashboard_tile/web_dashboard_tile/static/description/tile_tile_2_tree_view.png" src="https://raw.githubusercontent.com/legalsylvain/web/12.0-mig-web_dashboard_tile/web_dashboard_tile/static/description/tile_tile_2_tree_view.png" />
<p><strong>Note</strong></p>
<p>When you are in a tree view, with a domain, you can save it in the favorite menu, but the configuration is limited.</p>
<img alt="https://raw.githubusercontent.com/legalsylvain/web/12.0-mig-web_dashboard_tile/web_dashboard_tile/static/description/favorite_menu_create_tile.png" src="https://raw.githubusercontent.com/legalsylvain/web/12.0-mig-web_dashboard_tile/web_dashboard_tile/static/description/favorite_menu_create_tile.png" />
<img alt="https://raw.githubusercontent.com/legalsylvain/web/12.0-mig-web_dashboard_tile/web_dashboard_tile/static/description/favorite_menu_create_tile_result.png" src="https://raw.githubusercontent.com/legalsylvain/web/12.0-mig-web_dashboard_tile/web_dashboard_tile/static/description/favorite_menu_create_tile_result.png" />
</div>
<div class="section" id="known-issues-roadmap">
<h1><a class="toc-backref" href="#id3">Known issues / Roadmap</a></h1>
<p><strong>Known issues</strong></p>
<ul class="simple">
<li>Can not edit color from dashboard</li>
<li>Original context is ignored.</li>
<li>Original domain and filter are not restored.</li>
<li>To preserve a relative date domain, you have to manually edit the tiles domain from “Configuration &gt; User Interface &gt; Dashboard Tile”. You can use the same variables available in filters (<tt class="docutils literal">uid</tt>, <tt class="docutils literal">context_today()</tt>, <tt class="docutils literal">current_date</tt>, <tt class="docutils literal">time</tt>, <tt class="docutils literal">datetime</tt>, <cite>relativedelta</cite>).</li>
</ul>
<p><strong>Roadmap</strong></p>
<ul class="simple">
<li>Add icons.</li>
<li>Support client side action (like inbox).</li>
<li>Restore original Domain + Filter when an action is set.</li>
<li>Posibility to hide the tile based on a field expression.</li>
<li>Posibility to set the background color based on a field expression.</li>
</ul>
</div>
<div class="section" id="bug-tracker">
<h1><a class="toc-backref" href="#id4">Bug Tracker</a></h1>
<p>Bugs are tracked on <a class="reference external" href="https://github.com/legalsylvain/web/issues">GitHub Issues</a>.
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
<a class="reference external" href="https://github.com/legalsylvain/web/issues/new?body=module:%20web_dashboard_tile%0Aversion:%2012.0-mig-web_dashboard_tile%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
<p>Do not contact contributors directly about support or help with technical issues.</p>
</div>
<div class="section" id="credits">
<h1><a class="toc-backref" href="#id5">Credits</a></h1>
<div class="section" id="authors">
<h2><a class="toc-backref" href="#id6">Authors</a></h2>
<ul class="simple">
<li>initOS GmbH &amp; Co. KG</li>
<li>GRAP</li>
</ul>
</div>
<div class="section" id="contributors">
<h2><a class="toc-backref" href="#id7">Contributors</a></h2>
<ul class="simple">
<li>Markus Schneider &lt;markus.schneider at initos.com&gt;</li>
<li>Sylvain Le Gal (<a class="reference external" href="https://twitter.com/legalsylvain">https://twitter.com/legalsylvain</a>)</li>
<li>Iván Todorovich &lt;<a class="reference external" href="mailto:ivan.todorovich&#64;gmail.com">ivan.todorovich&#64;gmail.com</a>&gt;</li>
</ul>
</div>
<div class="section" id="maintainers">
<h2><a class="toc-backref" href="#id8">Maintainers</a></h2>
<p>Current maintainer:</p>
<p><a class="reference external" href="https://github.com/legalsylvain"><img alt="legalsylvain" src="https://github.com/legalsylvain.png?size=40px" /></a></p>
<p>This module is part of the <a class="reference external" href="https://github.com/legalsylvain/web/tree/12.0-mig-web_dashboard_tile/web_dashboard_tile">legalsylvain/web</a> project on GitHub.</p>
<p>You are welcome to contribute.</p>
</div>
</div>
</div>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

View File

@ -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;
}

View File

@ -0,0 +1 @@
from . import test_tile

View File

@ -0,0 +1,63 @@
# © 2016 Antonio Espinosa - <antonio.espinosa@tecnativa.com>
# 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)

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<menuitem id="menu_dashboard_tile"
parent="base.menu_board_root"
name="Overview"
sequence="0"/>
<menuitem id="menu_dashboard_tile_settings"
parent="base.menu_board_root"
name="Settings"
sequence="100"/>
</odoo>

View File

@ -0,0 +1,63 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="action_category_2_tile" model="ir.actions.act_window">
<field name="name">Dashboard Items</field>
<field name="res_model">tile.tile</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="context">{'search_default_category_id': active_id}</field>
</record>
<record model="ir.ui.view" id="view_tile_category_form">
<field name="model">tile.category</field>
<field name="arch" type="xml">
<form>
<sheet>
<div class="oe_button_box" name="button_box">
<button name="toggle_active" type="object" class="oe_stat_button" icon="fa-archive">
<field name="active" widget="boolean_button" options="{&quot;terminology&quot;: &quot;archive&quot;}"/>
</button>
<button class="oe_stat_button" type="action" name="%(web_dashboard_tile.action_category_2_tile)d"
attrs="{'invisible': [('tile_qty', '=', 0)]}" icon="fa-list">
<field string="Items" name="tile_qty" widget="statinfo"/>
</button>
</div>
<div class="oe_title">
<label for="name" class="oe_edit_only"/>
<h1><field name="name" required="1"/></h1>
</div>
<group string="Technical Informations">
<field name="menu_id"/>
<field name="action_id"/>
</group>
</sheet>
</form>
</field>
</record>
<record model="ir.ui.view" id="view_tile_category_tree">
<field name="model">tile.category</field>
<field name="arch" type="xml">
<tree decoration-muted="(active == False)">
<field name="sequence" widget="handle"/>
<field name="name"/>
<field name="tile_qty"/>
<field name="active"/>
</tree>
</field>
</record>
<record model="ir.actions.act_window" id="action_tile_category">
<field name="name">Dashboard Categories</field>
<field name="res_model">tile.category</field>
<field name="view_type">form</field>
<field name="view_mode">tree,kanban,form</field>
<field name="context">{'active_test': False}</field>
</record>
<menuitem id="menu_tile_category"
parent="menu_dashboard_tile_settings"
action="action_tile_category" sequence="50"/>
</odoo>

View File

@ -0,0 +1,160 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_tile_tile_search" model="ir.ui.view">
<field name="model">tile.tile</field>
<field name="arch" type="xml">
<search>
<field name="name"/>
<field name="category_id"/>
<field name="model_id"/>
</search>
</field>
</record>
<record id="view_tile_tile_tree" model="ir.ui.view">
<field name="model">tile.tile</field>
<field name="arch" type="xml">
<tree>
<field name="sequence" widget="handle"/>
<field name="name"/>
<field name="category_id"/>
<field name="model_id"/>
<field name="primary_function"/>
<field name="primary_field_id"/>
<field name="secondary_function"/>
<field name="secondary_field_id"/>
<field name="user_id"/>
<field name="group_ids"/>
<field name="background_color" widget="color"/>
</tree>
</field>
</record>
<record id="view_tile_tile_form" model="ir.ui.view">
<field name="model">tile.tile</field>
<field name="arch" type="xml">
<form>
<sheet>
<h1>
<field name="name"/>
</h1>
<group>
<field name="category_id"/>
<field name="user_id"/>
</group>
<group col="4">
<separator string="Display" colspan="4"/>
<field name="background_color" widget="color"/>
<field name="font_color" widget="color"/>
<separator string="Technical Informations" colspan="4"/>
<field name="model_id"/>
<field name="action_id"/>
<field name="domain" colspan="4"/>
<field name="model_name" invisible="1"/>
<separator colspan="4"/>
<field name="error" attrs="{'invisible':[('error','=',False)]}"/>
</group>
<notebook>
<page string="Settings">
<group string="Main Value">
<group>
<field name="primary_function"/>
<field name="primary_field_id" attrs="{
'invisible':[('primary_function','in',[False,'count'])],
'required':[('primary_function','not in',[False,'count'])],
}"/>
</group>
<group>
<field name="primary_format"/>
<field name="hide_if_null"/>
</group>
<group>
<field name="primary_helper"/>
<field name="primary_value"/>
<field name="primary_formated_value" attrs="{'invisible':[('primary_value','=',False)]}"/>
</group>
</group>
<group string="Secondary Value">
<group>
<field name="secondary_function"/>
<field name="secondary_field_id" attrs="{
'invisible':[('secondary_function','in',[False,'count'])],
'required':[('secondary_function','not in',[False,'count'])],
}"/>
</group>
<group>
<field name="secondary_format"/>
</group>
<group>
<field name="secondary_helper"/>
<field name="secondary_value"/>
<field name="secondary_formated_value" attrs="{'invisible':[('secondary_value','=',False)]}"/>
</group>
</group>
</page>
<page string="Security">
<field name="group_ids"/>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
<record id="view_tile_tile_kanban" model="ir.ui.view">
<field name="model">tile.tile</field>
<field name="arch" type="xml">
<kanban edit="false" create="false">
<field name="name"/>
<field name="domain"/>
<field name="model_id"/>
<field name="action_id"/>
<field name="background_color"/>
<field name="font_color"/>
<field name="primary_function"/>
<field name="primary_helper"/>
<field name="secondary_function"/>
<field name="secondary_helper"/>
<templates>
<t t-name="kanban-box">
<div t-attf-class="oe_dashboard_tile oe_kanban_global_click" t-attf-style="background-color:#{record.background_color.raw_value}" >
<div class="oe_kanban_content">
<a type="object" name="open_link" args="[]" t-attf-style="color:#{record.font_color.raw_value};">
<div style="height:100%;" t-att-class="record.secondary_function.raw_value and 'with_secondary' or 'simple'">
<div class="tile_label">
<field name="name"/>
</div>
<div class="tile_primary_value" t-att-title="record.primary_helper.raw_value">
<t t-set="l" t-value="record.primary_formated_value.raw_value.length" />
<t t-set="s" t-value="l>=12 and 35 or l>=10 and 45 or l>=8 and 55 or l>=6 and 75 or l>4 and 85 or 100"/>
<span t-attf-style="font-size: #{s}%;"><field name="primary_formated_value"/></span>
</div>
<div class="tile_secondary_value" t-att-title="record.secondary_helper.raw_value">
<span><field name="secondary_formated_value"/></span>
</div>
</div>
</a>
</div>
<div class="oe_clear"></div>
</div>
</t>
</templates>
</kanban>
</field>
</record>
<record model="ir.actions.act_window" id="action_tile_tile">
<field name="name">Dashboard Items</field>
<field name="res_model">tile.tile</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form,kanban</field>
</record>
<menuitem id="menu_tile_tile"
parent="menu_dashboard_tile_settings"
action="action_tile_tile" sequence="10"/>
</odoo>