[IMP] Werkzeug everything
bzr revid: xmo@openerp.com-20110902145204-p1vwgwoy9no4tqx6
This commit is contained in:
commit
a022e94b94
|
@ -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
|
||||
|
|
|
@ -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)
|
|
@ -1 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
|
@ -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()
|
|
@ -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"
|
||||
)
|
|
@ -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')]
|
||||
)
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
import main
|
|
@ -1,12 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#import glob, os
|
||||
#import pprint
|
||||
#
|
||||
#import simplejson
|
||||
#
|
||||
#import openerpweb
|
||||
#import openerpweb.ast
|
||||
#import openerpweb.nonliterals
|
||||
#
|
||||
#import cherrypy
|
||||
#
|
|
@ -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, ...)
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
Loading…
Reference in New Issue