|
@ -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.
|
|
@ -0,0 +1,2 @@
|
|||
from . import controllers
|
||||
from . import models
|
|
@ -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"],
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
from . import main
|
|
@ -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)
|
|
@ -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>
|
|
@ -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>
|
|
@ -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"
|
||||
|
|
@ -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})
|
|
@ -0,0 +1,2 @@
|
|||
from . import tile_tile
|
||||
from . import tile_category
|
|
@ -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()
|
|
@ -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
|
|
@ -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
|
||||
|
|
@ -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>
|
|
@ -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.*
|
|
@ -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.
|
||||
|
|
@ -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
|
|
@ -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
|
|
|
@ -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>
|
After Width: | Height: | Size: 49 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 1.0 KiB |
|
@ -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 doesn’t 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 > Settings > 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 > Settings > 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 > 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, you’ll 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 tile’s domain from “Configuration > User Interface > 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 & 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 <markus.schneider at initos.com></li>
|
||||
<li>Sylvain Le Gal (<a class="reference external" href="https://twitter.com/legalsylvain">https://twitter.com/legalsylvain</a>)</li>
|
||||
<li>Iván Todorovich <<a class="reference external" href="mailto:ivan.todorovich@gmail.com">ivan.todorovich@gmail.com</a>></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>
|
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 132 KiB |
After Width: | Height: | Size: 49 KiB |
After Width: | Height: | Size: 29 KiB |
After Width: | Height: | Size: 8.0 KiB |
|
@ -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;
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
from . import test_tile
|
|
@ -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)
|
|
@ -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>
|
|
@ -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="{"terminology": "archive"}"/>
|
||||
</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>
|
|
@ -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>
|