~openerp-dev/openerp-web/trunk-customfilter-jir

« back to all changes in this revision

Viewing changes to addons/web/controllers/main.py

  • Committer: Vidhin Mehta (OpenERP)
  • Date: 2014-04-21 05:26:17 UTC
  • mfrom: (3747.2.239 trunk)
  • Revision ID: vme@tinyerp.com-20140421052617-spns3fo5ryybbwhx
[MERGE]Trunk.

Show diffs side-by-side

added added

removed removed

Lines of Context:
3
3
import ast
4
4
import base64
5
5
import csv
 
6
import functools
6
7
import glob
7
8
import itertools
 
9
import jinja2
8
10
import logging
9
11
import operator
10
12
import datetime
12
14
import os
13
15
import re
14
16
import simplejson
 
17
import sys
15
18
import time
16
 
import urllib
17
19
import urllib2
18
 
import urlparse
19
 
import xmlrpclib
20
20
import zlib
21
21
from xml.etree import ElementTree
22
22
from cStringIO import StringIO
32
32
import openerp
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
37
36
 
38
 
from openerp.http import request
 
37
from openerp.http import request, serialize_exception as _serialize_exception
 
38
 
 
39
_logger = logging.getLogger(__name__)
 
40
 
 
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)
 
45
else:
 
46
    loader = jinja2.PackageLoader('openerp.addons.web', "views")
 
47
 
 
48
env = jinja2.Environment(loader=loader, autoescape=True)
 
49
env.filters["json"] = simplejson.dumps
39
50
 
40
51
#----------------------------------------------------------
41
52
# OpenERP Web helpers
89
100
 
90
101
db_monodb = http.db_monodb
91
102
 
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):
 
104
    @functools.wraps(f)
 
105
    def wrap(*args, **kwargs):
95
106
        try:
96
 
            version = float(request.httprequest.user_agent.version)
97
 
            if version < 10:
98
 
                return redirect_code
99
 
        except Exception:
100
 
            pass
101
 
    elif request.httprequest.user_agent.browser == 'safari':
102
 
        return redirect_code
103
 
    return werkzeug.utils.redirect(url, code)
 
107
            return f(*args, **kwargs)
 
108
        except Exception, e:
 
109
            _logger.exception("An exception occured during an http request")
 
110
            se = _serialize_exception(e)
 
111
            error = {
 
112
                'code': 200,
 
113
                'message': "OpenERP Server Error",
 
114
                'data': se
 
115
            }
 
116
            return werkzeug.exceptions.InternalServerError(simplejson.dumps(error))
 
117
    return wrap
 
118
 
 
119
def redirect_with_hash(*args, **kw):
 
120
    """
 
121
        .. deprecated:: 8.0
 
122
 
 
123
        Use the ``http.redirect_with_hash()`` function instead.
 
124
    """
 
125
    return http.redirect_with_hash(*args, **kw)
 
126
 
 
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)
 
132
 
 
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')
 
142
 
 
143
    # Ensure db is legit
 
144
    if db and db not in http.db_filter([db]):
 
145
        db = None
 
146
 
 
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
 
156
        if r.query_string:
 
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)
 
163
 
 
164
    # if db not provided, use the session one
 
165
    if not db:
 
166
        db = request.session.db
 
167
 
 
168
    # if no database provided and no database in session, use monodb
 
169
    if not db:
 
170
        db = db_monodb(request.httprequest)
 
171
 
 
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
 
174
    if not db:
 
175
        werkzeug.exceptions.abort(werkzeug.utils.redirect(redirect, 303))
 
176
 
 
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)
 
181
 
 
182
    request.session.db = db
104
183
 
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))
293
372
            else:
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":
 
377
                        if include_remotes:
 
378
                            r.insert(0, (None, fs2web(path[len(addons_path):])))
 
379
                    else:
 
380
                        r.append((path, fs2web(path[len(addons_path):])))
296
381
    return r
297
382
 
298
383
def manifest_list(extension, mods=None, db=None, debug=False):
304
389
    if not debug:
305
390
        path = '/web/webclient/' + extension
306
391
        if mods is not None:
307
 
            path += '?' + urllib.urlencode({'mods': mods})
 
392
            path += '?' + werkzeug.url_encode({'mods': mods})
308
393
        elif db:
309
 
            path += '?' + urllib.urlencode({'db': db})
 
394
            path += '?' + werkzeug.url_encode({'db': db})
310
395
 
311
396
        remotes = [wp for fp, wp in files if fp is None]
312
397
        return [path] + remotes
357
442
    redirect.autocorrect_location_header = False
358
443
    return redirect
359
444
 
 
445
def login_redirect():
 
446
    url = '/web/login?'
 
447
    if request.debug:
 
448
        url += 'debug&'
 
449
    return """<html><head><script>
 
450
        window.location = '%sredirect=' + encodeURIComponent(window.location);
 
451
    </script></head></html>
 
452
    """ % (url,)
 
453
 
360
454
def load_actions_from_ir_values(key, key2, models, meta):
361
455
    Values = request.session.model('ir.values')
362
456
    actions = Values.get(key, key2, models, meta, request.context)
499
593
# OpenERP Web web Controllers
500
594
#----------------------------------------------------------
501
595
 
 
596
# TODO: to remove once the database manager has been migrated server side
 
597
#       and `edi` + `pos` addons has been adapted to use render_bootstrap_template()
502
598
html_template = """<!DOCTYPE html>
503
599
<html style="height: 100%%">
504
600
    <head>
525
621
</html>
526
622
"""
527
623
 
 
624
def render_bootstrap_template(template, values=None, debug=False, db=None, **kw):
 
625
    if not db:
 
626
        db = request.db
 
627
    if request.debug:
 
628
        debug = True
 
629
    if values is None:
 
630
        values = {}
 
631
    values['debug'] = debug
 
632
    values['current_db'] = db
 
633
    try:
 
634
        values['databases'] = http.db_list()
 
635
    except openerp.exceptions.AccessDenied:
 
636
        values['databases'] = None
 
637
 
 
638
    for res in ['js', 'css']:
 
639
        if res not in values:
 
640
            values[res] = manifest_list(res, db=db, debug=debug)
 
641
 
 
642
    if 'modules' not in values:
 
643
        values['modules'] = module_boot(db=db)
 
644
    values['modules'] = simplejson.dumps(values['modules'])
 
645
 
 
646
    return request.render(template, values, **kw)
 
647
 
528
648
class Home(http.Controller):
529
649
 
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)
535
653
 
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
539
 
 
540
 
        lst = http.db_list(True)
541
 
        if db not in lst:
542
 
            db = None
543
 
        guessed_db = http.db_monodb(request.httprequest)
544
 
        if guessed_db is None and len(lst) > 0:
545
 
            guessed_db = lst[0]
546
 
 
547
 
        def redirect(db):
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)
552
 
 
553
 
        if db is None and guessed_db is not None:
554
 
            return redirect(guessed_db)
555
 
 
556
 
        if db is not None and db != request.session.db:
557
 
            request.session.logout()
558
 
            request.session.db = db
559
 
            guessed_db = db
560
 
 
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))
563
 
 
564
 
        r = html_template % {
565
 
            'js': js,
566
 
            'css': css,
567
 
            'modules': simplejson.dumps(module_boot(db=guessed_db)),
568
 
            'init': 'var wc = new s.web.WebClient();wc.appendTo($(document.body));'
569
 
        }
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):
 
656
        ensure_db()
 
657
 
 
658
        if request.session.uid:
 
659
            if kw.get('redirect'):
 
660
                return werkzeug.utils.redirect(kw.get('redirect'), 303)
 
661
 
 
662
            headers = {
 
663
                'Cache-Control': 'no-cache',
 
664
                'Content-Type': 'text/html; charset=utf-8',
 
665
            }
 
666
            return render_bootstrap_template("web.webclient_bootstrap", headers=headers)
 
667
        else:
 
668
            return login_redirect()
 
669
 
 
670
    @http.route('/web/login', type='http', auth="none")
 
671
    def web_login(self, redirect=None, **kw):
 
672
        ensure_db()
 
673
 
 
674
        if request.httprequest.method == 'GET' and redirect and request.session.uid:
 
675
            return http.redirect_with_hash(redirect)
 
676
 
 
677
        if not request.uid:
 
678
            request.uid = openerp.SUPERUSER_ID
 
679
 
 
680
        values = request.params.copy()
 
681
        if not redirect:
 
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'])
 
686
            if uid is not False:
 
687
                return http.redirect_with_hash(redirect)
 
688
            values['error'] = "Wrong login/password"
 
689
        return render_bootstrap_template('web.login', values)
571
690
 
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)
575
694
 
576
695
class WebClient(http.Controller):
577
696
 
687
806
        return {"modules": translations_per_module,
688
807
                "lang_parameters": None}
689
808
 
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
692
813
        if mods is None:
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'])]
696
817
        if lang is None:
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
701
822
        if ids:
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"])
704
825
 
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')
741
862
 
742
863
class Database(http.Controller):
743
864
 
 
865
    @http.route('/web/database/selector', type='http', auth="none")
 
866
    def selector(self, **kw):
 
867
        try:
 
868
            dbs = http.db_list()
 
869
            if not dbs:
 
870
                return http.local_redirect('/web/database/manager')
 
871
        except openerp.exceptions.AccessDenied:
 
872
            dbs = False
 
873
        return env.get_template("database_selector.html").render({
 
874
            'databases': dbs,
 
875
            'debug': request.debug,
 
876
        })
 
877
 
 
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))
 
884
 
 
885
        r = html_template % {
 
886
            'js': js,
 
887
            'css': css,
 
888
            'modules': simplejson.dumps(module_boot()),
 
889
            'init': """
 
890
                var wc = new s.web.WebClient(null, { action: 'database_manager' });
 
891
                wc.appendTo($(document.body));
 
892
            """
 
893
        }
 
894
        return r
 
895
 
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'])
 
916
        if db_created:
 
917
            request.session.authenticate(params['db_name'], 'admin', params['create_admin_pwd'])
 
918
        return db_created
764
919
 
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')}]])
809
964
 
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):
812
967
        try:
 
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)
815
971
            return ''
816
972
        except openerp.exceptions.AccessDenied, e:
817
973
            raise Exception("AccessDenied")
892
1048
        :return: A key identifying the saved action.
893
1049
        :rtype: integer
894
1050
        """
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
905
 
        return key
 
1051
        return request.httpsession.save_action(the_action)
906
1052
 
907
1053
    @http.route('/web/session/get_session_action', type='json', auth="user")
908
1054
    def get_session_action(self, key):
915
1061
        :return: The saved action or None.
916
1062
        :rtype: anything
917
1063
        """
918
 
        saved_actions = request.httpsession.get('saved_actions')
919
 
        if not saved_actions:
920
 
            return None
921
 
        return saved_actions["actions"].get(key)
 
1064
        return request.httpsession.get_action(key)
922
1065
 
923
1066
    @http.route('/web/session/check', type='json', auth="user")
924
1067
    def check(self):
929
1072
    def destroy(self):
930
1073
        request.session.logout()
931
1074
 
932
 
    @http.route('/web/session/logout', type='http', auth="user")
 
1075
    @http.route('/web/session/logout', type='http', auth="none")
933
1076
    def logout(self, redirect='/web'):
934
 
        request.session.logout()
 
1077
        request.session.logout(keep_db=True)
935
1078
        return werkzeug.utils.redirect(redirect, 303)
936
1079
 
937
1080
class Menu(http.Controller):
1042
1185
        """
1043
1186
        Model = request.session.model(model)
1044
1187
 
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:
 
1190
        if not records:
 
1191
            return {
 
1192
                'length': 0,
 
1193
                'records': []
 
1194
            }
 
1195
        if limit and len(records) == limit:
1048
1196
            length = Model.search_count(domain, request.context)
1049
1197
        else:
1050
 
            length = len(ids) + (offset or 0)
1051
 
        if fields and fields == ['id']:
1052
 
            # shortcut read if we only want the ids
1053
 
            return {
1054
 
                'length': length,
1055
 
                'records': [{'id': id} for id in ids]
1056
 
            }
1057
 
 
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)
1060
1199
        return {
1061
1200
            'length': length,
1062
1201
            'records': records
1076
1215
 
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))
 
1222
                else:
 
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']))
1224
1366
        return open(os.path.join(addons_path, 'web', 'static', 'src', 'img', image), 'rb').read()
1225
1367
 
1226
1368
    @http.route('/web/binary/saveas', type='http', auth="user")
 
1369
    @serialize_exception
1227
1370
    def saveas(self, model, field, id=None, filename_field=None, **kw):
1228
1371
        """ Download link for files stored as binary fields.
1229
1372
 
1257
1400
                 ('Content-Disposition', content_disposition(filename))])
1258
1401
 
1259
1402
    @http.route('/web/binary/saveas_ajax', type='http', auth="user")
 
1403
    @serialize_exception
1260
1404
    def saveas_ajax(self, data, token):
1261
1405
        jdata = simplejson.loads(data)
1262
1406
        model = jdata['model']
1290
1434
                cookies={'fileToken': token})
1291
1435
 
1292
1436
    @http.route('/web/binary/upload', type='http', auth="user")
 
1437
    @serialize_exception
1293
1438
    def upload(self, callback, ufile):
1294
1439
        # TODO: might be useful to have a configuration flag for max-length file uploads
1295
1440
        out = """<script language="javascript" type="text/javascript">
1305
1450
        return out % (simplejson.dumps(callback), simplejson.dumps(args))
1306
1451
 
1307
1452
    @http.route('/web/binary/upload_attachment', type='http', auth="user")
 
1453
    @serialize_exception
1308
1454
    def upload_attachment(self, callback, model, id, ufile):
1309
1455
        Model = request.session.model('ir.attachment')
1310
1456
        out = """<script language="javascript" type="text/javascript">
1327
1473
            args = {'error': "Something horrible happened"}
1328
1474
        return out % (simplejson.dumps(callback), simplejson.dumps(args))
1329
1475
 
1330
 
    @http.route('/web/binary/company_logo', type='http', auth="none")
 
1476
    @http.route([
 
1477
        '/web/binary/company_logo',
 
1478
        '/logo',
 
1479
        '/logo.png',
 
1480
    ], type='http', auth="none")
1331
1481
    def company_logo(self, dbname=None):
1332
1482
        # TODO add etag, refactor to use /image code for etag
1333
1483
        uid = None
1533
1683
                    fields[base]['relation'], base, fields[base]['string'],
1534
1684
                    subfields
1535
1685
                ))
1536
 
            else:
 
1686
            elif base in fields:
1537
1687
                info[base] = fields[base]['string']
1538
1688
 
1539
1689
        return info
1545
1695
            for k, v in self.fields_info(model, export_fields).iteritems())
1546
1696
 
1547
1697
class ExportFormat(object):
 
1698
    raw_data = False
 
1699
 
1548
1700
    @property
1549
1701
    def content_type(self):
1550
1702
        """ Provides the format's content type """
1577
1729
        ids = ids or Model.search(domain, 0, False, False, request.context)
1578
1730
 
1579
1731
        field_names = map(operator.itemgetter('name'), fields)
1580
 
        import_data = Model.export_data(ids, field_names, request.context).get('datas',[])
 
1732
        import_data = Model.export_data(ids, field_names, self.raw_data, context=request.context).get('datas',[])
1581
1733
 
1582
1734
        if import_compat:
1583
1735
            columns_headers = field_names
1594
1746
class CSVExport(ExportFormat, http.Controller):
1595
1747
 
1596
1748
    @http.route('/web/export/csv', type='http', auth="user")
 
1749
    @serialize_exception
1597
1750
    def index(self, data, token):
1598
1751
        return self.base(data, token)
1599
1752
 
1629
1782
        return data
1630
1783
 
1631
1784
class ExcelExport(ExportFormat, http.Controller):
 
1785
    # Excel needs raw data to correctly handle numbers and date values
 
1786
    raw_data = True
1632
1787
 
1633
1788
    @http.route('/web/export/xls', type='http', auth="user")
 
1789
    @serialize_exception
1634
1790
    def index(self, data, token):
1635
1791
        return self.base(data, token)
1636
1792
 
1649
1805
            worksheet.write(0, i, fieldname)
1650
1806
            worksheet.col(i).width = 8000 # around 220 pixels
1651
1807
 
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')
1653
1811
 
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)
1660
1822
 
1661
1823
        fp = StringIO()
1662
1824
        workbook.save(fp)
1677
1839
    }
1678
1840
 
1679
1841
    @http.route('/web/report', type='http', auth="user")
 
1842
    @serialize_exception
1680
1843
    def index(self, action, token):
1681
1844
        action = simplejson.loads(action)
1682
1845
 
1730
1893
                 ('Content-Length', len(report))],
1731
1894
             cookies={'fileToken': token})
1732
1895
 
 
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')
 
1901
        try:
 
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'
 
1905
        except ValueError:
 
1906
            action = False
 
1907
        try:
 
1908
            app_id = ir_model_data.get_object_reference('base', 'module_%s' % app)[1]
 
1909
        except ValueError:
 
1910
            app_id = False
 
1911
 
 
1912
        if action and app_id:
 
1913
            action['res_id'] = app_id
 
1914
            action['view_mode'] = 'form'
 
1915
            action['views'] = [(False, u'form')]
 
1916
 
 
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))
 
1920
        
 
1921
 
 
1922
 
1733
1923
# vim:expandtab:tabstop=4:softtabstop=4:shiftwidth=4: