[MERGE] : with main
bzr revid: aja@tinyerp.com-20140131050641-myqx720j2gsnywn1
This commit is contained in:
commit
ccd4768cb6
|
@ -3187,11 +3187,14 @@ class wizard_multi_charts_accounts(osv.osv_memory):
|
|||
def _get_analytic_journal(journal_type):
|
||||
# Get the analytic journal
|
||||
data = False
|
||||
if journal_type in ('sale', 'sale_refund'):
|
||||
data = obj_data.get_object_reference(cr, uid, 'account', 'analytic_journal_sale')
|
||||
elif journal_type in ('purchase', 'purchase_refund'):
|
||||
pass
|
||||
elif journal_type == 'general':
|
||||
try:
|
||||
if journal_type in ('sale', 'sale_refund'):
|
||||
data = obj_data.get_object_reference(cr, uid, 'account', 'analytic_journal_sale')
|
||||
elif journal_type in ('purchase', 'purchase_refund'):
|
||||
data = obj_data.get_object_reference(cr, uid, 'account', 'exp')
|
||||
elif journal_type == 'general':
|
||||
pass
|
||||
except ValueError:
|
||||
pass
|
||||
return data and data[1] or False
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ import time
|
|||
|
||||
from openerp.osv import fields, osv
|
||||
from openerp.tools.translate import _
|
||||
from openerp.tools.float_utils import float_round
|
||||
import openerp.addons.decimal_precision as dp
|
||||
|
||||
class account_move_line_reconcile(osv.osv_memory):
|
||||
|
@ -64,7 +65,11 @@ class account_move_line_reconcile(osv.osv_memory):
|
|||
credit += line.credit
|
||||
debit += line.debit
|
||||
account_id = line.account_id.id
|
||||
return {'trans_nbr': count, 'account_id': account_id, 'credit': credit, 'debit': debit, 'writeoff': debit - credit}
|
||||
precision = self.pool['decimal.precision'].precision_get(cr, uid, 'Account')
|
||||
writeoff = float_round(debit-credit, precision_digits=precision)
|
||||
credit = float_round(credit, precision_digits=precision)
|
||||
debit = float_round(debit, precision_digits=precision)
|
||||
return {'trans_nbr': count, 'account_id': account_id, 'credit': credit, 'debit': debit, 'writeoff': writeoff}
|
||||
|
||||
def trans_rec_addendum_writeoff(self, cr, uid, ids, context=None):
|
||||
return self.pool.get('account.move.line.reconcile.writeoff').trans_rec_addendum(cr, uid, ids, context)
|
||||
|
|
|
@ -17,8 +17,8 @@
|
|||
</group>
|
||||
<footer>
|
||||
<button string="Reconcile" name="trans_rec_reconcile_full" type="object" default_focus="1" attrs="{'invisible':[('writeoff','!=',0)]}" class="oe_highlight"/>
|
||||
<button string="Reconcile With Write-Off" name="trans_rec_addendum_writeoff" type="object" attrs="{'invisible':[('writeoff','==',0)]}" class="oe_highlight"/>
|
||||
<button string="Partial Reconcile" name="trans_rec_reconcile_partial_reconcile" type="object" attrs="{'invisible':[('writeoff','==',0)]}" class="oe_highlight"/>
|
||||
<button string="Reconcile With Write-Off" name="trans_rec_addendum_writeoff" type="object" attrs="{'invisible':[('writeoff','=',0)]}" class="oe_highlight"/>
|
||||
<button string="Partial Reconcile" name="trans_rec_reconcile_partial_reconcile" type="object" attrs="{'invisible':[('writeoff','=',0)]}" class="oe_highlight"/>
|
||||
or
|
||||
<button string="Cancel" class="oe_link" special="cancel"/>
|
||||
</footer>
|
||||
|
|
|
@ -219,7 +219,7 @@
|
|||
<filter name="open" string="In Progress" domain="[('state','in',('open','draft'))]" help="Contracts in progress (open, draft)"/>
|
||||
<filter name="pending" string="To Renew" domain="[('state','=','pending')]" help="Pending contracts"/>
|
||||
<filter name="closed" string="Closed" domain="[('state','=','close')]" help="Closed contracts"/>
|
||||
<filter name="cancelled" string="Cancelled" domain="[('state','=','cancel')]" help="Cancelled contracts"/>
|
||||
<filter name="cancelled" string="Cancelled" domain="[('state','=','cancelled')]" help="Cancelled contracts"/>
|
||||
<separator/>
|
||||
<filter
|
||||
string="Expired or consumed"
|
||||
|
|
|
@ -35,9 +35,9 @@ class account_asset_category(osv.osv):
|
|||
'name': fields.char('Name', size=64, required=True, select=1),
|
||||
'note': fields.text('Note'),
|
||||
'account_analytic_id': fields.many2one('account.analytic.account', 'Analytic account'),
|
||||
'account_asset_id': fields.many2one('account.account', 'Asset Account', required=True),
|
||||
'account_depreciation_id': fields.many2one('account.account', 'Depreciation Account', required=True),
|
||||
'account_expense_depreciation_id': fields.many2one('account.account', 'Depr. Expense Account', required=True),
|
||||
'account_asset_id': fields.many2one('account.account', 'Asset Account', required=True, domain=[('type','=','other')]),
|
||||
'account_depreciation_id': fields.many2one('account.account', 'Depreciation Account', required=True, domain=[('type','=','other')]),
|
||||
'account_expense_depreciation_id': fields.many2one('account.account', 'Depr. Expense Account', required=True, domain=[('type','=','other')]),
|
||||
'journal_id': fields.many2one('account.journal', 'Journal', required=True),
|
||||
'company_id': fields.many2one('res.company', 'Company', required=True),
|
||||
'method': fields.selection([('linear','Linear'),('degressive','Degressive')], 'Computation Method', required=True, help="Choose the method to use to compute the amount of depreciation lines.\n"\
|
||||
|
|
|
@ -149,7 +149,7 @@
|
|||
<para style="terp_default_8">
|
||||
<font color="white"> </font>
|
||||
</para>
|
||||
<para style="terp_default_9_followup_id">[[ format(get_text(o,data['form']['followup_id'])) ]]</para>
|
||||
<pre style="terp_default_9_followup_id">[[ format(get_text(o,data['form']['followup_id'])) ]]</pre>
|
||||
<para style="terp_default_9">
|
||||
<font color="white"> </font>
|
||||
</para>
|
||||
|
|
|
@ -99,6 +99,9 @@ class CompanyLDAP(osv.osv):
|
|||
filter = filter_format(conf['ldap_filter'], (login,))
|
||||
try:
|
||||
results = self.query(conf, filter)
|
||||
|
||||
# Get rid of (None, attrs) for searchResultReference replies
|
||||
results = [i for i in results if i[0]]
|
||||
if results and len(results) == 1:
|
||||
dn = results[0][0]
|
||||
conn = self.connect(conf)
|
||||
|
|
|
@ -68,8 +68,6 @@ class OAuthLogin(openerp.addons.web.controllers.main.Home):
|
|||
|
||||
@http.route()
|
||||
def web_login(self, *args, **kw):
|
||||
http.ensure_db(with_registry=True)
|
||||
request.disable_db = False
|
||||
providers = self.list_providers()
|
||||
|
||||
response = super(OAuthLogin, self).web_login(*args, **kw)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import logging
|
||||
|
||||
import urllib
|
||||
import werkzeug.urls
|
||||
import urlparse
|
||||
import urllib2
|
||||
import simplejson
|
||||
|
@ -25,7 +25,7 @@ class res_users(osv.Model):
|
|||
]
|
||||
|
||||
def _auth_oauth_rpc(self, cr, uid, endpoint, access_token, context=None):
|
||||
params = urllib.urlencode({'access_token': access_token})
|
||||
params = werkzeug.url_encode({'access_token': access_token})
|
||||
if urlparse.urlparse(endpoint)[4]:
|
||||
url = endpoint + '&' + params
|
||||
else:
|
||||
|
|
|
@ -23,7 +23,6 @@ import logging
|
|||
import os
|
||||
import tempfile
|
||||
import getpass
|
||||
import urllib
|
||||
|
||||
import werkzeug.urls
|
||||
import werkzeug.exceptions
|
||||
|
@ -79,7 +78,7 @@ class GoogleAppsAwareConsumer(consumer.GenericConsumer):
|
|||
# update fields
|
||||
for attr in ['claimed_id', 'identity']:
|
||||
value = message.getArg(consumer.OPENID2_NS, attr, '')
|
||||
value = 'https://www.google.com/accounts/o8/user-xrds?uri=%s' % urllib.quote_plus(value)
|
||||
value = 'https://www.google.com/accounts/o8/user-xrds?uri=%s' % werkzeug.url_quote_plus(value)
|
||||
message.setArg(consumer.OPENID2_NS, attr, value)
|
||||
|
||||
# now, resign the message
|
||||
|
|
|
@ -34,8 +34,6 @@ class Home(openerp.addons.web.controllers.main.Home):
|
|||
|
||||
@http.route()
|
||||
def web_login(self, *args, **kw):
|
||||
http.ensure_db(with_registry=True)
|
||||
|
||||
mode = request.params.get('mode')
|
||||
qcontext = request.params.copy()
|
||||
response = webmain.render_bootstrap_template(request.session.db, 'auth_signup.signup', qcontext, lazy=True)
|
||||
|
|
|
@ -1330,13 +1330,13 @@ class calendar_event(osv.Model):
|
|||
new_id = get_real_ids(arg[2])
|
||||
new_arg = (arg[0], arg[1], new_id)
|
||||
new_args.append(new_arg)
|
||||
#offset, limit and count must be treated separately as we may need to deal with virtual ids
|
||||
|
||||
if context.get('virtual_id', True):
|
||||
res = super(calendar_event, self).search(cr, uid, new_args, offset=0, limit=0, order=None, context=context, count=False)
|
||||
res = self.get_recurrent_ids(cr, uid, res, args, order=order, context=context)
|
||||
else:
|
||||
res = super(calendar_event, self).search(cr, uid, new_args, offset=0, limit=0, order=order, context=context, count=False)
|
||||
if not context.get('virtual_id', True):
|
||||
return super(calendar_event, self).search(cr, uid, new_args, offset=offset, limit=limit, order=order, context=context, count=count)
|
||||
|
||||
# offset, limit, order and count must be treated separately as we may need to deal with virtual ids
|
||||
res = super(calendar_event, self).search(cr, uid, new_args, offset=0, limit=0, order=None, context=context, count=False)
|
||||
res = self.get_recurrent_ids(cr, uid, res, args, order=order, context=context)
|
||||
if count:
|
||||
return len(res)
|
||||
elif limit:
|
||||
|
|
|
@ -313,7 +313,7 @@
|
|||
<field name="name">Meetings</field>
|
||||
<field name="res_model">calendar.event</field>
|
||||
<field name="view_mode">form,calendar,tree,gantt</field>
|
||||
<field name="view_id" ref="action_view_calendar_event_form"/>
|
||||
<field name="view_id" ref="view_calendar_event_form"/>
|
||||
</record>
|
||||
|
||||
|
||||
|
|
|
@ -50,9 +50,9 @@ class decimal_precision(orm.Model):
|
|||
self.precision_get.clear_cache(self)
|
||||
for obj in self.pool.obj_list():
|
||||
for colname, col in self.pool.get(obj)._columns.items():
|
||||
if isinstance(col, (fields.float, fields.function)):
|
||||
if hasattr(col, 'digits_change'):
|
||||
col.digits_change(cr)
|
||||
RegistryManager.signal_registry_change(cr.dbname)
|
||||
RegistryManager.signal_caches_change(cr.dbname)
|
||||
|
||||
def create(self, cr, uid, data, context=None):
|
||||
res = super(decimal_precision, self).create(cr, uid, data, context=context)
|
||||
|
|
|
@ -110,11 +110,11 @@ class delivery_carrier(osv.osv):
|
|||
|
||||
# not using advanced pricing per destination: override grid
|
||||
grid_id = grid_pool.search(cr, uid, [('carrier_id', '=', record.id)], context=context)
|
||||
|
||||
if grid_id and not (record.normal_price or record.free_if_more_than):
|
||||
grid_pool.unlink(cr, uid, grid_id, context=context)
|
||||
|
||||
if not (record.normal_price or record.free_if_more_than):
|
||||
# Check that float, else 0.0 is False
|
||||
if not (isinstance(record.normal_price,float) or record.free_if_more_than):
|
||||
continue
|
||||
|
||||
if not grid_id:
|
||||
|
@ -141,7 +141,7 @@ class delivery_carrier(osv.osv):
|
|||
'list_price': 0.0,
|
||||
}
|
||||
grid_line_pool.create(cr, uid, line_data, context=context)
|
||||
if record.normal_price:
|
||||
if isinstance(record.normal_price,float):
|
||||
line_data = {
|
||||
'grid_id': grid_id and grid_id[0],
|
||||
'name': _('Default price'),
|
||||
|
@ -192,7 +192,7 @@ class delivery_grid(osv.osv):
|
|||
weight = 0
|
||||
volume = 0
|
||||
for line in order.order_line:
|
||||
if not line.product_id:
|
||||
if not line.product_id or line.is_delivery:
|
||||
continue
|
||||
total += line.price_subtotal or 0.0
|
||||
weight += (line.product_id.weight or 0.0) * line.product_uom_qty
|
||||
|
@ -205,9 +205,8 @@ class delivery_grid(osv.osv):
|
|||
grid = self.browse(cr, uid, id, context=context)
|
||||
price = 0.0
|
||||
ok = False
|
||||
|
||||
price_dict = {'price': total, 'volume':volume, 'weight': weight, 'wv':volume*weight}
|
||||
for line in grid.line_ids:
|
||||
price_dict = {'price': total, 'volume':volume, 'weight': weight, 'wv':volume*weight}
|
||||
test = eval(line.type+line.operator+str(line.max_value), price_dict)
|
||||
if test:
|
||||
if line.price_type=='variable':
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
<field name="normal_price">20</field>
|
||||
<field name="partner_id" ref="res_partner_23"/>
|
||||
<field name="product_id" ref="product_product_delivery"/>
|
||||
<field name="use_detailed_pricelist" eval="True"/>
|
||||
</record>
|
||||
|
||||
<record id="free_delivery_carrier" model="delivery.carrier">
|
||||
|
|
|
@ -262,6 +262,7 @@ class abstracted_fs(object):
|
|||
if path == '/' and mode in ('list', 'cwd'):
|
||||
return (None, None, None )
|
||||
|
||||
if path == '..': path = self.cwd + '/..'
|
||||
path = _to_unicode(os.path.normpath(path)) # again, for '/db/../ss'
|
||||
if path == '.': path = ''
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import simplejson
|
||||
import urllib
|
||||
import werkzeug.urls
|
||||
|
||||
import openerp
|
||||
import openerp.addons.web.controllers.main as webmain
|
||||
|
@ -17,7 +17,7 @@ class EDI(openerp.http.Controller):
|
|||
css = "\n ".join('<link rel="stylesheet" href="%s">' % i for i in webmain.manifest_list(req, modules_str, 'css'))
|
||||
|
||||
# `url` may contain a full URL with a valid query string, we basically want to watch out for XML brackets and double-quotes
|
||||
safe_url = urllib.quote_plus(url,':/?&;=')
|
||||
safe_url = werkzeug.url_quote_plus(url,':/?&;=')
|
||||
|
||||
return webmain.html_template % {
|
||||
'js': js,
|
||||
|
|
|
@ -417,8 +417,8 @@ class email_template(osv.osv):
|
|||
|
||||
# create a mail_mail based on values, without attachments
|
||||
values = self.generate_email(cr, uid, template_id, res_id, context=context)
|
||||
assert values.get('email_from'), 'email_from is missing or empty after template rendering, send_mail() cannot proceed'
|
||||
|
||||
if not values.get('email_from'):
|
||||
raise osv.except_osv(_('Warning!'),_("Sender email is missing or empty after template rendering. Specify one to deliver your message"))
|
||||
# process partner_to field that is a comma separated list of partner_ids -> recipient_ids
|
||||
# NOTE: only usable if force_send is True, because otherwise the value is
|
||||
# not stored on the mail_mail, and therefore lost -> fixed in v8
|
||||
|
|
|
@ -23,5 +23,3 @@ import event
|
|||
import wizard
|
||||
import report
|
||||
import res_partner
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
||||
|
|
|
@ -160,6 +160,8 @@ class event_event(osv.osv):
|
|||
'email_confirmation_id' : fields.many2one('email.template','Event Confirmation Email', help="If you set an email template, each participant will receive this email announcing the confirmation of the event."),
|
||||
'reply_to': fields.char('Reply-To Email', size=64, readonly=False, states={'done': [('readonly', True)]}, help="The email address of the organizer is likely to be put here, with the effect to be in the 'Reply-To' of the mails sent automatically at event or registrations confirmation. You can also put the email address of your mail gateway if you use one."),
|
||||
'address_id': fields.many2one('res.partner','Location', readonly=False, states={'done': [('readonly', True)]}),
|
||||
'country_id': fields.related('address_id', 'country_id',
|
||||
type='many2one', relation='res.country', string='Country', readonly=False, states={'done': [('readonly', True)]}, store=True),
|
||||
'description': fields.html(
|
||||
'Description', readonly=False,
|
||||
states={'done': [('readonly', True)]},
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
from openerp.osv import fields, osv
|
||||
from openerp.tools.translate import _
|
||||
|
||||
class product(osv.osv):
|
||||
class product_template(osv.osv):
|
||||
_inherit = 'product.template'
|
||||
_columns = {
|
||||
'event_ok': fields.boolean('Event Subscription', help='Determine if a product needs to create automatically an event registration at the confirmation of a sales order line.'),
|
||||
|
@ -30,7 +30,18 @@ class product(osv.osv):
|
|||
}
|
||||
|
||||
def onchange_event_ok(self, cr, uid, ids, type, event_ok, context=None):
|
||||
return {'value': {'type': event_ok and 'service' or type != 'service' and type or False}}
|
||||
if event_ok:
|
||||
return {'value': {'type': 'service'}}
|
||||
return {}
|
||||
|
||||
class product(osv.osv):
|
||||
_inherit = 'product.product'
|
||||
|
||||
def onchange_event_ok(self, cr, uid, ids, type, event_ok, context=None):
|
||||
# cannot directly forward to product.template as the ids are theoretically different
|
||||
if event_ok:
|
||||
return {'value': {'type': 'service'}}
|
||||
return {}
|
||||
|
||||
|
||||
class sale_order_line(osv.osv):
|
||||
|
|
|
@ -1,22 +1,36 @@
|
|||
<?xml version="1.0"?>
|
||||
<openerp>
|
||||
<data>
|
||||
<record model="ir.ui.view" id="event_sale_order">
|
||||
<field name="name">event.product</field>
|
||||
<field name="model">product.template</field>
|
||||
<record model="ir.ui.view" id="event_sale_product_form">
|
||||
<field name="model">product.product</field>
|
||||
<field name="inherit_id" ref="product.product_normal_form_view" />
|
||||
<field name="arch" type="xml">
|
||||
<div name="options" position="inside">
|
||||
<field name="event_ok" on_change="onchange_event_ok(type, event_ok, context)"/>
|
||||
<label for="event_ok"
|
||||
attrs="{'readonly': [('is_only_child', '=', False)]}"/>
|
||||
<field name="event_ok" on_change="onchange_event_ok(type, event_ok, context)"
|
||||
attrs="{'readonly': [('is_only_child', '=', False)]}"/>
|
||||
<label for="event_ok"/>
|
||||
</div>
|
||||
<div name='ean' position="after">
|
||||
<field name="event_type_id" attrs="{'readonly': ['|', ('event_ok', '=', False), ('is_only_child', '=', False)]}"/>
|
||||
<field name="event_type_id" attrs="{'invisible': [('event_ok', '=', False)],
|
||||
'readonly': [('is_only_child', '=', False)]}"/>
|
||||
</div>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="event_sale_product_template_form">
|
||||
<field name="model">product.template</field>
|
||||
<field name="inherit_id" ref="product.product_template_form_view" />
|
||||
<field name="arch" type="xml">
|
||||
<div name="options" position="inside">
|
||||
<field name="event_ok" on_change="onchange_event_ok(type, event_ok, context)"/>
|
||||
<label for="event_ok"/>
|
||||
</div>
|
||||
<field name='company_id' position="after">
|
||||
<field name="event_type_id" attrs="{'invisible': [('event_ok', '=', False)]}"/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="event_order_line">
|
||||
<field name="name">event.sale.order</field>
|
||||
<field name="model">sale.order</field>
|
||||
|
|
|
@ -24,7 +24,7 @@ from openerp import SUPERUSER_ID
|
|||
from openerp.tools.translate import _
|
||||
from openerp.addons.web.http import request
|
||||
|
||||
import urllib
|
||||
import werkzeug.urls
|
||||
import urllib2
|
||||
import simplejson
|
||||
|
||||
|
@ -43,7 +43,7 @@ class google_service(osv.osv_memory):
|
|||
#Get the Refresh Token From Google And store it in ir.config_parameter
|
||||
headers = {"Content-type": "application/x-www-form-urlencoded"}
|
||||
data = dict(code=authorization_code, client_id=client_id, client_secret=client_secret, redirect_uri=redirect_uri, grant_type="authorization_code")
|
||||
data = urllib.urlencode(data)
|
||||
data = werkzeug.url_encode(data)
|
||||
try:
|
||||
req = urllib2.Request("https://accounts.google.com/o/oauth2/token", data, headers)
|
||||
content = urllib2.urlopen(req).read()
|
||||
|
@ -62,7 +62,7 @@ class google_service(osv.osv_memory):
|
|||
'response_type': 'code',
|
||||
'client_id': ir_config.get_param(cr, SUPERUSER_ID, 'google_%s_client_id' % service),
|
||||
}
|
||||
uri = 'https://accounts.google.com/o/oauth2/auth?%s' % urllib.urlencode(params)
|
||||
uri = 'https://accounts.google.com/o/oauth2/auth?%s' % werkzeug.url_encode(params)
|
||||
return uri
|
||||
|
||||
#If no scope is passed, we use service by default to get a default scope
|
||||
|
@ -83,7 +83,7 @@ class google_service(osv.osv_memory):
|
|||
'access_type':'offline'
|
||||
}
|
||||
|
||||
uri = self.get_uri_oauth(a='auth') + "?%s" % urllib.urlencode(params)
|
||||
uri = self.get_uri_oauth(a='auth') + "?%s" % werkzeug.url_encode(params)
|
||||
return uri
|
||||
|
||||
def _get_google_token_json(self, cr, uid, authorize_code, service, context=None):
|
||||
|
@ -103,7 +103,7 @@ class google_service(osv.osv_memory):
|
|||
headers = {"content-type": "application/x-www-form-urlencoded"}
|
||||
|
||||
try:
|
||||
data = urllib.urlencode(params)
|
||||
data = werkzeug.url_encode(params)
|
||||
req = urllib2.Request(self.get_uri_oauth(a='token'), data, headers)
|
||||
|
||||
content = urllib2.urlopen(req).read()
|
||||
|
@ -128,7 +128,7 @@ class google_service(osv.osv_memory):
|
|||
headers = {"content-type": "application/x-www-form-urlencoded"}
|
||||
|
||||
try:
|
||||
data = urllib.urlencode(params)
|
||||
data = werkzeug.url_encode(params)
|
||||
req = urllib2.Request(self.get_uri_oauth(a='token'), data, headers)
|
||||
content = urllib2.urlopen(req).read()
|
||||
res = simplejson.loads(content)
|
||||
|
@ -139,12 +139,12 @@ class google_service(osv.osv_memory):
|
|||
|
||||
|
||||
def _do_request(self,cr,uid,uri,params={},headers={},type='POST', context=None):
|
||||
_logger.debug("Uri: %s - Type : %s - Headers: %s - Params : %s !" % (uri,type,headers,urllib.urlencode(params) if type =='GET' else params))
|
||||
_logger.debug("Uri: %s - Type : %s - Headers: %s - Params : %s !" % (uri,type,headers,werkzeug.url_encode(params) if type =='GET' else params))
|
||||
res = False
|
||||
|
||||
try:
|
||||
if type.upper() == 'GET' or type.upper() == 'DELETE':
|
||||
data = urllib.urlencode(params)
|
||||
data = werkzeug.url_encode(params)
|
||||
req = urllib2.Request(self.get_uri_api() + uri + "?" + data)
|
||||
elif type.upper() == 'POST' or type.upper() == 'PATCH' or type.upper() == 'PUT':
|
||||
req = urllib2.Request(self.get_uri_api() + uri, params, headers)
|
||||
|
|
|
@ -23,7 +23,7 @@ from openerp import SUPERUSER_ID
|
|||
from openerp.osv import fields, osv
|
||||
from openerp.tools.translate import _
|
||||
|
||||
import urllib
|
||||
import werkzeug.urls
|
||||
import urllib2
|
||||
import json
|
||||
import re
|
||||
|
@ -70,7 +70,7 @@ class config(osv.Model):
|
|||
google_drive_client_secret = ir_config.get_param(cr, SUPERUSER_ID, 'google_drive_client_secret')
|
||||
#For Getting New Access Token With help of old Refresh Token
|
||||
|
||||
data = urllib.urlencode(dict(client_id=google_drive_client_id,
|
||||
data = werkzeug.url_encode(dict(client_id=google_drive_client_id,
|
||||
refresh_token=google_drive_refresh_token,
|
||||
client_secret=google_drive_client_secret,
|
||||
grant_type="refresh_token",
|
||||
|
|
|
@ -23,7 +23,7 @@ import simplejson
|
|||
import logging
|
||||
from lxml import etree
|
||||
import re
|
||||
import urllib
|
||||
import werkzeug.urls
|
||||
import urllib2
|
||||
|
||||
from openerp.osv import osv
|
||||
|
@ -89,7 +89,7 @@ class config(osv.osv):
|
|||
|
||||
try:
|
||||
req = urllib2.Request(
|
||||
'https://spreadsheets.google.com/feeds/cells/%s/od6/private/full/batch?%s' % (spreadsheet_key, urllib.urlencode({'v': 3, 'access_token': access_token})),
|
||||
'https://spreadsheets.google.com/feeds/cells/%s/od6/private/full/batch?%s' % (spreadsheet_key, werkzeug.url_encode({'v': 3, 'access_token': access_token})),
|
||||
data=request,
|
||||
headers={'content-type': 'application/atom+xml', 'If-Match': '*'})
|
||||
urllib2.urlopen(req)
|
||||
|
|
|
@ -23,7 +23,7 @@ from datetime import datetime
|
|||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
from openerp.addons.hr_holidays.tests.common import TestHrHolidaysBase
|
||||
from openerp.exceptions import Warning, AccessError
|
||||
from openerp.exceptions import AccessError
|
||||
from openerp.osv.orm import except_orm
|
||||
from openerp.tools import mute_logger
|
||||
|
||||
|
@ -204,5 +204,5 @@ class TestHolidaysFlow(TestHrHolidaysBase):
|
|||
'date_to': (datetime.today() + relativedelta(days=7)),
|
||||
'number_of_days_temp': 4,
|
||||
})
|
||||
with self.assertRaises(Warning):
|
||||
with self.assertRaises(except_orm):
|
||||
self.hr_holidays.signal_confirm(cr, self.user_hrmanager_id, [hol2_id])
|
||||
|
|
|
@ -21,6 +21,7 @@ except ImportError:
|
|||
try:
|
||||
from .. import escpos
|
||||
from ..escpos import printer
|
||||
from ..escpos import supported_devices
|
||||
except ImportError:
|
||||
escpos = printer = None
|
||||
|
||||
|
@ -39,21 +40,16 @@ class EscposDriver(Thread):
|
|||
self.queue = Queue()
|
||||
self.status = {'status':'connecting', 'messages':[]}
|
||||
|
||||
self.supported_printers = [
|
||||
{ 'vendor' : 0x04b8, 'product' : 0x0e03, 'name' : 'Epson TM-T20' },
|
||||
{ 'vendor' : 0x04b8, 'product' : 0x0202, 'name' : 'Epson TM-T70' },
|
||||
]
|
||||
|
||||
def connected_usb_devices(self,devices):
|
||||
def connected_usb_devices(self):
|
||||
connected = []
|
||||
for device in devices:
|
||||
for device in supported_devices.device_list:
|
||||
if usb.core.find(idVendor=device['vendor'], idProduct=device['product']) != None:
|
||||
connected.append(device)
|
||||
return connected
|
||||
|
||||
def get_escpos_printer(self):
|
||||
try:
|
||||
printers = self.connected_usb_devices(self.supported_printers)
|
||||
printers = self.connected_usb_devices()
|
||||
if len(printers) > 0:
|
||||
self.set_status('connected','Connected to '+printers[0]['name'])
|
||||
return escpos.printer.Usb(printers[0]['vendor'], printers[0]['product'])
|
||||
|
@ -267,12 +263,12 @@ hw_proxy.drivers['escpos'] = driver
|
|||
|
||||
class EscposProxy(hw_proxy.Proxy):
|
||||
|
||||
@http.route('/hw_proxy/open_cashbox', type='json', auth='admin')
|
||||
@http.route('/hw_proxy/open_cashbox', type='json', auth='none', cors='*')
|
||||
def open_cashbox(self):
|
||||
_logger.info('ESC/POS: OPEN CASHBOX')
|
||||
driver.push_task('cashbox')
|
||||
|
||||
@http.route('/hw_proxy/print_receipt', type='json', auth='admin')
|
||||
@http.route('/hw_proxy/print_receipt', type='json', auth='none', cors='*')
|
||||
def print_receipt(self, receipt):
|
||||
_logger.info('ESC/POS: PRINT RECEIPT')
|
||||
driver.push_task('receipt',receipt)
|
||||
|
|
|
@ -1 +1 @@
|
|||
__all__ = ["constants","escpos","exceptions","printer"]
|
||||
__all__ = ["constants","escpos","exceptions","printer","supported_devices"]
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
#!/usr/bin/python
|
||||
|
||||
# This is a list of esc/pos compatible usb printers. The vendor and product ids can be found by
|
||||
# typing lsusb in a linux terminal, this will give you the ids in the form ID VENDOR:PRODUCT
|
||||
|
||||
device_list = [
|
||||
{ 'vendor' : 0x04b8, 'product' : 0x0e03, 'name' : 'Epson TM-T20' },
|
||||
{ 'vendor' : 0x04b8, 'product' : 0x0202, 'name' : 'Epson TM-T70' },
|
||||
]
|
||||
|
|
@ -33,20 +33,15 @@ class Proxy(http.Controller):
|
|||
statuses[driver] = drivers[driver].get_status()
|
||||
return statuses
|
||||
|
||||
@http.route('/hw_proxy/hello', type='http', auth='admin')
|
||||
@http.route('/hw_proxy/hello', type='http', auth='none', cors='*')
|
||||
def hello(self):
|
||||
return request.make_response('ping', {
|
||||
'Cache-Control': 'no-cache',
|
||||
'Content-Type': 'text/html; charset=utf-8',
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Methods': 'GET',
|
||||
})
|
||||
return "ping"
|
||||
|
||||
@http.route('/hw_proxy/handshake', type='json', auth='admin')
|
||||
@http.route('/hw_proxy/handshake', type='json', auth='none', cors='*')
|
||||
def handshake(self):
|
||||
return True
|
||||
|
||||
@http.route('/hw_proxy/status', type='http', auth='admin')
|
||||
@http.route('/hw_proxy/status', type='http', auth='none', cors='*')
|
||||
def status_http(self):
|
||||
resp = '<html>\n<body>\n<h1>Hardware Proxy Status</h1>\n'
|
||||
statuses = self.get_status()
|
||||
|
@ -75,39 +70,39 @@ class Proxy(http.Controller):
|
|||
'Access-Control-Allow-Methods': 'GET',
|
||||
})
|
||||
|
||||
@http.route('/hw_proxy/status_json', type='json', auth='admin')
|
||||
@http.route('/hw_proxy/status_json', type='json', auth='none', cors='*')
|
||||
def status_json(self):
|
||||
return self.get_status()
|
||||
|
||||
@http.route('/hw_proxy/scan_item_success', type='json', auth='admin')
|
||||
@http.route('/hw_proxy/scan_item_success', type='json', auth='none', cors='*')
|
||||
def scan_item_success(self, ean):
|
||||
"""
|
||||
A product has been scanned with success
|
||||
"""
|
||||
print 'scan_item_success: ' + str(ean)
|
||||
|
||||
@http.route('/hw_proxy/scan_item_error_unrecognized', type='json', auth='admin')
|
||||
@http.route('/hw_proxy/scan_item_error_unrecognized', type='json', auth='none', cors='*')
|
||||
def scan_item_error_unrecognized(self, ean):
|
||||
"""
|
||||
A product has been scanned without success
|
||||
"""
|
||||
print 'scan_item_error_unrecognized: ' + str(ean)
|
||||
|
||||
@http.route('/hw_proxy/help_needed', type='json', auth='admin')
|
||||
@http.route('/hw_proxy/help_needed', type='json', auth='none', cors='*')
|
||||
def help_needed(self):
|
||||
"""
|
||||
The user wants an help (ex: light is on)
|
||||
"""
|
||||
print "help_needed"
|
||||
|
||||
@http.route('/hw_proxy/help_canceled', type='json', auth='admin')
|
||||
@http.route('/hw_proxy/help_canceled', type='json', auth='none', cors='*')
|
||||
def help_canceled(self):
|
||||
"""
|
||||
The user stops the help request
|
||||
"""
|
||||
print "help_canceled"
|
||||
|
||||
@http.route('/hw_proxy/weighting_start', type='json', auth='admin')
|
||||
@http.route('/hw_proxy/weighting_start', type='json', auth='none', cors='*')
|
||||
def weighting_start(self):
|
||||
if self.scale == 'closed':
|
||||
print "Opening (Fake) Connection to Scale..."
|
||||
|
@ -118,7 +113,7 @@ class Proxy(http.Controller):
|
|||
else:
|
||||
print "WARNING: Scale already Connected !!!"
|
||||
|
||||
@http.route('/hw_proxy/weighting_read_kg', type='json', auth='admin')
|
||||
@http.route('/hw_proxy/weighting_read_kg', type='json', auth='none', cors='*')
|
||||
def weighting_read_kg(self):
|
||||
if self.scale == 'open':
|
||||
print "Reading Scale..."
|
||||
|
@ -130,7 +125,7 @@ class Proxy(http.Controller):
|
|||
print "WARNING: Reading closed scale !!!"
|
||||
return 0.0
|
||||
|
||||
@http.route('/hw_proxy/weighting_end', type='json', auth='admin')
|
||||
@http.route('/hw_proxy/weighting_end', type='json', auth='none', cors='*')
|
||||
def weighting_end(self):
|
||||
if self.scale == 'open':
|
||||
print "Closing Connection to Scale ..."
|
||||
|
@ -141,7 +136,7 @@ class Proxy(http.Controller):
|
|||
else:
|
||||
print "WARNING: Scale already Closed !!!"
|
||||
|
||||
@http.route('/hw_proxy/payment_request', type='json', auth='admin')
|
||||
@http.route('/hw_proxy/payment_request', type='json', auth='none', cors='*')
|
||||
def payment_request(self, price):
|
||||
"""
|
||||
The PoS will activate the method payment
|
||||
|
@ -149,55 +144,55 @@ class Proxy(http.Controller):
|
|||
print "payment_request: price:"+str(price)
|
||||
return 'ok'
|
||||
|
||||
@http.route('/hw_proxy/payment_status', type='json', auth='admin')
|
||||
@http.route('/hw_proxy/payment_status', type='json', auth='none', cors='*')
|
||||
def payment_status(self):
|
||||
print "payment_status"
|
||||
return { 'status':'waiting' }
|
||||
|
||||
@http.route('/hw_proxy/payment_cancel', type='json', auth='admin')
|
||||
@http.route('/hw_proxy/payment_cancel', type='json', auth='none', cors='*')
|
||||
def payment_cancel(self):
|
||||
print "payment_cancel"
|
||||
|
||||
@http.route('/hw_proxy/transaction_start', type='json', auth='admin')
|
||||
@http.route('/hw_proxy/transaction_start', type='json', auth='none', cors='*')
|
||||
def transaction_start(self):
|
||||
print 'transaction_start'
|
||||
|
||||
@http.route('/hw_proxy/transaction_end', type='json', auth='admin')
|
||||
@http.route('/hw_proxy/transaction_end', type='json', auth='none', cors='*')
|
||||
def transaction_end(self):
|
||||
print 'transaction_end'
|
||||
|
||||
@http.route('/hw_proxy/cashier_mode_activated', type='json', auth='admin')
|
||||
@http.route('/hw_proxy/cashier_mode_activated', type='json', auth='none', cors='*')
|
||||
def cashier_mode_activated(self):
|
||||
print 'cashier_mode_activated'
|
||||
|
||||
@http.route('/hw_proxy/cashier_mode_deactivated', type='json', auth='admin')
|
||||
@http.route('/hw_proxy/cashier_mode_deactivated', type='json', auth='none', cors='*')
|
||||
def cashier_mode_deactivated(self):
|
||||
print 'cashier_mode_deactivated'
|
||||
|
||||
@http.route('/hw_proxy/open_cashbox', type='json', auth='admin')
|
||||
@http.route('/hw_proxy/open_cashbox', type='json', auth='none', cors='*')
|
||||
def open_cashbox(self):
|
||||
print 'open_cashbox'
|
||||
|
||||
@http.route('/hw_proxy/print_receipt', type='json', auth='admin')
|
||||
@http.route('/hw_proxy/print_receipt', type='json', auth='none', cors='*')
|
||||
def print_receipt(self, receipt):
|
||||
print 'print_receipt' + str(receipt)
|
||||
|
||||
@http.route('/hw_proxy/is_scanner_connected', type='json', auth='admin')
|
||||
@http.route('/hw_proxy/is_scanner_connected', type='json', auth='none', cors='*')
|
||||
def print_receipt(self, receipt):
|
||||
print 'is_scanner_connected?'
|
||||
return False
|
||||
|
||||
@http.route('/hw_proxy/scanner', type='json', auth='admin')
|
||||
@http.route('/hw_proxy/scanner', type='json', auth='none', cors='*')
|
||||
def print_receipt(self, receipt):
|
||||
print 'scanner'
|
||||
time.sleep(10)
|
||||
return ''
|
||||
|
||||
@http.route('/hw_proxy/log', type='json', auth='admin')
|
||||
@http.route('/hw_proxy/log', type='json', auth='none', cors='*')
|
||||
def log(self, arguments):
|
||||
_logger.info(' '.join(str(v) for v in arguments))
|
||||
|
||||
@http.route('/hw_proxy/print_pdf_invoice', type='json', auth='admin')
|
||||
@http.route('/hw_proxy/print_pdf_invoice', type='json', auth='none', cors='*')
|
||||
def print_pdf_invoice(self, pdfinvoice):
|
||||
print 'print_pdf_invoice' + str(pdfinvoice)
|
||||
|
||||
|
|
|
@ -109,8 +109,8 @@ class Scanner(Thread):
|
|||
if not evdev:
|
||||
return None
|
||||
devices = [ device for device in listdir(self.input_dir)]
|
||||
keyboards = [ device for device in devices if 'kbd' in device ]
|
||||
scanners = [ device for device in devices if ('barcode' in device.lower()) or ('scanner' in device.lower()) ]
|
||||
keyboards = [ device for device in devices if ('kbd' in device) and ('keyboard' not in device.lower())]
|
||||
scanners = [ device for device in devices if ('barcode' in device.lower()) or ('scanner' in device.lower())]
|
||||
if len(scanners) > 0:
|
||||
self.set_status('connected','Connected to '+scanners[0])
|
||||
return evdev.InputDevice(join(self.input_dir,scanners[0]))
|
||||
|
@ -124,7 +124,7 @@ class Scanner(Thread):
|
|||
self.set_status('error',str(e))
|
||||
return None
|
||||
|
||||
@http.route('/hw_proxy/Vis_scanner_connected', type='json', auth='admin')
|
||||
@http.route('/hw_proxy/Vis_scanner_connected', type='json', auth='none', cors='*')
|
||||
def is_scanner_connected(self):
|
||||
return self.get_device() != None
|
||||
|
||||
|
@ -144,6 +144,8 @@ class Scanner(Thread):
|
|||
return ''
|
||||
|
||||
def get_status(self):
|
||||
if not s.isAlive():
|
||||
s.start()
|
||||
return self.status
|
||||
|
||||
def run(self):
|
||||
|
@ -205,7 +207,7 @@ s = Scanner()
|
|||
hw_proxy.drivers['scanner'] = s
|
||||
|
||||
class ScannerDriver(hw_proxy.Proxy):
|
||||
@http.route('/hw_proxy/scanner', type='json', auth='admin')
|
||||
@http.route('/hw_proxy/scanner', type='json', auth='none', cors='*')
|
||||
def scanner(self):
|
||||
if not s.isAlive():
|
||||
s.start()
|
||||
|
|
|
@ -185,8 +185,8 @@ class im_message(osv.osv):
|
|||
def post(self, cr, uid, message, to_session_id, technical=False, uuid=None, context=None):
|
||||
assert_uuid(uuid)
|
||||
my_id = self.pool.get('im.user').get_my_id(cr, uid, uuid)
|
||||
session = self.pool.get('im.session').browse(cr, uid, to_session_id, context)
|
||||
to_ids = [x.id for x in session.user_ids if x.id != my_id]
|
||||
session_user_ids = self.pool.get('im.session').get_session_users(cr, uid, to_session_id, context=context).get("user_ids", [])
|
||||
to_ids = [user_id for user_id in session_user_ids if user_id != my_id]
|
||||
self.create(cr, openerp.SUPERUSER_ID, {"message": message, 'from_id': my_id,
|
||||
'to_id': [(6, 0, to_ids)], 'session_id': to_session_id, 'technical': technical}, context=context)
|
||||
notify_channel(cr, "im_channel", {'type': 'message', 'receivers': [my_id] + to_ids})
|
||||
|
@ -202,7 +202,7 @@ class im_session(osv.osv):
|
|||
return res
|
||||
|
||||
_columns = {
|
||||
'user_ids': fields.many2many('im.user'),
|
||||
'user_ids': fields.many2many('im.user', 'im_session_im_user_rel', 'im_session_id', 'im_user_id', 'Users'),
|
||||
"name": fields.function(_calc_name, string="Name", type='char'),
|
||||
}
|
||||
|
||||
|
@ -225,6 +225,9 @@ class im_session(osv.osv):
|
|||
}, context=context)
|
||||
return self.read(cr, uid, session_id, context=context)
|
||||
|
||||
def get_session_users(self, cr, uid, session_id, context=None):
|
||||
return self.read(cr, openerp.SUPERUSER_ID, session_id, ['user_ids'], context=context)
|
||||
|
||||
def add_to_session(self, cr, uid, session_id, user_id, uuid=None, context=None):
|
||||
my_id = self.pool.get("im.user").get_my_id(cr, uid, uuid, context=context)
|
||||
session = self.read(cr, uid, session_id, context=context)
|
||||
|
@ -259,7 +262,7 @@ class im_user(osv.osv):
|
|||
return ['&', ('im_last_status', '=', True), ('im_last_status_update', '>', (current - delta).strftime(DEFAULT_SERVER_DATETIME_FORMAT))]
|
||||
else:
|
||||
return ['|', ('im_last_status', '=', False), ('im_last_status_update', '<=', (current - delta).strftime(DEFAULT_SERVER_DATETIME_FORMAT))]
|
||||
|
||||
# TODO: Remove fields arg in trunk. Also in im.js.
|
||||
def search_users(self, cr, uid, text_search, fields, limit, context=None):
|
||||
my_id = self.get_my_id(cr, uid, None, context)
|
||||
group_employee = self.pool['ir.model.data'].get_object_reference(cr, uid, 'base', 'group_user')[1]
|
||||
|
@ -271,7 +274,7 @@ class im_user(osv.osv):
|
|||
if len(found) < limit:
|
||||
found += self.search(cr, uid, [["name", "ilike", text_search], ["id", "<>", my_id], ["uuid", "=", False], ["im_status", "=", False], ["id", "not in", found]],
|
||||
order="name asc", limit=limit-len(found), context=context)
|
||||
users = self.read(cr, uid, found, fields, context=context)
|
||||
users = self.read(cr,openerp.SUPERUSER_ID, found, ["name", "user_id", "uuid", "im_status"], context=context)
|
||||
users.sort(key=lambda obj: found.index(obj['id']))
|
||||
return users
|
||||
|
||||
|
@ -319,6 +322,9 @@ class im_user(osv.osv):
|
|||
continue
|
||||
return res
|
||||
|
||||
def get_users(self, cr, uid, ids, context=None):
|
||||
return self.read(cr,openerp.SUPERUSER_ID, ids, ["name", "im_status", "uuid"], context=context)
|
||||
|
||||
_columns = {
|
||||
'name': fields.function(_get_name, type='char', size=200, string="Name", store=True, readonly=True),
|
||||
'assigned_name': fields.char(string="Assigned Name", size=200, required=False),
|
||||
|
@ -341,3 +347,16 @@ class im_user(osv.osv):
|
|||
('user_uniq', 'unique (user_id)', 'Only one chat user per OpenERP user.'),
|
||||
('uuid_uniq', 'unique (uuid)', 'Chat identifier already used.'),
|
||||
]
|
||||
|
||||
class res_users(osv.osv):
|
||||
_inherit = "res.users"
|
||||
|
||||
def _get_im_user(self, cr, uid, ids, field_name, arg, context=None):
|
||||
result = dict.fromkeys(ids, False)
|
||||
for index, im_user in enumerate(self.pool['im.user'].search_read(cr, uid, domain=[('user_id', 'in', ids)], fields=['name', 'user_id'], context=context)):
|
||||
result[ids[index]] = im_user.get('user_id') and (im_user['user_id'][0], im_user['name']) or False
|
||||
return result
|
||||
|
||||
_columns = {
|
||||
'im_user_id' : fields.function(_get_im_user, type='many2one', string="IM User", relation="im.user"),
|
||||
}
|
||||
|
|
|
@ -2,10 +2,10 @@
|
|||
<openerp>
|
||||
<data>
|
||||
<record id="message_rule_1" model="ir.rule">
|
||||
<field name="name">Can only read messages that you sent or messages sent to you</field>
|
||||
<field name="name">Can only read messages from a session where user is</field>
|
||||
<field name="model_id" ref="model_im_message"/>
|
||||
<field name="groups" eval="[(6,0,[ref('base.group_user')])]"/>
|
||||
<field name="domain_force">["|", ('to_id.user_id', 'in', [user.id]), ('from_id.user_id', '=', user.id)]</field>
|
||||
<field name="domain_force">[('session_id.user_ids', 'in', user.im_user_id.id)]</field>
|
||||
<field name="perm_read" eval="1"/>
|
||||
<field name="perm_write" eval="0"/>
|
||||
<field name="perm_create" eval="0"/>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_im_message,im.message,model_im_message,base.group_user,1,0,1,0
|
||||
access_im_message,im.message,model_im_message,,1,0,1,0
|
||||
access_im_user,im.user,model_im_user,,1,1,1,0
|
||||
access_im_session,im.session,model_im_session,,1,0,0,0
|
|
|
@ -92,6 +92,7 @@
|
|||
search_changed: function(e) {
|
||||
var users = new instance.web.Model("im.user");
|
||||
var self = this;
|
||||
// TODO: Remove fields arg in trunk. Also in im.js.
|
||||
return this.user_search_dm.add(users.call("search_users", [this.get("current_search"), ["name", "user_id", "uuid", "im_status"],
|
||||
USERS_LIMIT], {context:new instance.web.CompoundContext()})).then(function(users) {
|
||||
var logged_users = _.filter(users, function(u) { return !!u.im_status; });
|
||||
|
|
|
@ -145,7 +145,7 @@ function declare($, _, openerp) {
|
|||
if (_.size(no_cache) === 0)
|
||||
def = $.when();
|
||||
else
|
||||
def = im_common.connection.model("im.user").call("read", [_.values(no_cache), []]).then(function(users) {
|
||||
def = im_common.connection.model("im.user").call("get_users", [_.values(no_cache)]).then(function(users) {
|
||||
self.add_to_user_cache(users);
|
||||
});
|
||||
return def.then(function() {
|
||||
|
@ -230,7 +230,8 @@ function declare($, _, openerp) {
|
|||
return self.chat_with_users(users);
|
||||
});
|
||||
},
|
||||
activate_session: function(session_id, focus) {
|
||||
activate_session: function(session_id, focus, message) {
|
||||
var self = this;
|
||||
var conv = _.find(this.conversations, function(conv) {return conv.session_id == session_id;});
|
||||
var def = $.when();
|
||||
if (! conv) {
|
||||
|
@ -244,6 +245,9 @@ function declare($, _, openerp) {
|
|||
this.calc_positions();
|
||||
this.trigger("new_conversation", conv);
|
||||
}, this));
|
||||
def = def.then(function(){
|
||||
return self.load_history(conv, message);
|
||||
});
|
||||
}
|
||||
if (focus) {
|
||||
def = def.then(function() {
|
||||
|
@ -252,13 +256,32 @@ function declare($, _, openerp) {
|
|||
}
|
||||
return def.then(function() {return conv});
|
||||
},
|
||||
received_messages: function(messages) {
|
||||
load_history: function(conv, message){
|
||||
var self = this;
|
||||
var domain = [["session_id", "=", conv.session_id]];
|
||||
if (!_.isUndefined(message)){
|
||||
domain.push(["date", "<", message.date]);
|
||||
}
|
||||
return im_common.connection.model("im.message").call("search_read", [domain, [], 0, 10]).then(function(messages){
|
||||
messages.reverse();
|
||||
var users = _.unique(_.map(messages, function(message){
|
||||
return message.from_id[0];
|
||||
}));
|
||||
return self.ensure_users(users).then(function(){
|
||||
return self.received_messages(messages, true);
|
||||
});
|
||||
});
|
||||
},
|
||||
received_messages: function(messages, seen) {
|
||||
var self = this;
|
||||
var defs = [];
|
||||
var received = false;
|
||||
if (_.isUndefined(seen)){
|
||||
seen = false;
|
||||
}
|
||||
_.each(messages, function(message) {
|
||||
if (! message.technical) {
|
||||
defs.push(self.activate_session(message.session_id[0]).then(function(conv) {
|
||||
defs.push(self.activate_session(message.session_id[0], false, message).then(function(conv) {
|
||||
received = self.my_id !== message.from_id[0];
|
||||
return conv.received_message(message);
|
||||
}));
|
||||
|
@ -269,7 +292,7 @@ function declare($, _, openerp) {
|
|||
}
|
||||
});
|
||||
return $.when.apply($, defs).then(function(){
|
||||
if (! self.get("window_focus") && received) {
|
||||
if (! self.get("window_focus") && received && !seen) {
|
||||
self.set("waiting_messages", self.get("waiting_messages") + messages.length);
|
||||
self.ting.play();
|
||||
self.create_ting();
|
||||
|
@ -368,7 +391,7 @@ function declare($, _, openerp) {
|
|||
refresh_users: function() {
|
||||
var self = this;
|
||||
var user_ids;
|
||||
return im_common.connection.model("im.session").call("read", [self.session_id]).then(function(session) {
|
||||
return im_common.connection.model("im.session").call("get_session_users", [self.session_id]).then(function(session) {
|
||||
user_ids = _.without(session.user_ids, self.c_manager.me.get("id"));
|
||||
return self.c_manager.ensure_users(user_ids);
|
||||
}).then(function(users) {
|
||||
|
@ -449,7 +472,7 @@ function declare($, _, openerp) {
|
|||
date = "" + zpad(date.getHours(), 2) + ":" + zpad(date.getMinutes(), 2);
|
||||
var to_show = _.map(items, im_common.escape_keep_url);
|
||||
this.last_bubble = $(openerp.qweb.render("im_common.conversation_bubble", {"items": to_show, "user": user, "time": date}));
|
||||
$(this.$(".oe_im_chatview_content").children()[0]).append(this.last_bubble);
|
||||
$(this.$(".oe_im_chatview_conversation")).append(this.last_bubble);
|
||||
this._go_bottom();
|
||||
},
|
||||
_go_bottom: function() {
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
All users are offline. They will receive your messages on their next connection.
|
||||
</div>
|
||||
<div class="oe_im_chatview_content">
|
||||
<div></div>
|
||||
<div class="oe_im_chatview_conversation"></div>
|
||||
</div>
|
||||
<div class="oe_im_chatview_footer">
|
||||
<input class="oe_im_chatview_input" t-att-placeholder="widget.inputPlaceholder" />
|
||||
|
|
|
@ -171,8 +171,8 @@ class im_livechat_channel(osv.osv):
|
|||
return users
|
||||
|
||||
def get_session(self, cr, uid, channel_id, uuid, context=None):
|
||||
my_id = self.pool.get("im.user").get_my_id(cr, uid, uuid, context=context)
|
||||
users = self.get_available_users(cr, uid, channel_id, context=context)
|
||||
self.pool.get("im.user").get_my_id(cr, uid, uuid, context=context)
|
||||
users = self.get_available_users(cr, openerp.SUPERUSER_ID, channel_id, context=context)
|
||||
if len(users) == 0:
|
||||
return False
|
||||
user_id = random.choice(users).id
|
||||
|
|
|
@ -125,6 +125,7 @@
|
|||
<field name="res_model">im.message</field>
|
||||
<field name="view_mode">list</field>
|
||||
<field name="domain">[('session_id.channel_id', '!=', None)]</field>
|
||||
<field name="context">{'search_default_group_by_session_id': 1, 'search_default_group_by_date': 1, 'search_default_session_id': 1}</field>
|
||||
</record>
|
||||
<menuitem name="History" parent="im_livechat" id="history" action="action_history" groups="group_im_livechat_manager"/>
|
||||
|
||||
|
@ -141,5 +142,21 @@
|
|||
</field>
|
||||
</record>
|
||||
|
||||
<record id="im_message_search" model="ir.ui.view">
|
||||
<field name="name">im.message.search</field>
|
||||
<field name="model">im.message</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Search history">
|
||||
<filter name="session_id" string="My Sessions" domain="[('session_id.user_ids','in', uid)]"/>
|
||||
<field name="from_id"/>
|
||||
<field name="to_id"/>
|
||||
<group expand="0" string="Group By...">
|
||||
<filter name="group_by_session_id" string="Session" domain="[]" context="{'group_by':'session_id'}"/>
|
||||
<filter name="group_by_date" string="Date" domain="[]" context="{'group_by':'date'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
|
|
|
@ -2,7 +2,5 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
|||
access_ls_chann1,im_livechat.channel,model_im_livechat_channel,,1,0,0,0
|
||||
access_ls_chann2,im_livechat.channel,model_im_livechat_channel,group_im_livechat,1,1,1,0
|
||||
access_ls_chann3,im_livechat.channel,model_im_livechat_channel,group_im_livechat_manager,1,1,1,1
|
||||
access_ls_message_portal,im_livechat.im.message.portal,im.model_im_message,base.group_portal,0,0,0,0
|
||||
access_im_user_portal,im_livechat.im.user.portal,im.model_im_user,base.group_portal,1,0,0,0
|
||||
access_ls_message,im_livechat.im.message,im.model_im_message,base.group_public,0,0,0,0
|
||||
access_im_user,im_livechat.im.user,im.model_im_user,base.group_public,1,0,0,0
|
|
|
@ -107,11 +107,14 @@ define(["openerp", "im_common", "underscore", "require", "jquery",
|
|||
}
|
||||
self.manager.activate_session(session_id, true).then(function(conv) {
|
||||
if (self.options.defaultMessage) {
|
||||
conv.received_message({
|
||||
message: self.options.defaultMessage,
|
||||
date: openerp.datetime_to_str(new Date()),
|
||||
from_id: [conv.get("users")[0].get("id"), "Unknown"]
|
||||
});
|
||||
setTimeout(function(){
|
||||
conv.received_message({
|
||||
message: self.options.defaultMessage,
|
||||
date: openerp.datetime_to_str(new Date()),
|
||||
from_id: [conv.get("users")[0].get("id"), "Unknown"]
|
||||
});
|
||||
},
|
||||
2500);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -83,7 +83,7 @@ class res_users(osv.Model):
|
|||
|
||||
def copy_data(self, *args, **kwargs):
|
||||
data = super(res_users, self).copy_data(*args, **kwargs)
|
||||
if data.get('alias_name'):
|
||||
if data and data.get('alias_name'):
|
||||
data['alias_name'] = data['login']
|
||||
return data
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import datetime
|
||||
import logging
|
||||
import sys
|
||||
import urllib
|
||||
import werkzeug.urls
|
||||
import urllib2
|
||||
|
||||
import openerp
|
||||
|
@ -59,7 +59,7 @@ def get_sys_logs(self, cr, uid):
|
|||
|
||||
add_arg = {"timeout":30} if sys.version_info >= (2,6) else {}
|
||||
arguments = {'arg0': msg, "action": "update",}
|
||||
arguments_raw = urllib.urlencode(arguments)
|
||||
arguments_raw = werkzeug.url_encode(arguments)
|
||||
|
||||
url = config.get("publisher_warranty_url")
|
||||
|
||||
|
|
|
@ -322,9 +322,9 @@ class mail_compose_message(osv.TransientModel):
|
|||
mail_values['email_from'] = email_dict.pop('email_from')
|
||||
# replies redirection: mass mailing only
|
||||
if not wizard.same_thread:
|
||||
mail_values['reply_to'] = email_dict.pop('reply_to')
|
||||
mail_values['reply_to'] = email_dict.pop('reply_to', None)
|
||||
else:
|
||||
email_dict.pop('reply_to')
|
||||
email_dict.pop('reply_to', None)
|
||||
mail_values.update(email_dict)
|
||||
# mass mailing without post: mail_mail values
|
||||
if mass_mail_mode and not wizard.post:
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
|
||||
from openerp import http
|
||||
from openerp import http, SUPERUSER_ID
|
||||
from openerp.http import request
|
||||
|
||||
class MassMailController(http.Controller):
|
||||
@http.route('/mail/track/<int:mail_id>/blank.gif', type='http', auth='admin')
|
||||
@http.route('/mail/track/<int:mail_id>/blank.gif', type='http', auth='none')
|
||||
def track_mail_open(self, mail_id):
|
||||
""" Email tracking. """
|
||||
mail_mail_stats = request.registry.get('mail.mail.statistics')
|
||||
mail_mail_stats.set_opened(request.cr, request.uid, mail_mail_ids=[mail_id])
|
||||
mail_mail_stats.set_opened(request.cr, SUPERUSER_ID, mail_mail_ids=[mail_id])
|
||||
return "data:image/gif;base64,R0lGODlhAQABAIAAANvf7wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw=="
|
||||
|
|
|
@ -320,10 +320,9 @@ class mrp_bom(osv.osv):
|
|||
"""
|
||||
routing_obj = self.pool.get('mrp.routing')
|
||||
factor = factor / (bom.product_efficiency or 1.0)
|
||||
max_rounding = max(bom.product_rounding, bom.product_uom.rounding)
|
||||
factor = rounding(factor, max_rounding)
|
||||
if factor < max_rounding:
|
||||
factor = max_rounding
|
||||
factor = rounding(factor, bom.product_rounding)
|
||||
if factor < bom.product_rounding:
|
||||
factor = bom.product_rounding
|
||||
result = []
|
||||
result2 = []
|
||||
phantom = False
|
||||
|
@ -772,7 +771,7 @@ class mrp_production(osv.osv):
|
|||
# qty available for consume and produce
|
||||
qty_avail = scheduled.product_qty - consumed_data.get(scheduled.product_id.id, 0.0)
|
||||
|
||||
if qty_avail <= 0.0:
|
||||
if float_compare(qty_avail, 0, precision_rounding=scheduled.product_id.uom_id.rounding) <= 0:
|
||||
# there will be nothing to consume for this raw material
|
||||
continue
|
||||
|
||||
|
@ -784,7 +783,7 @@ class mrp_production(osv.osv):
|
|||
# if qtys we have to consume is more than qtys available to consume
|
||||
prod_name = scheduled.product_id.name_get()[0][1]
|
||||
raise osv.except_osv(_('Warning!'), _('You are going to consume total %s quantities of "%s".\nBut you can only consume up to total %s quantities.') % (qty, prod_name, qty_avail))
|
||||
if qty <= 0.0:
|
||||
if float_compare(qty, 0, precision_rounding=scheduled.product_id.uom_id.rounding) <= 0:
|
||||
# we already have more qtys consumed than we need
|
||||
continue
|
||||
|
||||
|
|
|
@ -764,7 +764,7 @@
|
|||
<field name="workcenter_id"/>
|
||||
<field name="sequence"/>
|
||||
<field name="cycle"/>
|
||||
<field name="hour"/>
|
||||
<field name="hour" widget="float_time"/>
|
||||
</group>
|
||||
</form>
|
||||
<tree string="Production Work Centers">
|
||||
|
@ -772,7 +772,7 @@
|
|||
<field name="name"/>
|
||||
<field name="workcenter_id"/>
|
||||
<field name="cycle"/>
|
||||
<field name="hour"/>
|
||||
<field name="hour" widget="float_time"/>
|
||||
</tree>
|
||||
</field>
|
||||
</page>
|
||||
|
|
|
@ -51,7 +51,7 @@
|
|||
<field name="name"/>
|
||||
<field name="workcenter_id" widget="selection"/>
|
||||
<field name="cycle"/>
|
||||
<field name="hour"/>
|
||||
<field name="hour" widget="float_time"/>
|
||||
<field name="state" />
|
||||
</tree>
|
||||
</field>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
"""Module to talk to EtherpadLite API."""
|
||||
|
||||
import json
|
||||
import urllib
|
||||
import werkzeug.urls
|
||||
import urllib2
|
||||
|
||||
|
||||
|
@ -32,7 +32,7 @@ class EtherpadLiteClient:
|
|||
|
||||
params = arguments or {}
|
||||
params.update({'apikey': self.apiKey})
|
||||
data = urllib.urlencode(params, True)
|
||||
data = werkzeug.url_encode(params, True)
|
||||
|
||||
try:
|
||||
opener = urllib2.build_opener()
|
||||
|
|
|
@ -8,8 +8,8 @@ import logging
|
|||
import pprint
|
||||
import werkzeug
|
||||
|
||||
from openerp.addons.web import http
|
||||
from openerp.addons.web.http import request
|
||||
from openerp import http, SUPERUSER_ID
|
||||
from openerp.http import request
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
@ -19,12 +19,12 @@ class AdyenController(http.Controller):
|
|||
|
||||
@http.route([
|
||||
'/payment/adyen/return/',
|
||||
], type='http', auth='admin')
|
||||
], type='http', auth='none')
|
||||
def adyen_return(self, pspReference, **post):
|
||||
""" Paypal IPN."""
|
||||
post["pspReference"] = pspReference
|
||||
_logger.info('Beginning Adyen form_feedback with post data %s', pprint.pformat(post)) # debug
|
||||
request.registry['payment.transaction'].form_feedback(request.cr, request.uid, post, 'adyen', context=request.context)
|
||||
request.registry['payment.transaction'].form_feedback(request.cr, SUPERUSER_ID, post, 'adyen', context=request.context)
|
||||
return_url = post.pop('return_url', '')
|
||||
if not return_url:
|
||||
custom = json.loads(post.pop('merchantReturnData', '{}'))
|
||||
|
|
|
@ -3,8 +3,8 @@ import logging
|
|||
import pprint
|
||||
import werkzeug
|
||||
|
||||
from openerp.addons.web import http
|
||||
from openerp.addons.web.http import request
|
||||
from openerp import http, SUPERUSER_ID
|
||||
from openerp.http import request
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
@ -20,10 +20,10 @@ class OgoneController(http.Controller):
|
|||
'/payment/ogone/decline', '/payment/ogone/test/decline',
|
||||
'/payment/ogone/exception', '/payment/ogone/test/exception',
|
||||
'/payment/ogone/cancel', '/payment/ogone/test/cancel',
|
||||
], type='http', auth='admin')
|
||||
], type='http', auth='none')
|
||||
def ogone_form_feedback(self, **post):
|
||||
""" Ogone contacts using GET, at least for accept """
|
||||
_logger.info('Ogone: entering form_feedback with post data %s', pprint.pformat(post)) # debug
|
||||
cr, uid, context = request.cr, request.uid, request.context
|
||||
cr, uid, context = request.cr, SUPERUSER_ID, request.context
|
||||
request.registry['payment.transaction'].form_feedback(cr, uid, post, 'ogone', context=context)
|
||||
return werkzeug.utils.redirect(post.pop('return_url', '/'))
|
||||
|
|
|
@ -6,12 +6,11 @@ except ImportError:
|
|||
import json
|
||||
import logging
|
||||
import pprint
|
||||
import urllib
|
||||
import urllib2
|
||||
import werkzeug
|
||||
|
||||
from openerp.addons.web import http
|
||||
from openerp.addons.web.http import request
|
||||
from openerp import http, SUPERUSER_ID
|
||||
from openerp.http import request
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
@ -41,12 +40,12 @@ class PaypalController(http.Controller):
|
|||
Once data is validated, process it. """
|
||||
res = False
|
||||
new_post = dict(post, cmd='_notify-validate')
|
||||
urequest = urllib2.Request("https://www.sandbox.paypal.com/cgi-bin/webscr", urllib.urlencode(new_post))
|
||||
urequest = urllib2.Request("https://www.sandbox.paypal.com/cgi-bin/webscr", werkzeug.url_encode(new_post))
|
||||
uopen = urllib2.urlopen(urequest)
|
||||
resp = uopen.read()
|
||||
if resp == 'VERIFIED':
|
||||
_logger.info('Paypal: validated data')
|
||||
cr, uid, context = request.cr, request.uid, request.context
|
||||
cr, uid, context = request.cr, SUPERUSER_ID, request.context
|
||||
res = request.registry['payment.transaction'].form_feedback(cr, uid, post, 'paypal', context=context)
|
||||
elif resp == 'INVALID':
|
||||
_logger.warning('Paypal: answered INVALID on data verification')
|
||||
|
@ -54,14 +53,14 @@ class PaypalController(http.Controller):
|
|||
_logger.warning('Paypal: unrecognized paypal answer, received %s instead of VERIFIED or INVALID' % resp.text)
|
||||
return res
|
||||
|
||||
@http.route('/payment/paypal/ipn/', type='http', auth='admin', methods=['POST'])
|
||||
@http.route('/payment/paypal/ipn/', type='http', auth='none', methods=['POST'])
|
||||
def paypal_ipn(self, **post):
|
||||
""" Paypal IPN. """
|
||||
_logger.info('Beginning Paypal IPN form_feedback with post data %s', pprint.pformat(post)) # debug
|
||||
self.paypal_validate_data(**post)
|
||||
return ''
|
||||
|
||||
@http.route('/payment/paypal/dpn', type='http', auth="admin", methods=['POST'])
|
||||
@http.route('/payment/paypal/dpn', type='http', auth="none", methods=['POST'])
|
||||
def paypal_dpn(self, **post):
|
||||
""" Paypal DPN """
|
||||
_logger.info('Beginning Paypal DPN form_feedback with post data %s', pprint.pformat(post)) # debug
|
||||
|
@ -69,10 +68,10 @@ class PaypalController(http.Controller):
|
|||
self.paypal_validate_data(**post)
|
||||
return werkzeug.utils.redirect(return_url)
|
||||
|
||||
@http.route('/payment/paypal/cancel', type='http', auth="admin")
|
||||
@http.route('/payment/paypal/cancel', type='http', auth="none")
|
||||
def paypal_cancel(self, **post):
|
||||
""" When the user cancels its Paypal payment: GET on this route """
|
||||
cr, uid, context = request.cr, request.uid, request.context
|
||||
cr, uid, context = request.cr, SUPERUSER_ID, request.context
|
||||
_logger.info('Beginning Paypal cancel with post data %s', pprint.pformat(post)) # debug
|
||||
return_url = self._get_return_url(**post)
|
||||
return werkzeug.utils.redirect(return_url)
|
||||
|
|
|
@ -7,7 +7,7 @@ except ImportError:
|
|||
import json
|
||||
import logging
|
||||
import urlparse
|
||||
import urllib
|
||||
import werkzeug.urls
|
||||
import urllib2
|
||||
|
||||
from openerp.addons.payment.models.payment_acquirer import ValidationError
|
||||
|
@ -134,7 +134,7 @@ class AcquirerPaypal(osv.Model):
|
|||
password manager
|
||||
"""
|
||||
res = dict.fromkeys(ids, False)
|
||||
parameters = urllib.urlencode({'grant_type': 'client_credentials'})
|
||||
parameters = werkzeug.url_encode({'grant_type': 'client_credentials'})
|
||||
|
||||
for acquirer in self.browse(cr, uid, ids, context=context):
|
||||
tx_url = self._get_paypal_urls(cr, uid, acquirer.env)['paypal_rest_url']
|
||||
|
|
|
@ -3,8 +3,8 @@ import logging
|
|||
import pprint
|
||||
import werkzeug
|
||||
|
||||
from openerp.addons.web import http
|
||||
from openerp.addons.web.http import request
|
||||
from openerp import http, SUPERUSER_ID
|
||||
from openerp.http import request
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
@ -14,9 +14,9 @@ class OgoneController(http.Controller):
|
|||
|
||||
@http.route([
|
||||
'/payment/transfer/feedback',
|
||||
], type='http', auth='admin')
|
||||
], type='http', auth='none')
|
||||
def transfer_form_feedback(self, **post):
|
||||
cr, uid, context = request.cr, request.uid, request.context
|
||||
cr, uid, context = request.cr, SUPERUSER_ID, request.context
|
||||
_logger.info('Beginning form_feedback with post data %s', pprint.pformat(post)) # debug
|
||||
request.registry['payment.transaction'].form_feedback(cr, uid, post, 'transfer', context)
|
||||
return werkzeug.utils.redirect(post.pop('return_url', '/'))
|
||||
|
|
|
@ -93,8 +93,9 @@ class plugin_handler(osv.osv_memory):
|
|||
"""
|
||||
mail_message = self.pool.get('mail.message')
|
||||
model_obj = self.pool[model]
|
||||
msg = self.pool.get('mail.thread').message_parse(cr, uid, email)
|
||||
message_id = msg.get('message-id')
|
||||
mail_thread_obj = self.pool.get('mail.thread')
|
||||
msg = mail_thread_obj.message_parse(cr, uid, email)
|
||||
message_id = msg.get('message_id')
|
||||
mail_ids = mail_message.search(cr, uid, [('message_id', '=', message_id), ('res_id', '=', res_id), ('model', '=', model)])
|
||||
if message_id and mail_ids:
|
||||
mail_record = mail_message.browse(cr, uid, mail_ids)[0]
|
||||
|
@ -107,12 +108,22 @@ class plugin_handler(osv.osv_memory):
|
|||
res_id = model_obj.message_process(cr, uid, model, email)
|
||||
notify = _("Mail successfully pushed, a new %s has been created.") % model
|
||||
else:
|
||||
email_from = msg.get('email_from')
|
||||
if not email_from:
|
||||
author_id = False
|
||||
else:
|
||||
authors = mail_thread_obj.message_find_partner_from_emails(cr, uid, [res_id], [email_from])
|
||||
author_id = authors and authors[0].get('partner_id') or False
|
||||
|
||||
model_obj.message_post(cr, uid, [res_id],
|
||||
body=msg.get('body'),
|
||||
subject=msg.get('subject'),
|
||||
type='comment' if model == 'res.partner' else 'email',
|
||||
parent_id=msg.get('parent_id'),
|
||||
attachments=msg.get('attachments'))
|
||||
attachments=msg.get('attachments'),
|
||||
message_id=message_id,
|
||||
email_from=email_from,
|
||||
author_id=author_id)
|
||||
notify = _("Mail successfully pushed")
|
||||
url = self._make_url(cr, uid, res_id, model)
|
||||
return (model, res_id, url, notify)
|
||||
|
@ -151,7 +162,7 @@ class plugin_handler(osv.osv_memory):
|
|||
ir_attachment_obj = self.pool.get('ir.attachment')
|
||||
attach_ids = []
|
||||
msg = self.pool.get('mail.thread').message_parse(cr, uid, headers)
|
||||
message_id = msg.get('message-id')
|
||||
message_id = msg.get('message_id')
|
||||
push_mail = self.push_message(cr, uid, model, headers, res_id)
|
||||
res_id = push_mail[1]
|
||||
model = push_mail[0]
|
||||
|
@ -165,6 +176,6 @@ class plugin_handler(osv.osv_memory):
|
|||
attach_ids.append(ir_attachment_obj.create(cr, uid, vals))
|
||||
mail_ids = mail_message.search(cr, uid, [('message_id', '=', message_id), ('res_id', '=', res_id), ('model', '=', model)])
|
||||
if mail_ids:
|
||||
mail_message.write(cr, uid, mail_ids[0], {'attachment_ids': [(6, 0, attach_ids)], 'body': body, 'body_html': body_html})
|
||||
mail_message.write(cr, uid, mail_ids[0], {'attachment_ids': [(6, 0, attach_ids)], 'body': body_html})
|
||||
url = self._make_url(cr, uid, res_id, model)
|
||||
return (model, res_id, url, notify)
|
||||
|
|
|
@ -102,6 +102,8 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal
|
|||
};
|
||||
this.custom_payment_status = this.default_payment_status;
|
||||
|
||||
this.receipt_queue = [];
|
||||
|
||||
this.notifications = {};
|
||||
this.bypass_proxy = false;
|
||||
|
||||
|
@ -113,6 +115,13 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal
|
|||
|
||||
this.set_connection_status('disconnected');
|
||||
|
||||
this.on('change:status',this,function(eh,status){
|
||||
status = status.newValue;
|
||||
if(status.status === 'connected'){
|
||||
self.print_receipt();
|
||||
}
|
||||
});
|
||||
|
||||
window.hw_proxy = this;
|
||||
},
|
||||
set_connection_status: function(status,drivers){
|
||||
|
@ -502,7 +511,23 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal
|
|||
* }
|
||||
*/
|
||||
print_receipt: function(receipt){
|
||||
return this.message('print_receipt',{receipt: receipt});
|
||||
var self = this;
|
||||
if(receipt){
|
||||
this.receipt_queue.push(receipt);
|
||||
}
|
||||
var aborted = false;
|
||||
function send_printing_job(){
|
||||
if (self.receipt_queue.length > 0){
|
||||
var r = self.receipt_queue.shift();
|
||||
self.message('print_receipt',{ receipt: r },{ timeout: 5000 })
|
||||
.then(function(){
|
||||
send_printing_job();
|
||||
},function(){
|
||||
self.receipt_queue.unshift(r)
|
||||
});
|
||||
}
|
||||
}
|
||||
send_printing_job();
|
||||
},
|
||||
|
||||
// asks the proxy to log some information, as with the debug.log you can provide several arguments.
|
||||
|
|
|
@ -92,6 +92,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
|
|||
},
|
||||
connect_to_proxy: function(){
|
||||
var self = this;
|
||||
var done = new $.Deferred();
|
||||
this.barcode_reader.disconnect_from_proxy();
|
||||
this.pos_widget.loading_message(_t('Connecting to the PosBox'),0);
|
||||
this.pos_widget.loading_skip(function(){
|
||||
|
@ -106,7 +107,10 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
|
|||
if(self.config.iface_scan_via_proxy){
|
||||
self.barcode_reader.connect_to_proxy();
|
||||
}
|
||||
}).always(function(){
|
||||
done.resolve();
|
||||
});
|
||||
return done;
|
||||
},
|
||||
|
||||
// helper function to load data from the server
|
||||
|
|
|
@ -52,7 +52,8 @@ class project_compute_phases(osv.osv_memory):
|
|||
else:
|
||||
project_ids = project_pool.search(cr, uid, [('user_id','=',uid)], context=context)
|
||||
|
||||
project_pool.schedule_phases(cr, uid, project_ids, context=context)
|
||||
if project_ids:
|
||||
project_pool.schedule_phases(cr, uid, project_ids, context=context)
|
||||
return self._open_phases_list(cr, uid, data, context=context)
|
||||
|
||||
def _open_phases_list(self, cr, uid, data, context=None):
|
||||
|
|
|
@ -695,7 +695,7 @@ class purchase_order(osv.osv):
|
|||
continue
|
||||
if order_line.product_id.type in ('product', 'consu'):
|
||||
move = stock_move.create(cr, uid, self._prepare_order_line_move(cr, uid, order, order_line, picking_id, context=context))
|
||||
if order_line.move_dest_id:
|
||||
if order_line.move_dest_id and order_line.move_dest_id.state != 'done':
|
||||
order_line.move_dest_id.write({'location_id': order.location_id.id})
|
||||
todo_moves.append(move)
|
||||
stock_move.action_confirm(cr, uid, todo_moves)
|
||||
|
@ -909,6 +909,15 @@ class purchase_order_line(osv.osv):
|
|||
default.update({'state':'draft', 'move_ids':[],'invoiced':0,'invoice_lines':[]})
|
||||
return super(purchase_order_line, self).copy_data(cr, uid, id, default, context)
|
||||
|
||||
def unlink(self, cr, uid, ids, context=None):
|
||||
procurement_ids_to_cancel = []
|
||||
for line in self.browse(cr, uid, ids, context=context):
|
||||
if line.move_dest_id:
|
||||
procurement_ids_to_cancel.extend(procurement.id for procurement in line.move_dest_id.procurements)
|
||||
if procurement_ids_to_cancel:
|
||||
self.pool['procurement.order'].action_cancel(cr, uid, procurement_ids_to_cancel)
|
||||
return super(purchase_order_line, self).unlink(cr, uid, ids, context=context)
|
||||
|
||||
def onchange_product_uom(self, cr, uid, ids, pricelist_id, product_id, qty, uom_id,
|
||||
partner_id, date_order=False, fiscal_position_id=False, date_planned=False,
|
||||
name=False, price_unit=False, context=None):
|
||||
|
|
|
@ -208,7 +208,7 @@
|
|||
<page string="Purchase Order">
|
||||
<field name="order_line">
|
||||
<tree string="Purchase Order Lines" editable="bottom">
|
||||
<field name="product_id" on_change="onchange_product_id(parent.pricelist_id,product_id,0,product_uom,parent.partner_id, parent.date_order,parent.fiscal_position,date_planned,name,price_unit,context)"/>
|
||||
<field name="product_id" on_change="onchange_product_id(parent.pricelist_id,product_id,0,False,parent.partner_id, parent.date_order,parent.fiscal_position,date_planned,name,price_unit,context)"/>
|
||||
<field name="name"/>
|
||||
<field name="date_planned"/>
|
||||
<field name="company_id" groups="base.group_multi_company" widget="selection"/>
|
||||
|
@ -387,7 +387,7 @@
|
|||
<sheet>
|
||||
<group>
|
||||
<group>
|
||||
<field name="product_id" on_change="onchange_product_id(parent.pricelist_id,product_id,0,product_uom,parent.partner_id, parent.date_order,parent.fiscal_position,date_planned,name,price_unit,context)"/>
|
||||
<field name="product_id" on_change="onchange_product_id(parent.pricelist_id,product_id,0,False,parent.partner_id, parent.date_order,parent.fiscal_position,date_planned,name,price_unit,context)"/>
|
||||
<label for="product_qty"/>
|
||||
<div>
|
||||
<field name="product_qty" on_change="onchange_product_id(parent.pricelist_id,product_id,product_qty,product_uom,parent.partner_id,parent.date_order,parent.fiscal_position,date_planned,name,price_unit,context)" class="oe_inline"/>
|
||||
|
|
|
@ -24,3 +24,13 @@
|
|||
!python {model: procurement.order}: |
|
||||
procurement = self.browse(cr, uid, ref('procurement_order_testcase0'))
|
||||
assert procurement.purchase_id, 'RFQ should be generated!'
|
||||
-
|
||||
I delete the line from the purchase order and check that the move and the procurement are cancelled
|
||||
-
|
||||
!python {model: procurement.order}: |
|
||||
procurement = self.browse(cr, uid, ref('procurement_order_testcase0'))
|
||||
move = procurement.purchase_id.order_line[0].move_dest_id
|
||||
procurement.purchase_id.order_line[0].unlink()
|
||||
assert move.state == 'cancel', 'Move should be cancelled'
|
||||
procurement.refresh()
|
||||
assert procurement.state == 'cancel', 'Procurement should be cancelled'
|
||||
|
|
|
@ -125,7 +125,7 @@
|
|||
<field name="product_id"
|
||||
context="{'partner_id':parent.partner_id, 'quantity':product_uom_qty, 'pricelist':parent.pricelist_id, 'uom':product_uom}"
|
||||
groups="base.group_user"
|
||||
on_change="product_id_change(parent.pricelist_id, product_id, product_uom_qty, product_uom, product_uos_qty, product_uos, name, parent.partner_id, False, True, parent.date_order, False, parent.fiscal_position, False, context)"/>
|
||||
on_change="product_id_change(parent.pricelist_id, product_id, product_uom_qty, False, product_uos_qty, False, name, parent.partner_id, False, True, parent.date_order, False, parent.fiscal_position, False, context)"/>
|
||||
<label for="product_uom_qty"/>
|
||||
<div>
|
||||
<field
|
||||
|
@ -169,7 +169,7 @@
|
|||
<field name="product_id"
|
||||
context="{'partner_id':parent.partner_id, 'quantity':product_uom_qty, 'pricelist':parent.pricelist_id, 'uom':product_uom}"
|
||||
groups="base.group_user"
|
||||
on_change="product_id_change(parent.pricelist_id, product_id, product_uom_qty, product_uom, product_uos_qty, product_uos, name, parent.partner_id, False, True, parent.date_order, False, parent.fiscal_position, False, context)"/>
|
||||
on_change="product_id_change(parent.pricelist_id, product_id, product_uom_qty, False, product_uos_qty, False, name, parent.partner_id, False, True, parent.date_order, False, parent.fiscal_position, False, context)"/>
|
||||
<field name="name"/>
|
||||
<field name="product_uom_qty"
|
||||
context="{'partner_id':parent.partner_id, 'quantity':product_uom_qty, 'pricelist':parent.pricelist_id, 'uom':product_uom}"
|
||||
|
|
|
@ -33,7 +33,7 @@
|
|||
<field name="product_id"
|
||||
context="{'partner_id':parent.partner_id, 'quantity':product_uom_qty, 'pricelist':parent.pricelist_id, 'uom':product_uom}"
|
||||
groups="base.group_user"
|
||||
on_change="product_id_change(parent.pricelist_id,product_id,product_uom_qty,product_uom,product_uos_qty,product_uos,name,parent.partner_id, False, True, parent.date_order, product_packaging, parent.fiscal_position, False, context)"/>
|
||||
on_change="product_id_change(parent.pricelist_id,product_id,product_uom_qty,False,product_uos_qty,False,name,parent.partner_id, False, True, parent.date_order, product_packaging, parent.fiscal_position, False, context)"/>
|
||||
</field>
|
||||
<field name="product_uom_qty" position="replace">
|
||||
<field context="{'partner_id':parent.partner_id, 'quantity':product_uom_qty, 'pricelist':parent.pricelist_id, 'uom':product_uom}"
|
||||
|
|
|
@ -21,7 +21,6 @@
|
|||
import logging
|
||||
import random
|
||||
import time
|
||||
from urllib import quote_plus
|
||||
import uuid
|
||||
from openerp import SUPERUSER_ID
|
||||
|
||||
|
|
|
@ -90,13 +90,14 @@ class product_product(osv.osv):
|
|||
stock_output_acc = datas.get('stock_output_account', False)
|
||||
stock_input_acc = datas.get('stock_input_account', False)
|
||||
journal_id = datas.get('stock_journal', False)
|
||||
product_obj=self.browse(cr, uid, ids, context=context)[0]
|
||||
account_valuation = product_obj.categ_id.property_stock_valuation_account_id
|
||||
account_valuation_id = account_valuation and account_valuation.id or False
|
||||
if not account_valuation_id: raise osv.except_osv(_('Error!'), _('Specify valuation Account for Product Category: %s.') % (product_obj.categ_id.name))
|
||||
move_ids = []
|
||||
loc_ids = location_obj.search(cr, uid,[('usage','=','internal')])
|
||||
for rec_id in ids:
|
||||
for product in self.browse(cr, uid, ids, context=context):
|
||||
if product.valuation != 'real_time':
|
||||
continue
|
||||
account_valuation = product.categ_id.property_stock_valuation_account_id
|
||||
account_valuation_id = account_valuation and account_valuation.id or False
|
||||
if not account_valuation_id: raise osv.except_osv(_('Error!'), _('Specify valuation Account for Product Category: %s.') % (product.categ_id.name))
|
||||
for location in location_obj.browse(cr, uid, loc_ids, context=context):
|
||||
c = context.copy()
|
||||
c.update({
|
||||
|
@ -104,7 +105,6 @@ class product_product(osv.osv):
|
|||
'compute_child': False
|
||||
})
|
||||
|
||||
product = self.browse(cr, uid, rec_id, context=c)
|
||||
qty = product.qty_available
|
||||
diff = product.standard_price - new_price
|
||||
if not diff: raise osv.except_osv(_('Error!'), _("No difference between standard price and new price!"))
|
||||
|
@ -182,8 +182,7 @@ class product_product(osv.osv):
|
|||
'debit': amount_diff,
|
||||
'move_id': move_id
|
||||
})
|
||||
|
||||
self.write(cr, uid, rec_id, {'standard_price': new_price})
|
||||
self.write(cr, uid, ids, {'standard_price': new_price})
|
||||
|
||||
return move_ids
|
||||
|
||||
|
|
|
@ -2625,7 +2625,7 @@ class stock_move(osv.osv):
|
|||
quantity = move.product_qty
|
||||
|
||||
uos_qty = quantity / move_qty * move.product_uos_qty
|
||||
if quantity_rest > 0:
|
||||
if float_compare(quantity_rest, 0, precision_rounding=move.product_id.uom_id.rounding):
|
||||
default_val = {
|
||||
'product_qty': quantity,
|
||||
'product_uos_qty': uos_qty,
|
||||
|
|
|
@ -28,11 +28,15 @@ logger = logging.getLogger(__name__)
|
|||
MAX_IMAGE_WIDTH, MAX_IMAGE_HEIGHT = IMAGE_LIMITS = (1024, 768)
|
||||
|
||||
class Website(openerp.addons.web.controllers.main.Home):
|
||||
#------------------------------------------------------
|
||||
# View
|
||||
#------------------------------------------------------
|
||||
@http.route('/', type='http', auth="public", website=True, multilang=True)
|
||||
def index(self, **kw):
|
||||
try:
|
||||
main_menu = request.registry['ir.model.data'].get_object(request.cr, request.uid, 'website', 'main_menu')
|
||||
first_menu = main_menu.child_id and main_menu.child_id[0]
|
||||
# Dont 302 loop on /
|
||||
if first_menu and not ((first_menu.url == '/') or first_menu.url.startswith('/#') or first_menu.url.startswith('/?')):
|
||||
return request.redirect(first_menu.url)
|
||||
except:
|
||||
|
@ -47,7 +51,50 @@ class Website(openerp.addons.web.controllers.main.Home):
|
|||
response = request.website.render(response.params['template'], values)
|
||||
return response
|
||||
|
||||
@http.route('/pagenew/<path:path>', type='http', auth="user", website=True)
|
||||
@http.route('/page/<page:page>', type='http', auth="public", website=True, multilang=True)
|
||||
def page(self, page, **opt):
|
||||
values = {
|
||||
'path': page,
|
||||
}
|
||||
# allow shortcut for /page/<website_xml_id>
|
||||
if '.' not in page:
|
||||
page = 'website.%s' % page
|
||||
|
||||
try:
|
||||
request.website.get_template(page)
|
||||
except ValueError, e:
|
||||
# page not found
|
||||
if request.context['editable']:
|
||||
page = 'website.page_404'
|
||||
else:
|
||||
return request.registry['ir.http']._handle_exception(e, 404)
|
||||
|
||||
return request.website.render(page, values)
|
||||
|
||||
@http.route(['/robots.txt'], type='http', auth="public", website=True)
|
||||
def robots(self):
|
||||
response = request.website.render('website.robots', {'url_root': request.httprequest.url_root})
|
||||
response.mimetype = 'text/plain'
|
||||
return response
|
||||
|
||||
@http.route('/sitemap', type='http', auth='public', website=True, multilang=True)
|
||||
def sitemap(self):
|
||||
return request.website.render('website.sitemap', {
|
||||
'pages': request.website.enumerate_pages()
|
||||
})
|
||||
|
||||
@http.route('/sitemap.xml', type='http', auth="public", website=True)
|
||||
def sitemap_xml(self):
|
||||
response = request.website.render('website.sitemap_xml', {
|
||||
'pages': request.website.enumerate_pages()
|
||||
})
|
||||
response.headers['Content-Type'] = 'application/xml;charset=utf-8'
|
||||
return response
|
||||
|
||||
#------------------------------------------------------
|
||||
# Edit
|
||||
#------------------------------------------------------
|
||||
@http.route('/website/add/<path:path>', type='http', auth="user", website=True)
|
||||
def pagenew(self, path, noredirect=False):
|
||||
xml_id = request.registry['website'].new_page(request.cr, request.uid, path, context=request.context)
|
||||
url = "/page/" + xml_id
|
||||
|
@ -55,7 +102,7 @@ class Website(openerp.addons.web.controllers.main.Home):
|
|||
return werkzeug.wrappers.Response(url, mimetype='text/plain')
|
||||
return werkzeug.utils.redirect(url)
|
||||
|
||||
@http.route('/website/theme_change', type='http', auth="admin", website=True)
|
||||
@http.route('/website/theme_change', type='http', auth="user", website=True)
|
||||
def theme_change(self, theme_id=False, **kwargs):
|
||||
imd = request.registry['ir.model.data']
|
||||
view = request.registry['ir.ui.view']
|
||||
|
@ -81,26 +128,6 @@ class Website(openerp.addons.web.controllers.main.Home):
|
|||
def snippets(self):
|
||||
return request.website._render('website.snippets')
|
||||
|
||||
@http.route('/page/<page:page>', type='http', auth="public", website=True, multilang=True)
|
||||
def page(self, page, **opt):
|
||||
values = {
|
||||
'path': page,
|
||||
}
|
||||
# allow shortcut for /page/<website_xml_id>
|
||||
if '.' not in page:
|
||||
page = 'website.%s' % page
|
||||
|
||||
try:
|
||||
request.website.get_template(page)
|
||||
except ValueError, e:
|
||||
# page not found
|
||||
if request.context['editable']:
|
||||
page = 'website.page_404'
|
||||
else:
|
||||
return request.registry['ir.http']._handle_exception(e, 404)
|
||||
|
||||
return request.website.render(page, values)
|
||||
|
||||
@http.route('/website/reset_templates', type='http', auth='user', methods=['POST'], website=True)
|
||||
def reset_template(self, templates, redirect='/'):
|
||||
templates = request.httprequest.form.getlist('templates')
|
||||
|
@ -141,7 +168,7 @@ class Website(openerp.addons.web.controllers.main.Home):
|
|||
group_ids = [g.id for g in user.groups_id]
|
||||
|
||||
view = request.registry.get("ir.ui.view")
|
||||
views = view._views_get(request.cr, request.uid, xml_id, request.context)
|
||||
views = view._views_get(request.cr, request.uid, xml_id, context=request.context)
|
||||
done = {}
|
||||
result = []
|
||||
for v in views:
|
||||
|
@ -166,7 +193,7 @@ class Website(openerp.addons.web.controllers.main.Home):
|
|||
})
|
||||
return result
|
||||
|
||||
@http.route('/website/get_view_translations', type='json', auth='admin', website=True)
|
||||
@http.route('/website/get_view_translations', type='json', auth='public', website=True)
|
||||
def get_view_translations(self, xml_id, lang=None):
|
||||
lang = lang or request.context.get('lang')
|
||||
views = self.customize_template_get(xml_id, optional=False)
|
||||
|
@ -175,7 +202,7 @@ class Website(openerp.addons.web.controllers.main.Home):
|
|||
irt = request.registry.get('ir.translation')
|
||||
return irt.search_read(request.cr, request.uid, domain, ['id', 'res_id', 'value'], context=request.context)
|
||||
|
||||
@http.route('/website/set_translations', type='json', auth='admin', website=True)
|
||||
@http.route('/website/set_translations', type='json', auth='public', website=True)
|
||||
def set_translations(self, data, lang):
|
||||
irt = request.registry.get('ir.translation')
|
||||
for view_id, trans in data.items():
|
||||
|
@ -210,11 +237,9 @@ class Website(openerp.addons.web.controllers.main.Home):
|
|||
irt.create(request.cr, request.uid, new_trans)
|
||||
return True
|
||||
|
||||
@http.route('/website/attach', type='http', auth='user', website=True)
|
||||
@http.route('/website/attach', type='http', auth='user', methods=['POST'], website=True)
|
||||
def attach(self, func, upload):
|
||||
req = request.httprequest
|
||||
if req.method != 'POST':
|
||||
return werkzeug.exceptions.MethodNotAllowed(valid_methods=['POST'])
|
||||
|
||||
url = message = None
|
||||
try:
|
||||
|
@ -257,42 +282,27 @@ class Website(openerp.addons.web.controllers.main.Home):
|
|||
obj = _object.browse(request.cr, request.uid, _id)
|
||||
return bool(obj.website_published)
|
||||
|
||||
#------------------------------------------------------
|
||||
# Helpers
|
||||
#------------------------------------------------------
|
||||
@http.route(['/website/kanban/'], type='http', auth="public", methods=['POST'], website=True)
|
||||
def kanban(self, **post):
|
||||
return request.website.kanban_col(**post)
|
||||
|
||||
@http.route(['/robots.txt'], type='http', auth="public", website=True)
|
||||
def robots(self):
|
||||
response = request.website.render('website.robots', {'url_root': request.httprequest.url_root})
|
||||
response.mimetype = 'text/plain'
|
||||
return response
|
||||
|
||||
@http.route('/sitemap', type='http', auth='public', website=True, multilang=True)
|
||||
def sitemap(self):
|
||||
return request.website.render('website.sitemap', {
|
||||
'pages': request.website.enumerate_pages()
|
||||
})
|
||||
|
||||
@http.route('/sitemap.xml', type='http', auth="public", website=True)
|
||||
def sitemap_xml(self):
|
||||
response = request.website.render('website.sitemap_xml', {
|
||||
'pages': request.website.enumerate_pages()
|
||||
})
|
||||
response.headers['Content-Type'] = 'application/xml;charset=utf-8'
|
||||
return response
|
||||
|
||||
class Images(http.Controller):
|
||||
def placeholder(self, response):
|
||||
# file_open may return a StringIO. StringIO can be closed but are
|
||||
# not context managers in Python 2 though that is fixed in 3
|
||||
with contextlib.closing(openerp.tools.misc.file_open(
|
||||
os.path.join('web', 'static', 'src', 'img', 'placeholder.png'),
|
||||
mode='rb')) as f:
|
||||
response.set_data(f.read())
|
||||
response.data = f.read()
|
||||
return response.make_conditional(request.httprequest)
|
||||
|
||||
@http.route('/website/image', auth="public", website=True)
|
||||
def image(self, model, id, field, max_width=maxint, max_height=maxint):
|
||||
@http.route([
|
||||
'/website/image',
|
||||
'/website/image/<model>/<id>/<field>'
|
||||
], auth="public", website=True)
|
||||
def website_image(self, model, id, field, max_width=maxint, max_height=maxint):
|
||||
Model = request.registry[model]
|
||||
|
||||
response = werkzeug.wrappers.Response()
|
||||
|
@ -346,7 +356,7 @@ class Images(http.Controller):
|
|||
max_w, max_h = fit
|
||||
|
||||
if w < max_w and h < max_h:
|
||||
response.set_data(data)
|
||||
response.data = data
|
||||
else:
|
||||
image.thumbnail(fit, Image.ANTIALIAS)
|
||||
image.save(response.stream, image.format)
|
||||
|
@ -356,4 +366,5 @@ class Images(http.Controller):
|
|||
|
||||
return response
|
||||
|
||||
|
||||
# vim:expandtab:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
<field name="company_id" ref="base.main_company"/>
|
||||
<field name="language_ids" eval="[(6, 0, [ ref('base.lang_en')])]"/>
|
||||
<field name="default_lang_id" ref="base.lang_en"/>
|
||||
<field name="user_id" ref="base.public_user"/>
|
||||
</record>
|
||||
|
||||
<record id="main_menu" model="website.menu">
|
||||
|
|
|
@ -95,7 +95,7 @@ class ir_http(orm.AbstractModel):
|
|||
werkzeug.exceptions.abort(werkzeug.utils.redirect(url))
|
||||
|
||||
def _handle_exception(self, exception=None, code=500):
|
||||
if isinstance(exception, werkzeug.exceptions.HTTPException) and exception.response:
|
||||
if isinstance(exception, werkzeug.exceptions.HTTPException) and hasattr(exception, 'response') and exception.response:
|
||||
return exception.response
|
||||
if getattr(request, 'website_enabled', False) and request.website:
|
||||
values = dict(
|
||||
|
@ -103,20 +103,16 @@ class ir_http(orm.AbstractModel):
|
|||
traceback=traceback.format_exc(exception),
|
||||
)
|
||||
if exception:
|
||||
current_exception = exception
|
||||
code = getattr(exception, 'code', code)
|
||||
if isinstance(exception, ir_qweb.QWebException):
|
||||
values.update(qweb_exception=exception)
|
||||
if exception.inner:
|
||||
current_exception = exception.inner
|
||||
if isinstance(current_exception, openerp.exceptions.AccessError):
|
||||
code = 403
|
||||
else:
|
||||
code = getattr(exception, 'code', code)
|
||||
if isinstance(exception.qweb.get('inner'), openerp.exceptions.AccessError):
|
||||
code = 403
|
||||
if code == 500:
|
||||
logger.error("500 Internal Server Error:\n\n%s", values['traceback'])
|
||||
if values.get('qweb_exception'):
|
||||
if 'qweb_exception' in values:
|
||||
view = request.registry.get("ir.ui.view")
|
||||
views = view._views_get(request.cr, request.uid, values['qweb_exception'].template, request.context)
|
||||
views = view._views_get(request.cr, request.uid, exception.qweb['template'], request.context)
|
||||
to_reset = [v for v in views if v.model_data_id.noupdate is True]
|
||||
values['views'] = to_reset
|
||||
elif code == 403:
|
||||
|
@ -132,7 +128,7 @@ class ir_http(orm.AbstractModel):
|
|||
|
||||
try:
|
||||
html = request.website._render('website.%s' % code, values)
|
||||
except:
|
||||
except Exception:
|
||||
html = request.website._render('website.http_error', values)
|
||||
return werkzeug.wrappers.Response(html, status=code, content_type='text/html;charset=utf-8')
|
||||
|
||||
|
|
|
@ -46,6 +46,16 @@ class view(osv.osv):
|
|||
view = view.inherit_id
|
||||
|
||||
result = [view]
|
||||
|
||||
node = etree.fromstring(view.arch)
|
||||
for child in node.xpath("//t[@t-call]"):
|
||||
try:
|
||||
call_view = view_obj(child.get('t-call'))
|
||||
except ValueError:
|
||||
continue
|
||||
if call_view not in result:
|
||||
result += self._views_get(cr, uid, call_view, options=options, context=context, stack_result=result)
|
||||
|
||||
todo = view.inherit_children_ids
|
||||
if options:
|
||||
todo += filter(lambda x: not x.inherit_id, view.inherited_option_ids)
|
||||
|
@ -55,14 +65,6 @@ class view(osv.osv):
|
|||
for r in self._views_get(cr, uid, child_view, options=bool(child_view.inherit_id), context=context, root=False, stack_result=result):
|
||||
if r not in result:
|
||||
result.append(r)
|
||||
node = etree.fromstring(view.arch)
|
||||
for child in node.xpath("//t[@t-call]"):
|
||||
try:
|
||||
call_view = view_obj(child.get('t-call'))
|
||||
except ValueError:
|
||||
continue
|
||||
if call_view not in result:
|
||||
result += self._views_get(cr, uid, call_view, options=options, context=context, stack_result=result)
|
||||
return result
|
||||
|
||||
def extract_embedded_fields(self, cr, uid, arch, context=None):
|
||||
|
|
|
@ -24,8 +24,8 @@ class test_converter(orm.Model):
|
|||
('B', "Qu'il était supposé arriver à Toronto"),
|
||||
('C', "Qu'est-ce qu'il fout ce maudit pancake, tabernacle ?"),
|
||||
('D', "La réponse D"),
|
||||
], string="Lorsqu'un pancake prend l'avion à destination de Toronto et "
|
||||
"qu'il fait une escale technique à St Claude, on dit:"),
|
||||
], string=u"Lorsqu'un pancake prend l'avion à destination de Toronto et "
|
||||
u"qu'il fait une escale technique à St Claude, on dit:"),
|
||||
'html': fields.html(),
|
||||
'text': fields.text(),
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@ import itertools
|
|||
import logging
|
||||
import math
|
||||
import re
|
||||
import urllib
|
||||
import urlparse
|
||||
|
||||
import simplejson
|
||||
|
@ -38,24 +37,38 @@ def keep_query(*args, **kw):
|
|||
|
||||
def url_for(path_or_uri, lang=None):
|
||||
location = path_or_uri.strip()
|
||||
force_lang = lang is not None
|
||||
url = urlparse.urlparse(location)
|
||||
if request and not url.netloc and not url.scheme:
|
||||
|
||||
if request and not url.netloc and not url.scheme and (url.path or force_lang):
|
||||
location = urlparse.urljoin(request.httprequest.path, location)
|
||||
force_lang = lang is not None
|
||||
lang = lang or request.context.get('lang')
|
||||
langs = [lg[0] for lg in request.website.get_languages()]
|
||||
if lang != request.website.default_lang_code and (force_lang or (location[0] == '/' and len(langs) > 1)):
|
||||
if is_multilang_url(location):
|
||||
ps = location.split('/')
|
||||
if ps[1] in langs:
|
||||
|
||||
if (len(langs) > 1 or force_lang) and is_multilang_url(location, langs):
|
||||
ps = location.split('/')
|
||||
if ps[1] in langs:
|
||||
# Replace the language only if we explicitly provide a language to url_for
|
||||
if force_lang:
|
||||
ps[1] = lang
|
||||
else:
|
||||
ps.insert(1, lang)
|
||||
location = '/'.join(ps)
|
||||
# Remove the default language unless it's explicitly provided
|
||||
elif ps[1] == request.website.default_lang_code:
|
||||
ps.pop(1)
|
||||
# Insert the context language or the provided language
|
||||
elif lang != request.website.default_lang_code or force_lang:
|
||||
ps.insert(1, lang)
|
||||
location = '/'.join(ps)
|
||||
|
||||
return location
|
||||
|
||||
def is_multilang_url(path):
|
||||
def is_multilang_url(path, langs=None):
|
||||
if not langs:
|
||||
langs = [lg[0] for lg in request.website.get_languages()]
|
||||
spath = path.split('/')
|
||||
# if a language is already in the path, remove it
|
||||
if spath[1] in langs:
|
||||
spath.pop(1)
|
||||
path = '/'.join(spath)
|
||||
try:
|
||||
router = request.httprequest.app.get_db_router(request.db).bind('')
|
||||
func = router.match(path)[0]
|
||||
|
@ -80,17 +93,7 @@ def slug(value):
|
|||
return "%s-%d" % (slugify(name), id)
|
||||
|
||||
def urlplus(url, params):
|
||||
if not params:
|
||||
return url
|
||||
|
||||
# can't use urlencode because it encodes to (ascii, replace) in p2
|
||||
return "%s?%s" % (url, '&'.join(
|
||||
k + '=' + urllib.quote_plus(v.encode('utf-8') if isinstance(v, unicode) else str(v))
|
||||
for k, v in params.iteritems()
|
||||
))
|
||||
|
||||
def quote_plus(value):
|
||||
return urllib.quote_plus(value.encode('utf-8') if isinstance(value, unicode) else str(value))
|
||||
return werkzeug.Href(url)(params or None)
|
||||
|
||||
class website(osv.osv):
|
||||
def _get_menu_website(self, cr, uid, ids, context=None):
|
||||
|
@ -122,6 +125,7 @@ class website(osv.osv):
|
|||
'social_youtube': fields.char('Youtube Account'),
|
||||
'social_googleplus': fields.char('Google+ Account'),
|
||||
'google_analytics_key': fields.char('Google Analytics Key'),
|
||||
'user_id': fields.many2one('res.users', string='Public User'),
|
||||
'public_user': fields.function(_get_public_user, relation='res.users', type='many2one', string='Public User'),
|
||||
'menu_id': fields.function(_get_menu, relation='website.menu', type='many2one', string='Main Menu',
|
||||
store= {
|
||||
|
@ -243,7 +247,7 @@ class website(osv.osv):
|
|||
slug=slug,
|
||||
res_company=request.website.company_id,
|
||||
user_id=user.browse(cr, uid, uid),
|
||||
quote_plus=quote_plus,
|
||||
quote_plus=werkzeug.url_quote_plus,
|
||||
)
|
||||
qweb_values.setdefault('editable', False)
|
||||
|
||||
|
@ -279,7 +283,7 @@ class website(osv.osv):
|
|||
def get_url(page):
|
||||
_url = "%spage/%s/" % (url, page)
|
||||
if url_args:
|
||||
_url = "%s?%s" % (_url, urllib.urlencode(url_args))
|
||||
_url = "%s?%s" % (_url, werkzeug.url_encode(url_args))
|
||||
return _url
|
||||
|
||||
return {
|
||||
|
@ -446,7 +450,7 @@ class website(osv.osv):
|
|||
|
||||
get_args.setdefault('kanban', "")
|
||||
kanban = get_args.pop('kanban')
|
||||
kanban_url = "?%s&kanban=" % urllib.urlencode(get_args)
|
||||
kanban_url = "?%s&kanban=" % werkzeug.url_encode(get_args)
|
||||
|
||||
pages = {}
|
||||
for col in kanban.split(","):
|
||||
|
@ -605,9 +609,10 @@ class res_partner(osv.osv):
|
|||
def google_map_link(self, cr, uid, ids, zoom=8, context=None):
|
||||
partner = self.browse(cr, uid, ids[0], context=context)
|
||||
params = {
|
||||
'q': '%s, %s %s, %s' % (partner.street, partner.city, partner.zip, partner.country_id and partner.country_id.name_get()[0][1] or ''),
|
||||
'q': '%s, %s %s, %s' % (partner.street or '', partner.city or '', partner.zip or '', partner.country_id and partner.country_id.name_get()[0][1] or ''),
|
||||
'z': 10
|
||||
}
|
||||
return urlplus('https://maps.google.be/maps' , params)
|
||||
return urlplus('https://maps.google.com/maps' , params)
|
||||
|
||||
class res_company(osv.osv):
|
||||
_inherit = "res.company"
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
After Width: | Height: | Size: 6.3 KiB |
Binary file not shown.
Before Width: | Height: | Size: 3.2 KiB |
|
@ -488,6 +488,7 @@
|
|||
$('.css_non_editable_mode_hidden').removeClass("css_non_editable_mode_hidden");
|
||||
|
||||
this.rte.start_edition().then(this.check_height.bind(this));
|
||||
this.trigger('rte:called');
|
||||
},
|
||||
rte_changed: function () {
|
||||
this.$buttons.save.prop('disabled', false);
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
input: "Page Title",
|
||||
}).then(function (val) {
|
||||
if (val) {
|
||||
document.location = '/pagenew/' + encodeURI(val);
|
||||
document.location = '/website/add/' + encodeURI(val);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -18,53 +18,43 @@
|
|||
var self = this;
|
||||
self.steps = [
|
||||
{
|
||||
stepId: 'welcome',
|
||||
title: "Welcome to your website!",
|
||||
content: "This tutorial will guide you to build your home page. We will start by adding a banner.",
|
||||
template: self.popover({ next: "Start Tutorial", end: "Skip It" }),
|
||||
backdrop: true,
|
||||
},
|
||||
{
|
||||
stepId: 'edit-page',
|
||||
waitNot: '.popover.tour',
|
||||
element: 'button[data-action=edit]',
|
||||
placement: 'bottom',
|
||||
title: "Edit this page",
|
||||
content: "Every page of your website can be modified through the <i>Edit</i> button.",
|
||||
template: self.popover({ fixed: true }),
|
||||
trigger: {
|
||||
id: 'rte:ready',
|
||||
type: 'openerp',
|
||||
emitter: editor,
|
||||
},
|
||||
},
|
||||
{
|
||||
stepId: 'add-banner',
|
||||
element: 'button[data-action=snippet]',
|
||||
placement: 'bottom',
|
||||
title: "Insert building blocks",
|
||||
content: "To add content in a page, you can insert building blocks.",
|
||||
template: self.popover({ fixed: true }),
|
||||
trigger: 'click',
|
||||
},
|
||||
{
|
||||
stepId: 'drag-banner',
|
||||
snippet: 'carousel',
|
||||
placement: 'bottom',
|
||||
title: "Drag & Drop a Banner",
|
||||
content: "Drag the Banner block and drop it in your page.",
|
||||
template: self.popover({ fixed: true }),
|
||||
trigger: 'drag',
|
||||
},
|
||||
{
|
||||
stepId: 'edit-title',
|
||||
waitFor: '.oe_overlay_options .oe_options:visible',
|
||||
element: '#wrap [data-snippet-id=carousel]:first .carousel-caption',
|
||||
sampleText: 'My Title',
|
||||
placement: 'top',
|
||||
title: "Customize banner's text",
|
||||
content: "Click in the text and start editing it. Click continue once it's done.",
|
||||
template: self.popover({ next: "Continue" }),
|
||||
},
|
||||
{
|
||||
stepId: 'customize-banner',
|
||||
waitNot: '#wrap [data-snippet-id=carousel]:first .carousel-caption:contains("Your Banner Title")',
|
||||
element: '.oe_overlay_options .oe_options',
|
||||
placement: 'left',
|
||||
title: "Customize the banner",
|
||||
|
@ -72,67 +62,54 @@
|
|||
template: self.popover({ next: "Continue" }),
|
||||
},
|
||||
{
|
||||
stepId: 'add-three-cols',
|
||||
waitNot: '.popover.tour',
|
||||
element: 'button[data-action=snippet]',
|
||||
placement: 'bottom',
|
||||
title: "Add Another Block",
|
||||
content: "Let's add another building block to your page.",
|
||||
template: self.popover({ fixed: true }),
|
||||
trigger: 'click',
|
||||
},
|
||||
{
|
||||
stepId: 'drag-three-columns',
|
||||
snippet: 'three-columns',
|
||||
placement: 'bottom',
|
||||
title: "Drag & Drop a Block",
|
||||
content: "Drag the <em>'3 Columns'</em> block and drop it below the banner.",
|
||||
template: self.popover({ fixed: true }),
|
||||
trigger: 'drag',
|
||||
},
|
||||
{
|
||||
stepId: 'save-changes',
|
||||
waitFor: '.oe_overlay_options .oe_options:visible',
|
||||
element: 'button[data-action=save]',
|
||||
placement: 'right',
|
||||
title: "Save your modifications",
|
||||
content: "Publish your page by clicking on the <em>'Save'</em> button.",
|
||||
template: self.popover({ fixed: true }),
|
||||
trigger: 'click',
|
||||
},
|
||||
{
|
||||
stepId: 'part-2',
|
||||
waitFor: 'button[data-action=edit]:visible',
|
||||
title: "Congratulation!",
|
||||
content: "Your homepage has been updated.",
|
||||
template: self.popover({ next: "Continue" }),
|
||||
},
|
||||
{
|
||||
stepId: 'show-mobile',
|
||||
waitNot: '.popover.tour',
|
||||
element: 'a[data-action=show-mobile-preview]',
|
||||
placement: 'bottom',
|
||||
title: "Test Your Mobile Version",
|
||||
content: "Let's check how your homepage looks like on mobile devices.",
|
||||
template: self.popover({ fixed: true }),
|
||||
trigger: {
|
||||
id: 'shown.bs.modal',
|
||||
emitter: $(document),
|
||||
},
|
||||
},
|
||||
{
|
||||
stepId: 'show-mobile-close',
|
||||
element: 'button[data-dismiss=modal]',
|
||||
placement: 'right',
|
||||
title: "Close Mobile Preview",
|
||||
content: "Scroll in the mobile preview to test the rendering. Once it's ok, close this dialog.",
|
||||
trigger: 'click',
|
||||
},
|
||||
{
|
||||
stepId: 'show-tutorials',
|
||||
element: '#help-menu-button',
|
||||
placement: 'left',
|
||||
title: "More Tutorials",
|
||||
content: "Get more tutorials through this <em>'Help'</em> menu or click on the left <em>'Edit'</em> button to continue modifying this page.",
|
||||
template: self.popover({ fixed: true, end: "Close Tutorial" }),
|
||||
trigger: 'click',
|
||||
}
|
||||
waitNot: '.modal',
|
||||
title: "Congrats",
|
||||
content: "Congratulation. This tour is finished.",
|
||||
template: self.popover({ fixed: true, next: "Close Tutorial" }),
|
||||
},
|
||||
];
|
||||
return this._super();
|
||||
},
|
||||
|
|
|
@ -1,483 +1,439 @@
|
|||
(function () {
|
||||
'use strict';
|
||||
|
||||
var website = openerp.website;
|
||||
website.add_template_file('/website/static/src/xml/website.tour.xml');
|
||||
var website = openerp.website;
|
||||
website.add_template_file('/website/static/src/xml/website.tour.xml');
|
||||
|
||||
website.Tour = openerp.Class.extend({
|
||||
steps: [], // Override
|
||||
tourStorage: window.localStorage, // FIXME: will break on iPad in private mode
|
||||
init: function () {
|
||||
this.tour = new Tour({
|
||||
name: this.id,
|
||||
storage: this.tourStorage,
|
||||
keyboard: false,
|
||||
template: this.popover(),
|
||||
onHide: function () {
|
||||
window.scrollTo(0, 0);
|
||||
}
|
||||
website.EditorBar.include({
|
||||
tours: [],
|
||||
start: function () {
|
||||
// $('.tour-backdrop').click(function (e) {
|
||||
// e.stopImmediatePropagation();
|
||||
// e.preventDefault();
|
||||
// });
|
||||
var self = this;
|
||||
var menu = $('#help-menu');
|
||||
_.each(this.tours, function (tour) {
|
||||
var $menuItem = $($.parseHTML('<li><a href="#">'+tour.name+'</a></li>'));
|
||||
$menuItem.click(function () {
|
||||
tour.reset();
|
||||
tour.trigger();
|
||||
});
|
||||
this.registerSteps();
|
||||
},
|
||||
registerStep: function (step) {
|
||||
var self = this;
|
||||
step.title = openerp.qweb.render('website.tour_popover_title', { title: step.title });
|
||||
if (!step.element) {
|
||||
step.orphan = true;
|
||||
menu.append($menuItem);
|
||||
});
|
||||
|
||||
this.waitRTEReady = false;
|
||||
this.on('rte:called', this, function () {self.waitRTEReady = true; });
|
||||
this.on('rte:ready', this, function () {self.waitRTEReady = false;});
|
||||
|
||||
var res = this._super();
|
||||
website.Tour.waitReady.call(this, this.testRunning);
|
||||
return res;
|
||||
},
|
||||
registerTour: function (tour) {
|
||||
website.Tour.add(tour);
|
||||
this.tours.push(tour);
|
||||
},
|
||||
testRunning: function () {
|
||||
if (this.waitRTEReady) {
|
||||
this.on('rte:ready', this, function () {
|
||||
website.Tour.each(function () {
|
||||
this.running();
|
||||
});
|
||||
});
|
||||
} else {
|
||||
website.Tour.each(function () {
|
||||
this.running();
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
/////////////////////////////////////////////////
|
||||
|
||||
|
||||
/* 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--;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
website.Tour = openerp.Class.extend({
|
||||
steps: [],
|
||||
defaultDelay: 50, //ms
|
||||
localStorage: window.localStorage,
|
||||
init: function (url) {
|
||||
this.tour = new Tour({
|
||||
name: this.id,
|
||||
storage: this.tourStorage,
|
||||
keyboard: false,
|
||||
template: this.popover(),
|
||||
onHide: function () {
|
||||
window.scrollTo(0, 0);
|
||||
}
|
||||
});
|
||||
this.registerSteps();
|
||||
},
|
||||
|
||||
run: function (automatic, force) {
|
||||
if (force) this.reset();
|
||||
|
||||
for (var k in this.localStorage) {
|
||||
if (!k.indexOf("tour-") && k.indexOf("-test") > -1) return;
|
||||
}
|
||||
|
||||
// only one test running
|
||||
if (website.Tour.busy) return;
|
||||
|
||||
website.Tour.busy = true;
|
||||
this.localStorage.setItem("tour-"+this.id+"-test", 0);
|
||||
|
||||
website.Tour.waitReady.call(this, function () {this._run(automatic, force);});
|
||||
},
|
||||
running: function () {
|
||||
if (+this.localStorage.getItem("tour-"+this.id+"-test") >= this.steps.length) {
|
||||
this.endTour();
|
||||
return;
|
||||
}
|
||||
|
||||
if (website.Tour.busy || !this.testUrl()) return;
|
||||
|
||||
var self = this;
|
||||
website.Tour.waitReady.call(this, function () {
|
||||
self._running();
|
||||
}, 500);
|
||||
},
|
||||
|
||||
_run: function (automatic, force) {
|
||||
this.reset();
|
||||
this.localStorage.setItem("tour-"+this.id+"-test", 0);
|
||||
if (automatic) {
|
||||
this.localStorage.setItem("tour-"+this.id+"-test-automatic", true);
|
||||
}
|
||||
this.nextStep(null, automatic ? this.autoNextStep : null, automatic ? 5000 : null);
|
||||
},
|
||||
_running: function () {
|
||||
var stepId = this.localStorage.getItem("tour-"+this.id+"-test");
|
||||
var automatic = !!this.localStorage.getItem("tour-"+this.id+"-test-automatic");
|
||||
|
||||
if (stepId || this.checkRuningUrl()) {
|
||||
|
||||
if (!this.check(this.step(stepId))) {
|
||||
var step = this.next(stepId);
|
||||
stepId = step ? step.stepId : stepId;
|
||||
}
|
||||
website.Tour.busy = true;
|
||||
this.nextStep(stepId, automatic ? this.autoNextStep : null, automatic ? 5000 : null);
|
||||
}
|
||||
},
|
||||
|
||||
reset: function () {
|
||||
website.Tour.busy = false;
|
||||
for (var k in this.steps) {
|
||||
this.steps[k].busy = false;
|
||||
}
|
||||
clearTimeout(self.timer);
|
||||
clearTimeout(self.testtimer);
|
||||
|
||||
for (var k in this.localStorage) {
|
||||
if (!k.indexOf("tour-") || !k.indexOf(this.id)) {
|
||||
this.localStorage.removeItem(k);
|
||||
}
|
||||
}
|
||||
|
||||
$('.popover.tour').remove();
|
||||
},
|
||||
trigger: function (automatic) {
|
||||
this.reset();
|
||||
if (this.path) {
|
||||
this.localStorage.setItem("tour-"+this.id+"-test", 0);
|
||||
if (automatic) this.localStorage.setItem("tour-"+this.id+"-test-automatic", true);
|
||||
var path = this.path.split('?');
|
||||
window.location.href = path[0] + "?tutorial."+this.id+"=true" + path.slice(1, path.length).join("?");
|
||||
} else {
|
||||
this.run(automatic);
|
||||
}
|
||||
},
|
||||
testUrl: function () {
|
||||
return !this.testPath || this.testPath.test(window.location.href);
|
||||
},
|
||||
checkRuningUrl: function () {
|
||||
return window.location.search.indexOf("tutorial."+this.id+"=true") > -1;
|
||||
},
|
||||
|
||||
registerSteps: function () {
|
||||
for (var index=0, len=this.steps.length; index<len; index++) {
|
||||
var step = this.steps[index];
|
||||
step.stepId = step.stepId || ""+index;
|
||||
|
||||
if (!step.waitNot && index > 0 && $(this.steps[index-1].template).has("button[data-role='next']").size()) {
|
||||
step.waitNot = '.popover.tour';
|
||||
}
|
||||
if (!step.waitFor && index > 0 && this.steps[index-1].snippet) {
|
||||
step.waitFor = '.oe_overlay_options .oe_options:visible';
|
||||
}
|
||||
|
||||
step._title = step.title;
|
||||
step.title = openerp.qweb.render('website.tour_popover_title', { title: step.title });
|
||||
if (!step.element) step.orphan = true;
|
||||
if (step.snippet) {
|
||||
step.element = '#oe_snippets div.oe_snippet[data-snippet-id="'+step.snippet+'"] .oe_snippet_thumbnail';
|
||||
}
|
||||
if (step.trigger) {
|
||||
if (step.trigger === 'click') {
|
||||
step.triggers = function (callback) {
|
||||
$(step.element).one('click', function () {
|
||||
(callback || self.moveToNextStep).apply(self);
|
||||
});
|
||||
};
|
||||
} else if (step.trigger === 'reload') {
|
||||
step.triggers = function (callback) {
|
||||
var stack = JSON.parse(localStorage.getItem("website-reloads")) || [];
|
||||
var index = stack.indexOf(step.stepId);
|
||||
if (index !== -1) {
|
||||
stack.splice(index,1);
|
||||
(callback || self.moveToNextStep).apply(self);
|
||||
} else {
|
||||
stack.push(step.stepId);
|
||||
}
|
||||
localStorage.setItem("website-reloads", JSON.stringify(stack));
|
||||
};
|
||||
} else if (step.trigger === 'drag') {
|
||||
step.triggers = function (callback) {
|
||||
self.onSnippetDragged(callback || self.moveToNextStep);
|
||||
};
|
||||
} else if (step.trigger.id) {
|
||||
if (step.trigger.emitter && step.trigger.type === 'openerp') {
|
||||
step.triggers = function (callback) {
|
||||
step.trigger.emitter.on(step.trigger.id, self, function customHandler () {
|
||||
step.trigger.emitter.off(step.trigger.id, customHandler);
|
||||
(callback || self.moveToNextStep).apply(self, arguments);
|
||||
});
|
||||
};
|
||||
} else {
|
||||
step.triggers = function (callback) {
|
||||
var emitter = _.isString(step.trigger.emitter) ? $(step.trigger.emitter) : (step.trigger.emitter || $(step.element));
|
||||
if (!emitter.size()) throw "Emitter is undefined";
|
||||
emitter.on(step.trigger.id, function () {
|
||||
(callback || self.moveToNextStep).apply(self, arguments);
|
||||
});
|
||||
};
|
||||
}
|
||||
} else if (step.trigger.url) {
|
||||
step.triggers = function (callback) {
|
||||
var stack = JSON.parse(localStorage.getItem("website-geturls")) || [];
|
||||
var id = step.trigger.url.toString();
|
||||
var index = stack.indexOf(id);
|
||||
if (index !== -1) {
|
||||
var url = new website.UrlParser(window.location.href);
|
||||
var test = typeof step.trigger.url === "string" ?
|
||||
step.trigger.url == url.pathname+url.search :
|
||||
step.trigger.url.test(url.pathname+url.search);
|
||||
if (!test) return;
|
||||
stack.splice(index,1);
|
||||
(callback || self.moveToNextStep).apply(self);
|
||||
} else {
|
||||
stack.push(id);
|
||||
}
|
||||
localStorage.setItem("website-geturls", JSON.stringify(stack));
|
||||
return index !== -1;
|
||||
};
|
||||
} else if (step.trigger.modal) {
|
||||
step.triggers = function (callback, auto) {
|
||||
var $doc = $(document);
|
||||
function onStop () {
|
||||
if (step.trigger.modal.stopOnClose) {
|
||||
self.stop();
|
||||
}
|
||||
}
|
||||
$doc.on('hide.bs.modal', onStop);
|
||||
$doc.one('shown.bs.modal', function () {
|
||||
$('.modal button.btn-primary').one('click', function () {
|
||||
$doc.off('hide.bs.modal', onStop);
|
||||
console.log(callback);
|
||||
if (!callback) {
|
||||
self.moveToStep(step.trigger.modal.afterSubmit);
|
||||
}
|
||||
});
|
||||
(callback || self.moveToNextStep).apply(self);
|
||||
});
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
if ($(this.steps[index-1].template).has("button[data-role='next']").size()) {
|
||||
var step = {
|
||||
stepId: index,
|
||||
waitNot: '.popover.tour:visible'
|
||||
};
|
||||
this.steps.push(step);
|
||||
}
|
||||
|
||||
this.tour.addSteps(this.steps);
|
||||
},
|
||||
|
||||
popover: function (options) {
|
||||
return openerp.qweb.render('website.tour_popover', options);
|
||||
},
|
||||
|
||||
timer: null,
|
||||
testtimer: null,
|
||||
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 (step, callback, overlaps) {
|
||||
var self = this;
|
||||
var time = new Date().getTime();
|
||||
var timer;
|
||||
|
||||
window.onbeforeunload = function () {
|
||||
clearTimeout(self.timer);
|
||||
clearTimeout(self.testtimer);
|
||||
};
|
||||
|
||||
// check popover activity
|
||||
$(".popover.tour button")
|
||||
.off()
|
||||
.on("click", function () {
|
||||
$(".popover.tour").remove();
|
||||
if (step.busy) return;
|
||||
if (!$(this).is("[data-role='next']")) {
|
||||
clearTimeout(self.timer);
|
||||
step.busy = true;
|
||||
self.tour.end();
|
||||
self.endTour(callback);
|
||||
}
|
||||
}
|
||||
step.onShow = (function () {
|
||||
var executed = false;
|
||||
return function () {
|
||||
if (!executed) {
|
||||
_.isFunction(step.onStart) && step.onStart();
|
||||
_.isFunction(step.triggers) && step.triggers();
|
||||
executed = true;
|
||||
}
|
||||
};
|
||||
}());
|
||||
return step;
|
||||
},
|
||||
registerSteps: function () {
|
||||
var self = this;
|
||||
this.tour.addSteps(_.map(this.steps, function (step) {
|
||||
return self.registerStep(step);
|
||||
}));
|
||||
},
|
||||
reset: function () {
|
||||
this.tourStorage.removeItem(this.id+'_current_step');
|
||||
this.tourStorage.removeItem(this.id+'_end');
|
||||
this.tour._current = 0;
|
||||
$('.popover.tour').remove();
|
||||
},
|
||||
start: function () {
|
||||
if (this.resume() || ((this.currentStepIndex() === 0) && !this.tour.ended())) {
|
||||
this.tour.start();
|
||||
}
|
||||
},
|
||||
currentStepIndex: function () {
|
||||
var index = this.tourStorage.getItem(this.id+'_current_step') || 0;
|
||||
return parseInt(index, 10);
|
||||
},
|
||||
indexOfStep: function (stepId) {
|
||||
var index = -1;
|
||||
_.each(this.steps, function (step, i) {
|
||||
if (step.stepId === stepId) {
|
||||
index = i;
|
||||
}
|
||||
});
|
||||
return index;
|
||||
},
|
||||
isCurrentStep: function (stepId) {
|
||||
return this.currentStepIndex() === this.indexOfStep(stepId);
|
||||
},
|
||||
moveToStep: function (step) {
|
||||
var index = _.isNumber(step) ? step : this.indexOfStep(step);
|
||||
if (index >= this.steps.length) {
|
||||
this.stop();
|
||||
} else if (index >= 0) {
|
||||
var self = this;
|
||||
|
||||
function checkNext () {
|
||||
clearTimeout(self.timer);
|
||||
if (step.busy) return;
|
||||
if (self.check(step)) {
|
||||
step.busy = true;
|
||||
// use an other timeout for cke dom loading
|
||||
setTimeout(function () {
|
||||
$('.popover.tour').remove();
|
||||
setTimeout(function () {
|
||||
self.tour.goto(index);
|
||||
}, 0);
|
||||
}, 0);
|
||||
}
|
||||
},
|
||||
moveToNextStep: function () {
|
||||
var nextStepIndex = this.currentStepIndex() + 1;
|
||||
this.moveToStep(nextStepIndex);
|
||||
},
|
||||
stop: function () {
|
||||
this.tour.end();
|
||||
},
|
||||
redirect: function (url) {
|
||||
url = url || new website.UrlParser(window.location.href);
|
||||
var path = (this.path && url.pathname !== this.path) ? this.path : url.pathname;
|
||||
var search = url.activateTutorial(this.id);
|
||||
var newUrl = path + search;
|
||||
window.location.replace(newUrl);
|
||||
},
|
||||
ended: function () {
|
||||
return this.tourStorage.getItem(this.id+'_end') === "yes";
|
||||
},
|
||||
resume: function () {
|
||||
// Override if necessary
|
||||
return this.tourStorage.getItem(this.id+'_current_step') && !this.ended();
|
||||
},
|
||||
trigger: function (url) {
|
||||
// Override if necessary
|
||||
url = url || new website.UrlParser(window.location.href);
|
||||
return url.isActive(this.id);
|
||||
},
|
||||
testUrl: function (pattern) {
|
||||
var url = new website.UrlParser(window.location.href);
|
||||
return pattern.test(url.pathname+url.search);
|
||||
},
|
||||
popover: function (options) {
|
||||
return openerp.qweb.render('website.tour_popover', options);
|
||||
},
|
||||
onSnippetDragged: function (callback) {
|
||||
var self = this;
|
||||
var selector = '#website-top-navbar [data-snippet-id].ui-draggable';
|
||||
var beginDrag = function beginDrag () {
|
||||
$('.popover.tour').remove();
|
||||
var advance = function advance () {
|
||||
if (_.isFunction(callback)) {
|
||||
callback.apply(self);
|
||||
}
|
||||
$(selector).off('mouseup', advance);
|
||||
};
|
||||
$(document.body).one('mouseup', advance);
|
||||
$(selector).off('mousedown', beginDrag);
|
||||
};
|
||||
$(selector).one('mousedown', beginDrag);
|
||||
},
|
||||
onSnippetDraggedAdvance: function () {
|
||||
onSnippetDragged(self.moveToNextStep);
|
||||
},
|
||||
});
|
||||
|
||||
website.UrlParser = openerp.Class.extend({
|
||||
init: function (url) {
|
||||
var a = document.createElement('a');
|
||||
a.href = url;
|
||||
this.href = a.href;
|
||||
this.host = a.host;
|
||||
this.protocol = a.protocol;
|
||||
this.port = a.port;
|
||||
this.hostname = a.hostname;
|
||||
this.pathname = a.pathname;
|
||||
this.origin = a.origin;
|
||||
this.search = a.search;
|
||||
this.hash = a.hash;
|
||||
function generateTrigger (id) {
|
||||
return "tutorial."+id+"=true";
|
||||
}
|
||||
this.activateTutorial = function (id) {
|
||||
var urlTrigger = generateTrigger(id);
|
||||
var querystring = _.filter(this.search.split('?'), function (str) {
|
||||
return str;
|
||||
});
|
||||
if (querystring.length > 0) {
|
||||
var queries = _.filter(querystring[0].split("&"), function (query) {
|
||||
return query.indexOf("tutorial.") < 0
|
||||
});
|
||||
queries.push(urlTrigger);
|
||||
return "?"+_.uniq(queries).join("&");
|
||||
} else {
|
||||
return "?"+urlTrigger;
|
||||
}
|
||||
};
|
||||
this.isActive = function (id) {
|
||||
var urlTrigger = generateTrigger(id);
|
||||
return this.search.indexOf(urlTrigger) >= 0;
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
var TestConsole = openerp.Class.extend({
|
||||
tests: [],
|
||||
editor: null,
|
||||
init: function (editor) {
|
||||
if (!editor) {
|
||||
throw new Error("Editor cannot be null or undefined");
|
||||
}
|
||||
this.editor = editor;
|
||||
},
|
||||
test: function (id) {
|
||||
return _.find(this.tests, function (tour) {
|
||||
return tour.id === id;
|
||||
});
|
||||
},
|
||||
snippetSelector: function (snippetId) {
|
||||
return '#oe_snippets div.oe_snippet[data-snippet-id="'+snippetId+'"] .oe_snippet_thumbnail';
|
||||
},
|
||||
snippetThumbnail: function (snippetId) {
|
||||
return $(this.snippetSelector(snippetId)).first();
|
||||
},
|
||||
snippetThumbnailExists: function (snippetId) {
|
||||
return this.snippetThumbnail(snippetId).length > 0;
|
||||
},
|
||||
dragAndDropSnippet: function (snippetId) {
|
||||
function actualDragAndDrop ($thumbnail) {
|
||||
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 }));
|
||||
}
|
||||
if (this.snippetThumbnailExists(snippetId)) {
|
||||
actualDragAndDrop(this.snippetThumbnail(snippetId));
|
||||
self.nextStep(step.stepId, callback, overlaps);
|
||||
}, self.defaultDelay);
|
||||
} else if (!overlaps || new Date().getTime() - time < overlaps) {
|
||||
self.timer = setTimeout(checkNext, self.defaultDelay);
|
||||
} else {
|
||||
this.editor.on('rte:ready', this, function () {
|
||||
actualDragAndDrop(this.snippetThumbnail(snippetId));
|
||||
});
|
||||
self.reset();
|
||||
throw new Error("Time overlaps to arrive to step " + step.stepId + ": '" + step._title + "'");
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
checkNext();
|
||||
},
|
||||
step: function (stepId) {
|
||||
var steps = this.steps.slice(0,this.steps.length),
|
||||
step;
|
||||
while (step = steps.shift()) {
|
||||
if (!stepId || step.stepId === stepId)
|
||||
return step;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
next: function (stepId) {
|
||||
var steps = this.steps.slice(0,this.steps.length),
|
||||
step, next, index=0;
|
||||
while (step = steps.shift()) {
|
||||
if (!stepId || step.stepId === stepId) {
|
||||
// clear popover (fix for boostrap tour if the element is removed before destroy popover)
|
||||
$(".popover.tour").remove();
|
||||
// go to step in bootstrap tour
|
||||
this.tour.goto(index);
|
||||
if (step.callback) step.callback();
|
||||
next = steps.shift();
|
||||
break;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
return next;
|
||||
},
|
||||
nextStep: function (stepId, callback, overlaps) {
|
||||
var self = this;
|
||||
if (!this.localStorage.getItem("tour-"+this.id+"-test")) return;
|
||||
|
||||
this.localStorage.setItem("tour-"+this.id+"-test", stepId || 0);
|
||||
|
||||
website.EditorBar.include({
|
||||
tours: [],
|
||||
init: function () {
|
||||
var result = this._super();
|
||||
website.TestConsole = new TestConsole(this);
|
||||
return result;
|
||||
},
|
||||
start: function () {
|
||||
$('.tour-backdrop').click(function (e) {
|
||||
e.stopImmediatePropagation();
|
||||
e.preventDefault();
|
||||
});
|
||||
var menu = $('#help-menu');
|
||||
_.each(this.tours, function (tour) {
|
||||
var $menuItem = $($.parseHTML('<li><a href="#">'+tour.name+'</a></li>'));
|
||||
$menuItem.click(function () {
|
||||
tour.redirect(new website.UrlParser(window.location.href));
|
||||
tour.reset();
|
||||
tour.start();
|
||||
});
|
||||
menu.append($menuItem);
|
||||
if (tour.trigger()) {
|
||||
tour.start();
|
||||
this.current = this.step(stepId);
|
||||
var next = this.next(stepId);
|
||||
|
||||
if (next) {
|
||||
setTimeout(function () {
|
||||
self.waitNextStep(next, callback, overlaps);
|
||||
if (callback) setTimeout(function(){callback.call(self, next);}, self.defaultDelay);
|
||||
}, next && next.wait || 0);
|
||||
} else {
|
||||
this.endTour();
|
||||
}
|
||||
},
|
||||
endTour: function () {
|
||||
console.log('{ "event": "success" }');
|
||||
this.reset();
|
||||
},
|
||||
autoNextStep: function () {
|
||||
var self = this;
|
||||
clearTimeout(self.testtimer);
|
||||
|
||||
function autoStep () {
|
||||
var step = self.current;
|
||||
if (!step) return;
|
||||
|
||||
if (step.autoComplete) {
|
||||
step.autoComplete(tour);
|
||||
}
|
||||
|
||||
var $popover = $(".popover.tour");
|
||||
if ($popover.find("button[data-role='next']:visible").size()) {
|
||||
$popover.find("button[data-role='next']:visible").click();
|
||||
$popover.remove();
|
||||
}
|
||||
|
||||
var $element = $(step.element);
|
||||
if (!$element.size()) return;
|
||||
|
||||
if (step.snippet) {
|
||||
|
||||
var selector = '#oe_snippets div.oe_snippet[data-snippet-id="'+step.snippet+'"] .oe_snippet_thumbnail';
|
||||
self.autoDragAndDropSnippet(selector);
|
||||
|
||||
} else if (step.element.match(/#oe_snippets .* \.oe_snippet_thumbnail/)) {
|
||||
|
||||
self.autoDragAndDropSnippet($element);
|
||||
|
||||
} else if (step.sampleText) {
|
||||
|
||||
$element.trigger($.Event("keydown", { srcElement: $element }));
|
||||
if ($element.is("select") || $element.is("input") ) {
|
||||
$element.val(step.sampleText);
|
||||
} else {
|
||||
$element.html(step.sampleText);
|
||||
}
|
||||
});
|
||||
return this._super();
|
||||
},
|
||||
registerTour: function (tour) {
|
||||
var self = this;
|
||||
var testId = 'test_'+tour.id+'_tour';
|
||||
this.tours.push(tour);
|
||||
var defaultDelay = 250; //ms
|
||||
var defaultDelayReload = 1500; //ms
|
||||
var overlapsCrash;
|
||||
var test = {
|
||||
id: tour.id,
|
||||
run: function (force) {
|
||||
if (force === true) {
|
||||
this.reset();
|
||||
}
|
||||
var actionSteps = _.filter(tour.steps, function (step) {
|
||||
return step.trigger || step.sampleText;
|
||||
});
|
||||
window.onbeforeunload = function () {
|
||||
clearTimeout(overlapsCrash);
|
||||
};
|
||||
function throwError (message) {
|
||||
console.log(window.localStorage.getItem("test-report"));
|
||||
test.reset();
|
||||
throw message;
|
||||
}
|
||||
function initReport () {
|
||||
// set last time for report
|
||||
if (!window.localStorage.getItem("test-last-time")) {
|
||||
window.localStorage.setItem("test-last-time", new Date().getTime());
|
||||
}
|
||||
}
|
||||
function setReport (step) {
|
||||
var report = JSON.parse(window.localStorage.getItem("test-report")) || {};
|
||||
report[step.stepId] = (new Date().getTime() - window.localStorage.getItem("test-last-time")) + " ms";
|
||||
window.localStorage.setItem("test-report", JSON.stringify(report));
|
||||
}
|
||||
function testCycling (step) {
|
||||
var lastStep = window.localStorage.getItem(testId);
|
||||
var tryStep = lastStep != step.stepId ? 0 : (+(window.localStorage.getItem("test-last-"+testId) || 0) + 1);
|
||||
window.localStorage.setItem("test-last-"+testId, tryStep);
|
||||
if (tryStep > 2) {
|
||||
throwError("Test: '" + testId + "' cycling step: '" + step.stepId + "'");
|
||||
}
|
||||
return tryStep;
|
||||
}
|
||||
function getDelay (step) {
|
||||
return step.delay ||
|
||||
((step.trigger === 'reload' || (step.trigger && step.trigger.url))
|
||||
? defaultDelayReload
|
||||
: defaultDelay);
|
||||
}
|
||||
function executeStep (step) {
|
||||
if (testCycling(step) === 0) initReport();
|
||||
setTimeout(function () {
|
||||
$element.trigger($.Event("keyup", { srcElement: $element }));
|
||||
$element.trigger($.Event("change", { srcElement: $element }));
|
||||
}, self.defaultDelay<<1);
|
||||
|
||||
} else if ($element.is(":visible")) {
|
||||
|
||||
var delay = getDelay (step);
|
||||
$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);
|
||||
|
||||
overlapsCrash = setTimeout(function () {
|
||||
throwError("Test: '" + testId + "' can't resolve step: '" + step.stepId + "'");
|
||||
}, delay + 1000);
|
||||
|
||||
|
||||
var _next = false;
|
||||
window.localStorage.setItem(testId, step.stepId);
|
||||
function next () {
|
||||
_next = true;
|
||||
clearTimeout(overlapsCrash);
|
||||
|
||||
setReport(step);
|
||||
|
||||
var nextStep = actionSteps.shift();
|
||||
|
||||
if (nextStep) {
|
||||
setTimeout(function () {
|
||||
executeStep(nextStep);
|
||||
}, delay);
|
||||
} else {
|
||||
window.localStorage.removeItem(testId);
|
||||
}
|
||||
}
|
||||
|
||||
setTimeout(function () {
|
||||
var $element = $(step.element);
|
||||
var flag = step.triggers && (!step.trigger || !step.trigger.modal);
|
||||
if (flag) {
|
||||
try {
|
||||
step.triggers(next, true);
|
||||
} catch (e) {
|
||||
throwError(e);
|
||||
}
|
||||
}
|
||||
if ((step.trigger === 'reload' || (step.trigger && step.trigger.url)) && _next) return;
|
||||
|
||||
if (step.snippet && step.trigger === 'drag') {
|
||||
website.TestConsole.dragAndDropSnippet(step.snippet);
|
||||
} else if (step.trigger && step.trigger.id === 'change') {
|
||||
$element.trigger($.Event("change", { srcElement: $element }));
|
||||
} else if (step.sampleText) {
|
||||
$element.val(step.sampleText);
|
||||
$element.trigger($.Event("change", { srcElement: $element }));
|
||||
} else if ($element.is(":visible")) { // Click by default
|
||||
if (step.trigger.id === 'mousedown') {
|
||||
$element.trigger($.Event("mousedown", { srcElement: $element }));
|
||||
}
|
||||
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);
|
||||
if (step.trigger.id === 'mouseup') {
|
||||
$element.trigger($.Event("mouseup", { srcElement: $element }));
|
||||
}
|
||||
}
|
||||
if (!flag) next();
|
||||
},0);
|
||||
}
|
||||
var url = new website.UrlParser(window.location.href);
|
||||
if (tour.path && url.pathname !== tour.path && !window.localStorage.getItem(testId)) {
|
||||
window.localStorage.setItem(testId, actionSteps[0].stepId);
|
||||
window.location.href = tour.path;
|
||||
} else {
|
||||
var lastStepId = window.localStorage.getItem(testId);
|
||||
var currentStep = actionSteps.shift();
|
||||
if (lastStepId) {
|
||||
while (currentStep && lastStepId !== currentStep.stepId) {
|
||||
currentStep = actionSteps.shift();
|
||||
}
|
||||
}
|
||||
if (currentStep.snippet && $(currentStep.element).length === 0) {
|
||||
self.on('rte:ready', this, function () {
|
||||
executeStep(currentStep);
|
||||
});
|
||||
} else {
|
||||
executeStep(currentStep);
|
||||
}
|
||||
}
|
||||
},
|
||||
reset: function () {
|
||||
window.localStorage.removeItem(testId);
|
||||
window.localStorage.removeItem("test-report");
|
||||
for (var k in window.localStorage) {
|
||||
if (window.localStorage[k].indexOf("test-last")) {
|
||||
window.localStorage.removeItem(k);
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
website.TestConsole.tests.push(test);
|
||||
if (window.localStorage.getItem(testId)) {
|
||||
test.run();
|
||||
// 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);
|
||||
}
|
||||
},
|
||||
}
|
||||
self.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 }));
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
|
||||
website.Tour.tours = {};
|
||||
website.Tour.busy = false;
|
||||
website.Tour.add = function (tour) {
|
||||
website.Tour.waitReady(function () {
|
||||
tour = tour.id ? tour : new tour();
|
||||
website.Tour.tours[tour.id] = tour;
|
||||
});
|
||||
};
|
||||
website.Tour.get = function (id) {
|
||||
return website.Tour.tours[id];
|
||||
};
|
||||
website.Tour.each = function (callback) {
|
||||
website.Tour.waitReady(function () {
|
||||
for (var k in website.Tour.tours) {
|
||||
callback.call(website.Tour.tours[k]);
|
||||
}
|
||||
});
|
||||
};
|
||||
website.Tour.waitReady = function (callback) {
|
||||
var self = this;
|
||||
$(document).ready(function () {
|
||||
if ($.ajaxBusy == null || $.ajaxBusy) {
|
||||
$(document).ajaxStop(function() {
|
||||
setTimeout(function () {
|
||||
callback.call(self);
|
||||
},0);
|
||||
});
|
||||
}
|
||||
else {
|
||||
setTimeout(function () {
|
||||
callback.call(self);
|
||||
},0);
|
||||
}
|
||||
});
|
||||
};
|
||||
website.Tour.run_test = function (id) {
|
||||
website.Tour.get(id).trigger(true);
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
}());
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import collections
|
||||
import urllib
|
||||
import urlparse
|
||||
import unittest2
|
||||
import urllib2
|
||||
import werkzeug.urls
|
||||
|
||||
import lxml.html
|
||||
|
||||
|
@ -65,12 +65,12 @@ class CrawlSuite(unittest2.TestSuite):
|
|||
# blow up in multidb situations
|
||||
self.opener.open('http://localhost:{port}/web/?db={db}'.format(
|
||||
port=tools.config['xmlrpc_port'],
|
||||
db=urllib.quote_plus(tools.config['db_name']),
|
||||
db=werkzeug.url_quote_plus(tools.config['db_name']),
|
||||
))
|
||||
if user is not None:
|
||||
url = 'http://localhost:{port}/login?{query}'.format(
|
||||
port=tools.config['xmlrpc_port'],
|
||||
query=urllib.urlencode({
|
||||
query=werkzeug.url_encode({
|
||||
'db': tools.config['db_name'],
|
||||
'login': user,
|
||||
'key': password,
|
||||
|
|
|
@ -58,8 +58,9 @@ class WebsiteUiSuite(unittest.TestSuite):
|
|||
return iter([self])
|
||||
|
||||
def run(self, result):
|
||||
# clean slate
|
||||
if sql_db._Pool is not None:
|
||||
return
|
||||
# clean slate
|
||||
if sql_db._Pool is not None:
|
||||
sql_db._Pool.close_all(sql_db.dsn(tools.config['db_name']))
|
||||
# check for PhantomJS...
|
||||
try:
|
||||
|
|
|
@ -7,30 +7,11 @@ testRunner.run(function homepageTest (page, timeout) {
|
|||
waitFor(function clientReady () {
|
||||
return page.evaluate(function () {
|
||||
return window.$ && window.openerp && window.openerp.website
|
||||
&& window.openerp.website.TestConsole
|
||||
&& window.openerp.website.TestConsole.test
|
||||
&& window.openerp.website.TestConsole.test('banner');
|
||||
&& window.openerp.website.Tour;
|
||||
});
|
||||
}, function executeTest () {
|
||||
var before = page.evaluate(function () {
|
||||
var result = {
|
||||
carousel: $('#wrap [data-snippet-id=carousel]').length,
|
||||
columns: $('#wrap [data-snippet-id=three-columns]').length,
|
||||
};
|
||||
window.openerp.website.TestConsole.test('banner').run(true);
|
||||
return result;
|
||||
page.evaluate(function () {
|
||||
window.openerp.website.Tour.run_test('banner');
|
||||
});
|
||||
waitFor(function testExecuted () {
|
||||
var after = page.evaluate(function () {
|
||||
return window.$ && $('button[data-action=edit]').is(":visible") && {
|
||||
carousel: $('#wrap [data-snippet-id=carousel]').length,
|
||||
columns: $('#wrap [data-snippet-id=three-columns]').length,
|
||||
};
|
||||
});
|
||||
return after && (after.carousel === before.carousel + 1) && (after.columns === before.columns + 1);
|
||||
}, function finish () {
|
||||
console.log('{ "event": "success" }');
|
||||
phantom.exit();
|
||||
}, 4*timeout/5);
|
||||
}, timeout/5);
|
||||
}, timeout);
|
||||
});
|
||||
|
|
|
@ -372,22 +372,6 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div data-snippet-id='colmd' data-selector-vertical-children='.row'>
|
||||
<div class="oe_snippet_thumbnail">
|
||||
<img class="oe_snippet_thumbnail_img" src="/website/static/src/img/blocks/block_column.png"/>
|
||||
<span class="oe_snippet_thumbnail_title">Add Column</span>
|
||||
</div>
|
||||
<div class="oe_snippet_body col-md-4">
|
||||
<img class="img img-rounded img-responsive" src="/website/static/src/img/china_thumb.jpg"/>
|
||||
<h4 class="mt16">Feature</h4>
|
||||
<p>
|
||||
Delete the above image or replace it with a picture
|
||||
that illustrates your message. Click on the picture to
|
||||
change it's <em>rounded corner</em> style.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div id="snippet_feature" class="tab-pane fade">
|
||||
|
@ -878,6 +862,22 @@
|
|||
<!-- use to declare snippet for snippet editor -->
|
||||
<div id="snippet_hidden" class="hidden">
|
||||
|
||||
<div data-snippet-id='colmd' data-selector-vertical-children='.row'>
|
||||
<div class="oe_snippet_thumbnail">
|
||||
<img class="oe_snippet_thumbnail_img" src="/website/static/src/img/blocks/block_column.png"/>
|
||||
<span class="oe_snippet_thumbnail_title">Add Column</span>
|
||||
</div>
|
||||
<div class="oe_snippet_body col-md-4">
|
||||
<img class="img img-rounded img-responsive" src="/website/static/src/img/china_thumb.jpg"/>
|
||||
<h4 class="mt16">Feature</h4>
|
||||
<p>
|
||||
Delete the above image or replace it with a picture
|
||||
that illustrates your message. Click on the picture to
|
||||
change it's <em>rounded corner</em> style.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div id="snippet_styles" class="hidden">
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
Have a look at <a href="/">your homepage</a> or try another theme below.
|
||||
</p>
|
||||
</div>
|
||||
<h1 class="text-center">Try a New Theme</h1>
|
||||
<h1 class="text-center">Try a New Bootswatch Theme</h1>
|
||||
<h3 class="text-center text-muted">You'll be able to change the theme at anytime</h3>
|
||||
|
||||
<div class="row mt32" id="themes-list">
|
||||
|
@ -26,11 +26,11 @@
|
|||
<div class="col-md-4">
|
||||
<div class="well text-center">
|
||||
<div class="image">
|
||||
<img src="http://bootswatch.com/spacelab/thumbnail.png" class="img-responsive" alt="Bootstrap Theme"/>
|
||||
<img src="/website/static/src/img/bootswatch_default_thumbnail.png" class="img-responsive" alt="Default Theme"/>
|
||||
</div>
|
||||
<div class="options">
|
||||
<h3>Default</h3>
|
||||
<p>Pure bootstrap</p>
|
||||
<p>Pure Bootstrap</p>
|
||||
<a class="btn btn-info" href="/website/theme_change">Apply</a>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -178,6 +178,20 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<div class="well text-center">
|
||||
<div class="image">
|
||||
<img class="img-responsive" src="http://bootswatch.com/yeti/thumbnail.png" alt="Yeti"/>
|
||||
</div>
|
||||
<div class="options">
|
||||
<h3>Yeti</h3>
|
||||
<p>A friendly foundation</p>
|
||||
<a class="btn btn-info" href="/website/theme_change?theme_id=website.theme_yeti">Apply</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -255,5 +269,11 @@
|
|||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="website.theme_yeti" name="Yeti" inherit_option_id="website.theme">
|
||||
<xpath expr="//link[@id='bootstrap_css']" position="replace">
|
||||
<link rel='stylesheet' href='/website/static/src/css/bootswatch/yeti.min.css' t-ignore="true"/>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
((submenu.url != '/' and request.httprequest.path.startswith(submenu.url)) or
|
||||
request.httprequest.path == submenu.url) and 'active'
|
||||
">
|
||||
<a t-att-href="url_for(submenu.url)" t-ignore="true" t-att-target="'blank' if submenu.new_window else None">
|
||||
<a t-att-href="submenu.url" t-ignore="true" t-att-target="'blank' if submenu.new_window else None">
|
||||
<span t-field="submenu.name"/>
|
||||
</a>
|
||||
</li>
|
||||
|
@ -215,7 +215,7 @@
|
|||
<template id="layout_logo_show" inherit_option_id="website.layout" name="Show Logo">
|
||||
<xpath expr="//header//a[@class='navbar-brand']" position="replace">
|
||||
<a href="/" class="navbar-brand logo">
|
||||
<img src="/website/static/src/img/odoo_logo.png"/>
|
||||
<img src="/logo.png"/>
|
||||
</a>
|
||||
</xpath>
|
||||
</template>
|
||||
|
@ -415,7 +415,7 @@
|
|||
<div class="container">
|
||||
<div class="well mt32">
|
||||
<p>This page does not exists, but you can create it as you are administrator of this site.</p>
|
||||
<a class="btn btn-primary" t-attf-href="/pagenew/#{ path }">Create Page</a>
|
||||
<a class="btn btn-primary" t-attf-href="/website/add/#{ path }">Create Page</a>
|
||||
<span class="text-muted">or</span> <a href="/sitemap">Search a Page</a>
|
||||
</div>
|
||||
<div class="text-center text-muted">Edit the content below this line to adapt the default "page not found" page.</div>
|
||||
|
@ -451,7 +451,10 @@
|
|||
<div id="error_main" class="panel-collapse collapse in">
|
||||
<div class="panel-body">
|
||||
<p t-if="website_controller">The following error was raised in the website controller <code t-esc="website_controller"/></p>
|
||||
<p><strong>Error message:</strong> <pre t-esc="exception.message"/></p>
|
||||
<p>
|
||||
<strong>Error message:</strong>
|
||||
<pre t-esc="exception.message"/>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -466,11 +469,15 @@
|
|||
<div id="error_qweb" class="panel-collapse collapse">
|
||||
<div class="panel-body">
|
||||
<p>
|
||||
The error occured while rendering the template <code t-esc="qweb_exception.template"/>
|
||||
<t t-if="qweb_exception.expression">and evaluating the following expression: <code t-esc="qweb_exception.expression"/></t>
|
||||
<strong>Error message:</strong>
|
||||
<pre t-esc="exception.qweb['message']"/>
|
||||
</p>
|
||||
<t t-if="qweb_exception.node">
|
||||
<pre id="exception_node" t-esc="qweb_exception.node.toxml()"/>
|
||||
<p>
|
||||
The error occured while rendering the template <code t-esc="qweb_exception.qweb.get('template')"/>
|
||||
<t t-if="'expression' in qweb_exception.qweb">and evaluating the following expression: <code t-esc="qweb_exception.qweb['expression']"/></t>
|
||||
</p>
|
||||
<t t-if="'node' in qweb_exception.qweb">
|
||||
<pre id="exception_node" t-esc="qweb_exception.qweb['node'].toxml()"/>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -633,8 +640,8 @@ Sitemap: <t t-esc="url_root"/>sitemap.xml
|
|||
<address>
|
||||
<div t-field="res_company.name">Name</div>
|
||||
<br />
|
||||
<div>&#x2706; <span t-field="res_company.phone"></span></div>
|
||||
<div class="fa fa-envelope" t-field="res_company.email"></div>
|
||||
<div><i class="fa fa-phone"/> <span t-field="res_company.phone"/></div>
|
||||
<div><i class="fa fa-envelope"/> <span t-field="res_company.email"/></div>
|
||||
</address>
|
||||
<a t-att-href="res_company.google_map_link()" target="_BLANK">
|
||||
<img class="thumbnail img-responsive" t-att-src="res_company.google_map_img()" />
|
||||
|
|
|
@ -13,148 +13,107 @@
|
|||
website.BlogTour = website.Tour.extend({
|
||||
id: 'blog',
|
||||
name: "Create a blog post",
|
||||
testPath: /\/blogpost\/[0-9]+\//,
|
||||
init: function (editor) {
|
||||
var self = this;
|
||||
self.steps = [
|
||||
{
|
||||
stepId: 'welcome-blog',
|
||||
title: "New Blog Post",
|
||||
content: "Let's go through the first steps to write beautiful blog posts.",
|
||||
template: self.popover({ next: "Start Tutorial", end: "Skip" }),
|
||||
backdrop: true,
|
||||
},
|
||||
{
|
||||
stepId: 'content-menu',
|
||||
element: '#content-menu-button',
|
||||
placement: 'left',
|
||||
title: "Add Content",
|
||||
content: "Create new pages, blogs, menu items and products through the <em>'Content'</em> menu.",
|
||||
template: self.popover({ fixed: true }),
|
||||
trigger: 'click',
|
||||
},
|
||||
{
|
||||
stepId: 'new-post-entry',
|
||||
element: 'a[data-action=new_blog_post]',
|
||||
placement: 'left',
|
||||
title: "New Blog Post",
|
||||
content: "Select this menu item to create a new blog post.",
|
||||
template: self.popover({ fixed: true }),
|
||||
trigger: {
|
||||
modal: {
|
||||
stopOnClose: true,
|
||||
afterSubmit: 'post-page',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
stepId: 'continue-blog',
|
||||
element: '.modal button.btn-primary',
|
||||
placement: 'bottom',
|
||||
title: "Create Blog Post",
|
||||
content: "Click <em>Continue</em> to create the blog post.",
|
||||
trigger: {
|
||||
url: /blogpost\/[0-9]+\/.*/,
|
||||
},
|
||||
},
|
||||
{
|
||||
stepId: 'post-page',
|
||||
waitNot: '.modal',
|
||||
title: "Blog Post Created",
|
||||
content: "This is your new blog post. Let's edit it.",
|
||||
template: self.popover({ next: "Continue" }),
|
||||
},
|
||||
{
|
||||
stepId: 'post-title',
|
||||
element: 'h1[data-oe-expression="blog_post.name"]',
|
||||
placement: 'bottom',
|
||||
sampleText: 'New Blog',
|
||||
title: "Set a Title",
|
||||
content: "Click on this area and set a catchy title for your blog post.",
|
||||
template: self.popover({ next: "Continue" }),
|
||||
},
|
||||
{
|
||||
stepId: 'add-image-text',
|
||||
waitNot: '#wrap h1[data-oe-model="blog.post"]:contains("Blog Post Title")',
|
||||
element: 'button[data-action=snippet]',
|
||||
placement: 'bottom',
|
||||
title: "Layout Your Blog Post",
|
||||
content: "Use well designed building blocks to structure the content of your blog. Click 'Insert Blocks' to add new content.",
|
||||
template: self.popover({ fixed: true }),
|
||||
trigger: 'click',
|
||||
},
|
||||
{
|
||||
stepId: 'drag-image-text',
|
||||
snippet: 'image-text',
|
||||
placement: 'bottom',
|
||||
title: "Drag & Drop a Block",
|
||||
content: "Drag the <em>'Image-Text'</em> block and drop it in your page.",
|
||||
template: self.popover({ fixed: true }),
|
||||
trigger: 'drag',
|
||||
},
|
||||
{
|
||||
stepId: 'add-text-block',
|
||||
element: 'button[data-action=snippet]',
|
||||
placement: 'bottom',
|
||||
title: "Add Another Block",
|
||||
content: "Let's add another block to your post.",
|
||||
template: self.popover({ fixed: true }),
|
||||
trigger: 'click',
|
||||
},
|
||||
{
|
||||
stepId: 'drag-text-block',
|
||||
snippet: 'text-block',
|
||||
placement: 'bottom',
|
||||
title: "Drag & Drop a block",
|
||||
content: "Drag the <em>'Text Block'</em> block and drop it below the image block.",
|
||||
template: self.popover({ fixed: true }),
|
||||
trigger: 'drag',
|
||||
},
|
||||
{
|
||||
stepId: 'activate-text-block-title',
|
||||
element: '#wrap [data-snippet-id=text-block] .text-center[data-snippet-id=colmd]',
|
||||
placement: 'top',
|
||||
title: "Edit an Area",
|
||||
content: "Select any area of the page to modify it. Click on this subtitle.",
|
||||
trigger: {
|
||||
id: 'snippet-activated',
|
||||
}
|
||||
},
|
||||
{
|
||||
stepId: 'remove-text-block-title',
|
||||
element: '.oe_active .oe_snippet_remove',
|
||||
placement: 'top',
|
||||
title: "Delete the Title",
|
||||
content: "From this toolbar you can move, duplicate or delete the selected zone. Click on the garbage can image to delete the title.",
|
||||
trigger: 'click',
|
||||
},
|
||||
{
|
||||
stepId: 'save-changes',
|
||||
waitNot: '.oe_active .oe_snippet_remove:visible',
|
||||
element: 'button[data-action=save]',
|
||||
placement: 'right',
|
||||
title: "Save Your Blog",
|
||||
content: "Click the <em>Save</em> button to record changes on the page.",
|
||||
template: self.popover({ fixed: true }),
|
||||
trigger: 'reload',
|
||||
},
|
||||
{
|
||||
stepId: 'publish-post',
|
||||
waitFor: 'button[data-action=edit]:visible',
|
||||
element: 'button.btn-danger.js_publish_btn',
|
||||
placement: 'top',
|
||||
title: "Publish Your Post",
|
||||
content: "Your blog post is not yet published. You can update this draft version and publish it once you are ready.",
|
||||
trigger: 'click',
|
||||
delay: 5000,
|
||||
},
|
||||
{
|
||||
stepId: 'end-tutorial',
|
||||
waitFor: '.js_publish_management button.js_publish_btn.btn-success:visible',
|
||||
title: "Thanks!",
|
||||
content: "This tutorial is finished. To discover more features, improve the content of this page and try the <em>Promote</em> button in the top right menu.",
|
||||
template: self.popover({ end: "Close Tutorial" }),
|
||||
backdrop: true,
|
||||
},
|
||||
];
|
||||
return this._super();
|
||||
},
|
||||
trigger: function () {
|
||||
return (this.resume() && this.testUrl(/^\/blogpost\/[0-9]+\//)) || this._super();
|
||||
},
|
||||
});
|
||||
|
||||
}());
|
||||
|
|
|
@ -7,25 +7,11 @@ testRunner.run(function blogTest (page, timeout) {
|
|||
waitFor(function clientReady () {
|
||||
return page.evaluate(function () {
|
||||
return window.$ && window.openerp && window.openerp.website
|
||||
&& window.openerp.website.TestConsole
|
||||
&& window.openerp.website.TestConsole.test('blog');
|
||||
&& window.openerp.website.Tour;
|
||||
});
|
||||
}, function executeTest () {
|
||||
page.evaluate(function () {
|
||||
window.openerp.website.TestConsole.test('blog').run(true);
|
||||
window.openerp.website.Tour.run_test('blog');
|
||||
});
|
||||
waitFor(function testExecuted () {
|
||||
var after = page.evaluate(function () {
|
||||
return window.$ && $('button[data-action=edit]').is(":visible") && {
|
||||
image: $('#wrap [data-snippet-id=image-text]').length,
|
||||
text: $('#wrap [data-snippet-id=text-block]').length,
|
||||
};
|
||||
});
|
||||
var result = after && (after.image === 1) && (after.text === 1);
|
||||
return result;
|
||||
}, function finish () {
|
||||
console.log('{ "event": "success" }');
|
||||
phantom.exit();
|
||||
}, 4*timeout/5);
|
||||
}, timeout/5);
|
||||
}, timeout);
|
||||
});
|
|
@ -77,7 +77,7 @@
|
|||
</div>
|
||||
<div class="text-muted" t-if="len(blog.message_ids) > 0">
|
||||
<span class="fa fa-comment-o">
|
||||
<a t-attf-href="/blogpost/#{ slug(blog) }/?#{ tag and 'tag=%s' % tag.id or '' }#{tag and date and '&' or ''}#{ date and 'date=%s' % date or ''}#comment">
|
||||
<a t-attf-href="/blogpost/#{ slug(blog) }/?#{ tag and 'tag=%s' % tag.id or '' }#{tag and date and '&' or ''}#{ date and 'date=%s' % date or ''}#comments">
|
||||
<t t-if="len(blog.message_ids) <= 1" ><t t-esc="len(blog.message_ids)"/> comment</t>
|
||||
<t t-if="len(blog.message_ids) > 1"><t t-esc="len(blog.message_ids)"/> comments</t>
|
||||
</a>
|
||||
|
@ -119,7 +119,7 @@
|
|||
<p class="post-meta text-muted text-center" name='blog_post_data'>
|
||||
<span class="fa fa-calendar oe_date"> <span t-field="blog_post.create_date"/> &nbsp;</span>
|
||||
<span t-if="len(blog_post.message_ids) > 0" class="fa fa-comment-o">
|
||||
<a t-attf-href="/blogpost/#{ slug(blog_post) }/?#{ tag and 'tag=%s' % tag.id or '' }#{tag and date and '&' or ''}#{ date and 'date=%s' % date or ''}#comment">
|
||||
<a t-attf-href="/blogpost/#{ slug(blog_post) }/?#{ tag and 'tag=%s' % tag.id or '' }#{tag and date and '&' or ''}#{ date and 'date=%s' % date or ''}#comments">
|
||||
<t t-if="len(blog_post.message_ids) <= 1" ><t t-esc="len(blog_post.message_ids)"/> comment</t>
|
||||
<t t-if="len(blog_post.message_ids) > 1"><t t-esc="len(blog_post.message_ids)"/> comments</t>
|
||||
</a>
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue