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 00000000000..5eda2823c46 Binary files /dev/null and b/addons/board/static/src/img/layout_1-1-1.png differ diff --git a/addons/board/static/src/img/layout_1-1.png b/addons/board/static/src/img/layout_1-1.png new file mode 100644 index 00000000000..e72aa3a0533 Binary files /dev/null and b/addons/board/static/src/img/layout_1-1.png differ diff --git a/addons/board/static/src/img/layout_1-2.png b/addons/board/static/src/img/layout_1-2.png new file mode 100644 index 00000000000..4b14d7aeaad Binary files /dev/null and b/addons/board/static/src/img/layout_1-2.png differ 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 00000000000..69a0e30854c Binary files /dev/null and b/addons/board/static/src/img/layout_1.png differ 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 00000000000..ed866add47c Binary files /dev/null and b/addons/board/static/src/img/layout_2-1.png differ diff --git a/addons/board/static/src/img/view_todo_arrow.png b/addons/board/static/src/img/view_todo_arrow.png new file mode 100644 index 00000000000..8633430e9ae Binary files /dev/null and b/addons/board/static/src/img/view_todo_arrow.png differ 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 @@ +