[MERGE] sync with trunk
authorChristophe Matthieu <chm@openerp.com>
Mon, 2 Dec 2013 15:08:56 +0000 (16:08 +0100)
committerChristophe Matthieu <chm@openerp.com>
Mon, 2 Dec 2013 15:08:56 +0000 (16:08 +0100)
bzr revid: chm@openerp.com-20131202150856-fzrfcoa8pmsjutau

16 files changed:
1  2 
openerp/addons/base/base.sql
openerp/addons/base/ir/ir_attachment.py
openerp/addons/base/ir/ir_fields.py
openerp/addons/base/ir/ir_http.py
openerp/addons/base/ir/ir_model.py
openerp/addons/base/ir/ir_translation.py
openerp/addons/base/res/res_users.py
openerp/addons/base/tests/test_views.py
openerp/http.py
openerp/osv/fields.py
openerp/osv/orm.py
openerp/tests/test_acl.py
openerp/tests/test_mail.py
openerp/tools/mail.py
openerp/tools/misc.py
openerpcommand/common.py

Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
@@@ -8,428 -5,54 +8,475 @@@ from openerp.tests import commo
  from openerp.osv.orm import except_orm
  from openerp.tools import mute_logger
  
 +Field = E.field
 +
 +
 +class TestNodeLocator(common.BaseCase):
 +    """
 +    The node locator returns None when it can not find a node, and the first
 +    match when it finds something (no jquery-style node sets)
 +    """
 +    def setUp(self):
 +        super(TestNodeLocator, self).setUp()
 +        self.Views = self.registry('ir.ui.view')
 +
 +    def test_no_match_xpath(self):
 +        """
 +        xpath simply uses the provided @expr pattern to find a node
 +        """
 +        node = self.Views.locate_node(
 +            E.root(E.foo(), E.bar(), E.baz()),
 +            E.xpath(expr="//qux"))
 +        self.assertIsNone(node)
 +
 +    def test_match_xpath(self):
 +        bar = E.bar()
 +        node = self.Views.locate_node(
 +            E.root(E.foo(), bar, E.baz()),
 +            E.xpath(expr="//bar"))
 +        self.assertIs(node, bar)
 +
 +
 +    def test_no_match_field(self):
 +        """
 +        A field spec will match by @name against all fields of the view
 +        """
 +        node = self.Views.locate_node(
 +            E.root(E.foo(), E.bar(), E.baz()),
 +            Field(name="qux"))
 +        self.assertIsNone(node)
 +
 +        node = self.Views.locate_node(
 +            E.root(Field(name="foo"), Field(name="bar"), Field(name="baz")),
 +            Field(name="qux"))
 +        self.assertIsNone(node)
 +
 +    def test_match_field(self):
 +        bar = Field(name="bar")
 +        node = self.Views.locate_node(
 +            E.root(Field(name="foo"), bar, Field(name="baz")),
 +            Field(name="bar"))
 +        self.assertIs(node, bar)
 +
 +
 +    def test_no_match_other(self):
 +        """
 +        Non-xpath non-fields are matched by node name first
 +        """
 +        node = self.Views.locate_node(
 +            E.root(E.foo(), E.bar(), E.baz()),
 +            E.qux())
 +        self.assertIsNone(node)
 +
 +    def test_match_other(self):
 +        bar = E.bar()
 +        node = self.Views.locate_node(
 +            E.root(E.foo(), bar, E.baz()),
 +            E.bar())
 +        self.assertIs(bar, node)
 +
 +    def test_attribute_mismatch(self):
 +        """
 +        Non-xpath non-field are filtered by matching attributes on spec and
 +        matched nodes
 +        """
 +        node = self.Views.locate_node(
 +            E.root(E.foo(attr='1'), E.bar(attr='2'), E.baz(attr='3')),
 +            E.bar(attr='5'))
 +        self.assertIsNone(node)
 +
 +    def test_attribute_filter(self):
 +        match = E.bar(attr='2')
 +        node = self.Views.locate_node(
 +            E.root(E.bar(attr='1'), match, E.root(E.bar(attr='3'))),
 +            E.bar(attr='2'))
 +        self.assertIs(node, match)
 +
 +    def test_version_mismatch(self):
 +        """
 +        A @version on the spec will be matched against the view's version
 +        """
 +        node = self.Views.locate_node(
 +            E.root(E.foo(attr='1'), version='4'),
 +            E.foo(attr='1', version='3'))
 +        self.assertIsNone(node)
 +
 +class TestViewInheritance(common.TransactionCase):
 +    def arch_for(self, name, view_type='form', parent=None):
 +        """ Generates a trivial view of the specified ``view_type``.
 +
 +        The generated view is empty but ``name`` is set as its root's ``@string``.
 +
 +        If ``parent`` is not falsy, generates an extension view (instead of
 +        a root view) replacing the parent's ``@string`` by ``name``
 +
 +        :param str name: ``@string`` value for the view root
 +        :param str view_type:
 +        :param bool parent:
 +        :return: generated arch
 +        :rtype: str
 +        """
 +        if not parent:
 +            element = E(view_type, string=name)
 +        else:
 +            element = E(view_type,
 +                E.attribute(name, name='string'),
 +                position='attributes'
 +            )
 +        return ET.tostring(element)
 +
 +    def makeView(self, name, parent=None, arch=None):
 +        """ Generates a basic ir.ui.view with the provided name, parent and arch.
 +
 +        If no parent is provided, the view is top-level.
 +
 +        If no arch is provided, generates one by calling :meth:`~.arch_for`.
 +
 +        :param str name:
 +        :param int parent: id of the parent view, if any
 +        :param str arch:
 +        :returns: the created view's id.
 +        :rtype: int
 +        """
 +        view_id = self.View.create(self.cr, self.uid, {
 +            'model': self.model,
 +            'name': name,
 +            'arch': arch or self.arch_for(name, parent=parent),
 +            'inherit_id': parent,
 +        })
 +        self.ids[name] = view_id
 +        return view_id
 +
 +    def setUp(self):
 +        super(TestViewInheritance, self).setUp()
 +
 +        self.model = 'dummy'
 +        self.View = self.registry('ir.ui.view')
 +        self._init = self.View.pool._init
 +        self.View.pool._init = False
 +        self.ids = {}
 +
 +        a = self.makeView("A")
 +        a1 = self.makeView("A1", a)
 +        a11 = self.makeView("A11", a1)
 +        self.makeView("A111", a11)
 +        self.makeView("A12", a1)
 +        a2 = self.makeView("A2", a)
 +        self.makeView("A21", a2)
 +        a22 = self.makeView("A22", a2)
 +        self.makeView("A221", a22)
 +
 +        b = self.makeView('B', arch=self.arch_for("B", 'tree'))
 +        self.makeView('B1', b, arch=self.arch_for("B1", 'tree', parent=b))
 +        c = self.makeView('C', arch=self.arch_for("C", 'tree'))
 +        self.View.write(self.cr, self.uid, c, {'priority': 1})
 +
 +    def tearDown(self):
 +        self.View.pool._init = self._init
 +        super(TestViewInheritance, self).tearDown()
 +
 +    def test_get_inheriting_views_arch(self):
 +        self.assertEqual(self.View.get_inheriting_views_arch(
 +            self.cr, self.uid, self.ids['A'], self.model), [
 +            (self.arch_for('A1', parent=True), self.ids['A1']),
 +            (self.arch_for('A2', parent=True), self.ids['A2']),
 +        ])
 +
 +        self.assertEqual(self.View.get_inheriting_views_arch(
 +            self.cr, self.uid, self.ids['A21'], self.model),
 +            [])
 +
 +        self.assertEqual(self.View.get_inheriting_views_arch(
 +            self.cr, self.uid, self.ids['A11'], self.model),
 +            [(self.arch_for('A111', parent=True), self.ids['A111'])])
 +
 +    def test_default_view(self):
 +        default = self.View.default_view(
 +            self.cr, self.uid, model=self.model, view_type='form')
 +        self.assertEqual(default, self.ids['A'])
 +
 +        default_tree = self.View.default_view(
 +            self.cr, self.uid, model=self.model, view_type='tree')
 +        self.assertEqual(default_tree, self.ids['C'])
 +
 +    def test_no_default_view(self):
 +        self.assertFalse(
 +            self.View.default_view(
 +                self.cr, self.uid, model='does.not.exist', view_type='form'))
 +
 +        self.assertFalse(
 +            self.View.default_view(
 +                self.cr, self.uid, model=self.model, view_type='graph'))
 +
 +class TestApplyInheritanceSpecs(common.TransactionCase):
 +    """ Applies a sequence of inheritance specification nodes to a base
 +    architecture. IO state parameters (cr, uid, model, context) are used for
 +    error reporting
 +
 +    The base architecture is altered in-place.
 +    """
 +    def setUp(self):
 +        super(TestApplyInheritanceSpecs, self).setUp()
 +        self.View = self.registry('ir.ui.view')
 +        self.base_arch = E.form(
 +            Field(name="target"),
 +            string="Title")
 +
 +    def test_replace(self):
 +        spec = Field(
 +                Field(name="replacement"),
 +                name="target", position="replace")
 +
 +        self.View.apply_inheritance_specs(self.cr, self.uid,
 +                                          self.base_arch,
 +                                          spec, None)
 +
 +        self.assertEqual(
 +            ET.tostring(self.base_arch),
 +            ET.tostring(E.form(Field(name="replacement"), string="Title")))
 +
 +    def test_delete(self):
 +        spec = Field(name="target", position="replace")
 +
 +        self.View.apply_inheritance_specs(self.cr, self.uid,
 +                                          self.base_arch,
 +                                          spec, None)
 +
 +        self.assertEqual(
 +            ET.tostring(self.base_arch),
 +            ET.tostring(E.form(string="Title")))
 +
 +    def test_insert_after(self):
 +        spec = Field(
 +                Field(name="inserted"),
 +                name="target", position="after")
 +
 +        self.View.apply_inheritance_specs(self.cr, self.uid,
 +                                          self.base_arch,
 +                                          spec, None)
 +
 +        self.assertEqual(
 +            ET.tostring(self.base_arch),
 +            ET.tostring(E.form(
 +                Field(name="target"),
 +                Field(name="inserted"),
 +                string="Title"
 +            )))
 +
 +    def test_insert_before(self):
 +        spec = Field(
 +                Field(name="inserted"),
 +                name="target", position="before")
 +
 +        self.View.apply_inheritance_specs(self.cr, self.uid,
 +                                          self.base_arch,
 +                                          spec, None)
 +
 +        self.assertEqual(
 +            ET.tostring(self.base_arch),
 +            ET.tostring(E.form(
 +                Field(name="inserted"),
 +                Field(name="target"),
 +                string="Title")))
 +
 +    def test_insert_inside(self):
 +        default = Field(Field(name="inserted"), name="target")
 +        spec = Field(Field(name="inserted 2"), name="target", position='inside')
 +
 +        self.View.apply_inheritance_specs(self.cr, self.uid,
 +                                          self.base_arch,
 +                                          default, None)
 +        self.View.apply_inheritance_specs(self.cr, self.uid,
 +                                          self.base_arch,
 +                                          spec, None)
 +
 +        self.assertEqual(
 +            ET.tostring(self.base_arch),
 +            ET.tostring(E.form(
 +                Field(
 +                    Field(name="inserted"),
 +                    Field(name="inserted 2"),
 +                    name="target"),
 +                string="Title")))
 +
 +    def test_unpack_data(self):
 +        spec = E.data(
 +                Field(Field(name="inserted 0"), name="target"),
 +                Field(Field(name="inserted 1"), name="target"),
 +                Field(Field(name="inserted 2"), name="target"),
 +                Field(Field(name="inserted 3"), name="target"),
 +            )
 +
 +        self.View.apply_inheritance_specs(self.cr, self.uid,
 +                                          self.base_arch,
 +                                          spec, None)
 +
 +        self.assertEqual(
 +            ET.tostring(self.base_arch),
 +            ET.tostring(E.form(
 +                Field(
 +                    Field(name="inserted 0"),
 +                    Field(name="inserted 1"),
 +                    Field(name="inserted 2"),
 +                    Field(name="inserted 3"),
 +                    name="target"),
 +                string="Title")))
 +
 +    def test_invalid_position(self):
 +        spec = Field(
 +                Field(name="whoops"),
 +                name="target", position="serious_series")
 +
 +        with self.assertRaises(AttributeError):
 +            self.View.apply_inheritance_specs(self.cr, self.uid,
 +                                              self.base_arch,
 +                                              spec, None)
 +
 +    def test_incorrect_version(self):
 +        # Version ignored on //field elements, so use something else
 +        arch = E.form(E.element(foo="42"))
 +        spec = E.element(
 +            Field(name="placeholder"),
 +            foo="42", version="7.0")
 +
 +        with self.assertRaises(AttributeError):
 +            self.View.apply_inheritance_specs(self.cr, self.uid,
 +                                              arch,
 +                                              spec, None)
 +
 +    def test_target_not_found(self):
 +        spec = Field(name="targut")
 +
 +        with self.assertRaises(AttributeError):
 +            self.View.apply_inheritance_specs(self.cr, self.uid,
 +                                              self.base_arch,
 +                                              spec, None)
 +
 +class TestApplyInheritedArchs(common.TransactionCase):
 +    """ Applies a sequence of modificator archs to a base view
 +    """
 +
 +class TestViewCombined(common.TransactionCase):
 +    """
 +    Test fallback operations of View.read_combined:
 +    * defaults mapping
 +    * ?
 +    """
 +
 +class TestNoModel(common.TransactionCase):
 +    def test_create_view_nomodel(self):
 +        View = self.registry('ir.ui.view')
 +        view_id = View.create(self.cr, self.uid, {
 +            'name': 'dummy',
 +            'arch': '<form string=""/>',
 +            'inherit_id': False
 +        })
 +        fields = ['name', 'arch', 'type', 'priority', 'inherit_id', 'model']
 +        [view] = View.read(self.cr, self.uid, [view_id], fields)
 +        self.assertEqual(view, {
 +            'id': view_id,
 +            'name': 'dummy',
 +            'arch': '<form string=""/>',
 +            'type': 'form',
 +            'priority': 16,
 +            'inherit_id': False,
 +            'model': False,
 +        })
 +
 +    arch = E.body(
 +        E.div(
 +            E.h1("Title"),
 +            id="header"),
 +        E.p("Welcome!"),
 +        E.div(
 +            E.hr(),
 +            E.p("Copyright copyrighter", {'class': 'legalese'}),
 +            id="footer"),
 +        {'class': "index"},)
 +    def test_fields_mess(self):
 +        """
 +        Try to call __view_look_dom_arch without a model provided, will need
 +        to be altered once it's broken up into sane components
 +        """
 +        View = self.registry('ir.ui.view')
 +
 +        sarch, fields = View.postprocess_and_fields(
 +            self.cr, self.uid, None, self.arch, None)
 +
 +        self.assertEqual(sarch, ET.tostring(self.arch, encoding='utf-8'))
 +        self.assertEqual(fields, {})
 +
 +    def test_mess_translation(self):
 +        """
 +        Test if translations work correctly without a model
 +        """
 +        View = self.registry('ir.ui.view')
 +        self.registry('res.lang').load_lang(self.cr, self.uid, 'fr_FR')
 +        self.registry('ir.translation').create(self.cr, self.uid, {
 +            'name': '',
 +            'type': 'view',
 +            'lang': 'fr_FR',
 +            'src': 'Copyright copyrighter',
 +            'value': u"Copyrighter, tous droits réservés",
 +        })
 +        sarch, fields = View.postprocess_and_fields(
 +            self.cr, self.uid, None, self.arch, None, {'lang': 'fr_FR'})
 +        self.assertEqual(
 +            sarch,
 +            ET.tostring(self.arch, encoding='utf-8')
 +                .replace('Copyright copyrighter',
 +                         'Copyrighter, tous droits réservés'))
 +        self.assertEqual(fields, {})
 +
 +
  class test_views(common.TransactionCase):
+     @mute_logger('openerp.osv.orm', 'openerp.addons.base.ir.ir_ui_view')
+     def test_00_init_check_views(self):
+         Views = self.registry('ir.ui.view')
+         self.assertTrue(Views.pool._init)
+         error_msg = "The model name does not exist or the view architecture cannot be rendered"
+         # test arch check is call for views without xmlid during registry initialization
+         with self.assertRaisesRegexp(except_orm, error_msg):
+             Views.create(self.cr, self.uid, {
+                 'name': 'Test View #1',
+                 'model': 'ir.ui.view',
+                 'arch': """<?xml version="1.0"?>
+                             <tree>
+                               <field name="test_1"/>
+                             </tree>
+                         """,
+             })
+         # same for inherited views
+         with self.assertRaisesRegexp(except_orm, error_msg):
+             # Views.pudb = True
+             Views.create(self.cr, self.uid, {
+                 'name': 'Test View #2',
+                 'model': 'ir.ui.view',
+                 'inherit_id': self.browse_ref('base.view_view_tree').id,
+                 'arch': """<?xml version="1.0"?>
+                             <xpath expr="//field[@name='name']" position="after">
+                               <field name="test_2"/>
+                             </xpath>
+                         """,
+             })
+     def test_20_remove_unexisting_attribute(self):
+         Views = self.registry('ir.ui.view')
+         Views.create(self.cr, self.uid, {
+             'name': 'Test View',
+             'model': 'ir.ui.view',
+             'inherit_id': self.browse_ref('base.view_view_tree').id,
+             'arch': """<?xml version="1.0"?>
+                         <xpath expr="//field[@name='name']" position="attributes">
+                             <attribute name="non_existing_attribute"></attribute>
+                         </xpath>
+                     """,
+         })
      def _insert_view(self, **kw):
          """Insert view into database via a query to passtrough validation"""
          kw.pop('id', None)
diff --cc openerp/http.py
@@@ -971,18 -974,30 +970,32 @@@ class Root(object)
  
              request = self.get_request(httprequest)
  
+             def _dispatch_nodb():
+                 func, arguments = self.nodb_routing_map.bind_to_environ(request.httprequest.environ).match()
+                 request.set_handler(func, arguments, "none")
+                 result = request.dispatch()
+                 return result
 -            with set_request(request):
 +            with request:
                  db = request.session.db
                  if db:
                      openerp.modules.registry.RegistryManager.check_registry_signaling(db)
-                     result = request.registry['ir.http']._dispatch()
-                     openerp.modules.registry.RegistryManager.signal_caches_change(db)
+                     try:
+                         ir_http = request.registry['ir.http']
+                     except psycopg2.OperationalError:
+                         # psycopg2 error. At this point, that's mean the database does not exists
+                         # anymore. We unlog the user and failback in nodb mode
+                         request.session.logout()
+                         result = _dispatch_nodb()
+                     else:
+                         result = ir_http._dispatch()
+                         openerp.modules.registry.RegistryManager.signal_caches_change(db)
                  else:
 -                    result = _dispatch_nodb()
 -
 -            response = self.get_response(httprequest, result, explicit_session)
 +                    # fallback to non-db handlers
 +                    func, arguments = self.nodb_routing_map.bind_to_environ(request.httprequest.environ).match()
 +                    request.set_handler(func, arguments, "none")
 +                    result = request.dispatch()
 +                response = self.get_response(httprequest, result, explicit_session)
              return response(environ, start_response)
  
          except werkzeug.exceptions.HTTPException, e:
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
@@@ -1136,32 -1069,42 +1139,71 @@@ def stripped_sys_argv(*strip_args)
  
      return [x for i, x in enumerate(args) if not strip(args, i)]
  
 +class ConstantMapping(Mapping):
 +    """
 +    An immutable mapping returning the provided value for every single key.
 +
 +    Useful for default value to methods
 +    """
 +    __slots__ = ['_value']
 +    def __init__(self, val):
 +        self._value = val
 +
 +    def __len__(self):
 +        """
 +        defaultdict updates its length for each individually requested key, is
 +        that really useful?
 +        """
 +        return 0
 +
 +    def __iter__(self):
 +        """
 +        same as len, defaultdict udpates its iterable keyset with each key
 +        requested, is there a point for this?
 +        """
 +        return iter([])
 +
 +    def __getitem__(self, item):
 +        return self._value
 +
 +
+ def dumpstacks(sig, frame):
+     """ Signal handler: dump a stack trace for each existing thread."""
+     code = []
+     def extract_stack(stack):
+         for filename, lineno, name, line in traceback.extract_stack(stack):
+             yield 'File: "%s", line %d, in %s' % (filename, lineno, name)
+             if line:
+                 yield "  %s" % (line.strip(),)
+     # code from http://stackoverflow.com/questions/132058/getting-stack-trace-from-a-running-python-application#answer-2569696
+     # modified for python 2.5 compatibility
+     threads_info = dict([(th.ident, {'name': th.name, 'uid': getattr(th, 'uid', 'n/a')})
+                         for th in threading.enumerate()])
+     for threadId, stack in sys._current_frames().items():
+         thread_info = threads_info.get(threadId)
+         code.append("\n# Thread: %s (id:%s) (uid:%s)" %
+                     (thread_info and thread_info['name'] or 'n/a',
+                      threadId,
+                      thread_info and thread_info['uid'] or 'n/a'))
+         for line in extract_stack(stack):
+             code.append(line)
+     if openerp.evented:
+         # code from http://stackoverflow.com/questions/12510648/in-gevent-how-can-i-dump-stack-traces-of-all-running-greenlets
+         import gc
+         from greenlet import greenlet
+         for ob in gc.get_objects():
+             if not isinstance(ob, greenlet) or not ob:
+                 continue
+             code.append("\n# Greenlet: %r" % (ob,))
+             for line in extract_stack(ob.gr_frame):
+                 code.append(line)
+     _logger.info("\n".join(code))
++
  # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
Simple merge