diff --git a/addons/account/partner.py b/addons/account/partner.py index d9bb6083f43..70f5280b861 100644 --- a/addons/account/partner.py +++ b/addons/account/partner.py @@ -27,13 +27,19 @@ from openerp.osv import fields, osv class account_fiscal_position(osv.osv): _name = 'account.fiscal.position' _description = 'Fiscal Position' + _order = 'sequence' _columns = { + 'sequence': fields.integer('Sequence'), 'name': fields.char('Fiscal Position', required=True), 'active': fields.boolean('Active', help="By unchecking the active field, you may hide a fiscal position without deleting it."), 'company_id': fields.many2one('res.company', 'Company'), 'account_ids': fields.one2many('account.fiscal.position.account', 'position_id', 'Account Mapping'), 'tax_ids': fields.one2many('account.fiscal.position.tax', 'position_id', 'Tax Mapping'), 'note': fields.text('Notes'), + 'auto_apply': fields.boolean('Automatic', help="Apply automatically this fiscal position."), + 'vat_required': fields.boolean('VAT required', help="Apply only if partner has a VAT number."), + 'country_id': fields.many2one('res.country', 'Countries', help="Apply only if delivery or invoicing country match."), + 'country_group_id': fields.many2one('res.country.group', 'Country Group', help="Apply only if delivery or invocing country match the group."), } _defaults = { @@ -66,6 +72,33 @@ class account_fiscal_position(osv.osv): break return account_id + def get_fiscal_position(self, cr, uid, company_id, partner_id, delivery_id=None, context=None): + if not partner_id: + return False + # This can be easily overriden to apply more complex fiscal rules + part_obj = self.pool['res.partner'] + partner = part_obj.browse(cr, uid, partner_id, context=context) + + # partner manually set fiscal position always win + if partner.property_account_position: + return part.property_account_position.id + + # if no delivery use invocing + if delivery_id: + delivery = part_obj.browse(cr, uid, delivery_id, context=context) + else: + delivery = partner + + domain = [ + ('auto_apply', '=', True), + '|', ('vat_required', '=', False), ('vat_required', '=', partner.vat_subjected), + '|', ('country_id', '=', None), ('country_id', '=', delivery.country_id.id), + '|', ('country_group_id', '=', None), ('country_group_id.country_ids', '=', delivery.country_id.id) + ] + fiscal_position_ids = self.search(cr, uid, domain, context=context) + if fiscal_position_ids: + return fiscal_position_ids[0] + return False class account_fiscal_position_tax(osv.osv): _name = 'account.fiscal.position.tax' @@ -206,6 +239,7 @@ class res_partner(osv.osv): return self.write(cr, uid, ids, {'last_reconciliation_date': time.strftime('%Y-%m-%d %H:%M:%S')}, context=context) _columns = { + 'vat_subjected': fields.boolean('VAT Legal Statement', help="Check this box if the partner is subjected to the VAT. It will be used for the VAT legal statement."), 'credit': fields.function(_credit_debit_get, fnct_search=_credit_search, string='Total Receivable', multi='dc', help="Total amount this customer owes you."), 'debit': fields.function(_credit_debit_get, fnct_search=_debit_search, string='Total Payable', multi='dc', help="Total amount you have to pay to this supplier."), diff --git a/addons/account/partner_view.xml b/addons/account/partner_view.xml index c2a01c7e40a..309296c7955 100644 --- a/addons/account/partner_view.xml +++ b/addons/account/partner_view.xml @@ -12,6 +12,13 @@ + + + + + + + @@ -44,6 +51,7 @@ account.fiscal.position + diff --git a/addons/account/report/account_invoice_report_view.xml b/addons/account/report/account_invoice_report_view.xml index 01e3dc72d68..35cb43467f2 100644 --- a/addons/account/report/account_invoice_report_view.xml +++ b/addons/account/report/account_invoice_report_view.xml @@ -21,7 +21,7 @@ - + diff --git a/addons/base_vat/base_vat.py b/addons/base_vat/base_vat.py index 9b3862508b0..b7ac9592dd3 100644 --- a/addons/base_vat/base_vat.py +++ b/addons/base_vat/base_vat.py @@ -133,10 +133,6 @@ class res_partner(osv.osv): def vat_change(self, cr, uid, ids, value, context=None): return {'value': {'vat_subjected': bool(value)}} - _columns = { - 'vat_subjected': fields.boolean('VAT Legal Statement', help="Check this box if the partner is subjected to the VAT. It will be used for the VAT legal statement.") - } - def _commercial_fields(self, cr, uid, context=None): return super(res_partner, self)._commercial_fields(cr, uid, context=context) + ['vat_subjected'] diff --git a/addons/google_drive/google_drive_data.xml b/addons/google_drive/google_drive_data.xml index f3f916b201b..72e541df56e 100644 --- a/addons/google_drive/google_drive_data.xml +++ b/addons/google_drive/google_drive_data.xml @@ -5,7 +5,7 @@ google_drive_client_id 598905559630.apps.googleusercontent.com - + diff --git a/addons/mail/static/src/js/mail.js b/addons/mail/static/src/js/mail.js index dbe7d54aade..b3fb0dab18f 100644 --- a/addons/mail/static/src/js/mail.js +++ b/addons/mail/static/src/js/mail.js @@ -1984,51 +1984,6 @@ openerp.mail = function (session) { } }); - - /** - * ------------------------------------------------------------ - * UserMenu - * ------------------------------------------------------------ - * - * Add a link on the top user bar for write a full mail - */ - session.web.ComposeMessageTopButton = session.web.Widget.extend({ - template:'mail.ComposeMessageTopButton', - - start: function () { - this.$el.on('click', this.on_compose_message); - this._super(); - }, - - on_compose_message: function (event) { - event.preventDefault(); - event.stopPropagation(); - var action = { - type: 'ir.actions.act_window', - res_model: 'mail.compose.message', - view_mode: 'form', - view_type: 'form', - views: [[false, 'form']], - target: 'new', - context: {}, - }; - session.client.action_manager.do_action(action); - }, - }); - - session.web.UserMenu.include({ - do_update: function(){ - var self = this; - this._super.apply(this, arguments); - this.update_promise.then(function() { - var mail_button = new session.web.ComposeMessageTopButton(); - mail_button.appendTo(session.webclient.$el.parents().find('.oe_systray')); - openerp.web.bus.trigger('resize'); // Re-trigger the reflow logic - }); - }, - }); - - /** * ------------------------------------------------------------ * Sub-widgets loading diff --git a/addons/mail/static/src/xml/mail.xml b/addons/mail/static/src/xml/mail.xml index c79b95224b1..86041cfcdae 100644 --- a/addons/mail/static/src/xml/mail.xml +++ b/addons/mail/static/src/xml/mail.xml @@ -335,18 +335,6 @@ - - -
  • - - - -
  • -
    - diff --git a/addons/purchase_requisition/test/purchase_requisition.yml b/addons/purchase_requisition/test/purchase_requisition.yml index 5ac152d3d29..355e13e0677 100644 --- a/addons/purchase_requisition/test/purchase_requisition.yml +++ b/addons/purchase_requisition/test/purchase_requisition.yml @@ -69,11 +69,11 @@ purchase_requisition = self.pool.get("purchase.requisition") purchase_ids = self.search(cr, uid, [('requisition_id','=',ref("requisition1"))]) assert purchase_ids, "RFQ is not created." - rfq = self.browse(cr, uid, purchase_ids[0], context=context) - requisition = rfq.requisition_id - supplier = rfq.partner_id - assert supplier.id == ref('base.res_partner_12'), "RFQ Partner is not correspond." - assert len(rfq.order_line) == len(requisition.line_ids), "Lines are not correspond." + for rfq in self.browse(cr, uid, purchase_ids, context=context): + if rfq.partner_id.id == ref('base.res_partner_12'): + break + else: + assert False, "No PO found for res_partner_12." - I confirmed RFQ which has best price. - diff --git a/addons/sale/sale.py b/addons/sale/sale.py index 87ad2d80be0..5e44ad5b0f4 100644 --- a/addons/sale/sale.py +++ b/addons/sale/sale.py @@ -320,6 +320,16 @@ class sale_order(osv.osv): context_lang.update({'lang': partner_lang}) return self.pool.get('res.users').browse(cr, uid, uid, context=context_lang).company_id.sale_note + def onchange_delivery_id(self, cr, uid, ids, company_id, partner_id, delivery_id, fiscal_position, context=None): + r = {'value': {}} + if not fiscal_position: + if not company_id: + company_id = self._get_default_company(cr, uid, context=context) + fiscal_position = self.pool['account.fiscal.position'].get_fiscal_position(cr, uid, company_id, partner_id, delivery_id, context=context) + if fiscal_position: + r['value']['fiscal_position'] = fiscal_position + return r + def onchange_partner_id(self, cr, uid, ids, part, context=None): if not part: return {'value': {'partner_invoice_id': False, 'partner_shipping_id': False, 'payment_term': False, 'fiscal_position': False}} @@ -328,15 +338,15 @@ class sale_order(osv.osv): addr = self.pool.get('res.partner').address_get(cr, uid, [part.id], ['delivery', 'invoice', 'contact']) pricelist = part.property_product_pricelist and part.property_product_pricelist.id or False payment_term = part.property_payment_term and part.property_payment_term.id or False - fiscal_position = part.property_account_position and part.property_account_position.id or False dedicated_salesman = part.user_id and part.user_id.id or uid val = { 'partner_invoice_id': addr['invoice'], 'partner_shipping_id': addr['delivery'], 'payment_term': payment_term, - 'fiscal_position': fiscal_position, 'user_id': dedicated_salesman, } + delivery_onchange = self.onchange_delivery_id(cr, uid, ids, False, part.id, addr['delivery'], False, context=context) + val.update(delivery_onchange['value']) if pricelist: val['pricelist_id'] = pricelist sale_note = self.get_salenote(cr, uid, ids, part.id, context=context) @@ -345,11 +355,14 @@ class sale_order(osv.osv): def create(self, cr, uid, vals, context=None): if context is None: - context = {} + context = {} if vals.get('name', '/') == '/': vals['name'] = self.pool.get('ir.sequence').get(cr, uid, 'sale.order') or '/' - if vals.get('partner_id') and any(f not in vals for f in ['partner_invoice_id', 'partner_shipping_id', 'pricelist_id']): - defaults = self.onchange_partner_id(cr, uid, [], vals['partner_id'], context)['value'] + if vals.get('partner_id') and any(f not in vals for f in ['partner_invoice_id', 'partner_shipping_id', 'pricelist_id', 'fiscal_position']): + defaults = self.onchange_partner_id(cr, uid, [], vals['partner_id'], context=context)['value'] + if not vals.get('fiscal_position') and vals.get('partner_shipping_id'): + delivery_onchange = self.onchange_delivery_id(cr, uid, [], vals.get('company_id'), None, vals['partner_id'], vals.get('partner_shipping_id'), context=context) + defaults.update(delivery_onchange['value']) vals = dict(defaults, **vals) context.update({'mail_create_nolog': True}) new_id = super(sale_order, self).create(cr, uid, vals, context=context) diff --git a/addons/sale/sale_view.xml b/addons/sale/sale_view.xml index d5d072c4513..f61c518918e 100644 --- a/addons/sale/sale_view.xml +++ b/addons/sale/sale_view.xml @@ -102,7 +102,7 @@ - + diff --git a/addons/web/static/src/js/chrome.js b/addons/web/static/src/js/chrome.js index 9fcbdff1f89..3534de032eb 100644 --- a/addons/web/static/src/js/chrome.js +++ b/addons/web/static/src/js/chrome.js @@ -1377,7 +1377,6 @@ instance.web.WebClient = instance.web.Client.extend({ var state = event.getState(true); if(!state.action && state.menu_id) { self.menu.is_bound.done(function() { - self.menu.do_reload(); self.menu.menu_click(state.menu_id); }); } else { diff --git a/addons/website/tests/__init__.py b/addons/website/tests/__init__.py index b7eb1831806..468a16017e8 100644 --- a/addons/website/tests/__init__.py +++ b/addons/website/tests/__init__.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- import test_converter -import test_requests +import test_crawl import test_ui import test_views diff --git a/addons/website/tests/cases.py b/addons/website/tests/cases.py deleted file mode 100644 index 3e777293ff9..00000000000 --- a/addons/website/tests/cases.py +++ /dev/null @@ -1,35 +0,0 @@ -# -*- coding: utf-8 -*- -import unittest2 - -class URLCase(unittest2.TestCase): - """ - URLCase moved out of test_requests, otherwise discovery attempts to - instantiate and run it - """ - def __init__(self, user, url, source, result): - super(URLCase, self).__init__() - self.user = user - self.url = url - self.source = source - self.result = result - - @property - def username(self): - return self.user or "Anonymous Coward" - - def __str__(self): - if self.source: - return "%s (from %s, as %s)" % (self.url, self.source, self.username) - return "%s (as %s)" % (self.url, self.username) - - __repr__ = __str__ - - def shortDescription(self): - return "" - - def runTest(self): - code = self.result.getcode() - self.assertIn( - code, xrange(200, 300), - "Fetching %s as %s returned an error response (%d)" % ( - self.url, self.username, code)) diff --git a/addons/website/tests/test_crawl.py b/addons/website/tests/test_crawl.py new file mode 100644 index 00000000000..2d68410f6f9 --- /dev/null +++ b/addons/website/tests/test_crawl.py @@ -0,0 +1,90 @@ +# -*- coding: utf-8 -*- +import logging +import urlparse +import unittest2 +import urllib2 +import time +import werkzeug.urls + +import lxml.html + +import openerp +from openerp import tools + +_logger = logging.getLogger(__name__) + +class Crawler(openerp.tests.HttpCase): + """ Test suite crawling an openerp CMS instance and checking that all + internal links lead to a 200 response. + + If a username and a password are provided, authenticates the user before + starting the crawl + """ + + at_install = False + post_install = True + + def crawl(self, url, seen=None, msg=''): + if seen == None: + seen = set() + if url in seen: + return seen + else: + seen.add(url) + + _logger.info("%s %s", msg, url) + r = self.url_open(url) + code = r.getcode() + self.assertIn( code, xrange(200, 300), "%s Fetching %s returned error response (%d)" % (msg, url, code)) + + if r.info().gettype() == 'text/html': + doc = lxml.html.fromstring(r.read()) + for link in doc.xpath('//a[@href]'): + href = link.get('href') + + parts = urlparse.urlsplit(href) + # href with any fragment removed + href = urlparse.urlunsplit(( + parts.scheme, + parts.netloc, + parts.path, + parts.query, + '' + )) + + # FIXME: handle relative link (not parts.path.startswith /) + if parts.netloc or \ + not parts.path.startswith('/') or \ + parts.path == '/web' or\ + parts.path.startswith('/web/') or \ + parts.path.startswith('/en_US/') or \ + (parts.scheme and parts.scheme not in ('http', 'https')): + continue + + self.crawl(href, seen, msg) + return seen + + + def test_10_crawl_public(self): + t0 = time.time() + seen = self.crawl('/', msg='Anonymous Coward') + count = len(seen) + duration = time.time() - t0 + _logger.log(25, "public crawled %s urls in %.2fs, %.3fs per query", count, duration, duration/count) + + def test_20_crawl_demo(self): + t0 = time.time() + self.authenticate('demo', 'demo') + seen = self.crawl('/', msg='demo') + count = len(seen) + duration = time.time() - t0 + _logger.log(25, "demo crawled %s urls in %.2fs, %.3fs per query", count, duration, duration/count) + + def test_30_crawl_admin(self): + t0 = time.time() + self.authenticate('admin', 'admin') + seen = self.crawl('/', msg='admin') + count = len(seen) + duration = time.time() - t0 + _logger.log(25, "admin crawled %s urls in %.2fs, %.3fs per query", count, duration, duration/count) + diff --git a/addons/website/tests/test_requests.py b/addons/website/tests/test_requests.py deleted file mode 100644 index 2a1eea4c877..00000000000 --- a/addons/website/tests/test_requests.py +++ /dev/null @@ -1,155 +0,0 @@ -# -*- coding: utf-8 -*- -import urlparse -import unittest2 -import urllib2 -import werkzeug.urls - -import lxml.html - -import openerp -from openerp import tools - -import cases - -__all__ = ['load_tests', 'CrawlSuite'] - -class RedirectHandler(urllib2.HTTPRedirectHandler): - """ - HTTPRedirectHandler is predicated upon HTTPErrorProcessor being used and - works by intercepting 3xy "errors". - - Inherit from it to handle 3xy non-error responses instead, as we're not - using the error processor - """ - - def http_response(self, request, response): - code, msg, hdrs = response.code, response.msg, response.info() - - if 300 <= code < 400: - return self.parent.error( - 'http', request, response, code, msg, hdrs) - - return response - - https_response = http_response - -class CrawlSuite(unittest2.TestSuite): - """ Test suite crawling an openerp CMS instance and checking that all - internal links lead to a 200 response. - - If a username and a password are provided, authenticates the user before - starting the crawl - """ - - at_install = False - post_install = True - - def __init__(self, user=None, password=None): - super(CrawlSuite, self).__init__() - - registry = openerp.registry(tools.config['db_name']) - try: - # switch registry to test mode, so that requests can be made - registry.enter_test_mode() - - self.opener = urllib2.OpenerDirector() - self.opener.add_handler(urllib2.UnknownHandler()) - self.opener.add_handler(urllib2.HTTPHandler()) - self.opener.add_handler(urllib2.HTTPSHandler()) - self.opener.add_handler(urllib2.HTTPCookieProcessor()) - self.opener.add_handler(RedirectHandler()) - - self._authenticate(user, password) - self.user = user - - finally: - registry.leave_test_mode() - - def _request(self, path): - return self.opener.open(urlparse.urlunsplit([ - 'http', 'localhost:%s' % tools.config['xmlrpc_port'], - path, '', '' - ])) - - def _authenticate(self, user, password): - # force tools.config['db_name'] in user session so opening `/` doesn't - # blow up in multidb situations - self.opener.open('http://localhost:{port}/web/?db={db}'.format( - port=tools.config['xmlrpc_port'], - db=werkzeug.urls.url_quote_plus(tools.config['db_name']), - )) - if user is not None: - url = 'http://localhost:{port}/login?{query}'.format( - port=tools.config['xmlrpc_port'], - query=werkzeug.urls.url_encode({ - 'db': tools.config['db_name'], - 'login': user, - 'key': password, - }) - ) - auth = self.opener.open(url) - assert auth.getcode() < 400, "Auth failure %d" % auth.getcode() - - def _wrapped_run(self, result, debug=False): - registry = openerp.registry(tools.config['db_name']) - try: - # switch registry to test mode, so that requests can be made - registry.enter_test_mode() - - paths = [URL('/')] - seen = set(paths) - - while paths: - url = paths.pop(0) - r = self._request(url.url) - url.to_case(self.user, r).run(result) - - if r.info().gettype() != 'text/html': - continue - - doc = lxml.html.fromstring(r.read()) - for link in doc.xpath('//a[@href]'): - href = link.get('href') - - parts = urlparse.urlsplit(href) - # href with any fragment removed - href = urlparse.urlunsplit(( - parts.scheme, - parts.netloc, - parts.path, - parts.query, - '' - )) - - # avoid repeats, even for links we won't crawl no need to - # bother splitting them if we've already ignored them - # previously - if href in seen: continue - seen.add(href) - - # FIXME: handle relative link (not parts.path.startswith /) - if parts.netloc or \ - not parts.path.startswith('/') or \ - parts.path == '/web' or\ - parts.path.startswith('/web/') or \ - (parts.scheme and parts.scheme not in ('http', 'https')): - continue - - paths.append(URL(href, url.url)) - - finally: - registry.leave_test_mode() - -class URL(object): - def __init__(self, url, source=None): - self.url = url - self.source = source - - def to_case(self, user, result): - return cases.URLCase(user, self.url, self.source, result) - -def load_tests(loader, base, _): - base.addTest(CrawlSuite()) - base.addTest(CrawlSuite('admin', 'admin')) - base.addTest(CrawlSuite('demo', 'demo')) - return base diff --git a/addons/website_sale/controllers/main.py b/addons/website_sale/controllers/main.py index f187e6799ac..656f581bc63 100644 --- a/addons/website_sale/controllers/main.py +++ b/addons/website_sale/controllers/main.py @@ -456,6 +456,7 @@ class website_sale(http.Controller): if partner_id and request.website.partner_id.id != partner_id: orm_partner.write(cr, SUPERUSER_ID, [partner_id], billing_info, context=context) else: + # create partner partner_id = orm_partner.create(cr, SUPERUSER_ID, billing_info, context=context) # create a new shipping partner @@ -472,7 +473,9 @@ class website_sale(http.Controller): 'partner_invoice_id': partner_id, 'partner_shipping_id': shipping_id or partner_id } - order_info.update(registry.get('sale.order').onchange_partner_id(cr, SUPERUSER_ID, [], partner_id, context=context)['value']) + order_info.update(order_obj.onchange_partner_id(cr, SUPERUSER_ID, [], partner_id, context=context)['value']) + order_info.update(order_obj.onchange_delivery_id(cr, SUPERUSER_ID, [], order.company_id.id, partner_id, shipping_id, None, context=context)['value']) + order_info.pop('user_id') order_obj.write(cr, SUPERUSER_ID, [order.id], order_info, context=context) @@ -512,6 +515,8 @@ class website_sale(http.Controller): self.checkout_form_save(values["checkout"]) request.session['sale_last_order_id'] = order.id + request.website.sale_get_order(update_pricelist=True, context=context) + return request.redirect("/shop/payment") #------------------------------------------------------ diff --git a/addons/website_sale/models/sale_order.py b/addons/website_sale/models/sale_order.py index 165030f9f1a..7c0700a555a 100644 --- a/addons/website_sale/models/sale_order.py +++ b/addons/website_sale/models/sale_order.py @@ -128,7 +128,7 @@ class website(orm.Model): def sale_product_domain(self, cr, uid, ids, context=None): return [("sale_ok", "=", True)] - def sale_get_order(self, cr, uid, ids, force_create=False, code=None, context=None): + def sale_get_order(self, cr, uid, ids, force_create=False, code=None, update_pricelist=None, context=None): sale_order_obj = self.pool['sale.order'] sale_order_id = request.session.get('sale_order_id') sale_order = None @@ -157,26 +157,20 @@ class website(orm.Model): request.session['sale_order_id'] = None return None - def update_pricelist(pricelist_id): - values = {'pricelist_id': pricelist_id} - values.update(sale_order.onchange_pricelist_id(pricelist_id, None)['value']) - sale_order.write(values) - for line in sale_order.order_line: - sale_order._cart_update(product_id=line.product_id.id, add_qty=0) - # check for change of pricelist with a coupon if code and code != sale_order.pricelist_id.code: pricelist_ids = self.pool['product.pricelist'].search(cr, SUPERUSER_ID, [('code', '=', code)], context=context) if pricelist_ids: pricelist_id = pricelist_ids[0] request.session['sale_order_code_pricelist_id'] = pricelist_id - update_pricelist(pricelist_id) + update_pricelist = True request.session['sale_order_code_pricelist_id'] = False + pricelist_id = request.session.get('sale_order_code_pricelist_id') or partner.property_product_pricelist.id + # check for change of partner_id ie after signup if sale_order.partner_id.id != partner.id and request.website.partner_id.id != partner.id: flag_pricelist = False - pricelist_id = request.session.get('sale_order_code_pricelist_id') or partner.property_product_pricelist.id if pricelist_id != sale_order.pricelist_id.id: flag_pricelist = True fiscal_position = sale_order.fiscal_position and sale_order.fiscal_position.id or False @@ -190,7 +184,15 @@ class website(orm.Model): sale_order_obj.write(cr, SUPERUSER_ID, [sale_order_id], values, context=context) if flag_pricelist or values.get('fiscal_position') != fiscal_position: - update_pricelist(pricelist_id) + update_pricelist = True + + # update the pricelist + if update_pricelist: + values = {'pricelist_id': pricelist_id} + values.update(sale_order.onchange_pricelist_id(pricelist_id, None)['value']) + sale_order.write(values) + for line in sale_order.order_line: + sale_order._cart_update(product_id=line.product_id.id, add_qty=0) # update browse record if (code and code != sale_order.pricelist_id.code) or sale_order.partner_id.id != partner.id: diff --git a/odoo.py b/odoo.py index a998c3f8db6..b8f93ad19d5 100755 --- a/odoo.py +++ b/odoo.py @@ -68,7 +68,7 @@ def cmd_setup_git(): git_dir = os.getcwd() if git_dir: # push sane config for git < 2.0, and hooks - run('git','config','push.default','simple') + #run('git','config','push.default','simple') # alias run('git','config','alias.st','status') # merge bzr style diff --git a/openerp/addons/base/ir/ir_ui_menu.py b/openerp/addons/base/ir/ir_ui_menu.py index 8ace579defb..718dcb0c36c 100644 --- a/openerp/addons/base/ir/ir_ui_menu.py +++ b/openerp/addons/base/ir/ir_ui_menu.py @@ -349,6 +349,7 @@ class ir_ui_menu(osv.osv): menu_domain = [('parent_id', '=', False)] return self.search(cr, uid, menu_domain, context=context) + @tools.ormcache() def load_menus(self, cr, uid, context=None): """ Loads all menu items (all applications and their sub-menus). diff --git a/openerp/addons/base/res/res_country.py b/openerp/addons/base/res/res_country.py index 10f82df2ee8..f086f1440e8 100644 --- a/openerp/addons/base/res/res_country.py +++ b/openerp/addons/base/res/res_country.py @@ -84,6 +84,14 @@ addresses belonging to this country.\n\nYou can use the python-style string pate context=context) +class CountryGroup(osv.osv): + _description="Country Group" + _name = 'res.country.group' + _columns = { + 'name': fields.char('Name', required=True), + 'country_ids': fields.many2many('res.country', string='Countries'), + } + class CountryState(osv.osv): _description="Country state" _name = 'res.country.state' diff --git a/openerp/addons/base/res/res_country_data.xml b/openerp/addons/base/res/res_country_data.xml index 633b4cc42c4..1bdd6a73460 100644 --- a/openerp/addons/base/res/res_country_data.xml +++ b/openerp/addons/base/res/res_country_data.xml @@ -1516,5 +1516,18 @@
    + + + + + Europe + + diff --git a/openerp/addons/base/res/res_country_view.xml b/openerp/addons/base/res/res_country_view.xml index ad46235c5c6..f1316844dd7 100644 --- a/openerp/addons/base/res/res_country_view.xml +++ b/openerp/addons/base/res/res_country_view.xml @@ -50,6 +50,38 @@ + + res.country.group.tree + res.country.group + + + + + + + + + res.country.group.form + res.country.group + +
    + + + + +
    + + + Country Group + ir.actions.act_window + res.country.group + form + Display and manage the list of all countries group. You can create or delete country group to make sure the ones you are working on will be maintained. + + + + + diff --git a/openerp/addons/base/security/ir.model.access.csv b/openerp/addons/base/security/ir.model.access.csv index c70733b5140..bbf579486a4 100644 --- a/openerp/addons/base/security/ir.model.access.csv +++ b/openerp/addons/base/security/ir.model.access.csv @@ -45,8 +45,10 @@ "access_res_company_group_user","res_company group_user","model_res_company",,1,0,0,0 "access_res_country_group_all","res_country group_user_all","model_res_country",,1,0,0,0 "access_res_country_state_group_all","res_country_state group_user_all","model_res_country_state",,1,0,0,0 +"access_res_country_group_group_all","res_country_group group_user_all","model_res_country_group",,1,0,0,0 "access_res_country_group_user","res_country group_user","model_res_country","group_partner_manager",1,1,1,1 "access_res_country_state_group_user","res_country_state group_user","model_res_country_state","group_partner_manager",1,1,1,1 +"access_res_country_group_group_user","res_country_group group_user","model_res_country_group","group_partner_manager",1,1,1,1 "access_res_currency_group_all","res_currency group_all","model_res_currency",,1,0,0,0 "access_res_currency_rate_group_all","res_currency_rate group_all","model_res_currency_rate",,1,0,0,0 "access_res_currency_rate_type_group_all","res_currency_rate_type group_all","model_res_currency_rate_type",,1,0,0,0 diff --git a/openerp/modules/loading.py b/openerp/modules/loading.py index d8391f4757c..b9b421a20ca 100644 --- a/openerp/modules/loading.py +++ b/openerp/modules/loading.py @@ -226,17 +226,17 @@ def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules= if hasattr(package, kind): delattr(package, kind) - _logger.log(25, "%s loaded in %.2fs", package.name, time.time() - tm0) + #_logger.log(25, "%s loaded in %.2fs", package.name, time.time() - tm0) registry._init_modules.add(package.name) cr.commit() - _logger.log(25, "%s modules loaded in %.2fs", len(graph), time.time() - ta0) + _logger.log(25, "%s modules loaded in %.2fs", len(graph), time.time() - ta0) # The query won't be valid for models created later (i.e. custom model # created after the registry has been loaded), so empty its result. registry.fields_by_model = None - + cr.commit() return loaded_modules, processed_modules diff --git a/openerp/modules/module.py b/openerp/modules/module.py index 18a0cdbf727..66119ce31f1 100644 --- a/openerp/modules/module.py +++ b/openerp/modules/module.py @@ -428,7 +428,8 @@ def run_unit_tests(module_name, dbname, position=runs_at_install): tm0 = time.time() _logger.info('%s running tests.', m.__name__) result = unittest2.TextTestRunner(verbosity=2, stream=TestStream(m.__name__)).run(suite) - _logger.log(25, "%s tested in %.2fs", m.__name__, time.time() - tm0) + if time.time() - tm0 > 5: + _logger.log(25, "%s tested in %.2fs", m.__name__, time.time() - tm0) if not result.wasSuccessful(): r = False _logger.error("Module %s: %d failures, %d errors", module_name, len(result.failures), len(result.errors)) diff --git a/openerp/netsvc.py b/openerp/netsvc.py index 0703c654f80..856faa8f61d 100644 --- a/openerp/netsvc.py +++ b/openerp/netsvc.py @@ -68,6 +68,8 @@ def LocalService(name): with registry.cursor() as cr: return registry['ir.actions.report.xml']._lookup_report(cr, name[len('report.'):]) +path_prefix = os.path.realpath(os.path.dirname(os.path.dirname(__file__))) + class PostgreSQLHandler(logging.Handler): """ PostgreSQL Loggin Handler will store logs in the database, by default the current database, can be set using --log-db=DBNAME @@ -87,7 +89,8 @@ class PostgreSQLHandler(logging.Handler): msg = "%s\n%s" % (msg, traceback) # we do not use record.levelname because it may have been changed by ColoredFormatter. levelname = logging.getLevelName(record.levelno) - val = ('server', ct_db, record.name, levelname, msg, record.pathname, record.lineno, record.funcName) + + val = ('server', ct_db, record.name, levelname, msg, record.pathname[len(path_prefix)+1:], record.lineno, record.funcName) cr.execute(""" INSERT INTO ir_logging(create_date, type, dbname, name, level, message, path, line, func) VALUES (NOW() at time zone 'UTC', %s, %s, %s, %s, %s, %s, %s, %s) diff --git a/openerp/tests/common.py b/openerp/tests/common.py index 658ef89423d..50b85555d04 100644 --- a/openerp/tests/common.py +++ b/openerp/tests/common.py @@ -5,6 +5,7 @@ helpers and classes to write tests. """ import errno +import glob import json import logging import os @@ -130,6 +131,25 @@ class SingleTransactionCase(BaseCase): cls.cr.rollback() cls.cr.close() +class RedirectHandler(urllib2.HTTPRedirectHandler): + """ + HTTPRedirectHandler is predicated upon HTTPErrorProcessor being used and + works by intercepting 3xy "errors". + + Inherit from it to handle 3xy non-error responses instead, as we're not + using the error processor + """ + + def http_response(self, request, response): + code, msg, hdrs = response.code, response.msg, response.info() + + if 300 <= code < 400: + return self.parent.error( + 'http', request, response, code, msg, hdrs) + + return response + + https_response = http_response class HttpCase(TransactionCase): """ Transactionnal HTTP TestCase with url_open and phantomjs helpers. @@ -151,19 +171,29 @@ class HttpCase(TransactionCase): self.session_id = self.session.sid self.session.db = DB openerp.http.root.session_store.save(self.session) - self.localstorage_path = mkdtemp() + # setup an url opener helper + self.opener = urllib2.OpenerDirector() + self.opener.add_handler(urllib2.UnknownHandler()) + self.opener.add_handler(urllib2.HTTPHandler()) + self.opener.add_handler(urllib2.HTTPSHandler()) + self.opener.add_handler(urllib2.HTTPCookieProcessor()) + self.opener.add_handler(RedirectHandler()) + self.opener.addheaders.append(('Cookie', 'session_id=%s' % self.session_id)) def tearDown(self): - rmtree(self.localstorage_path) self.registry.leave_test_mode() super(HttpCase, self).tearDown() def url_open(self, url, data=None, timeout=10): - opener = urllib2.build_opener() - opener.addheaders.append(('Cookie', 'session_id=%s' % self.session_id)) if url.startswith('/'): url = "http://localhost:%s%s" % (PORT, url) - return opener.open(url, data, timeout) + return self.opener.open(url, data, timeout) + + def authenticate(self, user, password): + if user is not None: + url = '/login?%s' % werkzeug.urls.url_encode({'db': DB,'login': user, 'key': password}) + auth = self.url_open(url) + assert auth.getcode() < 400, "Auth failure %d" % auth.getcode() def phantom_poll(self, phantom, timeout): """ Phantomjs Test protocol. @@ -223,6 +253,11 @@ class HttpCase(TransactionCase): def phantom_run(self, cmd, timeout): _logger.info('phantom_run executing %s', ' '.join(cmd)) + + ls_glob = os.path.expanduser('~/.qws/share/data/Ofi Labs/PhantomJS/http_localhost_%s.*'%PORT) + for i in glob.glob(ls_glob): + _logger.info('phantomjs unlink localstorage %s', i) + os.unlink(i) try: phantom = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) except OSError: @@ -265,7 +300,6 @@ class HttpCase(TransactionCase): # phantom.args[1] == options cmd = [ 'phantomjs', - '--local-storage-path', self.localstorage_path, jsfile, phantomtest, json.dumps(options) ] self.phantom_run(cmd, timeout) diff --git a/openerp/tests/phantomtest.js b/openerp/tests/phantomtest.js index 84f886c5dbb..49da52e07d9 100644 --- a/openerp/tests/phantomtest.js +++ b/openerp/tests/phantomtest.js @@ -116,6 +116,8 @@ function PhantomTest() { phantom.exit(1); } else { console.log('loaded', url, status); + // clear localstorage leftovers + self.page.evaluate(function () { localStorage.clear() }); // process ready waitFor(function() { console.log("PhantomTest.run: wait for condition:", ready);