[REVERT] revert commit fme@openerp.com-20130418171750-7oldgiewo1eewxk7: do not break stable API !!!
bzr revid: chs@openerp.com-20130423124151-h025b891xp77flg3
This commit is contained in:
parent
d50b081191
commit
e5736828f9
|
@ -1,9 +1,11 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
import ast
|
||||
import base64
|
||||
import csv
|
||||
import glob
|
||||
import itertools
|
||||
import logging
|
||||
import operator
|
||||
import datetime
|
||||
import hashlib
|
||||
|
@ -28,6 +30,7 @@ except ImportError:
|
|||
xlwt = None
|
||||
|
||||
import openerp
|
||||
import openerp.modules.registry
|
||||
from openerp.tools.translate import _
|
||||
|
||||
from .. import http
|
||||
|
@ -1436,8 +1439,7 @@ class Action(openerpweb.Controller):
|
|||
else:
|
||||
return False
|
||||
|
||||
|
||||
class Export(openerpweb.Controller):
|
||||
class Export(View):
|
||||
_cp_path = "/web/export"
|
||||
|
||||
@openerpweb.jsonrequest
|
||||
|
@ -1578,13 +1580,7 @@ class Export(openerpweb.Controller):
|
|||
(prefix + '/' + k, prefix_string + '/' + v)
|
||||
for k, v in self.fields_info(req, model, export_fields).iteritems())
|
||||
|
||||
|
||||
class ExportFormat(object):
|
||||
"""
|
||||
Superclass for export formats, should probably be an abc and have a way to
|
||||
generate _cp_path from fmt but it's a pain to deal with conflicts with
|
||||
ControllerType
|
||||
"""
|
||||
#noinspection PyPropertyDefinition
|
||||
@property
|
||||
def content_type(self):
|
||||
""" Provides the format's content type """
|
||||
|
@ -1625,14 +1621,14 @@ class ExportFormat(object):
|
|||
else:
|
||||
columns_headers = [val['label'].strip() for val in fields]
|
||||
|
||||
|
||||
return req.make_response(self.from_data(columns_headers, import_data),
|
||||
headers=[('Content-Disposition',
|
||||
content_disposition(self.filename(model), req)),
|
||||
('Content-Type', self.content_type)],
|
||||
cookies={'fileToken': int(token)})
|
||||
|
||||
|
||||
class CSVExport(ExportFormat, http.Controller):
|
||||
class CSVExport(Export):
|
||||
_cp_path = '/web/export/csv'
|
||||
fmt = {'tag': 'csv', 'label': 'CSV'}
|
||||
|
||||
|
@ -1667,8 +1663,7 @@ class CSVExport(ExportFormat, http.Controller):
|
|||
fp.close()
|
||||
return data
|
||||
|
||||
|
||||
class ExcelExport(ExportFormat, http.Controller):
|
||||
class ExcelExport(Export):
|
||||
_cp_path = '/web/export/xls'
|
||||
fmt = {
|
||||
'tag': 'xls',
|
||||
|
@ -1707,8 +1702,7 @@ class ExcelExport(ExportFormat, http.Controller):
|
|||
fp.close()
|
||||
return data
|
||||
|
||||
|
||||
class Reports(openerpweb.Controller):
|
||||
class Reports(View):
|
||||
_cp_path = "/web/report"
|
||||
POLLING_DELAY = 0.25
|
||||
TYPES_MAPPING = {
|
||||
|
|
|
@ -355,64 +355,18 @@ def httprequest(f):
|
|||
#----------------------------------------------------------
|
||||
addons_module = {}
|
||||
addons_manifest = {}
|
||||
controllers_class = {}
|
||||
controllers_class = []
|
||||
controllers_object = {}
|
||||
controllers_path = {}
|
||||
|
||||
class ControllerType(type):
|
||||
def __init__(cls, name, bases, attrs):
|
||||
super(ControllerType, cls).__init__(name, bases, attrs)
|
||||
# Only for root "Controller"
|
||||
if bases == (object,):
|
||||
assert name == 'Controller'
|
||||
return
|
||||
|
||||
path = attrs.get('_cp_path')
|
||||
if Controller in bases:
|
||||
assert path, "Controller subclass %s missing a _cp_path" % name
|
||||
else:
|
||||
parent_paths = set(base._cp_path for base in bases
|
||||
if issubclass(base, Controller))
|
||||
assert len(parent_paths) == 1,\
|
||||
"%s inheriting from multiple controllers is not supported" % (
|
||||
name)
|
||||
[parent_path] = parent_paths
|
||||
[parent] = [
|
||||
controller for controller in controllers_class.itervalues()
|
||||
if controller._cp_path == parent_path]
|
||||
|
||||
# inherit from a Controller subclass
|
||||
if path:
|
||||
# if extending in place with same URL, ignore URL
|
||||
if parent_path == path:
|
||||
_logger.warn(
|
||||
"Controller %s extending %s in-place should not "
|
||||
"explicitly specify URL", name, parent)
|
||||
return
|
||||
_logger.warn("Re-exposing %s at %s.\n"
|
||||
"\tThis usage is unsupported.",
|
||||
parent.__name__,
|
||||
attrs['_cp_path'])
|
||||
|
||||
if path:
|
||||
assert path not in controllers_class,\
|
||||
"Trying to expose %s at the same URL as %s" % (
|
||||
name, controllers_class[path])
|
||||
controllers_class[path] = cls
|
||||
|
||||
controllers_class.append(("%s.%s" % (cls.__module__, cls.__name__), cls))
|
||||
|
||||
class Controller(object):
|
||||
__metaclass__ = ControllerType
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
subclasses = [c for c in cls.__subclasses__()
|
||||
if c._cp_path == cls._cp_path]
|
||||
if subclasses:
|
||||
name = "%s (+%s)" % (
|
||||
cls.__name__,
|
||||
'+'.join(sub.__name__ for sub in subclasses))
|
||||
cls = type(name, tuple(reversed(subclasses)), {})
|
||||
return object.__new__(cls)
|
||||
|
||||
#----------------------------------------------------------
|
||||
# Session context manager
|
||||
#----------------------------------------------------------
|
||||
|
@ -612,8 +566,12 @@ class Root(object):
|
|||
addons_manifest[module] = manifest
|
||||
self.statics['/%s/static' % module] = path_static
|
||||
|
||||
for c in controllers_class.itervalues():
|
||||
controllers_path[c._cp_path] = c()
|
||||
for k, v in controllers_class:
|
||||
if k not in controllers_object:
|
||||
o = v()
|
||||
controllers_object[k] = o
|
||||
if hasattr(o, '_cp_path'):
|
||||
controllers_path[o._cp_path] = o
|
||||
|
||||
app = werkzeug.wsgi.SharedDataMiddleware(self.dispatch, self.statics)
|
||||
self.dispatch = DisableCacheMiddleware(app)
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from . import test_dataset, test_menu, test_serving_base, test_js, test_dispatch
|
||||
from . import test_dataset, test_menu, test_serving_base, test_js
|
||||
|
||||
fast_suite = []
|
||||
checks = [
|
||||
test_dataset,
|
||||
test_menu,
|
||||
test_serving_base,
|
||||
test_dispatch,
|
||||
]
|
||||
|
|
|
@ -1,373 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import contextlib
|
||||
import json
|
||||
import logging
|
||||
import logging.handlers
|
||||
import types
|
||||
import unittest2
|
||||
|
||||
from .. import http
|
||||
import werkzeug.test
|
||||
|
||||
|
||||
def setUpModule():
|
||||
"""
|
||||
Force load_addons once to import all the crap we don't care for as this
|
||||
thing is full of side-effects
|
||||
"""
|
||||
http.Root().load_addons()
|
||||
|
||||
class DispatchCleanup(unittest2.TestCase):
|
||||
"""
|
||||
Cleans up controllers registries in the web client so it's possible to
|
||||
test controllers registration and dispatching in isolation.
|
||||
"""
|
||||
def setUp(self):
|
||||
self.classes = http.controllers_class
|
||||
self.paths = http.controllers_path
|
||||
|
||||
http.controllers_class = {}
|
||||
http.controllers_path = {}
|
||||
|
||||
def tearDown(self):
|
||||
http.controllers_path = self.paths
|
||||
http.controllers_class = self.classes
|
||||
|
||||
|
||||
def jsonrpc_body(params=None):
|
||||
"""
|
||||
Builds and dumps the body of a JSONRPC request with params ``params``
|
||||
"""
|
||||
return json.dumps({
|
||||
'jsonrpc': '2.0',
|
||||
'method': 'call',
|
||||
'id': None,
|
||||
'params': params or {},
|
||||
})
|
||||
|
||||
|
||||
def jsonrpc_response(result=None):
|
||||
"""
|
||||
Builds a JSONRPC response (as a Python dict) with result ``result``
|
||||
"""
|
||||
return {
|
||||
u'jsonrpc': u'2.0',
|
||||
u'id': None,
|
||||
u'result': result,
|
||||
}
|
||||
|
||||
|
||||
class TestHandler(logging.handlers.BufferingHandler):
|
||||
def __init__(self):
|
||||
logging.handlers.BufferingHandler.__init__(self, 0)
|
||||
|
||||
def shouldFlush(self, record):
|
||||
return False
|
||||
|
||||
@contextlib.contextmanager
|
||||
def capture_logging(logger, level=logging.DEBUG):
|
||||
logger = logging.getLogger(logger)
|
||||
old_level = logger.level
|
||||
old_handlers = logger.handlers
|
||||
old_propagate = logger.propagate
|
||||
|
||||
test_handler = TestHandler()
|
||||
logger.handlers = [test_handler]
|
||||
logger.setLevel(level)
|
||||
logger.propagate = False
|
||||
|
||||
try:
|
||||
yield test_handler
|
||||
finally:
|
||||
logger.propagate = old_propagate
|
||||
logger.setLevel(old_level)
|
||||
logger.handlers = old_handlers
|
||||
|
||||
|
||||
class TestDispatching(DispatchCleanup):
|
||||
def setUp(self):
|
||||
super(TestDispatching, self).setUp()
|
||||
self.app = http.Root()
|
||||
self.client = werkzeug.test.Client(self.app)
|
||||
|
||||
def test_not_exposed(self):
|
||||
class CatController(http.Controller):
|
||||
_cp_path = '/cat'
|
||||
|
||||
def index(self):
|
||||
return 'Blessid iz da feline'
|
||||
|
||||
self.app.load_addons()
|
||||
|
||||
body, status, headers = self.client.get('/cat')
|
||||
self.assertEqual('404 NOT FOUND', status)
|
||||
|
||||
def test_basic_http(self):
|
||||
class CatController(http.Controller):
|
||||
_cp_path = '/cat'
|
||||
|
||||
@http.httprequest
|
||||
def index(self, req):
|
||||
return 'no walk in counsil of wickid,'
|
||||
|
||||
self.app.load_addons()
|
||||
|
||||
body, status, headers = self.client.get('/cat')
|
||||
self.assertEqual('200 OK', status)
|
||||
self.assertEqual('no walk in counsil of wickid,', ''.join(body))
|
||||
|
||||
def test_basic_jsonrpc(self):
|
||||
class CatController(http.Controller):
|
||||
_cp_path = '/cat'
|
||||
|
||||
@http.jsonrequest
|
||||
def index(self, req):
|
||||
return 'no place paws in path of da sinnerz,'
|
||||
self.app.load_addons()
|
||||
|
||||
body, status, headers = self.client.post('/cat', data=jsonrpc_body())
|
||||
|
||||
self.assertEqual('200 OK', status)
|
||||
self.assertEqual(
|
||||
jsonrpc_response('no place paws in path of da sinnerz,'),
|
||||
json.loads(''.join(body)))
|
||||
|
||||
|
||||
class TestSubclassing(DispatchCleanup):
|
||||
def setUp(self):
|
||||
super(TestSubclassing, self).setUp()
|
||||
self.app = http.Root()
|
||||
self.client = werkzeug.test.Client(self.app)
|
||||
|
||||
def test_add_method(self):
|
||||
class CatController(http.Controller):
|
||||
_cp_path = '/cat'
|
||||
|
||||
@http.httprequest
|
||||
def index(self, req):
|
||||
return 'no sit and purr with da mockerz.'
|
||||
|
||||
class CeilingController(CatController):
|
||||
@http.httprequest
|
||||
def lol(self, req):
|
||||
return 'But der delightz in lawz of Ceiling Cat,'
|
||||
|
||||
self.app.load_addons()
|
||||
|
||||
body, status, headers = self.client.get('/cat')
|
||||
self.assertEqual('200 OK', status)
|
||||
self.assertEqual('no sit and purr with da mockerz.', ''.join(body))
|
||||
body, status, headers = self.client.get('/cat/lol')
|
||||
self.assertEqual('200 OK', status)
|
||||
self.assertEqual('But der delightz in lawz of Ceiling Cat,',
|
||||
''.join(body))
|
||||
|
||||
def test_override_method(self):
|
||||
class CatController(http.Controller):
|
||||
_cp_path = '/cat'
|
||||
|
||||
@http.httprequest
|
||||
def index(self, req):
|
||||
return 'an ponderz'
|
||||
|
||||
class CeilingController(CatController):
|
||||
@http.httprequest
|
||||
def index(self, req):
|
||||
return '%s much.' % super(CeilingController, self).index(req)
|
||||
|
||||
self.app.load_addons()
|
||||
|
||||
body, status, headers = self.client.get('/cat')
|
||||
self.assertEqual('200 OK', status)
|
||||
self.assertEqual('an ponderz much.', ''.join(body))
|
||||
|
||||
def test_make_invisible(self):
|
||||
class CatController(http.Controller):
|
||||
_cp_path = '/cat'
|
||||
|
||||
@http.httprequest
|
||||
def index(self, req):
|
||||
return 'Tehy liek treez bai teh waterz,'
|
||||
|
||||
class CeilingController(CatController):
|
||||
def index(self, req):
|
||||
return super(CeilingController, self).index(req)
|
||||
|
||||
self.app.load_addons()
|
||||
|
||||
body, status, headers = self.client.get('/cat')
|
||||
self.assertEqual('404 NOT FOUND', status)
|
||||
|
||||
def test_make_json_invisible(self):
|
||||
class CatController(http.Controller):
|
||||
_cp_path = '/cat'
|
||||
|
||||
@http.jsonrequest
|
||||
def index(self, req):
|
||||
return 'Tehy liek treez bai teh waterz,'
|
||||
|
||||
class CeilingController(CatController):
|
||||
def index(self, req):
|
||||
return super(CeilingController, self).index(req)
|
||||
|
||||
self.app.load_addons()
|
||||
|
||||
body, status, headers = self.client.post('/cat')
|
||||
self.assertEqual('404 NOT FOUND', status)
|
||||
|
||||
def test_extends(self):
|
||||
"""
|
||||
When subclassing an existing Controller new classes are "merged" into
|
||||
the base one
|
||||
"""
|
||||
class A(http.Controller):
|
||||
_cp_path = '/foo'
|
||||
@http.httprequest
|
||||
def index(self, req):
|
||||
return '1'
|
||||
|
||||
class B(A):
|
||||
@http.httprequest
|
||||
def index(self, req):
|
||||
return "%s 2" % super(B, self).index(req)
|
||||
|
||||
class C(A):
|
||||
@http.httprequest
|
||||
def index(self, req):
|
||||
return "%s 3" % super(C, self).index(req)
|
||||
|
||||
self.app.load_addons()
|
||||
|
||||
body, status, headers = self.client.get('/foo')
|
||||
self.assertEqual('200 OK', status)
|
||||
self.assertEqual('1 2 3', ''.join(body))
|
||||
|
||||
def test_extends_same_path(self):
|
||||
"""
|
||||
When subclassing an existing Controller and specifying the same
|
||||
_cp_path as the parent, ???
|
||||
"""
|
||||
class A(http.Controller):
|
||||
_cp_path = '/foo'
|
||||
@http.httprequest
|
||||
def index(self, req):
|
||||
return '1'
|
||||
|
||||
class B(A):
|
||||
_cp_path = '/foo'
|
||||
@http.httprequest
|
||||
def index(self, req):
|
||||
return '2'
|
||||
|
||||
self.app.load_addons()
|
||||
|
||||
body, status, headers = self.client.get('/foo')
|
||||
self.assertEqual('200 OK', status)
|
||||
self.assertEqual('2', ''.join(body))
|
||||
|
||||
def test_re_expose(self):
|
||||
"""
|
||||
An existing Controller should not be extended with a new cp_path
|
||||
(re-exposing somewhere else)
|
||||
"""
|
||||
class CatController(http.Controller):
|
||||
_cp_path = '/cat'
|
||||
|
||||
@http.httprequest
|
||||
def index(self, req):
|
||||
return '[%s]' % self.speak()
|
||||
|
||||
def speak(self):
|
||||
return 'Yu ordered cheezburgerz,'
|
||||
|
||||
with capture_logging('openerp.addons.web.http') as handler:
|
||||
class DogController(CatController):
|
||||
_cp_path = '/dog'
|
||||
|
||||
def speak(self):
|
||||
return 'Woof woof woof woof'
|
||||
|
||||
[record] = handler.buffer
|
||||
self.assertEqual(logging.WARN, record.levelno)
|
||||
self.assertEqual("Re-exposing CatController at /dog.\n"
|
||||
"\tThis usage is unsupported.",
|
||||
record.getMessage())
|
||||
|
||||
def test_fail_redefine(self):
|
||||
"""
|
||||
An existing Controller can't be overwritten by a new one on the same
|
||||
path (? or should this generate a warning and still work as if it was
|
||||
an extend?)
|
||||
"""
|
||||
class FooController(http.Controller):
|
||||
_cp_path = '/foo'
|
||||
|
||||
with self.assertRaises(AssertionError):
|
||||
class BarController(http.Controller):
|
||||
_cp_path = '/foo'
|
||||
|
||||
def test_fail_no_path(self):
|
||||
"""
|
||||
A Controller must have a path (and thus be exposed)
|
||||
"""
|
||||
with self.assertRaises(AssertionError):
|
||||
class FooController(http.Controller):
|
||||
pass
|
||||
|
||||
def test_mixin(self):
|
||||
"""
|
||||
Can mix "normal" python classes into a controller directly
|
||||
"""
|
||||
class Mixin(object):
|
||||
@http.httprequest
|
||||
def index(self, req):
|
||||
return 'ok'
|
||||
|
||||
class FooController(http.Controller, Mixin):
|
||||
_cp_path = '/foo'
|
||||
|
||||
class BarContoller(Mixin, http.Controller):
|
||||
_cp_path = '/bar'
|
||||
|
||||
self.app.load_addons()
|
||||
|
||||
body, status, headers = self.client.get('/foo')
|
||||
self.assertEqual('200 OK', status)
|
||||
self.assertEqual('ok', ''.join(body))
|
||||
|
||||
body, status, headers = self.client.get('/bar')
|
||||
self.assertEqual('200 OK', status)
|
||||
self.assertEqual('ok', ''.join(body))
|
||||
|
||||
def test_mixin_extend(self):
|
||||
"""
|
||||
Can mix "normal" python class into a controller by extension
|
||||
"""
|
||||
class FooController(http.Controller):
|
||||
_cp_path = '/foo'
|
||||
|
||||
class M1(object):
|
||||
@http.httprequest
|
||||
def m1(self, req):
|
||||
return 'ok 1'
|
||||
|
||||
class M2(object):
|
||||
@http.httprequest
|
||||
def m2(self, req):
|
||||
return 'ok 2'
|
||||
|
||||
class AddM1(FooController, M1):
|
||||
pass
|
||||
|
||||
class AddM2(M2, FooController):
|
||||
pass
|
||||
|
||||
self.app.load_addons()
|
||||
|
||||
body, status, headers = self.client.get('/foo/m1')
|
||||
self.assertEqual('200 OK', status)
|
||||
self.assertEqual('ok 1', ''.join(body))
|
||||
|
||||
body, status, headers = self.client.get('/foo/m2')
|
||||
self.assertEqual('200 OK', status)
|
||||
self.assertEqual('ok 2', ''.join(body))
|
|
@ -1,10 +1,9 @@
|
|||
from openerp.addons.web.http import Controller, jsonrequest
|
||||
import openerp
|
||||
|
||||
|
||||
class Diagram(Controller):
|
||||
class DiagramView(openerp.addons.web.controllers.main.View):
|
||||
_cp_path = "/web_diagram/diagram"
|
||||
|
||||
@jsonrequest
|
||||
@openerp.addons.web.http.jsonrequest
|
||||
def get_diagram_info(self, req, id, model, node, connector,
|
||||
src_node, des_node, label, **kw):
|
||||
|
||||
|
|
Loading…
Reference in New Issue