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