[MERGE] complete migration of module base to the new API
This commit is contained in:
@@ -12,18 +12,17 @@ class TestServerActionsEmail(TestServerActionsBase):
|
||||
'name': 'TestTemplate',
|
||||
'email_from': 'myself@example.com',
|
||||
'email_to': 'brigitte@example.com',
|
||||
'partner_to': '%s' % self.test_partner_id,
|
||||
'model_id': self.res_partner_model_id,
|
||||
'partner_to': '%s' % self.test_partner.id,
|
||||
'model_id': self.res_partner_model.id,
|
||||
'subject': 'About ${object.name}',
|
||||
'body_html': '<p>Dear ${object.name}, your parent is ${object.parent_id and object.parent_id.name or "False"}</p>',
|
||||
})
|
||||
action = self.env['ir.actions.server'].browse(self.act_id)
|
||||
action.write({'state': 'email', 'template_id': email_template.id})
|
||||
run_res = action.with_context(self.context).run()
|
||||
self.action.write({'state': 'email', 'template_id': email_template.id})
|
||||
run_res = self.action.with_context(self.context).run()
|
||||
self.assertFalse(run_res, 'ir_actions_server: email server action correctly finished should return False')
|
||||
# check an email is waiting for sending
|
||||
mail = self.env['mail.mail'].search([('subject', '=', 'About TestingPartner')])
|
||||
self.assertEqual(len(mail.ids), 1, 'ir_actions_server: TODO')
|
||||
self.assertEqual(len(mail), 1, 'ir_actions_server: TODO')
|
||||
# check email content
|
||||
self.assertEqual(mail.body, '<p>Dear TestingPartner, your parent is False</p>',
|
||||
'ir_actions_server: TODO')
|
||||
|
||||
@@ -10,10 +10,12 @@ import tests
|
||||
|
||||
def post_init(cr, registry):
|
||||
"""Rewrite ICP's to force groups"""
|
||||
from openerp import SUPERUSER_ID
|
||||
from openerp import api, SUPERUSER_ID
|
||||
from openerp.addons.base.ir.ir_config_parameter import _default_parameters
|
||||
ICP = registry['ir.config_parameter']
|
||||
for k, func in _default_parameters.items():
|
||||
v = ICP.get_param(cr, SUPERUSER_ID, k)
|
||||
_, g = func()
|
||||
ICP.set_param(cr, SUPERUSER_ID, k, v, g)
|
||||
|
||||
env = api.Environment(cr, SUPERUSER_ID, {})
|
||||
ICP = env['ir.config_parameter']
|
||||
for key, func in _default_parameters.iteritems():
|
||||
val = ICP.get_param(key)
|
||||
_, groups = func()
|
||||
ICP.set_param(key, val, groups)
|
||||
|
||||
@@ -72,11 +72,7 @@ The kernel of Odoo, needed for all installation.
|
||||
'res/res_partner_demo.yml',
|
||||
'res/res_partner_image_demo.xml',
|
||||
],
|
||||
'test': [
|
||||
'tests/base_test.yml',
|
||||
'tests/test_osv_expression.yml',
|
||||
'tests/test_ir_rule.yml', # <-- These tests modify/add/delete ir_rules.
|
||||
],
|
||||
'test': [],
|
||||
'installable': True,
|
||||
'auto_install': True,
|
||||
'post_init_hook': 'post_init',
|
||||
|
||||
@@ -1,40 +1,31 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
from collections import defaultdict
|
||||
from operator import attrgetter
|
||||
import importlib
|
||||
import logging
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
import urllib2
|
||||
import urlparse
|
||||
import zipfile
|
||||
|
||||
from docutils import nodes
|
||||
from docutils.core import publish_string
|
||||
from docutils.transforms import Transform, writer_aux
|
||||
from docutils.writers.html4css1 import Writer
|
||||
import importlib
|
||||
import logging
|
||||
from operator import attrgetter
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import tempfile
|
||||
import urllib
|
||||
import urllib2
|
||||
import urlparse
|
||||
import zipfile
|
||||
import zipimport
|
||||
import lxml.html
|
||||
from openerp.exceptions import UserError
|
||||
|
||||
try:
|
||||
from cStringIO import StringIO
|
||||
except ImportError:
|
||||
from StringIO import StringIO # NOQA
|
||||
|
||||
import openerp
|
||||
import openerp.exceptions
|
||||
from openerp import modules, tools
|
||||
from openerp.modules.db import create_categories
|
||||
from openerp.modules import get_module_resource
|
||||
from openerp.tools import ormcache
|
||||
from openerp.tools.parse_version import parse_version
|
||||
from openerp.tools.translate import _
|
||||
from openerp.tools import html_sanitize
|
||||
from openerp.osv import osv, orm, fields
|
||||
from openerp import api, fields as fields2
|
||||
import odoo
|
||||
from odoo import api, fields, models, modules, tools, _
|
||||
from odoo.exceptions import AccessDenied, UserError
|
||||
from odoo.tools.parse_version import parse_version
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -61,42 +52,45 @@ def backup(path, raise_exception=True):
|
||||
cnt += 1
|
||||
|
||||
|
||||
class module_category(osv.osv):
|
||||
class ModuleCategory(models.Model):
|
||||
_name = "ir.module.category"
|
||||
_description = "Application"
|
||||
_order = 'name'
|
||||
|
||||
def _module_nbr(self, cr, uid, ids, prop, unknow_none, context):
|
||||
@api.depends('module_ids')
|
||||
def _compute_module_nr(self):
|
||||
cr = self._cr
|
||||
cr.execute('SELECT category_id, COUNT(*) \
|
||||
FROM ir_module_module \
|
||||
WHERE category_id IN %(ids)s \
|
||||
OR category_id IN (SELECT id \
|
||||
FROM ir_module_category \
|
||||
WHERE parent_id IN %(ids)s) \
|
||||
GROUP BY category_id', {'ids': tuple(ids)}
|
||||
GROUP BY category_id', {'ids': tuple(self.ids)}
|
||||
)
|
||||
result = dict(cr.fetchall())
|
||||
for id in ids:
|
||||
cr.execute('select id from ir_module_category where parent_id=%s', (id,))
|
||||
result[id] = sum([result.get(c, 0) for (c,) in cr.fetchall()],
|
||||
result.get(id, 0))
|
||||
return result
|
||||
for cat in self.filtered('id'):
|
||||
cr.execute('SELECT id FROM ir_module_category WHERE parent_id=%s', (cat.id,))
|
||||
cat.module_nr = sum([result.get(c, 0) for (c,) in cr.fetchall()], result.get(cat.id, 0))
|
||||
|
||||
_columns = {
|
||||
'name': fields.char("Name", required=True, translate=True, select=True),
|
||||
'parent_id': fields.many2one('ir.module.category', 'Parent Application', select=True),
|
||||
'child_ids': fields.one2many('ir.module.category', 'parent_id', 'Child Applications'),
|
||||
'module_nr': fields.function(_module_nbr, string='Number of Apps', type='integer'),
|
||||
'module_ids': fields.one2many('ir.module.module', 'category_id', 'Modules'),
|
||||
'description': fields.text("Description", translate=True),
|
||||
'sequence': fields.integer('Sequence'),
|
||||
'visible': fields.boolean('Visible'),
|
||||
'xml_id': fields.function(osv.osv.get_external_id, type='char', string="External ID"),
|
||||
}
|
||||
_order = 'name'
|
||||
name = fields.Char(string='Name', required=True, translate=True, index=True)
|
||||
parent_id = fields.Many2one('ir.module.category', string='Parent Application', index=True)
|
||||
child_ids = fields.One2many('ir.module.category', 'parent_id', string='Child Applications')
|
||||
module_nr = fields.Integer(string='Number of Apps', compute='_compute_module_nr')
|
||||
module_ids = fields.One2many('ir.module.module', 'category_id', string='Modules')
|
||||
description = fields.Text(string='Description', translate=True)
|
||||
sequence = fields.Integer(string='Sequence')
|
||||
visible = fields.Boolean(string='Visible', default=True)
|
||||
xml_id = fields.Char(string='External ID', compute='_compute_xml_id')
|
||||
|
||||
def _compute_xml_id(self):
|
||||
xml_ids = defaultdict(list)
|
||||
domain = [('model', '=', self._name), ('res_id', 'in', self.ids)]
|
||||
for data in self.env['ir.model.data'].search_read(domain, ['module', 'name', 'res_id']):
|
||||
xml_ids[data['res_id']].append("%s.%s" % (data['module'], data['name']))
|
||||
for cat in self:
|
||||
cat.xml_id = xml_ids.get(cat.id, [''])[0]
|
||||
|
||||
_defaults = {
|
||||
'visible': 1,
|
||||
}
|
||||
|
||||
class MyFilterMessages(Transform):
|
||||
"""
|
||||
@@ -107,7 +101,6 @@ class MyFilterMessages(Transform):
|
||||
the `settings_override` dictionary, but if we use it, we can't see them
|
||||
and generate warnings.)
|
||||
"""
|
||||
|
||||
default_priority = 870
|
||||
|
||||
def apply(self):
|
||||
@@ -115,43 +108,52 @@ class MyFilterMessages(Transform):
|
||||
_logger.warning("docutils' system message present: %s", str(node))
|
||||
node.parent.remove(node)
|
||||
|
||||
|
||||
class MyWriter(Writer):
|
||||
"""
|
||||
Custom docutils html4ccs1 writer that doesn't add the warnings to the
|
||||
output document.
|
||||
"""
|
||||
|
||||
def get_transforms(self):
|
||||
return [MyFilterMessages, writer_aux.Admonitions]
|
||||
|
||||
class module(osv.osv):
|
||||
|
||||
STATES = [
|
||||
('uninstallable', 'Uninstallable'),
|
||||
('uninstalled', 'Not Installed'),
|
||||
('installed', 'Installed'),
|
||||
('to upgrade', 'To be upgraded'),
|
||||
('to remove', 'To be removed'),
|
||||
('to install', 'To be installed'),
|
||||
]
|
||||
|
||||
class Module(models.Model):
|
||||
_name = "ir.module.module"
|
||||
_rec_name = "shortdesc"
|
||||
_description = "Module"
|
||||
_order = 'sequence,name'
|
||||
|
||||
def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
|
||||
res = super(module, self).fields_view_get(cr, uid, view_id=view_id, view_type=view_type, context=context, toolbar=toolbar, submenu=False)
|
||||
result = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'base', 'action_server_module_immediate_install')[1]
|
||||
if view_type == 'form':
|
||||
if res.get('toolbar',False):
|
||||
list = [rec for rec in res['toolbar']['action'] if rec.get('id', False) != result]
|
||||
res['toolbar'] = {'action': list}
|
||||
return res
|
||||
@api.model
|
||||
def fields_view_get(self, view_id=None, view_type='form', toolbar=False, submenu=False):
|
||||
res = super(Module, self).fields_view_get(view_id, view_type, toolbar=toolbar, submenu=False)
|
||||
if view_type == 'form' and res.get('toolbar',False):
|
||||
install_id = self.env.ref('base.action_server_module_immediate_install').id
|
||||
action = [rec for rec in res['toolbar']['action'] if rec.get('id', False) != install_id]
|
||||
res['toolbar'] = {'action': action}
|
||||
return res
|
||||
|
||||
@classmethod
|
||||
def get_module_info(cls, name):
|
||||
info = {}
|
||||
try:
|
||||
info = modules.load_information_from_description_file(name)
|
||||
return modules.load_information_from_description_file(name)
|
||||
except Exception:
|
||||
_logger.debug('Error when trying to fetch information for '
|
||||
'module %s', name, exc_info=True)
|
||||
return info
|
||||
_logger.debug('Error when trying to fetch information for module %s', name, exc_info=True)
|
||||
return {}
|
||||
|
||||
def _get_desc(self, cr, uid, ids, field_name=None, arg=None, context=None):
|
||||
res = dict.fromkeys(ids, '')
|
||||
for module in self.browse(cr, uid, ids, context=context):
|
||||
path = get_module_resource(module.name, 'static/description/index.html')
|
||||
@api.depends('name', 'description')
|
||||
def _get_desc(self):
|
||||
for module in self:
|
||||
path = modules.get_module_resource(module.name, 'static/description/index.html')
|
||||
if path:
|
||||
with tools.file_open(path, 'rb') as desc_file:
|
||||
doc = desc_file.read()
|
||||
@@ -159,7 +161,7 @@ class module(osv.osv):
|
||||
for element, attribute, link, pos in html.iterlinks():
|
||||
if element.get('src') and not '//' in element.get('src') and not 'static/' in element.get('src'):
|
||||
element.set('src', "/%s/static/description/%s" % (module.name, element.get('src')))
|
||||
res[module.id] = html_sanitize(lxml.html.tostring(html))
|
||||
module.description_html = tools.html_sanitize(lxml.html.tostring(html))
|
||||
else:
|
||||
overrides = {
|
||||
'embed_stylesheet': False,
|
||||
@@ -168,170 +170,115 @@ class module(osv.osv):
|
||||
'xml_declaration': False,
|
||||
}
|
||||
output = publish_string(source=module.description or '', settings_overrides=overrides, writer=MyWriter())
|
||||
res[module.id] = html_sanitize(output)
|
||||
return res
|
||||
module.description_html = tools.html_sanitize(output)
|
||||
|
||||
def _get_latest_version(self, cr, uid, ids, field_name=None, arg=None, context=None):
|
||||
@api.depends('name')
|
||||
def _get_latest_version(self):
|
||||
default_version = modules.adapt_version('1.0')
|
||||
res = dict.fromkeys(ids, default_version)
|
||||
for m in self.browse(cr, uid, ids):
|
||||
res[m.id] = self.get_module_info(m.name).get('version', default_version)
|
||||
return res
|
||||
for module in self:
|
||||
module.installed_version = self.get_module_info(module.name).get('version', default_version)
|
||||
|
||||
def _get_views(self, cr, uid, ids, field_name=None, arg=None, context=None):
|
||||
res = {}
|
||||
model_data_obj = self.pool.get('ir.model.data')
|
||||
|
||||
dmodels = []
|
||||
if field_name is None or 'views_by_module' in field_name:
|
||||
dmodels.append('ir.ui.view')
|
||||
if field_name is None or 'reports_by_module' in field_name:
|
||||
dmodels.append('ir.actions.report.xml')
|
||||
if field_name is None or 'menus_by_module' in field_name:
|
||||
dmodels.append('ir.ui.menu')
|
||||
assert dmodels, "no models for %s" % field_name
|
||||
|
||||
for module_rec in self.browse(cr, uid, ids, context=context):
|
||||
res_mod_dic = res[module_rec.id] = {
|
||||
'menus_by_module': [],
|
||||
'reports_by_module': [],
|
||||
'views_by_module': []
|
||||
}
|
||||
@api.depends('name', 'state')
|
||||
def _get_views(self):
|
||||
IrModelData = self.env['ir.model.data'].with_context(active_test=True)
|
||||
dmodels = ['ir.ui.view', 'ir.actions.report.xml', 'ir.ui.menu']
|
||||
|
||||
for module in self:
|
||||
# Skip uninstalled modules below, no data to find anyway.
|
||||
if module_rec.state not in ('installed', 'to upgrade', 'to remove'):
|
||||
if module.state not in ('installed', 'to upgrade', 'to remove'):
|
||||
module.views_by_module = ""
|
||||
module.reports_by_module = ""
|
||||
module.menus_by_module = ""
|
||||
continue
|
||||
|
||||
# then, search and group ir.model.data records
|
||||
imd_models = dict([(m, []) for m in dmodels])
|
||||
imd_ids = model_data_obj.search(cr, uid, [
|
||||
('module', '=', module_rec.name),
|
||||
('model', 'in', tuple(dmodels))
|
||||
])
|
||||
|
||||
for imd_res in model_data_obj.read(cr, uid, imd_ids, ['model', 'res_id'], context=context):
|
||||
imd_models[imd_res['model']].append(imd_res['res_id'])
|
||||
imd_models = defaultdict(list)
|
||||
imd_domain = [('module', '=', module.name), ('model', 'in', tuple(dmodels))]
|
||||
for data in IrModelData.search(imd_domain):
|
||||
imd_models[data.model].append(data.res_id)
|
||||
|
||||
def browse(model):
|
||||
M = self.pool[model]
|
||||
# as this method is called before the module update, some xmlid may be invalid at this stage
|
||||
# explictly filter records before reading them
|
||||
ids = M.exists(cr, uid, imd_models.get(model, []), context)
|
||||
return M.browse(cr, uid, ids, context)
|
||||
# as this method is called before the module update, some xmlid
|
||||
# may be invalid at this stage; explictly filter records before
|
||||
# reading them
|
||||
return self.env[model].browse(imd_models[model]).exists()
|
||||
|
||||
def format_view(v):
|
||||
aa = v.inherit_id and '* INHERIT ' or ''
|
||||
return '%s%s (%s)' % (aa, v.name, v.type)
|
||||
return '%s%s (%s)' % (v.inherit_id and '* INHERIT ' or '', v.name, v.type)
|
||||
|
||||
res_mod_dic['views_by_module'] = map(format_view, browse('ir.ui.view'))
|
||||
res_mod_dic['reports_by_module'] = map(attrgetter('name'), browse('ir.actions.report.xml'))
|
||||
res_mod_dic['menus_by_module'] = map(attrgetter('complete_name'), browse('ir.ui.menu'))
|
||||
module.views_by_module = "\n".join(sorted(map(format_view, browse('ir.ui.view'))))
|
||||
module.reports_by_module = "\n".join(sorted(map(attrgetter('name'), browse('ir.actions.report.xml'))))
|
||||
module.menus_by_module = "\n".join(sorted(map(attrgetter('complete_name'), browse('ir.ui.menu'))))
|
||||
|
||||
for key in res.iterkeys():
|
||||
for k, v in res[key].iteritems():
|
||||
res[key][k] = "\n".join(sorted(v))
|
||||
return res
|
||||
|
||||
def _get_icon_image(self, cr, uid, ids, field_name=None, arg=None, context=None):
|
||||
res = dict.fromkeys(ids, '')
|
||||
for module in self.browse(cr, uid, ids, context=context):
|
||||
path = get_module_resource(module.name, 'static', 'description', 'icon.png')
|
||||
@api.depends('name')
|
||||
def _get_icon_image(self):
|
||||
for module in self:
|
||||
module.icon_image = ''
|
||||
path = modules.get_module_resource(module.name, 'static', 'description', 'icon.png')
|
||||
if path:
|
||||
image_file = tools.file_open(path, 'rb')
|
||||
try:
|
||||
res[module.id] = image_file.read().encode('base64')
|
||||
finally:
|
||||
image_file.close()
|
||||
return res
|
||||
with tools.file_open(path, 'rb') as image_file:
|
||||
module.icon_image = image_file.read().encode('base64')
|
||||
|
||||
_columns = {
|
||||
'name': fields.char("Technical Name", readonly=True, required=True, select=True),
|
||||
'category_id': fields.many2one('ir.module.category', 'Category', readonly=True, select=True),
|
||||
'shortdesc': fields.char('Module Name', readonly=True, translate=True),
|
||||
'summary': fields.char('Summary', readonly=True, translate=True),
|
||||
'description': fields.text("Description", readonly=True, translate=True),
|
||||
'description_html': fields.function(_get_desc, string='Description HTML', type='html', method=True, readonly=True),
|
||||
'author': fields.char("Author", readonly=True),
|
||||
'maintainer': fields.char('Maintainer', readonly=True),
|
||||
'contributors': fields.text('Contributors', readonly=True),
|
||||
'website': fields.char("Website", readonly=True),
|
||||
name = fields.Char('Technical Name', readonly=True, required=True, index=True)
|
||||
category_id = fields.Many2one('ir.module.category', string='Category', readonly=True, index=True)
|
||||
shortdesc = fields.Char('Module Name', readonly=True, translate=True)
|
||||
summary = fields.Char('Summary', readonly=True, translate=True)
|
||||
description = fields.Text('Description', readonly=True, translate=True)
|
||||
description_html = fields.Html('Description HTML', compute='_get_desc')
|
||||
author = fields.Char("Author", readonly=True)
|
||||
maintainer = fields.Char('Maintainer', readonly=True)
|
||||
contributors = fields.Text('Contributors', readonly=True)
|
||||
website = fields.Char("Website", readonly=True)
|
||||
|
||||
# attention: Incorrect field names !!
|
||||
# installed_version refers the latest version (the one on disk)
|
||||
# latest_version refers the installed version (the one in database)
|
||||
# published_version refers the version available on the repository
|
||||
'installed_version': fields.function(_get_latest_version, string='Latest Version', type='char'),
|
||||
'latest_version': fields.char('Installed Version', readonly=True),
|
||||
'published_version': fields.char('Published Version', readonly=True),
|
||||
# attention: Incorrect field names !!
|
||||
# installed_version refers the latest version (the one on disk)
|
||||
# latest_version refers the installed version (the one in database)
|
||||
# published_version refers the version available on the repository
|
||||
installed_version = fields.Char('Latest Version', compute='_get_latest_version')
|
||||
latest_version = fields.Char('Installed Version', readonly=True)
|
||||
published_version = fields.Char('Published Version', readonly=True)
|
||||
|
||||
'url': fields.char('URL', readonly=True),
|
||||
'sequence': fields.integer('Sequence'),
|
||||
'dependencies_id': fields.one2many('ir.module.module.dependency', 'module_id', 'Dependencies', readonly=True),
|
||||
'auto_install': fields.boolean('Automatic Installation',
|
||||
help='An auto-installable module is automatically installed by the '
|
||||
'system when all its dependencies are satisfied. '
|
||||
'If the module has no dependency, it is always installed.'),
|
||||
'state': fields.selection([
|
||||
('uninstallable', 'Not Installable'),
|
||||
('uninstalled', 'Not Installed'),
|
||||
('installed', 'Installed'),
|
||||
('to upgrade', 'To be upgraded'),
|
||||
('to remove', 'To be removed'),
|
||||
('to install', 'To be installed')
|
||||
], string='Status', readonly=True, select=True),
|
||||
'demo': fields.boolean('Demo Data', readonly=True),
|
||||
'license': fields.selection([
|
||||
('GPL-2', 'GPL Version 2'),
|
||||
('GPL-2 or any later version', 'GPL-2 or later version'),
|
||||
('GPL-3', 'GPL Version 3'),
|
||||
('GPL-3 or any later version', 'GPL-3 or later version'),
|
||||
('AGPL-3', 'Affero GPL-3'),
|
||||
('LGPL-3', 'LGPL Version 3'),
|
||||
('Other OSI approved licence', 'Other OSI Approved Licence'),
|
||||
('OEEL-1', 'Odoo Enterprise Edition License v1.0'),
|
||||
('Other proprietary', 'Other Proprietary')
|
||||
], string='License', readonly=True),
|
||||
'menus_by_module': fields.function(_get_views, string='Menus', type='text', multi="meta", store=True),
|
||||
'reports_by_module': fields.function(_get_views, string='Reports', type='text', multi="meta", store=True),
|
||||
'views_by_module': fields.function(_get_views, string='Views', type='text', multi="meta", store=True),
|
||||
'application': fields.boolean('Application', readonly=True),
|
||||
'icon': fields.char('Icon URL'),
|
||||
'icon_image': fields.function(_get_icon_image, string='Icon', type="binary"),
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
'state': 'uninstalled',
|
||||
'sequence': 100,
|
||||
'demo': False,
|
||||
'license': 'LGPL-3',
|
||||
}
|
||||
_order = 'sequence,name'
|
||||
|
||||
def _name_uniq_msg(self, cr, uid, ids, context=None):
|
||||
return _('The name of the module must be unique !')
|
||||
url = fields.Char('URL', readonly=True)
|
||||
sequence = fields.Integer('Sequence', default=100)
|
||||
dependencies_id = fields.One2many('ir.module.module.dependency', 'module_id',
|
||||
string='Dependencies', readonly=True)
|
||||
auto_install = fields.Boolean('Automatic Installation',
|
||||
help='An auto-installable module is automatically installed by the '
|
||||
'system when all its dependencies are satisfied. '
|
||||
'If the module has no dependency, it is always installed.')
|
||||
state = fields.Selection(STATES, string='Status', default='uninstalled', readonly=True, index=True)
|
||||
demo = fields.Boolean('Demo Data', default=False, readonly=True)
|
||||
license = fields.Selection([
|
||||
('GPL-2', 'GPL Version 2'),
|
||||
('GPL-2 or any later version', 'GPL-2 or later version'),
|
||||
('GPL-3', 'GPL Version 3'),
|
||||
('GPL-3 or any later version', 'GPL-3 or later version'),
|
||||
('AGPL-3', 'Affero GPL-3'),
|
||||
('LGPL-3', 'LGPL Version 3'),
|
||||
('Other OSI approved licence', 'Other OSI Approved Licence'),
|
||||
('OEEL-1', 'Odoo Enterprise Edition License v1.0'),
|
||||
('Other proprietary', 'Other Proprietary')
|
||||
], string='License', default='LGPL-3', readonly=True)
|
||||
menus_by_module = fields.Text(string='Menus', compute='_get_views', store=True)
|
||||
reports_by_module = fields.Text(string='Reports', compute='_get_views', store=True)
|
||||
views_by_module = fields.Text(string='Views', compute='_get_views', store=True)
|
||||
application = fields.Boolean('Application', readonly=True)
|
||||
icon = fields.Char('Icon URL')
|
||||
icon_image = fields.Binary(string='Icon', compute='_get_icon_image')
|
||||
|
||||
_sql_constraints = [
|
||||
('name_uniq', 'UNIQUE (name)', _name_uniq_msg),
|
||||
('name_uniq', 'UNIQUE (name)', 'The name of the module must be unique!'),
|
||||
]
|
||||
|
||||
def unlink(self, cr, uid, ids, context=None):
|
||||
if not ids:
|
||||
@api.multi
|
||||
def unlink(self):
|
||||
if not self:
|
||||
return True
|
||||
if isinstance(ids, (int, long)):
|
||||
ids = [ids]
|
||||
mod_names = []
|
||||
for mod in self.read(cr, uid, ids, ['state', 'name'], context):
|
||||
if mod['state'] in ('installed', 'to upgrade', 'to remove', 'to install'):
|
||||
for module in self:
|
||||
if module.state in ('installed', 'to upgrade', 'to remove', 'to install'):
|
||||
raise UserError(_('You try to remove a module that is installed or will be installed'))
|
||||
mod_names.append(mod['name'])
|
||||
#Removing the entry from ir_model_data
|
||||
#ids_meta = self.pool.get('ir.model.data').search(cr, uid, [('name', '=', 'module_meta_information'), ('module', 'in', mod_names)])
|
||||
|
||||
#if ids_meta:
|
||||
# self.pool.get('ir.model.data').unlink(cr, uid, ids_meta, context)
|
||||
|
||||
self.clear_caches()
|
||||
return super(module, self).unlink(cr, uid, ids, context=context)
|
||||
return super(Module, self).unlink()
|
||||
|
||||
@staticmethod
|
||||
def _check_external_dependencies(terp):
|
||||
@@ -447,163 +394,166 @@ class module(osv.osv):
|
||||
) % ("\n".join(themes_list)))
|
||||
return dict(ACTION_DICT, name=_('Install'))
|
||||
|
||||
def button_immediate_install(self, cr, uid, ids, context=None):
|
||||
@api.multi
|
||||
def button_immediate_install(self):
|
||||
""" Installs the selected module(s) immediately and fully,
|
||||
returns the next res.config action to execute
|
||||
|
||||
:param ids: identifiers of the modules to install
|
||||
:returns: next res.config item to execute
|
||||
:rtype: dict[str, object]
|
||||
"""
|
||||
return self._button_immediate_function(cr, uid, ids, self.button_install, context=context)
|
||||
return self._button_immediate_function(type(self).button_install)
|
||||
|
||||
def button_install_cancel(self, cr, uid, ids, context=None):
|
||||
self.write(cr, uid, ids, {'state': 'uninstalled', 'demo': False})
|
||||
@api.multi
|
||||
def button_install_cancel(self):
|
||||
self.write({'state': 'uninstalled', 'demo': False})
|
||||
return True
|
||||
|
||||
def module_uninstall(self, cr, uid, ids, context=None):
|
||||
"""Perform the various steps required to uninstall a module completely
|
||||
@api.multi
|
||||
def module_uninstall(self):
|
||||
""" Perform the various steps required to uninstall a module completely
|
||||
including the deletion of all database structures created by the module:
|
||||
tables, columns, constraints, etc."""
|
||||
ir_model_data = self.pool.get('ir.model.data')
|
||||
modules_to_remove = [m.name for m in self.browse(cr, uid, ids, context)]
|
||||
ir_model_data._module_data_uninstall(cr, uid, modules_to_remove, context)
|
||||
self.write(cr, uid, ids, {'state': 'uninstalled', 'latest_version': False})
|
||||
tables, columns, constraints, etc.
|
||||
"""
|
||||
modules_to_remove = self.mapped('name')
|
||||
self.env['ir.model.data']._module_data_uninstall(modules_to_remove)
|
||||
self.write({'state': 'uninstalled', 'latest_version': False})
|
||||
return True
|
||||
|
||||
def downstream_dependencies(self, cr, uid, ids, known_dep_ids=None,
|
||||
exclude_states=['uninstalled', 'uninstallable', 'to remove'],
|
||||
context=None):
|
||||
"""Return the ids of all modules that directly or indirectly depend
|
||||
on the given module `ids`, and that satisfy the `exclude_states`
|
||||
filter"""
|
||||
if not ids:
|
||||
return []
|
||||
known_dep_ids = set(known_dep_ids or [])
|
||||
cr.execute('''SELECT DISTINCT m.id
|
||||
FROM
|
||||
ir_module_module_dependency d
|
||||
JOIN
|
||||
ir_module_module m ON (d.module_id=m.id)
|
||||
WHERE
|
||||
d.name IN (SELECT name from ir_module_module where id in %s) AND
|
||||
m.state NOT IN %s AND
|
||||
m.id NOT IN %s ''',
|
||||
(tuple(ids), tuple(exclude_states), tuple(known_dep_ids or ids)))
|
||||
new_dep_ids = set([m[0] for m in cr.fetchall()])
|
||||
missing_mod_ids = new_dep_ids - known_dep_ids
|
||||
known_dep_ids |= new_dep_ids
|
||||
if missing_mod_ids:
|
||||
known_dep_ids |= set(self.downstream_dependencies(cr, uid, list(missing_mod_ids),
|
||||
known_dep_ids, exclude_states, context))
|
||||
return list(known_dep_ids)
|
||||
@api.multi
|
||||
@api.returns('self')
|
||||
def downstream_dependencies(self, known_deps=None,
|
||||
exclude_states=('uninstalled', 'uninstallable', 'to remove')):
|
||||
""" Return the modules that directly or indirectly depend on the modules
|
||||
in `self`, and that satisfy the `exclude_states` filter.
|
||||
"""
|
||||
if not self:
|
||||
return self
|
||||
known_deps = known_deps or self.browse()
|
||||
query = """ SELECT DISTINCT m.id
|
||||
FROM ir_module_module_dependency d
|
||||
JOIN ir_module_module m ON (d.module_id=m.id)
|
||||
WHERE
|
||||
d.name IN (SELECT name from ir_module_module where id in %s) AND
|
||||
m.state NOT IN %s AND
|
||||
m.id NOT IN %s """
|
||||
self._cr.execute(query, (tuple(self.ids), tuple(exclude_states), tuple(known_deps.ids or self.ids)))
|
||||
new_deps = self.browse([row[0] for row in self._cr.fetchall()])
|
||||
missing_mods = new_deps - known_deps
|
||||
known_deps |= new_deps
|
||||
if missing_mods:
|
||||
known_deps |= missing_mods.downstream_dependencies(known_deps, exclude_states)
|
||||
return known_deps
|
||||
|
||||
def upstream_dependencies(self, cr, uid, ids, known_dep_ids=None,
|
||||
exclude_states=['installed', 'uninstallable', 'to remove'],
|
||||
context=None):
|
||||
""" Return the dependency tree of modules of the given `ids`, and that
|
||||
satisfy the `exclude_states` filter """
|
||||
if not ids:
|
||||
return []
|
||||
known_dep_ids = set(known_dep_ids or [])
|
||||
cr.execute('''SELECT DISTINCT m.id
|
||||
FROM
|
||||
ir_module_module_dependency d
|
||||
JOIN
|
||||
ir_module_module m ON (d.module_id=m.id)
|
||||
WHERE
|
||||
m.name IN (SELECT name from ir_module_module_dependency where module_id in %s) AND
|
||||
m.state NOT IN %s AND
|
||||
m.id NOT IN %s ''',
|
||||
(tuple(ids), tuple(exclude_states), tuple(known_dep_ids or ids)))
|
||||
new_dep_ids = set([m[0] for m in cr.fetchall()])
|
||||
missing_mod_ids = new_dep_ids - known_dep_ids
|
||||
known_dep_ids |= new_dep_ids
|
||||
if missing_mod_ids:
|
||||
known_dep_ids |= set(self.upstream_dependencies(cr, uid, list(missing_mod_ids),
|
||||
known_dep_ids, exclude_states, context))
|
||||
return list(known_dep_ids)
|
||||
@api.multi
|
||||
@api.returns('self')
|
||||
def upstream_dependencies(self, known_deps=None,
|
||||
exclude_states=('installed', 'uninstallable', 'to remove')):
|
||||
""" Return the dependency tree of modules of the modules in `self`, and
|
||||
that satisfy the `exclude_states` filter.
|
||||
"""
|
||||
if not self:
|
||||
return self
|
||||
known_deps = known_deps or self.browse()
|
||||
query = """ SELECT DISTINCT m.id
|
||||
FROM ir_module_module_dependency d
|
||||
JOIN ir_module_module m ON (d.module_id=m.id)
|
||||
WHERE
|
||||
m.name IN (SELECT name from ir_module_module_dependency where module_id in %s) AND
|
||||
m.state NOT IN %s AND
|
||||
m.id NOT IN %s """
|
||||
self._cr.execute(query, (tuple(self.ids), tuple(exclude_states), tuple(known_deps.ids or self.ids)))
|
||||
new_deps = self.browse([row[0] for row in self._cr.fetchall()])
|
||||
missing_mods = new_deps - known_deps
|
||||
known_deps |= new_deps
|
||||
if missing_mods:
|
||||
known_deps |= missing_mods.upstream_dependencies(known_deps, exclude_states)
|
||||
return known_deps
|
||||
|
||||
def _button_immediate_function(self, cr, uid, ids, function, context=None):
|
||||
function(cr, uid, ids, context=context)
|
||||
@api.multi
|
||||
def _button_immediate_function(self, function):
|
||||
function(self)
|
||||
|
||||
cr.commit()
|
||||
self._cr.commit()
|
||||
api.Environment.reset()
|
||||
registry = openerp.modules.registry.RegistryManager.new(cr.dbname, update_module=True)
|
||||
modules.registry.RegistryManager.new(self._cr.dbname, update_module=True)
|
||||
|
||||
cr.commit()
|
||||
config = registry['res.config'].next(cr, uid, [], context=context) or {}
|
||||
self._cr.commit()
|
||||
env = api.Environment(self._cr, self._uid, self._context)
|
||||
config = env['res.config'].next() or {}
|
||||
if config.get('type') not in ('ir.actions.act_window_close',):
|
||||
return config
|
||||
|
||||
# reload the client; open the first available root menu
|
||||
menu_obj = registry['ir.ui.menu']
|
||||
menu_ids = menu_obj.search(cr, uid, [('parent_id', '=', False)], context=context)
|
||||
menu = env['ir.ui.menu'].search([('parent_id', '=', False)])[:1]
|
||||
return {
|
||||
'type': 'ir.actions.client',
|
||||
'tag': 'reload',
|
||||
'params': {'menu_id': menu_ids and menu_ids[0] or False}
|
||||
'params': {'menu_id': menu.id},
|
||||
}
|
||||
|
||||
def button_immediate_uninstall(self, cr, uid, ids, context=None):
|
||||
@api.multi
|
||||
def button_immediate_uninstall(self):
|
||||
"""
|
||||
Uninstall the selected module(s) immediately and fully,
|
||||
returns the next res.config action to execute
|
||||
"""
|
||||
return self._button_immediate_function(cr, uid, ids, self.button_uninstall, context=context)
|
||||
return self._button_immediate_function(type(self).button_uninstall)
|
||||
|
||||
def button_uninstall(self, cr, uid, ids, context=None):
|
||||
if any(m.name == 'base' for m in self.browse(cr, uid, ids, context=context)):
|
||||
@api.multi
|
||||
def button_uninstall(self):
|
||||
if 'base' in self.mapped('name'):
|
||||
raise UserError(_("The `base` module cannot be uninstalled"))
|
||||
dep_ids = self.downstream_dependencies(cr, uid, ids, context=context)
|
||||
self.write(cr, uid, ids + dep_ids, {'state': 'to remove'})
|
||||
deps = self.downstream_dependencies()
|
||||
(self + deps).write({'state': 'to remove'})
|
||||
return dict(ACTION_DICT, name=_('Uninstall'))
|
||||
|
||||
def button_uninstall_cancel(self, cr, uid, ids, context=None):
|
||||
self.write(cr, uid, ids, {'state': 'installed'})
|
||||
@api.multi
|
||||
def button_uninstall_cancel(self):
|
||||
self.write({'state': 'installed'})
|
||||
return True
|
||||
|
||||
def button_immediate_upgrade(self, cr, uid, ids, context=None):
|
||||
@api.multi
|
||||
def button_immediate_upgrade(self):
|
||||
"""
|
||||
Upgrade the selected module(s) immediately and fully,
|
||||
return the next res.config action to execute
|
||||
"""
|
||||
return self._button_immediate_function(cr, uid, ids, self.button_upgrade, context=context)
|
||||
return self._button_immediate_function(type(self).button_upgrade)
|
||||
|
||||
def button_upgrade(self, cr, uid, ids, context=None):
|
||||
depobj = self.pool.get('ir.module.module.dependency')
|
||||
todo = list(self.browse(cr, uid, ids, context=context))
|
||||
self.update_list(cr, uid)
|
||||
@api.multi
|
||||
def button_upgrade(self):
|
||||
Dependency = self.env['ir.module.module.dependency']
|
||||
self.update_list()
|
||||
|
||||
todo = list(self)
|
||||
i = 0
|
||||
while i < len(todo):
|
||||
mod = todo[i]
|
||||
module = todo[i]
|
||||
i += 1
|
||||
if mod.state not in ('installed', 'to upgrade'):
|
||||
raise UserError(_("Can not upgrade module '%s'. It is not installed.") % (mod.name,))
|
||||
self.check_external_dependencies(mod.name, 'to upgrade')
|
||||
iids = depobj.search(cr, uid, [('name', '=', mod.name)], context=context)
|
||||
for dep in depobj.browse(cr, uid, iids, context=context):
|
||||
if module.state not in ('installed', 'to upgrade'):
|
||||
raise UserError(_("Can not upgrade module '%s'. It is not installed.") % (module.name,))
|
||||
self.check_external_dependencies(module.name, 'to upgrade')
|
||||
for dep in Dependency.search([('name', '=', module.name)]):
|
||||
if dep.module_id.state == 'installed' and dep.module_id not in todo:
|
||||
todo.append(dep.module_id)
|
||||
|
||||
ids = map(lambda x: x.id, todo)
|
||||
self.write(cr, uid, ids, {'state': 'to upgrade'}, context=context)
|
||||
self.browse(module.id for module in todo).write({'state': 'to upgrade'})
|
||||
|
||||
to_install = []
|
||||
for mod in todo:
|
||||
for dep in mod.dependencies_id:
|
||||
for module in todo:
|
||||
for dep in module.dependencies_id:
|
||||
if dep.state == 'unknown':
|
||||
raise UserError(_('You try to upgrade a module that depends on the module: %s.\nBut this module is not available in your system.') % (dep.name,))
|
||||
if dep.state == 'uninstalled':
|
||||
ids2 = self.search(cr, uid, [('name', '=', dep.name)])
|
||||
to_install.extend(ids2)
|
||||
to_install += self.search([('name', '=', dep.name)]).ids
|
||||
|
||||
self.button_install(cr, uid, to_install, context=context)
|
||||
self.browse(to_install).button_install()
|
||||
return dict(ACTION_DICT, name=_('Apply Schedule Upgrade'))
|
||||
|
||||
def button_upgrade_cancel(self, cr, uid, ids, context=None):
|
||||
self.write(cr, uid, ids, {'state': 'installed'})
|
||||
@api.multi
|
||||
def button_upgrade_cancel(self):
|
||||
self.write({'state': 'installed'})
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
@@ -624,25 +574,27 @@ class module(osv.osv):
|
||||
'url': terp.get('url') or terp.get('live_test_url', ''),
|
||||
}
|
||||
|
||||
def create(self, cr, uid, vals, context=None):
|
||||
new_id = super(module, self).create(cr, uid, vals, context=context)
|
||||
@api.model
|
||||
def create(self, vals):
|
||||
new = super(Module, self).create(vals)
|
||||
module_metadata = {
|
||||
'name': 'module_%s' % vals['name'],
|
||||
'model': 'ir.module.module',
|
||||
'module': 'base',
|
||||
'res_id': new_id,
|
||||
'res_id': new.id,
|
||||
'noupdate': True,
|
||||
}
|
||||
self.pool['ir.model.data'].create(cr, uid, module_metadata)
|
||||
return new_id
|
||||
self.env['ir.model.data'].create(module_metadata)
|
||||
return new
|
||||
|
||||
# update the list of available packages
|
||||
def update_list(self, cr, uid, context=None):
|
||||
@api.model
|
||||
def update_list(self):
|
||||
res = [0, 0] # [update, add]
|
||||
|
||||
default_version = modules.adapt_version('1.0')
|
||||
known_mods = self.browse(cr, uid, self.search(cr, uid, []))
|
||||
known_mods_names = dict([(m.name, m) for m in known_mods])
|
||||
known_mods = self.search([])
|
||||
known_mods_names = {mod.name: mod for mod in known_mods}
|
||||
|
||||
# iterate through detected modules and update/create them in db
|
||||
for mod_name in modules.get_modules():
|
||||
@@ -654,7 +606,7 @@ class module(osv.osv):
|
||||
updated_values = {}
|
||||
for key in values:
|
||||
old = getattr(mod, key)
|
||||
updated = isinstance(values[key], basestring) and tools.ustr(values[key]) or values[key]
|
||||
updated = tools.ustr(values[key]) if isinstance(values[key], basestring) else values[key]
|
||||
if (old or updated) and updated != old:
|
||||
updated_values[key] = values[key]
|
||||
if terp.get('installable', True) and mod.state == 'uninstallable':
|
||||
@@ -662,43 +614,44 @@ class module(osv.osv):
|
||||
if parse_version(terp.get('version', default_version)) > parse_version(mod.latest_version or default_version):
|
||||
res[0] += 1
|
||||
if updated_values:
|
||||
self.write(cr, uid, mod.id, updated_values)
|
||||
mod.write(updated_values)
|
||||
else:
|
||||
mod_path = modules.get_module_path(mod_name)
|
||||
if not mod_path:
|
||||
continue
|
||||
if not terp or not terp.get('installable', True):
|
||||
continue
|
||||
id = self.create(cr, uid, dict(name=mod_name, state='uninstalled', **values))
|
||||
mod = self.browse(cr, uid, id)
|
||||
mod = self.create(dict(name=mod_name, state='uninstalled', **values))
|
||||
res[1] += 1
|
||||
|
||||
self._update_dependencies(cr, uid, mod, terp.get('depends', []))
|
||||
self._update_category(cr, uid, mod, terp.get('category', 'Uncategorized'))
|
||||
mod._update_dependencies(terp.get('depends', []))
|
||||
mod._update_category(terp.get('category', 'Uncategorized'))
|
||||
|
||||
return res
|
||||
|
||||
def download(self, cr, uid, ids, download=True, context=None):
|
||||
@api.multi
|
||||
def download(self, download=True):
|
||||
return []
|
||||
|
||||
def install_from_urls(self, cr, uid, urls, context=None):
|
||||
if not self.pool['res.users'].has_group(cr, uid, 'base.group_system'):
|
||||
raise openerp.exceptions.AccessDenied()
|
||||
@api.model
|
||||
def install_from_urls(self, urls):
|
||||
if not self.env.user.has_group('base.group_system'):
|
||||
raise AccessDenied()
|
||||
|
||||
apps_server = urlparse.urlparse(self.get_apps_server(cr, uid, context=context))
|
||||
apps_server = urlparse.urlparse(self.get_apps_server())
|
||||
|
||||
OPENERP = openerp.release.product_name.lower()
|
||||
OPENERP = odoo.release.product_name.lower()
|
||||
tmp = tempfile.mkdtemp()
|
||||
_logger.debug('Install from url: %r', urls)
|
||||
try:
|
||||
# 1. Download & unzip missing modules
|
||||
for module_name, url in urls.items():
|
||||
for module_name, url in urls.iteritems():
|
||||
if not url:
|
||||
continue # nothing to download, local version is already the last one
|
||||
|
||||
up = urlparse.urlparse(url)
|
||||
if up.scheme != apps_server.scheme or up.netloc != apps_server.netloc:
|
||||
raise openerp.exceptions.AccessDenied()
|
||||
raise AccessDenied()
|
||||
|
||||
try:
|
||||
_logger.info('Downloading module `%s` from OpenERP Apps', module_name)
|
||||
@@ -711,7 +664,7 @@ class module(osv.osv):
|
||||
assert os.path.isdir(os.path.join(tmp, module_name))
|
||||
|
||||
# 2a. Copy/Replace module source in addons path
|
||||
for module_name, url in urls.items():
|
||||
for module_name, url in urls.iteritems():
|
||||
if module_name == OPENERP or not url:
|
||||
continue # OPENERP is special case, handled below, and no URL means local module
|
||||
module_path = modules.get_module_path(module_name, downloaded=True, display_warning=False)
|
||||
@@ -722,7 +675,7 @@ class module(osv.osv):
|
||||
shutil.rmtree(bck)
|
||||
|
||||
# 2b. Copy/Replace server+base module source if downloaded
|
||||
if urls.get(OPENERP, None):
|
||||
if urls.get(OPENERP):
|
||||
# special case. it contains the server and the base module.
|
||||
# extract path is not the same
|
||||
base_path = os.path.dirname(modules.get_module_path('base'))
|
||||
@@ -734,51 +687,51 @@ class module(osv.osv):
|
||||
shutil.copytree(os.path.join(base_path, d), destdir)
|
||||
|
||||
# then replace the server by the new "base" module
|
||||
server_dir = openerp.tools.config['root_path'] # XXX or dirname()
|
||||
server_dir = tools.config['root_path'] # XXX or dirname()
|
||||
bck = backup(server_dir)
|
||||
_logger.info('Copy downloaded module `openerp` to `%s`', server_dir)
|
||||
shutil.move(os.path.join(tmp, OPENERP), server_dir)
|
||||
#if bck:
|
||||
# shutil.rmtree(bck)
|
||||
|
||||
self.update_list(cr, uid, context=context)
|
||||
self.update_list()
|
||||
|
||||
with_urls = [m for m, u in urls.items() if u]
|
||||
downloaded_ids = self.search(cr, uid, [('name', 'in', with_urls)], context=context)
|
||||
already_installed = self.search(cr, uid, [('id', 'in', downloaded_ids), ('state', '=', 'installed')], context=context)
|
||||
with_urls = [module_name for module_name, url in urls.iteritems() if url]
|
||||
downloaded = self.search([('name', 'in', with_urls)])
|
||||
installed = self.search([('id', 'in', downloaded.ids), ('state', '=', 'installed')])
|
||||
|
||||
to_install_ids = self.search(cr, uid, [('name', 'in', urls.keys()), ('state', '=', 'uninstalled')], context=context)
|
||||
post_install_action = self.button_immediate_install(cr, uid, to_install_ids, context=context)
|
||||
to_install = self.search([('name', 'in', urls.keys()), ('state', '=', 'uninstalled')])
|
||||
post_install_action = to_install.button_immediate_install()
|
||||
|
||||
if already_installed:
|
||||
if installed:
|
||||
# in this case, force server restart to reload python code...
|
||||
cr.commit()
|
||||
openerp.service.server.restart()
|
||||
self._cr.commit()
|
||||
odoo.service.server.restart()
|
||||
return {
|
||||
'type': 'ir.actions.client',
|
||||
'tag': 'home',
|
||||
'params': {'wait': True},
|
||||
}
|
||||
return post_install_action
|
||||
|
||||
finally:
|
||||
shutil.rmtree(tmp)
|
||||
|
||||
def get_apps_server(self, cr, uid, context=None):
|
||||
@api.model
|
||||
def get_apps_server(self):
|
||||
return tools.config.get('apps_server', 'https://apps.openerp.com/apps')
|
||||
|
||||
def _update_dependencies(self, cr, uid, mod_browse, depends=None):
|
||||
if depends is None:
|
||||
depends = []
|
||||
existing = set(x.name for x in mod_browse.dependencies_id)
|
||||
needed = set(depends)
|
||||
def _update_dependencies(self, depends=None):
|
||||
existing = set(dep.name for dep in self.dependencies_id)
|
||||
needed = set(depends or [])
|
||||
for dep in (needed - existing):
|
||||
cr.execute('INSERT INTO ir_module_module_dependency (module_id, name) values (%s, %s)', (mod_browse.id, dep))
|
||||
self._cr.execute('INSERT INTO ir_module_module_dependency (module_id, name) values (%s, %s)', (self.id, dep))
|
||||
for dep in (existing - needed):
|
||||
cr.execute('DELETE FROM ir_module_module_dependency WHERE module_id = %s and name = %s', (mod_browse.id, dep))
|
||||
self.invalidate_cache(cr, uid, ['dependencies_id'], [mod_browse.id])
|
||||
self._cr.execute('DELETE FROM ir_module_module_dependency WHERE module_id = %s and name = %s', (self.id, dep))
|
||||
self.invalidate_cache(['dependencies_id'], self.ids)
|
||||
|
||||
def _update_category(self, cr, uid, mod_browse, category='Uncategorized'):
|
||||
current_category = mod_browse.category_id
|
||||
def _update_category(self, category='Uncategorized'):
|
||||
current_category = self.category_id
|
||||
current_category_path = []
|
||||
while current_category:
|
||||
current_category_path.insert(0, current_category.name)
|
||||
@@ -786,26 +739,27 @@ class module(osv.osv):
|
||||
|
||||
categs = category.split('/')
|
||||
if categs != current_category_path:
|
||||
cat_id = create_categories(cr, categs)
|
||||
mod_browse.write({'category_id': cat_id})
|
||||
cat_id = modules.db.create_categories(self._cr, categs)
|
||||
self.write({'category_id': cat_id})
|
||||
|
||||
def update_translations(self, cr, uid, ids, filter_lang=None, context=None):
|
||||
@api.multi
|
||||
def update_translations(self, filter_lang=None):
|
||||
if not filter_lang:
|
||||
res_lang = self.pool.get('res.lang')
|
||||
lang_ids = res_lang.search(cr, uid, [('translatable', '=', True)])
|
||||
filter_lang = [lang.code for lang in res_lang.browse(cr, uid, lang_ids)]
|
||||
langs = self.env['res.lang'].search([('translatable', '=', True)])
|
||||
filter_lang = [lang.code for lang in langs]
|
||||
elif not isinstance(filter_lang, (list, tuple)):
|
||||
filter_lang = [filter_lang]
|
||||
modules = [m.name for m in self.browse(cr, uid, ids) if m.state in ('installed', 'to install', 'to upgrade')]
|
||||
self.pool.get('ir.translation').load_module_terms(cr, modules, filter_lang, context=context)
|
||||
mod_names = [mod.name for mod in self if mod.state in ('installed', 'to install', 'to upgrade')]
|
||||
self.env['ir.translation'].load_module_terms(mod_names, filter_lang)
|
||||
|
||||
def check(self, cr, uid, ids, context=None):
|
||||
for mod in self.browse(cr, uid, ids, context=context):
|
||||
if not mod.description:
|
||||
_logger.warning('module %s: description is empty !', mod.name)
|
||||
@api.multi
|
||||
def check(self):
|
||||
for module in self:
|
||||
if not module.description:
|
||||
_logger.warning('module %s: description is empty !', module.name)
|
||||
|
||||
@api.model
|
||||
@ormcache()
|
||||
@tools.ormcache()
|
||||
def _installed(self):
|
||||
""" Return the set of installed modules as a dictionary {name: id} """
|
||||
return {
|
||||
@@ -814,29 +768,21 @@ class module(osv.osv):
|
||||
}
|
||||
|
||||
|
||||
DEP_STATES = [
|
||||
('uninstallable', 'Uninstallable'),
|
||||
('uninstalled', 'Not Installed'),
|
||||
('installed', 'Installed'),
|
||||
('to upgrade', 'To be upgraded'),
|
||||
('to remove', 'To be removed'),
|
||||
('to install', 'To be installed'),
|
||||
('unknown', 'Unknown'),
|
||||
]
|
||||
DEP_STATES = STATES + [('unknown', 'Unknown')]
|
||||
|
||||
class module_dependency(osv.Model):
|
||||
class ModuleDependency(models.Model):
|
||||
_name = "ir.module.module.dependency"
|
||||
_description = "Module dependency"
|
||||
|
||||
# the dependency name
|
||||
name = fields2.Char(index=True)
|
||||
name = fields.Char(index=True)
|
||||
|
||||
# the module that depends on it
|
||||
module_id = fields2.Many2one('ir.module.module', 'Module', ondelete='cascade')
|
||||
module_id = fields.Many2one('ir.module.module', 'Module', ondelete='cascade')
|
||||
|
||||
# the module corresponding to the dependency, and its status
|
||||
depend_id = fields2.Many2one('ir.module.module', 'Dependency', compute='_compute_depend')
|
||||
state = fields2.Selection(DEP_STATES, string='Status', compute='_compute_state')
|
||||
depend_id = fields.Many2one('ir.module.module', 'Dependency', compute='_compute_depend')
|
||||
state = fields.Selection(DEP_STATES, string='Status', compute='_compute_state')
|
||||
|
||||
@api.multi
|
||||
@api.depends('name')
|
||||
|
||||
@@ -1,42 +1,39 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from openerp import models, api
|
||||
from odoo import api, models
|
||||
|
||||
class IrModelReferenceReport(models.AbstractModel):
|
||||
_name = 'report.base.report_irmodulereference'
|
||||
|
||||
@api.model
|
||||
def _object_find(self, module):
|
||||
model_data_obj = self.env['ir.model.data']
|
||||
models_data = model_data_obj.search([('model','=','ir.model'), ('module','=',module.name)])
|
||||
res_ids = [model_data.res_id for model_data in models_data]
|
||||
Data = self.env['ir.model.data']
|
||||
data = Data.search([('model','=','ir.model'), ('module','=',module.name)])
|
||||
res_ids = data.mapped('res_id')
|
||||
return self.env['ir.model'].browse(res_ids)
|
||||
|
||||
@api.multi
|
||||
def _fields_find(self, model, module):
|
||||
res = []
|
||||
model_obj = self.env[model]
|
||||
model_fields = self.env['ir.model.fields']
|
||||
fname_wildcard = 'field_' + model_obj._name.replace('.', '_') + '_%'
|
||||
module_fields_ids = self.env['ir.model.data'].search([('model', '=', 'ir.model.fields'), ('module', '=', module.name), ('name', 'like', fname_wildcard)])
|
||||
module_fields_res_ids = [x.res_id for x in module_fields_ids]
|
||||
if module_fields_res_ids:
|
||||
module_fields_names = [x.name for x in model_fields.browse(module_fields_res_ids)]
|
||||
res = model_obj.fields_get(allfields=module_fields_names).items()
|
||||
res.sort()
|
||||
return res
|
||||
Data = self.env['ir.model.data']
|
||||
fname_wildcard = 'field_' + model.replace('.', '_') + '_%'
|
||||
data = Data.search([('model', '=', 'ir.model.fields'), ('module', '=', module.name), ('name', 'like', fname_wildcard)])
|
||||
if data:
|
||||
res_ids = data.mapped('res_ids')
|
||||
fnames = self.env['ir.model.fields'].browse(res_ids).mapped('name')
|
||||
return sorted(self.env[model].fields_get(fnames).iteritems())
|
||||
return []
|
||||
|
||||
@api.multi
|
||||
def render_html(self, data=None):
|
||||
report_obj = self.env['report']
|
||||
module_report = report_obj._get_report_from_name('base.report_irmodulereference')
|
||||
Report = self.env['report']
|
||||
report = Report._get_report_from_name('base.report_irmodulereference')
|
||||
selected_modules = self.env['ir.module.module'].browse(self.ids)
|
||||
docargs = {
|
||||
'doc_ids': self.ids,
|
||||
'doc_model': module_report.model,
|
||||
'doc_model': report.model,
|
||||
'docs': selected_modules,
|
||||
'findobj': self._object_find,
|
||||
'findfields': self._fields_find,
|
||||
}
|
||||
return report_obj.render('base.report_irmodulereference', docargs)
|
||||
return Report.render('base.report_irmodulereference', docargs)
|
||||
|
||||
@@ -5,58 +5,49 @@ import base64
|
||||
import contextlib
|
||||
import cStringIO
|
||||
|
||||
from openerp import tools
|
||||
from openerp.osv import fields,osv
|
||||
from openerp.tools.translate import _
|
||||
from openerp.tools.misc import get_iso_codes
|
||||
from odoo import api, fields, models, tools, _
|
||||
|
||||
NEW_LANG_KEY = '__new__'
|
||||
|
||||
class base_language_export(osv.osv_memory):
|
||||
class BaseLanguageExport(models.TransientModel):
|
||||
_name = "base.language.export"
|
||||
|
||||
def _get_languages(self, cr, uid, context):
|
||||
lang_obj = self.pool.get('res.lang')
|
||||
ids = lang_obj.search(cr, uid, [('translatable', '=', True)])
|
||||
langs = lang_obj.browse(cr, uid, ids)
|
||||
return [(NEW_LANG_KEY, _('New Language (Empty translation template)'))] + [(lang.code, lang.name) for lang in langs]
|
||||
@api.model
|
||||
def _get_languages(self):
|
||||
langs = self.env['res.lang'].search([('translatable', '=', True)])
|
||||
return [(NEW_LANG_KEY, _('New Language (Empty translation template)'))] + \
|
||||
[(lang.code, lang.name) for lang in langs]
|
||||
|
||||
_columns = {
|
||||
'name': fields.char('File Name', readonly=True),
|
||||
'lang': fields.selection(_get_languages, 'Language', required=True),
|
||||
'format': fields.selection([('csv','CSV File'),
|
||||
('po','PO File'),
|
||||
('tgz', 'TGZ Archive')], 'File Format', required=True),
|
||||
'modules': fields.many2many('ir.module.module', 'rel_modules_langexport', 'wiz_id', 'module_id', 'Apps To Export', domain=[('state','=','installed')]),
|
||||
'data': fields.binary('File', readonly=True),
|
||||
'state': fields.selection([('choose', 'choose'), # choose language
|
||||
('get', 'get')]) # get the file
|
||||
}
|
||||
_defaults = {
|
||||
'state': 'choose',
|
||||
'lang': NEW_LANG_KEY,
|
||||
'format': 'csv',
|
||||
}
|
||||
name = fields.Char('File Name', readonly=True)
|
||||
lang = fields.Selection(_get_languages, string='Language', required=True, default=NEW_LANG_KEY)
|
||||
format = fields.Selection([('csv','CSV File'), ('po','PO File'), ('tgz', 'TGZ Archive')],
|
||||
string='File Format', required=True, default='csv')
|
||||
modules = fields.Many2many('ir.module.module', 'rel_modules_langexport', 'wiz_id', 'module_id',
|
||||
string='Apps To Export', domain=[('state','=','installed')])
|
||||
data = fields.Binary('File', readonly=True)
|
||||
state = fields.Selection([('choose', 'choose'), ('get', 'get')], # choose language or get the file
|
||||
default='choose')
|
||||
|
||||
def act_getfile(self, cr, uid, ids, context=None):
|
||||
this = self.browse(cr, uid, ids, context=context)[0]
|
||||
@api.multi
|
||||
def act_getfile(self):
|
||||
this = self[0]
|
||||
lang = this.lang if this.lang != NEW_LANG_KEY else False
|
||||
mods = sorted(map(lambda m: m.name, this.modules)) or ['all']
|
||||
mods = sorted(this.mapped('modules.name')) or ['all']
|
||||
|
||||
with contextlib.closing(cStringIO.StringIO()) as buf:
|
||||
tools.trans_export(lang, mods, buf, this.format, cr)
|
||||
tools.trans_export(lang, mods, buf, this.format, self._cr)
|
||||
out = base64.encodestring(buf.getvalue())
|
||||
|
||||
filename = 'new'
|
||||
if lang:
|
||||
filename = get_iso_codes(lang)
|
||||
filename = tools.get_iso_codes(lang)
|
||||
elif len(mods) == 1:
|
||||
filename = mods[0]
|
||||
extension = this.format
|
||||
if not lang and extension == 'po':
|
||||
extension = 'pot'
|
||||
name = "%s.%s" % (filename, extension)
|
||||
this.write({ 'state': 'get', 'data': out, 'name': name })
|
||||
this.write({'state': 'get', 'data': out, 'name': name})
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'res_model': 'base.language.export',
|
||||
|
||||
@@ -1,52 +1,45 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import os
|
||||
import base64
|
||||
import logging
|
||||
import os
|
||||
from tempfile import TemporaryFile
|
||||
|
||||
from openerp import tools
|
||||
from openerp.osv import osv, fields
|
||||
from openerp.exceptions import UserError
|
||||
from openerp.tools.translate import _
|
||||
from odoo import api, fields, models, tools, _
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class base_language_import(osv.osv_memory):
|
||||
""" Language Import """
|
||||
|
||||
class BaseLanguageImport(models.TransientModel):
|
||||
_name = "base.language.import"
|
||||
_description = "Language Import"
|
||||
_columns = {
|
||||
'name': fields.char('Language Name', required=True),
|
||||
'code': fields.char('ISO Code', size=5, help="ISO Language and Country code, e.g. en_US", required=True),
|
||||
'data': fields.binary('File', required=True),
|
||||
'filename': fields.char('File Name', required=True),
|
||||
'overwrite': fields.boolean('Overwrite Existing Terms',
|
||||
help="If you enable this option, existing translations (including custom ones) "
|
||||
"will be overwritten and replaced by those in this file"),
|
||||
}
|
||||
|
||||
def import_lang(self, cr, uid, ids, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
this = self.browse(cr, uid, ids[0])
|
||||
if this.overwrite:
|
||||
context = dict(context, overwrite=True)
|
||||
fileobj = TemporaryFile('w+')
|
||||
try:
|
||||
fileobj.write(base64.decodestring(this.data))
|
||||
|
||||
# now we determine the file format
|
||||
fileobj.seek(0)
|
||||
fileformat = os.path.splitext(this.filename)[-1][1:].lower()
|
||||
|
||||
tools.trans_load_data(cr, fileobj, fileformat, this.code, lang_name=this.name, context=context)
|
||||
except Exception, e:
|
||||
_logger.exception('File unsuccessfully imported, due to format mismatch.')
|
||||
raise UserError(_('File not imported due to format mismatch or a malformed file. (Valid formats are .csv, .po, .pot)\n\nTechnical Details:\n%s') % tools.ustr(e))
|
||||
finally:
|
||||
fileobj.close()
|
||||
name = fields.Char('Language Name', required=True)
|
||||
code = fields.Char('ISO Code', size=5, required=True,
|
||||
help="ISO Language and Country code, e.g. en_US")
|
||||
data = fields.Binary('File', required=True)
|
||||
filename = fields.Char('File Name', required=True)
|
||||
overwrite = fields.Boolean('Overwrite Existing Terms',
|
||||
help="If you enable this option, existing translations (including custom ones) "
|
||||
"will be overwritten and replaced by those in this file")
|
||||
|
||||
@api.multi
|
||||
def import_lang(self):
|
||||
this = self[0]
|
||||
this = this.with_context(overwrite=this.overwrite)
|
||||
with TemporaryFile('w+') as buf:
|
||||
try:
|
||||
buf.write(base64.decodestring(this.data))
|
||||
|
||||
# now we determine the file format
|
||||
buf.seek(0)
|
||||
fileformat = os.path.splitext(this.filename)[-1][1:].lower()
|
||||
|
||||
tools.trans_load_data(this._cr, buf, fileformat, this.code,
|
||||
lang_name=this.name, context=this._context)
|
||||
except Exception as e:
|
||||
_logger.exception('File unsuccessfully imported, due to format mismatch.')
|
||||
raise UserError(_('File not imported due to format mismatch or a malformed file. (Valid formats are .csv, .po, .pot)\n\nTechnical Details:\n%s') % tools.ustr(e))
|
||||
return True
|
||||
|
||||
@@ -1,43 +1,30 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from operator import itemgetter
|
||||
from odoo import api, fields, models, _
|
||||
|
||||
from openerp import tools
|
||||
from openerp.osv import osv, fields
|
||||
from openerp.tools.translate import _
|
||||
|
||||
class base_language_install(osv.osv_memory):
|
||||
""" Install Language"""
|
||||
class BaseLanguageInstall(models.TransientModel):
|
||||
_name = "base.language.install"
|
||||
_description = "Install Language"
|
||||
|
||||
def _languages(self, cr, uid, context=None):
|
||||
return self.pool['res.lang'].get_available(cr, uid, context=context)
|
||||
@api.model
|
||||
def _get_languages(self):
|
||||
return self.env['res.lang'].get_available()
|
||||
|
||||
_columns = {
|
||||
'lang': fields.selection(_languages, 'Language', required=True),
|
||||
'overwrite': fields.boolean('Overwrite Existing Terms', help="If you check this box, your customized translations will be overwritten and replaced by the official ones."),
|
||||
'state':fields.selection([('init','init'),('done','done')], 'Status', readonly=True),
|
||||
}
|
||||
lang = fields.Selection(_get_languages, string='Language', required=True)
|
||||
overwrite = fields.Boolean('Overwrite Existing Terms',
|
||||
help="If you check this box, your customized translations will be overwritten and replaced by the official ones.")
|
||||
state = fields.Selection([('init', 'init'), ('done', 'done')],
|
||||
string='Status', readonly=True, default='init')
|
||||
|
||||
_defaults = {
|
||||
'state': 'init',
|
||||
'overwrite': False
|
||||
}
|
||||
|
||||
def lang_install(self, cr, uid, ids, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
language_obj = self.browse(cr, uid, ids)[0]
|
||||
lang = language_obj.lang
|
||||
if lang:
|
||||
modobj = self.pool.get('ir.module.module')
|
||||
mids = modobj.search(cr, uid, [('state', '=', 'installed')])
|
||||
if language_obj.overwrite:
|
||||
context = {'overwrite': True}
|
||||
modobj.update_translations(cr, uid, mids, lang, context or {})
|
||||
self.write(cr, uid, ids, {'state': 'done'}, context=context)
|
||||
@api.multi
|
||||
def lang_install(self):
|
||||
this = self[0]
|
||||
this = this.with_context(overwrite=this.overwrite)
|
||||
mods = self.env['ir.module.module'].search([('state', '=', 'installed')])
|
||||
mods.update_translations(this.lang)
|
||||
this.state = 'done'
|
||||
return {
|
||||
'name': _('Language Pack'),
|
||||
'view_type': 'form',
|
||||
@@ -45,8 +32,8 @@ class base_language_install(osv.osv_memory):
|
||||
'view_id': False,
|
||||
'res_model': 'base.language.install',
|
||||
'domain': [],
|
||||
'context': dict(context, active_ids=ids),
|
||||
'context': dict(self._context, active_ids=self.ids),
|
||||
'type': 'ir.actions.act_window',
|
||||
'target': 'new',
|
||||
'res_id': ids and ids[0] or False,
|
||||
'res_id': this.id,
|
||||
}
|
||||
|
||||
@@ -1,33 +1,27 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from openerp.osv import osv
|
||||
from openerp.tools.translate import _
|
||||
from odoo import api, models, _
|
||||
|
||||
class base_module_configuration(osv.osv_memory):
|
||||
|
||||
class BaseModuleConfiguration(models.TransientModel):
|
||||
_name = "base.module.configuration"
|
||||
|
||||
def start(self, cr, uid, ids, context=None):
|
||||
todo_ids = self.pool.get('ir.actions.todo').search(cr, uid,
|
||||
['|', ('type','=','recurring'), ('state', '=', 'open')])
|
||||
if not todo_ids:
|
||||
@api.multi
|
||||
def start(self):
|
||||
todos_domain = ['|', ('type','=','recurring'), ('state', '=', 'open')]
|
||||
if self.env['ir.actions.todo'].search_count(todos_domain):
|
||||
# Run the config wizards
|
||||
return self.env['res.config'].start()
|
||||
else:
|
||||
# When there is no wizard todo it will display message
|
||||
data_obj = self.pool.get('ir.model.data')
|
||||
result = data_obj._get_id(cr, uid, 'base', 'view_base_module_configuration_form')
|
||||
view_id = data_obj.browse(cr, uid, result).res_id
|
||||
value = {
|
||||
'name': _('System Configuration done'),
|
||||
'view_type': 'form',
|
||||
'view_mode': 'form',
|
||||
'res_model': 'base.module.configuration',
|
||||
'view_id': [view_id],
|
||||
'type': 'ir.actions.act_window',
|
||||
'target': 'new'
|
||||
}
|
||||
return value
|
||||
# Run the config wizards
|
||||
config_pool = self.pool.get('res.config')
|
||||
return config_pool.start(cr, uid, ids, context=context)
|
||||
|
||||
base_module_configuration()
|
||||
view = self.env.ref('base.view_base_module_configuration_form')
|
||||
return {
|
||||
'name': _('System Configuration done'),
|
||||
'view_type': 'form',
|
||||
'view_mode': 'form',
|
||||
'res_model': 'base.mdule.configuration',
|
||||
'view_id': [view.id],
|
||||
'type': 'ir.actions.act_window',
|
||||
'target': 'new',
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from openerp import models, fields, api
|
||||
from odoo import api, fields, models
|
||||
|
||||
class base_module_update(models.TransientModel):
|
||||
|
||||
class BaseModuleUpdate(models.TransientModel):
|
||||
_name = "base.module.update"
|
||||
_description = "Update Module"
|
||||
|
||||
@@ -10,10 +11,11 @@ class base_module_update(models.TransientModel):
|
||||
added = fields.Integer('Number of modules added', readonly=True)
|
||||
state = fields.Selection([('init', 'init'), ('done', 'done')], 'Status', readonly=True, default='init')
|
||||
|
||||
@api.one
|
||||
@api.multi
|
||||
def update_module(self):
|
||||
self.updated, self.added = self.env['ir.module.module'].update_list()
|
||||
self.state = 'done'
|
||||
for this in self:
|
||||
updated, added = self.env['ir.module.module'].update_list()
|
||||
this.write({'updated': updated, 'added': added, 'state': 'done'})
|
||||
return False
|
||||
|
||||
@api.multi
|
||||
|
||||
@@ -1,34 +1,37 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import openerp
|
||||
from openerp.osv import osv, fields
|
||||
from openerp.tools.translate import _
|
||||
from openerp.exceptions import UserError
|
||||
import odoo
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
class base_module_upgrade(osv.osv_memory):
|
||||
""" Module Upgrade """
|
||||
|
||||
class BaseModuleUpgrade(models.TransientModel):
|
||||
_name = "base.module.upgrade"
|
||||
_description = "Module Upgrade"
|
||||
|
||||
_columns = {
|
||||
'module_info': fields.text('Apps to Update',readonly=True),
|
||||
}
|
||||
@api.model
|
||||
@api.returns('ir.module.module')
|
||||
def get_module_list(self):
|
||||
states = ['to upgrade', 'to remove', 'to install']
|
||||
return self.env['ir.module.module'].search([('state', 'in', states)])
|
||||
|
||||
def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
|
||||
res = super(base_module_upgrade, self).fields_view_get(cr, uid, view_id=view_id, view_type=view_type, context=context, toolbar=toolbar,submenu=False)
|
||||
@api.model
|
||||
def _default_module_info(self):
|
||||
return "\n".join("%s: %s" % (mod.name, mod.state) for mod in self.get_module_list())
|
||||
|
||||
module_info = fields.Text('Apps to Update', readonly=True, default=_default_module_info)
|
||||
|
||||
@api.model
|
||||
def fields_view_get(self, view_id=None, view_type='form', toolbar=False, submenu=False):
|
||||
res = super(BaseModuleUpgrade, self).fields_view_get(view_id, view_type, toolbar=toolbar,submenu=False)
|
||||
if view_type != 'form':
|
||||
return res
|
||||
|
||||
context = {} if context is None else context
|
||||
record_id = context and context.get('active_id', False) or False
|
||||
active_model = context.get('active_model')
|
||||
if (not record_id) or (not active_model):
|
||||
if not(self._context.get('active_model') and self._context.get('active_id')):
|
||||
return res
|
||||
|
||||
ids = self.get_module_list(cr, uid, context=context)
|
||||
if not ids:
|
||||
if not self.get_module_list():
|
||||
res['arch'] = '''<form string="Upgrade Completed" version="7.0">
|
||||
<separator string="Upgrade Completed" colspan="4"/>
|
||||
<footer>
|
||||
@@ -39,55 +42,41 @@ class base_module_upgrade(osv.osv_memory):
|
||||
|
||||
return res
|
||||
|
||||
def get_module_list(self, cr, uid, context=None):
|
||||
mod_obj = self.pool.get('ir.module.module')
|
||||
ids = mod_obj.search(cr, uid, [
|
||||
('state', 'in', ['to upgrade', 'to remove', 'to install'])])
|
||||
return ids
|
||||
|
||||
def default_get(self, cr, uid, fields, context=None):
|
||||
mod_obj = self.pool.get('ir.module.module')
|
||||
ids = self.get_module_list(cr, uid, context=context)
|
||||
res = mod_obj.read(cr, uid, ids, ['name','state'], context)
|
||||
return {'module_info': '\n'.join(map(lambda x: x['name']+' : '+x['state'], res))}
|
||||
|
||||
def upgrade_module_cancel(self, cr, uid, ids, context=None):
|
||||
mod_obj = self.pool.get('ir.module.module')
|
||||
to_installed_ids = mod_obj.search(cr, uid, [
|
||||
('state', 'in', ['to upgrade', 'to remove'])])
|
||||
if to_installed_ids:
|
||||
mod_obj.write(cr, uid, to_installed_ids, {'state': 'installed'}, context=context)
|
||||
|
||||
to_uninstalled_ids = mod_obj.search(cr, uid, [
|
||||
('state', '=', 'to install')])
|
||||
if to_uninstalled_ids:
|
||||
mod_obj.write(cr, uid, to_uninstalled_ids, {'state': 'uninstalled'}, context=context)
|
||||
|
||||
@api.multi
|
||||
def upgrade_module_cancel(self):
|
||||
Module = self.env['ir.module.module']
|
||||
to_install = Module.search([('state', 'in', ['to upgrade', 'to remove'])])
|
||||
to_install.write({'state': 'installed'})
|
||||
to_uninstall = Module.search([('state', '=', 'to install')])
|
||||
to_uninstall.write({'state': 'uninstalled'})
|
||||
return {'type': 'ir.actions.act_window_close'}
|
||||
|
||||
def upgrade_module(self, cr, uid, ids, context=None):
|
||||
ir_module = self.pool.get('ir.module.module')
|
||||
@api.multi
|
||||
def upgrade_module(self):
|
||||
Module = self.env['ir.module.module']
|
||||
|
||||
# install/upgrade: double-check preconditions
|
||||
ids = ir_module.search(cr, uid, [('state', 'in', ['to upgrade', 'to install'])])
|
||||
if ids:
|
||||
cr.execute("""SELECT d.name FROM ir_module_module m
|
||||
JOIN ir_module_module_dependency d ON (m.id = d.module_id)
|
||||
LEFT JOIN ir_module_module m2 ON (d.name = m2.name)
|
||||
WHERE m.id in %s and (m2.state IS NULL or m2.state IN %s)""",
|
||||
(tuple(ids), ('uninstalled',)))
|
||||
unmet_packages = [x[0] for x in cr.fetchall()]
|
||||
mods = Module.search([('state', 'in', ['to upgrade', 'to install'])])
|
||||
if mods:
|
||||
query = """ SELECT d.name
|
||||
FROM ir_module_module m
|
||||
JOIN ir_module_module_dependency d ON (m.id = d.module_id)
|
||||
LEFT JOIN ir_module_module m2 ON (d.name = m2.name)
|
||||
WHERE m.id in %s and (m2.state IS NULL or m2.state IN %s) """
|
||||
self._cr.execute(query, (tuple(mods.ids), ('uninstalled',)))
|
||||
unmet_packages = [row[0] for row in self._cr.fetchall()]
|
||||
if unmet_packages:
|
||||
raise UserError(_('Following modules are not installed or unknown: %s') % ('\n\n' + '\n'.join(unmet_packages)))
|
||||
raise UserError(_('The following modules are not installed or unknown: %s') % ('\n\n' + '\n'.join(unmet_packages)))
|
||||
|
||||
ir_module.download(cr, uid, ids, context=context)
|
||||
mods.download()
|
||||
|
||||
# terminate transaction before re-creating cursor below
|
||||
cr.commit()
|
||||
openerp.api.Environment.reset()
|
||||
openerp.modules.registry.RegistryManager.new(cr.dbname, update_module=True)
|
||||
self._cr.commit()
|
||||
api.Environment.reset()
|
||||
odoo.modules.registry.RegistryManager.new(self._cr.dbname, update_module=True)
|
||||
|
||||
return {'type': 'ir.actions.act_window_close'}
|
||||
|
||||
def config(self, cr, uid, ids, context=None):
|
||||
return self.pool.get('res.config').next(cr, uid, [], context=context)
|
||||
@api.multi
|
||||
def config(self):
|
||||
return self.env['res.config'].next()
|
||||
|
||||
@@ -1,52 +1,43 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import contextlib
|
||||
import cStringIO
|
||||
|
||||
from openerp import tools
|
||||
from openerp.osv import osv, fields
|
||||
from openerp.tools.translate import _
|
||||
from openerp.exceptions import UserError
|
||||
from odoo import api, fields, models, tools, _
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
class base_update_translations(osv.osv_memory):
|
||||
def _get_languages(self, cr, uid, context):
|
||||
lang_obj = self.pool.get('res.lang')
|
||||
ids = lang_obj.search(cr, uid, ['&', ('active', '=', True), ('translatable', '=', True),])
|
||||
langs = lang_obj.browse(cr, uid, ids)
|
||||
|
||||
class BaseUpdateTranslations(models.TransientModel):
|
||||
_name = 'base.update.translations'
|
||||
|
||||
@api.model
|
||||
def _get_languages(self):
|
||||
langs = self.env['res.lang'].search([('active', '=', True), ('translatable', '=', True)])
|
||||
return [(lang.code, lang.name) for lang in langs]
|
||||
|
||||
def _get_lang_name(self, cr, uid, lang_code):
|
||||
lang_obj = self.pool.get('res.lang')
|
||||
ids = lang_obj.search(cr, uid, [('code', '=', lang_code)])
|
||||
if not ids:
|
||||
@api.model
|
||||
def _default_language(self):
|
||||
if self._context.get('active_model') == 'res.lang':
|
||||
lang = self.env['res.lang'].browse(self._context.get('active_id'))
|
||||
return lang.code
|
||||
return False
|
||||
|
||||
lang = fields.Selection(_get_languages, 'Language', required=True,
|
||||
default=_default_language)
|
||||
|
||||
@api.model
|
||||
def _get_lang_name(self, lang_code):
|
||||
lang = self.env['res.lang'].search([('code', '=', lang_code)], limit=1)
|
||||
if not lang:
|
||||
raise UserError(_('No language with code "%s" exists') % lang_code)
|
||||
lang = lang_obj.browse(cr, uid, ids[0])
|
||||
return lang.name
|
||||
|
||||
def act_update(self, cr, uid, ids, context=None):
|
||||
this = self.browse(cr, uid, ids)[0]
|
||||
lang_name = self._get_lang_name(cr, uid, this.lang)
|
||||
buf = cStringIO.StringIO()
|
||||
tools.trans_export(this.lang, ['all'], buf, 'csv', cr)
|
||||
tools.trans_load_data(cr, buf, 'csv', this.lang, lang_name=lang_name)
|
||||
buf.close()
|
||||
@api.multi
|
||||
def act_update(self):
|
||||
this = self[0]
|
||||
lang_name = self._get_lang_name(this.lang)
|
||||
with contextlib.closing(cStringIO.StringIO()) as buf:
|
||||
tools.trans_export(this.lang, ['all'], buf, 'csv', self._cr)
|
||||
tools.trans_load_data(self._cr, buf, 'csv', this.lang, lang_name=lang_name)
|
||||
return {'type': 'ir.actions.act_window_close'}
|
||||
|
||||
def default_get(self, cr, uid, fields, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
res = super(base_update_translations, self).default_get(cr, uid, fields, context=context)
|
||||
|
||||
if context.get('active_model') != "res.lang":
|
||||
return res
|
||||
|
||||
record_id = context.get('active_id', False) or False
|
||||
if record_id:
|
||||
lang = self.pool.get('res.lang').browse(cr, uid, record_id).code
|
||||
res.update(lang=lang)
|
||||
return res
|
||||
|
||||
_name = 'base.update.translations'
|
||||
_columns = {
|
||||
'lang': fields.selection(_get_languages, 'Language', required=True),
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import test_base
|
||||
import test_basecase
|
||||
import test_db_cursor
|
||||
import test_expression
|
||||
import test_float
|
||||
import test_func
|
||||
import test_ir_actions
|
||||
import test_ir_attachment
|
||||
@@ -16,6 +17,7 @@ import test_menu
|
||||
import test_orm
|
||||
import test_ormcache
|
||||
import test_osv
|
||||
import test_phase
|
||||
import test_qweb
|
||||
import test_res_config
|
||||
import test_res_lang
|
||||
|
||||
@@ -1,337 +0,0 @@
|
||||
- |
|
||||
To check that common dangerous operations are not allowed by the safe_eval mechanism, attempt to
|
||||
evaluate unauthorized expressions, and verify that they trigger an error.
|
||||
-
|
||||
1. Try a few common expressions to verify they work with safe_eval
|
||||
-
|
||||
!python {model: ir.model}: |
|
||||
from openerp.tools.safe_eval import safe_eval
|
||||
expected = (1, {"a": 9 * 2}, (True, False, None))
|
||||
actual = safe_eval('(1, {"a": 9 * 2}, (True, False, None))')
|
||||
assert actual == expected, "Simple python expressions are not working with safe_eval"
|
||||
-
|
||||
2. Try simple literal definition to verify it works with literal_eval
|
||||
-
|
||||
!python {model: ir.model}: |
|
||||
import ast
|
||||
expected = (1, {"a": 9}, (True, False, None))
|
||||
actual = ast.literal_eval('(1, {"a": 9}, (True, False, None))')
|
||||
assert actual == expected, "Simple python expressions are not working with literal_eval"
|
||||
-
|
||||
3. Try arithmetic expression in literal_eval to verify it does not work
|
||||
-
|
||||
!python {model: ir.model}: |
|
||||
import ast
|
||||
try:
|
||||
ast.literal_eval('(1, {"a": 2*9}, (True, False, None))')
|
||||
assert False, "literal_eval should not accept arithmetic expressions"
|
||||
except ValueError:
|
||||
pass
|
||||
-
|
||||
4. Try forbidden expressions in literal_eval to verify they are not allowed
|
||||
-
|
||||
!python {model: ir.model}: |
|
||||
import ast
|
||||
try:
|
||||
ast.literal_eval('{"a": True.__class__}')
|
||||
assert False, "literal_eval should accept only literals"
|
||||
except ValueError:
|
||||
pass
|
||||
-
|
||||
5. Try forbidden expressions in safe_eval to verify they are not allowed (open)
|
||||
-
|
||||
!python {model: ir.model}: |
|
||||
from openerp.tools.safe_eval import safe_eval
|
||||
from openerp.tools.misc import mute_logger
|
||||
try:
|
||||
with mute_logger('openerp.tools.safe_eval'):
|
||||
safe_eval('open("/etc/passwd","r")')
|
||||
assert False, "safe_eval should not allow calling open() builtin"
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
-
|
||||
"ORM test: verify that parent_store computation are going right"
|
||||
-
|
||||
0. Emulate normal behavior of tree structure storing
|
||||
-
|
||||
!python {model: res.partner.category}: |
|
||||
# pretend the pool has finished loading to avoid deferring parent_store computation
|
||||
self.pool._init = False
|
||||
|
||||
# Force partner_categ.copy() to copy children
|
||||
self._fields['child_ids'].copy = True
|
||||
self._columns['child_ids'].copy = True
|
||||
-
|
||||
"1.0 Setup test partner categories: parent root"
|
||||
-
|
||||
!record {model: res.partner.category, id: test_categ_root}:
|
||||
name: Root category
|
||||
-
|
||||
"1.1 Setup test partner categories: parent category"
|
||||
-
|
||||
!record {model: res.partner.category, id: test_categ_0}:
|
||||
name: Parent category
|
||||
parent_id: test_categ_root
|
||||
-
|
||||
"1.2 Setup test partner categories: child 1"
|
||||
-
|
||||
!record {model: res.partner.category, id: test_categ_1}:
|
||||
name: Child 1
|
||||
parent_id: test_categ_0
|
||||
-
|
||||
"1.3 Setup test partner categories: child 2"
|
||||
-
|
||||
!record {model: res.partner.category, id: test_categ_2}:
|
||||
name: Child 2
|
||||
parent_id: test_categ_0
|
||||
-
|
||||
"1.4 Setup test partner categories: child 2-1"
|
||||
-
|
||||
!record {model: res.partner.category, id: test_categ_21}:
|
||||
name: Child 2-1
|
||||
parent_id: test_categ_2
|
||||
-
|
||||
2. Duplicate the parent category and verify that the children have been duplicated too and are below the new parent
|
||||
-
|
||||
!python {model: res.partner.category}: |
|
||||
self._columns['child_ids'].copy = True # force copying children for test
|
||||
new_id = self.copy(cr, uid, ref('test_categ_0'))
|
||||
new_struct = self.search(cr, uid, [('parent_id', 'child_of', new_id)])
|
||||
assert len(new_struct) == 4, "After duplication, the new object must have the childs records"
|
||||
old_struct = self.search(cr, uid, [('parent_id', 'child_of', ref('test_categ_0'))])
|
||||
assert len(old_struct) == 4, "After duplication, previous record must have old childs records only"
|
||||
assert (not set(new_struct).intersection(old_struct)), "After duplication, nodes should not be mixed"
|
||||
-
|
||||
3. Duplicate the children then reassign them to the new parent (1st method) and check the parent_store structure.
|
||||
-
|
||||
!python {model: res.partner.category}: |
|
||||
new_child1_id = self.copy(cr, uid, ref('test_categ_1'))
|
||||
new_child2_id = self.copy(cr, uid, ref('test_categ_2'))
|
||||
new_id = self.copy(cr, uid, ref('test_categ_0'), {'child_ids': []})
|
||||
self.write(cr, uid, [new_child1_id, new_child2_id], {'parent_id': new_id})
|
||||
new_struct = self.search(cr, uid, [('parent_id', 'child_of', new_id)])
|
||||
assert len(new_struct) == 4, "After duplication, the new object must have the childs records"
|
||||
old_struct = self.search(cr, uid, [('parent_id', 'child_of', ref('test_categ_0'))])
|
||||
assert len(old_struct) == 4, "After duplication, previous record must have old childs records only"
|
||||
assert (not set(new_struct).intersection(old_struct)), "After duplication, nodes should not be mixed"
|
||||
-
|
||||
4. Duplicate the children then reassign them to the new parent (2nd method) and check the parent_store structure.
|
||||
-
|
||||
!python {model: res.partner.category}: |
|
||||
new_child1_id = self.copy(cr, uid, ref('test_categ_1'))
|
||||
new_child2_id = self.copy(cr, uid, ref('test_categ_2'))
|
||||
old_struct = self.search(cr, uid, [('parent_id', 'child_of', ref('test_categ_0'))])
|
||||
new_id = self.copy(cr, uid, ref('test_categ_0'), {'child_ids': [(6,0,[new_child1_id, new_child2_id])]})
|
||||
new_struct = self.search(cr, uid, [('parent_id', 'child_of', new_id)])
|
||||
assert len(new_struct) == 4, "After duplication, the new object must have the childs records"
|
||||
old_struct = self.search(cr, uid, [('parent_id', 'child_of', ref('test_categ_0'))])
|
||||
assert len(old_struct) == 4, "After duplication, previous record must have old childs records only"
|
||||
assert (not set(new_struct).intersection(old_struct)), "After duplication, nodes should not be mixed"
|
||||
-
|
||||
5. Duplicate the children then reassign them to the new parent (3rd method) and make sure the parent_store structure is still right.
|
||||
-
|
||||
!python {model: res.partner.category}: |
|
||||
new_child1_id = self.copy(cr, uid, ref('test_categ_1'))
|
||||
new_child2_id = self.copy(cr, uid, ref('test_categ_2'))
|
||||
new_id = self.copy(cr, uid, ref('test_categ_0'), {'child_ids': []})
|
||||
self.write(cr, uid, [new_id], {'child_ids': [(4,new_child1_id), (4,new_child2_id)]})
|
||||
new_struct = self.search(cr, uid, [('parent_id', 'child_of', new_id)])
|
||||
assert len(new_struct) == 4, "After duplication, the new object must have the childs records"
|
||||
old_struct = self.search(cr, uid, [('parent_id', 'child_of', ref('test_categ_0'))])
|
||||
assert len(old_struct) == 4, "After duplication, previous record must have old childs records only"
|
||||
assert (not set(new_struct).intersection(old_struct)), "After duplication, nodes should not be mixed"
|
||||
-
|
||||
6. Restore pool state after the test
|
||||
-
|
||||
!python {model: res.partner.category}: |
|
||||
self.pool._init = True
|
||||
self._fields['child_ids'].copy = False
|
||||
self._columns['child_ids'].copy = False
|
||||
|
||||
-
|
||||
"Float precision tests: verify that float rounding methods are working correctly via res.currency"
|
||||
-
|
||||
!python {model: res.currency}: |
|
||||
from openerp.tools import float_repr
|
||||
from math import log10
|
||||
currency = self.browse(cr, uid, ref('base.EUR'))
|
||||
def try_round(amount, expected, self=self, cr=cr, currency=currency, float_repr=float_repr,
|
||||
log10=log10):
|
||||
digits = max(0,-int(log10(currency.rounding)))
|
||||
result = float_repr(self.round(cr, 1, currency, amount), precision_digits=digits)
|
||||
assert result == expected, 'Rounding error: got %s, expected %s' % (result, expected)
|
||||
try_round(2.674,'2.67')
|
||||
try_round(2.675,'2.68') # in Python 2.7.2, round(2.675,2) gives 2.67
|
||||
try_round(-2.675,'-2.68') # in Python 2.7.2, round(2.675,2) gives 2.67
|
||||
try_round(0.001,'0.00')
|
||||
try_round(-0.001,'-0.00')
|
||||
try_round(0.0049,'0.00') # 0.0049 is closer to 0 than to 0.01, so should round down
|
||||
try_round(0.005,'0.01') # the rule is to round half away from zero
|
||||
try_round(-0.005,'-0.01') # the rule is to round half away from zero
|
||||
|
||||
def try_zero(amount, expected, self=self, cr=cr, currency=currency):
|
||||
assert self.is_zero(cr, 1, currency, amount) == expected, "Rounding error: %s should be zero!" % amount
|
||||
try_zero(0.01, False)
|
||||
try_zero(-0.01, False)
|
||||
try_zero(0.001, True)
|
||||
try_zero(-0.001, True)
|
||||
try_zero(0.0046, True)
|
||||
try_zero(-0.0046, True)
|
||||
try_zero(2.68-2.675, False) # 2.68 - 2.675 = 0.005 -> rounds to 0.01
|
||||
try_zero(2.68-2.676, True) # 2.68 - 2.675 = 0.004 -> rounds to 0.0
|
||||
try_zero(2.676-2.68, True) # 2.675 - 2.68 = -0.004 -> rounds to -0.0
|
||||
try_zero(2.675-2.68, False) # 2.675 - 2.68 = -0.005 -> rounds to -0.01
|
||||
|
||||
def try_compare(amount1, amount2, expected, self=self, cr=cr, currency=currency):
|
||||
assert self.compare_amounts(cr, 1, currency, amount1, amount2) == expected, \
|
||||
"Rounding error, compare_amounts(%s,%s) should be %s" % (amount1, amount2, expected)
|
||||
try_compare(0.001, 0.001, 0)
|
||||
try_compare(-0.001, -0.001, 0)
|
||||
try_compare(0.001, 0.002, 0)
|
||||
try_compare(-0.001, -0.002, 0)
|
||||
try_compare(2.675, 2.68, 0)
|
||||
try_compare(2.676, 2.68, 0)
|
||||
try_compare(-2.676, -2.68, 0)
|
||||
try_compare(2.674, 2.68, -1)
|
||||
try_compare(-2.674, -2.68, 1)
|
||||
try_compare(3, 2.68, 1)
|
||||
try_compare(-3, -2.68, -1)
|
||||
try_compare(0.01, 0, 1)
|
||||
try_compare(-0.01, 0, -1)
|
||||
|
||||
-
|
||||
"Float precision tests: verify that float rounding methods are working correctly via tools"
|
||||
-
|
||||
!python {model: res.currency}: |
|
||||
from openerp.tools import float_compare, float_is_zero, float_round, float_repr
|
||||
def try_round(amount, expected, precision_digits=3, float_round=float_round, float_repr=float_repr, rounding_method='HALF-UP'):
|
||||
result = float_repr(float_round(amount, precision_digits=precision_digits, rounding_method=rounding_method),
|
||||
precision_digits=precision_digits)
|
||||
assert result == expected, 'Rounding error: got %s, expected %s' % (result, expected)
|
||||
try_round(2.6745, '2.675')
|
||||
try_round(-2.6745, '-2.675')
|
||||
try_round(2.6744, '2.674')
|
||||
try_round(-2.6744, '-2.674')
|
||||
try_round(0.0004, '0.000')
|
||||
try_round(-0.0004, '-0.000')
|
||||
try_round(357.4555, '357.456')
|
||||
try_round(-357.4555, '-357.456')
|
||||
try_round(457.4554, '457.455')
|
||||
try_round(-457.4554, '-457.455')
|
||||
|
||||
# Try some rounding value with rounding method UP instead of HALF-UP
|
||||
# We use 8.175 because when normalizing 8.175 with precision_digits=3 it gives
|
||||
# us 8175,0000000001234 as value, and if not handle correctly the rounding UP
|
||||
# value will be incorrect (should be 8,175 and not 8,176)
|
||||
try_round(8.175, '8.175', rounding_method='UP')
|
||||
try_round(8.1751, '8.176', rounding_method='UP')
|
||||
try_round(-8.175, '-8.175', rounding_method='UP')
|
||||
try_round(-8.1751, '-8.176', rounding_method='UP')
|
||||
try_round(-6.000, '-6.000', rounding_method='UP')
|
||||
try_round(1.8, '2', 0, rounding_method='UP')
|
||||
try_round(-1.8, '-2', 0, rounding_method='UP')
|
||||
|
||||
# Extended float range test, inspired by Cloves Almeida's test on bug #882036.
|
||||
fractions = [.0, .015, .01499, .675, .67499, .4555, .4555, .45555]
|
||||
expecteds = ['.00', '.02', '.01', '.68', '.67', '.46', '.456', '.4556']
|
||||
precisions = [2, 2, 2, 2, 2, 2, 3, 4]
|
||||
# Note: max precision for double floats is 53 bits of precision or
|
||||
# 17 significant decimal digits
|
||||
for magnitude in range(7):
|
||||
for i in xrange(len(fractions)):
|
||||
frac, exp, prec = fractions[i], expecteds[i], precisions[i]
|
||||
for sign in [-1,1]:
|
||||
for x in xrange(0,10000,97):
|
||||
n = x * 10**magnitude
|
||||
f = sign * (n + frac)
|
||||
f_exp = ('-' if f != 0 and sign == -1 else '') + str(n) + exp
|
||||
try_round(f, f_exp, precision_digits=prec)
|
||||
|
||||
|
||||
def try_zero(amount, expected, float_is_zero=float_is_zero):
|
||||
assert float_is_zero(amount, precision_digits=3) == expected, "Rounding error: %s should be zero!" % amount
|
||||
try_zero(0.0002, True)
|
||||
try_zero(-0.0002, True)
|
||||
try_zero(0.00034, True)
|
||||
try_zero(0.0005, False)
|
||||
try_zero(-0.0005, False)
|
||||
try_zero(0.0008, False)
|
||||
try_zero(-0.0008, False)
|
||||
|
||||
def try_compare(amount1, amount2, expected, float_compare=float_compare):
|
||||
assert float_compare(amount1, amount2, precision_digits=3) == expected, \
|
||||
"Rounding error, compare_amounts(%s,%s) should be %s" % (amount1, amount2, expected)
|
||||
try_compare(0.0003, 0.0004, 0)
|
||||
try_compare(-0.0003, -0.0004, 0)
|
||||
try_compare(0.0002, 0.0005, -1)
|
||||
try_compare(-0.0002, -0.0005, 1)
|
||||
try_compare(0.0009, 0.0004, 1)
|
||||
try_compare(-0.0009, -0.0004, -1)
|
||||
try_compare(557.4555, 557.4556, 0)
|
||||
try_compare(-557.4555, -557.4556, 0)
|
||||
try_compare(657.4444, 657.445, -1)
|
||||
try_compare(-657.4444, -657.445, 1)
|
||||
|
||||
# Rounding to unusual rounding units (e.g. coin values)
|
||||
def try_round(amount, expected, precision_rounding=None, float_round=float_round, float_repr=float_repr):
|
||||
result = float_repr(float_round(amount, precision_rounding=precision_rounding),
|
||||
precision_digits=2)
|
||||
assert result == expected, 'Rounding error: got %s, expected %s' % (result, expected)
|
||||
try_round(-457.4554, '-457.45', precision_rounding=0.05)
|
||||
try_round(457.444, '457.50', precision_rounding=0.5)
|
||||
try_round(457.3, '455.00', precision_rounding=5)
|
||||
try_round(457.5, '460.00', precision_rounding=5)
|
||||
try_round(457.1, '456.00', precision_rounding=3)
|
||||
|
||||
-
|
||||
"Float precision tests: check that proper rounding is performed for float persistence"
|
||||
-
|
||||
!python {model: res.currency}: |
|
||||
currency = self.browse(cr, uid, ref('base.EUR'))
|
||||
res_currency_rate = self.pool.get('res.currency.rate')
|
||||
from openerp.tools import float_compare, float_is_zero, float_round, float_repr
|
||||
def try_roundtrip(value, expected, self=self, cr=cr, currency=currency,
|
||||
res_currency_rate=res_currency_rate):
|
||||
rate_id = res_currency_rate.create(cr, 1, {'name':'2000-01-01',
|
||||
'rate': value,
|
||||
'currency_id': currency.id})
|
||||
rate = res_currency_rate.read(cr, 1, [rate_id], ['rate'])[0]['rate']
|
||||
assert rate == expected, 'Roundtrip error: got %s back from db, expected %s' % (rate, expected)
|
||||
# res.currency.rate uses 6 digits of precision by default
|
||||
try_roundtrip(2.6748955, 2.674896)
|
||||
try_roundtrip(-2.6748955, -2.674896)
|
||||
try_roundtrip(10000.999999, 10000.999999)
|
||||
try_roundtrip(-10000.999999, -10000.999999)
|
||||
|
||||
-
|
||||
"Float precision tests: verify that invalid parameters are forbidden"
|
||||
-
|
||||
!python {model: res.currency}: |
|
||||
from openerp.tools import float_compare, float_is_zero, float_round
|
||||
try:
|
||||
float_is_zero(0.01, precision_digits=3, precision_rounding=0.01)
|
||||
except AssertionError:
|
||||
pass
|
||||
try:
|
||||
float_compare(0.01, 0.02, precision_digits=3, precision_rounding=0.01)
|
||||
except AssertionError:
|
||||
pass
|
||||
try:
|
||||
float_round(0.01, precision_digits=3, precision_rounding=0.01)
|
||||
except AssertionError:
|
||||
pass
|
||||
-
|
||||
Test res.groups name search
|
||||
-
|
||||
!python {model: res.groups}: |
|
||||
all_groups = self.search(cr, uid, [])
|
||||
full_names = [(group.id, group.full_name) for group in self.browse(cr, uid, all_groups)]
|
||||
group_ids = self.search(cr, uid, [('full_name', 'like', '%Sale%')])
|
||||
assert set(group_ids) == set([id for (id, full_name) in full_names if 'Sale' in full_name]), "did not match search for 'Sale'"
|
||||
group_ids = self.search(cr, uid, [('full_name', 'like', '%Technical%')])
|
||||
assert set(group_ids) == set([id for (id, full_name) in full_names if 'Technical' in full_name]), "did not match search for 'Technical'"
|
||||
group_ids = self.search(cr, uid, [('full_name', 'like', '%Sales /%')])
|
||||
assert set(group_ids) == set([id for (id, full_name) in full_names if 'Sales /' in full_name]), "did not match search for 'Sales /'"
|
||||
group_ids = self.search(cr, uid, [('full_name', 'in', ['Administration / Access Rights','Contact Creation'])])
|
||||
assert group_ids, "did not match search for 'Administration / Access Rights' and 'Contact Creation'"
|
||||
@@ -1,26 +1,23 @@
|
||||
import unittest
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from lxml import etree
|
||||
|
||||
from openerp.exceptions import AccessError
|
||||
from openerp.tools.misc import mute_logger
|
||||
from openerp.tests import common
|
||||
from odoo.exceptions import AccessError
|
||||
from odoo.tests.common import TransactionCase
|
||||
from odoo.tools.misc import mute_logger
|
||||
|
||||
# test group that demo user should not have
|
||||
USER_DEMO = 'base.user_demo'
|
||||
GROUP_SYSTEM = 'base.group_system'
|
||||
GROUP_ERP_MANAGER = 'base.group_erp_manager'
|
||||
|
||||
|
||||
class TestACL(common.TransactionCase):
|
||||
class TestACL(TransactionCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestACL, self).setUp()
|
||||
self.res_currency = self.registry('res.currency')
|
||||
self.res_partner = self.registry('res.partner')
|
||||
self.res_users = self.registry('res.users')
|
||||
self.res_company = self.registry('res.company')
|
||||
self.demo_uid = self.env.ref('base.user_demo').id
|
||||
self.demo_user = self.env.ref(USER_DEMO)
|
||||
self.erp_system_group = self.env.ref(GROUP_SYSTEM)
|
||||
self.erp_manager_group = self.env.ref(GROUP_ERP_MANAGER)
|
||||
|
||||
def _set_field_groups(self, model, field_name, groups):
|
||||
field = model._fields[field_name]
|
||||
@@ -32,109 +29,103 @@ class TestACL(common.TransactionCase):
|
||||
def test_field_visibility_restriction(self):
|
||||
"""Check that model-level ``groups`` parameter effectively restricts access to that
|
||||
field for users who do not belong to one of the explicitly allowed groups"""
|
||||
currency = self.env['res.currency'].sudo(self.demo_user)
|
||||
|
||||
# Verify the test environment first
|
||||
original_fields = self.res_currency.fields_get(self.cr, self.demo_uid, [])
|
||||
form_view = self.res_currency.fields_view_get(self.cr, self.demo_uid, False, 'form')
|
||||
original_fields = currency.fields_get([])
|
||||
form_view = currency.fields_view_get(False, 'form')
|
||||
view_arch = etree.fromstring(form_view.get('arch'))
|
||||
has_group_system = self.res_users.has_group(self.cr, self.demo_uid, GROUP_SYSTEM)
|
||||
has_group_system = self.demo_user.has_group(GROUP_SYSTEM)
|
||||
self.assertFalse(has_group_system, "`demo` user should not belong to the restricted group before the test")
|
||||
self.assertTrue('decimal_places' in original_fields, "'decimal_places' field must be properly visible before the test")
|
||||
self.assertIn('decimal_places', original_fields, "'decimal_places' field must be properly visible before the test")
|
||||
self.assertNotEquals(view_arch.xpath("//field[@name='decimal_places']"), [],
|
||||
"Field 'decimal_places' must be found in view definition before the test")
|
||||
|
||||
# restrict access to the field and check it's gone
|
||||
self._set_field_groups(self.res_currency, 'decimal_places', GROUP_SYSTEM)
|
||||
self._set_field_groups(currency, 'decimal_places', GROUP_SYSTEM)
|
||||
|
||||
fields = self.res_currency.fields_get(self.cr, self.demo_uid, [])
|
||||
form_view = self.res_currency.fields_view_get(self.cr, self.demo_uid, False, 'form')
|
||||
fields = currency.fields_get([])
|
||||
form_view = currency.fields_view_get(False, 'form')
|
||||
view_arch = etree.fromstring(form_view.get('arch'))
|
||||
self.assertFalse('decimal_places' in fields, "'decimal_places' field should be gone")
|
||||
self.assertNotIn('decimal_places', fields, "'decimal_places' field should be gone")
|
||||
self.assertEquals(view_arch.xpath("//field[@name='decimal_places']"), [],
|
||||
"Field 'decimal_places' must not be found in view definition")
|
||||
|
||||
# Make demo user a member of the restricted group and check that the field is back
|
||||
self.erp_system_group.write({'users': [(4, self.demo_uid)]})
|
||||
has_group_system = self.res_users.has_group(self.cr, self.demo_uid, GROUP_SYSTEM)
|
||||
fields = self.res_currency.fields_get(self.cr, self.demo_uid, [])
|
||||
form_view = self.res_currency.fields_view_get(self.cr, self.demo_uid, False, 'form')
|
||||
self.erp_system_group.users += self.demo_user
|
||||
has_group_system = self.demo_user.has_group(GROUP_SYSTEM)
|
||||
fields = currency.fields_get([])
|
||||
form_view = currency.fields_view_get(False, 'form')
|
||||
view_arch = etree.fromstring(form_view.get('arch'))
|
||||
#import pprint; pprint.pprint(fields); pprint.pprint(form_view)
|
||||
self.assertTrue(has_group_system, "`demo` user should now belong to the restricted group")
|
||||
self.assertTrue('decimal_places' in fields, "'decimal_places' field must be properly visible again")
|
||||
self.assertIn('decimal_places', fields, "'decimal_places' field must be properly visible again")
|
||||
self.assertNotEquals(view_arch.xpath("//field[@name='decimal_places']"), [],
|
||||
"Field 'decimal_places' must be found in view definition again")
|
||||
|
||||
#cleanup
|
||||
self.erp_system_group.write({'users': [(3, self.demo_uid)]})
|
||||
|
||||
@mute_logger('openerp.models')
|
||||
def test_field_crud_restriction(self):
|
||||
"Read/Write RPC access to restricted field should be forbidden"
|
||||
partner = self.env['res.partner'].browse(1).sudo(self.demo_user)
|
||||
|
||||
# Verify the test environment first
|
||||
has_group_system = self.res_users.has_group(self.cr, self.demo_uid, GROUP_SYSTEM)
|
||||
has_group_system = self.demo_user.has_group(GROUP_SYSTEM)
|
||||
self.assertFalse(has_group_system, "`demo` user should not belong to the restricted group")
|
||||
self.assert_(self.res_partner.read(self.cr, self.demo_uid, [1], ['bank_ids']))
|
||||
self.assert_(self.res_partner.write(self.cr, self.demo_uid, [1], {'bank_ids': []}))
|
||||
self.assert_(partner.read(['bank_ids']))
|
||||
self.assert_(partner.write({'bank_ids': []}))
|
||||
|
||||
# Now restrict access to the field and check it's forbidden
|
||||
self._set_field_groups(self.res_partner, 'bank_ids', GROUP_SYSTEM)
|
||||
self._set_field_groups(partner, 'bank_ids', GROUP_SYSTEM)
|
||||
|
||||
with self.assertRaises(AccessError):
|
||||
self.res_partner.read(self.cr, self.demo_uid, [1], ['bank_ids'])
|
||||
partner.read(['bank_ids'])
|
||||
with self.assertRaises(AccessError):
|
||||
self.res_partner.write(self.cr, self.demo_uid, [1], {'bank_ids': []})
|
||||
partner.write({'bank_ids': []})
|
||||
|
||||
# Add the restricted group, and check that it works again
|
||||
self.erp_system_group.write({'users': [(4, self.demo_uid)]})
|
||||
has_group_system = self.res_users.has_group(self.cr, self.demo_uid, GROUP_SYSTEM)
|
||||
self.erp_system_group.users += self.demo_user
|
||||
has_group_system = self.demo_user.has_group(GROUP_SYSTEM)
|
||||
self.assertTrue(has_group_system, "`demo` user should now belong to the restricted group")
|
||||
self.assert_(self.res_partner.read(self.cr, self.demo_uid, [1], ['bank_ids']))
|
||||
self.assert_(self.res_partner.write(self.cr, self.demo_uid, [1], {'bank_ids': []}))
|
||||
|
||||
#cleanup
|
||||
self.erp_system_group.write({'users': [(3, self.demo_uid)]})
|
||||
self.assert_(partner.read(['bank_ids']))
|
||||
self.assert_(partner.write({'bank_ids': []}))
|
||||
|
||||
@mute_logger('openerp.models')
|
||||
def test_fields_browse_restriction(self):
|
||||
"""Test access to records having restricted fields"""
|
||||
self._set_field_groups(self.res_partner, 'email', GROUP_SYSTEM)
|
||||
partner = self.env['res.partner'].sudo(self.demo_user)
|
||||
self._set_field_groups(partner, 'email', GROUP_SYSTEM)
|
||||
|
||||
pid = self.res_partner.search(self.cr, self.demo_uid, [], limit=1)[0]
|
||||
part = self.res_partner.browse(self.cr, self.demo_uid, pid)
|
||||
# accessing fields must no raise exceptions...
|
||||
part.name
|
||||
partner = partner.search([], limit=1)
|
||||
partner.name
|
||||
# ... except if they are restricted
|
||||
with self.assertRaises(AccessError) as cm:
|
||||
with self.assertRaises(AccessError):
|
||||
with mute_logger('openerp.models'):
|
||||
part.email
|
||||
|
||||
#Useless because we get the title in the client side now in the chrome.js file(map_title)
|
||||
#self.assertEqual(cm.exception.args[0], 'Access Error')
|
||||
partner.email
|
||||
|
||||
def test_view_create_edit_button_invisibility(self):
|
||||
""" Test form view Create, Edit, Delete button visibility based on access right of model"""
|
||||
methods = ['create', 'edit', 'delete']
|
||||
company_view = self.res_company.fields_view_get(self.cr, self.demo_uid, False, 'form')
|
||||
company = self.env['res.company'].sudo(self.demo_user)
|
||||
company_view = company.fields_view_get(False, 'form')
|
||||
view_arch = etree.fromstring(company_view['arch'])
|
||||
for method in methods:
|
||||
self.assertEqual(view_arch.get(method), 'false')
|
||||
|
||||
def test_view_create_edit_button_visibility(self):
|
||||
""" Test form view Create, Edit, Delete button visibility based on access right of model"""
|
||||
self.erp_system_group.users += self.demo_user
|
||||
methods = ['create', 'edit', 'delete']
|
||||
self.erp_manager_group.write({'users': [(4, self.demo_uid)]})
|
||||
company_view = self.res_company.fields_view_get(self.cr, self.demo_uid, False, 'form')
|
||||
company = self.env['res.company'].sudo(self.demo_user)
|
||||
company_view = company.fields_view_get(False, 'form')
|
||||
view_arch = etree.fromstring(company_view['arch'])
|
||||
for method in methods:
|
||||
self.assertIsNone(view_arch.get(method))
|
||||
# cleanup
|
||||
self.erp_manager_group.write({'users': [(3, self.demo_uid)]})
|
||||
|
||||
def test_m2o_field_create_edit_invisibility(self):
|
||||
""" Test many2one field Create and Edit option visibility based on access rights of relation field"""
|
||||
methods = ['create', 'write']
|
||||
|
||||
company_view = self.res_company.fields_view_get(self.cr, self.demo_uid, False, 'form')
|
||||
company = self.env['res.company'].sudo(self.demo_user)
|
||||
company_view = company.fields_view_get(False, 'form')
|
||||
view_arch = etree.fromstring(company_view['arch'])
|
||||
field_node = view_arch.xpath("//field[@name='currency_id']")
|
||||
self.assertTrue(len(field_node), "currency_id field should be in company from view")
|
||||
@@ -143,17 +134,125 @@ class TestACL(common.TransactionCase):
|
||||
|
||||
def test_m2o_field_create_edit_visibility(self):
|
||||
""" Test many2one field Create and Edit option visibility based on access rights of relation field"""
|
||||
self.erp_system_group.users += self.demo_user
|
||||
methods = ['create', 'write']
|
||||
|
||||
self.erp_system_group.write({'users': [(4, self.demo_uid)]})
|
||||
company_view = self.res_company.fields_view_get(self.cr, self.demo_uid, False, 'form')
|
||||
company = self.env['res.company'].sudo(self.demo_user)
|
||||
company_view = company.fields_view_get(False, 'form')
|
||||
view_arch = etree.fromstring(company_view['arch'])
|
||||
field_node = view_arch.xpath("//field[@name='currency_id']")
|
||||
self.assertTrue(len(field_node), "currency_id field should be in company from view")
|
||||
for method in methods:
|
||||
self.assertEqual(field_node[0].get('can_' + method), 'true')
|
||||
# cleanup
|
||||
self.erp_manager_group.write({'users': [(3, self.demo_uid)]})
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
class TestIrRule(TransactionCase):
|
||||
|
||||
def test_ir_rule(self):
|
||||
model_res_partner = self.env.ref('base.model_res_partner')
|
||||
group_user = self.env.ref('base.group_user')
|
||||
user_demo = self.env.ref('base.user_demo')
|
||||
|
||||
# create an ir_rule for the Employee group with an blank domain
|
||||
rule1 = self.env['ir.rule'].create({
|
||||
'name': 'test_rule1',
|
||||
'model_id': model_res_partner.id,
|
||||
'domain_force': False,
|
||||
'groups': [(6, 0, group_user.ids)],
|
||||
})
|
||||
|
||||
# read as demo user the partners (one blank domain)
|
||||
partners_demo = self.env['res.partner'].sudo(user_demo)
|
||||
partners = partners_demo.search([])
|
||||
self.assertTrue(partners, "Demo user should see some partner.")
|
||||
|
||||
# same with domain 1=1
|
||||
rule1.domain_force = "[(1,'=',1)]"
|
||||
partners = partners_demo.search([])
|
||||
self.assertTrue(partners, "Demo user should see some partner.")
|
||||
|
||||
# same with domain []
|
||||
rule1.domain_force = "[]"
|
||||
partners = partners_demo.search([])
|
||||
self.assertTrue(partners, "Demo user should see some partner.")
|
||||
|
||||
# create another ir_rule for the Employee group (to test multiple rules)
|
||||
rule2 = self.env['ir.rule'].create({
|
||||
'name': 'test_rule2',
|
||||
'model_id': model_res_partner.id,
|
||||
'domain_force': False,
|
||||
'groups': [(6, 0, group_user.ids)],
|
||||
})
|
||||
|
||||
# read as demo user with domains [] and blank
|
||||
partners = partners_demo.search([])
|
||||
self.assertTrue(partners, "Demo user should see some partner.")
|
||||
|
||||
# same with domains 1=1 and blank
|
||||
rule1.domain_force = "[(1,'=',1)]"
|
||||
partners = partners_demo.search([])
|
||||
self.assertTrue(partners, "Demo user should see some partner.")
|
||||
|
||||
# same with domains 1=1 and 1=1
|
||||
rule2.domain_force = "[(1,'=',1)]"
|
||||
partners = partners_demo.search([])
|
||||
self.assertTrue(partners, "Demo user should see some partner.")
|
||||
|
||||
# create another ir_rule for the Employee group (to test multiple rules)
|
||||
rule3 = self.env['ir.rule'].create({
|
||||
'name': 'test_rule3',
|
||||
'model_id': model_res_partner.id,
|
||||
'domain_force': False,
|
||||
'groups': [(6, 0, group_user.ids)],
|
||||
})
|
||||
|
||||
# read the partners as demo user
|
||||
partners = partners_demo.search([])
|
||||
self.assertTrue(partners, "Demo user should see some partner.")
|
||||
|
||||
# same with domains 1=1, 1=1 and 1=1
|
||||
rule3.domain_force = "[(1,'=',1)]"
|
||||
partners = partners_demo.search([])
|
||||
self.assertTrue(partners, "Demo user should see some partner.")
|
||||
|
||||
# modify the global rule on res_company which triggers a recursive check
|
||||
# of the rules on company
|
||||
global_rule = self.env.ref('base.res_company_rule_employee')
|
||||
global_rule.domain_force = "[('id','child_of',[user.company_id.id])]"
|
||||
|
||||
# read as demo user (exercising the global company rule)
|
||||
partners = partners_demo.search([])
|
||||
self.assertTrue(partners, "Demo user should see some partner.")
|
||||
|
||||
# Modify the ir_rule for employee to have a rule that fordids seeing any
|
||||
# record. We use a domain with implicit AND operator for later tests on
|
||||
# normalization.
|
||||
rule2.domain_force = "[('id','=',False),('name','=',False)]"
|
||||
|
||||
# check that demo user still sees partners, because group-rules are OR'ed
|
||||
partners = partners_demo.search([])
|
||||
self.assertTrue(partners, "Demo user should see some partner.")
|
||||
|
||||
# create a new group with demo user in it, and a complex rule
|
||||
group_test = self.env['res.groups'].create({
|
||||
'name': 'Test Group',
|
||||
'users': [(6, 0, user_demo.ids)],
|
||||
})
|
||||
|
||||
# add the rule to the new group, with a domain containing an implicit
|
||||
# AND operator, which is more tricky because it will have to be
|
||||
# normalized before combining it
|
||||
rule3.write({
|
||||
'domain_force': "[('name','!=',False),('id','!=',False)]",
|
||||
'groups': [(6, 0, group_test.ids)],
|
||||
})
|
||||
|
||||
# read the partners again as demo user, which should give results
|
||||
partners = partners_demo.search([])
|
||||
self.assertTrue(partners, "Demo user should see partners even with the combined rules.")
|
||||
|
||||
# delete global domains (to combine only group domains)
|
||||
self.env['ir.rule'].search([('groups', '=', False)]).unlink()
|
||||
|
||||
# read the partners as demo user (several group domains, no global domain)
|
||||
partners = partners_demo.search([])
|
||||
self.assertTrue(partners, "Demo user should see some partners.")
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from openerp import models
|
||||
from openerp.tools import mute_logger
|
||||
from openerp.tests import common
|
||||
from openerp.exceptions import AccessError
|
||||
from odoo import models
|
||||
from odoo.tools import mute_logger
|
||||
from odoo.tests import common
|
||||
from odoo.exceptions import AccessError
|
||||
|
||||
|
||||
class TestAPI(common.TransactionCase):
|
||||
@@ -84,7 +86,7 @@ class TestAPI(common.TransactionCase):
|
||||
ids = map(int, partners)
|
||||
|
||||
# modify those partners, and check that partners has not changed
|
||||
self.registry('res.partner').write(self.cr, self.uid, ids, {'active': False})
|
||||
partners.write({'active': False})
|
||||
self.assertEqual(ids, map(int, partners))
|
||||
|
||||
# redo the search, and check that the result is now empty
|
||||
@@ -94,7 +96,7 @@ class TestAPI(common.TransactionCase):
|
||||
@mute_logger('openerp.models')
|
||||
def test_06_fields(self):
|
||||
""" Check that relation fields return records, recordsets or nulls. """
|
||||
user = self.registry('res.users').browse(self.cr, self.uid, self.uid)
|
||||
user = self.env.user
|
||||
self.assertIsRecord(user, 'res.users')
|
||||
self.assertIsRecord(user.partner_id, 'res.partner')
|
||||
self.assertIsRecordset(user.groups_id, 'res.groups')
|
||||
|
||||
@@ -1,80 +1,113 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import ast
|
||||
import unittest
|
||||
|
||||
import openerp.tests.common as common
|
||||
from openerp.exceptions import ValidationError
|
||||
from odoo.exceptions import ValidationError
|
||||
from odoo.tests.common import TransactionCase
|
||||
from odoo.tools import mute_logger
|
||||
from odoo.tools.safe_eval import safe_eval
|
||||
|
||||
class test_base(common.TransactionCase):
|
||||
|
||||
def setUp(self):
|
||||
super(test_base,self).setUp()
|
||||
self.res_partner = self.registry('res.partner')
|
||||
self.res_users = self.registry('res.users')
|
||||
self.res_partner_title = self.registry('res.partner.title')
|
||||
class TestSafeEval(unittest.TestCase):
|
||||
|
||||
# samples use effective TLDs from the Mozilla public suffix
|
||||
# list at http://publicsuffix.org
|
||||
self.samples = [
|
||||
('"Raoul Grosbedon" <raoul@chirurgiens-dentistes.fr> ', 'Raoul Grosbedon', 'raoul@chirurgiens-dentistes.fr'),
|
||||
('ryu+giga-Sushi@aizubange.fukushima.jp', '', 'ryu+giga-Sushi@aizubange.fukushima.jp'),
|
||||
('Raoul chirurgiens-dentistes.fr', 'Raoul chirurgiens-dentistes.fr', ''),
|
||||
(" Raoul O'hara <!@historicalsociety.museum>", "Raoul O'hara", '!@historicalsociety.museum')
|
||||
]
|
||||
def test_01_safe_eval(self):
|
||||
""" Try a few common expressions to verify they work with safe_eval """
|
||||
expected = (1, {"a": 9 * 2}, (True, False, None))
|
||||
actual = safe_eval('(1, {"a": 9 * 2}, (True, False, None))')
|
||||
self.assertEqual(actual, expected, "Simple python expressions are not working with safe_eval")
|
||||
|
||||
def test_02_literal_eval(self):
|
||||
""" Try simple literal definition to verify it works with literal_eval """
|
||||
expected = (1, {"a": 9}, (True, False, None))
|
||||
actual = ast.literal_eval('(1, {"a": 9}, (True, False, None))')
|
||||
self.assertEqual(actual, expected, "Simple python expressions are not working with literal_eval")
|
||||
|
||||
def test_03_literal_eval_arithmetic(self):
|
||||
""" Try arithmetic expression in literal_eval to verify it does not work """
|
||||
with self.assertRaises(ValueError):
|
||||
ast.literal_eval('(1, {"a": 2*9}, (True, False, None))')
|
||||
|
||||
def test_04_literal_eval_forbidden(self):
|
||||
""" Try forbidden expressions in literal_eval to verify they are not allowed """
|
||||
with self.assertRaises(ValueError):
|
||||
ast.literal_eval('{"a": True.__class__}')
|
||||
|
||||
@mute_logger('openerp.tools.safe_eval')
|
||||
def test_05_safe_eval_forbiddon(self):
|
||||
""" Try forbidden expressions in safe_eval to verify they are not allowed (open) """
|
||||
with self.assertRaises(ValueError):
|
||||
safe_eval('open("/etc/passwd","r")')
|
||||
|
||||
|
||||
# samples use effective TLDs from the Mozilla public suffix
|
||||
# list at http://publicsuffix.org
|
||||
SAMPLES = [
|
||||
('"Raoul Grosbedon" <raoul@chirurgiens-dentistes.fr> ', 'Raoul Grosbedon', 'raoul@chirurgiens-dentistes.fr'),
|
||||
('ryu+giga-Sushi@aizubange.fukushima.jp', '', 'ryu+giga-Sushi@aizubange.fukushima.jp'),
|
||||
('Raoul chirurgiens-dentistes.fr', 'Raoul chirurgiens-dentistes.fr', ''),
|
||||
(" Raoul O'hara <!@historicalsociety.museum>", "Raoul O'hara", '!@historicalsociety.museum'),
|
||||
]
|
||||
|
||||
class TestBase(TransactionCase):
|
||||
|
||||
def test_00_res_partner_name_create(self):
|
||||
cr, uid = self.cr, self.uid
|
||||
parse = self.res_partner._parse_partner_name
|
||||
for text, name, mail in self.samples:
|
||||
self.assertEqual((name,mail), parse(text), 'Partner name parsing failed')
|
||||
partner_id, dummy = self.res_partner.name_create(cr, uid, text)
|
||||
partner = self.res_partner.browse(cr, uid, partner_id)
|
||||
res_partner = self.env['res.partner']
|
||||
parse = res_partner._parse_partner_name
|
||||
for text, name, mail in SAMPLES:
|
||||
self.assertEqual((name, mail), parse(text), 'Partner name parsing failed')
|
||||
partner_id, dummy = res_partner.name_create(text)
|
||||
partner = res_partner.browse(partner_id)
|
||||
self.assertEqual(name or mail, partner.name, 'Partner name incorrect')
|
||||
self.assertEqual(mail or False, partner.email, 'Partner email incorrect')
|
||||
|
||||
def test_10_res_partner_find_or_create(self):
|
||||
cr,uid = self.cr, self.uid
|
||||
email = self.samples[0][0]
|
||||
partner_id, dummy = self.res_partner.name_create(cr, uid, email)
|
||||
found_id = self.res_partner.find_or_create(cr, uid, email)
|
||||
res_partner = self.env['res.partner']
|
||||
email = SAMPLES[0][0]
|
||||
partner_id, dummy = res_partner.name_create(email)
|
||||
found_id = res_partner.find_or_create(email)
|
||||
self.assertEqual(partner_id, found_id, 'find_or_create failed')
|
||||
partner_id2, dummy2 = self.res_partner.name_create(cr, uid, 'sarah.john@connor.com')
|
||||
found_id2 = self.res_partner.find_or_create(cr, uid, 'john@connor.com')
|
||||
partner_id2, dummy2 = res_partner.name_create('sarah.john@connor.com')
|
||||
found_id2 = res_partner.find_or_create('john@connor.com')
|
||||
self.assertNotEqual(partner_id2, found_id2, 'john@connor.com match sarah.john@connor.com')
|
||||
new_id = self.res_partner.find_or_create(cr, uid, self.samples[1][0])
|
||||
new_id = res_partner.find_or_create(SAMPLES[1][0])
|
||||
self.assertTrue(new_id > partner_id, 'find_or_create failed - should have created new one')
|
||||
new_id2 = self.res_partner.find_or_create(cr, uid, self.samples[2][0])
|
||||
new_id2 = res_partner.find_or_create(SAMPLES[2][0])
|
||||
self.assertTrue(new_id2 > new_id, 'find_or_create failed - should have created new one again')
|
||||
|
||||
def test_15_res_partner_name_search(self):
|
||||
cr,uid = self.cr, self.uid
|
||||
for name, active in [
|
||||
res_partner = self.env['res.partner']
|
||||
DATA = [
|
||||
('"A Raoul Grosbedon" <raoul@chirurgiens-dentistes.fr>', False),
|
||||
('B Raoul chirurgiens-dentistes.fr', True),
|
||||
("C Raoul O'hara <!@historicalsociety.museum>", True),
|
||||
('ryu+giga-Sushi@aizubange.fukushima.jp', True),
|
||||
]:
|
||||
partner_id, dummy = self.res_partner.name_create(cr, uid, name, context={'default_active': active})
|
||||
partners = self.res_partner.name_search(cr, uid, 'Raoul')
|
||||
]
|
||||
for name, active in DATA:
|
||||
partner_id, dummy = res_partner.with_context(default_active=active).name_create(name)
|
||||
partners = res_partner.name_search('Raoul')
|
||||
self.assertEqual(len(partners), 2, 'Incorrect search number result for name_search')
|
||||
partners = self.res_partner.name_search(cr, uid, 'Raoul', limit=1)
|
||||
partners = res_partner.name_search('Raoul', limit=1)
|
||||
self.assertEqual(len(partners), 1, 'Incorrect search number result for name_search with a limit')
|
||||
self.assertEqual(partners[0][1], 'B Raoul chirurgiens-dentistes.fr', 'Incorrect partner returned, should be the first active')
|
||||
|
||||
def test_20_res_partner_address_sync(self):
|
||||
cr, uid = self.cr, self.uid
|
||||
ghoststep = self.res_partner.browse(cr, uid, self.res_partner.create(cr, uid,
|
||||
{'name': 'GhostStep',
|
||||
'is_company': True,
|
||||
'street': 'Main Street, 10',
|
||||
'phone': '123456789',
|
||||
'email': 'info@ghoststep.com',
|
||||
'vat': 'BE0477472701',
|
||||
'type': 'contact'}))
|
||||
p1 = self.res_partner.browse(cr, uid, self.res_partner.name_create(cr, uid, 'Denis Bladesmith <denis.bladesmith@ghoststep.com>')[0])
|
||||
res_partner = self.env['res.partner']
|
||||
ghoststep = res_partner.create({
|
||||
'name': 'GhostStep',
|
||||
'is_company': True,
|
||||
'street': 'Main Street, 10',
|
||||
'phone': '123456789',
|
||||
'email': 'info@ghoststep.com',
|
||||
'vat': 'BE0477472701',
|
||||
'type': 'contact',
|
||||
})
|
||||
p1 = res_partner.browse(res_partner.name_create('Denis Bladesmith <denis.bladesmith@ghoststep.com>')[0])
|
||||
self.assertEqual(p1.type, 'contact', 'Default type must be "contact"')
|
||||
p1phone = '123456789#34'
|
||||
p1.write({'phone': p1phone,
|
||||
'parent_id': ghoststep.id})
|
||||
p1.refresh()
|
||||
self.assertEqual(p1.street, ghoststep.street, 'Address fields must be synced')
|
||||
self.assertEqual(p1.phone, p1phone, 'Phone should be preserved after address sync')
|
||||
self.assertEqual(p1.type, 'contact', 'Type should be preserved after address sync')
|
||||
@@ -84,13 +117,11 @@ class test_base(common.TransactionCase):
|
||||
p1street = 'Different street, 42'
|
||||
p1.write({'street': p1street,
|
||||
'type': 'invoice'})
|
||||
p1.refresh(), ghoststep.refresh()
|
||||
self.assertEqual(p1.street, p1street, 'Address fields must not be synced after turning sync off')
|
||||
self.assertNotEqual(ghoststep.street, p1street, 'Parent address must never be touched')
|
||||
|
||||
# turn on sync again
|
||||
p1.write({'type': 'contact'})
|
||||
p1.refresh()
|
||||
self.assertEqual(p1.street, ghoststep.street, 'Address fields must be synced again')
|
||||
self.assertEqual(p1.phone, p1phone, 'Phone should be preserved after address sync')
|
||||
self.assertEqual(p1.type, 'contact', 'Type should be preserved after address sync')
|
||||
@@ -99,213 +130,197 @@ class test_base(common.TransactionCase):
|
||||
# Modify parent, sync to children
|
||||
ghoststreet = 'South Street, 25'
|
||||
ghoststep.write({'street': ghoststreet})
|
||||
p1.refresh()
|
||||
self.assertEqual(p1.street, ghoststreet, 'Address fields must be synced automatically')
|
||||
self.assertEqual(p1.phone, p1phone, 'Phone should not be synced')
|
||||
self.assertEqual(p1.email, 'denis.bladesmith@ghoststep.com', 'Email should be preserved after sync')
|
||||
|
||||
p1street = 'My Street, 11'
|
||||
p1.write({'street': p1street})
|
||||
ghoststep.refresh()
|
||||
self.assertEqual(ghoststep.street, ghoststreet, 'Touching contact should never alter parent')
|
||||
|
||||
def test_30_res_partner_first_contact_sync(self):
|
||||
""" Test initial creation of company/contact pair where contact address gets copied to
|
||||
company """
|
||||
cr, uid = self.cr, self.uid
|
||||
ironshield = self.res_partner.browse(cr, uid, self.res_partner.name_create(cr, uid, 'IronShield')[0])
|
||||
res_partner = self.env['res.partner']
|
||||
ironshield = res_partner.browse(res_partner.name_create('IronShield')[0])
|
||||
self.assertFalse(ironshield.is_company, 'Partners are not companies by default')
|
||||
self.assertEqual(ironshield.type, 'contact', 'Default type must be "contact"')
|
||||
ironshield.write({'type': 'contact'})
|
||||
p1 = self.res_partner.browse(cr, uid, self.res_partner.create(cr, uid,
|
||||
{'name': 'Isen Hardearth',
|
||||
'street': 'Strongarm Avenue, 12',
|
||||
'parent_id': ironshield.id}))
|
||||
|
||||
p1 = res_partner.create({
|
||||
'name': 'Isen Hardearth',
|
||||
'street': 'Strongarm Avenue, 12',
|
||||
'parent_id': ironshield.id,
|
||||
})
|
||||
self.assertEquals(p1.type, 'contact', 'Default type must be "contact", not the copied parent type')
|
||||
ironshield.refresh()
|
||||
self.assertEqual(ironshield.street, p1.street, 'Address fields should be copied to company')
|
||||
|
||||
def test_40_res_partner_address_getc(self):
|
||||
def test_40_res_partner_address_get(self):
|
||||
""" Test address_get address resolution mechanism: it should first go down through descendants,
|
||||
stopping when encountering another is_copmany entity, then go up, stopping again at the first
|
||||
is_company entity or the root ancestor and if nothing matches, it should use the provided partner
|
||||
itself """
|
||||
cr, uid = self.cr, self.uid
|
||||
elmtree = self.res_partner.browse(cr, uid, self.res_partner.name_create(cr, uid, 'Elmtree')[0])
|
||||
branch1 = self.res_partner.browse(cr, uid, self.res_partner.create(cr, uid, {'name': 'Branch 1',
|
||||
'parent_id': elmtree.id,
|
||||
'is_company': True}))
|
||||
leaf10 = self.res_partner.browse(cr, uid, self.res_partner.create(cr, uid, {'name': 'Leaf 10',
|
||||
'parent_id': branch1.id,
|
||||
'type': 'invoice'}))
|
||||
branch11 = self.res_partner.browse(cr, uid, self.res_partner.create(cr, uid, {'name': 'Branch 11',
|
||||
'parent_id': branch1.id,
|
||||
'type': 'other'}))
|
||||
leaf111 = self.res_partner.browse(cr, uid, self.res_partner.create(cr, uid, {'name': 'Leaf 111',
|
||||
'parent_id': branch11.id,
|
||||
'type': 'delivery'}))
|
||||
res_partner = self.env['res.partner']
|
||||
elmtree = res_partner.browse(res_partner.name_create('Elmtree')[0])
|
||||
branch1 = res_partner.create({'name': 'Branch 1',
|
||||
'parent_id': elmtree.id,
|
||||
'is_company': True})
|
||||
leaf10 = res_partner.create({'name': 'Leaf 10',
|
||||
'parent_id': branch1.id,
|
||||
'type': 'invoice'})
|
||||
branch11 = res_partner.create({'name': 'Branch 11',
|
||||
'parent_id': branch1.id,
|
||||
'type': 'other'})
|
||||
leaf111 = res_partner.create({'name': 'Leaf 111',
|
||||
'parent_id': branch11.id,
|
||||
'type': 'delivery'})
|
||||
branch11.write({'is_company': False}) # force is_company after creating 1rst child
|
||||
branch2 = self.res_partner.browse(cr, uid, self.res_partner.create(cr, uid, {'name': 'Branch 2',
|
||||
'parent_id': elmtree.id,
|
||||
'is_company': True}))
|
||||
leaf21 = self.res_partner.browse(cr, uid, self.res_partner.create(cr, uid, {'name': 'Leaf 21',
|
||||
'parent_id': branch2.id,
|
||||
'type': 'delivery'}))
|
||||
leaf22 = self.res_partner.browse(cr, uid, self.res_partner.create(cr, uid, {'name': 'Leaf 22',
|
||||
'parent_id': branch2.id}))
|
||||
leaf23 = self.res_partner.browse(cr, uid, self.res_partner.create(cr, uid, {'name': 'Leaf 23',
|
||||
'parent_id': branch2.id,
|
||||
'type': 'contact'}))
|
||||
branch2 = res_partner.create({'name': 'Branch 2',
|
||||
'parent_id': elmtree.id,
|
||||
'is_company': True})
|
||||
leaf21 = res_partner.create({'name': 'Leaf 21',
|
||||
'parent_id': branch2.id,
|
||||
'type': 'delivery'})
|
||||
leaf22 = res_partner.create({'name': 'Leaf 22',
|
||||
'parent_id': branch2.id})
|
||||
leaf23 = res_partner.create({'name': 'Leaf 23',
|
||||
'parent_id': branch2.id,
|
||||
'type': 'contact'})
|
||||
|
||||
# go up, stop at branch1
|
||||
self.assertEqual(self.res_partner.address_get(cr, uid, [leaf111.id], ['delivery', 'invoice', 'contact', 'other']),
|
||||
self.assertEqual(leaf111.address_get(['delivery', 'invoice', 'contact', 'other']),
|
||||
{'delivery': leaf111.id,
|
||||
'invoice': leaf10.id,
|
||||
'contact': branch1.id,
|
||||
'other': branch11.id}, 'Invalid address resolution')
|
||||
self.assertEqual(self.res_partner.address_get(cr, uid, [branch11.id], ['delivery', 'invoice', 'contact', 'other']),
|
||||
self.assertEqual(branch11.address_get(['delivery', 'invoice', 'contact', 'other']),
|
||||
{'delivery': leaf111.id,
|
||||
'invoice': leaf10.id,
|
||||
'contact': branch1.id,
|
||||
'other': branch11.id}, 'Invalid address resolution')
|
||||
|
||||
# go down, stop at at all child companies
|
||||
self.assertEqual(self.res_partner.address_get(cr, uid, [elmtree.id], ['delivery', 'invoice', 'contact', 'other']),
|
||||
self.assertEqual(elmtree.address_get(['delivery', 'invoice', 'contact', 'other']),
|
||||
{'delivery': elmtree.id,
|
||||
'invoice': elmtree.id,
|
||||
'contact': elmtree.id,
|
||||
'other': elmtree.id}, 'Invalid address resolution')
|
||||
|
||||
# go down through children
|
||||
self.assertEqual(self.res_partner.address_get(cr, uid, [branch1.id], ['delivery', 'invoice', 'contact', 'other']),
|
||||
self.assertEqual(branch1.address_get(['delivery', 'invoice', 'contact', 'other']),
|
||||
{'delivery': leaf111.id,
|
||||
'invoice': leaf10.id,
|
||||
'contact': branch1.id,
|
||||
'other': branch11.id}, 'Invalid address resolution')
|
||||
|
||||
self.assertEqual(self.res_partner.address_get(cr, uid, [branch2.id], ['delivery', 'invoice', 'contact', 'other']),
|
||||
self.assertEqual(branch2.address_get(['delivery', 'invoice', 'contact', 'other']),
|
||||
{'delivery': leaf21.id,
|
||||
'invoice': branch2.id,
|
||||
'contact': branch2.id,
|
||||
'other': branch2.id}, 'Invalid address resolution. Company is the first encountered contact, therefore default for unfound addresses.')
|
||||
|
||||
# go up then down through siblings
|
||||
self.assertEqual(self.res_partner.address_get(cr, uid, [leaf21.id], ['delivery', 'invoice', 'contact', 'other']),
|
||||
self.assertEqual(leaf21.address_get(['delivery', 'invoice', 'contact', 'other']),
|
||||
{'delivery': leaf21.id,
|
||||
'invoice': branch2.id,
|
||||
'contact': branch2.id,
|
||||
'other': branch2.id}, 'Invalid address resolution, should scan commercial entity ancestor and its descendants')
|
||||
self.assertEqual(self.res_partner.address_get(cr, uid, [leaf22.id], ['delivery', 'invoice', 'contact', 'other']),
|
||||
self.assertEqual(leaf22.address_get(['delivery', 'invoice', 'contact', 'other']),
|
||||
{'delivery': leaf21.id,
|
||||
'invoice': leaf22.id,
|
||||
'contact': leaf22.id,
|
||||
'other': leaf22.id}, 'Invalid address resolution, should scan commercial entity ancestor and its descendants')
|
||||
self.assertEqual(self.res_partner.address_get(cr, uid, [leaf23.id], ['delivery', 'invoice', 'contact', 'other']),
|
||||
self.assertEqual(leaf23.address_get(['delivery', 'invoice', 'contact', 'other']),
|
||||
{'delivery': leaf21.id,
|
||||
'invoice': leaf23.id,
|
||||
'contact': leaf23.id,
|
||||
'other': leaf23.id}, 'Invalid address resolution, `default` should only override if no partner with specific type exists')
|
||||
|
||||
# empty adr_pref means only 'contact'
|
||||
self.assertEqual(self.res_partner.address_get(cr, uid, [elmtree.id], []),
|
||||
self.assertEqual(elmtree.address_get([]),
|
||||
{'contact': elmtree.id}, 'Invalid address resolution, no contact means commercial entity ancestor')
|
||||
self.assertEqual(self.res_partner.address_get(cr, uid, [leaf111.id], []),
|
||||
self.assertEqual(leaf111.address_get([]),
|
||||
{'contact': branch1.id}, 'Invalid address resolution, no contact means finding contact in ancestors')
|
||||
branch11.write({'type': 'contact'})
|
||||
self.assertEqual(self.res_partner.address_get(cr, uid, [leaf111.id], []),
|
||||
self.assertEqual(leaf111.address_get([]),
|
||||
{'contact': branch11.id}, 'Invalid address resolution, branch11 should now be contact')
|
||||
|
||||
|
||||
def test_50_res_partner_commercial_sync(self):
|
||||
cr, uid = self.cr, self.uid
|
||||
p0 = self.res_partner.browse(cr, uid, self.res_partner.create(cr, uid,
|
||||
{'name': 'Sigurd Sunknife',
|
||||
'email': 'ssunknife@gmail.com'}))
|
||||
sunhelm = self.res_partner.browse(cr, uid, self.res_partner.create(cr, uid,
|
||||
{'name': 'Sunhelm',
|
||||
'is_company': True,
|
||||
'street': 'Rainbow Street, 13',
|
||||
'phone': '1122334455',
|
||||
'email': 'info@sunhelm.com',
|
||||
'vat': 'BE0477472701',
|
||||
'child_ids': [(4, p0.id),
|
||||
(0, 0, {'name': 'Alrik Greenthorn',
|
||||
'email': 'agr@sunhelm.com'})],
|
||||
}))
|
||||
p1 = self.res_partner.browse(cr, uid, self.res_partner.create(cr, uid,
|
||||
{'name': 'Otto Blackwood',
|
||||
'email': 'otto.blackwood@sunhelm.com',
|
||||
'parent_id': sunhelm.id}))
|
||||
p11 = self.res_partner.browse(cr, uid, self.res_partner.create(cr, uid,
|
||||
{'name': 'Gini Graywool',
|
||||
'email': 'ggr@sunhelm.com',
|
||||
'parent_id': p1.id}))
|
||||
p2 = self.res_partner.browse(cr, uid, self.res_partner.search(cr, uid,
|
||||
[('email', '=', 'agr@sunhelm.com')])[0])
|
||||
self.res_partner.write(cr, uid, sunhelm.id, {'child_ids': [(0, 0, {'name': 'Ulrik Greenthorn',
|
||||
'email': 'ugr@sunhelm.com'})]})
|
||||
p3 = self.res_partner.browse(cr, uid, self.res_partner.search(cr, uid,
|
||||
[('email', '=', 'ugr@sunhelm.com')])[0])
|
||||
def test_50_res_partner_commercial_sync(self):
|
||||
res_partner = self.env['res.partner']
|
||||
p0 = res_partner.create({'name': 'Sigurd Sunknife',
|
||||
'email': 'ssunknife@gmail.com'})
|
||||
sunhelm = res_partner.create({'name': 'Sunhelm',
|
||||
'is_company': True,
|
||||
'street': 'Rainbow Street, 13',
|
||||
'phone': '1122334455',
|
||||
'email': 'info@sunhelm.com',
|
||||
'vat': 'BE0477472701',
|
||||
'child_ids': [(4, p0.id),
|
||||
(0, 0, {'name': 'Alrik Greenthorn',
|
||||
'email': 'agr@sunhelm.com'})]})
|
||||
p1 = res_partner.create({'name': 'Otto Blackwood',
|
||||
'email': 'otto.blackwood@sunhelm.com',
|
||||
'parent_id': sunhelm.id})
|
||||
p11 = res_partner.create({'name': 'Gini Graywool',
|
||||
'email': 'ggr@sunhelm.com',
|
||||
'parent_id': p1.id})
|
||||
p2 = res_partner.search([('email', '=', 'agr@sunhelm.com')], limit=1)
|
||||
sunhelm.write({'child_ids': [(0, 0, {'name': 'Ulrik Greenthorn',
|
||||
'email': 'ugr@sunhelm.com'})]})
|
||||
p3 = res_partner.search([('email', '=', 'ugr@sunhelm.com')], limit=1)
|
||||
|
||||
for p in (p0, p1, p11, p2, p3):
|
||||
p.refresh()
|
||||
self.assertEquals(p.commercial_partner_id, sunhelm, 'Incorrect commercial entity resolution')
|
||||
self.assertEquals(p.vat, sunhelm.vat, 'Commercial fields must be automatically synced')
|
||||
sunhelmvat = 'BE0123456789'
|
||||
sunhelm.write({'vat': sunhelmvat})
|
||||
for p in (p0, p1, p11, p2, p3):
|
||||
p.refresh()
|
||||
self.assertEquals(p.vat, sunhelmvat, 'Commercial fields must be automatically and recursively synced')
|
||||
|
||||
p1vat = 'BE0987654321'
|
||||
p1.write({'vat': p1vat})
|
||||
for p in (sunhelm, p0, p11, p2, p3):
|
||||
p.refresh()
|
||||
self.assertEquals(p.vat, sunhelmvat, 'Sync to children should only work downstream and on commercial entities')
|
||||
|
||||
# promote p1 to commercial entity
|
||||
p1.write({
|
||||
'parent_id': sunhelm.id,
|
||||
'is_company': True,
|
||||
'name': 'Sunhelm Subsidiary'})
|
||||
p1.refresh()
|
||||
p1.write({'parent_id': sunhelm.id,
|
||||
'is_company': True,
|
||||
'name': 'Sunhelm Subsidiary'})
|
||||
self.assertEquals(p1.vat, p1vat, 'Setting is_company should stop auto-sync of commercial fields')
|
||||
self.assertEquals(p1.commercial_partner_id, p1, 'Incorrect commercial entity resolution after setting is_company')
|
||||
|
||||
# writing on parent should not touch child commercial entities
|
||||
sunhelmvat2 = 'BE0112233445'
|
||||
sunhelm.write({'vat': sunhelmvat2})
|
||||
p1.refresh()
|
||||
self.assertEquals(p1.vat, p1vat, 'Setting is_company should stop auto-sync of commercial fields')
|
||||
p0.refresh()
|
||||
self.assertEquals(p0.vat, sunhelmvat2, 'Commercial fields must be automatically synced')
|
||||
|
||||
def test_60_read_group(self):
|
||||
cr, uid = self.cr, self.uid
|
||||
title_sir = self.res_partner_title.create(cr, uid, {'name': 'Sir...'})
|
||||
title_lady = self.res_partner_title.create(cr, uid, {'name': 'Lady...'})
|
||||
title_sir = self.env['res.partner.title'].create({'name': 'Sir...'})
|
||||
title_lady = self.env['res.partner.title'].create({'name': 'Lady...'})
|
||||
test_users = [
|
||||
{'name': 'Alice', 'login': 'alice', 'color': 1, 'function': 'Friend', 'date': '2015-03-28', 'title': title_lady},
|
||||
{'name': 'Alice', 'login': 'alice2', 'color': 0, 'function': 'Friend', 'date': '2015-01-28', 'title': title_lady},
|
||||
{'name': 'Bob', 'login': 'bob', 'color': 2, 'function': 'Friend', 'date': '2015-03-02', 'title': title_sir},
|
||||
{'name': 'Eve', 'login': 'eve', 'color': 3, 'function': 'Eavesdropper', 'date': '2015-03-20', 'title': title_lady},
|
||||
{'name': 'Nab', 'login': 'nab', 'color': -3, 'function': '5$ Wrench', 'date': '2014-09-10', 'title': title_sir},
|
||||
{'name': 'Nab', 'login': 'nab-she', 'color': 6, 'function': '5$ Wrench', 'date': '2014-01-02', 'title': title_lady},
|
||||
{'name': 'Alice', 'login': 'alice', 'color': 1, 'function': 'Friend', 'date': '2015-03-28', 'title': title_lady.id},
|
||||
{'name': 'Alice', 'login': 'alice2', 'color': 0, 'function': 'Friend', 'date': '2015-01-28', 'title': title_lady.id},
|
||||
{'name': 'Bob', 'login': 'bob', 'color': 2, 'function': 'Friend', 'date': '2015-03-02', 'title': title_sir.id},
|
||||
{'name': 'Eve', 'login': 'eve', 'color': 3, 'function': 'Eavesdropper', 'date': '2015-03-20', 'title': title_lady.id},
|
||||
{'name': 'Nab', 'login': 'nab', 'color': -3, 'function': '5$ Wrench', 'date': '2014-09-10', 'title': title_sir.id},
|
||||
{'name': 'Nab', 'login': 'nab-she', 'color': 6, 'function': '5$ Wrench', 'date': '2014-01-02', 'title': title_lady.id},
|
||||
]
|
||||
ids = [self.res_users.create(cr, uid, u) for u in test_users]
|
||||
domain = [('id', 'in', ids)]
|
||||
res_users = self.env['res.users']
|
||||
user_ids = [res_users.create(vals).id for vals in test_users]
|
||||
domain = [('id', 'in', user_ids)]
|
||||
|
||||
# group on local char field without domain and without active_test (-> empty WHERE clause)
|
||||
groups_data = self.res_users.read_group(cr, uid, [], fields=['login'], groupby=['login'], orderby='login DESC', context={'active_test': False})
|
||||
groups_data = res_users.with_context(active_test=False).read_group([], fields=['login'], groupby=['login'], orderby='login DESC')
|
||||
self.assertGreater(len(groups_data), 6, "Incorrect number of results when grouping on a field")
|
||||
|
||||
# group on local char field with limit
|
||||
groups_data = self.res_users.read_group(cr, uid, domain, fields=['login'], groupby=['login'], orderby='login DESC', limit=3, offset=3)
|
||||
groups_data = res_users.read_group(domain, fields=['login'], groupby=['login'], orderby='login DESC', limit=3, offset=3)
|
||||
self.assertEqual(len(groups_data), 3, "Incorrect number of results when grouping on a field with limit")
|
||||
self.assertEqual(['bob', 'alice2', 'alice'], [g['login'] for g in groups_data], 'Result mismatch')
|
||||
|
||||
# group on inherited char field, aggregate on int field (second groupby ignored on purpose)
|
||||
groups_data = self.res_users.read_group(cr, uid, domain, fields=['name', 'color', 'function'], groupby=['function', 'login'])
|
||||
groups_data = res_users.read_group(domain, fields=['name', 'color', 'function'], groupby=['function', 'login'])
|
||||
self.assertEqual(len(groups_data), 3, "Incorrect number of results when grouping on a field")
|
||||
self.assertEqual(['5$ Wrench', 'Eavesdropper', 'Friend'], [g['function'] for g in groups_data], 'incorrect read_group order')
|
||||
for group_data in groups_data:
|
||||
@@ -313,210 +328,186 @@ class test_base(common.TransactionCase):
|
||||
self.assertEqual(group_data['color'], 3, "Incorrect sum for aggregated data for the column 'color'")
|
||||
|
||||
# group on inherited char field, reverse order
|
||||
groups_data = self.res_users.read_group(cr, uid, domain, fields=['name', 'color'], groupby='name', orderby='name DESC')
|
||||
groups_data = res_users.read_group(domain, fields=['name', 'color'], groupby='name', orderby='name DESC')
|
||||
self.assertEqual(['Nab', 'Eve', 'Bob', 'Alice'], [g['name'] for g in groups_data], 'Incorrect ordering of the list')
|
||||
|
||||
# group on int field, default ordering
|
||||
groups_data = self.res_users.read_group(cr, uid, domain, fields=['color'], groupby='color')
|
||||
groups_data = res_users.read_group(domain, fields=['color'], groupby='color')
|
||||
self.assertEqual([-3, 0, 1, 2, 3, 6], [g['color'] for g in groups_data], 'Incorrect ordering of the list')
|
||||
|
||||
# multi group, second level is int field, should still be summed in first level grouping
|
||||
groups_data = self.res_users.read_group(cr, uid, domain, fields=['name', 'color'], groupby=['name', 'color'], orderby='name DESC')
|
||||
groups_data = res_users.read_group(domain, fields=['name', 'color'], groupby=['name', 'color'], orderby='name DESC')
|
||||
self.assertEqual(['Nab', 'Eve', 'Bob', 'Alice'], [g['name'] for g in groups_data], 'Incorrect ordering of the list')
|
||||
self.assertEqual([3, 3, 2, 1], [g['color'] for g in groups_data], 'Incorrect ordering of the list')
|
||||
|
||||
# group on inherited char field, multiple orders with directions
|
||||
groups_data = self.res_users.read_group(cr, uid, domain, fields=['name', 'color'], groupby='name', orderby='color DESC, name')
|
||||
groups_data = res_users.read_group(domain, fields=['name', 'color'], groupby='name', orderby='color DESC, name')
|
||||
self.assertEqual(len(groups_data), 4, "Incorrect number of results when grouping on a field")
|
||||
self.assertEqual(['Eve', 'Nab', 'Bob', 'Alice'], [g['name'] for g in groups_data], 'Incorrect ordering of the list')
|
||||
self.assertEqual([1, 2, 1, 2], [g['name_count'] for g in groups_data], 'Incorrect number of results')
|
||||
|
||||
# group on inherited date column (res_partner.date) -> Year-Month, default ordering
|
||||
groups_data = self.res_users.read_group(cr, uid, domain, fields=['function', 'color', 'date'], groupby=['date'])
|
||||
groups_data = res_users.read_group(domain, fields=['function', 'color', 'date'], groupby=['date'])
|
||||
self.assertEqual(len(groups_data), 4, "Incorrect number of results when grouping on a field")
|
||||
self.assertEqual(['January 2014', 'September 2014', 'January 2015', 'March 2015'], [g['date'] for g in groups_data], 'Incorrect ordering of the list')
|
||||
self.assertEqual([1, 1, 1, 3], [g['date_count'] for g in groups_data], 'Incorrect number of results')
|
||||
|
||||
# group on inherited date column (res_partner.date) -> Year-Month, custom order
|
||||
groups_data = self.res_users.read_group(cr, uid, domain, fields=['function', 'color', 'date'], groupby=['date'], orderby='date DESC')
|
||||
groups_data = res_users.read_group(domain, fields=['function', 'color', 'date'], groupby=['date'], orderby='date DESC')
|
||||
self.assertEqual(len(groups_data), 4, "Incorrect number of results when grouping on a field")
|
||||
self.assertEqual(['March 2015', 'January 2015', 'September 2014', 'January 2014'], [g['date'] for g in groups_data], 'Incorrect ordering of the list')
|
||||
self.assertEqual([3, 1, 1, 1], [g['date_count'] for g in groups_data], 'Incorrect number of results')
|
||||
|
||||
# group on inherited many2one (res_partner.title), default order
|
||||
groups_data = self.res_users.read_group(cr, uid, domain, fields=['function', 'color', 'title'], groupby=['title'])
|
||||
groups_data = res_users.read_group(domain, fields=['function', 'color', 'title'], groupby=['title'])
|
||||
self.assertEqual(len(groups_data), 2, "Incorrect number of results when grouping on a field")
|
||||
# m2o is returned as a (id, label) pair
|
||||
self.assertEqual([(title_lady, 'Lady...'), (title_sir, 'Sir...')], [g['title'] for g in groups_data], 'Incorrect ordering of the list')
|
||||
self.assertEqual([(title_lady.id, 'Lady...'), (title_sir.id, 'Sir...')], [g['title'] for g in groups_data], 'Incorrect ordering of the list')
|
||||
self.assertEqual([4, 2], [g['title_count'] for g in groups_data], 'Incorrect number of results')
|
||||
self.assertEqual([10, -1], [g['color'] for g in groups_data], 'Incorrect aggregation of int column')
|
||||
|
||||
# group on inherited many2one (res_partner.title), reversed natural order
|
||||
groups_data = self.res_users.read_group(cr, uid, domain, fields=['function', 'color', 'title'], groupby=['title'], orderby="title desc")
|
||||
groups_data = res_users.read_group(domain, fields=['function', 'color', 'title'], groupby=['title'], orderby="title desc")
|
||||
self.assertEqual(len(groups_data), 2, "Incorrect number of results when grouping on a field")
|
||||
# m2o is returned as a (id, label) pair
|
||||
self.assertEqual([(title_sir, 'Sir...'), (title_lady, 'Lady...')], [g['title'] for g in groups_data], 'Incorrect ordering of the list')
|
||||
self.assertEqual([(title_sir.id, 'Sir...'), (title_lady.id, 'Lady...')], [g['title'] for g in groups_data], 'Incorrect ordering of the list')
|
||||
self.assertEqual([2, 4], [g['title_count'] for g in groups_data], 'Incorrect number of results')
|
||||
self.assertEqual([-1, 10], [g['color'] for g in groups_data], 'Incorrect aggregation of int column')
|
||||
|
||||
# group on inherited many2one (res_partner.title), multiple orders with m2o in second position
|
||||
groups_data = self.res_users.read_group(cr, uid, domain, fields=['function', 'color', 'title'], groupby=['title'], orderby="color desc, title desc")
|
||||
groups_data = res_users.read_group(domain, fields=['function', 'color', 'title'], groupby=['title'], orderby="color desc, title desc")
|
||||
self.assertEqual(len(groups_data), 2, "Incorrect number of results when grouping on a field")
|
||||
# m2o is returned as a (id, label) pair
|
||||
self.assertEqual([(title_lady, 'Lady...'), (title_sir, 'Sir...')], [g['title'] for g in groups_data], 'Incorrect ordering of the result')
|
||||
self.assertEqual([(title_lady.id, 'Lady...'), (title_sir.id, 'Sir...')], [g['title'] for g in groups_data], 'Incorrect ordering of the result')
|
||||
self.assertEqual([4, 2], [g['title_count'] for g in groups_data], 'Incorrect number of results')
|
||||
self.assertEqual([10, -1], [g['color'] for g in groups_data], 'Incorrect aggregation of int column')
|
||||
|
||||
# group on inherited many2one (res_partner.title), ordered by other inherited field (color)
|
||||
groups_data = self.res_users.read_group(cr, uid, domain, fields=['function', 'color', 'title'], groupby=['title'], orderby='color')
|
||||
groups_data = res_users.read_group(domain, fields=['function', 'color', 'title'], groupby=['title'], orderby='color')
|
||||
self.assertEqual(len(groups_data), 2, "Incorrect number of results when grouping on a field")
|
||||
# m2o is returned as a (id, label) pair
|
||||
self.assertEqual([(title_sir, 'Sir...'), (title_lady, 'Lady...')], [g['title'] for g in groups_data], 'Incorrect ordering of the list')
|
||||
self.assertEqual([(title_sir.id, 'Sir...'), (title_lady.id, 'Lady...')], [g['title'] for g in groups_data], 'Incorrect ordering of the list')
|
||||
self.assertEqual([2, 4], [g['title_count'] for g in groups_data], 'Incorrect number of results')
|
||||
self.assertEqual([-1, 10], [g['color'] for g in groups_data], 'Incorrect aggregation of int column')
|
||||
|
||||
|
||||
class test_partner_recursion(common.TransactionCase):
|
||||
class TestPartnerRecursion(TransactionCase):
|
||||
|
||||
def setUp(self):
|
||||
super(test_partner_recursion,self).setUp()
|
||||
self.res_partner = self.registry('res.partner')
|
||||
cr, uid = self.cr, self.uid
|
||||
self.p1 = self.res_partner.name_create(cr, uid, 'Elmtree')[0]
|
||||
self.p2 = self.res_partner.create(cr, uid, {'name': 'Elmtree Child 1', 'parent_id': self.p1})
|
||||
self.p3 = self.res_partner.create(cr, uid, {'name': 'Elmtree Grand-Child 1.1', 'parent_id': self.p2})
|
||||
super(TestPartnerRecursion,self).setUp()
|
||||
res_partner = self.env['res.partner']
|
||||
self.p1 = res_partner.browse(res_partner.name_create('Elmtree')[0])
|
||||
self.p2 = res_partner.create({'name': 'Elmtree Child 1', 'parent_id': self.p1.id})
|
||||
self.p3 = res_partner.create({'name': 'Elmtree Grand-Child 1.1', 'parent_id': self.p2.id})
|
||||
|
||||
# split 101, 102, 103 tests to force SQL rollback between them
|
||||
|
||||
def test_101_res_partner_recursion(self):
|
||||
cr, uid, p1, p3 = self.cr, self.uid, self.p1, self.p3
|
||||
self.assertRaises(ValidationError, self.res_partner.write, cr, uid, [p1], {'parent_id': p3})
|
||||
with self.assertRaises(ValidationError):
|
||||
self.p1.write({'parent_id': self.p3.id})
|
||||
|
||||
def test_102_res_partner_recursion(self):
|
||||
cr, uid, p2, p3 = self.cr, self.uid, self.p2, self.p3
|
||||
self.assertRaises(ValidationError, self.res_partner.write, cr, uid, [p2], {'parent_id': p3})
|
||||
with self.assertRaises(ValidationError):
|
||||
self.p2.write({'parent_id': self.p3.id})
|
||||
|
||||
def test_103_res_partner_recursion(self):
|
||||
cr, uid, p3 = self.cr, self.uid, self.p3
|
||||
self.assertRaises(ValidationError, self.res_partner.write, cr, uid, [p3], {'parent_id': p3})
|
||||
with self.assertRaises(ValidationError):
|
||||
self.p3.write({'parent_id': self.p3.id})
|
||||
|
||||
def test_104_res_partner_recursion_indirect_cycle(self):
|
||||
""" Indirect hacky write to create cycle in children """
|
||||
cr, uid, p2, p3 = self.cr, self.uid, self.p2, self.p3
|
||||
p3b = self.res_partner.create(cr, uid, {'name': 'Elmtree Grand-Child 1.2', 'parent_id': self.p2})
|
||||
self.assertRaises(ValidationError, self.res_partner.write, cr, uid, [p2],
|
||||
{'child_ids': [(1, p3, {'parent_id': p3b}), (1, p3b, {'parent_id': p3})]})
|
||||
p3b = self.p1.create({'name': 'Elmtree Grand-Child 1.2', 'parent_id': self.p2.id})
|
||||
with self.assertRaises(ValidationError):
|
||||
self.p2.write({'child_ids': [(1, self.p3.id, {'parent_id': p3b.id}),
|
||||
(1, p3b.id, {'parent_id': self.p3.id})]})
|
||||
|
||||
def test_110_res_partner_recursion_multi_update(self):
|
||||
""" multi-write on several partners in same hierarchy must not trigger a false cycle detection """
|
||||
cr, uid, p1, p2, p3 = self.cr, self.uid, self.p1, self.p2, self.p3
|
||||
self.assertTrue(self.res_partner.write(cr, uid, [p1,p2,p3], {'phone': '123456'}))
|
||||
ps = self.p1 + self.p2 + self.p3
|
||||
self.assertTrue(ps.write({'phone': '123456'}))
|
||||
|
||||
class test_translation(common.TransactionCase):
|
||||
|
||||
class TestParentStore(TransactionCase):
|
||||
""" Verify that parent_store computation is done right """
|
||||
|
||||
def setUp(self):
|
||||
super(test_translation, self).setUp()
|
||||
self.res_category = self.registry('res.partner.category')
|
||||
self.ir_translation = self.registry('ir.translation')
|
||||
cr, uid = self.cr, self.uid
|
||||
self.registry('ir.translation').load_module_terms(cr, ['base'], ['fr_FR'])
|
||||
self.cat_id = self.res_category.create(cr, uid, {'name': 'Customers'})
|
||||
self.ir_translation.create(cr, uid, {'name': 'res.partner.category,name', 'module':'base',
|
||||
'value': 'Clients', 'res_id': self.cat_id, 'lang':'fr_FR', 'state':'translated', 'type': 'model'})
|
||||
super(TestParentStore, self).setUp()
|
||||
# pretend the pool has finished loading to avoid deferring parent_store computation
|
||||
self.patch(self.registry, '_init', False)
|
||||
|
||||
def test_101_create_translated_record(self):
|
||||
cr, uid = self.cr, self.uid
|
||||
|
||||
no_context_cat = self.res_category.browse(cr, uid, self.cat_id)
|
||||
self.assertEqual(no_context_cat.name, 'Customers', "Error in basic name_get")
|
||||
# force res_partner_category.copy() to copy children
|
||||
category = self.env['res.partner.category']
|
||||
self.patch(category._fields['child_ids'], 'copy', True)
|
||||
self.patch(category._columns['child_ids'], 'copy', True)
|
||||
|
||||
fr_context_cat = self.res_category.browse(cr, uid, self.cat_id, context={'lang':'fr_FR'})
|
||||
self.assertEqual(fr_context_cat.name, 'Clients', "Translation not found")
|
||||
# setup categories
|
||||
self.root = category.create({'name': 'Root category'})
|
||||
self.cat0 = category.create({'name': 'Parent category', 'parent_id': self.root.id})
|
||||
self.cat1 = category.create({'name': 'Child 1', 'parent_id': self.cat0.id})
|
||||
self.cat2 = category.create({'name': 'Child 2', 'parent_id': self.cat0.id})
|
||||
self.cat21 = category.create({'name': 'Child 2-1', 'parent_id': self.cat2.id})
|
||||
|
||||
def test_102_duplicate_record(self):
|
||||
cr, uid = self.cr, self.uid
|
||||
self.new_cat_id = self.res_category.copy(cr, uid, self.cat_id, context={'lang':'fr_FR'})
|
||||
def test_duplicate_parent(self):
|
||||
""" Duplicate the parent category and verify that the children have been duplicated too """
|
||||
new_cat0 = self.cat0.copy()
|
||||
new_struct = new_cat0.search([('parent_id', 'child_of', new_cat0.id)])
|
||||
self.assertEqual(len(new_struct), 4, "After duplication, the new object must have the childs records")
|
||||
old_struct = new_cat0.search([('parent_id', 'child_of', self.cat0.id)])
|
||||
self.assertEqual(len(old_struct), 4, "After duplication, previous record must have old childs records only")
|
||||
self.assertFalse(new_struct & old_struct, "After duplication, nodes should not be mixed")
|
||||
|
||||
no_context_cat = self.res_category.browse(cr, uid, self.new_cat_id)
|
||||
self.assertEqual(no_context_cat.name, 'Customers', "Duplication did not set untranslated value")
|
||||
def test_duplicate_children_01(self):
|
||||
""" Duplicate the children then reassign them to the new parent (1st method). """
|
||||
new_cat1 = self.cat1.copy()
|
||||
new_cat2 = self.cat2.copy()
|
||||
new_cat0 = self.cat0.copy({'child_ids': []})
|
||||
(new_cat1 + new_cat2).write({'parent_id': new_cat0.id})
|
||||
new_struct = new_cat0.search([('parent_id', 'child_of', new_cat0.id)])
|
||||
self.assertEqual(len(new_struct), 4, "After duplication, the new object must have the childs records")
|
||||
old_struct = new_cat0.search([('parent_id', 'child_of', self.cat0.id)])
|
||||
self.assertEqual(len(old_struct), 4, "After duplication, previous record must have old childs records only")
|
||||
self.assertFalse(new_struct & old_struct, "After duplication, nodes should not be mixed")
|
||||
|
||||
fr_context_cat = self.res_category.browse(cr, uid, self.new_cat_id, context={'lang':'fr_FR'})
|
||||
self.assertEqual(fr_context_cat.name, 'Clients', "Did not found translation for initial value")
|
||||
def test_duplicate_children_02(self):
|
||||
""" Duplicate the children then reassign them to the new parent (2nd method). """
|
||||
new_cat1 = self.cat1.copy()
|
||||
new_cat2 = self.cat2.copy()
|
||||
new_cat0 = self.cat0.copy({'child_ids': [(6, 0, (new_cat1 + new_cat2).ids)]})
|
||||
new_struct = new_cat0.search([('parent_id', 'child_of', new_cat0.id)])
|
||||
self.assertEqual(len(new_struct), 4, "After duplication, the new object must have the childs records")
|
||||
old_struct = new_cat0.search([('parent_id', 'child_of', self.cat0.id)])
|
||||
self.assertEqual(len(old_struct), 4, "After duplication, previous record must have old childs records only")
|
||||
self.assertFalse(new_struct & old_struct, "After duplication, nodes should not be mixed")
|
||||
|
||||
def test_103_duplicate_record_fr(self):
|
||||
cr, uid = self.cr, self.uid
|
||||
self.new_fr_cat_id = self.res_category.copy(cr, uid, self.cat_id, default={'name': 'Clients (copie)'}, context={'lang':'fr_FR'})
|
||||
def test_duplicate_children_03(self):
|
||||
""" Duplicate the children then reassign them to the new parent (3rd method). """
|
||||
new_cat1 = self.cat1.copy()
|
||||
new_cat2 = self.cat2.copy()
|
||||
new_cat0 = self.cat0.copy({'child_ids': []})
|
||||
new_cat0.write({'child_ids': [(4, new_cat1.id), (4, new_cat2.id)]})
|
||||
new_struct = new_cat0.search([('parent_id', 'child_of', new_cat0.id)])
|
||||
self.assertEqual(len(new_struct), 4, "After duplication, the new object must have the childs records")
|
||||
old_struct = new_cat0.search([('parent_id', 'child_of', self.cat0.id)])
|
||||
self.assertEqual(len(old_struct), 4, "After duplication, previous record must have old childs records only")
|
||||
self.assertFalse(new_struct & old_struct, "After duplication, nodes should not be mixed")
|
||||
|
||||
no_context_cat = self.res_category.browse(cr, uid, self.new_fr_cat_id)
|
||||
self.assertEqual(no_context_cat.name, 'Customers', "Duplication erased original untranslated value")
|
||||
|
||||
fr_context_cat = self.res_category.browse(cr, uid, self.new_fr_cat_id, context={'lang':'fr_FR'})
|
||||
self.assertEqual(fr_context_cat.name, 'Clients (copie)', "Did not used default value for translated value")
|
||||
class TestGroups(TransactionCase):
|
||||
|
||||
def test_104_orderby_translated_field(self):
|
||||
""" Test search ordered by a translated field. """
|
||||
# create a category with a French translation
|
||||
category = self.env['res.partner.category'].create({'name': 'Padawans'})
|
||||
category_fr = category.with_context(lang='fr_FR')
|
||||
category_fr.write({'name': 'Apprentis'})
|
||||
# search for categories, and sort them by (translated) name
|
||||
categories = category_fr.search([('id', 'in', [self.cat_id, category.id])], order='name')
|
||||
self.assertEqual(categories.ids, [category.id, self.cat_id],
|
||||
"Search ordered by translated name should return Padawans (Apprentis) before Customers (Clients)")
|
||||
def test_res_groups_fullname_search(self):
|
||||
all_groups = self.env['res.groups'].search([])
|
||||
|
||||
test_state = None
|
||||
#: Stores state information across multiple test classes
|
||||
def setUpModule():
|
||||
global test_state
|
||||
test_state = {}
|
||||
def tearDownModule():
|
||||
global test_state
|
||||
test_state = None
|
||||
groups = all_groups.search([('full_name', 'like', '%Sale%')])
|
||||
self.assertItemsEqual(groups.ids, [g.id for g in all_groups if 'Sale' in g.full_name],
|
||||
"did not match search for 'Sale'")
|
||||
|
||||
class TestPhaseInstall00(unittest.TestCase):
|
||||
"""
|
||||
WARNING: Relies on tests being run in alphabetical order
|
||||
"""
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.state = None
|
||||
groups = all_groups.search([('full_name', 'like', '%Technical%')])
|
||||
self.assertItemsEqual(groups.ids, [g.id for g in all_groups if 'Technical' in g.full_name],
|
||||
"did not match search for 'Technical'")
|
||||
|
||||
def test_00_setup(self):
|
||||
type(self).state = 'init'
|
||||
groups = all_groups.search([('full_name', 'like', '%Sales /%')])
|
||||
self.assertItemsEqual(groups.ids, [g.id for g in all_groups if 'Sales /' in g.full_name],
|
||||
"did not match search for 'Sales /'")
|
||||
|
||||
@common.at_install(False)
|
||||
def test_01_no_install(self):
|
||||
type(self).state = 'error'
|
||||
|
||||
def test_02_check(self):
|
||||
self.assertEqual(
|
||||
self.state, 'init',
|
||||
"Testcase state should not have been transitioned from 00")
|
||||
|
||||
class TestPhaseInstall01(unittest.TestCase):
|
||||
at_install = False
|
||||
|
||||
def test_default_norun(self):
|
||||
self.fail("An unmarket test in a non-at-install case should not run")
|
||||
|
||||
@common.at_install(True)
|
||||
def test_set_run(self):
|
||||
test_state['set_at_install'] = True
|
||||
|
||||
class TestPhaseInstall02(unittest.TestCase):
|
||||
"""
|
||||
Can't put the check for test_set_run in the same class: if
|
||||
@common.at_install does not work for test_set_run, it won't work for
|
||||
the other one either. Thus move checking of whether test_set_run has
|
||||
correctly run indeed to a separate class.
|
||||
|
||||
Warning: relies on *classes* being run in alphabetical order in test
|
||||
modules
|
||||
"""
|
||||
def test_check_state(self):
|
||||
self.assertTrue(
|
||||
test_state.get('set_at_install'),
|
||||
"The flag should be set if local overriding of runstate")
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
groups = all_groups.search([('full_name', 'in', ['Administration / Access Rights','Contact Creation'])])
|
||||
self.assertTrue(groups, "did not match search for 'Administration / Access Rights' and 'Contact Creation'")
|
||||
|
||||
@@ -1,86 +1,72 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import unittest
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from openerp.tests import common
|
||||
from odoo.tests import common
|
||||
|
||||
class test_single_transaction_case(common.SingleTransactionCase):
|
||||
|
||||
class TestSingleTransactionCase(common.SingleTransactionCase):
|
||||
"""
|
||||
Check the whole-class transaction behavior of SingleTransactionCase.
|
||||
"""
|
||||
|
||||
def test_00(self):
|
||||
"""Create a partner."""
|
||||
cr, uid = self.cr, self.uid
|
||||
self.registry('res.partner').create(cr, uid, {'name': 'test_per_class_teardown_partner'})
|
||||
ids = self.registry('res.partner').search(cr, uid, [('name', '=', 'test_per_class_teardown_partner')])
|
||||
self.assertEqual(1, len(ids), "Test partner not found.")
|
||||
""" Create a partner. """
|
||||
self.env['res.partner'].create({'name': 'test_per_class_teardown_partner'})
|
||||
partners = self.env['res.partner'].search([('name', '=', 'test_per_class_teardown_partner')])
|
||||
self.assertEqual(1, len(partners), "Test partner not found.")
|
||||
|
||||
def test_01(self):
|
||||
"""Find the created partner."""
|
||||
cr, uid = self.cr, self.uid
|
||||
ids = self.registry('res.partner').search(cr, uid, [('name', '=', 'test_per_class_teardown_partner')])
|
||||
self.assertEqual(1, len(ids), "Test partner not found.")
|
||||
""" Find the created partner. """
|
||||
partners = self.env['res.partner'].search([('name', '=', 'test_per_class_teardown_partner')])
|
||||
self.assertEqual(1, len(partners), "Test partner not found.")
|
||||
|
||||
def test_20a(self):
|
||||
""" Create a partner with a XML ID """
|
||||
cr, uid = self.cr, self.uid
|
||||
res_partner = self.registry('res.partner')
|
||||
ir_model_data = self.registry('ir.model.data')
|
||||
pid, _ = res_partner.name_create(cr, uid, 'Mr Blue')
|
||||
ir_model_data.create(cr, uid, {'name': 'test_partner_blue',
|
||||
'module': 'base',
|
||||
'model': 'res.partner',
|
||||
'res_id': pid})
|
||||
pid, _ = self.env['res.partner'].name_create('Mr Blue')
|
||||
self.env['ir.model.data'].create({'name': 'test_partner_blue',
|
||||
'module': 'base',
|
||||
'model': 'res.partner',
|
||||
'res_id': pid})
|
||||
|
||||
def test_20b(self):
|
||||
""" Resolve xml id with ref() and browse_ref() """
|
||||
cr, uid = self.cr, self.uid
|
||||
res_partner = self.registry('res.partner')
|
||||
xid = 'base.test_partner_blue'
|
||||
p_ref = self.ref(xid)
|
||||
self.assertTrue(p_ref, "ref() should resolve xid to database ID")
|
||||
partner = res_partner.browse(cr, uid, p_ref)
|
||||
p_browse_ref = self.browse_ref(xid)
|
||||
self.assertEqual(partner, p_browse_ref, "browse_ref() should resolve xid to browse records")
|
||||
|
||||
partner = self.env.ref(xid)
|
||||
pid = self.ref(xid)
|
||||
self.assertTrue(pid, "ref() should resolve xid to database ID")
|
||||
self.assertEqual(pid, partner.id, "ref() is not consistent with env.ref()")
|
||||
partner2 = self.browse_ref(xid)
|
||||
self.assertEqual(partner, partner2, "browse_ref() should resolve xid to browse records")
|
||||
|
||||
|
||||
class test_transaction_case(common.TransactionCase):
|
||||
class TestTransactionCase(common.TransactionCase):
|
||||
"""
|
||||
Check the per-method transaction behavior of TransactionCase.
|
||||
"""
|
||||
|
||||
def test_00(self):
|
||||
"""Create a partner."""
|
||||
cr, uid = self.cr, self.uid
|
||||
ids = self.registry('res.partner').search(cr, uid, [('name', '=', 'test_per_class_teardown_partner')])
|
||||
self.assertEqual(0, len(ids), "Test partner found.")
|
||||
self.registry('res.partner').create(cr, uid, {'name': 'test_per_class_teardown_partner'})
|
||||
ids = self.registry('res.partner').search(cr, uid, [('name', '=', 'test_per_class_teardown_partner')])
|
||||
self.assertEqual(1, len(ids), "Test partner not found.")
|
||||
""" Create a partner. """
|
||||
partners = self.env['res.partner'].search([('name', '=', 'test_per_class_teardown_partner')])
|
||||
self.assertEqual(0, len(partners), "Test partner found.")
|
||||
self.env['res.partner'].create({'name': 'test_per_class_teardown_partner'})
|
||||
partners = self.env['res.partner'].search([('name', '=', 'test_per_class_teardown_partner')])
|
||||
self.assertEqual(1, len(partners), "Test partner not found.")
|
||||
|
||||
def test_01(self):
|
||||
"""Don't find the created partner."""
|
||||
cr, uid = self.cr, self.uid
|
||||
ids = self.registry('res.partner').search(cr, uid, [('name', '=', 'test_per_class_teardown_partner')])
|
||||
self.assertEqual(0, len(ids), "Test partner found.")
|
||||
|
||||
""" Don't find the created partner. """
|
||||
partners = self.env['res.partner'].search([('name', '=', 'test_per_class_teardown_partner')])
|
||||
self.assertEqual(0, len(partners), "Test partner found.")
|
||||
|
||||
def test_20a(self):
|
||||
""" Create a partner with a XML ID then resolve xml id with ref() and browse_ref() """
|
||||
cr, uid = self.cr, self.uid
|
||||
res_partner = self.registry('res.partner')
|
||||
ir_model_data = self.registry('ir.model.data')
|
||||
pid, _ = res_partner.name_create(cr, uid, 'Mr Yellow')
|
||||
ir_model_data.create(cr, uid, {'name': 'test_partner_yellow',
|
||||
'module': 'base',
|
||||
'model': 'res.partner',
|
||||
'res_id': pid})
|
||||
pid, _ = self.env['res.partner'].name_create('Mr Yellow')
|
||||
self.env['ir.model.data'].create({'name': 'test_partner_yellow',
|
||||
'module': 'base',
|
||||
'model': 'res.partner',
|
||||
'res_id': pid})
|
||||
xid = 'base.test_partner_yellow'
|
||||
p_ref = self.ref(xid)
|
||||
self.assertEquals(p_ref, pid, "ref() should resolve xid to database ID")
|
||||
partner = res_partner.browse(cr, uid, pid)
|
||||
p_browse_ref = self.browse_ref(xid)
|
||||
self.assertEqual(partner, p_browse_ref, "browse_ref() should resolve xid to browse records")
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
partner = self.env.ref(xid)
|
||||
pid = self.ref(xid)
|
||||
self.assertEquals(pid, partner.id, "ref() should resolve xid to database ID")
|
||||
partner2 = self.browse_ref(xid)
|
||||
self.assertEqual(partner, partner2, "browse_ref() should resolve xid to browse records")
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import unittest
|
||||
|
||||
import openerp
|
||||
from openerp.tools.misc import mute_logger
|
||||
from openerp.tests import common
|
||||
import odoo
|
||||
from odoo.tests import common
|
||||
from odoo.tools.misc import mute_logger
|
||||
|
||||
ADMIN_USER_ID = common.ADMIN_USER_ID
|
||||
|
||||
def registry():
|
||||
return openerp.modules.registry.RegistryManager.get(common.get_db_name())
|
||||
return odoo.registry(common.get_db_name())
|
||||
|
||||
|
||||
class test_cr_execute(unittest.TestCase):
|
||||
class TestExecute(unittest.TestCase):
|
||||
""" Try cr.execute with wrong parameters """
|
||||
|
||||
@mute_logger('openerp.sql_db')
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,177 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from math import log10
|
||||
|
||||
from odoo.tests.common import TransactionCase
|
||||
from odoo.tools import float_compare, float_is_zero, float_repr, float_round
|
||||
|
||||
|
||||
class TestFloatPrecision(TransactionCase):
|
||||
""" Tests on float precision. """
|
||||
|
||||
def test_rounding_02(self):
|
||||
""" Test rounding methods with 2 digits. """
|
||||
currency = self.env.ref('base.EUR')
|
||||
|
||||
def try_round(amount, expected):
|
||||
digits = max(0, -int(log10(currency.rounding)))
|
||||
result = float_repr(currency.round(amount), precision_digits=digits)
|
||||
self.assertEqual(result, expected, 'Rounding error: got %s, expected %s' % (result, expected))
|
||||
|
||||
try_round(2.674,'2.67')
|
||||
try_round(2.675,'2.68') # in Python 2.7.2, round(2.675,2) gives 2.67
|
||||
try_round(-2.675,'-2.68') # in Python 2.7.2, round(2.675,2) gives 2.67
|
||||
try_round(0.001,'0.00')
|
||||
try_round(-0.001,'-0.00')
|
||||
try_round(0.0049,'0.00') # 0.0049 is closer to 0 than to 0.01, so should round down
|
||||
try_round(0.005,'0.01') # the rule is to round half away from zero
|
||||
try_round(-0.005,'-0.01') # the rule is to round half away from zero
|
||||
|
||||
def try_zero(amount, expected):
|
||||
self.assertEqual(currency.is_zero(amount), expected,
|
||||
"Rounding error: %s should be zero!" % amount)
|
||||
|
||||
try_zero(0.01, False)
|
||||
try_zero(-0.01, False)
|
||||
try_zero(0.001, True)
|
||||
try_zero(-0.001, True)
|
||||
try_zero(0.0046, True)
|
||||
try_zero(-0.0046, True)
|
||||
try_zero(2.68-2.675, False) # 2.68 - 2.675 = 0.005 -> rounds to 0.01
|
||||
try_zero(2.68-2.676, True) # 2.68 - 2.675 = 0.004 -> rounds to 0.0
|
||||
try_zero(2.676-2.68, True) # 2.675 - 2.68 = -0.004 -> rounds to -0.0
|
||||
try_zero(2.675-2.68, False) # 2.675 - 2.68 = -0.005 -> rounds to -0.01
|
||||
|
||||
def try_compare(amount1, amount2, expected):
|
||||
self.assertEqual(currency.compare_amounts(amount1, amount2), expected,
|
||||
"Rounding error, compare_amounts(%s,%s) should be %s" % (amount1, amount2, expected))
|
||||
|
||||
try_compare(0.001, 0.001, 0)
|
||||
try_compare(-0.001, -0.001, 0)
|
||||
try_compare(0.001, 0.002, 0)
|
||||
try_compare(-0.001, -0.002, 0)
|
||||
try_compare(2.675, 2.68, 0)
|
||||
try_compare(2.676, 2.68, 0)
|
||||
try_compare(-2.676, -2.68, 0)
|
||||
try_compare(2.674, 2.68, -1)
|
||||
try_compare(-2.674, -2.68, 1)
|
||||
try_compare(3, 2.68, 1)
|
||||
try_compare(-3, -2.68, -1)
|
||||
try_compare(0.01, 0, 1)
|
||||
try_compare(-0.01, 0, -1)
|
||||
|
||||
def test_rounding_03(self):
|
||||
""" Test rounding methods with 3 digits. """
|
||||
|
||||
def try_round(amount, expected, digits=3, method='HALF-UP'):
|
||||
value = float_round(amount, precision_digits=digits, rounding_method=method)
|
||||
result = float_repr(value, precision_digits=digits)
|
||||
self.assertEqual(result, expected, 'Rounding error: got %s, expected %s' % (result, expected))
|
||||
|
||||
try_round(2.6745, '2.675')
|
||||
try_round(-2.6745, '-2.675')
|
||||
try_round(2.6744, '2.674')
|
||||
try_round(-2.6744, '-2.674')
|
||||
try_round(0.0004, '0.000')
|
||||
try_round(-0.0004, '-0.000')
|
||||
try_round(357.4555, '357.456')
|
||||
try_round(-357.4555, '-357.456')
|
||||
try_round(457.4554, '457.455')
|
||||
try_round(-457.4554, '-457.455')
|
||||
|
||||
# Try some rounding value with rounding method UP instead of HALF-UP
|
||||
# We use 8.175 because when normalizing 8.175 with precision_digits=3 it gives
|
||||
# us 8175,0000000001234 as value, and if not handle correctly the rounding UP
|
||||
# value will be incorrect (should be 8,175 and not 8,176)
|
||||
try_round(8.175, '8.175', method='UP')
|
||||
try_round(8.1751, '8.176', method='UP')
|
||||
try_round(-8.175, '-8.175', method='UP')
|
||||
try_round(-8.1751, '-8.176', method='UP')
|
||||
try_round(-6.000, '-6.000', method='UP')
|
||||
try_round(1.8, '2', 0, method='UP')
|
||||
try_round(-1.8, '-2', 0, method='UP')
|
||||
|
||||
# Extended float range test, inspired by Cloves Almeida's test on bug #882036.
|
||||
fractions = [.0, .015, .01499, .675, .67499, .4555, .4555, .45555]
|
||||
expecteds = ['.00', '.02', '.01', '.68', '.67', '.46', '.456', '.4556']
|
||||
precisions = [2, 2, 2, 2, 2, 2, 3, 4]
|
||||
# Note: max precision for double floats is 53 bits of precision or
|
||||
# 17 significant decimal digits
|
||||
for magnitude in range(7):
|
||||
for i in xrange(len(fractions)):
|
||||
frac, exp, prec = fractions[i], expecteds[i], precisions[i]
|
||||
for sign in [-1,1]:
|
||||
for x in xrange(0,10000,97):
|
||||
n = x * 10**magnitude
|
||||
f = sign * (n + frac)
|
||||
f_exp = ('-' if f != 0 and sign == -1 else '') + str(n) + exp
|
||||
try_round(f, f_exp, digits=prec)
|
||||
|
||||
def try_zero(amount, expected):
|
||||
self.assertEqual(float_is_zero(amount, precision_digits=3), expected,
|
||||
"Rounding error: %s should be zero!" % amount)
|
||||
|
||||
try_zero(0.0002, True)
|
||||
try_zero(-0.0002, True)
|
||||
try_zero(0.00034, True)
|
||||
try_zero(0.0005, False)
|
||||
try_zero(-0.0005, False)
|
||||
try_zero(0.0008, False)
|
||||
try_zero(-0.0008, False)
|
||||
|
||||
def try_compare(amount1, amount2, expected):
|
||||
self.assertEqual(float_compare(amount1, amount2, precision_digits=3), expected,
|
||||
"Rounding error, compare_amounts(%s,%s) should be %s" % (amount1, amount2, expected))
|
||||
|
||||
try_compare(0.0003, 0.0004, 0)
|
||||
try_compare(-0.0003, -0.0004, 0)
|
||||
try_compare(0.0002, 0.0005, -1)
|
||||
try_compare(-0.0002, -0.0005, 1)
|
||||
try_compare(0.0009, 0.0004, 1)
|
||||
try_compare(-0.0009, -0.0004, -1)
|
||||
try_compare(557.4555, 557.4556, 0)
|
||||
try_compare(-557.4555, -557.4556, 0)
|
||||
try_compare(657.4444, 657.445, -1)
|
||||
try_compare(-657.4444, -657.445, 1)
|
||||
|
||||
# Rounding to unusual rounding units (e.g. coin values)
|
||||
def try_round(amount, expected, precision_rounding=None):
|
||||
value = float_round(amount, precision_rounding=precision_rounding)
|
||||
result = float_repr(value, precision_digits=2)
|
||||
self.assertEqual(result, expected, 'Rounding error: got %s, expected %s' % (result, expected))
|
||||
|
||||
try_round(-457.4554, '-457.45', precision_rounding=0.05)
|
||||
try_round(457.444, '457.50', precision_rounding=0.5)
|
||||
try_round(457.3, '455.00', precision_rounding=5)
|
||||
try_round(457.5, '460.00', precision_rounding=5)
|
||||
try_round(457.1, '456.00', precision_rounding=3)
|
||||
|
||||
def test_rounding_04(self):
|
||||
""" check that proper rounding is performed for float persistence """
|
||||
currency = self.env.ref('base.EUR')
|
||||
currency_rate = self.env['res.currency.rate']
|
||||
|
||||
def try_roundtrip(value, expected):
|
||||
rate = currency_rate.create({'name':'2000-01-01',
|
||||
'rate': value,
|
||||
'currency_id': currency.id})
|
||||
self.assertEqual(rate.rate, expected,
|
||||
'Roundtrip error: got %s back from db, expected %s' % (rate, expected))
|
||||
|
||||
# res.currency.rate uses 6 digits of precision by default
|
||||
try_roundtrip(2.6748955, 2.674896)
|
||||
try_roundtrip(-2.6748955, -2.674896)
|
||||
try_roundtrip(10000.999999, 10000.999999)
|
||||
try_roundtrip(-10000.999999, -10000.999999)
|
||||
|
||||
def test_rounding_invalid(self):
|
||||
""" verify that invalid parameters are forbidden """
|
||||
with self.assertRaises(AssertionError):
|
||||
float_is_zero(0.01, precision_digits=3, precision_rounding=0.01)
|
||||
|
||||
with self.assertRaises(AssertionError):
|
||||
float_compare(0.01, 0.02, precision_digits=3, precision_rounding=0.01)
|
||||
|
||||
with self.assertRaises(AssertionError):
|
||||
float_round(0.01, precision_digits=3, precision_rounding=0.01)
|
||||
@@ -1,16 +1,17 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import functools
|
||||
import unittest
|
||||
|
||||
from openerp.tools.func import compose
|
||||
from openerp.tools import frozendict
|
||||
from odoo.tools import frozendict
|
||||
from odoo.tools.func import compose
|
||||
|
||||
|
||||
class TestCompose(unittest.TestCase):
|
||||
def test_basic(self):
|
||||
str_add = compose(str, lambda a, b: a + b)
|
||||
self.assertEqual(
|
||||
str_add(1, 2),
|
||||
"3")
|
||||
self.assertEqual(str_add(1, 2), "3")
|
||||
|
||||
def test_decorator(self):
|
||||
""" ensure compose() can be partially applied as a decorator
|
||||
@@ -21,6 +22,7 @@ class TestCompose(unittest.TestCase):
|
||||
|
||||
self.assertEqual(mul(5, 42), u"210")
|
||||
|
||||
|
||||
class TestFrozendict(unittest.TestCase):
|
||||
def test_frozendict_immutable(self):
|
||||
""" Ensure that a frozendict is immutable. """
|
||||
|
||||
@@ -1,58 +1,50 @@
|
||||
import unittest
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from openerp.exceptions import ValidationError
|
||||
import openerp.tests.common as common
|
||||
from openerp.tools import mute_logger
|
||||
from odoo.exceptions import ValidationError
|
||||
from odoo.tools import mute_logger
|
||||
import odoo.tests.common as common
|
||||
import odoo.workflow
|
||||
|
||||
|
||||
class TestServerActionsBase(common.TransactionCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestServerActionsBase, self).setUp()
|
||||
cr, uid = self.cr, self.uid
|
||||
|
||||
# Models
|
||||
self.ir_actions_server = self.registry('ir.actions.server')
|
||||
self.ir_actions_client = self.registry('ir.actions.client')
|
||||
self.ir_values = self.registry('ir.values')
|
||||
self.ir_model = self.registry('ir.model')
|
||||
self.ir_model_fields = self.registry('ir.model.fields')
|
||||
self.res_partner = self.registry('res.partner')
|
||||
self.res_country = self.registry('res.country')
|
||||
|
||||
# Data on which we will run the server action
|
||||
self.test_country_id = self.res_country.create(cr, uid, {
|
||||
self.test_country = self.env['res.country'].create({
|
||||
'name': 'TestingCountry',
|
||||
'code': 'TY',
|
||||
'address_format': 'SuperFormat',
|
||||
})
|
||||
self.test_country = self.res_country.browse(cr, uid, self.test_country_id)
|
||||
self.test_partner_id = self.res_partner.create(cr, uid, {
|
||||
self.test_partner = self.env['res.partner'].create({
|
||||
'name': 'TestingPartner',
|
||||
'city': 'OrigCity',
|
||||
'country_id': self.test_country_id,
|
||||
'country_id': self.test_country.id,
|
||||
})
|
||||
self.test_partner = self.res_partner.browse(cr, uid, self.test_partner_id)
|
||||
self.context = {
|
||||
'active_id': self.test_partner_id,
|
||||
'active_model': 'res.partner',
|
||||
'active_id': self.test_partner.id,
|
||||
}
|
||||
|
||||
# Model data
|
||||
self.res_partner_model_id = self.ir_model.search(cr, uid, [('model', '=', 'res.partner')])[0]
|
||||
self.res_partner_name_field_id = self.ir_model_fields.search(cr, uid, [('model', '=', 'res.partner'), ('name', '=', 'name')])[0]
|
||||
self.res_partner_city_field_id = self.ir_model_fields.search(cr, uid, [('model', '=', 'res.partner'), ('name', '=', 'city')])[0]
|
||||
self.res_partner_country_field_id = self.ir_model_fields.search(cr, uid, [('model', '=', 'res.partner'), ('name', '=', 'country_id')])[0]
|
||||
self.res_partner_parent_field_id = self.ir_model_fields.search(cr, uid, [('model', '=', 'res.partner'), ('name', '=', 'parent_id')])[0]
|
||||
self.res_country_model_id = self.ir_model.search(cr, uid, [('model', '=', 'res.country')])[0]
|
||||
self.res_country_name_field_id = self.ir_model_fields.search(cr, uid, [('model', '=', 'res.country'), ('name', '=', 'name')])[0]
|
||||
self.res_country_code_field_id = self.ir_model_fields.search(cr, uid, [('model', '=', 'res.country'), ('name', '=', 'code')])[0]
|
||||
Model = self.env['ir.model']
|
||||
Fields = self.env['ir.model.fields']
|
||||
self.res_partner_model = Model.search([('model', '=', 'res.partner')])
|
||||
self.res_partner_name_field = Fields.search([('model', '=', 'res.partner'), ('name', '=', 'name')])
|
||||
self.res_partner_city_field = Fields.search([('model', '=', 'res.partner'), ('name', '=', 'city')])
|
||||
self.res_partner_country_field = Fields.search([('model', '=', 'res.partner'), ('name', '=', 'country_id')])
|
||||
self.res_partner_parent_field = Fields.search([('model', '=', 'res.partner'), ('name', '=', 'parent_id')])
|
||||
self.res_country_model = Model.search([('model', '=', 'res.country')])
|
||||
self.res_country_name_field = Fields.search([('model', '=', 'res.country'), ('name', '=', 'name')])
|
||||
self.res_country_code_field = Fields.search([('model', '=', 'res.country'), ('name', '=', 'code')])
|
||||
|
||||
# create server action to
|
||||
self.act_id = self.ir_actions_server.create(cr, uid, {
|
||||
self.action = self.env['ir.actions.server'].create({
|
||||
'name': 'TestAction',
|
||||
'condition': 'True',
|
||||
'model_id': self.res_partner_model_id,
|
||||
'model_id': self.res_partner_model.id,
|
||||
'state': 'code',
|
||||
'code': 'obj.write({"comment": "MyComment"})',
|
||||
})
|
||||
@@ -61,346 +53,318 @@ class TestServerActionsBase(common.TransactionCase):
|
||||
class TestServerActions(TestServerActionsBase):
|
||||
|
||||
def test_00_action(self):
|
||||
cr, uid = self.cr, self.uid
|
||||
|
||||
# Do: eval 'True' condition
|
||||
self.ir_actions_server.run(cr, uid, [self.act_id], self.context)
|
||||
self.test_partner.refresh()
|
||||
self.action.with_context(self.context).run()
|
||||
self.assertEqual(self.test_partner.comment, 'MyComment', 'ir_actions_server: invalid condition check')
|
||||
self.test_partner.write({'comment': False})
|
||||
|
||||
# Do: eval False condition, that should be considered as True (void = True)
|
||||
self.ir_actions_server.write(cr, uid, [self.act_id], {'condition': False})
|
||||
self.ir_actions_server.run(cr, uid, [self.act_id], self.context)
|
||||
self.test_partner.refresh()
|
||||
self.action.write({'condition': False})
|
||||
self.action.with_context(self.context).run()
|
||||
self.assertEqual(self.test_partner.comment, 'MyComment', 'ir_actions_server: invalid condition check')
|
||||
|
||||
# Do: create contextual action
|
||||
self.ir_actions_server.create_action(cr, uid, [self.act_id])
|
||||
self.action.create_action()
|
||||
|
||||
# Test: ir_values created
|
||||
ir_values_ids = self.ir_values.search(cr, uid, [('name', '=', 'Run TestAction')])
|
||||
self.assertEqual(len(ir_values_ids), 1, 'ir_actions_server: create_action should have created an entry in ir_values')
|
||||
ir_value = self.ir_values.browse(cr, uid, ir_values_ids[0])
|
||||
self.assertEqual(ir_value.value, 'ir.actions.server,%s' % self.act_id, 'ir_actions_server: created ir_values should reference the server action')
|
||||
self.assertEqual(ir_value.model, 'res.partner', 'ir_actions_server: created ir_values should be linked to the action base model')
|
||||
ir_values = self.env['ir.values'].search([('name', '=', 'Run TestAction')])
|
||||
self.assertEqual(len(ir_values), 1, 'ir_actions_server: create_action should have created an entry in ir_values')
|
||||
self.assertEqual(ir_values.value, 'ir.actions.server,%s' % self.action.id, 'ir_actions_server: created ir_values should reference the server action')
|
||||
self.assertEqual(ir_values.model, 'res.partner', 'ir_actions_server: created ir_values should be linked to the action base model')
|
||||
|
||||
# Do: remove contextual action
|
||||
self.ir_actions_server.unlink_action(cr, uid, [self.act_id])
|
||||
self.action.unlink_action()
|
||||
|
||||
# Test: ir_values removed
|
||||
ir_values_ids = self.ir_values.search(cr, uid, [('name', '=', 'Run TestAction')])
|
||||
self.assertEqual(len(ir_values_ids), 0, 'ir_actions_server: unlink_action should remove the ir_values record')
|
||||
ir_values = self.env['ir.values'].search([('name', '=', 'Run TestAction')])
|
||||
self.assertEqual(len(ir_values), 0, 'ir_actions_server: unlink_action should remove the ir_values record')
|
||||
|
||||
def test_10_code(self):
|
||||
cr, uid = self.cr, self.uid
|
||||
self.ir_actions_server.write(cr, uid, self.act_id, {
|
||||
self.action.write({
|
||||
'state': 'code',
|
||||
'code': """partner_name = obj.name + '_code'
|
||||
self.pool["res.partner"].create(cr, uid, {"name": partner_name}, context=context)
|
||||
workflow"""
|
||||
'code': ("partner_name = obj.name + '_code'\n"
|
||||
"obj.env['res.partner'].create({'name': partner_name})\n"
|
||||
"workflow"),
|
||||
})
|
||||
run_res = self.ir_actions_server.run(cr, uid, [self.act_id], context=self.context)
|
||||
run_res = self.action.with_context(self.context).run()
|
||||
self.assertFalse(run_res, 'ir_actions_server: code server action correctly finished should return False')
|
||||
|
||||
pids = self.res_partner.search(cr, uid, [('name', 'ilike', 'TestingPartner_code')])
|
||||
self.assertEqual(len(pids), 1, 'ir_actions_server: 1 new partner should have been created')
|
||||
partners = self.test_partner.search([('name', 'ilike', 'TestingPartner_code')])
|
||||
self.assertEqual(len(partners), 1, 'ir_actions_server: 1 new partner should have been created')
|
||||
|
||||
def test_20_trigger(self):
|
||||
cr, uid = self.cr, self.uid
|
||||
Workflow = self.env['workflow']
|
||||
WorkflowActivity = self.env['workflow.activity']
|
||||
WorkflowTransition = self.env['workflow.transition']
|
||||
|
||||
# Data: code server action (at this point code-based actions should work)
|
||||
act_id2 = self.ir_actions_server.create(cr, uid, {
|
||||
action2 = self.action.create({
|
||||
'name': 'TestAction2',
|
||||
'type': 'ir.actions.server',
|
||||
'condition': 'True',
|
||||
'model_id': self.res_partner_model_id,
|
||||
'model_id': self.res_partner_model.id,
|
||||
'state': 'code',
|
||||
'code': 'obj.write({"comment": "MyComment"})',
|
||||
})
|
||||
act_id3 = self.ir_actions_server.create(cr, uid, {
|
||||
action3 = self.action.create({
|
||||
'name': 'TestAction3',
|
||||
'type': 'ir.actions.server',
|
||||
'condition': 'True',
|
||||
'model_id': self.res_country_model_id,
|
||||
'model_id': self.res_country_model.id,
|
||||
'state': 'code',
|
||||
'code': 'obj.write({"code": "ZZ"})',
|
||||
})
|
||||
|
||||
# Data: create workflows
|
||||
partner_wf_id = self.registry('workflow').create(cr, uid, {
|
||||
partner_workflow = Workflow.create({
|
||||
'name': 'TestWorkflow',
|
||||
'osv': 'res.partner',
|
||||
'on_create': True,
|
||||
})
|
||||
partner_act1_id = self.registry('workflow.activity').create(cr, uid, {
|
||||
partner_activity1 = WorkflowActivity.create({
|
||||
'name': 'PartnerStart',
|
||||
'wkf_id': partner_wf_id,
|
||||
'flow_start': True
|
||||
'wkf_id': partner_workflow.id,
|
||||
'flow_start': True,
|
||||
})
|
||||
partner_act2_id = self.registry('workflow.activity').create(cr, uid, {
|
||||
partner_activity2 = WorkflowActivity.create({
|
||||
'name': 'PartnerTwo',
|
||||
'wkf_id': partner_wf_id,
|
||||
'wkf_id': partner_workflow.id,
|
||||
'kind': 'function',
|
||||
'action': 'True',
|
||||
'action_id': act_id2,
|
||||
'action_id': action2.id,
|
||||
})
|
||||
partner_trs1_id = self.registry('workflow.transition').create(cr, uid, {
|
||||
partner_transition1 = WorkflowTransition.create({
|
||||
'signal': 'partner_trans',
|
||||
'act_from': partner_act1_id,
|
||||
'act_to': partner_act2_id
|
||||
'act_from': partner_activity1.id,
|
||||
'act_to': partner_activity2.id,
|
||||
})
|
||||
country_wf_id = self.registry('workflow').create(cr, uid, {
|
||||
country_workflow = Workflow.create({
|
||||
'name': 'TestWorkflow',
|
||||
'osv': 'res.country',
|
||||
'on_create': True,
|
||||
})
|
||||
country_act1_id = self.registry('workflow.activity').create(cr, uid, {
|
||||
country_activity1 = WorkflowActivity.create({
|
||||
'name': 'CountryStart',
|
||||
'wkf_id': country_wf_id,
|
||||
'flow_start': True
|
||||
'wkf_id': country_workflow.id,
|
||||
'flow_start': True,
|
||||
})
|
||||
country_act2_id = self.registry('workflow.activity').create(cr, uid, {
|
||||
country_activity2 = WorkflowActivity.create({
|
||||
'name': 'CountryTwo',
|
||||
'wkf_id': country_wf_id,
|
||||
'wkf_id': country_workflow.id,
|
||||
'kind': 'function',
|
||||
'action': 'True',
|
||||
'action_id': act_id3,
|
||||
'action_id': action3.id,
|
||||
})
|
||||
country_trs1_id = self.registry('workflow.transition').create(cr, uid, {
|
||||
country_transition1 = WorkflowTransition.create({
|
||||
'signal': 'country_trans',
|
||||
'act_from': country_act1_id,
|
||||
'act_to': country_act2_id
|
||||
'act_from': country_activity1.id,
|
||||
'act_to': country_activity2.id,
|
||||
})
|
||||
|
||||
# Data: re-create country and partner to benefit from the workflows
|
||||
self.test_country_id = self.res_country.create(cr, uid, {
|
||||
country = self.test_country.create({
|
||||
'name': 'TestingCountry2',
|
||||
'code': 'T2',
|
||||
})
|
||||
self.test_country = self.res_country.browse(cr, uid, self.test_country_id)
|
||||
self.test_partner_id = self.res_partner.create(cr, uid, {
|
||||
partner = self.test_partner.create({
|
||||
'name': 'TestingPartner2',
|
||||
'country_id': self.test_country_id,
|
||||
'country_id': country.id,
|
||||
})
|
||||
self.test_partner = self.res_partner.browse(cr, uid, self.test_partner_id)
|
||||
self.context = {
|
||||
'active_id': self.test_partner_id,
|
||||
'active_model': 'res.partner',
|
||||
}
|
||||
context = dict(self.context, active_id=partner.id)
|
||||
|
||||
# Run the action on partner object itself ('base')
|
||||
self.ir_actions_server.write(cr, uid, [self.act_id], {
|
||||
self.action.write({
|
||||
'state': 'trigger',
|
||||
'use_relational_model': 'base',
|
||||
'wkf_model_id': self.res_partner_model_id,
|
||||
'wkf_transition_id': partner_trs1_id,
|
||||
'wkf_model_id': self.res_partner_model.id,
|
||||
'wkf_transition_id': partner_transition1.id,
|
||||
})
|
||||
self.ir_actions_server.run(cr, uid, [self.act_id], self.context)
|
||||
self.test_partner.refresh()
|
||||
self.assertEqual(self.test_partner.comment, 'MyComment', 'ir_actions_server: incorrect signal trigger')
|
||||
self.action.with_context(context).run()
|
||||
self.assertEqual(partner.comment, 'MyComment', 'ir_actions_server: incorrect signal trigger')
|
||||
|
||||
# Run the action on related country object ('relational')
|
||||
self.ir_actions_server.write(cr, uid, [self.act_id], {
|
||||
self.action.write({
|
||||
'use_relational_model': 'relational',
|
||||
'wkf_model_id': self.res_country_model_id,
|
||||
'wkf_field_id': self.res_partner_country_field_id,
|
||||
'wkf_transition_id': country_trs1_id,
|
||||
'wkf_model_id': self.res_country_model.id,
|
||||
'wkf_field_id': self.res_partner_country_field.id,
|
||||
'wkf_transition_id': country_transition1.id,
|
||||
})
|
||||
self.ir_actions_server.run(cr, uid, [self.act_id], self.context)
|
||||
self.test_country.refresh()
|
||||
self.assertEqual(self.test_country.code, 'ZZ', 'ir_actions_server: incorrect signal trigger')
|
||||
self.action.with_context(context).run()
|
||||
self.assertEqual(country.code, 'ZZ', 'ir_actions_server: incorrect signal trigger')
|
||||
|
||||
# Clear workflow cache, otherwise openerp will try to create workflows even if it has been deleted
|
||||
from openerp.workflow import clear_cache
|
||||
clear_cache(cr, uid)
|
||||
# Clear workflow cache, otherwise odoo will try to create workflows even if it has been deleted
|
||||
odoo.workflow.clear_cache(self.cr, self.uid)
|
||||
|
||||
def test_30_client(self):
|
||||
cr, uid = self.cr, self.uid
|
||||
client_action_id = self.registry('ir.actions.client').create(cr, uid, {
|
||||
client_action = self.env['ir.actions.client'].create({
|
||||
'name': 'TestAction2',
|
||||
'tag': 'Test',
|
||||
})
|
||||
self.ir_actions_server.write(cr, uid, [self.act_id], {
|
||||
self.action.write({
|
||||
'state': 'client_action',
|
||||
'action_id': client_action_id,
|
||||
'action_id': client_action.id,
|
||||
})
|
||||
res = self.ir_actions_server.run(cr, uid, [self.act_id], context=self.context)
|
||||
res = self.action.with_context(self.context).run()
|
||||
self.assertEqual(res['name'], 'TestAction2', 'ir_actions_server: incorrect return result for a client action')
|
||||
|
||||
def test_40_crud_create(self):
|
||||
cr, uid = self.cr, self.uid
|
||||
_city = 'TestCity'
|
||||
_name = 'TestNew'
|
||||
|
||||
# Do: create a new record in the same model and link it
|
||||
self.ir_actions_server.write(cr, uid, [self.act_id], {
|
||||
self.action.write({
|
||||
'state': 'object_create',
|
||||
'use_create': 'new',
|
||||
'link_new_record': True,
|
||||
'link_field_id': self.res_partner_parent_field_id,
|
||||
'fields_lines': [(0, 0, {'col1': self.res_partner_name_field_id, 'value': _name}),
|
||||
(0, 0, {'col1': self.res_partner_city_field_id, 'value': _city})],
|
||||
'link_field_id': self.res_partner_parent_field.id,
|
||||
'fields_lines': [(0, 0, {'col1': self.res_partner_name_field.id, 'value': _name}),
|
||||
(0, 0, {'col1': self.res_partner_city_field.id, 'value': _city})],
|
||||
})
|
||||
run_res = self.ir_actions_server.run(cr, uid, [self.act_id], context=self.context)
|
||||
run_res = self.action.with_context(self.context).run()
|
||||
self.assertFalse(run_res, 'ir_actions_server: create record action correctly finished should return False')
|
||||
# Test: new partner created
|
||||
pids = self.res_partner.search(cr, uid, [('name', 'ilike', _name)])
|
||||
self.assertEqual(len(pids), 1, 'ir_actions_server: TODO')
|
||||
partner = self.res_partner.browse(cr, uid, pids[0])
|
||||
partner = self.test_partner.search([('name', 'ilike', _name)])
|
||||
self.assertEqual(len(partner), 1, 'ir_actions_server: TODO')
|
||||
self.assertEqual(partner.city, _city, 'ir_actions_server: TODO')
|
||||
# Test: new partner linked
|
||||
self.test_partner.refresh()
|
||||
self.assertEqual(self.test_partner.parent_id.id, pids[0], 'ir_actions_server: TODO')
|
||||
self.assertEqual(self.test_partner.parent_id, partner, 'ir_actions_server: TODO')
|
||||
|
||||
# Do: copy current record
|
||||
self.ir_actions_server.write(cr, uid, [self.act_id], {'fields_lines': [[5]]})
|
||||
self.ir_actions_server.write(cr, uid, [self.act_id], {
|
||||
self.action.write({
|
||||
'state': 'object_create',
|
||||
'use_create': 'copy_current',
|
||||
'link_new_record': False,
|
||||
'fields_lines': [(0, 0, {'col1': self.res_partner_name_field_id, 'value': 'TestCopyCurrent'}),
|
||||
(0, 0, {'col1': self.res_partner_city_field_id, 'value': 'TestCity'})],
|
||||
'fields_lines': [(5,),
|
||||
(0, 0, {'col1': self.res_partner_name_field.id, 'value': 'TestCopyCurrent'}),
|
||||
(0, 0, {'col1': self.res_partner_city_field.id, 'value': 'TestCity'})],
|
||||
})
|
||||
run_res = self.ir_actions_server.run(cr, uid, [self.act_id], context=self.context)
|
||||
run_res = self.action.with_context(self.context).run()
|
||||
self.assertFalse(run_res, 'ir_actions_server: create record action correctly finished should return False')
|
||||
# Test: new partner created
|
||||
pids = self.res_partner.search(cr, uid, [('name', 'ilike', 'TestingPartner (copy)')]) # currently res_partner overrides default['name'] whatever its value
|
||||
self.assertEqual(len(pids), 1, 'ir_actions_server: TODO')
|
||||
partner = self.res_partner.browse(cr, uid, pids[0])
|
||||
partner = self.test_partner.search([('name', 'ilike', 'TestingPartner (copy)')]) # currently res_partner overrides default['name'] whatever its value
|
||||
self.assertEqual(len(partner), 1, 'ir_actions_server: TODO')
|
||||
self.assertEqual(partner.city, 'TestCity', 'ir_actions_server: TODO')
|
||||
self.assertEqual(partner.country_id.id, self.test_partner.country_id.id, 'ir_actions_server: TODO')
|
||||
self.assertEqual(partner.country_id, self.test_partner.country_id, 'ir_actions_server: TODO')
|
||||
|
||||
# Do: create a new record in another model
|
||||
self.ir_actions_server.write(cr, uid, [self.act_id], {'fields_lines': [[5]]})
|
||||
self.ir_actions_server.write(cr, uid, [self.act_id], {
|
||||
self.action.write({
|
||||
'state': 'object_create',
|
||||
'use_create': 'new_other',
|
||||
'crud_model_id': self.res_country_model_id,
|
||||
'crud_model_id': self.res_country_model.id,
|
||||
'link_new_record': False,
|
||||
'fields_lines': [(0, 0, {'col1': self.res_country_name_field_id, 'value': 'obj.name', 'type': 'equation'}),
|
||||
(0, 0, {'col1': self.res_country_code_field_id, 'value': 'obj.name[0:2]', 'type': 'equation'})],
|
||||
'fields_lines': [(5,),
|
||||
(0, 0, {'col1': self.res_country_name_field.id, 'value': 'obj.name', 'type': 'equation'}),
|
||||
(0, 0, {'col1': self.res_country_code_field.id, 'value': 'obj.name[0:2]', 'type': 'equation'})],
|
||||
})
|
||||
run_res = self.ir_actions_server.run(cr, uid, [self.act_id], context=self.context)
|
||||
run_res = self.action.with_context(self.context).run()
|
||||
self.assertFalse(run_res, 'ir_actions_server: create record action correctly finished should return False')
|
||||
# Test: new country created
|
||||
cids = self.res_country.search(cr, uid, [('name', 'ilike', 'TestingPartner')])
|
||||
self.assertEqual(len(cids), 1, 'ir_actions_server: TODO')
|
||||
country = self.res_country.browse(cr, uid, cids[0])
|
||||
country = self.test_country.search([('name', 'ilike', 'TestingPartner')])
|
||||
self.assertEqual(len(country), 1, 'ir_actions_server: TODO')
|
||||
self.assertEqual(country.code, 'TE', 'ir_actions_server: TODO')
|
||||
|
||||
# Do: copy a record in another model
|
||||
self.ir_actions_server.write(cr, uid, [self.act_id], {'fields_lines': [[5]]})
|
||||
self.ir_actions_server.write(cr, uid, [self.act_id], {
|
||||
self.action.write({
|
||||
'state': 'object_create',
|
||||
'use_create': 'copy_other',
|
||||
'crud_model_id': self.res_country_model_id,
|
||||
'crud_model_id': self.res_country_model.id,
|
||||
'link_new_record': False,
|
||||
'ref_object': 'res.country,%s' % self.test_country_id,
|
||||
'fields_lines': [(0, 0, {'col1': self.res_country_name_field_id, 'value': 'NewCountry', 'type': 'value'}),
|
||||
(0, 0, {'col1': self.res_country_code_field_id, 'value': 'NY', 'type': 'value'})],
|
||||
'ref_object': 'res.country,%s' % self.test_country.id,
|
||||
'fields_lines': [(5,),
|
||||
(0, 0, {'col1': self.res_country_name_field.id, 'value': 'NewCountry', 'type': 'value'}),
|
||||
(0, 0, {'col1': self.res_country_code_field.id, 'value': 'NY', 'type': 'value'})],
|
||||
})
|
||||
run_res = self.ir_actions_server.run(cr, uid, [self.act_id], context=self.context)
|
||||
run_res = self.action.with_context(self.context).run()
|
||||
self.assertFalse(run_res, 'ir_actions_server: create record action correctly finished should return False')
|
||||
# Test: new country created
|
||||
cids = self.res_country.search(cr, uid, [('name', 'ilike', 'NewCountry')])
|
||||
self.assertEqual(len(cids), 1, 'ir_actions_server: TODO')
|
||||
country = self.res_country.browse(cr, uid, cids[0])
|
||||
country = self.test_country.search([('name', 'ilike', 'NewCountry')])
|
||||
self.assertEqual(len(country), 1, 'ir_actions_server: TODO')
|
||||
self.assertEqual(country.code, 'NY', 'ir_actions_server: TODO')
|
||||
self.assertEqual(country.address_format, 'SuperFormat', 'ir_actions_server: TODO')
|
||||
|
||||
def test_50_crud_write(self):
|
||||
cr, uid = self.cr, self.uid
|
||||
_name = 'TestNew'
|
||||
|
||||
# Do: create a new record in the same model and link it
|
||||
self.ir_actions_server.write(cr, uid, [self.act_id], {
|
||||
self.action.write({
|
||||
'state': 'object_write',
|
||||
'use_write': 'current',
|
||||
'fields_lines': [(0, 0, {'col1': self.res_partner_name_field_id, 'value': _name})],
|
||||
'fields_lines': [(0, 0, {'col1': self.res_partner_name_field.id, 'value': _name})],
|
||||
})
|
||||
run_res = self.ir_actions_server.run(cr, uid, [self.act_id], context=self.context)
|
||||
run_res = self.action.with_context(self.context).run()
|
||||
self.assertFalse(run_res, 'ir_actions_server: create record action correctly finished should return False')
|
||||
# Test: new partner created
|
||||
pids = self.res_partner.search(cr, uid, [('name', 'ilike', _name)])
|
||||
self.assertEqual(len(pids), 1, 'ir_actions_server: TODO')
|
||||
partner = self.res_partner.browse(cr, uid, pids[0])
|
||||
partner = self.test_partner.search([('name', 'ilike', _name)])
|
||||
self.assertEqual(len(partner), 1, 'ir_actions_server: TODO')
|
||||
self.assertEqual(partner.city, 'OrigCity', 'ir_actions_server: TODO')
|
||||
|
||||
# Do: copy current record
|
||||
self.ir_actions_server.write(cr, uid, [self.act_id], {'fields_lines': [[5]]})
|
||||
self.ir_actions_server.write(cr, uid, [self.act_id], {
|
||||
self.action.write({
|
||||
'use_write': 'other',
|
||||
'crud_model_id': self.res_country_model_id,
|
||||
'ref_object': 'res.country,%s' % self.test_country_id,
|
||||
'fields_lines': [(0, 0, {'col1': self.res_country_name_field_id, 'value': 'obj.name', 'type': 'equation'})],
|
||||
'crud_model_id': self.res_country_model.id,
|
||||
'ref_object': 'res.country,%s' % self.test_country.id,
|
||||
'fields_lines': [(5,), (0, 0, {'col1': self.res_country_name_field.id, 'value': 'obj.name', 'type': 'equation'})],
|
||||
})
|
||||
run_res = self.ir_actions_server.run(cr, uid, [self.act_id], context=self.context)
|
||||
run_res = self.action.with_context(self.context).run()
|
||||
self.assertFalse(run_res, 'ir_actions_server: create record action correctly finished should return False')
|
||||
# Test: new country created
|
||||
cids = self.res_country.search(cr, uid, [('name', 'ilike', 'TestNew')])
|
||||
self.assertEqual(len(cids), 1, 'ir_actions_server: TODO')
|
||||
country = self.test_country.search([('name', 'ilike', 'TestNew')])
|
||||
self.assertEqual(len(country), 1, 'ir_actions_server: TODO')
|
||||
|
||||
# Do: copy a record in another model
|
||||
self.ir_actions_server.write(cr, uid, [self.act_id], {'fields_lines': [[5]]})
|
||||
self.ir_actions_server.write(cr, uid, [self.act_id], {
|
||||
self.action.write({
|
||||
'use_write': 'expression',
|
||||
'crud_model_id': self.res_country_model_id,
|
||||
'crud_model_id': self.res_country_model.id,
|
||||
'write_expression': 'object.country_id',
|
||||
'fields_lines': [(0, 0, {'col1': self.res_country_name_field_id, 'value': 'NewCountry', 'type': 'value'})],
|
||||
'fields_lines': [(5,), (0, 0, {'col1': self.res_country_name_field.id, 'value': 'NewCountry', 'type': 'value'})],
|
||||
})
|
||||
run_res = self.ir_actions_server.run(cr, uid, [self.act_id], context=self.context)
|
||||
run_res = self.action.with_context(self.context).run()
|
||||
self.assertFalse(run_res, 'ir_actions_server: create record action correctly finished should return False')
|
||||
# Test: new country created
|
||||
cids = self.res_country.search(cr, uid, [('name', 'ilike', 'NewCountry')])
|
||||
self.assertEqual(len(cids), 1, 'ir_actions_server: TODO')
|
||||
country = self.test_country.search([('name', 'ilike', 'NewCountry')])
|
||||
self.assertEqual(len(country), 1, 'ir_actions_server: TODO')
|
||||
|
||||
@mute_logger('openerp.addons.base.ir.ir_model', 'openerp.models')
|
||||
def test_60_multi(self):
|
||||
cr, uid = self.cr, self.uid
|
||||
|
||||
# Data: 2 server actions that will be nested
|
||||
act1_id = self.ir_actions_server.create(cr, uid, {
|
||||
action1 = self.action.create({
|
||||
'name': 'Subaction1',
|
||||
'sequence': 1,
|
||||
'model_id': self.res_partner_model_id,
|
||||
'model_id': self.res_partner_model.id,
|
||||
'state': 'code',
|
||||
'code': 'action = {"type": "ir.actions.act_window"}',
|
||||
})
|
||||
act2_id = self.ir_actions_server.create(cr, uid, {
|
||||
action2 = self.action.create({
|
||||
'name': 'Subaction2',
|
||||
'sequence': 2,
|
||||
'model_id': self.res_partner_model_id,
|
||||
'model_id': self.res_partner_model.id,
|
||||
'state': 'object_create',
|
||||
'use_create': 'copy_current',
|
||||
})
|
||||
act3_id = self.ir_actions_server.create(cr, uid, {
|
||||
action3 = self.action.create({
|
||||
'name': 'Subaction3',
|
||||
'sequence': 3,
|
||||
'model_id': self.res_partner_model_id,
|
||||
'model_id': self.res_partner_model.id,
|
||||
'state': 'code',
|
||||
'code': 'action = {"type": "ir.actions.act_url"}',
|
||||
})
|
||||
self.ir_actions_server.write(cr, uid, [self.act_id], {
|
||||
self.action.write({
|
||||
'state': 'multi',
|
||||
'child_ids': [(6, 0, [act1_id, act2_id, act3_id])],
|
||||
'child_ids': [(6, 0, [action1.id, action2.id, action3.id])],
|
||||
})
|
||||
|
||||
# Do: run the action
|
||||
res = self.ir_actions_server.run(cr, uid, [self.act_id], context=self.context)
|
||||
res = self.action.with_context(self.context).run()
|
||||
|
||||
# Test: new partner created
|
||||
pids = self.res_partner.search(cr, uid, [('name', 'ilike', 'TestingPartner (copy)')]) # currently res_partner overrides default['name'] whatever its value
|
||||
self.assertEqual(len(pids), 1, 'ir_actions_server: TODO')
|
||||
# currently res_partner overrides default['name'] whatever its value
|
||||
partner = self.test_partner.search([('name', 'ilike', 'TestingPartner (copy)')])
|
||||
self.assertEqual(len(partner), 1, 'ir_actions_server: TODO')
|
||||
# Test: action returned
|
||||
self.assertEqual(res.get('type'), 'ir.actions.act_url')
|
||||
|
||||
# Test loops
|
||||
with self.assertRaises(ValidationError):
|
||||
self.ir_actions_server.write(cr, uid, [self.act_id], {
|
||||
'child_ids': [(6, 0, [self.act_id])]
|
||||
self.action.write({
|
||||
'child_ids': [(6, 0, [self.action.id])]
|
||||
})
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import hashlib
|
||||
import os
|
||||
|
||||
import openerp
|
||||
import openerp.tests.common
|
||||
from odoo.tests.common import TransactionCase
|
||||
|
||||
HASH_SPLIT = 2 # FIXME: testing implementations detail is not a good idea
|
||||
|
||||
class test_ir_attachment(openerp.tests.common.TransactionCase):
|
||||
|
||||
class TestIrAttachment(TransactionCase):
|
||||
def setUp(self):
|
||||
super(test_ir_attachment, self).setUp()
|
||||
registry, cr, uid = self.registry, self.cr, self.uid
|
||||
self.ira = registry('ir.attachment')
|
||||
self.filestore = self.ira._filestore(cr, uid)
|
||||
super(TestIrAttachment, self).setUp()
|
||||
self.Attachment = self.env['ir.attachment']
|
||||
self.ira = self.Attachment._model
|
||||
self.filestore = self.Attachment._filestore()
|
||||
|
||||
# Blob1
|
||||
self.blob1 = 'blob1'
|
||||
@@ -20,73 +23,55 @@ class test_ir_attachment(openerp.tests.common.TransactionCase):
|
||||
self.blob1_fname = blob1_hash[:HASH_SPLIT] + '/' + blob1_hash
|
||||
|
||||
# Blob2
|
||||
blob2 = 'blob2'
|
||||
self.blob2_b64 = blob2.encode('base64')
|
||||
self.blob2 = 'blob2'
|
||||
self.blob2_b64 = self.blob2.encode('base64')
|
||||
|
||||
def test_01_store_in_db(self):
|
||||
registry, cr, uid = self.registry, self.cr, self.uid
|
||||
|
||||
# force storing in database
|
||||
registry('ir.config_parameter').set_param(cr, uid, 'ir_attachment.location', 'db')
|
||||
self.env['ir.config_parameter'].set_param('ir_attachment.location', 'db')
|
||||
|
||||
# 'ir_attachment.location' is undefined test database storage
|
||||
a1 = self.ira.create(cr, uid, {'name': 'a1', 'datas': self.blob1_b64})
|
||||
a1_read = self.ira.read(cr, uid, [a1], ['datas'])
|
||||
self.assertEqual(a1_read[0]['datas'], self.blob1_b64)
|
||||
a1 = self.Attachment.create({'name': 'a1', 'datas': self.blob1_b64})
|
||||
self.assertEqual(a1.datas, self.blob1_b64)
|
||||
|
||||
a1_db_datas = self.ira.browse(cr, uid, a1).db_datas
|
||||
a1_db_datas = a1.db_datas
|
||||
self.assertEqual(a1_db_datas, self.blob1_b64)
|
||||
|
||||
def test_02_store_on_disk(self):
|
||||
registry, cr, uid = self.registry, self.cr, self.uid
|
||||
|
||||
a2 = self.ira.create(cr, uid, {'name': 'a2', 'datas': self.blob1_b64})
|
||||
a2_store_fname = self.ira.browse(cr, uid, a2).store_fname
|
||||
|
||||
self.assertEqual(a2_store_fname, self.blob1_fname)
|
||||
self.assertTrue(os.path.isfile(os.path.join(self.filestore, a2_store_fname)))
|
||||
a2 = self.Attachment.create({'name': 'a2', 'datas': self.blob1_b64})
|
||||
self.assertEqual(a2.store_fname, self.blob1_fname)
|
||||
self.assertTrue(os.path.isfile(os.path.join(self.filestore, a2.store_fname)))
|
||||
|
||||
def test_03_no_duplication(self):
|
||||
registry, cr, uid = self.registry, self.cr, self.uid
|
||||
|
||||
a2 = self.ira.create(cr, uid, {'name': 'a2', 'datas': self.blob1_b64})
|
||||
a2_store_fname = self.ira.browse(cr, uid, a2).store_fname
|
||||
|
||||
a3 = self.ira.create(cr, uid, {'name': 'a3', 'datas': self.blob1_b64})
|
||||
a3_store_fname = self.ira.browse(cr, uid, a3).store_fname
|
||||
|
||||
self.assertEqual(a3_store_fname, a2_store_fname)
|
||||
a2 = self.Attachment.create({'name': 'a2', 'datas': self.blob1_b64})
|
||||
a3 = self.Attachment.create({'name': 'a3', 'datas': self.blob1_b64})
|
||||
self.assertEqual(a3.store_fname, a2.store_fname)
|
||||
|
||||
def test_04_keep_file(self):
|
||||
registry, cr, uid = self.registry, self.cr, self.uid
|
||||
a2 = self.Attachment.create({'name': 'a2', 'datas': self.blob1_b64})
|
||||
a3 = self.Attachment.create({'name': 'a3', 'datas': self.blob1_b64})
|
||||
|
||||
a2 = self.ira.create(cr, uid, {'name': 'a2', 'datas': self.blob1_b64})
|
||||
a3 = self.ira.create(cr, uid, {'name': 'a3', 'datas': self.blob1_b64})
|
||||
a2_fn = os.path.join(self.filestore, a2.store_fname)
|
||||
|
||||
a2_store_fname = self.ira.browse(cr, uid, a2).store_fname
|
||||
a2_fn = os.path.join(self.filestore, a2_store_fname)
|
||||
|
||||
self.ira.unlink(cr, uid, [a3])
|
||||
a3.unlink()
|
||||
self.assertTrue(os.path.isfile(a2_fn))
|
||||
|
||||
# delete a2 it is unlinked
|
||||
self.ira.unlink(cr, uid, [a2])
|
||||
a2.unlink()
|
||||
self.assertFalse(os.path.isfile(a2_fn))
|
||||
|
||||
def test_05_change_data_change_file(self):
|
||||
registry, cr, uid = self.registry, self.cr, self.uid
|
||||
|
||||
a2 = self.ira.create(cr, uid, {'name': 'a2', 'datas': self.blob1_b64})
|
||||
a2_store_fname = self.ira.browse(cr, uid, a2).store_fname
|
||||
a2_fn = os.path.join(self.filestore, a2_store_fname)
|
||||
a2 = self.Attachment.create({'name': 'a2', 'datas': self.blob1_b64})
|
||||
a2_store_fname1 = a2.store_fname
|
||||
a2_fn = os.path.join(self.filestore, a2_store_fname1)
|
||||
|
||||
self.assertTrue(os.path.isfile(a2_fn))
|
||||
|
||||
self.ira.write(cr, uid, [a2], {'datas': self.blob2_b64})
|
||||
a2.write({'datas': self.blob2_b64})
|
||||
self.assertFalse(os.path.isfile(a2_fn))
|
||||
|
||||
new_a2_store_fname = self.ira.browse(cr, uid, a2).store_fname
|
||||
self.assertNotEqual(a2_store_fname, new_a2_store_fname)
|
||||
a2_store_fname2 = a2.store_fname
|
||||
self.assertNotEqual(a2_store_fname1, a2_store_fname2)
|
||||
|
||||
new_a2_fn = os.path.join(self.filestore, new_a2_store_fname)
|
||||
self.assertTrue(os.path.isfile(new_a2_fn))
|
||||
a2_fn = os.path.join(self.filestore, a2_store_fname2)
|
||||
self.assertTrue(os.path.isfile(a2_fn))
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import functools
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from openerp import exceptions
|
||||
from openerp.tests import common
|
||||
from odoo import exceptions
|
||||
from odoo.tests.common import TransactionCase, ADMIN_USER_ID
|
||||
|
||||
def noid(d):
|
||||
""" Removes values that are not relevant for the test comparisons """
|
||||
@@ -10,17 +10,19 @@ def noid(d):
|
||||
d.pop('action_id', None)
|
||||
return d
|
||||
|
||||
class FiltersCase(common.TransactionCase):
|
||||
|
||||
class FiltersCase(TransactionCase):
|
||||
def build(self, model, *args):
|
||||
Model = self.registry(model)
|
||||
for vars in args:
|
||||
Model.create(self.cr, common.ADMIN_USER_ID, vars, {})
|
||||
Model = self.env[model].sudo(ADMIN_USER_ID)
|
||||
for vals in args:
|
||||
Model.create(vals)
|
||||
|
||||
|
||||
class TestGetFilters(FiltersCase):
|
||||
def setUp(self):
|
||||
super(TestGetFilters, self).setUp()
|
||||
self.USER = self.registry('res.users').name_search(self.cr, self.uid, 'demo')[0]
|
||||
self.USER_ID = self.USER[0]
|
||||
self.USER_NG = self.env['res.users'].name_search('demo')[0]
|
||||
self.USER_ID = self.USER_NG[0]
|
||||
|
||||
def test_own_filters(self):
|
||||
self.build(
|
||||
@@ -30,14 +32,13 @@ class TestGetFilters(FiltersCase):
|
||||
dict(name='c', user_id=self.USER_ID, model_id='ir.filters'),
|
||||
dict(name='d', user_id=self.USER_ID, model_id='ir.filters'))
|
||||
|
||||
filters = self.registry('ir.filters').get_filters(
|
||||
self.cr, self.USER_ID, 'ir.filters')
|
||||
filters = self.env['ir.filters'].sudo(self.USER_ID).get_filters('ir.filters')
|
||||
|
||||
self.assertItemsEqual(map(noid, filters), [
|
||||
dict(name='a', is_default=False, user_id=self.USER, domain='[]', context='{}', sort='[]'),
|
||||
dict(name='b', is_default=False, user_id=self.USER, domain='[]', context='{}', sort='[]'),
|
||||
dict(name='c', is_default=False, user_id=self.USER, domain='[]', context='{}', sort='[]'),
|
||||
dict(name='d', is_default=False, user_id=self.USER, domain='[]', context='{}', sort='[]'),
|
||||
dict(name='a', is_default=False, user_id=self.USER_NG, domain='[]', context='{}', sort='[]'),
|
||||
dict(name='b', is_default=False, user_id=self.USER_NG, domain='[]', context='{}', sort='[]'),
|
||||
dict(name='c', is_default=False, user_id=self.USER_NG, domain='[]', context='{}', sort='[]'),
|
||||
dict(name='d', is_default=False, user_id=self.USER_NG, domain='[]', context='{}', sort='[]'),
|
||||
])
|
||||
|
||||
def test_global_filters(self):
|
||||
@@ -49,8 +50,7 @@ class TestGetFilters(FiltersCase):
|
||||
dict(name='d', user_id=False, model_id='ir.filters'),
|
||||
)
|
||||
|
||||
filters = self.registry('ir.filters').get_filters(
|
||||
self.cr, self.USER_ID, 'ir.filters')
|
||||
filters = self.env['ir.filters'].sudo(self.USER_ID).get_filters('ir.filters')
|
||||
|
||||
self.assertItemsEqual(map(noid, filters), [
|
||||
dict(name='a', is_default=False, user_id=False, domain='[]', context='{}', sort='[]'),
|
||||
@@ -63,40 +63,40 @@ class TestGetFilters(FiltersCase):
|
||||
self.build(
|
||||
'ir.filters',
|
||||
dict(name='a', user_id=False, model_id='ir.filters'),
|
||||
dict(name='b', user_id=common.ADMIN_USER_ID, model_id='ir.filters'),
|
||||
dict(name='b', user_id=ADMIN_USER_ID, model_id='ir.filters'),
|
||||
dict(name='c', user_id=self.USER_ID, model_id='ir.filters'),
|
||||
dict(name='d', user_id=common.ADMIN_USER_ID, model_id='ir.filters') )
|
||||
dict(name='d', user_id=ADMIN_USER_ID, model_id='ir.filters') )
|
||||
|
||||
filters = self.registry('ir.filters').get_filters(
|
||||
self.cr, self.USER_ID, 'ir.filters')
|
||||
filters = self.env['ir.filters'].sudo(self.USER_ID).get_filters('ir.filters')
|
||||
|
||||
self.assertItemsEqual(map(noid, filters), [
|
||||
dict(name='a', is_default=False, user_id=False, domain='[]', context='{}', sort='[]'),
|
||||
dict(name='c', is_default=False, user_id=self.USER, domain='[]', context='{}', sort='[]'),
|
||||
dict(name='c', is_default=False, user_id=self.USER_NG, domain='[]', context='{}', sort='[]'),
|
||||
])
|
||||
|
||||
|
||||
class TestOwnDefaults(FiltersCase):
|
||||
def setUp(self):
|
||||
super(TestOwnDefaults, self).setUp()
|
||||
self.USER = self.registry('res.users').name_search(self.cr, self.uid, 'demo')[0]
|
||||
self.USER_ID = self.USER[0]
|
||||
self.USER_NG = self.env['res.users'].name_search('demo')[0]
|
||||
self.USER_ID = self.USER_NG[0]
|
||||
|
||||
def test_new_no_filter(self):
|
||||
"""
|
||||
When creating a @is_default filter with no existing filter, that new
|
||||
filter gets the default flag
|
||||
"""
|
||||
Filters = self.registry('ir.filters')
|
||||
Filters.create_or_replace(self.cr, self.USER_ID, {
|
||||
Filters = self.env['ir.filters'].sudo(self.USER_ID)
|
||||
Filters.create_or_replace({
|
||||
'name': 'a',
|
||||
'model_id': 'ir.filters',
|
||||
'user_id': self.USER_ID,
|
||||
'is_default': True,
|
||||
})
|
||||
filters = Filters.get_filters(self.cr, self.USER_ID, 'ir.filters')
|
||||
filters = Filters.get_filters('ir.filters')
|
||||
|
||||
self.assertItemsEqual(map(noid, filters), [
|
||||
dict(name='a', user_id=self.USER, is_default=True,
|
||||
dict(name='a', user_id=self.USER_NG, is_default=True,
|
||||
domain='[]', context='{}', sort='[]')
|
||||
])
|
||||
|
||||
@@ -111,19 +111,19 @@ class TestOwnDefaults(FiltersCase):
|
||||
dict(name='b', user_id=self.USER_ID, model_id='ir.filters'),
|
||||
)
|
||||
|
||||
Filters = self.registry('ir.filters')
|
||||
Filters.create_or_replace(self.cr, self.USER_ID, {
|
||||
Filters = self.env['ir.filters'].sudo(self.USER_ID)
|
||||
Filters.create_or_replace({
|
||||
'name': 'c',
|
||||
'model_id': 'ir.filters',
|
||||
'user_id': self.USER_ID,
|
||||
'is_default': True,
|
||||
})
|
||||
filters = Filters.get_filters(self.cr, self.USER_ID, 'ir.filters')
|
||||
filters = Filters.get_filters('ir.filters')
|
||||
|
||||
self.assertItemsEqual(map(noid, filters), [
|
||||
dict(name='a', user_id=self.USER, is_default=False, domain='[]', context='{}', sort='[]'),
|
||||
dict(name='b', user_id=self.USER, is_default=False, domain='[]', context='{}', sort='[]'),
|
||||
dict(name='c', user_id=self.USER, is_default=True, domain='[]', context='{}', sort='[]'),
|
||||
dict(name='a', user_id=self.USER_NG, is_default=False, domain='[]', context='{}', sort='[]'),
|
||||
dict(name='b', user_id=self.USER_NG, is_default=False, domain='[]', context='{}', sort='[]'),
|
||||
dict(name='c', user_id=self.USER_NG, is_default=True, domain='[]', context='{}', sort='[]'),
|
||||
])
|
||||
|
||||
def test_new_filter_existing_default(self):
|
||||
@@ -137,19 +137,19 @@ class TestOwnDefaults(FiltersCase):
|
||||
dict(name='b', is_default=True, user_id=self.USER_ID, model_id='ir.filters'),
|
||||
)
|
||||
|
||||
Filters = self.registry('ir.filters')
|
||||
Filters.create_or_replace(self.cr, self.USER_ID, {
|
||||
Filters = self.env['ir.filters'].sudo(self.USER_ID)
|
||||
Filters.create_or_replace({
|
||||
'name': 'c',
|
||||
'model_id': 'ir.filters',
|
||||
'user_id': self.USER_ID,
|
||||
'is_default': True,
|
||||
})
|
||||
filters = Filters.get_filters(self.cr, self.USER_ID, 'ir.filters')
|
||||
filters = Filters.get_filters('ir.filters')
|
||||
|
||||
self.assertItemsEqual(map(noid, filters), [
|
||||
dict(name='a', user_id=self.USER, is_default=False, domain='[]', context='{}', sort='[]'),
|
||||
dict(name='b', user_id=self.USER, is_default=False, domain='[]', context='{}', sort='[]'),
|
||||
dict(name='c', user_id=self.USER, is_default=True, domain='[]', context='{}', sort='[]'),
|
||||
dict(name='a', user_id=self.USER_NG, is_default=False, domain='[]', context='{}', sort='[]'),
|
||||
dict(name='b', user_id=self.USER_NG, is_default=False, domain='[]', context='{}', sort='[]'),
|
||||
dict(name='c', user_id=self.USER_NG, is_default=True, domain='[]', context='{}', sort='[]'),
|
||||
])
|
||||
|
||||
def test_update_filter_set_default(self):
|
||||
@@ -163,25 +163,26 @@ class TestOwnDefaults(FiltersCase):
|
||||
dict(name='b', is_default=True, user_id=self.USER_ID, model_id='ir.filters'),
|
||||
)
|
||||
|
||||
Filters = self.registry('ir.filters')
|
||||
Filters.create_or_replace(self.cr, self.USER_ID, {
|
||||
Filters = self.env['ir.filters'].sudo(self.USER_ID)
|
||||
Filters.create_or_replace({
|
||||
'name': 'a',
|
||||
'model_id': 'ir.filters',
|
||||
'user_id': self.USER_ID,
|
||||
'is_default': True,
|
||||
})
|
||||
filters = Filters.get_filters(self.cr, self.USER_ID, 'ir.filters')
|
||||
filters = Filters.get_filters('ir.filters')
|
||||
|
||||
self.assertItemsEqual(map(noid, filters), [
|
||||
dict(name='a', user_id=self.USER, is_default=True, domain='[]', context='{}', sort='[]'),
|
||||
dict(name='b', user_id=self.USER, is_default=False, domain='[]', context='{}', sort='[]'),
|
||||
dict(name='a', user_id=self.USER_NG, is_default=True, domain='[]', context='{}', sort='[]'),
|
||||
dict(name='b', user_id=self.USER_NG, is_default=False, domain='[]', context='{}', sort='[]'),
|
||||
])
|
||||
|
||||
|
||||
class TestGlobalDefaults(FiltersCase):
|
||||
def setUp(self):
|
||||
super(TestGlobalDefaults, self).setUp()
|
||||
self.USER = self.registry('res.users').name_search(self.cr, self.uid, 'demo')[0]
|
||||
self.USER_ID = self.USER[0]
|
||||
self.USER_NG = self.env['res.users'].name_search('demo')[0]
|
||||
self.USER_ID = self.USER_NG[0]
|
||||
|
||||
def test_new_filter_not_default(self):
|
||||
"""
|
||||
@@ -194,14 +195,14 @@ class TestGlobalDefaults(FiltersCase):
|
||||
dict(name='b', user_id=False, model_id='ir.filters'),
|
||||
)
|
||||
|
||||
Filters = self.registry('ir.filters')
|
||||
Filters.create_or_replace(self.cr, self.USER_ID, {
|
||||
Filters = self.env['ir.filters'].sudo(self.USER_ID)
|
||||
Filters.create_or_replace({
|
||||
'name': 'c',
|
||||
'model_id': 'ir.filters',
|
||||
'user_id': False,
|
||||
'is_default': True,
|
||||
})
|
||||
filters = Filters.get_filters(self.cr, self.USER_ID, 'ir.filters')
|
||||
filters = Filters.get_filters('ir.filters')
|
||||
|
||||
self.assertItemsEqual(map(noid, filters), [
|
||||
dict(name='a', user_id=False, is_default=False, domain='[]', context='{}', sort='[]'),
|
||||
@@ -220,9 +221,9 @@ class TestGlobalDefaults(FiltersCase):
|
||||
dict(name='b', is_default=True, user_id=False, model_id='ir.filters'),
|
||||
)
|
||||
|
||||
Filters = self.registry('ir.filters')
|
||||
Filters = self.env['ir.filters'].sudo(self.USER_ID)
|
||||
with self.assertRaises(exceptions.Warning):
|
||||
Filters.create_or_replace(self.cr, self.USER_ID, {
|
||||
Filters.create_or_replace({
|
||||
'name': 'c',
|
||||
'model_id': 'ir.filters',
|
||||
'user_id': False,
|
||||
@@ -240,10 +241,9 @@ class TestGlobalDefaults(FiltersCase):
|
||||
dict(name='b', is_default=True, user_id=False, model_id='ir.filters'),
|
||||
)
|
||||
|
||||
Filters = self.registry('ir.filters')
|
||||
|
||||
Filters = self.env['ir.filters'].sudo(self.USER_ID)
|
||||
with self.assertRaises(exceptions.Warning):
|
||||
Filters.create_or_replace(self.cr, self.USER_ID, {
|
||||
Filters.create_or_replace({
|
||||
'name': 'a',
|
||||
'model_id': 'ir.filters',
|
||||
'user_id': False,
|
||||
@@ -260,42 +260,37 @@ class TestGlobalDefaults(FiltersCase):
|
||||
dict(name='b', is_default=True, user_id=False, model_id='ir.filters'),
|
||||
)
|
||||
|
||||
Filters = self.registry('ir.filters')
|
||||
Filters = self.env['ir.filters'].sudo(self.USER_ID)
|
||||
context_value = "{'some_key': True}"
|
||||
Filters.create_or_replace(self.cr, self.USER_ID, {
|
||||
Filters.create_or_replace({
|
||||
'name': 'b',
|
||||
'model_id': 'ir.filters',
|
||||
'user_id': False,
|
||||
'context': context_value,
|
||||
'is_default': True,
|
||||
})
|
||||
filters = Filters.get_filters(self.cr, self.USER_ID, 'ir.filters')
|
||||
filters = Filters.get_filters('ir.filters')
|
||||
|
||||
self.assertItemsEqual(map(noid, filters), [
|
||||
dict(name='a', user_id=False, is_default=False, domain='[]', context='{}', sort='[]'),
|
||||
dict(name='b', user_id=False, is_default=True, domain='[]', context=context_value, sort='[]'),
|
||||
])
|
||||
|
||||
from openerp.tests.common import TransactionCase
|
||||
|
||||
class TestReadGroup(TransactionCase):
|
||||
"""Test function read_group with groupby on a many2one field to a model
|
||||
(in test, "user_id" to "res.users") which is ordered by an inherited not stored field (in
|
||||
test, "name" inherited from "res.partners").
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super(TestReadGroup, self).setUp()
|
||||
self.ir_filters_model = self.env['ir.filters']
|
||||
self.res_partner_model = self.env['res.partner']
|
||||
self.res_users_model = self.env['res.users']
|
||||
|
||||
def test_read_group_1(self):
|
||||
self.assertEqual(self.res_users_model._order, "name, login", "Model res.users must be ordered by name, login")
|
||||
self.assertFalse(self.res_users_model._fields['name'].store, "Field name is not stored in res.users")
|
||||
Users = self.env['res.users']
|
||||
self.assertEqual(Users._order, "name, login", "Model res.users must be ordered by name, login")
|
||||
self.assertFalse(Users._fields['name'].store, "Field name is not stored in res.users")
|
||||
|
||||
filter_a = self.ir_filters_model.create(dict(name="Filter_A", model_id="ir.filters"))
|
||||
filter_b = self.ir_filters_model.create(dict(name="Filter_B", model_id="ir.filters"))
|
||||
Filters = self.env['ir.filters']
|
||||
filter_a = Filters.create(dict(name="Filter_A", model_id="ir.filters"))
|
||||
filter_b = Filters.create(dict(name="Filter_B", model_id="ir.filters"))
|
||||
filter_b.write(dict(user_id=False))
|
||||
res = self.ir_filters_model.read_group([], ['name', 'user_id'], ['user_id'])
|
||||
|
||||
res = Filters.read_group([], ['name', 'user_id'], ['user_id'])
|
||||
self.assertTrue(any(val['user_id'] == False for val in res), "At least one group must contain val['user_id'] == False.")
|
||||
|
||||
@@ -1,189 +0,0 @@
|
||||
-
|
||||
Exercise ir_rule.py code (and indirectly expression.py).
|
||||
-
|
||||
Create an ir_rule for the Employee group (called base.group_user)
|
||||
with an blank domain.
|
||||
-
|
||||
!record {model: ir.rule, id: test_rule}:
|
||||
model_id: base.model_res_partner
|
||||
domain_force: False
|
||||
name: test_rule
|
||||
groups:
|
||||
- base.group_user
|
||||
perm_unlink: 1
|
||||
perm_write: 1
|
||||
perm_read: 1
|
||||
perm_create: 1
|
||||
-
|
||||
Read as demo user the partners (one blank domain).
|
||||
-
|
||||
!python {model: res.partner }: |
|
||||
ids = self.search(cr, ref('base.user_demo'), [])
|
||||
assert ids, "Demo user should see some partner."
|
||||
-
|
||||
Domain is not empty.
|
||||
-
|
||||
!record {model: ir.rule, id: test_rule}:
|
||||
model_id: base.model_res_partner
|
||||
domain_force: "[(1,'=',1)]"
|
||||
-
|
||||
Read as demo user the partners (one 1=1 domain).
|
||||
-
|
||||
!python {model: res.partner }: |
|
||||
ids = self.search(cr, ref('base.user_demo'), [])
|
||||
assert ids, "Demo user should see some partner."
|
||||
-
|
||||
Domain is an empty list.
|
||||
-
|
||||
!record {model: ir.rule, id: test_rule}:
|
||||
model_id: base.model_res_partner
|
||||
domain_force: "[]"
|
||||
-
|
||||
Read as demo user the partners (one [] domain).
|
||||
-
|
||||
!python {model: res.partner }: |
|
||||
ids = self.search(cr, ref('base.user_demo'), [])
|
||||
assert ids, "Demo user should see some partner."
|
||||
-
|
||||
Create another ir_rule for the Employee group (to test rules from
|
||||
multiple groups).
|
||||
-
|
||||
!record {model: ir.rule, id: test_rule2}:
|
||||
model_id: base.model_res_partner
|
||||
domain_force: False
|
||||
name: test_rule2
|
||||
groups:
|
||||
- base.group_user
|
||||
perm_unlink: 1
|
||||
perm_write: 1
|
||||
perm_read: 1
|
||||
perm_create: 1
|
||||
-
|
||||
Read as demo user the partners (blank and [] domains).
|
||||
-
|
||||
!python {model: res.partner }: |
|
||||
ids = self.search(cr, ref('base.user_demo'), [])
|
||||
assert ids, "Demo user should see some partner."
|
||||
-
|
||||
Domain is not empty.
|
||||
-
|
||||
!record {model: ir.rule, id: test_rule}:
|
||||
model_id: base.model_res_partner
|
||||
domain_force: "[(1,'=',1)]"
|
||||
-
|
||||
Read as demo user the partners (1=1 and blank domain).
|
||||
-
|
||||
!python {model: res.partner }: |
|
||||
ids = self.search(cr, ref('base.user_demo'), [])
|
||||
assert ids, "Demo user should see some partner."
|
||||
-
|
||||
Domain is not empty.
|
||||
-
|
||||
!record {model: ir.rule, id: test_rule2}:
|
||||
model_id: base.model_res_partner
|
||||
domain_force: "[(1,'=',1)]"
|
||||
-
|
||||
Read as demo user the partners (two 1=1 domains).
|
||||
-
|
||||
!python {model: res.partner }: |
|
||||
ids = self.search(cr, ref('base.user_demo'), [])
|
||||
assert ids, "Demo user should see some partner."
|
||||
-
|
||||
Create another ir_rule for the Employee group (to test rules from
|
||||
multiple groups).
|
||||
-
|
||||
!record {model: ir.rule, id: test_rule3}:
|
||||
model_id: base.model_res_partner
|
||||
domain_force: False
|
||||
name: test_rule3
|
||||
groups:
|
||||
- base.group_user
|
||||
perm_unlink: 1
|
||||
perm_write: 1
|
||||
perm_read: 1
|
||||
perm_create: 1
|
||||
-
|
||||
Read as demo user the partners.
|
||||
-
|
||||
!python {model: res.partner }: |
|
||||
ids = self.search(cr, ref('base.user_demo'), [])
|
||||
assert ids, "Demo user should see some partner."
|
||||
-
|
||||
Domain is not empty.
|
||||
-
|
||||
!record {model: ir.rule, id: test_rule3}:
|
||||
model_id: base.model_res_partner
|
||||
domain_force: "[(1,'=',1)]"
|
||||
-
|
||||
Read as demo user the partners (three 1=1 domains).
|
||||
-
|
||||
!python {model: res.partner }: |
|
||||
ids = self.search(cr, ref('base.user_demo'), [])
|
||||
assert ids, "Demo user should see some partner."
|
||||
-
|
||||
Modify the global rule on res_company which triggers a recursive check
|
||||
of the rules on company.
|
||||
-
|
||||
!record {model: ir.rule, id: res_company_rule_employee}:
|
||||
domain_force: "[('id','child_of',[user.company_id.id])]"
|
||||
-
|
||||
Read as demo user the partners (exercising the global company rule).
|
||||
-
|
||||
!python {model: res.partner }: |
|
||||
ids = self.search(cr, ref('base.user_demo'), [])
|
||||
assert ids, "Demo user should see some partner."
|
||||
-
|
||||
Modify the ir_rule for employee to have a rule that fordids
|
||||
seeing any record.
|
||||
We use a domain with implicit AND operator for later tests
|
||||
on normalization.
|
||||
-
|
||||
!record {model: ir.rule, id: test_rule2}:
|
||||
domain_force: "[('id','=',False),('name','=',False)]"
|
||||
-
|
||||
Check that demo user still sees partners, because group-rules are OR'ed.
|
||||
-
|
||||
!python {model: res.partner }: |
|
||||
ids = self.search(cr, ref('base.user_demo'), [])
|
||||
assert ids, "Demo user should see some partner."
|
||||
-
|
||||
Create a new group with demo user in it, and a complex rule.
|
||||
Demo should still see partners.
|
||||
-
|
||||
!record {model: res.groups, id: test_group}:
|
||||
name: Test Group
|
||||
users:
|
||||
- base.user_demo
|
||||
-
|
||||
Add the rule to the new group, with a domain containing an implicit AND operator,
|
||||
which is more tricky because it will have to be normalized before combining it.
|
||||
-
|
||||
!record {model: ir.rule, id: test_rule3}:
|
||||
model_id: base.model_res_partner
|
||||
domain_force: "[('name', '!=', False),('id', '!=', False)]"
|
||||
name: test_rule4
|
||||
groups:
|
||||
- test_group
|
||||
perm_unlink: 1
|
||||
perm_write: 1
|
||||
perm_read: 1
|
||||
perm_create: 1
|
||||
-
|
||||
Read the partners again as demo user, which should give results.
|
||||
-
|
||||
!python {model: res.partner }: |
|
||||
ids = self.search(cr, ref('base.user_demo'), [])
|
||||
assert ids, "Demo user should see partners even with the combined rules."
|
||||
-
|
||||
Delete global domains (to combine only group domains).
|
||||
-
|
||||
!python {model: ir.rule}: |
|
||||
ids = self.search(cr, uid, [('groups','=',False)])
|
||||
assert ids, "Demo user should see some partner."
|
||||
self.unlink(cr, uid, ids)
|
||||
-
|
||||
Read as demo user the partners (several group domains, no global domain).
|
||||
-
|
||||
!python {model: res.partner }: |
|
||||
ids = self.search(cr, ref('base.user_demo'), [])
|
||||
assert ids, "Demo user should see some partner."
|
||||
@@ -1,193 +1,177 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Run with one of these commands:
|
||||
# > OPENERP_ADDONS_PATH='../../addons/trunk' OPENERP_PORT=8069 \
|
||||
# OPENERP_DATABASE=yy PYTHONPATH=. python tests/test_ir_sequence.py
|
||||
# > OPENERP_ADDONS_PATH='../../addons/trunk' OPENERP_PORT=8069 \
|
||||
# OPENERP_DATABASE=yy nosetests tests/test_ir_sequence.py
|
||||
# > OPENERP_ADDONS_PATH='../../../addons/trunk' OPENERP_PORT=8069 \
|
||||
# OPENERP_DATABASE=yy PYTHONPATH=../:. unit2 test_ir_sequence
|
||||
# This assume an existing database.
|
||||
import psycopg2
|
||||
import psycopg2.errorcodes
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from contextlib import contextmanager
|
||||
import unittest
|
||||
|
||||
import openerp
|
||||
from openerp.tests import common
|
||||
import psycopg2
|
||||
import psycopg2.errorcodes
|
||||
|
||||
import odoo
|
||||
from odoo.tests import common
|
||||
|
||||
ADMIN_USER_ID = common.ADMIN_USER_ID
|
||||
|
||||
def registry(model):
|
||||
return openerp.modules.registry.RegistryManager.get(common.get_db_name())[model]
|
||||
|
||||
def cursor():
|
||||
return openerp.modules.registry.RegistryManager.get(common.get_db_name()).cursor()
|
||||
@contextmanager
|
||||
def environment():
|
||||
""" Return an environment with a new cursor for the current database; the
|
||||
cursor is committed and closed after the context block.
|
||||
"""
|
||||
registry = odoo.registry(common.get_db_name())
|
||||
with registry.cursor() as cr:
|
||||
yield odoo.api.Environment(cr, ADMIN_USER_ID, {})
|
||||
cr.commit()
|
||||
|
||||
|
||||
def drop_sequence(code):
|
||||
cr = cursor()
|
||||
s = registry('ir.sequence')
|
||||
ids = s.search(cr, ADMIN_USER_ID, [('code', '=', code)])
|
||||
s.unlink(cr, ADMIN_USER_ID, ids)
|
||||
cr.commit()
|
||||
cr.close()
|
||||
with environment() as env:
|
||||
seq = env['ir.sequence'].search([('code', '=', code)])
|
||||
seq.unlink()
|
||||
|
||||
class test_ir_sequence_standard(unittest.TestCase):
|
||||
|
||||
class TestIrSequenceStandard(unittest.TestCase):
|
||||
""" A few tests for a 'Standard' (i.e. PostgreSQL) sequence. """
|
||||
|
||||
def test_ir_sequence_create(self):
|
||||
""" Try to create a sequence object. """
|
||||
cr = cursor()
|
||||
d = dict(code='test_sequence_type', name='Test sequence')
|
||||
c = registry('ir.sequence').create(cr, ADMIN_USER_ID, d, {})
|
||||
assert c
|
||||
cr.commit()
|
||||
cr.close()
|
||||
with environment() as env:
|
||||
seq = env['ir.sequence'].create({
|
||||
'code': 'test_sequence_type',
|
||||
'name': 'Test sequence',
|
||||
})
|
||||
self.assertTrue(seq)
|
||||
|
||||
def test_ir_sequence_search(self):
|
||||
""" Try a search. """
|
||||
cr = cursor()
|
||||
ids = registry('ir.sequence').search(cr, ADMIN_USER_ID, [], {})
|
||||
assert ids
|
||||
cr.commit()
|
||||
cr.close()
|
||||
with environment() as env:
|
||||
seqs = env['ir.sequence'].search([])
|
||||
self.assertTrue(seqs)
|
||||
|
||||
def test_ir_sequence_draw(self):
|
||||
""" Try to draw a number. """
|
||||
cr = cursor()
|
||||
n = registry('ir.sequence').next_by_code(cr, ADMIN_USER_ID, 'test_sequence_type', {})
|
||||
assert n
|
||||
cr.commit()
|
||||
cr.close()
|
||||
with environment() as env:
|
||||
n = env['ir.sequence'].next_by_code('test_sequence_type')
|
||||
self.assertTrue(n)
|
||||
|
||||
def test_ir_sequence_draw_twice(self):
|
||||
""" Try to draw a number from two transactions. """
|
||||
cr0 = cursor()
|
||||
cr1 = cursor()
|
||||
n0 = registry('ir.sequence').next_by_code(cr0, ADMIN_USER_ID, 'test_sequence_type', {})
|
||||
assert n0
|
||||
n1 = registry('ir.sequence').next_by_code(cr1, ADMIN_USER_ID, 'test_sequence_type', {})
|
||||
assert n1
|
||||
cr0.commit()
|
||||
cr1.commit()
|
||||
cr0.close()
|
||||
cr1.close()
|
||||
with environment() as env0:
|
||||
with environment() as env1:
|
||||
n0 = env0['ir.sequence'].next_by_code('test_sequence_type')
|
||||
self.assertTrue(n0)
|
||||
n1 = env1['ir.sequence'].next_by_code('test_sequence_type')
|
||||
self.assertTrue(n1)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
drop_sequence('test_sequence_type')
|
||||
|
||||
class test_ir_sequence_no_gap(unittest.TestCase):
|
||||
|
||||
class TestIrSequenceNoGap(unittest.TestCase):
|
||||
""" Copy of the previous tests for a 'No gap' sequence. """
|
||||
|
||||
def test_ir_sequence_create_no_gap(self):
|
||||
""" Try to create a sequence object. """
|
||||
cr = cursor()
|
||||
d = dict(code='test_sequence_type_2', name='Test sequence',
|
||||
implementation='no_gap')
|
||||
c = registry('ir.sequence').create(cr, ADMIN_USER_ID, d, {})
|
||||
assert c
|
||||
cr.commit()
|
||||
cr.close()
|
||||
with environment() as env:
|
||||
seq = env['ir.sequence'].create({
|
||||
'code': 'test_sequence_type_2',
|
||||
'name': 'Test sequence',
|
||||
'implementation': 'no_gap',
|
||||
})
|
||||
self.assertTrue(seq)
|
||||
|
||||
def test_ir_sequence_draw_no_gap(self):
|
||||
""" Try to draw a number. """
|
||||
cr = cursor()
|
||||
n = registry('ir.sequence').next_by_code(cr, ADMIN_USER_ID, 'test_sequence_type_2', {})
|
||||
assert n
|
||||
cr.commit()
|
||||
cr.close()
|
||||
with environment() as env:
|
||||
n = env['ir.sequence'].next_by_code('test_sequence_type_2')
|
||||
self.assertTrue(n)
|
||||
|
||||
def test_ir_sequence_draw_twice_no_gap(self):
|
||||
""" Try to draw a number from two transactions.
|
||||
This is expected to not work.
|
||||
"""
|
||||
cr0 = cursor()
|
||||
cr1 = cursor()
|
||||
cr1._default_log_exceptions = False # Prevent logging a traceback
|
||||
with self.assertRaises(psycopg2.OperationalError) as e:
|
||||
n0 = registry('ir.sequence').next_by_code(cr0, ADMIN_USER_ID, 'test_sequence_type_2', {})
|
||||
assert n0
|
||||
n1 = registry('ir.sequence').next_by_code(cr1, ADMIN_USER_ID, 'test_sequence_type_2', {})
|
||||
self.assertEqual(e.exception.pgcode, psycopg2.errorcodes.LOCK_NOT_AVAILABLE, msg="postgresql returned an incorrect errcode")
|
||||
cr0.close()
|
||||
cr1.close()
|
||||
with environment() as env0:
|
||||
with environment() as env1:
|
||||
env1.cr._default_log_exceptions = False # Prevent logging a traceback
|
||||
with self.assertRaises(psycopg2.OperationalError) as e:
|
||||
n0 = env0['ir.sequence'].next_by_code('test_sequence_type_2')
|
||||
self.assertTrue(n0)
|
||||
n1 = env1['ir.sequence'].next_by_code('test_sequence_type_2')
|
||||
self.assertEqual(e.exception.pgcode, psycopg2.errorcodes.LOCK_NOT_AVAILABLE, msg="postgresql returned an incorrect errcode")
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
drop_sequence('test_sequence_type_2')
|
||||
|
||||
class test_ir_sequence_change_implementation(unittest.TestCase):
|
||||
|
||||
class TestIrSequenceChangeImplementation(unittest.TestCase):
|
||||
""" Create sequence objects and change their ``implementation`` field. """
|
||||
|
||||
def test_ir_sequence_1_create(self):
|
||||
""" Try to create a sequence object. """
|
||||
cr = cursor()
|
||||
d = dict(code='test_sequence_type_3', name='Test sequence')
|
||||
c = registry('ir.sequence').create(cr, ADMIN_USER_ID, d, {})
|
||||
assert c
|
||||
d = dict(code='test_sequence_type_4', name='Test sequence',
|
||||
implementation='no_gap')
|
||||
c = registry('ir.sequence').create(cr, ADMIN_USER_ID, d, {})
|
||||
assert c
|
||||
cr.commit()
|
||||
cr.close()
|
||||
with environment() as env:
|
||||
seq = env['ir.sequence'].create({
|
||||
'code': 'test_sequence_type_3',
|
||||
'name': 'Test sequence',
|
||||
})
|
||||
self.assertTrue(seq)
|
||||
seq = env['ir.sequence'].create({
|
||||
'code': 'test_sequence_type_4',
|
||||
'name': 'Test sequence',
|
||||
'implementation': 'no_gap',
|
||||
})
|
||||
self.assertTrue(seq)
|
||||
|
||||
def test_ir_sequence_2_write(self):
|
||||
cr = cursor()
|
||||
ids = registry('ir.sequence').search(cr, ADMIN_USER_ID,
|
||||
[('code', 'in', ['test_sequence_type_3', 'test_sequence_type_4'])], {})
|
||||
registry('ir.sequence').write(cr, ADMIN_USER_ID, ids,
|
||||
{'implementation': 'standard'}, {})
|
||||
registry('ir.sequence').write(cr, ADMIN_USER_ID, ids,
|
||||
{'implementation': 'no_gap'}, {})
|
||||
cr.commit()
|
||||
cr.close()
|
||||
with environment() as env:
|
||||
domain = [('code', 'in', ['test_sequence_type_3', 'test_sequence_type_4'])]
|
||||
seqs = env['ir.sequence'].search(domain)
|
||||
seqs.write({'implementation': 'standard'})
|
||||
seqs.write({'implementation': 'no_gap'})
|
||||
|
||||
def test_ir_sequence_3_unlink(self):
|
||||
cr = cursor()
|
||||
ids = registry('ir.sequence').search(cr, ADMIN_USER_ID,
|
||||
[('code', 'in', ['test_sequence_type_3', 'test_sequence_type_4'])], {})
|
||||
registry('ir.sequence').unlink(cr, ADMIN_USER_ID, ids, {})
|
||||
cr.commit()
|
||||
cr.close()
|
||||
with environment() as env:
|
||||
domain = [('code', 'in', ['test_sequence_type_3', 'test_sequence_type_4'])]
|
||||
seqs = env['ir.sequence'].search(domain)
|
||||
seqs.unlink()
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
drop_sequence('test_sequence_type_3')
|
||||
drop_sequence('test_sequence_type_4')
|
||||
|
||||
class test_ir_sequence_generate(unittest.TestCase):
|
||||
|
||||
class TestIrSequenceGenerate(unittest.TestCase):
|
||||
""" Create sequence objects and generate some values. """
|
||||
|
||||
def test_ir_sequence_create(self):
|
||||
""" Try to create a sequence object. """
|
||||
cr = cursor()
|
||||
d = dict(code='test_sequence_type_5', name='Test sequence')
|
||||
c = registry('ir.sequence').create(cr, ADMIN_USER_ID, d, {})
|
||||
assert c
|
||||
cr.commit()
|
||||
cr.close()
|
||||
with environment() as env:
|
||||
seq = env['ir.sequence'].create({
|
||||
'code': 'test_sequence_type_5',
|
||||
'name': 'Test sequence',
|
||||
})
|
||||
self.assertTrue(seq)
|
||||
|
||||
cr = cursor()
|
||||
f = lambda *a: registry('ir.sequence').next_by_code(cr, ADMIN_USER_ID, 'test_sequence_type_5', {})
|
||||
assert all(str(x) == f() for x in xrange(1,10))
|
||||
cr.commit()
|
||||
cr.close()
|
||||
with environment() as env:
|
||||
for i in xrange(1, 10):
|
||||
n = env['ir.sequence'].next_by_code('test_sequence_type_5')
|
||||
self.assertEqual(n, str(i))
|
||||
|
||||
def test_ir_sequence_create_no_gap(self):
|
||||
""" Try to create a sequence object. """
|
||||
cr = cursor()
|
||||
d = dict(code='test_sequence_type_6', name='Test sequence', implementation='no_gap')
|
||||
c = registry('ir.sequence').create(cr, ADMIN_USER_ID, d, {})
|
||||
assert c
|
||||
cr.commit()
|
||||
cr.close()
|
||||
with environment() as env:
|
||||
seq = env['ir.sequence'].create({
|
||||
'code': 'test_sequence_type_6',
|
||||
'name': 'Test sequence',
|
||||
'implementation': 'no_gap',
|
||||
})
|
||||
self.assertTrue(seq)
|
||||
|
||||
cr = cursor()
|
||||
f = lambda *a: registry('ir.sequence').next_by_code(cr, ADMIN_USER_ID, 'test_sequence_type_6', {})
|
||||
assert all(str(x) == f() for x in xrange(1,10))
|
||||
cr.commit()
|
||||
cr.close()
|
||||
with environment() as env:
|
||||
for i in xrange(1, 10):
|
||||
n = env['ir.sequence'].next_by_code('test_sequence_type_6')
|
||||
self.assertEqual(n, str(i))
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
@@ -195,33 +179,29 @@ class test_ir_sequence_generate(unittest.TestCase):
|
||||
drop_sequence('test_sequence_type_6')
|
||||
|
||||
|
||||
class Test_ir_sequence_init(common.TransactionCase):
|
||||
class TestIrSequenceInit(common.TransactionCase):
|
||||
|
||||
def test_00(self):
|
||||
registry, cr, uid = self.registry, self.cr, self.uid
|
||||
|
||||
# test if read statement return the good number_next value (from postgreSQL sequence and not ir_sequence value)
|
||||
sequence = registry('ir.sequence')
|
||||
""" test whether the read method returns the right number_next value
|
||||
(from postgreSQL sequence and not ir_sequence value)
|
||||
"""
|
||||
# first creation of sequence (normal)
|
||||
values = {'number_next': 1,
|
||||
'company_id': 1,
|
||||
'padding': 4,
|
||||
'number_increment': 1,
|
||||
'implementation': 'standard',
|
||||
'name': 'test-sequence-00'}
|
||||
seq_id = sequence.create(cr, uid, values)
|
||||
# Call get next 4 times
|
||||
sequence.next_by_id(cr, uid, seq_id)
|
||||
sequence.next_by_id(cr, uid, seq_id)
|
||||
sequence.next_by_id(cr, uid, seq_id)
|
||||
read_sequence = sequence.next_by_id(cr, uid, seq_id)
|
||||
seq = self.env['ir.sequence'].create({
|
||||
'number_next': 1,
|
||||
'company_id': 1,
|
||||
'padding': 4,
|
||||
'number_increment': 1,
|
||||
'implementation': 'standard',
|
||||
'name': 'test-sequence-00',
|
||||
})
|
||||
# Call next() 4 times, and check the last returned value
|
||||
seq.next_by_id()
|
||||
seq.next_by_id()
|
||||
seq.next_by_id()
|
||||
n = seq.next_by_id()
|
||||
self.assertEqual(n, "0004", 'The actual sequence value must be 4. reading : %s' % n)
|
||||
# reset sequence to 1 by write()
|
||||
seq.write({'number_next': 1})
|
||||
# Read the value of the current sequence
|
||||
assert read_sequence == "0004", 'The actual sequence value must be 4. reading : %s' % read_sequence
|
||||
# reset sequence to 1 by write method calling
|
||||
sequence.write(cr, uid, [seq_id], {'number_next': 1})
|
||||
# Read the value of the current sequence
|
||||
read_sequence = sequence.next_by_id(cr, uid, seq_id)
|
||||
assert read_sequence == "0001", 'The actual sequence value must be 1. reading : %s' % read_sequence
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
n = seq.next_by_id()
|
||||
self.assertEqual(n, "0001", 'The actual sequence value must be 1. reading : %s' % n)
|
||||
|
||||
@@ -1,122 +1,142 @@
|
||||
import unittest
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from openerp.tests import common
|
||||
from datetime import date
|
||||
|
||||
from odoo.tests.common import SingleTransactionCase
|
||||
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT as DATE_FORMAT
|
||||
|
||||
class test_ir_sequence_date_range_standard(common.SingleTransactionCase):
|
||||
|
||||
class TestIrSequenceDateRangeStandard(SingleTransactionCase):
|
||||
""" A few tests for a 'Standard' (i.e. PostgreSQL) sequence. """
|
||||
|
||||
def test_ir_sequence_date_range_1_create(self):
|
||||
""" Try to create a sequence object with date ranges enabled. """
|
||||
d = dict(code='test_sequence_date_range', name='Test sequence', use_date_range=True)
|
||||
c = self.registry('ir.sequence').create(self.cr, self.uid, d, {})
|
||||
assert c
|
||||
seq = self.env['ir.sequence'].create({
|
||||
'code': 'test_sequence_date_range',
|
||||
'name': 'Test sequence',
|
||||
'use_date_range': True,
|
||||
})
|
||||
self.assertTrue(seq)
|
||||
|
||||
def test_ir_sequence_date_range_2_change_dates(self):
|
||||
""" Draw numbers to create a first subsequence then change its date range. Then, try to draw a new number adn check a new subsequence was correctly created. """
|
||||
year = str(int(date.today().strftime("%Y")) - 1)
|
||||
dt = "{}-01-16".format(year)
|
||||
n = self.registry('ir.sequence').next_by_code(self.cr, self.uid, 'test_sequence_date_range', {'ir_sequence_date': dt})
|
||||
year = date.today().year - 1
|
||||
january = lambda d: date(year, 1, d).strftime(DATE_FORMAT)
|
||||
|
||||
seq16 = self.env['ir.sequence'].with_context({'ir_sequence_date': january(16)})
|
||||
n = seq16.next_by_code('test_sequence_date_range')
|
||||
self.assertEqual(n, '1')
|
||||
n = self.registry('ir.sequence').next_by_code(self.cr, self.uid, 'test_sequence_date_range', {'ir_sequence_date': dt})
|
||||
n = seq16.next_by_code('test_sequence_date_range')
|
||||
self.assertEqual(n, '2')
|
||||
|
||||
# modify the range of date created
|
||||
domain = [('sequence_id.code', '=', 'test_sequence_date_range'), ('date_from', '=', '{}-01-01'.format(year))]
|
||||
seq_date_range_id = self.registry('ir.sequence.date_range').search(self.cr, self.uid, domain, {})
|
||||
self.registry('ir.sequence.date_range').write(self.cr, self.uid, seq_date_range_id, {'date_from': '{}-01-18'.format(year)}, {})
|
||||
n = self.registry('ir.sequence').next_by_code(self.cr, self.uid, 'test_sequence_date_range', {'ir_sequence_date': dt})
|
||||
domain = [('sequence_id.code', '=', 'test_sequence_date_range'), ('date_from', '=', january(1))]
|
||||
seq_date_range = self.env['ir.sequence.date_range'].search(domain)
|
||||
seq_date_range.write({'date_from': january(18)})
|
||||
n = seq16.next_by_code('test_sequence_date_range')
|
||||
self.assertEqual(n, '1')
|
||||
|
||||
# check the newly created sequence stops at the 17th of January
|
||||
domain = [('sequence_id.code', '=', 'test_sequence_date_range'), ('date_from', '=', '{}-01-01'.format(year))]
|
||||
seq_date_range_id = self.registry('ir.sequence.date_range').search(self.cr, self.uid, domain, {})
|
||||
seq_date_range = self.registry('ir.sequence.date_range').browse(self.cr, self.uid, seq_date_range_id[0])
|
||||
self.assertEqual(seq_date_range.date_to, '{}-01-17'.format(year))
|
||||
domain = [('sequence_id.code', '=', 'test_sequence_date_range'), ('date_from', '=', january(1))]
|
||||
seq_date_range = self.env['ir.sequence.date_range'].search(domain)
|
||||
self.assertEqual(seq_date_range.date_to, january(17))
|
||||
|
||||
def test_ir_sequence_date_range_3_unlink(self):
|
||||
ids = self.registry('ir.sequence').search(self.cr, self.uid, [('code', '=', 'test_sequence_date_range')])
|
||||
self.registry('ir.sequence').unlink(self.cr, self.uid, ids)
|
||||
seq = self.env['ir.sequence'].search([('code', '=', 'test_sequence_date_range')])
|
||||
seq.unlink()
|
||||
|
||||
|
||||
class test_ir_sequence_date_range_no_gap(common.SingleTransactionCase):
|
||||
class TestIrSequenceDateRangeNoGap(SingleTransactionCase):
|
||||
""" Copy of the previous tests for a 'No gap' sequence. """
|
||||
|
||||
def test_ir_sequence_date_range_1_create_no_gap(self):
|
||||
""" Try to create a sequence object. """
|
||||
d = dict(code='test_sequence_date_range_2', name='Test sequence',
|
||||
implementation='no_gap', use_date_range=True)
|
||||
c = self.registry('ir.sequence').create(self.cr, self.uid, d, {})
|
||||
assert c
|
||||
seq = self.env['ir.sequence'].create({
|
||||
'code': 'test_sequence_date_range_2',
|
||||
'name': 'Test sequence',
|
||||
'use_date_range': True,
|
||||
'implementation': 'no_gap',
|
||||
})
|
||||
self.assertTrue(seq)
|
||||
|
||||
def test_ir_sequence_date_range_2_change_dates(self):
|
||||
""" Draw numbers to create a first subsequence then change its date range. Then, try to draw a new number adn check a new subsequence was correctly created. """
|
||||
year = str(int(date.today().strftime("%Y")) - 1)
|
||||
dt = "{}-01-16".format(year)
|
||||
n = self.registry('ir.sequence').next_by_code(self.cr, self.uid, 'test_sequence_date_range_2', {'ir_sequence_date': dt})
|
||||
year = date.today().year - 1
|
||||
january = lambda d: date(year, 1, d).strftime(DATE_FORMAT)
|
||||
|
||||
seq16 = self.env['ir.sequence'].with_context({'ir_sequence_date': january(16)})
|
||||
n = seq16.next_by_code('test_sequence_date_range_2')
|
||||
self.assertEqual(n, '1')
|
||||
n = self.registry('ir.sequence').next_by_code(self.cr, self.uid, 'test_sequence_date_range_2', {'ir_sequence_date': dt})
|
||||
n = seq16.next_by_code('test_sequence_date_range_2')
|
||||
self.assertEqual(n, '2')
|
||||
|
||||
# modify the range of date created
|
||||
domain = [('sequence_id.code', '=', 'test_sequence_date_range_2'), ('date_from', '=', '{}-01-01'.format(year))]
|
||||
seq_date_range_id = self.registry('ir.sequence.date_range').search(self.cr, self.uid, domain, {})
|
||||
self.registry('ir.sequence.date_range').write(self.cr, self.uid, seq_date_range_id, {'date_from': '{}-01-18'.format(year)}, {})
|
||||
n = self.registry('ir.sequence').next_by_code(self.cr, self.uid, 'test_sequence_date_range_2', {'ir_sequence_date': dt})
|
||||
domain = [('sequence_id.code', '=', 'test_sequence_date_range_2'), ('date_from', '=', january(1))]
|
||||
seq_date_range = self.env['ir.sequence.date_range'].search(domain)
|
||||
seq_date_range.write({'date_from': january(18)})
|
||||
n = seq16.next_by_code('test_sequence_date_range_2')
|
||||
self.assertEqual(n, '1')
|
||||
|
||||
# check the newly created sequence stops at the 17th of January
|
||||
domain = [('sequence_id.code', '=', 'test_sequence_date_range_2'), ('date_from', '=', '{}-01-01'.format(year))]
|
||||
seq_date_range_id = self.registry('ir.sequence.date_range').search(self.cr, self.uid, domain, {})
|
||||
seq_date_range = self.registry('ir.sequence.date_range').browse(self.cr, self.uid, seq_date_range_id[0])
|
||||
self.assertEqual(seq_date_range.date_to, '{}-01-17'.format(year))
|
||||
domain = [('sequence_id.code', '=', 'test_sequence_date_range_2'), ('date_from', '=', january(1))]
|
||||
seq_date_range = self.env['ir.sequence.date_range'].search(domain)
|
||||
self.assertEqual(seq_date_range.date_to, january(17))
|
||||
|
||||
def test_ir_sequence_date_range_3_unlink(self):
|
||||
ids = self.registry('ir.sequence').search(self.cr, self.uid, [('code', '=', 'test_sequence_date_range_2')])
|
||||
self.registry('ir.sequence').unlink(self.cr, self.uid, ids)
|
||||
seq = self.env['ir.sequence'].search([('code', '=', 'test_sequence_date_range_2')])
|
||||
seq.unlink()
|
||||
|
||||
|
||||
class test_ir_sequence_date_range_change_implementation(common.SingleTransactionCase):
|
||||
class TestIrSequenceDateRangeChangeImplementation(SingleTransactionCase):
|
||||
""" Create sequence objects and change their ``implementation`` field. """
|
||||
|
||||
def test_ir_sequence_date_range_1_create(self):
|
||||
""" Try to create a sequence object. """
|
||||
d = dict(code='test_sequence_date_range_3', name='Test sequence', use_date_range=True)
|
||||
c = self.registry('ir.sequence').create(self.cr, self.uid, d, {})
|
||||
assert c
|
||||
d = {
|
||||
seq = self.env['ir.sequence'].create({
|
||||
'code': 'test_sequence_date_range_3',
|
||||
'name': 'Test sequence',
|
||||
'use_date_range': True,
|
||||
})
|
||||
self.assertTrue(seq)
|
||||
|
||||
seq = self.env['ir.sequence'].create({
|
||||
'code': 'test_sequence_date_range_4',
|
||||
'name': 'Test sequence',
|
||||
'use_date_range': True,
|
||||
'implementation': 'no_gap',
|
||||
'use_date_range': True
|
||||
}
|
||||
c = self.registry('ir.sequence').create(self.cr, self.uid, d, {})
|
||||
assert c
|
||||
})
|
||||
self.assertTrue(seq)
|
||||
|
||||
def test_ir_sequence_date_range_2_use(self):
|
||||
""" Make some use of the sequences to create some subsequences """
|
||||
year = str(int(date.today().strftime("%Y")) - 1)
|
||||
dt = "{}-01-16".format(year)
|
||||
f = lambda *a: self.registry('ir.sequence').next_by_code(self.cr, self.uid, 'test_sequence_date_range_3', {})
|
||||
assert all(str(x) == f() for x in xrange(1, 5))
|
||||
f = lambda *a: self.registry('ir.sequence').next_by_code(self.cr, self.uid, 'test_sequence_date_range_3', {'ir_sequence_date': dt})
|
||||
assert all(str(x) == f() for x in xrange(1, 5))
|
||||
f = lambda *a: self.registry('ir.sequence').next_by_code(self.cr, self.uid, 'test_sequence_date_range_4', {})
|
||||
assert all(str(x) == f() for x in xrange(1, 5))
|
||||
f = lambda *a: self.registry('ir.sequence').next_by_code(self.cr, self.uid, 'test_sequence_date_range_4', {'ir_sequence_date': dt})
|
||||
assert all(str(x) == f() for x in xrange(1, 5))
|
||||
year = date.today().year - 1
|
||||
january = lambda d: date(year, 1, d).strftime(DATE_FORMAT)
|
||||
|
||||
seq = self.env['ir.sequence']
|
||||
seq16 = self.env['ir.sequence'].with_context({'ir_sequence_date': january(16)})
|
||||
|
||||
for i in xrange(1, 5):
|
||||
n = seq.next_by_code('test_sequence_date_range_3')
|
||||
self.assertEqual(n, str(i))
|
||||
for i in xrange(1, 5):
|
||||
n = seq16.next_by_code('test_sequence_date_range_3')
|
||||
self.assertEqual(n, str(i))
|
||||
for i in xrange(1, 5):
|
||||
n = seq.next_by_code('test_sequence_date_range_4')
|
||||
self.assertEqual(n, str(i))
|
||||
for i in xrange(1, 5):
|
||||
n = seq16.next_by_code('test_sequence_date_range_4')
|
||||
self.assertEqual(n, str(i))
|
||||
|
||||
def test_ir_sequence_date_range_3_write(self):
|
||||
"""swap the implementation method on both"""
|
||||
ids = self.registry('ir.sequence').search(self.cr, self.uid, [
|
||||
('code', 'in', ['test_sequence_date_range_3', 'test_sequence_date_range_4'])
|
||||
], {})
|
||||
self.registry('ir.sequence').write(self.cr, self.uid, ids, {'implementation': 'standard'})
|
||||
self.registry('ir.sequence').write(self.cr, self.uid, ids, {'implementation': 'no_gap'})
|
||||
domain = [('code', 'in', ['test_sequence_date_range_3', 'test_sequence_date_range_4'])]
|
||||
seqs = self.env['ir.sequence'].search(domain)
|
||||
seqs.write({'implementation': 'standard'})
|
||||
seqs.write({'implementation': 'no_gap'})
|
||||
|
||||
def test_ir_sequence_date_range_4_unlink(self):
|
||||
ids = self.registry('ir.sequence').search(self.cr, self.uid, [
|
||||
('code', 'in', ['test_sequence_date_range_3', 'test_sequence_date_range_4'])
|
||||
], {})
|
||||
self.registry('ir.sequence').unlink(self.cr, self.uid, ids, {})
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
domain = [('code', 'in', ['test_sequence_date_range_3', 'test_sequence_date_range_4'])]
|
||||
seqs = self.env['ir.sequence'].search(domain)
|
||||
seqs.unlink()
|
||||
|
||||
@@ -1,98 +1,99 @@
|
||||
import unittest
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import openerp.tests.common as common
|
||||
from odoo.tests.common import TransactionCase
|
||||
|
||||
class test_ir_values(common.TransactionCase):
|
||||
|
||||
class TestIrValues(TransactionCase):
|
||||
|
||||
def test_00(self):
|
||||
# Create some default value for some (non-existing) model, for all users.
|
||||
|
||||
ir_values = self.registry('ir.values')
|
||||
ir_values = self.env['ir.values']
|
||||
# use the old API
|
||||
ir_values.set(self.cr, self.uid, 'default', False, 'my_test_field',
|
||||
['unexisting_model'], 'global value')
|
||||
ir_values.set('default', False, 'my_test_field', ['unexisting_model'],
|
||||
'global value')
|
||||
# use the new API
|
||||
ir_values.set_default(self.cr, self.uid, 'other_unexisting_model',
|
||||
'my_other_test_field', 'conditional value', condition='foo=bar')
|
||||
|
||||
ir_values.set_default('other_unexisting_model', 'my_other_test_field',
|
||||
'conditional value', condition='foo=bar')
|
||||
|
||||
# Retrieve them.
|
||||
|
||||
ir_values = self.registry('ir.values')
|
||||
# d is a list of triplets (id, name, value)
|
||||
# Old API
|
||||
d = ir_values.get(self.cr, self.uid, 'default', False, ['unexisting_model'])
|
||||
assert len(d) == 1, "Only one single value should be retrieved for this model"
|
||||
assert d[0][1] == 'my_test_field', "Can't retrieve the created default value. (1)"
|
||||
assert d[0][2] == 'global value', "Can't retrieve the created default value. (2)"
|
||||
d = ir_values.get('default', False, ['unexisting_model'])
|
||||
self.assertEqual(len(d), 1, "Only one single value should be retrieved for this model")
|
||||
self.assertEqual(d[0][1], 'my_test_field', "Can't retrieve the created default value. (1)")
|
||||
self.assertEqual(d[0][2], 'global value', "Can't retrieve the created default value. (2)")
|
||||
|
||||
# New API, Conditional version
|
||||
d = ir_values.get_defaults(self.cr, self.uid, 'other_unexisting_model')
|
||||
assert len(d) == 0, "No value should be retrieved, the condition is not met"
|
||||
d = ir_values.get_defaults(self.cr, self.uid, 'other_unexisting_model', condition="foo=eggs")
|
||||
assert len(d) == 0, 'Condition is not met either, no defaults should be returned'
|
||||
d = ir_values.get_defaults(self.cr, self.uid, 'other_unexisting_model', condition="foo=bar")
|
||||
assert len(d) == 1, "Only one single value should be retrieved"
|
||||
assert d[0][1] == 'my_other_test_field', "Can't retrieve the created default value. (5)"
|
||||
assert d[0][2] == 'conditional value', "Can't retrieve the created default value. (6)"
|
||||
d = ir_values.get_defaults('other_unexisting_model')
|
||||
self.assertEqual(len(d), 0, "No value should be retrieved, the condition is not met")
|
||||
d = ir_values.get_defaults('other_unexisting_model', condition="foo=eggs")
|
||||
self.assertEqual(len(d), 0, 'Condition is not met either, no defaults should be returned')
|
||||
d = ir_values.get_defaults('other_unexisting_model', condition="foo=bar")
|
||||
self.assertEqual(len(d), 1, "Only one single value should be retrieved")
|
||||
self.assertEqual(d[0][1], 'my_other_test_field', "Can't retrieve the created default value. (5)")
|
||||
self.assertEqual(d[0][2], 'conditional value', "Can't retrieve the created default value. (6)")
|
||||
|
||||
# Do it again but for a specific user.
|
||||
|
||||
ir_values = self.registry('ir.values')
|
||||
ir_values.set(self.cr, self.uid, 'default', False, 'my_test_field',['unexisting_model'], 'specific value', preserve_user=True)
|
||||
ir_values.set('default', False, 'my_test_field',['unexisting_model'],
|
||||
'specific value', preserve_user=True)
|
||||
|
||||
# Retrieve it and check it is the one for the current user.
|
||||
ir_values = self.registry('ir.values')
|
||||
d = ir_values.get(self.cr, self.uid, 'default', False, ['unexisting_model'])
|
||||
assert len(d) == 1, "Only one default must be returned per field"
|
||||
assert d[0][1] == 'my_test_field', "Can't retrieve the created default value."
|
||||
assert d[0][2] == 'specific value', "Can't retrieve the created default value."
|
||||
|
||||
d = ir_values.get('default', False, ['unexisting_model'])
|
||||
self.assertEqual(len(d), 1, "Only one default must be returned per field")
|
||||
self.assertEqual(d[0][1], 'my_test_field', "Can't retrieve the created default value.")
|
||||
self.assertEqual(d[0][2], 'specific value', "Can't retrieve the created default value.")
|
||||
|
||||
# Create some action bindings for a non-existing model.
|
||||
|
||||
act_id_1 = self.ref('base.act_values_form_action')
|
||||
act_id_2 = self.ref('base.act_values_form_defaults')
|
||||
act_id_3 = self.ref('base.action_res_company_form')
|
||||
|
||||
ir_values = self.registry('ir.values')
|
||||
ir_values.set(self.cr, self.uid, 'action', 'tree_but_open', 'OnDblClick Action', ['unexisting_model'], 'ir.actions.act_window,%d' % act_id_1, isobject=True)
|
||||
ir_values.set(self.cr, self.uid, 'action', 'tree_but_open', 'OnDblClick Action 2', ['unexisting_model'], 'ir.actions.act_window,%d' % act_id_2, isobject=True)
|
||||
ir_values.set(self.cr, self.uid, 'action', 'client_action_multi', 'Side Wizard', ['unexisting_model'], 'ir.actions.act_window,%d' % act_id_3, isobject=True)
|
||||
report_ids = self.registry('ir.actions.report.xml').search(self.cr, self.uid, [], {})
|
||||
reports = self.registry('ir.actions.report.xml').browse(self.cr, self.uid, report_ids, {})
|
||||
report_id = [report.id for report in reports if not report.groups_id][0] # assume at least one
|
||||
ir_values.set(self.cr, self.uid, 'action', 'client_print_multi', 'Nice Report', ['unexisting_model'], 'ir.actions.report.xml,%d' % report_id, isobject=True)
|
||||
ir_values.set('action', 'tree_but_open', 'OnDblClick Action', ['unexisting_model'],
|
||||
'ir.actions.act_window,%d' % act_id_1, isobject=True)
|
||||
ir_values.set('action', 'tree_but_open', 'OnDblClick Action 2', ['unexisting_model'],
|
||||
'ir.actions.act_window,%d' % act_id_2, isobject=True)
|
||||
ir_values.set('action', 'client_action_multi', 'Side Wizard', ['unexisting_model'],
|
||||
'ir.actions.act_window,%d' % act_id_3, isobject=True)
|
||||
reports = self.env['ir.actions.report.xml'].search([])
|
||||
report_id = next(report.id for report in reports if not report.groups_id)
|
||||
ir_values.set('action', 'client_print_multi', 'Nice Report', ['unexisting_model'],
|
||||
'ir.actions.report.xml,%d' % report_id, isobject=True)
|
||||
|
||||
# Replace one action binding to set a new name.
|
||||
|
||||
ir_values = self.registry('ir.values')
|
||||
ir_values.set(self.cr, self.uid, 'action', 'tree_but_open', 'OnDblClick Action New', ['unexisting_model'], 'ir.actions.act_window,%d' % act_id_1, isobject=True)
|
||||
ir_values.set('action', 'tree_but_open', 'OnDblClick Action New', ['unexisting_model'],
|
||||
'ir.actions.act_window,%d' % act_id_1, isobject=True)
|
||||
|
||||
# Retrieve the action bindings and check they're correct
|
||||
|
||||
ir_values = self.registry('ir.values')
|
||||
actions = ir_values.get(self.cr, self.uid, 'action', 'tree_but_open', ['unexisting_model'])
|
||||
assert len(actions) == 2, "Mismatching number of bound actions"
|
||||
actions = ir_values.get('action', 'tree_but_open', ['unexisting_model'])
|
||||
self.assertEqual(len(actions), 2, "Mismatching number of bound actions")
|
||||
#first action
|
||||
assert len(actions[0]) == 3, "Malformed action definition"
|
||||
assert actions[0][1] == 'OnDblClick Action 2', 'Bound action does not match definition'
|
||||
assert isinstance(actions[0][2], dict) and actions[0][2]['id'] == act_id_2, 'Bound action does not match definition'
|
||||
self.assertEqual(len(actions[0]), 3, "Malformed action definition")
|
||||
self.assertEqual(actions[0][1], 'OnDblClick Action 2', 'Bound action does not match definition')
|
||||
self.assertTrue(isinstance(actions[0][2], dict) and actions[0][2]['id'] == act_id_2,
|
||||
'Bound action does not match definition')
|
||||
#second action - this ones comes last because it was re-created with a different name
|
||||
assert len(actions[1]) == 3, "Malformed action definition"
|
||||
assert actions[1][1] == 'OnDblClick Action New', 'Re-Registering an action should replace it'
|
||||
assert isinstance(actions[1][2], dict) and actions[1][2]['id'] == act_id_1, 'Bound action does not match definition'
|
||||
self.assertEqual(len(actions[1]), 3, "Malformed action definition")
|
||||
self.assertEqual(actions[1][1], 'OnDblClick Action New', 'Re-Registering an action should replace it')
|
||||
self.assertTrue(isinstance(actions[1][2], dict) and actions[1][2]['id'] == act_id_1,
|
||||
'Bound action does not match definition')
|
||||
|
||||
actions = ir_values.get(self.cr, self.uid, 'action', 'client_action_multi', ['unexisting_model'])
|
||||
assert len(actions) == 1, "Mismatching number of bound actions"
|
||||
assert len(actions[0]) == 3, "Malformed action definition"
|
||||
assert actions[0][1] == 'Side Wizard', 'Bound action does not match definition'
|
||||
assert isinstance(actions[0][2], dict) and actions[0][2]['id'] == act_id_3, 'Bound action does not match definition'
|
||||
actions = ir_values.get('action', 'client_action_multi', ['unexisting_model'])
|
||||
self.assertEqual(len(actions), 1, "Mismatching number of bound actions")
|
||||
self.assertEqual(len(actions[0]), 3, "Malformed action definition")
|
||||
self.assertEqual(actions[0][1], 'Side Wizard', 'Bound action does not match definition')
|
||||
self.assertTrue(isinstance(actions[0][2], dict) and actions[0][2]['id'] == act_id_3,
|
||||
'Bound action does not match definition')
|
||||
|
||||
actions = ir_values.get(self.cr, self.uid, 'action', 'client_print_multi', ['unexisting_model'])
|
||||
assert len(actions) == 1, "Mismatching number of bound actions"
|
||||
assert len(actions[0]) == 3, "Malformed action definition"
|
||||
assert actions[0][1] == 'Nice Report', 'Bound action does not match definition'
|
||||
assert isinstance(actions[0][2], dict) and actions[0][2]['id'] == report_id, 'Bound action does not match definition'
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
actions = ir_values.get('action', 'client_print_multi', ['unexisting_model'])
|
||||
self.assertEqual(len(actions), 1, "Mismatching number of bound actions")
|
||||
self.assertEqual(len(actions[0]), 3, "Malformed action definition")
|
||||
self.assertEqual(actions[0][1], 'Nice Report', 'Bound action does not match definition')
|
||||
self.assertTrue(isinstance(actions[0][2], dict) and actions[0][2]['id'] == report_id,
|
||||
'Bound action does not match definition')
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# This test can be run stand-alone with something like:
|
||||
# > PYTHONPATH=. python2 openerp/tests/test_misc.py
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import cgi
|
||||
import unittest
|
||||
|
||||
from openerp.tools import html_sanitize, append_content_to_html, plaintext2html, email_split
|
||||
import test_mail_examples
|
||||
from odoo.tools import html_sanitize, append_content_to_html, plaintext2html, email_split
|
||||
from . import test_mail_examples
|
||||
|
||||
|
||||
class TestSanitizer(unittest.TestCase):
|
||||
@@ -325,7 +322,3 @@ class TestEmailTools(unittest.TestCase):
|
||||
]
|
||||
for text, expected in cases:
|
||||
self.assertEqual(email_split(text), expected, 'email_split is broken')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
MISC_HTML_SOURCE = """
|
||||
<font size="2" style="color: rgb(31, 31, 31); font-family: monospace; font-variant: normal; line-height: normal; ">test1</font>
|
||||
@@ -515,4 +515,4 @@ REMOVE_CLASS_IN = [
|
||||
REMOVE_CLASS_OUT = [
|
||||
'<div class="modal-backdrop in">',
|
||||
'<div class="modal-content openerp">',
|
||||
'<div class="modal-header">']
|
||||
'<div class="modal-header">']
|
||||
|
||||
@@ -1,32 +1,30 @@
|
||||
import openerp.tests.common as common
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
class test_menu(common.TransactionCase):
|
||||
from odoo.tests.common import TransactionCase
|
||||
|
||||
def setUp(self):
|
||||
super(test_menu,self).setUp()
|
||||
self.Menus = self.registry('ir.ui.menu')
|
||||
|
||||
class TestMenu(TransactionCase):
|
||||
|
||||
def test_00_menu_deletion(self):
|
||||
"""Verify that menu deletion works properly when there are child menus, and those
|
||||
are indeed made orphans"""
|
||||
cr, uid, Menus = self.cr, self.uid, self.Menus
|
||||
|
||||
# Generic trick necessary for search() calls to avoid hidden menus
|
||||
ctx = {'ir.ui.menu.full_list': True}
|
||||
|
||||
root_id = Menus.create(cr, uid, {'name': 'Test root'})
|
||||
child1_id = Menus.create(cr, uid, {'name': 'Test child 1', 'parent_id': root_id})
|
||||
child2_id = Menus.create(cr, uid, {'name': 'Test child 2', 'parent_id': root_id})
|
||||
child21_id = Menus.create(cr, uid, {'name': 'Test child 2-1', 'parent_id': child2_id})
|
||||
|
||||
all_ids = [root_id, child1_id, child2_id, child21_id]
|
||||
Menu = self.env['ir.ui.menu']
|
||||
root = Menu.create({'name': 'Test root'})
|
||||
child1 = Menu.create({'name': 'Test child 1', 'parent_id': root.id})
|
||||
child2 = Menu.create({'name': 'Test child 2', 'parent_id': root.id})
|
||||
child21 = Menu.create({'name': 'Test child 2-1', 'parent_id': child2.id})
|
||||
all_ids = [root.id, child1.id, child2.id, child21.id]
|
||||
|
||||
# delete and check that direct children are promoted to top-level
|
||||
# cfr. explanation in menu.unlink()
|
||||
Menus.unlink(cr, uid, [root_id])
|
||||
root.unlink()
|
||||
|
||||
remaining_ids = Menus.search(cr, uid, [('id', 'in', all_ids)], order="id", context=ctx)
|
||||
self.assertEqual([child1_id, child2_id, child21_id], remaining_ids)
|
||||
# Generic trick necessary for search() calls to avoid hidden menus
|
||||
Menu = self.env['ir.ui.menu'].with_context({'ir.ui.menu.full_list': True})
|
||||
|
||||
orphan_ids = Menus.search(cr, uid, [('id', 'in', all_ids), ('parent_id', '=', False)], order="id", context=ctx)
|
||||
self.assertEqual([child1_id, child2_id], orphan_ids)
|
||||
remaining = Menu.search([('id', 'in', all_ids)], order="id")
|
||||
self.assertEqual([child1.id, child2.id, child21.id], remaining.ids)
|
||||
|
||||
orphans = Menu.search([('id', 'in', all_ids), ('parent_id', '=', False)], order="id")
|
||||
self.assertEqual([child1.id, child2.id], orphans.ids)
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import unittest
|
||||
|
||||
from openerp.tools import misc
|
||||
from odoo.tools import misc
|
||||
|
||||
|
||||
class test_countingstream(unittest.TestCase):
|
||||
class TestCountingStream(unittest.TestCase):
|
||||
def test_empty_stream(self):
|
||||
s = misc.CountingStream(iter([]))
|
||||
self.assertEqual(s.index, -1)
|
||||
@@ -32,6 +35,3 @@ class test_countingstream(unittest.TestCase):
|
||||
self.assertEqual(s.index, 0)
|
||||
self.assertIsNone(next(s, None))
|
||||
self.assertEqual(s.index, 0)
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
@@ -1,117 +1,125 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from collections import defaultdict
|
||||
from openerp.tools import mute_logger
|
||||
from openerp.tests import common
|
||||
|
||||
UID = common.ADMIN_USER_ID
|
||||
from odoo.exceptions import AccessError, MissingError
|
||||
from odoo.tests.common import TransactionCase
|
||||
from odoo.tools import mute_logger
|
||||
|
||||
|
||||
class TestORM(common.TransactionCase):
|
||||
""" test special behaviors of ORM CRUD functions
|
||||
|
||||
TODO: use real Exceptions types instead of Exception """
|
||||
|
||||
def setUp(self):
|
||||
super(TestORM, self).setUp()
|
||||
cr, uid = self.cr, self.uid
|
||||
self.partner = self.registry('res.partner')
|
||||
self.users = self.registry('res.users')
|
||||
self.p1 = self.partner.name_create(cr, uid, 'W')[0]
|
||||
self.p2 = self.partner.name_create(cr, uid, 'Y')[0]
|
||||
self.ir_rule = self.registry('ir.rule')
|
||||
|
||||
# sample unprivileged user
|
||||
employee_gid = self.ref('base.group_user')
|
||||
self.uid2 = self.users.create(cr, uid, {'name': 'test user', 'login': 'test', 'groups_id': [4,employee_gid]})
|
||||
class TestORM(TransactionCase):
|
||||
""" test special behaviors of ORM CRUD functions """
|
||||
|
||||
@mute_logger('openerp.models')
|
||||
def testAccessDeletedRecords(self):
|
||||
def test_access_deleted_records(self):
|
||||
""" Verify that accessing deleted records works as expected """
|
||||
cr, uid, uid2, p1, p2 = self.cr, self.uid, self.uid2, self.p1, self.p2
|
||||
self.partner.unlink(cr, uid, [p1])
|
||||
p1 = self.env['res.partner'].create({'name': 'W'})
|
||||
p2 = self.env['res.partner'].create({'name': 'Y'})
|
||||
p1.unlink()
|
||||
|
||||
# read() is expected to skip deleted records because our API is not
|
||||
# transactional for a sequence of search()->read() performed from the
|
||||
# client-side... a concurrent deletion could therefore cause spurious
|
||||
# exceptions even when simply opening a list view!
|
||||
# /!\ Using unprileged user to detect former side effects of ir.rules!
|
||||
self.assertEqual([{'id': p2, 'name': 'Y'}], self.partner.read(cr, uid2, [p1,p2], ['name']), "read() should skip deleted records")
|
||||
self.assertEqual([], self.partner.read(cr, uid2, [p1], ['name']), "read() should skip deleted records")
|
||||
user = self.env['res.users'].create({
|
||||
'name': 'test user',
|
||||
'login': 'test2',
|
||||
'groups_id': [4, self.ref('base.group_user')],
|
||||
})
|
||||
ps = (p1 + p2).sudo(user)
|
||||
self.assertEqual([{'id': p2.id, 'name': 'Y'}], ps.read(['name']), "read() should skip deleted records")
|
||||
self.assertEqual([], ps[0].read(['name']), "read() should skip deleted records")
|
||||
|
||||
# Deleting an already deleted record should be simply ignored
|
||||
self.assertTrue(self.partner.unlink(cr, uid, [p1]), "Re-deleting should be a no-op")
|
||||
self.assertTrue(p1.unlink(), "Re-deleting should be a no-op")
|
||||
|
||||
# Updating an already deleted record should raise, even as admin
|
||||
with self.assertRaises(Exception):
|
||||
self.partner.write(cr, uid, [p1], {'name': 'foo'})
|
||||
with self.assertRaises(MissingError):
|
||||
p1.write({'name': 'foo'})
|
||||
|
||||
@mute_logger('openerp.models')
|
||||
def testAccessFilteredRecords(self):
|
||||
def test_access_filtered_records(self):
|
||||
""" Verify that accessing filtered records works as expected for non-admin user """
|
||||
cr, uid, uid2, p1, p2 = self.cr, self.uid, self.uid2, self.p1, self.p2
|
||||
partner_model = self.registry('ir.model').search(cr, uid, [('model','=','res.partner')])[0]
|
||||
self.ir_rule.create(cr, uid, {'name': 'Y is invisible',
|
||||
'domain_force': [('id', '!=', p1)],
|
||||
'model_id': partner_model})
|
||||
p1 = self.env['res.partner'].create({'name': 'W'})
|
||||
p2 = self.env['res.partner'].create({'name': 'Y'})
|
||||
user = self.env['res.users'].create({
|
||||
'name': 'test user',
|
||||
'login': 'test2',
|
||||
'groups_id': [4, self.ref('base.group_user')],
|
||||
})
|
||||
|
||||
partner_model = self.env['ir.model'].search([('model','=','res.partner')])
|
||||
self.env['ir.rule'].create({
|
||||
'name': 'Y is invisible',
|
||||
'domain_force': [('id', '!=', p1.id)],
|
||||
'model_id': partner_model.id,
|
||||
})
|
||||
|
||||
# search as unprivileged user
|
||||
partners = self.partner.search(cr, uid2, [])
|
||||
self.assertFalse(p1 in partners, "W should not be visible...")
|
||||
self.assertTrue(p2 in partners, "... but Y should be visible")
|
||||
partners = self.env['res.partner'].sudo(user).search([])
|
||||
self.assertNotIn(p1, partners, "W should not be visible...")
|
||||
self.assertIn(p2, partners, "... but Y should be visible")
|
||||
|
||||
# read as unprivileged user
|
||||
with self.assertRaises(Exception):
|
||||
self.partner.read(cr, uid2, [p1], ['name'])
|
||||
with self.assertRaises(AccessError):
|
||||
p1.sudo(user).read(['name'])
|
||||
# write as unprivileged user
|
||||
with self.assertRaises(Exception):
|
||||
self.partner.write(cr, uid2, [p1], {'name': 'foo'})
|
||||
with self.assertRaises(AccessError):
|
||||
p1.sudo(user).write({'name': 'foo'})
|
||||
# unlink as unprivileged user
|
||||
with self.assertRaises(Exception):
|
||||
self.partner.unlink(cr, uid2, [p1])
|
||||
with self.assertRaises(AccessError):
|
||||
p1.sudo(user).unlink()
|
||||
|
||||
# Prepare mixed case
|
||||
self.partner.unlink(cr, uid, [p2])
|
||||
p2.unlink()
|
||||
# read mixed records: some deleted and some filtered
|
||||
with self.assertRaises(Exception):
|
||||
self.partner.read(cr, uid2, [p1,p2], ['name'])
|
||||
with self.assertRaises(AccessError):
|
||||
(p1 + p2).sudo(user).read(['name'])
|
||||
# delete mixed records: some deleted and some filtered
|
||||
with self.assertRaises(Exception):
|
||||
self.partner.unlink(cr, uid2, [p1,p2])
|
||||
with self.assertRaises(AccessError):
|
||||
(p1 + p2).sudo(user).unlink()
|
||||
|
||||
def test_multi_read(self):
|
||||
record_id = self.partner.create(self.cr, UID, {'name': 'MyPartner1'})
|
||||
records = self.partner.read(self.cr, UID, [record_id])
|
||||
self.assertIsInstance(records, list)
|
||||
def test_read(self):
|
||||
partner = self.env['res.partner'].create({'name': 'MyPartner1'})
|
||||
result = partner.read()
|
||||
self.assertIsInstance(result, list)
|
||||
|
||||
def test_one_read(self):
|
||||
record_id = self.partner.create(self.cr, UID, {'name': 'MyPartner1'})
|
||||
record = self.partner.read(self.cr, UID, record_id)
|
||||
self.assertIsInstance(record, dict)
|
||||
# special cases for the old API only
|
||||
result = self.registry('res.partner').read(self.cr, self.uid, [partner.id])
|
||||
self.assertIsInstance(result, list)
|
||||
result = self.registry('res.partner').read(self.cr, self.uid, partner.id)
|
||||
self.assertIsInstance(result, dict)
|
||||
|
||||
@mute_logger('openerp.models')
|
||||
def test_search_read(self):
|
||||
partner = self.env['res.partner']
|
||||
|
||||
# simple search_read
|
||||
self.partner.create(self.cr, UID, {'name': 'MyPartner1'})
|
||||
found = self.partner.search_read(self.cr, UID, [['name', '=', 'MyPartner1']], ['name'])
|
||||
partner.create({'name': 'MyPartner1'})
|
||||
found = partner.search_read([('name', '=', 'MyPartner1')], ['name'])
|
||||
self.assertEqual(len(found), 1)
|
||||
self.assertEqual(found[0]['name'], 'MyPartner1')
|
||||
self.assertTrue('id' in found[0])
|
||||
self.assertIn('id', found[0])
|
||||
|
||||
# search_read correct order
|
||||
self.partner.create(self.cr, UID, {'name': 'MyPartner2'})
|
||||
found = self.partner.search_read(self.cr, UID, [['name', 'like', 'MyPartner']], ['name'], order="name")
|
||||
partner.create({'name': 'MyPartner2'})
|
||||
found = partner.search_read([('name', 'like', 'MyPartner')], ['name'], order="name")
|
||||
self.assertEqual(len(found), 2)
|
||||
self.assertEqual(found[0]['name'], 'MyPartner1')
|
||||
self.assertEqual(found[1]['name'], 'MyPartner2')
|
||||
found = self.partner.search_read(self.cr, UID, [['name', 'like', 'MyPartner']], ['name'], order="name desc")
|
||||
found = partner.search_read([('name', 'like', 'MyPartner')], ['name'], order="name desc")
|
||||
self.assertEqual(len(found), 2)
|
||||
self.assertEqual(found[0]['name'], 'MyPartner2')
|
||||
self.assertEqual(found[1]['name'], 'MyPartner1')
|
||||
|
||||
# search_read that finds nothing
|
||||
found = self.partner.search_read(self.cr, UID, [['name', '=', 'Does not exists']], ['name'])
|
||||
found = partner.search_read([('name', '=', 'Does not exists')], ['name'])
|
||||
self.assertEqual(len(found), 0)
|
||||
|
||||
def test_exists(self):
|
||||
partner = self.partner.browse(self.cr, UID, [])
|
||||
partner = self.env['res.partner']
|
||||
|
||||
# check that records obtained from search exist
|
||||
recs = partner.search([])
|
||||
@@ -123,7 +131,7 @@ class TestORM(common.TransactionCase):
|
||||
self.assertFalse(recs.exists())
|
||||
|
||||
def test_groupby_date(self):
|
||||
partners = dict(
|
||||
partners_data = dict(
|
||||
A='2012-11-19',
|
||||
B='2012-12-17',
|
||||
C='2012-12-31',
|
||||
@@ -133,42 +141,37 @@ class TestORM(common.TransactionCase):
|
||||
G='2013-02-11',
|
||||
)
|
||||
|
||||
all_partners = []
|
||||
partners_by_day = defaultdict(set)
|
||||
partners_by_month = defaultdict(set)
|
||||
partners_by_year = defaultdict(set)
|
||||
partner_ids = []
|
||||
partner_ids_by_day = defaultdict(list)
|
||||
partner_ids_by_month = defaultdict(list)
|
||||
partner_ids_by_year = defaultdict(list)
|
||||
|
||||
for name, date in partners.items():
|
||||
p = self.partner.create(self.cr, UID, dict(name=name, date=date))
|
||||
all_partners.append(p)
|
||||
partners_by_day[date].add(p)
|
||||
partners_by_month[date.rsplit('-', 1)[0]].add(p)
|
||||
partners_by_year[date.split('-', 1)[0]].add(p)
|
||||
partners = self.env['res.partner']
|
||||
for name, date in partners_data.items():
|
||||
p = partners.create(dict(name=name, date=date))
|
||||
partner_ids.append(p.id)
|
||||
partner_ids_by_day[date].append(p.id)
|
||||
partner_ids_by_month[date.rsplit('-', 1)[0]].append(p.id)
|
||||
partner_ids_by_year[date.split('-', 1)[0]].append(p.id)
|
||||
|
||||
def read_group(interval, domain=None):
|
||||
main_domain = [('id', 'in', all_partners)]
|
||||
if domain:
|
||||
domain = ['&'] + main_domain + domain
|
||||
else:
|
||||
domain = main_domain
|
||||
|
||||
rg = self.partner.read_group(self.cr, self.uid, domain, ['date'], 'date' + ':' + interval)
|
||||
def read_group(interval):
|
||||
domain = [('id', 'in', partner_ids)]
|
||||
result = {}
|
||||
for r in rg:
|
||||
result[r['date:' + interval]] = set(self.partner.search(self.cr, self.uid, r['__domain']))
|
||||
for grp in partners.read_group(domain, ['date'], ['date:' + interval]):
|
||||
result[grp['date:' + interval]] = partners.search(grp['__domain'])
|
||||
return result
|
||||
|
||||
self.assertEqual(len(read_group('day')), len(partners_by_day))
|
||||
self.assertEqual(len(read_group('month')), len(partners_by_month))
|
||||
self.assertEqual(len(read_group('year')), len(partners_by_year))
|
||||
self.assertEqual(len(read_group('day')), len(partner_ids_by_day))
|
||||
self.assertEqual(len(read_group('month')), len(partner_ids_by_month))
|
||||
self.assertEqual(len(read_group('year')), len(partner_ids_by_year))
|
||||
|
||||
rg = self.partner.read_group(self.cr, self.uid, [('id', 'in', all_partners)],
|
||||
['date'], ['date:month', 'date:day'], lazy=False)
|
||||
self.assertEqual(len(rg), len(all_partners))
|
||||
res = partners.read_group([('id', 'in', partner_ids)], ['date'],
|
||||
['date:month', 'date:day'], lazy=False)
|
||||
self.assertEqual(len(res), len(partner_ids))
|
||||
|
||||
def test_write_duplicate(self):
|
||||
cr, uid, p1 = self.cr, self.uid, self.p1
|
||||
self.partner.write(cr, uid, [p1, p1], {'name': 'X'})
|
||||
p1 = self.env['res.partner'].create({'name': 'W'})
|
||||
(p1 + p1).write({'name': 'X'})
|
||||
|
||||
def test_m2m_store_trigger(self):
|
||||
group_user = self.env.ref('base.group_user')
|
||||
@@ -187,110 +190,93 @@ class TestORM(common.TransactionCase):
|
||||
self.assertTrue(user.share)
|
||||
|
||||
|
||||
class TestInherits(common.TransactionCase):
|
||||
class TestInherits(TransactionCase):
|
||||
""" test the behavior of the orm for models that use _inherits;
|
||||
specifically: res.users, that inherits from res.partner
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super(TestInherits, self).setUp()
|
||||
self.partner = self.registry('res.partner')
|
||||
self.user = self.registry('res.users')
|
||||
|
||||
def test_default(self):
|
||||
""" `default_get` cannot return a dictionary or a new id """
|
||||
defaults = self.user.default_get(self.cr, UID, ['partner_id'])
|
||||
defaults = self.env['res.users'].default_get(['partner_id'])
|
||||
if 'partner_id' in defaults:
|
||||
self.assertIsInstance(defaults['partner_id'], (bool, int, long))
|
||||
|
||||
def test_create(self):
|
||||
""" creating a user should automatically create a new partner """
|
||||
partners_before = self.partner.search(self.cr, UID, [])
|
||||
foo_id = self.user.create(self.cr, UID, {'name': 'Foo', 'login': 'foo', 'password': 'foo'})
|
||||
foo = self.user.browse(self.cr, UID, foo_id)
|
||||
partners_before = self.env['res.partner'].search([])
|
||||
user_foo = self.env['res.users'].create({'name': 'Foo', 'login': 'foo'})
|
||||
|
||||
self.assertNotIn(foo.partner_id.id, partners_before)
|
||||
self.assertNotIn(user_foo.partner_id, partners_before)
|
||||
|
||||
def test_create_with_ancestor(self):
|
||||
""" creating a user with a specific 'partner_id' should not create a new partner """
|
||||
par_id = self.partner.create(self.cr, UID, {'name': 'Foo'})
|
||||
partners_before = self.partner.search(self.cr, UID, [])
|
||||
foo_id = self.user.create(self.cr, UID, {'partner_id': par_id, 'login': 'foo', 'password': 'foo'})
|
||||
partners_after = self.partner.search(self.cr, UID, [])
|
||||
partner_foo = self.env['res.partner'].create({'name': 'Foo'})
|
||||
partners_before = self.env['res.partner'].search([])
|
||||
user_foo = self.env['res.users'].create({'partner_id': partner_foo.id, 'login': 'foo'})
|
||||
partners_after = self.env['res.partner'].search([])
|
||||
|
||||
self.assertEqual(set(partners_before), set(partners_after))
|
||||
|
||||
foo = self.user.browse(self.cr, UID, foo_id)
|
||||
self.assertEqual(foo.name, 'Foo')
|
||||
self.assertEqual(foo.partner_id.id, par_id)
|
||||
self.assertEqual(partners_before, partners_after)
|
||||
self.assertEqual(user_foo.name, 'Foo')
|
||||
self.assertEqual(user_foo.partner_id, partner_foo)
|
||||
|
||||
@mute_logger('openerp.models')
|
||||
def test_read(self):
|
||||
""" inherited fields should be read without any indirection """
|
||||
foo_id = self.user.create(self.cr, UID, {'name': 'Foo', 'login': 'foo', 'password': 'foo'})
|
||||
foo_values, = self.user.read(self.cr, UID, [foo_id])
|
||||
partner_id = foo_values['partner_id'][0]
|
||||
partner_values, = self.partner.read(self.cr, UID, [partner_id])
|
||||
self.assertEqual(foo_values['name'], partner_values['name'])
|
||||
user_foo = self.env['res.users'].create({'name': 'Foo', 'login': 'foo'})
|
||||
user_values, = user_foo.read()
|
||||
partner_values, = user_foo.partner_id.read()
|
||||
|
||||
foo = self.user.browse(self.cr, UID, foo_id)
|
||||
self.assertEqual(foo.name, foo.partner_id.name)
|
||||
self.assertEqual(user_values['name'], partner_values['name'])
|
||||
self.assertEqual(user_foo.name, user_foo.partner_id.name)
|
||||
|
||||
@mute_logger('openerp.models')
|
||||
def test_copy(self):
|
||||
""" copying a user should automatically copy its partner, too """
|
||||
foo_id = self.user.create(self.cr, UID, {
|
||||
user_foo = self.env['res.users'].create({
|
||||
'name': 'Foo',
|
||||
'login': 'foo',
|
||||
'password': 'foo',
|
||||
'supplier': True,
|
||||
})
|
||||
foo_before, = self.user.read(self.cr, UID, [foo_id])
|
||||
foo_before, = user_foo.read()
|
||||
del foo_before['__last_update']
|
||||
bar_id = self.user.copy(self.cr, UID, foo_id, {
|
||||
'login': 'bar',
|
||||
'password': 'bar',
|
||||
})
|
||||
foo_after, = self.user.read(self.cr, UID, [foo_id])
|
||||
user_bar = user_foo.copy({'login': 'bar'})
|
||||
foo_after, = user_foo.read()
|
||||
del foo_after['__last_update']
|
||||
|
||||
self.assertEqual(foo_before, foo_after)
|
||||
|
||||
foo, bar = self.user.browse(self.cr, UID, [foo_id, bar_id])
|
||||
self.assertEqual(bar.name, 'Foo (copy)')
|
||||
self.assertEqual(bar.login, 'bar')
|
||||
self.assertEqual(foo.supplier, bar.supplier)
|
||||
self.assertNotEqual(foo.id, bar.id)
|
||||
self.assertNotEqual(foo.partner_id.id, bar.partner_id.id)
|
||||
self.assertEqual(user_bar.name, 'Foo (copy)')
|
||||
self.assertEqual(user_bar.login, 'bar')
|
||||
self.assertEqual(user_foo.supplier, user_bar.supplier)
|
||||
self.assertNotEqual(user_foo.id, user_bar.id)
|
||||
self.assertNotEqual(user_foo.partner_id.id, user_bar.partner_id.id)
|
||||
|
||||
@mute_logger('openerp.models')
|
||||
def test_copy_with_ancestor(self):
|
||||
""" copying a user with 'parent_id' in defaults should not duplicate the partner """
|
||||
foo_id = self.user.create(self.cr, UID, {'name': 'Foo', 'login': 'foo', 'password': 'foo',
|
||||
user_foo = self.env['res.users'].create({'name': 'Foo', 'login': 'foo', 'password': 'foo',
|
||||
'login_date': '2016-01-01', 'signature': 'XXX'})
|
||||
par_id = self.partner.create(self.cr, UID, {'name': 'Bar'})
|
||||
partner_bar = self.env['res.partner'].create({'name': 'Bar'})
|
||||
|
||||
foo_before, = self.user.read(self.cr, UID, [foo_id])
|
||||
foo_before, = user_foo.read()
|
||||
del foo_before['__last_update']
|
||||
del foo_before['login_date']
|
||||
partners_before = self.partner.search(self.cr, UID, [])
|
||||
bar_id = self.user.copy(self.cr, UID, foo_id, {'partner_id': par_id, 'login': 'bar'})
|
||||
foo_after, = self.user.read(self.cr, UID, [foo_id])
|
||||
partners_before = self.env['res.partner'].search([])
|
||||
user_bar = user_foo.copy({'partner_id': partner_bar.id, 'login': 'bar'})
|
||||
foo_after, = user_foo.read()
|
||||
del foo_after['__last_update']
|
||||
del foo_after['login_date']
|
||||
partners_after = self.partner.search(self.cr, UID, [])
|
||||
partners_after = self.env['res.partner'].search([])
|
||||
|
||||
self.assertEqual(foo_before, foo_after)
|
||||
self.assertEqual(set(partners_before), set(partners_after))
|
||||
|
||||
foo, bar = self.user.browse(self.cr, UID, [foo_id, bar_id])
|
||||
self.assertNotEqual(foo.id, bar.id)
|
||||
self.assertEqual(bar.partner_id.id, par_id)
|
||||
self.assertEqual(bar.login, 'bar', "login is given from copy parameters")
|
||||
self.assertFalse(bar.password, "password should not be copied from original record")
|
||||
self.assertEqual(bar.name, 'Bar', "name is given from specific partner")
|
||||
self.assertEqual(bar.signature, foo.signature, "signature should be copied")
|
||||
self.assertEqual(partners_before, partners_after)
|
||||
|
||||
self.assertNotEqual(user_foo.id, user_bar.id)
|
||||
self.assertEqual(user_bar.partner_id.id, partner_bar.id)
|
||||
self.assertEqual(user_bar.login, 'bar', "login is given from copy parameters")
|
||||
self.assertFalse(user_bar.password, "password should not be copied from original record")
|
||||
self.assertEqual(user_bar.name, 'Bar', "name is given from specific partner")
|
||||
self.assertEqual(user_bar.signature, user_foo.signature, "signature should be copied")
|
||||
|
||||
|
||||
CREATE = lambda values: (0, False, values)
|
||||
@@ -301,11 +287,8 @@ LINK_TO = lambda id: (4, id, False)
|
||||
DELETE_ALL = lambda: (5, False, False)
|
||||
REPLACE_WITH = lambda ids: (6, False, ids)
|
||||
|
||||
def sorted_by_id(list_of_dicts):
|
||||
"sort dictionaries by their 'id' field; useful for comparisons"
|
||||
return sorted(list_of_dicts, key=lambda d: d.get('id'))
|
||||
|
||||
class TestO2MSerialization(common.TransactionCase):
|
||||
class TestO2MSerialization(TransactionCase):
|
||||
""" test the orm method 'write' on one2many fields """
|
||||
|
||||
def setUp(self):
|
||||
@@ -314,136 +297,121 @@ class TestO2MSerialization(common.TransactionCase):
|
||||
|
||||
def test_no_command(self):
|
||||
" empty list of commands yields an empty list of records "
|
||||
results = self.partner.resolve_2many_commands(
|
||||
self.cr, UID, 'child_ids', [])
|
||||
|
||||
results = self.env['res.partner'].resolve_2many_commands('child_ids', [])
|
||||
self.assertEqual(results, [])
|
||||
|
||||
def test_CREATE_commands(self):
|
||||
" returns the VALUES dict as-is "
|
||||
values = [{'foo': 'bar'}, {'foo': 'baz'}, {'foo': 'baq'}]
|
||||
results = self.partner.resolve_2many_commands(
|
||||
self.cr, UID, 'child_ids', map(CREATE, values))
|
||||
|
||||
results = self.env['res.partner'].resolve_2many_commands('child_ids', map(CREATE, values))
|
||||
self.assertEqual(results, values)
|
||||
|
||||
def test_LINK_TO_command(self):
|
||||
" reads the records from the database, records are returned with their ids. "
|
||||
ids = [
|
||||
self.partner.create(self.cr, UID, {'name': 'foo'}),
|
||||
self.partner.create(self.cr, UID, {'name': 'bar'}),
|
||||
self.partner.create(self.cr, UID, {'name': 'baz'})
|
||||
self.env['res.partner'].create({'name': 'foo'}).id,
|
||||
self.env['res.partner'].create({'name': 'bar'}).id,
|
||||
self.env['res.partner'].create({'name': 'baz'}).id,
|
||||
]
|
||||
commands = map(LINK_TO, ids)
|
||||
|
||||
results = self.partner.resolve_2many_commands(
|
||||
self.cr, UID, 'child_ids', commands, ['name'])
|
||||
|
||||
self.assertEqual(sorted_by_id(results), sorted_by_id([
|
||||
results = self.env['res.partner'].resolve_2many_commands('child_ids', commands, ['name'])
|
||||
self.assertItemsEqual(results, [
|
||||
{'id': ids[0], 'name': 'foo'},
|
||||
{'id': ids[1], 'name': 'bar'},
|
||||
{'id': ids[2], 'name': 'baz'}
|
||||
]))
|
||||
{'id': ids[2], 'name': 'baz'},
|
||||
])
|
||||
|
||||
def test_bare_ids_command(self):
|
||||
" same as the equivalent LINK_TO commands "
|
||||
ids = [
|
||||
self.partner.create(self.cr, UID, {'name': 'foo'}),
|
||||
self.partner.create(self.cr, UID, {'name': 'bar'}),
|
||||
self.partner.create(self.cr, UID, {'name': 'baz'})
|
||||
self.env['res.partner'].create({'name': 'foo'}).id,
|
||||
self.env['res.partner'].create({'name': 'bar'}).id,
|
||||
self.env['res.partner'].create({'name': 'baz'}).id,
|
||||
]
|
||||
|
||||
results = self.partner.resolve_2many_commands(
|
||||
self.cr, UID, 'child_ids', ids, ['name'])
|
||||
|
||||
self.assertEqual(sorted_by_id(results), sorted_by_id([
|
||||
results = self.env['res.partner'].resolve_2many_commands('child_ids', ids, ['name'])
|
||||
self.assertItemsEqual(results, [
|
||||
{'id': ids[0], 'name': 'foo'},
|
||||
{'id': ids[1], 'name': 'bar'},
|
||||
{'id': ids[2], 'name': 'baz'}
|
||||
]))
|
||||
{'id': ids[2], 'name': 'baz'},
|
||||
])
|
||||
|
||||
def test_UPDATE_command(self):
|
||||
" take the in-db records and merge the provided information in "
|
||||
id_foo = self.partner.create(self.cr, UID, {'name': 'foo'})
|
||||
id_bar = self.partner.create(self.cr, UID, {'name': 'bar'})
|
||||
id_baz = self.partner.create(self.cr, UID, {'name': 'baz', 'city': 'tag'})
|
||||
foo = self.env['res.partner'].create({'name': 'foo'})
|
||||
bar = self.env['res.partner'].create({'name': 'bar'})
|
||||
baz = self.env['res.partner'].create({'name': 'baz', 'city': 'tag'})
|
||||
commands = [
|
||||
LINK_TO(foo.id),
|
||||
UPDATE(bar.id, {'name': 'qux', 'city': 'tagtag'}),
|
||||
UPDATE(baz.id, {'name': 'quux'}),
|
||||
]
|
||||
|
||||
results = self.partner.resolve_2many_commands(
|
||||
self.cr, UID, 'child_ids', [
|
||||
LINK_TO(id_foo),
|
||||
UPDATE(id_bar, {'name': 'qux', 'city': 'tagtag'}),
|
||||
UPDATE(id_baz, {'name': 'quux'})
|
||||
], ['name', 'city'])
|
||||
|
||||
self.assertEqual(sorted_by_id(results), sorted_by_id([
|
||||
{'id': id_foo, 'name': 'foo', 'city': False},
|
||||
{'id': id_bar, 'name': 'qux', 'city': 'tagtag'},
|
||||
{'id': id_baz, 'name': 'quux', 'city': 'tag'}
|
||||
]))
|
||||
results = self.env['res.partner'].resolve_2many_commands('child_ids', commands, ['name', 'city'])
|
||||
self.assertItemsEqual(results, [
|
||||
{'id': foo.id, 'name': 'foo', 'city': False},
|
||||
{'id': bar.id, 'name': 'qux', 'city': 'tagtag'},
|
||||
{'id': baz.id, 'name': 'quux', 'city': 'tag'},
|
||||
])
|
||||
|
||||
def test_DELETE_command(self):
|
||||
" deleted records are not returned at all. "
|
||||
ids = [
|
||||
self.partner.create(self.cr, UID, {'name': 'foo'}),
|
||||
self.partner.create(self.cr, UID, {'name': 'bar'}),
|
||||
self.partner.create(self.cr, UID, {'name': 'baz'})
|
||||
self.env['res.partner'].create({'name': 'foo'}).id,
|
||||
self.env['res.partner'].create({'name': 'bar'}).id,
|
||||
self.env['res.partner'].create({'name': 'baz'}).id,
|
||||
]
|
||||
commands = [DELETE(ids[0]), DELETE(ids[1]), DELETE(ids[2])]
|
||||
|
||||
results = self.partner.resolve_2many_commands(
|
||||
self.cr, UID, 'child_ids', commands, ['name'])
|
||||
commands = map(DELETE, ids)
|
||||
|
||||
results = self.env['res.partner'].resolve_2many_commands('child_ids', commands, ['name'])
|
||||
self.assertEqual(results, [])
|
||||
|
||||
def test_mixed_commands(self):
|
||||
ids = [
|
||||
self.partner.create(self.cr, UID, {'name': name})
|
||||
self.env['res.partner'].create({'name': name}).id
|
||||
for name in ['NObar', 'baz', 'qux', 'NOquux', 'NOcorge', 'garply']
|
||||
]
|
||||
commands = [
|
||||
CREATE({'name': 'foo'}),
|
||||
UPDATE(ids[0], {'name': 'bar'}),
|
||||
LINK_TO(ids[1]),
|
||||
DELETE(ids[2]),
|
||||
UPDATE(ids[3], {'name': 'quux',}),
|
||||
UPDATE(ids[4], {'name': 'corge'}),
|
||||
CREATE({'name': 'grault'}),
|
||||
LINK_TO(ids[5]),
|
||||
]
|
||||
|
||||
results = self.partner.resolve_2many_commands(
|
||||
self.cr, UID, 'child_ids', [
|
||||
CREATE({'name': 'foo'}),
|
||||
UPDATE(ids[0], {'name': 'bar'}),
|
||||
LINK_TO(ids[1]),
|
||||
DELETE(ids[2]),
|
||||
UPDATE(ids[3], {'name': 'quux',}),
|
||||
UPDATE(ids[4], {'name': 'corge'}),
|
||||
CREATE({'name': 'grault'}),
|
||||
LINK_TO(ids[5])
|
||||
], ['name'])
|
||||
|
||||
self.assertEqual(sorted_by_id(results), sorted_by_id([
|
||||
results = self.env['res.partner'].resolve_2many_commands('child_ids', commands, ['name'])
|
||||
self.assertItemsEqual(results, [
|
||||
{'name': 'foo'},
|
||||
{'id': ids[0], 'name': 'bar'},
|
||||
{'id': ids[1], 'name': 'baz'},
|
||||
{'id': ids[3], 'name': 'quux'},
|
||||
{'id': ids[4], 'name': 'corge'},
|
||||
{'name': 'grault'},
|
||||
{'id': ids[5], 'name': 'garply'}
|
||||
]))
|
||||
{'id': ids[5], 'name': 'garply'},
|
||||
])
|
||||
|
||||
def test_LINK_TO_pairs(self):
|
||||
"LINK_TO commands can be written as pairs, instead of triplets"
|
||||
ids = [
|
||||
self.partner.create(self.cr, UID, {'name': 'foo'}),
|
||||
self.partner.create(self.cr, UID, {'name': 'bar'}),
|
||||
self.partner.create(self.cr, UID, {'name': 'baz'})
|
||||
self.env['res.partner'].create({'name': 'foo'}).id,
|
||||
self.env['res.partner'].create({'name': 'bar'}).id,
|
||||
self.env['res.partner'].create({'name': 'baz'}).id,
|
||||
]
|
||||
commands = map(lambda id: (4, id), ids)
|
||||
commands = [(4, id) for id in ids]
|
||||
|
||||
results = self.partner.resolve_2many_commands(
|
||||
self.cr, UID, 'child_ids', commands, ['name'])
|
||||
|
||||
self.assertEqual(sorted_by_id(results), sorted_by_id([
|
||||
results = self.env['res.partner'].resolve_2many_commands('child_ids', commands, ['name'])
|
||||
self.assertItemsEqual(results, [
|
||||
{'id': ids[0], 'name': 'foo'},
|
||||
{'id': ids[1], 'name': 'bar'},
|
||||
{'id': ids[2], 'name': 'baz'}
|
||||
]))
|
||||
{'id': ids[2], 'name': 'baz'},
|
||||
])
|
||||
|
||||
def test_singleton_commands(self):
|
||||
"DELETE_ALL can appear as a singleton"
|
||||
results = self.partner.resolve_2many_commands(
|
||||
self.cr, UID, 'child_ids', [DELETE_ALL()], ['name'])
|
||||
|
||||
commands = [DELETE_ALL()]
|
||||
results = self.env['res.partner'].resolve_2many_commands('child_ids', commands, ['name'])
|
||||
self.assertEqual(results, [])
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from openerp.tests import common
|
||||
from openerp.tools import get_cache_key_counter
|
||||
from odoo.tests.common import TransactionCase
|
||||
from odoo.tools import get_cache_key_counter
|
||||
|
||||
class TestOrmcache(common.TransactionCase):
|
||||
|
||||
class TestOrmcache(TransactionCase):
|
||||
def test_ormcache(self):
|
||||
""" Test the effectiveness of the ormcache() decorator. """
|
||||
IMD = self.env['ir.model.data']
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import unittest
|
||||
from openerp.osv.query import Query
|
||||
|
||||
from odoo.osv.query import Query
|
||||
|
||||
|
||||
class QueryTestCase(unittest.TestCase):
|
||||
|
||||
@@ -1,632 +0,0 @@
|
||||
-
|
||||
Testing for hierarchical search in M2M
|
||||
-
|
||||
!python {model: res.partner }: |
|
||||
ids = self.search(cr, uid, [('category_id', 'child_of',ref('res_partner_category_0'))])
|
||||
assert len(ids) >= 1, ids
|
||||
-
|
||||
"1.0 Setup test partner categories: parent root"
|
||||
-
|
||||
!record {model: res.partner.category, id: categ_root}:
|
||||
name: Root category
|
||||
-
|
||||
"1.1 Setup test partner categories: parent category"
|
||||
-
|
||||
!record {model: res.partner.category, id: categ_0}:
|
||||
name: Parent category
|
||||
parent_id: categ_root
|
||||
-
|
||||
"1.2 Setup test partner categories: child 1"
|
||||
-
|
||||
!record {model: res.partner.category, id: categ_1}:
|
||||
name: Child 1
|
||||
parent_id: categ_0
|
||||
-
|
||||
Test hierarchical search in M2M with child ID (list of ids)
|
||||
-
|
||||
!python {model: res.partner.category }: |
|
||||
ids = self.search(cr, uid, [('id', 'child_of',[ref('categ_root')])])
|
||||
assert len(ids) == 3, ids
|
||||
-
|
||||
Test hierarchical search in M2M with child ID (single id)
|
||||
-
|
||||
!python {model: res.partner.category }: |
|
||||
ids = self.search(cr, uid, [('id', 'child_of',ref('categ_root'))])
|
||||
assert len(ids) == 3, ids
|
||||
-
|
||||
Test hierarchical search in M2M with child IDs
|
||||
-
|
||||
!python {model: res.partner.category }: |
|
||||
ids = self.search(cr, uid, [('id', 'child_of',[ref('categ_1'), ref('categ_0')])])
|
||||
assert len(ids) == 2, ids
|
||||
-
|
||||
Test hierarchical search in M2M with child IDs
|
||||
-
|
||||
!python {model: res.partner.category }: |
|
||||
ids = self.search(cr, uid, [('id', 'child_of',[ref('categ_0')])])
|
||||
assert len(ids) == 2, ids
|
||||
-
|
||||
Test hierarchical search in M2M with child IDs
|
||||
-
|
||||
!python {model: res.partner.category }: |
|
||||
ids = self.search(cr, uid, [('id', 'child_of',[ref('categ_1')])])
|
||||
assert len(ids) == 1, ids
|
||||
-
|
||||
Test hierarchical search in M2M with parent ID (list of ids)
|
||||
-
|
||||
!python {model: res.partner.category }: |
|
||||
ids = self.search(cr, uid, [('id', 'parent_of',[ref('categ_1')])])
|
||||
assert len(ids) == 3, ids
|
||||
-
|
||||
Test hierarchical search in M2M with parent ID (single id)
|
||||
-
|
||||
!python {model: res.partner.category }: |
|
||||
ids = self.search(cr, uid, [('id', 'parent_of',ref('categ_1'))])
|
||||
assert len(ids) == 3, ids
|
||||
-
|
||||
Test hierarchical search in M2M with parent IDs
|
||||
-
|
||||
!python {model: res.partner.category }: |
|
||||
ids = self.search(cr, uid, [('id', 'parent_of',[ref('categ_root'), ref('categ_0')])])
|
||||
assert len(ids) == 2, ids
|
||||
-
|
||||
Test hierarchical search in M2M with parent IDs
|
||||
-
|
||||
!python {model: res.partner.category }: |
|
||||
ids = self.search(cr, uid, [('id', 'parent_of',[ref('categ_0')])])
|
||||
assert len(ids) == 2, ids
|
||||
-
|
||||
Test hierarchical search in M2M with parent IDs
|
||||
-
|
||||
!python {model: res.partner.category }: |
|
||||
ids = self.search(cr, uid, [('id', 'parent_of',[ref('categ_1')])])
|
||||
assert len(ids) == 3, ids
|
||||
-
|
||||
Test hierarchical search in M2M with parent IDs
|
||||
-
|
||||
!python {model: res.partner.category }: |
|
||||
ids = self.search(cr, uid, [('id', 'parent_of',[ref('categ_root')])])
|
||||
assert len(ids) == 1, ids
|
||||
-
|
||||
Testing that some domain expressions work
|
||||
-
|
||||
!python {model: res.partner }: |
|
||||
ids = self.search(cr, uid, [('parent_id','=','Agrolait')])
|
||||
assert len(ids) >= 1, ids
|
||||
-
|
||||
Trying the "in" operator, for scalar value
|
||||
-
|
||||
!python {model: res.partner }: |
|
||||
ids = self.search(cr, uid, [('parent_id','in','Agrolait')])
|
||||
assert len(ids) >= 1, ids
|
||||
-
|
||||
Trying the "in" operator for list value
|
||||
-
|
||||
!python {model: res.partner }: |
|
||||
ids = self.search(cr, uid, [('parent_id','in',['Agrolait','ASUStek'])])
|
||||
assert len(ids) >= 1, ids
|
||||
-
|
||||
Check we can use "in" operator for plain fields.
|
||||
-
|
||||
!python {model: ir.ui.menu }: |
|
||||
ids = self.search(cr, uid, [('sequence','in',[1, 2, 10, 20])])
|
||||
assert len(ids) >= 1, ids
|
||||
-
|
||||
Test one2many operator with empty search list
|
||||
-
|
||||
!assert {model: res.partner, search: "[('child_ids', 'in', [])]", count: 0, string: "Ids should be empty"}
|
||||
-
|
||||
Test one2many operator with False
|
||||
-
|
||||
!assert {model: res.partner, search: "[('child_ids', '=', False)]"}:
|
||||
- list(child_ids) == []
|
||||
-
|
||||
Test many2many operator with empty search list
|
||||
-
|
||||
!assert {model: res.partner, search: "[('category_id', 'in', [])]", count: 0, string: "Ids should be empty"}
|
||||
-
|
||||
Test many2many operator with False
|
||||
-
|
||||
!assert {model: res.partner, search: "[('category_id', '=', False)]"}:
|
||||
- list(category_id) == []
|
||||
-
|
||||
Filtering on invalid value across x2many relationship should return an empty set
|
||||
-
|
||||
!assert {model: res.partner, search: "[('child_ids.city','=','foo')]", count: 0, string: "Searching for address.city = foo should give empty results"}
|
||||
-
|
||||
Check if many2one works with empty search list
|
||||
-
|
||||
!assert {model: res.partner, search: "[('company_id','in', [])]", count: 0, string: "Searching for company_id in [] should be empty!" }
|
||||
-
|
||||
For the sake of the following tests, I will create a second company
|
||||
-
|
||||
!record {model: res.company, id: ymltest_company2}:
|
||||
name: Acme 2
|
||||
-
|
||||
And create a few partners with that company or no company
|
||||
-
|
||||
!python {model: res.partner }: |
|
||||
for r in range(4):
|
||||
self.create(cr, uid, { 'name': 'P of Acme %d' % r,
|
||||
'company_id': ref('ymltest_company2') })
|
||||
for r in range(4):
|
||||
self.create(cr, uid, { 'name': 'P of All %d' % r,
|
||||
'company_id': False })
|
||||
-
|
||||
Check if many2one works with negative empty list
|
||||
-
|
||||
!python {model: res.partner }: |
|
||||
all_ids = self.search(cr, uid, [])
|
||||
all_ids.sort()
|
||||
res_ids = self.search(cr, uid,['|',('company_id','not in', []), ('company_id','=',False)])
|
||||
res_ids.sort()
|
||||
assert all_ids == res_ids, "not in [] fails"
|
||||
-
|
||||
Check that many2one will pick the correct records with a list
|
||||
-
|
||||
!python {model: res.partner }: |
|
||||
res_ids = self.search(cr, uid, [('company_id', 'in', [False,])])
|
||||
assert len(res_ids) >= 4, "We created 4 partners w/company, why find %d? %r" % \
|
||||
(len(res_ids), res_ids)
|
||||
-
|
||||
Check that many2one will exclude the correct records with a list
|
||||
-
|
||||
!python {model: res.partner }: |
|
||||
# assuming that the default company is #1
|
||||
res_ids = self.search(cr, uid, [('company_id', 'not in', [1])])
|
||||
assert len(res_ids) >= 4, "We should have found 4 records at least, only have %d! %r" % \
|
||||
(len(res_ids), res_ids)
|
||||
-
|
||||
Check that we exclude the correct records, + False
|
||||
-
|
||||
!python {model: res.partner }: |
|
||||
# assuming that the default company is #1
|
||||
res_ids = self.search(cr, uid, ['|', ('company_id', 'not in', [1]), ('company_id', '=', False)])
|
||||
assert len(res_ids) >= 8, "We should have found 8 records at least, only have %d! %r" % \
|
||||
(len(res_ids), res_ids)
|
||||
-
|
||||
Check that multi-level expressions also work
|
||||
-
|
||||
!python {model: res.partner }: |
|
||||
res_ids = self.search(cr, uid, [('company_id.partner_id', 'in', [])])
|
||||
assert res_ids == [], "Searching an empty set should return empty result, not %r" % res_ids
|
||||
-
|
||||
Check that multi-level expressions with negative op work
|
||||
-
|
||||
!python {model: res.partner }: |
|
||||
all_ids = self.search(cr, uid, [('company_id', '!=', False)])
|
||||
all_ids.sort()
|
||||
res_ids = self.search(cr, uid, [('company_id.partner_id', 'not in', [])])
|
||||
res_ids.sort()
|
||||
assert res_ids == all_ids, "Searching against empty set failed, returns %r" % res_ids
|
||||
-
|
||||
Test the '(not) like/in' behavior. res.partner and its parent_id column are used because
|
||||
parent_id is a many2one, allowing to test the Null value, and there are actually some
|
||||
null and non-null values in the demo data.
|
||||
-
|
||||
!python {model: res.partner }: |
|
||||
partner_ids = self.search(cr, uid, [])
|
||||
partner_ids.sort()
|
||||
max_partner_id = max(partner_ids)
|
||||
|
||||
# Grab test sample data without using a normal
|
||||
# search domain, because we want to test these later,
|
||||
# so we can't rely on them!
|
||||
partners = self.browse(cr, uid, partner_ids)
|
||||
with_parent = []
|
||||
without_parent = []
|
||||
with_website = []
|
||||
for x in partners:
|
||||
if x.parent_id:
|
||||
with_parent.append(x.id)
|
||||
else:
|
||||
without_parent.append(x.id)
|
||||
if x.website:
|
||||
with_website.append(x.id)
|
||||
with_parent.sort()
|
||||
without_parent.sort()
|
||||
with_website.sort()
|
||||
|
||||
# We treat null values differently than in SQL. For instance in SQL:
|
||||
# SELECT id FROM res_partner WHERE parent_id NOT IN (0)
|
||||
# will return only the records with non-null parent_id.
|
||||
# SELECT id FROM res_partner WHERE parent_id IN (0)
|
||||
# will return expectedly nothing (our ids always begin at 1).
|
||||
# This means the union of those two results will give only some
|
||||
# records, but not all present in database.
|
||||
#
|
||||
# When using domains and the ORM's search method, we think it is
|
||||
# more intuitive that the union returns all the records, and that
|
||||
# a domain like ('parent_id', 'not in', [0]) will return all
|
||||
# the records. For instance, if you perform a search for the companies
|
||||
# that don't have OpenERP has a parent company, you expect to find,
|
||||
# among others, the companies that don't have parent company.
|
||||
#
|
||||
|
||||
# existing values be treated similarly if we simply check that some
|
||||
# existing value belongs to them.
|
||||
|
||||
res_0 = self.search(cr, uid, [('parent_id', 'not like', 'probably_unexisting_name')]) # get all rows, included null parent_id
|
||||
res_0.sort()
|
||||
res_1 = self.search(cr, uid, [('parent_id', 'not in', [max_partner_id + 1])]) # get all rows, included null parent_id
|
||||
res_1.sort()
|
||||
res_2 = self.search(cr, uid, [('parent_id', '!=', False)]) # get rows with not null parent_id, deprecated syntax
|
||||
res_2.sort()
|
||||
res_3 = self.search(cr, uid, [('parent_id', 'not in', [])]) # get all rows, included null parent_id
|
||||
res_3.sort()
|
||||
res_4 = self.search(cr, uid, [('parent_id', 'not in', [False])]) # get rows with not null parent_id
|
||||
res_4.sort()
|
||||
res_4b = self.search(cr, uid, [('parent_id', 'not ilike', '')]) # get only rows without parent
|
||||
res_4b.sort()
|
||||
assert res_0 == partner_ids, 'res0: expected %r, got %r' % (partner_ids, res_0)
|
||||
assert res_1 == partner_ids, 'res1: expected %r, got %r' % (partner_ids, res_1)
|
||||
assert res_2 == with_parent, 'res2: expected %r, got %r' % (with_parent, res_2)
|
||||
assert res_3 == partner_ids, 'res3: expected %r, got %r' % (partner_ids, res_3)
|
||||
assert res_4 == with_parent, 'res4: expected %r, got %r' % (with_parent, res_4)
|
||||
assert res_4b == without_parent, 'res4b: expected %r, got %r' % (without_parent, res_4b)
|
||||
# The results of these queries, when combined with queries 0..4 must
|
||||
# give the whole set of ids.
|
||||
res_5 = self.search(cr, uid, [('parent_id', 'like', 'probably_unexisting_name')])
|
||||
res_5.sort()
|
||||
res_6 = self.search(cr, uid, [('parent_id', 'in', [max_partner_id + 1])])
|
||||
res_6.sort()
|
||||
res_7 = self.search(cr, uid, [('parent_id', '=', False)])
|
||||
res_7.sort()
|
||||
res_8 = self.search(cr, uid, [('parent_id', 'in', [])])
|
||||
res_8.sort()
|
||||
res_9 = self.search(cr, uid, [('parent_id', 'in', [False])])
|
||||
res_9.sort()
|
||||
res_9b = self.search(cr, uid, [('parent_id', 'ilike', '')]) # get those with a parent
|
||||
res_9b.sort()
|
||||
|
||||
assert res_5 == [], 'res5: expected %r, got %r' % ([], res_5)
|
||||
assert res_6 == [], 'res6: expected %r, got %r' % ([], res_6)
|
||||
assert res_7 == without_parent, 'res7: expected %r, got %r' % (without_parent, res_7)
|
||||
assert res_8 == [], 'res8: expected %r, got %r' % ([], res_8)
|
||||
assert res_9 == without_parent, 'res9: expected %r, got %r' % (without_parent, res_9)
|
||||
assert res_9b == with_parent, 'res9b: expected %r, got %r' % (with_parent, res_9b)
|
||||
# These queries must return exactly the results than the queries 0..4,
|
||||
# i.e. not ... in ... must be the same as ... not in ... .
|
||||
res_10 = self.search(cr, uid, ['!', ('parent_id', 'like', 'probably_unexisting_name')])
|
||||
res_10.sort()
|
||||
res_11 = self.search(cr, uid, ['!', ('parent_id', 'in', [max_partner_id + 1])])
|
||||
res_11.sort()
|
||||
res_12 = self.search(cr, uid, ['!', ('parent_id', '=', False)])
|
||||
res_12.sort()
|
||||
res_13 = self.search(cr, uid, ['!', ('parent_id', 'in', [])])
|
||||
res_13.sort()
|
||||
res_14 = self.search(cr, uid, ['!', ('parent_id', 'in', [False])])
|
||||
res_14.sort()
|
||||
assert res_0 == res_10
|
||||
assert res_1 == res_11
|
||||
assert res_2 == res_12
|
||||
assert res_3 == res_13
|
||||
assert res_4 == res_14
|
||||
|
||||
# Testing many2one field is not enough, a regular char field is tested
|
||||
# with in [] and must not return any result.
|
||||
res_15 = self.search(cr, uid, [('website', 'in', [])])
|
||||
assert res_15 == []
|
||||
# not in [] must return everything.
|
||||
res_16 = self.search(cr, uid, [('website', 'not in', [])])
|
||||
res_16.sort()
|
||||
assert res_16 == partner_ids
|
||||
|
||||
res_17 = self.search(cr, uid, [('website', '!=', False)])
|
||||
res_17.sort()
|
||||
assert res_17 == with_website
|
||||
|
||||
-
|
||||
Check behavior for required many2one fields
|
||||
-
|
||||
!python {model: res.company }: |
|
||||
company_ids = sorted(self.search(cr, uid, []))
|
||||
# currency_id is required
|
||||
res_101 = sorted(self.search(cr, uid, [('currency_id', 'not ilike', '')])) # get no companies
|
||||
res_102 = sorted(self.search(cr, uid, [('currency_id', 'ilike', '')])) # get all companies
|
||||
assert res_101 == [], 'res_101: expected %r, got %r' % ([], res_101)
|
||||
assert res_102 == company_ids, 'res_102: expected %r, got %r' % (company_ids, res_102)
|
||||
-
|
||||
Verify domain evaluation for `one2many != False`
|
||||
-
|
||||
!python {model: res.partner.category }: |
|
||||
all_ids = self.search(cr, uid, [])
|
||||
parent_categs = set([c.parent_id.id for c in self.browse(cr, uid, all_ids) if c.parent_id])
|
||||
result = set(self.search(cr, uid, [('child_ids', '!=', False)]))
|
||||
assert result and result == parent_categs, "Got %r, expected %r" % (result, parent_categs)
|
||||
-
|
||||
Verify domain evaluation for `one2many == False`
|
||||
-
|
||||
!python {model: res.partner.category }: |
|
||||
all_ids = self.search(cr, uid, [])
|
||||
parent_categs = set([c.parent_id.id for c in self.browse(cr, uid, all_ids) if c.parent_id])
|
||||
leaf_categs = set(all_ids) - parent_categs
|
||||
result = set(self.search(cr, uid, [('child_ids', '=', False)]))
|
||||
assert result and result == leaf_categs, "Got %r, expected %r" % (result, leaf_categs)
|
||||
-
|
||||
Equivalent queries.
|
||||
-
|
||||
!python {model: res.currency }: |
|
||||
max_currency_id = max(self.search(cr, uid, []))
|
||||
res_0 = self.search(cr, uid, [])
|
||||
res_1 = self.search(cr, uid, [('name', 'not like', 'probably_unexisting_name')])
|
||||
res_2 = self.search(cr, uid, [('id', 'not in', [max_currency_id + 1003])])
|
||||
res_3 = self.search(cr, uid, [('id', 'not in', [])])
|
||||
res_4 = self.search(cr, uid, [('id', '!=', False)])
|
||||
res_0.sort()
|
||||
res_1.sort()
|
||||
res_2.sort()
|
||||
res_3.sort()
|
||||
res_4.sort()
|
||||
assert res_0 == res_1
|
||||
assert res_0 == res_2
|
||||
assert res_0 == res_3
|
||||
assert res_0 == res_4
|
||||
-
|
||||
Equivalent queries, integer and string.
|
||||
-
|
||||
!python {model: res.partner }: |
|
||||
all_ids = self.search(cr, uid, [])
|
||||
if len(all_ids) > 1:
|
||||
one = all_ids[0]
|
||||
record = self.browse(cr, uid, one)
|
||||
others = all_ids[1:]
|
||||
res_1 = self.search(cr, uid, [('id', '=', one)])
|
||||
# self.search(cr, uid, [('id', '!=', others)]) # not permitted
|
||||
res_2 = self.search(cr, uid, [('id', 'not in', others)])
|
||||
res_3 = self.search(cr, uid, ['!', ('id', '!=', one)])
|
||||
res_4 = self.search(cr, uid, ['!', ('id', 'in', others)])
|
||||
# res_5 = self.search(cr, uid, [('id', 'in', one)]) # TODO make it permitted, just like for child_of
|
||||
res_6 = self.search(cr, uid, [('id', 'in', [one])])
|
||||
res_7 = self.search(cr, uid, [('name', '=', record.name)])
|
||||
res_8 = self.search(cr, uid, [('name', 'in', [record.name])])
|
||||
# res_9 = self.search(cr, uid, [('name', 'in', record.name)]) # TODO
|
||||
assert [one] == res_1
|
||||
assert [one] == res_2
|
||||
assert [one] == res_3
|
||||
assert [one] == res_4
|
||||
#assert [one] == res_5
|
||||
assert [one] == res_6
|
||||
assert [one] == res_7
|
||||
-
|
||||
Need a company with a parent_id.
|
||||
-
|
||||
!record {model: res.company, id: ymltest_company3}:
|
||||
name: Acme 3
|
||||
-
|
||||
Need a company with a parent_id.
|
||||
-
|
||||
!record {model: res.company, id: ymltest_company4}:
|
||||
name: Acme 4
|
||||
parent_id: ymltest_company3
|
||||
-
|
||||
Equivalent queries, one2many.
|
||||
-
|
||||
!python {model: res.company }: |
|
||||
company = self.browse(cr, uid, ref('ymltest_company3'))
|
||||
|
||||
# one2many towards same model
|
||||
res_1 = self.search(cr, uid, [('child_ids', 'in', [x.id for x in company.child_ids])]) # any company having a child of company3 as child
|
||||
res_2 = self.search(cr, uid, [('child_ids', 'in', [company.child_ids[0].id])]) # any company having the first child of company3 as child
|
||||
assert res_1 == [ref('ymltest_company3')], "res_1"
|
||||
assert res_2 == [ref('ymltest_company3')], "res_2"
|
||||
|
||||
# try testing real subsets with IN/NOT IN
|
||||
res_partner = self.pool.get('res.partner')
|
||||
res_users = self.pool.get('res.users')
|
||||
p1, _ = res_partner.name_create(cr, uid, "Dédé Boitaclou")
|
||||
p2, _ = res_partner.name_create(cr, uid, "Raoulette Pizza O'poil")
|
||||
u1a = res_users.create(cr, uid, {'login': 'dbo', 'partner_id': p1})
|
||||
u1b = res_users.create(cr, uid, {'login': 'dbo2', 'partner_id': p1})
|
||||
u2 = res_users.create(cr, uid, {'login': 'rpo', 'partner_id': p2})
|
||||
assert [p1] == res_partner.search(cr, uid, [('user_ids', 'in', u1a)]), "o2m IN accept single int on right side"
|
||||
assert [p1] == res_partner.search(cr, uid, [('user_ids', '=', 'Dédé Boitaclou')]), "o2m NOT IN matches none on the right side"
|
||||
assert [] == res_partner.search(cr, uid, [('user_ids', 'in', [10000])]), "o2m NOT IN matches none on the right side"
|
||||
assert [p1,p2] == res_partner.search(cr, uid, [('user_ids', 'in', [u1a,u2])]), "o2m IN matches any on the right side"
|
||||
all_partners = res_partner.search(cr, uid, [])
|
||||
assert (set(all_partners) - set([p1])) == set(res_partner.search(cr, uid, [('user_ids', 'not in', u1a)])), "o2m NOT IN matches none on the right side"
|
||||
assert (set(all_partners) - set([p1])) == set(res_partner.search(cr, uid, [('user_ids', '!=', 'Dédé Boitaclou')])), "o2m NOT IN matches none on the right side"
|
||||
assert (set(all_partners) - set([p1,p2])) == set(res_partner.search(cr, uid, [('user_ids', 'not in', [u1b, u2])])), "o2m NOT IN matches none on the right side"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# child_of x returns x and its children (direct or not).
|
||||
company = self.browse(cr, uid, ref('ymltest_company3'))
|
||||
expected = [ref('ymltest_company3'), ref('ymltest_company4')]
|
||||
expected.sort()
|
||||
res_1 = self.search(cr, uid, [('id', 'child_of', [ref('ymltest_company3')])])
|
||||
res_1.sort()
|
||||
res_2 = self.search(cr, uid, [('id', 'child_of', ref('ymltest_company3'))])
|
||||
res_2.sort()
|
||||
res_3 = self.search(cr, uid, [('id', 'child_of', [company.name])])
|
||||
res_3.sort()
|
||||
res_4 = self.search(cr, uid, [('id', 'child_of', company.name)])
|
||||
res_4.sort()
|
||||
assert res_1 == expected
|
||||
assert res_2 == expected
|
||||
assert res_3 == expected
|
||||
assert res_4 == expected
|
||||
|
||||
# parent_of x returns x and its parents (direct or not).
|
||||
company = self.browse(cr, uid, ref('ymltest_company4'))
|
||||
expected = [ref('ymltest_company4'), ref('ymltest_company3')]
|
||||
expected.sort()
|
||||
res_1 = self.search(cr, uid, [('id', 'parent_of', [ref('ymltest_company4')])])
|
||||
res_1.sort()
|
||||
res_2 = self.search(cr, uid, [('id', 'parent_of', ref('ymltest_company4'))])
|
||||
res_2.sort()
|
||||
res_3 = self.search(cr, uid, [('id', 'parent_of', [company.name])])
|
||||
res_3.sort()
|
||||
res_4 = self.search(cr, uid, [('id', 'parent_of', company.name)])
|
||||
res_4.sort()
|
||||
assert res_1 == expected
|
||||
assert res_2 == expected
|
||||
assert res_3 == expected
|
||||
assert res_4 == expected
|
||||
-
|
||||
Equivalent queries, one2many.
|
||||
-
|
||||
!python {model: res.currency }: |
|
||||
# Create a currency and a currency rate
|
||||
res_currency = self.pool['res.currency']
|
||||
res_currency_rate = self.pool['res.currency.rate']
|
||||
currency = res_currency.create(cr, uid, {'name': 'ZZZ', 'symbol': 'ZZZ', 'rounding': 1.0})
|
||||
currency_rate = self.pool['res.currency.rate'].create(cr, uid, {'name': '2010-01-01', 'currency_id': currency, 'rate': 1.0})
|
||||
|
||||
# Search the currency via its rates one2many (the one2many must point back at the currency).
|
||||
max_currency_rate_id = max(res_currency_rate.search(cr, uid, []))
|
||||
currency_rate1 = res_currency_rate.search(cr, uid, [('name', 'not like', 'probably_unexisting_name')])
|
||||
currency_rate2 = res_currency_rate.search(cr, uid, [('id', 'not in', [max_currency_rate_id + 1003])])
|
||||
currency_rate3 = res_currency_rate.search(cr, uid, [('id', 'not in', [])])
|
||||
assert currency_rate1 == currency_rate2 == currency_rate3, 'All 3 results should be the same : empty'
|
||||
default_currency = self.browse(cr, uid, 1)
|
||||
|
||||
# one2many towards another model
|
||||
res_3 = self.search(cr, uid, [('rate_ids', 'in', [x.id for x in default_currency.rate_ids])]) # currencies having a rate of main currency
|
||||
res_4 = self.search(cr, uid, [('rate_ids', 'in', [default_currency.rate_ids[0].id])]) # currencies having first rate of main currency
|
||||
res_5 = self.search(cr, uid, [('rate_ids', 'in', default_currency.rate_ids[0].id)]) # currencies having first rate of main currency
|
||||
# res_6 = self.search(cr, uid, [('rate_ids', 'in', [default_currency.rate_ids[0].name])])
|
||||
# res_7 = self.search(cr, uid, [('rate_ids', '=', default_currency.rate_ids[0].name)])
|
||||
# res_8 = self.search(cr, uid, [('rate_ids', 'like', default_currency.rate_ids[0].name)])
|
||||
|
||||
res_9 = self.search(cr, uid, [('rate_ids', 'like', 'probably_unexisting_name')])
|
||||
# self.search(cr, uid, [('rate_ids', 'unexisting_op', 'probably_unexisting_name')]) # TODO expected exception
|
||||
assert res_3 == [1], "res_3"
|
||||
assert res_4 == [1], "res_4"
|
||||
assert res_5 == [1], "res_5"
|
||||
# assert res_7 == [1], "res_7"
|
||||
# assert res_8 == [1], "res_8"
|
||||
assert res_9 == [], "res_9"
|
||||
|
||||
# get the currencies referenced by some currency rates using a weird negative domain
|
||||
res_10 = self.search(cr, uid, [('rate_ids', 'not like', 'probably_unexisting_name')])
|
||||
res_11 = self.search(cr, uid, [('rate_ids', 'not in', [max_currency_rate_id + 1])])
|
||||
res_12 = self.search(cr, uid, [('rate_ids', '!=', False)])
|
||||
res_13 = self.search(cr, uid, [('rate_ids', 'not in', [])])
|
||||
res_10.sort()
|
||||
res_11.sort()
|
||||
res_12.sort()
|
||||
res_13.sort()
|
||||
assert res_10 == res_11, 'res_11'
|
||||
assert res_10 == res_12, 'res_12'
|
||||
assert res_10 == res_13, 'res_13'
|
||||
-
|
||||
Unaccent. Create a company with an accent in its name.
|
||||
-
|
||||
!record {model: res.company, id: ymltest_unaccent_company}:
|
||||
name: Hélène
|
||||
-
|
||||
Test the unaccent-enabled 'ilike'.
|
||||
-
|
||||
!python {model: res.company}: |
|
||||
if self.pool.has_unaccent:
|
||||
ids = self.search(cr, uid, [('name','ilike','Helene')], {})
|
||||
assert ids == [ref('ymltest_unaccent_company')]
|
||||
ids = self.search(cr, uid, [('name','ilike','hélène')], {})
|
||||
assert ids == [ref('ymltest_unaccent_company')]
|
||||
ids = self.search(cr, uid, [('name','not ilike','Helene')], {})
|
||||
assert ref('ymltest_unaccent_company') not in ids
|
||||
ids = self.search(cr, uid, [('name','not ilike','hélène')], {})
|
||||
assert ref('ymltest_unaccent_company') not in ids
|
||||
-
|
||||
Check that =like/=ilike expressions (no wildcard variants of like/ilike) are working on an untranslated field.
|
||||
-
|
||||
!python {model: res.partner }: |
|
||||
all_ids = self.search(cr, uid, [('name', '=like', 'A_U_TeK')])
|
||||
assert len(all_ids) == 1, "Must match one partner (ASUSTeK), got %r"%all_ids
|
||||
all_ids = self.search(cr, uid, [('name', '=ilike', 'c%')])
|
||||
assert len(all_ids) >= 1, "Must match one partner (China Export), got %r"%all_ids
|
||||
-
|
||||
Check that =like/=ilike expressions (no wildcard variants of like/ilike) are working on translated field.
|
||||
-
|
||||
!python {model: res.country }: |
|
||||
all_ids = self.search(cr, uid, [('name', '=like', 'Ind__')])
|
||||
assert len(all_ids) == 1, "Must match India only, got %r"%all_ids
|
||||
all_ids = self.search(cr, uid, [('name', '=ilike', 'z%')])
|
||||
assert len(all_ids) == 3, "Must match only countries with names starting with Z (currently 3), got %r"%all_ids
|
||||
-
|
||||
Use the create_date column on res.country (which doesn't declare it in _columns).
|
||||
-
|
||||
!python {model: res.country }: |
|
||||
ids = self.search(cr, uid, [('create_date', '<', '2001-01-01 12:00:00')])
|
||||
|
||||
|
||||
-
|
||||
Verify that invalid expressions are refused, even for magic fields
|
||||
-
|
||||
!python {model: res.country }: |
|
||||
try:
|
||||
self.search(cr, uid, [('does_not_exist', '=', 'foo')])
|
||||
raise AssertionError('Invalid fields should not be accepted')
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
try:
|
||||
self.search(cr, uid, [('create_date', '>>', 'foo')])
|
||||
raise AssertionError('Invalid operators should not be accepted')
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
import psycopg2
|
||||
try:
|
||||
cr._default_log_exceptions = False
|
||||
cr.execute('SAVEPOINT expression_failure_test')
|
||||
self.search(cr, uid, [('create_date', '=', "1970-01-01'); --")])
|
||||
# if the above search gives no error, the operand was not escaped!
|
||||
cr.execute('RELEASE SAVEPOINT expression_failure_test')
|
||||
raise AssertionError('Operands should always be SQL escaped')
|
||||
except psycopg2.DataError:
|
||||
# Should give: 'DataError: invalid input syntax for type timestamp' or similar
|
||||
cr.execute('ROLLBACK TO SAVEPOINT expression_failure_test')
|
||||
-
|
||||
Testing for Many2Many field with category vendor and active=False
|
||||
-
|
||||
!python {model: res.partner }: |
|
||||
vals = {'category_id': [(6, 0, [ref("base.res_partner_category_1")])],
|
||||
'name': 'OpenERP Test',
|
||||
'active': False,
|
||||
'child_ids': [(0, 0, {'name': 'address of OpenERP Test', 'country_id': ref("base.be")})]
|
||||
}
|
||||
self.create(cr, uid, vals, context=context)
|
||||
res_ids = self.search(cr, uid, [('category_id', 'ilike', 'vendor'), ('active', '=', False)])
|
||||
assert len(res_ids) != 0, "Record not Found with category vendor and active False."
|
||||
-
|
||||
Testing for One2Many field with country Belgium and active=False
|
||||
-
|
||||
!python {model: res.partner }: |
|
||||
res_ids = self.search(cr, uid, [('child_ids.country_id','=','Belgium'),('active','=',False)])
|
||||
assert len(res_ids) != 0, "Record not Found with country Belgium and active False."
|
||||
-
|
||||
Check that we can exclude translated fields (bug lp:1071710)
|
||||
-
|
||||
!python {model: res.country}: |
|
||||
# first install french language
|
||||
Modules = self.pool.get('ir.module.module')
|
||||
base = Modules.browse(cr, uid, Modules.search(cr, uid, [('name', '=', 'base')])[0])
|
||||
|
||||
# hack: mark module as installed to allow install of the translation
|
||||
base_state = base.state
|
||||
base.write({'state': 'installed'})
|
||||
base.update_translations('fr_FR')
|
||||
base.write({'state': base_state})
|
||||
|
||||
# tests
|
||||
be_id = self.search(cr, uid, [('name', '=', 'Belgium')])[0]
|
||||
|
||||
ctx = {'lang': 'fr_FR'}
|
||||
not_be = self.search(cr, uid, [('name', '!=', 'Belgique')], context=ctx)
|
||||
|
||||
assert be_id not in not_be, "Search match failed"
|
||||
|
||||
# indirect search via m2o
|
||||
Partners = self.pool.get('res.partner')
|
||||
agrolait = Partners.search(cr, uid, [('name', '=', 'Agrolait')])[0]
|
||||
|
||||
not_be = Partners.search(cr, uid, [('country_id', '!=', 'Belgium')])
|
||||
assert agrolait not in not_be, "Search match failed (m2o)"
|
||||
|
||||
not_be = Partners.search(cr, uid, [('country_id', '!=', 'Belgique')], context=ctx)
|
||||
assert agrolait not in not_be, "Search match failed (m2o)"
|
||||
@@ -0,0 +1,64 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import unittest
|
||||
|
||||
from odoo.tests import common
|
||||
|
||||
|
||||
#: Stores state information across multiple test classes
|
||||
test_state = None
|
||||
|
||||
def setUpModule():
|
||||
global test_state
|
||||
test_state = {}
|
||||
|
||||
def tearDownModule():
|
||||
global test_state
|
||||
test_state = None
|
||||
|
||||
|
||||
class TestPhaseInstall00(unittest.TestCase):
|
||||
"""
|
||||
WARNING: Relies on tests being run in alphabetical order
|
||||
"""
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.state = None
|
||||
|
||||
def test_00_setup(self):
|
||||
type(self).state = 'init'
|
||||
|
||||
@common.at_install(False)
|
||||
def test_01_no_install(self):
|
||||
type(self).state = 'error'
|
||||
|
||||
def test_02_check(self):
|
||||
self.assertEqual(self.state, 'init',
|
||||
"Testcase state should not have been transitioned from 00")
|
||||
|
||||
|
||||
class TestPhaseInstall01(unittest.TestCase):
|
||||
at_install = False
|
||||
|
||||
def test_default_norun(self):
|
||||
self.fail("An unmarket test in a non-at-install case should not run")
|
||||
|
||||
@common.at_install(True)
|
||||
def test_set_run(self):
|
||||
test_state['set_at_install'] = True
|
||||
|
||||
|
||||
class TestPhaseInstall02(unittest.TestCase):
|
||||
"""
|
||||
Can't put the check for test_set_run in the same class: if
|
||||
@common.at_install does not work for test_set_run, it won't work for
|
||||
the other one either. Thus move checking of whether test_set_run has
|
||||
correctly run indeed to a separate class.
|
||||
|
||||
Warning: relies on *classes* being run in alphabetical order in test
|
||||
modules
|
||||
"""
|
||||
def test_check_state(self):
|
||||
self.assertTrue(test_state.get('set_at_install'),
|
||||
"The flag should be set if local overriding of runstate")
|
||||
@@ -1,92 +1,78 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import cgi
|
||||
import collections
|
||||
import json
|
||||
import os.path
|
||||
import glob
|
||||
import re
|
||||
import collections
|
||||
|
||||
from lxml import etree
|
||||
|
||||
import openerp.modules
|
||||
from openerp.addons.base.ir.ir_qweb import QWebContext, FileSystemLoader
|
||||
from openerp.tests import common
|
||||
from odoo.addons.base.ir.ir_qweb import QWebContext, FileSystemLoader
|
||||
from odoo.modules import get_module_resource
|
||||
from odoo.tests.common import TransactionCase
|
||||
|
||||
class TestQWebTField(common.TransactionCase):
|
||||
|
||||
class TestQWebTField(TransactionCase):
|
||||
def setUp(self):
|
||||
super(TestQWebTField, self).setUp()
|
||||
self.env = self.env(context={'inherit_branding': True})
|
||||
self.engine = self.env['ir.qweb']
|
||||
self.env_branding = self.env(context={'inherit_branding': True})
|
||||
self.engine = self.env_branding['ir.qweb']
|
||||
|
||||
def context(self, values):
|
||||
return QWebContext(self.env, values)
|
||||
return QWebContext(self.env_branding, values)
|
||||
|
||||
def test_trivial(self):
|
||||
field = etree.Element('span', {'t-field': u'company.name'})
|
||||
company = self.env['res.company'].create({'name': "My Test Company"})
|
||||
|
||||
Companies = self.registry('res.company')
|
||||
company_id = Companies.create(self.cr, self.uid, {
|
||||
'name': "My Test Company"
|
||||
})
|
||||
result = self.engine.render_node(field, self.context({
|
||||
'company': Companies.browse(self.cr, self.uid, company_id),
|
||||
}))
|
||||
|
||||
result = self.engine.render_node(field, self.context({'company': company}))
|
||||
self.assertEqual(
|
||||
result,
|
||||
'<span data-oe-model="res.company" data-oe-id="%d" '
|
||||
'data-oe-field="name" data-oe-type="char" '
|
||||
'data-oe-expression="company.name">%s</span>' % (
|
||||
company_id,
|
||||
"My Test Company",))
|
||||
company.id,
|
||||
"My Test Company",
|
||||
),
|
||||
)
|
||||
|
||||
def test_i18n(self):
|
||||
field = etree.Element('span', {'t-field': u'company.name'})
|
||||
|
||||
Companies = self.registry('res.company')
|
||||
s = u"Testing «ταБЬℓσ»: 1<2 & 4+1>3, now 20% off!"
|
||||
company_id = Companies.create(self.cr, self.uid, {
|
||||
'name': s,
|
||||
})
|
||||
result = self.engine.render_node(field, self.context({
|
||||
'company': Companies.browse(self.cr, self.uid, company_id),
|
||||
}))
|
||||
company = self.env['res.company'].create({'name': s})
|
||||
|
||||
result = self.engine.render_node(field, self.context({'company': company}))
|
||||
self.assertEqual(
|
||||
result,
|
||||
'<span data-oe-model="res.company" data-oe-id="%d" '
|
||||
'data-oe-field="name" data-oe-type="char" '
|
||||
'data-oe-expression="company.name">%s</span>' % (
|
||||
company_id,
|
||||
cgi.escape(s.encode('utf-8')),))
|
||||
company.id,
|
||||
cgi.escape(s.encode('utf-8')),
|
||||
),
|
||||
)
|
||||
|
||||
def test_reject_crummy_tags(self):
|
||||
field = etree.Element('td', {'t-field': u'company.name'})
|
||||
|
||||
with self.assertRaisesRegexp(
|
||||
AssertionError,
|
||||
r'^RTE widgets do not work correctly'):
|
||||
self.engine.render_node(field, self.context({
|
||||
'company': None
|
||||
}))
|
||||
with self.assertRaisesRegexp(AssertionError, r'^RTE widgets do not work correctly'):
|
||||
self.engine.render_node(field, self.context({'company': None}))
|
||||
|
||||
def test_reject_t_tag(self):
|
||||
field = etree.Element('t', {'t-field': u'company.name'})
|
||||
|
||||
with self.assertRaisesRegexp(
|
||||
AssertionError,
|
||||
r'^t-field can not be used on a t element'):
|
||||
self.engine.render_node(field, self.context({
|
||||
'company': None
|
||||
}))
|
||||
with self.assertRaisesRegexp(AssertionError, r'^t-field can not be used on a t element'):
|
||||
self.engine.render_node(field, self.context({'company': None}))
|
||||
|
||||
class TestQWeb(common.TransactionCase):
|
||||
matcher = re.compile('^qweb-test-(.*)\.xml$')
|
||||
|
||||
class TestQWeb(TransactionCase):
|
||||
matcher = re.compile(r'^qweb-test-(.*)\.xml$')
|
||||
|
||||
@classmethod
|
||||
def get_cases(cls):
|
||||
path = cls.qweb_test_file_path()
|
||||
|
||||
return (
|
||||
cls("test_qweb_{}".format(cls.matcher.match(f).group(1)))
|
||||
for f in os.listdir(path)
|
||||
@@ -94,12 +80,10 @@ class TestQWeb(common.TransactionCase):
|
||||
if f != 'qweb-test-extend.xml'
|
||||
if cls.matcher.match(f)
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def qweb_test_file_path(cls):
|
||||
path = os.path.dirname(
|
||||
openerp.modules.get_module_resource(
|
||||
'web', 'static', 'lib', 'qweb', 'qweb2.js'))
|
||||
return path
|
||||
return os.path.dirname(get_module_resource('web', 'static', 'lib', 'qweb', 'qweb2.js'))
|
||||
|
||||
def __getattr__(self, item):
|
||||
if not item.startswith('test_qweb_'):
|
||||
@@ -129,9 +113,10 @@ class TestQWeb(common.TransactionCase):
|
||||
self.assertEqual(
|
||||
qweb.render(template, qwebcontext=ctx).strip(),
|
||||
(result or u'').strip().encode('utf-8'),
|
||||
template
|
||||
template,
|
||||
)
|
||||
|
||||
|
||||
def load_tests(loader, suite, _):
|
||||
# can't override TestQWeb.__dir__ because dir() called on *class* not
|
||||
# instance
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import unittest
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import openerp
|
||||
import openerp.tests.common as common
|
||||
from odoo import exceptions
|
||||
from odoo.tests.common import TransactionCase
|
||||
|
||||
class test_res_config(common.TransactionCase):
|
||||
|
||||
class TestResConfig(TransactionCase):
|
||||
|
||||
def setUp(self):
|
||||
super(test_res_config, self).setUp()
|
||||
self.res_config = self.registry('res.config.settings')
|
||||
super(TestResConfig, self).setUp()
|
||||
self.ResConfig = self.env['res.config.settings']
|
||||
|
||||
# Define the test values
|
||||
self.menu_xml_id = 'base.menu_action_res_users'
|
||||
@@ -17,15 +19,13 @@ class test_res_config(common.TransactionCase):
|
||||
# Note: see the get_config_warning() doc for a better example
|
||||
|
||||
# Fetch the expected values
|
||||
module_name, menu_xml_id = self.menu_xml_id.split('.')
|
||||
dummy, menu_id = self.registry('ir.model.data').get_object_reference(self.cr, self.uid, module_name, menu_xml_id)
|
||||
ir_ui_menu = self.registry('ir.ui.menu').browse(self.cr, self.uid, menu_id, context=None)
|
||||
menu = self.env.ref(self.menu_xml_id)
|
||||
|
||||
model_name, field_name = self.full_field_name.rsplit('.', 1)
|
||||
|
||||
self.expected_path = ir_ui_menu.complete_name
|
||||
self.expected_action_id = ir_ui_menu.action.id
|
||||
self.expected_name = self.registry(model_name).fields_get(self.cr, self.uid, allfields=[field_name], context=None)[field_name]['string']
|
||||
self.expected_path = menu.complete_name
|
||||
self.expected_action_id = menu.action.id
|
||||
self.expected_name = self.env[model_name].fields_get([field_name])[field_name]['string']
|
||||
self.expected_final_error_msg = self.error_msg % {
|
||||
'field:res.partner.lang': self.expected_name,
|
||||
'menu:base.menu_action_res_users': self.expected_path
|
||||
@@ -36,7 +36,7 @@ class test_res_config(common.TransactionCase):
|
||||
|
||||
def test_00_get_option_path(self):
|
||||
""" The get_option_path() method should return a tuple containing a string and an integer """
|
||||
res = self.res_config.get_option_path(self.cr, self.uid, self.menu_xml_id, context=None)
|
||||
res = self.ResConfig.get_option_path(self.menu_xml_id)
|
||||
|
||||
# Check types
|
||||
self.assertIsInstance(res, tuple)
|
||||
@@ -50,7 +50,7 @@ class test_res_config(common.TransactionCase):
|
||||
|
||||
def test_10_get_option_name(self):
|
||||
""" The get_option_name() method should return a string """
|
||||
res = self.res_config.get_option_name(self.cr, self.uid, self.full_field_name, context=None)
|
||||
res = self.ResConfig.get_option_name(self.full_field_name)
|
||||
|
||||
# Check type
|
||||
self.assertIsInstance(res, basestring)
|
||||
@@ -60,10 +60,10 @@ class test_res_config(common.TransactionCase):
|
||||
|
||||
def test_20_get_config_warning(self):
|
||||
""" The get_config_warning() method should return a RedirectWarning """
|
||||
res = self.res_config.get_config_warning(self.cr, self.error_msg, context=None)
|
||||
res = self.ResConfig.get_config_warning(self.error_msg)
|
||||
|
||||
# Check type
|
||||
self.assertIsInstance(res, openerp.exceptions.RedirectWarning)
|
||||
self.assertIsInstance(res, exceptions.RedirectWarning)
|
||||
|
||||
# Check returned value
|
||||
self.assertEqual(res.args[0], self.expected_final_error_msg)
|
||||
@@ -71,10 +71,10 @@ class test_res_config(common.TransactionCase):
|
||||
|
||||
def test_30_get_config_warning_wo_menu(self):
|
||||
""" The get_config_warning() method should return a Warning exception """
|
||||
res = self.res_config.get_config_warning(self.cr, self.error_msg_wo_menu, context=None)
|
||||
res = self.ResConfig.get_config_warning(self.error_msg_wo_menu)
|
||||
|
||||
# Check type
|
||||
self.assertIsInstance(res, openerp.exceptions.Warning)
|
||||
self.assertIsInstance(res, exceptions.Warning)
|
||||
|
||||
# Check returned value
|
||||
self.assertEqual(res.args[0], self.expected_final_error_msg_wo_menu)
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import unittest
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import openerp.tests.common as common
|
||||
from odoo.tests.common import TransactionCase
|
||||
|
||||
class test_res_lang(common.TransactionCase):
|
||||
class test_res_lang(TransactionCase):
|
||||
|
||||
def test_00_intersperse(self):
|
||||
from openerp.addons.base.res.res_lang import intersperse
|
||||
from odoo.addons.base.res.res_lang import intersperse
|
||||
|
||||
assert intersperse("", []) == ("", 0)
|
||||
assert intersperse("0", []) == ("0", 0)
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
|
||||
# Copyright (c) 2015 ACSONE SA/NV (<http://acsone.eu>)
|
||||
|
||||
from openerp.tests.common import TransactionCase
|
||||
from odoo.tests.common import TransactionCase
|
||||
|
||||
|
||||
class TestResPartnerBank(TransactionCase):
|
||||
"""Tests acc_number
|
||||
|
||||
@@ -1,26 +1,24 @@
|
||||
import unittest
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import openerp.tests.common as common
|
||||
from odoo.tests.common import TransactionCase
|
||||
|
||||
|
||||
class test_search(common.TransactionCase):
|
||||
class test_search(TransactionCase):
|
||||
|
||||
def test_00_search_order(self):
|
||||
|
||||
registry, cr, uid = self.registry, self.cr, self.uid
|
||||
|
||||
# Create 6 partners with a given name, and a given creation order to
|
||||
# ensure the order of their ID. Some are set as unactive to verify they
|
||||
# ensure the order of their ID. Some are set as inactive to verify they
|
||||
# are by default excluded from the searches and to provide a second
|
||||
# `order` argument.
|
||||
|
||||
partners = registry('res.partner')
|
||||
c = partners.create(cr, uid, {'name': 'test_search_order_C'})
|
||||
d = partners.create(cr, uid, {'name': 'test_search_order_D', 'active': False})
|
||||
a = partners.create(cr, uid, {'name': 'test_search_order_A'})
|
||||
b = partners.create(cr, uid, {'name': 'test_search_order_B'})
|
||||
ab = partners.create(cr, uid, {'name': 'test_search_order_AB'})
|
||||
e = partners.create(cr, uid, {'name': 'test_search_order_E', 'active': False})
|
||||
Partner = self.env['res.partner']
|
||||
c = Partner.create({'name': 'test_search_order_C'})
|
||||
d = Partner.create({'name': 'test_search_order_D', 'active': False})
|
||||
a = Partner.create({'name': 'test_search_order_A'})
|
||||
b = Partner.create({'name': 'test_search_order_B'})
|
||||
ab = Partner.create({'name': 'test_search_order_AB'})
|
||||
e = Partner.create({'name': 'test_search_order_E', 'active': False})
|
||||
|
||||
# The tests.
|
||||
|
||||
@@ -28,116 +26,109 @@ class test_search(common.TransactionCase):
|
||||
# The order of the returned ids should be given by the `order`
|
||||
# parameter of search().
|
||||
|
||||
name_asc = partners.search(cr, uid, [('name', 'like', 'test_search_order%')], order="name asc")
|
||||
self.assertEqual([a, ab, b, c], name_asc, "Search with 'NAME ASC' order failed.")
|
||||
name_desc = partners.search(cr, uid, [('name', 'like', 'test_search_order%')], order="name desc")
|
||||
self.assertEqual([c, b, ab, a], name_desc, "Search with 'NAME DESC' order failed.")
|
||||
id_asc = partners.search(cr, uid, [('name', 'like', 'test_search_order%')], order="id asc")
|
||||
self.assertEqual([c, a, b, ab], id_asc, "Search with 'ID ASC' order failed.")
|
||||
id_desc = partners.search(cr, uid, [('name', 'like', 'test_search_order%')], order="id desc")
|
||||
self.assertEqual([ab, b, a, c], id_desc, "Search with 'ID DESC' order failed.")
|
||||
name_asc = Partner.search([('name', 'like', 'test_search_order%')], order="name asc")
|
||||
self.assertEqual([a, ab, b, c], list(name_asc), "Search with 'NAME ASC' order failed.")
|
||||
name_desc = Partner.search([('name', 'like', 'test_search_order%')], order="name desc")
|
||||
self.assertEqual([c, b, ab, a], list(name_desc), "Search with 'NAME DESC' order failed.")
|
||||
id_asc = Partner.search([('name', 'like', 'test_search_order%')], order="id asc")
|
||||
self.assertEqual([c, a, b, ab], list(id_asc), "Search with 'ID ASC' order failed.")
|
||||
id_desc = Partner.search([('name', 'like', 'test_search_order%')], order="id desc")
|
||||
self.assertEqual([ab, b, a, c], list(id_desc), "Search with 'ID DESC' order failed.")
|
||||
|
||||
# The inactive records shouldn't be excluded as soon as a condition on
|
||||
# that field is present in the domain. The `order` parameter of
|
||||
# search() should support any legal coma-separated values.
|
||||
|
||||
active_asc_id_asc = partners.search(cr, uid, [('name', 'like', 'test_search_order%'), '|', ('active', '=', True), ('active', '=', False)], order="active asc, id asc")
|
||||
self.assertEqual([d, e, c, a, b, ab], active_asc_id_asc, "Search with 'ACTIVE ASC, ID ASC' order failed.")
|
||||
active_desc_id_asc = partners.search(cr, uid, [('name', 'like', 'test_search_order%'), '|', ('active', '=', True), ('active', '=', False)], order="active desc, id asc")
|
||||
self.assertEqual([c, a, b, ab, d, e], active_desc_id_asc, "Search with 'ACTIVE DESC, ID ASC' order failed.")
|
||||
active_asc_id_desc = partners.search(cr, uid, [('name', 'like', 'test_search_order%'), '|', ('active', '=', True), ('active', '=', False)], order="active asc, id desc")
|
||||
self.assertEqual([e, d, ab, b, a, c], active_asc_id_desc, "Search with 'ACTIVE ASC, ID DESC' order failed.")
|
||||
active_desc_id_desc = partners.search(cr, uid, [('name', 'like', 'test_search_order%'), '|', ('active', '=', True), ('active', '=', False)], order="active desc, id desc")
|
||||
self.assertEqual([ab, b, a, c, e, d], active_desc_id_desc, "Search with 'ACTIVE DESC, ID DESC' order failed.")
|
||||
id_asc_active_asc = partners.search(cr, uid, [('name', 'like', 'test_search_order%'), '|', ('active', '=', True), ('active', '=', False)], order="id asc, active asc")
|
||||
self.assertEqual([c, d, a, b, ab, e], id_asc_active_asc, "Search with 'ID ASC, ACTIVE ASC' order failed.")
|
||||
id_asc_active_desc = partners.search(cr, uid, [('name', 'like', 'test_search_order%'), '|', ('active', '=', True), ('active', '=', False)], order="id asc, active desc")
|
||||
self.assertEqual([c, d, a, b, ab, e], id_asc_active_desc, "Search with 'ID ASC, ACTIVE DESC' order failed.")
|
||||
id_desc_active_asc = partners.search(cr, uid, [('name', 'like', 'test_search_order%'), '|', ('active', '=', True), ('active', '=', False)], order="id desc, active asc")
|
||||
self.assertEqual([e, ab, b, a, d, c], id_desc_active_asc, "Search with 'ID DESC, ACTIVE ASC' order failed.")
|
||||
id_desc_active_desc = partners.search(cr, uid, [('name', 'like', 'test_search_order%'), '|', ('active', '=', True), ('active', '=', False)], order="id desc, active desc")
|
||||
self.assertEqual([e, ab, b, a, d, c], id_desc_active_desc, "Search with 'ID DESC, ACTIVE DESC' order failed.")
|
||||
active_asc_id_asc = Partner.search([('name', 'like', 'test_search_order%'), '|', ('active', '=', True), ('active', '=', False)], order="active asc, id asc")
|
||||
self.assertEqual([d, e, c, a, b, ab], list(active_asc_id_asc), "Search with 'ACTIVE ASC, ID ASC' order failed.")
|
||||
active_desc_id_asc = Partner.search([('name', 'like', 'test_search_order%'), '|', ('active', '=', True), ('active', '=', False)], order="active desc, id asc")
|
||||
self.assertEqual([c, a, b, ab, d, e], list(active_desc_id_asc), "Search with 'ACTIVE DESC, ID ASC' order failed.")
|
||||
active_asc_id_desc = Partner.search([('name', 'like', 'test_search_order%'), '|', ('active', '=', True), ('active', '=', False)], order="active asc, id desc")
|
||||
self.assertEqual([e, d, ab, b, a, c], list(active_asc_id_desc), "Search with 'ACTIVE ASC, ID DESC' order failed.")
|
||||
active_desc_id_desc = Partner.search([('name', 'like', 'test_search_order%'), '|', ('active', '=', True), ('active', '=', False)], order="active desc, id desc")
|
||||
self.assertEqual([ab, b, a, c, e, d], list(active_desc_id_desc), "Search with 'ACTIVE DESC, ID DESC' order failed.")
|
||||
id_asc_active_asc = Partner.search([('name', 'like', 'test_search_order%'), '|', ('active', '=', True), ('active', '=', False)], order="id asc, active asc")
|
||||
self.assertEqual([c, d, a, b, ab, e], list(id_asc_active_asc), "Search with 'ID ASC, ACTIVE ASC' order failed.")
|
||||
id_asc_active_desc = Partner.search([('name', 'like', 'test_search_order%'), '|', ('active', '=', True), ('active', '=', False)], order="id asc, active desc")
|
||||
self.assertEqual([c, d, a, b, ab, e], list(id_asc_active_desc), "Search with 'ID ASC, ACTIVE DESC' order failed.")
|
||||
id_desc_active_asc = Partner.search([('name', 'like', 'test_search_order%'), '|', ('active', '=', True), ('active', '=', False)], order="id desc, active asc")
|
||||
self.assertEqual([e, ab, b, a, d, c], list(id_desc_active_asc), "Search with 'ID DESC, ACTIVE ASC' order failed.")
|
||||
id_desc_active_desc = Partner.search([('name', 'like', 'test_search_order%'), '|', ('active', '=', True), ('active', '=', False)], order="id desc, active desc")
|
||||
self.assertEqual([e, ab, b, a, d, c], list(id_desc_active_desc), "Search with 'ID DESC, ACTIVE DESC' order failed.")
|
||||
|
||||
def test_10_inherits_m2order(self):
|
||||
registry, cr, uid = self.registry, self.cr, self.uid
|
||||
users_obj = registry('res.users')
|
||||
Users = self.env['res.users']
|
||||
|
||||
# Find Employee group
|
||||
group_employee_ref = self.registry('ir.model.data').get_object_reference(cr, uid, 'base', 'group_user')
|
||||
group_employee_id = group_employee_ref and group_employee_ref[1] or False
|
||||
group_employee = self.env.ref('base.group_user')
|
||||
|
||||
# Get country/state data
|
||||
country_us_id = registry('res.country').search(cr, uid, [('code', 'like', 'US')])[0]
|
||||
state_ids = registry('res.country.state').search(cr, uid, [('country_id', '=', country_us_id)], limit=2)
|
||||
country_be_id = registry('res.country').search(cr, uid, [('code', 'like', 'BE')])[0]
|
||||
country_be = self.env.ref('base.be')
|
||||
country_us = self.env.ref('base.us')
|
||||
states_us = country_us.state_ids[:2]
|
||||
|
||||
# Create test users
|
||||
search_user = users_obj.create(cr, uid, {'name': '__search', 'login': '__search', 'groups_id': [(6, 0, [group_employee_id])]})
|
||||
a = users_obj.create(cr, uid, {'name': '__test_A', 'login': '__test_A', 'country_id': country_be_id, 'state_id': country_be_id})
|
||||
b = users_obj.create(cr, uid, {'name': '__test_B', 'login': '__a_test_B', 'country_id': country_us_id, 'state_id': state_ids[1]})
|
||||
c = users_obj.create(cr, uid, {'name': '__test_B', 'login': '__z_test_B', 'country_id': country_us_id, 'state_id': state_ids[0]})
|
||||
u = Users.create({'name': '__search', 'login': '__search', 'groups_id': [(6, 0, [group_employee.id])]})
|
||||
a = Users.create({'name': '__test_A', 'login': '__test_A', 'country_id': country_be.id, 'state_id': country_be.id})
|
||||
b = Users.create({'name': '__test_B', 'login': '__a_test_B', 'country_id': country_us.id, 'state_id': states_us[1].id})
|
||||
c = Users.create({'name': '__test_B', 'login': '__z_test_B', 'country_id': country_us.id, 'state_id': states_us[0].id})
|
||||
|
||||
# Search as search user
|
||||
Users = Users.sudo(u)
|
||||
|
||||
# Do: search on res.users, order on a field on res.partner to try inherits'd fields, then res.users
|
||||
user_ids = users_obj.search(cr, search_user, [], order='name asc, login desc')
|
||||
expected_ids = [search_user, a, c, b]
|
||||
test_user_ids = filter(lambda x: x in expected_ids, user_ids)
|
||||
self.assertEqual(test_user_ids, expected_ids, 'search on res_users did not provide expected ids or expected order')
|
||||
expected_ids = [u.id, a.id, c.id, b.id]
|
||||
user_ids = Users.search([('id', 'in', expected_ids)], order='name asc, login desc').ids
|
||||
self.assertEqual(user_ids, expected_ids, 'search on res_users did not provide expected ids or expected order')
|
||||
|
||||
# Do: order on many2one and inherits'd fields
|
||||
user_ids = users_obj.search(cr, search_user, [], order='state_id asc, country_id desc, name asc, login desc')
|
||||
expected_ids = [c, b, a, search_user]
|
||||
test_user_ids = filter(lambda x: x in expected_ids, user_ids)
|
||||
self.assertEqual(test_user_ids, expected_ids, 'search on res_users did not provide expected ids or expected order')
|
||||
expected_ids = [c.id, b.id, a.id, u.id]
|
||||
user_ids = Users.search([('id', 'in', expected_ids)], order='state_id asc, country_id desc, name asc, login desc').ids
|
||||
self.assertEqual(user_ids, expected_ids, 'search on res_users did not provide expected ids or expected order')
|
||||
|
||||
# Do: order on many2one and inherits'd fields
|
||||
user_ids = users_obj.search(cr, search_user, [], order='country_id desc, state_id desc, name asc, login desc')
|
||||
expected_ids = [search_user, b, c, a]
|
||||
test_user_ids = filter(lambda x: x in expected_ids, user_ids)
|
||||
self.assertEqual(test_user_ids, expected_ids, 'search on res_users did not provide expected ids or expected order')
|
||||
expected_ids = [u.id, b.id, c.id, a.id]
|
||||
user_ids = Users.search([('id', 'in', expected_ids)], order='country_id desc, state_id desc, name asc, login desc').ids
|
||||
self.assertEqual(user_ids, expected_ids, 'search on res_users did not provide expected ids or expected order')
|
||||
|
||||
# Do: order on many2one, but not by specifying in order parameter of search, but by overriding _order of res_users
|
||||
self.patch_order('res.users', 'country_id desc, name asc, login desc')
|
||||
user_ids = users_obj.search(cr, search_user, [])
|
||||
expected_ids = [search_user, c, b, a]
|
||||
test_user_ids = filter(lambda x: x in expected_ids, user_ids)
|
||||
self.assertEqual(test_user_ids, expected_ids, 'search on res_users did not provide expected ids or expected order')
|
||||
expected_ids = [u.id, c.id, b.id, a.id]
|
||||
user_ids = Users.search([('id', 'in', expected_ids)]).ids
|
||||
self.assertEqual(user_ids, expected_ids, 'search on res_users did not provide expected ids or expected order')
|
||||
|
||||
def test_11_indirect_inherits_m2o_order(self):
|
||||
registry, cr, uid = self.registry, self.cr, self.uid
|
||||
Cron = registry('ir.cron')
|
||||
Users = registry('res.users')
|
||||
Cron = self.env['ir.cron']
|
||||
Users = self.env['res.users']
|
||||
|
||||
user_ids = {}
|
||||
cron_ids = {}
|
||||
for u in 'BAC':
|
||||
user_ids[u] = Users.create(cr, uid, {'name': u, 'login': u})
|
||||
cron_ids[u] = Cron.create(cr, uid, {'name': u, 'user_id': user_ids[u]})
|
||||
user_ids[u] = Users.create({'name': u, 'login': u}).id
|
||||
cron_ids[u] = Cron.create({'name': u, 'user_id': user_ids[u]}).id
|
||||
|
||||
ids = Cron.search(cr, uid, [('id', 'in', cron_ids.values())], order='user_id')
|
||||
ids = Cron.search([('id', 'in', cron_ids.values())], order='user_id').ids
|
||||
expected_ids = [cron_ids[l] for l in 'ABC']
|
||||
self.assertEqual(ids, expected_ids)
|
||||
|
||||
def test_12_m2o_order_loop_self(self):
|
||||
registry, cr, uid = self.registry, self.cr, self.uid
|
||||
|
||||
Cats = registry('ir.module.category')
|
||||
ids = {}
|
||||
Cats = self.env['ir.module.category']
|
||||
cat_ids = {}
|
||||
def create(name, **kw):
|
||||
ids[name] = Cats.create(cr, uid, dict(kw, name=name))
|
||||
cat_ids[name] = Cats.create(dict(kw, name=name)).id
|
||||
|
||||
self.patch_order('ir.module.category', 'parent_id desc, name')
|
||||
|
||||
create('A')
|
||||
create('B', parent_id=ids['A'])
|
||||
create('C', parent_id=ids['A'])
|
||||
create('B', parent_id=cat_ids['A'])
|
||||
create('C', parent_id=cat_ids['A'])
|
||||
create('D')
|
||||
create('E', parent_id=ids['D'])
|
||||
create('F', parent_id=ids['D'])
|
||||
create('E', parent_id=cat_ids['D'])
|
||||
create('F', parent_id=cat_ids['D'])
|
||||
|
||||
expected_order = [ids[x] for x in 'ADEFBC']
|
||||
domain = [('id', 'in', ids.values())]
|
||||
search_result = Cats.search(cr, uid, domain)
|
||||
self.assertEqual(search_result, expected_order)
|
||||
expected_ids = [cat_ids[x] for x in 'ADEFBC']
|
||||
found_ids = Cats.search([('id', 'in', cat_ids.values())]).ids
|
||||
self.assertEqual(found_ids, expected_ids)
|
||||
|
||||
def test_13_m2o_order_loop_multi(self):
|
||||
Users = self.env['res.users']
|
||||
@@ -154,11 +145,6 @@ class test_search(common.TransactionCase):
|
||||
u3 = Users.create(dict(name='C', login='c', **kw)).id
|
||||
u4 = Users.sudo(user=u2).create(dict(name='D', login='z', **kw)).id
|
||||
|
||||
expected_order = [u2, u4, u3, u1]
|
||||
|
||||
domain = [('id', 'in', [u1, u2, u3, u4])]
|
||||
search_result = list(Users.search(domain)._ids)
|
||||
self.assertEqual(search_result, expected_order)
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
expected_ids = [u2, u4, u3, u1]
|
||||
found_ids = Users.search([('id', 'in', expected_ids)]).ids
|
||||
self.assertEqual(found_ids, expected_ids)
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from lxml.etree import XMLSyntaxError
|
||||
import unittest
|
||||
from openerp.tools.translate import quote, unquote, xml_translate, html_translate
|
||||
|
||||
from odoo.tools.translate import quote, unquote, xml_translate, html_translate
|
||||
from odoo.tests.common import TransactionCase
|
||||
|
||||
|
||||
class TranslationToolsTestCase(unittest.TestCase):
|
||||
|
||||
@@ -167,3 +169,56 @@ class TranslationToolsTestCase(unittest.TestCase):
|
||||
self.assertEquals(result, """<i class="fa-check"/>""")
|
||||
result = html_translate(lambda term: term, source)
|
||||
self.assertEquals(result, source)
|
||||
|
||||
|
||||
class TestTranslation(TransactionCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestTranslation, self).setUp()
|
||||
self.env['ir.translation'].load_module_terms(['base'], ['fr_FR'])
|
||||
self.customers = self.env['res.partner.category'].create({'name': 'Customers'})
|
||||
self.env['ir.translation'].create({
|
||||
'type': 'model',
|
||||
'name': 'res.partner.category,name',
|
||||
'module':'base',
|
||||
'lang': 'fr_FR',
|
||||
'res_id': self.customers.id,
|
||||
'value': 'Clients',
|
||||
'state': 'translated',
|
||||
})
|
||||
|
||||
def test_101_create_translated_record(self):
|
||||
category = self.customers.with_context({})
|
||||
self.assertEqual(category.name, 'Customers', "Error in basic name_get")
|
||||
|
||||
category_fr = category.with_context({'lang': 'fr_FR'})
|
||||
self.assertEqual(category_fr.name, 'Clients', "Translation not found")
|
||||
|
||||
def test_102_duplicate_record(self):
|
||||
category = self.customers.with_context({'lang': 'fr_FR'}).copy()
|
||||
|
||||
category_no = category.with_context({})
|
||||
self.assertEqual(category_no.name, 'Customers', "Duplication did not set untranslated value")
|
||||
|
||||
category_fr = category.with_context({'lang': 'fr_FR'})
|
||||
self.assertEqual(category_fr.name, 'Clients', "Did not found translation for initial value")
|
||||
|
||||
def test_103_duplicate_record_fr(self):
|
||||
category = self.customers.with_context({'lang': 'fr_FR'}).copy({'name': 'Clients (copie)'})
|
||||
|
||||
category_no = category.with_context({})
|
||||
self.assertEqual(category_no.name, 'Customers', "Duplication erased original untranslated value")
|
||||
|
||||
category_fr = category.with_context({'lang': 'fr_FR'})
|
||||
self.assertEqual(category_fr.name, 'Clients (copie)', "Did not used default value for translated value")
|
||||
|
||||
def test_104_orderby_translated_field(self):
|
||||
""" Test search ordered by a translated field. """
|
||||
# create a category with a French translation
|
||||
padawans = self.env['res.partner.category'].create({'name': 'Padawans'})
|
||||
padawans_fr = padawans.with_context(lang='fr_FR')
|
||||
padawans_fr.write({'name': 'Apprentis'})
|
||||
# search for categories, and sort them by (translated) name
|
||||
categories = padawans_fr.search([('id', 'in', [self.customers.id, padawans.id])], order='name')
|
||||
self.assertEqual(categories.ids, [padawans.id, self.customers.id],
|
||||
"Search ordered by translated name should return Padawans (Apprentis) before Customers (Clients)")
|
||||
|
||||
@@ -1,57 +1,31 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
# This assumes an existing but uninitialized database.
|
||||
|
||||
from contextlib import contextmanager
|
||||
import unittest
|
||||
|
||||
import openerp
|
||||
from openerp import SUPERUSER_ID
|
||||
import common
|
||||
from openerp import api, registry, SUPERUSER_ID
|
||||
from openerp.tests import common
|
||||
from openerp.modules.registry import RegistryManager
|
||||
|
||||
ADMIN_USER_ID = common.ADMIN_USER_ID
|
||||
|
||||
def registry(model):
|
||||
return openerp.modules.registry.RegistryManager.get(common.get_db_name())[model]
|
||||
@contextmanager
|
||||
def environment():
|
||||
""" Return an environment with a new cursor for the current database; the
|
||||
cursor is committed and closed after the context block.
|
||||
"""
|
||||
reg = registry(common.get_db_name())
|
||||
with reg.cursor() as cr:
|
||||
yield api.Environment(cr, SUPERUSER_ID, {})
|
||||
cr.commit()
|
||||
|
||||
def cursor():
|
||||
return openerp.modules.registry.RegistryManager.get(common.get_db_name()).cursor()
|
||||
|
||||
def get_module(module_name):
|
||||
registry = openerp.modules.registry.RegistryManager.get(common.get_db_name())
|
||||
return registry.get(module_name)
|
||||
MODULE = 'test_uninstall'
|
||||
MODEL = 'test_uninstall.model'
|
||||
|
||||
def reload_registry():
|
||||
openerp.modules.registry.RegistryManager.new(
|
||||
common.get_db_name(), update_module=True)
|
||||
|
||||
def search_registry(model_name, domain):
|
||||
cr = cursor()
|
||||
model = registry(model_name)
|
||||
record_ids = model.search(cr, SUPERUSER_ID, domain, {})
|
||||
cr.close()
|
||||
return record_ids
|
||||
|
||||
def install_module(module_name):
|
||||
ir_module_module = registry('ir.module.module')
|
||||
cr = cursor()
|
||||
module_ids = ir_module_module.search(cr, SUPERUSER_ID,
|
||||
[('name', '=', module_name)], {})
|
||||
assert len(module_ids) == 1
|
||||
ir_module_module.button_install(cr, SUPERUSER_ID, module_ids, {})
|
||||
cr.commit()
|
||||
cr.close()
|
||||
reload_registry()
|
||||
|
||||
def uninstall_module(module_name):
|
||||
ir_module_module = registry('ir.module.module')
|
||||
cr = cursor()
|
||||
module_ids = ir_module_module.search(cr, SUPERUSER_ID,
|
||||
[('name', '=', module_name)], {})
|
||||
assert len(module_ids) == 1
|
||||
ir_module_module.button_uninstall(cr, SUPERUSER_ID, module_ids, {})
|
||||
cr.commit()
|
||||
cr.close()
|
||||
reload_registry()
|
||||
|
||||
class test_uninstall(unittest.TestCase):
|
||||
class TestUninstall(unittest.TestCase):
|
||||
"""
|
||||
Test the install/uninstall of a test module. The module is available in
|
||||
`openerp.tests` which should be present in the addons-path.
|
||||
@@ -59,26 +33,29 @@ class test_uninstall(unittest.TestCase):
|
||||
|
||||
def test_01_install(self):
|
||||
""" Check a few things showing the module is installed. """
|
||||
install_module('test_uninstall')
|
||||
assert get_module('test_uninstall.model')
|
||||
with environment() as env:
|
||||
module = env['ir.module.module'].search([('name', '=', MODULE)])
|
||||
assert len(module) == 1
|
||||
module.button_install()
|
||||
RegistryManager.new(common.get_db_name(), update_module=True)
|
||||
|
||||
assert search_registry('ir.model.data',
|
||||
[('module', '=', 'test_uninstall')])
|
||||
|
||||
assert search_registry('ir.model.fields',
|
||||
[('model', '=', 'test_uninstall.model')])
|
||||
with environment() as env:
|
||||
self.assertIn('test_uninstall.model', env.registry)
|
||||
self.assertTrue(env['ir.model.data'].search([('module', '=', MODULE)]))
|
||||
self.assertTrue(env['ir.model.fields'].search([('model', '=', MODEL)]))
|
||||
|
||||
def test_02_uninstall(self):
|
||||
""" Check a few things showing the module is uninstalled. """
|
||||
uninstall_module('test_uninstall')
|
||||
assert not get_module('test_uninstall.model')
|
||||
|
||||
assert not search_registry('ir.model.data',
|
||||
[('module', '=', 'test_uninstall')])
|
||||
|
||||
assert not search_registry('ir.model.fields',
|
||||
[('model', '=', 'test_uninstall.model')])
|
||||
with environment() as env:
|
||||
module = env['ir.module.module'].search([('name', '=', MODULE)])
|
||||
assert len(module) == 1
|
||||
module.button_uninstall()
|
||||
RegistryManager.new(common.get_db_name(), update_module=True)
|
||||
|
||||
with environment() as env:
|
||||
self.assertNotIn('test_uninstall.model', env.registry)
|
||||
self.assertFalse(env['ir.model.data'].search([('module', '=', MODULE)]))
|
||||
self.assertFalse(env['ir.model.fields'].search([('model', '=', MODEL)]))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import openerp.tests
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
class TestHasGroup(openerp.tests.TransactionCase):
|
||||
from odoo.tests.common import TransactionCase
|
||||
|
||||
|
||||
class TestHasGroup(TransactionCase):
|
||||
def setUp(self):
|
||||
super(TestHasGroup, self).setUp()
|
||||
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
# This test can be run stand-alone with something like:
|
||||
# > PYTHONPATH=. python2 openerp/tests/test_view_validation.py
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from lxml import etree
|
||||
from StringIO import StringIO
|
||||
import unittest
|
||||
|
||||
from openerp.tools.view_validation import (valid_page_in_book, valid_att_in_form, valid_type_in_colspan,
|
||||
valid_type_in_col, valid_att_in_field, valid_att_in_label,
|
||||
valid_field_in_graph, valid_field_in_tree
|
||||
)
|
||||
from odoo.tools.view_validation import (
|
||||
valid_page_in_book, valid_att_in_form, valid_type_in_colspan,
|
||||
valid_type_in_col, valid_att_in_field, valid_att_in_label,
|
||||
valid_field_in_graph, valid_field_in_tree,
|
||||
)
|
||||
|
||||
invalid_form = etree.parse(StringIO('''\
|
||||
<form>
|
||||
@@ -92,7 +94,7 @@ valid_tree = etree.parse(StringIO('''\
|
||||
''')).getroot()
|
||||
|
||||
|
||||
class test_view_validation(unittest.TestCase):
|
||||
class TestViewValidation(unittest.TestCase):
|
||||
""" Test the view validation code (but not the views themselves). """
|
||||
|
||||
def test_page_validation(self):
|
||||
@@ -125,7 +127,3 @@ class test_view_validation(unittest.TestCase):
|
||||
def test_col_datatype_validation(self):
|
||||
assert not valid_type_in_col(invalid_form)
|
||||
assert valid_type_in_col(valid_form)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,21 +1,23 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import openerp.tests.common
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.tests import common
|
||||
|
||||
|
||||
class test_xmlrpc(openerp.tests.common.HttpCase):
|
||||
class TestXMLRPC(common.HttpCase):
|
||||
at_install = False
|
||||
post_install = True
|
||||
|
||||
def test_01_xmlrpc_login(self):
|
||||
""" Try to login on the common service. """
|
||||
db_name = openerp.tests.common.get_db_name()
|
||||
db_name = common.get_db_name()
|
||||
uid = self.xmlrpc_common.login(db_name, 'admin', 'admin')
|
||||
self.assertEqual(uid, 1)
|
||||
|
||||
def test_xmlrpc_ir_model_search(self):
|
||||
""" Try a search on the object service. """
|
||||
o = self.xmlrpc_object
|
||||
db_name = openerp.tests.common.get_db_name()
|
||||
db_name = common.get_db_name()
|
||||
ids = o.execute(db_name, 1, 'admin', 'ir.model', 'search', [])
|
||||
self.assertIsInstance(ids, list)
|
||||
ids = o.execute(db_name, 1, 'admin', 'ir.model', 'search', [], {})
|
||||
|
||||
@@ -1,183 +1,183 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from openerp.exceptions import UserError
|
||||
from openerp.osv import fields, osv
|
||||
from openerp.tools.translate import _
|
||||
import openerp.workflow
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.exceptions import UserError
|
||||
import odoo.workflow
|
||||
|
||||
class workflow(osv.osv):
|
||||
|
||||
class Workflow(models.Model):
|
||||
_name = "workflow"
|
||||
_table = "wkf"
|
||||
_order = "name"
|
||||
_columns = {
|
||||
'name': fields.char('Name', required=True),
|
||||
'osv': fields.char('Resource Object', required=True,select=True),
|
||||
'on_create': fields.boolean('On Create', select=True),
|
||||
'activities': fields.one2many('workflow.activity', 'wkf_id', 'Activities'),
|
||||
}
|
||||
_defaults = {
|
||||
'on_create': lambda *a: True
|
||||
}
|
||||
|
||||
def copy(self, cr, uid, id, values, context=None):
|
||||
name = fields.Char('Name', required=True)
|
||||
osv = fields.Char('Resource Object', required=True, index=True)
|
||||
on_create = fields.Boolean('On Create', default=True, index=True)
|
||||
activities = fields.One2many('workflow.activity', 'wkf_id', string='Activities')
|
||||
|
||||
@api.multi
|
||||
def copy(self, values):
|
||||
raise UserError(_("Duplicating workflows is not possible, please create a new workflow"))
|
||||
|
||||
def write(self, cr, user, ids, vals, context=None):
|
||||
if not context:
|
||||
context={}
|
||||
openerp.workflow.clear_cache(cr, user)
|
||||
return super(workflow, self).write(cr, user, ids, vals, context=context)
|
||||
@api.multi
|
||||
def write(self, vals):
|
||||
odoo.workflow.clear_cache(self._cr, self._uid)
|
||||
return super(Workflow, self).write(vals)
|
||||
|
||||
def get_active_workitems(self, cr, uid, res, res_id, context=None):
|
||||
cr.execute('select * from wkf where osv=%s limit 1',(res,))
|
||||
@api.model
|
||||
def get_active_workitems(self, res_model, res_id):
|
||||
cr = self._cr
|
||||
cr.execute("SELECT * FROM wkf WHERE osv=%s LIMIT 1", (res_model,))
|
||||
wkfinfo = cr.dictfetchone()
|
||||
workitems = []
|
||||
if wkfinfo:
|
||||
cr.execute('SELECT id FROM wkf_instance \
|
||||
WHERE res_id=%s AND wkf_id=%s \
|
||||
ORDER BY state LIMIT 1',
|
||||
(res_id, wkfinfo['id']))
|
||||
query = """ SELECT id FROM wkf_instance
|
||||
WHERE res_id=%s AND wkf_id=%s
|
||||
ORDER BY state LIMIT 1 """
|
||||
cr.execute(query, (res_id, wkfinfo['id']))
|
||||
inst_id = cr.fetchone()
|
||||
|
||||
cr.execute('select act_id,count(*) from wkf_workitem where inst_id=%s group by act_id', (inst_id,))
|
||||
query = """ SELECT act_id, COUNT(*) FROM wkf_workitem
|
||||
WHERE inst_id=%s GROUP BY act_id """
|
||||
cr.execute(query, (inst_id,))
|
||||
workitems = dict(cr.fetchall())
|
||||
return {'wkf': wkfinfo, 'workitems': workitems}
|
||||
return {'wkf': wkfinfo, 'workitems': workitems}
|
||||
|
||||
def create(self, cr, user, vals, context=None):
|
||||
if not context:
|
||||
context={}
|
||||
openerp.workflow.clear_cache(cr, user)
|
||||
return super(workflow, self).create(cr, user, vals, context=context)
|
||||
@api.model
|
||||
def create(self, vals):
|
||||
odoo.workflow.clear_cache(self._cr, self._uid)
|
||||
return super(Workflow, self).create(vals)
|
||||
|
||||
workflow()
|
||||
|
||||
class wkf_activity(osv.osv):
|
||||
class WorkflowActivity(models.Model):
|
||||
_name = "workflow.activity"
|
||||
_table = "wkf_activity"
|
||||
_order = "name"
|
||||
_columns = {
|
||||
'name': fields.char('Name', required=True),
|
||||
'wkf_id': fields.many2one('workflow', 'Workflow', required=True, select=True, ondelete='cascade'),
|
||||
'split_mode': fields.selection([('XOR', 'Xor'), ('OR','Or'), ('AND','And')], 'Split Mode', size=3, required=True),
|
||||
'join_mode': fields.selection([('XOR', 'Xor'), ('AND', 'And')], 'Join Mode', size=3, required=True),
|
||||
'kind': fields.selection([('dummy', 'Dummy'), ('function', 'Function'), ('subflow', 'Subflow'), ('stopall', 'Stop All')], 'Kind', required=True),
|
||||
'action': fields.text('Python Action'),
|
||||
'action_id': fields.many2one('ir.actions.server', 'Server Action', ondelete='set null'),
|
||||
'flow_start': fields.boolean('Flow Start'),
|
||||
'flow_stop': fields.boolean('Flow Stop'),
|
||||
'subflow_id': fields.many2one('workflow', 'Subflow'),
|
||||
'signal_send': fields.char('Signal (subflow.*)'),
|
||||
'out_transitions': fields.one2many('workflow.transition', 'act_from', 'Outgoing Transitions'),
|
||||
'in_transitions': fields.one2many('workflow.transition', 'act_to', 'Incoming Transitions'),
|
||||
}
|
||||
_defaults = {
|
||||
'kind': lambda *a: 'dummy',
|
||||
'join_mode': lambda *a: 'XOR',
|
||||
'split_mode': lambda *a: 'XOR',
|
||||
}
|
||||
|
||||
def unlink(self, cr, uid, ids, context=None):
|
||||
if context is None: context = {}
|
||||
if not context.get('_force_unlink') and self.pool.get('workflow.workitem').search(cr, uid, [('act_id', 'in', ids)]):
|
||||
name = fields.Char('Name', required=True)
|
||||
wkf_id = fields.Many2one('workflow', string='Workflow', ondelete='cascade',
|
||||
required=True, index=True)
|
||||
split_mode = fields.Selection([('XOR', 'Xor'), ('OR','Or'), ('AND','And')],
|
||||
string='Split Mode', size=3, required=True, default='XOR')
|
||||
join_mode = fields.Selection([('XOR', 'Xor'), ('AND', 'And')],
|
||||
string='Join Mode', size=3, required=True, default='XOR')
|
||||
kind = fields.Selection([('dummy', 'Dummy'), ('function', 'Function'),
|
||||
('subflow', 'Subflow'), ('stopall', 'Stop All')],
|
||||
string='Kind', required=True, default='dummy')
|
||||
action = fields.Text('Python Action')
|
||||
action_id = fields.Many2one('ir.actions.server', string='Server Action', ondelete='set null')
|
||||
flow_start = fields.Boolean('Flow Start')
|
||||
flow_stop = fields.Boolean('Flow Stop')
|
||||
subflow_id = fields.Many2one('workflow', string='Subflow')
|
||||
signal_send = fields.Char('Signal (subflow.*)')
|
||||
out_transitions = fields.One2many('workflow.transition', 'act_from', string='Outgoing Transitions')
|
||||
in_transitions = fields.One2many('workflow.transition', 'act_to', string='Incoming Transitions')
|
||||
|
||||
@api.multi
|
||||
def unlink(self):
|
||||
if not self._context.get('_force_unlink') and \
|
||||
self.env['workflow.workitem'].search([('act_id', 'in', self.ids)]):
|
||||
raise UserError(_('Please make sure no workitems refer to an activity before deleting it!'))
|
||||
super(wkf_activity, self).unlink(cr, uid, ids, context=context)
|
||||
super(WorkflowActivity, self).unlink()
|
||||
|
||||
wkf_activity()
|
||||
|
||||
class wkf_transition(osv.osv):
|
||||
_table = "wkf_transition"
|
||||
class WorkflowTransition(models.Model):
|
||||
_name = "workflow.transition"
|
||||
_table = "wkf_transition"
|
||||
_rec_name = 'signal'
|
||||
_columns = {
|
||||
'trigger_model': fields.char('Trigger Object'),
|
||||
'trigger_expr_id': fields.char('Trigger Expression'),
|
||||
'sequence': fields.integer('Sequence'),
|
||||
'signal': fields.char('Signal (Button Name)',
|
||||
help="When the operation of transition comes from a button pressed in the client form, "\
|
||||
"signal tests the name of the pressed button. If signal is NULL, no button is necessary to validate this transition."),
|
||||
'group_id': fields.many2one('res.groups', 'Group Required',
|
||||
help="The group that a user must have to be authorized to validate this transition."),
|
||||
'condition': fields.char('Condition', required=True,
|
||||
help="Expression to be satisfied if we want the transition done."),
|
||||
'act_from': fields.many2one('workflow.activity', 'Source Activity', required=True, select=True, ondelete='cascade',
|
||||
help="Source activity. When this activity is over, the condition is tested to determine if we can start the ACT_TO activity."),
|
||||
'act_to': fields.many2one('workflow.activity', 'Destination Activity', required=True, select=True, ondelete='cascade',
|
||||
help="The destination activity."),
|
||||
'wkf_id': fields.related('act_from','wkf_id', type='many2one', relation='workflow', string='Workflow', select=True),
|
||||
}
|
||||
_defaults = {
|
||||
'condition': lambda *a: 'True',
|
||||
'sequence': 10,
|
||||
}
|
||||
|
||||
_order = 'sequence,id'
|
||||
|
||||
def name_get(self, cr, uid, ids, context=None):
|
||||
return [(line.id, (line.act_from.name) + '+' + (line.act_to.name)) if line.signal == False else (line.id, line.signal) for line in self.browse(cr, uid, ids, context=context)]
|
||||
trigger_model = fields.Char('Trigger Object')
|
||||
trigger_expr_id = fields.Char('Trigger Expression')
|
||||
sequence = fields.Integer('Sequence', default=10)
|
||||
signal = fields.Char('Signal (Button Name)',
|
||||
help="When the operation of transition comes from a button pressed in the client form, "
|
||||
"signal tests the name of the pressed button. If signal is NULL, no button is necessary to validate this transition.")
|
||||
group_id = fields.Many2one('res.groups', string='Group Required',
|
||||
help="The group that a user must have to be authorized to validate this transition.")
|
||||
condition = fields.Char('Condition', required=True, default='True',
|
||||
help="Expression to be satisfied if we want the transition done.")
|
||||
act_from = fields.Many2one('workflow.activity', string='Source Activity',
|
||||
ondelete='cascade', required=True, index=True,
|
||||
help="Source activity. When this activity is over, the condition is tested to determine if we can start the ACT_TO activity.")
|
||||
act_to = fields.Many2one('workflow.activity', string='Destination Activity',
|
||||
ondelete='cascade', required=True, index=True,
|
||||
help="The destination activity.")
|
||||
wkf_id = fields.Many2one('workflow', related='act_from.wkf_id', string='Workflow')
|
||||
|
||||
def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=100):
|
||||
if args is None:
|
||||
args = []
|
||||
@api.multi
|
||||
def name_get(self):
|
||||
return [
|
||||
(line.id, line.signal or "%s+%s" % (line.act_from.name, line.act_to.name))
|
||||
for line in self
|
||||
]
|
||||
|
||||
@api.model
|
||||
def name_search(self, name, args=None, operator='ilike', limit=100):
|
||||
if name:
|
||||
ids = self.search(cr, user, ['|',('act_from', operator, name),('act_to', operator, name)] + args, limit=limit)
|
||||
return self.name_get(cr, user, ids, context=context)
|
||||
return super(wkf_transition, self).name_search(cr, user, name, args=args, operator=operator, context=context, limit=limit)
|
||||
domain = ['|', ('act_from', operator, name), ('act_to', operator, name)] + (args or [])
|
||||
return self.search(domain, limit=limit).name_get()
|
||||
return super(WorkflowTransition, self).name_search(name, args, operator, limit=limit)
|
||||
|
||||
|
||||
wkf_transition()
|
||||
|
||||
class wkf_instance(osv.osv):
|
||||
_table = "wkf_instance"
|
||||
class WorkflowInstance(models.Model):
|
||||
_name = "workflow.instance"
|
||||
_table = "wkf_instance"
|
||||
_rec_name = 'res_type'
|
||||
_log_access = False
|
||||
_columns = {
|
||||
'uid': fields.integer('User'), # FIXME no constraint??
|
||||
'wkf_id': fields.many2one('workflow', 'Workflow', ondelete='cascade', select=True),
|
||||
'res_id': fields.integer('Resource ID'),
|
||||
'res_type': fields.char('Resource Object'),
|
||||
'state': fields.char('Status'),
|
||||
'transition_ids': fields.many2many('workflow.transition', 'wkf_witm_trans', 'inst_id', 'trans_id'),
|
||||
}
|
||||
def _auto_init(self, cr, context=None):
|
||||
super(wkf_instance, self)._auto_init(cr, context)
|
||||
cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = \'wkf_instance_res_type_res_id_state_index\'')
|
||||
|
||||
uid = fields.Integer('User') # FIXME no constraint??
|
||||
wkf_id = fields.Many2one('workflow', string='Workflow', ondelete='cascade', index=True)
|
||||
res_id = fields.Integer('Resource ID')
|
||||
res_type = fields.Char('Resource Object')
|
||||
state = fields.Char('Status')
|
||||
transition_ids = fields.Many2many('workflow.transition', 'wkf_witm_trans', 'inst_id', 'trans_id')
|
||||
|
||||
@api.model_cr_context
|
||||
def _auto_init(self):
|
||||
super(WorkflowInstance, self)._auto_init()
|
||||
cr = self._cr
|
||||
cr.execute('SELECT indexname FROM pg_indexes WHERE indexname=%s', ['wkf_instance_res_type_res_id_state_index'])
|
||||
if not cr.fetchone():
|
||||
cr.execute('CREATE INDEX wkf_instance_res_type_res_id_state_index ON wkf_instance (res_type, res_id, state)')
|
||||
cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = \'wkf_instance_res_id_wkf_id_index\'')
|
||||
cr.execute('SELECT indexname FROM pg_indexes WHERE indexname=%s', ['wkf_instance_res_id_wkf_id_index'])
|
||||
if not cr.fetchone():
|
||||
cr.execute('CREATE INDEX wkf_instance_res_id_wkf_id_index ON wkf_instance (res_id, wkf_id)')
|
||||
|
||||
wkf_instance()
|
||||
|
||||
class wkf_workitem(osv.osv):
|
||||
_table = "wkf_workitem"
|
||||
class WorkflowWorkitem(models.Model):
|
||||
_name = "workflow.workitem"
|
||||
_log_access = False
|
||||
_table = "wkf_workitem"
|
||||
_rec_name = 'state'
|
||||
_columns = {
|
||||
'act_id': fields.many2one('workflow.activity', 'Activity', required=True, ondelete="cascade", select=True),
|
||||
'wkf_id': fields.related('act_id','wkf_id', type='many2one', relation='workflow', string='Workflow'),
|
||||
'subflow_id': fields.many2one('workflow.instance', 'Subflow', ondelete="set null", select=True),
|
||||
'inst_id': fields.many2one('workflow.instance', 'Instance', required=True, ondelete="cascade", select=True),
|
||||
'state': fields.char('Status', select=True),
|
||||
}
|
||||
wkf_workitem()
|
||||
|
||||
class wkf_triggers(osv.osv):
|
||||
_table = "wkf_triggers"
|
||||
_name = "workflow.triggers"
|
||||
_log_access = False
|
||||
_columns = {
|
||||
'res_id': fields.integer('Resource ID', size=128),
|
||||
'model': fields.char('Object'),
|
||||
'instance_id': fields.many2one('workflow.instance', 'Destination Instance', ondelete="cascade"),
|
||||
'workitem_id': fields.many2one('workflow.workitem', 'Workitem', required=True, ondelete="cascade"),
|
||||
}
|
||||
def _auto_init(self, cr, context=None):
|
||||
super(wkf_triggers, self)._auto_init(cr, context)
|
||||
cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = \'wkf_triggers_res_id_model_index\'')
|
||||
|
||||
act_id = fields.Many2one('workflow.activity', string='Activity',
|
||||
ondelete="cascade", required=True, index=True)
|
||||
wkf_id = fields.Many2one('workflow', related='act_id.wkf_id', string='Workflow')
|
||||
subflow_id = fields.Many2one('workflow.instance', string='Subflow',
|
||||
ondelete="set null", index=True)
|
||||
inst_id = fields.Many2one('workflow.instance', string='Instance',
|
||||
ondelete="cascade", required=True, index=True)
|
||||
state = fields.Char('Status', index=True)
|
||||
|
||||
|
||||
class WorkflowTriggers(models.Model):
|
||||
_name = "workflow.triggers"
|
||||
_table = "wkf_triggers"
|
||||
_log_access = False
|
||||
|
||||
res_id = fields.Integer('Resource ID', size=128)
|
||||
model = fields.Char('Object')
|
||||
instance_id = fields.Many2one('workflow.instance', string='Destination Instance',
|
||||
ondelete="cascade")
|
||||
workitem_id = fields.Many2one('workflow.workitem', string='Workitem',
|
||||
ondelete="cascade", required=True)
|
||||
|
||||
@api.model_cr_context
|
||||
def _auto_init(self):
|
||||
super(WorkflowTriggers, self)._auto_init()
|
||||
cr = self._cr
|
||||
cr.execute('SELECT indexname FROM pg_indexes WHERE indexname=%s', ['wkf_triggers_res_id_model_index'])
|
||||
if not cr.fetchone():
|
||||
cr.execute('CREATE INDEX wkf_triggers_res_id_model_index ON wkf_triggers (res_id, model)')
|
||||
wkf_triggers()
|
||||
|
||||
@@ -1972,6 +1972,7 @@ class Many2many(_RelationalMulti):
|
||||
'relation': None, # name of table
|
||||
'column1': None, # column of table referring to model
|
||||
'column2': None, # column of table referring to comodel
|
||||
'auto_join': False, # whether joins are generated upon search
|
||||
'limit': None, # optional limit to use upon read
|
||||
}
|
||||
|
||||
@@ -2014,6 +2015,7 @@ class Many2many(_RelationalMulti):
|
||||
_column_rel = property(attrgetter('relation'))
|
||||
_column_id1 = property(attrgetter('column1'))
|
||||
_column_id2 = property(attrgetter('column2'))
|
||||
_column_auto_join = property(attrgetter('auto_join'))
|
||||
_column_limit = property(attrgetter('limit'))
|
||||
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
The main duty of this module is to compile a domain expression into a
|
||||
SQL query. A lot of things should be documented here, but as a first
|
||||
step in the right direction, some tests in test_osv_expression.yml
|
||||
step in the right direction, some tests in test_expression.py
|
||||
might give you some additional information.
|
||||
|
||||
For legacy reasons, a domain uses an inconsistent two-levels abstract
|
||||
|
||||
@@ -947,6 +947,7 @@ class many2many(_column):
|
||||
args['relation'] = self._rel
|
||||
args['column1'] = self._id1
|
||||
args['column2'] = self._id2
|
||||
args['auto_join'] = self._auto_join
|
||||
args['limit'] = self._limit
|
||||
return args
|
||||
|
||||
|
||||
Reference in New Issue
Block a user