33
33
import openerp.modules.registry
34
34
from openerp.tools.translate import _
35
from openerp.tools import config
36
35
from openerp import http
38
from openerp.http import request
37
from openerp.http import request, serialize_exception as _serialize_exception
39
_logger = logging.getLogger(__name__)
41
if hasattr(sys, 'frozen'):
42
# When running on compiled windows binary, we don't have access to package loader.
43
path = os.path.realpath(os.path.join(os.path.dirname(__file__), '..', 'views'))
44
loader = jinja2.FileSystemLoader(path)
46
loader = jinja2.PackageLoader('openerp.addons.web', "views")
48
env = jinja2.Environment(loader=loader, autoescape=True)
49
env.filters["json"] = simplejson.dumps
40
51
#----------------------------------------------------------
41
52
# OpenERP Web helpers
90
101
db_monodb = http.db_monodb
92
def redirect_with_hash(url, code=303):
93
redirect_code = "<html><head><script>window.location = '%s' + location.hash;</script></head></html>" % url
94
if request.httprequest.user_agent.browser == 'msie':
103
def serialize_exception(f):
105
def wrap(*args, **kwargs):
96
version = float(request.httprequest.user_agent.version)
101
elif request.httprequest.user_agent.browser == 'safari':
103
return werkzeug.utils.redirect(url, code)
107
return f(*args, **kwargs)
109
_logger.exception("An exception occured during an http request")
110
se = _serialize_exception(e)
113
'message': "OpenERP Server Error",
116
return werkzeug.exceptions.InternalServerError(simplejson.dumps(error))
119
def redirect_with_hash(*args, **kw):
123
Use the ``http.redirect_with_hash()`` function instead.
125
return http.redirect_with_hash(*args, **kw)
127
def abort_and_redirect(url):
128
r = request.httprequest
129
response = werkzeug.utils.redirect(url, 302)
130
response = r.app.get_response(r, response, explicit_session=False)
131
werkzeug.exceptions.abort(response)
133
def ensure_db(redirect='/web/database/selector'):
134
# This helper should be used in web client auth="none" routes
135
# if those routes needs a db to work with.
136
# If the heuristics does not find any database, then the users will be
137
# redirected to db selector or any url specified by `redirect` argument.
138
# If the db is taken out of a query parameter, it will be checked against
139
# `http.db_filter()` in order to ensure it's legit and thus avoid db
140
# forgering that could lead to xss attacks.
141
db = request.params.get('db')
144
if db and db not in http.db_filter([db]):
147
if db and not request.session.db:
148
# User asked a specific database on a new session.
149
# That mean the nodb router has been used to find the route
150
# Depending on installed module in the database, the rendering of the page
151
# may depend on data injected by the database route dispatcher.
152
# Thus, we redirect the user to the same page but with the session cookie set.
153
# This will force using the database route dispatcher...
154
r = request.httprequest
155
url_redirect = r.base_url
157
# Can't use werkzeug.wrappers.BaseRequest.url with encoded hashes:
158
# https://github.com/amigrave/werkzeug/commit/b4a62433f2f7678c234cdcac6247a869f90a7eb7
159
url_redirect += '?' + r.query_string
160
response = werkzeug.utils.redirect(url_redirect, 302)
161
request.session.db = db
162
abort_and_redirect(url_redirect)
164
# if db not provided, use the session one
166
db = request.session.db
168
# if no database provided and no database in session, use monodb
170
db = db_monodb(request.httprequest)
172
# if no db can be found til here, send to the database selector
173
# the database selector will redirect to database manager if needed
175
werkzeug.exceptions.abort(werkzeug.utils.redirect(redirect, 303))
177
# always switch the session to the computed db
178
if db != request.session.db:
179
request.session.logout()
180
abort_and_redirect(request.httprequest.url)
182
request.session.db = db
105
184
def module_topological_sort(modules):
106
185
""" Return a list of module names sorted so that their dependencies of the
292
371
r.append((None, pattern))
294
373
for path in glob.glob(os.path.normpath(os.path.join(addons_path, addon, pattern))):
295
r.append((path, fs2web(path[len(addons_path):])))
374
# Hack for IE, who limit 288Ko, 4095 rules, 31 sheets
375
# http://support.microsoft.com/kb/262161/en
376
if pattern == "static/lib/bootstrap/css/bootstrap.css":
378
r.insert(0, (None, fs2web(path[len(addons_path):])))
380
r.append((path, fs2web(path[len(addons_path):])))
298
383
def manifest_list(extension, mods=None, db=None, debug=False):
624
def render_bootstrap_template(template, values=None, debug=False, db=None, **kw):
631
values['debug'] = debug
632
values['current_db'] = db
634
values['databases'] = http.db_list()
635
except openerp.exceptions.AccessDenied:
636
values['databases'] = None
638
for res in ['js', 'css']:
639
if res not in values:
640
values[res] = manifest_list(res, db=db, debug=debug)
642
if 'modules' not in values:
643
values['modules'] = module_boot(db=db)
644
values['modules'] = simplejson.dumps(values['modules'])
646
return request.render(template, values, **kw)
528
648
class Home(http.Controller):
530
650
@http.route('/', type='http', auth="none")
531
def index(self, s_action=None, db=None, debug=False, **kw):
532
query = dict(urlparse.parse_qsl(request.httprequest.query_string, keep_blank_values=True))
533
redirect = '/web' + '?' + urllib.urlencode(query)
534
return redirect_with_hash(redirect)
651
def index(self, s_action=None, db=None, **kw):
652
return http.local_redirect('/web', query=request.params, keep_hash=True)
536
654
@http.route('/web', type='http', auth="none")
537
def web_client(self, s_action=None, db=None, debug=False, **kw):
538
debug = debug != False
540
lst = http.db_list(True)
543
guessed_db = http.db_monodb(request.httprequest)
544
if guessed_db is None and len(lst) > 0:
548
query = dict(urlparse.parse_qsl(request.httprequest.query_string, keep_blank_values=True))
549
query.update({'db': db})
550
redirect = request.httprequest.path + '?' + urllib.urlencode(query)
551
return redirect_with_hash(redirect)
553
if db is None and guessed_db is not None:
554
return redirect(guessed_db)
556
if db is not None and db != request.session.db:
557
request.session.logout()
558
request.session.db = db
561
js = "\n ".join('<script type="text/javascript" src="%s"></script>' % i for i in manifest_list('js', db=guessed_db, debug=debug))
562
css = "\n ".join('<link rel="stylesheet" href="%s">' % i for i in manifest_list('css', db=guessed_db, debug=debug))
564
r = html_template % {
567
'modules': simplejson.dumps(module_boot(db=guessed_db)),
568
'init': 'var wc = new s.web.WebClient();wc.appendTo($(document.body));'
570
return request.make_response(r, {'Cache-Control': 'no-cache', 'Content-Type': 'text/html; charset=utf-8'})
655
def web_client(self, s_action=None, **kw):
658
if request.session.uid:
659
if kw.get('redirect'):
660
return werkzeug.utils.redirect(kw.get('redirect'), 303)
663
'Cache-Control': 'no-cache',
664
'Content-Type': 'text/html; charset=utf-8',
666
return render_bootstrap_template("web.webclient_bootstrap", headers=headers)
668
return login_redirect()
670
@http.route('/web/login', type='http', auth="none")
671
def web_login(self, redirect=None, **kw):
674
if request.httprequest.method == 'GET' and redirect and request.session.uid:
675
return http.redirect_with_hash(redirect)
678
request.uid = openerp.SUPERUSER_ID
680
values = request.params.copy()
682
redirect = '/web?' + request.httprequest.query_string
683
values['redirect'] = redirect
684
if request.httprequest.method == 'POST':
685
uid = request.session.authenticate(request.session.db, request.params['login'], request.params['password'])
687
return http.redirect_with_hash(redirect)
688
values['error'] = "Wrong login/password"
689
return render_bootstrap_template('web.login', values)
572
691
@http.route('/login', type='http', auth="none")
573
def login(self, db, login, key):
574
return login_and_redirect(db, login, key)
692
def login(self, db, login, key, redirect="/web", **kw):
693
return login_and_redirect(db, login, key, redirect_url=redirect)
576
695
class WebClient(http.Controller):
687
806
return {"modules": translations_per_module,
688
807
"lang_parameters": None}
690
@http.route('/web/webclient/translations', type='json', auth="admin")
809
@http.route('/web/webclient/translations', type='json', auth="none")
691
810
def translations(self, mods=None, lang=None):
811
request.disable_db = False
812
uid = openerp.SUPERUSER_ID
693
814
m = request.registry.get('ir.module.module')
694
mods = [x['name'] for x in m.search_read(request.cr, request.uid,
815
mods = [x['name'] for x in m.search_read(request.cr, uid,
695
816
[('state','=','installed')], ['name'])]
697
818
lang = request.context["lang"]
698
819
res_lang = request.registry.get('res.lang')
699
ids = res_lang.search(request.cr, request.uid, [("code", "=", lang)])
820
ids = res_lang.search(request.cr, uid, [("code", "=", lang)])
700
821
lang_params = None
702
lang_params = res_lang.read(request.cr, request.uid, ids[0], ["direction", "date_format", "time_format",
823
lang_params = res_lang.read(request.cr, uid, ids[0], ["direction", "date_format", "time_format",
703
824
"grouping", "decimal_point", "thousands_sep"])
705
826
# Regional languages (ll_CC) must inherit/override their parent lang (ll), but this is
706
827
# done server-side when the language is loaded, so we only need to load the user's lang.
707
828
ir_translation = request.registry.get('ir.translation')
708
829
translations_per_module = {}
709
messages = ir_translation.search_read(request.cr, request.uid, [('module','in',mods),('lang','=',lang),
830
messages = ir_translation.search_read(request.cr, uid, [('module','in',mods),('lang','=',lang),
710
831
('comments','like','openerp-web'),('value','!=',False),
711
832
('value','!=','')],
712
833
['module','src','value','lang'], order='module')
742
863
class Database(http.Controller):
865
@http.route('/web/database/selector', type='http', auth="none")
866
def selector(self, **kw):
870
return http.local_redirect('/web/database/manager')
871
except openerp.exceptions.AccessDenied:
873
return env.get_template("database_selector.html").render({
875
'debug': request.debug,
878
@http.route('/web/database/manager', type='http', auth="none")
879
def manager(self, **kw):
880
# TODO: migrate the webclient's database manager to server side views
881
request.session.logout()
882
js = "\n ".join('<script type="text/javascript" src="%s"></script>' % i for i in manifest_list('js', debug=request.debug))
883
css = "\n ".join('<link rel="stylesheet" href="%s">' % i for i in manifest_list('css', debug=request.debug))
885
r = html_template % {
888
'modules': simplejson.dumps(module_boot()),
890
var wc = new s.web.WebClient(null, { action: 'database_manager' });
891
wc.appendTo($(document.body));
744
896
@http.route('/web/database/get_list', type='json', auth="none")
745
897
def get_list(self):
746
898
# TODO change js to avoid calling this method if in monodb mode
755
907
@http.route('/web/database/create', type='json', auth="none")
756
908
def create(self, fields):
757
909
params = dict(map(operator.itemgetter('name', 'value'), fields))
758
return request.session.proxy("db").create_database(
910
db_created = request.session.proxy("db").create_database(
759
911
params['super_admin_pwd'],
760
912
params['db_name'],
761
913
bool(params.get('demo_data')),
762
914
params['db_lang'],
763
915
params['create_admin_pwd'])
917
request.session.authenticate(params['db_name'], 'admin', params['create_admin_pwd'])
765
920
@http.route('/web/database/duplicate', type='json', auth="none")
766
921
def duplicate(self, fields):
808
963
return simplejson.dumps([[],[{'error': openerp.tools.ustr(e), 'title': _('Backup Database')}]])
810
965
@http.route('/web/database/restore', type='http', auth="none")
811
def restore(self, db_file, restore_pwd, new_db):
966
def restore(self, db_file, restore_pwd, new_db, mode):
968
copy = mode == 'copy'
813
969
data = base64.b64encode(db_file.read())
814
request.session.proxy("db").restore(restore_pwd, new_db, data)
970
request.session.proxy("db").restore(restore_pwd, new_db, data, copy)
816
972
except openerp.exceptions.AccessDenied, e:
817
973
raise Exception("AccessDenied")
892
1048
:return: A key identifying the saved action.
895
saved_actions = request.httpsession.get('saved_actions')
896
if not saved_actions:
897
saved_actions = {"next":1, "actions":{}}
898
request.httpsession['saved_actions'] = saved_actions
899
# we don't allow more than 10 stored actions
900
if len(saved_actions["actions"]) >= 10:
901
del saved_actions["actions"][min(saved_actions["actions"])]
902
key = saved_actions["next"]
903
saved_actions["actions"][key] = the_action
904
saved_actions["next"] = key + 1
1051
return request.httpsession.save_action(the_action)
907
1053
@http.route('/web/session/get_session_action', type='json', auth="user")
908
1054
def get_session_action(self, key):
1043
1186
Model = request.session.model(model)
1045
ids = Model.search(domain, offset or 0, limit or False, sort or False,
1188
records = Model.search_read(domain, fields, offset or 0, limit or False, sort or False,
1046
1189
request.context)
1047
if limit and len(ids) == limit:
1195
if limit and len(records) == limit:
1048
1196
length = Model.search_count(domain, request.context)
1050
length = len(ids) + (offset or 0)
1051
if fields and fields == ['id']:
1052
# shortcut read if we only want the ids
1055
'records': [{'id': id} for id in ids]
1058
records = Model.read(ids, fields or False, request.context)
1059
records.sort(key=lambda obj: ids.index(obj['id']))
1198
length = len(records) + (offset or 0)
1061
1200
'length': length,
1062
1201
'records': records
1077
1216
def _call_kw(self, model, method, args, kwargs):
1078
1217
# Temporary implements future display_name special field for model#read()
1079
if method == 'read' and kwargs.get('context', {}).get('future_display_name'):
1218
if method in ('read', 'search_read') and kwargs.get('context', {}).get('future_display_name'):
1080
1219
if 'display_name' in args[1]:
1081
names = dict(request.session.model(model).name_get(args[0], **kwargs))
1220
if method == 'read':
1221
names = dict(request.session.model(model).name_get(args[0], **kwargs))
1223
names = dict(request.session.model(model).name_search('', args[0], **kwargs))
1082
1224
args[1].remove('display_name')
1083
records = request.session.model(model).read(*args, **kwargs)
1225
records = getattr(request.session.model(model), method)(*args, **kwargs)
1084
1226
for record in records:
1085
1227
record['display_name'] = \
1086
1228
names.get(record['id']) or "%s#%d" % (model, (record['id']))
1649
1805
worksheet.write(0, i, fieldname)
1650
1806
worksheet.col(i).width = 8000 # around 220 pixels
1652
style = xlwt.easyxf('align: wrap yes')
1808
base_style = xlwt.easyxf('align: wrap yes')
1809
date_style = xlwt.easyxf('align: wrap yes', num_format_str='YYYY-MM-DD')
1810
datetime_style = xlwt.easyxf('align: wrap yes', num_format_str='YYYY-MM-DD HH:mm:SS')
1654
1812
for row_index, row in enumerate(rows):
1655
1813
for cell_index, cell_value in enumerate(row):
1814
cell_style = base_style
1656
1815
if isinstance(cell_value, basestring):
1657
1816
cell_value = re.sub("\r", " ", cell_value)
1658
if cell_value is False: cell_value = None
1659
worksheet.write(row_index + 1, cell_index, cell_value, style)
1817
elif isinstance(cell_value, datetime.datetime):
1818
cell_style = datetime_style
1819
elif isinstance(cell_value, datetime.date):
1820
cell_style = date_style
1821
worksheet.write(row_index + 1, cell_index, cell_value, cell_style)
1661
1823
fp = StringIO()
1662
1824
workbook.save(fp)
1730
1893
('Content-Length', len(report))],
1731
1894
cookies={'fileToken': token})
1896
class Apps(http.Controller):
1897
@http.route('/apps/<app>', auth='user')
1898
def get_app_url(self, req, app):
1899
act_window_obj = request.session.model('ir.actions.act_window')
1900
ir_model_data = request.session.model('ir.model.data')
1902
action_id = ir_model_data.get_object_reference('base', 'open_module_tree')[1]
1903
action = act_window_obj.read(action_id, ['name', 'type', 'res_model', 'view_mode', 'view_type', 'context', 'views', 'domain'])
1904
action['target'] = 'current'
1908
app_id = ir_model_data.get_object_reference('base', 'module_%s' % app)[1]
1912
if action and app_id:
1913
action['res_id'] = app_id
1914
action['view_mode'] = 'form'
1915
action['views'] = [(False, u'form')]
1917
sakey = Session().save_session_action(action)
1918
debug = '?debug' if req.debug else ''
1919
return werkzeug.utils.redirect('/web{0}#sa={1}'.format(debug, sakey))
1733
1923
# vim:expandtab:tabstop=4:softtabstop=4:shiftwidth=4: