[IMP] Werkzeug everything

bzr revid: xmo@openerp.com-20110902145204-p1vwgwoy9no4tqx6
This commit is contained in:
Xavier Morel 2011-09-02 16:52:04 +02:00
commit a022e94b94
14 changed files with 349 additions and 540 deletions

View File

@ -1,5 +1,6 @@
#!/usr/bin/python
import urllib
from __future__ import with_statement
import functools
import logging
import os
@ -8,23 +9,22 @@ import traceback
import uuid
import xmlrpclib
import cherrypy
import cherrypy.lib.static
import simplejson
import werkzeug.datastructures
import werkzeug.exceptions
import werkzeug.urls
import werkzeug.utils
import werkzeug.wrappers
import werkzeug.wsgi
import ast
import nonliterals
import http
# import backendlocal as backend
import backendrpc as backend
#-----------------------------------------------------------
# Globals
#-----------------------------------------------------------
import __main__
path_root = __main__.path_root
path_addons = __main__.path_addons
cherrypy_root = None
__all__ = ['Root', 'jsonrequest', 'httprequest', 'Controller',
'WebRequest', 'JsonRequest', 'HttpRequest']
#-----------------------------------------------------------
# Globals (wont move into a pool)
@ -40,27 +40,73 @@ controllers_path = {}
#----------------------------------------------------------
# OpenERP Web RequestHandler
#----------------------------------------------------------
class CherryPyRequest(object):
""" CherryPy request handling
class WebRequest(object):
""" Parent class for all OpenERP Web request types, mostly deals with
initialization and setup of the request object (the dispatching itself has
to be handled by the subclasses)
:param request: a wrapped werkzeug Request object
:type request: :class:`werkzeug.wrappers.BaseRequest`
:param config: configuration object
.. attribute:: applicationsession
an application-wide :class:`~collections.Mapping`
.. attribute:: httprequest
the original :class:`werkzeug.wrappers.Request` object provided to the
request
.. attribute:: httpsession
a :class:`~collections.Mapping` holding the HTTP session data for the
current http session
.. attribute:: config
config parameter provided to the request object
.. attribute:: params
:class:`~collections.Mapping` of request parameters, not generally
useful as they're provided directly to the handler method as keyword
arguments
.. attribute:: session_id
opaque identifier for the :class:`backend.OpenERPSession` instance of
the current request
.. attribute:: session
:class:`~backend.OpenERPSession` instance for the current request
.. attribute:: context
:class:`~collections.Mapping` of context values for the current request
.. attribute:: debug
``bool``, indicates whether the debug mode is active on the client
"""
def init(self,params):
self.params = params
# Move cherrypy thread local objects to attributes
def __init__(self, request, config):
self.applicationsession = applicationsession
self.httprequest = cherrypy.request
self.httpresponse = cherrypy.response
self.httpsession = cherrypy.session
self.httpsession_id = "cookieid"
self.httprequest = request
self.httpresponse = None
self.httpsession = request.session
self.config = config
def init(self, params):
self.params = dict(params)
# OpenERP session setup
self.session_id = self.params.pop("session_id", None) or uuid.uuid4().hex
host = cherrypy.config['openerp.server.host']
port = cherrypy.config['openerp.server.port']
self.session = self.httpsession.setdefault(self.session_id, backend.OpenERPSession(host, port))
# Request attributes
self.session = self.httpsession.setdefault(
self.session_id, backend.OpenERPSession(
self.config.server_host, self.config.server_port))
self.context = self.params.pop('context', None)
self.debug = self.params.pop('debug',False) != False
self.debug = self.params.pop('debug', False) != False
class JsonRequest(CherryPyRequest):
class JsonRequest(WebRequest):
""" JSON-RPC2 over HTTP.
Sucessful request::
@ -138,8 +184,8 @@ class JsonRequest(CherryPyRequest):
}
}
except Exception:
cherrypy.log("An error occured while handling a json request",
severity=logging.ERROR, traceback=True)
logging.getLogger('openerp.JSONRequest.dispatch').exception\
("An error occured while handling a json request")
error = {
'code': 300,
'message': "OpenERP WebClient Error",
@ -156,47 +202,89 @@ class JsonRequest(CherryPyRequest):
print
content = simplejson.dumps(response, cls=nonliterals.NonLiteralEncoder)
cherrypy.response.headers['Content-Type'] = 'application/json'
cherrypy.response.headers['Content-Length'] = len(content)
return content
return werkzeug.wrappers.Response(
content, headers=[('Content-Type', 'application/json'),
('Content-Length', len(content))])
def jsonrequest(f):
@cherrypy.expose
""" Decorator marking the decorated method as being a handler for a
JSON-RPC request (the exact request path is specified via the
``$(Controller._cp_path)/$methodname`` combination.
If the method is called, it will be provided with a :class:`JsonRequest`
instance and all ``params`` sent during the JSON-RPC request, apart from
the ``session_id``, ``context`` and ``debug`` keys (which are stripped out
beforehand)
"""
@functools.wraps(f)
def json_handler(controller):
return JsonRequest().dispatch(controller, f, requestf=cherrypy.request.body)
def json_handler(controller, request, config):
return JsonRequest(request, config).dispatch(
controller, f, requestf=request.stream)
json_handler.exposed = True
return json_handler
class HttpRequest(CherryPyRequest):
class HttpRequest(WebRequest):
""" Regular GET/POST request
"""
def dispatch(self, controller, method, **kw):
self.init(kw)
def dispatch(self, controller, method):
self.init(dict(self.httprequest.args, **self.httprequest.form))
akw = {}
for key in kw.keys():
if isinstance(kw[key], basestring) and len(kw[key]) < 1024:
akw[key] = kw[key]
for key, value in self.httprequest.args.iteritems():
if isinstance(value, basestring) and len(value) < 1024:
akw[key] = value
else:
akw[key] = type(kw[key])
akw[key] = type(value)
if self.debug or 1:
print "%s --> %s.%s %r" % (self.httprequest.method, controller.__class__.__name__, method.__name__, akw)
r = method(controller, self, **kw)
r = method(controller, self, **self.params)
if self.debug or 1:
print "<--", 'size:', len(r)
if isinstance(r, werkzeug.wrappers.BaseResponse):
print '<--', r
else:
print "<--", 'size:', len(r)
print
return r
def httprequest(f):
# check cleaner wrapping:
# functools.wraps(f)(lambda x: JsonRequest().dispatch(x, f))
def http_handler(controller,*l, **kw):
return HttpRequest().dispatch(controller, f, **kw)
http_handler.exposed = 1
return http_handler
def make_response(self, data, headers=None, cookies=None):
""" Helper for non-HTML responses, or HTML responses with custom
response headers or cookies.
#-----------------------------------------------------------
# Cherrypy stuff
#-----------------------------------------------------------
While handlers can just return the HTML markup of a page they want to
send as a string if non-HTML data is returned they need to create a
complete response object, or the returned data will not be correctly
interpreted by the clients.
:param basestring data: response body
:param headers: HTTP headers to set on the response
:type headers: ``[(name, value)]``
:param collections.Mapping cookies: cookies to set on the client
"""
response = werkzeug.wrappers.Response(data, headers=headers)
if cookies:
for k, v in cookies.iteritems():
response.set_cookie(k, v)
return response
def not_found(self, description=None):
""" Helper for 404 response, return its result from the method
"""
return werkzeug.exceptions.NotFound(description)
def httprequest(f):
""" Decorator marking the decorated method as being a handler for a
normal HTTP request (the exact request path is specified via the
``$(Controller._cp_path)/$methodname`` combination.
If the method is called, it will be provided with a :class:`HttpRequest`
instance and all ``params`` sent during the request (``GET`` and ``POST``
merged in the same dictionary), apart from the ``session_id``, ``context``
and ``debug`` keys (which are stripped out beforehand)
"""
@functools.wraps(f)
def http_handler(controller, request, config):
return HttpRequest(request, config).dispatch(controller, f)
http_handler.exposed = True
return http_handler
class ControllerType(type):
def __init__(cls, name, bases, attrs):
@ -207,41 +295,124 @@ class Controller(object):
__metaclass__ = ControllerType
class Root(object):
def __init__(self):
"""Root WSGI application for the OpenERP Web Client.
:param options: mandatory initialization options object, must provide
the following attributes:
``server_host`` (``str``)
hostname of the OpenERP server to dispatch RPC to
``server_port`` (``int``)
RPC port of the OpenERP server
``serve_static`` (``bool | None``)
whether this application should serve the various
addons's static files
``storage_path`` (``str``)
filesystem path where HTTP session data will be stored
``dbfilter`` (``str``)
only used in case the list of databases is requested
by the server, will be filtered by this pattern
"""
def __init__(self, options):
self.root = werkzeug.urls.Href('/base/webclient/home')
self.config = options
self.session_cookie = 'sessionid'
self.addons = {}
self._load_addons()
static_dirs = self._load_addons()
if options.serve_static:
self.dispatch = werkzeug.wsgi.SharedDataMiddleware(
self.dispatch, static_dirs)
if options.session_storage:
if not os.path.exists(options.session_storage):
os.mkdir(options.session_storage, 0700)
self.session_storage = options.session_storage
def __call__(self, environ, start_response):
""" Handle a WSGI request
"""
return self.dispatch(environ, start_response)
def dispatch(self, environ, start_response):
"""
Performs the actual WSGI dispatching for the application, may be
wrapped during the initialization of the object.
Call the object directly.
"""
request = werkzeug.wrappers.Request(environ)
request.parameter_storage_class = werkzeug.datastructures.ImmutableDict
if request.path == '/':
return werkzeug.utils.redirect(
self.root(request.args), 301)(
environ, start_response)
elif request.path == '/mobile':
return werkzeug.utils.redirect(
'/web_mobile/static/src/web_mobile.html', 301)(
environ, start_response)
handler = self.find_handler(*(request.path.split('/')[1:]))
if not handler:
response = werkzeug.exceptions.NotFound()
else:
with http.session(request, self.session_storage, self.session_cookie) as session:
result = handler(
request, self.config)
if isinstance(result, basestring):
response = werkzeug.wrappers.Response(
result, headers=[('Content-Type', 'text/html; charset=utf-8'),
('Content-Length', len(result))])
else:
response = result
response.set_cookie(self.session_cookie, session.sid)
return response(environ, start_response)
def _load_addons(self):
if path_addons not in sys.path:
sys.path.insert(0, path_addons)
for i in os.listdir(path_addons):
if i not in addons_module:
manifest_path = os.path.join(path_addons, i, '__openerp__.py')
"""
Loads all addons at the specified addons path, returns a mapping of
static URLs to the corresponding directories
"""
statics = {}
addons_path = self.config.addons_path
if addons_path not in sys.path:
sys.path.insert(0, addons_path)
for module in os.listdir(addons_path):
if module not in addons_module:
manifest_path = os.path.join(addons_path, module, '__openerp__.py')
if os.path.isfile(manifest_path):
manifest = eval(open(manifest_path).read())
print "Loading", i
m = __import__(i)
addons_module[i] = m
addons_manifest[i] = manifest
manifest = ast.literal_eval(open(manifest_path).read())
print "Loading", module
m = __import__(module)
addons_module[module] = m
addons_manifest[module] = manifest
statics['/%s/static' % module] = \
os.path.join(addons_path, module, 'static')
for k, v in controllers_class.items():
if k not in controllers_object:
o = v()
controllers_object[k] = o
if hasattr(o, '_cp_path'):
controllers_path[o._cp_path] = o
return statics
def default(self, *l, **kw):
print "default",l,kw
# handle static files
if len(l) > 2 and l[1] == 'static':
# sanitize path
p = os.path.normpath(os.path.join(*l))
p2 = os.path.join(path_addons, p)
print "p",p
print "p2",p2
def find_handler(self, *l):
"""
Tries to discover the controller handling the request for the path
specified by the provided parameters
return cherrypy.lib.static.serve_file(p2)
elif len(l) > 1:
:param l: path sections to a controller or controller method
:returns: a callable matching the path sections, or ``None``
:rtype: ``Controller | None``
"""
if len(l) > 1:
for i in range(len(l), 1, -1):
ps = "/" + "/".join(l[0:i])
if ps in controllers_path:
@ -249,19 +420,7 @@ class Root(object):
rest = l[i:] or ['index']
meth = rest[0]
m = getattr(c, meth)
if getattr(m, 'exposed', 0):
print "Calling", ps, c, meth, m
return m(**kw)
raise cherrypy.NotFound('/' + '/'.join(l))
elif l and l[0] == 'mobile':
#for the mobile web client we are supposed to use a different url to just add '/mobile'
raise cherrypy.HTTPRedirect('/web_mobile/static/src/web_mobile.html', 301)
else:
if kw:
qs = '?' + urllib.urlencode(kw)
else:
qs = ''
raise cherrypy.HTTPRedirect('/base/webclient/home' + qs, 301)
default.exposed = True
#
if getattr(m, 'exposed', False):
print "Dispatching to", ps, c, meth, m
return m
return None

View File

@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
import contextlib
import werkzeug.contrib.sessions
STORES = {}
@contextlib.contextmanager
def session(request, storage_path, session_cookie='sessionid'):
session_store = STORES.get(storage_path)
if not session_store:
session_store = werkzeug.contrib.sessions.FilesystemSessionStore(
storage_path)
STORES[storage_path] = session_store
sid = request.cookies.get(session_cookie)
if sid:
request.session = session_store.get(sid)
else:
request.session = session_store.new()
yield request.session
session_store.save(request.session)

View File

@ -1 +0,0 @@
# -*- coding: utf-8 -*-

View File

@ -1,81 +0,0 @@
# -*- coding: utf-8 -*-
import cherrypy
import mock
import unittest2
import openerpweb.openerpweb
class OpenERPModelTest(unittest2.TestCase):
def test_rpc_call(self):
session = mock.Mock(['execute'])
Model = openerpweb.openerpweb.OpenERPModel(
session, 'a.b')
Model.search([('field', 'op', 'value')], {'key': 'value'})
session.execute.assert_called_once_with(
'a.b', 'search', [('field', 'op', 'value')], {'key': 'value'})
session.execute.reset_mock()
Model.read([42])
session.execute.assert_called_once_with(
'a.b', 'read', [42])
class FakeController(object):
pass
class DispatcherTest(unittest2.TestCase):
def setUp(self):
controller = FakeController()
self.mock_method = mock.Mock()
controller.method = self.mock_method
self.mock_method.exposed = True
self.mock_index = mock.Mock()
controller.index = self.mock_index
self.mock_index.exposed = True
self.patcher = mock.patch.dict(
openerpweb.openerpweb.controllers_path,
{'/some/controller/path': controller})
self.patcher.start()
controller2 = FakeController()
controller2.index = self.mock_index
self.patcher2 = mock.patch.dict(
openerpweb.openerpweb.controllers_path,
{'/some/other/controller': FakeController(),
'/some/other/controller/2': controller2})
self.patcher2.start()
def tearDown(self):
self.patcher2.stop()
self.patcher.stop()
def test_default_redirect(self):
self.assertRaises(
cherrypy.HTTPRedirect,
openerpweb.openerpweb.Root().default)
def test_serve_static_missing(self):
self.assertRaises(
cherrypy.NotFound,
openerpweb.openerpweb.Root().default,
'does-not-exist', 'static', 'bar')
def test_serve_controller_missing(self):
self.assertRaises(
cherrypy.NotFound,
openerpweb.openerpweb.Root().default,
'controller', 'does', 'not', 'exist')
def test_find_controller_method(self):
openerpweb.openerpweb.Root().default(
'some', 'controller', 'path', 'method')
self.mock_method.assert_called_once_with()
def test_find_controller_index(self):
openerpweb.openerpweb.Root().default(
'some', 'controller', 'path')
self.mock_index.assert_called_once_with()
def test_nested_paths(self):
openerpweb.openerpweb.Root().default(
'some', 'other', 'controller', '2')
self.mock_index.assert_called_once_with()

View File

@ -1,145 +0,0 @@
# -*- coding: utf-8 -*-
import mock
import simplejson
import unittest2
from openerpweb.nonliterals import Domain, Context
import openerpweb.nonliterals
import openerpweb.openerpweb
class NonLiteralDomainTest(unittest2.TestCase):
def setUp(self):
self.session = mock.Mock(spec=openerpweb.openerpweb.OpenERPSession)
self.session.domains_store = {}
def test_store_domain(self):
d = Domain(self.session, "some arbitrary string")
self.assertEqual(
self.session.domains_store[d.key],
"some arbitrary string")
def test_get_domain_back(self):
d = Domain(self.session, "some arbitrary string")
self.assertEqual(
d.get_domain_string(),
"some arbitrary string")
def test_retrieve_second_domain(self):
""" A different domain should be able to retrieve the nonliteral set
previously
"""
key = Domain(self.session, "some arbitrary string").key
self.assertEqual(
Domain(self.session, key=key).get_domain_string(),
"some arbitrary string")
def test_key_and_string(self):
self.assertRaises(
ValueError, Domain, None, domain_string="a", key="b")
def test_eval(self):
self.session.evaluation_context.return_value = {'foo': 3}
result = Domain(self.session, "[('a', '=', foo)]").evaluate({'foo': 3})
self.assertEqual(
result, [('a', '=', 3)])
def test_own_values(self):
self.session.evaluation_context.return_value = {}
domain = Domain(self.session, "[('a', '=', self)]")
domain.own = {'self': 3}
result = domain.evaluate()
self.assertEqual(
result, [('a', '=', 3)])
class NonLiteralContextTest(unittest2.TestCase):
def setUp(self):
self.session = mock.Mock(spec=openerpweb.openerpweb.OpenERPSession)
self.session.contexts_store = {}
def test_store_domain(self):
c = Context(self.session, "some arbitrary string")
self.assertEqual(
self.session.contexts_store[c.key],
"some arbitrary string")
def test_get_domain_back(self):
c = Context(self.session, "some arbitrary string")
self.assertEqual(
c.get_context_string(),
"some arbitrary string")
def test_retrieve_second_domain(self):
""" A different domain should be able to retrieve the nonliteral set
previously
"""
key = Context(self.session, "some arbitrary string").key
self.assertEqual(
Context(self.session, key=key).get_context_string(),
"some arbitrary string")
def test_key_and_string(self):
self.assertRaises(
ValueError, Context, None, context_string="a", key="b")
def test_eval(self):
self.session.evaluation_context.return_value = {'foo': 3}
result = Context(self.session, "[('a', '=', foo)]")\
.evaluate({'foo': 3})
self.assertEqual(
result, [('a', '=', 3)])
def test_own_values(self):
self.session.evaluation_context.return_value = {}
context = Context(self.session, "{'a': self}")
context.own = {'self': 3}
result = context.evaluate()
self.assertEqual(
result, {'a': 3})
class NonLiteralJSON(unittest2.TestCase):
def setUp(self):
self.session = mock.Mock(spec=openerpweb.openerpweb.OpenERPSession)
self.session.domains_store = {}
self.session.contexts_store = {}
def test_encode_domain(self):
d = Domain(self.session, "some arbitrary string")
self.assertEqual(
simplejson.dumps(d, cls=openerpweb.nonliterals.NonLiteralEncoder),
simplejson.dumps({'__ref': 'domain', '__id': d.key}))
def test_decode_domain(self):
encoded = simplejson.dumps(
Domain(self.session, "some arbitrary string"),
cls=openerpweb.nonliterals.NonLiteralEncoder)
domain = simplejson.loads(
encoded, object_hook=openerpweb.nonliterals.non_literal_decoder)
domain.session = self.session
self.assertEqual(
domain.get_domain_string(),
"some arbitrary string"
)
def test_encode_context(self):
c = Context(self.session, "some arbitrary string")
self.assertEqual(
simplejson.dumps(c, cls=openerpweb.nonliterals.NonLiteralEncoder),
simplejson.dumps({'__ref': 'context', '__id': c.key}))
def test_decode_context(self):
encoded = simplejson.dumps(
Context(self.session, "some arbitrary string"),
cls=openerpweb.nonliterals.NonLiteralEncoder)
context = simplejson.loads(
encoded, object_hook=openerpweb.nonliterals.non_literal_decoder)
context.session = self.session
self.assertEqual(
context.get_context_string(),
"some arbitrary string"
)

View File

@ -1,110 +0,0 @@
# -*- coding: utf-8 -*-
import unittest2
from openerpweb.nonliterals import Domain, Context, CompoundDomain, CompoundContext
import openerpweb.openerpweb
class TestOpenERPSession(unittest2.TestCase):
def setUp(self):
self.module = object()
self.session = openerpweb.openerpweb.OpenERPSession()
self.session._uid = -1
self.session.context = {
'current_date': '1945-08-05',
'date': self.module,
'time': self.module,
'datetime': self.module,
'relativedelta': self.module
}
def test_base_eval_context(self):
self.assertEqual(type(self.session.base_eval_context), dict)
self.assertEqual(
self.session.base_eval_context,
{'uid': -1, 'current_date': '1945-08-05',
'date': self.module, 'datetime': self.module, 'time': self.module,
'relativedelta': self.module}
)
def test_evaluation_context_nocontext(self):
self.assertEqual(
type(self.session.evaluation_context()),
dict
)
self.assertEqual(
self.session.evaluation_context(),
self.session.base_eval_context
)
def test_evaluation_context(self):
ctx = self.session.evaluation_context({'foo': 3})
self.assertEqual(
type(ctx),
dict
)
self.assertIn('foo', ctx)
def test_eval_with_context(self):
self.assertEqual(
eval('current_date', self.session.evaluation_context()),
'1945-08-05')
self.assertEqual(
eval('foo + 3', self.session.evaluation_context({'foo': 4})),
7)
def test_eval_domain_typeerror(self):
self.assertRaises(
TypeError, self.session.eval_domain, "foo")
def test_eval_domain_list(self):
self.assertEqual(
self.session.eval_domain([]),
[])
def test_eval_nonliteral_domain(self):
d = Domain(self.session, "[('foo', 'is', 3)]")
self.assertEqual(
self.session.eval_domain(d),
[('foo', 'is', 3)])
def test_eval_nonliteral_domain_bykey(self):
key = Domain(
self.session, "[('foo', 'is', 3)]").key
d = Domain(None, key=key)
self.assertEqual(
self.session.eval_domain(d),
[('foo', 'is', 3)])
def test_eval_empty_domains(self):
self.assertEqual(
self.session.eval_domain(CompoundDomain()),
[])
def test_eval_literal_domains(self):
domains = [
[('a', 'is', 3)],
[('b', 'ilike', 'foo')],
['|',
('c', '=', False),
('c', 'in', ['a', 'b', 'c'])]
]
self.assertEqual(
self.session.eval_domain(CompoundDomain(*domains)),
[
('a', 'is', 3),
('b', 'ilike', 'foo'),
'|',
('c', '=', False),
('c', 'in', ['a', 'b', 'c'])
])
def test_eval_nonliteral_domains(self):
domains = [
Domain(self.session, "[('uid', '=', uid)]"),
Domain(self.session,
"['|', ('date', '<', current_date),"
" ('date', '>', current_date)]")]
self.assertEqual(
self.session.eval_domain(CompoundDomain(*domains)),
[('uid', '=', -1),
'|', ('date', '<', '1945-08-05'), ('date', '>', '1945-08-05')]
)

View File

@ -13,9 +13,7 @@ import time
from xml.etree import ElementTree
from cStringIO import StringIO
import cherrypy
import base.common as openerpweb
import base.common.dispatch as openerpweb
import base.common.ast
import base.common.nonliterals
openerpweb.ast = base.common.ast
@ -68,16 +66,16 @@ class Xml2Json:
# OpenERP Web base Controllers
#----------------------------------------------------------
def manifest_glob(addons, key):
def manifest_glob(addons_path, addons, key):
files = []
for addon in addons:
globlist = openerpweb.addons_manifest.get(addon, {}).get(key, [])
for pattern in globlist:
for path in glob.glob(os.path.join(openerpweb.path_addons, addon, pattern)):
files.append(path[len(openerpweb.path_addons):])
for path in glob.glob(os.path.join(addons_path, addon, pattern)):
files.append(path[len(addons_path):])
return files
def concat_files(file_list):
def concat_files(addons_path, file_list):
""" Concatenate file content
return (concat,timestamp)
concat: concatenation of file content
@ -86,13 +84,13 @@ def concat_files(file_list):
files_content = []
files_timestamp = 0
for i in file_list:
fname = os.path.join(openerpweb.path_addons, i[1:])
fname = os.path.join(addons_path, i[1:])
ftime = os.path.getmtime(fname)
if ftime > files_timestamp:
files_timestamp = ftime
files_content.append(open(fname).read())
files_concat = "".join(files_content)
return (files_concat,files_timestamp)
return files_concat,files_timestamp
home_template = textwrap.dedent("""<!DOCTYPE html>
<html style="height: 100%%">
@ -122,40 +120,38 @@ class WebClient(openerpweb.Controller):
@openerpweb.jsonrequest
def csslist(self, req, mods='base'):
return manifest_glob(mods.split(','), 'css')
return manifest_glob(req.config.addons_path, mods.split(','), 'css')
@openerpweb.jsonrequest
def jslist(self, req, mods='base'):
return manifest_glob(mods.split(','), 'js')
return manifest_glob(req.config.addons_path, mods.split(','), 'js')
@openerpweb.httprequest
def css(self, req, mods='base'):
req.httpresponse.headers['Content-Type'] = 'text/css'
files = manifest_glob(mods.split(','), 'css')
content,timestamp = concat_files(files)
files = manifest_glob(req.config.addons_path, mods.split(','), 'css')
content,timestamp = concat_files(req.config.addons_path, files)
# TODO request set the Date of last modif and Etag
return content
return req.make_response(content, [('Content-Type', 'text/css')])
@openerpweb.httprequest
def js(self, req, mods='base'):
req.httpresponse.headers['Content-Type'] = 'application/javascript'
files = manifest_glob(mods.split(','), 'js')
content,timestamp = concat_files(files)
files = manifest_glob(req.config.addons_path, mods.split(','), 'js')
content,timestamp = concat_files(req.config.addons_path, files)
# TODO request set the Date of last modif and Etag
return content
return req.make_response(content, [('Content-Type', 'application/javascript')])
@openerpweb.httprequest
def home(self, req, s_action=None, **kw):
# script tags
jslist = ['/base/webclient/js']
if req.debug:
jslist = manifest_glob(['base'], 'js')
jslist = manifest_glob(req.config.addons_path, ['base'], 'js')
js = "\n ".join(['<script type="text/javascript" src="%s"></script>'%i for i in jslist])
# css tags
csslist = ['/base/webclient/css']
if req.debug:
csslist = manifest_glob(['base'], 'css')
csslist = manifest_glob(req.config.addons_path, ['base'], 'css')
css = "\n ".join(['<link rel="stylesheet" href="%s">'%i for i in csslist])
r = home_template % {
'javascript': js,
@ -185,7 +181,7 @@ class WebClient(openerpweb.Controller):
transl = {"messages":[]}
transs[addon_name] = transl
for l in langs:
f_name = os.path.join(openerpweb.path_addons, addon_name, "po", l + ".po")
f_name = os.path.join(req.config.addons_path, addon_name, "po", l + ".po")
if not os.path.exists(f_name):
continue
try:
@ -208,7 +204,7 @@ class Database(openerpweb.Controller):
dbs = proxy.list()
h = req.httprequest.headers['Host'].split(':')[0]
d = h.split('.')[0]
r = cherrypy.config['openerp.dbfilter'].replace('%h', h).replace('%d', d)
r = req.config.dbfilter.replace('%h', h).replace('%d', d)
dbs = [i for i in dbs if re.match(r, i)]
return {"db_list": dbs}
@ -253,11 +249,11 @@ class Database(openerpweb.Controller):
try:
db_dump = base64.decodestring(
req.session.proxy("db").dump(backup_pwd, backup_db))
req.httpresponse.headers['Content-Type'] = "application/octet-stream; charset=binary"
req.httpresponse.headers['Content-Disposition'] = 'attachment; filename="' + backup_db + '.dump"'
req.httpresponse.cookie['fileToken'] = token
req.httpresponse.cookie['fileToken']['path'] = '/'
return db_dump
return req.make_response(db_dump,
[('Content-Type', 'application/octet-stream; charset=binary'),
('Content-Disposition', 'attachment; filename="' + backup_db + '.dump"')],
{'fileToken': token}
)
except xmlrpclib.Fault, e:
if e.faultCode and e.faultCode.split(':')[0] == 'AccessDenied':
return 'Backup Database|' + e.faultCode
@ -974,9 +970,9 @@ class Binary(openerpweb.Controller):
res = Model.read([int(id)], [field], context)[0].get(field, '')
return base64.decodestring(res)
except: # TODO: what's the exception here?
return self.placeholder()
def placeholder(self):
return open(os.path.join(openerpweb.path_addons, 'base', 'static', 'src', 'img', 'placeholder.png'), 'rb').read()
return self.placeholder(req)
def placeholder(self, req):
return open(os.path.join(req.addons_path, 'base', 'static', 'src', 'img', 'placeholder.png'), 'rb').read()
@openerpweb.httprequest
def saveas(self, req, model, id, field, fieldname, **kw):
@ -985,18 +981,17 @@ class Binary(openerpweb.Controller):
res = Model.read([int(id)], [field, fieldname], context)[0]
filecontent = res.get(field, '')
if not filecontent:
raise cherrypy.NotFound
return req.not_found()
else:
req.httpresponse.headers['Content-Type'] = 'application/octet-stream'
filename = '%s_%s' % (model.replace('.', '_'), id)
if fieldname:
filename = res.get(fieldname, '') or filename
req.httpresponse.headers['Content-Disposition'] = 'attachment; filename=' + filename
return base64.decodestring(filecontent)
return req.make_response(filecontent,
[('Content-Type', 'application/octet-stream'),
('Content-Disposition', 'attachment; filename=' + filename)])
@openerpweb.httprequest
def upload(self, req, callback, ufile=None):
cherrypy.response.timeout = 500
headers = {}
for key, val in req.httprequest.headers.iteritems():
headers[key.lower()] = val
@ -1023,7 +1018,6 @@ class Binary(openerpweb.Controller):
@openerpweb.httprequest
def upload_attachment(self, req, callback, model, id, ufile=None):
cherrypy.response.timeout = 500
context = req.session.eval_context(req.context)
Model = req.session.model('ir.attachment')
try:

View File

@ -1,9 +1,7 @@
# -*- coding: utf-8 -*-
import sys, time
import cherrypy
import time
import simplejson
import random
import base.common as openerpweb
#----------------------------------------------------------
@ -38,7 +36,7 @@ class PollServerMessageQueue(object):
if msg['to'] == recipient:
return self.messages
def gc():
def gc(self):
# remove message older than 300s from self.l
# remove dead users from self.users
pass
@ -72,7 +70,6 @@ class PollServer(openerpweb.Controller):
#r = 'logged in'
#u = generate random.randint(0,2**32)
#s = cherrypy cookie id
#f = mq.userlist()
# username = 'Guest'+ str(random.randint(0, 2**32))
@ -132,8 +129,6 @@ class PollServer(openerpweb.Controller):
msg = '[]'
cherrypy.response.headers['content-type'] = 'application/javascript';
for i in range(5):
received_msg = mq.read('Guest1', i)
if received_msg:

View File

@ -1 +0,0 @@
import main

View File

@ -1,12 +0,0 @@
# -*- coding: utf-8 -*-
#import glob, os
#import pprint
#
#import simplejson
#
#import openerpweb
#import openerpweb.ast
#import openerpweb.nonliterals
#
#import cherrypy
#

View File

@ -338,10 +338,10 @@ replace ``addons`` by the directory in which your own addon lives.
Python
++++++
.. autoclass:: openerpweb.openerpweb.OpenERPSession
.. autoclass:: base.common.backendrpc.OpenERPSession
:members:
.. autoclass:: openerpweb.openerpweb.OpenERPModel
.. autoclass:: base.common.backendrpc.OpenERPModel
:members:
* Addons lifecycle (loading, execution, events, ...)

View File

@ -376,7 +376,7 @@ entirely.
.. note::
the list-wise deletion button (next to the record addition button)
the list-wise deletion button (next to the record addition button)
simply proxies to :js:func:`~openerp.base.ListView.do_delete` after
obtaining all selected record ids, but it is possible to override it
alone by replacing
@ -391,11 +391,12 @@ Python
These classes should be moved to other sections of the doc as needed,
probably.
.. automodule:: openerpweb.openerpweb
:members: JsonRequest
.. automodule:: base.common.dispatch
:members:
:undoc-members:
See also: :class:`~openerpweb.openerpweb.OpenERPSession`,
:class:`~openerpweb.openerpweb.OpenERPModel`
See also: :class:`~base.common.backendrpc.OpenERPSession`,
:class:`~base.common.backendrpc.OpenERPModel`
.. automodule:: base.controllers.main
:members:

View File

@ -1,58 +1,43 @@
#!/usr/bin/env python
import optparse,os,sys,tempfile
import optparse
import os
import sys
import tempfile
import cherrypy
import cherrypy.lib.static
optparser = optparse.OptionParser()
optparser.add_option("-p", "--port", dest="server.socket_port", default=8002,
help="listening port", type="int", metavar="NUMBER")
optparser.add_option("-s", "--session-path", dest="tools.sessions.storage_path",
default=os.path.join(tempfile.gettempdir(), "cpsessions"),
help="directory used for session storage", metavar="DIR")
optparser.add_option("--server-host", dest="openerp.server.host",
default='127.0.0.1', help="OpenERP server hostname", metavar="HOST")
optparser.add_option("--server-port", dest="openerp.server.port", default=8069,
help="OpenERP server port", type="int", metavar="NUMBER")
optparser.add_option("--db-filter", dest="openerp.dbfilter", default='.*',
help="Filter listed database", metavar="REGEXP")
import werkzeug.serving
path_root = os.path.dirname(os.path.abspath(__file__))
path_addons = os.path.join(path_root, 'addons')
if path_addons not in sys.path:
sys.path.insert(0, path_addons)
import base
optparser = optparse.OptionParser()
optparser.add_option("-p", "--port", dest="socket_port", default=8002,
help="listening port", type="int", metavar="NUMBER")
optparser.add_option("-s", "--session-path", dest="session_storage",
default=os.path.join(tempfile.gettempdir(), "oe-sessions"),
help="directory used for session storage", metavar="DIR")
optparser.add_option("--server-host", dest="server_host",
default='127.0.0.1', help="OpenERP server hostname", metavar="HOST")
optparser.add_option("--server-port", dest="server_port", default=8069,
help="OpenERP server port", type="int", metavar="NUMBER")
optparser.add_option("--db-filter", dest="dbfilter", default='.*',
help="Filter listed database", metavar="REGEXP")
optparser.add_option('--addons-path', dest='addons_path', default=path_addons,
help="Path do addons directory", metavar="PATH")
optparser.add_option('--no-serve-static', dest='serve_static',
default=True, action='store_false',
help="Do not serve static files via this server")
def main(options):
# change the timezone of the program to the OpenERP server's assumed timezone
os.environ["TZ"] = "UTC"
DEFAULT_CONFIG = {
'server.socket_host': '0.0.0.0',
'tools.sessions.on': True,
'tools.sessions.storage_type': 'file',
'tools.sessions.timeout': 60
}
cherrypy.config.update(config=DEFAULT_CONFIG)
if os.path.exists(os.path.join(path_root,'openerp-web.cfg')):
cherrypy.config.update(os.path.join(path_root,'openerp-web.cfg'))
if os.path.exists(os.path.expanduser('~/.openerp_webrc')):
cherrypy.config.update(os.path.expanduser('~/.openerp_webrc'))
cherrypy.config.update(options)
if not os.path.exists(cherrypy.config['tools.sessions.storage_path']):
os.makedirs(cherrypy.config['tools.sessions.storage_path'], 0700)
return base.common.Root()
import base.common.dispatch
if __name__ == "__main__":
(o, args) = optparser.parse_args(sys.argv[1:])
o = dict((k, v) for k, v in vars(o).iteritems() if v is not None)
(options, args) = optparser.parse_args(sys.argv[1:])
cherrypy.tree.mount(main(o))
cherrypy.server.subscribe()
cherrypy.engine.start()
cherrypy.engine.block()
os.environ["TZ"] = "UTC"
app = base.common.dispatch.Root(options)
werkzeug.serving.run_simple(
'0.0.0.0', options.socket_port, app,
use_reloader=True, threaded=True)

View File

@ -54,11 +54,11 @@ setup(
download_url=download_url,
license=license,
install_requires=[
"CherryPy >= 3.1.2",
"Babel >= 0.9.6",
"simplejson >= 2.0.9",
"python-dateutil >= 1.4.1",
"pytz",
"werkzeug = 0.7",
],
tests_require=[
'unittest2',