diff --git a/addons/base_report_designer/__init__.py b/addons/base_report_designer/__init__.py deleted file mode 100644 index 2a14d2821ae..00000000000 --- a/addons/base_report_designer/__init__.py +++ /dev/null @@ -1,26 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# Copyright (C) 2004-2010 Tiny SPRL (). -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# 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 . -# -############################################################################## -import wizard -import base_report_designer -import installer -import openerp_sxw2rml -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: - diff --git a/addons/base_report_designer/__openerp__.py b/addons/base_report_designer/__openerp__.py deleted file mode 100644 index 36e15c96877..00000000000 --- a/addons/base_report_designer/__openerp__.py +++ /dev/null @@ -1,43 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# Copyright (C) 2004-2010 Tiny SPRL (). -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# 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 . -# -############################################################################## - - -{ - 'name': 'OpenOffice Report Designer', - 'version': '0.1', - 'category': 'Reporting', - 'description': """ -This module is used along with OpenERP OpenOffice Plugin. -========================================================= - -This module adds wizards to Import/Export .sxw report that you can modify in OpenOffice. -Once you have modified it you can upload the report using the same wizard. -""", - 'author': 'OpenERP SA', - 'website': 'http://www.openerp.com', - 'depends': ['base'], - 'data': ['wizard/base_report_design_view.xml' , 'base_report_designer_installer.xml'], - 'demo': [], - 'installable': True, - 'auto_install': False, - 'images': ['images/base_report_designer1.jpeg','images/base_report_designer2.jpeg',], -} -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/base_report_designer/base_report_designer.py b/addons/base_report_designer/base_report_designer.py deleted file mode 100644 index a7289f42d4e..00000000000 --- a/addons/base_report_designer/base_report_designer.py +++ /dev/null @@ -1,79 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# Copyright (C) 2004-2010 Tiny SPRL (). -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# 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 . -# -############################################################################## - -import base64 -from StringIO import StringIO - -from openerp.modules.module import get_module_resource -import openerp.modules.registry -from openerp.osv import osv -from openerp_sxw2rml import sxw2rml - - -class report_xml(osv.osv): - _inherit = 'ir.actions.report.xml' - - def sxwtorml(self, cr, uid, file_sxw, file_type): - ''' - The use of this function is to get rml file from sxw file. - ''' - sxwval = StringIO(base64.decodestring(file_sxw)) - if file_type=='sxw': - fp = open(get_module_resource('base_report_designer','openerp_sxw2rml', 'normalized_oo2rml.xsl'),'rb') - if file_type=='odt': - fp = open(get_module_resource('base_report_designer','openerp_sxw2rml', 'normalized_odt2rml.xsl'),'rb') - return {'report_rml_content': str(sxw2rml(sxwval, xsl=fp.read()))} - - def upload_report(self, cr, uid, report_id, file_sxw, file_type, context=None): - ''' - Untested function - ''' - sxwval = StringIO(base64.decodestring(file_sxw)) - if file_type=='sxw': - fp = open(get_module_resource('base_report_designer','openerp_sxw2rml', 'normalized_oo2rml.xsl'),'rb') - if file_type=='odt': - fp = open(get_module_resource('base_report_designer','openerp_sxw2rml', 'normalized_odt2rml.xsl'),'rb') - report = self.pool['ir.actions.report.xml'].write(cr, uid, [report_id], { - 'report_sxw_content': base64.decodestring(file_sxw), - 'report_rml_content': str(sxw2rml(sxwval, xsl=fp.read())), - }) - - return True - - def report_get(self, cr, uid, report_id, context=None): - # skip osv.fields.sanitize_binary_value() because we want the raw bytes in all cases - context = dict(context or {}, bin_raw=True) - report = self.browse(cr, uid, report_id, context=context) - sxw_data = report.report_sxw_content - rml_data = report.report_rml_content - if isinstance(sxw_data, unicode): - sxw_data = sxw_data.encode("iso-8859-1", "replace") - if isinstance(rml_data, unicode): - rml_data = rml_data.encode("iso-8859-1", "replace") - return { - 'file_type' : report.report_type, - 'report_sxw_content': sxw_data and base64.encodestring(sxw_data) or False, - 'report_rml_content': rml_data and base64.encodestring(rml_data) or False - } - - -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: - diff --git a/addons/base_report_designer/base_report_designer_installer.xml b/addons/base_report_designer/base_report_designer_installer.xml deleted file mode 100644 index 2df241478cf..00000000000 --- a/addons/base_report_designer/base_report_designer_installer.xml +++ /dev/null @@ -1,64 +0,0 @@ - - - - - Form View: Odoo Report Designer Installation - base_report_designer.installer - - - -
- Odoo Report Designer Configuration -
- - - - '); + $cross.addClass('oe_tip_close'); + + if (tip.title) { + $('.popover-title').prepend($cross); + } else { + $('.popover-content').prepend($cross); + } + + // consume tip + tip.end_selector = tip.end_selector ? tip.end_selector : tip.highlight_selector; + $(tip.end_selector).one(tip.end_event, function($ev) { + self.end_tip(tip); + def.resolve(); + }); + + // dismiss tip + $cross.on('click', function($ev) { + self.end_tip(tip); + def.resolve(); + }); + self.$overlay.on('click', function($ev) { + self.end_tip(tip); + def.resolve(); + }); + $(document).on('keyup.web_tip', function($ev) { + if ($ev.which === 27) { // esc + self.end_tip(tip); + def.resolve(); + } + }); + } else { + def.reject(); + } + return def; + }, + + end_tip: function(tip) { + var self = this; + var Tips = new instance.web.Model('web.tip'); + self.$element.popover('destroy'); + self.$overlay.remove(); + self.$helper.remove(); + self.$element.removeClass('oe_tip_show_element'); + _.each($('.oe_tip_fix_parent'), function(el) { + $(el).removeClass('oe_tip_fix_parent'); + }); + $(document).off('keyup.web_tip'); + Tips.call('consume', [tip.id], {}); + tip.is_consumed = true; + } + }); + + instance.web.WebClient = instance.web.WebClient.extend({ + show_application: function() { + this._super(); + this.tip_handler = new instance.web.Tip(); + } + }); +})(); diff --git a/addons/web_tip/views/tip.xml b/addons/web_tip/views/tip.xml new file mode 100644 index 00000000000..49159b395a4 --- /dev/null +++ b/addons/web_tip/views/tip.xml @@ -0,0 +1,11 @@ + + + + + + diff --git a/addons/web_tip/web_tip.py b/addons/web_tip/web_tip.py new file mode 100644 index 00000000000..18673cfbd9f --- /dev/null +++ b/addons/web_tip/web_tip.py @@ -0,0 +1,63 @@ +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2009-Today OpenERP SA () +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# 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 +# +############################################################################## + +from openerp.osv import osv, fields + + +class tip(osv.Model): + _name = 'web.tip' + _description = 'Tips' + + def _is_consumed(self, cr, uid, ids, fields, arg, context=None): + results = {} + records = self.read(cr, uid, ids, ['user_ids']) + for rec in records: + if uid in rec['user_ids']: + results[rec['id']] = True + else: + results[rec['id']] = False + return results + + _columns = { + 'title': fields.char('Tip title'), + 'description': fields.html('Tip Description', required=True), + 'action_id': fields.many2one('ir.actions.act_window', string="Action", + help="The action that will trigger the tip"), + 'model': fields.char("Model", help="Model name on which to trigger the tip, e.g. 'res.partner'."), + 'type': fields.char("Type", help="Model type, e.g. lead or opportunity for crm.lead"), + 'mode': fields.char("Mode", help="Mode, e.g. kanban, form"), + 'trigger_selector': fields.char('Trigger selector', help='CSS selectors used to trigger the tip, separated by a comma (ANDed).'), + 'highlight_selector': fields.char('Highlight selector', help='CSS selector for the element to highlight'), + 'end_selector': fields.char('End selector', help='CSS selector used to end the tip'), + 'end_event': fields.char('End event', help='Event to end the tip'), + 'placement': fields.char('Placement', help='Popover placement, bottom, top, left or right'), + 'user_ids': fields.many2many('res.users', string='Consumed by'), + 'is_consumed': fields.function(_is_consumed, type='boolean', string='Tip consumed') + } + + _defaults = { + 'placement': 'auto', + 'end_event': 'click' + } + + def consume(self, cr, uid, ids, context=None): + return self.write(cr, uid, ids, { + 'user_ids': [(4, uid)] + }, context=context) diff --git a/addons/web_tip/web_tip_view.xml b/addons/web_tip/web_tip_view.xml new file mode 100644 index 00000000000..246116f9566 --- /dev/null +++ b/addons/web_tip/web_tip_view.xml @@ -0,0 +1,66 @@ + + + + + web.tip + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + web.tip + + + + + + + + + + web.tip.search + web.tip + + + + + + + + + Tips + web.tip + form + + + + +
+
diff --git a/openerp/addons/base/ir/ir_ui_view.py b/openerp/addons/base/ir/ir_ui_view.py index 08fb4d4ef59..31a2cff29da 100644 --- a/openerp/addons/base/ir/ir_ui_view.py +++ b/openerp/addons/base/ir/ir_ui_view.py @@ -37,8 +37,10 @@ from lxml import etree import openerp from openerp import tools, api from openerp.http import request +from openerp.modules.module import get_resource_path, get_resource_from_path from openerp.osv import fields, osv, orm -from openerp.tools import graph, SKIPPED_ELEMENT_TYPES +from openerp.tools import config, graph, SKIPPED_ELEMENT_TYPES +from openerp.tools.convert import _fix_multiple_roots from openerp.tools.parse_version import parse_version from openerp.tools.safe_eval import safe_eval as eval from openerp.tools.view_validation import valid_view @@ -105,6 +107,30 @@ def _hasclass(context, *cls): return node_classes.issuperset(cls) +def get_view_arch_from_file(filename, xmlid): + doc = etree.parse(filename) + node = None + for n in doc.xpath('//*[@id="%s"] | //*[@id="%s"]' % (xmlid, xmlid.split('.')[1])): + if n.tag in ('template', 'record'): + node = n + break + if node is not None: + if node.tag == 'record': + field = node.find('field[@name="arch"]') + _fix_multiple_roots(field) + inner = ''.join([etree.tostring(child) for child in field.iterchildren()]) + return field.text + inner + elif node.tag == 'template': + # The following dom operations has been copied from convert.py's _tag_template() + if not node.get('inherit_id'): + node.set('t-name', xmlid) + node.tag = 't' + else: + node.tag = 'data' + node.attrib.pop('id', None) + return etree.tostring(node) + raise ValueError("Could not find view arch definition in file '%s' for xmlid '%s'" % (filename, xmlid)) + xpath_utils = etree.FunctionNamespace(None) xpath_utils['hasclass'] = _hasclass @@ -123,6 +149,33 @@ class view(osv.osv): data_ids = IMD.search_read(cr, uid, [('id', 'in', ids), ('model', '=', 'ir.ui.view')], ['res_id'], context=context) return map(itemgetter('res_id'), data_ids) + def _arch_get(self, cr, uid, ids, name, arg, context=None): + result = {} + for view in self.browse(cr, uid, ids, context=context): + arch_fs = None + if config['dev_mode'] and view.arch_fs and view.xml_id: + # It is safe to split on / herebelow because arch_fs is explicitely stored with '/' + fullpath = get_resource_path(*view.arch_fs.split('/')) + arch_fs = get_view_arch_from_file(fullpath, view.xml_id) + result[view.id] = arch_fs or view.arch_db + return result + + def _arch_set(self, cr, uid, ids, field_name, field_value, args, context=None): + if not isinstance(ids, list): + ids = [ids] + if field_value: + for view in self.browse(cr, uid, ids, context=context): + data = dict(arch_db=field_value) + key = 'install_mode_data' + if context and key in context: + imd = context[key] + if self._model._name == imd['model'] and (not view.xml_id or view.xml_id == imd['xml_id']): + # we store the relative path to the resource instead of the absolute path + data['arch_fs'] = '/'.join(get_resource_from_path(imd['xml_file'])[0:2]) + self.write(cr, uid, ids, data, context=context) + + return True + _columns = { 'name': fields.char('View Name', required=True), 'model': fields.char('Object', select=True), @@ -137,7 +190,9 @@ class view(osv.osv): ('kanban', 'Kanban'), ('search','Search'), ('qweb', 'QWeb')], string='View Type'), - 'arch': fields.text('View Architecture', required=True), + 'arch': fields.function(_arch_get, fnct_inv=_arch_set, string='View Architecture', type="text", nodrop=True), + 'arch_db': fields.text('Arch Blob'), + 'arch_fs': fields.char('Arch Filename'), 'inherit_id': fields.many2one('ir.ui.view', 'Inherited View', ondelete='cascade', select=True), 'inherit_children_ids': fields.one2many('ir.ui.view','inherit_id', 'Inherit Views'), 'field_parent': fields.char('Child Field'), @@ -850,7 +905,9 @@ class view(osv.osv): #------------------------------------------------------ # QWeb template views #------------------------------------------------------ - @tools.ormcache_context(accepted_keys=('lang','inherit_branding', 'editable', 'translatable')) + @tools.conditional( + not config['dev_mode'], + tools.ormcache_context(accepted_keys=('lang','inherit_branding', 'editable', 'translatable'))) def read_template(self, cr, uid, xml_id, context=None): if isinstance(xml_id, (int, long)): view_id = xml_id diff --git a/openerp/addons/base/tests/test_views.py b/openerp/addons/base/tests/test_views.py index cf814947d5d..563b7c4db65 100644 --- a/openerp/addons/base/tests/test_views.py +++ b/openerp/addons/base/tests/test_views.py @@ -663,7 +663,7 @@ class test_views(ViewCase): name='base view', model=model, priority=1, - arch=""" + arch_db=""" @@ -677,7 +677,7 @@ class test_views(ViewCase): model=model, priority=1, inherit_id=vid, - arch=""" + arch_db=""" diff --git a/openerp/modules/__init__.py b/openerp/modules/__init__.py index 55eeefaaa19..79ed266150a 100644 --- a/openerp/modules/__init__.py +++ b/openerp/modules/__init__.py @@ -30,6 +30,7 @@ from openerp.modules.loading import load_modules from openerp.modules.module import get_modules, get_modules_with_version, \ load_information_from_description_file, get_module_resource, get_module_path, \ - initialize_sys_path, load_openerp_module, init_module_models, adapt_version + initialize_sys_path, load_openerp_module, init_module_models, adapt_version, \ + get_resource_path, get_resource_from_path # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/openerp/modules/module.py b/openerp/modules/module.py index 72b87e0bbfa..ea9e2b07424 100644 --- a/openerp/modules/module.py +++ b/openerp/modules/module.py @@ -154,7 +154,7 @@ def get_module_filetree(module, dir='.'): return tree -def get_module_resource(module, *args): +def get_resource_path(module, *args): """Return the full path of a resource of the given module. :param module: module name @@ -163,7 +163,6 @@ def get_module_resource(module, *args): :rtype: str :return: absolute path to the resource - TODO name it get_resource_path TODO make it available inside on osv object (self.get_resource_path) """ mod_path = get_module_path(module) @@ -175,6 +174,33 @@ def get_module_resource(module, *args): return resource_path return False +# backwards compatibility +get_module_resource = get_resource_path + +def get_resource_from_path(path): + """Tries to extract the module name and the resource's relative path + out of an absolute resource path. + + If operation is successfull, returns a tuple containing the module name, the relative path + to the resource using '/' as filesystem seperator[1] and the same relative path using + os.path.sep seperators. + + [1] same convention as the resource path declaration in manifests + + :param path: absolute resource path + + :rtype: tuple + :return: tuple(module_name, relative_path, os_relative_path) if possible, else None + """ + resource = [path.replace(adpath, '') for adpath in ad_paths if path.startswith(adpath)] + if resource: + relative = resource[0].split(os.path.sep) + if not relative[0]: + relative.pop(0) + module = relative.pop(0) + return (module, '/'.join(relative), os.path.sep.join(relative)) + return None + def get_module_icon(module): iconpath = ['static', 'description', 'icon.png'] if get_module_resource(module, *iconpath): diff --git a/openerp/tools/config.py b/openerp/tools/config.py index a9132ff42bc..11080959c78 100644 --- a/openerp/tools/config.py +++ b/openerp/tools/config.py @@ -257,6 +257,7 @@ class configmanager(object): group = optparse.OptionGroup(parser, "Advanced options") if os.name == 'posix': group.add_option('--auto-reload', dest='auto_reload', action='store_true', my_default=False, help='enable auto reload') + group.add_option('--dev', dest='dev_mode', action='store_true', my_default=False, help='enable developper mode') group.add_option('--debug', dest='debug_mode', action='store_true', my_default=False, help='enable debug mode') group.add_option("--stop-after-init", action="store_true", dest="stop_after_init", my_default=False, help="stop the server after its initialization") @@ -407,7 +408,7 @@ class configmanager(object): # if defined but None take the configfile value keys = [ 'language', 'translate_out', 'translate_in', 'overwrite_existing_translations', - 'debug_mode', 'smtp_ssl', 'load_language', + 'debug_mode', 'dev_mode', 'smtp_ssl', 'load_language', 'stop_after_init', 'logrotate', 'without_demo', 'xmlrpc', 'syslog', 'list_db', 'xmlrpcs', 'proxy_mode', 'test_file', 'test_enable', 'test_commit', 'test_report_directory', diff --git a/openerp/tools/convert.py b/openerp/tools/convert.py index d55ba52eb71..9be6547ba63 100644 --- a/openerp/tools/convert.py +++ b/openerp/tools/convert.py @@ -774,9 +774,17 @@ form: module.record_id""" % (xml_id,) rec_model = rec.get("model").encode('ascii') model = self.pool[rec_model] rec_id = rec.get("id",'').encode('ascii') - rec_context = rec.get("context", None) + rec_context = rec.get("context", {}) if rec_context: rec_context = unsafe_eval(rec_context) + + if self.xml_filename and rec_id: + rec_context['install_mode_data'] = dict( + xml_file=self.xml_filename, + xml_id=rec_id, + model=rec_model, + ) + self._test_xml_id(rec_id) # in update mode, the record won't be updated if the data node explicitely # opt-out using @noupdate="1". A second check will be performed in @@ -941,7 +949,7 @@ form: module.record_id""" % (xml_id,) raise ParseError, (misc.ustr(e), etree.tostring(rec).rstrip(), rec.getroottree().docinfo.URL, rec.sourceline), exc_info[2] return True - def __init__(self, cr, module, idref, mode, report=None, noupdate=False): + def __init__(self, cr, module, idref, mode, report=None, noupdate=False, xml_filename=None): self.mode = mode self.module = module @@ -953,6 +961,7 @@ form: module.record_id""" % (xml_id,) report = assertion_report.assertion_report() self.assertion_report = report self.noupdate = noupdate + self.xml_filename = xml_filename self._tags = { 'menuitem': self._tag_menuitem, 'record': self._tag_record, @@ -1066,7 +1075,11 @@ def convert_xml_import(cr, module, xmlfile, idref=None, mode='init', noupdate=Fa if idref is None: idref={} - obj = xml_import(cr, module, idref, mode, report=report, noupdate=noupdate) + if isinstance(xmlfile, file): + xml_filename = xmlfile.name + else: + xml_filename = xmlfile + obj = xml_import(cr, module, idref, mode, report=report, noupdate=noupdate, xml_filename=xml_filename) obj.parse(doc.getroot()) return True diff --git a/openerp/tools/func.py b/openerp/tools/func.py index 7d04c62a1c9..9df320c22d0 100644 --- a/openerp/tools/func.py +++ b/openerp/tools/func.py @@ -20,7 +20,7 @@ # ############################################################################## -__all__ = ['synchronized', 'lazy_property'] +__all__ = ['synchronized', 'lazy_property', 'conditional'] from functools import wraps from inspect import getsourcefile @@ -51,6 +51,19 @@ class lazy_property(object): if isinstance(getattr(cls, name, None), lazy_property): obj_dict.pop(name) +def conditional(condition, decorator): + """ Decorator for a conditionally applied decorator. + + Example: + + @conditional(get_config('use_cache'), ormcache) + def fn(): + pass + """ + if condition: + return decorator + else: + return lambda fn: fn def synchronized(lock_attr='_lock'): def decorator(func):