merge upstream
This commit is contained in:
commit
e75d9525bb
|
@ -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',
|
||||
|
|
|
@ -741,6 +741,8 @@ class account_move_line(osv.osv):
|
|||
def search(self, cr, uid, args, offset=0, limit=None, order=None, context=None, count=False):
|
||||
if context is None:
|
||||
context = {}
|
||||
if context.get('fiscalyear'):
|
||||
args.append(('period_id.fiscalyear_id', '=', context.get('fiscalyear', False)))
|
||||
if context and context.get('next_partner_only', False):
|
||||
if not context.get('partner_id', False):
|
||||
partner = self.list_partners_to_reconcile(cr, uid, context=context)
|
||||
|
@ -823,7 +825,7 @@ class account_move_line(osv.osv):
|
|||
'line_partial_ids': map(lambda x: (4,x,False), merges+unmerge)
|
||||
}, context=context)
|
||||
move_rec_obj.reconcile_partial_check(cr, uid, [r_id] + merges_rec, context=context)
|
||||
return True
|
||||
return r_id
|
||||
|
||||
def reconcile(self, cr, uid, ids, type='auto', writeoff_acc_id=False, writeoff_period_id=False, writeoff_journal_id=False, context=None):
|
||||
account_obj = self.pool.get('account.account')
|
||||
|
|
|
@ -351,7 +351,7 @@
|
|||
<act_window
|
||||
id="action_account_items"
|
||||
name="Journal Items"
|
||||
context="{'search_default_account_id': [active_id]}"
|
||||
context="{'search_default_account_id': [active_id], 'fiscalyear': context.get('fiscalyear')}"
|
||||
res_model="account.move.line"
|
||||
src_model="account.account"
|
||||
key2="tree_but_open"/>
|
||||
|
|
|
@ -99,7 +99,7 @@
|
|||
</tr>
|
||||
<tr t-foreach="get_lines_with_out_partner(data['form'])" t-as="not_partner">
|
||||
<td>
|
||||
<span t-esc="partner['name']"/>
|
||||
<span t-esc="not_partner['name']"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="formatLang(not_partner['direction'], currency_obj=res_company.currency_id)"/>
|
||||
|
|
|
@ -62,9 +62,10 @@ class account_chart(osv.osv_memory):
|
|||
ORDER BY p.date_stop DESC
|
||||
LIMIT 1) AS period_stop''', (fiscalyear_id, fiscalyear_id))
|
||||
periods = [i[0] for i in cr.fetchall()]
|
||||
if periods and len(periods) > 1:
|
||||
if periods:
|
||||
start_period = periods[0]
|
||||
end_period = periods[1]
|
||||
if len(periods) > 1:
|
||||
end_period = periods[1]
|
||||
res['value'] = {'period_from': start_period, 'period_to': end_period}
|
||||
else:
|
||||
res['value'] = {'period_from': False, 'period_to': False}
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -248,7 +248,7 @@
|
|||
<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}">
|
||||
<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="amount"/>
|
||||
<field name="account_analytic_id" groups="analytic.group_analytic_accounting"/>
|
||||
|
|
|
@ -1,137 +1,41 @@
|
|||
#
|
||||
# Implements encrypting functions.
|
||||
#
|
||||
# Copyright (c) 2008, F S 3 Consulting Inc.
|
||||
#
|
||||
# Maintainer:
|
||||
# Alec Joseph Rivera (agi<at>fs3.ph)
|
||||
# refactored by Antony Lesuisse <al<at>openerp.com>
|
||||
#
|
||||
|
||||
import hashlib
|
||||
import hmac
|
||||
import logging
|
||||
from random import sample
|
||||
from string import ascii_letters, digits
|
||||
|
||||
from passlib.context import CryptContext
|
||||
|
||||
import openerp
|
||||
from openerp.osv import fields, osv
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
magic_md5 = '$1$'
|
||||
magic_sha256 = '$5$'
|
||||
|
||||
def gen_salt(length=8, symbols=None):
|
||||
if symbols is None:
|
||||
symbols = ascii_letters + digits
|
||||
return ''.join(sample(symbols, length))
|
||||
|
||||
def md5crypt( raw_pw, salt, magic=magic_md5 ):
|
||||
""" md5crypt FreeBSD crypt(3) based on but different from md5
|
||||
|
||||
The md5crypt is based on Mark Johnson's md5crypt.py, which in turn is
|
||||
based on FreeBSD src/lib/libcrypt/crypt.c (1.2) by Poul-Henning Kamp.
|
||||
Mark's port can be found in ActiveState ASPN Python Cookbook. Kudos to
|
||||
Poul and Mark. -agi
|
||||
|
||||
Original license:
|
||||
|
||||
* "THE BEER-WARE LICENSE" (Revision 42):
|
||||
*
|
||||
* <phk@login.dknet.dk> wrote this file. As long as you retain this
|
||||
* notice you can do whatever you want with this stuff. If we meet some
|
||||
* day, and you think this stuff is worth it, you can buy me a beer in
|
||||
* return.
|
||||
*
|
||||
* Poul-Henning Kamp
|
||||
"""
|
||||
raw_pw = raw_pw.encode('utf-8')
|
||||
salt = salt.encode('utf-8')
|
||||
hash = hashlib.md5()
|
||||
hash.update( raw_pw + magic + salt )
|
||||
st = hashlib.md5()
|
||||
st.update( raw_pw + salt + raw_pw)
|
||||
stretch = st.digest()
|
||||
|
||||
for i in range( 0, len( raw_pw ) ):
|
||||
hash.update( stretch[i % 16] )
|
||||
|
||||
i = len( raw_pw )
|
||||
|
||||
while i:
|
||||
if i & 1:
|
||||
hash.update('\x00')
|
||||
else:
|
||||
hash.update( raw_pw[0] )
|
||||
i >>= 1
|
||||
|
||||
saltedmd5 = hash.digest()
|
||||
|
||||
for i in range( 1000 ):
|
||||
hash = hashlib.md5()
|
||||
|
||||
if i & 1:
|
||||
hash.update( raw_pw )
|
||||
else:
|
||||
hash.update( saltedmd5 )
|
||||
|
||||
if i % 3:
|
||||
hash.update( salt )
|
||||
if i % 7:
|
||||
hash.update( raw_pw )
|
||||
if i & 1:
|
||||
hash.update( saltedmd5 )
|
||||
else:
|
||||
hash.update( raw_pw )
|
||||
|
||||
saltedmd5 = hash.digest()
|
||||
|
||||
itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
|
||||
|
||||
rearranged = ''
|
||||
for a, b, c in ((0, 6, 12), (1, 7, 13), (2, 8, 14), (3, 9, 15), (4, 10, 5)):
|
||||
v = ord( saltedmd5[a] ) << 16 | ord( saltedmd5[b] ) << 8 | ord( saltedmd5[c] )
|
||||
|
||||
for i in range(4):
|
||||
rearranged += itoa64[v & 0x3f]
|
||||
v >>= 6
|
||||
|
||||
v = ord( saltedmd5[11] )
|
||||
|
||||
for i in range( 2 ):
|
||||
rearranged += itoa64[v & 0x3f]
|
||||
v >>= 6
|
||||
|
||||
return magic + salt + '$' + rearranged
|
||||
|
||||
def sh256crypt(cls, password, salt, magic=magic_sha256):
|
||||
iterations = 1000
|
||||
# see http://en.wikipedia.org/wiki/PBKDF2
|
||||
result = password.encode('utf8')
|
||||
for i in xrange(cls.iterations):
|
||||
result = hmac.HMAC(result, salt, hashlib.sha256).digest() # uses HMAC (RFC 2104) to apply salt
|
||||
result = result.encode('base64') # doesnt seem to be crypt(3) compatible
|
||||
return '%s%s$%s' % (magic_sha256, salt, result)
|
||||
default_crypt_context = CryptContext(
|
||||
# kdf which can be verified by the context. The default encryption kdf is
|
||||
# the first of the list
|
||||
['pbkdf2_sha512', 'md5_crypt'],
|
||||
# deprecated algorithms are still verified as usual, but ``needs_update``
|
||||
# will indicate that the stored hash should be replaced by a more recent
|
||||
# algorithm. Passlib 1.6 supports an `auto` value which deprecates any
|
||||
# algorithm but the default, but Debian only provides 1.5 so...
|
||||
deprecated=['md5_crypt'],
|
||||
)
|
||||
|
||||
class res_users(osv.osv):
|
||||
_inherit = "res.users"
|
||||
|
||||
def init(self, cr):
|
||||
_logger.info("Hashing passwords, may be slow for databases with many users...")
|
||||
cr.execute("SELECT id, password FROM res_users"
|
||||
" WHERE password IS NOT NULL"
|
||||
" AND password != ''")
|
||||
for uid, pwd in cr.fetchall():
|
||||
self._set_password(cr, openerp.SUPERUSER_ID, uid, pwd)
|
||||
|
||||
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))
|
||||
del value
|
||||
self._set_password(cr, uid, id, value, context=context)
|
||||
|
||||
def get_pw( self, cr, uid, ids, name, args, context ):
|
||||
cr.execute('select id, password from res_users where id in %s', (tuple(map(int, ids)),))
|
||||
stored_pws = cr.fetchall()
|
||||
res = {}
|
||||
|
||||
for id, stored_pw in stored_pws:
|
||||
res[id] = stored_pw
|
||||
|
||||
return res
|
||||
return dict(cr.fetchall())
|
||||
|
||||
_columns = {
|
||||
'password': fields.function(get_pw, fnct_inv=set_pw, type='char', string='Password', invisible=True, store=True),
|
||||
|
@ -141,27 +45,51 @@ class res_users(osv.osv):
|
|||
def check_credentials(self, cr, uid, password):
|
||||
# convert to base_crypt if needed
|
||||
cr.execute('SELECT password, password_crypt FROM res_users WHERE id=%s AND active', (uid,))
|
||||
encrypted = None
|
||||
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))
|
||||
stored, encrypted = cr.fetchone()
|
||||
if stored and not encrypted:
|
||||
self._set_password(cr, uid, uid, stored)
|
||||
try:
|
||||
return super(res_users, self).check_credentials(cr, uid, password)
|
||||
except openerp.exceptions.AccessDenied:
|
||||
# check md5crypt
|
||||
if stored_password_crypt:
|
||||
if stored_password_crypt[:len(magic_md5)] == magic_md5:
|
||||
salt = stored_password_crypt[len(magic_md5):11]
|
||||
if stored_password_crypt == md5crypt(password, salt):
|
||||
return
|
||||
elif stored_password_crypt[:len(magic_md5)] == magic_sha256:
|
||||
salt = stored_password_crypt[len(magic_md5):11]
|
||||
if stored_password_crypt == md5crypt(password, salt):
|
||||
return
|
||||
# Reraise password incorrect
|
||||
if encrypted:
|
||||
valid_pass, replacement = self._crypt_context(cr, uid, uid)\
|
||||
.verify_and_update(password, encrypted)
|
||||
if replacement is not None:
|
||||
self._set_encrypted_password(cr, uid, uid, replacement)
|
||||
if valid_pass:
|
||||
return
|
||||
|
||||
raise
|
||||
|
||||
def _set_password(self, cr, uid, id, password, context=None):
|
||||
""" Encrypts then stores the provided plaintext password for the user
|
||||
``id``
|
||||
"""
|
||||
encrypted = self._crypt_context(cr, uid, id, context=context).encrypt(password)
|
||||
self._set_encrypted_password(cr, uid, id, encrypted, context=context)
|
||||
|
||||
def _set_encrypted_password(self, cr, uid, id, encrypted, context=None):
|
||||
""" Store the provided encrypted password to the database, and clears
|
||||
any plaintext password
|
||||
|
||||
:param uid: id of the current user
|
||||
:param id: id of the user on which the password should be set
|
||||
"""
|
||||
cr.execute(
|
||||
"UPDATE res_users SET password='', password_crypt=%s WHERE id=%s",
|
||||
(encrypted, id))
|
||||
|
||||
def _crypt_context(self, cr, uid, id, context=None):
|
||||
""" Passlib CryptContext instance used to encrypt and verify
|
||||
passwords. Can be overridden if technical, legal or political matters
|
||||
require different kdfs than the provided default.
|
||||
|
||||
Requires a CryptContext as deprecation and upgrade notices are used
|
||||
internally
|
||||
"""
|
||||
return default_crypt_context
|
||||
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -245,7 +245,8 @@ class event_ticket(osv.osv):
|
|||
]
|
||||
|
||||
def onchange_product_id(self, cr, uid, ids, product_id=False, context=None):
|
||||
return {'value': {'price': self.pool.get("product.product").browse(cr, uid, product_id).list_price or 0}}
|
||||
price = self.pool.get("product.product").browse(cr, uid, product_id).list_price if product_id else 0
|
||||
return {'value': {'price': price}}
|
||||
|
||||
|
||||
class event_registration(osv.osv):
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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),
|
||||
},
|
||||
|
|
|
@ -40,7 +40,9 @@
|
|||
<page string="Public Information">
|
||||
<group>
|
||||
<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="work_location"/>
|
||||
</group>
|
||||
|
@ -68,7 +70,9 @@
|
|||
<field name="otherid" groups="base.group_hr_user"/>
|
||||
</group>
|
||||
<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 string="Status">
|
||||
<field name="gender"/>
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -32,7 +32,7 @@ This is the latest UK OpenERP localisation necessary to run OpenERP accounting f
|
|||
- a few other adaptations""",
|
||||
'author': 'SmartMode LTD',
|
||||
'website': 'http://www.smartmode.co.uk',
|
||||
'depends': ['base_iban', 'base_vat', 'account_chart'],
|
||||
'depends': ['base_iban', 'base_vat', 'account_chart', 'account_anglo_saxon'],
|
||||
'data': [
|
||||
'data/account.account.type.csv',
|
||||
'data/account.account.template.csv',
|
||||
|
|
|
@ -28,7 +28,7 @@ United States - Chart of accounts.
|
|||
==================================
|
||||
""",
|
||||
'website': 'http://www.openerp.com',
|
||||
'depends': ['account_chart'],
|
||||
'depends': ['account_chart', 'account_anglo_saxon'],
|
||||
'data': [
|
||||
'l10n_us_account_type.xml',
|
||||
'account_chart_template.xml',
|
||||
|
|
|
@ -176,7 +176,7 @@ class mail_notification(osv.Model):
|
|||
references = message.parent_id.message_id if message.parent_id else False
|
||||
|
||||
# create email values
|
||||
max_recipients = 100
|
||||
max_recipients = 50
|
||||
chunks = [email_pids[x:x + max_recipients] for x in xrange(0, len(email_pids), max_recipients)]
|
||||
email_ids = []
|
||||
for chunk in chunks:
|
||||
|
@ -188,7 +188,7 @@ class mail_notification(osv.Model):
|
|||
'references': references,
|
||||
}
|
||||
email_ids.append(self.pool.get('mail.mail').create(cr, uid, mail_values, context=context))
|
||||
if force_send and len(chunks) < 6: # for more than 500 followers, use the queue system
|
||||
if force_send and len(chunks) < 2: # for more than 50 followers, use the queue system
|
||||
self.pool.get('mail.mail').send(cr, uid, email_ids, context=context)
|
||||
return True
|
||||
|
||||
|
|
|
@ -211,3 +211,16 @@ class mail_group(osv.Model):
|
|||
return []
|
||||
else:
|
||||
return super(mail_group, self).get_suggested_thread(cr, uid, removed_suggested_threads, context)
|
||||
|
||||
def message_get_email_values(self, cr, uid, id, notif_mail=None, context=None):
|
||||
res = super(mail_group, self).message_get_email_values(cr, uid, id, notif_mail=notif_mail, context=context)
|
||||
group = self.browse(cr, uid, id, context=context)
|
||||
res.update({
|
||||
'headers': {
|
||||
'Precedence': 'list',
|
||||
}
|
||||
})
|
||||
if group.alias_domain:
|
||||
res['headers']['List-Id'] = '%s.%s' % (group.alias_name, group.alias_domain)
|
||||
res['headers']['List-Post'] = '<mailto:%s@%s>' % (group.alias_name, group.alias_domain)
|
||||
return res
|
||||
|
|
|
@ -204,12 +204,15 @@ class mail_mail(osv.Model):
|
|||
"""
|
||||
body = self.send_get_mail_body(cr, uid, mail, partner=partner, context=context)
|
||||
body_alternative = tools.html2plaintext(body)
|
||||
return {
|
||||
res = {
|
||||
'body': body,
|
||||
'body_alternative': body_alternative,
|
||||
'subject': self.send_get_mail_subject(cr, uid, mail, partner=partner, context=context),
|
||||
'email_to': self.send_get_mail_to(cr, uid, mail, partner=partner, context=context),
|
||||
}
|
||||
if mail.model and mail.res_id and self.pool.get(mail.model) and hasattr(self.pool[mail.model], 'message_get_email_values'):
|
||||
res.update(self.pool[mail.model].message_get_email_values(cr, uid, mail.res_id, mail, context=context))
|
||||
return res
|
||||
|
||||
def send(self, cr, uid, ids, auto_commit=False, raise_exception=False, context=None):
|
||||
""" Sends the selected emails immediately, ignoring their current
|
||||
|
@ -268,6 +271,9 @@ class mail_mail(osv.Model):
|
|||
# build an RFC2822 email.message.Message object and send it without queuing
|
||||
res = None
|
||||
for email in email_list:
|
||||
email_headers = dict(headers)
|
||||
if email.get('headers'):
|
||||
email_headers.update(email['headers'])
|
||||
msg = ir_mail_server.build_email(
|
||||
email_from=mail.email_from,
|
||||
email_to=email.get('email_to'),
|
||||
|
@ -282,7 +288,7 @@ class mail_mail(osv.Model):
|
|||
object_id=mail.res_id and ('%s-%s' % (mail.res_id, mail.model)),
|
||||
subtype='html',
|
||||
subtype_alternative='plain',
|
||||
headers=headers)
|
||||
headers=email_headers)
|
||||
res = ir_mail_server.send_email(cr, uid, msg,
|
||||
mail_server_id=mail.mail_server_id.id,
|
||||
context=context)
|
||||
|
|
|
@ -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, []))))
|
||||
|
||||
|
@ -694,6 +697,16 @@ class mail_thread(osv.AbstractModel):
|
|||
if record.alias_domain and record.alias_name else False
|
||||
for record in self.browse(cr, SUPERUSER_ID, ids, context=context)]
|
||||
|
||||
def message_get_email_values(self, cr, uid, id, notif_mail=None, context=None):
|
||||
""" Temporary method to create custom notification email values for a given
|
||||
model and document. This should be better to have a headers field on
|
||||
the mail.mail model, computed when creating the notification email, but
|
||||
this cannot be done in a stable version.
|
||||
|
||||
TDE FIXME: rethink this ulgy thing. """
|
||||
res = dict()
|
||||
return res
|
||||
|
||||
#------------------------------------------------------
|
||||
# Mail gateway
|
||||
#------------------------------------------------------
|
||||
|
@ -1301,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]
|
||||
|
||||
|
|
|
@ -267,10 +267,7 @@ class mail_compose_message(osv.TransientModel):
|
|||
# mass mailing: rendering override wizard static values
|
||||
if mass_mail_mode and wizard.model:
|
||||
# always keep a copy, reset record name (avoid browsing records)
|
||||
mail_values.update(notification=True, record_name=False)
|
||||
if hasattr(self.pool[wizard.model], 'message_new'):
|
||||
mail_values['model'] = wizard.model
|
||||
mail_values['res_id'] = res_id
|
||||
mail_values.update(notification=True, model=wizard.model, res_id=res_id, record_name=False)
|
||||
# auto deletion of mail_mail
|
||||
if 'mail_auto_delete' in context:
|
||||
mail_values['auto_delete'] = context.get('mail_auto_delete')
|
||||
|
|
|
@ -84,7 +84,8 @@ class MailMail(osv.Model):
|
|||
def send_get_email_dict(self, cr, uid, mail, partner=None, context=None):
|
||||
res = super(MailMail, self).send_get_email_dict(cr, uid, mail, partner, context=context)
|
||||
if mail.mailing_id and res.get('body') and res.get('email_to'):
|
||||
email_to = tools.email_split(res.get('email_to')[0])
|
||||
emails = tools.email_split(res.get('email_to')[0])
|
||||
email_to = emails and emails[0] or False
|
||||
unsubscribe_url = self._get_unsubscribe_url(cr, uid, mail, email_to, context=context)
|
||||
if unsubscribe_url:
|
||||
res['body'] = tools.append_content_to_html(res['body'], unsubscribe_url, plaintext=False, container_tag='p')
|
||||
|
|
|
@ -591,6 +591,7 @@
|
|||
<field name="mail_mail_id"/>
|
||||
<field name="message_id"/>
|
||||
<field name="sent"/>
|
||||
<field name="exception"/>
|
||||
<field name="opened"/>
|
||||
<field name="replied"/>
|
||||
<field name="bounced"/>
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -678,6 +678,17 @@ class product_template(osv.osv):
|
|||
if not context or "create_product_product" not in context:
|
||||
self.create_variant_ids(cr, uid, [product_template_id], context=context)
|
||||
self._set_standard_price(cr, uid, product_template_id, vals.get('standard_price', 0.0), context=context)
|
||||
|
||||
# TODO: this is needed to set given values to first variant after creation
|
||||
# these fields should be moved to product as lead to confusion
|
||||
related_vals = {}
|
||||
if vals.get('ean13'):
|
||||
related_vals['ean13'] = vals['ean13']
|
||||
if vals.get('default_code'):
|
||||
related_vals['default_code'] = vals['default_code']
|
||||
if related_vals:
|
||||
self.write(cr, uid, product_template_id, related_vals, context=context)
|
||||
|
||||
return product_template_id
|
||||
|
||||
def write(self, cr, uid, ids, vals, context=None):
|
||||
|
|
|
@ -27,7 +27,7 @@ class account_invoice(osv.Model):
|
|||
template_values = Composer.onchange_template_id(
|
||||
cr, uid, composer_id, line.product_id.email_template_id.id, 'comment', 'account.invoice', invoice.id
|
||||
)['value']
|
||||
template_values['attachment_ids'] = [(4, id) for id in template_values.get('attachment_ids', '[]')]
|
||||
template_values['attachment_ids'] = [(4, id) for id in template_values.get('attachment_ids', [])]
|
||||
Composer.write(cr, uid, [composer_id], template_values, context=context)
|
||||
Composer.send_mail(cr, uid, [composer_id], context=context)
|
||||
return True
|
||||
|
|
|
@ -50,8 +50,8 @@
|
|||
<field name="user_id" ref="base.user_demo"/>
|
||||
<field name="alias_model">project.task</field>
|
||||
<field name="message_follower_ids" eval="[(6, 0, [
|
||||
ref('base.user_root'),
|
||||
ref('base.user_demo')])]"/>
|
||||
ref('base.partner_root'),
|
||||
ref('base.partner_demo')])]"/>
|
||||
</record>
|
||||
|
||||
<!-- We assign after so that default values applies -->
|
||||
|
|
|
@ -749,7 +749,7 @@
|
|||
</tree>
|
||||
</field>
|
||||
-->
|
||||
<field name="move_lines" context="{'address_in_id': partner_id, 'form_view_ref':'stock.view_move_picking_form', 'tree_view_ref':'view_move_picking_tree', 'default_picking_type_id': picking_type_id,'default_picking_id': active_id}"/>
|
||||
<field name="move_lines" context="{'address_in_id': partner_id, 'form_view_ref':'stock.view_move_picking_form', 'tree_view_ref':'stock.view_move_picking_tree', 'default_picking_type_id': picking_type_id,'default_picking_id': active_id}"/>
|
||||
<field name="pack_operation_exist" invisible="1"/>
|
||||
<field name="note" placeholder="Add an internal note..." class="oe_inline"/>
|
||||
</page>
|
||||
|
@ -1288,7 +1288,7 @@
|
|||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="domain" eval="['|','&',('picking_id','=',False),('location_dest_id.usage', 'in', ['customer','supplier']),'&',('picking_id','!=',False),('picking_id.picking_type_id.code','=','outgoing')]"/>
|
||||
<field name="domain" eval="[('picking_id.picking_type_id.code','=','incoming'), ('location_id.usage','!=','internal'), ('location_dest_id.usage', '=', 'internal')]"/>
|
||||
<field name="view_id" ref="view_move_tree_reception_picking"/>
|
||||
<field name="context">{'product_receive': True, 'search_default_future': True}</field>
|
||||
<field name="help" type="html">
|
||||
|
|
|
@ -2373,7 +2373,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 {
|
||||
|
@ -3324,6 +3324,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 {
|
||||
|
@ -3447,6 +3450,39 @@ input[type="radio"], input[type="checkbox"] {
|
|||
opacity: 0.6;
|
||||
}
|
||||
|
||||
/* ---- EDITOR TOUR ---- {{{ */
|
||||
div.tour-backdrop {
|
||||
z-index: 2009;
|
||||
}
|
||||
|
||||
.popover.tour.orphan .arrow {
|
||||
display: none;
|
||||
}
|
||||
.popover.tour .popover-navigation {
|
||||
padding: 9px 14px;
|
||||
}
|
||||
.popover.tour .popover-navigation *[data-role="end"] {
|
||||
float: right;
|
||||
}
|
||||
.popover.tour .popover-navigation *[data-role="next"], .popover.tour .popover-navigation *[data-role="end"] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.popover.fixed {
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
.tour-backdrop {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 1100;
|
||||
background-color: black;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
body {
|
||||
overflow: auto;
|
||||
}
|
||||
|
|
|
@ -1939,7 +1939,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
|
||||
|
@ -2691,6 +2691,8 @@ body.oe_single_form
|
|||
> .arrow span
|
||||
background-color: #729fcf !important
|
||||
|
||||
.oe_webclient
|
||||
height: auto !important
|
||||
// }}}
|
||||
|
||||
// @media print {{{
|
||||
|
@ -2799,6 +2801,33 @@ input[type="radio"], input[type="checkbox"]
|
|||
background-color: black
|
||||
opacity: 0.6000000238418579
|
||||
|
||||
/* ---- EDITOR TOUR ---- {{{ */
|
||||
|
||||
div.tour-backdrop
|
||||
z-index: 2009
|
||||
.popover.tour
|
||||
&.orphan .arrow
|
||||
display: none
|
||||
.popover-navigation
|
||||
padding: 9px 14px
|
||||
*[data-role="end"]
|
||||
float: right
|
||||
*[data-role="next"],*[data-role="end"]
|
||||
cursor: pointer
|
||||
.popover.fixed
|
||||
position: fixed
|
||||
.tour-backdrop
|
||||
position: fixed
|
||||
top: 0
|
||||
right: 0
|
||||
bottom: 0
|
||||
left: 0
|
||||
z-index: 1100
|
||||
background-color: #000
|
||||
opacity: 0.8
|
||||
|
||||
|
||||
// }}}
|
||||
|
||||
body
|
||||
overflow: auto
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -346,11 +346,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;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,543 @@
|
|||
(function () {
|
||||
'use strict';
|
||||
|
||||
// raise an error in test mode if openerp don't exist
|
||||
if (typeof openerp === "undefined") {
|
||||
var error = "openerp is undefined"
|
||||
+ "\nhref: " + window.location.href
|
||||
+ "\nreferrer: " + document.referrer
|
||||
+ "\nlocalStorage: " + window.localStorage.getItem("tour");
|
||||
if (typeof $ !== "undefined") {
|
||||
error += '\n\n' + $("body").html();
|
||||
}
|
||||
throw new Error(error);
|
||||
}
|
||||
|
||||
var website = openerp.website;
|
||||
|
||||
// don't rewrite T in test mode
|
||||
if (typeof openerp.Tour !== "undefined") {
|
||||
return;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////
|
||||
|
||||
|
||||
/* jQuery selector to match exact text inside an element
|
||||
* :containsExact() - case insensitive
|
||||
* :containsExactCase() - case sensitive
|
||||
* :containsRegex() - set by user ( use: $(el).find(':containsRegex(/(red|blue|yellow)/gi)') )
|
||||
*/
|
||||
$.extend($.expr[':'],{
|
||||
containsExact: function(a,i,m){
|
||||
return $.trim(a.innerHTML.toLowerCase()) === m[3].toLowerCase();
|
||||
},
|
||||
containsExactCase: function(a,i,m){
|
||||
return $.trim(a.innerHTML) === m[3];
|
||||
},
|
||||
// Note all escaped characters need to be double escaped
|
||||
// inside of the containsRegex, so "\(" needs to be "\\("
|
||||
containsRegex: function(a,i,m){
|
||||
var regreg = /^\/((?:\\\/|[^\/])+)\/([mig]{0,3})$/,
|
||||
reg = regreg.exec(m[3]);
|
||||
return reg ? new RegExp(reg[1], reg[2]).test($.trim(a.innerHTML)) : false;
|
||||
}
|
||||
});
|
||||
$.ajaxSetup({
|
||||
beforeSend:function(){
|
||||
$.ajaxBusy = ($.ajaxBusy|0) + 1;
|
||||
},
|
||||
complete:function(){
|
||||
$.ajaxBusy--;
|
||||
}
|
||||
});
|
||||
|
||||
/////////////////////////////////////////////////
|
||||
|
||||
var localStorage = window.localStorage;
|
||||
|
||||
var Tour = {
|
||||
tours: {},
|
||||
defaultDelay: 50,
|
||||
retryRunningDelay: 1000,
|
||||
errorDelay: 5000,
|
||||
state: null,
|
||||
$element: null,
|
||||
timer: null,
|
||||
testtimer: null,
|
||||
currentTimer: null,
|
||||
register: function (tour) {
|
||||
if (tour.mode !== "test") tour.mode = "tutorial";
|
||||
Tour.tours[tour.id] = tour;
|
||||
},
|
||||
run: function (tour_id, mode) {
|
||||
var tour = Tour.tours[tour_id];
|
||||
if (!tour) {
|
||||
Tour.error(null, "Can't run '"+tour_id+"' (tour undefined)");
|
||||
}
|
||||
this.time = new Date().getTime();
|
||||
if (tour.path && !window.location.href.match(new RegExp("("+Tour.getLang()+")?"+tour.path+"#?$", "i"))) {
|
||||
var href = Tour.getLang()+tour.path;
|
||||
console.log("Tour Begin from run method (redirection to "+href+")");
|
||||
Tour.saveState(tour.id, mode || tour.mode, -1, 0);
|
||||
$(document).one("ajaxStop", Tour.running);
|
||||
window.location.href = href;
|
||||
} else {
|
||||
console.log("Tour Begin from run method");
|
||||
Tour.saveState(tour.id, mode || tour.mode, 0, 0);
|
||||
Tour.running();
|
||||
}
|
||||
},
|
||||
registerSteps: function (tour, mode) {
|
||||
if (tour.register) {
|
||||
return;
|
||||
}
|
||||
tour.register = true;
|
||||
|
||||
for (var index=0, len=tour.steps.length; index<len; index++) {
|
||||
var step = tour.steps[index];
|
||||
step.id = index;
|
||||
|
||||
if (!step.waitNot && index > 0 && tour.steps[index-1] &&
|
||||
tour.steps[index-1].popover && tour.steps[index-1].popover.next) {
|
||||
step.waitNot = '.popover.tour.fade.in:visible';
|
||||
}
|
||||
if (!step.waitFor && index > 0 && tour.steps[index-1].snippet) {
|
||||
step.waitFor = '.oe_overlay_options .oe_options:visible';
|
||||
}
|
||||
|
||||
|
||||
var snippet = step.element && step.element.match(/#oe_snippets (.*) \.oe_snippet_thumbnail/);
|
||||
if (snippet) {
|
||||
step.snippet = snippet[1];
|
||||
} else if (step.snippet) {
|
||||
step.element = '#oe_snippets '+step.snippet+' .oe_snippet_thumbnail';
|
||||
}
|
||||
|
||||
if (!step.element) {
|
||||
step.element = "body";
|
||||
step.orphan = true;
|
||||
step.backdrop = true;
|
||||
} else {
|
||||
step.popover = step.popover || {};
|
||||
step.popover.arrow = true;
|
||||
}
|
||||
}
|
||||
if (tour.steps[index-1] &&
|
||||
tour.steps[index-1].popover && tour.steps[index-1].popover.next) {
|
||||
var step = {
|
||||
_title: "close popover and finish",
|
||||
id: index,
|
||||
waitNot: '.popover.tour.fade.in:visible'
|
||||
};
|
||||
tour.steps.push(step);
|
||||
}
|
||||
|
||||
// rendering bootstrap tour and popover
|
||||
if (mode !== "test") {
|
||||
for (var index=0, len=tour.steps.length; index<len; index++) {
|
||||
var step = tour.steps[index];
|
||||
step._title = step._title || step.title;
|
||||
step.title = Tour.popoverTitle(tour, { title: step._title });
|
||||
step.template = step.template || Tour.popover( step.popover );
|
||||
}
|
||||
}
|
||||
},
|
||||
closePopover: function () {
|
||||
if (Tour.$element) {
|
||||
Tour.$element.popover('destroy');
|
||||
Tour.$element.removeData("tour");
|
||||
Tour.$element.removeData("tour-step");
|
||||
$(".tour-backdrop").remove();
|
||||
$(".popover.tour").remove();
|
||||
Tour.$element = null;
|
||||
}
|
||||
},
|
||||
autoTogglePopover: function () {
|
||||
var state = Tour.getState();
|
||||
var step = state.step;
|
||||
|
||||
if (Tour.$element &&
|
||||
Tour.$element.is(":visible") &&
|
||||
Tour.$element.data("tour") === state.id &&
|
||||
Tour.$element.data("tour-step") === step.id) {
|
||||
Tour.repositionPopover();
|
||||
return;
|
||||
}
|
||||
|
||||
if (step.busy) {
|
||||
return;
|
||||
}
|
||||
|
||||
Tour.closePopover();
|
||||
|
||||
var $element = $(step.element).first();
|
||||
if (!step.element || !$element.size() || !$element.is(":visible")) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Tour.$element = $element;
|
||||
$element.data("tour", state.id);
|
||||
$element.data("tour-step", step.id);
|
||||
$element.popover({
|
||||
placement: step.placement || "auto",
|
||||
animation: true,
|
||||
trigger: "manual",
|
||||
title: step.title,
|
||||
content: step.content,
|
||||
html: true,
|
||||
container: "body",
|
||||
template: step.template,
|
||||
orphan: step.orphan
|
||||
}).popover("show");
|
||||
|
||||
|
||||
var $tip = $element.data("bs.popover").tip();
|
||||
|
||||
|
||||
// add popover style (orphan, static, backdrop)
|
||||
if (step.orphan) {
|
||||
$tip.addClass("orphan");
|
||||
}
|
||||
|
||||
var node = $element[0];
|
||||
var css;
|
||||
do {
|
||||
css = window.getComputedStyle(node);
|
||||
if (!css || css.position == "fixed") {
|
||||
$tip.addClass("fixed");
|
||||
break;
|
||||
}
|
||||
} while ((node = node.parentNode) && node !== document);
|
||||
|
||||
if (step.backdrop) {
|
||||
$("body").append('<div class="tour-backdrop"></div>');
|
||||
}
|
||||
|
||||
if (step.backdrop || $element.parents("#website-top-navbar, .oe_navbar, .modal").size()) {
|
||||
$tip.css("z-index", 2010);
|
||||
}
|
||||
|
||||
// button click event
|
||||
$tip.find("button")
|
||||
.one("click", function () {
|
||||
step.busy = true;
|
||||
if (!$(this).is("[data-role='next']")) {
|
||||
clearTimeout(Tour.timer);
|
||||
Tour.endTour();
|
||||
}
|
||||
Tour.closePopover();
|
||||
});
|
||||
|
||||
Tour.repositionPopover();
|
||||
},
|
||||
repositionPopover: function() {
|
||||
var popover = Tour.$element.data("bs.popover");
|
||||
var $tip = Tour.$element.data("bs.popover").tip();
|
||||
|
||||
if (popover.options.orphan) {
|
||||
return $tip.css("top", $(window).outerHeight() / 2 - $tip.outerHeight() / 2);
|
||||
}
|
||||
|
||||
var offsetBottom, offsetHeight, offsetRight, offsetWidth, originalLeft, originalTop, tipOffset;
|
||||
offsetWidth = $tip[0].offsetWidth;
|
||||
offsetHeight = $tip[0].offsetHeight;
|
||||
tipOffset = $tip.offset();
|
||||
originalLeft = tipOffset.left;
|
||||
originalTop = tipOffset.top;
|
||||
offsetBottom = $(document).outerHeight() - tipOffset.top - $tip.outerHeight();
|
||||
if (offsetBottom < 0) {
|
||||
tipOffset.top = tipOffset.top + offsetBottom;
|
||||
}
|
||||
offsetRight = $("html").outerWidth() - tipOffset.left - $tip.outerWidth();
|
||||
if (offsetRight < 0) {
|
||||
tipOffset.left = tipOffset.left + offsetRight;
|
||||
}
|
||||
if (tipOffset.top < 0) {
|
||||
tipOffset.top = 0;
|
||||
}
|
||||
if (tipOffset.left < 0) {
|
||||
tipOffset.left = 0;
|
||||
}
|
||||
$tip.offset(tipOffset);
|
||||
if (popover.options.placement === "bottom" || popover.options.placement === "top") {
|
||||
var left = Tour.$element.offset().left + Tour.$element.outerWidth()/2 - tipOffset.left;
|
||||
$tip.find(".arrow").css("left", left ? left + "px" : "");
|
||||
} else if (popover.options.placement !== "auto") {
|
||||
var top = Tour.$element.offset().top + Tour.$element.outerHeight()/2 - tipOffset.top;
|
||||
$tip.find(".arrow").css("top", top ? top + "px" : "");
|
||||
}
|
||||
},
|
||||
_load_template: false,
|
||||
load_template: function () {
|
||||
// don't need template to use bootstrap Tour in automatic mode
|
||||
Tour._load_template = true;
|
||||
if (typeof QWeb2 === "undefined") return $.when();
|
||||
var def = $.Deferred();
|
||||
openerp.qweb.add_template('/web/static/src/xml/website.tour.xml', function(err) {
|
||||
if (err) {
|
||||
def.reject(err);
|
||||
} else {
|
||||
def.resolve();
|
||||
}
|
||||
});
|
||||
return def;
|
||||
},
|
||||
popoverTitle: function (tour, options) {
|
||||
return typeof QWeb2 !== "undefined" ? openerp.qweb.render('tour.popover_title', options) : options.title;
|
||||
},
|
||||
popover: function (options) {
|
||||
return typeof QWeb2 !== "undefined" ? openerp.qweb.render('tour.popover', options) : options.title;
|
||||
},
|
||||
getLang: function () {
|
||||
return $("html").attr("lang") ? "/" + $("html").attr("lang").replace(/-/, '_') : "";
|
||||
},
|
||||
getState: function () {
|
||||
var state = JSON.parse(localStorage.getItem("tour") || 'false') || {};
|
||||
if (state) { this.time = state.time; }
|
||||
var tour_id,mode,step_id;
|
||||
if (!state.id && window.location.href.indexOf("#tutorial.") > -1) {
|
||||
state = {
|
||||
"id": window.location.href.match(/#tutorial\.(.*)=true/)[1],
|
||||
"mode": "tutorial",
|
||||
"step_id": 0
|
||||
};
|
||||
window.location.hash = "";
|
||||
console.log("Tour Begin from url hash");
|
||||
Tour.saveState(state.id, state.mode, state.step_id, 0);
|
||||
}
|
||||
if (!state.id) {
|
||||
return;
|
||||
}
|
||||
state.tour = Tour.tours[state.id];
|
||||
state.step = state.tour && state.tour.steps[state.step_id === -1 ? 0 : state.step_id];
|
||||
return state;
|
||||
},
|
||||
error: function (step, message) {
|
||||
var state = Tour.getState();
|
||||
message += '\n tour: ' + state.id
|
||||
+ (step ? '\n step: ' + step.id + ": '" + (step._title || step.title) + "'" : '' )
|
||||
+ '\n href: ' + window.location.href
|
||||
+ '\n referrer: ' + document.referrer
|
||||
+ (step ? '\n element: ' + Boolean(!step.element || ($(step.element).size() && $(step.element).is(":visible") && !$(step.element).is(":hidden"))) : '' )
|
||||
+ (step ? '\n waitNot: ' + Boolean(!step.waitNot || !$(step.waitNot).size()) : '' )
|
||||
+ (step ? '\n waitFor: ' + Boolean(!step.waitFor || $(step.waitFor).size()) : '' )
|
||||
+ "\n localStorage: " + JSON.stringify(localStorage)
|
||||
+ '\n\n' + $("body").html();
|
||||
Tour.reset();
|
||||
if (state.mode === "test") {
|
||||
throw new Error(message);
|
||||
}
|
||||
},
|
||||
lists: function () {
|
||||
var tour_ids = [];
|
||||
for (var k in Tour.tours) {
|
||||
tour_ids.push(k);
|
||||
}
|
||||
return tour_ids;
|
||||
},
|
||||
saveState: function (tour_id, mode, step_id, number, wait) {
|
||||
localStorage.setItem("tour", JSON.stringify({
|
||||
"id":tour_id,
|
||||
"mode":mode,
|
||||
"step_id":step_id || 0,
|
||||
"time": this.time,
|
||||
"number": number+1,
|
||||
"wait": wait || 0
|
||||
}));
|
||||
},
|
||||
reset: function () {
|
||||
var state = Tour.getState();
|
||||
if (state && state.tour) {
|
||||
for (var k in state.tour.steps) {
|
||||
state.tour.steps[k].busy = false;
|
||||
}
|
||||
}
|
||||
localStorage.removeItem("tour");
|
||||
clearTimeout(Tour.timer);
|
||||
clearTimeout(Tour.testtimer);
|
||||
Tour.closePopover();
|
||||
},
|
||||
running: function () {
|
||||
var state = Tour.getState();
|
||||
if (!state) return;
|
||||
else if (state.tour) {
|
||||
if (!Tour._load_template) {
|
||||
Tour.load_template().then(Tour.running);
|
||||
return;
|
||||
}
|
||||
console.log("Tour '"+state.id+"' is running");
|
||||
Tour.registerSteps(state.tour, state.mode);
|
||||
Tour.nextStep();
|
||||
} else {
|
||||
if (state.mode === "test" && state.wait >= 10) {
|
||||
Tour.error(state.step, "Tour '"+state.id+"' undefined");
|
||||
}
|
||||
Tour.saveState(state.id, state.mode, state.step_id, state.number-1, state.wait+1);
|
||||
console.log("Tour '"+state.id+"' wait for running (tour undefined)");
|
||||
setTimeout(Tour.running, state.mode === "test" ? Tour.defaultDelay : Tour.retryRunningDelay);
|
||||
}
|
||||
},
|
||||
check: function (step) {
|
||||
return (step &&
|
||||
(!step.element || ($(step.element).size() && $(step.element).is(":visible") && !$(step.element).is(":hidden"))) &&
|
||||
(!step.waitNot || !$(step.waitNot).size()) &&
|
||||
(!step.waitFor || $(step.waitFor).size()));
|
||||
},
|
||||
waitNextStep: function () {
|
||||
var state = Tour.getState();
|
||||
var time = new Date().getTime();
|
||||
var timer;
|
||||
var next = state.tour.steps[state.step.id+1];
|
||||
var overlaps = state.mode === "test" ? Tour.errorDelay : 0;
|
||||
|
||||
window.onbeforeunload = function () {
|
||||
clearTimeout(Tour.timer);
|
||||
clearTimeout(Tour.testtimer);
|
||||
};
|
||||
|
||||
function checkNext () {
|
||||
Tour.autoTogglePopover();
|
||||
|
||||
clearTimeout(Tour.timer);
|
||||
if (Tour.check(next)) {
|
||||
clearTimeout(Tour.currentTimer);
|
||||
// use an other timeout for cke dom loading
|
||||
Tour.saveState(state.id, state.mode, state.step.id, 0);
|
||||
setTimeout(function () {
|
||||
Tour.nextStep(next);
|
||||
}, Tour.defaultDelay);
|
||||
} else if (!overlaps || new Date().getTime() - time < overlaps) {
|
||||
Tour.timer = setTimeout(checkNext, Tour.defaultDelay);
|
||||
} else {
|
||||
Tour.error(next, "Can't reach the next step");
|
||||
}
|
||||
}
|
||||
checkNext();
|
||||
},
|
||||
nextStep: function (step) {
|
||||
var state = Tour.getState();
|
||||
|
||||
if (!state) {
|
||||
return;
|
||||
}
|
||||
|
||||
step = step || state.step;
|
||||
var next = state.tour.steps[step.id+1];
|
||||
|
||||
if (state.mode === "test" && state.number > 3) {
|
||||
Tour.error(next, "Cycling. Can't reach the next step");
|
||||
}
|
||||
|
||||
Tour.saveState(state.id, state.mode, step.id, state.number);
|
||||
|
||||
if (step.id !== state.step_id) {
|
||||
console.log("Tour Step: '" + (step._title || step.title) + "' (" + (new Date().getTime() - this.time) + "ms)");
|
||||
}
|
||||
|
||||
Tour.autoTogglePopover(true);
|
||||
|
||||
if (step.onload) {
|
||||
step.onload();
|
||||
}
|
||||
|
||||
if (next) {
|
||||
setTimeout(function () {
|
||||
if (Tour.getState()) {
|
||||
Tour.waitNextStep();
|
||||
}
|
||||
if (state.mode === "test") {
|
||||
setTimeout(function(){
|
||||
Tour.autoNextStep(state.tour, step);
|
||||
}, Tour.defaultDelay);
|
||||
}
|
||||
}, next.wait || 0);
|
||||
} else {
|
||||
setTimeout(function(){
|
||||
Tour.autoNextStep(state.tour, step);
|
||||
}, Tour.defaultDelay);
|
||||
Tour.endTour();
|
||||
}
|
||||
},
|
||||
endTour: function () {
|
||||
var state = Tour.getState();
|
||||
var test = state.step.id >= state.tour.steps.length-1;
|
||||
Tour.reset();
|
||||
if (test) {
|
||||
console.log('ok');
|
||||
} else {
|
||||
console.log('error');
|
||||
}
|
||||
},
|
||||
autoNextStep: function (tour, step) {
|
||||
clearTimeout(Tour.testtimer);
|
||||
|
||||
function autoStep () {
|
||||
if (!step) return;
|
||||
|
||||
if (step.autoComplete) {
|
||||
step.autoComplete(tour);
|
||||
}
|
||||
|
||||
$(".popover.tour [data-role='next']").click();
|
||||
|
||||
var $element = $(step.element);
|
||||
if (!$element.size()) return;
|
||||
|
||||
if (step.snippet) {
|
||||
|
||||
Tour.autoDragAndDropSnippet($element);
|
||||
|
||||
} else if ($element.is(":visible")) {
|
||||
|
||||
$element.trigger($.Event("mouseenter", { srcElement: $element[0] }));
|
||||
$element.trigger($.Event("mousedown", { srcElement: $element[0] }));
|
||||
|
||||
var evt = document.createEvent("MouseEvents");
|
||||
evt.initMouseEvent("click", true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
|
||||
$element[0].dispatchEvent(evt);
|
||||
|
||||
// trigger after for step like: mouseenter, next step click on button display with mouseenter
|
||||
setTimeout(function () {
|
||||
$element.trigger($.Event("mouseup", { srcElement: $element[0] }));
|
||||
$element.trigger($.Event("mouseleave", { srcElement: $element[0] }));
|
||||
}, 1000);
|
||||
}
|
||||
if (step.sampleText) {
|
||||
|
||||
$element.trigger($.Event("keydown", { srcElement: $element }));
|
||||
if ($element.is("input") ) {
|
||||
$element.val(step.sampleText);
|
||||
} if ($element.is("select")) {
|
||||
$element.find("[value='"+step.sampleText+"'], option:contains('"+step.sampleText+"')").attr("selected", true);
|
||||
$element.val(step.sampleText);
|
||||
} else {
|
||||
$element.html(step.sampleText);
|
||||
}
|
||||
setTimeout(function () {
|
||||
$element.trigger($.Event("keyup", { srcElement: $element }));
|
||||
$element.trigger($.Event("change", { srcElement: $element }));
|
||||
}, self.defaultDelay<<1);
|
||||
|
||||
}
|
||||
}
|
||||
Tour.testtimer = setTimeout(autoStep, 100);
|
||||
},
|
||||
autoDragAndDropSnippet: function (selector) {
|
||||
var $thumbnail = $(selector).first();
|
||||
var thumbnailPosition = $thumbnail.position();
|
||||
$thumbnail.trigger($.Event("mousedown", { which: 1, pageX: thumbnailPosition.left, pageY: thumbnailPosition.top }));
|
||||
$thumbnail.trigger($.Event("mousemove", { which: 1, pageX: document.body.scrollWidth/2, pageY: document.body.scrollHeight/2 }));
|
||||
var $dropZone = $(".oe_drop_zone").first();
|
||||
var dropPosition = $dropZone.position();
|
||||
$dropZone.trigger($.Event("mouseup", { which: 1, pageX: dropPosition.left, pageY: dropPosition.top }));
|
||||
}
|
||||
};
|
||||
openerp.Tour = Tour;
|
||||
|
||||
/////////////////////////////////////////////////
|
||||
|
||||
$(document).ready(Tour.running);
|
||||
|
||||
}());
|
|
@ -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");
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
});
|
||||
},
|
||||
|
|
|
@ -2038,4 +2038,5 @@
|
|||
</t>
|
||||
<t t-name="StatInfo">
|
||||
<strong><t t-esc="value"/></strong><br/><t t-esc="text"/></t>
|
||||
|
||||
</templates>
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<templates id="template" xml:space="preserve">
|
||||
<t t-name="website.tour_popover">
|
||||
<t t-name="tour.popover">
|
||||
<div t-attf-class="#{ fixed ? 'popover tour fixed' : 'popover tour' }">
|
||||
<div class="arrow"></div>
|
||||
<div class="arrow" t-if="!next"></div>
|
||||
<h3 class="popover-title"></h3>
|
||||
<div class="popover-content"></div>
|
||||
<t t-if="next or end">
|
||||
|
@ -21,7 +21,7 @@
|
|||
</t>
|
||||
</div>
|
||||
</t>
|
||||
<t t-name="website.tour_popover_title">
|
||||
<t t-name="tour.popover_title">
|
||||
<t t-esc="title"/><button title="End This Tutorial" type="button" class="close" data-role="end">×</button>
|
||||
</t>
|
||||
</templates>
|
|
@ -51,6 +51,7 @@
|
|||
<script src="/web/static/src/js/view_list_editable.js" type="text/javascript"></script>
|
||||
<script src="/web/static/src/js/view_tree.js" type="text/javascript"></script>
|
||||
<script src="/base/static/src/js/apps.js" type="text/javascript"></script>
|
||||
<script src="/web/static/src/js/tour.js" type="text/javascript"></script>
|
||||
<link href="/web/static/lib/fontawesome/css/font-awesome.css" rel="stylesheet"/>
|
||||
<link href="/web/static/lib/cleditor/jquery.cleditor.css" rel="stylesheet"/>
|
||||
<link href="/web/static/lib/jquery.textext/jquery.textext.css" rel="stylesheet"/>
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
|
||||
<script type="text/javascript" src="/web/static/lib/qweb/qweb2.js"></script>
|
||||
<script type="text/javascript" src="/web/static/src/js/openerpframework.js"></script>
|
||||
<script type="text/javascript" src="/web/static/src/js/tour.js"></script>
|
||||
<script type="text/javascript" charset="utf-8">
|
||||
openerp._modules = <t t-raw="modules"/>;
|
||||
</script>
|
||||
|
@ -246,6 +247,7 @@
|
|||
<t t-call="web.assets_backend"/>
|
||||
|
||||
<script type="text/javascript" id="qunit_config">
|
||||
localStorage.clear();
|
||||
QUnit.config.testTimeout = 5 * 60 * 1000;
|
||||
QUnit.moduleDone(function(result) {
|
||||
console.log(result.name + " (" + result.passed + "/" + result.total + " passed tests)");
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -516,36 +516,3 @@ ul.oe_menu_editor .disclose {
|
|||
filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=0);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
/* ---- EDITOR TOUR ---- {{{ */
|
||||
div.tour-backdrop {
|
||||
z-index: 2009;
|
||||
}
|
||||
|
||||
.popover.tour.orphan .arrow {
|
||||
display: none;
|
||||
}
|
||||
.popover.tour .popover-navigation {
|
||||
padding: 9px 14px;
|
||||
}
|
||||
.popover.tour .popover-navigation *[data-role="end"] {
|
||||
float: right;
|
||||
}
|
||||
.popover.tour .popover-navigation *[data-role="next"], .popover.tour .popover-navigation *[data-role="end"] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.popover.fixed {
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
.tour-backdrop {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 1100;
|
||||
background-color: black;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
|
|
@ -450,32 +450,4 @@ $infobar_height: 20px
|
|||
|
||||
// }}}
|
||||
|
||||
/* ---- EDITOR TOUR ---- {{{ */
|
||||
|
||||
div.tour-backdrop
|
||||
z-index: 2009
|
||||
.popover.tour
|
||||
&.orphan .arrow
|
||||
display: none
|
||||
.popover-navigation
|
||||
padding: 9px 14px
|
||||
*[data-role="end"]
|
||||
float: right
|
||||
*[data-role="next"],*[data-role="end"]
|
||||
cursor: pointer
|
||||
.popover.fixed
|
||||
position: fixed
|
||||
.tour-backdrop
|
||||
position: fixed
|
||||
top: 0
|
||||
right: 0
|
||||
bottom: 0
|
||||
left: 0
|
||||
z-index: 1100
|
||||
background-color: #000
|
||||
opacity: 0.8
|
||||
|
||||
|
||||
// }}}
|
||||
|
||||
// vim:tabstop=4:shiftwidth=4:softtabstop=4:fdm=marker:
|
||||
|
|
|
@ -469,7 +469,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',
|
||||
|
@ -1516,7 +1516,7 @@
|
|||
url: this.link
|
||||
});
|
||||
this.media.renameNode("img");
|
||||
this.media.$.attributes.src = this.link;
|
||||
$(this.media).attr('src', this.link);
|
||||
return this._super();
|
||||
},
|
||||
clear: function () {
|
||||
|
@ -2000,6 +2000,11 @@
|
|||
// a/@href, ...)
|
||||
_(mutations).chain()
|
||||
.filter(function (m) {
|
||||
// ignore any SVG target, these blokes are like weird mon
|
||||
if (m.target && m.target instanceof SVGElement) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// ignore any change related to mundane image-edit-button
|
||||
if (m.target && m.target.className
|
||||
&& m.target.className.indexOf('image-edit-button') !== -1) {
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
if (!window.location.origin) { // fix for ie9
|
||||
window.location.origin = window.location.protocol + "//" + window.location.hostname + (window.location.port ? ':' + window.location.port: '');
|
||||
}
|
||||
document.getElementById("mobile-viewport").src = window.location.origin + window.location.pathname + "#mobile-preview";
|
||||
document.getElementById("mobile-viewport").src = window.location.origin + window.location.pathname + window.location.search + "#mobile-preview";
|
||||
this.$el.modal();
|
||||
},
|
||||
destroy: function () {
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
(function () {
|
||||
'use strict';
|
||||
|
||||
var website = openerp.website;
|
||||
var _t = openerp._t;
|
||||
|
||||
website.Tour.register({
|
||||
openerp.Tour.register({
|
||||
id: 'banner',
|
||||
name: _t("Build a page"),
|
||||
path: '/page/website.homepage',
|
||||
|
|
|
@ -1,535 +1,24 @@
|
|||
(function () {
|
||||
'use strict';
|
||||
|
||||
// raise an error in test mode if openerp don't exist
|
||||
if (typeof openerp === "undefined") {
|
||||
var error = "openerp is undefined"
|
||||
+ "\nhref: " + window.location.href
|
||||
+ "\nreferrer: " + document.referrer
|
||||
+ "\nlocalStorage: " + window.localStorage.getItem("tour");
|
||||
if (typeof $ !== "undefined") {
|
||||
error += '\n\n' + $("body").html();
|
||||
}
|
||||
throw new Error(error);
|
||||
}
|
||||
|
||||
var website = window.openerp.website;
|
||||
|
||||
// don't rewrite T in test mode
|
||||
if (typeof website.Tour !== "undefined") {
|
||||
return;
|
||||
}
|
||||
|
||||
// don't need template to use bootstrap Tour in automatic mode
|
||||
if (typeof QWeb2 !== "undefined") {
|
||||
website.add_template_file('/website/static/src/xml/website.tour.xml');
|
||||
}
|
||||
|
||||
if (website.EditorBar) {
|
||||
website.EditorBar.include({
|
||||
tours: [],
|
||||
start: function () {
|
||||
var self = this;
|
||||
var menu = $('#help-menu');
|
||||
_.each(T.tours, function (tour) {
|
||||
if (tour.mode === "test") {
|
||||
return;
|
||||
}
|
||||
var $menuItem = $($.parseHTML('<li><a href="#">'+tour.name+'</a></li>'));
|
||||
$menuItem.click(function () {
|
||||
T.reset();
|
||||
T.run(tour.id);
|
||||
});
|
||||
menu.append($menuItem);
|
||||
window.openerp.website.EditorBar.include({
|
||||
tours: [],
|
||||
start: function () {
|
||||
var self = this;
|
||||
var menu = $('#help-menu');
|
||||
_.each(window.openerp.Tour.tours, function (tour) {
|
||||
if (tour.mode === "test") {
|
||||
return;
|
||||
}
|
||||
var $menuItem = $($.parseHTML('<li><a href="#">'+tour.name+'</a></li>'));
|
||||
$menuItem.click(function () {
|
||||
T.reset();
|
||||
T.run(tour.id);
|
||||
});
|
||||
return this._super();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/////////////////////////////////////////////////
|
||||
|
||||
|
||||
/* jQuery selector to match exact text inside an element
|
||||
* :containsExact() - case insensitive
|
||||
* :containsExactCase() - case sensitive
|
||||
* :containsRegex() - set by user ( use: $(el).find(':containsRegex(/(red|blue|yellow)/gi)') )
|
||||
*/
|
||||
$.extend($.expr[':'],{
|
||||
containsExact: function(a,i,m){
|
||||
return $.trim(a.innerHTML.toLowerCase()) === m[3].toLowerCase();
|
||||
},
|
||||
containsExactCase: function(a,i,m){
|
||||
return $.trim(a.innerHTML) === m[3];
|
||||
},
|
||||
// Note all escaped characters need to be double escaped
|
||||
// inside of the containsRegex, so "\(" needs to be "\\("
|
||||
containsRegex: function(a,i,m){
|
||||
var regreg = /^\/((?:\\\/|[^\/])+)\/([mig]{0,3})$/,
|
||||
reg = regreg.exec(m[3]);
|
||||
return reg ? new RegExp(reg[1], reg[2]).test($.trim(a.innerHTML)) : false;
|
||||
menu.append($menuItem);
|
||||
});
|
||||
return this._super();
|
||||
}
|
||||
});
|
||||
$.ajaxSetup({
|
||||
beforeSend:function(){
|
||||
$.ajaxBusy = ($.ajaxBusy|0) + 1;
|
||||
},
|
||||
complete:function(){
|
||||
$.ajaxBusy--;
|
||||
}
|
||||
});
|
||||
|
||||
/////////////////////////////////////////////////
|
||||
|
||||
var localStorage = window.localStorage;
|
||||
|
||||
var T = website.Tour = {
|
||||
tours: {},
|
||||
defaultDelay: 50,
|
||||
retryRunningDelay: 1000,
|
||||
errorDelay: 5000,
|
||||
state: null,
|
||||
$element: null,
|
||||
timer: null,
|
||||
testtimer: null,
|
||||
currentTimer: null,
|
||||
register: function (tour) {
|
||||
if (tour.mode !== "test") tour.mode = "tutorial";
|
||||
T.tours[tour.id] = tour;
|
||||
},
|
||||
run: function (tour_id, mode) {
|
||||
var tour = T.tours[tour_id];
|
||||
this.time = new Date().getTime();
|
||||
if (tour.path && !window.location.href.match(new RegExp("("+T.getLang()+")?"+tour.path+"#?$", "i"))) {
|
||||
var href = "/"+T.getLang()+tour.path;
|
||||
console.log("Tour Begin from run method (redirection to "+href+")");
|
||||
T.saveState(tour.id, mode || tour.mode, -1, 0);
|
||||
window.location.href = href;
|
||||
} else {
|
||||
console.log("Tour Begin from run method");
|
||||
T.saveState(tour.id, mode || tour.mode, 0, 0);
|
||||
T.running();
|
||||
}
|
||||
},
|
||||
registerSteps: function (tour) {
|
||||
if (tour.register) {
|
||||
return;
|
||||
}
|
||||
tour.register = true;
|
||||
|
||||
for (var index=0, len=tour.steps.length; index<len; index++) {
|
||||
var step = tour.steps[index];
|
||||
step.id = index;
|
||||
|
||||
if (!step.waitNot && index > 0 && tour.steps[index-1] &&
|
||||
tour.steps[index-1].popover && tour.steps[index-1].popover.next) {
|
||||
step.waitNot = '.popover.tour.fade.in:visible';
|
||||
}
|
||||
if (!step.waitFor && index > 0 && tour.steps[index-1].snippet) {
|
||||
step.waitFor = '.oe_overlay_options .oe_options:visible';
|
||||
}
|
||||
|
||||
|
||||
var snippet = step.element && step.element.match(/#oe_snippets (.*) \.oe_snippet_thumbnail/);
|
||||
if (snippet) {
|
||||
step.snippet = snippet[1];
|
||||
} else if (step.snippet) {
|
||||
step.element = '#oe_snippets '+step.snippet+' .oe_snippet_thumbnail';
|
||||
}
|
||||
|
||||
if (!step.element) {
|
||||
step.element = "body";
|
||||
step.orphan = true;
|
||||
step.backdrop = true;
|
||||
}
|
||||
}
|
||||
if (tour.steps[index-1] &&
|
||||
tour.steps[index-1].popover && tour.steps[index-1].popover.next) {
|
||||
var step = {
|
||||
_title: "",
|
||||
id: index,
|
||||
waitNot: '.popover.tour.fade.in:visible'
|
||||
};
|
||||
tour.steps.push(step);
|
||||
}
|
||||
|
||||
// rendering bootstrap tour and popover
|
||||
if (tour.mode !== "test") {
|
||||
for (var index=0, len=tour.steps.length; index<len; index++) {
|
||||
var step = tour.steps[index];
|
||||
step._title = step._title || step.title;
|
||||
step.title = T.popoverTitle(tour, { title: step._title });
|
||||
step.template = step.template || T.popover( step.popover );
|
||||
}
|
||||
}
|
||||
},
|
||||
closePopover: function () {
|
||||
if (T.$element) {
|
||||
T.$element.popover('destroy');
|
||||
T.$element.removeData("tour");
|
||||
T.$element.removeData("tour-step");
|
||||
$(".tour-backdrop").remove();
|
||||
$(".popover.tour").remove();
|
||||
T.$element = null;
|
||||
}
|
||||
},
|
||||
autoTogglePopover: function () {
|
||||
var state = T.getState();
|
||||
var step = state.step;
|
||||
|
||||
if (T.$element &&
|
||||
T.$element.is(":visible") &&
|
||||
T.$element.data("tour") === state.id &&
|
||||
T.$element.data("tour-step") === step.id) {
|
||||
T.repositionPopover();
|
||||
return;
|
||||
}
|
||||
|
||||
if (step.busy) {
|
||||
return;
|
||||
}
|
||||
|
||||
T.closePopover();
|
||||
|
||||
var $element = $(step.element).first();
|
||||
if (!step.element || !$element.size() || !$element.is(":visible")) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
T.$element = $element;
|
||||
$element.data("tour", state.id);
|
||||
$element.data("tour-step", step.id);
|
||||
$element.popover({
|
||||
placement: step.placement || "auto",
|
||||
animation: true,
|
||||
trigger: "manual",
|
||||
title: step.title,
|
||||
content: step.content,
|
||||
html: true,
|
||||
container: "body",
|
||||
template: step.template,
|
||||
orphan: step.orphan
|
||||
}).popover("show");
|
||||
|
||||
|
||||
var $tip = $element.data("bs.popover").tip();
|
||||
|
||||
|
||||
// add popover style (orphan, static, backdrop)
|
||||
if (step.orphan) {
|
||||
$tip.addClass("orphan");
|
||||
}
|
||||
|
||||
var node = $element[0];
|
||||
var css;
|
||||
do {
|
||||
css = window.getComputedStyle(node);
|
||||
if (!css || css.position == "fixed") {
|
||||
$tip.addClass("fixed");
|
||||
break;
|
||||
}
|
||||
} while ((node = node.parentNode) && node !== document);
|
||||
|
||||
if (step.backdrop) {
|
||||
$("body").append('<div class="tour-backdrop"></div>');
|
||||
}
|
||||
|
||||
if (step.backdrop || $element.parents("#website-top-navbar, .modal").size()) {
|
||||
$tip.css("z-index", 2010);
|
||||
}
|
||||
|
||||
// button click event
|
||||
$tip.find("button")
|
||||
.one("click", function () {
|
||||
step.busy = true;
|
||||
if (!$(this).is("[data-role='next']")) {
|
||||
clearTimeout(T.timer);
|
||||
T.endTour();
|
||||
}
|
||||
T.closePopover();
|
||||
});
|
||||
|
||||
T.repositionPopover();
|
||||
},
|
||||
repositionPopover: function() {
|
||||
var popover = T.$element.data("bs.popover");
|
||||
var $tip = T.$element.data("bs.popover").tip();
|
||||
|
||||
if (popover.options.orphan) {
|
||||
return $tip.css("top", $(window).outerHeight() / 2 - $tip.outerHeight() / 2);
|
||||
}
|
||||
|
||||
var offsetBottom, offsetHeight, offsetRight, offsetWidth, originalLeft, originalTop, tipOffset;
|
||||
offsetWidth = $tip[0].offsetWidth;
|
||||
offsetHeight = $tip[0].offsetHeight;
|
||||
tipOffset = $tip.offset();
|
||||
originalLeft = tipOffset.left;
|
||||
originalTop = tipOffset.top;
|
||||
offsetBottom = $(document).outerHeight() - tipOffset.top - $tip.outerHeight();
|
||||
if (offsetBottom < 0) {
|
||||
tipOffset.top = tipOffset.top + offsetBottom;
|
||||
}
|
||||
offsetRight = $("html").outerWidth() - tipOffset.left - $tip.outerWidth();
|
||||
if (offsetRight < 0) {
|
||||
tipOffset.left = tipOffset.left + offsetRight;
|
||||
}
|
||||
if (tipOffset.top < 0) {
|
||||
tipOffset.top = 0;
|
||||
}
|
||||
if (tipOffset.left < 0) {
|
||||
tipOffset.left = 0;
|
||||
}
|
||||
$tip.offset(tipOffset);
|
||||
if (popover.options.placement === "bottom" || popover.options.placement === "top") {
|
||||
var left = T.$element.offset().left + T.$element.outerWidth()/2 - tipOffset.left;
|
||||
$tip.find(".arrow").css("left", left ? left + "px" : "");
|
||||
} else if (popover.options.placement !== "auto") {
|
||||
var top = T.$element.offset().top + T.$element.outerHeight()/2 - tipOffset.top;
|
||||
$tip.find(".arrow").css("top", top ? top + "px" : "");
|
||||
}
|
||||
},
|
||||
popoverTitle: function (tour, options) {
|
||||
return openerp.qweb ? openerp.qweb.render('website.tour_popover_title', options) : options.title;
|
||||
},
|
||||
popover: function (options) {
|
||||
return openerp.qweb ? openerp.qweb.render('website.tour_popover', options) : options.title;
|
||||
},
|
||||
getLang: function () {
|
||||
return $("html").attr("lang").replace(/-/, '_');
|
||||
},
|
||||
getState: function () {
|
||||
var state = JSON.parse(localStorage.getItem("tour") || 'false') || {};
|
||||
if (state) { this.time = state.time; }
|
||||
var tour_id,mode,step_id;
|
||||
if (!state.id && window.location.href.indexOf("#tutorial.") > -1) {
|
||||
state = {
|
||||
"id": window.location.href.match(/#tutorial\.(.*)=true/)[1],
|
||||
"mode": "tutorial",
|
||||
"step_id": 0
|
||||
};
|
||||
window.location.hash = "";
|
||||
console.log("Tour Begin from url hash");
|
||||
T.saveState(state.id, state.mode, state.step_id, 0);
|
||||
}
|
||||
if (!state.id) {
|
||||
return;
|
||||
}
|
||||
state.tour = T.tours[state.id];
|
||||
state.step = state.tour && state.tour.steps[state.step_id === -1 ? 0 : state.step_id];
|
||||
return state;
|
||||
},
|
||||
error: function (step, message) {
|
||||
var state = T.getState();
|
||||
message += '\n tour: ' + state.id
|
||||
+ '\n step: ' + step.id + ": '" + (step._title || step.title) + "'"
|
||||
+ '\n href: ' + window.location.href
|
||||
+ '\n referrer: ' + document.referrer
|
||||
+ '\n element: ' + Boolean(!step.element || ($(step.element).size() && $(step.element).is(":visible") && !$(step.element).is(":hidden")))
|
||||
+ '\n waitNot: ' + Boolean(!step.waitNot || !$(step.waitNot).size())
|
||||
+ '\n waitFor: ' + Boolean(!step.waitFor || $(step.waitFor).size())
|
||||
+ "\n localStorage: " + JSON.stringify(localStorage)
|
||||
+ '\n\n' + $("body").html();
|
||||
T.reset();
|
||||
throw new Error(message);
|
||||
},
|
||||
lists: function () {
|
||||
var tour_ids = [];
|
||||
for (var k in T.tours) {
|
||||
tour_ids.push(k);
|
||||
}
|
||||
return tour_ids;
|
||||
},
|
||||
saveState: function (tour_id, mode, step_id, number) {
|
||||
localStorage.setItem("tour", JSON.stringify({"id":tour_id, "mode":mode, "step_id":step_id || 0, "time": this.time, "number": number+1}));
|
||||
},
|
||||
reset: function () {
|
||||
var state = T.getState();
|
||||
if (state) {
|
||||
for (var k in state.tour.steps) {
|
||||
state.tour.steps[k].busy = false;
|
||||
}
|
||||
}
|
||||
localStorage.removeItem("tour");
|
||||
clearTimeout(T.timer);
|
||||
clearTimeout(T.testtimer);
|
||||
T.closePopover();
|
||||
},
|
||||
running: function () {
|
||||
function run () {
|
||||
var state = T.getState();
|
||||
if (!state) return;
|
||||
if (state.tour) {
|
||||
console.log("Tour '"+state.id+"' is running");
|
||||
T.registerSteps(state.tour);
|
||||
T.nextStep();
|
||||
} else {
|
||||
console.log("Tour '"+state.id+"' wait for running (tour undefined)");
|
||||
setTimeout(T.running, state.mode === "test" ? T.defaultDelay : T.retryRunningDelay);
|
||||
}
|
||||
}
|
||||
setTimeout(function () {
|
||||
if ($.ajaxBusy) {
|
||||
$(document).ajaxStop(run);
|
||||
} else {
|
||||
run();
|
||||
}
|
||||
},0);
|
||||
},
|
||||
check: function (step) {
|
||||
return (step &&
|
||||
(!step.element || ($(step.element).size() && $(step.element).is(":visible") && !$(step.element).is(":hidden"))) &&
|
||||
(!step.waitNot || !$(step.waitNot).size()) &&
|
||||
(!step.waitFor || $(step.waitFor).size()));
|
||||
},
|
||||
waitNextStep: function () {
|
||||
var state = T.getState();
|
||||
var time = new Date().getTime();
|
||||
var timer;
|
||||
var next = state.tour.steps[state.step.id+1];
|
||||
var overlaps = state.mode === "test" ? T.errorDelay : 0;
|
||||
|
||||
window.onbeforeunload = function () {
|
||||
clearTimeout(T.timer);
|
||||
clearTimeout(T.testtimer);
|
||||
};
|
||||
|
||||
function checkNext () {
|
||||
T.autoTogglePopover();
|
||||
|
||||
clearTimeout(T.timer);
|
||||
if (T.check(next)) {
|
||||
clearTimeout(T.currentTimer);
|
||||
// use an other timeout for cke dom loading
|
||||
T.saveState(state.id, state.mode, state.step.id, 0);
|
||||
setTimeout(function () {
|
||||
T.nextStep(next);
|
||||
}, T.defaultDelay);
|
||||
} else if (!overlaps || new Date().getTime() - time < overlaps) {
|
||||
T.timer = setTimeout(checkNext, T.defaultDelay);
|
||||
} else {
|
||||
T.error(next, "Can't reach the next step");
|
||||
}
|
||||
}
|
||||
checkNext();
|
||||
},
|
||||
nextStep: function (step) {
|
||||
var state = T.getState();
|
||||
|
||||
if (!state) {
|
||||
return;
|
||||
}
|
||||
|
||||
step = step || state.step;
|
||||
var next = state.tour.steps[step.id+1];
|
||||
|
||||
if (state.number > 3) {
|
||||
T.error(next, "Cycling. Can't reach the next step");
|
||||
}
|
||||
|
||||
T.saveState(state.id, state.mode, step.id, state.number);
|
||||
|
||||
if (step.id !== state.step_id) {
|
||||
console.log("Tour Step: '" + (step._title || step.title) + "' (" + (new Date().getTime() - this.time) + "ms)");
|
||||
}
|
||||
|
||||
T.autoTogglePopover(true);
|
||||
|
||||
if (step.onload) {
|
||||
step.onload();
|
||||
}
|
||||
|
||||
if (next) {
|
||||
setTimeout(function () {
|
||||
T.waitNextStep();
|
||||
if (state.mode === "test") {
|
||||
setTimeout(function(){
|
||||
T.autoNextStep(state.tour, step);
|
||||
}, T.defaultDelay);
|
||||
}
|
||||
}, next.wait || 0);
|
||||
} else {
|
||||
T.endTour();
|
||||
}
|
||||
},
|
||||
endTour: function () {
|
||||
var state = T.getState();
|
||||
var test = state.step.id >= state.tour.steps.length-1;
|
||||
T.reset();
|
||||
if (test) {
|
||||
console.log('ok');
|
||||
} else {
|
||||
console.log('error');
|
||||
}
|
||||
},
|
||||
autoNextStep: function (tour, step) {
|
||||
clearTimeout(T.testtimer);
|
||||
|
||||
function autoStep () {
|
||||
if (!step) return;
|
||||
|
||||
if (step.autoComplete) {
|
||||
step.autoComplete(tour);
|
||||
}
|
||||
|
||||
$(".popover.tour [data-role='next']").click();
|
||||
|
||||
var $element = $(step.element);
|
||||
if (!$element.size()) return;
|
||||
|
||||
if (step.snippet) {
|
||||
|
||||
T.autoDragAndDropSnippet($element);
|
||||
|
||||
} else if ($element.is(":visible")) {
|
||||
|
||||
$element.trigger($.Event("mouseenter", { srcElement: $element[0] }));
|
||||
$element.trigger($.Event("mousedown", { srcElement: $element[0] }));
|
||||
|
||||
var evt = document.createEvent("MouseEvents");
|
||||
evt.initMouseEvent("click", true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
|
||||
$element[0].dispatchEvent(evt);
|
||||
|
||||
// trigger after for step like: mouseenter, next step click on button display with mouseenter
|
||||
setTimeout(function () {
|
||||
$element.trigger($.Event("mouseup", { srcElement: $element[0] }));
|
||||
$element.trigger($.Event("mouseleave", { srcElement: $element[0] }));
|
||||
}, 1000);
|
||||
}
|
||||
if (step.sampleText) {
|
||||
|
||||
$element.trigger($.Event("keydown", { srcElement: $element }));
|
||||
if ($element.is("input") ) {
|
||||
$element.val(step.sampleText);
|
||||
} if ($element.is("select")) {
|
||||
$element.find("[value='"+step.sampleText+"'], option:contains('"+step.sampleText+"')").attr("selected", true);
|
||||
$element.val(step.sampleText);
|
||||
} else {
|
||||
$element.html(step.sampleText);
|
||||
}
|
||||
setTimeout(function () {
|
||||
$element.trigger($.Event("keyup", { srcElement: $element }));
|
||||
$element.trigger($.Event("change", { srcElement: $element }));
|
||||
}, self.defaultDelay<<1);
|
||||
|
||||
}
|
||||
}
|
||||
T.testtimer = setTimeout(autoStep, 100);
|
||||
},
|
||||
autoDragAndDropSnippet: function (selector) {
|
||||
var $thumbnail = $(selector).first();
|
||||
var thumbnailPosition = $thumbnail.position();
|
||||
$thumbnail.trigger($.Event("mousedown", { which: 1, pageX: thumbnailPosition.left, pageY: thumbnailPosition.top }));
|
||||
$thumbnail.trigger($.Event("mousemove", { which: 1, pageX: document.body.scrollWidth/2, pageY: document.body.scrollHeight/2 }));
|
||||
var $dropZone = $(".oe_drop_zone").first();
|
||||
var dropPosition = $dropZone.position();
|
||||
$dropZone.trigger($.Event("mouseup", { which: 1, pageX: dropPosition.left, pageY: dropPosition.top }));
|
||||
}
|
||||
};
|
||||
|
||||
//$(document).ready(T.running);
|
||||
website.ready().then(T.running);
|
||||
|
||||
|
||||
}());
|
||||
|
|
|
@ -8,6 +8,6 @@ class TestUi(openerp.tests.HttpCase):
|
|||
self.phantom_js("/", "console.log('ok')", "openerp.website.editor", login='admin')
|
||||
|
||||
def test_04_admin_tour_banner(self):
|
||||
self.phantom_js("/", "openerp.website.Tour.run('banner', 'test')", "openerp.website.Tour.tours.banner", login='admin')
|
||||
self.phantom_js("/", "openerp.Tour.run('banner', 'test')", "openerp.Tour.tours.banner", login='admin')
|
||||
|
||||
# vim:et:
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
(function () {
|
||||
'use strict';
|
||||
|
||||
var website = openerp.website;
|
||||
var _t = openerp._t;
|
||||
|
||||
website.Tour.register({
|
||||
openerp.Tour.register({
|
||||
id: 'blog',
|
||||
name: _t("Create a blog post"),
|
||||
steps: [
|
||||
|
|
|
@ -2,5 +2,5 @@ import openerp.tests
|
|||
|
||||
class TestUi(openerp.tests.HttpCase):
|
||||
def test_admin(self):
|
||||
self.phantom_js("/", "openerp.website.Tour.run('blog', 'test')", "openerp.website.Tour.tours.blog")
|
||||
self.phantom_js("/", "openerp.Tour.run('blog', 'test')", "openerp.Tour.tours.blog")
|
||||
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
(function () {
|
||||
'use strict';
|
||||
|
||||
var website = openerp.website;
|
||||
var _t = openerp._t;
|
||||
|
||||
website.Tour.register({
|
||||
openerp.Tour.register({
|
||||
id: 'event',
|
||||
name: _t("Create an event"),
|
||||
steps: [
|
||||
|
|
|
@ -2,5 +2,5 @@ import openerp.tests
|
|||
|
||||
class TestUi(openerp.tests.HttpCase):
|
||||
def test_admin(self):
|
||||
self.phantom_js("/", "openerp.website.Tour.run('event', 'test')", "openerp.website.Tour.tours.event")
|
||||
self.phantom_js("/", "openerp.Tour.run('event', 'test')", "openerp.Tour.tours.event")
|
||||
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
(function () {
|
||||
'use strict';
|
||||
|
||||
var website = openerp.website;
|
||||
|
||||
website.Tour.register({
|
||||
openerp.Tour.register({
|
||||
id: 'event_buy_tickets',
|
||||
name: "Try to buy tickets for event",
|
||||
path: '/event',
|
||||
|
|
|
@ -3,19 +3,19 @@ import os
|
|||
import openerp.tests
|
||||
|
||||
inject = [
|
||||
("openerp.website.Tour", os.path.join(os.path.dirname(__file__), '../../website/static/src/js/website.tour.js')),
|
||||
("openerp.website.Tour.ShopTest", os.path.join(os.path.dirname(__file__), "../static/src/js/website.tour.event_sale.js")),
|
||||
("openerp.Tour", os.path.join(os.path.dirname(__file__), '../../web/static/src/js/tour.js')),
|
||||
("openerp.Tour.ShopTest", os.path.join(os.path.dirname(__file__), "../static/src/js/website.tour.event_sale.js")),
|
||||
]
|
||||
|
||||
@openerp.tests.common.at_install(False)
|
||||
@openerp.tests.common.post_install(True)
|
||||
class TestUi(openerp.tests.HttpCase):
|
||||
def test_admin(self):
|
||||
self.phantom_js("/", "openerp.website.Tour.run('event_buy_tickets', 'test')", "openerp.website.Tour.tours.event_buy_tickets", inject=inject)
|
||||
self.phantom_js("/", "openerp.Tour.run('event_buy_tickets', 'test')", "openerp.Tour.tours.event_buy_tickets", inject=inject)
|
||||
|
||||
def test_demo(self):
|
||||
self.phantom_js("/", "openerp.website.Tour.run('event_buy_tickets', 'test')", "openerp.website.Tour.tours.event_buy_tickets", login="demo", password="demo", inject=inject);
|
||||
self.phantom_js("/", "openerp.Tour.run('event_buy_tickets', 'test')", "openerp.Tour.tours.event_buy_tickets", login="demo", password="demo", inject=inject);
|
||||
|
||||
def test_public(self):
|
||||
self.phantom_js("/", "openerp.website.Tour.run('event_buy_tickets', 'test')", "openerp.website.Tour.tours.event_buy_tickets", login=None, inject=inject);
|
||||
self.phantom_js("/", "openerp.Tour.run('event_buy_tickets', 'test')", "openerp.Tour.tours.event_buy_tickets", login=None, inject=inject);
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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.");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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');
|
||||
},
|
||||
});
|
||||
})();
|
|
@ -21,6 +21,7 @@
|
|||
|
||||
<template id="head" inherit_id="website.assets_frontend" name="Mail customization">
|
||||
<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'/>
|
||||
</xpath>
|
||||
</template>
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
import controllers
|
||||
import models
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
import datetime
|
||||
from dateutil import relativedelta
|
||||
|
||||
from openerp import tools
|
||||
from openerp import tools, SUPERUSER_ID
|
||||
from openerp.addons.web import http
|
||||
from openerp.addons.website.models.website import slug
|
||||
from openerp.addons.web.http import request
|
||||
|
@ -28,12 +29,23 @@ class MailGroup(http.Controller):
|
|||
def view(self, **post):
|
||||
cr, uid, context = request.cr, request.uid, request.context
|
||||
group_obj = request.registry.get('mail.group')
|
||||
mail_message_obj = request.registry.get('mail.message')
|
||||
group_ids = group_obj.search(cr, uid, [('alias_id', '!=', False), ('alias_id.alias_name', '!=', False)], context=context)
|
||||
values = {'groups': group_obj.browse(cr, uid, group_ids, context)}
|
||||
groups = group_obj.browse(cr, uid, group_ids, context)
|
||||
# compute statistics
|
||||
month_date = datetime.datetime.today() - relativedelta.relativedelta(months=1)
|
||||
group_data = dict.fromkeys(group_ids, dict())
|
||||
for group in groups:
|
||||
group_data[group.id]['monthly_message_nbr'] = mail_message_obj.search(
|
||||
cr, SUPERUSER_ID,
|
||||
[('model', '=', 'mail.group'), ('res_id', '=', group.id), ('date', '>=', month_date.strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT))],
|
||||
count=True, context=context)
|
||||
values = {'groups': groups, 'group_data': group_data}
|
||||
return request.website.render('website_mail_group.mail_groups', values)
|
||||
|
||||
@http.route(["/groups/subscription/"], type='json', auth="user")
|
||||
def subscription(self, group_id=0, action=False, **post):
|
||||
""" TDE FIXME: seems dead code """
|
||||
cr, uid, context = request.cr, request.uid, request.context
|
||||
group_obj = request.registry.get('mail.group')
|
||||
if action:
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
import mail_group
|
|
@ -0,0 +1,18 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from openerp.osv import osv
|
||||
|
||||
|
||||
class MailGroup(osv.Model):
|
||||
_inherit = 'mail.group'
|
||||
|
||||
def message_get_email_values(self, cr, uid, id, notif_mail=None, context=None):
|
||||
res = super(MailGroup, self).message_get_email_values(cr, uid, id, notif_mail=notif_mail, context=context)
|
||||
group = self.browse(cr, uid, id, context=context)
|
||||
base_url = self.pool['ir.config_parameter'].get_param(cr, uid, 'web.base.url')
|
||||
res['headers'].update({
|
||||
'List-Archive': '<%s/groups/%s>' % (base_url, group.id),
|
||||
'List-Subscribe': '<%s/groups>' % (base_url),
|
||||
'List-Unsubscribe': '<%s/groups>' % (base_url),
|
||||
})
|
||||
return res
|
|
@ -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();
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
<span class="oe_snippet_thumbnail_title">Discussion Group</span>
|
||||
</div>
|
||||
|
||||
<div class="oe_snippet_body js_follow"
|
||||
<div class="oe_snippet_body js_follow_alias"
|
||||
data-id="0"
|
||||
data-object="mail.group"
|
||||
data-follow="off">
|
||||
|
@ -38,7 +38,7 @@
|
|||
|
||||
<xpath expr="//div[@id='snippet_options']" position="inside">
|
||||
<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"
|
||||
>
|
||||
<li>
|
||||
|
|
|
@ -45,7 +45,7 @@
|
|||
</div>
|
||||
<div class="col-md-2">
|
||||
<i class='fa fa-user'/> <t t-esc="len(group.message_follower_ids)"/> participants<br />
|
||||
<i class='fa fa-envelope-o'/> <t t-esc="len(group.message_ids)"/> messages
|
||||
<i class='fa fa-envelope-o'/> <t t-raw="group_data[group.id]['monthly_message_nbr']"/> messages / month
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<t t-call="website_mail.follow"><t t-set="object" t-value="group"/></t>
|
||||
|
|
|
@ -62,8 +62,6 @@
|
|||
<script type="text/javascript" src="/website/static/src/js/website.menu.js"></script> <!-- groups="base.group_website_designer" -->
|
||||
<script type="text/javascript" src="/website/static/src/js/website.mobile.js"></script>
|
||||
<script type="text/javascript" src="/website/static/src/js/website.seo.js"></script>
|
||||
<script type="text/javascript" src="/website/static/src/js/website.tour.js"></script>
|
||||
<script type="text/javascript" src="/website/static/src/js/website.tour.banner.js"></script> <!-- groups="base.group_website_designer" -->
|
||||
<script type="text/javascript" src="/website/static/src/js/website.snippets.editor.js"></script>
|
||||
<script type="text/javascript" src="/website/static/src/js/website.ace.js"></script>
|
||||
<script type="text/javascript" src="/website/static/src/js/website.translator.js"></script>
|
||||
|
|
|
@ -63,7 +63,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))
|
||||
|
@ -179,7 +179,7 @@ class website_sale(http.Controller):
|
|||
|
||||
values = {
|
||||
'search': search,
|
||||
'category': category and int(category),
|
||||
'category': category,
|
||||
'attrib_values': attrib_values,
|
||||
'attrib_set': attrib_set,
|
||||
'pager': pager,
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
(function () {
|
||||
'use strict';
|
||||
|
||||
var website = openerp.website;
|
||||
|
||||
website.Tour.register({
|
||||
openerp.Tour.register({
|
||||
id: 'shop_customize',
|
||||
name: "Customize the page and search a product",
|
||||
path: '/shop',
|
||||
|
@ -79,7 +76,7 @@
|
|||
]
|
||||
});
|
||||
|
||||
website.Tour.register({
|
||||
openerp.Tour.register({
|
||||
id: 'shop_buy_product',
|
||||
name: "Try to buy products",
|
||||
path: '/shop',
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
(function () {
|
||||
'use strict';
|
||||
|
||||
var website = openerp.website;
|
||||
var _t = openerp._t;
|
||||
|
||||
website.Tour.register({
|
||||
openerp.Tour.register({
|
||||
id: 'shop',
|
||||
name: _t("Create a product"),
|
||||
steps: [
|
||||
|
|
|
@ -3,22 +3,30 @@ import os
|
|||
import openerp.tests
|
||||
|
||||
inject = [
|
||||
("openerp.website.Tour", os.path.join(os.path.dirname(__file__), '../../website/static/src/js/website.tour.js')),
|
||||
("openerp.website.Tour.ShopTest", os.path.join(os.path.dirname(__file__), "../static/src/js/website.tour.sale.js")),
|
||||
("openerp.Tour", os.path.join(os.path.dirname(__file__), '../../web/static/src/js/tour.js')),
|
||||
("openerp.Tour.ShopTest", os.path.join(os.path.dirname(__file__), "../static/src/js/website.tour.sale.js")),
|
||||
]
|
||||
|
||||
@openerp.tests.common.at_install(False)
|
||||
@openerp.tests.common.post_install(True)
|
||||
class TestUi(openerp.tests.HttpCase):
|
||||
def test_01_admin_shop_tour(self):
|
||||
<<<<<<< HEAD
|
||||
self.phantom_js("/", "openerp.website.Tour.run('shop', 'test')", "openerp.website.Tour.tours.shop", login="admin")
|
||||
self.phantom_js("/", "openerp.website.Tour.run('shop_customize', 'test')", "openerp.website.Tour.tours.shop_customize", login="admin", inject=inject)
|
||||
|
||||
def test_02_admin_checkout(self):
|
||||
self.phantom_js("/", "openerp.website.Tour.run('shop_buy_product', 'test')", "openerp.website.Tour.tours.shop_buy_product", login="admin", inject=inject)
|
||||
=======
|
||||
self.phantom_js("/", "openerp.Tour.run('shop', 'test')", "openerp.Tour.tours.shop", login="admin")
|
||||
|
||||
def test_02_admin_checkout(self):
|
||||
self.phantom_js("/", "openerp.Tour.run('shop_customize', 'test')", "openerp.Tour.tours.shop_customize", login="admin", inject=inject)
|
||||
self.phantom_js("/", "openerp.Tour.run('shop_buy_product', 'test')", "openerp.Tour.tours.shop_buy_product", login="admin", inject=inject)
|
||||
>>>>>>> remotes/odoo/master
|
||||
|
||||
def test_03_demo_checkout(self):
|
||||
self.phantom_js("/", "openerp.website.Tour.run('shop_buy_product', 'test')", "openerp.website.Tour.tours.shop_buy_product", login="demo", inject=inject)
|
||||
self.phantom_js("/", "openerp.Tour.run('shop_buy_product', 'test')", "openerp.Tour.tours.shop_buy_product", login="demo", inject=inject)
|
||||
|
||||
def test_04_public_checkout(self):
|
||||
self.phantom_js("/", "openerp.website.Tour.run('shop_buy_product', 'test')", "openerp.website.Tour.tours.shop_buy_product", inject=inject)
|
||||
self.phantom_js("/", "openerp.Tour.run('shop_buy_product', 'test')", "openerp.Tour.tours.shop_buy_product", inject=inject)
|
||||
|
|
|
@ -244,7 +244,7 @@
|
|||
<!-- Add to cart button-->
|
||||
|
||||
<template id="categories_recursive" name="Category list">
|
||||
<li t-att-class="'active' if c.id == category else ''">
|
||||
<li t-att-class="'active' if c.id == int(category or 0) else ''">
|
||||
<a t-att-href="keep('/shop/category/' + slug(c), category=0)" t-field="c.name"></a>
|
||||
<ul t-if="c.child_id" class="nav nav-pills nav-stacked nav-hierarchy">
|
||||
<t t-foreach="c.child_id" t-as="c">
|
||||
|
|
|
@ -491,6 +491,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
|
||||
|
@ -650,6 +652,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):
|
||||
|
@ -753,6 +759,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}
|
||||
|
||||
|
|
|
@ -349,8 +349,9 @@
|
|||
Check to attach the newly created record to the record on which the server action runs.
|
||||
</p>
|
||||
<group>
|
||||
<field name="model_name" invisible="1"/>
|
||||
<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)],
|
||||
'invisible': ['|', ('state', '!=', 'object_create'), ('link_new_record', '=', False)]}"/>
|
||||
</group>
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -3348,6 +3348,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
|
||||
|
|
|
@ -30,12 +30,11 @@ the ORM does, in fact.
|
|||
from contextlib import contextmanager
|
||||
from functools import wraps
|
||||
import logging
|
||||
import time
|
||||
import uuid
|
||||
import psycopg2.extras
|
||||
import psycopg2.extensions
|
||||
from psycopg2.extensions import ISOLATION_LEVEL_AUTOCOMMIT, ISOLATION_LEVEL_READ_COMMITTED, ISOLATION_LEVEL_REPEATABLE_READ
|
||||
from psycopg2.pool import PoolError
|
||||
from psycopg2.psycopg1 import cursor as psycopg1cursor
|
||||
|
||||
psycopg2.extensions.register_type(psycopg2.extensions.UNICODE)
|
||||
|
||||
|
@ -76,7 +75,7 @@ sql_counter = 0
|
|||
class Cursor(object):
|
||||
"""Represents an open transaction to the PostgreSQL DB backend,
|
||||
acting as a lightweight wrapper around psycopg2's
|
||||
``psycopg1cursor`` objects.
|
||||
``cursor`` objects.
|
||||
|
||||
``Cursor`` is the object behind the ``cr`` variable used all
|
||||
over the OpenERP code.
|
||||
|
@ -175,7 +174,7 @@ class Cursor(object):
|
|||
self._serialized = serialized
|
||||
|
||||
self._cnx = pool.borrow(dsn(dbname))
|
||||
self._obj = self._cnx.cursor(cursor_factory=psycopg1cursor)
|
||||
self._obj = self._cnx.cursor()
|
||||
if self.sql_log:
|
||||
self.__caller = frame_codeinfo(currentframe(),2)
|
||||
else:
|
||||
|
@ -188,6 +187,16 @@ class Cursor(object):
|
|||
|
||||
self.cache = {}
|
||||
|
||||
def __build_dict(self, row):
|
||||
return { d.name: row[i] for i, d in enumerate(self._obj.description) }
|
||||
def dictfetchone(self):
|
||||
row = self._obj.fetchone()
|
||||
return row and self.__build_dict(row)
|
||||
def dictfetchmany(self, size):
|
||||
return map(self.__build_dict, self._obj.fetchmany(size))
|
||||
def dictfetchall(self):
|
||||
return map(self.__build_dict, self._obj.fetchall())
|
||||
|
||||
def __del__(self):
|
||||
if not self._closed and not self._cnx.closed:
|
||||
# Oops. 'self' has not been closed explicitly.
|
||||
|
|
2
setup.py
2
setup.py
|
@ -95,6 +95,7 @@ def py2exe_options():
|
|||
"markupsafe", # dependence of jinja2 and mako
|
||||
"mock",
|
||||
"openerp",
|
||||
"passlib",
|
||||
"poplib",
|
||||
"psutil",
|
||||
"pychart",
|
||||
|
@ -163,6 +164,7 @@ setuptools.setup(
|
|||
'lxml', # windows binary http://www.lfd.uci.edu/~gohlke/pythonlibs/
|
||||
'mako',
|
||||
'mock',
|
||||
'passlib',
|
||||
'pillow', # windows binary http://www.lfd.uci.edu/~gohlke/pythonlibs/
|
||||
'psutil', # windows binary code.google.com/p/psutil/downloads/list
|
||||
'psycopg2 >= 2.2',
|
||||
|
|
|
@ -28,6 +28,7 @@ Depends:
|
|||
python-mako,
|
||||
python-mock,
|
||||
python-openid,
|
||||
python-passlib,
|
||||
python-psutil,
|
||||
python-psycopg2,
|
||||
python-pybabel,
|
||||
|
|
Loading…
Reference in New Issue