From 591680f99ca5ebaf5acd798c685910736e532572 Mon Sep 17 00:00:00 2001 From: Christophe Simonis Date: Thu, 9 Aug 2012 17:30:27 +0200 Subject: [PATCH] [IMP] merge of modules board and web_dashboard bzr revid: chs@openerp.com-20120809153027-3sl125kda0jtdrcg --- addons/board/__init__.py | 7 +- addons/board/__openerp__.py | 15 +- addons/board/board.py | 166 +++++--- addons/board/board_data_admin.xml | 6 - addons/board/board_data_home.xml | 41 -- addons/board/board_demo.xml | 5 - addons/board/board_mydashboard_view.xml | 2 +- addons/board/board_view.xml | 73 ++-- addons/board/controllers.py | 55 +++ addons/board/security/ir.model.access.csv | 1 - addons/board/static/src/css/dashboard.css | 309 ++++++++++++++ addons/board/static/src/img/layout_1-1-1.png | Bin 0 -> 306 bytes addons/board/static/src/img/layout_1-1.png | Bin 0 -> 313 bytes addons/board/static/src/img/layout_1-2.png | Bin 0 -> 313 bytes addons/board/static/src/img/layout_1.png | Bin 0 -> 292 bytes addons/board/static/src/img/layout_2-1.png | Bin 0 -> 304 bytes .../board/static/src/img/view_todo_arrow.png | Bin 0 -> 3389 bytes addons/board/static/src/js/dashboard.js | 378 ++++++++++++++++++ addons/board/static/src/xml/board.xml | 80 ++++ addons/board/wizard/__init__.py | 25 -- addons/board/wizard/board_menu_create.py | 102 ----- .../board/wizard/board_menu_create_view.xml | 38 -- 22 files changed, 969 insertions(+), 334 deletions(-) delete mode 100644 addons/board/board_data_admin.xml delete mode 100644 addons/board/board_data_home.xml delete mode 100644 addons/board/board_demo.xml create mode 100644 addons/board/controllers.py create mode 100644 addons/board/static/src/css/dashboard.css create mode 100644 addons/board/static/src/img/layout_1-1-1.png create mode 100644 addons/board/static/src/img/layout_1-1.png create mode 100644 addons/board/static/src/img/layout_1-2.png create mode 100644 addons/board/static/src/img/layout_1.png create mode 100644 addons/board/static/src/img/layout_2-1.png create mode 100644 addons/board/static/src/img/view_todo_arrow.png create mode 100644 addons/board/static/src/js/dashboard.js create mode 100644 addons/board/static/src/xml/board.xml delete mode 100644 addons/board/wizard/__init__.py delete mode 100644 addons/board/wizard/board_menu_create.py delete mode 100644 addons/board/wizard/board_menu_create_view.xml diff --git a/addons/board/__init__.py b/addons/board/__init__.py index e169935ef84..5b8ceac3ad6 100644 --- a/addons/board/__init__.py +++ b/addons/board/__init__.py @@ -1,8 +1,9 @@ # -*- coding: utf-8 -*- ############################################################################## -# +# # OpenERP, Open Source Management Solution # Copyright (C) 2004-2010 Tiny SPRL (). +# Copyright (C) 2010-2012 OpenERP s.a. (). # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as @@ -15,11 +16,11 @@ # GNU Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . +# along with this program. If not, see . # ############################################################################## import board -import wizard +import controllers # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/board/__openerp__.py b/addons/board/__openerp__.py index 503f11afe6b..62986c452be 100644 --- a/addons/board/__openerp__.py +++ b/addons/board/__openerp__.py @@ -3,6 +3,7 @@ # # OpenERP, Open Source Management Solution # Copyright (C) 2004-2010 Tiny SPRL (). +# Copyright (C) 2010-2012 OpenERP s.a. (). # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as @@ -35,15 +36,19 @@ The user can also publish notes. 'depends': ['base'], 'update_xml': [ 'security/ir.model.access.csv', - 'wizard/board_menu_create_view.xml', 'board_view.xml', - 'board_data_admin.xml', - 'board_data_home.xml', 'board_mydashboard_view.xml' ], - 'demo_xml': [ - 'board_demo.xml' + "js": [ + 'static/src/js/dashboard.js', ], + "css": [ + 'static/src/css/dashboard.css', + ], + 'qweb': [ + "static/src/xml/*.xml", + ], + 'installable': True, 'auto_install': False, 'certificate': '0076912305725', diff --git a/addons/board/board.py b/addons/board/board.py index 985bc3cd09a..a76bff7a66b 100644 --- a/addons/board/board.py +++ b/addons/board/board.py @@ -3,6 +3,7 @@ # # OpenERP, Open Source Management Solution # Copyright (C) 2004-2010 Tiny SPRL (). +# Copyright (C) 2010-2012 OpenERP s.a. (). # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as @@ -19,75 +20,58 @@ # ############################################################################## +from operator import itemgetter +from textwrap import dedent from osv import fields, osv -import time import tools class board_board(osv.osv): - """ - Board - """ _name = 'board.board' _description = "Board" + _auto = False + _columns = {} - def create_view(self, cr, uid, ids, context=None): - """ - Create view - @param cr: the current row, from the database cursor, - @param uid: the current user’s ID for security checks, - @param ids: List of Board's IDs - @return: arch of xml view. - """ - arch = """ -
- - - - -
""" - return arch + @tools.cache() + def list(self, cr, uid, context=None): + Actions = self.pool.get('ir.actions.act_window') + Menus = self.pool.get('ir.ui.menu') + IrValues = self.pool.get('ir.values') + + act_ids = Actions.search(cr, uid, [('res_model', '=', self._name)], context=context) + refs = ['%s,%s' % (Actions._name, act_id) for act_id in act_ids] + + # cannot search "action" field on menu (non stored function field without search_fnct) + irv_ids = IrValues.search(cr, uid, [ + ('model', '=', 'ir.ui.menu'), + ('key', '=', 'action'), + ('key2', '=', 'tree_but_open'), + ('value', 'in', refs), + ], context=context) + menu_ids = map(itemgetter('res_id'), IrValues.read(cr, uid, irv_ids, ['res_id'], context=context)) + menu_names = Menus.name_get(cr, uid, menu_ids, context=context) + return [dict(id=m[0], name=m[1]) for m in menu_names] + + def _clear_list_cache(self): + self.list.clear_cache(self) def create(self, cr, user, vals, context=None): - """ - create new record. - @param cr: the current row, from the database cursor, - @param uid: the current user’s ID for security checks, - @param vals: dictionary of values for every field. - dictionary must use this form: {‘name_of_the_field’: value, ...} - @return: id of new created record of board.board. - """ + return 0 - - if not 'name' in vals: - return False - id = super(board_board, self).create(cr, user, vals, context=context) - view_id = self.pool.get('ir.ui.view').create(cr, user, { - 'name': vals['name'], - 'model': 'board.board', - 'priority': 16, - 'type': 'form', - 'arch': self.create_view(cr, user, id, context=context), - }) - - super(board_board, self).write(cr, user, [id], {'view_id': view_id}, context) - return id - - def fields_view_get(self, cr, user, view_id=None, view_type='form', context=None,\ - toolbar=False, submenu=False): + def fields_view_get(self, cr, user, view_id=None, view_type='form', context=None, toolbar=False, submenu=False): """ Overrides orm field_view_get. @return: Dictionary of Fields, arch and toolbar. """ res = {} - res = super(board_board, self).fields_view_get(cr, user, view_id, view_type,\ - context, toolbar=toolbar, submenu=submenu) + res = super(board_board, self).fields_view_get(cr, user, view_id, view_type, + context, toolbar=toolbar, submenu=submenu) - vids = self.pool.get('ir.ui.view.custom').search(cr, user,\ - [('user_id', '=', user), ('ref_id' ,'=', view_id)]) + CustView = self.pool.get('ir.ui.view.custom') + vids = CustView.search(cr, user, [('user_id', '=', user), ('ref_id', '=', view_id)], context=context) if vids: view_id = vids[0] - arch = self.pool.get('ir.ui.view.custom').browse(cr, user, view_id, context=context) + arch = CustView.browse(cr, user, view_id, context=context) res['custom_view_id'] = view_id res['arch'] = arch.arch res['arch'] = self._arch_preprocessing(cr, user, res['arch'], context=context) @@ -98,10 +82,10 @@ class board_board(osv.osv): from lxml import etree def remove_unauthorized_children(node): for child in node.iterchildren(): - if child.tag=='action' and child.get('invisible'): + if child.tag == 'action' and child.get('invisible'): node.remove(child) else: - child=remove_unauthorized_children(child) + child = remove_unauthorized_children(child) return node def encode(s): @@ -110,16 +94,84 @@ class board_board(osv.osv): return s archnode = etree.fromstring(encode(arch)) - return etree.tostring(remove_unauthorized_children(archnode),pretty_print=True) + return etree.tostring(remove_unauthorized_children(archnode), pretty_print=True) + + +class board_create(osv.osv_memory): + + def board_create(self, cr, uid, ids, context=None): + assert len(ids) == 1 + this = self.browse(cr, uid, ids[0], context=context) + + view_arch = dedent(""" +
+ + + + +
+ """.strip() % (this.name,)) + + view_id = self.pool.get('ir.ui.view').create(cr, uid, { + 'name': this.name, + 'model': 'board.board', + 'priority': 16, + 'type': 'form', + 'arch': view_arch, + }, context=context) + + action_id = self.pool.get('ir.actions.act_window').create(cr, uid, { + 'name': this.name, + 'view_type': 'form', + 'view_mode': 'form', + 'res_model': 'board.board', + 'usage': 'menu', + 'view_id': view_id, + 'help': dedent('''
+

+ This dashboard is empty. +

+ To add the first report into this dashboard, go to any + menu, switch to list or graph view, and click 'Add to + Dashboard' in the extended search options. +

+ You can filter and group data before inserting into the + dashboard using the search options. +

+
+ ''') + }, context=context) + + menu_id = self.pool.get('ir.ui.menu').create(cr, uid, { + 'name': this.name, + 'parent_id': this.menu_parent_id.id, + 'action': 'ir.actions.act_window,%s' % (action_id,) + }, context=context) + + self.pool.get('board.board')._clear_list_cache() + + return { + 'type': 'ir.actions.client', + 'tag': 'reload', + 'params': { + 'menu_id': menu_id + }, + } + + def _default_menu_parent_id(self, cr, uid, context=None): + _, menu_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'base', 'menu_reporting_dashboard') + return menu_id + + _name = "board.create" + _description = "Board Creation" _columns = { - 'name': fields.char('Dashboard', size=64, required=True), - 'view_id': fields.many2one('ir.ui.view', 'Board View'), + 'name': fields.char('Board Name', size=64, required=True), + 'menu_parent_id': fields.many2one('ir.ui.menu', 'Parent Menu', required=True), } - # the following lines added to let the button on dashboard work. _defaults = { - 'name':lambda *args: 'Dashboard' + 'menu_parent_id': _default_menu_parent_id, } # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/board/board_data_admin.xml b/addons/board/board_data_admin.xml deleted file mode 100644 index 30816750035..00000000000 --- a/addons/board/board_data_admin.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/addons/board/board_data_home.xml b/addons/board/board_data_home.xml deleted file mode 100644 index f1e90ace7ad..00000000000 --- a/addons/board/board_data_home.xml +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - Applications Tiles - board.home.applications - - - Tweets Widget - board.home.widgets - - - - Events Widget - board.home.widgets - - - - Facebook Widget - board.home.widgets - - - - Note Widget - board.home.widgets - - - - Google Maps Widget - board.home.widgets - - - - Currency Converter Widget - board.home.widgets - - - - - diff --git a/addons/board/board_demo.xml b/addons/board/board_demo.xml deleted file mode 100644 index d36ec6161fe..00000000000 --- a/addons/board/board_demo.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/addons/board/board_mydashboard_view.xml b/addons/board/board_mydashboard_view.xml index 9d7e27d57d8..b08cf05b490 100644 --- a/addons/board/board_mydashboard_view.xml +++ b/addons/board/board_mydashboard_view.xml @@ -1,6 +1,6 @@ - + My Dashboard diff --git a/addons/board/board_view.xml b/addons/board/board_view.xml index 4a72b2f714a..219fc17f1b9 100644 --- a/addons/board/board_view.xml +++ b/addons/board/board_view.xml @@ -1,64 +1,37 @@ - - - board.board.search - board.board - search - - - - - - - - - - board.board.tree - board.board - tree - - - - - - - - - - board.board.form - board.board + + board.create.form + board.create form - -
-
-
- - - - - - -
+
+ + + + +
+
+
- - - Dashboard Definition - board.board + + Create Board + board.create form - tree,form - + form + + new - - -
diff --git a/addons/board/controllers.py b/addons/board/controllers.py new file mode 100644 index 00000000000..53845c96095 --- /dev/null +++ b/addons/board/controllers.py @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- +from xml.etree import ElementTree + +try: + import openerp.addons.web.common.http as openerpweb + from openerp.addons.web.common import nonliterals + from openerp.addons.web.controllers.main import load_actions_from_ir_values +except ImportError: + import web.common.http as openerpweb # noqa + from web.common import nonliterals # noqa + from web.controllers.main import load_actions_from_ir_values # noqa + +class Board(openerpweb.Controller): + _cp_path = '/board' + + @openerpweb.jsonrequest + def add_to_dashboard(self, req, menu_id, action_id, context_to_save, domain, view_mode, name=''): + # FIXME move this method to board.board model + to_eval = nonliterals.CompoundContext(context_to_save) + to_eval.session = req.session + ctx = dict((k, v) for k, v in to_eval.evaluate().iteritems() + if not k.startswith('search_default_')) + ctx['dashboard_merge_domains_contexts'] = False # TODO: replace this 6.1 workaround by attribute on + domain = nonliterals.CompoundDomain(domain) + domain.session = req.session + domain = domain.evaluate() + + dashboard_action = load_actions_from_ir_values(req, 'action', 'tree_but_open', [('ir.ui.menu', menu_id)], False) + + if dashboard_action: + action = dashboard_action[0][2] + if action['res_model'] == 'board.board' and action['views'][0][1] == 'form': + # Maybe should check the content instead of model board.board ? + view_id = action['views'][0][0] + board = req.session.model(action['res_model']).fields_view_get(view_id, 'form') + if board and 'arch' in board: + xml = ElementTree.fromstring(board['arch']) + column = xml.find('./board/column') + if column is not None: + new_action = ElementTree.Element('action', { + 'name': str(action_id), + 'string': name, + 'view_mode': view_mode, + 'context': str(ctx), + 'domain': str(domain) + }) + column.insert(0, new_action) + arch = ElementTree.tostring(xml, 'utf-8') + return req.session.model('ir.ui.view.custom').create({ + 'user_id': req.session._uid, + 'ref_id': view_id, + 'arch': arch + }, req.session.eval_context(req.context)) + + return False diff --git a/addons/board/security/ir.model.access.csv b/addons/board/security/ir.model.access.csv index ec9d2e89f4b..4833c8ded0f 100644 --- a/addons/board/security/ir.model.access.csv +++ b/addons/board/security/ir.model.access.csv @@ -1,3 +1,2 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink access_board_board all,board.board,model_board_board,,1,0,0,0 -access_board_board system,board.board system,model_board_board,base.group_system,1,1,1,1 diff --git a/addons/board/static/src/css/dashboard.css b/addons/board/static/src/css/dashboard.css new file mode 100644 index 00000000000..74410b3e937 --- /dev/null +++ b/addons/board/static/src/css/dashboard.css @@ -0,0 +1,309 @@ +.openerp table.oe_dashboard { + width: 100%; +} +.openerp .oe_dashboard_links { + text-align: right; + margin: 0 4px 6px 0; +} +.openerp .oe_dashboard_action { + margin: 0 0.5em 0.5em 0; + padding: 0px; + background-color: white; + border-radius: 3px; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; +} + +.openerp .oe_dashboard_action .oe_dashboard_action_header { + font-size: 85%; + font-weight: bold; + text-transform: uppercase; + text-indent: 10px; + vertical-align: middle; + border-bottom: 1px solid #e5e5e5; + background: white url("/web/static/src/img/box-a-header-a.gif") 0% 0% repeat-x; +} + +.openerp h2.oe_dashboard_action_header { + margin: 0; + padding:4px 4px; + -moz-border-radius-topleft: 3px; + -webkit-border-top-left-radius: 3px; + border-top-left-radius: 3px; + -moz-border-radius-topright: 3px; + -webkit-border-top-right-radius: 3px; + border-top-right-radius: 3px; +} +.openerp h2.oe_dashboard_action_header_empty { + padding-top: 0; + padding-bottom: 2px; +} + +.openerp .oe_dashboard_button_create { + margin-left: 4px; + padding: 0 4px 0 4px; + height: 16px !important; +} + +.openerp a.oe_dashboard_action_rename { + float: left; + padding-right: 4px; + position: relative; + top: 1px; +} +.openerp .oe_dashboard_action_input { + height: 16px; + position: relative; + top: 2px; +} + +.openerp .oe_dashboard_action .oe_dashboard_action_header:hover { + cursor: move; +} +.openerp .oe_dashboard_action .ui-icon { + cursor: pointer; +} +.openerp .oe_dashboard_action .ui-icon:hover { + background-color: #ccc; + border-radius: 4px; + -moz-border-radius: 4px; + -webkit-border-radius: 4px; +} + +.openerp .oe_dashboard_action .oe_dashboard_action_header .ui-icon { + float: right; +} + +.openerp .oe_dashboard .ui-sortable-placeholder { + border: 1px dotted black; + visibility: visible !important; + height: 50px !important; +} + +.openerp .oe_dashboard .ui-sortable-placeholder * { + visibility: hidden; +} + +/* Base overwriting */ +.openerp .oe_dashboard .oe_list_content, .openerp .oe_dashboard .ui-widget-header { + border-right:none !important; + padding:0 3px; +} + +/* Layouts */ +.openerp .oe_dashboard_layout_1 .oe_dashboard_column.index_0 { + width: 100%; +} +.openerp .oe_dashboard_layout_1 .oe_dashboard_column.index_1, +.openerp .oe_dashboard_layout_1 .oe_dashboard_column.index_2 { + display: none; +} + +.openerp .oe_dashboard_layout_1-1 .oe_dashboard_column { + width: 50%; +} +.openerp .oe_dashboard_layout_1-1 .oe_dashboard_column.index_2 { + display: none; +} + +.openerp .oe_dashboard_layout_1-1-1 .oe_dashboard_column { + width: 33%; +} + +.openerp .oe_dashboard_layout_2-1 .oe_dashboard_column.index_0 { + width: 70%; +} +.openerp .oe_dashboard_layout_2-1 .oe_dashboard_column.index_1 { + width: 30%; +} +.openerp .oe_dashboard_layout_2-1 .oe_dashboard_column.index_2 { + display: none; +} + +.openerp .oe_dashboard_layout_1-2 .oe_dashboard_column.index_0 { + width: 30%; +} +.openerp .oe_dashboard_layout_1-2 .oe_dashboard_column.index_1 { + width: 70%; +} +.openerp .oe_dashboard_layout_1-2 .oe_dashboard_column.index_2 { + display: none; +} + + +.openerp .oe_dashboard_layout_selector { + overflow: auto; + padding: 10px; +} + +.openerp .oe_dashboard_layout_selector ul { + margin: 0; + padding: 0; +} + +.openerp .oe_dashboard_layout_selector ul li { + position: relative; + float: left; + height: 51px; + list-style-type: none; + margin: 5px; + padding: 0; + width: 82px; + cursor: pointer; + border: 1px solid white; +} +.openerp .oe_dashboard_layout_selector ul li:hover { + border: 1px solid #090; +} +.openerp .oe_dashboard_layout_selector ul li img.oe_dashboard_selected_layout { + position: absolute; + top: 0px; + right: 0px; +} + +.openerp .oe_dashboard_home_tile { + text-align: center; + margin: 10px; + -webkit-border-radius: 10px; + -moz-border-radius: 10px; + border-radius: 10px; + -webkit-box-shadow: 3px 3px 5px 3px #DADDDD; + -moz-box-shadow: 3px 3px 5px 3px #DADDDD; + box-shadow: 3px 3px 5px 3px #DADDDD; +} +.openerp .oe_dashboard_home_tile span { + display: block; + padding: 0 0 15px; + font-weight: bold; + text-transform: uppercase; + color: #555; + white-space: nowrap; +} +.openerp .oe_dashboard_home_tile_icon { + height: 100px; +} +.openerp .oe_dashboard_home_tile_icon img { + display: block; + margin: 0 auto; +} +.openerp .oe_dashboard_home_tile_icon img.hover { + display: none; +} +.openerp .oe_dashboard_home_tile:hover { + background-color: #fafafa; + -webkit-box-shadow: 3px 3px 5px 3px #979797; + -moz-box-shadow: 3px 3px 5px 3px #979797; + box-shadow: 3px 3px 5px 3px #979797; +} +.openerp .oe_dashboard_home_tile:hover img { + display: none; +} +.openerp .oe_dashboard_home_tile:hover img.hover { + display: block; +} +.openerp .oe_dashboard_home_tile:hover span { + color: black; +} + +.openerp .oe_dashboard_action .view-manager-main-content { + padding: 2px; +} + +.openerp .oe_app_tiles h1, .openerp .oe_app_tiles h3 { + margin: 16px 24px; +} + +.openerp .oe_app_tiles { + padding: 0 10px; +} + +.openerp .oe_app_tiles li { + float: left; + list-style: none; +} + +.openerp .oe_app_tiles li img { + display: block; + margin: 0 auto; + height: 100px; + width: 100px; +} +.openerp .oe_app_tiles li img.hover { + display: none; +} +.openerp .oe_app_tiles li:hover img { + display: none; +} +.openerp .oe_app_tiles li:hover img.hover { + display: block; +} + +.openerp .oe_app_tiles li a { + display: block; + height: 120px; + width: 194px; + color: #4C4C4C; + border: 1px solid #f4f2f2; + margin: 6px; + padding: 12px; + text-align: center; + text-transform: uppercase; + text-decoration: none; + font-size: 12px; + font-weight: 800; + background: white; + -moz-border-radius: 6px; + -webkit-border-radius: 6px; + -ms-border-radius: 6px; + border-radius: 6px; + -moz-box-shadow: 0 1px 2px #bbb; + -webkit-box-shadow: 0 1px 2px #bbb; + -o-box-shadow: 0 1px 2px #bbb; + box-shadow: 0 1px 2px #bbb; +} +/* changing icon for the change layout button */ + +.openerp .oe_dashboard_link_change_layout, .openerp .oe_dashboard_link_reset { + padding-top: 1px; + height: 22px; +} +.openerp .oe_dashboard_link_change_layout > *, .openerp .oe_dashboard_link_reset > *{ + vertical-align: middle; +} + +.openerp .oe_welcome_message { + display:none; +} +.openerp .oe_initial_welcome_message { + width:30%; + text-align:center; + margin:10px 35% 0 35%; + padding: 5px 10px; + border: 1px solid #ccc; + border-radius: 5px; + -moz-border-radius: 5px; + -webkit-border-radius: 5px; + background: #eeeded; + box-shadow: 0 1px 0 #fff; + -moz-box-shadow: 0 1px 0 #fff; + -webkit-box-shadow: 0 1px 0 #fff; + color: #8c8c8c; + font-size: 90%; + text-transform: uppercase; + font-weight: bold; + text-shadow: #fff 0 1px 0; +} + +.openerp .oe_initial_welcome_message ul{ + padding:10px 0 0 0; + margin:0; + list-style-type: none; + padding: 7px 0 5px 5px; + background-position: 0 50%; + background-repeat: no-repeat; + cursor: pointer; +} +.openerp .oe_initial_welcome_message ul a, .openerp .initial_welcome_message ul a:hover, .openerp .initial_welcome_message ul a:active, .openerp .initial_welcome_message ul a:focus { + color:#222; + text-decoration:none; +} diff --git a/addons/board/static/src/img/layout_1-1-1.png b/addons/board/static/src/img/layout_1-1-1.png new file mode 100644 index 0000000000000000000000000000000000000000..5eda2823c46c11bcfed2b657847dba77ba568d52 GIT binary patch literal 306 zcmeAS@N?(olHy`uVBq!ia0vp^AZ*OR1|(Tp;!}VWV{wqX6T`Z5GB1G~wj^(N7l!{J zxM1({$v_d#0*}aI1_o|n5N2eUHAey{$X?><>&kwcjggO={}+qxO`y;wPZ!6Kid%2* zY~(s*AmVT_{9`i{U##_yn?Qo&=NvIDD-SpBNhty6?(R}4s8IF3m3a4h`ifn}E=Ou+ zdxn_AE;mo9yzqUhqRqk#VUGz4|G!`P7jkar>1B^odpbIv?)!6O*U~AII=^H!vuI9G zaO6?}s$mrZl1!XJOB|Y5ID-@%n^2`><>&kwcjggN>>c$NJuRx)lo-U3d6}R5r z*~`~tAkuKL`_%6V78{Iy+~8Zoqh`a$(#h8rD0JEFkLCSi*XG<==Iw15(;ls{)7izP zI?q$(%A8s3Pd+WY9^F>ZuH>)a*!1Ua@bAdl`}zA0{EBdQaVdXk7VjMzs&?rb8)pzu zTEt_5f+Lp-kYp87@n~e>6k6iY!~&Pvzb|v1!?P84rYq#;PWXTORqCIur=P#insdBA pvbz(cScqRbf7@lPKaL9i<>&kwcjggOsH`@D@5m0ESr;B4q#jUq@ z47r;eBpM!`KlNYPWQWz4&C)TlO!a+9LCzLxN9IoWx@UGbJOBN{)0u{M+TAsF2D`Xa z=X$DKiJ2w-8MOoBrGl{vBI;KYvq3?K)+pMY~IPA6m8Ql*_GK3XWVV z9ut^2g;YEmSvZB3I5e?nP5^2U@c@!gsVDDdKjTd4JC)3N=^ES5{cGcn#qX|}cgdoE s_P+ZrAeHe|H-4m9uRSEs^63l9x%sjacD6Gc13kgu>FVdQ&MBb@00A6wf&c&j literal 0 HcmV?d00001 diff --git a/addons/board/static/src/img/layout_1.png b/addons/board/static/src/img/layout_1.png new file mode 100644 index 0000000000000000000000000000000000000000..69a0e30854c06cb060533c0c41963584cc0aaf71 GIT binary patch literal 292 zcmeAS@N?(olHy`uVBq!ia0vp^AZ*OR1|(Tp;!}VWV{wqX6T`Z5GB1G~wj^(N7l!{J zxM1({$v_d#0*}aI1_o|n5N2eUHAey{$X?><>&kwcjggPr#GC)hU!c$uPZ!6Kid%2* z7;-fmh%`J*U;F)}pp4D7X7>|?EZT1sn!1}*yl=n1zo~A|1XWMt{m;r*>_~SxV&bR5 z^{TzD`HuLm`*tn-BBe~6LQme$yOsWQ4yUY_=1h-hX%&w~7S13A$0iod2|!ZBV}b&! zkc!6yph~#ZpI_5T86y|3d+TuPmcsw-r%(R~pME}738-h~?e<>t+o`5?0xK@FA8ZpB URCT#*40JDpr>mdKI;Vst0C^K*^Z)<= literal 0 HcmV?d00001 diff --git a/addons/board/static/src/img/layout_2-1.png b/addons/board/static/src/img/layout_2-1.png new file mode 100644 index 0000000000000000000000000000000000000000..ed866add47c5e1c2384f18981f0fe13edc9cda0a GIT binary patch literal 304 zcmeAS@N?(olHy`uVBq!ia0vp^AZ*OR1|(Tp;!}VWV{wqX6T`Z5GB1G~wj^(N7l!{J zxM1({$v_d#0*}aI1_o|n5N2eUHAey{$X?><>&kwcjggOAlJ7!C2T*8(r;B4q#jUq@ zHgYu^h&Wta{=>16Zyn#?H$bAbrq9V+wkvt5>a-}!@?t^TJrh);dbfWR)LPfs@j~CW zvuRiH_m&vFFO{bSu4!!JRPmT_{O;EHsm1?RPtWwvb#dXj{!sh7zvfI2Z)p{eMi$N> z1;-{9%?UtK#AAX2s}N8F6DN>Fmdg5T@bA{c&$ecFm(RD|-pRr#wB+86oSMzCVNdy) bZksS4=9HQcS-jp2=ynEAS3j3^P6 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 Tu8hFi00000NkvXXu0mjfq2O{c literal 0 HcmV?d00001 diff --git a/addons/board/static/src/js/dashboard.js b/addons/board/static/src/js/dashboard.js new file mode 100644 index 00000000000..cc7fe1e508c --- /dev/null +++ b/addons/board/static/src/js/dashboard.js @@ -0,0 +1,378 @@ +openerp.board = function(instance) { +var QWeb = instance.web.qweb, + _t = instance.web._t; + +if (!instance.board) { + /** @namespace */ + instance.board = {}; +} + +instance.web.form.DashBoard = instance.web.form.FormWidget.extend({ + init: function(view, node) { + this._super(view, node); + this.form_template = 'DashBoard'; + this.actions_attrs = {}; + this.action_managers = []; + }, + start: function() { + var self = this; + this._super.apply(this, arguments); + + this.$element.find('.oe_dashboard_column').sortable({ + connectWith: '.oe_dashboard_column', + handle: '.oe_dashboard_action_header', + scroll: false + }).disableSelection().bind('sortstop', self.do_save_dashboard); + + // Events + this.$element.find('.oe_dashboard_link_reset').click(this.on_reset); + this.$element.find('.oe_dashboard_link_change_layout').click(this.on_change_layout); + + this.$element.delegate('.oe_dashboard_column .oe_dashboard_fold', 'click', this.on_fold_action); + this.$element.delegate('.oe_dashboard_column .ui-icon-closethick', 'click', this.on_close_action); + + // Init actions + _.each(this.node.children, function(column, column_index) { + _.each(column.children, function(action, action_index) { + delete(action.attrs.width); + delete(action.attrs.height); + delete(action.attrs.colspan); + var action_id = _.str.toNumber(action.attrs.name); + if (!_.isNaN(action_id)) { + self.rpc('/web/action/load', {action_id: action_id}, function(result) { + self.on_load_action(result, column_index + '_' + action_index, action.attrs); + }); + } + }); + }); + }, + on_reset: function() { + this.rpc('/web/view/undo_custom', { + view_id: this.view.fields_view.view_id, + reset: true + }, this.do_reload); + }, + on_change_layout: function() { + var self = this; + var qdict = { + current_layout : this.$element.find('.oe_dashboard').attr('data-layout') + }; + var $dialog = instance.web.dialog($('
'), { + modal: true, + title: _t("Edit Layout"), + width: 'auto', + height: 'auto' + }).html(QWeb.render('DashBoard.layouts', qdict)); + $dialog.find('li').click(function() { + var layout = $(this).attr('data-layout'); + $dialog.dialog('destroy'); + self.do_change_layout(layout); + }); + }, + do_change_layout: function(new_layout) { + var $dashboard = this.$element.find('.oe_dashboard'); + var current_layout = $dashboard.attr('data-layout'); + if (current_layout != new_layout) { + var clayout = current_layout.split('-').length, + nlayout = new_layout.split('-').length, + column_diff = clayout - nlayout; + if (column_diff > 0) { + var $last_column = $(); + $dashboard.find('.oe_dashboard_column').each(function(k, v) { + if (k >= nlayout) { + $(v).find('.oe_dashboard_action').appendTo($last_column); + } else { + $last_column = $(v); + } + }); + } + $dashboard.toggleClass('oe_dashboard_layout_' + current_layout + ' oe_dashboard_layout_' + new_layout); + $dashboard.attr('data-layout', new_layout); + this.do_save_dashboard(); + } + }, + on_fold_action: function(e) { + var $e = $(e.currentTarget), + $action = $e.parents('.oe_dashboard_action:first'), + id = parseInt($action.attr('data-id'), 10); + if ($e.is('.ui-icon-minusthick')) { + $action.data('action_attrs').fold = '1'; + } else { + delete($action.data('action_attrs').fold); + } + $e.toggleClass('ui-icon-minusthick ui-icon-plusthick'); + $action.find('.oe_dashboard_action_content').toggle(); + this.do_save_dashboard(); + }, + on_close_action: function(e) { + if (confirm(_t("Are you sure you want to remove this item ?"))) { + $(e.currentTarget).parents('.oe_dashboard_action:first').remove(); + this.do_save_dashboard(); + } + }, + do_save_dashboard: function() { + var self = this; + var board = { + form_title : this.view.fields_view.arch.attrs.string, + style : this.$element.find('.oe_dashboard').attr('data-layout'), + columns : [] + }; + this.$element.find('.oe_dashboard_column').each(function() { + var actions = []; + $(this).find('.oe_dashboard_action').each(function() { + var action_id = $(this).attr('data-id'), + new_attrs = _.clone($(this).data('action_attrs')); + if (new_attrs.domain) { + new_attrs.domain = new_attrs.domain_string; + delete(new_attrs.domain_string); + } + if (new_attrs.context) { + new_attrs.context = new_attrs.context_string; + delete(new_attrs.context_string); + } + actions.push(new_attrs); + }); + board.columns.push(actions); + }); + var arch = QWeb.render('DashBoard.xml', board); + this.rpc('/web/view/add_custom', { + view_id: this.view.fields_view.view_id, + arch: arch + }, function() { + self.$element.find('.oe_dashboard_link_reset').show(); + }); + }, + on_load_action: function(result, index, action_attrs) { + var self = this, + action = result.result, + view_mode = action_attrs.view_mode; + + if (action_attrs.context && action_attrs.context['dashboard_merge_domains_contexts'] === false) { + // TODO: replace this 6.1 workaround by attribute on + action.context = action_attrs.context || {}; + action.domain = action_attrs.domain || []; + } else { + if (action_attrs.context) { + action.context = _.extend((action.context || {}), action_attrs.context); + } + if (action_attrs.domain) { + action.domain = action.domain || []; + action.domain.unshift.apply(action.domain, action_attrs.domain); + } + } + + var action_orig = _.extend({ flags : {} }, action); + + if (view_mode && view_mode != action.view_mode) { + action.views = _.map(view_mode.split(','), function(mode) { + mode = mode === 'tree' ? 'list' : mode; + return _(action.views).find(function(view) { return view[1] == mode; }) + || [false, mode]; + }); + } + + action.flags = { + search_view : false, + sidebar : false, + views_switcher : false, + action_buttons : false, + pager: false, + low_profile: true, + display_title: false, + list: { + selectable: false + } + }; + var am = new instance.web.ActionManager(this), + // FIXME: ideally the dashboard view shall be refactored like kanban. + $action = $('#' + this.view.element_id + '_action_' + index); + $action.parent().data('action_attrs', action_attrs); + this.action_managers.push(am); + am.appendTo($action); + am.do_action(action); + am.do_action = function (action) { + self.do_action(action); + }; + if (action_attrs.creatable && action_attrs.creatable !== 'false') { + var action_id = parseInt(action_attrs.creatable, 10); + $action.parent().find('button.oe_dashboard_button_create').click(function() { + if (isNaN(action_id)) { + action_orig.flags.default_view = 'form'; + self.do_action(action_orig); + } else { + self.rpc('/web/action/load', { + action_id: action_id + }, function(result) { + result.result.flags = result.result.flags || {}; + result.result.flags.default_view = 'form'; + self.do_action(result.result); + }); + } + }); + } + if (am.inner_widget) { + am.inner_widget.on_mode_switch.add(function(mode) { + var new_views = []; + _.each(action_orig.views, function(view) { + new_views[view[1] === mode ? 'unshift' : 'push'](view); + }); + if (!new_views.length || new_views[0][1] !== mode) { + new_views.unshift([false, mode]); + } + action_orig.views = new_views; + action_orig.res_id = am.inner_widget.dataset.ids[am.inner_widget.dataset.index]; + self.do_action(action_orig); + }); + } + }, + renderElement: function() { + this._super(); + + var check = _.detect(this.node.children, function(column, column_index) { + return _.detect(column.children,function(element){ + return element.tag === "action"? element: false; + }); + }); + if (!check) { + return this.no_result(); + } + // We should start with three columns available + for (var i = this.node.children.length; i < 3; i++) { + this.node.children.push({ + tag: 'column', + attrs: {}, + children: [] + }); + } + var rendered = QWeb.render(this.form_template, this); + this.$element.html(rendered); + }, + no_result: function() { + if (this.view.options.action.help) { + this.$element.append( + $('
') + .append($('
').html(this.view.options.action.help || " ")) + ); + } + }, + do_reload: function() { + var view_manager = this.view.getParent(), + action_manager = view_manager.getParent(); + this.view.destroy(); + action_manager.do_action(view_manager.action); + } +}); +instance.web.form.DashBoardLegacy = instance.web.form.DashBoard.extend({ + renderElement: function() { + if (this.node.tag == 'hpaned') { + this.node.attrs.style = '2-1'; + } else if (this.node.tag == 'vpaned') { + this.node.attrs.style = '1'; + } + this.node.tag = 'board'; + _.each(this.node.children, function(child) { + if (child.tag.indexOf('child') == 0) { + child.tag = 'column'; + var actions = [], first_child = child.children[0]; + if (first_child && first_child.tag == 'vpaned') { + _.each(first_child.children, function(subchild) { + actions.push.apply(actions, subchild.children); + }); + child.children = actions; + } + } + }); + this._super(this); + } +}); + +instance.web.form.tags.add('hpaned', 'instance.web.form.DashBoardLegacy'); +instance.web.form.tags.add('vpaned', 'instance.web.form.DashBoardLegacy'); +instance.web.form.tags.add('board', 'instance.web.form.DashBoard'); + + +instance.board.AddToDashboard = instance.web.search.Input.extend({ + template: 'SearchView.addtodashboard', + _in_drawer: true, + start: function () { + var self = this; + this.$element + .on('click', 'h4', this.proxy('show_option')) + .on('submit', 'form', function (e) { + e.preventDefault(); + self.add_dashboard(); + }); + return this.load_data().then(this.proxy("render_data")); + }, + load_data:function(){ + var board = new instance.web.Model('board.board'); + return board.call('list'); + }, + _x:function() { + if (!instance.webclient) { return $.Deferred().reject(); } + var dashboard_menu = instance.webclient.menu.data.data.children; + return new instance.web.Model('ir.model.data') + .query(['res_id']) + .filter([['name','=','menu_reporting_dashboard']]) + .first().pipe(function (result) { + var menu = _(dashboard_menu).chain() + .pluck('children') + .flatten(true) + .find(function (child) { return child.id === result.res_id; }) + .value(); + return menu ? menu.children : []; + }); + }, + render_data: function(dashboard_choices){ + var selection = instance.web.qweb.render( + "SearchView.addtodashboard.selection", { + selections: dashboard_choices}); + this.$("input").before(selection) + }, + add_dashboard: function(){ + var self = this; + var getParent = this.getParent(); + var view_parent = this.getParent().getParent(); + if (! view_parent.action || ! this.$element.find("select").val()) { + this.do_warn("Can't find dashboard action"); + return; + } + var data = getParent.build_search_data(); + var context = new instance.web.CompoundContext(getParent.dataset.get_context() || []); + var domain = new instance.web.CompoundDomain(getParent.dataset.get_domain() || []); + _.each(data.contexts, context.add, context); + _.each(data.domains, domain.add, domain); + this.rpc('/board/add_to_dashboard', { + menu_id: this.$element.find("select").val(), + action_id: view_parent.action.id, + context_to_save: context, + domain: domain, + view_mode: view_parent.active_view, + name: this.$element.find("input").val() + }, function(r) { + if (r === false) { + self.do_warn("Could not add filter to dashboard"); + } else { + self.$element.toggleClass('oe_opened'); + self.do_notify("Filter added to dashboard", ''); + } + }); + }, + show_option:function(){ + this.$element.toggleClass('oe_opened'); + if (! this.$element.hasClass('oe_opened')) + return; + this.$("input").val(this.getParent().fields_view.name || "" ); + } +}); + + +instance.web.SearchView.include({ + add_common_inputs: function() { + this._super(); + (new instance.board.AddToDashboard(this)); + + } +}); + +}; diff --git a/addons/board/static/src/xml/board.xml b/addons/board/static/src/xml/board.xml new file mode 100644 index 00000000000..09e68fa28cd --- /dev/null +++ b/addons/board/static/src/xml/board.xml @@ -0,0 +1,80 @@ +