[MERGE] from trunk
This commit is contained in:
commit
abab5c2e94
|
@ -956,7 +956,6 @@
|
|||
<field name="ref_tax_sign"/>
|
||||
</group>
|
||||
<group string="Children/Sub Taxes" colspan="2">
|
||||
<field name="child_depend" class="oe_inline"/>
|
||||
<field name="child_ids" nolabel="1" colspan="2">
|
||||
<tree string="Account Tax">
|
||||
<field name="sequence"/>
|
||||
|
|
|
@ -13,7 +13,7 @@ for customization purpose.
|
|||
'depends': ['web'],
|
||||
'installable': True,
|
||||
'auto_install': False,
|
||||
'data': [],
|
||||
'data': ['views/base_import_module.xml'],
|
||||
'qweb': [],
|
||||
'test': [],
|
||||
}
|
||||
|
|
|
@ -1,14 +1,8 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import functools
|
||||
import os
|
||||
import zipfile
|
||||
from os.path import join as opj
|
||||
|
||||
import openerp
|
||||
from openerp.http import Controller, route, request, Response
|
||||
|
||||
MAX_FILE_SIZE = 100 * 1024 * 1024 # in megabytes
|
||||
|
||||
def webservice(f):
|
||||
@functools.wraps(f)
|
||||
def wrap(*args, **kw):
|
||||
|
@ -42,32 +36,4 @@ class ImportModule(Controller):
|
|||
@webservice
|
||||
def upload(self, mod_file=None, **kw):
|
||||
self.check_user()
|
||||
imm = request.registry['ir.module.module']
|
||||
|
||||
if not mod_file:
|
||||
raise Exception("No file sent.")
|
||||
if not zipfile.is_zipfile(mod_file):
|
||||
raise Exception("Not a zipfile.")
|
||||
|
||||
success = []
|
||||
errors = dict()
|
||||
with zipfile.ZipFile(mod_file, "r") as z:
|
||||
for zf in z.filelist:
|
||||
if zf.file_size > MAX_FILE_SIZE:
|
||||
raise Exception("File '%s' exceed maximum allowed file size" % zf.filename)
|
||||
|
||||
with openerp.tools.osutil.tempdir() as module_dir:
|
||||
z.extractall(module_dir)
|
||||
dirs = [d for d in os.listdir(module_dir) if os.path.isdir(opj(module_dir, d))]
|
||||
for mod_name in dirs:
|
||||
try:
|
||||
# assert mod_name.startswith('theme_')
|
||||
path = opj(module_dir, mod_name)
|
||||
imm.import_module(request.cr, request.uid, mod_name, path, context=request.context)
|
||||
success.append(mod_name)
|
||||
except Exception, e:
|
||||
errors[mod_name] = str(e)
|
||||
r = ["Successfully imported module '%s'" % mod for mod in success]
|
||||
for mod, error in errors.items():
|
||||
r.append("Error while importing module '%s': %r" % (mod, error))
|
||||
return '\n'.join(r)
|
||||
return request.registry['ir.module.module'].import_zipfile(request.cr, request.uid, mod_file, context=request.context)[0]
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import ir_module
|
||||
import base_import_module
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
import base64
|
||||
from StringIO import StringIO
|
||||
from io import BytesIO
|
||||
from openerp.osv import osv, fields
|
||||
|
||||
class base_import_module(osv.TransientModel):
|
||||
""" Import Module """
|
||||
_name = "base.import.module"
|
||||
_description = "Import Module"
|
||||
|
||||
_columns = {
|
||||
'module_file': fields.binary('Module .ZIP file', required=True),
|
||||
'state':fields.selection([('init','init'),('done','done')], 'Status', readonly=True),
|
||||
'import_message': fields.char('Import message'),
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
'state': 'init',
|
||||
}
|
||||
|
||||
def import_module(self, cr, uid, ids, context=None):
|
||||
module_obj = self.pool.get('ir.module.module')
|
||||
data = self.browse(cr, uid, ids[0] , context=context)
|
||||
zip_data = base64.decodestring(data.module_file)
|
||||
fp = BytesIO()
|
||||
fp.write(zip_data)
|
||||
res = module_obj.import_zipfile(cr, uid, fp, context=context)
|
||||
self.write(cr, uid, ids, {'state': 'done', 'import_message': res[0]}, context=context)
|
||||
context = dict(context, module_name=res[1])
|
||||
# Return wizard otherwise it will close wizard and will not show result message to user.
|
||||
return {
|
||||
'name': 'Import Module',
|
||||
'view_type': 'form',
|
||||
'view_mode': 'form',
|
||||
'target': 'new',
|
||||
'res_id': ids[0],
|
||||
'res_model': 'base.import.module',
|
||||
'type': 'ir.actions.act_window',
|
||||
'context': context,
|
||||
}
|
||||
|
||||
def action_module_open(self, cr, uid, ids, context):
|
||||
return {
|
||||
'domain': [('name', 'in', context.get('module_name',[]))],
|
||||
'name': 'Modules',
|
||||
'view_type': 'form',
|
||||
'view_mode': 'tree,form',
|
||||
'res_model': 'ir.module.module',
|
||||
'view_id': False,
|
||||
'type': 'ir.actions.act_window',
|
||||
}
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
|
@ -1,14 +1,18 @@
|
|||
import logging
|
||||
import os
|
||||
import sys
|
||||
import zipfile
|
||||
from os.path import join as opj
|
||||
|
||||
import openerp
|
||||
from openerp.osv import osv
|
||||
from openerp.tools import convert_file
|
||||
from openerp.tools.translate import _
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
MAX_FILE_SIZE = 100 * 1024 * 1024 # in megabytes
|
||||
|
||||
class view(osv.osv):
|
||||
_inherit = "ir.module.module"
|
||||
|
||||
|
@ -22,7 +26,7 @@ class view(osv.osv):
|
|||
|
||||
unmet_dependencies = set(terp['depends']).difference(known_mods_names.keys())
|
||||
if unmet_dependencies:
|
||||
raise Exception("Unmet module dependencies: %s" % ', '.join(unmet_dependencies))
|
||||
raise osv.except_osv(_('Error !'), _("Unmet module dependencies: %s" % ', '.join(unmet_dependencies)))
|
||||
|
||||
if mod:
|
||||
self.write(cr, uid, mod.id, values)
|
||||
|
@ -69,3 +73,33 @@ class view(osv.osv):
|
|||
|
||||
return True
|
||||
|
||||
def import_zipfile(self, cr, uid, module_file, context=None):
|
||||
if not module_file:
|
||||
raise Exception("No file sent.")
|
||||
if not zipfile.is_zipfile(module_file):
|
||||
raise osv.except_osv(_('Error !'), _('File is not a zip file!'))
|
||||
|
||||
success = []
|
||||
errors = dict()
|
||||
module_names = []
|
||||
with zipfile.ZipFile(module_file, "r") as z:
|
||||
for zf in z.filelist:
|
||||
if zf.file_size > MAX_FILE_SIZE:
|
||||
raise osv.except_osv(_('Error !'), _("File '%s' exceed maximum allowed file size" % zf.filename))
|
||||
|
||||
with openerp.tools.osutil.tempdir() as module_dir:
|
||||
z.extractall(module_dir)
|
||||
dirs = [d for d in os.listdir(module_dir) if os.path.isdir(opj(module_dir, d))]
|
||||
for mod_name in dirs:
|
||||
module_names.append(mod_name)
|
||||
try:
|
||||
# assert mod_name.startswith('theme_')
|
||||
path = opj(module_dir, mod_name)
|
||||
self.import_module(cr, uid, mod_name, path, context=context)
|
||||
success.append(mod_name)
|
||||
except Exception, e:
|
||||
errors[mod_name] = str(e)
|
||||
r = ["Successfully imported module '%s'" % mod for mod in success]
|
||||
for mod, error in errors.items():
|
||||
r.append("Error while importing module '%s': %r" % (mod, error))
|
||||
return '\n'.join(r), module_names
|
||||
|
|
|
@ -3,49 +3,51 @@
|
|||
<data>
|
||||
|
||||
<record id="view_base_module_import" model="ir.ui.view">
|
||||
<field name="name">Module Import</field>
|
||||
<field name="model">base.module.import</field>
|
||||
<field name="name">Import Module</field>
|
||||
<field name="model">base.import.module</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Import module" version="7.0">
|
||||
<field name="state" invisible="1"/>
|
||||
<separator string="Module Import" colspan="4"/>
|
||||
<separator string="Import Module" colspan="4"/>
|
||||
<group states="init" col="4">
|
||||
<label string="Select module package to import (.zip file):" colspan="4"/>
|
||||
<field name="module_file" colspan="4"/>
|
||||
</group>
|
||||
<group states="done" col="4">
|
||||
<label string="Module file successfully imported!" colspan="4"/>
|
||||
<field name="import_message" colspan="4" nolabel="1" readonly="1"/>
|
||||
</group>
|
||||
<footer>
|
||||
<button name="importzip" string="Import module" type="object" states="init" class="oe_highlight"/>
|
||||
<label string="or" states="init"/>
|
||||
<button name="action_module_open" string="Open Modules" type="object" states="done" class="oe_highlight"/>
|
||||
<label string="or" states="done"/>
|
||||
<button string="Cancel" class="oe_link" special="cancel"/>
|
||||
<div states="init">
|
||||
<button name="import_module" string="Import Module" type="object" class="oe_highlight"/> or
|
||||
<button special="cancel" string="Cancel" class="oe_link"/>
|
||||
</div>
|
||||
<div states="done">
|
||||
<button name="action_module_open" string="Open Modules" type="object" class="oe_highlight"/> or
|
||||
<button special="cancel" string="Close" class="oe_link"/>
|
||||
</div>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_view_base_module_import" model="ir.actions.act_window">
|
||||
<field name="name">Module Import</field>
|
||||
<field name="name">Import Module</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">base.module.import</field>
|
||||
<field name="res_model">base.import.module</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="target">new</field>
|
||||
</record>
|
||||
|
||||
<!-- This feature is now deprecated, but may come back later.
|
||||
<menuitem
|
||||
name="Import Module"
|
||||
action="action_view_base_module_import"
|
||||
id="menu_view_base_module_import"
|
||||
parent="menu_management"
|
||||
parent="base.menu_management"
|
||||
groups="base.group_no_one"
|
||||
sequence="6"/>
|
||||
-->
|
||||
sequence="100"/>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
|
||||
|
|
@ -16,4 +16,7 @@ class AccountPaymentConfig(osv.TransientModel):
|
|||
'module_payment_adyen': fields.boolean(
|
||||
'Manage Payments Using Adyen',
|
||||
help='-It installs the module payment_adyen.'),
|
||||
'module_payment_buckaroo': fields.boolean(
|
||||
'Manage Payments Using Buckaroo',
|
||||
help='-It installs the module payment_buckaroo.'),
|
||||
}
|
||||
|
|
|
@ -20,6 +20,10 @@
|
|||
<field name="module_payment_adyen" class="oe_inline"/>
|
||||
<label for="module_payment_adyen"/>
|
||||
</div>
|
||||
<div>
|
||||
<field name="module_payment_buckaroo" class="oe_inline"/>
|
||||
<label for="module_payment_buckaroo"/>
|
||||
</div>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
<input t-if="tx_values.get('merchantReturnData')" type='hidden' name='merchantReturnData'
|
||||
t-att-value="tx_values.get('merchantReturnData')"/>
|
||||
<!-- submit -->
|
||||
<button type="image" name="submit" width="100px"
|
||||
<button type="submit" width="100px"
|
||||
t-att-class="submit_class">
|
||||
<img t-if="not submit_txt" src="/payment_adyen/static/src/img/adyen_icon.png"/>
|
||||
<span t-if="submit_txt"><t t-esc="submit_txt"/> <span class="fa fa-long-arrow-right"/></span>
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2014-Today OpenERP SA (<http://www.openerp.com>).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
import models
|
||||
import controllers
|
|
@ -0,0 +1,17 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
{
|
||||
'name': 'Buckaroo Payment Acquirer',
|
||||
'category': 'Hidden',
|
||||
'summary': 'Payment Acquirer: Buckaroo Implementation',
|
||||
'version': '1.0',
|
||||
'description': """Buckaroo Payment Acquirer""",
|
||||
'author': 'OpenERP SA',
|
||||
'depends': ['payment'],
|
||||
'data': [
|
||||
'views/buckaroo.xml',
|
||||
'views/payment_acquirer.xml',
|
||||
'data/buckaroo.xml',
|
||||
],
|
||||
'installable': True,
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
import main
|
|
@ -0,0 +1,38 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
try:
|
||||
import simplejson as json
|
||||
except ImportError:
|
||||
import json
|
||||
|
||||
import logging
|
||||
import pprint
|
||||
import werkzeug
|
||||
|
||||
from openerp import http, SUPERUSER_ID
|
||||
from openerp.http import request
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class BuckarooController(http.Controller):
|
||||
_return_url = '/payment/buckaroo/return'
|
||||
_cancel_url = '/payment/buckaroo/cancel'
|
||||
_exception_url = '/payment/buckaroo/error'
|
||||
_reject_url = '/payment/buckaroo/reject'
|
||||
|
||||
@http.route([
|
||||
'/payment/buckaroo/return',
|
||||
'/payment/buckaroo/cancel',
|
||||
'/payment/buckaroo/error',
|
||||
'/payment/buckaroo/reject',
|
||||
], type='http', auth='none')
|
||||
def buckaroo_return(self, **post):
|
||||
""" Buckaroo."""
|
||||
_logger.info('Buckaroo: entering form_feedback with post data %s', pprint.pformat(post)) # debug
|
||||
request.registry['payment.transaction'].form_feedback(request.cr, SUPERUSER_ID, post, 'buckaroo', context=request.context)
|
||||
return_url = post.pop('return_url', '')
|
||||
if not return_url:
|
||||
data ='' + post.pop('ADD_RETURNDATA', '{}').replace("'", "\"")
|
||||
custom = json.loads(data)
|
||||
return_url = custom.pop('return_url', '/')
|
||||
return werkzeug.utils.redirect(return_url)
|
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data noupdate="1">
|
||||
|
||||
<record id="payment_acquirer_buckaroo" model="payment.acquirer">
|
||||
<field name="name">Buckaroo</field>
|
||||
<field name="provider">buckaroo</field>
|
||||
<field name="company_id" ref="base.main_company"/>
|
||||
<field name="view_template_id" ref="buckaroo_acquirer_button"/>
|
||||
<field name="environment">test</field>
|
||||
<field name="pre_msg"><![CDATA[
|
||||
<p>You will be redirected to the Buckaroo website after cliking on the payment button.</p>]]></field>
|
||||
<field name="brq_websitekey">dummy</field>
|
||||
<field name="brq_secretkey">dummy</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
|
@ -0,0 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
import buckaroo
|
|
@ -0,0 +1,191 @@
|
|||
# -*- coding: utf-'8' "-*-"
|
||||
from hashlib import sha1
|
||||
import logging
|
||||
import urlparse
|
||||
|
||||
from openerp.addons.payment.models.payment_acquirer import ValidationError
|
||||
from openerp.addons.payment_buckaroo.controllers.main import BuckarooController
|
||||
from openerp.osv import osv, fields
|
||||
from openerp.tools.float_utils import float_compare
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AcquirerBuckaroo(osv.Model):
|
||||
_inherit = 'payment.acquirer'
|
||||
|
||||
def _get_buckaroo_urls(self, cr, uid, environment, context=None):
|
||||
""" Buckaroo URLs
|
||||
"""
|
||||
if environment == 'prod':
|
||||
return {
|
||||
'buckaroo_form_url': 'https://checkout.buckaroo.nl/html/',
|
||||
}
|
||||
else:
|
||||
return {
|
||||
'buckaroo_form_url': 'https://testcheckout.buckaroo.nl/html/',
|
||||
}
|
||||
|
||||
def _get_providers(self, cr, uid, context=None):
|
||||
providers = super(AcquirerBuckaroo, self)._get_providers(cr, uid, context=context)
|
||||
providers.append(['buckaroo', 'Buckaroo'])
|
||||
return providers
|
||||
|
||||
_columns = {
|
||||
'brq_websitekey': fields.char('WebsiteKey', required_if_provider='buckaroo'),
|
||||
'brq_secretkey': fields.char('SecretKey', required_if_provider='buckaroo'),
|
||||
}
|
||||
|
||||
def _buckaroo_generate_digital_sign(self, acquirer, inout, values):
|
||||
""" Generate the shasign for incoming or outgoing communications.
|
||||
|
||||
:param browse acquirer: the payment.acquirer browse record. It should
|
||||
have a shakey in shaky out
|
||||
:param string inout: 'in' (openerp contacting buckaroo) or 'out' (buckaroo
|
||||
contacting openerp).
|
||||
:param dict values: transaction values
|
||||
|
||||
:return string: shasign
|
||||
"""
|
||||
assert inout in ('in', 'out')
|
||||
assert acquirer.provider == 'buckaroo'
|
||||
|
||||
keys = "add_returndata Brq_amount Brq_culture Brq_currency Brq_invoicenumber Brq_return Brq_returncancel Brq_returnerror Brq_returnreject brq_test Brq_websitekey".split()
|
||||
|
||||
def get_value(key):
|
||||
if values.get(key):
|
||||
return values[key]
|
||||
return ''
|
||||
|
||||
if inout == 'out':
|
||||
if 'BRQ_SIGNATURE' in values:
|
||||
del values['BRQ_SIGNATURE']
|
||||
items = sorted((k.upper(), v) for k, v in values.items())
|
||||
sign = ''.join('%s=%s' % (k, v) for k, v in items)
|
||||
else:
|
||||
sign = ''.join('%s=%s' % (k,get_value(k)) for k in keys)
|
||||
#Add the pre-shared secret key at the end of the signature
|
||||
sign = sign + acquirer.brq_secretkey
|
||||
if isinstance(sign, str):
|
||||
sign = urlparse.parse_qsl(sign)
|
||||
shasign = sha1(sign).hexdigest()
|
||||
return shasign
|
||||
|
||||
|
||||
def buckaroo_form_generate_values(self, cr, uid, id, partner_values, tx_values, context=None):
|
||||
base_url = self.pool['ir.config_parameter'].get_param(cr, uid, 'web.base.url')
|
||||
acquirer = self.browse(cr, uid, id, context=context)
|
||||
buckaroo_tx_values = dict(tx_values)
|
||||
buckaroo_tx_values.update({
|
||||
'Brq_websitekey': acquirer.brq_websitekey,
|
||||
'Brq_amount': tx_values['amount'],
|
||||
'Brq_currency': tx_values['currency'] and tx_values['currency'].name or '',
|
||||
'Brq_invoicenumber': tx_values['reference'],
|
||||
'brq_test' : True,
|
||||
'Brq_return': '%s' % urlparse.urljoin(base_url, BuckarooController._return_url),
|
||||
'Brq_returncancel': '%s' % urlparse.urljoin(base_url, BuckarooController._cancel_url),
|
||||
'Brq_returnerror': '%s' % urlparse.urljoin(base_url, BuckarooController._exception_url),
|
||||
'Brq_returnreject': '%s' % urlparse.urljoin(base_url, BuckarooController._reject_url),
|
||||
'Brq_culture': 'en-US',
|
||||
})
|
||||
if buckaroo_tx_values.get('return_url'):
|
||||
buckaroo_tx_values['add_returndata'] = {'return_url': '%s' % buckaroo_tx_values.pop('return_url')}
|
||||
else:
|
||||
buckaroo_tx_values['add_returndata'] = ''
|
||||
buckaroo_tx_values['Brq_signature'] = self._buckaroo_generate_digital_sign(acquirer, 'in', buckaroo_tx_values)
|
||||
return partner_values, buckaroo_tx_values
|
||||
|
||||
def buckaroo_get_form_action_url(self, cr, uid, id, context=None):
|
||||
acquirer = self.browse(cr, uid, id, context=context)
|
||||
return self._get_buckaroo_urls(cr, uid, acquirer.environment, context=context)['buckaroo_form_url']
|
||||
|
||||
class TxBuckaroo(osv.Model):
|
||||
_inherit = 'payment.transaction'
|
||||
|
||||
# buckaroo status
|
||||
_buckaroo_valid_tx_status = [190]
|
||||
_buckaroo_pending_tx_status = [790, 791, 792, 793]
|
||||
_buckaroo_cancel_tx_status = [890, 891]
|
||||
_buckaroo_error_tx_status = [490, 491, 492]
|
||||
_buckaroo_reject_tx_status = [690]
|
||||
|
||||
_columns = {
|
||||
'buckaroo_txnid': fields.char('Transaction ID'),
|
||||
}
|
||||
|
||||
|
||||
# --------------------------------------------------
|
||||
# FORM RELATED METHODS
|
||||
# --------------------------------------------------
|
||||
|
||||
def _buckaroo_form_get_tx_from_data(self, cr, uid, data, context=None):
|
||||
""" Given a data dict coming from buckaroo, verify it and find the related
|
||||
transaction record. """
|
||||
reference, pay_id, shasign = data.get('BRQ_INVOICENUMBER'), data.get('BRQ_PAYMENT'), data.get('BRQ_SIGNATURE')
|
||||
if not reference or not pay_id or not shasign:
|
||||
error_msg = 'Buckaroo: received data with missing reference (%s) or pay_id (%s) or shashign (%s)' % (reference, pay_id, shasign)
|
||||
_logger.error(error_msg)
|
||||
raise ValidationError(error_msg)
|
||||
|
||||
tx_ids = self.search(cr, uid, [('reference', '=', reference)], context=context)
|
||||
if not tx_ids or len(tx_ids) > 1:
|
||||
error_msg = 'Buckaroo: received data for reference %s' % (reference)
|
||||
if not tx_ids:
|
||||
error_msg += '; no order found'
|
||||
else:
|
||||
error_msg += '; multiple order found'
|
||||
_logger.error(error_msg)
|
||||
raise ValidationError(error_msg)
|
||||
tx = self.pool['payment.transaction'].browse(cr, uid, tx_ids[0], context=context)
|
||||
|
||||
#verify shasign
|
||||
shasign_check = self.pool['payment.acquirer']._buckaroo_generate_digital_sign(tx.acquirer_id, 'out' ,data)
|
||||
if shasign_check.upper() != shasign.upper():
|
||||
error_msg = 'Buckaroo: invalid shasign, received %s, computed %s, for data %s' % (shasign, shasign_check, data)
|
||||
_logger.error(error_msg)
|
||||
raise ValidationError(error_msg)
|
||||
|
||||
return tx
|
||||
|
||||
def _buckaroo_form_get_invalid_parameters(self, cr, uid, tx, data, context=None):
|
||||
invalid_parameters = []
|
||||
|
||||
if tx.acquirer_reference and data.get('BRQ_TRANSACTIONS') != tx.acquirer_reference:
|
||||
invalid_parameters.append(('Transaction Id', data.get('BRQ_TRANSACTIONS'), tx.acquirer_reference))
|
||||
# check what is buyed
|
||||
if float_compare(float(data.get('BRQ_AMOUNT', '0.0')), tx.amount, 2) != 0:
|
||||
invalid_parameters.append(('Amount', data.get('BRQ_AMOUNT'), '%.2f' % tx.amount))
|
||||
if data.get('BRQ_CURRENCY') != tx.currency_id.name:
|
||||
invalid_parameters.append(('Currency', data.get('BRQ_CURRENCY'), tx.currency_id.name))
|
||||
|
||||
return invalid_parameters
|
||||
|
||||
def _buckaroo_form_validate(self, cr, uid, tx, data, context=None):
|
||||
status_code = int(data.get('BRQ_STATUSCODE','0'))
|
||||
if status_code in self._buckaroo_valid_tx_status:
|
||||
tx.write({
|
||||
'state': 'done',
|
||||
'buckaroo_txnid': data.get('BRQ_TRANSACTIONS'),
|
||||
})
|
||||
return True
|
||||
elif status_code in self._buckaroo_pending_tx_status:
|
||||
tx.write({
|
||||
'state': 'pending',
|
||||
'buckaroo_txnid': data.get('BRQ_TRANSACTIONS'),
|
||||
})
|
||||
return True
|
||||
elif status_code in self._buckaroo_cancel_tx_status:
|
||||
tx.write({
|
||||
'state': 'cancel',
|
||||
'buckaroo_txnid': data.get('BRQ_TRANSACTIONS'),
|
||||
})
|
||||
return True
|
||||
else:
|
||||
error = 'Buckaroo: feedback error'
|
||||
_logger.info(error)
|
||||
tx.write({
|
||||
'state': 'error',
|
||||
'state_message': error,
|
||||
'buckaroo_txnid': data.get('BRQ_TRANSACTIONS'),
|
||||
})
|
||||
return False
|
Binary file not shown.
After Width: | Height: | Size: 34 KiB |
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
Binary file not shown.
After Width: | Height: | Size: 34 KiB |
|
@ -0,0 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from openerp.addons.payment_buckaroo.tests import test_buckaroo
|
||||
|
||||
checks = [
|
||||
test_buckaroo,
|
||||
]
|
|
@ -0,0 +1,178 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from lxml import objectify
|
||||
import urlparse
|
||||
|
||||
import openerp
|
||||
from openerp.addons.payment.models.payment_acquirer import ValidationError
|
||||
from openerp.addons.payment.tests.common import PaymentAcquirerCommon
|
||||
from openerp.addons.payment_buckaroo.controllers.main import BuckarooController
|
||||
from openerp.tools import mute_logger
|
||||
|
||||
|
||||
@openerp.tests.common.at_install(False)
|
||||
@openerp.tests.common.post_install(False)
|
||||
class BuckarooCommon(PaymentAcquirerCommon):
|
||||
|
||||
def setUp(self):
|
||||
super(BuckarooCommon, self).setUp()
|
||||
cr, uid = self.cr, self.uid
|
||||
self.base_url = self.registry('ir.config_parameter').get_param(cr, uid, 'web.base.url')
|
||||
|
||||
# get the buckaroo account
|
||||
model, self.buckaroo_id = self.registry('ir.model.data').get_object_reference(cr, uid, 'payment_buckaroo', 'payment_acquirer_buckaroo')
|
||||
|
||||
|
||||
@openerp.tests.common.at_install(False)
|
||||
@openerp.tests.common.post_install(False)
|
||||
class BuckarooForm(BuckarooCommon):
|
||||
|
||||
def test_10_Buckaroo_form_render(self):
|
||||
cr, uid, context = self.cr, self.uid, {}
|
||||
# be sure not to do stupid things
|
||||
buckaroo = self.payment_acquirer.browse(self.cr, self.uid, self.buckaroo_id, None)
|
||||
self.assertEqual(buckaroo.environment, 'test', 'test without test environment')
|
||||
|
||||
# ----------------------------------------
|
||||
# Test: button direct rendering
|
||||
# ----------------------------------------
|
||||
|
||||
form_values = {
|
||||
'add_returndata': None,
|
||||
'Brq_websitekey': buckaroo.brq_websitekey,
|
||||
'Brq_amount': '2240.0',
|
||||
'Brq_currency': 'EUR',
|
||||
'Brq_invoicenumber': 'SO004',
|
||||
'Brq_signature': '1b8c10074c622d965272a91a9e88b5b3777d2474', # update me
|
||||
'brq_test': 'True',
|
||||
'Brq_return': '%s' % urlparse.urljoin(self.base_url, BuckarooController._return_url),
|
||||
'Brq_returncancel': '%s' % urlparse.urljoin(self.base_url, BuckarooController._cancel_url),
|
||||
'Brq_returnerror': '%s' % urlparse.urljoin(self.base_url, BuckarooController._exception_url),
|
||||
'Brq_returnreject': '%s' % urlparse.urljoin(self.base_url, BuckarooController._reject_url),
|
||||
'Brq_culture': 'en-US',
|
||||
}
|
||||
|
||||
# render the button
|
||||
res = self.payment_acquirer.render(
|
||||
cr, uid, self.buckaroo_id,
|
||||
'SO004', 2240.0, self.currency_euro_id,
|
||||
partner_id=None,
|
||||
partner_values=self.buyer_values,
|
||||
context=context)
|
||||
|
||||
# check form result
|
||||
tree = objectify.fromstring(res)
|
||||
self.assertEqual(tree.get('action'), 'https://testcheckout.buckaroo.nl/html/', 'Buckaroo: wrong form POST url')
|
||||
for form_input in tree.input:
|
||||
if form_input.get('name') in ['submit']:
|
||||
continue
|
||||
self.assertEqual(
|
||||
form_input.get('value'),
|
||||
form_values[form_input.get('name')],
|
||||
'Buckaroo: wrong value for input %s: received %s instead of %s' % (form_input.get('name'), form_input.get('value'), form_values[form_input.get('name')])
|
||||
)
|
||||
|
||||
# ----------------------------------------
|
||||
# Test2: button using tx + validation
|
||||
# ----------------------------------------
|
||||
|
||||
# create a new draft tx
|
||||
tx_id = self.payment_transaction.create(
|
||||
cr, uid, {
|
||||
'amount': 2240.0,
|
||||
'acquirer_id': self.buckaroo_id,
|
||||
'currency_id': self.currency_euro_id,
|
||||
'reference': 'SO004',
|
||||
'partner_id': self.buyer_id,
|
||||
}, context=context
|
||||
)
|
||||
|
||||
# render the button
|
||||
res = self.payment_acquirer.render(
|
||||
cr, uid, self.buckaroo_id,
|
||||
'should_be_erased', 2240.0, self.currency_euro,
|
||||
tx_id=tx_id,
|
||||
partner_id=None,
|
||||
partner_values=self.buyer_values,
|
||||
context=context)
|
||||
|
||||
# check form result
|
||||
tree = objectify.fromstring(res)
|
||||
self.assertEqual(tree.get('action'), 'https://testcheckout.buckaroo.nl/html/', 'Buckaroo: wrong form POST url')
|
||||
for form_input in tree.input:
|
||||
if form_input.get('name') in ['submit']:
|
||||
continue
|
||||
self.assertEqual(
|
||||
form_input.get('value'),
|
||||
form_values[form_input.get('name')],
|
||||
'Buckaroo: wrong value for form input %s: received %s instead of %s' % (form_input.get('name'), form_input.get('value'), form_values[form_input.get('name')])
|
||||
)
|
||||
|
||||
@mute_logger('openerp.addons.payment_buckaroo.models.buckaroo', 'ValidationError')
|
||||
def test_20_buckaroo_form_management(self):
|
||||
cr, uid, context = self.cr, self.uid, {}
|
||||
# be sure not to do stupid thing
|
||||
buckaroo = self.payment_acquirer.browse(self.cr, self.uid, self.buckaroo_id, None)
|
||||
self.assertEqual(buckaroo.environment, 'test', 'test without test environment')
|
||||
|
||||
# typical data posted by buckaroo after client has successfully paid
|
||||
buckaroo_post_data = {
|
||||
'BRQ_RETURNDATA': u'',
|
||||
'BRQ_AMOUNT': u'2240.00',
|
||||
'BRQ_CURRENCY': u'EUR',
|
||||
'BRQ_CUSTOMER_NAME': u'Jan de Tester',
|
||||
'BRQ_INVOICENUMBER': u'SO004',
|
||||
'BRQ_PAYMENT': u'573311D081B04069BD6336001611DBD4',
|
||||
'BRQ_PAYMENT_METHOD': u'paypal',
|
||||
'BRQ_SERVICE_PAYPAL_PAYERCOUNTRY': u'NL',
|
||||
'BRQ_SERVICE_PAYPAL_PAYEREMAIL': u'fhe@openerp.com',
|
||||
'BRQ_SERVICE_PAYPAL_PAYERFIRSTNAME': u'Jan',
|
||||
'BRQ_SERVICE_PAYPAL_PAYERLASTNAME': u'Tester',
|
||||
'BRQ_SERVICE_PAYPAL_PAYERMIDDLENAME': u'de',
|
||||
'BRQ_SERVICE_PAYPAL_PAYERSTATUS': u'verified',
|
||||
'BRQ_SIGNATURE': u'175d82dd53a02bad393fee32cb1eafa3b6fbbd91',
|
||||
'BRQ_STATUSCODE': u'190',
|
||||
'BRQ_STATUSCODE_DETAIL': u'S001',
|
||||
'BRQ_STATUSMESSAGE': u'Transaction successfully processed',
|
||||
'BRQ_TEST': u'true',
|
||||
'BRQ_TIMESTAMP': u'2014-05-08 12:41:21',
|
||||
'BRQ_TRANSACTIONS': u'D6106678E1D54EEB8093F5B3AC42EA7B',
|
||||
'BRQ_WEBSITEKEY': u'5xTGyGyPyl',
|
||||
}
|
||||
|
||||
# should raise error about unknown tx
|
||||
with self.assertRaises(ValidationError):
|
||||
self.payment_transaction.form_feedback(cr, uid, buckaroo_post_data, 'buckaroo', context=context)
|
||||
|
||||
tx_id = self.payment_transaction.create(
|
||||
cr, uid, {
|
||||
'amount': 2240.0,
|
||||
'acquirer_id': self.buckaroo_id,
|
||||
'currency_id': self.currency_euro_id,
|
||||
'reference': 'SO004',
|
||||
'partner_name': 'Norbert Buyer',
|
||||
'partner_country_id': self.country_france_id,
|
||||
}, context=context
|
||||
)
|
||||
# validate it
|
||||
self.payment_transaction.form_feedback(cr, uid, buckaroo_post_data, 'buckaroo', context=context)
|
||||
# check state
|
||||
tx = self.payment_transaction.browse(cr, uid, tx_id, context=context)
|
||||
self.assertEqual(tx.state, 'done', 'Buckaroo: validation did not put tx into done state')
|
||||
self.assertEqual(tx.buckaroo_txnid, buckaroo_post_data.get('BRQ_TRANSACTIONS'), 'Buckaroo: validation did not update tx payid')
|
||||
|
||||
# reset tx
|
||||
tx.write({'state': 'draft', 'date_validate': False, 'buckaroo_txnid': False})
|
||||
|
||||
# now buckaroo post is ok: try to modify the SHASIGN
|
||||
buckaroo_post_data['BRQ_SIGNATURE'] = '54d928810e343acf5fb0c3ee75fd747ff159ef7a'
|
||||
with self.assertRaises(ValidationError):
|
||||
self.payment_transaction.form_feedback(cr, uid, buckaroo_post_data, 'buckaroo', context=context)
|
||||
|
||||
# simulate an error
|
||||
buckaroo_post_data['BRQ_STATUSCODE'] = 2
|
||||
buckaroo_post_data['BRQ_SIGNATURE'] = '4164b52adb1e6a2221d3d8a39d8c3e18a9ecb90b'
|
||||
self.payment_transaction.form_feedback(cr, uid, buckaroo_post_data, 'buckaroo', context=context)
|
||||
# check state
|
||||
tx = self.payment_transaction.browse(cr, uid, tx_id, context=context)
|
||||
self.assertEqual(tx.state, 'error', 'Buckaroo: erroneous validation did not put tx into error state')
|
|
@ -0,0 +1,34 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data noupdate="1">
|
||||
|
||||
<template id="buckaroo_acquirer_button">
|
||||
<form t-if="acquirer.brq_websitekey" t-att-action="tx_url" method="post" target="_self">
|
||||
<input type="hidden" name="Brq_websitekey" t-att-value="tx_values['Brq_websitekey']"/>
|
||||
<input type="hidden" name="Brq_amount" t-att-value="tx_values['Brq_amount'] or '0.0'"/>
|
||||
<input type="hidden" name="Brq_currency" t-att-value="tx_values['Brq_currency']"/>
|
||||
<input type="hidden" name="Brq_invoicenumber" t-att-value="tx_values['Brq_invoicenumber']"/>
|
||||
<input type="hidden" name="Brq_signature" t-att-value="tx_values['Brq_signature']"/>
|
||||
<input type="hidden" name="brq_test" t-att-value="tx_values['brq_test']"/>
|
||||
<input type="hidden" name="Brq_culture" t-att-value="tx_values['Brq_culture']"/>
|
||||
<!-- URLs -->
|
||||
<input t-if="tx_values.get('Brq_return')" type='hidden' name='Brq_return'
|
||||
t-att-value="tx_values.get('Brq_return')"/>
|
||||
<input t-if="tx_values.get('Brq_returncancel')" type='hidden' name='Brq_returncancel'
|
||||
t-att-value="tx_values.get('Brq_returncancel')"/>
|
||||
<input t-if="tx_values.get('Brq_returnerror')" type='hidden' name='Brq_returnerror'
|
||||
t-att-value="tx_values.get('Brq_returnerror')"/>
|
||||
<input t-if="tx_values.get('Brq_returnreject')" type='hidden' name='Brq_returnreject'
|
||||
t-att-value="tx_values.get('Brq_returnreject')"/>
|
||||
<input type='hidden' name='add_returndata' t-att-value="tx_values.get('add_returndata')"/>
|
||||
<!-- submit -->
|
||||
<button type="submit" width="100px"
|
||||
t-att-class="submit_class">
|
||||
<img t-if="not submit_txt" src="/payment_buckaroo/static/src/img/buckaroo_icon.png"/>
|
||||
<span t-if="submit_txt"><t t-esc="submit_txt"/> <span class="fa fa-long-arrow-right"/></span>
|
||||
</button>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
</data>
|
||||
</openerp>
|
|
@ -0,0 +1,35 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
<record id="acquirer_form_buckaroo" model="ir.ui.view">
|
||||
<field name="name">acquirer.form.buckaroo</field>
|
||||
<field name="model">payment.acquirer</field>
|
||||
<field name="inherit_id" ref="payment.acquirer_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr='//group[@name="acquirer_display"]' position='after'>
|
||||
<group attrs="{'invisible': [('provider', '!=', 'buckaroo')]}">
|
||||
<field name="brq_websitekey"/>
|
||||
<field name="brq_secretkey"/>
|
||||
</group>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="transaction_form_buckaroo" model="ir.ui.view">
|
||||
<field name="name">acquirer.transaction.form.buckaroo</field>
|
||||
<field name="model">payment.transaction</field>
|
||||
<field name="inherit_id" ref="payment.transaction_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr='//notebook' position='inside'>
|
||||
<page string="Buckaroo TX Details">
|
||||
<group>
|
||||
<field name="buckaroo_txnid"/>
|
||||
</group>
|
||||
</page>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
|
@ -41,7 +41,7 @@
|
|||
<input type='hidden' name='EXCEPTIONURL' t-att-value='tx_values["EXCEPTIONURL"]'/>
|
||||
<input type='hidden' name='CANCELURL' t-att-value='tx_values["CANCELURL"]'/>
|
||||
<!-- submit -->
|
||||
<button type="image" name="submit" width="100px"
|
||||
<button type="submit" width="100px"
|
||||
t-att-class="submit_class">
|
||||
<img t-if="not submit_txt" src="/payment_ogone/static/src/img/ogone_icon.png"/>
|
||||
<span t-if="submit_txt"><t t-esc="submit_txt"/> <span class="fa fa-long-arrow-right"/></span>
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
<input t-if="tx_values.get('cancel_return')" type="hidden" name="cancel_return"
|
||||
t-att-value="tx_values.get('cancel_return')"/>
|
||||
<!-- submit -->
|
||||
<button type="image" name="submit" width="100px"
|
||||
<button type="submit" width="100px"
|
||||
t-att-class="submit_class">
|
||||
<img t-if="not submit_txt" src="/payment_paypal/static/src/img/paypal_icon.png"/>
|
||||
<span t-if="submit_txt"><t t-esc="submit_txt"/> <span class="fa fa-long-arrow-right"/></span>
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
<input type='hidden' name='amount' t-att-value='amount or "0.0"'/>
|
||||
<input type='hidden' name='currency' t-att-value='currency.name'/>
|
||||
<!-- submit -->
|
||||
<button name="submit" width="100px"
|
||||
<button type="submit" width="100px"
|
||||
t-att-class="submit_class">
|
||||
<img t-if="not submit_txt" src="/payment_transfer/static/src/img/transfer_icon.png"/>
|
||||
<span t-if="submit_txt"><t t-esc="submit_txt"/> <span class="fa fa-long-arrow-right"/></span>
|
||||
|
|
|
@ -3394,6 +3394,12 @@ instance.web.form.CompletionFieldMixin = {
|
|||
classname: 'oe_m2o_dropdown_option'
|
||||
});
|
||||
}
|
||||
else if (values.length == 0)
|
||||
values.push({
|
||||
label: _t("No results to show..."),
|
||||
action: function() {},
|
||||
classname: 'oe_m2o_dropdown_option'
|
||||
});
|
||||
|
||||
return values;
|
||||
});
|
||||
|
|
|
@ -108,8 +108,11 @@ class Website(openerp.addons.web.controllers.main.Home):
|
|||
locs = request.website.enumerate_pages()
|
||||
while True:
|
||||
start = pages * LOC_PER_SITEMAP
|
||||
loc_slice = islice(locs, start, start + LOC_PER_SITEMAP)
|
||||
urls = iuv.render(cr, uid, 'website.sitemap_locs', dict(locs=loc_slice), context=context)
|
||||
values = {
|
||||
'locs': islice(locs, start, start + LOC_PER_SITEMAP),
|
||||
'url_root': request.httprequest.url_root[:-1],
|
||||
}
|
||||
urls = iuv.render(cr, uid, 'website.sitemap_locs', values, context=context)
|
||||
if urls.strip():
|
||||
page = iuv.render(cr, uid, 'website.sitemap_xml', dict(content=urls), context=context)
|
||||
if not first_page:
|
||||
|
@ -155,22 +158,25 @@ class Website(openerp.addons.web.controllers.main.Home):
|
|||
@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']
|
||||
Views = request.registry['ir.ui.view']
|
||||
|
||||
view_model, view_option_id = imd.get_object_reference(
|
||||
_, theme_template_id = imd.get_object_reference(
|
||||
request.cr, request.uid, 'website', 'theme')
|
||||
views = view.search(
|
||||
request.cr, request.uid, [('inherit_id', '=', view_option_id)],
|
||||
context=request.context)
|
||||
view.write(request.cr, request.uid, views, {'inherit_id': False},
|
||||
context=request.context)
|
||||
views = Views.search(request.cr, request.uid, [
|
||||
('inherit_id', '=', theme_template_id),
|
||||
('application', '=', 'enabled'),
|
||||
], context=request.context)
|
||||
Views.write(request.cr, request.uid, views, {
|
||||
'application': 'disabled',
|
||||
}, context=request.context)
|
||||
|
||||
if theme_id:
|
||||
module, xml_id = theme_id.split('.')
|
||||
view_model, view_id = imd.get_object_reference(
|
||||
_, view_id = imd.get_object_reference(
|
||||
request.cr, request.uid, module, xml_id)
|
||||
view.write(request.cr, request.uid, [view_id],
|
||||
{'inherit_id': view_option_id}, context=request.context)
|
||||
Views.write(request.cr, request.uid, [view_id], {
|
||||
'application': 'enabled'
|
||||
}, context=request.context)
|
||||
|
||||
return request.render('website.themes', {'theme_changed': True})
|
||||
|
||||
|
@ -194,54 +200,45 @@ class Website(openerp.addons.web.controllers.main.Home):
|
|||
module_obj.button_immediate_upgrade(request.cr, request.uid, module_ids, context=request.context)
|
||||
return request.redirect(redirect)
|
||||
|
||||
@http.route('/website/customize_template_toggle', type='json', auth='user', website=True)
|
||||
def customize_template_set(self, view_id):
|
||||
view_obj = request.registry.get("ir.ui.view")
|
||||
view = view_obj.browse(request.cr, request.uid, int(view_id),
|
||||
context=request.context)
|
||||
if view.inherit_id:
|
||||
value = False
|
||||
else:
|
||||
value = view.inherit_option_id and view.inherit_option_id.id or False
|
||||
view_obj.write(request.cr, request.uid, [view_id], {
|
||||
'inherit_id': value
|
||||
}, context=request.context)
|
||||
return True
|
||||
|
||||
@http.route('/website/customize_template_get', type='json', auth='user', website=True)
|
||||
def customize_template_get(self, xml_id, optional=True):
|
||||
def customize_template_get(self, xml_id, full=False):
|
||||
""" Lists the templates customizing ``xml_id``. By default, only
|
||||
returns optional templates (which can be toggled on and off), if
|
||||
``full=True`` returns all templates customizing ``xml_id``
|
||||
"""
|
||||
imd = request.registry['ir.model.data']
|
||||
view_model, view_theme_id = imd.get_object_reference(
|
||||
request.cr, request.uid, 'website', 'theme')
|
||||
|
||||
user = request.registry['res.users'].browse(request.cr, request.uid, request.uid, request.context)
|
||||
group_ids = [g.id for g in user.groups_id]
|
||||
user = request.registry['res.users']\
|
||||
.browse(request.cr, request.uid, request.uid, request.context)
|
||||
user_groups = set(user.groups_id)
|
||||
|
||||
view = request.registry.get("ir.ui.view")
|
||||
views = view._views_get(request.cr, request.uid, xml_id, context=request.context)
|
||||
done = {}
|
||||
views = request.registry["ir.ui.view"]\
|
||||
._views_get(request.cr, request.uid, xml_id, context=request.context)
|
||||
done = set()
|
||||
result = []
|
||||
for v in views:
|
||||
if v.groups_id and [g for g in v.groups_id if g.id not in group_ids]:
|
||||
if not user_groups.issuperset(v.groups_id):
|
||||
continue
|
||||
if v.inherit_option_id and v.inherit_option_id.id != view_theme_id or not optional:
|
||||
if v.inherit_option_id.id not in done:
|
||||
if full or (v.application != 'always' and v.inherit_id.id != view_theme_id):
|
||||
if v.inherit_id not in done:
|
||||
result.append({
|
||||
'name': v.inherit_option_id.name,
|
||||
'name': v.inherit_id.name,
|
||||
'id': v.id,
|
||||
'xml_id': v.xml_id,
|
||||
'inherit_id': v.inherit_id.id,
|
||||
'header': True,
|
||||
'active': False
|
||||
})
|
||||
done[v.inherit_option_id.id] = True
|
||||
done.add(v.inherit_id)
|
||||
result.append({
|
||||
'name': v.name,
|
||||
'id': v.id,
|
||||
'xml_id': v.xml_id,
|
||||
'inherit_id': v.inherit_id.id,
|
||||
'header': False,
|
||||
'active': (v.inherit_id.id == v.inherit_option_id.id) or (not optional and v.inherit_id.id)
|
||||
'active': v.application in ('always', 'enabled'),
|
||||
})
|
||||
return result
|
||||
|
||||
|
|
|
@ -13,8 +13,6 @@ from openerp.osv import osv, fields
|
|||
class view(osv.osv):
|
||||
_inherit = "ir.ui.view"
|
||||
_columns = {
|
||||
'inherit_option_id': fields.many2one('ir.ui.view','Optional Inheritancy'),
|
||||
'inherited_option_ids': fields.one2many('ir.ui.view','inherit_option_id','Optional Inheritancies'),
|
||||
'page': fields.boolean("Whether this view is a web page template (complete)"),
|
||||
'website_meta_title': fields.char("Website meta title", size=70, translate=True),
|
||||
'website_meta_description': fields.text("Website meta description", size=160, translate=True),
|
||||
|
@ -24,25 +22,30 @@ class view(osv.osv):
|
|||
'page': False,
|
||||
}
|
||||
|
||||
|
||||
def _view_obj(self, cr, uid, view_id, context=None):
|
||||
if isinstance(view_id, basestring):
|
||||
return self.pool['ir.model.data'].xmlid_to_object(
|
||||
cr, uid, view_id, raise_if_not_found=True, context=context
|
||||
)
|
||||
elif isinstance(view_id, (int, long)):
|
||||
return self.browse(cr, uid, view_id, context=context)
|
||||
|
||||
# assume it's already a view object (WTF?)
|
||||
return view_id
|
||||
|
||||
# Returns all views (called and inherited) related to a view
|
||||
# Used by translation mechanism, SEO and optional templates
|
||||
def _views_get(self, cr, uid, view, options=True, context=None, root=True, stack_result=None):
|
||||
if not context:
|
||||
context = {}
|
||||
if not stack_result:
|
||||
stack_result = []
|
||||
|
||||
def view_obj(view):
|
||||
if isinstance(view, basestring):
|
||||
mod_obj = self.pool.get("ir.model.data")
|
||||
m, n = view.split('.')
|
||||
view = mod_obj.get_object(cr, uid, m, n, context=context)
|
||||
elif isinstance(view, (int, long)):
|
||||
view = self.pool.get("ir.ui.view").browse(cr, uid, view, context=context)
|
||||
return view
|
||||
def _views_get(self, cr, uid, view_id, options=True, context=None, root=True):
|
||||
""" For a given view ``view_id``, should return:
|
||||
|
||||
* the view itself
|
||||
* all views inheriting from it, enabled or not
|
||||
- but not the optional children of a non-enabled child
|
||||
* all views called from it (via t-call)
|
||||
"""
|
||||
try:
|
||||
view = view_obj(view)
|
||||
view = self._view_obj(cr, uid, view_id, context=context)
|
||||
except ValueError:
|
||||
# Shall we log that ?
|
||||
return []
|
||||
|
@ -55,19 +58,25 @@ class view(osv.osv):
|
|||
node = etree.fromstring(view.arch)
|
||||
for child in node.xpath("//t[@t-call]"):
|
||||
try:
|
||||
call_view = view_obj(child.get('t-call'))
|
||||
called_view = self._view_obj(cr, uid, child.get('t-call'), context=context)
|
||||
except ValueError:
|
||||
continue
|
||||
if call_view not in result:
|
||||
result += self._views_get(cr, uid, call_view, options=options, context=context, stack_result=result)
|
||||
if called_view not in result:
|
||||
result += self._views_get(cr, uid, called_view, options=options, context=context)
|
||||
|
||||
todo = view.inherit_children_ids
|
||||
if options:
|
||||
todo += filter(lambda x: not x.inherit_id, view.inherited_option_ids)
|
||||
# Keep options in a determinitic order whatever their enabled disabled status
|
||||
todo.sort(lambda x,y:cmp(x.id,y.id))
|
||||
for child_view in todo:
|
||||
for r in self._views_get(cr, uid, child_view, options=bool(child_view.inherit_id), context=context, root=False, stack_result=result):
|
||||
extensions = view.inherit_children_ids
|
||||
if not options:
|
||||
# only active children
|
||||
extensions = (v for v in view.inherit_children_ids
|
||||
if v.application in ('always', 'enabled'))
|
||||
|
||||
# Keep options in a deterministic order regardless of their applicability
|
||||
for extension in sorted(extensions, key=lambda v: v.id):
|
||||
for r in self._views_get(
|
||||
cr, uid, extension,
|
||||
# only return optional grandchildren if this child is enabled
|
||||
options=extension.application in ('always', 'enabled'),
|
||||
context=context, root=False):
|
||||
if r not in result:
|
||||
result.append(r)
|
||||
return result
|
||||
|
|
|
@ -97,7 +97,7 @@
|
|||
var viewId = $(document.documentElement).data('view-xmlid');
|
||||
openerp.jsonRpc('/website/customize_template_get', 'call', {
|
||||
'xml_id': viewId,
|
||||
'optional': false,
|
||||
'full': true,
|
||||
}).then(function (views) {
|
||||
self.loadViews.call(self, views);
|
||||
self.open.call(self);
|
||||
|
|
|
@ -470,8 +470,14 @@
|
|||
});
|
||||
menu.on('click', 'a[data-action!=ace]', function (event) {
|
||||
var view_id = $(event.currentTarget).data('view-id');
|
||||
openerp.jsonRpc('/website/customize_template_toggle', 'call', {
|
||||
'view_id': view_id
|
||||
return openerp.jsonRpc('/web/dataset/call_kw', 'call', {
|
||||
model: 'ir.ui.view',
|
||||
method: 'toggle',
|
||||
args: [],
|
||||
kwargs: {
|
||||
ids: [parseInt(view_id, 10)],
|
||||
context: website.get_context()
|
||||
}
|
||||
}).then( function() {
|
||||
window.location.reload();
|
||||
});
|
||||
|
|
|
@ -203,82 +203,82 @@
|
|||
All Default Themes
|
||||
-->
|
||||
|
||||
<template id="website.theme_amelia" name="Amelia" inherit_option_id="website.theme">
|
||||
<template id="website.theme_amelia" name="Amelia" inherit_id="website.theme" optional="disabled">
|
||||
<xpath expr="//link[@id='bootstrap_css']" position="replace">
|
||||
<link rel='stylesheet' href='/website/static/src/css/bootswatch/amelia.min.css' t-ignore="true"/>
|
||||
<link rel='stylesheet' href='/website/static/src/css/bootswatch/amelia.fix.css' t-ignore="true"/>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="website.theme_cerulean" name="Cerulean" inherit_option_id="website.theme">
|
||||
<template id="website.theme_cerulean" name="Cerulean" inherit_id="website.theme" optional="disabled">
|
||||
<xpath expr="//link[@id='bootstrap_css']" position="replace">
|
||||
<link rel='stylesheet' href='/website/static/src/css/bootswatch/cerulean.min.css' t-ignore="true"/>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="website.theme_cosmo" name="Cosmo" inherit_option_id="website.theme">
|
||||
<template id="website.theme_cosmo" name="Cosmo" inherit_id="website.theme" optional="disabled">
|
||||
<xpath expr="//link[@id='bootstrap_css']" position="replace">
|
||||
<link rel='stylesheet' href='/website/static/src/css/bootswatch/cosmo.min.css' t-ignore="true"/>
|
||||
<link rel='stylesheet' href='/website/static/src/css/bootswatch/cosmo.fix.css' t-ignore="true"/>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="website.theme_cyborg" name="Cyborg" inherit_option_id="website.theme">
|
||||
<template id="website.theme_cyborg" name="Cyborg" inherit_id="website.theme" optional="disabled">
|
||||
<xpath expr="//link[@id='bootstrap_css']" position="replace">
|
||||
<link rel='stylesheet' href='/website/static/src/css/bootswatch/cyborg.min.css' t-ignore="true"/>
|
||||
<link rel='stylesheet' href='/website/static/src/css/bootswatch/cyborg.fix.css' t-ignore="true"/>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="website.theme_flatly" name="Flatly" inherit_option_id="website.theme">
|
||||
<template id="website.theme_flatly" name="Flatly" inherit_id="website.theme" optional="disabled">
|
||||
<xpath expr="//link[@id='bootstrap_css']" position="replace">
|
||||
<link rel='stylesheet' href='/website/static/src/css/bootswatch/flatly.min.css' t-ignore="true"/>
|
||||
<link rel='stylesheet' href='/website/static/src/css/bootswatch/flatly.fix.css' t-ignore="true"/>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="website.theme_journal" name="Journal" inherit_option_id="website.theme">
|
||||
<template id="website.theme_journal" name="Journal" inherit_id="website.theme" optional="disabled">
|
||||
<xpath expr="//link[@id='bootstrap_css']" position="replace">
|
||||
<link rel='stylesheet' href='/website/static/src/css/bootswatch/journal.min.css' t-ignore="true"/>
|
||||
<link rel='stylesheet' href='/website/static/src/css/bootswatch/journal.fix.css' t-ignore="true"/>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="website.theme_readable" name="Readable" inherit_option_id="website.theme">
|
||||
<template id="website.theme_readable" name="Readable" inherit_id="website.theme" optional="disabled">
|
||||
<xpath expr="//link[@id='bootstrap_css']" position="replace">
|
||||
<link rel='stylesheet' href='/website/static/src/css/bootswatch/readable.min.css' t-ignore="true"/>
|
||||
<link rel='stylesheet' href='/website/static/src/css/bootswatch/readable.fix.css' t-ignore="true"/>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="website.theme_simplex" name="Simplex" inherit_option_id="website.theme">
|
||||
<template id="website.theme_simplex" name="Simplex" inherit_id="website.theme" optional="disabled">
|
||||
<xpath expr="//link[@id='bootstrap_css']" position="replace">
|
||||
<link rel='stylesheet' href='/website/static/src/css/bootswatch/simplex.min.css' t-ignore="true"/>
|
||||
<link rel='stylesheet' href='/website/static/src/css/bootswatch/simplex.fix.css' t-ignore="true"/>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="website.theme_slate" name="Slate" inherit_option_id="website.theme">
|
||||
<template id="website.theme_slate" name="Slate" inherit_id="website.theme" optional="disabled">
|
||||
<xpath expr="//link[@id='bootstrap_css']" position="replace">
|
||||
<link rel='stylesheet' href='/website/static/src/css/bootswatch/slate.min.css' t-ignore="true"/>
|
||||
<link rel='stylesheet' href='/website/static/src/css/bootswatch/slate.fix.css' t-ignore="true"/>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="website.theme_spacelab" name="Spacelab" inherit_option_id="website.theme">
|
||||
<template id="website.theme_spacelab" name="Spacelab" inherit_id="website.theme" optional="disabled">
|
||||
<xpath expr="//link[@id='bootstrap_css']" position="replace">
|
||||
<link rel='stylesheet' href='/website/static/src/css/bootswatch/spacelab.min.css' t-ignore="true"/>
|
||||
<link rel='stylesheet' href='/website/static/src/css/bootswatch/spacelab.fix.css' t-ignore="true"/>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="website.theme_united" name="United" inherit_option_id="website.theme">
|
||||
<template id="website.theme_united" name="United" inherit_id="website.theme" optional="disabled">
|
||||
<xpath expr="//link[@id='bootstrap_css']" position="replace">
|
||||
<link rel='stylesheet' href='/website/static/src/css/bootswatch/united.min.css' t-ignore="true"/>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="website.theme_yeti" name="Yeti" inherit_option_id="website.theme">
|
||||
<template id="website.theme_yeti" name="Yeti" inherit_id="website.theme" optional="disabled">
|
||||
<xpath expr="//link[@id='bootstrap_css']" position="replace">
|
||||
<link rel='stylesheet' href='/website/static/src/css/bootswatch/yeti.min.css' t-ignore="true"/>
|
||||
<link rel='stylesheet' href='/website/static/src/css/bootswatch/yeti.fix.css' t-ignore="true"/>
|
||||
|
|
|
@ -220,7 +220,7 @@
|
|||
</html>
|
||||
</template>
|
||||
|
||||
<template id="layout_logo_show" inherit_id="website.layout" inherit_option_id="website.layout" name="Show Logo">
|
||||
<template id="layout_logo_show" inherit_id="website.layout" optional="enabled" name="Show Logo">
|
||||
<xpath expr="//header//a[@class='navbar-brand']" position="replace">
|
||||
<a href="/" class="navbar-brand logo">
|
||||
<img src="/logo.png"/>
|
||||
|
@ -304,7 +304,7 @@
|
|||
<script type="text/javascript" src="/website/static/src/js/jQuery.transfo.js"></script>
|
||||
</template>
|
||||
|
||||
<template id="debugger" inherit_option_id="website.layout" name="Debugger & Tests">
|
||||
<template id="debugger" inherit_id="website.layout" optional="disabled" name="Debugger & Tests">
|
||||
<xpath expr='//t[@name="layout_head"]' position="after">
|
||||
<script type="text/javascript" src="/website/static/src/js/website.tour.js"></script>
|
||||
</xpath>
|
||||
|
@ -318,7 +318,7 @@
|
|||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="show_sign_in" inherit_option_id="website.layout" inherit_id="website.layout" name="Show Sign In" groups="base.group_public">
|
||||
<template id="show_sign_in" optional="enabled" inherit_id="website.layout" name="Show Sign In" groups="base.group_public">
|
||||
<xpath expr="//ul[@id='top_menu']" position="inside">
|
||||
<li class="divider"/>
|
||||
<li>
|
||||
|
@ -329,7 +329,7 @@
|
|||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="footer_custom" inherit_option_id="website.layout" name="Custom Footer">
|
||||
<template id="footer_custom" inherit_id="website.layout" optional="disabled" name="Custom Footer">
|
||||
<xpath expr="//div[@id='footer_container']" position="before">
|
||||
<div class="oe_structure">
|
||||
<section data-snippet-id='three-columns' class="mt16 mb16">
|
||||
|
|
|
@ -161,7 +161,7 @@
|
|||
|
||||
<!-- Option: Blog Post List: show tags -->
|
||||
<template id="opt_blog_post_short_tags" name="Tags"
|
||||
inherit_option_id="website_blog.blog_post_short" inherit_id="website_blog.blog_post_short">
|
||||
optional="enabled" inherit_id="website_blog.blog_post_short">
|
||||
<xpath expr="//div[@name='blog_post_data']" position="inside">
|
||||
<p class="post-meta text-muted text-center" t-if="len(blog_post.tag_ids)">
|
||||
<span class="fa fa-tags"/>
|
||||
|
@ -265,7 +265,7 @@
|
|||
|
||||
<!-- Options: Blog Post: breadcrumb -->
|
||||
<template id="blog_breadcrumb" name="Breadcrumb"
|
||||
inherit_option_id="website_blog.blog_post_complete">
|
||||
inherit_id="website_blog.blog_post_complete" optional="disabled">
|
||||
<xpath expr="//div[@id='title']" position="before">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
|
@ -285,7 +285,7 @@
|
|||
|
||||
<!-- Options: Blog Post: user can reply -->
|
||||
<template id="opt_blog_post_complete_comment" name="Allow blog post comment"
|
||||
inherit_option_id="website_blog.blog_post_complete"
|
||||
inherit_id="website_blog.blog_post_complete" optional="disabled"
|
||||
groups="website_mail.group_comment">
|
||||
<xpath expr="//ul[@id='comments-list']" position="before">
|
||||
<section class="mb32 read_width css_editable_mode_hidden">
|
||||
|
@ -307,7 +307,7 @@
|
|||
|
||||
<!-- Options: Blog Post: user can select text for tweet -->
|
||||
<template id="opt_blog_post_select_to_tweet" name="Select to Tweet"
|
||||
inherit_option_id="website_blog.blog_post_complete">
|
||||
inherit_id="website_blog.blog_post_complete" optional="disabled">
|
||||
<xpath expr="//div[@id='blog_content']" position="attributes">
|
||||
<attribute name="class">js_tweet mt32</attribute>
|
||||
</xpath>
|
||||
|
@ -318,7 +318,7 @@
|
|||
|
||||
<!-- Options: Blog Post: user can add Inline Discussion -->
|
||||
<template id="opt_blog_post_inline_discussion" name="Allow comment in text"
|
||||
inherit_option_id="website_blog.blog_post_complete">
|
||||
inherit_id="website_blog.blog_post_complete" optional="disabled">
|
||||
<xpath expr="//div[@id='blog_content']" position="attributes">
|
||||
<attribute name="enable_chatter_discuss">True</attribute>
|
||||
</xpath>
|
||||
|
@ -326,7 +326,7 @@
|
|||
|
||||
<!-- Options: Blog Post: show tags -->
|
||||
<template id="opt_blog_post_complete_tags" name="Tags"
|
||||
inherit_option_id="website_blog.blog_post_complete" inherit_id="website_blog.blog_post_complete">
|
||||
optional="enabled" inherit_id="website_blog.blog_post_complete">
|
||||
<xpath expr="//p[@name='blog_post_data']" position="after">
|
||||
<p class="post-meta text-muted text-center" t-if="len(blog_post.tag_ids)">
|
||||
<span class="fa fa-tags"/>
|
||||
|
@ -355,7 +355,7 @@
|
|||
<!-- Option:Right Column for extra info -->
|
||||
|
||||
<template id="index_right" name="Right Column"
|
||||
inherit_option_id="website_blog.blog_post_short">
|
||||
inherit_id="website_blog.blog_post_short" optional="disabled">
|
||||
<xpath expr="//div[@id='main_column']" position="attributes">
|
||||
<attribute name="class">col-sm-8</attribute>
|
||||
</xpath>
|
||||
|
@ -366,7 +366,7 @@
|
|||
|
||||
<!-- Option:Right Column: tags -->
|
||||
<template id="opt_blog_rc_tags" name="Tags"
|
||||
inherit_option_id="website_blog.index_right">
|
||||
inherit_id="website_blog.index_right" optional="disabled">
|
||||
<xpath expr="//div[@id='blog_right_column']" position="inside">
|
||||
<section class="mt32">
|
||||
<h4>Tags</h4>
|
||||
|
@ -383,7 +383,7 @@
|
|||
|
||||
<!-- Option:Right Column: archives -->
|
||||
<template id="opt_blog_rc_history" name="Archives"
|
||||
inherit_option_id="website_blog.index_right">
|
||||
inherit_id="website_blog.index_right" optional="disabled">
|
||||
<xpath expr="//div[@id='blog_right_column']" position="inside">
|
||||
<section class="mt32">
|
||||
<h4>Archives</h4>
|
||||
|
@ -400,7 +400,7 @@
|
|||
|
||||
<!-- Option:Right Column: about us -->
|
||||
<template id="opt_blog_rc_about_us" name="About Us" priority="2"
|
||||
inherit_option_id="website_blog.index_right">
|
||||
inherit_id="website_blog.index_right" optional="disabled">
|
||||
<xpath expr="//div[@id='blog_right_column']" position="inside">
|
||||
<section class="mt32">
|
||||
<h4>About us</h4>
|
||||
|
@ -417,7 +417,7 @@
|
|||
|
||||
<!-- Option:Right Column: follow us -->
|
||||
<template id="opt_blog_rc_follow_us" name="Follow us" priority="4"
|
||||
inherit_option_id="website_blog.index_right">
|
||||
inherit_id="website_blog.index_right" optional="disabled">
|
||||
<xpath expr="//div[@id='blog_right_column']" position="inside">
|
||||
<section class="mt32">
|
||||
<h4>Follow us<small t-if="blog">: <t t-esc="blog.name"/></small></h4>
|
||||
|
@ -444,7 +444,7 @@
|
|||
|
||||
<!-- Option:Right Column: blogs -->
|
||||
<template id="opt_blog_rc_blogs" name="Our Blogs" priority="6"
|
||||
inherit_option_id="website_blog.index_right">
|
||||
inherit_id="website_blog.index_right" optional="disabled">
|
||||
<xpath expr="//div[@id='blog_right_column']" position="inside">
|
||||
<section class="mt32 mb32">
|
||||
<h4>Our Blogs</h4>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<openerp>
|
||||
<data>
|
||||
|
||||
<template id="contactus_form" name="Contact Form" inherit_id="website.contactus" inherit_option_id="website.contactus">
|
||||
<template id="contactus_form" name="Contact Form" inherit_id="website.contactus" optional="enabled">
|
||||
<xpath expr="//div[@name='mail_button']" position="replace">
|
||||
<form action="/crm/contactus" method="post" class="form-horizontal mt32" enctype="multipart/form-data">
|
||||
<t t-foreach="kwargs" t-as="kwarg">
|
||||
|
@ -47,7 +47,7 @@
|
|||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="contactus_form_company_name" name="Company Name" inherit_id="website_crm.contactus_form" inherit_option_id="website_crm.contactus_form">
|
||||
<template id="contactus_form_company_name" name="Company Name" inherit_id="website_crm.contactus_form" optional="enabled">
|
||||
<xpath expr="//div[@name='email_from_container']" position="after">
|
||||
<div t-attf-class="form-group #{error and 'partner_name' in error and 'has-error' or ''}">
|
||||
<label class="col-md-3 col-sm-4 control-label" for="partner_name">Your Company</label>
|
||||
|
|
|
@ -113,7 +113,7 @@
|
|||
</t>
|
||||
</template>
|
||||
|
||||
<template id="ref_country" inherit_id="website_crm_partner_assign.index" inherit_option_id="website_crm_partner_assign.index" name="Left World Map">
|
||||
<template id="ref_country" inherit_id="website_crm_partner_assign.index" optional="enabled" name="Left World Map">
|
||||
<xpath expr="//ul[@id='reseller_countries']" position="after">
|
||||
<h3>World Map</h3>
|
||||
<ul class="nav">
|
||||
|
|
|
@ -66,7 +66,7 @@
|
|||
</template>
|
||||
|
||||
<!-- Option: left column: World Map -->
|
||||
<template id="opt_country" inherit_option_id="website_customer.index" name="Show Map">
|
||||
<template id="opt_country" inherit_id="website_customer.index" optional="disabled" name="Show Map">
|
||||
<xpath expr="//div[@id='ref_left_column']" position="inside">
|
||||
|
||||
<iframe t-attf-src="/google_map/?partner_ids=#{ google_map_partner_ids }&partner_url=/customers/&output=embed"
|
||||
|
@ -74,7 +74,7 @@
|
|||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="opt_country_list" inherit_id="website_customer.index" inherit_option_id="website_customer.index" name="Filter on Countries">
|
||||
<template id="opt_country_list" inherit_id="website_customer.index" optional="enabled" name="Filter on Countries">
|
||||
<xpath expr="//div[@id='ref_left_column']" position="inside">
|
||||
<h3>References by Country</h3>
|
||||
<ul class="nav nav-pills nav-stacked mt16 mb32">
|
||||
|
|
|
@ -87,7 +87,7 @@
|
|||
</t>
|
||||
</template>
|
||||
|
||||
<template id="event_right_photos" inherit_option_id="website_event.index" name="Photos">
|
||||
<template id="event_right_photos" inherit_id="website_event.index" optional="disabled" name="Photos">
|
||||
<xpath expr="//div[@id='right_column']" position="inside">
|
||||
<div class="row">
|
||||
<div class="col-md-12 mb16">
|
||||
|
@ -106,7 +106,7 @@
|
|||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="event_right_quotes" inherit_option_id="website_event.index" name="Quotes">
|
||||
<template id="event_right_quotes" inherit_id="website_event.index" optional="disabled" name="Quotes">
|
||||
<xpath expr="//div[@id='right_column']" position="inside">
|
||||
<div class="row">
|
||||
<div class="col-md-12 mb16">
|
||||
|
@ -123,7 +123,7 @@
|
|||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="event_right_country_event" inherit_option_id="website_event.index" name="Country Events">
|
||||
<template id="event_right_country_event" inherit_id="website_event.index" optional="disabled" name="Country Events">
|
||||
<xpath expr="//div[@id='right_column']" position="inside">
|
||||
<div class="row">
|
||||
<div class="col-md-12 mb16 mt16 country_events">
|
||||
|
@ -140,7 +140,7 @@
|
|||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="event_left_column" inherit_option_id="website_event.index" inherit_id="website_event.index" name="Filters">
|
||||
<template id="event_left_column" optional="enabled" inherit_id="website_event.index" name="Filters">
|
||||
<xpath expr="//div[@id='middle_column']" position="attributes">
|
||||
<attribute name="class">col-md-6</attribute>
|
||||
</xpath>
|
||||
|
@ -159,7 +159,7 @@
|
|||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="event_category" inherit_option_id="website_event.event_left_column" name="Filter by Category">
|
||||
<template id="event_category" inherit_id="website_event.event_left_column" optional="disabled" name="Filter by Category">
|
||||
<xpath expr="//div[@id='left_column']" position="inside">
|
||||
<ul class="nav nav-pills nav-stacked mt32">
|
||||
<t t-foreach="types">
|
||||
|
@ -173,7 +173,7 @@
|
|||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="event_location" inherit_option_id="website_event.event_left_column" name="Filter by Country">
|
||||
<template id="event_location" inherit_id="website_event.event_left_column" optional="disabled" name="Filter by Country">
|
||||
<xpath expr="//div[@id='left_column']" position="inside">
|
||||
<ul class="nav nav-pills nav-stacked mt32">
|
||||
<t t-foreach="countries">
|
||||
|
|
|
@ -53,7 +53,7 @@
|
|||
{
|
||||
title: "Pay Now",
|
||||
waitFor: '#payment_method label:has(input:checked):has(img[title="Wire Transfer"])',
|
||||
element: '.oe_sale_acquirer_button .btn[name="submit"]:visible',
|
||||
element: '.oe_sale_acquirer_button .btn[type="submit"]:visible',
|
||||
},
|
||||
{
|
||||
title: "finish",
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="event_description_full" inherit_id="website_event.event_description_full" inherit_option_id="website_event.event_description_full" name="Event's Ticket form">
|
||||
<template id="event_description_full" inherit_id="website_event.event_description_full" optional="enabled" name="Event's Ticket form">
|
||||
<xpath expr="//div[@t-field='event.description']" position="before">
|
||||
<form t-attf-action="/event/cart/update?event_id=#{ event.id }" method="post" t-if="event.event_ticket_ids">
|
||||
<table itemprop="offers" class="table table-striped">
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<openerp>
|
||||
<data>
|
||||
|
||||
<template name="Sponsors" id="event_sponsor" inherit_option_id="website_event.layout" inherit_id="website_event.layout">
|
||||
<template name="Sponsors" id="event_sponsor" optional="enabled" inherit_id="website_event.layout">
|
||||
<xpath expr="//t[@t-call='website.layout']" position="inside">
|
||||
<t t-set="head">
|
||||
<link rel='stylesheet' href='/website_event_track/static/src/css/website_event_track.css'/>
|
||||
|
@ -157,7 +157,7 @@
|
|||
</t>
|
||||
</template>
|
||||
|
||||
<template id="tracks_filter" inherit_id="website_event_track.tracks" inherit_option_id="website_event_track.tracks" name="Filter on Tags">
|
||||
<template id="tracks_filter" inherit_id="website_event_track.tracks" optional="enabled" name="Filter on Tags">
|
||||
<xpath expr="//div[@id='left_column']" position="inside">
|
||||
<ul class="nav nav-pills nav-stacked">
|
||||
<li t-att-class="'' if searches.get('tag') else 'active'"><a t-attf-href="/event/#{ slug(event) }/track">All Tags</a></li>
|
||||
|
@ -246,7 +246,7 @@
|
|||
</t>
|
||||
</template>
|
||||
|
||||
<template id="event_track_social" name="Social Widgets" inherit_option_id="website_event_track.track_view">
|
||||
<template id="event_track_social" name="Social Widgets" inherit_id="website_event_track.track_view" optional="disabled">
|
||||
<xpath expr="//div[@id='right_column']" position="inside">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
|
|
|
@ -449,7 +449,10 @@ class WebsiteForum(http.Controller):
|
|||
pager = request.website.pager(url="/forum/%s/users" % slug(forum), total=tag_count, page=page, step=step, scope=30)
|
||||
|
||||
obj_ids = User.search(cr, SUPERUSER_ID, [('karma', '>', 1)], limit=step, offset=pager['offset'], order='karma DESC', context=context)
|
||||
users = User.browse(cr, SUPERUSER_ID, obj_ids, context=context)
|
||||
# put the users in block of 3 to display them as a table
|
||||
users = [[] for i in range(len(obj_ids)/3+1)]
|
||||
for index, user in enumerate(User.browse(cr, SUPERUSER_ID, obj_ids, context=context)):
|
||||
users[index/3].append(user)
|
||||
searches['users'] = 'True'
|
||||
|
||||
values = self._prepare_forum_values(forum=forum, searches=searches)
|
||||
|
|
|
@ -725,11 +725,8 @@
|
|||
|
||||
<template id="users">
|
||||
<t t-call="website_forum.header">
|
||||
<t t-foreach="users" t-as="user">
|
||||
<t t-if="user_index % 3 == 0">
|
||||
<div class="row"></div>
|
||||
</t>
|
||||
<div class="col-sm-4">
|
||||
<div t-foreach="users" t-as="row_users" class="row mt16">
|
||||
<div t-foreach="row_users" t-as="user" class="col-sm-4">
|
||||
<img class="pull-left img img-circle img-avatar" t-attf-src="/forum/user/#{user.id}/avatar"/>
|
||||
<div>
|
||||
<a t-attf-href="/forum/#{slug(forum)}/user/#{user.id}" t-field="user.name"/>
|
||||
|
@ -750,7 +747,7 @@
|
|||
<t t-raw="0"/>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
<div class="pull-left">
|
||||
<t t-call="website.pager"/>
|
||||
</div>
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<data>
|
||||
<!-- Page -->
|
||||
|
||||
<template id="aboutus" inherit_id="website.aboutus" inherit_option_id="website.aboutus" name="Our Team">
|
||||
<template id="aboutus" inherit_id="website.aboutus" optional="enabled" name="Our Team">
|
||||
<xpath expr="//div[@class='oe_structure']" position="after">
|
||||
<section class="container">
|
||||
<div class="col-sm-12 text-center" t-if="len(employee_ids)">
|
||||
|
|
|
@ -230,7 +230,7 @@
|
|||
</t>
|
||||
</template>
|
||||
|
||||
<template id="job_departments" inherit_option_id="website_hr_recruitment.index" name="Filter by Departments">
|
||||
<template id="job_departments" inherit_id="website_hr_recruitment.index" optional="disabled" name="Filter by Departments">
|
||||
<xpath expr="//div[@id='jobs_grid_left']" position="inside">
|
||||
<ul class="nav nav-pills nav-stacked mb32">
|
||||
<li t-att-class=" '' if department_id else 'active' "><a href="/jobs">All Departments</a></li>
|
||||
|
@ -249,7 +249,7 @@
|
|||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="job_offices" inherit_option_id="website_hr_recruitment.index" name="Filter by Offices">
|
||||
<template id="job_offices" inherit_id="website_hr_recruitment.index" optional="disabled" name="Filter by Offices">
|
||||
<xpath expr="//div[@id='jobs_grid_left']" position="inside">
|
||||
<ul class="nav nav-pills nav-stacked mb32">
|
||||
<li t-att-class=" '' if office_id else 'active' "><a href="/jobs">All Offices</a></li>
|
||||
|
|
|
@ -84,7 +84,7 @@
|
|||
</template>
|
||||
|
||||
<template id="opt_index_country" name="Location"
|
||||
inherit_option_id="website_membership.index" inherit_id="website_membership.index">
|
||||
optional="enabled" inherit_id="website_membership.index">
|
||||
<xpath expr="//div[@id='left_column']/ul[last()]" position="after">
|
||||
<ul class="nav nav-pills nav-stacked mt16">
|
||||
<li class="nav-header"><h3>Location</h3></li>
|
||||
|
@ -101,7 +101,7 @@
|
|||
|
||||
<!-- Option: index: Left Google Map -->
|
||||
<template id="opt_index_google_map" name="Left World Map"
|
||||
inherit_option_id="website_membership.index" inherit_id="website_membership.index">
|
||||
optional="enabled" inherit_id="website_membership.index">
|
||||
<xpath expr="//div[@id='left_column']/ul[1]" position="before">
|
||||
<ul class="nav nav-pills nav-stacked mt16">
|
||||
<li class="nav-header"><h3>World Map</h3></li>
|
||||
|
|
|
@ -92,7 +92,7 @@
|
|||
</section>
|
||||
</template>
|
||||
|
||||
<template id="change_quantity" inherit_option_id="website_quote.pricing" name="Change Quantity">
|
||||
<template id="change_quantity" inherit_id="website_quote.pricing" optional="disabled" name="Change Quantity">
|
||||
<xpath expr="//div[@id='quote_qty']" position="replace">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon hidden-print">
|
||||
|
@ -130,7 +130,7 @@
|
|||
</template>
|
||||
|
||||
<!-- Options:Quotation Chatter: user can reply -->
|
||||
<template id="opt_quotation_chatter_post_complete_comment" name="Allow Comments" inherit_option_id="website_quote.chatter" inherit_id="website_quote.chatter">
|
||||
<template id="opt_quotation_chatter_post_complete_comment" name="Allow Comments" optional="enabled" inherit_id="website_quote.chatter">
|
||||
<xpath expr="//h1" position="after">
|
||||
<section class="mb32 css_editable_mode_hidden hidden-print">
|
||||
<form id="comment" t-attf-action="/quote/#{quotation.id}/#{quotation.access_token}/post" method="POST">
|
||||
|
@ -388,7 +388,7 @@
|
|||
</template>
|
||||
|
||||
<!-- Options:Quotation Signature -->
|
||||
<template id="opt_quotation_signature" name="Ask Signature" inherit_option_id="website_quote.so_quotation" inherit_id="website_quote.so_quotation">
|
||||
<template id="opt_quotation_signature" name="Ask Signature" optional="enabled" inherit_id="website_quote.so_quotation">
|
||||
<xpath expr="//div[@id='sign-dialog']" position="inside">
|
||||
<div class="panel panel-default mt16 mb0" id="drawsign">
|
||||
<div class="panel-heading">
|
||||
|
|
|
@ -509,7 +509,6 @@ class website_sale(http.Controller):
|
|||
values['acquirers'] = payment_obj.browse(cr, uid, acquirer_ids, context=context)
|
||||
render_ctx = dict(context, submit_class='btn btn-primary', submit_txt='Pay Now')
|
||||
for acquirer in values['acquirers']:
|
||||
render_ctx['tx_url'] = '/shop/payment/transaction/%s' % acquirer.id
|
||||
acquirer.button = payment_obj.render(
|
||||
cr, SUPERUSER_ID, acquirer.id,
|
||||
order.name,
|
||||
|
@ -523,20 +522,17 @@ class website_sale(http.Controller):
|
|||
|
||||
return request.website.render("website_sale.payment", values)
|
||||
|
||||
@http.route(['/shop/payment/transaction/<int:acquirer_id>'], type='http', methods=['POST'], auth="public", website=True)
|
||||
def payment_transaction(self, acquirer_id, **post):
|
||||
""" Hook method that creates a payment.transaction and redirect to the
|
||||
acquirer, using post values to re-create the post action.
|
||||
@http.route(['/shop/payment/transaction/<int:acquirer_id>'], type='json', auth="public", website=True)
|
||||
def payment_transaction(self, acquirer_id):
|
||||
""" Json method that creates a payment.transaction, used to create a
|
||||
transaction when the user clicks on 'pay now' button. After having
|
||||
created the transaction, the event continues and the user is redirected
|
||||
to the acquirer website.
|
||||
|
||||
:param int acquirer_id: id of a payment.acquirer record. If not set the
|
||||
user is redirected to the checkout page
|
||||
:param dict post: should coutain all post data for the acquirer
|
||||
"""
|
||||
# @TDEFIXME: don't know why we received those data, but should not be send to the acquirer
|
||||
post.pop('submit.x', None)
|
||||
post.pop('submit.y', None)
|
||||
cr, uid, context = request.cr, request.uid, request.context
|
||||
payment_obj = request.registry.get('payment.acquirer')
|
||||
transaction_obj = request.registry.get('payment.transaction')
|
||||
sale_order_obj = request.registry['sale.order']
|
||||
order = request.website.sale_get_order(context=context)
|
||||
|
@ -548,7 +544,13 @@ class website_sale(http.Controller):
|
|||
|
||||
# find an already existing transaction
|
||||
tx = request.website.sale_get_transaction()
|
||||
if not tx:
|
||||
if tx:
|
||||
if tx.state == 'draft': # button cliked but no more info -> rewrite on tx or create a new one ?
|
||||
tx.write({
|
||||
'acquirer_id': acquirer_id,
|
||||
})
|
||||
tx_id = tx.id
|
||||
else:
|
||||
tx_id = transaction_obj.create(cr, SUPERUSER_ID, {
|
||||
'acquirer_id': acquirer_id,
|
||||
'type': 'form',
|
||||
|
@ -560,10 +562,6 @@ class website_sale(http.Controller):
|
|||
'sale_order_id': order.id,
|
||||
}, context=context)
|
||||
request.session['sale_transaction_id'] = tx_id
|
||||
elif tx and tx.state == 'draft': # button cliked but no more info -> rewrite on tx or create a new one ?
|
||||
tx.write({
|
||||
'acquirer_id': acquirer_id,
|
||||
})
|
||||
|
||||
# update quotation
|
||||
sale_order_obj.write(
|
||||
|
@ -574,9 +572,7 @@ class website_sale(http.Controller):
|
|||
# confirm the quotation
|
||||
sale_order_obj.action_button_confirm(cr, SUPERUSER_ID, [order.id], context=request.context)
|
||||
|
||||
acquirer_form_post_url = payment_obj.get_form_action_url(cr, uid, acquirer_id, context=context)
|
||||
acquirer_total_url = '%s?%s' % (acquirer_form_post_url, werkzeug.url_encode(post))
|
||||
return request.redirect(acquirer_total_url)
|
||||
return tx_id
|
||||
|
||||
@http.route('/shop/payment/get_status/<int:sale_order_id>', type='json', auth="public", website=True)
|
||||
def payment_get_status(self, sale_order_id, **post):
|
||||
|
|
|
@ -113,7 +113,7 @@
|
|||
{
|
||||
title: "Pay Now",
|
||||
waitFor: '#payment_method label:has(input:checked):has(img[title="Wire Transfer"])',
|
||||
element: '.oe_sale_acquirer_button .btn[name="submit"]:visible',
|
||||
element: '.oe_sale_acquirer_button .btn[type="submit"]:visible',
|
||||
},
|
||||
{
|
||||
title: "finish",
|
||||
|
|
|
@ -9,4 +9,18 @@ $(document).ready(function () {
|
|||
})
|
||||
.find("input[name='acquirer']:checked").click();
|
||||
|
||||
// When clicking on payment button: create the tx using json then continue to the acquirer
|
||||
$('button[type="submit"]').on("click", function (ev) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
var $form = $(ev.currentTarget).parents('form');
|
||||
var acquirer_id = $(ev.currentTarget).parents('div.oe_sale_acquirer_button').first().data('id');
|
||||
if (! acquirer_id) {
|
||||
return false;
|
||||
}
|
||||
openerp.jsonRpc('/shop/payment/transaction/' + acquirer_id, 'call', {}).then(function (data) {
|
||||
$form.submit();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
@ -89,7 +89,7 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<template id="products_description" inherit_option_id="website_sale.products_item" name="Product Description">
|
||||
<template id="products_description" inherit_id="website_sale.products_item" optional="disabled" name="Product Description">
|
||||
<xpath expr="//div[@class='product_price']" position="before">
|
||||
<div class="text-info oe_subdescription" contenteditable="false">
|
||||
<div itemprop="description" t-field="product.description_sale"></div>
|
||||
|
@ -97,7 +97,7 @@
|
|||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="products_add_to_cart" inherit_option_id="website_sale.products_item" name="Add to Cart">
|
||||
<template id="products_add_to_cart" inherit_id="website_sale.products_item" optional="disabled" name="Add to Cart">
|
||||
<xpath expr="//div[@class='product_price']" position="inside">
|
||||
<form action="/shop/cart/update" method="post" style="display: inline-block;">
|
||||
<input name="product_id" t-att-value="product.product_variant_ids[0].id" type="hidden"/>
|
||||
|
@ -250,7 +250,7 @@
|
|||
</li>
|
||||
</template>
|
||||
|
||||
<template id="products_categories" inherit_option_id="website_sale.products" name="Product Categories">
|
||||
<template id="products_categories" inherit_id="website_sale.products" optional="disabled" name="Product Categories">
|
||||
<xpath expr="//div[@id='products_grid_before']" position="inside">
|
||||
<ul class="nav nav-pills nav-stacked mt16">
|
||||
<li t-att-class=" '' if category else 'active' "><a t-att-href="keep('/shop',category=0)">All Products</a></li>
|
||||
|
@ -267,7 +267,7 @@
|
|||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="products_attributes" inherit_option_id="website_sale.products" name="Product Attribute's Filters">
|
||||
<template id="products_attributes" inherit_id="website_sale.products" optional="disabled" name="Product Attribute's Filters">
|
||||
<xpath expr="//div[@id='products_grid_before']" position="inside">
|
||||
<form class="js_attributes" method="get">
|
||||
<input type="hidden" name="search" t-att-value="search"/>
|
||||
|
@ -320,7 +320,7 @@
|
|||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="products_list_view" inherit_option_id="website_sale.products" name="List View">
|
||||
<template id="products_list_view" inherit_id="website_sale.products" optional="disabled" name="List View">
|
||||
<xpath expr="//div[@id='products_grid']//table" position="replace">
|
||||
<t t-foreach="products" t-as="product">
|
||||
<div class="oe_product oe_list oe_product_cart" t-att-data-publish="product.website_published and 'on' or 'off'">
|
||||
|
@ -505,7 +505,7 @@
|
|||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="recommended_products" inherit_id="website_sale.product" inherit_option_id="website_sale.product" name="Alternative Products">
|
||||
<template id="recommended_products" inherit_id="website_sale.product" optional="enabled" name="Alternative Products">
|
||||
<xpath expr="//div[@id='product_full_description']" position="after">
|
||||
<div class="container mt32" t-if="product.alternative_product_ids">
|
||||
<h3>Suggested alternatives:</h3>
|
||||
|
@ -527,7 +527,7 @@
|
|||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="product_attributes" inherit_id="website_sale.product" inherit_option_id="website_sale.product" name="Product attributes">
|
||||
<template id="product_attributes" inherit_id="website_sale.product" optional="enabled" name="Product attributes">
|
||||
<xpath expr="//p[@t-field='product.description_sale']" position="after">
|
||||
<hr t-if="product.variant_ids"/>
|
||||
<p class="text-muted">
|
||||
|
@ -543,7 +543,7 @@
|
|||
|
||||
<!-- Product options: OpenChatter -->
|
||||
|
||||
<template id="product_comment" inherit_option_id="website_sale.product" name="Discussion">
|
||||
<template id="product_comment" inherit_id="website_sale.product" optional="disabled" name="Discussion">
|
||||
<xpath expr="//div[@t-field='product.website_description']" position="after">
|
||||
<hr class="mb32"/>
|
||||
<section class="container">
|
||||
|
@ -709,7 +709,7 @@
|
|||
</t>
|
||||
</template>
|
||||
|
||||
<template id="suggested_products_list" inherit_id="website_sale.cart" inherit_option_id="website_sale.cart" name="Suggested Products in my cart">
|
||||
<template id="suggested_products_list" inherit_id="website_sale.cart" optional="enabled" name="Suggested Products in my cart">
|
||||
<xpath expr="//table[@id='cart_products']" position="after">
|
||||
<table t-if="suggested_products" class='table table-striped table-condensed'>
|
||||
<colgroup>
|
||||
|
@ -765,13 +765,13 @@
|
|||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="continue_shopping" inherit_id="website_sale.cart" inherit_option_id="website_sale.cart" name="Continue Shopping Button">
|
||||
<template id="continue_shopping" inherit_id="website_sale.cart" optional="enabled" name="Continue Shopping Button">
|
||||
<xpath expr="//a[@href='/shop/checkout']" position="before">
|
||||
<a href="/shop" class="btn btn-default mb32"><span class="fa fa-long-arrow-left"/> Continue Shopping</a>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="reduction_code" inherit_option_id="website_sale.cart" name="Reduction Code">
|
||||
<template id="reduction_code" inherit_id="website_sale.cart" optional="disabled" name="Reduction Code">
|
||||
<xpath expr="//div[@id='right_column']" position="inside">
|
||||
<h4>Coupon Code</h4>
|
||||
<p>
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
Contributing to Odoo
|
||||
====================
|
||||
|
||||
Reporting Issues
|
||||
----------------
|
||||
If possible, always attach a pull request when creating an issue (GitHub will automatically create an issue when submitting the changes). The issues not linked to a pull request or an internal request on odoo.com will be handled with a lower priority.
|
||||
|
||||
If later on you create a pull request solving an opened issue, do not forget to reference to it in your pull request (e.g.: "This patch fixes issue #42").
|
||||
|
||||
When reporting an issue or creating a pull request, use the following structure:
|
||||
|
||||
> **Quantity field is ignored in a sale order**
|
||||
>
|
||||
> Impacted versions:
|
||||
>
|
||||
> - 7.0 and above
|
||||
>
|
||||
> Steps to reproduce:
|
||||
>
|
||||
> 1. create a new sale order
|
||||
> 2. add a line with product 'Service', quantity 2, unit price 10.0
|
||||
> 3. validate the sale order
|
||||
>
|
||||
> Current behavior:
|
||||
>
|
||||
> - Total price of 10.0
|
||||
>
|
||||
> Expected behavior:
|
||||
>
|
||||
> - Total price of 20.0 (2 * 10 = 20)
|
||||
|
||||
For web or rendering issues, do not forget to specify the operating system and browser you are using.
|
||||
|
||||
Against which version should I submit a patch?
|
||||
----------------------------------------------
|
||||
Periodically, we forward port the fixes realized in the latest stable version to master and intermediate saas repositories. This means that you should submit your pull request against the lowest supported version. If applying, you should always submit your code against `odoo/7.0`. The `saas-x` versions are intermediate versions between stable versions.
|
||||
|
||||
![Submiting against the right version](/doc/_static/pull-request-version.png)
|
||||
|
||||
However your change **must** be submitted against `odoo/master` if
|
||||
|
||||
* it modifies the database structure (e.g.: adding a new field)
|
||||
* it adds a new feature
|
||||
|
||||
Why was my fix labeled as blocked?
|
||||
----------------------------------
|
||||
The label *blocked* is used when an action is required from the submitter. The typical reasons are:
|
||||
|
||||
* the fix is incomplete/incorrect and a change is required
|
||||
* more information is required
|
||||
|
||||
Pull requests with the blocked label will not be processed as long as the label remains. Once the correction done, we will review it and eventually remove the label.
|
||||
|
||||
Why was my issue closed without merging?
|
||||
----------------------------------------
|
||||
A pull request is closed when it will not be merged into odoo. This will typically happens if the fix/issue:
|
||||
|
||||
* is not relevant to odoo development (label *invalid*)
|
||||
* is not considered as a bug or we have no plan to change the current behavior (label *wontfix*)
|
||||
* is a duplicated of another opened issue (label *duplicate*)
|
||||
* the pull request should be resubmitted against another version
|
||||
|
||||
What is this odoo-dev repository? Should I use it?
|
||||
--------------------------------------------------
|
||||
|
||||
The `odoo-dev/odoo` repository is an internal repository used by the R&D of Odoo to keep the main repository clean. If you are coming from Launchpad, this is the equivalent of the `~openerp-dev` repository.
|
||||
|
||||
When forking odoo to submit a patch, always use the `github.com/odoo/odoo` repository. Be also careful of the version you are branching as it will determine the history once the pull request will be realized (e.g.: `git checkout -b 7.0-my-branch odoo/7.0`).
|
Binary file not shown.
After Width: | Height: | Size: 13 KiB |
|
@ -63,7 +63,6 @@ The kernel of OpenERP, needed for all installation.
|
|||
'module/module_view.xml',
|
||||
'module/module_data.xml',
|
||||
'module/module_report.xml',
|
||||
'module/wizard/base_module_import_view.xml',
|
||||
'module/wizard/base_module_update_view.xml',
|
||||
'module/wizard/base_language_install_view.xml',
|
||||
'module/wizard/base_import_language_view.xml',
|
||||
|
|
|
@ -612,7 +612,7 @@ class FloatConverter(osv.AbstractModel):
|
|||
# it to switch to scientific notation starting at a million *and* to
|
||||
# strip decimals. So use %f and if no precision was specified manually
|
||||
# strip trailing 0.
|
||||
if not precision:
|
||||
if precision is None:
|
||||
formatted = re.sub(r'(?:(0|\d+?)0+)$', r'\1', formatted)
|
||||
return formatted
|
||||
|
||||
|
|
|
@ -141,8 +141,35 @@ class view(osv.osv):
|
|||
'model_ids': fields.one2many('ir.model.data', 'res_id', domain=[('model','=','ir.ui.view')], auto_join=True),
|
||||
'create_date': fields.datetime('Create Date', readonly=True),
|
||||
'write_date': fields.datetime('Last Modification Date', readonly=True),
|
||||
|
||||
'mode': fields.selection(
|
||||
[('primary', "Base view"), ('extension', "Extension View")],
|
||||
string="View inheritance mode", required=True,
|
||||
help="""Only applies if this view inherits from an other one (inherit_id is not False/Null).
|
||||
|
||||
* if extension (default), if this view is requested the closest primary view
|
||||
is looked up (via inherit_id), then all views inheriting from it with this
|
||||
view's model are applied
|
||||
* if primary, the closest primary view is fully resolved (even if it uses a
|
||||
different model than this one), then this view's inheritance specs
|
||||
(<xpath/>) are applied, and the result is used as if it were this view's
|
||||
actual arch.
|
||||
"""),
|
||||
'application': fields.selection([
|
||||
('always', "Always applied"),
|
||||
('enabled', "Optional, enabled"),
|
||||
('disabled', "Optional, disabled"),
|
||||
],
|
||||
required=True, string="Application status",
|
||||
help="""If this view is inherited,
|
||||
* if always, the view always extends its parent
|
||||
* if enabled, the view currently extends its parent but can be disabled
|
||||
* if disabled, the view currently does not extend its parent but can be enabled
|
||||
"""),
|
||||
}
|
||||
_defaults = {
|
||||
'mode': 'primary',
|
||||
'application': 'always',
|
||||
'priority': 16,
|
||||
}
|
||||
_order = "priority,name"
|
||||
|
@ -191,8 +218,14 @@ class view(osv.osv):
|
|||
return False
|
||||
return True
|
||||
|
||||
_sql_constraints = [
|
||||
('inheritance_mode',
|
||||
"CHECK (mode != 'extension' OR inherit_id IS NOT NULL)",
|
||||
"Invalid inheritance mode: if the mode is 'extension', the view must"
|
||||
" extend an other view"),
|
||||
]
|
||||
_constraints = [
|
||||
(_check_xml, 'Invalid view definition', ['arch'])
|
||||
(_check_xml, 'Invalid view definition', ['arch']),
|
||||
]
|
||||
|
||||
def _auto_init(self, cr, context=None):
|
||||
|
@ -201,6 +234,12 @@ class view(osv.osv):
|
|||
if not cr.fetchone():
|
||||
cr.execute('CREATE INDEX ir_ui_view_model_type_inherit_id ON ir_ui_view (model, inherit_id)')
|
||||
|
||||
def _compute_defaults(self, cr, uid, values, context=None):
|
||||
if 'inherit_id' in values:
|
||||
values.setdefault(
|
||||
'mode', 'extension' if values['inherit_id'] else 'primary')
|
||||
return values
|
||||
|
||||
def create(self, cr, uid, values, context=None):
|
||||
if 'type' not in values:
|
||||
if values.get('inherit_id'):
|
||||
|
@ -209,10 +248,13 @@ class view(osv.osv):
|
|||
values['type'] = etree.fromstring(values['arch']).tag
|
||||
|
||||
if not values.get('name'):
|
||||
values['name'] = "%s %s" % (values['model'], values['type'])
|
||||
values['name'] = "%s %s" % (values.get('model'), values['type'])
|
||||
|
||||
self.read_template.clear_cache(self)
|
||||
return super(view, self).create(cr, uid, values, context)
|
||||
return super(view, self).create(
|
||||
cr, uid,
|
||||
self._compute_defaults(cr, uid, values, context=context),
|
||||
context=context)
|
||||
|
||||
def write(self, cr, uid, ids, vals, context=None):
|
||||
if not isinstance(ids, (list, tuple)):
|
||||
|
@ -226,10 +268,36 @@ class view(osv.osv):
|
|||
if custom_view_ids:
|
||||
self.pool.get('ir.ui.view.custom').unlink(cr, uid, custom_view_ids)
|
||||
|
||||
if vals.get('application') == 'disabled':
|
||||
from_always = self.search(
|
||||
cr, uid, [('id', 'in', ids), ('application', '=', 'always')], context=context)
|
||||
if from_always:
|
||||
raise ValueError(
|
||||
"Can't disable views %s marked as always applied" % (
|
||||
', '.join(map(str, from_always))))
|
||||
|
||||
self.read_template.clear_cache(self)
|
||||
ret = super(view, self).write(cr, uid, ids, vals, context)
|
||||
ret = super(view, self).write(
|
||||
cr, uid, ids,
|
||||
self._compute_defaults(cr, uid, vals, context=context),
|
||||
context)
|
||||
return ret
|
||||
|
||||
def toggle(self, cr, uid, ids, context=None):
|
||||
""" Switches between enabled and disabled application statuses
|
||||
"""
|
||||
for view in self.browse(cr, uid, ids, context=context):
|
||||
if view.application == 'enabled':
|
||||
view.write({'application': 'disabled'})
|
||||
elif view.application == 'disabled':
|
||||
view.write({'application': 'enabled'})
|
||||
else:
|
||||
raise ValueError(_("Can't toggle view %d with application %r") % (
|
||||
view.id,
|
||||
view.application,
|
||||
))
|
||||
|
||||
|
||||
def copy(self, cr, uid, id, default=None, context=None):
|
||||
if not default:
|
||||
default = {}
|
||||
|
@ -241,7 +309,7 @@ class view(osv.osv):
|
|||
# default view selection
|
||||
def default_view(self, cr, uid, model, view_type, context=None):
|
||||
""" Fetches the default view for the provided (model, view_type) pair:
|
||||
view with no parent (inherit_id=Fase) with the lowest priority.
|
||||
primary view with the lowest priority.
|
||||
|
||||
:param str model:
|
||||
:param int view_type:
|
||||
|
@ -251,7 +319,7 @@ class view(osv.osv):
|
|||
domain = [
|
||||
['model', '=', model],
|
||||
['type', '=', view_type],
|
||||
['inherit_id', '=', False],
|
||||
['mode', '=', 'primary'],
|
||||
]
|
||||
ids = self.search(cr, uid, domain, limit=1, context=context)
|
||||
if not ids:
|
||||
|
@ -278,15 +346,19 @@ class view(osv.osv):
|
|||
user = self.pool['res.users'].browse(cr, 1, uid, context=context)
|
||||
user_groups = frozenset(user.groups_id or ())
|
||||
|
||||
check_view_ids = context and context.get('check_view_ids') or (0,)
|
||||
conditions = [['inherit_id', '=', view_id], ['model', '=', model]]
|
||||
conditions = [
|
||||
['inherit_id', '=', view_id],
|
||||
['model', '=', model],
|
||||
['mode', '=', 'extension'],
|
||||
['application', 'in', ['always', 'enabled']],
|
||||
]
|
||||
if self.pool._init:
|
||||
# Module init currently in progress, only consider views from
|
||||
# modules whose code is already loaded
|
||||
conditions.extend([
|
||||
'|',
|
||||
['model_ids.module', 'in', tuple(self.pool._init_modules)],
|
||||
['id', 'in', check_view_ids],
|
||||
['id', 'in', context and context.get('check_view_ids') or (0,)],
|
||||
])
|
||||
view_ids = self.search(cr, uid, conditions, context=context)
|
||||
|
||||
|
@ -442,7 +514,7 @@ class view(osv.osv):
|
|||
if context is None: context = {}
|
||||
if root_id is None:
|
||||
root_id = source_id
|
||||
sql_inherit = self.pool.get('ir.ui.view').get_inheriting_views_arch(cr, uid, source_id, model, context=context)
|
||||
sql_inherit = self.pool['ir.ui.view'].get_inheriting_views_arch(cr, uid, source_id, model, context=context)
|
||||
for (specs, view_id) in sql_inherit:
|
||||
specs_tree = etree.fromstring(specs.encode('utf-8'))
|
||||
if context.get('inherit_branding'):
|
||||
|
@ -465,7 +537,7 @@ class view(osv.osv):
|
|||
|
||||
# if view_id is not a root view, climb back to the top.
|
||||
base = v = self.browse(cr, uid, view_id, context=context)
|
||||
while v.inherit_id:
|
||||
while v.mode != 'primary':
|
||||
v = v.inherit_id
|
||||
root_id = v.id
|
||||
|
||||
|
@ -475,7 +547,16 @@ class view(osv.osv):
|
|||
|
||||
# read the view arch
|
||||
[view] = self.read(cr, uid, [root_id], fields=fields, context=context)
|
||||
arch_tree = etree.fromstring(view['arch'].encode('utf-8'))
|
||||
view_arch = etree.fromstring(view['arch'].encode('utf-8'))
|
||||
if not v.inherit_id:
|
||||
arch_tree = view_arch
|
||||
else:
|
||||
parent_view = self.read_combined(
|
||||
cr, uid, v.inherit_id.id, fields=fields, context=context)
|
||||
arch_tree = etree.fromstring(parent_view['arch'])
|
||||
self.apply_inheritance_specs(
|
||||
cr, uid, arch_tree, view_arch, parent_view['id'], context=context)
|
||||
|
||||
|
||||
if context.get('inherit_branding'):
|
||||
arch_tree.attrib.update({
|
||||
|
|
|
@ -19,7 +19,6 @@
|
|||
#
|
||||
##############################################################################
|
||||
|
||||
import base_module_import
|
||||
import base_module_update
|
||||
import base_language_install
|
||||
import base_import_language
|
||||
|
|
|
@ -1,63 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
import base64
|
||||
import os
|
||||
from StringIO import StringIO
|
||||
import zipfile
|
||||
|
||||
from openerp import tools
|
||||
from openerp.osv import osv, fields
|
||||
from openerp.tools.translate import _
|
||||
|
||||
class base_module_import(osv.osv_memory):
|
||||
""" Import Module """
|
||||
|
||||
_name = "base.module.import"
|
||||
_description = "Import Module"
|
||||
_columns = {
|
||||
'module_file': fields.binary('Module .ZIP file', required=True),
|
||||
'state':fields.selection([('init','init'),('done','done')],
|
||||
'Status', readonly=True),
|
||||
'module_name': fields.char('Module Name', size=128),
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
'state': 'init',
|
||||
}
|
||||
|
||||
def importzip(self, cr, uid, ids, context):
|
||||
#TODO: drop this model and the corresponding view/action in trunk
|
||||
raise NotImplementedError('This feature is not available')
|
||||
|
||||
def action_module_open(self, cr, uid, ids, context):
|
||||
(data,) = self.browse(cr, uid, ids , context=context)
|
||||
return {
|
||||
'domain': str([('name', '=', data.module_name)]),
|
||||
'name': 'Modules',
|
||||
'view_type': 'form',
|
||||
'view_mode': 'tree,form',
|
||||
'res_model': 'ir.module.module',
|
||||
'type': 'ir.actions.act_window',
|
||||
}
|
||||
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
|
@ -1,12 +1,16 @@
|
|||
# -*- encoding: utf-8 -*-
|
||||
from functools import partial
|
||||
import itertools
|
||||
|
||||
import unittest2
|
||||
|
||||
from lxml import etree as ET
|
||||
from lxml.builder import E
|
||||
|
||||
from psycopg2 import IntegrityError
|
||||
|
||||
from openerp.tests import common
|
||||
import openerp.tools
|
||||
|
||||
Field = E.field
|
||||
|
||||
|
@ -14,9 +18,22 @@ class ViewCase(common.TransactionCase):
|
|||
def setUp(self):
|
||||
super(ViewCase, self).setUp()
|
||||
self.addTypeEqualityFunc(ET._Element, self.assertTreesEqual)
|
||||
self.Views = self.registry('ir.ui.view')
|
||||
|
||||
def browse(self, id, context=None):
|
||||
return self.Views.browse(self.cr, self.uid, id, context=context)
|
||||
def create(self, value, context=None):
|
||||
return self.Views.create(self.cr, self.uid, value, context=context)
|
||||
|
||||
def read_combined(self, id):
|
||||
return self.Views.read_combined(
|
||||
self.cr, self.uid,
|
||||
id, ['arch'],
|
||||
context={'check_view_ids': self.Views.search(self.cr, self.uid, [])}
|
||||
)
|
||||
|
||||
def assertTreesEqual(self, n1, n2, msg=None):
|
||||
self.assertEqual(n1.tag, n2.tag)
|
||||
self.assertEqual(n1.tag, n2.tag, msg)
|
||||
self.assertEqual((n1.text or '').strip(), (n2.text or '').strip(), msg)
|
||||
self.assertEqual((n1.tail or '').strip(), (n2.tail or '').strip(), msg)
|
||||
|
||||
|
@ -24,8 +41,8 @@ class ViewCase(common.TransactionCase):
|
|||
# equality (!?!?!?!)
|
||||
self.assertEqual(dict(n1.attrib), dict(n2.attrib), msg)
|
||||
|
||||
for c1, c2 in zip(n1, n2):
|
||||
self.assertTreesEqual(c1, c2, msg)
|
||||
for c1, c2 in itertools.izip_longest(n1, n2):
|
||||
self.assertEqual(c1, c2, msg)
|
||||
|
||||
|
||||
class TestNodeLocator(common.TransactionCase):
|
||||
|
@ -374,13 +391,6 @@ class TestApplyInheritedArchs(ViewCase):
|
|||
""" Applies a sequence of modificator archs to a base view
|
||||
"""
|
||||
|
||||
class TestViewCombined(ViewCase):
|
||||
"""
|
||||
Test fallback operations of View.read_combined:
|
||||
* defaults mapping
|
||||
* ?
|
||||
"""
|
||||
|
||||
class TestNoModel(ViewCase):
|
||||
def test_create_view_nomodel(self):
|
||||
View = self.registry('ir.ui.view')
|
||||
|
@ -628,6 +638,8 @@ class test_views(ViewCase):
|
|||
def _insert_view(self, **kw):
|
||||
"""Insert view into database via a query to passtrough validation"""
|
||||
kw.pop('id', None)
|
||||
kw.setdefault('mode', 'extension' if kw.get('inherit_id') else 'primary')
|
||||
kw.setdefault('application', 'always')
|
||||
|
||||
keys = sorted(kw.keys())
|
||||
fields = ','.join('"%s"' % (k.replace('"', r'\"'),) for k in keys)
|
||||
|
@ -805,6 +817,354 @@ class test_views(ViewCase):
|
|||
string="Replacement title", version="7.0"
|
||||
))
|
||||
|
||||
class ViewModeField(ViewCase):
|
||||
"""
|
||||
This should probably, eventually, be folded back into other test case
|
||||
classes, integrating the test (or not) of the mode field to regular cases
|
||||
"""
|
||||
|
||||
def testModeImplicitValue(self):
|
||||
""" mode is auto-generated from inherit_id:
|
||||
* inherit_id -> mode=extension
|
||||
* not inherit_id -> mode=primary
|
||||
"""
|
||||
view = self.browse(self.create({
|
||||
'inherit_id': None,
|
||||
'arch': '<qweb/>'
|
||||
}))
|
||||
self.assertEqual(view.mode, 'primary')
|
||||
|
||||
view2 = self.browse(self.create({
|
||||
'inherit_id': view.id,
|
||||
'arch': '<qweb/>'
|
||||
}))
|
||||
self.assertEqual(view2.mode, 'extension')
|
||||
|
||||
@openerp.tools.mute_logger('openerp.sql_db')
|
||||
def testModeExplicit(self):
|
||||
view = self.browse(self.create({
|
||||
'inherit_id': None,
|
||||
'arch': '<qweb/>'
|
||||
}))
|
||||
view2 = self.browse(self.create({
|
||||
'inherit_id': view.id,
|
||||
'mode': 'primary',
|
||||
'arch': '<qweb/>'
|
||||
}))
|
||||
self.assertEqual(view.mode, 'primary')
|
||||
|
||||
with self.assertRaises(IntegrityError):
|
||||
self.create({
|
||||
'inherit_id': None,
|
||||
'mode': 'extension',
|
||||
'arch': '<qweb/>'
|
||||
})
|
||||
|
||||
@openerp.tools.mute_logger('openerp.sql_db')
|
||||
def testPurePrimaryToExtension(self):
|
||||
"""
|
||||
A primary view with inherit_id=None can't be converted to extension
|
||||
"""
|
||||
view_pure_primary = self.browse(self.create({
|
||||
'inherit_id': None,
|
||||
'arch': '<qweb/>'
|
||||
}))
|
||||
with self.assertRaises(IntegrityError):
|
||||
view_pure_primary.write({'mode': 'extension'})
|
||||
|
||||
def testInheritPrimaryToExtension(self):
|
||||
"""
|
||||
A primary view with an inherit_id can be converted to extension
|
||||
"""
|
||||
base = self.create({'inherit_id': None, 'arch': '<qweb/>'})
|
||||
view = self.browse(self.create({
|
||||
'inherit_id': base,
|
||||
'mode': 'primary',
|
||||
'arch': '<qweb/>'
|
||||
}))
|
||||
|
||||
view.write({'mode': 'extension'})
|
||||
|
||||
def testDefaultExtensionToPrimary(self):
|
||||
"""
|
||||
An extension view can be converted to primary
|
||||
"""
|
||||
base = self.create({'inherit_id': None, 'arch': '<qweb/>'})
|
||||
view = self.browse(self.create({
|
||||
'inherit_id': base,
|
||||
'arch': '<qweb/>'
|
||||
}))
|
||||
|
||||
view.write({'mode': 'primary'})
|
||||
|
||||
class TestDefaultView(ViewCase):
|
||||
def testDefaultViewBase(self):
|
||||
self.create({
|
||||
'inherit_id': False,
|
||||
'priority': 10,
|
||||
'mode': 'primary',
|
||||
'arch': '<qweb/>',
|
||||
})
|
||||
v2 = self.create({
|
||||
'inherit_id': False,
|
||||
'priority': 1,
|
||||
'mode': 'primary',
|
||||
'arch': '<qweb/>',
|
||||
})
|
||||
|
||||
default = self.Views.default_view(self.cr, self.uid, False, 'qweb')
|
||||
self.assertEqual(
|
||||
default, v2,
|
||||
"default_view should get the view with the lowest priority for "
|
||||
"a (model, view_type) pair"
|
||||
)
|
||||
|
||||
def testDefaultViewPrimary(self):
|
||||
v1 = self.create({
|
||||
'inherit_id': False,
|
||||
'priority': 10,
|
||||
'mode': 'primary',
|
||||
'arch': '<qweb/>',
|
||||
})
|
||||
self.create({
|
||||
'inherit_id': False,
|
||||
'priority': 5,
|
||||
'mode': 'primary',
|
||||
'arch': '<qweb/>',
|
||||
})
|
||||
v3 = self.create({
|
||||
'inherit_id': v1,
|
||||
'priority': 1,
|
||||
'mode': 'primary',
|
||||
'arch': '<qweb/>',
|
||||
})
|
||||
|
||||
default = self.Views.default_view(self.cr, self.uid, False, 'qweb')
|
||||
self.assertEqual(
|
||||
default, v3,
|
||||
"default_view should get the view with the lowest priority for "
|
||||
"a (model, view_type) pair in all the primary tables"
|
||||
)
|
||||
|
||||
class TestViewCombined(ViewCase):
|
||||
"""
|
||||
* When asked for a view, instead of looking for the closest parent with
|
||||
inherit_id=False look for mode=primary
|
||||
* If root.inherit_id, resolve the arch for root.inherit_id (?using which
|
||||
model?), then apply root's inheritance specs to it
|
||||
* Apply inheriting views on top
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super(TestViewCombined, self).setUp()
|
||||
|
||||
self.a1 = self.create({
|
||||
'model': 'a',
|
||||
'arch': '<qweb><a1/></qweb>'
|
||||
})
|
||||
self.a2 = self.create({
|
||||
'model': 'a',
|
||||
'inherit_id': self.a1,
|
||||
'priority': 5,
|
||||
'arch': '<xpath expr="//a1" position="after"><a2/></xpath>'
|
||||
})
|
||||
self.a3 = self.create({
|
||||
'model': 'a',
|
||||
'inherit_id': self.a1,
|
||||
'arch': '<xpath expr="//a1" position="after"><a3/></xpath>'
|
||||
})
|
||||
# mode=primary should be an inheritance boundary in both direction,
|
||||
# even within a model it should not extend the parent
|
||||
self.a4 = self.create({
|
||||
'model': 'a',
|
||||
'inherit_id': self.a1,
|
||||
'mode': 'primary',
|
||||
'arch': '<xpath expr="//a1" position="after"><a4/></xpath>',
|
||||
})
|
||||
|
||||
self.b1 = self.create({
|
||||
'model': 'b',
|
||||
'inherit_id': self.a3,
|
||||
'mode': 'primary',
|
||||
'arch': '<xpath expr="//a1" position="after"><b1/></xpath>'
|
||||
})
|
||||
self.b2 = self.create({
|
||||
'model': 'b',
|
||||
'inherit_id': self.b1,
|
||||
'arch': '<xpath expr="//a1" position="after"><b2/></xpath>'
|
||||
})
|
||||
|
||||
self.c1 = self.create({
|
||||
'model': 'c',
|
||||
'inherit_id': self.a1,
|
||||
'mode': 'primary',
|
||||
'arch': '<xpath expr="//a1" position="after"><c1/></xpath>'
|
||||
})
|
||||
self.c2 = self.create({
|
||||
'model': 'c',
|
||||
'inherit_id': self.c1,
|
||||
'priority': 5,
|
||||
'arch': '<xpath expr="//a1" position="after"><c2/></xpath>'
|
||||
})
|
||||
self.c3 = self.create({
|
||||
'model': 'c',
|
||||
'inherit_id': self.c2,
|
||||
'priority': 10,
|
||||
'arch': '<xpath expr="//a1" position="after"><c3/></xpath>'
|
||||
})
|
||||
|
||||
self.d1 = self.create({
|
||||
'model': 'd',
|
||||
'inherit_id': self.b1,
|
||||
'mode': 'primary',
|
||||
'arch': '<xpath expr="//a1" position="after"><d1/></xpath>'
|
||||
})
|
||||
|
||||
def test_basic_read(self):
|
||||
arch = self.read_combined(self.a1)['arch']
|
||||
self.assertEqual(
|
||||
ET.fromstring(arch),
|
||||
E.qweb(
|
||||
E.a1(),
|
||||
E.a3(),
|
||||
E.a2(),
|
||||
), arch)
|
||||
|
||||
def test_read_from_child(self):
|
||||
arch = self.read_combined(self.a3)['arch']
|
||||
self.assertEqual(
|
||||
ET.fromstring(arch),
|
||||
E.qweb(
|
||||
E.a1(),
|
||||
E.a3(),
|
||||
E.a2(),
|
||||
), arch)
|
||||
|
||||
def test_read_from_child_primary(self):
|
||||
arch = self.read_combined(self.a4)['arch']
|
||||
self.assertEqual(
|
||||
ET.fromstring(arch),
|
||||
E.qweb(
|
||||
E.a1(),
|
||||
E.a4(),
|
||||
E.a3(),
|
||||
E.a2(),
|
||||
), arch)
|
||||
|
||||
def test_cross_model_simple(self):
|
||||
arch = self.read_combined(self.c2)['arch']
|
||||
self.assertEqual(
|
||||
ET.fromstring(arch),
|
||||
E.qweb(
|
||||
E.a1(),
|
||||
E.c3(),
|
||||
E.c2(),
|
||||
E.c1(),
|
||||
E.a3(),
|
||||
E.a2(),
|
||||
), arch)
|
||||
|
||||
def test_cross_model_double(self):
|
||||
arch = self.read_combined(self.d1)['arch']
|
||||
self.assertEqual(
|
||||
ET.fromstring(arch),
|
||||
E.qweb(
|
||||
E.a1(),
|
||||
E.d1(),
|
||||
E.b2(),
|
||||
E.b1(),
|
||||
E.a3(),
|
||||
E.a2(),
|
||||
), arch)
|
||||
|
||||
class TestOptionalViews(ViewCase):
|
||||
"""
|
||||
Tests ability to enable/disable inherited views, formerly known as
|
||||
inherit_option_id
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super(TestOptionalViews, self).setUp()
|
||||
self.v0 = self.create({
|
||||
'model': 'a',
|
||||
'arch': '<qweb><base/></qweb>',
|
||||
})
|
||||
self.v1 = self.create({
|
||||
'model': 'a',
|
||||
'inherit_id': self.v0,
|
||||
'application': 'always',
|
||||
'priority': 10,
|
||||
'arch': '<xpath expr="//base" position="after"><v1/></xpath>',
|
||||
})
|
||||
self.v2 = self.create({
|
||||
'model': 'a',
|
||||
'inherit_id': self.v0,
|
||||
'application': 'enabled',
|
||||
'priority': 9,
|
||||
'arch': '<xpath expr="//base" position="after"><v2/></xpath>',
|
||||
})
|
||||
self.v3 = self.create({
|
||||
'model': 'a',
|
||||
'inherit_id': self.v0,
|
||||
'application': 'disabled',
|
||||
'priority': 8,
|
||||
'arch': '<xpath expr="//base" position="after"><v3/></xpath>'
|
||||
})
|
||||
|
||||
def test_applied(self):
|
||||
""" mandatory and enabled views should be applied
|
||||
"""
|
||||
arch = self.read_combined(self.v0)['arch']
|
||||
self.assertEqual(
|
||||
ET.fromstring(arch),
|
||||
E.qweb(
|
||||
E.base(),
|
||||
E.v1(),
|
||||
E.v2(),
|
||||
)
|
||||
)
|
||||
|
||||
def test_applied_state_toggle(self):
|
||||
""" Change application states of v2 and v3, check that the results
|
||||
are as expected
|
||||
"""
|
||||
self.browse(self.v2).write({'application': 'disabled'})
|
||||
arch = self.read_combined(self.v0)['arch']
|
||||
self.assertEqual(
|
||||
ET.fromstring(arch),
|
||||
E.qweb(
|
||||
E.base(),
|
||||
E.v1(),
|
||||
)
|
||||
)
|
||||
|
||||
self.browse(self.v3).write({'application': 'enabled'})
|
||||
arch = self.read_combined(self.v0)['arch']
|
||||
self.assertEqual(
|
||||
ET.fromstring(arch),
|
||||
E.qweb(
|
||||
E.base(),
|
||||
E.v1(),
|
||||
E.v3(),
|
||||
)
|
||||
)
|
||||
|
||||
self.browse(self.v2).write({'application': 'enabled'})
|
||||
arch = self.read_combined(self.v0)['arch']
|
||||
self.assertEqual(
|
||||
ET.fromstring(arch),
|
||||
E.qweb(
|
||||
E.base(),
|
||||
E.v1(),
|
||||
E.v2(),
|
||||
E.v3(),
|
||||
)
|
||||
)
|
||||
|
||||
def test_mandatory_no_disabled(self):
|
||||
with self.assertRaises(Exception):
|
||||
self.browse(self.v1).write({'application': 'disabled'})
|
||||
|
||||
class TestXPathExtentions(common.BaseCase):
|
||||
def test_hasclass(self):
|
||||
tree = E.node(
|
||||
|
|
|
@ -217,9 +217,23 @@
|
|||
<rng:optional><rng:attribute name="priority"/></rng:optional>
|
||||
<rng:choice>
|
||||
<rng:group>
|
||||
<rng:optional><rng:attribute name="inherit_id"/></rng:optional>
|
||||
<rng:optional><rng:attribute name="inherit_option_id"/></rng:optional>
|
||||
<rng:optional>
|
||||
<rng:attribute name="inherit_id"/>
|
||||
<rng:optional>
|
||||
<rng:attribute name="primary">
|
||||
<rng:value>True</rng:value>
|
||||
</rng:attribute>
|
||||
</rng:optional>
|
||||
</rng:optional>
|
||||
<rng:optional><rng:attribute name="groups"/></rng:optional>
|
||||
<rng:optional>
|
||||
<rng:attribute name="optional">
|
||||
<rng:choice>
|
||||
<rng:value>enabled</rng:value>
|
||||
<rng:value>disabled</rng:value>
|
||||
</rng:choice>
|
||||
</rng:attribute>
|
||||
</rng:optional>
|
||||
</rng:group>
|
||||
<rng:optional>
|
||||
<rng:attribute name="page"><rng:value>True</rng:value></rng:attribute>
|
||||
|
|
|
@ -729,7 +729,6 @@ class BaseModel(object):
|
|||
_all_columns = {}
|
||||
|
||||
_table = None
|
||||
_invalids = set()
|
||||
_log_create = False
|
||||
_sql_constraints = []
|
||||
_protected = ['read', 'write', 'create', 'default_get', 'perm_read', 'unlink', 'fields_get', 'fields_view_get', 'search', 'name_get', 'distinct_field_get', 'name_search', 'copy', 'import_data', 'search_count', 'exists']
|
||||
|
@ -1543,9 +1542,6 @@ class BaseModel(object):
|
|||
|
||||
yield dbid, xid, converted, dict(extras, record=stream.index)
|
||||
|
||||
def get_invalid_fields(self, cr, uid):
|
||||
return list(self._invalids)
|
||||
|
||||
def _validate(self, cr, uid, ids, context=None):
|
||||
context = context or {}
|
||||
lng = context.get('lang')
|
||||
|
@ -1566,12 +1562,9 @@ class BaseModel(object):
|
|||
# Check presence of __call__ directly instead of using
|
||||
# callable() because it will be deprecated as of Python 3.0
|
||||
if hasattr(msg, '__call__'):
|
||||
tmp_msg = msg(self, cr, uid, ids, context=context)
|
||||
if isinstance(tmp_msg, tuple):
|
||||
tmp_msg, params = tmp_msg
|
||||
translated_msg = tmp_msg % params
|
||||
else:
|
||||
translated_msg = tmp_msg
|
||||
translated_msg = msg(self, cr, uid, ids, context=context)
|
||||
if isinstance(translated_msg, tuple):
|
||||
translated_msg = translated_msg[0] % translated_msg[1]
|
||||
else:
|
||||
translated_msg = trans._get_source(cr, uid, self._name, 'constraint', lng, msg)
|
||||
if extra_error:
|
||||
|
@ -1579,11 +1572,8 @@ class BaseModel(object):
|
|||
error_msgs.append(
|
||||
_("The field(s) `%s` failed against a constraint: %s") % (', '.join(fields), translated_msg)
|
||||
)
|
||||
self._invalids.update(fields)
|
||||
if error_msgs:
|
||||
raise except_orm('ValidateError', '\n'.join(error_msgs))
|
||||
else:
|
||||
self._invalids.clear()
|
||||
|
||||
def default_get(self, cr, uid, fields_list, context=None):
|
||||
"""
|
||||
|
|
|
@ -866,7 +866,7 @@ form: module.record_id""" % (xml_id,)
|
|||
if '.' not in full_tpl_id:
|
||||
full_tpl_id = '%s.%s' % (self.module, tpl_id)
|
||||
# set the full template name for qweb <module>.<id>
|
||||
if not (el.get('inherit_id') or el.get('inherit_option_id')):
|
||||
if not el.get('inherit_id'):
|
||||
el.set('t-name', full_tpl_id)
|
||||
el.tag = 't'
|
||||
else:
|
||||
|
@ -889,15 +889,18 @@ form: module.record_id""" % (xml_id,)
|
|||
record.append(Field("qweb", name='type'))
|
||||
record.append(Field(el.get('priority', "16"), name='priority'))
|
||||
record.append(Field(el, name="arch", type="xml"))
|
||||
for field_name in ('inherit_id','inherit_option_id'):
|
||||
value = el.attrib.pop(field_name, None)
|
||||
if value: record.append(Field(name=field_name, ref=value))
|
||||
if 'inherit_id' in el.attrib:
|
||||
record.append(Field(name='inherit_id', ref=el.get('inherit_id')))
|
||||
groups = el.attrib.pop('groups', None)
|
||||
if groups:
|
||||
grp_lst = map(lambda x: "ref('%s')" % x, groups.split(','))
|
||||
record.append(Field(name="groups_id", eval="[(6, 0, ["+', '.join(grp_lst)+"])]"))
|
||||
if el.attrib.pop('page', None) == 'True':
|
||||
record.append(Field(name="page", eval="True"))
|
||||
if el.get('primary') == 'True':
|
||||
record.append(Field('primary', name='mode'))
|
||||
if el.get('optional'):
|
||||
record.append(Field(el.get('optional'), name='application'))
|
||||
|
||||
return self._tag_record(cr, record, data_node)
|
||||
|
||||
|
|
Loading…
Reference in New Issue