[MIG] web_dashboard_tile from 12.0 to 16.0

- remove 12.0 migration scripts
- bump version to 16.0
- use new api convention @api.model_create_multi ; new compute function logic ;
- add dependency to spreasheet_dashboard to use 'Dashboard' main menu item
- use new way to include assets
- remove totally useless controllers
- distinct different errors, depending on domain or format errors
- fix : _compute_data depends on many fields
- update : documentation and printscreens
pull/2325/head
Sylvain LE GAL 2022-10-26 21:05:31 +02:00
parent 0d15ce1df8
commit dd52e48dce
26 changed files with 272 additions and 320 deletions

View File

@ -113,7 +113,7 @@ Known issues / Roadmap
* 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`).
* To preserve a relative date domain, you have to manually edit the tile's domain from "Configuration > User Interface > Dashboard Tile". You can use the same variables available in filters (``uid``, ``context_today()``, ``current_date``, ``relativedelta``).
**Roadmap**

View File

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

View File

@ -4,8 +4,11 @@
{
"name": "Overview Dashboard (Tiles)",
"summary": "Add Overview Dashboards with Tiles",
"version": "12.0.1.0.2",
"depends": ["web", "board", "mail", "web_widget_color"],
"version": "16.0.1.0.2",
"depends": [
"web",
"spreadsheet_dashboard",
],
"author": "initOS GmbH & Co. KG, "
"GRAP, "
"Iván Todorovich <ivan.todorovich@gmail.com>, "
@ -17,11 +20,15 @@
"data": [
"security/ir.model.access.csv",
"security/ir_rule.xml",
"views/templates.xml",
"views/menu.xml",
"views/tile_tile.xml",
"views/tile_category.xml",
],
"assets": {
"web.assets_common": [
"web_dashboard_tile/static/src/css/web_dashboard_tile.css",
],
},
"demo": [
"demo/tile_category.xml",
"demo/tile_tile.xml",

View File

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

View File

@ -1,14 +0,0 @@
# 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, request, route
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

@ -4,23 +4,21 @@
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 12.0\n"
"Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-08-02 15:38+0000\n"
"PO-Revision-Date: 2022-01-19 13:10+0000\n"
"Last-Translator: Yann Papouin <y.papouin@dec-industrie.com>\n"
"POT-Creation-Date: 2022-10-26 21:53+0000\n"
"PO-Revision-Date: 2022-10-26 21:53+0000\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: fr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: nplurals=2; plural=n > 1;\n"
"X-Generator: Weblate 4.3.2\n"
"Plural-Forms: \n"
#. module: web_dashboard_tile
#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_tile__action_id
msgid "Action"
msgstr "Action"
msgstr ""
#. module: web_dashboard_tile
#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_category__active
@ -29,13 +27,18 @@ msgid "Active"
msgstr "Actif"
#. module: web_dashboard_tile
#: selection:tile.tile,primary_function:0
#: selection:tile.tile,secondary_function:0
#: model_terms:ir.ui.view,arch_db:web_dashboard_tile.view_tile_category_form
msgid "Archived"
msgstr "Archivée"
#. module: web_dashboard_tile
#: model:ir.model.fields.selection,name:web_dashboard_tile.selection__tile_tile__primary_function__avg
#: model:ir.model.fields.selection,name:web_dashboard_tile.selection__tile_tile__secondary_function__avg
msgid "Average"
msgstr "Moyenne"
#. module: web_dashboard_tile
#: code:addons/web_dashboard_tile/models/tile_tile.py:53
#: code:addons/web_dashboard_tile/models/tile_tile.py:0
#, python-format
msgid "Average value of '%s'"
msgstr "Valeur moyenne du champ '%s'"
@ -51,10 +54,10 @@ msgid "Category"
msgstr "Catégorie"
#. module: web_dashboard_tile
#: selection:tile.tile,primary_function:0
#: selection:tile.tile,secondary_function:0
#: model:ir.model.fields.selection,name:web_dashboard_tile.selection__tile_tile__primary_function__count
#: model:ir.model.fields.selection,name:web_dashboard_tile.selection__tile_tile__secondary_function__count
msgid "Count"
msgstr "Nbr"
msgstr "Décompte"
#. module: web_dashboard_tile
#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_category__create_uid
@ -108,17 +111,18 @@ msgid "Domain"
msgstr "Domaine"
#. module: web_dashboard_tile
#: 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/models/tile_tile.py:0
#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_tile__domain_error
#, python-format
msgid "Domain Error"
msgstr "Erreur sur le domaine"
#. module: web_dashboard_tile
#: code:addons/web_dashboard_tile/models/tile_tile.py:0
#, python-format
msgid "Error"
msgstr "Erreur"
#. module: web_dashboard_tile
#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_tile__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"
@ -143,7 +147,7 @@ msgstr "Cacher si non défini"
#: 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 "ID"
msgstr ""
#. module: web_dashboard_tile
#: model:ir.model.fields,help:web_dashboard_tile.field_tile_tile__hide_if_null
@ -198,37 +202,37 @@ msgid "Main Value"
msgstr "Valeur principale"
#. module: web_dashboard_tile
#: selection:tile.tile,primary_function:0
#: selection:tile.tile,secondary_function:0
#: model:ir.model.fields.selection,name:web_dashboard_tile.selection__tile_tile__primary_function__max
#: model:ir.model.fields.selection,name:web_dashboard_tile.selection__tile_tile__secondary_function__max
msgid "Maximum"
msgstr "Maximum"
msgstr ""
#. module: web_dashboard_tile
#: code:addons/web_dashboard_tile/models/tile_tile.py:41
#: code:addons/web_dashboard_tile/models/tile_tile.py:0
#, python-format
msgid "Maximum value of '%s'"
msgstr "Valeur maximale du champ '%s'"
#. module: web_dashboard_tile
#: selection:tile.tile,primary_function:0
#: selection:tile.tile,secondary_function:0
#: model:ir.model.fields.selection,name:web_dashboard_tile.selection__tile_tile__primary_function__median
#: model:ir.model.fields.selection,name:web_dashboard_tile.selection__tile_tile__secondary_function__median
msgid "Median"
msgstr "Médiane"
#. module: web_dashboard_tile
#: code:addons/web_dashboard_tile/models/tile_tile.py:61
#: code:addons/web_dashboard_tile/models/tile_tile.py:0
#, python-format
msgid "Median value of '%s'"
msgstr "Valeur médiane du champ '%s'"
#. module: web_dashboard_tile
#: selection:tile.tile,primary_function:0
#: selection:tile.tile,secondary_function:0
#: model:ir.model.fields.selection,name:web_dashboard_tile.selection__tile_tile__primary_function__min
#: model:ir.model.fields.selection,name:web_dashboard_tile.selection__tile_tile__secondary_function__min
msgid "Minimum"
msgstr "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:0
#, python-format
msgid "Minimum value of '%s'"
msgstr "Valeur minimale du champ '%s'"
@ -250,7 +254,7 @@ msgid "Name"
msgstr "Nom"
#. module: web_dashboard_tile
#: code:addons/web_dashboard_tile/models/tile_tile.py:25
#: code:addons/web_dashboard_tile/models/tile_tile.py:0
#, python-format
msgid "Number of records"
msgstr "Nombre d'enregistrements"
@ -271,11 +275,21 @@ msgid "Overview"
msgstr "Vue d'ensemble"
#. module: web_dashboard_tile
#: code:addons/web_dashboard_tile/models/tile_tile.py:317
#: model:ir.ui.menu,name:web_dashboard_tile.menu_tile_configuration
msgid "Overview Settings"
msgstr "Paramétrage de la vue d'ensemble"
#. module: web_dashboard_tile
#: code:addons/web_dashboard_tile/models/tile_tile.py:0
#, python-format
msgid "Please select a field from the selected model."
msgstr "Veuillez sélectionner un champ correspondant au modèle sélectionné."
#. module: web_dashboard_tile
#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_tile__primary_error
msgid "Primary Error"
msgstr "Erreur principale"
#. module: web_dashboard_tile
#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_tile__primary_field_id
msgid "Primary Field"
@ -316,6 +330,11 @@ msgstr ""
"Chaîne de format python valide, avec str.format()\n"
"par exemple: {:,} Kgs' affichera '1000 Kgs' si la valeur est 1000."
#. module: web_dashboard_tile
#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_tile__secondary_error
msgid "Secondary Error"
msgstr "Erreur secondaire"
#. module: web_dashboard_tile
#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_tile__secondary_field_id
msgid "Secondary Field"
@ -359,14 +378,13 @@ 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
#: model:ir.model.fields.selection,name:web_dashboard_tile.selection__tile_tile__primary_function__sum
#: model:ir.model.fields.selection,name:web_dashboard_tile.selection__tile_tile__secondary_function__sum
msgid "Sum"
msgstr "Somme"
@ -387,13 +405,13 @@ msgid "Tiles Quantity"
msgstr "Nombre de tuiles"
#. module: web_dashboard_tile
#: code:addons/web_dashboard_tile/models/tile_tile.py:46
#: code:addons/web_dashboard_tile/models/tile_tile.py:0
#, python-format
msgid "Total value of '%s'"
msgstr "Somme du champ '%s'"
#. module: web_dashboard_tile
#: code:addons/web_dashboard_tile/models/tile_tile.py:288
#: code:addons/web_dashboard_tile/models/tile_tile.py:0
#, python-format
msgid "Unimplemented Feature. Search on Active field disabled."
msgstr ""
@ -404,24 +422,3 @@ msgstr ""
#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_tile__user_id
msgid "User"
msgstr "Utilisateur"
#~ msgid "'%s' added to the overview dashboard"
#~ msgstr "'%s' a été ajouté au tableau de bord synthétique"
#~ msgid "Add"
#~ msgstr "Ajouter"
#~ msgid "Add to the Overview Dashboard"
#~ msgstr "Ajouter au tableau de bord synthétique"
#~ msgid "Could not add new element to the overview dashboard"
#~ msgstr ""
#~ "Impossible d'ajouter un nouvel élément au tableau de bord synthétique"
#~ msgid "Name Field is required."
#~ msgstr "Le nom du champ est requis."
#~ msgid "Please refresh your browser for the changes to take effect."
#~ msgstr ""
#~ "Veuillez rafraichir votre navigateur pour que les changements prennent "
#~ "effets."

View File

@ -1,12 +1,14 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * web_dashboard_tile
# * web_dashboard_tile
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 12.0\n"
"Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n"
"Last-Translator: <>\n"
"POT-Creation-Date: 2022-10-26 21:53+0000\n"
"PO-Revision-Date: 2022-10-26 21:53+0000\n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@ -25,13 +27,18 @@ msgid "Active"
msgstr ""
#. module: web_dashboard_tile
#: selection:tile.tile,primary_function:0
#: selection:tile.tile,secondary_function:0
#: model_terms:ir.ui.view,arch_db:web_dashboard_tile.view_tile_category_form
msgid "Archived"
msgstr ""
#. module: web_dashboard_tile
#: model:ir.model.fields.selection,name:web_dashboard_tile.selection__tile_tile__primary_function__avg
#: model:ir.model.fields.selection,name:web_dashboard_tile.selection__tile_tile__secondary_function__avg
msgid "Average"
msgstr ""
#. module: web_dashboard_tile
#: code:addons/web_dashboard_tile/models/tile_tile.py:53
#: code:addons/web_dashboard_tile/models/tile_tile.py:0
#, python-format
msgid "Average value of '%s'"
msgstr ""
@ -47,8 +54,8 @@ msgid "Category"
msgstr ""
#. module: web_dashboard_tile
#: selection:tile.tile,primary_function:0
#: selection:tile.tile,secondary_function:0
#: model:ir.model.fields.selection,name:web_dashboard_tile.selection__tile_tile__primary_function__count
#: model:ir.model.fields.selection,name:web_dashboard_tile.selection__tile_tile__secondary_function__count
msgid "Count"
msgstr ""
@ -104,15 +111,16 @@ msgid "Domain"
msgstr ""
#. module: web_dashboard_tile
#: 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/models/tile_tile.py:0
#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_tile__domain_error
#, python-format
msgid "Error"
msgid "Domain Error"
msgstr ""
#. module: web_dashboard_tile
#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_tile__error
msgid "Error Details"
#: code:addons/web_dashboard_tile/models/tile_tile.py:0
#, python-format
msgid "Error"
msgstr ""
#. module: web_dashboard_tile
@ -148,7 +156,10 @@ msgstr ""
#. module: web_dashboard_tile
#: model:ir.model.fields,help:web_dashboard_tile.field_tile_tile__group_ids
msgid "If this field is set, only users of this group can view this tile. Please note that it will only work for global tiles (that is, when User field is left empty)"
msgid ""
"If this field is set, only users of this group can view this tile. Please "
"note that it will only work for global tiles (that is, when User field is "
"left empty)"
msgstr ""
#. module: web_dashboard_tile
@ -185,37 +196,37 @@ msgid "Main Value"
msgstr ""
#. module: web_dashboard_tile
#: selection:tile.tile,primary_function:0
#: selection:tile.tile,secondary_function:0
#: model:ir.model.fields.selection,name:web_dashboard_tile.selection__tile_tile__primary_function__max
#: model:ir.model.fields.selection,name:web_dashboard_tile.selection__tile_tile__secondary_function__max
msgid "Maximum"
msgstr ""
#. module: web_dashboard_tile
#: code:addons/web_dashboard_tile/models/tile_tile.py:41
#: code:addons/web_dashboard_tile/models/tile_tile.py:0
#, python-format
msgid "Maximum value of '%s'"
msgstr ""
#. module: web_dashboard_tile
#: selection:tile.tile,primary_function:0
#: selection:tile.tile,secondary_function:0
#: model:ir.model.fields.selection,name:web_dashboard_tile.selection__tile_tile__primary_function__median
#: model:ir.model.fields.selection,name:web_dashboard_tile.selection__tile_tile__secondary_function__median
msgid "Median"
msgstr ""
#. module: web_dashboard_tile
#: code:addons/web_dashboard_tile/models/tile_tile.py:61
#: code:addons/web_dashboard_tile/models/tile_tile.py:0
#, python-format
msgid "Median value of '%s'"
msgstr ""
#. module: web_dashboard_tile
#: selection:tile.tile,primary_function:0
#: selection:tile.tile,secondary_function:0
#: model:ir.model.fields.selection,name:web_dashboard_tile.selection__tile_tile__primary_function__min
#: model:ir.model.fields.selection,name:web_dashboard_tile.selection__tile_tile__secondary_function__min
msgid "Minimum"
msgstr ""
#. module: web_dashboard_tile
#: code:addons/web_dashboard_tile/models/tile_tile.py:33
#: code:addons/web_dashboard_tile/models/tile_tile.py:0
#, python-format
msgid "Minimum value of '%s'"
msgstr ""
@ -237,7 +248,7 @@ msgid "Name"
msgstr ""
#. module: web_dashboard_tile
#: code:addons/web_dashboard_tile/models/tile_tile.py:25
#: code:addons/web_dashboard_tile/models/tile_tile.py:0
#, python-format
msgid "Number of records"
msgstr ""
@ -258,11 +269,21 @@ msgid "Overview"
msgstr ""
#. module: web_dashboard_tile
#: code:addons/web_dashboard_tile/models/tile_tile.py:317
#: model:ir.ui.menu,name:web_dashboard_tile.menu_tile_configuration
msgid "Overview Settings"
msgstr ""
#. module: web_dashboard_tile
#: code:addons/web_dashboard_tile/models/tile_tile.py:0
#, python-format
msgid "Please select a field from the selected model."
msgstr ""
#. module: web_dashboard_tile
#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_tile__primary_error
msgid "Primary Error"
msgstr ""
#. module: web_dashboard_tile
#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_tile__primary_field_id
msgid "Primary Field"
@ -296,10 +317,16 @@ msgstr ""
#. module: web_dashboard_tile
#: model:ir.model.fields,help:web_dashboard_tile.field_tile_tile__primary_format
#: model:ir.model.fields,help:web_dashboard_tile.field_tile_tile__secondary_format
msgid "Python Format String valid with str.format()\n"
msgid ""
"Python Format String valid with str.format()\n"
"ie: '{:,} Kgs' will output '1,000 Kgs' if value is 1000."
msgstr ""
#. module: web_dashboard_tile
#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_tile__secondary_error
msgid "Secondary Error"
msgstr ""
#. module: web_dashboard_tile
#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_tile__secondary_field_id
msgid "Secondary Field"
@ -343,14 +370,13 @@ msgid "Sequence"
msgstr ""
#. 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 ""
#. module: web_dashboard_tile
#: selection:tile.tile,primary_function:0
#: selection:tile.tile,secondary_function:0
#: model:ir.model.fields.selection,name:web_dashboard_tile.selection__tile_tile__primary_function__sum
#: model:ir.model.fields.selection,name:web_dashboard_tile.selection__tile_tile__secondary_function__sum
msgid "Sum"
msgstr ""
@ -371,13 +397,13 @@ msgid "Tiles Quantity"
msgstr ""
#. module: web_dashboard_tile
#: code:addons/web_dashboard_tile/models/tile_tile.py:46
#: code:addons/web_dashboard_tile/models/tile_tile.py:0
#, python-format
msgid "Total value of '%s'"
msgstr ""
#. module: web_dashboard_tile
#: code:addons/web_dashboard_tile/models/tile_tile.py:288
#: code:addons/web_dashboard_tile/models/tile_tile.py:0
#, python-format
msgid "Unimplemented Feature. Search on Active field disabled."
msgstr ""
@ -385,5 +411,4 @@ msgstr ""
#. module: web_dashboard_tile
#: model:ir.model.fields,field_description:web_dashboard_tile.field_tile_tile__user_id
msgid "User"
msgstr ""
msgstr ""

View File

@ -1,27 +0,0 @@
# 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

@ -82,12 +82,11 @@ class TileCategory(models.Model):
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
@api.model_create_multi
def create(self, vals_list):
categories = super().create(vals_list)
categories.filtered(lambda x: x.active)._create_ui()
return categories
def write(self, vals):
res = super().write(vals)
@ -105,4 +104,4 @@ class TileCategory(models.Model):
def unlink(self):
self._delete_ui()
super().unlink()
return super().unlink()

View File

@ -3,8 +3,6 @@
# © 2015-Today GRAP
# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html
import datetime
import time
from collections import OrderedDict
from statistics import median
@ -12,7 +10,7 @@ from dateutil.relativedelta import relativedelta
from odoo import api, fields, models
from odoo.exceptions import ValidationError, except_orm
from odoo.tools.safe_eval import safe_eval as eval
from odoo.tools.safe_eval import safe_eval
from odoo.tools.translate import _
FIELD_FUNCTIONS = OrderedDict(
@ -101,11 +99,15 @@ class TileTile(models.Model):
"(that is, when User field is left empty)",
)
model_id = fields.Many2one(comodel_name="ir.model", string="Model", required=True)
model_id = fields.Many2one(
comodel_name="ir.model", string="Model", required=True, ondelete="cascade"
)
model_name = fields.Char(string="Model name", related="model_id.model")
domain = fields.Text(default="[]")
domain = fields.Text(default="[]", required=True)
domain_error = fields.Char(compute="_compute_data")
action_id = fields.Many2one(
comodel_name="ir.actions.act_window",
@ -123,13 +125,10 @@ class TileTile(models.Model):
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"
)
hidden = fields.Boolean(compute="_compute_data", search="_search_hidden")
# Primary Value
primary_function = fields.Selection(
string="Primary Function",
required=True,
selection=FIELD_FUNCTION_SELECTION,
default="count",
@ -143,24 +142,20 @@ class TileTile(models.Model):
)
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_value = fields.Float(compute="_compute_data")
primary_formated_value = fields.Char(
string="Primary Formated Value", compute="_compute_data"
)
primary_formated_value = fields.Char(compute="_compute_data")
primary_helper = fields.Char(
string="Primary Helper", compute="_compute_helper", store=True
)
primary_helper = fields.Char(compute="_compute_helper", store=True)
primary_error = fields.Char(compute="_compute_data")
# Secondary Value
secondary_function = fields.Selection(
string="Secondary Function",
selection=FIELD_FUNCTION_SELECTION,
)
@ -172,44 +167,61 @@ class TileTile(models.Model):
)
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_value = fields.Float(compute="_compute_data")
secondary_formated_value = fields.Char(
string="Secondary Formated Value", compute="_compute_data"
)
secondary_formated_value = fields.Char(compute="_compute_data")
secondary_helper = fields.Char(
string="Secondary Helper", compute="_compute_helper", store=True
)
secondary_helper = fields.Char(compute="_compute_helper", store=True)
error = fields.Char(string="Error Details", compute="_compute_data")
secondary_error = fields.Char(compute="_compute_data")
# Compute Section
@api.depends("primary_format", "secondary_format", "model_id", "domain")
@api.depends(
"model_id",
"domain",
"primary_format",
"primary_function",
"primary_field_id",
"secondary_format",
"secondary_function",
"secondary_field_id",
)
def _compute_data(self):
for tile in self:
# initialize all vals
tile.hidden = False
tile.primary_value = False
tile.primary_formated_value = False
tile.secondary_value = False
tile.secondary_formated_value = False
tile.domain_error = False
tile.primary_error = False
tile.secondary_error = False
if not tile.model_id or not tile.active:
return
model = self.env[tile.model_id.model]
eval_context = self._get_eval_context()
domain = tile.domain or "[]"
try:
count = model.search_count(eval(domain, eval_context))
count = model.search_count(safe_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)
tile.primary_formated_value = tile.secondary_formated_value = _(
"Domain Error"
)
tile.domain_error = str(e)
return
fields = [
f.name for f in [tile.primary_field_id, tile.secondary_field_id] if f
]
read_vals = (
fields and model.search_read(eval(domain, eval_context), fields) or []
fields
and model.search_read(safe_eval(domain, eval_context), fields)
or []
)
for f in ["primary_", "secondary_"]:
f_function = f + "function"
@ -217,28 +229,25 @@ class TileTile(models.Model):
f_format = f + "format"
f_value = f + "value"
f_formated_value = f + "formated_value"
value = 0
f_error = f + "error"
if not tile[f_function]:
tile[f_value] = 0.0
tile[f_formated_value] = False
continue
elif tile[f_function] == "count":
value = count
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)
func = FIELD_FUNCTIONS[tile[f_function]]["func"]
vals = [x[tile[f_field_id].name] for x in read_vals]
value = func(vals or [0.0])
tile[f_value] = value
try:
tile[f_formated_value] = (tile[f_format] or "{:,}").format(value)
except ValueError as e:
tile[f_formated_value] = _("Error")
tile[f_error] = str(e)
tile.hidden = tile.hide_if_null and not tile.primary_value
@api.depends(
"primary_function",
@ -254,19 +263,17 @@ class TileTile(models.Model):
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
help_text = field_func.get("help", False)
if help_text and tile[f_function] != "count" and tile[f_field_id]:
tile[f_helper] = help_text % tile[f_field_id].field_description
else:
tile[f_helper] = help_text
def _compute_active(self):
ima = self.env["ir.model.access"]
IrModelAccess = self.env["ir.model.access"]
for tile in self:
if tile.model_id:
tile.active = ima.check(tile.model_id.model, "read", False)
tile.active = IrModelAccess.check(tile.model_id.model, "read", False)
else:
tile.active = True
@ -288,7 +295,7 @@ class TileTile(models.Model):
raise except_orm(
_("Unimplemented Feature. Search on Active field disabled.")
)
ima = self.env["ir.model.access"]
IrModelAccess = self.env["ir.model.access"]
ids = []
cr.execute(
"""
@ -298,20 +305,20 @@ class TileTile(models.Model):
ON tt.model_id = im.id"""
)
for result in cr.fetchall():
if ima.check(result[1], "read", False) == value:
if IrModelAccess.check(result[1], "read", False) == value:
ids.append(result[0])
return [("id", "in", ids)]
# Constraints Sections
@api.constrains("model_id", "primary_field_id", "secondary_field_id")
def _check_model_id_field_id(self):
for rec in self:
for tile 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,
tile.primary_field_id
and tile.primary_field_id.model_id.id != tile.model_id.id,
tile.secondary_field_id
and tile.secondary_field_id.model_id.id != tile.model_id.id,
]
):
raise ValidationError(
@ -333,13 +340,11 @@ class TileTile(models.Model):
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,
@ -357,27 +362,15 @@ class TileTile(models.Model):
)
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,
"context_today": fields.Date.from_string(
fields.Date.context_today(self)
),
"current_date": fields.Date.today(),
}
)

View File

@ -1,23 +1,21 @@
First, you have to create tile categories.
* Go to "Dashboards > Settings > Dashboard Categories"
* Go to "Dashboards > Configuration > Overview Settings > Dashboard Categories"
* Click on Create
* Create categories
* Set a name, and save.
Odoo menu and action are automatically created.
Odoo menu and action are automatically created in the "Dashboard > Overview" menu
You should refresh your browser to see new menu items.
.. image:: ../static/description/tile_category_form.png
Then you can create tiles.
* go to "Dashboards > Settings > Dashboard Tiles"
* Go to "Dashboards > Configuration > Overview Settings > Dashboard Items"
* create a new tile, set a name, a category and a model.
* You can optionally define colors, domain a specific action to use.
* You can optionally define colors, domain and a specific action to use.
* Setting a user, or a group in "Security" tab will restrict the display of the tile.

View File

@ -1,4 +1,4 @@
Adds a dashboard where you can configure tiles from any view and add them as short cut.
This module extends the web module to add new dashboard overview system.
By default, the tile displays items count of a given model restricted to a given domain.

View File

@ -3,7 +3,7 @@
* 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`).
* To preserve a relative date domain, you have to manually edit the tile's domain from "Configuration > User Interface > Dashboard Tile". You can use the same variables available in filters (``uid``, ``context_today()``, ``current_date``, ``relativedelta``).
**Roadmap**

View File

@ -7,11 +7,3 @@
* 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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 132 KiB

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.0 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

View File

@ -1,12 +1,8 @@
/* custom kanban style */
.o_kanban_view .oe_dashboard_tile {
height: 150px;
height: 150px !important;
}
/* 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%;
@ -26,7 +22,6 @@
.o_kanban_view .oe_dashboard_tile .tile_primary_value {
font-size: 54px;
position: absolute;
left: 5px;
right: 5px;
bottom: 5px;
@ -36,7 +31,6 @@
display: none;
font-size: 18px;
font-style: italic;
position: absolute;
left: 5px;
right: 5px;
bottom: 5px;
@ -50,10 +44,3 @@
.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

@ -2,17 +2,10 @@
<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"
id="menu_tile_configuration"
name="Overview Settings"
parent="spreadsheet_dashboard.spreadsheet_dashboard_menu_configuration"
sequence="160"
/>
</odoo>

View File

@ -1,13 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<template id="assets_backend" inherit_id="web.assets_backend">
<xpath expr="." position="inside">
<link
rel="stylesheet"
href="/web_dashboard_tile/static/src/css/web_dashboard_tile.css"
/>
</xpath>
</template>
</odoo>

View File

@ -4,7 +4,6 @@
<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>
@ -15,18 +14,6 @@
<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"
@ -37,11 +24,18 @@
<field string="Items" name="tile_qty" widget="statinfo" />
</button>
</div>
<widget
name="web_ribbon"
title="Archived"
bg_color="bg-danger"
attrs="{'invisible': [('active', '=', True)]}"
/>
<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="active" invisible="1" />
<field name="menu_id" />
<field name="action_id" />
</group>
@ -65,16 +59,15 @@
<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"
parent="menu_tile_configuration"
action="action_tile_category"
sequence="50"
sequence="160"
/>
</odoo>

View File

@ -35,27 +35,30 @@
<field name="model">tile.tile</field>
<field name="arch" type="xml">
<form>
<field name="model_name" invisible="1" />
<sheet>
<h1>
<field name="name" />
</h1>
<group>
<field name="category_id" />
<field name="user_id" />
</group>
<group col="4">
<separator string="Display" colspan="4" />
<group col="4" string="Display">
<field name="category_id" colspan="4" />
<field name="background_color" widget="color" />
<field name="font_color" widget="color" />
<separator string="Technical Informations" colspan="4" />
</group>
<group col="4" string="Technical Informations">
<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)]}"
name="domain"
colspan="4"
widget="ace"
option="{'mode': 'python'}"
/>
<field
name="domain_error"
attrs="{'invisible':[('domain_error','=',False)]}"
nolabel="1"
colspan="4"
/>
</group>
<notebook>
@ -83,6 +86,12 @@
attrs="{'invisible':[('primary_value','=',False)]}"
/>
</group>
<group>
<field
name="primary_error"
attrs="{'invisible': [('primary_error', '=', False)]}"
/>
</group>
</group>
<group string="Secondary Value">
<group>
@ -106,10 +115,17 @@
attrs="{'invisible':[('secondary_value','=',False)]}"
/>
</group>
<group>
<field
name="secondary_error"
attrs="{'invisible': [('secondary_error', '=', False)]}"
/>
</group>
</group>
</page>
<page string="Security">
<field name="group_ids" />
<field name="user_id" />
</page>
</notebook>
</sheet>
@ -193,15 +209,23 @@
<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"
parent="menu_tile_configuration"
action="action_tile_tile"
sequence="10"
sequence="161"
/>
<!-- Root menu item that will contains all the sub menu entries
that contains tiles -->
<menuitem
id="menu_dashboard_tile"
parent="spreadsheet_dashboard.spreadsheet_dashboard_menu_root"
name="Overview"
sequence="0"
/>
</odoo>