[MERGE] forward port of branch saas-4 up to 5087612

This commit is contained in:
Christophe Simonis 2014-06-19 16:13:35 +02:00
commit adf07a9490
38 changed files with 213 additions and 113 deletions

View File

@ -295,7 +295,8 @@ class account_invoice(osv.osv):
}, },
multi='all'), multi='all'),
'currency_id': fields.many2one('res.currency', 'Currency', required=True, readonly=True, states={'draft':[('readonly',False)]}, track_visibility='always'), '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)]}), '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)]}), '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', 'reconciled': fields.function(_reconciled, string='Paid/Reconciled', type='boolean',

View File

@ -99,7 +99,7 @@
</tr> </tr>
<tr t-foreach="get_lines_with_out_partner(data['form'])" t-as="not_partner"> <tr t-foreach="get_lines_with_out_partner(data['form'])" t-as="not_partner">
<td> <td>
<span t-esc="partner['name']"/> <span t-esc="not_partner['name']"/>
</td> </td>
<td class="text-right"> <td class="text-right">
<span t-esc="formatLang(not_partner['direction'], currency_obj=res_company.currency_id)"/> <span t-esc="formatLang(not_partner['direction'], currency_obj=res_company.currency_id)"/>

View File

@ -22,7 +22,6 @@ from dateutil.relativedelta import relativedelta
import datetime import datetime
import logging import logging
import time import time
import traceback
from openerp.osv import osv, fields from openerp.osv import osv, fields
from openerp.osv.orm import intersect, except_orm from openerp.osv.orm import intersect, except_orm
@ -73,6 +72,7 @@ class account_analytic_invoice_line(osv.osv):
result = {} result = {}
res = self.pool.get('product.product').browse(cr, uid, product, context=local_context) res = self.pool.get('product.product').browse(cr, uid, product, context=local_context)
price = False
if price_unit is not False: if price_unit is not False:
price = price_unit price = price_unit
elif pricelist_id: elif pricelist_id:
@ -746,29 +746,32 @@ class account_analytic_account(osv.osv):
contract_ids = ids contract_ids = ids
else: else:
contract_ids = self.search(cr, uid, [('recurring_next_date','<=', current_date), ('state','=', 'open'), ('recurring_invoices','=', True), ('type', '=', 'contract')]) 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): if contract_ids:
try: 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),))
invoice_values = self._prepare_invoice(cr, uid, contract, context=context) for company_id, ids in cr.fetchall():
invoice_ids.append(self.pool['account.invoice'].create(cr, uid, invoice_values, context=context)) for contract in self.browse(cr, uid, ids, context=dict(context, company_id=company_id, force_company=company_id)):
next_date = datetime.datetime.strptime(contract.recurring_next_date or current_date, "%Y-%m-%d") try:
interval = contract.recurring_interval invoice_values = self._prepare_invoice(cr, uid, contract, context=context)
if contract.recurring_rule_type == 'daily': invoice_ids.append(self.pool['account.invoice'].create(cr, uid, invoice_values, context=context))
new_date = next_date+relativedelta(days=+interval) next_date = datetime.datetime.strptime(contract.recurring_next_date or current_date, "%Y-%m-%d")
elif contract.recurring_rule_type == 'weekly': interval = contract.recurring_interval
new_date = next_date+relativedelta(weeks=+interval) if contract.recurring_rule_type == 'daily':
elif contract.recurring_rule_type == 'monthly': new_date = next_date+relativedelta(days=+interval)
new_date = next_date+relativedelta(months=+interval) elif contract.recurring_rule_type == 'weekly':
else: new_date = next_date+relativedelta(weeks=+interval)
new_date = next_date+relativedelta(years=+interval) elif contract.recurring_rule_type == 'monthly':
self.write(cr, uid, [contract.id], {'recurring_next_date': new_date.strftime('%Y-%m-%d')}, context=context) new_date = next_date+relativedelta(months=+interval)
if automatic: else:
cr.commit() new_date = next_date+relativedelta(years=+interval)
except Exception: self.write(cr, uid, [contract.id], {'recurring_next_date': new_date.strftime('%Y-%m-%d')}, context=context)
if automatic: if automatic:
cr.rollback() cr.commit()
_logger.error(traceback.format_exc()) except Exception:
else: if automatic:
raise cr.rollback()
_logger.exception('Fail to create recurring invoice for contract %s', contract.code)
else:
raise
return invoice_ids return invoice_ids
class account_analytic_account_summary_user(osv.osv): class account_analytic_account_summary_user(osv.osv):

View File

@ -248,7 +248,7 @@
<page string="Bill Information"> <page string="Bill Information">
<field name="line_dr_ids" on_change="onchange_price(line_dr_ids, tax_id, partner_id)" context="{'journal_id':journal_id,'partner_id':partner_id}"> <field name="line_dr_ids" on_change="onchange_price(line_dr_ids, tax_id, partner_id)" context="{'journal_id':journal_id,'partner_id':partner_id}">
<tree string="Expense Lines" editable="bottom"> <tree string="Expense Lines" editable="bottom">
<field name="account_id" widget="selection" domain="[('user_type.report_type','=','expense'), ('type','!=','view')]"/> <field name="account_id" domain="[('user_type.report_type','=','expense'), ('type','!=','view')]"/>
<field name="name"/> <field name="name"/>
<field name="amount"/> <field name="amount"/>
<field name="account_analytic_id" groups="analytic.group_analytic_accounting"/> <field name="account_analytic_id" groups="analytic.group_analytic_accounting"/>

View File

@ -117,10 +117,22 @@ def sh256crypt(cls, password, salt, magic=magic_sha256):
class res_users(osv.osv): class res_users(osv.osv):
_inherit = "res.users" _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): def set_pw(self, cr, uid, id, name, value, args, context):
if value: if value:
encrypted = md5crypt(value, gen_salt()) self._set_encrypted_password(cr, id, value)
cr.execute("update res_users set password='', password_crypt=%s where id=%s", (encrypted, id))
del value del value
def get_pw( self, cr, uid, ids, name, args, context ): def get_pw( self, cr, uid, ids, name, args, context ):
@ -144,9 +156,7 @@ class res_users(osv.osv):
if cr.rowcount: if cr.rowcount:
stored_password, stored_password_crypt = cr.fetchone() stored_password, stored_password_crypt = cr.fetchone()
if stored_password and not stored_password_crypt: if stored_password and not stored_password_crypt:
salt = gen_salt() self._set_encrypted_password(cr, uid, stored_password)
stored_password_crypt = md5crypt(stored_password, salt)
cr.execute("UPDATE res_users SET password='', password_crypt=%s WHERE id=%s", (stored_password_crypt, uid))
try: try:
return super(res_users, self).check_credentials(cr, uid, password) return super(res_users, self).check_credentials(cr, uid, password)
except openerp.exceptions.AccessDenied: except openerp.exceptions.AccessDenied:

View File

@ -74,7 +74,7 @@ class OAuthLogin(Home):
state = dict( state = dict(
d=request.session.db, d=request.session.db,
p=provider['id'], p=provider['id'],
r=redirect, r=werkzeug.url_quote_plus(redirect),
) )
token = request.params.get('token') token = request.params.get('token')
if token: if token:
@ -143,7 +143,7 @@ class OAuthController(http.Controller):
cr.commit() cr.commit()
action = state.get('a') action = state.get('a')
menu = state.get('m') menu = state.get('m')
redirect = state.get('r') redirect = werkzeug.url_unquote_plus(state['r']) if state.get('r') else False
url = '/web' url = '/web'
if redirect: if redirect:
url = redirect url = redirect

View File

@ -102,7 +102,7 @@ openerp.calendar = function(instance) {
var self = this; var self = this;
var action_url = ''; 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(){ var reload_page = function(){
return location.replace(action_url); return location.replace(action_url);

View File

@ -32,7 +32,7 @@ class report_event_registration(osv.osv):
'draft_state': fields.integer(' # No of Draft Registrations', size=20), 'draft_state': fields.integer(' # No of Draft Registrations', size=20),
'confirm_state': fields.integer(' # No of Confirmed Registrations', size=20), 'confirm_state': fields.integer(' # No of Confirmed Registrations', size=20),
'seats_max': fields.integer('Max Seats'), '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'), '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), '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), '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, r.name AS name_registration,
e.company_id AS company_id, e.company_id AS company_id,
e.date_begin AS event_date, 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 ('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, CASE WHEN r.state IN ('open','done') THEN r.nb_register ELSE 0 END AS confirm_state,
e.type AS event_type, e.type AS event_type,

View File

@ -1,7 +1,7 @@
.oe_event_date{ .oe_event_date{
border-top-left-radius:3px; border-top-left-radius:3px;
border-top-right-radius:3px; border-top-right-radius:3px;
font-size: 48px; font-size: 36px;
height: auto; height: auto;
font-weight: bold; font-weight: bold;
text-align: center; text-align: center;

View File

@ -58,7 +58,7 @@ def start_end_date_for_period(period, default_start_date=False, default_end_date
end_date = default_end_date end_date = default_end_date
if start_date and 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: else:
return (start_date, end_date) return (start_date, end_date)

View File

@ -699,7 +699,7 @@ class google_calendar(osv.AbstractModel):
for att in att_obj.browse(cr, uid, my_att_ids, context=context): for att in att_obj.browse(cr, uid, my_att_ids, context=context):
event = att.event_id 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: if base_event_id not in event_to_synchronize:
event_to_synchronize[base_event_id] = {} event_to_synchronize[base_event_id] = {}
@ -721,7 +721,7 @@ class google_calendar(osv.AbstractModel):
for event in all_event_from_google.values(): for event in all_event_from_google.values():
event_id = event.get('id') 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: if base_event_id not in event_to_synchronize:
event_to_synchronize[base_event_id] = {} event_to_synchronize[base_event_id] = {}
@ -786,7 +786,7 @@ class google_calendar(osv.AbstractModel):
if actSrc == 'OE': if actSrc == 'OE':
self.delete_an_event(cr, uid, current_event[0], context=context) self.delete_an_event(cr, uid, current_event[0], context=context)
elif actSrc == 'GG': 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: if 'T' in new_google_event_id:
new_google_event_id = new_google_event_id.replace('T', '')[:-1] new_google_event_id = new_google_event_id.replace('T', '')[:-1]
else: else:
@ -795,7 +795,8 @@ class google_calendar(osv.AbstractModel):
if event.GG.status: if event.GG.status:
parent_event = {} parent_event = {}
if not event_to_synchronize[base_event][0][1].OE.event_id: 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) 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) res = self.update_from_google(cr, uid, parent_event, event.GG.event, "copy", context)

View File

@ -225,7 +225,7 @@ class hr_employee(osv.osv):
"resized as a 128x128px image, with aspect ratio preserved. "\ "resized as a 128x128px image, with aspect ratio preserved. "\
"Use this field in form views or some kanban views."), "Use this field in form views or some kanban views."),
'image_small': fields.function(_get_image, fnct_inv=_set_image, '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 = { store = {
'hr.employee': (lambda self, cr, uid, ids, c={}: ids, ['image'], 10), 'hr.employee': (lambda self, cr, uid, ids, c={}: ids, ['image'], 10),
}, },

View File

@ -40,7 +40,9 @@
<page string="Public Information"> <page string="Public Information">
<group> <group>
<group string="Contact Information"> <group string="Contact Information">
<field name="address_id" on_change="onchange_address_id(address_id)" context="{'show_address': 1}" options='{"always_reload": True, "highlight_first_line": True}'/> <field name="address_id" on_change="onchange_address_id(address_id)"
context="{'show_address': 1, 'default_customer': False}"
options='{"always_reload": True, "highlight_first_line": True}'/>
<field name="mobile_phone"/> <field name="mobile_phone"/>
<field name="work_location"/> <field name="work_location"/>
</group> </group>
@ -68,7 +70,9 @@
<field name="otherid" groups="base.group_hr_user"/> <field name="otherid" groups="base.group_hr_user"/>
</group> </group>
<group string="Contact Information"> <group string="Contact Information">
<field name="address_home_id" context="{'show_address': 1}" options='{"always_reload": True, "highlight_first_line": True}'/> <field name="address_home_id"
context="{'show_address': 1, 'default_customer': False}"
options='{"always_reload": True, "highlight_first_line": True}'/>
</group> </group>
<group string="Status"> <group string="Status">
<field name="gender"/> <field name="gender"/>

View File

@ -99,7 +99,7 @@ class hr_holidays_status(osv.osv):
for record in self.browse(cr, uid, ids, context=context): for record in self.browse(cr, uid, ids, context=context):
name = record.name name = record.name
if not record.limit: 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)) res.append((record.id, name))
return res return res

View File

@ -141,7 +141,7 @@ class account_invoice(osv.osv):
elif algorithm == 'random': elif algorithm == 'random':
if not self.check_bbacomm(reference): if not self.check_bbacomm(reference):
base = random.randint(1, 9999999999) base = random.randint(1, 9999999999)
bbacomm = str(base).rjust(7, '0') bbacomm = str(base).rjust(10, '0')
base = int(bbacomm) base = int(bbacomm)
mod = base % 97 or 97 mod = base % 97 or 97
mod = str(mod).rjust(2, '0') mod = str(mod).rjust(2, '0')

View File

@ -34,6 +34,7 @@ import pytz
import socket import socket
import time import time
import xmlrpclib import xmlrpclib
import re
from email.message import Message from email.message import Message
from urllib import urlencode from urllib import urlencode
@ -48,6 +49,8 @@ from openerp.tools.translate import _
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
mail_header_msgid_re = re.compile('<[^<>]+>')
def decode_header(message, header, separator=' '): def decode_header(message, header, separator=' '):
return separator.join(map(decode, filter(None, message.get_all(header, [])))) 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) msg_dict['date'] = stored_date.strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT)
if message.get('In-Reply-To'): 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: if parent_ids:
msg_dict['parent_id'] = parent_ids[0] msg_dict['parent_id'] = parent_ids[0]
if message.get('References') and 'parent_id' not in msg_dict: if message.get('References') and 'parent_id' not in msg_dict:
parent_ids = self.pool.get('mail.message').search(cr, uid, [('message_id', 'in', msg_list = mail_header_msgid_re.findall(decode(message['References']))
[x.strip() for x in decode(message['References']).split()])]) parent_ids = self.pool.get('mail.message').search(cr, uid, [('message_id', 'in', [x.strip() for x in msg_list])])
if parent_ids: if parent_ids:
msg_dict['parent_id'] = parent_ids[0] msg_dict['parent_id'] = parent_ids[0]

View File

@ -1071,8 +1071,8 @@ class mrp_production(osv.osv):
return False return False
# Take routing location as a Source Location. # Take routing location as a Source Location.
source_location_id = production.location_src_id.id source_location_id = production.location_src_id.id
if production.bom_id.routing_id and production.bom_id.routing_id.location_id: if production.routing_id and production.routing_id.location_id:
source_location_id = production.bom_id.routing_id.location_id.id source_location_id = production.routing_id.location_id.id
destination_location_id = production.product_id.property_stock_production.id destination_location_id = production.product_id.property_stock_production.id
if not source_location_id: if not source_location_id:

View File

@ -50,8 +50,8 @@
<field name="user_id" ref="base.user_demo"/> <field name="user_id" ref="base.user_demo"/>
<field name="alias_model">project.task</field> <field name="alias_model">project.task</field>
<field name="message_follower_ids" eval="[(6, 0, [ <field name="message_follower_ids" eval="[(6, 0, [
ref('base.user_root'), ref('base.partner_root'),
ref('base.user_demo')])]"/> ref('base.partner_demo')])]"/>
</record> </record>
<!-- We assign after so that default values applies --> <!-- We assign after so that default values applies -->

View File

@ -2350,7 +2350,7 @@
} }
.openerp .oe_fileupload .oe_add button.oe_attach .oe_e { .openerp .oe_fileupload .oe_add button.oe_attach .oe_e {
position: relative; position: relative;
top: -1px; top: -10px;
left: -9px; left: -9px;
} }
.openerp .oe_fileupload .oe_add input.oe_form_binary_file { .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 { .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; background-color: #729fcf !important;
} }
}
.openerp_ie .oe_webclient {
height: auto !important;
@media print { @media print {
.openerp { .openerp {

View File

@ -1918,7 +1918,7 @@ $sheet-padding: 16px
text-shadow: none text-shadow: none
.oe_e .oe_e
position: relative position: relative
top: -1px top: -10px
left: -9px left: -9px
input.oe_form_binary_file input.oe_form_binary_file
display: inline-block display: inline-block
@ -2670,6 +2670,8 @@ body.oe_single_form
> .arrow span > .arrow span
background-color: #729fcf !important background-color: #729fcf !important
.oe_webclient
height: auto !important
// }}} // }}}
// @media print {{{ // @media print {{{

View File

@ -233,7 +233,8 @@ instance.web.parse_value = function (value, descriptor, value_if_empty) {
value = value.replace(instance.web._t.database.parameters.thousands_sep, ""); value = value.replace(instance.web._t.database.parameters.thousands_sep, "");
} while(tmp !== value); } while(tmp !== value);
tmp = Number(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)); throw new Error(_.str.sprintf(_t("'%s' is not a correct integer"), value));
return tmp; return tmp;
case 'float': case 'float':
@ -268,6 +269,11 @@ instance.web.parse_value = function (value, descriptor, value_if_empty) {
case 'datetime': case 'datetime':
var datetime = Date.parseExact( var datetime = Date.parseExact(
value, (date_pattern + ' ' + time_pattern)); 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) if (datetime !== null)
return instance.web.datetime_to_str(datetime); return instance.web.datetime_to_str(datetime);
datetime = Date.parse(value); 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)); throw new Error(_.str.sprintf(_t("'%s' is not a correct datetime"), value));
case 'date': case 'date':
var date = Date.parseExact(value, date_pattern); 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) if (date !== null)
return instance.web.date_to_str(date); return instance.web.date_to_str(date);
date = Date.parse(value); date = Date.parse(value);

View File

@ -345,11 +345,11 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
'keydown .oe_searchview_input, .oe_searchview_facet': function (e) { 'keydown .oe_searchview_input, .oe_searchview_facet': function (e) {
switch(e.which) { switch(e.which) {
case $.ui.keyCode.LEFT: case $.ui.keyCode.LEFT:
this.focusPreceding(this); this.focusPreceding(e.target);
e.preventDefault(); e.preventDefault();
break; break;
case $.ui.keyCode.RIGHT: case $.ui.keyCode.RIGHT:
this.focusFollowing(this); this.focusFollowing(e.target);
e.preventDefault(); e.preventDefault();
break; break;
} }

View File

@ -2632,6 +2632,7 @@ instance.web.DateTimeWidget = instance.web.Widget.extend({
type_of_date: "datetime", type_of_date: "datetime",
events: { events: {
'change .oe_datepicker_master': 'change_datetime', 'change .oe_datepicker_master': 'change_datetime',
'keypress .oe_datepicker_master': 'change_datetime',
}, },
init: function(parent) { init: function(parent) {
this._super(parent); this._super(parent);
@ -2750,8 +2751,8 @@ instance.web.DateTimeWidget = instance.web.Widget.extend({
format_client: function(v) { format_client: function(v) {
return instance.web.format_value(v, {"widget": this.type_of_date}); return instance.web.format_value(v, {"widget": this.type_of_date});
}, },
change_datetime: function() { change_datetime: function(e) {
if (this.is_valid_()) { if ((e.type !== "keypress" || e.which === 13) && this.is_valid_()) {
this.set_value_from_ui_(); this.set_value_from_ui_();
this.trigger("datetime_changed"); this.trigger("datetime_changed");
} }

View File

@ -130,15 +130,7 @@
if (this.editable()) { if (this.editable()) {
this.$el.find('table:first').show(); this.$el.find('table:first').show();
this.$el.find('.oe_view_nocontent').remove(); this.$el.find('.oe_view_nocontent').remove();
this.start_edition().then(function(){ this.start_edition();
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;
}
});
});
} else { } else {
this._super(); this._super();
} }
@ -243,6 +235,7 @@
return this.ensure_saved().then(function () { return this.ensure_saved().then(function () {
var $recordRow = self.groups.get_row_for(record); var $recordRow = self.groups.get_row_for(record);
var cells = self.get_cells_for($recordRow); var cells = self.get_cells_for($recordRow);
var fields = {};
self.fields_for_resize.splice(0, self.fields_for_resize.length); self.fields_for_resize.splice(0, self.fields_for_resize.length);
return self.with_event('edit', { return self.with_event('edit', {
record: record.attributes, record: record.attributes,
@ -256,10 +249,16 @@
// FIXME: need better way to get the field back from bubbling (delegated) DOM events somehow // FIXME: need better way to get the field back from bubbling (delegated) DOM events somehow
field.$el.attr('data-fieldname', field_name); field.$el.attr('data-fieldname', field_name);
fields[field_name] = field;
self.fields_for_resize.push({field: field, cell: cell}); self.fields_for_resize.push({field: field, cell: cell});
}, options).then(function () { }, options).then(function () {
$recordRow.addClass('oe_edition'); $recordRow.addClass('oe_edition');
self.resize_fields(); 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; return record.attributes;
}); });
}).fail(function () { }).fail(function () {
@ -749,31 +748,6 @@
throw new Error("is_editing's state filter must be either `new` or" + throw new Error("is_editing's state filter must be either `new` or" +
" `edit` if provided"); " `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) { edit: function (record, configureField, options) {
// TODO: specify sequence of edit calls // TODO: specify sequence of edit calls
var self = this; var self = this;
@ -788,7 +762,6 @@
_(form.fields).each(function (field, name) { _(form.fields).each(function (field, name) {
configureField(name, field); configureField(name, field);
}); });
self._focus_setup(options && options.focus_field);
return form; return form;
}); });
}, },

View File

@ -218,7 +218,12 @@ openerp.web_calendar = function(instance) {
this.info_fields.push(fv.arch.children[fld].attrs.name); 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]) .call("check_access_rights", ["create", false])
.then(function (create_right) { .then(function (create_right) {
self.create_right = create_right; self.create_right = create_right;
@ -228,6 +233,7 @@ openerp.web_calendar = function(instance) {
self.ready.resolve(); self.ready.resolve();
}); });
}); });
return $.when(edit_check, init);
}, },
get_fc_init_options: function () { get_fc_init_options: function () {
@ -841,7 +847,11 @@ openerp.web_calendar = function(instance) {
if (! this.open_popup_action) { if (! this.open_popup_action) {
var index = this.dataset.get_id_index(id); var index = this.dataset.get_id_index(id);
this.dataset.index = index; 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 { else {
var pop = new instance.web.form.FormOpenPopup(this); var pop = new instance.web.form.FormOpenPopup(this);

View File

@ -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'); var view_id = $(event.currentTarget).data('view-id');
return openerp.jsonRpc('/web/dataset/call_kw', 'call', { return openerp.jsonRpc('/web/dataset/call_kw', 'call', {
model: 'ir.ui.view', model: 'ir.ui.view',

View File

@ -120,7 +120,7 @@ class WebsiteForum(http.Controller):
question_count = Post.search(cr, uid, domain, count=True, context=context) question_count = Post.search(cr, uid, domain, count=True, context=context)
if tag: if tag:
url = "/forum/%s/%s/questions" % (slug(forum), slug(tag)) url = "/forum/%s/tag/%s/questions" % (slug(forum), slug(tag))
else: else:
url = "/forum/%s" % slug(forum) url = "/forum/%s" % slug(forum)

View File

@ -148,7 +148,7 @@ function IsKarmaValid(eventNumber,minKarma){
CKEDITOR.tools.callFunction(eventNumber,this); CKEDITOR.tools.callFunction(eventNumber,this);
return false; return false;
} else { } else {
alert("Sorry you need more than 30 Karma."); alert("Sorry you need more than " + minKarma + " Karma.");
} }
} }

View File

@ -92,9 +92,8 @@ class WebsiteMail(http.Controller):
values['is_follower'] = len( values['is_follower'] = len(
request.registry['mail.followers'].search( request.registry['mail.followers'].search(
cr, SUPERUSER_ID, [ cr, SUPERUSER_ID, [
('res_model', '=', 'mail.group'), ('res_model', '=', model),
('res_id', '=', obj_ids[0]), ('res_id', '=', obj_ids[0]),
('partner_id', '=', partner_id.id) ('partner_id', '=', partner_id.id)
], context=context)) == 1 ], context=context)) == 1
return values return values

View File

@ -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');
},
});
})();

View File

@ -21,6 +21,7 @@
<template id="head" inherit_id="website.assets_frontend" name="Mail customization"> <template id="head" inherit_id="website.assets_frontend" name="Mail customization">
<xpath expr="/t" position="inside"> <xpath expr="/t" position="inside">
<script type="text/javascript" src="/website_mail/static/src/js/follow.js"></script>
<link rel='stylesheet' href='/website_mail/static/src/css/website_mail.css'/> <link rel='stylesheet' href='/website_mail/static/src/css/website_mail.css'/>
</xpath> </xpath>
</template> </template>

View File

@ -3,8 +3,8 @@
var website = openerp.website; var website = openerp.website;
website.snippet.animationRegistry.follow = website.snippet.Animation.extend({ website.snippet.animationRegistry.follow_alias = website.snippet.Animation.extend({
selector: ".js_follow", selector: ".js_follow_alias",
start: function (editable_mode) { start: function (editable_mode) {
var self = this; var self = this;
this.is_user = false; this.is_user = false;
@ -23,8 +23,8 @@
// not if editable mode to allow designer to edit alert field // not if editable mode to allow designer to edit alert field
if (!editable_mode) { if (!editable_mode) {
$('.js_follow > .alert').addClass("hidden"); $('.js_follow_alias > .alert').addClass("hidden");
$('.js_follow > .input-group-btn.hidden').removeClass("hidden"); $('.js_follow_alias > .input-group-btn.hidden').removeClass("hidden");
this.$target.find('.js_follow_btn, .js_unfollow_btn').on('click', function (event) { this.$target.find('.js_follow_btn, .js_unfollow_btn').on('click', function (event) {
event.preventDefault(); event.preventDefault();
self.on_click(); self.on_click();

View File

@ -11,7 +11,7 @@
<span class="oe_snippet_thumbnail_title">Discussion Group</span> <span class="oe_snippet_thumbnail_title">Discussion Group</span>
</div> </div>
<div class="oe_snippet_body js_follow" <div class="oe_snippet_body js_follow_alias"
data-id="0" data-id="0"
data-object="mail.group" data-object="mail.group"
data-follow="off"> data-follow="off">
@ -38,7 +38,7 @@
<xpath expr="//div[@id='snippet_options']" position="inside"> <xpath expr="//div[@id='snippet_options']" position="inside">
<div data-snippet-option-id='subscribe' <div data-snippet-option-id='subscribe'
data-selector=".js_follow" data-selector=".js_follow_alias"
data-selector-siblings="p, h1, h2, h3, blockquote, .well, .panel" data-selector-siblings="p, h1, h2, h3, blockquote, .well, .panel"
> >
<li> <li>

View File

@ -55,7 +55,7 @@ class table_compute(object):
self.table[(pos/PPR)+y2][(pos%PPR)+x2] = False self.table[(pos/PPR)+y2][(pos%PPR)+x2] = False
self.table[pos/PPR][pos%PPR] = { self.table[pos/PPR][pos%PPR] = {
'product': p, 'x':x, 'y': y, '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: if index<=PPG:
maxy=max(maxy,y+(pos/PPR)) maxy=max(maxy,y+(pos/PPR))

View File

@ -482,6 +482,8 @@ class ir_actions_server(osv.osv):
"based on the sequence. Low number means high priority."), "based on the sequence. Low number means high priority."),
'model_id': fields.many2one('ir.model', 'Base Model', required=True, ondelete='cascade', 'model_id': fields.many2one('ir.model', 'Base Model', required=True, ondelete='cascade',
help="Base model on which the server action runs."), 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, 'menu_ir_values_id': fields.many2one('ir.values', 'More Menu entry', readonly=True,
help='More menu entry.'), help='More menu entry.'),
# Client Action # Client Action
@ -641,6 +643,10 @@ class ir_actions_server(osv.osv):
'wkf_field_id': False, 'wkf_field_id': False,
'crud_model_id': model_id, '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} 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): 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 crud_model_name = False
if crud_model_id: if crud_model_id:
crud_model_name = self.pool.get('ir.model').browse(cr, uid, crud_model_id, context).model 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} values = {'link_field_id': False, 'crud_model_name': crud_model_name}
return {'value': values} return {'value': values}

View File

@ -349,8 +349,9 @@
Check to attach the newly created record to the record on which the server action runs. Check to attach the newly created record to the record on which the server action runs.
</p> </p>
<group> <group>
<field name="model_name" invisible="1"/>
<field name="link_field_id" <field name="link_field_id"
domain="[('model_id', '=', model_id), ('relation', '=', crud_model_name), ('ttype', 'in', ['many2one'])]" domain="[('model_id', '=', crud_model_id), ('relation', '=', model_name), ('ttype', 'in', ['many2one'])]"
attrs="{'required': [('state', '=', 'object_create'), ('link_new_record', '=', True)], attrs="{'required': [('state', '=', 'object_create'), ('link_new_record', '=', True)],
'invisible': ['|', ('state', '!=', 'object_create'), ('link_new_record', '=', False)]}"/> 'invisible': ['|', ('state', '!=', 'object_create'), ('link_new_record', '=', False)]}"/>
</group> </group>

View File

@ -561,7 +561,7 @@ class many2one(_column):
# we use uid=1 because the visibility of a many2one field value (just id and name) # 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. # must be the access right of the parent form and not the linked object itself.
records = dict(obj.name_get(cr, SUPERUSER_ID, 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)) context=context))
for id in res: for id in res:
if res[id] in records: if res[id] in records:

View File

@ -3347,6 +3347,8 @@ class BaseModel(object):
return [] return []
if fields_to_read is None: if fields_to_read is None:
fields_to_read = self._columns.keys() 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 # 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 fields_pre = [f for f in fields_to_read if