diff --git a/addons/account/account_invoice.py b/addons/account/account_invoice.py index 9d4654c9eee..87c96fbdaf2 100644 --- a/addons/account/account_invoice.py +++ b/addons/account/account_invoice.py @@ -295,7 +295,8 @@ class account_invoice(osv.osv): }, multi='all'), 'currency_id': fields.many2one('res.currency', 'Currency', required=True, readonly=True, states={'draft':[('readonly',False)]}, track_visibility='always'), - 'journal_id': fields.many2one('account.journal', 'Journal', required=True, readonly=True, states={'draft':[('readonly',False)]}), + 'journal_id': fields.many2one('account.journal', 'Journal', required=True, readonly=True, states={'draft':[('readonly',False)]}, + domain="[('type', 'in', {'out_invoice': ['sale'], 'out_refund': ['sale_refund'], 'in_refund': ['purchase_refund'], 'in_invoice': ['purchase']}.get(type, [])), ('company_id', '=', company_id)]"), 'company_id': fields.many2one('res.company', 'Company', required=True, change_default=True, readonly=True, states={'draft':[('readonly',False)]}), 'check_total': fields.float('Verification Total', digits_compute=dp.get_precision('Account'), readonly=True, states={'draft':[('readonly',False)]}), 'reconciled': fields.function(_reconciled, string='Paid/Reconciled', type='boolean', diff --git a/addons/account/views/report_agedpartnerbalance.xml b/addons/account/views/report_agedpartnerbalance.xml index 019f32a35de..c1f9047e330 100644 --- a/addons/account/views/report_agedpartnerbalance.xml +++ b/addons/account/views/report_agedpartnerbalance.xml @@ -99,7 +99,7 @@ - + diff --git a/addons/account_analytic_analysis/account_analytic_analysis.py b/addons/account_analytic_analysis/account_analytic_analysis.py index 519660fb708..9550f77bcab 100644 --- a/addons/account_analytic_analysis/account_analytic_analysis.py +++ b/addons/account_analytic_analysis/account_analytic_analysis.py @@ -22,7 +22,6 @@ from dateutil.relativedelta import relativedelta import datetime import logging import time -import traceback from openerp.osv import osv, fields from openerp.osv.orm import intersect, except_orm @@ -73,6 +72,7 @@ class account_analytic_invoice_line(osv.osv): result = {} res = self.pool.get('product.product').browse(cr, uid, product, context=local_context) + price = False if price_unit is not False: price = price_unit elif pricelist_id: @@ -746,29 +746,32 @@ class account_analytic_account(osv.osv): contract_ids = ids else: contract_ids = self.search(cr, uid, [('recurring_next_date','<=', current_date), ('state','=', 'open'), ('recurring_invoices','=', True), ('type', '=', 'contract')]) - for contract in self.browse(cr, uid, contract_ids, context=context): - try: - invoice_values = self._prepare_invoice(cr, uid, contract, context=context) - invoice_ids.append(self.pool['account.invoice'].create(cr, uid, invoice_values, context=context)) - next_date = datetime.datetime.strptime(contract.recurring_next_date or current_date, "%Y-%m-%d") - interval = contract.recurring_interval - if contract.recurring_rule_type == 'daily': - new_date = next_date+relativedelta(days=+interval) - elif contract.recurring_rule_type == 'weekly': - new_date = next_date+relativedelta(weeks=+interval) - elif contract.recurring_rule_type == 'monthly': - new_date = next_date+relativedelta(months=+interval) - else: - new_date = next_date+relativedelta(years=+interval) - self.write(cr, uid, [contract.id], {'recurring_next_date': new_date.strftime('%Y-%m-%d')}, context=context) - if automatic: - cr.commit() - except Exception: - if automatic: - cr.rollback() - _logger.error(traceback.format_exc()) - else: - raise + if contract_ids: + cr.execute('SELECT company_id, array_agg(id) as ids FROM account_analytic_account WHERE id IN %s GROUP BY company_id', (tuple(contract_ids),)) + for company_id, ids in cr.fetchall(): + for contract in self.browse(cr, uid, ids, context=dict(context, company_id=company_id, force_company=company_id)): + try: + invoice_values = self._prepare_invoice(cr, uid, contract, context=context) + invoice_ids.append(self.pool['account.invoice'].create(cr, uid, invoice_values, context=context)) + next_date = datetime.datetime.strptime(contract.recurring_next_date or current_date, "%Y-%m-%d") + interval = contract.recurring_interval + if contract.recurring_rule_type == 'daily': + new_date = next_date+relativedelta(days=+interval) + elif contract.recurring_rule_type == 'weekly': + new_date = next_date+relativedelta(weeks=+interval) + elif contract.recurring_rule_type == 'monthly': + new_date = next_date+relativedelta(months=+interval) + else: + new_date = next_date+relativedelta(years=+interval) + self.write(cr, uid, [contract.id], {'recurring_next_date': new_date.strftime('%Y-%m-%d')}, context=context) + if automatic: + cr.commit() + except Exception: + if automatic: + cr.rollback() + _logger.exception('Fail to create recurring invoice for contract %s', contract.code) + else: + raise return invoice_ids class account_analytic_account_summary_user(osv.osv): diff --git a/addons/account_voucher/voucher_sales_purchase_view.xml b/addons/account_voucher/voucher_sales_purchase_view.xml index 97f96f75901..2dd53832529 100644 --- a/addons/account_voucher/voucher_sales_purchase_view.xml +++ b/addons/account_voucher/voucher_sales_purchase_view.xml @@ -248,7 +248,7 @@ - + diff --git a/addons/auth_crypt/auth_crypt.py b/addons/auth_crypt/auth_crypt.py index 4651d27fe7d..c5bd5799017 100644 --- a/addons/auth_crypt/auth_crypt.py +++ b/addons/auth_crypt/auth_crypt.py @@ -117,10 +117,22 @@ def sh256crypt(cls, password, salt, magic=magic_sha256): class res_users(osv.osv): _inherit = "res.users" + def init(self, cr): + """Encrypt all passwords at module installation""" + cr.execute("SELECT id, password FROM res_users WHERE password IS NOT NULL and password != ''") + for user in cr.fetchall(): + self._set_encrypted_password(cr, user[0], user[1]) + + def _set_encrypted_password(self, cr, uid, plain_password): + """Set an encrypted password for a given user""" + salt = gen_salt() + stored_password_crypt = md5crypt(plain_password, salt) + cr.execute("UPDATE res_users SET password = '', password_crypt = %s WHERE id = %s", + (stored_password_crypt, uid)) + def set_pw(self, cr, uid, id, name, value, args, context): if value: - encrypted = md5crypt(value, gen_salt()) - cr.execute("update res_users set password='', password_crypt=%s where id=%s", (encrypted, id)) + self._set_encrypted_password(cr, id, value) del value def get_pw( self, cr, uid, ids, name, args, context ): @@ -144,9 +156,7 @@ class res_users(osv.osv): if cr.rowcount: stored_password, stored_password_crypt = cr.fetchone() if stored_password and not stored_password_crypt: - salt = gen_salt() - stored_password_crypt = md5crypt(stored_password, salt) - cr.execute("UPDATE res_users SET password='', password_crypt=%s WHERE id=%s", (stored_password_crypt, uid)) + self._set_encrypted_password(cr, uid, stored_password) try: return super(res_users, self).check_credentials(cr, uid, password) except openerp.exceptions.AccessDenied: diff --git a/addons/auth_oauth/controllers/main.py b/addons/auth_oauth/controllers/main.py index 99f134a5cbd..7652420d7c6 100644 --- a/addons/auth_oauth/controllers/main.py +++ b/addons/auth_oauth/controllers/main.py @@ -74,7 +74,7 @@ class OAuthLogin(Home): state = dict( d=request.session.db, p=provider['id'], - r=redirect, + r=werkzeug.url_quote_plus(redirect), ) token = request.params.get('token') if token: @@ -143,7 +143,7 @@ class OAuthController(http.Controller): cr.commit() action = state.get('a') menu = state.get('m') - redirect = state.get('r') + redirect = werkzeug.url_unquote_plus(state['r']) if state.get('r') else False url = '/web' if redirect: url = redirect diff --git a/addons/calendar/static/src/js/base_calendar.js b/addons/calendar/static/src/js/base_calendar.js index eed958e20d9..37669840818 100644 --- a/addons/calendar/static/src/js/base_calendar.js +++ b/addons/calendar/static/src/js/base_calendar.js @@ -102,7 +102,7 @@ openerp.calendar = function(instance) { var self = this; var action_url = ''; - action_url = _.str.sprintf('/?db=%s#id=%s&view_type=form&model=calendar.event', db, meeting_id); + action_url = _.str.sprintf('/web?db=%s#id=%s&view_type=form&model=calendar.event', db, meeting_id); var reload_page = function(){ return location.replace(action_url); diff --git a/addons/event/report/report_event_registration.py b/addons/event/report/report_event_registration.py index e76b5c60d1f..c4a4dfd6b2d 100644 --- a/addons/event/report/report_event_registration.py +++ b/addons/event/report/report_event_registration.py @@ -32,7 +32,7 @@ class report_event_registration(osv.osv): 'draft_state': fields.integer(' # No of Draft Registrations', size=20), 'confirm_state': fields.integer(' # No of Confirmed Registrations', size=20), 'seats_max': fields.integer('Max Seats'), - 'nbevent': fields.integer('Number Of Events'), + 'nbevent': fields.integer('Number of Registrations'), 'event_type': fields.many2one('event.type', 'Event Type'), 'registration_state': fields.selection([('draft', 'Draft'), ('confirm', 'Confirmed'), ('done', 'Attended'), ('cancel', 'Cancelled')], 'Registration State', readonly=True, required=True), 'event_state': fields.selection([('draft', 'Draft'), ('confirm', 'Confirmed'), ('done', 'Done'), ('cancel', 'Cancelled')], 'Event State', readonly=True, required=True), @@ -59,7 +59,7 @@ class report_event_registration(osv.osv): r.name AS name_registration, e.company_id AS company_id, e.date_begin AS event_date, - count(e.id) AS nbevent, + count(r.id) AS nbevent, CASE WHEN r.state IN ('draft') THEN r.nb_register ELSE 0 END AS draft_state, CASE WHEN r.state IN ('open','done') THEN r.nb_register ELSE 0 END AS confirm_state, e.type AS event_type, diff --git a/addons/event/static/src/css/event.css b/addons/event/static/src/css/event.css index ff5bfaba19b..53c03cc03f0 100644 --- a/addons/event/static/src/css/event.css +++ b/addons/event/static/src/css/event.css @@ -1,7 +1,7 @@ .oe_event_date{ border-top-left-radius:3px; border-top-right-radius:3px; - font-size: 48px; + font-size: 36px; height: auto; font-weight: bold; text-align: center; diff --git a/addons/gamification/models/challenge.py b/addons/gamification/models/challenge.py index 6c79d439c4a..60723ffdc3a 100644 --- a/addons/gamification/models/challenge.py +++ b/addons/gamification/models/challenge.py @@ -58,7 +58,7 @@ def start_end_date_for_period(period, default_start_date=False, default_end_date end_date = default_end_date if start_date and end_date: - return (start_date.strftime(DF), end_date.strftime(DF)) + return (datetime.strftime(start_date, DF), datetime.strftime(end_date, DF)) else: return (start_date, end_date) diff --git a/addons/google_calendar/google_calendar.py b/addons/google_calendar/google_calendar.py index 543c81977aa..dd1d45c05fe 100644 --- a/addons/google_calendar/google_calendar.py +++ b/addons/google_calendar/google_calendar.py @@ -699,7 +699,7 @@ class google_calendar(osv.AbstractModel): for att in att_obj.browse(cr, uid, my_att_ids, context=context): event = att.event_id - base_event_id = att.google_internal_event_id.split('_')[0] + base_event_id = att.google_internal_event_id.rsplit('_', 1)[0] if base_event_id not in event_to_synchronize: event_to_synchronize[base_event_id] = {} @@ -721,7 +721,7 @@ class google_calendar(osv.AbstractModel): for event in all_event_from_google.values(): event_id = event.get('id') - base_event_id = event_id.split('_')[0] + base_event_id = event_id.rsplit('_', 1)[0] if base_event_id not in event_to_synchronize: event_to_synchronize[base_event_id] = {} @@ -786,7 +786,7 @@ class google_calendar(osv.AbstractModel): if actSrc == 'OE': self.delete_an_event(cr, uid, current_event[0], context=context) elif actSrc == 'GG': - new_google_event_id = event.GG.event['id'].split('_')[1] + new_google_event_id = event.GG.event['id'].rsplit('_', 1)[1] if 'T' in new_google_event_id: new_google_event_id = new_google_event_id.replace('T', '')[:-1] else: @@ -795,7 +795,8 @@ class google_calendar(osv.AbstractModel): if event.GG.status: parent_event = {} if not event_to_synchronize[base_event][0][1].OE.event_id: - event_to_synchronize[base_event][0][1].OE.event_id = att_obj.search_read(cr, uid, [('google_internal_event_id', '=', event.GG.event['id'].split('_')[0])], ['event_id'], context=context_novirtual)[0].get('event_id')[0] + main_ev = att_obj.search_read(cr, uid, [('google_internal_event_id', '=', event.GG.event['id'].rsplit('_', 1)[0])], fields=['event_id'], context=context_novirtual) + event_to_synchronize[base_event][0][1].OE.event_id = main_ev[0].get('event_id')[0] parent_event['id'] = "%s-%s" % (event_to_synchronize[base_event][0][1].OE.event_id, new_google_event_id) res = self.update_from_google(cr, uid, parent_event, event.GG.event, "copy", context) diff --git a/addons/hr/hr.py b/addons/hr/hr.py index 5bb172eb082..fbadc726e4b 100644 --- a/addons/hr/hr.py +++ b/addons/hr/hr.py @@ -225,7 +225,7 @@ class hr_employee(osv.osv): "resized as a 128x128px image, with aspect ratio preserved. "\ "Use this field in form views or some kanban views."), 'image_small': fields.function(_get_image, fnct_inv=_set_image, - string="Smal-sized photo", type="binary", multi="_get_image", + string="Small-sized photo", type="binary", multi="_get_image", store = { 'hr.employee': (lambda self, cr, uid, ids, c={}: ids, ['image'], 10), }, diff --git a/addons/hr/hr_view.xml b/addons/hr/hr_view.xml index 41f2bba0073..73a39806c66 100644 --- a/addons/hr/hr_view.xml +++ b/addons/hr/hr_view.xml @@ -40,7 +40,9 @@ - + @@ -68,7 +70,9 @@ - + diff --git a/addons/hr_holidays/hr_holidays.py b/addons/hr_holidays/hr_holidays.py index 3d467e0b065..ed25b75aa83 100644 --- a/addons/hr_holidays/hr_holidays.py +++ b/addons/hr_holidays/hr_holidays.py @@ -99,7 +99,7 @@ class hr_holidays_status(osv.osv): for record in self.browse(cr, uid, ids, context=context): name = record.name if not record.limit: - name = name + (' (%d/%d)' % (record.leaves_taken or 0.0, record.max_leaves or 0.0)) + name = name + (' (%g/%g)' % (record.leaves_taken or 0.0, record.max_leaves or 0.0)) res.append((record.id, name)) return res diff --git a/addons/l10n_be_invoice_bba/invoice.py b/addons/l10n_be_invoice_bba/invoice.py index 854593a87cc..499e6e6c4ac 100644 --- a/addons/l10n_be_invoice_bba/invoice.py +++ b/addons/l10n_be_invoice_bba/invoice.py @@ -141,7 +141,7 @@ class account_invoice(osv.osv): elif algorithm == 'random': if not self.check_bbacomm(reference): base = random.randint(1, 9999999999) - bbacomm = str(base).rjust(7, '0') + bbacomm = str(base).rjust(10, '0') base = int(bbacomm) mod = base % 97 or 97 mod = str(mod).rjust(2, '0') diff --git a/addons/mail/mail_thread.py b/addons/mail/mail_thread.py index 4ad9ca63049..8231e887b71 100644 --- a/addons/mail/mail_thread.py +++ b/addons/mail/mail_thread.py @@ -34,6 +34,7 @@ import pytz import socket import time import xmlrpclib +import re from email.message import Message from urllib import urlencode @@ -48,6 +49,8 @@ from openerp.tools.translate import _ _logger = logging.getLogger(__name__) +mail_header_msgid_re = re.compile('<[^<>]+>') + def decode_header(message, header, separator=' '): return separator.join(map(decode, filter(None, message.get_all(header, [])))) @@ -1311,13 +1314,13 @@ class mail_thread(osv.AbstractModel): msg_dict['date'] = stored_date.strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT) if message.get('In-Reply-To'): - parent_ids = self.pool.get('mail.message').search(cr, uid, [('message_id', '=', decode(message['In-Reply-To']))]) + parent_ids = self.pool.get('mail.message').search(cr, uid, [('message_id', '=', decode(message['In-Reply-To'].strip()))]) if parent_ids: msg_dict['parent_id'] = parent_ids[0] if message.get('References') and 'parent_id' not in msg_dict: - parent_ids = self.pool.get('mail.message').search(cr, uid, [('message_id', 'in', - [x.strip() for x in decode(message['References']).split()])]) + msg_list = mail_header_msgid_re.findall(decode(message['References'])) + parent_ids = self.pool.get('mail.message').search(cr, uid, [('message_id', 'in', [x.strip() for x in msg_list])]) if parent_ids: msg_dict['parent_id'] = parent_ids[0] diff --git a/addons/mrp/mrp.py b/addons/mrp/mrp.py index f5b6cf466a5..ca4109c1b9a 100644 --- a/addons/mrp/mrp.py +++ b/addons/mrp/mrp.py @@ -1071,8 +1071,8 @@ class mrp_production(osv.osv): return False # Take routing location as a Source Location. source_location_id = production.location_src_id.id - if production.bom_id.routing_id and production.bom_id.routing_id.location_id: - source_location_id = production.bom_id.routing_id.location_id.id + if production.routing_id and production.routing_id.location_id: + source_location_id = production.routing_id.location_id.id destination_location_id = production.product_id.property_stock_production.id if not source_location_id: diff --git a/addons/project/project_demo.xml b/addons/project/project_demo.xml index 54f78ba8c8b..695aa80af2a 100644 --- a/addons/project/project_demo.xml +++ b/addons/project/project_demo.xml @@ -50,8 +50,8 @@ project.task + ref('base.partner_root'), + ref('base.partner_demo')])]"/> diff --git a/addons/web/static/src/css/base.css b/addons/web/static/src/css/base.css index f664baf5319..901e0325cae 100644 --- a/addons/web/static/src/css/base.css +++ b/addons/web/static/src/css/base.css @@ -2350,7 +2350,7 @@ } .openerp .oe_fileupload .oe_add button.oe_attach .oe_e { position: relative; - top: -1px; + top: -10px; left: -9px; } .openerp .oe_fileupload .oe_add input.oe_form_binary_file { @@ -3301,6 +3301,9 @@ body.oe_single_form .oe_single_form_container { .openerp_ie ul.oe_form_status li.oe_active > .arrow span, .openerp_ie ul.oe_form_status_clickable li.oe_active > .arrow span { background-color: #729fcf !important; } +} +.openerp_ie .oe_webclient { + height: auto !important; @media print { .openerp { diff --git a/addons/web/static/src/css/base.sass b/addons/web/static/src/css/base.sass index 4d952cc47bb..136cdbdef24 100644 --- a/addons/web/static/src/css/base.sass +++ b/addons/web/static/src/css/base.sass @@ -1918,7 +1918,7 @@ $sheet-padding: 16px text-shadow: none .oe_e position: relative - top: -1px + top: -10px left: -9px input.oe_form_binary_file display: inline-block @@ -2670,6 +2670,8 @@ body.oe_single_form > .arrow span background-color: #729fcf !important + .oe_webclient + height: auto !important // }}} // @media print {{{ diff --git a/addons/web/static/src/js/formats.js b/addons/web/static/src/js/formats.js index 3c32dbe0426..f08dc31021a 100644 --- a/addons/web/static/src/js/formats.js +++ b/addons/web/static/src/js/formats.js @@ -233,7 +233,8 @@ instance.web.parse_value = function (value, descriptor, value_if_empty) { value = value.replace(instance.web._t.database.parameters.thousands_sep, ""); } while(tmp !== value); tmp = Number(value); - if (isNaN(tmp)) + // do not accept not numbers or float values + if (isNaN(tmp) || tmp % 1) throw new Error(_.str.sprintf(_t("'%s' is not a correct integer"), value)); return tmp; case 'float': @@ -268,6 +269,11 @@ instance.web.parse_value = function (value, descriptor, value_if_empty) { case 'datetime': var datetime = Date.parseExact( value, (date_pattern + ' ' + time_pattern)); + if (datetime !== null) + return instance.web.datetime_to_str(datetime); + datetime = Date.parseExact(value.replace(/\d+/g, function(m){ + return m.length === 1 ? "0" + m : m ; + }), (date_pattern + ' ' + time_pattern)); if (datetime !== null) return instance.web.datetime_to_str(datetime); datetime = Date.parse(value); @@ -276,6 +282,11 @@ instance.web.parse_value = function (value, descriptor, value_if_empty) { throw new Error(_.str.sprintf(_t("'%s' is not a correct datetime"), value)); case 'date': var date = Date.parseExact(value, date_pattern); + if (date !== null) + return instance.web.date_to_str(date); + date = Date.parseExact(value.replace(/\d+/g, function(m){ + return m.length === 1 ? "0" + m : m ; + }), date_pattern); if (date !== null) return instance.web.date_to_str(date); date = Date.parse(value); diff --git a/addons/web/static/src/js/search.js b/addons/web/static/src/js/search.js index 832faa4ca62..c59932265f7 100644 --- a/addons/web/static/src/js/search.js +++ b/addons/web/static/src/js/search.js @@ -345,11 +345,11 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea 'keydown .oe_searchview_input, .oe_searchview_facet': function (e) { switch(e.which) { case $.ui.keyCode.LEFT: - this.focusPreceding(this); + this.focusPreceding(e.target); e.preventDefault(); break; case $.ui.keyCode.RIGHT: - this.focusFollowing(this); + this.focusFollowing(e.target); e.preventDefault(); break; } diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js index b6ff72cfcde..c816d186fde 100644 --- a/addons/web/static/src/js/view_form.js +++ b/addons/web/static/src/js/view_form.js @@ -2632,6 +2632,7 @@ instance.web.DateTimeWidget = instance.web.Widget.extend({ type_of_date: "datetime", events: { 'change .oe_datepicker_master': 'change_datetime', + 'keypress .oe_datepicker_master': 'change_datetime', }, init: function(parent) { this._super(parent); @@ -2750,8 +2751,8 @@ instance.web.DateTimeWidget = instance.web.Widget.extend({ format_client: function(v) { return instance.web.format_value(v, {"widget": this.type_of_date}); }, - change_datetime: function() { - if (this.is_valid_()) { + change_datetime: function(e) { + if ((e.type !== "keypress" || e.which === 13) && this.is_valid_()) { this.set_value_from_ui_(); this.trigger("datetime_changed"); } diff --git a/addons/web/static/src/js/view_list_editable.js b/addons/web/static/src/js/view_list_editable.js index e2c63eb191a..ea2c28d9c3f 100644 --- a/addons/web/static/src/js/view_list_editable.js +++ b/addons/web/static/src/js/view_list_editable.js @@ -130,15 +130,7 @@ if (this.editable()) { this.$el.find('table:first').show(); this.$el.find('.oe_view_nocontent').remove(); - this.start_edition().then(function(){ - var fields = self.editor.form.fields; - self.editor.form.fields_order.some(function(field){ - if (fields[field].$el.is(':visible')){ - fields[field].$el.find("input").select(); - return true; - } - }); - }); + this.start_edition(); } else { this._super(); } @@ -243,6 +235,7 @@ return this.ensure_saved().then(function () { var $recordRow = self.groups.get_row_for(record); var cells = self.get_cells_for($recordRow); + var fields = {}; self.fields_for_resize.splice(0, self.fields_for_resize.length); return self.with_event('edit', { record: record.attributes, @@ -256,10 +249,16 @@ // FIXME: need better way to get the field back from bubbling (delegated) DOM events somehow field.$el.attr('data-fieldname', field_name); + fields[field_name] = field; self.fields_for_resize.push({field: field, cell: cell}); }, options).then(function () { $recordRow.addClass('oe_edition'); self.resize_fields(); + var focus_field = options && options.focus_field ? options.focus_field : undefined; + if (!focus_field){ + focus_field = _.find(self.editor.form.fields_order, function(field){ return fields[field] && fields[field].$el.is(':visible:has(input)'); }); + } + if (focus_field) fields[focus_field].$el.find('input').select(); return record.attributes; }); }).fail(function () { @@ -749,31 +748,6 @@ throw new Error("is_editing's state filter must be either `new` or" + " `edit` if provided"); }, - _focus_setup: function (focus_field) { - var form = this.form; - - var field; - // If a field to focus was specified - if (focus_field - // Is actually in the form - && (field = form.fields[focus_field]) - // And is visible - && field.$el.is(':visible')) { - // focus it - field.focus(); - return; - } - - _(form.fields_order).detect(function (name) { - // look for first visible field in fields_order, focus it - var field = form.fields[name]; - if (!field.$el.is(':visible')) { - return false; - } - // Stop as soon as a field got focused - return field.focus() !== false; - }); - }, edit: function (record, configureField, options) { // TODO: specify sequence of edit calls var self = this; @@ -788,7 +762,6 @@ _(form.fields).each(function (field, name) { configureField(name, field); }); - self._focus_setup(options && options.focus_field); return form; }); }, diff --git a/addons/web_calendar/static/src/js/web_calendar.js b/addons/web_calendar/static/src/js/web_calendar.js index 23b11254b53..a7cff99f436 100644 --- a/addons/web_calendar/static/src/js/web_calendar.js +++ b/addons/web_calendar/static/src/js/web_calendar.js @@ -218,7 +218,12 @@ openerp.web_calendar = function(instance) { this.info_fields.push(fv.arch.children[fld].attrs.name); } - return (new instance.web.Model(this.dataset.model)) + var edit_check = new instance.web.Model(this.dataset.model) + .call("check_access_rights", ["write", false]) + .then(function (write_right) { + self.write_right = write_right; + }); + var init = new instance.web.Model(this.dataset.model) .call("check_access_rights", ["create", false]) .then(function (create_right) { self.create_right = create_right; @@ -228,6 +233,7 @@ openerp.web_calendar = function(instance) { self.ready.resolve(); }); }); + return $.when(edit_check, init); }, get_fc_init_options: function () { @@ -841,7 +847,11 @@ openerp.web_calendar = function(instance) { if (! this.open_popup_action) { var index = this.dataset.get_id_index(id); this.dataset.index = index; - this.do_switch_view('form', null, { mode: "edit" }); + if (this.write_right) { + this.do_switch_view('form', null, { mode: "edit" }); + } else { + this.do_switch_view('form', null, { mode: "view" }); + } } else { var pop = new instance.web.form.FormOpenPopup(this); diff --git a/addons/website/static/src/js/website.editor.js b/addons/website/static/src/js/website.editor.js index 0a4ed5e7d15..c3c016b3ef2 100644 --- a/addons/website/static/src/js/website.editor.js +++ b/addons/website/static/src/js/website.editor.js @@ -468,7 +468,7 @@ } ); }); - menu.on('click', 'a[data-action!=ace]', function (event) { + menu.on('click', 'a[data-view-id]', function (event) { var view_id = $(event.currentTarget).data('view-id'); return openerp.jsonRpc('/web/dataset/call_kw', 'call', { model: 'ir.ui.view', diff --git a/addons/website_forum/controllers/main.py b/addons/website_forum/controllers/main.py index 2c6e234d653..f20a0b1d5ef 100644 --- a/addons/website_forum/controllers/main.py +++ b/addons/website_forum/controllers/main.py @@ -120,7 +120,7 @@ class WebsiteForum(http.Controller): question_count = Post.search(cr, uid, domain, count=True, context=context) if tag: - url = "/forum/%s/%s/questions" % (slug(forum), slug(tag)) + url = "/forum/%s/tag/%s/questions" % (slug(forum), slug(tag)) else: url = "/forum/%s" % slug(forum) diff --git a/addons/website_forum/static/src/js/website_forum.js b/addons/website_forum/static/src/js/website_forum.js index 9d4e9e587c9..5db211ab9aa 100644 --- a/addons/website_forum/static/src/js/website_forum.js +++ b/addons/website_forum/static/src/js/website_forum.js @@ -148,7 +148,7 @@ function IsKarmaValid(eventNumber,minKarma){ CKEDITOR.tools.callFunction(eventNumber,this); return false; } else { - alert("Sorry you need more than 30 Karma."); + alert("Sorry you need more than " + minKarma + " Karma."); } } diff --git a/addons/website_mail/controllers/main.py b/addons/website_mail/controllers/main.py index c0592000208..18bddd09a58 100644 --- a/addons/website_mail/controllers/main.py +++ b/addons/website_mail/controllers/main.py @@ -92,9 +92,8 @@ class WebsiteMail(http.Controller): values['is_follower'] = len( request.registry['mail.followers'].search( cr, SUPERUSER_ID, [ - ('res_model', '=', 'mail.group'), + ('res_model', '=', model), ('res_id', '=', obj_ids[0]), ('partner_id', '=', partner_id.id) ], context=context)) == 1 - return values diff --git a/addons/website_mail/static/src/js/follow.js b/addons/website_mail/static/src/js/follow.js new file mode 100644 index 00000000000..18c36378328 --- /dev/null +++ b/addons/website_mail/static/src/js/follow.js @@ -0,0 +1,68 @@ +(function () { + 'use strict'; + + var website = openerp.website; + + website.snippet.animationRegistry.follow = website.snippet.Animation.extend({ + selector: ".js_follow", + start: function (editable_mode) { + var self = this; + this.is_user = false; + + openerp.jsonRpc('/website_mail/is_follower', 'call', { + model: this.$target.data('object'), + id: this.$target.data('id'), + }).always(function (data) { + self.is_user = data.is_user; + self.email = data.email; + self.toggle_subscription(data.is_follower, data.email); + self.$target.removeClass("hidden"); + }); + + // not if editable mode to allow designer to edit alert field + if (!editable_mode) { + $('.js_follow > .alert').addClass("hidden"); + $('.js_follow > .input-group-btn.hidden').removeClass("hidden"); + this.$target.find('.js_follow_btn, .js_unfollow_btn').on('click', function (event) { + event.preventDefault(); + self.on_click(); + }); + } + return; + }, + on_click: function () { + var self = this; + var $email = this.$target.find(".js_follow_email"); + + if ($email.length && !$email.val().match(/.+@.+/)) { + this.$target.addClass('has-error'); + return false; + } + this.$target.removeClass('has-error'); + + openerp.jsonRpc('/website_mail/follow', 'call', { + 'id': +this.$target.data('id'), + 'object': this.$target.data('object'), + 'message_is_follower': this.$target.attr("data-follow") || "off", + 'email': $email.length ? $email.val() : false, + }).then(function (follow) { + self.toggle_subscription(follow, self.email); + }); + }, + toggle_subscription: function(follow, email) { + console.log(follow, email); + if (follow) { + this.$target.find(".js_follow_btn").addClass("hidden"); + this.$target.find(".js_unfollow_btn").removeClass("hidden"); + } + else { + this.$target.find(".js_follow_btn").removeClass("hidden"); + this.$target.find(".js_unfollow_btn").addClass("hidden"); + } + this.$target.find('input.js_follow_email') + .val(email ? email : "") + .attr("disabled", follow || (email.length && this.is_user) ? "disabled" : false); + this.$target.attr("data-follow", follow ? 'on' : 'off'); + }, + }); +})(); diff --git a/addons/website_mail/views/website_mail.xml b/addons/website_mail/views/website_mail.xml index 2b2bbea7e5f..0ccae00e21d 100644 --- a/addons/website_mail/views/website_mail.xml +++ b/addons/website_mail/views/website_mail.xml @@ -21,6 +21,7 @@ diff --git a/addons/website_mail_group/static/src/js/website_mail_group.snippet.js b/addons/website_mail_group/static/src/js/website_mail_group.snippet.js index fd6a2d38cfa..8ad4f9ba796 100644 --- a/addons/website_mail_group/static/src/js/website_mail_group.snippet.js +++ b/addons/website_mail_group/static/src/js/website_mail_group.snippet.js @@ -3,8 +3,8 @@ var website = openerp.website; - website.snippet.animationRegistry.follow = website.snippet.Animation.extend({ - selector: ".js_follow", + website.snippet.animationRegistry.follow_alias = website.snippet.Animation.extend({ + selector: ".js_follow_alias", start: function (editable_mode) { var self = this; this.is_user = false; @@ -23,8 +23,8 @@ // not if editable mode to allow designer to edit alert field if (!editable_mode) { - $('.js_follow > .alert').addClass("hidden"); - $('.js_follow > .input-group-btn.hidden').removeClass("hidden"); + $('.js_follow_alias > .alert').addClass("hidden"); + $('.js_follow_alias > .input-group-btn.hidden').removeClass("hidden"); this.$target.find('.js_follow_btn, .js_unfollow_btn').on('click', function (event) { event.preventDefault(); self.on_click(); diff --git a/addons/website_mail_group/views/snippets.xml b/addons/website_mail_group/views/snippets.xml index 452f59f4902..95935cd4998 100644 --- a/addons/website_mail_group/views/snippets.xml +++ b/addons/website_mail_group/views/snippets.xml @@ -11,7 +11,7 @@ Discussion Group -
@@ -38,7 +38,7 @@
  • diff --git a/addons/website_sale/controllers/main.py b/addons/website_sale/controllers/main.py index 78b411b20e6..a6bcd15f9e5 100644 --- a/addons/website_sale/controllers/main.py +++ b/addons/website_sale/controllers/main.py @@ -55,7 +55,7 @@ class table_compute(object): self.table[(pos/PPR)+y2][(pos%PPR)+x2] = False self.table[pos/PPR][pos%PPR] = { 'product': p, 'x':x, 'y': y, - 'class': " ".join(map(lambda x: x.html_class, p.website_style_ids)) + 'class': " ".join(map(lambda x: x.html_class or '', p.website_style_ids)) } if index<=PPG: maxy=max(maxy,y+(pos/PPR)) diff --git a/openerp/addons/base/ir/ir_actions.py b/openerp/addons/base/ir/ir_actions.py index e9de9dc7028..5a505ee52ce 100644 --- a/openerp/addons/base/ir/ir_actions.py +++ b/openerp/addons/base/ir/ir_actions.py @@ -482,6 +482,8 @@ class ir_actions_server(osv.osv): "based on the sequence. Low number means high priority."), 'model_id': fields.many2one('ir.model', 'Base Model', required=True, ondelete='cascade', help="Base model on which the server action runs."), + 'model_name': fields.related('model_id', 'model', type='char', + string='Model Name', readonly=True), 'menu_ir_values_id': fields.many2one('ir.values', 'More Menu entry', readonly=True, help='More menu entry.'), # Client Action @@ -641,6 +643,10 @@ class ir_actions_server(osv.osv): 'wkf_field_id': False, 'crud_model_id': model_id, } + + if model_id: + values['model_name'] = self.pool.get('ir.model').browse(cr, uid, model_id, context).model + return {'value': values} def on_change_wkf_wonfig(self, cr, uid, ids, use_relational_model, wkf_field_id, wkf_model_id, model_id, context=None): @@ -744,6 +750,7 @@ class ir_actions_server(osv.osv): crud_model_name = False if crud_model_id: crud_model_name = self.pool.get('ir.model').browse(cr, uid, crud_model_id, context).model + values = {'link_field_id': False, 'crud_model_name': crud_model_name} return {'value': values} diff --git a/openerp/addons/base/ir/ir_actions.xml b/openerp/addons/base/ir/ir_actions.xml index 4eb869daa80..42408132794 100644 --- a/openerp/addons/base/ir/ir_actions.xml +++ b/openerp/addons/base/ir/ir_actions.xml @@ -349,8 +349,9 @@ Check to attach the newly created record to the record on which the server action runs.

    + diff --git a/openerp/osv/fields.py b/openerp/osv/fields.py index e24d74e0625..db59d37d350 100644 --- a/openerp/osv/fields.py +++ b/openerp/osv/fields.py @@ -561,7 +561,7 @@ class many2one(_column): # we use uid=1 because the visibility of a many2one field value (just id and name) # must be the access right of the parent form and not the linked object itself. records = dict(obj.name_get(cr, SUPERUSER_ID, - list(set([x for x in res.values() if isinstance(x, (int,long))])), + list(set([x for x in res.values() if x and isinstance(x, (int,long))])), context=context)) for id in res: if res[id] in records: diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 73ad3796418..6bf53246846 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -3347,6 +3347,8 @@ class BaseModel(object): return [] if fields_to_read is None: fields_to_read = self._columns.keys() + else: + fields_to_read = list(set(fields_to_read)) # all inherited fields + all non inherited fields for which the attribute whose name is in load is True fields_pre = [f for f in fields_to_read if