diff --git a/addons/.bzrignore b/addons/.bzrignore deleted file mode 100644 index 8c34871011766994d9efa304fb1e95991198d608..0000000000000000000000000000000000000000 --- a/addons/.bzrignore +++ /dev/null @@ -1,2 +0,0 @@ -.* -**/node_modules diff --git a/addons/sale/sale.py b/addons/sale/sale.py index 45c32a33b22384af596855a83d939ad5dacaad80..397bc0deccc15a4f27d0b7e0ddf9d6d1cdc2726a 100644 --- a/addons/sale/sale.py +++ b/addons/sale/sale.py @@ -128,13 +128,12 @@ class sale_order(osv.osv): sale_clause = '' no_invoiced = False for arg in args: - if arg[1] == '=': - if arg[2]: - clause += 'AND inv.state = \'paid\'' - else: - clause += 'AND inv.state != \'cancel\' AND sale.state != \'cancel\' AND inv.state <> \'paid\' AND rel.order_id = sale.id ' - sale_clause = ', sale_order AS sale ' - no_invoiced = True + if (arg[1] == '=' and arg[2]) or (arg[1] == '!=' and not arg[2]): + clause += 'AND inv.state = \'paid\'' + else: + clause += 'AND inv.state != \'cancel\' AND sale.state != \'cancel\' AND inv.state <> \'paid\' AND rel.order_id = sale.id ' + sale_clause = ', sale_order AS sale ' + no_invoiced = True cursor.execute('SELECT rel.order_id ' \ 'FROM sale_order_invoice_rel AS rel, account_invoice AS inv '+ sale_clause + \ diff --git a/addons/web/static/src/css/base.css b/addons/web/static/src/css/base.css index b4c0d3050bf68b50907c1491003cee4e9eee04c6..6a82696ac4b1d53a46e71ca4604faa8e66303344 100644 --- a/addons/web/static/src/css/base.css +++ b/addons/web/static/src/css/base.css @@ -2624,11 +2624,11 @@ .openerp .oe_list_editable .oe_list_content td.oe_list_field_cell { padding: 4px 6px 3px; } -.openerp .oe_list.oe_list_editable.oe_editing .oe_edition .oe_list_field_cell:not(.oe_readonly) { +.openerp .oe_list.oe_list_editable.oe_editing .oe_edition .oe_list_field_cell { color: transparent; text-shadow: none; } -.openerp .oe_list.oe_list_editable.oe_editing .oe_edition .oe_list_field_cell:not(.oe_readonly) * { +.openerp .oe_list.oe_list_editable.oe_editing .oe_edition .oe_list_field_cell * { visibility: hidden; } .openerp .oe_list.oe_list_editable.oe_editing .oe_m2o_drop_down_button { @@ -2644,6 +2644,13 @@ min-width: 0; max-width: none; } +.openerp .oe_list.oe_list_editable.oe_editing .oe_form_field.oe_list_field_handle { + color: transparent; +} +.openerp .oe_list.oe_list_editable.oe_editing .oe_form_field.oe_readonly { + padding: 4px 6px 3px; + text-align: left; +} .openerp .oe_list.oe_list_editable.oe_editing .oe_form_field input, .openerp .oe_list.oe_list_editable.oe_editing .oe_form_field textarea { height: 27px; -moz-border-radius: 0; @@ -2655,9 +2662,14 @@ .openerp .oe_list.oe_list_editable.oe_editing .oe_form_field input, .openerp .oe_list.oe_list_editable.oe_editing .oe_form_field textarea, .openerp .oe_list.oe_list_editable.oe_editing .oe_form_field select { min-width: 0; } -.openerp .oe_list.oe_list_editable.oe_editing .oe_form_field.oe_form_field_float input, .openerp .oe_list.oe_list_editable.oe_editing .oe_form_field.oe_form_view_integer input { +.openerp .oe_list.oe_list_editable.oe_editing .oe_form_field.oe_form_field_float.oe_readonly, .openerp .oe_list.oe_list_editable.oe_editing .oe_form_field.oe_form_view_integer.oe_readonly { + padding: 6px 0px 0px; text-align: right; + max-width: 100px; +} +.openerp .oe_list.oe_list_editable.oe_editing .oe_form_field.oe_form_field_float input, .openerp .oe_list.oe_list_editable.oe_editing .oe_form_field.oe_form_view_integer input { width: 100% !important; + text-align: right; } .openerp .oe_list.oe_list_editable.oe_editing .oe_form_field.oe_form_field_datetime input.oe_datepicker_master, .openerp .oe_list.oe_list_editable.oe_editing .oe_form_field.oe_form_field_date input.oe_datepicker_master { width: 100% !important; diff --git a/addons/web/static/src/css/base.sass b/addons/web/static/src/css/base.sass index 136cdbdef24bfbeed2427738715fed0c8aa76a7e..69d338b637d23544ba4e69b8d9e69ed23df00bf1 100644 --- a/addons/web/static/src/css/base.sass +++ b/addons/web/static/src/css/base.sass @@ -2117,7 +2117,7 @@ $sheet-padding: 16px .oe_list_editable .oe_list_content td.oe_list_field_cell padding: 4px 6px 3px .oe_list.oe_list_editable.oe_editing - .oe_edition .oe_list_field_cell:not(.oe_readonly) + .oe_edition .oe_list_field_cell * visibility: hidden color: transparent @@ -2129,6 +2129,11 @@ $sheet-padding: 16px .oe_input_icon margin-top: 5px .oe_form_field + &.oe_list_field_handle + color: transparent + &.oe_readonly + padding: 4px 6px 3px + text-align: left min-width: 0 max-width: none input, textarea @@ -2139,9 +2144,13 @@ $sheet-padding: 16px input, textarea, select min-width: 0 &.oe_form_field_float,&.oe_form_view_integer - input + &.oe_readonly + padding: 6px 0px 0px text-align: right + max-width: 100px + input width: 100% !important + text-align: right &.oe_form_field_datetime,&.oe_form_field_date input.oe_datepicker_master width: 100% !important diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js index c816d186fde91bdab5e27c450a49e317a4b9bc1b..1c75f341436773003f02f58edab46e9d2a2e482b 100644 --- a/addons/web/static/src/js/view_form.js +++ b/addons/web/static/src/js/view_form.js @@ -2810,7 +2810,9 @@ instance.web.form.FieldDatetime = instance.web.form.AbstractField.extend(instanc }, set_dimensions: function (height, width) { this._super(height, width); - this.datewidget.$input.css('height', height); + if (!this.get("effective_readonly")) { + this.datewidget.$input.css('height', height); + } } }); @@ -3510,6 +3512,7 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(instanc this.floating = false; this.current_display = null; this.is_started = false; + this.ignore_focusout = false; }, reinit_value: function(val) { this.internal_set_value(val); @@ -3641,6 +3644,7 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(instanc var ed_delay = 200; var ed_duration = 15000; var anyoneLoosesFocus = function (e) { + if (self.ignore_focusout) { return; } var used = false; if (self.floating) { if (self.last_search.length > 0) { @@ -3844,11 +3848,17 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(instanc _search_create_popup: function() { this.no_ed = true; this.ed_def.reject(); - return instance.web.form.CompletionFieldMixin._search_create_popup.apply(this, arguments); + this.ignore_focusout = true; + this.reinit_value(false); + var res = instance.web.form.CompletionFieldMixin._search_create_popup.apply(this, arguments); + this.ignore_focusout = false; + this.no_ed = false; + return res; }, set_dimensions: function (height, width) { this._super(height, width); - this.$input.css('height', height); + if (!this.get("effective_readonly") && this.$input) + this.$input.css('height', height); } }); diff --git a/addons/web/static/src/js/view_list_editable.js b/addons/web/static/src/js/view_list_editable.js index ea2c28d9c3f924a5b8e5ac1ac4a9f727d5ff93aa..144de8eb21c914c2239309f593cdcdedc1f29443 100644 --- a/addons/web/static/src/js/view_list_editable.js +++ b/addons/web/static/src/js/view_list_editable.js @@ -285,9 +285,7 @@ if (!this.editor.is_editing()) { return; } for(var i=0, len=this.fields_for_resize.length; i<len; ++i) { var item = this.fields_for_resize[i]; - if (!item.field.get('effective_invisible')) { - this.resize_field(item.field, item.cell); - } + this.resize_field(item.field, item.cell); } }, /** @@ -306,6 +304,11 @@ at: 'left top', of: $cell }); + if (field.get('effective_readonly')) { + field.$el.addClass('oe_readonly'); + } + if(field.widget == "handle") + field.$el.addClass('oe_list_field_handle'); }, /** * @return {jQuery.Deferred} @@ -451,13 +454,7 @@ setup_events: function () { var self = this; _.each(this.editor.form.fields, function(field, field_name) { - var set_invisible = function() { - field.set({'force_invisible': field.get('effective_readonly')}); - }; - field.on("change:effective_readonly", self, set_invisible); - set_invisible(); - field.on('change:effective_invisible', self, function () { - if (field.get('effective_invisible')) { return; } + field.on("change:effective_readonly", self, function(){ var item = _(self.fields_for_resize).find(function (item) { return item.field === field; }); diff --git a/openerp/addons/base/ir/ir_http.py b/openerp/addons/base/ir/ir_http.py index ac3091e33b954de26e23761c34d01831ce83ea3d..21c8b7ef94181f773b3666be4c6d817040ade131 100644 --- a/openerp/addons/base/ir/ir_http.py +++ b/openerp/addons/base/ir/ir_http.py @@ -76,17 +76,23 @@ class ir_http(osv.AbstractModel): request.uid = request.session.uid def _authenticate(self, auth_method='user'): - if request.session.uid: - try: - request.session.check_security() - # what if error in security.check() - # -> res_users.check() - # -> res_users.check_credentials() - except (openerp.exceptions.AccessDenied, openerp.http.SessionExpiredException): - # All other exceptions mean undetermined status (e.g. connection pool full), - # let them bubble up - request.session.logout() - getattr(self, "_auth_method_%s" % auth_method)() + try: + if request.session.uid: + try: + request.session.check_security() + # what if error in security.check() + # -> res_users.check() + # -> res_users.check_credentials() + except (openerp.exceptions.AccessDenied, openerp.http.SessionExpiredException): + # All other exceptions mean undetermined status (e.g. connection pool full), + # let them bubble up + request.session.logout() + getattr(self, "_auth_method_%s" % auth_method)() + except (openerp.exceptions.AccessDenied, openerp.http.SessionExpiredException): + raise + except Exception: + _logger.exception("Exception during request Authentication.") + raise openerp.exceptions.AccessDenied() return auth_method def _handle_exception(self, exception): diff --git a/openerp/addons/base/module/module.py b/openerp/addons/base/module/module.py index eb154d4a05fb237fc2450ec3c614a33fa35d05e8..df673f95b32d8472b3a4e4d47c536a0470776e92 100644 --- a/openerp/addons/base/module/module.py +++ b/openerp/addons/base/module/module.py @@ -496,6 +496,7 @@ class module(osv.osv): 'params': {'menu_id': menu_ids and menu_ids[0] or False} } + #TODO remove me in master, not called anymore def button_immediate_uninstall(self, cr, uid, ids, context=None): """ Uninstall the selected module(s) immediately and fully, diff --git a/openerp/addons/base/module/module_view.xml b/openerp/addons/base/module/module_view.xml index 110b607fd4e73b1feb5a351b1e51143978bbfa79..9077a1f86db93c4fdf7fc9c929a7cc3ea49cfa22 100644 --- a/openerp/addons/base/module/module_view.xml +++ b/openerp/addons/base/module/module_view.xml @@ -82,8 +82,7 @@ <div> <button name="button_immediate_install" states="uninstalled" string="Install" type="object" class="oe_highlight"/> <button name="button_immediate_upgrade" states="installed" string="Upgrade" type="object" class="oe_highlight"/> - <button name="button_immediate_uninstall" states="installed" string="Uninstall" type="object" - confirm="Do you confirm the uninstallation of this module? This will permanently erase all data currently stored by the module!"/> + <button name="button_uninstall" states="installed" string="Uninstall" type="object"/> <button name="button_uninstall_cancel" states="to remove" string="Cancel Uninstall" type="object"/> <button name="button_upgrade_cancel" states="to upgrade" string="Cancel Upgrade" type="object"/> <button name="button_install_cancel" states="to install" string="Cancel Install" type="object"/> diff --git a/openerp/addons/base/module/wizard/base_module_upgrade.py b/openerp/addons/base/module/wizard/base_module_upgrade.py index c75058ab8350c13f462b105c8086f15ffcccab3c..365895be7acae460f043cab71a4a6d88cab899bf 100644 --- a/openerp/addons/base/module/wizard/base_module_upgrade.py +++ b/openerp/addons/base/module/wizard/base_module_upgrade.py @@ -68,6 +68,20 @@ class base_module_upgrade(osv.osv_memory): res = mod_obj.read(cr, uid, ids, ['name','state'], context) return {'module_info': '\n'.join(map(lambda x: x['name']+' : '+x['state'], res))} + def upgrade_module_cancel(self, cr, uid, ids, context=None): + mod_obj = self.pool.get('ir.module.module') + to_installed_ids = mod_obj.search(cr, uid, [ + ('state', 'in', ['to upgrade', 'to remove'])]) + if to_installed_ids: + mod_obj.write(cr, uid, to_installed_ids, {'state': 'installed'}, context=context) + + to_uninstalled_ids = mod_obj.search(cr, uid, [ + ('state', '=', 'to install')]) + if to_uninstalled_ids: + mod_obj.write(cr, uid, to_uninstalled_ids, {'state': 'uninstalled'}, context=context) + + return {'type': 'ir.actions.act_window_close'} + def upgrade_module(self, cr, uid, ids, context=None): ir_module = self.pool.get('ir.module.module') diff --git a/openerp/addons/base/module/wizard/base_module_upgrade_view.xml b/openerp/addons/base/module/wizard/base_module_upgrade_view.xml index 1a1225befcb78ea5761487918ac6a751b5a9581e..10c023e51482e3713a321b6907761bfbb66c9921 100644 --- a/openerp/addons/base/module/wizard/base_module_upgrade_view.xml +++ b/openerp/addons/base/module/wizard/base_module_upgrade_view.xml @@ -7,14 +7,15 @@ <field name="model">base.module.upgrade</field> <field name="arch" type="xml"> <form string="System Update" version="7.0"> - <div><label string="Your system will be updated."/></div> - <div><label string="Note that this operation might take a few minutes."/></div> - <separator string="Modules to Update"/> + <p>This module will trigger the uninstallation of below modules.</p> + <p><strong>This operation will permanently erase all data currently stored by the modules!</strong></p> + <p>If you wish to cancel the process, press the cancel button below</p> + <separator string="Impacted Modules"/> <field name="module_info"/> <footer> <button name="upgrade_module" string="Update" type="object" class="oe_highlight"/> or - <button string="Cancel" class="oe_link" special="cancel"/> + <button string="Cancel" class="oe_link" name="upgrade_module_cancel" type="object"/> </footer> </form> </field> diff --git a/openerp/addons/base/res/res_config.py b/openerp/addons/base/res/res_config.py index 5bbd377f29dc736d3e81584670df883ed4c79866..06453526ebcba285dd10af5aadc9994b7efc38c1 100644 --- a/openerp/addons/base/res/res_config.py +++ b/openerp/addons/base/res/res_config.py @@ -28,6 +28,7 @@ from openerp.osv import osv, fields from openerp.tools import ustr from openerp.tools.translate import _ from openerp import exceptions +from lxml import etree _logger = logging.getLogger(__name__) @@ -434,6 +435,43 @@ class res_config_settings(osv.osv_memory, res_config_module_installation_mixin): def copy(self, cr, uid, id, values, context=None): raise osv.except_osv(_("Cannot duplicate configuration!"), "") + def fields_view_get(self, cr, user, view_id=None, view_type='form', + context=None, toolbar=False, submenu=False): + ret_val = super(res_config_settings, self).fields_view_get( + cr, user, view_id=view_id, view_type=view_type, context=context, + toolbar=toolbar, submenu=submenu) + + doc = etree.XML(ret_val['arch']) + + for field in ret_val['fields']: + if not field.startswith("module_"): + continue + for node in doc.xpath("//field[@name='%s']" % field): + if 'on_change' not in node.attrib: + node.set("on_change", + "onchange_module(%s, '%s')" % (field, field)) + + ret_val['arch'] = etree.tostring(doc) + return ret_val + + def onchange_module(self, cr, uid, ids, field_value, module_name, context={}): + module_pool = self.pool.get('ir.module.module') + module_ids = module_pool.search( + cr, uid, [('name', '=', module_name.replace("module_", '')), + ('state','in', ['to install', 'installed', 'to upgrade'])], + context=context) + + if module_ids and not field_value: + dep_ids = module_pool.downstream_dependencies(cr, uid, module_ids, context=context) + dep_name = [x.shortdesc for x in module_pool.browse( + cr, uid, dep_ids + module_ids, context=context)] + message = '\n'.join(dep_name) + return {'warning': {'title': _('Warning!'), + 'message': + _('Disabling this option will also uninstall the following modules \n%s' % message) + }} + return {} + def _get_classified_fields(self, cr, uid, context=None): """ return a dictionary with the fields classified by category:: diff --git a/openerp/http.py b/openerp/http.py index abee8ac2b3d70ab815d3ffbfa7dc32a02b0f5f98..ceddf234c1cba8fc3530bee4bc5796b7dee423ff 100644 --- a/openerp/http.py +++ b/openerp/http.py @@ -270,8 +270,6 @@ class WebRequest(object): to abitrary responses. Anything returned (except None) will be used as response.""" self._failed = exception # prevent tx commit - if isinstance(exception, werkzeug.exceptions.HTTPException): - return exception raise def _call_function(self, *args, **kwargs): @@ -538,6 +536,15 @@ class HttpRequest(WebRequest): params.pop('session_id', None) self.params = params + def _handle_exception(self, exception): + """Called within an except block to allow converting exceptions + to abitrary responses. Anything returned (except None) will + be used as response.""" + try: + return super(HttpRequest, self)._handle_exception(exception) + except werkzeug.exceptions.HTTPException, e: + return e + def dispatch(self): if request.httprequest.method == 'OPTIONS' and request.endpoint and request.endpoint.routing.get('cors'): headers = { diff --git a/openerp/service/server.py b/openerp/service/server.py index 72e130edf8b6a8e17985f49c88a86f6ecad285f2..c0dc15caab7af8004ca3888a425a6626ddbc9c40 100644 --- a/openerp/service/server.py +++ b/openerp/service/server.py @@ -46,8 +46,15 @@ SLEEP_INTERVAL = 60 # 1 min #---------------------------------------------------------- # Werkzeug WSGI servers patched #---------------------------------------------------------- +class LoggingBaseWSGIServerMixIn(object): + def handle_error(self, request, client_address): + t, e, _ = sys.exc_info() + if t == socket.error and e.errno == errno.EPIPE: + # broken pipe, ignore error + return + _logger.exception('Exception happened during processing of request from %s', client_address) -class BaseWSGIServerNoBind(werkzeug.serving.BaseWSGIServer): +class BaseWSGIServerNoBind(LoggingBaseWSGIServerMixIn, werkzeug.serving.BaseWSGIServer): """ werkzeug Base WSGI Server patched to skip socket binding. PreforkServer use this class, sets the socket and calls the process_request() manually """ @@ -74,7 +81,7 @@ class RequestHandler(werkzeug.serving.WSGIRequestHandler): # should also work with systemd socket activation. This is currently untested # and not yet used. -class ThreadedWSGIServerReloadable(werkzeug.serving.ThreadedWSGIServer): +class ThreadedWSGIServerReloadable(LoggingBaseWSGIServerMixIn, werkzeug.serving.ThreadedWSGIServer): """ werkzeug Threaded WSGI Server patched to allow reusing a listen socket given by the environement, this is used by autoreload to keep the listen socket open when a reload happens.