From e5736828f9eaa9de22360b52f6c31267cc2ee892 Mon Sep 17 00:00:00 2001 From: Christophe Simonis Date: Tue, 23 Apr 2013 14:41:51 +0200 Subject: [PATCH] [REVERT] revert commit fme@openerp.com-20130418171750-7oldgiewo1eewxk7: do not break stable API !!! bzr revid: chs@openerp.com-20130423124151-h025b891xp77flg3 --- addons/web/controllers/main.py | 24 +- addons/web/http.py | 60 +--- addons/web/tests/__init__.py | 3 +- addons/web/tests/test_dispatch.py | 373 ------------------------- addons/web_diagram/controllers/main.py | 7 +- 5 files changed, 22 insertions(+), 445 deletions(-) delete mode 100644 addons/web/tests/test_dispatch.py diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py index 47498752c24..01126694059 100644 --- a/addons/web/controllers/main.py +++ b/addons/web/controllers/main.py @@ -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 = { diff --git a/addons/web/http.py b/addons/web/http.py index 7f99d27bd04..d12cdfb13a1 100644 --- a/addons/web/http.py +++ b/addons/web/http.py @@ -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) diff --git a/addons/web/tests/__init__.py b/addons/web/tests/__init__.py index 2734bd45ddb..d7abb8e5a8c 100644 --- a/addons/web/tests/__init__.py +++ b/addons/web/tests/__init__.py @@ -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, ] diff --git a/addons/web/tests/test_dispatch.py b/addons/web/tests/test_dispatch.py deleted file mode 100644 index b532cebac44..00000000000 --- a/addons/web/tests/test_dispatch.py +++ /dev/null @@ -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)) diff --git a/addons/web_diagram/controllers/main.py b/addons/web_diagram/controllers/main.py index 2efaab7183d..c598a7e4652 100644 --- a/addons/web_diagram/controllers/main.py +++ b/addons/web_diagram/controllers/main.py @@ -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):